@jmoyers/harness 0.1.11 → 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 (232) hide show
  1. package/README.md +31 -39
  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/packages/harness-ui/src/modal-manager.ts +222 -0
  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 -3872
  38. package/scripts/control-plane-daemon.ts +11 -0
  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 -3019
  43. package/scripts/nim-tui-smoke.ts +748 -0
  44. package/src/cli/auth/runtime.ts +948 -0
  45. package/src/cli/gateway/runtime.ts +1872 -0
  46. package/src/cli/parsing/flags.ts +23 -0
  47. package/src/cli/parsing/session.ts +42 -0
  48. package/src/cli/runtime/context.ts +193 -0
  49. package/src/cli/runtime-app/application.ts +392 -0
  50. package/src/cli/runtime-infra/gateway-control.ts +729 -0
  51. package/{scripts/harness-inspector.ts → src/cli/workflows/inspector.ts} +14 -11
  52. package/src/cli/workflows/runtime.ts +965 -0
  53. package/src/clients/tui/left-rail-interactions.ts +519 -0
  54. package/src/clients/tui/main-pane-interactions.ts +509 -0
  55. package/src/clients/tui/modal-input-routing.ts +71 -0
  56. package/src/clients/tui/render-snapshot-adapter.ts +88 -0
  57. package/src/clients/web/synced-selectors.ts +132 -0
  58. package/src/codex/live-session.ts +82 -29
  59. package/src/config/config-core.ts +348 -8
  60. package/src/config/harness.config.template.jsonc +33 -0
  61. package/src/control-plane/agent-realtime-api.ts +82 -427
  62. package/src/control-plane/session-summary.ts +10 -81
  63. package/src/control-plane/status/reducer-base.ts +12 -12
  64. package/src/control-plane/status/reducers/claude-status-reducer.ts +3 -3
  65. package/src/control-plane/status/reducers/codex-status-reducer.ts +4 -4
  66. package/src/control-plane/status/reducers/cursor-status-reducer.ts +3 -3
  67. package/src/control-plane/stream-client.ts +12 -2
  68. package/src/control-plane/stream-command-parser.ts +83 -143
  69. package/src/control-plane/stream-protocol.ts +53 -37
  70. package/src/control-plane/stream-server-command.ts +376 -69
  71. package/src/control-plane/stream-server-session-runtime.ts +3 -2
  72. package/src/control-plane/stream-server.ts +864 -70
  73. package/src/control-plane/stream-session-runtime-types.ts +41 -0
  74. package/src/{mux/live-mux/control-plane-records.ts → core/contracts/records.ts} +24 -97
  75. package/src/core/state/observed-stream-cursor.ts +43 -0
  76. package/src/core/state/synced-observed-state.ts +273 -0
  77. package/src/core/store/harness-synced-store.ts +81 -0
  78. package/src/diff/budget.ts +136 -0
  79. package/src/diff/build.ts +289 -0
  80. package/src/diff/chunker.ts +146 -0
  81. package/src/diff/git-invoke.ts +315 -0
  82. package/src/diff/git-parse.ts +472 -0
  83. package/src/diff/hash.ts +70 -0
  84. package/src/diff/index.ts +24 -0
  85. package/src/diff/normalize.ts +134 -0
  86. package/src/diff/types.ts +178 -0
  87. package/src/diff-ui/args.ts +346 -0
  88. package/src/diff-ui/commands.ts +123 -0
  89. package/src/diff-ui/finder.ts +94 -0
  90. package/src/diff-ui/highlight.ts +127 -0
  91. package/src/diff-ui/index.ts +2 -0
  92. package/src/diff-ui/model.ts +141 -0
  93. package/src/diff-ui/pager.ts +412 -0
  94. package/src/diff-ui/render.ts +337 -0
  95. package/src/diff-ui/runtime.ts +379 -0
  96. package/src/diff-ui/state.ts +224 -0
  97. package/src/diff-ui/types.ts +236 -0
  98. package/src/domain/workspace.ts +68 -5
  99. package/src/mux/control-plane-op-queue.ts +93 -7
  100. package/src/mux/conversation-rail.ts +28 -71
  101. package/src/mux/dual-pane-core.ts +13 -13
  102. package/src/mux/harness-core-ui.ts +313 -42
  103. package/src/mux/input-shortcuts.ts +13 -131
  104. package/src/mux/keybinding-catalog.ts +340 -0
  105. package/src/mux/keybinding-registry.ts +103 -0
  106. package/src/mux/live-mux/command-menu-open-in.ts +280 -0
  107. package/src/mux/live-mux/command-menu.ts +167 -4
  108. package/src/mux/live-mux/conversation-state.ts +13 -0
  109. package/src/mux/live-mux/directory-resolution.ts +1 -1
  110. package/src/mux/live-mux/git-snapshot.ts +33 -2
  111. package/src/mux/live-mux/global-shortcut-handlers.ts +6 -0
  112. package/src/mux/live-mux/home-pane-drop.ts +1 -1
  113. package/src/mux/live-mux/home-pane-pointer.ts +10 -0
  114. package/src/mux/live-mux/input-forwarding.ts +59 -2
  115. package/src/mux/live-mux/left-nav-activation.ts +124 -7
  116. package/src/mux/live-mux/left-nav.ts +35 -0
  117. package/src/mux/live-mux/link-click.ts +292 -0
  118. package/src/mux/live-mux/modal-command-menu-handler.ts +46 -9
  119. package/src/mux/live-mux/modal-conversation-handlers.ts +5 -1
  120. package/src/mux/live-mux/modal-input-reducers.ts +77 -12
  121. package/src/mux/live-mux/modal-overlays.ts +168 -34
  122. package/src/mux/live-mux/modal-pointer.ts +3 -7
  123. package/src/mux/live-mux/modal-prompt-handlers.ts +23 -2
  124. package/src/mux/live-mux/modal-release-notes-handler.ts +111 -0
  125. package/src/mux/live-mux/modal-task-editor-handler.ts +16 -11
  126. package/src/mux/live-mux/pointer-routing.ts +5 -2
  127. package/src/mux/live-mux/project-pane-pointer.ts +8 -0
  128. package/src/mux/live-mux/rail-layout.ts +33 -30
  129. package/src/mux/live-mux/release-notes.ts +383 -0
  130. package/src/mux/live-mux/render-trace-analysis.ts +52 -7
  131. package/src/mux/live-mux/repository-folding.ts +3 -0
  132. package/src/mux/live-mux/selection.ts +0 -4
  133. package/src/mux/live-mux/session-diagnostics-paths.ts +21 -0
  134. package/src/mux/project-pane-github-review.ts +271 -0
  135. package/src/mux/render-frame.ts +4 -0
  136. package/src/mux/runtime-app/codex-live-mux-runtime.ts +5191 -0
  137. package/src/mux/task-composer.ts +21 -14
  138. package/src/mux/task-focused-pane.ts +118 -117
  139. package/src/mux/task-screen-keybindings.ts +10 -101
  140. package/src/mux/workspace-rail-model.ts +270 -104
  141. package/src/mux/workspace-rail.ts +45 -22
  142. package/src/pty/session-broker.ts +1 -1
  143. package/{scripts → src/recording}/terminal-recording-gif-lib.ts +2 -2
  144. package/src/services/control-plane.ts +50 -32
  145. package/src/services/conversation-lifecycle.ts +118 -87
  146. package/src/services/conversation-startup-hydration.ts +20 -12
  147. package/src/services/directory-hydration.ts +21 -16
  148. package/src/services/event-persistence.ts +7 -0
  149. package/src/services/left-rail-pointer-handler.ts +329 -0
  150. package/src/services/mux-ui-state-persistence.ts +5 -1
  151. package/src/services/recording.ts +34 -26
  152. package/src/services/runtime-command-menu-agent-tools.ts +1 -1
  153. package/src/services/runtime-control-actions.ts +79 -61
  154. package/src/services/runtime-control-plane-ops.ts +122 -83
  155. package/src/services/runtime-conversation-actions.ts +40 -26
  156. package/src/services/runtime-conversation-activation.ts +73 -46
  157. package/src/services/runtime-conversation-starter.ts +53 -45
  158. package/src/services/runtime-conversation-title-edit.ts +91 -80
  159. package/src/services/runtime-envelope-handler.ts +107 -105
  160. package/src/services/runtime-git-state.ts +42 -29
  161. package/src/services/runtime-layout-resize.ts +3 -1
  162. package/src/services/runtime-left-rail-render.ts +99 -63
  163. package/src/services/runtime-nim-cli-session.ts +438 -0
  164. package/src/services/runtime-nim-session.ts +705 -0
  165. package/src/services/runtime-nim-tool-bridge.ts +141 -0
  166. package/src/services/runtime-observed-event-projection-pipeline.ts +45 -0
  167. package/src/services/runtime-process-wiring.ts +29 -36
  168. package/src/services/runtime-project-pane-github-review-cache.ts +164 -0
  169. package/src/services/runtime-render-flush.ts +63 -70
  170. package/src/services/runtime-render-lifecycle.ts +65 -64
  171. package/src/services/runtime-render-orchestrator.ts +55 -45
  172. package/src/services/runtime-render-pipeline.ts +106 -103
  173. package/src/services/runtime-render-state.ts +62 -49
  174. package/src/services/runtime-repository-actions.ts +97 -72
  175. package/src/services/runtime-right-pane-render.ts +80 -53
  176. package/src/services/runtime-shutdown.ts +38 -35
  177. package/src/services/runtime-stream-subscriptions.ts +35 -27
  178. package/src/services/runtime-task-composer-persistence.ts +71 -59
  179. package/src/services/runtime-task-composer-snapshot.ts +14 -0
  180. package/src/services/runtime-task-editor-actions.ts +46 -29
  181. package/src/services/runtime-task-pane-actions.ts +220 -134
  182. package/src/services/runtime-task-pane-shortcuts.ts +323 -123
  183. package/src/services/runtime-workspace-observed-effect-queue.ts +25 -0
  184. package/src/services/runtime-workspace-observed-events.ts +33 -184
  185. package/src/services/runtime-workspace-observed-transition-policy.ts +228 -0
  186. package/src/services/session-diagnostics-store.ts +217 -0
  187. package/src/services/startup-background-resume.ts +26 -21
  188. package/src/services/startup-orchestrator.ts +16 -13
  189. package/src/services/startup-paint-tracker.ts +29 -21
  190. package/src/services/startup-persisted-conversation-queue.ts +19 -13
  191. package/src/services/startup-settled-gate.ts +25 -15
  192. package/src/services/startup-shutdown.ts +18 -22
  193. package/src/services/startup-state-hydration.ts +44 -34
  194. package/src/services/startup-visibility.ts +12 -4
  195. package/src/services/task-pane-selection-actions.ts +89 -72
  196. package/src/services/task-planning-hydration.ts +24 -18
  197. package/src/services/task-planning-observed-events.ts +50 -52
  198. package/src/services/workspace-observed-events.ts +66 -63
  199. package/src/storage/storage-lifecycle-core.ts +438 -0
  200. package/src/store/control-plane-store-normalize.ts +33 -242
  201. package/src/store/control-plane-store-types.ts +1 -35
  202. package/src/store/control-plane-store.ts +360 -56
  203. package/src/store/event-store.ts +366 -8
  204. package/src/terminal/snapshot-oracle.ts +207 -94
  205. package/src/ui/mux-theme.ts +112 -8
  206. package/src/ui/panes/home-gridfire.ts +40 -31
  207. package/src/ui/panes/home.ts +10 -2
  208. package/src/ui/panes/nim.ts +315 -0
  209. package/src/mux/live-mux/actions-task.ts +0 -115
  210. package/src/mux/live-mux/left-rail-actions.ts +0 -118
  211. package/src/mux/live-mux/left-rail-conversation-click.ts +0 -85
  212. package/src/mux/live-mux/left-rail-pointer.ts +0 -74
  213. package/src/mux/live-mux/task-pane-shortcuts.ts +0 -206
  214. package/src/services/runtime-directory-actions.ts +0 -164
  215. package/src/services/runtime-input-pipeline.ts +0 -50
  216. package/src/services/runtime-input-router.ts +0 -195
  217. package/src/services/runtime-main-pane-input.ts +0 -230
  218. package/src/services/runtime-modal-input.ts +0 -137
  219. package/src/services/runtime-navigation-input.ts +0 -197
  220. package/src/services/runtime-rail-input.ts +0 -279
  221. package/src/services/runtime-task-pane.ts +0 -62
  222. package/src/services/runtime-workspace-actions.ts +0 -158
  223. package/src/ui/conversation-input-forwarder.ts +0 -114
  224. package/src/ui/conversation-selection-input.ts +0 -103
  225. package/src/ui/global-shortcut-input.ts +0 -89
  226. package/src/ui/input.ts +0 -269
  227. package/src/ui/kit.ts +0 -509
  228. package/src/ui/left-nav-input.ts +0 -80
  229. package/src/ui/left-rail-pointer-input.ts +0 -148
  230. package/src/ui/modals/manager.ts +0 -218
  231. package/src/ui/repository-fold-input.ts +0 -91
  232. package/src/ui/surface.ts +0 -224
