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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (164) hide show
  1. package/CHANGELOG.md +64 -0
  2. package/README.md +9 -10
  3. package/contracts/engine-api.json +2921 -0
  4. package/contracts/public-api.json +267 -1
  5. package/contracts/stable-api-signatures.json +43 -43
  6. package/dist/acp-cli.js +137 -15002
  7. package/dist/approvals/approval-history.d.ts +22 -0
  8. package/dist/approvals/explicit-review.d.ts +24 -0
  9. package/dist/approvals/host-policy-identity.d.ts +3 -0
  10. package/dist/cli/arguments.d.ts +6 -2
  11. package/dist/cli/checkpoints.d.ts +230 -0
  12. package/dist/cli/cli-options.d.ts +2 -41
  13. package/dist/cli/cli-profiles.d.ts +4 -2
  14. package/dist/cli/cli-stream.d.ts +2 -167
  15. package/dist/cli/governance-report.d.ts +5 -0
  16. package/dist/cli/policy-decisions.d.ts +9 -0
  17. package/dist/cli/policy.d.ts +5 -0
  18. package/dist/cli/presentation.d.ts +3 -1
  19. package/dist/cli/run-document.d.ts +2 -91
  20. package/dist/cli/terminal/terminal-continuation.d.ts +2 -3
  21. package/dist/cli/terminal/terminal-ui.d.ts +4 -0
  22. package/dist/{cli-entry-c2f58zj2.js → cli-entry-ae7x44d4.js} +14969 -10731
  23. package/dist/{cli-entry-427869g9.js → cli-entry-d5788aay.js} +388 -47
  24. package/dist/cli-entry-ev76ezcd.js +656 -0
  25. package/dist/{cli-3jmy4h5h.js → cli-t7hrhayq.js} +918 -2223
  26. package/dist/cli.js +3 -5
  27. package/dist/client/acp-mcp-admission.d.ts +32 -0
  28. package/dist/client/acp-mcp-host.d.ts +19 -0
  29. package/dist/client/acp-mcp-session.d.ts +14 -0
  30. package/dist/client/acp-stdio.d.ts +2 -0
  31. package/dist/client/acp.d.ts +4 -1
  32. package/dist/client/adapter.d.ts +13 -1
  33. package/dist/client/continuation.d.ts +3 -0
  34. package/dist/client/json-contracts.d.ts +107 -4
  35. package/dist/client/local-service.d.ts +2 -0
  36. package/dist/client/protocol.d.ts +127 -1
  37. package/dist/client/run-document.d.ts +91 -0
  38. package/dist/client/service-events.d.ts +4 -0
  39. package/dist/client/stream.d.ts +167 -0
  40. package/dist/compat/cli-options.d.ts +53 -0
  41. package/dist/compat/code-support.d.ts +22 -0
  42. package/dist/compat/code-support.js +74 -0
  43. package/dist/{config-1k9t4jhh.js → config-c002f06f.js} +1 -1
  44. package/dist/config-j3jga5wh.js +61 -0
  45. package/dist/context/adaptive-compaction.d.ts +1 -0
  46. package/dist/context/compaction.d.ts +1 -1
  47. package/dist/context/request-measurements.d.ts +17 -0
  48. package/dist/context/request-projection.d.ts +10 -0
  49. package/dist/context/task-memory.d.ts +1 -0
  50. package/dist/engine/acp.d.ts +6 -0
  51. package/dist/engine/acp.js +205 -0
  52. package/dist/engine/client.d.ts +6 -0
  53. package/dist/engine/client.js +28 -0
  54. package/dist/engine/desktop-providers-v1.d.ts +4 -0
  55. package/dist/engine/desktop-providers-v1.js +18 -0
  56. package/dist/engine/desktop-state-v1.d.ts +10 -0
  57. package/dist/engine/desktop-state-v1.js +168 -0
  58. package/dist/engine/index.d.ts +73 -0
  59. package/dist/engine/index.js +977 -0
  60. package/dist/engine/mcp-stdio-v1.d.ts +11 -0
  61. package/dist/engine/mcp-stdio-v1.js +108 -0
  62. package/dist/engine/models.d.ts +3 -0
  63. package/dist/engine/models.js +894 -0
  64. package/dist/engine/protocol.d.ts +7 -0
  65. package/dist/engine/protocol.js +182 -0
  66. package/dist/engine/service.d.ts +5 -0
  67. package/dist/engine/service.js +28 -0
  68. package/dist/execution/acceptance-snapshot.d.ts +2 -0
  69. package/dist/execution/execution-environment.d.ts +10 -0
  70. package/dist/execution/mcp-oci-channel.d.ts +11 -0
  71. package/dist/execution/mcp-oci-server.d.ts +38 -0
  72. package/dist/execution/mcp-oci-workspace.d.ts +23 -0
  73. package/dist/execution/mcp-resource-recovery.d.ts +19 -0
  74. package/dist/execution/mcp-workspace-snapshot.d.ts +20 -0
  75. package/dist/hostile-repository-demo.js +3368 -1304
  76. package/dist/index-1bx83sw4.js +4 -0
  77. package/dist/index-2gz7jv7g.js +549 -0
  78. package/dist/{cli-entry-b1mfvmxe.js → index-2jrdcj6b.js} +72 -115
  79. package/dist/index-3ah1f369.js +356 -0
  80. package/dist/index-42gp2hy8.js +1555 -0
  81. package/dist/index-4jw3tvy8.js +301 -0
  82. package/dist/index-8705zxpr.js +145 -0
  83. package/dist/index-8vm2m34s.js +2080 -0
  84. package/dist/index-8zzhggxv.js +477 -0
  85. package/dist/index-a97bezbz.js +1073 -0
  86. package/dist/index-gq9xagx2.js +1343 -0
  87. package/dist/index-h0mqx2ed.js +1373 -0
  88. package/dist/index-jgrx804e.js +5589 -0
  89. package/dist/index-qfpz5y2v.js +174 -0
  90. package/dist/index-t60rva31.js +1004 -0
  91. package/dist/index-wg1sd3at.js +80 -0
  92. package/dist/index-xqxeypt5.js +888 -0
  93. package/dist/index-yb239ghk.js +961 -0
  94. package/dist/index-z0qxmkm2.js +3389 -0
  95. package/dist/index-z4e8d0ph.js +1768 -0
  96. package/dist/index.js +251 -18649
  97. package/dist/integrations/mcp-host-session.d.ts +26 -0
  98. package/dist/integrations/mcp-stdio-admission.d.ts +54 -0
  99. package/dist/integrations/mcp-stdio-client.d.ts +44 -0
  100. package/dist/integrations/mcp-stdio-framing.d.ts +18 -0
  101. package/dist/integrations/mcp-stdio-launch.d.ts +20 -0
  102. package/dist/integrations/mcp-stdio-session.d.ts +7 -0
  103. package/dist/integrations/mcp-stdio-tools.d.ts +12 -0
  104. package/dist/internal/desktop/catalog.d.ts +2 -0
  105. package/dist/internal/desktop/persistence.d.ts +2 -3
  106. package/dist/internal/desktop/providers.d.ts +1 -0
  107. package/dist/models/catalog.d.ts +2 -0
  108. package/dist/persistence/governance-context.d.ts +126 -0
  109. package/dist/persistence/governance-report.d.ts +299 -0
  110. package/dist/persistence/host-database-backup.d.ts +28 -0
  111. package/dist/persistence/mcp-execution-journal.d.ts +19 -0
  112. package/dist/persistence/mcp-reconciliation.d.ts +33 -0
  113. package/dist/persistence/mcp-resource-journal.d.ts +54 -0
  114. package/dist/persistence/workspace-checkpoints.d.ts +138 -0
  115. package/dist/providers/anthropic-continuation.d.ts +5 -0
  116. package/dist/providers/providers.d.ts +1 -1
  117. package/dist/providers/vertex-auth.d.ts +27 -0
  118. package/dist/runtime/delegation-contracts.d.ts +9 -0
  119. package/dist/runtime/delegation-correction.d.ts +9 -0
  120. package/dist/runtime/delegation-result.d.ts +73 -0
  121. package/dist/runtime/error-diagnostics.d.ts +86 -0
  122. package/dist/runtime/harness.d.ts +11 -1
  123. package/dist/runtime/policy-decisions.d.ts +66 -0
  124. package/dist/runtime/policy-inspection.d.ts +52 -0
  125. package/dist/runtime/provider-diagnostics.d.ts +1 -0
  126. package/dist/runtime/runtime-instructions.d.ts +8 -0
  127. package/dist/runtime/task-acceptance-checks.d.ts +103 -0
  128. package/dist/runtime/task-acceptance-delivery.d.ts +107 -0
  129. package/dist/runtime/task-acceptance-host.d.ts +20 -0
  130. package/dist/runtime/task-acceptance-record.d.ts +107 -0
  131. package/dist/runtime/task-acceptance-validation.d.ts +25 -0
  132. package/dist/runtime/task-acceptance.d.ts +47 -0
  133. package/dist/runtime/time-to-safe-fix.d.ts +83 -0
  134. package/dist/runtime/tool-policy-file.d.ts +23 -0
  135. package/dist/runtime/tool-policy-paths.d.ts +5 -0
  136. package/dist/runtime/tool-policy.d.ts +6 -0
  137. package/dist/service-cli.js +19 -14504
  138. package/dist/{service-client-cli-yx56n0hc.js → service-client-cli-kns4mf57.js} +307 -248
  139. package/dist/verify-historical-migrations.js +3314 -1251
  140. package/dist/workspace/path-policy.d.ts +4 -0
  141. package/dist/zhx.js +3 -5
  142. package/docs/ACP.md +14 -2
  143. package/docs/ANTHROPIC.md +33 -0
  144. package/docs/CLI.md +132 -0
  145. package/docs/CLIENT_PROTOCOL.md +45 -0
  146. package/docs/CODE_RELEASE.md +93 -0
  147. package/docs/ENGINE_API.md +319 -0
  148. package/docs/EXTENSIBILITY.md +46 -3
  149. package/docs/FIRST_USE.md +6 -6
  150. package/docs/LOCAL_SERVICE.md +13 -0
  151. package/docs/MCP_STDIO.md +20 -0
  152. package/docs/README.md +6 -0
  153. package/docs/STABILITY.md +9 -1
  154. package/docs/SUPPORT_MATRIX.md +1 -1
  155. package/docs/TOOL_POLICY.md +81 -0
  156. package/docs/VERTEX.md +50 -0
  157. package/docs/adr/0002-isolated-mcp-stdio.md +135 -0
  158. package/docs/fixtures/mcp-stdio-contract-v1.json +189 -0
  159. package/docs/support-matrix.json +2 -0
  160. package/evaluations/representative-assembly-matrix.json +64 -1
  161. package/evaluations/task-acceptance-routes.json +7 -0
  162. package/examples/README.md +1 -1
  163. package/package.json +74 -12
  164. package/dist/cli-entry-64pdp3k5.js +0 -17
