@ashx-j/lunr 0.2.10 → 0.2.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/dist/builtin-extensions/lunr-cron.d.ts.map +1 -1
  2. package/dist/builtin-extensions/lunr-cron.js +6 -1
  3. package/dist/builtin-extensions/lunr-cron.js.map +1 -1
  4. package/dist/builtin-extensions/pi-subagents/src/extension/fanout-child.d.ts.map +1 -1
  5. package/dist/builtin-extensions/pi-subagents/src/extension/fanout-child.js +1 -1
  6. package/dist/builtin-extensions/pi-subagents/src/extension/fanout-child.js.map +1 -1
  7. package/dist/builtin-extensions/pi-subagents/src/extension/index.d.ts.map +1 -1
  8. package/dist/builtin-extensions/pi-subagents/src/extension/index.js +3 -4
  9. package/dist/builtin-extensions/pi-subagents/src/extension/index.js.map +1 -1
  10. package/dist/builtin-extensions/pi-subagents/src/extension/tool-description.d.ts +2 -2
  11. package/dist/builtin-extensions/pi-subagents/src/extension/tool-description.d.ts.map +1 -1
  12. package/dist/builtin-extensions/pi-subagents/src/extension/tool-description.js +4 -4
  13. package/dist/builtin-extensions/pi-subagents/src/extension/tool-description.js.map +1 -1
  14. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/subagent-executor.d.ts.map +1 -1
  15. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/subagent-executor.js +8 -7
  16. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/subagent-executor.js.map +1 -1
  17. package/dist/builtin-extensions/pi-subagents/src/shared/types.d.ts +2 -1
  18. package/dist/builtin-extensions/pi-subagents/src/shared/types.d.ts.map +1 -1
  19. package/dist/builtin-extensions/pi-subagents/src/shared/types.js.map +1 -1
  20. package/dist/catalog/models.json +1 -1
  21. package/dist/catalog/providers/openrouter.json +1 -1
  22. package/dist/catalog/providers/xai.json +1 -1
  23. package/dist/catalog/publication.json +2 -2
  24. package/dist/core/agent-session.d.ts.map +1 -1
  25. package/dist/core/agent-session.js +2 -2
  26. package/dist/core/agent-session.js.map +1 -1
  27. package/dist/core/models-store.d.ts.map +1 -1
  28. package/dist/core/models-store.js +13 -1
  29. package/dist/core/models-store.js.map +1 -1
  30. package/dist/core/permissions.d.ts +9 -0
  31. package/dist/core/permissions.d.ts.map +1 -1
  32. package/dist/core/permissions.js +60 -20
  33. package/dist/core/permissions.js.map +1 -1
  34. package/dist/core/plan-mode.d.ts.map +1 -1
  35. package/dist/core/plan-mode.js +49 -7
  36. package/dist/core/plan-mode.js.map +1 -1
  37. package/dist/core/swarm.d.ts +17 -4
  38. package/dist/core/swarm.d.ts.map +1 -1
  39. package/dist/core/swarm.js +36 -4
  40. package/dist/core/swarm.js.map +1 -1
  41. package/dist/gateway/agent-bridge.d.ts +1 -1
  42. package/dist/gateway/agent-bridge.d.ts.map +1 -1
  43. package/dist/gateway/agent-bridge.js +11 -1
  44. package/dist/gateway/agent-bridge.js.map +1 -1
  45. package/dist/gateway/commands.d.ts.map +1 -1
  46. package/dist/gateway/commands.js +2 -1
  47. package/dist/gateway/commands.js.map +1 -1
  48. package/dist/gateway/cron.d.ts +4 -1
  49. package/dist/gateway/cron.d.ts.map +1 -1
  50. package/dist/gateway/cron.js +17 -8
  51. package/dist/gateway/cron.js.map +1 -1
  52. package/dist/gateway/router.d.ts +1 -1
  53. package/dist/gateway/router.d.ts.map +1 -1
  54. package/dist/gateway/router.js.map +1 -1
  55. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  56. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  57. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  58. package/examples/extensions/gondolin/package-lock.json +2 -2
  59. package/examples/extensions/gondolin/package.json +1 -1
  60. package/examples/extensions/sandbox/package-lock.json +2 -2
  61. package/examples/extensions/sandbox/package.json +1 -1
  62. package/examples/extensions/with-deps/package-lock.json +2 -2
  63. package/examples/extensions/with-deps/package.json +1 -1
  64. package/package.json +4 -4
@@ -1 +1 @@
1
- {"version":3,"file":"lunr-cron.d.ts","sourceRoot":"","sources":["../../src/builtin-extensions/lunr-cron.ts"],"names":[],"mappings":"AACA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAoB,MAAM,iCAAiC,CAAC;AAsFtF,MAAM,CAAC,OAAO,WAAW,EAAE,EAAE,YAAY,GAAG,IAAI,CAyP/C","sourcesContent":["// @ts-nocheck\n/**\n * lunr-cron — lunR-native scheduled jobs (Phase 1: local TUI delivery;\n * Phase 4: gateway delivery + origin stamping).\n *\n * lunR: this file is lunR-native (not an absorbed upstream extension). It wires\n * core/cron (jobs store + scheduler) into the interactive session:\n *\n * - `/cron list|create|pause|resume|run|remove|status` command.\n * - One `cron` tool (TypeBox) so the agent can manage jobs. The shared\n * core/cron/fire-guard depth counter refuses the tool while a cron-fired\n * turn is in flight — TUI-fired OR gateway-fired (jobs cannot schedule\n * jobs).\n * - Jobs created inside a gateway chat turn are stamped with that chat as\n * their origin and default deliver to \"origin\" (core/cron/origin-context).\n * - Scheduler starts on session_start only in \"tui\" mode, stops on\n * session_shutdown. runJob = sendUserMessage + wait for agent_end;\n * deliverResult reads the `@lunr/cron-delivery` bridge on globalThis\n * (registered here as the local notify; the Phase 4 gateway replaces\n * it — core/cron/scheduler.ts never touches the bridge itself).\n *\n * `// @ts-nocheck` matches the builtin-extension convention (see lunr-behavior).\n * Runtime imports stay on concrete core modules — never the package barrel.\n */\n\nimport type { ExtensionAPI, ExtensionContext } from \"@ashx-j/lunr\";\nimport { Type } from \"@sinclair/typebox\";\nimport { beginCronFire, endCronFire, isCronFire } from \"../core/cron/fire-guard.ts\";\nimport {\n\tcreateJob,\n\tgetJob,\n\tlistJobs,\n\tparseSchedule,\n\tpauseJob,\n\tremoveJob,\n\tresumeJob,\n\tupdateJob,\n} from \"../core/cron/jobs.ts\";\nimport { currentOrigin } from \"../core/cron/origin-context.ts\";\nimport { executeJob, startScheduler } from \"../core/cron/scheduler.ts\";\n\n// ---------------------------------------------------------------------------\n// Module state\n// ---------------------------------------------------------------------------\n/** The cron-fired turn currently waiting for agent_end. */\nlet pendingRun: { resolve: (text: string) => void; reject: (err: Error) => void } | null = null;\nlet scheduler: { stop(): void } | null = null;\nlet lastCtx: ExtensionContext | null = null;\n\nconst DELIVERY_BRIDGE_SYMBOL = Symbol.for(\"@lunr/cron-delivery\");\n\n// ---------------------------------------------------------------------------\n// Turn plumbing\n// ---------------------------------------------------------------------------\n\n/** Final assistant text from an agent_end messages array (last assistant message, text blocks only). */\nfunction extractAssistantText(messages: unknown[]): string {\n\tfor (let i = messages.length - 1; i >= 0; i--) {\n\t\tconst msg = messages[i] as any;\n\t\tif (msg?.role !== \"assistant\") continue;\n\t\tconst parts = Array.isArray(msg.content) ? msg.content : [];\n\t\tconst text = parts\n\t\t\t.filter((p: any) => p?.type === \"text\")\n\t\t\t.map((p: any) => String(p.text ?? \"\"))\n\t\t\t.join(\"\")\n\t\t\t.trim();\n\t\tif (text) return text;\n\t}\n\treturn \"\";\n}\n\n// ---------------------------------------------------------------------------\n// Formatting helpers\n// ---------------------------------------------------------------------------\n\nfunction fmtTime(iso: string | null): string {\n\tif (!iso) return \"-\";\n\tconst d = new Date(iso);\n\tif (Number.isNaN(d.getTime())) return \"-\";\n\tconst p = (n: number) => String(n).padStart(2, \"0\");\n\t// Stored timestamps are UTC ISO; render in local time so users read wall-clock.\n\treturn `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}`;\n}\n\nfunction formatJobList(): string {\n\tconst jobs = listJobs();\n\tif (jobs.length === 0) return \"No cron jobs. Create one with /cron create <schedule> <prompt>.\";\n\tconst lines = jobs.map(\n\t\t(j) =>\n\t\t\t`${j.id} ${j.state}${j.enabled ? \"\" : \" (disabled)\"} ${j.scheduleDisplay} next=${fmtTime(j.nextRunAt)} last=${j.lastStatus ?? \"-\"}${j.lastError ? ` (${j.lastError})` : \"\"} ${j.name}`,\n\t);\n\treturn `Cron jobs (${jobs.length}):\\n${lines.join(\"\\n\")}`;\n}\n\nfunction formatStatus(): string {\n\tconst jobs = listJobs();\n\tconst count = (s: string) => jobs.filter((j) => j.state === s).length;\n\tconst next = jobs\n\t\t.filter((j) => j.enabled && j.state === \"scheduled\" && j.nextRunAt)\n\t\t.sort((a, b) => String(a.nextRunAt).localeCompare(String(b.nextRunAt)))[0];\n\treturn (\n\t\t`Cron: scheduler ${scheduler ? \"running\" : \"stopped\"}; ${jobs.length} job(s) — ` +\n\t\t`${count(\"scheduled\")} scheduled, ${count(\"paused\")} paused, ${count(\"completed\")} completed, ${count(\"error\")} error.` +\n\t\t(next ? ` Next: '${next.name}' at ${fmtTime(next.nextRunAt)}.` : \"\")\n\t);\n}\n\n// ---------------------------------------------------------------------------\n// Extension\n// ---------------------------------------------------------------------------\n\nexport default function (pi: ExtensionAPI): void {\n\t// --- Local delivery bridge (Phase 4 gateway replaces this) ---\n\t(globalThis as Record<symbol, unknown>)[DELIVERY_BRIDGE_SYMBOL] = async (job: { name: string }, _content: string) => {\n\t\ttry {\n\t\t\tlastCtx?.ui.notify(`Cron job '${job.name}' finished`, \"info\");\n\t\t\treturn null;\n\t\t} catch (err) {\n\t\t\treturn String((err as Error)?.message ?? err);\n\t\t}\n\t};\n\n\t// --- Scheduler deps (TUI) ---\n\tconst runJob = (prompt: string, _job: unknown): Promise<string> => {\n\t\tif (pendingRun) return Promise.reject(new Error(\"another cron job turn is already in flight\"));\n\t\treturn new Promise<string>((resolve, reject) => {\n\t\t\tbeginCronFire();\n\t\t\tpendingRun = { resolve, reject };\n\t\t\ttry {\n\t\t\t\tpi.sendUserMessage(prompt);\n\t\t\t} catch (err) {\n\t\t\t\tpendingRun = null;\n\t\t\t\tendCronFire();\n\t\t\t\treject(err as Error);\n\t\t\t}\n\t\t});\n\t};\n\n\tconst deliverResult = async (job: any, content: string): Promise<void> => {\n\t\tconst bridge = (globalThis as Record<symbol, unknown>)[DELIVERY_BRIDGE_SYMBOL] as\n\t\t\t| ((job: unknown, content: string) => Promise<string | null>)\n\t\t\t| undefined;\n\t\tif (!bridge) throw new Error(\"no cron delivery bridge registered\");\n\t\tconst err = await bridge(job, content);\n\t\tif (err) throw new Error(String(err));\n\t};\n\n\tconst schedulerDeps = { runJob, deliverResult };\n\n\t/** Manual trigger: run inline when idle, otherwise make the job due for the next tick. */\n\tconst triggerRun = async (job: any, ctx: ExtensionContext): Promise<string> => {\n\t\tif (ctx.isIdle()) {\n\t\t\ttry {\n\t\t\t\tawait executeJob(getJob(job.id), schedulerDeps);\n\t\t\t\treturn `Cron job '${job.name}' (${job.id}) finished.`;\n\t\t\t} catch (err) {\n\t\t\t\treturn `Cron job '${job.name}' (${job.id}) failed: ${String((err as Error)?.message ?? err)}`;\n\t\t\t}\n\t\t}\n\t\tawait updateJob(job.id, { nextRunAt: new Date().toISOString() });\n\t\treturn `Cron job '${job.name}' (${job.id}) queued for the next scheduler tick.`;\n\t};\n\n\t// --- Resolve the cron-fired turn when the agent turn ends ---\n\tpi.on(\"agent_end\", (event) => {\n\t\tif (!pendingRun) return;\n\t\tconst pending = pendingRun;\n\t\tpendingRun = null;\n\t\tendCronFire();\n\t\tpending.resolve(extractAssistantText(event.messages ?? []));\n\t});\n\n\t// --- Scheduler lifecycle: TUI sessions only ---\n\tpi.on(\"session_start\", (_event, ctx) => {\n\t\tlastCtx = ctx;\n\t\tif (ctx.mode !== \"tui\") return;\n\t\tscheduler?.stop();\n\t\tscheduler = startScheduler(schedulerDeps);\n\t});\n\n\tpi.on(\"session_shutdown\", () => {\n\t\tscheduler?.stop();\n\t\tscheduler = null;\n\t});\n\n\t// --- /cron command ---\n\tpi.registerCommand(\"cron\", {\n\t\tdescription: \"Manage cron jobs: /cron list | create <schedule> <prompt> | pause|resume|run|remove <id-or-name> | status\",\n\t\tgetArgumentCompletions: (prefix: string) => {\n\t\t\tconst subs = [\"list\", \"create\", \"pause\", \"resume\", \"run\", \"remove\", \"status\"];\n\t\t\tconst lower = prefix.toLowerCase();\n\t\t\treturn subs\n\t\t\t\t.filter((s) => s.startsWith(lower))\n\t\t\t\t.map((s) => ({ value: s, label: s, description: `/cron ${s}` }));\n\t\t},\n\t\thandler: async (args, ctx) => {\n\t\t\tlastCtx = ctx;\n\t\t\tconst tokens = args.trim().split(/\\s+/).filter(Boolean);\n\t\t\tconst sub = (tokens.shift() ?? \"list\").toLowerCase();\n\t\t\ttry {\n\t\t\t\tswitch (sub) {\n\t\t\t\t\tcase \"list\": {\n\t\t\t\t\t\tctx.ui.notify(formatJobList(), \"info\");\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tcase \"status\": {\n\t\t\t\t\t\tctx.ui.notify(formatStatus(), \"info\");\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tcase \"create\": {\n\t\t\t\t\t\t// Greedy: the schedule is the shortest leading token run that parses.\n\t\t\t\t\t\tlet used = 0;\n\t\t\t\t\t\tfor (let k = 1; k <= Math.min(6, tokens.length); k++) {\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\tparseSchedule(tokens.slice(0, k).join(\" \"));\n\t\t\t\t\t\t\t\tused = k;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t\t// keep extending\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst prompt = tokens.slice(used).join(\" \").trim();\n\t\t\t\t\t\tif (!used || !prompt) {\n\t\t\t\t\t\t\tctx.ui.notify(\"Usage: /cron create <schedule> <prompt> — e.g. /cron create every 30m check the deploy\", \"error\");\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst job = await createJob({ prompt, schedule: tokens.slice(0, used).join(\" \") });\n\t\t\t\t\t\tctx.ui.notify(`Created cron job '${job.name}' (${job.id}) — ${job.scheduleDisplay}, next run ${fmtTime(job.nextRunAt)}.`, \"info\");\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tcase \"pause\":\n\t\t\t\t\tcase \"resume\":\n\t\t\t\t\tcase \"remove\":\n\t\t\t\t\tcase \"run\": {\n\t\t\t\t\t\tconst idOrName = tokens.join(\" \").trim();\n\t\t\t\t\t\tif (!idOrName) {\n\t\t\t\t\t\t\tctx.ui.notify(`Usage: /cron ${sub} <id-or-name>`, \"error\");\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (sub === \"pause\") {\n\t\t\t\t\t\t\tconst job = await pauseJob(idOrName);\n\t\t\t\t\t\t\tctx.ui.notify(`Paused cron job '${job.name}' (${job.id}).`, \"info\");\n\t\t\t\t\t\t} else if (sub === \"resume\") {\n\t\t\t\t\t\t\tconst job = await resumeJob(idOrName);\n\t\t\t\t\t\t\tctx.ui.notify(`Resumed cron job '${job.name}' (${job.id}) — next run ${fmtTime(job.nextRunAt)}.`, \"info\");\n\t\t\t\t\t\t} else if (sub === \"remove\") {\n\t\t\t\t\t\t\tconst job = await removeJob(idOrName);\n\t\t\t\t\t\t\tctx.ui.notify(`Removed cron job '${job.name}' (${job.id}).`, \"info\");\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tconst job = getJob(idOrName);\n\t\t\t\t\t\t\tctx.ui.notify(await triggerRun(job, ctx), \"info\");\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tdefault: {\n\t\t\t\t\t\tctx.ui.notify(\"Usage: /cron list | create <schedule> <prompt> | pause|resume|run|remove <id-or-name> | status\", \"error\");\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\tctx.ui.notify(String((err as Error)?.message ?? err), \"error\");\n\t\t\t}\n\t\t},\n\t});\n\n\t// --- cron tool (agent-facing) ---\n\tpi.registerTool({\n\t\tname: \"cron\",\n\t\tlabel: \"Cron\",\n\t\tdescription: [\n\t\t\t\"Manage scheduled cron jobs that run prompts unattended in this session.\",\n\t\t\t\"Actions: create (needs prompt + schedule), list, update (id + fields), pause, resume, remove, run (trigger now).\",\n\t\t\t\"Schedule formats: 'every 30m' / 'every 2h' / 'every 1d' (recurring), '30m'/'2h' or an ISO timestamp (one-shot), or a 5-field cron expression.\",\n\t\t\t\"Job output is delivered automatically; the job's prompt can answer [SILENT] to suppress delivery.\",\n\t\t].join(\"\\n\"),\n\t\tparameters: Type.Object({\n\t\t\taction: Type.Union([\n\t\t\t\tType.Literal(\"create\"),\n\t\t\t\tType.Literal(\"list\"),\n\t\t\t\tType.Literal(\"update\"),\n\t\t\t\tType.Literal(\"pause\"),\n\t\t\t\tType.Literal(\"resume\"),\n\t\t\t\tType.Literal(\"remove\"),\n\t\t\t\tType.Literal(\"run\"),\n\t\t\t]),\n\t\t\tid: Type.Optional(Type.String({ description: \"Job id or unique name (all actions except create/list).\" })),\n\t\t\tname: Type.Optional(Type.String({ description: \"Display name (<=50 chars); derived from the prompt when omitted.\" })),\n\t\t\tprompt: Type.Optional(Type.String({ description: \"The prompt the job runs (create/update).\" })),\n\t\t\tschedule: Type.Optional(Type.String({ description: \"Schedule input (create/update).\" })),\n\t\t\tdeliver: Type.Optional(\n\t\t\t\tType.String({\n\t\t\t\t\tdescription:\n\t\t\t\t\t\t\"Delivery target(s), comma-separated: 'local' (output file only), 'origin' (the chat that created the job), 'telegram'/'discord' (platform home channel), or 'telegram:<chatId>[:<threadId>]' / 'discord:<chatId>[:<threadId>]'. Defaults to 'origin' when created from a gateway chat, else 'local'.\",\n\t\t\t\t}),\n\t\t\t),\n\t\t}),\n\t\tasync execute(_toolCallId, params, _signal, _onUpdate, ctx) {\n\t\t\tconst text = (t: string) => ({ content: [{ type: \"text\" as const, text: t }] });\n\t\t\tif (isCronFire()) {\n\t\t\t\treturn text(\"cron: unavailable while a cron-fired turn is running (cron jobs cannot schedule cron jobs).\");\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tswitch (params.action) {\n\t\t\t\t\tcase \"create\": {\n\t\t\t\t\t\tif (!params.prompt?.trim() || !params.schedule?.trim()) {\n\t\t\t\t\t\t\treturn text(\"cron create: prompt and schedule are required.\");\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// Inside a gateway chat turn, stamp the chat as the delivery\n\t\t\t\t\t\t// origin and default deliver to \"origin\" (Phase 4); outside\n\t\t\t\t\t\t// any origin context the default stays \"local\".\n\t\t\t\t\t\tconst origin = currentOrigin();\n\t\t\t\t\t\tconst job = await createJob({\n\t\t\t\t\t\t\tprompt: params.prompt,\n\t\t\t\t\t\t\tschedule: params.schedule,\n\t\t\t\t\t\t\tname: params.name,\n\t\t\t\t\t\t\tdeliver: params.deliver ?? (origin ? \"origin\" : undefined),\n\t\t\t\t\t\t\torigin: origin ? { ...origin } : undefined,\n\t\t\t\t\t\t});\n\t\t\t\t\t\treturn text(`Created cron job '${job.name}' (${job.id}) — ${job.scheduleDisplay}, next run ${fmtTime(job.nextRunAt)}.`);\n\t\t\t\t\t}\n\t\t\t\t\tcase \"list\": {\n\t\t\t\t\t\treturn text(formatJobList());\n\t\t\t\t\t}\n\t\t\t\t\tcase \"update\": {\n\t\t\t\t\t\tif (!params.id) return text(\"cron update: id (or unique name) is required.\");\n\t\t\t\t\t\tconst job = await updateJob(params.id, {\n\t\t\t\t\t\t\tname: params.name,\n\t\t\t\t\t\t\tprompt: params.prompt,\n\t\t\t\t\t\t\tschedule: params.schedule,\n\t\t\t\t\t\t\tdeliver: params.deliver,\n\t\t\t\t\t\t});\n\t\t\t\t\t\treturn text(`Updated cron job '${job.name}' (${job.id}) — ${job.scheduleDisplay}, next run ${fmtTime(job.nextRunAt)}.`);\n\t\t\t\t\t}\n\t\t\t\t\tcase \"pause\": {\n\t\t\t\t\t\tif (!params.id) return text(\"cron pause: id (or unique name) is required.\");\n\t\t\t\t\t\tconst job = await pauseJob(params.id);\n\t\t\t\t\t\treturn text(`Paused cron job '${job.name}' (${job.id}).`);\n\t\t\t\t\t}\n\t\t\t\t\tcase \"resume\": {\n\t\t\t\t\t\tif (!params.id) return text(\"cron resume: id (or unique name) is required.\");\n\t\t\t\t\t\tconst job = await resumeJob(params.id);\n\t\t\t\t\t\treturn text(`Resumed cron job '${job.name}' (${job.id}) — next run ${fmtTime(job.nextRunAt)}.`);\n\t\t\t\t\t}\n\t\t\t\t\tcase \"remove\": {\n\t\t\t\t\t\tif (!params.id) return text(\"cron remove: id (or unique name) is required.\");\n\t\t\t\t\t\tconst job = await removeJob(params.id);\n\t\t\t\t\t\treturn text(`Removed cron job '${job.name}' (${job.id}).`);\n\t\t\t\t\t}\n\t\t\t\t\tcase \"run\": {\n\t\t\t\t\t\tif (!params.id) return text(\"cron run: id (or unique name) is required.\");\n\t\t\t\t\t\tconst job = getJob(params.id);\n\t\t\t\t\t\treturn text(await triggerRun(job, ctx));\n\t\t\t\t\t}\n\t\t\t\t\tdefault:\n\t\t\t\t\t\treturn text(`cron: unknown action \"${String(params.action)}\".`);\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\treturn text(`cron: ${String((err as Error)?.message ?? err)}`);\n\t\t\t}\n\t\t},\n\t});\n}\n"]}
1
+ {"version":3,"file":"lunr-cron.d.ts","sourceRoot":"","sources":["../../src/builtin-extensions/lunr-cron.ts"],"names":[],"mappings":"AACA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAoB,MAAM,iCAAiC,CAAC;AAyFtF,MAAM,CAAC,OAAO,WAAW,EAAE,EAAE,YAAY,GAAG,IAAI,CA6P/C","sourcesContent":["// @ts-nocheck\n/**\n * lunr-cron — lunR-native scheduled jobs (Phase 1: local TUI delivery;\n * Phase 4: gateway delivery + origin stamping).\n *\n * lunR: this file is lunR-native (not an absorbed upstream extension). It wires\n * core/cron (jobs store + scheduler) into the interactive session:\n *\n * - `/cron list|create|pause|resume|run|remove|status` command.\n * - One `cron` tool (TypeBox) so the agent can manage jobs. The shared\n * core/cron/fire-guard depth counter refuses the tool while a cron-fired\n * turn is in flight — TUI-fired OR gateway-fired (jobs cannot schedule\n * jobs).\n * - Jobs created inside a gateway chat turn are stamped with that chat as\n * their origin and default deliver to \"origin\" (core/cron/origin-context).\n * - Scheduler starts on session_start only in \"tui\" mode, stops on\n * session_shutdown. runJob = sendUserMessage + wait for agent_end;\n * deliverResult reads the `@lunr/cron-delivery` bridge on globalThis\n * (registered here as the local notify; the Phase 4 gateway replaces\n * it — core/cron/scheduler.ts never touches the bridge itself).\n *\n * `// @ts-nocheck` matches the builtin-extension convention (see lunr-behavior).\n * Runtime imports stay on concrete core modules — never the package barrel.\n */\n\nimport type { ExtensionAPI, ExtensionContext } from \"@ashx-j/lunr\";\nimport { Type } from \"@sinclair/typebox\";\nimport { beginCronFire, endCronFire, isCronFire } from \"../core/cron/fire-guard.ts\";\nimport {\n\tcreateJob,\n\tgetJob,\n\tlistJobs,\n\tparseSchedule,\n\tpauseJob,\n\tremoveJob,\n\tresumeJob,\n\tsetCronDeliverValidator,\n\tupdateJob,\n} from \"../core/cron/jobs.ts\";\nimport { currentOrigin } from \"../core/cron/origin-context.ts\";\nimport { executeJob, startScheduler } from \"../core/cron/scheduler.ts\";\nimport { loadGatewayConfig } from \"../gateway/config.ts\";\nimport { createDeliverValidator } from \"../gateway/cron.ts\";\n\n// ---------------------------------------------------------------------------\n// Module state\n// ---------------------------------------------------------------------------\n/** The cron-fired turn currently waiting for agent_end. */\nlet pendingRun: { resolve: (text: string) => void; reject: (err: Error) => void } | null = null;\nlet scheduler: { stop(): void } | null = null;\nlet lastCtx: ExtensionContext | null = null;\n\nconst DELIVERY_BRIDGE_SYMBOL = Symbol.for(\"@lunr/cron-delivery\");\n\n// ---------------------------------------------------------------------------\n// Turn plumbing\n// ---------------------------------------------------------------------------\n\n/** Final assistant text from an agent_end messages array (last assistant message, text blocks only). */\nfunction extractAssistantText(messages: unknown[]): string {\n\tfor (let i = messages.length - 1; i >= 0; i--) {\n\t\tconst msg = messages[i] as any;\n\t\tif (msg?.role !== \"assistant\") continue;\n\t\tconst parts = Array.isArray(msg.content) ? msg.content : [];\n\t\tconst text = parts\n\t\t\t.filter((p: any) => p?.type === \"text\")\n\t\t\t.map((p: any) => String(p.text ?? \"\"))\n\t\t\t.join(\"\")\n\t\t\t.trim();\n\t\tif (text) return text;\n\t}\n\treturn \"\";\n}\n\n// ---------------------------------------------------------------------------\n// Formatting helpers\n// ---------------------------------------------------------------------------\n\nfunction fmtTime(iso: string | null): string {\n\tif (!iso) return \"-\";\n\tconst d = new Date(iso);\n\tif (Number.isNaN(d.getTime())) return \"-\";\n\tconst p = (n: number) => String(n).padStart(2, \"0\");\n\t// Stored timestamps are UTC ISO; render in local time so users read wall-clock.\n\treturn `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}`;\n}\n\nfunction formatJobList(): string {\n\tconst jobs = listJobs();\n\tif (jobs.length === 0) return \"No cron jobs. Create one with /cron create <schedule> <prompt>.\";\n\tconst lines = jobs.map(\n\t\t(j) =>\n\t\t\t`${j.id} ${j.state}${j.enabled ? \"\" : \" (disabled)\"} ${j.scheduleDisplay} next=${fmtTime(j.nextRunAt)} last=${j.lastStatus ?? \"-\"}${j.lastError ? ` (${j.lastError})` : \"\"} ${j.name}`,\n\t);\n\treturn `Cron jobs (${jobs.length}):\\n${lines.join(\"\\n\")}`;\n}\n\nfunction formatStatus(): string {\n\tconst jobs = listJobs();\n\tconst count = (s: string) => jobs.filter((j) => j.state === s).length;\n\tconst next = jobs\n\t\t.filter((j) => j.enabled && j.state === \"scheduled\" && j.nextRunAt)\n\t\t.sort((a, b) => String(a.nextRunAt).localeCompare(String(b.nextRunAt)))[0];\n\treturn (\n\t\t`Cron: scheduler ${scheduler ? \"running\" : \"stopped\"}; ${jobs.length} job(s) — ` +\n\t\t`${count(\"scheduled\")} scheduled, ${count(\"paused\")} paused, ${count(\"completed\")} completed, ${count(\"error\")} error.` +\n\t\t(next ? ` Next: '${next.name}' at ${fmtTime(next.nextRunAt)}.` : \"\")\n\t);\n}\n\n// ---------------------------------------------------------------------------\n// Extension\n// ---------------------------------------------------------------------------\n\nexport default function (pi: ExtensionAPI): void {\n\t// Same allowlist the gateway uses, so a TUI session cannot persist a job\n\t// that later posts to a chat the bot can reach but is not allowed.\n\tsetCronDeliverValidator(createDeliverValidator(loadGatewayConfig()));\n\n\t// --- Local delivery bridge (Phase 4 gateway replaces this) ---\n\t(globalThis as Record<symbol, unknown>)[DELIVERY_BRIDGE_SYMBOL] = async (job: { name: string }, _content: string) => {\n\t\ttry {\n\t\t\tlastCtx?.ui.notify(`Cron job '${job.name}' finished`, \"info\");\n\t\t\treturn null;\n\t\t} catch (err) {\n\t\t\treturn String((err as Error)?.message ?? err);\n\t\t}\n\t};\n\n\t// --- Scheduler deps (TUI) ---\n\tconst runJob = (prompt: string, _job: unknown): Promise<string> => {\n\t\tif (pendingRun) return Promise.reject(new Error(\"another cron job turn is already in flight\"));\n\t\treturn new Promise<string>((resolve, reject) => {\n\t\t\tbeginCronFire();\n\t\t\tpendingRun = { resolve, reject };\n\t\t\ttry {\n\t\t\t\tpi.sendUserMessage(prompt);\n\t\t\t} catch (err) {\n\t\t\t\tpendingRun = null;\n\t\t\t\tendCronFire();\n\t\t\t\treject(err as Error);\n\t\t\t}\n\t\t});\n\t};\n\n\tconst deliverResult = async (job: any, content: string): Promise<void> => {\n\t\tconst bridge = (globalThis as Record<symbol, unknown>)[DELIVERY_BRIDGE_SYMBOL] as\n\t\t\t| ((job: unknown, content: string) => Promise<string | null>)\n\t\t\t| undefined;\n\t\tif (!bridge) throw new Error(\"no cron delivery bridge registered\");\n\t\tconst err = await bridge(job, content);\n\t\tif (err) throw new Error(String(err));\n\t};\n\n\tconst schedulerDeps = { runJob, deliverResult };\n\n\t/** Manual trigger: run inline when idle, otherwise make the job due for the next tick. */\n\tconst triggerRun = async (job: any, ctx: ExtensionContext): Promise<string> => {\n\t\tif (ctx.isIdle()) {\n\t\t\ttry {\n\t\t\t\tawait executeJob(getJob(job.id), schedulerDeps);\n\t\t\t\treturn `Cron job '${job.name}' (${job.id}) finished.`;\n\t\t\t} catch (err) {\n\t\t\t\treturn `Cron job '${job.name}' (${job.id}) failed: ${String((err as Error)?.message ?? err)}`;\n\t\t\t}\n\t\t}\n\t\tawait updateJob(job.id, { nextRunAt: new Date().toISOString() });\n\t\treturn `Cron job '${job.name}' (${job.id}) queued for the next scheduler tick.`;\n\t};\n\n\t// --- Resolve the cron-fired turn when the agent turn ends ---\n\tpi.on(\"agent_end\", (event) => {\n\t\tif (!pendingRun) return;\n\t\tconst pending = pendingRun;\n\t\tpendingRun = null;\n\t\tendCronFire();\n\t\tpending.resolve(extractAssistantText(event.messages ?? []));\n\t});\n\n\t// --- Scheduler lifecycle: TUI sessions only ---\n\tpi.on(\"session_start\", (_event, ctx) => {\n\t\tlastCtx = ctx;\n\t\tif (ctx.mode !== \"tui\") return;\n\t\tscheduler?.stop();\n\t\tscheduler = startScheduler(schedulerDeps);\n\t});\n\n\tpi.on(\"session_shutdown\", () => {\n\t\tscheduler?.stop();\n\t\tscheduler = null;\n\t});\n\n\t// --- /cron command ---\n\tpi.registerCommand(\"cron\", {\n\t\tdescription: \"Manage cron jobs: /cron list | create <schedule> <prompt> | pause|resume|run|remove <id-or-name> | status\",\n\t\tgetArgumentCompletions: (prefix: string) => {\n\t\t\tconst subs = [\"list\", \"create\", \"pause\", \"resume\", \"run\", \"remove\", \"status\"];\n\t\t\tconst lower = prefix.toLowerCase();\n\t\t\treturn subs\n\t\t\t\t.filter((s) => s.startsWith(lower))\n\t\t\t\t.map((s) => ({ value: s, label: s, description: `/cron ${s}` }));\n\t\t},\n\t\thandler: async (args, ctx) => {\n\t\t\tlastCtx = ctx;\n\t\t\tconst tokens = args.trim().split(/\\s+/).filter(Boolean);\n\t\t\tconst sub = (tokens.shift() ?? \"list\").toLowerCase();\n\t\t\ttry {\n\t\t\t\tswitch (sub) {\n\t\t\t\t\tcase \"list\": {\n\t\t\t\t\t\tctx.ui.notify(formatJobList(), \"info\");\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tcase \"status\": {\n\t\t\t\t\t\tctx.ui.notify(formatStatus(), \"info\");\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tcase \"create\": {\n\t\t\t\t\t\t// Greedy: the schedule is the shortest leading token run that parses.\n\t\t\t\t\t\tlet used = 0;\n\t\t\t\t\t\tfor (let k = 1; k <= Math.min(6, tokens.length); k++) {\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\tparseSchedule(tokens.slice(0, k).join(\" \"));\n\t\t\t\t\t\t\t\tused = k;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t\t// keep extending\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst prompt = tokens.slice(used).join(\" \").trim();\n\t\t\t\t\t\tif (!used || !prompt) {\n\t\t\t\t\t\t\tctx.ui.notify(\"Usage: /cron create <schedule> <prompt> — e.g. /cron create every 30m check the deploy\", \"error\");\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst job = await createJob({ prompt, schedule: tokens.slice(0, used).join(\" \") });\n\t\t\t\t\t\tctx.ui.notify(`Created cron job '${job.name}' (${job.id}) — ${job.scheduleDisplay}, next run ${fmtTime(job.nextRunAt)}.`, \"info\");\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tcase \"pause\":\n\t\t\t\t\tcase \"resume\":\n\t\t\t\t\tcase \"remove\":\n\t\t\t\t\tcase \"run\": {\n\t\t\t\t\t\tconst idOrName = tokens.join(\" \").trim();\n\t\t\t\t\t\tif (!idOrName) {\n\t\t\t\t\t\t\tctx.ui.notify(`Usage: /cron ${sub} <id-or-name>`, \"error\");\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (sub === \"pause\") {\n\t\t\t\t\t\t\tconst job = await pauseJob(idOrName);\n\t\t\t\t\t\t\tctx.ui.notify(`Paused cron job '${job.name}' (${job.id}).`, \"info\");\n\t\t\t\t\t\t} else if (sub === \"resume\") {\n\t\t\t\t\t\t\tconst job = await resumeJob(idOrName);\n\t\t\t\t\t\t\tctx.ui.notify(`Resumed cron job '${job.name}' (${job.id}) — next run ${fmtTime(job.nextRunAt)}.`, \"info\");\n\t\t\t\t\t\t} else if (sub === \"remove\") {\n\t\t\t\t\t\t\tconst job = await removeJob(idOrName);\n\t\t\t\t\t\t\tctx.ui.notify(`Removed cron job '${job.name}' (${job.id}).`, \"info\");\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tconst job = getJob(idOrName);\n\t\t\t\t\t\t\tctx.ui.notify(await triggerRun(job, ctx), \"info\");\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tdefault: {\n\t\t\t\t\t\tctx.ui.notify(\"Usage: /cron list | create <schedule> <prompt> | pause|resume|run|remove <id-or-name> | status\", \"error\");\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\tctx.ui.notify(String((err as Error)?.message ?? err), \"error\");\n\t\t\t}\n\t\t},\n\t});\n\n\t// --- cron tool (agent-facing) ---\n\tpi.registerTool({\n\t\tname: \"cron\",\n\t\tlabel: \"Cron\",\n\t\tdescription: [\n\t\t\t\"Manage scheduled cron jobs that run prompts unattended in this session.\",\n\t\t\t\"Actions: create (needs prompt + schedule), list, update (id + fields), pause, resume, remove, run (trigger now).\",\n\t\t\t\"Schedule formats: 'every 30m' / 'every 2h' / 'every 1d' (recurring), '30m'/'2h' or an ISO timestamp (one-shot), or a 5-field cron expression.\",\n\t\t\t\"Job output is delivered automatically; the job's prompt can answer [SILENT] to suppress delivery.\",\n\t\t].join(\"\\n\"),\n\t\tparameters: Type.Object({\n\t\t\taction: Type.Union([\n\t\t\t\tType.Literal(\"create\"),\n\t\t\t\tType.Literal(\"list\"),\n\t\t\t\tType.Literal(\"update\"),\n\t\t\t\tType.Literal(\"pause\"),\n\t\t\t\tType.Literal(\"resume\"),\n\t\t\t\tType.Literal(\"remove\"),\n\t\t\t\tType.Literal(\"run\"),\n\t\t\t]),\n\t\t\tid: Type.Optional(Type.String({ description: \"Job id or unique name (all actions except create/list).\" })),\n\t\t\tname: Type.Optional(Type.String({ description: \"Display name (<=50 chars); derived from the prompt when omitted.\" })),\n\t\t\tprompt: Type.Optional(Type.String({ description: \"The prompt the job runs (create/update).\" })),\n\t\t\tschedule: Type.Optional(Type.String({ description: \"Schedule input (create/update).\" })),\n\t\t\tdeliver: Type.Optional(\n\t\t\t\tType.String({\n\t\t\t\t\tdescription:\n\t\t\t\t\t\t\"Delivery target(s), comma-separated: 'local' (output file only), 'origin' (the chat that created the job), 'telegram'/'discord' (platform home channel), or 'telegram:<chatId>[:<threadId>]' / 'discord:<chatId>[:<threadId>]'. Defaults to 'origin' when created from a gateway chat, else 'local'.\",\n\t\t\t\t}),\n\t\t\t),\n\t\t}),\n\t\tasync execute(_toolCallId, params, _signal, _onUpdate, ctx) {\n\t\t\tconst text = (t: string) => ({ content: [{ type: \"text\" as const, text: t }] });\n\t\t\tif (isCronFire()) {\n\t\t\t\treturn text(\"cron: unavailable while a cron-fired turn is running (cron jobs cannot schedule cron jobs).\");\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tswitch (params.action) {\n\t\t\t\t\tcase \"create\": {\n\t\t\t\t\t\tif (!params.prompt?.trim() || !params.schedule?.trim()) {\n\t\t\t\t\t\t\treturn text(\"cron create: prompt and schedule are required.\");\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// Inside a gateway chat turn, stamp the chat as the delivery\n\t\t\t\t\t\t// origin and default deliver to \"origin\" (Phase 4); outside\n\t\t\t\t\t\t// any origin context the default stays \"local\".\n\t\t\t\t\t\tconst origin = currentOrigin();\n\t\t\t\t\t\tconst job = await createJob({\n\t\t\t\t\t\t\tprompt: params.prompt,\n\t\t\t\t\t\t\tschedule: params.schedule,\n\t\t\t\t\t\t\tname: params.name,\n\t\t\t\t\t\t\tdeliver: params.deliver ?? (origin ? \"origin\" : undefined),\n\t\t\t\t\t\t\torigin: origin ? { ...origin } : undefined,\n\t\t\t\t\t\t});\n\t\t\t\t\t\treturn text(`Created cron job '${job.name}' (${job.id}) — ${job.scheduleDisplay}, next run ${fmtTime(job.nextRunAt)}.`);\n\t\t\t\t\t}\n\t\t\t\t\tcase \"list\": {\n\t\t\t\t\t\treturn text(formatJobList());\n\t\t\t\t\t}\n\t\t\t\t\tcase \"update\": {\n\t\t\t\t\t\tif (!params.id) return text(\"cron update: id (or unique name) is required.\");\n\t\t\t\t\t\tconst job = await updateJob(params.id, {\n\t\t\t\t\t\t\tname: params.name,\n\t\t\t\t\t\t\tprompt: params.prompt,\n\t\t\t\t\t\t\tschedule: params.schedule,\n\t\t\t\t\t\t\tdeliver: params.deliver,\n\t\t\t\t\t\t});\n\t\t\t\t\t\treturn text(`Updated cron job '${job.name}' (${job.id}) — ${job.scheduleDisplay}, next run ${fmtTime(job.nextRunAt)}.`);\n\t\t\t\t\t}\n\t\t\t\t\tcase \"pause\": {\n\t\t\t\t\t\tif (!params.id) return text(\"cron pause: id (or unique name) is required.\");\n\t\t\t\t\t\tconst job = await pauseJob(params.id);\n\t\t\t\t\t\treturn text(`Paused cron job '${job.name}' (${job.id}).`);\n\t\t\t\t\t}\n\t\t\t\t\tcase \"resume\": {\n\t\t\t\t\t\tif (!params.id) return text(\"cron resume: id (or unique name) is required.\");\n\t\t\t\t\t\tconst job = await resumeJob(params.id);\n\t\t\t\t\t\treturn text(`Resumed cron job '${job.name}' (${job.id}) — next run ${fmtTime(job.nextRunAt)}.`);\n\t\t\t\t\t}\n\t\t\t\t\tcase \"remove\": {\n\t\t\t\t\t\tif (!params.id) return text(\"cron remove: id (or unique name) is required.\");\n\t\t\t\t\t\tconst job = await removeJob(params.id);\n\t\t\t\t\t\treturn text(`Removed cron job '${job.name}' (${job.id}).`);\n\t\t\t\t\t}\n\t\t\t\t\tcase \"run\": {\n\t\t\t\t\t\tif (!params.id) return text(\"cron run: id (or unique name) is required.\");\n\t\t\t\t\t\tconst job = getJob(params.id);\n\t\t\t\t\t\treturn text(await triggerRun(job, ctx));\n\t\t\t\t\t}\n\t\t\t\t\tdefault:\n\t\t\t\t\t\treturn text(`cron: unknown action \"${String(params.action)}\".`);\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\treturn text(`cron: ${String((err as Error)?.message ?? err)}`);\n\t\t\t}\n\t\t},\n\t});\n}\n"]}
@@ -24,9 +24,11 @@
24
24
  */
