mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-10-11 03:40:05 +00:00
parent
86ef1dd6e2
commit
2756a039ca
4 changed files with 634 additions and 97 deletions
463
run.json
463
run.json
File diff suppressed because one or more lines are too long
257
stages/007-simplify_gpt@1/diff.patch
Normal file
257
stages/007-simplify_gpt@1/diff.patch
Normal file
|
|
@ -0,0 +1,257 @@
|
|||
diff --git a/apps/fabro-web/app/lib/query-keys.test.ts b/apps/fabro-web/app/lib/query-keys.test.ts
|
||||
index 4ac4f438e..914f3b11f 100644
|
||||
--- a/apps/fabro-web/app/lib/query-keys.test.ts
|
||||
+++ b/apps/fabro-web/app/lib/query-keys.test.ts
|
||||
@@ -69,13 +69,14 @@ describe("queryKeys", () => {
|
||||
queryKeys.runs.graph("run-1", "TB"),
|
||||
queryKeys.runs.detail("run-1"),
|
||||
queryKeys.runs.stageEvents("run-1", "stage-1"),
|
||||
+ queryKeys.runs.stageContextWindow("run-1", "stage-1"),
|
||||
]);
|
||||
expect(queryKeysForRunEvent("run-1", "run.title.updated")).toEqual([
|
||||
queryKeys.runs.detail("run-1"),
|
||||
]);
|
||||
});
|
||||
|
||||
- test("agent activity events invalidate the per-stage events key", () => {
|
||||
+ test("agent activity events invalidate per-stage resources", () => {
|
||||
for (const event of [
|
||||
"stage.prompt",
|
||||
"agent.message",
|
||||
@@ -86,6 +87,7 @@ describe("queryKeys", () => {
|
||||
]) {
|
||||
expect(queryKeysForRunEvent("run-1", event, "stage-1")).toEqual([
|
||||
queryKeys.runs.stageEvents("run-1", "stage-1"),
|
||||
+ queryKeys.runs.stageContextWindow("run-1", "stage-1"),
|
||||
]);
|
||||
}
|
||||
});
|
||||
diff --git a/apps/fabro-web/app/lib/run-events.test.tsx b/apps/fabro-web/app/lib/run-events.test.tsx
|
||||
index 141826e41..0a33cc3d5 100644
|
||||
--- a/apps/fabro-web/app/lib/run-events.test.tsx
|
||||
+++ b/apps/fabro-web/app/lib/run-events.test.tsx
|
||||
@@ -52,7 +52,7 @@ describe("queryKeysForRunEvent", () => {
|
||||
]);
|
||||
});
|
||||
|
||||
- test("stage.retrying invalidates stages, billing, events, graph, detail, and stage events", () => {
|
||||
+ test("stage.retrying invalidates stage-scoped and run-scoped resources", () => {
|
||||
expect(queryKeysForRunEvent("run-1", "stage.retrying", "verify@2")).toEqual([
|
||||
queryKeys.runs.stages("run-1"),
|
||||
queryKeys.runs.billing("run-1"),
|
||||
@@ -61,29 +61,34 @@ describe("queryKeysForRunEvent", () => {
|
||||
queryKeys.runs.graph("run-1", "TB"),
|
||||
queryKeys.runs.detail("run-1"),
|
||||
queryKeys.runs.stageEvents("run-1", "verify@2"),
|
||||
+ queryKeys.runs.stageContextWindow("run-1", "verify@2"),
|
||||
]);
|
||||
});
|
||||
|
||||
- test("stage-scoped steering events invalidate run events and stage events", () => {
|
||||
+ test("stage-scoped steering events invalidate run events and stage-scoped resources", () => {
|
||||
expect(queryKeysForRunEvent("run-1", "agent.session.activated", "agent@1")).toEqual([
|
||||
queryKeys.runs.events("run-1", 1000),
|
||||
queryKeys.runs.stageEvents("run-1", "agent@1"),
|
||||
+ queryKeys.runs.stageContextWindow("run-1", "agent@1"),
|
||||
]);
|
||||
});
|
||||
|
||||
- test("stage-scoped interrupt injection invalidates run events and stage events", () => {
|
||||
+ test("stage-scoped interrupt injection invalidates run events and stage-scoped resources", () => {
|
||||
expect(queryKeysForRunEvent("run-1", "agent.interrupt.injected", "nap@1")).toEqual([
|
||||
queryKeys.runs.events("run-1", 1000),
|
||||
queryKeys.runs.stageEvents("run-1", "nap@1"),
|
||||
+ queryKeys.runs.stageContextWindow("run-1", "nap@1"),
|
||||
]);
|
||||
});
|
||||
|
||||
- test("pair messages invalidate the stage events query", () => {
|
||||
+ test("pair messages invalidate stage-scoped resources", () => {
|
||||
expect(queryKeysForRunEvent("run-1", "agent.pair.user_message", "nap@1")).toEqual([
|
||||
queryKeys.runs.stageEvents("run-1", "nap@1"),
|
||||
+ queryKeys.runs.stageContextWindow("run-1", "nap@1"),
|
||||
]);
|
||||
expect(queryKeysForRunEvent("run-1", "agent.pair.system_message", "nap@1")).toEqual([
|
||||
queryKeys.runs.stageEvents("run-1", "nap@1"),
|
||||
+ queryKeys.runs.stageContextWindow("run-1", "nap@1"),
|
||||
]);
|
||||
});
|
||||
|
||||
diff --git a/apps/fabro-web/app/lib/run-events.ts b/apps/fabro-web/app/lib/run-events.ts
|
||||
index 70bacc7d7..c88c76341 100644
|
||||
--- a/apps/fabro-web/app/lib/run-events.ts
|
||||
+++ b/apps/fabro-web/app/lib/run-events.ts
|
||||
@@ -155,6 +155,7 @@ export function queryKeysForRunEvent(
|
||||
];
|
||||
if (stageId) {
|
||||
keys.push(queryKeys.runs.stageEvents(runId, stageId));
|
||||
+ keys.push(queryKeys.runs.stageContextWindow(runId, stageId));
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
@@ -163,6 +164,7 @@ export function queryKeysForRunEvent(
|
||||
const keys: Key[] = [queryKeys.runs.events(runId, 1000)];
|
||||
if (stageId) {
|
||||
keys.push(queryKeys.runs.stageEvents(runId, stageId));
|
||||
+ keys.push(queryKeys.runs.stageContextWindow(runId, stageId));
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
@@ -177,7 +179,12 @@ export function queryKeysForRunEvent(
|
||||
}
|
||||
|
||||
if (STAGE_ACTIVITY_EVENTS.has(event)) {
|
||||
- return stageId ? [queryKeys.runs.stageEvents(runId, stageId)] : [];
|
||||
+ return stageId
|
||||
+ ? [
|
||||
+ queryKeys.runs.stageEvents(runId, stageId),
|
||||
+ queryKeys.runs.stageContextWindow(runId, stageId),
|
||||
+ ]
|
||||
+ : [];
|
||||
}
|
||||
|
||||
if (TODO_EVENTS.has(event)) {
|
||||
diff --git a/lib/crates/fabro-agent/src/session.rs b/lib/crates/fabro-agent/src/session.rs
|
||||
index 5ad7824a7..4f7cd9ccf 100644
|
||||
--- a/lib/crates/fabro-agent/src/session.rs
|
||||
+++ b/lib/crates/fabro-agent/src/session.rs
|
||||
@@ -17,7 +17,9 @@ use fabro_llm::{Error as LlmError, retry};
|
||||
use fabro_mcp::config::{McpServerSettings, McpTransport};
|
||||
use fabro_mcp::connection_manager::McpConnectionManager;
|
||||
use fabro_model::{AgentProfileKind, Catalog, ModelRef, Speed};
|
||||
-use fabro_types::{Principal, SessionMessage, SessionRecord, SteeringMessage};
|
||||
+use fabro_types::{
|
||||
+ Principal, SessionMessage, SessionRecord, StageContextWindowProjection, SteeringMessage,
|
||||
+};
|
||||
use futures::StreamExt;
|
||||
use tokio::sync::{Mutex as AsyncMutex, Notify, broadcast};
|
||||
use tokio::time;
|
||||
@@ -306,6 +308,11 @@ struct BuiltRequest {
|
||||
tools: Vec<ToolDefinitionWithSource>,
|
||||
}
|
||||
|
||||
+struct EmittedContextWindowSnapshot {
|
||||
+ local_snapshot: StageContextWindowProjection,
|
||||
+ fingerprint: Option<u64>,
|
||||
+}
|
||||
+
|
||||
pub struct Session {
|
||||
id: String,
|
||||
/// Root agent session ID for this session's agent tree. A root session
|
||||
@@ -333,6 +340,7 @@ pub struct Session {
|
||||
system_prompt: String,
|
||||
activated_skill_context_observed: bool,
|
||||
context_window_counted_fingerprints: HashSet<u64>,
|
||||
+ context_window_response_usage_fingerprints: Arc<Mutex<HashSet<u64>>>,
|
||||
file_tracker: FileTracker,
|
||||
tool_env_provider: Option<Arc<dyn ToolEnvProvider>>,
|
||||
subagent_manager: Option<Arc<AsyncMutex<SubAgentManager>>>,
|
||||
@@ -372,6 +380,7 @@ impl Session {
|
||||
system_prompt: String::new(),
|
||||
activated_skill_context_observed: false,
|
||||
context_window_counted_fingerprints: HashSet::new(),
|
||||
+ context_window_response_usage_fingerprints: Arc::new(Mutex::new(HashSet::new())),
|
||||
file_tracker: FileTracker::default(),
|
||||
tool_env_provider: None,
|
||||
subagent_manager,
|
||||
@@ -1591,9 +1600,7 @@ impl Session {
|
||||
.cloned()
|
||||
.collect();
|
||||
let usage = response.usage.clone();
|
||||
- if let Some(local_snapshot) = context_window_snapshot.as_ref() {
|
||||
- self.emit_response_usage_context_window_snapshot(local_snapshot, &usage);
|
||||
- }
|
||||
+ self.emit_response_usage_context_window_snapshot(&context_window_snapshot, &usage);
|
||||
|
||||
self.history.push(Message::Assistant {
|
||||
content: text.clone(),
|
||||
@@ -1727,7 +1734,7 @@ impl Session {
|
||||
fn emit_context_window_snapshots(
|
||||
&mut self,
|
||||
built_request: &BuiltRequest,
|
||||
- ) -> Option<fabro_types::StageContextWindowProjection> {
|
||||
+ ) -> EmittedContextWindowSnapshot {
|
||||
let provider = self.provider_profile.provider_id().to_string();
|
||||
let model = self.provider_profile.model().to_string();
|
||||
let local_snapshot = build_local_snapshot(ContextWindowSnapshotInput {
|
||||
@@ -1746,9 +1753,17 @@ impl Session {
|
||||
AgentEvent::ContextWindowSnapshot(local_snapshot.clone()),
|
||||
);
|
||||
|
||||
- let fingerprint = request_fingerprint(&built_request.request)?;
|
||||
+ let Some(fingerprint) = request_fingerprint(&built_request.request) else {
|
||||
+ return EmittedContextWindowSnapshot {
|
||||
+ local_snapshot,
|
||||
+ fingerprint: None,
|
||||
+ };
|
||||
+ };
|
||||
if !self.context_window_counted_fingerprints.insert(fingerprint) {
|
||||
- return Some(local_snapshot);
|
||||
+ return EmittedContextWindowSnapshot {
|
||||
+ local_snapshot,
|
||||
+ fingerprint: Some(fingerprint),
|
||||
+ };
|
||||
}
|
||||
|
||||
let client = self.llm_client.clone();
|
||||
@@ -1757,6 +1772,8 @@ impl Session {
|
||||
let emitter = self.event_emitter.clone();
|
||||
let local_for_count = local_snapshot.clone();
|
||||
let close_token = self.close_token.clone();
|
||||
+ let response_usage_fingerprints =
|
||||
+ Arc::clone(&self.context_window_response_usage_fingerprints);
|
||||
tokio::spawn(async move {
|
||||
let count_result = tokio::select! {
|
||||
biased;
|
||||
@@ -1766,6 +1783,13 @@ impl Session {
|
||||
if close_token.is_cancelled() {
|
||||
return;
|
||||
}
|
||||
+ if response_usage_fingerprints
|
||||
+ .lock()
|
||||
+ .expect("context window response-usage fingerprint lock poisoned")
|
||||
+ .contains(&fingerprint)
|
||||
+ {
|
||||
+ return;
|
||||
+ }
|
||||
let snapshot = match count_result {
|
||||
Ok(count) if count.method == InputTokenCountMethod::ProviderApi => {
|
||||
let input_tokens = u64::try_from(count.input_tokens.max(0)).unwrap_or(u64::MAX);
|
||||
@@ -1801,12 +1825,15 @@ impl Session {
|
||||
emitter.emit(session_id, AgentEvent::ContextWindowSnapshot(snapshot));
|
||||
});
|
||||
|
||||
- Some(local_snapshot)
|
||||
+ EmittedContextWindowSnapshot {
|
||||
+ local_snapshot,
|
||||
+ fingerprint: Some(fingerprint),
|
||||
+ }
|
||||
}
|
||||
|
||||
fn emit_response_usage_context_window_snapshot(
|
||||
&self,
|
||||
- local_snapshot: &fabro_types::StageContextWindowProjection,
|
||||
+ context_window_snapshot: &EmittedContextWindowSnapshot,
|
||||
usage: &TokenCounts,
|
||||
) {
|
||||
let input_tokens = usage
|
||||
@@ -1816,11 +1843,17 @@ impl Session {
|
||||
if input_tokens <= 0 {
|
||||
return;
|
||||
}
|
||||
+ if let Some(fingerprint) = context_window_snapshot.fingerprint {
|
||||
+ self.context_window_response_usage_fingerprints
|
||||
+ .lock()
|
||||
+ .expect("context window response-usage fingerprint lock poisoned")
|
||||
+ .insert(fingerprint);
|
||||
+ }
|
||||
let snapshot = scaled_snapshot(
|
||||
- local_snapshot,
|
||||
+ &context_window_snapshot.local_snapshot,
|
||||
u64::try_from(input_tokens).unwrap_or(u64::MAX),
|
||||
fabro_types::StageContextWindowCountMethod::ResponseUsageScaledBreakdown,
|
||||
- local_snapshot.warnings.clone(),
|
||||
+ context_window_snapshot.local_snapshot.warnings.clone(),
|
||||
);
|
||||
self.event_emitter
|
||||
.emit(self.id.clone(), AgentEvent::ContextWindowSnapshot(snapshot));
|
||||
6
stages/007-simplify_gpt@1/status.json
Normal file
6
stages/007-simplify_gpt@1/status.json
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"outcome": "succeeded",
|
||||
"notes": "Stage completed: simplify_gpt",
|
||||
"failure_reason": null,
|
||||
"timestamp": "2026-05-23T21:35:11.507680Z"
|
||||
}
|
||||
5
stages/008-verify@1/script_invocation.json
Normal file
5
stages/008-verify@1/script_invocation.json
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"script": "git fetch origin main 2>&1 && git merge --no-edit --no-stat origin/main 2>&1 && cargo +nightly-2026-04-14 fmt --all 2>&1 && cargo dev docs refresh 2>&1 && cargo +nightly-2026-04-14 fmt --check --all 2>&1 && ! rg -n 'AuthMode::Disabled|RunAuthMethod|RunSubjectProvenance|\\bActorRef\\b|\\bActorKind\\b|AuthenticatedSubject|AuthenticatedService|AuthorizeRunScoped|AuthorizeRunBlob|AuthorizeStageArtifact|AuthorizeCommandLog|auth_method\\s*==\\s*\"disabled\"' lib/crates apps lib/packages docs/public/api-reference/fabro-api.yaml 2>&1 && cargo +nightly-2026-04-14 clippy --workspace --all-targets -- -D warnings 2>&1 && cargo nextest run --workspace --status-level slow --profile ci 2>&1 && cargo dev docs check 2>&1 && bun install --frozen-lockfile 2>&1 && (cd apps/fabro-web && bun run typecheck) 2>&1 && (cd apps/fabro-web && bun run test) 2>&1 && (cd lib/packages/fabro-api-client && bun run typecheck) 2>&1 && cargo dev build -- -p fabro-cli --release 2>&1",
|
||||
"command": "exec 2>&1\ngit fetch origin main 2>&1 && git merge --no-edit --no-stat origin/main 2>&1 && cargo +nightly-2026-04-14 fmt --all 2>&1 && cargo dev docs refresh 2>&1 && cargo +nightly-2026-04-14 fmt --check --all 2>&1 && ! rg -n 'AuthMode::Disabled|RunAuthMethod|RunSubjectProvenance|\\bActorRef\\b|\\bActorKind\\b|AuthenticatedSubject|AuthenticatedService|AuthorizeRunScoped|AuthorizeRunBlob|AuthorizeStageArtifact|AuthorizeCommandLog|auth_method\\s*==\\s*\"disabled\"' lib/crates apps lib/packages docs/public/api-reference/fabro-api.yaml 2>&1 && cargo +nightly-2026-04-14 clippy --workspace --all-targets -- -D warnings 2>&1 && cargo nextest run --workspace --status-level slow --profile ci 2>&1 && cargo dev docs check 2>&1 && bun install --frozen-lockfile 2>&1 && (cd apps/fabro-web && bun run typecheck) 2>&1 && (cd apps/fabro-web && bun run test) 2>&1 && (cd lib/packages/fabro-api-client && bun run typecheck) 2>&1 && cargo dev build -- -p fabro-cli --release 2>&1",
|
||||
"language": "shell"
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue