@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
@@ -3,7 +3,6 @@ import type { StreamCommand } from './stream-protocol.ts';
3
3
  type StreamCommandType = StreamCommand['type'];
4
4
  type CommandRecord = Record<string, unknown>;
5
5
  type CommandParser = (record: CommandRecord) => StreamCommand | null;
6
- type ParsedTaskLinearInput = NonNullable<Extract<StreamCommand, { type: 'task.create' }>['linear']>;
7
6
  const INVALID_OPTIONAL = Symbol('invalid-optional');
8
7
 
9
8
  function asRecord(value: unknown): CommandRecord | null {
@@ -95,115 +94,6 @@ function readOptionalNullableString(
95
94
  return value;
96
95
  }
97
96
 
98
- function readOptionalNullableNonNegativeInteger(
99
- record: CommandRecord,
100
- field: string,
101
- ): number | null | undefined | typeof INVALID_OPTIONAL {
102
- const value = record[field];
103
- if (value === undefined) {
104
- return undefined;
105
- }
106
- if (value === null) {
107
- return null;
108
- }
109
- if (typeof value !== 'number' || !Number.isInteger(value) || value < 0) {
110
- return INVALID_OPTIONAL;
111
- }
112
- return value;
113
- }
114
-
115
- function parseTaskLinearInput(value: unknown): ParsedTaskLinearInput | null {
116
- const record = asRecord(value);
117
- if (record === null) {
118
- return null;
119
- }
120
- const issueId = readOptionalNullableString(record, 'issueId');
121
- const identifier = readOptionalNullableString(record, 'identifier');
122
- const url = readOptionalNullableString(record, 'url');
123
- const teamId = readOptionalNullableString(record, 'teamId');
124
- const projectId = readOptionalNullableString(record, 'projectId');
125
- const projectMilestoneId = readOptionalNullableString(record, 'projectMilestoneId');
126
- const cycleId = readOptionalNullableString(record, 'cycleId');
127
- const stateId = readOptionalNullableString(record, 'stateId');
128
- const assigneeId = readOptionalNullableString(record, 'assigneeId');
129
- const dueDate = readOptionalNullableString(record, 'dueDate');
130
- const priority = readOptionalNullableNonNegativeInteger(record, 'priority');
131
- const estimate = readOptionalNullableNonNegativeInteger(record, 'estimate');
132
- const labelsRaw = record['labelIds'];
133
- let labelIds: string[] | null | undefined;
134
- if (labelsRaw !== undefined) {
135
- if (labelsRaw === null) {
136
- labelIds = null;
137
- } else if (Array.isArray(labelsRaw) && labelsRaw.every((entry) => typeof entry === 'string')) {
138
- labelIds = [...labelsRaw];
139
- } else {
140
- return null;
141
- }
142
- }
143
-
144
- if (
145
- issueId === INVALID_OPTIONAL ||
146
- identifier === INVALID_OPTIONAL ||
147
- url === INVALID_OPTIONAL ||
148
- teamId === INVALID_OPTIONAL ||
149
- projectId === INVALID_OPTIONAL ||
150
- projectMilestoneId === INVALID_OPTIONAL ||
151
- cycleId === INVALID_OPTIONAL ||
152
- stateId === INVALID_OPTIONAL ||
153
- assigneeId === INVALID_OPTIONAL ||
154
- dueDate === INVALID_OPTIONAL ||
155
- priority === INVALID_OPTIONAL ||
156
- estimate === INVALID_OPTIONAL
157
- ) {
158
- return null;
159
- }
160
- if (priority !== undefined && priority !== null && priority > 4) {
161
- return null;
162
- }
163
-
164
- const out: ParsedTaskLinearInput = {};
165
- if (issueId !== undefined) {
166
- out.issueId = issueId;
167
- }
168
- if (identifier !== undefined) {
169
- out.identifier = identifier;
170
- }
171
- if (url !== undefined) {
172
- out.url = url;
173
- }
174
- if (teamId !== undefined) {
175
- out.teamId = teamId;
176
- }
177
- if (projectId !== undefined) {
178
- out.projectId = projectId;
179
- }
180
- if (projectMilestoneId !== undefined) {
181
- out.projectMilestoneId = projectMilestoneId;
182
- }
183
- if (cycleId !== undefined) {
184
- out.cycleId = cycleId;
185
- }
186
- if (stateId !== undefined) {
187
- out.stateId = stateId;
188
- }
189
- if (assigneeId !== undefined) {
190
- out.assigneeId = assigneeId;
191
- }
192
- if (priority !== undefined) {
193
- out.priority = priority as 0 | 1 | 2 | 3 | 4 | null;
194
- }
195
- if (estimate !== undefined) {
196
- out.estimate = estimate;
197
- }
198
- if (dueDate !== undefined) {
199
- out.dueDate = dueDate;
200
- }
201
- if (labelIds !== undefined) {
202
- out.labelIds = labelIds;
203
- }
204
- return out;
205
- }
206
-
207
97
  function parseSessionControllerType(value: unknown): 'human' | 'agent' | 'automation' | null {
208
98
  if (value === 'human' || value === 'agent' || value === 'automation') {
209
99
  return value;
@@ -586,32 +476,38 @@ function parseRepositoryArchive(record: CommandRecord): StreamCommand | null {
586
476
  }
587
477
 
588
478
  function parseTaskCreate(record: CommandRecord): StreamCommand | null {
589
- const title = readString(record['title']);
590
- if (title === null) {
479
+ const body = readString(record['body']);
480
+ if (body === null) {
591
481
  return null;
592
482
  }
483
+ const title = readOptionalNullableString(record, 'title');
593
484
  const taskId = readOptionalString(record, 'taskId');
594
485
  const tenantId = readOptionalString(record, 'tenantId');
595
486
  const userId = readOptionalString(record, 'userId');
596
487
  const workspaceId = readOptionalString(record, 'workspaceId');
597
488
  const repositoryId = readOptionalString(record, 'repositoryId');
598
489
  const projectId = readOptionalString(record, 'projectId');
599
- const description = readOptionalString(record, 'description');
600
490
  if (
491
+ title === INVALID_OPTIONAL ||
601
492
  taskId === undefined ||
602
493
  tenantId === undefined ||
603
494
  userId === undefined ||
604
495
  workspaceId === undefined ||
605
496
  repositoryId === undefined ||
606
- projectId === undefined ||
607
- description === undefined
497
+ projectId === undefined
608
498
  ) {
609
499
  return null;
610
500
  }
501
+ if (repositoryId === null && projectId === null) {
502
+ return null;
503
+ }
611
504
  const command: StreamCommand = {
612
505
  type: 'task.create',
613
- title,
506
+ body,
614
507
  };
508
+ if (title !== undefined) {
509
+ command.title = title;
510
+ }
615
511
  if (taskId !== null) {
616
512
  command.taskId = taskId;
617
513
  }
@@ -630,16 +526,6 @@ function parseTaskCreate(record: CommandRecord): StreamCommand | null {
630
526
  if (projectId !== null) {
631
527
  command.projectId = projectId;
632
528
  }
633
- if (description !== null) {
634
- command.description = description;
635
- }
636
- if (record['linear'] !== undefined) {
637
- const linear = parseTaskLinearInput(record['linear']);
638
- if (linear === null) {
639
- return null;
640
- }
641
- command.linear = linear;
642
- }
643
529
  return command;
644
530
  }
645
531
 
@@ -728,9 +614,9 @@ function parseTaskUpdate(record: CommandRecord): StreamCommand | null {
728
614
  if (taskId === null) {
729
615
  return null;
730
616
  }
731
- const title = readOptionalString(record, 'title');
732
- const description = readOptionalString(record, 'description');
733
- if (title === undefined || description === undefined) {
617
+ const title = readOptionalNullableString(record, 'title');
618
+ const body = readOptionalString(record, 'body');
619
+ if (title === INVALID_OPTIONAL || body === undefined) {
734
620
  return null;
735
621
  }
736
622
  let repositoryId: string | null | undefined;
@@ -753,16 +639,19 @@ function parseTaskUpdate(record: CommandRecord): StreamCommand | null {
753
639
  } else {
754
640
  return null;
755
641
  }
642
+ if (repositoryId === null && projectId === null) {
643
+ return null;
644
+ }
756
645
 
757
646
  const command: StreamCommand = {
758
647
  type: 'task.update',
759
648
  taskId,
760
649
  };
761
- if (title !== null) {
650
+ if (title !== undefined) {
762
651
  command.title = title;
763
652
  }
764
- if (description !== null) {
765
- command.description = description;
653
+ if (body !== null) {
654
+ command.body = body;
766
655
  }
767
656
  if (repositoryId !== undefined) {
768
657
  command.repositoryId = repositoryId;
@@ -770,17 +659,6 @@ function parseTaskUpdate(record: CommandRecord): StreamCommand | null {
770
659
  if (projectId !== undefined) {
771
660
  command.projectId = projectId;
772
661
  }
773
- if (record['linear'] !== undefined) {
774
- if (record['linear'] === null) {
775
- command.linear = null;
776
- } else {
777
- const linear = parseTaskLinearInput(record['linear']);
778
- if (linear === null) {
779
- return null;
780
- }
781
- command.linear = linear;
782
- }
783
- }
784
662
  return command;
785
663
  }
786
664
 
@@ -1109,6 +987,25 @@ function parseGitHubProjectPr(record: CommandRecord): StreamCommand | null {
1109
987
  };
1110
988
  }
1111
989
 
990
+ function parseGitHubProjectReview(record: CommandRecord): StreamCommand | null {
991
+ const directoryId = readString(record['directoryId']);
992
+ const forceRefresh = readOptionalBoolean(record, 'forceRefresh');
993
+ if (directoryId === null) {
994
+ return null;
995
+ }
996
+ if (forceRefresh === undefined) {
997
+ return null;
998
+ }
999
+ const command: StreamCommand = {
1000
+ type: 'github.project-review',
1001
+ directoryId,
1002
+ };
1003
+ if (forceRefresh !== null) {
1004
+ command.forceRefresh = forceRefresh;
1005
+ }
1006
+ return command;
1007
+ }
1008
+
1112
1009
  function parseGitHubPrList(record: CommandRecord): StreamCommand | null {
1113
1010
  const tenantId = readOptionalString(record, 'tenantId');
1114
1011
  const userId = readOptionalString(record, 'userId');
@@ -1256,6 +1153,47 @@ function parseGitHubRepoMyPrsUrl(record: CommandRecord): StreamCommand | null {
1256
1153
  };
1257
1154
  }
1258
1155
 
1156
+ function parseLinearIssueImport(record: CommandRecord): StreamCommand | null {
1157
+ const url = readString(record['url']);
1158
+ if (url === null) {
1159
+ return null;
1160
+ }
1161
+ const tenantId = readOptionalString(record, 'tenantId');
1162
+ const userId = readOptionalString(record, 'userId');
1163
+ const workspaceId = readOptionalString(record, 'workspaceId');
1164
+ const repositoryId = readOptionalString(record, 'repositoryId');
1165
+ const projectId = readOptionalString(record, 'projectId');
1166
+ if (
1167
+ tenantId === undefined ||
1168
+ userId === undefined ||
1169
+ workspaceId === undefined ||
1170
+ repositoryId === undefined ||
1171
+ projectId === undefined
1172
+ ) {
1173
+ return null;
1174
+ }
1175
+ const command: StreamCommand = {
1176
+ type: 'linear.issue.import',
1177
+ url,
1178
+ };
1179
+ if (tenantId !== null) {
1180
+ command.tenantId = tenantId;
1181
+ }
1182
+ if (userId !== null) {
1183
+ command.userId = userId;
1184
+ }
1185
+ if (workspaceId !== null) {
1186
+ command.workspaceId = workspaceId;
1187
+ }
1188
+ if (repositoryId !== null) {
1189
+ command.repositoryId = repositoryId;
1190
+ }
1191
+ if (projectId !== null) {
1192
+ command.projectId = projectId;
1193
+ }
1194
+ return command;
1195
+ }
1196
+
1259
1197
  function parseStreamSubscribe(record: CommandRecord): StreamCommand | null {
1260
1198
  const tenantId = readOptionalString(record, 'tenantId');
1261
1199
  const userId = readOptionalString(record, 'userId');
@@ -1664,10 +1602,12 @@ export const DEFAULT_STREAM_COMMAND_PARSERS: StreamCommandParserRegistry = {
1664
1602
  'automation.policy-get': parseAutomationPolicyGet,
1665
1603
  'automation.policy-set': parseAutomationPolicySet,
1666
1604
  'github.project-pr': parseGitHubProjectPr,
1605
+ 'github.project-review': parseGitHubProjectReview,
1667
1606
  'github.pr-list': parseGitHubPrList,
1668
1607
  'github.pr-create': parseGitHubPrCreate,
1669
1608
  'github.pr-jobs-list': parseGitHubPrJobsList,
1670
1609
  'github.repo-my-prs-url': parseGitHubRepoMyPrsUrl,
1610
+ 'linear.issue.import': parseLinearIssueImport,
1671
1611
  'stream.subscribe': parseStreamSubscribe,
1672
1612
  'stream.unsubscribe': parseStreamUnsubscribe,
1673
1613
  'session.list': parseSessionList,
@@ -10,6 +10,17 @@ export type StreamPromptCaptureSource = 'otlp-log' | 'hook-notify' | 'history';
10
10
  export type StreamPromptConfidence = 'high' | 'medium' | 'low';
11
11
  export type StreamSessionControllerType = 'human' | 'agent' | 'automation';
12
12
  export type StreamSessionDisplayPhase = 'needs-action' | 'starting' | 'working' | 'idle' | 'exited';
13
+ export type StreamSessionActivityHint = 'needs-action' | 'working' | 'idle';
14
+
15
+ export function isStreamSessionRuntimeStatus(value: unknown): value is StreamSessionRuntimeStatus {
16
+ return (
17
+ value === 'running' || value === 'needs-input' || value === 'completed' || value === 'exited'
18
+ );
19
+ }
20
+
21
+ export function parseStreamSessionRuntimeStatus(value: unknown): StreamSessionRuntimeStatus | null {
22
+ return isStreamSessionRuntimeStatus(value) ? value : null;
23
+ }
13
24
 
14
25
  export interface StreamSessionController {
15
26
  controllerId: string;
@@ -35,7 +46,7 @@ export interface StreamSessionStatusModel {
35
46
  attentionReason: string | null;
36
47
  lastKnownWork: string | null;
37
48
  lastKnownWorkAt: string | null;
38
- phaseHint: 'needs-action' | 'working' | 'idle' | null;
49
+ activityHint: StreamSessionActivityHint | null;
39
50
  observedAt: string;
40
51
  }
41
52
 
@@ -131,27 +142,10 @@ interface ConversationDeleteCommand {
131
142
 
132
143
  type StreamTaskStatus = 'draft' | 'ready' | 'in-progress' | 'completed';
133
144
  type StreamTaskScopeKind = 'global' | 'repository' | 'project';
134
- type StreamTaskLinearPriority = 0 | 1 | 2 | 3 | 4;
135
145
  type StreamProjectTaskFocusMode = 'balanced' | 'own-only';
136
146
  type StreamProjectThreadSpawnMode = 'new-thread' | 'reuse-thread';
137
147
  type StreamAutomationPolicyScope = 'global' | 'repository' | 'project';
138
148
 
139
- interface StreamTaskLinearInput {
140
- issueId?: string | null;
141
- identifier?: string | null;
142
- url?: string | null;
143
- teamId?: string | null;
144
- projectId?: string | null;
145
- projectMilestoneId?: string | null;
146
- cycleId?: string | null;
147
- stateId?: string | null;
148
- assigneeId?: string | null;
149
- priority?: StreamTaskLinearPriority | null;
150
- estimate?: number | null;
151
- dueDate?: string | null;
152
- labelIds?: readonly string[] | null;
153
- }
154
-
155
149
  interface RepositoryUpsertCommand {
156
150
  type: 'repository.upsert';
157
151
  repositoryId?: string;
@@ -200,9 +194,8 @@ interface TaskCreateCommand {
200
194
  workspaceId?: string;
201
195
  repositoryId?: string;
202
196
  projectId?: string;
203
- title: string;
204
- description?: string;
205
- linear?: StreamTaskLinearInput;
197
+ title?: string | null;
198
+ body: string;
206
199
  }
207
200
 
208
201
  interface TaskGetCommand {
@@ -225,11 +218,10 @@ interface TaskListCommand {
225
218
  interface TaskUpdateCommand {
226
219
  type: 'task.update';
227
220
  taskId: string;
228
- title?: string;
229
- description?: string;
221
+ title?: string | null;
222
+ body?: string;
230
223
  repositoryId?: string | null;
231
224
  projectId?: string | null;
232
- linear?: StreamTaskLinearInput | null;
233
225
  }
234
226
 
235
227
  interface TaskDeleteCommand {
@@ -329,6 +321,12 @@ interface GitHubProjectPrCommand {
329
321
  directoryId: string;
330
322
  }
331
323
 
324
+ interface GitHubProjectReviewCommand {
325
+ type: 'github.project-review';
326
+ directoryId: string;
327
+ forceRefresh?: boolean;
328
+ }
329
+
332
330
  interface GitHubPrListCommand {
333
331
  type: 'github.pr-list';
334
332
  tenantId?: string;
@@ -366,6 +364,16 @@ interface GitHubRepoMyPrsUrlCommand {
366
364
  repositoryId: string;
367
365
  }
368
366
 
367
+ interface LinearIssueImportCommand {
368
+ type: 'linear.issue.import';
369
+ url: string;
370
+ tenantId?: string;
371
+ userId?: string;
372
+ workspaceId?: string;
373
+ repositoryId?: string;
374
+ projectId?: string;
375
+ }
376
+
369
377
  interface StreamSubscribeCommand {
370
378
  type: 'stream.subscribe';
371
379
  tenantId?: string;
@@ -524,10 +532,12 @@ export type StreamCommand =
524
532
  | AutomationPolicyGetCommand
525
533
  | AutomationPolicySetCommand
526
534
  | GitHubProjectPrCommand
535
+ | GitHubProjectReviewCommand
527
536
  | GitHubPrListCommand
528
537
  | GitHubPrCreateCommand
529
538
  | GitHubPrJobsListCommand
530
539
  | GitHubRepoMyPrsUrlCommand
540
+ | LinearIssueImportCommand
531
541
  | StreamSubscribeCommand
532
542
  | StreamUnsubscribeCommand
533
543
  | SessionListCommand
@@ -1097,6 +1107,13 @@ function parsePromptConfidence(value: unknown): StreamPromptConfidence | null {
1097
1107
  return null;
1098
1108
  }
1099
1109
 
1110
+ function parseSessionActivityHint(value: unknown): StreamSessionActivityHint | undefined {
1111
+ if (value === 'needs-action' || value === 'working' || value === 'idle') {
1112
+ return value;
1113
+ }
1114
+ return undefined;
1115
+ }
1116
+
1100
1117
  function parseTelemetrySummary(value: unknown): StreamTelemetrySummary | null | undefined {
1101
1118
  if (value === undefined) {
1102
1119
  return undefined;
@@ -1131,7 +1148,9 @@ function parseTelemetrySummary(value: unknown): StreamTelemetrySummary | null |
1131
1148
  };
1132
1149
  }
1133
1150
 
1134
- function parseSessionStatusModel(value: unknown): StreamSessionStatusModel | null | undefined {
1151
+ export function parseStreamSessionStatusModel(
1152
+ value: unknown,
1153
+ ): StreamSessionStatusModel | null | undefined {
1135
1154
  if (value === undefined) {
1136
1155
  return undefined;
1137
1156
  }
@@ -1153,14 +1172,15 @@ function parseSessionStatusModel(value: unknown): StreamSessionStatusModel | nul
1153
1172
  record['lastKnownWork'] === null ? null : readString(record['lastKnownWork']);
1154
1173
  const lastKnownWorkAt =
1155
1174
  record['lastKnownWorkAt'] === null ? null : readString(record['lastKnownWorkAt']);
1156
- const phaseHint = record['phaseHint'] === null ? null : readString(record['phaseHint']);
1175
+ const activityHintValue = record['activityHint'];
1176
+ const activityHint =
1177
+ activityHintValue === undefined || activityHintValue === null
1178
+ ? null
1179
+ : parseSessionActivityHint(activityHintValue);
1157
1180
  const observedAt = readString(record['observedAt']);
1158
1181
  if (
1159
1182
  runtimeStatus === null ||
1160
- (runtimeStatus !== 'running' &&
1161
- runtimeStatus !== 'needs-input' &&
1162
- runtimeStatus !== 'completed' &&
1163
- runtimeStatus !== 'exited') ||
1183
+ !isStreamSessionRuntimeStatus(runtimeStatus) ||
1164
1184
  phase === null ||
1165
1185
  (phase !== 'needs-action' &&
1166
1186
  phase !== 'starting' &&
@@ -1175,11 +1195,7 @@ function parseSessionStatusModel(value: unknown): StreamSessionStatusModel | nul
1175
1195
  (attentionReason === null && record['attentionReason'] !== null) ||
1176
1196
  (lastKnownWork === null && record['lastKnownWork'] !== null) ||
1177
1197
  (lastKnownWorkAt === null && record['lastKnownWorkAt'] !== null) ||
1178
- (phaseHint === null && record['phaseHint'] !== null) ||
1179
- (phaseHint !== null &&
1180
- phaseHint !== 'needs-action' &&
1181
- phaseHint !== 'working' &&
1182
- phaseHint !== 'idle') ||
1198
+ activityHint === undefined ||
1183
1199
  observedAt === null
1184
1200
  ) {
1185
1201
  return undefined;
@@ -1193,7 +1209,7 @@ function parseSessionStatusModel(value: unknown): StreamSessionStatusModel | nul
1193
1209
  attentionReason,
1194
1210
  lastKnownWork,
1195
1211
  lastKnownWorkAt,
1196
- phaseHint,
1212
+ activityHint,
1197
1213
  observedAt,
1198
1214
  };
1199
1215
  }
@@ -1599,7 +1615,7 @@ function parseStreamObservedEvent(value: unknown): StreamObservedEvent | null {
1599
1615
  const sessionId = readString(record['sessionId']);
1600
1616
  const status = readString(record['status']);
1601
1617
  const attentionReason = readString(record['attentionReason']);
1602
- const statusModel = parseSessionStatusModel(record['statusModel']);
1618
+ const statusModel = parseStreamSessionStatusModel(record['statusModel']);
1603
1619
  const live = readBoolean(record['live']);
1604
1620
  const ts = readString(record['ts']);
1605
1621
  const directoryId = readString(record['directoryId']);
@@ -13,6 +13,16 @@ const HISTORY_POLL_JITTER_RATIO = 0.35;
13
13
  const HISTORY_POLL_MAX_DELAY_MS = 60_000;
14
14
  const LINE_FEED_BYTE = '\n'.charCodeAt(0);
15
15
 
16
+ function isClosedDatabaseError(error: unknown): boolean {
17
+ const message = error instanceof Error ? error.message : String(error);
18
+ const normalized = message.trim().toLowerCase();
19
+ return (
20
+ normalized.includes('database has closed') ||
21
+ normalized.includes('database is closed') ||
22
+ normalized.includes('cannot use a closed database')
23
+ );
24
+ }
25
+
16
26
  interface GitStatusSummary {
17
27
  branch: string | null;
18
28
  changedFiles: number;
@@ -215,7 +225,10 @@ export async function pollHistoryFile(ctx: BackgroundContext): Promise<void> {
215
225
  );
216
226
  ctx.historyNextAllowedPollAtMs = Date.now() + jitterDelayMs(backoffMs);
217
227
  }
218
- } catch {
228
+ } catch (error: unknown) {
229
+ if (isClosedDatabaseError(error)) {
230
+ throw error;
231
+ }
219
232
  ctx.historyIdleStreak = Math.min(ctx.historyIdleStreak + 1, 4);
220
233
  const backoffMs = Math.min(
221
234
  HISTORY_POLL_MAX_DELAY_MS,
@@ -467,7 +480,10 @@ export async function refreshGitStatusForDirectory(
467
480
  forcePublished: options.forcePublish ? 1 : 0,
468
481
  repositoryLinked: repositoryId === null ? 0 : 1,
469
482
  });
470
- } catch {
483
+ } catch (error: unknown) {
484
+ if (isClosedDatabaseError(error)) {
485
+ throw error;
486
+ }
471
487
  if (previous !== null) {
472
488
  ctx.gitStatusByDirectoryId.set(directory.directoryId, {
473
489
  ...previous,