@opengeni/react 0.15.0 → 0.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. package/README.md +110 -10
  2. package/dist/{chunk-TOJR776I.js → chunk-6XUS5VFM.js} +45 -369
  3. package/dist/chunk-6XUS5VFM.js.map +1 -0
  4. package/dist/chunk-CATATY77.js +2393 -0
  5. package/dist/chunk-CATATY77.js.map +1 -0
  6. package/dist/chunk-I3BJZIG5.js +439 -0
  7. package/dist/chunk-I3BJZIG5.js.map +1 -0
  8. package/dist/chunk-TK7G6XLT.js +18 -0
  9. package/dist/chunk-TK7G6XLT.js.map +1 -0
  10. package/dist/chunk-UAHPKLB6.js +2237 -0
  11. package/dist/chunk-UAHPKLB6.js.map +1 -0
  12. package/dist/chunk-UQS7OQDE.js +591 -0
  13. package/dist/chunk-UQS7OQDE.js.map +1 -0
  14. package/dist/chunk-YDNWMKXO.js +102 -0
  15. package/dist/chunk-YDNWMKXO.js.map +1 -0
  16. package/dist/composer-CVm5uoUI.d.ts +585 -0
  17. package/dist/composer.d.ts +6 -0
  18. package/dist/composer.js +56 -0
  19. package/dist/composer.js.map +1 -0
  20. package/dist/index.d.ts +1288 -1785
  21. package/dist/index.js +7140 -8817
  22. package/dist/index.js.map +1 -1
  23. package/dist/machines.d.ts +412 -3
  24. package/dist/machines.js +3 -1
  25. package/dist/noto-sans-arabic-loader-IA2T4X2A.js +21 -0
  26. package/dist/noto-sans-arabic-loader-IA2T4X2A.js.map +1 -0
  27. package/dist/noto-sans-jp-loader.generated-HFGLYBR3.js +292 -0
  28. package/dist/noto-sans-jp-loader.generated-HFGLYBR3.js.map +1 -0
  29. package/dist/queue-surface-implementation-3UW3MIIR.js +890 -0
  30. package/dist/queue-surface-implementation-3UW3MIIR.js.map +1 -0
  31. package/dist/session-Db00TRwl.d.ts +446 -0
  32. package/dist/session-context-rgrfElMl.d.ts +58 -0
  33. package/dist/session.d.ts +4 -0
  34. package/dist/session.js +54 -0
  35. package/dist/session.js.map +1 -0
  36. package/dist/use-composer-By1wn3MM.d.ts +99 -0
  37. package/package.json +20 -4
  38. package/src/approvals.ts +5 -3
  39. package/src/client.ts +44 -0
  40. package/src/components/approval-surface.tsx +198 -0
  41. package/src/components/chat-composer.tsx +94 -993
  42. package/src/components/code-editor.tsx +104 -26
  43. package/src/components/command-palette.tsx +8 -2
  44. package/src/components/composer-transcription-control.tsx +211 -0
  45. package/src/components/composer.tsx +1536 -0
  46. package/src/components/desktop-viewer.tsx +3 -3
  47. package/src/components/enrollment-consent.tsx +2 -2
  48. package/src/components/file-browser.tsx +367 -29
  49. package/src/components/human-input-form.tsx +417 -0
  50. package/src/components/machine-card.tsx +1 -1
  51. package/src/components/machines-dashboard.tsx +3 -3
  52. package/src/components/message-timeline.tsx +205 -25
  53. package/src/components/model-picker.tsx +12 -3
  54. package/src/components/pierre-diff.tsx +32 -6
  55. package/src/components/queue-draft-policy.ts +18 -0
  56. package/src/components/queue-surface-implementation.tsx +1037 -0
  57. package/src/components/queue-surface-state.tsx +94 -0
  58. package/src/components/queue-surface.tsx +42 -559
  59. package/src/components/sandbox-files.tsx +138 -14
  60. package/src/components/sandbox-terminal.tsx +8 -2
  61. package/src/components/sandbox-workspace.tsx +339 -119
  62. package/src/components/workbench-changes.tsx +84 -33
  63. package/src/components/workspace-dock.tsx +188 -18
  64. package/src/composer.ts +60 -0
  65. package/src/hooks/internal.ts +115 -34
  66. package/src/hooks/use-composer.ts +385 -104
  67. package/src/hooks/use-human-input.ts +131 -0
  68. package/src/hooks/use-machine-chip.ts +2 -2
  69. package/src/hooks/use-machines.ts +27 -11
  70. package/src/hooks/use-sandbox-files.ts +310 -62
  71. package/src/hooks/use-sandbox-git.ts +154 -40
  72. package/src/hooks/use-sandbox-terminal.ts +28 -6
  73. package/src/hooks/use-session-capabilities.ts +38 -9
  74. package/src/hooks/use-session-control.ts +37 -8
  75. package/src/hooks/use-session-events.ts +522 -48
  76. package/src/hooks/use-session-mcp-approval-policy.ts +161 -0
  77. package/src/hooks/use-slash-commands.ts +38 -38
  78. package/src/hooks/use-transcription.ts +757 -0
  79. package/src/hooks/use-turn-queue.ts +134 -66
  80. package/src/hooks/use-windowed-sections.ts +2 -2
  81. package/src/hooks/use-workspace-capture.ts +146 -40
  82. package/src/hooks/use-workspace-edit.ts +50 -14
  83. package/src/hooks/use-workspace-sessions.ts +3 -0
  84. package/src/human-input.ts +72 -0
  85. package/src/index.ts +85 -6
  86. package/src/lib/noto-sans-arabic-loader.ts +21 -0
  87. package/src/lib/noto-sans-jp-loader.generated.ts +295 -0
  88. package/src/lib/use-portal-token-style.ts +51 -0
  89. package/src/lib/use-unicode-fonts.ts +57 -0
  90. package/src/provider.tsx +4 -69
  91. package/src/session-context.ts +160 -0
  92. package/src/session.ts +89 -0
  93. package/src/timeline/parsers.ts +35 -0
  94. package/src/timeline/projection.ts +12 -4
  95. package/src/timeline/tool-renderers.tsx +41 -0
  96. package/src/timeline/turn-summary.tsx +2 -2
  97. package/src/timeline/types.ts +10 -9
  98. package/styles/index.css +56 -0
  99. package/styles/index.d.ts +2 -0
  100. package/styles/tokens.css +11 -6
  101. package/styles/tokens.d.ts +2 -0
  102. package/dist/chunk-TOJR776I.js.map +0 -1
  103. package/dist/machines-BpdwuQcD.d.ts +0 -449
package/dist/index.d.ts CHANGED
@@ -1,21 +1,30 @@
1
- import { C as ClientOverride, S as SessionClientLike } from './machines-BpdwuQcD.js';
2
- export { a as CONNECTION_STATUS_META, b as ConnectionDot, c as ConnectionDotProps, d as ConnectionStatus, e as ConnectionStatusMeta, f as ConnectionStatusPill, g as ConnectionStatusPillProps, D as DeviceFlowPhase, E as EnrollmentConsent, h as EnrollmentConsentMachine, i as EnrollmentConsentPhase, j as EnrollmentConsentProps, k as EnrollmentDeviceFlow, l as EnrollmentDeviceFlowProps, H as HEALTH_TOKEN, m as HealthLevel, n as HealthVerdict, M as MACHINE_STATE_BADGE_META, o as METRICS, p as METRIC_WINDOWS, q as MachineCard, r as MachineCardProps, s as MachineDetail, t as MachineDetailProps, u as MachineDockBar, v as MachineDockBarProps, w as MachineHealthPill, x as MachineHealthPillProps, y as MachineMetrics, z as MachineMetricsProps, A as MachineStateBadgeMeta, B as MachineStatusPill, F as MachineStatusPillProps, G as MachinesClientLike, I as MachinesDashboard, J as MachinesDashboardProps, K as MetricDef, L as MetricHistoryChart, N as MetricHistoryChartProps, O as MetricKey, P as MetricSparkline, Q as MetricSparklineProps, R as MetricWindow, T as OpenGeniContextValue, U as OpenGeniProvider, V as OpenGeniProviderProps, W as SeriesPoint, X as SharedMachineDisclosure, Y as SharedMachineDisclosureProps, Z as UseMachinesOptions, _ as UseMachinesResult, $ as WINDOW_LABEL, a0 as connectionStatusForState, a1 as deriveHealth, a2 as healthPulses, a3 as pointsFor, a4 as useMachines, a5 as useOpenGeni, a6 as useOpenGeniClient } from './machines-BpdwuQcD.js';
3
- import { SessionEvent, Session, ResourceRef, ToolRef, SessionStatus as SessionStatus$1, GitFileDiff, StreamConnectionState, EffectiveControlResumeOption, ComposerDraft, SendMessageInput, EffectiveSessionControl, FileAsset, FileResourceRef, SessionQueueSnapshot, SessionTurn, Permission, ClientModel, SessionGoal, SessionControlResponse, ScheduledTask, SessionLineageResponse, VariableSet, CreateVariableSetRequest, UpdateVariableSetRequest, VariableSetVariableMetadata, RigChange, Rig, UpdateRigRequest, RigVersion, ProposeRigChangeRequest, CreateRigRequest, CapabilityPack, PackInstallation, RegisterCapabilityPackRequest, WorkspaceRegisteredPack, EnablePackRequest, Workspace, CreateWorkspaceRequest, UpdateWorkspaceRequest, BillingBalance, UsageEvent, SessionCapabilities, DesktopStreamCapability, DesktopConnectionState, DesktopRfbFactory, TerminalCapability, FsReadResponse, FsWriteResponse, WorkspaceCaptureManifest, WorkspaceCaptureDegradedReason, MachineState, CapabilityUnavailableReason, MachineView, CodexAccountsResponse, CodexAccount, CodexRotationSettings } from '@opengeni/sdk';
4
- export { MachineKind, MachineMetricsSeriesResponse, MachineState, MachineView, MachinesResponse, MetricSample } from '@opengeni/sdk';
1
+ import { S as SessionClientLike, C as ClientOverride } from './session-context-rgrfElMl.js';
2
+ export { a as EmbeddedHumanInputSessionClientLike, b as EmbeddedSessionClientLike, c as EmbeddedSessionMcpApprovalPolicyClientLike, O as OpenGeniContextValue, u as useOpenGeni, d as useOpenGeniClient } from './session-context-rgrfElMl.js';
5
3
  import * as react_jsx_runtime from 'react/jsx-runtime';
4
+ import { WorkspaceControlEvent, Session, SessionEvent, EffectiveSessionControl, ClientModel, SessionGoal, ScheduledTask, SessionLineageResponse, VariableSet, CreateVariableSetRequest, UpdateVariableSetRequest, VariableSetVariableMetadata, RigChange, Rig, UpdateRigRequest, RigVersion, ProposeRigChangeRequest, CreateRigRequest, CapabilityPack, PackInstallation, RegisterCapabilityPackRequest, WorkspaceRegisteredPack, EnablePackRequest, Workspace, CreateWorkspaceRequest, UpdateWorkspaceRequest, BillingBalance, UsageEvent, SessionCapabilities, DesktopStreamCapability, DesktopConnectionState, DesktopRfbFactory, TerminalCapability, OpenGeniRequestOptions, FsReadResponse, FsWriteResponse, WorkspaceCaptureManifest, GitFileDiff, WorkspaceCaptureDegradedReason, MachineState, SessionHumanInputRequest, SubmitHumanInputResponseRequest, HumanInputQuestion, HumanInputAnswer, Permission, SessionStatus as SessionStatus$1, CapabilityUnavailableReason, MachineView, CodexAccountsResponse, CodexAccount, CodexRotationSettings } from '@opengeni/sdk';
5
+ export { MachineKind, MachineMetricsSeriesResponse, MachineState, MachineView, MachinesResponse, MetricSample } from '@opengeni/sdk';
6
6
  import * as react from 'react';
7
- import { ComponentType, ReactNode, KeyboardEvent, ClipboardEvent, RefObject } from 'react';
7
+ import { ReactNode, ClipboardEvent, RefObject, ComponentType } from 'react';
8
+ import { S as SessionEventFeedOptions, C as ComposerState } from './use-composer-By1wn3MM.js';
9
+ export { a as ComposerSendExtras, F as FILE_ONLY_MESSAGE_TEXT, U as UseComposerOptions, c as composeSendInput, s as shouldSteerOnKey, b as shouldSubmitOnKey, u as useComposer } from './use-composer-By1wn3MM.js';
10
+ import { U as UseTurnQueueResult, P as PendingApproval, T as ToolCallItem, A as ActivityItem, a as TurnOutcome, b as TimelineItem, c as AgentMessageItem, d as UserMessageItem, e as AuthNeededItem } from './session-Db00TRwl.js';
11
+ export { B as BrowserSessionEventWindow, G as GoalItem, N as NoticeItem, f as PendingHumanInputRequest, Q as QueueMutationKind, R as ReasoningItem, S as SESSION_EVENT_BROWSER_MAX_BYTES, g as SESSION_EVENT_BROWSER_MAX_COUNT, h as SESSION_EVENT_BROWSER_PENDING_MAX_BYTES, i as SESSION_EVENT_BROWSER_PENDING_MAX_COUNT, j as SESSION_EVENT_BROWSER_SINGLE_EVENT_MAX_BYTES, k as SandboxItem, l as SessionEventsConnectionState, m as SessionStatusItem, n as TimelineGroup, o as TurnEndItem, p as UseHumanInputRequestsOptions, q as UseHumanInputRequestsResult, r as UseSessionControlOptions, s as UseSessionControlResult, t as UseSessionEventsOptions, u as UseSessionEventsResult, v as UseSessionMcpApprovalPolicyOptions, w as UseSessionMcpApprovalPolicyResult, x as UseTurnQueueOptions, W as WorkerItem, y as approvalsFromRequiresAction, z as boundBrowserSessionEventWindow, C as buildTimeline, D as creditExhaustedFromEvents, E as extractSessionRef, F as groupTimeline, H as humanInputRequestFromEvent, I as isHumanInputEvent, J as isSessionMcpApprovalPolicyEvent, K as isTurnQueueEvent, L as projectPendingApprovals, M as projectPendingHumanInputRequests, O as sessionStatusFromEvents, V as toolDisplayName, X as useHumanInputRequests, Y as useSessionControl, Z as useSessionEvents, _ as useSessionMcpApprovalPolicy, $ as useTurnQueue } from './session-Db00TRwl.js';
12
+ import { C as ComposerControlLinks, a as ComposerTranscriptionControlProps, U as UseFileAttachmentsResult, S as SlashCommand, b as SlashCommandContext, c as ChatComposerMessages, d as SlashArg, e as CommandContext } from './composer-CVm5uoUI.js';
13
+ export { f as CommandResult, g as ComposerTranscriptionControl, h as ComposerTranscriptionMessages, i as ConfirmState, F as FileAttachment, I as INITIAL_TRANSCRIPTION_CONTROL_STATE, N as Notice, O as OPEN_WORKSTREAM_CONTROL_EVENT, j as SlashCommandHandlers, T as TranscriptionControlAction, k as TranscriptionControlState, l as TranscriptionControlTransition, m as UseFileAttachmentsOptions, n as UseSlashCommandsOptions, o as UseSlashCommandsResult, p as UseTranscriptionOptions, q as UseTranscriptionResult, r as appendFinalTranscript, s as defaultChatComposerMessages, t as transitionTranscriptionControl, u as useFileAttachments, v as useSlashCommands, w as useTranscription } from './composer-CVm5uoUI.js';
14
+ export { CONNECTION_STATUS_META, ConnectionDot, ConnectionDotProps, ConnectionStatus, ConnectionStatusMeta, ConnectionStatusPill, ConnectionStatusPillProps, DeviceFlowPhase, EnrollmentConsent, EnrollmentConsentMachine, EnrollmentConsentPhase, EnrollmentConsentProps, EnrollmentDeviceFlow, EnrollmentDeviceFlowProps, HEALTH_TOKEN, HealthLevel, HealthVerdict, MACHINE_STATE_BADGE_META, METRICS, METRIC_WINDOWS, MachineCard, MachineCardProps, MachineDetail, MachineDetailProps, MachineDockBar, MachineDockBarProps, MachineHealthPill, MachineHealthPillProps, MachineMetrics, MachineMetricsProps, MachineStateBadgeMeta, MachineStatusPill, MachineStatusPillProps, MachinesClientLike, MachinesDashboard, MachinesDashboardProps, MetricDef, MetricHistoryChart, MetricHistoryChartProps, MetricKey, MetricSparkline, MetricSparklineProps, MetricWindow, SeriesPoint, SharedMachineDisclosure, SharedMachineDisclosureProps, UseMachinesOptions, UseMachinesResult, WINDOW_LABEL, connectionStatusForState, deriveHealth, healthPulses, pointsFor, useMachines } from './machines.js';
8
15
  import { ClassValue } from 'clsx';
9
16
 
