Repository navigation
Conversation
…nder default_agent default_agent resolves silently inside context selection, so a session created without an explicit agent never emits an agent-switch event. The context reconcile then first fires mid-generation: its synthetic is admitted while the first turn runs, the inbox promotes it as new input when that turn ends, and the model starts a second, unprompted turn - with default_agent: plan, every single-shot run answered the prompt and then invented a task to work on (anomalyco#58287). The plan plugin now hooks the prompt path: when the session carries no explicit agent and the default resolves to plan, the enter reminder is persisted before the user's message is admitted - the exact ordering an explicit --agent plan already gets at session creation. The context reconcile stays as the safety net for compaction-stripped reminders. Fixes anomalyco#58287
There was a problem hiding this comment.
Thanks for tracking down the cause. As written, though, this doesn't fix #58287. I ran the issue's repro from source (mock provider, default_agent: "plan", opencode run without --agent). The PR head behaves exactly like the base: the model gets a second request, prompt → PROBE_C → plan reminder, and the reply prints twice.
The reason is that the new prompt hook is never registered, because ctx.session.hook(...) is missing yield* (see the inline comment). When I added yield* locally, the repro made a single request with the reminder before the prompt and printed one reply, both with and without --agent plan. So the approach itself works.
The new tests didn't catch this because the stub session.hook saves the callback as soon as it's called, without running the returned Effect. Please make the stub record the callback only when its Effect runs (e.g. inside Effect.sync), so the test fails without yield*.
Public API: unchanged. This only changes the built-in plan plugin.
Related: #57013
| // Persisting the reminder before the user's message is admitted gives the | ||
| // ordering an explicit `--agent plan` already produces at session creation. | ||
| const defaulted = new Set<Session.ID>() | ||
| ctx.session.hook("prompt", (event) => |
There was a problem hiding this comment.
This needs yield* ctx.session.hook("prompt", ...). hook returns an Effect<Registration, never, Scope> and registration happens only when that Effect runs (PluginHooks.register), so without yield* the hook is never added. With the PR as-is, the #58287 repro still makes the second, unprompted request. With yield* added it makes a single request and prints one reply.
| get: (input) => Effect.succeed({ ...sessionInfo, id: input.sessionID, agent: currentAgent }), | ||
| hook: (name, callback) => { | ||
| if (name === "context") contextHook = callback as (input: SessionContext) => Effect.Effect<void> | ||
| if (name === "prompt") promptHook = callback as unknown as (input: SessionHooks["prompt"]) => Effect.Effect<void> |
There was a problem hiding this comment.
This stub saves the prompt callback as soon as hook is called, without running the returned Effect. That's why these tests pass even though the plugin never registers the hook. Please record the callback inside the returned Effect (e.g. Effect.sync(() => { ...; return { dispose: Effect.void } })), so the new test fails without yield*. The _sessionAgent field and the as unknown as cast in promptEvent can also go, since the hook doesn't read them.
|
Thanks for updating your PR! It now meets our contributing guidelines. 👍 |
…lly lands The prompt hook was registered without yield*, and hook only registers when its returned Effect runs (PluginHooks.register) - so the hook never landed, the anomalyco#58287 repro still made the second, unprompted request, and the PR behaved exactly like the base. The registration is yielded now, and the test stub records the callback only when the returned Effect runs (matching real registration), so the new tests fail without the yield* instead of passing vacuously.
|
Good catch, and thank you for running the repro both ways — fixed in cb7650c.
|
There was a problem hiding this comment.
Both earlier points are fixed. The prompt hook is now registered with yield*, and the test stub records the callback only when its Effect runs, so the tests fail without the yield* (I checked). I re-ran the #58287 repro from source with a mock provider and default_agent: "plan". The base still makes the extra prompt → PROBE_C → plan reminder request. With this PR, both opencode run and opencode run --agent plan make a single request, with the reminder before the prompt, and print one reply. plan.test.ts passes 14/14.
Public API: unchanged. This only changes the built-in plan plugin.
No further concerns.
Issue for this PR
Closes #58287
Type of change
What does this PR do?
default_agentresolves silently inside context selection, so a session created without an explicit agent never emits an agent-switch event. The plan plugin's context reconcile then fires for the first time mid-generation: its synthetic reminder is admitted while the first turn runs, the inbox promotes it as new input when that turn ends, and the model starts a second, unprompted turn. Withdefault_agent: "plan", every single-shot run answered the prompt and then invented a task to work on.The plan plugin now hooks the prompt path: when the session carries no explicit agent and the configured default resolves to
plan, the enter reminder is persisted before the user's message is admitted — the same ordering an explicit--agent planalready gets at session creation. A per-session guard makes it idempotent; the context reconcile stays as the safety net for compaction-stripped reminders. I verified the ordering claim by tracingSessionEvent.Created(agent undefined) vsswitchAgent(agent set) through to the plugin's event subscription and the inbox's steer promotion.How did you verify your code works?
bun test test/plugin/plan.test.ts(14/14) and the plugin + session-runner suites (435/435) pass, with two new cases: the reminder persists on the first prompt for an agent-less session under a plan default and does not re-inject on the second; nothing persists when the session carries an explicit agent.bun run typecheckis clean acrossaiandcore. CI on the PR branch is green.Screenshots / recordings
Not a UI change.
Checklist
Added by the opencode bot (AI-generated).
Related: #57013