@zhivex-ai/harness 1.4.0 → 1.4.1

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 (117) hide show
  1. package/CHANGELOG.md +29 -10
  2. package/README.md +7 -5
  3. package/SUPPORT.md +2 -2
  4. package/contracts/client-protocol-v1.schema.json +593 -0
  5. package/contracts/engine-api.json +156 -0
  6. package/contracts/public-api.json +12 -0
  7. package/contracts/stable-api-signatures.json +33 -33
  8. package/dist/acp-cli.js +54 -20
  9. package/dist/audit/index.d.ts +3 -0
  10. package/dist/audit/index.js +482 -0
  11. package/dist/audit/pack.d.ts +72 -0
  12. package/dist/audit-pack-e4vaq7np.js +532 -0
  13. package/dist/cli/arguments.d.ts +3 -1
  14. package/dist/cli/audit-pack.d.ts +24 -0
  15. package/dist/cli/cli-credentials.d.ts +6 -0
  16. package/dist/cli/cli-profiles.d.ts +4 -2
  17. package/dist/cli/node-engine.d.ts +2 -0
  18. package/dist/{cli-2h37qyvg.js → cli-1cf4dcnj.js} +245 -642
  19. package/dist/{cli-entry-w0hterjs.js → cli-entry-3s63zn4w.js} +40 -13
  20. package/dist/cli-entry-7f0cen32.js +9990 -0
  21. package/dist/cli-entry-a890x3kq.js +10348 -0
  22. package/dist/{cli-entry-20w6nh0k.js → cli-entry-hdtfef27.js} +439 -19
  23. package/dist/cli-entry-rgs6bxmy.js +484 -0
  24. package/dist/cli.js +27 -2
  25. package/dist/client/json-contracts.d.ts +4 -0
  26. package/dist/client/protocol.d.ts +133 -0
  27. package/dist/client/task-control-host.d.ts +400 -0
  28. package/dist/client/task-projection-host.d.ts +12 -0
  29. package/dist/client/task-projection.d.ts +168 -0
  30. package/dist/compat/code-support.js +28 -22
  31. package/dist/{config-qt69hkqj.js → config-r9fdhce6.js} +3 -3
  32. package/dist/{config-j9624t6t.js → config-vyajmksy.js} +1 -1
  33. package/dist/engine/acp.js +15 -11
  34. package/dist/engine/client.js +14 -10
  35. package/dist/engine/desktop-providers-v1.d.ts +2 -0
  36. package/dist/engine/desktop-providers-v1.js +23 -3
  37. package/dist/engine/desktop-state-v1.js +11 -8
  38. package/dist/engine/index.d.ts +3 -0
  39. package/dist/engine/index.js +101 -554
  40. package/dist/engine/mcp-stdio-v1.js +8 -6
  41. package/dist/engine/models.js +76 -4
  42. package/dist/engine/protocol.d.ts +2 -0
  43. package/dist/engine/protocol.js +110 -0
  44. package/dist/engine/service.js +15 -11
  45. package/dist/hostile-repository-demo.js +1095 -505
  46. package/dist/{index-937cb7y8.js → index-02pqtbbg.js} +559 -33
  47. package/dist/{index-pp474137.js → index-0zs2dz26.js} +766 -931
  48. package/dist/{index-8zjmnyw5.js → index-1265j7t2.js} +73 -6
  49. package/dist/index-4fx5f3k5.js +269 -0
  50. package/dist/{index-nmjdwd4p.js → index-4gt045j0.js} +109 -1
  51. package/dist/{index-6dft2vev.js → index-6599mpmy.js} +5 -3
  52. package/dist/{index-tfzta544.js → index-74kkpyqk.js} +9 -7
  53. package/dist/{index-61gthdz6.js → index-8sm2a1wa.js} +1 -1
  54. package/dist/index-c39197wq.js +552 -0
  55. package/dist/{index-5jhgatga.js → index-crta5wzv.js} +7 -6
  56. package/dist/{index-m34q25jj.js → index-d9zkmhz3.js} +143 -824
  57. package/dist/{index-pdmevfxv.js → index-dtb2c6ds.js} +5 -259
  58. package/dist/index-f6mqa5gk.js +1535 -0
  59. package/dist/index-j6zxnc8m.js +490 -0
  60. package/dist/{index-rkbf7dqg.js → index-mv8sb1ns.js} +5 -3
  61. package/dist/{index-xqxeypt5.js → index-mzydbzp8.js} +76 -4
  62. package/dist/{index-wg1sd3at.js → index-qfsrrnz6.js} +1 -1
  63. package/dist/{index-m82cfspw.js → index-sk3cap35.js} +877 -963
  64. package/dist/index-sstgcsr0.js +511 -0
  65. package/dist/index-wm9xeq1n.js +329 -0
  66. package/dist/index-xdbkcccf.js +719 -0
  67. package/dist/{index-s2k2edts.js → index-zxg4ve4j.js} +41 -1548
  68. package/dist/index.d.ts +3 -0
  69. package/dist/index.js +93 -77
  70. package/dist/internal/desktop/providers.d.ts +2 -0
  71. package/dist/models/catalog.d.ts +2 -0
  72. package/dist/persistence/sqlite-database.d.ts +1 -0
  73. package/dist/providers/azure-openai.d.ts +14 -0
  74. package/dist/providers/bedrock.d.ts +23 -0
  75. package/dist/providers/providers.d.ts +1 -1
  76. package/dist/runtime/durable-review-group.d.ts +33 -0
  77. package/dist/runtime/harness.d.ts +11 -0
  78. package/dist/runtime/run-interruption.d.ts +7 -0
  79. package/dist/runtime/shared-budget.d.ts +30 -0
  80. package/dist/runtime/task-continuity.d.ts +300 -77
  81. package/dist/service-cli.js +15 -11
  82. package/dist/{service-client-cli-fn54ev6w.js → service-client-cli-eqrdc9zq.js} +400 -305
  83. package/dist/verify-historical-migrations.js +1095 -505
  84. package/dist/zhx.js +27 -2
  85. package/docs/AUDIT_PACK.md +99 -0
  86. package/docs/AZURE_OPENAI.md +53 -0
  87. package/docs/BEDROCK.md +56 -0
  88. package/docs/CLI.md +15 -0
  89. package/docs/CLIENT_PROTOCOL.md +178 -0
  90. package/docs/CODE_RELEASE.md +31 -1
  91. package/docs/CODE_WEB_TASKS.md +116 -0
  92. package/docs/CONTEXT_ENGINEERING.md +59 -0
  93. package/docs/CREDENTIALS.md +21 -0
  94. package/docs/DURABLE_OPERATIONS.md +93 -1
  95. package/docs/ENGINE_API.md +5 -2
  96. package/docs/EXTENSIBILITY.md +2 -2
  97. package/docs/FIRST_USE.md +6 -6
  98. package/docs/MINIMUM_TASK_AUTHORITY.md +3 -3
  99. package/docs/PROJECT_MEMORY.md +1 -1
  100. package/docs/README.md +6 -1
  101. package/docs/STABILITY.md +3 -1
  102. package/docs/SUPPORT_MATRIX.md +1 -1
  103. package/docs/SUPPORT_MATRIX_1_1.md +1 -1
  104. package/docs/USAGE.md +3 -1
  105. package/docs/VERTEX.md +2 -2
  106. package/docs/WEB_LIMITS.md +4 -3
  107. package/docs/adr/0002-isolated-mcp-stdio.md +6 -3
  108. package/docs/support-matrix-1.1.json +3 -1
  109. package/docs/support-matrix.json +6 -4
  110. package/evaluations/code-task-ui.json +20 -0
  111. package/evaluations/representative-assembly-matrix.json +10 -1
  112. package/evaluations/task-projection.json +10 -0
  113. package/evaluations/task-recovery-lab.json +60 -0
  114. package/examples/README.md +3 -3
  115. package/package.json +23 -13
  116. package/dist/cli-entry-4ngptr62.js +0 -19900
  117. package/dist/index-m49jx72n.js +0 -220
