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
6 changes: 5 additions & 1 deletion apps/opencode-plugin/native-commands.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -545,14 +545,18 @@ describe("V2 session URL delivery", () => {
// Regression: without `resume: false` upstream calls `execution.wake`
// (packages/core/src/session/session.ts), so merely showing a URL would start
// a model turn the reviewer never asked for and burn tokens on every command.
// #1459 extension: resume: false only defers the immediate wake; the host
// default delivery is "steer", which any later wake (including spurious
// idle wakes on OpenCode 2 betas) promotes into its own model turn. The
// notice must therefore also pin queue delivery.
test("the notice never wakes a model turn", async () => {
const { synthetic, ctx } = makeSyntheticCtx();
const client = createV2BridgeClient({ ctx, getAgents: async () => [], sessionID: "session-1" });

pushUrlLine(client);
await Promise.resolve();

expect(synthetic.mock.calls[0]![0]).toMatchObject({ resume: false });
expect(synthetic.mock.calls[0]![0]).toMatchObject({ resume: false, delivery: "queue" });
});

// Regression: `session.synthetic` is absent on older V2 hosts, and a session
Expand Down
2 changes: 2 additions & 0 deletions apps/opencode-plugin/server.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,8 @@ describe("V2 plan review URL delivery", () => {
sessionID: "session-1",
description: formatSessionUrlNotice(SESSION_URL),
resume: false,
// #1459: queue delivery keeps the notice out of steer-scoped promotion.
delivery: "queue",
});
});

Expand Down
17 changes: 14 additions & 3 deletions apps/opencode-plugin/v2-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -247,8 +247,12 @@ export function formatSessionUrlNotice(url: string): string {
* - Setting no `metadata.source` keeps it on the plain "Notice" row rather
* than the subagent/shell completion row.
*
* `delivery` is left at the host default, matching upstream's own synthetic
* notices; only feedback (`FEEDBACK_DELIVERY`) needs an explicit queue.
* `delivery` is an explicit "queue", mirroring `FEEDBACK_DELIVERY` (#1459).
* The host default resolves to "steer", and a pending steer row is promoted
* FIRST by any wake, including spurious idle wakes observed on OpenCode 2
* betas where `resume: false` defers the immediate wake but a later wake
* turns the notice into its own model turn. Queue delivery keeps the notice
* out of every steer-scoped promotion and is a no-op on well-behaved hosts.
*
* This does not contradict the reason feedback avoids synthetic injection.
* Upstream #44788 is about a synthetic message not reliably reaching the MODEL
Expand All @@ -267,7 +271,14 @@ export function createSessionUrlNotifier(
if (typeof synthetic !== "function" || !sessionID) return undefined;
return async ({ url }) => {
const notice = formatSessionUrlNotice(url);
return await synthetic({ sessionID, text: notice, description: notice, resume: false });
return await synthetic({
sessionID,
text: notice,
description: notice,
resume: false,
// #1459: never ride the "steer" default; see the delivery note above.
delivery: FEEDBACK_DELIVERY,
});
};
}

Expand Down