Something went wrong. Try again.
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170/** * Response envelope — PROTOCOL §9.3. * * The reply to a request/response command (§5.1). `command` echoes the command type being * responded to; `id` correlates with the command's `id`. `success:false` carries an * `error: { code, message, data? }` from the §8 registry. Events never carry a correlation * `id` — `response` is the only server→client direction that does (§5.1). */import { Type, type Static } from '@sinclair/typebox';import { ErrorCodeSchema, type ErrorCode } from '@/protocol/error-codes';
const WebClientSchema = Type.Object({ id: Type.String({ minLength: 1 }), name: Type.Optional(Type.String()), createdAt: Type.Integer({ minimum: 0 }), lastSeenAt: Type.Optional(Type.Integer({ minimum: 0 })), revokedAt: Type.Optional(Type.Integer({ minimum: 0 })),}, { additionalProperties: false });
export const WebApproveResponseSchema = Type.Object({}, { additionalProperties: false });export const WebTokenCreateResponseSchema = Type.Object({ setupToken: Type.String({ minLength: 1 }) }, { additionalProperties: false });export const WebClientsResponseSchema = Type.Object({ clients: Type.Array(WebClientSchema) }, { additionalProperties: false });export const WebRevokeResponseSchema = Type.Object({}, { additionalProperties: false });export const WebTokenRotateResponseSchema = Type.Object({ token: Type.String({ minLength: 1 }) }, { additionalProperties: false });
// ─── Workspace response payloads (saga j8dtju.6, protocol v1 additive) ───────────// Worktree cwd is intentionally exposed for CLI/web rendering, alongside the derived// `repository_cwd` (the repository's main-checkout root — grouped/labeling display path// for the worktree's ORIGINAL project; the repository common-dir/git-dir paths proper// remain omitted).export const WorkspaceSummarySchema = Type.Object({ workspace_id: Type.String({ minLength: 1 }), label: Type.String({ minLength: 1 }), cwd: Type.String({ minLength: 1 }), ownership: Type.Union([Type.Literal('user'), Type.Literal('managed')]), lifecycle: Type.Union([ Type.Literal('active'), Type.Literal('retiring'), Type.Literal('retired'), Type.Literal('conflict'), ]), repository_fingerprint: Type.String({ minLength: 1 }), /** Canonical main-checkout root of the repository this worktree belongs to (optional * additive v1 field; bare repositories project their root). Lets clients group and * label sibling worktrees by their original project. */ repository_cwd: Type.Optional(Type.String({ minLength: 1 })), worktree_fingerprint: Type.String({ minLength: 1 }), branch: Type.Optional(Type.String()), integration_target: Type.Optional(Type.String()), created_at: Type.Integer({ minimum: 0 }), updated_at: Type.Integer({ minimum: 0 }), binding_count: Type.Integer({ minimum: 0 }), live_agent_count: Type.Integer({ minimum: 0 }), live_session_count: Type.Integer({ minimum: 0 }),}, { additionalProperties: false });
export const WorkspaceBindingSummarySchema = Type.Object({ agent_id: Type.String({ minLength: 1 }), session_id: Type.String({ minLength: 1 }), bound_at: Type.Integer({ minimum: 0 }),}, { additionalProperties: false });
export const WorkspaceDetailSchema = Type.Object({ summary: WorkspaceSummarySchema, last_head_commit: Type.Optional(Type.String()), last_probed_at: Type.Optional(Type.Integer({ minimum: 0 })), tombstone: Type.Optional(Type.Object({ retired_at: Type.Integer({ minimum: 0 }), reason: Type.String({ minLength: 1 }), }, { additionalProperties: false })), conflict: Type.Optional(Type.Object({ detected_at: Type.Integer({ minimum: 0 }), reason: Type.String({ minLength: 1 }), }, { additionalProperties: false })), bindings: Type.Array(WorkspaceBindingSummarySchema),}, { additionalProperties: false });
export const WorkspaceCreateResponseSchema = Type.Object({ workspace: WorkspaceDetailSchema, agent_id: Type.String({ minLength: 1 }), session_id: Type.String({ minLength: 1 }),}, { additionalProperties: false });export const WorkspaceAttachResponseSchema = Type.Object({ workspace: WorkspaceDetailSchema, agent_id: Type.String({ minLength: 1 }), session_id: Type.String({ minLength: 1 }),}, { additionalProperties: false });export const WorkspaceListResponseSchema = Type.Object({ workspaces: Type.Array(WorkspaceSummarySchema) }, { additionalProperties: false });export const WorkspaceStatusResponseSchema = WorkspaceDetailSchema;export const WorkspaceRetireResponseSchema = Type.Object({ workspace: WorkspaceDetailSchema, branch_deleted: Type.Boolean() }, { additionalProperties: false });
/** Structured refusal returned when retirement cannot safely proceed. */export const WorkspaceRetireRefusalSchema = Type.Object({ kind: Type.Union([Type.Literal('confirmation_required'), Type.Literal('refused')]), reason: Type.String({ minLength: 1 }), workspace_id: Type.String({ minLength: 1 }), requested_disposition: Type.Optional(Type.Union([Type.Literal('delete_if_merged'), Type.Literal('keep'), Type.Literal('delete_confirmed')])), confirmation_disposition: Type.Optional(Type.Literal('delete_confirmed')),}, { additionalProperties: false });export type WorkspaceSummary = Static<typeof WorkspaceSummarySchema>;export type WorkspaceDetail = Static<typeof WorkspaceDetailSchema>;export type WorkspaceCreateResponse = Static<typeof WorkspaceCreateResponseSchema>;export type WorkspaceAttachResponse = Static<typeof WorkspaceAttachResponseSchema>;export type WorkspaceListResponse = Static<typeof WorkspaceListResponseSchema>;export type WorkspaceStatusResponse = Static<typeof WorkspaceStatusResponseSchema>;export type WorkspaceRetireResponse = Static<typeof WorkspaceRetireResponseSchema>;export type WorkspaceRetireRefusal = Static<typeof WorkspaceRetireRefusalSchema>;
export const WorkspaceResponseSchemas = { 'workspace.create': WorkspaceCreateResponseSchema, 'workspace.attach': WorkspaceAttachResponseSchema, 'workspace.list': WorkspaceListResponseSchema, 'workspace.status': WorkspaceStatusResponseSchema, 'workspace.retire': WorkspaceRetireResponseSchema,} as const;
export const WebAdminResponseSchemas = { 'web.approve': WebApproveResponseSchema, 'web.token.create': WebTokenCreateResponseSchema, 'web.clients': WebClientsResponseSchema, 'web.revoke': WebRevokeResponseSchema, 'web.token.rotate': WebTokenRotateResponseSchema,} as const;export type WebAdminResponse = | Static<typeof WebApproveResponseSchema> | Static<typeof WebTokenCreateResponseSchema> | Static<typeof WebClientsResponseSchema> | Static<typeof WebRevokeResponseSchema> | Static<typeof WebTokenRotateResponseSchema>;
/** The error object on an unsuccessful response (§9.3, §8 codes). */export const ResponseErrorSchema = Type.Object({ code: ErrorCodeSchema, message: Type.String(), data: Type.Optional(Type.Unknown()),});
/** The response envelope (§9.3). */export const ResponseSchema = Type.Object({ type: Type.Literal('response'), /** Correlates with the command's `id` (§5.1). */ id: Type.String(), /** Echoes the command type being responded to (§9.3). */ command: Type.String(), success: Type.Boolean(), data: Type.Optional(Type.Unknown()), error: Type.Optional(ResponseErrorSchema),});
export type Response = Static<typeof ResponseSchema>;export type ResponseError = Static<typeof ResponseErrorSchema>;
/** * Build a successful response. Omit `data` entirely when undefined (exactOptionalPropertyTypes * — never pass `undefined` explicitly). */export function ok(id: string, command: string, data?: unknown): Response { const res: Response = { type: 'response', id, command, success: true }; if (data !== undefined) res.data = data; return res;}
/** Build an error response. Omit `error.data` when undefined. */export function err( id: string, command: string, code: ErrorCode, message: string, data?: unknown,): Response { const error: ResponseError = { code, message }; if (data !== undefined) error.data = data; return { type: 'response', id, command, success: false, error };}