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

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 (55) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +7 -8
  3. package/contracts/engine-api.json +2587 -0
  4. package/dist/acp-cli.js +95 -14996
  5. package/dist/cli/cli-options.d.ts +2 -41
  6. package/dist/cli/cli-stream.d.ts +2 -167
  7. package/dist/cli/run-document.d.ts +2 -91
  8. package/dist/cli/terminal/terminal-continuation.d.ts +2 -3
  9. package/dist/{cli-entry-427869g9.js → cli-entry-0mmeag20.js} +49 -1
  10. package/dist/{cli-entry-b1mfvmxe.js → cli-entry-7h9savv5.js} +1 -2
  11. package/dist/{cli-entry-c2f58zj2.js → cli-entry-xmf48p6a.js} +27 -18
  12. package/dist/{cli-3jmy4h5h.js → cli-qgwvtk0w.js} +5 -6
  13. package/dist/cli.js +2 -2
  14. package/dist/client/continuation.d.ts +3 -0
  15. package/dist/client/run-document.d.ts +91 -0
  16. package/dist/client/stream.d.ts +167 -0
  17. package/dist/compat/cli-options.d.ts +41 -0
  18. package/dist/compat/code-support.d.ts +22 -0
  19. package/dist/compat/code-support.js +138 -0
  20. package/dist/config-6gtpxvjv.js +59 -0
  21. package/dist/{config-1k9t4jhh.js → config-q32jm1e6.js} +1 -1
  22. package/dist/engine/acp.d.ts +4 -0
  23. package/dist/engine/acp.js +9 -0
  24. package/dist/engine/client.d.ts +6 -0
  25. package/dist/engine/client.js +21 -0
  26. package/dist/engine/index.d.ts +61 -0
  27. package/dist/engine/index.js +477 -0
  28. package/dist/engine/protocol.d.ts +6 -0
  29. package/dist/engine/protocol.js +158 -0
  30. package/dist/engine/service.d.ts +5 -0
  31. package/dist/engine/service.js +21 -0
  32. package/dist/hostile-repository-demo.js +59 -1
  33. package/dist/index-1bx83sw4.js +4 -0
  34. package/dist/index-2aymdsqc.js +150 -0
  35. package/dist/index-41fkzt91.js +293 -0
  36. package/dist/index-5wjwe91x.js +9434 -0
  37. package/dist/index-77h9qr5h.js +326 -0
  38. package/dist/index-d0wrghff.js +3271 -0
  39. package/dist/index-f4sb0e2q.js +318 -0
  40. package/dist/index-fvca1xea.js +2069 -0
  41. package/dist/index-kfgt9yqq.js +526 -0
  42. package/dist/index-pgqz45fj.js +2449 -0
  43. package/dist/index.js +238 -18649
  44. package/dist/service-cli.js +10 -14502
  45. package/dist/{service-client-cli-yx56n0hc.js → service-client-cli-gs78wrxe.js} +2 -2
  46. package/dist/verify-historical-migrations.js +59 -1
  47. package/dist/zhx.js +2 -2
  48. package/docs/CODE_RELEASE.md +93 -0
  49. package/docs/ENGINE_API.md +111 -0
  50. package/docs/FIRST_USE.md +6 -6
  51. package/docs/README.md +5 -0
  52. package/docs/STABILITY.md +6 -0
  53. package/evaluations/representative-assembly-matrix.json +28 -1
  54. package/examples/README.md +1 -1
  55. package/package.json +35 -4
