Просмотр исходного кода

Append phase reminder at message tail

Alvin Unreal 3 месяцев назад
Родитель
Сommit
983eef0857

+ 0 - 5
src/agents/orchestrator.ts

@@ -1,5 +1,4 @@
 import type { AgentConfig } from '@opencode-ai/sdk/v2';
-import { PHASE_REMINDER_TEXT } from '../config/constants';
 
 export interface AgentDefinition {
   name: string;
@@ -213,10 +212,6 @@ ${enabledValidationRouting}
 
 </Workflow>
 
-<WorkflowReminder>
-${PHASE_REMINDER_TEXT}
-</WorkflowReminder>
-
 <Communication>
 
 ## Clarity Over Assumptions

+ 23 - 5
src/hooks/phase-reminder/index.test.ts

@@ -1,8 +1,9 @@
 import { describe, expect, test } from 'bun:test';
+import { SLIM_INTERNAL_INITIATOR_MARKER } from '../../utils';
 import { createPhaseReminderHook, PHASE_REMINDER } from './index';
 
 describe('createPhaseReminderHook', () => {
-  test('does not mutate orchestrator messages', async () => {
+  test('appends reminder for orchestrator sessions', async () => {
     const hook = createPhaseReminderHook();
     const output = {
       messages: [
@@ -15,8 +16,9 @@ describe('createPhaseReminderHook', () => {
 
     await hook['experimental.chat.messages.transform']({}, output);
 
-    expect(output.messages[0].parts[0].text).toBe('hello');
-    expect(output.messages[0].parts[0].text).not.toContain(PHASE_REMINDER);
+    expect(output.messages[0].parts[0].text).toBe(
+      `hello\n\n---\n\n${PHASE_REMINDER}`,
+    );
   });
 
   test('skips non-orchestrator sessions', async () => {
@@ -37,8 +39,7 @@ describe('createPhaseReminderHook', () => {
 
   test('does not mutate internal notification turns', async () => {
     const hook = createPhaseReminderHook();
-    const text =
-      '[Background task "x" completed]\n<!-- slim-internal-initiator -->';
+    const text = `[Background task "x" completed]\n${SLIM_INTERNAL_INITIATOR_MARKER}`;
     const output = {
       messages: [
         {
@@ -53,4 +54,21 @@ describe('createPhaseReminderHook', () => {
     expect(output.messages[0].parts[0].text).toBe(text);
     expect(output.messages[0].parts[0].text).not.toContain(PHASE_REMINDER);
   });
+
+  test('does not append duplicate reminder', async () => {
+    const hook = createPhaseReminderHook();
+    const text = `hello\n\n---\n\n${PHASE_REMINDER}`;
+    const output = {
+      messages: [
+        {
+          info: { role: 'user', agent: 'orchestrator' },
+          parts: [{ type: 'text', text }],
+        },
+      ],
+    };
+
+    await hook['experimental.chat.messages.transform']({}, output);
+
+    expect(output.messages[0].parts[0].text).toBe(text);
+  });
 });

+ 47 - 6
src/hooks/phase-reminder/index.ts

@@ -1,12 +1,12 @@
 /**
- * Phase reminder hook retained for backwards-compatible hook wiring.
+ * Phase reminder to append after each latest user message.
  *
- * The reminder now lives in the static orchestrator prompt. Injecting it into
- * every latest user message changed request content unnecessarily and could
- * interfere with provider prompt-cache reuse. Keeping this transform as a
- * no-op preserves the hook shape without mutating chat messages.
+ * Keeping this at the tail preserves immediate workflow guidance without
+ * mutating the cached system prompt or prepending request-local content ahead
+ * of the user's actual turn.
  */
 import { PHASE_REMINDER_TEXT } from '../../config/constants';
+import { SLIM_INTERNAL_INITIATOR_MARKER } from '../../utils';
 
 export const PHASE_REMINDER = `<reminder>${PHASE_REMINDER_TEXT}</reminder>`;
 
@@ -38,7 +38,48 @@ export function createPhaseReminderHook() {
       _input: Record<string, never>,
       output: { messages: MessageWithParts[] },
     ): Promise<void> => {
-      void output;
+      const { messages } = output;
+
+      if (messages.length === 0) {
+        return;
+      }
+
+      let lastUserMessageIndex = -1;
+      for (let i = messages.length - 1; i >= 0; i--) {
+        if (messages[i].info.role === 'user') {
+          lastUserMessageIndex = i;
+          break;
+        }
+      }
+
+      if (lastUserMessageIndex === -1) {
+        return;
+      }
+
+      const lastUserMessage = messages[lastUserMessageIndex];
+      const agent = lastUserMessage.info.agent;
+      if (agent && agent !== 'orchestrator') {
+        return;
+      }
+
+      const textPartIndex = lastUserMessage.parts.findIndex(
+        (p) => p.type === 'text' && p.text !== undefined,
+      );
+
+      if (textPartIndex === -1) {
+        return;
+      }
+
+      const originalText = lastUserMessage.parts[textPartIndex].text ?? '';
+      if (originalText.includes(SLIM_INTERNAL_INITIATOR_MARKER)) {
+        return;
+      }
+      if (originalText.includes(PHASE_REMINDER)) {
+        return;
+      }
+
+      lastUserMessage.parts[textPartIndex].text =
+        `${originalText}\n\n---\n\n${PHASE_REMINDER}`;
     },
   };
 }