Download src/tools/SendMessageTool/SendMessageTool.ts from chenbhao/codev: direct link, hf CLI and curl.
- Browser
- Download file 27.5 kB
-
https://huggingface.co/chenbhao/codev/resolve/main/src/tools/SendMessageTool/SendMessageTool.ts
- Command line
-
hf download hf://chenbhao/codev/src/tools/SendMessageTool/SendMessageTool.ts
-
curl -L -o SendMessageTool.ts https://huggingface.co/chenbhao/codev/resolve/main/src/tools/SendMessageTool/SendMessageTool.ts
27.5 kB
| import { feature } from 'bun:bundle' | |
| import { z } from 'zod/v4' | |
| import { isReplBridgeActive } from '../../bootstrap/state.js' | |
| import { getReplBridgeHandle } from '../../bridge/replBridgeHandle.js' | |
| import type { Tool, ToolUseContext } from '../../Tool.js' | |
| import { buildTool, type ToolDef } from '../../Tool.js' | |
| import { findTeammateTaskByAgentId } from '../../tasks/InProcessTeammateTask/InProcessTeammateTask.js' | |
| import { | |
| isLocalAgentTask, | |
| queuePendingMessage, | |
| } from '../../tasks/LocalAgentTask/LocalAgentTask.js' | |
| import { isMainSessionTask } from '../../tasks/LocalMainSessionTask.js' | |
| import { toAgentId } from '../../types/ids.js' | |
| import { generateRequestId } from '../../utils/agentId.js' | |
| import { isAgentSwarmsEnabled } from '../../utils/agentSwarmsEnabled.js' | |
| import { logForDebugging } from '../../utils/debug.js' | |
| import { errorMessage } from '../../utils/errors.js' | |
| import { truncate } from '../../utils/format.js' | |
| import { gracefulShutdown } from '../../utils/gracefulShutdown.js' | |
| import { lazySchema } from '../../utils/lazySchema.js' | |
| import { parseAddress } from '../../utils/peerAddress.js' | |
| import { semanticBoolean } from '../../utils/semanticBoolean.js' | |
| import { jsonStringify } from '../../utils/slowOperations.js' | |
| import type { BackendType } from '../../utils/swarm/backends/types.js' | |
| import { TEAM_LEAD_NAME } from '../../utils/swarm/constants.js' | |
| import { readTeamFileAsync } from '../../utils/swarm/teamHelpers.js' | |
| import { | |
| getAgentId, | |
| getAgentName, | |
| getTeammateColor, | |
| getTeamName, | |
| isTeamLead, | |
| isTeammate, | |
| } from '../../utils/teammate.js' | |
| import { | |
| createShutdownApprovedMessage, | |
| createShutdownRejectedMessage, | |
| createShutdownRequestMessage, | |
| writeToMailbox, | |
| } from '../../utils/teammateMailbox.js' | |
| import { resumeAgentBackground } from '../AgentTool/resumeAgent.js' | |
| import { SEND_MESSAGE_TOOL_NAME } from './constants.js' | |
| import { DESCRIPTION, getPrompt } from './prompt.js' | |
| import { renderToolResultMessage, renderToolUseMessage } from './UI.js' | |
| const StructuredMessage = lazySchema(() => | |
| z.discriminatedUnion('type', [ | |
| z.object({ | |
| type: z.literal('shutdown_request'), | |
| reason: z.string().optional(), | |
| }), | |
| z.object({ | |
| type: z.literal('shutdown_response'), | |
| request_id: z.string(), | |
| approve: semanticBoolean(), | |
| reason: z.string().optional(), | |
| }), | |
| z.object({ | |
| type: z.literal('plan_approval_response'), | |
| request_id: z.string(), | |
| approve: semanticBoolean(), | |
| feedback: z.string().optional(), | |
| }), | |
| ]), | |
| ) | |
| const inputSchema = lazySchema(() => | |
| z.object({ | |
| to: z | |
| .string() | |
| .describe( | |
| feature('UDS_INBOX') | |
| ? 'Recipient: teammate name, "*" for broadcast, "uds:<socket-path>" for a local peer, or "bridge:<session-id>" for a Remote Control peer (use ListPeers to discover)' | |
| : 'Recipient: teammate name, or "*" for broadcast to all teammates', | |
| ), | |
| summary: z | |
| .string() | |
| .optional() | |
| .describe( | |
| 'A 5-10 word summary shown as a preview in the UI (required when message is a string)', | |
| ), | |
| message: z.union([ | |
| z.string().describe('Plain text message content'), | |
| StructuredMessage(), | |
| ]), | |
| }), | |
| ) | |
| type InputSchema = ReturnType<typeof inputSchema> | |
| export type Input = z.infer<InputSchema> | |
| export type MessageRouting = { | |
| sender: string | |
| senderColor?: string | |
| target: string | |
| targetColor?: string | |
| summary?: string | |
| content?: string | |
| } | |
| export type MessageOutput = { | |
| success: boolean | |
| message: string | |
| routing?: MessageRouting | |
| } | |
| export type BroadcastOutput = { | |
| success: boolean | |
| message: string | |
| recipients: string[] | |
| routing?: MessageRouting | |
| } | |
| export type RequestOutput = { | |
| success: boolean | |
| message: string | |
| request_id: string | |
| target: string | |
| } | |
| export type ResponseOutput = { | |
| success: boolean | |
| message: string | |
| request_id?: string | |
| } | |
| export type SendMessageToolOutput = | |
| | MessageOutput | |
| | BroadcastOutput | |
| | RequestOutput | |
| | ResponseOutput | |
| function findTeammateColor( | |
| appState: { | |
| teamContext?: { teammates: { [id: string]: { color?: string } } } | |
| }, | |
| name: string, | |
| ): string | undefined { | |
| const teammates = appState.teamContext?.teammates | |
| if (!teammates) return undefined | |
| for (const teammate of Object.values(teammates)) { | |
| if ('name' in teammate && (teammate as { name: string }).name === name) { | |
| return teammate.color | |
| } | |
| } | |
| return undefined | |
| } | |
| async function handleMessage( | |
| recipientName: string, | |
| content: string, | |
| summary: string | undefined, | |
| context: ToolUseContext, | |
| ): Promise<{ data: MessageOutput }> { | |
| const appState = context.getAppState() | |
| const teamName = getTeamName(appState.teamContext) | |
| const senderName = | |
| getAgentName() || (isTeammate() ? 'teammate' : TEAM_LEAD_NAME) | |
| const senderColor = getTeammateColor() | |
| await writeToMailbox( | |
| recipientName, | |
| { | |
| from: senderName, | |
| text: content, | |
| summary, | |
| timestamp: new Date().toISOString(), | |
| color: senderColor, | |
| }, | |
| teamName, | |
| ) | |
| const recipientColor = findTeammateColor(appState, recipientName) | |
| return { | |
| data: { | |
| success: true, | |
| message: `Message sent to ${recipientName}'s inbox`, | |
| routing: { | |
| sender: senderName, | |
| senderColor, | |
| target: `@${recipientName}`, | |
| targetColor: recipientColor, | |
| summary, | |
| content, | |
| }, | |
| }, | |
| } | |
| } | |
| async function handleBroadcast( | |
| content: string, | |
| summary: string | undefined, | |
| context: ToolUseContext, | |
| ): Promise<{ data: BroadcastOutput }> { | |
| const appState = context.getAppState() | |
| const teamName = getTeamName(appState.teamContext) | |
| if (!teamName) { | |
| throw new Error( | |
| 'Not in a team context. Create a team with Teammate spawnTeam first, or set CLAUDE_CODE_TEAM_NAME.', | |
| ) | |
| } | |
| const teamFile = await readTeamFileAsync(teamName) | |
| if (!teamFile) { | |
| throw new Error(`Team "${teamName}" does not exist`) | |
| } | |
| const senderName = | |
| getAgentName() || (isTeammate() ? 'teammate' : TEAM_LEAD_NAME) | |
| if (!senderName) { | |
| throw new Error( | |
| 'Cannot broadcast: sender name is required. Set CLAUDE_CODE_AGENT_NAME.', | |
| ) | |
| } | |
| const senderColor = getTeammateColor() | |
| const recipients: string[] = [] | |
| for (const member of teamFile.members) { | |
| if (member.name.toLowerCase() === senderName.toLowerCase()) { | |
| continue | |
| } | |
| recipients.push(member.name) | |
| } | |
| if (recipients.length === 0) { | |
| return { | |
| data: { | |
| success: true, | |
| message: 'No teammates to broadcast to (you are the only team member)', | |
| recipients: [], | |
| }, | |
| } | |
| } | |
| for (const recipientName of recipients) { | |
| await writeToMailbox( | |
| recipientName, | |
| { | |
| from: senderName, | |
| text: content, | |
| summary, | |
| timestamp: new Date().toISOString(), | |
| color: senderColor, | |
| }, | |
| teamName, | |
| ) | |
| } | |
| return { | |
| data: { | |
| success: true, | |
| message: `Message broadcast to ${recipients.length} teammate(s): ${recipients.join(', ')}`, | |
| recipients, | |
| routing: { | |
| sender: senderName, | |
| senderColor, | |
| target: '@team', | |
| summary, | |
| content, | |
| }, | |
| }, | |
| } | |
| } | |
| async function handleShutdownRequest( | |
| targetName: string, | |
| reason: string | undefined, | |
| context: ToolUseContext, | |
| ): Promise<{ data: RequestOutput }> { | |
| const appState = context.getAppState() | |
| const teamName = getTeamName(appState.teamContext) | |
| const senderName = getAgentName() || TEAM_LEAD_NAME | |
| const requestId = generateRequestId('shutdown', targetName) | |
| const shutdownMessage = createShutdownRequestMessage({ | |
| requestId, | |
| from: senderName, | |
| reason, | |
| }) | |
| await writeToMailbox( | |
| targetName, | |
| { | |
| from: senderName, | |
| text: jsonStringify(shutdownMessage), | |
| timestamp: new Date().toISOString(), | |
| color: getTeammateColor(), | |
| }, | |
| teamName, | |
| ) | |
| return { | |
| data: { | |
| success: true, | |
| message: `Shutdown request sent to ${targetName}. Request ID: ${requestId}`, | |
| request_id: requestId, | |
| target: targetName, | |
| }, | |
| } | |
| } | |
| async function handleShutdownApproval( | |
| requestId: string, | |
| context: ToolUseContext, | |
| ): Promise<{ data: ResponseOutput }> { | |
| const teamName = getTeamName() | |
| const agentId = getAgentId() | |
| const agentName = getAgentName() || 'teammate' | |
| logForDebugging( | |
| `[SendMessageTool] handleShutdownApproval: teamName=${teamName}, agentId=${agentId}, agentName=${agentName}`, | |
| ) | |
| let ownPaneId: string | undefined | |
| let ownBackendType: BackendType | undefined | |
| if (teamName) { | |
| const teamFile = await readTeamFileAsync(teamName) | |
| if (teamFile && agentId) { | |
| const selfMember = teamFile.members.find(m => m.agentId === agentId) | |
| if (selfMember) { | |
| ownPaneId = selfMember.tmuxPaneId | |
| ownBackendType = selfMember.backendType | |
| } | |
| } | |
| } | |
| const approvedMessage = createShutdownApprovedMessage({ | |
| requestId, | |
| from: agentName, | |
| paneId: ownPaneId, | |
| backendType: ownBackendType, | |
| }) | |
| await writeToMailbox( | |
| TEAM_LEAD_NAME, | |
| { | |
| from: agentName, | |
| text: jsonStringify(approvedMessage), | |
| timestamp: new Date().toISOString(), | |
| color: getTeammateColor(), | |
| }, | |
| teamName, | |
| ) | |
| if (ownBackendType === 'in-process') { | |
| logForDebugging( | |
| `[SendMessageTool] In-process teammate ${agentName} approving shutdown - signaling abort`, | |
| ) | |
| if (agentId) { | |
| const appState = context.getAppState() | |
| const task = findTeammateTaskByAgentId(agentId, appState.tasks) | |
| if (task?.abortController) { | |
| task.abortController.abort() | |
| logForDebugging( | |
| `[SendMessageTool] Aborted controller for in-process teammate ${agentName}`, | |
| ) | |
| } else { | |
| logForDebugging( | |
| `[SendMessageTool] Warning: Could not find task/abortController for ${agentName}`, | |
| ) | |
| } | |
| } | |
| } else { | |
| if (agentId) { | |
| const appState = context.getAppState() | |
| const task = findTeammateTaskByAgentId(agentId, appState.tasks) | |
| if (task?.abortController) { | |
| logForDebugging( | |
| `[SendMessageTool] Fallback: Found in-process task for ${agentName} via AppState, aborting`, | |
| ) | |
| task.abortController.abort() | |
| return { | |
| data: { | |
| success: true, | |
| message: `Shutdown approved (fallback path). Agent ${agentName} is now exiting.`, | |
| request_id: requestId, | |
| }, | |
| } | |
| } | |
| } | |
| setImmediate(async () => { | |
| await gracefulShutdown(0, 'other') | |
| }) | |
| } | |
| return { | |
| data: { | |
| success: true, | |
| message: `Shutdown approved. Sent confirmation to team-lead. Agent ${agentName} is now exiting.`, | |
| request_id: requestId, | |
| }, | |
| } | |
| } | |
| async function handleShutdownRejection( | |
| requestId: string, | |
| reason: string, | |
| ): Promise<{ data: ResponseOutput }> { | |
| const teamName = getTeamName() | |
| const agentName = getAgentName() || 'teammate' | |
| const rejectedMessage = createShutdownRejectedMessage({ | |
| requestId, | |
| from: agentName, | |
| reason, | |
| }) | |
| await writeToMailbox( | |
| TEAM_LEAD_NAME, | |
| { | |
| from: agentName, | |
| text: jsonStringify(rejectedMessage), | |
| timestamp: new Date().toISOString(), | |
| color: getTeammateColor(), | |
| }, | |
| teamName, | |
| ) | |
| return { | |
| data: { | |
| success: true, | |
| message: `Shutdown rejected. Reason: "${reason}". Continuing to work.`, | |
| request_id: requestId, | |
| }, | |
| } | |
| } | |
| async function handlePlanApproval( | |
| recipientName: string, | |
| requestId: string, | |
| context: ToolUseContext, | |
| ): Promise<{ data: ResponseOutput }> { | |
| const appState = context.getAppState() | |
| const teamName = appState.teamContext?.teamName | |
| if (!isTeamLead(appState.teamContext)) { | |
| throw new Error( | |
| 'Only the team lead can approve plans. Teammates cannot approve their own or other plans.', | |
| ) | |
| } | |
| const leaderMode = appState.toolPermissionContext.mode | |
| const modeToInherit = leaderMode === 'plan' ? 'default' : leaderMode | |
| const approvalResponse = { | |
| type: 'plan_approval_response', | |
| requestId, | |
| approved: true, | |
| timestamp: new Date().toISOString(), | |
| permissionMode: modeToInherit, | |
| } | |
| await writeToMailbox( | |
| recipientName, | |
| { | |
| from: TEAM_LEAD_NAME, | |
| text: jsonStringify(approvalResponse), | |
| timestamp: new Date().toISOString(), | |
| }, | |
| teamName, | |
| ) | |
| return { | |
| data: { | |
| success: true, | |
| message: `Plan approved for ${recipientName}. They will receive the approval and can proceed with implementation.`, | |
| request_id: requestId, | |
| }, | |
| } | |
| } | |
| async function handlePlanRejection( | |
| recipientName: string, | |
| requestId: string, | |
| feedback: string, | |
| context: ToolUseContext, | |
| ): Promise<{ data: ResponseOutput }> { | |
| const appState = context.getAppState() | |
| const teamName = appState.teamContext?.teamName | |
| if (!isTeamLead(appState.teamContext)) { | |
| throw new Error( | |
| 'Only the team lead can reject plans. Teammates cannot reject their own or other plans.', | |
| ) | |
| } | |
| const rejectionResponse = { | |
| type: 'plan_approval_response', | |
| requestId, | |
| approved: false, | |
| feedback, | |
| timestamp: new Date().toISOString(), | |
| } | |
| await writeToMailbox( | |
| recipientName, | |
| { | |
| from: TEAM_LEAD_NAME, | |
| text: jsonStringify(rejectionResponse), | |
| timestamp: new Date().toISOString(), | |
| }, | |
| teamName, | |
| ) | |
| return { | |
| data: { | |
| success: true, | |
| message: `Plan rejected for ${recipientName} with feedback: "${feedback}"`, | |
| request_id: requestId, | |
| }, | |
| } | |
| } | |
| export const SendMessageTool: Tool<InputSchema, SendMessageToolOutput> = | |
| buildTool({ | |
| name: SEND_MESSAGE_TOOL_NAME, | |
| searchHint: 'send messages to agent teammates (swarm protocol)', | |
| maxResultSizeChars: 100_000, | |
| userFacingName() { | |
| return 'SendMessage' | |
| }, | |
| get inputSchema(): InputSchema { | |
| return inputSchema() | |
| }, | |
| shouldDefer: true, | |
| isEnabled() { | |
| return isAgentSwarmsEnabled() | |
| }, | |
| isReadOnly(input) { | |
| return typeof input.message === 'string' | |
| }, | |
| backfillObservableInput(input) { | |
| if ('type' in input) return | |
| if (typeof input.to !== 'string') return | |
| if (input.to === '*') { | |
| input.type = 'broadcast' | |
| if (typeof input.message === 'string') input.content = input.message | |
| } else if (typeof input.message === 'string') { | |
| input.type = 'message' | |
| input.recipient = input.to | |
| input.content = input.message | |
| } else if (typeof input.message === 'object' && input.message !== null) { | |
| const msg = input.message as { | |
| type?: string | |
| request_id?: string | |
| approve?: boolean | |
| reason?: string | |
| feedback?: string | |
| } | |
| input.type = msg.type | |
| input.recipient = input.to | |
| if (msg.request_id !== undefined) input.request_id = msg.request_id | |
| if (msg.approve !== undefined) input.approve = msg.approve | |
| const content = msg.reason ?? msg.feedback | |
| if (content !== undefined) input.content = content | |
| } | |
| }, | |
| toAutoClassifierInput(input) { | |
| if (typeof input.message === 'string') { | |
| return `to ${input.to}: ${input.message}` | |
| } | |
| switch (input.message.type) { | |
| case 'shutdown_request': | |
| return `shutdown_request to ${input.to}` | |
| case 'shutdown_response': | |
| return `shutdown_response ${input.message.approve ? 'approve' : 'reject'} ${input.message.request_id}` | |
| case 'plan_approval_response': | |
| return `plan_approval ${input.message.approve ? 'approve' : 'reject'} to ${input.to}` | |
| } | |
| }, | |
| async checkPermissions(input, _context) { | |
| if (feature('UDS_INBOX') && parseAddress(input.to).scheme === 'bridge') { | |
| return { | |
| behavior: 'ask' as const, | |
| message: `Send a message to Remote Control session ${input.to}? It arrives as a user prompt on the receiving Claude (possibly another machine) via Anthropic's servers.`, | |
| // safetyCheck (not mode) — permissions.ts guards this before both | |
| // bypassPermissions (step 1g) and auto-mode's allowlist/classifier. | |
| // Cross-machine prompt injection must stay bypass-immune. | |
| decisionReason: { | |
| type: 'safetyCheck', | |
| reason: | |
| 'Cross-machine bridge message requires explicit user consent', | |
| classifierApprovable: false, | |
| }, | |
| } | |
| } | |
| return { behavior: 'allow' as const, updatedInput: input } | |
| }, | |
| async validateInput(input, _context) { | |
| if (input.to.trim().length === 0) { | |
| return { | |
| result: false, | |
| message: 'to must not be empty', | |
| errorCode: 9, | |
| } | |
| } | |
| const addr = parseAddress(input.to) | |
| if ( | |
| (addr.scheme === 'bridge' || addr.scheme === 'uds') && | |
| addr.target.trim().length === 0 | |
| ) { | |
| return { | |
| result: false, | |
| message: 'address target must not be empty', | |
| errorCode: 9, | |
| } | |
| } | |
| if (input.to.includes('@')) { | |
| return { | |
| result: false, | |
| message: | |
| 'to must be a bare teammate name or "*" — there is only one team per session', | |
| errorCode: 9, | |
| } | |
| } | |
| if (feature('UDS_INBOX') && parseAddress(input.to).scheme === 'bridge') { | |
| // Structured-message rejection first — it's the permanent constraint. | |
| // Showing "not connected" first would make the user reconnect only to | |
| // hit this error on retry. | |
| if (typeof input.message !== 'string') { | |
| return { | |
| result: false, | |
| message: | |
| 'structured messages cannot be sent cross-session — only plain text', | |
| errorCode: 9, | |
| } | |
| } | |
| // postInterClaudeMessage derives from= via getReplBridgeHandle() — | |
| // check handle directly for the init-timing window. Also check | |
| // isReplBridgeActive() to reject outbound-only (CCR mirror) mode | |
| // where the bridge is write-only and peer messaging is unsupported. | |
| if (!getReplBridgeHandle() || !isReplBridgeActive()) { | |
| return { | |
| result: false, | |
| message: | |
| 'Remote Control is not connected — cannot send to a bridge: target. Reconnect with /remote-control first.', | |
| errorCode: 9, | |
| } | |
| } | |
| return { result: true } | |
| } | |
| if ( | |
| feature('UDS_INBOX') && | |
| parseAddress(input.to).scheme === 'uds' && | |
| typeof input.message === 'string' | |
| ) { | |
| // UDS cross-session send: summary isn't rendered (UI.tsx returns null | |
| // for string messages), so don't require it. Structured messages fall | |
| // through to the rejection below. | |
| return { result: true } | |
| } | |
| if (typeof input.message === 'string') { | |
| if (!input.summary || input.summary.trim().length === 0) { | |
| return { | |
| result: false, | |
| message: 'summary is required when message is a string', | |
| errorCode: 9, | |
| } | |
| } | |
| return { result: true } | |
| } | |
| if (input.to === '*') { | |
| return { | |
| result: false, | |
| message: 'structured messages cannot be broadcast (to: "*")', | |
| errorCode: 9, | |
| } | |
| } | |
| if (feature('UDS_INBOX') && parseAddress(input.to).scheme !== 'other') { | |
| return { | |
| result: false, | |
| message: | |
| 'structured messages cannot be sent cross-session — only plain text', | |
| errorCode: 9, | |
| } | |
| } | |
| if ( | |
| input.message.type === 'shutdown_response' && | |
| input.to !== TEAM_LEAD_NAME | |
| ) { | |
| return { | |
| result: false, | |
| message: `shutdown_response must be sent to "${TEAM_LEAD_NAME}"`, | |
| errorCode: 9, | |
| } | |
| } | |
| if ( | |
| input.message.type === 'shutdown_response' && | |
| !input.message.approve && | |
| (!input.message.reason || input.message.reason.trim().length === 0) | |
| ) { | |
| return { | |
| result: false, | |
| message: 'reason is required when rejecting a shutdown request', | |
| errorCode: 9, | |
| } | |
| } | |
| return { result: true } | |
| }, | |
| async description() { | |
| return DESCRIPTION | |
| }, | |
| async prompt() { | |
| return getPrompt() | |
| }, | |
| mapToolResultToToolResultBlockParam(data, toolUseID) { | |
| return { | |
| tool_use_id: toolUseID, | |
| type: 'tool_result' as const, | |
| content: [ | |
| { | |
| type: 'text' as const, | |
| text: jsonStringify(data), | |
| }, | |
| ], | |
| } | |
| }, | |
| async call(input, context, canUseTool, assistantMessage) { | |
| if (feature('UDS_INBOX') && typeof input.message === 'string') { | |
| const addr = parseAddress(input.to) | |
| if (addr.scheme === 'bridge') { | |
| // Re-check handle — checkPermissions blocks on user approval (can be | |
| // minutes). validateInput's check is stale if the bridge dropped | |
| // during the prompt wait; without this, from="unknown" ships. | |
| // Also re-check isReplBridgeActive for outbound-only mode. | |
| if (!getReplBridgeHandle() || !isReplBridgeActive()) { | |
| return { | |
| data: { | |
| success: false, | |
| message: `Remote Control disconnected before send — cannot deliver to ${input.to}`, | |
| }, | |
| } | |
| } | |
| /* eslint-disable @typescript-eslint/no-require-imports */ | |
| const { postInterClaudeMessage } = | |
| require('../../bridge/peerSessions.js') as typeof import('../../bridge/peerSessions.js') | |
| /* eslint-enable @typescript-eslint/no-require-imports */ | |
| const result = await postInterClaudeMessage( | |
| addr.target, | |
| input.message, | |
| ) | |
| const preview = input.summary || truncate(input.message, 50) | |
| return { | |
| data: { | |
| success: result.ok, | |
| message: result.ok | |
| ? `“${preview}” → ${input.to}` | |
| : `Failed to send to ${input.to}: ${result.error ?? 'unknown'}`, | |
| }, | |
| } | |
| } | |
| if (addr.scheme === 'uds') { | |
| /* eslint-disable @typescript-eslint/no-require-imports */ | |
| const { sendToUdsSocket } = | |
| require('../../utils/udsClient.js') as typeof import('../../utils/udsClient.js') | |
| /* eslint-enable @typescript-eslint/no-require-imports */ | |
| try { | |
| await sendToUdsSocket(addr.target, input.message) | |
| const preview = input.summary || truncate(input.message, 50) | |
| return { | |
| data: { | |
| success: true, | |
| message: `“${preview}” → ${input.to}`, | |
| }, | |
| } | |
| } catch (e) { | |
| return { | |
| data: { | |
| success: false, | |
| message: `Failed to send to ${input.to}: ${errorMessage(e)}`, | |
| }, | |
| } | |
| } | |
| } | |
| } | |
| // Route to in-process subagent by name or raw agentId before falling | |
| // through to ambient-team resolution. Stopped agents are auto-resumed. | |
| if (typeof input.message === 'string' && input.to !== '*') { | |
| const appState = context.getAppState() | |
| const registered = appState.agentNameRegistry.get(input.to) | |
| const agentId = registered ?? toAgentId(input.to) | |
| if (agentId) { | |
| const task = appState.tasks[agentId] | |
| if (isLocalAgentTask(task) && !isMainSessionTask(task)) { | |
| if (task.status === 'running') { | |
| queuePendingMessage( | |
| agentId, | |
| input.message, | |
| context.setAppStateForTasks ?? context.setAppState, | |
| ) | |
| return { | |
| data: { | |
| success: true, | |
| message: `Message queued for delivery to ${input.to} at its next tool round.`, | |
| }, | |
| } | |
| } | |
| // task exists but stopped — auto-resume | |
| try { | |
| const result = await resumeAgentBackground({ | |
| agentId, | |
| prompt: input.message, | |
| toolUseContext: context, | |
| canUseTool, | |
| invokingRequestId: assistantMessage?.requestId, | |
| }) | |
| return { | |
| data: { | |
| success: true, | |
| message: `Agent "${input.to}" was stopped (${task.status}); resumed it in the background with your message. You'll be notified when it finishes. Output: ${result.outputFile}`, | |
| }, | |
| } | |
| } catch (e) { | |
| return { | |
| data: { | |
| success: false, | |
| message: `Agent "${input.to}" is stopped (${task.status}) and could not be resumed: ${errorMessage(e)}`, | |
| }, | |
| } | |
| } | |
| } else { | |
| // task evicted from state — try resume from disk transcript. | |
| // agentId is either a registered name or a format-matching raw ID | |
| // (toAgentId validates the createAgentId format, so teammate names | |
| // never reach this block). | |
| try { | |
| const result = await resumeAgentBackground({ | |
| agentId, | |
| prompt: input.message, | |
| toolUseContext: context, | |
| canUseTool, | |
| invokingRequestId: assistantMessage?.requestId, | |
| }) | |
| return { | |
| data: { | |
| success: true, | |
| message: `Agent "${input.to}" had no active task; resumed from transcript in the background with your message. You'll be notified when it finishes. Output: ${result.outputFile}`, | |
| }, | |
| } | |
| } catch (e) { | |
| return { | |
| data: { | |
| success: false, | |
| message: `Agent "${input.to}" is registered but has no transcript to resume. It may have been cleaned up. (${errorMessage(e)})`, | |
| }, | |
| } | |
| } | |
| } | |
| } | |
| } | |
| if (typeof input.message === 'string') { | |
| if (input.to === '*') { | |
| return handleBroadcast(input.message, input.summary, context) | |
| } | |
| return handleMessage(input.to, input.message, input.summary, context) | |
| } | |
| if (input.to === '*') { | |
| throw new Error('structured messages cannot be broadcast') | |
| } | |
| switch (input.message.type) { | |
| case 'shutdown_request': | |
| return handleShutdownRequest(input.to, input.message.reason, context) | |
| case 'shutdown_response': | |
| if (input.message.approve) { | |
| return handleShutdownApproval(input.message.request_id, context) | |
| } | |
| return handleShutdownRejection( | |
| input.message.request_id, | |
| input.message.reason!, | |
| ) | |
| case 'plan_approval_response': | |
| if (input.message.approve) { | |
| return handlePlanApproval( | |
| input.to, | |
| input.message.request_id, | |
| context, | |
| ) | |
| } | |
| return handlePlanRejection( | |
| input.to, | |
| input.message.request_id, | |
| input.message.feedback ?? 'Plan needs revision', | |
| context, | |
| ) | |
| } | |
| }, | |
| renderToolUseMessage, | |
| renderToolResultMessage, | |
| } satisfies ToolDef<InputSchema, SendMessageToolOutput>) | |