@kylecheng3146/agent-ops 0.5.3 → 0.7.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.
Files changed (124) hide show
  1. package/README.md +103 -1
  2. package/dist/packages/cli/src/args.js +183 -3
  3. package/dist/packages/cli/src/bin.js +76 -19
  4. package/dist/packages/cli/src/cli.js +53 -8
  5. package/dist/packages/cli/src/codex-loop-process.js +19 -2
  6. package/dist/packages/cli/src/commands/advance.js +42 -5
  7. package/dist/packages/cli/src/commands/init.js +2 -0
  8. package/dist/packages/cli/src/commands/office.js +20 -0
  9. package/dist/packages/cli/src/commands/review-show.js +1 -1
  10. package/dist/packages/cli/src/commands/review.js +100 -9
  11. package/dist/packages/cli/src/commands/run.js +115 -0
  12. package/dist/packages/cli/src/commands/task.js +61 -17
  13. package/dist/packages/cli/src/commands/update.js +8 -0
  14. package/dist/packages/cli/src/commands/verify.js +1 -0
  15. package/dist/packages/cli/src/context.js +64 -1
  16. package/dist/packages/cli/src/hook-entry.js +2 -1
  17. package/dist/packages/cli/src/hook-process.js +54 -3
  18. package/dist/packages/cli/src/loop-entry.js +2 -1
  19. package/dist/packages/cli/src/office-entry.js +230 -0
  20. package/dist/packages/cli/src/owned-run-step.js +65 -0
  21. package/dist/packages/cli/src/parallel-deps.js +18 -5
  22. package/dist/packages/cli/src/run-deps.js +321 -0
  23. package/dist/packages/cli/src/run-entry.js +952 -0
  24. package/dist/packages/cli/src/run-setup-entry.js +32 -0
  25. package/dist/packages/cli/src/run-step-entry.js +58 -0
  26. package/dist/packages/cli/src/wizard.js +14 -2
  27. package/dist/runtime/src/adapters/agy/config.js +4 -3
  28. package/dist/runtime/src/adapters/agy/events.js +9 -0
  29. package/dist/runtime/src/adapters/claude/config.js +3 -3
  30. package/dist/runtime/src/adapters/claude/events.js +8 -0
  31. package/dist/runtime/src/adapters/codex/config.js +3 -3
  32. package/dist/runtime/src/adapters/codex/events.js +8 -0
  33. package/dist/runtime/src/adapters/opencode/config.js +5 -4
  34. package/dist/runtime/src/adapters/opencode/events.js +8 -0
  35. package/dist/runtime/src/config/explain.js +11 -0
  36. package/dist/runtime/src/config/hash.js +1 -1
  37. package/dist/runtime/src/config/merge.js +9 -1
  38. package/dist/runtime/src/contracts.js +2 -2
  39. package/dist/runtime/src/hooks/codex-loop.js +4 -0
  40. package/dist/runtime/src/hooks/completion-gate.js +8 -2
  41. package/dist/runtime/src/install/doctor.js +2 -0
  42. package/dist/runtime/src/install/harness.js +44 -1
  43. package/dist/runtime/src/install/plan.js +16 -11
  44. package/dist/runtime/src/install/preauth.js +44 -18
  45. package/dist/runtime/src/install/profiles.js +8 -2
  46. package/dist/runtime/src/install/update.js +1 -0
  47. package/dist/runtime/src/office/collect.js +233 -0
  48. package/dist/runtime/src/office/page.js +873 -0
  49. package/dist/runtime/src/office/scene.js +168 -0
  50. package/dist/runtime/src/office/server.js +139 -0
  51. package/dist/runtime/src/office/sessions.js +115 -0
  52. package/dist/runtime/src/office/snapshot.js +107 -0
  53. package/dist/runtime/src/parallel/finish.js +85 -22
  54. package/dist/runtime/src/parallel/guard.js +12 -2
  55. package/dist/runtime/src/parallel/integrate.js +288 -10
  56. package/dist/runtime/src/parallel/manage.js +4 -1
  57. package/dist/runtime/src/parallel/receipt.js +71 -5
  58. package/dist/runtime/src/parallel/service.js +41 -1
  59. package/dist/runtime/src/review/attestation.js +36 -11
  60. package/dist/runtime/src/review/execute.js +24 -1
  61. package/dist/runtime/src/review/packet.js +35 -1
  62. package/dist/runtime/src/review/runner.js +12 -1
  63. package/dist/runtime/src/review/scope.js +18 -1
  64. package/dist/runtime/src/review/slots.js +35 -10
  65. package/dist/runtime/src/run/authorization.js +60 -0
  66. package/dist/runtime/src/run/budget.js +30 -0
  67. package/dist/runtime/src/run/controls.js +139 -0
  68. package/dist/runtime/src/run/convergence.js +177 -0
  69. package/dist/runtime/src/run/hosts/claude.js +180 -0
  70. package/dist/runtime/src/run/hosts/codex.js +279 -0
  71. package/dist/runtime/src/run/hosts/index.js +4 -0
  72. package/dist/runtime/src/run/hosts/protocol.js +254 -0
  73. package/dist/runtime/src/run/hosts/types.js +1 -0
  74. package/dist/runtime/src/run/hosts/util.js +136 -0
  75. package/dist/runtime/src/run/integration.js +204 -0
  76. package/dist/runtime/src/run/macOS.js +189 -0
  77. package/dist/runtime/src/run/ownership.js +22 -0
  78. package/dist/runtime/src/run/phase.js +37 -0
  79. package/dist/runtime/src/run/planning.js +25 -0
  80. package/dist/runtime/src/run/policy-transition.js +1076 -0
  81. package/dist/runtime/src/run/policy.js +176 -0
  82. package/dist/runtime/src/run/ratchet.js +71 -0
  83. package/dist/runtime/src/run/scheduler.js +260 -0
  84. package/dist/runtime/src/run/service.js +433 -0
  85. package/dist/runtime/src/run/supervisor.js +635 -0
  86. package/dist/runtime/src/run/transport.js +159 -0
  87. package/dist/runtime/src/run/types.js +1 -0
  88. package/dist/runtime/src/run/usage.js +81 -0
  89. package/dist/runtime/src/run/verification.js +64 -0
  90. package/dist/runtime/src/run/worker-gate.js +48 -0
  91. package/dist/runtime/src/schema/acceptance.js +91 -0
  92. package/dist/runtime/src/schema/validate.js +80 -9
  93. package/dist/runtime/src/task/completion.js +14 -2
  94. package/dist/runtime/src/task/contract.js +17 -0
  95. package/dist/runtime/src/task/pin-finding.js +81 -0
  96. package/dist/runtime/src/task/service.js +316 -13
  97. package/dist/runtime/src/task/store.js +57 -7
  98. package/dist/runtime/src/verify/acceptance-coverage.js +85 -0
  99. package/dist/runtime/src/verify/acceptance-protocol.js +252 -0
  100. package/dist/runtime/src/verify/acceptance-replay.js +239 -0
  101. package/dist/runtime/src/verify/adapters/index.js +5 -0
  102. package/dist/runtime/src/verify/adapters/jest.js +142 -0
  103. package/dist/runtime/src/verify/adapters/node-reporter.js +20 -0
  104. package/dist/runtime/src/verify/adapters/node.js +254 -0
  105. package/dist/runtime/src/verify/adapters/pytest.js +188 -0
  106. package/dist/runtime/src/verify/adapters/rust.js +84 -0
  107. package/dist/runtime/src/verify/adapters/shared.js +82 -0
  108. package/dist/runtime/src/verify/adapters/vitest.js +218 -0
  109. package/dist/runtime/src/verify/evidence.js +5 -2
  110. package/dist/runtime/src/verify/service.js +24 -8
  111. package/dist/runtime/src/verify/source-fingerprint.js +1 -1
  112. package/dist/runtime/src/verify/spawn.js +13 -2
  113. package/docs/en/spec/acceptance-and-evidence.md +48 -0
  114. package/docs/en/spec/harness-adapters.md +113 -1
  115. package/docs/en/spec/loop-engineering.md +49 -0
  116. package/docs/zh-TW/spec/acceptance-and-evidence.md +21 -1
  117. package/docs/zh-TW/spec/harness-adapters.md +93 -1
  118. package/docs/zh-TW/spec/loop-engineering.md +22 -0
  119. package/package.json +1 -1
  120. package/schemas/config.schema.json +230 -19
  121. package/schemas/evidence.schema.json +129 -6
  122. package/schemas/task.schema.json +265 -6
  123. package/templates/acceptance/agent_ops_acceptance.rs +36 -0
  124. package/templates/acceptance/agent_ops_pytest.py +46 -0
