overlord-cli 0.2608031719.0 → 0.2608061552.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. package/README.md +1 -1
  2. package/dist/connectors/HARNESS-MATRIX.md +8 -5
  3. package/dist/connectors/README.md +3 -1
  4. package/dist/connectors/VERSION +1 -1
  5. package/dist/connectors/adapters/antigravity/plugin.json +1 -1
  6. package/dist/connectors/adapters/claude/.claude-plugin/plugin.json +1 -1
  7. package/dist/connectors/adapters/claude/CAPABILITIES.md +10 -4
  8. package/dist/connectors/adapters/claude/conformance-manifest.yaml +1 -1
  9. package/dist/connectors/adapters/claude/harness-capabilities.yaml +46 -3
  10. package/dist/connectors/adapters/codex/.codex-plugin/plugin.json +1 -1
  11. package/dist/connectors/adapters/codex/CAPABILITIES.md +7 -2
  12. package/dist/connectors/adapters/codex/conformance-manifest.yaml +1 -1
  13. package/dist/connectors/adapters/codex/harness-capabilities.yaml +32 -1
  14. package/dist/connectors/adapters/cursor/.cursor-plugin/plugin.json +1 -1
  15. package/dist/connectors/adapters/cursor/CAPABILITIES.md +9 -5
  16. package/dist/connectors/adapters/cursor/README.md +3 -2
  17. package/dist/connectors/adapters/cursor/codec/cursor.codec.yaml +71 -0
  18. package/dist/connectors/adapters/cursor/conformance-manifest.yaml +2 -1
  19. package/dist/connectors/adapters/cursor/fixtures/normalize-before-submit-prompt.json +31 -0
  20. package/dist/connectors/adapters/cursor/fixtures/normalize-post-tool-use-write.json +41 -0
  21. package/dist/connectors/adapters/cursor/fixtures/normalize-pre-tool-use-shell.json +40 -0
  22. package/dist/connectors/adapters/cursor/harness-capabilities.yaml +36 -9
  23. package/dist/connectors/core/README.md +3 -1
  24. package/dist/connectors/core/scripts/overlord-mcp.mjs +1 -1
  25. package/dist/index.js +300 -139
  26. package/dist/index.js.map +2 -2
  27. package/package.json +1 -1
package/README.md CHANGED
@@ -182,7 +182,7 @@ export OVERLORD_BACKEND_URL="https://overlord.example.com"
182
182
  export OVERLORD_USER_TOKEN="out_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
183
183
 
184
184
  # 4. Agent connector — write the local agent connector config (non-interactive).
185
- ovld agent-setup claude # one of: claude | codex | cursor | pi | all
185
+ ovld agent-setup claude # one of: claude | codex | cursor | pi | antigravity | opencode | all
186
186
  ```
187
187
 
188
188
  Then verify the backend is reachable and authenticated:
@@ -15,7 +15,7 @@ Legend: ✅ supported (fixture-proven) · ⛔ unsupported (harness cannot) ·
15
15
  | Adapter | Harness | Verified version | Shape | Tier | Binding | Decision hold |
16
16
  | --- | --- | --- | --- | --- | --- | --- |
17
17
  | [`antigravity`](adapters/antigravity/CAPABILITIES.md) | Antigravity CLI | — | `callback` | 0 (Unsupported) | ❓ unverified | ❓ unverified |
18
- | [`claude`](adapters/claude/CAPABILITIES.md) | Claude Code | `2.1.220` | `callback` | 3 (Conversational) | ✅ supported | ✅ supported |
18
+ | [`claude`](adapters/claude/CAPABILITIES.md) | Claude Code | `2.1.221` | `callback` | 3 (Conversational) | ✅ supported | ✅ supported |
19
19
  | [`codex`](adapters/codex/CAPABILITIES.md) | Codex CLI | `0.146.0` | `callback` | 3 (Conversational) | ✅ supported | ✅ supported |
20
20
  | [`cursor`](adapters/cursor/CAPABILITIES.md) | Cursor Agent CLI | `2026.07.23-e383d2b` | `callback` | 0 (Unsupported) | ❓ unverified | ✅ supported |
21
21
  | [`opencode`](adapters/opencode/CAPABILITIES.md) | OpenCode | — | `controlPlane` | 3 (Conversational) | ✅ supported | ✅ supported |
@@ -25,10 +25,10 @@ Legend: ✅ supported (fixture-proven) · ⛔ unsupported (harness cannot) ·
25
25
 
26
26
  | Capability | `antigravity` | `claude` | `codex` | `cursor` | `opencode` | `pi` |
27
27
  | --- | --- | --- | --- | --- | --- | --- |
28
- | `observe.prompt` | ❓ unverified | ✅ supported | ✅ supported | 🚧 not-implemented | 🚧 not-implemented | ✅ supported |
29
- | `observe.toolCall` | ❓ unverified | 🚧 not-implemented | ✅ supported | 🚧 not-implemented | 🚧 not-implemented | ✅ supported |
30
- | `observe.toolResult` | ❓ unverified | ✅ supported | ✅ supported | 🚧 not-implemented | ✅ supported | 🚧 not-implemented |
31
- | `observe.fileEdit` | ❓ unverified | ✅ supported | ✅ supported | 🚧 not-implemented | ✅ supported | 🚧 not-implemented |
28
+ | `observe.prompt` | ❓ unverified | ✅ supported | ✅ supported | ✅ supported | 🚧 not-implemented | ✅ supported |
29
+ | `observe.toolCall` | ❓ unverified | 🚧 not-implemented | ✅ supported | ✅ supported | 🚧 not-implemented | ✅ supported |
30
+ | `observe.toolResult` | ❓ unverified | ✅ supported | ✅ supported | ✅ supported | ✅ supported | 🚧 not-implemented |
31
+ | `observe.fileEdit` | ❓ unverified | ✅ supported | ✅ supported | ✅ supported | ✅ supported | 🚧 not-implemented |
32
32
  | `observe.sessionLifecycle` | ❓ unverified | ✅ supported | ✅ supported | 🚧 not-implemented | ✅ supported | ✅ supported |
33
33
  | `decide.shell` | ❓ unverified | ✅ supported | ✅ supported | ✅ supported | ✅ supported | ✅ supported |
34
34
  | `decide.mcp` | ❓ unverified | ✅ supported | ✅ supported | ✅ supported | ✅ supported | ✅ supported |
@@ -52,6 +52,9 @@ Legend: ✅ supported (fixture-proven) · ⛔ unsupported (harness cannot) ·
52
52
  | `claude` | `shipped-stop-hook-inert` | medium | verified | required | `agent-session-phase-3` |
53
53
  | `claude` | `unbound-session-side-effects` | medium | verified | required | `agent-session-phase-4` |
54
54
  | `claude` | `fork-and-subagent-identity-unknown` | medium | unverified | required | `taxonomy-7.6` |
55
+ | `claude` | `subagent-stop-unregistered` | medium | verified | required | `coo:585` |
56
+ | `claude` | `subagent-commits-escape-delivery-delta` | high | verified | required | `coo:585` |
57
+ | `codex` | `prompt-and-agent-hook-handlers-inert` | low | verified | accepted | — |
55
58
  | `codex` | `hooks-version-and-platform-gate` | medium | verified | required | `agent-session-phase-4` |
56
59
  | `cursor` | `reads-claude-hook-config` | high | unverified | required | `taxonomy-7.1` |
57
60
  | `cursor` | `allowlist-precedence` | medium | unverified | required | `taxonomy-7.8` |
@@ -24,7 +24,7 @@ OpenCode, Antigravity, and others). A connector is what lets an agent speak the
24
24
  Install or refresh a connector with:
25
25
 
26
26
  ```bash
