@jmoyers/harness 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (214) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +145 -0
  3. package/native/ptyd/Cargo.lock +16 -0
  4. package/native/ptyd/Cargo.toml +7 -0
  5. package/native/ptyd/src/main.rs +257 -0
  6. package/package.json +90 -0
  7. package/scripts/build-ptyd.sh +73 -0
  8. package/scripts/control-plane-daemon.ts +277 -0
  9. package/scripts/cursor-hook-relay.ts +82 -0
  10. package/scripts/harness-animate.ts +469 -0
  11. package/scripts/harness-bin.js +77 -0
  12. package/scripts/harness-core.ts +1 -0
  13. package/scripts/harness-inspector.ts +439 -0
  14. package/scripts/harness.ts +2493 -0
  15. package/src/adapters/agent-session-state.ts +390 -0
  16. package/src/cli/gateway-record.ts +173 -0
  17. package/src/codex/live-session.ts +872 -0
  18. package/src/config/config-core.ts +1359 -0
  19. package/src/config/secrets-core.ts +170 -0
  20. package/src/control-plane/agent-realtime-api.ts +2441 -0
  21. package/src/control-plane/codex-session-stream.ts +392 -0
  22. package/src/control-plane/codex-telemetry.ts +1325 -0
  23. package/src/control-plane/lifecycle-hooks.ts +706 -0
  24. package/src/control-plane/session-summary.ts +380 -0
  25. package/src/control-plane/status/agent-status-reducer.ts +21 -0
  26. package/src/control-plane/status/reducer-base.ts +170 -0
  27. package/src/control-plane/status/reducers/claude-status-reducer.ts +37 -0
  28. package/src/control-plane/status/reducers/codex-status-reducer.ts +48 -0
  29. package/src/control-plane/status/reducers/critique-status-reducer.ts +15 -0
  30. package/src/control-plane/status/reducers/cursor-status-reducer.ts +37 -0
  31. package/src/control-plane/status/reducers/terminal-status-reducer.ts +15 -0
  32. package/src/control-plane/status/session-status-engine.ts +76 -0
  33. package/src/control-plane/stream-client.ts +396 -0
  34. package/src/control-plane/stream-command-parser.ts +1673 -0
  35. package/src/control-plane/stream-protocol.ts +1808 -0
  36. package/src/control-plane/stream-server-background.ts +486 -0
  37. package/src/control-plane/stream-server-command.ts +2557 -0
  38. package/src/control-plane/stream-server-connection.ts +234 -0
  39. package/src/control-plane/stream-server-observed-filter.ts +112 -0
  40. package/src/control-plane/stream-server-session-runtime.ts +566 -0
  41. package/src/control-plane/stream-server-state-store.ts +15 -0
  42. package/src/control-plane/stream-server.ts +3192 -0
  43. package/src/cursor/managed-hooks.ts +282 -0
  44. package/src/domain/conversations.ts +414 -0
  45. package/src/domain/directories.ts +78 -0
  46. package/src/domain/repositories.ts +123 -0
  47. package/src/domain/tasks.ts +148 -0
  48. package/src/domain/workspace.ts +156 -0
  49. package/src/events/normalized-events.ts +124 -0
  50. package/src/mux/ansi-integrity.ts +103 -0
  51. package/src/mux/control-plane-op-queue.ts +212 -0
  52. package/src/mux/conversation-rail.ts +339 -0
  53. package/src/mux/double-click.ts +78 -0
  54. package/src/mux/dual-pane-core.ts +435 -0
  55. package/src/mux/harness-core-ui.ts +817 -0
  56. package/src/mux/input-shortcuts.ts +667 -0
  57. package/src/mux/live-mux/actions-conversation.ts +344 -0
  58. package/src/mux/live-mux/actions-repository.ts +246 -0
  59. package/src/mux/live-mux/actions-task.ts +115 -0
  60. package/src/mux/live-mux/args.ts +142 -0
  61. package/src/mux/live-mux/command-menu.ts +298 -0
  62. package/src/mux/live-mux/control-plane-records.ts +546 -0
  63. package/src/mux/live-mux/conversation-state.ts +188 -0
  64. package/src/mux/live-mux/directory-resolution.ts +34 -0
  65. package/src/mux/live-mux/event-mapping.ts +96 -0
  66. package/src/mux/live-mux/gateway-profiler.ts +152 -0
  67. package/src/mux/live-mux/gateway-render-trace.ts +177 -0
  68. package/src/mux/live-mux/gateway-status-timeline.ts +166 -0
  69. package/src/mux/live-mux/git-parsing.ts +131 -0
  70. package/src/mux/live-mux/git-snapshot.ts +263 -0
  71. package/src/mux/live-mux/git-state.ts +136 -0
  72. package/src/mux/live-mux/global-shortcut-handlers.ts +143 -0
  73. package/src/mux/live-mux/home-pane-actions.ts +58 -0
  74. package/src/mux/live-mux/home-pane-drop.ts +44 -0
  75. package/src/mux/live-mux/home-pane-entity-click.ts +96 -0
  76. package/src/mux/live-mux/home-pane-pointer.ts +96 -0
  77. package/src/mux/live-mux/input-forwarding.ts +112 -0
  78. package/src/mux/live-mux/layout.ts +30 -0
  79. package/src/mux/live-mux/left-nav-activation.ts +103 -0
  80. package/src/mux/live-mux/left-nav.ts +85 -0
  81. package/src/mux/live-mux/left-rail-actions.ts +118 -0
  82. package/src/mux/live-mux/left-rail-conversation-click.ts +82 -0
  83. package/src/mux/live-mux/left-rail-pointer.ts +74 -0
  84. package/src/mux/live-mux/modal-command-menu-handler.ts +101 -0
  85. package/src/mux/live-mux/modal-conversation-handlers.ts +217 -0
  86. package/src/mux/live-mux/modal-input-reducers.ts +94 -0
  87. package/src/mux/live-mux/modal-overlays.ts +287 -0
  88. package/src/mux/live-mux/modal-pointer.ts +70 -0
  89. package/src/mux/live-mux/modal-prompt-handlers.ts +187 -0
  90. package/src/mux/live-mux/modal-task-editor-handler.ts +156 -0
  91. package/src/mux/live-mux/observed-stream.ts +87 -0
  92. package/src/mux/live-mux/palette-parsing.ts +128 -0
  93. package/src/mux/live-mux/pointer-routing.ts +108 -0
  94. package/src/mux/live-mux/process-usage.ts +53 -0
  95. package/src/mux/live-mux/project-pane-pointer.ts +44 -0
  96. package/src/mux/live-mux/rail-layout.ts +244 -0
  97. package/src/mux/live-mux/render-trace-analysis.ts +213 -0
  98. package/src/mux/live-mux/render-trace-state.ts +84 -0
  99. package/src/mux/live-mux/repository-folding.ts +207 -0
  100. package/src/mux/live-mux/runtime-shutdown.ts +51 -0
  101. package/src/mux/live-mux/selection.ts +411 -0
  102. package/src/mux/live-mux/startup-utils.ts +187 -0
  103. package/src/mux/live-mux/status-timeline-state.ts +82 -0
  104. package/src/mux/live-mux/task-pane-shortcuts.ts +206 -0
  105. package/src/mux/live-mux/terminal-palette.ts +79 -0
  106. package/src/mux/new-thread-prompt.ts +165 -0
  107. package/src/mux/project-tree.ts +295 -0
  108. package/src/mux/render-frame.ts +113 -0
  109. package/src/mux/runtime-wiring.ts +185 -0
  110. package/src/mux/selector-index.ts +160 -0
  111. package/src/mux/startup-sequencer.ts +238 -0
  112. package/src/mux/task-composer.ts +289 -0
  113. package/src/mux/task-focused-pane.ts +417 -0
  114. package/src/mux/task-screen-keybindings.ts +539 -0
  115. package/src/mux/terminal-input-modes.ts +35 -0
  116. package/src/mux/workspace-path.ts +55 -0
  117. package/src/mux/workspace-rail-model.ts +701 -0
  118. package/src/mux/workspace-rail.ts +247 -0
  119. package/src/perf/perf-core.ts +307 -0
  120. package/src/pty/pty_host.ts +217 -0
  121. package/src/pty/session-broker.ts +158 -0
  122. package/src/recording/terminal-recording.ts +383 -0
  123. package/src/services/control-plane.ts +567 -0
  124. package/src/services/conversation-lifecycle.ts +176 -0
  125. package/src/services/conversation-startup-hydration.ts +47 -0
  126. package/src/services/directory-hydration.ts +49 -0
  127. package/src/services/event-persistence.ts +104 -0
  128. package/src/services/mux-ui-state-persistence.ts +82 -0
  129. package/src/services/output-load-sampler.ts +231 -0
  130. package/src/services/process-usage-refresh.ts +88 -0
  131. package/src/services/recording.ts +75 -0
  132. package/src/services/render-trace-recorder.ts +177 -0
  133. package/src/services/runtime-control-actions.ts +123 -0
  134. package/src/services/runtime-control-plane-ops.ts +131 -0
  135. package/src/services/runtime-conversation-actions.ts +113 -0
  136. package/src/services/runtime-conversation-activation.ts +78 -0
  137. package/src/services/runtime-conversation-starter.ts +171 -0
  138. package/src/services/runtime-conversation-title-edit.ts +149 -0
  139. package/src/services/runtime-directory-actions.ts +164 -0
  140. package/src/services/runtime-envelope-handler.ts +198 -0
  141. package/src/services/runtime-git-state.ts +92 -0
  142. package/src/services/runtime-input-pipeline.ts +50 -0
  143. package/src/services/runtime-input-router.ts +202 -0
  144. package/src/services/runtime-layout-resize.ts +236 -0
  145. package/src/services/runtime-left-rail-render.ts +159 -0
  146. package/src/services/runtime-main-pane-input.ts +230 -0
  147. package/src/services/runtime-modal-input.ts +119 -0
  148. package/src/services/runtime-navigation-input.ts +207 -0
  149. package/src/services/runtime-process-wiring.ts +68 -0
  150. package/src/services/runtime-rail-input.ts +287 -0
  151. package/src/services/runtime-render-flush.ts +146 -0
  152. package/src/services/runtime-render-lifecycle.ts +104 -0
  153. package/src/services/runtime-render-orchestrator.ts +108 -0
  154. package/src/services/runtime-render-pipeline.ts +167 -0
  155. package/src/services/runtime-render-state.ts +72 -0
  156. package/src/services/runtime-repository-actions.ts +197 -0
  157. package/src/services/runtime-right-pane-render.ts +132 -0
  158. package/src/services/runtime-shutdown.ts +79 -0
  159. package/src/services/runtime-stream-subscriptions.ts +56 -0
  160. package/src/services/runtime-task-composer-persistence.ts +139 -0
  161. package/src/services/runtime-task-editor-actions.ts +83 -0
  162. package/src/services/runtime-task-pane-actions.ts +198 -0
  163. package/src/services/runtime-task-pane-shortcuts.ts +189 -0
  164. package/src/services/runtime-task-pane.ts +62 -0
  165. package/src/services/runtime-workspace-actions.ts +153 -0
  166. package/src/services/runtime-workspace-observed-events.ts +190 -0
  167. package/src/services/session-projection-instrumentation.ts +190 -0
  168. package/src/services/startup-background-probe.ts +91 -0
  169. package/src/services/startup-background-resume.ts +65 -0
  170. package/src/services/startup-orchestrator.ts +166 -0
  171. package/src/services/startup-output-tracker.ts +54 -0
  172. package/src/services/startup-paint-tracker.ts +115 -0
  173. package/src/services/startup-persisted-conversation-queue.ts +45 -0
  174. package/src/services/startup-settled-gate.ts +67 -0
  175. package/src/services/startup-shutdown.ts +53 -0
  176. package/src/services/startup-span-tracker.ts +77 -0
  177. package/src/services/startup-state-hydration.ts +94 -0
  178. package/src/services/startup-visibility.ts +35 -0
  179. package/src/services/status-timeline-recorder.ts +144 -0
  180. package/src/services/task-pane-selection-actions.ts +153 -0
  181. package/src/services/task-planning-hydration.ts +58 -0
  182. package/src/services/task-planning-observed-events.ts +89 -0
  183. package/src/services/workspace-observed-events.ts +113 -0
  184. package/src/store/control-plane-store-normalize.ts +760 -0
  185. package/src/store/control-plane-store-types.ts +224 -0
  186. package/src/store/control-plane-store.ts +2951 -0
  187. package/src/store/event-store.ts +253 -0
  188. package/src/store/sqlite.ts +81 -0
  189. package/src/terminal/compat-matrix.ts +345 -0
  190. package/src/terminal/differential-checkpoints.ts +132 -0
  191. package/src/terminal/parity-suite.ts +441 -0
  192. package/src/terminal/snapshot-oracle.ts +1840 -0
  193. package/src/ui/conversation-input-forwarder.ts +114 -0
  194. package/src/ui/conversation-selection-input.ts +103 -0
  195. package/src/ui/debug-footer-notice.ts +39 -0
  196. package/src/ui/global-shortcut-input.ts +126 -0
  197. package/src/ui/input-preflight.ts +68 -0
  198. package/src/ui/input-token-router.ts +312 -0
  199. package/src/ui/input.ts +238 -0
  200. package/src/ui/kit.ts +509 -0
  201. package/src/ui/left-nav-input.ts +80 -0
  202. package/src/ui/left-rail-pointer-input.ts +148 -0
  203. package/src/ui/main-pane-pointer-input.ts +150 -0
  204. package/src/ui/modals/manager.ts +192 -0
  205. package/src/ui/mux-theme.ts +529 -0
  206. package/src/ui/panes/conversation.ts +19 -0
  207. package/src/ui/panes/home-gridfire.ts +302 -0
  208. package/src/ui/panes/home.ts +109 -0
  209. package/src/ui/panes/left-rail.ts +12 -0
  210. package/src/ui/panes/project.ts +44 -0
  211. package/src/ui/pointer-routing-input.ts +158 -0
  212. package/src/ui/repository-fold-input.ts +91 -0
  213. package/src/ui/screen.ts +210 -0
  214. package/src/ui/surface.ts +224 -0
