fabro/lib/components/fabro-agent/src/lib.rs
Bryan Helmkamp 579f3db26f
Add a cached GitHub installation-token source for push credentials
Every push previously re-minted a fresh GitHub App installation token and
embedded it in the origin URL, so pushes routinely landed inside GitHub's
token-replication lag window (run 01M0DH033P2XSTHAGVBHG6922F failed
terminally on four consecutive fresh-token 404s). Reusing mature tokens
removes the failure trigger and saves two GitHub API calls plus one sandbox
exec per push.

- New fabro_github::token_source::InstallationTokenSource: one cached,
  single-flight source per origin repo. Static credentials pass through
  (generation 0); App credentials mint through the cache and reuse tokens
  until REFRESH_MARGIN (10 min) before expiry. Every resolve returns a
  non-secret TokenSnapshot (generation + Minted/Reused/Static provenance),
  and the source logs mints at INFO and reuses at DEBUG.
- Docker and Daytona share the source through PushCredentialState: an embed
  mutex serializes compare -> set-url -> record, a matching generation skips
  the set-url exec, and the generation is recorded only after a successful
  exec. The clone still mints its own token, but now seeds the source cache
  (generation 1) and the last-embedded state, so a refresh mint failure
  falls back to the known embedded token instead of believing nothing was
  ever embedded.
- RefreshOutcome now reports the remote action (embedded/unchanged/none)
  separately from the token snapshot; git_push_via_exec logs token age and
  provenance with each push, and refresh failures log the last embedded
  generation.
- The run-metadata writer resolves through the sandbox's shared source
  instead of minting per snapshot (with its own cached source on resume).
- The ACP refresh-ahead loop reschedules from the embedded token's
  expires_at minus the margin instead of a fixed 45-minute interval, which
  a cached source would have broken for long turns; static credentials stop
  the loop.

Plan: .ai/plans/git-push-token-resilience.md (PR 1: items 3 and 6).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-20 09:33:43 -04:00

94 lines
3.2 KiB
Rust

#[cfg(feature = "docker")]
pub mod docker_sandbox;
pub mod agent_profile;
pub mod apply_patch;
pub mod cli;
pub mod compaction;
pub mod config;
pub(crate) mod context_window;
pub mod error;
pub mod event;
pub mod file_tracker;
pub mod history;
pub mod local_sandbox;
pub mod loop_detection;
pub mod mcp_integration;
pub mod memory;
pub mod native_tool;
pub mod profiles;
pub mod question_tools;
pub mod sandbox;
pub mod session;
pub mod skills;
pub mod subagent;
pub(crate) mod task_reminder;
pub mod todo_runtime;
pub mod todo_tools;
pub mod tool_execution;
pub mod tool_permissions;
pub mod tool_registry;
pub mod tools;
pub mod truncation;
pub mod types;
pub use agent_profile::AgentProfile;
pub use config::{
NativeToolOptions, SessionOptions, ToolAccess, ToolAccessPolicy, ToolApprovalAdapter,
ToolExposureMode, ToolHookCallback, ToolHookDecision, ToolSecrets,
};
#[cfg(feature = "docker")]
pub use docker_sandbox::{DockerSandbox, DockerSandboxOptions};
pub use error::{CompactionError, Error, InterruptReason, Result};
pub use event::Emitter;
pub use fabro_mcp::config::McpServerSettings;
pub use fabro_types::SteeringMessage;
pub use history::History;
pub use local_sandbox::LocalSandbox;
pub use loop_detection::detect_loop;
pub use memory::{MemoryDocument, discover_memory};
pub use native_tool::{NativeTool, ToolVocabulary};
pub use profiles::{
AgentProfileBuilder, AnthropicProfile, Claude5Profile, EnvContext, GeminiProfile, KimiProfile,
OpenAiProfile,
};
pub use question_tools::{
ANTHROPIC_ASK_USER_QUESTION_TOOL, AgentQuestion, AgentQuestionAnswer,
AgentQuestionAnswerStatus, AgentQuestionRuntime, AgentToolRuntime,
OPENAI_REQUEST_USER_INPUT_TOOL, register_question_tools,
};
pub use sandbox::{
CommandOutputCallback, DirEntry, ExecResult, ExecStreamingRequest, ExecStreamingResult,
GrepOptions, RefreshOutcome, RemoteCredentialAction, Sandbox, SandboxEvent,
SandboxEventCallback, StderrCollector, StdioProcess, StdioProcessHandle, TokenProvenance,
TokenSnapshot, format_lines_numbered, shell_quote,
};
pub use session::{
CompletionCoordinator, Session, SessionControlHandle, SessionInputTiming,
SessionShutdownReason, StaticEnvProvider, SteeringItem, ToolEnvProvider,
};
pub use skills::Skill;
pub use subagent::{SubAgentEventCallback, SubAgentResult, SubAgentStatus, SubAgentSupervisor};
pub use todo_runtime::TodoRuntime;
pub use todo_tools::{
make_task_create_tool, make_task_get_tool, make_task_list_tool, make_task_update_tool,
make_todo_list_tool, make_update_plan_tool,
};
pub use tool_permissions::canonical_tool_name;
pub use tool_registry::{AgentEventEmitter, ToolRegistry};
pub use tools::{
WebFetchSummarizer, make_edit_file_tool, make_glob_tool, make_grep_tool, make_read_file_tool,
make_shell_tool, make_shell_tool_with_options, make_write_file_tool, register_core_tools,
};
pub use truncation::{TruncationMode, truncate_lines, truncate_output, truncate_tool_output};
pub use types::{
AgentEvent, McpToolSummary, MemoryFileSummary, Message, SessionEvent, SessionState,
SkillActivationSource, SkillSummary,
};
#[cfg(test)]
#[allow(
unreachable_pub,
reason = "Test support stays crate-visible for cross-module unit tests."
)]
pub(crate) mod test_support;