diff --git a/src/agents/subagent-announce.ts b/src/agents/subagent-announce.ts index 5070b204392..e95e657eb19 100644 --- a/src/agents/subagent-announce.ts +++ b/src/agents/subagent-announce.ts @@ -1170,6 +1170,7 @@ export async function runSubagentAnnounceFlow(params: { wakeOnDescendantSettle?: boolean; signal?: AbortSignal; bestEffortDeliver?: boolean; + maxAnnounceChars?: number; }): Promise { let didAnnounce = false; const expectsCompletionMessage = params.expectsCompletionMessage === true; @@ -1395,6 +1396,16 @@ export async function runSubagentAnnounceFlow(params: { startedAt: params.startedAt, endedAt: params.endedAt, }); + + // Apply maxAnnounceChars truncation if specified + const truncatedFindings = + typeof params.maxAnnounceChars === "number" && + params.maxAnnounceChars >= 1 && + findings.length > params.maxAnnounceChars + ? findings.slice(0, params.maxAnnounceChars) + + "\n\n[truncated — full output in transcript]" + : findings; + const internalEvents: AgentInternalEvent[] = [ { type: "task_completion", @@ -1405,7 +1416,7 @@ export async function runSubagentAnnounceFlow(params: { taskLabel, status: outcome.status, statusLabel, - result: findings, + result: truncatedFindings, statsLine, replyInstruction, }, diff --git a/src/agents/subagent-registry.ts b/src/agents/subagent-registry.ts index d9c593c3e84..95d233c4a17 100644 --- a/src/agents/subagent-registry.ts +++ b/src/agents/subagent-registry.ts @@ -565,6 +565,7 @@ function startSubagentAnnounceCleanupFlow(runId: string, entry: SubagentRunRecor spawnMode: entry.spawnMode, expectsCompletionMessage: entry.expectsCompletionMessage, wakeOnDescendantSettle: entry.wakeOnDescendantSettle === true, + maxAnnounceChars: entry.maxAnnounceChars, }) .then((didAnnounce) => { finalizeAnnounceCleanup(didAnnounce); @@ -1171,6 +1172,7 @@ export function registerSubagentRun(params: { runTimeoutSeconds?: number; expectsCompletionMessage?: boolean; spawnMode?: "run" | "session"; + maxAnnounceChars?: number; attachmentsDir?: string; attachmentsRootDir?: string; retainAttachmentsOnKeep?: boolean; @@ -1204,6 +1206,10 @@ export function registerSubagentRun(params: { archiveAtMs, cleanupHandled: false, wakeOnDescendantSettle: undefined, + maxAnnounceChars: + typeof params.maxAnnounceChars === "number" && Number.isFinite(params.maxAnnounceChars) + ? Math.max(1, Math.floor(params.maxAnnounceChars)) + : undefined, attachmentsDir: params.attachmentsDir, attachmentsRootDir: params.attachmentsRootDir, retainAttachmentsOnKeep: params.retainAttachmentsOnKeep, diff --git a/src/agents/subagent-registry.types.ts b/src/agents/subagent-registry.types.ts index f5dc56775ae..fc6f2a9863f 100644 --- a/src/agents/subagent-registry.types.ts +++ b/src/agents/subagent-registry.types.ts @@ -55,4 +55,6 @@ export type SubagentRunRecord = { attachmentsDir?: string; attachmentsRootDir?: string; retainAttachmentsOnKeep?: boolean; + /** Maximum character limit for announce message delivery (truncates with marker if exceeded). */ + maxAnnounceChars?: number; }; diff --git a/src/agents/subagent-spawn.ts b/src/agents/subagent-spawn.ts index 1750d948e6c..12df1249920 100644 --- a/src/agents/subagent-spawn.ts +++ b/src/agents/subagent-spawn.ts @@ -56,6 +56,7 @@ export type SpawnSubagentParams = { cleanup?: "delete" | "keep"; sandbox?: SpawnSubagentSandboxMode; expectsCompletionMessage?: boolean; + maxAnnounceChars?: number; attachments?: Array<{ name: string; content: string; @@ -708,6 +709,7 @@ export async function spawnSubagentDirect( runTimeoutSeconds, expectsCompletionMessage, spawnMode, + maxAnnounceChars: params.maxAnnounceChars, attachmentsDir: attachmentAbsDir, attachmentsRootDir: attachmentRootDir, retainAttachmentsOnKeep: retainOnSessionKeep, diff --git a/src/agents/tools/sessions-spawn-tool.ts b/src/agents/tools/sessions-spawn-tool.ts index b735084d2b0..1aa2f9b19a3 100644 --- a/src/agents/tools/sessions-spawn-tool.ts +++ b/src/agents/tools/sessions-spawn-tool.ts @@ -63,6 +63,13 @@ const SessionsSpawnToolSchema = Type.Object({ mountPath: Type.Optional(Type.String()), }), ), + maxAnnounceChars: Type.Optional( + Type.Number({ + minimum: 1, + description: + "Maximum character limit for the sub-agent completion announce message. If exceeded, the message is truncated with a marker.", + }), + ), }); export function createSessionsSpawnTool( @@ -81,7 +88,7 @@ export function createSessionsSpawnTool( label: "Sessions", name: "sessions_spawn", description: - 'Spawn an isolated session (runtime="subagent" or runtime="acp"). mode="run" is one-shot and mode="session" is persistent/thread-bound. Subagents inherit the parent workspace directory automatically.', + 'Spawn an isolated session (runtime="subagent" or runtime="acp"). mode="run" is one-shot and mode="session" is persistent/thread-bound. Subagents inherit the parent workspace directory automatically. Use maxAnnounceChars to limit completion message length for platforms with character limits (e.g., Telegram 4096 chars).', parameters: SessionsSpawnToolSchema, execute: async (_toolCallId, args) => { const params = args as Record; @@ -118,6 +125,10 @@ export function createSessionsSpawnTool( ? Math.max(0, Math.floor(timeoutSecondsCandidate)) : undefined; const thread = params.thread === true; + const maxAnnounceCharsCandidate = + typeof params.maxAnnounceChars === "number" && Number.isFinite(params.maxAnnounceChars) + ? Math.max(1, Math.floor(params.maxAnnounceChars)) + : undefined; const attachments = Array.isArray(params.attachments) ? (params.attachments as Array<{ name: string; @@ -186,6 +197,7 @@ export function createSessionsSpawnTool( cleanup, sandbox, expectsCompletionMessage: true, + maxAnnounceChars: maxAnnounceCharsCandidate, attachments, attachMountPath: params.attachAs && typeof params.attachAs === "object"