@kylecheng3146/agent-ops 0.6.0 → 0.7.1
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 +60 -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/review.js +10 -4
- 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 +7 -5
- 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 +261 -0
- package/dist/packages/cli/src/parallel-deps.js +4 -2
- 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/fs/transaction.js +2 -1
- package/dist/runtime/src/hooks/codex-loop.js +4 -0
- package/dist/runtime/src/hooks/completion-gate.js +38 -3
- 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 +268 -0
- package/dist/runtime/src/office/page.js +887 -0
- package/dist/runtime/src/office/repos.js +43 -0
- package/dist/runtime/src/office/scene.js +181 -0
- package/dist/runtime/src/office/server.js +143 -0
- package/dist/runtime/src/office/sessions.js +130 -0
- package/dist/runtime/src/office/snapshot.js +117 -0
- package/dist/runtime/src/parallel/finish.js +3 -0
- package/dist/runtime/src/parallel/guard.js +7 -1
- package/dist/runtime/src/review/slots.js +1 -1
- package/dist/runtime/src/run/hosts/codex.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/dist/runtime/src/security/permissions.js +6 -0
- package/dist/runtime/src/verify/source-fingerprint.js +4 -0
- package/docs/en/spec/harness-adapters.md +122 -1
- package/docs/zh-TW/spec/harness-adapters.md +100 -1
- package/package.json +1 -1
- package/schemas/config.schema.json +10 -1
|
@@ -36,6 +36,8 @@ export async function canonicalPath(path) {
|
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
|
+
/** The gitignored goal file the `run` profile writes in the main checkout. */
|
|
40
|
+
export const RUN_GOAL_FILE = join(".agent-ops", "state", "run-goal.md");
|
|
39
41
|
/**
|
|
40
42
|
* In worktree auto mode the main checkout is shared by every session, so no
|
|
41
43
|
* session writes it directly: each edit belongs in that session's own
|
|
@@ -46,9 +48,13 @@ export async function evaluateWorktreeWrite(mainRoot, paths, sessionId,
|
|
|
46
48
|
/** Creates or reuses this writer's worktree and returns its path. */
|
|
47
49
|
ensureWorktree,
|
|
48
50
|
/** The subagent making the write, when there is one: it has a worktree of its own. */
|
|
49
|
-
agentId
|
|
51
|
+
agentId,
|
|
52
|
+
/** The `auto-run` capability: `agent-ops run` starts from the main checkout. */
|
|
53
|
+
autoRun = false) {
|
|
50
54
|
for (const path of paths) {
|
|
51
55
|
const target = await canonicalPath(path);
|
|
56
|
+
if (autoRun && target === join(mainRoot, RUN_GOAL_FILE))
|
|
57
|
+
continue;
|
|
52
58
|
const inMain = target === mainRoot || target.startsWith(`${mainRoot}${sep}`);
|
|
53
59
|
if (inMain && insideWorktreeDirectory(mainRoot, target) && sessionId !== undefined) {
|
|
54
60
|
// A worktree belongs to the session that made it: writing into another
|
|
@@ -62,7 +62,7 @@ export async function withReviewSlot(options, action) {
|
|
|
62
62
|
for (;;) {
|
|
63
63
|
try {
|
|
64
64
|
await mkdir(path);
|
|
65
|
-
await writeFile(join(path, "owner.json"), JSON.stringify({ pid: process.pid, at: Date.now(), token }));
|
|
65
|
+
await writeFile(join(path, "owner.json"), JSON.stringify({ ...options.holder, pid: process.pid, at: Date.now(), token }));
|
|
66
66
|
return true;
|
|
67
67
|
}
|
|
68
68
|
catch (error) {
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/** Move one worker, and the run with it, into a phase. Unknown workers leave the state unchanged. */
|
|
2
|
+
export function withRunPhase(state, workerId, phase) {
|
|
3
|
+
if (!state.workers.some(w => w.workerId === workerId))
|
|
4
|
+
return state;
|
|
5
|
+
return { ...state, phase, workers: state.workers.map(w => w.workerId === workerId ? { ...w, phase } : w) };
|
|
6
|
+
}
|
|
7
|
+
/** Merge the latest verify or review observation into one task node. */
|
|
8
|
+
export function withTaskProgress(state, taskId, update) {
|
|
9
|
+
return { ...state, tasks: state.tasks.map(t => t.taskId !== taskId ? t :
|
|
10
|
+
{ ...t, progress: { verify: null, review: null, passed: 0, total: 0, ...t.progress, ...update } }) };
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Passed/total criteria from one verify report: a command-backed criterion
|
|
14
|
+
* passes when every command result passed, a typed one when every replayed
|
|
15
|
+
* acceptance phase for it passed.
|
|
16
|
+
*/
|
|
17
|
+
export function criteriaProgress(criteria, report) {
|
|
18
|
+
const results = report.results ?? [];
|
|
19
|
+
const commandsPass = results.length > 0 && results.every(r => r.status === "PASS");
|
|
20
|
+
const passed = criteria.filter(c => {
|
|
21
|
+
if (c.acceptance === undefined)
|
|
22
|
+
return commandsPass;
|
|
23
|
+
const rows = (report.acceptance ?? []).filter(r => r.criterionId === c.id);
|
|
24
|
+
return rows.length > 0 && rows.every(r => r.status === "PASS");
|
|
25
|
+
}).length;
|
|
26
|
+
return { passed, total: criteria.length };
|
|
27
|
+
}
|
|
28
|
+
/** Phase writes are display data: a failed write must never fail the proof it describes. */
|
|
29
|
+
export async function recordRunPhase(repository, runId, workerId, phase, progress) {
|
|
30
|
+
try {
|
|
31
|
+
await repository.mutate(runId, state => {
|
|
32
|
+
const moved = withRunPhase(state, workerId, phase);
|
|
33
|
+
return progress === undefined ? moved : withTaskProgress(moved, progress.taskId, progress.update);
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
catch { /* ponytail: display-only; the next transition rewrites it */ }
|
|
37
|
+
}
|
|
@@ -5,6 +5,7 @@ import { sha256 } from "../fs/hash.js";
|
|
|
5
5
|
import { AgentOpsError } from "../fs/paths.js";
|
|
6
6
|
import { readPrivateFile, withPrivateFileLock, writePrivateFile } from "../security/permissions.js";
|
|
7
7
|
import { budgetExpired } from "./scheduler.js";
|
|
8
|
+
export const RUN_PHASES = ["planning", "implementing", "verifying", "reviewing", "integrating"];
|
|
8
9
|
/** Writer slots are deliberately bounded; the coordinator may occupy one. */
|
|
9
10
|
export const MAX_ACTIVE_WORKERS = 2;
|
|
10
11
|
const RUN_ID = /^[a-z][a-z0-9-]{7,63}$/u;
|
|
@@ -113,6 +114,13 @@ export function assertRunState(value) {
|
|
|
113
114
|
return invalid("Persisted run state failed identity, budget, or ownership validation.");
|
|
114
115
|
}
|
|
115
116
|
validUsage(state.budget.usage);
|
|
117
|
+
const phase = (value) => value === undefined || RUN_PHASES.includes(value);
|
|
118
|
+
const count = (value) => Number.isSafeInteger(value) && value >= 0 && value <= 64;
|
|
119
|
+
const result = (value) => value === null || value === "PASS" || value === "FAIL";
|
|
120
|
+
if (!phase(state.phase) || state.workers.some(w => !phase(w.phase)) || state.tasks.some(t => t.progress !== undefined &&
|
|
121
|
+
(!plain(t.progress) || !result(t.progress.verify) || !result(t.progress.review) || !count(t.progress.passed) ||
|
|
122
|
+
!count(t.progress.total) || t.progress.passed > t.progress.total)))
|
|
123
|
+
return invalid("Run phase or task progress is invalid.");
|
|
116
124
|
if (state.policyBinding !== undefined && !validPolicyBinding(state.policyBinding))
|
|
117
125
|
return invalid("Invalid run policy binding.");
|
|
118
126
|
if (state.proofProcess != null && (!plain(state.proofProcess) || !Number.isSafeInteger(state.proofProcess.processId) ||
|
|
@@ -3,7 +3,7 @@ import { acceptanceError, runnerError, commitIdentity } from "./acceptance.js";
|
|
|
3
3
|
const ID_PATTERN = /^[a-z][a-z0-9-]{0,127}$/;
|
|
4
4
|
const HASH_PATTERN = /^[a-f0-9]{64}$/;
|
|
5
5
|
const WINDOWS_RESERVED_SEGMENT = /^(?:aux|com[1-9]|con|lpt[1-9]|nul|prn)(?:\..*)?$/i;
|
|
6
|
-
const PROFILE_VALUES = new Set(["advisory", "core", "guardrails", "loop"]);
|
|
6
|
+
const PROFILE_VALUES = new Set(["advisory", "core", "guardrails", "loop", "run"]);
|
|
7
7
|
const EVIDENCE_KINDS = new Set(["exit-code", "file", "test-count"]);
|
|
8
8
|
const SCOPE_VALUES = new Set(["project", "user"]);
|
|
9
9
|
const HARNESS_VALUES = new Set(["agy", "claude", "codex", "opencode"]);
|
|
@@ -330,10 +330,21 @@ export function validateConfig(value) {
|
|
|
330
330
|
if (!isRecord(root.features)) {
|
|
331
331
|
return failure("INVALID_TYPE", "$.features", "features must be an object.");
|
|
332
332
|
}
|
|
333
|
-
const featuresUnknown = unknownFieldFailure(root.features, ["completionGate", "stopVerification"], "$.features");
|
|
333
|
+
const featuresUnknown = unknownFieldFailure(root.features, ["completionGate", "stopVerification", "office"], "$.features");
|
|
334
334
|
if (featuresUnknown !== undefined) {
|
|
335
335
|
return featuresUnknown;
|
|
336
336
|
}
|
|
337
|
+
if (root.features.office !== undefined) {
|
|
338
|
+
if (!isRecord(root.features.office)) {
|
|
339
|
+
return failure("INVALID_TYPE", "$.features.office", "office must be an object.");
|
|
340
|
+
}
|
|
341
|
+
const unknown = unknownFieldFailure(root.features.office, ["enabled"], "$.features.office");
|
|
342
|
+
if (unknown !== undefined)
|
|
343
|
+
return unknown;
|
|
344
|
+
if (typeof root.features.office.enabled !== "boolean") {
|
|
345
|
+
return failure("INVALID_FEATURE", "$.features.office.enabled", "office.enabled must be a boolean.");
|
|
346
|
+
}
|
|
347
|
+
}
|
|
337
348
|
if (!isRecord(root.features.stopVerification)) {
|
|
338
349
|
return failure("INVALID_TYPE", "$.features.stopVerification", "stopVerification must be an object.");
|
|
339
350
|
}
|
|
@@ -37,6 +37,10 @@ const execFile = promisify(execFileCallback);
|
|
|
37
37
|
const lockQueues = new Map();
|
|
38
38
|
const heartbeatFailures = new Map();
|
|
39
39
|
const heartbeatPaths = new Set();
|
|
40
|
+
// A heartbeat lives as long as the process. Its handle is held here, not only
|
|
41
|
+
// by the interval below, so a reset or mocked timer can never let garbage
|
|
42
|
+
// collection close it (an uncaught error since Node 26).
|
|
43
|
+
const heartbeatHandles = new Set();
|
|
40
44
|
const processHeartbeats = new Map();
|
|
41
45
|
const processIdentityCache = new Map();
|
|
42
46
|
let heartbeatCleanupRegistered = false;
|
|
@@ -151,6 +155,7 @@ async function ensureProcessHeartbeat(parent, anchorDirectory) {
|
|
|
151
155
|
throw error;
|
|
152
156
|
}
|
|
153
157
|
heartbeatPaths.add(heartbeatPath);
|
|
158
|
+
heartbeatHandles.add(handle);
|
|
154
159
|
registerHeartbeatCleanup();
|
|
155
160
|
const heartbeat = {
|
|
156
161
|
device,
|
|
@@ -161,6 +166,7 @@ async function ensureProcessHeartbeat(parent, anchorDirectory) {
|
|
|
161
166
|
void handle.utimes(new Date(), new Date()).catch((error) => {
|
|
162
167
|
heartbeatFailures.set(cacheKey, error);
|
|
163
168
|
clearInterval(timer);
|
|
169
|
+
heartbeatHandles.delete(handle);
|
|
164
170
|
void handle.close().catch(() => {
|
|
165
171
|
process.emitWarning("A failed private-state heartbeat handle could not be closed.");
|
|
166
172
|
});
|
|
@@ -39,6 +39,10 @@ export async function calculateSourceFingerprint(root, scope, runner) {
|
|
|
39
39
|
hash: sha256(bytes)
|
|
40
40
|
});
|
|
41
41
|
}
|
|
42
|
+
return worktreeFingerprint(head, paths);
|
|
43
|
+
}
|
|
44
|
+
/** A worktree-mode fingerprint; with no paths, that of a clean checkout at `head`. */
|
|
45
|
+
export function worktreeFingerprint(head, paths = []) {
|
|
42
46
|
return sha256(JSON.stringify({
|
|
43
47
|
domain: "agent-ops-source-v1",
|
|
44
48
|
mode: "worktree",
|
|
@@ -41,7 +41,7 @@ capabilities and MUST track generated source as one whole-file artifact.
|
|
|
41
41
|
- Action: For opencode, manage `.opencode/plugins/agent-ops.js` in a project or `.config/opencode/plugins/agent-ops.js` at user scope (`$XDG_CONFIG_HOME/opencode/plugins/agent-ops.js` when configured inside the managed user root, or `$OPENCODE_CONFIG_DIR/plugins/agent-ops.js` when that native config directory is configured), leave `opencode.json` untouched, and deduplicate a project `AGENTS.md` contribution by path.
|
|
42
42
|
- Evidence: The manifest contains the plugin hash, the generated source contains only the selected hook registrations, and shared project markers occur once.
|
|
43
43
|
- Positive: `codex,opencode` produces one project AGENTS route and one hashed opencode plugin.
|
|
44
|
-
- Negative: `Add an opencode.json instructions entry or register a plugin for a core-only profile.`
|
|
44
|
+
- Negative: `Add an opencode.json instructions entry or register a plugin for a core-only profile with Office disabled.`
|
|
45
45
|
|
|
46
46
|
## HARNESS-ADAPTER-004
|
|
47
47
|
|
|
@@ -106,6 +106,7 @@ The current registration matrix is intentionally asymmetric:
|
|
|
106
106
|
|
|
107
107
|
| Capability | agy | Codex | Claude Code | OpenCode |
|
|
108
108
|
| --- | --- | --- | --- | --- |
|
|
109
|
+
| office-presence (Preview) | degraded | degraded | supported | degraded |
|
|
109
110
|
| lifecycle-summary | degraded | supported | supported | degraded |
|
|
110
111
|
| command-policy | supported | unknown | supported | supported |
|
|
111
112
|
| completion-gate | supported | unsupported | unsupported | unsupported |
|
|
@@ -137,3 +138,123 @@ stores only bounded local event metadata, returns bounded redacted session
|
|
|
137
138
|
context, and preserves local goal, state, telemetry, and Codex TOML files on
|
|
138
139
|
update or uninstall. A clearly parsed `[features]` / `hooks = false` in an
|
|
139
140
|
existing Codex configuration MUST reject loop planning before any write.
|
|
141
|
+
|
|
142
|
+
## HARNESS-ADAPTER-007
|
|
143
|
+
|
|
144
|
+
The `run` profile MUST be opt-in, select `core` and `loop` with it, and add only
|
|
145
|
+
the `auto-run` capability. With it, the Claude Code and Codex managed rules hand
|
|
146
|
+
a change that needs more than five acceptance criteria to `agent-ops run`; five
|
|
147
|
+
or fewer stay in the session. agy rules, and every rule file without `auto-run`,
|
|
148
|
+
MUST stay byte-identical to the rules without the profile.
|
|
149
|
+
|
|
150
|
+
- Trigger: A project selects `run`, and a Claude Code or Codex session meets a
|
|
151
|
+
change that needs more than five acceptance criteria.
|
|
152
|
+
- Action: From the main checkout and before any `task create`, the agent writes
|
|
153
|
+
the user's prompt verbatim and its proposed acceptance criteria, marked as
|
|
154
|
+
proposals, to the gitignored `.agent-ops/state/run-goal.md`, then starts
|
|
155
|
+
`agent-ops run --goal-file .agent-ops/state/run-goal.md --host <claude|codex> --wait`
|
|
156
|
+
as a background shell command. Codex starts it like review: an outer request
|
|
157
|
+
with `sandbox_permissions: "require_escalated"` and
|
|
158
|
+
`env -u CODEX_SANDBOX_NETWORK_DISABLED`. An awaiting-input result is relayed
|
|
159
|
+
to the user and answered with `agent-ops run respond`. A start refused with
|
|
160
|
+
`RUN_TARGET_REQUIRED`, `RUN_BACKGROUND_UNSUPPORTED`, `RUN_TARGET_DIRTY` or
|
|
161
|
+
`WORKTREE_NESTED` is reported in one line naming the code and falls back to
|
|
162
|
+
the subtask flow; `RUN_REPO_UNTRUSTED` stops and asks the user. A run that
|
|
163
|
+
ends without completing (blocked, budget exhausted, stopped, failing review)
|
|
164
|
+
is reported with its `run status` state and code, its last `run logs` events,
|
|
165
|
+
and the exact `agent-ops run resume <id>` and `agent-ops run stop <id>`
|
|
166
|
+
commands, and is never resumed automatically or taken over by the session.
|
|
167
|
+
With worktree auto mode, the Claude `PreToolUse` worktree guard allows a
|
|
168
|
+
write to exactly `.agent-ops/state/run-goal.md` in the main checkout without
|
|
169
|
+
creating a worktree. Confirmed init/update pre-authorizes `agent-ops run`:
|
|
170
|
+
`Bash(agent-ops run *)` for Claude Code, and an escalated prefix rule for
|
|
171
|
+
`env -u CODEX_SANDBOX_NETWORK_DISABLED agent-ops run` for Codex.
|
|
172
|
+
- Evidence: Profile, managed-rule digest, pre-authorization, and worktree-guard
|
|
173
|
+
tests cover the resolved profiles, the unchanged rules, the added entries, and
|
|
174
|
+
the single exempt path.
|
|
175
|
+
- Positive: `A Claude Code session with the run profile writes .agent-ops/state/run-goal.md in the main checkout and starts agent-ops run --host claude --wait in the background.`
|
|
176
|
+
- Negative: `Split an eight-criteria change into subtasks while the run profile is active, add auto-run text to GEMINI.md, or resume a blocked run without the user.`
|
|
177
|
+
|
|
178
|
+
## HARNESS-ADAPTER-008
|
|
179
|
+
|
|
180
|
+
`agent-ops office` (Preview) MUST show progress read-only from agent-ops state alone. The
|
|
181
|
+
supervisor records an optional phase per run and worker (`planning`,
|
|
182
|
+
`implementing`, `verifying`, `reviewing`, `integrating`) at deterministic
|
|
183
|
+
transitions, and each task's latest verify and review result with passed/total
|
|
184
|
+
criteria. Run state written before phases existed MUST still validate and show
|
|
185
|
+
as `unknown`. Changed files come from each worktree's own `git diff` against its
|
|
186
|
+
base; narration uses paths only (`docs/**` is "writing docs", `tests/**` is
|
|
187
|
+
"writing tests", otherwise "editing <file>"). The office MUST NOT read native
|
|
188
|
+
Claude or Codex transcripts or file contents.
|
|
189
|
+
|
|
190
|
+
- Office Preview is opt-in via `features.office.enabled`. Init MUST offer an
|
|
191
|
+
off-by-default choice; interactive update MUST default to the existing choice.
|
|
192
|
+
Non-interactive update MUST preserve it unless `--office on|off` is supplied.
|
|
193
|
+
Older configs without the optional field remain valid and mean disabled.
|
|
194
|
+
Enabling Office MUST install its presence hooks even with only `core`; this
|
|
195
|
+
MUST NOT opt into command policy, Stop verification or lifecycle summaries.
|
|
196
|
+
Office-only OpenCode runtime failure MUST remain fail-open; adding Office to
|
|
197
|
+
command policy MUST preserve policy denial and unavailable-runtime behavior.
|
|
198
|
+
Agy startup is per invocation, OpenCode startup is app-scoped, and Codex
|
|
199
|
+
Stop delivery is unavailable in some execution modes; stale presence expires
|
|
200
|
+
using the bounded hook-activity timeout rather than assuming native Stop.
|
|
201
|
+
Features retain their existing whole-object layer precedence. Disabled hooks
|
|
202
|
+
MUST NOT record Office presence or start/open Office; the explicit command
|
|
203
|
+
MUST explain how to enable it. A running server MUST close after observing a
|
|
204
|
+
disabled choice on its next 15-second lifecycle check.
|
|
205
|
+
- Trigger: When enabled, a managed ordinary session starts or reports activity, or a user runs
|
|
206
|
+
`agent-ops office`, `agent-ops run start` or `agent-ops run status`.
|
|
207
|
+
- Action: The command reuses the user's one live server recorded in
|
|
208
|
+
`~/.agent-ops/state/office/office.json` (under `AGENT_OPS_HOME` when set), or
|
|
209
|
+
starts one, and prints its URL. Ordinary session hooks also start or reuse it,
|
|
210
|
+
including before a task or worktree exists. Only the successful new-server
|
|
211
|
+
owner opens the browser; sessions in every repository share the same Office.
|
|
212
|
+
An Office-enabled repository registers its main root and Git common directory
|
|
213
|
+
in `repos.json` beside that record. Each collection re-checks that the
|
|
214
|
+
directory exists and the repository still opts in, and forgets a repository
|
|
215
|
+
quiet for two hours with nothing left to show. Rooms carry their repository's
|
|
216
|
+
name, their keys are scoped by it, and the page filters by repository. The
|
|
217
|
+
first report from a repository that still has a 0.7 per-repository record
|
|
218
|
+
boots its launchd job out and removes that record; its pid is never signalled.
|
|
219
|
+
A successful `worktree finish` records the session's completion so its room
|
|
220
|
+
closes; only a new start or an active task reopens it. Session metadata contains bounded
|
|
221
|
+
identifiers, known work state and activity times, never native transcripts,
|
|
222
|
+
prompts or tool/file contents. Startup and observation failures are advisory.
|
|
223
|
+
The server binds
|
|
224
|
+
127.0.0.1 on a random port, requires the URL's unguessable token, refuses any
|
|
225
|
+
Host other than that address and port, answers every non-GET with 405, and
|
|
226
|
+
exits 10 minutes after neither a run nor an ordinary session is active.
|
|
227
|
+
Ordinary-session liveness follows hooks, with stale presence expiring after
|
|
228
|
+
30 minutes without activity; it does not inspect native host processes.
|
|
229
|
+
Its single inline page uses cozy cream, oak and sage in-code 16-color scene
|
|
230
|
+
sprites with no image assets. Each 32×48 character uses its own fixed skin,
|
|
231
|
+
hair and clothing palette, four-direction walking, and localized role labels.
|
|
232
|
+
The viewer's badge displays `You` in English and `你` in Chinese; its overview size matches the largest room's
|
|
233
|
+
characters. The fixed-viewport overview
|
|
234
|
+
fills the available page below the header and shows all furnished work-unit
|
|
235
|
+
rooms, shrinking rooms as necessary without document scrolling. A shared
|
|
236
|
+
hallway connects the rooms. The user controls a local Supervisor with arrow
|
|
237
|
+
keys or WASD, can walk into every room and return to the overview, without
|
|
238
|
+
model requests or network mutations. Each run team shares a room; each ordinary session has its own,
|
|
239
|
+
and running reviews remain visible. Clicking a room enters a larger detail
|
|
240
|
+
view with a return control. Every room contains planning, development,
|
|
241
|
+
verification, review and integration areas. Characters walk to their known
|
|
242
|
+
phase's zone, use light phase-driven animation, and have short name/status
|
|
243
|
+
labels; unknown state is not inferred from dialogue. Whiteboards show task
|
|
244
|
+
criteria progress, verify/review outcomes and pending questions, with `!` for
|
|
245
|
+
unanswered questions. Clicking a whiteboard or progress board opens the room
|
|
246
|
+
work list; workstations and phase areas show the known work in that phase.
|
|
247
|
+
Pending markers open complete questions with the existing copyable commands.
|
|
248
|
+
The same paged views are available with `L` (work list), `1–5` (phases) and
|
|
249
|
+
`Q` (questions). Clicking an actor opens bounded, paged status details
|
|
250
|
+
with copyable commands. Completed rooms are available through Recently
|
|
251
|
+
completed for at most two hours. Chinese/English switching remembers the
|
|
252
|
+
choice, and keyboard navigation and reduced motion are supported. A failure
|
|
253
|
+
to start the office never fails a hook, `run start` or `run status`.
|
|
254
|
+
- Evidence: Phase, snapshot, server, scene and CLI tests cover legacy state,
|
|
255
|
+
aggregation and narration, ordinary-session registration and startup-once,
|
|
256
|
+
the two-hour completion boundary, token/Host/method guards, reuse and the
|
|
257
|
+
injected-clock idle exit, viewport/room navigation, localization, and the
|
|
258
|
+
printed URL. A browser read-back checks the rendered single-page layout.
|
|
259
|
+
- Positive: `An ordinary session opens the shared Office, where its room is visible before it creates a worktree; entering a team room shows the coordinator at the verification bench and a whiteboard with 2 of 6 criteria passed.`
|
|
260
|
+
- Negative: `Serve the office on 0.0.0.0, accept a POST, read a worker's transcript to narrate it, or bundle a PNG sprite sheet.`
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Harness Adapter
|
|
2
2
|
|
|
3
|
-
English source version: 2026-
|
|
3
|
+
English source version: 2026-10-07. Revalidate: when the English specification or any vendor reference changes.
|
|
4
4
|
|
|
5
5
|
本文件所述 OpenCode plugin 行為已於 2026-07-31 依據[官方 plugin 文件](https://opencode.ai/docs/plugins/)與[Bun shell 文件](https://bun.sh/docs/runtime/shell)檢查;Codex 與 Claude Code loop-hook 行為已於 2026-08-03 依據 [Codex hook 文件](https://developers.openai.com/codex/config-advanced#hooks) 與 [Claude Code hook 文件](https://code.claude.com/docs/en/hooks) 檢查;agy hook 行為已於 2026-08-28 依據[官方 Antigravity hook 文件](https://antigravity.google/docs/hooks) 檢查。任何 vendor 參考變更時都必須重新驗證。
|
|
6
6
|
|
|
@@ -90,6 +90,7 @@ script,也不得改變一般 permission request。
|
|
|
90
90
|
|
|
91
91
|
| Capability | agy | Codex | Claude Code | OpenCode |
|
|
92
92
|
| --- | --- | --- | --- | --- |
|
|
93
|
+
| office-presence (Preview) | degraded | degraded | supported | degraded |
|
|
93
94
|
| lifecycle-summary | degraded | supported | supported | degraded |
|
|
94
95
|
| command-policy | supported | unknown | supported | supported |
|
|
95
96
|
| completion-gate | supported | unsupported | unsupported | unsupported |
|
|
@@ -118,3 +119,101 @@ Stop verification 必須明確啟用、具備 trust、為 report-only 且預設
|
|
|
118
119
|
event metadata、回傳有界且 redacted 的 session context,並在 update 或 uninstall
|
|
119
120
|
時保留 local goal、state、telemetry 與 Codex TOML file。既有 Codex configuration
|
|
120
121
|
中清楚解析出的 `[features]` / `hooks = false` MUST 在任何 write 前拒絕 loop planning。
|
|
122
|
+
|
|
123
|
+
## HARNESS-ADAPTER-007
|
|
124
|
+
|
|
125
|
+
`run` profile MUST 是 opt-in,會一併選取 `core` 與 `loop`,且只新增 `auto-run`
|
|
126
|
+
capability。啟用後,Claude Code 與 Codex 的 managed rules 會將需要超過五項
|
|
127
|
+
acceptance criteria 的變更交給 `agent-ops run`;五項以內仍留在 session 中處理。
|
|
128
|
+
agy rules,以及所有未含 `auto-run` 的 rule file,MUST 與未啟用此 profile 時
|
|
129
|
+
byte-identical。
|
|
130
|
+
|
|
131
|
+
- Trigger: Project 選擇 `run`,且 Claude Code 或 Codex session 遇到需要超過五項
|
|
132
|
+
acceptance criteria 的變更。
|
|
133
|
+
- Action: Agent 在 main checkout、且在任何 `task create` 之前,將使用者 prompt
|
|
134
|
+
原文與其提議的 acceptance criteria(標示為提議)寫入已 gitignore 的
|
|
135
|
+
`.agent-ops/state/run-goal.md`,再以 background shell command 啟動
|
|
136
|
+
`agent-ops run --goal-file .agent-ops/state/run-goal.md --host <claude|codex> --wait`。
|
|
137
|
+
Codex 的啟動方式與 review 相同:outer request 使用
|
|
138
|
+
`sandbox_permissions: "require_escalated"` 與
|
|
139
|
+
`env -u CODEX_SANDBOX_NETWORK_DISABLED`。Awaiting-input 結果會轉達給使用者,並以
|
|
140
|
+
`agent-ops run respond` 回覆。啟動遭 `RUN_TARGET_REQUIRED`、
|
|
141
|
+
`RUN_BACKGROUND_UNSUPPORTED`、`RUN_TARGET_DIRTY` 或 `WORKTREE_NESTED` 拒絕時,以
|
|
142
|
+
一行說明該 code,並退回 subtask flow;`RUN_REPO_UNTRUSTED` 則停止並詢問使用者。
|
|
143
|
+
未完成即結束的 run(blocked、budget exhausted、stopped、review 失敗)須回報其
|
|
144
|
+
`run status` state 與 code、最後的 `run logs` event,以及確切的
|
|
145
|
+
`agent-ops run resume <id>` 與 `agent-ops run stop <id>` command,且永不自動
|
|
146
|
+
resume 或由 session 接手。在 worktree auto mode 下,Claude `PreToolUse`
|
|
147
|
+
worktree guard 允許在 main checkout 寫入恰為 `.agent-ops/state/run-goal.md`
|
|
148
|
+
的路徑,且不建立 worktree。確認後的 init/update 會預先授權 `agent-ops run`:
|
|
149
|
+
Claude Code 為 `Bash(agent-ops run *)`,Codex 為
|
|
150
|
+
`env -u CODEX_SANDBOX_NETWORK_DISABLED agent-ops run` 的 escalated prefix rule。
|
|
151
|
+
- Evidence: Profile、managed-rule digest、pre-authorization 與 worktree-guard
|
|
152
|
+
test 涵蓋解析後的 profile、未變更的 rules、新增的 entry,以及唯一豁免的路徑。
|
|
153
|
+
- Positive: `啟用 run profile 的 Claude Code session 在 main checkout 寫入 .agent-ops/state/run-goal.md,並在 background 啟動 agent-ops run --host claude --wait。`
|
|
154
|
+
- Negative: `run profile 啟用時仍將八項 criteria 的變更拆成 subtask、在 GEMINI.md 加入 auto-run 文字,或未經使用者即 resume blocked run。`
|
|
155
|
+
|
|
156
|
+
## HARNESS-ADAPTER-008
|
|
157
|
+
|
|
158
|
+
`agent-ops office`(Preview)MUST 只依據 agent-ops state 以 read-only 方式呈現進度。
|
|
159
|
+
Supervisor 在確定性的轉換點記錄每個 run 與 worker 的 optional phase
|
|
160
|
+
(`planning`、`implementing`、`verifying`、`reviewing`、`integrating`),以及每個
|
|
161
|
+
task 最近一次 verify 與 review 結果與通過/總數 criteria。Phase 出現前寫入的 run
|
|
162
|
+
state MUST 仍能通過驗證,並顯示為 `unknown`。變更檔案來自各 worktree 自身相對
|
|
163
|
+
base 的 `git diff`;旁白只使用路徑(`docs/**` 為「writing docs」、`tests/**` 為
|
|
164
|
+
「writing tests」,其他為「editing <file>」)。Office MUST NOT 讀取 Claude 或 Codex
|
|
165
|
+
的 native transcript 或檔案內容。
|
|
166
|
+
|
|
167
|
+
- Office Preview 透過 `features.office.enabled` 明確啟用。Init MUST 提供預設關閉
|
|
168
|
+
的選項;互動式 update MUST 以既有選擇為預設。非互動式 update MUST 保留選擇,
|
|
169
|
+
除非傳入 `--office on|off`。即使只選 core,啟用 Office 也 MUST 安裝 presence
|
|
170
|
+
hook,但 MUST NOT 同時啟用 command policy、Stop verification 或 lifecycle
|
|
171
|
+
summary。Office-only OpenCode 的 runtime 失敗 MUST fail-open;與 command
|
|
172
|
+
policy 同時啟用時 MUST 保留原有拒絕與 runtime unavailable 行為。Agy 啟動事件
|
|
173
|
+
以 invocation 為單位,OpenCode 以 app 為單位,部分 Codex 模式沒有 Stop;過期
|
|
174
|
+
presence 依有界限的 hook 活動逾時處理。舊設定可省略此欄位,省略代表關閉;features 維持
|
|
175
|
+
原有的整個物件 layer 優先順序。關閉時 hook MUST NOT 記錄 Office presence、
|
|
176
|
+
啟動 server 或開啟瀏覽器,明確的 office 指令 MUST 說明啟用方法。運行中的
|
|
177
|
+
server MUST 在下一次 15 秒生命週期檢查觀察到關閉選擇後結束。
|
|
178
|
+
- Trigger: 啟用後,Managed 一般 session 啟動或回報活動,或使用者執行
|
|
179
|
+
`agent-ops office`、`agent-ops run start` 或 `agent-ops run status`。
|
|
180
|
+
- Action: Command 重用記錄於 `~/.agent-ops/state/office/office.json`(設定
|
|
181
|
+
`AGENT_OPS_HOME` 時位於其下)的使用者唯一 live server,否則啟動一個,並印出
|
|
182
|
+
URL。一般 session hook 也會自動啟動或重用 Office,包含尚未建立 task 或
|
|
183
|
+
worktree 的 session。只有成功取得新 server 所有權的 process 開啟瀏覽器,所有
|
|
184
|
+
repository 的 session 共用同一個 Office。啟用 Office 的 repository 會把 main
|
|
185
|
+
root 與 Git common directory 登記在同目錄的 `repos.json`。每次收集都重新確認
|
|
186
|
+
目錄存在且仍啟用,並忘記安靜超過兩小時且沒有內容可顯示的 repository。房間
|
|
187
|
+
帶有 repository 名稱,key 以其區隔,頁面可依 repository 篩選。仍留有 0.7
|
|
188
|
+
per-repository 紀錄的 repository 第一次回報時,會 bootout 其 launchd job 並
|
|
189
|
+
刪除該紀錄,絕不對其 pid 發送訊號。`worktree finish` 成功後會記錄 session
|
|
190
|
+
完成,使房間關閉;只有新的啟動或 active task 會重新開啟。Session metadata
|
|
191
|
+
只記錄有界限的識別資訊、已知工作狀態與活動時間,不包含 native transcript、
|
|
192
|
+
prompt、tool 或檔案內容。啟動與活動觀察失敗均為 advisory。
|
|
193
|
+
Server 綁定 127.0.0.1 的隨機 port,要求 URL 中不可猜測的 token,拒絕該位址與
|
|
194
|
+
port 以外的 Host,所有非 GET 回應 405,並在 run 與一般 session 都沒有活動
|
|
195
|
+
10 分鐘後結束。唯一的 inline page 使用奶油白、淺木與鼠尾草綠的程式內 16 色
|
|
196
|
+
場景 sprite,沒有圖片 asset。32×48 人物各自使用固定的膚色、髮色與服裝色盤,
|
|
197
|
+
支援四方向走路,並顯示本地化的職責文字。使用者標籤在中文顯示「你」、英文顯示「You」,總覽大小與
|
|
198
|
+
最大房間的成員一致。總覽填滿頂部工具列下方的單一
|
|
199
|
+
viewport,同時呈現全部有家具的工作房間,必要時自動縮小且不捲動頁面。
|
|
200
|
+
房間以共用走廊連接;使用者可以用方向鍵或 WASD 操控本機 Supervisor 角色,
|
|
201
|
+
走進每間房並返回總覽,不會呼叫模型或透過網路修改狀態。每個 run 團隊共用一間房,
|
|
202
|
+
每個一般 session 各有一間,執行中的 review 也保持可見。點房間進入放大的
|
|
203
|
+
詳細畫面,並可返回總覽。每間房包含規劃、開發、驗證、審查與整合區。角色依
|
|
204
|
+
已知 phase 移動與播放輕量動作,並有簡短名牌與狀態;不從對話推測未知狀態。
|
|
205
|
+
白板顯示 criteria 進度、verify/review 結果與待回覆問題,`!` 標示未回答的問題。
|
|
206
|
+
點白板或進度板開啟房間工作清單,點工位或階段區查看該階段的已知工作。
|
|
207
|
+
點待回覆標記查看完整問題與現有可複製指令;同一套分頁視窗也可用
|
|
208
|
+
`L`(工作清單)、`1–5`(階段)與 `Q`(問題)開啟。
|
|
209
|
+
點角色開啟有界限且分頁的 status 詳情,包含可複製的 command。完成房間移到
|
|
210
|
+
最近完成,最多保留兩小時。支援中英文切換並記住選擇、鍵盤操作與 reduced
|
|
211
|
+
motion。一般 session 的存活狀態依 hook 活動判定,30 分鐘沒有事件就會移除
|
|
212
|
+
過期的 presence,不會讀取 native host process。Office 啟動失敗絕不使
|
|
213
|
+
hook、`run start` 或 `run status` 失敗。
|
|
214
|
+
- Evidence: Phase、snapshot、server、scene 與 CLI test 涵蓋 legacy state、彙整與
|
|
215
|
+
旁白、一般 session 登記與只開啟一次、完成後兩小時的邊界、token/Host/method
|
|
216
|
+
防護、重用與 injected clock 的 idle 結束、viewport 與房間切換、中英文,以及
|
|
217
|
+
印出的 URL。Browser read-back 檢查實際渲染的單頁布局。
|
|
218
|
+
- Positive: `一般 session 自動開啟共用 Office,尚未建立 worktree 就能看到自己的房間;進入團隊房時看到 coordinator 在驗證桌旁,白板顯示 6 項 criteria 已通過 2 項。`
|
|
219
|
+
- Negative: `在 0.0.0.0 提供 office、接受 POST、讀取 worker transcript 來產生旁白,或打包 PNG sprite sheet。`
|
package/package.json
CHANGED
|
@@ -24,7 +24,8 @@
|
|
|
24
24
|
"core",
|
|
25
25
|
"advisory",
|
|
26
26
|
"guardrails",
|
|
27
|
-
"loop"
|
|
27
|
+
"loop",
|
|
28
|
+
"run"
|
|
28
29
|
]
|
|
29
30
|
}
|
|
30
31
|
},
|
|
@@ -243,6 +244,14 @@
|
|
|
243
244
|
"stopVerification"
|
|
244
245
|
],
|
|
245
246
|
"properties": {
|
|
247
|
+
"office": {
|
|
248
|
+
"type": "object",
|
|
249
|
+
"additionalProperties": false,
|
|
250
|
+
"required": ["enabled"],
|
|
251
|
+
"properties": {
|
|
252
|
+
"enabled": { "type": "boolean" }
|
|
253
|
+
}
|
|
254
|
+
},
|
|
246
255
|
"completionGate": {
|
|
247
256
|
"type": "object",
|
|
248
257
|
"additionalProperties": false,
|