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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (157) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/README.md +12 -10
  3. package/SECURITY.md +1 -1
  4. package/SUPPORT.md +4 -4
  5. package/contracts/engine-api.json +334 -0
  6. package/contracts/public-api.json +267 -1
  7. package/contracts/stable-api-signatures.json +43 -43
  8. package/dist/acp-cli.js +54 -18
  9. package/dist/approvals/approval-history.d.ts +22 -0
  10. package/dist/approvals/explicit-review.d.ts +24 -0
  11. package/dist/approvals/host-policy-identity.d.ts +3 -0
  12. package/dist/cli/arguments.d.ts +6 -2
  13. package/dist/cli/checkpoints.d.ts +230 -0
  14. package/dist/cli/cli-profiles.d.ts +4 -2
  15. package/dist/cli/governance-report.d.ts +5 -0
  16. package/dist/cli/policy-decisions.d.ts +9 -0
  17. package/dist/cli/policy.d.ts +5 -0
  18. package/dist/cli/presentation.d.ts +3 -1
  19. package/dist/cli/terminal/terminal-ui.d.ts +4 -0
  20. package/dist/{cli-entry-xmf48p6a.js → cli-entry-1gfg3brj.js} +14921 -10692
  21. package/dist/cli-entry-bx3vjw7h.js +656 -0
  22. package/dist/{index-fvca1xea.js → cli-entry-d5788aay.js} +339 -58
  23. package/dist/{cli-qgwvtk0w.js → cli-etsmtst3.js} +918 -2222
  24. package/dist/cli.js +3 -5
  25. package/dist/client/acp-mcp-admission.d.ts +32 -0
  26. package/dist/client/acp-mcp-host.d.ts +19 -0
  27. package/dist/client/acp-mcp-session.d.ts +14 -0
  28. package/dist/client/acp-stdio.d.ts +2 -0
  29. package/dist/client/acp.d.ts +4 -1
  30. package/dist/client/adapter.d.ts +13 -1
  31. package/dist/client/json-contracts.d.ts +107 -4
  32. package/dist/client/local-service.d.ts +2 -0
  33. package/dist/client/protocol.d.ts +127 -1
  34. package/dist/client/service-events.d.ts +4 -0
  35. package/dist/compat/cli-options.d.ts +13 -1
  36. package/dist/compat/code-support.js +23 -87
  37. package/dist/{config-q32jm1e6.js → config-c002f06f.js} +1 -1
  38. package/dist/{config-6gtpxvjv.js → config-j3jga5wh.js} +3 -1
  39. package/dist/context/adaptive-compaction.d.ts +1 -0
  40. package/dist/context/compaction.d.ts +1 -1
  41. package/dist/context/request-measurements.d.ts +17 -0
  42. package/dist/context/request-projection.d.ts +10 -0
  43. package/dist/context/task-memory.d.ts +1 -0
  44. package/dist/engine/acp.d.ts +2 -0
  45. package/dist/engine/acp.js +197 -1
  46. package/dist/engine/client.js +11 -4
  47. package/dist/engine/desktop-providers-v1.d.ts +4 -0
  48. package/dist/engine/desktop-providers-v1.js +18 -0
  49. package/dist/engine/desktop-state-v1.d.ts +10 -0
  50. package/dist/engine/desktop-state-v1.js +168 -0
  51. package/dist/engine/index.d.ts +12 -0
  52. package/dist/engine/index.js +571 -71
  53. package/dist/engine/mcp-stdio-v1.d.ts +11 -0
  54. package/dist/engine/mcp-stdio-v1.js +108 -0
  55. package/dist/engine/models.d.ts +3 -0
  56. package/dist/engine/models.js +894 -0
  57. package/dist/engine/protocol.d.ts +1 -0
  58. package/dist/engine/protocol.js +24 -0
  59. package/dist/engine/service.js +12 -5
  60. package/dist/execution/acceptance-snapshot.d.ts +2 -0
  61. package/dist/execution/execution-environment.d.ts +10 -0
  62. package/dist/execution/mcp-oci-channel.d.ts +11 -0
  63. package/dist/execution/mcp-oci-server.d.ts +38 -0
  64. package/dist/execution/mcp-oci-workspace.d.ts +23 -0
  65. package/dist/execution/mcp-resource-recovery.d.ts +19 -0
  66. package/dist/execution/mcp-workspace-snapshot.d.ts +20 -0
  67. package/dist/hostile-repository-demo.js +3169 -1163
  68. package/dist/{index-f4sb0e2q.js → index-2jrdcj6b.js} +17 -2
  69. package/dist/{index-77h9qr5h.js → index-3ah1f369.js} +40 -10
  70. package/dist/{index-pgqz45fj.js → index-42gp2hy8.js} +197 -1091
  71. package/dist/{index-41fkzt91.js → index-4jw3tvy8.js} +10 -2
  72. package/dist/index-6m08wvab.js +1768 -0
  73. package/dist/index-8705zxpr.js +145 -0
  74. package/dist/{index-d0wrghff.js → index-8vm2m34s.js} +225 -1416
  75. package/dist/index-8zzhggxv.js +477 -0
  76. package/dist/index-a97bezbz.js +1073 -0
  77. package/dist/index-azmwjp95.js +1004 -0
  78. package/dist/{cli-entry-0mmeag20.js → index-gq9xagx2.js} +122 -836
  79. package/dist/index-h0mqx2ed.js +1373 -0
  80. package/dist/index-hnaxfpb5.js +5589 -0
  81. package/dist/{index-2aymdsqc.js → index-qfpz5y2v.js} +24 -0
  82. package/dist/{index-kfgt9yqq.js → index-w2sj0wkw.js} +33 -10
  83. package/dist/index-wg1sd3at.js +80 -0
  84. package/dist/index-xqxeypt5.js +888 -0
  85. package/dist/index-yb239ghk.js +961 -0
  86. package/dist/index-z0qxmkm2.js +3389 -0
  87. package/dist/index.js +85 -72
  88. package/dist/integrations/mcp-host-session.d.ts +26 -0
  89. package/dist/integrations/mcp-stdio-admission.d.ts +54 -0
  90. package/dist/integrations/mcp-stdio-client.d.ts +44 -0
  91. package/dist/integrations/mcp-stdio-framing.d.ts +18 -0
  92. package/dist/integrations/mcp-stdio-launch.d.ts +20 -0
  93. package/dist/integrations/mcp-stdio-session.d.ts +7 -0
  94. package/dist/integrations/mcp-stdio-tools.d.ts +12 -0
  95. package/dist/internal/desktop/catalog.d.ts +2 -0
  96. package/dist/internal/desktop/persistence.d.ts +2 -3
  97. package/dist/internal/desktop/providers.d.ts +1 -0
  98. package/dist/models/catalog.d.ts +2 -0
  99. package/dist/persistence/governance-context.d.ts +126 -0
  100. package/dist/persistence/governance-report.d.ts +299 -0
  101. package/dist/persistence/host-database-backup.d.ts +28 -0
  102. package/dist/persistence/mcp-execution-journal.d.ts +19 -0
  103. package/dist/persistence/mcp-reconciliation.d.ts +33 -0
  104. package/dist/persistence/mcp-resource-journal.d.ts +54 -0
  105. package/dist/persistence/workspace-checkpoints.d.ts +138 -0
  106. package/dist/providers/anthropic-continuation.d.ts +5 -0
  107. package/dist/providers/providers.d.ts +1 -1
  108. package/dist/providers/vertex-auth.d.ts +27 -0
  109. package/dist/runtime/delegation-contracts.d.ts +9 -0
  110. package/dist/runtime/delegation-correction.d.ts +9 -0
  111. package/dist/runtime/delegation-result.d.ts +73 -0
  112. package/dist/runtime/error-diagnostics.d.ts +86 -0
  113. package/dist/runtime/harness.d.ts +11 -1
  114. package/dist/runtime/policy-decisions.d.ts +66 -0
  115. package/dist/runtime/policy-inspection.d.ts +52 -0
  116. package/dist/runtime/provider-diagnostics.d.ts +1 -0
  117. package/dist/runtime/runtime-instructions.d.ts +8 -0
  118. package/dist/runtime/task-acceptance-checks.d.ts +103 -0
  119. package/dist/runtime/task-acceptance-delivery.d.ts +107 -0
  120. package/dist/runtime/task-acceptance-host.d.ts +20 -0
  121. package/dist/runtime/task-acceptance-record.d.ts +107 -0
  122. package/dist/runtime/task-acceptance-validation.d.ts +25 -0
  123. package/dist/runtime/task-acceptance.d.ts +47 -0
  124. package/dist/runtime/time-to-safe-fix.d.ts +83 -0
  125. package/dist/runtime/tool-policy-file.d.ts +23 -0
  126. package/dist/runtime/tool-policy-paths.d.ts +5 -0
  127. package/dist/runtime/tool-policy.d.ts +6 -0
  128. package/dist/service-cli.js +14 -7
  129. package/dist/{service-client-cli-gs78wrxe.js → service-client-cli-3365rfav.js} +307 -248
  130. package/dist/verify-historical-migrations.js +3134 -1129
  131. package/dist/workspace/path-policy.d.ts +4 -0
  132. package/dist/zhx.js +3 -5
  133. package/docs/ACP.md +14 -2
  134. package/docs/ANTHROPIC.md +33 -0
  135. package/docs/CLI.md +132 -0
  136. package/docs/CLIENT_PROTOCOL.md +45 -0
  137. package/docs/CODE_RELEASE.md +35 -18
  138. package/docs/ENGINE_API.md +216 -7
  139. package/docs/EXTENSIBILITY.md +46 -3
  140. package/docs/FIRST_USE.md +6 -6
  141. package/docs/LOCAL_SERVICE.md +13 -0
  142. package/docs/MCP_STDIO.md +20 -0
  143. package/docs/README.md +1 -0
  144. package/docs/STABILITY.md +4 -2
  145. package/docs/SUPPORT_MATRIX.md +1 -1
  146. package/docs/TOOL_POLICY.md +81 -0
  147. package/docs/VERTEX.md +50 -0
  148. package/docs/adr/0002-isolated-mcp-stdio.md +135 -0
  149. package/docs/fixtures/mcp-stdio-contract-v1.json +189 -0
  150. package/docs/support-matrix.json +2 -0
  151. package/evaluations/representative-assembly-matrix.json +46 -1
  152. package/evaluations/task-acceptance-routes.json +7 -0
  153. package/examples/README.md +3 -3
  154. package/package.json +43 -12
  155. package/dist/cli-entry-64pdp3k5.js +0 -17
  156. package/dist/cli-entry-7h9savv5.js +0 -375
  157. package/dist/index-5wjwe91x.js +0 -9434
@@ -1,224 +1,30 @@
1
+ import {
2
+ SqliteDatabase,
3
+ taskAcceptanceChecks,
4
+ taskAcceptanceDeliveryDiagnostic,
5
+ taskAcceptanceOutcome,
6
+ workspaceFilePathSchema
7
+ } from "./index-yb239ghk.js";
1
8
  import {
2
9
  HarnessConfigError,
3
10
  HarnessStateConflictError,
4
11
  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
- }
12
+ } from "./index-gq9xagx2.js";
207
13
 
208
14
  // src/persistence/state-directory.ts
209
15
  import { lstat } from "node:fs/promises";
210
- import path2 from "node:path";
16
+ import path from "node:path";
211
17
  var SENSITIVE_STATE_SEGMENTS = new Set([".git", ".env", ".npmrc", "dist", "node_modules", "src"]);
212
18
  var isInside = (root, candidate) => {
213
- const relative = path2.relative(root, candidate);
214
- return relative === "" || !relative.startsWith(`..${path2.sep}`) && relative !== ".." && !path2.isAbsolute(relative);
19
+ const relative = path.relative(root, candidate);
20
+ return relative === "" || !relative.startsWith(`..${path.sep}`) && relative !== ".." && !path.isAbsolute(relative);
215
21
  };
