Implemented thread-level atomic elicitation counter for stopwatch pausing

While trying to build out CLI-Tools for the agent to use under skills we
have found that those tools sometimes need to invoke a user elicitation.
These elicitations are handled out of band of the codex app-server but
need to indicate to the exec manager that the command running is
not going to progress on the usual timeout horizon.

Model calls universal exec:
`$ download-credit-card-history --start-date 2026-01-19 --end-date 2026-02-19 > credit_history.jsonl`

download-cred-card-history might hit a hosted/preauthenticated service to fetch data.  That service might decide that the request requires an end user approval the access to the personal data.  It should be able to signal to the running thread that the command in question is blocked on user elicitation.  In that case we want the exec to continue, but the timeout to not expire on the tool call, essentially freezing time until the user approves or rejects the command at which point the tool would signal the app-server to decrement the outstanding elicitation count.  Now timeouts would proceed as normal.

- New v2 RPC methods:
    - thread/increment_elicitation
    - thread/decrement_elicitation
- Protocol updates in:
    - codex-rs/app-server-protocol/src/protocol/common.rs
    - codex-rs/app-server-protocol/src/protocol/v2.rs
- App-server handlers wired in:
    - codex-rs/app-server/src/codex_message_processor.rs

- Counter starts at 0 per thread.
- increment atomically increases the counter.
- decrement atomically decreases the counter; decrement at 0 returns invalid request.
- Transition rules:
    - 0 -> 1: broadcast pause state, pausing all active stopwatches immediately.
    - >0 -> >0: remain paused.
    - 1 -> 0: broadcast unpause state, resuming stopwatches.
- Core thread/session logic:
    - codex-rs/core/src/codex_thread.rs
    - codex-rs/core/src/codex.rs
    - codex-rs/core/src/mcp_connection_manager.rs

- Added centralized stopwatch tracking/controller:
    - codex-rs/exec-server/src/posix/stopwatch_controller.rs
- Hooked pause/unpause broadcast handling + stopwatch registration:
    - codex-rs/exec-server/src/posix/mcp.rs
    - codex-rs/exec-server/src/posix/stopwatch.rs
    - codex-rs/exec-server/src/posix.rs
This commit is contained in:
Channing Conger
2026-02-19 20:24:53 -08:00
parent 366ecaf17a
commit 54d4da7f29
24 changed files with 736 additions and 25 deletions

View File

