@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
@@ -1,4 +1,58 @@
1
- // src/cli/cli-options.ts
1
+ import {
2
+ DEFAULT_PROVIDER_REGISTRY,
3
+ HARNESS_SUBAGENT_PROFILES
4
+ } from "./index-gq9xagx2.js";
5
+
6
+ // src/providers/routing.ts
7
+ var routeError = (value) => new Error(`Invalid route: ${value}. Use <profile>=<provider>[:<model>].`);
8
+ var parseHarnessModelRoute = (value, registry = DEFAULT_PROVIDER_REGISTRY) => {
9
+ const separator = value.indexOf("=");
10
+ if (separator <= 0 || separator === value.length - 1) {
11
+ throw routeError(value);
12
+ }
13
+ const profile = value.slice(0, separator).trim().toLowerCase();
14
+ if (!HARNESS_SUBAGENT_PROFILES.includes(profile)) {
15
+ throw routeError(value);
16
+ }
17
+ const target = value.slice(separator + 1).trim();
18
+ const modelSeparator = target.indexOf(":");
19
+ const providerInput = (modelSeparator === -1 ? target : target.slice(0, modelSeparator)).trim();
20
+ const provider = registry.parse(providerInput);
21
+ const configuredModel = modelSeparator === -1 ? "" : target.slice(modelSeparator + 1).trim();
22
+ if (modelSeparator !== -1 && !configuredModel) {
23
+ throw routeError(value);
24
+ }
25
+ const model = configuredModel || registry.descriptor(provider).defaultModel;
26
+ if (/[\u0000-\u001f\u007f]/.test(model) || model.length > 256) {
27
+ throw routeError(value);
28
+ }
29
+ return Object.freeze({
30
+ profile,
31
+ provider,
32
+ model
33
+ });
34
+ };
35
+ var resolveHarnessModelRoutes = (values = [], registry = DEFAULT_PROVIDER_REGISTRY) => {
36
+ const routes = new Map;
37
+ for (const value of values) {
38
+ const route = parseHarnessModelRoute(value, registry);
39
+ if (routes.has(route.profile)) {
40
+ throw new Error(`Duplicate model route for ${route.profile}.`);
41
+ }
42
+ routes.set(route.profile, route);
43
+ }
44
+ return routes;
45
+ };
46
+ var createHarnessRouteModels = (routes, env = process.env, registry = DEFAULT_PROVIDER_REGISTRY) => Object.fromEntries([...routes].map(([profile, route]) => [
47
+ profile,
48
+ registry.createModel(route, env)
49
+ ]));
50
+ var serializeHarnessModelRoutes = (routes) => Object.fromEntries([...routes].map(([profile, route]) => [
51
+ profile,
52
+ { provider: route.provider, model: route.model }
53
+ ]));
54
+
55
+ // src/compat/cli-options.ts
2
56
  var repeatableOptions = new Set([
3
57
  "--route",
4
58
  "--oci-allow-command",
@@ -105,6 +159,7 @@ var CLI_OPTION_NAMES = [
105
159
  "--help",
106
160
  "--version",
107
161
  "--search",
162
+ "--tool-policy",
108
163
  "--pricing-file",
109
164
  "--usage-limit-usd"
110
165
  ];
@@ -167,6 +222,7 @@ var childBudgets = [
167
222
  var agent = [
168
223
  "--compaction-model",
169
224
  "--compaction-provider",
225
+ "--tool-policy",
170
226
  "--pricing-file",
171
227
  "--usage-limit-usd",
172
228
  ...provider,
@@ -196,6 +252,7 @@ var CLI_COMMAND_OPTION_CONTRACTS = {
196
252
  init: contract([...provider, ...profile, "--update", "--json"]),
197
253
  run: contract(["--service", "--session", ...agent, "--idempotency-key", "--approval-mode", "--yes", "--json", "--jsonl"]),
198
254
  review: contract([
255
+ "--tool-policy",
199
256
  "--pricing-file",
200
257
  "--usage-limit-usd",
201
258
  ...provider,
@@ -212,18 +269,30 @@ var CLI_COMMAND_OPTION_CONTRACTS = {
212
269
  ]),
213
270
  chat: contract(["--service", ...agent, "--approval-mode", "--yes", "--session", "--continue"]),
214
271
  providers: contract(["--json"]),
272
+ policy: contract(["--service", ...agent, "--json"]),
215
273
  doctor: contract([...provider, ...profile, ...locator, ...project, ...execution, ...budgets, "--allow-check", "--require-capability", "--subagent", "--json"]),
216
- resume: contract(["--service", "--session", ...locator, "--approve", "--deny", "--json", "--jsonl"], [["--approve", "--deny"]]),
274
+ resume: contract(["--tool-policy", "--service", "--session", ...locator, "--approve", "--deny", "--json", "--jsonl"], [["--approve", "--deny"]]),
217
275
  "runs:list": contract([...locator, "--status", "--limit", "--cursor", "--json"]),
218
276
  "runs:inspect": contract([...locator, "--json"]),
219
277
  "runs:cancel": contract([...locator, "--reason", "--cascade", "--final", "--json"]),
220
278
  "runs:cleanup": contract([...locator, "--before", "--status", "--limit", "--json"], ["--before"]),
221
279
  "runs:export": contract([...locator, "--json"]),
280
+ "runs:report": contract([...locator, "--json", "--session"]),
222
281
  "sessions:list": contract(["--service", ...locator, "--limit", "--json", "--search"]),
223
282
  "sessions:inspect": contract(["--service", ...locator, "--json"]),
224
283
  "sessions:rename": contract(["--service", ...locator, "--json"]),
225
284
  "sessions:fork": contract([...locator, "--json"]),
226
285
  "sessions:archive": contract([...locator, "--json"]),
286
+ "checkpoints:storage": contract([...locator, "--json"]),
287
+ "checkpoints:prune-review": contract([...locator, "--json"]),
288
+ "checkpoints:prune-apply": contract([...locator, "--json"]),
289
+ "checkpoints:list": contract([...locator, "--json"]),
290
+ "checkpoints:capture": contract([...locator, "--json"]),
291
+ "checkpoints:inspect": contract([...locator, "--json"]),
292
+ "checkpoints:prepare": contract([...locator, "--json"]),
293
+ "checkpoints:review": contract([...locator, "--json"]),
294
+ "checkpoints:apply": contract([...locator, "--json"]),
295
+ "checkpoints:recover": contract([...locator, "--json"]),
227
296
  "changes:create": contract(["--patch"], ["--patch"]),
228
297
  "changes:verify": contract(["--patch", "--preconditions", "--now"], ["--patch"]),
229
298
  "state:status": contract([...locator, "--json"]),
@@ -261,116 +330,4 @@ var validateCliCommandOptions = (commandKey, counts) => {
261
330
  }
262
331
  };
263
332
 
264
- // src/cli/cli-help.ts
265
- var usage = {
266
- init: "zhx init [--provider <id>] [--model <id>] [--profile <name>] [--update]",
267
- run: 'zhx run [options] "task" | zhx run [options] -',
268
- review: 'zhx review [options] "review task" | zhx review [options] -',
269
- chat: "zhx [--continue | --session <id>]",
270
- providers: "zhx providers [--json]",
271
- doctor: "zhx doctor [options]",
272
- resume: "zhx resume <runId> --approve|--deny",
273
- "runs:list": "zhx runs list [options]",
274
- "runs:inspect": "zhx runs inspect <runId>",
275
- "runs:cancel": "zhx runs cancel <runId> [options]",
276
- "runs:cleanup": "zhx runs cleanup --before <date> [options]",
277
- "runs:export": "zhx runs export <runId>",
278
- "sessions:list": "zhx sessions list [--search <text>]",
279
- "sessions:inspect": "zhx sessions inspect <sessionId>",
280
- "sessions:rename": 'zhx sessions rename <sessionId> "title"',
281
- "sessions:fork": "zhx sessions fork <sessionId>",
282
- "sessions:archive": "zhx sessions archive <sessionId>",
283
- "changes:create": "zhx changes create <input.json> --patch <artifact>",
284
- "changes:verify": "zhx changes verify <envelope.json> --patch <artifact> [options]",
285
- "state:status": "zhx state status",
286
- "state:export": "zhx state export <backup.json>",
287
- "state:import": "zhx state import <backup.json> [--apply]",
288
- version: "zhx --version"
289
- };
290
- var shortCliHelp = (version) => `Zhivex Harness v${version}
291
-
292
- Start in your project:
293
- zhx Open the conversation (first-use setup if needed)
294
- zhx --continue Reopen the latest conversation
295
- zhx run "task" Run one task for a script or automation
296
- zhx doctor Check local configuration
297
-
298
- Inside the console, type / for common actions or /help for guidance.
299
- zhx init Configure a provider/model profile explicitly
300
- zhx <command> --help Show help for one command
301
- zhx help all Show every command and advanced option
302
- zhx --version Show the installed version
303
-
304
- Requires Node >=22.13. The console guides API key setup; automation uses environment keys.
305
- zhx chat and zhivex-harness remain supported aliases.`;
306
- function resolveHelpTopic(parts, explicit) {
307
- if (!explicit || !parts.length)
308
- return;
309
- const topic = parts.join(":");
310
- if (topic === "all" || Object.hasOwn(usage, topic) || Object.keys(usage).some((key) => key.startsWith(`${topic}:`)))
311
- return topic;
312
- throw new Error(`Unknown help topic: ${parts.join(" ")}. Use zhx help all.`);
313
- }
314
- function formatCliHelp(topic, version, full) {
315
- if (!topic)
316
- return shortCliHelp(version);
317
- if (topic === "all")
318
- return full;
319
- const contract2 = CLI_COMMAND_OPTION_CONTRACTS[topic];
320
- if (!contract2) {
321
- return `Zhivex Harness — ${topic}
322
-
323
- ` + Object.entries(usage).filter(([key]) => key.startsWith(`${topic}:`)).map(([, command]) => ` ${command}`).join(`
324
- `) + `
325
-
326
- Use zhx ${topic} <subcommand> --help for its options.`;
327
- }
328
- const descriptions = new Map;
329
- for (const line of full.split(`
330
- `)) {
331
- const match = line.match(/^ (--[\w-]+)(?:\s|$)/);
332
- if (match)
333
- descriptions.set(match[1], line);
334
- }
335
- const extra = {
336
- "--approve": " --approve Approve the pending batch",
337
- "--deny": " --deny Deny the pending batch",
338
- "--status": " --status <status> Filter by run status; repeatable",
339
- "--limit": " --limit <n> Maximum number of results",
340
- "--search": " --search <text> Literal conversation title or ID filter",
341
- "--pricing-file": " --pricing-file <file.json> Operator-supplied price estimates",
342
- "--usage-limit-usd": " --usage-limit-usd <amount> Per-run monetary limit; requires pricing",
343
- "--version": " --version Show the installed version"
344
- };
345
- const examples = {
346
- run: ['zhx run "Explain this repository"', 'zhx run --profile daily --json "Review the parser"', "git diff | zhx run -"],
347
- review: ['zhx review "Review error handling"', "zhx review - < review-task.txt"],
348
- init: ["zhx init --profile daily", "zhx init --profile daily --update --model <id>"],
349
- doctor: ["zhx doctor --profile daily", "zhx doctor --workspace /path/to/project"]
350
- };
351
- const groups = new Map;
352
- for (const name of contract2.allowed) {
353
- const group = name.startsWith("--oci-") || ["--execution", "--require-verified-delivery", "--allow-check", "--yes"].includes(name) ? "Execution and approvals" : /subagent|reviewer|parallel-reviews|^--route$/.test(name) ? "Specialist agents" : /cost|pricing|usage-limit|^--max-|^--timeout/.test(name) ? "Budgets and limits" : ["--state-dir", "--store", "--tenant", "--user", "--namespace", "--idempotency-key"].includes(name) ? "State and scope" : ["--mcp-config", "--context-config", "--no-project-context", "--require-capability"].includes(name) ? "Context and capabilities" : "Common options";
354
- const lines = groups.get(group) ?? [];
355
- lines.push(descriptions.get(name) ?? extra[name] ?? ` ${name}`);
356
- groups.set(group, lines);
357
- }
358
- const sections = ["Common options", "Execution and approvals", "Budgets and limits", "Specialist agents", "Context and capabilities", "State and scope"].filter((group) => groups.has(group)).map((group) => `${group}:
359
- ${groups.get(group).join(`
360
- `)}`);
361
- return `Zhivex Harness — ${topic.replace(":", " ")}
362
-
363
- Usage: ${usage[topic]}
364
- ` + (examples[topic] ? `
365
- Examples:
366
- ${examples[topic].map((example) => ` ${example}`).join(`
367
- `)}
368
- ` : "") + `
369
- ${sections.join(`
370
-
371
- `)}
372
-
373
- Use zhx help all for the full reference and exit codes.`;
374
- }
375
-
376
- export { CLI_OPTION_DEFINITIONS, validateCliCommandOptions, shortCliHelp, resolveHelpTopic, formatCliHelp };
333
+ export { parseHarnessModelRoute, resolveHarnessModelRoutes, createHarnessRouteModels, serializeHarnessModelRoutes, CLI_OPTION_NAMES, CLI_OPTION_DEFINITIONS, CLI_COMMAND_OPTION_CONTRACTS, validateCliCommandOptions };
@@ -0,0 +1,356 @@
1
+ // src/client/acp-mcp-session.ts
2
+ var providers = new WeakMap;
3
+ function issueAcpMcpSessionProvider(provider) {
4
+ const token = Object.freeze({});
5
+ providers.set(token, provider);
6
+ return token;
7
+ }
8
+ function resolveAcpMcpSessionProvider(token, adapter) {
9
+ const provider = providers.get(token);
10
+ if (!provider || provider.adapter !== adapter)
11
+ throw new Error("ACP MCP requires its host-owned adapter capability.");
12
+ return provider;
13
+ }
14
+
15
+ // src/client/acp.ts
16
+ import { randomUUID } from "node:crypto";
17
+ import { isAbsolute, resolve } from "node:path";
18
+ import { z } from "zod";
19
+ var envelope = z.object({ jsonrpc: z.literal("2.0"), id: z.union([z.string(), z.number().int()]).optional(), method: z.string(), params: z.unknown().optional() }).strict();
20
+ var sessionParams = z.object({ sessionId: z.string().min(1) }).strict();
21
+ var permissionResult = z.object({ outcome: z.union([
22
+ z.object({ outcome: z.literal("cancelled") }).strict(),
23
+ z.object({ outcome: z.literal("selected"), optionId: z.enum(["allow_once", "reject_once"]) }).strict()
24
+ ]) });
25
+
26
+ class AcpFault extends Error {
27
+ code;
28
+ constructor(code, message) {
29
+ super(message);
30
+ this.code = code;
31
+ }
32
+ }
33
+ var createAcpConnection = (adapter, options) => {
34
+ if (!isAbsolute(options.workspace))
35
+ throw new Error("ACP workspace must be absolute");
36
+ const negotiated = adapter.negotiate([1]);
37
+ if (!negotiated.ok)
38
+ throw new Error(negotiated.error.code);
39
+ const mcp = options.mcpSessionProvider ? resolveAcpMcpSessionProvider(options.mcpSessionProvider, adapter) : undefined;
40
+ let initialized = false;
41
+ let creating = false;
42
+ const sessions = new Set;
43
+ let active;
44
+ const command = async (value) => {
45
+ const response = await adapter.dispatch({
46
+ protocolVersion: 1,
47
+ requestId: randomUUID(),
48
+ connectionId: negotiated.connectionId,
49
+ command: { ...value, projectId: negotiated.projectId }
50
+ });
51
+ if (!response.ok)
52
+ throw new AcpFault(-32000, response.error.code);
53
+ return response.data;
54
+ };
55
+ const requireSession = (id) => {
56
+ if (!sessions.has(id))
57
+ throw new AcpFault(-32602, "Unknown session");
58
+ };
59
+ const execute = async (method, params) => {
60
+ if (method === "initialize") {
61
+ const input2 = z.object({ protocolVersion: z.literal(1), clientCapabilities: z.record(z.string(), z.unknown()).optional(), clientInfo: z.unknown().optional(), _meta: z.unknown().optional() }).parse(params);
62
+ initialized = true;
63
+ return {
64
+ protocolVersion: 1,
65
+ agentCapabilities: { loadSession: false, promptCapabilities: { image: false, audio: false, embeddedContext: false }, mcpCapabilities: { http: false, sse: false } },
66
+ authMethods: [],
67
+ agentInfo: { name: "zhivex-harness", title: "Zhivex Harness (text-session subset)", version: "1" },
68
+ _meta: { zhivex: { subset: true, fixedWorkspace: true, clientMcp: !!mcp } }
69
+ };
70
+ }
71
+ if (!initialized)
72
+ throw new AcpFault(-32002, "Initialize first");
73
+ if (method === "session/new") {
74
+ if (sessions.size >= 256)
75
+ throw new AcpFault(-32000, "Session capacity exceeded");
76
+ if (active || creating)
77
+ throw new AcpFault(-32000, "BUSY");
78
+ const input2 = z.object({ cwd: z.string(), mcpServers: z.array(z.unknown()).max(mcp ? 8 : 0) }).strict().parse(params);
79
+ if (!isAbsolute(input2.cwd) || resolve(input2.cwd) !== resolve(options.workspace))
80
+ throw new AcpFault(-32602, "Workspace is host-owned");
81
+ let servers;
82
+ try {
83
+ servers = mcp?.parse(input2.mcpServers);
84
+ } catch {
85
+ throw new AcpFault(-32602, "MCP configuration was not accepted by the host");
86
+ }
87
+ creating = true;
88
+ try {
89
+ const data = await command({ method: "session.create", idempotencyKey: randomUUID() });
90
+ if (data.kind !== "session")
91
+ throw new AcpFault(-32603, "Invalid adapter response");
92
+ if (mcp && servers)
93
+ mcp.bind(data.session.sessionId, servers);
94
+ sessions.add(data.session.sessionId);
95
+ return { sessionId: data.session.sessionId };
96
+ } finally {
97
+ creating = false;
98
+ }
99
+ }
100
+ if (method === "session/cancel") {
101
+ const input2 = sessionParams.parse(params);
102
+ requireSession(input2.sessionId);
103
+ if (active?.sessionId === input2.sessionId) {
104
+ active.cancelled = true;
105
+ active.cancel();
106
+ await adapter.cancelActive();
107
+ }
108
+ return {};
109
+ }
110
+ if (method !== "session/prompt")
111
+ throw new AcpFault(-32601, "Method not supported");
112
+ const input = sessionParams.extend({ prompt: z.array(z.object({ type: z.literal("text"), text: z.string().max(64 * 1024) }).strict()).min(1).max(64) }).parse(params);
113
+ requireSession(input.sessionId);
114
+ const prompt = input.prompt.map((block) => block.text).join(`
115
+ `);
116
+ if (!prompt.trim() || prompt.length > 64 * 1024)
117
+ throw new AcpFault(-32602, "Invalid prompt size");
118
+ if (active || creating)
119
+ throw new AcpFault(-32000, "BUSY");
120
+ const permissionController = new AbortController;
121
+ let cancel;
122
+ const current = { sessionId: input.sessionId, cancelled: false, cancellation: new Promise((resolve2) => {
123
+ cancel = resolve2;
124
+ }), cancel: () => {
125
+ permissionController.abort();
126
+ cancel();
127
+ } };
128
+ active = current;
129
+ try {
130
+ const session = await command({ method: "session.get", sessionId: input.sessionId });
131
+ if (session.kind !== "session")
132
+ throw new AcpFault(-32603, "Invalid adapter response");
133
+ if (current.cancelled)
134
+ return { stopReason: "cancelled" };
135
+ let data = await command({
136
+ method: "run.start",
137
+ sessionId: input.sessionId,
138
+ expectedRevision: session.session.revision,
139
+ prompt,
140
+ idempotencyKey: randomUUID()
141
+ });
142
+ while (data.kind === "run" && data.run.status === "waiting_approval" && !current.cancelled) {
143
+ const decisions = [];
144
+ for (const approval of data.run.approvals) {
145
+ const answer = await Promise.race([
146
+ options.requestPermission({
147
+ sessionId: input.sessionId,
148
+ toolCall: { toolCallId: approval.approvalId, title: "Review pending action", kind: "other", status: "pending", rawInput: approval.action },
149
+ options: [{ optionId: "allow_once", name: "Allow once", kind: "allow_once" }, { optionId: "reject_once", name: "Reject once", kind: "reject_once" }]
150
+ }, permissionController.signal),
151
+ current.cancellation.then(() => ({ outcome: { outcome: "cancelled" } }))
152
+ ]);
153
+ const result = permissionResult.parse(answer);
154
+ if (result.outcome.outcome === "cancelled" || current.cancelled) {
155
+ current.cancelled = true;
156
+ break;
157
+ }
158
+ decisions.push({ approvalId: approval.approvalId, digest: approval.digest, approve: result.outcome.optionId === "allow_once" });
159
+ }
160
+ if (current.cancelled)
161
+ break;
162
+ if (!decisions.length)
163
+ throw new AcpFault(-32603, "Missing pending approval");
164
+ data = await command({
165
+ method: "approval.resolve",
166
+ sessionId: input.sessionId,
167
+ runId: data.run.runId,
168
+ expectedRevision: data.run.revision,
169
+ decisions,
170
+ idempotencyKey: randomUUID()
171
+ });
172
+ }
173
+ if (data.kind !== "run")
174
+ throw new AcpFault(-32603, "Invalid adapter response");
175
+ if (current.cancelled && !["completed", "failed", "cancelled", "timed_out"].includes(data.run.status)) {
176
+ await command({
177
+ method: "run.cancel",
178
+ sessionId: input.sessionId,
179
+ runId: data.run.runId,
180
+ expectedRevision: data.run.revision,
181
+ idempotencyKey: randomUUID()
182
+ });
183
+ }
184
+ if (data.run.output)
185
+ await options.notify({ jsonrpc: "2.0", method: "session/update", params: {
186
+ sessionId: input.sessionId,
187
+ update: { sessionUpdate: "agent_message_chunk", content: { type: "text", text: data.run.output } }
188
+ } });
189
+ return { stopReason: current.cancelled || data.run.status === "cancelled" ? "cancelled" : data.run.status === "completed" ? "end_turn" : "refusal" };
190
+ } catch (error) {
191
+ if (current.cancelled)
192
+ return { stopReason: "cancelled" };
193
+ throw error;
194
+ } finally {
195
+ permissionController.abort();
196
+ active = undefined;
197
+ }
198
+ };
199
+ return {
200
+ async cancelActive() {
201
+ if (active) {
202
+ active.cancelled = true;
203
+ active.cancel();
204
+ }
205
+ await adapter.cancelActive();
206
+ },
207
+ async handle(value) {
208
+ const parsed = envelope.safeParse(value);
209
+ if (!parsed.success)
210
+ return { jsonrpc: "2.0", id: null, error: { code: -32600, message: "Invalid request" } };
211
+ const request = parsed.data;
212
+ if (request.id === undefined && request.method !== "session/cancel")
213
+ return;
214
+ try {
215
+ const result = await execute(request.method, request.params);
216
+ return request.id === undefined ? undefined : { jsonrpc: "2.0", id: request.id, result };
217
+ } catch (error) {
218
+ if (request.id === undefined)
219
+ return;
220
+ return { jsonrpc: "2.0", id: request.id, error: {
221
+ code: error instanceof AcpFault ? error.code : error instanceof z.ZodError ? -32602 : -32603,
222
+ message: error instanceof AcpFault ? error.message : error instanceof z.ZodError ? "Invalid params" : "Internal error"
223
+ } };
224
+ }
225
+ }
226
+ };
227
+ };
228
+
229
+ // src/client/acp-stdio.ts
230
+ import { randomUUID as randomUUID2 } from "node:crypto";
231
+ var serveAcpStdio = async (adapter, options) => {
232
+ const timeoutMs = options.permissionTimeoutMs ?? 15 * 60000;
233
+ if (!Number.isSafeInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > 15 * 60000)
234
+ throw new Error("Invalid ACP permission timeout");
235
+ const pending = new Map;
236
+ const jobs = new Set;
237
+ const inflight = new Set;
238
+ const write = (value) => new Promise((resolve2, reject) => {
239
+ options.output.write(JSON.stringify(value) + `
240
+ `, (error) => error ? reject(error) : resolve2());
241
+ });
242
+ let transportFailure;
243
+ const connection = createAcpConnection(adapter, {
244
+ workspace: options.workspace,
245
+ ...options.mcpSessionProvider ? { mcpSessionProvider: options.mcpSessionProvider } : {},
246
+ notify: write,
247
+ requestPermission: async (params, signal) => {
248
+ const id = `permission_${randomUUID2()}`;
249
+ const cancel = () => {
250
+ const request = pending.get(id);
251
+ if (request) {
252
+ clearTimeout(request.timer);
253
+ pending.delete(id);
254
+ request.resolve({ outcome: { outcome: "cancelled" } });
255
+ }
256
+ };
257
+ const response = new Promise((resolve2) => {
258
+ const timer = setTimeout(() => {
259
+ pending.delete(id);
260
+ resolve2({ outcome: { outcome: "cancelled" } });
261
+ }, timeoutMs);
262
+ timer.unref();
263
+ pending.set(id, { resolve: resolve2, timer });
264
+ });
265
+ signal?.addEventListener("abort", cancel, { once: true });
266
+ if (signal?.aborted)
267
+ cancel();
268
+ try {
269
+ if (!signal?.aborted)
270
+ await write({ jsonrpc: "2.0", id, method: "session/request_permission", params });
271
+ return await response;
272
+ } finally {
273
+ signal?.removeEventListener("abort", cancel);
274
+ const request = pending.get(id);
275
+ if (request)
276
+ clearTimeout(request.timer);
277
+ pending.delete(id);
278
+ }
279
+ }
280
+ });
281
+ const dispatch = async (line) => {
282
+ let value;
283
+ try {
284
+ value = JSON.parse(line);
285
+ } catch {
286
+ await write({ jsonrpc: "2.0", id: null, error: { code: -32700, message: "Parse error" } });
287
+ return;
288
+ }
289
+ if (value?.jsonrpc === "2.0" && typeof value.id === "string" && value.method === undefined) {
290
+ const request = pending.get(value.id);
291
+ if (request) {
292
+ clearTimeout(request.timer);
293
+ pending.delete(value.id);
294
+ request.resolve("result" in value && !("error" in value) ? value.result : { outcome: { outcome: "cancelled" } });
295
+ }
296
+ return;
297
+ }
298
+ const key = value?.id === undefined ? undefined : JSON.stringify(value.id);
299
+ if (key && inflight.has(key)) {
300
+ await write({ jsonrpc: "2.0", id: value.id, error: { code: -32600, message: "Request ID already active" } });
301
+ return;
302
+ }
303
+ if (key)
304
+ inflight.add(key);
305
+ try {
306
+ const response = await connection.handle(value);
307
+ if (response !== undefined)
308
+ await write(response);
309
+ } finally {
310
+ if (key)
311
+ inflight.delete(key);
312
+ }
313
+ };
314
+ const launch = (line) => {
315
+ if (!line.trim())
316
+ return;
317
+ if (jobs.size >= 64)
318
+ throw new Error("ACP request capacity exceeded");
319
+ const job = dispatch(line).catch((error) => {
320
+ transportFailure = error;
321
+ options.input.destroy();
322
+ });
323
+ jobs.add(job);
324
+ job.finally(() => jobs.delete(job));
325
+ };
326
+ let buffer = Buffer.alloc(0);
327
+ try {
328
+ for await (const chunk of options.input) {
329
+ buffer = Buffer.concat([buffer, Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)]);
330
+ let newline;
331
+ while ((newline = buffer.indexOf(10)) !== -1) {
332
+ if (newline > 1024 * 1024)
333
+ throw new Error("ACP frame exceeds 1 MiB");
334
+ const line = buffer.subarray(0, newline).toString("utf8");
335
+ buffer = buffer.subarray(newline + 1);
336
+ launch(line);
337
+ }
338
+ if (buffer.length > 1024 * 1024)
339
+ throw new Error("ACP frame exceeds 1 MiB");
340
+ }
341
+ if (buffer.length)
342
+ launch(buffer.toString("utf8"));
343
+ } finally {
344
+ for (const request of pending.values()) {
345
+ clearTimeout(request.timer);
346
+ request.resolve({ outcome: { outcome: "cancelled" } });
347
+ }
348
+ pending.clear();
349
+ await connection.cancelActive();
350
+ await Promise.allSettled(jobs);
351
+ }
352
+ if (transportFailure)
353
+ throw transportFailure;
354
+ };
355
+
356
+ export { issueAcpMcpSessionProvider, createAcpConnection, serveAcpStdio };