216
22
  var validateStateDirectory = async (workspace, stateDirectory) => {
217
- if (stateDirectory === workspace || stateDirectory === path2.parse(stateDirectory).root) {
23
+ if (stateDirectory === workspace || stateDirectory === path.parse(stateDirectory).root) {
218
24
  throw new HarnessWorkspaceError("The state directory cannot be the workspace or filesystem root.");
219
25
  }
220
26
  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);
27
+ const relativeSegments = insideWorkspace ? path.relative(workspace, stateDirectory).split(path.sep).filter(Boolean) : stateDirectory.slice(path.parse(stateDirectory).root.length).split(path.sep).filter(Boolean);
222
28
  const sensitiveSegment = insideWorkspace ? relativeSegments.find((segment) => SENSITIVE_STATE_SEGMENTS.has(segment)) : undefined;
223
29
  if (sensitiveSegment) {
224
30
  throw new HarnessWorkspaceError(`The state directory is inside the protected workspace path: ${sensitiveSegment}.`);
@@ -241,7 +47,7 @@ var validateStateDirectory = async (workspace, stateDirectory) => {
241
47
  }
242
48
  let current = workspace;
243
49
  for (const segment of relativeSegments) {
244
- current = path2.join(current, segment);
50
+ current = path.join(current, segment);
245
51
  try {
246
52
  const entry = await lstat(current);
247
53
  if (entry.isSymbolicLink()) {
@@ -262,26 +68,26 @@ var validateStateDirectory = async (workspace, stateDirectory) => {
262
68
  // src/persistence/sessions.ts
263
69
  import { createHash, randomUUID } from "node:crypto";
264
70
  import { chmod, lstat as lstat3, mkdir, open as open2, realpath, stat } from "node:fs/promises";
265
- import path4 from "node:path";
71
+ import path3 from "node:path";
266
72
  import { createRedactionPolicy } from "@zhivex-ai/agents";
267
73
 
268
74
  // src/persistence/state-gitignore.ts
269
- import { constants as constants2 } from "node:fs";
75
+ import { constants } from "node:fs";
270
76
  import { lstat as lstat2, open } from "node:fs/promises";
271
- import path3 from "node:path";
77
+ import path2 from "node:path";
272
78
  var exclusion = `# Local Harness state; never commit sessions, runs, or database journals.
273
79
  *
274
80
  `;
275
81
  var maximumExistingBytes = 65536;
276
82
  var maximumProtectedBytes = maximumExistingBytes + Buffer.byteLength(exclusion) + 1;
277
83
  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");
84
+ const directory = path2.resolve(stateDirectory) === path2.join(workspace, ".zhivex-harness", "runs") ? path2.dirname(stateDirectory) : stateDirectory;
85
+ const filename = path2.join(directory, ".gitignore");
280
86
  try {
281
- const handle = await open(filename2, constants2.O_RDWR | constants2.O_CREAT | constants2.O_APPEND | constants2.O_NOFOLLOW, 384);
87
+ const handle = await open(filename, constants.O_RDWR | constants.O_CREAT | constants.O_APPEND | constants.O_NOFOLLOW, 384);
282
88
  try {
283
89
  const info = await handle.stat();
284
- const entry = await lstat2(filename2);
90
+ const entry = await lstat2(filename);
285
91
  if (entry.isSymbolicLink() || entry.dev !== info.dev || entry.ino !== info.ino)
286
92
  throw new Error;
287
93
  if (!info.isFile() || info.nlink !== 1 || process.getuid && info.uid !== process.getuid() || info.size > maximumProtectedBytes)
@@ -298,7 +104,7 @@ async function protectStateFromGit(workspace, stateDirectory) {
298
104
  `) ? `
299
105
  ` : ""}${exclusion}`);
300
106
  }
301
- const current = await lstat2(filename2);
107
+ const current = await lstat2(filename);
302
108
  if (current.isSymbolicLink() || current.dev !== info.dev || current.ino !== info.ino)
303
109
  throw new Error;
304
110
  } finally {
@@ -412,7 +218,7 @@ var stableKey = (kind, value) => createHash("sha256").update(kind).update("\x00"
412
218
  var sessionId = () => `ses_${randomUUID()}`;
413
219
  var turnId = () => `turn_${randomUUID()}`;
414
220
  var ensurePrivateDatabase = async (workspace, stateDirectory) => {
415
- const requestedStateDirectory = path4.resolve(stateDirectory);
221
+ const requestedStateDirectory = path3.resolve(stateDirectory);
416
222
  await validateStateDirectory(workspace, requestedStateDirectory);
417
223
  await mkdir(requestedStateDirectory, { recursive: true, mode: 448 });
418
224
  const directoryEntry = await lstat3(requestedStateDirectory);
@@ -422,7 +228,7 @@ var ensurePrivateDatabase = async (workspace, stateDirectory) => {
422
228
  await chmod(requestedStateDirectory, 448);
423
229
  const canonicalStateDirectory = await realpath(requestedStateDirectory);
424
230
  await protectStateFromGit(workspace, canonicalStateDirectory);
425
- const databasePath = path4.join(canonicalStateDirectory, HARNESS_SESSION_INDEX_FILE);
231
+ const databasePath = path3.join(canonicalStateDirectory, HARNESS_SESSION_INDEX_FILE);
426
232
  let databaseEntry;
427
233
  try {
428
234
  const handle = await open2(databasePath, "wx", 384);
@@ -455,7 +261,7 @@ var toRunReference = (row) => ({
455
261
  var metadataSize = (input) => Object.values(input).reduce((total, value) => total + (typeof value === "string" ? byteLength(value) : 0), 0);
456
262
  var isTerminal = (status) => TERMINAL_STATUSES.has(status);
457
263
  var openCliSessionStore = async (options) => {
458
- const workspace = await realpath(path4.resolve(options.workspace));
264
+ const workspace = await realpath(path3.resolve(options.workspace));
459
265
  const workspaceEntry = await stat(workspace);
460
266
  if (!workspaceEntry.isDirectory()) {
461
267
  throw new HarnessWorkspaceError(`The session workspace is not a directory: ${workspace}.`);
@@ -955,142 +761,6 @@ var openCliSessionStore = async (options) => {
955
761
  };
956
762
  var openSessionStore = openCliSessionStore;
957
763
 
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
764
  // src/runtime/runtime-diagnostics.ts
1095
765
  import { z } from "zod";
1096
766
  import { createRedactionPolicy as createRedactionPolicy2 } from "@zhivex-ai/agents";
@@ -1644,281 +1314,176 @@ class UsageLedger {
1644
1314
  }
1645
1315
  }
1646
1316
 
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();
1317
+ // src/runtime/task-acceptance.ts
1318
+ import { createHash as createHash2 } from "node:crypto";
1319
+ import { z as z5 } from "zod";
1320
+ var identifier = z5.string().regex(/^[A-Za-z0-9][A-Za-z0-9_.:-]{0,127}$/);
1321
+ var text = (maximum) => z5.string().min(1).max(maximum).refine((value) => value.trim().length > 0 && !/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/.test(value), "Text must be nonblank and contain no control characters.");
1322
+ var exactPath = workspaceFilePathSchema.refine((value) => !/[\\*?\[\]{}\x00-\x1f\x7f]/.test(value), "Use an exact normalized file path, without globs or controls.");
1323
+ var argv = z5.array(z5.string().max(8192).refine((value) => !value.includes("\x00"), "Arguments cannot contain NUL.")).max(256);
1324
+ var command = z5.string().regex(/^[A-Za-z0-9][A-Za-z0-9._+-]{0,63}$/).refine((value) => !["sh", "bash", "dash", "zsh", "fish", "cmd", "cmd.exe", "powershell", "pwsh"].includes(value.toLowerCase()), "Shell interpreters are not acceptance checks.");
1325
+ var commonCheck = { id: identifier, command, args: argv, purpose: text(500) };
1326
+ var check = z5.discriminatedUnion("kind", [
1327
+ z5.strictObject({
1328
+ ...commonCheck,
1329
+ kind: z5.literal("package-script"),
1330
+ script: identifier,
1331
+ expectedScript: text(8192),
1332
+ execution: z5.strictObject({ backend: z5.enum(["none", "oci"]), approval: z5.literal("required") })
1333
+ }),
1334
+ z5.strictObject({
1335
+ ...commonCheck,
1336
+ kind: z5.literal("argv"),
1337
+ execution: z5.strictObject({ backend: z5.literal("oci"), approval: z5.literal("required"), network: z5.literal("none") })
1338
+ })
1339
+ ]);
1340
+ var taskAcceptanceContractSchema = z5.strictObject({
1341
+ schemaVersion: z5.literal(1),
1342
+ taskId: identifier,
1343
+ allowedWritePaths: z5.array(exactPath).max(256),
1344
+ protectedFiles: z5.array(exactPath).max(256),
1345
+ requiredChecks: z5.array(check).min(1).max(32),
1346
+ humanReview: z5.array(z5.strictObject({ id: identifier, requirement: text(2000), status: z5.literal("pending") })).max(32)
1347
+ }).superRefine((contract, context) => {
1348
+ const unique = (values, field) => {
1349
+ if (new Set(values).size !== values.length)
1350
+ context.addIssue({ code: "custom", path: [field], message: "Duplicate entries are not permitted." });
1868
1351
  };
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?.();
1352
+ unique(contract.allowedWritePaths.map((p) => p.toLowerCase()), "allowedWritePaths");
1353
+ unique(contract.protectedFiles.map((p) => p.toLowerCase()), "protectedFiles");
1354
+ unique(contract.requiredChecks.map((c) => c.id), "requiredChecks");
1355
+ unique(contract.humanReview.map((c) => c.id), "humanReview");
1356
+ const overlaps = (a, b) => a === b || a.startsWith(b + "/") || b.startsWith(a + "/");
1357
+ const writes = contract.allowedWritePaths.map((p) => p.toLowerCase());
1358
+ if (writes.some((p, i) => writes.slice(i + 1).some((q) => overlaps(p, q)))) {
1359
+ context.addIssue({ code: "custom", path: ["allowedWritePaths"], message: "Exact file paths cannot contain ancestor/descendant conflicts." });
1360
+ }
1361
+ if (writes.some((p) => contract.protectedFiles.some((q) => overlaps(p, q.toLowerCase())))) {
1362
+ context.addIssue({ code: "custom", path: ["protectedFiles"], message: "Protected files conflict with permitted write paths." });
1363
+ }
1364
+ });
1365
+ var MAX_TASK_ACCEPTANCE_BYTES = 64 * 1024;
1366
+ function compileTaskAcceptanceContract(input) {
1367
+ let bytes;
1875
1368
  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);
1369
+ bytes = Buffer.byteLength(JSON.stringify(input));
1370
+ } catch {
1371
+ throw new Error("TASK_ACCEPTANCE_INVALID: requirements must be JSON serializable.");
1905
1372
  }
1906
- };
1373
+ if (bytes > MAX_TASK_ACCEPTANCE_BYTES)
1374
+ throw new Error("TASK_ACCEPTANCE_TOO_LARGE: requirements exceed 64 KiB.");
1375
+ const parsed = taskAcceptanceContractSchema.safeParse(input);
1376
+ if (!parsed.success)
1377
+ throw new Error("TASK_ACCEPTANCE_INVALID: " + parsed.error.issues.map((issue) => `${issue.path.join(".")}: ${issue.message}`).join("; "));
1378
+ const sorted = (items) => items.sort((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0);
1379
+ const contract = {
1380
+ schemaVersion: 1,
1381
+ taskId: parsed.data.taskId,
1382
+ allowedWritePaths: [...parsed.data.allowedWritePaths].sort(),
1383
+ protectedFiles: [...parsed.data.protectedFiles].sort(),
1384
+ requiredChecks: sorted(parsed.data.requiredChecks),
1385
+ humanReview: sorted(parsed.data.humanReview)
1386
+ };
1387
+ return { contract, digest: `sha256:${createHash2("sha256").update(JSON.stringify(contract)).digest("hex")}` };
1388
+ }
1907
1389
 
1908
1390
  // src/context/task-memory.ts
1909
- import { createHash as createHash2 } from "node:crypto";
1910
- import { tool, serializeJsonValue } from "@zhivex-ai/core";
1391
+ import { createHash as createHash3 } from "node:crypto";
1392
+ import { tool, serializeJsonValue as serializeJsonValue2 } from "@zhivex-ai/core";
1911
1393
  import { createRedactionPolicy as createRedactionPolicy3 } from "@zhivex-ai/agents";
1912
- import { z as z6 } from "zod";
1394
+ import { z as z8 } from "zod";
1913
1395
 
1914
1396
  // 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)
1397
+ import { z as z6 } from "zod";
1398
+ var verifierSchema = z6.strictObject({
1399
+ command: z6.string().min(1).max(64),
1400
+ args: z6.array(z6.string().max(8192)).max(256).default([]),
1401
+ purpose: z6.string().min(1).max(500)
1920
1402
  });
1921
1403
 
1404
+ // src/runtime/task-acceptance-record.ts
1405
+ import { z as z7 } from "zod";
1406
+ import { serializeJsonValue } from "@zhivex-ai/core";
1407
+ var TASK_ACCEPTANCE_KEY = "zhivexTaskAcceptanceV1";
1408
+ var TASK_ACCEPTANCE_EVIDENCE_KEY = "zhivexTaskAcceptanceEvidenceV1";
1409
+ var digest2 = z7.string().regex(/^sha256:[a-f0-9]{64}$/);
1410
+ var revisionSchema = z7.strictObject({ revision: z7.number().int().min(1), digest: digest2, previousDigest: digest2.nullable(), contract: taskAcceptanceContractSchema });
1411
+ var ledgerSchema = z7.strictObject({ schemaVersion: z7.literal(1), revisions: z7.array(revisionSchema).min(1).max(16) });
1412
+ function readTaskAcceptanceLedger(state) {
1413
+ const raw = state.metadata?.[TASK_ACCEPTANCE_KEY];
1414
+ if (raw === undefined)
1415
+ return;
1416
+ if (Buffer.byteLength(JSON.stringify(raw)) > 1024 * 1024)
1417
+ throw new Error("TASK_ACCEPTANCE_HISTORY_CAPACITY");
1418
+ const ledger = ledgerSchema.parse(raw);
1419
+ for (let index = 0;index < ledger.revisions.length; index++) {
1420
+ const row = ledger.revisions[index];
1421
+ if (row.revision !== index + 1 || row.previousDigest !== (ledger.revisions[index - 1]?.digest ?? null) || compileTaskAcceptanceContract(row.contract).digest !== row.digest || row.contract.taskId !== ledger.revisions[0].contract.taskId) {
1422
+ throw new Error("TASK_ACCEPTANCE_HISTORY_INVALID");
1423
+ }
1424
+ }
1425
+ return ledger;
1426
+ }
1427
+ function nextTaskAcceptanceLedger(current2, requirements) {
1428
+ if (current2)
1429
+ current2 = readTaskAcceptanceLedger({ metadata: { [TASK_ACCEPTANCE_KEY]: serializeJsonValue(current2) } });
1430
+ const { contract, digest: digest3 } = compileTaskAcceptanceContract(requirements);
1431
+ const previous = current2?.revisions.at(-1);
1432
+ if (previous && previous.contract.taskId !== contract.taskId)
1433
+ throw new Error("TASK_ACCEPTANCE_TASK_ID_IMMUTABLE");
1434
+ if (previous?.digest === digest3)
1435
+ throw new Error("TASK_ACCEPTANCE_UNCHANGED");
1436
+ if ((current2?.revisions.length ?? 0) >= 16)
1437
+ throw new Error("TASK_ACCEPTANCE_HISTORY_CAPACITY");
1438
+ const next = { schemaVersion: 1, revisions: [...structuredClone(current2?.revisions ?? []), {
1439
+ revision: (previous?.revision ?? 0) + 1,
1440
+ previousDigest: previous?.digest ?? null,
1441
+ digest: digest3,
1442
+ contract
1443
+ }] };
1444
+ if (Buffer.byteLength(JSON.stringify(next)) > 1024 * 1024)
1445
+ throw new Error("TASK_ACCEPTANCE_HISTORY_CAPACITY");
1446
+ return next;
1447
+ }
1448
+ async function persistTaskAcceptanceRevision(store, options) {
1449
+ const state = await store.load(options.runId, options.scope);
1450
+ if (!state || state.revision !== options.expectedRunRevision)
1451
+ throw new Error("TASK_ACCEPTANCE_REVISION_CONFLICT");
1452
+ const current2 = readTaskAcceptanceLedger(state);
1453
+ if ((current2?.revisions.at(-1)?.revision ?? 0) !== options.expectedContractRevision)
1454
+ throw new Error("TASK_ACCEPTANCE_REVISION_CONFLICT");
1455
+ const ledger = nextTaskAcceptanceLedger(current2, options.requirements), latest = ledger.revisions.at(-1);
1456
+ await store.save({ ...state, metadata: {
1457
+ ...state.metadata,
1458
+ [TASK_ACCEPTANCE_KEY]: serializeJsonValue(ledger),
1459
+ [TASK_ACCEPTANCE_EVIDENCE_KEY]: { schemaVersion: 1, contractRevision: latest.revision, contractDigest: latest.digest, status: "pending", checks: [], humanReview: serializeJsonValue(latest.contract.humanReview) }
1460
+ } }, { expectedRevision: options.expectedRunRevision });
1461
+ return structuredClone(ledger);
1462
+ }
1463
+ function taskAcceptanceCheckpointStore(store, runId, ledger) {
1464
+ const retained = serializeJsonValue(structuredClone(ledger));
1465
+ const latest = ledger.revisions.at(-1);
1466
+ const pending = { schemaVersion: 1, contractRevision: latest.revision, contractDigest: latest.digest, status: "pending", checks: [], humanReview: serializeJsonValue(latest.contract.humanReview) };
1467
+ return new Proxy(store, { get(target, key) {
1468
+ if (key === "save")
1469
+ return async (...args) => {
1470
+ const [state] = args;
1471
+ if (state.runId === runId) {
1472
+ const violation = taskAcceptanceDeliveryDiagnostic(latest.digest, latest.revision);
1473
+ state.metadata = { ...state.metadata, [TASK_ACCEPTANCE_KEY]: structuredClone(retained), [TASK_ACCEPTANCE_EVIDENCE_KEY]: serializeJsonValue({
1474
+ ...structuredClone(pending),
1475
+ ...await taskAcceptanceOutcome(state, ledger),
1476
+ checks: serializeJsonValue(taskAcceptanceChecks()?.snapshot() ?? []),
1477
+ ...violation ? { status: "failed", diagnostic: serializeJsonValue(violation) } : {}
1478
+ }) };
1479
+ }
1480
+ return target.save(...args);
1481
+ };
1482
+ const value = Reflect.get(target, key, target);
1483
+ return typeof value === "function" ? value.bind(target) : value;
1484
+ } });
1485
+ }
1486
+
1922
1487
  // src/context/task-memory.ts
1923
1488
  var redact2 = createRedactionPolicy3({ includeEmails: true });
1924
1489
  var TASK_SOURCE_KEY = "zhivexTaskSources";
@@ -1938,11 +1503,11 @@ var captureTaskSources = (metadata, messages) => {
1938
1503
  for (const part of message.parts) {
1939
1504
  if (part.type !== "text" || /^\[Compacted (?:conversation context|prior conversation)\]/.test(part.text))
1940
1505
  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")}`;
1506
+ const text2 = 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]");
1507
+ const id = `sha256:${createHash3("sha256").update(text2).digest("hex")}`;
1943
1508
  const original = sources.size === 0 || sources.get(id)?.original === true;
1944
1509
  sources.delete(id);
1945
- sources.set(id, { id, text, ...original ? { original: true } : {} });
1510
+ sources.set(id, { id, text: text2, ...original ? { original: true } : {} });
1946
1511
  }
1947
1512
  return [...sources.values()];
1948
1513
  };
@@ -1951,10 +1516,10 @@ var createTaskTools = () => ({
1951
1516
  name: "read_task",
1952
1517
  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
1518
  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()
1519
+ schema: z8.object({
1520
+ id: z8.string().regex(/^sha256:[a-f0-9]{64}$/).optional(),
1521
+ offset: z8.number().int().min(0).default(0),
1522
+ sourceOffset: z8.number().int().min(0).optional()
1958
1523
  }),
1959
1524
  execute: async ({ id, offset, sourceOffset }, context) => {
1960
1525
  const sources = taskSources(context?.metadata);
@@ -1962,9 +1527,11 @@ var createTaskTools = () => ({
1962
1527
  if (!source)
1963
1528
  throw new Error("Requested task source is not bound to this run.");
1964
1529
  const start = sourceOffset ?? Math.max(0, sources.length - 64);
1530
+ const acceptance = readTaskAcceptanceLedger(context?.metadata ? { metadata: context.metadata } : {})?.revisions.at(-1);
1965
1531
  return {
1966
1532
  id: source.id,
1967
1533
  sourceIds: sources.slice(start, start + 64).map((s) => s.id),
1534
+ ...acceptance ? { acceptance: serializeJsonValue2(acceptance) } : {},
1968
1535
  firstSourceId: (sources.find((item) => item.original) ?? sources[0]).id,
1969
1536
  sourceOffset: start,
1970
1537
  totalSources: sources.length,
@@ -1980,470 +1547,9 @@ var createTaskTools = () => ({
1980
1547
  name: "repair_plan",
1981
1548
  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
1549
  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 })
1550
+ schema: z8.strictObject({ hypothesis: z8.string().min(1).max(1000), expectedBehavior: z8.string().min(1).max(1000), paths: z8.array(z8.string().max(240)).max(8), nextCheck: z8.string().min(1).max(500), verifier: verifierSchema.optional() }),
1551
+ execute: async (input) => serializeJsonValue2({ kind: "repair-plan", ...input, verified: false })
1985
1552
  })
1986
1553
  });
1987
1554
 
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 };
1555
+ export { protectStateFromGit, validateStateDirectory, HARNESS_SESSION_INDEX_FILE, HARNESS_SESSION_SCHEMA_VERSION, SESSION_RUN_STATUSES, TERMINAL_SESSION_RUN_STATUSES, openCliSessionStore, openSessionStore, inspectRuntimeDiagnostics, inspectRuntimeManifest, estimateContextTokens, measureContext, MODEL_BUDGET_KEY, estimateRequestTokens, workBudgetReached, createModelBudget, harnessToolExecution, USAGE_LEDGER_KEY, usagePricingSchema, inspectUsageLedger, formatUsageLedger, UsageLedger, verifierSchema, taskAcceptanceContractSchema, compileTaskAcceptanceContract, TASK_ACCEPTANCE_KEY, TASK_ACCEPTANCE_EVIDENCE_KEY, readTaskAcceptanceLedger, nextTaskAcceptanceLedger, persistTaskAcceptanceRevision, taskAcceptanceCheckpointStore, TASK_SOURCE_KEY, taskSources, captureTaskSources, createTaskTools };