@@ -1,40 +1,64 @@
1
1
  import {
2
2
  openWorkspaceCheckpointStore
3
- } from "./index-6dft2vev.js";
3
+ } from "./index-6599mpmy.js";
4
4
  import {
5
- APPROVAL_HISTORY_KEY,
6
5
  appendUserMessage,
7
- approvalDecisionViews,
8
- approvalInputDigest,
9
6
  checkEvidence,
10
- createPolicyDecisionEvidence,
11
7
  hostPolicyIdentity,
12
- inspectHarnessPolicy,
8
+ initializeHarnessTaskBudget,
13
9
  issueExplicitReviewResponses,
14
10
  issueRecordedApprovalResponses,
15
- observeHarnessPolicyDecisions,
16
- readApprovalDecisions,
11
+ openHarnessTaskBudget,
12
+ persistHarnessTaskDraft,
13
+ readHarnessTaskDraft,
14
+ readTaskContinuityEvidence,
17
15
  requiresExplicitHostReview,
18
- runHarness
19
- } from "./index-pp474137.js";
16
+ reviseHarnessTaskAcceptance,
17
+ runHarness,
18
+ runHarnessTask
19
+ } from "./index-0zs2dz26.js";
20
20
  import {
21
21
  ASSISTANT_RESPONSE_KEY,
22
- SESSION_RUN_STATUSES,
23
- USAGE_LEDGER_KEY,
22
+ TASK_SOURCE_KEY,
24
23
  assistantResponses,
25
24
  cancelHarnessRun,
25
+ captureTaskSources
26
+ } from "./index-f6mqa5gk.js";
27
+ import {
28
+ resolvePackageCheckCommand
29
+ } from "./index-d9zkmhz3.js";
30
+ import {
31
+ APPROVAL_HISTORY_KEY,
32
+ approvalDecisionViews,
33
+ approvalInputDigest,
34
+ createPolicyDecisionEvidence,
35
+ inspectHarnessPolicy,
36
+ observeHarnessPolicyDecisions,
37
+ readApprovalDecisions
38
+ } from "./index-wm9xeq1n.js";
39
+ import {
40
+ SESSION_RUN_STATUSES,
41
+ TASK_ACCEPTANCE_KEY,
42
+ TASK_BUDGET_KEY,
43
+ TaskBudget,
44
+ USAGE_LEDGER_KEY,
45
+ compileTaskAcceptanceContract,
46
+ inspectTaskBudgetSummary,
26
47
  inspectUsageLedger,
27
- openCliSessionStore
28
- } from "./index-s2k2edts.js";
48
+ nextTaskAcceptanceLedger,
49
+ openCliSessionStore,
50
+ readTaskAcceptanceLedger
51
+ } from "./index-zxg4ve4j.js";
29
52
  import {
30
53
  createEditProposal,
31
54
  editProposalInputSchema,
32
55
  fileDigestSchema
33
- } from "./index-m34q25jj.js";
56
+ } from "./index-xdbkcccf.js";
34
57
  import {
35
58
  CLI_JSON_SCHEMA_VERSION,
36
- harnessClientRequestSchema
37
- } from "./index-nmjdwd4p.js";
59
+ harnessClientRequestSchema,
60
+ harnessTaskProjectionSchema
61
+ } from "./index-4gt045j0.js";
38
62
  import {
39
63
  HarnessStateConflictError,
40
64
  harnessErrorDocument,
@@ -125,16 +149,338 @@ var runResultDocument = (result, harness) => ({
125
149
  stateDirectory: harness.config.stateDirectory
126
150
  });
127
151
 
152
+ // src/client/task-control-host.ts
153
+ import { execFile } from "node:child_process";
154
+ import { promisify } from "node:util";
155
+ import { createHash, randomUUID } from "node:crypto";
156
+ var exec = promisify(execFile);
157
+ var TASK_HUMAN_DECISION_KEY = "harnessClientHumanDecisionV1";
158
+ var hash = (value) => "sha256:" + createHash("sha256").update(JSON.stringify(value)).digest("hex");
159
+ var taskReviewIdentity = (p) => hash(p.task);
160
+ async function taskHumanDecision(host, p) {
161
+ const state = await host.store.load(p.task.observedRunId, host.config.scope);
162
+ const raw = state?.metadata?.[TASK_HUMAN_DECISION_KEY];
163
+ if (!raw || raw.schemaVersion !== 1 || raw.kind !== "accepted" || raw.provenance !== "explicit_operator_review" || typeof raw.at !== "number")
164
+ return { status: "not_recorded", at: null, provenance: null };
165
+ return {
166
+ status: raw.runId === p.task.observedRunId && raw.contractDigest === p.task.contractDigest && raw.snapshotDigest === p.task.artifact.observedDigest && p.task.review.structure === "verified" ? "current" : "stale",
167
+ at: raw.at,
168
+ provenance: "explicit_operator_review"
169
+ };
170
+ }
171
+ async function visibleTaskFiles(host, paths) {
172
+ for (const file of paths) {
173
+ const result = await exec("git", ["--literal-pathspecs", "ls-files", "--error-unmatch", "-v", "-z", "--", file], { cwd: host.workspace.root, timeout: 1e4, maxBuffer: 65536 });
174
+ if (result.stdout !== "H " + file + "\x00")
175
+ throw new Error("TASK_FILES_UNAVAILABLE");
176
+ }
177
+ }
178
+ async function taskGitReview(host) {
179
+ const result = await host.workspace.gitDiff();
180
+ if ([result.status, result.diff, result.staged].some((r) => r.exitCode !== 0 || r.timedOut || r.stdout.length >= 20000))
181
+ throw new Error("TASK_DIFF_UNAVAILABLE");
182
+ const text = result.diff.stdout + result.staged.stdout;
183
+ if (Buffer.byteLength(text) > 128 * 1024)
184
+ throw new Error("TASK_DIFF_CAPACITY");
185
+ return { text, identity: hash([result.status.stdout, result.diff.stdout, result.staged.stdout]) };
186
+ }
187
+ async function prepareClientTask(host, brief) {
188
+ if (host.config.execution.backend !== "none" || host.config.orchestration.profiles.length || host.agent.subagents?.length)
189
+ throw new Error("TASK_HOST_UNSUPPORTED");
190
+ const clean = async () => {
191
+ const git = await host.workspace.gitDiff();
192
+ if ([git.status, git.diff, git.staged].some((r) => r.exitCode !== 0 || r.timedOut || r.stdout.trim()))
193
+ throw new Error("TASK_CLEAN_BASELINE_REQUIRED");
194
+ };
195
+ await clean();
196
+ const file = await host.workspace.readFile("package.json");
197
+ if (file.truncated)
198
+ throw new Error("TASK_MANIFEST_UNAVAILABLE");
199
+ const manifest = JSON.parse(file.content.replace(/^\d+: /gm, ""));
200
+ const requiredChecks = await Promise.all(brief.checks.map(async (script, index) => {
201
+ const expectedScript = manifest.scripts?.[script];
202
+ if (typeof expectedScript !== "string" || !expectedScript.trim() || expectedScript.length > 2000)
203
+ throw new Error("TASK_CHECK_UNAVAILABLE");
204
+ const resolved = await resolvePackageCheckCommand(host.workspace.root, manifest, script, expectedScript, host.config.allowedChecks);
205
+ return {
206
+ id: "check-" + (index + 1),
207
+ kind: "package-script",
208
+ script,
209
+ expectedScript,
210
+ command: resolved.command[0],
211
+ args: resolved.command.slice(1),
212
+ purpose: ("Check " + script + " for: " + brief.goal).slice(0, 500),
213
+ execution: { backend: "none", approval: "required" }
214
+ };
215
+ }));
216
+ const contract = compileTaskAcceptanceContract({
217
+ schemaVersion: 1,
218
+ taskId: randomUUID(),
219
+ allowedWritePaths: brief.paths,
220
+ protectedFiles: ["package.json"],
221
+ requiredChecks,
222
+ humanReview: [
223
+ { id: "operator", requirement: brief.goal, status: "pending" },
224
+ ...brief.constraints.map((requirement, index) => ({ id: "constraint-" + (index + 1), requirement, status: "pending" }))
225
+ ]
226
+ }).contract;
227
+ await visibleTaskFiles(host, contract.allowedWritePaths);
228
+ const baseline = {};
229
+ for (const path of contract.allowedWritePaths) {
230
+ const inspected = await host.workspace.inspectFile(path);
231
+ if (inspected.bytes > 65536 || (await host.workspace.readFile(path)).digest !== inspected.digest)
232
+ throw new Error("TASK_FILE_UNAVAILABLE");
233
+ baseline[path] = inspected.digest;
234
+ }
235
+ await clean();
236
+ await initializeHarnessTaskBudget(host, contract.taskId, brief.budget);
237
+ return { schemaVersion: 1, budgetVersion: 1, goal: brief.goal, constraints: brief.constraints, contract, baseline };
238
+ }
239
+ async function currentClientTask(host, runId, allowedRunIds) {
240
+ const state = await host.store.load(runId, host.config.scope);
241
+ const ledger = state && readTaskAcceptanceLedger(state);
242
+ if (!state || !ledger)
243
+ throw new Error("TASK_CONTRACT_UNAVAILABLE");
244
+ const taskId = ledger.revisions.at(-1).contract.taskId;
245
+ const evidence = await readTaskContinuityEvidence(host, taskId, { allowedRunIds });
246
+ const latest = evidence.report.runs.at(-1), current = evidence.report.currentContract;
247
+ if (!current || latest?.runId !== runId || latest.revision !== state.revision)
248
+ throw new Error("TASK_REVIEW_CHANGED");
249
+ return { state, ledger, evidence, current, taskId };
250
+ }
251
+ async function persistClientTaskPreparationFailure(host, state, contract) {
252
+ const account = await openHarnessTaskBudget(host, contract.taskId);
253
+ const before = await readTaskContinuityEvidence(host, contract.taskId);
254
+ const ledger = before.report.runs.at(-1)?.ledger ?? nextTaskAcceptanceLedger(undefined, contract);
255
+ if (ledger.revisions.at(-1).digest !== compileTaskAcceptanceContract(contract).digest)
256
+ throw new Error("TASK_REVIEW_CHANGED");
257
+ await account.run(state.runId, async () => {
258
+ await before.verify();
259
+ const summary = await account.summary();
260
+ await host.store.save({
261
+ ...state,
262
+ ...host.agent.harness ? { harness: host.agent.harness } : {},
263
+ metadata: {
264
+ ...state.metadata,
265
+ [TASK_ACCEPTANCE_KEY]: JSON.parse(JSON.stringify(ledger)),
266
+ [TASK_BUDGET_KEY]: JSON.parse(JSON.stringify(summary)),
267
+ [TASK_SOURCE_KEY]: captureTaskSources(state.metadata, state.messages)
268
+ }
269
+ }, { expectedRevision: 0 });
270
+ });
271
+ }
272
+ async function keepClientTask(host, runId, allowedRunIds, expected) {
273
+ const initial = await currentClientTask(host, runId, allowedRunIds);
274
+ const account = await openHarnessTaskBudget(host, initial.taskId);
275
+ const ownerId = randomUUID();
276
+ if (!await host.store.acquireLease(account.accountRunId, { ownerId, ttlMs: 30000 }, host.config.scope))
277
+ throw new Error("TASK_REVIEW_BUSY");
278
+ try {
279
+ const { state, evidence, current } = await currentClientTask(host, runId, allowedRunIds);
280
+ const latest = evidence.report.runs.at(-1);
281
+ if (state.revision !== expected.runRevision || current.digest !== expected.contractDigest || evidence.report.budget?.revision !== expected.budgetRevision || state.status !== "completed" || evidence.report.reasons.length || latest.checks.some((c) => c.status !== "confirmed") || latest.snapshot?.snapshotDigest !== expected.snapshotDigest || latest.delivery?.snapshotDigest !== expected.snapshotDigest)
282
+ throw new Error("TASK_REVIEW_CHANGED");
283
+ await visibleTaskFiles(host, current.contract.allowedWritePaths);
284
+ if ((await taskGitReview(host)).identity !== expected.gitIdentity)
285
+ throw new Error("TASK_REVIEW_CHANGED");
286
+ await evidence.verifyAll();
287
+ if (!await host.store.renewLease(account.accountRunId, { ownerId, ttlMs: 30000 }, host.config.scope))
288
+ throw new Error("TASK_REVIEW_BUSY");
289
+ const decision = {
290
+ schemaVersion: 1,
291
+ kind: "accepted",
292
+ runId,
293
+ contractDigest: current.digest,
294
+ contractRevision: current.revision,
295
+ snapshotDigest: expected.snapshotDigest,
296
+ reviewedRunRevision: expected.runRevision,
297
+ at: Date.now(),
298
+ provenance: "explicit_operator_review"
299
+ };
300
+ await host.store.save({ ...state, metadata: { ...state.metadata, [TASK_HUMAN_DECISION_KEY]: decision } }, { expectedRevision: state.revision });
301
+ return decision;
302
+ } finally {
303
+ await host.store.releaseLease(account.accountRunId, ownerId, host.config.scope);
304
+ }
305
+ }
306
+
307
+ // src/client/task-projection-host.ts
308
+ import { createHash as createHash2 } from "node:crypto";
309
+ import { createRedactionPolicy } from "@zhivex-ai/agents";
310
+ class TaskProjectionFault extends Error {
311
+ causeCode;
312
+ constructor(causeCode) {
313
+ super(causeCode);
314
+ this.causeCode = causeCode;
315
+ }
316
+ }
317
+ var fault = (cause) => {
318
+ throw new TaskProjectionFault(cause);
319
+ };
320
+ var hash2 = (value) => "sha256:" + createHash2("sha256").update(JSON.stringify(value)).digest("hex");
321
+ var object = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : {};
322
+ var sha = (value) => typeof value === "string" && /^sha256:[a-f0-9]{64}$/.test(value) ? value : null;
323
+ function redactTaskDisplayText(host, raw, sensitiveValues = []) {
324
+ const redaction = createRedactionPolicy({ includeEmails: true });
325
+ let safe = raw;
326
+ for (const secret of [host.workspace.root, ...sensitiveValues])
327
+ if (secret)
328
+ safe = safe.split(secret).join("[REDACTED]");
329
+ safe = redaction.redactText(safe).replace(/\b(?:sk|ghp|github_pat|xox[baprs])[-_][A-Za-z0-9_-]+/gi, "[REDACTED]").replace(/(^|[^A-Za-z0-9_./\\-])(?:\/(?!\/)|[A-Za-z]:[\\/]|\\\\)[^\s"'`<>\[\](){}]*/g, "$1[REDACTED_PATH]").replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g, "");
330
+ return safe;
331
+ }
332
+ async function projectHarnessTask(host, request, allowedRunIds, sensitiveValues = []) {
333
+ try {
334
+ if (host.config.execution.backend !== "none" || host.config.orchestration.profiles.length || host.agent.subagents?.length)
335
+ return fault("UNSUPPORTED_HOST");
336
+ if (!allowedRunIds.includes(request.runId))
337
+ return fault("OUT_OF_SCOPE");
338
+ let state = await host.store.load(request.runId, host.config.scope);
339
+ if (!state)
340
+ return fault("EVIDENCE_UNAVAILABLE");
341
+ const requestedState = state;
342
+ let sourceRunId = request.runId;
343
+ const prior = state.metadata?.clientTaskPriorRunV1;
344
+ if (!readTaskAcceptanceLedger(state) && typeof prior === "string" && ["failed", "cancelled"].includes(state.status) && !state.steps.length && !state.toolResults.length && !state.pendingApprovals.length) {
345
+ if (prior === request.runId || !allowedRunIds.includes(prior))
346
+ return fault("OUT_OF_SCOPE");
347
+ const retained = await host.store.load(prior, host.config.scope);
348
+ if (!retained)
349
+ return fault("EVIDENCE_UNAVAILABLE");
350
+ state = retained;
351
+ sourceRunId = prior;
352
+ }
353
+ const ledger = readTaskAcceptanceLedger(state);
354
+ if (!ledger)
355
+ return fault("CONTRACT_UNAVAILABLE");
356
+ const taskId = ledger.revisions.at(-1).contract.taskId;
357
+ const rawBudget = state.metadata?.[TASK_BUDGET_KEY];
358
+ const accountId = TaskBudget.accountId(host.config.scope, taskId);
359
+ if (rawBudget === undefined && await host.store.load(accountId, host.config.scope))
360
+ return fault("EVIDENCE_UNAVAILABLE");
361
+ const binding = inspectTaskBudgetSummary(rawBudget);
362
+ if (rawBudget !== undefined && (!binding || binding.taskId !== taskId))
363
+ return fault("EVIDENCE_UNAVAILABLE");
364
+ const evidence = await readTaskContinuityEvidence(host, taskId, {
365
+ allowedRunIds,
366
+ ...rawBudget === undefined ? { legacyRunId: request.runId } : {}
367
+ });
368
+ const report = evidence.report, latest = report.runs.at(-1), current = report.currentContract;
369
+ if (report.reasons.some((reason) => ["TASK_CONTINUITY_RUN_MISSING", "TASK_CONTINUITY_SOURCE_INVALID"].includes(reason)))
370
+ return fault("EVIDENCE_UNAVAILABLE");
371
+ if (sourceRunId !== request.runId && (!report.budget || report.budget.runs.includes(request.runId)))
372
+ return fault("EVIDENCE_UNAVAILABLE");
373
+ if (!latest || !current || !report.runs.some((run) => run.runId === sourceRunId))
374
+ return fault("EVIDENCE_UNAVAILABLE");
375
+ if (binding && (binding.accountRunId !== report.budget?.accountRunId || binding.coordinatorId !== report.budget?.coordinatorId))
376
+ return fault("EVIDENCE_UNAVAILABLE");
377
+ let textTruncated = false;
378
+ const text = (raw) => {
379
+ const safe = redactTaskDisplayText(host, raw, sensitiveValues);
380
+ if (safe.length > 2048) {
381
+ textTruncated = true;
382
+ return safe.slice(0, 2047) + "…";
383
+ }
384
+ return safe;
385
+ };
386
+ const delivery = object(latest.delivery);
387
+ const deliveredDigest = sha(delivery.snapshotDigest);
388
+ const observedDigest = latest.snapshot?.snapshotDigest ?? null;
389
+ const correspondence = observedDigest && deliveredDigest && latest.snapshot && delivery.runId === latest.runId && delivery.executionIdentity === latest.snapshot.executionIdentity && delivery.patchId === latest.snapshot.patchId ? observedDigest === deliveredDigest ? "current" : "stale" : deliveredDigest ? "stale" : "missing";
390
+ const effects = report.runs.flatMap((run) => run.effects);
391
+ const missingEvidence = report.reasons.some((reason) => /EFFECT_(?:EVIDENCE_MISSING|UNCERTAIN)/.test(reason));
392
+ const budget = report.budget;
393
+ const reasons = [...report.reasons];
394
+ if (budget && Object.values(budget.remaining).some((value) => value === 0))
395
+ reasons.push("TASK_BUDGET_EXHAUSTED");
396
+ const blocked = reasons.length > 0;
397
+ const execution = ["created", "queued", "running", "waiting_approval", "cancel_requested", "completed", "failed", "cancelled", "timed_out"].find((status) => status === latest.status) ?? "unknown";
398
+ const structure = execution === "completed" && correspondence === "current" && latest.hostPolicyCompatible && latest.checks.every((check) => check.status === "confirmed") && !missingEvidence ? "verified" : "incomplete";
399
+ const task = {
400
+ reference: hash2(["task", taskId]),
401
+ contractRevision: current.revision,
402
+ contractDigest: current.digest,
403
+ runRevision: latest.revision ?? 0,
404
+ objective: latest.objective === null ? null : text(latest.objective),
405
+ objectiveSource: latest.objective === null ? "unavailable" : latest.briefStatus === "current" ? "operator_brief" : "retained_operator_source",
406
+ observedRunId: latest.runId,
407
+ historicalRequest: latest.runId !== request.runId,
408
+ execution,
409
+ artifact: { observedDigest, deliveredDigest, correspondence, paths: current.contract.allowedWritePaths.map(text), contents: "not_included" },
410
+ review: {
411
+ structure,
412
+ semantic: "pending",
413
+ acceptance: "not_recorded",
414
+ human: current.contract.humanReview.map((row) => ({ id: text(row.id), requirement: text(row.requirement), status: "pending" })),
415
+ checks: latest.checks.map((check) => ({ id: text(check.id), status: check.status, provenance: "contract_snapshot_and_tool_journal" }))
416
+ },
417
+ budget: {
418
+ availability: budget ? "authoritative" : "legacy_not_enabled",
419
+ revision: budget?.revision ?? null,
420
+ confirmed: budget?.confirmed ?? null,
421
+ reserved: budget?.reserved ?? null,
422
+ unknown: budget?.unknown ?? null,
423
+ remaining: budget?.remaining ?? null,
424
+ limits: budget?.limits ?? null,
425
+ estimated: null,
426
+ estimation: "not_separately_recorded",
427
+ usageComplete: budget?.usageComplete ?? null,
428
+ admissionsClosed: budget?.admissionsClosed ?? null,
429
+ monetary: { estimatedConfirmedUsd: budget?.monetaryDetails.estimatedConfirmedUsd ?? null, reservedUsd: budget?.monetaryDetails.reservedUsd ?? null, unknownHeldUsd: budget?.monetaryDetails.unknownHeldUsd ?? null, remainingUsd: budget?.monetaryDetails.remainingUsd ?? null, unknown: !budget || !budget.monetaryDetails.costComplete, costKind: "estimate-not-invoice" }
430
+ },
431
+ effects: {
432
+ readOnly: effects.filter((row) => row.status === "read_only").length,
433
+ recorded: effects.filter((row) => row.status === "recorded").length,
434
+ unknown: effects.filter((row) => row.status === "unknown").length,
435
+ missingEvidence,
436
+ automaticReplay: false,
437
+ providerStopped: "unconfirmed"
438
+ },
439
+ nextAction: {
440
+ kind: blocked ? "reconcile" : ["created", "queued", "running", "cancel_requested"].includes(execution) ? "wait" : execution === "waiting_approval" ? "review_approval" : structure === "verified" ? "review_delivery" : "explicit_continuation",
441
+ permitted: "read_only",
442
+ authorization: "required_for_execution",
443
+ preserves: "contract_receipts_budget_and_effects",
444
+ automaticReplay: false
445
+ },
446
+ diagnostic: execution === "failed" ? { code: "EXECUTION_FAILED", cause: "execution_terminal_failure", impact: "acceptance_not_established", safeAction: missingEvidence ? "reconcile" : "review_configuration" } : execution === "cancelled" ? { code: "EXECUTION_CANCELLED", cause: "operator_or_runtime_cancellation", impact: "acceptance_not_established", safeAction: "reconcile" } : execution === "timed_out" ? { code: "EXECUTION_TIMED_OUT", cause: "execution_deadline", impact: "acceptance_not_established", safeAction: "reconcile" } : structure !== "verified" ? { code: "EVIDENCE_INCOMPLETE", cause: "receipts_missing_or_stale", impact: "acceptance_not_established", safeAction: "review_delivery" } : null,
447
+ reasons,
448
+ provenance: "host_durable_contract_journal_budget_and_fresh_native_snapshot",
449
+ textTruncated
450
+ };
451
+ await evidence.verifyAll();
452
+ if (rawBudget === undefined && await host.store.load(accountId, host.config.scope))
453
+ return fault("SNAPSHOT_CHANGED");
454
+ if (JSON.stringify(await host.store.load(request.runId, host.config.scope)) !== JSON.stringify(requestedState))
455
+ return fault("SNAPSHOT_CHANGED");
456
+ const projection = { schemaVersion: 1, ...request, task };
457
+ if (Buffer.byteLength(JSON.stringify(projection)) > 64 * 1024)
458
+ return fault("PAYLOAD_LIMIT");
459
+ return harnessTaskProjectionSchema.parse(projection);
460
+ } catch (error) {
461
+ if (error instanceof TaskProjectionFault)
462
+ throw error;
463
+ const code = error instanceof Error ? error.message.split(":")[0] : "";
464
+ if (code === "TASK_PROJECTION_SCOPE_MISMATCH")
465
+ return fault("OUT_OF_SCOPE");
466
+ if (code === "TASK_CONTINUITY_CHANGED")
467
+ return fault("SNAPSHOT_CHANGED");
468
+ if (code === "TASK_CONTINUITY_CAPACITY" || code === "TASK_ACCEPTANCE_HISTORY_CAPACITY")
469
+ return fault("PAYLOAD_LIMIT");
470
+ return fault("EVIDENCE_UNAVAILABLE");
471
+ }
472
+ }
473
+
128
474
  // src/approvals/approval-diff.ts
129
- import { createHash } from "node:crypto";
475
+ import { createHash as createHash3 } from "node:crypto";
130
476
  import { z } from "zod";
131
477
  var APPROVAL_DIFFS_KEY = "clientApprovalDiffsV1";
132
- var sha = z.string().regex(/^sha256:[a-f0-9]{64}$/);
133
- var file = z.object({ path: z.string().max(1024), expectedDigest: sha.nullable(), before: z.string().nullable(), after: z.string().nullable(), afterDigest: sha.nullable(), beforeMode: z.number().int().optional(), afterMode: z.number().int().optional(), operation: z.enum(["create", "update", "delete"]).optional() }).strict();
134
- var archive = z.object({ approvalId: z.string().max(256), digest: z.string().regex(/^[a-f0-9]{64}$/), proposalId: sha, files: z.array(file).min(1).max(50) }).strict();
478
+ var sha2 = z.string().regex(/^sha256:[a-f0-9]{64}$/);
479
+ var file = z.object({ path: z.string().max(1024), expectedDigest: sha2.nullable(), before: z.string().nullable(), after: z.string().nullable(), afterDigest: sha2.nullable(), beforeMode: z.number().int().optional(), afterMode: z.number().int().optional(), operation: z.enum(["create", "update", "delete"]).optional() }).strict();
480
+ var archive = z.object({ approvalId: z.string().max(256), digest: z.string().regex(/^[a-f0-9]{64}$/), proposalId: sha2, files: z.array(file).min(1).max(50) }).strict();
135
481
  var archives = z.array(archive).max(512);
136
482
  var maxBytes = 2 * 1024 * 1024;
137
- var contentDigest = (content) => content === null ? null : `sha256:${createHash("sha256").update(content).digest("hex")}`;
483
+ var contentDigest = (content) => content === null ? null : `sha256:${createHash3("sha256").update(content).digest("hex")}`;
138
484
  function read(state) {
139
485
  const value = state.metadata?.[APPROVAL_DIFFS_KEY] ?? [];
140
486
  if (Buffer.byteLength(JSON.stringify(value)) > maxBytes)
@@ -221,7 +567,7 @@ async function attachApprovalPreviews(run, workspace, options = {}) {
221
567
  }
222
568
 
223
569
  // src/client/adapter.ts
224
- import { createHash as createHash2, randomUUID } from "node:crypto";
570
+ import { createHash as createHash4, randomUUID as randomUUID2 } from "node:crypto";
225
571
  var canonical = (value) => {
226
572
  if (Array.isArray(value))
227
573
  return `[${value.map(canonical).join(",")}]`;
@@ -229,7 +575,7 @@ var canonical = (value) => {
229
575
  return `{${Object.entries(value).filter(([, v]) => v !== undefined).sort(([a], [b]) => a.localeCompare(b)).map(([k, v]) => `${JSON.stringify(k)}:${canonical(v)}`).join(",")}}`;
230
576
  return JSON.stringify(value) ?? "null";
231
577
  };
232
- var digest = (value) => createHash2("sha256").update(canonical(value)).digest("hex");
578
+ var digest = (value) => createHash4("sha256").update(canonical(value)).digest("hex");
233
579
 
234
580
  class ClientFault extends Error {
235
581
  code;
@@ -281,7 +627,7 @@ var createHarnessClientAdapter = async (harness, options = {}) => {
281
627
  scope: harness.config.scope
282
628
  });
283
629
  const projectId = `project_${digest([sessions.workspaceKey, sessions.scopeKey])}`;
284
- const connectionId = `connection_${randomUUID()}`;
630
+ const connectionId = `connection_${randomUUID2()}`;
285
631
  let checkpoints;
286
632
  const checkpointStore = async () => checkpoints ??= await openWorkspaceCheckpointStore(harness.workspace, sessions);
287
633
  const restoreDocument = async (operation) => {
@@ -298,8 +644,12 @@ var createHarnessClientAdapter = async (harness, options = {}) => {
298
644
  }, preview };
299
645
  };
300
646
  let closed = false, busy = false;
647
+ let taskSequence = 0;
648
+ const taskReviews = new Map;
649
+ const taskProjection = (s, runId) => projectHarnessTask(harness, { connectionId, projectId, sessionId: s.sessionId, runId, sequence: ++taskSequence }, s.runs.map((r) => r.runId), options.taskProjectionSensitiveValues);
650
+ const safeTaskDiff = (raw) => redactTaskDisplayText(harness, raw, options.taskProjectionSensitiveValues);
301
651
  let active;
302
- const invoke = async (sessionId, input, prompt) => {
652
+ const invoke = async (sessionId, input, prompt, taskOptions) => {
303
653
  const runId = "state" in input ? input.state.runId : input.runId;
304
654
  let settle;
305
655
  const settled = new Promise((resolve) => {
@@ -308,6 +658,7 @@ var createHarnessClientAdapter = async (harness, options = {}) => {
308
658
  const controller = new AbortController;
309
659
  active = { sessionId, runId, controller, settled };
310
660
  let prepared;
661
+ let invocationStarted = false;
311
662
  try {
312
663
  if (prompt !== undefined)
313
664
  await options.onPrompt?.(sessionId, runId, prompt);
@@ -321,10 +672,12 @@ var createHarnessClientAdapter = async (harness, options = {}) => {
321
672
  active.runtime = runtime;
322
673
  const mutationOffset = runtime.workspace.mutationAudit().length;
323
674
  const policyEvidence = createPolicyDecisionEvidence();
675
+ invocationStarted = true;
324
676
  const result = await observeHarnessPolicyDecisions(async (event) => {
325
677
  policyEvidence.append(event);
326
678
  await options.onPolicyDecision?.(sessionId, runId, event);
327
- }, () => runHarness(runtime, { ...input, abortSignal: controller.signal }, {
679
+ }, () => (taskOptions ? runHarnessTask : runHarness)(runtime, { ...input, abortSignal: controller.signal }, {
680
+ ...taskOptions,
328
681
  onEvent: (event) => options.onEvent?.(sessionId, runId, event)
329
682
  }));
330
683
  const document = { ...runResultDocument(result, runtime), policyEvidence: policyEvidence.snapshot(), mutations: runtime.workspace.mutationAudit().slice(mutationOffset) };
@@ -341,7 +694,7 @@ var createHarnessClientAdapter = async (harness, options = {}) => {
341
694
  persisted = await harness.store.load(runId, harness.config.scope);
342
695
  if (!persisted && !("state" in input)) {
343
696
  const now = Date.now();
344
- await harness.store.save({
697
+ const fallback = {
345
698
  schemaVersion: 1,
346
699
  revision: 0,
347
700
  runId,
@@ -358,8 +711,21 @@ var createHarnessClientAdapter = async (harness, options = {}) => {
358
711
  pendingApprovals: [],
359
712
  startedAt: now,
360
713
  updatedAt: now,
361
- error: { message: "Run stopped before initialization." }
362
- });
714
+ error: { message: "Run stopped before initialization." },
715
+ metadata: input.metadata ?? {}
716
+ };
717
+ if (taskOptions?.taskAdmissionReview && taskOptions.taskAcceptance) {
718
+ const budget = await (await openHarnessTaskBudget(harness, taskOptions.taskAcceptance.taskId)).summary();
719
+ if (budget.runs.includes(runId))
720
+ throw new Error("TASK_ADMISSION_STATE_UNAVAILABLE");
721
+ await harness.store.save({ ...fallback, metadata: {
722
+ ...fallback.metadata,
723
+ clientTaskPriorRunV1: taskOptions.taskAdmissionReview.runId
724
+ } }, { expectedRevision: 0 });
725
+ } else if (!invocationStarted && taskOptions?.taskAcceptance)
726
+ await persistClientTaskPreparationFailure(harness, fallback, taskOptions.taskAcceptance);
727
+ else
728
+ await harness.store.save(fallback);
363
729
  persisted = await harness.store.load(runId, harness.config.scope);
364
730
  }
365
731
  } catch {}
@@ -423,6 +789,135 @@ var createHarnessClientAdapter = async (harness, options = {}) => {
423
789
  let s = await refresh(await getSession(c.sessionId));
424
790
  if (c.method === "session.get")
425
791
  return { kind: "session", session: sessionDocument(s) };
792
+ if (c.method === "task.review") {
793
+ const projection = await taskProjection(s, c.runId);
794
+ const detail = await currentClientTask(harness, c.runId, s.runs.map((r) => r.runId));
795
+ const git = await taskGitReview(harness);
796
+ await detail.evidence.verifyAll();
797
+ const terminal = ["completed", "failed", "cancelled", "timed_out"].includes(projection.task.execution);
798
+ const blockers = projection.task.reasons.filter((r) => r !== "TASK_BUDGET_CANCELLED");
799
+ const canContinue = terminal && !blockers.length && projection.task.budget.availability === "authoritative" && projection.task.budget.usageComplete === true;
800
+ const diff = safeTaskDiff(git.text);
801
+ const complete = diff === git.text && !projection.task.textTruncated && detail.current.contract.humanReview.every((h) => safeTaskDiff(h.requirement) === h.requirement);
802
+ const canKeep = canContinue && complete && projection.task.review.structure === "verified";
803
+ const canRevise = terminal && !blockers.length && detail.current.contract.humanReview.length < 9;
804
+ const now = (options.now ?? Date.now)();
805
+ for (const [id, review] of taskReviews)
806
+ if (review.expiresAt <= now)
807
+ taskReviews.delete(id);
808
+ if (taskReviews.size >= 128)
809
+ return fail("CAPACITY_EXCEEDED");
810
+ const reviewId = randomUUID2(), expiresAt = now + 300000;
811
+ taskReviews.set(reviewId, {
812
+ sessionId: s.sessionId,
813
+ runId: c.runId,
814
+ expiresAt,
815
+ identity: taskReviewIdentity(projection),
816
+ gitIdentity: git.identity,
817
+ projection,
818
+ canKeep,
819
+ canContinue,
820
+ canRevise
821
+ });
822
+ return { kind: "taskReview", review: {
823
+ reviewId,
824
+ expiresAt,
825
+ projection,
826
+ diff,
827
+ complete,
828
+ canKeep,
829
+ canContinue,
830
+ canRevise,
831
+ humanDecision: await taskHumanDecision(harness, projection)
832
+ } };
833
+ }
834
+ if (c.method === "task.keep" || c.method === "task.revise" || c.method === "task.continue") {
835
+ const review = taskReviews.get(c.reviewId);
836
+ taskReviews.delete(c.reviewId);
837
+ if (!review || review.sessionId !== s.sessionId || review.runId !== c.runId || review.expiresAt <= (options.now ?? Date.now)())
838
+ return fail("REVISION_CONFLICT");
839
+ const projection = await taskProjection(s, c.runId);
840
+ if (taskReviewIdentity(projection) !== review.identity || (await taskGitReview(harness)).identity !== review.gitIdentity)
841
+ return fail("REVISION_CONFLICT");
842
+ const detail = await currentClientTask(harness, c.runId, s.runs.map((r) => r.runId));
843
+ if (c.method === "task.keep") {
844
+ if (!review.canKeep || !projection.task.artifact.observedDigest || projection.task.budget.revision === null)
845
+ return fail("INVALID_STATE");
846
+ await keepClientTask(harness, c.runId, s.runs.map((r) => r.runId), {
847
+ runRevision: projection.task.runRevision,
848
+ contractDigest: projection.task.contractDigest,
849
+ snapshotDigest: projection.task.artifact.observedDigest,
850
+ budgetRevision: projection.task.budget.revision,
851
+ gitIdentity: review.gitIdentity
852
+ });
853
+ } else if (c.method === "task.revise") {
854
+ if (!review.canRevise)
855
+ return fail("INVALID_STATE");
856
+ await reviseHarnessTaskAcceptance(harness, {
857
+ runId: c.runId,
858
+ expectedRunRevision: projection.task.runRevision,
859
+ expectedContractRevision: projection.task.contractRevision,
860
+ contract: {
861
+ ...detail.current.contract,
862
+ humanReview: [...detail.current.contract.humanReview, { id: "correction-" + randomUUID2(), requirement: c.correction, status: "pending" }]
863
+ }
864
+ });
865
+ } else {
866
+ if (!review.canContinue)
867
+ return fail("INVALID_STATE");
868
+ const runId = "run_" + randomUUID2();
869
+ s = await sessions.appendRun(s.sessionId, { runId, provider: harness.config.provider, model: harness.config.model, status: "created" }, { expectedRevision: s.revision });
870
+ const contract = detail.current.contract;
871
+ const brief = {
872
+ schemaVersion: 1,
873
+ budgetVersion: 1,
874
+ goal: contract.humanReview.find((h) => h.id === "operator")?.requirement ?? projection.task.objective ?? "Retained task",
875
+ constraints: contract.humanReview.filter((h) => h.id !== "operator").map((h) => h.requirement),
876
+ contract,
877
+ baseline: {}
878
+ };
879
+ const result = await invoke(s.sessionId, {
880
+ runId,
881
+ scope: harness.config.scope,
882
+ metadata: { zhivexCodeTaskV1: JSON.parse(JSON.stringify(brief)) },
883
+ messages: appendUserMessage(terminalContinuationMessages(detail.state.messages), c.prompt)
884
+ }, c.prompt, {
885
+ taskAcceptance: contract,
886
+ taskBudgetExisting: true,
887
+ taskBudgetContinue: true,
888
+ taskAdmissionReview: {
889
+ runId: c.runId,
890
+ runRevision: projection.task.runRevision,
891
+ contractDigest: projection.task.contractDigest,
892
+ snapshotDigest: projection.task.artifact.observedDigest,
893
+ budgetRevision: projection.task.budget.revision
894
+ }
895
+ });
896
+ s = await sessions.updateRun(s.sessionId, runId, { status: sessionStatus(result.state.status) });
897
+ return { kind: "run", session: sessionDocument(s), run: await documentRun(result.state) };
898
+ }
899
+ const updated = await taskProjection(s, c.runId);
900
+ return { kind: "task", projection: updated, humanDecision: await taskHumanDecision(harness, updated) };
901
+ }
902
+ if (c.method === "task.start") {
903
+ if (s.revision !== c.expectedRevision || s.runs.length)
904
+ return fail("REVISION_CONFLICT");
905
+ const brief = await prepareClientTask(harness, c.brief);
906
+ await persistHarnessTaskDraft(harness, s.sessionId, brief);
907
+ const runId = "run_" + randomUUID2();
908
+ s = await sessions.appendRun(s.sessionId, { runId, provider: harness.config.provider, model: harness.config.model, status: "created" }, { expectedRevision: s.revision });
909
+ const prompt = "Operator goal: " + brief.goal + `
910
+ Constraints: ` + brief.constraints.join("; ") + `
911
+ Exact editable files: ` + brief.contract.allowedWritePaths.join(", ") + ". Execute every declared check after the final edit. Passing checks still require human acceptance.";
912
+ const result = await invoke(s.sessionId, {
913
+ runId,
914
+ scope: harness.config.scope,
915
+ metadata: { zhivexCodeTaskV1: JSON.parse(JSON.stringify(brief)) },
916
+ messages: appendUserMessage([], prompt)
917
+ }, brief.goal, { taskAcceptance: brief.contract, taskBudgetExisting: true });
918
+ s = await sessions.updateRun(s.sessionId, runId, { status: sessionStatus(result.state.status) });
919
+ return { kind: "run", session: sessionDocument(s), run: await documentRun(result.state) };
920
+ }
426
921
  if (c.method === "checkpoint.list") {
427
922
  const store = await checkpointStore();
428
923
  return { kind: "checkpoints", checkpoints: store.listCheckpoints(s.sessionId), restores: store.listRestores(s.sessionId) };
@@ -468,9 +963,11 @@ var createHarnessClientAdapter = async (harness, options = {}) => {
468
963
  return { kind: "session", session: sessionDocument(await sessions.rename(s.sessionId, c.title, { expectedRevision: c.expectedRevision })) };
469
964
  const last = s.runs.at(-1);
470
965
  const previous = last ? await getRun(s, last.runId) : undefined;
966
+ if (previous && readTaskAcceptanceLedger(previous) || await readHarnessTaskDraft(harness.store, harness.config.scope, s.sessionId))
967
+ return fail("INVALID_STATE");
471
968
  if (previous && !["completed", "failed", "cancelled", "timed_out"].includes(previous.status))
472
969
  return fail("INVALID_STATE");
473
- const runId = `run_${randomUUID()}`;
970
+ const runId = `run_${randomUUID2()}`;
474
971
  s = await sessions.appendRun(s.sessionId, { runId, provider: harness.config.provider, model: harness.config.model, status: "created" }, { expectedRevision: c.expectedRevision });
475
972
  const result = await invoke(s.sessionId, {
476
973
  runId,
@@ -488,7 +985,7 @@ var createHarnessClientAdapter = async (harness, options = {}) => {
488
985
  return fail("REVISION_CONFLICT");
489
986
  if (c.method === "run.cancel") {
490
987
  if (["created", "running", "queued", "cancel_requested"].includes(state.status)) {
491
- const ownerId = `cancel_${randomUUID()}`;
988
+ const ownerId = `cancel_${randomUUID2()}`;
492
989
  if (!harness.store.acquireLease || !harness.store.releaseLease)
493
990
  return fail("INVALID_STATE");
494
991
  if (!await harness.store.acquireLease(state.runId, { ownerId, ttlMs: 30000 }, harness.config.scope))
@@ -607,7 +1104,7 @@ var createHarnessClientAdapter = async (harness, options = {}) => {
607
1104
  return { ok: false, error: { code: "CONNECTION_EXPIRED" } };
608
1105
  if (!versions.includes(1))
609
1106
  return { ok: false, error: { code: "VERSION_UNSUPPORTED" } };
610
- return { ok: true, protocolVersion: 1, connectionId, projectId, capabilities: ["project.get", "policy.get", "session.list", "session.create", "session.get", "session.rename", "run.start", "run.get", "approval.resolve", "run.cancel.checkpoint", "run.cancel.active", "idempotency.connection", "revision.precondition", "checkpoint.list", "checkpoint.inspect", "checkpoint.capture", "restore.prepare", "restore.get", "restore.apply", "restore.recoverFork"] };
1107
+ return { ok: true, protocolVersion: 1, connectionId, projectId, capabilities: ["task.control.v1", "task.start", "task.review", "task.keep", "task.revise", "task.continue", "task.get", "task.projection.v1", "project.get", "policy.get", "session.list", "session.create", "session.get", "session.rename", "run.start", "run.get", "approval.resolve", "run.cancel.checkpoint", "run.cancel.active", "idempotency.connection", "revision.precondition", "checkpoint.list", "checkpoint.inspect", "checkpoint.capture", "restore.prepare", "restore.get", "restore.apply", "restore.recoverFork"] };
611
1108
  },
612
1109
  async dispatch(value) {
613
1110
  const hostReviewed = Boolean(value && typeof value === "object" && reviewedRequests.delete(value));
@@ -622,8 +1119,36 @@ var createHarnessClientAdapter = async (harness, options = {}) => {
622
1119
  if (closed || request.connectionId !== connectionId)
623
1120
  return error("CONNECTION_EXPIRED");
624
1121
  const c = request.command;
1122
+ if (c.method === "task.get" && !hostReviewed) {
1123
+ const sequence = ++taskSequence;
1124
+ try {
1125
+ if (c.projectId !== projectId)
1126
+ throw new TaskProjectionFault("OUT_OF_SCOPE");
1127
+ if (c.projectionVersion !== 1)
1128
+ throw new TaskProjectionFault("UNSUPPORTED_VERSION");
1129
+ if (!Number.isSafeInteger(sequence))
1130
+ throw new TaskProjectionFault("PAYLOAD_LIMIT");
1131
+ const session = await sessions.get(c.sessionId);
1132
+ if (!session || session.archivedAt || session.deletedAt || !session.runs.some((run) => run.runId === c.runId))
1133
+ throw new TaskProjectionFault("OUT_OF_SCOPE");
1134
+ const projection = await projectHarnessTask(harness, { connectionId, projectId, sessionId: c.sessionId, runId: c.runId, sequence }, session.runs.map((run) => run.runId), options.taskProjectionSensitiveValues);
1135
+ if (JSON.stringify(await sessions.get(c.sessionId)) !== JSON.stringify(session))
1136
+ throw new TaskProjectionFault("SNAPSHOT_CHANGED");
1137
+ return { protocolVersion: 1, requestId: request.requestId, ok: true, data: { kind: "task", projection, humanDecision: await taskHumanDecision(harness, projection) } };
1138
+ } catch (cause) {
1139
+ const finite = cause instanceof TaskProjectionFault ? cause.causeCode : "EVIDENCE_UNAVAILABLE";
1140
+ const code = finite === "OUT_OF_SCOPE" ? "NOT_FOUND" : finite === "UNSUPPORTED_VERSION" ? "VERSION_UNSUPPORTED" : finite === "SNAPSHOT_CHANGED" ? "REVISION_CONFLICT" : finite === "PAYLOAD_LIMIT" ? "CAPACITY_EXCEEDED" : "INVALID_STATE";
1141
+ return { protocolVersion: 1, requestId: request.requestId, ok: false, error: { code, taskDiagnostic: {
1142
+ cause: finite,
1143
+ impact: "projection_unavailable",
1144
+ safeAction: finite === "SNAPSHOT_CHANGED" ? "retry_read" : ["UNSUPPORTED_VERSION", "UNSUPPORTED_HOST", "OUT_OF_SCOPE", "CONTRACT_UNAVAILABLE", "PAYLOAD_LIMIT"].includes(finite) ? "review_configuration" : "reconcile"
1145
+ } } };
1146
+ }
1147
+ }
625
1148
  if (hostReviewed && c.method !== "approval.resolve")
626
1149
  return error("INVALID_REQUEST");
1150
+ if (c.method === "task.get")
1151
+ return error("INVALID_REQUEST");
627
1152
  const key = "idempotencyKey" in c ? c.idempotencyKey : undefined;
628
1153
  const fingerprint = digest({ command: c, hostReviewed });
629
1154
  const existing = key ? receipts.get(key) : undefined;
@@ -697,6 +1222,7 @@ var createHarnessClientAdapter = async (harness, options = {}) => {
697
1222
  checkpoints?.close();
698
1223
  sessions.close();
699
1224
  receipts.clear();
1225
+ taskReviews.clear();
700
1226
  }
701
1227
  };
702
1228
  return adapter;