Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/promote-permission-stop-failure-events.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"agent-bundle": patch
---

Promote the `permission/request`, `permission/denied`, and `stop/failure` canonical event-route families. `permission/request` projects allow/deny decisions through the pinned PermissionRequest output contract on Claude Code and Codex (input rewrite stays fail-closed as reserved upstream); `permission/denied` and `stop/failure` are observation-only Claude families with fail-closed rejection of decision or context output. Codex permission-request wire schemas are byte-pinned from the rust-v0.147.0 tag; hosts without a documented native event carry dated `unavailable` capability rows.
3 changes: 3 additions & 0 deletions examples/rsc-agent-runtime/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,10 @@ Host/Origin allowlists mitigate DNS rebinding and cross-origin requests, but the
| `tool/failure` | `postToolUseFailure` (observe-only) | `PostToolUseFailure` (context) | Unavailable |
| `compact/before` | `preCompact` (observe-only; native `user_message` not modeled) | `PreCompact` (deny) | `PreCompact` (observe-only) |
| `compact/after` | Unavailable | `PostCompact` (observe-only) | `PostCompact` (observe-only; no summary field) |
| `permission/request` | Unavailable | `PermissionRequest` (allow/deny) | `PermissionRequest` (allow/deny) |
| `permission/denied` | Unavailable | `PermissionDenied` (observe-only) | Unavailable |
| `stop` | Supported | `Stop` | `Stop` |
| `stop/failure` | Unavailable | `StopFailure` (observe-only) | Unavailable |
| `agent/start` | `subagentStart` | `SubagentStart` | `SubagentStart` |
| `agent/stop` | `subagentStop` | `SubagentStop` | `SubagentStop` |
| `workspace/open` | Supported (observe-only; native `pluginPaths` return not modeled) | Unavailable | Unavailable |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,13 @@
"agent/stop": { "nativeEvent": "SubagentStop", "state": "supported" },
"compact/after": { "nativeEvent": "PostCompact", "state": "supported" },
"compact/before": { "nativeEvent": "PreCompact", "state": "supported" },
"permission/denied": { "nativeEvent": "PermissionDenied", "state": "supported" },
"permission/request": { "nativeEvent": "PermissionRequest", "state": "supported" },
Comment on lines +30 to +31

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Bump adapter revisions for the promoted event contracts

When an artifact produced before this change is validated after upgrading, Claude, Codex, and the unified plugin still expose their previous adapterRevision values despite now mapping and generating additional event-route contracts. matchesTargetMetadata uses that revision together with the unchanged observed version/schema metadata as its compatibility check, so the old artifact can be accepted as matching the newly registered adapter contract; bump the affected direct and unified adapter revisions as was done for the preceding event-family promotion.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed on main in #364 (merge 305161a).