package/README.md CHANGED
@@ -12,6 +12,86 @@ pre-1.0 interface.
12
12
  The CLI is published as `@kylecheng3146/agent-ops` as a pre-1.0 interface;
13
13
  command behavior may change before 1.0.
14
14
 
15
+ ## Executable acceptance and native goal runs (0.6.0)
16
+
17
+ Criteria can use `behavioral`, `invariant` or `review-only` acceptance contracts.
18
+ Behavioral proof requires each designated red check to fail by assertion on its
19
+ immutable baseline and every required check to pass on the candidate. Invariants
20
+ can pass on both versions. Review-only requirements still need two fresh reviews
21
+ against the original goal and an explicit `reviewOnlyReason` in the acceptance
22
+ contract. Legacy criteria keep their existing verifier behavior.
23
+
24
+ Configure and trust `verification.acceptanceRunners` in the repository. Tasks bind
25
+ stable check IDs and explicit committed test/fixture/helper materials; they cannot
26
+ override runner executables or arguments. Each version installs and builds its own
27
+ declared dependencies. Fixture errors, skipped/retried checks, incomplete output and
28
+ timeouts produce UNKNOWN. Review examines the checks themselves and every fallback.
29
+ `task revise`, `task replan` and `task pin-finding` preserve goal and evidence history;
30
+ `task status` and `doctor` distinguish mechanical coverage from review-only coverage.
31
+
32
+ On macOS, a trusted clean main checkout can start an isolated native goal run:
33
+
34
+ ```sh
35
+ agent-ops run "Implement the requested behavior" --host codex --time-budget 60m --jobs 2
36
+ agent-ops run status <run-id>
37
+ agent-ops run logs <run-id>
38
+ agent-ops run respond <run-id> --question-id <id> --answer "Clarification"
39
+ agent-ops run stop <run-id>
40
+ agent-ops run resume <run-id>
41
+ ```
42
+
43
+ `--host claude` uses Claude Code; `--wait` waits for a terminal or input state.
44
+ The coordinator counts toward the two writer slots. Native processes write only
45
+ under current leases in separate worktrees. Active time counts once across parallel
46
+ workers and continues through proof and integration; resume retains earlier usage
47
+ epochs. Missing token or cost counters remain unknown. Completion requires current
48
+ verification, two fresh reviews, completed tasks and a receipt bound to the target.
49
+ Setup runs in the background under registered process groups. Stop cancels native
50
+ writers, local verification and setup; interrupted setup requires explicit resume.
51
+ The coordinator can assess additional non-dangerous fixed commands through a
52
+ policy request. This authorization is bound to the run, runtime, lease and budget;
53
+ it creates no permanent repository trust. Policy transitions fence writers and
54
+ journal delivery imports and contract synchronization before starting new leases.
55
+ Native authorization decisions retain redacted command scope and rationale;
56
+ missing provider decisions remain unknown. Repair compares actual check outcomes
57
+ and outstanding finding pins rather than source fingerprints.
58
+
59
+ Office is a Preview feature, disabled by default. Enable it in the init/update
60
+ selector (update keeps your current choice), or use explicit flags:
61
+
62
+ ```sh
63
+ agent-ops update --office on
64
+ agent-ops office
65
+ # Disable later: agent-ops update --office off
66
+ ```
67
+
68
+ It prints a tokened `http://127.0.0.1:<port>/` URL (also shown by `run start` and
69
+ `run status`) to a read-only pixel office with cream walls, oak floors
70
+ and sage furniture. The overview fits the viewport without scrolling: each run
71
+ team shares a room, and each ordinary session has its own room, including sessions
72
+ without a task or worktree. The furnished floorplan fills the page below the
73
+ header, with a shared hallway between rooms. You control the Supervisor using
74
+ arrow keys or WASD and can walk into each room; clicking a room also opens its
75
+ larger detail view, with a return control. Characters move between planning, development,
76
+ verification, review and integration areas. Short nameplates show their current
77
+ work; room whiteboards show criteria progress, verification and review results,
78
+ and questions waiting for an answer. Clicking a character shows task details,
79
+ changed files and copyable commands. Chinese and English can be switched in the
80
+ header; the viewer badge reads “You” in English and “你” in Chinese. The page remembers the choice and respects reduced-motion preferences.
81
+ Supervisor movement and character animation run locally without model requests.
82
+ Completed rooms move to Recently completed and remain for at most two hours.
83
+ The page reads agent-ops state and `git diff` paths only, never transcripts or file
84
+ contents. When enabled, managed ordinary sessions start the shared repository server
85
+ automatically; a newly started Office opens the browser once, and subsequent
86
+ sessions reuse it. The server exits after ten minutes without active runs or
87
+ ordinary sessions. Ordinary-session presence follows hook activity; a crashed
88
+ or silent session expires after 30 minutes without an event. Office failures
89
+ remain advisory and never block session work.
90
+
91
+ Native lifecycle reachability and framework probe conditions are recorded in
92
+ [the capability probe](docs/harness/native-goal-probe.md); these observations do not
93
+ establish external adoption or a reduction in review cost.
94
+
15
95
  ## Quick start from npm