@@ -0,0 +1,277 @@
1
+ import { resolve } from 'node:path';
2
+ import { startCodexLiveSession } from '../src/codex/live-session.ts';
3
+ import { startControlPlaneStreamServer } from '../src/control-plane/stream-server.ts';
4
+ import { loadHarnessConfig } from '../src/config/config-core.ts';
5
+ import { loadHarnessSecrets } from '../src/config/secrets-core.ts';
6
+ import {
7
+ configurePerfCore,
8
+ recordPerfEvent,
9
+ shutdownPerfCore,
10
+ startPerfSpan,
11
+ } from '../src/perf/perf-core.ts';
12
+
13
+ interface DaemonOptions {
14
+ host: string;
15
+ port: number;
16
+ authToken: string | null;
17
+ stateDbPath: string;
18
+ }
19
+
20
+ function parseBooleanEnv(value: string | undefined, fallback: boolean): boolean {
21
+ if (typeof value !== 'string') {
22
+ return fallback;
23
+ }
24
+ const normalized = value.trim().toLowerCase();
25
+ if (normalized === '1' || normalized === 'true' || normalized === 'yes' || normalized === 'on') {
26
+ return true;
27
+ }
28
+ if (normalized === '0' || normalized === 'false' || normalized === 'no' || normalized === 'off') {
29
+ return false;
30
+ }
31
+ return fallback;
32
+ }
33
+
34
+ function resolveInvocationDirectory(): string {
35
+ return process.env.HARNESS_INVOKE_CWD ?? process.env.INIT_CWD ?? process.cwd();
36
+ }
37
+
38
+ function configureProcessPerf(invocationDirectory: string): void {
39
+ const loadedConfig = loadHarnessConfig({ cwd: invocationDirectory });
40
+ const configEnabled = loadedConfig.config.debug.enabled && loadedConfig.config.debug.perf.enabled;
41
+ const perfEnabled = parseBooleanEnv(process.env.HARNESS_PERF_ENABLED, configEnabled);
42
+ const configuredPath = resolve(invocationDirectory, loadedConfig.config.debug.perf.filePath);
43
+ const envPath = process.env.HARNESS_PERF_FILE_PATH;
44
+ const perfFilePath =
45
+ typeof envPath === 'string' && envPath.trim().length > 0
46
+ ? resolve(invocationDirectory, envPath)
47
+ : configuredPath;
48
+
49
+ configurePerfCore({
50
+ enabled: perfEnabled,
51
+ filePath: perfFilePath,
52
+ });
53
+
54
+ recordPerfEvent('daemon.perf.configured', {
55
+ process: 'daemon',
56
+ enabled: perfEnabled,
57
+ filePath: perfFilePath,
58
+ });
59
+ }
60
+
61
+ function parseArgs(argv: string[]): DaemonOptions {
62
+ const defaultHost = process.env.HARNESS_CONTROL_PLANE_HOST ?? '127.0.0.1';
63
+ const defaultPortRaw = process.env.HARNESS_CONTROL_PLANE_PORT ?? '7777';
64
+ const defaultAuthToken = process.env.HARNESS_CONTROL_PLANE_AUTH_TOKEN ?? null;
65
+ const defaultStateDbPath =
66
+ process.env.HARNESS_CONTROL_PLANE_DB_PATH ?? '.harness/control-plane.sqlite';
67
+
68
+ let host = defaultHost;
69
+ let portRaw = defaultPortRaw;
70
+ let authToken = defaultAuthToken;
71
+ let stateDbPath = defaultStateDbPath;
72
+
73
+ for (let idx = 0; idx < argv.length; idx += 1) {
74
+ const arg = argv[idx]!;
75
+ if (arg === '--host') {
76
+ const value = argv[idx + 1];
77
+ if (value === undefined) {
78
+ throw new Error('missing value for --host');
79
+ }
80
+ host = value;
81
+ idx += 1;
82
+ continue;
83
+ }
84
+
85
+ if (arg === '--port') {
86
+ const value = argv[idx + 1];
87
+ if (value === undefined) {
88
+ throw new Error('missing value for --port');
89
+ }
90
+ portRaw = value;
91
+ idx += 1;
92
+ continue;
93
+ }
94
+
95
+ if (arg === '--auth-token') {
96
+ const value = argv[idx + 1];
97
+ if (value === undefined) {
98
+ throw new Error('missing value for --auth-token');
99
+ }
100
+ authToken = value;
101
+ idx += 1;
102
+ continue;
103
+ }
104
+
105
+ if (arg === '--state-db-path') {
106
+ const value = argv[idx + 1];
107
+ if (value === undefined) {
108
+ throw new Error('missing value for --state-db-path');
109
+ }
110
+ stateDbPath = value;
111
+ idx += 1;
112
+ continue;
113
+ }
114
+ }
115
+
116
+ const port = Number.parseInt(portRaw, 10);
117
+ if (!Number.isFinite(port) || port <= 0 || port > 65535) {
118
+ throw new Error(`invalid --port value: ${portRaw}`);
119
+ }
120
+
121
+ const loopbackHosts = new Set(['127.0.0.1', 'localhost', '::1']);
122
+ if (!loopbackHosts.has(host) && authToken === null) {
123
+ throw new Error('non-loopback hosts require --auth-token or HARNESS_CONTROL_PLANE_AUTH_TOKEN');
124
+ }
125
+
126
+ return {
127
+ host,
128
+ port,
129
+ authToken,
130
+ stateDbPath,
131
+ };
132
+ }
133
+
134
+ async function main(): Promise<number> {
135
+ const invocationDirectory = resolveInvocationDirectory();
136
+ loadHarnessSecrets({ cwd: invocationDirectory });
137
+ configureProcessPerf(invocationDirectory);
138
+ const loadedConfig = loadHarnessConfig({ cwd: invocationDirectory });
139
+ const serverSnapshotModelEnabled = loadedConfig.config.debug.mux.serverSnapshotModelEnabled;
140
+ const codexLaunchDirectoryModes: Record<string, 'yolo' | 'standard'> = {};
141
+ for (const [directoryPath, mode] of Object.entries(
142
+ loadedConfig.config.codex.launch.directoryModes,
143
+ )) {
144
+ codexLaunchDirectoryModes[resolve(invocationDirectory, directoryPath)] = mode;
145
+ }
146
+ const cursorLaunchDirectoryModes: Record<string, 'yolo' | 'standard'> = {};
147
+ for (const [directoryPath, mode] of Object.entries(
148
+ loadedConfig.config.cursor.launch.directoryModes,
149
+ )) {
150
+ cursorLaunchDirectoryModes[resolve(invocationDirectory, directoryPath)] = mode;
151
+ }
152
+ const startupSpan = startPerfSpan('daemon.startup.total', {
153
+ process: 'daemon',
154
+ });
155
+ recordPerfEvent('daemon.startup.begin', {
156
+ process: 'daemon',
157
+ });
158
+ const options = parseArgs(process.argv.slice(2));
159
+
160
+ const listenSpan = startPerfSpan('daemon.startup.listen', {
161
+ process: 'daemon',
162
+ });
163
+ const serverOptions: Parameters<typeof startControlPlaneStreamServer>[0] = {
164
+ host: options.host,
165
+ port: options.port,
166
+ stateStorePath: options.stateDbPath,
167
+ codexTelemetry: loadedConfig.config.codex.telemetry,
168
+ codexHistory: loadedConfig.config.codex.history,
169
+ codexLaunch: {
170
+ defaultMode: loadedConfig.config.codex.launch.defaultMode,
171
+ directoryModes: codexLaunchDirectoryModes,
172
+ },
173
+ critique: loadedConfig.config.critique,
174
+ cursorLaunch: {
175
+ defaultMode: loadedConfig.config.cursor.launch.defaultMode,
176
+ directoryModes: cursorLaunchDirectoryModes,
177
+ },
178
+ gitStatus: {
179
+ enabled: loadedConfig.config.mux.git.enabled,
180
+ pollMs: loadedConfig.config.mux.git.idlePollMs,
181
+ maxConcurrency: loadedConfig.config.mux.git.maxConcurrency,
182
+ minDirectoryRefreshMs: Math.max(loadedConfig.config.mux.git.idlePollMs, 30_000),
183
+ },
184
+ lifecycleHooks: loadedConfig.config.hooks.lifecycle,
185
+ startSession: (input) => {
186
+ const sessionOptions: Parameters<typeof startCodexLiveSession>[0] = {
187
+ args: input.args,
188
+ initialCols: input.initialCols,
189
+ initialRows: input.initialRows,
190
+ enableSnapshotModel: serverSnapshotModelEnabled,
191
+ };
192
+ if (input.command !== undefined) {
193
+ sessionOptions.command = input.command;
194
+ }
195
+ if (input.baseArgs !== undefined) {
196
+ sessionOptions.baseArgs = input.baseArgs;
197
+ }
198
+ if (input.useNotifyHook !== undefined) {
199
+ sessionOptions.useNotifyHook = input.useNotifyHook;
200
+ }
201
+ if (input.notifyMode !== undefined) {
202
+ sessionOptions.notifyMode = input.notifyMode;
203
+ }
204
+ if (input.notifyFilePath !== undefined) {
205
+ sessionOptions.notifyFilePath = input.notifyFilePath;
206
+ }
207
+ if (input.env !== undefined) {
208
+ sessionOptions.env = input.env;
209
+ }
210
+ if (input.cwd !== undefined) {
211
+ sessionOptions.cwd = input.cwd;
212
+ }
213
+ if (input.terminalForegroundHex !== undefined) {
214
+ sessionOptions.terminalForegroundHex = input.terminalForegroundHex;
215
+ }
216
+ if (input.terminalBackgroundHex !== undefined) {
217
+ sessionOptions.terminalBackgroundHex = input.terminalBackgroundHex;
218
+ }
219
+ return startCodexLiveSession(sessionOptions);
220
+ },
221
+ };
222
+ if (options.authToken !== null) {
223
+ serverOptions.authToken = options.authToken;
224
+ }
225
+ const server = await startControlPlaneStreamServer(serverOptions);
226
+ listenSpan.end({ listening: true });
227
+
228
+ const address = server.address();
229
+ recordPerfEvent('daemon.startup.listening', {
230
+ process: 'daemon',
231
+ host: address.address,
232
+ port: address.port,
233
+ auth: options.authToken === null ? 'off' : 'on',
234
+ });
235
+ startupSpan.end({ listening: true });
236
+ process.stdout.write(
237
+ `[control-plane] listening host=${address.address} port=${String(address.port)} auth=${options.authToken === null ? 'off' : 'on'} db=${options.stateDbPath}\n`,
238
+ );
239
+
240
+ let stopRequested = false;
241
+ let resolveStop: (() => void) | null = null;
242
+ const stopPromise = new Promise<void>((resolve) => {
243
+ resolveStop = resolve;
244
+ });
245
+
246
+ const requestStop = (): void => {
247
+ if (stopRequested) {
248
+ return;
249
+ }
250
+ stopRequested = true;
251
+ resolveStop?.();
252
+ };
253
+
254
+ process.once('SIGINT', requestStop);
255
+ process.once('SIGTERM', requestStop);
256
+
257
+ await stopPromise;
258
+ recordPerfEvent('daemon.runtime.stop-requested', {
259
+ process: 'daemon',
260
+ });
261
+ await server.close();
262
+ recordPerfEvent('daemon.runtime.closed', {
263
+ process: 'daemon',
264
+ });
265
+ return 0;
266
+ }
267
+
268
+ try {
269
+ process.exitCode = await main();
270
+ } catch (error: unknown) {
271
+ process.stderr.write(
272
+ `control-plane daemon fatal error: ${error instanceof Error ? (error.stack ?? error.message) : String(error)}\n`,
273
+ );
274
+ process.exitCode = 1;
275
+ } finally {
276
+ shutdownPerfCore();
277
+ }
@@ -0,0 +1,82 @@
1
+ import { appendFileSync, readFileSync } from 'node:fs';
2
+ import {
3
+ CURSOR_HOOK_NOTIFY_FILE_ENV,
4
+ CURSOR_HOOK_SESSION_ID_ENV,
5
+ } from '../src/cursor/managed-hooks.ts';
6
+
7
+ function asRecord(value: unknown): Record<string, unknown> | null {
8
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) {
9
+ return null;
10
+ }
11
+ return value as Record<string, unknown>;
12
+ }
13
+
14
+ function parsePayload(input: string): Record<string, unknown> {
15
+ try {
16
+ const parsed = JSON.parse(input) as unknown;
17
+ const record = asRecord(parsed);
18
+ if (record !== null) {
19
+ return record;
20
+ }
21
+ } catch {
22
+ // Fall through to raw payload envelope.
23
+ }
24
+ return {
25
+ type: 'unknown',
26
+ raw: input,
27
+ };
28
+ }
29
+
30
+ function parseManagedHookId(argv: readonly string[]): string | null {
31
+ for (let index = 2; index < argv.length; index += 1) {
32
+ const arg = argv[index];
33
+ if (arg !== '--managed-hook-id') {
34
+ continue;
35
+ }
36
+ const next = argv[index + 1];
37
+ if (typeof next === 'string' && next.trim().length > 0) {
38
+ return next.trim();
39
+ }
40
+ return null;
41
+ }
42
+ return null;
43
+ }
44
+
45
+ function main(): number {
46
+ const notifyFilePath = process.env[CURSOR_HOOK_NOTIFY_FILE_ENV]?.trim() ?? '';
47
+ if (notifyFilePath.length === 0) {
48
+ return 0;
49
+ }
50
+ let payloadRaw = '';
51
+ try {
52
+ payloadRaw = readFileSync(0, 'utf8');
53
+ } catch {
54
+ payloadRaw = '';
55
+ }
56
+ if (payloadRaw.trim().length === 0) {
57
+ return 0;
58
+ }
59
+ const payload = parsePayload(payloadRaw);
60
+ const managedHookId = parseManagedHookId(process.argv);
61
+ if (managedHookId !== null && typeof payload['managed_hook_id'] !== 'string') {
62
+ payload['managed_hook_id'] = managedHookId;
63
+ }
64
+ const sessionId = process.env[CURSOR_HOOK_SESSION_ID_ENV]?.trim() ?? '';
65
+ if (sessionId.length > 0) {
66
+ if (typeof payload['harness_session_id'] !== 'string') {
67
+ payload['harness_session_id'] = sessionId;
68
+ }
69
+ }
70
+ const record = {
71
+ ts: new Date().toISOString(),
72
+ payload,
73
+ };
74
+ try {
75
+ appendFileSync(notifyFilePath, `${JSON.stringify(record)}\n`, 'utf8');
76
+ } catch {
77
+ return 0;
78
+ }
79
+ return 0;
80
+ }
81
+
82
+ process.exitCode = main();