/** * Command catalog — every client→server command (PROTOCOL §9.2, §9.5, §10.1). * * Per-agent commands carry an OPTIONAL `agent_id` (omitted on direct connections, required * on daemon — §5.2). Orchestrator commands (§10.1, daemon-only) MUST NOT carry `agent_id` * at all; this is encoded by omitting the field from their schema AND setting * `additionalProperties: false`, so a stray `agent_id` is rejected by validation. */ import { Type, type Static } from '@sinclair/typebox'; import { ImageSchema } from '@/types/image'; import { ToolUsageBucketGranularitySchema } from '@/contracts/tool-usage-stats'; import { TokenUsageBucketGranularitySchema } from '@/contracts/token-usage-stats'; import { SubagentCreateCommandSchema, SubagentWaitCommandSchema, SubagentSteerCommandSchema, SubagentRelaunchCommandSchema, SubagentStatusCommandSchema, SubagentStopCommandSchema, } from '@/protocol/subagent-commands'; const agentIdOptional = Type.Optional(Type.String()); const correlationId = Type.String(); // ─── Per-agent drive commands (§9.2) ───────────────────────────────────────────── export const PromptCommandSchema = Type.Object({ type: Type.Literal('prompt'), agent_id: agentIdOptional, message: Type.String(), images: Type.Optional(Type.Array(ImageSchema)), /** Required if the agent is mid-turn; omit when idle (§9.2). */ streaming_behavior: Type.Optional( Type.Union([Type.Literal('steer'), Type.Literal('follow_up')]), ), }); export const SteerCommandSchema = Type.Object({ type: Type.Literal('steer'), agent_id: agentIdOptional, message: Type.String(), images: Type.Optional(Type.Array(ImageSchema)), /** * Managed (strict) active-steer correlation (saga u4uo9m.3 plan item 6). Present only * when the daemon coordinator drives a leased run's strict steer handshake; the child binds * it to the in-flight `turn_id`, acknowledges via a `managed_steer_ack` event, and rejects * (conflict) if that turn ended. Absent → ordinary unleased idle-steer behavior (the * `steer()` queue), unchanged and backward compatible. */ correlation: Type.Optional( Type.Object({ run_id: Type.String(), turn_id: Type.String(), correlation_id: Type.String(), }), ), }); export const AbortCommandSchema = Type.Object({ type: Type.Literal('abort'), agent_id: agentIdOptional, }); export const SetModeCommandSchema = Type.Object({ type: Type.Literal('set_mode'), agent_id: agentIdOptional, mode: Type.String(), }); export const ListModesCommandSchema = Type.Object( { type: Type.Literal('list_modes'), id: Type.Optional(Type.String()), agent_id: agentIdOptional, }, // additionalProperties:false (saga vr7kgx.6): `list_modes` has no payload — reject a // stray `mode` (a `set_mode`-shaped mistake) rather than silently accepting it. The // correlation `id` (§5.1) and daemon routing `agent_id` (§5.2) remain allowed. { additionalProperties: false }, ); export const ListModelsCommandSchema = Type.Object({ type: Type.Literal('list_models'), agent_id: agentIdOptional, }); export const SetModelCommandSchema = Type.Object({ type: Type.Literal('set_model'), agent_id: agentIdOptional, model: Type.String(), }); /** * `list_reasoning_levels` (saga p7m8jp.3) — request/response correlated. Returns the active * model's reasoning effort catalog (levels, active effort). `id` is required (§5.1); * `agent_id` is omitted on direct connections and required on daemon connections (§5.2). * Strict (`additionalProperties: false`) — a stray `model`/`effort` is rejected. */ export const ListReasoningLevelsCommandSchema = Type.Object( { type: Type.Literal('list_reasoning_levels'), id: correlationId, agent_id: agentIdOptional, }, { additionalProperties: false }, ); /** * `set_reasoning_level` (saga p7m8jp.3) — request/response correlated. Changes the active * model's reasoning effort. `model` is an optimistic-concurrency precondition (the active * descriptor ID); a stale value returns `CONFLICT`. `effort` must be a non-empty string * supported by the active model. `id` is required (§5.1); `agent_id` follows §5.2. Strict. */ export const SetReasoningLevelCommandSchema = Type.Object( { type: Type.Literal('set_reasoning_level'), id: correlationId, agent_id: agentIdOptional, model: Type.String(), effort: Type.String({ minLength: 1 }), }, { additionalProperties: false }, ); /** * Loose v0.1 session-op args record. Op-specific shapes (`fork` takes `from_entry_id`; * `compact` takes an optional `keep_recent_tokens`) are pinned in the session-ops * sub-spec in transports/stdio.ts. `branch`/`rewind` were dropped before * implementation (saga 3aliod): fork-to-new-session is the single mutation primitive. */ export const SessionOpArgsSchema = Type.Record(Type.String(), Type.Unknown()); export const SessionOpCommandSchema = Type.Object({ type: Type.Literal('session_op'), agent_id: agentIdOptional, op: Type.Union([ Type.Literal('list'), Type.Literal('compact'), Type.Literal('fork'), ]), args: Type.Optional(SessionOpArgsSchema), }); // ─── Permission / UI response commands (§9.5, fire-and-forget) ──────────────────── const PermissionResponseDecisionSchema = Type.Union([ Type.Literal('allow'), Type.Literal('allow_always'), Type.Literal('deny'), ]); const PermissionResponseDecisionFormSchema = Type.Object( { type: Type.Literal('permission_response'), agent_id: agentIdOptional, /** Optional streamed-command correlation; §5.1 permits clients to omit it. */ id: Type.Optional(Type.String()), request_id: Type.String(), decision: PermissionResponseDecisionSchema, source: Type.Optional(Type.String()), reason: Type.Optional(Type.String()), }, { additionalProperties: false }, ); const PermissionResponseChoiceFormSchema = Type.Object( { type: Type.Literal('permission_response'), agent_id: agentIdOptional, /** Optional streamed-command correlation; §5.1 permits clients to omit it. */ id: Type.Optional(Type.String()), request_id: Type.String(), choice_id: Type.String({ minLength: 1 }), source: Type.Optional(Type.String()), reason: Type.Optional(Type.String()), }, { additionalProperties: false }, ); /** Exactly one legacy decision or opaque server-issued choice ID. */ export const PermissionResponseCommandSchema = Type.Union([ PermissionResponseDecisionFormSchema, PermissionResponseChoiceFormSchema, ]); export const UiResponseCommandSchema = Type.Object({ type: Type.Literal('ui_response'), agent_id: agentIdOptional, request_id: Type.String(), value: Type.Union([Type.String(), Type.Array(Type.String()), Type.Null()]), /** Identifies daemon-generated fallback responses while remaining optional for v0.1 clients. */ source: Type.Optional(Type.String()), }); // ─── Orchestrator commands (§10.1, daemon-only) — NO agent_id field (§5.2 / §10) ─── // additionalProperties:false enforces the rule: a stray agent_id is rejected. export const AgentListCommandSchema = Type.Object( { type: Type.Literal('agent.list'), id: correlationId, }, { additionalProperties: false }, ); export const AgentCreateCommandSchema = Type.Object( { type: Type.Literal('agent.create'), id: correlationId, cwd: Type.String(), /** Adopt an existing opaque session; resolved and validated by the daemon. */ session_id: Type.Optional(Type.String({ minLength: 1 })), model: Type.Optional(Type.String()), provider: Type.Optional(Type.String()), mode: Type.Optional(Type.String()), tools: Type.Optional(Type.Array(Type.String())), skills: Type.Optional(Type.Array(Type.String())), name: Type.Optional(Type.String()), /** Set iff this is a subagent; the parent's `subagent` tool uses this (§10.1). */ parent_agent_id: Type.Optional(Type.String()), /** Byte-bounded pinned system prompt (saga u4uo9m.1). Private — never broadcast. */ system_prompt: Type.Optional(Type.String()), /** Launch policy for launches made BY this agent (saga u4uo9m.1). Ignored for root. */ launch_policy: Type.Optional( Type.Object({ maxTasks: Type.Integer({ minimum: 1 }), maxDepth: Type.Integer({ minimum: 0 }) }), ), /** Per-run whole-lifecycle deadline seconds (saga u4uo9m.1; 0/omitted = no deadline). */ timeout: Type.Optional(Type.Number({ minimum: 0 })), /** Definition name this Agent was launched from (saga u4uo9m.1). */ definition_name: Type.Optional(Type.String()), /** Durable parent-scoped friendly alias (saga u4uo9m.1). */ alias: Type.Optional(Type.String()), /** Derived display name (saga u4uo9m.1). */ display_name: Type.Optional(Type.String()), }, { additionalProperties: false }, ); /** Broker attachment role (saga u4uo9m.2). `interactive` (default) counts as a * permission/UI responder; `observer` receives events but never answers prompts. */ export const AttachmentRoleSchema = Type.Union([ Type.Literal('interactive'), Type.Literal('observer'), ]); export type AttachmentRole = Static; /** `agent_id` here is the target arg (payload), not routing — §10.1. The optional `role` * (saga u4uo9m.2) defaults to `interactive`; `observer` attachments receive the event * stream but never count as permission/UI responders. */ export const AgentAttachCommandSchema = Type.Object( { type: Type.Literal('agent.attach'), id: correlationId, agent_id: Type.String(), role: Type.Optional(AttachmentRoleSchema), }, { additionalProperties: false }, ); export const AgentDetachCommandSchema = Type.Object( { type: Type.Literal('agent.detach'), id: correlationId, agent_id: Type.String(), }, { additionalProperties: false }, ); /** Request normal runtime teardown. Busy agents finish their current work before stopping. */ export const AgentTerminateCommandSchema = Type.Object( { type: Type.Literal('agent.terminate'), id: correlationId, agent_id: Type.String(), }, { additionalProperties: false }, ); export const AgentKillCommandSchema = Type.Object( { type: Type.Literal('agent.kill'), id: correlationId, agent_id: Type.String(), force: Type.Optional(Type.Boolean()), }, { additionalProperties: false }, ); export const AgentStatusCommandSchema = Type.Object( { type: Type.Literal('agent.status'), id: correlationId, /** Omit for all agents (§10.1). */ agent_id: Type.Optional(Type.String()), }, { additionalProperties: false }, ); export const WebApproveCommandSchema = Type.Object({ type: Type.Literal('web.approve'), id: correlationId, code: Type.String({ pattern: '^[0-9]{6}$' }) }, { additionalProperties: false }); export const WebTokenCreateCommandSchema = Type.Object({ type: Type.Literal('web.token.create'), id: correlationId, name: Type.String({ minLength: 1, maxLength: 64, pattern: '^\\S+$' }) }, { additionalProperties: false }); export const WebClientsCommandSchema = Type.Object({ type: Type.Literal('web.clients'), id: correlationId }, { additionalProperties: false }); export const WebRevokeCommandSchema = Type.Union([ Type.Object({ type: Type.Literal('web.revoke'), id: correlationId, browser_id: Type.String({ minLength: 1 }) }, { additionalProperties: false }), Type.Object({ type: Type.Literal('web.revoke'), id: correlationId, all: Type.Literal(true) }, { additionalProperties: false }), ]); export const WebTokenRotateCommandSchema = Type.Object({ type: Type.Literal('web.token.rotate'), id: correlationId }, { additionalProperties: false }); export const DaemonShutdownCommandSchema = Type.Object( { type: Type.Literal('daemon.shutdown'), id: correlationId, force: Type.Optional(Type.Boolean()), }, { additionalProperties: false }, ); /** Create a durable session and its initial runtime. */ export const SessionCreateCommandSchema = Type.Object( { type: Type.Literal('session.create'), id: correlationId, cwd: Type.String({ minLength: 1 }), title: Type.Optional(Type.String({ minLength: 1 })), model: Type.Optional(Type.String({ minLength: 1 })), provider: Type.Optional(Type.String({ minLength: 1 })), mode: Type.Optional(Type.String({ minLength: 1 })), }, { additionalProperties: false }, ); const SessionRuntimeTargetSchema = { id: correlationId, session_id: Type.String({ minLength: 1 }), cwd: Type.String({ minLength: 1 }), }; export const SessionStatusCommandSchema = Type.Object( { type: Type.Literal('session.status'), ...SessionRuntimeTargetSchema }, { additionalProperties: false }, ); export const SessionTerminateCommandSchema = Type.Object( { type: Type.Literal('session.terminate'), ...SessionRuntimeTargetSchema }, { additionalProperties: false }, ); export const SessionKillCommandSchema = Type.Object( { type: Type.Literal('session.kill'), ...SessionRuntimeTargetSchema, force: Type.Optional(Type.Boolean()) }, { additionalProperties: false }, ); /** Ensure one runtime for a durable session and subscribe the requesting connection. */ export const SessionActivateCommandSchema = Type.Object( { type: Type.Literal('session.activate'), ...SessionRuntimeTargetSchema }, { additionalProperties: false }, ); /** * List all daemon-owned sessions, or retain the legacy cwd-filtered picker form. * * `scope` (saga j8dtju.7) controls how a `cwd` filter is interpreted: * - `'repository'` (default when omitted): for a Git-backed cwd, resolve the repository * scope via the centralized Git resolver and return sessions across all sibling worktrees * in the same common-dir, tagged with workspace metadata. Non-Git cwd falls back to * exact-cwd matching. * - `'exact'`: preserve the legacy exact-canonical-cwd picker form regardless of Git state. * * The field is additive/optional: existing clients that omit it get the new repository- * scoped behavior for Git-backed cwds (an intentional improvement) and unchanged exact-cwd * behavior for non-Git cwds. */ export const SessionListCommandSchema = Type.Object( { type: Type.Literal('session.list'), id: correlationId, cwd: Type.Optional(Type.String({ minLength: 1 })), scope: Type.Optional(Type.Union([Type.Literal('repository'), Type.Literal('exact')])), }, { additionalProperties: false }, ); /** Explicit replay with stable backwards pagination pinned to an active-lineage tail. */ export const SessionReplayCommandSchema = Type.Object( { type: Type.Literal('session.replay'), id: correlationId, session_id: Type.String({ minLength: 1 }), cwd: Type.String({ minLength: 1 }), limit: Type.Optional(Type.Integer({ minimum: 1 })), tail_id: Type.Optional(Type.String({ minLength: 1 })), before_entry_id: Type.Optional(Type.String({ minLength: 1 })), }, { additionalProperties: false }, ); /** * Aggregate tool-usage stats over time (PROTOCOL §10 stats surface). Orchestrator-level * (no routing `agent_id`); the daemon derives the aggregate from the durable session JSONL * and returns counts/errors/latency only — never raw args/paths/results (see * `ToolUsageStatsSchema`). All filters optional; omitting them yields a global aggregate. */ export const StatsToolUsageCommandSchema = Type.Object( { type: Type.Literal('stats.tool_usage'), id: correlationId, /** Inclusive lower bound (epoch ms). Omit for "all history". */ from: Type.Optional(Type.Integer({ minimum: 0 })), /** Exclusive upper bound (epoch ms). Omit for "now". */ to: Type.Optional(Type.Integer({ minimum: 0 })), /** Time-series bucket granularity. Defaults to `day`. */ bucket: Type.Optional(ToolUsageBucketGranularitySchema), /** Restrict to one durable session. Omit for a global aggregate across all sessions. */ session_id: Type.Optional(Type.String({ minLength: 1 })), /** Restrict to one tool name. Omit for all tools. */ tool: Type.Optional(Type.String({ minLength: 1 })), /** * Whether to include delegated subagent (child) sessions. Default `true` — a global * dashboard counts all tool activity. Subagent children carry `parent_session_id` * linkage; the aggregate does not expose that linkage, only the counts. */ include_subagents: Type.Optional(Type.Boolean()), }, { additionalProperties: false }, ); /** * Aggregate token/cost usage by model & provider over time (PROTOCOL §10 stats surface; * sibling to `stats.tool_usage`). Orchestrator-level (no routing `agent_id`); the daemon * derives the aggregate from its append-only usage log (`~/.shard/usage.jsonl`) and returns * token/cache/cost counts only — never prompts, completions, or agent ids (see * `TokenUsageStatsSchema`). All filters optional; omitting them yields a global aggregate. */ export const StatsTokenUsageCommandSchema = Type.Object( { type: Type.Literal('stats.token_usage'), id: correlationId, /** Inclusive lower bound (epoch ms). Omit for "all history". */ from: Type.Optional(Type.Integer({ minimum: 0 })), /** Exclusive upper bound (epoch ms). Omit for "now". */ to: Type.Optional(Type.Integer({ minimum: 0 })), /** Time-series bucket granularity. Defaults to `day`. */ bucket: Type.Optional(TokenUsageBucketGranularitySchema), }, { additionalProperties: false }, ); // ─── Workspace orchestrator commands (saga j8dtju.6, §10.1 additive) ───────────── /** Branch cleanup policy used by workspace retirement. */ export const BranchDispositionSchema = Type.Union([ Type.Literal('delete_if_merged'), Type.Literal('keep'), Type.Literal('delete_confirmed'), ]); export type BranchDisposition = Static; /** Launch fields shared by workspace creation and fresh follow-up attachment. */ const WorkspaceLaunchFields = { /** Session display title for the newly-created conversation. */ title: Type.Optional(Type.String({ minLength: 1 })), /** Agent display/name metadata for the newly-created runtime. */ name: Type.Optional(Type.String({ minLength: 1 })), model: Type.Optional(Type.String({ minLength: 1 })), provider: Type.Optional(Type.String({ minLength: 1 })), mode: Type.Optional(Type.String({ minLength: 1 })), }; /** Create a managed workspace and launch a fresh daemon-generated Agent + Session. */ export const WorkspaceCreateCommandSchema = Type.Object( { type: Type.Literal('workspace.create'), id: correlationId, version: Type.Optional(Type.Literal(1)), cwd: Type.String({ minLength: 1 }), label: Type.Optional(Type.String({ minLength: 1 })), branch: Type.Optional(Type.String({ minLength: 1 })), base_ref: Type.Optional(Type.String({ minLength: 1 })), integration_target: Type.Optional(Type.String({ minLength: 1 })), ...WorkspaceLaunchFields, }, { additionalProperties: false }, ); /** Attach by workspace id and launch a fresh daemon-generated Agent + Session. */ export const WorkspaceAttachCommandSchema = Type.Object( { type: Type.Literal('workspace.attach'), id: correlationId, version: Type.Optional(Type.Literal(1)), workspace_id: Type.String({ minLength: 1 }), ...WorkspaceLaunchFields, }, { additionalProperties: false }, ); export const WorkspaceListCommandSchema = Type.Object( { type: Type.Literal('workspace.list'), id: correlationId, version: Type.Optional(Type.Literal(1)), cwd: Type.Optional(Type.String({ minLength: 1 })), include_retired: Type.Optional(Type.Boolean()), }, { additionalProperties: false }, ); export const WorkspaceStatusCommandSchema = Type.Object( { type: Type.Literal('workspace.status'), id: correlationId, version: Type.Optional(Type.Literal(1)), workspace_id: Type.String({ minLength: 1 }), }, { additionalProperties: false }, ); export const WorkspaceRetireCommandSchema = Type.Object( { type: Type.Literal('workspace.retire'), id: correlationId, version: Type.Optional(Type.Literal(1)), workspace_id: Type.String({ minLength: 1 }), branch_disposition: Type.Optional(BranchDispositionSchema), }, { additionalProperties: false }, ); // ─── Subagent job orchestrator commands (saga u4uo9m.1, §10.1 additive) ────────── // Orchestrator-level (no routing agent_id); ownership is the `parent_agent_id` payload // field. Schemas + types live in `src/protocol/subagent-commands.ts`; re-exported here so // the closed `CommandSchema` union + `Command` type stay the single wire-catalog surface. export { SubagentCreateCommandSchema, SubagentWaitCommandSchema, SubagentSteerCommandSchema, SubagentRelaunchCommandSchema, SubagentStatusCommandSchema, SubagentStopCommandSchema, type SubagentCreateCommand, type SubagentWaitCommand, type SubagentSteerCommand, type SubagentRelaunchCommand, type SubagentStatusCommand, type SubagentStopCommand, } from '@/protocol/subagent-commands'; // ─── Union ──────────────────────────────────────────────────────────────────────── export const CommandSchema = Type.Union([ PromptCommandSchema, SteerCommandSchema, AbortCommandSchema, SetModeCommandSchema, ListModesCommandSchema, ListModelsCommandSchema, SetModelCommandSchema, ListReasoningLevelsCommandSchema, SetReasoningLevelCommandSchema, SessionOpCommandSchema, PermissionResponseCommandSchema, UiResponseCommandSchema, AgentListCommandSchema, AgentCreateCommandSchema, AgentAttachCommandSchema, AgentDetachCommandSchema, AgentTerminateCommandSchema, AgentKillCommandSchema, AgentStatusCommandSchema, DaemonShutdownCommandSchema, WebApproveCommandSchema, WebTokenCreateCommandSchema, WebClientsCommandSchema, WebRevokeCommandSchema, WebTokenRotateCommandSchema, SessionCreateCommandSchema, SessionStatusCommandSchema, SessionTerminateCommandSchema, SessionKillCommandSchema, SessionActivateCommandSchema, SessionListCommandSchema, SessionReplayCommandSchema, StatsToolUsageCommandSchema, StatsTokenUsageCommandSchema, WorkspaceCreateCommandSchema, WorkspaceAttachCommandSchema, WorkspaceListCommandSchema, WorkspaceStatusCommandSchema, WorkspaceRetireCommandSchema, // Subagent job orchestrator commands (saga u4uo9m.1). SubagentCreateCommandSchema, SubagentWaitCommandSchema, SubagentSteerCommandSchema, SubagentRelaunchCommandSchema, SubagentStatusCommandSchema, SubagentStopCommandSchema, ]); export type PromptCommand = Static; export type SteerCommand = Static; export type AbortCommand = Static; export type SetModeCommand = Static; export type ListModesCommand = Static; export type ListModelsCommand = Static; export type SetModelCommand = Static; export type ListReasoningLevelsCommand = Static; export type SetReasoningLevelCommand = Static; export type SessionOpCommand = Static; export type SessionOpArgs = Static; export type PermissionResponseCommand = Static; export type UiResponseCommand = Static; export type AgentListCommand = Static; export type AgentCreateCommand = Static; export type AgentAttachCommand = Static; export type AgentDetachCommand = Static; export type AgentTerminateCommand = Static; export type AgentKillCommand = Static; export type AgentStatusCommand = Static; export type DaemonShutdownCommand = Static; export type WebApproveCommand = Static; export type WebTokenCreateCommand = Static; export type WebClientsCommand = Static; export type WebRevokeCommand = Static; export type WebTokenRotateCommand = Static; export type SessionCreateCommand = Static; export type SessionStatusCommand = Static; export type SessionTerminateCommand = Static; export type SessionKillCommand = Static; export type SessionActivateCommand = Static; export type SessionListCommand = Static; export type SessionReplayCommand = Static; export type StatsToolUsageCommand = Static; export type StatsTokenUsageCommand = Static; export type WorkspaceCreateCommand = Static; export type WorkspaceAttachCommand = Static; export type WorkspaceListCommand = Static; export type WorkspaceStatusCommand = Static; export type WorkspaceRetireCommand = Static; export type Command = Static;