"prompt/submit": { "nativeEvent": "UserPromptSubmit", "state": "supported" },
"session/end": { "nativeEvent": "SessionEnd", "state": "supported" },
"session/start": { "nativeEvent": "SessionStart", "state": "supported" },
"stop": { "nativeEvent": "Stop", "state": "supported" },
"stop/failure": { "nativeEvent": "StopFailure", "state": "supported" },
"tool/after": { "nativeEvent": "PostToolUse", "state": "supported" },
"tool/before": { "nativeEvent": "PreToolUse", "state": "supported" },
"tool/failure": { "nativeEvent": "PostToolUseFailure", "state": "supported" },
Expand Down
13 changes: 13 additions & 0 deletions packages/agent-bundle/src/adapters/capabilities/codex-0.147.0.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,19 @@
"agent/stop": { "nativeEvent": "SubagentStop", "state": "supported" },
"compact/after": { "nativeEvent": "PostCompact", "state": "supported" },
"compact/before": { "nativeEvent": "PreCompact", "state": "supported" },
"permission/denied": {
"reason": "retrieved 2026-09-02: the complete rust-v0.147.0 generated hook schema directory contains no permission-denied event.",
"state": "unavailable"
},
"permission/request": { "nativeEvent": "PermissionRequest", "state": "supported" },
"prompt/submit": { "nativeEvent": "UserPromptSubmit", "state": "supported" },
"session/end": { "nativeEvent": "SessionEnd", "state": "supported" },
"session/start": { "nativeEvent": "SessionStart", "state": "supported" },
"stop": { "nativeEvent": "Stop", "state": "supported" },
"stop/failure": {
"reason": "retrieved 2026-09-02: the complete rust-v0.147.0 generated hook schema directory contains no stop-failure event.",
"state": "unavailable"
},
"tool/after": { "nativeEvent": "PostToolUse", "state": "supported" },
"tool/before": { "nativeEvent": "PreToolUse", "state": "supported" },
"tool/failure": {
Expand Down Expand Up @@ -263,6 +272,8 @@
"liveRevision": "codex-cli 0.147.0 app-server protocol",
"matchingRelativePaths": [],
"missingPinnedRelativePaths": [
"permission-request.command.input.schema.json",
"permission-request.command.output.schema.json",
"post-compact.command.input.schema.json",
"post-compact.command.output.schema.json",
"pre-compact.command.input.schema.json",
Expand All @@ -276,6 +287,8 @@
"user-prompt-submit.command.output.schema.json"
],
"pinnedRepositorySha256": {
"permission-request.command.input.schema.json": "75c73d7a38cfc0e73ef06bd1fc506a44d25874522069ec4fb85e0bf1e7d6b8fb",
"permission-request.command.output.schema.json": "749c73245b4b6d43537c3049f76720ab1c2bd48d7e4752b744b376925b9d57a1",
"post-compact.command.input.schema.json": "d5cecd14bd2ca18605ba8209108f76291f886ffe3cb4762d70e712c148836f31",
"post-compact.command.output.schema.json": "811b7ae2a4b277cd51c9df989f347e494fa981d01e346bc2a757506e97734882",
"pre-compact.command.input.schema.json": "5728b5da4c63e1e07f2d53ac8b2adc18306cd3dc8b01b556fc422e75b32a8734",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,14 @@
"state": "unavailable"
},
"compact/before": { "nativeEvent": "preCompact", "state": "supported" },
"permission/denied": {
"reason": "retrieved 2026-09-02: the complete https://cursor.com/docs/hooks event inventory documents no permission-denied event.",
"state": "unavailable"
},
"permission/request": {
"reason": "retrieved 2026-09-02: https://cursor.com/docs/hooks documents no standalone permission-request event; pre-tool permission control is beforeShellExecution/beforeMCPExecution and is not inferred as this family per #258.",
"state": "unavailable"
},
"prompt/submit": { "nativeEvent": "beforeSubmitPrompt", "state": "supported" },
"session/end": {
"availability": {
Expand All @@ -44,6 +52,10 @@
},
"session/start": { "nativeEvent": "sessionStart", "state": "supported" },
"stop": { "nativeEvent": "stop", "state": "supported" },
"stop/failure": {
"reason": "retrieved 2026-09-02: the complete https://cursor.com/docs/hooks event inventory documents no stop-failure event.",
"state": "unavailable"
},
"tool/after": { "nativeEvent": "postToolUse", "state": "supported" },
"tool/before": { "nativeEvent": "preToolUse", "state": "supported" },
"tool/failure": { "nativeEvent": "postToolUseFailure", "state": "supported" },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,14 @@
"reason": "2026-09-02: Agent Plugins 1.0.0 defines no hooks and therefore no native PreCompact/preCompact equivalent (https://agent-plugins.org/).",
"state": "unavailable"
},
"permission/denied": {
"reason": "2026-09-02: Agent Plugins 1.0.0 defines no hooks and therefore no native PermissionDenied equivalent (https://agent-plugins.org/).",
"state": "unavailable"
},
"permission/request": {
"reason": "2026-09-02: Agent Plugins 1.0.0 defines no hooks and therefore no native PermissionRequest equivalent (https://agent-plugins.org/).",
"state": "unavailable"
},
"prompt/submit": {
"reason": "2026-09-02: Agent Plugins 1.0.0 defines no hooks and therefore no native UserPromptSubmit/beforeSubmitPrompt equivalent (https://agent-plugins.org/).",
"state": "unavailable"
Expand All @@ -30,6 +38,10 @@
},
"session/start": { "reason": "Agent Plugins 1.0.0 does not define hooks.", "state": "unavailable" },
"stop": { "reason": "Agent Plugins 1.0.0 does not define hooks.", "state": "unavailable" },
"stop/failure": {
"reason": "2026-09-02: Agent Plugins 1.0.0 defines no hooks and therefore no native StopFailure equivalent (https://agent-plugins.org/).",
"state": "unavailable"
},
"tool/after": { "reason": "Agent Plugins 1.0.0 does not define hooks.", "state": "unavailable" },
"tool/before": { "reason": "Agent Plugins 1.0.0 does not define hooks.", "state": "unavailable" },
"tool/failure": {
Expand Down
30 changes: 30 additions & 0 deletions packages/agent-bundle/src/adapters/hook-contract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,36 @@ export const createNativeEventStarter = (
: {}),
stop_hook_active: false,
});
case 'permission/request':
return deepFreeze({
...base,
tool_input: toolInput,
tool_name: toolName,
...(target === 'codex'
? {
agent_id: 'lifecycle-replay-agent',
agent_type: 'general-purpose',
model: 'default',
permission_mode: 'default',
turn_id: 'lifecycle-replay-turn',
}
: { permission_mode: 'default' }),
});
case 'permission/denied':
return deepFreeze({
...base,
permission_decision: 'deny',
permission_decision_reason: 'Lifecycle replay permission denial.',
tool_input: toolInput,
tool_name: toolName,
});
case 'stop/failure':
return deepFreeze({
...base,
error: 'Lifecycle replay API failure.',
last_assistant_message: null,
stop_hook_active: false,
});
case 'workspace/open':
return deepFreeze(target === 'cursor'
? {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"observedCliVersion": "0.147.0",
"retrievedAt": "2026-09-02",
"schemaSource": "https://github.com/openai/codex/blob/main/codex-rs/skills/src/assets/samples/plugin-creator/references/plugin-json-spec.md",
"notes": "plugin.schema.json transcribes the 2026-09-02 documented package manifest rather than a host-exported schema because Codex 0.147.0 publishes no plugin validate command. Skills stays optional so MCP-only plugins admitted by the documented packaging and submission flows validate. The generated subagent-{start,stop}.command.{input,output}.schema.json, user-prompt-submit.command.{input,output}.schema.json, session-end.command.input.schema.json, and {pre,post}-compact.command.{input,output}.schema.json evidence snapshots are byte-pinned from the rust-v0.147.0 tag at https://github.com/openai/codex/tree/rust-v0.147.0/codex-rs/hooks/schema/generated. That tag has no session-end.command.output.schema.json or tool-failure schema. Repository text files add one POSIX trailing newline; adapter-metadata.test.ts removes only that byte before comparing the authoritative upstream SHA-256 digests recorded in codex-0.147.0.json.",
"notes": "plugin.schema.json transcribes the 2026-09-02 documented package manifest rather than a host-exported schema because Codex 0.147.0 publishes no plugin validate command. Skills stays optional so MCP-only plugins admitted by the documented packaging and submission flows validate. The generated subagent-{start,stop}.command.{input,output}.schema.json, user-prompt-submit.command.{input,output}.schema.json, session-end.command.input.schema.json, {pre,post}-compact.command.{input,output}.schema.json, and permission-request.command.{input,output}.schema.json evidence snapshots are byte-pinned from the rust-v0.147.0 tag at https://github.com/openai/codex/tree/rust-v0.147.0/codex-rs/hooks/schema/generated. That tag has no session-end.command.output.schema.json and no tool-failure, permission-denied, or stop-failure schema. Repository text files add one POSIX trailing newline; adapter-metadata.test.ts removes only that byte before comparing the authoritative upstream SHA-256 digests recorded in codex-0.147.0.json.",
"schemaTightenings": {
"plugin.schema.json": [
"Top-level and author objects are closed; author admits only the documented name, email, and url fields.",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"additionalProperties": false,
"definitions": {
"NullableString": {
"type": [
"string",
"null"
]
}
},
"properties": {
"agent_id": {
"type": "string"
},
"agent_type": {
"type": "string"
},
"cwd": {
"type": "string"
},
"hook_event_name": {
"const": "PermissionRequest",
"type": "string"
},
"model": {
"type": "string"
},
"permission_mode": {
"enum": [
"default",
"acceptEdits",
"plan",
"dontAsk",
"bypassPermissions"
],
"type": "string"
},
"session_id": {
"type": "string"
},
"tool_input": true,
"tool_name": {
"type": "string"
},
"transcript_path": {
"$ref": "#/definitions/NullableString"
},
"turn_id": {
"description": "Codex extension: expose the active turn id to internal turn-scoped hooks.",
"type": "string"
}
},
"required": [
"cwd",
"hook_event_name",
"model",
"permission_mode",
"session_id",
"tool_input",
"tool_name",
"transcript_path",
"turn_id"
],
"title": "permission-request.command.input",
"type": "object"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"additionalProperties": false,
"definitions": {
"PermissionRequestBehaviorWire": {
"enum": [
"allow",
"deny"
],
"type": "string"
},
"PermissionRequestDecisionWire": {
"additionalProperties": false,
"properties": {
"behavior": {
"$ref": "#/definitions/PermissionRequestBehaviorWire"
},
"interrupt": {
"default": false,
"description": "Reserved for future short-circuiting semantics.\n\nPermissionRequest hooks currently fail closed if this field is `true`.",
"type": "boolean"
},
"message": {
"default": null,
"type": "string"
},
"updatedInput": {
"default": null,
"description": "Reserved for a future input-rewrite capability.\n\nPermissionRequest hooks currently fail closed if this field is present."
},
"updatedPermissions": {
"default": null,
"description": "Reserved for a future permission-rewrite capability.\n\nPermissionRequest hooks currently fail closed if this field is present."
}
},
"required": [
"behavior"
],
"type": "object"
},
"PermissionRequestHookSpecificOutputWire": {
"additionalProperties": false,
"properties": {
"decision": {
"allOf": [
{
"$ref": "#/definitions/PermissionRequestDecisionWire"
}
],
"default": null
},
"hookEventName": {
"const": "PermissionRequest",
"type": "string"
}
},
"required": [
"hookEventName"
],
"type": "object"
}
},
"properties": {
"continue": {
"default": true,
"type": "boolean"
},
"hookSpecificOutput": {
"allOf": [
{
"$ref": "#/definitions/PermissionRequestHookSpecificOutputWire"
}
],
"default": null
},
"stopReason": {
"default": null,
"type": "string"
},
"suppressOutput": {
"default": false,
"type": "boolean"
},
"systemMessage": {
"default": null,
"type": "string"
}
},
"title": "permission-request.command.output",
"type": "object"
}
3 changes: 3 additions & 0 deletions packages/agent-bundle/src/config/normalize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,9 @@ const hookEventForRoute: Readonly<Record<CanonicalAgentEvent, NormalizedHookEven
'tool/failure': 'toolFailure',
'compact/before': 'compactBefore',
'compact/after': 'compactAfter',
'permission/request': 'permissionRequest',
'permission/denied': 'permissionDenied',
'stop/failure': 'stopFailure',
'workspace/open': 'workspaceOpen',
});

Expand Down
3 changes: 3 additions & 0 deletions packages/agent-bundle/src/core/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ export const eventRouteOnlyHookEvents = Object.freeze([
'toolFailure',
'compactBefore',
'compactAfter',
'permissionRequest',
'permissionDenied',
'stopFailure',
] as const);

export type EventRouteOnlyHookEvent = (typeof eventRouteOnlyHookEvents)[number];
Expand Down
Loading
Loading