@kylecheng3146/agent-ops 0.10.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -2
- package/dist/packages/cli/src/args.js +18 -53
- package/dist/packages/cli/src/bin.js +28 -29
- package/dist/packages/cli/src/cli.js +25 -17
- package/dist/packages/cli/src/codex-loop-process.js +0 -6
- package/dist/packages/cli/src/commands/advance.js +2 -1
- package/dist/packages/cli/src/commands/parallel.js +1 -1
- package/dist/packages/cli/src/commands/review.js +8 -11
- package/dist/packages/cli/src/commands/run.js +12 -3
- package/dist/packages/cli/src/commands/task.js +6 -50
- package/dist/packages/cli/src/commands/verify.js +7 -3
- package/dist/packages/cli/src/context.js +22 -4
- package/dist/packages/cli/src/default-base.js +26 -0
- package/dist/packages/cli/src/hook-process.js +19 -83
- package/dist/packages/cli/src/office-entry.js +8 -4
- package/dist/packages/cli/src/owned-run-step.js +4 -4
- package/dist/packages/cli/src/parallel-deps.js +0 -2
- package/dist/packages/cli/src/run-deps.js +12 -9
- package/dist/packages/cli/src/run-entry.js +123 -19
- package/dist/packages/cli/src/run-setup-entry.js +2 -2
- package/dist/runtime/src/adapters/agy/config.js +2 -3
- package/dist/runtime/src/adapters/claude/config.js +2 -2
- package/dist/runtime/src/adapters/codex/config.js +2 -5
- package/dist/runtime/src/hooks/completion-gate.js +0 -15
- package/dist/runtime/src/install/harness.js +7 -4
- package/dist/runtime/src/install/plan.js +3 -3
- package/dist/runtime/src/install/profiles.js +0 -2
- package/dist/runtime/src/office/scene.js +1 -1
- package/dist/runtime/src/office/server.js +43 -16
- package/dist/runtime/src/office/snapshot.js +5 -2
- package/dist/runtime/src/parallel/finish.js +139 -65
- package/dist/runtime/src/parallel/manage.js +0 -3
- package/dist/runtime/src/parallel/service.js +5 -1
- package/dist/runtime/src/review/execute.js +77 -11
- package/dist/runtime/src/review/packet.js +17 -6
- package/dist/runtime/src/review/runner.js +2 -0
- package/dist/runtime/src/run/controls.js +99 -1
- package/dist/runtime/src/run/hosts/codex.js +28 -1
- package/dist/runtime/src/run/hosts/protocol.js +5 -2
- package/dist/runtime/src/run/integration.js +35 -13
- package/dist/runtime/src/run/policy.js +65 -10
- package/dist/runtime/src/run/scheduler.js +10 -5
- package/dist/runtime/src/run/service.js +68 -21
- package/dist/runtime/src/run/supervisor.js +11 -2
- package/dist/runtime/src/run/transport.js +1 -0
- package/dist/runtime/src/run/verification.js +2 -2
- package/dist/runtime/src/schema/validate.js +10 -5
- package/dist/runtime/src/security/permissions.js +85 -0
- package/dist/runtime/src/task/completion.js +4 -42
- package/dist/runtime/src/task/contract.js +11 -0
- package/dist/runtime/src/task/service.js +34 -57
- package/dist/runtime/src/task/store.js +3 -7
- package/dist/runtime/src/verify/acceptance-coverage.js +2 -3
- package/dist/runtime/src/verify/acceptance-protocol.js +4 -3
- package/dist/runtime/src/verify/acceptance-replay.js +4 -7
- package/dist/runtime/src/verify/service.js +6 -23
- package/dist/runtime/src/verify/source-fingerprint.js +17 -4
- package/docs/en/guides/configuration.md +8 -4
- package/docs/en/guides/task-debug.md +39 -121
- package/docs/en/spec/acceptance-and-evidence.md +31 -19
- package/docs/en/spec/harness-adapters.md +35 -12
- package/docs/en/spec/loop-engineering.md +40 -6
- package/docs/en/spec/review.md +6 -0
- package/docs/zh-TW/guides/configuration.md +6 -3
- package/docs/zh-TW/guides/task-debug.md +27 -66
- package/docs/zh-TW/spec/acceptance-and-evidence.md +19 -18
- package/docs/zh-TW/spec/harness-adapters.md +28 -9
- package/docs/zh-TW/spec/loop-engineering.md +14 -3
- package/docs/zh-TW/spec/review.md +5 -0
- package/package.json +1 -1
- package/schemas/config.schema.json +1 -21
- package/dist/packages/cli/src/debug-intake.js +0 -245
- package/dist/runtime/src/task/debug-intake.js +0 -15
- package/dist/runtime/src/task/debug-state.js +0 -129
- package/dist/runtime/src/task/debug-user-event.js +0 -95
- package/dist/runtime/src/task/debug.js +0 -612
package/README.md
CHANGED
|
@@ -32,7 +32,7 @@ timeouts produce UNKNOWN. Review examines the checks themselves and every fallba
|
|
|
32
32
|
On macOS, a trusted clean main checkout can start an isolated native goal run:
|
|
33
33
|
|
|
34
34
|
```sh
|
|
35
|
-
agent-ops run "Implement the requested behavior" --host codex --
|
|
35
|
+
agent-ops run "Implement the requested behavior" --host codex --jobs 2
|
|
36
36
|
agent-ops run status <run-id>
|
|
37
37
|
agent-ops run logs <run-id>
|
|
38
38
|
agent-ops run respond <run-id> --question-id <id> --answer "Clarification"
|
|
@@ -49,7 +49,8 @@ verification, two fresh reviews, completed tasks and a receipt bound to the targ
|
|
|
49
49
|
Setup runs in the background under registered process groups. Stop cancels native
|
|
50
50
|
writers, local verification and setup; interrupted setup requires explicit resume.
|
|
51
51
|
The coordinator can assess additional non-dangerous fixed commands through a
|
|
52
|
-
policy request.
|
|
52
|
+
policy request. New runs have no total time cap; manual resume upgrades legacy finite runs while keeping accounting.
|
|
53
|
+
This finite authorization is bound to the run, runtime and lease;
|
|
53
54
|
it creates no permanent repository trust. Policy transitions fence writers and
|
|
54
55
|
journal delivery imports and contract synchronization before starting new leases.
|
|
55
56
|
Native authorization decisions retain redacted command scope and rationale;
|
|
@@ -66,6 +66,10 @@ function parseHookTarget(option, value) {
|
|
|
66
66
|
export function parseArgs(argv) {
|
|
67
67
|
if (argv[0] === "run")
|
|
68
68
|
return parseRunArgs(argv.slice(1));
|
|
69
|
+
if (argv[0] === "task" && argv[1] === "debug")
|
|
70
|
+
throw new CliArgumentError("TASK_DEBUG_REMOVED", "task debug was removed in 0.11.0. A DEBUG task is task create --debug with a behavioral " +
|
|
71
|
+
"criterion whose redCheckIds fail at the baseline and pass on the candidate; then verify, review and finish as usual. " +
|
|
72
|
+
"Declare an escape origin, if known, with task revise --escape-of.");
|
|
69
73
|
let command;
|
|
70
74
|
let action;
|
|
71
75
|
let scope;
|
|
@@ -78,10 +82,6 @@ export function parseArgs(argv) {
|
|
|
78
82
|
let intent;
|
|
79
83
|
let taskGoal;
|
|
80
84
|
let debug = false;
|
|
81
|
-
let debugAction;
|
|
82
|
-
let payloadFile;
|
|
83
|
-
let commandId;
|
|
84
|
-
let eventId;
|
|
85
85
|
let sessionId;
|
|
86
86
|
let base;
|
|
87
87
|
let parentBase;
|
|
@@ -233,30 +233,6 @@ export function parseArgs(argv) {
|
|
|
233
233
|
duplicate(token);
|
|
234
234
|
debug = true;
|
|
235
235
|
break;
|
|
236
|
-
case "--debug-action": {
|
|
237
|
-
if (debugAction !== undefined)
|
|
238
|
-
duplicate(token);
|
|
239
|
-
const value = readOptionValue(argv, index++, token);
|
|
240
|
-
if (!["status", "hypothesis", "strategy", "investigate", "checkpoint", "resume", "extend", "origin"].includes(value))
|
|
241
|
-
invalidValue(token, value);
|
|
242
|
-
debugAction = value;
|
|
243
|
-
break;
|
|
244
|
-
}
|
|
245
|
-
case "--payload-file":
|
|
246
|
-
if (payloadFile !== undefined)
|
|
247
|
-
duplicate(token);
|
|
248
|
-
payloadFile = readOptionValue(argv, index++, token);
|
|
249
|
-
break;
|
|
250
|
-
case "--command-id":
|
|
251
|
-
if (commandId !== undefined)
|
|
252
|
-
duplicate(token);
|
|
253
|
-
commandId = readOptionValue(argv, index++, token);
|
|
254
|
-
break;
|
|
255
|
-
case "--event-id":
|
|
256
|
-
if (eventId !== undefined)
|
|
257
|
-
duplicate(token);
|
|
258
|
-
eventId = readOptionValue(argv, index++, token);
|
|
259
|
-
break;
|
|
260
236
|
case "--tree": {
|
|
261
237
|
if (tree)
|
|
262
238
|
duplicate(token);
|
|
@@ -530,7 +506,7 @@ export function parseArgs(argv) {
|
|
|
530
506
|
"complete",
|
|
531
507
|
"create",
|
|
532
508
|
"export",
|
|
533
|
-
"status", "revise", "replan", "pin-finding"
|
|
509
|
+
"status", "revise", "replan", "pin-finding"
|
|
534
510
|
].includes(token)) {
|
|
535
511
|
action = token;
|
|
536
512
|
break;
|
|
@@ -674,8 +650,8 @@ export function parseArgs(argv) {
|
|
|
674
650
|
throw new CliArgumentError("CLI_OPTION_NOT_ALLOWED", "--target-version may be used only with update.");
|
|
675
651
|
}
|
|
676
652
|
if (base !== undefined && command !== "verify" && command !== "review" &&
|
|
677
|
-
command !== "batch" && !(command === "task" &&
|
|
678
|
-
throw new CliArgumentError("CLI_OPTION_NOT_ALLOWED", "--base may be used only with verify, review, batch
|
|
653
|
+
command !== "batch" && !(command === "task" && action === "complete")) {
|
|
654
|
+
throw new CliArgumentError("CLI_OPTION_NOT_ALLOWED", "--base may be used only with verify, review, batch or task complete.");
|
|
679
655
|
}
|
|
680
656
|
if ((parentBase !== undefined || width !== undefined) && command !== "batch") {
|
|
681
657
|
throw new CliArgumentError("CLI_OPTION_NOT_ALLOWED", `${parentBase !== undefined ? "--parent-base" : "--width"} may be used only with batch.`);
|
|
@@ -714,13 +690,6 @@ export function parseArgs(argv) {
|
|
|
714
690
|
const revisionAction = command === "task" && ["revise", "replan", "pin-finding"].includes(action ?? "");
|
|
715
691
|
if ((taskGoal !== undefined || debug) && !(command === "task" && action === "create"))
|
|
716
692
|
throw new CliArgumentError("CLI_OPTION_NOT_ALLOWED", "--goal and --debug apply only to task create.");
|
|
717
|
-
if ((debugAction !== undefined || commandId !== undefined || eventId !== undefined || payloadFile !== undefined) && !(command === "task" && action === "debug"))
|
|
718
|
-
throw new CliArgumentError("CLI_OPTION_NOT_ALLOWED", "Debug operation options apply only to task debug.");
|
|
719
|
-
if (command === "task" && action === "debug" && (taskId === undefined || debugAction === undefined || title !== undefined || intent !== undefined ||
|
|
720
|
-
parentTaskId !== undefined || criteria.length > 0 || evidence.length > 0 ||
|
|
721
|
-
((debugAction === "hypothesis" || debugAction === "strategy" || debugAction === "origin") !== (payloadFile !== undefined)) ||
|
|
722
|
-
((debugAction === "investigate") !== (commandId !== undefined)) || ((debugAction === "extend") !== (eventId !== undefined))))
|
|
723
|
-
throw new CliArgumentError("CLI_MISSING_VALUE", "Task debug requires --task, --debug-action and the operation's exact payload/command/event options.");
|
|
724
693
|
if ((criterionFiles.length > 0 && !(command === "task" && (action === "create" || action === "pin-finding"))) ||
|
|
725
694
|
(criteriaFile !== undefined && !(command === "task" && action === "revise")) ||
|
|
726
695
|
(surfaceFile !== undefined && !(command === "task" && (action === "create" || action === "revise"))) ||
|
|
@@ -739,7 +708,7 @@ export function parseArgs(argv) {
|
|
|
739
708
|
if (command === "escapes" && argv.some(token => token !== "escapes" && token !== "--json"))
|
|
740
709
|
throw new CliArgumentError("CLI_OPTION_NOT_ALLOWED", "escapes accepts only --json; it is a read-only report.");
|
|
741
710
|
if (revisionAction && (taskId === undefined || evidence.length > 0 || title !== undefined || intent !== undefined || parentTaskId !== undefined || sessionId !== undefined || criteria.length > 0 || base !== undefined ||
|
|
742
|
-
(action === "revise" && (expectedContract === undefined || (criteriaFile === undefined && surfaceFile === undefined && escapeOf === undefined) || reason === undefined)) ||
|
|
711
|
+
(action === "revise" && (expectedContract === undefined || (criteriaFile === undefined && surfaceFile === undefined && escapeOf === undefined && baseline === undefined) || reason === undefined)) ||
|
|
743
712
|
(action === "replan" && (expectedTreeContract === undefined || planFile === undefined || reason === undefined)) ||
|
|
744
713
|
(action === "pin-finding" && (expectedContract === undefined || findingReference === undefined || criterionFiles.length !== 1))))
|
|
745
714
|
throw new CliArgumentError("CLI_MISSING_VALUE", "Acceptance revision requires task, expected contract, payload and reason.");
|
|
@@ -902,10 +871,6 @@ export function parseArgs(argv) {
|
|
|
902
871
|
...(intent === undefined ? {} : { intent }),
|
|
903
872
|
...(taskGoal === undefined ? {} : { goal: taskGoal }),
|
|
904
873
|
...(debug ? { debug: true } : {}),
|
|
905
|
-
...(debugAction === undefined ? {} : { debugAction }),
|
|
906
|
-
...(payloadFile === undefined ? {} : { payloadFile }),
|
|
907
|
-
...(commandId === undefined ? {} : { commandId }),
|
|
908
|
-
...(eventId === undefined ? {} : { eventId }),
|
|
909
874
|
...(reviewTargets.length === 0 ? {} : { reviewTargets }),
|
|
910
875
|
...(completionGate === undefined ? {} : { completionGate }),
|
|
911
876
|
...(office === undefined ? {} : { office }),
|
|
@@ -960,7 +925,7 @@ function parseRunArgs(argv) {
|
|
|
960
925
|
positional = token;
|
|
961
926
|
continue;
|
|
962
927
|
}
|
|
963
|
-
if (seen.has(token))
|
|
928
|
+
if (seen.has(token) && token !== "--retry-approval")
|
|
964
929
|
duplicate(token);
|
|
965
930
|
seen.add(token);
|
|
966
931
|
if (token === "--json") {
|
|
@@ -971,10 +936,15 @@ function parseRunArgs(argv) {
|
|
|
971
936
|
args.wait = true;
|
|
972
937
|
continue;
|
|
973
938
|
}
|
|
974
|
-
if (!["--host", "--goal-file", "--time-budget", "--jobs", "--question-id", "--answer", "--answer-file"].includes(token))
|
|
939
|
+
if (!["--host", "--goal-file", "--time-budget", "--jobs", "--question-id", "--answer", "--answer-file", "--retry-approval"].includes(token))
|
|
975
940
|
throw new CliArgumentError("CLI_UNKNOWN_OPTION", `Unknown run option: ${token}`, token);
|
|
976
941
|
const value = readOptionValue(argv, i++, token);
|
|
977
942
|
switch (token) {
|
|
943
|
+
case "--retry-approval":
|
|
944
|
+
if (!/^auth-[a-f0-9]{64}$/u.test(value) || args.retryApprovalIds?.includes(value))
|
|
945
|
+
invalidValue(token, value);
|
|
946
|
+
args.retryApprovalIds = [...(args.retryApprovalIds ?? []), value];
|
|
947
|
+
break;
|
|
978
948
|
case "--host":
|
|
979
949
|
if (value !== "claude" && value !== "codex")
|
|
980
950
|
invalidValue(token, value);
|
|
@@ -998,17 +968,12 @@ function parseRunArgs(argv) {
|
|
|
998
968
|
args.jobs = Number(value);
|
|
999
969
|
break;
|
|
1000
970
|
case "--time-budget": {
|
|
1001
|
-
|
|
1002
|
-
if (match === null)
|
|
1003
|
-
invalidValue(token, value);
|
|
1004
|
-
const ms = Number(match[1]) * ({ s: 1000, m: 60000, h: 3600000 }[match[2]]);
|
|
1005
|
-
if (!Number.isSafeInteger(ms) || ms > 86400000)
|
|
1006
|
-
invalidValue(token, value);
|
|
1007
|
-
args.timeBudgetMs = ms;
|
|
1008
|
-
break;
|
|
971
|
+
throw new CliArgumentError("RUN_TIME_BUDGET_UNSUPPORTED", "New runs have no total time cap; omit --time-budget. Command and review timeouts remain effective.", token);
|
|
1009
972
|
}
|
|
1010
973
|
}
|
|
1011
974
|
}
|
|
975
|
+
if (args.retryApprovalIds !== undefined && action !== "resume")
|
|
976
|
+
throw new CliArgumentError("CLI_INVALID_OPTION", "--retry-approval requires run resume.");
|
|
1012
977
|
if (action === "start") {
|
|
1013
978
|
args.goal = positional;
|
|
1014
979
|
if ((args.goal === undefined) === (args.goalFile === undefined))
|
|
@@ -47,9 +47,11 @@ import { memoizePreflight } from "../../../runtime/src/review/batch.js";
|
|
|
47
47
|
import { createReviewExecutor, ReviewInterruptedError } from "../../../runtime/src/review/execute.js";
|
|
48
48
|
import { probeReviewTargetDetailed } from "../../../runtime/src/review/probe.js";
|
|
49
49
|
import { resolveReviewScope } from "../../../runtime/src/review/scope.js";
|
|
50
|
+
import { runSurfaceGate } from "../../../runtime/src/surface/discovery.js";
|
|
50
51
|
import { resolveReviewRole } from "../../../runtime/src/review/roles.js";
|
|
51
52
|
import { runTrustCommand } from "./commands/trust.js";
|
|
52
53
|
import { runVerifyCommand } from "./commands/verify.js";
|
|
54
|
+
import { defaultBase } from "./default-base.js";
|
|
53
55
|
import { runAdvanceCommand } from "./commands/advance.js";
|
|
54
56
|
import { runReviewShowCommand } from "./commands/review-show.js";
|
|
55
57
|
import { runEscapesCommand } from "./commands/escapes.js";
|
|
@@ -255,6 +257,16 @@ else {
|
|
|
255
257
|
const root = args.scope === "user"
|
|
256
258
|
? process.env.AGENT_OPS_HOME ?? homedir()
|
|
257
259
|
: process.cwd();
|
|
260
|
+
// Committed work is judged as a range by default; see defaultBase.
|
|
261
|
+
if ((args.command === "verify" || (args.command === "review" && args.action !== "show")) && args.base === undefined && args.scope !== "user") {
|
|
262
|
+
const taskId = args.taskId;
|
|
263
|
+
const base = await defaultBase(root, gitRunner(root), async () => taskId === undefined ? undefined :
|
|
264
|
+
(await new TaskService(new FileTaskStore(join(root, ".agent-ops", "tasks", "state.json"), root)).status({ taskId })).createdSourceCommit)
|
|
265
|
+
// Only a default: any failure leaves the command's own scope handling and errors in charge.
|
|
266
|
+
.catch(() => undefined);
|
|
267
|
+
if (base !== undefined)
|
|
268
|
+
args = { ...args, base };
|
|
269
|
+
}
|
|
258
270
|
const isTTY = !args.json &&
|
|
259
271
|
process.stdin.isTTY === true &&
|
|
260
272
|
process.stdout.isTTY === true;
|
|
@@ -495,7 +507,6 @@ else {
|
|
|
495
507
|
return await runTaskCommand({
|
|
496
508
|
args,
|
|
497
509
|
service: taskService,
|
|
498
|
-
...(args.action !== "debug" ? {} : { trusted: await (async () => { const binding = await plannedTrustBinding(root, (await loadEffectiveConfig(root, "project")).config); return binding !== null && (await trustStore().status(binding)).status === "TRUSTED"; })() }),
|
|
499
510
|
...(policyConfigHash === undefined ? {} : { policyConfigHash }),
|
|
500
511
|
...(sessionId === undefined ? {} : { sessionId }),
|
|
501
512
|
...(fromMain
|
|
@@ -553,41 +564,16 @@ else {
|
|
|
553
564
|
reviewActivity = undefined;
|
|
554
565
|
}
|
|
555
566
|
});
|
|
556
|
-
const debugRecord = reviewArgs.taskId === undefined ? undefined : await taskService.status({ taskId: reviewArgs.taskId });
|
|
557
|
-
const inDebug = (executor) => debugRecord?.task.debug === undefined ? executor : async (request) => await taskService.debugService().review(debugRecord.task.id, () => executor(request));
|
|
558
567
|
const inSlot = (executor) => slotDir === undefined
|
|
559
|
-
?
|
|
568
|
+
? executor
|
|
560
569
|
: async (request) => await withReviewSlot({
|
|
561
570
|
dir: slotDir,
|
|
562
571
|
signal,
|
|
563
572
|
holder: { root, ...(reviewArgs.taskId === undefined ? {} : { taskId: reviewArgs.taskId }) },
|
|
564
|
-
...(debugRecord?.task.debug === undefined || reviewSessionId === undefined
|
|
565
|
-
? {}
|
|
566
|
-
: (() => {
|
|
567
|
-
let paused = false;
|
|
568
|
-
const debug = taskService.debugService();
|
|
569
|
-
return {
|
|
570
|
-
onWaitStart: async () => {
|
|
571
|
-
if (paused)
|
|
572
|
-
return;
|
|
573
|
-
const current = await debug.root(debugRecord.task.id).catch(() => null);
|
|
574
|
-
if (current?.debug.foreground?.sessionId !== reviewSessionId)
|
|
575
|
-
return;
|
|
576
|
-
await debug.foregroundStop(debugRecord.task.id, reviewSessionId);
|
|
577
|
-
paused = true;
|
|
578
|
-
},
|
|
579
|
-
onWaitEnd: async () => {
|
|
580
|
-
if (!paused)
|
|
581
|
-
return;
|
|
582
|
-
await debug.foregroundStart(debugRecord.task.id, reviewSessionId);
|
|
583
|
-
paused = false;
|
|
584
|
-
}
|
|
585
|
-
};
|
|
586
|
-
})()),
|
|
587
573
|
onWait: (line) => {
|
|
588
574
|
process.stderr.write(`${progressPrefix}${line}\n`);
|
|
589
575
|
}
|
|
590
|
-
}, async () => await
|
|
576
|
+
}, async () => await executor(request));
|
|
591
577
|
return {
|
|
592
578
|
args: reviewArgs,
|
|
593
579
|
authorized: reviewArgs.yes,
|
|
@@ -607,7 +593,6 @@ else {
|
|
|
607
593
|
config: reviewConfig,
|
|
608
594
|
evidenceStore: new FileEvidenceStore(root, root),
|
|
609
595
|
execute: inActivity(inSlot(createReviewExecutor({
|
|
610
|
-
...(debugRecord?.task.debug === undefined ? {} : { remainingBudgetMs: () => taskService.debugService().remaining(debugRecord.task.id) }),
|
|
611
596
|
runner: registeredRunProofRunner(),
|
|
612
597
|
targets: configuredReviewTargets,
|
|
613
598
|
cwd: root,
|
|
@@ -749,6 +734,20 @@ else {
|
|
|
749
734
|
return await runVerifyCommand({
|
|
750
735
|
args,
|
|
751
736
|
taskService,
|
|
737
|
+
surface: async (taskId) => {
|
|
738
|
+
const record = await taskService.status({ taskId });
|
|
739
|
+
// A subtask's surface is its parent's; review checks the tree, so verify stays quiet here.
|
|
740
|
+
if (record.task.parentTaskId !== undefined)
|
|
741
|
+
return [];
|
|
742
|
+
const scope = await resolveReviewScope({ root, runner: gitRunner(root), ...(args.base === undefined ? {} : { base: args.base }) });
|
|
743
|
+
const gate = await runSurfaceGate({ root, taskId, surface: record.task.surface, required: record.task.surfaceRequired === true,
|
|
744
|
+
...(scope.mode === "base" ? { base: scope.resolvedBase } : {}), changedFiles: scope.changedFiles, configHash: calculateConfigHash(config) });
|
|
745
|
+
if (gate.status === "PASS")
|
|
746
|
+
return ["Surface: PASS"];
|
|
747
|
+
const paths = gate.details.flatMap(d => typeof d === "object" && d !== null && "path" in d ? [String(d.path)] : []);
|
|
748
|
+
return [`Surface (advisory; review would stop with ${gate.code}): ${gate.message}`,
|
|
749
|
+
...paths.slice(0, 20).map(path => ` - ${path}`), ...(paths.length > 20 ? [` ... ${paths.length - 20} more`] : [])];
|
|
750
|
+
},
|
|
752
751
|
activity: { commonDir: await officeCommonDir(root), root, ...(verifySessionId === undefined ? {} : { sessionId: verifySessionId }) },
|
|
753
752
|
service: { verify: async (taskId) => {
|
|
754
753
|
const run = await runPolicyContext(root);
|
|
@@ -25,7 +25,7 @@ Commands:
|
|
|
25
25
|
doctor Diagnose an installation
|
|
26
26
|
update Update managed artifacts
|
|
27
27
|
uninstall Remove managed artifacts
|
|
28
|
-
task <create|status|attach|revise|replan|pin-finding|
|
|
28
|
+
task <create|status|attach|revise|replan|pin-finding|advance|complete|archive|export>
|
|
29
29
|
Manage independent task acceptance state
|
|
30
30
|
verify Run configured verification
|
|
31
31
|
review Run an independent review or show recorded reports
|
|
@@ -98,13 +98,22 @@ browser once; later sessions reuse it. It exits after 10 minutes without active
|
|
|
98
98
|
runs or sessions. Recently completed rooms remain for at most 2 hours.
|
|
99
99
|
The URL carries an access token; do not share it.
|
|
100
100
|
`,
|
|
101
|
-
run: `Usage: agent-ops run [start] "<goal>" --host <claude|codex> [--jobs <1|2>] [--
|
|
101
|
+
run: `Usage: agent-ops run [start] "<goal>" --host <claude|codex> [--jobs <1|2>] [--wait] [--json]
|
|
102
102
|
agent-ops run [start] --goal-file <path> --host <claude|codex>
|
|
103
103
|
agent-ops run <status|logs|resume|stop> <run-id> [--json]
|
|
104
|
+
agent-ops run resume <run-id> [--retry-approval <auth-event-id>]... [--json]
|
|
104
105
|
agent-ops run respond <run-id> --question-id <id> <--answer <text>|--answer-file <path>> [--json]
|
|
105
106
|
|
|
107
|
+
New runs have no total time cap. Manual resume upgrades legacy finite runs and keeps accounting.
|
|
108
|
+
Command/review timeouts, provider limits and stop controls remain effective.
|
|
106
109
|
Native goal state is observational. Completion requires current verification,
|
|
107
110
|
two fresh reviews, task completion and a target-bound integration receipt.
|
|
111
|
+
--retry-approval explicitly retries an intact Codex approval-review usage-limit
|
|
112
|
+
failure. Original denials remain recorded; every command needs fresh approval.
|
|
113
|
+
Status includes phase, original cause, recorded sealed proof, cleanup state,
|
|
114
|
+
resumability and remediation. Resume revalidates authority and sealed artifacts;
|
|
115
|
+
receipt-bound recovery executes only missing cleanup steps. Resolve the saved
|
|
116
|
+
blocker before retrying: unchanged cleanup inputs are refused.
|
|
108
117
|
`,
|
|
109
118
|
init: `Usage: agent-ops init [options]
|
|
110
119
|
|
|
@@ -179,7 +188,7 @@ Options:
|
|
|
179
188
|
--json
|
|
180
189
|
--yes
|
|
181
190
|
`,
|
|
182
|
-
task: `Usage: agent-ops task <create|status|attach|revise|replan|pin-finding|
|
|
191
|
+
task: `Usage: agent-ops task <create|status|attach|revise|replan|pin-finding|advance|complete|archive|export> [options]
|
|
183
192
|
|
|
184
193
|
Manage independent task acceptance state. Task commands accept none of
|
|
185
194
|
--harness, --profile or --dry-run. Only advance accepts --yes.
|
|
@@ -187,16 +196,12 @@ Manage independent task acceptance state. Task commands accept none of
|
|
|
187
196
|
Options:
|
|
188
197
|
--title <text> create
|
|
189
198
|
--goal <text> create: immutable original issue/goal
|
|
190
|
-
--debug create:
|
|
191
|
-
--debug-action <status|hypothesis|strategy|investigate|checkpoint|resume|extend|origin>
|
|
192
|
-
--payload-file <path> debug hypothesis/strategy/origin: bounded JSON
|
|
193
|
-
--command-id <id> debug investigate: trusted configured command
|
|
194
|
-
--event-id <native-event-id|latest> debug extend: authentic human continue event
|
|
199
|
+
--debug create: bug fix; needs --goal and a behavioral criterion with redCheckIds
|
|
195
200
|
--intent <text> create: intended behavior and constraints before editing
|
|
196
201
|
--criterion <json> create, repeatable, two to five total
|
|
197
202
|
--criterion-file <path> create: one criterion object, repeatable; pin-finding: exactly one
|
|
198
203
|
--criteria-file <path> revise: replacement criterion array
|
|
199
|
-
--surface-file <path> create (required
|
|
204
|
+
--surface-file <path> create (required), revise: EVIDENCE-SURFACE-001
|
|
200
205
|
audit JSON, or {"covers":[surface-id]} under a surfaced parent
|
|
201
206
|
--escape-of <commit> create, revise: the commit this bug escaped from (Escape Loop)
|
|
202
207
|
--escape-label <label> with --escape-of: requirement|surface|scenario|criteria|verify|review|flagged|out-of-model
|
|
@@ -207,12 +212,12 @@ Options:
|
|
|
207
212
|
--expected-contract <hash> revise, pin-finding: current task contract from status
|
|
208
213
|
--expected-tree-contract <hash> replan: current tree contract from status
|
|
209
214
|
--reason <text> required for revise/replan; optional for pin-finding
|
|
210
|
-
--baseline <git-ref> create
|
|
215
|
+
--baseline <git-ref> create: baseline for typed criteria; revise: move every typed baseline
|
|
211
216
|
--parent <task-id> create: record a subtask; status: list subtasks
|
|
212
217
|
--task <id> status, attach, revise, replan, pin-finding, complete, archive, export, advance
|
|
213
|
-
--session <id> create, attach, status, advance
|
|
218
|
+
--session <id> create, attach, status, advance
|
|
214
219
|
--evidence <criterion-id=reference> complete, repeatable
|
|
215
|
-
--base <git-ref> complete
|
|
220
|
+
--base <git-ref> complete: a clean committed range
|
|
216
221
|
--yes advance: authorize the two review sessions
|
|
217
222
|
--json
|
|
218
223
|
|
|
@@ -224,7 +229,8 @@ Each legacy criterion needs at least one verifierIds entry naming a verification
|
|
|
224
229
|
command id configured in .agent-ops/config.json. Typed criteria additionally
|
|
225
230
|
accept an acceptance object: mode (behavioral, invariant or review-only),
|
|
226
231
|
bindings and optional baselineCommit/reviewOnlyReason. Mechanical bindings name
|
|
227
|
-
configured acceptance runner IDs, checkIds
|
|
232
|
+
configured acceptance runner IDs, checkIds (the checks this criterion needs;
|
|
233
|
+
other runner output is ignored), committed materials and optional
|
|
228
234
|
redCheckIds; tasks cannot supply commands. Creation freezes the baseline.
|
|
229
235
|
Review-only example (mandatory repository policy commands still run):
|
|
230
236
|
|
|
@@ -232,7 +238,8 @@ Review-only example (mandatory repository policy commands still run):
|
|
|
232
238
|
|
|
233
239
|
Pin a saved finding with task pin-finding --task <id> <report-digest:index>
|
|
234
240
|
--expected-contract <hash> --criterion-file <path>. Revise/replan preserve the
|
|
235
|
-
original goal and
|
|
241
|
+
original goal and existing baselines unless revise --baseline moves them all.
|
|
242
|
+
A revise that keeps the criteria keeps verification; every revise needs review.
|
|
236
243
|
`,
|
|
237
244
|
verify: `Usage: agent-ops verify [options]
|
|
238
245
|
|
|
@@ -308,9 +315,10 @@ voiding each other's verification and review. Run it from the main checkout.
|
|
|
308
315
|
before Git runs; see agent-ops worktree list.
|
|
309
316
|
finish <name>
|
|
310
317
|
Once its task is complete, fast-forward the main checkout's
|
|
311
|
-
branch to the worktree's
|
|
312
|
-
|
|
313
|
-
|
|
318
|
+
branch to the worktree's, record the task in git notes, and
|
|
319
|
+
remove the worktree. If the branch moved, it rebases the
|
|
320
|
+
worktree and stops: verify and review again with the printed
|
|
321
|
+
--base, then finish again. One finish runs at a time.
|
|
314
322
|
commit Stage every change in the current worktree and commit it with
|
|
315
323
|
--message. Refuses the main checkout and a worktree another
|
|
316
324
|
session owns, and needs no Git in the shell.
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { PROJECT_LOOP_EVENTS, resolveProjectLoopRoot, runProjectLoop } from "../../../runtime/src/hooks/codex-loop.js";
|
|
2
2
|
import { officeEnabled, officeLoopPhaseHint } from "./office-entry.js";
|
|
3
|
-
import { routeDebugPrompt } from "./debug-intake.js";
|
|
4
3
|
const MAX_LOOP_INPUT_BYTES = 64 * 1024;
|
|
5
4
|
function isLoopHarness(value) {
|
|
6
5
|
return value === "claude" || value === "codex";
|
|
@@ -74,11 +73,6 @@ export async function runLoopProcess(argv, io, dependencies = {}) {
|
|
|
74
73
|
? {}
|
|
75
74
|
: { telemetryMaxBytes: dependencies.telemetryMaxBytes })
|
|
76
75
|
});
|
|
77
|
-
if (event === "UserPromptSubmit" && managedRoot !== null && result.exitCode === 0 && result.stdout === "" && result.stderr === "") {
|
|
78
|
-
const context = await routeDebugPrompt(managedRoot, input, harness).catch(error => `DEBUG intake unavailable: ${error instanceof Error ? error.message : "preserve current state"}`);
|
|
79
|
-
if (context !== null)
|
|
80
|
-
io.writeStdout(harness === "claude" ? JSON.stringify({ hookSpecificOutput: { hookEventName: "UserPromptSubmit", additionalContext: context } }) : context + "\n");
|
|
81
|
-
}
|
|
82
76
|
if (result.stdout.length > 0) {
|
|
83
77
|
io.writeStdout(result.stdout);
|
|
84
78
|
}
|
|
@@ -90,7 +90,8 @@ export async function runAdvanceCommand(options) {
|
|
|
90
90
|
throw new AgentOpsError("ADVANCE_WORKTREE_MISSING", `No coordinator worktree for session ${options.sessionId}.`);
|
|
91
91
|
}
|
|
92
92
|
const savedProof = await readRunIntegrationProof(commonDir, record);
|
|
93
|
-
if (savedProof !== null && await git(deps, mainRoot, ["rev-parse", `${record.targetBranch}^{commit}`]) === savedProof.head
|
|
93
|
+
if (savedProof !== null && (await git(deps, mainRoot, ["rev-parse", `${record.targetBranch}^{commit}`]) === savedProof.head ||
|
|
94
|
+
(await deps.git(mainRoot, ["merge-base", "--is-ancestor", savedProof.head, record.targetBranch])).exitCode === 0)) {
|
|
94
95
|
const finished = await finishWorktree(deps, { cwd: mainRoot, name, finalProof: { ...savedProof, recovery: true } });
|
|
95
96
|
return okEnvelope("TASK_ADVANCED", { ...finished, text: "Recovered the sealed integration without moving the target again." });
|
|
96
97
|
}
|
|
@@ -41,7 +41,7 @@ function addText(result) {
|
|
|
41
41
|
function finishText(result) {
|
|
42
42
|
const { record } = result;
|
|
43
43
|
return [
|
|
44
|
-
`Merged ${record.branch} into ${record.targetBranch} at ${result.mergedHead}
|
|
44
|
+
`Merged ${record.branch} into ${record.targetBranch} at ${result.mergedHead}.`,
|
|
45
45
|
`Completed and recorded in git notes (refs/notes/agent-ops): ${result.taskIds.join(", ")}.`,
|
|
46
46
|
`Removed ${record.path}.`,
|
|
47
47
|
...(result.receipt === undefined ? [] : [`Receipt: ${result.receipt}`]),
|
|
@@ -4,7 +4,7 @@ import { renderReviewResult } from "../../../../runtime/src/review/render.js";
|
|
|
4
4
|
import { reviewReportResults } from "../../../../runtime/src/review/report.js";
|
|
5
5
|
import { findPriorFailingFindings, findReviewAttestation, invalidateReviewAttestation, readReviewReportArtifact, reviewReportDigest, saveReviewAttestation, saveReviewReportArtifact } from "../../../../runtime/src/review/attestation.js";
|
|
6
6
|
import { detectHostTarget, planReviewTargets, resolveReviewRole } from "../../../../runtime/src/review/roles.js";
|
|
7
|
-
import { taskContractHash, treeContractHash, goalHash } from "../../../../runtime/src/task/contract.js";
|
|
7
|
+
import { evidenceMatchesContract, taskContractHash, treeContractHash, goalHash } from "../../../../runtime/src/task/contract.js";
|
|
8
8
|
import { acceptanceCoverage, coverageDigest } from "../../../../runtime/src/verify/acceptance-coverage.js";
|
|
9
9
|
import { resolveGitCommit } from "../../../../runtime/src/verify/change-surface.js";
|
|
10
10
|
import { sha256 } from "../../../../runtime/src/fs/hash.js";
|
|
@@ -15,7 +15,6 @@ import { assertSafeSupportingPaths, isReviewerPolicyPath, resolveReviewScope, re
|
|
|
15
15
|
import { calculateSourceFingerprint } from "../../../../runtime/src/verify/source-fingerprint.js";
|
|
16
16
|
import { calculateConfigHash, isPassingVerificationEvidence } from "../../../../runtime/src/verify/evidence.js";
|
|
17
17
|
import { validateEvidence } from "../../../../runtime/src/schema/validate.js";
|
|
18
|
-
import { loadDebugProof, debugProofDigest } from "../../../../runtime/src/task/debug-state.js";
|
|
19
18
|
import { logSurfaceResidualRisks, runSurfaceGate } from "../../../../runtime/src/surface/discovery.js";
|
|
20
19
|
import { escapeReviewData } from "../../../../runtime/src/escape/link.js";
|
|
21
20
|
import { logGateCatch } from "../../../../runtime/src/logging/local-log.js";
|
|
@@ -187,7 +186,7 @@ async function currentEvidence(options, context, criterionId, commandId, configH
|
|
|
187
186
|
evidence.sourceFingerprint !== sourceFingerprint ||
|
|
188
187
|
(context.records.some(r => r.task.id === (owner?.taskId ?? context.taskId) &&
|
|
189
188
|
(r.task.goal !== undefined || (r.task.contractRevision ?? 0) > 0 || r.task.criteria.some(c => c.acceptance !== undefined))) &&
|
|
190
|
-
evidence.taskContractHash
|
|
189
|
+
!evidenceMatchesContract(evidence.taskContractHash, context.records.find(r => r.task.id === (owner?.taskId ?? context.taskId)).task))) {
|
|
191
190
|
stale = true;
|
|
192
191
|
continue;
|
|
193
192
|
}
|
|
@@ -343,7 +342,7 @@ async function reviewEscapes(options, context, scope) {
|
|
|
343
342
|
const origin = record.task.origin;
|
|
344
343
|
if (origin === undefined)
|
|
345
344
|
continue;
|
|
346
|
-
const base = record.
|
|
345
|
+
const base = record.createdSourceCommit ?? (scope?.mode === "base" ? scope.resolvedBase : undefined);
|
|
347
346
|
try {
|
|
348
347
|
if (base === undefined)
|
|
349
348
|
throw new Error("no base");
|
|
@@ -770,7 +769,6 @@ export async function runReviewCommand(options) {
|
|
|
770
769
|
}
|
|
771
770
|
context.coverage = rows;
|
|
772
771
|
context.binding = {
|
|
773
|
-
...(context.records.every(r => r.task.debug === undefined) || options.root === undefined ? {} : { debugProofs: Object.fromEntries(await Promise.all(context.records.filter(r => r.task.debug !== undefined).map(async (r) => [r.task.id, debugProofDigest(await loadDebugProof(options.root, r.task.debug.rootTaskId))]))) }),
|
|
774
772
|
taskContractHash: taskContractHash(context.records.find(r => r.task.id === context.taskId).task),
|
|
775
773
|
taskContracts: Object.fromEntries(context.records.map(r => [r.task.id, taskContractHash(r.task)])),
|
|
776
774
|
...(context.treeTasks === undefined ? {} : { treeContractHash: treeContractHash(context.records.map(r => r.task)) }),
|
|
@@ -816,12 +814,13 @@ export async function runReviewCommand(options) {
|
|
|
816
814
|
contractArtifacts.push({ path: executionPath, digest: sha256(execution), content: execution });
|
|
817
815
|
}
|
|
818
816
|
}
|
|
817
|
+
// Compact: indentation alone pushed a 494 KiB 0.10.2 manifest past the 512 KiB bound.
|
|
819
818
|
const content = redactSecrets(JSON.stringify({ binding: context.binding, coverage: context.coverage,
|
|
820
819
|
executionPolicy: options.config,
|
|
820
|
+
...(surfacePacket === undefined ? {} : { surface: surfacePacket }),
|
|
821
821
|
artifacts: contractArtifacts.map(({ path, digest }) => ({ path, digest })),
|
|
822
|
-
tasks:
|
|
823
|
-
|
|
824
|
-
superseded: context.historyRecords ?? [] }, null, 2));
|
|
822
|
+
tasks: context.records.map(r => ({ task: r.task, revisions: r.revisions ?? [] })),
|
|
823
|
+
superseded: context.historyRecords ?? [] }));
|
|
825
824
|
const digest = sha256(content);
|
|
826
825
|
const path = ".agent-ops/tasks/review-contracts/" + digest + ".json";
|
|
827
826
|
contractManifest = { path, digest, content };
|
|
@@ -924,9 +923,7 @@ export async function runReviewCommand(options) {
|
|
|
924
923
|
await options.tasks.treeContract(context.taskId) !== context.binding.treeContractHash) ||
|
|
925
924
|
(context !== undefined && options.tasks !== undefined &&
|
|
926
925
|
(await Promise.all(context.records.map(async (r) => taskContractHash((await options.tasks.status({ taskId: r.task.id })).task))))
|
|
927
|
-
.some((hash, i) => hash !== taskContractHash(context.records[i].task)))
|
|
928
|
-
(context?.binding?.debugProofs !== undefined && options.root !== undefined &&
|
|
929
|
-
(await Promise.all(context.records.filter(r => r.task.debug !== undefined).map(async (r) => debugProofDigest(await loadDebugProof(options.root, r.task.debug.rootTaskId)) !== context.binding.debugProofs[r.task.id]))).some(Boolean))) {
|
|
926
|
+
.some((hash, i) => hash !== taskContractHash(context.records[i].task)))) {
|
|
930
927
|
const changed = await persistReviewEvidence(options, sourceChangedResult(result), sourceFingerprint, context);
|
|
931
928
|
return notRunEnvelope(changed);
|
|
932
929
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { readFile } from "node:fs/promises";
|
|
2
2
|
import { AgentOpsError, resolveContainedPath } from "../../../../runtime/src/fs/paths.js";
|
|
3
|
+
import { describeRunRecovery } from "../../../../runtime/src/run/service.js";
|
|
3
4
|
import { errorEnvelope, okEnvelope } from "../output.js";
|
|
4
5
|
function dynamic(args) {
|
|
5
6
|
return args;
|
|
@@ -19,13 +20,18 @@ function runIdOf(args) {
|
|
|
19
20
|
return runId;
|
|
20
21
|
}
|
|
21
22
|
function textFor(action, state, message) {
|
|
23
|
+
const recovery = describeRunRecovery(state);
|
|
22
24
|
const active = state.workers.filter((worker) => ["assigned", "starting", "running", "idle", "handing-off"].includes(worker.status)).length;
|
|
23
25
|
return [
|
|
24
26
|
message,
|
|
25
27
|
`Status: ${state.status}`,
|
|
28
|
+
`Phase: ${recovery.phase}`,
|
|
29
|
+
`Cause: ${recovery.cause === null ? "none recorded" : recovery.cause.code + ": " + recovery.cause.detail}`,
|
|
30
|
+
`Sealed proof: ${recovery.sealedProof}; cleanup: ${recovery.cleanup}; resumable: ${recovery.resumable ? "yes, after reconciliation" : "no"}`,
|
|
31
|
+
`Remediation: ${recovery.remediation}`,
|
|
26
32
|
`Host: ${state.host}`,
|
|
27
33
|
`Workers: ${active}/${state.jobs}`,
|
|
28
|
-
`Budget: ${Math.max(0, state.budget.limitMs - state.budget.accumulatedMs)}ms remaining`,
|
|
34
|
+
state.budget.limitMs === null ? `Budget: unlimited; ${state.budget.accumulatedMs}ms active time recorded` : `Budget: ${Math.max(0, state.budget.limitMs - state.budget.accumulatedMs)}ms remaining (legacy; manual resume upgrades)`,
|
|
29
35
|
`Usage: ${state.budget.usage.tokens ?? "unknown"} tokens; ${state.budget.usage.usd === null ? "unknown" : "$" + state.budget.usage.usd} cost (${state.budget.usage.completeness})`,
|
|
30
36
|
action === "logs" ? "" : "Native goal completion is not final proof; verify, review, task state, and receipt are still required."
|
|
31
37
|
].filter((line) => line.length > 0).join("\n");
|
|
@@ -35,12 +41,15 @@ function data(action, result) {
|
|
|
35
41
|
action,
|
|
36
42
|
runId: result.state.runId,
|
|
37
43
|
state: result.state,
|
|
44
|
+
recovery: describeRunRecovery(result.state),
|
|
38
45
|
message: result.message,
|
|
39
46
|
text: textFor(action, result.state, result.message)
|
|
40
47
|
};
|
|
41
48
|
}
|
|
42
49
|
function stateData(action, state, message) {
|
|
43
|
-
|
|
50
|
+
const { events, goal: _goal, ...brief } = state;
|
|
51
|
+
return { action, runId: state.runId, state: { ...brief, eventCount: events.length }, recovery: describeRunRecovery(state), message,
|
|
52
|
+
text: textFor(action, state, message) };
|
|
44
53
|
}
|
|
45
54
|
async function goalFromArgs(args, root) {
|
|
46
55
|
if (args.goal !== undefined && args.goalFile !== undefined)
|
|
@@ -99,7 +108,7 @@ export async function runRunCommand(options) {
|
|
|
99
108
|
return okEnvelope("RUN_LOGS", { ...stateData(action, state, `Read ${events.length} event(s) for run ${runId}.`), events });
|
|
100
109
|
}
|
|
101
110
|
if (action === "resume")
|
|
102
|
-
return okEnvelope("RUN_RESUMED", data(action, await options.service.resume(runId)));
|
|
111
|
+
return okEnvelope("RUN_RESUMED", data(action, await options.service.resume(runId, options.args.retryApprovalIds)));
|
|
103
112
|
if (action === "stop")
|
|
104
113
|
return okEnvelope("RUN_STOP_REQUESTED", data(action, await options.service.stop(runId, options.args.answer ?? "user requested stop")));
|
|
105
114
|
const questionId = options.args.questionId;
|