{"version":3,"file":"subagent-control.d.ts","sourceRoot":"","sources":["../../../../src/runs/shared/subagent-control.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACX,aAAa,EACb,aAAa,EACb,YAAY,EACZ,gBAAgB,EAEhB,qBAAqB,EACrB,MAAM,uBAAuB,CAAC;AAM/B,eAAO,MAAM,sBAAsB,EAAE,qBAOpC,CAAC;AAgBF,wBAAgB,oBAAoB,CAAC,YAAY,CAAC,EAAE,aAAa,EAAE,QAAQ,CAAC,EAAE,aAAa,GAAG,qBAAqB,CAoClH;AAED,wBAAgB,mBAAmB,CAAC,KAAK,EAAE;IAC1C,MAAM,EAAE,qBAAqB,CAAC;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;CACb,GAAG,aAAa,GAAG,SAAS,CAM5B;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE;IACxC,IAAI,CAAC,EAAE,gBAAgB,CAAC;IACxB,IAAI,CAAC,EAAE,aAAa,CAAC;IACrB,EAAE,EAAE,aAAa,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,YAAY,CAAC,QAAQ,CAAC,CAAC;IAChC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,oBAAoB,CAAC,EAAE,MAAM,CAAC;CAC9B,GAAG,YAAY,CA+Bf;AAED,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,qBAAqB,EAAE,KAAK,EAAE,YAAY,GAAG,OAAO,CAEpG;AAED,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,YAAY,EAAE,mBAAmB,CAAC,EAAE,MAAM,GAAG,MAAM,CAGhG;AAED,wBAAgB,wBAAwB,CACvC,MAAM,EAAE,qBAAqB,EAC7B,KAAK,EAAE,YAAY,EACnB,QAAQ,EAAE,GAAG,CAAC,MAAM,CAAC,EACrB,mBAAmB,CAAC,EAAE,MAAM,GAC1B,OAAO,CAMT;AAgBD,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,YAAY,EAAE,mBAAmB,CAAC,EAAE,MAAM,GAAG,MAAM,CAsDpG;AAED,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,YAAY,EAAE,mBAAmB,CAAC,EAAE,MAAM,GAAG,MAAM,CAkBtG","sourcesContent":["import type {\n\tActivityState,\n\tControlConfig,\n\tControlEvent,\n\tControlEventType,\n\tControlNotificationChannel,\n\tResolvedControlConfig,\n} from \"../../shared/types.ts\";\n\nconst CONTROL_EVENT_TYPES: ControlEventType[] = [\"active_long_running\", \"needs_attention\"];\nconst CONTROL_NOTIFICATION_CHANNELS: ControlNotificationChannel[] = [\"event\", \"async\", \"intercom\"];\nconst DEFAULT_NOTIFY_ON: ControlEventType[] = [\"active_long_running\", \"needs_attention\"];\n\nexport const DEFAULT_CONTROL_CONFIG: ResolvedControlConfig = {\n\tenabled: true,\n\tneedsAttentionAfterMs: 60_000,\n\tactiveNoticeAfterMs: 240_000,\n\tfailedToolAttemptsBeforeAttention: 3,\n\tnotifyOn: DEFAULT_NOTIFY_ON,\n\tnotifyChannels: CONTROL_NOTIFICATION_CHANNELS,\n};\n\nfunction parsePositiveInt(value: unknown): number | undefined {\n\tif (typeof value !== \"number\") return undefined;\n\tif (!Number.isFinite(value) || !Number.isInteger(value) || value < 1) return undefined;\n\treturn value;\n}\n\nfunction parseControlList<T extends string>(value: unknown, allowed: readonly T[]): T[] | undefined {\n\tif (!Array.isArray(value)) return undefined;\n\tif (value.length === 0) return [];\n\tconst allowedSet = new Set(allowed);\n\tconst parsed = value.filter((entry): entry is T => typeof entry === \"string\" && allowedSet.has(entry as T));\n\treturn parsed.length > 0 ? Array.from(new Set(parsed)) : undefined;\n}\n\nexport function resolveControlConfig(globalConfig?: ControlConfig, override?: ControlConfig): ResolvedControlConfig {\n\tconst enabled = override?.enabled ?? globalConfig?.enabled ?? DEFAULT_CONTROL_CONFIG.enabled;\n\tconst needsAttentionAfterMs =\n\t\tparsePositiveInt(override?.needsAttentionAfterMs) ??\n\t\tparsePositiveInt(globalConfig?.needsAttentionAfterMs) ??\n\t\tDEFAULT_CONTROL_CONFIG.needsAttentionAfterMs;\n\tconst activeNoticeAfterMs =\n\t\tparsePositiveInt(override?.activeNoticeAfterMs) ??\n\t\tparsePositiveInt(globalConfig?.activeNoticeAfterMs) ??\n\t\tDEFAULT_CONTROL_CONFIG.activeNoticeAfterMs;\n\tconst activeNoticeAfterTurns =\n\t\tparsePositiveInt(override?.activeNoticeAfterTurns) ?? parsePositiveInt(globalConfig?.activeNoticeAfterTurns);\n\tconst activeNoticeAfterTokens =\n\t\tparsePositiveInt(override?.activeNoticeAfterTokens) ?? parsePositiveInt(globalConfig?.activeNoticeAfterTokens);\n\tconst failedToolAttemptsBeforeAttention =\n\t\tparsePositiveInt(override?.failedToolAttemptsBeforeAttention) ??\n\t\tparsePositiveInt(globalConfig?.failedToolAttemptsBeforeAttention) ??\n\t\tDEFAULT_CONTROL_CONFIG.failedToolAttemptsBeforeAttention;\n\tconst notifyOn =\n\t\tparseControlList(override?.notifyOn, CONTROL_EVENT_TYPES) ??\n\t\tparseControlList(globalConfig?.notifyOn, CONTROL_EVENT_TYPES) ??\n\t\tDEFAULT_CONTROL_CONFIG.notifyOn;\n\tconst notifyChannels =\n\t\tparseControlList(override?.notifyChannels, CONTROL_NOTIFICATION_CHANNELS) ??\n\t\tparseControlList(globalConfig?.notifyChannels, CONTROL_NOTIFICATION_CHANNELS) ??\n\t\tDEFAULT_CONTROL_CONFIG.notifyChannels;\n\treturn {\n\t\tenabled,\n\t\tneedsAttentionAfterMs,\n\t\tactiveNoticeAfterMs,\n\t\tactiveNoticeAfterTurns,\n\t\tactiveNoticeAfterTokens,\n\t\tfailedToolAttemptsBeforeAttention,\n\t\tnotifyOn: [...notifyOn],\n\t\tnotifyChannels: [...notifyChannels],\n\t};\n}\n\nexport function deriveActivityState(input: {\n\tconfig: ResolvedControlConfig;\n\tstartedAt: number;\n\tlastActivityAt?: number;\n\tcurrentTool?: string;\n\tnow?: number;\n}): ActivityState | undefined {\n\tif (!input.config.enabled || input.currentTool) return undefined;\n\tconst now = input.now ?? Date.now();\n\tconst lastActivity = input.lastActivityAt ?? input.startedAt;\n\tconst ageMs = Math.max(0, now - lastActivity);\n\treturn ageMs > input.config.needsAttentionAfterMs ? \"needs_attention\" : undefined;\n}\n\nexport function buildControlEvent(input: {\n\ttype?: ControlEventType;\n\tfrom?: ActivityState;\n\tto: ActivityState;\n\trunId: string;\n\tagent: string;\n\tindex?: number;\n\tts?: number;\n\tlastActivityAt?: number;\n\tmessage?: string;\n\treason?: ControlEvent[\"reason\"];\n\tturns?: number;\n\ttokens?: number;\n\ttoolCount?: number;\n\tcurrentTool?: string;\n\tcurrentToolDurationMs?: number;\n\tcurrentPath?: string;\n\telapsedMs?: number;\n\trecentFailureSummary?: string;\n}): ControlEvent {\n\tconst ts = input.ts ?? Date.now();\n\tconst type = input.type ?? (input.to === \"active_long_running\" ? \"active_long_running\" : \"needs_attention\");\n\tconst elapsedMs = input.elapsedMs ?? (input.lastActivityAt ? Math.max(0, ts - input.lastActivityAt) : undefined);\n\tconst elapsedSeconds = elapsedMs !== undefined ? Math.floor(elapsedMs / 1000) : undefined;\n\tconst message =\n\t\tinput.message ??\n\t\t(type === \"active_long_running\"\n\t\t\t? `${input.agent} is still active but long-running`\n\t\t\t: elapsedSeconds !== undefined\n\t\t\t\t? `${input.agent} needs attention (no observed activity for ${elapsedSeconds}s)`\n\t\t\t\t: `${input.agent} needs attention`);\n\treturn {\n\t\ttype,\n\t\t...(input.from ? { from: input.from } : {}),\n\t\tto: input.to,\n\t\tts,\n\t\trunId: input.runId,\n\t\tagent: input.agent,\n\t\t...(input.index !== undefined ? { index: input.index } : {}),\n\t\tmessage,\n\t\treason: input.reason ?? (type === \"active_long_running\" ? \"active_long_running\" : \"idle\"),\n\t\t...(input.turns !== undefined ? { turns: input.turns } : {}),\n\t\t...(input.tokens !== undefined ? { tokens: input.tokens } : {}),\n\t\t...(input.toolCount !== undefined ? { toolCount: input.toolCount } : {}),\n\t\t...(input.currentTool ? { currentTool: input.currentTool } : {}),\n\t\t...(input.currentToolDurationMs !== undefined ? { currentToolDurationMs: input.currentToolDurationMs } : {}),\n\t\t...(input.currentPath ? { currentPath: input.currentPath } : {}),\n\t\t...(elapsedMs !== undefined ? { elapsedMs } : {}),\n\t\t...(input.recentFailureSummary ? { recentFailureSummary: input.recentFailureSummary } : {}),\n\t};\n}\n\nexport function shouldNotifyControlEvent(config: ResolvedControlConfig, event: ControlEvent): boolean {\n\treturn config.enabled && config.notifyOn.includes(event.type);\n}\n\nexport function controlNotificationKey(event: ControlEvent, childIntercomTarget?: string): string {\n\tconst childKey = childIntercomTarget ?? (event.index !== undefined ? `${event.runId}:${event.index}` : event.runId);\n\treturn `${childKey}:${event.type}:${event.reason ?? \"idle\"}`;\n}\n\nexport function claimControlNotification(\n\tconfig: ResolvedControlConfig,\n\tevent: ControlEvent,\n\tseenKeys: Set<string>,\n\tchildIntercomTarget?: string,\n): boolean {\n\tif (!shouldNotifyControlEvent(config, event)) return false;\n\tconst key = controlNotificationKey(event, childIntercomTarget);\n\tif (seenKeys.has(key)) return false;\n\tseenKeys.add(key);\n\treturn true;\n}\n\nfunction formatLongRunningFacts(event: ControlEvent): string | undefined {\n\tconst facts: string[] = [];\n\tif (event.elapsedMs !== undefined) facts.push(`elapsed ${Math.floor(Math.max(0, event.elapsedMs) / 1000)}s`);\n\tif (event.turns !== undefined) facts.push(`${event.turns} turns`);\n\tif (event.tokens !== undefined) facts.push(`${event.tokens} tokens`);\n\tif (event.toolCount !== undefined) facts.push(`${event.toolCount} tools`);\n\tif (event.currentTool)\n\t\tfacts.push(\n\t\t\t`tool ${event.currentTool}${event.currentToolDurationMs !== undefined ? ` ${Math.floor(Math.max(0, event.currentToolDurationMs) / 1000)}s` : \"\"}`,\n\t\t);\n\tif (event.currentPath) facts.push(`path ${event.currentPath}`);\n\treturn facts.length > 0 ? facts.join(\" | \") : undefined;\n}\n\nexport function formatControlNoticeMessage(event: ControlEvent, childIntercomTarget?: string): string {\n\tconst runTarget = event.runId;\n\tif (event.reason === \"completion_guard\") {\n\t\treturn [\n\t\t\t`Subagent failed: ${event.agent}`,\n\t\t\t`Run: ${runTarget}${event.index !== undefined ? ` step ${event.index + 1}` : \"\"}`,\n\t\t\t`Signal: ${event.message}`,\n\t\t\t\"Next: read the output artifact or session from the subagent result, then retry with a more explicit implementation prompt or handle the fix directly.\",\n\t\t\tchildIntercomTarget ? `Run intercom target (may be inactive): ${childIntercomTarget}` : undefined,\n\t\t]\n\t\t\t.filter((line): line is string => Boolean(line))\n\t\t\t.join(\"\\n\");\n\t}\n\n\tconst nudgeMessage = \"What are you blocked on? Reply with the smallest next step or ask for a decision.\";\n\tconst steerCommand = `subagent({ action: \"steer\", id: \"${runTarget}\", ${event.index !== undefined ? `index: ${event.index}, ` : \"\"}message: \"${nudgeMessage}\" })`;\n\tconst nestedResumeCommand = `subagent({ action: \"resume\", id: \"${runTarget}\", message: \"${nudgeMessage}\" })`;\n\tif (event.type === \"active_long_running\") {\n\t\tconst facts = formatLongRunningFacts(event);\n\t\treturn [\n\t\t\t`Subagent active but long-running: ${event.agent}`,\n\t\t\t`Run: ${runTarget}${event.index !== undefined ? ` step ${event.index + 1}` : \"\"}`,\n\t\t\t`Signal: ${event.message}`,\n\t\t\tfacts ? `Facts: ${facts}` : undefined,\n\t\t\t\"Hint: Inspect status first. Use steer for a top-level live async child, routed resume for a live nested child, or resume to revive a paused/completed/failed child.\",\n\t\t\t`Top-level live async nudge: ${steerCommand}`,\n\t\t\t`Routed live nested nudge: ${nestedResumeCommand}`,\n\t\t\tchildIntercomTarget ? `Direct intercom target: ${childIntercomTarget}` : undefined,\n\t\t\t`Status: subagent({ action: \"status\", id: \"${runTarget}\" })`,\n\t\t\t`Interrupt: subagent({ action: \"interrupt\", id: \"${runTarget}\" })`,\n\t\t]\n\t\t\t.filter((line): line is string => Boolean(line))\n\t\t\t.join(\"\\n\");\n\t}\n\n\tconst supervisorHint =\n\t\tevent.reason === \"supervisor_request\"\n\t\t\t? \"Supervisor request: reply to the pending request. If subagent_supervisor pending is empty, check intercom pending because an external intercom tool may own the request.\"\n\t\t\t: undefined;\n\treturn [\n\t\t`Subagent needs attention: ${event.agent}`,\n\t\t`Run: ${runTarget}${event.index !== undefined ? ` step ${event.index + 1}` : \"\"}`,\n\t\t`Signal: ${event.message}`,\n\t\tevent.recentFailureSummary ? `Recent failures: ${event.recentFailureSummary}` : undefined,\n\t\tsupervisorHint,\n\t\t\"Hint: Inspect status first unless the run is clearly blocked. Use steer for a top-level live async child, routed resume for a live nested child, or resume to revive a paused/completed/failed child.\",\n\t\t`Top-level live async nudge: ${steerCommand}`,\n\t\t`Routed live nested nudge: ${nestedResumeCommand}`,\n\t\tchildIntercomTarget ? `Direct intercom target: ${childIntercomTarget}` : undefined,\n\t\t`Status: subagent({ action: \"status\", id: \"${runTarget}\" })`,\n\t\t`Interrupt: subagent({ action: \"interrupt\", id: \"${runTarget}\" })`,\n\t]\n\t\t.filter((line): line is string => Boolean(line))\n\t\t.join(\"\\n\");\n}\n\nexport function formatControlIntercomMessage(event: ControlEvent, childIntercomTarget?: string): string {\n\tconst statusLabel =\n\t\tevent.reason === \"completion_guard\"\n\t\t\t? \"subagent failed\"\n\t\t\t: event.type === \"active_long_running\"\n\t\t\t\t? \"subagent active but long-running\"\n\t\t\t\t: \"subagent needs attention\";\n\treturn [\n\t\tstatusLabel,\n\t\t\"\",\n\t\tevent.reason === \"completion_guard\"\n\t\t\t? `${event.agent} failed in run ${event.runId}.`\n\t\t\t: event.type === \"active_long_running\"\n\t\t\t\t? `${event.agent} is still active but long-running in run ${event.runId}.`\n\t\t\t\t: `${event.agent} needs attention in run ${event.runId}.`,\n\t\t\"\",\n\t\tformatControlNoticeMessage(event, childIntercomTarget),\n\t].join(\"\\n\");\n}\n"]}