feat(claw): run the bounded request view by default
The claw's sessions never end by design — one interactive chat, forge threads alive as long as their pull requests, coding jobs stashed across runs — so off-by-default would mean the view is never on where it matters most. On: chat turns wrap the provider seam with the view and join zoom to the resolver chain, the mirror living on the handler so it survives the per-dispatch agent rebuild; forge threads and coding jobs set the coding agent's transcript field. The config turns it off with transcript: false.
This commit is contained in:
1 parent
b70a56c325
commit
50b8feba07
10 files changed
+187
-3
No files matched your search
@@ -32,6 +32,8 @@ rhema-autonomous-agent = { path = "../rhema-autonomous-agent" }
|
|||||||
rhema-coding-agent = { path = "../rhema-coding-agent" }
|
rhema-coding-agent = { path = "../rhema-coding-agent" }
|
||||||
rhema-config = { path = "../rhema-config" }
|
rhema-config = { path = "../rhema-config" }
|
||||||
rhema-core = { path = "../rhema-core" }
|
rhema-core = { path = "../rhema-core" }
|
||||||
|
# The bounded request view over the claw sessions, on by default here
|
||||||
|
rhema-transcript = { path = "../rhema-transcript" }
|
||||||
rhema-calendar-integration = { path = "../../integrations/rhema-calendar-integration" }
|
rhema-calendar-integration = { path = "../../integrations/rhema-calendar-integration" }
|
||||||
rhema-forge-integration = { path = "../../integrations/rhema-forge-integration" }
|
rhema-forge-integration = { path = "../../integrations/rhema-forge-integration" }
|
||||||
# The accounts layer: identities hold the forge tokens the watches poll
|
# The accounts layer: identities hold the forge tokens the watches poll
|
||||||
|
|||||||
@@ -44,6 +44,16 @@ budget — in a named workspace, with one tool and an `action` argument:
|
|||||||
two agents editing the same files. Across instances, jobs run in
|
two agents editing the same files. Across instances, jobs run in
|
||||||
parallel.
|
parallel.
|
||||||
|
|
||||||
|
### The bounded request view
|
||||||
|
|
||||||
|
A claw session never ends: the interactive chat is one conversation,
|
||||||
|
forge threads live as long as their pull requests, and a coding job's
|
||||||
|
session carries to its next run. From the start, the claw therefore
|
||||||
|
runs the transcript's bounded request view (`rhema-transcript`):
|
||||||
|
past a byte budget, older messages summarize into `id+n|text` lines
|
||||||
|
the agent's `zoom` tool opens to read any original back. The config
|
||||||
|
turns it off with `transcript: false`.
|
||||||
|
|
||||||
For the debug loop, daemon plus UI against throwaway state and zero
|
For the debug loop, daemon plus UI against throwaway state and zero
|
||||||
credentials: `scripts/dev.sh --mock` (canned streamed replies; the UI
|
credentials: `scripts/dev.sh --mock` (canned streamed replies; the UI
|
||||||
is at `localhost:3000`, the claw at `127.0.0.1:7799`). Drop `--mock`
|
is at `localhost:3000`, the claw at `127.0.0.1:7799`). Drop `--mock`
|
||||||
|
|||||||
@@ -278,6 +278,13 @@ pub struct ClawConfig {
|
|||||||
pub model: Option<String>,
|
pub model: Option<String>,
|
||||||
/// The HTTP surface's `host:port`. [`DEFAULT_LISTEN`] when unset.
|
/// The HTTP surface's `host:port`. [`DEFAULT_LISTEN`] when unset.
|
||||||
pub listen: Option<String>,
|
pub listen: Option<String>,
|
||||||
|
/// The bounded request view over the claw's sessions — past a byte
|
||||||
|
/// budget, older messages summarize into lines the agent's `zoom`
|
||||||
|
/// tool can open. On when unset; `transcript: false` sends the
|
||||||
|
/// whole history every step, as the coding agent does by default.
|
||||||
|
/// The claw turns it on because its chats and coding jobs never end
|
||||||
|
/// by design: a stashed session only ever grows.
|
||||||
|
pub transcript: Option<bool>,
|
||||||
/// Bearer token for the `/v1` routes. Unset means the surface is
|
/// Bearer token for the `/v1` routes. Unset means the surface is
|
||||||
/// open on its interface; set it when the port is reachable from
|
/// open on its interface; set it when the port is reachable from
|
||||||
/// anywhere but loopback. `/healthz` stays open either way.
|
/// anywhere but loopback. `/healthz` stays open either way.
|
||||||
@@ -292,6 +299,14 @@ pub struct ClawConfig {
|
|||||||
pub calendars: Vec<CalendarWatch>,
|
pub calendars: Vec<CalendarWatch>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ClawConfig {
|
||||||
|
/// Whether the claw's sessions run the bounded request view: on
|
||||||
|
/// unless the config says `transcript: false`.
|
||||||
|
pub fn transcript_enabled(&self) -> bool {
|
||||||
|
self.transcript != Some(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Default for ClawConfig {
|
impl Default for ClawConfig {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
@@ -305,6 +320,7 @@ impl Default for ClawConfig {
|
|||||||
model: None,
|
model: None,
|
||||||
listen: None,
|
listen: None,
|
||||||
token: None,
|
token: None,
|
||||||
|
transcript: None,
|
||||||
watches: Vec::new(),
|
watches: Vec::new(),
|
||||||
calendars: Vec::new(),
|
calendars: Vec::new(),
|
||||||
}
|
}
|
||||||
@@ -568,6 +584,21 @@ impl ClawConfig {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
#[test]
|
||||||
|
fn the_transcript_is_on_unless_the_config_turns_it_off() {
|
||||||
|
assert!(ClawConfig::default().transcript_enabled());
|
||||||
|
let off = ClawConfig {
|
||||||
|
transcript: Some(false),
|
||||||
|
..ClawConfig::default()
|
||||||
|
};
|
||||||
|
assert!(!off.transcript_enabled());
|
||||||
|
let explicit_on = ClawConfig {
|
||||||
|
transcript: Some(true),
|
||||||
|
..ClawConfig::default()
|
||||||
|
};
|
||||||
|
assert!(explicit_on.transcript_enabled());
|
||||||
|
}
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -233,6 +233,7 @@ impl Daemon {
|
|||||||
workspace_root,
|
workspace_root,
|
||||||
watches.clone(),
|
watches.clone(),
|
||||||
accounts.clone(),
|
accounts.clone(),
|
||||||
|
config.transcript_enabled(),
|
||||||
);
|
);
|
||||||
// Three drivers over one store admit from the same table, so
|
// Three drivers over one store admit from the same table, so
|
||||||
// each owns its slice of the key space: a forge pass must never
|
// each owns its slice of the key space: a forge pass must never
|
||||||
@@ -326,6 +327,7 @@ impl Daemon {
|
|||||||
notifications.clone(),
|
notifications.clone(),
|
||||||
config.max_turn_steps,
|
config.max_turn_steps,
|
||||||
extensions.clone(),
|
extensions.clone(),
|
||||||
|
config.transcript_enabled(),
|
||||||
);
|
);
|
||||||
let interactive_driver = Arc::new(
|
let interactive_driver = Arc::new(
|
||||||
Driver::new(
|
Driver::new(
|
||||||
@@ -345,6 +347,7 @@ impl Daemon {
|
|||||||
jobs.clone(),
|
jobs.clone(),
|
||||||
notifications.clone(),
|
notifications.clone(),
|
||||||
config.max_turn_steps,
|
config.max_turn_steps,
|
||||||
|
config.transcript_enabled(),
|
||||||
);
|
);
|
||||||
let jobs_driver = Arc::new(
|
let jobs_driver = Arc::new(
|
||||||
Driver::new(jobs_dispatcher.clone(), Arc::new(jobs_handler))
|
Driver::new(jobs_dispatcher.clone(), Arc::new(jobs_handler))
|
||||||
|
|||||||
@@ -57,6 +57,9 @@ fn allowlist() -> rhema_autonomous_agent::Allowlist {
|
|||||||
pub struct ClawHandler {
|
pub struct ClawHandler {
|
||||||
/// The deployment's providers and the active pair, read per dispatch.
|
/// The deployment's providers and the active pair, read per dispatch.
|
||||||
backends: Arc<BackendRegistry>,
|
backends: Arc<BackendRegistry>,
|
||||||
|
/// Whether the sessions run the bounded request view (the config's
|
||||||
|
/// `transcript`, on unless set false).
|
||||||
|
transcript: bool,
|
||||||
workspace_root: PathBuf,
|
workspace_root: PathBuf,
|
||||||
watches: Arc<ClawStore>,
|
watches: Arc<ClawStore>,
|
||||||
/// The account store a blank watch credential resolves through: the
|
/// The account store a blank watch credential resolves through: the
|
||||||
@@ -74,9 +77,11 @@ impl ClawHandler {
|
|||||||
workspace_root: impl Into<PathBuf>,
|
workspace_root: impl Into<PathBuf>,
|
||||||
watches: Arc<ClawStore>,
|
watches: Arc<ClawStore>,
|
||||||
accounts: Arc<dyn IdentityStore>,
|
accounts: Arc<dyn IdentityStore>,
|
||||||
|
transcript: bool,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
backends,
|
backends,
|
||||||
|
transcript,
|
||||||
workspace_root: workspace_root.into(),
|
workspace_root: workspace_root.into(),
|
||||||
watches,
|
watches,
|
||||||
accounts,
|
accounts,
|
||||||
@@ -210,7 +215,7 @@ impl ClawHandler {
|
|||||||
key: &str,
|
key: &str,
|
||||||
) -> Result<CodingAgent, String> {
|
) -> Result<CodingAgent, String> {
|
||||||
let (provider, model) = self.backends.current();
|
let (provider, model) = self.backends.current();
|
||||||
let config = CodingAgentConfig::new(model, workspace);
|
let config = self.agent_config(&model, workspace);
|
||||||
let agent = match &ctx.session.core.driver_state {
|
let agent = match &ctx.session.core.driver_state {
|
||||||
// A never-dispatched key carries no stash: start clean.
|
// A never-dispatched key carries no stash: start clean.
|
||||||
serde_json::Value::Null => CodingAgent::new(provider, config),
|
serde_json::Value::Null => CodingAgent::new(provider, config),
|
||||||
@@ -223,6 +228,18 @@ impl ClawHandler {
|
|||||||
agent.map_err(|err| format!("cannot start the coding agent for {key}: {err}"))
|
agent.map_err(|err| format!("cannot start the coding agent for {key}: {err}"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The config one dispatch's agent runs on: the default coding
|
||||||
|
/// agent's, with the bounded request view when the config has it on.
|
||||||
|
/// A forge thread answers for as long as the thread lives; without
|
||||||
|
/// the view its history would ride whole on every step.
|
||||||
|
fn agent_config(&self, model: &str, workspace: &Path) -> CodingAgentConfig {
|
||||||
|
let mut config = CodingAgentConfig::new(model, workspace);
|
||||||
|
if self.transcript {
|
||||||
|
config.transcript = Some(rhema_transcript::TranscriptSettings::default());
|
||||||
|
}
|
||||||
|
config
|
||||||
|
}
|
||||||
|
|
||||||
/// The per-thread workspace: one directory under the root, created on
|
/// The per-thread workspace: one directory under the root, created on
|
||||||
/// demand. The name mapping is not injective (separators collapse);
|
/// demand. The name mapping is not injective (separators collapse);
|
||||||
/// thread identity lives in the dispatch key — the directory is only
|
/// thread identity lives in the dispatch key — the directory is only
|
||||||
@@ -759,6 +776,7 @@ mod tests {
|
|||||||
"/tmp/w",
|
"/tmp/w",
|
||||||
watches,
|
watches,
|
||||||
Arc::new(rhema_identity::VolatileIdentityStore::new()),
|
Arc::new(rhema_identity::VolatileIdentityStore::new()),
|
||||||
|
true,
|
||||||
);
|
);
|
||||||
|
|
||||||
// A watch added to the table AFTER the handler was built resolves:
|
// A watch added to the table AFTER the handler was built resolves:
|
||||||
@@ -780,6 +798,40 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn the_transcript_setting_reaches_the_dispatch_config() {
|
||||||
|
let watches = Arc::new(ClawStore::in_memory().expect("store opens"));
|
||||||
|
let registry = Arc::new(
|
||||||
|
crate::backends::BackendRegistry::new(
|
||||||
|
vec![crate::backends::Backend::headless(
|
||||||
|
"mock",
|
||||||
|
Arc::new(NoopProvider),
|
||||||
|
)],
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.expect("constructs"),
|
||||||
|
);
|
||||||
|
let accounts = Arc::new(rhema_identity::VolatileIdentityStore::new());
|
||||||
|
let on = ClawHandler::new(
|
||||||
|
registry.clone(),
|
||||||
|
"/tmp/w",
|
||||||
|
watches.clone(),
|
||||||
|
accounts.clone(),
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
let off = ClawHandler::new(registry, "/tmp/w", watches, accounts, false);
|
||||||
|
assert!(
|
||||||
|
on.agent_config("m", Path::new("/tmp/w"))
|
||||||
|
.transcript
|
||||||
|
.is_some()
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
off.agent_config("m", Path::new("/tmp/w"))
|
||||||
|
.transcript
|
||||||
|
.is_none()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn the_workspace_name_flattens_to_filesystem_safe_characters() {
|
fn the_workspace_name_flattens_to_filesystem_safe_characters() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|||||||
@@ -182,6 +182,10 @@ pub struct InteractiveHandler {
|
|||||||
/// dispatch, in the standard set's order — the same set whose
|
/// dispatch, in the standard set's order — the same set whose
|
||||||
/// sources the daemon's tick drives and whose boot pass runs first.
|
/// sources the daemon's tick drives and whose boot pass runs first.
|
||||||
extensions: Arc<crate::extension::Extensions>,
|
extensions: Arc<crate::extension::Extensions>,
|
||||||
|
/// The transcript's mirror, when the config has the view on: the
|
||||||
|
/// decorator refreshes it per request, `zoom` reads it, and it
|
||||||
|
/// lives here — the agent rebuilds per dispatch, the log must not.
|
||||||
|
transcript_mirror: Option<Arc<std::sync::Mutex<rhema_transcript::Transcript>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl InteractiveHandler {
|
impl InteractiveHandler {
|
||||||
@@ -193,7 +197,7 @@ impl InteractiveHandler {
|
|||||||
/// shell (approval v1-permissive — the shell is the point of a
|
/// shell (approval v1-permissive — the shell is the point of a
|
||||||
/// resident coding agent — installed once, since resolution is
|
/// resident coding agent — installed once, since resolution is
|
||||||
/// synchronous within a turn), memory, and `bootstrap_save`.
|
/// synchronous within a turn), memory, and `bootstrap_save`.
|
||||||
#[allow(clippy::too_many_arguments)] // eight parts, each load-bearing
|
#[allow(clippy::too_many_arguments)] // nine parts, each load-bearing
|
||||||
pub fn new(
|
pub fn new(
|
||||||
backends: Arc<BackendRegistry>,
|
backends: Arc<BackendRegistry>,
|
||||||
workspace: impl Into<PathBuf>,
|
workspace: impl Into<PathBuf>,
|
||||||
@@ -203,6 +207,7 @@ impl InteractiveHandler {
|
|||||||
notifications: Arc<NotificationStore>,
|
notifications: Arc<NotificationStore>,
|
||||||
max_turn_steps: u32,
|
max_turn_steps: u32,
|
||||||
extensions: Arc<crate::extension::Extensions>,
|
extensions: Arc<crate::extension::Extensions>,
|
||||||
|
transcript: bool,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let workspace = workspace.into();
|
let workspace = workspace.into();
|
||||||
let tools = Arc::new(ShellTools::new(&workspace));
|
let tools = Arc::new(ShellTools::new(&workspace));
|
||||||
@@ -220,6 +225,11 @@ impl InteractiveHandler {
|
|||||||
notifications,
|
notifications,
|
||||||
max_turn_steps,
|
max_turn_steps,
|
||||||
extensions,
|
extensions,
|
||||||
|
transcript_mirror: transcript.then(|| {
|
||||||
|
Arc::new(std::sync::Mutex::new(rhema_transcript::Transcript::new(
|
||||||
|
rhema_transcript::TranscriptSettings::default(),
|
||||||
|
)))
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -238,6 +248,15 @@ impl InteractiveHandler {
|
|||||||
// the same expansion the coding driver wraps its own with: the
|
// the same expansion the coding driver wraps its own with: the
|
||||||
// model sees plain text, the transcript keeps the chips.
|
// model sees plain text, the transcript keeps the chips.
|
||||||
let provider: Arc<dyn ModelProvider> = Arc::new(ExpandPastes::new(provider));
|
let provider: Arc<dyn ModelProvider> = Arc::new(ExpandPastes::new(provider));
|
||||||
|
// The view rides outside the expansion: stubs, whole messages,
|
||||||
|
// and zoom output all show the text the model would have seen.
|
||||||
|
let provider: Arc<dyn ModelProvider> = match &self.transcript_mirror {
|
||||||
|
Some(mirror) => Arc::new(rhema_transcript::TranscriptView::new(
|
||||||
|
provider,
|
||||||
|
mirror.clone(),
|
||||||
|
)),
|
||||||
|
None => provider,
|
||||||
|
};
|
||||||
let resolver = self.resolver(key);
|
let resolver = self.resolver(key);
|
||||||
let system = render_system(
|
let system = render_system(
|
||||||
DEFAULT_SYSTEM_MD,
|
DEFAULT_SYSTEM_MD,
|
||||||
@@ -273,6 +292,11 @@ impl InteractiveHandler {
|
|||||||
self.bootstrap_resolver.clone(),
|
self.bootstrap_resolver.clone(),
|
||||||
];
|
];
|
||||||
resolvers.extend(self.extensions.tools(key));
|
resolvers.extend(self.extensions.tools(key));
|
||||||
|
if let Some(mirror) = &self.transcript_mirror {
|
||||||
|
// Unique name, read-only: it shadows nothing and a replay
|
||||||
|
// may re-run it after a crash resume.
|
||||||
|
resolvers.push(Arc::new(rhema_transcript::Zoom::new(mirror.clone())));
|
||||||
|
}
|
||||||
Arc::new(ResolverChain::new(resolvers))
|
Arc::new(ResolverChain::new(resolvers))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -730,6 +754,47 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The transcript wiring: on, the prompt advertises zoom and the
|
||||||
|
/// mirror exists; off, neither appears and the request rides whole.
|
||||||
|
#[test]
|
||||||
|
fn the_transcript_joins_zoom_when_on_and_nothing_when_off() {
|
||||||
|
for on in [true, false] {
|
||||||
|
let workspace = tempfile::tempdir().expect("tempdir");
|
||||||
|
let hub = Arc::new(EventHub::default());
|
||||||
|
let store = Arc::new(ClawStore::in_memory().expect("store opens"));
|
||||||
|
let notifications = notifications();
|
||||||
|
let extensions = test_extensions(
|
||||||
|
hub.clone(),
|
||||||
|
store,
|
||||||
|
notifications.clone(),
|
||||||
|
tasks(),
|
||||||
|
jobs(),
|
||||||
|
accounts(),
|
||||||
|
consents(),
|
||||||
|
bridge(),
|
||||||
|
attachments(),
|
||||||
|
phone(),
|
||||||
|
identity(),
|
||||||
|
&workspace.path().join("interactive"),
|
||||||
|
);
|
||||||
|
let handler = InteractiveHandler::new(
|
||||||
|
test_registry(RecordingProvider::new()),
|
||||||
|
workspace.path().join("interactive"),
|
||||||
|
Arc::new(rhema_memory::VolatileStore::new()),
|
||||||
|
Arc::new(Bootstrap::new(workspace.path().join("bootstrap")).expect("store opens")),
|
||||||
|
hub,
|
||||||
|
notifications,
|
||||||
|
crate::config::DEFAULT_MAX_TURN_STEPS,
|
||||||
|
extensions,
|
||||||
|
on,
|
||||||
|
);
|
||||||
|
let key = Key::from("interactive:web");
|
||||||
|
let agent = handler.agent(&key).expect("the agent builds");
|
||||||
|
assert_eq!(agent.system().contains("zoom"), on, "on={on}");
|
||||||
|
assert_eq!(handler.transcript_mirror.is_some(), on, "on={on}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// The paste contract across the provider seam: a chat input that
|
/// The paste contract across the provider seam: a chat input that
|
||||||
/// carries an embedded paste (the compact form the UIs mint) is
|
/// carries an embedded paste (the compact form the UIs mint) is
|
||||||
/// expanded before the request leaves, while the transcript the hub
|
/// expanded before the request leaves, while the transcript the hub
|
||||||
@@ -764,6 +829,7 @@ mod tests {
|
|||||||
notifications.clone(),
|
notifications.clone(),
|
||||||
crate::config::DEFAULT_MAX_TURN_STEPS,
|
crate::config::DEFAULT_MAX_TURN_STEPS,
|
||||||
extensions,
|
extensions,
|
||||||
|
true,
|
||||||
);
|
);
|
||||||
let dispatcher = Arc::new(Dispatcher::new(
|
let dispatcher = Arc::new(Dispatcher::new(
|
||||||
DispatcherConfig {
|
DispatcherConfig {
|
||||||
@@ -851,6 +917,7 @@ mod tests {
|
|||||||
notifications.clone(),
|
notifications.clone(),
|
||||||
crate::config::DEFAULT_MAX_TURN_STEPS,
|
crate::config::DEFAULT_MAX_TURN_STEPS,
|
||||||
extensions,
|
extensions,
|
||||||
|
true,
|
||||||
);
|
);
|
||||||
let specs = handler.resolver(&Key::from("interactive:web")).specs();
|
let specs = handler.resolver(&Key::from("interactive:web")).specs();
|
||||||
assert!(
|
assert!(
|
||||||
@@ -1121,6 +1188,7 @@ mod tests {
|
|||||||
notifications.clone(),
|
notifications.clone(),
|
||||||
crate::config::DEFAULT_MAX_TURN_STEPS,
|
crate::config::DEFAULT_MAX_TURN_STEPS,
|
||||||
extensions,
|
extensions,
|
||||||
|
true,
|
||||||
);
|
);
|
||||||
|
|
||||||
let dispatcher = Arc::new(Dispatcher::new(
|
let dispatcher = Arc::new(Dispatcher::new(
|
||||||
@@ -1216,6 +1284,7 @@ mod tests {
|
|||||||
notifications.clone(),
|
notifications.clone(),
|
||||||
crate::config::DEFAULT_MAX_TURN_STEPS,
|
crate::config::DEFAULT_MAX_TURN_STEPS,
|
||||||
extensions,
|
extensions,
|
||||||
|
true,
|
||||||
);
|
);
|
||||||
|
|
||||||
let dispatcher = Arc::new(Dispatcher::new(
|
let dispatcher = Arc::new(Dispatcher::new(
|
||||||
@@ -1320,6 +1389,7 @@ mod tests {
|
|||||||
notifications.clone(),
|
notifications.clone(),
|
||||||
crate::config::DEFAULT_MAX_TURN_STEPS,
|
crate::config::DEFAULT_MAX_TURN_STEPS,
|
||||||
extensions,
|
extensions,
|
||||||
|
true,
|
||||||
);
|
);
|
||||||
|
|
||||||
let dispatcher = Arc::new(Dispatcher::new(
|
let dispatcher = Arc::new(Dispatcher::new(
|
||||||
@@ -1400,6 +1470,7 @@ mod tests {
|
|||||||
notifications.clone(),
|
notifications.clone(),
|
||||||
crate::config::DEFAULT_MAX_TURN_STEPS,
|
crate::config::DEFAULT_MAX_TURN_STEPS,
|
||||||
extensions,
|
extensions,
|
||||||
|
true,
|
||||||
);
|
);
|
||||||
let backing = Arc::new(VolatileStore::new());
|
let backing = Arc::new(VolatileStore::new());
|
||||||
let dispatcher = Arc::new(Dispatcher::new(
|
let dispatcher = Arc::new(Dispatcher::new(
|
||||||
@@ -1481,6 +1552,7 @@ mod tests {
|
|||||||
notifications.clone(),
|
notifications.clone(),
|
||||||
crate::config::DEFAULT_MAX_TURN_STEPS,
|
crate::config::DEFAULT_MAX_TURN_STEPS,
|
||||||
extensions,
|
extensions,
|
||||||
|
true,
|
||||||
));
|
));
|
||||||
|
|
||||||
let dispatcher = Arc::new(Dispatcher::new(
|
let dispatcher = Arc::new(Dispatcher::new(
|
||||||
@@ -1604,6 +1676,7 @@ mod tests {
|
|||||||
notifications.clone(),
|
notifications.clone(),
|
||||||
crate::config::DEFAULT_MAX_TURN_STEPS,
|
crate::config::DEFAULT_MAX_TURN_STEPS,
|
||||||
extensions,
|
extensions,
|
||||||
|
true,
|
||||||
);
|
);
|
||||||
let backing = Arc::new(VolatileStore::new());
|
let backing = Arc::new(VolatileStore::new());
|
||||||
let dispatcher = Arc::new(Dispatcher::new(
|
let dispatcher = Arc::new(Dispatcher::new(
|
||||||
|
|||||||
@@ -138,6 +138,9 @@ pub struct CodingJobHandler {
|
|||||||
store: Arc<CodingJobStore>,
|
store: Arc<CodingJobStore>,
|
||||||
notifications: Arc<NotificationStore>,
|
notifications: Arc<NotificationStore>,
|
||||||
max_turn_steps: u32,
|
max_turn_steps: u32,
|
||||||
|
/// Whether the jobs run the bounded request view (the config's
|
||||||
|
/// `transcript`, on unless set false).
|
||||||
|
transcript: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CodingJobHandler {
|
impl CodingJobHandler {
|
||||||
@@ -149,12 +152,14 @@ impl CodingJobHandler {
|
|||||||
store: Arc<CodingJobStore>,
|
store: Arc<CodingJobStore>,
|
||||||
notifications: Arc<NotificationStore>,
|
notifications: Arc<NotificationStore>,
|
||||||
max_turn_steps: u32,
|
max_turn_steps: u32,
|
||||||
|
transcript: bool,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
backends,
|
backends,
|
||||||
store,
|
store,
|
||||||
notifications,
|
notifications,
|
||||||
max_turn_steps,
|
max_turn_steps,
|
||||||
|
transcript,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -195,6 +200,11 @@ impl CodingJobHandler {
|
|||||||
let (provider, model) = self.backends.current();
|
let (provider, model) = self.backends.current();
|
||||||
let mut config = CodingAgentConfig::new(model, &workspace);
|
let mut config = CodingAgentConfig::new(model, &workspace);
|
||||||
config.max_turn_steps = self.max_turn_steps;
|
config.max_turn_steps = self.max_turn_steps;
|
||||||
|
if self.transcript {
|
||||||
|
// A job's stashed session carries across runs and only ever
|
||||||
|
// grows; the view is what keeps a repeat run's steps cheap.
|
||||||
|
config.transcript = Some(rhema_transcript::TranscriptSettings::default());
|
||||||
|
}
|
||||||
let stashed = self
|
let stashed = self
|
||||||
.store
|
.store
|
||||||
.session(&instance_name)
|
.session(&instance_name)
|
||||||
@@ -332,7 +342,7 @@ mod tests {
|
|||||||
BackendRegistry::new(vec![Backend::headless("mock", provider)], None)
|
BackendRegistry::new(vec![Backend::headless("mock", provider)], None)
|
||||||
.expect("a single mock backend always constructs"),
|
.expect("a single mock backend always constructs"),
|
||||||
);
|
);
|
||||||
CodingJobHandler::new(backends, store, notifications, 16)
|
CodingJobHandler::new(backends, store, notifications, 16, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn reply(text: &str) -> ModelOutput {
|
fn reply(text: &str) -> ModelOutput {
|
||||||
|
|||||||
@@ -67,6 +67,7 @@ fn config(server: &MockServer, dir: &tempfile::TempDir) -> ClawConfig {
|
|||||||
model: Some("test-model".to_owned()),
|
model: Some("test-model".to_owned()),
|
||||||
listen: None,
|
listen: None,
|
||||||
token: None,
|
token: None,
|
||||||
|
transcript: None,
|
||||||
watches: vec![Watch {
|
watches: vec![Watch {
|
||||||
forge: ForgeKind::GitHub,
|
forge: ForgeKind::GitHub,
|
||||||
repo: "acme/widgets".to_owned(),
|
repo: "acme/widgets".to_owned(),
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ fn config(server: &MockServer, dir: &tempfile::TempDir) -> ClawConfig {
|
|||||||
model: Some("test-model".to_owned()),
|
model: Some("test-model".to_owned()),
|
||||||
listen: None,
|
listen: None,
|
||||||
token: None,
|
token: None,
|
||||||
|
transcript: None,
|
||||||
watches: vec![Watch {
|
watches: vec![Watch {
|
||||||
forge: ForgeKind::GitHub,
|
forge: ForgeKind::GitHub,
|
||||||
repo: "acme/widgets".to_owned(),
|
repo: "acme/widgets".to_owned(),
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ fn config(dir: &tempfile::TempDir, token: Option<&str>) -> ClawConfig {
|
|||||||
model: Some("test-model".to_owned()),
|
model: Some("test-model".to_owned()),
|
||||||
listen: None,
|
listen: None,
|
||||||
token: token.map(str::to_owned),
|
token: token.map(str::to_owned),
|
||||||
|
transcript: None,
|
||||||
watches: Vec::new(),
|
watches: Vec::new(),
|
||||||
calendars: Vec::new(),
|
calendars: Vec::new(),
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user