27
- ovld agent-setup <agent> # claude | codex | cursor | pi | all
27
+ ovld agent-setup <agent> # claude | codex | cursor | pi | antigravity | opencode | all
28
28
  ovld doctor # verify managed files and permissions
29
29
  ```
30
30
 
@@ -38,6 +38,8 @@ connector contract version changes.
38
38
  - [Codex](adapters/codex/README.md): Codex plugin with hooks, MCP bridge, and permission warmup.
39
39
  - [Cursor](adapters/cursor/README.md): Cursor plugin with slash commands, hooks, rules, and MCP bridge.
40
40
  - [PI](adapters/pi/README.md): PI Agent skill and extension with follow-up capture and native-session resume.
41
+ - [Antigravity](adapters/antigravity/README.md): Antigravity plugin exposing the local mission workflow to Antigravity CLI/IDE sessions.
42
+ - [OpenCode](adapters/opencode/README.md): control-plane connector (Shape C) driven by `ovld agent-session sidecar` — no hook scripts.
41
43
 
42
44
  ### Extension point
43
45
 
@@ -1 +1 @@
1
- 0.3.17
1
+ 0.3.18
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://antigravity.google/schemas/v1/plugin.json",
3
3
  "name": "overlord",
4
- "version": "0.3.17",
4
+ "version": "0.3.18",
5
5
  "description": "Chat-driven access to Overlord mission protocol workflows with hook-driven activity capture.",
6
6
  "author": {
7
7
  "name": "Cooperativ",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "overlord",
3
- "version": "0.3.17",
3
+ "version": "0.3.18",
4
4
  "description": "Overlord mission protocol workflow for Claude Code — attach/update/ask/deliver, slash commands, and hook-driven activity capture.",
5
5
  "author": {
6
6
  "name": "Cooperativ",
@@ -4,9 +4,9 @@
4
4
 
5
5
  **Adapter** `claude` · **Codec** `claude` · **Integration shape** `callback` · **Capability tier** 3 (Conversational)
6
6
 
7
- **Harness version verified** `2.1.220` · **range** `>=2.1.0` · **scheme** `semver`
7
+ **Harness version verified** `2.1.221` · **range** `>=2.1.0` · **scheme** `semver`
8
8
 
9
- **Descriptor digest** `58131e0daccf7382cada7d456bfb6ca38e6da8adefe065e4c053028f0c3d8aec`
9
+ **Descriptor digest** `a90745bb59252df29006cf632a030108e68fde84f956910e02cc842833e6f8d6`
10
10
 
11
11
  > The tier is derived from passing fixtures, never authored. `unsupported` means the harness
12
12
  > cannot do it — do not attempt it. `not-implemented` means it is buildable and unbuilt: that is
@@ -83,14 +83,20 @@ Per-command `timeout` in seconds with no documented ceiling. Omitting `decision`
83
83
  | `shipped-stop-hook-inert` | medium | verified | required | `agent-session-phase-3` |
84
84
  | `unbound-session-side-effects` | medium | verified | required | `agent-session-phase-4` |
85
85
  | `fork-and-subagent-identity-unknown` | medium | unverified | required | `taxonomy-7.6` |
86
+ | `subagent-stop-unregistered` | medium | verified | required | `coo:585` |
87
+ | `subagent-commits-escape-delivery-delta` | high | verified | required | `coo:585` |
86
88
 
87
89
  **`shipped-permission-hook-inert`** — The installed PermissionRequest hook calls a `ovld protocol permission-request` subcommand that does not exist, backgrounds the call with `) & disown`, and is gated on a MISSION_ID environment variable launched sessions do not always set. It records nothing and, being backgrounded with stdout discarded, is structurally incapable of returning a decision. The shipped conformance `permissionHook` flag therefore overstates what happens today.
88
90
 
89
- **`shipped-stop-hook-inert`** — The installed Stop hook calls `ovld protocol hook-event --hook-type Stop`, which the CLI rejects, and then parses a `deliveryStatus` field nothing produces. Its mission-link footer still works.
91
+ **`shipped-stop-hook-inert`** — The installed Stop hook calls `ovld protocol hook-event --hook-type Stop`, which the CLI rejects, and then parses a `deliveryStatus` field nothing produces. Its mission-link footer still works. Confirmed again at 2.1.221 from a live agent-pod session: every entry in ~/.ovld/logs/stop-hook.log takes the `no launch mission id` branch and returns before the delivery check, because the hook gates on a `MISSION_ID` environment variable the pod does not set. The PostToolUse hook works in the same session because it resolves the mission from the per-cwd active-session manifest instead; the Stop hook should resolve it the same way.
90
92
 
91
93
  **`unbound-session-side-effects`** — NARROWED, NOT CLOSED. The new agent-session registrations gate in bash on OVERLORD_SESSION_CHANNEL_ID before spawning anything, so they are completely silent in an unbound session (proved by fixtures/agent-session-hook-unbound.json). The LEGACY PostToolUse hook still spawns `ovld protocol record-touched` and appends to ~/.ovld/logs in every session on the machine, because its scope gate lives inside the CLI rather than before the spawn (proved by fixtures/unbound-session.json). It stays installed on purpose: it is what feeds touched-file change attribution at deliver time, and removing it before the normalized path has demonstrated equivalent attribution would trade a privacy nit for a data-loss bug. Closing this means retiring the legacy hook, which is a migration, not a patch.
92
94
 
93
- **`fork-and-subagent-identity-unknown`** — Whether an in-process fork mints a new native session id, and whether subagent tool calls carry the parent's id, are both unverified. Guessing either routes a session's requests to the wrong mission.
95
+ **`fork-and-subagent-identity-unknown`** — Whether an in-process fork mints a new native session id, and whether subagent tool calls carry the parent's id, are both unverified. Guessing either routes a session's requests to the wrong mission. Partially narrowed at 2.1.221: the binary carries distinct `SubagentStart` and `SubagentStop` hook events, converts an agent-frontmatter `Stop` registration into `SubagentStop` ("subagents trigger SubagentStop"), and records `parent_session_id` and `agent_type` as separate fields from `session_id`. That the harness distinguishes the two identities is now established; which value lands in a subagent hook payload's `session_id` is still not, and that is the part binding depends on. Read it from a real subagent payload before changing any binding code.
96
+
97
+ **`subagent-stop-unregistered`** — The adapter registers `Stop` and no subagent-lifecycle event. Claude Code 2.1.221 fires `SubagentStop` — not `Stop` — when a subagent finishes, and subagents now run in the background past the parent's turn boundary, so the shipped Stop registration is guaranteed not to run for work a subagent completes after the parent stopped. Nothing is lost today because that registration is already inert (see `shipped-stop-hook-inert`), which is exactly why the fix is to repair the Stop path first and only then decide whether the repaired body should also ride `SubagentStop`. Registering the current inert script on a second event would add a spawn per subagent completion and buy nothing.
98
+
99
+ **`subagent-commits-escape-delivery-delta`** — Delivery change attribution is computed from `git status --porcelain` against the attach baseline (cli/src/vcs.ts `readChangedFiles` / `filterRunAttributableChanges`); nothing in that path consults HEAD or the commit graph. A background subagent that commits, merges, or opens a PR for its own work therefore removes those files from the worktree delta, and they vanish from the delivery report entirely — not flagged, not skipped, silently absent. The PostToolUse touched-files log is unaffected (its key is `sha256(abspath(cwd) + "\0" + MISSION_ID)`, so a subagent sharing the cwd writes to the same log), which means the evidence that the edit happened survives while the delta that decides coverage does not. Closing this means teaching the delta about commits made since the baseline, which is a CLI change with branch and worktree semantics to settle, not a hook edit.
94
100
 
95
101
  ## Native decision shape
96
102
 
@@ -15,7 +15,7 @@ connector:
15
15
  harnessCapabilities:
16
16
  path: connectors/adapters/claude/harness-capabilities.yaml
17
17
  schemaVersion: 1
18
- digest: "58131e0daccf7382cada7d456bfb6ca38e6da8adefe065e4c053028f0c3d8aec"
18
+ digest: "a90745bb59252df29006cf632a030108e68fde84f956910e02cc842833e6f8d6"
19
19
  capabilities:
20
20
  - followUpHook
21
21
  - permissionHook
@@ -14,7 +14,7 @@ decisionCodecSpec: codec/claude.decision-codec.yaml
14
14
 
15
15
  harness:
16
16
  name: Claude Code
17
- verifiedVersion: '2.1.220'
17
+ verifiedVersion: '2.1.221'
18
18
  versionRange: '>=2.1.0'
19
19
  versionScheme: semver
20
20
 
@@ -184,7 +184,12 @@ hazards:
184
184
  summary: >-
185
185
  The installed Stop hook calls `ovld protocol hook-event --hook-type Stop`, which the CLI
186
186
  rejects, and then parses a `deliveryStatus` field nothing produces. Its mission-link
187
- footer still works.
187
+ footer still works. Confirmed again at 2.1.221 from a live agent-pod session: every entry
188
+ in ~/.ovld/logs/stop-hook.log takes the `no launch mission id` branch and returns before
189
+ the delivery check, because the hook gates on a `MISSION_ID` environment variable the pod
190
+ does not set. The PostToolUse hook works in the same session because it resolves the
191
+ mission from the per-cwd active-session manifest instead; the Stop hook should resolve it
192
+ the same way.
188
193
  verification: verified
189
194
  mitigation: required
190
195
  trackedAs: agent-session-phase-3
@@ -210,10 +215,48 @@ hazards:
210
215
  summary: >-
211
216
  Whether an in-process fork mints a new native session id, and whether subagent tool
212
217
  calls carry the parent's id, are both unverified. Guessing either routes a session's
213
- requests to the wrong mission.
218
+ requests to the wrong mission. Partially narrowed at 2.1.221: the binary carries
219
+ distinct `SubagentStart` and `SubagentStop` hook events, converts an agent-frontmatter
220
+ `Stop` registration into `SubagentStop` ("subagents trigger SubagentStop"), and records
221
+ `parent_session_id` and `agent_type` as separate fields from `session_id`. That the
222
+ harness distinguishes the two identities is now established; which value lands in a
223
+ subagent hook payload's `session_id` is still not, and that is the part binding depends
224
+ on. Read it from a real subagent payload before changing any binding code.
214
225
  verification: unverified
215
226
  mitigation: required
216
227
  trackedAs: taxonomy-7.6
228
+ - id: subagent-stop-unregistered
229
+ severity: medium
230
+ summary: >-
231
+ The adapter registers `Stop` and no subagent-lifecycle event. Claude Code 2.1.221 fires
232
+ `SubagentStop` — not `Stop` — when a subagent finishes, and subagents now run in the
233
+ background past the parent's turn boundary, so the shipped Stop registration is
234
+ guaranteed not to run for work a subagent completes after the parent stopped. Nothing is
235
+ lost today because that registration is already inert (see `shipped-stop-hook-inert`),
236
+ which is exactly why the fix is to repair the Stop path first and only then decide
237
+ whether the repaired body should also ride `SubagentStop`. Registering the current
238
+ inert script on a second event would add a spawn per subagent completion and buy
239
+ nothing.
240
+ verification: verified
241
+ mitigation: required
242
+ trackedAs: coo:585
243
+ - id: subagent-commits-escape-delivery-delta
244
+ severity: high
245
+ summary: >-
246
+ Delivery change attribution is computed from `git status --porcelain` against the attach
247
+ baseline (cli/src/vcs.ts `readChangedFiles` / `filterRunAttributableChanges`); nothing in
248
+ that path consults HEAD or the commit graph. A background subagent that commits, merges,
249
+ or opens a PR for its own work therefore removes those files from the worktree delta, and
250
+ they vanish from the delivery report entirely — not flagged, not skipped, silently
251
+ absent. The PostToolUse touched-files log is unaffected (its key is
252
+ `sha256(abspath(cwd) + "\0" + MISSION_ID)`, so a subagent sharing the cwd writes to the
253
+ same log), which means the evidence that the edit happened survives while the delta that
254
+ decides coverage does not. Closing this means teaching the delta about commits made since
255
+ the baseline, which is a CLI change with branch and worktree semantics to settle, not a
256
+ hook edit.
257
+ verification: verified
258
+ mitigation: required
259
+ trackedAs: coo:585
217
260
 
218
261
  decisionShape:
219
262
  codec: claude
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "overlord",
3
- "version": "0.3.17",
3
+ "version": "0.3.18",
4
4
  "description": "Chat-driven access to Overlord mission protocol workflows with hook-driven activity capture.",
5
5
  "author": {
6
6
  "name": "Cooperativ",
@@ -6,7 +6,7 @@
6
6
 
7
7
  **Harness version verified** `0.146.0` · **range** `>=0.124.0` · **scheme** `semver`
8
8
 
9
- **Descriptor digest** `c8cb9aabe2beac3777c5f565241079f068a4672509e427c760813e2341a0ed78`
9
+ **Descriptor digest** `ad12a2a5006966b0d00f074290a6d8d2396c23343305fb8504d6d489313dacc7`
10
10
 
11
11
  > The tier is derived from passing fixtures, never authored. `unsupported` means the harness
12
12
  > cannot do it — do not attempt it. `not-implemented` means it is buildable and unbuilt: that is
@@ -64,6 +64,8 @@ Per-hook timeouts in seconds; the documentation gives a 600-second default. The
64
64
 
65
65
  ### Capability notes
66
66
 
67
+ - **`observe.toolCall`** — PreToolUse fires for MCP tool calls, not only shell and patch tools. Re-verified against the installed 0.146.0 binary and the matching upstream tag: the upstream suite `hooks_mcp.rs` (under codex-rs/core/tests/suite) proves a PreToolUse hook can both deny and rewrite an MCP call before it executes, and the binary carries a distinct "Tool call blocked by PreToolUse hook: {}. Tool: {}" message alongside the shell-only one. `tool_name` is the full MCP identifier (`mcp__<server>__<tool>`) and the matcher matches that string, so a matcher written for a bare tool name will silently never fire.
68
+ - **`observe.toolResult`** — Also covers MCP tool results, on the same 0.146.0 evidence as `observe.toolCall`. What PostToolUse cannot do is rewrite one: the binary rejects a returned `updatedMCPToolOutput` as unsupported. Observation of an MCP result is therefore reliable; mutation of it is not.
67
69
  - **`decide.universal`** — Codex PermissionRequest covers Bash, apply_patch/Edit/Write, and MCP tool names. The codec emits only documented allow/deny behavior and deliberately never emits reserved `updatedInput`, `updatedPermissions`, or `interrupt` fields.
68
70
  - **`answer.structuredQuestion`** — `codex app-server` exposes ToolRequestUserInput with per-option label/description and an autoResolutionMs — an ACP-shaped structured question in everything but name. It is unreachable until the single-subscriber question is settled.
69
71
  - **`inject.midTurn`** — ThreadInjectItems/TurnSteer exist on the experimental app-server surface only. The binary carries "expected exactly one client subscribed to the thread, found {}", so if the TUI is that one client Overlord cannot also subscribe and this path is closed.
@@ -74,12 +76,15 @@ Per-hook timeouts in seconds; the documentation gives a 600-second default. The
74
76
  | --- | --- | --- | --- | --- |
75
77
  | `two-integration-surfaces-could-disagree` | high | verified | implemented | fixture `fixtures/agent-session-hooks.json` |
76
78
  | `shipped-permission-hook-inert` | high | verified | implemented | fixture `fixtures/agent-session-hooks.json` |
79
+ | `prompt-and-agent-hook-handlers-inert` | low | verified | accepted | — |
77
80
  | `hooks-version-and-platform-gate` | medium | verified | required | `agent-session-phase-4` |
78
81
 
79
82
  **`two-integration-surfaces-could-disagree`** — Hooks and `codex app-server` can both answer a permission. A session driven by both would race in a way no revision CAS can settle, so exactly one surface must be chosen and documented before either is built.
80
83
 
81
84
  **`shipped-permission-hook-inert`** — The former PermissionRequest hook called a nonexistent protocol command in the background. The managed registration now invokes the fixed request runtime directly.
82
85
 
86
+ **`prompt-and-agent-hook-handlers-inert`** — Codex 0.146.0 accepts three hook handler types — `command`, `prompt`, and `agent` — but only `command` runs. Hook discovery parses `prompt` and `agent` and then skips them with a "not supported yet" warning, so a manifest that used either would install cleanly, list in `hooks`, and never execute. The connector must keep emitting `command` handlers only, and must not read the presence of these variants in the schema as availability. `command` is a shell command line run through `$SHELL -lc` (`/bin/sh` when SHELL is unset) — it is not restricted to Bash and not restricted to shell scripting, since the command may invoke any interpreter.
87
+
83
88
  **`hooks-version-and-platform-gate`** — Hooks were experimental behind `features.codex_hooks` before v0.124. The connector requires Codex >=0.124; installations outside that range must be treated as unavailable.
84
89
 
85
90
  ## Native decision shape
@@ -90,7 +95,7 @@ Per-hook timeouts in seconds; the documentation gives a 600-second default. The
90
95
  - **Deny fixture** `fixtures/decision-codec.json`
91
96
  - **Defer fixture** `fixtures/decision-codec.json`
92
97
 
93
- PermissionRequest accepts the documented hookSpecificOutput decision.behavior allow/deny shape. `ask` remains a deliberate no-output defer so Codex shows its own approval prompt. The adapter never emits updatedInput, updatedPermissions, or interrupt.
98
+ PermissionRequest accepts the documented hookSpecificOutput decision.behavior allow/deny shape. `ask` remains a deliberate no-output defer so Codex shows its own approval prompt. The adapter never emits updatedInput, updatedPermissions, or interrupt. The reserved list is not symmetric across events at 0.146.0: PermissionRequest rejects `updatedInput` as unsupported, while PreToolUse accepts it together with `permissionDecision: "allow"` and will rewrite the call. Keeping `updatedInput` on `neverSend` stays a deliberate policy choice — silently editing a tool call a user approved is not a decision Overlord should make — and not an inference about what the harness can do.
94
99
 
95
100
  ## Unbound-session negative test
96
101
 
@@ -15,7 +15,7 @@ connector:
15
15
  harnessCapabilities:
16
16
  path: connectors/adapters/codex/harness-capabilities.yaml
17
17
  schemaVersion: 1
18
- digest: "c8cb9aabe2beac3777c5f565241079f068a4672509e427c760813e2341a0ed78"
18
+ digest: "ad12a2a5006966b0d00f074290a6d8d2396c23343305fb8504d6d489313dacc7"
19
19
  capabilities:
20
20
  - followUpHook
21
21
  - permissionHook
@@ -52,10 +52,23 @@ capabilities:
52
52
  status: supported
53
53
  native: PreToolUse
54
54
  fixtures: [fixtures/normalize-pre-tool-use.json]
55
+ notes: >-
56
+ PreToolUse fires for MCP tool calls, not only shell and patch tools. Re-verified against
57
+ the installed 0.146.0 binary and the matching upstream tag: the upstream suite
58
+ `hooks_mcp.rs` (under codex-rs/core/tests/suite) proves a PreToolUse hook can both deny
59
+ and rewrite an MCP call before it executes, and the binary carries a distinct
60
+ "Tool call blocked by PreToolUse hook: {}.
61
+ Tool: {}" message alongside the shell-only one. `tool_name` is the full MCP identifier
62
+ (`mcp__<server>__<tool>`) and the matcher matches that string, so a matcher written for a
63
+ bare tool name will silently never fire.
55
64
  observe.toolResult:
56
65
  status: supported
57
66
  native: PostToolUse
58
67
  fixtures: [fixtures/normalize-post-tool-use.json]
68
+ notes: >-
69
+ Also covers MCP tool results, on the same 0.146.0 evidence as `observe.toolCall`. What
70
+ PostToolUse cannot do is rewrite one: the binary rejects a returned `updatedMCPToolOutput`
71
+ as unsupported. Observation of an MCP result is therefore reliable; mutation of it is not.
59
72
  observe.fileEdit:
60
73
  status: supported
61
74
  native: PostToolUse
@@ -144,6 +157,19 @@ hazards:
144
157
  verification: verified
145
158
  mitigation: implemented
146
159
  fixture: fixtures/agent-session-hooks.json
160
+ - id: prompt-and-agent-hook-handlers-inert
161
+ severity: low
162
+ summary: >-
163
+ Codex 0.146.0 accepts three hook handler types — `command`, `prompt`, and `agent` — but
164
+ only `command` runs. Hook discovery parses `prompt` and `agent` and then skips them with
165
+ a "not supported yet" warning, so a manifest that used either would install cleanly,
166
+ list in `hooks`, and never execute. The connector must keep emitting `command` handlers
167
+ only, and must not read the presence of these variants in the schema as availability.
168
+ `command` is a shell command line run through `$SHELL -lc` (`/bin/sh` when SHELL is
169
+ unset) — it is not restricted to Bash and not restricted to shell scripting, since the
170
+ command may invoke any interpreter.
171
+ verification: verified
172
+ mitigation: accepted
147
173
  - id: hooks-version-and-platform-gate
148
174
  severity: medium
149
175
  summary: >-
@@ -162,7 +188,12 @@ decisionShape:
162
188
  notes: >-
163
189
  PermissionRequest accepts the documented hookSpecificOutput decision.behavior allow/deny
164
190
  shape. `ask` remains a deliberate no-output defer so Codex shows its own approval prompt.
165
- The adapter never emits updatedInput, updatedPermissions, or interrupt.
191
+ The adapter never emits updatedInput, updatedPermissions, or interrupt. The reserved list
192
+ is not symmetric across events at 0.146.0: PermissionRequest rejects `updatedInput` as
193
+ unsupported, while PreToolUse accepts it together with `permissionDecision: "allow"` and
194
+ will rewrite the call. Keeping `updatedInput` on `neverSend` stays a deliberate policy
195
+ choice — silently editing a tool call a user approved is not a decision Overlord should
196
+ make — and not an inference about what the harness can do.
166
197
 
167
198
  unboundSessionFixture: fixtures/unbound-session.json
168
199
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "overlord",
3
- "version": "0.3.17",
3
+ "version": "0.3.18",
4
4
  "description": "Overlord mission protocol workflow for Cursor — attach/update/ask/deliver, slash commands, MCP bridge, and follow-up capture.",
5
5
  "author": {
6
6
  "name": "Cooperativ",
@@ -6,7 +6,7 @@
6
6
 
7
7
  **Harness version verified** `2026.07.23-e383d2b` · **range** `>=2026.07.01` · **scheme** `calendar`
8
8
 
9
- **Descriptor digest** `5241e4ab23c78af568a9cb06faa78486722bb21b34c96fa8589da02653e1a390`
9
+ **Descriptor digest** `4f6c4bb7a8c3637a6fc3ddd31e0ea98c8fd354badbb61117c3263561ec17ff74`
10
10
 
11
11
  > The tier is derived from passing fixtures, never authored. `unsupported` means the harness
12
12
  > cannot do it — do not attempt it. `not-implemented` means it is buildable and unbuilt: that is
@@ -24,6 +24,7 @@
24
24
  | Evidence | tracked as `agent-session-verify-cursor-binding` |
25
25
 
26
26
  Hook input carries `conversation_id` and an optional `session_id`. Which of the two is stable across a resumed conversation, and whether either appears in the environment of a tool subprocess, were not established. Binding on the wrong one silently misattributes a session, so this must be settled by fixture before the adapter binds on it.
27
+ This is now the *only* thing holding Cursor at tier 0. Four `observe.*` capabilities and three `decide.*` capabilities are fixture-proven, and `inject.turnBoundary` with them — enough for tier 3 on every axis the tier derivation reads except this one. Recording a real Cursor hook payload across a resume, and flipping this node, is therefore a single-session piece of work with a three-tier payoff, not an incremental improvement. Note that the shipped prompt hook and `codec/cursor.codec.yaml` both already treat `conversation_id` as the primary identity; if the recorded evidence says `session_id` is the stable one, both must change together or one session splits into two.
27
28
 
28
29
  The native session id is a **correlation alias only**. Authorization and mission scope come
29
30
  from the verified channel/session credential; neither the working directory nor an unverified
@@ -45,10 +46,10 @@ The 60-second per-script default is an order of magnitude below any window sized
45
46
 
46
47
  | Capability | Status | Native | Evidence |
47
48
  | --- | --- | --- | --- |
48
- | `observe.prompt` | 🚧 not-implemented | `beforeSubmitPrompt` | tracked as `agent-session-phase-1` |
49
- | `observe.toolCall` | 🚧 not-implemented | `preToolUse` | tracked as `agent-session-phase-1` |
50
- | `observe.toolResult` | 🚧 not-implemented | `postToolUse` | tracked as `agent-session-phase-1` |
51
- | `observe.fileEdit` | 🚧 not-implemented | `afterFileEdit` | tracked as `agent-session-phase-1` |
49
+ | `observe.prompt` | ✅ supported | `beforeSubmitPrompt` | fixtures: `fixtures/normalize-before-submit-prompt.json` |
50
+ | `observe.toolCall` | ✅ supported | `preToolUse` | fixtures: `fixtures/normalize-pre-tool-use-shell.json` |
51
+ | `observe.toolResult` | ✅ supported | `postToolUse` | fixtures: `fixtures/normalize-post-tool-use-write.json` |
52
+ | `observe.fileEdit` | ✅ supported | `postToolUse` | fixtures: `fixtures/normalize-post-tool-use-write.json` |
52
53
  | `observe.sessionLifecycle` | 🚧 not-implemented | `sessionStart` | tracked as `agent-session-phase-1` |
53
54
  | `decide.shell` | ✅ supported | `beforeShellExecution` | fixtures: `fixtures/permission-request.json`, `fixtures/decision-codec.json` |
54
55
  | `decide.mcp` | ✅ supported | `beforeMCPExecution` | fixtures: `fixtures/permission-request.json`, `fixtures/decision-codec.json` |
@@ -65,6 +66,9 @@ The 60-second per-script default is an order of magnitude below any window sized
65
66
 
66
67
  ### Capability notes
67
68
 
69
+ - **`observe.toolCall`** — preToolUse carries both an observational and a decision registration, rendered as two different action scripts (`event` and `request`). The observational one cannot allow, deny, or delay a call — the action is fixed at install time, so neither registration can become the other.
70
+ - **`observe.fileEdit`** — Carried on postToolUse rather than on Cursor's dedicated `afterFileEdit`, for the same reason Claude carries it on PostToolUse: one registration then covers every mutation, including the ones a shell command makes, and the split into `file.edited` happens where the normalized tool name is already known. `afterFileEdit` remains available if a future need appears for edit-level granularity the tool-level event cannot express.
71
+ - **`observe.sessionLifecycle`** — Cursor fires the event, but no shipped code in this repo has ever read one of its payloads, so the field carrying the equivalent of Claude's `source` is unknown. A codec rule on a guessed path would not fail loudly — an unresolved path yields a card with no detail — so this stays unbuilt until a real payload is recorded, rather than being claimed on an assumed field name.
68
72
  - **`inject.turnBoundary`** — `stop` returns `followup_message`, a first-class continuation channel. It can only ever report Queued(turn-boundary), never Delivered, and the UI must say so.
69
73
  - **`inject.nextTurn`** — Cursor's followup_message is scheduled for the next turn; the same path covers turn-boundary and next-turn naming.
70
74
  - **`terminal.statusSurface`** — Whether Cursor exposes an in-TUI status surface equivalent to Claude's statusMessage was not established.
@@ -29,7 +29,7 @@ From an OpenOverlord checkout:
29
29
  ovld agent-setup cursor
30
30
  ```
