@jmoyers/harness 0.1.10 → 0.1.20

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 (239) hide show
  1. package/README.md +31 -35
  2. package/package.json +31 -11
  3. package/packages/harness-ai/src/anthropic-protocol.ts +68 -68
  4. package/packages/harness-ai/src/stream-text.ts +13 -91
  5. package/packages/harness-ui/src/frame-primitives.ts +158 -0
  6. package/packages/harness-ui/src/index.ts +18 -0
  7. package/packages/harness-ui/src/interaction/conversation-input-forwarder.ts +221 -0
  8. package/packages/harness-ui/src/interaction/conversation-selection-input.ts +213 -0
  9. package/packages/harness-ui/src/interaction/global-shortcut-input.ts +172 -0
  10. package/{src/ui → packages/harness-ui/src/interaction}/input-preflight.ts +10 -12
  11. package/{src/ui → packages/harness-ui/src/interaction}/input-token-router.ts +120 -69
  12. package/packages/harness-ui/src/interaction/input.ts +420 -0
  13. package/packages/harness-ui/src/interaction/left-nav-input.ts +166 -0
  14. package/{src/ui → packages/harness-ui/src/interaction}/main-pane-pointer-input.ts +91 -23
  15. package/{src/ui → packages/harness-ui/src/interaction}/pointer-routing-input.ts +112 -48
  16. package/packages/harness-ui/src/interaction/rail-pointer-input.ts +62 -0
  17. package/packages/harness-ui/src/interaction/repository-fold-input.ts +118 -0
  18. package/packages/harness-ui/src/kit.ts +476 -0
  19. package/packages/harness-ui/src/layout.ts +238 -0
  20. package/{src/ui/modals/manager.ts → packages/harness-ui/src/modal-manager.ts} +94 -64
  21. package/{src/ui → packages/harness-ui/src}/screen.ts +53 -26
  22. package/packages/harness-ui/src/surface.ts +252 -0
  23. package/packages/harness-ui/src/text-layout.ts +210 -0
  24. package/packages/nim-core/src/contracts.ts +239 -0
  25. package/packages/nim-core/src/event-store.ts +299 -0
  26. package/packages/nim-core/src/events.ts +53 -0
  27. package/packages/nim-core/src/index.ts +9 -0
  28. package/packages/nim-core/src/provider-router.ts +129 -0
  29. package/packages/nim-core/src/providers/anthropic-driver.ts +291 -0
  30. package/packages/nim-core/src/runtime-factory.ts +49 -0
  31. package/packages/nim-core/src/runtime.ts +1797 -0
  32. package/packages/nim-core/src/session-store.ts +516 -0
  33. package/packages/nim-core/src/telemetry.ts +48 -0
  34. package/packages/nim-test-tui/src/index.ts +150 -0
  35. package/packages/nim-ui-core/src/index.ts +1 -0
  36. package/packages/nim-ui-core/src/projection.ts +87 -0
  37. package/scripts/codex-live-mux-runtime.ts +2 -3721
  38. package/scripts/control-plane-daemon.ts +24 -2
  39. package/scripts/harness-bin.js +5 -0
  40. package/scripts/harness-commands.ts +300 -0
  41. package/scripts/harness-runtime.ts +82 -0
  42. package/scripts/harness.ts +33 -3007
  43. package/scripts/nim-tui-smoke.ts +748 -0
  44. package/src/cli/auth/runtime.ts +948 -0
  45. package/src/cli/default-gateway-pointer.ts +193 -0
  46. package/src/cli/gateway/runtime.ts +1872 -0
  47. package/src/cli/parsing/flags.ts +23 -0
  48. package/src/cli/parsing/session.ts +42 -0
  49. package/src/cli/runtime/context.ts +193 -0
  50. package/src/cli/runtime-app/application.ts +392 -0
  51. package/src/cli/runtime-infra/gateway-control.ts +729 -0
  52. package/{scripts/harness-inspector.ts → src/cli/workflows/inspector.ts} +14 -11
  53. package/src/cli/workflows/runtime.ts +965 -0
  54. package/src/clients/tui/left-rail-interactions.ts +519 -0
  55. package/src/clients/tui/main-pane-interactions.ts +509 -0
  56. package/src/clients/tui/modal-input-routing.ts +71 -0
  57. package/src/clients/tui/render-snapshot-adapter.ts +88 -0
  58. package/src/clients/web/synced-selectors.ts +132 -0
  59. package/src/codex/live-session.ts +82 -29
  60. package/src/config/config-core.ts +361 -10
  61. package/src/config/harness-paths.ts +4 -7
  62. package/src/config/harness-runtime-migration.ts +142 -19
  63. package/src/config/harness.config.template.jsonc +33 -0
  64. package/src/config/secrets-core.ts +92 -4
  65. package/src/control-plane/agent-realtime-api.ts +82 -427
  66. package/src/control-plane/prompt/thread-title-namer.ts +49 -23
  67. package/src/control-plane/session-summary.ts +10 -81
  68. package/src/control-plane/status/reducer-base.ts +12 -12
  69. package/src/control-plane/status/reducers/claude-status-reducer.ts +3 -3
  70. package/src/control-plane/status/reducers/codex-status-reducer.ts +4 -4
  71. package/src/control-plane/status/reducers/cursor-status-reducer.ts +3 -3
  72. package/src/control-plane/stream-client.ts +12 -2
  73. package/src/control-plane/stream-command-parser.ts +83 -143
  74. package/src/control-plane/stream-protocol.ts +53 -37
  75. package/src/control-plane/stream-server-background.ts +18 -2
  76. package/src/control-plane/stream-server-command.ts +376 -69
  77. package/src/control-plane/stream-server-session-runtime.ts +3 -2
  78. package/src/control-plane/stream-server.ts +943 -80
  79. package/src/control-plane/stream-session-runtime-types.ts +41 -0
  80. package/src/{mux/live-mux/control-plane-records.ts → core/contracts/records.ts} +24 -97
  81. package/src/core/state/observed-stream-cursor.ts +43 -0
  82. package/src/core/state/synced-observed-state.ts +273 -0
  83. package/src/core/store/harness-synced-store.ts +81 -0
  84. package/src/diff/budget.ts +136 -0
  85. package/src/diff/build.ts +289 -0
  86. package/src/diff/chunker.ts +146 -0
  87. package/src/diff/git-invoke.ts +315 -0
  88. package/src/diff/git-parse.ts +472 -0
  89. package/src/diff/hash.ts +70 -0
  90. package/src/diff/index.ts +24 -0
  91. package/src/diff/normalize.ts +134 -0
  92. package/src/diff/types.ts +178 -0
  93. package/src/diff-ui/args.ts +346 -0
  94. package/src/diff-ui/commands.ts +123 -0
  95. package/src/diff-ui/finder.ts +94 -0
  96. package/src/diff-ui/highlight.ts +127 -0
  97. package/src/diff-ui/index.ts +2 -0
  98. package/src/diff-ui/model.ts +141 -0
  99. package/src/diff-ui/pager.ts +412 -0
  100. package/src/diff-ui/render.ts +337 -0
  101. package/src/diff-ui/runtime.ts +379 -0
  102. package/src/diff-ui/state.ts +224 -0
  103. package/src/diff-ui/types.ts +236 -0
  104. package/src/domain/conversations.ts +11 -7
  105. package/src/domain/workspace.ts +76 -4
  106. package/src/mux/control-plane-op-queue.ts +93 -7
  107. package/src/mux/conversation-rail.ts +28 -71
  108. package/src/mux/dual-pane-core.ts +13 -13
  109. package/src/mux/harness-core-ui.ts +313 -42
  110. package/src/mux/input-shortcuts.ts +22 -112
  111. package/src/mux/keybinding-catalog.ts +340 -0
  112. package/src/mux/keybinding-registry.ts +103 -0
  113. package/src/mux/live-mux/command-menu-open-in.ts +280 -0
  114. package/src/mux/live-mux/command-menu.ts +167 -4
  115. package/src/mux/live-mux/conversation-state.ts +13 -0
  116. package/src/mux/live-mux/directory-resolution.ts +1 -1
  117. package/src/mux/live-mux/git-parsing.ts +16 -0
  118. package/src/mux/live-mux/git-snapshot.ts +33 -2
  119. package/src/mux/live-mux/global-shortcut-handlers.ts +6 -0
  120. package/src/mux/live-mux/home-pane-drop.ts +1 -1
  121. package/src/mux/live-mux/home-pane-pointer.ts +10 -0
  122. package/src/mux/live-mux/input-forwarding.ts +59 -2
  123. package/src/mux/live-mux/left-nav-activation.ts +124 -7
  124. package/src/mux/live-mux/left-nav.ts +35 -0
  125. package/src/mux/live-mux/link-click.ts +292 -0
  126. package/src/mux/live-mux/modal-command-menu-handler.ts +46 -9
  127. package/src/mux/live-mux/modal-conversation-handlers.ts +5 -1
  128. package/src/mux/live-mux/modal-input-reducers.ts +106 -8
  129. package/src/mux/live-mux/modal-overlays.ts +210 -31
  130. package/src/mux/live-mux/modal-pointer.ts +3 -7
  131. package/src/mux/live-mux/modal-prompt-handlers.ts +107 -1
  132. package/src/mux/live-mux/modal-release-notes-handler.ts +111 -0
  133. package/src/mux/live-mux/modal-task-editor-handler.ts +16 -11
  134. package/src/mux/live-mux/pointer-routing.ts +5 -2
  135. package/src/mux/live-mux/project-pane-pointer.ts +8 -0
  136. package/src/mux/live-mux/rail-layout.ts +33 -30
  137. package/src/mux/live-mux/release-notes.ts +383 -0
  138. package/src/mux/live-mux/render-trace-analysis.ts +52 -7
  139. package/src/mux/live-mux/repository-folding.ts +3 -0
  140. package/src/mux/live-mux/selection.ts +0 -4
  141. package/src/mux/live-mux/session-diagnostics-paths.ts +21 -0
  142. package/src/mux/project-pane-github-review.ts +271 -0
  143. package/src/mux/render-frame.ts +4 -0
  144. package/src/mux/runtime-app/codex-live-mux-runtime.ts +5191 -0
  145. package/src/mux/task-composer.ts +21 -14
  146. package/src/mux/task-focused-pane.ts +118 -117
  147. package/src/mux/task-screen-keybindings.ts +19 -82
  148. package/src/mux/workspace-rail-model.ts +270 -104
  149. package/src/mux/workspace-rail.ts +45 -22
  150. package/src/pty/session-broker.ts +1 -1
  151. package/{scripts → src/recording}/terminal-recording-gif-lib.ts +2 -2
  152. package/src/services/control-plane.ts +50 -32
  153. package/src/services/conversation-lifecycle.ts +118 -87
  154. package/src/services/conversation-startup-hydration.ts +20 -12
  155. package/src/services/directory-hydration.ts +21 -16
  156. package/src/services/event-persistence.ts +7 -0
  157. package/src/services/left-rail-pointer-handler.ts +329 -0
  158. package/src/services/mux-ui-state-persistence.ts +5 -1
  159. package/src/services/recording.ts +34 -26
  160. package/src/services/runtime-command-menu-agent-tools.ts +1 -1
  161. package/src/services/runtime-control-actions.ts +79 -61
  162. package/src/services/runtime-control-plane-ops.ts +122 -83
  163. package/src/services/runtime-conversation-actions.ts +40 -26
  164. package/src/services/runtime-conversation-activation.ts +82 -30
  165. package/src/services/runtime-conversation-starter.ts +80 -48
  166. package/src/services/runtime-conversation-title-edit.ts +91 -80
  167. package/src/services/runtime-envelope-handler.ts +107 -105
  168. package/src/services/runtime-git-state.ts +42 -29
  169. package/src/services/runtime-layout-resize.ts +3 -1
  170. package/src/services/runtime-left-rail-render.ts +99 -63
  171. package/src/services/runtime-nim-cli-session.ts +438 -0
  172. package/src/services/runtime-nim-session.ts +705 -0
  173. package/src/services/runtime-nim-tool-bridge.ts +141 -0
  174. package/src/services/runtime-observed-event-projection-pipeline.ts +45 -0
  175. package/src/services/runtime-process-wiring.ts +29 -36
  176. package/src/services/runtime-project-pane-github-review-cache.ts +164 -0
  177. package/src/services/runtime-render-flush.ts +63 -70
  178. package/src/services/runtime-render-lifecycle.ts +65 -64
  179. package/src/services/runtime-render-orchestrator.ts +55 -45
  180. package/src/services/runtime-render-pipeline.ts +106 -103
  181. package/src/services/runtime-render-state.ts +62 -49
  182. package/src/services/runtime-repository-actions.ts +97 -70
  183. package/src/services/runtime-right-pane-render.ts +80 -53
  184. package/src/services/runtime-shutdown.ts +38 -35
  185. package/src/services/runtime-stream-subscriptions.ts +35 -27
  186. package/src/services/runtime-task-composer-persistence.ts +71 -59
  187. package/src/services/runtime-task-composer-snapshot.ts +14 -0
  188. package/src/services/runtime-task-editor-actions.ts +46 -29
  189. package/src/services/runtime-task-pane-actions.ts +220 -134
  190. package/src/services/runtime-task-pane-shortcuts.ts +323 -123
  191. package/src/services/runtime-workspace-observed-effect-queue.ts +25 -0
  192. package/src/services/runtime-workspace-observed-events.ts +33 -184
  193. package/src/services/runtime-workspace-observed-transition-policy.ts +228 -0
  194. package/src/services/session-diagnostics-store.ts +217 -0
  195. package/src/services/startup-background-resume.ts +26 -21
  196. package/src/services/startup-orchestrator.ts +16 -13
  197. package/src/services/startup-paint-tracker.ts +29 -21
  198. package/src/services/startup-persisted-conversation-queue.ts +19 -13
  199. package/src/services/startup-settled-gate.ts +25 -15
  200. package/src/services/startup-shutdown.ts +18 -22
  201. package/src/services/startup-state-hydration.ts +44 -34
  202. package/src/services/startup-visibility.ts +12 -4
  203. package/src/services/task-pane-selection-actions.ts +89 -72
  204. package/src/services/task-planning-hydration.ts +24 -18
  205. package/src/services/task-planning-observed-events.ts +50 -52
  206. package/src/services/workspace-observed-events.ts +66 -63
  207. package/src/storage/storage-lifecycle-core.ts +438 -0
  208. package/src/store/control-plane-store-normalize.ts +33 -242
  209. package/src/store/control-plane-store-types.ts +1 -35
  210. package/src/store/control-plane-store.ts +396 -56
  211. package/src/store/event-store.ts +397 -3
  212. package/src/terminal/snapshot-oracle.ts +207 -94
  213. package/src/ui/mux-theme.ts +112 -8
  214. package/src/ui/panes/home-gridfire.ts +40 -31
  215. package/src/ui/panes/home.ts +10 -2
  216. package/src/ui/panes/nim.ts +315 -0
  217. package/src/mux/live-mux/actions-task.ts +0 -115
  218. package/src/mux/live-mux/left-rail-actions.ts +0 -118
  219. package/src/mux/live-mux/left-rail-conversation-click.ts +0 -82
  220. package/src/mux/live-mux/left-rail-pointer.ts +0 -74
  221. package/src/mux/live-mux/task-pane-shortcuts.ts +0 -206
  222. package/src/services/runtime-directory-actions.ts +0 -164
  223. package/src/services/runtime-input-pipeline.ts +0 -50
  224. package/src/services/runtime-input-router.ts +0 -189
  225. package/src/services/runtime-main-pane-input.ts +0 -230
  226. package/src/services/runtime-modal-input.ts +0 -119
  227. package/src/services/runtime-navigation-input.ts +0 -197
  228. package/src/services/runtime-rail-input.ts +0 -278
  229. package/src/services/runtime-task-pane.ts +0 -62
  230. package/src/services/runtime-workspace-actions.ts +0 -158
  231. package/src/ui/conversation-input-forwarder.ts +0 -114
  232. package/src/ui/conversation-selection-input.ts +0 -103
  233. package/src/ui/global-shortcut-input.ts +0 -89
  234. package/src/ui/input.ts +0 -238
  235. package/src/ui/kit.ts +0 -509
  236. package/src/ui/left-nav-input.ts +0 -80
  237. package/src/ui/left-rail-pointer-input.ts +0 -148
  238. package/src/ui/repository-fold-input.ts +0 -91
  239. package/src/ui/surface.ts +0 -224
