Add goal extension idle continuation (#25060)

## Why

The goal extension needs a way to resume an active goal after the thread
becomes idle, but the old core goal runtime should not be refactored as
part of this step. The missing piece is a small core-owned turn-start
primitive: let an extension ask for a normal model turn only when the
thread is idle, and otherwise fail without injecting into whatever is
currently active.

## What Changed

- Adds `CodexThread::try_start_turn_if_idle(...)` as the narrow
extension-facing primitive for synthetic idle work.
- Implements the session side so it refuses to start when:
  - the provided input is empty,
  - the session is in plan mode,
  - a turn is already active, or
  - trigger-turn mailbox work is pending.
- Gives trigger-turn mailbox work priority if it appears while the idle
turn is being prepared.
- Wires `GoalExtension::on_thread_idle` to read the active persisted
goal and submit the continuation prompt through this idle-only
primitive.
- Keeps the legacy core goal continuation implementation in place
instead of folding it into this PR.

## Behavior

This is intentionally best-effort. If `try_start_turn_if_idle` observes
that the thread is not idle, or that higher-priority mailbox work should
run first, it returns the input to the caller. The goal extension drops
that continuation prompt and waits for a future idle opportunity instead
of injecting stale synthetic goal text into an active turn.

## Validation

- `just test -p codex-core
try_start_turn_if_idle_rejects_active_turn_without_injecting`
- `just test -p codex-goal-extension`
This commit is contained in:
jif-oai authored and GitHub committed 2026-06-01 10:42:01 +02:00
1 parent 8d49394feb
commit f1b1b64005
6 files changed
+239

No files matched your search

+14
View File
@@ -7,6 +7,7 @@ use codex_extension_api::ConfigContributor;
use codex_extension_api::ExtensionData;
use codex_extension_api::ExtensionEventSink;
use codex_extension_api::ExtensionRegistryBuilder;
use codex_extension_api::ThreadIdleInput;
use codex_extension_api::ThreadLifecycleContributor;
use codex_extension_api::ThreadResumeInput;
use codex_extension_api::ThreadStartInput;
@@ -133,6 +134,19 @@ where
);
}
}
async fn on_thread_idle(&self, input: ThreadIdleInput<'_>) {
let Some(runtime) = goal_runtime_handle(input.thread_store) else {
return;
};
if let Err(err) = runtime.continue_if_idle().await {
tracing::warn!(
"failed to continue active goal for idle thread {}: {err}",
runtime.thread_id()
);
}
}
}
impl<C> ConfigContributor<C> for GoalExtension<C>