@zhivex-ai/harness 1.2.0 → 1.3.0-rc.7

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 (164) hide show
  1. package/CHANGELOG.md +64 -0
  2. package/README.md +9 -10
  3. package/contracts/engine-api.json +2921 -0
  4. package/contracts/public-api.json +267 -1
  5. package/contracts/stable-api-signatures.json +43 -43
  6. package/dist/acp-cli.js +137 -15002
  7. package/dist/approvals/approval-history.d.ts +22 -0
  8. package/dist/approvals/explicit-review.d.ts +24 -0
  9. package/dist/approvals/host-policy-identity.d.ts +3 -0
  10. package/dist/cli/arguments.d.ts +6 -2
  11. package/dist/cli/checkpoints.d.ts +230 -0
  12. package/dist/cli/cli-options.d.ts +2 -41
  13. package/dist/cli/cli-profiles.d.ts +4 -2
  14. package/dist/cli/cli-stream.d.ts +2 -167
  15. package/dist/cli/governance-report.d.ts +5 -0
  16. package/dist/cli/policy-decisions.d.ts +9 -0
  17. package/dist/cli/policy.d.ts +5 -0
  18. package/dist/cli/presentation.d.ts +3 -1
  19. package/dist/cli/run-document.d.ts +2 -91
  20. package/dist/cli/terminal/terminal-continuation.d.ts +2 -3
  21. package/dist/cli/terminal/terminal-ui.d.ts +4 -0
  22. package/dist/{cli-entry-c2f58zj2.js → cli-entry-ae7x44d4.js} +14969 -10731
  23. package/dist/{cli-entry-427869g9.js → cli-entry-d5788aay.js} +388 -47
  24. package/dist/cli-entry-ev76ezcd.js +656 -0
  25. package/dist/{cli-3jmy4h5h.js → cli-t7hrhayq.js} +918 -2223
  26. package/dist/cli.js +3 -5
  27. package/dist/client/acp-mcp-admission.d.ts +32 -0
  28. package/dist/client/acp-mcp-host.d.ts +19 -0
  29. package/dist/client/acp-mcp-session.d.ts +14 -0
  30. package/dist/client/acp-stdio.d.ts +2 -0
  31. package/dist/client/acp.d.ts +4 -1
  32. package/dist/client/adapter.d.ts +13 -1
  33. package/dist/client/continuation.d.ts +3 -0
  34. package/dist/client/json-contracts.d.ts +107 -4
  35. package/dist/client/local-service.d.ts +2 -0
  36. package/dist/client/protocol.d.ts +127 -1
  37. package/dist/client/run-document.d.ts +91 -0
  38. package/dist/client/service-events.d.ts +4 -0
  39. package/dist/client/stream.d.ts +167 -0
  40. package/dist/compat/cli-options.d.ts +53 -0
  41. package/dist/compat/code-support.d.ts +22 -0
  42. package/dist/compat/code-support.js +74 -0
  43. package/dist/{config-1k9t4jhh.js → config-c002f06f.js} +1 -1
  44. package/dist/config-j3jga5wh.js +61 -0
  45. package/dist/context/adaptive-compaction.d.ts +1 -0
  46. package/dist/context/compaction.d.ts +1 -1
  47. package/dist/context/request-measurements.d.ts +17 -0
  48. package/dist/context/request-projection.d.ts +10 -0
  49. package/dist/context/task-memory.d.ts +1 -0
  50. package/dist/engine/acp.d.ts +6 -0
  51. package/dist/engine/acp.js +205 -0
  52. package/dist/engine/client.d.ts +6 -0
  53. package/dist/engine/client.js +28 -0
  54. package/dist/engine/desktop-providers-v1.d.ts +4 -0
  55. package/dist/engine/desktop-providers-v1.js +18 -0
  56. package/dist/engine/desktop-state-v1.d.ts +10 -0
  57. package/dist/engine/desktop-state-v1.js +168 -0
  58. package/dist/engine/index.d.ts +73 -0
  59. package/dist/engine/index.js +977 -0
  60. package/dist/engine/mcp-stdio-v1.d.ts +11 -0
  61. package/dist/engine/mcp-stdio-v1.js +108 -0
  62. package/dist/engine/models.d.ts +3 -0
  63. package/dist/engine/models.js +894 -0
  64. package/dist/engine/protocol.d.ts +7 -0
  65. package/dist/engine/protocol.js +182 -0
  66. package/dist/engine/service.d.ts +5 -0
  67. package/dist/engine/service.js +28 -0
  68. package/dist/execution/acceptance-snapshot.d.ts +2 -0
  69. package/dist/execution/execution-environment.d.ts +10 -0
  70. package/dist/execution/mcp-oci-channel.d.ts +11 -0
  71. package/dist/execution/mcp-oci-server.d.ts +38 -0
  72. package/dist/execution/mcp-oci-workspace.d.ts +23 -0
  73. package/dist/execution/mcp-resource-recovery.d.ts +19 -0
  74. package/dist/execution/mcp-workspace-snapshot.d.ts +20 -0
  75. package/dist/hostile-repository-demo.js +3368 -1304
  76. package/dist/index-1bx83sw4.js +4 -0
  77. package/dist/index-2gz7jv7g.js +549 -0
  78. package/dist/{cli-entry-b1mfvmxe.js → index-2jrdcj6b.js} +72 -115
  79. package/dist/index-3ah1f369.js +356 -0
  80. package/dist/index-42gp2hy8.js +1555 -0
  81. package/dist/index-4jw3tvy8.js +301 -0
  82. package/dist/index-8705zxpr.js +145 -0
  83. package/dist/index-8vm2m34s.js +2080 -0
  84. package/dist/index-8zzhggxv.js +477 -0
  85. package/dist/index-a97bezbz.js +1073 -0
  86. package/dist/index-gq9xagx2.js +1343 -0
  87. package/dist/index-h0mqx2ed.js +1373 -0
  88. package/dist/index-jgrx804e.js +5589 -0
  89. package/dist/index-qfpz5y2v.js +174 -0
  90. package/dist/index-t60rva31.js +1004 -0
  91. package/dist/index-wg1sd3at.js +80 -0
  92. package/dist/index-xqxeypt5.js +888 -0
  93. package/dist/index-yb239ghk.js +961 -0
  94. package/dist/index-z0qxmkm2.js +3389 -0
  95. package/dist/index-z4e8d0ph.js +1768 -0
  96. package/dist/index.js +251 -18649
  97. package/dist/integrations/mcp-host-session.d.ts +26 -0
  98. package/dist/integrations/mcp-stdio-admission.d.ts +54 -0
  99. package/dist/integrations/mcp-stdio-client.d.ts +44 -0
  100. package/dist/integrations/mcp-stdio-framing.d.ts +18 -0
  101. package/dist/integrations/mcp-stdio-launch.d.ts +20 -0
  102. package/dist/integrations/mcp-stdio-session.d.ts +7 -0
  103. package/dist/integrations/mcp-stdio-tools.d.ts +12 -0
  104. package/dist/internal/desktop/catalog.d.ts +2 -0
  105. package/dist/internal/desktop/persistence.d.ts +2 -3
  106. package/dist/internal/desktop/providers.d.ts +1 -0
  107. package/dist/models/catalog.d.ts +2 -0
  108. package/dist/persistence/governance-context.d.ts +126 -0
  109. package/dist/persistence/governance-report.d.ts +299 -0
  110. package/dist/persistence/host-database-backup.d.ts +28 -0
  111. package/dist/persistence/mcp-execution-journal.d.ts +19 -0
  112. package/dist/persistence/mcp-reconciliation.d.ts +33 -0
  113. package/dist/persistence/mcp-resource-journal.d.ts +54 -0
  114. package/dist/persistence/workspace-checkpoints.d.ts +138 -0
  115. package/dist/providers/anthropic-continuation.d.ts +5 -0
  116. package/dist/providers/providers.d.ts +1 -1
  117. package/dist/providers/vertex-auth.d.ts +27 -0
  118. package/dist/runtime/delegation-contracts.d.ts +9 -0
  119. package/dist/runtime/delegation-correction.d.ts +9 -0
  120. package/dist/runtime/delegation-result.d.ts +73 -0
  121. package/dist/runtime/error-diagnostics.d.ts +86 -0
  122. package/dist/runtime/harness.d.ts +11 -1
  123. package/dist/runtime/policy-decisions.d.ts +66 -0
  124. package/dist/runtime/policy-inspection.d.ts +52 -0
  125. package/dist/runtime/provider-diagnostics.d.ts +1 -0
  126. package/dist/runtime/runtime-instructions.d.ts +8 -0
  127. package/dist/runtime/task-acceptance-checks.d.ts +103 -0
  128. package/dist/runtime/task-acceptance-delivery.d.ts +107 -0
  129. package/dist/runtime/task-acceptance-host.d.ts +20 -0
  130. package/dist/runtime/task-acceptance-record.d.ts +107 -0
  131. package/dist/runtime/task-acceptance-validation.d.ts +25 -0
  132. package/dist/runtime/task-acceptance.d.ts +47 -0
  133. package/dist/runtime/time-to-safe-fix.d.ts +83 -0
  134. package/dist/runtime/tool-policy-file.d.ts +23 -0
  135. package/dist/runtime/tool-policy-paths.d.ts +5 -0
  136. package/dist/runtime/tool-policy.d.ts +6 -0
  137. package/dist/service-cli.js +19 -14504
  138. package/dist/{service-client-cli-yx56n0hc.js → service-client-cli-kns4mf57.js} +307 -248
  139. package/dist/verify-historical-migrations.js +3314 -1251
  140. package/dist/workspace/path-policy.d.ts +4 -0
  141. package/dist/zhx.js +3 -5
  142. package/docs/ACP.md +14 -2
  143. package/docs/ANTHROPIC.md +33 -0
  144. package/docs/CLI.md +132 -0
  145. package/docs/CLIENT_PROTOCOL.md +45 -0
  146. package/docs/CODE_RELEASE.md +93 -0
  147. package/docs/ENGINE_API.md +319 -0
  148. package/docs/EXTENSIBILITY.md +46 -3
  149. package/docs/FIRST_USE.md +6 -6
  150. package/docs/LOCAL_SERVICE.md +13 -0
  151. package/docs/MCP_STDIO.md +20 -0
  152. package/docs/README.md +6 -0
  153. package/docs/STABILITY.md +9 -1
  154. package/docs/SUPPORT_MATRIX.md +1 -1
  155. package/docs/TOOL_POLICY.md +81 -0
  156. package/docs/VERTEX.md +50 -0
  157. package/docs/adr/0002-isolated-mcp-stdio.md +135 -0
  158. package/docs/fixtures/mcp-stdio-contract-v1.json +189 -0
  159. package/docs/support-matrix.json +2 -0
  160. package/evaluations/representative-assembly-matrix.json +64 -1
  161. package/evaluations/task-acceptance-routes.json +7 -0
  162. package/examples/README.md +1 -1
  163. package/package.json +74 -12
  164. package/dist/cli-entry-64pdp3k5.js +0 -17