@@ -35,7 +35,8 @@ const FALLBACK_STOP_WORDS = new Set([
35
35
  'up',
36
36
  'with',
37
37
  ]);
38
- const DEFAULT_HAIKU_MODEL_ID = 'claude-3-5-haiku-latest';
38
+ const DEFAULT_HAIKU_MODEL_ID = 'claude-haiku-4-5-20251001';
39
+ const FALLBACK_HAIKU_MODEL_IDS = ['claude-3-haiku-20240307'] as const;
39
40
 
40
41
  interface ThreadTitlePromptHistoryEntry {
41
42
  readonly text: string;
@@ -61,6 +62,22 @@ interface AnthropicThreadTitleNamerOptions {
61
62
  readonly fetch?: typeof fetch;
62
63
  }
63
64
 
65
+ function resolveModelCandidateIds(modelId: string | undefined): readonly string[] {
66
+ const ordered = [modelId, DEFAULT_HAIKU_MODEL_ID, ...FALLBACK_HAIKU_MODEL_IDS];
67
+ const deduped: string[] = [];
68
+ for (const candidate of ordered) {
69
+ if (typeof candidate !== 'string') {
70
+ continue;
71
+ }
72
+ const trimmed = candidate.trim();
73
+ if (trimmed.length === 0 || deduped.includes(trimmed)) {
74
+ continue;
75
+ }
76
+ deduped.push(trimmed);
77
+ }
78
+ return deduped;
79
+ }
80
+
64
81
  function asRecord(value: unknown): Record<string, unknown> | null {
65
82
  if (typeof value !== 'object' || value === null || Array.isArray(value)) {
66
83
  return null;
@@ -255,7 +272,7 @@ export function createAnthropicThreadTitleNamer(
255
272
  ...(options.baseUrl === undefined ? {} : { baseUrl: options.baseUrl }),
256
273
  ...(options.fetch === undefined ? {} : { fetch: options.fetch }),
257
274
  });
258
- const model = anthropic(options.modelId ?? DEFAULT_HAIKU_MODEL_ID);
275
+ const modelCandidateIds = resolveModelCandidateIds(options.modelId);
259
276
  return {
260
277
  async suggest(input: ThreadTitleNamerInput): Promise<string | null> {
261
278
  if (input.promptHistory.length === 0) {
@@ -264,27 +281,36 @@ export function createAnthropicThreadTitleNamer(
264
281
  const promptLines = input.promptHistory.map(
265
282
  (entry, index) => `${String(index + 1)}. ${entry.text}`,
266
283
  );
267
- const response = await generateText({
268
- model,
269
- system: [
270
- 'You name active coding-agent threads.',
271
- 'Use the full user prompt history to keep titles relevant and fresh.',
272
- 'Stay high-level and avoid low-level implementation details.',
273
- 'Return exactly 2 words in lowercase with no punctuation and no extra text.',
274
- ].join(' '),
275
- prompt: [
276
- `Agent: ${input.agentType}`,
277
- `Current title: ${input.currentTitle}`,
278
- `Conversation id: ${input.conversationId}`,
279
- 'Prompt history (oldest to newest):',
280
- ...promptLines,
281
- 'Return a new title now.',
282
- ].join('\n'),
283
- maxOutputTokens: 16,
284
- temperature: 0,
285
- });
286
- const normalized = normalizeThreadTitleCandidate(response.text);
287
- return normalized ?? fallbackThreadTitleFromPromptHistory(input.promptHistory);
284
+ for (const modelId of modelCandidateIds) {
285
+ const model = anthropic(modelId);
286
+ const response = await generateText({
287
+ model,
288
+ system: [
289
+ 'You name active coding-agent threads.',
290
+ 'Use the full user prompt history to keep titles relevant and fresh.',
291
+ 'Stay high-level and avoid low-level implementation details.',
292
+ 'Return exactly 2 words in lowercase with no punctuation and no extra text.',
293
+ ].join(' '),
294
+ prompt: [
295
+ `Agent: ${input.agentType}`,
296
+ `Current title: ${input.currentTitle}`,
297
+ `Conversation id: ${input.conversationId}`,
298
+ 'Prompt history (oldest to newest):',
299
+ ...promptLines,
300
+ 'Return a new title now.',
301
+ ].join('\n'),
302
+ maxOutputTokens: 16,
303
+ temperature: 0,
304
+ });
305
+ const normalized = normalizeThreadTitleCandidate(response.text);
306
+ if (normalized !== null) {
307
+ return normalized;
308
+ }
309
+ if (response.finishReason !== 'error') {
310
+ break;
311
+ }
312
+ }
313
+ return fallbackThreadTitleFromPromptHistory(input.promptHistory);
288
314
  },
289
315
  };
290
316
  }
@@ -1,10 +1,12 @@
1
1
  import type { PtyExit } from '../pty/pty_host.ts';
2
- import type {
3
- StreamSessionController,
4
- StreamSessionControllerType,
5
- StreamSessionRuntimeStatus,
6
- StreamSessionStatusModel,
7
- StreamTelemetrySummary,
2
+ import {
3
+ isStreamSessionRuntimeStatus,
4
+ parseStreamSessionStatusModel,
5
+ type StreamSessionController,
6
+ type StreamSessionControllerType,
7
+ type StreamSessionRuntimeStatus,
8
+ type StreamSessionStatusModel,
9
+ type StreamTelemetrySummary,
8
10
  } from './stream-protocol.ts';
9
11
 
10
12
  interface StreamSessionSummary {
@@ -187,79 +189,6 @@ function readSessionController(value: unknown): StreamSessionController | null |
187
189
  };
188
190
  }
189
191
 
190
- function readSessionStatusModel(value: unknown): StreamSessionStatusModel | null | undefined {
191
- if (value === undefined) {
192
- return undefined;
193
- }
194
- if (value === null) {
195
- return null;
196
- }
197
- const record = asRecord(value);
198
- if (record === null) {
199
- return undefined;
200
- }
201
- const runtimeStatus = readString(record['runtimeStatus']);
202
- const phase = readString(record['phase']);
203
- const glyph = readString(record['glyph']);
204
- const badge = readString(record['badge']);
205
- const detailText = readString(record['detailText']);
206
- const attentionReason = readNullableString(record['attentionReason']);
207
- const lastKnownWork = readNullableString(record['lastKnownWork']);
208
- const lastKnownWorkAt = readNullableString(record['lastKnownWorkAt']);
209
- const phaseHintRaw = readNullableString(record['phaseHint']);
210
- const observedAt = readString(record['observedAt']);
211
- if (
212
- runtimeStatus === null ||
213
- !isRuntimeStatus(runtimeStatus) ||
214
- phase === null ||
215
- (phase !== 'needs-action' &&
216
- phase !== 'starting' &&
217
- phase !== 'working' &&
218
- phase !== 'idle' &&
219
- phase !== 'exited') ||
220
- glyph === null ||
221
- (glyph !== '▲' && glyph !== '◔' && glyph !== '◆' && glyph !== '○' && glyph !== '■') ||
222
- badge === null ||
223
- (badge !== 'NEED' && badge !== 'RUN ' && badge !== 'DONE' && badge !== 'EXIT') ||
224
- detailText === null ||
225
- attentionReason === undefined ||
226
- lastKnownWork === undefined ||
227
- lastKnownWorkAt === undefined ||
228
- phaseHintRaw === undefined ||
229
- observedAt === null
230
- ) {
231
- return undefined;
232
- }
233
- const phaseHint =
234
- phaseHintRaw === null ||
235
- phaseHintRaw === 'needs-action' ||
236
- phaseHintRaw === 'working' ||
237
- phaseHintRaw === 'idle'
238
- ? phaseHintRaw
239
- : undefined;
240
- if (phaseHint === undefined) {
241
- return undefined;
242
- }
243
- return {
244
- runtimeStatus,
245
- phase,
246
- glyph,
247
- badge,
248
- detailText,
249
- attentionReason,
250
- lastKnownWork,
251
- lastKnownWorkAt,
252
- phaseHint,
253
- observedAt,
254
- };
255
- }
256
-
257
- function isRuntimeStatus(value: string): value is StreamSessionRuntimeStatus {
258
- return (
259
- value === 'running' || value === 'needs-input' || value === 'completed' || value === 'exited'
260
- );
261
- }
262
-
263
192
  export function parseSessionSummaryRecord(value: unknown): StreamSessionSummary | null {
264
193
  const record = asRecord(value);
265
194
  if (record === null) {
@@ -280,12 +209,12 @@ export function parseSessionSummaryRecord(value: unknown): StreamSessionSummary
280
209
  workspaceId === null ||
281
210
  worktreeId === null ||
282
211
  status === null ||
283
- !isRuntimeStatus(status)
212
+ !isStreamSessionRuntimeStatus(status)
284
213
  ) {
285
214
  return null;
286
215
  }
287
216
  const attentionReason = readNullableString(record['attentionReason']);
288
- const statusModel = readSessionStatusModel(record['statusModel']);
217
+ const statusModel = parseStreamSessionStatusModel(record['statusModel']);
289
218
  const latestCursor = readNullableNumber(record['latestCursor']);
290
219
  const processId = readNullableNumber(record['processId']);
291
220
  const attachedClients = readNumber(record['attachedClients']);
@@ -8,7 +8,7 @@ import type { AgentStatusProjectionInput, AgentStatusReducer } from './agent-sta
8
8
 
9
9
  interface WorkProjection {
10
10
  readonly text: string | null;
11
- readonly phaseHint: 'needs-action' | 'working' | 'idle' | null;
11
+ readonly activityHint: 'needs-action' | 'working' | 'idle' | null;
12
12
  }
13
13
 
14
14
  function normalizeText(value: string | null): string | null {
@@ -40,7 +40,7 @@ function eventIsNewer(observedAt: string, previousObservedAt: string | null): bo
40
40
 
41
41
  function phaseFromRuntimeStatus(
42
42
  runtimeStatus: StreamSessionRuntimeStatus,
43
- phaseHint: WorkProjection['phaseHint'],
43
+ activityHint: WorkProjection['activityHint'],
44
44
  ): StreamSessionDisplayPhase {
45
45
  if (runtimeStatus === 'needs-input') {
46
46
  return 'needs-action';
@@ -48,13 +48,13 @@ function phaseFromRuntimeStatus(
48
48
  if (runtimeStatus === 'exited') {
49
49
  return 'exited';
50
50
  }
51
- if (phaseHint === 'working') {
51
+ if (activityHint === 'working') {
52
52
  return 'working';
53
53
  }
54
- if (phaseHint === 'needs-action') {
54
+ if (activityHint === 'needs-action') {
55
55
  return 'needs-action';
56
56
  }
57
- if (phaseHint === 'idle') {
57
+ if (activityHint === 'idle') {
58
58
  return 'idle';
59
59
  }
60
60
  if (runtimeStatus === 'running') {
@@ -118,14 +118,14 @@ export abstract class BaseAgentStatusReducer implements AgentStatusReducer {
118
118
  project(input: AgentStatusProjectionInput): StreamSessionStatusModel | null {
119
119
  const previous = input.previous;
120
120
  let workText = previous?.lastKnownWork ?? null;
121
- let workPhaseHint = previous?.phaseHint ?? null;
121
+ let workActivityHint = previous?.activityHint ?? null;
122
122
  let workObservedAt = previous?.lastKnownWorkAt ?? null;
123
123
 
124
124
  if (input.telemetry !== null && eventIsNewer(input.telemetry.observedAt, workObservedAt)) {
125
125
  const projected = this.projectFromTelemetry(input.telemetry);
126
126
  if (projected !== null) {
127
127
  workText = projected.text;
128
- workPhaseHint = projected.phaseHint;
128
+ workActivityHint = projected.activityHint;
129
129
  workObservedAt = input.telemetry.observedAt;
130
130
  }
131
131
  }
@@ -133,19 +133,19 @@ export abstract class BaseAgentStatusReducer implements AgentStatusReducer {
133
133
  if (
134
134
  input.runtimeStatus === 'completed' &&
135
135
  eventIsNewer(input.observedAt, workObservedAt) &&
136
- workPhaseHint !== 'needs-action'
136
+ workActivityHint !== 'needs-action'
137
137
  ) {
138
138
  workText = 'inactive';
139
- workPhaseHint = 'idle';
139
+ workActivityHint = 'idle';
140
140
  workObservedAt = input.observedAt;
141
141
  }
142
142
  if (input.runtimeStatus === 'exited' && eventIsNewer(input.observedAt, workObservedAt)) {
143
143
  workText = 'exited';
144
- workPhaseHint = 'idle';
144
+ workActivityHint = 'idle';
145
145
  workObservedAt = input.observedAt;
146
146
  }
147
147
 
148
- const phase = phaseFromRuntimeStatus(input.runtimeStatus, workPhaseHint);
148
+ const phase = phaseFromRuntimeStatus(input.runtimeStatus, workActivityHint);
149
149
  const normalizedAttentionReason = normalizeText(input.attentionReason);
150
150
  const detailText =
151
151
  (input.runtimeStatus === 'needs-input' ? normalizedAttentionReason : null) ??
@@ -162,7 +162,7 @@ export abstract class BaseAgentStatusReducer implements AgentStatusReducer {
162
162
  attentionReason: normalizedAttentionReason,
163
163
  lastKnownWork: workText,
164
164
  lastKnownWorkAt: workObservedAt,
165
- phaseHint: workPhaseHint,
165
+ activityHint: workActivityHint,
166
166
  observedAt: input.observedAt,
167
167
  };
168
168
  }
@@ -14,12 +14,12 @@ export class ClaudeStatusReducer extends BaseAgentStatusReducer {
14
14
 
15
15
  protected override projectFromTelemetry(
16
16
  telemetry: StreamTelemetrySummary,
17
- ): { text: string | null; phaseHint: 'needs-action' | 'working' | 'idle' | null } | null {
17
+ ): { text: string | null; activityHint: 'needs-action' | 'working' | 'idle' | null } | null {
18
18
  const eventName = normalize(telemetry.eventName);
19
19
  if (eventName === 'claude.userpromptsubmit' || eventName === 'claude.pretooluse') {
20
20
  return {
21
21
  text: 'active',
22
- phaseHint: 'working',
22
+ activityHint: 'working',
23
23
  };
24
24
  }
25
25
  if (
@@ -29,7 +29,7 @@ export class ClaudeStatusReducer extends BaseAgentStatusReducer {
29
29
  ) {
30
30
  return {
31
31
  text: 'inactive',
32
- phaseHint: 'idle',
32
+ activityHint: 'idle',
33
33
  };
34
34
  }
35
35
  return null;
@@ -14,19 +14,19 @@ export class CodexStatusReducer extends BaseAgentStatusReducer {
14
14
 
15
15
  protected override projectFromTelemetry(
16
16
  telemetry: StreamTelemetrySummary,
17
- ): { text: string | null; phaseHint: 'needs-action' | 'working' | 'idle' | null } | null {
17
+ ): { text: string | null; activityHint: 'needs-action' | 'working' | 'idle' | null } | null {
18
18
  const eventName = normalize(telemetry.eventName);
19
19
  const summary = normalize(telemetry.summary);
20
20
  if (eventName === 'codex.user_prompt') {
21
21
  return {
22
22
  text: 'active',
23
- phaseHint: 'working',
23
+ activityHint: 'working',
24
24
  };
25
25
  }
26
26
  if (eventName === 'codex.turn.e2e_duration_ms') {
27
27
  return {
28
28
  text: 'inactive',
29
- phaseHint: 'idle',
29
+ activityHint: 'idle',
30
30
  };
31
31
  }
32
32
  if (eventName === 'codex.sse_event') {
@@ -39,7 +39,7 @@ export class CodexStatusReducer extends BaseAgentStatusReducer {
39
39
  ) {
40
40
  return {
41
41
  text: 'active',
42
- phaseHint: 'working',
42
+ activityHint: 'working',
43
43
  };
44
44
  }
45
45
  }
@@ -14,7 +14,7 @@ export class CursorStatusReducer extends BaseAgentStatusReducer {
14
14
 
15
15
  protected override projectFromTelemetry(
16
16
  telemetry: StreamTelemetrySummary,
17
- ): { text: string | null; phaseHint: 'needs-action' | 'working' | 'idle' | null } | null {
17
+ ): { text: string | null; activityHint: 'needs-action' | 'working' | 'idle' | null } | null {
18
18
  const eventName = normalize(telemetry.eventName);
19
19
  if (
20
20
  eventName === 'cursor.beforesubmitprompt' ||
@@ -23,13 +23,13 @@ export class CursorStatusReducer extends BaseAgentStatusReducer {
23
23
  ) {
24
24
  return {
25
25
  text: 'active',
26
- phaseHint: 'working',
26
+ activityHint: 'working',
27
27
  };
28
28
  }
29
29
  if (eventName === 'cursor.stop' || eventName === 'cursor.sessionend') {
30
30
  return {
31
31
  text: 'inactive',
32
- phaseHint: 'idle',
32
+ activityHint: 'idle',
33
33
  };
34
34
  }
35
35
  return null;
@@ -26,7 +26,17 @@ interface PendingCommand {
26
26
  reject: (error: Error) => void;
27
27
  }
28
28
 
29
- export class ControlPlaneStreamClient {
29
+ export interface ControlPlaneStreamClient {
30
+ onEnvelope(listener: (envelope: StreamServerEnvelope) => void): () => void;
31
+ sendCommand(command: StreamCommand): Promise<Record<string, unknown>>;
32
+ authenticate(token: string): Promise<void>;
33
+ sendInput(sessionId: string, data: Buffer): void;
34
+ sendResize(sessionId: string, cols: number, rows: number): void;
35
+ sendSignal(sessionId: string, signal: StreamSignal): void;
36
+ close(): void;
37
+ }
38
+
39
+ class ControlPlaneStreamClientImpl implements ControlPlaneStreamClient {
30
40
  private readonly socket: Socket;
31
41
  private readonly listeners = new Set<(envelope: StreamServerEnvelope) => void>();
32
42
  private readonly pending = new Map<string, PendingCommand>();
@@ -380,7 +390,7 @@ export async function connectControlPlaneStreamClient(
380
390
  }
381
391
  }
382
392
 
383
- const client = new ControlPlaneStreamClient(socket);
393
+ const client = new ControlPlaneStreamClientImpl(socket);
384
394
  if (typeof options.authToken === 'string') {
385
395
  await client.authenticate(options.authToken);
386
396
  }