feat(team): harden messaging tools and runtime bootstrap

This commit is contained in:
777genius 2026-04-24 23:44:44 +03:00
parent e6e3ae9f54
commit dec0eaba18
8 changed files with 54 additions and 8 deletions

View file

@ -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);
}

View file

@ -684,6 +684,8 @@ function buildMemberTaskProtocol(teamName, messagingProtocol = createMemberMessa
{ teamName: "${teamName}", taskId: "<taskId>", text: "<summary of your finding or decision>", from: "<your-name>" }
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 #<displayId> 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 #<displayId>.
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.

View file

@ -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');

View file

@ -14,7 +14,7 @@ export function registerMessageTools(server: Pick<FastMCP, 'addTool'>) {
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),

View file

@ -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');
});

View file

@ -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"
}

View file

@ -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

View file

@ -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 () => {