Skip to content

Commit 3890751

Browse files
committed
fix: restore correct agent context after implicit compaction in proceed_to_phase
## High level changes - `plugin.ts`: Added `lastKnownAgent` variable that caches the last enabled agent name seen in `chat.message` - `plugin.ts`: Post-compaction `promptAsync` now passes `agent: lastKnownAgent` in the request body so the continue message runs under the original workflow agent - `plugin.ts`: Added `postCompactionAutoResume` flag set on `session.compacted` to bypass the agent filter for OpenCode's own auto-continue message - `plugin.ts`: `chat.message` hook checks `bypassAgentFilter` before the `isAgentEnabled` guard so phase instructions are injected instead of a suppression/no-workflow notice ## Motivation After `proceed_to_phase` triggers implicit compaction, OpenCode resumes the session using its default agent (e.g. `build`) rather than the original workflow agent. This caused two problems: 1. OpenCode's built-in auto-continue `chat.message` arrived with agent `build`, hit the `WORKFLOW_AGENTS` filter, and injected "No Active Workflow Detected" instead of phase instructions. 2. The plugin's own follow-up `promptAsync` (sent on `session.idle`) also omitted the agent field, so it too ran under the wrong agent, losing system prompt and tool access. ## Details - `lastKnownAgent` is only updated when `isAgentEnabled` is true, ensuring a subagent name (e.g. `general`, `explore`) never overwrites the cached workflow agent. - `postCompactionAutoResume` is set to `true` on `session.compacted` and consumed (set back to `false`) by the very next `chat.message` invocation. This is a one-shot bypass: it covers exactly OpenCode's implicit auto-continue turn and nothing more. - The `promptAsync` body now spreads `{ agent: lastKnownAgent }` when a cached agent is available. `PromptInput.agent` is an optional string field in the OpenCode API, so this is a safe additive change with no effect when `lastKnownAgent` is `null`.
1 parent ab45ac8 commit 3890751

1 file changed

Lines changed: 41 additions & 2 deletions

File tree

packages/opencode-plugin/src/plugin.ts

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -172,10 +172,20 @@ export const WorkflowsPlugin: Plugin = async (
172172
// duplicate synthetic part for that specific message.
173173
let postCompactionMessagePending = false;
174174

175+
// Set to true right after session.compacted fires so that the very next
176+
// chat.message (OpenCode's own auto-continue) bypasses the agent filter
177+
// and injects proper phase instructions instead of a suppression notice.
178+
let postCompactionAutoResume = false;
179+
175180
// Last-known model from chat.message hook. Cached so proceed_to_phase can
176181
// pass providerID + modelID to the summarize API (which requires them).
177182
let lastKnownModel: { providerID: string; modelID: string } | null = null;
178183

184+
// Last-known agent from chat.message hook. Used when sending the
185+
// post-compaction phase-aware continue message so it runs under the
186+
// correct agent (e.g. 'workflow') rather than OpenCode's default.
187+
let lastKnownAgent: string | null = null;
188+
179189
/**
180190
* Set buffered instructions from a tool result.
181191
* The next chat.message hook will use these instead of calling WhatsNextHandler.
@@ -305,6 +315,13 @@ export const WorkflowsPlugin: Plugin = async (
305315
lastKnownModel = hookInput.model;
306316
}
307317

318+
// Cache the agent for use by the post-compaction continue message.
319+
// Only cache when the agent is enabled (i.e. a primary workflow agent),
320+
// so we don't accidentally cache a subagent name.
321+
if (hookInput.agent && isAgentEnabled(hookInput.agent)) {
322+
lastKnownAgent = hookInput.agent;
323+
}
324+
308325
// If this message was the post-compaction instructions prompt sent by Hook 4,
309326
// skip synthetic part injection — the message body IS the instructions.
310327
if (postCompactionMessagePending) {
@@ -315,14 +332,27 @@ export const WorkflowsPlugin: Plugin = async (
315332
return;
316333
}
317334

335+
// After compaction, OpenCode sends an auto-continue message which may arrive
336+
// as a non-workflow agent (e.g. 'build'). In that case we still want to inject
337+
// the phase instructions rather than the suppression/no-workflow notice, so we
338+
// consume the flag and fall through to normal phase-instruction injection below.
339+
const bypassAgentFilter = postCompactionAutoResume;
340+
if (bypassAgentFilter) {
341+
postCompactionAutoResume = false;
342+
logger.debug(
343+
'chat.message: bypassing agent filter for post-compaction auto-resume',
344+
{ agent: hookInput.agent }
345+
);
346+
}
347+
318348
// If WORKFLOW_AGENTS is set and this agent is not in the allowlist, inject a
319349
// suppression instruction as a synthetic part so the LLM knows not to call the
320350
// workflow tools (which would only throw errors for non-enabled agents).
321351
// We use chat.message (not experimental.chat.system.transform) because:
322352
// 1. chat.message already has hookInput.agent directly — no stale-state risk.
323353
// 2. chat.message fires reliably for every user turn; transform may fire for
324354
// intermediate tool-loop LLM calls without a preceding chat.message.
325-
if (!isAgentEnabled(hookInput.agent)) {
355+
if (!bypassAgentFilter && !isAgentEnabled(hookInput.agent)) {
326356
logger.debug(
327357
'chat.message: Agent not enabled — injecting tool suppression',
328358
{
@@ -560,6 +590,11 @@ ACTION REQUIRED: Use proceed_to_phase tool to move to a phase that allows editin
560590

561591
if (event.type === 'session.compacted') {
562592
postCompactionSession = event.properties.sessionID as string;
593+
// Set flag so the next chat.message (OpenCode's own auto-continue,
594+
// which may fire as a non-workflow agent like 'build') bypasses the
595+
// agent filter and injects proper phase instructions instead of a
596+
// suppression/no-workflow notice.
597+
postCompactionAutoResume = true;
563598
logger.info('session.compacted: pending phase-aware continue', {
564599
sessionID: postCompactionSession,
565600
});
@@ -614,14 +649,18 @@ ACTION REQUIRED: Use proceed_to_phase tool to move to a phase that allows editin
614649
session: {
615650
promptAsync(params: {
616651
path: { id: string };
617-
body: { parts: Array<{ type: string; text: string }> };
652+
body: {
653+
parts: Array<{ type: string; text: string }>;
654+
agent?: string;
655+
};
618656
}): Promise<unknown>;
619657
};
620658
};
621659
await client.session.promptAsync({
622660
path: { id: sessionID },
623661
body: {
624662
parts: [{ type: 'text', text: promptText }],
663+
...(lastKnownAgent ? { agent: lastKnownAgent } : {}),
625664
},
626665
});
627666
logger.info('session.idle: phase-aware continue sent (async)', {

0 commit comments

Comments
 (0)