@@ -0,0 +1,1555 @@
1
+ import {
2
+ SqliteDatabase,
3
+ taskAcceptanceChecks,
4
+ taskAcceptanceDeliveryDiagnostic,
5
+ taskAcceptanceOutcome,
6
+ workspaceFilePathSchema
7
+ } from "./index-yb239ghk.js";
8
+ import {
9
+ HarnessConfigError,
10
+ HarnessStateConflictError,
11
+ HarnessWorkspaceError
12
+ } from "./index-gq9xagx2.js";
13
+
14
+ // src/persistence/state-directory.ts
15
+ import { lstat } from "node:fs/promises";
16
+ import path from "node:path";
17
+ var SENSITIVE_STATE_SEGMENTS = new Set([".git", ".env", ".npmrc", "dist", "node_modules", "src"]);
18
+ var isInside = (root, candidate) => {
19
+ const relative = path.relative(root, candidate);
20
+ return relative === "" || !relative.startsWith(`..${path.sep}`) && relative !== ".." && !path.isAbsolute(relative);
21
+ };
22
+ var validateStateDirectory = async (workspace, stateDirectory) => {
23
+ if (stateDirectory === workspace || stateDirectory === path.parse(stateDirectory).root) {
24
+ throw new HarnessWorkspaceError("The state directory cannot be the workspace or filesystem root.");
25
+ }
26
+ const insideWorkspace = isInside(workspace, stateDirectory);
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);
28
+ const sensitiveSegment = insideWorkspace ? relativeSegments.find((segment) => SENSITIVE_STATE_SEGMENTS.has(segment)) : undefined;
29
+ if (sensitiveSegment) {
30
+ throw new HarnessWorkspaceError(`The state directory is inside the protected workspace path: ${sensitiveSegment}.`);
31
+ }
32
+ if (!insideWorkspace) {
33
+ try {
34
+ const externalEntry = await lstat(stateDirectory);
35
+ if (externalEntry.isSymbolicLink()) {
36
+ throw new HarnessWorkspaceError(`The state directory must not be a symbolic link: ${stateDirectory}.`);
37
+ }
38
+ if (!externalEntry.isDirectory()) {
39
+ throw new HarnessWorkspaceError(`The state directory is not a directory: ${stateDirectory}.`);
40
+ }
41
+ } catch (error) {
42
+ if (error.code !== "ENOENT") {
43
+ throw error;
44
+ }
45
+ }
46
+ return;
47
+ }
48
+ let current = workspace;
49
+ for (const segment of relativeSegments) {
50
+ current = path.join(current, segment);
51
+ try {
52
+ const entry = await lstat(current);
53
+ if (entry.isSymbolicLink()) {
54
+ throw new HarnessWorkspaceError(`The state directory must not resolve through a symbolic link: ${current}.`);
55
+ }
56
+ if (!entry.isDirectory()) {
57
+ throw new HarnessWorkspaceError(`The state directory path contains a non-directory entry: ${current}.`);
58
+ }
59
+ } catch (error) {
60
+ if (error.code === "ENOENT") {
61
+ break;
62
+ }
63
+ throw error;
64
+ }
65
+ }
66
+ };
67
+
68
+ // src/persistence/sessions.ts
69
+ import { createHash, randomUUID } from "node:crypto";
70
+ import { chmod, lstat as lstat3, mkdir, open as open2, realpath, stat } from "node:fs/promises";
71
+ import path3 from "node:path";
72
+ import { createRedactionPolicy } from "@zhivex-ai/agents";
73
+
74
+ // src/persistence/state-gitignore.ts
75
+ import { constants } from "node:fs";
76
+ import { lstat as lstat2, open } from "node:fs/promises";
77
+ import path2 from "node:path";
78
+ var exclusion = `# Local Harness state; never commit sessions, runs, or database journals.
79
+ *
80
+ `;
81
+ var maximumExistingBytes = 65536;
82
+ var maximumProtectedBytes = maximumExistingBytes + Buffer.byteLength(exclusion) + 1;
83
+ async function protectStateFromGit(workspace, stateDirectory) {
84
+ const directory = path2.resolve(stateDirectory) === path2.join(workspace, ".zhivex-harness", "runs") ? path2.dirname(stateDirectory) : stateDirectory;
85
+ const filename = path2.join(directory, ".gitignore");
86
+ try {
87
+ const handle = await open(filename, constants.O_RDWR | constants.O_CREAT | constants.O_APPEND | constants.O_NOFOLLOW, 384);
88
+ try {
89
+ const info = await handle.stat();
90
+ const entry = await lstat2(filename);
91
+ if (entry.isSymbolicLink() || entry.dev !== info.dev || entry.ino !== info.ino)
92
+ throw new Error;
93
+ if (!info.isFile() || info.nlink !== 1 || process.getuid && info.uid !== process.getuid() || info.size > maximumProtectedBytes)
94
+ throw new Error;
95
+ const buffer = Buffer.alloc(maximumProtectedBytes + 1);
96
+ const { bytesRead } = await handle.read(buffer, 0, buffer.length, 0);
97
+ if (bytesRead > maximumProtectedBytes)
98
+ throw new Error;
99
+ const content = buffer.subarray(0, bytesRead).toString("utf8");
100
+ if (!content.endsWith(exclusion)) {
101
+ if (bytesRead > maximumExistingBytes)
102
+ throw new Error;
103
+ await handle.writeFile(`${content && !content.endsWith(`
104
+ `) ? `
105
+ ` : ""}${exclusion}`);
106
+ }
107
+ const current = await lstat2(filename);
108
+ if (current.isSymbolicLink() || current.dev !== info.dev || current.ino !== info.ino)
109
+ throw new Error;
110
+ } finally {
111
+ await handle.close();
112
+ }
113
+ } catch (cause) {
114
+ throw new HarnessWorkspaceError("Cannot protect the local state directory with a safe .gitignore.", { cause });
115
+ }
116
+ }
117
+
118
+ // src/persistence/sessions.ts
119
+ var HARNESS_SESSION_INDEX_FILE = "operations.sqlite";
120
+ var HARNESS_SESSION_SCHEMA_VERSION = 1;
121
+ var SESSION_RUN_STATUSES = [
122
+ "created",
123
+ "running",
124
+ "waiting_approval",
125
+ "cancel_requested",
126
+ "completed",
127
+ "failed",
128
+ "cancelled",
129
+ "timed_out"
130
+ ];
131
+ var TERMINAL_SESSION_RUN_STATUSES = [
132
+ "completed",
133
+ "failed",
134
+ "cancelled",
135
+ "timed_out"
136
+ ];
137
+ var DEFAULT_RETENTION_MS = 90 * 24 * 60 * 60 * 1000;
138
+ var DEFAULT_MAX_SESSIONS = 200;
139
+ var DEFAULT_MAX_RUNS = 1000;
140
+ var DEFAULT_MAX_METADATA_BYTES = 4096;
141
+ var DEFAULT_MAX_INDEX_BYTES = 16 * 1024 * 1024;
142
+ var MAX_LIST_LIMIT = 200;
143
+ var ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:@/+\-]{0,255}$/;
144
+ var TERMINAL_STATUSES = new Set(TERMINAL_SESSION_RUN_STATUSES);
145
+ var ACTIVE_STATUSES = new Set([
146
+ "created",
147
+ "running",
148
+ "waiting_approval",
149
+ "cancel_requested"
150
+ ]);
151
+ var STATUS_TRANSITIONS = {
152
+ created: new Set(["created", "running", "waiting_approval", "completed", "failed", "cancelled", "timed_out"]),
153
+ running: new Set(["running", "waiting_approval", "cancel_requested", "completed", "failed", "cancelled", "timed_out"]),
154
+ waiting_approval: new Set(["waiting_approval", "running", "cancel_requested", "completed", "failed", "cancelled", "timed_out"]),
155
+ cancel_requested: new Set(["cancel_requested", "cancelled", "failed", "timed_out"]),
156
+ completed: new Set(["completed"]),
157
+ failed: new Set(["failed"]),
158
+ cancelled: new Set(["cancelled"]),
159
+ timed_out: new Set(["timed_out"])
160
+ };
161
+ var metadataRedaction = createRedactionPolicy({
162
+ includeEmails: true,
163
+ rules: [
164
+ { name: "private-key", pattern: /-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/g },
165
+ { name: "common-provider-key", pattern: /\b(?:sk|ghp|github_pat|xox[baprs])[-_][A-Za-z0-9_-]{8,}\b/gi },
166
+ { name: "url-credentials", pattern: /:\/\/[^\s/@:]+:[^\s/@]+@/g, replacement: "://[REDACTED]@" }
167
+ ]
168
+ });
169
+ var byteLength = (value) => Buffer.byteLength(value, "utf8");
170
+ var boundedInteger = (name, value, fallback, minimum) => {
171
+ const resolved = value ?? fallback;
172
+ if (!Number.isSafeInteger(resolved) || resolved < minimum) {
173
+ throw new HarnessConfigError(`${name} must be an integer greater than or equal to ${minimum}.`);
174
+ }
175
+ return resolved;
176
+ };
177
+ var assertTimestamp = (name, value) => {
178
+ if (!Number.isSafeInteger(value) || value < 0) {
179
+ throw new HarnessConfigError(`${name} must be a non-negative integer timestamp.`);
180
+ }
181
+ };
182
+ var assertIdentifier = (name, value) => {
183
+ if (!ID_PATTERN.test(value)) {
184
+ throw new HarnessConfigError(`${name} contains unsupported characters or exceeds 256 bytes.`);
185
+ }
186
+ if (metadataRedaction.redactText(value) !== value) {
187
+ throw new HarnessConfigError(`${name} resembles sensitive metadata and cannot be stored in the session index.`);
188
+ }
189
+ return value;
190
+ };
191
+ var assertScopeSegment = (name, value) => {
192
+ const normalized = value.trim();
193
+ if (!normalized || normalized.length > 128 || /[\u0000-\u001f\u007f]/.test(normalized)) {
194
+ throw new HarnessConfigError(`${name} must contain 1-128 printable characters.`);
195
+ }
196
+ return normalized;
197
+ };
198
+ var normalizeTitle = (value, maxMetadataBytes) => {
199
+ if (value === undefined || value === null) {
200
+ return value;
201
+ }
202
+ const normalized = metadataRedaction.redactText(value).replace(/[\u0000-\u001f\u007f]+/g, " ").trim();
203
+ if (!normalized) {
204
+ throw new HarnessConfigError("Session title must not be empty.");
205
+ }
206
+ if (byteLength(normalized) > maxMetadataBytes) {
207
+ throw new HarnessConfigError(`Session title exceeds the ${maxMetadataBytes}-byte metadata limit.`);
208
+ }
209
+ return normalized;
210
+ };
211
+ var normalizeScope = (scope) => {
212
+ const tenantId = assertScopeSegment("scope.tenantId", scope.tenantId);
213
+ const userId = scope.userId === undefined ? "" : assertScopeSegment("scope.userId", scope.userId);
214
+ const namespace = scope.namespace === undefined ? "" : assertScopeSegment("scope.namespace", scope.namespace);
215
+ return `${tenantId}\x00${userId}\x00${namespace}`;
216
+ };
217
+ var stableKey = (kind, value) => createHash("sha256").update(kind).update("\x00").update(value).digest("hex");
218
+ var sessionId = () => `ses_${randomUUID()}`;
219
+ var turnId = () => `turn_${randomUUID()}`;
220
+ var ensurePrivateDatabase = async (workspace, stateDirectory) => {
221
+ const requestedStateDirectory = path3.resolve(stateDirectory);
222
+ await validateStateDirectory(workspace, requestedStateDirectory);
223
+ await mkdir(requestedStateDirectory, { recursive: true, mode: 448 });
224
+ const directoryEntry = await lstat3(requestedStateDirectory);
225
+ if (directoryEntry.isSymbolicLink() || !directoryEntry.isDirectory()) {
226
+ throw new HarnessWorkspaceError(`The session state directory must be a real directory: ${requestedStateDirectory}.`);
227
+ }
228
+ await chmod(requestedStateDirectory, 448);
229
+ const canonicalStateDirectory = await realpath(requestedStateDirectory);
230
+ await protectStateFromGit(workspace, canonicalStateDirectory);
231
+ const databasePath = path3.join(canonicalStateDirectory, HARNESS_SESSION_INDEX_FILE);
232
+ let databaseEntry;
233
+ try {
234
+ const handle = await open2(databasePath, "wx", 384);
235
+ await handle.close();
236
+ databaseEntry = await lstat3(databasePath);
237
+ } catch (error) {
238
+ if (error.code !== "EEXIST")
239
+ throw error;
240
+ databaseEntry = await lstat3(databasePath);
241
+ }
242
+ if (databaseEntry.isSymbolicLink() || !databaseEntry.isFile()) {
243
+ throw new HarnessWorkspaceError(`The session SQLite path must be a real file: ${databasePath}.`);
244
+ }
245
+ await chmod(databasePath, 384);
246
+ return { databasePath, databaseEntry };
247
+ };
248
+ var toRunReference = (row) => ({
249
+ turnId: row.turn_id,
250
+ ...row.source_turn_id ? { sourceTurnId: row.source_turn_id } : {},
251
+ sequence: row.ordinal,
252
+ runId: row.run_id,
253
+ provider: row.provider,
254
+ model: row.model,
255
+ ...row.role ? { role: row.role } : {},
256
+ status: row.status,
257
+ createdAt: row.created_at,
258
+ updatedAt: row.updated_at,
259
+ ...row.completed_at === null ? {} : { completedAt: row.completed_at }
260
+ });
261
+ var metadataSize = (input) => Object.values(input).reduce((total, value) => total + (typeof value === "string" ? byteLength(value) : 0), 0);
262
+ var isTerminal = (status) => TERMINAL_STATUSES.has(status);
263
+ var openCliSessionStore = async (options) => {
264
+ const workspace = await realpath(path3.resolve(options.workspace));
265
+ const workspaceEntry = await stat(workspace);
266
+ if (!workspaceEntry.isDirectory()) {
267
+ throw new HarnessWorkspaceError(`The session workspace is not a directory: ${workspace}.`);
268
+ }
269
+ const scopeValue = normalizeScope(options.scope);
270
+ const workspaceKey = stableKey("workspace", workspace);
271
+ const scopeKey = stableKey("scope", scopeValue);
272
+ const retentionMs = boundedInteger("retentionMs", options.retentionMs, DEFAULT_RETENTION_MS, 0);
273
+ const maxSessions = boundedInteger("maxSessionsPerWorkspace", options.maxSessionsPerWorkspace, DEFAULT_MAX_SESSIONS, 1);
274
+ const maxRuns = boundedInteger("maxRunsPerSession", options.maxRunsPerSession, DEFAULT_MAX_RUNS, 1);
275
+ const maxMetadataBytes = boundedInteger("maxMetadataBytes", options.maxMetadataBytes, DEFAULT_MAX_METADATA_BYTES, 32);
276
+ const maxIndexBytes = boundedInteger("maxIndexBytes", options.maxIndexBytes, DEFAULT_MAX_INDEX_BYTES, 1024);
277
+ const busyTimeoutMs = boundedInteger("busyTimeoutMs", options.busyTimeoutMs, 5000, 0);
278
+ if (busyTimeoutMs > 5000)
279
+ throw new HarnessConfigError("busyTimeoutMs cannot exceed 5000.");
280
+ const now = options.now ?? Date.now;
281
+ const { databasePath, databaseEntry } = await ensurePrivateDatabase(workspace, options.stateDirectory);
282
+ const database = new SqliteDatabase(databasePath, { create: false, strict: true });
283
+ let closed = false;
284
+ const assertOpen = () => {
285
+ if (closed) {
286
+ throw new HarnessStateConflictError("The CLI session store is closed.");
287
+ }
288
+ };
289
+ const transaction = (operation) => {
290
+ assertOpen();
291
+ database.exec("BEGIN IMMEDIATE");
292
+ try {
293
+ const result = operation();
294
+ const size = database.query(`
295
+ SELECT
296
+ COALESCE((SELECT SUM(metadata_bytes) FROM zhivex_cli_sessions WHERE workspace_key = ?1 AND scope_key = ?2), 0) +
297
+ 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)
298
+ AS size
299
+ `).get(workspaceKey, scopeKey)?.size ?? 0;
300
+ if (size > maxIndexBytes) {
301
+ throw new HarnessStateConflictError(`Session index metadata exceeds the ${maxIndexBytes}-byte limit.`);
302
+ }
303
+ database.exec("COMMIT");
304
+ return result;
305
+ } catch (error) {
306
+ try {
307
+ database.exec("ROLLBACK");
308
+ } catch {}
309
+ throw error;
310
+ }
311
+ };
312
+ const ensureRevision = (row, expected) => {
313
+ if (expected !== undefined && row.revision !== expected) {
314
+ throw new HarnessStateConflictError(`Session ${row.session_id} changed concurrently (expected revision ${expected}, found ${row.revision}).`, { retryable: true });
315
+ }
316
+ };
317
+ const selectSessionRow = (candidateSessionId, includeDeleted = false) => {
318
+ assertIdentifier("sessionId", candidateSessionId);
319
+ return database.query(`
320
+ SELECT session_id, title, parent_session_id, forked_from_turn_id, revision, activity_seq,
321
+ created_at, updated_at, archived_at, deleted_at
322
+ FROM zhivex_cli_sessions
323
+ WHERE session_id = ?1 AND workspace_key = ?2 AND scope_key = ?3
324
+ ${includeDeleted ? "" : "AND deleted_at IS NULL"}
325
+ `).get(candidateSessionId, workspaceKey, scopeKey);
326
+ };
327
+ const requireSessionRow = (candidateSessionId, includeDeleted = false) => {
328
+ const row = selectSessionRow(candidateSessionId, includeDeleted);
329
+ if (!row) {
330
+ throw new HarnessStateConflictError(`Session ${candidateSessionId} was not found in this workspace and scope.`);
331
+ }
332
+ return row;
333
+ };
334
+ const selectRuns = (candidateSessionId) => database.query(`
335
+ SELECT turn_id, source_turn_id, ordinal, run_id, provider, model, role, status,
336
+ created_at, updated_at, completed_at
337
+ FROM zhivex_cli_session_runs
338
+ WHERE session_id = ?1
339
+ ORDER BY ordinal ASC
340
+ `).all(candidateSessionId).map(toRunReference);
341
+ const nextActivitySequence = () => database.query(`
342
+ SELECT COALESCE(MAX(activity_seq), 0) + 1 AS count
343
+ FROM zhivex_cli_sessions
344
+ WHERE workspace_key = ?1 AND scope_key = ?2
345
+ `).get(workspaceKey, scopeKey)?.count ?? 1;
346
+ const sessionBase = (row) => ({
347
+ schemaVersion: HARNESS_SESSION_SCHEMA_VERSION,
348
+ sessionId: row.session_id,
349
+ workspaceKey,
350
+ scopeKey,
351
+ ...row.title ? { title: row.title } : {},
352
+ ...row.parent_session_id ? { parentSessionId: row.parent_session_id } : {},
353
+ ...row.forked_from_turn_id ? { forkedFromTurnId: row.forked_from_turn_id } : {},
354
+ revision: row.revision,
355
+ createdAt: row.created_at,
356
+ updatedAt: row.updated_at,
357
+ ...row.archived_at === null ? {} : { archivedAt: row.archived_at },
358
+ ...row.deleted_at === null ? {} : { deletedAt: row.deleted_at }
359
+ });
360
+ const hydrate = (row) => ({
361
+ ...sessionBase(row),
362
+ runs: selectRuns(row.session_id)
363
+ });
364
+ const latestRunRow = (candidateSessionId) => database.query(`
365
+ SELECT turn_id, source_turn_id, ordinal, run_id, provider, model, role, status,
366
+ created_at, updated_at, completed_at
367
+ FROM zhivex_cli_session_runs
368
+ WHERE session_id = ?1
369
+ ORDER BY ordinal DESC
370
+ LIMIT 1
371
+ `).get(candidateSessionId);
372
+ const assertNoActiveRun = (candidateSessionId, action) => {
373
+ const latest = latestRunRow(candidateSessionId);
374
+ if (latest && ACTIVE_STATUSES.has(latest.status)) {
375
+ throw new HarnessStateConflictError(`Cannot ${action} session ${candidateSessionId} while run ${latest.run_id} is ${latest.status}.`);
376
+ }
377
+ };
378
+ const insertRun = (candidateSessionId, input, sequence, sourceTurnId) => {
379
+ const runId = assertIdentifier("runId", input.runId);
380
+ const provider = assertIdentifier("provider", input.provider);
381
+ const model = assertIdentifier("model", input.model);
382
+ const role = input.role === undefined ? null : assertIdentifier("role", input.role);
383
+ const status = input.status ?? "created";
384
+ if (!SESSION_RUN_STATUSES.includes(status)) {
385
+ throw new HarnessConfigError(`Unsupported session run status: ${String(status)}.`);
386
+ }
387
+ const createdAt = input.createdAt ?? now();
388
+ assertTimestamp("createdAt", createdAt);
389
+ const entryBytes = metadataSize({ runId, provider, model, role });
390
+ if (entryBytes > maxMetadataBytes) {
391
+ throw new HarnessConfigError(`Run reference metadata exceeds the ${maxMetadataBytes}-byte limit.`);
392
+ }
393
+ const candidateTurnId = turnId();
394
+ database.query(`
395
+ INSERT INTO zhivex_cli_session_runs (
396
+ turn_id, source_turn_id, session_id, ordinal, run_id, provider, model, role,
397
+ status, created_at, updated_at, completed_at, metadata_bytes
398
+ ) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?10, ?11, ?12)
399
+ `).run(candidateTurnId, sourceTurnId ?? null, candidateSessionId, sequence, runId, provider, model, role, status, createdAt, isTerminal(status) ? createdAt : null, entryBytes);
400
+ };
401
+ try {
402
+ const openedEntry = await lstat3(databasePath);
403
+ if (openedEntry.isSymbolicLink() || !openedEntry.isFile() || openedEntry.dev !== databaseEntry.dev || openedEntry.ino !== databaseEntry.ino) {
404
+ throw new HarnessWorkspaceError(`The session SQLite path changed while it was being opened: ${databasePath}.`);
405
+ }
406
+ database.exec(`PRAGMA busy_timeout = ${busyTimeoutMs}`);
407
+ database.exec("PRAGMA journal_mode = WAL");
408
+ database.exec("PRAGMA foreign_keys = ON");
409
+ database.exec(`
410
+ CREATE TABLE IF NOT EXISTS zhivex_cli_session_schema (
411
+ singleton INTEGER PRIMARY KEY CHECK (singleton = 1),
412
+ version INTEGER NOT NULL
413
+ );
414
+ INSERT OR IGNORE INTO zhivex_cli_session_schema (singleton, version)
415
+ VALUES (1, ${HARNESS_SESSION_SCHEMA_VERSION});
416
+ CREATE TABLE IF NOT EXISTS zhivex_cli_sessions (
417
+ session_id TEXT PRIMARY KEY,
418
+ workspace_key TEXT NOT NULL,
419
+ scope_key TEXT NOT NULL,
420
+ title TEXT,
421
+ parent_session_id TEXT,
422
+ forked_from_turn_id TEXT,
423
+ revision INTEGER NOT NULL DEFAULT 0,
424
+ activity_seq INTEGER NOT NULL,
425
+ created_at INTEGER NOT NULL,
426
+ updated_at INTEGER NOT NULL,
427
+ archived_at INTEGER,
428
+ deleted_at INTEGER,
429
+ metadata_bytes INTEGER NOT NULL,
430
+ CHECK (length(session_id) <= 260),
431
+ CHECK (metadata_bytes >= 0)
432
+ );
433
+ CREATE INDEX IF NOT EXISTS zhivex_cli_sessions_latest
434
+ ON zhivex_cli_sessions (workspace_key, scope_key, deleted_at, archived_at, activity_seq DESC);
435
+ CREATE TABLE IF NOT EXISTS zhivex_cli_session_runs (
436
+ turn_id TEXT PRIMARY KEY,
437
+ source_turn_id TEXT,
438
+ session_id TEXT NOT NULL REFERENCES zhivex_cli_sessions(session_id) ON DELETE CASCADE,
439
+ ordinal INTEGER NOT NULL,
440
+ run_id TEXT NOT NULL,
441
+ provider TEXT NOT NULL,
442
+ model TEXT NOT NULL,
443
+ role TEXT,
444
+ status TEXT NOT NULL CHECK (status IN ('created', 'running', 'waiting_approval', 'cancel_requested', 'completed', 'failed', 'cancelled', 'timed_out')),
445
+ created_at INTEGER NOT NULL,
446
+ updated_at INTEGER NOT NULL,
447
+ completed_at INTEGER,
448
+ metadata_bytes INTEGER NOT NULL,
449
+ UNIQUE (session_id, ordinal),
450
+ UNIQUE (session_id, run_id),
451
+ CHECK (ordinal >= 0),
452
+ CHECK (metadata_bytes >= 0)
453
+ );
454
+ CREATE INDEX IF NOT EXISTS zhivex_cli_session_runs_latest
455
+ ON zhivex_cli_session_runs (session_id, ordinal DESC);
456
+ `);
457
+ const schemaVersion = database.query("SELECT version FROM zhivex_cli_session_schema WHERE singleton = 1").get()?.version;
458
+ if (schemaVersion !== HARNESS_SESSION_SCHEMA_VERSION) {
459
+ throw new HarnessStateConflictError(`Unsupported CLI session schema version: ${String(schemaVersion)}.`);
460
+ }
461
+ await chmod(databasePath, 384);
462
+ } catch (error) {
463
+ database.close(false);
464
+ throw error;
465
+ }
466
+ const prune = async (input = {}) => transaction(() => {
467
+ const before = input.before ?? Math.max(0, now() - retentionMs);
468
+ assertTimestamp("before", before);
469
+ const deletedSessions = database.query(`
470
+ SELECT COUNT(*) AS count FROM zhivex_cli_sessions
471
+ WHERE workspace_key = ?1 AND scope_key = ?2
472
+ AND updated_at < ?3
473
+ AND (archived_at IS NOT NULL OR deleted_at IS NOT NULL)
474
+ `).get(workspaceKey, scopeKey, before)?.count ?? 0;
475
+ database.query(`
476
+ DELETE FROM zhivex_cli_sessions
477
+ WHERE workspace_key = ?1 AND scope_key = ?2
478
+ AND updated_at < ?3
479
+ AND (archived_at IS NOT NULL OR deleted_at IS NOT NULL)
480
+ `).run(workspaceKey, scopeKey, before);
481
+ return { before, deletedSessions };
482
+ });
483
+ const store = {
484
+ databasePath,
485
+ workspaceKey,
486
+ scopeKey,
487
+ async create(input = {}) {
488
+ return transaction(() => {
489
+ const cutoff = Math.max(0, now() - retentionMs);
490
+ database.query(`
491
+ DELETE FROM zhivex_cli_sessions
492
+ WHERE workspace_key = ?1 AND scope_key = ?2 AND updated_at < ?3
493
+ AND (archived_at IS NOT NULL OR deleted_at IS NOT NULL)
494
+ `).run(workspaceKey, scopeKey, cutoff);
495
+ const count = database.query(`
496
+ SELECT COUNT(*) AS count FROM zhivex_cli_sessions
497
+ WHERE workspace_key = ?1 AND scope_key = ?2
498
+ `).get(workspaceKey, scopeKey)?.count ?? 0;
499
+ if (count >= maxSessions) {
500
+ throw new HarnessStateConflictError(`Session limit reached for this workspace and scope (${maxSessions}). Archive and prune an older session.`);
501
+ }
502
+ const createdAt = now();
503
+ assertTimestamp("now", createdAt);
504
+ const title = normalizeTitle(input.title, maxMetadataBytes) ?? null;
505
+ const candidateSessionId = sessionId();
506
+ const activitySequence = nextActivitySequence();
507
+ database.query(`
508
+ INSERT INTO zhivex_cli_sessions (
509
+ session_id, workspace_key, scope_key, title, revision, created_at,
510
+ updated_at, activity_seq, metadata_bytes
511
+ ) VALUES (?1, ?2, ?3, ?4, 0, ?5, ?5, ?6, ?7)
512
+ `).run(candidateSessionId, workspaceKey, scopeKey, title, createdAt, activitySequence, metadataSize({ title }));
513
+ if (input.initialRun) {
514
+ insertRun(candidateSessionId, input.initialRun, 0);
515
+ }
516
+ return hydrate(requireSessionRow(candidateSessionId));
517
+ });
518
+ },
519
+ async get(candidateSessionId, input = {}) {
520
+ assertOpen();
521
+ const row = selectSessionRow(candidateSessionId, input.includeDeleted ?? false);
522
+ return row ? hydrate(row) : undefined;
523
+ },
524
+ async list(query = {}) {
525
+ assertOpen();
526
+ const limit = boundedInteger("limit", query.limit, 50, 1);
527
+ if (limit > MAX_LIST_LIMIT) {
528
+ throw new HarnessConfigError(`limit cannot exceed ${MAX_LIST_LIMIT}.`);
529
+ }
530
+ const search = query.search?.trim() ?? "";
531
+ if (search.length > 256)
532
+ throw new HarnessConfigError("Session search exceeds 256 characters.");
533
+ const rows = database.query(`
534
+ SELECT session_id, title, parent_session_id, forked_from_turn_id, revision, activity_seq,
535
+ created_at, updated_at, archived_at, deleted_at
536
+ FROM zhivex_cli_sessions
537
+ WHERE workspace_key = ?1 AND scope_key = ?2
538
+ AND (?4 = '' OR instr(lower(COALESCE(title, '')), lower(?4)) > 0 OR instr(lower(session_id), lower(?4)) > 0)
539
+ ${query.includeArchived ? "" : "AND archived_at IS NULL"}
540
+ ${query.includeDeleted ? "" : "AND deleted_at IS NULL"}
541
+ ORDER BY activity_seq DESC
542
+ LIMIT ?3
543
+ `).all(workspaceKey, scopeKey, limit, search);
544
+ return rows.map((row) => {
545
+ const latest = latestRunRow(row.session_id);
546
+ const runCount = database.query("SELECT COUNT(*) AS count FROM zhivex_cli_session_runs WHERE session_id = ?1").get(row.session_id)?.count ?? 0;
547
+ return {
548
+ ...sessionBase(row),
549
+ runCount,
550
+ ...latest ? { latestRun: toRunReference(latest) } : {}
551
+ };
552
+ });
553
+ },
554
+ async latest(input = {}) {
555
+ assertOpen();
556
+ const row = database.query(`
557
+ SELECT session_id, title, parent_session_id, forked_from_turn_id, revision, activity_seq,
558
+ created_at, updated_at, archived_at, deleted_at
559
+ FROM zhivex_cli_sessions
560
+ WHERE workspace_key = ?1 AND scope_key = ?2 AND deleted_at IS NULL
561
+ ${input.includeArchived ? "" : "AND archived_at IS NULL"}
562
+ ORDER BY activity_seq DESC
563
+ LIMIT 1
564
+ `).get(workspaceKey, scopeKey);
565
+ return row ? hydrate(row) : undefined;
566
+ },
567
+ async update(candidateSessionId, input) {
568
+ return transaction(() => {
569
+ const row = requireSessionRow(candidateSessionId);
570
+ ensureRevision(row, input.expectedRevision);
571
+ if (input.archived === true) {
572
+ assertNoActiveRun(candidateSessionId, "archive");
573
+ }
574
+ const title = input.title === undefined ? row.title : normalizeTitle(input.title, maxMetadataBytes) ?? null;
575
+ const updatedAt = now();
576
+ assertTimestamp("now", updatedAt);
577
+ const archivedAt = input.archived === undefined ? row.archived_at : input.archived ? updatedAt : null;
578
+ const activitySequence = nextActivitySequence();
579
+ database.query(`
580
+ UPDATE zhivex_cli_sessions
581
+ SET title = ?1, archived_at = ?2, updated_at = ?3, revision = revision + 1,
582
+ metadata_bytes = ?4, activity_seq = ?5
583
+ WHERE session_id = ?6 AND workspace_key = ?7 AND scope_key = ?8 AND revision = ?9
584
+ `).run(title, archivedAt, updatedAt, metadataSize({ title }), activitySequence, candidateSessionId, workspaceKey, scopeKey, row.revision);
585
+ return hydrate(requireSessionRow(candidateSessionId));
586
+ });
587
+ },
588
+ async rename(candidateSessionId, title, input = {}) {
589
+ return store.update(candidateSessionId, {
590
+ title,
591
+ ...input.expectedRevision === undefined ? {} : { expectedRevision: input.expectedRevision }
592
+ });
593
+ },
594
+ async appendRun(candidateSessionId, input, appendOptions = {}) {
595
+ return transaction(() => {
596
+ const row = requireSessionRow(candidateSessionId);
597
+ ensureRevision(row, appendOptions.expectedRevision);
598
+ if (row.archived_at !== null) {
599
+ throw new HarnessStateConflictError(`Cannot append a run to archived session ${candidateSessionId}.`);
600
+ }
601
+ const existing = database.query(`
602
+ SELECT turn_id, source_turn_id, ordinal, run_id, provider, model, role, status,
603
+ created_at, updated_at, completed_at
604
+ FROM zhivex_cli_session_runs WHERE session_id = ?1 AND run_id = ?2
605
+ `).get(candidateSessionId, input.runId);
606
+ if (existing) {
607
+ const requestedStatus = input.status ?? "created";
608
+ if (existing.provider === input.provider && existing.model === input.model && (existing.role ?? undefined) === input.role && existing.status === requestedStatus) {
609
+ return hydrate(row);
610
+ }
611
+ throw new HarnessStateConflictError(`Run ${input.runId} is already immutably bound to ${existing.provider}/${existing.model} in this session.`);
612
+ }
613
+ assertNoActiveRun(candidateSessionId, "start another run in");
614
+ const count = database.query("SELECT COUNT(*) AS count FROM zhivex_cli_session_runs WHERE session_id = ?1").get(candidateSessionId)?.count ?? 0;
615
+ if (count >= maxRuns) {
616
+ throw new HarnessStateConflictError(`Run-reference limit reached for session ${candidateSessionId} (${maxRuns}).`);
617
+ }
618
+ insertRun(candidateSessionId, input, count);
619
+ const updatedAt = now();
620
+ assertTimestamp("now", updatedAt);
621
+ const activitySequence = nextActivitySequence();
622
+ database.query(`
623
+ UPDATE zhivex_cli_sessions SET updated_at = ?1, activity_seq = ?2, revision = revision + 1
624
+ WHERE session_id = ?3 AND revision = ?4
625
+ `).run(updatedAt, activitySequence, candidateSessionId, row.revision);
626
+ return hydrate(requireSessionRow(candidateSessionId));
627
+ });
628
+ },
629
+ async updateRun(candidateSessionId, runId, input) {
630
+ return transaction(() => {
631
+ const session = requireSessionRow(candidateSessionId);
632
+ ensureRevision(session, input.expectedSessionRevision);
633
+ assertIdentifier("runId", runId);
634
+ const run = database.query(`
635
+ SELECT turn_id, source_turn_id, ordinal, run_id, provider, model, role, status,
636
+ created_at, updated_at, completed_at
637
+ FROM zhivex_cli_session_runs WHERE session_id = ?1 AND run_id = ?2
638
+ `).get(candidateSessionId, runId);
639
+ if (!run) {
640
+ throw new HarnessStateConflictError(`Run ${runId} is not linked to session ${candidateSessionId}.`);
641
+ }
642
+ if (!STATUS_TRANSITIONS[run.status].has(input.status)) {
643
+ throw new HarnessStateConflictError(`Run ${runId} cannot transition from ${run.status} to ${input.status}.`);
644
+ }
645
+ const updatedAt = input.updatedAt ?? now();
646
+ assertTimestamp("updatedAt", updatedAt);
647
+ if (updatedAt < run.updated_at) {
648
+ throw new HarnessStateConflictError(`Run ${runId} update timestamp cannot move backwards.`);
649
+ }
650
+ const completedAt = isTerminal(input.status) ? input.completedAt ?? updatedAt : null;
651
+ if (completedAt !== null) {
652
+ assertTimestamp("completedAt", completedAt);
653
+ if (completedAt < run.created_at || completedAt > updatedAt) {
654
+ throw new HarnessConfigError("completedAt must be between the run creation and update timestamps.");
655
+ }
656
+ }
657
+ database.query(`
658
+ UPDATE zhivex_cli_session_runs
659
+ SET status = ?1, updated_at = ?2, completed_at = ?3
660
+ WHERE session_id = ?4 AND run_id = ?5
661
+ `).run(input.status, updatedAt, completedAt, candidateSessionId, runId);
662
+ const activitySequence = nextActivitySequence();
663
+ database.query(`
664
+ UPDATE zhivex_cli_sessions SET updated_at = ?1, activity_seq = ?2, revision = revision + 1
665
+ WHERE session_id = ?3 AND revision = ?4
666
+ `).run(updatedAt, activitySequence, candidateSessionId, session.revision);
667
+ return hydrate(requireSessionRow(candidateSessionId));
668
+ });
669
+ },
670
+ async fork(candidateSessionId, input = {}) {
671
+ return transaction(() => {
672
+ const source = requireSessionRow(candidateSessionId);
673
+ ensureRevision(source, input.expectedRevision);
674
+ const sourceRuns = selectRuns(candidateSessionId);
675
+ let forkRuns = sourceRuns;
676
+ let forkedFromTurnId;
677
+ if (input.atTurnId) {
678
+ const index = sourceRuns.findIndex((run) => run.turnId === input.atTurnId);
679
+ if (index < 0) {
680
+ throw new HarnessStateConflictError(`Turn ${input.atTurnId} is not part of session ${candidateSessionId}.`);
681
+ }
682
+ forkRuns = sourceRuns.slice(0, index + 1);
683
+ forkedFromTurnId = input.atTurnId;
684
+ } else if (sourceRuns.length > 0) {
685
+ forkedFromTurnId = sourceRuns.at(-1).turnId;
686
+ }
687
+ const branchPoint = forkRuns.at(-1);
688
+ if (branchPoint && !isTerminal(branchPoint.status)) {
689
+ throw new HarnessStateConflictError(`Cannot fork from non-terminal run ${branchPoint.runId} (${branchPoint.status}).`);
690
+ }
691
+ if (forkRuns.length > maxRuns) {
692
+ throw new HarnessStateConflictError(`Fork history exceeds the ${maxRuns}-run limit.`);
693
+ }
694
+ const count = database.query(`
695
+ SELECT COUNT(*) AS count FROM zhivex_cli_sessions
696
+ WHERE workspace_key = ?1 AND scope_key = ?2
697
+ `).get(workspaceKey, scopeKey)?.count ?? 0;
698
+ if (count >= maxSessions) {
699
+ throw new HarnessStateConflictError(`Session limit reached for this workspace and scope (${maxSessions}).`);
700
+ }
701
+ const createdAt = now();
702
+ assertTimestamp("now", createdAt);
703
+ const title = normalizeTitle(input.title, maxMetadataBytes) ?? source.title;
704
+ const forkSessionId = sessionId();
705
+ const activitySequence = nextActivitySequence();
706
+ database.query(`
707
+ INSERT INTO zhivex_cli_sessions (
708
+ session_id, workspace_key, scope_key, title, parent_session_id,
709
+ forked_from_turn_id, revision, created_at, updated_at, activity_seq, metadata_bytes
710
+ ) VALUES (?1, ?2, ?3, ?4, ?5, ?6, 0, ?7, ?7, ?8, ?9)
711
+ `).run(forkSessionId, workspaceKey, scopeKey, title, candidateSessionId, forkedFromTurnId ?? null, createdAt, activitySequence, metadataSize({ title }));
712
+ for (const [index, run] of forkRuns.entries()) {
713
+ insertRun(forkSessionId, {
714
+ runId: run.runId,
715
+ provider: run.provider,
716
+ model: run.model,
717
+ ...run.role ? { role: run.role } : {},
718
+ status: run.status,
719
+ createdAt: run.createdAt
720
+ }, index, run.turnId);
721
+ database.query(`
722
+ UPDATE zhivex_cli_session_runs
723
+ SET updated_at = ?1, completed_at = ?2
724
+ WHERE session_id = ?3 AND ordinal = ?4
725
+ `).run(run.updatedAt, run.completedAt ?? null, forkSessionId, index);
726
+ }
727
+ return hydrate(requireSessionRow(forkSessionId));
728
+ });
729
+ },
730
+ async archive(candidateSessionId, input = {}) {
731
+ return store.update(candidateSessionId, {
732
+ archived: true,
733
+ ...input.expectedRevision === undefined ? {} : { expectedRevision: input.expectedRevision }
734
+ });
735
+ },
736
+ async delete(candidateSessionId, input = {}) {
737
+ return transaction(() => {
738
+ const row = requireSessionRow(candidateSessionId);
739
+ ensureRevision(row, input.expectedRevision);
740
+ assertNoActiveRun(candidateSessionId, "delete");
741
+ const deletedAt = now();
742
+ assertTimestamp("now", deletedAt);
743
+ const activitySequence = nextActivitySequence();
744
+ database.query(`
745
+ UPDATE zhivex_cli_sessions
746
+ SET deleted_at = ?1, updated_at = ?1, activity_seq = ?2, revision = revision + 1
747
+ WHERE session_id = ?3 AND revision = ?4
748
+ `).run(deletedAt, activitySequence, candidateSessionId, row.revision);
749
+ return hydrate(requireSessionRow(candidateSessionId, true));
750
+ });
751
+ },
752
+ prune,
753
+ close() {
754
+ if (!closed) {
755
+ closed = true;
756
+ database.close(false);
757
+ }
758
+ }
759
+ };
760
+ return store;
761
+ };
762
+ var openSessionStore = openCliSessionStore;
763
+
764
+ // src/runtime/runtime-diagnostics.ts
765
+ import { z } from "zod";
766
+ import { createRedactionPolicy as createRedactionPolicy2 } from "@zhivex-ai/agents";
767
+ var count = z.number().int().nonnegative().finite();
768
+ var digest = z.string().regex(/^sha256:[a-f0-9]{64}$/).nullable();
769
+ var diagnosticsSchema = z.object({
770
+ schemaVersion: z.literal(2),
771
+ requireVerifiedDelivery: z.boolean(),
772
+ budget: z.object({
773
+ inputTokens: count,
774
+ outputTokens: count,
775
+ cachedInputTokens: count,
776
+ modelCalls: count,
777
+ usageComplete: z.boolean(),
778
+ inFlight: z.boolean(),
779
+ stopReason: z.enum(["USAGE_UNAVAILABLE", "WORK_TOKEN_BUDGET", "INPUT_TOKEN_BUDGET", "OUTPUT_TOKEN_BUDGET"]).nullable(),
780
+ reservedInputTokens: count,
781
+ reservedOutputTokens: count,
782
+ predictedInputTokens: count,
783
+ outputCapApplied: z.boolean().default(false)
784
+ }),
785
+ phase: z.enum(["explore", "reproduce", "candidate", "verify", "recover", "delivered", "incomplete"]),
786
+ candidate: digest,
787
+ revision: count,
788
+ checks: z.array(z.object({
789
+ commandId: z.string().max(128),
790
+ purpose: z.string().max(500),
791
+ argvDigest: digest,
792
+ candidate: digest,
793
+ exitCode: z.number().int(),
794
+ verified: z.boolean()
795
+ })).max(8),
796
+ contextMeasurements: z.array(z.object({
797
+ systemCharacters: count,
798
+ userCharacters: count,
799
+ assistantCharacters: count,
800
+ toolResultCharacters: count,
801
+ otherMessageCharacters: count,
802
+ toolDefinitionCharacters: count,
803
+ unmeasuredToolDefinitions: count
804
+ })).max(128),
805
+ modelTimings: z.array(z.object({ durationMs: z.number().nonnegative().finite(), firstTokenMs: z.number().nonnegative().finite().nullable(), completed: z.boolean() })).max(128),
806
+ omittedContextMeasurements: count
807
+ });
808
+ var manifestSchema = z.object({
809
+ schemaVersion: z.literal(2),
810
+ policyVersion: z.literal("assistant-v3-durable-closure"),
811
+ role: z.string().max(128),
812
+ requireVerifiedDelivery: z.boolean(),
813
+ backend: z.enum(["none", "oci"]),
814
+ tools: z.array(z.string().max(128)).max(1024),
815
+ budget: z.object({ unlimitedTokens: z.boolean().optional(), maxSteps: count, maxToolCalls: count, maxToolErrors: count, maxInputTokens: count, maxOutputTokens: count, includeChildRuns: z.boolean() }),
816
+ timeoutMs: count,
817
+ closureController: z.boolean(),
818
+ contextEnabled: z.boolean()
819
+ });
820
+ var redact = createRedactionPolicy2({ includeEmails: true });
821
+ var inspectRuntimeDiagnostics = (value) => {
822
+ const parsed = diagnosticsSchema.safeParse(value);
823
+ if (!parsed.success)
824
+ return null;
825
+ return { ...parsed.data, checks: parsed.data.checks.map((check) => ({
826
+ ...check,
827
+ commandId: redact.redactText(check.commandId),
828
+ purpose: redact.redactText(check.purpose)
829
+ })) };
830
+ };
831
+ var inspectRuntimeManifest = (value) => {
832
+ const parsed = manifestSchema.safeParse(value);
833
+ if (!parsed.success)
834
+ return null;
835
+ return { ...parsed.data, role: redact.redactText(parsed.data.role), tools: parsed.data.tools.map((name) => redact.redactText(name)) };
836
+ };
837
+
838
+ // src/runtime/tool-execution.ts
839
+ var harnessToolExecution = {
840
+ parallel: true,
841
+ independentOnly: true,
842
+ maxConcurrency: 4,
843
+ stopOnError: false,
844
+ validationErrorMode: "tool-result",
845
+ unknownToolMode: "tool-result"
846
+ };
847
+
848
+ // src/runtime/usage-ledger.ts
849
+ import { AsyncLocalStorage } from "node:async_hooks";
850
+ import { randomUUID as randomUUID2 } from "node:crypto";
851
+ import { lstat as lstat4 } from "node:fs/promises";
852
+ import { z as z4 } from "zod";
853
+ import { wrapLanguageModel } from "@zhivex-ai/core";
854
+
855
+ // src/runtime/model-budget.ts
856
+ import { ProviderToolCallError } from "@zhivex-ai/core/provider";
857
+ import { z as z3 } from "zod";
858
+
859
+ // src/context/context-metrics.ts
860
+ import { z as z2 } from "zod";
861
+ var schemaJson = new WeakMap;
862
+ var serializeSchema = (schema) => {
863
+ let serialized = schemaJson.get(schema);
864
+ if (serialized === undefined) {
865
+ serialized = JSON.stringify(z2.toJSONSchema(schema));
866
+ schemaJson.set(schema, serialized);
867
+ }
868
+ return serialized;
869
+ };
870
+ var estimateContextTokens = (counts) => {
871
+ if (counts.unmeasuredToolDefinitions)
872
+ throw new Error("CONTEXT_ESTIMATE_UNAVAILABLE");
873
+ return Math.ceil((counts.systemCharacters + counts.userCharacters + counts.assistantCharacters + counts.toolResultCharacters + counts.otherMessageCharacters + counts.toolDefinitionCharacters) / 3) + 64;
874
+ };
875
+ var measureContext = (input) => {
876
+ const counts = {
877
+ systemCharacters: 0,
878
+ userCharacters: 0,
879
+ assistantCharacters: 0,
880
+ toolResultCharacters: 0,
881
+ otherMessageCharacters: 0,
882
+ toolDefinitionCharacters: 0,
883
+ unmeasuredToolDefinitions: 0
884
+ };
885
+ for (const message of input.messages) {
886
+ const size = JSON.stringify(message).length;
887
+ if (message.role === "system")
888
+ counts.systemCharacters += size;
889
+ else if (message.role === "user")
890
+ counts.userCharacters += size;
891
+ else if (message.role === "assistant")
892
+ counts.assistantCharacters += size;
893
+ else if (message.role === "tool")
894
+ counts.toolResultCharacters += size;
895
+ else
896
+ counts.otherMessageCharacters += size;
897
+ }
898
+ for (const [name, definition] of Object.entries(input.tools ?? {})) {
899
+ try {
900
+ if (!("schema" in definition)) {
901
+ counts.unmeasuredToolDefinitions++;
902
+ continue;
903
+ }
904
+ const header = JSON.stringify({ name, description: definition.description });
905
+ counts.toolDefinitionCharacters += header.length - 1 + ',"schema":'.length + serializeSchema(definition.schema).length + 1;
906
+ } catch {
907
+ counts.unmeasuredToolDefinitions++;
908
+ }
909
+ }
910
+ return counts;
911
+ };
912
+
913
+ // src/runtime/model-budget.ts
914
+ var MODEL_BUDGET_KEY = "zhivexTransportBudget";
915
+ var savedBudget = z3.object({
916
+ inputTokens: z3.number().int().nonnegative(),
917
+ outputTokens: z3.number().int().nonnegative(),
918
+ cachedInputTokens: z3.number().int().nonnegative(),
919
+ modelCalls: z3.number().int().nonnegative(),
920
+ usageComplete: z3.boolean(),
921
+ inFlight: z3.boolean().default(false)
922
+ });
923
+ var estimateRequestTokens = (input) => estimateContextTokens(measureContext(input));
924
+ var workBudgetReached = (input, stats, limits) => stats.inputTokens + estimateRequestTokens(input) > limits.inputTokens - stats.reservedInputTokens || stats.outputTokens >= limits.outputTokens - stats.reservedOutputTokens;
925
+ var createModelBudget = (limits, options = {}) => {
926
+ const restored = options.saved === undefined ? undefined : savedBudget.parse(options.saved);
927
+ const reserve = options.reserveFraction ?? 0.3;
928
+ if (!Number.isFinite(reserve) || reserve < 0 || reserve >= 1)
929
+ throw new Error("Invalid closure reserve.");
930
+ const prior = inspectRuntimeDiagnostics(options.diagnostics);
931
+ const contextMetrics = prior?.contextMeasurements ?? [];
932
+ let omittedContextMeasurements = prior?.omittedContextMeasurements ?? 0;
933
+ const stats = {
934
+ inputTokens: 0,
935
+ outputTokens: 0,
936
+ cachedInputTokens: 0,
937
+ modelCalls: 0,
938
+ usageComplete: true,
939
+ inFlight: false,
940
+ ...restored,
941
+ stopReason: null,
942
+ reservedInputTokens: Number.isFinite(limits.inputTokens) ? Math.ceil(limits.inputTokens * reserve) : 0,
943
+ reservedOutputTokens: Number.isFinite(limits.outputTokens) ? Math.ceil(limits.outputTokens * reserve) : 0,
944
+ predictedInputTokens: 0,
945
+ outputCapApplied: false
946
+ };
947
+ if (restored?.inFlight)
948
+ stats.usageComplete = false;
949
+ const modelTimings = prior?.modelTimings ?? [];
950
+ const snapshot = () => ({
951
+ inputTokens: stats.inputTokens,
952
+ outputTokens: stats.outputTokens,
953
+ cachedInputTokens: stats.cachedInputTokens,
954
+ modelCalls: stats.modelCalls,
955
+ usageComplete: stats.usageComplete,
956
+ inFlight: stats.inFlight
957
+ });
958
+ const before = (input, provider) => {
959
+ input.abortSignal?.throwIfAborted();
960
+ const measured = measureContext(input);
961
+ stats.predictedInputTokens = estimateContextTokens(measured);
962
+ const closure = options.closure?.() ?? false;
963
+ const inputCeiling = limits.inputTokens - (closure ? 0 : stats.reservedInputTokens);
964
+ const outputCeiling = limits.outputTokens - (closure ? 0 : stats.reservedOutputTokens);
965
+ 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;
966
+ if (stats.stopReason)
967
+ throw new Error(stats.stopReason);
968
+ const capSupported = provider !== "qwen" || input.providerOptions?.apiMode === "chat" || input.providerOptions?.apiMode !== "responses" && (input.maxTokens !== undefined || input.reasoning?.budgetTokens !== undefined);
969
+ const remainingOutput = outputCeiling - stats.outputTokens;
970
+ const outputCap = Math.min(input.maxTokens ?? remainingOutput, remainingOutput);
971
+ stats.outputCapApplied = capSupported && Number.isFinite(outputCap);
972
+ if (stats.outputCapApplied)
973
+ input.maxTokens = Math.max(1, outputCap);
974
+ stats.modelCalls++;
975
+ stats.inFlight = true;
976
+ if (contextMetrics.length < 128)
977
+ contextMetrics.push(measured);
978
+ else
979
+ omittedContextMeasurements++;
980
+ };
981
+ const record = (usage) => {
982
+ stats.inFlight = false;
983
+ if (!usage || usage.inputTokens === undefined || usage.outputTokens === undefined || ![usage.inputTokens, usage.outputTokens, usage.cachedInputTokens ?? 0].every((v) => Number.isSafeInteger(v) && v >= 0)) {
984
+ stats.usageComplete = false;
985
+ return;
986
+ }
987
+ stats.inputTokens += usage.inputTokens;
988
+ stats.outputTokens += usage.outputTokens;
989
+ stats.cachedInputTokens += usage.cachedInputTokens ?? 0;
990
+ };
991
+ const recordFailure = (error, provider) => {
992
+ const usage = error instanceof ProviderToolCallError && error.provider === provider ? Reflect.get(error, "usage") : undefined;
993
+ record(usage && typeof usage === "object" ? usage : undefined);
994
+ };
995
+ const middleware = {
996
+ name: "harness-transport-budget-v2",
997
+ async wrapGenerate(context, next) {
998
+ before(context.input, context.model.provider);
999
+ const started = performance.now();
1000
+ let completed = false;
1001
+ try {
1002
+ const result = await next();
1003
+ record(result.usage);
1004
+ completed = true;
1005
+ return result;
1006
+ } catch (error) {
1007
+ recordFailure(error, context.model.provider);
1008
+ throw error;
1009
+ } finally {
1010
+ if (modelTimings.length < 128)
1011
+ modelTimings.push({ durationMs: performance.now() - started, firstTokenMs: null, completed });
1012
+ }
1013
+ },
1014
+ async wrapStream(context, next) {
1015
+ before(context.input, context.model.provider);
1016
+ const started = performance.now();
1017
+ let firstTokenMs = null;
1018
+ try {
1019
+ const stream = await next();
1020
+ return async function* () {
1021
+ let finished = false, failureAccounted = false;
1022
+ try {
1023
+ for await (const event of stream) {
1024
+ if (firstTokenMs === null && (event.type === "text-delta" || event.type === "tool-call"))
1025
+ firstTokenMs = performance.now() - started;
1026
+ if (event.type === "finish" && !finished) {
1027
+ record(event.usage);
1028
+ finished = true;
1029
+ }
1030
+ yield event;
1031
+ }
1032
+ } catch (error) {
1033
+ if (!finished) {
1034
+ recordFailure(error, context.model.provider);
1035
+ failureAccounted = true;
1036
+ }
1037
+ throw error;
1038
+ } finally {
1039
+ if (!finished && !failureAccounted)
1040
+ stats.usageComplete = false;
1041
+ stats.inFlight = false;
1042
+ if (modelTimings.length < 128)
1043
+ modelTimings.push({ durationMs: performance.now() - started, firstTokenMs, completed: finished });
1044
+ }
1045
+ }();
1046
+ } catch (error) {
1047
+ recordFailure(error, context.model.provider);
1048
+ if (modelTimings.length < 128)
1049
+ modelTimings.push({ durationMs: performance.now() - started, firstTokenMs, completed: false });
1050
+ throw error;
1051
+ }
1052
+ }
1053
+ };
1054
+ return { stats, middleware, snapshot, contextMetrics, modelTimings, get omittedContextMeasurements() {
1055
+ return omittedContextMeasurements;
1056
+ } };
1057
+ };
1058
+
1059
+ // src/runtime/usage-ledger.ts
1060
+ var USAGE_LEDGER_KEY = "zhivexUsageLedger";
1061
+ var rate = z4.number().finite().nonnegative();
1062
+ var usagePricingSchema = z4.strictObject({
1063
+ schemaVersion: z4.literal(1),
1064
+ prices: z4.array(z4.strictObject({
1065
+ provider: z4.string().min(1).max(128),
1066
+ model: z4.string().min(1).max(512),
1067
+ inputUsdPerMillion: rate,
1068
+ outputUsdPerMillion: rate,
1069
+ source: z4.string().min(1).max(1024),
1070
+ asOf: z4.iso.datetime(),
1071
+ expiresAt: z4.iso.datetime()
1072
+ })).max(100)
1073
+ }).superRefine((value, ctx) => {
1074
+ const keys = new Set;
1075
+ for (const price of value.prices) {
1076
+ const key = JSON.stringify([price.provider, price.model]);
1077
+ if (keys.has(key) || Date.parse(price.expiresAt) <= Date.parse(price.asOf)) {
1078
+ ctx.addIssue({ code: "custom", message: "Duplicate model price or invalid validity interval." });
1079
+ }
1080
+ keys.add(key);
1081
+ }
1082
+ });
1083
+ var summaryView = z4.object({
1084
+ calls: z4.number().int().nonnegative(),
1085
+ inputTokens: rate,
1086
+ outputTokens: rate,
1087
+ usageComplete: z4.boolean(),
1088
+ estimatedUsd: rate.nullable(),
1089
+ limitUsd: rate.nullable()
1090
+ });
1091
+ var ledgerView = summaryView.extend({
1092
+ schemaVersion: z4.literal(1),
1093
+ runId: z4.string().max(256),
1094
+ historicalUsageUnknown: z4.boolean().optional(),
1095
+ costKind: z4.literal("estimate-not-invoice"),
1096
+ routes: z4.array(z4.object({
1097
+ provider: z4.string().max(128),
1098
+ model: z4.string().max(512),
1099
+ calls: z4.number().int().nonnegative(),
1100
+ inputTokens: rate,
1101
+ outputTokens: rate,
1102
+ unknownCalls: z4.number().int().nonnegative(),
1103
+ estimatedUsd: rate.nullable(),
1104
+ priceStatuses: z4.array(z4.enum(["missing", "stale", "estimate"]))
1105
+ })).max(100)
1106
+ });
1107
+ var inspectUsageLedger = (value) => {
1108
+ const parsed = ledgerView.safeParse(value);
1109
+ return parsed.success ? parsed.data : null;
1110
+ };
1111
+ var formatUsageLedger = (value) => {
1112
+ const parsed = summaryView.safeParse(value);
1113
+ if (!parsed.success)
1114
+ return "Usage: unavailable for this run.";
1115
+ const v = parsed.data;
1116
+ 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)`;
1117
+ };
1118
+ var current = new AsyncLocalStorage;
1119
+
1120
+ class UsageLedger {
1121
+ database;
1122
+ key;
1123
+ options;
1124
+ now;
1125
+ constructor(database, key, options, now) {
1126
+ this.database = database;
1127
+ this.key = key;
1128
+ this.options = options;
1129
+ this.now = now;
1130
+ }
1131
+ static async open(config, options = {}, now = Date.now) {
1132
+ if (options.limitUsd !== undefined && (!Number.isFinite(options.limitUsd) || options.limitUsd <= 0))
1133
+ throw new Error("Usage limit must be positive USD.");
1134
+ if (options.pricing)
1135
+ usagePricingSchema.parse(options.pricing);
1136
+ const sessions = await openCliSessionStore({ workspace: config.workspace, stateDirectory: config.stateDirectory, scope: config.scope });
1137
+ const key = `${sessions.workspaceKey}:${sessions.scopeKey}`;
1138
+ const databasePath = sessions.databasePath;
1139
+ sessions.close();
1140
+ const before = await lstat4(databasePath);
1141
+ if (before.isSymbolicLink() || !before.isFile())
1142
+ throw new Error("Usage database must be a regular private file.");
1143
+ const database = new SqliteDatabase(databasePath, { create: false, strict: true });
1144
+ const after = await lstat4(databasePath);
1145
+ if (after.isSymbolicLink() || !after.isFile() || before.dev !== after.dev || before.ino !== after.ino) {
1146
+ database.close();
1147
+ throw new Error("Usage database changed while opening.");
1148
+ }
1149
+ database.exec("PRAGMA busy_timeout = 5000");
1150
+ database.exec(`CREATE TABLE IF NOT EXISTS zhivex_usage_policies (
1151
+ scope_key TEXT NOT NULL, run_id TEXT NOT NULL, policy TEXT NOT NULL,
1152
+ PRIMARY KEY (scope_key, run_id));
1153
+ CREATE TABLE IF NOT EXISTS zhivex_usage_calls (
1154
+ id TEXT PRIMARY KEY, scope_key TEXT NOT NULL, run_id TEXT NOT NULL,
1155
+ provider TEXT NOT NULL, model TEXT NOT NULL, status TEXT NOT NULL,
1156
+ input_tokens INTEGER, output_tokens INTEGER, estimate_usd REAL, reserved_usd REAL NOT NULL,
1157
+ price_status TEXT NOT NULL);`);
1158
+ return new UsageLedger(database, key, options, now);
1159
+ }
1160
+ close() {
1161
+ this.database.close();
1162
+ }
1163
+ assertResume(runId) {
1164
+ const row = this.database.query("SELECT policy FROM zhivex_usage_policies WHERE scope_key = ?1 AND run_id = ?2").get(this.key, runId);
1165
+ if (!row)
1166
+ throw new Error("USAGE_LEDGER_MISSING: restore the complete state backup; a run snapshot cannot reset its monetary policy.");
1167
+ }
1168
+ policy(runId) {
1169
+ const row = this.database.query("SELECT policy FROM zhivex_usage_policies WHERE scope_key = ?1 AND run_id = ?2").get(this.key, runId);
1170
+ return row ? JSON.parse(row.policy) : this.options;
1171
+ }
1172
+ async run(runId, operation, historicalUsageUnknown = false) {
1173
+ 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 } : {} }));
1174
+ 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);
1175
+ return current.run({ ledger: this, runId }, operation);
1176
+ }
1177
+ summary(runId) {
1178
+ 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);
1179
+ const routes = new Map;
1180
+ for (const row of rows) {
1181
+ const key = JSON.stringify([row.provider, row.model]);
1182
+ const entry = routes.get(key) ?? { provider: row.provider, model: row.model, calls: 0, inputTokens: 0, outputTokens: 0, unknownCalls: 0, estimatedUsd: 0, priceStatuses: [] };
1183
+ entry.calls++;
1184
+ entry.inputTokens += row.input_tokens ?? 0;
1185
+ entry.outputTokens += row.output_tokens ?? 0;
1186
+ if (row.status !== "confirmed")
1187
+ entry.unknownCalls++;
1188
+ entry.estimatedUsd = entry.estimatedUsd === null || row.estimate_usd === null ? null : entry.estimatedUsd + row.estimate_usd;
1189
+ if (!entry.priceStatuses.includes(row.price_status))
1190
+ entry.priceStatuses.push(row.price_status);
1191
+ routes.set(key, entry);
1192
+ }
1193
+ const values = [...routes.values()];
1194
+ return {
1195
+ schemaVersion: 1,
1196
+ runId,
1197
+ calls: rows.length,
1198
+ routes: values,
1199
+ inputTokens: values.reduce((n, r) => n + r.inputTokens, 0),
1200
+ outputTokens: values.reduce((n, r) => n + r.outputTokens, 0),
1201
+ historicalUsageUnknown: this.policy(runId).historicalUsageUnknown ?? false,
1202
+ usageComplete: !this.policy(runId).historicalUsageUnknown && rows.every((row) => row.status === "confirmed"),
1203
+ estimatedUsd: this.policy(runId).historicalUsageUnknown || values.some((r) => r.estimatedUsd === null) ? null : values.reduce((n, r) => n + (r.estimatedUsd ?? 0), 0),
1204
+ limitUsd: this.policy(runId).limitUsd ?? null,
1205
+ costKind: "estimate-not-invoice"
1206
+ };
1207
+ }
1208
+ begin(runId, provider, model, input) {
1209
+ const policy = this.policy(runId);
1210
+ const price = policy.pricing?.prices.find((p) => p.provider === provider && p.model === model);
1211
+ const priceStatus = !price ? "missing" : Date.parse(price.asOf) > this.now() || Date.parse(price.expiresAt) <= this.now() ? "stale" : "estimate";
1212
+ const usable = priceStatus === "estimate" ? price : undefined;
1213
+ let reserved = 0;
1214
+ if (policy.limitUsd !== undefined) {
1215
+ if (policy.historicalUsageUnknown)
1216
+ throw new Error("USAGE_UNCERTAIN: historical usage predates the ledger.");
1217
+ if (!usable)
1218
+ throw new Error(`USAGE_PRICE_${priceStatus.toUpperCase()}: monetary limit cannot be calculated for ${provider}/${model}.`);
1219
+ if (provider === "qwen" && input.providerOptions?.apiMode !== "chat" && input.maxTokens === undefined) {
1220
+ throw new Error("USAGE_OUTPUT_CAP_UNAVAILABLE: select a route with a supported output cap.");
1221
+ }
1222
+ input.maxTokens = Math.min(input.maxTokens ?? 2048, 2048);
1223
+ reserved = (estimateRequestTokens(input) * usable.inputUsdPerMillion + input.maxTokens * usable.outputUsdPerMillion) / 1e6;
1224
+ }
1225
+ const id = randomUUID2();
1226
+ this.database.exec("BEGIN IMMEDIATE");
1227
+ try {
1228
+ if (policy.requireCompleteUsage) {
1229
+ 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);
1230
+ if (policy.historicalUsageUnknown || (unresolved?.n ?? 0) > 0)
1231
+ throw new Error("USAGE_UNCERTAIN: reconcile unknown utility usage before further execution.");
1232
+ }
1233
+ if (policy.limitUsd !== undefined) {
1234
+ const rows = this.database.query("SELECT * FROM zhivex_usage_calls WHERE scope_key = ?1 AND run_id = ?2").all(this.key, runId);
1235
+ if (rows.some((r) => r.status === "unknown" || r.status === "confirmed" && r.estimate_usd === null))
1236
+ throw new Error("USAGE_UNCERTAIN: inspect unresolved calls before further monetary-budget execution.");
1237
+ const spent = rows.reduce((n, r) => n + (r.status === "pending" ? r.reserved_usd : r.estimate_usd ?? 0), 0);
1238
+ if (spent + reserved > policy.limitUsd)
1239
+ throw new Error("USAGE_COST_BUDGET: insufficient estimated budget for the next request.");
1240
+ }
1241
+ this.database.query(`INSERT INTO zhivex_usage_calls (id, scope_key, run_id, provider, model, status, reserved_usd, price_status)
1242
+ VALUES (?1, ?2, ?3, ?4, ?5, 'pending', ?6, ?7)`).run(id, this.key, runId, provider, model, reserved, priceStatus);
1243
+ this.database.exec("COMMIT");
1244
+ } catch (error) {
1245
+ this.database.exec("ROLLBACK");
1246
+ throw error;
1247
+ }
1248
+ return { id, price: usable };
1249
+ }
1250
+ finish(call, usage) {
1251
+ const confirmed = usage && [usage.inputTokens, usage.outputTokens].every((v) => Number.isSafeInteger(v) && v >= 0);
1252
+ const estimate = confirmed && call.price ? (usage.inputTokens * call.price.inputUsdPerMillion + usage.outputTokens * call.price.outputUsdPerMillion) / 1e6 : null;
1253
+ this.database.query(`UPDATE zhivex_usage_calls SET status = ?1, input_tokens = ?2, output_tokens = ?3, estimate_usd = ?4
1254
+ 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);
1255
+ }
1256
+ model(model) {
1257
+ const ledger = this;
1258
+ const middleware = {
1259
+ name: "harness-durable-usage-v1",
1260
+ async wrapGenerate(context, next) {
1261
+ const scope = current.getStore();
1262
+ if (!scope || scope.ledger !== ledger)
1263
+ throw new Error("Usage accounting requires a logical run scope.");
1264
+ const call = ledger.begin(scope.runId, model.provider, model.modelId, context.input);
1265
+ try {
1266
+ const result = await next();
1267
+ ledger.finish(call, result.usage);
1268
+ return result;
1269
+ } catch (error) {
1270
+ ledger.finish(call, undefined);
1271
+ throw error;
1272
+ }
1273
+ },
1274
+ async wrapStream(context, next) {
1275
+ const scope = current.getStore();
1276
+ if (!scope || scope.ledger !== ledger)
1277
+ throw new Error("Usage accounting requires a logical run scope.");
1278
+ const call = ledger.begin(scope.runId, model.provider, model.modelId, context.input);
1279
+ try {
1280
+ const stream = await next();
1281
+ return async function* () {
1282
+ try {
1283
+ for await (const event of stream) {
1284
+ if (event.type === "finish")
1285
+ ledger.finish(call, event.usage);
1286
+ yield event;
1287
+ }
1288
+ } finally {
1289
+ ledger.finish(call, undefined);
1290
+ }
1291
+ }();
1292
+ } catch (error) {
1293
+ ledger.finish(call, undefined);
1294
+ throw error;
1295
+ }
1296
+ }
1297
+ };
1298
+ return wrapLanguageModel(model, [middleware]);
1299
+ }
1300
+ store(store) {
1301
+ const ledger = this;
1302
+ return new Proxy(store, { get(target, key) {
1303
+ if (key === "save")
1304
+ return async (...args) => {
1305
+ const scope = current.getStore();
1306
+ if (scope?.ledger === ledger && args[0].runId === scope.runId) {
1307
+ args[0].metadata = { ...args[0].metadata, [USAGE_LEDGER_KEY]: ledger.summary(scope.runId) };
1308
+ }
1309
+ return target.save(...args);
1310
+ };
1311
+ const value = Reflect.get(target, key, target);
1312
+ return typeof value === "function" ? value.bind(target) : value;
1313
+ } });
1314
+ }
1315
+ }
1316
+
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." });
1351
+ };
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;
1368
+ try {
1369
+ bytes = Buffer.byteLength(JSON.stringify(input));
1370
+ } catch {
1371
+ throw new Error("TASK_ACCEPTANCE_INVALID: requirements must be JSON serializable.");
1372
+ }
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
+ }
1389
+
1390
+ // src/context/task-memory.ts
1391
+ import { createHash as createHash3 } from "node:crypto";
1392
+ import { tool, serializeJsonValue as serializeJsonValue2 } from "@zhivex-ai/core";
1393
+ import { createRedactionPolicy as createRedactionPolicy3 } from "@zhivex-ai/agents";
1394
+ import { z as z8 } from "zod";
1395
+
1396
+ // src/runtime/repair-verifier.ts
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)
1402
+ });
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
+
1487
+ // src/context/task-memory.ts
1488
+ var redact2 = createRedactionPolicy3({ includeEmails: true });
1489
+ var TASK_SOURCE_KEY = "zhivexTaskSources";
1490
+ var taskSources = (metadata) => {
1491
+ const value = metadata && typeof metadata === "object" ? metadata[TASK_SOURCE_KEY] : undefined;
1492
+ 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 } : {} })) : [];
1493
+ };
1494
+ var captureTaskSources = (metadata, messages) => {
1495
+ const previous = taskSources(metadata);
1496
+ const originalId = previous.find((source) => source.original)?.id ?? previous[0]?.id;
1497
+ const sources = new Map(previous.map((source) => [
1498
+ source.id,
1499
+ { id: source.id, text: source.text, ...source.id === originalId ? { original: true } : {} }
1500
+ ]));
1501
+ for (const message of messages)
1502
+ if (message.role === "user")
1503
+ for (const part of message.parts) {
1504
+ if (part.type !== "text" || /^\[Compacted (?:conversation context|prior conversation)\]/.test(part.text))
1505
+ continue;
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")}`;
1508
+ const original = sources.size === 0 || sources.get(id)?.original === true;
1509
+ sources.delete(id);
1510
+ sources.set(id, { id, text: text2, ...original ? { original: true } : {} });
1511
+ }
1512
+ return [...sources.values()];
1513
+ };
1514
+ var createTaskTools = () => ({
1515
+ read_task: tool({
1516
+ name: "read_task",
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.",
1518
+ metadata: { advancedRegistry: { permissions: ["read"], audit: { riskLevel: "low" } } },
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()
1523
+ }),
1524
+ execute: async ({ id, offset, sourceOffset }, context) => {
1525
+ const sources = taskSources(context?.metadata);
1526
+ const source = id ? sources.find((s) => s.id === id) : sources.at(-1);
1527
+ if (!source)
1528
+ throw new Error("Requested task source is not bound to this run.");
1529
+ const start = sourceOffset ?? Math.max(0, sources.length - 64);
1530
+ const acceptance = readTaskAcceptanceLedger(context?.metadata ? { metadata: context.metadata } : {})?.revisions.at(-1);
1531
+ return {
1532
+ id: source.id,
1533
+ sourceIds: sources.slice(start, start + 64).map((s) => s.id),
1534
+ ...acceptance ? { acceptance: serializeJsonValue2(acceptance) } : {},
1535
+ firstSourceId: (sources.find((item) => item.original) ?? sources[0]).id,
1536
+ sourceOffset: start,
1537
+ totalSources: sources.length,
1538
+ nextSourceOffset: start + 64 < sources.length ? start + 64 : null,
1539
+ content: source.text.slice(offset, offset + 4000),
1540
+ offset,
1541
+ totalCharacters: source.text.length,
1542
+ nextOffset: offset + 4000 < source.text.length ? offset + 4000 : null
1543
+ };
1544
+ }
1545
+ }),
1546
+ repair_plan: tool({
1547
+ name: "repair_plan",
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.",
1549
+ metadata: { advancedRegistry: { permissions: ["read"], audit: { riskLevel: "low" } } },
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 })
1552
+ })
1553
+ });
1554
+
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 };