@@ -0,0 +1,4 @@
1
+ import { createRequire } from "node:module";
2
+ var __require = /* @__PURE__ */ createRequire(import.meta.url);
3
+
4
+ export { __require };
@@ -0,0 +1,549 @@
1
+ import {
2
+ createHarnessClientAdapter,
3
+ harnessPolicyDecisionEventSchema
4
+ } from "./index-jgrx804e.js";
5
+ import {
6
+ openCliSessionStore
7
+ } from "./index-42gp2hy8.js";
8
+ import {
9
+ SqliteDatabase
10
+ } from "./index-yb239ghk.js";
11
+ import {
12
+ harnessClientRequestSchema,
13
+ streamEventDocument
14
+ } from "./index-qfpz5y2v.js";
15
+ import {
16
+ __require
17
+ } from "./index-1bx83sw4.js";
18
+
19
+ // src/client/service-events.ts
20
+ import { createHash } from "node:crypto";
21
+ import { createRedactionPolicy } from "@zhivex-ai/agents";
22
+ var openHarnessActivityStore = async (config, options = {}) => {
23
+ const index = await openCliSessionStore({ workspace: config.workspace, stateDirectory: config.stateDirectory, scope: config.scope });
24
+ const scope = `${index.workspaceKey}:${index.scopeKey}`;
25
+ const db = new SqliteDatabase(index.databasePath);
26
+ index.close();
27
+ const maxEvents = options.maxEvents ?? 1e4, retentionMs = options.retentionMs ?? 7 * 24 * 3600 * 1000, now = options.now ?? Date.now;
28
+ if (!Number.isSafeInteger(maxEvents) || maxEvents < 1 || maxEvents > 1e5 || !Number.isSafeInteger(retentionMs) || retentionMs < 1) {
29
+ db.close();
30
+ throw new Error("ACTIVITY_LIMIT_INVALID");
31
+ }
32
+ db.exec(`CREATE TABLE IF NOT EXISTS client_activity_events(sequence INTEGER PRIMARY KEY AUTOINCREMENT, scope TEXT NOT NULL, session TEXT NOT NULL, run TEXT NOT NULL, at INTEGER NOT NULL, activity TEXT NOT NULL);
33
+ CREATE INDEX IF NOT EXISTS client_activity_scope ON client_activity_events(scope, session, sequence);
34
+ CREATE TABLE IF NOT EXISTS client_activity_snapshots(scope TEXT NOT NULL, session TEXT NOT NULL, sequence INTEGER NOT NULL, expired_through INTEGER NOT NULL DEFAULT 0, snapshot TEXT NOT NULL, PRIMARY KEY(scope,session));`);
35
+ const redaction = createRedactionPolicy({ includeEmails: true });
36
+ const redact = (text) => {
37
+ let result = text;
38
+ for (const secret of options.sensitiveValues ?? [])
39
+ if (secret)
40
+ result = result.split(secret).join("[REDACTED]");
41
+ return redaction.redactText(result).replace(/\b(?:sk|ghp|github_pat|xox[baprs])[-_][A-Za-z0-9_-]+/gi, "[REDACTED]");
42
+ };
43
+ const redactValue = (value) => typeof value === "string" ? redact(value) : Array.isArray(value) ? value.map(redactValue) : value && typeof value === "object" ? Object.fromEntries(Object.entries(value).map(([k, v]) => [k, redactValue(v)])) : value;
44
+ const tails = new Map;
45
+ const discard = new Set;
46
+ const flush = (key, chunk, final = false) => {
47
+ let text = (tails.get(key) ?? "") + chunk;
48
+ for (const secret of options.sensitiveValues ?? [])
49
+ if (secret)
50
+ text = text.split(secret).join("[REDACTED]");
51
+ if (discard.has(key)) {
52
+ const end = text.search(/\s/);
53
+ if (end < 0 && !final)
54
+ return "";
55
+ text = end < 0 ? "" : text.slice(end);
56
+ discard.delete(key);
57
+ }
58
+ let boundary = final ? text.length : text.match(/^[\s\S]*\s/)?.[0].length ?? 0;
59
+ if (!final)
60
+ for (const secret of options.sensitiveValues ?? []) {
61
+ if (!secret)
62
+ continue;
63
+ let start = text.indexOf(secret[0], Math.max(0, text.length - secret.length + 1));
64
+ while (start >= 0 && start < boundary) {
65
+ if (secret.startsWith(text.slice(start))) {
66
+ boundary = start;
67
+ break;
68
+ }
69
+ start = text.indexOf(secret[0], start + 1);
70
+ }
71
+ }
72
+ let result = redact(text.slice(0, boundary));
73
+ text = text.slice(boundary);
74
+ if (text.length > 16384) {
75
+ result += "[TRUNCATED]";
76
+ text = "";
77
+ discard.add(key);
78
+ }
79
+ tails.set(key, text);
80
+ return result;
81
+ };
82
+ const snapshot = (sessionId) => {
83
+ const row = db.query("SELECT snapshot,expired_through FROM client_activity_snapshots WHERE scope=? AND session=?").get(scope, sessionId);
84
+ return { state: row ? JSON.parse(row.snapshot) : { schemaVersion: 1, sessionId, sequence: 0, runs: {} }, expiredThrough: row?.expired_through ?? 0 };
85
+ };
86
+ const prune = () => {
87
+ const expired = db.query(`SELECT session,MAX(sequence) AS seq FROM client_activity_events WHERE scope=? AND (at < ? OR sequence NOT IN (SELECT sequence FROM client_activity_events WHERE scope=? ORDER BY sequence DESC LIMIT ?)) GROUP BY session`).all(scope, now() - retentionMs, scope, maxEvents);
88
+ for (const row of expired) {
89
+ db.query("UPDATE client_activity_snapshots SET expired_through=MAX(expired_through,?) WHERE scope=? AND session=?").run(row.seq, scope, row.session);
90
+ db.query("DELETE FROM client_activity_events WHERE scope=? AND session=? AND sequence<=?").run(scope, row.session, row.seq);
91
+ }
92
+ };
93
+ const write = (sessionId, runId, activity) => {
94
+ if (!/^[A-Za-z0-9._:-]{1,128}$/.test(sessionId) || !/^[A-Za-z0-9._:-]{1,128}$/.test(runId))
95
+ throw new Error("ACTIVITY_ID_INVALID");
96
+ const at = now();
97
+ const encoded = JSON.stringify(activity);
98
+ if (Buffer.byteLength(encoded) > 64 * 1024)
99
+ throw new Error("ACTIVITY_TOO_LARGE");
100
+ db.exec("BEGIN IMMEDIATE");
101
+ try {
102
+ const current = snapshot(sessionId).state;
103
+ const runs = Object.keys(current.runs);
104
+ if (!current.runs[runId] && runs.length >= 1000)
105
+ throw new Error("ACTIVITY_RUN_LIMIT");
106
+ const state = current.runs[runId] ?? { text: "", status: "running", truncated: false };
107
+ if (typeof activity.textDelta === "string") {
108
+ state.text += activity.textDelta;
109
+ if (state.text.length > 256 * 1024) {
110
+ state.text = state.text.slice(-256 * 1024);
111
+ state.truncated = true;
112
+ }
113
+ }
114
+ if (activity.type === "user-message" && typeof activity.prompt === "string")
115
+ state.prompt = activity.prompt;
116
+ if (typeof activity.toolCallId === "string" && typeof activity.toolName === "string") {
117
+ state.tools ??= {};
118
+ const key = `tool:${activity.toolCallId}`;
119
+ if (state.tools[key] || Object.keys(state.tools).length < 256) {
120
+ const status = activity.type === "tool-result" ? activity.isError === true || activity.timedOut === true || typeof activity.exitCode === "number" && activity.exitCode !== 0 ? "failed" : "completed" : "running";
121
+ state.tools[key] = { name: activity.toolName, status, ...typeof activity.exitCode === "number" ? { exitCode: activity.exitCode } : {}, ...typeof activity.timedOut === "boolean" ? { timedOut: activity.timedOut } : {} };
122
+ } else
123
+ state.truncated = true;
124
+ }
125
+ if (typeof activity.status === "string" && (activity.type === "checkpoint" || activity.type === "agent-run-finish"))
126
+ state.status = activity.status;
127
+ if (activity.type === "agent-run-start")
128
+ state.status = "running";
129
+ if (activity.type === "tool-approval-request" || activity.type === "agent-approval-request")
130
+ state.status = "waiting_approval";
131
+ current.runs[runId] = state;
132
+ if (Buffer.byteLength(JSON.stringify(current)) > 2 * 1024 * 1024)
133
+ throw new Error("ACTIVITY_SNAPSHOT_LIMIT");
134
+ db.query("INSERT INTO client_activity_events(scope,session,run,at,activity) VALUES(?,?,?,?,?)").run(scope, sessionId, runId, at, encoded);
135
+ current.sequence = db.query("SELECT last_insert_rowid() AS seq").get().seq;
136
+ db.query("INSERT INTO client_activity_snapshots(scope,session,sequence,snapshot) VALUES(?,?,?,?) ON CONFLICT(scope,session) DO UPDATE SET sequence=excluded.sequence,snapshot=excluded.snapshot").run(scope, sessionId, current.sequence, JSON.stringify(current));
137
+ prune();
138
+ db.exec("COMMIT");
139
+ } catch (e) {
140
+ db.exec("ROLLBACK");
141
+ throw e;
142
+ }
143
+ };
144
+ return {
145
+ policyDecision(sessionId, runId, event) {
146
+ write(sessionId, runId, redactValue(harnessPolicyDecisionEventSchema.parse(event)));
147
+ },
148
+ prompt(sessionId, runId, prompt) {
149
+ const safe = redact(prompt);
150
+ const truncated = Buffer.byteLength(safe) > 60 * 1024;
151
+ write(sessionId, runId, { type: "user-message", prompt: truncated ? Buffer.from(safe).subarray(0, 60 * 1024).toString("utf8") + "[TRUNCATED]" : safe });
152
+ },
153
+ append(sessionId, runId, event) {
154
+ const key = `${sessionId}:${runId}`;
155
+ if (event.type === "text-delta") {
156
+ const textDelta = flush(key, event.textDelta);
157
+ if (textDelta)
158
+ write(sessionId, runId, { type: "text-delta", textDelta });
159
+ return;
160
+ }
161
+ if (event.type === "agent-run-finish" || event.type === "error") {
162
+ const textDelta = flush(key, "", true);
163
+ if (textDelta)
164
+ write(sessionId, runId, { type: "text-delta", textDelta });
165
+ tails.delete(key);
166
+ }
167
+ const projected = streamEventDocument(event);
168
+ if (projected) {
169
+ let receipt = {};
170
+ if (event.type === "tool-result" && event.toolResult.toolName === "run_check") {
171
+ const output = event.toolResult.output;
172
+ if (output && typeof output === "object" && !Array.isArray(output) && "exitCode" in output && Number.isSafeInteger(output.exitCode))
173
+ receipt = { exitCode: output.exitCode, timedOut: "timedOut" in output && output.timedOut === true };
174
+ }
175
+ write(sessionId, runId, redactValue({ ...projected, ...receipt }));
176
+ }
177
+ },
178
+ checkpoint(sessionId, runId, status) {
179
+ const key = `${sessionId}:${runId}`;
180
+ const textDelta = flush(key, "", true);
181
+ if (textDelta)
182
+ write(sessionId, runId, { type: "text-delta", textDelta });
183
+ tails.delete(key);
184
+ write(sessionId, runId, { type: "checkpoint", status });
185
+ },
186
+ replay(sessionId, after = 0) {
187
+ if (!Number.isSafeInteger(after) || after < 0)
188
+ throw new Error("ACTIVITY_CURSOR_INVALID");
189
+ db.exec("BEGIN IMMEDIATE");
190
+ try {
191
+ prune();
192
+ db.exec("COMMIT");
193
+ } catch (e) {
194
+ db.exec("ROLLBACK");
195
+ throw e;
196
+ }
197
+ const current = snapshot(sessionId);
198
+ const expired = after < current.expiredThrough;
199
+ if (after > current.state.sequence)
200
+ throw new Error("ACTIVITY_CURSOR_AHEAD");
201
+ if (expired)
202
+ return { schemaVersion: 1, cursorExpired: true, hasMore: false, policyEvidenceIncomplete: true, nextCursor: current.state.sequence, events: [], snapshot: current.state };
203
+ const rows = db.query("SELECT sequence,session,run,at,activity FROM client_activity_events WHERE scope=? AND session=? AND sequence>? ORDER BY sequence LIMIT 201").all(scope, sessionId, after);
204
+ const events = rows.slice(0, 200).map((row) => ({ schemaVersion: 1, eventId: createHash("sha256").update(`${scope}:${row.sequence}`).digest("hex"), sequence: row.sequence, sessionId: row.session, runId: row.run, at: row.at, activity: JSON.parse(row.activity) }));
205
+ return { schemaVersion: 1, cursorExpired: false, hasMore: rows.length > 200, policyEvidenceIncomplete: current.expiredThrough > 0, nextCursor: events.at(-1)?.sequence ?? after, events };
206
+ },
207
+ close() {
208
+ db.close();
209
+ tails.clear();
210
+ discard.clear();
211
+ }
212
+ };
213
+ };
214
+
215
+ // src/client/local-service.ts
216
+ import { createServer, request as httpRequest } from "node:http";
217
+ import { randomBytes, timingSafeEqual, createHash as createHash2 } from "node:crypto";
218
+ import { mkdir, lstat, realpath, open, unlink, chmod } from "node:fs/promises";
219
+ import path from "node:path";
220
+ import { z } from "zod";
221
+ var harnessLocalCredentialsSchema = z.object({ schemaVersion: z.literal(1), socketPath: z.string().min(1), token: z.string().regex(/^[a-f0-9]{64}$/) }).strict();
222
+ var send = (response, status, document) => {
223
+ response.writeHead(status, { "content-type": "application/json", "cache-control": "no-store", "x-content-type-options": "nosniff", connection: "close" });
224
+ response.end(JSON.stringify(document));
225
+ };
226
+ var fault = (code) => ({ ok: false, error: { code } });
227
+ var body = async (request) => {
228
+ const chunks = [];
229
+ let size = 0;
230
+ for await (const chunk of request) {
231
+ const bytes = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
232
+ size += bytes.length;
233
+ if (size > 128 * 1024)
234
+ throw new Error("REQUEST_TOO_LARGE");
235
+ chunks.push(bytes);
236
+ }
237
+ return JSON.parse(Buffer.concat(chunks).toString("utf8"));
238
+ };
239
+ var privateFile = async (filename, data) => {
240
+ const file = await open(filename, "wx", 384);
241
+ try {
242
+ await file.writeFile(data);
243
+ await file.sync();
244
+ } finally {
245
+ await file.close();
246
+ }
247
+ };
248
+ var exists = async (filename) => {
249
+ try {
250
+ return await lstat(filename);
251
+ } catch (e) {
252
+ if (e.code === "ENOENT")
253
+ return;
254
+ throw e;
255
+ }
256
+ };
257
+ var startHarnessLocalService = async (harness, options) => {
258
+ if (process.platform === "win32")
259
+ throw new Error("LOCAL_SERVICE_PLATFORM_UNSUPPORTED");
260
+ const directory = path.resolve(options.directory);
261
+ await mkdir(directory, { recursive: true, mode: 448 });
262
+ const info = await lstat(directory);
263
+ if (!info.isDirectory() || info.isSymbolicLink() || (info.mode & 63) !== 0 || info.uid !== process.getuid?.())
264
+ throw new Error("LOCAL_SERVICE_DIRECTORY_UNSAFE");
265
+ const canonicalDirectory = await realpath(directory);
266
+ const workspaceIdentity = createHash2("sha256").update(JSON.stringify([harness.config.workspace, harness.config.scope])).digest("hex").slice(0, 20);
267
+ const socketPath = path.join(canonicalDirectory, `${workspaceIdentity}.sock`);
268
+ if (Buffer.byteLength(socketPath) > 100)
269
+ throw new Error("LOCAL_SERVICE_SOCKET_PATH_TOO_LONG");
270
+ const credentialsPath = path.join(canonicalDirectory, `${workspaceIdentity}.json`);
271
+ const lockPath = path.join(canonicalDirectory, `${workspaceIdentity}.lock`);
272
+ const token = randomBytes(32).toString("hex");
273
+ await privateFile(lockPath, JSON.stringify({ schemaVersion: 1, pid: process.pid }));
274
+ let adapter;
275
+ let activity;
276
+ const pending = new Set;
277
+ let closing = false, paused = false, activeMutations = 0, closePromise;
278
+ const server = createServer((req, res) => {
279
+ const operation = (async () => {
280
+ const actual = Buffer.from(req.headers.authorization ?? "");
281
+ const expected = Buffer.from(`Bearer ${token}`);
282
+ if (actual.length !== expected.length || !timingSafeEqual(actual, expected))
283
+ return send(res, 401, fault("UNAUTHORIZED"));
284
+ if (req.headers.origin !== undefined || req.headers["sec-fetch-site"] !== undefined)
285
+ return send(res, 403, fault("ORIGIN_REJECTED"));
286
+ if (closing)
287
+ return send(res, 503, fault("SERVICE_CLOSING"));
288
+ if (req.method === "GET" && req.url === "/health")
289
+ return send(res, 200, { ok: true, schemaVersion: 1, status: "ready" });
290
+ if (req.method !== "POST" || !["/hello", "/command", "/events"].includes(req.url ?? ""))
291
+ return send(res, 404, fault("NOT_FOUND"));
292
+ if (req.headers["content-type"] !== "application/json")
293
+ return send(res, 415, fault("CONTENT_TYPE_REQUIRED"));
294
+ let value;
295
+ try {
296
+ value = await body(req);
297
+ } catch {
298
+ return send(res, 400, fault("INVALID_REQUEST"));
299
+ }
300
+ if (closing)
301
+ return send(res, 503, fault("SERVICE_CLOSING"));
302
+ if (req.url === "/hello") {
303
+ const parsed2 = z.object({ versions: z.array(z.number().int()).min(1).max(16) }).strict().safeParse(value);
304
+ return send(res, 200, parsed2.success ? adapter.negotiate(parsed2.data.versions) : fault("INVALID_REQUEST"));
305
+ }
306
+ if (req.url === "/events") {
307
+ const parsed2 = z.object({ projectId: z.string(), sessionId: z.string().regex(/^[A-Za-z0-9._:-]{1,128}$/), after: z.number().int().nonnegative().safe().optional() }).strict().safeParse(value);
308
+ const hello = adapter.negotiate([1]);
309
+ if (!parsed2.success)
310
+ return send(res, 400, fault("INVALID_REQUEST"));
311
+ if (!hello.ok || parsed2.data.projectId !== hello.projectId)
312
+ return send(res, 404, fault("NOT_FOUND"));
313
+ try {
314
+ return send(res, 200, activity.replay(parsed2.data.sessionId, parsed2.data.after));
315
+ } catch {
316
+ return send(res, 400, fault("INVALID_CURSOR"));
317
+ }
318
+ }
319
+ const parsed = harnessClientRequestSchema.safeParse(value);
320
+ const method = parsed.success ? parsed.data.command.method : undefined;
321
+ const mutation = method !== undefined && !["project.get", "policy.get", "session.list", "session.get", "run.get", "checkpoint.list", "checkpoint.inspect", "restore.get"].includes(method);
322
+ if (paused && mutation && method !== "run.cancel")
323
+ return send(res, 503, fault("SERVICE_PAUSED"));
324
+ if (mutation)
325
+ activeMutations++;
326
+ try {
327
+ return send(res, 200, await adapter.dispatch(value));
328
+ } finally {
329
+ if (mutation)
330
+ activeMutations--;
331
+ }
332
+ })().catch(() => {
333
+ if (!res.headersSent)
334
+ send(res, 500, fault("EXECUTION_FAILED"));
335
+ else
336
+ res.destroy();
337
+ });
338
+ pending.add(operation);
339
+ operation.finally(() => pending.delete(operation));
340
+ });
341
+ server.requestTimeout = Math.max(30000, harness.config.timeoutMs + 1e4);
342
+ server.headersTimeout = 1e4;
343
+ server.keepAliveTimeout = 1000;
344
+ let ownsSocket = false, ownsCredentials = false;
345
+ const cleanup = async () => {
346
+ if (ownsSocket)
347
+ await unlink(socketPath).catch((e) => {
348
+ if (e.code !== "ENOENT")
349
+ throw e;
350
+ });
351
+ if (ownsCredentials)
352
+ await unlink(credentialsPath).catch((e) => {
353
+ if (e.code !== "ENOENT")
354
+ throw e;
355
+ });
356
+ await unlink(lockPath);
357
+ };
358
+ try {
359
+ if (await exists(socketPath) || await exists(credentialsPath))
360
+ throw new Error("LOCAL_SERVICE_STALE_STATE");
361
+ activity = await openHarnessActivityStore(harness.config, {
362
+ sensitiveValues: [token, ...options.sensitiveValues ?? [], ...Object.entries(process.env).filter(([k, v]) => /(?:API_KEY|TOKEN|SECRET|PASSWORD)$/.test(k) && v && v.length >= 8).map(([, v]) => v)],
363
+ ...options.maxEvents === undefined ? {} : { maxEvents: options.maxEvents },
364
+ ...options.retentionMs === undefined ? {} : { retentionMs: options.retentionMs }
365
+ });
366
+ adapter = await createHarnessClientAdapter(harness, {
367
+ ...options.approvalNow ? { now: options.approvalNow } : {},
368
+ onPolicyDecision: (sessionId, runId, event) => activity.policyDecision(sessionId, runId, event),
369
+ onPrompt: (sessionId, runId, prompt) => activity.prompt(sessionId, runId, prompt),
370
+ onEvent: (sessionId, runId, event) => activity.append(sessionId, runId, event),
371
+ onCheckpoint: (sessionId, runId, status) => activity.checkpoint(sessionId, runId, status)
372
+ });
373
+ await new Promise((resolve, reject) => {
374
+ server.once("error", reject);
375
+ server.listen(socketPath, () => {
376
+ server.off("error", reject);
377
+ resolve();
378
+ });
379
+ });
380
+ ownsSocket = true;
381
+ await chmod(socketPath, 384);
382
+ await privateFile(credentialsPath, JSON.stringify({ schemaVersion: 1, socketPath, token }));
383
+ ownsCredentials = true;
384
+ return {
385
+ socketPath,
386
+ credentialsPath,
387
+ async dispatchReviewed(request) {
388
+ if (closing || paused)
389
+ throw new Error("SERVICE_ADMISSION_PAUSED");
390
+ if (!adapter?.dispatchReviewed)
391
+ throw new Error("EXPLICIT_REVIEW_UNAVAILABLE");
392
+ activeMutations++;
393
+ const operation = adapter.dispatchReviewed(request);
394
+ const tracked = operation.then(() => {}, () => {});
395
+ pending.add(tracked);
396
+ try {
397
+ return await operation;
398
+ } finally {
399
+ pending.delete(tracked);
400
+ activeMutations--;
401
+ }
402
+ },
403
+ pauseAdmission() {
404
+ if (closing)
405
+ throw new Error("SERVICE_CLOSING");
406
+ paused = true;
407
+ return activeMutations > 0;
408
+ },
409
+ resumeAdmission() {
410
+ if (closing)
411
+ throw new Error("SERVICE_CLOSING");
412
+ paused = false;
413
+ },
414
+ cancelActive() {
415
+ return adapter.cancelActive();
416
+ },
417
+ close() {
418
+ closePromise ??= (async () => {
419
+ closing = true;
420
+ const stopped = new Promise((resolve, reject) => server.close((e) => e ? reject(e) : resolve()));
421
+ await Promise.allSettled([...pending]);
422
+ await stopped;
423
+ adapter.close();
424
+ activity.close();
425
+ await harness.close();
426
+ await cleanup();
427
+ })();
428
+ return closePromise;
429
+ }
430
+ };
431
+ } catch (e) {
432
+ if (server.listening)
433
+ await new Promise((resolve) => server.close(() => resolve()));
434
+ adapter?.close();
435
+ activity?.close();
436
+ await cleanup();
437
+ throw e;
438
+ }
439
+ };
440
+ var readHarnessLocalCredentials = async (filename) => {
441
+ const { constants } = await import("node:fs");
442
+ const file = await open(filename, constants.O_RDONLY | constants.O_NOFOLLOW);
443
+ try {
444
+ const info = await file.stat();
445
+ if (!info.isFile() || (info.mode & 63) !== 0 || info.uid !== process.getuid?.() || info.size > 4096)
446
+ throw new Error("LOCAL_SERVICE_CREDENTIALS_UNSAFE");
447
+ return harnessLocalCredentialsSchema.parse(JSON.parse(await file.readFile("utf8")));
448
+ } finally {
449
+ await file.close();
450
+ }
451
+ };
452
+ var requestHarnessLocalService = async (credentials, endpoint, payload) => {
453
+ const parsed = harnessLocalCredentialsSchema.parse(credentials);
454
+ if (!path.isAbsolute(parsed.socketPath) || process.platform === "win32")
455
+ throw new Error("LOCAL_SERVICE_SOCKET_INVALID");
456
+ const encoded = JSON.stringify(payload);
457
+ if (Buffer.byteLength(encoded) > 131072)
458
+ throw new Error("REQUEST_TOO_LARGE");
459
+ return new Promise((resolve, reject) => {
460
+ const req = httpRequest({ socketPath: parsed.socketPath, path: `/${endpoint}`, method: "POST", headers: {
461
+ authorization: `Bearer ${parsed.token}`,
462
+ "content-type": "application/json",
463
+ "content-length": Buffer.byteLength(encoded)
464
+ } }, (res) => {
465
+ const chunks = [];
466
+ let size = 0;
467
+ res.on("data", (chunk) => {
468
+ size += chunk.length;
469
+ if (size > 4194304)
470
+ res.destroy(new Error("RESPONSE_TOO_LARGE"));
471
+ else
472
+ chunks.push(chunk);
473
+ });
474
+ res.on("error", reject);
475
+ res.on("end", () => {
476
+ try {
477
+ if (res.statusCode !== 200)
478
+ return reject(new Error(`LOCAL_SERVICE_HTTP_${res.statusCode}`));
479
+ resolve(JSON.parse(Buffer.concat(chunks).toString("utf8")));
480
+ } catch {
481
+ reject(new Error("LOCAL_SERVICE_RESPONSE_INVALID"));
482
+ }
483
+ });
484
+ });
485
+ req.on("error", () => reject(new Error("LOCAL_SERVICE_UNAVAILABLE")));
486
+ req.end(encoded);
487
+ });
488
+ };
489
+ var recoverHarnessLocalService = async (harness, directory) => {
490
+ const info = await lstat(directory);
491
+ if (!info.isDirectory() || info.isSymbolicLink() || (info.mode & 63) !== 0 || info.uid !== process.getuid?.())
492
+ throw new Error("LOCAL_SERVICE_DIRECTORY_UNSAFE");
493
+ const root = await realpath(directory);
494
+ const identity = createHash2("sha256").update(JSON.stringify([harness.config.workspace, harness.config.scope])).digest("hex").slice(0, 20);
495
+ const lockPath = path.join(root, `${identity}.lock`);
496
+ const index = await openCliSessionStore({ workspace: harness.config.workspace, stateDirectory: harness.config.stateDirectory, scope: harness.config.scope, busyTimeoutMs: 0 });
497
+ const databasePath = index.databasePath;
498
+ index.close();
499
+ const db = new SqliteDatabase(databasePath);
500
+ let locked = false;
501
+ try {
502
+ db.exec("PRAGMA busy_timeout = 0");
503
+ db.exec("BEGIN IMMEDIATE");
504
+ locked = true;
505
+ const { constants } = await import("node:fs");
506
+ const handle = await open(lockPath, constants.O_RDONLY | constants.O_NOFOLLOW);
507
+ let owner;
508
+ try {
509
+ const stat = await handle.stat();
510
+ if (!stat.isFile() || stat.uid !== process.getuid?.() || (stat.mode & 63) !== 0 || stat.size > 4096)
511
+ throw new Error("LOCAL_SERVICE_LOCK_UNSAFE");
512
+ owner = z.object({ schemaVersion: z.literal(1), pid: z.number().int().positive().max(2147483647) }).strict().parse(JSON.parse(await handle.readFile("utf8")));
513
+ } finally {
514
+ await handle.close();
515
+ }
516
+ try {
517
+ process.kill(owner.pid, 0);
518
+ throw new Error("LOCAL_SERVICE_OWNER_ALIVE");
519
+ } catch (e) {
520
+ if (e.code !== "ESRCH")
521
+ throw e;
522
+ }
523
+ for (const extension of ["sock", "json"]) {
524
+ const filename = path.join(root, `${identity}.${extension}`);
525
+ const entry = await exists(filename);
526
+ if (!entry)
527
+ continue;
528
+ if (entry.isSymbolicLink() || entry.uid !== process.getuid?.() || (entry.mode & 63) !== 0 || (extension === "sock" ? !entry.isSocket() : !entry.isFile()))
529
+ throw new Error("LOCAL_SERVICE_RECOVERY_UNSAFE");
530
+ }
531
+ for (const extension of ["sock", "json"])
532
+ await unlink(path.join(root, `${identity}.${extension}`)).catch((e) => {
533
+ if (e.code !== "ENOENT")
534
+ throw e;
535
+ });
536
+ await unlink(lockPath);
537
+ db.exec("COMMIT");
538
+ locked = false;
539
+ } finally {
540
+ try {
541
+ if (locked)
542
+ db.exec("ROLLBACK");
543
+ } finally {
544
+ db.close();
545
+ }
546
+ }
547
+ };
548
+
549
+ export { openHarnessActivityStore, harnessLocalCredentialsSchema, startHarnessLocalService, readHarnessLocalCredentials, requestHarnessLocalService, recoverHarnessLocalService };