@jmoyers/harness 0.1.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 (214) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +145 -0
  3. package/native/ptyd/Cargo.lock +16 -0
  4. package/native/ptyd/Cargo.toml +7 -0
  5. package/native/ptyd/src/main.rs +257 -0
  6. package/package.json +90 -0
  7. package/scripts/build-ptyd.sh +73 -0
  8. package/scripts/control-plane-daemon.ts +277 -0
  9. package/scripts/cursor-hook-relay.ts +82 -0
  10. package/scripts/harness-animate.ts +469 -0
  11. package/scripts/harness-bin.js +77 -0
  12. package/scripts/harness-core.ts +1 -0
  13. package/scripts/harness-inspector.ts +439 -0
  14. package/scripts/harness.ts +2493 -0
  15. package/src/adapters/agent-session-state.ts +390 -0
  16. package/src/cli/gateway-record.ts +173 -0
  17. package/src/codex/live-session.ts +872 -0
  18. package/src/config/config-core.ts +1359 -0
  19. package/src/config/secrets-core.ts +170 -0
  20. package/src/control-plane/agent-realtime-api.ts +2441 -0
  21. package/src/control-plane/codex-session-stream.ts +392 -0
  22. package/src/control-plane/codex-telemetry.ts +1325 -0
  23. package/src/control-plane/lifecycle-hooks.ts +706 -0
  24. package/src/control-plane/session-summary.ts +380 -0
  25. package/src/control-plane/status/agent-status-reducer.ts +21 -0
  26. package/src/control-plane/status/reducer-base.ts +170 -0
  27. package/src/control-plane/status/reducers/claude-status-reducer.ts +37 -0
  28. package/src/control-plane/status/reducers/codex-status-reducer.ts +48 -0
  29. package/src/control-plane/status/reducers/critique-status-reducer.ts +15 -0
  30. package/src/control-plane/status/reducers/cursor-status-reducer.ts +37 -0
  31. package/src/control-plane/status/reducers/terminal-status-reducer.ts +15 -0
  32. package/src/control-plane/status/session-status-engine.ts +76 -0
  33. package/src/control-plane/stream-client.ts +396 -0
  34. package/src/control-plane/stream-command-parser.ts +1673 -0
  35. package/src/control-plane/stream-protocol.ts +1808 -0
  36. package/src/control-plane/stream-server-background.ts +486 -0
  37. package/src/control-plane/stream-server-command.ts +2557 -0
  38. package/src/control-plane/stream-server-connection.ts +234 -0
  39. package/src/control-plane/stream-server-observed-filter.ts +112 -0
  40. package/src/control-plane/stream-server-session-runtime.ts +566 -0
  41. package/src/control-plane/stream-server-state-store.ts +15 -0
  42. package/src/control-plane/stream-server.ts +3192 -0
  43. package/src/cursor/managed-hooks.ts +282 -0
  44. package/src/domain/conversations.ts +414 -0
  45. package/src/domain/directories.ts +78 -0
  46. package/src/domain/repositories.ts +123 -0
  47. package/src/domain/tasks.ts +148 -0
  48. package/src/domain/workspace.ts +156 -0
  49. package/src/events/normalized-events.ts +124 -0
  50. package/src/mux/ansi-integrity.ts +103 -0
  51. package/src/mux/control-plane-op-queue.ts +212 -0
  52. package/src/mux/conversation-rail.ts +339 -0
  53. package/src/mux/double-click.ts +78 -0
  54. package/src/mux/dual-pane-core.ts +435 -0
  55. package/src/mux/harness-core-ui.ts +817 -0
  56. package/src/mux/input-shortcuts.ts +667 -0
  57. package/src/mux/live-mux/actions-conversation.ts +344 -0
  58. package/src/mux/live-mux/actions-repository.ts +246 -0
  59. package/src/mux/live-mux/actions-task.ts +115 -0
  60. package/src/mux/live-mux/args.ts +142 -0
  61. package/src/mux/live-mux/command-menu.ts +298 -0
  62. package/src/mux/live-mux/control-plane-records.ts +546 -0
  63. package/src/mux/live-mux/conversation-state.ts +188 -0
  64. package/src/mux/live-mux/directory-resolution.ts +34 -0
  65. package/src/mux/live-mux/event-mapping.ts +96 -0
  66. package/src/mux/live-mux/gateway-profiler.ts +152 -0
  67. package/src/mux/live-mux/gateway-render-trace.ts +177 -0
  68. package/src/mux/live-mux/gateway-status-timeline.ts +166 -0
  69. package/src/mux/live-mux/git-parsing.ts +131 -0
  70. package/src/mux/live-mux/git-snapshot.ts +263 -0
  71. package/src/mux/live-mux/git-state.ts +136 -0
  72. package/src/mux/live-mux/global-shortcut-handlers.ts +143 -0
  73. package/src/mux/live-mux/home-pane-actions.ts +58 -0
  74. package/src/mux/live-mux/home-pane-drop.ts +44 -0
  75. package/src/mux/live-mux/home-pane-entity-click.ts +96 -0
  76. package/src/mux/live-mux/home-pane-pointer.ts +96 -0
  77. package/src/mux/live-mux/input-forwarding.ts +112 -0
  78. package/src/mux/live-mux/layout.ts +30 -0
  79. package/src/mux/live-mux/left-nav-activation.ts +103 -0
  80. package/src/mux/live-mux/left-nav.ts +85 -0
  81. package/src/mux/live-mux/left-rail-actions.ts +118 -0
  82. package/src/mux/live-mux/left-rail-conversation-click.ts +82 -0
  83. package/src/mux/live-mux/left-rail-pointer.ts +74 -0
  84. package/src/mux/live-mux/modal-command-menu-handler.ts +101 -0
  85. package/src/mux/live-mux/modal-conversation-handlers.ts +217 -0
  86. package/src/mux/live-mux/modal-input-reducers.ts +94 -0
  87. package/src/mux/live-mux/modal-overlays.ts +287 -0
  88. package/src/mux/live-mux/modal-pointer.ts +70 -0
  89. package/src/mux/live-mux/modal-prompt-handlers.ts +187 -0
  90. package/src/mux/live-mux/modal-task-editor-handler.ts +156 -0
  91. package/src/mux/live-mux/observed-stream.ts +87 -0
  92. package/src/mux/live-mux/palette-parsing.ts +128 -0
  93. package/src/mux/live-mux/pointer-routing.ts +108 -0
  94. package/src/mux/live-mux/process-usage.ts +53 -0
  95. package/src/mux/live-mux/project-pane-pointer.ts +44 -0
  96. package/src/mux/live-mux/rail-layout.ts +244 -0
  97. package/src/mux/live-mux/render-trace-analysis.ts +213 -0
  98. package/src/mux/live-mux/render-trace-state.ts +84 -0
  99. package/src/mux/live-mux/repository-folding.ts +207 -0
  100. package/src/mux/live-mux/runtime-shutdown.ts +51 -0
  101. package/src/mux/live-mux/selection.ts +411 -0
  102. package/src/mux/live-mux/startup-utils.ts +187 -0
  103. package/src/mux/live-mux/status-timeline-state.ts +82 -0
  104. package/src/mux/live-mux/task-pane-shortcuts.ts +206 -0
  105. package/src/mux/live-mux/terminal-palette.ts +79 -0
  106. package/src/mux/new-thread-prompt.ts +165 -0
  107. package/src/mux/project-tree.ts +295 -0
  108. package/src/mux/render-frame.ts +113 -0
  109. package/src/mux/runtime-wiring.ts +185 -0
  110. package/src/mux/selector-index.ts +160 -0
  111. package/src/mux/startup-sequencer.ts +238 -0
  112. package/src/mux/task-composer.ts +289 -0
  113. package/src/mux/task-focused-pane.ts +417 -0
  114. package/src/mux/task-screen-keybindings.ts +539 -0
  115. package/src/mux/terminal-input-modes.ts +35 -0
  116. package/src/mux/workspace-path.ts +55 -0
  117. package/src/mux/workspace-rail-model.ts +701 -0
  118. package/src/mux/workspace-rail.ts +247 -0
  119. package/src/perf/perf-core.ts +307 -0
  120. package/src/pty/pty_host.ts +217 -0
  121. package/src/pty/session-broker.ts +158 -0
  122. package/src/recording/terminal-recording.ts +383 -0
  123. package/src/services/control-plane.ts +567 -0
  124. package/src/services/conversation-lifecycle.ts +176 -0
  125. package/src/services/conversation-startup-hydration.ts +47 -0
  126. package/src/services/directory-hydration.ts +49 -0
  127. package/src/services/event-persistence.ts +104 -0
  128. package/src/services/mux-ui-state-persistence.ts +82 -0
  129. package/src/services/output-load-sampler.ts +231 -0
  130. package/src/services/process-usage-refresh.ts +88 -0
  131. package/src/services/recording.ts +75 -0
  132. package/src/services/render-trace-recorder.ts +177 -0
  133. package/src/services/runtime-control-actions.ts +123 -0
  134. package/src/services/runtime-control-plane-ops.ts +131 -0
  135. package/src/services/runtime-conversation-actions.ts +113 -0
  136. package/src/services/runtime-conversation-activation.ts +78 -0
  137. package/src/services/runtime-conversation-starter.ts +171 -0
  138. package/src/services/runtime-conversation-title-edit.ts +149 -0
  139. package/src/services/runtime-directory-actions.ts +164 -0
  140. package/src/services/runtime-envelope-handler.ts +198 -0
  141. package/src/services/runtime-git-state.ts +92 -0
  142. package/src/services/runtime-input-pipeline.ts +50 -0
  143. package/src/services/runtime-input-router.ts +202 -0
  144. package/src/services/runtime-layout-resize.ts +236 -0
  145. package/src/services/runtime-left-rail-render.ts +159 -0
  146. package/src/services/runtime-main-pane-input.ts +230 -0
  147. package/src/services/runtime-modal-input.ts +119 -0
  148. package/src/services/runtime-navigation-input.ts +207 -0
  149. package/src/services/runtime-process-wiring.ts +68 -0
  150. package/src/services/runtime-rail-input.ts +287 -0
  151. package/src/services/runtime-render-flush.ts +146 -0
  152. package/src/services/runtime-render-lifecycle.ts +104 -0
  153. package/src/services/runtime-render-orchestrator.ts +108 -0
  154. package/src/services/runtime-render-pipeline.ts +167 -0
  155. package/src/services/runtime-render-state.ts +72 -0
  156. package/src/services/runtime-repository-actions.ts +197 -0
  157. package/src/services/runtime-right-pane-render.ts +132 -0
  158. package/src/services/runtime-shutdown.ts +79 -0
  159. package/src/services/runtime-stream-subscriptions.ts +56 -0
  160. package/src/services/runtime-task-composer-persistence.ts +139 -0
  161. package/src/services/runtime-task-editor-actions.ts +83 -0
  162. package/src/services/runtime-task-pane-actions.ts +198 -0
  163. package/src/services/runtime-task-pane-shortcuts.ts +189 -0
  164. package/src/services/runtime-task-pane.ts +62 -0
  165. package/src/services/runtime-workspace-actions.ts +153 -0
  166. package/src/services/runtime-workspace-observed-events.ts +190 -0
  167. package/src/services/session-projection-instrumentation.ts +190 -0
  168. package/src/services/startup-background-probe.ts +91 -0
  169. package/src/services/startup-background-resume.ts +65 -0
  170. package/src/services/startup-orchestrator.ts +166 -0
  171. package/src/services/startup-output-tracker.ts +54 -0
  172. package/src/services/startup-paint-tracker.ts +115 -0
  173. package/src/services/startup-persisted-conversation-queue.ts +45 -0
  174. package/src/services/startup-settled-gate.ts +67 -0
  175. package/src/services/startup-shutdown.ts +53 -0
  176. package/src/services/startup-span-tracker.ts +77 -0
  177. package/src/services/startup-state-hydration.ts +94 -0
  178. package/src/services/startup-visibility.ts +35 -0
  179. package/src/services/status-timeline-recorder.ts +144 -0
  180. package/src/services/task-pane-selection-actions.ts +153 -0
  181. package/src/services/task-planning-hydration.ts +58 -0
  182. package/src/services/task-planning-observed-events.ts +89 -0
  183. package/src/services/workspace-observed-events.ts +113 -0
  184. package/src/store/control-plane-store-normalize.ts +760 -0
  185. package/src/store/control-plane-store-types.ts +224 -0
  186. package/src/store/control-plane-store.ts +2951 -0
  187. package/src/store/event-store.ts +253 -0
  188. package/src/store/sqlite.ts +81 -0
  189. package/src/terminal/compat-matrix.ts +345 -0
  190. package/src/terminal/differential-checkpoints.ts +132 -0
  191. package/src/terminal/parity-suite.ts +441 -0
  192. package/src/terminal/snapshot-oracle.ts +1840 -0
  193. package/src/ui/conversation-input-forwarder.ts +114 -0
  194. package/src/ui/conversation-selection-input.ts +103 -0
  195. package/src/ui/debug-footer-notice.ts +39 -0
  196. package/src/ui/global-shortcut-input.ts +126 -0
  197. package/src/ui/input-preflight.ts +68 -0
  198. package/src/ui/input-token-router.ts +312 -0
  199. package/src/ui/input.ts +238 -0
  200. package/src/ui/kit.ts +509 -0
  201. package/src/ui/left-nav-input.ts +80 -0
  202. package/src/ui/left-rail-pointer-input.ts +148 -0
  203. package/src/ui/main-pane-pointer-input.ts +150 -0
  204. package/src/ui/modals/manager.ts +192 -0
  205. package/src/ui/mux-theme.ts +529 -0
  206. package/src/ui/panes/conversation.ts +19 -0
  207. package/src/ui/panes/home-gridfire.ts +302 -0
  208. package/src/ui/panes/home.ts +109 -0
  209. package/src/ui/panes/left-rail.ts +12 -0
  210. package/src/ui/panes/project.ts +44 -0
  211. package/src/ui/pointer-routing-input.ts +158 -0
  212. package/src/ui/repository-fold-input.ts +91 -0
  213. package/src/ui/screen.ts +210 -0
  214. package/src/ui/surface.ts +224 -0
