@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.
- package/CHANGELOG.md +29 -10
- package/README.md +7 -5
- package/SUPPORT.md +2 -2
- package/contracts/client-protocol-v1.schema.json +593 -0
- package/contracts/engine-api.json +156 -0
- package/contracts/public-api.json +12 -0
- package/contracts/stable-api-signatures.json +33 -33
- package/dist/acp-cli.js +54 -20
- package/dist/audit/index.d.ts +3 -0
- package/dist/audit/index.js +482 -0
- package/dist/audit/pack.d.ts +72 -0
- package/dist/audit-pack-e4vaq7np.js +532 -0
- package/dist/cli/arguments.d.ts +3 -1
- package/dist/cli/audit-pack.d.ts +24 -0
- package/dist/cli/cli-credentials.d.ts +6 -0
- package/dist/cli/cli-profiles.d.ts +4 -2
- package/dist/cli/node-engine.d.ts +2 -0
- package/dist/{cli-2h37qyvg.js → cli-1cf4dcnj.js} +245 -642
- package/dist/{cli-entry-w0hterjs.js → cli-entry-3s63zn4w.js} +40 -13
- package/dist/cli-entry-7f0cen32.js +9990 -0
- package/dist/cli-entry-a890x3kq.js +10348 -0
- package/dist/{cli-entry-20w6nh0k.js → cli-entry-hdtfef27.js} +439 -19
- package/dist/cli-entry-rgs6bxmy.js +484 -0
- package/dist/cli.js +27 -2
- package/dist/client/json-contracts.d.ts +4 -0
- package/dist/client/protocol.d.ts +133 -0
- package/dist/client/task-control-host.d.ts +400 -0
- package/dist/client/task-projection-host.d.ts +12 -0
- package/dist/client/task-projection.d.ts +168 -0
- package/dist/compat/code-support.js +28 -22
- package/dist/{config-qt69hkqj.js → config-r9fdhce6.js} +3 -3
- package/dist/{config-j9624t6t.js → config-vyajmksy.js} +1 -1
- package/dist/engine/acp.js +15 -11
- package/dist/engine/client.js +14 -10
- package/dist/engine/desktop-providers-v1.d.ts +2 -0
- package/dist/engine/desktop-providers-v1.js +23 -3
- package/dist/engine/desktop-state-v1.js +11 -8
- package/dist/engine/index.d.ts +3 -0
- package/dist/engine/index.js +101 -554
- package/dist/engine/mcp-stdio-v1.js +8 -6
- package/dist/engine/models.js +76 -4
- package/dist/engine/protocol.d.ts +2 -0
- package/dist/engine/protocol.js +110 -0
- package/dist/engine/service.js +15 -11
- package/dist/hostile-repository-demo.js +1095 -505
- package/dist/{index-937cb7y8.js → index-02pqtbbg.js} +559 -33
- package/dist/{index-pp474137.js → index-0zs2dz26.js} +766 -931
- package/dist/{index-8zjmnyw5.js → index-1265j7t2.js} +73 -6
- package/dist/index-4fx5f3k5.js +269 -0
- package/dist/{index-nmjdwd4p.js → index-4gt045j0.js} +109 -1
- package/dist/{index-6dft2vev.js → index-6599mpmy.js} +5 -3
- package/dist/{index-tfzta544.js → index-74kkpyqk.js} +9 -7
- package/dist/{index-61gthdz6.js → index-8sm2a1wa.js} +1 -1
- package/dist/index-c39197wq.js +552 -0
- package/dist/{index-5jhgatga.js → index-crta5wzv.js} +7 -6
- package/dist/{index-m34q25jj.js → index-d9zkmhz3.js} +143 -824
- package/dist/{index-pdmevfxv.js → index-dtb2c6ds.js} +5 -259
- package/dist/index-f6mqa5gk.js +1535 -0
- package/dist/index-j6zxnc8m.js +490 -0
- package/dist/{index-rkbf7dqg.js → index-mv8sb1ns.js} +5 -3
- package/dist/{index-xqxeypt5.js → index-mzydbzp8.js} +76 -4
- package/dist/{index-wg1sd3at.js → index-qfsrrnz6.js} +1 -1
- package/dist/{index-m82cfspw.js → index-sk3cap35.js} +877 -963
- package/dist/index-sstgcsr0.js +511 -0
- package/dist/index-wm9xeq1n.js +329 -0
- package/dist/index-xdbkcccf.js +719 -0
- package/dist/{index-s2k2edts.js → index-zxg4ve4j.js} +41 -1548
- package/dist/index.d.ts +3 -0
- package/dist/index.js +93 -77
- package/dist/internal/desktop/providers.d.ts +2 -0
- package/dist/models/catalog.d.ts +2 -0
- package/dist/persistence/sqlite-database.d.ts +1 -0
- package/dist/providers/azure-openai.d.ts +14 -0
- package/dist/providers/bedrock.d.ts +23 -0
- package/dist/providers/providers.d.ts +1 -1
- package/dist/runtime/durable-review-group.d.ts +33 -0
- package/dist/runtime/harness.d.ts +11 -0
- package/dist/runtime/run-interruption.d.ts +7 -0
- package/dist/runtime/shared-budget.d.ts +30 -0
- package/dist/runtime/task-continuity.d.ts +300 -77
- package/dist/service-cli.js +15 -11
- package/dist/{service-client-cli-fn54ev6w.js → service-client-cli-eqrdc9zq.js} +400 -305
- package/dist/verify-historical-migrations.js +1095 -505
- package/dist/zhx.js +27 -2
- package/docs/AUDIT_PACK.md +99 -0
- package/docs/AZURE_OPENAI.md +53 -0
- package/docs/BEDROCK.md +56 -0
- package/docs/CLI.md +15 -0
- package/docs/CLIENT_PROTOCOL.md +178 -0
- package/docs/CODE_RELEASE.md +31 -1
- package/docs/CODE_WEB_TASKS.md +116 -0
- package/docs/CONTEXT_ENGINEERING.md +59 -0
- package/docs/CREDENTIALS.md +21 -0
- package/docs/DURABLE_OPERATIONS.md +93 -1
- package/docs/ENGINE_API.md +5 -2
- package/docs/EXTENSIBILITY.md +2 -2
- package/docs/FIRST_USE.md +6 -6
- package/docs/MINIMUM_TASK_AUTHORITY.md +3 -3
- package/docs/PROJECT_MEMORY.md +1 -1
- package/docs/README.md +6 -1
- package/docs/STABILITY.md +3 -1
- package/docs/SUPPORT_MATRIX.md +1 -1
- package/docs/SUPPORT_MATRIX_1_1.md +1 -1
- package/docs/USAGE.md +3 -1
- package/docs/VERTEX.md +2 -2
- package/docs/WEB_LIMITS.md +4 -3
- package/docs/adr/0002-isolated-mcp-stdio.md +6 -3
- package/docs/support-matrix-1.1.json +3 -1
- package/docs/support-matrix.json +6 -4
- package/evaluations/code-task-ui.json +20 -0
- package/evaluations/representative-assembly-matrix.json +10 -1
- package/evaluations/task-projection.json +10 -0
- package/evaluations/task-recovery-lab.json +60 -0
- package/examples/README.md +3 -3
- package/package.json +23 -13
- package/dist/cli-entry-4ngptr62.js +0 -19900
- package/dist/index-m49jx72n.js +0 -220
|
@@ -1,40 +1,64 @@
|
|
|
1
1
|
import {
|
|
2
2
|
openWorkspaceCheckpointStore
|
|
3
|
-
} from "./index-
|
|
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
|
-
|
|
8
|
+
initializeHarnessTaskBudget,
|
|
13
9
|
issueExplicitReviewResponses,
|
|
14
10
|
issueRecordedApprovalResponses,
|
|
15
|
-
|
|
16
|
-
|
|
11
|
+
openHarnessTaskBudget,
|
|
12
|
+
persistHarnessTaskDraft,
|
|
13
|
+
readHarnessTaskDraft,
|
|
14
|
+
readTaskContinuityEvidence,
|
|
17
15
|
requiresExplicitHostReview,
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
reviseHarnessTaskAcceptance,
|
|
17
|
+
runHarness,
|
|
18
|
+
runHarnessTask
|
|
19
|
+
} from "./index-0zs2dz26.js";
|
|
20
20
|
import {
|
|
21
21
|
ASSISTANT_RESPONSE_KEY,
|
|
22
|
-
|
|
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
|
-
|
|
28
|
-
|
|
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-
|
|
56
|
+
} from "./index-xdbkcccf.js";
|
|
34
57
|
import {
|
|
35
58
|
CLI_JSON_SCHEMA_VERSION,
|
|
36
|
-
harnessClientRequestSchema
|
|
37
|
-
|
|
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
|
|
133
|
-
var file = z.object({ path: z.string().max(1024), expectedDigest:
|
|
134
|
-
var archive = z.object({ approvalId: z.string().max(256), digest: z.string().regex(/^[a-f0-9]{64}$/), proposalId:
|
|
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:${
|
|
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
|
|
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) =>
|
|
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_${
|
|
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
|
-
|
|
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_${
|
|
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_${
|
|
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;
|