feat(team): harden messaging tools and runtime bootstrap
This commit is contained in:
parent
e6e3ae9f54
commit
dec0eaba18
8 changed files with 54 additions and 8 deletions
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 () => {
|
||||
|
|
|
|||
Loading…
Reference in a new issue