@@ -0,0 +1,326 @@
1
+ // src/client/acp.ts
2
+ import { randomUUID } from "node:crypto";
3
+ import { isAbsolute, resolve } from "node:path";
4
+ import { z } from "zod";
5
+ 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();
6
+ var sessionParams = z.object({ sessionId: z.string().min(1) }).strict();
7
+ var permissionResult = z.object({ outcome: z.union([
8
+ z.object({ outcome: z.literal("cancelled") }).strict(),
9
+ z.object({ outcome: z.literal("selected"), optionId: z.enum(["allow_once", "reject_once"]) }).strict()
10
+ ]) });
11
+
12
+ class AcpFault extends Error {
13
+ code;
14
+ constructor(code, message) {
15
+ super(message);
16
+ this.code = code;
17
+ }
18
+ }
19
+ var createAcpConnection = (adapter, options) => {
20
+ if (!isAbsolute(options.workspace))
21
+ throw new Error("ACP workspace must be absolute");
22
+ const negotiated = adapter.negotiate([1]);
23
+ if (!negotiated.ok)
24
+ throw new Error(negotiated.error.code);
25
+ let initialized = false;
26
+ const sessions = new Set;
27
+ let active;
28
+ const command = async (value) => {
29
+ const response = await adapter.dispatch({
30
+ protocolVersion: 1,
31
+ requestId: randomUUID(),
32
+ connectionId: negotiated.connectionId,
33
+ command: { ...value, projectId: negotiated.projectId }
34
+ });
35
+ if (!response.ok)
36
+ throw new AcpFault(-32000, response.error.code);
37
+ return response.data;
38
+ };
39
+ const requireSession = (id) => {
40
+ if (!sessions.has(id))
41
+ throw new AcpFault(-32602, "Unknown session");
42
+ };
43
+ const execute = async (method, params) => {
44
+ if (method === "initialize") {
45
+ 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);
46
+ initialized = true;
47
+ return {
48
+ protocolVersion: 1,
49
+ agentCapabilities: { loadSession: false, promptCapabilities: { image: false, audio: false, embeddedContext: false }, mcpCapabilities: { http: false, sse: false } },
50
+ authMethods: [],
51
+ agentInfo: { name: "zhivex-harness", title: "Zhivex Harness (text-session subset)", version: "1" },
52
+ _meta: { zhivex: { subset: true, fixedWorkspace: true, clientMcp: false } }
53
+ };
54
+ }
55
+ if (!initialized)
56
+ throw new AcpFault(-32002, "Initialize first");
57
+ if (method === "session/new") {
58
+ if (sessions.size >= 256)
59
+ throw new AcpFault(-32000, "Session capacity exceeded");
60
+ if (active)
61
+ throw new AcpFault(-32000, "BUSY");
62
+ const input2 = z.object({ cwd: z.string(), mcpServers: z.array(z.unknown()).max(0) }).strict().parse(params);
63
+ if (!isAbsolute(input2.cwd) || resolve(input2.cwd) !== resolve(options.workspace))
64
+ throw new AcpFault(-32602, "Workspace is host-owned");
65
+ const data = await command({ method: "session.create", idempotencyKey: randomUUID() });
66
+ if (data.kind !== "session")
67
+ throw new AcpFault(-32603, "Invalid adapter response");
68
+ sessions.add(data.session.sessionId);
69
+ return { sessionId: data.session.sessionId };
70
+ }
71
+ if (method === "session/cancel") {
72
+ const input2 = sessionParams.parse(params);
73
+ requireSession(input2.sessionId);
74
+ if (active?.sessionId === input2.sessionId) {
75
+ active.cancelled = true;
76
+ active.cancel();
77
+ await adapter.cancelActive();
78
+ }
79
+ return {};
80
+ }
81
+ if (method !== "session/prompt")
82
+ throw new AcpFault(-32601, "Method not supported");
83
+ 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);
84
+ requireSession(input.sessionId);
85
+ const prompt = input.prompt.map((block) => block.text).join(`
86
+ `);
87
+ if (!prompt.trim() || prompt.length > 64 * 1024)
88
+ throw new AcpFault(-32602, "Invalid prompt size");
89
+ if (active)
90
+ throw new AcpFault(-32000, "BUSY");
91
+ const permissionController = new AbortController;
92
+ let cancel;
93
+ const current = { sessionId: input.sessionId, cancelled: false, cancellation: new Promise((resolve2) => {
94
+ cancel = resolve2;
95
+ }), cancel: () => {
96
+ permissionController.abort();
97
+ cancel();
98
+ } };
99
+ active = current;
100
+ try {
101
+ const session = await command({ method: "session.get", sessionId: input.sessionId });
102
+ if (session.kind !== "session")
103
+ throw new AcpFault(-32603, "Invalid adapter response");
104
+ if (current.cancelled)
105
+ return { stopReason: "cancelled" };
106
+ let data = await command({
107
+ method: "run.start",
108
+ sessionId: input.sessionId,
109
+ expectedRevision: session.session.revision,
110
+ prompt,
111
+ idempotencyKey: randomUUID()
112
+ });
113
+ while (data.kind === "run" && data.run.status === "waiting_approval" && !current.cancelled) {
114
+ const decisions = [];
115
+ for (const approval of data.run.approvals) {
116
+ const answer = await Promise.race([
117
+ options.requestPermission({
118
+ sessionId: input.sessionId,
119
+ toolCall: { toolCallId: approval.approvalId, title: "Review pending action", kind: "other", status: "pending", rawInput: approval.action },
120
+ options: [{ optionId: "allow_once", name: "Allow once", kind: "allow_once" }, { optionId: "reject_once", name: "Reject once", kind: "reject_once" }]
121
+ }, permissionController.signal),
122
+ current.cancellation.then(() => ({ outcome: { outcome: "cancelled" } }))
123
+ ]);
124
+ const result = permissionResult.parse(answer);
125
+ if (result.outcome.outcome === "cancelled" || current.cancelled) {
126
+ current.cancelled = true;
127
+ break;
128
+ }
129
+ decisions.push({ approvalId: approval.approvalId, digest: approval.digest, approve: result.outcome.optionId === "allow_once" });
130
+ }
131
+ if (current.cancelled)
132
+ break;
133
+ if (!decisions.length)
134
+ throw new AcpFault(-32603, "Missing pending approval");
135
+ data = await command({
136
+ method: "approval.resolve",
137
+ sessionId: input.sessionId,
138
+ runId: data.run.runId,
139
+ expectedRevision: data.run.revision,
140
+ decisions,
141
+ idempotencyKey: randomUUID()
142
+ });
143
+ }
144
+ if (data.kind !== "run")
145
+ throw new AcpFault(-32603, "Invalid adapter response");
146
+ if (current.cancelled && !["completed", "failed", "cancelled", "timed_out"].includes(data.run.status)) {
147
+ await command({
148
+ method: "run.cancel",
149
+ sessionId: input.sessionId,
150
+ runId: data.run.runId,
151
+ expectedRevision: data.run.revision,
152
+ idempotencyKey: randomUUID()
153
+ });
154
+ }
155
+ if (data.run.output)
156
+ await options.notify({ jsonrpc: "2.0", method: "session/update", params: {
157
+ sessionId: input.sessionId,
158
+ update: { sessionUpdate: "agent_message_chunk", content: { type: "text", text: data.run.output } }
159
+ } });
160
+ return { stopReason: current.cancelled || data.run.status === "cancelled" ? "cancelled" : data.run.status === "completed" ? "end_turn" : "refusal" };
161
+ } catch (error) {
162
+ if (current.cancelled)
163
+ return { stopReason: "cancelled" };
164
+ throw error;
165
+ } finally {
166
+ permissionController.abort();
167
+ active = undefined;
168
+ }
169
+ };
170
+ return {
171
+ async cancelActive() {
172
+ if (active) {
173
+ active.cancelled = true;
174
+ active.cancel();
175
+ }
176
+ await adapter.cancelActive();
177
+ },
178
+ async handle(value) {
179
+ const parsed = envelope.safeParse(value);
180
+ if (!parsed.success)
181
+ return { jsonrpc: "2.0", id: null, error: { code: -32600, message: "Invalid request" } };
182
+ const request = parsed.data;
183
+ if (request.id === undefined && request.method !== "session/cancel")
184
+ return;
185
+ try {
186
+ const result = await execute(request.method, request.params);
187
+ return request.id === undefined ? undefined : { jsonrpc: "2.0", id: request.id, result };
188
+ } catch (error) {
189
+ if (request.id === undefined)
190
+ return;
191
+ return { jsonrpc: "2.0", id: request.id, error: {
192
+ code: error instanceof AcpFault ? error.code : error instanceof z.ZodError ? -32602 : -32603,
193
+ message: error instanceof AcpFault ? error.message : error instanceof z.ZodError ? "Invalid params" : "Internal error"
194
+ } };
195
+ }
196
+ }
197
+ };
198
+ };
199
+
200
+ // src/client/acp-stdio.ts
201
+ import { randomUUID as randomUUID2 } from "node:crypto";
202
+ var serveAcpStdio = async (adapter, options) => {
203
+ const timeoutMs = options.permissionTimeoutMs ?? 15 * 60000;
204
+ if (!Number.isSafeInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > 15 * 60000)
205
+ throw new Error("Invalid ACP permission timeout");
206
+ const pending = new Map;
207
+ const jobs = new Set;
208
+ const inflight = new Set;
209
+ const write = (value) => new Promise((resolve2, reject) => {
210
+ options.output.write(JSON.stringify(value) + `
211
+ `, (error) => error ? reject(error) : resolve2());
212
+ });
213
+ let transportFailure;
214
+ const connection = createAcpConnection(adapter, {
215
+ workspace: options.workspace,
216
+ notify: write,
217
+ requestPermission: async (params, signal) => {
218
+ const id = `permission_${randomUUID2()}`;
219
+ const cancel = () => {
220
+ const request = pending.get(id);
221
+ if (request) {
222
+ clearTimeout(request.timer);
223
+ pending.delete(id);
224
+ request.resolve({ outcome: { outcome: "cancelled" } });
225
+ }
226
+ };
227
+ const response = new Promise((resolve2) => {
228
+ const timer = setTimeout(() => {
229
+ pending.delete(id);
230
+ resolve2({ outcome: { outcome: "cancelled" } });
231
+ }, timeoutMs);
232
+ timer.unref();
233
+ pending.set(id, { resolve: resolve2, timer });
234
+ });
235
+ signal?.addEventListener("abort", cancel, { once: true });
236
+ if (signal?.aborted)
237
+ cancel();
238
+ try {
239
+ if (!signal?.aborted)
240
+ await write({ jsonrpc: "2.0", id, method: "session/request_permission", params });
241
+ return await response;
242
+ } finally {
243
+ signal?.removeEventListener("abort", cancel);
244
+ const request = pending.get(id);
245
+ if (request)
246
+ clearTimeout(request.timer);
247
+ pending.delete(id);
248
+ }
249
+ }
250
+ });
251
+ const dispatch = async (line) => {
252
+ let value;
253
+ try {
254
+ value = JSON.parse(line);
255
+ } catch {
256
+ await write({ jsonrpc: "2.0", id: null, error: { code: -32700, message: "Parse error" } });
257
+ return;
258
+ }
259
+ if (value?.jsonrpc === "2.0" && typeof value.id === "string" && value.method === undefined) {
260
+ const request = pending.get(value.id);
261
+ if (request) {
262
+ clearTimeout(request.timer);
263
+ pending.delete(value.id);
264
+ request.resolve("result" in value && !("error" in value) ? value.result : { outcome: { outcome: "cancelled" } });
265
+ }
266
+ return;
267
+ }
268
+ const key = value?.id === undefined ? undefined : JSON.stringify(value.id);
269
+ if (key && inflight.has(key)) {
270
+ await write({ jsonrpc: "2.0", id: value.id, error: { code: -32600, message: "Request ID already active" } });
271
+ return;
272
+ }
273
+ if (key)
274
+ inflight.add(key);
275
+ try {
276
+ const response = await connection.handle(value);
277
+ if (response !== undefined)
278
+ await write(response);
279
+ } finally {
280
+ if (key)
281
+ inflight.delete(key);
282
+ }
283
+ };
284
+ const launch = (line) => {
285
+ if (!line.trim())
286
+ return;
287
+ if (jobs.size >= 64)
288
+ throw new Error("ACP request capacity exceeded");
289
+ const job = dispatch(line).catch((error) => {
290
+ transportFailure = error;
291
+ options.input.destroy();
292
+ });
293
+ jobs.add(job);
294
+ job.finally(() => jobs.delete(job));
295
+ };
296
+ let buffer = Buffer.alloc(0);
297
+ try {
298
+ for await (const chunk of options.input) {
299
+ buffer = Buffer.concat([buffer, Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)]);
300
+ let newline;
301
+ while ((newline = buffer.indexOf(10)) !== -1) {
302
+ if (newline > 1024 * 1024)
303
+ throw new Error("ACP frame exceeds 1 MiB");
304
+ const line = buffer.subarray(0, newline).toString("utf8");
305
+ buffer = buffer.subarray(newline + 1);
306
+ launch(line);
307
+ }
308
+ if (buffer.length > 1024 * 1024)
309
+ throw new Error("ACP frame exceeds 1 MiB");
310
+ }
311
+ if (buffer.length)
312
+ launch(buffer.toString("utf8"));
313
+ } finally {
314
+ for (const request of pending.values()) {
315
+ clearTimeout(request.timer);
316
+ request.resolve({ outcome: { outcome: "cancelled" } });
317
+ }
318
+ pending.clear();
319
+ await connection.cancelActive();
320
+ await Promise.allSettled(jobs);
321
+ }
322
+ if (transportFailure)
323
+ throw transportFailure;
324
+ };
325
+
326
+ export { createAcpConnection, serveAcpStdio };