@@ -0,0 +1,1808 @@
1
+ import type { PtyExit } from '../pty/pty_host.ts';
2
+ import { parseStreamCommand } from './stream-command-parser.ts';
3
+
4
+ export type StreamSignal = 'interrupt' | 'eof' | 'terminate';
5
+ export type StreamSessionRuntimeStatus = 'running' | 'needs-input' | 'completed' | 'exited';
6
+ export type StreamSessionListSort = 'attention-first' | 'started-desc' | 'started-asc';
7
+ export type StreamTelemetrySource = 'otlp-log' | 'otlp-metric' | 'otlp-trace' | 'history';
8
+ export type StreamTelemetryStatusHint = 'running' | 'completed' | 'needs-input';
9
+ export type StreamSessionControllerType = 'human' | 'agent' | 'automation';
10
+ export type StreamSessionDisplayPhase = 'needs-action' | 'starting' | 'working' | 'idle' | 'exited';
11
+
12
+ export interface StreamSessionController {
13
+ controllerId: string;
14
+ controllerType: StreamSessionControllerType;
15
+ controllerLabel: string | null;
16
+ claimedAt: string;
17
+ }
18
+
19
+ export interface StreamTelemetrySummary {
20
+ source: StreamTelemetrySource;
21
+ eventName: string | null;
22
+ severity: string | null;
23
+ summary: string | null;
24
+ observedAt: string;
25
+ }
26
+
27
+ export interface StreamSessionStatusModel {
28
+ runtimeStatus: StreamSessionRuntimeStatus;
29
+ phase: StreamSessionDisplayPhase;
30
+ glyph: '▲' | '◔' | '◆' | '○' | '■';
31
+ badge: 'NEED' | 'RUN ' | 'DONE' | 'EXIT';
32
+ detailText: string;
33
+ attentionReason: string | null;
34
+ lastKnownWork: string | null;
35
+ lastKnownWorkAt: string | null;
36
+ phaseHint: 'needs-action' | 'working' | 'idle' | null;
37
+ observedAt: string;
38
+ }
39
+
40
+ export interface StreamSessionKeyEventRecord {
41
+ source: StreamTelemetrySource;
42
+ eventName: string | null;
43
+ severity: string | null;
44
+ summary: string | null;
45
+ observedAt: string;
46
+ statusHint: StreamTelemetryStatusHint | null;
47
+ }
48
+
49
+ interface DirectoryUpsertCommand {
50
+ type: 'directory.upsert';
51
+ directoryId?: string;
52
+ tenantId?: string;
53
+ userId?: string;
54
+ workspaceId?: string;
55
+ path: string;
56
+ }
57
+
58
+ interface DirectoryListCommand {
59
+ type: 'directory.list';
60
+ tenantId?: string;
61
+ userId?: string;
62
+ workspaceId?: string;
63
+ includeArchived?: boolean;
64
+ limit?: number;
65
+ }
66
+
67
+ interface DirectoryArchiveCommand {
68
+ type: 'directory.archive';
69
+ directoryId: string;
70
+ }
71
+
72
+ interface DirectoryGitStatusListCommand {
73
+ type: 'directory.git-status';
74
+ tenantId?: string;
75
+ userId?: string;
76
+ workspaceId?: string;
77
+ directoryId?: string;
78
+ }
79
+
80
+ interface ConversationCreateCommand {
81
+ type: 'conversation.create';
82
+ conversationId?: string;
83
+ directoryId: string;
84
+ title: string;
85
+ agentType: string;
86
+ adapterState?: Record<string, unknown>;
87
+ }
88
+
89
+ interface ConversationListCommand {
90
+ type: 'conversation.list';
91
+ directoryId?: string;
92
+ tenantId?: string;
93
+ userId?: string;
94
+ workspaceId?: string;
95
+ includeArchived?: boolean;
96
+ limit?: number;
97
+ }
98
+
99
+ interface ConversationArchiveCommand {
100
+ type: 'conversation.archive';
101
+ conversationId: string;
102
+ }
103
+
104
+ interface ConversationUpdateCommand {
105
+ type: 'conversation.update';
106
+ conversationId: string;
107
+ title: string;
108
+ }
109
+
110
+ interface ConversationDeleteCommand {
111
+ type: 'conversation.delete';
112
+ conversationId: string;
113
+ }
114
+
115
+ type StreamTaskStatus = 'draft' | 'ready' | 'in-progress' | 'completed';
116
+ type StreamTaskScopeKind = 'global' | 'repository' | 'project';
117
+ type StreamTaskLinearPriority = 0 | 1 | 2 | 3 | 4;
118
+ type StreamProjectTaskFocusMode = 'balanced' | 'own-only';
119
+ type StreamProjectThreadSpawnMode = 'new-thread' | 'reuse-thread';
120
+ type StreamAutomationPolicyScope = 'global' | 'repository' | 'project';
121
+
122
+ interface StreamTaskLinearInput {
123
+ issueId?: string | null;
124
+ identifier?: string | null;
125
+ url?: string | null;
126
+ teamId?: string | null;
127
+ projectId?: string | null;
128
+ projectMilestoneId?: string | null;
129
+ cycleId?: string | null;
130
+ stateId?: string | null;
131
+ assigneeId?: string | null;
132
+ priority?: StreamTaskLinearPriority | null;
133
+ estimate?: number | null;
134
+ dueDate?: string | null;
135
+ labelIds?: readonly string[] | null;
136
+ }
137
+
138
+ interface RepositoryUpsertCommand {
139
+ type: 'repository.upsert';
140
+ repositoryId?: string;
141
+ tenantId?: string;
142
+ userId?: string;
143
+ workspaceId?: string;
144
+ name: string;
145
+ remoteUrl: string;
146
+ defaultBranch?: string;
147
+ metadata?: Record<string, unknown>;
148
+ }
149
+
150
+ interface RepositoryGetCommand {
151
+ type: 'repository.get';
152
+ repositoryId: string;
153
+ }
154
+
155
+ interface RepositoryListCommand {
156
+ type: 'repository.list';
157
+ tenantId?: string;
158
+ userId?: string;
159
+ workspaceId?: string;
160
+ includeArchived?: boolean;
161
+ limit?: number;
162
+ }
163
+
164
+ interface RepositoryUpdateCommand {
165
+ type: 'repository.update';
166
+ repositoryId: string;
167
+ name?: string;
168
+ remoteUrl?: string;
169
+ defaultBranch?: string;
170
+ metadata?: Record<string, unknown>;
171
+ }
172
+
173
+ interface RepositoryArchiveCommand {
174
+ type: 'repository.archive';
175
+ repositoryId: string;
176
+ }
177
+
178
+ interface TaskCreateCommand {
179
+ type: 'task.create';
180
+ taskId?: string;
181
+ tenantId?: string;
182
+ userId?: string;
183
+ workspaceId?: string;
184
+ repositoryId?: string;
185
+ projectId?: string;
186
+ title: string;
187
+ description?: string;
188
+ linear?: StreamTaskLinearInput;
189
+ }
190
+
191
+ interface TaskGetCommand {
192
+ type: 'task.get';
193
+ taskId: string;
194
+ }
195
+
196
+ interface TaskListCommand {
197
+ type: 'task.list';
198
+ tenantId?: string;
199
+ userId?: string;
200
+ workspaceId?: string;
201
+ repositoryId?: string;
202
+ projectId?: string;
203
+ scopeKind?: StreamTaskScopeKind;
204
+ status?: StreamTaskStatus;
205
+ limit?: number;
206
+ }
207
+
208
+ interface TaskUpdateCommand {
209
+ type: 'task.update';
210
+ taskId: string;
211
+ title?: string;
212
+ description?: string;
213
+ repositoryId?: string | null;
214
+ projectId?: string | null;
215
+ linear?: StreamTaskLinearInput | null;
216
+ }
217
+
218
+ interface TaskDeleteCommand {
219
+ type: 'task.delete';
220
+ taskId: string;
221
+ }
222
+
223
+ interface TaskClaimCommand {
224
+ type: 'task.claim';
225
+ taskId: string;
226
+ controllerId: string;
227
+ directoryId?: string;
228
+ branchName?: string;
229
+ baseBranch?: string;
230
+ }
231
+
232
+ interface TaskPullCommand {
233
+ type: 'task.pull';
234
+ tenantId?: string;
235
+ userId?: string;
236
+ workspaceId?: string;
237
+ controllerId: string;
238
+ directoryId?: string;
239
+ repositoryId?: string;
240
+ branchName?: string;
241
+ baseBranch?: string;
242
+ }
243
+
244
+ interface TaskCompleteCommand {
245
+ type: 'task.complete';
246
+ taskId: string;
247
+ }
248
+
249
+ interface TaskQueueCommand {
250
+ type: 'task.queue';
251
+ taskId: string;
252
+ }
253
+
254
+ interface TaskReadyCommand {
255
+ type: 'task.ready';
256
+ taskId: string;
257
+ }
258
+
259
+ interface TaskDraftCommand {
260
+ type: 'task.draft';
261
+ taskId: string;
262
+ }
263
+
264
+ interface TaskReorderCommand {
265
+ type: 'task.reorder';
266
+ tenantId: string;
267
+ userId: string;
268
+ workspaceId: string;
269
+ orderedTaskIds: string[];
270
+ }
271
+
272
+ interface ProjectSettingsGetCommand {
273
+ type: 'project.settings-get';
274
+ directoryId: string;
275
+ }
276
+
277
+ interface ProjectSettingsUpdateCommand {
278
+ type: 'project.settings-update';
279
+ directoryId: string;
280
+ pinnedBranch?: string | null;
281
+ taskFocusMode?: StreamProjectTaskFocusMode;
282
+ threadSpawnMode?: StreamProjectThreadSpawnMode;
283
+ }
284
+
285
+ interface ProjectStatusCommand {
286
+ type: 'project.status';
287
+ directoryId: string;
288
+ }
289
+
290
+ interface AutomationPolicyGetCommand {
291
+ type: 'automation.policy-get';
292
+ tenantId?: string;
293
+ userId?: string;
294
+ workspaceId?: string;
295
+ scope: StreamAutomationPolicyScope;
296
+ scopeId?: string;
297
+ }
298
+
299
+ interface AutomationPolicySetCommand {
300
+ type: 'automation.policy-set';
301
+ tenantId?: string;
302
+ userId?: string;
303
+ workspaceId?: string;
304
+ scope: StreamAutomationPolicyScope;
305
+ scopeId?: string;
306
+ automationEnabled?: boolean;
307
+ frozen?: boolean;
308
+ }
309
+
310
+ interface GitHubProjectPrCommand {
311
+ type: 'github.project-pr';
312
+ directoryId: string;
313
+ }
314
+
315
+ interface GitHubPrListCommand {
316
+ type: 'github.pr-list';
317
+ tenantId?: string;
318
+ userId?: string;
319
+ workspaceId?: string;
320
+ repositoryId?: string;
321
+ directoryId?: string;
322
+ headBranch?: string;
323
+ state?: 'open' | 'closed';
324
+ limit?: number;
325
+ }
326
+
327
+ interface GitHubPrCreateCommand {
328
+ type: 'github.pr-create';
329
+ directoryId: string;
330
+ title?: string;
331
+ body?: string;
332
+ baseBranch?: string;
333
+ headBranch?: string;
334
+ draft?: boolean;
335
+ }
336
+
337
+ interface GitHubPrJobsListCommand {
338
+ type: 'github.pr-jobs-list';
339
+ tenantId?: string;
340
+ userId?: string;
341
+ workspaceId?: string;
342
+ repositoryId?: string;
343
+ prRecordId?: string;
344
+ limit?: number;
345
+ }
346
+
347
+ interface GitHubRepoMyPrsUrlCommand {
348
+ type: 'github.repo-my-prs-url';
349
+ repositoryId: string;
350
+ }
351
+
352
+ interface StreamSubscribeCommand {
353
+ type: 'stream.subscribe';
354
+ tenantId?: string;
355
+ userId?: string;
356
+ workspaceId?: string;
357
+ repositoryId?: string;
358
+ taskId?: string;
359
+ directoryId?: string;
360
+ conversationId?: string;
361
+ includeOutput?: boolean;
362
+ afterCursor?: number;
363
+ }
364
+
365
+ interface StreamUnsubscribeCommand {
366
+ type: 'stream.unsubscribe';
367
+ subscriptionId: string;
368
+ }
369
+
370
+ interface SessionListCommand {
371
+ type: 'session.list';
372
+ tenantId?: string;
373
+ userId?: string;
374
+ workspaceId?: string;
375
+ worktreeId?: string;
376
+ status?: StreamSessionRuntimeStatus;
377
+ live?: boolean;
378
+ sort?: StreamSessionListSort;
379
+ limit?: number;
380
+ }
381
+
382
+ interface AttentionListCommand {
383
+ type: 'attention.list';
384
+ }
385
+
386
+ interface SessionStatusCommand {
387
+ type: 'session.status';
388
+ sessionId: string;
389
+ }
390
+
391
+ interface SessionSnapshotCommand {
392
+ type: 'session.snapshot';
393
+ sessionId: string;
394
+ tailLines?: number;
395
+ }
396
+
397
+ interface SessionRespondCommand {
398
+ type: 'session.respond';
399
+ sessionId: string;
400
+ text: string;
401
+ }
402
+
403
+ interface SessionClaimCommand {
404
+ type: 'session.claim';
405
+ sessionId: string;
406
+ controllerId: string;
407
+ controllerType: StreamSessionControllerType;
408
+ controllerLabel?: string;
409
+ reason?: string;
410
+ takeover?: boolean;
411
+ }
412
+
413
+ interface SessionReleaseCommand {
414
+ type: 'session.release';
415
+ sessionId: string;
416
+ reason?: string;
417
+ }
418
+
419
+ interface SessionInterruptCommand {
420
+ type: 'session.interrupt';
421
+ sessionId: string;
422
+ }
423
+
424
+ interface SessionRemoveCommand {
425
+ type: 'session.remove';
426
+ sessionId: string;
427
+ }
428
+
429
+ interface PtyStartCommand {
430
+ type: 'pty.start';
431
+ sessionId: string;
432
+ args: string[];
433
+ env?: Record<string, string>;
434
+ cwd?: string;
435
+ initialCols: number;
436
+ initialRows: number;
437
+ terminalForegroundHex?: string;
438
+ terminalBackgroundHex?: string;
439
+ tenantId?: string;
440
+ userId?: string;
441
+ workspaceId?: string;
442
+ worktreeId?: string;
443
+ }
444
+
445
+ interface PtyAttachCommand {
446
+ type: 'pty.attach';
447
+ sessionId: string;
448
+ sinceCursor?: number;
449
+ }
450
+
451
+ interface PtyDetachCommand {
452
+ type: 'pty.detach';
453
+ sessionId: string;
454
+ }
455
+
456
+ interface PtySubscribeEventsCommand {
457
+ type: 'pty.subscribe-events';
458
+ sessionId: string;
459
+ }
460
+
461
+ interface PtyUnsubscribeEventsCommand {
462
+ type: 'pty.unsubscribe-events';
463
+ sessionId: string;
464
+ }
465
+
466
+ interface PtyCloseCommand {
467
+ type: 'pty.close';
468
+ sessionId: string;
469
+ }
470
+
471
+ export type StreamCommand =
472
+ | DirectoryUpsertCommand
473
+ | DirectoryListCommand
474
+ | DirectoryArchiveCommand
475
+ | DirectoryGitStatusListCommand
476
+ | ConversationCreateCommand
477
+ | ConversationListCommand
478
+ | ConversationArchiveCommand
479
+ | ConversationUpdateCommand
480
+ | ConversationDeleteCommand
481
+ | RepositoryUpsertCommand
482
+ | RepositoryGetCommand
483
+ | RepositoryListCommand
484
+ | RepositoryUpdateCommand
485
+ | RepositoryArchiveCommand
486
+ | TaskCreateCommand
487
+ | TaskGetCommand
488
+ | TaskListCommand
489
+ | TaskUpdateCommand
490
+ | TaskDeleteCommand
491
+ | TaskClaimCommand
492
+ | TaskPullCommand
493
+ | TaskCompleteCommand
494
+ | TaskQueueCommand
495
+ | TaskReadyCommand
496
+ | TaskDraftCommand
497
+ | TaskReorderCommand
498
+ | ProjectSettingsGetCommand
499
+ | ProjectSettingsUpdateCommand
500
+ | ProjectStatusCommand
501
+ | AutomationPolicyGetCommand
502
+ | AutomationPolicySetCommand
503
+ | GitHubProjectPrCommand
504
+ | GitHubPrListCommand
505
+ | GitHubPrCreateCommand
506
+ | GitHubPrJobsListCommand
507
+ | GitHubRepoMyPrsUrlCommand
508
+ | StreamSubscribeCommand
509
+ | StreamUnsubscribeCommand
510
+ | SessionListCommand
511
+ | AttentionListCommand
512
+ | SessionStatusCommand
513
+ | SessionSnapshotCommand
514
+ | SessionRespondCommand
515
+ | SessionClaimCommand
516
+ | SessionReleaseCommand
517
+ | SessionInterruptCommand
518
+ | SessionRemoveCommand
519
+ | PtyStartCommand
520
+ | PtyAttachCommand
521
+ | PtyDetachCommand
522
+ | PtySubscribeEventsCommand
523
+ | PtyUnsubscribeEventsCommand
524
+ | PtyCloseCommand;
525
+
526
+ export interface StreamCommandEnvelope {
527
+ kind: 'command';
528
+ commandId: string;
529
+ command: StreamCommand;
530
+ }
531
+
532
+ interface StreamAuthEnvelope {
533
+ kind: 'auth';
534
+ token: string;
535
+ }
536
+
537
+ interface StreamInputEnvelope {
538
+ kind: 'pty.input';
539
+ sessionId: string;
540
+ dataBase64: string;
541
+ }
542
+
543
+ interface StreamResizeEnvelope {
544
+ kind: 'pty.resize';
545
+ sessionId: string;
546
+ cols: number;
547
+ rows: number;
548
+ }
549
+
550
+ interface StreamSignalEnvelope {
551
+ kind: 'pty.signal';
552
+ sessionId: string;
553
+ signal: StreamSignal;
554
+ }
555
+
556
+ export type StreamClientEnvelope =
557
+ | StreamAuthEnvelope
558
+ | StreamCommandEnvelope
559
+ | StreamInputEnvelope
560
+ | StreamResizeEnvelope
561
+ | StreamSignalEnvelope;
562
+
563
+ interface StreamNotifyRecord {
564
+ ts: string;
565
+ payload: Record<string, unknown>;
566
+ }
567
+
568
+ export type StreamSessionEvent =
569
+ | {
570
+ type: 'notify';
571
+ record: StreamNotifyRecord;
572
+ }
573
+ | {
574
+ type: 'session-exit';
575
+ exit: PtyExit;
576
+ };
577
+
578
+ export type StreamObservedEvent =
579
+ | {
580
+ type: 'directory-upserted';
581
+ directory: Record<string, unknown>;
582
+ }
583
+ | {
584
+ type: 'directory-archived';
585
+ directoryId: string;
586
+ ts: string;
587
+ }
588
+ | {
589
+ type: 'directory-git-updated';
590
+ directoryId: string;
591
+ summary: {
592
+ branch: string;
593
+ changedFiles: number;
594
+ additions: number;
595
+ deletions: number;
596
+ };
597
+ repositorySnapshot: {
598
+ normalizedRemoteUrl: string | null;
599
+ commitCount: number | null;
600
+ lastCommitAt: string | null;
601
+ shortCommitHash: string | null;
602
+ inferredName: string | null;
603
+ defaultBranch: string | null;
604
+ };
605
+ repositoryId: string | null;
606
+ repository: Record<string, unknown> | null;
607
+ observedAt: string;
608
+ }
609
+ | {
610
+ type: 'conversation-created';
611
+ conversation: Record<string, unknown>;
612
+ }
613
+ | {
614
+ type: 'conversation-updated';
615
+ conversation: Record<string, unknown>;
616
+ }
617
+ | {
618
+ type: 'conversation-archived';
619
+ conversationId: string;
620
+ ts: string;
621
+ }
622
+ | {
623
+ type: 'conversation-deleted';
624
+ conversationId: string;
625
+ ts: string;
626
+ }
627
+ | {
628
+ type: 'repository-upserted';
629
+ repository: Record<string, unknown>;
630
+ }
631
+ | {
632
+ type: 'repository-updated';
633
+ repository: Record<string, unknown>;
634
+ }
635
+ | {
636
+ type: 'repository-archived';
637
+ repositoryId: string;
638
+ ts: string;
639
+ }
640
+ | {
641
+ type: 'task-created';
642
+ task: Record<string, unknown>;
643
+ }
644
+ | {
645
+ type: 'task-updated';
646
+ task: Record<string, unknown>;
647
+ }
648
+ | {
649
+ type: 'task-deleted';
650
+ taskId: string;
651
+ ts: string;
652
+ }
653
+ | {
654
+ type: 'task-reordered';
655
+ tasks: Record<string, unknown>[];
656
+ ts: string;
657
+ }
658
+ | {
659
+ type: 'github-pr-upserted';
660
+ pr: Record<string, unknown>;
661
+ }
662
+ | {
663
+ type: 'github-pr-closed';
664
+ prRecordId: string;
665
+ repositoryId: string;
666
+ ts: string;
667
+ }
668
+ | {
669
+ type: 'github-pr-jobs-updated';
670
+ prRecordId: string;
671
+ repositoryId: string;
672
+ ciRollup: 'pending' | 'success' | 'failure' | 'cancelled' | 'neutral' | 'none';
673
+ jobs: Record<string, unknown>[];
674
+ ts: string;
675
+ }
676
+ | {
677
+ type: 'session-status';
678
+ sessionId: string;
679
+ status: StreamSessionRuntimeStatus;
680
+ attentionReason: string | null;
681
+ statusModel: StreamSessionStatusModel | null;
682
+ live: boolean;
683
+ ts: string;
684
+ directoryId: string | null;
685
+ conversationId: string | null;
686
+ telemetry: StreamTelemetrySummary | null;
687
+ controller: StreamSessionController | null;
688
+ }
689
+ | {
690
+ type: 'session-event';
691
+ sessionId: string;
692
+ event: StreamSessionEvent;
693
+ ts: string;
694
+ directoryId: string | null;
695
+ conversationId: string | null;
696
+ }
697
+ | {
698
+ type: 'session-key-event';
699
+ sessionId: string;
700
+ keyEvent: StreamSessionKeyEventRecord;
701
+ ts: string;
702
+ directoryId: string | null;
703
+ conversationId: string | null;
704
+ }
705
+ | {
706
+ type: 'session-control';
707
+ sessionId: string;
708
+ action: 'claimed' | 'released' | 'taken-over';
709
+ controller: StreamSessionController | null;
710
+ previousController: StreamSessionController | null;
711
+ reason: string | null;
712
+ ts: string;
713
+ directoryId: string | null;
714
+ conversationId: string | null;
715
+ }
716
+ | {
717
+ type: 'session-output';
718
+ sessionId: string;
719
+ outputCursor: number;
720
+ chunkBase64: string;
721
+ ts: string;
722
+ directoryId: string | null;
723
+ conversationId: string | null;
724
+ };
725
+
726
+ interface StreamCommandAcceptedEnvelope {
727
+ kind: 'command.accepted';
728
+ commandId: string;
729
+ }
730
+
731
+ interface StreamAuthOkEnvelope {
732
+ kind: 'auth.ok';
733
+ }
734
+
735
+ interface StreamAuthErrorEnvelope {
736
+ kind: 'auth.error';
737
+ error: string;
738
+ }
739
+
740
+ interface StreamCommandCompletedEnvelope {
741
+ kind: 'command.completed';
742
+ commandId: string;
743
+ result: Record<string, unknown>;
744
+ }
745
+
746
+ interface StreamCommandFailedEnvelope {
747
+ kind: 'command.failed';
748
+ commandId: string;
749
+ error: string;
750
+ }
751
+
752
+ interface StreamPtyOutputEnvelope {
753
+ kind: 'pty.output';
754
+ sessionId: string;
755
+ cursor: number;
756
+ chunkBase64: string;
757
+ }
758
+
759
+ interface StreamPtyExitEnvelope {
760
+ kind: 'pty.exit';
761
+ sessionId: string;
762
+ exit: PtyExit;
763
+ }
764
+
765
+ interface StreamPtyEventEnvelope {
766
+ kind: 'pty.event';
767
+ sessionId: string;
768
+ event: StreamSessionEvent;
769
+ }
770
+
771
+ interface StreamObservedEventEnvelope {
772
+ kind: 'stream.event';
773
+ subscriptionId: string;
774
+ cursor: number;
775
+ event: StreamObservedEvent;
776
+ }
777
+
778
+ export type StreamServerEnvelope =
779
+ | StreamAuthOkEnvelope
780
+ | StreamAuthErrorEnvelope
781
+ | StreamCommandAcceptedEnvelope
782
+ | StreamCommandCompletedEnvelope
783
+ | StreamCommandFailedEnvelope
784
+ | StreamPtyOutputEnvelope
785
+ | StreamPtyExitEnvelope
786
+ | StreamPtyEventEnvelope
787
+ | StreamObservedEventEnvelope;
788
+
789
+ interface ConsumedJsonLines {
790
+ messages: unknown[];
791
+ remainder: string;
792
+ }
793
+
794
+ export function encodeStreamEnvelope(
795
+ envelope: StreamClientEnvelope | StreamServerEnvelope,
796
+ ): string {
797
+ return `${JSON.stringify(envelope)}\n`;
798
+ }
799
+
800
+ export function consumeJsonLines(buffer: string): ConsumedJsonLines {
801
+ const lines = buffer.split('\n');
802
+ const remainder = lines.pop() as string;
803
+ const messages: unknown[] = [];
804
+
805
+ for (const line of lines) {
806
+ const trimmed = line.trim();
807
+ if (trimmed.length === 0) {
808
+ continue;
809
+ }
810
+ try {
811
+ messages.push(JSON.parse(trimmed));
812
+ } catch {
813
+ // Invalid lines are ignored so malformed peers cannot break stream processing.
814
+ }
815
+ }
816
+
817
+ return {
818
+ messages,
819
+ remainder,
820
+ };
821
+ }
822
+
823
+ function asRecord(value: unknown): Record<string, unknown> | null {
824
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) {
825
+ return null;
826
+ }
827
+ return value as Record<string, unknown>;
828
+ }
829
+
830
+ function readString(value: unknown): string | null {
831
+ return typeof value === 'string' ? value : null;
832
+ }
833
+
834
+ function readNumber(value: unknown): number | null {
835
+ return typeof value === 'number' && Number.isFinite(value) ? value : null;
836
+ }
837
+
838
+ function readBoolean(value: unknown): boolean | null {
839
+ return typeof value === 'boolean' ? value : null;
840
+ }
841
+
842
+ function readSignalName(value: unknown): NodeJS.Signals | null {
843
+ if (typeof value !== 'string') {
844
+ return null;
845
+ }
846
+ if (!/^SIG[A-Z0-9]+(?:_[A-Z0-9]+)*$/.test(value)) {
847
+ return null;
848
+ }
849
+ return value as NodeJS.Signals;
850
+ }
851
+
852
+ export function parseClientEnvelope(value: unknown): StreamClientEnvelope | null {
853
+ const record = asRecord(value);
854
+ if (record === null) {
855
+ return null;
856
+ }
857
+
858
+ const kind = readString(record['kind']);
859
+ if (kind === null) {
860
+ return null;
861
+ }
862
+
863
+ if (kind === 'auth') {
864
+ const token = readString(record['token']);
865
+ if (token === null) {
866
+ return null;
867
+ }
868
+ return {
869
+ kind,
870
+ token,
871
+ };
872
+ }
873
+
874
+ if (kind === 'command') {
875
+ const commandId = readString(record['commandId']);
876
+ const command = parseStreamCommand(record['command']);
877
+ if (commandId === null || command === null) {
878
+ return null;
879
+ }
880
+
881
+ return {
882
+ kind,
883
+ commandId,
884
+ command,
885
+ };
886
+ }
887
+
888
+ const sessionId = readString(record['sessionId']);
889
+ if (sessionId === null) {
890
+ return null;
891
+ }
892
+
893
+ if (kind === 'pty.input') {
894
+ const dataBase64 = readString(record['dataBase64']);
895
+ if (dataBase64 === null) {
896
+ return null;
897
+ }
898
+ return {
899
+ kind,
900
+ sessionId,
901
+ dataBase64,
902
+ };
903
+ }
904
+
905
+ if (kind === 'pty.resize') {
906
+ const cols = readNumber(record['cols']);
907
+ const rows = readNumber(record['rows']);
908
+ if (cols === null || rows === null) {
909
+ return null;
910
+ }
911
+ return {
912
+ kind,
913
+ sessionId,
914
+ cols,
915
+ rows,
916
+ };
917
+ }
918
+
919
+ if (kind === 'pty.signal') {
920
+ const signal = readString(record['signal']);
921
+ if (signal !== 'interrupt' && signal !== 'eof' && signal !== 'terminate') {
922
+ return null;
923
+ }
924
+ return {
925
+ kind,
926
+ sessionId,
927
+ signal,
928
+ };
929
+ }
930
+
931
+ return null;
932
+ }
933
+
934
+ function parseStreamSessionEvent(value: unknown): StreamSessionEvent | null {
935
+ const record = asRecord(value);
936
+ if (record === null) {
937
+ return null;
938
+ }
939
+ const type = readString(record['type']);
940
+ if (type === null) {
941
+ return null;
942
+ }
943
+
944
+ if (type === 'session-exit') {
945
+ const exitRecord = asRecord(record['exit']);
946
+ if (exitRecord === null) {
947
+ return null;
948
+ }
949
+ const code = exitRecord['code'];
950
+ const signal = exitRecord['signal'];
951
+ const normalizedCode = code === null ? null : readNumber(code);
952
+ const normalizedSignal = signal === null ? null : readSignalName(signal);
953
+ if (normalizedCode === null && code !== null) {
954
+ return null;
955
+ }
956
+ if (normalizedSignal === null && signal !== null) {
957
+ return null;
958
+ }
959
+ return {
960
+ type,
961
+ exit: {
962
+ code: normalizedCode,
963
+ signal: normalizedSignal,
964
+ },
965
+ };
966
+ }
967
+
968
+ if (type === 'notify') {
969
+ const notifyRecord = asRecord(record['record']);
970
+ if (notifyRecord === null) {
971
+ return null;
972
+ }
973
+ const ts = readString(notifyRecord['ts']);
974
+ const payload = asRecord(notifyRecord['payload']);
975
+ if (ts === null || payload === null) {
976
+ return null;
977
+ }
978
+ return {
979
+ type: 'notify',
980
+ record: {
981
+ ts,
982
+ payload,
983
+ },
984
+ };
985
+ }
986
+
987
+ return null;
988
+ }
989
+
990
+ function parseTelemetrySource(value: unknown): StreamTelemetrySource | null {
991
+ if (
992
+ value === 'otlp-log' ||
993
+ value === 'otlp-metric' ||
994
+ value === 'otlp-trace' ||
995
+ value === 'history'
996
+ ) {
997
+ return value;
998
+ }
999
+ return null;
1000
+ }
1001
+
1002
+ function parseSessionControllerType(value: unknown): StreamSessionControllerType | null {
1003
+ if (value === 'human' || value === 'agent' || value === 'automation') {
1004
+ return value;
1005
+ }
1006
+ return null;
1007
+ }
1008
+
1009
+ function parseSessionController(value: unknown): StreamSessionController | null | undefined {
1010
+ if (value === undefined) {
1011
+ return undefined;
1012
+ }
1013
+ if (value === null) {
1014
+ return null;
1015
+ }
1016
+ const record = asRecord(value);
1017
+ if (record === null) {
1018
+ return undefined;
1019
+ }
1020
+ const controllerId = readString(record['controllerId']);
1021
+ const controllerType = parseSessionControllerType(record['controllerType']);
1022
+ const controllerLabel =
1023
+ record['controllerLabel'] === null ? null : readString(record['controllerLabel']);
1024
+ const claimedAt = readString(record['claimedAt']);
1025
+ if (
1026
+ controllerId === null ||
1027
+ controllerType === null ||
1028
+ (controllerLabel === null && record['controllerLabel'] !== null) ||
1029
+ claimedAt === null
1030
+ ) {
1031
+ return undefined;
1032
+ }
1033
+ return {
1034
+ controllerId,
1035
+ controllerType,
1036
+ controllerLabel,
1037
+ claimedAt,
1038
+ };
1039
+ }
1040
+
1041
+ function parseTelemetryStatusHint(value: unknown): StreamTelemetryStatusHint | null | undefined {
1042
+ if (value === undefined) {
1043
+ return undefined;
1044
+ }
1045
+ if (value === null) {
1046
+ return null;
1047
+ }
1048
+ if (value === 'running' || value === 'completed' || value === 'needs-input') {
1049
+ return value;
1050
+ }
1051
+ return undefined;
1052
+ }
1053
+
1054
+ function parseTelemetrySummary(value: unknown): StreamTelemetrySummary | null | undefined {
1055
+ if (value === undefined) {
1056
+ return undefined;
1057
+ }
1058
+ if (value === null) {
1059
+ return null;
1060
+ }
1061
+ const record = asRecord(value);
1062
+ if (record === null) {
1063
+ return undefined;
1064
+ }
1065
+ const source = parseTelemetrySource(record['source']);
1066
+ const eventName = record['eventName'] === null ? null : readString(record['eventName']);
1067
+ const severity = record['severity'] === null ? null : readString(record['severity']);
1068
+ const summary = record['summary'] === null ? null : readString(record['summary']);
1069
+ const observedAt = readString(record['observedAt']);
1070
+ if (
1071
+ source === null ||
1072
+ (eventName === null && record['eventName'] !== null) ||
1073
+ (severity === null && record['severity'] !== null) ||
1074
+ (summary === null && record['summary'] !== null) ||
1075
+ observedAt === null
1076
+ ) {
1077
+ return undefined;
1078
+ }
1079
+ return {
1080
+ source,
1081
+ eventName,
1082
+ severity,
1083
+ summary,
1084
+ observedAt,
1085
+ };
1086
+ }
1087
+
1088
+ function parseSessionStatusModel(value: unknown): StreamSessionStatusModel | null | undefined {
1089
+ if (value === undefined) {
1090
+ return undefined;
1091
+ }
1092
+ if (value === null) {
1093
+ return null;
1094
+ }
1095
+ const record = asRecord(value);
1096
+ if (record === null) {
1097
+ return undefined;
1098
+ }
1099
+ const runtimeStatus = readString(record['runtimeStatus']);
1100
+ const phase = readString(record['phase']);
1101
+ const glyph = readString(record['glyph']);
1102
+ const badge = readString(record['badge']);
1103
+ const detailText = readString(record['detailText']);
1104
+ const attentionReason =
1105
+ record['attentionReason'] === null ? null : readString(record['attentionReason']);
1106
+ const lastKnownWork =
1107
+ record['lastKnownWork'] === null ? null : readString(record['lastKnownWork']);
1108
+ const lastKnownWorkAt =
1109
+ record['lastKnownWorkAt'] === null ? null : readString(record['lastKnownWorkAt']);
1110
+ const phaseHint = record['phaseHint'] === null ? null : readString(record['phaseHint']);
1111
+ const observedAt = readString(record['observedAt']);
1112
+ if (
1113
+ runtimeStatus === null ||
1114
+ (runtimeStatus !== 'running' &&
1115
+ runtimeStatus !== 'needs-input' &&
1116
+ runtimeStatus !== 'completed' &&
1117
+ runtimeStatus !== 'exited') ||
1118
+ phase === null ||
1119
+ (phase !== 'needs-action' &&
1120
+ phase !== 'starting' &&
1121
+ phase !== 'working' &&
1122
+ phase !== 'idle' &&
1123
+ phase !== 'exited') ||
1124
+ glyph === null ||
1125
+ (glyph !== '▲' && glyph !== '◔' && glyph !== '◆' && glyph !== '○' && glyph !== '■') ||
1126
+ badge === null ||
1127
+ (badge !== 'NEED' && badge !== 'RUN ' && badge !== 'DONE' && badge !== 'EXIT') ||
1128
+ detailText === null ||
1129
+ (attentionReason === null && record['attentionReason'] !== null) ||
1130
+ (lastKnownWork === null && record['lastKnownWork'] !== null) ||
1131
+ (lastKnownWorkAt === null && record['lastKnownWorkAt'] !== null) ||
1132
+ (phaseHint === null && record['phaseHint'] !== null) ||
1133
+ (phaseHint !== null &&
1134
+ phaseHint !== 'needs-action' &&
1135
+ phaseHint !== 'working' &&
1136
+ phaseHint !== 'idle') ||
1137
+ observedAt === null
1138
+ ) {
1139
+ return undefined;
1140
+ }
1141
+ return {
1142
+ runtimeStatus,
1143
+ phase,
1144
+ glyph,
1145
+ badge,
1146
+ detailText,
1147
+ attentionReason,
1148
+ lastKnownWork,
1149
+ lastKnownWorkAt,
1150
+ phaseHint,
1151
+ observedAt,
1152
+ };
1153
+ }
1154
+
1155
+ function parseSessionKeyEventRecord(value: unknown): StreamSessionKeyEventRecord | null {
1156
+ const record = asRecord(value);
1157
+ if (record === null) {
1158
+ return null;
1159
+ }
1160
+ const source = parseTelemetrySource(record['source']);
1161
+ const eventName = record['eventName'] === null ? null : readString(record['eventName']);
1162
+ const severity = record['severity'] === null ? null : readString(record['severity']);
1163
+ const summary = record['summary'] === null ? null : readString(record['summary']);
1164
+ const observedAt = readString(record['observedAt']);
1165
+ const statusHint = parseTelemetryStatusHint(record['statusHint']);
1166
+ if (
1167
+ source === null ||
1168
+ (eventName === null && record['eventName'] !== null) ||
1169
+ (severity === null && record['severity'] !== null) ||
1170
+ (summary === null && record['summary'] !== null) ||
1171
+ observedAt === null ||
1172
+ statusHint === undefined
1173
+ ) {
1174
+ return null;
1175
+ }
1176
+ return {
1177
+ source,
1178
+ eventName,
1179
+ severity,
1180
+ summary,
1181
+ observedAt,
1182
+ statusHint,
1183
+ };
1184
+ }
1185
+
1186
+ function parseStreamObservedEvent(value: unknown): StreamObservedEvent | null {
1187
+ const record = asRecord(value);
1188
+ if (record === null) {
1189
+ return null;
1190
+ }
1191
+ const type = readString(record['type']);
1192
+ if (type === null) {
1193
+ return null;
1194
+ }
1195
+
1196
+ if (type === 'directory-upserted') {
1197
+ const directory = asRecord(record['directory']);
1198
+ if (directory === null) {
1199
+ return null;
1200
+ }
1201
+ return {
1202
+ type,
1203
+ directory,
1204
+ };
1205
+ }
1206
+
1207
+ if (type === 'directory-archived') {
1208
+ const directoryId = readString(record['directoryId']);
1209
+ const ts = readString(record['ts']);
1210
+ if (directoryId === null || ts === null) {
1211
+ return null;
1212
+ }
1213
+ return {
1214
+ type,
1215
+ directoryId,
1216
+ ts,
1217
+ };
1218
+ }
1219
+
1220
+ if (type === 'directory-git-updated') {
1221
+ const directoryId = readString(record['directoryId']);
1222
+ const summaryRecord = asRecord(record['summary']);
1223
+ const repositorySnapshotRecord = asRecord(record['repositorySnapshot']);
1224
+ const repositoryId =
1225
+ record['repositoryId'] === null ? null : readString(record['repositoryId']);
1226
+ const repository = record['repository'] === null ? null : asRecord(record['repository']);
1227
+ const observedAt = readString(record['observedAt']);
1228
+ if (
1229
+ directoryId === null ||
1230
+ summaryRecord === null ||
1231
+ repositorySnapshotRecord === null ||
1232
+ (repositoryId === null && record['repositoryId'] !== null) ||
1233
+ (repository === null && record['repository'] !== null) ||
1234
+ observedAt === null
1235
+ ) {
1236
+ return null;
1237
+ }
1238
+ const branch = readString(summaryRecord['branch']);
1239
+ const changedFiles = readNumber(summaryRecord['changedFiles']);
1240
+ const additions = readNumber(summaryRecord['additions']);
1241
+ const deletions = readNumber(summaryRecord['deletions']);
1242
+ if (branch === null || changedFiles === null || additions === null || deletions === null) {
1243
+ return null;
1244
+ }
1245
+ const normalizedRemoteUrl =
1246
+ repositorySnapshotRecord['normalizedRemoteUrl'] === null
1247
+ ? null
1248
+ : readString(repositorySnapshotRecord['normalizedRemoteUrl']);
1249
+ const commitCount =
1250
+ repositorySnapshotRecord['commitCount'] === null
1251
+ ? null
1252
+ : readNumber(repositorySnapshotRecord['commitCount']);
1253
+ const lastCommitAt =
1254
+ repositorySnapshotRecord['lastCommitAt'] === null
1255
+ ? null
1256
+ : readString(repositorySnapshotRecord['lastCommitAt']);
1257
+ const shortCommitHash =
1258
+ repositorySnapshotRecord['shortCommitHash'] === null
1259
+ ? null
1260
+ : readString(repositorySnapshotRecord['shortCommitHash']);
1261
+ const inferredName =
1262
+ repositorySnapshotRecord['inferredName'] === null
1263
+ ? null
1264
+ : readString(repositorySnapshotRecord['inferredName']);
1265
+ const defaultBranch =
1266
+ repositorySnapshotRecord['defaultBranch'] === null
1267
+ ? null
1268
+ : readString(repositorySnapshotRecord['defaultBranch']);
1269
+ if (
1270
+ (normalizedRemoteUrl === null && repositorySnapshotRecord['normalizedRemoteUrl'] !== null) ||
1271
+ (commitCount === null && repositorySnapshotRecord['commitCount'] !== null) ||
1272
+ (lastCommitAt === null && repositorySnapshotRecord['lastCommitAt'] !== null) ||
1273
+ (shortCommitHash === null && repositorySnapshotRecord['shortCommitHash'] !== null) ||
1274
+ (inferredName === null && repositorySnapshotRecord['inferredName'] !== null) ||
1275
+ (defaultBranch === null && repositorySnapshotRecord['defaultBranch'] !== null)
1276
+ ) {
1277
+ return null;
1278
+ }
1279
+ return {
1280
+ type,
1281
+ directoryId,
1282
+ summary: {
1283
+ branch,
1284
+ changedFiles,
1285
+ additions,
1286
+ deletions,
1287
+ },
1288
+ repositorySnapshot: {
1289
+ normalizedRemoteUrl,
1290
+ commitCount,
1291
+ lastCommitAt,
1292
+ shortCommitHash,
1293
+ inferredName,
1294
+ defaultBranch,
1295
+ },
1296
+ repositoryId,
1297
+ repository,
1298
+ observedAt,
1299
+ };
1300
+ }
1301
+
1302
+ if (type === 'conversation-created') {
1303
+ const conversation = asRecord(record['conversation']);
1304
+ if (conversation === null) {
1305
+ return null;
1306
+ }
1307
+ return {
1308
+ type,
1309
+ conversation,
1310
+ };
1311
+ }
1312
+
1313
+ if (type === 'conversation-updated') {
1314
+ const conversation = asRecord(record['conversation']);
1315
+ if (conversation === null) {
1316
+ return null;
1317
+ }
1318
+ return {
1319
+ type,
1320
+ conversation,
1321
+ };
1322
+ }
1323
+
1324
+ if (type === 'conversation-archived') {
1325
+ const conversationId = readString(record['conversationId']);
1326
+ const ts = readString(record['ts']);
1327
+ if (conversationId === null || ts === null) {
1328
+ return null;
1329
+ }
1330
+ return {
1331
+ type,
1332
+ conversationId,
1333
+ ts,
1334
+ };
1335
+ }
1336
+
1337
+ if (type === 'conversation-deleted') {
1338
+ const conversationId = readString(record['conversationId']);
1339
+ const ts = readString(record['ts']);
1340
+ if (conversationId === null || ts === null) {
1341
+ return null;
1342
+ }
1343
+ return {
1344
+ type,
1345
+ conversationId,
1346
+ ts,
1347
+ };
1348
+ }
1349
+
1350
+ if (type === 'repository-upserted') {
1351
+ const repository = asRecord(record['repository']);
1352
+ if (repository === null) {
1353
+ return null;
1354
+ }
1355
+ return {
1356
+ type,
1357
+ repository,
1358
+ };
1359
+ }
1360
+
1361
+ if (type === 'repository-updated') {
1362
+ const repository = asRecord(record['repository']);
1363
+ if (repository === null) {
1364
+ return null;
1365
+ }
1366
+ return {
1367
+ type,
1368
+ repository,
1369
+ };
1370
+ }
1371
+
1372
+ if (type === 'repository-archived') {
1373
+ const repositoryId = readString(record['repositoryId']);
1374
+ const ts = readString(record['ts']);
1375
+ if (repositoryId === null || ts === null) {
1376
+ return null;
1377
+ }
1378
+ return {
1379
+ type,
1380
+ repositoryId,
1381
+ ts,
1382
+ };
1383
+ }
1384
+
1385
+ if (type === 'task-created') {
1386
+ const task = asRecord(record['task']);
1387
+ if (task === null) {
1388
+ return null;
1389
+ }
1390
+ return {
1391
+ type,
1392
+ task,
1393
+ };
1394
+ }
1395
+
1396
+ if (type === 'task-updated') {
1397
+ const task = asRecord(record['task']);
1398
+ if (task === null) {
1399
+ return null;
1400
+ }
1401
+ return {
1402
+ type,
1403
+ task,
1404
+ };
1405
+ }
1406
+
1407
+ if (type === 'task-deleted') {
1408
+ const taskId = readString(record['taskId']);
1409
+ const ts = readString(record['ts']);
1410
+ if (taskId === null || ts === null) {
1411
+ return null;
1412
+ }
1413
+ return {
1414
+ type,
1415
+ taskId,
1416
+ ts,
1417
+ };
1418
+ }
1419
+
1420
+ if (type === 'task-reordered') {
1421
+ const tasksValue = record['tasks'];
1422
+ if (!Array.isArray(tasksValue)) {
1423
+ return null;
1424
+ }
1425
+ const tasks: Record<string, unknown>[] = [];
1426
+ for (const entry of tasksValue) {
1427
+ const normalized = asRecord(entry);
1428
+ if (normalized === null) {
1429
+ return null;
1430
+ }
1431
+ tasks.push(normalized);
1432
+ }
1433
+ const ts = readString(record['ts']);
1434
+ if (ts === null) {
1435
+ return null;
1436
+ }
1437
+ return {
1438
+ type,
1439
+ tasks,
1440
+ ts,
1441
+ };
1442
+ }
1443
+
1444
+ if (type === 'github-pr-upserted') {
1445
+ const pr = asRecord(record['pr']);
1446
+ if (pr === null) {
1447
+ return null;
1448
+ }
1449
+ return {
1450
+ type,
1451
+ pr,
1452
+ };
1453
+ }
1454
+
1455
+ if (type === 'github-pr-closed') {
1456
+ const prRecordId = readString(record['prRecordId']);
1457
+ const repositoryId = readString(record['repositoryId']);
1458
+ const ts = readString(record['ts']);
1459
+ if (prRecordId === null || repositoryId === null || ts === null) {
1460
+ return null;
1461
+ }
1462
+ return {
1463
+ type,
1464
+ prRecordId,
1465
+ repositoryId,
1466
+ ts,
1467
+ };
1468
+ }
1469
+
1470
+ if (type === 'github-pr-jobs-updated') {
1471
+ const prRecordId = readString(record['prRecordId']);
1472
+ const repositoryId = readString(record['repositoryId']);
1473
+ const ciRollup = readString(record['ciRollup']);
1474
+ const jobsRaw = record['jobs'];
1475
+ const ts = readString(record['ts']);
1476
+ if (
1477
+ prRecordId === null ||
1478
+ repositoryId === null ||
1479
+ (ciRollup !== 'pending' &&
1480
+ ciRollup !== 'success' &&
1481
+ ciRollup !== 'failure' &&
1482
+ ciRollup !== 'cancelled' &&
1483
+ ciRollup !== 'neutral' &&
1484
+ ciRollup !== 'none') ||
1485
+ !Array.isArray(jobsRaw) ||
1486
+ ts === null
1487
+ ) {
1488
+ return null;
1489
+ }
1490
+ const jobs: Record<string, unknown>[] = [];
1491
+ for (const entry of jobsRaw) {
1492
+ const job = asRecord(entry);
1493
+ if (job === null) {
1494
+ return null;
1495
+ }
1496
+ jobs.push(job);
1497
+ }
1498
+ return {
1499
+ type,
1500
+ prRecordId,
1501
+ repositoryId,
1502
+ ciRollup,
1503
+ jobs,
1504
+ ts,
1505
+ };
1506
+ }
1507
+
1508
+ if (type === 'session-status') {
1509
+ const sessionId = readString(record['sessionId']);
1510
+ const status = readString(record['status']);
1511
+ const attentionReason = readString(record['attentionReason']);
1512
+ const statusModel = parseSessionStatusModel(record['statusModel']);
1513
+ const live = readBoolean(record['live']);
1514
+ const ts = readString(record['ts']);
1515
+ const directoryId = readString(record['directoryId']);
1516
+ const conversationId = readString(record['conversationId']);
1517
+ const telemetry = parseTelemetrySummary(record['telemetry']);
1518
+ const controller = parseSessionController(record['controller']);
1519
+ if (
1520
+ sessionId === null ||
1521
+ status === null ||
1522
+ statusModel === undefined ||
1523
+ live === null ||
1524
+ ts === null ||
1525
+ (record['attentionReason'] !== null && attentionReason === null) ||
1526
+ (record['directoryId'] !== null && directoryId === null) ||
1527
+ (record['conversationId'] !== null && conversationId === null) ||
1528
+ (record['telemetry'] !== undefined && telemetry === undefined) ||
1529
+ (record['controller'] !== undefined && controller === undefined)
1530
+ ) {
1531
+ return null;
1532
+ }
1533
+ if (
1534
+ status !== 'running' &&
1535
+ status !== 'needs-input' &&
1536
+ status !== 'completed' &&
1537
+ status !== 'exited'
1538
+ ) {
1539
+ return null;
1540
+ }
1541
+ return {
1542
+ type,
1543
+ sessionId,
1544
+ status,
1545
+ attentionReason: record['attentionReason'] === null ? null : attentionReason,
1546
+ statusModel,
1547
+ live,
1548
+ ts,
1549
+ directoryId: record['directoryId'] === null ? null : directoryId,
1550
+ conversationId: record['conversationId'] === null ? null : conversationId,
1551
+ telemetry: telemetry ?? null,
1552
+ controller: controller ?? null,
1553
+ };
1554
+ }
1555
+
1556
+ if (type === 'session-event') {
1557
+ const sessionId = readString(record['sessionId']);
1558
+ const event = parseStreamSessionEvent(record['event']);
1559
+ const ts = readString(record['ts']);
1560
+ const directoryId = readString(record['directoryId']);
1561
+ const conversationId = readString(record['conversationId']);
1562
+ if (
1563
+ sessionId === null ||
1564
+ event === null ||
1565
+ ts === null ||
1566
+ (record['directoryId'] !== null && directoryId === null) ||
1567
+ (record['conversationId'] !== null && conversationId === null)
1568
+ ) {
1569
+ return null;
1570
+ }
1571
+ return {
1572
+ type,
1573
+ sessionId,
1574
+ event,
1575
+ ts,
1576
+ directoryId: record['directoryId'] === null ? null : directoryId,
1577
+ conversationId: record['conversationId'] === null ? null : conversationId,
1578
+ };
1579
+ }
1580
+
1581
+ if (type === 'session-key-event') {
1582
+ const sessionId = readString(record['sessionId']);
1583
+ const keyEvent = parseSessionKeyEventRecord(record['keyEvent']);
1584
+ const ts = readString(record['ts']);
1585
+ const directoryId = readString(record['directoryId']);
1586
+ const conversationId = readString(record['conversationId']);
1587
+ if (
1588
+ sessionId === null ||
1589
+ keyEvent === null ||
1590
+ ts === null ||
1591
+ (record['directoryId'] !== null && directoryId === null) ||
1592
+ (record['conversationId'] !== null && conversationId === null)
1593
+ ) {
1594
+ return null;
1595
+ }
1596
+ return {
1597
+ type,
1598
+ sessionId,
1599
+ keyEvent,
1600
+ ts,
1601
+ directoryId: record['directoryId'] === null ? null : directoryId,
1602
+ conversationId: record['conversationId'] === null ? null : conversationId,
1603
+ };
1604
+ }
1605
+
1606
+ if (type === 'session-control') {
1607
+ const sessionId = readString(record['sessionId']);
1608
+ const action = readString(record['action']);
1609
+ const controller = parseSessionController(record['controller']);
1610
+ const previousController = parseSessionController(record['previousController']);
1611
+ const reason = record['reason'] === null ? null : readString(record['reason']);
1612
+ const ts = readString(record['ts']);
1613
+ const directoryId = readString(record['directoryId']);
1614
+ const conversationId = readString(record['conversationId']);
1615
+ if (
1616
+ sessionId === null ||
1617
+ (action !== 'claimed' && action !== 'released' && action !== 'taken-over') ||
1618
+ controller === undefined ||
1619
+ previousController === undefined ||
1620
+ (reason === null && record['reason'] !== null) ||
1621
+ ts === null ||
1622
+ (record['directoryId'] !== null && directoryId === null) ||
1623
+ (record['conversationId'] !== null && conversationId === null)
1624
+ ) {
1625
+ return null;
1626
+ }
1627
+ return {
1628
+ type,
1629
+ sessionId,
1630
+ action,
1631
+ controller,
1632
+ previousController,
1633
+ reason,
1634
+ ts,
1635
+ directoryId: record['directoryId'] === null ? null : directoryId,
1636
+ conversationId: record['conversationId'] === null ? null : conversationId,
1637
+ };
1638
+ }
1639
+
1640
+ if (type === 'session-output') {
1641
+ const sessionId = readString(record['sessionId']);
1642
+ const outputCursor = readNumber(record['outputCursor']);
1643
+ const chunkBase64 = readString(record['chunkBase64']);
1644
+ const ts = readString(record['ts']);
1645
+ const directoryId = readString(record['directoryId']);
1646
+ const conversationId = readString(record['conversationId']);
1647
+ if (
1648
+ sessionId === null ||
1649
+ outputCursor === null ||
1650
+ chunkBase64 === null ||
1651
+ ts === null ||
1652
+ (record['directoryId'] !== null && directoryId === null) ||
1653
+ (record['conversationId'] !== null && conversationId === null)
1654
+ ) {
1655
+ return null;
1656
+ }
1657
+ return {
1658
+ type,
1659
+ sessionId,
1660
+ outputCursor,
1661
+ chunkBase64,
1662
+ ts,
1663
+ directoryId: record['directoryId'] === null ? null : directoryId,
1664
+ conversationId: record['conversationId'] === null ? null : conversationId,
1665
+ };
1666
+ }
1667
+
1668
+ return null;
1669
+ }
1670
+
1671
+ export function parseServerEnvelope(value: unknown): StreamServerEnvelope | null {
1672
+ const record = asRecord(value);
1673
+ if (record === null) {
1674
+ return null;
1675
+ }
1676
+
1677
+ const kind = readString(record['kind']);
1678
+ if (kind === null) {
1679
+ return null;
1680
+ }
1681
+
1682
+ if (kind === 'auth.ok') {
1683
+ return {
1684
+ kind,
1685
+ };
1686
+ }
1687
+
1688
+ if (kind === 'auth.error') {
1689
+ const error = readString(record['error']);
1690
+ if (error === null) {
1691
+ return null;
1692
+ }
1693
+ return {
1694
+ kind,
1695
+ error,
1696
+ };
1697
+ }
1698
+
1699
+ if (kind === 'command.accepted') {
1700
+ const commandId = readString(record['commandId']);
1701
+ if (commandId === null) {
1702
+ return null;
1703
+ }
1704
+ return {
1705
+ kind,
1706
+ commandId,
1707
+ };
1708
+ }
1709
+
1710
+ if (kind === 'command.completed') {
1711
+ const commandId = readString(record['commandId']);
1712
+ const result = asRecord(record['result']);
1713
+ if (commandId === null || result === null) {
1714
+ return null;
1715
+ }
1716
+ return {
1717
+ kind,
1718
+ commandId,
1719
+ result,
1720
+ };
1721
+ }
1722
+
1723
+ if (kind === 'command.failed') {
1724
+ const commandId = readString(record['commandId']);
1725
+ const error = readString(record['error']);
1726
+ if (commandId === null || error === null) {
1727
+ return null;
1728
+ }
1729
+ return {
1730
+ kind,
1731
+ commandId,
1732
+ error,
1733
+ };
1734
+ }
1735
+
1736
+ if (kind === 'stream.event') {
1737
+ const subscriptionId = readString(record['subscriptionId']);
1738
+ const cursor = readNumber(record['cursor']);
1739
+ const event = parseStreamObservedEvent(record['event']);
1740
+ if (subscriptionId === null || cursor === null || event === null) {
1741
+ return null;
1742
+ }
1743
+ return {
1744
+ kind,
1745
+ subscriptionId,
1746
+ cursor,
1747
+ event,
1748
+ };
1749
+ }
1750
+
1751
+ const sessionId = readString(record['sessionId']);
1752
+ if (sessionId === null) {
1753
+ return null;
1754
+ }
1755
+
1756
+ if (kind === 'pty.output') {
1757
+ const cursor = readNumber(record['cursor']);
1758
+ const chunkBase64 = readString(record['chunkBase64']);
1759
+ if (cursor === null || chunkBase64 === null) {
1760
+ return null;
1761
+ }
1762
+ return {
1763
+ kind,
1764
+ sessionId,
1765
+ cursor,
1766
+ chunkBase64,
1767
+ };
1768
+ }
1769
+
1770
+ if (kind === 'pty.exit') {
1771
+ const exitRecord = asRecord(record['exit']);
1772
+ if (exitRecord === null) {
1773
+ return null;
1774
+ }
1775
+ const code = exitRecord['code'];
1776
+ const signal = exitRecord['signal'];
1777
+ const normalizedCode = code === null ? null : readNumber(code);
1778
+ const normalizedSignal = signal === null ? null : readSignalName(signal);
1779
+ if (normalizedCode === null && code !== null) {
1780
+ return null;
1781
+ }
1782
+ if (normalizedSignal === null && signal !== null) {
1783
+ return null;
1784
+ }
1785
+ return {
1786
+ kind,
1787
+ sessionId,
1788
+ exit: {
1789
+ code: normalizedCode,
1790
+ signal: normalizedSignal,
1791
+ },
1792
+ };
1793
+ }
1794
+
1795
+ if (kind === 'pty.event') {
1796
+ const event = parseStreamSessionEvent(record['event']);
1797
+ if (event === null) {
1798
+ return null;
1799
+ }
1800
+ return {
1801
+ kind,
1802
+ sessionId,
1803
+ event,
1804
+ };
1805
+ }
1806
+
1807
+ return null;
1808
+ }