10
- type SessionEventFeedOptions = {
11
- /**
12
- * Share an existing event log (from `useSessionEvents`) instead of opening
13
- * a second stream. When omitted the hook tails the session's event stream
14
- * itself, starting at the current `lastSequence`.
15
- */
16
- events?: SessionEvent[] | undefined;
17
- enabled?: boolean | undefined;
17
+ type OpenGeniProviderProps = {
18
+ client: SessionClientLike;
19
+ workspaceId: string;
20
+ onWorkspaceControlEvent?: ((event: WorkspaceControlEvent) => void) | undefined;
21
+ children?: ReactNode;
18
22
  };
23
+ /**
24
+ * Supplies the OpenGeni client + workspace to all hooks below it. Hooks also
25
+ * accept `{ client, workspaceId }` overrides per call for multi-workspace UIs.
26
+ */
27
+ declare function OpenGeniProvider({ client, workspaceId, onWorkspaceControlEvent, children, }: OpenGeniProviderProps): react_jsx_runtime.JSX.Element;
19
28
 
20
29
  type UseSessionOptions = ClientOverride & SessionEventFeedOptions & {
21
30
  /** Re-fetch on an interval (ms). Off by default — pair with `useSessionEvents` for live status. */
@@ -38,1939 +47,1390 @@ declare function isTitleEvent(event: Pick<SessionEvent, "type">): boolean;
38
47
  /** Fetch one session (with optional polling), live-patching its title on `session.title_set`. */
39
48
  declare function useSession(sessionId: string | null | undefined, options?: UseSessionOptions): UseSessionResult;
40
49
 
41
- type UserMessageItem = {
42
- kind: "user-message";
43
- id: string;
44
- text: string;
45
- /** Resources attached to this message (file uploads, repositories). */
46
- resources: ResourceRef[];
47
- /** Tools requested for the turn this message starts. */
48
- tools: ToolRef[];
49
- occurredAt: string;
50
- };
51
- type AgentMessageItem = {
52
- kind: "agent-message";
53
- id: string;
54
- turnId: string | null;
55
- text: string;
56
- /** Still receiving deltas (no completed/turn-end seen yet). */
57
- streaming: boolean;
58
- occurredAt: string;
59
- };
60
- type ReasoningItem = {
61
- kind: "reasoning";
62
- id: string;
63
- turnId: string | null;
64
- text: string;
65
- streaming: boolean;
66
- occurredAt: string;
67
- };
68
- type ToolCallItem = {
69
- kind: "tool-call";
70
- id: string;
71
- turnId: string | null;
72
- callId: string | null;
73
- name: string;
74
- arguments: unknown;
75
- output: unknown;
76
- /**
77
- * The provider-native tool item (`agent.toolCall.created.payload.raw`). Carries
78
- * `type` (e.g. `apply_patch_call`, `computer_call`, `hosted_tool_call`) and the
79
- * tool-specific fields the per-tool renderers read (`operation`, `action`,
80
- * `providerData`, …). `undefined` for first-party MCP tools, which carry their
81
- * payload in `arguments`/`output` instead.
82
- */
83
- raw: unknown;
84
- status: "running" | "complete" | "failed" | "cancelled";
85
- occurredAt: string;
86
- };
87
- /**
88
- * An orchestration call against another session — the manager spawning or
89
- * messaging a worker. Rendered as a first-class "worker" row, not a generic
90
- * tool call.
91
- */
92
- type WorkerItem = {
93
- kind: "worker";
94
- id: string;
95
- turnId: string | null;
96
- callId: string | null;
97
- action: "spawn" | "message";
98
- /** The worker's initial message / the message sent to it, when parseable. */
99
- prompt: string | null;
100
- /** The target/spawned worker session id, when parseable from args/output. */
101
- workerSessionId: string | null;
102
- status: "running" | "complete" | "failed" | "cancelled";
103
- occurredAt: string;
104
- };
105
- type WorkerCompletionItem = {
106
- kind: "worker-completion";
107
- id: string;
108
- turnId: string | null;
109
- occurredAt: string;
110
- childSessionId: string;
111
- childStatus: string;
112
- goalStatus: string | null;
113
- goalText: string | null;
114
- evidence: string | null;
115
- pausedReason: string | null;
116
- text: string;
50
+ /** The sole human prompt queue: compact above Goal, Agents, and composer. */
51
+ type QueueSurfaceCommonProps = {
52
+ /** Focus the composer owned by this queue after checkout/removal. */
53
+ onRequestComposerFocus?: (() => void) | undefined;
117
54
  };
118
- type SandboxItem = {
119
- kind: "sandbox";
120
- id: string;
121
- turnId: string | null;
122
- name: string;
123
- command: string | null;
124
- output: string;
125
- origin?: "created" | "restored" | "resumed" | null;
126
- status: "running" | "complete" | "failed" | "cancelled";
127
- occurredAt: string;
55
+ type QueueSurfaceProps = (QueueSurfaceCommonProps & {
56
+ queue: UseTurnQueueResult;
57
+ composer: ComposerState;
58
+ readOnly?: false | undefined;
59
+ }) | (QueueSurfaceCommonProps & {
60
+ queue: UseTurnQueueResult;
61
+ composer?: undefined;
62
+ readOnly: true;
63
+ });
64
+
65
+ /** The sole human prompt queue: compact above Goal, Agents, and composer. */
66
+ declare const QueueSurface: (props: QueueSurfaceProps) => react_jsx_runtime.JSX.Element | null;
67
+
68
+ type ChatComposerProps = {
69
+ composer: ComposerState;
70
+ /** Canonical workstream control, separate from lifecycle status. */
71
+ effectiveControl?: EffectiveSessionControl | null | undefined;
72
+ /** Waiting prompts already ahead of a normal Send. */
73
+ queuedAheadCount?: number | undefined;
74
+ /** Whether broader Workspace Resume is authorized for this viewer. */
75
+ canControlWorkspace?: boolean | undefined;
76
+ /** Optional host routes used to navigate from effective Pause blockers. */
77
+ controlLinks?: ComposerControlLinks | undefined;
78
+ placeholder?: string | undefined;
79
+ disabled?: boolean | undefined;
80
+ autoFocus?: boolean | undefined;
81
+ /** Replaces the default keyboard hint under the field. */
82
+ hint?: string | undefined;
83
+ /** App controls in the footer row, replacing the hint. */
84
+ controlsStart?: ReactNode | undefined;
85
+ /** Provider-neutral speech capability. Provider configuration stays in workspace settings. */
86
+ transcription?: ComposerTranscriptionControlProps | undefined;
87
+ /** Content rendered above the textarea, inside the field chrome. */
88
+ header?: ReactNode | undefined;
89
+ /** Paste hook composed with the attachment paste path. */
90
+ onPaste?: ((event: ClipboardEvent<HTMLTextAreaElement>) => void) | undefined;
91
+ /** Opt-in file attachment state, typically from `useFileAttachments`. */
92
+ attachments?: UseFileAttachmentsResult | undefined;
93
+ /** Opt-in model picker choices. */
94
+ models?: ClientModel[] | undefined;
95
+ selectedModel?: string | undefined;
96
+ onSelectModel?: ((modelId: string) => void) | undefined;
97
+ className?: string | undefined;
98
+ commands?: readonly SlashCommand[] | undefined;
99
+ commandContext?: SlashCommandContext | undefined;
100
+ onClearView?: (() => void) | undefined;
101
+ /** Partial overrides for all composer-owned visible and accessible copy. */
102
+ messages?: Partial<ChatComposerMessages> | undefined;
128
103
  };
129
104
  /**
130
- * A first-party workspace-memory write the agent made mid-turn a `memory.saved`
131
- * (it committed a new preference / fact / procedure / decision / history) or a
132
- * `memory.corrected` (it updated or archived an existing one). A settled save is
133
- * ordinary progress, not an exceptional state, so it renders as a calm NEUTRAL
134
- * step on the rail (never accent/color). When the host app supplies an
135
- * `onMemoryClick` handler the row also deep-links to the record in its memory
136
- * pane; without one it is non-interactive rich content.
105
+ * Batteries-included chat composer. This preset is assembled exclusively from
106
+ * the public controller and compound primitives exported by the composer
107
+ * subpath, so custom and default layouts share one behavioral implementation.
137
108
  */
138
- type MemoryItem = {
139
- kind: "memory";
140
- id: string;
141
- turnId: string | null;
142
- variant: "saved" | "corrected";
143
- /** The memory's kind enum (`"preference" | "semantic" | …`); mapped to a human label at render. */
144
- memoryKind: string;
145
- /** The memory text, ellipsized to ≤120 chars server-side. For a supersede this is the OLD text. */
146
- preview: string;
147
- /** The save collapsed into an existing memory (no new row was written). Saved variant only. */
148
- deduped?: boolean;
149
- /** The NEW text when a correction superseded the memory with a replacement; absent = updated-in-place or archived. */
150
- replacementPreview?: string;
151
- /**
152
- * What a `memory.corrected` did: `"superseded"` (replaced by a new record, see
153
- * `replacementPreview`), `"updated"` (edited in place — the record lives on), or
154
- * `"archived"` (retired). Distinguishes updated-in-place from archived when there
155
- * is no replacement. Corrected variant only; read defensively (may be absent).
156
- */
157
- action?: string;
158
- /** The saved / corrected memory's id — the deep-link target for a save. */
159
- memoryId: string;
160
- /** The replacement memory's id when a correction produced one — the LIVE record the deep-link targets. */
161
- replacementMemoryId?: string;
162
- occurredAt: string;
163
- };
164
- type SessionStatusItem = {
165
- kind: "session-status";
166
- id: string;
167
- status: SessionStatus$1;
168
- occurredAt: string;
169
- };
170
- type GoalItem = {
171
- kind: "goal";
172
- id: string;
173
- action: "set" | "updated" | "completed" | "paused" | "resumed" | "cleared" | "continuation";
174
- text: string | null;
175
- occurredAt: string;
109
+ declare function ChatComposer({ composer, effectiveControl, queuedAheadCount, canControlWorkspace, controlLinks, placeholder, disabled, autoFocus, hint, controlsStart, transcription, header, onPaste, attachments, models, selectedModel, onSelectModel, className, commands, commandContext, onClearView, messages, }: ChatComposerProps): react_jsx_runtime.JSX.Element;
110
+
111
+ /** Event types that change the session goal (set/updated/completed/paused/...). */
112
+ declare function isGoalEvent(event: Pick<SessionEvent, "type">): boolean;
113
+ type UseGoalOptions = ClientOverride & SessionEventFeedOptions & {
114
+ /** Optional safety-net polling (ms). Off by default goal.* events drive updates. */
115
+ pollIntervalMs?: number | undefined;
176
116
  };
177
- type NoticeItem = {
178
- kind: "notice";
179
- id: string;
180
- tone: "waiting" | "cancelled" | "failed";
181
- text: string;
182
- /** Optional evidence kept inspectable without overwhelming the main rail. */
183
- details?: {
184
- label: string;
185
- value: unknown;
186
- };
187
- action?: {
188
- label: string;
189
- url: string;
190
- };
191
- occurredAt: string;
192
- };
193
- /**
194
- * A tool call hit a connection whose credential lapsed — the broker asked the
195
- * user to reconnect the provider before the turn can continue. Carries the
196
- * structured `tool.auth_needed` payload so the renderer can draw a clean inline
197
- * reconnect affordance (provider logo + one human line + a Reconnect button)
198
- * and the app can start the right recovery flow (OAuth reconnect for the
199
- * surviving connection, or credential re-entry for an api-key one). The `reason`
200
- * shapes the human copy but is never shown raw.
201
- */
202
- type AuthNeededItem = {
203
- kind: "auth-needed";
204
- id: string;
205
- turnId: string | null;
206
- /** The connection's registrable domain, e.g. "linear.app". */
207
- providerDomain: string;
208
- /** The lapsed connection to reconnect, when the row survived. */
209
- connectionId: string | null;
210
- reason: "missing_connection" | "expired" | "insufficient_scope" | "refresh_failed" | null;
211
- /** Scopes the provider now needs; may inform the copy, never shown as a raw label. */
212
- scopes: string[];
213
- /** The OAuth `resource` (RFC 8707) the reconnect should target, when supplied. */
214
- resource: string | null;
215
- /** The tool whose call triggered the reauth, for context. */
216
- toolName: string | null;
217
- /** A pre-minted authorization URL, when the broker already produced one. */
218
- authorizationUrl: string | null;
219
- occurredAt: string;
220
- };
221
- type TurnOutcome = "complete" | "failed" | "cancelled";
222
- type TurnEndItem = {
223
- kind: "turn-end";
224
- id: string;
225
- turnId: string | null;
226
- outcome: TurnOutcome;
227
- failureText: string | null;
228
- occurredAt: string;
229
- };
230
- type TimelineItem = UserMessageItem | AgentMessageItem | ReasoningItem | ToolCallItem | WorkerItem | WorkerCompletionItem | SandboxItem | SessionStatusItem | GoalItem | NoticeItem | AuthNeededItem | MemoryItem | TurnEndItem;
231
- /** Activity items cluster between chat messages (reasoning, tools, workers, sandbox, memory). */
232
- type ActivityItem = ReasoningItem | ToolCallItem | WorkerItem | SandboxItem | MemoryItem;
233
- type TimelineGroup = {
234
- kind: "item";
235
- item: TimelineItem;
236
- } | {
237
- kind: "activity";
238
- id: string;
239
- items: ActivityItem[];
240
- outcome?: TurnOutcome;
241
- failureText?: string;
242
- } | {
243
- kind: "turn";
244
- id: string;
245
- outcome: TurnOutcome;
246
- failureText?: string;
247
- startedAt: string;
248
- endedAt: string;
249
- groups: TimelineGroup[];
117
+ type UseGoalResult = {
118
+ /** The session goal, or null when the session has none. */
119
+ goal: SessionGoal | null;
120
+ /** Convenience flags over `goal.status`. */
121
+ isActive: boolean;
122
+ isPaused: boolean;
123
+ isCompleted: boolean;
124
+ loading: boolean;
125
+ error: Error | null;
126
+ refresh: () => Promise<void>;
127
+ /** Pause the goal loop (PATCH status=paused). */
128
+ pause: (rationale?: string) => Promise<SessionGoal | null>;
129
+ /** Resume a paused goal: resets counters and re-arms continuations. */
130
+ resume: () => Promise<SessionGoal | null>;
131
+ /** Clear the session goal; goal-less sessions remain a successful no-op. */
132
+ clearGoal: () => Promise<void>;
133
+ /** Alias for `clearGoal`. */
134
+ deleteGoal: () => Promise<void>;
135
+ /** True while a pause/resume/clear is in flight. */
136
+ updating: boolean;
137
+ mutationError: Error | null;
138
+ clearMutationError: () => void;
250
139
  };
251
-
252
- declare function buildTimeline(events: SessionEvent[]): TimelineItem[];
253
- /**
254
- * Whether the session's most recent turn ended in credit exhaustion — the
255
- * terminal credit state apps key their "add credits" affordances on. Derived
256
- * from the LAST turn-end event (completed/failed/cancelled): a later turn that
257
- * settles any other way (someone topped up and kept working) clears it.
258
- */
259
- declare function creditExhaustedFromEvents(events: SessionEvent[]): boolean;
260
- /** The latest session status carried in the event log, if any. */
261
- declare function sessionStatusFromEvents(events: SessionEvent[]): SessionStatus$1 | null;
262
- declare function groupTimeline(items: TimelineItem[]): TimelineGroup[];
263
140
  /**
264
- * Find a session id in orchestration tool arguments or output. Handles raw
265
- * objects, JSON strings, and MCP tool results (`{ content: [{ type: "text",
266
- * text: "{...}" }], structuredContent? }`).
141
+ * The session's goal: state, the autonomy counters (`autoContinuations`,
142
+ * `noProgressStreak`), and pause/resume control. A goal-less session yields
143
+ * `goal: null` (the 404 is absorbed). Live-updates on `goal.*` events —
144
+ * pass `options.events` from `useSessionEvents` to reuse its stream.
267
145
  */
268
- declare function extractSessionRef(value: unknown, depth?: number): string | null;
269
- /**
270
- * Readable label for a tool call ("session_create" -> "session create").
271
- *
272
- * MCP tools are namespaced `<serverId>__<toolName>` (see prefixedMcpToolName),
273
- * and for catalog-imported servers that serverId is an opaque slug+hash
274
- * ("mcp-integrations-sh-supabase-com-34ed9dcf1390-0i6tcf8"). De-slugging the
275
- * whole thing leaked that id into the timeline; strip the server prefix and show
276
- * just the tool ("list organizations"). Names without the `__` boundary (plain
277
- * built-ins like "session_create") are unaffected.
278
- */
279
- declare function toolDisplayName(name: string): string;
146
+ declare function useGoal(sessionId: string | null | undefined, options?: UseGoalOptions): UseGoalResult;
280
147
 
281
- type ToolRendererProps = {
282
- item: ToolCallItem;
148
+ type UseScheduledTasksOptions = ClientOverride & {
149
+ limit?: number | undefined;
150
+ pollIntervalMs?: number | undefined;
151
+ enabled?: boolean | undefined;
283
152
  };
284
- type ToolRenderer = ComponentType<ToolRendererProps>;
285
- /** A registry entry: which key it matches and the component that renders it. */
286
- type ToolRegistryEntry = {
287
- match: "rawType";
288
- type: string;
289
- render: ToolRenderer;
290
- } | {
291
- match: "name";
292
- name: string;
293
- render: ToolRenderer;
153
+ type UseScheduledTasksResult = {
154
+ tasks: ScheduledTask[];
155
+ loading: boolean;
156
+ error: Error | null;
157
+ refresh: () => Promise<void>;
294
158
  };
295
- type ToolRegistry = {
296
- /** Resolve the renderer for a call (never null — falls back to generic). */
297
- resolve: (item: ToolCallItem) => ToolRenderer;
298
- /** The generic fallback renderer. */
299
- fallback: ToolRenderer;
159
+ /** List the workspace's scheduled tasks (drift checks, sentinels, reapers, ...). */
160
+ declare function useScheduledTasks(options?: UseScheduledTasksOptions): UseScheduledTasksResult;
161
+
162
+ type UseWorkspaceSessionsOptions = ClientOverride & {
163
+ limit?: number | undefined;
164
+ parentSessionId?: string | null | undefined;
165
+ cursor?: string | undefined;
166
+ search?: string | undefined;
167
+ /** Refresh interval (ms) for fleet/manager views. Off by default. */
168
+ pollIntervalMs?: number | undefined;
169
+ enabled?: boolean | undefined;
300
170
  };
301
- type CreateToolRegistryOptions = {
171
+ type UseWorkspaceSessionsResult = {
302
172
  /**
303
- * Entries that take precedence over the built-ins. Earlier entries win, so a
304
- * consumer can shadow a default renderer for the same key.
173
+ * All visible rows, with pins first. This preserves the pre-pinning hook
174
+ * contract for consumers that only read `sessions`.
305
175
  */
306
- entries?: ToolRegistryEntry[] | undefined;
307
- /** Replace the generic fallback used for unmatched tools. */
308
- fallback?: ToolRenderer | undefined;
176
+ sessions: Session[];
177
+ /** The complete personal pinned section, also present in `sessions`. */
178
+ pinned: Session[];
179
+ /** True when the server omitted older pins from its bounded pinned section. */
180
+ pinnedTruncated: boolean;
181
+ nextCursor: string | null;
182
+ loading: boolean;
183
+ error: Error | null;
184
+ refresh: () => Promise<void>;
309
185
  };
310
- /** The `raw.type` of a projected tool call, when the provider item carries one. */
311
- declare function rawTypeOf(item: ToolCallItem): string | null;
312
- /**
313
- * Build a tool registry from a set of entries and a fallback. The returned
314
- * registry resolves in priority order: `raw.type` entries first, then `name`
315
- * entries, then the fallback. Consumer `entries` are consulted before the
316
- * built-in `baseEntries`, so they shadow defaults cleanly.
317
- */
318
- declare function createToolRegistry(baseEntries: ToolRegistryEntry[], baseFallback: ToolRenderer, options?: CreateToolRegistryOptions): ToolRegistry;
319
-
320
- /** The built-in tool renderer registry: every first-party tool plus a fallback. */
321
- declare const defaultToolRegistry: ToolRegistry;
322
- /** Build a registry that extends the built-ins with consumer entries/fallback. */
323
- declare function createDefaultToolRegistry(options?: Parameters<typeof createToolRegistry>[2]): ToolRegistry;
186
+ /** List the workspace's sessions the data behind fleet and manager views. */
187
+ declare function useWorkspaceSessions(options?: UseWorkspaceSessionsOptions): UseWorkspaceSessionsResult;
324
188
 
325
- type ActivityRailProps = {
326
- items: ActivityItem[];
327
- /** Renderer registry for tool calls. Defaults to {@link defaultToolRegistry}. */
328
- toolRegistry?: ToolRegistry | undefined;
329
- /** Drill into a spawned worker session. */
330
- onOpenSession?: ((sessionId: string) => void) | undefined;
331
- /**
332
- * Deep-link a memory row to its record in the host's memory pane. Opt-in: the
333
- * library draws no "View in memory" affordance without a handler (the memory
334
- * row is then non-interactive rich content). See {@link MessageTimelineProps}.
335
- */
336
- onMemoryClick?: ((memoryId: string) => void) | undefined;
337
- /** Drop the left rule + indent (used inside a folded turn summary). */
338
- bare?: boolean | undefined;
339
- className?: string | undefined;
189
+ type UseSessionLineageOptions = ClientOverride & {
190
+ events?: SessionEvent[] | undefined;
191
+ /** Refresh interval (ms). Off by default. */
192
+ pollIntervalMs?: number | undefined;
193
+ enabled?: boolean | undefined;
340
194
  };
341
- declare function ActivityRail({ items, toolRegistry, onOpenSession, onMemoryClick, bare, className, }: ActivityRailProps): react_jsx_runtime.JSX.Element;
195
+ type UseSessionLineageResult = {
196
+ lineage: SessionLineageResponse | null;
197
+ loading: boolean;
198
+ error: Error | null;
199
+ refresh: () => Promise<void>;
200
+ };
201
+ declare function isLineageRefreshEvent(event: SessionEvent): boolean;
202
+ /** Read the ancestors + descendant tree for one session. Data-only; no UI state. */
203
+ declare function useSessionLineage(sessionId: string | null | undefined, options?: UseSessionLineageOptions): UseSessionLineageResult;
342
204
 
205
+ type UseVariableSetsOptions = ClientOverride & {
206
+ pollIntervalMs?: number | undefined;
207
+ enabled?: boolean | undefined;
208
+ };
209
+ type UseVariableSetsResult = {
210
+ variableSets: VariableSet[];
211
+ loading: boolean;
212
+ error: Error | null;
213
+ refresh: () => Promise<void>;
214
+ create: (request: CreateVariableSetRequest) => Promise<VariableSet | null>;
215
+ update: (variableSetId: string, request: UpdateVariableSetRequest) => Promise<VariableSet | null>;
216
+ remove: (variableSetId: string) => Promise<boolean>;
217
+ /** Set/rotate a variable. Values are write-only — reads expose metadata only. */
218
+ setVariable: (variableSetId: string, name: string, value: string) => Promise<VariableSetVariableMetadata | null>;
219
+ deleteVariable: (variableSetId: string, name: string) => Promise<boolean>;
220
+ mutating: boolean;
221
+ mutationError: Error | null;
222
+ clearMutationError: () => void;
223
+ };
343
224
  /**
344
- * A subtle settle signal see the CHIP DOCTRINE above. The closed tone set.
345
- * `"interrupted"` is a calm neutral tone for cancelled items no dot, same
346
- * quiet weight as `"muted"`, but semantically distinct from metadata.
225
+ * Variable sets (named, encrypted variable sets attached to sessions
226
+ * and scheduled tasks). Variable values are write-only end to end: this hook
227
+ * never sees a value after it is sent.
347
228
  */
348
- type DisclosureChip = {
349
- tone: "ok" | "bad" | "muted" | "interrupted";
350
- text: string;
229
+ declare function useVariableSets(options?: UseVariableSetsOptions): UseVariableSetsResult;
230
+ /** @deprecated use UseVariableSetsOptions */
231
+ type UseEnvironmentsOptions = UseVariableSetsOptions;
232
+ /** @deprecated use UseVariableSetsResult */
233
+ type UseEnvironmentsResult = UseVariableSetsResult & {
234
+ environments: VariableSet[];
351
235
  };
352
- type ActivityDisclosureProps = {
353
- icon: ReactNode;
354
- /** Icon tint. Defaults to the muted foreground; renderers pass accent/failed. */
355
- iconTone?: "accent" | "failed" | "running" | "muted" | undefined;
356
- title: ReactNode;
357
- /** Render the title in the mono face (commands, paths). */
358
- titleMono?: boolean | undefined;
359
- /** Shimmer the title while the tool is in-flight. */
360
- running?: boolean | undefined;
361
- /**
362
- * Quiet single-line secondary text (truncated). It is detail-on-demand: hidden
363
- * when a media preview is set, AND hidden once the row is expanded (the body
364
- * then owns the detail), so a stat/path never appears twice at once.
365
- */
366
- preview?: ReactNode | undefined;
367
- /** A small inline media preview (a screenshot thumbnail) shown in place of `preview`. */
368
- media?: ReactNode | undefined;
369
- /** At most one quiet settle chip, right-aligned to the gutter. */
370
- chip?: DisclosureChip | undefined;
371
- /**
372
- * When true the row carries the standard failure affordance: the icon is tinted
373
- * red and a "failed" bad-chip appears in the right gutter (unless an explicit
374
- * `chip` is already supplied — the caller's chip wins). Output is still visible
375
- * on expand; this is a quiet status signal, not a blocking banner.
376
- *
377
- * Renderers should pass `failed={item.status === "failed"}` on their settled
378
- * (non-running) paths so any tool with a failed status shows a consistent
379
- * affordance without each renderer having to duplicate the logic.
380
- */
381
- failed?: boolean | undefined;
382
- /**
383
- * When true the row carries a calm "interrupted" affordance: the icon stays
384
- * muted (no red) and a quiet "interrupted" chip appears in the right gutter
385
- * (unless an explicit `chip` is already supplied — the caller's chip wins).
386
- * This is the cancelled-status analogue of `failed`, but deliberately calm
387
- * and neutral — it is NOT an error; the user chose to stop.
388
- *
389
- * Renderers should pass `cancelled={item.status === "cancelled"}` so any
390
- * in-flight item that was interrupted on turn.cancelled reads consistently.
391
- * `cancelled` is ignored when `failed` is also true (failure takes precedence).
392
- */
393
- cancelled?: boolean | undefined;
394
- /** When false the row is a static line (no expand affordance). */
395
- expandable?: boolean | undefined;
396
- children?: ReactNode | undefined;
236
+ /** @deprecated use useVariableSets */
237
+ declare function useEnvironments(options?: UseEnvironmentsOptions): UseEnvironmentsResult;
238
+
239
+ type UseRigsOptions = ClientOverride & {
240
+ pollIntervalMs?: number | undefined;
241
+ enabled?: boolean | undefined;
242
+ };
243
+ type UseRigsResult = {
244
+ rigs: Rig[];
245
+ loading: boolean;
246
+ error: Error | null;
247
+ refresh: () => Promise<void>;
248
+ create: (request: CreateRigRequest) => Promise<Rig | null>;
249
+ update: (rigId: string, request: UpdateRigRequest) => Promise<Rig | null>;
250
+ remove: (rigId: string) => Promise<boolean>;
251
+ listVersions: (rigId: string) => Promise<RigVersion[] | null>;
252
+ activateVersion: (rigId: string, versionId: string) => Promise<RigVersion | null>;
253
+ listChanges: (rigId: string) => Promise<RigChange[] | null>;
254
+ proposeChange: (rigId: string, request: ProposeRigChangeRequest) => Promise<RigChange | null>;
255
+ mutating: boolean;
256
+ mutationError: Error | null;
257
+ clearMutationError: () => void;
397
258
  };
398
259
  /**
399
- * The one disclosure row shape every activity row reuses (tool calls, reasoning,
400
- * sandbox ops): a chevron, a tinted icon, a title, an optional muted preview or
401
- * inline media, and at most one right-gutter settle chip. Compact by default;
402
- * the body mounts only when expanded.
403
- */
404
- declare function ActivityDisclosure({ icon, iconTone: iconToneProp, title, titleMono, running, preview, media, chip: chipProp, failed, cancelled, expandable, children, }: ActivityDisclosureProps): react_jsx_runtime.JSX.Element;
405
- declare function TermBlock({ command, workdir, output, live, tailLines, failed, }: {
406
- /**
407
- * The command shown in the prompt header. Pass `null` when the row title
408
- * already carries it (e.g. an exec row titled `$ cmd`): the header then drops
409
- * the command — and the whole prompt line if there is no workdir either — so
410
- * the command never reads twice, stacked, above the output.
411
- */
412
- command: string | null;
413
- workdir?: string | null | undefined;
414
- /** The FULL output. TermBlock owns the tail/full slicing internally. */
415
- output: string;
416
- live?: boolean | undefined;
417
- /** A non-zero exit / failed call — tints the left accent red (the one hue). */
418
- failed?: boolean | undefined;
419
- /**
420
- * When the output exceeds the tail window, only the last `tailLines` are shown
421
- * with a "show full output" toggle. The component holds the full text, so the
422
- * toggle reveals the rest (never a dead affordance). Defaults to 12.
423
- */
424
- tailLines?: number | undefined;
425
- }): react_jsx_runtime.JSX.Element;
426
- declare function PayloadBlock({ label, value, failed, }: {
427
- label: string;
428
- value: unknown;
429
- failed?: boolean | undefined;
430
- }): react_jsx_runtime.JSX.Element | null;
431
- /** A quiet inline note inside an expanded body (lost output, empty frame, …). */
432
- declare function BodyNote({ children, tone, }: {
433
- children: ReactNode;
434
- tone?: "error" | "muted" | undefined;
435
- }): react_jsx_runtime.JSX.Element;
436
- /**
437
- * A loading screenshot placeholder. A faint camera glyph over a shimmering box,
438
- * so a still frame of the running state reads unambiguously as "capturing" — not
439
- * a broken thumbnail.
260
+ * Rigs (workspace-scoped, versioned sandbox machine definitions). The list
261
+ * polls; version/change reads are on-demand (they are per-rig detail, not part
262
+ * of the list surface).
440
263
  */
441
- declare function MediaSkeleton(): react_jsx_runtime.JSX.Element;
442
- /** A standardized "tool ran, produced no image" placeholder in the media slot. */
443
- declare function MediaEmpty(): react_jsx_runtime.JSX.Element;
264
+ declare function useRigs(options?: UseRigsOptions): UseRigsResult;
265
+ type UseRigOptions = ClientOverride & {
266
+ pollIntervalMs?: number | undefined;
267
+ enabled?: boolean | undefined;
268
+ };
269
+ type UseRigResult = {
270
+ rig: Rig | null;
271
+ loading: boolean;
272
+ error: Error | null;
273
+ refresh: () => Promise<void>;
274
+ update: (request: UpdateRigRequest) => Promise<Rig | null>;
275
+ remove: () => Promise<boolean>;
276
+ activateVersion: (versionId: string) => Promise<RigVersion | null>;
277
+ proposeChange: (request: ProposeRigChangeRequest) => Promise<RigChange | null>;
278
+ /** Re-run verification for a change (asynchronous — poll for the outcome). */
279
+ verifyChange: (changeId: string) => Promise<RigChange | null>;
280
+ /** Promote a verified definition_edit into a new active version (rigs:manage). */
281
+ promoteChange: (changeId: string) => Promise<RigVersion | null>;
282
+ /** Re-verify the active version's checks in a clean throwaway sandbox. */
283
+ verify: () => Promise<{
284
+ ok: boolean;
285
+ versionId: string;
286
+ } | null>;
287
+ mutating: boolean;
288
+ mutationError: Error | null;
289
+ clearMutationError: () => void;
290
+ };
444
291
  /**
445
- * A small inline screenshot thumbnail that opens the app lightbox on click.
446
- *
447
- * Requires a `LightboxProvider` ancestor for the click-to-expand affordance.
448
- * Outside one it degrades to a plain, non-interactive image — never a dead
449
- * "Expand" button that announces an action it cannot perform.
292
+ * A single rig (its active version + counts), polled so verification and
293
+ * promotion state stay live. Owns the rig's write actions; the versions and
294
+ * changes lists are separate polled reads (`useRigVersions`/`useRigChanges`).
450
295
  */
451
- declare function Thumbnail({ src, caption, alt, }: {
452
- src: string;
453
- caption?: string | undefined;
454
- alt?: string;
455
- }): react_jsx_runtime.JSX.Element;
296
+ declare function useRig(rigId: string, options?: UseRigOptions): UseRigResult;
297
+ type UseRigVersionsOptions = ClientOverride & {
298
+ pollIntervalMs?: number | undefined;
299
+ enabled?: boolean | undefined;
300
+ };
301
+ type UseRigVersionsResult = {
302
+ versions: RigVersion[];
303
+ loading: boolean;
304
+ error: Error | null;
305
+ refresh: () => Promise<void>;
306
+ };
307
+ /** A rig's append-only version history, newest-first (polled). */
308
+ declare function useRigVersions(rigId: string, options?: UseRigVersionsOptions): UseRigVersionsResult;
309
+ type UseRigChangesOptions = ClientOverride & {
310
+ pollIntervalMs?: number | undefined;
311
+ enabled?: boolean | undefined;
312
+ };
313
+ type UseRigChangesResult = {
314
+ changes: RigChange[];
315
+ loading: boolean;
316
+ error: Error | null;
317
+ refresh: () => Promise<void>;
318
+ };
456
319
  /**
457
- * The expanded screenshot inside a tool body: a contained, clickable preview
458
- * (opens the lightbox) with a quiet caption. Constrained height + object-contain
459
- * so it never breaks the row layout. Like {@link Thumbnail}, it degrades to a
460
- * plain image outside a `LightboxProvider`.
320
+ * A rig's change queue (polled). The default poll cadence lets a change move
321
+ * through verifying merged/rejected without a manual refresh.
461
322
  */
462
- declare function ScreenshotFigure({ src, caption, alt, }: {
463
- src: string;
464
- caption?: string | undefined;
465
- alt?: string;
466
- }): react_jsx_runtime.JSX.Element;
323
+ declare function useRigChanges(rigId: string, options?: UseRigChangesOptions): UseRigChangesResult;
467
324
 
468
- type LightboxController = {
469
- open: (src: string, caption?: string) => void;
325
+ type UsePacksOptions = ClientOverride & {
326
+ pollIntervalMs?: number | undefined;
327
+ enabled?: boolean | undefined;
470
328
  };
471
- /** Open the app-level screenshot lightbox. No-op outside a `LightboxProvider`. */
472
- declare function useLightbox(): LightboxController;
473
- /**
474
- * The lightbox controller when one is mounted, or `null` outside a
475
- * `LightboxProvider`. Lets a media primitive degrade to a non-interactive image
476
- * (rather than a dead "Expand" button that announces an action it cannot do).
477
- */
478
- declare function useLightboxOptional(): LightboxController | null;
479
- /**
480
- * The app-level screenshot lightbox. Render once near the timeline; renderers
481
- * call `useLightbox().open(src)`.
482
- *
483
- * Idempotent by design: when an ancestor `LightboxProvider` already exists (e.g.
484
- * a `MessageTimeline` mounted inside an app that already wraps its shell), this
485
- * one becomes a pass-through and does NOT mount a second focus-trapping Dialog.
486
- * That keeps `MessageTimeline` self-sufficient (it owns its own provider) while
487
- * composing cleanly when nested.
488
- */
489
- declare function LightboxProvider({ children }: {
490
- children: ReactNode;
491
- }): react_jsx_runtime.JSX.Element;
329
+ type UsePacksResult = {
330
+ /** Built-in + registered packs available to the workspace. */
331
+ packs: CapabilityPack[];
332
+ /** Enable/disable state per pack. */
333
+ installations: PackInstallation[];
334
+ /** The installation for a pack id, if any. */
335
+ installationFor: (packId: string) => PackInstallation | null;
336
+ loading: boolean;
337
+ error: Error | null;
338
+ refresh: () => Promise<void>;
339
+ /** Register (or replace) a workspace-scoped pack manifest. */
340
+ register: (manifest: RegisterCapabilityPackRequest) => Promise<WorkspaceRegisteredPack | null>;
341
+ enable: (packId: string, request?: EnablePackRequest) => Promise<PackInstallation | null>;
342
+ /** Unregister a workspace-scoped pack (built-ins cannot be removed). */
343
+ remove: (packId: string) => Promise<boolean>;
344
+ mutating: boolean;
345
+ mutationError: Error | null;
346
+ clearMutationError: () => void;
347
+ };
348
+ /** Capability packs: catalog + installations + register/enable/unregister. */
349
+ declare function usePacks(options?: UsePacksOptions): UsePacksResult;
492
350
 
351
+ type UseWorkspacesOptions = Pick<ClientOverride, "client"> & {
352
+ pollIntervalMs?: number | undefined;
353
+ enabled?: boolean | undefined;
354
+ };
355
+ type UseWorkspacesResult = {
356
+ workspaces: Workspace[];
357
+ loading: boolean;
358
+ error: Error | null;
359
+ refresh: () => Promise<void>;
360
+ create: (request: CreateWorkspaceRequest) => Promise<Workspace | null>;
361
+ update: (workspaceId: string, request: UpdateWorkspaceRequest) => Promise<Workspace | null>;
362
+ mutating: boolean;
363
+ mutationError: Error | null;
364
+ clearMutationError: () => void;
365
+ };
493
366
  /**
494
- * Seed the initial open state of every timeline collapsible below this node.
495
- * Intended for screenshot/test instrumentation only; absent by default.
367
+ * The caller's workspaces (workspace switchers, onboarding). Not scoped to
368
+ * the provider's workspace, so it only needs the client.
496
369
  */
497
- declare function DisclosureDefaultsProvider({ defaultOpen, children, }: {
498
- defaultOpen: boolean;
499
- children: ReactNode;
500
- }): react_jsx_runtime.JSX.Element;
501
-
502
- type TurnSummaryProps = {
503
- /** The activity items in the turn (used only to compute the facet counts). */
504
- items: ActivityItem[];
505
- /**
506
- * The settled verdict — or absent for a completed CLUSTER of a still-running
507
- * turn, which folds neutrally: no verdict glyph (the turn has none yet), a
508
- * quiet pulse dot in its place so alignment and the running feel both hold.
509
- */
510
- outcome?: TurnOutcome | undefined;
511
- /** A short failure reason shown inline on a failed chip (never hidden). */
512
- failureText?: string | undefined;
513
- /** Elapsed turn duration; shown as a trailing facet when at least 1s. */
514
- durationMs?: number | undefined;
515
- /** Start expanded. */
516
- defaultOpen?: boolean | undefined;
517
- /**
518
- * A nested fold — a cluster or sub-turn INSIDE an already-expanded turn. It
519
- * drops the bordered/filled chip and renders as a plain disclosure node on the
520
- * parent's rail (chevron + glyph + facets), so expanding a turn reveals a thread
521
- * of nodes, never a stack of boxes-in-boxes. The top-level fold stays a chip.
522
- */
523
- bare?: boolean | undefined;
524
- /** The rendered activity rail revealed on expand. */
525
- children: React.ReactNode;
526
- };
527
- declare function TurnSummary({ items, outcome, failureText, durationMs, defaultOpen, bare, children, }: TurnSummaryProps): react_jsx_runtime.JSX.Element;
528
-
529
- /** Recover the exit code from a sandbox exec banner (`Process exited with code N`). */
530
- declare function sandboxCommandExitCode(out: unknown): number | null;
531
- /**
532
- * Recover the numeric exec-session id the sandbox embeds for a STILL-RUNNING
533
- * (backgrounded) process (`Process running with session ID N`). A finished
534
- * command emits `Process exited with code N` instead, which yields `null`.
535
- */
536
- declare function parseExecBannerSessionId(out: unknown): number | null;
537
- /** Strip the exec banner (`Chunk ID ...\n...\nOutput:\n`) down to the command's stdout. */
538
- declare function stripExecBanner(out: unknown): string;
539
- /** The sandbox clamped the output (token/line truncation markers in the banner). */
540
- declare function execTruncated(out: unknown): boolean;
541
- /** A `write_stdin` whose target PTY vanished (`write_stdin failed: session not found: N`). */
542
- declare function isExecSessionLostBanner(out: unknown): boolean;
543
- /** True when the exec stdout looks binary/garbled (a NUL byte or ELF magic). */
544
- declare function looksBinary(text: string): boolean;
545
- /**
546
- * Render unprintable control characters as caret notation (0x03 -> `^C`) so a
547
- * `write_stdin` keystroke payload reads cleanly in the row title.
548
- */
549
- declare function controlCaret(printable: string): string;
550
- /** One operation inside an `apply_patch_call` (a V4A file edit). */
551
- type ApplyPatchOperation = {
552
- /**
553
- * The V4A op kind. The three canonical values are `create_file`,
554
- * `update_file`, and `delete_file`; the open `string` tail tolerates a
555
- * forward-compatible/unknown op kind from the provider without a type error
556
- * (it falls through to the "Edited" treatment).
557
- */
558
- type: "create_file" | "update_file" | "delete_file" | (string & {});
559
- path: string;
560
- /** Rename target -- when present the op is a move/rename. */
561
- moveTo?: string | null | undefined;
562
- /** The V4A hunk string (`@@ ...` lines with `+`/`-`/context prefixes). */
563
- diff?: string | undefined;
564
- };
565
- /**
566
- * Parse a single V4A `apply_patch` operation into the SDK's `GitFileDiff` shape
567
- * so it can flow into the SAME `DiffView` / `PierreDiff` the Files tab uses.
568
- * Throws on a hunk string it cannot structure (no `@@` anchor on an update); the
569
- * renderer catches and falls back to a raw-patch view.
570
- */
571
- declare function v4aToGitFileDiff(op: ApplyPatchOperation): GitFileDiff;
572
- /**
573
- * Extract the `apply_patch` operations from a provider-native tool item's `raw`
574
- * payload, normalizing the two wire shapes (`raw.operations[]` for a multi-file
575
- * patch, `raw.operation` for a single op). The single owner of this shape so the
576
- * renderer and the turn-summary facet counter never drift.
577
- */
578
- declare function applyPatchOps(raw: unknown): ApplyPatchOperation[];
579
- /**
580
- * True when a tool item is an `apply_patch_call` — by its provider-native
581
- * `raw.type` (the live-wire source of truth) or by tool `name` (first-party
582
- * replays that omit `raw`). Centralizes the rawType-or-name check.
583
- */
584
- declare function isApplyPatch(item: {
585
- name: string;
586
- raw: unknown;
587
- }): boolean;
588
- /** Deep-redact secret-looking values so arguments never leak a key into the UI. */
589
- declare function redactSecrets(value: unknown): unknown;
590
- /** Parse tool arguments that may arrive as a JSON string or an object. */
591
- declare function parseToolArgs(args: unknown): Record<string, unknown>;
592
- /** The last non-empty line of a string -- the compact "what happened" peek. */
593
- declare function tailPeek(text: string): string;
594
- /**
595
- * Unwrap an MCP tool result (`{ content: [{ type: "text", text }], isError? }`)
596
- * into a flat `{ text, isError }`. Non-MCP outputs pass through as their string
597
- * form.
598
- */
599
- declare function unwrapMcpOutput(output: unknown): {
600
- text: string;
601
- isError: boolean;
602
- };
370
+ declare function useWorkspaces(options?: UseWorkspacesOptions): UseWorkspacesResult;
603
371
 
604
- type SessionEventsConnectionState = StreamConnectionState | "idle" | "ended" | "error";
605
- type UseSessionEventsOptions = ClientOverride & {
606
- /** Resume after this sequence (exclusive). Nonzero keeps full replay/resume semantics. */
607
- after?: number | undefined;
608
- /** Load a bounded tail by default, or opt back into full replay from `after`. */
609
- replay?: "windowed" | "full" | undefined;
610
- /** Pause the stream without unmounting (e.g. hidden tab). Defaults to true. */
372
+ type UseBillingUsageOptions = Pick<ClientOverride, "client"> & {
373
+ /** Account to read. Defaults to the caller's default account server-side. */
374
+ accountId?: string | undefined;
375
+ /** Filter usage to one workspace. */
376
+ workspaceId?: string | undefined;
377
+ /** Refresh interval (ms) for live billing meters. Off by default. */
378
+ pollIntervalMs?: number | undefined;
611
379
  enabled?: boolean | undefined;
612
380
  };
613
- type UseSessionEventsResult = {
614
- /** Replayed + live events, ordered by sequence, no gaps, no duplicates. */
615
- events: SessionEvent[];
616
- /** Projected, renderable timeline (memoized over `events`). */
617
- timeline: TimelineItem[];
618
- /** Latest session status observed in the event log, if any. */
619
- sessionStatus: SessionStatus$1 | null;
620
- connectionState: SessionEventsConnectionState;
621
- /** Highest sequence seen so far (0 before the first event). */
622
- lastSequence: number;
623
- /** True until the initial tail window has been applied (windowed mode). */
624
- initialLoading: boolean;
625
- /** Whether older durable events are available before the current window. */
626
- hasOlder: boolean;
627
- /** True while an older window is being fetched. */
628
- loadingOlder: boolean;
629
- /** Prepend an older density-bounded window; resolves true when more remain. */
630
- loadOlder: () => Promise<boolean>;
631
- error: Error | null;
632
- };
633
- /**
634
- * Live-stream a session's event log with replay-by-sequence, reconnect, and
635
- * batched React updates. Fresh loads default to a bounded tail window; pass
636
- * `replay: "full"` or a nonzero `after` for the previous full replay path.
637
- */
638
- declare function useSessionEvents(sessionId: string | null | undefined, options?: UseSessionEventsOptions): UseSessionEventsResult;
639
-
640
- type ComposerSendExtras = Omit<SendMessageInput, "text" | "clientEventId">;
641
- type UseComposerOptions = ClientOverride & SessionEventFeedOptions & {
642
- /** Called with the accepted text after a successful send. */
643
- onSent?: ((text: string) => void) | undefined;
644
- /**
645
- * Extra message fields (resources, tools, model, reasoningEffort) merged
646
- * into every send. A function is evaluated at send time so it can read the
647
- * surrounding UI state (attachment pickers, model selectors, ...).
648
- */
649
- sendExtras?: ComposerSendExtras | (() => ComposerSendExtras) | undefined;
650
- /** Latest server-derived workstream control; bound into Send/Steer OCC. */
651
- effectiveControl?: EffectiveSessionControl | null | undefined;
652
- /** Apply durable model/tool/reasoning settings in the host's controlled UI. */
653
- onDraftApplied?: ((draft: ComposerDraft) => void) | undefined;
654
- };
655
- type ComposerState = {
656
- value: string;
657
- setValue: (value: string) => void;
658
- /** Append the draft behind prompts already visible in the queue. */
659
- send: (text?: string) => Promise<boolean>;
660
- /** Supersede current direction with the draft. */
661
- steer: (text?: string) => Promise<boolean>;
662
- sending: boolean;
663
- canSend: boolean;
664
- /** Pause the session without deleting its prompt queue. */
665
- pause: (reason?: string) => Promise<void>;
666
- pausing: boolean;
667
- resume: (reason?: string) => Promise<void>;
668
- resumeScope: (option: EffectiveControlResumeOption) => Promise<void>;
669
- resuming: boolean;
670
- draft: ComposerDraft | null;
671
- draftRevision: number;
672
- draftLoading: boolean;
673
- draftSaving: boolean;
674
- draftConflict: Error | null;
675
- /** Apply an atomic queue Edit checkout without a second read. */
676
- applyDraft: (draft: ComposerDraft) => void;
677
- reloadDraft: () => Promise<void>;
678
- resolveDraftConflict: (choice: "keep_mine" | "use_remote") => Promise<void>;
679
- restoredResources: ResourceRef[];
680
- removeRestoredResource: (index: number) => void;
381
+ type UseBillingUsageResult = {
382
+ /** Prepaid credit balance (micro-USD), null until loaded. */
383
+ balance: BillingBalance | null;
384
+ /** Recent usage events (runs, tokens, cost, uploads, ...). */
385
+ usage: UsageEvent[];
386
+ loading: boolean;
681
387
  error: Error | null;
682
- clearError: () => void;
388
+ refresh: () => Promise<void>;
683
389
  };
684
390
  /**
685
- * Draft + send + Pause/Resume state for the chat composer — the only
686
- * human-to-agent input surface. The draft survives a failed send (nothing is
687
- * more hostile than losing a typed message); each send carries a generated
688
- * `clientEventId` so retries stay idempotent server-side.
689
- */
690
- declare function useComposer(sessionId: string | null | undefined, options?: UseComposerOptions): ComposerState;
691
- /**
692
- * Default text for a file-only message (attachment(s) present, no typed draft).
693
- * Kept non-empty so the wire contract (`text: z.string().min(1)`) and the
694
- * worker's non-whitespace guard accept it; the attached files still ride in
695
- * `resources`. Exported for tests.
696
- */
697
- declare const FILE_ONLY_MESSAGE_TEXT = "(see attached files)";
698
- /**
699
- * Merge the draft text + idempotency key with caller-provided extras. The
700
- * text and clientEventId always win over extras. Exported for tests.
391
+ * Account billing usage: credit balance + recent usage events — the data
392
+ * behind per-call billing meters. Account-scoped, so it only needs the
393
+ * client; pass `workspaceId` to narrow usage to one workspace.
701
394
  */
702
- declare function composeSendInput(text: string, clientEventId: string, extras: ComposerSendExtras | (() => ComposerSendExtras) | undefined, bound?: Partial<SendMessageInput>): SendMessageInput;
703
- /** Submit on plain Enter; Shift+Enter inserts a newline. Exported for tests. */
704
- declare function shouldSubmitOnKey(event: {
705
- key: string;
706
- shiftKey: boolean;
707
- metaKey?: boolean;
708
- ctrlKey?: boolean;
709
- nativeEvent?: {
710
- isComposing?: boolean;
711
- };
712
- }): boolean;
713
- /** Cmd/Ctrl+Enter steers; ordinary Enter appends to the queue. */
714
- declare function shouldSteerOnKey(event: {
715
- metaKey?: boolean;
716
- ctrlKey?: boolean;
717
- }): boolean;
718
-
719
- type UseFileAttachmentsOptions = ClientOverride & {
720
- /**
721
- * Only files matching this predicate are accepted by {@link
722
- * UseFileAttachmentsResult.addFromPaste} (the clipboard path). Defaults to
723
- * `image/*` — the console's historical paste filter. {@link
724
- * UseFileAttachmentsResult.addFiles} (the explicit picker / drop path)
725
- * bypasses it.
726
- */
727
- pasteFilter?: ((file: File) => boolean) | undefined;
728
- };
729
- type FileAttachment = {
730
- id: string;
731
- name: string;
732
- contentType: string;
733
- sizeBytes: number;
734
- status: "uploading" | "ready" | "failed";
735
- /** The SDK `FileAsset` once the upload finishes. */
736
- file?: FileAsset | undefined;
737
- /** Object-URL for an inline preview; minted for `image/*` files only. */
738
- previewUrl?: string | undefined;
739
- error?: string | undefined;
740
- };
741
- type UseFileAttachmentsResult = {
742
- attachments: FileAttachment[];
743
- /**
744
- * `FileResourceRef[]` for every attachment that finished uploading — feed
745
- * straight into `useComposer`'s `sendExtras.resources`.
746
- */
747
- readyResources: FileResourceRef[];
748
- /** True while any attachment is still uploading (drives the send-gate). */
749
- uploading: boolean;
750
- /** Explicit picker / drop path — uploads every file, no filter. */
751
- addFiles: (files: Iterable<File>) => void;
752
- /** Clipboard path — applies `pasteFilter` (default `image/*`) then uploads. */
753
- addFromPaste: (event: {
754
- clipboardData: DataTransfer | null;
755
- }) => void;
756
- /**
757
- * Re-run the upload for a `failed` attachment, in place (same id, same
758
- * source file). No-op for an id that isn't a known failed upload.
759
- */
760
- retry: (id: string) => void;
761
- /** Remove one attachment; revokes its object-URL. */
762
- remove: (id: string) => void;
763
- /** Remove all; revokes every object-URL. Call from `useComposer`'s `onSent`. */
764
- clear: () => void;
765
- };
766
- /**
767
- * Upload-and-track state for files attached to the next message. Owns the
768
- * full client-side upload layer: a per-file `uploading | ready | failed`
769
- * status machine driven by the SDK's `client.uploadFile`, object-URL image
770
- * previews with create/revoke lifecycle, the `image/*` clipboard paste filter,
771
- * and a `FileResourceRef[]` projection that drops straight into a message's
772
- * `resources`. Workspace-scoped, so it resolves both client and workspace from
773
- * the {@link OpenGeniProvider} (or a per-call `{ client, workspaceId }`).
774
- */
775
- declare function useFileAttachments(options?: UseFileAttachmentsOptions): UseFileAttachmentsResult;
395
+ declare function useBillingUsage(options?: UseBillingUsageOptions): UseBillingUsageResult;
776
396
 
777
- /** Events that can change the authoritative prompt queue or effective control. */
778
- declare function isTurnQueueEvent(event: Pick<SessionEvent, "type">): boolean;
779
- type QueueMutationKind = "move" | "edit" | "steer" | "delete";
780
- type UseTurnQueueOptions = ClientOverride & SessionEventFeedOptions & {
397
+ type UseAvailableModelsOptions = Pick<ClientOverride, "client"> & {
398
+ /** Refresh interval (ms). Off by default — the host model list rarely moves. */
781
399
  pollIntervalMs?: number | undefined;
400
+ enabled?: boolean | undefined;
782
401
  };
783
- type UseTurnQueueResult = {
784
- snapshot: SessionQueueSnapshot | null;
785
- /** Human/API prompts exactly in server execution order. Never client-sorted. */
786
- queue: SessionTurn[];
787
- effectiveControl: EffectiveSessionControl | null;
402
+ type UseAvailableModelsResult = {
403
+ /** The provider-grouped models the host exposes (empty until loaded). */
404
+ models: ClientModel[];
405
+ /** The deployment's default model id, null until loaded. */
406
+ defaultModel: string | null;
788
407
  loading: boolean;
789
408
  error: Error | null;
790
409
  refresh: () => Promise<void>;
791
- moveTurn: (turnId: string, beforeTurnId: string | null) => Promise<boolean>;
792
- /** Atomically withdraw a waiting prompt into the private durable composer draft. */
793
- editTurn: (turnId: string, options: {
794
- expectedDraftRevision: number;
795
- replaceDraft: boolean;
796
- }) => Promise<ComposerDraft | null>;
797
- /** Advance the same durable waiting prompt; no duplicate prompt is created. */
798
- steerTurn: (turnId: string) => Promise<boolean>;
799
- removeTurn: (turnId: string) => Promise<boolean>;
800
- pendingByTurn: Readonly<Record<string, QueueMutationKind>>;
801
- mutationFor: (turnId: string) => QueueMutationKind | null;
802
- mutating: boolean;
803
- mutationError: Error | null;
804
- clearMutationError: () => void;
805
410
  };
806
411
  /**
807
- * The one authoritative human prompt queue. Every mutation carries the exact
808
- * server versions the operator saw and accepts only monotonic snapshots. A
809
- * conflict immediately reloads server truth; the client never invents order.
412
+ * The host-exposed model list for a <ModelPicker>: fetches the deployment's
413
+ * public client config (`GET /v1/config/client`) and surfaces the richer
414
+ * provider-grouped `models` plus the `defaultModel` the picker should preselect.
415
+ * Deployment-scoped, so it only needs the client (no workspace).
810
416
  */
811
- declare function useTurnQueue(sessionId: string | null | undefined, options?: UseTurnQueueOptions): UseTurnQueueResult;
812
-
813
- /** The sole human prompt queue: compact above Goal, Agents, and composer. */
814
- type QueueSurfaceProps = {
815
- queue: UseTurnQueueResult;
816
- composer: ComposerState;
817
- readOnly?: false | undefined;
818
- } | {
819
- queue: UseTurnQueueResult;
820
- composer?: undefined;
821
- readOnly: true;
822
- };
823
- declare function QueueSurface({ queue, composer, readOnly }: QueueSurfaceProps): react_jsx_runtime.JSX.Element | null;
417
+ declare function useAvailableModels(options?: UseAvailableModelsOptions): UseAvailableModelsResult;
824
418
 
825
- /**
826
- * The slash-command registry. A command is a SESSION / OPERATOR control — an
827
- * action on the session or the UI (clear, compact, pause the goal, show help) —
828
- * NOT a structured way to talk to the agent. The human↔agent channel stays
829
- * plain chat; the palette only recognizes a leading "/" and never sends a
830
- * command to the model.
831
- *
832
- * Two kinds, modeled by where the handler does its work:
833
- * - CLIENT commands touch only the local UI (e.g. /help, /clear-view).
834
- * - SERVER commands call the API through the SDK (e.g. /clear, /compact,
835
- * /goal).
836
- * Both are just `run(args, ctx)`; `ctx` exposes the client for server commands
837
- * and the UI affordances (notice, openHelp, clearView, confirm) for both.
838
- */
839
- /** A positional argument a command accepts after its name. */
840
- type SlashArg = {
841
- name: string;
842
- /** Enter runs only once every required arg is present; otherwise autocompletes. */
843
- required?: boolean;
844
- /** Closed value set (rendered as a hint; validated by the command itself). */
845
- oneOf?: readonly string[];
846
- description?: string;
847
- };
848
- /** Transient feedback surfaced in the composer (generalized error line). */
849
- type Notice = {
850
- tone: "ok" | "error";
851
- message: string;
852
- };
853
- /** Everything a command handler can reach. Assembled by the composer. */
854
- type CommandContext = {
855
- /** SDK-shaped client for server commands. */
856
- client: SessionClientLike;
857
- workspaceId: string;
858
- /** Null before a session exists (server commands should guard on this). */
859
- sessionId: string | null;
860
- status: SessionStatus$1 | null;
861
- /** The operator's permissions on this workspace (gates command visibility). */
862
- permissions: Permission[];
863
- /** Surface a transient ok/error notice in the composer. */
864
- notice: (notice: Notice) => void;
865
- /** Open the in-composer /help panel (rendered from the registry). */
866
- openHelp: () => void;
419
+ type SessionCapabilitiesState = "idle" | "negotiating" | "ready" | "cold" | "on-demand" | "error";
420
+ type UseSessionCapabilitiesOptions = ClientOverride & {
867
421
  /**
868
- * Reset only the LOCAL timeline view — no server call. Returns whether a
869
- * view-reset affordance was actually wired (and thus had an effect): the host
870
- * surface supplies one via the composer's `onClearView` prop, and consoles
871
- * that don't (no resettable local timeline) get `false`. The /clear-view
872
- * command uses this to avoid reporting a false "cleared" success on a no-op.
422
+ * Live event log to fold `stream.url.rotated` from (usually
423
+ * `useSessionEvents().events`). When present the desktop socket stays fresh on
424
+ * a box rollover without a round-trip; stale-epoch rotations are dropped.
873
425
  */
874
- clearView: () => boolean;
875
- /** Show the danger confirm bar; resolves true once the operator confirms. */
876
- confirm: () => Promise<boolean>;
877
- };
878
- type CommandResult = {
879
- status: "ok" | "error";
880
- message?: string;
426
+ events?: SessionEvent[] | undefined;
881
427
  /**
882
- * Keep the composer draft instead of clearing it on an ok result. Used when a
883
- * command resolves to a no-op the operator may want to retry — e.g. canceling
884
- * the /clear confirm bar returns ok (no error) but must NOT wipe the typed
885
- * "/clear" draft. Default false: a successful command clears the draft.
428
+ * Whether to acquire a viewer holder for the desktop pixel plane. Requires the
429
+ * un-redacted acknowledgment to have been recorded (else the attach 409s and
430
+ * the hook surfaces the consent requirement). Default false: read-only
431
+ * negotiation (no holder, no warm) terminal/files/git work without it.
886
432
  */
887
- keepDraft?: boolean;
888
- };
889
- type SlashCommand = {
890
- /** Primary token after the slash (no leading "/"). */
891
- name: string;
892
- /** Alternate tokens that resolve to this command. */
893
- aliases?: readonly string[];
894
- description: string;
895
- args?: readonly SlashArg[];
896
- /** Required permission; the command is hidden from the palette without it. */
897
- permission?: Permission;
898
- /** Destructive — the palette shows a confirm bar before running. */
899
- danger?: boolean;
433
+ attachDesktop?: boolean | undefined;
900
434
  /**
901
- * Dynamic availability beyond the permission gate (e.g. hide a server command
902
- * until a session exists). Returning false hides the command.
435
+ * Whether to acquire a viewer holder to warm the box for the REAL interactive
436
+ * terminal (the ttyd pty-ws plane). Symmetric with `attachDesktop` and shares
437
+ * the SAME viewer attach (one warm box serves both planes), but needs NO
438
+ * un-redacted acknowledgment — a shell is interactive by nature, and the gate
439
+ * is the scoped tunnel URL + stream token. Default false: the terminal stays on
440
+ * the read-only Channel-A firehose until the user opens/focuses it. The attach
441
+ * folds the minted `pty-ws` url+token into the `Terminal` cell.
903
442
  */
904
- available?: (ctx: Pick<CommandContext, "sessionId" | "status" | "permissions">) => boolean;
905
- /** Execute the command. Throwing is caught and surfaced as an error notice. */
906
- run: (args: string[], ctx: CommandContext) => Promise<CommandResult> | CommandResult;
907
- };
908
-
909
- /**
910
- * Context the composer supplies for command execution and visibility. The
911
- * composer owns the UI affordances (notice/openHelp/clearView/confirm), so they
912
- * are NOT part of this slice — the hook closes over them via `handlers`.
913
- */
914
- type SlashCommandContext = Pick<CommandContext, "client" | "workspaceId" | "sessionId" | "status" | "permissions">;
915
- /**
916
- * UI affordances the composer supplies. `confirm` differs from the registry-
917
- * facing {@link CommandContext.confirm} (which takes no args): the composer's
918
- * confirm receives the command being run so the confirm bar renders from that
919
- * exact command's identity. The hook bridges the two in {@link buildContext}.
920
- */
921
- type SlashCommandHandlers = Pick<CommandContext, "notice" | "openHelp" | "clearView"> & {
922
- confirm: (command: SlashCommand) => Promise<boolean>;
923
- };
924
- type ConfirmState = {
925
- command: SlashCommand;
926
- /** Resolve the pending confirm() promise. */
927
- resolve: (confirmed: boolean) => void;
928
- } | null;
929
- type UseSlashCommandsOptions = {
930
- commands: readonly SlashCommand[];
931
- context: SlashCommandContext | undefined;
932
- handlers: SlashCommandHandlers;
933
- /** The current composer draft. */
934
- value: string;
935
- /** Replace the composer draft (autocomplete writes through this). */
936
- setValue: (value: string) => void;
937
- };
938
- type UseSlashCommandsResult = {
939
- /** Whether the palette is open (a command token is being typed). */
940
- open: boolean;
443
+ attachTerminal?: boolean | undefined;
941
444
  /**
942
- * Whether the draft is a slash-command attempt (matches a registered command)
943
- * true even after Escape dismisses the popover. The composer blocks its send
944
- * path while this holds so a command can't be delivered to the agent as chat.
445
+ * Whether to acquire a viewer holder to warm the box for a FILE WRITE — the
446
+ * wake-on-edit INTENT (a first keystroke in the editor), NOT passive browsing.
447
+ * Shares the SAME viewer attach as the desktop/terminal (one warm box, one
448
+ * holder) and — like the terminal — needs NO un-redacted acknowledgment:
449
+ * reading/writing files is the ordinary Channel-A control plane, not the pixel
450
+ * plane. It folds NO live URL (files ride the stateless HTTP plane) — it only
451
+ * refcounts liveness. Unlike desktop/terminal it warms even a COLD box: an edit
452
+ * legitimately cold-creates (that IS the wake), so the write lands ~100ms warm
453
+ * instead of paying the ~5s cold resume. Default false. IMPORTANT: merely
454
+ * opening/reading the Files tab must NOT set this — browsing capture-served
455
+ * trees/diffs needs no box, and warming one on a cold glance burns box-hours to
456
+ * serve reads the capture already answers for free.
945
457
  */
946
- isCommandDraft: boolean;
947
- /** Commands shown for the current token + context, in display order. */
948
- items: SlashCommand[];
949
- /** Index into `items` of the highlighted row. */
950
- highlight: number;
951
- setHighlight: (index: number) => void;
952
- /** The matched command once the name is closed by a space (arg-hint mode). */
953
- activeCommand: SlashCommand | null;
954
- /** The arg hint string for the active command (footer), or "". */
955
- activeArgHint: string;
458
+ attachFiles?: boolean | undefined;
459
+ /** Hold off negotiating (e.g. the workbench panel is collapsed). Default true. */
460
+ enabled?: boolean | undefined;
461
+ /** Poll cadence (ms) while the lease is cold/warming. Default 1500. */
462
+ warmingPollMs?: number | undefined;
956
463
  /**
957
- * Key handler for the textarea. Returns true when it consumed the event
958
- * (the composer must then NOT run its send path). Only consumes while open.
464
+ * Give up waiting for `warm` after this long while polling (ms) and surface a
465
+ * stalled error with a manual `renegotiate`. Default 30000. This is a UI
466
+ * patience deadline, independent from the worker's sandbox warming timeout.
467
+ * 0 disables the deadline.
959
468
  */
960
- onKeyDown: (event: KeyboardEvent<HTMLTextAreaElement>) => boolean;
961
- /** Run the highlighted command (or the active command in arg-hint mode). */
962
- runHighlighted: () => Promise<void>;
469
+ warmingDeadlineMs?: number | undefined;
470
+ };
471
+ type UseSessionCapabilitiesResult = {
472
+ /** The negotiated capability doc — the single source of UI truth. */
473
+ capabilities: SessionCapabilities | null;
474
+ state: SessionCapabilitiesState;
475
+ error: Error | null;
963
476
  /**
964
- * Run the command at an explicitly chosen index (a pointer click on a row).
965
- * Bypasses the exact-match token heuristic that runHighlighted uses for
966
- * keyboard Enter, so an explicit click always runs the clicked command.
477
+ * 409 from the desktop attach: the un-redacted (or shared) plane needs explicit
478
+ * acknowledgment before a viewer holder is granted. Drives the consent prompt.
967
479
  */
968
- runAt: (index: number) => Promise<void>;
969
- /** Autocomplete the highlighted command name + a trailing space. */
970
- autocompleteHighlighted: () => void;
480
+ acknowledgmentRequired: "unredacted" | "shared" | null;
481
+ /** 429 from the desktop attach: the per-session viewer cap is reached. */
482
+ viewerCapReached: boolean;
483
+ /** The viewer holder id minted on a desktop attach (for detach/heartbeat). */
484
+ viewerId: string | null;
485
+ /** Force a re-negotiation (after acknowledging, a resolution change, etc.). */
486
+ renegotiate: () => void;
971
487
  };
972
- declare function useSlashCommands(options: UseSlashCommandsOptions): UseSlashCommandsResult;
973
-
974
- type ChatComposerProps = {
975
- composer: ComposerState;
976
- /** Canonical workstream control, separate from lifecycle status. */
977
- effectiveControl?: EffectiveSessionControl | null | undefined;
978
- /** Waiting prompts already ahead of a normal Send. */
979
- queuedAheadCount?: number | undefined;
980
- /** Whether broader Workspace Resume is authorized for this viewer. */
981
- canControlWorkspace?: boolean | undefined;
982
- /** Optional host routes used to navigate from effective Pause blockers. */
983
- controlLinks?: {
984
- workspaceHref?: string | undefined;
985
- sessionHref?: ((sessionId: string) => string) | undefined;
986
- } | undefined;
987
- placeholder?: string | undefined;
988
- disabled?: boolean | undefined;
989
- autoFocus?: boolean | undefined;
990
- /** Replaces the default keyboard hint under the field. */
991
- hint?: string | undefined;
992
- /** App controls (model picker, attach button, ...) in the footer row, replacing the hint. */
993
- controlsStart?: ReactNode | undefined;
994
- /** Content rendered above the textarea, inside the field chrome (e.g. attachment chips). */
995
- header?: ReactNode | undefined;
996
- /** Paste hook on the textarea (e.g. paste-image-to-attach). */
997
- onPaste?: ((event: ClipboardEvent<HTMLTextAreaElement>) => void) | undefined;
998
- /**
999
- * Opt-in file attachments. When supplied (e.g. from {@link useFileAttachments}),
1000
- * the composer renders a built-in attach button (prepended to `controlsStart`),
1001
- * an attachment-chips strip (above the textarea, before any host `header`),
1002
- * routes paste through `addFromPaste` (image/* filter lives in the hook), and
1003
- * gates send while `uploading` so a message never departs without its files.
1004
- * Absent → no attachment UI renders and the composer behaves exactly as before.
1005
- */
1006
- attachments?: UseFileAttachmentsResult | undefined;
1007
- /**
1008
- * Opt-in model picker. When supplied (e.g. from {@link useAvailableModels}),
1009
- * the composer renders a {@link ModelPicker} at the start of `controlsStart`
1010
- * so the operator can choose which host-exposed model serves the next message.
1011
- * The host owns the selection (`selectedModel`/`onSelectModel`) and is
1012
- * responsible for threading it into the composer's `sendExtras` (typically
1013
- * `useComposer({ sendExtras: () => ({ model }) })`) so `composeSendInput`
1014
- * carries it. Absent → no picker renders and the composer behaves as before.
1015
- */
1016
- models?: ClientModel[] | undefined;
1017
- /** The currently selected model id (the picker's controlled value). */
1018
- selectedModel?: string | undefined;
1019
- /** Called with the chosen model id when the operator picks one. */
1020
- onSelectModel?: ((modelId: string) => void) | undefined;
1021
- className?: string | undefined;
1022
- /**
1023
- * Slash-command palette. Defaults to the built-in {@link defaultCommands};
1024
- * apps concat their own. Backward-compatible: when `commandContext` is absent
1025
- * the palette is inert and behavior is identical to before.
1026
- */
1027
- commands?: readonly SlashCommand[] | undefined;
1028
- /**
1029
- * Wiring the palette needs to run server commands and gate visibility. The
1030
- * composer supplies notice/openHelp/clearView/confirm internally.
1031
- */
1032
- commandContext?: SlashCommandContext | undefined;
1033
- /** Reset the local timeline view (the /clear-view command target). */
1034
- onClearView?: (() => void) | undefined;
1035
- };
1036
- declare const OPEN_WORKSTREAM_CONTROL_EVENT = "opengeni:open-workstream-control";
1037
488
  /**
1038
- * The chat composer — the only human-to-agent input surface. Plain chat in,
1039
- * everything else is the agent's job. Enter sends, Shift+Enter breaks the
1040
- * line, and the stop control appears while a turn is running (sending while
1041
- * running is legitimate steering, so send stays available too).
489
+ * The capability-negotiation hook. Discovers what THIS session+backend+OS
490
+ * supports (FileSystem/Terminal/Git always-ish; DesktopStream/Recording
491
+ * sometimes), drives capability-gated rendering, and when `attachDesktop`
492
+ * holds a viewer lease + heartbeats it so the box stays warm while watched.
1042
493
  *
1043
- * Typing a leading "/" opens the slash-command palette SESSION/OPERATOR
1044
- * controls (clear, compact, pause goal, help), never a structured channel to
1045
- * the agent. The palette is purely additive: with no `commandContext` it is
1046
- * inert and the composer behaves exactly as before.
494
+ * Degradation is a value, never a crash: an unsupported surface comes back
495
+ * `available:false`/`transport:null` + a `reason`; the components render the
496
+ * reason-aware empty state. 409 (consent) and 429 (viewer cap) are surfaced as
497
+ * typed signals, not thrown.
1047
498
  */
1048
- declare function ChatComposer({ composer, effectiveControl, queuedAheadCount, canControlWorkspace, controlLinks, placeholder, disabled, autoFocus, hint, controlsStart, header, onPaste, attachments, models, selectedModel, onSelectModel, className, commands, commandContext, onClearView, }: ChatComposerProps): react_jsx_runtime.JSX.Element;
499
+ declare function useSessionCapabilities(sessionId: string | null | undefined, options?: UseSessionCapabilitiesOptions): UseSessionCapabilitiesResult;
1049
500
 
1050
- /** Event types that change the session goal (set/updated/completed/paused/...). */
1051
- declare function isGoalEvent(event: Pick<SessionEvent, "type">): boolean;
1052
- type UseGoalOptions = ClientOverride & SessionEventFeedOptions & {
1053
- /** Optional safety-net polling (ms). Off by default goal.* events drive updates. */
1054
- pollIntervalMs?: number | undefined;
501
+ /**
502
+ * The minimal WebSocket surface the frame renderer drives. Lets tests (and a
503
+ * future transport swap) inject a fake without a live socket — the exact
504
+ * analogue of `DesktopRfbFactory` for the noVNC path. Structurally a subset of
505
+ * the browser `WebSocket`.
506
+ */
507
+ interface DesktopWebSocketLike {
508
+ binaryType: string;
509
+ send(data: ArrayBuffer): void;
510
+ close(): void;
511
+ addEventListener(type: "open" | "message" | "error" | "close", listener: (ev: {
512
+ data?: unknown;
513
+ } & Record<string, unknown>) => void): void;
514
+ removeEventListener(type: "open" | "message" | "error" | "close", listener: (ev: {
515
+ data?: unknown;
516
+ } & Record<string, unknown>) => void): void;
517
+ }
518
+ type DesktopWebSocketFactory = (url: string) => DesktopWebSocketLike;
519
+ type UseRelayFrameStreamOptions = {
520
+ /** The desktop cell of the negotiated capabilities (`capabilities.DesktopStream`). */
521
+ capability: DesktopStreamCapability | null;
522
+ /** The mount target. A `<canvas>` is appended here on connect. */
523
+ containerRef: RefObject<HTMLDivElement | null>;
524
+ /** Custom socket factory (tests / a transport swap). Defaults to `new WebSocket(url)`. */
525
+ webSocketFactory?: DesktopWebSocketFactory | undefined;
1055
526
  };
1056
- type UseGoalResult = {
1057
- /** The session goal, or null when the session has none. */
1058
- goal: SessionGoal | null;
1059
- /** Convenience flags over `goal.status`. */
1060
- isActive: boolean;
1061
- isPaused: boolean;
1062
- isCompleted: boolean;
1063
- loading: boolean;
527
+ type UseRelayFrameStreamResult = {
528
+ state: DesktopConnectionState;
1064
529
  error: Error | null;
1065
- refresh: () => Promise<void>;
1066
- /** Pause the goal loop (PATCH status=paused). */
1067
- pause: (rationale?: string) => Promise<SessionGoal | null>;
1068
- /** Resume a paused goal: resets counters and re-arms continuations. */
1069
- resume: () => Promise<SessionGoal | null>;
1070
- /** Clear the session goal; goal-less sessions remain a successful no-op. */
1071
- clearGoal: () => Promise<void>;
1072
- /** Alias for `clearGoal`. */
1073
- deleteGoal: () => Promise<void>;
1074
- /** True while a pause/resume/clear is in flight. */
1075
- updating: boolean;
1076
- mutationError: Error | null;
1077
- clearMutationError: () => void;
530
+ /** Tear down + reopen the socket (e.g. after a drop, once a fresh url arrives). */
531
+ reconnect: () => void;
1078
532
  };
1079
533
  /**
1080
- * The session's goal: state, the autonomy counters (`autoContinuations`,
1081
- * `noProgressStreak`), and pause/resume control. A goal-less session yields
1082
- * `goal: null` (the 404 is absorbed). Live-updates on `goal.*` events
1083
- * pass `options.events` from `useSessionEvents` to reuse its stream.
534
+ * VIEW-ONLY PNG-frame renderer for a SELF-HOSTED desktop stream — the relay
535
+ * transport (`transport: "relay-frames"`, `client: "frames"`). The self-hosted
536
+ * agent produces one PNG per frame as a protobuf datagram over the relay; there
537
+ * is no RFB/noVNC here. This hook opens the relay DESKTOP channel as a CLIENT
538
+ * (mirroring the proven diagnostic wire protocol), decodes each PNG onto a
539
+ * `<canvas>` mounted into `containerRef`, and drives the connection state
540
+ * machine: idle → connecting (ws opening) → connected (first painted frame) →
541
+ * error (ws error/close/ack rejection).
542
+ *
543
+ * It is DORMANT for any other transport (or no url): it stays idle and touches
544
+ * nothing, so the noVNC path owns the surface. SSR-safe: the socket + DOM attach
545
+ * live inside `useEffect`.
546
+ *
547
+ * Backpressure (critical — frames are ~1.8MB at ~10fps): at most one frame is
548
+ * decoded at a time; a frame arriving mid-decode replaces the pending one
549
+ * (latest-wins), so a slow decode can never build an unbounded queue.
1084
550
  */
1085
- declare function useGoal(sessionId: string | null | undefined, options?: UseGoalOptions): UseGoalResult;
551
+ declare function useRelayFrameStream(options: UseRelayFrameStreamOptions): UseRelayFrameStreamResult;
1086
552
 
1087
- type UseSessionControlOptions = ClientOverride;
1088
- type UseSessionControlResult = {
1089
- pause: (reason?: string) => Promise<SessionControlResponse | null>;
1090
- resume: (reason?: string) => Promise<SessionControlResponse | null>;
1091
- controlling: boolean;
1092
- /** Approve a pending `requires_action` approval. */
1093
- approve: (approvalId: string, message?: string) => Promise<SessionEvent | null>;
1094
- /** Reject a pending `requires_action` approval. */
1095
- reject: (approvalId: string, message?: string) => Promise<SessionEvent | null>;
1096
- /** True while an approval decision is in flight. */
1097
- responding: boolean;
553
+ type UseDesktopStreamOptions = {
554
+ /** The desktop cell of the negotiated capabilities (`capabilities.DesktopStream`). */
555
+ capability: DesktopStreamCapability | null;
556
+ /** The mount target. RFB (or the frame canvas) attaches here on connect. */
557
+ containerRef: RefObject<HTMLDivElement | null>;
558
+ /** Read-only by default (v1 ruling H). interactive only when cap.mode allows. */
559
+ interactive?: boolean | undefined;
560
+ scaleViewport?: boolean | undefined;
561
+ /** Custom RFB factory (tests / a WebRTC swap). Defaults to a lazy @novnc/novnc. */
562
+ rfbFactory?: DesktopRfbFactory | undefined;
563
+ /** Custom socket factory for the `relay-frames` transport (tests). Defaults to
564
+ * `new WebSocket(url)`. Mirrors `rfbFactory` for the frame renderer. */
565
+ webSocketFactory?: DesktopWebSocketFactory | undefined;
566
+ };
567
+ type UseDesktopStreamResult = {
568
+ state: DesktopConnectionState;
1098
569
  error: Error | null;
1099
- clearError: () => void;
570
+ /** Manual reconnect (e.g. after a securityfailure once a fresh URL arrives). */
571
+ reconnect: () => void;
1100
572
  };
1101
573
  /**
1102
- * Session pause/resume and approval decisions. Pair with
1103
- * `useSessionEvents` (for `session.requiresAction` payloads carrying the
1104
- * `approvalId`) to render an approval bar.
574
+ * Drive the noVNC RFB lifecycle from a `DesktopStreamCapability`, using the
575
+ * SDK's `desktop.ts` reducer + `desktopSocketUrl`. SSR-safe: the RFB import and
576
+ * the DOM attach happen inside `useEffect`, so a server render is a no-op and
577
+ * the component shows its placeholder until hydration.
578
+ *
579
+ * Read-only is enforced at three layers: `capability.mode` (server) →
580
+ * `interactive` prop → `RFB.viewOnly`. v1 always resolves to read-only. On a
581
+ * capability `url` change (a rotation), the old RFB disconnects and a fresh one
582
+ * connects to the new URL — a brief "desktop blink", acceptable on rollover.
583
+ *
584
+ * Transport dispatch: a Modal box negotiates `transport: "vnc-ws"` and drives the
585
+ * noVNC RFB below. A SELF-HOSTED machine negotiates `transport: "relay-frames"`
586
+ * (PNG-per-frame over the relay, view-only) — that path is delegated to
587
+ * `useRelayFrameStream`, which paints a `<canvas>`. Both hooks are ALWAYS called
588
+ * (rules of hooks); each is dormant for the other's transport, and we return the
589
+ * result of whichever owns the surface. The public interface is identical either
590
+ * way, so `DesktopViewer` is transport-agnostic.
1105
591
  */
1106
- declare function useSessionControl(sessionId: string | null | undefined, options?: UseSessionControlOptions): UseSessionControlResult;
592
+ declare function useDesktopStream(options: UseDesktopStreamOptions): UseDesktopStreamResult;
1107
593
 
1108
- type UseScheduledTasksOptions = ClientOverride & {
1109
- limit?: number | undefined;
1110
- pollIntervalMs?: number | undefined;
1111
- enabled?: boolean | undefined;
594
+ /** The ttyd connection lifecycle as surfaced to the component. */
595
+ type TerminalStreamStatus = "connecting" | "open" | "closed" | "error";
596
+ type UseTerminalStreamOptions = {
597
+ /** The Terminal cell of the negotiated capabilities (`capabilities.Terminal`).
598
+ * The stream connects ONLY when `transport === "pty-ws"` and `url` is set; on a
599
+ * cold box (`transport === "sse-events"` / no url) it stays idle and the caller
600
+ * falls back to the Channel-A read-only firehose. */
601
+ capability: Pick<TerminalCapability, "transport" | "url" | "token"> | null;
602
+ /** Called for each OUTPUT payload from ttyd (write verbatim into xterm). */
603
+ onOutput?: ((data: string) => void) | undefined;
604
+ /** Called when ttyd sends a SET_WINDOW_TITLE frame. */
605
+ onTitle?: ((title: string) => void) | undefined;
606
+ /** Initial PTY size to seed the ttyd auth frame + first resize. */
607
+ initialCols?: number | undefined;
608
+ initialRows?: number | undefined;
1112
609
  };
1113
- type UseScheduledTasksResult = {
1114
- tasks: ScheduledTask[];
1115
- loading: boolean;
1116
- error: Error | null;
1117
- refresh: () => Promise<void>;
610
+ type UseTerminalStreamResult = {
611
+ /** True once the ttyd socket is open (and the auth frame has been sent). */
612
+ connected: boolean;
613
+ status: TerminalStreamStatus;
614
+ /** Pipe a keystroke/paste to the PTY stdin. No-op until the socket is open. */
615
+ write: (data: string) => void;
616
+ /** Tell ttyd the PTY window changed size (on xterm fit/resize). */
617
+ resize: (cols: number, rows: number) => void;
618
+ /** Tear the socket down (the effect also tears down on unmount / url change). */
619
+ disconnect: () => void;
1118
620
  };
1119
- /** List the workspace's scheduled tasks (drift checks, sentinels, reapers, ...). */
1120
- declare function useScheduledTasks(options?: UseScheduledTasksOptions): UseScheduledTasksResult;
621
+ /**
622
+ * Drive a ttyd PTY-over-websocket connection from a `pty-ws` Terminal capability,
623
+ * symmetric with `use-desktop-stream` (the noVNC-over-tunnel hook). The scoped
624
+ * stream token is already embedded in the minted tunnel `url`; the WebSocket is
625
+ * opened with the REQUIRED ttyd subprotocol "tty".
626
+ *
627
+ * ttyd wire protocol (see `@opengeni/sdk/terminal`):
628
+ * - first frame: `JSON.stringify({ AuthToken: "" })` (+ optional columns/rows).
629
+ * - client→server: INPUT = "0"+data ; RESIZE = "1"+JSON({columns,rows}).
630
+ * - server→client: "0" = OUTPUT (→ xterm) ; "1" = SET_WINDOW_TITLE ;
631
+ * "2" = SET_PREFERENCES (ignored). Binary frames are decoded the same way.
632
+ *
633
+ * On a `url`/`token` rotation (a box rollover folds a fresh address into the cell)
634
+ * the effect re-runs: the old socket closes and a fresh one connects — a brief
635
+ * terminal blink, acceptable on rollover (mirrors the desktop's RFB hot-swap).
636
+ * SSR-safe: the socket open lives in `useEffect`, so a server render is a no-op.
637
+ */
638
+ declare function useTerminalStream(options: UseTerminalStreamOptions): UseTerminalStreamResult;
1121
639
 
1122
- type UseWorkspaceSessionsOptions = ClientOverride & {
1123
- limit?: number | undefined;
1124
- parentSessionId?: string | null | undefined;
1125
- cursor?: string | undefined;
1126
- search?: string | undefined;
1127
- /** Refresh interval (ms) for fleet/manager views. Off by default. */
1128
- pollIntervalMs?: number | undefined;
1129
- enabled?: boolean | undefined;
640
+ type TerminalChunk = {
641
+ /** Stable key (the source event id) so the xterm writer tracks a written-cursor. */
642
+ id: string;
643
+ /** Raw output bytes (utf-8 lossy) — written verbatim into xterm. */
644
+ text: string;
645
+ /** stdout vs stderr (drives optional tinting). */
646
+ stream: "stdout" | "stderr";
647
+ /** Global ordering: the source event sequence. */
648
+ seq: number;
1130
649
  };
1131
- type UseWorkspaceSessionsResult = {
650
+ type UseSandboxTerminalOptions = ClientOverride & {
651
+ /** The live session event log (usually `useSessionEvents().events`). */
652
+ events: SessionEvent[];
653
+ /** Restrict to one PTY (by ptyId). Omit to interleave the agent firehose +
654
+ * every PTY. */
655
+ ptyId?: string | undefined;
656
+ /** Include the agent's command-output firehose (sandbox.command.output.delta).
657
+ * Default true — the read-only "terminal-as-events" the data path settled on. */
658
+ includeAgentFirehose?: boolean | undefined;
1132
659
  /**
1133
- * All visible rows, with pins first. This preserves the pre-pinning hook
1134
- * contract for consumers that only read `sessions`.
660
+ * OPEN an interactive PTY against the box so the user can type, not just watch.
661
+ * When true (and the session is live) the hook calls `terminalPtyOpen` once,
662
+ * tracks the returned ptyId, exposes `write` immediately (bound to that ptyId),
663
+ * and closes the PTY on unmount. The PTY's banner + every output delta ride the
664
+ * SSE spine (`terminal.pty.*`) back into `events`, so xterm fills in. Default
665
+ * false — a caller that only wants the read-only firehose stays projection-only.
1135
666
  */
1136
- sessions: Session[];
1137
- /** The complete personal pinned section, also present in `sessions`. */
1138
- pinned: Session[];
1139
- nextCursor: string | null;
1140
- loading: boolean;
1141
- error: Error | null;
1142
- refresh: () => Promise<void>;
667
+ interactive?: boolean | undefined;
668
+ /** Lease liveness ("cold" | "warm" | "draining"). The interactive PTY is only
669
+ * opened once the box is warm — opening on a cold box (ptyCapable is advertised
670
+ * cold too) races the box and leaves a dead read-only terminal. */
671
+ liveness?: string | undefined;
1143
672
  };
1144
- /** List the workspace's sessions — the data behind fleet and manager views. */
1145
- declare function useWorkspaceSessions(options?: UseWorkspaceSessionsOptions): UseWorkspaceSessionsResult;
1146
-
1147
- type UseSessionLineageOptions = ClientOverride & {
1148
- events?: SessionEvent[] | undefined;
1149
- /** Refresh interval (ms). Off by default. */
1150
- pollIntervalMs?: number | undefined;
1151
- enabled?: boolean | undefined;
1152
- };
1153
- type UseSessionLineageResult = {
1154
- lineage: SessionLineageResponse | null;
1155
- loading: boolean;
673
+ type UseSandboxTerminalResult = {
674
+ /** Ordered, deduped output chunks to write() into xterm.js. */
675
+ chunks: TerminalChunk[];
676
+ /** Whether a PTY is currently open (drives the prompt/cursor affordance). */
677
+ running: boolean;
678
+ /**
679
+ * Interactive write fn when a PTY is open and the backend supports stdin
680
+ * (`terminal.transport === "pty-ws"` / `PtyOpenResponse.supportsInput`). Null
681
+ * in the read-only event-projection case (v1 default).
682
+ */
683
+ write: ((data: string) => void) | null;
684
+ /** The active PTY id, if one is open. */
685
+ activePtyId: string | null;
686
+ /** Close the active PTY (no-op when none is open). */
687
+ close: () => void;
688
+ /** A PTY-open failure (interactive mode), if any. */
1156
689
  error: Error | null;
1157
- refresh: () => Promise<void>;
1158
690
  };
1159
- declare function isLineageRefreshEvent(event: SessionEvent): boolean;
1160
- /** Read the ancestors + descendant tree for one session. Data-only; no UI state. */
1161
- declare function useSessionLineage(sessionId: string | null | undefined, options?: UseSessionLineageOptions): UseSessionLineageResult;
691
+ /**
692
+ * Project the Channel-A event log into an xterm-writable byte stream. The
693
+ * terminal is "terminal-as-events": there is NO new socket in v1 — the agent's
694
+ * command output (`sandbox.command.output.delta`) and any interactive PTY
695
+ * (`terminal.pty.output.delta`) ride the existing SSE spine. When a PTY is open
696
+ * and the backend accepts stdin, `write` pipes keystrokes via the SDK
697
+ * `terminalPtyWrite` (the synchronous Channel-A control path).
698
+ */
699
+ declare function useSandboxTerminal(sessionId: string | null | undefined, options: UseSandboxTerminalOptions): UseSandboxTerminalResult;
1162
700
 
1163
- type UseVariableSetsOptions = ClientOverride & {
1164
- pollIntervalMs?: number | undefined;
701
+ /** The git-status overlay a file row may carry (tints modified files in the tree). */
702
+ type FileTreeStatus = "added" | "modified" | "deleted" | "renamed" | "untracked";
703
+ type CapturedFileUnavailableReason = "not-captured" | "too-large" | "content-missing";
704
+ /** A cold capture can index a path without retaining its contents. Components
705
+ * use this typed result to offer an explicit wake action instead of silently
706
+ * turning passive review into a live sandbox read. */
707
+ declare class CapturedFileUnavailableError extends Error {
708
+ readonly path: string;
709
+ readonly reason: CapturedFileUnavailableReason;
710
+ readonly code = "captured_file_unavailable";
711
+ constructor(path: string, reason: CapturedFileUnavailableReason);
712
+ }
713
+ declare class FileWriteConflictError extends Error {
714
+ readonly path: string;
715
+ readonly expectedContent: string;
716
+ readonly liveContent: string;
717
+ readonly code = "file_write_conflict";
718
+ constructor(path: string, expectedContent: string, liveContent: string);
719
+ }
720
+ type SandboxWriteFileOptions = {
721
+ /** Exact text loaded into the editor. The live file is re-read before write;
722
+ * divergence fails closed with FileWriteConflictError. */
723
+ expectedContent?: string | undefined;
724
+ /** Deliberately bypass the expected-content guard after a visible conflict. */
725
+ force?: boolean | undefined;
726
+ };
727
+ /** A node in the Pierre file tree. `children === undefined` ⇒ an unexpanded dir
728
+ * (lazy treeMode); `children: []` ⇒ an expanded-but-empty dir. */
729
+ type FileTreeNode = {
730
+ path: string;
731
+ name: string;
732
+ kind: "file" | "dir";
733
+ children?: FileTreeNode[] | undefined;
734
+ size?: number | null | undefined;
735
+ status?: FileTreeStatus | undefined;
736
+ /** A residue dir the capture COLLAPSED (node_modules, .git, dist, …): its
737
+ * contents were never indexed. Cold, expanding it can't list children — the
738
+ * UI shows an inline "contents on machine" row until the box is warm. */
739
+ truncated?: boolean | undefined;
740
+ };
741
+ type UseSandboxFilesOptions = ClientOverride & {
742
+ /** Live event log (usually `useSessionEvents().events`) — drives auto-refresh
743
+ * on `fs.changed` / `git.changed`. */
744
+ events?: SessionEvent[] | undefined;
745
+ /** Initial path to list (workspace root by default). */
746
+ rootPath?: string | undefined;
747
+ /** Hold off the initial list (e.g. panel collapsed). Default true. */
1165
748
  enabled?: boolean | undefined;
749
+ /** The lease liveness ("cold" | "warm" | "draining"). The structured FileSystem
750
+ * capability is advertised even on a COLD box, so the mount-time list can race
751
+ * the box: it lists before the box is warm, gets an empty/errored result, and
752
+ * (with no `fs.changed` event) never re-lists. Passing liveness re-lists when
753
+ * the box first becomes warm, so the tree populates as soon as the box is up. */
754
+ liveness?: string | undefined;
755
+ /** The latest turn-end workspace capture (from `useWorkspaceCapture`). The tree
756
+ * paints immediately from this durable index, then a warm box reconciles live
757
+ * in place. If that live read fails, the capture remains a truthful read-only
758
+ * fallback instead of turning into an empty error surface. */
759
+ capture?: WorkspaceCaptureManifest | null | undefined;
760
+ /** Called when an OPTIMISTIC mutation is reverted because its background
761
+ * Channel-A op failed (e.g. a 409 rename collision). The host wires this to a
762
+ * toast — the tree silently rolls the node back, the user sees why. */
763
+ onMutationError?: ((error: Error, op: string) => void) | undefined;
1166
764
  };
1167
- type UseVariableSetsResult = {
1168
- variableSets: VariableSet[];
765
+ type UseSandboxFilesResult = {
766
+ /** The tree roots (the listed root's children). */
767
+ tree: FileTreeNode[];
768
+ /** Lazy-expand a directory node in place (lists its immediate children). */
769
+ expand: (path: string) => Promise<void>;
770
+ /** Paths whose lazy `fs.list` is currently in flight — the FileBrowser shows a
771
+ * spinner on these nodes so a 2-3s Channel-A list never looks frozen. */
772
+ expandingPaths: Set<string>;
773
+ /** Read a file for the preview pane (text or base64-for-binary, size-capped). */
774
+ readFile: (path: string, options?: OpenGeniRequestOptions) => Promise<FsReadResponse>;
775
+ /** Write a file (overwrite, last-writer-wins) — the editor save path.
776
+ * Optimistic: a brand-new file is spliced into the tree immediately and the
777
+ * Channel-A write runs in the background; on failure the splice is reverted. */
778
+ writeFile: (path: string, content: string, options?: SandboxWriteFileOptions) => Promise<FsWriteResponse>;
779
+ /** Create a new empty file (refuses to clobber an existing path: overwrite=false). */
780
+ createFile: (path: string) => Promise<void>;
781
+ /** Create a directory (recursive by default). */
782
+ createDir: (path: string) => Promise<void>;
783
+ /** Delete a path (pass recursive=true for a non-empty directory). */
784
+ deleteEntry: (path: string, recursive?: boolean) => Promise<void>;
785
+ /** Move / rename a path (rename == move). Refuses to clobber unless overwrite=true. */
786
+ moveEntry: (path: string, newPath: string, opts?: {
787
+ overwrite?: boolean;
788
+ }) => Promise<void>;
789
+ /** Re-list the whole tree from the root. */
790
+ refresh: () => Promise<void>;
791
+ /** Which source the tree is currently served from: the live box, the turn-end
792
+ * capture (cold/offline), or neither yet. M5's source badge reads this. */
793
+ source: "live" | "capture" | null;
794
+ /** When the served capture was taken (ISO), when `source === "capture"`. */
795
+ capturedAt: string | null;
1169
796
  loading: boolean;
1170
797
  error: Error | null;
1171
- refresh: () => Promise<void>;
1172
- create: (request: CreateVariableSetRequest) => Promise<VariableSet | null>;
1173
- update: (variableSetId: string, request: UpdateVariableSetRequest) => Promise<VariableSet | null>;
1174
- remove: (variableSetId: string) => Promise<boolean>;
1175
- /** Set/rotate a variable. Values are write-only — reads expose metadata only. */
1176
- setVariable: (variableSetId: string, name: string, value: string) => Promise<VariableSetVariableMetadata | null>;
1177
- deleteVariable: (variableSetId: string, name: string) => Promise<boolean>;
1178
- mutating: boolean;
1179
- mutationError: Error | null;
1180
- clearMutationError: () => void;
1181
798
  };
1182
799
  /**
1183
- * Variable sets (named, encrypted variable sets attached to sessions
1184
- * and scheduled tasks). Variable values are write-only end to end: this hook
1185
- * never sees a value after it is sent.
800
+ * Project the FileSystem service into a lazy-loaded Pierre tree. The initial
801
+ * list pulls one level (depth 1); `expand(path)` lists a directory's immediate
802
+ * children on demand (the fast lazy-tree UX). A git-status overlay tints
803
+ * modified files. Auto-refreshes when an `fs.changed` / `git.changed` event
804
+ * arrives on the live log.
1186
805
  */
1187
- declare function useVariableSets(options?: UseVariableSetsOptions): UseVariableSetsResult;
1188
- /** @deprecated use UseVariableSetsOptions */
1189
- type UseEnvironmentsOptions = UseVariableSetsOptions;
1190
- /** @deprecated use UseVariableSetsResult */
1191
- type UseEnvironmentsResult = UseVariableSetsResult & {
1192
- environments: VariableSet[];
1193
- };
1194
- /** @deprecated use useVariableSets */
1195
- declare function useEnvironments(options?: UseEnvironmentsOptions): UseEnvironmentsResult;
806
+ declare function useSandboxFiles(sessionId: string | null | undefined, options?: UseSandboxFilesOptions): UseSandboxFilesResult;
1196
807
 
1197
- type UseRigsOptions = ClientOverride & {
1198
- pollIntervalMs?: number | undefined;
808
+ type UseSandboxGitOptions = ClientOverride & {
809
+ /** Live event log (usually `useSessionEvents().events`) — drives auto-refresh
810
+ * on `git.changed`. */
811
+ events?: SessionEvent[] | undefined;
812
+ /** Repo root within the workspace (multi-repo). Default: workspace root. */
813
+ repoPath?: string | undefined;
814
+ /** All repository roots in the workspace. When supplied, status and diff are
815
+ * aggregated into one workspace-wide result and paths are workspace-relative. */
816
+ repoPaths?: readonly string[] | undefined;
817
+ /** Diff the staged index vs HEAD (`--cached`) instead of the working tree. */
818
+ staged?: boolean | undefined;
819
+ /** Hold off the initial fetch. Default true. */
1199
820
  enabled?: boolean | undefined;
821
+ /** Lease liveness ("warm" | "draining" | "cold"). When NOT warm, the diff is
822
+ * served from the capture (cold/offline) instead of a live `gitDiff` RPC. */
823
+ liveness?: string | undefined;
824
+ /** The latest turn-end capture (from `useWorkspaceCapture`). Seeds the diff
825
+ * immediately; a warm box reconciles live and leaves this durable review
826
+ * surface in place if the live request is temporarily unavailable. */
827
+ capture?: WorkspaceCaptureManifest | null | undefined;
1200
828
  };
1201
- type UseRigsResult = {
1202
- rigs: Rig[];
829
+ /** A Git diff qualified into workspace scope. `repoRoot` is present for the
830
+ * workspace-wide path and lets review UI group real repositories without
831
+ * guessing from the first path segment. */
832
+ type SandboxGitFileDiff = GitFileDiff & {
833
+ repoRoot?: string | undefined;
834
+ };
835
+ type UseSandboxGitResult = {
836
+ /** Working-tree (or staged) diff vs HEAD — the structured hunks the Pierre
837
+ * diff view renders. */
838
+ diff: SandboxGitFileDiff[];
839
+ branch: string | null;
840
+ /** Whether a repo is actually mounted (drives "no repository" vs "no changes"). */
841
+ isRepo: boolean;
842
+ ahead: number;
843
+ behind: number;
844
+ /** Number and workspace-relative roots of repositories represented here. */
845
+ repoCount: number;
846
+ repoRoots: string[];
847
+ refresh: () => Promise<void>;
848
+ /** Which source the diff is served from: the live box or the turn-end capture. */
849
+ source: "live" | "capture" | null;
850
+ /** When the served capture was taken (ISO), when `source === "capture"`. */
851
+ capturedAt: string | null;
1203
852
  loading: boolean;
1204
853
  error: Error | null;
1205
- refresh: () => Promise<void>;
1206
- create: (request: CreateRigRequest) => Promise<Rig | null>;
1207
- update: (rigId: string, request: UpdateRigRequest) => Promise<Rig | null>;
1208
- remove: (rigId: string) => Promise<boolean>;
1209
- listVersions: (rigId: string) => Promise<RigVersion[] | null>;
1210
- activateVersion: (rigId: string, versionId: string) => Promise<RigVersion | null>;
1211
- listChanges: (rigId: string) => Promise<RigChange[] | null>;
1212
- proposeChange: (rigId: string, request: ProposeRigChangeRequest) => Promise<RigChange | null>;
1213
- mutating: boolean;
1214
- mutationError: Error | null;
1215
- clearMutationError: () => void;
1216
854
  };
1217
855
  /**
1218
- * Rigs (workspace-scoped, versioned sandbox machine definitions). The list
1219
- * polls; version/change reads are on-demand (they are per-rig detail, not part
1220
- * of the list surface).
856
+ * Project the Git service into the Pierre diff data contract: structured
857
+ * `GitFileDiff[]` (per-file hunks with per-line old/new numbers, rename
858
+ * detection, binary flag, add/del counts) plus branch + ahead/behind. When the
859
+ * box is warm the `git diff` runs in-box (API-direct); when it is cold/offline the
860
+ * diff is served from the turn-end capture instead. Refreshes on
861
+ * `git.changed`; reconciles live in place on the cold→warm transition.
1221
862
  */
1222
- declare function useRigs(options?: UseRigsOptions): UseRigsResult;
1223
- type UseRigOptions = ClientOverride & {
1224
- pollIntervalMs?: number | undefined;
863
+ declare function useSandboxGit(sessionId: string | null | undefined, options?: UseSandboxGitOptions): UseSandboxGitResult;
864
+
865
+ type UseWorkspaceCaptureOptions = ClientOverride & {
866
+ /** Live event log (usually `useSessionEvents().events`) — a
867
+ * `workspace.revision.captured` for a NEWER revision refreshes the manifest. */
868
+ events?: SessionEvent[] | undefined;
869
+ /** Hold off the mount fetch (e.g. the workbench panel is collapsed). Default true. */
1225
870
  enabled?: boolean | undefined;
1226
871
  };
1227
- type UseRigResult = {
1228
- rig: Rig | null;
872
+ type UseWorkspaceCaptureResult = {
873
+ /** The resolved manifest (tree index + per-repo diffs + file refs), or null when
874
+ * no capture exists (falls back to the live/wake path — status quo). */
875
+ capture: WorkspaceCaptureManifest | null;
876
+ /** The loaded capture's monotonic revision, or null when unavailable. */
877
+ revision: number | null;
878
+ /** When the loaded capture was taken (ISO), for the "as of <time>" source badge. */
879
+ capturedAt: string | null;
880
+ /** Whether a capture is available at all (the `{available:false}` discriminator). */
881
+ available: boolean;
882
+ /** Why the newest durable revision is unavailable. null means no capture has
883
+ * been attempted yet; a value means capture explicitly failed closed and
884
+ * consumers must use the live box rather than trust an incomplete snapshot. */
885
+ degradedReason: WorkspaceCaptureDegradedReason | null;
886
+ /** The changed-file count from the capture's stats, resolved on the FIRST GET
887
+ * (from the response's top-level `stats`, before any manifest-URL hop). null
888
+ * until that first resolve; 0 when no capture exists. This is the pre-paint
889
+ * "changes exist?" signal the dock uses to pick its default tab with no
890
+ * embedder events-at-mount contract. */
891
+ fileCount: number | null;
892
+ /** A newer revision has been ANNOUNCED than the one currently loaded (a refresh
893
+ * is in flight or pending). M5's source badge can show a subtle "updating…". */
894
+ isStale: boolean;
1229
895
  loading: boolean;
1230
896
  error: Error | null;
897
+ /** Force a re-fetch of the latest capture. */
1231
898
  refresh: () => Promise<void>;
1232
- update: (request: UpdateRigRequest) => Promise<Rig | null>;
1233
- remove: () => Promise<boolean>;
1234
- activateVersion: (versionId: string) => Promise<RigVersion | null>;
1235
- proposeChange: (request: ProposeRigChangeRequest) => Promise<RigChange | null>;
1236
- /** Re-run verification for a change (asynchronous — poll for the outcome). */
1237
- verifyChange: (changeId: string) => Promise<RigChange | null>;
1238
- /** Promote a verified definition_edit into a new active version (rigs:manage). */
1239
- promoteChange: (changeId: string) => Promise<RigVersion | null>;
1240
- /** Re-verify the active version's checks in a clean throwaway sandbox. */
1241
- verify: () => Promise<{
1242
- ok: boolean;
1243
- versionId: string;
1244
- } | null>;
1245
- mutating: boolean;
1246
- mutationError: Error | null;
1247
- clearMutationError: () => void;
1248
899
  };
1249
900
  /**
1250
- * A single rig (its active version + counts), polled so verification and
1251
- * promotion state stay live. Owns the rig's write actions; the versions and
1252
- * changes lists are separate polled reads (`useRigVersions`/`useRigChanges`).
901
+ * The cold-paint data source: fetch the latest turn-end workspace capture with a
902
+ * SINGLE api round-trip on mount (no machine, no Channel-A — this is the <200ms
903
+ * first paint). The manifest is served inline in the common
904
+ * case; a rare >2MB manifest comes back as a short-TTL signed URL we follow.
905
+ *
906
+ * Subscribes to the live event log: a `workspace.revision.captured` for a revision
907
+ * newer than the one loaded triggers a background refresh (stale-while-revalidate).
908
+ * `{available:false}` is a value, never a crash — consumers fall back to the
909
+ * live/wake path exactly as before the capture feature existed.
1253
910
  */
1254
- declare function useRig(rigId: string, options?: UseRigOptions): UseRigResult;
1255
- type UseRigVersionsOptions = ClientOverride & {
1256
- pollIntervalMs?: number | undefined;
1257
- enabled?: boolean | undefined;
1258
- };
1259
- type UseRigVersionsResult = {
1260
- versions: RigVersion[];
1261
- loading: boolean;
1262
- error: Error | null;
1263
- refresh: () => Promise<void>;
911
+ declare function useWorkspaceCapture(sessionId: string | null | undefined, options?: UseWorkspaceCaptureOptions): UseWorkspaceCaptureResult;
912
+
913
+ /**
914
+ * The wake-on-edit state machine. This hook owns the logic and the UI renders
915
+ * its result. States are explicit and exhaustive:
916
+ *
917
+ * viewing-cold — cold snapshot open, no local edits (editable, cloud).
918
+ * buffering — local edits buffered; the box is not warm and no warm is
919
+ * yet in flight (the host has not attached).
920
+ * warming — edits buffered; the box is actively coming up (the host
921
+ * attached a viewer in response to `wantsWarm`).
922
+ * flushing — the box is warm; the guarded write is in flight.
923
+ * flushed — the buffer was written to the live box.
924
+ * conflict — the live file diverged from the captured base between
925
+ * capture and flush; NO write was issued (C2).
926
+ * readonly-offline — a self-hosted machine that is offline; read-only, no wake.
927
+ */
928
+ type WorkspaceEditState = "viewing-cold" | "buffering" | "warming" | "flushing" | "flushed" | "conflict" | "readonly-offline";
929
+ type WorkspaceEditConflict = {
930
+ path: string;
931
+ /** The capture-served content the edit was based on. */
932
+ base: string;
933
+ /** The live content found on the box at flush time (what would be overwritten). */
934
+ live: string;
1264
935
  };
1265
- /** A rig's append-only version history, newest-first (polled). */
1266
- declare function useRigVersions(rigId: string, options?: UseRigVersionsOptions): UseRigVersionsResult;
1267
- type UseRigChangesOptions = ClientOverride & {
1268
- pollIntervalMs?: number | undefined;
1269
- enabled?: boolean | undefined;
936
+ type UseWorkspaceEditOptions = ClientOverride & {
937
+ /** The file being edited (workspace-relative). null disables the machine. */
938
+ path?: string | null | undefined;
939
+ /** The capture-served content loaded into the editor — the flush base (C2). */
940
+ baseContent?: string | null | undefined;
941
+ /** `capabilities.liveness` — "warm" | "draining" | "cold". Drives the flush. */
942
+ liveness?: string | undefined;
943
+ /** True while the host is actively warming the box (attach in flight, not warm
944
+ * yet) — lifts `buffering` → `warming`. The host sets this after acting on
945
+ * `wantsWarm` (e.g. flipping `attachFiles` on `useSessionCapabilities`). */
946
+ warming?: boolean | undefined;
947
+ /** A self-hosted machine that is OFFLINE: read-only, no remote wake possible. */
948
+ offline?: boolean | undefined;
949
+ /** Called once when the first cold edit needs the box warmed — the host wires
950
+ * this to its liveness primitive (flip `attachFiles`/`attachViewer`). */
951
+ onWarmRequested?: (() => void) | undefined;
1270
952
  };
1271
- type UseRigChangesResult = {
1272
- changes: RigChange[];
1273
- loading: boolean;
953
+ type UseWorkspaceEditResult = {
954
+ state: WorkspaceEditState;
955
+ /** The editor should be read-only (self-hosted offline). */
956
+ readOnly: boolean;
957
+ /** The current buffered content (null = no local edit yet). */
958
+ buffer: string | null;
959
+ /** The host should warm the box (flip its attach primitive). Latches true from
960
+ * the first cold edit until the buffer flushes or is discarded. */
961
+ wantsWarm: boolean;
962
+ /** Record an edit from the editor (idempotent per identical content). */
963
+ edit: (content: string) => void;
964
+ /** The conflict detail when `state === "conflict"`. */
965
+ conflict: WorkspaceEditConflict | null;
966
+ /** Flush the buffer regardless of the base guard (the user chose "overwrite"
967
+ * from the conflict bar). Writes last-writer-wins. */
968
+ overwrite: () => Promise<void>;
969
+ /** Drop the local buffer and return to viewing the snapshot. */
970
+ discard: () => void;
1274
971
  error: Error | null;
1275
- refresh: () => Promise<void>;
1276
972
  };
1277
973
  /**
1278
- * A rig's change queue (polled). The default poll cadence lets a change move
1279
- * through verifying merged/rejected without a manual refresh.
974
+ * Cold-editing without a spinner: the editor opens instantly from the capture,
975
+ * the first keystroke buffers locally AND signals the host to warm the box, and on
976
+ * warm the buffer flushes IF the live file still matches the captured base — else a
977
+ * non-blocking conflict is surfaced and nothing is overwritten.
978
+ *
979
+ * The base guard compares the live content at flush time against `baseContent`
980
+ * (the exact bytes the editor loaded). This is equivalent to the sha256 compare
981
+ * the design specifies — M1 leaves `baseHash` null, so the loaded content IS the
982
+ * authoritative base — and avoids async Web Crypto in the render path.
1280
983
  */
1281
- declare function useRigChanges(rigId: string, options?: UseRigChangesOptions): UseRigChangesResult;
984
+ declare function useWorkspaceEdit(sessionId: string | null | undefined, options?: UseWorkspaceEditOptions): UseWorkspaceEditResult;
1282
985
 
1283
- type UsePacksOptions = ClientOverride & {
1284
- pollIntervalMs?: number | undefined;
1285
- enabled?: boolean | undefined;
986
+ /** The one truthful machine indicator. Honest staleness beats
987
+ * fake liveness: a cold/asleep box reads "offline — as of <time>", never "live". */
988
+ type MachineChipState = "live" | "waking" | "offline";
989
+ type MachineChip = {
990
+ state: MachineChipState;
991
+ /** A ready-to-render label ("Live" / "Waking…" / "Offline — as of 3m ago"). */
992
+ label: string;
993
+ /** The capture time backing an offline/stale label (ISO), or null. M4 may
994
+ * reformat this; `label` already embeds a relative form. */
995
+ asOf: string | null;
1286
996
  };
1287
- type UsePacksResult = {
1288
- /** Built-in + registered packs available to the workspace. */
1289
- packs: CapabilityPack[];
1290
- /** Enable/disable state per pack. */
1291
- installations: PackInstallation[];
1292
- /** The installation for a pack id, if any. */
1293
- installationFor: (packId: string) => PackInstallation | null;
1294
- loading: boolean;
1295
- error: Error | null;
1296
- refresh: () => Promise<void>;
1297
- /** Register (or replace) a workspace-scoped pack manifest. */
1298
- register: (manifest: RegisterCapabilityPackRequest) => Promise<WorkspaceRegisteredPack | null>;
1299
- enable: (packId: string, request?: EnablePackRequest) => Promise<PackInstallation | null>;
1300
- /** Unregister a workspace-scoped pack (built-ins cannot be removed). */
1301
- remove: (packId: string) => Promise<boolean>;
1302
- mutating: boolean;
1303
- mutationError: Error | null;
1304
- clearMutationError: () => void;
997
+ type DeriveMachineChipInput = {
998
+ /** `capabilities.liveness` "warm" | "draining" | "cold" (or null pre-negotiation). */
999
+ liveness?: string | null | undefined;
1000
+ /** `useSessionCapabilities().state` drives the "waking" (negotiating) read. */
1001
+ capabilitiesState?: SessionCapabilitiesState | null | undefined;
1002
+ /** The ACTIVE machine's connection state (from `useMachines`), when known. A
1003
+ * self-hosted machine that is `offline` cannot be remotely woken. */
1004
+ activeMachineState?: MachineState | null | undefined;
1005
+ /** Whether the active sandbox is a user-owned self-hosted machine (no remote wake). */
1006
+ activeIsSelfhosted?: boolean | undefined;
1007
+ /** An interaction (wake-on-edit, terminal focus) is actively warming the box. */
1008
+ wantsWarm?: boolean | undefined;
1009
+ /** The latest capture's `capturedAt` (ISO) the "as of <time>" backing. */
1010
+ capturedAt?: string | null | undefined;
1011
+ /** Injectable clock for deterministic relative labels (defaults to Date.now). */
1012
+ now?: number | undefined;
1305
1013
  };
1306
- /** Capability packs: catalog + installations + register/enable/unregister. */
1307
- declare function usePacks(options?: UsePacksOptions): UsePacksResult;
1014
+ /** Compact relative-time for the "as of" label. Pure, no Intl dependency. */
1015
+ declare function formatAsOf(capturedAt: string, now: number): string;
1016
+ /**
1017
+ * Derive the machine-state chip from the live capability/liveness surface, the
1018
+ * active machine's connection state, and the latest capture time. PURE (M4 renders
1019
+ * it; the whole point is a deterministic, testable projection).
1020
+ *
1021
+ * Precedence:
1022
+ * 1. warm/draining lease → **live** (a warm box always wins).
1023
+ * 2. actively warming (negotiating, wake-on-edit, or a reconnecting/enrolling
1024
+ * machine) → **waking**.
1025
+ * 3. everything else (a cold/asleep box, a self-hosted machine that's offline,
1026
+ * an error) → **offline**, labelled "as of <capture time>".
1027
+ */
1028
+ declare function deriveMachineChip(input: DeriveMachineChipInput): MachineChip;
1029
+ type UseMachineChipOptions = DeriveMachineChipInput;
1030
+ /**
1031
+ * Thin memoized wrapper over `deriveMachineChip` for the dock header (M4). The
1032
+ * dock already runs `useSessionCapabilities` + `useMachines` + `useWorkspace
1033
+ * capture`; it feeds their `liveness` / `state` / active-machine state /
1034
+ * `capturedAt` here. Pass a periodically-updated `now` if you want the relative
1035
+ * "as of" label to tick.
1036
+ */
1037
+ declare function useMachineChip(input: UseMachineChipOptions): MachineChip;
1308
1038
 
1309
- type UseWorkspacesOptions = Pick<ClientOverride, "client"> & {
1310
- pollIntervalMs?: number | undefined;
1311
- enabled?: boolean | undefined;
1312
- };
1313
- type UseWorkspacesResult = {
1314
- workspaces: Workspace[];
1315
- loading: boolean;
1316
- error: Error | null;
1317
- refresh: () => Promise<void>;
1318
- create: (request: CreateWorkspaceRequest) => Promise<Workspace | null>;
1319
- update: (workspaceId: string, request: UpdateWorkspaceRequest) => Promise<Workspace | null>;
1320
- mutating: boolean;
1321
- mutationError: Error | null;
1322
- clearMutationError: () => void;
1039
+ type ApprovalSurfaceMessages = {
1040
+ title: string;
1041
+ description: string;
1042
+ approve: string;
1043
+ reject: string;
1044
+ approving: string;
1045
+ rejecting: string;
1046
+ formatToolName: (name: string) => string;
1047
+ };
1048
+ declare const defaultApprovalSurfaceMessages: ApprovalSurfaceMessages;
1049
+ type ApprovalSurfaceProps = {
1050
+ approvals: PendingApproval[];
1051
+ onApprove: (approval: PendingApproval) => void | Promise<void>;
1052
+ onReject: (approval: PendingApproval) => void | Promise<void>;
1053
+ responding?: boolean | undefined;
1054
+ error?: string | Error | null | undefined;
1055
+ messages?: Partial<ApprovalSurfaceMessages> | undefined;
1056
+ renderApproval?: ((approval: PendingApproval) => ReactNode) | undefined;
1057
+ className?: string | undefined;
1323
1058
  };
1324
1059
  /**
1325
- * The caller's workspaces (workspace switchers, onboarding). Not scoped to
1326
- * the provider's workspace, so it only needs the client.
1060
+ * Host-neutral approval presentation backed by the native pending-approval
1061
+ * projection and control callbacks. It owns only presentation and duplicate
1062
+ * click fencing; OpenGeni remains the authority for approval state.
1327
1063
  */
1328
- declare function useWorkspaces(options?: UseWorkspacesOptions): UseWorkspacesResult;
1064
+ declare function ApprovalSurface({ approvals, onApprove, onReject, responding, error, messages: overrides, renderApproval, className, }: ApprovalSurfaceProps): react_jsx_runtime.JSX.Element | null;
1329
1065
 
1330
- type UseBillingUsageOptions = Pick<ClientOverride, "client"> & {
1331
- /** Account to read. Defaults to the caller's default account server-side. */
1332
- accountId?: string | undefined;
1333
- /** Filter usage to one workspace. */
1334
- workspaceId?: string | undefined;
1335
- /** Refresh interval (ms) for live billing meters. Off by default. */
1336
- pollIntervalMs?: number | undefined;
1337
- enabled?: boolean | undefined;
1066
+ type HumanInputAnswerDraft = {
1067
+ values: string[];
1068
+ other: string;
1069
+ otherSelected: boolean;
1338
1070
  };
1339
- type UseBillingUsageResult = {
1340
- /** Prepaid credit balance (micro-USD), null until loaded. */
1341
- balance: BillingBalance | null;
1342
- /** Recent usage events (runs, tokens, cost, uploads, ...). */
1343
- usage: UsageEvent[];
1344
- loading: boolean;
1345
- error: Error | null;
1346
- refresh: () => Promise<void>;
1071
+ type HumanInputFormMessages = {
1072
+ title: string;
1073
+ description: string;
1074
+ submit: string;
1075
+ skip: string;
1076
+ submitting: string;
1077
+ other: string;
1078
+ deadlineLabel: string;
1079
+ formatDeadline: (value: string) => string;
1080
+ required: string;
1081
+ minLength: (count: number) => string;
1082
+ maxLength: (count: number) => string;
1083
+ otherRequired: string;
1084
+ minSelections: (count: number) => string;
1085
+ maxSelections: (count: number) => string;
1086
+ };
1087
+ declare const defaultHumanInputFormMessages: HumanInputFormMessages;
1088
+ type HumanInputFormProps = {
1089
+ request: Pick<SessionHumanInputRequest, "id" | "questions" | "allowSkip" | "expiresAt">;
1090
+ onSubmit: (response: SubmitHumanInputResponseRequest) => void | Promise<void>;
1091
+ submitting?: boolean | undefined;
1092
+ error?: string | null | undefined;
1093
+ title?: ReactNode;
1094
+ description?: ReactNode;
1095
+ submitLabel?: string | undefined;
1096
+ skipLabel?: string | undefined;
1097
+ messages?: Partial<HumanInputFormMessages> | undefined;
1098
+ autoFocus?: boolean | undefined;
1099
+ className?: string | undefined;
1347
1100
  };
1348
1101
  /**
1349
- * Account billing usage: credit balance + recent usage events the data
1350
- * behind per-call billing meters. Account-scoped, so it only needs the
1351
- * client; pass `workspaceId` to narrow usage to one workspace.
1102
+ * Styled but host-neutral renderer for one structured request. Hosts can use
1103
+ * the headless `useHumanInputRequests` hook instead, or replace the title and
1104
+ * description while retaining accessible field semantics and validation.
1352
1105
  */
1353
- declare function useBillingUsage(options?: UseBillingUsageOptions): UseBillingUsageResult;
1106
+ declare function HumanInputForm({ request, onSubmit, submitting, error, title, description, submitLabel, skipLabel, messages: messageOverrides, autoFocus, className, }: HumanInputFormProps): react_jsx_runtime.JSX.Element;
1107
+ declare function answersFromDrafts(questions: HumanInputQuestion[], drafts: Record<string, HumanInputAnswerDraft>, messageOverrides?: Partial<HumanInputFormMessages>): {
1108
+ answers: HumanInputAnswer[];
1109
+ errors: Record<string, string>;
1110
+ };
1354
1111
 
1355
- type UseAvailableModelsOptions = Pick<ClientOverride, "client"> & {
1356
- /** Refresh interval (ms). Off by default — the host model list rarely moves. */
1357
- pollIntervalMs?: number | undefined;
1358
- enabled?: boolean | undefined;
1112
+ type ToolRendererProps = {
1113
+ item: ToolCallItem;
1359
1114
  };
1360
- type UseAvailableModelsResult = {
1361
- /** The provider-grouped models the host exposes (empty until loaded). */
1362
- models: ClientModel[];
1363
- /** The deployment's default model id, null until loaded. */
1364
- defaultModel: string | null;
1365
- loading: boolean;
1366
- error: Error | null;
1367
- refresh: () => Promise<void>;
1115
+ type ToolRenderer = ComponentType<ToolRendererProps>;
1116
+ /** A registry entry: which key it matches and the component that renders it. */
1117
+ type ToolRegistryEntry = {
1118
+ match: "rawType";
1119
+ type: string;
1120
+ render: ToolRenderer;
1121
+ } | {
1122
+ match: "name";
1123
+ name: string;
1124
+ render: ToolRenderer;
1125
+ };
1126
+ type ToolRegistry = {
1127
+ /** Resolve the renderer for a call (never null — falls back to generic). */
1128
+ resolve: (item: ToolCallItem) => ToolRenderer;
1129
+ /** The generic fallback renderer. */
1130
+ fallback: ToolRenderer;
1131
+ };
1132
+ type CreateToolRegistryOptions = {
1133
+ /**
1134
+ * Entries that take precedence over the built-ins. Earlier entries win, so a
1135
+ * consumer can shadow a default renderer for the same key.
1136
+ */
1137
+ entries?: ToolRegistryEntry[] | undefined;
1138
+ /** Replace the generic fallback used for unmatched tools. */
1139
+ fallback?: ToolRenderer | undefined;
1368
1140
  };
1141
+ /** The `raw.type` of a projected tool call, when the provider item carries one. */
1142
+ declare function rawTypeOf(item: ToolCallItem): string | null;
1369
1143
  /**
1370
- * The host-exposed model list for a <ModelPicker>: fetches the deployment's
1371
- * public client config (`GET /v1/config/client`) and surfaces the richer
1372
- * provider-grouped `models` plus the `defaultModel` the picker should preselect.
1373
- * Deployment-scoped, so it only needs the client (no workspace).
1144
+ * Build a tool registry from a set of entries and a fallback. The returned
1145
+ * registry resolves in priority order: `raw.type` entries first, then `name`
1146
+ * entries, then the fallback. Consumer `entries` are consulted before the
1147
+ * built-in `baseEntries`, so they shadow defaults cleanly.
1374
1148
  */
1375
- declare function useAvailableModels(options?: UseAvailableModelsOptions): UseAvailableModelsResult;
1149
+ declare function createToolRegistry(baseEntries: ToolRegistryEntry[], baseFallback: ToolRenderer, options?: CreateToolRegistryOptions): ToolRegistry;
1376
1150
 
1377
- type SessionCapabilitiesState = "idle" | "negotiating" | "ready" | "cold" | "on-demand" | "error";
1378
- type UseSessionCapabilitiesOptions = ClientOverride & {
1151
+ /** The built-in tool renderer registry: every first-party tool plus a fallback. */
1152
+ declare const defaultToolRegistry: ToolRegistry;
1153
+ /** Build a registry that extends the built-ins with consumer entries/fallback. */
1154
+ declare function createDefaultToolRegistry(options?: Parameters<typeof createToolRegistry>[2]): ToolRegistry;
1155
+
1156
+ type ActivityRailProps = {
1157
+ items: ActivityItem[];
1158
+ /** Renderer registry for tool calls. Defaults to {@link defaultToolRegistry}. */
1159
+ toolRegistry?: ToolRegistry | undefined;
1160
+ /** Drill into a spawned worker session. */
1161
+ onOpenSession?: ((sessionId: string) => void) | undefined;
1379
1162
  /**
1380
- * Live event log to fold `stream.url.rotated` from (usually
1381
- * `useSessionEvents().events`). When present the desktop socket stays fresh on
1382
- * a box rollover without a round-trip; stale-epoch rotations are dropped.
1163
+ * Deep-link a memory row to its record in the host's memory pane. Opt-in: the
1164
+ * library draws no "View in memory" affordance without a handler (the memory
1165
+ * row is then non-interactive rich content). See {@link MessageTimelineProps}.
1383
1166
  */
1384
- events?: SessionEvent[] | undefined;
1167
+ onMemoryClick?: ((memoryId: string) => void) | undefined;
1168
+ /** Drop the left rule + indent (used inside a folded turn summary). */
1169
+ bare?: boolean | undefined;
1170
+ className?: string | undefined;
1171
+ };
1172
+ declare function ActivityRail({ items, toolRegistry, onOpenSession, onMemoryClick, bare, className, }: ActivityRailProps): react_jsx_runtime.JSX.Element;
1173
+
1174
+ /**
1175
+ * A subtle settle signal — see the CHIP DOCTRINE above. The closed tone set.
1176
+ * `"interrupted"` is a calm neutral tone for cancelled items — no dot, same
1177
+ * quiet weight as `"muted"`, but semantically distinct from metadata.
1178
+ */
1179
+ type DisclosureChip = {
1180
+ tone: "ok" | "bad" | "muted" | "interrupted";
1181
+ text: string;
1182
+ };
1183
+ type ActivityDisclosureProps = {
1184
+ icon: ReactNode;
1185
+ /** Icon tint. Defaults to the muted foreground; renderers pass accent/failed. */
1186
+ iconTone?: "accent" | "failed" | "running" | "muted" | undefined;
1187
+ title: ReactNode;
1188
+ /** Render the title in the mono face (commands, paths). */
1189
+ titleMono?: boolean | undefined;
1190
+ /** Shimmer the title while the tool is in-flight. */
1191
+ running?: boolean | undefined;
1385
1192
  /**
1386
- * Whether to acquire a viewer holder for the desktop pixel plane. Requires the
1387
- * un-redacted acknowledgment to have been recorded (else the attach 409s and
1388
- * the hook surfaces the consent requirement). Default false: read-only
1389
- * negotiation (no holder, no warm) — terminal/files/git work without it.
1193
+ * Quiet single-line secondary text (truncated). It is detail-on-demand: hidden
1194
+ * when a media preview is set, AND hidden once the row is expanded (the body
1195
+ * then owns the detail), so a stat/path never appears twice at once.
1390
1196
  */
1391
- attachDesktop?: boolean | undefined;
1197
+ preview?: ReactNode | undefined;
1198
+ /** A small inline media preview (a screenshot thumbnail) shown in place of `preview`. */
1199
+ media?: ReactNode | undefined;
1200
+ /** At most one quiet settle chip, right-aligned to the gutter. */
1201
+ chip?: DisclosureChip | undefined;
1392
1202
  /**
1393
- * Whether to acquire a viewer holder to warm the box for the REAL interactive
1394
- * terminal (the ttyd pty-ws plane). Symmetric with `attachDesktop` and shares
1395
- * the SAME viewer attach (one warm box serves both planes), but needs NO
1396
- * un-redacted acknowledgment a shell is interactive by nature, and the gate
1397
- * is the scoped tunnel URL + stream token. Default false: the terminal stays on
1398
- * the read-only Channel-A firehose until the user opens/focuses it. The attach
1399
- * folds the minted `pty-ws` url+token into the `Terminal` cell.
1203
+ * When true the row carries the standard failure affordance: the icon is tinted
1204
+ * red and a "failed" bad-chip appears in the right gutter (unless an explicit
1205
+ * `chip` is already supplied the caller's chip wins). Output is still visible
1206
+ * on expand; this is a quiet status signal, not a blocking banner.
1207
+ *
1208
+ * Renderers should pass `failed={item.status === "failed"}` on their settled
1209
+ * (non-running) paths so any tool with a failed status shows a consistent
1210
+ * affordance without each renderer having to duplicate the logic.
1400
1211
  */
1401
- attachTerminal?: boolean | undefined;
1212
+ failed?: boolean | undefined;
1402
1213
  /**
1403
- * Whether to acquire a viewer holder to warm the box for a FILE WRITE the
1404
- * wake-on-edit INTENT (a first keystroke in the editor), NOT passive browsing.
1405
- * Shares the SAME viewer attach as the desktop/terminal (one warm box, one
1406
- * holder) and like the terminal needs NO un-redacted acknowledgment:
1407
- * reading/writing files is the ordinary Channel-A control plane, not the pixel
1408
- * plane. It folds NO live URL (files ride the stateless HTTP plane) — it only
1409
- * refcounts liveness. Unlike desktop/terminal it warms even a COLD box: an edit
1410
- * legitimately cold-creates (that IS the wake), so the write lands ~100ms warm
1411
- * instead of paying the ~5s cold resume. Default false. IMPORTANT: merely
1412
- * opening/reading the Files tab must NOT set this — browsing capture-served
1413
- * trees/diffs needs no box, and warming one on a cold glance burns box-hours to
1414
- * serve reads the capture already answers for free.
1214
+ * When true the row carries a calm "interrupted" affordance: the icon stays
1215
+ * muted (no red) and a quiet "interrupted" chip appears in the right gutter
1216
+ * (unless an explicit `chip` is already supplied the caller's chip wins).
1217
+ * This is the cancelled-status analogue of `failed`, but deliberately calm
1218
+ * and neutral — it is NOT an error; the user chose to stop.
1219
+ *
1220
+ * Renderers should pass `cancelled={item.status === "cancelled"}` so any
1221
+ * in-flight item that was interrupted on turn.cancelled reads consistently.
1222
+ * `cancelled` is ignored when `failed` is also true (failure takes precedence).
1415
1223
  */
1416
- attachFiles?: boolean | undefined;
1417
- /** Hold off negotiating (e.g. the workbench panel is collapsed). Default true. */
1418
- enabled?: boolean | undefined;
1419
- /** Poll cadence (ms) while the lease is cold/warming. Default 1500. */
1420
- warmingPollMs?: number | undefined;
1224
+ cancelled?: boolean | undefined;
1225
+ /** When false the row is a static line (no expand affordance). */
1226
+ expandable?: boolean | undefined;
1227
+ children?: ReactNode | undefined;
1228
+ };
1229
+ /**
1230
+ * The one disclosure row shape every activity row reuses (tool calls, reasoning,
1231
+ * sandbox ops): a chevron, a tinted icon, a title, an optional muted preview or
1232
+ * inline media, and at most one right-gutter settle chip. Compact by default;
1233
+ * the body mounts only when expanded.
1234
+ */
1235
+ declare function ActivityDisclosure({ icon, iconTone: iconToneProp, title, titleMono, running, preview, media, chip: chipProp, failed, cancelled, expandable, children, }: ActivityDisclosureProps): react_jsx_runtime.JSX.Element;
1236
+ declare function TermBlock({ command, workdir, output, live, tailLines, failed, }: {
1421
1237
  /**
1422
- * Give up waiting for `warm` after this long while polling (ms) and surface a
1423
- * stalled error with a manual `renegotiate`. Default 30000. This is a UI
1424
- * patience deadline, independent from the worker's sandbox warming timeout.
1425
- * 0 disables the deadline.
1238
+ * The command shown in the prompt header. Pass `null` when the row title
1239
+ * already carries it (e.g. an exec row titled `$ cmd`): the header then drops
1240
+ * the command and the whole prompt line if there is no workdir either — so
1241
+ * the command never reads twice, stacked, above the output.
1426
1242
  */
1427
- warmingDeadlineMs?: number | undefined;
1428
- };
1429
- type UseSessionCapabilitiesResult = {
1430
- /** The negotiated capability doc — the single source of UI truth. */
1431
- capabilities: SessionCapabilities | null;
1432
- state: SessionCapabilitiesState;
1433
- error: Error | null;
1243
+ command: string | null;
1244
+ workdir?: string | null | undefined;
1245
+ /** The FULL output. TermBlock owns the tail/full slicing internally. */
1246
+ output: string;
1247
+ live?: boolean | undefined;
1248
+ /** A non-zero exit / failed call — tints the left accent red (the one hue). */
1249
+ failed?: boolean | undefined;
1434
1250
  /**
1435
- * 409 from the desktop attach: the un-redacted (or shared) plane needs explicit
1436
- * acknowledgment before a viewer holder is granted. Drives the consent prompt.
1251
+ * When the output exceeds the tail window, only the last `tailLines` are shown
1252
+ * with a "show full output" toggle. The component holds the full text, so the
1253
+ * toggle reveals the rest (never a dead affordance). Defaults to 12.
1437
1254
  */
1438
- acknowledgmentRequired: "unredacted" | "shared" | null;
1439
- /** 429 from the desktop attach: the per-session viewer cap is reached. */
1440
- viewerCapReached: boolean;
1441
- /** The viewer holder id minted on a desktop attach (for detach/heartbeat). */
1442
- viewerId: string | null;
1443
- /** Force a re-negotiation (after acknowledging, a resolution change, etc.). */
1444
- renegotiate: () => void;
1445
- };
1446
- /**
1447
- * The capability-negotiation hook. Discovers what THIS session+backend+OS
1448
- * supports (FileSystem/Terminal/Git always-ish; DesktopStream/Recording
1449
- * sometimes), drives capability-gated rendering, and — when `attachDesktop` —
1450
- * holds a viewer lease + heartbeats it so the box stays warm while watched.
1451
- *
1452
- * Degradation is a value, never a crash: an unsupported surface comes back
1453
- * `available:false`/`transport:null` + a `reason`; the components render the
1454
- * reason-aware empty state. 409 (consent) and 429 (viewer cap) are surfaced as
1455
- * typed signals, not thrown.
1456
- */
1457
- declare function useSessionCapabilities(sessionId: string | null | undefined, options?: UseSessionCapabilitiesOptions): UseSessionCapabilitiesResult;
1458
-
1255
+ tailLines?: number | undefined;
1256
+ }): react_jsx_runtime.JSX.Element;
1257
+ declare function PayloadBlock({ label, value, failed, }: {
1258
+ label: string;
1259
+ value: unknown;
1260
+ failed?: boolean | undefined;
1261
+ }): react_jsx_runtime.JSX.Element | null;
1262
+ /** A quiet inline note inside an expanded body (lost output, empty frame, …). */
1263
+ declare function BodyNote({ children, tone, }: {
1264
+ children: ReactNode;
1265
+ tone?: "error" | "muted" | undefined;
1266
+ }): react_jsx_runtime.JSX.Element;
1459
1267
  /**
1460
- * The minimal WebSocket surface the frame renderer drives. Lets tests (and a
1461
- * future transport swap) inject a fake without a live socketthe exact
1462
- * analogue of `DesktopRfbFactory` for the noVNC path. Structurally a subset of
1463
- * the browser `WebSocket`.
1268
+ * A loading screenshot placeholder. A faint camera glyph over a shimmering box,
1269
+ * so a still frame of the running state reads unambiguously as "capturing" not
1270
+ * a broken thumbnail.
1464
1271
  */
1465
- interface DesktopWebSocketLike {
1466
- binaryType: string;
1467
- send(data: ArrayBuffer): void;
1468
- close(): void;
1469
- addEventListener(type: "open" | "message" | "error" | "close", listener: (ev: {
1470
- data?: unknown;
1471
- } & Record<string, unknown>) => void): void;
1472
- removeEventListener(type: "open" | "message" | "error" | "close", listener: (ev: {
1473
- data?: unknown;
1474
- } & Record<string, unknown>) => void): void;
1475
- }
1476
- type DesktopWebSocketFactory = (url: string) => DesktopWebSocketLike;
1477
- type UseRelayFrameStreamOptions = {
1478
- /** The desktop cell of the negotiated capabilities (`capabilities.DesktopStream`). */
1479
- capability: DesktopStreamCapability | null;
1480
- /** The mount target. A `<canvas>` is appended here on connect. */
1481
- containerRef: RefObject<HTMLDivElement | null>;
1482
- /** Custom socket factory (tests / a transport swap). Defaults to `new WebSocket(url)`. */
1483
- webSocketFactory?: DesktopWebSocketFactory | undefined;
1484
- };
1485
- type UseRelayFrameStreamResult = {
1486
- state: DesktopConnectionState;
1487
- error: Error | null;
1488
- /** Tear down + reopen the socket (e.g. after a drop, once a fresh url arrives). */
1489
- reconnect: () => void;
1490
- };
1272
+ declare function MediaSkeleton(): react_jsx_runtime.JSX.Element;
1273
+ /** A standardized "tool ran, produced no image" placeholder in the media slot. */
1274
+ declare function MediaEmpty(): react_jsx_runtime.JSX.Element;
1491
1275
  /**
1492
- * VIEW-ONLY PNG-frame renderer for a SELF-HOSTED desktop stream the relay
1493
- * transport (`transport: "relay-frames"`, `client: "frames"`). The self-hosted
1494
- * agent produces one PNG per frame as a protobuf datagram over the relay; there
1495
- * is no RFB/noVNC here. This hook opens the relay DESKTOP channel as a CLIENT
1496
- * (mirroring the proven diagnostic wire protocol), decodes each PNG onto a
1497
- * `<canvas>` mounted into `containerRef`, and drives the connection state
1498
- * machine: idle → connecting (ws opening) → connected (first painted frame) →
1499
- * error (ws error/close/ack rejection).
1500
- *
1501
- * It is DORMANT for any other transport (or no url): it stays idle and touches
1502
- * nothing, so the noVNC path owns the surface. SSR-safe: the socket + DOM attach
1503
- * live inside `useEffect`.
1276
+ * A small inline screenshot thumbnail that opens the app lightbox on click.
1504
1277
  *
1505
- * Backpressure (critical frames are ~1.8MB at ~10fps): at most one frame is
1506
- * decoded at a time; a frame arriving mid-decode replaces the pending one
1507
- * (latest-wins), so a slow decode can never build an unbounded queue.
1278
+ * Requires a `LightboxProvider` ancestor for the click-to-expand affordance.
1279
+ * Outside one it degrades to a plain, non-interactive image never a dead
1280
+ * "Expand" button that announces an action it cannot perform.
1508
1281
  */
1509
- declare function useRelayFrameStream(options: UseRelayFrameStreamOptions): UseRelayFrameStreamResult;
1510
-
1511
- type UseDesktopStreamOptions = {
1512
- /** The desktop cell of the negotiated capabilities (`capabilities.DesktopStream`). */
1513
- capability: DesktopStreamCapability | null;
1514
- /** The mount target. RFB (or the frame canvas) attaches here on connect. */
1515
- containerRef: RefObject<HTMLDivElement | null>;
1516
- /** Read-only by default (v1 ruling H). interactive only when cap.mode allows. */
1517
- interactive?: boolean | undefined;
1518
- scaleViewport?: boolean | undefined;
1519
- /** Custom RFB factory (tests / a WebRTC swap). Defaults to a lazy @novnc/novnc. */
1520
- rfbFactory?: DesktopRfbFactory | undefined;
1521
- /** Custom socket factory for the `relay-frames` transport (tests). Defaults to
1522
- * `new WebSocket(url)`. Mirrors `rfbFactory` for the frame renderer. */
1523
- webSocketFactory?: DesktopWebSocketFactory | undefined;
1524
- };
1525
- type UseDesktopStreamResult = {
1526
- state: DesktopConnectionState;
1527
- error: Error | null;
1528
- /** Manual reconnect (e.g. after a securityfailure once a fresh URL arrives). */
1529
- reconnect: () => void;
1530
- };
1282
+ declare function Thumbnail({ src, caption, alt, }: {
1283
+ src: string;
1284
+ caption?: string | undefined;
1285
+ alt?: string;
1286
+ }): react_jsx_runtime.JSX.Element;
1531
1287
  /**
1532
- * Drive the noVNC RFB lifecycle from a `DesktopStreamCapability`, using the
1533
- * SDK's `desktop.ts` reducer + `desktopSocketUrl`. SSR-safe: the RFB import and
1534
- * the DOM attach happen inside `useEffect`, so a server render is a no-op and
1535
- * the component shows its placeholder until hydration.
1536
- *
1537
- * Read-only is enforced at three layers: `capability.mode` (server) →
1538
- * `interactive` prop → `RFB.viewOnly`. v1 always resolves to read-only. On a
1539
- * capability `url` change (a rotation), the old RFB disconnects and a fresh one
1540
- * connects to the new URL — a brief "desktop blink", acceptable on rollover.
1541
- *
1542
- * Transport dispatch: a Modal box negotiates `transport: "vnc-ws"` and drives the
1543
- * noVNC RFB below. A SELF-HOSTED machine negotiates `transport: "relay-frames"`
1544
- * (PNG-per-frame over the relay, view-only) that path is delegated to
1545
- * `useRelayFrameStream`, which paints a `<canvas>`. Both hooks are ALWAYS called
1546
- * (rules of hooks); each is dormant for the other's transport, and we return the
1547
- * result of whichever owns the surface. The public interface is identical either
1548
- * way, so `DesktopViewer` is transport-agnostic.
1549
- */
1550
- declare function useDesktopStream(options: UseDesktopStreamOptions): UseDesktopStreamResult;
1551
-
1552
- /** The ttyd connection lifecycle as surfaced to the component. */
1553
- type TerminalStreamStatus = "connecting" | "open" | "closed" | "error";
1554
- type UseTerminalStreamOptions = {
1555
- /** The Terminal cell of the negotiated capabilities (`capabilities.Terminal`).
1556
- * The stream connects ONLY when `transport === "pty-ws"` and `url` is set; on a
1557
- * cold box (`transport === "sse-events"` / no url) it stays idle and the caller
1558
- * falls back to the Channel-A read-only firehose. */
1559
- capability: Pick<TerminalCapability, "transport" | "url" | "token"> | null;
1560
- /** Called for each OUTPUT payload from ttyd (write verbatim into xterm). */
1561
- onOutput?: ((data: string) => void) | undefined;
1562
- /** Called when ttyd sends a SET_WINDOW_TITLE frame. */
1563
- onTitle?: ((title: string) => void) | undefined;
1564
- /** Initial PTY size to seed the ttyd auth frame + first resize. */
1565
- initialCols?: number | undefined;
1566
- initialRows?: number | undefined;
1567
- };
1568
- type UseTerminalStreamResult = {
1569
- /** True once the ttyd socket is open (and the auth frame has been sent). */
1570
- connected: boolean;
1571
- status: TerminalStreamStatus;
1572
- /** Pipe a keystroke/paste to the PTY stdin. No-op until the socket is open. */
1573
- write: (data: string) => void;
1574
- /** Tell ttyd the PTY window changed size (on xterm fit/resize). */
1575
- resize: (cols: number, rows: number) => void;
1576
- /** Tear the socket down (the effect also tears down on unmount / url change). */
1577
- disconnect: () => void;
1578
- };
1579
- /**
1580
- * Drive a ttyd PTY-over-websocket connection from a `pty-ws` Terminal capability,
1581
- * symmetric with `use-desktop-stream` (the noVNC-over-tunnel hook). The scoped
1582
- * stream token is already embedded in the minted tunnel `url`; the WebSocket is
1583
- * opened with the REQUIRED ttyd subprotocol "tty".
1584
- *
1585
- * ttyd wire protocol (see `@opengeni/sdk/terminal`):
1586
- * - first frame: `JSON.stringify({ AuthToken: "" })` (+ optional columns/rows).
1587
- * - client→server: INPUT = "0"+data ; RESIZE = "1"+JSON({columns,rows}).
1588
- * - server→client: "0" = OUTPUT (→ xterm) ; "1" = SET_WINDOW_TITLE ;
1589
- * "2" = SET_PREFERENCES (ignored). Binary frames are decoded the same way.
1590
- *
1591
- * On a `url`/`token` rotation (a box rollover folds a fresh address into the cell)
1592
- * the effect re-runs: the old socket closes and a fresh one connects — a brief
1593
- * terminal blink, acceptable on rollover (mirrors the desktop's RFB hot-swap).
1594
- * SSR-safe: the socket open lives in `useEffect`, so a server render is a no-op.
1595
- */
1596
- declare function useTerminalStream(options: UseTerminalStreamOptions): UseTerminalStreamResult;
1597
-
1598
- type TerminalChunk = {
1599
- /** Stable key (the source event id) so the xterm writer tracks a written-cursor. */
1600
- id: string;
1601
- /** Raw output bytes (utf-8 lossy) — written verbatim into xterm. */
1602
- text: string;
1603
- /** stdout vs stderr (drives optional tinting). */
1604
- stream: "stdout" | "stderr";
1605
- /** Global ordering: the source event sequence. */
1606
- seq: number;
1607
- };
1608
- type UseSandboxTerminalOptions = ClientOverride & {
1609
- /** The live session event log (usually `useSessionEvents().events`). */
1610
- events: SessionEvent[];
1611
- /** Restrict to one PTY (by ptyId). Omit to interleave the agent firehose +
1612
- * every PTY. */
1613
- ptyId?: string | undefined;
1614
- /** Include the agent's command-output firehose (sandbox.command.output.delta).
1615
- * Default true — the read-only "terminal-as-events" the data path settled on. */
1616
- includeAgentFirehose?: boolean | undefined;
1617
- /**
1618
- * OPEN an interactive PTY against the box so the user can type, not just watch.
1619
- * When true (and the session is live) the hook calls `terminalPtyOpen` once,
1620
- * tracks the returned ptyId, exposes `write` immediately (bound to that ptyId),
1621
- * and closes the PTY on unmount. The PTY's banner + every output delta ride the
1622
- * SSE spine (`terminal.pty.*`) back into `events`, so xterm fills in. Default
1623
- * false — a caller that only wants the read-only firehose stays projection-only.
1624
- */
1625
- interactive?: boolean | undefined;
1626
- /** Lease liveness ("cold" | "warm" | "draining"). The interactive PTY is only
1627
- * opened once the box is warm — opening on a cold box (ptyCapable is advertised
1628
- * cold too) races the box and leaves a dead read-only terminal. */
1629
- liveness?: string | undefined;
1630
- };
1631
- type UseSandboxTerminalResult = {
1632
- /** Ordered, deduped output chunks to write() into xterm.js. */
1633
- chunks: TerminalChunk[];
1634
- /** Whether a PTY is currently open (drives the prompt/cursor affordance). */
1635
- running: boolean;
1636
- /**
1637
- * Interactive write fn when a PTY is open and the backend supports stdin
1638
- * (`terminal.transport === "pty-ws"` / `PtyOpenResponse.supportsInput`). Null
1639
- * in the read-only event-projection case (v1 default).
1640
- */
1641
- write: ((data: string) => void) | null;
1642
- /** The active PTY id, if one is open. */
1643
- activePtyId: string | null;
1644
- /** Close the active PTY (no-op when none is open). */
1645
- close: () => void;
1646
- /** A PTY-open failure (interactive mode), if any. */
1647
- error: Error | null;
1648
- };
1649
- /**
1650
- * Project the Channel-A event log into an xterm-writable byte stream. The
1651
- * terminal is "terminal-as-events": there is NO new socket in v1 — the agent's
1652
- * command output (`sandbox.command.output.delta`) and any interactive PTY
1653
- * (`terminal.pty.output.delta`) ride the existing SSE spine. When a PTY is open
1654
- * and the backend accepts stdin, `write` pipes keystrokes via the SDK
1655
- * `terminalPtyWrite` (the synchronous Channel-A control path).
1656
- */
1657
- declare function useSandboxTerminal(sessionId: string | null | undefined, options: UseSandboxTerminalOptions): UseSandboxTerminalResult;
1658
-
1659
- /** The git-status overlay a file row may carry (tints modified files in the tree). */
1660
- type FileTreeStatus = "added" | "modified" | "deleted" | "renamed" | "untracked";
1661
- /** A node in the Pierre file tree. `children === undefined` ⇒ an unexpanded dir
1662
- * (lazy treeMode); `children: []` ⇒ an expanded-but-empty dir. */
1663
- type FileTreeNode = {
1664
- path: string;
1665
- name: string;
1666
- kind: "file" | "dir";
1667
- children?: FileTreeNode[] | undefined;
1668
- size?: number | null | undefined;
1669
- status?: FileTreeStatus | undefined;
1670
- /** A residue dir the capture COLLAPSED (node_modules, .git, dist, …): its
1671
- * contents were never indexed. Cold, expanding it can't list children — the
1672
- * UI shows an inline "contents on machine" row until the box is warm. */
1673
- truncated?: boolean | undefined;
1674
- };
1675
- type UseSandboxFilesOptions = ClientOverride & {
1676
- /** Live event log (usually `useSessionEvents().events`) — drives auto-refresh
1677
- * on `fs.changed` / `git.changed`. */
1678
- events?: SessionEvent[] | undefined;
1679
- /** Initial path to list (workspace root by default). */
1680
- rootPath?: string | undefined;
1681
- /** Hold off the initial list (e.g. panel collapsed). Default true. */
1682
- enabled?: boolean | undefined;
1683
- /** The lease liveness ("cold" | "warm" | "draining"). The structured FileSystem
1684
- * capability is advertised even on a COLD box, so the mount-time list can race
1685
- * the box: it lists before the box is warm, gets an empty/errored result, and
1686
- * (with no `fs.changed` event) never re-lists. Passing liveness re-lists when
1687
- * the box first becomes warm, so the tree populates as soon as the box is up. */
1688
- liveness?: string | undefined;
1689
- /** The latest turn-end workspace capture (from `useWorkspaceCapture`). When the
1690
- * box is NOT warm, the tree paints INSTANTLY from this capture's tree index (the
1691
- * <200ms cold first paint) instead of blocking on a Channel-A list. A warm box
1692
- * always wins (live path unchanged). On the cold→warm transition the live list
1693
- * is merged in place — no remount, no flash (dossier §10.4 / §12-A1/D1). */
1694
- capture?: WorkspaceCaptureManifest | null | undefined;
1695
- /** Called when an OPTIMISTIC mutation is reverted because its background
1696
- * Channel-A op failed (e.g. a 409 rename collision). The host wires this to a
1697
- * toast — the tree silently rolls the node back, the user sees why. */
1698
- onMutationError?: ((error: Error, op: string) => void) | undefined;
1699
- };
1700
- type UseSandboxFilesResult = {
1701
- /** The tree roots (the listed root's children). */
1702
- tree: FileTreeNode[];
1703
- /** Lazy-expand a directory node in place (lists its immediate children). */
1704
- expand: (path: string) => Promise<void>;
1705
- /** Paths whose lazy `fs.list` is currently in flight — the FileBrowser shows a
1706
- * spinner on these nodes so a 2-3s Channel-A list never looks frozen. */
1707
- expandingPaths: Set<string>;
1708
- /** Read a file for the preview pane (text or base64-for-binary, size-capped). */
1709
- readFile: (path: string) => Promise<FsReadResponse>;
1710
- /** Write a file (overwrite, last-writer-wins) — the editor save path.
1711
- * Optimistic: a brand-new file is spliced into the tree immediately and the
1712
- * Channel-A write runs in the background; on failure the splice is reverted. */
1713
- writeFile: (path: string, content: string) => Promise<FsWriteResponse>;
1714
- /** Create a new empty file (refuses to clobber an existing path: overwrite=false). */
1715
- createFile: (path: string) => Promise<void>;
1716
- /** Create a directory (recursive by default). */
1717
- createDir: (path: string) => Promise<void>;
1718
- /** Delete a path (pass recursive=true for a non-empty directory). */
1719
- deleteEntry: (path: string, recursive?: boolean) => Promise<void>;
1720
- /** Move / rename a path (rename == move). Refuses to clobber unless overwrite=true. */
1721
- moveEntry: (path: string, newPath: string, opts?: {
1722
- overwrite?: boolean;
1723
- }) => Promise<void>;
1724
- /** Re-list the whole tree from the root. */
1725
- refresh: () => Promise<void>;
1726
- /** Which source the tree is currently served from: the live box, the turn-end
1727
- * capture (cold/offline), or neither yet. M5's source badge reads this. */
1728
- source: "live" | "capture" | null;
1729
- /** When the served capture was taken (ISO), when `source === "capture"`. */
1730
- capturedAt: string | null;
1731
- loading: boolean;
1732
- error: Error | null;
1733
- };
1734
- /**
1735
- * Project the FileSystem service into a lazy-loaded Pierre tree. The initial
1736
- * list pulls one level (depth 1); `expand(path)` lists a directory's immediate
1737
- * children on demand (the fast lazy-tree UX). A git-status overlay tints
1738
- * modified files. Auto-refreshes when an `fs.changed` / `git.changed` event
1739
- * arrives on the live log.
1740
- */
1741
- declare function useSandboxFiles(sessionId: string | null | undefined, options?: UseSandboxFilesOptions): UseSandboxFilesResult;
1742
-
1743
- type UseSandboxGitOptions = ClientOverride & {
1744
- /** Live event log (usually `useSessionEvents().events`) — drives auto-refresh
1745
- * on `git.changed`. */
1746
- events?: SessionEvent[] | undefined;
1747
- /** Repo root within the workspace (multi-repo). Default: workspace root. */
1748
- repoPath?: string | undefined;
1749
- /** Diff the staged index vs HEAD (`--cached`) instead of the working tree. */
1750
- staged?: boolean | undefined;
1751
- /** Hold off the initial fetch. Default true. */
1752
- enabled?: boolean | undefined;
1753
- /** Lease liveness ("warm" | "draining" | "cold"). When NOT warm, the diff is
1754
- * served from the capture (cold/offline) instead of a live `gitDiff` RPC. */
1755
- liveness?: string | undefined;
1756
- /** The latest turn-end capture (from `useWorkspaceCapture`). Supplies the cold
1757
- * diff for this repo. A warm box always wins (live `gitDiff` unchanged). */
1758
- capture?: WorkspaceCaptureManifest | null | undefined;
1759
- };
1760
- type UseSandboxGitResult = {
1761
- /** Working-tree (or staged) diff vs HEAD — the structured hunks the Pierre
1762
- * diff view renders. */
1763
- diff: GitFileDiff[];
1764
- branch: string | null;
1765
- /** Whether a repo is actually mounted (drives "no repository" vs "no changes"). */
1766
- isRepo: boolean;
1767
- ahead: number;
1768
- behind: number;
1769
- refresh: () => Promise<void>;
1770
- /** Which source the diff is served from: the live box or the turn-end capture. */
1771
- source: "live" | "capture" | null;
1772
- /** When the served capture was taken (ISO), when `source === "capture"`. */
1773
- capturedAt: string | null;
1774
- loading: boolean;
1775
- error: Error | null;
1776
- };
1777
- /**
1778
- * Project the Git service into the Pierre diff data contract: structured
1779
- * `GitFileDiff[]` (per-file hunks with per-line old/new numbers, rename
1780
- * detection, binary flag, add/del counts) plus branch + ahead/behind. When the
1781
- * box is warm the `git diff` runs in-box (API-direct); when it is cold/offline the
1782
- * diff is served from the turn-end capture instead (dossier §10.4). Refreshes on
1783
- * `git.changed`; reconciles live in place on the cold→warm transition.
1784
- */
1785
- declare function useSandboxGit(sessionId: string | null | undefined, options?: UseSandboxGitOptions): UseSandboxGitResult;
1786
-
1787
- type UseWorkspaceCaptureOptions = ClientOverride & {
1788
- /** Live event log (usually `useSessionEvents().events`) — a
1789
- * `workspace.revision.captured` for a NEWER revision refreshes the manifest. */
1790
- events?: SessionEvent[] | undefined;
1791
- /** Hold off the mount fetch (e.g. the workbench panel is collapsed). Default true. */
1792
- enabled?: boolean | undefined;
1793
- };
1794
- type UseWorkspaceCaptureResult = {
1795
- /** The resolved manifest (tree index + per-repo diffs + file refs), or null when
1796
- * no capture exists (falls back to the live/wake path — status quo). */
1797
- capture: WorkspaceCaptureManifest | null;
1798
- /** The loaded capture's monotonic revision, or null when unavailable. */
1799
- revision: number | null;
1800
- /** When the loaded capture was taken (ISO), for the "as of <time>" source badge. */
1801
- capturedAt: string | null;
1802
- /** Whether a capture is available at all (the `{available:false}` discriminator). */
1803
- available: boolean;
1804
- /** Why the newest durable revision is unavailable. null means no capture has
1805
- * been attempted yet; a value means capture explicitly failed closed and
1806
- * consumers must use the live box rather than trust an incomplete snapshot. */
1807
- degradedReason: WorkspaceCaptureDegradedReason | null;
1808
- /** The changed-file count from the capture's stats, resolved on the FIRST GET
1809
- * (from the response's top-level `stats`, before any manifest-URL hop). null
1810
- * until that first resolve; 0 when no capture exists. This is the pre-paint
1811
- * "changes exist?" signal the dock uses to pick its default tab with no
1812
- * embedder events-at-mount contract. */
1813
- fileCount: number | null;
1814
- /** A newer revision has been ANNOUNCED than the one currently loaded (a refresh
1815
- * is in flight or pending). M5's source badge can show a subtle "updating…". */
1816
- isStale: boolean;
1817
- loading: boolean;
1818
- error: Error | null;
1819
- /** Force a re-fetch of the latest capture. */
1820
- refresh: () => Promise<void>;
1288
+ * The expanded screenshot inside a tool body: a contained, clickable preview
1289
+ * (opens the lightbox) with a quiet caption. Constrained height + object-contain
1290
+ * so it never breaks the row layout. Like {@link Thumbnail}, it degrades to a
1291
+ * plain image outside a `LightboxProvider`.
1292
+ */
1293
+ declare function ScreenshotFigure({ src, caption, alt, }: {
1294
+ src: string;
1295
+ caption?: string | undefined;
1296
+ alt?: string;
1297
+ }): react_jsx_runtime.JSX.Element;
1298
+
1299
+ type LightboxController = {
1300
+ open: (src: string, caption?: string) => void;
1821
1301
  };
1302
+ /** Open the app-level screenshot lightbox. No-op outside a `LightboxProvider`. */
1303
+ declare function useLightbox(): LightboxController;
1822
1304
  /**
1823
- * The cold-paint data source: fetch the latest turn-end workspace capture with a
1824
- * SINGLE api round-trip on mount (no machine, no Channel-A — this is the <200ms
1825
- * first paint, dossier §10.4/§12-A1). The manifest is served inline in the common
1826
- * case; a rare >2MB manifest comes back as a short-TTL signed URL we follow.
1827
- *
1828
- * Subscribes to the live event log: a `workspace.revision.captured` for a revision
1829
- * newer than the one loaded triggers a background refresh (stale-while-revalidate).
1830
- * `{available:false}` is a value, never a crash — consumers fall back to the
1831
- * live/wake path exactly as before the capture feature existed.
1305
+ * The lightbox controller when one is mounted, or `null` outside a
1306
+ * `LightboxProvider`. Lets a media primitive degrade to a non-interactive image
1307
+ * (rather than a dead "Expand" button that announces an action it cannot do).
1832
1308
  */
1833
- declare function useWorkspaceCapture(sessionId: string | null | undefined, options?: UseWorkspaceCaptureOptions): UseWorkspaceCaptureResult;
1834
-
1309
+ declare function useLightboxOptional(): LightboxController | null;
1835
1310
  /**
1836
- * The wake-on-edit state machine (dossier §3 #5 / §10.4 / §12-C). M3 OWNS this
1837
- * logic; M5 renders it. States are explicit and exhaustive:
1311
+ * The app-level screenshot lightbox. Render once near the timeline; renderers
1312
+ * call `useLightbox().open(src)`.
1838
1313
  *
1839
- * viewing-cold — cold snapshot open, no local edits (editable, cloud).
1840
- * buffering — local edits buffered; the box is not warm and no warm is
1841
- * yet in flight (the host has not attached).
1842
- * warming — edits buffered; the box is actively coming up (the host
1843
- * attached a viewer in response to `wantsWarm`).
1844
- * flushing — the box is warm; the guarded write is in flight.
1845
- * flushed — the buffer was written to the live box.
1846
- * conflict — the live file diverged from the captured base between
1847
- * capture and flush; NO write was issued (C2).
1848
- * readonly-offline — a self-hosted machine that is offline; read-only, no wake.
1314
+ * Idempotent by design: when an ancestor `LightboxProvider` already exists (e.g.
1315
+ * a `MessageTimeline` mounted inside an app that already wraps its shell), this
1316
+ * one becomes a pass-through and does NOT mount a second focus-trapping Dialog.
1317
+ * That keeps `MessageTimeline` self-sufficient (it owns its own provider) while
1318
+ * composing cleanly when nested.
1849
1319
  */
1850
- type WorkspaceEditState = "viewing-cold" | "buffering" | "warming" | "flushing" | "flushed" | "conflict" | "readonly-offline";
1851
- type WorkspaceEditConflict = {
1852
- path: string;
1853
- /** The capture-served content the edit was based on. */
1854
- base: string;
1855
- /** The live content found on the box at flush time (what would be overwritten). */
1856
- live: string;
1857
- };
1858
- type UseWorkspaceEditOptions = ClientOverride & {
1859
- /** The file being edited (workspace-relative). null disables the machine. */
1860
- path?: string | null | undefined;
1861
- /** The capture-served content loaded into the editor — the flush base (C2). */
1862
- baseContent?: string | null | undefined;
1863
- /** `capabilities.liveness` — "warm" | "draining" | "cold". Drives the flush. */
1864
- liveness?: string | undefined;
1865
- /** True while the host is actively warming the box (attach in flight, not warm
1866
- * yet) — lifts `buffering` → `warming`. The host sets this after acting on
1867
- * `wantsWarm` (e.g. flipping `attachFiles` on `useSessionCapabilities`). */
1868
- warming?: boolean | undefined;
1869
- /** A self-hosted machine that is OFFLINE: read-only, no remote wake possible. */
1870
- offline?: boolean | undefined;
1871
- /** Called once when the first cold edit needs the box warmed — the host wires
1872
- * this to its liveness primitive (flip `attachFiles`/`attachViewer`). */
1873
- onWarmRequested?: (() => void) | undefined;
1874
- };
1875
- type UseWorkspaceEditResult = {
1876
- state: WorkspaceEditState;
1877
- /** The editor should be read-only (self-hosted offline). */
1878
- readOnly: boolean;
1879
- /** The current buffered content (null = no local edit yet). */
1880
- buffer: string | null;
1881
- /** The host should warm the box (flip its attach primitive). Latches true from
1882
- * the first cold edit until the buffer flushes or is discarded. */
1883
- wantsWarm: boolean;
1884
- /** Record an edit from the editor (idempotent per identical content). */
1885
- edit: (content: string) => void;
1886
- /** The conflict detail when `state === "conflict"`. */
1887
- conflict: WorkspaceEditConflict | null;
1888
- /** Flush the buffer regardless of the base guard (the user chose "overwrite"
1889
- * from the conflict bar). Writes last-writer-wins. */
1890
- overwrite: () => Promise<void>;
1891
- /** Drop the local buffer and return to viewing the snapshot. */
1892
- discard: () => void;
1893
- error: Error | null;
1894
- };
1320
+ declare function LightboxProvider({ children }: {
1321
+ children: ReactNode;
1322
+ }): react_jsx_runtime.JSX.Element;
1323
+
1895
1324
  /**
1896
- * Cold-editing without a spinner: the editor opens instantly from the capture,
1897
- * the first keystroke buffers locally AND signals the host to warm the box, and on
1898
- * warm the buffer flushes IF the live file still matches the captured base — else a
1899
- * non-blocking conflict is surfaced and nothing is overwritten (dossier §12-C2).
1900
- *
1901
- * The base guard compares the live content at flush time against `baseContent`
1902
- * (the exact bytes the editor loaded). This is equivalent to the sha256 compare
1903
- * the dossier describes — M1 leaves `baseHash` null, so the loaded content IS the
1904
- * authoritative base — and avoids async Web Crypto in the render path.
1325
+ * Seed the initial open state of every timeline collapsible below this node.
1326
+ * Intended for screenshot/test instrumentation only; absent by default.
1905
1327
  */
1906
- declare function useWorkspaceEdit(sessionId: string | null | undefined, options?: UseWorkspaceEditOptions): UseWorkspaceEditResult;
1328
+ declare function DisclosureDefaultsProvider({ defaultOpen, children, }: {
1329
+ defaultOpen: boolean;
1330
+ children: ReactNode;
1331
+ }): react_jsx_runtime.JSX.Element;
1907
1332
 
1908
- /** The one truthful machine indicator (dossier §3 #10). Honest staleness beats
1909
- * fake liveness: a cold/asleep box reads "offline as of <time>", never "live". */
1910
- type MachineChipState = "live" | "waking" | "offline";
1911
- type MachineChip = {
1912
- state: MachineChipState;
1913
- /** A ready-to-render label ("Live" / "Waking…" / "Offline as of 3m ago"). */
1914
- label: string;
1915
- /** The capture time backing an offline/stale label (ISO), or null. M4 may
1916
- * reformat this; `label` already embeds a relative form. */
1917
- asOf: string | null;
1333
+ type TurnSummaryProps = {
1334
+ /** The activity items in the turn (used only to compute the facet counts). */
1335
+ items: ActivityItem[];
1336
+ /**
1337
+ * The settled verdict — or absent for a completed CLUSTER of a still-running
1338
+ * turn, which folds neutrally: no verdict glyph (the turn has none yet), a
1339
+ * quiet pulse dot in its place so alignment and the running feel both hold.
1340
+ */
1341
+ outcome?: TurnOutcome | undefined;
1342
+ /** A short failure reason shown inline on a failed chip (never hidden). */
1343
+ failureText?: string | undefined;
1344
+ /** Elapsed turn duration; shown as a trailing facet when at least 1s. */
1345
+ durationMs?: number | undefined;
1346
+ /** Start expanded. */
1347
+ defaultOpen?: boolean | undefined;
1348
+ /**
1349
+ * A nested fold — a cluster or sub-turn INSIDE an already-expanded turn. It
1350
+ * drops the bordered/filled chip and renders as a plain disclosure node on the
1351
+ * parent's rail (chevron + glyph + facets), so expanding a turn reveals a thread
1352
+ * of nodes, never a stack of boxes-in-boxes. The top-level fold stays a chip.
1353
+ */
1354
+ bare?: boolean | undefined;
1355
+ /** The rendered activity rail revealed on expand. */
1356
+ children: React.ReactNode;
1918
1357
  };
1919
- type DeriveMachineChipInput = {
1920
- /** `capabilities.liveness` — "warm" | "draining" | "cold" (or null pre-negotiation). */
1921
- liveness?: string | null | undefined;
1922
- /** `useSessionCapabilities().state` drives the "waking" (negotiating) read. */
1923
- capabilitiesState?: SessionCapabilitiesState | null | undefined;
1924
- /** The ACTIVE machine's connection state (from `useMachines`), when known. A
1925
- * self-hosted machine that is `offline` cannot be remotely woken. */
1926
- activeMachineState?: MachineState | null | undefined;
1927
- /** Whether the active sandbox is a user-owned self-hosted machine (no remote wake). */
1928
- activeIsSelfhosted?: boolean | undefined;
1929
- /** An interaction (wake-on-edit, terminal focus) is actively warming the box. */
1930
- wantsWarm?: boolean | undefined;
1931
- /** The latest capture's `capturedAt` (ISO) the "as of <time>" backing. */
1932
- capturedAt?: string | null | undefined;
1933
- /** Injectable clock for deterministic relative labels (defaults to Date.now). */
1934
- now?: number | undefined;
1358
+ declare function TurnSummary({ items, outcome, failureText, durationMs, defaultOpen, bare, children, }: TurnSummaryProps): react_jsx_runtime.JSX.Element;
1359
+
1360
+ /** Recover the exit code from a sandbox exec banner (`Process exited with code N`). */
1361
+ declare function sandboxCommandExitCode(out: unknown): number | null;
1362
+ /**
1363
+ * Recover the numeric exec-session id the sandbox embeds for a STILL-RUNNING
1364
+ * (backgrounded) process (`Process running with session ID N`). A finished
1365
+ * command emits `Process exited with code N` instead, which yields `null`.
1366
+ */
1367
+ declare function parseExecBannerSessionId(out: unknown): number | null;
1368
+ /** Strip the exec banner (`Chunk ID ...\n...\nOutput:\n`) down to the command's stdout. */
1369
+ declare function stripExecBanner(out: unknown): string;
1370
+ /** The sandbox clamped the output (token/line truncation markers in the banner). */
1371
+ declare function execTruncated(out: unknown): boolean;
1372
+ /** A `write_stdin` whose target PTY vanished (`write_stdin failed: session not found: N`). */
1373
+ declare function isExecSessionLostBanner(out: unknown): boolean;
1374
+ /** True when the exec stdout looks binary/garbled (a NUL byte or ELF magic). */
1375
+ declare function looksBinary(text: string): boolean;
1376
+ /**
1377
+ * Render unprintable control characters as caret notation (0x03 -> `^C`) so a
1378
+ * `write_stdin` keystroke payload reads cleanly in the row title.
1379
+ */
1380
+ declare function controlCaret(printable: string): string;
1381
+ /** One operation inside an `apply_patch_call` (a V4A file edit). */
1382
+ type ApplyPatchOperation = {
1383
+ /**
1384
+ * The V4A op kind. The three canonical values are `create_file`,
1385
+ * `update_file`, and `delete_file`; the open `string` tail tolerates a
1386
+ * forward-compatible/unknown op kind from the provider without a type error
1387
+ * (it falls through to the "Edited" treatment).
1388
+ */
1389
+ type: "create_file" | "update_file" | "delete_file" | (string & {});
1390
+ path: string;
1391
+ /** Rename target -- when present the op is a move/rename. */
1392
+ moveTo?: string | null | undefined;
1393
+ /** The V4A hunk string (`@@ ...` lines with `+`/`-`/context prefixes). */
1394
+ diff?: string | undefined;
1935
1395
  };
1936
- /** Compact relative-time for the "as of" label. Pure, no Intl dependency. */
1937
- declare function formatAsOf(capturedAt: string, now: number): string;
1938
1396
  /**
1939
- * Derive the machine-state chip from the live capability/liveness surface, the
1940
- * active machine's connection state, and the latest capture time. PURE (M4 renders
1941
- * it; the whole point is a deterministic, testable projection).
1942
- *
1943
- * Precedence:
1944
- * 1. warm/draining lease → **live** (a warm box always wins).
1945
- * 2. actively warming (negotiating, wake-on-edit, or a reconnecting/enrolling
1946
- * machine) → **waking**.
1947
- * 3. everything else (a cold/asleep box, a self-hosted machine that's offline,
1948
- * an error) → **offline**, labelled "as of <capture time>".
1397
+ * Parse a single V4A `apply_patch` operation into the SDK's `GitFileDiff` shape
1398
+ * so it can flow into the SAME `DiffView` / `PierreDiff` the Files tab uses.
1399
+ * Throws on a hunk string it cannot structure (no `@@` anchor on an update); the
1400
+ * renderer catches and falls back to a raw-patch view.
1949
1401
  */
1950
- declare function deriveMachineChip(input: DeriveMachineChipInput): MachineChip;
1951
- type UseMachineChipOptions = DeriveMachineChipInput;
1402
+ declare function v4aToGitFileDiff(op: ApplyPatchOperation): GitFileDiff;
1952
1403
  /**
1953
- * Thin memoized wrapper over `deriveMachineChip` for the dock header (M4). The
1954
- * dock already runs `useSessionCapabilities` + `useMachines` + `useWorkspace
1955
- * capture`; it feeds their `liveness` / `state` / active-machine state /
1956
- * `capturedAt` here. Pass a periodically-updated `now` if you want the relative
1957
- * "as of" label to tick.
1404
+ * Extract the `apply_patch` operations from a provider-native tool item's `raw`
1405
+ * payload, normalizing the two wire shapes (`raw.operations[]` for a multi-file
1406
+ * patch, `raw.operation` for a single op). The single owner of this shape so the
1407
+ * renderer and the turn-summary facet counter never drift.
1958
1408
  */
1959
- declare function useMachineChip(input: UseMachineChipOptions): MachineChip;
1960
-
1961
- type PendingApproval = {
1962
- /** The id to send back via `user.approvalDecision` (`approvalId`). */
1963
- id: string;
1964
- /** Tool/function name awaiting the decision. */
1409
+ declare function applyPatchOps(raw: unknown): ApplyPatchOperation[];
1410
+ /**
1411
+ * True when a tool item is an `apply_patch_call` — by its provider-native
1412
+ * `raw.type` (the live-wire source of truth) or by tool `name` (first-party
1413
+ * replays that omit `raw`). Centralizes the rawType-or-name check.
1414
+ */
1415
+ declare function isApplyPatch(item: {
1965
1416
  name: string;
1966
- arguments?: unknown;
1967
- /** The raw approval entry from the `session.requiresAction` payload. */
1968
- raw?: unknown;
1969
- };
1970
- /** The approvals carried by one `session.requiresAction` payload. */
1971
- declare function approvalsFromRequiresAction(payload: unknown): PendingApproval[];
1972
- /** The approvals still awaiting a decision after replaying `events` in order. */
1973
- declare function projectPendingApprovals(events: SessionEvent[]): PendingApproval[];
1417
+ raw: unknown;
1418
+ }): boolean;
1419
+ /** Deep-redact secret-looking values so arguments never leak a key into the UI. */
1420
+ declare function redactSecrets(value: unknown): unknown;
1421
+ /** Parse tool arguments that may arrive as a JSON string or an object. */
1422
+ declare function parseToolArgs(args: unknown): Record<string, unknown>;
1423
+ /** The last non-empty line of a string -- the compact "what happened" peek. */
1424
+ declare function tailPeek(text: string): string;
1425
+ /**
1426
+ * Unwrap an MCP tool result (`{ content: [{ type: "text", text }], isError? }`)
1427
+ * into a flat `{ text, isError }`. Non-MCP outputs pass through as their string
1428
+ * form.
1429
+ */
1430
+ declare function unwrapMcpOutput(output: unknown): {
1431
+ text: string;
1432
+ isError: boolean;
1433
+ };
1974
1434
 
1975
1435
  /**
1976
1436
  * Parse a composer value into a command name + the rest. A command is
@@ -2023,6 +1483,10 @@ type CommandPaletteProps = {
2023
1483
  argHintText: string;
2024
1484
  /** id used for aria-activedescendant wiring from the textarea. */
2025
1485
  listboxId: string;
1486
+ /** Accessible list label. Defaults to "Slash commands". */
1487
+ label?: string | undefined;
1488
+ /** Badge shown for destructive commands. Defaults to "danger". */
1489
+ dangerLabel?: string | undefined;
2026
1490
  };
2027
1491
  /**
2028
1492
  * The slash-command palette: a popover anchored above the textarea, rendered
@@ -2030,7 +1494,7 @@ type CommandPaletteProps = {
2030
1494
  * the opengeni#46 design tokens. Full keyboard nav lives in useSlashCommands;
2031
1495
  * this component is presentational + aria.
2032
1496
  */
2033
- declare function CommandPalette({ open, items, highlight, onHighlight, onRun, argHintText, listboxId, }: CommandPaletteProps): react_jsx_runtime.JSX.Element;
1497
+ declare function CommandPalette({ open, items, highlight, onHighlight, onRun, argHintText, listboxId, label, dangerLabel, }: CommandPaletteProps): react_jsx_runtime.JSX.Element;
2034
1498
 
2035
1499
  type ModelPickerProps = {
2036
1500
  /** The host-exposed models to choose from (typically {@link useAvailableModels}). */
@@ -2041,6 +1505,8 @@ type ModelPickerProps = {
2041
1505
  onChange: (modelId: string) => void;
2042
1506
  disabled?: boolean | undefined;
2043
1507
  className?: string | undefined;
1508
+ /** Visible-to-assistive-technology label. Defaults to "Model". */
1509
+ label?: string | undefined;
2044
1510
  };
2045
1511
  /**
2046
1512
  * The model picker — a compact dropdown for the composer footer, grouping the
@@ -2053,7 +1519,7 @@ type ModelPickerProps = {
2053
1519
  * the send path. Renders nothing when no models are exposed, so a single-model
2054
1520
  * deployment shows no chrome.
2055
1521
  */
2056
- declare function ModelPicker({ models, value, onChange, disabled, className }: ModelPickerProps): react_jsx_runtime.JSX.Element | null;
1522
+ declare function ModelPicker({ models, value, onChange, disabled, className, label, }: ModelPickerProps): react_jsx_runtime.JSX.Element | null;
2057
1523
 
2058
1524
  type MessageTimelineProps = {
2059
1525
  /** Raw session events (projected internally) … */
@@ -2337,7 +1803,8 @@ type FileBrowserProps = {
2337
1803
  editable?: boolean | undefined;
2338
1804
  /**
2339
1805
  * Confirm a (recursive) delete before it runs. Return `false` to cancel.
2340
- * Defaults to `window.confirm`. Pass a no-op returning `true` to skip.
1806
+ * The default is an accessible, non-blocking confirmation dialog. Supply this
1807
+ * callback to delegate confirmation to a host-owned dialog.
2341
1808
  */
2342
1809
  confirmDelete?: ((node: FileTreeNode) => boolean | Promise<boolean>) | undefined;
2343
1810
  className?: string | undefined;
@@ -2467,6 +1934,10 @@ type CodeEditorProps = {
2467
1934
  initialContents: string;
2468
1935
  /** Persist the current buffer. Resolves when the write lands; rejects to surface an error. */
2469
1936
  onSave: (contents: string) => Promise<unknown>;
1937
+ /** Explicitly overwrite after a visible expected-content conflict. */
1938
+ onOverwrite?: ((contents: string) => Promise<unknown>) | undefined;
1939
+ /** Re-read the selected file after a visible save conflict. */
1940
+ onReload?: (() => void) | undefined;
2470
1941
  /** Fired once when the buffer FIRST diverges from its baseline (the first real
2471
1942
  * keystroke) — the wake-on-edit intent. The host warms the box on this so the
2472
1943
  * eventual save lands fast; merely opening the file for reading never fires it. */
@@ -2492,7 +1963,7 @@ type CodeEditorProps = {
2492
1963
  * `readOnly` suppresses every mutation path so a truncated/binary file can never
2493
1964
  * be saved back (which would corrupt it by writing the truncated prefix).
2494
1965
  */
2495
- declare function CodeEditor({ path, initialContents, onSave, onEditIntent, readOnly, themeType, loading, fallback, className, }: CodeEditorProps): react_jsx_runtime.JSX.Element;
1966
+ declare function CodeEditor({ path, initialContents, onSave, onOverwrite, onReload, onEditIntent, readOnly, themeType, loading, fallback, className, }: CodeEditorProps): react_jsx_runtime.JSX.Element;
2496
1967
 
2497
1968
  type SandboxFilesProps = {
2498
1969
  /** From `useSandboxFiles(...)`. */
@@ -2521,6 +1992,15 @@ type SandboxFilesProps = {
2521
1992
  requestedPathRequestId?: string | number | undefined;
2522
1993
  /** False while the parent is waking a cold sandbox for `requestedPath`. */
2523
1994
  requestedPathReady?: boolean | undefined;
1995
+ /** The machine is cold and no durable capture exists yet. Render an explicit
1996
+ * wake gate instead of an empty-tree lie or an implicit Channel-A read. */
1997
+ workspaceResting?: boolean | undefined;
1998
+ /** A deliberate wake has started but the live file surface is not ready yet. */
1999
+ workspaceWaking?: boolean | undefined;
2000
+ /** Whether live reads are currently authoritative. */
2001
+ liveWorkspaceReady?: boolean | undefined;
2002
+ /** Deliberately wake the machine to read content absent from the capture. */
2003
+ onWakeWorkspace?: (() => void) | undefined;
2524
2004
  themeType?: "dark" | "light" | undefined;
2525
2005
  className?: string | undefined;
2526
2006
  };
@@ -2531,7 +2011,7 @@ type SandboxFilesProps = {
2531
2011
  * surface deliberately does NOT replicate the changed-files list, and does not
2532
2012
  * diff here — one job per tab). The agent commits; the human reviews.
2533
2013
  */
2534
- declare function SandboxFiles({ files, git, fileSystemAvailable, usePierre, editable, onEditIntent, requestedPath, requestedPathRequestId, requestedPathReady, themeType, className, }: SandboxFilesProps): react_jsx_runtime.JSX.Element;
2014
+ declare function SandboxFiles({ files, git, fileSystemAvailable, usePierre, editable, onEditIntent, requestedPath, requestedPathRequestId, requestedPathReady, workspaceResting, workspaceWaking, liveWorkspaceReady, onWakeWorkspace, themeType, className, }: SandboxFilesProps): react_jsx_runtime.JSX.Element;
2535
2015
 
2536
2016
  type DesktopViewerProps = {
2537
2017
  /** The desktop cell of the negotiated capabilities (`capabilities.DesktopStream`). */
@@ -2659,6 +2139,15 @@ type WorkspaceNotification = {
2659
2139
  /** The workbench's canonical tab ids (a host injects extras around these). */
2660
2140
  declare const WORKBENCH_TAB_CHANGES = "changes";
2661
2141
  declare const WORKBENCH_TAB_FILES = "files";
2142
+ declare const WORKBENCH_TAB_TERMINAL = "terminal";
2143
+ declare const WORKBENCH_TAB_DESKTOP = "desktop";
2144
+ /**
2145
+ * The built-in workbench surfaces in their canonical display order. Hosts can
2146
+ * pass a subset through `surfaces`; omission preserves the complete standalone
2147
+ * workbench.
2148
+ */
2149
+ declare const WORKBENCH_SURFACES: readonly ["changes", "files", "terminal", "desktop"];
2150
+ type SandboxWorkspaceSurface = (typeof WORKBENCH_SURFACES)[number];
2662
2151
  /**
2663
2152
  * Decide a workspace tab from an already-local event log: the newest
2664
2153
  * `workspace.revision.captured` announce carries the change surface stats, so
@@ -2671,7 +2160,9 @@ declare const WORKBENCH_TAB_FILES = "files";
2671
2160
  */
2672
2161
  declare function initialWorkspaceTab(events: SessionEvent[] | undefined, override?: string | null): string;
2673
2162
  type WorkspaceMachine = {
2674
- /** The derived live/waking/offline chip model (dossier §3 #10). */
2163
+ /** Whether at least one built-in workbench surface is enabled. */
2164
+ enabled: boolean;
2165
+ /** The derived live/waking/offline chip model. */
2675
2166
  chip: MachineChip;
2676
2167
  /** The machine these surfaces are bound to (the Modal group box or a
2677
2168
  * self-hosted machine), or null while the fleet is still resolving. */
@@ -2684,6 +2175,12 @@ type UseSandboxWorkspaceTabsOptions = ClientOverride & {
2684
2175
  sessionId: string;
2685
2176
  /** Live event log (usually `useSessionEvents().events`). */
2686
2177
  events: SessionEvent[];
2178
+ /**
2179
+ * Built-in surfaces this host wants to expose. Omit for all four. A disabled
2180
+ * surface is behaviorally dormant: its data hooks, warm intents, stream
2181
+ * attachment, and navigation callbacks are not activated.
2182
+ */
2183
+ surfaces?: readonly SandboxWorkspaceSurface[] | undefined;
2687
2184
  /** Override the source-driven default tab (e.g. a host landing tab id). When
2688
2185
  * omitted the workbench picks Changes-vs-Files from capture or live Git. */
2689
2186
  initialTab?: string | null | undefined;
@@ -2704,7 +2201,8 @@ type UseSandboxWorkspaceTabsResult = {
2704
2201
  tabs: WorkspaceTab[];
2705
2202
  /** The source-driven default tab: Changes when the first authoritative capture
2706
2203
  * or live Git result has changes, else Files (a host `initialTab` overrides).
2707
- * null while that source resolves; the choice latches once. */
2204
+ * null while that source resolves, or permanently when neither Changes nor
2205
+ * Files is enabled; the choice latches once. */
2708
2206
  defaultTab: string | null;
2709
2207
  /** The machine-state model for the dock-header chip. */
2710
2208
  machine: WorkspaceMachine;
@@ -2721,6 +2219,11 @@ type SandboxWorkspaceProps = ClientOverride & {
2721
2219
  events: SessionEvent[];
2722
2220
  /** The chat / primary pane shown beside the dock. */
2723
2221
  primary: ReactNode;
2222
+ /**
2223
+ * Built-in surfaces this host wants to expose. Omit for Changes, Files,
2224
+ * Terminal, and Desktop. Disabled surfaces remain behaviorally dormant.
2225
+ */
2226
+ surfaces?: readonly SandboxWorkspaceSurface[] | undefined;
2724
2227
  /** Host tabs injected BEFORE the workbench tabs (e.g. a "Run" landing tab). */
2725
2228
  leadingTabs?: WorkspaceTab[] | undefined;
2726
2229
  /** Host tabs injected AFTER the workbench tabs (e.g. a "Debug" tab). */
@@ -2882,4 +2385,4 @@ declare function isCreditExhaustion(input: {
2882
2385
  */
2883
2386
  declare function humanizeFailureReason(reason: string | null): string | null;
2884
2387
 
2885
- export { ActivityDisclosure, type ActivityDisclosureProps, type ActivityItem, ActivityRail, type ActivityRailProps, type AgentMessageItem, type ApplyPatchOperation, type AuthNeededItem, BodyNote, CREDIT_EXHAUSTION_MESSAGE, ChatComposer, type ChatComposerProps, ClientOverride, CodeEditor, type CodeEditorProps, type CodexAccountsClientLike, type CommandContext, CommandPalette, type CommandPaletteProps, type CommandResult, type ComposerSendExtras, type ComposerState, type ConfirmState, type CreateToolRegistryOptions, type DeriveMachineChipInput, DesktopViewer, type DesktopViewerProps, type DesktopWebSocketFactory, type DesktopWebSocketLike, type DiffTheme, DiffView, type DiffViewProps, type DisclosureChip, DisclosureDefaultsProvider, FILE_ONLY_MESSAGE_TEXT, type FileAttachment, FileBrowser, type FileBrowserProps, type FileTreeNode, type FileTreeStatus, FleetTile, type FleetTileProps, type GoalItem, LightboxProvider, type MachineChip, type MachineChipState, Markdown, type MarkdownProps, MediaEmpty, MediaSkeleton, MessageTimeline, type MessageTimelineProps, ModelPicker, type ModelPickerProps, type Notice, type NoticeItem, OPEN_WORKSTREAM_CONTROL_EVENT, type ParsedCommandLine, PayloadBlock, type PendingApproval, PierreDiff, type PierreDiffProps, PierreFile, type PierreFileProps, type QueueMutationKind, QueueSurface, type QueueSurfaceProps, type ReasoningItem, SESSION_STATUS_META, SandboxFiles, type SandboxFilesProps, type SandboxItem, SandboxTerminal, type SandboxTerminalProps, SandboxWorkspace, type SandboxWorkspaceProps, ScreenshotFigure, type SessionCapabilitiesState, SessionClientLike, type SessionEventsConnectionState, SessionStatus, type SessionStatusItem, type SessionStatusMeta, type SessionStatusProps, type SlashArg, type SlashCommand, type SlashCommandContext, type SlashCommandHandlers, StatusDot, type StatusDotProps, TermBlock, type TerminalChunk, type TerminalStreamStatus, Thumbnail, type TimelineGroup, type TimelineItem, TimelineRow, type ToolCallItem, type ToolRegistry, type ToolRegistryEntry, type ToolRenderer, type ToolRendererProps, type TurnEndItem, type TurnOutcome, TurnSummary, type TurnSummaryProps, type UseAvailableModelsOptions, type UseAvailableModelsResult, type UseBillingUsageOptions, type UseBillingUsageResult, type UseCodexAccountsOptions, type UseCodexAccountsResult, type UseComposerOptions, type UseDesktopStreamOptions, type UseDesktopStreamResult, type UseEnvironmentsOptions, type UseEnvironmentsResult, type UseFileAttachmentsOptions, type UseFileAttachmentsResult, type UseGoalOptions, type UseGoalResult, type UseMachineChipOptions, type UsePacksOptions, type UsePacksResult, type UseRelayFrameStreamOptions, type UseRelayFrameStreamResult, type UseRigChangesOptions, type UseRigChangesResult, type UseRigOptions, type UseRigResult, type UseRigVersionsOptions, type UseRigVersionsResult, type UseRigsOptions, type UseRigsResult, type UseSandboxFilesOptions, type UseSandboxFilesResult, type UseSandboxGitOptions, type UseSandboxGitResult, type UseSandboxTerminalOptions, type UseSandboxTerminalResult, type UseSandboxWorkspaceTabsOptions, type UseSandboxWorkspaceTabsResult, type UseScheduledTasksOptions, type UseScheduledTasksResult, type UseSessionCapabilitiesOptions, type UseSessionCapabilitiesResult, type UseSessionControlOptions, type UseSessionControlResult, type UseSessionEventsOptions, type UseSessionEventsResult, type UseSessionLineageOptions, type UseSessionLineageResult, type UseSessionOptions, type UseSessionResult, type UseSlashCommandsOptions, type UseSlashCommandsResult, type UseTerminalStreamOptions, type UseTerminalStreamResult, type UseTurnQueueOptions, type UseTurnQueueResult, type UseVariableSetsOptions, type UseVariableSetsResult, type UseWorkspaceCaptureOptions, type UseWorkspaceCaptureResult, type UseWorkspaceEditOptions, type UseWorkspaceEditResult, type UseWorkspaceSessionsOptions, type UseWorkspaceSessionsResult, type UseWorkspacesOptions, type UseWorkspacesResult, type UserMessageItem, WORKBENCH_TAB_CHANGES, WORKBENCH_TAB_FILES, type WorkerItem, WorkspaceDock, type WorkspaceDockProps, type WorkspaceEditConflict, type WorkspaceEditState, type WorkspaceMachine, type WorkspaceNotification, type WorkspaceTab, type XtermTheme, applyPatchOps, approvalsFromRequiresAction, argHint, buildTimeline, cn, composeSendInput, controlCaret, createDefaultToolRegistry, createToolRegistry, creditExhaustedFromEvents, defaultCommands, defaultToolRegistry, deriveMachineChip, execTruncated, extractSessionRef, filterCommands, firstMissingRequiredArg, formatAsOf, formatBytes, formatRelativeTime, gitFileDiffToPatch, groupTimeline, hasPermission, humanizeFailureReason, initialWorkspaceTab, isApplyPatch, isCodexAccountEvent, isCreditExhaustion, isExecSessionLostBanner, isGoalEvent, isLineageRefreshEvent, isTitleEvent, isTurnQueueEvent, languageForPath, looksBinary, matchCommand, parseCommandLine, parseExecBannerSessionId, parseToolArgs, projectPendingApprovals, rawTypeOf, redactSecrets, sandboxCommandExitCode, sessionDisplayTitle, sessionStatusFromEvents, shouldSteerOnKey, shouldSubmitOnKey, stringifyPayload, stripExecBanner, tailPeek, toolDisplayName, truncate, tryParseJson, unwrapMcpOutput, useAvailableModels, useBillingUsage, useCodexAccounts, useComposer, useDesktopStream, useEnvironments, useFileAttachments, useGoal, useLightbox, useLightboxOptional, useMachineChip, usePacks, useRelayFrameStream, useRig, useRigChanges, useRigVersions, useRigs, useSandboxFiles, useSandboxGit, useSandboxTerminal, useSandboxWorkspaceTabs, useScheduledTasks, useSession, useSessionCapabilities, useSessionControl, useSessionEvents, useSessionLineage, useSlashCommands, useTerminalStream, useTurnQueue, useVariableSets, useWorkspaceCapture, useWorkspaceEdit, useWorkspaceSessions, useWorkspaces, v4aToGitFileDiff, xtermThemeFromTokens };
2388
+ export { ActivityDisclosure, type ActivityDisclosureProps, ActivityItem, ActivityRail, type ActivityRailProps, AgentMessageItem, type ApplyPatchOperation, ApprovalSurface, type ApprovalSurfaceMessages, type ApprovalSurfaceProps, AuthNeededItem, BodyNote, CREDIT_EXHAUSTION_MESSAGE, CapturedFileUnavailableError, type CapturedFileUnavailableReason, ChatComposer, ChatComposerMessages, type ChatComposerProps, ClientOverride, CodeEditor, type CodeEditorProps, type CodexAccountsClientLike, CommandContext, CommandPalette, type CommandPaletteProps, ComposerState, ComposerTranscriptionControlProps, type CreateToolRegistryOptions, type DeriveMachineChipInput, DesktopViewer, type DesktopViewerProps, type DesktopWebSocketFactory, type DesktopWebSocketLike, type DiffTheme, DiffView, type DiffViewProps, type DisclosureChip, DisclosureDefaultsProvider, FileBrowser, type FileBrowserProps, type FileTreeNode, type FileTreeStatus, FileWriteConflictError, FleetTile, type FleetTileProps, type HumanInputAnswerDraft, HumanInputForm, type HumanInputFormMessages, type HumanInputFormProps, LightboxProvider, type MachineChip, type MachineChipState, Markdown, type MarkdownProps, MediaEmpty, MediaSkeleton, MessageTimeline, type MessageTimelineProps, ModelPicker, type ModelPickerProps, OpenGeniProvider, type OpenGeniProviderProps, type ParsedCommandLine, PayloadBlock, PendingApproval, PierreDiff, type PierreDiffProps, PierreFile, type PierreFileProps, QueueSurface, type QueueSurfaceProps, SESSION_STATUS_META, SandboxFiles, type SandboxFilesProps, type SandboxGitFileDiff, SandboxTerminal, type SandboxTerminalProps, SandboxWorkspace, type SandboxWorkspaceProps, type SandboxWorkspaceSurface, type SandboxWriteFileOptions, ScreenshotFigure, type SessionCapabilitiesState, SessionClientLike, SessionStatus, type SessionStatusMeta, type SessionStatusProps, SlashArg, SlashCommand, SlashCommandContext, StatusDot, type StatusDotProps, TermBlock, type TerminalChunk, type TerminalStreamStatus, Thumbnail, TimelineItem, TimelineRow, ToolCallItem, type ToolRegistry, type ToolRegistryEntry, type ToolRenderer, type ToolRendererProps, TurnOutcome, TurnSummary, type TurnSummaryProps, type UseAvailableModelsOptions, type UseAvailableModelsResult, type UseBillingUsageOptions, type UseBillingUsageResult, type UseCodexAccountsOptions, type UseCodexAccountsResult, type UseDesktopStreamOptions, type UseDesktopStreamResult, type UseEnvironmentsOptions, type UseEnvironmentsResult, UseFileAttachmentsResult, type UseGoalOptions, type UseGoalResult, type UseMachineChipOptions, type UsePacksOptions, type UsePacksResult, type UseRelayFrameStreamOptions, type UseRelayFrameStreamResult, type UseRigChangesOptions, type UseRigChangesResult, type UseRigOptions, type UseRigResult, type UseRigVersionsOptions, type UseRigVersionsResult, type UseRigsOptions, type UseRigsResult, type UseSandboxFilesOptions, type UseSandboxFilesResult, type UseSandboxGitOptions, type UseSandboxGitResult, type UseSandboxTerminalOptions, type UseSandboxTerminalResult, type UseSandboxWorkspaceTabsOptions, type UseSandboxWorkspaceTabsResult, type UseScheduledTasksOptions, type UseScheduledTasksResult, type UseSessionCapabilitiesOptions, type UseSessionCapabilitiesResult, type UseSessionLineageOptions, type UseSessionLineageResult, type UseSessionOptions, type UseSessionResult, type UseTerminalStreamOptions, type UseTerminalStreamResult, UseTurnQueueResult, type UseVariableSetsOptions, type UseVariableSetsResult, type UseWorkspaceCaptureOptions, type UseWorkspaceCaptureResult, type UseWorkspaceEditOptions, type UseWorkspaceEditResult, type UseWorkspaceSessionsOptions, type UseWorkspaceSessionsResult, type UseWorkspacesOptions, type UseWorkspacesResult, UserMessageItem, WORKBENCH_SURFACES, WORKBENCH_TAB_CHANGES, WORKBENCH_TAB_DESKTOP, WORKBENCH_TAB_FILES, WORKBENCH_TAB_TERMINAL, WorkspaceDock, type WorkspaceDockProps, type WorkspaceEditConflict, type WorkspaceEditState, type WorkspaceMachine, type WorkspaceNotification, type WorkspaceTab, type XtermTheme, answersFromDrafts, applyPatchOps, argHint, cn, controlCaret, createDefaultToolRegistry, createToolRegistry, defaultApprovalSurfaceMessages, defaultCommands, defaultHumanInputFormMessages, defaultToolRegistry, deriveMachineChip, execTruncated, filterCommands, firstMissingRequiredArg, formatAsOf, formatBytes, formatRelativeTime, gitFileDiffToPatch, hasPermission, humanizeFailureReason, initialWorkspaceTab, isApplyPatch, isCodexAccountEvent, isCreditExhaustion, isExecSessionLostBanner, isGoalEvent, isLineageRefreshEvent, isTitleEvent, languageForPath, looksBinary, matchCommand, parseCommandLine, parseExecBannerSessionId, parseToolArgs, rawTypeOf, redactSecrets, sandboxCommandExitCode, sessionDisplayTitle, stringifyPayload, stripExecBanner, tailPeek, truncate, tryParseJson, unwrapMcpOutput, useAvailableModels, useBillingUsage, useCodexAccounts, useDesktopStream, useEnvironments, useGoal, useLightbox, useLightboxOptional, useMachineChip, usePacks, useRelayFrameStream, useRig, useRigChanges, useRigVersions, useRigs, useSandboxFiles, useSandboxGit, useSandboxTerminal, useSandboxWorkspaceTabs, useScheduledTasks, useSession, useSessionCapabilities, useSessionLineage, useTerminalStream, useVariableSets, useWorkspaceCapture, useWorkspaceEdit, useWorkspaceSessions, useWorkspaces, v4aToGitFileDiff, xtermThemeFromTokens };