forge_agent/lib.rs
1//! `forge-agent` - drives one [`forge_sdk::Client`] and exposes a
2//! channel-based [`Agent`] / [`AgentHandle`] surface to UI consumers.
3//!
4//! Public API: [`Agent::spawn`] returns an [`AgentHandle`] holding
5//! `Sender<Command>`, a `take_events()` for the bridge's `AgentEvent`
6//! stream, and direct-accessor passthroughs (config_dir,
7//! settings_documents, oauth_*).
8//!
9//! `ForgeSdkBridge` is a `pub(crate)` implementation detail - Agent's
10//! dispatcher task is the only caller.
11//!
12//! # Module layout
13//!
14//! - [`agent`] - `Agent::spawn` + `AgentHandle` (the public API).
15//! - [`client`] - `AgentEvent` enum + supporting types.
16//! - [`cloud`] - network-side state: oauth + cli usage fetchers.
17//! - [`userdata`] - disk-side state: settings, sessions catalog, memory, plugins.
18//! - [`commands`] / [`session_lifecycle`] - bridge helpers consumed by
19//! the worker and the workspace.
20//! - `forge_sdk_worker` (crate-private) / `replay` / [`tooling`] /
21//! [`user_interaction`] - internal implementation modules consumed by
22//! `agent`'s dispatcher and translator paths.
23
24pub mod agent;
25pub mod client;
26pub mod cloud;
27pub mod commands;
28pub mod env;
29pub(crate) mod forge_sdk_bridge;
30pub(crate) mod forge_sdk_worker;
31pub mod http_trust;
32pub mod logging;
33pub(crate) mod replay;
34pub mod session_lifecycle;
35pub mod tooling;
36pub mod translate;
37pub mod user_interaction;
38pub mod userdata;
39
40pub use agent::{Agent, AgentError, AgentHandle};
41pub use client::{AgentEvent, SessionLaunchSettings};
42pub use forge_primitives::permission::PermissionMode;
43
44/// Point every session spawn this process makes at a mock binary, so a
45/// cross-crate fixture can drive the real spawn path against a child it
46/// controls; `None` restores the real one. Process-wide - see its own doc.
47#[cfg(any(test, feature = "testing"))]
48pub use forge_sdk_worker::set_test_sdk_binary;
49
50/// The messages a session's transcript holds and the compactions it
51/// records. The same read the spawn performs, available for a session that
52/// is already running.
53///
54/// **A view is not the only way to a running session's conversation any
55/// more.** The socket's transport holds one it is seeded with by the session
56/// task, so this read is for a consumer that has no such copy - a view
57/// attaching late, or a fixture.
58pub fn session_history(
59 config_dir: &std::path::Path,
60 session_id: &str,
61 cwd: &str,
62) -> forge_primitives::ConversationHistory {
63 forge_sdk_worker::load_history_messages(config_dir, session_id, cwd, session_id)
64}
65
66/// [`session_history`] over the session's own rows only: sub-agent sidecars
67/// are not opened or folded. The handoff brief takes this one, so it pays for
68/// neither the frames nor the parse. A page read does not: a dispatch row
69/// draws its sidecar frames (#1763).
70pub fn session_history_main_thread(
71 config_dir: &std::path::Path,
72 session_id: &str,
73 cwd: &str,
74) -> forge_primitives::ConversationHistory {
75 forge_sdk_worker::load_history_messages_main_thread(config_dir, session_id, cwd, session_id)
76}
77
78/// Whether a transcript row carries a frame, which is what tells a copy's
79/// numbering from the file's when the frames the CLI never wrote are in it.
80pub use userdata::catalog::scan::has_a_transcript_row;
81
82/// The rows of a session's transcript that sit strictly before `below`, for a
83/// paging read that has walked below the window a conversation is kept in.
84///
85/// [`session_history`] is the whole file and answers what a session IS; this
86/// is a page of it and answers what a page needs. `below` is the row the page
87/// above stopped at - the id it carries and, once a read has seen it, the byte
88/// it starts at - and `None` reads the file's own newest rows, which is the
89/// walk's first ask on a window that holds no row to name.
90pub fn transcript_rows(
91 config_dir: &std::path::Path,
92 session_id: &str,
93 cwd: &str,
94 below: Option<&forge_primitives::TranscriptAnchor>,
95 rows: usize,
96) -> Option<forge_primitives::TranscriptSpan> {
97 let dir = if cwd.is_empty() { None } else { Some(cwd) };
98 userdata::catalog::scan::read_rows(config_dir, session_id, dir, below, rows)
99}