31
31
 
32
- This copies managed plugin files to `~/.cursor/plugins/local/overlord`, merges Overlord's prompt, permission, tool-result, and stop hooks into `~/.cursor/hooks.json`, and adds a `Shell(ovld protocol:*)` permission allow rule to `~/.cursor/settings.json` when missing. Existing hooks and settings are preserved.
32
+ This copies managed plugin files to `~/.cursor/plugins/local/overlord`, merges Overlord's prompt, permission, tool-call, tool-result, agent-session observation, and stop hooks into `~/.cursor/hooks.json`, and adds a `Shell(ovld protocol:*)` permission allow rule to `~/.cursor/settings.json` when missing. Existing hooks and settings are preserved.
33
33
 
34
34
  Re-run safely any time the connector contract version changes:
35
35
 
@@ -82,6 +82,7 @@ This connector is intentionally reviewable against the four connector layers in
82
82
  Cursor keeps its native authority.
83
83
  - `hooks/overlord-post-tool-use.sh` — records `postToolUse` file edits and shell-mediated changes for exact per-session delivery attribution.
84
84
  - `hooks/overlord-stop.sh` — checks pending delivery through `ovld protocol hook-event`; when needed, it asks Cursor to continue once so the agent can finish delivery.
85
+ - `scripts/agent-session-event.sh` — **generated**, like the MCP shim: rendered from `connectors/core/scripts/agent-session-hook.sh` with the adapter key and the fixed `event` action substituted at setup time. Registered on `beforeSubmitPrompt`, `preToolUse`, and `postToolUse`, it publishes normalized activity events on the session channel through `codec/cursor.codec.yaml`. It is a separate registration from the protocol hooks above rather than a line added to them, because those speak the older `ovld protocol` surface, and because the scope gate that makes an unbound session cost nothing has to be the script's own first line. `sessionStart` is deliberately unregistered: no recorded Cursor payload establishes its field names, and a codec rule on a guessed path degrades silently rather than failing.
85
86
  - `rules/overlord-local.mdc` — always-on workflow rules for Cursor sessions.
