start of hooks engine (#13276)

(Experimental)

This PR adds a first MVP for hooks, with SessionStart and Stop

The core design is:

- hooks live in a dedicated engine under codex-rs/hooks
- each hook type has its own event-specific file
- hook execution is synchronous and blocks normal turn progression while
running
- matching hooks run in parallel, then their results are aggregated into
a normalized HookRunSummary

On the AppServer side, hooks are exposed as operational metadata rather
than transcript-native items:

- new live notifications: hook/started, hook/completed
- persisted/replayed hook results live on Turn.hookRuns
- we intentionally did not add hook-specific ThreadItem variants

Hooks messages are not persisted, they remain ephemeral. The context
changes they add are (they get appended to the user's prompt)
This commit is contained in:
Andrei Eternal
2026-03-09 21:11:31 -07:00
committed by GitHub
parent da616136cc
commit 244b2d53f4
73 changed files with 4791 additions and 483 deletions

View File

@@ -1056,6 +1056,184 @@
},
"type": "object"
},
"HookCompletedNotification": {
"properties": {
"run": {
"$ref": "#/definitions/HookRunSummary"
},
"threadId": {
"type": "string"
},
"turnId": {
"type": [
"string",
"null"
]
}
},
"required": [
"run",
"threadId"
],
"type": "object"
},
"HookEventName": {
"enum": [
"sessionStart",
"stop"
],
"type": "string"
},
"HookExecutionMode": {
"enum": [
"sync",
"async"
],
"type": "string"
},
"HookHandlerType": {
"enum": [
"command",
"prompt",
"agent"
],
"type": "string"
},
"HookOutputEntry": {
"properties": {
"kind": {
"$ref": "#/definitions/HookOutputEntryKind"
},
"text": {
"type": "string"
}
},
"required": [
"kind",
"text"
],
"type": "object"
},
"HookOutputEntryKind": {
"enum": [
"warning",
"stop",
"feedback",
"context",
"error"
],
"type": "string"
},
"HookRunStatus": {
"enum": [
"running",
"completed",
"failed",
"blocked",
"stopped"
],
"type": "string"
},
"HookRunSummary": {
"properties": {
"completedAt": {
"format": "int64",
"type": [
"integer",
"null"
]
},
"displayOrder": {
"format": "int64",
"type": "integer"
},
"durationMs": {
"format": "int64",
"type": [
"integer",
"null"
]
},
"entries": {
"items": {
"$ref": "#/definitions/HookOutputEntry"
},
"type": "array"
},
"eventName": {
"$ref": "#/definitions/HookEventName"
},
"executionMode": {
"$ref": "#/definitions/HookExecutionMode"
},
"handlerType": {
"$ref": "#/definitions/HookHandlerType"
},
"id": {
"type": "string"
},
"scope": {
"$ref": "#/definitions/HookScope"
},
"sourcePath": {
"type": "string"
},
"startedAt": {
"format": "int64",
"type": "integer"
},
"status": {
"$ref": "#/definitions/HookRunStatus"
},
"statusMessage": {
"type": [
"string",
"null"
]
}
},
"required": [
"displayOrder",
"entries",
"eventName",
"executionMode",
"handlerType",
"id",
"scope",
"sourcePath",
"startedAt",
"status"
],
"type": "object"
},
"HookScope": {
"enum": [
"thread",
"turn"
],
"type": "string"
},
"HookStartedNotification": {
"properties": {
"run": {
"$ref": "#/definitions/HookRunSummary"
},
"threadId": {
"type": "string"
},
"turnId": {
"type": [
"string",
"null"
]
}
},
"required": [
"run",
"threadId"
],
"type": "object"
},
"ItemCompletedNotification": {
"properties": {
"item": {
@@ -3378,6 +3556,26 @@
"title": "Turn/startedNotification",
"type": "object"
},
{
"properties": {
"method": {
"enum": [
"hook/started"
],
"title": "Hook/startedNotificationMethod",
"type": "string"
},
"params": {
"$ref": "#/definitions/HookStartedNotification"
}
},
"required": [
"method",
"params"
],
"title": "Hook/startedNotification",
"type": "object"
},
{
"properties": {
"method": {
@@ -3398,6 +3596,26 @@
"title": "Turn/completedNotification",
"type": "object"
},
{
"properties": {
"method": {
"enum": [
"hook/completed"
],
"title": "Hook/completedNotificationMethod",
"type": "string"
},
"params": {
"$ref": "#/definitions/HookCompletedNotification"
}
},
"required": [
"method",
"params"
],
"title": "Hook/completedNotification",
"type": "object"
},
{
"properties": {
"method": {