@kylecheng3146/agent-ops 0.7.3 → 0.8.3
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 +2 -0
- package/dist/packages/cli/src/args.js +72 -1
- package/dist/packages/cli/src/bin.js +69 -10
- package/dist/packages/cli/src/cli.js +11 -3
- package/dist/packages/cli/src/codex-loop-process.js +6 -0
- package/dist/packages/cli/src/commands/batch.js +3 -1
- package/dist/packages/cli/src/commands/hook.js +4 -1
- package/dist/packages/cli/src/commands/review.js +9 -3
- package/dist/packages/cli/src/commands/task.js +44 -2
- package/dist/packages/cli/src/commands/update.js +18 -0
- package/dist/packages/cli/src/commands/verify.js +3 -1
- package/dist/packages/cli/src/debug-intake.js +245 -0
- package/dist/packages/cli/src/hook-process.js +155 -34
- package/dist/packages/cli/src/office-entry.js +34 -6
- package/dist/packages/cli/src/run-deps.js +8 -0
- package/dist/runtime/src/adapters/agy/config.js +3 -2
- package/dist/runtime/src/adapters/claude/config.js +7 -3
- package/dist/runtime/src/adapters/codex/config.js +10 -3
- package/dist/runtime/src/adapters/codex/events.js +2 -1
- package/dist/runtime/src/adapters/codex/input.js +20 -2
- package/dist/runtime/src/adapters/codex/output.js +4 -1
- package/dist/runtime/src/config/merge.js +1 -1
- package/dist/runtime/src/hooks/completion-gate.js +15 -0
- package/dist/runtime/src/install/harness.js +2 -0
- package/dist/runtime/src/install/plan.js +3 -3
- package/dist/runtime/src/install/profiles.js +2 -0
- package/dist/runtime/src/install/update.js +10 -2
- package/dist/runtime/src/office/activity.js +105 -0
- package/dist/runtime/src/office/art.js +1000 -0
- package/dist/runtime/src/office/collect.js +137 -10
- package/dist/runtime/src/office/layout.js +203 -0
- package/dist/runtime/src/office/page.js +883 -644
- package/dist/runtime/src/office/scene.js +38 -33
- package/dist/runtime/src/office/sessions.js +18 -1
- package/dist/runtime/src/office/snapshot.js +74 -1
- package/dist/runtime/src/parallel/finish.js +4 -2
- package/dist/runtime/src/parallel/manage.js +19 -2
- package/dist/runtime/src/review/attestation.js +4 -1
- package/dist/runtime/src/review/batch.js +10 -0
- package/dist/runtime/src/review/execute.js +10 -1
- package/dist/runtime/src/review/slots.js +59 -44
- package/dist/runtime/src/run/hosts/codex.js +1 -1
- package/dist/runtime/src/run/transport.js +3 -0
- package/dist/runtime/src/schema/validate.js +8 -2
- package/dist/runtime/src/security/permissions.js +3 -1
- package/dist/runtime/src/task/completion.js +20 -1
- package/dist/runtime/src/task/contract.js +1 -1
- package/dist/runtime/src/task/debug-intake.js +12 -0
- package/dist/runtime/src/task/debug-state.js +130 -0
- package/dist/runtime/src/task/debug-user-event.js +93 -0
- package/dist/runtime/src/task/debug.js +586 -0
- package/dist/runtime/src/task/service.js +46 -0
- package/dist/runtime/src/task/store.js +8 -1
- package/dist/runtime/src/verify/acceptance-replay.js +9 -4
- package/dist/runtime/src/verify/service.js +21 -2
- package/docs/en/guides/configuration.md +7 -0
- package/docs/en/guides/task-debug.md +111 -0
- package/docs/en/spec/harness-adapters.md +31 -4
- package/docs/zh-TW/guides/configuration.md +6 -0
- package/docs/zh-TW/guides/task-debug.md +66 -0
- package/docs/zh-TW/spec/harness-adapters.md +23 -4
- package/package.json +1 -1
- package/schemas/config.schema.json +30 -2
- package/schemas/manifest.schema.json +1 -0
- package/schemas/task.schema.json +62 -2
package/README.md
CHANGED
|
@@ -257,6 +257,8 @@ Init and update pre-authorize `agent-ops run` for Claude Code and Codex.
|
|
|
257
257
|
|
|
258
258
|
```bash
|
|
259
259
|
agent-ops init --scope project --harness codex,claude --profile run --yes
|
|
260
|
+
# an existing installation: add it, or later remove it (loop stays)
|
|
261
|
+
agent-ops update --auto-run on
|
|
260
262
|
```
|
|
261
263
|
|
|
262
264
|
## Quick start from a source checkout
|
|
@@ -75,6 +75,12 @@ export function parseArgs(argv) {
|
|
|
75
75
|
let targetVersion;
|
|
76
76
|
let title;
|
|
77
77
|
let intent;
|
|
78
|
+
let taskGoal;
|
|
79
|
+
let debug = false;
|
|
80
|
+
let debugAction;
|
|
81
|
+
let payloadFile;
|
|
82
|
+
let commandId;
|
|
83
|
+
let eventId;
|
|
78
84
|
let sessionId;
|
|
79
85
|
let base;
|
|
80
86
|
let parentBase;
|
|
@@ -95,6 +101,7 @@ export function parseArgs(argv) {
|
|
|
95
101
|
const checkAuthTargets = [];
|
|
96
102
|
let completionGate;
|
|
97
103
|
let office;
|
|
104
|
+
let autoRun;
|
|
98
105
|
let dryRun = false;
|
|
99
106
|
let json = false;
|
|
100
107
|
let yes = false;
|
|
@@ -209,6 +216,40 @@ export function parseArgs(argv) {
|
|
|
209
216
|
index += 1;
|
|
210
217
|
break;
|
|
211
218
|
}
|
|
219
|
+
case "--goal":
|
|
220
|
+
if (taskGoal !== undefined)
|
|
221
|
+
duplicate(token);
|
|
222
|
+
taskGoal = readOptionValue(argv, index++, token);
|
|
223
|
+
break;
|
|
224
|
+
case "--debug":
|
|
225
|
+
if (debug)
|
|
226
|
+
duplicate(token);
|
|
227
|
+
debug = true;
|
|
228
|
+
break;
|
|
229
|
+
case "--debug-action": {
|
|
230
|
+
if (debugAction !== undefined)
|
|
231
|
+
duplicate(token);
|
|
232
|
+
const value = readOptionValue(argv, index++, token);
|
|
233
|
+
if (!["status", "hypothesis", "strategy", "investigate", "checkpoint", "resume", "extend"].includes(value))
|
|
234
|
+
invalidValue(token, value);
|
|
235
|
+
debugAction = value;
|
|
236
|
+
break;
|
|
237
|
+
}
|
|
238
|
+
case "--payload-file":
|
|
239
|
+
if (payloadFile !== undefined)
|
|
240
|
+
duplicate(token);
|
|
241
|
+
payloadFile = readOptionValue(argv, index++, token);
|
|
242
|
+
break;
|
|
243
|
+
case "--command-id":
|
|
244
|
+
if (commandId !== undefined)
|
|
245
|
+
duplicate(token);
|
|
246
|
+
commandId = readOptionValue(argv, index++, token);
|
|
247
|
+
break;
|
|
248
|
+
case "--event-id":
|
|
249
|
+
if (eventId !== undefined)
|
|
250
|
+
duplicate(token);
|
|
251
|
+
eventId = readOptionValue(argv, index++, token);
|
|
252
|
+
break;
|
|
212
253
|
case "--tree": {
|
|
213
254
|
if (tree)
|
|
214
255
|
duplicate(token);
|
|
@@ -328,6 +369,16 @@ export function parseArgs(argv) {
|
|
|
328
369
|
index += 1;
|
|
329
370
|
break;
|
|
330
371
|
}
|
|
372
|
+
case "--auto-run": {
|
|
373
|
+
if (autoRun !== undefined)
|
|
374
|
+
duplicate(token);
|
|
375
|
+
const value = readOptionValue(argv, index, token);
|
|
376
|
+
if (value !== "on" && value !== "off")
|
|
377
|
+
invalidValue(token, value);
|
|
378
|
+
autoRun = value;
|
|
379
|
+
index += 1;
|
|
380
|
+
break;
|
|
381
|
+
}
|
|
331
382
|
case "--worktree": {
|
|
332
383
|
if (worktree !== undefined) {
|
|
333
384
|
duplicate(token);
|
|
@@ -442,7 +493,7 @@ export function parseArgs(argv) {
|
|
|
442
493
|
"complete",
|
|
443
494
|
"create",
|
|
444
495
|
"export",
|
|
445
|
-
"status", "revise", "replan", "pin-finding"
|
|
496
|
+
"status", "revise", "replan", "pin-finding", "debug"
|
|
446
497
|
].includes(token)) {
|
|
447
498
|
action = token;
|
|
448
499
|
break;
|
|
@@ -523,6 +574,7 @@ export function parseArgs(argv) {
|
|
|
523
574
|
reviewTargets.length > 0 ||
|
|
524
575
|
completionGate !== undefined ||
|
|
525
576
|
office !== undefined ||
|
|
577
|
+
autoRun !== undefined ||
|
|
526
578
|
sessionId !== undefined ||
|
|
527
579
|
base !== undefined ||
|
|
528
580
|
parentBase !== undefined ||
|
|
@@ -616,10 +668,22 @@ export function parseArgs(argv) {
|
|
|
616
668
|
if (office !== undefined && command !== "init" && command !== "update") {
|
|
617
669
|
throw new CliArgumentError("CLI_OPTION_NOT_ALLOWED", "--office may be used only with init or update.");
|
|
618
670
|
}
|
|
671
|
+
if (autoRun !== undefined && command !== "update") {
|
|
672
|
+
throw new CliArgumentError("CLI_OPTION_NOT_ALLOWED", "--auto-run may be used only with update; init selects the run profile.");
|
|
673
|
+
}
|
|
619
674
|
if (worktree !== undefined && command !== "init" && command !== "update") {
|
|
620
675
|
throw new CliArgumentError("CLI_OPTION_NOT_ALLOWED", "--worktree may be used only with init or update.");
|
|
621
676
|
}
|
|
622
677
|
const revisionAction = command === "task" && ["revise", "replan", "pin-finding"].includes(action ?? "");
|
|
678
|
+
if ((taskGoal !== undefined || debug) && !(command === "task" && action === "create"))
|
|
679
|
+
throw new CliArgumentError("CLI_OPTION_NOT_ALLOWED", "--goal and --debug apply only to task create.");
|
|
680
|
+
if ((debugAction !== undefined || commandId !== undefined || eventId !== undefined || payloadFile !== undefined) && !(command === "task" && action === "debug"))
|
|
681
|
+
throw new CliArgumentError("CLI_OPTION_NOT_ALLOWED", "Debug operation options apply only to task debug.");
|
|
682
|
+
if (command === "task" && action === "debug" && (taskId === undefined || debugAction === undefined || title !== undefined || intent !== undefined ||
|
|
683
|
+
parentTaskId !== undefined || criteria.length > 0 || evidence.length > 0 ||
|
|
684
|
+
((debugAction === "hypothesis" || debugAction === "strategy") !== (payloadFile !== undefined)) ||
|
|
685
|
+
((debugAction === "investigate") !== (commandId !== undefined)) || ((debugAction === "extend") !== (eventId !== undefined))))
|
|
686
|
+
throw new CliArgumentError("CLI_MISSING_VALUE", "Task debug requires --task, --debug-action and the operation's exact payload/command/event options.");
|
|
623
687
|
if ((criterionFiles.length > 0 && !(command === "task" && (action === "create" || action === "pin-finding"))) ||
|
|
624
688
|
(criteriaFile !== undefined && !(command === "task" && action === "revise")) ||
|
|
625
689
|
(planFile !== undefined && !(command === "task" && action === "replan")) ||
|
|
@@ -790,9 +854,16 @@ export function parseArgs(argv) {
|
|
|
790
854
|
...(targetVersion === undefined ? {} : { targetVersion }),
|
|
791
855
|
...(title === undefined ? {} : { title }),
|
|
792
856
|
...(intent === undefined ? {} : { intent }),
|
|
857
|
+
...(taskGoal === undefined ? {} : { goal: taskGoal }),
|
|
858
|
+
...(debug ? { debug: true } : {}),
|
|
859
|
+
...(debugAction === undefined ? {} : { debugAction }),
|
|
860
|
+
...(payloadFile === undefined ? {} : { payloadFile }),
|
|
861
|
+
...(commandId === undefined ? {} : { commandId }),
|
|
862
|
+
...(eventId === undefined ? {} : { eventId }),
|
|
793
863
|
...(reviewTargets.length === 0 ? {} : { reviewTargets }),
|
|
794
864
|
...(completionGate === undefined ? {} : { completionGate }),
|
|
795
865
|
...(office === undefined ? {} : { office }),
|
|
866
|
+
...(autoRun === undefined ? {} : { autoRun }),
|
|
796
867
|
...(criteria.length === 0 ? {} : { criteria }),
|
|
797
868
|
...(criterionFiles.length === 0 ? {} : { criterionFiles }),
|
|
798
869
|
...(criteriaFile === undefined ? {} : { criteriaFile }),
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { advancePhaseObserver, productionRunContext } from "./run-deps.js";
|
|
3
3
|
import { AgentOpsError } from "../../../runtime/src/fs/paths.js";
|
|
4
4
|
import { recordRunVerification } from "../../../runtime/src/run/verification.js";
|
|
5
5
|
import { runOwnedLocalProof } from "./owned-run-step.js";
|
|
6
6
|
import { runRunCommand } from "./commands/run.js";
|
|
7
7
|
import { runOfficeCommand } from "./commands/office.js";
|
|
8
|
-
import { ensureBackgroundOffice, observeOfficeSession, openOfficeBrowser, serveOffice } from "./office-entry.js";
|
|
8
|
+
import { ensureBackgroundOffice, observeOfficeSession, officePhaseReporter, openOfficeBrowser, serveOffice } from "./office-entry.js";
|
|
9
9
|
import { readFile, readdir, stat } from "node:fs/promises";
|
|
10
10
|
import { randomUUID } from "node:crypto";
|
|
11
11
|
import { homedir } from "node:os";
|
|
@@ -42,6 +42,7 @@ import { hasFreshVerification, runReviewCommand } from "./commands/review.js";
|
|
|
42
42
|
import { runBatchCommand } from "./commands/batch.js";
|
|
43
43
|
import { createSourceGuard } from "../../../runtime/src/review/batch-guard.js";
|
|
44
44
|
import { withReviewSlot } from "../../../runtime/src/review/slots.js";
|
|
45
|
+
import { withActivity } from "../../../runtime/src/office/activity.js";
|
|
45
46
|
import { memoizePreflight } from "../../../runtime/src/review/batch.js";
|
|
46
47
|
import { createReviewExecutor, ReviewInterruptedError } from "../../../runtime/src/review/execute.js";
|
|
47
48
|
import { probeReviewTargetDetailed } from "../../../runtime/src/review/probe.js";
|
|
@@ -60,6 +61,16 @@ import { runWorktreeCommand } from "./commands/parallel.js";
|
|
|
60
61
|
import { ensureSessionWorktree, readWorktreeRecord, resolveCheckouts } from "../../../runtime/src/parallel/service.js";
|
|
61
62
|
import { listWorktrees, branchLockDoctorResult, worktreeDoctorResult } from "../../../runtime/src/parallel/manage.js";
|
|
62
63
|
import { detectGhostFiles, ghostFilesDoctorResult } from "../../../runtime/src/install/doctor.js";
|
|
64
|
+
/** The repository's common Git directory, where display-only office records live; undefined when git cannot say. */
|
|
65
|
+
async function officeCommonDir(dir) {
|
|
66
|
+
try {
|
|
67
|
+
const result = await gitRunner(dir).run(["rev-parse", "--path-format=absolute", "--git-common-dir"]);
|
|
68
|
+
return result.exitCode === 0 ? new TextDecoder().decode(result.stdout).trim() || undefined : undefined;
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
return undefined;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
63
74
|
const HOOK_RUNTIME_PATH = fileURLToPath(new URL("./hook-entry.js", import.meta.url));
|
|
64
75
|
async function readOptionalText(path) {
|
|
65
76
|
try {
|
|
@@ -413,6 +424,7 @@ else {
|
|
|
413
424
|
calculateTrustBinding: async (config) => await plannedTrustBinding(root, config),
|
|
414
425
|
confirm: async (plan, trust) => await confirmPlan(formatUpdatePlan(plan, trust)),
|
|
415
426
|
promptOffice: async (message, enabled) => await selectYesNo(message, { input: process.stdin, output: process.stdout }, enabled),
|
|
427
|
+
promptAutoRun: async (message, enabled) => await selectYesNo(message, { input: process.stdin, output: process.stdout }, enabled),
|
|
416
428
|
promptWorktree: async (message) => await selectYesNo(message, { input: process.stdin, output: process.stdout }, true),
|
|
417
429
|
...(updateArgs.targetVersion === undefined
|
|
418
430
|
? {}
|
|
@@ -450,13 +462,13 @@ else {
|
|
|
450
462
|
? await resolveCommandSessionId(root)
|
|
451
463
|
: sessionIdFromEnvironment() ?? await readRecordedSessionId(root));
|
|
452
464
|
if (args.action === "advance") {
|
|
465
|
+
const advanceSession = sessionId ?? await resolveCommandSessionId(root);
|
|
453
466
|
return await runAdvanceCommand({
|
|
454
467
|
cwd: root,
|
|
455
|
-
sessionId:
|
|
468
|
+
sessionId: advanceSession,
|
|
456
469
|
parentTaskId: args.taskId,
|
|
457
470
|
deps: worktreeDependencies(),
|
|
458
|
-
|
|
459
|
-
{ onPhase: await runPhaseObserver(root, process.env.AGENT_OPS_RUN_ID, process.env.AGENT_OPS_WORKER_ID) })
|
|
471
|
+
onPhase: await advancePhaseObserver(root, advanceSession)
|
|
460
472
|
});
|
|
461
473
|
}
|
|
462
474
|
const createConfig = args.action === "create"
|
|
@@ -476,6 +488,7 @@ else {
|
|
|
476
488
|
return await runTaskCommand({
|
|
477
489
|
args,
|
|
478
490
|
service: taskService,
|
|
491
|
+
...(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"; })() }),
|
|
479
492
|
...(policyConfigHash === undefined ? {} : { policyConfigHash }),
|
|
480
493
|
...(sessionId === undefined ? {} : { sessionId }),
|
|
481
494
|
...(fromMain
|
|
@@ -521,16 +534,53 @@ else {
|
|
|
521
534
|
const slotDir = commonDirResult.exitCode === 0
|
|
522
535
|
? new TextDecoder().decode(commonDirResult.stdout).trim()
|
|
523
536
|
: undefined;
|
|
537
|
+
let reviewActivity;
|
|
538
|
+
const inActivity = (executor) => async (request) => await withActivity(slotDir, { kind: "review", root,
|
|
539
|
+
...(reviewArgs.taskId === undefined ? {} : { taskId: reviewArgs.taskId }),
|
|
540
|
+
...(reviewSessionId === undefined ? {} : { sessionId: reviewSessionId }) }, async (handle) => {
|
|
541
|
+
reviewActivity = handle;
|
|
542
|
+
try {
|
|
543
|
+
return await executor(request);
|
|
544
|
+
}
|
|
545
|
+
finally {
|
|
546
|
+
reviewActivity = undefined;
|
|
547
|
+
}
|
|
548
|
+
});
|
|
549
|
+
const debugRecord = reviewArgs.taskId === undefined ? undefined : await taskService.status({ taskId: reviewArgs.taskId });
|
|
550
|
+
const inDebug = (executor) => debugRecord?.task.debug === undefined ? executor : async (request) => await taskService.debugService().review(debugRecord.task.id, () => executor(request));
|
|
524
551
|
const inSlot = (executor) => slotDir === undefined
|
|
525
|
-
? executor
|
|
552
|
+
? inDebug(executor)
|
|
526
553
|
: async (request) => await withReviewSlot({
|
|
527
554
|
dir: slotDir,
|
|
528
555
|
signal,
|
|
529
556
|
holder: { root, ...(reviewArgs.taskId === undefined ? {} : { taskId: reviewArgs.taskId }) },
|
|
557
|
+
...(debugRecord?.task.debug === undefined || reviewSessionId === undefined
|
|
558
|
+
? {}
|
|
559
|
+
: (() => {
|
|
560
|
+
let paused = false;
|
|
561
|
+
const debug = taskService.debugService();
|
|
562
|
+
return {
|
|
563
|
+
onWaitStart: async () => {
|
|
564
|
+
if (paused)
|
|
565
|
+
return;
|
|
566
|
+
const current = await debug.root(debugRecord.task.id).catch(() => null);
|
|
567
|
+
if (current?.debug.foreground?.sessionId !== reviewSessionId)
|
|
568
|
+
return;
|
|
569
|
+
await debug.foregroundStop(debugRecord.task.id, reviewSessionId);
|
|
570
|
+
paused = true;
|
|
571
|
+
},
|
|
572
|
+
onWaitEnd: async () => {
|
|
573
|
+
if (!paused)
|
|
574
|
+
return;
|
|
575
|
+
await debug.foregroundStart(debugRecord.task.id, reviewSessionId);
|
|
576
|
+
paused = false;
|
|
577
|
+
}
|
|
578
|
+
};
|
|
579
|
+
})()),
|
|
530
580
|
onWait: (line) => {
|
|
531
581
|
process.stderr.write(`${progressPrefix}${line}\n`);
|
|
532
582
|
}
|
|
533
|
-
}, async () => await executor(request));
|
|
583
|
+
}, async () => await inDebug(executor)(request));
|
|
534
584
|
return {
|
|
535
585
|
args: reviewArgs,
|
|
536
586
|
authorized: reviewArgs.yes,
|
|
@@ -549,7 +599,8 @@ else {
|
|
|
549
599
|
currentPolicyConfigHash: async () => calculateConfigHash((await loadEffectiveConfig(root, reviewArgs.scope === "user" ? "user" : "project")).config),
|
|
550
600
|
config: reviewConfig,
|
|
551
601
|
evidenceStore: new FileEvidenceStore(root, root),
|
|
552
|
-
execute: inSlot(createReviewExecutor({
|
|
602
|
+
execute: inActivity(inSlot(createReviewExecutor({
|
|
603
|
+
...(debugRecord?.task.debug === undefined ? {} : { remainingBudgetMs: () => taskService.debugService().remaining(debugRecord.task.id) }),
|
|
553
604
|
runner: registeredRunProofRunner(),
|
|
554
605
|
targets: configuredReviewTargets,
|
|
555
606
|
cwd: root,
|
|
@@ -575,8 +626,11 @@ else {
|
|
|
575
626
|
signal,
|
|
576
627
|
onProgress: (line) => {
|
|
577
628
|
process.stderr.write(`${progressPrefix}${line}\n`);
|
|
629
|
+
},
|
|
630
|
+
onRound: (round, target, targets) => {
|
|
631
|
+
void reviewActivity?.update({ round, target, targets });
|
|
578
632
|
}
|
|
579
|
-
}))
|
|
633
|
+
})))
|
|
580
634
|
};
|
|
581
635
|
};
|
|
582
636
|
if (args.command === "review") {
|
|
@@ -662,7 +716,10 @@ else {
|
|
|
662
716
|
signal: controller.signal,
|
|
663
717
|
onProgress: (line) => {
|
|
664
718
|
process.stderr.write(`batch: ${line}\n`);
|
|
665
|
-
}
|
|
719
|
+
},
|
|
720
|
+
// The batch's tasks name the sessions whose people follow its steps.
|
|
721
|
+
phaseReporter: async (taskIds) => await officePhaseReporter(root, (await new FileTaskStore(join(root, ".agent-ops", "tasks", "state.json"), root).read()).sessions
|
|
722
|
+
.filter(({ taskId }) => taskIds.includes(taskId)).map(({ sessionId }) => sessionId))
|
|
666
723
|
});
|
|
667
724
|
if (interruptedBy !== undefined) {
|
|
668
725
|
process.exit(interruptedBy === "SIGINT" ? 130 : 143);
|
|
@@ -681,9 +738,11 @@ else {
|
|
|
681
738
|
const merged = await loadEffectiveConfig(root, args.scope === "user" ? "user" : "project");
|
|
682
739
|
const config = merged.config;
|
|
683
740
|
const trustStatus = await repositoryTrust(root, config, CLI_VERSION);
|
|
741
|
+
const verifySessionId = sessionIdFromEnvironment();
|
|
684
742
|
return await runVerifyCommand({
|
|
685
743
|
args,
|
|
686
744
|
taskService,
|
|
745
|
+
activity: { commonDir: await officeCommonDir(root), root, ...(verifySessionId === undefined ? {} : { sessionId: verifySessionId }) },
|
|
687
746
|
service: { verify: async (taskId) => {
|
|
688
747
|
const run = await runPolicyContext(root);
|
|
689
748
|
const report = await new VerificationService({
|
|
@@ -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|advance|complete|archive|export>
|
|
28
|
+
task <create|status|attach|revise|replan|pin-finding|debug|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
|
|
@@ -45,6 +45,7 @@ Options:
|
|
|
45
45
|
--review-target <codex|agy|claude> Repeatable init option; review pair order
|
|
46
46
|
--completion-gate Init only: enable the project-loop completion gate
|
|
47
47
|
--office <on|off> Init/update: enable or disable Office (Preview)
|
|
48
|
+
--auto-run <on|off> Update: add or remove the run profile (loop stays)
|
|
48
49
|
--worktree <auto|off> Init/update: configure session worktree isolation
|
|
49
50
|
--check-auth Doctor only: probe selected review targets'
|
|
50
51
|
authentication with one real call
|
|
@@ -152,6 +153,7 @@ Options:
|
|
|
152
153
|
--harness <all|both|agy|claude|codex|opencode|comma-separated>
|
|
153
154
|
--target-version <version> Offline-capable update target
|
|
154
155
|
--office <on|off> Office (Preview); preserve choice unless supplied
|
|
156
|
+
--auto-run <on|off> Add or remove the run profile (loop stays); preserve unless supplied
|
|
155
157
|
--worktree <auto|off> Configure session worktree isolation
|
|
156
158
|
--dry-run
|
|
157
159
|
--json
|
|
@@ -168,13 +170,19 @@ Options:
|
|
|
168
170
|
--json
|
|
169
171
|
--yes
|
|
170
172
|
`,
|
|
171
|
-
task: `Usage: agent-ops task <create|status|attach|revise|replan|pin-finding|advance|complete|archive|export> [options]
|
|
173
|
+
task: `Usage: agent-ops task <create|status|attach|revise|replan|pin-finding|debug|advance|complete|archive|export> [options]
|
|
172
174
|
|
|
173
175
|
Manage independent task acceptance state. Task commands accept none of
|
|
174
176
|
--harness, --profile or --dry-run. Only advance accepts --yes.
|
|
175
177
|
|
|
176
178
|
Options:
|
|
177
179
|
--title <text> create
|
|
180
|
+
--goal <text> create: immutable original issue/goal
|
|
181
|
+
--debug create: task-level investigation tag
|
|
182
|
+
--debug-action <status|hypothesis|strategy|investigate|checkpoint|resume|extend>
|
|
183
|
+
--payload-file <path> debug hypothesis/strategy: bounded JSON
|
|
184
|
+
--command-id <id> debug investigate: trusted configured command
|
|
185
|
+
--event-id <native-event-id|latest> debug extend: authentic human continue event
|
|
178
186
|
--intent <text> create: intended behavior and constraints before editing
|
|
179
187
|
--criterion <json> create, repeatable, two to five total
|
|
180
188
|
--criterion-file <path> create: one criterion object, repeatable; pin-finding: exactly one
|
|
@@ -186,7 +194,7 @@ Options:
|
|
|
186
194
|
--baseline <git-ref> create/revise: baseline for new mechanical requirements
|
|
187
195
|
--parent <task-id> create: record a subtask; status: list subtasks
|
|
188
196
|
--task <id> status, attach, revise, replan, pin-finding, complete, archive, export, advance
|
|
189
|
-
--session <id> create, attach, status, advance
|
|
197
|
+
--session <id> create, attach, status, advance, debug
|
|
190
198
|
--evidence <criterion-id=reference> complete, repeatable
|
|
191
199
|
--base <git-ref> complete: a clean committed range
|
|
192
200
|
--yes advance: authorize the two review sessions
|
|
@@ -1,5 +1,6 @@
|
|
|
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";
|
|
3
4
|
const MAX_LOOP_INPUT_BYTES = 64 * 1024;
|
|
4
5
|
function isLoopHarness(value) {
|
|
5
6
|
return value === "claude" || value === "codex";
|
|
@@ -73,6 +74,11 @@ export async function runLoopProcess(argv, io, dependencies = {}) {
|
|
|
73
74
|
? {}
|
|
74
75
|
: { telemetryMaxBytes: dependencies.telemetryMaxBytes })
|
|
75
76
|
});
|
|
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
|
+
}
|
|
76
82
|
if (result.stdout.length > 0) {
|
|
77
83
|
io.writeStdout(result.stdout);
|
|
78
84
|
}
|
|
@@ -66,6 +66,7 @@ export async function runBatchCommand(options) {
|
|
|
66
66
|
};
|
|
67
67
|
}
|
|
68
68
|
const width = args.width ?? DEFAULT_BATCH_WIDTH;
|
|
69
|
+
const report = await options.phaseReporter?.(plan.map((item) => item.record.task.id)).catch(() => undefined);
|
|
69
70
|
const outcomes = await runBatch(plan.map((item) => item.record.task.id), {
|
|
70
71
|
isVerified: (id) => options.isVerified(id, base(id)),
|
|
71
72
|
verify: (id, signal) => options.verify(id, base(id), signal),
|
|
@@ -74,7 +75,8 @@ export async function runBatchCommand(options) {
|
|
|
74
75
|
}, {
|
|
75
76
|
width,
|
|
76
77
|
...(options.signal === undefined ? {} : { signal: options.signal }),
|
|
77
|
-
...(options.onProgress === undefined ? {} : { onProgress: options.onProgress })
|
|
78
|
+
...(options.onProgress === undefined ? {} : { onProgress: options.onProgress }),
|
|
79
|
+
...(report === undefined ? {} : { onPhase: (phase) => void report(phase).catch(() => { }) })
|
|
78
80
|
});
|
|
79
81
|
const byId = new Map(outcomes.map((outcome) => [outcome.id, outcome]));
|
|
80
82
|
const tasks = plan.map((item) => {
|
|
@@ -28,7 +28,9 @@ export async function runHookCommand(options) {
|
|
|
28
28
|
const { capabilities } = options.config.profiles.length === 0
|
|
29
29
|
? { capabilities: [] }
|
|
30
30
|
: resolveCapabilities(options.config);
|
|
31
|
-
const input =
|
|
31
|
+
const input = options.input === undefined
|
|
32
|
+
? JSON.parse(options.stdin)
|
|
33
|
+
: options.input;
|
|
32
34
|
const descriptor = harnessDescriptor(options.harness);
|
|
33
35
|
const normalized = normalizeHookInput(options.harness, input);
|
|
34
36
|
if (normalized === null ||
|
|
@@ -55,6 +57,7 @@ export async function runHookCommand(options) {
|
|
|
55
57
|
? {}
|
|
56
58
|
: { worktreeEnter: options.worktreeEnter })
|
|
57
59
|
});
|
|
60
|
+
options.onResult?.(result);
|
|
58
61
|
return descriptor.runtime.formatOutput(options.event, result);
|
|
59
62
|
}
|
|
60
63
|
catch {
|
|
@@ -15,6 +15,7 @@ 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";
|
|
18
19
|
import { okEnvelope } from "../output.js";
|
|
19
20
|
/**
|
|
20
21
|
* Review runs against one target. Argument parsing already rejects a
|
|
@@ -640,6 +641,7 @@ export async function runReviewCommand(options) {
|
|
|
640
641
|
}
|
|
641
642
|
context.coverage = rows;
|
|
642
643
|
context.binding = {
|
|
644
|
+
...(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))]))) }),
|
|
643
645
|
taskContractHash: taskContractHash(context.records.find(r => r.task.id === context.taskId).task),
|
|
644
646
|
taskContracts: Object.fromEntries(context.records.map(r => [r.task.id, taskContractHash(r.task)])),
|
|
645
647
|
...(context.treeTasks === undefined ? {} : { treeContractHash: treeContractHash(context.records.map(r => r.task)) }),
|
|
@@ -682,7 +684,8 @@ export async function runReviewCommand(options) {
|
|
|
682
684
|
const content = redactSecrets(JSON.stringify({ binding: context.binding, coverage: context.coverage,
|
|
683
685
|
executionPolicy: options.config,
|
|
684
686
|
artifacts: contractArtifacts.map(({ path, digest }) => ({ path, digest })),
|
|
685
|
-
tasks: context.records.map(r => ({ task: r.task, revisions: r.revisions ?? []
|
|
687
|
+
tasks: await Promise.all(context.records.map(async (r) => ({ task: r.task, revisions: r.revisions ?? [],
|
|
688
|
+
...(r.task.debug === undefined ? {} : { debugProof: await loadDebugProof(options.root, r.task.debug.rootTaskId) }) }))),
|
|
686
689
|
superseded: context.historyRecords ?? [] }, null, 2));
|
|
687
690
|
const digest = sha256(content);
|
|
688
691
|
const path = ".agent-ops/tasks/review-contracts/" + digest + ".json";
|
|
@@ -719,7 +722,7 @@ export async function runReviewCommand(options) {
|
|
|
719
722
|
const priorFindings = options.root === undefined || context === undefined
|
|
720
723
|
? []
|
|
721
724
|
: await findPriorFailingFindings(options.root, context.taskId);
|
|
722
|
-
const
|
|
725
|
+
const executeReview = async () => await runIndependentReview({
|
|
723
726
|
invocation: {
|
|
724
727
|
harness: target,
|
|
725
728
|
plannedTargets,
|
|
@@ -738,6 +741,7 @@ export async function runReviewCommand(options) {
|
|
|
738
741
|
reason: "missing-cli"
|
|
739
742
|
}))
|
|
740
743
|
});
|
|
744
|
+
const result = await executeReview();
|
|
741
745
|
if (scope !== undefined && result.report !== undefined && options.root !== undefined) {
|
|
742
746
|
try {
|
|
743
747
|
if (result.report.changedFilesInspected.length !== scope.changedFiles.length ||
|
|
@@ -781,7 +785,9 @@ export async function runReviewCommand(options) {
|
|
|
781
785
|
await options.tasks.treeContract(context.taskId) !== context.binding.treeContractHash) ||
|
|
782
786
|
(context !== undefined && options.tasks !== undefined &&
|
|
783
787
|
(await Promise.all(context.records.map(async (r) => taskContractHash((await options.tasks.status({ taskId: r.task.id })).task))))
|
|
784
|
-
.some((hash, i) => hash !== taskContractHash(context.records[i].task)))
|
|
788
|
+
.some((hash, i) => hash !== taskContractHash(context.records[i].task))) ||
|
|
789
|
+
(context?.binding?.debugProofs !== undefined && options.root !== undefined &&
|
|
790
|
+
(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))) {
|
|
785
791
|
const changed = await persistReviewEvidence(options, sourceChangedResult(result), sourceFingerprint, context);
|
|
786
792
|
return notRunEnvelope(changed);
|
|
787
793
|
}
|
|
@@ -60,7 +60,7 @@ function taskAction(args) {
|
|
|
60
60
|
"complete",
|
|
61
61
|
"create",
|
|
62
62
|
"export",
|
|
63
|
-
"status", "revise", "replan", "pin-finding"
|
|
63
|
+
"status", "revise", "replan", "pin-finding", "debug"
|
|
64
64
|
].includes(args.action)) {
|
|
65
65
|
throw new AgentOpsError("TASK_ACTION_INVALID", "A supported task action is required.");
|
|
66
66
|
}
|
|
@@ -72,7 +72,7 @@ function taskEnvelope(action, code, message, record) {
|
|
|
72
72
|
message,
|
|
73
73
|
record,
|
|
74
74
|
contractHash: taskContractHash(record.task),
|
|
75
|
-
text: renderTaskMarkdown(record) + "\nContract: " + taskContractHash(record.task)
|
|
75
|
+
text: renderTaskMarkdown(record) + "\nContract: " + taskContractHash(record.task) + (record.debug === undefined ? "" : "\nDEBUG: " + JSON.stringify(record.debug))
|
|
76
76
|
});
|
|
77
77
|
}
|
|
78
78
|
function renderTaskList(records) {
|
|
@@ -91,11 +91,15 @@ export async function runTaskCommand(options) {
|
|
|
91
91
|
if (options.args.title === undefined) {
|
|
92
92
|
throw new AgentOpsError("TASK_TITLE_REQUIRED", "Task creation requires --title.");
|
|
93
93
|
}
|
|
94
|
+
if (options.args.debug)
|
|
95
|
+
await options.service.assertDebugBaseline();
|
|
94
96
|
const worktree = sessionId === undefined || options.sessionWorktree === undefined
|
|
95
97
|
? undefined
|
|
96
98
|
: await options.sessionWorktree(sessionId);
|
|
97
99
|
const record = await (worktree?.service ?? options.service).create({
|
|
98
100
|
title: options.args.title,
|
|
101
|
+
...(options.args.goal === undefined ? {} : { goal: options.args.goal }),
|
|
102
|
+
...(options.args.debug ? { debug: {} } : {}),
|
|
99
103
|
...(options.args.intent === undefined ? {} : { intent: options.args.intent }),
|
|
100
104
|
criteria: [...(options.args.criteria ?? []).map(parseCriterion),
|
|
101
105
|
...await Promise.all((options.args.criterionFiles ?? []).map(async (path) => await jsonFile(path)))]
|
|
@@ -109,6 +113,12 @@ export async function runTaskCommand(options) {
|
|
|
109
113
|
: { parentTaskId: options.args.parentTaskId }),
|
|
110
114
|
...(sessionId === undefined ? {} : { sessionId })
|
|
111
115
|
});
|
|
116
|
+
// An explicit CLI DEBUG intake may be the only start signal on hosts
|
|
117
|
+
// without a prompt hook (notably agy). The native session owns the clock;
|
|
118
|
+
// foregroundStart is idempotent for a repeated hook or command.
|
|
119
|
+
if (options.args.debug && sessionId !== undefined) {
|
|
120
|
+
await (worktree?.service ?? options.service).debugService().foregroundStart(record.task.id, sessionId);
|
|
121
|
+
}
|
|
112
122
|
const created = taskEnvelope(action, "TASK_CREATED", sessionId === undefined
|
|
113
123
|
? `Created task ${record.task.id}.`
|
|
114
124
|
: record.task.parentTaskId === undefined
|
|
@@ -134,6 +144,38 @@ export async function runTaskCommand(options) {
|
|
|
134
144
|
}
|
|
135
145
|
};
|
|
136
146
|
}
|
|
147
|
+
if (action === "debug") {
|
|
148
|
+
const id = requireTaskId(options.args), service = options.service.debugService();
|
|
149
|
+
switch (options.args.debugAction) {
|
|
150
|
+
case "hypothesis":
|
|
151
|
+
await service.hypothesis(id, await jsonFile(options.args.payloadFile));
|
|
152
|
+
break;
|
|
153
|
+
case "strategy":
|
|
154
|
+
await service.strategy(id, await jsonFile(options.args.payloadFile));
|
|
155
|
+
break;
|
|
156
|
+
case "investigate":
|
|
157
|
+
await service.investigate(id, options.args.commandId, options.trusted === true);
|
|
158
|
+
break;
|
|
159
|
+
case "checkpoint":
|
|
160
|
+
if (sessionId === undefined)
|
|
161
|
+
throw new AgentOpsError("TASK_SESSION_REQUIRED", "Debug checkpoint requires a session.");
|
|
162
|
+
await service.checkpoint(id, sessionId);
|
|
163
|
+
break;
|
|
164
|
+
case "resume":
|
|
165
|
+
if (sessionId === undefined)
|
|
166
|
+
throw new AgentOpsError("TASK_SESSION_REQUIRED", "Debug resume requires a session.");
|
|
167
|
+
await service.resume(id, sessionId);
|
|
168
|
+
break;
|
|
169
|
+
case "extend":
|
|
170
|
+
if (sessionId === undefined)
|
|
171
|
+
throw new AgentOpsError("TASK_SESSION_REQUIRED", "Debug extension requires a native session.");
|
|
172
|
+
await service.extend(id, sessionId, options.args.eventId);
|
|
173
|
+
break;
|
|
174
|
+
case "status": break;
|
|
175
|
+
default: throw new AgentOpsError("TASK_DEBUG_ACTION_INVALID", "A supported debug operation is required.");
|
|
176
|
+
}
|
|
177
|
+
return taskEnvelope(action, "TASK_DEBUG_UPDATED", "Original issue debug state preserved.", await service.root(id));
|
|
178
|
+
}
|
|
137
179
|
if (action === "revise") {
|
|
138
180
|
const payload = await jsonFile(options.args.criteriaFile);
|
|
139
181
|
if (!Array.isArray(payload))
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { readFile } from "node:fs/promises";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { loadConfigFile } from "../../../../runtime/src/config/load.js";
|
|
4
|
+
import { parseInstallManifest } from "../../../../runtime/src/fs/manifest.js";
|
|
4
5
|
import { detectWorktreeSetup } from "../../../../runtime/src/install/parallel-setup.js";
|
|
5
6
|
import { applyUpdatePlan, createUpdatePlan } from "../../../../runtime/src/install/update.js";
|
|
6
7
|
import { okEnvelope } from "../output.js";
|
|
@@ -17,6 +18,7 @@ export function formatUpdatePlan(plan, trust) {
|
|
|
17
18
|
metadata: [
|
|
18
19
|
`Target version: ${plan.targetVersion}`,
|
|
19
20
|
`Office (Preview): ${plan.installation.config.features.office?.enabled === true ? "enabled" : "disabled"}`,
|
|
21
|
+
`Auto-run: ${plan.installation.config.profiles.includes("run") ? "enabled" : "disabled"}`,
|
|
20
22
|
`Harness: ${plan.installation.harness.join(", ")}`,
|
|
21
23
|
...(plan.installation.config.worktree === undefined
|
|
22
24
|
? []
|
|
@@ -73,6 +75,14 @@ async function existingConfigHasWorktree(root) {
|
|
|
73
75
|
return false;
|
|
74
76
|
}
|
|
75
77
|
}
|
|
78
|
+
async function installedHarness(root) {
|
|
79
|
+
try {
|
|
80
|
+
return parseInstallManifest(await readFile(join(root, ".agent-ops", "manifest.json"), "utf8")).harness;
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
return [];
|
|
84
|
+
}
|
|
85
|
+
}
|
|
76
86
|
export async function runUpdateCommand(options) {
|
|
77
87
|
let worktree;
|
|
78
88
|
if (options.args.worktree === "auto") {
|
|
@@ -102,10 +112,18 @@ export async function runUpdateCommand(options) {
|
|
|
102
112
|
const { config } = await loadConfigFile(join(options.root, ".agent-ops", "config.json"));
|
|
103
113
|
officeEnabled = await options.promptOffice("Enable Office (Preview)?", config.features.office?.enabled === true);
|
|
104
114
|
}
|
|
115
|
+
let autoRun = options.args.autoRun === undefined ? undefined : options.args.autoRun === "on";
|
|
116
|
+
if (autoRun === undefined && options.promptAutoRun !== undefined && options.isTTY && !options.args.yes &&
|
|
117
|
+
(options.platform ?? process.platform) === "darwin" &&
|
|
118
|
+
(options.args.harness ?? await installedHarness(options.root)).some((harness) => harness === "claude" || harness === "codex")) {
|
|
119
|
+
const { config } = await loadConfigFile(join(options.root, ".agent-ops", "config.json"));
|
|
120
|
+
autoRun = await options.promptAutoRun("Enable auto-run (hand changes needing more than five acceptance criteria to agent-ops run)?", config.profiles.includes("run"));
|
|
121
|
+
}
|
|
105
122
|
const plan = await createUpdatePlan({
|
|
106
123
|
root: options.root,
|
|
107
124
|
adapters: options.adapters,
|
|
108
125
|
...(officeEnabled === undefined ? {} : { officeEnabled }),
|
|
126
|
+
...(autoRun === undefined ? {} : { autoRun }),
|
|
109
127
|
...(options.args.harness === undefined
|
|
110
128
|
? {}
|
|
111
129
|
: { harness: options.args.harness }),
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { withActivity } from "../../../../runtime/src/office/activity.js";
|
|
1
2
|
import { AgentOpsError } from "../../../../runtime/src/fs/paths.js";
|
|
2
3
|
import { redactSecrets } from "../../../../runtime/src/security/redact.js";
|
|
3
4
|
import { safeTaskText } from "../../../../runtime/src/task/render.js";
|
|
@@ -90,7 +91,8 @@ async function resolveTaskId(options) {
|
|
|
90
91
|
}
|
|
91
92
|
export async function runVerifyCommand(options) {
|
|
92
93
|
try {
|
|
93
|
-
const
|
|
94
|
+
const taskId = await resolveTaskId(options), activity = options.activity;
|
|
95
|
+
const report = publicReport(await withActivity(activity?.commonDir, { kind: "verify", root: activity?.root ?? "", taskId, ...(activity?.sessionId === undefined ? {} : { sessionId: activity.sessionId }) }, async () => await options.service.verify(taskId)));
|
|
94
96
|
const text = formatReport(report);
|
|
95
97
|
if (report.status === "PASS") {
|
|
96
98
|
return okEnvelope("VERIFICATION_PASSED", {
|