16
96
 
17
97
  Requires Node.js `>=22.14.0`. Install the published CLI globally:
@@ -35,7 +115,7 @@ installation plan.
35
115
 
36
116
  The interactive multi-select screens start with no harness or profile selected.
37
117
  Choose at least one of `agy`, `codex`, `claude`, and `opencode`, and at least one of
38
- the `core`, `advisory`, `guardrails`, and `loop` profiles before confirming. For
118
+ the `core`, `advisory`, `guardrails`, `loop`, and `run` profiles before confirming. For
39
119
  scripted use, `--harness all` selects all four harnesses; comma-separated
40
120
  selections such as `codex,opencode` are also supported. The legacy `both` value
41
121
  remains an alias for `codex,claude`.
@@ -152,6 +232,28 @@ agent-ops update \
152
232
  --dry-run --json
153
233
  ```
154
234
 
235
+ ### Automatic runs (`run` profile)
236
+
237
+ `run` is an opt-in profile that also selects `core` and `loop`. With it, the
238
+ Claude Code and Codex rules hand a change that needs more than five acceptance
239
+ criteria to `agent-ops run` instead of splitting it into subtasks; five or fewer
240
+ stay in the session. The agent writes the prompt verbatim plus its proposed
241
+ criteria to the gitignored `.agent-ops/state/run-goal.md` from the main checkout
242
+ (the worktree guard lets exactly that write through), then starts
243
+ `agent-ops run --goal-file .agent-ops/state/run-goal.md --host claude --wait`
244
+ (or `--host codex`, escalated like review) in the background and relays any
245
+ question through `agent-ops run respond`. A start refused with
246
+ `RUN_TARGET_REQUIRED`, `RUN_BACKGROUND_UNSUPPORTED`, `RUN_TARGET_DIRTY`, or
247
+ `WORKTREE_NESTED` falls back to the subtask flow; `RUN_REPO_UNTRUSTED` stops and
248
+ asks you. A run that ends without completing is reported with its status, last
249
+ log events, and the `agent-ops run resume <id>` / `agent-ops run stop <id>`
250
+ commands; it is never resumed automatically. agy keeps the subtask flow.
251
+ Init and update pre-authorize `agent-ops run` for Claude Code and Codex.
252
+
253
+ ```bash
254
+ agent-ops init --scope project --harness codex,claude --profile run --yes
255
+ ```
256
+
155
257
  ## Quick start from a source checkout
156
258
 
157
259
  For development or to run the repository version directly, use a source
@@ -11,11 +11,13 @@ export const COMMAND_NAMES = [
11
11
  "review",
12
12
  "batch",
13
13
  "allow-stop",
14
- "worktree"
14
+ "worktree",
15
+ "run",
16
+ "office"
15
17
  ];
16
18
  const COMMAND_SET = new Set(COMMAND_NAMES);
17
19
  const SCOPES = new Set(["project", "user"]);
18
- const PROFILES = new Set(["advisory", "core", "guardrails", "loop"]);
20
+ const PROFILES = new Set(["advisory", "core", "guardrails", "loop", "run"]);
19
21
  // opencode is absent: it has no read-only flag, so it cannot review.
20
22
  const REVIEW_TARGETS = new Set(["agy", "claude", "codex"]);
21
23
  export const WORKTREE_ACTIONS = ["add", "commit", "finish", "list", "resume", "remove"];
@@ -61,6 +63,8 @@ function parseHookTarget(option, value) {
61
63
  return { harness: harness, surfaceId };
62
64
  }
63
65
  export function parseArgs(argv) {
66
+ if (argv[0] === "run")
67
+ return parseRunArgs(argv.slice(1));
64
68
  let command;
65
69
  let action;
66
70
  let scope;
@@ -78,10 +82,19 @@ export function parseArgs(argv) {
78
82
  const profiles = [];
79
83
  const reviewTargets = [];
80
84
  const criteria = [];
85
+ const criterionFiles = [];
86
+ let criteriaFile;
87
+ let planFile;
88
+ let expectedContract;
89
+ let expectedTreeContract;
90
+ let reason;
91
+ let baseline;
92
+ let findingReference;
81
93
  const evidence = [];
82
94
  let checkAuth = false;
83
95
  const checkAuthTargets = [];
84
96
  let completionGate;
97
+ let office;
85
98
  let dryRun = false;
86
99
  let json = false;
87
100
  let yes = false;
@@ -202,6 +215,39 @@ export function parseArgs(argv) {
202
215
  tree = true;
203
216
  break;
204
217
  }
218
+ case "--criterion-file":
219
+ criterionFiles.push(readOptionValue(argv, index++, token));
220
+ break;
221
+ case "--criteria-file":
222
+ if (criteriaFile !== undefined)
223
+ duplicate(token);
224
+ criteriaFile = readOptionValue(argv, index++, token);
225
+ break;
226
+ case "--plan-file":
227
+ if (planFile !== undefined)
228
+ duplicate(token);
229
+ planFile = readOptionValue(argv, index++, token);
230
+ break;
231
+ case "--expected-contract":
232
+ if (expectedContract !== undefined)
233
+ duplicate(token);
234
+ expectedContract = readOptionValue(argv, index++, token);
235
+ break;
236
+ case "--expected-tree-contract":
237
+ if (expectedTreeContract !== undefined)
238
+ duplicate(token);
239
+ expectedTreeContract = readOptionValue(argv, index++, token);
240
+ break;
241
+ case "--reason":
242
+ if (reason !== undefined)
243
+ duplicate(token);
244
+ reason = readOptionValue(argv, index++, token);
245
+ break;
246
+ case "--baseline":
247
+ if (baseline !== undefined)
248
+ duplicate(token);
249
+ baseline = readOptionValue(argv, index++, token);
250
+ break;
205
251
  case "--criterion": {
206
252
  criteria.push(readOptionValue(argv, index, token));
207
253
  index += 1;
@@ -272,6 +318,16 @@ export function parseArgs(argv) {
272
318
  }
273
319
  completionGate = true;
274
320
  break;
321
+ case "--office": {
322
+ if (office !== undefined)
323
+ duplicate(token);
324
+ const value = readOptionValue(argv, index, token);
325
+ if (value !== "on" && value !== "off")
326
+ invalidValue(token, value);
327
+ office = value;
328
+ index += 1;
329
+ break;
330
+ }
275
331
  case "--worktree": {
276
332
  if (worktree !== undefined) {
277
333
  duplicate(token);
@@ -386,7 +442,7 @@ export function parseArgs(argv) {
386
442
  "complete",
387
443
  "create",
388
444
  "export",
389
- "status"
445
+ "status", "revise", "replan", "pin-finding"
390
446
  ].includes(token)) {
391
447
  action = token;
392
448
  break;
@@ -409,6 +465,14 @@ export function parseArgs(argv) {
409
465
  worktreeName = token;
410
466
  break;
411
467
  }
468
+ if (command === "task" && ["revise", "replan", "pin-finding"].includes(action ?? "") && taskId === undefined) {
469
+ taskId = token;
470
+ break;
471
+ }
472
+ if (command === "task" && action === "pin-finding" && findingReference === undefined) {
473
+ findingReference = token;
474
+ break;
475
+ }
412
476
  if (command !== undefined) {
413
477
  throw new CliArgumentError("CLI_UNEXPECTED_ARGUMENT", `Unexpected argument: ${token}`);
414
478
  }
@@ -458,6 +522,7 @@ export function parseArgs(argv) {
458
522
  evidence.length > 0 ||
459
523
  reviewTargets.length > 0 ||
460
524
  completionGate !== undefined ||
525
+ office !== undefined ||
461
526
  sessionId !== undefined ||
462
527
  base !== undefined ||
463
528
  parentBase !== undefined ||
@@ -548,9 +613,26 @@ export function parseArgs(argv) {
548
613
  if (completionGate !== undefined && command !== "init") {
549
614
  throw new CliArgumentError("CLI_OPTION_NOT_ALLOWED", "--completion-gate may be used only with init.");
550
615
  }
616
+ if (office !== undefined && command !== "init" && command !== "update") {
617
+ throw new CliArgumentError("CLI_OPTION_NOT_ALLOWED", "--office may be used only with init or update.");
618
+ }
551
619
  if (worktree !== undefined && command !== "init" && command !== "update") {
552
620
  throw new CliArgumentError("CLI_OPTION_NOT_ALLOWED", "--worktree may be used only with init or update.");
553
621
  }
622
+ const revisionAction = command === "task" && ["revise", "replan", "pin-finding"].includes(action ?? "");
623
+ if ((criterionFiles.length > 0 && !(command === "task" && (action === "create" || action === "pin-finding"))) ||
624
+ (criteriaFile !== undefined && !(command === "task" && action === "revise")) ||
625
+ (planFile !== undefined && !(command === "task" && action === "replan")) ||
626
+ (expectedContract !== undefined && !(command === "task" && (action === "revise" || action === "pin-finding"))) ||
627
+ (expectedTreeContract !== undefined && !(command === "task" && action === "replan")) ||
628
+ (reason !== undefined && !revisionAction) ||
629
+ (baseline !== undefined && !(command === "task" && (action === "create" || action === "revise"))))
630
+ throw new CliArgumentError("CLI_OPTION_NOT_ALLOWED", "Acceptance options are not supported by this command.");
631
+ if (revisionAction && (taskId === undefined || evidence.length > 0 || title !== undefined || intent !== undefined || parentTaskId !== undefined || sessionId !== undefined || criteria.length > 0 || base !== undefined ||
632
+ (action === "revise" && (expectedContract === undefined || criteriaFile === undefined || reason === undefined)) ||
633
+ (action === "replan" && (expectedTreeContract === undefined || planFile === undefined || reason === undefined)) ||
634
+ (action === "pin-finding" && (expectedContract === undefined || findingReference === undefined || criterionFiles.length !== 1))))
635
+ throw new CliArgumentError("CLI_MISSING_VALUE", "Acceptance revision requires task, expected contract, payload and reason.");
554
636
  if (command === "task") {
555
637
  if (harness !== undefined ||
556
638
  profiles.length > 0 ||
@@ -710,7 +792,16 @@ export function parseArgs(argv) {
710
792
  ...(intent === undefined ? {} : { intent }),
711
793
  ...(reviewTargets.length === 0 ? {} : { reviewTargets }),
712
794
  ...(completionGate === undefined ? {} : { completionGate }),
795
+ ...(office === undefined ? {} : { office }),
713
796
  ...(criteria.length === 0 ? {} : { criteria }),
797
+ ...(criterionFiles.length === 0 ? {} : { criterionFiles }),
798
+ ...(criteriaFile === undefined ? {} : { criteriaFile }),
799
+ ...(planFile === undefined ? {} : { planFile }),
800
+ ...(expectedContract === undefined ? {} : { expectedContract }),
801
+ ...(expectedTreeContract === undefined ? {} : { expectedTreeContract }),
802
+ ...(reason === undefined ? {} : { reason }),
803
+ ...(baseline === undefined ? {} : { baseline }),
804
+ ...(findingReference === undefined ? {} : { findingReference }),
714
805
  ...(evidence.length === 0 ? {} : { evidence }),
715
806
  ...(sessionId === undefined ? {} : { sessionId }),
716
807
  ...(base === undefined ? {} : { base }),
@@ -730,3 +821,92 @@ export function parseArgs(argv) {
730
821
  ...(worktree === undefined ? {} : { worktree })
731
822
  };
732
823
  }
824
+ function parseRunArgs(argv) {
825
+ const actions = ["start", "status", "logs", "resume", "stop", "respond"];
826
+ if (argv.includes("--help") || argv.includes("-h"))
827
+ return { command: "help", helpTopic: "run", profiles: [], dryRun: false, json: false, yes: false, rerun: false };
828
+ const action = actions.includes(argv[0]) ? argv[0] : "start";
829
+ const args = { command: "run", action, runAction: action, profiles: [], dryRun: false, json: false, yes: false, rerun: false };
830
+ const seen = new Set();
831
+ let positional;
832
+ for (let i = actions.includes(argv[0]) ? 1 : 0; i < argv.length; i++) {
833
+ const token = argv[i];
834
+ if (!token.startsWith("-")) {
835
+ if (positional !== undefined)
836
+ throw new CliArgumentError("CLI_UNEXPECTED_ARGUMENT", "Quote the goal as one argument; control actions take one run ID.");
837
+ positional = token;
838
+ continue;
839
+ }
840
+ if (seen.has(token))
841
+ duplicate(token);
842
+ seen.add(token);
843
+ if (token === "--json") {
844
+ args.json = true;
845
+ continue;
846
+ }
847
+ if (token === "--wait") {
848
+ args.wait = true;
849
+ continue;
850
+ }
851
+ if (!["--host", "--goal-file", "--time-budget", "--jobs", "--question-id", "--answer", "--answer-file"].includes(token))
852
+ throw new CliArgumentError("CLI_UNKNOWN_OPTION", `Unknown run option: ${token}`, token);
853
+ const value = readOptionValue(argv, i++, token);
854
+ switch (token) {
855
+ case "--host":
856
+ if (value !== "claude" && value !== "codex")
857
+ invalidValue(token, value);
858
+ args.host = value;
859
+ break;
860
+ case "--goal-file":
861
+ args.goalFile = value;
862
+ break;
863
+ case "--question-id":
864
+ args.questionId = value;
865
+ break;
866
+ case "--answer":
867
+ args.answer = value;
868
+ break;
869
+ case "--answer-file":
870
+ args.answerFile = value;
871
+ break;
872
+ case "--jobs":
873
+ if (value !== "1" && value !== "2")
874
+ invalidValue(token, value);
875
+ args.jobs = Number(value);
876
+ break;
877
+ case "--time-budget": {
878
+ const match = value.match(/^([1-9][0-9]*)(s|m|h)$/u);
879
+ if (match === null)
880
+ invalidValue(token, value);
881
+ const ms = Number(match[1]) * ({ s: 1000, m: 60000, h: 3600000 }[match[2]]);
882
+ if (!Number.isSafeInteger(ms) || ms > 86400000)
883
+ invalidValue(token, value);
884
+ args.timeBudgetMs = ms;
885
+ break;
886
+ }
887
+ }
888
+ }
889
+ if (action === "start") {
890
+ args.goal = positional;
891
+ if ((args.goal === undefined) === (args.goalFile === undefined))
892
+ throw new CliArgumentError("RUN_GOAL_REQUIRED", "Provide exactly one quoted goal or --goal-file.");
893
+ if (args.host === undefined)
894
+ throw new CliArgumentError("RUN_HOST_REQUIRED", "Choose --host claude or --host codex.");
895
+ if (args.questionId !== undefined || args.answer !== undefined || args.answerFile !== undefined)
896
+ throw new CliArgumentError("CLI_INVALID_OPTION", "Answer options require run respond.");
897
+ }
898
+ else {
899
+ if (positional === undefined || !/^[a-z][a-z0-9-]{7,63}$/u.test(positional))
900
+ throw new CliArgumentError("RUN_ID_REQUIRED", "Control actions require a valid run ID.");
901
+ args.runId = positional;
902
+ if (args.host !== undefined || args.goalFile !== undefined || args.jobs !== undefined || args.timeBudgetMs !== undefined || args.wait)
903
+ throw new CliArgumentError("CLI_INVALID_OPTION", "Start options are not accepted by run control actions.");
904
+ if (action === "respond") {
905
+ if (args.questionId === undefined || (args.answer === undefined) === (args.answerFile === undefined))
906
+ throw new CliArgumentError("RUN_ANSWER_REQUIRED", "Respond requires --question-id and exactly one --answer or --answer-file.");
907
+ }
908
+ else if (args.questionId !== undefined || args.answer !== undefined || args.answerFile !== undefined)
909
+ throw new CliArgumentError("CLI_INVALID_OPTION", "Answer options require run respond.");
910
+ }
911
+ return args;
912
+ }
@@ -1,4 +1,11 @@
1
1
  #!/usr/bin/env node
2
+ import { productionRunContext, runPhaseObserver } from "./run-deps.js";
3
+ import { AgentOpsError } from "../../../runtime/src/fs/paths.js";
4
+ import { recordRunVerification } from "../../../runtime/src/run/verification.js";
5
+ import { runOwnedLocalProof } from "./owned-run-step.js";
6
+ import { runRunCommand } from "./commands/run.js";
7
+ import { runOfficeCommand } from "./commands/office.js";
8
+ import { ensureBackgroundOffice, observeOfficeSession, openOfficeBrowser, serveOffice } from "./office-entry.js";
2
9
  import { readFile, readdir, stat } from "node:fs/promises";
3
10
  import { randomUUID } from "node:crypto";
4
11
  import { homedir } from "node:os";
@@ -17,10 +24,10 @@ import { calculateConfigHash } from "../../../runtime/src/config/hash.js";
17
24
  import { FileEvidenceStore } from "../../../runtime/src/verify/evidence.js";
18
25
  import { calculateSourceFingerprint } from "../../../runtime/src/verify/source-fingerprint.js";
19
26
  import { VerificationService } from "../../../runtime/src/verify/service.js";
20
- import { NodeVerificationProcessRunner } from "../../../runtime/src/verify/spawn.js";
27
+ import { registeredRunProofRunner } from "../../../runtime/src/verify/spawn.js";
21
28
  import { COMMAND_NAMES, parseArgs } from "./args.js";
22
29
  import { runCli } from "./cli.js";
23
- import { loadEffectiveConfig, repositoryTrust, repositoryTrustBinding } from "./context.js";
30
+ import { loadEffectiveConfig, runPolicyContext, repositoryTrust, repositoryTrustBinding } from "./context.js";
24
31
  import { runHookProcess } from "./hook-process.js";
25
32
  import { gitRunner, trustStore, worktreeDependencies } from "./parallel-deps.js";
26
33
  import { selectYesNo, writeBanner } from "./ui.js";
@@ -165,7 +172,22 @@ function runAgy(args, options = {}) {
165
172
  ...(process.platform === "win32" ? { shell: true } : {})
166
173
  });
167
174
  }
168
- if (argv[0] === "agy-run") {
175
+ let ownedProofExit = null;
176
+ try {
177
+ ownedProofExit = await runOwnedLocalProof(argv);
178
+ }
179
+ catch (cause) {
180
+ const code = cause instanceof AgentOpsError ? cause.code : "RUN_LOCAL_PROOF_FAILED";
181
+ const message = cause instanceof AgentOpsError ? cause.message : "Local proof could not be registered safely.";
182
+ if (argv.includes("--json"))
183
+ process.stdout.write(JSON.stringify(errorEnvelope(code, message)) + "\n");
184
+ else
185
+ process.stderr.write(message + "\n");
186
+ ownedProofExit = 2;
187
+ }
188
+ if (ownedProofExit !== null)
189
+ process.exitCode = ownedProofExit;
190
+ else if (argv[0] === "agy-run") {
169
191
  try {
170
192
  const root = process.cwd();
171
193
  const config = (await loadEffectiveConfig(root, "project")).config;
@@ -203,7 +225,7 @@ else if (argv[0] === "hook") {
203
225
  stdin: process.stdin,
204
226
  writeStdout: (value) => process.stdout.write(value),
205
227
  writeStderr: (value) => process.stderr.write(value)
206
- }, CLI_VERSION, { worktree: worktreeDependencies() });
228
+ }, CLI_VERSION, { worktree: worktreeDependencies(), office: observeOfficeSession });
207
229
  }
208
230
  else {
209
231
  process.exitCode = await runCli(argv, {
@@ -224,6 +246,17 @@ else {
224
246
  const isTTY = !args.json &&
225
247
  process.stdin.isTTY === true &&
226
248
  process.stdout.isTTY === true;
249
+ if ((["init", "update"].includes(args.command) || (args.command === "trust" && args.action !== "status")) &&
250
+ (process.env.AGENT_OPS_RUN_ID !== undefined || await runPolicyContext(process.cwd()) !== null))
251
+ throw new AgentOpsError("RUN_POLICY_COORDINATOR_REQUIRED", "Run workers must request scoped policy review from the coordinator; permanent trust and host rules cannot be changed by the run.");
252
+ if (args.command === "run") {
253
+ const context = await productionRunContext(root);
254
+ return await runRunCommand({ args, ...context, root: context.mainRoot, office: async () => await ensureBackgroundOffice(root, undefined, openOfficeBrowser) });
255
+ }
256
+ if (args.command === "office") {
257
+ return await runOfficeCommand({ ensure: async () => await ensureBackgroundOffice(root, undefined, openOfficeBrowser),
258
+ foreground: async (onUrl) => await serveOffice(root, onUrl), writeStdout: (text) => { process.stdout.write(text); } });
259
+ }
227
260
  if (args.command === "init") {
228
261
  const store = trustStore();
229
262
  return await runInitCommand({
@@ -267,6 +300,17 @@ else {
267
300
  root,
268
301
  toolkitVersion: CLI_VERSION,
269
302
  probes: {
303
+ acceptanceCoverage: async () => {
304
+ const service = new TaskService(new FileTaskStore(join(root, ".agent-ops/tasks/state.json"), root), { completion: { root, gitRunner: gitRunner(root), loadConfig: async () => config } });
305
+ const records = (await service.list()).filter(r => r.status !== "archived");
306
+ const rows = await Promise.all(records.map(r => service.coverage(r.task.id)));
307
+ const criteria = rows.flatMap(row => row.rows);
308
+ const mechanical = criteria.filter(c => c.mode === "behavioral" || c.mode === "invariant");
309
+ return { status: "PASS", message: criteria.length + " criteria; " +
310
+ criteria.filter(c => c.mode === "legacy").length + " legacy; " + mechanical.filter(c => c.status === "proven").length + "/" + mechanical.length +
311
+ " mechanical proven; " + criteria.filter(c => c.mode === "review-only").length + " review-only; originally mechanical " + rows.reduce((n, r) => n + r.originalMechanical, 0) +
312
+ "; undischarged " + rows.reduce((n, r) => n + r.rows.filter(c => c.status !== "proven" && !r.reviewed).length, 0) };
313
+ },
270
314
  ...(doctorManifest?.harness.includes("agy") === true
271
315
  ? {
272
316
  agyRuntime: () => {
@@ -368,6 +412,7 @@ else {
368
412
  trustStore: store,
369
413
  calculateTrustBinding: async (config) => await plannedTrustBinding(root, config),
370
414
  confirm: async (plan, trust) => await confirmPlan(formatUpdatePlan(plan, trust)),
415
+ promptOffice: async (message, enabled) => await selectYesNo(message, { input: process.stdin, output: process.stdout }, enabled),
371
416
  promptWorktree: async (message) => await selectYesNo(message, { input: process.stdin, output: process.stdout }, true),
372
417
  ...(updateArgs.targetVersion === undefined
373
418
  ? {}
@@ -409,7 +454,9 @@ else {
409
454
  cwd: root,
410
455
  sessionId: sessionId ?? await resolveCommandSessionId(root),
411
456
  parentTaskId: args.taskId,
412
- deps: worktreeDependencies()
457
+ deps: worktreeDependencies(),
458
+ ...(process.env.AGENT_OPS_RUN_ID === undefined || process.env.AGENT_OPS_WORKER_ID === undefined ? {} :
459
+ { onPhase: await runPhaseObserver(root, process.env.AGENT_OPS_RUN_ID, process.env.AGENT_OPS_WORKER_ID) })
413
460
  });
414
461
  }
415
462
  const createConfig = args.action === "create"
@@ -441,7 +488,8 @@ else {
441
488
  return {
442
489
  path: record.path,
443
490
  base: record.base,
444
- service: new TaskService(new FileTaskStore(join(record.path, ".agent-ops", "tasks", "state.json"), record.path))
491
+ service: new TaskService(new FileTaskStore(join(record.path, ".agent-ops", "tasks", "state.json"), record.path), { completion: { root: record.path, gitRunner: gitRunner(record.path),
492
+ loadConfig: async () => (await loadEffectiveConfig(record.path, "project")).config } })
445
493
  };
446
494
  }
447
495
  }
@@ -478,6 +526,7 @@ else {
478
526
  : async (request) => await withReviewSlot({
479
527
  dir: slotDir,
480
528
  signal,
529
+ holder: { root, ...(reviewArgs.taskId === undefined ? {} : { taskId: reviewArgs.taskId }) },
481
530
  onWait: (line) => {
482
531
  process.stderr.write(`${progressPrefix}${line}\n`);
483
532
  }
@@ -501,6 +550,7 @@ else {
501
550
  config: reviewConfig,
502
551
  evidenceStore: new FileEvidenceStore(root, root),
503
552
  execute: inSlot(createReviewExecutor({
553
+ runner: registeredRunProofRunner(),
504
554
  targets: configuredReviewTargets,
505
555
  cwd: root,
506
556
  ...(reviewRole?.model === undefined
@@ -513,10 +563,11 @@ else {
513
563
  ? {}
514
564
  : { timeoutMs: reviewRole.timeoutMs }),
515
565
  preflightTarget,
516
- verifySourceFingerprint: async (expected) => {
566
+ verifySourceFingerprint: async (expected, scope) => {
517
567
  const currentScope = await resolveReviewScope({
518
568
  root,
519
569
  runner: reviewGit,
570
+ ...(scope?.mode === "base" && scope.noChange === true ? { noChangePaths: scope.changedFiles } : {}),
520
571
  ...(reviewArgs.base === undefined ? {} : { base: reviewArgs.base })
521
572
  });
522
573
  return await calculateSourceFingerprint(root, currentScope, reviewGit) === expected;
@@ -589,7 +640,7 @@ else {
589
640
  scope: batchScope,
590
641
  config: batchConfig,
591
642
  gitRunner: batchGit,
592
- processRunner: new NodeVerificationProcessRunner(),
643
+ processRunner: registeredRunProofRunner(),
593
644
  taskService,
594
645
  evidenceStore: new FileEvidenceStore(root, root),
595
646
  trusted,
@@ -633,17 +684,23 @@ else {
633
684
  return await runVerifyCommand({
634
685
  args,
635
686
  taskService,
636
- service: new VerificationService({
637
- root,
638
- scope: args.scope === "user" ? "user" : "project",
639
- config,
640
- gitRunner: gitRunner(root),
641
- processRunner: new NodeVerificationProcessRunner(),
642
- taskService,
643
- evidenceStore: new FileEvidenceStore(root, root),
644
- trusted: trustStatus === "TRUSTED",
645
- ...(args.base === undefined ? {} : { base: args.base })
646
- })
687
+ service: { verify: async (taskId) => {
688
+ const run = await runPolicyContext(root);
689
+ const report = await new VerificationService({
690
+ root,
691
+ scope: args.scope === "user" ? "user" : "project",
692
+ config,
693
+ gitRunner: gitRunner(root),
694
+ processRunner: registeredRunProofRunner(),
695
+ taskService,
696
+ evidenceStore: new FileEvidenceStore(root, root),
697
+ trusted: trustStatus === "TRUSTED",
698
+ ...(args.base === undefined ? {} : { base: args.base })
699
+ }).verify(taskId);
700
+ if (run !== null)
701
+ await recordRunVerification(run.repository, run.state, run.worker?.workerId ?? run.state.coordinatorId, await taskService.status({ taskId }), report);
702
+ return report;
703
+ } }
647
704
  });
648
705
  }
649
706
  if (args.command === "trust") {