@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.
- package/README.md +103 -1
- package/dist/packages/cli/src/args.js +183 -3
- package/dist/packages/cli/src/bin.js +76 -19
- package/dist/packages/cli/src/cli.js +53 -8
- package/dist/packages/cli/src/codex-loop-process.js +19 -2
- package/dist/packages/cli/src/commands/advance.js +42 -5
- package/dist/packages/cli/src/commands/init.js +2 -0
- package/dist/packages/cli/src/commands/office.js +20 -0
- package/dist/packages/cli/src/commands/review-show.js +1 -1
- package/dist/packages/cli/src/commands/review.js +100 -9
- package/dist/packages/cli/src/commands/run.js +115 -0
- package/dist/packages/cli/src/commands/task.js +61 -17
- package/dist/packages/cli/src/commands/update.js +8 -0
- package/dist/packages/cli/src/commands/verify.js +1 -0
- package/dist/packages/cli/src/context.js +64 -1
- package/dist/packages/cli/src/hook-entry.js +2 -1
- package/dist/packages/cli/src/hook-process.js +54 -3
- package/dist/packages/cli/src/loop-entry.js +2 -1
- package/dist/packages/cli/src/office-entry.js +230 -0
- package/dist/packages/cli/src/owned-run-step.js +65 -0
- package/dist/packages/cli/src/parallel-deps.js +18 -5
- package/dist/packages/cli/src/run-deps.js +321 -0
- package/dist/packages/cli/src/run-entry.js +952 -0
- package/dist/packages/cli/src/run-setup-entry.js +32 -0
- package/dist/packages/cli/src/run-step-entry.js +58 -0
- package/dist/packages/cli/src/wizard.js +14 -2
- package/dist/runtime/src/adapters/agy/config.js +4 -3
- package/dist/runtime/src/adapters/agy/events.js +9 -0
- package/dist/runtime/src/adapters/claude/config.js +3 -3
- package/dist/runtime/src/adapters/claude/events.js +8 -0
- package/dist/runtime/src/adapters/codex/config.js +3 -3
- package/dist/runtime/src/adapters/codex/events.js +8 -0
- package/dist/runtime/src/adapters/opencode/config.js +5 -4
- package/dist/runtime/src/adapters/opencode/events.js +8 -0
- package/dist/runtime/src/config/explain.js +11 -0
- package/dist/runtime/src/config/hash.js +1 -1
- package/dist/runtime/src/config/merge.js +9 -1
- package/dist/runtime/src/contracts.js +2 -2
- package/dist/runtime/src/hooks/codex-loop.js +4 -0
- package/dist/runtime/src/hooks/completion-gate.js +8 -2
- package/dist/runtime/src/install/doctor.js +2 -0
- package/dist/runtime/src/install/harness.js +44 -1
- package/dist/runtime/src/install/plan.js +16 -11
- package/dist/runtime/src/install/preauth.js +44 -18
- package/dist/runtime/src/install/profiles.js +8 -2
- package/dist/runtime/src/install/update.js +1 -0
- package/dist/runtime/src/office/collect.js +233 -0
- package/dist/runtime/src/office/page.js +873 -0
- package/dist/runtime/src/office/scene.js +168 -0
- package/dist/runtime/src/office/server.js +139 -0
- package/dist/runtime/src/office/sessions.js +115 -0
- package/dist/runtime/src/office/snapshot.js +107 -0
- package/dist/runtime/src/parallel/finish.js +85 -22
- package/dist/runtime/src/parallel/guard.js +12 -2
- package/dist/runtime/src/parallel/integrate.js +288 -10
- package/dist/runtime/src/parallel/manage.js +4 -1
- package/dist/runtime/src/parallel/receipt.js +71 -5
- package/dist/runtime/src/parallel/service.js +41 -1
- package/dist/runtime/src/review/attestation.js +36 -11
- package/dist/runtime/src/review/execute.js +24 -1
- package/dist/runtime/src/review/packet.js +35 -1
- package/dist/runtime/src/review/runner.js +12 -1
- package/dist/runtime/src/review/scope.js +18 -1
- package/dist/runtime/src/review/slots.js +35 -10
- package/dist/runtime/src/run/authorization.js +60 -0
- package/dist/runtime/src/run/budget.js +30 -0
- package/dist/runtime/src/run/controls.js +139 -0
- package/dist/runtime/src/run/convergence.js +177 -0
- package/dist/runtime/src/run/hosts/claude.js +180 -0
- package/dist/runtime/src/run/hosts/codex.js +279 -0
- package/dist/runtime/src/run/hosts/index.js +4 -0
- package/dist/runtime/src/run/hosts/protocol.js +254 -0
- package/dist/runtime/src/run/hosts/types.js +1 -0
- package/dist/runtime/src/run/hosts/util.js +136 -0
- package/dist/runtime/src/run/integration.js +204 -0
- package/dist/runtime/src/run/macOS.js +189 -0
- package/dist/runtime/src/run/ownership.js +22 -0
- package/dist/runtime/src/run/phase.js +37 -0
- package/dist/runtime/src/run/planning.js +25 -0
- package/dist/runtime/src/run/policy-transition.js +1076 -0
- package/dist/runtime/src/run/policy.js +176 -0
- package/dist/runtime/src/run/ratchet.js +71 -0
- package/dist/runtime/src/run/scheduler.js +260 -0
- package/dist/runtime/src/run/service.js +433 -0
- package/dist/runtime/src/run/supervisor.js +635 -0
- package/dist/runtime/src/run/transport.js +159 -0
- package/dist/runtime/src/run/types.js +1 -0
- package/dist/runtime/src/run/usage.js +81 -0
- package/dist/runtime/src/run/verification.js +64 -0
- package/dist/runtime/src/run/worker-gate.js +48 -0
- package/dist/runtime/src/schema/acceptance.js +91 -0
- package/dist/runtime/src/schema/validate.js +80 -9
- package/dist/runtime/src/task/completion.js +14 -2
- package/dist/runtime/src/task/contract.js +17 -0
- package/dist/runtime/src/task/pin-finding.js +81 -0
- package/dist/runtime/src/task/service.js +316 -13
- package/dist/runtime/src/task/store.js +57 -7
- package/dist/runtime/src/verify/acceptance-coverage.js +85 -0
- package/dist/runtime/src/verify/acceptance-protocol.js +252 -0
- package/dist/runtime/src/verify/acceptance-replay.js +239 -0
- package/dist/runtime/src/verify/adapters/index.js +5 -0
- package/dist/runtime/src/verify/adapters/jest.js +142 -0
- package/dist/runtime/src/verify/adapters/node-reporter.js +20 -0
- package/dist/runtime/src/verify/adapters/node.js +254 -0
- package/dist/runtime/src/verify/adapters/pytest.js +188 -0
- package/dist/runtime/src/verify/adapters/rust.js +84 -0
- package/dist/runtime/src/verify/adapters/shared.js +82 -0
- package/dist/runtime/src/verify/adapters/vitest.js +218 -0
- package/dist/runtime/src/verify/evidence.js +5 -2
- package/dist/runtime/src/verify/service.js +24 -8
- package/dist/runtime/src/verify/source-fingerprint.js +1 -1
- package/dist/runtime/src/verify/spawn.js +13 -2
- package/docs/en/spec/acceptance-and-evidence.md +48 -0
- package/docs/en/spec/harness-adapters.md +113 -1
- package/docs/en/spec/loop-engineering.md +49 -0
- package/docs/zh-TW/spec/acceptance-and-evidence.md +21 -1
- package/docs/zh-TW/spec/harness-adapters.md +93 -1
- package/docs/zh-TW/spec/loop-engineering.md +22 -0
- package/package.json +1 -1
- package/schemas/config.schema.json +230 -19
- package/schemas/evidence.schema.json +129 -6
- package/schemas/task.schema.json +265 -6
- package/templates/acceptance/agent_ops_acceptance.rs +36 -0
- 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 `
|
|
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 {
|
|
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
|
-
|
|
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:
|
|
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:
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
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") {
|