@@ -10,6 +10,10 @@ import {
10
10
  import { homedir } from 'node:os';
11
11
  import { dirname, resolve } from 'node:path';
12
12
  import { fileURLToPath } from 'node:url';
13
+ import {
14
+ DEFAULT_STORAGE_LIFECYCLE_POLICY,
15
+ type StorageLifecyclePolicy,
16
+ } from '../storage/storage-lifecycle-core.ts';
13
17
 
14
18
  export const HARNESS_CONFIG_FILE_NAME = 'harness.config.jsonc';
15
19
  export const HARNESS_CONFIG_VERSION = 1;
@@ -41,13 +45,46 @@ const HARNESS_LIFECYCLE_EVENT_TYPES = [
41
45
 
42
46
  export type HarnessLifecycleEventType = (typeof HARNESS_LIFECYCLE_EVENT_TYPES)[number];
43
47
 
48
+ export const HARNESS_MUX_OPEN_IN_TARGET_IDS = [
49
+ 'iterm2',
50
+ 'ghostty',
51
+ 'zed',
52
+ 'cursor',
53
+ 'vscode',
54
+ 'warp',
55
+ 'finder',
56
+ ] as const;
57
+
58
+ export type HarnessMuxOpenInTargetId = (typeof HARNESS_MUX_OPEN_IN_TARGET_IDS)[number];
59
+
60
+ export interface HarnessMuxOpenInTargetOverrideConfig {
61
+ readonly enabled?: boolean;
62
+ readonly appName?: string;
63
+ readonly detectCommand?: string | null;
64
+ readonly launchCommand?: readonly string[];
65
+ }
66
+
67
+ interface HarnessMuxOpenInLinkConfig {
68
+ readonly browserCommand: readonly string[] | null;
69
+ readonly fileCommand: readonly string[] | null;
70
+ }
71
+
72
+ interface HarnessMuxOpenInConfig {
73
+ readonly targets: Readonly<
74
+ Partial<Record<HarnessMuxOpenInTargetId, HarnessMuxOpenInTargetOverrideConfig>>
75
+ >;
76
+ readonly links: HarnessMuxOpenInLinkConfig;
77
+ }
78
+
44
79
  interface HarnessMuxConfig {
45
80
  readonly keybindings: Readonly<Record<string, readonly string[]>>;
46
81
  readonly ui: HarnessMuxUiConfig;
47
82
  readonly git: HarnessMuxGitConfig;
83
+ readonly openIn: HarnessMuxOpenInConfig;
48
84
  }
49
85
 
50
86
  type HarnessMuxThemeMode = 'dark' | 'light';
87
+ type HarnessMuxStartupPane = 'home' | 'nim';
51
88
 
52
89
  export interface HarnessMuxThemeConfig {
53
90
  readonly preset: string;
@@ -59,6 +96,9 @@ interface HarnessMuxUiConfig {
59
96
  readonly paneWidthPercent: number | null;
60
97
  readonly repositoriesCollapsed: boolean;
61
98
  readonly shortcutsCollapsed: boolean;
99
+ readonly startupPane: HarnessMuxStartupPane;
100
+ readonly showTasks: boolean;
101
+ readonly showDebugBar: boolean;
62
102
  readonly theme: HarnessMuxThemeConfig | null;
63
103
  }
64
104
 
@@ -84,6 +124,16 @@ interface HarnessGitHubConfig {
84
124
  readonly viewerLogin: string | null;
85
125
  }
86
126
 
127
+ interface HarnessLinearConfig {
128
+ readonly enabled: boolean;
129
+ readonly apiBaseUrl: string;
130
+ readonly tokenEnvVar: string;
131
+ }
132
+
133
+ interface HarnessGatewayConfig {
134
+ readonly host: string;
135
+ }
136
+
87
137
  interface HarnessPerfConfig {
88
138
  readonly enabled: boolean;
89
139
  readonly filePath: string;
@@ -216,15 +266,24 @@ interface HarnessHooksConfig {
216
266
  readonly lifecycle: HarnessLifecycleHooksConfig;
217
267
  }
218
268
 
269
+ export type HarnessStorageLifecycleConfig = StorageLifecyclePolicy;
270
+
271
+ interface HarnessStorageConfig {
272
+ readonly lifecycle: HarnessStorageLifecycleConfig;
273
+ }
274
+
219
275
  interface HarnessConfig {
220
276
  readonly configVersion: number;
221
277
  readonly mux: HarnessMuxConfig;
222
278
  readonly github: HarnessGitHubConfig;
279
+ readonly gateway: HarnessGatewayConfig;
280
+ readonly linear: HarnessLinearConfig;
223
281
  readonly debug: HarnessDebugConfig;
224
282
  readonly codex: HarnessCodexConfig;
225
283
  readonly claude: HarnessClaudeConfig;
226
284
  readonly cursor: HarnessCursorConfig;
227
285
  readonly critique: HarnessCritiqueConfig;
286
+ readonly storage: HarnessStorageConfig;
228
287
  readonly hooks: HarnessHooksConfig;
229
288
  }
230
289
 
@@ -243,6 +302,9 @@ export const DEFAULT_HARNESS_CONFIG: HarnessConfig = {
243
302
  paneWidthPercent: null,
244
303
  repositoriesCollapsed: false,
245
304
  shortcutsCollapsed: false,
305
+ startupPane: 'home',
306
+ showTasks: false,
307
+ showDebugBar: false,
246
308
  theme: null,
247
309
  },
248
310
  git: {
@@ -254,6 +316,13 @@ export const DEFAULT_HARNESS_CONFIG: HarnessConfig = {
254
316
  triggerDebounceMs: 180,
255
317
  maxConcurrency: 1,
256
318
  },
319
+ openIn: {
320
+ targets: {},
321
+ links: {
322
+ browserCommand: null,
323
+ fileCommand: null,
324
+ },
325
+ },
257
326
  },
258
327
  github: {
259
328
  enabled: true,
@@ -264,6 +333,14 @@ export const DEFAULT_HARNESS_CONFIG: HarnessConfig = {
264
333
  branchStrategy: 'pinned-then-current',
265
334
  viewerLogin: null,
266
335
  },
336
+ gateway: {
337
+ host: '127.0.0.1',
338
+ },
339
+ linear: {
340
+ enabled: true,
341
+ apiBaseUrl: 'https://api.linear.app/graphql',
342
+ tokenEnvVar: 'LINEAR_API_KEY',
343
+ },
267
344
  debug: {
268
345
  enabled: true,
269
346
  overwriteArtifactsOnStart: true,
@@ -336,6 +413,11 @@ export const DEFAULT_HARNESS_CONFIG: HarnessConfig = {
336
413
  command: 'bun add --global critique@latest',
337
414
  },
338
415
  },
416
+ storage: {
417
+ lifecycle: {
418
+ ...DEFAULT_STORAGE_LIFECYCLE_POLICY,
419
+ },
420
+ },
339
421
  hooks: {
340
422
  lifecycle: {
341
423
  enabled: false,
@@ -539,6 +621,16 @@ function normalizeMuxThemeMode(value: unknown, fallback: HarnessMuxThemeMode): H
539
621
  return fallback;
540
622
  }
541
623
 
624
+ function normalizeMuxStartupPane(
625
+ value: unknown,
626
+ fallback: HarnessMuxStartupPane,
627
+ ): HarnessMuxStartupPane {
628
+ if (value === 'home' || value === 'nim') {
629
+ return value;
630
+ }
631
+ return fallback;
632
+ }
633
+
542
634
  function normalizeMuxThemeConfig(input: unknown): HarnessMuxThemeConfig | null {
543
635
  if (input === null || input === false) {
544
636
  return null;
@@ -574,18 +666,33 @@ function normalizeMuxUiConfig(input: unknown): HarnessMuxUiConfig {
574
666
  record['paneWidthPercent'],
575
667
  DEFAULT_HARNESS_CONFIG.mux.ui.paneWidthPercent,
576
668
  );
577
- const shortcutsCollapsed =
578
- typeof record['shortcutsCollapsed'] === 'boolean'
579
- ? record['shortcutsCollapsed']
580
- : DEFAULT_HARNESS_CONFIG.mux.ui.shortcutsCollapsed;
581
669
  const repositoriesCollapsed =
582
670
  typeof record['repositoriesCollapsed'] === 'boolean'
583
671
  ? record['repositoriesCollapsed']
584
672
  : DEFAULT_HARNESS_CONFIG.mux.ui.repositoriesCollapsed;
673
+ const shortcutsCollapsed =
674
+ typeof record['shortcutsCollapsed'] === 'boolean'
675
+ ? record['shortcutsCollapsed']
676
+ : DEFAULT_HARNESS_CONFIG.mux.ui.shortcutsCollapsed;
677
+ const startupPane = normalizeMuxStartupPane(
678
+ record['startupPane'],
679
+ DEFAULT_HARNESS_CONFIG.mux.ui.startupPane,
680
+ );
681
+ const showTasks =
682
+ typeof record['showTasks'] === 'boolean'
683
+ ? record['showTasks']
684
+ : DEFAULT_HARNESS_CONFIG.mux.ui.showTasks;
685
+ const showDebugBar =
686
+ typeof record['showDebugBar'] === 'boolean'
687
+ ? record['showDebugBar']
688
+ : DEFAULT_HARNESS_CONFIG.mux.ui.showDebugBar;
585
689
  return {
586
690
  paneWidthPercent,
587
691
  repositoriesCollapsed,
588
692
  shortcutsCollapsed,
693
+ startupPane,
694
+ showTasks,
695
+ showDebugBar,
589
696
  theme: normalizeMuxThemeConfig(record['theme']),
590
697
  };
591
698
  }
@@ -630,6 +737,113 @@ function normalizeMuxGitConfig(input: unknown): HarnessMuxGitConfig {
630
737
  };
631
738
  }
632
739
 
740
+ function normalizeMuxOpenInTargetOverride(
741
+ input: unknown,
742
+ ): HarnessMuxOpenInTargetOverrideConfig | null {
743
+ const record = asRecord(input);
744
+ if (record === null) {
745
+ return null;
746
+ }
747
+ const enabled = typeof record['enabled'] === 'boolean' ? record['enabled'] : undefined;
748
+ const appName =
749
+ typeof record['appName'] === 'string' && record['appName'].trim().length > 0
750
+ ? record['appName'].trim()
751
+ : undefined;
752
+ const detectCommand =
753
+ record['detectCommand'] === null
754
+ ? null
755
+ : typeof record['detectCommand'] === 'string' && record['detectCommand'].trim().length > 0
756
+ ? record['detectCommand'].trim()
757
+ : undefined;
758
+ const launchCommandFromArray = Array.isArray(record['launchCommand'])
759
+ ? record['launchCommand']
760
+ .flatMap((entry) => (typeof entry === 'string' ? [entry.trim()] : []))
761
+ .filter((entry) => entry.length > 0)
762
+ : null;
763
+ const launchCommand =
764
+ launchCommandFromArray !== null
765
+ ? launchCommandFromArray.length > 0
766
+ ? launchCommandFromArray
767
+ : undefined
768
+ : typeof record['launchCommand'] === 'string' && record['launchCommand'].trim().length > 0
769
+ ? [record['launchCommand'].trim()]
770
+ : undefined;
771
+ if (
772
+ enabled === undefined &&
773
+ appName === undefined &&
774
+ detectCommand === undefined &&
775
+ launchCommand === undefined
776
+ ) {
777
+ return null;
778
+ }
779
+ return {
780
+ ...(enabled === undefined ? {} : { enabled }),
781
+ ...(appName === undefined ? {} : { appName }),
782
+ ...(detectCommand === undefined ? {} : { detectCommand }),
783
+ ...(launchCommand === undefined ? {} : { launchCommand }),
784
+ };
785
+ }
786
+
787
+ function normalizeOpenInLinkCommand(
788
+ input: unknown,
789
+ fallback: readonly string[] | null,
790
+ ): readonly string[] | null {
791
+ if (input === null) {
792
+ return null;
793
+ }
794
+ if (Array.isArray(input)) {
795
+ const normalized = input
796
+ .flatMap((entry) => (typeof entry === 'string' ? [entry.trim()] : []))
797
+ .filter((entry) => entry.length > 0);
798
+ return normalized.length > 0 ? normalized : null;
799
+ }
800
+ if (typeof input === 'string' && input.trim().length > 0) {
801
+ return [input.trim()];
802
+ }
803
+ return fallback;
804
+ }
805
+
806
+ function normalizeMuxOpenInLinksConfig(input: unknown): HarnessMuxOpenInLinkConfig {
807
+ const record = asRecord(input);
808
+ if (record === null) {
809
+ return DEFAULT_HARNESS_CONFIG.mux.openIn.links;
810
+ }
811
+ return {
812
+ browserCommand: normalizeOpenInLinkCommand(
813
+ record['browserCommand'],
814
+ DEFAULT_HARNESS_CONFIG.mux.openIn.links.browserCommand,
815
+ ),
816
+ fileCommand: normalizeOpenInLinkCommand(
817
+ record['fileCommand'],
818
+ DEFAULT_HARNESS_CONFIG.mux.openIn.links.fileCommand,
819
+ ),
820
+ };
821
+ }
822
+
823
+ function normalizeMuxOpenInConfig(input: unknown): HarnessMuxOpenInConfig {
824
+ const record = asRecord(input);
825
+ if (record === null) {
826
+ return DEFAULT_HARNESS_CONFIG.mux.openIn;
827
+ }
828
+ const targetsRecord = asRecord(record['targets']);
829
+ const normalizedTargets: Partial<
830
+ Record<HarnessMuxOpenInTargetId, HarnessMuxOpenInTargetOverrideConfig>
831
+ > = {};
832
+ if (targetsRecord !== null) {
833
+ for (const targetId of HARNESS_MUX_OPEN_IN_TARGET_IDS) {
834
+ const raw = targetsRecord[targetId];
835
+ const normalized = normalizeMuxOpenInTargetOverride(raw);
836
+ if (normalized !== null) {
837
+ normalizedTargets[targetId] = normalized;
838
+ }
839
+ }
840
+ }
841
+ return {
842
+ targets: normalizedTargets,
843
+ links: normalizeMuxOpenInLinksConfig(record['links']),
844
+ };
845
+ }
846
+
633
847
  function normalizeGitHubBranchStrategy(value: unknown): HarnessGitHubBranchStrategy {
634
848
  if (value === 'current-only' || value === 'pinned-only' || value === 'pinned-then-current') {
635
849
  return value;
@@ -680,6 +894,41 @@ function normalizeGitHubConfig(input: unknown): HarnessGitHubConfig {
680
894
  };
681
895
  }
682
896
 
897
+ function normalizeGatewayConfig(input: unknown): HarnessGatewayConfig {
898
+ const record = asRecord(input);
899
+ if (record === null) {
900
+ return DEFAULT_HARNESS_CONFIG.gateway;
901
+ }
902
+ return {
903
+ host: normalizeHost(record['host'], DEFAULT_HARNESS_CONFIG.gateway.host),
904
+ };
905
+ }
906
+
907
+ function normalizeLinearConfig(input: unknown): HarnessLinearConfig {
908
+ const record = asRecord(input);
909
+ if (record === null) {
910
+ return DEFAULT_HARNESS_CONFIG.linear;
911
+ }
912
+ const tokenEnvVarRaw = record['tokenEnvVar'];
913
+ const tokenEnvVar =
914
+ typeof tokenEnvVarRaw === 'string' && tokenEnvVarRaw.trim().length > 0
915
+ ? tokenEnvVarRaw.trim()
916
+ : DEFAULT_HARNESS_CONFIG.linear.tokenEnvVar;
917
+ const apiBaseUrlRaw = record['apiBaseUrl'];
918
+ const apiBaseUrl =
919
+ typeof apiBaseUrlRaw === 'string' && apiBaseUrlRaw.trim().length > 0
920
+ ? apiBaseUrlRaw.trim().replace(/\/+$/u, '')
921
+ : DEFAULT_HARNESS_CONFIG.linear.apiBaseUrl;
922
+ return {
923
+ enabled:
924
+ typeof record['enabled'] === 'boolean'
925
+ ? record['enabled']
926
+ : DEFAULT_HARNESS_CONFIG.linear.enabled,
927
+ apiBaseUrl,
928
+ tokenEnvVar,
929
+ };
930
+ }
931
+
683
932
  function normalizePerfConfig(input: unknown): HarnessPerfConfig {
684
933
  const record = asRecord(input);
685
934
  if (record === null) {
@@ -710,6 +959,14 @@ function normalizeNonNegativeInt(value: unknown, fallback: number): number {
710
959
  return normalized;
711
960
  }
712
961
 
962
+ function normalizePositiveInt(value: unknown, fallback: number): number {
963
+ const normalized = normalizeNonNegativeInt(value, fallback);
964
+ if (normalized < 1) {
965
+ return fallback;
966
+ }
967
+ return normalized;
968
+ }
969
+
713
970
  function normalizeDebugMuxConfig(input: unknown): HarnessDebugMuxConfig {
714
971
  const record = asRecord(input);
715
972
  if (record === null) {
@@ -1305,6 +1562,69 @@ function normalizeLifecycleWebhookConfig(
1305
1562
  };
1306
1563
  }
1307
1564
 
1565
+ function normalizeStorageLifecycleConfig(input: unknown): HarnessStorageLifecycleConfig {
1566
+ const record = asRecord(input);
1567
+ if (record === null) {
1568
+ return DEFAULT_HARNESS_CONFIG.storage.lifecycle;
1569
+ }
1570
+ return {
1571
+ eventRetentionMs: normalizePositiveInt(
1572
+ record['eventRetentionMs'],
1573
+ DEFAULT_HARNESS_CONFIG.storage.lifecycle.eventRetentionMs,
1574
+ ),
1575
+ telemetryRetentionMs: normalizePositiveInt(
1576
+ record['telemetryRetentionMs'],
1577
+ DEFAULT_HARNESS_CONFIG.storage.lifecycle.telemetryRetentionMs,
1578
+ ),
1579
+ maintenanceIntervalMs: normalizePositiveInt(
1580
+ record['maintenanceIntervalMs'],
1581
+ DEFAULT_HARNESS_CONFIG.storage.lifecycle.maintenanceIntervalMs,
1582
+ ),
1583
+ pruneBatchSize: normalizePositiveInt(
1584
+ record['pruneBatchSize'],
1585
+ DEFAULT_HARNESS_CONFIG.storage.lifecycle.pruneBatchSize,
1586
+ ),
1587
+ compactFreelistPages: normalizePositiveInt(
1588
+ record['compactFreelistPages'],
1589
+ DEFAULT_HARNESS_CONFIG.storage.lifecycle.compactFreelistPages,
1590
+ ),
1591
+ copyForwardBatchSize: normalizePositiveInt(
1592
+ record['copyForwardBatchSize'],
1593
+ DEFAULT_HARNESS_CONFIG.storage.lifecycle.copyForwardBatchSize,
1594
+ ),
1595
+ copyForwardFinalizeTailRows: normalizePositiveInt(
1596
+ record['copyForwardFinalizeTailRows'],
1597
+ DEFAULT_HARNESS_CONFIG.storage.lifecycle.copyForwardFinalizeTailRows,
1598
+ ),
1599
+ telemetryPayloadMaxBytes: normalizePositiveInt(
1600
+ record['telemetryPayloadMaxBytes'],
1601
+ DEFAULT_HARNESS_CONFIG.storage.lifecycle.telemetryPayloadMaxBytes,
1602
+ ),
1603
+ textDeltaPayloadMaxBytes: normalizePositiveInt(
1604
+ record['textDeltaPayloadMaxBytes'],
1605
+ DEFAULT_HARNESS_CONFIG.storage.lifecycle.textDeltaPayloadMaxBytes,
1606
+ ),
1607
+ textDeltaCoalesceWindowMs: normalizePositiveInt(
1608
+ record['textDeltaCoalesceWindowMs'],
1609
+ DEFAULT_HARNESS_CONFIG.storage.lifecycle.textDeltaCoalesceWindowMs,
1610
+ ),
1611
+ busyTimeoutMs: normalizePositiveInt(
1612
+ record['busyTimeoutMs'],
1613
+ DEFAULT_HARNESS_CONFIG.storage.lifecycle.busyTimeoutMs,
1614
+ ),
1615
+ };
1616
+ }
1617
+
1618
+ function normalizeStorageConfig(input: unknown): HarnessStorageConfig {
1619
+ const record = asRecord(input);
1620
+ if (record === null) {
1621
+ return DEFAULT_HARNESS_CONFIG.storage;
1622
+ }
1623
+ return {
1624
+ lifecycle: normalizeStorageLifecycleConfig(record['lifecycle']),
1625
+ };
1626
+ }
1627
+
1308
1628
  function normalizeLifecycleHooksConfig(input: unknown): HarnessLifecycleHooksConfig {
1309
1629
  const record = asRecord(input);
1310
1630
  if (record === null) {
@@ -1372,12 +1692,15 @@ export function parseHarnessConfigText(text: string): HarnessConfig {
1372
1692
 
1373
1693
  const mux = asRecord(migratedRoot['mux']);
1374
1694
  const github = normalizeGitHubConfig(migratedRoot['github']);
1695
+ const gateway = normalizeGatewayConfig(migratedRoot['gateway']);
1696
+ const linear = normalizeLinearConfig(migratedRoot['linear']);
1375
1697
  const legacyPerf = normalizePerfConfig(migratedRoot['perf']);
1376
1698
  const debug = normalizeDebugConfig(migratedRoot['debug'], legacyPerf);
1377
1699
  const codex = normalizeCodexConfig(migratedRoot['codex']);
1378
1700
  const claude = normalizeClaudeConfig(migratedRoot['claude']);
1379
1701
  const cursor = normalizeCursorConfig(migratedRoot['cursor']);
1380
1702
  const critique = normalizeCritiqueConfig(migratedRoot['critique']);
1703
+ const storage = normalizeStorageConfig(migratedRoot['storage']);
1381
1704
  const hooks = normalizeLifecycleHooksConfig(asRecord(migratedRoot['hooks'])?.['lifecycle']);
1382
1705
 
1383
1706
  return {
@@ -1386,13 +1709,18 @@ export function parseHarnessConfigText(text: string): HarnessConfig {
1386
1709
  keybindings: mux === null ? {} : normalizeKeybindings(mux['keybindings']),
1387
1710
  ui: mux === null ? DEFAULT_HARNESS_CONFIG.mux.ui : normalizeMuxUiConfig(mux['ui']),
1388
1711
  git: mux === null ? DEFAULT_HARNESS_CONFIG.mux.git : normalizeMuxGitConfig(mux['git']),
1712
+ openIn:
1713
+ mux === null ? DEFAULT_HARNESS_CONFIG.mux.openIn : normalizeMuxOpenInConfig(mux['openIn']),
1389
1714
  },
1390
1715
  github,
1716
+ gateway,
1717
+ linear,
1391
1718
  debug,
1392
1719
  codex,
1393
1720
  claude,
1394
1721
  cursor,
1395
1722
  critique,
1723
+ storage,
1396
1724
  hooks: {
1397
1725
  lifecycle: hooks,
1398
1726
  },
@@ -1555,6 +1883,9 @@ export function updateHarnessMuxUiConfig(
1555
1883
  paneWidthPercent: number | null;
1556
1884
  repositoriesCollapsed: boolean;
1557
1885
  shortcutsCollapsed: boolean;
1886
+ startupPane: HarnessMuxStartupPane;
1887
+ showTasks: boolean;
1888
+ showDebugBar: boolean;
1558
1889
  }>,
1559
1890
  options?: {
1560
1891
  cwd?: string;
@@ -1573,14 +1904,20 @@ export function updateHarnessMuxUiConfig(
1573
1904
  update.paneWidthPercent === undefined
1574
1905
  ? current.mux.ui.paneWidthPercent
1575
1906
  : normalizePaneWidthPercent(update.paneWidthPercent, null);
1576
- const nextShortcutsCollapsed =
1577
- update.shortcutsCollapsed === undefined
1578
- ? current.mux.ui.shortcutsCollapsed
1579
- : update.shortcutsCollapsed;
1580
1907
  const nextRepositoriesCollapsed =
1581
1908
  update.repositoriesCollapsed === undefined
1582
1909
  ? current.mux.ui.repositoriesCollapsed
1583
1910
  : update.repositoriesCollapsed;
1911
+ const nextShortcutsCollapsed =
1912
+ update.shortcutsCollapsed === undefined
1913
+ ? current.mux.ui.shortcutsCollapsed
1914
+ : update.shortcutsCollapsed;
1915
+ const nextStartupPane =
1916
+ update.startupPane === undefined ? current.mux.ui.startupPane : update.startupPane;
1917
+ const nextShowTasks =
1918
+ update.showTasks === undefined ? current.mux.ui.showTasks : update.showTasks;
1919
+ const nextShowDebugBar =
1920
+ update.showDebugBar === undefined ? current.mux.ui.showDebugBar : update.showDebugBar;
1584
1921
  return {
1585
1922
  ...current,
1586
1923
  mux: {
@@ -1590,6 +1927,9 @@ export function updateHarnessMuxUiConfig(
1590
1927
  nextPaneWidthPercent === null ? null : roundUiPercent(nextPaneWidthPercent),
1591
1928
  repositoriesCollapsed: nextRepositoriesCollapsed,
1592
1929
  shortcutsCollapsed: nextShortcutsCollapsed,
1930
+ startupPane: nextStartupPane,
1931
+ showTasks: nextShowTasks,
1932
+ showDebugBar: nextShowDebugBar,
1593
1933
  theme: current.mux.ui.theme,
1594
1934
  },
1595
1935
  },
@@ -6,6 +6,9 @@
6
6
  "paneWidthPercent": null,
7
7
  "repositoriesCollapsed": false,
8
8
  "shortcutsCollapsed": false,
9
+ "startupPane": "home",
10
+ "showTasks": false,
11
+ "showDebugBar": false,
9
12
  "theme": null,
10
13
  },
11
14
  "git": {
@@ -17,6 +20,13 @@
17
20
  "triggerDebounceMs": 180,
18
21
  "maxConcurrency": 1,
19
22
  },
23
+ "openIn": {
24
+ "targets": {},
25
+ "links": {
26
+ "browserCommand": null,
27
+ "fileCommand": null,
28
+ },
29
+ },
20
30
  },
21
31
  "github": {
22
32
  "enabled": true,
@@ -27,6 +37,14 @@
27
37
  "branchStrategy": "pinned-then-current",
28
38
  "viewerLogin": null,
29
39
  },
40
+ "gateway": {
41
+ "host": "127.0.0.1",
42
+ },
43
+ "linear": {
44
+ "enabled": true,
45
+ "apiBaseUrl": "https://api.linear.app/graphql",
46
+ "tokenEnvVar": "LINEAR_API_KEY",
47
+ },
30
48
  "debug": {
31
49
  "enabled": true,
32
50
  "overwriteArtifactsOnStart": true,
@@ -90,6 +108,21 @@
90
108
  "command": "bun add --global critique@latest",
91
109
  },
92
110
  },
111
+ "storage": {
112
+ "lifecycle": {
113
+ "eventRetentionMs": 259200000,
114
+ "telemetryRetentionMs": 259200000,
115
+ "maintenanceIntervalMs": 5000,
116
+ "pruneBatchSize": 500,
117
+ "compactFreelistPages": 256,
118
+ "copyForwardBatchSize": 250,
119
+ "copyForwardFinalizeTailRows": 500,
120
+ "telemetryPayloadMaxBytes": 16384,
121
+ "textDeltaPayloadMaxBytes": 32768,
122
+ "textDeltaCoalesceWindowMs": 1200,
123
+ "busyTimeoutMs": 5000,
124
+ },
125
+ },
93
126
  "hooks": {
94
127
  "lifecycle": {
95
128
  "enabled": false,