86
87
  - `mcp.json` + `scripts/overlord-mcp.mjs` — MCP bridge to common `ovld protocol` operations. The shim is **generated**: it is rendered from `connectors/core/scripts/overlord-mcp.mjs` at setup time with the adapter key substituted, and there is no copy in this directory to edit.
87
88
  - `conformance-manifest.yaml` — connector conformance declaration for the Overlord contract.
@@ -96,4 +97,4 @@ The shared Connector Core source lives at `connectors/core/overlord-mission`. `o
96
97
 
97
98
  See the [adapter capability matrix](../../README.md#adapter-capability-matrix) for how this compares across adapters.
98
99
 
99
- Cursor's generic `preToolUse` hook is intentionally not used for permission capture. Cursor currently documents that `ask` is not enforced for `preToolUse`; the narrower shell and MCP hooks expose the native permission-decision schema without changing the user's policy. Cursor also documents hook execution as fail-open by default, so Overlord telemetry failures do not strand the agent.
100
+ Cursor's generic `preToolUse` hook is intentionally not used for *permission capture* — it is registered for observation only, which is a different action script and cannot allow, deny, or delay a call. Cursor currently documents that `ask` is not enforced for `preToolUse`; the narrower shell and MCP hooks expose the native permission-decision schema without changing the user's policy. Cursor also documents hook execution as fail-open by default, so Overlord telemetry failures do not strand the agent.
@@ -0,0 +1,71 @@
1
+ # Cursor Agent CLI — agent-session event codec.
2
+ #
3
+ # Cursor already had a *decision* codec (`cursor.decision-codec.yaml`) and no event codec, which
4
+ # is the whole reason every `observe.*` capability read `not-implemented`: the adapter could hold
5
+ # and answer a permission request, but nothing it saw ever reached the activity feed.
6
+ #
7
+ # Field names here are not guessed. Each one is already load-bearing somewhere in the shipped
8
+ # adapter, which is the only evidence standard that survives not being able to run the harness:
9
+ #
10
+ # `hook_event_name` / `event` — both declared in cursor.decision-codec.yaml
11
+ # `cwd` — same
12
+ # `tool_name`, `tool_input` — same (preToolUse), and `tool_input.file_path` is what the
13
+ # shipped postToolUse hook feeds to `ovld protocol record-touched`
14
+ # `prompt` (then `message`) — read by hooks/overlord-user-prompt-submit.sh
15
+ # `conversation_id` — read by the same hook, and persisted as the native session alias
16
+ #
17
+ # `sessionStart` is deliberately absent. Cursor has the event, but no shipped code in this repo
18
+ # has ever read one of its payloads, so its `source`-equivalent field name is unknown. Declaring
19
+ # a rule on a guessed field would not fail loudly — an unresolved path just produces a card with
20
+ # no detail — so `observe.sessionLifecycle` stays `not-implemented` until a real payload is read.
21
+ codecVersion: 1
22
+ adapter: cursor
23
+
24
+ # Cursor is the adapter that forced the plural form: `hook_event_name` is its Claude-compatible
25
+ # name and `event` its own. Order matters only in that the first non-empty one wins; both are
26
+ # treated as the same authority.
27
+ eventNamePaths:
28
+ - hook_event_name
29
+ - event
30
+
31
+ # Correlation only. `conversation_id` is the id the shipped prompt hook already persists as this
32
+ # session's external alias, so using anything else here would split one conversation across two
33
+ # identities. Neither value authorizes anything — the channel credential does.
34
+ nativeSessionPaths:
35
+ - conversation_id
36
+ - session_id
37
+
38
+ # Used solely to relativize file paths, so a card can say `src/app.ts` rather than disclose
39
+ # someone's home directory layout.
40
+ projectRootPaths:
41
+ - cwd
42
+
43
+ events:
44
+ # Cursor's composer prompt. Same deliberate exception as every other adapter: prompt text is
45
+ # the one raw user content that flows, because showing follow-up prompts is what the activity
46
+ # feed is for. Secret redaction still runs over it.
47
+ - native: beforeSubmitPrompt
48
+ kind: prompt.submitted
49
+ origin: user
50
+ promptPath: prompt
51
+
52
+ # Fires before the call runs. This rule is observational only — the decision path for the same
53
+ # event lives in cursor.decision-codec.yaml and is reached through a different action script,
54
+ # so nothing here can allow, deny, or delay a tool call.
55
+ - native: preToolUse
56
+ kind: tool.called
57
+ origin: agent
58
+ toolPath: tool_name
59
+ inputPath: tool_input
60
+ callIdPath: call_id
61
+
62
+ # Fires after the tool has already run, which makes it observational by construction. A
63
+ # file-mutating tool becomes `file.edited` instead, so the feed can render a change timeline
64
+ # without the reader having to know which Cursor tool names mutate files.
65
+ - native: postToolUse
66
+ kind: tool.completed
67
+ origin: agent
68
+ toolPath: tool_name
69
+ inputPath: tool_input
70
+ callIdPath: call_id
71
+ fileEditKind: file.edited
@@ -15,7 +15,7 @@ connector:
15
15
  harnessCapabilities:
16
16
  path: connectors/adapters/cursor/harness-capabilities.yaml
17
17
  schemaVersion: 1
18
- digest: "5241e4ab23c78af568a9cb06faa78486722bb21b34c96fa8589da02653e1a390"
18
+ digest: "4f6c4bb7a8c3637a6fc3ddd31e0ea98c8fd354badbb61117c3263561ec17ff74"
19
19
  capabilities:
20
20
  - followUpHook
21
21
  - stopHook
@@ -47,6 +47,7 @@ connector:
47
47
  - "hooks/overlord-permission-request.sh"
48
48
  - "hooks/overlord-post-tool-use.sh"
49
49
  - "hooks/overlord-stop.sh"
50
+ - "scripts/agent-session-event.sh"
50
51
  - "mcp.json"
51
52
  - "prompt-wrapper.md"
52
53
  - "rules/overlord-local.mdc"
@@ -0,0 +1,31 @@
1
+ {
2
+ "fixtureVersion": 1,
3
+ "kind": "normalized-event",
4
+ "description": "A Cursor beforeSubmitPrompt body. Proves observe.prompt: the composer prompt becomes prompt.submitted correlated to conversation_id, and the credential pasted into the prompt is redacted before the text is persisted. It also pins the `hook_event_name` spelling of the event name.",
5
+ "codec": "codec/cursor.codec.yaml",
6
+ "occurredAt": "2026-01-01T00:00:00.000Z",
7
+ "payload": {
8
+ "conversation_id": "conv_7f3a91c2",
9
+ "hook_event_name": "beforeSubmitPrompt",
10
+ "cwd": "/repo",
11
+ "prompt": "Re-run the migration against staging, my token is out_live_9f3ab21c7d"
12
+ },
13
+ "expect": {
14
+ "eventId": "ev_735b2ab46b295c9b6fbde04faa4ce9cf",
15
+ "producerSequence": null,
16
+ "occurredAt": "2026-01-01T00:00:00.000Z",
17
+ "kind": "prompt.submitted",
18
+ "severity": "info",
19
+ "nativeEvent": "beforeSubmitPrompt",
20
+ "nativeTurnId": null,
21
+ "nativeCallId": null,
22
+ "subagentId": null,
23
+ "correlationId": "conv_7f3a91c2",
24
+ "origin": "user",
25
+ "summary": "Re-run the migration against staging, my token is [redacted]",
26
+ "payload": {
27
+ "promptText": "Re-run the migration against staging, my token is [redacted]"
28
+ }
29
+ },
30
+ "mustNotContain": ["out_live_9f3ab21c7d"]
31
+ }
@@ -0,0 +1,41 @@
1
+ {
2
+ "fixtureVersion": 1,
3
+ "kind": "normalized-event",
4
+ "description": "A Cursor postToolUse body for a Write call. Proves observe.toolResult and observe.fileEdit: the event becomes file.edited with a repo-relative path, and the file contents — deliberately carrying credentials — appear nowhere in the envelope. It is also the fixture that pins the `event` spelling of the event name, which is why the codec declares eventNamePaths rather than a single path.",
5
+ "codec": "codec/cursor.codec.yaml",
6
+ "occurredAt": "2026-01-01T00:00:00.000Z",
7
+ "payload": {
8
+ "conversation_id": "conv_7f3a91c2",
9
+ "event": "postToolUse",
10
+ "cwd": "/repo",
11
+ "tool_name": "Write",
12
+ "call_id": "call_2b81",
13
+ "tool_input": {
14
+ "file_path": "/repo/config/secrets.ts",
15
+ "content": "export const AWS_KEY = 'AKIAIOSFODNN7EXAMPLE';\nexport const DB = 'postgres://user:hunter2@db/app';\n"
16
+ }
17
+ },
18
+ "expect": {
19
+ "eventId": "ev_005e149f289472f0bb9c94aea6c5cfa0",
20
+ "producerSequence": null,
21
+ "occurredAt": "2026-01-01T00:00:00.000Z",
22
+ "kind": "file.edited",
23
+ "severity": "info",
24
+ "nativeEvent": "postToolUse",
25
+ "nativeTurnId": null,
26
+ "nativeCallId": "call_2b81",
27
+ "subagentId": null,
28
+ "correlationId": "conv_7f3a91c2",
29
+ "origin": "agent",
30
+ "summary": "Write a file: config/secrets.ts: 99 bytes",
31
+ "payload": {
32
+ "tool": "write",
33
+ "action": "Write a file",
34
+ "subject": "config/secrets.ts",
35
+ "detail": "99 bytes",
36
+ "paths": ["config/secrets.ts"],
37
+ "formatterVersion": 1
38
+ }
39
+ },
40
+ "mustNotContain": ["AKIAIOSFODNN7EXAMPLE", "hunter2", "postgres://", "/repo/config"]
41
+ }
@@ -0,0 +1,40 @@
1
+ {
2
+ "fixtureVersion": 1,
3
+ "kind": "normalized-event",
4
+ "description": "A Cursor preToolUse body for a Shell call. Proves observe.toolCall: the event becomes tool.called with the destructive risk marker derived on the machine, and the raw command survives only as a bounded detail. This is the observational path for preToolUse; the decision path for the same native event is proven separately by fixtures/decision-codec.json and reached through a different action script.",
5
+ "codec": "codec/cursor.codec.yaml",
6
+ "occurredAt": "2026-01-01T00:00:00.000Z",
7
+ "payload": {
8
+ "conversation_id": "conv_7f3a91c2",
9
+ "hook_event_name": "preToolUse",
10
+ "cwd": "/repo",
11
+ "tool_name": "Shell",
12
+ "call_id": "call_44c0",
13
+ "tool_input": {
14
+ "command": "rm -rf ./build && yarn build"
15
+ }
16
+ },
17
+ "expect": {
18
+ "eventId": "ev_ad3ad02aaa00e3b1da140996b421f7cb",
19
+ "producerSequence": null,
20
+ "occurredAt": "2026-01-01T00:00:00.000Z",
21
+ "kind": "tool.called",
22
+ "severity": "info",
23
+ "nativeEvent": "preToolUse",
24
+ "nativeTurnId": null,
25
+ "nativeCallId": "call_44c0",
26
+ "subagentId": null,
27
+ "correlationId": "conv_7f3a91c2",
28
+ "origin": "agent",
29
+ "summary": "Run a shell command: rm -rf ./build && yarn build [destructive]",
30
+ "payload": {
31
+ "tool": "shell",
32
+ "action": "Run a shell command",
33
+ "subject": "rm",
34
+ "detail": "rm -rf ./build && yarn build",
35
+ "riskMarkers": ["destructive"],
36
+ "formatterVersion": 1
37
+ }
38
+ },
39
+ "mustNotContain": ["/repo/build"]
40
+ }