@kylecheng3146/agent-ops 0.6.0 → 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 +55 -1
- package/dist/packages/cli/src/args.js +19 -2
- package/dist/packages/cli/src/bin.js +14 -4
- package/dist/packages/cli/src/cli.js +20 -2
- package/dist/packages/cli/src/codex-loop-process.js +19 -2
- package/dist/packages/cli/src/commands/advance.js +19 -2
- 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/run.js +6 -2
- package/dist/packages/cli/src/commands/update.js +8 -0
- package/dist/packages/cli/src/context.js +1 -0
- package/dist/packages/cli/src/hook-entry.js +2 -1
- package/dist/packages/cli/src/hook-process.js +23 -2
- 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/run-deps.js +9 -2
- package/dist/packages/cli/src/run-entry.js +9 -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 +5 -0
- package/dist/runtime/src/hooks/codex-loop.js +4 -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/guard.js +7 -1
- package/dist/runtime/src/review/slots.js +1 -1
- package/dist/runtime/src/run/phase.js +37 -0
- package/dist/runtime/src/run/service.js +8 -0
- package/dist/runtime/src/schema/validate.js +13 -2
- package/docs/en/spec/harness-adapters.md +113 -1
- package/docs/zh-TW/spec/harness-adapters.md +93 -1
- package/package.json +1 -1
- package/schemas/config.schema.json +10 -1
package/README.md
CHANGED
|
@@ -56,6 +56,38 @@ Native authorization decisions retain redacted command scope and rationale;
|
|
|
56
56
|
missing provider decisions remain unknown. Repair compares actual check outcomes
|
|
57
57
|
and outstanding finding pins rather than source fingerprints.
|
|
58
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
|
+
|
|
59
91
|
Native lifecycle reachability and framework probe conditions are recorded in
|
|
60
92
|
[the capability probe](docs/harness/native-goal-probe.md); these observations do not
|
|
61
93
|
establish external adoption or a reduction in review cost.
|
|
@@ -83,7 +115,7 @@ installation plan.
|
|
|
83
115
|
|
|
84
116
|
The interactive multi-select screens start with no harness or profile selected.
|
|
85
117
|
Choose at least one of `agy`, `codex`, `claude`, and `opencode`, and at least one of
|
|
86
|
-
the `core`, `advisory`, `guardrails`, and `
|
|
118
|
+
the `core`, `advisory`, `guardrails`, `loop`, and `run` profiles before confirming. For
|
|
87
119
|
scripted use, `--harness all` selects all four harnesses; comma-separated
|
|
88
120
|
selections such as `codex,opencode` are also supported. The legacy `both` value
|
|
89
121
|
remains an alias for `codex,claude`.
|
|
@@ -200,6 +232,28 @@ agent-ops update \
|
|
|
200
232
|
--dry-run --json
|
|
201
233
|
```
|
|
202
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
|
+
|
|
203
257
|
## Quick start from a source checkout
|
|
204
258
|
|
|
205
259
|
For development or to run the repository version directly, use a source
|
|
@@ -12,11 +12,12 @@ export const COMMAND_NAMES = [
|
|
|
12
12
|
"batch",
|
|
13
13
|
"allow-stop",
|
|
14
14
|
"worktree",
|
|
15
|
-
"run"
|
|
15
|
+
"run",
|
|
16
|
+
"office"
|
|
16
17
|
];
|
|
17
18
|
const COMMAND_SET = new Set(COMMAND_NAMES);
|
|
18
19
|
const SCOPES = new Set(["project", "user"]);
|
|
19
|
-
const PROFILES = new Set(["advisory", "core", "guardrails", "loop"]);
|
|
20
|
+
const PROFILES = new Set(["advisory", "core", "guardrails", "loop", "run"]);
|
|
20
21
|
// opencode is absent: it has no read-only flag, so it cannot review.
|
|
21
22
|
const REVIEW_TARGETS = new Set(["agy", "claude", "codex"]);
|
|
22
23
|
export const WORKTREE_ACTIONS = ["add", "commit", "finish", "list", "resume", "remove"];
|
|
@@ -93,6 +94,7 @@ export function parseArgs(argv) {
|
|
|
93
94
|
let checkAuth = false;
|
|
94
95
|
const checkAuthTargets = [];
|
|
95
96
|
let completionGate;
|
|
97
|
+
let office;
|
|
96
98
|
let dryRun = false;
|
|
97
99
|
let json = false;
|
|
98
100
|
let yes = false;
|
|
@@ -316,6 +318,16 @@ export function parseArgs(argv) {
|
|
|
316
318
|
}
|
|
317
319
|
completionGate = true;
|
|
318
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
|
+
}
|
|
319
331
|
case "--worktree": {
|
|
320
332
|
if (worktree !== undefined) {
|
|
321
333
|
duplicate(token);
|
|
@@ -510,6 +522,7 @@ export function parseArgs(argv) {
|
|
|
510
522
|
evidence.length > 0 ||
|
|
511
523
|
reviewTargets.length > 0 ||
|
|
512
524
|
completionGate !== undefined ||
|
|
525
|
+
office !== undefined ||
|
|
513
526
|
sessionId !== undefined ||
|
|
514
527
|
base !== undefined ||
|
|
515
528
|
parentBase !== undefined ||
|
|
@@ -600,6 +613,9 @@ export function parseArgs(argv) {
|
|
|
600
613
|
if (completionGate !== undefined && command !== "init") {
|
|
601
614
|
throw new CliArgumentError("CLI_OPTION_NOT_ALLOWED", "--completion-gate may be used only with init.");
|
|
602
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
|
+
}
|
|
603
619
|
if (worktree !== undefined && command !== "init" && command !== "update") {
|
|
604
620
|
throw new CliArgumentError("CLI_OPTION_NOT_ALLOWED", "--worktree may be used only with init or update.");
|
|
605
621
|
}
|
|
@@ -776,6 +792,7 @@ export function parseArgs(argv) {
|
|
|
776
792
|
...(intent === undefined ? {} : { intent }),
|
|
777
793
|
...(reviewTargets.length === 0 ? {} : { reviewTargets }),
|
|
778
794
|
...(completionGate === undefined ? {} : { completionGate }),
|
|
795
|
+
...(office === undefined ? {} : { office }),
|
|
779
796
|
...(criteria.length === 0 ? {} : { criteria }),
|
|
780
797
|
...(criterionFiles.length === 0 ? {} : { criterionFiles }),
|
|
781
798
|
...(criteriaFile === undefined ? {} : { criteriaFile }),
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { productionRunContext } from "./run-deps.js";
|
|
2
|
+
import { productionRunContext, runPhaseObserver } 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
|
+
import { runOfficeCommand } from "./commands/office.js";
|
|
8
|
+
import { ensureBackgroundOffice, observeOfficeSession, openOfficeBrowser, serveOffice } from "./office-entry.js";
|
|
7
9
|
import { readFile, readdir, stat } from "node:fs/promises";
|
|
8
10
|
import { randomUUID } from "node:crypto";
|
|
9
11
|
import { homedir } from "node:os";
|
|
@@ -223,7 +225,7 @@ else if (argv[0] === "hook") {
|
|
|
223
225
|
stdin: process.stdin,
|
|
224
226
|
writeStdout: (value) => process.stdout.write(value),
|
|
225
227
|
writeStderr: (value) => process.stderr.write(value)
|
|
226
|
-
}, CLI_VERSION, { worktree: worktreeDependencies() });
|
|
228
|
+
}, CLI_VERSION, { worktree: worktreeDependencies(), office: observeOfficeSession });
|
|
227
229
|
}
|
|
228
230
|
else {
|
|
229
231
|
process.exitCode = await runCli(argv, {
|
|
@@ -249,7 +251,11 @@ else {
|
|
|
249
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.");
|
|
250
252
|
if (args.command === "run") {
|
|
251
253
|
const context = await productionRunContext(root);
|
|
252
|
-
return await runRunCommand({ args, ...context, root: context.mainRoot });
|
|
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); } });
|
|
253
259
|
}
|
|
254
260
|
if (args.command === "init") {
|
|
255
261
|
const store = trustStore();
|
|
@@ -406,6 +412,7 @@ else {
|
|
|
406
412
|
trustStore: store,
|
|
407
413
|
calculateTrustBinding: async (config) => await plannedTrustBinding(root, config),
|
|
408
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),
|
|
409
416
|
promptWorktree: async (message) => await selectYesNo(message, { input: process.stdin, output: process.stdout }, true),
|
|
410
417
|
...(updateArgs.targetVersion === undefined
|
|
411
418
|
? {}
|
|
@@ -447,7 +454,9 @@ else {
|
|
|
447
454
|
cwd: root,
|
|
448
455
|
sessionId: sessionId ?? await resolveCommandSessionId(root),
|
|
449
456
|
parentTaskId: args.taskId,
|
|
450
|
-
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) })
|
|
451
460
|
});
|
|
452
461
|
}
|
|
453
462
|
const createConfig = args.action === "create"
|
|
@@ -517,6 +526,7 @@ else {
|
|
|
517
526
|
: async (request) => await withReviewSlot({
|
|
518
527
|
dir: slotDir,
|
|
519
528
|
signal,
|
|
529
|
+
holder: { root, ...(reviewArgs.taskId === undefined ? {} : { taskId: reviewArgs.taskId }) },
|
|
520
530
|
onWait: (line) => {
|
|
521
531
|
process.stderr.write(`${progressPrefix}${line}\n`);
|
|
522
532
|
}
|
|
@@ -30,6 +30,7 @@ Commands:
|
|
|
30
30
|
verify Run configured verification
|
|
31
31
|
review Run an independent review or show recorded reports
|
|
32
32
|
run Start or control a supervised Claude/Codex native goal
|
|
33
|
+
office Open the local pixel office (Preview; opt-in)
|
|
33
34
|
batch Verify and review a parent task's subtasks together
|
|
34
35
|
allow-stop Grant one fingerprint-bound completion-gate Stop permit (requires --session)
|
|
35
36
|
agy-run Run headless agy with a process-exit completion recheck
|
|
@@ -40,9 +41,10 @@ Options:
|
|
|
40
41
|
--scope <project|user>
|
|
41
42
|
--harness <all|both|agy|claude|codex|opencode|comma-separated> Init/update/uninstall; review uses configured targets
|
|
42
43
|
--hook-target <harness=surface-id> Repeatable advanced init/update option
|
|
43
|
-
--profile <core|advisory|guardrails|loop> Repeatable
|
|
44
|
+
--profile <core|advisory|guardrails|loop|run> Repeatable
|
|
44
45
|
--review-target <codex|agy|claude> Repeatable init option; review pair order
|
|
45
46
|
--completion-gate Init only: enable the project-loop completion gate
|
|
47
|
+
--office <on|off> Init/update: enable or disable Office (Preview)
|
|
46
48
|
--worktree <auto|off> Init/update: configure session worktree isolation
|
|
47
49
|
--check-auth Doctor only: probe selected review targets'
|
|
48
50
|
authentication with one real call
|
|
@@ -72,6 +74,20 @@ Options:
|
|
|
72
74
|
* rejected.
|
|
73
75
|
*/
|
|
74
76
|
export const COMMAND_HELP_TEXT = {
|
|
77
|
+
office: `Usage: agent-ops office [--json]
|
|
78
|
+
|
|
79
|
+
Office (Preview) is opt-in: select it during init/update or run
|
|
80
|
+
agent-ops update --office on. Disable with --office off. A missing choice is off.
|
|
81
|
+
|
|
82
|
+
Print the URL of this repository's office: a read-only pixel page on
|
|
83
|
+
127.0.0.1. All run teams and ordinary sessions appear in a single-screen room
|
|
84
|
+
overview; walk the Supervisor with arrows/WASD or click a room for details.
|
|
85
|
+
Switch Chinese/English in the header. Movement and animation run locally.
|
|
86
|
+
When enabled, managed sessions start the shared server automatically. A new Office opens the
|
|
87
|
+
browser once; later sessions reuse it. It exits after 10 minutes without active
|
|
88
|
+
runs or sessions. Recently completed rooms remain for at most 2 hours.
|
|
89
|
+
The URL carries an access token; do not share it.
|
|
90
|
+
`,
|
|
75
91
|
run: `Usage: agent-ops run [start] "<goal>" --host <claude|codex> [--jobs <1|2>] [--time-budget <60m>] [--wait] [--json]
|
|
76
92
|
agent-ops run [start] --goal-file <path> --host <claude|codex>
|
|
77
93
|
agent-ops run <status|logs|resume|stop> <run-id> [--json]
|
|
@@ -88,9 +104,10 @@ Options:
|
|
|
88
104
|
--scope <project|user>
|
|
89
105
|
--harness <all|both|agy|claude|codex|opencode|comma-separated>
|
|
90
106
|
--hook-target <harness=surface-id> Repeatable
|
|
91
|
-
--profile <core|advisory|guardrails|loop> Repeatable
|
|
107
|
+
--profile <core|advisory|guardrails|loop|run> Repeatable
|
|
92
108
|
--review-target <codex|agy|claude> Repeatable, in fallback-chain order
|
|
93
109
|
--completion-gate Enable the project-loop completion gate
|
|
110
|
+
--office <on|off> Office (Preview); default off, update preserves choice
|
|
94
111
|
--worktree <auto|off> Configure session worktree isolation
|
|
95
112
|
--dry-run Print the plan without writing
|
|
96
113
|
--json
|
|
@@ -134,6 +151,7 @@ Options:
|
|
|
134
151
|
--scope <project|user>
|
|
135
152
|
--harness <all|both|agy|claude|codex|opencode|comma-separated>
|
|
136
153
|
--target-version <version> Offline-capable update target
|
|
154
|
+
--office <on|off> Office (Preview); preserve choice unless supplied
|
|
137
155
|
--worktree <auto|off> Configure session worktree isolation
|
|
138
156
|
--dry-run
|
|
139
157
|
--json
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { PROJECT_LOOP_EVENTS, runProjectLoop } from "../../../runtime/src/hooks/codex-loop.js";
|
|
1
|
+
import { PROJECT_LOOP_EVENTS, resolveProjectLoopRoot, runProjectLoop } from "../../../runtime/src/hooks/codex-loop.js";
|
|
2
|
+
import { officeEnabled, officeLoopPhaseHint } from "./office-entry.js";
|
|
2
3
|
const MAX_LOOP_INPUT_BYTES = 64 * 1024;
|
|
3
4
|
function isLoopHarness(value) {
|
|
4
5
|
return value === "claude" || value === "codex";
|
|
@@ -43,10 +44,26 @@ export async function runLoopProcess(argv, io, dependencies = {}) {
|
|
|
43
44
|
return 0;
|
|
44
45
|
}
|
|
45
46
|
try {
|
|
47
|
+
const input = parseInput(await readStdin(io.stdin));
|
|
48
|
+
const inputIsObject = typeof input === "object" && input !== null && !Array.isArray(input);
|
|
49
|
+
const managedRoot = inputIsObject
|
|
50
|
+
? await resolveProjectLoopRoot(input, dependencies.root ?? process.cwd(), harness).catch(() => null)
|
|
51
|
+
: null;
|
|
52
|
+
if (dependencies.office !== undefined && managedRoot !== null && await officeEnabled(managedRoot)) {
|
|
53
|
+
const phase = officeLoopPhaseHint(input, managedRoot);
|
|
54
|
+
void dependencies.office({
|
|
55
|
+
root: managedRoot,
|
|
56
|
+
harness,
|
|
57
|
+
event,
|
|
58
|
+
input,
|
|
59
|
+
validated: true,
|
|
60
|
+
...(phase === undefined ? {} : { phase })
|
|
61
|
+
}).catch(() => undefined);
|
|
62
|
+
}
|
|
46
63
|
const result = await runProjectLoop({
|
|
47
64
|
harness,
|
|
48
65
|
event,
|
|
49
|
-
input
|
|
66
|
+
input,
|
|
50
67
|
root: dependencies.root ?? process.cwd(),
|
|
51
68
|
...(dependencies.now === undefined ? {} : { now: dependencies.now }),
|
|
52
69
|
...(dependencies.gitStatus === undefined
|
|
@@ -9,6 +9,7 @@ import { renderReviewDetails, renderReviewHighlights } from "../../../../runtime
|
|
|
9
9
|
import { calculateSourceFingerprint } from "../../../../runtime/src/verify/source-fingerprint.js";
|
|
10
10
|
import { okEnvelope } from "../output.js";
|
|
11
11
|
import { readFinishedReview } from "./review-show.js";
|
|
12
|
+
import { criteriaProgress } from "../../../../runtime/src/run/phase.js";
|
|
12
13
|
import { prepareRunIntegration, readRunIntegrationProof, abandonPreparedRunIntegration } from "../../../../runtime/src/run/integration.js";
|
|
13
14
|
/** The child process runs the existing CLI in the candidate checkout. */
|
|
14
15
|
export async function runAdvanceStep(cwd, args) {
|
|
@@ -132,10 +133,26 @@ export async function runAdvanceCommand(options) {
|
|
|
132
133
|
if (noChangePaths !== undefined)
|
|
133
134
|
for (const task of ordered)
|
|
134
135
|
await deps.tasks(record.path).recordEvidence(task.task.id, {}, undefined, noChangePaths);
|
|
136
|
+
const onPhase = options.onPhase ?? (async () => { });
|
|
137
|
+
await onPhase("verifying");
|
|
135
138
|
for (const task of ordered) {
|
|
136
|
-
|
|
139
|
+
const verified = await step(record.path, ["verify", "--task", task.task.id, "--base", target]);
|
|
140
|
+
await onPhase("verifying", { taskId: task.task.id, update: { verify: verified.status === "ok" ? "PASS" : "FAIL",
|
|
141
|
+
...criteriaProgress(task.task.criteria, verified.data?.report ?? {}) } });
|
|
142
|
+
requirePass(verified, `Verify ${task.task.id}`);
|
|
137
143
|
}
|
|
138
|
-
|
|
144
|
+
await onPhase("reviewing");
|
|
145
|
+
let reviewMode;
|
|
146
|
+
try {
|
|
147
|
+
reviewMode = await reviewFinalTree(step, record.path, parent.task.id, ordered.map(({ task }) => task.id), target);
|
|
148
|
+
}
|
|
149
|
+
catch (failure) {
|
|
150
|
+
for (const { task } of ordered)
|
|
151
|
+
await onPhase("reviewing", { taskId: task.id, update: { review: "FAIL" } });
|
|
152
|
+
throw failure;
|
|
153
|
+
}
|
|
154
|
+
for (const { task } of ordered)
|
|
155
|
+
await onPhase("integrating", { taskId: task.id, update: { review: "PASS", passed: task.criteria.length, total: task.criteria.length } });
|
|
139
156
|
const scope = await resolveReviewScope({ root: record.path, runner: runner(deps, record.path), base: target,
|
|
140
157
|
...(noChangePaths === undefined ? {} : { noChangePaths }) });
|
|
141
158
|
const sourceFingerprint = await calculateSourceFingerprint(record.path, scope, runner(deps, record.path));
|
|
@@ -13,6 +13,7 @@ export function formatInstallPlan(plan, trust, warnings = []) {
|
|
|
13
13
|
title: "Installation plan",
|
|
14
14
|
metadata: [
|
|
15
15
|
`Scope: ${plan.scope}`,
|
|
16
|
+
`Office (Preview): ${plan.config.features.office?.enabled === true ? "enabled" : "disabled"}`,
|
|
16
17
|
`Harness: ${plan.harness}`,
|
|
17
18
|
`Profiles: ${plan.profiles.join(", ")}`,
|
|
18
19
|
...(plan.config.worktree === undefined
|
|
@@ -103,6 +104,7 @@ export async function runInitCommand(options) {
|
|
|
103
104
|
scope: args.scope,
|
|
104
105
|
harness: args.harness,
|
|
105
106
|
profiles: args.profiles,
|
|
107
|
+
...(args.office === undefined ? {} : { officeEnabled: args.office === "on" }),
|
|
106
108
|
adapters: options.adapters,
|
|
107
109
|
...(options.toolkitVersion === undefined
|
|
108
110
|
? {}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { AgentOpsError } from "../../../../runtime/src/fs/paths.js";
|
|
2
|
+
import { errorEnvelope, okEnvelope } from "../output.js";
|
|
3
|
+
export async function runOfficeCommand(options) {
|
|
4
|
+
try {
|
|
5
|
+
const url = await options.ensure();
|
|
6
|
+
if (url !== null)
|
|
7
|
+
return okEnvelope("OFFICE_READY", { url, text: `Office (Preview): ${url}` });
|
|
8
|
+
let shown = "";
|
|
9
|
+
await options.foreground((served) => {
|
|
10
|
+
shown = served;
|
|
11
|
+
options.writeStdout(`Office (Preview): ${served}\nServing in the foreground; it exits 10 minutes after no run or session is active.\n`);
|
|
12
|
+
});
|
|
13
|
+
return okEnvelope("OFFICE_CLOSED", { url: shown, text: "Office closed: no run was active for 10 minutes." });
|
|
14
|
+
}
|
|
15
|
+
catch (cause) {
|
|
16
|
+
if (cause instanceof AgentOpsError)
|
|
17
|
+
return errorEnvelope(cause.code, cause.message);
|
|
18
|
+
throw cause;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -66,6 +66,10 @@ async function answerFromArgs(args, root) {
|
|
|
66
66
|
* External lifecycle surface. The command only starts/controls a supervisor;
|
|
67
67
|
* it never marks the task complete or treats a native process exit as proof.
|
|
68
68
|
*/
|
|
69
|
+
async function withOffice(options, response) {
|
|
70
|
+
const url = await options.office?.().catch(() => null) ?? null;
|
|
71
|
+
return url === null ? response : { ...response, officeUrl: url, text: `${response.text}\nOffice (Preview): ${url}` };
|
|
72
|
+
}
|
|
69
73
|
export async function runRunCommand(options) {
|
|
70
74
|
const action = actionOf(options.args);
|
|
71
75
|
try {
|
|
@@ -83,12 +87,12 @@ export async function runRunCommand(options) {
|
|
|
83
87
|
const state = options.args.wait === true && options.waitForCompletion !== undefined
|
|
84
88
|
? await options.waitForCompletion(result.state.runId)
|
|
85
89
|
: result.state;
|
|
86
|
-
const response = data(action, { ...result, state });
|
|
90
|
+
const response = await withOffice(options, data(action, { ...result, state }));
|
|
87
91
|
return okEnvelope(options.args.wait === true ? "RUN_FINISHED" : "RUN_STARTED", response);
|
|
88
92
|
}
|
|
89
93
|
const runId = runIdOf(options.args);
|
|
90
94
|
if (action === "status")
|
|
91
|
-
return okEnvelope("RUN_STATUS", stateData(action, await options.service.status(runId), `Read run ${runId}.`));
|
|
95
|
+
return okEnvelope("RUN_STATUS", await withOffice(options, stateData(action, await options.service.status(runId), `Read run ${runId}.`)));
|
|
92
96
|
if (action === "logs") {
|
|
93
97
|
const state = await options.service.status(runId);
|
|
94
98
|
const events = await options.service.logs(runId);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { readFile } from "node:fs/promises";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
+
import { loadConfigFile } from "../../../../runtime/src/config/load.js";
|
|
3
4
|
import { detectWorktreeSetup } from "../../../../runtime/src/install/parallel-setup.js";
|
|
4
5
|
import { applyUpdatePlan, createUpdatePlan } from "../../../../runtime/src/install/update.js";
|
|
5
6
|
import { okEnvelope } from "../output.js";
|
|
@@ -15,6 +16,7 @@ export function formatUpdatePlan(plan, trust) {
|
|
|
15
16
|
title: "Update plan",
|
|
16
17
|
metadata: [
|
|
17
18
|
`Target version: ${plan.targetVersion}`,
|
|
19
|
+
`Office (Preview): ${plan.installation.config.features.office?.enabled === true ? "enabled" : "disabled"}`,
|
|
18
20
|
`Harness: ${plan.installation.harness.join(", ")}`,
|
|
19
21
|
...(plan.installation.config.worktree === undefined
|
|
20
22
|
? []
|
|
@@ -95,9 +97,15 @@ export async function runUpdateCommand(options) {
|
|
|
95
97
|
};
|
|
96
98
|
}
|
|
97
99
|
}
|
|
100
|
+
let officeEnabled = options.args.office === undefined ? undefined : options.args.office === "on";
|
|
101
|
+
if (officeEnabled === undefined && options.promptOffice !== undefined && options.isTTY && !options.args.yes) {
|
|
102
|
+
const { config } = await loadConfigFile(join(options.root, ".agent-ops", "config.json"));
|
|
103
|
+
officeEnabled = await options.promptOffice("Enable Office (Preview)?", config.features.office?.enabled === true);
|
|
104
|
+
}
|
|
98
105
|
const plan = await createUpdatePlan({
|
|
99
106
|
root: options.root,
|
|
100
107
|
adapters: options.adapters,
|
|
108
|
+
...(officeEnabled === undefined ? {} : { officeEnabled }),
|
|
101
109
|
...(options.args.harness === undefined
|
|
102
110
|
? {}
|
|
103
111
|
: { harness: options.args.harness }),
|
|
@@ -4,8 +4,9 @@
|
|
|
4
4
|
import { CLI_VERSION } from "./version.js";
|
|
5
5
|
import { runHookProcess } from "./hook-process.js";
|
|
6
6
|
import { worktreeDependencies } from "./parallel-deps.js";
|
|
7
|
+
import { observeOfficeSession } from "./office-entry.js";
|
|
7
8
|
process.exitCode = await runHookProcess(process.argv.slice(2), {
|
|
8
9
|
stdin: process.stdin,
|
|
9
10
|
writeStdout: (value) => process.stdout.write(value),
|
|
10
11
|
writeStderr: (value) => process.stderr.write(value)
|
|
11
|
-
}, CLI_VERSION, { worktree: worktreeDependencies() });
|
|
12
|
+
}, CLI_VERSION, { worktree: worktreeDependencies(), office: observeOfficeSession });
|
|
@@ -9,6 +9,7 @@ import { resolveContainedPath } from "../../../runtime/src/fs/paths.js";
|
|
|
9
9
|
import { redactSecrets } from "../../../runtime/src/security/redact.js";
|
|
10
10
|
import { claudeStopRecursionMarker } from "../../../runtime/src/adapters/claude/input.js";
|
|
11
11
|
import { HARNESS_IDS, harnessDescriptor } from "../../../runtime/src/install/harness.js";
|
|
12
|
+
import { officePhaseHint } from "./office-entry.js";
|
|
12
13
|
import { evaluateWorktreeEnter } from "../../../runtime/src/parallel/enter.js";
|
|
13
14
|
import { evaluateWorktreeWrite, resolveMainRoot } from "../../../runtime/src/parallel/guard.js";
|
|
14
15
|
import { ensureSessionWorktree, recordSessionOrigin } from "../../../runtime/src/parallel/service.js";
|
|
@@ -284,7 +285,11 @@ export async function runHookProcess(argv, io, cliVersion, dependencies = {}) {
|
|
|
284
285
|
parsedInput !== null &&
|
|
285
286
|
typeof parsedInput.projectRoot === "string"
|
|
286
287
|
? parsedInput.projectRoot
|
|
287
|
-
:
|
|
288
|
+
: typeof parsedInput === "object" &&
|
|
289
|
+
parsedInput !== null &&
|
|
290
|
+
typeof parsedInput.cwd === "string"
|
|
291
|
+
? parsedInput.cwd
|
|
292
|
+
: undefined;
|
|
288
293
|
if (inputRoot !== undefined) {
|
|
289
294
|
root = inputRoot;
|
|
290
295
|
}
|
|
@@ -326,6 +331,22 @@ export async function runHookProcess(argv, io, cliVersion, dependencies = {}) {
|
|
|
326
331
|
? await repositoryTrust(root, config, cliVersion)
|
|
327
332
|
: await dependencies.trust(root, config, cliVersion);
|
|
328
333
|
const trusted = trustStatus === "TRUSTED";
|
|
334
|
+
if (dependencies.office !== undefined && config.features.office?.enabled === true) {
|
|
335
|
+
const normalized = normalizeHookInput(harnessId, parsedInput);
|
|
336
|
+
if (normalized !== null) {
|
|
337
|
+
const phase = officePhaseHint(normalized);
|
|
338
|
+
void dependencies.office({
|
|
339
|
+
root,
|
|
340
|
+
harness: harnessId,
|
|
341
|
+
event: hookEvent,
|
|
342
|
+
input: parsedInput,
|
|
343
|
+
validated: true,
|
|
344
|
+
...(normalized.sessionId === undefined ? {} : { sessionId: normalized.sessionId }),
|
|
345
|
+
...(normalized.agentId === undefined ? {} : { agentId: normalized.agentId }),
|
|
346
|
+
...(phase === undefined ? {} : { phase })
|
|
347
|
+
}).catch(() => undefined);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
329
350
|
const gitRunner = dependencies.gitRunner ?? defaultGitRunner(root);
|
|
330
351
|
const processRunner = dependencies.processRunner ?? new NodeVerificationProcessRunner();
|
|
331
352
|
const stopVerification = shouldBuildStopVerification(harnessId, event, config, parsedInput)
|
|
@@ -383,7 +404,7 @@ export async function runHookProcess(argv, io, cliVersion, dependencies = {}) {
|
|
|
383
404
|
? { action: "continue", status: "UNKNOWN", code: "WORKTREE_GUARD_UNAVAILABLE" }
|
|
384
405
|
: await evaluateWorktreeWrite(mainRoot, write.paths, write.sessionId, dependencies.worktree === undefined
|
|
385
406
|
? undefined
|
|
386
|
-
: async (sessionId, agentId) => (await ensureSessionWorktree(dependencies.worktree, { cwd: mainRoot, sessionId, ...(agentId === undefined ? {} : { agentId }) })).path, write.agentId);
|
|
407
|
+
: async (sessionId, agentId) => (await ensureSessionWorktree(dependencies.worktree, { cwd: mainRoot, sessionId, ...(agentId === undefined ? {} : { agentId }) })).path, write.agentId, config.profiles.includes("run"));
|
|
387
408
|
}
|
|
388
409
|
: undefined;
|
|
389
410
|
const worktreeEnter = config.worktree?.mode === "auto"
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// Entry point used only by the generated project-local loop launchers.
|
|
3
3
|
import { runLoopProcess } from "./codex-loop-process.js";
|
|
4
|
+
import { observeOfficeSession } from "./office-entry.js";
|
|
4
5
|
process.exitCode = await runLoopProcess(process.argv.slice(2), {
|
|
5
6
|
stdin: process.stdin,
|
|
6
7
|
writeStdout: (value) => process.stdout.write(value),
|
|
7
8
|
writeStderr: (value) => process.stderr.write(value)
|
|
8
|
-
});
|
|
9
|
+
}, { office: observeOfficeSession });
|