Merge 5b149501537151fd1234e7a0324e847508a5968d into d78e13f545136fcbba1feceecc5e0485a06c33a6

This commit is contained in:
w-sss 2026-03-21 04:50:28 +00:00 committed by GitHub
commit aa2d35c207
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 36 additions and 3 deletions

View File

@ -1262,6 +1262,7 @@ export async function runSubagentAnnounceFlow(params: {
wakeOnDescendantSettle?: boolean;
signal?: AbortSignal;
bestEffortDeliver?: boolean;
maxAnnounceChars?: number;
}): Promise<boolean> {
let didAnnounce = false;
const expectsCompletionMessage = params.expectsCompletionMessage === true;
@ -1488,6 +1489,16 @@ export async function runSubagentAnnounceFlow(params: {
startedAt: params.startedAt,
endedAt: params.endedAt,
});
// Apply maxAnnounceChars truncation if specified
const TRUNCATION_MARKER = "\n\n[truncated — full output in transcript]";
const truncatedFindings =
typeof params.maxAnnounceChars === "number" &&
params.maxAnnounceChars >= 1 &&
findings.length > params.maxAnnounceChars
? findings.slice(0, params.maxAnnounceChars - TRUNCATION_MARKER.length) + TRUNCATION_MARKER
: findings;
const internalEvents: AgentInternalEvent[] = [
{
type: "task_completion",
@ -1498,7 +1509,7 @@ export async function runSubagentAnnounceFlow(params: {
taskLabel,
status: outcome.status,
statusLabel,
result: findings,
result: truncatedFindings,
statsLine,
replyInstruction,
},

View File

@ -666,6 +666,7 @@ function startSubagentAnnounceCleanupFlow(runId: string, entry: SubagentRunRecor
spawnMode: entry.spawnMode,
expectsCompletionMessage: entry.expectsCompletionMessage,
wakeOnDescendantSettle: entry.wakeOnDescendantSettle === true,
maxAnnounceChars: entry.maxAnnounceChars,
})
.then((didAnnounce) => {
finalizeAnnounceCleanup(didAnnounce);
@ -1349,6 +1350,7 @@ export function registerSubagentRun(params: {
runTimeoutSeconds?: number;
expectsCompletionMessage?: boolean;
spawnMode?: "run" | "session";
maxAnnounceChars?: number;
attachmentsDir?: string;
attachmentsRootDir?: string;
retainAttachmentsOnKeep?: boolean;
@ -1388,6 +1390,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,

View File

@ -60,4 +60,6 @@ export type SubagentRunRecord = {
attachmentsDir?: string;
attachmentsRootDir?: string;
retainAttachmentsOnKeep?: boolean;
/** Maximum character limit for announce message delivery (truncates with marker if exceeded). */
maxAnnounceChars?: number;
};

View File

@ -62,6 +62,7 @@ export type SpawnSubagentParams = {
cleanup?: "delete" | "keep";
sandbox?: SpawnSubagentSandboxMode;
expectsCompletionMessage?: boolean;
maxAnnounceChars?: number;
attachments?: Array<{
name: string;
content: string;
@ -753,6 +754,7 @@ export async function spawnSubagentDirect(
runTimeoutSeconds,
expectsCompletionMessage,
spawnMode,
maxAnnounceChars: params.maxAnnounceChars,
attachmentsDir: attachmentAbsDir,
attachmentsRootDir: attachmentRootDir,
retainAttachmentsOnKeep: retainOnSessionKeep,

View File

@ -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<string, unknown>;
@ -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"

View File

@ -314,7 +314,7 @@ export async function tryDispatchAcpReply(params: {
await projector.flush(true);
const ttsMode = resolveTtsConfig(params.cfg).mode ?? "final";
const accumulatedBlockText = delivery.getAccumulatedBlockText();
if (ttsMode === "final" && delivery.getBlockCount() > 0 && accumulatedBlockText.trim()) {
if (ttsMode === "final" && accumulatedBlockText.trim()) {
try {
const ttsSyntheticReply = await maybeApplyTtsToPayload({
payload: { text: accumulatedBlockText },