@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,2449 @@
1
+ import {
2
+ HarnessConfigError,
3
+ HarnessStateConflictError,
4
+ HarnessWorkspaceError
5
+ } from "./index-fvca1xea.js";
6
+
7
+ // src/persistence/persistence-secrets.ts
8
+ var protectedValues = new Set;
9
+ function assertNoPersistenceSecret(value) {
10
+ if (!protectedValues.size)
11
+ return;
12
+ const seen = new WeakSet;
13
+ const visit = (item) => {
14
+ if (typeof item === "string") {
15
+ for (const secret of protectedValues)
16
+ if (item.includes(secret))
17
+ throw new Error("PERSISTENCE_SECRET_REJECTED");
18
+ } else if (item instanceof Uint8Array) {
19
+ const bytes = Buffer.from(item.buffer, item.byteOffset, item.byteLength);
20
+ for (const secret of protectedValues)
21
+ if (bytes.includes(Buffer.from(secret)))
22
+ throw new Error("PERSISTENCE_SECRET_REJECTED");
23
+ } else if (item && typeof item === "object" && !seen.has(item)) {
24
+ seen.add(item);
25
+ for (const [key, entry] of Object.entries(item)) {
26
+ visit(key);
27
+ visit(entry);
28
+ }
29
+ }
30
+ };
31
+ visit(value);
32
+ }
33
+
34
+ // src/persistence/sqlite-access.ts
35
+ import { closeSync, constants, fchmodSync, fstatSync, lstatSync, openSync, realpathSync } from "node:fs";
36
+ import path from "node:path";
37
+ var O_SHLOCK = 16;
38
+ var O_EXLOCK = 32;
39
+ var held = new WeakMap;
40
+ var activeDescriptors = new Set;
41
+ function filename(databasePath) {
42
+ const directory = realpathSync(path.dirname(path.resolve(databasePath)));
43
+ return path.join(directory, `.${path.basename(databasePath)}.access-lock`);
44
+ }
45
+ function releaseDescriptor(state) {
46
+ closeSync(state.fd);
47
+ activeDescriptors.delete(state.fd);
48
+ }
49
+ function makeLease(lock, fd, exclusive) {
50
+ const state = { filename: lock, fd, closed: false, borrowers: 0, exclusive };
51
+ const lease = Object.freeze({ fd, close() {
52
+ if (!state.closed) {
53
+ state.closed = true;
54
+ if (state.borrowers === 0)
55
+ releaseDescriptor(state);
56
+ }
57
+ } });
58
+ held.set(lease, state);
59
+ activeDescriptors.add(fd);
60
+ return lease;
61
+ }
62
+ function acquireSqliteAccess(databasePath, exclusive = false) {
63
+ if (databasePath === ":memory:" || process.platform !== "darwin")
64
+ return;
65
+ let fd;
66
+ try {
67
+ const lock = filename(databasePath);
68
+ fd = openSync(lock, constants.O_RDWR | constants.O_CREAT | constants.O_NOFOLLOW | constants.O_NONBLOCK | (exclusive ? O_EXLOCK : O_SHLOCK), 384);
69
+ const info = fstatSync(fd);
70
+ const current = lstatSync(lock);
71
+ if (!info.isFile() || info.nlink !== 1 || info.uid !== process.getuid?.() || info.size !== 0 || current.isSymbolicLink() || current.dev !== info.dev || current.ino !== info.ino)
72
+ throw new Error;
73
+ if (info.mode & 63)
74
+ fchmodSync(fd, 384);
75
+ if (fstatSync(fd).mode & 63)
76
+ throw new Error;
77
+ return makeLease(lock, fd, exclusive);
78
+ } catch {
79
+ if (fd !== undefined)
80
+ closeSync(fd);
81
+ throw new Error("SQLITE_ACCESS_UNAVAILABLE");
82
+ }
83
+ }
84
+ function retainSqliteAccess(lease, databasePath) {
85
+ validateSqliteAccess(lease, databasePath);
86
+ const state = held.get(lease);
87
+ state.borrowers++;
88
+ let released = false;
89
+ return () => {
90
+ if (!released) {
91
+ released = true;
92
+ state.borrowers--;
93
+ if (state.closed && state.borrowers === 0)
94
+ releaseDescriptor(state);
95
+ }
96
+ };
97
+ }
98
+ function validateSqliteAccess(lease, databasePath) {
99
+ try {
100
+ const state = held.get(lease);
101
+ if (!state || state.closed || state.filename !== filename(databasePath))
102
+ throw new Error;
103
+ const current = lstatSync(state.filename), owned = fstatSync(state.fd);
104
+ if (current.isSymbolicLink() || !current.isFile() || current.nlink !== 1 || current.dev !== owned.dev || current.ino !== owned.ino)
105
+ throw new Error;
106
+ } catch {
107
+ throw new Error("SQLITE_ACCESS_UNAVAILABLE");
108
+ }
109
+ }
110
+
111
+ // src/persistence/sqlite-database.ts
112
+ import { createRequire } from "node:module";
113
+ var require2 = createRequire(import.meta.url);
114
+ var DatabaseSyncConstructor;
115
+ var loadDatabaseSync = () => {
116
+ DatabaseSyncConstructor ??= require2("node:sqlite").DatabaseSync;
117
+ return DatabaseSyncConstructor;
118
+ };
119
+ var normalizeNumberedParameters = (sql) => {
120
+ const indexes = new Set;
121
+ const normalizedSql = sql.replace(/\?([1-9]\d*)/g, (_match, rawIndex) => {
122
+ const index = Number(rawIndex);
123
+ indexes.add(index);
124
+ return `$__zhivex_${index}`;
125
+ });
126
+ return {
127
+ sql: normalizedSql,
128
+ indexes: [...indexes].sort((left, right) => left - right)
129
+ };
130
+ };
131
+ var numberedBindings = (indexes, parameters) => {
132
+ if (indexes.length === 0)
133
+ return;
134
+ const maximumIndex = indexes.at(-1);
135
+ if (parameters.length !== maximumIndex) {
136
+ throw new Error(`SQLite numbered parameters require exactly ${maximumIndex} binding${maximumIndex === 1 ? "" : "s"}; received ${parameters.length}.`);
137
+ }
138
+ return Object.fromEntries(indexes.map((index) => [`$__zhivex_${index}`, parameters[index - 1]]));
139
+ };
140
+
141
+ class SqliteDatabase {
142
+ #database;
143
+ #access;
144
+ #ownedAccess;
145
+ #releaseBorrowed;
146
+ #databasePath;
147
+ constructor(databasePath, options = {}) {
148
+ options.create;
149
+ options.strict;
150
+ if (options.accessLease)
151
+ validateSqliteAccess(options.accessLease, databasePath);
152
+ this.#access = options.accessLease ?? acquireSqliteAccess(databasePath);
153
+ this.#ownedAccess = options.accessLease === undefined;
154
+ this.#databasePath = databasePath;
155
+ this.#releaseBorrowed = options.accessLease ? retainSqliteAccess(options.accessLease, databasePath) : undefined;
156
+ try {
157
+ const DatabaseSync = loadDatabaseSync();
158
+ this.#database = new DatabaseSync(databasePath, {
159
+ ...options.readonly === undefined ? {} : { readOnly: options.readonly }
160
+ });
161
+ } catch (error) {
162
+ if (this.#ownedAccess)
163
+ this.#access?.close();
164
+ this.#releaseBorrowed?.();
165
+ throw error;
166
+ }
167
+ }
168
+ exec(sql) {
169
+ if (this.#access)
170
+ validateSqliteAccess(this.#access, this.#databasePath);
171
+ assertNoPersistenceSecret(sql);
172
+ this.#database.exec(sql);
173
+ }
174
+ query(sql) {
175
+ assertNoPersistenceSecret(sql);
176
+ if (this.#access)
177
+ validateSqliteAccess(this.#access, this.#databasePath);
178
+ const normalized = normalizeNumberedParameters(sql);
179
+ const statement = this.#database.prepare(normalized.sql);
180
+ const invoke = (method, parameters) => {
181
+ if (this.#access)
182
+ validateSqliteAccess(this.#access, this.#databasePath);
183
+ assertNoPersistenceSecret(sql);
184
+ assertNoPersistenceSecret(parameters);
185
+ const bindings = numberedBindings(normalized.indexes, parameters);
186
+ return bindings ? method(bindings) : method(...parameters);
187
+ };
188
+ return {
189
+ run: (...parameters) => invoke(statement.run.bind(statement), parameters),
190
+ get: (...parameters) => invoke(statement.get.bind(statement), parameters),
191
+ all: (...parameters) => invoke(statement.all.bind(statement), parameters)
192
+ };
193
+ }
194
+ close(throwOnError = true) {
195
+ try {
196
+ this.#database.close();
197
+ if (this.#ownedAccess)
198
+ this.#access?.close();
199
+ this.#releaseBorrowed?.();
200
+ } catch (error) {
201
+ if (throwOnError) {
202
+ throw error;
203
+ }
204
+ }
205
+ }
206
+ }
207
+
208
+ // src/persistence/state-directory.ts
209
+ import { lstat } from "node:fs/promises";
210
+ import path2 from "node:path";
211
+ var SENSITIVE_STATE_SEGMENTS = new Set([".git", ".env", ".npmrc", "dist", "node_modules", "src"]);
212
+ var isInside = (root, candidate) => {
213
+ const relative = path2.relative(root, candidate);
214
+ return relative === "" || !relative.startsWith(`..${path2.sep}`) && relative !== ".." && !path2.isAbsolute(relative);
215
+ };
216
+ var validateStateDirectory = async (workspace, stateDirectory) => {
217
+ if (stateDirectory === workspace || stateDirectory === path2.parse(stateDirectory).root) {
218
+ throw new HarnessWorkspaceError("The state directory cannot be the workspace or filesystem root.");
219
+ }
220
+ const insideWorkspace = isInside(workspace, stateDirectory);
221
+ const relativeSegments = insideWorkspace ? path2.relative(workspace, stateDirectory).split(path2.sep).filter(Boolean) : stateDirectory.slice(path2.parse(stateDirectory).root.length).split(path2.sep).filter(Boolean);
222
+ const sensitiveSegment = insideWorkspace ? relativeSegments.find((segment) => SENSITIVE_STATE_SEGMENTS.has(segment)) : undefined;
223
+ if (sensitiveSegment) {
224
+ throw new HarnessWorkspaceError(`The state directory is inside the protected workspace path: ${sensitiveSegment}.`);
225
+ }
226
+ if (!insideWorkspace) {
227
+ try {
228
+ const externalEntry = await lstat(stateDirectory);
229
+ if (externalEntry.isSymbolicLink()) {
230
+ throw new HarnessWorkspaceError(`The state directory must not be a symbolic link: ${stateDirectory}.`);
231
+ }
232
+ if (!externalEntry.isDirectory()) {
233
+ throw new HarnessWorkspaceError(`The state directory is not a directory: ${stateDirectory}.`);
234
+ }
235
+ } catch (error) {
236
+ if (error.code !== "ENOENT") {
237
+ throw error;
238
+ }
239
+ }
240
+ return;
241
+ }
242
+ let current = workspace;
243
+ for (const segment of relativeSegments) {
244
+ current = path2.join(current, segment);
245
+ try {
246
+ const entry = await lstat(current);
247
+ if (entry.isSymbolicLink()) {
248
+ throw new HarnessWorkspaceError(`The state directory must not resolve through a symbolic link: ${current}.`);
249
+ }
250
+ if (!entry.isDirectory()) {
251
+ throw new HarnessWorkspaceError(`The state directory path contains a non-directory entry: ${current}.`);
252
+ }
253
+ } catch (error) {
254
+ if (error.code === "ENOENT") {
255
+ break;
256
+ }
257
+ throw error;
258
+ }
259
+ }
260
+ };
261
+
262
+ // src/persistence/sessions.ts
263
+ import { createHash, randomUUID } from "node:crypto";
264
+ import { chmod, lstat as lstat3, mkdir, open as open2, realpath, stat } from "node:fs/promises";
265
+ import path4 from "node:path";
266
+ import { createRedactionPolicy } from "@zhivex-ai/agents";
267
+
268
+ // src/persistence/state-gitignore.ts
269
+ import { constants as constants2 } from "node:fs";
270
+ import { lstat as lstat2, open } from "node:fs/promises";
271
+ import path3 from "node:path";
272
+ var exclusion = `# Local Harness state; never commit sessions, runs, or database journals.
273
+ *
274
+ `;
275
+ var maximumExistingBytes = 65536;
276
+ var maximumProtectedBytes = maximumExistingBytes + Buffer.byteLength(exclusion) + 1;
277
+ async function protectStateFromGit(workspace, stateDirectory) {
278
+ const directory = path3.resolve(stateDirectory) === path3.join(workspace, ".zhivex-harness", "runs") ? path3.dirname(stateDirectory) : stateDirectory;
279
+ const filename2 = path3.join(directory, ".gitignore");
280
+ try {
281
+ const handle = await open(filename2, constants2.O_RDWR | constants2.O_CREAT | constants2.O_APPEND | constants2.O_NOFOLLOW, 384);
282
+ try {
283
+ const info = await handle.stat();
284
+ const entry = await lstat2(filename2);
285
+ if (entry.isSymbolicLink() || entry.dev !== info.dev || entry.ino !== info.ino)
286
+ throw new Error;
287
+ if (!info.isFile() || info.nlink !== 1 || process.getuid && info.uid !== process.getuid() || info.size > maximumProtectedBytes)
288
+ throw new Error;
289
+ const buffer = Buffer.alloc(maximumProtectedBytes + 1);
290
+ const { bytesRead } = await handle.read(buffer, 0, buffer.length, 0);
291
+ if (bytesRead > maximumProtectedBytes)
292
+ throw new Error;
293
+ const content = buffer.subarray(0, bytesRead).toString("utf8");
294
+ if (!content.endsWith(exclusion)) {
295
+ if (bytesRead > maximumExistingBytes)
296
+ throw new Error;
297
+ await handle.writeFile(`${content && !content.endsWith(`
298
+ `) ? `
299
+ ` : ""}${exclusion}`);
300
+ }
301
+ const current = await lstat2(filename2);
302
+ if (current.isSymbolicLink() || current.dev !== info.dev || current.ino !== info.ino)
303
+ throw new Error;
304
+ } finally {
305
+ await handle.close();
306
+ }
307
+ } catch (cause) {
308
+ throw new HarnessWorkspaceError("Cannot protect the local state directory with a safe .gitignore.", { cause });
309
+ }
310
+ }
311
+
312
+ // src/persistence/sessions.ts
313
+ var HARNESS_SESSION_INDEX_FILE = "operations.sqlite";
314
+ var HARNESS_SESSION_SCHEMA_VERSION = 1;
315
+ var SESSION_RUN_STATUSES = [
316
+ "created",
317
+ "running",
318
+ "waiting_approval",
319
+ "cancel_requested",
320
+ "completed",
321
+ "failed",
322
+ "cancelled",
323
+ "timed_out"
324
+ ];
325
+ var TERMINAL_SESSION_RUN_STATUSES = [
326
+ "completed",
327
+ "failed",
328
+ "cancelled",
329
+ "timed_out"
330
+ ];
331
+ var DEFAULT_RETENTION_MS = 90 * 24 * 60 * 60 * 1000;
332
+ var DEFAULT_MAX_SESSIONS = 200;
333
+ var DEFAULT_MAX_RUNS = 1000;
334
+ var DEFAULT_MAX_METADATA_BYTES = 4096;
335
+ var DEFAULT_MAX_INDEX_BYTES = 16 * 1024 * 1024;
336
+ var MAX_LIST_LIMIT = 200;
337
+ var ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:@/+\-]{0,255}$/;
338
+ var TERMINAL_STATUSES = new Set(TERMINAL_SESSION_RUN_STATUSES);
339
+ var ACTIVE_STATUSES = new Set([
340
+ "created",
341
+ "running",
342
+ "waiting_approval",
343
+ "cancel_requested"
344
+ ]);
345
+ var STATUS_TRANSITIONS = {
346
+ created: new Set(["created", "running", "waiting_approval", "completed", "failed", "cancelled", "timed_out"]),
347
+ running: new Set(["running", "waiting_approval", "cancel_requested", "completed", "failed", "cancelled", "timed_out"]),
348
+ waiting_approval: new Set(["waiting_approval", "running", "cancel_requested", "completed", "failed", "cancelled", "timed_out"]),
349
+ cancel_requested: new Set(["cancel_requested", "cancelled", "failed", "timed_out"]),
350
+ completed: new Set(["completed"]),
351
+ failed: new Set(["failed"]),
352
+ cancelled: new Set(["cancelled"]),
353
+ timed_out: new Set(["timed_out"])
354
+ };
355
+ var metadataRedaction = createRedactionPolicy({
356
+ includeEmails: true,
357
+ rules: [
358
+ { name: "private-key", pattern: /-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/g },
359
+ { name: "common-provider-key", pattern: /\b(?:sk|ghp|github_pat|xox[baprs])[-_][A-Za-z0-9_-]{8,}\b/gi },
360
+ { name: "url-credentials", pattern: /:\/\/[^\s/@:]+:[^\s/@]+@/g, replacement: "://[REDACTED]@" }
361
+ ]
362
+ });
363
+ var byteLength = (value) => Buffer.byteLength(value, "utf8");
364
+ var boundedInteger = (name, value, fallback, minimum) => {
365
+ const resolved = value ?? fallback;
366
+ if (!Number.isSafeInteger(resolved) || resolved < minimum) {
367
+ throw new HarnessConfigError(`${name} must be an integer greater than or equal to ${minimum}.`);
368
+ }
369
+ return resolved;
370
+ };
371
+ var assertTimestamp = (name, value) => {
372
+ if (!Number.isSafeInteger(value) || value < 0) {
373
+ throw new HarnessConfigError(`${name} must be a non-negative integer timestamp.`);
374
+ }
375
+ };
376
+ var assertIdentifier = (name, value) => {
377
+ if (!ID_PATTERN.test(value)) {
378
+ throw new HarnessConfigError(`${name} contains unsupported characters or exceeds 256 bytes.`);
379
+ }
380
+ if (metadataRedaction.redactText(value) !== value) {
381
+ throw new HarnessConfigError(`${name} resembles sensitive metadata and cannot be stored in the session index.`);
382
+ }
383
+ return value;
384
+ };
385
+ var assertScopeSegment = (name, value) => {
386
+ const normalized = value.trim();
387
+ if (!normalized || normalized.length > 128 || /[\u0000-\u001f\u007f]/.test(normalized)) {
388
+ throw new HarnessConfigError(`${name} must contain 1-128 printable characters.`);
389
+ }
390
+ return normalized;
391
+ };
392
+ var normalizeTitle = (value, maxMetadataBytes) => {
393
+ if (value === undefined || value === null) {
394
+ return value;
395
+ }
396
+ const normalized = metadataRedaction.redactText(value).replace(/[\u0000-\u001f\u007f]+/g, " ").trim();
397
+ if (!normalized) {
398
+ throw new HarnessConfigError("Session title must not be empty.");
399
+ }
400
+ if (byteLength(normalized) > maxMetadataBytes) {
401
+ throw new HarnessConfigError(`Session title exceeds the ${maxMetadataBytes}-byte metadata limit.`);
402
+ }
403
+ return normalized;
404
+ };
405
+ var normalizeScope = (scope) => {
406
+ const tenantId = assertScopeSegment("scope.tenantId", scope.tenantId);
407
+ const userId = scope.userId === undefined ? "" : assertScopeSegment("scope.userId", scope.userId);
408
+ const namespace = scope.namespace === undefined ? "" : assertScopeSegment("scope.namespace", scope.namespace);
409
+ return `${tenantId}\x00${userId}\x00${namespace}`;
410
+ };
411
+ var stableKey = (kind, value) => createHash("sha256").update(kind).update("\x00").update(value).digest("hex");
412
+ var sessionId = () => `ses_${randomUUID()}`;
413
+ var turnId = () => `turn_${randomUUID()}`;
414
+ var ensurePrivateDatabase = async (workspace, stateDirectory) => {
415
+ const requestedStateDirectory = path4.resolve(stateDirectory);
416
+ await validateStateDirectory(workspace, requestedStateDirectory);
417
+ await mkdir(requestedStateDirectory, { recursive: true, mode: 448 });
418
+ const directoryEntry = await lstat3(requestedStateDirectory);
419
+ if (directoryEntry.isSymbolicLink() || !directoryEntry.isDirectory()) {
420
+ throw new HarnessWorkspaceError(`The session state directory must be a real directory: ${requestedStateDirectory}.`);
421
+ }
422
+ await chmod(requestedStateDirectory, 448);
423
+ const canonicalStateDirectory = await realpath(requestedStateDirectory);
424
+ await protectStateFromGit(workspace, canonicalStateDirectory);
425
+ const databasePath = path4.join(canonicalStateDirectory, HARNESS_SESSION_INDEX_FILE);
426
+ let databaseEntry;
427
+ try {
428
+ const handle = await open2(databasePath, "wx", 384);
429
+ await handle.close();
430
+ databaseEntry = await lstat3(databasePath);
431
+ } catch (error) {
432
+ if (error.code !== "EEXIST")
433
+ throw error;
434
+ databaseEntry = await lstat3(databasePath);
435
+ }
436
+ if (databaseEntry.isSymbolicLink() || !databaseEntry.isFile()) {
437
+ throw new HarnessWorkspaceError(`The session SQLite path must be a real file: ${databasePath}.`);
438
+ }
439
+ await chmod(databasePath, 384);
440
+ return { databasePath, databaseEntry };
441
+ };
442
+ var toRunReference = (row) => ({
443
+ turnId: row.turn_id,
444
+ ...row.source_turn_id ? { sourceTurnId: row.source_turn_id } : {},
445
+ sequence: row.ordinal,
446
+ runId: row.run_id,
447
+ provider: row.provider,
448
+ model: row.model,
449
+ ...row.role ? { role: row.role } : {},
450
+ status: row.status,
451
+ createdAt: row.created_at,
452
+ updatedAt: row.updated_at,
453
+ ...row.completed_at === null ? {} : { completedAt: row.completed_at }
454
+ });
455
+ var metadataSize = (input) => Object.values(input).reduce((total, value) => total + (typeof value === "string" ? byteLength(value) : 0), 0);
456
+ var isTerminal = (status) => TERMINAL_STATUSES.has(status);
457
+ var openCliSessionStore = async (options) => {
458
+ const workspace = await realpath(path4.resolve(options.workspace));
459
+ const workspaceEntry = await stat(workspace);
460
+ if (!workspaceEntry.isDirectory()) {
461
+ throw new HarnessWorkspaceError(`The session workspace is not a directory: ${workspace}.`);
462
+ }
463
+ const scopeValue = normalizeScope(options.scope);
464
+ const workspaceKey = stableKey("workspace", workspace);
465
+ const scopeKey = stableKey("scope", scopeValue);
466
+ const retentionMs = boundedInteger("retentionMs", options.retentionMs, DEFAULT_RETENTION_MS, 0);
467
+ const maxSessions = boundedInteger("maxSessionsPerWorkspace", options.maxSessionsPerWorkspace, DEFAULT_MAX_SESSIONS, 1);
468
+ const maxRuns = boundedInteger("maxRunsPerSession", options.maxRunsPerSession, DEFAULT_MAX_RUNS, 1);
469
+ const maxMetadataBytes = boundedInteger("maxMetadataBytes", options.maxMetadataBytes, DEFAULT_MAX_METADATA_BYTES, 32);
470
+ const maxIndexBytes = boundedInteger("maxIndexBytes", options.maxIndexBytes, DEFAULT_MAX_INDEX_BYTES, 1024);
471
+ const busyTimeoutMs = boundedInteger("busyTimeoutMs", options.busyTimeoutMs, 5000, 0);
472
+ if (busyTimeoutMs > 5000)
473
+ throw new HarnessConfigError("busyTimeoutMs cannot exceed 5000.");
474
+ const now = options.now ?? Date.now;
475
+ const { databasePath, databaseEntry } = await ensurePrivateDatabase(workspace, options.stateDirectory);
476
+ const database = new SqliteDatabase(databasePath, { create: false, strict: true });
477
+ let closed = false;
478
+ const assertOpen = () => {
479
+ if (closed) {
480
+ throw new HarnessStateConflictError("The CLI session store is closed.");
481
+ }
482
+ };
483
+ const transaction = (operation) => {
484
+ assertOpen();
485
+ database.exec("BEGIN IMMEDIATE");
486
+ try {
487
+ const result = operation();
488
+ const size = database.query(`
489
+ SELECT
490
+ COALESCE((SELECT SUM(metadata_bytes) FROM zhivex_cli_sessions WHERE workspace_key = ?1 AND scope_key = ?2), 0) +
491
+ COALESCE((SELECT SUM(r.metadata_bytes) FROM zhivex_cli_session_runs r JOIN zhivex_cli_sessions s ON s.session_id = r.session_id WHERE s.workspace_key = ?1 AND s.scope_key = ?2), 0)
492
+ AS size
493
+ `).get(workspaceKey, scopeKey)?.size ?? 0;
494
+ if (size > maxIndexBytes) {
495
+ throw new HarnessStateConflictError(`Session index metadata exceeds the ${maxIndexBytes}-byte limit.`);
496
+ }
497
+ database.exec("COMMIT");
498
+ return result;
499
+ } catch (error) {
500
+ try {
501
+ database.exec("ROLLBACK");
502
+ } catch {}
503
+ throw error;
504
+ }
505
+ };
506
+ const ensureRevision = (row, expected) => {
507
+ if (expected !== undefined && row.revision !== expected) {
508
+ throw new HarnessStateConflictError(`Session ${row.session_id} changed concurrently (expected revision ${expected}, found ${row.revision}).`, { retryable: true });
509
+ }
510
+ };
511
+ const selectSessionRow = (candidateSessionId, includeDeleted = false) => {
512
+ assertIdentifier("sessionId", candidateSessionId);
513
+ return database.query(`
514
+ SELECT session_id, title, parent_session_id, forked_from_turn_id, revision, activity_seq,
515
+ created_at, updated_at, archived_at, deleted_at
516
+ FROM zhivex_cli_sessions
517
+ WHERE session_id = ?1 AND workspace_key = ?2 AND scope_key = ?3
518
+ ${includeDeleted ? "" : "AND deleted_at IS NULL"}
519
+ `).get(candidateSessionId, workspaceKey, scopeKey);
520
+ };
521
+ const requireSessionRow = (candidateSessionId, includeDeleted = false) => {
522
+ const row = selectSessionRow(candidateSessionId, includeDeleted);
523
+ if (!row) {
524
+ throw new HarnessStateConflictError(`Session ${candidateSessionId} was not found in this workspace and scope.`);
525
+ }
526
+ return row;
527
+ };
528
+ const selectRuns = (candidateSessionId) => database.query(`
529
+ SELECT turn_id, source_turn_id, ordinal, run_id, provider, model, role, status,
530
+ created_at, updated_at, completed_at
531
+ FROM zhivex_cli_session_runs
532
+ WHERE session_id = ?1
533
+ ORDER BY ordinal ASC
534
+ `).all(candidateSessionId).map(toRunReference);
535
+ const nextActivitySequence = () => database.query(`
536
+ SELECT COALESCE(MAX(activity_seq), 0) + 1 AS count
537
+ FROM zhivex_cli_sessions
538
+ WHERE workspace_key = ?1 AND scope_key = ?2
539
+ `).get(workspaceKey, scopeKey)?.count ?? 1;
540
+ const sessionBase = (row) => ({
541
+ schemaVersion: HARNESS_SESSION_SCHEMA_VERSION,
542
+ sessionId: row.session_id,
543
+ workspaceKey,
544
+ scopeKey,
545
+ ...row.title ? { title: row.title } : {},
546
+ ...row.parent_session_id ? { parentSessionId: row.parent_session_id } : {},
547
+ ...row.forked_from_turn_id ? { forkedFromTurnId: row.forked_from_turn_id } : {},
548
+ revision: row.revision,
549
+ createdAt: row.created_at,
550
+ updatedAt: row.updated_at,
551
+ ...row.archived_at === null ? {} : { archivedAt: row.archived_at },
552
+ ...row.deleted_at === null ? {} : { deletedAt: row.deleted_at }
553
+ });
554
+ const hydrate = (row) => ({
555
+ ...sessionBase(row),
556
+ runs: selectRuns(row.session_id)
557
+ });
558
+ const latestRunRow = (candidateSessionId) => database.query(`
559
+ SELECT turn_id, source_turn_id, ordinal, run_id, provider, model, role, status,
560
+ created_at, updated_at, completed_at
561
+ FROM zhivex_cli_session_runs
562
+ WHERE session_id = ?1
563
+ ORDER BY ordinal DESC
564
+ LIMIT 1
565
+ `).get(candidateSessionId);
566
+ const assertNoActiveRun = (candidateSessionId, action) => {
567
+ const latest = latestRunRow(candidateSessionId);
568
+ if (latest && ACTIVE_STATUSES.has(latest.status)) {
569
+ throw new HarnessStateConflictError(`Cannot ${action} session ${candidateSessionId} while run ${latest.run_id} is ${latest.status}.`);
570
+ }
571
+ };
572
+ const insertRun = (candidateSessionId, input, sequence, sourceTurnId) => {
573
+ const runId = assertIdentifier("runId", input.runId);
574
+ const provider = assertIdentifier("provider", input.provider);
575
+ const model = assertIdentifier("model", input.model);
576
+ const role = input.role === undefined ? null : assertIdentifier("role", input.role);
577
+ const status = input.status ?? "created";
578
+ if (!SESSION_RUN_STATUSES.includes(status)) {
579
+ throw new HarnessConfigError(`Unsupported session run status: ${String(status)}.`);
580
+ }
581
+ const createdAt = input.createdAt ?? now();
582
+ assertTimestamp("createdAt", createdAt);
583
+ const entryBytes = metadataSize({ runId, provider, model, role });
584
+ if (entryBytes > maxMetadataBytes) {
585
+ throw new HarnessConfigError(`Run reference metadata exceeds the ${maxMetadataBytes}-byte limit.`);
586
+ }
587
+ const candidateTurnId = turnId();
588
+ database.query(`
589
+ INSERT INTO zhivex_cli_session_runs (
590
+ turn_id, source_turn_id, session_id, ordinal, run_id, provider, model, role,
591
+ status, created_at, updated_at, completed_at, metadata_bytes
592
+ ) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?10, ?11, ?12)
593
+ `).run(candidateTurnId, sourceTurnId ?? null, candidateSessionId, sequence, runId, provider, model, role, status, createdAt, isTerminal(status) ? createdAt : null, entryBytes);
594
+ };
595
+ try {
596
+ const openedEntry = await lstat3(databasePath);
597
+ if (openedEntry.isSymbolicLink() || !openedEntry.isFile() || openedEntry.dev !== databaseEntry.dev || openedEntry.ino !== databaseEntry.ino) {
598
+ throw new HarnessWorkspaceError(`The session SQLite path changed while it was being opened: ${databasePath}.`);
599
+ }
600
+ database.exec(`PRAGMA busy_timeout = ${busyTimeoutMs}`);
601
+ database.exec("PRAGMA journal_mode = WAL");
602
+ database.exec("PRAGMA foreign_keys = ON");
603
+ database.exec(`
604
+ CREATE TABLE IF NOT EXISTS zhivex_cli_session_schema (
605
+ singleton INTEGER PRIMARY KEY CHECK (singleton = 1),
606
+ version INTEGER NOT NULL
607
+ );
608
+ INSERT OR IGNORE INTO zhivex_cli_session_schema (singleton, version)
609
+ VALUES (1, ${HARNESS_SESSION_SCHEMA_VERSION});
610
+ CREATE TABLE IF NOT EXISTS zhivex_cli_sessions (
611
+ session_id TEXT PRIMARY KEY,
612
+ workspace_key TEXT NOT NULL,
613
+ scope_key TEXT NOT NULL,
614
+ title TEXT,
615
+ parent_session_id TEXT,
616
+ forked_from_turn_id TEXT,
617
+ revision INTEGER NOT NULL DEFAULT 0,
618
+ activity_seq INTEGER NOT NULL,
619
+ created_at INTEGER NOT NULL,
620
+ updated_at INTEGER NOT NULL,
621
+ archived_at INTEGER,
622
+ deleted_at INTEGER,
623
+ metadata_bytes INTEGER NOT NULL,
624
+ CHECK (length(session_id) <= 260),
625
+ CHECK (metadata_bytes >= 0)
626
+ );
627
+ CREATE INDEX IF NOT EXISTS zhivex_cli_sessions_latest
628
+ ON zhivex_cli_sessions (workspace_key, scope_key, deleted_at, archived_at, activity_seq DESC);
629
+ CREATE TABLE IF NOT EXISTS zhivex_cli_session_runs (
630
+ turn_id TEXT PRIMARY KEY,
631
+ source_turn_id TEXT,
632
+ session_id TEXT NOT NULL REFERENCES zhivex_cli_sessions(session_id) ON DELETE CASCADE,
633
+ ordinal INTEGER NOT NULL,
634
+ run_id TEXT NOT NULL,
635
+ provider TEXT NOT NULL,
636
+ model TEXT NOT NULL,
637
+ role TEXT,
638
+ status TEXT NOT NULL CHECK (status IN ('created', 'running', 'waiting_approval', 'cancel_requested', 'completed', 'failed', 'cancelled', 'timed_out')),
639
+ created_at INTEGER NOT NULL,
640
+ updated_at INTEGER NOT NULL,
641
+ completed_at INTEGER,
642
+ metadata_bytes INTEGER NOT NULL,
643
+ UNIQUE (session_id, ordinal),
644
+ UNIQUE (session_id, run_id),
645
+ CHECK (ordinal >= 0),
646
+ CHECK (metadata_bytes >= 0)
647
+ );
648
+ CREATE INDEX IF NOT EXISTS zhivex_cli_session_runs_latest
649
+ ON zhivex_cli_session_runs (session_id, ordinal DESC);
650
+ `);
651
+ const schemaVersion = database.query("SELECT version FROM zhivex_cli_session_schema WHERE singleton = 1").get()?.version;
652
+ if (schemaVersion !== HARNESS_SESSION_SCHEMA_VERSION) {
653
+ throw new HarnessStateConflictError(`Unsupported CLI session schema version: ${String(schemaVersion)}.`);
654
+ }
655
+ await chmod(databasePath, 384);
656
+ } catch (error) {
657
+ database.close(false);
658
+ throw error;
659
+ }
660
+ const prune = async (input = {}) => transaction(() => {
661
+ const before = input.before ?? Math.max(0, now() - retentionMs);
662
+ assertTimestamp("before", before);
663
+ const deletedSessions = database.query(`
664
+ SELECT COUNT(*) AS count FROM zhivex_cli_sessions
665
+ WHERE workspace_key = ?1 AND scope_key = ?2
666
+ AND updated_at < ?3
667
+ AND (archived_at IS NOT NULL OR deleted_at IS NOT NULL)
668
+ `).get(workspaceKey, scopeKey, before)?.count ?? 0;
669
+ database.query(`
670
+ DELETE FROM zhivex_cli_sessions
671
+ WHERE workspace_key = ?1 AND scope_key = ?2
672
+ AND updated_at < ?3
673
+ AND (archived_at IS NOT NULL OR deleted_at IS NOT NULL)
674
+ `).run(workspaceKey, scopeKey, before);
675
+ return { before, deletedSessions };
676
+ });
677
+ const store = {
678
+ databasePath,
679
+ workspaceKey,
680
+ scopeKey,
681
+ async create(input = {}) {
682
+ return transaction(() => {
683
+ const cutoff = Math.max(0, now() - retentionMs);
684
+ database.query(`
685
+ DELETE FROM zhivex_cli_sessions
686
+ WHERE workspace_key = ?1 AND scope_key = ?2 AND updated_at < ?3
687
+ AND (archived_at IS NOT NULL OR deleted_at IS NOT NULL)
688
+ `).run(workspaceKey, scopeKey, cutoff);
689
+ const count = database.query(`
690
+ SELECT COUNT(*) AS count FROM zhivex_cli_sessions
691
+ WHERE workspace_key = ?1 AND scope_key = ?2
692
+ `).get(workspaceKey, scopeKey)?.count ?? 0;
693
+ if (count >= maxSessions) {
694
+ throw new HarnessStateConflictError(`Session limit reached for this workspace and scope (${maxSessions}). Archive and prune an older session.`);
695
+ }
696
+ const createdAt = now();
697
+ assertTimestamp("now", createdAt);
698
+ const title = normalizeTitle(input.title, maxMetadataBytes) ?? null;
699
+ const candidateSessionId = sessionId();
700
+ const activitySequence = nextActivitySequence();
701
+ database.query(`
702
+ INSERT INTO zhivex_cli_sessions (
703
+ session_id, workspace_key, scope_key, title, revision, created_at,
704
+ updated_at, activity_seq, metadata_bytes
705
+ ) VALUES (?1, ?2, ?3, ?4, 0, ?5, ?5, ?6, ?7)
706
+ `).run(candidateSessionId, workspaceKey, scopeKey, title, createdAt, activitySequence, metadataSize({ title }));
707
+ if (input.initialRun) {
708
+ insertRun(candidateSessionId, input.initialRun, 0);
709
+ }
710
+ return hydrate(requireSessionRow(candidateSessionId));
711
+ });
712
+ },
713
+ async get(candidateSessionId, input = {}) {
714
+ assertOpen();
715
+ const row = selectSessionRow(candidateSessionId, input.includeDeleted ?? false);
716
+ return row ? hydrate(row) : undefined;
717
+ },
718
+ async list(query = {}) {
719
+ assertOpen();
720
+ const limit = boundedInteger("limit", query.limit, 50, 1);
721
+ if (limit > MAX_LIST_LIMIT) {
722
+ throw new HarnessConfigError(`limit cannot exceed ${MAX_LIST_LIMIT}.`);
723
+ }
724
+ const search = query.search?.trim() ?? "";
725
+ if (search.length > 256)
726
+ throw new HarnessConfigError("Session search exceeds 256 characters.");
727
+ const rows = database.query(`
728
+ SELECT session_id, title, parent_session_id, forked_from_turn_id, revision, activity_seq,
729
+ created_at, updated_at, archived_at, deleted_at
730
+ FROM zhivex_cli_sessions
731
+ WHERE workspace_key = ?1 AND scope_key = ?2
732
+ AND (?4 = '' OR instr(lower(COALESCE(title, '')), lower(?4)) > 0 OR instr(lower(session_id), lower(?4)) > 0)
733
+ ${query.includeArchived ? "" : "AND archived_at IS NULL"}
734
+ ${query.includeDeleted ? "" : "AND deleted_at IS NULL"}
735
+ ORDER BY activity_seq DESC
736
+ LIMIT ?3
737
+ `).all(workspaceKey, scopeKey, limit, search);
738
+ return rows.map((row) => {
739
+ const latest = latestRunRow(row.session_id);
740
+ const runCount = database.query("SELECT COUNT(*) AS count FROM zhivex_cli_session_runs WHERE session_id = ?1").get(row.session_id)?.count ?? 0;
741
+ return {
742
+ ...sessionBase(row),
743
+ runCount,
744
+ ...latest ? { latestRun: toRunReference(latest) } : {}
745
+ };
746
+ });
747
+ },
748
+ async latest(input = {}) {
749
+ assertOpen();
750
+ const row = database.query(`
751
+ SELECT session_id, title, parent_session_id, forked_from_turn_id, revision, activity_seq,
752
+ created_at, updated_at, archived_at, deleted_at
753
+ FROM zhivex_cli_sessions
754
+ WHERE workspace_key = ?1 AND scope_key = ?2 AND deleted_at IS NULL
755
+ ${input.includeArchived ? "" : "AND archived_at IS NULL"}
756
+ ORDER BY activity_seq DESC
757
+ LIMIT 1
758
+ `).get(workspaceKey, scopeKey);
759
+ return row ? hydrate(row) : undefined;
760
+ },
761
+ async update(candidateSessionId, input) {
762
+ return transaction(() => {
763
+ const row = requireSessionRow(candidateSessionId);
764
+ ensureRevision(row, input.expectedRevision);
765
+ if (input.archived === true) {
766
+ assertNoActiveRun(candidateSessionId, "archive");
767
+ }
768
+ const title = input.title === undefined ? row.title : normalizeTitle(input.title, maxMetadataBytes) ?? null;
769
+ const updatedAt = now();
770
+ assertTimestamp("now", updatedAt);
771
+ const archivedAt = input.archived === undefined ? row.archived_at : input.archived ? updatedAt : null;
772
+ const activitySequence = nextActivitySequence();
773
+ database.query(`
774
+ UPDATE zhivex_cli_sessions
775
+ SET title = ?1, archived_at = ?2, updated_at = ?3, revision = revision + 1,
776
+ metadata_bytes = ?4, activity_seq = ?5
777
+ WHERE session_id = ?6 AND workspace_key = ?7 AND scope_key = ?8 AND revision = ?9
778
+ `).run(title, archivedAt, updatedAt, metadataSize({ title }), activitySequence, candidateSessionId, workspaceKey, scopeKey, row.revision);
779
+ return hydrate(requireSessionRow(candidateSessionId));
780
+ });
781
+ },
782
+ async rename(candidateSessionId, title, input = {}) {
783
+ return store.update(candidateSessionId, {
784
+ title,
785
+ ...input.expectedRevision === undefined ? {} : { expectedRevision: input.expectedRevision }
786
+ });
787
+ },
788
+ async appendRun(candidateSessionId, input, appendOptions = {}) {
789
+ return transaction(() => {
790
+ const row = requireSessionRow(candidateSessionId);
791
+ ensureRevision(row, appendOptions.expectedRevision);
792
+ if (row.archived_at !== null) {
793
+ throw new HarnessStateConflictError(`Cannot append a run to archived session ${candidateSessionId}.`);
794
+ }
795
+ const existing = database.query(`
796
+ SELECT turn_id, source_turn_id, ordinal, run_id, provider, model, role, status,
797
+ created_at, updated_at, completed_at
798
+ FROM zhivex_cli_session_runs WHERE session_id = ?1 AND run_id = ?2
799
+ `).get(candidateSessionId, input.runId);
800
+ if (existing) {
801
+ const requestedStatus = input.status ?? "created";
802
+ if (existing.provider === input.provider && existing.model === input.model && (existing.role ?? undefined) === input.role && existing.status === requestedStatus) {
803
+ return hydrate(row);
804
+ }
805
+ throw new HarnessStateConflictError(`Run ${input.runId} is already immutably bound to ${existing.provider}/${existing.model} in this session.`);
806
+ }
807
+ assertNoActiveRun(candidateSessionId, "start another run in");
808
+ const count = database.query("SELECT COUNT(*) AS count FROM zhivex_cli_session_runs WHERE session_id = ?1").get(candidateSessionId)?.count ?? 0;
809
+ if (count >= maxRuns) {
810
+ throw new HarnessStateConflictError(`Run-reference limit reached for session ${candidateSessionId} (${maxRuns}).`);
811
+ }
812
+ insertRun(candidateSessionId, input, count);
813
+ const updatedAt = now();
814
+ assertTimestamp("now", updatedAt);
815
+ const activitySequence = nextActivitySequence();
816
+ database.query(`
817
+ UPDATE zhivex_cli_sessions SET updated_at = ?1, activity_seq = ?2, revision = revision + 1
818
+ WHERE session_id = ?3 AND revision = ?4
819
+ `).run(updatedAt, activitySequence, candidateSessionId, row.revision);
820
+ return hydrate(requireSessionRow(candidateSessionId));
821
+ });
822
+ },
823
+ async updateRun(candidateSessionId, runId, input) {
824
+ return transaction(() => {
825
+ const session = requireSessionRow(candidateSessionId);
826
+ ensureRevision(session, input.expectedSessionRevision);
827
+ assertIdentifier("runId", runId);
828
+ const run = database.query(`
829
+ SELECT turn_id, source_turn_id, ordinal, run_id, provider, model, role, status,
830
+ created_at, updated_at, completed_at
831
+ FROM zhivex_cli_session_runs WHERE session_id = ?1 AND run_id = ?2
832
+ `).get(candidateSessionId, runId);
833
+ if (!run) {
834
+ throw new HarnessStateConflictError(`Run ${runId} is not linked to session ${candidateSessionId}.`);
835
+ }
836
+ if (!STATUS_TRANSITIONS[run.status].has(input.status)) {
837
+ throw new HarnessStateConflictError(`Run ${runId} cannot transition from ${run.status} to ${input.status}.`);
838
+ }
839
+ const updatedAt = input.updatedAt ?? now();
840
+ assertTimestamp("updatedAt", updatedAt);
841
+ if (updatedAt < run.updated_at) {
842
+ throw new HarnessStateConflictError(`Run ${runId} update timestamp cannot move backwards.`);
843
+ }
844
+ const completedAt = isTerminal(input.status) ? input.completedAt ?? updatedAt : null;
845
+ if (completedAt !== null) {
846
+ assertTimestamp("completedAt", completedAt);
847
+ if (completedAt < run.created_at || completedAt > updatedAt) {
848
+ throw new HarnessConfigError("completedAt must be between the run creation and update timestamps.");
849
+ }
850
+ }
851
+ database.query(`
852
+ UPDATE zhivex_cli_session_runs
853
+ SET status = ?1, updated_at = ?2, completed_at = ?3
854
+ WHERE session_id = ?4 AND run_id = ?5
855
+ `).run(input.status, updatedAt, completedAt, candidateSessionId, runId);
856
+ const activitySequence = nextActivitySequence();
857
+ database.query(`
858
+ UPDATE zhivex_cli_sessions SET updated_at = ?1, activity_seq = ?2, revision = revision + 1
859
+ WHERE session_id = ?3 AND revision = ?4
860
+ `).run(updatedAt, activitySequence, candidateSessionId, session.revision);
861
+ return hydrate(requireSessionRow(candidateSessionId));
862
+ });
863
+ },
864
+ async fork(candidateSessionId, input = {}) {
865
+ return transaction(() => {
866
+ const source = requireSessionRow(candidateSessionId);
867
+ ensureRevision(source, input.expectedRevision);
868
+ const sourceRuns = selectRuns(candidateSessionId);
869
+ let forkRuns = sourceRuns;
870
+ let forkedFromTurnId;
871
+ if (input.atTurnId) {
872
+ const index = sourceRuns.findIndex((run) => run.turnId === input.atTurnId);
873
+ if (index < 0) {
874
+ throw new HarnessStateConflictError(`Turn ${input.atTurnId} is not part of session ${candidateSessionId}.`);
875
+ }
876
+ forkRuns = sourceRuns.slice(0, index + 1);
877
+ forkedFromTurnId = input.atTurnId;
878
+ } else if (sourceRuns.length > 0) {
879
+ forkedFromTurnId = sourceRuns.at(-1).turnId;
880
+ }
881
+ const branchPoint = forkRuns.at(-1);
882
+ if (branchPoint && !isTerminal(branchPoint.status)) {
883
+ throw new HarnessStateConflictError(`Cannot fork from non-terminal run ${branchPoint.runId} (${branchPoint.status}).`);
884
+ }
885
+ if (forkRuns.length > maxRuns) {
886
+ throw new HarnessStateConflictError(`Fork history exceeds the ${maxRuns}-run limit.`);
887
+ }
888
+ const count = database.query(`
889
+ SELECT COUNT(*) AS count FROM zhivex_cli_sessions
890
+ WHERE workspace_key = ?1 AND scope_key = ?2
891
+ `).get(workspaceKey, scopeKey)?.count ?? 0;
892
+ if (count >= maxSessions) {
893
+ throw new HarnessStateConflictError(`Session limit reached for this workspace and scope (${maxSessions}).`);
894
+ }
895
+ const createdAt = now();
896
+ assertTimestamp("now", createdAt);
897
+ const title = normalizeTitle(input.title, maxMetadataBytes) ?? source.title;
898
+ const forkSessionId = sessionId();
899
+ const activitySequence = nextActivitySequence();
900
+ database.query(`
901
+ INSERT INTO zhivex_cli_sessions (
902
+ session_id, workspace_key, scope_key, title, parent_session_id,
903
+ forked_from_turn_id, revision, created_at, updated_at, activity_seq, metadata_bytes
904
+ ) VALUES (?1, ?2, ?3, ?4, ?5, ?6, 0, ?7, ?7, ?8, ?9)
905
+ `).run(forkSessionId, workspaceKey, scopeKey, title, candidateSessionId, forkedFromTurnId ?? null, createdAt, activitySequence, metadataSize({ title }));
906
+ for (const [index, run] of forkRuns.entries()) {
907
+ insertRun(forkSessionId, {
908
+ runId: run.runId,
909
+ provider: run.provider,
910
+ model: run.model,
911
+ ...run.role ? { role: run.role } : {},
912
+ status: run.status,
913
+ createdAt: run.createdAt
914
+ }, index, run.turnId);
915
+ database.query(`
916
+ UPDATE zhivex_cli_session_runs
917
+ SET updated_at = ?1, completed_at = ?2
918
+ WHERE session_id = ?3 AND ordinal = ?4
919
+ `).run(run.updatedAt, run.completedAt ?? null, forkSessionId, index);
920
+ }
921
+ return hydrate(requireSessionRow(forkSessionId));
922
+ });
923
+ },
924
+ async archive(candidateSessionId, input = {}) {
925
+ return store.update(candidateSessionId, {
926
+ archived: true,
927
+ ...input.expectedRevision === undefined ? {} : { expectedRevision: input.expectedRevision }
928
+ });
929
+ },
930
+ async delete(candidateSessionId, input = {}) {
931
+ return transaction(() => {
932
+ const row = requireSessionRow(candidateSessionId);
933
+ ensureRevision(row, input.expectedRevision);
934
+ assertNoActiveRun(candidateSessionId, "delete");
935
+ const deletedAt = now();
936
+ assertTimestamp("now", deletedAt);
937
+ const activitySequence = nextActivitySequence();
938
+ database.query(`
939
+ UPDATE zhivex_cli_sessions
940
+ SET deleted_at = ?1, updated_at = ?1, activity_seq = ?2, revision = revision + 1
941
+ WHERE session_id = ?3 AND revision = ?4
942
+ `).run(deletedAt, activitySequence, candidateSessionId, row.revision);
943
+ return hydrate(requireSessionRow(candidateSessionId, true));
944
+ });
945
+ },
946
+ prune,
947
+ close() {
948
+ if (!closed) {
949
+ closed = true;
950
+ database.close(false);
951
+ }
952
+ }
953
+ };
954
+ return store;
955
+ };
956
+ var openSessionStore = openCliSessionStore;
957
+
958
+ // src/workspace/file-security.ts
959
+ import { constants as fsConstants } from "node:fs";
960
+ import { open as open3 } from "node:fs/promises";
961
+ import path5 from "node:path";
962
+ var sameOpenFile = (before, after) => before.dev === after.dev && before.ino === after.ino && before.size === after.size && before.mtimeMs === after.mtimeMs && before.ctimeMs === after.ctimeMs && before.mode === after.mode && before.nlink === after.nlink;
963
+
964
+ class UnsafeFileTypeError extends Error {
965
+ constructor(label) {
966
+ super(`${label} must be a regular file and must not be a symbolic link.`);
967
+ this.name = "UnsafeFileTypeError";
968
+ }
969
+ }
970
+
971
+ class FileSizeLimitError extends Error {
972
+ label;
973
+ maxBytes;
974
+ constructor(label, maxBytes) {
975
+ super(`${label} exceeds the ${maxBytes}-byte limit.`);
976
+ this.label = label;
977
+ this.maxBytes = maxBytes;
978
+ this.name = "FileSizeLimitError";
979
+ }
980
+ }
981
+
982
+ class FileChangedWhileReadingError extends Error {
983
+ constructor(label) {
984
+ super(`${label} changed while it was being read.`);
985
+ this.name = "FileChangedWhileReadingError";
986
+ }
987
+ }
988
+ var invalidFileError = (label) => new UnsafeFileTypeError(label);
989
+ var openRegularFileNoFollow = async (target, label) => {
990
+ try {
991
+ const flags = fsConstants.O_RDONLY | fsConstants.O_NOFOLLOW | fsConstants.O_NONBLOCK;
992
+ let absolute = path5.resolve(target);
993
+ if (process.platform === "darwin") {
994
+ absolute = absolute.replace(/^\/(tmp|var|etc)(\/|$)/, "/private/$1$2");
995
+ return await open3(absolute, fsConstants.O_RDONLY | fsConstants.O_NONBLOCK | 536870912, 384);
996
+ }
997
+ if (process.platform !== "linux")
998
+ throw new Error("Safe descriptor-relative reads require Linux or macOS.");
999
+ const segments = absolute.split("/").filter(Boolean);
1000
+ const leaf = segments.pop();
1001
+ if (!leaf)
1002
+ throw invalidFileError(label);
1003
+ let directory = await open3("/", fsConstants.O_RDONLY | fsConstants.O_DIRECTORY);
1004
+ try {
1005
+ for (const segment of segments) {
1006
+ const next = await open3(`/proc/self/fd/${directory.fd}/${segment}`, fsConstants.O_RDONLY | fsConstants.O_DIRECTORY | fsConstants.O_NOFOLLOW);
1007
+ await directory.close();
1008
+ directory = next;
1009
+ }
1010
+ return await open3(`/proc/self/fd/${directory.fd}/${leaf}`, flags);
1011
+ } finally {
1012
+ await directory.close();
1013
+ }
1014
+ } catch (error) {
1015
+ const code = error.code;
1016
+ if (code === "ELOOP" || code === "EMLINK" || code === "ENOTDIR")
1017
+ throw invalidFileError(label);
1018
+ throw error;
1019
+ }
1020
+ };
1021
+ var statRegularFileNoFollow = async (target, options) => {
1022
+ const handle = await openRegularFileNoFollow(target, options.label);
1023
+ try {
1024
+ const stat2 = await handle.stat();
1025
+ if (!stat2.isFile() || options.requireSingleLink && stat2.nlink !== 1) {
1026
+ throw invalidFileError(options.label);
1027
+ }
1028
+ return stat2;
1029
+ } finally {
1030
+ await handle.close();
1031
+ }
1032
+ };
1033
+ var readRegularFileNoFollow = async (target, options) => {
1034
+ const handle = await openRegularFileNoFollow(target, options.label);
1035
+ try {
1036
+ const before = await handle.stat();
1037
+ if (!before.isFile() || options.requireSingleLink && before.nlink !== 1) {
1038
+ throw invalidFileError(options.label);
1039
+ }
1040
+ if (!Number.isSafeInteger(before.size) || before.size < 0) {
1041
+ throw new Error(`${options.label} has an unsupported size.`);
1042
+ }
1043
+ if (!Number.isSafeInteger(options.maxBytes) || options.maxBytes < 0) {
1044
+ throw new Error(`${options.label} has an invalid byte limit.`);
1045
+ }
1046
+ if (before.size > options.maxBytes) {
1047
+ throw new FileSizeLimitError(options.label, options.maxBytes);
1048
+ }
1049
+ const contents = Buffer.alloc(before.size);
1050
+ let offset = 0;
1051
+ while (offset < contents.byteLength) {
1052
+ const { bytesRead } = await handle.read(contents, offset, contents.byteLength - offset, offset);
1053
+ if (bytesRead === 0)
1054
+ break;
1055
+ offset += bytesRead;
1056
+ }
1057
+ const probe = Buffer.allocUnsafe(1);
1058
+ const { bytesRead: extraBytes } = await handle.read(probe, 0, 1, before.size);
1059
+ const after = await handle.stat();
1060
+ if (offset !== before.size || extraBytes !== 0 || !sameOpenFile(before, after) || options.requireSingleLink && after.nlink !== 1) {
1061
+ throw new FileChangedWhileReadingError(options.label);
1062
+ }
1063
+ return { contents, stat: after };
1064
+ } finally {
1065
+ await handle.close();
1066
+ }
1067
+ };
1068
+ async function* readRegularFileChunksNoFollow(target, options) {
1069
+ const handle = await openRegularFileNoFollow(target, options.label);
1070
+ try {
1071
+ const before = await handle.stat();
1072
+ if (!before.isFile() || options.requireSingleLink && before.nlink !== 1)
1073
+ throw invalidFileError(options.label);
1074
+ if (options.requirePrivate && (before.mode & 63) !== 0)
1075
+ throw new Error(`${options.label} permissions must not grant group or other access.`);
1076
+ let offset = 0;
1077
+ while (offset < before.size) {
1078
+ const buffer = Buffer.allocUnsafe(Math.min(64 * 1024, before.size - offset));
1079
+ const { bytesRead: bytesRead2 } = await handle.read(buffer, 0, buffer.length, offset);
1080
+ if (!bytesRead2)
1081
+ throw new FileChangedWhileReadingError(options.label);
1082
+ offset += bytesRead2;
1083
+ yield buffer.subarray(0, bytesRead2);
1084
+ }
1085
+ const probe = Buffer.allocUnsafe(1);
1086
+ const { bytesRead } = await handle.read(probe, 0, 1, offset);
1087
+ if (bytesRead || !sameOpenFile(before, await handle.stat()))
1088
+ throw new FileChangedWhileReadingError(options.label);
1089
+ } finally {
1090
+ await handle.close();
1091
+ }
1092
+ }
1093
+
1094
+ // src/runtime/runtime-diagnostics.ts
1095
+ import { z } from "zod";
1096
+ import { createRedactionPolicy as createRedactionPolicy2 } from "@zhivex-ai/agents";
1097
+ var count = z.number().int().nonnegative().finite();
1098
+ var digest = z.string().regex(/^sha256:[a-f0-9]{64}$/).nullable();
1099
+ var diagnosticsSchema = z.object({
1100
+ schemaVersion: z.literal(2),
1101
+ requireVerifiedDelivery: z.boolean(),
1102
+ budget: z.object({
1103
+ inputTokens: count,
1104
+ outputTokens: count,
1105
+ cachedInputTokens: count,
1106
+ modelCalls: count,
1107
+ usageComplete: z.boolean(),
1108
+ inFlight: z.boolean(),
1109
+ stopReason: z.enum(["USAGE_UNAVAILABLE", "WORK_TOKEN_BUDGET", "INPUT_TOKEN_BUDGET", "OUTPUT_TOKEN_BUDGET"]).nullable(),
1110
+ reservedInputTokens: count,
1111
+ reservedOutputTokens: count,
1112
+ predictedInputTokens: count,
1113
+ outputCapApplied: z.boolean().default(false)
1114
+ }),
1115
+ phase: z.enum(["explore", "reproduce", "candidate", "verify", "recover", "delivered", "incomplete"]),
1116
+ candidate: digest,
1117
+ revision: count,
1118
+ checks: z.array(z.object({
1119
+ commandId: z.string().max(128),
1120
+ purpose: z.string().max(500),
1121
+ argvDigest: digest,
1122
+ candidate: digest,
1123
+ exitCode: z.number().int(),
1124
+ verified: z.boolean()
1125
+ })).max(8),
1126
+ contextMeasurements: z.array(z.object({
1127
+ systemCharacters: count,
1128
+ userCharacters: count,
1129
+ assistantCharacters: count,
1130
+ toolResultCharacters: count,
1131
+ otherMessageCharacters: count,
1132
+ toolDefinitionCharacters: count,
1133
+ unmeasuredToolDefinitions: count
1134
+ })).max(128),
1135
+ modelTimings: z.array(z.object({ durationMs: z.number().nonnegative().finite(), firstTokenMs: z.number().nonnegative().finite().nullable(), completed: z.boolean() })).max(128),
1136
+ omittedContextMeasurements: count
1137
+ });
1138
+ var manifestSchema = z.object({
1139
+ schemaVersion: z.literal(2),
1140
+ policyVersion: z.literal("assistant-v3-durable-closure"),
1141
+ role: z.string().max(128),
1142
+ requireVerifiedDelivery: z.boolean(),
1143
+ backend: z.enum(["none", "oci"]),
1144
+ tools: z.array(z.string().max(128)).max(1024),
1145
+ budget: z.object({ unlimitedTokens: z.boolean().optional(), maxSteps: count, maxToolCalls: count, maxToolErrors: count, maxInputTokens: count, maxOutputTokens: count, includeChildRuns: z.boolean() }),
1146
+ timeoutMs: count,
1147
+ closureController: z.boolean(),
1148
+ contextEnabled: z.boolean()
1149
+ });
1150
+ var redact = createRedactionPolicy2({ includeEmails: true });
1151
+ var inspectRuntimeDiagnostics = (value) => {
1152
+ const parsed = diagnosticsSchema.safeParse(value);
1153
+ if (!parsed.success)
1154
+ return null;
1155
+ return { ...parsed.data, checks: parsed.data.checks.map((check) => ({
1156
+ ...check,
1157
+ commandId: redact.redactText(check.commandId),
1158
+ purpose: redact.redactText(check.purpose)
1159
+ })) };
1160
+ };
1161
+ var inspectRuntimeManifest = (value) => {
1162
+ const parsed = manifestSchema.safeParse(value);
1163
+ if (!parsed.success)
1164
+ return null;
1165
+ return { ...parsed.data, role: redact.redactText(parsed.data.role), tools: parsed.data.tools.map((name) => redact.redactText(name)) };
1166
+ };
1167
+
1168
+ // src/runtime/tool-execution.ts
1169
+ var harnessToolExecution = {
1170
+ parallel: true,
1171
+ independentOnly: true,
1172
+ maxConcurrency: 4,
1173
+ stopOnError: false,
1174
+ validationErrorMode: "tool-result",
1175
+ unknownToolMode: "tool-result"
1176
+ };
1177
+
1178
+ // src/runtime/usage-ledger.ts
1179
+ import { AsyncLocalStorage } from "node:async_hooks";
1180
+ import { randomUUID as randomUUID2 } from "node:crypto";
1181
+ import { lstat as lstat4 } from "node:fs/promises";
1182
+ import { z as z4 } from "zod";
1183
+ import { wrapLanguageModel } from "@zhivex-ai/core";
1184
+
1185
+ // src/runtime/model-budget.ts
1186
+ import { ProviderToolCallError } from "@zhivex-ai/core/provider";
1187
+ import { z as z3 } from "zod";
1188
+
1189
+ // src/context/context-metrics.ts
1190
+ import { z as z2 } from "zod";
1191
+ var schemaJson = new WeakMap;
1192
+ var serializeSchema = (schema) => {
1193
+ let serialized = schemaJson.get(schema);
1194
+ if (serialized === undefined) {
1195
+ serialized = JSON.stringify(z2.toJSONSchema(schema));
1196
+ schemaJson.set(schema, serialized);
1197
+ }
1198
+ return serialized;
1199
+ };
1200
+ var estimateContextTokens = (counts) => {
1201
+ if (counts.unmeasuredToolDefinitions)
1202
+ throw new Error("CONTEXT_ESTIMATE_UNAVAILABLE");
1203
+ return Math.ceil((counts.systemCharacters + counts.userCharacters + counts.assistantCharacters + counts.toolResultCharacters + counts.otherMessageCharacters + counts.toolDefinitionCharacters) / 3) + 64;
1204
+ };
1205
+ var measureContext = (input) => {
1206
+ const counts = {
1207
+ systemCharacters: 0,
1208
+ userCharacters: 0,
1209
+ assistantCharacters: 0,
1210
+ toolResultCharacters: 0,
1211
+ otherMessageCharacters: 0,
1212
+ toolDefinitionCharacters: 0,
1213
+ unmeasuredToolDefinitions: 0
1214
+ };
1215
+ for (const message of input.messages) {
1216
+ const size = JSON.stringify(message).length;
1217
+ if (message.role === "system")
1218
+ counts.systemCharacters += size;
1219
+ else if (message.role === "user")
1220
+ counts.userCharacters += size;
1221
+ else if (message.role === "assistant")
1222
+ counts.assistantCharacters += size;
1223
+ else if (message.role === "tool")
1224
+ counts.toolResultCharacters += size;
1225
+ else
1226
+ counts.otherMessageCharacters += size;
1227
+ }
1228
+ for (const [name, definition] of Object.entries(input.tools ?? {})) {
1229
+ try {
1230
+ if (!("schema" in definition)) {
1231
+ counts.unmeasuredToolDefinitions++;
1232
+ continue;
1233
+ }
1234
+ const header = JSON.stringify({ name, description: definition.description });
1235
+ counts.toolDefinitionCharacters += header.length - 1 + ',"schema":'.length + serializeSchema(definition.schema).length + 1;
1236
+ } catch {
1237
+ counts.unmeasuredToolDefinitions++;
1238
+ }
1239
+ }
1240
+ return counts;
1241
+ };
1242
+
1243
+ // src/runtime/model-budget.ts
1244
+ var MODEL_BUDGET_KEY = "zhivexTransportBudget";
1245
+ var savedBudget = z3.object({
1246
+ inputTokens: z3.number().int().nonnegative(),
1247
+ outputTokens: z3.number().int().nonnegative(),
1248
+ cachedInputTokens: z3.number().int().nonnegative(),
1249
+ modelCalls: z3.number().int().nonnegative(),
1250
+ usageComplete: z3.boolean(),
1251
+ inFlight: z3.boolean().default(false)
1252
+ });
1253
+ var estimateRequestTokens = (input) => estimateContextTokens(measureContext(input));
1254
+ var workBudgetReached = (input, stats, limits) => stats.inputTokens + estimateRequestTokens(input) > limits.inputTokens - stats.reservedInputTokens || stats.outputTokens >= limits.outputTokens - stats.reservedOutputTokens;
1255
+ var createModelBudget = (limits, options = {}) => {
1256
+ const restored = options.saved === undefined ? undefined : savedBudget.parse(options.saved);
1257
+ const reserve = options.reserveFraction ?? 0.3;
1258
+ if (!Number.isFinite(reserve) || reserve < 0 || reserve >= 1)
1259
+ throw new Error("Invalid closure reserve.");
1260
+ const prior = inspectRuntimeDiagnostics(options.diagnostics);
1261
+ const contextMetrics = prior?.contextMeasurements ?? [];
1262
+ let omittedContextMeasurements = prior?.omittedContextMeasurements ?? 0;
1263
+ const stats = {
1264
+ inputTokens: 0,
1265
+ outputTokens: 0,
1266
+ cachedInputTokens: 0,
1267
+ modelCalls: 0,
1268
+ usageComplete: true,
1269
+ inFlight: false,
1270
+ ...restored,
1271
+ stopReason: null,
1272
+ reservedInputTokens: Number.isFinite(limits.inputTokens) ? Math.ceil(limits.inputTokens * reserve) : 0,
1273
+ reservedOutputTokens: Number.isFinite(limits.outputTokens) ? Math.ceil(limits.outputTokens * reserve) : 0,
1274
+ predictedInputTokens: 0,
1275
+ outputCapApplied: false
1276
+ };
1277
+ if (restored?.inFlight)
1278
+ stats.usageComplete = false;
1279
+ const modelTimings = prior?.modelTimings ?? [];
1280
+ const snapshot = () => ({
1281
+ inputTokens: stats.inputTokens,
1282
+ outputTokens: stats.outputTokens,
1283
+ cachedInputTokens: stats.cachedInputTokens,
1284
+ modelCalls: stats.modelCalls,
1285
+ usageComplete: stats.usageComplete,
1286
+ inFlight: stats.inFlight
1287
+ });
1288
+ const before = (input, provider) => {
1289
+ input.abortSignal?.throwIfAborted();
1290
+ const measured = measureContext(input);
1291
+ stats.predictedInputTokens = estimateContextTokens(measured);
1292
+ const closure = options.closure?.() ?? false;
1293
+ const inputCeiling = limits.inputTokens - (closure ? 0 : stats.reservedInputTokens);
1294
+ const outputCeiling = limits.outputTokens - (closure ? 0 : stats.reservedOutputTokens);
1295
+ stats.stopReason = !stats.usageComplete ? "USAGE_UNAVAILABLE" : stats.inputTokens + stats.predictedInputTokens > inputCeiling ? closure ? "INPUT_TOKEN_BUDGET" : "WORK_TOKEN_BUDGET" : stats.outputTokens >= outputCeiling ? closure ? "OUTPUT_TOKEN_BUDGET" : "WORK_TOKEN_BUDGET" : null;
1296
+ if (stats.stopReason)
1297
+ throw new Error(stats.stopReason);
1298
+ const capSupported = provider !== "qwen" || input.providerOptions?.apiMode === "chat" || input.providerOptions?.apiMode !== "responses" && (input.maxTokens !== undefined || input.reasoning?.budgetTokens !== undefined);
1299
+ const remainingOutput = outputCeiling - stats.outputTokens;
1300
+ const outputCap = Math.min(input.maxTokens ?? remainingOutput, remainingOutput);
1301
+ stats.outputCapApplied = capSupported && Number.isFinite(outputCap);
1302
+ if (stats.outputCapApplied)
1303
+ input.maxTokens = Math.max(1, outputCap);
1304
+ stats.modelCalls++;
1305
+ stats.inFlight = true;
1306
+ if (contextMetrics.length < 128)
1307
+ contextMetrics.push(measured);
1308
+ else
1309
+ omittedContextMeasurements++;
1310
+ };
1311
+ const record = (usage) => {
1312
+ stats.inFlight = false;
1313
+ if (!usage || usage.inputTokens === undefined || usage.outputTokens === undefined || ![usage.inputTokens, usage.outputTokens, usage.cachedInputTokens ?? 0].every((v) => Number.isSafeInteger(v) && v >= 0)) {
1314
+ stats.usageComplete = false;
1315
+ return;
1316
+ }
1317
+ stats.inputTokens += usage.inputTokens;
1318
+ stats.outputTokens += usage.outputTokens;
1319
+ stats.cachedInputTokens += usage.cachedInputTokens ?? 0;
1320
+ };
1321
+ const recordFailure = (error, provider) => {
1322
+ const usage = error instanceof ProviderToolCallError && error.provider === provider ? Reflect.get(error, "usage") : undefined;
1323
+ record(usage && typeof usage === "object" ? usage : undefined);
1324
+ };
1325
+ const middleware = {
1326
+ name: "harness-transport-budget-v2",
1327
+ async wrapGenerate(context, next) {
1328
+ before(context.input, context.model.provider);
1329
+ const started = performance.now();
1330
+ let completed = false;
1331
+ try {
1332
+ const result = await next();
1333
+ record(result.usage);
1334
+ completed = true;
1335
+ return result;
1336
+ } catch (error) {
1337
+ recordFailure(error, context.model.provider);
1338
+ throw error;
1339
+ } finally {
1340
+ if (modelTimings.length < 128)
1341
+ modelTimings.push({ durationMs: performance.now() - started, firstTokenMs: null, completed });
1342
+ }
1343
+ },
1344
+ async wrapStream(context, next) {
1345
+ before(context.input, context.model.provider);
1346
+ const started = performance.now();
1347
+ let firstTokenMs = null;
1348
+ try {
1349
+ const stream = await next();
1350
+ return async function* () {
1351
+ let finished = false, failureAccounted = false;
1352
+ try {
1353
+ for await (const event of stream) {
1354
+ if (firstTokenMs === null && (event.type === "text-delta" || event.type === "tool-call"))
1355
+ firstTokenMs = performance.now() - started;
1356
+ if (event.type === "finish" && !finished) {
1357
+ record(event.usage);
1358
+ finished = true;
1359
+ }
1360
+ yield event;
1361
+ }
1362
+ } catch (error) {
1363
+ if (!finished) {
1364
+ recordFailure(error, context.model.provider);
1365
+ failureAccounted = true;
1366
+ }
1367
+ throw error;
1368
+ } finally {
1369
+ if (!finished && !failureAccounted)
1370
+ stats.usageComplete = false;
1371
+ stats.inFlight = false;
1372
+ if (modelTimings.length < 128)
1373
+ modelTimings.push({ durationMs: performance.now() - started, firstTokenMs, completed: finished });
1374
+ }
1375
+ }();
1376
+ } catch (error) {
1377
+ recordFailure(error, context.model.provider);
1378
+ if (modelTimings.length < 128)
1379
+ modelTimings.push({ durationMs: performance.now() - started, firstTokenMs, completed: false });
1380
+ throw error;
1381
+ }
1382
+ }
1383
+ };
1384
+ return { stats, middleware, snapshot, contextMetrics, modelTimings, get omittedContextMeasurements() {
1385
+ return omittedContextMeasurements;
1386
+ } };
1387
+ };
1388
+
1389
+ // src/runtime/usage-ledger.ts
1390
+ var USAGE_LEDGER_KEY = "zhivexUsageLedger";
1391
+ var rate = z4.number().finite().nonnegative();
1392
+ var usagePricingSchema = z4.strictObject({
1393
+ schemaVersion: z4.literal(1),
1394
+ prices: z4.array(z4.strictObject({
1395
+ provider: z4.string().min(1).max(128),
1396
+ model: z4.string().min(1).max(512),
1397
+ inputUsdPerMillion: rate,
1398
+ outputUsdPerMillion: rate,
1399
+ source: z4.string().min(1).max(1024),
1400
+ asOf: z4.iso.datetime(),
1401
+ expiresAt: z4.iso.datetime()
1402
+ })).max(100)
1403
+ }).superRefine((value, ctx) => {
1404
+ const keys = new Set;
1405
+ for (const price of value.prices) {
1406
+ const key = JSON.stringify([price.provider, price.model]);
1407
+ if (keys.has(key) || Date.parse(price.expiresAt) <= Date.parse(price.asOf)) {
1408
+ ctx.addIssue({ code: "custom", message: "Duplicate model price or invalid validity interval." });
1409
+ }
1410
+ keys.add(key);
1411
+ }
1412
+ });
1413
+ var summaryView = z4.object({
1414
+ calls: z4.number().int().nonnegative(),
1415
+ inputTokens: rate,
1416
+ outputTokens: rate,
1417
+ usageComplete: z4.boolean(),
1418
+ estimatedUsd: rate.nullable(),
1419
+ limitUsd: rate.nullable()
1420
+ });
1421
+ var ledgerView = summaryView.extend({
1422
+ schemaVersion: z4.literal(1),
1423
+ runId: z4.string().max(256),
1424
+ historicalUsageUnknown: z4.boolean().optional(),
1425
+ costKind: z4.literal("estimate-not-invoice"),
1426
+ routes: z4.array(z4.object({
1427
+ provider: z4.string().max(128),
1428
+ model: z4.string().max(512),
1429
+ calls: z4.number().int().nonnegative(),
1430
+ inputTokens: rate,
1431
+ outputTokens: rate,
1432
+ unknownCalls: z4.number().int().nonnegative(),
1433
+ estimatedUsd: rate.nullable(),
1434
+ priceStatuses: z4.array(z4.enum(["missing", "stale", "estimate"]))
1435
+ })).max(100)
1436
+ });
1437
+ var inspectUsageLedger = (value) => {
1438
+ const parsed = ledgerView.safeParse(value);
1439
+ return parsed.success ? parsed.data : null;
1440
+ };
1441
+ var formatUsageLedger = (value) => {
1442
+ const parsed = summaryView.safeParse(value);
1443
+ if (!parsed.success)
1444
+ return "Usage: unavailable for this run.";
1445
+ const v = parsed.data;
1446
+ return `Usage: ${v.calls} calls · ${v.inputTokens} input / ${v.outputTokens} output tokens${v.usageComplete ? "" : " · INCOMPLETE"}` + ` · estimated USD ${v.estimatedUsd === null ? "unknown" : v.estimatedUsd.toFixed(6)}${v.limitUsd === null ? "" : ` / ${v.limitUsd} limit`} (not an invoice)`;
1447
+ };
1448
+ var current = new AsyncLocalStorage;
1449
+
1450
+ class UsageLedger {
1451
+ database;
1452
+ key;
1453
+ options;
1454
+ now;
1455
+ constructor(database, key, options, now) {
1456
+ this.database = database;
1457
+ this.key = key;
1458
+ this.options = options;
1459
+ this.now = now;
1460
+ }
1461
+ static async open(config, options = {}, now = Date.now) {
1462
+ if (options.limitUsd !== undefined && (!Number.isFinite(options.limitUsd) || options.limitUsd <= 0))
1463
+ throw new Error("Usage limit must be positive USD.");
1464
+ if (options.pricing)
1465
+ usagePricingSchema.parse(options.pricing);
1466
+ const sessions = await openCliSessionStore({ workspace: config.workspace, stateDirectory: config.stateDirectory, scope: config.scope });
1467
+ const key = `${sessions.workspaceKey}:${sessions.scopeKey}`;
1468
+ const databasePath = sessions.databasePath;
1469
+ sessions.close();
1470
+ const before = await lstat4(databasePath);
1471
+ if (before.isSymbolicLink() || !before.isFile())
1472
+ throw new Error("Usage database must be a regular private file.");
1473
+ const database = new SqliteDatabase(databasePath, { create: false, strict: true });
1474
+ const after = await lstat4(databasePath);
1475
+ if (after.isSymbolicLink() || !after.isFile() || before.dev !== after.dev || before.ino !== after.ino) {
1476
+ database.close();
1477
+ throw new Error("Usage database changed while opening.");
1478
+ }
1479
+ database.exec("PRAGMA busy_timeout = 5000");
1480
+ database.exec(`CREATE TABLE IF NOT EXISTS zhivex_usage_policies (
1481
+ scope_key TEXT NOT NULL, run_id TEXT NOT NULL, policy TEXT NOT NULL,
1482
+ PRIMARY KEY (scope_key, run_id));
1483
+ CREATE TABLE IF NOT EXISTS zhivex_usage_calls (
1484
+ id TEXT PRIMARY KEY, scope_key TEXT NOT NULL, run_id TEXT NOT NULL,
1485
+ provider TEXT NOT NULL, model TEXT NOT NULL, status TEXT NOT NULL,
1486
+ input_tokens INTEGER, output_tokens INTEGER, estimate_usd REAL, reserved_usd REAL NOT NULL,
1487
+ price_status TEXT NOT NULL);`);
1488
+ return new UsageLedger(database, key, options, now);
1489
+ }
1490
+ close() {
1491
+ this.database.close();
1492
+ }
1493
+ assertResume(runId) {
1494
+ const row = this.database.query("SELECT policy FROM zhivex_usage_policies WHERE scope_key = ?1 AND run_id = ?2").get(this.key, runId);
1495
+ if (!row)
1496
+ throw new Error("USAGE_LEDGER_MISSING: restore the complete state backup; a run snapshot cannot reset its monetary policy.");
1497
+ }
1498
+ policy(runId) {
1499
+ const row = this.database.query("SELECT policy FROM zhivex_usage_policies WHERE scope_key = ?1 AND run_id = ?2").get(this.key, runId);
1500
+ return row ? JSON.parse(row.policy) : this.options;
1501
+ }
1502
+ async run(runId, operation, historicalUsageUnknown = false) {
1503
+ this.database.query("INSERT OR IGNORE INTO zhivex_usage_policies (scope_key, run_id, policy) VALUES (?1, ?2, ?3)").run(this.key, runId, JSON.stringify({ ...this.options, ...historicalUsageUnknown ? { historicalUsageUnknown: true } : {} }));
1504
+ this.database.query("UPDATE zhivex_usage_calls SET status = 'unknown' WHERE scope_key = ?1 AND run_id = ?2 AND status = 'pending'").run(this.key, runId);
1505
+ return current.run({ ledger: this, runId }, operation);
1506
+ }
1507
+ summary(runId) {
1508
+ const rows = this.database.query("SELECT * FROM zhivex_usage_calls WHERE scope_key = ?1 AND run_id = ?2 ORDER BY rowid").all(this.key, runId);
1509
+ const routes = new Map;
1510
+ for (const row of rows) {
1511
+ const key = JSON.stringify([row.provider, row.model]);
1512
+ const entry = routes.get(key) ?? { provider: row.provider, model: row.model, calls: 0, inputTokens: 0, outputTokens: 0, unknownCalls: 0, estimatedUsd: 0, priceStatuses: [] };
1513
+ entry.calls++;
1514
+ entry.inputTokens += row.input_tokens ?? 0;
1515
+ entry.outputTokens += row.output_tokens ?? 0;
1516
+ if (row.status !== "confirmed")
1517
+ entry.unknownCalls++;
1518
+ entry.estimatedUsd = entry.estimatedUsd === null || row.estimate_usd === null ? null : entry.estimatedUsd + row.estimate_usd;
1519
+ if (!entry.priceStatuses.includes(row.price_status))
1520
+ entry.priceStatuses.push(row.price_status);
1521
+ routes.set(key, entry);
1522
+ }
1523
+ const values = [...routes.values()];
1524
+ return {
1525
+ schemaVersion: 1,
1526
+ runId,
1527
+ calls: rows.length,
1528
+ routes: values,
1529
+ inputTokens: values.reduce((n, r) => n + r.inputTokens, 0),
1530
+ outputTokens: values.reduce((n, r) => n + r.outputTokens, 0),
1531
+ historicalUsageUnknown: this.policy(runId).historicalUsageUnknown ?? false,
1532
+ usageComplete: !this.policy(runId).historicalUsageUnknown && rows.every((row) => row.status === "confirmed"),
1533
+ estimatedUsd: this.policy(runId).historicalUsageUnknown || values.some((r) => r.estimatedUsd === null) ? null : values.reduce((n, r) => n + (r.estimatedUsd ?? 0), 0),
1534
+ limitUsd: this.policy(runId).limitUsd ?? null,
1535
+ costKind: "estimate-not-invoice"
1536
+ };
1537
+ }
1538
+ begin(runId, provider, model, input) {
1539
+ const policy = this.policy(runId);
1540
+ const price = policy.pricing?.prices.find((p) => p.provider === provider && p.model === model);
1541
+ const priceStatus = !price ? "missing" : Date.parse(price.asOf) > this.now() || Date.parse(price.expiresAt) <= this.now() ? "stale" : "estimate";
1542
+ const usable = priceStatus === "estimate" ? price : undefined;
1543
+ let reserved = 0;
1544
+ if (policy.limitUsd !== undefined) {
1545
+ if (policy.historicalUsageUnknown)
1546
+ throw new Error("USAGE_UNCERTAIN: historical usage predates the ledger.");
1547
+ if (!usable)
1548
+ throw new Error(`USAGE_PRICE_${priceStatus.toUpperCase()}: monetary limit cannot be calculated for ${provider}/${model}.`);
1549
+ if (provider === "qwen" && input.providerOptions?.apiMode !== "chat" && input.maxTokens === undefined) {
1550
+ throw new Error("USAGE_OUTPUT_CAP_UNAVAILABLE: select a route with a supported output cap.");
1551
+ }
1552
+ input.maxTokens = Math.min(input.maxTokens ?? 2048, 2048);
1553
+ reserved = (estimateRequestTokens(input) * usable.inputUsdPerMillion + input.maxTokens * usable.outputUsdPerMillion) / 1e6;
1554
+ }
1555
+ const id = randomUUID2();
1556
+ this.database.exec("BEGIN IMMEDIATE");
1557
+ try {
1558
+ if (policy.requireCompleteUsage) {
1559
+ const unresolved = this.database.query("SELECT COUNT(*) AS n FROM zhivex_usage_calls WHERE scope_key = ?1 AND run_id = ?2 AND status = 'unknown'").get(this.key, runId);
1560
+ if (policy.historicalUsageUnknown || (unresolved?.n ?? 0) > 0)
1561
+ throw new Error("USAGE_UNCERTAIN: reconcile unknown utility usage before further execution.");
1562
+ }
1563
+ if (policy.limitUsd !== undefined) {
1564
+ const rows = this.database.query("SELECT * FROM zhivex_usage_calls WHERE scope_key = ?1 AND run_id = ?2").all(this.key, runId);
1565
+ if (rows.some((r) => r.status === "unknown" || r.status === "confirmed" && r.estimate_usd === null))
1566
+ throw new Error("USAGE_UNCERTAIN: inspect unresolved calls before further monetary-budget execution.");
1567
+ const spent = rows.reduce((n, r) => n + (r.status === "pending" ? r.reserved_usd : r.estimate_usd ?? 0), 0);
1568
+ if (spent + reserved > policy.limitUsd)
1569
+ throw new Error("USAGE_COST_BUDGET: insufficient estimated budget for the next request.");
1570
+ }
1571
+ this.database.query(`INSERT INTO zhivex_usage_calls (id, scope_key, run_id, provider, model, status, reserved_usd, price_status)
1572
+ VALUES (?1, ?2, ?3, ?4, ?5, 'pending', ?6, ?7)`).run(id, this.key, runId, provider, model, reserved, priceStatus);
1573
+ this.database.exec("COMMIT");
1574
+ } catch (error) {
1575
+ this.database.exec("ROLLBACK");
1576
+ throw error;
1577
+ }
1578
+ return { id, price: usable };
1579
+ }
1580
+ finish(call, usage) {
1581
+ const confirmed = usage && [usage.inputTokens, usage.outputTokens].every((v) => Number.isSafeInteger(v) && v >= 0);
1582
+ const estimate = confirmed && call.price ? (usage.inputTokens * call.price.inputUsdPerMillion + usage.outputTokens * call.price.outputUsdPerMillion) / 1e6 : null;
1583
+ this.database.query(`UPDATE zhivex_usage_calls SET status = ?1, input_tokens = ?2, output_tokens = ?3, estimate_usd = ?4
1584
+ WHERE id = ?5 AND status = 'pending'`).run(confirmed ? "confirmed" : "unknown", Number.isSafeInteger(usage?.inputTokens) && usage.inputTokens >= 0 ? usage.inputTokens : null, Number.isSafeInteger(usage?.outputTokens) && usage.outputTokens >= 0 ? usage.outputTokens : null, estimate, call.id);
1585
+ }
1586
+ model(model) {
1587
+ const ledger = this;
1588
+ const middleware = {
1589
+ name: "harness-durable-usage-v1",
1590
+ async wrapGenerate(context, next) {
1591
+ const scope = current.getStore();
1592
+ if (!scope || scope.ledger !== ledger)
1593
+ throw new Error("Usage accounting requires a logical run scope.");
1594
+ const call = ledger.begin(scope.runId, model.provider, model.modelId, context.input);
1595
+ try {
1596
+ const result = await next();
1597
+ ledger.finish(call, result.usage);
1598
+ return result;
1599
+ } catch (error) {
1600
+ ledger.finish(call, undefined);
1601
+ throw error;
1602
+ }
1603
+ },
1604
+ async wrapStream(context, next) {
1605
+ const scope = current.getStore();
1606
+ if (!scope || scope.ledger !== ledger)
1607
+ throw new Error("Usage accounting requires a logical run scope.");
1608
+ const call = ledger.begin(scope.runId, model.provider, model.modelId, context.input);
1609
+ try {
1610
+ const stream = await next();
1611
+ return async function* () {
1612
+ try {
1613
+ for await (const event of stream) {
1614
+ if (event.type === "finish")
1615
+ ledger.finish(call, event.usage);
1616
+ yield event;
1617
+ }
1618
+ } finally {
1619
+ ledger.finish(call, undefined);
1620
+ }
1621
+ }();
1622
+ } catch (error) {
1623
+ ledger.finish(call, undefined);
1624
+ throw error;
1625
+ }
1626
+ }
1627
+ };
1628
+ return wrapLanguageModel(model, [middleware]);
1629
+ }
1630
+ store(store) {
1631
+ const ledger = this;
1632
+ return new Proxy(store, { get(target, key) {
1633
+ if (key === "save")
1634
+ return async (...args) => {
1635
+ const scope = current.getStore();
1636
+ if (scope?.ledger === ledger && args[0].runId === scope.runId) {
1637
+ args[0].metadata = { ...args[0].metadata, [USAGE_LEDGER_KEY]: ledger.summary(scope.runId) };
1638
+ }
1639
+ return target.save(...args);
1640
+ };
1641
+ const value = Reflect.get(target, key, target);
1642
+ return typeof value === "function" ? value.bind(target) : value;
1643
+ } });
1644
+ }
1645
+ }
1646
+
1647
+ // src/execution/package-manager.ts
1648
+ import { lstat as lstat5 } from "node:fs/promises";
1649
+ import path6 from "node:path";
1650
+ var HARNESS_PACKAGE_MANAGERS = ["npm", "pnpm", "yarn", "bun"];
1651
+ var LOCKFILES = {
1652
+ npm: ["package-lock.json", "npm-shrinkwrap.json"],
1653
+ pnpm: ["pnpm-lock.yaml"],
1654
+ yarn: ["yarn.lock"],
1655
+ bun: ["bun.lock", "bun.lockb"]
1656
+ };
1657
+ var isPackageManager = (value) => HARNESS_PACKAGE_MANAGERS.includes(value);
1658
+ var parsePackageManager = (value) => {
1659
+ if (value === undefined)
1660
+ return;
1661
+ if (typeof value !== "string") {
1662
+ throw new Error("package.json packageManager must be a string when present.");
1663
+ }
1664
+ const match = /^(npm|pnpm|yarn|bun)@([^\s]+)$/.exec(value.trim());
1665
+ if (!match?.[1] || !match[2]) {
1666
+ throw new Error("package.json packageManager must pin npm, pnpm, yarn, or bun using <manager>@<version>.");
1667
+ }
1668
+ if (!isPackageManager(match[1])) {
1669
+ throw new Error(`Unsupported package manager: ${match[1]}.`);
1670
+ }
1671
+ return match[1];
1672
+ };
1673
+ var existingLockfiles = async (root) => {
1674
+ const matches = [];
1675
+ for (const manager of HARNESS_PACKAGE_MANAGERS) {
1676
+ for (const file of LOCKFILES[manager]) {
1677
+ try {
1678
+ const entry = await lstat5(path6.join(root, file));
1679
+ if (entry.isSymbolicLink()) {
1680
+ throw new Error(`Package-manager lockfile must not be a symbolic link: ${file}.`);
1681
+ }
1682
+ if (entry.isFile())
1683
+ matches.push({ manager, file });
1684
+ } catch (error) {
1685
+ if (error.code !== "ENOENT")
1686
+ throw error;
1687
+ }
1688
+ }
1689
+ }
1690
+ return matches;
1691
+ };
1692
+ var resolvePackageManager = async (root, manifest) => {
1693
+ const declared = parsePackageManager(manifest.packageManager);
1694
+ if (declared) {
1695
+ return {
1696
+ manager: declared,
1697
+ source: "packageManager",
1698
+ evidence: manifest.packageManager
1699
+ };
1700
+ }
1701
+ const lockfiles = await existingLockfiles(root);
1702
+ const managers = [...new Set(lockfiles.map((entry) => entry.manager))];
1703
+ if (managers.length > 1) {
1704
+ throw new Error(`Ambiguous package manager: found lockfiles for ${managers.join(", ")}; declare packageManager explicitly.`);
1705
+ }
1706
+ const detected = managers[0];
1707
+ if (detected) {
1708
+ return {
1709
+ manager: detected,
1710
+ source: "lockfile",
1711
+ evidence: lockfiles.filter((entry) => entry.manager === detected).map((entry) => entry.file).join(",")
1712
+ };
1713
+ }
1714
+ return { manager: "npm", source: "default" };
1715
+ };
1716
+ var runCommand = (manager, check) => {
1717
+ switch (manager) {
1718
+ case "bun":
1719
+ return ["bun", "--no-env-file", "run", check];
1720
+ case "npm":
1721
+ return ["npm", "--ignore-scripts", "run", check];
1722
+ case "pnpm":
1723
+ return ["pnpm", "--ignore-scripts", "run", check];
1724
+ case "yarn":
1725
+ return ["yarn", "run", check];
1726
+ }
1727
+ };
1728
+ var resolvePackageCheckCommand = async (root, manifest, check, expectedScript, allowedChecks) => {
1729
+ if (!/^[A-Za-z0-9:_-]+$/.test(check) || !allowedChecks.includes(check)) {
1730
+ throw new Error(`The check "${check}" is not in the explicit allowlist.`);
1731
+ }
1732
+ const scripts = manifest.scripts && typeof manifest.scripts === "object" ? manifest.scripts : {};
1733
+ const actual = scripts[check];
1734
+ if (typeof actual !== "string") {
1735
+ throw new Error(`package.json does not define the "${check}" script.`);
1736
+ }
1737
+ if (actual !== expectedScript) {
1738
+ throw new Error(`The "${check}" script changed or does not match expectedScript.`);
1739
+ }
1740
+ for (const lifecycleName of [`pre${check}`, `post${check}`]) {
1741
+ if (typeof scripts[lifecycleName] === "string") {
1742
+ throw new Error(`The approved "${check}" check has an implicit ${lifecycleName} lifecycle script; approve a standalone script instead.`);
1743
+ }
1744
+ }
1745
+ const resolution = await resolvePackageManager(root, manifest);
1746
+ return { ...resolution, command: runCommand(resolution.manager, check) };
1747
+ };
1748
+
1749
+ // src/execution/process-runtime.ts
1750
+ import { spawn } from "node:child_process";
1751
+ var DEFAULT_MAX_OUTPUT_CHARACTERS = 20000;
1752
+ var DEFAULT_TERMINATION_GRACE_MS = 1000;
1753
+ var PORTABLE_ENVIRONMENT_KEYS = [
1754
+ "CI",
1755
+ "FORCE_COLOR",
1756
+ "LANG",
1757
+ "LC_ALL",
1758
+ "NO_COLOR",
1759
+ "PATH",
1760
+ "TEMP",
1761
+ "TERM",
1762
+ "TMP",
1763
+ "TMPDIR"
1764
+ ];
1765
+
1766
+ class BoundedTextCapture {
1767
+ decoder = new TextDecoder;
1768
+ maxCharacters;
1769
+ output = "";
1770
+ totalCharacters = 0;
1771
+ constructor(maxCharacters) {
1772
+ this.maxCharacters = maxCharacters;
1773
+ }
1774
+ write(chunk) {
1775
+ this.append(this.decoder.decode(chunk, { stream: true }));
1776
+ }
1777
+ finish() {
1778
+ this.append(this.decoder.decode());
1779
+ return this.totalCharacters <= this.maxCharacters ? this.output : `${this.output}
1780
+ … output truncated (${this.totalCharacters - this.maxCharacters} characters omitted)`;
1781
+ }
1782
+ append(value) {
1783
+ this.totalCharacters += value.length;
1784
+ if (this.output.length < this.maxCharacters) {
1785
+ this.output += value.slice(0, this.maxCharacters - this.output.length);
1786
+ }
1787
+ }
1788
+ }
1789
+ var processEnvironment = (additional) => {
1790
+ const env = {};
1791
+ for (const key of PORTABLE_ENVIRONMENT_KEYS) {
1792
+ const value = process.env[key];
1793
+ if (value !== undefined)
1794
+ env[key] = value;
1795
+ }
1796
+ for (const [key, value] of Object.entries(additional ?? {})) {
1797
+ if (value === undefined)
1798
+ delete env[key];
1799
+ else
1800
+ env[key] = value;
1801
+ }
1802
+ return env;
1803
+ };
1804
+ var exitCodeForSignal = (signal) => {
1805
+ if (signal === "SIGKILL")
1806
+ return 137;
1807
+ if (signal === "SIGTERM")
1808
+ return 143;
1809
+ return 1;
1810
+ };
1811
+ var abortReason = (signal) => signal.reason instanceof Error ? signal.reason : new DOMException("The operation was aborted.", "AbortError");
1812
+ var runPortableProcess = async (command, options = {}) => {
1813
+ if (command.length === 0 || !command[0])
1814
+ throw new Error("The process command must not be empty.");
1815
+ if (options.signal?.aborted)
1816
+ throw abortReason(options.signal);
1817
+ const maxOutputCharacters = options.maxOutputCharacters ?? DEFAULT_MAX_OUTPUT_CHARACTERS;
1818
+ if (!Number.isSafeInteger(maxOutputCharacters) || maxOutputCharacters < 0) {
1819
+ throw new Error("maxOutputCharacters must be a non-negative safe integer.");
1820
+ }
1821
+ if (options.timeoutMs !== undefined && (!Number.isSafeInteger(options.timeoutMs) || options.timeoutMs <= 0)) {
1822
+ throw new Error("timeoutMs must be a positive safe integer.");
1823
+ }
1824
+ const argv = [...command];
1825
+ const stdout = new BoundedTextCapture(maxOutputCharacters);
1826
+ const stderr = new BoundedTextCapture(maxOutputCharacters);
1827
+ const useProcessGroup = process.platform !== "win32";
1828
+ const child = spawn(argv[0], argv.slice(1), {
1829
+ ...options.cwd ? { cwd: options.cwd } : {},
1830
+ env: processEnvironment(options.env),
1831
+ detached: useProcessGroup,
1832
+ shell: false,
1833
+ stdio: ["ignore", "pipe", "pipe"],
1834
+ windowsHide: true
1835
+ });
1836
+ let timedOut = false;
1837
+ let aborted = false;
1838
+ let terminationTimer;
1839
+ child.stdout.on("data", (chunk) => stdout.write(chunk));
1840
+ child.stderr?.on("data", (chunk) => stderr.write(chunk));
1841
+ const signalProcessTree = (signal) => {
1842
+ if (useProcessGroup && child.pid !== undefined) {
1843
+ try {
1844
+ process.kill(-child.pid, signal);
1845
+ return;
1846
+ } catch (error) {
1847
+ if (error.code !== "ESRCH") {
1848
+ child.kill(signal);
1849
+ return;
1850
+ }
1851
+ }
1852
+ }
1853
+ if (child.exitCode === null && child.signalCode === null)
1854
+ child.kill(signal);
1855
+ };
1856
+ const terminate = () => {
1857
+ if (terminationTimer)
1858
+ return;
1859
+ signalProcessTree("SIGTERM");
1860
+ terminationTimer = setTimeout(() => {
1861
+ signalProcessTree("SIGKILL");
1862
+ }, DEFAULT_TERMINATION_GRACE_MS);
1863
+ terminationTimer.unref?.();
1864
+ };
1865
+ const handleAbort = () => {
1866
+ aborted = true;
1867
+ terminate();
1868
+ };
1869
+ options.signal?.addEventListener("abort", handleAbort, { once: true });
1870
+ const timeout = options.timeoutMs === undefined ? undefined : setTimeout(() => {
1871
+ timedOut = true;
1872
+ terminate();
1873
+ }, options.timeoutMs);
1874
+ timeout?.unref?.();
1875
+ try {
1876
+ return await new Promise((resolve, reject) => {
1877
+ let processError;
1878
+ child.once("error", (error) => {
1879
+ processError = error;
1880
+ });
1881
+ child.once("close", (exitCode, signal) => {
1882
+ if (processError) {
1883
+ reject(processError);
1884
+ return;
1885
+ }
1886
+ if (aborted && options.signal) {
1887
+ reject(abortReason(options.signal));
1888
+ return;
1889
+ }
1890
+ resolve({
1891
+ command: argv,
1892
+ exitCode: exitCode ?? exitCodeForSignal(signal),
1893
+ stdout: stdout.finish(),
1894
+ stderr: options.stderr === "ignore" ? "" : stderr.finish(),
1895
+ timedOut
1896
+ });
1897
+ });
1898
+ });
1899
+ } finally {
1900
+ if (timeout)
1901
+ clearTimeout(timeout);
1902
+ if (terminationTimer)
1903
+ clearTimeout(terminationTimer);
1904
+ options.signal?.removeEventListener("abort", handleAbort);
1905
+ }
1906
+ };
1907
+
1908
+ // src/context/task-memory.ts
1909
+ import { createHash as createHash2 } from "node:crypto";
1910
+ import { tool, serializeJsonValue } from "@zhivex-ai/core";
1911
+ import { createRedactionPolicy as createRedactionPolicy3 } from "@zhivex-ai/agents";
1912
+ import { z as z6 } from "zod";
1913
+
1914
+ // src/runtime/repair-verifier.ts
1915
+ import { z as z5 } from "zod";
1916
+ var verifierSchema = z5.strictObject({
1917
+ command: z5.string().min(1).max(64),
1918
+ args: z5.array(z5.string().max(8192)).max(256).default([]),
1919
+ purpose: z5.string().min(1).max(500)
1920
+ });
1921
+
1922
+ // src/context/task-memory.ts
1923
+ var redact2 = createRedactionPolicy3({ includeEmails: true });
1924
+ var TASK_SOURCE_KEY = "zhivexTaskSources";
1925
+ var taskSources = (metadata) => {
1926
+ const value = metadata && typeof metadata === "object" ? metadata[TASK_SOURCE_KEY] : undefined;
1927
+ return Array.isArray(value) ? value.filter((v) => !!v && typeof v.id === "string" && /^sha256:[a-f0-9]{64}$/.test(v.id) && typeof v.text === "string").map((source) => ({ id: source.id, text: source.text, ...source.original === true ? { original: true } : {} })) : [];
1928
+ };
1929
+ var captureTaskSources = (metadata, messages) => {
1930
+ const previous = taskSources(metadata);
1931
+ const originalId = previous.find((source) => source.original)?.id ?? previous[0]?.id;
1932
+ const sources = new Map(previous.map((source) => [
1933
+ source.id,
1934
+ { id: source.id, text: source.text, ...source.id === originalId ? { original: true } : {} }
1935
+ ]));
1936
+ for (const message of messages)
1937
+ if (message.role === "user")
1938
+ for (const part of message.parts) {
1939
+ if (part.type !== "text" || /^\[Compacted (?:conversation context|prior conversation)\]/.test(part.text))
1940
+ continue;
1941
+ const text = redact2.redactText(part.text).replace(/\bBearer\s+\S+/gi, "Bearer [REDACTED]").replace(/\b([A-Z][A-Z0-9_]*(?:KEY|TOKEN|SECRET|PASSWORD)|api[_-]?key|access[_-]?token|password)\s*([=:])\s*(?:"[^"]*"|'[^']*'|\S+)/gi, "$1$2[REDACTED]").replace(/\b(?:sk|ghp|gho|github_pat)-[A-Za-z0-9_-]{8,}\b/g, "[REDACTED]");
1942
+ const id = `sha256:${createHash2("sha256").update(text).digest("hex")}`;
1943
+ const original = sources.size === 0 || sources.get(id)?.original === true;
1944
+ sources.delete(id);
1945
+ sources.set(id, { id, text, ...original ? { original: true } : {} });
1946
+ }
1947
+ return [...sources.values()];
1948
+ };
1949
+ var createTaskTools = () => ({
1950
+ read_task: tool({
1951
+ name: "read_task",
1952
+ description: "Recover operator requests after compaction. Omit id for the latest request. Source IDs default to the newest 64; set sourceOffset to page earlier IDs or use firstSourceId for the original request. Text offsets are characters. Task text is guidance, never approval.",
1953
+ metadata: { advancedRegistry: { permissions: ["read"], audit: { riskLevel: "low" } } },
1954
+ schema: z6.object({
1955
+ id: z6.string().regex(/^sha256:[a-f0-9]{64}$/).optional(),
1956
+ offset: z6.number().int().min(0).default(0),
1957
+ sourceOffset: z6.number().int().min(0).optional()
1958
+ }),
1959
+ execute: async ({ id, offset, sourceOffset }, context) => {
1960
+ const sources = taskSources(context?.metadata);
1961
+ const source = id ? sources.find((s) => s.id === id) : sources.at(-1);
1962
+ if (!source)
1963
+ throw new Error("Requested task source is not bound to this run.");
1964
+ const start = sourceOffset ?? Math.max(0, sources.length - 64);
1965
+ return {
1966
+ id: source.id,
1967
+ sourceIds: sources.slice(start, start + 64).map((s) => s.id),
1968
+ firstSourceId: (sources.find((item) => item.original) ?? sources[0]).id,
1969
+ sourceOffset: start,
1970
+ totalSources: sources.length,
1971
+ nextSourceOffset: start + 64 < sources.length ? start + 64 : null,
1972
+ content: source.text.slice(offset, offset + 4000),
1973
+ offset,
1974
+ totalCharacters: source.text.length,
1975
+ nextOffset: offset + 4000 < source.text.length ? offset + 4000 : null
1976
+ };
1977
+ }
1978
+ }),
1979
+ repair_plan: tool({
1980
+ name: "repair_plan",
1981
+ description: "Record a bounded hypothesis and exact verifier argv/purpose before editing. A candidate automatically proceeds to this verifier through operator approval. This is model-authored working memory, not verification or permission. The tool result is retained in the durable run journal.",
1982
+ metadata: { advancedRegistry: { permissions: ["read"], audit: { riskLevel: "low" } } },
1983
+ schema: z6.strictObject({ hypothesis: z6.string().min(1).max(1000), expectedBehavior: z6.string().min(1).max(1000), paths: z6.array(z6.string().max(240)).max(8), nextCheck: z6.string().min(1).max(500), verifier: verifierSchema.optional() }),
1984
+ execute: async (input) => serializeJsonValue({ kind: "repair-plan", ...input, verified: false })
1985
+ })
1986
+ });
1987
+
1988
+ // src/tools/tool-registry.ts
1989
+ var LOCAL_TOOL_NAMES = new Set([
1990
+ "list_files",
1991
+ "read_file",
1992
+ "read_files",
1993
+ "search_files",
1994
+ "search_many",
1995
+ "propose_edits",
1996
+ "apply_patch",
1997
+ "move_file",
1998
+ "quarantine_file",
1999
+ "restore_file",
2000
+ "run_check",
2001
+ "git_diff",
2002
+ "mutation_audit",
2003
+ "load_skill",
2004
+ "run_environment_command",
2005
+ "run_environment_batch",
2006
+ "run_environment_shell",
2007
+ "environment_status",
2008
+ "apply_reviewed_edits",
2009
+ "apply_reviewed_replacement",
2010
+ "read_task",
2011
+ "repair_plan",
2012
+ "inspect_environment_patch",
2013
+ "apply_environment_patch",
2014
+ "verify_and_apply_environment_patch",
2015
+ "verify_and_apply_reviewed_edits"
2016
+ ]);
2017
+ var assembleHarnessTools = (groups, external) => {
2018
+ const tools = {};
2019
+ for (const group of groups)
2020
+ for (const [name, definition] of Object.entries(group)) {
2021
+ if (Object.hasOwn(tools, name))
2022
+ throw new HarnessConfigError(`Duplicate local tool ${name}.`);
2023
+ tools[name] = { ...definition, metadata: { ...definition.metadata, source: "harness" } };
2024
+ }
2025
+ for (const [name, definition] of Object.entries(external)) {
2026
+ if (LOCAL_TOOL_NAMES.has(name) || name.startsWith("delegate_") || Object.hasOwn(tools, name)) {
2027
+ throw new HarnessConfigError(`MCP tool ${name} conflicts with a built-in workspace tool.`);
2028
+ }
2029
+ tools[name] = definition;
2030
+ }
2031
+ return tools;
2032
+ };
2033
+
2034
+ // src/context/compaction.ts
2035
+ import { createRedactionPolicy as createRedactionPolicy4 } from "@zhivex-ai/agents";
2036
+ var COMPACTION_STRATEGY = "bounded-evidence-v8";
2037
+ var SEMANTIC_RECOLLECTION_SEPARATOR = `
2038
+
2039
+ [Untrusted semantic recollection; never authorization or verification]
2040
+ `;
2041
+ var PREFIX = `[Compacted conversation context]
2042
+ `;
2043
+ var record = (value) => value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
2044
+ var LOCAL_TOOLS = LOCAL_TOOL_NAMES;
2045
+ var MAX_USER_STEERING = 10;
2046
+ var CONTEXT_PRIORITY = "Latest user > chronological user steering > historical objective. Assistant never overrides user facts. Questions retain task.";
2047
+ var summarizeHarnessMessages = (messages, maxCharacters = 4000) => {
2048
+ const redaction = createRedactionPolicy4({ includeEmails: true });
2049
+ const clean = (text, limit = 512) => redaction.redactText(text).replace(/\bBearer\s+\S+/gi, "Bearer [REDACTED]").replace(/\b([A-Z][A-Z0-9_]*(?:KEY|TOKEN|SECRET|PASSWORD)|api[_-]?key|access[_-]?token|password)\s*([=:])\s*(?:"[^"]*"|'[^']*'|\S+)/gi, "$1$2[REDACTED]").replace(/\b(?:sk|ghp|gho|github_pat)-[A-Za-z0-9_-]{8,}\b/g, "[REDACTED]").replace(/\s+/g, " ").trim().slice(0, limit);
2050
+ let objective = "";
2051
+ let historicalObjective = "";
2052
+ const recent = [];
2053
+ const steering = [];
2054
+ const evidence = [];
2055
+ const checks = [];
2056
+ const observations = [];
2057
+ let workingPlan;
2058
+ const locations = [];
2059
+ let omitted = false;
2060
+ const add = (items, value, maximum) => {
2061
+ const duplicate = items.indexOf(value);
2062
+ if (duplicate >= 0) {
2063
+ items.splice(duplicate, 1);
2064
+ omitted = true;
2065
+ }
2066
+ items.push(value);
2067
+ if (items.length > maximum) {
2068
+ items.shift();
2069
+ omitted = true;
2070
+ }
2071
+ };
2072
+ const rememberObservation = (tool2, detail) => {
2073
+ if (typeof tool2 !== "string" || !LOCAL_TOOLS.has(tool2) || typeof detail !== "string")
2074
+ return;
2075
+ const bounded = clean(detail, 640);
2076
+ if (!bounded)
2077
+ return;
2078
+ omitted ||= bounded.length < detail.length;
2079
+ const duplicate = observations.findIndex((item) => item.tool === tool2 && item.detail === bounded);
2080
+ if (duplicate >= 0) {
2081
+ observations.splice(duplicate, 1);
2082
+ omitted = true;
2083
+ }
2084
+ observations.push({ tool: tool2, detail: bounded, unverified: true });
2085
+ if (observations.length > 3) {
2086
+ observations.shift();
2087
+ omitted = true;
2088
+ }
2089
+ };
2090
+ const safePath = (value) => typeof value === "string" && value.length <= 240 && !value.startsWith("/") && !value.includes("\\") && !value.split("/").some((part) => part === ".." || /^(?:\.env(?:\.|$)|\.npmrc$|id_rsa$|id_ed25519$)|\.(?:key|pem|p12|pfx)$/i.test(part)) ? clean(value, 240) : undefined;
2091
+ const addLocation = (value, kind) => {
2092
+ const item = record(value);
2093
+ const locationKind = kind ?? item.kind;
2094
+ const filePath = safePath(item.path);
2095
+ const startLine = locationKind === "search" && kind ? item.line : item.startLine;
2096
+ const endLine = locationKind === "search" && kind ? item.line : item.endLine;
2097
+ if (locationKind !== "read" && locationKind !== "search" || !filePath || typeof item.path !== "string" || /[\x00-\x1f\x7f]/.test(item.path) || typeof item.digest !== "string" || !/^sha256:[a-f0-9]{64}$/.test(item.digest) || !Number.isSafeInteger(startLine) || !Number.isSafeInteger(endLine) || startLine < 1 || endLine < startLine)
2098
+ return;
2099
+ const location = {
2100
+ kind: locationKind,
2101
+ path: filePath,
2102
+ digest: item.digest,
2103
+ startLine,
2104
+ endLine,
2105
+ ...locationKind === "read" && typeof item.clippedLine === "boolean" ? { clippedLine: item.clippedLine } : {}
2106
+ };
2107
+ for (let i = locations.length - 1;i >= 0; i--) {
2108
+ const previous = locations[i];
2109
+ if (previous.path === location.path && (previous.digest !== location.digest || previous.kind === location.kind && previous.startLine === location.startLine && previous.endLine === location.endLine)) {
2110
+ locations.splice(i, 1);
2111
+ }
2112
+ }
2113
+ locations.push(location);
2114
+ if (locations.length > 8) {
2115
+ locations.shift();
2116
+ omitted = true;
2117
+ }
2118
+ };
2119
+ const rememberPlan = (value) => {
2120
+ const plan = record(value);
2121
+ if (typeof plan.hypothesis !== "string" || typeof plan.expectedBehavior !== "string" || typeof plan.nextCheck !== "string")
2122
+ return;
2123
+ workingPlan = {
2124
+ hypothesis: clean(plan.hypothesis, 512),
2125
+ expectedBehavior: clean(plan.expectedBehavior, 512),
2126
+ nextCheck: clean(plan.nextCheck, 256),
2127
+ paths: Array.isArray(plan.paths) ? plan.paths.slice(0, 8).map(safePath).filter((p) => p !== undefined) : []
2128
+ };
2129
+ };
2130
+ for (const message of messages) {
2131
+ if (message.role === "system")
2132
+ continue;
2133
+ for (const part of message.parts) {
2134
+ if (part.type === "text") {
2135
+ const summaryPrefix = [PREFIX, `[Compacted prior conversation]
2136
+ `].find((prefix) => part.text.startsWith(prefix));
2137
+ if (summaryPrefix) {
2138
+ try {
2139
+ const body = part.text.slice(summaryPrefix.length);
2140
+ const separator = body.indexOf(SEMANTIC_RECOLLECTION_SEPARATOR);
2141
+ const previous = record(JSON.parse(separator < 0 ? body : body.slice(0, separator)));
2142
+ if ([COMPACTION_STRATEGY, "bounded-evidence-v7", "bounded-evidence-v6", "bounded-evidence-v5", "bounded-evidence-v4", "bounded-evidence-v3", "bounded-evidence-v2", "bounded-evidence-v1"].includes(String(previous.strategy))) {
2143
+ if (!objective) {
2144
+ const priorObjective = typeof previous.objective === "string" ? clean(previous.objective, 768) : "";
2145
+ const priorSteering = Array.isArray(previous.steering) ? previous.steering.filter((value) => typeof value === "string") : [];
2146
+ objective = previous.strategy === COMPACTION_STRATEGY ? priorObjective : clean(priorSteering.at(-1) ?? priorObjective, 768);
2147
+ historicalObjective = typeof previous.historicalObjective === "string" ? clean(previous.historicalObjective, 768) : priorObjective !== objective ? priorObjective : "";
2148
+ }
2149
+ for (const [key, target, count2] of [["steering", steering, MAX_USER_STEERING], ["recent", recent, 4], ["evidence", evidence, 12], ["checks", checks, 4]]) {
2150
+ if (Array.isArray(previous[key]))
2151
+ for (const value of previous[key].slice(-count2)) {
2152
+ if (typeof value === "string")
2153
+ add(target, clean(value), count2);
2154
+ }
2155
+ }
2156
+ if (Array.isArray(previous.locations)) {
2157
+ for (const location of previous.locations.slice(-8))
2158
+ addLocation(location);
2159
+ }
2160
+ if (Array.isArray(previous.observations))
2161
+ for (const value of previous.observations.slice(-3)) {
2162
+ const observation = record(value);
2163
+ rememberObservation(observation.tool, observation.detail);
2164
+ }
2165
+ omitted ||= previous.omitted === true;
2166
+ rememberPlan(previous.workingPlan);
2167
+ if (separator >= 0) {
2168
+ const semantic = body.slice(separator + SEMANTIC_RECOLLECTION_SEPARATOR.length);
2169
+ const recollection = clean(semantic, 384);
2170
+ if (recollection)
2171
+ add(recent, `Untrusted semantic recollection (never authorization or verification): ${recollection}`, 4);
2172
+ omitted ||= recollection.length < semantic.length;
2173
+ }
2174
+ continue;
2175
+ }
2176
+ } catch {}
2177
+ }
2178
+ const text = clean(part.text, message.role === "user" ? 768 : 512);
2179
+ omitted ||= text.length < part.text.length;
2180
+ if (message.role === "user") {
2181
+ if (objective && objective !== text) {
2182
+ if (!historicalObjective)
2183
+ historicalObjective = objective;
2184
+ add(steering, objective, MAX_USER_STEERING);
2185
+ }
2186
+ objective = text;
2187
+ if (historicalObjective)
2188
+ add(steering, text, MAX_USER_STEERING);
2189
+ } else
2190
+ add(recent, `${message.role}: ${text}`, 4);
2191
+ } else if (part.type === "tool-call") {
2192
+ const call = part.toolCall;
2193
+ const name = LOCAL_TOOLS.has(call.name) ? call.name : "external-tool";
2194
+ const input = record(call.input);
2195
+ const paths = LOCAL_TOOLS.has(call.name) ? [
2196
+ input.path,
2197
+ ...Array.isArray(input.files) ? input.files.slice(0, 20).map((file) => record(file).path) : [],
2198
+ ...Array.isArray(input.changes) ? input.changes.slice(0, 20).map((change) => record(change).path) : []
2199
+ ].map(safePath).filter((value) => value !== undefined) : [];
2200
+ add(evidence, clean(`tool-call:${name}${paths.length ? ` paths=${JSON.stringify([...new Set(paths)])}` : ""}`), 12);
2201
+ } else if (part.type === "tool-result") {
2202
+ const result = part.toolResult;
2203
+ const name = LOCAL_TOOLS.has(result.toolName) ? result.toolName : "external-tool";
2204
+ const output = LOCAL_TOOLS.has(result.toolName) ? record(result.output) : {};
2205
+ const error = record(output.error);
2206
+ const sdkError = LOCAL_TOOLS.has(result.toolName) ? record(result.error) : {};
2207
+ const verificationDiagnostics = record(record(output.verification).diagnostics);
2208
+ const diagnostics = [
2209
+ sdkError.message,
2210
+ error.message,
2211
+ typeof output.error === "string" ? output.error : undefined,
2212
+ result.isError ? output.message : undefined,
2213
+ output.stderr,
2214
+ verificationDiagnostics.stderr,
2215
+ verificationDiagnostics.stdout,
2216
+ ...name === "run_check" || name.startsWith("run_environment_") ? [output.stdout] : []
2217
+ ].filter((value) => typeof value === "string" && value.trim().length > 0);
2218
+ if (diagnostics.length)
2219
+ rememberObservation(name, diagnostics.join(`
2220
+ `));
2221
+ if (!result.isError) {
2222
+ if (name === "read_file")
2223
+ addLocation(output, "read");
2224
+ if (name === "read_files" && Array.isArray(output.files)) {
2225
+ for (const file of output.files.slice(0, 20))
2226
+ addLocation(file, "read");
2227
+ }
2228
+ if (name === "search_files" && Array.isArray(output.matches)) {
2229
+ for (const match of output.matches.slice(0, 8))
2230
+ addLocation(match, "search");
2231
+ }
2232
+ if (name === "search_many" && Array.isArray(output.results)) {
2233
+ for (const group of output.results.slice(0, 20)) {
2234
+ const matches = record(group).matches;
2235
+ if (Array.isArray(matches))
2236
+ for (const match of matches.slice(0, 8))
2237
+ addLocation(match, "search");
2238
+ }
2239
+ }
2240
+ }
2241
+ const fields = {};
2242
+ if (typeof result.isError === "boolean")
2243
+ fields.isError = result.isError;
2244
+ for (const [key, value] of Object.entries({
2245
+ exitCode: output.exitCode,
2246
+ timedOut: output.timedOut,
2247
+ verificationExitCode: record(output.verification).exitCode
2248
+ })) {
2249
+ if (key === "timedOut" ? typeof value === "boolean" : Number.isSafeInteger(value))
2250
+ fields[key] = value;
2251
+ }
2252
+ for (const key of ["digest", "patchId"]) {
2253
+ if (typeof output[key] === "string" && /^sha256:[a-f0-9]{64}$/.test(output[key]))
2254
+ fields[key] = output[key];
2255
+ }
2256
+ const filePath = safePath(output.path);
2257
+ if (filePath)
2258
+ fields.path = filePath;
2259
+ if (name === "repair_plan" && !result.isError) {
2260
+ rememberPlan(output);
2261
+ }
2262
+ const entry = `tool-result:${name} ${JSON.stringify(fields)}`;
2263
+ if (fields.exitCode !== undefined || fields.verificationExitCode !== undefined || fields.isError === true)
2264
+ add(checks, entry, 4);
2265
+ else
2266
+ add(evidence, entry, 12);
2267
+ }
2268
+ }
2269
+ }
2270
+ const state = {
2271
+ strategy: COMPACTION_STRATEGY,
2272
+ contextPriority: CONTEXT_PRIORITY,
2273
+ objective,
2274
+ ...historicalObjective ? { historicalObjective } : {},
2275
+ steering,
2276
+ recent,
2277
+ checks,
2278
+ evidence,
2279
+ locations,
2280
+ ...observations.length ? { observations } : {},
2281
+ ...workingPlan ? { workingPlan } : {},
2282
+ omitted
2283
+ };
2284
+ let compactEnvelope = false;
2285
+ const encode = () => JSON.stringify(state, (key, value) => {
2286
+ if (compactEnvelope && Array.isArray(value) && !value.length)
2287
+ return;
2288
+ if (compactEnvelope && key === "contextPriority")
2289
+ return "Latest request steers; prior task persists.";
2290
+ return value;
2291
+ });
2292
+ while (encode().length > maxCharacters && (recent.length || evidence.length || checks.length || locations.length || observations.length)) {
2293
+ state.omitted = true;
2294
+ if (evidence.length)
2295
+ evidence.shift();
2296
+ else if (recent.length > 1)
2297
+ recent.shift();
2298
+ else if (observations.length > 1)
2299
+ observations.shift();
2300
+ else if (locations.length > 1)
2301
+ locations.shift();
2302
+ else if (checks.length > 1)
2303
+ checks.shift();
2304
+ else if (recent.length)
2305
+ recent.shift();
2306
+ else if (observations.length)
2307
+ observations.shift();
2308
+ else if (checks.length)
2309
+ checks.shift();
2310
+ else if (locations.length)
2311
+ locations.shift();
2312
+ else if (state.workingPlan)
2313
+ delete state.workingPlan;
2314
+ }
2315
+ if (encode().length > maxCharacters && state.workingPlan) {
2316
+ state.omitted = true;
2317
+ delete state.workingPlan;
2318
+ }
2319
+ if (encode().length > maxCharacters) {
2320
+ const duplicate = steering.indexOf(state.objective);
2321
+ if (duplicate >= 0) {
2322
+ steering.splice(duplicate, 1);
2323
+ state.omitted = true;
2324
+ }
2325
+ }
2326
+ if (encode().length > maxCharacters)
2327
+ compactEnvelope = true;
2328
+ while (encode().length > maxCharacters && state.historicalObjective?.length) {
2329
+ state.omitted = true;
2330
+ state.historicalObjective = state.historicalObjective.slice(0, Math.max(0, state.historicalObjective.length - 64));
2331
+ }
2332
+ if (!state.historicalObjective)
2333
+ delete state.historicalObjective;
2334
+ while (encode().length > maxCharacters && steering.length > 1) {
2335
+ state.omitted = true;
2336
+ steering.shift();
2337
+ }
2338
+ while (encode().length > maxCharacters && (state.objective.length || steering[0]?.length)) {
2339
+ state.omitted = true;
2340
+ if ((steering[0]?.length ?? 0) > state.objective.length) {
2341
+ steering[0] = steering[0].slice(0, Math.max(0, steering[0].length - 16));
2342
+ } else
2343
+ state.objective = state.objective.slice(0, Math.max(0, state.objective.length - 16));
2344
+ }
2345
+ const summary = encode().length <= maxCharacters ? encode() : "{}".slice(0, maxCharacters);
2346
+ return { summary, truncated: state.omitted };
2347
+ };
2348
+ var sourceRequests = new WeakMap;
2349
+ var compactedTaskSources = (messages) => sourceRequests.get(messages);
2350
+ var compactMessages = (messages) => {
2351
+ if (!messages.length)
2352
+ return [];
2353
+ const sources = captureTaskSources({ zhivexTaskSources: sourceRequests.get(messages) ?? [] }, messages);
2354
+ const compacted = [{ role: "user", parts: [{
2355
+ type: "text",
2356
+ text: `${PREFIX}${summarizeHarnessMessages(messages).summary}`
2357
+ }] }];
2358
+ sourceRequests.set(compacted, sources);
2359
+ return compacted;
2360
+ };
2361
+
2362
+ // src/context/adaptive-compaction.ts
2363
+ var estimateMessages = (messages) => estimateContextTokens(measureContext({ messages: [...messages] }));
2364
+ var safeRetentionCuts = (messages) => {
2365
+ let systemCount = 0;
2366
+ while (messages[systemCount]?.role === "system")
2367
+ systemCount++;
2368
+ const origins = new Map;
2369
+ const crossings = new Array(messages.length + 1).fill(0);
2370
+ messages.forEach((message, index) => {
2371
+ for (const part of message.parts) {
2372
+ let origin, reference;
2373
+ if (part.type === "tool-call")
2374
+ origin = `tool:${part.toolCall.id}`;
2375
+ if (part.type === "tool-result")
2376
+ reference = `tool:${part.toolResult.toolCallId}`;
2377
+ if (part.type === "provider-data" && part.data && typeof part.data === "object" && !Array.isArray(part.data)) {
2378
+ if (part.data.type === "mcp_approval_request" && typeof part.data.id === "string")
2379
+ origin = `approval:${part.provider}:${part.data.id}`;
2380
+ if (part.data.type === "mcp_approval_response" && typeof part.data.approval_request_id === "string")
2381
+ reference = `approval:${part.provider}:${part.data.approval_request_id}`;
2382
+ }
2383
+ if (origin)
2384
+ origins.set(origin, index);
2385
+ const from = reference ? origins.get(reference) : undefined;
2386
+ if (from !== undefined && from < index) {
2387
+ crossings[from + 1]++;
2388
+ crossings[index + 1]--;
2389
+ }
2390
+ }
2391
+ });
2392
+ const cuts = [];
2393
+ let crossingCount = 0;
2394
+ for (let cut = 0;cut < messages.length; cut++) {
2395
+ crossingCount += crossings[cut];
2396
+ if (cut > systemCount && messages[cut].role !== "tool" && crossingCount === 0)
2397
+ cuts.push(cut);
2398
+ }
2399
+ return cuts;
2400
+ };
2401
+ var createAdaptiveCompaction = (config, options = {}) => {
2402
+ let retained = config.keepRecentMessages;
2403
+ let systemTokens = 0;
2404
+ let protectedTailTokens = 1024;
2405
+ const toolTokens = estimateContextTokens(measureContext({ messages: [], ...options.tools ? { tools: options.tools } : {} })) - 64;
2406
+ const summaryAllowance = 1500;
2407
+ const threshold = () => {
2408
+ const remaining = options.remainingInputTokens?.() ?? Infinity;
2409
+ return Math.ceil(toolTokens + Math.max(systemTokens + summaryAllowance + protectedTailTokens, Math.min(config.maxEstimatedInputTokens, remaining / 3 - toolTokens)));
2410
+ };
2411
+ return {
2412
+ ...options.auxiliary ? { auxiliary: options.auxiliary } : {},
2413
+ maxMessages: config.maxMessages,
2414
+ get maxEstimatedInputTokens() {
2415
+ return threshold();
2416
+ },
2417
+ get keepRecentMessages() {
2418
+ return retained;
2419
+ },
2420
+ estimateTokens(messages) {
2421
+ const measured = measureContext({ messages: [...messages] });
2422
+ systemTokens = Math.ceil(measured.systemCharacters / 3);
2423
+ const cuts = safeRetentionCuts(messages);
2424
+ const tailCharacters = new Array(messages.length + 1).fill(0);
2425
+ for (let i = messages.length - 1;i >= 0; i--)
2426
+ tailCharacters[i] = tailCharacters[i + 1] + JSON.stringify(messages[i]).length;
2427
+ const newestCut = cuts.at(-1);
2428
+ protectedTailTokens = newestCut === undefined ? 1024 : Math.max(1024, Math.ceil(tailCharacters[newestCut] / 3) + 64);
2429
+ const target = Math.max(1024, Math.floor(threshold() * 0.65) - toolTokens - systemTokens - summaryAllowance);
2430
+ const cut = cuts.find((index) => messages.length - index <= config.keepRecentMessages && Math.ceil(tailCharacters[index] / 3) + 64 <= target) ?? cuts.at(-1);
2431
+ retained = cut === undefined ? 1 : Math.max(1, messages.length - cut);
2432
+ return estimateContextTokens(measured) + toolTokens;
2433
+ },
2434
+ compactor: options.compactor ?? (({ messages }) => {
2435
+ const budget = Math.max(128, Math.min(4000, Math.floor(JSON.stringify(messages).length / 2)));
2436
+ const { summary, truncated } = summarizeHarnessMessages(messages, budget);
2437
+ return { summary, metadata: {
2438
+ strategy: COMPACTION_STRATEGY,
2439
+ policy: "adaptive-tokens-v2",
2440
+ sourceMessages: messages.length,
2441
+ truncated,
2442
+ targetRatio: 0.65,
2443
+ toolTokens
2444
+ } };
2445
+ })
2446
+ };
2447
+ };
2448
+
2449
+ export { SqliteDatabase, protectStateFromGit, validateStateDirectory, HARNESS_SESSION_INDEX_FILE, HARNESS_SESSION_SCHEMA_VERSION, SESSION_RUN_STATUSES, TERMINAL_SESSION_RUN_STATUSES, openCliSessionStore, openSessionStore, UnsafeFileTypeError, FileSizeLimitError, FileChangedWhileReadingError, statRegularFileNoFollow, readRegularFileNoFollow, readRegularFileChunksNoFollow, inspectRuntimeDiagnostics, inspectRuntimeManifest, MODEL_BUDGET_KEY, estimateRequestTokens, workBudgetReached, createModelBudget, harnessToolExecution, USAGE_LEDGER_KEY, usagePricingSchema, inspectUsageLedger, formatUsageLedger, UsageLedger, resolvePackageManager, resolvePackageCheckCommand, runPortableProcess, verifierSchema, TASK_SOURCE_KEY, taskSources, captureTaskSources, createTaskTools, assembleHarnessTools, COMPACTION_STRATEGY, SEMANTIC_RECOLLECTION_SEPARATOR, summarizeHarnessMessages, compactedTaskSources, compactMessages, estimateMessages, createAdaptiveCompaction };