Read the stage view from the agent's fold

StageProjection loses todos, subagents, skills, mcp_servers, and
context_window, the types behind them, their fold arms and helpers, and
their OpenAPI schemas: every one of those facts is pebble's fold in
StageProjection.agent now. The context-window endpoint reads the fold's
snapshot, whose event_seq is the agent's own sequence. The parity module
keeps its assertions on the surviving own fields, usage and model, and
checks that what the stage view reads from agent is the whole-session
fold's for the stage's events. The TypeScript client is regenerated and
its stale models removed.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Bryan Helmkamp 2026-09-13 08:13:10 -06:00 • committed by Bryan Helmkamp
parent d5009d976b
commit 318fdf7206
27 changed files with 149 additions and 2759 deletions

View file

@ -1660,9 +1660,9 @@ Pebble's `RouteFailover`, `McpServerReady`, `McpServerFailed`, and
`McpServerDisconnected` events, stored verbatim with pebble's envelope in
`properties` like every other pebble event. Fabro also mirrors each onto
its own `agent.failover`, `agent.mcp.ready`, `agent.mcp.failed`, and
`agent.mcp.disconnected`, which the store folds into `StageProjection`'s
`mcp_servers`; the pebble events feed `StageProjection.agent`. The mirrors
go once every reader is on `agent`.
`agent.mcp.disconnected`. The stage view reads MCP state from
`StageProjection.agent`, which the pebble events feed; the mirrors change
nothing on the stage any more and go next.
### `agent.route.failover.stopped`

View file

@ -11029,8 +11029,11 @@ components:
example: "2026-05-23T12:34:56Z"
event_seq:
type: ["integer", "null"]
format: uint32
minimum: 1
format: uint64
minimum: 0
description: >-
The coding agent's own event sequence for the snapshot, when it
carried one; not the run event sequence.
example: 42
breakdown:
type: array
@ -11170,23 +11173,6 @@ components:
oneOf:
- $ref: "#/components/schemas/BillingModelRef"
- type: "null"
todos:
oneOf:
- $ref: "#/components/schemas/TodoListProjection"
- type: "null"
description: |
Todo / task list owned by this stage's root agent session. OpenAI
child sessions have separate per-session plans that do not appear
here. Anthropic task lists are root-scoped and shared with child
sessions, so child mutations of that shared list do appear here.
subagents:
type: array
description: Subagents spawned by this stage, in replay/insertion order.
items:
$ref: "#/components/schemas/SubAgentProjection"
skills:
$ref: "#/components/schemas/SkillsProjection"
description: Agent skills discovered and activated during this stage.
permission_level:
oneOf:
- $ref: "#/components/schemas/PermissionLevel"
@ -11199,16 +11185,6 @@ components:
Tool parameter schemas are intentionally omitted from this projection.
items:
$ref: "#/components/schemas/ToolSummary"
mcp_servers:
type: array
description: MCP servers observed by this stage.
items:
$ref: "#/components/schemas/McpServerProjection"
context_window:
oneOf:
- $ref: "#/components/schemas/ContextWindowSnapshot"
- type: "null"
description: Latest content-free context-window snapshot for this agent stage.
inference:
oneOf:
- $ref: "#/components/schemas/StageInferenceProjection"
@ -11340,102 +11316,6 @@ components:
- text
- tool_call
SubAgentProjection:
description: Current projected state for one subagent spawned by an agent stage.
type: object
required:
- agent_id
- depth
- task
- status
properties:
agent_id:
type: string
depth:
type: integer
minimum: 0
task:
type: string
status:
$ref: "#/components/schemas/SubAgentStatus"
SubAgentStatus:
description: Projected lifecycle status for a subagent.
oneOf:
- $ref: "#/components/schemas/SubAgentStatusRunning"
- $ref: "#/components/schemas/SubAgentStatusCompleted"
- $ref: "#/components/schemas/SubAgentStatusFailed"
- $ref: "#/components/schemas/SubAgentStatusClosed"
discriminator:
propertyName: kind
mapping:
running: "#/components/schemas/SubAgentStatusRunning"
completed: "#/components/schemas/SubAgentStatusCompleted"
failed: "#/components/schemas/SubAgentStatusFailed"
closed: "#/components/schemas/SubAgentStatusClosed"
SubAgentStatusRunning:
type: object
required:
- kind
properties:
kind:
type: string
enum: [running]
SubAgentStatusCompleted:
type: object
required:
- kind
- success
- turns_used
properties:
kind:
type: string
enum: [completed]
success:
type: boolean
turns_used:
type: integer
minimum: 0
SubAgentStatusFailed:
type: object
required:
- kind
- error
properties:
kind:
type: string
enum: [failed]
error:
description: Provider/tool error payload captured by the subagent event.
SubAgentStatusClosed:
type: object
required:
- kind
properties:
kind:
type: string
enum: [closed]
SkillsProjection:
description: Agent skills discovered and activated during a stage.
type: object
required:
- available
- activated
properties:
available:
type: array
items:
$ref: "#/components/schemas/SkillSummary"
activated:
type: array
items:
$ref: "#/components/schemas/ActivatedSkill"
SkillSummary:
description: Summary of an available agent skill.
type: object
@ -11448,18 +11328,6 @@ components:
description:
type: string
ActivatedSkill:
description: One observed agent skill activation.
type: object
required:
- name
- source
properties:
name:
type: string
source:
$ref: "#/components/schemas/SkillActivationSource"
SkillActivationSource:
description: Source that activated an agent skill.
type: string
@ -11556,91 +11424,6 @@ components:
type: string
enum: [read, write, shell, subagent, other]
McpServerProjection:
description: Projected state for one MCP server observed by an agent stage.
type: object
required:
- server_name
- tool_count
- status
- invoked
properties:
server_name:
type: string
tool_count:
type: integer
minimum: 0
status:
$ref: "#/components/schemas/McpServerStatus"
invoked:
type: boolean
description: True once the agent has invoked at least one tool from this server during the stage.
McpServerStatus:
description: Projected MCP server readiness status.
oneOf:
- $ref: "#/components/schemas/McpServerStatusReady"
- $ref: "#/components/schemas/McpServerStatusFailed"
- $ref: "#/components/schemas/McpServerStatusDisconnected"
discriminator:
propertyName: kind
mapping:
ready: "#/components/schemas/McpServerStatusReady"
failed: "#/components/schemas/McpServerStatusFailed"
disconnected: "#/components/schemas/McpServerStatusDisconnected"
McpServerStatusReady:
type: object
required:
- kind
- tools
properties:
kind:
type: string
enum: [ready]
tools:
type: array
items:
$ref: "#/components/schemas/AgentMcpToolSummary"
McpServerStatusFailed:
type: object
required:
- kind
- error
properties:
kind:
type: string
enum: [failed]
error:
type: string
McpServerStatusDisconnected:
description: The server was ready and then its connection closed during the stage; its tools fail until the session ends.
type: object
required:
- kind
- error
properties:
kind:
type: string
enum: [disconnected]
error:
type: string
description: What closed the connection, as the client observed it.
AgentMcpToolSummary:
description: Summary of one tool exposed by an MCP server.
type: object
required:
- name
- original_name
properties:
name:
type: string
original_name:
type: string
AgentSessionProjection:
description: >-
The coding agent's fold of one stage's event stream: token counts and

