diff --git a/agent-teams-controller/src/internal/messages.js b/agent-teams-controller/src/internal/messages.js index 8ff2122b..a35e10fe 100644 --- a/agent-teams-controller/src/internal/messages.js +++ b/agent-teams-controller/src/internal/messages.js @@ -1,6 +1,27 @@ const messageStore = require('./messageStore.js'); const runtimeHelpers = require('./runtimeHelpers.js'); +const PLACEHOLDER_TASK_REF_PREFIX = /^\s*#0{8}\b\s*(?:[:.-]\s*)?/i; + +function stripPlaceholderTaskRefPrefix(value) { + if (typeof value !== 'string' || !PLACEHOLDER_TASK_REF_PREFIX.test(value)) { + return value; + } + return value.replace(PLACEHOLDER_TASK_REF_PREFIX, '').trimStart(); +} + +function normalizePlaceholderTaskRefPrefixes(flags) { + const next = { ...(flags || {}) }; + if (typeof next.text === 'string') { + const strippedText = stripPlaceholderTaskRefPrefix(next.text); + next.text = strippedText.trim() ? strippedText : next.text; + } + if (typeof next.summary === 'string') { + next.summary = stripPlaceholderTaskRefPrefix(next.summary); + } + return next; +} + function normalizeMessageSendFlags(context, flags) { const next = { ...(flags || {}) }; const rawTo = @@ -61,7 +82,7 @@ function assertUserDirectedMessageHasSender(context, flags) { } function sendMessage(context, flags) { - const normalized = normalizeMessageSendFlags(context, flags); + const normalized = normalizeMessageSendFlags(context, normalizePlaceholderTaskRefPrefixes(flags)); assertUserDirectedMessageHasSender(context, normalized); return messageStore.sendInboxMessage(context.paths, normalized); } diff --git a/agent-teams-controller/src/internal/tasks.js b/agent-teams-controller/src/internal/tasks.js index cbedb1bf..2ed89b1a 100644 --- a/agent-teams-controller/src/internal/tasks.js +++ b/agent-teams-controller/src/internal/tasks.js @@ -684,6 +684,8 @@ function buildMemberTaskProtocol(teamName, messagingProtocol = createMemberMessa { teamName: "${teamName}", taskId: "", text: "", from: "" } Do NOT comment on trivial coordination messages. Only comment when the information is valuable context for the task. 9. When sending a message about a specific task, include its short display label like # in your ${messagingProtocol.sendToolName} summary field for traceability. + - If the message is NOT about a real board task, do NOT include any # task label. + - Never invent placeholder task refs such as #00000000 or #. 10. In ALL human-facing or teammate-facing message text, when you mention a task reference, ALWAYS write it with a leading # (for example: #abcd1234, not abcd1234 or "task abcd1234"). 11. Review workflow clarity (IMPORTANT): - The work task (e.g. #1) is the thing that must end up APPROVED after review. diff --git a/agent-teams-controller/test/controller.test.js b/agent-teams-controller/test/controller.test.js index 0ca9abcd..81bb58a1 100644 --- a/agent-teams-controller/test/controller.test.js +++ b/agent-teams-controller/test/controller.test.js @@ -177,10 +177,28 @@ describe('agent-teams-controller API', () => { 'agent-teams_message_send { teamName: "my-team", to: "alice", from: "bob"' ); expect(briefing).toContain('Full details in task comment e5f6a7b8'); + expect(briefing).toContain('Never invent placeholder task refs such as #00000000'); expect(briefing).not.toContain('task_get_comment {'); expect(briefing).not.toContain('notify your team lead via SendMessage'); }); + it('strips hallucinated zero task placeholder prefixes from visible messages', () => { + const claudeDir = makeClaudeDir(); + const controller = createController({ teamName: 'my-team', claudeDir }); + + controller.messages.sendMessage({ + to: 'user', + from: 'bob', + text: '#00000000 bootstrap check-in and briefing retrieved. No actionable tasks.', + summary: '#00000000 ready', + }); + + const userInboxPath = path.join(claudeDir, 'teams', 'my-team', 'inboxes', 'user.json'); + const rows = JSON.parse(fs.readFileSync(userInboxPath, 'utf8')); + expect(rows[0].text).toBe('bootstrap check-in and briefing retrieved. No actionable tasks.'); + expect(rows[0].summary).toBe('ready'); + }); + it('does not infer OpenCode briefing from a generic provider-scoped model alone', async () => { const claudeDir = makeClaudeDir(); const configPath = path.join(claudeDir, 'teams', 'my-team', 'config.json'); diff --git a/mcp-server/src/tools/messageTools.ts b/mcp-server/src/tools/messageTools.ts index 56e8f46b..91419a1c 100644 --- a/mcp-server/src/tools/messageTools.ts +++ b/mcp-server/src/tools/messageTools.ts @@ -14,7 +14,7 @@ export function registerMessageTools(server: Pick) { server.addTool({ name: 'message_send', description: - 'Send a visible team/user message into team inbox. OpenCode teammates should use this for normal replies to the human user, lead, or same-team teammates. When to is "user", from is required and must be your configured teammate name.', + 'Send a visible team/user message into team inbox. OpenCode teammates should use this for normal replies to the human user, lead, or same-team teammates. When to is "user", from is required and must be your configured teammate name. Do not invent placeholder task refs. If the message is not about a real board task, omit # task labels; never use #00000000.', parameters: z.object({ ...toolContextSchema, to: z.string().min(1), diff --git a/mcp-server/test/tools.test.ts b/mcp-server/test/tools.test.ts index e1788d1b..befbdd40 100644 --- a/mcp-server/test/tools.test.ts +++ b/mcp-server/test/tools.test.ts @@ -592,6 +592,9 @@ describe('agent-teams-mcp tools', () => { ).content[0]?.text; expect(openCodeMemberBriefingText).toContain('agent-teams_message_send'); expect(openCodeMemberBriefingText).toContain('Full details in task comment e5f6a7b8'); + expect(openCodeMemberBriefingText).toContain( + 'Never invent placeholder task refs such as #00000000' + ); expect(openCodeMemberBriefingText).not.toContain('task_get_comment {'); expect(openCodeMemberBriefingText).not.toContain('notify your team lead via SendMessage'); }); diff --git a/runtime.lock.json b/runtime.lock.json index 7de2bad8..cdaa66c4 100644 --- a/runtime.lock.json +++ b/runtime.lock.json @@ -1,27 +1,27 @@ { - "version": "0.0.7", - "sourceRef": "v0.0.7", + "version": "0.0.8", + "sourceRef": "v0.0.8", "sourceRepository": "777genius/agent_teams_orchestrator", "releaseRepository": "777genius/claude_agent_teams_ui", "releaseTag": "v1.2.0", "assets": { "darwin-arm64": { - "file": "agent-teams-runtime-darwin-arm64-v0.0.7.tar.gz", + "file": "agent-teams-runtime-darwin-arm64-v0.0.8.tar.gz", "archiveKind": "tar.gz", "binaryName": "claude-multimodel" }, "darwin-x64": { - "file": "agent-teams-runtime-darwin-x64-v0.0.7.tar.gz", + "file": "agent-teams-runtime-darwin-x64-v0.0.8.tar.gz", "archiveKind": "tar.gz", "binaryName": "claude-multimodel" }, "linux-x64": { - "file": "agent-teams-runtime-linux-x64-v0.0.7.tar.gz", + "file": "agent-teams-runtime-linux-x64-v0.0.8.tar.gz", "archiveKind": "tar.gz", "binaryName": "claude-multimodel" }, "win32-x64": { - "file": "agent-teams-runtime-win32-x64-v0.0.7.zip", + "file": "agent-teams-runtime-win32-x64-v0.0.8.zip", "archiveKind": "zip", "binaryName": "claude-multimodel.exe" } diff --git a/src/main/services/team/runtime/OpenCodeTeamRuntimeAdapter.ts b/src/main/services/team/runtime/OpenCodeTeamRuntimeAdapter.ts index 2f13b866..2cb5a965 100644 --- a/src/main/services/team/runtime/OpenCodeTeamRuntimeAdapter.ts +++ b/src/main/services/team/runtime/OpenCodeTeamRuntimeAdapter.ts @@ -585,6 +585,7 @@ function buildOpenCodeRuntimeMessageText(input: OpenCodeTeamRuntimeMessageInput) `Use teamName="${input.teamName}", to="${replyRecipient}", from="${input.memberName}", text, and summary.`, 'Do not answer only with plain assistant text when agent-teams_message_send is available.', 'Do not use SendMessage or runtime_deliver_message for ordinary visible replies.', + 'Do not invent placeholder task labels. If no explicit taskRefs are provided and the reply is not about a real board task, do not prefix text or summary with a # task label; never use #00000000.', input.actionMode ? `Action mode for this message: ${input.actionMode}.` : null, taskRefs ? `If your reply is about these tasks, include taskRefs exactly: ${taskRefs}` : null, input.messageId diff --git a/test/main/services/team/OpenCodeTeamRuntimeAdapter.test.ts b/test/main/services/team/OpenCodeTeamRuntimeAdapter.test.ts index 21d4ce23..2d9e3d84 100644 --- a/test/main/services/team/OpenCodeTeamRuntimeAdapter.test.ts +++ b/test/main/services/team/OpenCodeTeamRuntimeAdapter.test.ts @@ -359,6 +359,7 @@ describe('OpenCodeTeamRuntimeAdapter', () => { 'If your reply is about these tasks, include taskRefs exactly: [{"taskId":"task-1","displayId":"abcd1234","teamName":"team-a"}]' ); expect(sentText).toContain('Do not use SendMessage or runtime_deliver_message'); + expect(sentText).toContain('never use #00000000'); }); it('does not parse legacy native SendMessage wording to infer OpenCode reply recipient', async () => {