From 15e7330ff07e34d65f643eacb6ef17e1c93d04a0 Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Fri, 15 May 2026 15:35:35 +0900 Subject: [PATCH] fix(team-mode): gate status injection by keyword --- .../team-mode-status-injector/hook.test.ts | 47 ++++++++++++++++--- src/hooks/team-mode-status-injector/hook.ts | 29 ++++++++++-- src/plugin/hooks/create-transform-hooks.ts | 2 +- 3 files changed, 67 insertions(+), 11 deletions(-) diff --git a/src/hooks/team-mode-status-injector/hook.test.ts b/src/hooks/team-mode-status-injector/hook.test.ts index 41ac3341f..4bd33c1c7 100644 --- a/src/hooks/team-mode-status-injector/hook.test.ts +++ b/src/hooks/team-mode-status-injector/hook.test.ts @@ -3,7 +3,7 @@ import { describe, expect, it } from "bun:test" import { TeamModeConfigSchema } from "../../config/schema/team-mode" import { createTeamModeStatusInjector } from "./hook" -function createOutput(sessionID: string): { +function createOutput(sessionID: string, text = "original message"): { messages: Array<{ info: { role: string; sessionID: string } parts: Array<{ type: string; text?: string; synthetic?: boolean }> @@ -16,7 +16,7 @@ function createOutput(sessionID: string): { role: "user", sessionID, }, - parts: [{ type: "text", text: "original message" }], + parts: [{ type: "text", text }], }, ], } @@ -26,7 +26,7 @@ describe("createTeamModeStatusInjector", () => { it("injects a one-time team mode enabled message before the latest user message", async () => { // given const hook = createTeamModeStatusInjector(TeamModeConfigSchema.parse({ enabled: true })) - const output = createOutput("session-team-mode") + const output = createOutput("session-team-mode", "team mode please") // when await hook["experimental.chat.messages.transform"]?.( @@ -49,14 +49,14 @@ describe("createTeamModeStatusInjector", () => { }, ], }) - expect(output.messages[1]?.parts[0]?.text).toBe("original message") + expect(output.messages[1]?.parts[0]?.text).toBe("team mode please") }) it("does not inject again when the team mode status was already added", async () => { // given const hook = createTeamModeStatusInjector(TeamModeConfigSchema.parse({ enabled: true })) - const firstOutput = createOutput("session-team-mode") - const secondOutput = createOutput("session-team-mode") + const firstOutput = createOutput("session-team-mode", "team mode please") + const secondOutput = createOutput("session-team-mode", "team mode please") // when await hook["experimental.chat.messages.transform"]?.( @@ -94,4 +94,39 @@ describe("createTeamModeStatusInjector", () => { expect(output.messages).toHaveLength(1) expect(output.messages[0]?.parts[0]?.text).toBe("original message") }) + + it("does not inject team mode status for punctuation-only prompts", async () => { + // given + const hook = createTeamModeStatusInjector(TeamModeConfigSchema.parse({ enabled: true })) + const output = createOutput("session-team-mode", ".") + + // when + await hook["experimental.chat.messages.transform"]?.( + { sessionID: "session-team-mode" }, + output, + ) + + // then + expect(output.messages).toHaveLength(1) + expect(output.messages[0]?.parts[0]?.text).toBe(".") + }) + + it("does not inject team mode status when the team keyword is disabled", async () => { + // given + const hook = createTeamModeStatusInjector( + TeamModeConfigSchema.parse({ enabled: true }), + { disabled_keywords: ["team"] }, + ) + const output = createOutput("session-team-mode", "team mode please") + + // when + await hook["experimental.chat.messages.transform"]?.( + { sessionID: "session-team-mode" }, + output, + ) + + // then + expect(output.messages).toHaveLength(1) + expect(output.messages[0]?.parts[0]?.text).toBe("team mode please") + }) }) diff --git a/src/hooks/team-mode-status-injector/hook.ts b/src/hooks/team-mode-status-injector/hook.ts index 57877ddd9..6bb291427 100644 --- a/src/hooks/team-mode-status-injector/hook.ts +++ b/src/hooks/team-mode-status-injector/hook.ts @@ -1,4 +1,6 @@ import type { TeamModeConfig } from "../../config/schema/team-mode" +import type { KeywordDetectorConfig } from "../../config/schema/keyword-detector" +import { detectKeywordsWithType, extractPromptText } from "../keyword-detector/detector" type TransformPart = { type: string @@ -72,6 +74,25 @@ function hasInjectedTeamModeStatus(messages: MessageWithParts[]): boolean { ) } +function latestUserMessageRequestsTeamMode( + messages: MessageWithParts[], + userMessageIndex: number, + keywordDetectorConfig?: KeywordDetectorConfig, +): boolean { + const message = messages[userMessageIndex] + if (message === undefined) { + return false + } + + const promptText = extractPromptText(message.parts) + return detectKeywordsWithType( + promptText, + undefined, + undefined, + keywordDetectorConfig?.disabled_keywords, + ).some((keyword) => keyword.type === "team") +} + function buildTeamModeStatusContent(): string { return `${TEAM_MODE_STATUS_MARKER} Team mode is ENABLED for this session. @@ -93,6 +114,7 @@ function createInjectedMessage(sessionID: string): MessageWithParts { export function createTeamModeStatusInjector( config: TeamModeConfig, + keywordDetectorConfig?: KeywordDetectorConfig, ): TeamModeStatusInjectorHook { return { "experimental.chat.messages.transform": async ( @@ -113,13 +135,12 @@ export function createTeamModeStatusInjector( } const lastUserMessageIndex = findLastUserMessageIndex(output.messages) - const injectedMessage = createInjectedMessage(sessionID) - - if (lastUserMessageIndex === -1) { - output.messages.unshift(injectedMessage) + if (!latestUserMessageRequestsTeamMode(output.messages, lastUserMessageIndex, keywordDetectorConfig)) { return } + const injectedMessage = createInjectedMessage(sessionID) + output.messages.splice(lastUserMessageIndex, 0, injectedMessage) }, } diff --git a/src/plugin/hooks/create-transform-hooks.ts b/src/plugin/hooks/create-transform-hooks.ts index 387ae8eb3..36fb4c4d9 100644 --- a/src/plugin/hooks/create-transform-hooks.ts +++ b/src/plugin/hooks/create-transform-hooks.ts @@ -74,7 +74,7 @@ export function createTransformHooks(args: { const teamModeStatusInjector = teamModeConfig?.enabled ? safeCreateHook( "team-mode-status-injector", - () => createTeamModeStatusInjector(teamModeConfig), + () => createTeamModeStatusInjector(teamModeConfig, pluginConfig.keyword_detector), { enabled: safeHookEnabled }, ) : null