View file

@ -1772,7 +1772,11 @@ async fn get_run_stage_context_window(
.into_response();
}
let Some(snapshot) = stage.context_window.as_ref() else {
let Some(snapshot) = stage
.agent
.as_ref()
.and_then(|agent| agent.context_window.as_ref())
else {
return Json(StageContextWindow::unavailable(
stage_id,
StageContextWindowUnavailableReason::NotObserved,
@ -1789,7 +1793,11 @@ async fn get_run_stage_context_window(
}
fn is_agent_context_window_stage(stage: &StageProjection) -> bool {
if stage.context_window.is_some() {
if stage
.agent
.as_ref()
.is_some_and(|agent| agent.context_window.is_some())
{
return true;
}
if stage.handler == Some(StageHandler::Agent) {

File diff suppressed because it is too large Load diff

View file

@ -404,10 +404,6 @@ fn main() {
&[],
),
("TodoListProjection", "fabro_types::TodoListProjection", &[]),
("SubAgentProjection", "fabro_types::SubAgentProjection", &[]),
("SubAgentStatus", "fabro_types::SubAgentStatus", &[]),
("SkillsProjection", "fabro_types::SkillsProjection", &[]),
("ActivatedSkill", "fabro_types::ActivatedSkill", &[]),
("SkillSummary", "fabro_types::SkillSummary", &[]),
(
"SkillActivationSource",
@ -423,17 +419,6 @@ fn main() {
"fabro_types::AgentToolsAvailableProps",
&[],
),
(
"McpServerProjection",
"fabro_types::McpServerProjection",
&[],
),
("McpServerStatus", "fabro_types::McpServerStatus", &[]),
(
"AgentMcpToolSummary",
"fabro_types::AgentMcpToolSummary",
&[],
),
// Pebble's own fold of a stage's agent events, embedded in
// `StageProjection.agent`. Every nested type is pebble's; the schema
// names carry an `AgentSession` prefix where fabro already has a

View file

@ -38,17 +38,16 @@ pub mod types {
BlockedReason, FailureReason, PendingReason, RunControlAction, RunStatus, SuccessReason,
};
pub use fabro_types::{
ActivatedSkill, AgentControlState, AgentEventProps, AgentMcpToolSummary,
AgentToolsAvailableProps, AskFabro, AuthMethod, AutomationRef, BilledModelUsage,
BilledTokenCounts, BlobHash, CommandTermination, Conclusion, ContextWindowBreakdownItem,
ContextWindowCategory, ContextWindowCountMethod, ContextWindowSnapshot,
ContextWindowStaleness, ContextWindowWarning, CreateVariableRequest, DiffStats,
DiffSummary, DirtyStatus, EventEnvelope, ExecOutputTail, FailureCategory, FailureDetail,
FailureSignature, GitContext, GitRunTarget, GitRunTarget as AutomationGitWorkflowSource,
IdpIdentity, IntegrationConnectionKind, IntegrationConnectionState,
IntegrationConnectionStatus, IntegrationProvider, IntegrationStatus, InterviewOption,
InterviewQuestionRecord, LlmOutputKind, McpServerDraft as CreateMcpServerRequest,
McpServerProjection, McpServerReplace as ReplaceMcpServerRequest, McpServerStatus,
AgentControlState, AgentEventProps, AgentToolsAvailableProps, AskFabro, AuthMethod,
AutomationRef, BilledModelUsage, BilledTokenCounts, BlobHash, CommandTermination,
Conclusion, ContextWindowBreakdownItem, ContextWindowCategory, ContextWindowCountMethod,
ContextWindowSnapshot, ContextWindowStaleness, ContextWindowWarning, CreateVariableRequest,
DiffStats, DiffSummary, DirtyStatus, EventEnvelope, ExecOutputTail, FailureCategory,
FailureDetail, FailureSignature, GitContext, GitRunTarget,
GitRunTarget as AutomationGitWorkflowSource, IdpIdentity, IntegrationConnectionKind,
IntegrationConnectionState, IntegrationConnectionStatus, IntegrationProvider,
IntegrationStatus, InterviewOption, InterviewQuestionRecord, LlmOutputKind,
McpServerDraft as CreateMcpServerRequest, McpServerReplace as ReplaceMcpServerRequest,
McpServerView as McpServer, McpTransportView, Model, ModelControls, ModelCosts,
ModelFeatures, ModelLimits, ModelRef as BillingModelRef, ModelTestMode, PairId,
PairMessageId, PairMessageRecord, PairMessageRequest, PairRecord, PairStartRequest,
@ -65,13 +64,13 @@ pub mod types {
SandboxDetails, SandboxInfo, SandboxListMeta, SandboxListResponse, SandboxProviderKind,
SandboxProviderLookupError, SandboxService, SandboxServiceListResponse, SecretMetadata,
SecretType, ServerSettings, SessionDetail, SessionId, SessionStatus, SessionSummary,
SessionTurn, SkillActivationSource, SkillSummary, SkillsProjection, StageCompletion,
StageContextWindow, StageContextWindowUnavailableReason, StageHandler, StageId,
StageInferenceProjection, StageModelUsage, StageOutcome, StageProjection, StageState,
StageToolBatchProjection, SubAgentProjection, SubAgentStatus, SystemActorKind,
SystemIntegrationStatus, SystemIntegrationsResponse, TodoListProjection, ToolCategory,
ToolSource, ToolSummary, TurnId, UpdateVariableRequest, UserPrincipal, Variable,
VariableListResponse, WorkflowPath, WorkflowSettings, WorkflowVersion, WorkflowVersionId,
SessionTurn, SkillActivationSource, SkillSummary, StageCompletion, StageContextWindow,
StageContextWindowUnavailableReason, StageHandler, StageId, StageInferenceProjection,
StageModelUsage, StageOutcome, StageProjection, StageState, StageToolBatchProjection,
SystemActorKind, SystemIntegrationStatus, SystemIntegrationsResponse, TodoListProjection,
ToolCategory, ToolSource, ToolSummary, TurnId, UpdateVariableRequest, UserPrincipal,
Variable, VariableListResponse, WorkflowPath, WorkflowSettings, WorkflowVersion,
WorkflowVersionId,
};
pub use lithos_llm::catalog::{ModelHandle, ProviderId};
pub use lithos_llm::types::{

View file

@ -1,8 +1,7 @@
use std::any::{TypeId, type_name};
use fabro_api::types::{
ActivatedSkill as ApiActivatedSkill, AgentControlState as ApiAgentControlState,
AgentMcpToolSummary as ApiAgentMcpToolSummary,
AgentControlState as ApiAgentControlState,
AgentToolsAvailableProps as ApiAgentToolsAvailableProps,
BilledModelUsage as ApiBilledModelUsage,
ContextWindowBreakdownItem as ApiContextWindowBreakdownItem,
@ -11,26 +10,23 @@ use fabro_api::types::{
ContextWindowSnapshot as ApiContextWindowSnapshot,
ContextWindowStaleness as ApiContextWindowStaleness,
ContextWindowWarning as ApiContextWindowWarning, LlmOutputKind as ApiLlmOutputKind,
McpServerProjection as ApiMcpServerProjection, McpServerStatus as ApiMcpServerStatus,
ParallelBranchResult as ApiParallelBranchResult, PermissionLevel as ApiPermissionLevel,
SkillActivationSource as ApiSkillActivationSource, SkillSummary as ApiSkillSummary,
SkillsProjection as ApiSkillsProjection, StageContextWindow as ApiStageContextWindow,
StageContextWindow as ApiStageContextWindow,
StageContextWindowUnavailableReason as ApiStageContextWindowUnavailableReason,
StageInferenceProjection as ApiStageInferenceProjection, StageProjection as ApiStageProjection,
StageToolBatchProjection as ApiStageToolBatchProjection,
SubAgentProjection as ApiSubAgentProjection, SubAgentStatus as ApiSubAgentStatus,
TodoListProjection as ApiTodoListProjection, ToolCategory as ApiToolCategory,
ToolSource as ApiToolSource, ToolSummary as ApiToolSummary,
};
use fabro_types::{
ActivatedSkill, AgentControlState, AgentMcpToolSummary, AgentToolsAvailableProps,
BilledModelUsage, ContextWindowBreakdownItem, ContextWindowCategory, ContextWindowCountMethod,
ContextWindowSnapshot, ContextWindowStaleness, ContextWindowWarning, LlmOutputKind,
McpServerProjection, McpServerStatus, ModelRef, ParallelBranchId, ParallelBranchResult,
PermissionLevel, SkillActivationSource, SkillSummary, SkillsProjection, StageContextWindow,
AgentControlState, AgentToolsAvailableProps, BilledModelUsage, ContextWindowBreakdownItem,
ContextWindowCategory, ContextWindowCountMethod, ContextWindowSnapshot, ContextWindowStaleness,
ContextWindowWarning, LlmOutputKind, ModelRef, ParallelBranchId, ParallelBranchResult,
PermissionLevel, SkillActivationSource, SkillSummary, StageContextWindow,
StageContextWindowUnavailableReason, StageId, StageInferenceProjection, StageProjection,
StageToolBatchProjection, SubAgentProjection, SubAgentStatus, TodoListKind, TodoListProjection,
ToolCategory, ToolSource, ToolSummary,
StageToolBatchProjection, TodoListKind, TodoListProjection, ToolCategory, ToolSource,
ToolSummary,
};
use lithos_llm::catalog::{ModelId, ProviderId};
use lithos_llm::types::TokenCounts;
@ -116,19 +112,12 @@ fn stage_projection_reuses_nested_agent_state_types() {
assert_same_type::<ApiAgentControlState, AgentControlState>();
assert_same_type::<ApiParallelBranchResult, ParallelBranchResult>();
assert_same_type::<ApiTodoListProjection, TodoListProjection>();
assert_same_type::<ApiSubAgentProjection, SubAgentProjection>();
assert_same_type::<ApiSubAgentStatus, SubAgentStatus>();
assert_same_type::<ApiSkillsProjection, SkillsProjection>();
assert_same_type::<ApiActivatedSkill, ActivatedSkill>();
assert_same_type::<ApiSkillSummary, SkillSummary>();
assert_same_type::<ApiSkillActivationSource, SkillActivationSource>();
assert_same_type::<ApiToolSummary, ToolSummary>();
assert_same_type::<ApiToolSource, ToolSource>();
assert_same_type::<ApiToolCategory, ToolCategory>();
assert_same_type::<ApiAgentToolsAvailableProps, AgentToolsAvailableProps>();
assert_same_type::<ApiMcpServerProjection, McpServerProjection>();
assert_same_type::<ApiMcpServerStatus, McpServerStatus>();
assert_same_type::<ApiAgentMcpToolSummary, AgentMcpToolSummary>();
assert_same_type::<ApiPermissionLevel, PermissionLevel>();
assert_same_type::<ApiStageContextWindow, StageContextWindow>();
assert_same_type::<ApiContextWindowSnapshot, ContextWindowSnapshot>();
@ -300,45 +289,6 @@ fn stage_projection_round_trips_representative_json() {
"cache_read_tokens": 0,
"cache_write_tokens": 0
},
"todos": {
"kind": "openai_plan",
"list_id": "openai_plan:ses_root",
"items": [
{
"id": "todo-1",
"status": "in_progress",
"order": 0,
"subject": "Write tests",
"active_form": "Writing tests"
}
]
},
"subagents": [
{
"agent_id": "sub-1",
"depth": 1,
"task": "Investigate failing test",
"status": {
"kind": "completed",
"success": true,
"turns_used": 3
}
}
],
"skills": {
"available": [
{
"name": "rust",
"description": "Rust workflow help"
}
],
"activated": [
{
"name": "rust",
"source": "slash"
}
]
},
"permission_level": "read-only",
"agent_tools": [
{
@ -360,41 +310,6 @@ fn stage_projection_round_trips_representative_json() {
"invoked": false
}
],
"mcp_servers": [
{
"server_name": "filesystem",
"tool_count": 1,
"status": {
"kind": "ready",
"tools": [
{
"name": "read_file",
"original_name": "read_file"
}
]
},
"invoked": true
}
],
"context_window": {
"provider": "openai",
"model": "gpt-5.4",
"context_window_tokens": 400000,
"input_tokens": 123456,
"usage_percent": 30.864,
"count_method": "provider_api_scaled_breakdown",
"staleness": "live",
"generated_at": "2026-05-23T12:34:56.000Z",
"event_seq": 42,
"breakdown": [
{
"category": "system_prompt",
"tokens": 30000,
"usage_percent": 7.5
}
],
"warnings": []
},
"inference": {
"session_id": "ses_root",
"started_at": "2026-04-29T12:34:00Z",
@ -461,7 +376,7 @@ fn permission_level_matches_openapi_json_shape() {
}
#[test]
fn nested_agent_state_types_match_openapi_json_shape() {
fn todo_list_and_skill_types_match_openapi_json_shape() {
for (kind, list_id, wire_kind) in [
(
TodoListKind::OpenAiPlan,
@ -484,32 +399,6 @@ fn nested_agent_state_types_match_openapi_json_shape() {
assert_eq!(api_todo_list, todo_list);
}
let subagent = SubAgentProjection {
agent_id: "sub-1".to_string(),
depth: 1,
task: "Investigate failing test".to_string(),
status: SubAgentStatus::Completed {
success: true,
turns_used: 3,
},
};
let subagent_json = serde_json::to_value(&subagent).unwrap();
assert_eq!(
subagent_json,
json!({
"agent_id": "sub-1",
"depth": 1,
"task": "Investigate failing test",
"status": {
"kind": "completed",
"success": true,
"turns_used": 3
}
})
);
let api_subagent: ApiSubAgentProjection = serde_json::from_value(subagent_json).unwrap();
assert_eq!(api_subagent, subagent);
let skill = SkillSummary {
name: "rust".to_string(),
description: "Rust workflow help".to_string(),
@ -529,103 +418,6 @@ fn nested_agent_state_types_match_openapi_json_shape() {
assert_eq!(source_json, json!("slash"));
let api_source: ApiSkillActivationSource = serde_json::from_value(source_json).unwrap();
assert_eq!(api_source, SkillActivationSource::Slash);
let activated = ActivatedSkill {
name: "rust".to_string(),
source: SkillActivationSource::Slash,
};
let skills = SkillsProjection {
available: vec![skill],
activated: vec![activated],
};
let skills_json = serde_json::to_value(&skills).unwrap();
assert_eq!(
skills_json,
json!({
"available": [
{
"name": "rust",
"description": "Rust workflow help"
}
],
"activated": [
{
"name": "rust",
"source": "slash"
}
]
})
);
let api_skills: ApiSkillsProjection = serde_json::from_value(skills_json).unwrap();
assert_eq!(api_skills, skills);
let tool = AgentMcpToolSummary {
name: "read_file".to_string(),
original_name: "read_file".to_string(),
};
let tool_json = serde_json::to_value(&tool).unwrap();
assert_eq!(
tool_json,
json!({
"name": "read_file",
"original_name": "read_file"
})
);
let api_tool: ApiAgentMcpToolSummary = serde_json::from_value(tool_json).unwrap();
assert_eq!(api_tool, tool);
let mcp_server = McpServerProjection {
server_name: "filesystem".to_string(),
tool_count: 1,
status: McpServerStatus::Ready { tools: vec![tool] },
invoked: true,
};
let mcp_json = serde_json::to_value(&mcp_server).unwrap();
assert_eq!(
mcp_json,
json!({
"server_name": "filesystem",
"tool_count": 1,
"status": {
"kind": "ready",
"tools": [
{
"name": "read_file",
"original_name": "read_file"
}
]
},
"invoked": true,
})
);
let api_mcp: ApiMcpServerProjection = serde_json::from_value(mcp_json).unwrap();
assert_eq!(api_mcp, mcp_server);
assert_eq!(mcp_server.tool_count, 1);
let disconnected = McpServerProjection {
server_name: "filesystem".to_string(),
tool_count: 1,
status: McpServerStatus::Disconnected {
error: "transport closed".to_string(),
},
invoked: true,
};
let disconnected_json = serde_json::to_value(&disconnected).unwrap();
assert_eq!(
disconnected_json,
json!({
"server_name": "filesystem",
"tool_count": 1,
"status": {
"kind": "disconnected",
"error": "transport closed"
},
"invoked": true,
})
);
let api_disconnected: ApiMcpServerProjection =
serde_json::from_value(disconnected_json).unwrap();
assert_eq!(api_disconnected, disconnected);
}
#[test]

View file

@ -143,10 +143,9 @@ pub use run_intent::{
TargetValidationError, ValidatedGitRunTarget, ValidatedRunTarget,
};
pub use run_projection::{
ActivatedSkill, AgentControlState, CheckpointRecord, McpServerProjection, McpServerStatus,
PendingInterviewRecord, RunProjection, SkillsProjection, StageContextWindow,
AgentControlState, CheckpointRecord, PendingInterviewRecord, RunProjection, StageContextWindow,
StageContextWindowUnavailableReason, StageInferenceProjection, StageModelUsage,
StageProjection, StageToolBatchProjection, SubAgentProjection, SubAgentStatus, first_event_seq,
StageProjection, StageToolBatchProjection, first_event_seq,
};
pub use run_sandbox::{
RunSandbox, RunSandboxFailure, RunSandboxInstance, RunSandboxKind, RunSandboxPlan,

View file

@ -6,19 +6,18 @@ use chrono::{DateTime, Utc};
use lithos_llm::types::{ReasoningEffort, Speed};
use pebble_coding_agent::events::{
ContextWindowBreakdownItem, ContextWindowCountMethod, ContextWindowSnapshot,
ContextWindowStaleness, ContextWindowWarning, LlmOutputKind, PermissionLevel,
SkillActivationSource, SkillSummary, TodoListProjection, ToolSummary,
ContextWindowStaleness, ContextWindowWarning, LlmOutputKind, PermissionLevel, ToolSummary,
};
use pebble_coding_agent::projection::SessionProjection;
use strum::{Display, EnumString, IntoStaticStr};
use crate::run_event::{AgentSessionActivatedProps, StagePromptProps};
use crate::{
AgentBackend, AgentMcpToolSummary, BilledModelUsage, BilledTokenCounts, Checkpoint, Conclusion,
GitIdentity, InterviewQuestionRecord, InvalidTransition, ModelRef, ParallelBranchId,
PullRequestCreation, PullRequestLink, RunApproval, RunControlAction, RunDiff, RunId,
RunSandbox, RunSpec, RunStatus, RunTiming, StageCompletion, StageHandler, StageId, StageState,
StageTiming, StartRecord, timing,
AgentBackend, BilledModelUsage, BilledTokenCounts, Checkpoint, Conclusion, GitIdentity,
InterviewQuestionRecord, InvalidTransition, ModelRef, ParallelBranchId, PullRequestCreation,
PullRequestLink, RunApproval, RunControlAction, RunDiff, RunId, RunSandbox, RunSpec, RunStatus,
RunTiming, StageCompletion, StageHandler, StageId, StageState, StageTiming, StartRecord,
timing,
};
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
@ -304,25 +303,10 @@ pub struct StageProjection {
/// `usage` to `model`.
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub billing_by_model: Vec<BilledModelUsage>,
/// Todo/task list owned by the stage's root agent session.
///
/// OpenAI child sessions own separate per-session plans and do not appear
/// here. Anthropic task lists are root-scoped and shared with child
/// sessions, so child mutations of that shared list do appear here.
#[serde(default, rename = "todos", skip_serializing_if = "Option::is_none")]
pub root_agent_todos: Option<TodoListProjection>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub subagents: Vec<SubAgentProjection>,
#[serde(default, skip_serializing_if = "SkillsProjection::is_empty")]
pub skills: SkillsProjection,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub permission_level: Option<PermissionLevel>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub agent_tools: Vec<ToolSummary>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub mcp_servers: Vec<McpServerProjection>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub context_window: Option<ContextWindowSnapshot>,
/// Open inference bracket for this stage, if the event log contains one.
///
/// `Some` means exactly *"an `agent.llm.started` was recorded and no
@ -341,10 +325,12 @@ pub struct StageProjection {
#[serde(default)]
pub agent_control: AgentControlState,
/// Pebble's fold of this stage's agent events: the one agent projection,
/// fed every `agent.*` and `todo.*` event stored on the stage. Present
/// for pebble-backed agent stages once their first agent event is
/// stored; `None` for prompt, command, ACP, human, parallel, and
/// conditional stages.
/// fed every `agent.*` and `todo.*` event stored on the stage, and what
/// the stage view reads for todos, subagents, skills, MCP servers, files,
/// failovers, compactions, and the context window. Present for
/// pebble-backed agent stages once their first agent event is stored;
/// `None` for prompt, command, ACP, human, parallel, and conditional
/// stages.
///
/// Its lifetime fields are the stage's totals across every prompt the
/// stage ran, because each stage gets its own fold over its own events.
@ -427,67 +413,6 @@ pub enum AgentControlState {
WaitingForSteer,
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct SubAgentProjection {
pub agent_id: String,
pub depth: usize,
pub task: String,
pub status: SubAgentStatus,
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum SubAgentStatus {
Running,
Completed { success: bool, turns_used: usize },
Failed { error: serde_json::Value },
Closed,
}
#[derive(Debug, Clone, Default, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct SkillsProjection {
pub available: Vec<SkillSummary>,
pub activated: Vec<ActivatedSkill>,
}
impl SkillsProjection {
#[must_use]
pub fn is_empty(&self) -> bool {
self.available.is_empty() && self.activated.is_empty()
}
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct ActivatedSkill {
pub name: String,
pub source: SkillActivationSource,
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct McpServerProjection {
pub server_name: String,
pub tool_count: usize,
pub status: McpServerStatus,
/// True once any tool from this server has been invoked during the stage.
pub invoked: bool,
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum McpServerStatus {
Ready {
tools: Vec<AgentMcpToolSummary>,
},
Failed {
error: String,
},
/// The server was ready and then its connection closed during the
/// stage; its tools fail until the session ends.
Disconnected {
error: String,
},
}
/// Convert a 1-based event sequence number into the `NonZeroU32` form used for
/// `StageProjection::first_event_seq`. Run event seqs always start at 1.
#[must_use]
@ -510,13 +435,8 @@ impl StageProjection {
tool_batch: None,
usage: BilledTokenCounts::default(),
model: None,
root_agent_todos: None,
subagents: Vec::new(),
skills: SkillsProjection::default(),
permission_level: None,
agent_tools: Vec::new(),
mcp_servers: Vec::new(),
context_window: None,
inference: None,
acp_started_at: None,
agent_control: AgentControlState::default(),

View file

@ -27,12 +27,10 @@ base.ts
common.ts
configuration.ts
index.ts
models/activated-skill.ts
models/agent-control-state.ts
models/agent-error-data.ts
models/agent-error-kind.ts
models/agent-event-props.ts
models/agent-mcp-tool-summary.ts
models/agent-session-activated-props.ts
models/agent-session-activated-skill.ts
models/agent-session-activity.ts
@ -244,12 +242,7 @@ models/manifest-workflow.ts
models/mcp-http-protocol.ts
models/mcp-server-list-meta.ts
models/mcp-server-list-response.ts
models/mcp-server-projection.ts
models/mcp-server-settings.ts
models/mcp-server-status-disconnected.ts
models/mcp-server-status-failed.ts
models/mcp-server-status-ready.ts
models/mcp-server-status.ts
models/mcp-server.ts
models/mcp-tool-summary.ts
models/mcp-transport-http.ts
@ -508,7 +501,6 @@ models/session-summary.ts
models/session-turn.ts
models/skill-activation-source.ts
models/skill-summary.ts
models/skills-projection.ts
models/slack-integration-settings.ts
models/ssh-access-request.ts
models/ssh-access-response.ts
@ -527,12 +519,6 @@ models/stage-tool-batch-projection.ts
models/start-record.ts
models/start-run-request.ts
models/steer-run-request.ts
models/sub-agent-projection.ts
models/sub-agent-status-closed.ts
models/sub-agent-status-completed.ts
models/sub-agent-status-failed.ts
models/sub-agent-status-running.ts
models/sub-agent-status.ts
models/submit-answer-multi-selected-request.ts
models/submit-answer-no-request.ts
models/submit-answer-request.ts

View file

@ -1,26 +0,0 @@
/* tslint:disable */
/* eslint-disable */
/**
* Fabro Run API
* HTTP API for managing Fabro workflow run executions.
*
* The version of the OpenAPI document: 0.2.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
// May contain unused imports in some cases
// @ts-ignore
import type { SkillActivationSource } from './skill-activation-source';
/**
* One observed agent skill activation.
*/
export interface ActivatedSkill {
'name': string;
'source': SkillActivationSource;
}

View file

@ -1,23 +0,0 @@
/* tslint:disable */
/* eslint-disable */
/**
* Fabro Run API
* HTTP API for managing Fabro workflow run executions.
*
* The version of the OpenAPI document: 0.2.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
/**
* Summary of one tool exposed by an MCP server.
*/
export interface AgentMcpToolSummary {
'name': string;
'original_name': string;
}

View file

@ -1,9 +1,7 @@
export * from './activated-skill';
export * from './agent-control-state';
export * from './agent-error-data';
export * from './agent-error-kind';
export * from './agent-event-props';
export * from './agent-mcp-tool-summary';
export * from './agent-session-activated-props';
export * from './agent-session-activated-skill';
export * from './agent-session-activity';
@ -215,12 +213,7 @@ export * from './mcp-http-protocol';
export * from './mcp-server';
export * from './mcp-server-list-meta';
export * from './mcp-server-list-response';
export * from './mcp-server-projection';
export * from './mcp-server-settings';
export * from './mcp-server-status';
export * from './mcp-server-status-disconnected';
export * from './mcp-server-status-failed';
export * from './mcp-server-status-ready';
export * from './mcp-tool-summary';
export * from './mcp-transport';
export * from './mcp-transport-http';
@ -478,7 +471,6 @@ export * from './session-summary';
export * from './session-turn';
export * from './skill-activation-source';
export * from './skill-summary';
export * from './skills-projection';
export * from './slack-integration-settings';
export * from './ssh-access-request';
export * from './ssh-access-response';
@ -497,12 +489,6 @@ export * from './stage-tool-batch-projection';
export * from './start-record';
export * from './start-run-request';
export * from './steer-run-request';
export * from './sub-agent-projection';
export * from './sub-agent-status';
export * from './sub-agent-status-closed';
export * from './sub-agent-status-completed';
export * from './sub-agent-status-failed';
export * from './sub-agent-status-running';
export * from './submit-answer-multi-selected-request';
export * from './submit-answer-no-request';
export * from './submit-answer-request';

View file

@ -1,31 +0,0 @@
/* tslint:disable */
/* eslint-disable */
/**
* Fabro Run API
* HTTP API for managing Fabro workflow run executions.
*
* The version of the OpenAPI document: 0.2.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
// May contain unused imports in some cases
// @ts-ignore
import type { McpServerStatus } from './mcp-server-status';
/**
* Projected state for one MCP server observed by an agent stage.
*/
export interface McpServerProjection {
'server_name': string;
'tool_count': number;
'status': McpServerStatus;
/**
* True once the agent has invoked at least one tool from this server during the stage.
*/
'invoked': boolean;
}

View file

@ -1,32 +0,0 @@
/* tslint:disable */
/* eslint-disable */
/**
* Fabro Run API
* HTTP API for managing Fabro workflow run executions.
*
* The version of the OpenAPI document: 0.2.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
/**
* The server was ready and then its connection closed during the stage; its tools fail until the session ends.
*/
export interface McpServerStatusDisconnected {
'kind': McpServerStatusDisconnectedKindEnum;
/**
* What closed the connection, as the client observed it.
*/
'error': string;
}
export const McpServerStatusDisconnectedKindEnum = {
DISCONNECTED: 'disconnected'
} as const;
export type McpServerStatusDisconnectedKindEnum = typeof McpServerStatusDisconnectedKindEnum[keyof typeof McpServerStatusDisconnectedKindEnum];

View file

@ -1,26 +0,0 @@
/* tslint:disable */
/* eslint-disable */
/**
* Fabro Run API
* HTTP API for managing Fabro workflow run executions.
*
* The version of the OpenAPI document: 0.2.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
export interface McpServerStatusFailed {
'kind': McpServerStatusFailedKindEnum;
'error': string;
}
export const McpServerStatusFailedKindEnum = {
FAILED: 'failed'
} as const;
export type McpServerStatusFailedKindEnum = typeof McpServerStatusFailedKindEnum[keyof typeof McpServerStatusFailedKindEnum];

View file

@ -1,29 +0,0 @@
/* tslint:disable */
/* eslint-disable */
/**
* Fabro Run API
* HTTP API for managing Fabro workflow run executions.
*
* The version of the OpenAPI document: 0.2.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
// May contain unused imports in some cases
// @ts-ignore
import type { AgentMcpToolSummary } from './agent-mcp-tool-summary';
export interface McpServerStatusReady {
'kind': McpServerStatusReadyKindEnum;
'tools': Array<AgentMcpToolSummary>;
}
export const McpServerStatusReadyKindEnum = {
READY: 'ready'
} as const;
export type McpServerStatusReadyKindEnum = typeof McpServerStatusReadyKindEnum[keyof typeof McpServerStatusReadyKindEnum];

View file

@ -1,33 +0,0 @@
/* tslint:disable */
/* eslint-disable */
/**
* Fabro Run API
* HTTP API for managing Fabro workflow run executions.
*
* The version of the OpenAPI document: 0.2.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
// May contain unused imports in some cases
// @ts-ignore
import type { AgentMcpToolSummary } from './agent-mcp-tool-summary';
// May contain unused imports in some cases
// @ts-ignore
import type { McpServerStatusDisconnected } from './mcp-server-status-disconnected';
// May contain unused imports in some cases
// @ts-ignore
import type { McpServerStatusFailed } from './mcp-server-status-failed';
// May contain unused imports in some cases
// @ts-ignore
import type { McpServerStatusReady } from './mcp-server-status-ready';
/**
* @type McpServerStatus
* Projected MCP server readiness status.
*/
export type McpServerStatus = { kind: 'disconnected' } & McpServerStatusDisconnected | { kind: 'failed' } & McpServerStatusFailed | { kind: 'ready' } & McpServerStatusReady;

View file

@ -1,29 +0,0 @@
/* tslint:disable */
/* eslint-disable */
/**
* Fabro Run API
* HTTP API for managing Fabro workflow run executions.
*
* The version of the OpenAPI document: 0.2.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
// May contain unused imports in some cases
// @ts-ignore
import type { ActivatedSkill } from './activated-skill';
// May contain unused imports in some cases
// @ts-ignore
import type { SkillSummary } from './skill-summary';
/**
* Agent skills discovered and activated during a stage.
*/
export interface SkillsProjection {
'available': Array<SkillSummary>;
'activated': Array<ActivatedSkill>;
}

View file

@ -50,6 +50,9 @@ export interface StageContextWindow {
'count_method': ContextWindowCountMethod | null;
'staleness': ContextWindowStaleness;
'generated_at': string | null;
/**
* The coding agent\'s own event sequence for the snapshot, when it carried one; not the run event sequence.
*/
'event_seq': number | null;
'breakdown': Array<ContextWindowBreakdownItem>;
'warnings': Array<ContextWindowWarning>;

View file

@ -33,21 +33,12 @@ import type { BillingModelRef } from './billing-model-ref';
import type { CommandTermination } from './command-termination';
// May contain unused imports in some cases
// @ts-ignore
import type { ContextWindowSnapshot } from './context-window-snapshot';
// May contain unused imports in some cases
// @ts-ignore
import type { McpServerProjection } from './mcp-server-projection';
// May contain unused imports in some cases
// @ts-ignore
import type { ParallelBranchResult } from './parallel-branch-result';
// May contain unused imports in some cases
// @ts-ignore
import type { PermissionLevel } from './permission-level';
// May contain unused imports in some cases
// @ts-ignore
import type { SkillsProjection } from './skills-projection';
// May contain unused imports in some cases
// @ts-ignore
import type { StageCompletion } from './stage-completion';
// May contain unused imports in some cases
// @ts-ignore
@ -66,12 +57,6 @@ import type { StageTiming } from './stage-timing';
import type { StageToolBatchProjection } from './stage-tool-batch-projection';
// May contain unused imports in some cases
// @ts-ignore
import type { SubAgentProjection } from './sub-agent-projection';
// May contain unused imports in some cases
// @ts-ignore
import type { TodoListProjection } from './todo-list-projection';
// May contain unused imports in some cases
// @ts-ignore
import type { ToolSummary } from './tool-summary';
/**
@ -120,25 +105,11 @@ export interface StageProjection {
'tool_batch'?: StageToolBatchProjection | null;
'usage': BilledTokenCounts;
'model'?: BillingModelRef | null;
'todos'?: TodoListProjection | null;
/**
* Subagents spawned by this stage, in replay/insertion order.
*/
'subagents'?: Array<SubAgentProjection>;
/**
* Agent skills discovered and activated during this stage.
*/
'skills'?: SkillsProjection;
'permission_level'?: PermissionLevel | null;
/**
* Effective model-callable tools exposed to this agent stage session. Tool parameter schemas are intentionally omitted from this projection.
*/
'agent_tools'?: Array<ToolSummary>;
/**
* MCP servers observed by this stage.
*/
'mcp_servers'?: Array<McpServerProjection>;
'context_window'?: ContextWindowSnapshot | null;
'inference'?: StageInferenceProjection | null;
/**
* Start of an external ACP agent process, if one is running. ACP agents do not expose Fabro\'s internal LLM brackets, so the process lifetime supplies their live inference estimate.

View file

@ -1,28 +0,0 @@
/* tslint:disable */
/* eslint-disable */
/**
* Fabro Run API
* HTTP API for managing Fabro workflow run executions.
*
* The version of the OpenAPI document: 0.2.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
// May contain unused imports in some cases
// @ts-ignore
import type { SubAgentStatus } from './sub-agent-status';
/**
* Current projected state for one subagent spawned by an agent stage.
*/
export interface SubAgentProjection {
'agent_id': string;
'depth': number;
'task': string;
'status': SubAgentStatus;
}

View file

@ -1,25 +0,0 @@
/* tslint:disable */
/* eslint-disable */
/**
* Fabro Run API
* HTTP API for managing Fabro workflow run executions.
*
* The version of the OpenAPI document: 0.2.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
export interface SubAgentStatusClosed {
'kind': SubAgentStatusClosedKindEnum;
}
export const SubAgentStatusClosedKindEnum = {
CLOSED: 'closed'
} as const;
export type SubAgentStatusClosedKindEnum = typeof SubAgentStatusClosedKindEnum[keyof typeof SubAgentStatusClosedKindEnum];

View file

@ -1,27 +0,0 @@
/* tslint:disable */
/* eslint-disable */
/**
* Fabro Run API
* HTTP API for managing Fabro workflow run executions.
*
* The version of the OpenAPI document: 0.2.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
export interface SubAgentStatusCompleted {
'kind': SubAgentStatusCompletedKindEnum;
'success': boolean;
'turns_used': number;
}
export const SubAgentStatusCompletedKindEnum = {
COMPLETED: 'completed'
} as const;
export type SubAgentStatusCompletedKindEnum = typeof SubAgentStatusCompletedKindEnum[keyof typeof SubAgentStatusCompletedKindEnum];

View file

@ -1,26 +0,0 @@
/* tslint:disable */
/* eslint-disable */
/**
* Fabro Run API
* HTTP API for managing Fabro workflow run executions.
*
* The version of the OpenAPI document: 0.2.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
export interface SubAgentStatusFailed {
'kind': SubAgentStatusFailedKindEnum;
'error': any;
}
export const SubAgentStatusFailedKindEnum = {
FAILED: 'failed'
} as const;
export type SubAgentStatusFailedKindEnum = typeof SubAgentStatusFailedKindEnum[keyof typeof SubAgentStatusFailedKindEnum];

View file

@ -1,25 +0,0 @@
/* tslint:disable */
/* eslint-disable */
/**
* Fabro Run API
* HTTP API for managing Fabro workflow run executions.
*
* The version of the OpenAPI document: 0.2.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
export interface SubAgentStatusRunning {
'kind': SubAgentStatusRunningKindEnum;
}
export const SubAgentStatusRunningKindEnum = {
RUNNING: 'running'
} as const;
export type SubAgentStatusRunningKindEnum = typeof SubAgentStatusRunningKindEnum[keyof typeof SubAgentStatusRunningKindEnum];

View file

@ -1,33 +0,0 @@
/* tslint:disable */
/* eslint-disable */
/**
* Fabro Run API
* HTTP API for managing Fabro workflow run executions.
*
* The version of the OpenAPI document: 0.2.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
// May contain unused imports in some cases
// @ts-ignore
import type { SubAgentStatusClosed } from './sub-agent-status-closed';
// May contain unused imports in some cases
// @ts-ignore
import type { SubAgentStatusCompleted } from './sub-agent-status-completed';
// May contain unused imports in some cases
// @ts-ignore
import type { SubAgentStatusFailed } from './sub-agent-status-failed';
// May contain unused imports in some cases
// @ts-ignore
import type { SubAgentStatusRunning } from './sub-agent-status-running';
/**
* @type SubAgentStatus
* Projected lifecycle status for a subagent.
*/
export type SubAgentStatus = { kind: 'closed' } & SubAgentStatusClosed | { kind: 'completed' } & SubAgentStatusCompleted | { kind: 'failed' } & SubAgentStatusFailed | { kind: 'running' } & SubAgentStatusRunning;