25
25
  import { Type } from "@sinclair/typebox";
26
26
  import { beginCronFire, endCronFire, isCronFire } from "../core/cron/fire-guard.js";
27
- import { createJob, getJob, listJobs, parseSchedule, pauseJob, removeJob, resumeJob, updateJob, } from "../core/cron/jobs.js";
27
+ import { createJob, getJob, listJobs, parseSchedule, pauseJob, removeJob, resumeJob, setCronDeliverValidator, updateJob, } from "../core/cron/jobs.js";
28
28
  import { currentOrigin } from "../core/cron/origin-context.js";
29
29
  import { executeJob, startScheduler } from "../core/cron/scheduler.js";
30
+ import { loadGatewayConfig } from "../gateway/config.js";
31
+ import { createDeliverValidator } from "../gateway/cron.js";
30
32
  // ---------------------------------------------------------------------------
31
33
  // Module state
32
34
  // ---------------------------------------------------------------------------
@@ -89,6 +91,9 @@ function formatStatus() {
89
91
  // Extension
90
92
  // ---------------------------------------------------------------------------
91
93
  export default function (pi) {
94
+ // Same allowlist the gateway uses, so a TUI session cannot persist a job
95
+ // that later posts to a chat the bot can reach but is not allowed.
96
+ setCronDeliverValidator(createDeliverValidator(loadGatewayConfig()));
92
97
  // --- Local delivery bridge (Phase 4 gateway replaces this) ---
93
98
  globalThis[DELIVERY_BRIDGE_SYMBOL] = async (job, _content) => {
94
99
  try {
@@ -1 +1 @@
1
- {"version":3,"file":"lunr-cron.js","sourceRoot":"","sources":["../../src/builtin-extensions/lunr-cron.ts"],"names":[],"mappings":"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","sourcesContent":["// @ts-nocheck\n/**\n * lunr-cron — lunR-native scheduled jobs (Phase 1: local TUI delivery;\n * Phase 4: gateway delivery + origin stamping).\n *\n * lunR: this file is lunR-native (not an absorbed upstream extension). It wires\n * core/cron (jobs store + scheduler) into the interactive session:\n *\n * - `/cron list|create|pause|resume|run|remove|status` command.\n * - One `cron` tool (TypeBox) so the agent can manage jobs. The shared\n * core/cron/fire-guard depth counter refuses the tool while a cron-fired\n * turn is in flight — TUI-fired OR gateway-fired (jobs cannot schedule\n * jobs).\n * - Jobs created inside a gateway chat turn are stamped with that chat as\n * their origin and default deliver to \"origin\" (core/cron/origin-context).\n * - Scheduler starts on session_start only in \"tui\" mode, stops on\n * session_shutdown. runJob = sendUserMessage + wait for agent_end;\n * deliverResult reads the `@lunr/cron-delivery` bridge on globalThis\n * (registered here as the local notify; the Phase 4 gateway replaces\n * it — core/cron/scheduler.ts never touches the bridge itself).\n *\n * `// @ts-nocheck` matches the builtin-extension convention (see lunr-behavior).\n * Runtime imports stay on concrete core modules — never the package barrel.\n */\n\nimport type { ExtensionAPI, ExtensionContext } from \"@ashx-j/lunr\";\nimport { Type } from \"@sinclair/typebox\";\nimport { beginCronFire, endCronFire, isCronFire } from \"../core/cron/fire-guard.ts\";\nimport {\n\tcreateJob,\n\tgetJob,\n\tlistJobs,\n\tparseSchedule,\n\tpauseJob,\n\tremoveJob,\n\tresumeJob,\n\tupdateJob,\n} from \"../core/cron/jobs.ts\";\nimport { currentOrigin } from \"../core/cron/origin-context.ts\";\nimport { executeJob, startScheduler } from \"../core/cron/scheduler.ts\";\n\n// ---------------------------------------------------------------------------\n// Module state\n// ---------------------------------------------------------------------------\n/** The cron-fired turn currently waiting for agent_end. */\nlet pendingRun: { resolve: (text: string) => void; reject: (err: Error) => void } | null = null;\nlet scheduler: { stop(): void } | null = null;\nlet lastCtx: ExtensionContext | null = null;\n\nconst DELIVERY_BRIDGE_SYMBOL = Symbol.for(\"@lunr/cron-delivery\");\n\n// ---------------------------------------------------------------------------\n// Turn plumbing\n// ---------------------------------------------------------------------------\n\n/** Final assistant text from an agent_end messages array (last assistant message, text blocks only). */\nfunction extractAssistantText(messages: unknown[]): string {\n\tfor (let i = messages.length - 1; i >= 0; i--) {\n\t\tconst msg = messages[i] as any;\n\t\tif (msg?.role !== \"assistant\") continue;\n\t\tconst parts = Array.isArray(msg.content) ? msg.content : [];\n\t\tconst text = parts\n\t\t\t.filter((p: any) => p?.type === \"text\")\n\t\t\t.map((p: any) => String(p.text ?? \"\"))\n\t\t\t.join(\"\")\n\t\t\t.trim();\n\t\tif (text) return text;\n\t}\n\treturn \"\";\n}\n\n// ---------------------------------------------------------------------------\n// Formatting helpers\n// ---------------------------------------------------------------------------\n\nfunction fmtTime(iso: string | null): string {\n\tif (!iso) return \"-\";\n\tconst d = new Date(iso);\n\tif (Number.isNaN(d.getTime())) return \"-\";\n\tconst p = (n: number) => String(n).padStart(2, \"0\");\n\t// Stored timestamps are UTC ISO; render in local time so users read wall-clock.\n\treturn `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}`;\n}\n\nfunction formatJobList(): string {\n\tconst jobs = listJobs();\n\tif (jobs.length === 0) return \"No cron jobs. Create one with /cron create <schedule> <prompt>.\";\n\tconst lines = jobs.map(\n\t\t(j) =>\n\t\t\t`${j.id} ${j.state}${j.enabled ? \"\" : \" (disabled)\"} ${j.scheduleDisplay} next=${fmtTime(j.nextRunAt)} last=${j.lastStatus ?? \"-\"}${j.lastError ? ` (${j.lastError})` : \"\"} ${j.name}`,\n\t);\n\treturn `Cron jobs (${jobs.length}):\\n${lines.join(\"\\n\")}`;\n}\n\nfunction formatStatus(): string {\n\tconst jobs = listJobs();\n\tconst count = (s: string) => jobs.filter((j) => j.state === s).length;\n\tconst next = jobs\n\t\t.filter((j) => j.enabled && j.state === \"scheduled\" && j.nextRunAt)\n\t\t.sort((a, b) => String(a.nextRunAt).localeCompare(String(b.nextRunAt)))[0];\n\treturn (\n\t\t`Cron: scheduler ${scheduler ? \"running\" : \"stopped\"}; ${jobs.length} job(s) — ` +\n\t\t`${count(\"scheduled\")} scheduled, ${count(\"paused\")} paused, ${count(\"completed\")} completed, ${count(\"error\")} error.` +\n\t\t(next ? ` Next: '${next.name}' at ${fmtTime(next.nextRunAt)}.` : \"\")\n\t);\n}\n\n// ---------------------------------------------------------------------------\n// Extension\n// ---------------------------------------------------------------------------\n\nexport default function (pi: ExtensionAPI): void {\n\t// --- Local delivery bridge (Phase 4 gateway replaces this) ---\n\t(globalThis as Record<symbol, unknown>)[DELIVERY_BRIDGE_SYMBOL] = async (job: { name: string }, _content: string) => {\n\t\ttry {\n\t\t\tlastCtx?.ui.notify(`Cron job '${job.name}' finished`, \"info\");\n\t\t\treturn null;\n\t\t} catch (err) {\n\t\t\treturn String((err as Error)?.message ?? err);\n\t\t}\n\t};\n\n\t// --- Scheduler deps (TUI) ---\n\tconst runJob = (prompt: string, _job: unknown): Promise<string> => {\n\t\tif (pendingRun) return Promise.reject(new Error(\"another cron job turn is already in flight\"));\n\t\treturn new Promise<string>((resolve, reject) => {\n\t\t\tbeginCronFire();\n\t\t\tpendingRun = { resolve, reject };\n\t\t\ttry {\n\t\t\t\tpi.sendUserMessage(prompt);\n\t\t\t} catch (err) {\n\t\t\t\tpendingRun = null;\n\t\t\t\tendCronFire();\n\t\t\t\treject(err as Error);\n\t\t\t}\n\t\t});\n\t};\n\n\tconst deliverResult = async (job: any, content: string): Promise<void> => {\n\t\tconst bridge = (globalThis as Record<symbol, unknown>)[DELIVERY_BRIDGE_SYMBOL] as\n\t\t\t| ((job: unknown, content: string) => Promise<string | null>)\n\t\t\t| undefined;\n\t\tif (!bridge) throw new Error(\"no cron delivery bridge registered\");\n\t\tconst err = await bridge(job, content);\n\t\tif (err) throw new Error(String(err));\n\t};\n\n\tconst schedulerDeps = { runJob, deliverResult };\n\n\t/** Manual trigger: run inline when idle, otherwise make the job due for the next tick. */\n\tconst triggerRun = async (job: any, ctx: ExtensionContext): Promise<string> => {\n\t\tif (ctx.isIdle()) {\n\t\t\ttry {\n\t\t\t\tawait executeJob(getJob(job.id), schedulerDeps);\n\t\t\t\treturn `Cron job '${job.name}' (${job.id}) finished.`;\n\t\t\t} catch (err) {\n\t\t\t\treturn `Cron job '${job.name}' (${job.id}) failed: ${String((err as Error)?.message ?? err)}`;\n\t\t\t}\n\t\t}\n\t\tawait updateJob(job.id, { nextRunAt: new Date().toISOString() });\n\t\treturn `Cron job '${job.name}' (${job.id}) queued for the next scheduler tick.`;\n\t};\n\n\t// --- Resolve the cron-fired turn when the agent turn ends ---\n\tpi.on(\"agent_end\", (event) => {\n\t\tif (!pendingRun) return;\n\t\tconst pending = pendingRun;\n\t\tpendingRun = null;\n\t\tendCronFire();\n\t\tpending.resolve(extractAssistantText(event.messages ?? []));\n\t});\n\n\t// --- Scheduler lifecycle: TUI sessions only ---\n\tpi.on(\"session_start\", (_event, ctx) => {\n\t\tlastCtx = ctx;\n\t\tif (ctx.mode !== \"tui\") return;\n\t\tscheduler?.stop();\n\t\tscheduler = startScheduler(schedulerDeps);\n\t});\n\n\tpi.on(\"session_shutdown\", () => {\n\t\tscheduler?.stop();\n\t\tscheduler = null;\n\t});\n\n\t// --- /cron command ---\n\tpi.registerCommand(\"cron\", {\n\t\tdescription: \"Manage cron jobs: /cron list | create <schedule> <prompt> | pause|resume|run|remove <id-or-name> | status\",\n\t\tgetArgumentCompletions: (prefix: string) => {\n\t\t\tconst subs = [\"list\", \"create\", \"pause\", \"resume\", \"run\", \"remove\", \"status\"];\n\t\t\tconst lower = prefix.toLowerCase();\n\t\t\treturn subs\n\t\t\t\t.filter((s) => s.startsWith(lower))\n\t\t\t\t.map((s) => ({ value: s, label: s, description: `/cron ${s}` }));\n\t\t},\n\t\thandler: async (args, ctx) => {\n\t\t\tlastCtx = ctx;\n\t\t\tconst tokens = args.trim().split(/\\s+/).filter(Boolean);\n\t\t\tconst sub = (tokens.shift() ?? \"list\").toLowerCase();\n\t\t\ttry {\n\t\t\t\tswitch (sub) {\n\t\t\t\t\tcase \"list\": {\n\t\t\t\t\t\tctx.ui.notify(formatJobList(), \"info\");\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tcase \"status\": {\n\t\t\t\t\t\tctx.ui.notify(formatStatus(), \"info\");\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tcase \"create\": {\n\t\t\t\t\t\t// Greedy: the schedule is the shortest leading token run that parses.\n\t\t\t\t\t\tlet used = 0;\n\t\t\t\t\t\tfor (let k = 1; k <= Math.min(6, tokens.length); k++) {\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\tparseSchedule(tokens.slice(0, k).join(\" \"));\n\t\t\t\t\t\t\t\tused = k;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t\t// keep extending\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst prompt = tokens.slice(used).join(\" \").trim();\n\t\t\t\t\t\tif (!used || !prompt) {\n\t\t\t\t\t\t\tctx.ui.notify(\"Usage: /cron create <schedule> <prompt> — e.g. /cron create every 30m check the deploy\", \"error\");\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst job = await createJob({ prompt, schedule: tokens.slice(0, used).join(\" \") });\n\t\t\t\t\t\tctx.ui.notify(`Created cron job '${job.name}' (${job.id}) — ${job.scheduleDisplay}, next run ${fmtTime(job.nextRunAt)}.`, \"info\");\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tcase \"pause\":\n\t\t\t\t\tcase \"resume\":\n\t\t\t\t\tcase \"remove\":\n\t\t\t\t\tcase \"run\": {\n\t\t\t\t\t\tconst idOrName = tokens.join(\" \").trim();\n\t\t\t\t\t\tif (!idOrName) {\n\t\t\t\t\t\t\tctx.ui.notify(`Usage: /cron ${sub} <id-or-name>`, \"error\");\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (sub === \"pause\") {\n\t\t\t\t\t\t\tconst job = await pauseJob(idOrName);\n\t\t\t\t\t\t\tctx.ui.notify(`Paused cron job '${job.name}' (${job.id}).`, \"info\");\n\t\t\t\t\t\t} else if (sub === \"resume\") {\n\t\t\t\t\t\t\tconst job = await resumeJob(idOrName);\n\t\t\t\t\t\t\tctx.ui.notify(`Resumed cron job '${job.name}' (${job.id}) — next run ${fmtTime(job.nextRunAt)}.`, \"info\");\n\t\t\t\t\t\t} else if (sub === \"remove\") {\n\t\t\t\t\t\t\tconst job = await removeJob(idOrName);\n\t\t\t\t\t\t\tctx.ui.notify(`Removed cron job '${job.name}' (${job.id}).`, \"info\");\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tconst job = getJob(idOrName);\n\t\t\t\t\t\t\tctx.ui.notify(await triggerRun(job, ctx), \"info\");\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tdefault: {\n\t\t\t\t\t\tctx.ui.notify(\"Usage: /cron list | create <schedule> <prompt> | pause|resume|run|remove <id-or-name> | status\", \"error\");\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\tctx.ui.notify(String((err as Error)?.message ?? err), \"error\");\n\t\t\t}\n\t\t},\n\t});\n\n\t// --- cron tool (agent-facing) ---\n\tpi.registerTool({\n\t\tname: \"cron\",\n\t\tlabel: \"Cron\",\n\t\tdescription: [\n\t\t\t\"Manage scheduled cron jobs that run prompts unattended in this session.\",\n\t\t\t\"Actions: create (needs prompt + schedule), list, update (id + fields), pause, resume, remove, run (trigger now).\",\n\t\t\t\"Schedule formats: 'every 30m' / 'every 2h' / 'every 1d' (recurring), '30m'/'2h' or an ISO timestamp (one-shot), or a 5-field cron expression.\",\n\t\t\t\"Job output is delivered automatically; the job's prompt can answer [SILENT] to suppress delivery.\",\n\t\t].join(\"\\n\"),\n\t\tparameters: Type.Object({\n\t\t\taction: Type.Union([\n\t\t\t\tType.Literal(\"create\"),\n\t\t\t\tType.Literal(\"list\"),\n\t\t\t\tType.Literal(\"update\"),\n\t\t\t\tType.Literal(\"pause\"),\n\t\t\t\tType.Literal(\"resume\"),\n\t\t\t\tType.Literal(\"remove\"),\n\t\t\t\tType.Literal(\"run\"),\n\t\t\t]),\n\t\t\tid: Type.Optional(Type.String({ description: \"Job id or unique name (all actions except create/list).\" })),\n\t\t\tname: Type.Optional(Type.String({ description: \"Display name (<=50 chars); derived from the prompt when omitted.\" })),\n\t\t\tprompt: Type.Optional(Type.String({ description: \"The prompt the job runs (create/update).\" })),\n\t\t\tschedule: Type.Optional(Type.String({ description: \"Schedule input (create/update).\" })),\n\t\t\tdeliver: Type.Optional(\n\t\t\t\tType.String({\n\t\t\t\t\tdescription:\n\t\t\t\t\t\t\"Delivery target(s), comma-separated: 'local' (output file only), 'origin' (the chat that created the job), 'telegram'/'discord' (platform home channel), or 'telegram:<chatId>[:<threadId>]' / 'discord:<chatId>[:<threadId>]'. Defaults to 'origin' when created from a gateway chat, else 'local'.\",\n\t\t\t\t}),\n\t\t\t),\n\t\t}),\n\t\tasync execute(_toolCallId, params, _signal, _onUpdate, ctx) {\n\t\t\tconst text = (t: string) => ({ content: [{ type: \"text\" as const, text: t }] });\n\t\t\tif (isCronFire()) {\n\t\t\t\treturn text(\"cron: unavailable while a cron-fired turn is running (cron jobs cannot schedule cron jobs).\");\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tswitch (params.action) {\n\t\t\t\t\tcase \"create\": {\n\t\t\t\t\t\tif (!params.prompt?.trim() || !params.schedule?.trim()) {\n\t\t\t\t\t\t\treturn text(\"cron create: prompt and schedule are required.\");\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// Inside a gateway chat turn, stamp the chat as the delivery\n\t\t\t\t\t\t// origin and default deliver to \"origin\" (Phase 4); outside\n\t\t\t\t\t\t// any origin context the default stays \"local\".\n\t\t\t\t\t\tconst origin = currentOrigin();\n\t\t\t\t\t\tconst job = await createJob({\n\t\t\t\t\t\t\tprompt: params.prompt,\n\t\t\t\t\t\t\tschedule: params.schedule,\n\t\t\t\t\t\t\tname: params.name,\n\t\t\t\t\t\t\tdeliver: params.deliver ?? (origin ? \"origin\" : undefined),\n\t\t\t\t\t\t\torigin: origin ? { ...origin } : undefined,\n\t\t\t\t\t\t});\n\t\t\t\t\t\treturn text(`Created cron job '${job.name}' (${job.id}) — ${job.scheduleDisplay}, next run ${fmtTime(job.nextRunAt)}.`);\n\t\t\t\t\t}\n\t\t\t\t\tcase \"list\": {\n\t\t\t\t\t\treturn text(formatJobList());\n\t\t\t\t\t}\n\t\t\t\t\tcase \"update\": {\n\t\t\t\t\t\tif (!params.id) return text(\"cron update: id (or unique name) is required.\");\n\t\t\t\t\t\tconst job = await updateJob(params.id, {\n\t\t\t\t\t\t\tname: params.name,\n\t\t\t\t\t\t\tprompt: params.prompt,\n\t\t\t\t\t\t\tschedule: params.schedule,\n\t\t\t\t\t\t\tdeliver: params.deliver,\n\t\t\t\t\t\t});\n\t\t\t\t\t\treturn text(`Updated cron job '${job.name}' (${job.id}) — ${job.scheduleDisplay}, next run ${fmtTime(job.nextRunAt)}.`);\n\t\t\t\t\t}\n\t\t\t\t\tcase \"pause\": {\n\t\t\t\t\t\tif (!params.id) return text(\"cron pause: id (or unique name) is required.\");\n\t\t\t\t\t\tconst job = await pauseJob(params.id);\n\t\t\t\t\t\treturn text(`Paused cron job '${job.name}' (${job.id}).`);\n\t\t\t\t\t}\n\t\t\t\t\tcase \"resume\": {\n\t\t\t\t\t\tif (!params.id) return text(\"cron resume: id (or unique name) is required.\");\n\t\t\t\t\t\tconst job = await resumeJob(params.id);\n\t\t\t\t\t\treturn text(`Resumed cron job '${job.name}' (${job.id}) — next run ${fmtTime(job.nextRunAt)}.`);\n\t\t\t\t\t}\n\t\t\t\t\tcase \"remove\": {\n\t\t\t\t\t\tif (!params.id) return text(\"cron remove: id (or unique name) is required.\");\n\t\t\t\t\t\tconst job = await removeJob(params.id);\n\t\t\t\t\t\treturn text(`Removed cron job '${job.name}' (${job.id}).`);\n\t\t\t\t\t}\n\t\t\t\t\tcase \"run\": {\n\t\t\t\t\t\tif (!params.id) return text(\"cron run: id (or unique name) is required.\");\n\t\t\t\t\t\tconst job = getJob(params.id);\n\t\t\t\t\t\treturn text(await triggerRun(job, ctx));\n\t\t\t\t\t}\n\t\t\t\t\tdefault:\n\t\t\t\t\t\treturn text(`cron: unknown action \"${String(params.action)}\".`);\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\treturn text(`cron: ${String((err as Error)?.message ?? err)}`);\n\t\t\t}\n\t\t},\n\t});\n}\n"]}
1
+ {"version":3,"file":"lunr-cron.js","sourceRoot":"","sources":["../../src/builtin-extensions/lunr-cron.ts"],"names":[],"mappings":"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","sourcesContent":["// @ts-nocheck\n/**\n * lunr-cron — lunR-native scheduled jobs (Phase 1: local TUI delivery;\n * Phase 4: gateway delivery + origin stamping).\n *\n * lunR: this file is lunR-native (not an absorbed upstream extension). It wires\n * core/cron (jobs store + scheduler) into the interactive session:\n *\n * - `/cron list|create|pause|resume|run|remove|status` command.\n * - One `cron` tool (TypeBox) so the agent can manage jobs. The shared\n * core/cron/fire-guard depth counter refuses the tool while a cron-fired\n * turn is in flight — TUI-fired OR gateway-fired (jobs cannot schedule\n * jobs).\n * - Jobs created inside a gateway chat turn are stamped with that chat as\n * their origin and default deliver to \"origin\" (core/cron/origin-context).\n * - Scheduler starts on session_start only in \"tui\" mode, stops on\n * session_shutdown. runJob = sendUserMessage + wait for agent_end;\n * deliverResult reads the `@lunr/cron-delivery` bridge on globalThis\n * (registered here as the local notify; the Phase 4 gateway replaces\n * it — core/cron/scheduler.ts never touches the bridge itself).\n *\n * `// @ts-nocheck` matches the builtin-extension convention (see lunr-behavior).\n * Runtime imports stay on concrete core modules — never the package barrel.\n */\n\nimport type { ExtensionAPI, ExtensionContext } from \"@ashx-j/lunr\";\nimport { Type } from \"@sinclair/typebox\";\nimport { beginCronFire, endCronFire, isCronFire } from \"../core/cron/fire-guard.ts\";\nimport {\n\tcreateJob,\n\tgetJob,\n\tlistJobs,\n\tparseSchedule,\n\tpauseJob,\n\tremoveJob,\n\tresumeJob,\n\tsetCronDeliverValidator,\n\tupdateJob,\n} from \"../core/cron/jobs.ts\";\nimport { currentOrigin } from \"../core/cron/origin-context.ts\";\nimport { executeJob, startScheduler } from \"../core/cron/scheduler.ts\";\nimport { loadGatewayConfig } from \"../gateway/config.ts\";\nimport { createDeliverValidator } from \"../gateway/cron.ts\";\n\n// ---------------------------------------------------------------------------\n// Module state\n// ---------------------------------------------------------------------------\n/** The cron-fired turn currently waiting for agent_end. */\nlet pendingRun: { resolve: (text: string) => void; reject: (err: Error) => void } | null = null;\nlet scheduler: { stop(): void } | null = null;\nlet lastCtx: ExtensionContext | null = null;\n\nconst DELIVERY_BRIDGE_SYMBOL = Symbol.for(\"@lunr/cron-delivery\");\n\n// ---------------------------------------------------------------------------\n// Turn plumbing\n// ---------------------------------------------------------------------------\n\n/** Final assistant text from an agent_end messages array (last assistant message, text blocks only). */\nfunction extractAssistantText(messages: unknown[]): string {\n\tfor (let i = messages.length - 1; i >= 0; i--) {\n\t\tconst msg = messages[i] as any;\n\t\tif (msg?.role !== \"assistant\") continue;\n\t\tconst parts = Array.isArray(msg.content) ? msg.content : [];\n\t\tconst text = parts\n\t\t\t.filter((p: any) => p?.type === \"text\")\n\t\t\t.map((p: any) => String(p.text ?? \"\"))\n\t\t\t.join(\"\")\n\t\t\t.trim();\n\t\tif (text) return text;\n\t}\n\treturn \"\";\n}\n\n// ---------------------------------------------------------------------------\n// Formatting helpers\n// ---------------------------------------------------------------------------\n\nfunction fmtTime(iso: string | null): string {\n\tif (!iso) return \"-\";\n\tconst d = new Date(iso);\n\tif (Number.isNaN(d.getTime())) return \"-\";\n\tconst p = (n: number) => String(n).padStart(2, \"0\");\n\t// Stored timestamps are UTC ISO; render in local time so users read wall-clock.\n\treturn `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}`;\n}\n\nfunction formatJobList(): string {\n\tconst jobs = listJobs();\n\tif (jobs.length === 0) return \"No cron jobs. Create one with /cron create <schedule> <prompt>.\";\n\tconst lines = jobs.map(\n\t\t(j) =>\n\t\t\t`${j.id} ${j.state}${j.enabled ? \"\" : \" (disabled)\"} ${j.scheduleDisplay} next=${fmtTime(j.nextRunAt)} last=${j.lastStatus ?? \"-\"}${j.lastError ? ` (${j.lastError})` : \"\"} ${j.name}`,\n\t);\n\treturn `Cron jobs (${jobs.length}):\\n${lines.join(\"\\n\")}`;\n}\n\nfunction formatStatus(): string {\n\tconst jobs = listJobs();\n\tconst count = (s: string) => jobs.filter((j) => j.state === s).length;\n\tconst next = jobs\n\t\t.filter((j) => j.enabled && j.state === \"scheduled\" && j.nextRunAt)\n\t\t.sort((a, b) => String(a.nextRunAt).localeCompare(String(b.nextRunAt)))[0];\n\treturn (\n\t\t`Cron: scheduler ${scheduler ? \"running\" : \"stopped\"}; ${jobs.length} job(s) — ` +\n\t\t`${count(\"scheduled\")} scheduled, ${count(\"paused\")} paused, ${count(\"completed\")} completed, ${count(\"error\")} error.` +\n\t\t(next ? ` Next: '${next.name}' at ${fmtTime(next.nextRunAt)}.` : \"\")\n\t);\n}\n\n// ---------------------------------------------------------------------------\n// Extension\n// ---------------------------------------------------------------------------\n\nexport default function (pi: ExtensionAPI): void {\n\t// Same allowlist the gateway uses, so a TUI session cannot persist a job\n\t// that later posts to a chat the bot can reach but is not allowed.\n\tsetCronDeliverValidator(createDeliverValidator(loadGatewayConfig()));\n\n\t// --- Local delivery bridge (Phase 4 gateway replaces this) ---\n\t(globalThis as Record<symbol, unknown>)[DELIVERY_BRIDGE_SYMBOL] = async (job: { name: string }, _content: string) => {\n\t\ttry {\n\t\t\tlastCtx?.ui.notify(`Cron job '${job.name}' finished`, \"info\");\n\t\t\treturn null;\n\t\t} catch (err) {\n\t\t\treturn String((err as Error)?.message ?? err);\n\t\t}\n\t};\n\n\t// --- Scheduler deps (TUI) ---\n\tconst runJob = (prompt: string, _job: unknown): Promise<string> => {\n\t\tif (pendingRun) return Promise.reject(new Error(\"another cron job turn is already in flight\"));\n\t\treturn new Promise<string>((resolve, reject) => {\n\t\t\tbeginCronFire();\n\t\t\tpendingRun = { resolve, reject };\n\t\t\ttry {\n\t\t\t\tpi.sendUserMessage(prompt);\n\t\t\t} catch (err) {\n\t\t\t\tpendingRun = null;\n\t\t\t\tendCronFire();\n\t\t\t\treject(err as Error);\n\t\t\t}\n\t\t});\n\t};\n\n\tconst deliverResult = async (job: any, content: string): Promise<void> => {\n\t\tconst bridge = (globalThis as Record<symbol, unknown>)[DELIVERY_BRIDGE_SYMBOL] as\n\t\t\t| ((job: unknown, content: string) => Promise<string | null>)\n\t\t\t| undefined;\n\t\tif (!bridge) throw new Error(\"no cron delivery bridge registered\");\n\t\tconst err = await bridge(job, content);\n\t\tif (err) throw new Error(String(err));\n\t};\n\n\tconst schedulerDeps = { runJob, deliverResult };\n\n\t/** Manual trigger: run inline when idle, otherwise make the job due for the next tick. */\n\tconst triggerRun = async (job: any, ctx: ExtensionContext): Promise<string> => {\n\t\tif (ctx.isIdle()) {\n\t\t\ttry {\n\t\t\t\tawait executeJob(getJob(job.id), schedulerDeps);\n\t\t\t\treturn `Cron job '${job.name}' (${job.id}) finished.`;\n\t\t\t} catch (err) {\n\t\t\t\treturn `Cron job '${job.name}' (${job.id}) failed: ${String((err as Error)?.message ?? err)}`;\n\t\t\t}\n\t\t}\n\t\tawait updateJob(job.id, { nextRunAt: new Date().toISOString() });\n\t\treturn `Cron job '${job.name}' (${job.id}) queued for the next scheduler tick.`;\n\t};\n\n\t// --- Resolve the cron-fired turn when the agent turn ends ---\n\tpi.on(\"agent_end\", (event) => {\n\t\tif (!pendingRun) return;\n\t\tconst pending = pendingRun;\n\t\tpendingRun = null;\n\t\tendCronFire();\n\t\tpending.resolve(extractAssistantText(event.messages ?? []));\n\t});\n\n\t// --- Scheduler lifecycle: TUI sessions only ---\n\tpi.on(\"session_start\", (_event, ctx) => {\n\t\tlastCtx = ctx;\n\t\tif (ctx.mode !== \"tui\") return;\n\t\tscheduler?.stop();\n\t\tscheduler = startScheduler(schedulerDeps);\n\t});\n\n\tpi.on(\"session_shutdown\", () => {\n\t\tscheduler?.stop();\n\t\tscheduler = null;\n\t});\n\n\t// --- /cron command ---\n\tpi.registerCommand(\"cron\", {\n\t\tdescription: \"Manage cron jobs: /cron list | create <schedule> <prompt> | pause|resume|run|remove <id-or-name> | status\",\n\t\tgetArgumentCompletions: (prefix: string) => {\n\t\t\tconst subs = [\"list\", \"create\", \"pause\", \"resume\", \"run\", \"remove\", \"status\"];\n\t\t\tconst lower = prefix.toLowerCase();\n\t\t\treturn subs\n\t\t\t\t.filter((s) => s.startsWith(lower))\n\t\t\t\t.map((s) => ({ value: s, label: s, description: `/cron ${s}` }));\n\t\t},\n\t\thandler: async (args, ctx) => {\n\t\t\tlastCtx = ctx;\n\t\t\tconst tokens = args.trim().split(/\\s+/).filter(Boolean);\n\t\t\tconst sub = (tokens.shift() ?? \"list\").toLowerCase();\n\t\t\ttry {\n\t\t\t\tswitch (sub) {\n\t\t\t\t\tcase \"list\": {\n\t\t\t\t\t\tctx.ui.notify(formatJobList(), \"info\");\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tcase \"status\": {\n\t\t\t\t\t\tctx.ui.notify(formatStatus(), \"info\");\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tcase \"create\": {\n\t\t\t\t\t\t// Greedy: the schedule is the shortest leading token run that parses.\n\t\t\t\t\t\tlet used = 0;\n\t\t\t\t\t\tfor (let k = 1; k <= Math.min(6, tokens.length); k++) {\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\tparseSchedule(tokens.slice(0, k).join(\" \"));\n\t\t\t\t\t\t\t\tused = k;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t\t// keep extending\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst prompt = tokens.slice(used).join(\" \").trim();\n\t\t\t\t\t\tif (!used || !prompt) {\n\t\t\t\t\t\t\tctx.ui.notify(\"Usage: /cron create <schedule> <prompt> — e.g. /cron create every 30m check the deploy\", \"error\");\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst job = await createJob({ prompt, schedule: tokens.slice(0, used).join(\" \") });\n\t\t\t\t\t\tctx.ui.notify(`Created cron job '${job.name}' (${job.id}) — ${job.scheduleDisplay}, next run ${fmtTime(job.nextRunAt)}.`, \"info\");\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tcase \"pause\":\n\t\t\t\t\tcase \"resume\":\n\t\t\t\t\tcase \"remove\":\n\t\t\t\t\tcase \"run\": {\n\t\t\t\t\t\tconst idOrName = tokens.join(\" \").trim();\n\t\t\t\t\t\tif (!idOrName) {\n\t\t\t\t\t\t\tctx.ui.notify(`Usage: /cron ${sub} <id-or-name>`, \"error\");\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (sub === \"pause\") {\n\t\t\t\t\t\t\tconst job = await pauseJob(idOrName);\n\t\t\t\t\t\t\tctx.ui.notify(`Paused cron job '${job.name}' (${job.id}).`, \"info\");\n\t\t\t\t\t\t} else if (sub === \"resume\") {\n\t\t\t\t\t\t\tconst job = await resumeJob(idOrName);\n\t\t\t\t\t\t\tctx.ui.notify(`Resumed cron job '${job.name}' (${job.id}) — next run ${fmtTime(job.nextRunAt)}.`, \"info\");\n\t\t\t\t\t\t} else if (sub === \"remove\") {\n\t\t\t\t\t\t\tconst job = await removeJob(idOrName);\n\t\t\t\t\t\t\tctx.ui.notify(`Removed cron job '${job.name}' (${job.id}).`, \"info\");\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tconst job = getJob(idOrName);\n\t\t\t\t\t\t\tctx.ui.notify(await triggerRun(job, ctx), \"info\");\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tdefault: {\n\t\t\t\t\t\tctx.ui.notify(\"Usage: /cron list | create <schedule> <prompt> | pause|resume|run|remove <id-or-name> | status\", \"error\");\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\tctx.ui.notify(String((err as Error)?.message ?? err), \"error\");\n\t\t\t}\n\t\t},\n\t});\n\n\t// --- cron tool (agent-facing) ---\n\tpi.registerTool({\n\t\tname: \"cron\",\n\t\tlabel: \"Cron\",\n\t\tdescription: [\n\t\t\t\"Manage scheduled cron jobs that run prompts unattended in this session.\",\n\t\t\t\"Actions: create (needs prompt + schedule), list, update (id + fields), pause, resume, remove, run (trigger now).\",\n\t\t\t\"Schedule formats: 'every 30m' / 'every 2h' / 'every 1d' (recurring), '30m'/'2h' or an ISO timestamp (one-shot), or a 5-field cron expression.\",\n\t\t\t\"Job output is delivered automatically; the job's prompt can answer [SILENT] to suppress delivery.\",\n\t\t].join(\"\\n\"),\n\t\tparameters: Type.Object({\n\t\t\taction: Type.Union([\n\t\t\t\tType.Literal(\"create\"),\n\t\t\t\tType.Literal(\"list\"),\n\t\t\t\tType.Literal(\"update\"),\n\t\t\t\tType.Literal(\"pause\"),\n\t\t\t\tType.Literal(\"resume\"),\n\t\t\t\tType.Literal(\"remove\"),\n\t\t\t\tType.Literal(\"run\"),\n\t\t\t]),\n\t\t\tid: Type.Optional(Type.String({ description: \"Job id or unique name (all actions except create/list).\" })),\n\t\t\tname: Type.Optional(Type.String({ description: \"Display name (<=50 chars); derived from the prompt when omitted.\" })),\n\t\t\tprompt: Type.Optional(Type.String({ description: \"The prompt the job runs (create/update).\" })),\n\t\t\tschedule: Type.Optional(Type.String({ description: \"Schedule input (create/update).\" })),\n\t\t\tdeliver: Type.Optional(\n\t\t\t\tType.String({\n\t\t\t\t\tdescription:\n\t\t\t\t\t\t\"Delivery target(s), comma-separated: 'local' (output file only), 'origin' (the chat that created the job), 'telegram'/'discord' (platform home channel), or 'telegram:<chatId>[:<threadId>]' / 'discord:<chatId>[:<threadId>]'. Defaults to 'origin' when created from a gateway chat, else 'local'.\",\n\t\t\t\t}),\n\t\t\t),\n\t\t}),\n\t\tasync execute(_toolCallId, params, _signal, _onUpdate, ctx) {\n\t\t\tconst text = (t: string) => ({ content: [{ type: \"text\" as const, text: t }] });\n\t\t\tif (isCronFire()) {\n\t\t\t\treturn text(\"cron: unavailable while a cron-fired turn is running (cron jobs cannot schedule cron jobs).\");\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tswitch (params.action) {\n\t\t\t\t\tcase \"create\": {\n\t\t\t\t\t\tif (!params.prompt?.trim() || !params.schedule?.trim()) {\n\t\t\t\t\t\t\treturn text(\"cron create: prompt and schedule are required.\");\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// Inside a gateway chat turn, stamp the chat as the delivery\n\t\t\t\t\t\t// origin and default deliver to \"origin\" (Phase 4); outside\n\t\t\t\t\t\t// any origin context the default stays \"local\".\n\t\t\t\t\t\tconst origin = currentOrigin();\n\t\t\t\t\t\tconst job = await createJob({\n\t\t\t\t\t\t\tprompt: params.prompt,\n\t\t\t\t\t\t\tschedule: params.schedule,\n\t\t\t\t\t\t\tname: params.name,\n\t\t\t\t\t\t\tdeliver: params.deliver ?? (origin ? \"origin\" : undefined),\n\t\t\t\t\t\t\torigin: origin ? { ...origin } : undefined,\n\t\t\t\t\t\t});\n\t\t\t\t\t\treturn text(`Created cron job '${job.name}' (${job.id}) — ${job.scheduleDisplay}, next run ${fmtTime(job.nextRunAt)}.`);\n\t\t\t\t\t}\n\t\t\t\t\tcase \"list\": {\n\t\t\t\t\t\treturn text(formatJobList());\n\t\t\t\t\t}\n\t\t\t\t\tcase \"update\": {\n\t\t\t\t\t\tif (!params.id) return text(\"cron update: id (or unique name) is required.\");\n\t\t\t\t\t\tconst job = await updateJob(params.id, {\n\t\t\t\t\t\t\tname: params.name,\n\t\t\t\t\t\t\tprompt: params.prompt,\n\t\t\t\t\t\t\tschedule: params.schedule,\n\t\t\t\t\t\t\tdeliver: params.deliver,\n\t\t\t\t\t\t});\n\t\t\t\t\t\treturn text(`Updated cron job '${job.name}' (${job.id}) — ${job.scheduleDisplay}, next run ${fmtTime(job.nextRunAt)}.`);\n\t\t\t\t\t}\n\t\t\t\t\tcase \"pause\": {\n\t\t\t\t\t\tif (!params.id) return text(\"cron pause: id (or unique name) is required.\");\n\t\t\t\t\t\tconst job = await pauseJob(params.id);\n\t\t\t\t\t\treturn text(`Paused cron job '${job.name}' (${job.id}).`);\n\t\t\t\t\t}\n\t\t\t\t\tcase \"resume\": {\n\t\t\t\t\t\tif (!params.id) return text(\"cron resume: id (or unique name) is required.\");\n\t\t\t\t\t\tconst job = await resumeJob(params.id);\n\t\t\t\t\t\treturn text(`Resumed cron job '${job.name}' (${job.id}) — next run ${fmtTime(job.nextRunAt)}.`);\n\t\t\t\t\t}\n\t\t\t\t\tcase \"remove\": {\n\t\t\t\t\t\tif (!params.id) return text(\"cron remove: id (or unique name) is required.\");\n\t\t\t\t\t\tconst job = await removeJob(params.id);\n\t\t\t\t\t\treturn text(`Removed cron job '${job.name}' (${job.id}).`);\n\t\t\t\t\t}\n\t\t\t\t\tcase \"run\": {\n\t\t\t\t\t\tif (!params.id) return text(\"cron run: id (or unique name) is required.\");\n\t\t\t\t\t\tconst job = getJob(params.id);\n\t\t\t\t\t\treturn text(await triggerRun(job, ctx));\n\t\t\t\t\t}\n\t\t\t\t\tdefault:\n\t\t\t\t\t\treturn text(`cron: unknown action \"${String(params.action)}\".`);\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\treturn text(`cron: ${String((err as Error)?.message ?? err)}`);\n\t\t\t}\n\t\t},\n\t});\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"fanout-child.d.ts","sourceRoot":"","sources":["../../../../../src/builtin-extensions/pi-subagents/src/extension/fanout-child.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,YAAY,EAAkB,MAAM,iCAAiC,CAAC;AA+HpF,MAAM,CAAC,OAAO,UAAU,oCAAoC,CAAC,EAAE,EAAE,YAAY,GAAG,IAAI,CA2CnF","sourcesContent":["// @ts-nocheck\nimport * as fs from \"node:fs\";\nimport * as os from \"node:os\";\nimport * as path from \"node:path\";\nimport type { ExtensionAPI, ToolDefinition } from \"@ashx-j/lunr\";\nimport { discoverAgents } from \"../agents/agents.ts\";\nimport { getArtifactsDir } from \"../shared/artifacts.ts\";\nimport { createSubagentExecutor, type SubagentParamsLike } from \"../runs/foreground/subagent-executor.ts\";\nimport { resolveWaitToolConfig } from \"../runs/background/wait-config.ts\";\nimport { SUBAGENT_CHILD_ENV, SUBAGENT_FANOUT_CHILD_ENV } from \"../runs/shared/pi-args.ts\";\nimport { readNestedControlRequests, resolveNestedRouteFromEnv, writeNestedControlResult } from \"../runs/shared/nested-events.ts\";\nimport { deliverSubagentIntercomMessageEvent } from \"../intercom/result-intercom.ts\";\nimport { resolveSubagentIntercomTarget } from \"../intercom/intercom-bridge.ts\";\nimport { SubagentParams } from \"./schemas.ts\";\nimport { loadConfig } from \"./config.ts\";\nimport { type Details, type SubagentState } from \"../shared/types.ts\";\n\nfunction getSubagentSessionRoot(parentSessionFile: string | null): string {\n\tif (parentSessionFile) {\n\t\tconst baseName = path.basename(parentSessionFile, \".jsonl\");\n\t\tconst sessionsDir = path.dirname(parentSessionFile);\n\t\treturn path.join(sessionsDir, baseName);\n\t}\n\treturn fs.mkdtempSync(path.join(os.tmpdir(), \"pi-subagent-session-\"));\n}\n\nfunction expandTilde(p: string): string {\n\treturn p.startsWith(\"~/\") ? path.join(os.homedir(), p.slice(2)) : p;\n}\n\nfunction createChildSafeState(): SubagentState {\n\treturn {\n\t\tbaseCwd: \"\",\n\t\tcurrentSessionId: null,\n\t\tsubagentInProgress: false,\n\t\tsubagentSpawns: { sessionId: null, count: 0 },\n\t\tasyncJobs: new Map(),\n\t\tforegroundRuns: new Map(),\n\t\tforegroundControls: new Map(),\n\t\tlastForegroundControlId: null,\n\t\tpendingForegroundControlNotices: new Map(),\n\t\tcleanupTimers: new Map(),\n\t\tlastUiContext: null,\n\t\tpoller: null,\n\t\tcompletionSeen: new Map(),\n\t\twatcher: null,\n\t\twatcherRestartTimer: null,\n\t\tresultFileCoalescer: {\n\t\t\tschedule: () => false,\n\t\t\tclear: () => {},\n\t\t},\n\t};\n}\n\nfunction startNestedControlInboxListener(pi: ExtensionAPI, state: SubagentState): NodeJS.Timeout | undefined {\n\tlet route;\n\ttry {\n\t\troute = resolveNestedRouteFromEnv();\n\t} catch {\n\t\treturn undefined;\n\t}\n\tif (!route) return undefined;\n\tconst seen = new Set<string>();\n\tconst inFlight = new Set<string>();\n\tconst pendingResults = new Map<string, Parameters<typeof writeNestedControlResult>[1]>();\n\tconst timer = setInterval(() => {\n\t\ttry {\n\t\t\tfor (const request of readNestedControlRequests(route)) {\n\t\t\t\tif (seen.has(request.requestId) || inFlight.has(request.requestId)) continue;\n\t\t\t\tinFlight.add(request.requestId);\n\t\t\t\tvoid (async () => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tlet result = pendingResults.get(request.requestId);\n\t\t\t\t\t\tif (!result) {\n\t\t\t\t\t\t\tlet ok = false;\n\t\t\t\t\t\t\tlet message = \"Control request failed.\";\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\tconst control = state.foregroundControls.get(request.targetRunId);\n\t\t\t\t\t\t\t\tif (!control) {\n\t\t\t\t\t\t\t\t\tmessage = `Nested run ${request.targetRunId} is not active in this fanout child.`;\n\t\t\t\t\t\t\t\t} else if (request.action === \"interrupt\") {\n\t\t\t\t\t\t\t\t\tok = control.interrupt?.() === true;\n\t\t\t\t\t\t\t\t\tmessage = ok\n\t\t\t\t\t\t\t\t\t\t? `Interrupt requested for nested run ${request.targetRunId}.`\n\t\t\t\t\t\t\t\t\t\t: `Nested run ${request.targetRunId} has no active child step to interrupt.`;\n\t\t\t\t\t\t\t\t} else if (!request.message?.trim()) {\n\t\t\t\t\t\t\t\t\tmessage = \"Nested resume requires message.\";\n\t\t\t\t\t\t\t\t} else if (!control.currentAgent) {\n\t\t\t\t\t\t\t\t\tmessage = `Nested run ${request.targetRunId} has no active child message route.`;\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tconst index = control.currentIndex ?? 0;\n\t\t\t\t\t\t\t\t\tconst target = resolveSubagentIntercomTarget(request.targetRunId, control.currentAgent, index);\n\t\t\t\t\t\t\t\t\tok = await deliverSubagentIntercomMessageEvent(\n\t\t\t\t\t\t\t\t\t\tpi.events,\n\t\t\t\t\t\t\t\t\t\ttarget,\n\t\t\t\t\t\t\t\t\t\t`Follow-up for nested run ${request.targetRunId} (${control.currentAgent}):\\n\\n${request.message.trim()}`,\n\t\t\t\t\t\t\t\t\t\t500,\n\t\t\t\t\t\t\t\t\t\t{ source: \"nested-resume\", runId: request.targetRunId, agent: control.currentAgent, index },\n\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t\tmessage = ok\n\t\t\t\t\t\t\t\t\t\t? `Delivered follow-up to live nested run ${request.targetRunId}.`\n\t\t\t\t\t\t\t\t\t\t: `Nested child intercom target is not registered: ${target}`;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\t\t\tmessage = error instanceof Error ? error.message : String(error);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tresult = { ts: Date.now(), requestId: request.requestId, targetRunId: request.targetRunId, ok, message };\n\t\t\t\t\t\t}\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\twriteNestedControlResult(route, result);\n\t\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\t\tpendingResults.set(request.requestId, result);\n\t\t\t\t\t\t\tconsole.error(`Failed to write nested control result for request '${request.requestId}' targeting '${request.targetRunId}' via inbox '${route.controlInbox}'; keeping request for retry:`, error);\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tpendingResults.delete(request.requestId);\n\t\t\t\t\t\tseen.add(request.requestId);\n\t\t\t\t\t\ttry { fs.unlinkSync(request.filePath); } catch {}\n\t\t\t\t\t} finally {\n\t\t\t\t\t\tinFlight.delete(request.requestId);\n\t\t\t\t\t}\n\t\t\t\t})();\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tconsole.error(`Failed to poll nested control inbox '${route.controlInbox}' for root '${route.rootRunId}':`, error);\n\t\t}\n\t}, 200);\n\ttimer.unref?.();\n\treturn timer;\n}\n\nexport default function registerFanoutChildSubagentExtension(pi: ExtensionAPI): void {\n\tif (process.env[SUBAGENT_CHILD_ENV] !== \"1\" || process.env[SUBAGENT_FANOUT_CHILD_ENV] !== \"1\") return;\n\n\tconst globalStore = globalThis as Record<string, unknown>;\n\tconst registeredKey = \"__piSubagentFanoutChildRegisteredApis\";\n\tconst registeredApis = globalStore[registeredKey] instanceof WeakSet\n\t\t? globalStore[registeredKey] as WeakSet<ExtensionAPI>\n\t\t: new WeakSet<ExtensionAPI>();\n\tglobalStore[registeredKey] = registeredApis;\n\tif (registeredApis.has(pi)) return;\n\tregisteredApis.add(pi);\n\n\tconst config = loadConfig();\n\tconst state = createChildSafeState();\n\tconst executor = createSubagentExecutor({\n\t\tpi,\n\t\tstate,\n\t\tconfig,\n\t\tasyncByDefault: config.asyncByDefault === true,\n\t\twaitToolEnabled: resolveWaitToolConfig(config.waitTool).enabled,\n\t\ttempArtifactsDir: getArtifactsDir(null),\n\t\tgetSubagentSessionRoot,\n\t\texpandTilde,\n\t\tdiscoverAgents,\n\t\tallowMutatingManagementActions: false,\n\t});\n\n\tconst tool: ToolDefinition<typeof SubagentParams, Details> = {\n\t\tname: \"subagent\",\n\t\tlabel: \"Subagent\",\n\t\tdescription: [\n\t\t\t\"Delegate to subagents from child-safe fanout mode.\",\n\t\t\t\"Allowed management/control actions: list, get, status, interrupt, resume, steer, append-step, doctor.\",\n\t\t\t\"Agent config mutation actions (create, update, delete, eject, disable, enable, reset) are blocked in this mode.\",\n\t\t].join(\"\\n\"),\n\t\tparameters: SubagentParams,\n\t\texecute(id, params, signal, onUpdate, ctx) {\n\t\t\treturn executor.execute(id, params as SubagentParamsLike, signal, onUpdate, ctx);\n\t\t},\n\t};\n\n\tpi.registerTool(tool);\n\tstartNestedControlInboxListener(pi, state);\n}\n"]}
1
+ {"version":3,"file":"fanout-child.d.ts","sourceRoot":"","sources":["../../../../../src/builtin-extensions/pi-subagents/src/extension/fanout-child.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,YAAY,EAAkB,MAAM,iCAAiC,CAAC;AA+HpF,MAAM,CAAC,OAAO,UAAU,oCAAoC,CAAC,EAAE,EAAE,YAAY,GAAG,IAAI,CA2CnF","sourcesContent":["// @ts-nocheck\nimport * as fs from \"node:fs\";\nimport * as os from \"node:os\";\nimport * as path from \"node:path\";\nimport type { ExtensionAPI, ToolDefinition } from \"@ashx-j/lunr\";\nimport { discoverAgents } from \"../agents/agents.ts\";\nimport { getArtifactsDir } from \"../shared/artifacts.ts\";\nimport { createSubagentExecutor, type SubagentParamsLike } from \"../runs/foreground/subagent-executor.ts\";\nimport { resolveWaitToolConfig } from \"../runs/background/wait-config.ts\";\nimport { SUBAGENT_CHILD_ENV, SUBAGENT_FANOUT_CHILD_ENV } from \"../runs/shared/pi-args.ts\";\nimport { readNestedControlRequests, resolveNestedRouteFromEnv, writeNestedControlResult } from \"../runs/shared/nested-events.ts\";\nimport { deliverSubagentIntercomMessageEvent } from \"../intercom/result-intercom.ts\";\nimport { resolveSubagentIntercomTarget } from \"../intercom/intercom-bridge.ts\";\nimport { SubagentParams } from \"./schemas.ts\";\nimport { loadConfig } from \"./config.ts\";\nimport { type Details, type SubagentState } from \"../shared/types.ts\";\n\nfunction getSubagentSessionRoot(parentSessionFile: string | null): string {\n\tif (parentSessionFile) {\n\t\tconst baseName = path.basename(parentSessionFile, \".jsonl\");\n\t\tconst sessionsDir = path.dirname(parentSessionFile);\n\t\treturn path.join(sessionsDir, baseName);\n\t}\n\treturn fs.mkdtempSync(path.join(os.tmpdir(), \"pi-subagent-session-\"));\n}\n\nfunction expandTilde(p: string): string {\n\treturn p.startsWith(\"~/\") ? path.join(os.homedir(), p.slice(2)) : p;\n}\n\nfunction createChildSafeState(): SubagentState {\n\treturn {\n\t\tbaseCwd: \"\",\n\t\tcurrentSessionId: null,\n\t\tforegroundSubagentInFlight: 0,\n\t\tsubagentSpawns: { sessionId: null, count: 0 },\n\t\tasyncJobs: new Map(),\n\t\tforegroundRuns: new Map(),\n\t\tforegroundControls: new Map(),\n\t\tlastForegroundControlId: null,\n\t\tpendingForegroundControlNotices: new Map(),\n\t\tcleanupTimers: new Map(),\n\t\tlastUiContext: null,\n\t\tpoller: null,\n\t\tcompletionSeen: new Map(),\n\t\twatcher: null,\n\t\twatcherRestartTimer: null,\n\t\tresultFileCoalescer: {\n\t\t\tschedule: () => false,\n\t\t\tclear: () => {},\n\t\t},\n\t};\n}\n\nfunction startNestedControlInboxListener(pi: ExtensionAPI, state: SubagentState): NodeJS.Timeout | undefined {\n\tlet route;\n\ttry {\n\t\troute = resolveNestedRouteFromEnv();\n\t} catch {\n\t\treturn undefined;\n\t}\n\tif (!route) return undefined;\n\tconst seen = new Set<string>();\n\tconst inFlight = new Set<string>();\n\tconst pendingResults = new Map<string, Parameters<typeof writeNestedControlResult>[1]>();\n\tconst timer = setInterval(() => {\n\t\ttry {\n\t\t\tfor (const request of readNestedControlRequests(route)) {\n\t\t\t\tif (seen.has(request.requestId) || inFlight.has(request.requestId)) continue;\n\t\t\t\tinFlight.add(request.requestId);\n\t\t\t\tvoid (async () => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tlet result = pendingResults.get(request.requestId);\n\t\t\t\t\t\tif (!result) {\n\t\t\t\t\t\t\tlet ok = false;\n\t\t\t\t\t\t\tlet message = \"Control request failed.\";\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\tconst control = state.foregroundControls.get(request.targetRunId);\n\t\t\t\t\t\t\t\tif (!control) {\n\t\t\t\t\t\t\t\t\tmessage = `Nested run ${request.targetRunId} is not active in this fanout child.`;\n\t\t\t\t\t\t\t\t} else if (request.action === \"interrupt\") {\n\t\t\t\t\t\t\t\t\tok = control.interrupt?.() === true;\n\t\t\t\t\t\t\t\t\tmessage = ok\n\t\t\t\t\t\t\t\t\t\t? `Interrupt requested for nested run ${request.targetRunId}.`\n\t\t\t\t\t\t\t\t\t\t: `Nested run ${request.targetRunId} has no active child step to interrupt.`;\n\t\t\t\t\t\t\t\t} else if (!request.message?.trim()) {\n\t\t\t\t\t\t\t\t\tmessage = \"Nested resume requires message.\";\n\t\t\t\t\t\t\t\t} else if (!control.currentAgent) {\n\t\t\t\t\t\t\t\t\tmessage = `Nested run ${request.targetRunId} has no active child message route.`;\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tconst index = control.currentIndex ?? 0;\n\t\t\t\t\t\t\t\t\tconst target = resolveSubagentIntercomTarget(request.targetRunId, control.currentAgent, index);\n\t\t\t\t\t\t\t\t\tok = await deliverSubagentIntercomMessageEvent(\n\t\t\t\t\t\t\t\t\t\tpi.events,\n\t\t\t\t\t\t\t\t\t\ttarget,\n\t\t\t\t\t\t\t\t\t\t`Follow-up for nested run ${request.targetRunId} (${control.currentAgent}):\\n\\n${request.message.trim()}`,\n\t\t\t\t\t\t\t\t\t\t500,\n\t\t\t\t\t\t\t\t\t\t{ source: \"nested-resume\", runId: request.targetRunId, agent: control.currentAgent, index },\n\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t\tmessage = ok\n\t\t\t\t\t\t\t\t\t\t? `Delivered follow-up to live nested run ${request.targetRunId}.`\n\t\t\t\t\t\t\t\t\t\t: `Nested child intercom target is not registered: ${target}`;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\t\t\tmessage = error instanceof Error ? error.message : String(error);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tresult = { ts: Date.now(), requestId: request.requestId, targetRunId: request.targetRunId, ok, message };\n\t\t\t\t\t\t}\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\twriteNestedControlResult(route, result);\n\t\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\t\tpendingResults.set(request.requestId, result);\n\t\t\t\t\t\t\tconsole.error(`Failed to write nested control result for request '${request.requestId}' targeting '${request.targetRunId}' via inbox '${route.controlInbox}'; keeping request for retry:`, error);\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tpendingResults.delete(request.requestId);\n\t\t\t\t\t\tseen.add(request.requestId);\n\t\t\t\t\t\ttry { fs.unlinkSync(request.filePath); } catch {}\n\t\t\t\t\t} finally {\n\t\t\t\t\t\tinFlight.delete(request.requestId);\n\t\t\t\t\t}\n\t\t\t\t})();\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tconsole.error(`Failed to poll nested control inbox '${route.controlInbox}' for root '${route.rootRunId}':`, error);\n\t\t}\n\t}, 200);\n\ttimer.unref?.();\n\treturn timer;\n}\n\nexport default function registerFanoutChildSubagentExtension(pi: ExtensionAPI): void {\n\tif (process.env[SUBAGENT_CHILD_ENV] !== \"1\" || process.env[SUBAGENT_FANOUT_CHILD_ENV] !== \"1\") return;\n\n\tconst globalStore = globalThis as Record<string, unknown>;\n\tconst registeredKey = \"__piSubagentFanoutChildRegisteredApis\";\n\tconst registeredApis = globalStore[registeredKey] instanceof WeakSet\n\t\t? globalStore[registeredKey] as WeakSet<ExtensionAPI>\n\t\t: new WeakSet<ExtensionAPI>();\n\tglobalStore[registeredKey] = registeredApis;\n\tif (registeredApis.has(pi)) return;\n\tregisteredApis.add(pi);\n\n\tconst config = loadConfig();\n\tconst state = createChildSafeState();\n\tconst executor = createSubagentExecutor({\n\t\tpi,\n\t\tstate,\n\t\tconfig,\n\t\tasyncByDefault: config.asyncByDefault === true,\n\t\twaitToolEnabled: resolveWaitToolConfig(config.waitTool).enabled,\n\t\ttempArtifactsDir: getArtifactsDir(null),\n\t\tgetSubagentSessionRoot,\n\t\texpandTilde,\n\t\tdiscoverAgents,\n\t\tallowMutatingManagementActions: false,\n\t});\n\n\tconst tool: ToolDefinition<typeof SubagentParams, Details> = {\n\t\tname: \"subagent\",\n\t\tlabel: \"Subagent\",\n\t\tdescription: [\n\t\t\t\"Delegate to subagents from child-safe fanout mode.\",\n\t\t\t\"Allowed management/control actions: list, get, status, interrupt, resume, steer, append-step, doctor.\",\n\t\t\t\"Agent config mutation actions (create, update, delete, eject, disable, enable, reset) are blocked in this mode.\",\n\t\t].join(\"\\n\"),\n\t\tparameters: SubagentParams,\n\t\texecute(id, params, signal, onUpdate, ctx) {\n\t\t\treturn executor.execute(id, params as SubagentParamsLike, signal, onUpdate, ctx);\n\t\t},\n\t};\n\n\tpi.registerTool(tool);\n\tstartNestedControlInboxListener(pi, state);\n}\n"]}
@@ -27,7 +27,7 @@ function createChildSafeState() {
27
27
  return {
28
28
  baseCwd: "",
29
29
  currentSessionId: null,
30
- subagentInProgress: false,
30
+ foregroundSubagentInFlight: 0,
31
31
  subagentSpawns: { sessionId: null, count: 0 },
32
32
  asyncJobs: new Map(),
33
33
  foregroundRuns: new Map(),
@@ -1 +1 @@
1
- {"version":3,"file":"fanout-child.js","sourceRoot":"","sources":["../../../../../src/builtin-extensions/pi-subagents/src/extension/fanout-child.ts"],"names":[],"mappings":"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","sourcesContent":["// @ts-nocheck\nimport * as fs from \"node:fs\";\nimport * as os from \"node:os\";\nimport * as path from \"node:path\";\nimport type { ExtensionAPI, ToolDefinition } from \"@ashx-j/lunr\";\nimport { discoverAgents } from \"../agents/agents.ts\";\nimport { getArtifactsDir } from \"../shared/artifacts.ts\";\nimport { createSubagentExecutor, type SubagentParamsLike } from \"../runs/foreground/subagent-executor.ts\";\nimport { resolveWaitToolConfig } from \"../runs/background/wait-config.ts\";\nimport { SUBAGENT_CHILD_ENV, SUBAGENT_FANOUT_CHILD_ENV } from \"../runs/shared/pi-args.ts\";\nimport { readNestedControlRequests, resolveNestedRouteFromEnv, writeNestedControlResult } from \"../runs/shared/nested-events.ts\";\nimport { deliverSubagentIntercomMessageEvent } from \"../intercom/result-intercom.ts\";\nimport { resolveSubagentIntercomTarget } from \"../intercom/intercom-bridge.ts\";\nimport { SubagentParams } from \"./schemas.ts\";\nimport { loadConfig } from \"./config.ts\";\nimport { type Details, type SubagentState } from \"../shared/types.ts\";\n\nfunction getSubagentSessionRoot(parentSessionFile: string | null): string {\n\tif (parentSessionFile) {\n\t\tconst baseName = path.basename(parentSessionFile, \".jsonl\");\n\t\tconst sessionsDir = path.dirname(parentSessionFile);\n\t\treturn path.join(sessionsDir, baseName);\n\t}\n\treturn fs.mkdtempSync(path.join(os.tmpdir(), \"pi-subagent-session-\"));\n}\n\nfunction expandTilde(p: string): string {\n\treturn p.startsWith(\"~/\") ? path.join(os.homedir(), p.slice(2)) : p;\n}\n\nfunction createChildSafeState(): SubagentState {\n\treturn {\n\t\tbaseCwd: \"\",\n\t\tcurrentSessionId: null,\n\t\tsubagentInProgress: false,\n\t\tsubagentSpawns: { sessionId: null, count: 0 },\n\t\tasyncJobs: new Map(),\n\t\tforegroundRuns: new Map(),\n\t\tforegroundControls: new Map(),\n\t\tlastForegroundControlId: null,\n\t\tpendingForegroundControlNotices: new Map(),\n\t\tcleanupTimers: new Map(),\n\t\tlastUiContext: null,\n\t\tpoller: null,\n\t\tcompletionSeen: new Map(),\n\t\twatcher: null,\n\t\twatcherRestartTimer: null,\n\t\tresultFileCoalescer: {\n\t\t\tschedule: () => false,\n\t\t\tclear: () => {},\n\t\t},\n\t};\n}\n\nfunction startNestedControlInboxListener(pi: ExtensionAPI, state: SubagentState): NodeJS.Timeout | undefined {\n\tlet route;\n\ttry {\n\t\troute = resolveNestedRouteFromEnv();\n\t} catch {\n\t\treturn undefined;\n\t}\n\tif (!route) return undefined;\n\tconst seen = new Set<string>();\n\tconst inFlight = new Set<string>();\n\tconst pendingResults = new Map<string, Parameters<typeof writeNestedControlResult>[1]>();\n\tconst timer = setInterval(() => {\n\t\ttry {\n\t\t\tfor (const request of readNestedControlRequests(route)) {\n\t\t\t\tif (seen.has(request.requestId) || inFlight.has(request.requestId)) continue;\n\t\t\t\tinFlight.add(request.requestId);\n\t\t\t\tvoid (async () => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tlet result = pendingResults.get(request.requestId);\n\t\t\t\t\t\tif (!result) {\n\t\t\t\t\t\t\tlet ok = false;\n\t\t\t\t\t\t\tlet message = \"Control request failed.\";\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\tconst control = state.foregroundControls.get(request.targetRunId);\n\t\t\t\t\t\t\t\tif (!control) {\n\t\t\t\t\t\t\t\t\tmessage = `Nested run ${request.targetRunId} is not active in this fanout child.`;\n\t\t\t\t\t\t\t\t} else if (request.action === \"interrupt\") {\n\t\t\t\t\t\t\t\t\tok = control.interrupt?.() === true;\n\t\t\t\t\t\t\t\t\tmessage = ok\n\t\t\t\t\t\t\t\t\t\t? `Interrupt requested for nested run ${request.targetRunId}.`\n\t\t\t\t\t\t\t\t\t\t: `Nested run ${request.targetRunId} has no active child step to interrupt.`;\n\t\t\t\t\t\t\t\t} else if (!request.message?.trim()) {\n\t\t\t\t\t\t\t\t\tmessage = \"Nested resume requires message.\";\n\t\t\t\t\t\t\t\t} else if (!control.currentAgent) {\n\t\t\t\t\t\t\t\t\tmessage = `Nested run ${request.targetRunId} has no active child message route.`;\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tconst index = control.currentIndex ?? 0;\n\t\t\t\t\t\t\t\t\tconst target = resolveSubagentIntercomTarget(request.targetRunId, control.currentAgent, index);\n\t\t\t\t\t\t\t\t\tok = await deliverSubagentIntercomMessageEvent(\n\t\t\t\t\t\t\t\t\t\tpi.events,\n\t\t\t\t\t\t\t\t\t\ttarget,\n\t\t\t\t\t\t\t\t\t\t`Follow-up for nested run ${request.targetRunId} (${control.currentAgent}):\\n\\n${request.message.trim()}`,\n\t\t\t\t\t\t\t\t\t\t500,\n\t\t\t\t\t\t\t\t\t\t{ source: \"nested-resume\", runId: request.targetRunId, agent: control.currentAgent, index },\n\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t\tmessage = ok\n\t\t\t\t\t\t\t\t\t\t? `Delivered follow-up to live nested run ${request.targetRunId}.`\n\t\t\t\t\t\t\t\t\t\t: `Nested child intercom target is not registered: ${target}`;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\t\t\tmessage = error instanceof Error ? error.message : String(error);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tresult = { ts: Date.now(), requestId: request.requestId, targetRunId: request.targetRunId, ok, message };\n\t\t\t\t\t\t}\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\twriteNestedControlResult(route, result);\n\t\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\t\tpendingResults.set(request.requestId, result);\n\t\t\t\t\t\t\tconsole.error(`Failed to write nested control result for request '${request.requestId}' targeting '${request.targetRunId}' via inbox '${route.controlInbox}'; keeping request for retry:`, error);\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tpendingResults.delete(request.requestId);\n\t\t\t\t\t\tseen.add(request.requestId);\n\t\t\t\t\t\ttry { fs.unlinkSync(request.filePath); } catch {}\n\t\t\t\t\t} finally {\n\t\t\t\t\t\tinFlight.delete(request.requestId);\n\t\t\t\t\t}\n\t\t\t\t})();\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tconsole.error(`Failed to poll nested control inbox '${route.controlInbox}' for root '${route.rootRunId}':`, error);\n\t\t}\n\t}, 200);\n\ttimer.unref?.();\n\treturn timer;\n}\n\nexport default function registerFanoutChildSubagentExtension(pi: ExtensionAPI): void {\n\tif (process.env[SUBAGENT_CHILD_ENV] !== \"1\" || process.env[SUBAGENT_FANOUT_CHILD_ENV] !== \"1\") return;\n\n\tconst globalStore = globalThis as Record<string, unknown>;\n\tconst registeredKey = \"__piSubagentFanoutChildRegisteredApis\";\n\tconst registeredApis = globalStore[registeredKey] instanceof WeakSet\n\t\t? globalStore[registeredKey] as WeakSet<ExtensionAPI>\n\t\t: new WeakSet<ExtensionAPI>();\n\tglobalStore[registeredKey] = registeredApis;\n\tif (registeredApis.has(pi)) return;\n\tregisteredApis.add(pi);\n\n\tconst config = loadConfig();\n\tconst state = createChildSafeState();\n\tconst executor = createSubagentExecutor({\n\t\tpi,\n\t\tstate,\n\t\tconfig,\n\t\tasyncByDefault: config.asyncByDefault === true,\n\t\twaitToolEnabled: resolveWaitToolConfig(config.waitTool).enabled,\n\t\ttempArtifactsDir: getArtifactsDir(null),\n\t\tgetSubagentSessionRoot,\n\t\texpandTilde,\n\t\tdiscoverAgents,\n\t\tallowMutatingManagementActions: false,\n\t});\n\n\tconst tool: ToolDefinition<typeof SubagentParams, Details> = {\n\t\tname: \"subagent\",\n\t\tlabel: \"Subagent\",\n\t\tdescription: [\n\t\t\t\"Delegate to subagents from child-safe fanout mode.\",\n\t\t\t\"Allowed management/control actions: list, get, status, interrupt, resume, steer, append-step, doctor.\",\n\t\t\t\"Agent config mutation actions (create, update, delete, eject, disable, enable, reset) are blocked in this mode.\",\n\t\t].join(\"\\n\"),\n\t\tparameters: SubagentParams,\n\t\texecute(id, params, signal, onUpdate, ctx) {\n\t\t\treturn executor.execute(id, params as SubagentParamsLike, signal, onUpdate, ctx);\n\t\t},\n\t};\n\n\tpi.registerTool(tool);\n\tstartNestedControlInboxListener(pi, state);\n}\n"]}
1
+ {"version":3,"file":"fanout-child.js","sourceRoot":"","sources":["../../../../../src/builtin-extensions/pi-subagents/src/extension/fanout-child.ts"],"names":[],"mappings":"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","sourcesContent":["// @ts-nocheck\nimport * as fs from \"node:fs\";\nimport * as os from \"node:os\";\nimport * as path from \"node:path\";\nimport type { ExtensionAPI, ToolDefinition } from \"@ashx-j/lunr\";\nimport { discoverAgents } from \"../agents/agents.ts\";\nimport { getArtifactsDir } from \"../shared/artifacts.ts\";\nimport { createSubagentExecutor, type SubagentParamsLike } from \"../runs/foreground/subagent-executor.ts\";\nimport { resolveWaitToolConfig } from \"../runs/background/wait-config.ts\";\nimport { SUBAGENT_CHILD_ENV, SUBAGENT_FANOUT_CHILD_ENV } from \"../runs/shared/pi-args.ts\";\nimport { readNestedControlRequests, resolveNestedRouteFromEnv, writeNestedControlResult } from \"../runs/shared/nested-events.ts\";\nimport { deliverSubagentIntercomMessageEvent } from \"../intercom/result-intercom.ts\";\nimport { resolveSubagentIntercomTarget } from \"../intercom/intercom-bridge.ts\";\nimport { SubagentParams } from \"./schemas.ts\";\nimport { loadConfig } from \"./config.ts\";\nimport { type Details, type SubagentState } from \"../shared/types.ts\";\n\nfunction getSubagentSessionRoot(parentSessionFile: string | null): string {\n\tif (parentSessionFile) {\n\t\tconst baseName = path.basename(parentSessionFile, \".jsonl\");\n\t\tconst sessionsDir = path.dirname(parentSessionFile);\n\t\treturn path.join(sessionsDir, baseName);\n\t}\n\treturn fs.mkdtempSync(path.join(os.tmpdir(), \"pi-subagent-session-\"));\n}\n\nfunction expandTilde(p: string): string {\n\treturn p.startsWith(\"~/\") ? path.join(os.homedir(), p.slice(2)) : p;\n}\n\nfunction createChildSafeState(): SubagentState {\n\treturn {\n\t\tbaseCwd: \"\",\n\t\tcurrentSessionId: null,\n\t\tforegroundSubagentInFlight: 0,\n\t\tsubagentSpawns: { sessionId: null, count: 0 },\n\t\tasyncJobs: new Map(),\n\t\tforegroundRuns: new Map(),\n\t\tforegroundControls: new Map(),\n\t\tlastForegroundControlId: null,\n\t\tpendingForegroundControlNotices: new Map(),\n\t\tcleanupTimers: new Map(),\n\t\tlastUiContext: null,\n\t\tpoller: null,\n\t\tcompletionSeen: new Map(),\n\t\twatcher: null,\n\t\twatcherRestartTimer: null,\n\t\tresultFileCoalescer: {\n\t\t\tschedule: () => false,\n\t\t\tclear: () => {},\n\t\t},\n\t};\n}\n\nfunction startNestedControlInboxListener(pi: ExtensionAPI, state: SubagentState): NodeJS.Timeout | undefined {\n\tlet route;\n\ttry {\n\t\troute = resolveNestedRouteFromEnv();\n\t} catch {\n\t\treturn undefined;\n\t}\n\tif (!route) return undefined;\n\tconst seen = new Set<string>();\n\tconst inFlight = new Set<string>();\n\tconst pendingResults = new Map<string, Parameters<typeof writeNestedControlResult>[1]>();\n\tconst timer = setInterval(() => {\n\t\ttry {\n\t\t\tfor (const request of readNestedControlRequests(route)) {\n\t\t\t\tif (seen.has(request.requestId) || inFlight.has(request.requestId)) continue;\n\t\t\t\tinFlight.add(request.requestId);\n\t\t\t\tvoid (async () => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tlet result = pendingResults.get(request.requestId);\n\t\t\t\t\t\tif (!result) {\n\t\t\t\t\t\t\tlet ok = false;\n\t\t\t\t\t\t\tlet message = \"Control request failed.\";\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\tconst control = state.foregroundControls.get(request.targetRunId);\n\t\t\t\t\t\t\t\tif (!control) {\n\t\t\t\t\t\t\t\t\tmessage = `Nested run ${request.targetRunId} is not active in this fanout child.`;\n\t\t\t\t\t\t\t\t} else if (request.action === \"interrupt\") {\n\t\t\t\t\t\t\t\t\tok = control.interrupt?.() === true;\n\t\t\t\t\t\t\t\t\tmessage = ok\n\t\t\t\t\t\t\t\t\t\t? `Interrupt requested for nested run ${request.targetRunId}.`\n\t\t\t\t\t\t\t\t\t\t: `Nested run ${request.targetRunId} has no active child step to interrupt.`;\n\t\t\t\t\t\t\t\t} else if (!request.message?.trim()) {\n\t\t\t\t\t\t\t\t\tmessage = \"Nested resume requires message.\";\n\t\t\t\t\t\t\t\t} else if (!control.currentAgent) {\n\t\t\t\t\t\t\t\t\tmessage = `Nested run ${request.targetRunId} has no active child message route.`;\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tconst index = control.currentIndex ?? 0;\n\t\t\t\t\t\t\t\t\tconst target = resolveSubagentIntercomTarget(request.targetRunId, control.currentAgent, index);\n\t\t\t\t\t\t\t\t\tok = await deliverSubagentIntercomMessageEvent(\n\t\t\t\t\t\t\t\t\t\tpi.events,\n\t\t\t\t\t\t\t\t\t\ttarget,\n\t\t\t\t\t\t\t\t\t\t`Follow-up for nested run ${request.targetRunId} (${control.currentAgent}):\\n\\n${request.message.trim()}`,\n\t\t\t\t\t\t\t\t\t\t500,\n\t\t\t\t\t\t\t\t\t\t{ source: \"nested-resume\", runId: request.targetRunId, agent: control.currentAgent, index },\n\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t\tmessage = ok\n\t\t\t\t\t\t\t\t\t\t? `Delivered follow-up to live nested run ${request.targetRunId}.`\n\t\t\t\t\t\t\t\t\t\t: `Nested child intercom target is not registered: ${target}`;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\t\t\tmessage = error instanceof Error ? error.message : String(error);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tresult = { ts: Date.now(), requestId: request.requestId, targetRunId: request.targetRunId, ok, message };\n\t\t\t\t\t\t}\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\twriteNestedControlResult(route, result);\n\t\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\t\tpendingResults.set(request.requestId, result);\n\t\t\t\t\t\t\tconsole.error(`Failed to write nested control result for request '${request.requestId}' targeting '${request.targetRunId}' via inbox '${route.controlInbox}'; keeping request for retry:`, error);\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tpendingResults.delete(request.requestId);\n\t\t\t\t\t\tseen.add(request.requestId);\n\t\t\t\t\t\ttry { fs.unlinkSync(request.filePath); } catch {}\n\t\t\t\t\t} finally {\n\t\t\t\t\t\tinFlight.delete(request.requestId);\n\t\t\t\t\t}\n\t\t\t\t})();\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tconsole.error(`Failed to poll nested control inbox '${route.controlInbox}' for root '${route.rootRunId}':`, error);\n\t\t}\n\t}, 200);\n\ttimer.unref?.();\n\treturn timer;\n}\n\nexport default function registerFanoutChildSubagentExtension(pi: ExtensionAPI): void {\n\tif (process.env[SUBAGENT_CHILD_ENV] !== \"1\" || process.env[SUBAGENT_FANOUT_CHILD_ENV] !== \"1\") return;\n\n\tconst globalStore = globalThis as Record<string, unknown>;\n\tconst registeredKey = \"__piSubagentFanoutChildRegisteredApis\";\n\tconst registeredApis = globalStore[registeredKey] instanceof WeakSet\n\t\t? globalStore[registeredKey] as WeakSet<ExtensionAPI>\n\t\t: new WeakSet<ExtensionAPI>();\n\tglobalStore[registeredKey] = registeredApis;\n\tif (registeredApis.has(pi)) return;\n\tregisteredApis.add(pi);\n\n\tconst config = loadConfig();\n\tconst state = createChildSafeState();\n\tconst executor = createSubagentExecutor({\n\t\tpi,\n\t\tstate,\n\t\tconfig,\n\t\tasyncByDefault: config.asyncByDefault === true,\n\t\twaitToolEnabled: resolveWaitToolConfig(config.waitTool).enabled,\n\t\ttempArtifactsDir: getArtifactsDir(null),\n\t\tgetSubagentSessionRoot,\n\t\texpandTilde,\n\t\tdiscoverAgents,\n\t\tallowMutatingManagementActions: false,\n\t});\n\n\tconst tool: ToolDefinition<typeof SubagentParams, Details> = {\n\t\tname: \"subagent\",\n\t\tlabel: \"Subagent\",\n\t\tdescription: [\n\t\t\t\"Delegate to subagents from child-safe fanout mode.\",\n\t\t\t\"Allowed management/control actions: list, get, status, interrupt, resume, steer, append-step, doctor.\",\n\t\t\t\"Agent config mutation actions (create, update, delete, eject, disable, enable, reset) are blocked in this mode.\",\n\t\t].join(\"\\n\"),\n\t\tparameters: SubagentParams,\n\t\texecute(id, params, signal, onUpdate, ctx) {\n\t\t\treturn executor.execute(id, params as SubagentParamsLike, signal, onUpdate, ctx);\n\t\t},\n\t};\n\n\tpi.registerTool(tool);\n\tstartNestedControlInboxListener(pi, state);\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/builtin-extensions/pi-subagents/src/extension/index.ts"],"names":[],"mappings":"AACA;;;;;;;;;;;;GAYG;AAOH,OAAO,EAAW,KAAK,YAAY,EAA8C,MAAM,iCAAiC,CAAC;AAoDzH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AA2JzC,MAAM,CAAC,OAAO,UAAU,yBAAyB,CAAC,EAAE,EAAE,YAAY,GAAG,IAAI,CAqaxE","sourcesContent":["// @ts-nocheck\n/**\n * Subagent Tool\n *\n * Full-featured subagent with sync and async modes.\n * - Sync (default): Streams output, renders markdown, tracks usage\n * - Async: Background execution, emits events when done\n *\n * Modes: single (agent + task), parallel (tasks[]), chain (chain[] with {previous})\n * Toggle: async parameter (default: false, configurable via config.json)\n *\n * Config file: ~/.pi/agent/extensions/subagent/config.json\n * { \"asyncByDefault\": true, \"forceTopLevelAsync\": true, \"maxSubagentDepth\": 1, \"intercomBridge\": { \"mode\": \"always\", \"instructionFile\": \"./intercom-bridge.md\" }, \"worktreeSetupHook\": \"./scripts/setup-worktree.mjs\" }\n */\n\nimport { randomUUID } from \"node:crypto\";\nimport * as fs from \"node:fs\";\nimport * as os from \"node:os\";\nimport * as path from \"node:path\";\nimport type { AgentToolResult } from \"@ashx-j/lunr-agent\";\nimport { keyText, type ExtensionAPI, type ExtensionContext, type ToolDefinition } from \"@ashx-j/lunr\";\nimport { Box, Container, Spacer, Text, truncateToWidth, visibleWidth, wrapTextWithAnsi, type Component } from \"@ashx-j/lunr-tui\";\nimport { discoverAgents } from \"../agents/agents.ts\";\nimport { cleanupAllArtifactDirs, cleanupOldArtifacts, getArtifactsDir } from \"../shared/artifacts.ts\";\nimport { resolveCurrentSessionId } from \"../shared/session-identity.ts\";\nimport { cleanupOldChainDirs } from \"../shared/settings.ts\";\nimport { clearLegacyResultAnimationTimer, renderSubagentResult, subagentAnimSink } from \"../tui/render.ts\";\nimport { SubagentParams } from \"./schemas.ts\";\nimport { validateChainInput } from \"./chain-validation.ts\";\nimport { createSubagentExecutor, type SubagentParamsLike } from \"../runs/foreground/subagent-executor.ts\";\nimport { createAsyncJobTracker } from \"../runs/background/async-job-tracker.ts\";\nimport { createResultWatcher } from \"../runs/background/result-watcher.ts\";\nimport { createScheduledRunManager } from \"../runs/background/scheduled-runs.ts\";\nimport { registerSlashCommands } from \"../slash/slash-commands.ts\";\nimport { registerPromptTemplateDelegationBridge } from \"../slash/prompt-template-bridge.ts\";\nimport { registerMainWatchdog } from \"../watchdog/register-main.ts\";\nimport { registerSlashSubagentBridge } from \"../slash/slash-bridge.ts\";\nimport { createNativeSupervisorChannel } from \"../intercom/native-supervisor-channel.ts\";\nimport { registerSubagentRpcBridge } from \"./rpc.ts\";\nimport { clearSlashSnapshots, getSlashRenderableSnapshot, resolveSlashMessageDetails, restoreSlashFinalSnapshots, type SlashMessageDetails } from \"../slash/slash-live-state.ts\";\nimport { inspectSubagentStatus } from \"../runs/background/run-status.ts\";\nimport { resolveWaitToolConfig } from \"../runs/background/subagent-wait.ts\";\nimport { registerWaitTool } from \"../runs/background/wait-tool.ts\";\nimport { drainOutstandingWork } from \"../runs/background/auto-drain.ts\";\nimport registerSubagentNotify, { parseSubagentNotifyContent, type SubagentNotifyDetails } from \"../runs/background/notify.ts\";\nimport { formatSteeringNotice, handleSubagentSteeringNotice, SUBAGENT_STEERING_MESSAGE_TYPE, type SubagentSteeringMessageDetails } from \"./steering-notices.ts\";\nimport { SUBAGENT_CHILD_ENV, SUBAGENT_PARENT_SESSION_ENV } from \"../runs/shared/pi-args.ts\";\nimport { formatDuration, shortenPath } from \"../shared/formatters.ts\";\nimport { loadConfig } from \"./config.ts\";\nimport { buildSubagentToolDescription } from \"./tool-description.ts\";\nimport {\n\ttype Details,\n\ttype SubagentState,\n\tASYNC_DIR,\n\tDEFAULT_ARTIFACT_CONFIG,\n\tRESULTS_DIR,\n\tSLASH_RESULT_TYPE,\n\tSLASH_TEXT_RESULT_TYPE,\n\tSUBAGENT_ASYNC_COMPLETE_EVENT,\n\tSUBAGENT_ASYNC_STARTED_EVENT,\n\tSUBAGENT_CONTROL_EVENT,\n\tSUBAGENT_STEERING_NOTICE_EVENT,\n\tWIDGET_KEY,\n} from \"../shared/types.ts\";\nimport {\n\tclearPendingForegroundControlNotices,\n\tformatSubagentControlNotice,\n\thandleSubagentControlNotice,\n\tSUBAGENT_CONTROL_MESSAGE_TYPE,\n\ttype SubagentControlMessageDetails,\n} from \"./control-notices.ts\";\n\nexport { loadConfig } from \"./config.ts\";\n\n/**\n * Derive subagent session base directory from parent session file.\n * If parent session is ~/.pi/agent/sessions/abc123.jsonl,\n * returns ~/.pi/agent/sessions/abc123/ as the base.\n * Callers add runId to create the actual session root: abc123/{runId}/\n * Falls back to a unique temp directory if no parent session.\n */\nfunction getSubagentSessionRoot(parentSessionFile: string | null): string {\n\tif (parentSessionFile) {\n\t\tconst baseName = path.basename(parentSessionFile, \".jsonl\");\n\t\tconst sessionsDir = path.dirname(parentSessionFile);\n\t\treturn path.join(sessionsDir, baseName);\n\t}\n\treturn fs.mkdtempSync(path.join(os.tmpdir(), \"pi-subagent-session-\"));\n}\n\nfunction expandTilde(p: string): string {\n\treturn p.startsWith(\"~/\") ? path.join(os.homedir(), p.slice(2)) : p;\n}\n\n/**\n * Create a directory and verify it is actually accessible.\n * On Windows with Azure AD/Entra ID, directories created shortly after\n * wake-from-sleep can end up with broken NTFS ACLs (null DACL) when the\n * cloud SID cannot be resolved without network connectivity. This leaves\n * the directory completely inaccessible to the creating user.\n */\nfunction ensureAccessibleDir(dirPath: string): void {\n\tfs.mkdirSync(dirPath, { recursive: true });\n\ttry {\n\t\tfs.accessSync(dirPath, fs.constants.R_OK | fs.constants.W_OK);\n\t} catch {\n\t\ttry {\n\t\t\tfs.rmSync(dirPath, { recursive: true, force: true });\n\t\t} catch {\n\t\t\t// Best effort: retry mkdir/access even if cleanup fails.\n\t\t}\n\t\tfs.mkdirSync(dirPath, { recursive: true });\n\t\tfs.accessSync(dirPath, fs.constants.R_OK | fs.constants.W_OK);\n\t}\n}\n\nfunction isSlashResultRunning(result: { details?: Details }): boolean {\n\treturn result.details?.progress?.some((entry) => entry.status === \"running\")\n\t\t|| result.details?.results.some((entry) => entry.progress?.status === \"running\")\n\t\t|| false;\n}\n\n// Drives the inline running-indicator braille animation for foreground subagent\n// results. Foreground runs receive progress only on child events, so the glyph\n// (derived from progress fields) would freeze between events. While a result is\n// running we tick a frame counter every 80ms; renderResult registers the\n// running-glyph lines in state.animEntries (via subagentAnimSink) and the timer\n// rewrites just those Text nodes + requests a repaint, instead of invalidate()-ing\n// the whole tool component (a full renderSubagentResult rebuild 12.5x/s made the\n// spinner laggy).\nfunction subagentResultIsRunning(result: { details?: Details }): boolean {\n\treturn result.details?.progress?.some((entry) => entry.status === \"running\")\n\t\t|| result.details?.results.some((entry) => entry.progress?.status === \"running\")\n\t\t|| false;\n}\n\nfunction ensureSubagentResultAnimation(context: { state: Record<string, unknown>; invalidate?: () => void; requestRender?: () => void }): void {\n\tconst state = context.state as {\n\t\tsubagentResultAnimationTimer?: ReturnType<typeof setInterval>;\n\t\tframe?: number;\n\t\tanimEntries?: Array<{ text: Text; line: (frame: number) => string }>;\n\t};\n\tif (state.subagentResultAnimationTimer) return;\n\tif (typeof context.invalidate !== \"function\") return;\n\tif (state.frame === undefined) state.frame = 0;\n\tstate.subagentResultAnimationTimer = setInterval(() => {\n\t\tstate.frame = ((state.frame ?? 0) + 1) % 10;\n\t\ttry {\n\t\t\tconst entries = state.animEntries;\n\t\t\tif (entries?.length && typeof context.requestRender === \"function\") {\n\t\t\t\tfor (const entry of entries) entry.text.setText(entry.line(state.frame ?? 0));\n\t\t\t\tcontext.requestRender();\n\t\t\t} else {\n\t\t\t\tcontext.invalidate();\n\t\t\t}\n\t\t} catch {}\n\t}, 80);\n}\n\nfunction isSlashResultError(result: { details?: Details }): boolean {\n\treturn result.details?.results.some((entry) => entry.exitCode !== 0 && entry.progress?.status !== \"running\") || false;\n}\n\nfunction isStaleExtensionContextError(error: unknown): boolean {\n\treturn error instanceof Error && error.message.includes(\"Extension context no longer active\");\n}\n\nfunction rebuildSlashResultContainer(\n\tcontainer: Container,\n\tresult: AgentToolResult<Details>,\n\toptions: { expanded: boolean },\n\ttheme: ExtensionContext[\"ui\"][\"theme\"],\n): void {\n\tcontainer.clear();\n\tcontainer.addChild(new Spacer(1));\n\tconst boxTheme = isSlashResultRunning(result) ? \"toolPendingBg\" : isSlashResultError(result) ? \"toolErrorBg\" : \"toolSuccessBg\";\n\tconst box = new Box(1, 1, (text: string) => theme.bg(boxTheme, text));\n\tbox.addChild(renderSubagentResult(result, options, theme));\n\tcontainer.addChild(box);\n}\n\nfunction createSlashResultComponent(\n\tdetails: SlashMessageDetails,\n\toptions: { expanded: boolean },\n\ttheme: ExtensionContext[\"ui\"][\"theme\"],\n): Container {\n\tconst container = new Container();\n\tlet lastVersion = -1;\n\tcontainer.render = (width: number): string[] => {\n\t\tconst snapshot = getSlashRenderableSnapshot(details);\n\t\tif (snapshot.version !== lastVersion || isSlashResultRunning(snapshot.result)) {\n\t\t\tlastVersion = snapshot.version;\n\t\t\trebuildSlashResultContainer(container, snapshot.result, options, theme);\n\t\t}\n\t\treturn Container.prototype.render.call(container, width);\n\t};\n\treturn container;\n}\n\nclass SubagentControlNoticeComponent implements Component {\n\tconstructor(\n\t\tprivate readonly details: SubagentControlMessageDetails,\n\t\tprivate readonly theme: ExtensionContext[\"ui\"][\"theme\"],\n\t) {}\n\n\tinvalidate(): void {}\n\n\trender(width: number): string[] {\n\t\tconst eventLabel = this.details.event.type.replaceAll(\"_\", \" \");\n\t\tif (width < 3) return [truncateToWidth(`Subagent ${eventLabel}`, width)];\n\t\tconst bodyWidth = Math.max(1, width - 2);\n\t\tconst borderChar = \"─\";\n\t\tconst header = ` ⚠ Subagent ${eventLabel}: ${this.details.event.agent} `;\n\t\tconst headerText = truncateToWidth(header, bodyWidth, \"\");\n\t\tconst headerPadding = Math.max(0, bodyWidth - visibleWidth(headerText));\n\t\tconst lines = [this.theme.fg(\"accent\", `╭${headerText}${borderChar.repeat(headerPadding)}╮`)];\n\n\t\tfor (const line of wrapTextWithAnsi(formatSubagentControlNotice(this.details), bodyWidth)) {\n\t\t\tconst text = truncateToWidth(line, bodyWidth, \"\");\n\t\t\tconst padding = Math.max(0, bodyWidth - visibleWidth(text));\n\t\t\tlines.push(this.theme.fg(\"accent\", `│${text}${\" \".repeat(padding)}│`));\n\t\t}\n\t\tlines.push(this.theme.fg(\"accent\", `╰${borderChar.repeat(bodyWidth)}╯`));\n\t\treturn lines;\n\t}\n}\n\nexport default function registerSubagentExtension(pi: ExtensionAPI): void {\n\tif (process.env[SUBAGENT_CHILD_ENV] === \"1\") {\n\t\treturn;\n\t}\n\tconst globalStore = globalThis as Record<string, unknown>;\n\tconst runtimeCleanupStoreKey = \"__piSubagentRuntimeCleanup\";\n\tconst previousRuntimeCleanup = globalStore[runtimeCleanupStoreKey];\n\tif (typeof previousRuntimeCleanup === \"function\") {\n\t\ttry {\n\t\t\tpreviousRuntimeCleanup();\n\t\t} catch {\n\t\t\t// Best effort cleanup for stale timers from an older reload.\n\t\t}\n\t}\n\n\tensureAccessibleDir(RESULTS_DIR);\n\tensureAccessibleDir(ASYNC_DIR);\n\tcleanupOldChainDirs();\n\n\tconst config = loadConfig();\n\tconst waitToolConfig = resolveWaitToolConfig(config.waitTool);\n\tconst asyncByDefault = config.asyncByDefault === true;\n\tconst tempArtifactsDir = getArtifactsDir(null);\n\tcleanupAllArtifactDirs(DEFAULT_ARTIFACT_CONFIG.cleanupDays);\n\n\tconst state: SubagentState = {\n\t\tbaseCwd: \"\",\n\t\tcurrentSessionId: null,\n\t\tsubagentInProgress: false,\n\t\tsubagentSpawns: { sessionId: null, count: 0 },\n\t\tasyncJobs: new Map(),\n\t\tfleetJobs: new Map(),\n\t\tforegroundRuns: new Map(),\n\t\tforegroundControls: new Map(),\n\t\tlastForegroundControlId: null,\n\t\tpendingForegroundControlNotices: new Map(),\n\t\tcleanupTimers: new Map(),\n\t\tlastUiContext: null,\n\t\tpoller: null,\n\t\tcompletionSeen: new Map(),\n\t\twatcher: null,\n\t\twatcherRestartTimer: null,\n\t\tresultFileCoalescer: {\n\t\t\tschedule: () => false,\n\t\t\tclear: () => {},\n\t\t},\n\t};\n\n\tconst supervisorChannel = createNativeSupervisorChannel(pi, state);\n\tconst mainWatchdog = registerMainWatchdog(pi);\n\tconst { startResultWatcher, primeExistingResults, stopResultWatcher } = createResultWatcher(\n\t\tpi,\n\t\tstate,\n\t\tRESULTS_DIR,\n\t\t10 * 60 * 1000,\n\t);\n\tstartResultWatcher();\n\tprimeExistingResults();\n\n\tconst runtimeCleanup = () => {\n\t\tmainWatchdog.dispose();\n\t\tstopResultWatcher();\n\t\tscheduledRunManager.stop();\n\t\tsupervisorChannel.dispose();\n\t\tclearPendingForegroundControlNotices(state);\n\t\tif (state.poller) {\n\t\t\tclearInterval(state.poller);\n\t\t\tstate.poller = null;\n\t\t}\n\t};\n\tglobalStore[runtimeCleanupStoreKey] = runtimeCleanup;\n\n\tconst { ensurePoller, refreshWidget, handleStarted, handleComplete, resetJobs, restoreActiveJobs } = createAsyncJobTracker(pi, state, ASYNC_DIR, {\n\t\twidgetEnabled: config.asyncWidget !== false,\n\t});\n\tlet executorExecute: ((id: string, params: SubagentParamsLike, signal: AbortSignal, onUpdate: ((r: AgentToolResult<Details>) => void) | undefined, ctx: ExtensionContext) => Promise<AgentToolResult<Details>>) | undefined;\n\tconst scheduledRunManager = createScheduledRunManager({\n\t\tconfig,\n\t\tlaunch: (params, ctx, signal) => {\n\t\t\tif (!executorExecute) {\n\t\t\t\treturn Promise.resolve({\n\t\t\t\t\tcontent: [{ type: \"text\", text: \"Scheduled subagent launch is unavailable (executor not ready).\" }],\n\t\t\t\t\tisError: true,\n\t\t\t\t\tdetails: { mode: \"management\" as const, results: [] },\n\t\t\t\t});\n\t\t\t}\n\t\t\treturn executorExecute(randomUUID(), params, signal, undefined, ctx);\n\t\t},\n\t});\n\tconst executor = createSubagentExecutor({\n\t\tpi,\n\t\tstate,\n\t\tconfig,\n\t\tasyncByDefault,\n\t\twaitToolEnabled: waitToolConfig.enabled,\n\t\thandleScheduledRunAction: (params, ctx) => scheduledRunManager.handleToolCall(params, ctx),\n\t\twatchdog: mainWatchdog,\n\t\ttempArtifactsDir,\n\t\tgetSubagentSessionRoot,\n\t\texpandTilde,\n\t\tdiscoverAgents,\n\t});\n\texecutorExecute = executor.execute;\n\n\tpi.registerMessageRenderer<SlashMessageDetails>(SLASH_RESULT_TYPE, (message, options, theme) => {\n\t\tconst details = resolveSlashMessageDetails(message.details);\n\t\tif (!details) return undefined;\n\t\treturn createSlashResultComponent(details, options, theme);\n\t});\n\n\tpi.registerMessageRenderer<undefined>(SLASH_TEXT_RESULT_TYPE, (message, _options, _theme) => {\n\t\tconst content = typeof message.content === \"string\"\n\t\t\t? message.content\n\t\t\t: message.content\n\t\t\t\t.filter((entry) => entry.type === \"text\")\n\t\t\t\t.map((entry) => entry.text)\n\t\t\t\t.join(\"\\n\");\n\t\treturn new Text(content, 0, 0);\n\t});\n\n\tpi.registerMessageRenderer<SubagentNotifyDetails>(\"subagent-notify\", (message, options, theme) => {\n\t\tconst content = typeof message.content === \"string\" ? message.content : \"\";\n\t\tconst details = (message.details as SubagentNotifyDetails | undefined) ?? parseSubagentNotifyContent(content);\n\t\tif (!details) return new Text(content, 0, 0);\n\t\tconst icon = details.status === \"completed\"\n\t\t\t? theme.fg(\"success\", \"✓\")\n\t\t\t: details.status === \"paused\"\n\t\t\t\t? theme.fg(\"warning\", \"■\")\n\t\t\t\t: theme.fg(\"error\", \"✗\");\n\t\tconst parts: string[] = [];\n\t\tif (details.taskInfo) parts.push(details.taskInfo);\n\t\tif (details.durationMs !== undefined) parts.push(formatDuration(details.durationMs));\n\t\tlet text = `${icon} ${theme.bold(details.agent)} ${theme.fg(\"dim\", details.status)}`;\n\t\tif (parts.length > 0) text += ` ${theme.fg(\"dim\", \"·\")} ${parts.map((part) => theme.fg(\"dim\", part)).join(` ${theme.fg(\"dim\", \"·\")} `)}`;\n\t\tconst trimmedPreview = details.resultPreview.trim();\n\t\tconst previewLines = options.expanded\n\t\t\t? trimmedPreview.split(\"\\n\").filter((line) => line.trim())\n\t\t\t: [trimmedPreview.split(\"\\n\", 1)[0] ?? \"\"].filter((line) => line.trim());\n\t\tfor (const line of previewLines.length > 0 ? previewLines : [\"(no output)\"]) {\n\t\t\ttext += `\\n ${theme.fg(\"dim\", `⎿ ${line}`)}`;\n\t\t}\n\t\tif (!options.expanded && trimmedPreview.includes(\"\\n\")) {\n\t\t\tconst expandKey = keyText(\"app.tools.expand\");\n\t\t\ttext += `\\n ${theme.fg(\"dim\", `${expandKey} full notification`)}`;\n\t\t}\n\t\tif (details.sessionLabel && details.sessionValue) {\n\t\t\ttext += `\\n ${theme.fg(\"muted\", `${details.sessionLabel}: ${shortenPath(details.sessionValue)}`)}`;\n\t\t}\n\t\treturn new Text(text, 0, 0);\n\t});\n\n\tpi.registerMessageRenderer<SubagentSteeringMessageDetails>(SUBAGENT_STEERING_MESSAGE_TYPE, (message, _options, theme) => {\n\t\tconst details = message.details as SubagentSteeringMessageDetails | undefined;\n\t\tif (!details) return undefined;\n\t\treturn new Text(theme.fg(details.state === \"recovered\" ? \"warning\" : \"error\", formatSteeringNotice(details)), 0, 0);\n\t});\n\n\tpi.registerMessageRenderer<SubagentControlMessageDetails>(SUBAGENT_CONTROL_MESSAGE_TYPE, () => undefined);\n\n\tconst executeSubagentCollapsed = (id: string, params: SubagentParamsLike, signal: AbortSignal, onUpdate: ((result: AgentToolResult<Details>) => void) | undefined, ctx: ExtensionContext) => {\n\t\tif (ctx.hasUI) ctx.ui.setToolsExpanded(false);\n\t\treturn executor.execute(id, params, signal, onUpdate, ctx);\n\t};\n\n\tconst slashBridge = registerSlashSubagentBridge({\n\t\tevents: pi.events,\n\t\tgetContext: () => state.lastUiContext,\n\t\texecute: (id, params, signal, onUpdate, ctx) =>\n\t\t\texecuteSubagentCollapsed(id, params, signal, onUpdate, ctx),\n\t});\n\n\tconst promptTemplateBridge = registerPromptTemplateDelegationBridge({\n\t\tevents: pi.events,\n\t\tgetContext: () => state.lastUiContext,\n\t\texecute: (requestId, params, signal, ctx, onUpdate) =>\n\t\t\texecuteSubagentCollapsed(requestId, params, signal, onUpdate, ctx),\n\t});\n\n\tconst rpcBridge = registerSubagentRpcBridge({\n\t\tevents: pi.events,\n\t\tgetContext: () => state.lastUiContext,\n\t\texecute: (id, params, signal, onUpdate, ctx) => executor.execute(id, params, signal, onUpdate, ctx),\n\t});\n\n\tfunction effectiveParallelTaskCount(tasks: Array<{ count?: unknown }> | undefined): number {\n\t\tif (!tasks || tasks.length === 0) return 0;\n\t\treturn tasks.reduce((total, task) => {\n\t\t\tconst count = typeof task.count === \"number\" && Number.isInteger(task.count) && task.count >= 1 ? task.count : 1;\n\t\t\treturn total + count;\n\t\t}, 0);\n\t}\n\n\tconst tool: ToolDefinition<typeof SubagentParams, Details> = {\n\t\tname: \"subagent\",\n\t\tlabel: \"Subagent\",\n\t\tdescription: buildSubagentToolDescription(config),\n\t\tparameters: SubagentParams,\n\t\t// lunr: sequential so multiple subagent calls in one turn run one-at-a-time\n\t\t// instead of tripping the subagentInProgress guard (parallel mode uses the tasks array).\n\t\texecutionMode: \"sequential\",\n\n\t\tprepareArguments(args) {\n\t\t\t// Run friendly chain validation before pi-ai's raw TypeBox schema check\n\t\t\t// so the model sees which property is disallowed, what is allowed, and a\n\t\t\t// valid example instead of `chain.N: must not have additional properties`.\n\t\t\tvalidateChainInput(args);\n\t\t\treturn args as never;\n\t\t},\n\n\t\texecute(id, params, signal, onUpdate, ctx) {\n\t\t\treturn executeSubagentCollapsed(id, params, signal, onUpdate, ctx);\n\t\t},\n\n\t\trenderCall(args, theme) {\n\t\t\tif (args.action) {\n\t\t\t\tconst target = args.agent || args.chainName || \"\";\n\t\t\t\treturn new Text(\n\t\t\t\t\t`${theme.fg(\"toolTitle\", theme.bold(\"subagent \"))}${args.action}${target ? ` ${theme.fg(\"accent\", target)}` : \"\"}`,\n\t\t\t\t\t0, 0,\n\t\t\t\t);\n\t\t\t}\n\t\t\tconst isParallel = (args.tasks?.length ?? 0) > 0;\n\t\t\tconst parallelCount = effectiveParallelTaskCount(args.tasks as Array<{ count?: unknown }> | undefined);\n\t\t\tconst asyncLabel = args.async === true && args.clarify !== true ? theme.fg(\"warning\", \" [async]\") : \"\";\n\t\t\tif (args.chain?.length)\n\t\t\t\treturn new Text(\n\t\t\t\t\t`${theme.fg(\"toolTitle\", theme.bold(\"subagent \"))}chain (${args.chain.length})${asyncLabel}`,\n\t\t\t\t\t0,\n\t\t\t\t\t0,\n\t\t\t\t);\n\t\t\tif (isParallel)\n\t\t\t\treturn new Text(\n\t\t\t\t\t`${theme.fg(\"toolTitle\", theme.bold(\"subagent \"))}parallel (${parallelCount})${asyncLabel}`,\n\t\t\t\t\t0,\n\t\t\t\t\t0,\n\t\t\t\t);\n\t\t\treturn new Text(\n\t\t\t\t`${theme.fg(\"toolTitle\", theme.bold(\"subagent \"))}${theme.fg(\"accent\", args.agent || \"?\")}${asyncLabel}`,\n\t\t\t\t0,\n\t\t\t\t0,\n\t\t\t);\n\t\t},\n\n\t\trenderResult(result, options, theme, context) {\n\t\t\t// lunr: hide `subagent list` / `subagent models` result bodies — the call\n\t\t\t// row already shows the action; the dump stays in the transcript for the model.\n\t\t\tconst action = (context.args as { action?: string } | undefined)?.action;\n\t\t\tif (action === \"list\" || action === \"models\") {\n\t\t\t\treturn { render: () => [], invalidate() {} };\n\t\t\t}\n\t\t\tif (subagentResultIsRunning(result)) {\n\t\t\t\tensureSubagentResultAnimation(context);\n\t\t\t} else {\n\t\t\t\tclearLegacyResultAnimationTimer(context);\n\t\t\t}\n\t\t\tconst state = context.state as { frame?: number; animEntries?: Array<{ text: Text; line: (frame: number) => string }> };\n\t\t\t// lunr: collect running-glyph line templates while rendering so the\n\t\t\t// animation timer can rewrite them in place (see ensureSubagentResultAnimation).\n\t\t\tconst entries: Array<{ text: Text; line: (frame: number) => string }> = [];\n\t\t\tstate.animEntries = entries;\n\t\t\tsubagentAnimSink.current = entries;\n\t\t\ttry {\n\t\t\t\treturn renderSubagentResult(result, options, theme, state.frame ?? 0);\n\t\t\t} finally {\n\t\t\t\tsubagentAnimSink.current = null;\n\t\t\t}\n\t\t},\n\n\t};\n\n\tpi.registerTool(tool);\n\n\t// lunr: register a callback so toggling model tiers mid-session rebuilds the subagent\n\t// tool description (adds/removes MODEL TIERS guidance) and refreshes the tool registry.\n\tconst modelTierBridge = (globalThis as Record<symbol, unknown>)[Symbol.for(\"@lunr/model-tiers\")] as\n\t\t| { registerToolDescriptionRefresher?: (refresher: () => void) => void }\n\t\t| undefined;\n\tmodelTierBridge?.registerToolDescriptionRefresher?.(() => {\n\t\ttool.description = buildSubagentToolDescription(config);\n\t\t// pi.runtime is not on ExtensionAPI; registerTool refreshes the live registry.\n\t\tpi.registerTool(tool);\n\t});\n\n\tregisterWaitTool(pi, state, waitToolConfig.enabled);\n\n\tpi.on(\"agent_end\", async (_event, ctx) => {\n\t\tif (ctx.hasUI) return;\n\t\tawait drainOutstandingWork({ state, events: pi.events });\n\t});\n\n\tregisterSlashCommands(pi, state);\n\n\tconst eventUnsubscribeStoreKey = \"__piSubagentEventUnsubscribes\";\n\tconst controlNoticeSeenStoreKey = \"__piSubagentVisibleControlNotices\";\n\tconst previousEventUnsubscribes = globalStore[eventUnsubscribeStoreKey];\n\tif (Array.isArray(previousEventUnsubscribes)) {\n\t\tfor (const unsubscribe of previousEventUnsubscribes) {\n\t\t\tif (typeof unsubscribe !== \"function\") continue;\n\t\t\ttry {\n\t\t\t\tunsubscribe();\n\t\t\t} catch {\n\t\t\t\t// Best effort cleanup for stale handlers from an older reload.\n\t\t\t}\n\t\t}\n\t}\n\tregisterSubagentNotify(pi, state, { batchConfig: config.completionBatch });\n\n\tconst existingVisibleControlNotices = globalStore[controlNoticeSeenStoreKey];\n\tconst visibleControlNotices = existingVisibleControlNotices instanceof Set ? existingVisibleControlNotices as Set<string> : new Set<string>();\n\tglobalStore[controlNoticeSeenStoreKey] = visibleControlNotices;\n\tconst controlEventHandler = (payload: unknown) => {\n\t\thandleSubagentControlNotice({\n\t\t\tpi,\n\t\t\tstate,\n\t\t\tvisibleControlNotices,\n\t\t\tdetails: payload as SubagentControlMessageDetails,\n\t\t});\n\t};\n\tconst steeringNoticeHandler = (payload: unknown) => {\n\t\thandleSubagentSteeringNotice({ pi, state, details: payload as SubagentSteeringMessageDetails });\n\t};\n\tconst eventUnsubscribes = [\n\t\tpi.events.on(SUBAGENT_ASYNC_STARTED_EVENT, handleStarted),\n\t\tpi.events.on(SUBAGENT_ASYNC_COMPLETE_EVENT, handleComplete),\n\t\tpi.events.on(SUBAGENT_CONTROL_EVENT, controlEventHandler),\n\t\tpi.events.on(SUBAGENT_STEERING_NOTICE_EVENT, steeringNoticeHandler),\n\t\trpcBridge.dispose,\n\t];\n\tglobalStore[eventUnsubscribeStoreKey] = eventUnsubscribes;\n\n\tpi.on(\"tool_result\", (event, ctx) => {\n\t\tif (event.toolName !== \"subagent\") return;\n\t\tif (!ctx.hasUI) return;\n\t\tstate.lastUiContext = ctx;\n\t\tif (state.asyncJobs.size > 0) {\n\t\t\trefreshWidget(ctx);\n\t\t\tensurePoller();\n\t\t}\n\t});\n\n\tconst cleanupSessionArtifacts = (ctx: ExtensionContext) => {\n\t\ttry {\n\t\t\tconst sessionFile = ctx.sessionManager.getSessionFile();\n\t\t\tif (sessionFile) {\n\t\t\t\tcleanupOldArtifacts(getArtifactsDir(sessionFile), DEFAULT_ARTIFACT_CONFIG.cleanupDays);\n\t\t\t}\n\t\t} catch {\n\t\t\t// Cleanup failures should not block session lifecycle events.\n\t\t}\n\t};\n\n\tconst resetSessionState = (ctx: ExtensionContext) => {\n\t\tstate.baseCwd = ctx.cwd;\n\t\tstate.currentSessionId = resolveCurrentSessionId(ctx.sessionManager);\n\t\tstate.subagentSpawns = { sessionId: state.currentSessionId, count: 0 };\n\t\t// Set PI_SUBAGENT_PARENT_SESSION for permission-system forwarding.\n\t\t// Only set in the root session (the interactive UI session), not in\n\t\t// child subagent processes — children inherit the parent's value\n\t\t// through the process environment at spawn time and must not overwrite\n\t\t// it with their own session identity.\n\t\tif (!process.env[SUBAGENT_CHILD_ENV]) {\n\t\t\tconst sessionId = ctx.sessionManager.getSessionId();\n\t\t\tif (sessionId) {\n\t\t\t\tprocess.env[SUBAGENT_PARENT_SESSION_ENV] = sessionId;\n\t\t\t}\n\t\t}\n\t\tstate.lastUiContext = ctx;\n\t\tcleanupSessionArtifacts(ctx);\n\t\tclearPendingForegroundControlNotices(state);\n\t\tresetJobs(ctx);\n\t\trestoreActiveJobs(ctx);\n\t\tscheduledRunManager.bindSession(ctx);\n\t\trestoreSlashFinalSnapshots(ctx.sessionManager.getEntries());\n\t\tprimeExistingResults();\n\t};\n\n\tpi.on(\"session_start\", (_event, ctx) => {\n\t\tresetSessionState(ctx);\n\t\trpcBridge.emitReady(ctx);\n\t\tsupervisorChannel.start();\n\t});\n\n\tpi.on(\"session_shutdown\", () => {\n\t\tdelete process.env[SUBAGENT_PARENT_SESSION_ENV];\n\t\tfor (const unsubscribe of eventUnsubscribes) {\n\t\t\ttry {\n\t\t\t\tunsubscribe();\n\t\t\t} catch {\n\t\t\t\t// Best effort cleanup during shutdown.\n\t\t\t}\n\t\t}\n\t\tif (globalStore[eventUnsubscribeStoreKey] === eventUnsubscribes) {\n\t\t\tdelete globalStore[eventUnsubscribeStoreKey];\n\t\t}\n\t\tstopResultWatcher();\n\t\tscheduledRunManager.stop();\n\t\tif (state.poller) clearInterval(state.poller);\n\t\tstate.poller = null;\n\t\tclearPendingForegroundControlNotices(state);\n\t\tfor (const timer of state.cleanupTimers.values()) {\n\t\t\tclearTimeout(timer);\n\t\t}\n\t\tstate.cleanupTimers.clear();\n\t\tstate.asyncJobs.clear();\n\t\tclearSlashSnapshots();\n\t\tslashBridge.cancelAll();\n\t\tslashBridge.dispose();\n\t\tpromptTemplateBridge.cancelAll();\n\t\tpromptTemplateBridge.dispose();\n\t\tsupervisorChannel.dispose();\n\t\tif (globalStore[runtimeCleanupStoreKey] === runtimeCleanup) {\n\t\t\tdelete globalStore[runtimeCleanupStoreKey];\n\t\t}\n\t\ttry {\n\t\t\tif (state.lastUiContext?.hasUI) {\n\t\t\t\tstate.lastUiContext.ui.setWidget(WIDGET_KEY, undefined);\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tif (!isStaleExtensionContextError(error)) throw error;\n\t\t}\n\t});\n}\n"]}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/builtin-extensions/pi-subagents/src/extension/index.ts"],"names":[],"mappings":"AACA;;;;;;;;;;;;GAYG;AAOH,OAAO,EAAW,KAAK,YAAY,EAA8C,MAAM,iCAAiC,CAAC;AAoDzH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AA2JzC,MAAM,CAAC,OAAO,UAAU,yBAAyB,CAAC,EAAE,EAAE,YAAY,GAAG,IAAI,CAoaxE","sourcesContent":["// @ts-nocheck\n/**\n * Subagent Tool\n *\n * Full-featured subagent with sync and async modes.\n * - Sync (default): Streams output, renders markdown, tracks usage\n * - Async: Background execution, emits events when done\n *\n * Modes: single (agent + task), parallel (tasks[]), chain (chain[] with {previous})\n * Toggle: async parameter (default: false, configurable via config.json)\n *\n * Config file: ~/.pi/agent/extensions/subagent/config.json\n * { \"asyncByDefault\": true, \"forceTopLevelAsync\": true, \"maxSubagentDepth\": 1, \"intercomBridge\": { \"mode\": \"always\", \"instructionFile\": \"./intercom-bridge.md\" }, \"worktreeSetupHook\": \"./scripts/setup-worktree.mjs\" }\n */\n\nimport { randomUUID } from \"node:crypto\";\nimport * as fs from \"node:fs\";\nimport * as os from \"node:os\";\nimport * as path from \"node:path\";\nimport type { AgentToolResult } from \"@ashx-j/lunr-agent\";\nimport { keyText, type ExtensionAPI, type ExtensionContext, type ToolDefinition } from \"@ashx-j/lunr\";\nimport { Box, Container, Spacer, Text, truncateToWidth, visibleWidth, wrapTextWithAnsi, type Component } from \"@ashx-j/lunr-tui\";\nimport { discoverAgents } from \"../agents/agents.ts\";\nimport { cleanupAllArtifactDirs, cleanupOldArtifacts, getArtifactsDir } from \"../shared/artifacts.ts\";\nimport { resolveCurrentSessionId } from \"../shared/session-identity.ts\";\nimport { cleanupOldChainDirs } from \"../shared/settings.ts\";\nimport { clearLegacyResultAnimationTimer, renderSubagentResult, subagentAnimSink } from \"../tui/render.ts\";\nimport { SubagentParams } from \"./schemas.ts\";\nimport { validateChainInput } from \"./chain-validation.ts\";\nimport { createSubagentExecutor, type SubagentParamsLike } from \"../runs/foreground/subagent-executor.ts\";\nimport { createAsyncJobTracker } from \"../runs/background/async-job-tracker.ts\";\nimport { createResultWatcher } from \"../runs/background/result-watcher.ts\";\nimport { createScheduledRunManager } from \"../runs/background/scheduled-runs.ts\";\nimport { registerSlashCommands } from \"../slash/slash-commands.ts\";\nimport { registerPromptTemplateDelegationBridge } from \"../slash/prompt-template-bridge.ts\";\nimport { registerMainWatchdog } from \"../watchdog/register-main.ts\";\nimport { registerSlashSubagentBridge } from \"../slash/slash-bridge.ts\";\nimport { createNativeSupervisorChannel } from \"../intercom/native-supervisor-channel.ts\";\nimport { registerSubagentRpcBridge } from \"./rpc.ts\";\nimport { clearSlashSnapshots, getSlashRenderableSnapshot, resolveSlashMessageDetails, restoreSlashFinalSnapshots, type SlashMessageDetails } from \"../slash/slash-live-state.ts\";\nimport { inspectSubagentStatus } from \"../runs/background/run-status.ts\";\nimport { resolveWaitToolConfig } from \"../runs/background/subagent-wait.ts\";\nimport { registerWaitTool } from \"../runs/background/wait-tool.ts\";\nimport { drainOutstandingWork } from \"../runs/background/auto-drain.ts\";\nimport registerSubagentNotify, { parseSubagentNotifyContent, type SubagentNotifyDetails } from \"../runs/background/notify.ts\";\nimport { formatSteeringNotice, handleSubagentSteeringNotice, SUBAGENT_STEERING_MESSAGE_TYPE, type SubagentSteeringMessageDetails } from \"./steering-notices.ts\";\nimport { SUBAGENT_CHILD_ENV, SUBAGENT_PARENT_SESSION_ENV } from \"../runs/shared/pi-args.ts\";\nimport { formatDuration, shortenPath } from \"../shared/formatters.ts\";\nimport { loadConfig } from \"./config.ts\";\nimport { buildSubagentToolDescription } from \"./tool-description.ts\";\nimport {\n\ttype Details,\n\ttype SubagentState,\n\tASYNC_DIR,\n\tDEFAULT_ARTIFACT_CONFIG,\n\tRESULTS_DIR,\n\tSLASH_RESULT_TYPE,\n\tSLASH_TEXT_RESULT_TYPE,\n\tSUBAGENT_ASYNC_COMPLETE_EVENT,\n\tSUBAGENT_ASYNC_STARTED_EVENT,\n\tSUBAGENT_CONTROL_EVENT,\n\tSUBAGENT_STEERING_NOTICE_EVENT,\n\tWIDGET_KEY,\n} from \"../shared/types.ts\";\nimport {\n\tclearPendingForegroundControlNotices,\n\tformatSubagentControlNotice,\n\thandleSubagentControlNotice,\n\tSUBAGENT_CONTROL_MESSAGE_TYPE,\n\ttype SubagentControlMessageDetails,\n} from \"./control-notices.ts\";\n\nexport { loadConfig } from \"./config.ts\";\n\n/**\n * Derive subagent session base directory from parent session file.\n * If parent session is ~/.pi/agent/sessions/abc123.jsonl,\n * returns ~/.pi/agent/sessions/abc123/ as the base.\n * Callers add runId to create the actual session root: abc123/{runId}/\n * Falls back to a unique temp directory if no parent session.\n */\nfunction getSubagentSessionRoot(parentSessionFile: string | null): string {\n\tif (parentSessionFile) {\n\t\tconst baseName = path.basename(parentSessionFile, \".jsonl\");\n\t\tconst sessionsDir = path.dirname(parentSessionFile);\n\t\treturn path.join(sessionsDir, baseName);\n\t}\n\treturn fs.mkdtempSync(path.join(os.tmpdir(), \"pi-subagent-session-\"));\n}\n\nfunction expandTilde(p: string): string {\n\treturn p.startsWith(\"~/\") ? path.join(os.homedir(), p.slice(2)) : p;\n}\n\n/**\n * Create a directory and verify it is actually accessible.\n * On Windows with Azure AD/Entra ID, directories created shortly after\n * wake-from-sleep can end up with broken NTFS ACLs (null DACL) when the\n * cloud SID cannot be resolved without network connectivity. This leaves\n * the directory completely inaccessible to the creating user.\n */\nfunction ensureAccessibleDir(dirPath: string): void {\n\tfs.mkdirSync(dirPath, { recursive: true });\n\ttry {\n\t\tfs.accessSync(dirPath, fs.constants.R_OK | fs.constants.W_OK);\n\t} catch {\n\t\ttry {\n\t\t\tfs.rmSync(dirPath, { recursive: true, force: true });\n\t\t} catch {\n\t\t\t// Best effort: retry mkdir/access even if cleanup fails.\n\t\t}\n\t\tfs.mkdirSync(dirPath, { recursive: true });\n\t\tfs.accessSync(dirPath, fs.constants.R_OK | fs.constants.W_OK);\n\t}\n}\n\nfunction isSlashResultRunning(result: { details?: Details }): boolean {\n\treturn result.details?.progress?.some((entry) => entry.status === \"running\")\n\t\t|| result.details?.results.some((entry) => entry.progress?.status === \"running\")\n\t\t|| false;\n}\n\n// Drives the inline running-indicator braille animation for foreground subagent\n// results. Foreground runs receive progress only on child events, so the glyph\n// (derived from progress fields) would freeze between events. While a result is\n// running we tick a frame counter every 80ms; renderResult registers the\n// running-glyph lines in state.animEntries (via subagentAnimSink) and the timer\n// rewrites just those Text nodes + requests a repaint, instead of invalidate()-ing\n// the whole tool component (a full renderSubagentResult rebuild 12.5x/s made the\n// spinner laggy).\nfunction subagentResultIsRunning(result: { details?: Details }): boolean {\n\treturn result.details?.progress?.some((entry) => entry.status === \"running\")\n\t\t|| result.details?.results.some((entry) => entry.progress?.status === \"running\")\n\t\t|| false;\n}\n\nfunction ensureSubagentResultAnimation(context: { state: Record<string, unknown>; invalidate?: () => void; requestRender?: () => void }): void {\n\tconst state = context.state as {\n\t\tsubagentResultAnimationTimer?: ReturnType<typeof setInterval>;\n\t\tframe?: number;\n\t\tanimEntries?: Array<{ text: Text; line: (frame: number) => string }>;\n\t};\n\tif (state.subagentResultAnimationTimer) return;\n\tif (typeof context.invalidate !== \"function\") return;\n\tif (state.frame === undefined) state.frame = 0;\n\tstate.subagentResultAnimationTimer = setInterval(() => {\n\t\tstate.frame = ((state.frame ?? 0) + 1) % 10;\n\t\ttry {\n\t\t\tconst entries = state.animEntries;\n\t\t\tif (entries?.length && typeof context.requestRender === \"function\") {\n\t\t\t\tfor (const entry of entries) entry.text.setText(entry.line(state.frame ?? 0));\n\t\t\t\tcontext.requestRender();\n\t\t\t} else {\n\t\t\t\tcontext.invalidate();\n\t\t\t}\n\t\t} catch {}\n\t}, 80);\n}\n\nfunction isSlashResultError(result: { details?: Details }): boolean {\n\treturn result.details?.results.some((entry) => entry.exitCode !== 0 && entry.progress?.status !== \"running\") || false;\n}\n\nfunction isStaleExtensionContextError(error: unknown): boolean {\n\treturn error instanceof Error && error.message.includes(\"Extension context no longer active\");\n}\n\nfunction rebuildSlashResultContainer(\n\tcontainer: Container,\n\tresult: AgentToolResult<Details>,\n\toptions: { expanded: boolean },\n\ttheme: ExtensionContext[\"ui\"][\"theme\"],\n): void {\n\tcontainer.clear();\n\tcontainer.addChild(new Spacer(1));\n\tconst boxTheme = isSlashResultRunning(result) ? \"toolPendingBg\" : isSlashResultError(result) ? \"toolErrorBg\" : \"toolSuccessBg\";\n\tconst box = new Box(1, 1, (text: string) => theme.bg(boxTheme, text));\n\tbox.addChild(renderSubagentResult(result, options, theme));\n\tcontainer.addChild(box);\n}\n\nfunction createSlashResultComponent(\n\tdetails: SlashMessageDetails,\n\toptions: { expanded: boolean },\n\ttheme: ExtensionContext[\"ui\"][\"theme\"],\n): Container {\n\tconst container = new Container();\n\tlet lastVersion = -1;\n\tcontainer.render = (width: number): string[] => {\n\t\tconst snapshot = getSlashRenderableSnapshot(details);\n\t\tif (snapshot.version !== lastVersion || isSlashResultRunning(snapshot.result)) {\n\t\t\tlastVersion = snapshot.version;\n\t\t\trebuildSlashResultContainer(container, snapshot.result, options, theme);\n\t\t}\n\t\treturn Container.prototype.render.call(container, width);\n\t};\n\treturn container;\n}\n\nclass SubagentControlNoticeComponent implements Component {\n\tconstructor(\n\t\tprivate readonly details: SubagentControlMessageDetails,\n\t\tprivate readonly theme: ExtensionContext[\"ui\"][\"theme\"],\n\t) {}\n\n\tinvalidate(): void {}\n\n\trender(width: number): string[] {\n\t\tconst eventLabel = this.details.event.type.replaceAll(\"_\", \" \");\n\t\tif (width < 3) return [truncateToWidth(`Subagent ${eventLabel}`, width)];\n\t\tconst bodyWidth = Math.max(1, width - 2);\n\t\tconst borderChar = \"─\";\n\t\tconst header = ` ⚠ Subagent ${eventLabel}: ${this.details.event.agent} `;\n\t\tconst headerText = truncateToWidth(header, bodyWidth, \"\");\n\t\tconst headerPadding = Math.max(0, bodyWidth - visibleWidth(headerText));\n\t\tconst lines = [this.theme.fg(\"accent\", `╭${headerText}${borderChar.repeat(headerPadding)}╮`)];\n\n\t\tfor (const line of wrapTextWithAnsi(formatSubagentControlNotice(this.details), bodyWidth)) {\n\t\t\tconst text = truncateToWidth(line, bodyWidth, \"\");\n\t\t\tconst padding = Math.max(0, bodyWidth - visibleWidth(text));\n\t\t\tlines.push(this.theme.fg(\"accent\", `│${text}${\" \".repeat(padding)}│`));\n\t\t}\n\t\tlines.push(this.theme.fg(\"accent\", `╰${borderChar.repeat(bodyWidth)}╯`));\n\t\treturn lines;\n\t}\n}\n\nexport default function registerSubagentExtension(pi: ExtensionAPI): void {\n\tif (process.env[SUBAGENT_CHILD_ENV] === \"1\") {\n\t\treturn;\n\t}\n\tconst globalStore = globalThis as Record<string, unknown>;\n\tconst runtimeCleanupStoreKey = \"__piSubagentRuntimeCleanup\";\n\tconst previousRuntimeCleanup = globalStore[runtimeCleanupStoreKey];\n\tif (typeof previousRuntimeCleanup === \"function\") {\n\t\ttry {\n\t\t\tpreviousRuntimeCleanup();\n\t\t} catch {\n\t\t\t// Best effort cleanup for stale timers from an older reload.\n\t\t}\n\t}\n\n\tensureAccessibleDir(RESULTS_DIR);\n\tensureAccessibleDir(ASYNC_DIR);\n\tcleanupOldChainDirs();\n\n\tconst config = loadConfig();\n\tconst waitToolConfig = resolveWaitToolConfig(config.waitTool);\n\tconst asyncByDefault = config.asyncByDefault === true;\n\tconst tempArtifactsDir = getArtifactsDir(null);\n\tcleanupAllArtifactDirs(DEFAULT_ARTIFACT_CONFIG.cleanupDays);\n\n\tconst state: SubagentState = {\n\t\tbaseCwd: \"\",\n\t\tcurrentSessionId: null,\n\t\tforegroundSubagentInFlight: 0,\n\t\tsubagentSpawns: { sessionId: null, count: 0 },\n\t\tasyncJobs: new Map(),\n\t\tfleetJobs: new Map(),\n\t\tforegroundRuns: new Map(),\n\t\tforegroundControls: new Map(),\n\t\tlastForegroundControlId: null,\n\t\tpendingForegroundControlNotices: new Map(),\n\t\tcleanupTimers: new Map(),\n\t\tlastUiContext: null,\n\t\tpoller: null,\n\t\tcompletionSeen: new Map(),\n\t\twatcher: null,\n\t\twatcherRestartTimer: null,\n\t\tresultFileCoalescer: {\n\t\t\tschedule: () => false,\n\t\t\tclear: () => {},\n\t\t},\n\t};\n\n\tconst supervisorChannel = createNativeSupervisorChannel(pi, state);\n\tconst mainWatchdog = registerMainWatchdog(pi);\n\tconst { startResultWatcher, primeExistingResults, stopResultWatcher } = createResultWatcher(\n\t\tpi,\n\t\tstate,\n\t\tRESULTS_DIR,\n\t\t10 * 60 * 1000,\n\t);\n\tstartResultWatcher();\n\tprimeExistingResults();\n\n\tconst runtimeCleanup = () => {\n\t\tmainWatchdog.dispose();\n\t\tstopResultWatcher();\n\t\tscheduledRunManager.stop();\n\t\tsupervisorChannel.dispose();\n\t\tclearPendingForegroundControlNotices(state);\n\t\tif (state.poller) {\n\t\t\tclearInterval(state.poller);\n\t\t\tstate.poller = null;\n\t\t}\n\t};\n\tglobalStore[runtimeCleanupStoreKey] = runtimeCleanup;\n\n\tconst { ensurePoller, refreshWidget, handleStarted, handleComplete, resetJobs, restoreActiveJobs } = createAsyncJobTracker(pi, state, ASYNC_DIR, {\n\t\twidgetEnabled: config.asyncWidget !== false,\n\t});\n\tlet executorExecute: ((id: string, params: SubagentParamsLike, signal: AbortSignal, onUpdate: ((r: AgentToolResult<Details>) => void) | undefined, ctx: ExtensionContext) => Promise<AgentToolResult<Details>>) | undefined;\n\tconst scheduledRunManager = createScheduledRunManager({\n\t\tconfig,\n\t\tlaunch: (params, ctx, signal) => {\n\t\t\tif (!executorExecute) {\n\t\t\t\treturn Promise.resolve({\n\t\t\t\t\tcontent: [{ type: \"text\", text: \"Scheduled subagent launch is unavailable (executor not ready).\" }],\n\t\t\t\t\tisError: true,\n\t\t\t\t\tdetails: { mode: \"management\" as const, results: [] },\n\t\t\t\t});\n\t\t\t}\n\t\t\treturn executorExecute(randomUUID(), params, signal, undefined, ctx);\n\t\t},\n\t});\n\tconst executor = createSubagentExecutor({\n\t\tpi,\n\t\tstate,\n\t\tconfig,\n\t\tasyncByDefault,\n\t\twaitToolEnabled: waitToolConfig.enabled,\n\t\thandleScheduledRunAction: (params, ctx) => scheduledRunManager.handleToolCall(params, ctx),\n\t\twatchdog: mainWatchdog,\n\t\ttempArtifactsDir,\n\t\tgetSubagentSessionRoot,\n\t\texpandTilde,\n\t\tdiscoverAgents,\n\t});\n\texecutorExecute = executor.execute;\n\n\tpi.registerMessageRenderer<SlashMessageDetails>(SLASH_RESULT_TYPE, (message, options, theme) => {\n\t\tconst details = resolveSlashMessageDetails(message.details);\n\t\tif (!details) return undefined;\n\t\treturn createSlashResultComponent(details, options, theme);\n\t});\n\n\tpi.registerMessageRenderer<undefined>(SLASH_TEXT_RESULT_TYPE, (message, _options, _theme) => {\n\t\tconst content = typeof message.content === \"string\"\n\t\t\t? message.content\n\t\t\t: message.content\n\t\t\t\t.filter((entry) => entry.type === \"text\")\n\t\t\t\t.map((entry) => entry.text)\n\t\t\t\t.join(\"\\n\");\n\t\treturn new Text(content, 0, 0);\n\t});\n\n\tpi.registerMessageRenderer<SubagentNotifyDetails>(\"subagent-notify\", (message, options, theme) => {\n\t\tconst content = typeof message.content === \"string\" ? message.content : \"\";\n\t\tconst details = (message.details as SubagentNotifyDetails | undefined) ?? parseSubagentNotifyContent(content);\n\t\tif (!details) return new Text(content, 0, 0);\n\t\tconst icon = details.status === \"completed\"\n\t\t\t? theme.fg(\"success\", \"✓\")\n\t\t\t: details.status === \"paused\"\n\t\t\t\t? theme.fg(\"warning\", \"■\")\n\t\t\t\t: theme.fg(\"error\", \"✗\");\n\t\tconst parts: string[] = [];\n\t\tif (details.taskInfo) parts.push(details.taskInfo);\n\t\tif (details.durationMs !== undefined) parts.push(formatDuration(details.durationMs));\n\t\tlet text = `${icon} ${theme.bold(details.agent)} ${theme.fg(\"dim\", details.status)}`;\n\t\tif (parts.length > 0) text += ` ${theme.fg(\"dim\", \"·\")} ${parts.map((part) => theme.fg(\"dim\", part)).join(` ${theme.fg(\"dim\", \"·\")} `)}`;\n\t\tconst trimmedPreview = details.resultPreview.trim();\n\t\tconst previewLines = options.expanded\n\t\t\t? trimmedPreview.split(\"\\n\").filter((line) => line.trim())\n\t\t\t: [trimmedPreview.split(\"\\n\", 1)[0] ?? \"\"].filter((line) => line.trim());\n\t\tfor (const line of previewLines.length > 0 ? previewLines : [\"(no output)\"]) {\n\t\t\ttext += `\\n ${theme.fg(\"dim\", `⎿ ${line}`)}`;\n\t\t}\n\t\tif (!options.expanded && trimmedPreview.includes(\"\\n\")) {\n\t\t\tconst expandKey = keyText(\"app.tools.expand\");\n\t\t\ttext += `\\n ${theme.fg(\"dim\", `${expandKey} full notification`)}`;\n\t\t}\n\t\tif (details.sessionLabel && details.sessionValue) {\n\t\t\ttext += `\\n ${theme.fg(\"muted\", `${details.sessionLabel}: ${shortenPath(details.sessionValue)}`)}`;\n\t\t}\n\t\treturn new Text(text, 0, 0);\n\t});\n\n\tpi.registerMessageRenderer<SubagentSteeringMessageDetails>(SUBAGENT_STEERING_MESSAGE_TYPE, (message, _options, theme) => {\n\t\tconst details = message.details as SubagentSteeringMessageDetails | undefined;\n\t\tif (!details) return undefined;\n\t\treturn new Text(theme.fg(details.state === \"recovered\" ? \"warning\" : \"error\", formatSteeringNotice(details)), 0, 0);\n\t});\n\n\tpi.registerMessageRenderer<SubagentControlMessageDetails>(SUBAGENT_CONTROL_MESSAGE_TYPE, () => undefined);\n\n\tconst executeSubagentCollapsed = (id: string, params: SubagentParamsLike, signal: AbortSignal, onUpdate: ((result: AgentToolResult<Details>) => void) | undefined, ctx: ExtensionContext) => {\n\t\tif (ctx.hasUI) ctx.ui.setToolsExpanded(false);\n\t\treturn executor.execute(id, params, signal, onUpdate, ctx);\n\t};\n\n\tconst slashBridge = registerSlashSubagentBridge({\n\t\tevents: pi.events,\n\t\tgetContext: () => state.lastUiContext,\n\t\texecute: (id, params, signal, onUpdate, ctx) =>\n\t\t\texecuteSubagentCollapsed(id, params, signal, onUpdate, ctx),\n\t});\n\n\tconst promptTemplateBridge = registerPromptTemplateDelegationBridge({\n\t\tevents: pi.events,\n\t\tgetContext: () => state.lastUiContext,\n\t\texecute: (requestId, params, signal, ctx, onUpdate) =>\n\t\t\texecuteSubagentCollapsed(requestId, params, signal, onUpdate, ctx),\n\t});\n\n\tconst rpcBridge = registerSubagentRpcBridge({\n\t\tevents: pi.events,\n\t\tgetContext: () => state.lastUiContext,\n\t\texecute: (id, params, signal, onUpdate, ctx) => executor.execute(id, params, signal, onUpdate, ctx),\n\t});\n\n\tfunction effectiveParallelTaskCount(tasks: Array<{ count?: unknown }> | undefined): number {\n\t\tif (!tasks || tasks.length === 0) return 0;\n\t\treturn tasks.reduce((total, task) => {\n\t\t\tconst count = typeof task.count === \"number\" && Number.isInteger(task.count) && task.count >= 1 ? task.count : 1;\n\t\t\treturn total + count;\n\t\t}, 0);\n\t}\n\n\tconst tool: ToolDefinition<typeof SubagentParams, Details> = {\n\t\tname: \"subagent\",\n\t\tlabel: \"Subagent\",\n\t\tdescription: buildSubagentToolDescription(config),\n\t\tparameters: SubagentParams,\n\t\t// lunr: same-turn SINGLE calls overlap; sequential work uses chain.\n\t\texecutionMode: \"parallel\",\n\n\t\tprepareArguments(args) {\n\t\t\t// Run friendly chain validation before pi-ai's raw TypeBox schema check\n\t\t\t// so the model sees which property is disallowed, what is allowed, and a\n\t\t\t// valid example instead of `chain.N: must not have additional properties`.\n\t\t\tvalidateChainInput(args);\n\t\t\treturn args as never;\n\t\t},\n\n\t\texecute(id, params, signal, onUpdate, ctx) {\n\t\t\treturn executeSubagentCollapsed(id, params, signal, onUpdate, ctx);\n\t\t},\n\n\t\trenderCall(args, theme) {\n\t\t\tif (args.action) {\n\t\t\t\tconst target = args.agent || args.chainName || \"\";\n\t\t\t\treturn new Text(\n\t\t\t\t\t`${theme.fg(\"toolTitle\", theme.bold(\"subagent \"))}${args.action}${target ? ` ${theme.fg(\"accent\", target)}` : \"\"}`,\n\t\t\t\t\t0, 0,\n\t\t\t\t);\n\t\t\t}\n\t\t\tconst isParallel = (args.tasks?.length ?? 0) > 0;\n\t\t\tconst parallelCount = effectiveParallelTaskCount(args.tasks as Array<{ count?: unknown }> | undefined);\n\t\t\tconst asyncLabel = args.async === true && args.clarify !== true ? theme.fg(\"warning\", \" [async]\") : \"\";\n\t\t\tif (args.chain?.length)\n\t\t\t\treturn new Text(\n\t\t\t\t\t`${theme.fg(\"toolTitle\", theme.bold(\"subagent \"))}chain (${args.chain.length})${asyncLabel}`,\n\t\t\t\t\t0,\n\t\t\t\t\t0,\n\t\t\t\t);\n\t\t\tif (isParallel)\n\t\t\t\treturn new Text(\n\t\t\t\t\t`${theme.fg(\"toolTitle\", theme.bold(\"subagent \"))}parallel (${parallelCount})${asyncLabel}`,\n\t\t\t\t\t0,\n\t\t\t\t\t0,\n\t\t\t\t);\n\t\t\treturn new Text(\n\t\t\t\t`${theme.fg(\"toolTitle\", theme.bold(\"subagent \"))}${theme.fg(\"accent\", args.agent || \"?\")}${asyncLabel}`,\n\t\t\t\t0,\n\t\t\t\t0,\n\t\t\t);\n\t\t},\n\n\t\trenderResult(result, options, theme, context) {\n\t\t\t// lunr: hide `subagent list` / `subagent models` result bodies — the call\n\t\t\t// row already shows the action; the dump stays in the transcript for the model.\n\t\t\tconst action = (context.args as { action?: string } | undefined)?.action;\n\t\t\tif (action === \"list\" || action === \"models\") {\n\t\t\t\treturn { render: () => [], invalidate() {} };\n\t\t\t}\n\t\t\tif (subagentResultIsRunning(result)) {\n\t\t\t\tensureSubagentResultAnimation(context);\n\t\t\t} else {\n\t\t\t\tclearLegacyResultAnimationTimer(context);\n\t\t\t}\n\t\t\tconst state = context.state as { frame?: number; animEntries?: Array<{ text: Text; line: (frame: number) => string }> };\n\t\t\t// lunr: collect running-glyph line templates while rendering so the\n\t\t\t// animation timer can rewrite them in place (see ensureSubagentResultAnimation).\n\t\t\tconst entries: Array<{ text: Text; line: (frame: number) => string }> = [];\n\t\t\tstate.animEntries = entries;\n\t\t\tsubagentAnimSink.current = entries;\n\t\t\ttry {\n\t\t\t\treturn renderSubagentResult(result, options, theme, state.frame ?? 0);\n\t\t\t} finally {\n\t\t\t\tsubagentAnimSink.current = null;\n\t\t\t}\n\t\t},\n\n\t};\n\n\tpi.registerTool(tool);\n\n\t// lunr: register a callback so toggling model tiers mid-session rebuilds the subagent\n\t// tool description (adds/removes MODEL TIERS guidance) and refreshes the tool registry.\n\tconst modelTierBridge = (globalThis as Record<symbol, unknown>)[Symbol.for(\"@lunr/model-tiers\")] as\n\t\t| { registerToolDescriptionRefresher?: (refresher: () => void) => void }\n\t\t| undefined;\n\tmodelTierBridge?.registerToolDescriptionRefresher?.(() => {\n\t\ttool.description = buildSubagentToolDescription(config);\n\t\t// pi.runtime is not on ExtensionAPI; registerTool refreshes the live registry.\n\t\tpi.registerTool(tool);\n\t});\n\n\tregisterWaitTool(pi, state, waitToolConfig.enabled);\n\n\tpi.on(\"agent_end\", async (_event, ctx) => {\n\t\tif (ctx.hasUI) return;\n\t\tawait drainOutstandingWork({ state, events: pi.events });\n\t});\n\n\tregisterSlashCommands(pi, state);\n\n\tconst eventUnsubscribeStoreKey = \"__piSubagentEventUnsubscribes\";\n\tconst controlNoticeSeenStoreKey = \"__piSubagentVisibleControlNotices\";\n\tconst previousEventUnsubscribes = globalStore[eventUnsubscribeStoreKey];\n\tif (Array.isArray(previousEventUnsubscribes)) {\n\t\tfor (const unsubscribe of previousEventUnsubscribes) {\n\t\t\tif (typeof unsubscribe !== \"function\") continue;\n\t\t\ttry {\n\t\t\t\tunsubscribe();\n\t\t\t} catch {\n\t\t\t\t// Best effort cleanup for stale handlers from an older reload.\n\t\t\t}\n\t\t}\n\t}\n\tregisterSubagentNotify(pi, state, { batchConfig: config.completionBatch });\n\n\tconst existingVisibleControlNotices = globalStore[controlNoticeSeenStoreKey];\n\tconst visibleControlNotices = existingVisibleControlNotices instanceof Set ? existingVisibleControlNotices as Set<string> : new Set<string>();\n\tglobalStore[controlNoticeSeenStoreKey] = visibleControlNotices;\n\tconst controlEventHandler = (payload: unknown) => {\n\t\thandleSubagentControlNotice({\n\t\t\tpi,\n\t\t\tstate,\n\t\t\tvisibleControlNotices,\n\t\t\tdetails: payload as SubagentControlMessageDetails,\n\t\t});\n\t};\n\tconst steeringNoticeHandler = (payload: unknown) => {\n\t\thandleSubagentSteeringNotice({ pi, state, details: payload as SubagentSteeringMessageDetails });\n\t};\n\tconst eventUnsubscribes = [\n\t\tpi.events.on(SUBAGENT_ASYNC_STARTED_EVENT, handleStarted),\n\t\tpi.events.on(SUBAGENT_ASYNC_COMPLETE_EVENT, handleComplete),\n\t\tpi.events.on(SUBAGENT_CONTROL_EVENT, controlEventHandler),\n\t\tpi.events.on(SUBAGENT_STEERING_NOTICE_EVENT, steeringNoticeHandler),\n\t\trpcBridge.dispose,\n\t];\n\tglobalStore[eventUnsubscribeStoreKey] = eventUnsubscribes;\n\n\tpi.on(\"tool_result\", (event, ctx) => {\n\t\tif (event.toolName !== \"subagent\") return;\n\t\tif (!ctx.hasUI) return;\n\t\tstate.lastUiContext = ctx;\n\t\tif (state.asyncJobs.size > 0) {\n\t\t\trefreshWidget(ctx);\n\t\t\tensurePoller();\n\t\t}\n\t});\n\n\tconst cleanupSessionArtifacts = (ctx: ExtensionContext) => {\n\t\ttry {\n\t\t\tconst sessionFile = ctx.sessionManager.getSessionFile();\n\t\t\tif (sessionFile) {\n\t\t\t\tcleanupOldArtifacts(getArtifactsDir(sessionFile), DEFAULT_ARTIFACT_CONFIG.cleanupDays);\n\t\t\t}\n\t\t} catch {\n\t\t\t// Cleanup failures should not block session lifecycle events.\n\t\t}\n\t};\n\n\tconst resetSessionState = (ctx: ExtensionContext) => {\n\t\tstate.baseCwd = ctx.cwd;\n\t\tstate.currentSessionId = resolveCurrentSessionId(ctx.sessionManager);\n\t\tstate.subagentSpawns = { sessionId: state.currentSessionId, count: 0 };\n\t\t// Set PI_SUBAGENT_PARENT_SESSION for permission-system forwarding.\n\t\t// Only set in the root session (the interactive UI session), not in\n\t\t// child subagent processes — children inherit the parent's value\n\t\t// through the process environment at spawn time and must not overwrite\n\t\t// it with their own session identity.\n\t\tif (!process.env[SUBAGENT_CHILD_ENV]) {\n\t\t\tconst sessionId = ctx.sessionManager.getSessionId();\n\t\t\tif (sessionId) {\n\t\t\t\tprocess.env[SUBAGENT_PARENT_SESSION_ENV] = sessionId;\n\t\t\t}\n\t\t}\n\t\tstate.lastUiContext = ctx;\n\t\tcleanupSessionArtifacts(ctx);\n\t\tclearPendingForegroundControlNotices(state);\n\t\tresetJobs(ctx);\n\t\trestoreActiveJobs(ctx);\n\t\tscheduledRunManager.bindSession(ctx);\n\t\trestoreSlashFinalSnapshots(ctx.sessionManager.getEntries());\n\t\tprimeExistingResults();\n\t};\n\n\tpi.on(\"session_start\", (_event, ctx) => {\n\t\tresetSessionState(ctx);\n\t\trpcBridge.emitReady(ctx);\n\t\tsupervisorChannel.start();\n\t});\n\n\tpi.on(\"session_shutdown\", () => {\n\t\tdelete process.env[SUBAGENT_PARENT_SESSION_ENV];\n\t\tfor (const unsubscribe of eventUnsubscribes) {\n\t\t\ttry {\n\t\t\t\tunsubscribe();\n\t\t\t} catch {\n\t\t\t\t// Best effort cleanup during shutdown.\n\t\t\t}\n\t\t}\n\t\tif (globalStore[eventUnsubscribeStoreKey] === eventUnsubscribes) {\n\t\t\tdelete globalStore[eventUnsubscribeStoreKey];\n\t\t}\n\t\tstopResultWatcher();\n\t\tscheduledRunManager.stop();\n\t\tif (state.poller) clearInterval(state.poller);\n\t\tstate.poller = null;\n\t\tclearPendingForegroundControlNotices(state);\n\t\tfor (const timer of state.cleanupTimers.values()) {\n\t\t\tclearTimeout(timer);\n\t\t}\n\t\tstate.cleanupTimers.clear();\n\t\tstate.asyncJobs.clear();\n\t\tclearSlashSnapshots();\n\t\tslashBridge.cancelAll();\n\t\tslashBridge.dispose();\n\t\tpromptTemplateBridge.cancelAll();\n\t\tpromptTemplateBridge.dispose();\n\t\tsupervisorChannel.dispose();\n\t\tif (globalStore[runtimeCleanupStoreKey] === runtimeCleanup) {\n\t\t\tdelete globalStore[runtimeCleanupStoreKey];\n\t\t}\n\t\ttry {\n\t\t\tif (state.lastUiContext?.hasUI) {\n\t\t\t\tstate.lastUiContext.ui.setWidget(WIDGET_KEY, undefined);\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tif (!isStaleExtensionContextError(error)) throw error;\n\t\t}\n\t});\n}\n"]}
@@ -211,7 +211,7 @@ export default function registerSubagentExtension(pi) {
211
211
  const state = {
212
212
  baseCwd: "",
213
213
  currentSessionId: null,
214
- subagentInProgress: false,
214
+ foregroundSubagentInFlight: 0,
215
215
  subagentSpawns: { sessionId: null, count: 0 },
216
216
  asyncJobs: new Map(),
217
217
  fleetJobs: new Map(),
@@ -367,9 +367,8 @@ export default function registerSubagentExtension(pi) {
367
367
  label: "Subagent",
368
368
  description: buildSubagentToolDescription(config),
369
369
  parameters: SubagentParams,
370
- // lunr: sequential so multiple subagent calls in one turn run one-at-a-time
371
- // instead of tripping the subagentInProgress guard (parallel mode uses the tasks array).
372
- executionMode: "sequential",
370
+ // lunr: same-turn SINGLE calls overlap; sequential work uses chain.
371
+ executionMode: "parallel",
373
372
  prepareArguments(args) {
374
373
  // Run friendly chain validation before pi-ai's raw TypeBox schema check
375
374
  // so the model sees which property is disallowed, what is allowed, and a