@@ -2588,6 +2588,17 @@
],
"type": "object"
},
"ThreadDecrementElicitationParams": {
"properties": {
"threadId": {
"type": "string"
}
},
"required": [
"threadId"
],
"type": "object"
},
"ThreadForkParams": {
"description": "There are two ways to fork a thread: 1. By thread_id: load the thread from disk by thread_id and fork it into a new thread. 2. By path: load the thread from disk by path and fork it into a new thread.\n\nIf using path, the thread_id param will be ignored.\n\nPrefer using thread_id whenever possible.",
"properties": {
@@ -2661,6 +2672,17 @@
"ThreadId": {
"type": "string"
},
"ThreadIncrementElicitationParams": {
"properties": {
"threadId": {
"type": "string"
}
},
"required": [
"threadId"
],
"type": "object"
},
"ThreadListParams": {
"properties": {
"archived": {
@@ -3523,6 +3545,54 @@
"title": "Thread/archiveRequest",
"type": "object"
},
{
"properties": {
"id": {
"$ref": "#/definitions/RequestId"
},
"method": {
"enum": [
"thread/increment_elicitation"
],
"title": "Thread/incrementElicitationRequestMethod",
"type": "string"
},
"params": {
"$ref": "#/definitions/ThreadIncrementElicitationParams"
}
},
"required": [
"id",
"method",
"params"
],
"title": "Thread/incrementElicitationRequest",
"type": "object"
},
{
"properties": {
"id": {
"$ref": "#/definitions/RequestId"
},
"method": {
"enum": [
"thread/decrement_elicitation"
],
"title": "Thread/decrementElicitationRequestMethod",
"type": "string"
},
"params": {
"$ref": "#/definitions/ThreadDecrementElicitationParams"
}
},
"required": [
"id",
"method",
"params"
],
"title": "Thread/decrementElicitationRequest",
"type": "object"
},
{
"properties": {
"id": {

View File

@@ -570,6 +570,54 @@
"title": "Thread/archiveRequest",
"type": "object"
},
{
"properties": {
"id": {
"$ref": "#/definitions/RequestId"
},
"method": {
"enum": [
"thread/increment_elicitation"
],
"title": "Thread/incrementElicitationRequestMethod",
"type": "string"
},
"params": {
"$ref": "#/definitions/v2/ThreadIncrementElicitationParams"
}
},
"required": [
"id",
"method",
"params"
],
"title": "Thread/incrementElicitationRequest",
"type": "object"
},
{
"properties": {
"id": {
"$ref": "#/definitions/RequestId"
},
"method": {
"enum": [
"thread/decrement_elicitation"
],
"title": "Thread/decrementElicitationRequestMethod",
"type": "string"
},
"params": {
"$ref": "#/definitions/v2/ThreadDecrementElicitationParams"
}
},
"required": [
"id",
"method",
"params"
],
"title": "Thread/decrementElicitationRequest",
"type": "object"
},
{
"properties": {
"id": {
@@ -15359,6 +15407,38 @@
"title": "ThreadCompactStartResponse",
"type": "object"
},
"ThreadDecrementElicitationParams": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"threadId": {
"type": "string"
}
},
"required": [
"threadId"
],
"title": "ThreadDecrementElicitationParams",
"type": "object"
},
"ThreadDecrementElicitationResponse": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"count": {
"format": "uint64",
"minimum": 0.0,
"type": "integer"
},
"paused": {
"type": "boolean"
}
},
"required": [
"count",
"paused"
],
"title": "ThreadDecrementElicitationResponse",
"type": "object"
},
"ThreadForkParams": {
"$schema": "http://json-schema.org/draft-07/schema#",
"description": "There are two ways to fork a thread: 1. By thread_id: load the thread from disk by thread_id and fork it into a new thread. 2. By path: load the thread from disk by path and fork it into a new thread.\n\nIf using path, the thread_id param will be ignored.\n\nPrefer using thread_id whenever possible.",
@@ -15477,6 +15557,38 @@
"ThreadId": {
"type": "string"
},
"ThreadIncrementElicitationParams": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"threadId": {
"type": "string"
}
},
"required": [
"threadId"
],
"title": "ThreadIncrementElicitationParams",
"type": "object"
},
"ThreadIncrementElicitationResponse": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"count": {
"format": "uint64",
"minimum": 0.0,
"type": "integer"
},
"paused": {
"type": "boolean"
}
},
"required": [
"count",
"paused"
],
"title": "ThreadIncrementElicitationResponse",
"type": "object"
},
"ThreadItem": {
"oneOf": [
{

View File

@@ -0,0 +1,13 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"threadId": {
"type": "string"
}
},
"required": [
"threadId"
],
"title": "ThreadDecrementElicitationParams",
"type": "object"
}

View File

@@ -0,0 +1,19 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"count": {
"format": "uint64",
"minimum": 0.0,
"type": "integer"
},
"paused": {
"type": "boolean"
}
},
"required": [
"count",
"paused"
],
"title": "ThreadDecrementElicitationResponse",
"type": "object"
}

View File

@@ -0,0 +1,13 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"threadId": {
"type": "string"
}
},
"required": [
"threadId"
],
"title": "ThreadIncrementElicitationParams",
"type": "object"
}

View File

@@ -0,0 +1,19 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"count": {
"format": "uint64",
"minimum": 0.0,
"type": "integer"
},
"paused": {
"type": "boolean"
}
},
"required": [
"count",
"paused"
],
"title": "ThreadIncrementElicitationResponse",
"type": "object"
}