@posthog/wizard 2.65.0 → 2.67.0

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 (119) hide show
  1. package/dist/{add-mcp-server-to-clients-ArtlMX9-.js → add-mcp-server-to-clients-DHTRbyJX.js} +228 -64
  2. package/dist/add-mcp-server-to-clients-DHTRbyJX.js.map +1 -0
  3. package/dist/{agent-interface-ORzGEFNM.js → agent-interface-tM_b0l__.js} +458 -76
  4. package/dist/agent-interface-tM_b0l__.js.map +1 -0
  5. package/dist/{agent-prompt-loader-D3w6sc34.js → agent-prompt-loader-BSMh5bfD.js} +5 -5
  6. package/dist/{agent-prompt-loader-D3w6sc34.js.map → agent-prompt-loader-BSMh5bfD.js.map} +1 -1
  7. package/dist/{agent-runner-Ca-e2TOQ.js → agent-runner-DD65OMkB.js} +153 -71
  8. package/dist/agent-runner-DD65OMkB.js.map +1 -0
  9. package/dist/analytics-B4Igh5nv.js +2 -0
  10. package/dist/{analytics-zBrIVjMV.js → analytics-CtIYzkK4.js} +2 -2
  11. package/dist/{analytics-zBrIVjMV.js.map → analytics-CtIYzkK4.js.map} +1 -1
  12. package/dist/{api-Wgb7AhrP.js → api-BJft9v6K.js} +3 -3
  13. package/dist/{api-Wgb7AhrP.js.map → api-BJft9v6K.js.map} +1 -1
  14. package/dist/bin.js +245 -102
  15. package/dist/bin.js.map +1 -1
  16. package/dist/{bounded-fs-DqsVmh4r.js → bounded-fs-BWqoFQvQ.js} +3 -3
  17. package/dist/{bounded-fs-DqsVmh4r.js.map → bounded-fs-BWqoFQvQ.js.map} +1 -1
  18. package/dist/{ci-install-D1QqmJIc.js → ci-install-BXGnVI7T.js} +18 -9
  19. package/dist/ci-install-BXGnVI7T.js.map +1 -0
  20. package/dist/{debug-Cm1hvkVy.js → debug-BsHD83Vy.js} +4 -4
  21. package/dist/debug-BsHD83Vy.js.map +1 -0
  22. package/dist/{debug-CtQ13sCW.js → debug-mO3yg4Z7.js} +1 -1
  23. package/dist/{defaults-BHhXrX9F.js → defaults-DI642M3n.js} +1 -1
  24. package/dist/{defaults-BHhXrX9F.js.map → defaults-DI642M3n.js.map} +1 -1
  25. package/dist/{env-api-key-CdI2XZIz.js → env-api-key-DS0ghlcO.js} +2 -2
  26. package/dist/{env-api-key-CdI2XZIz.js.map → env-api-key-DS0ghlcO.js.map} +1 -1
  27. package/dist/{environment-BhFvfkQg.js → environment-C7CKGIOE.js} +4 -4
  28. package/dist/{environment-BhFvfkQg.js.map → environment-C7CKGIOE.js.map} +1 -1
  29. package/dist/errors-BHervYGs.js +156 -0
  30. package/dist/errors-BHervYGs.js.map +1 -0
  31. package/dist/{file-watcher-Dciz09Rr.js → file-watcher-D1bdo9mq.js} +2 -2
  32. package/dist/{file-watcher-Dciz09Rr.js.map → file-watcher-D1bdo9mq.js.map} +1 -1
  33. package/dist/{headless-ui-B4FnKm11.js → headless-ui-6QuByWZ_.js} +2 -2
  34. package/dist/{headless-ui-B4FnKm11.js.map → headless-ui-6QuByWZ_.js.map} +1 -1
  35. package/dist/{interactive-DxHFVYr2.js → interactive-DUpW84WU.js} +2 -2
  36. package/dist/{interactive-DxHFVYr2.js.map → interactive-DUpW84WU.js.map} +1 -1
  37. package/dist/{local-dev-BN6NKQ0B.js → local-dev-DPZiIPCK.js} +1 -1
  38. package/dist/{local-dev-BN6NKQ0B.js.map → local-dev-DPZiIPCK.js.map} +1 -1
  39. package/dist/{mcp-t0UxAPur.js → mcp-CHo9h-WV.js} +2 -2
  40. package/dist/{mcp-t0UxAPur.js.map → mcp-CHo9h-WV.js.map} +1 -1
  41. package/dist/mcp-install-smoke-test.ts +477 -0
  42. package/dist/{mcp-prompt-streaming-ro98iUDc.js → mcp-prompt-streaming-CHRPxxd0.js} +4 -4
  43. package/dist/{mcp-prompt-streaming-ro98iUDc.js.map → mcp-prompt-streaming-CHRPxxd0.js.map} +1 -1
  44. package/dist/{non-interactive-BA2L_jDT.js → non-interactive-Cr-chffe.js} +7 -2
  45. package/dist/non-interactive-Cr-chffe.js.map +1 -0
  46. package/dist/{orchestrator-tools-vZmNsHy9.js → orchestrator-tools-Tx18_TMb.js} +3 -3
  47. package/dist/{orchestrator-tools-vZmNsHy9.js.map → orchestrator-tools-Tx18_TMb.js.map} +1 -1
  48. package/dist/{package-json-D0DCWpe5.js → package-json-GEU2VZlS.js} +1 -1
  49. package/dist/{package-json-D0DCWpe5.js.map → package-json-GEU2VZlS.js.map} +1 -1
  50. package/dist/{package-manager-DgqJdAQk.js → package-manager-CLfc31yf.js} +4 -4
  51. package/dist/package-manager-CLfc31yf.js.map +1 -0
  52. package/dist/{pi-nwrCARRp.js → pi-RlnYUHdb.js} +12 -11
  53. package/dist/{pi-nwrCARRp.js.map → pi-RlnYUHdb.js.map} +1 -1
  54. package/dist/{playground-xkEke_h0.js → playground-DQ633rje.js} +7 -7
  55. package/dist/{playground-xkEke_h0.js.map → playground-DQ633rje.js.map} +1 -1
  56. package/dist/{posthog-9To56cSd.js → posthog-DSn9bIZc.js} +2 -2
  57. package/dist/{posthog-9To56cSd.js.map → posthog-DSn9bIZc.js.map} +1 -1
  58. package/dist/{posthog-integration-CoPvN6YZ.js → posthog-integration-DFfBRGOC.js} +667 -37
  59. package/dist/posthog-integration-DFfBRGOC.js.map +1 -0
  60. package/dist/{provisioning-BYFSHSHY.js → provisioning-Cvy_vQHV.js} +3 -3
  61. package/dist/{provisioning-BYFSHSHY.js.map → provisioning-Cvy_vQHV.js.map} +1 -1
  62. package/dist/{queue-tools-BREbgGSC.js → queue-tools-BC9HxOV6.js} +33 -7
  63. package/dist/queue-tools-BC9HxOV6.js.map +1 -0
  64. package/dist/{registry-DGJQ_8QI.js → registry-DCAElOyj.js} +81 -81
  65. package/dist/{registry-DGJQ_8QI.js.map → registry-DCAElOyj.js.map} +1 -1
  66. package/dist/{security-wc-naGq5.js → security-CuSX3XVo.js} +21 -9
  67. package/dist/security-CuSX3XVo.js.map +1 -0
  68. package/dist/{setup-utils-Cz0wPLsQ.js → setup-utils-D1tv0v_R.js} +13 -13
  69. package/dist/{setup-utils-Cz0wPLsQ.js.map → setup-utils-D1tv0v_R.js.map} +1 -1
  70. package/dist/{start-tui-DDW9uVnD.js → start-tui-B5YfmqNr.js} +57 -25
  71. package/dist/start-tui-B5YfmqNr.js.map +1 -0
  72. package/dist/{steps-CZI0B24_.js → steps-CLaSMwfR.js} +7 -7
  73. package/dist/{steps-CZI0B24_.js.map → steps-CLaSMwfR.js.map} +1 -1
  74. package/dist/{store-xMkU8xAa.js → store-DzEgsAPN.js} +7 -6
  75. package/dist/{store-xMkU8xAa.js.map → store-DzEgsAPN.js.map} +1 -1
  76. package/dist/{subagent-BztacyoV.js → subagent-BUV6aD7p.js} +2 -2
  77. package/dist/{subagent-BztacyoV.js.map → subagent-BUV6aD7p.js.map} +1 -1
  78. package/dist/{task-CJ7x3McC.js → task-GnWId75q.js} +13 -12
  79. package/dist/{task-CJ7x3McC.js.map → task-GnWId75q.js.map} +1 -1
  80. package/dist/{task-stream-BK7JQ27G.js → task-stream-DSzvwJrD.js} +16 -9
  81. package/dist/task-stream-DSzvwJrD.js.map +1 -0
  82. package/dist/{tasks-FGzp_zBd.js → tasks-BO0W8ocx.js} +2 -2
  83. package/dist/{tasks-FGzp_zBd.js.map → tasks-BO0W8ocx.js.map} +1 -1
  84. package/dist/{telemetry-By56gAtF.js → telemetry-Vi9dWddJ.js} +3 -3
  85. package/dist/{telemetry-By56gAtF.js.map → telemetry-Vi9dWddJ.js.map} +1 -1
  86. package/dist/{terminal-C_rmkaku.js → terminal-B5wZK7aS.js} +73 -80
  87. package/dist/terminal-B5wZK7aS.js.map +1 -0
  88. package/dist/{tools-BpCJgMez.js → tools-C9cNluqc.js} +54 -47
  89. package/dist/tools-C9cNluqc.js.map +1 -0
  90. package/dist/{wizard-abort-BlDla_Jd.js → wizard-abort-BsNbguJt.js} +1 -1
  91. package/dist/wizard-abort-CGv3lVxw.js +71 -0
  92. package/dist/wizard-abort-CGv3lVxw.js.map +1 -0
  93. package/dist/{wizard-session-Bn6dO1gE.js → wizard-session-Cex1BiXs.js} +3 -2
  94. package/dist/wizard-session-Cex1BiXs.js.map +1 -0
  95. package/dist/{wizard-session-UxA_A0cA.js → wizard-session-DHbUcjI7.js} +1 -1
  96. package/dist/yara-hooks-C3Vm2Gow.js +2 -0
  97. package/dist/{yara-hooks-BrisBebM.js → yara-hooks-DZIVzhA6.js} +57 -8
  98. package/dist/yara-hooks-DZIVzhA6.js.map +1 -0
  99. package/package.json +2 -1
  100. package/dist/add-mcp-server-to-clients-ArtlMX9-.js.map +0 -1
  101. package/dist/agent-interface-ORzGEFNM.js.map +0 -1
  102. package/dist/agent-runner-Ca-e2TOQ.js.map +0 -1
  103. package/dist/analytics-C4j6grao.js +0 -2
  104. package/dist/ci-install-D1QqmJIc.js.map +0 -1
  105. package/dist/debug-Cm1hvkVy.js.map +0 -1
  106. package/dist/non-interactive-BA2L_jDT.js.map +0 -1
  107. package/dist/package-manager-DgqJdAQk.js.map +0 -1
  108. package/dist/posthog-integration-CoPvN6YZ.js.map +0 -1
  109. package/dist/queue-tools-BREbgGSC.js.map +0 -1
  110. package/dist/security-wc-naGq5.js.map +0 -1
  111. package/dist/start-tui-DDW9uVnD.js.map +0 -1
  112. package/dist/task-stream-BK7JQ27G.js.map +0 -1
  113. package/dist/terminal-C_rmkaku.js.map +0 -1
  114. package/dist/tools-BpCJgMez.js.map +0 -1
  115. package/dist/wizard-abort-CzwID162.js +0 -49
  116. package/dist/wizard-abort-CzwID162.js.map +0 -1
  117. package/dist/wizard-session-Bn6dO1gE.js.map +0 -1
  118. package/dist/yara-hooks-BrisBebM.js.map +0 -1
  119. package/dist/yara-hooks-D2B7Fd9G.js +0 -2
@@ -1 +0,0 @@
1
- {"version":3,"file":"queue-tools-BREbgGSC.js","names":["path"],"sources":["../src/lib/agent/runner/switchboard/models.ts","../src/utils/atomic-ledger.ts","../src/lib/agent/runner/sequence/orchestrator/queue.ts","../src/lib/agent/runner/sequence/orchestrator/queue-tools.ts"],"sourcesContent":["/**\n * Model capabilities — the traits a harness needs that a bare gateway model id\n * doesn't carry. The switchboard resolves *which* model (harness.ts); this\n * resolves *what the model can do*, so a harness never hardcodes it.\n *\n * `reasoning` gates whether a harness requests reasoning at all; `thinkingLevel`\n * sets how much. Non-reasoning openai-completions models reject the reasoning\n * params (gpt-4o → gateway `UnsupportedParamsError` → the pi run no-ops), and\n * effort trades speed for depth (flagship gpt-5 at high effort runs long). Both\n * are silent when wrong, so they live here as one configurable table.\n */\nimport {\n DEFAULT_AGENT_MODEL,\n SONNET_5_MODEL,\n OPUS_MODEL,\n HAIKU_MODEL,\n GPT5_6_LUNA_MODEL,\n GPT5_6_SOL_MODEL,\n GPT5_6_TERRA_MODEL,\n HAIKU_TRIAGE_MODEL,\n Harness,\n} from '@lib/constants';\n\n/** Reasoning effort. pi maps it to `reasoning_effort` for openai-completions. */\nconst THINKING_LEVELS = [\n 'off',\n 'minimal',\n 'low',\n 'medium',\n 'high',\n 'xhigh',\n] as const;\nexport type ThinkingLevel = (typeof THINKING_LEVELS)[number];\n\n/** Whether a value (e.g. remote prompt frontmatter) names a valid effort. */\nexport function isThinkingLevel(value: unknown): value is ThinkingLevel {\n return (THINKING_LEVELS as readonly unknown[]).includes(value);\n}\n\n/**\n * An effort *override* is always a positive level — `'off'` is a model trait\n * (`reasoning: false`), not something a flag may force onto a reasoning model.\n */\nexport type EffortLevel = Exclude<ThinkingLevel, 'off'>;\n\nexport interface ModelCapabilities {\n /** Model supports reasoning; safe to request reasoning effort. */\n reasoning: boolean;\n /** Effort to request when reasoning. Omit for the harness/provider default. */\n thinkingLevel?: ThinkingLevel;\n}\n\n/** Explicit per-model traits. Anything absent falls back to `defaultCaps`. */\nexport const MODEL_CAPABILITIES: Record<string, ModelCapabilities> = {\n [DEFAULT_AGENT_MODEL]: { reasoning: true }, // claude-sonnet-4-6\n [SONNET_5_MODEL]: { reasoning: true },\n [OPUS_MODEL]: { reasoning: true },\n [HAIKU_MODEL]: { reasoning: true },\n // The openai 5.6 line; all reasoning models, so they must opt in past the\n // openai-completions default (reasoning off). Luna stays low for cheap,\n // short-context mechanical work; terra runs medium as the sonnet-tier parallel\n // — enough reasoning depth for the judgment tasks without high's latency blowup.\n [GPT5_6_LUNA_MODEL]: { reasoning: true, thinkingLevel: 'low' },\n [GPT5_6_TERRA_MODEL]: { reasoning: true, thinkingLevel: 'medium' },\n [GPT5_6_SOL_MODEL]: { reasoning: true, thinkingLevel: 'low' },\n};\n\n/** The only models the wizard may dispatch on. */\nexport const VALID_MODELS: ReadonlySet<string> = new Set(\n Object.keys(MODEL_CAPABILITIES),\n);\n\n/** Whether the value is an allow-listed model. */\nexport function isValidModel(model: unknown): model is string {\n return typeof model === 'string' && VALID_MODELS.has(model);\n}\n\n/** First allow-listed candidate, else throw so a bad id fails loud. */\nexport function requireKnownModel(\n ...candidates: (string | undefined)[]\n): string {\n for (const c of candidates) if (isValidModel(c)) return c;\n throw new Error(\n `No valid model to dispatch on (tried ${JSON.stringify(candidates)}). ` +\n `Allowed: ${[...VALID_MODELS].join(', ')}.`,\n );\n}\n\n/**\n * Default for a model not in the table: reasoning on for anthropic-messages\n * models, off for openai-completions — the non-reasoning openai models reject\n * reasoning effort, so off is the safe default (a reasoning openai model opts\n * back in via the table above). Transport is inferred the same way the pi\n * harness infers it (`openai/` prefix → openai-completions).\n */\nfunction defaultCaps(modelId: string): ModelCapabilities {\n return { reasoning: !modelId.startsWith('openai/') };\n}\n\n/**\n * Scan-triage classifier per harness: the cheapest tier of the line that harness\n * already speaks. Undated ids on purpose — triage is a boolean classifier, so it\n * should follow the current release rather than pin one, and these are not\n * dispatchable agent models (absent from MODEL_CAPABILITIES by design).\n */\nexport const TRIAGE_MODELS: Record<Harness, string> = {\n [Harness.anthropic]: HAIKU_TRIAGE_MODEL,\n [Harness.pi]: GPT5_6_LUNA_MODEL,\n};\n\nexport function triageModelFor(harness: Harness): string {\n return TRIAGE_MODELS[harness];\n}\n\n/**\n * Capabilities for a gateway model id, table override then transport default.\n * `effortOverride` is a switchboard-resolved effort (e.g. from a pi effort\n * flag); it applies only when the model reasons at all — a non-reasoning model\n * rejects reasoning effort, so the override is dropped.\n */\nexport function modelCapabilities(\n modelId: string,\n effortOverride?: EffortLevel,\n): ModelCapabilities {\n const caps = MODEL_CAPABILITIES[modelId] ?? defaultCaps(modelId);\n if (caps.reasoning && effortOverride) {\n return { ...caps, thinkingLevel: effortOverride };\n }\n return caps;\n}\n","/**\n * Small shared primitives for on-disk ledgers: an atomic JSON writer and a\n * single-chain async mutex. Used by the audit tools and by the orchestrator\n * queue. Lifted here so both share one implementation.\n */\nimport * as fs from 'fs';\n\n/**\n * Atomically write JSON: write to a `.tmp` file then rename over the target. The\n * rename bumps the file's mtime in one step, which is what a file watcher polls.\n */\nexport function writeJsonAtomic(targetPath: string, data: unknown): void {\n const tmpPath = `${targetPath}.tmp`;\n fs.writeFileSync(tmpPath, JSON.stringify(data, null, 2), 'utf8');\n fs.renameSync(tmpPath, targetPath);\n}\n\n/**\n * A single async mutex. Serializes read-modify-write cycles so concurrent callers\n * (parallel task agents, audit tool calls) never interleave a mutation.\n */\nexport function makeMutex() {\n let chain: Promise<unknown> = Promise.resolve();\n return async function run<T>(fn: () => Promise<T> | T): Promise<T> {\n const next = chain.then(() => fn());\n chain = next.catch(() => undefined);\n return next;\n };\n}\n","/**\n * The orchestrator task queue.\n *\n * In memory, synchronous, single-owner: one Node process drives the run, so\n * there is no locking. The queue imposes no execution policy — `nextRunnable`\n * returns every pending task whose dependencies are satisfied, and how many of\n * those run at once is decided by the task graph, not the queue.\n *\n * Every transition rewrites `<installDir>/.posthog-wizard-cache/queue.json`, a\n * small file holding the whole queue, handoffs included. It is the run's log\n * and the report's source. The whole cache folder is run-scoped and wiped when\n * the run ends.\n */\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport { randomUUID } from 'crypto';\nimport { writeJsonAtomic } from '@utils/atomic-ledger';\nimport { analytics } from '@utils/analytics';\n\nexport const TaskStatus = {\n Pending: 'pending',\n Running: 'running',\n Done: 'done',\n Skipped: 'not needed',\n Failed: 'failed',\n} as const;\n\nexport type TaskStatus = (typeof TaskStatus)[keyof typeof TaskStatus];\n\nexport interface QueuedTask {\n id: string;\n type: string;\n /** Human-readable label for the TUI, set by the enqueuing agent. */\n label?: string;\n status: TaskStatus;\n /**\n * Ids of tasks that must finish before this one runs. Ids are generated at\n * enqueue, so a task can only depend on tasks created before it — the graph is\n * a DAG by construction, cycles cannot form. Unknown ids are rejected by the\n * enqueue_task guard.\n *\n * One sanctioned exception: {@link QueueStore.addDependencies} adds edges to a\n * still-pending task, for a runner-seeded task that was queued before the\n * planner ran and so could not name its dependencies then. It is additive,\n * pending-only, and cycle-checked per edge, so the DAG property above still\n * holds. Nothing else mutates this field.\n */\n dependsOn: string[];\n inputs: Record<string, unknown>;\n model?: string;\n attempts: number;\n maxAttempts: number;\n /** The structured handoff the task reported on completion. */\n handoff?: TaskHandoff;\n /** 'orchestrator' for seeded tasks, or the id of the task that enqueued this one. */\n enqueuedBy: string;\n /** Wizard-seeded only: terminal failure unblocks dependents and never fails the run. */\n optional?: boolean;\n createdAt: string;\n startedAt?: string;\n finishedAt?: string;\n error?: { type: string; message: string };\n}\n\nexport interface QueueFile {\n version: 1;\n runId: string;\n tasks: QueuedTask[];\n}\n\n/** The structured handoff a task leaves for the next agent. */\nexport interface TaskHandoff {\n goals: string;\n did: string;\n forNextAgent: string;\n filesTouched?: string[];\n /** How the agent knows it worked — what it ran or observed. */\n evidence?: string;\n /** What the agent assumed about the app and could not verify. */\n assumptions?: string;\n /** A one-line summary of any unresolved conflict, surfaced in the outro. */\n conflict?: string;\n /** A finished section for the run's report, written by the task that owns the subject. */\n reportSection?: string;\n}\n\nexport interface EnqueueInput {\n type: string;\n label?: string;\n inputs?: Record<string, unknown>;\n dependsOn?: string[];\n model?: string;\n maxAttempts?: number;\n enqueuedBy?: string;\n optional?: boolean;\n}\n\nexport const QUEUE_DIR_NAME = '.posthog-wizard-cache';\nconst DEFAULT_MAX_ATTEMPTS = 2;\n\nfunction nowIso(): string {\n return new Date().toISOString();\n}\n\n/** Dropped in the cache folder so an orphaned copy explains itself. */\nconst DELETE_ME_FILE = '.DELETE-ME.md';\nconst DELETE_ME_BODY = `# Safe to delete\n\nThis folder contains run artifacts from the PostHog Wizard. This should have\nbeen deleted if the Wizard has finished running. If this wasn't deleted for\nsome reason, you can safely delete the entire \\`${QUEUE_DIR_NAME}/\\` folder.\n`;\n\n/** Every queue transition, in the order it is reflected. */\nexport type TransitionEvent =\n | 'enqueue'\n | 'start'\n | 'complete'\n | 'skip'\n | 'fail'\n | 'requeue';\n\nexport interface QueueStoreOptions {\n /**\n * Called on every transition with the task's post-transition state. The\n * runner uses it for telemetry; the store itself stays analytics-free.\n * Listener errors are reported but cannot break a transition.\n */\n onTransition?: (event: TransitionEvent, task: QueuedTask) => void;\n}\n\nexport class QueueStore {\n private tasks: QueuedTask[] = [];\n private readonly onTransition?: (\n event: TransitionEvent,\n task: QueuedTask,\n ) => void;\n\n readonly runId: string;\n readonly queuePath: string;\n\n constructor(installDir: string, runId: string, opts?: QueueStoreOptions) {\n this.onTransition = opts?.onTransition;\n this.runId = runId;\n const dir = path.join(installDir, QUEUE_DIR_NAME);\n this.queuePath = path.join(dir, 'queue.json');\n fs.mkdirSync(dir, { recursive: true });\n fs.writeFileSync(path.join(dir, DELETE_ME_FILE), DELETE_ME_BODY);\n }\n\n // ── Reads ───────────────────────────────────────────────────────────\n\n list(): readonly QueuedTask[] {\n return this.tasks;\n }\n\n get(id: string): QueuedTask | undefined {\n return this.tasks.find((t) => t.id === id);\n }\n\n /**\n * Every pending task whose dependencies are all satisfied (`done` or\n * `skipped`). A skipped dependency does not block downstream work.\n */\n nextRunnable(): QueuedTask[] {\n // A TERMINALLY failed optional dep satisfies like skipped; retryable failure still blocks.\n const doneIds = new Set(\n this.tasks\n .filter(\n (t) =>\n t.status === TaskStatus.Done ||\n t.status === TaskStatus.Skipped ||\n (t.status === TaskStatus.Failed &&\n t.optional === true &&\n t.attempts >= t.maxAttempts),\n )\n .map((t) => t.id),\n );\n return this.tasks.filter(\n (t) =>\n t.status === TaskStatus.Pending &&\n t.dependsOn.every((d) => doneIds.has(d)),\n );\n }\n\n /**\n * True when no task is running and none can be started. Either everything\n * is terminal, or the only pending tasks are blocked by a failed dependency.\n */\n isDrained(): boolean {\n if (this.tasks.some((t) => t.status === TaskStatus.Running)) return false;\n return this.nextRunnable().length === 0;\n }\n\n summary(): Record<TaskStatus, number> & { total: number } {\n const counts: Record<TaskStatus, number> = {\n [TaskStatus.Pending]: 0,\n [TaskStatus.Running]: 0,\n [TaskStatus.Done]: 0,\n [TaskStatus.Skipped]: 0,\n [TaskStatus.Failed]: 0,\n };\n for (const t of this.tasks) counts[t.status] += 1;\n return { ...counts, total: this.tasks.length };\n }\n\n readHandoff(id: string): TaskHandoff | null {\n return this.get(id)?.handoff ?? null;\n }\n\n /** Handoffs of completed tasks of a given type, oldest first. */\n readHandoffsByType(type: string): TaskHandoff[] {\n return this.tasks\n .filter((t) => t.type === type && t.handoff)\n .map((t) => t.handoff as TaskHandoff);\n }\n\n // ── Transitions (each one reflected to queue.json) ──────────────────\n\n enqueue(input: EnqueueInput): QueuedTask {\n const task: QueuedTask = {\n id: randomUUID(),\n type: input.type,\n label: input.label,\n status: TaskStatus.Pending,\n dependsOn: input.dependsOn ?? [],\n inputs: input.inputs ?? {},\n model: input.model,\n attempts: 0,\n maxAttempts: input.maxAttempts ?? DEFAULT_MAX_ATTEMPTS,\n enqueuedBy: input.enqueuedBy ?? 'orchestrator',\n optional: input.optional,\n createdAt: nowIso(),\n };\n this.tasks.push(task);\n this.reflect();\n this.notify('enqueue', task);\n return task;\n }\n\n /**\n * Add dependency edges to a pending task — the one sanctioned exception to\n * `dependsOn` being immutable (see the field's doc).\n *\n * A runner-seeded task is enqueued before the planner runs, so the tasks it\n * should wait for do not exist yet and it cannot name them. This closes that\n * gap once, between planning and the drain.\n *\n * Three rules keep the graph a DAG: additive only (never removes an edge),\n * pending only (a task that already started keeps the graph it ran under), and\n * every edge cycle-checked — a dep that already reaches this task through its\n * own chain is refused. Refusals come back in the result rather than throwing:\n * one bad edge is a bug worth reporting, not a reason to end a run that is\n * otherwise fine.\n */\n addDependencies(\n id: string,\n depIds: readonly string[],\n ): { added: string[]; refused: string[] } {\n const task = this.require(id);\n if (task.status !== TaskStatus.Pending) {\n return { added: [], refused: [...depIds] };\n }\n\n const added: string[] = [];\n const refused: string[] = [];\n for (const depId of depIds) {\n // Already an edge, or a self-loop: nothing to do and nothing wrong.\n if (depId === id || task.dependsOn.includes(depId)) continue;\n // Checked against the graph as it stands, so two edges added in one call\n // cannot together form a cycle the first check missed.\n if (!this.get(depId) || this.reaches(depId, id)) {\n refused.push(depId);\n continue;\n }\n task.dependsOn.push(depId);\n added.push(depId);\n }\n if (added.length > 0) this.reflect();\n return { added, refused };\n }\n\n start(id: string): QueuedTask {\n const t = this.require(id);\n t.status = TaskStatus.Running;\n t.startedAt = nowIso();\n t.attempts += 1;\n this.reflect();\n this.notify('start', t);\n return t;\n }\n\n complete(id: string, handoff?: TaskHandoff): QueuedTask {\n return this.finish(id, TaskStatus.Done, handoff);\n }\n\n /** Terminal: the agent could not do the task. Not done, not failed. */\n skip(id: string, handoff?: TaskHandoff): QueuedTask {\n return this.finish(id, TaskStatus.Skipped, handoff);\n }\n\n fail(\n id: string,\n error: { type: string; message: string },\n handoff?: TaskHandoff,\n ): QueuedTask {\n const t = this.require(id);\n t.error = error;\n return this.finish(id, TaskStatus.Failed, handoff);\n }\n\n /** Put a failed/running task back to pending for a retry within the run. */\n requeue(id: string): QueuedTask {\n const t = this.require(id);\n t.status = TaskStatus.Pending;\n t.startedAt = undefined;\n t.finishedAt = undefined;\n this.reflect();\n this.notify('requeue', t);\n return t;\n }\n\n // ── Internals ───────────────────────────────────────────────────────\n\n private finish(\n id: string,\n status: 'done' | 'not needed' | 'failed',\n handoff?: TaskHandoff,\n ): QueuedTask {\n const t = this.require(id);\n if (handoff) t.handoff = handoff;\n t.status = status;\n t.finishedAt = nowIso();\n this.reflect();\n this.notify(\n status === TaskStatus.Done\n ? 'complete'\n : status === TaskStatus.Skipped\n ? 'skip'\n : 'fail',\n t,\n );\n return t;\n }\n\n private reflect(): void {\n const file: QueueFile = {\n version: 1,\n runId: this.runId,\n tasks: this.tasks,\n };\n writeJsonAtomic(this.queuePath, file);\n }\n\n private notify(event: TransitionEvent, task: QueuedTask): void {\n try {\n this.onTransition?.(event, task);\n } catch (error) {\n // A listener must never break a transition, but its failure is a bug.\n analytics.captureException(\n error instanceof Error ? error : new Error(String(error)),\n { step: 'orchestrator_queue_listener', event },\n );\n }\n }\n\n /** Whether `fromId` reaches `targetId` by following dependsOn edges. */\n private reaches(fromId: string, targetId: string): boolean {\n const seen = new Set<string>();\n const stack = [fromId];\n while (stack.length > 0) {\n const current = stack.pop() as string;\n if (current === targetId) return true;\n if (seen.has(current)) continue;\n seen.add(current);\n stack.push(...(this.get(current)?.dependsOn ?? []));\n }\n return false;\n }\n\n private require(id: string): QueuedTask {\n const t = this.get(id);\n if (!t) throw new Error(`No task ${id} in the queue`);\n return t;\n }\n}\n","/**\n * Orchestrator MCP tools, registered into the existing `wizard-tools` server when\n * a queue is present. They let the orchestrator agent and task agents grow the\n * queue, report completion with a structured handoff, and read prior handoffs.\n *\n * The guard logic and the apply functions are plain, exported, and unit-tested.\n * `buildOrchestratorTools` wraps them in the SDK `tool()` shape.\n */\nimport { z } from 'zod';\nimport { analytics } from '@utils/analytics';\nimport {\n isValidModel,\n VALID_MODELS,\n} from '@lib/agent/runner/switchboard/models';\nimport {\n TaskStatus,\n type QueueStore,\n type QueuedTask,\n type TaskHandoff,\n} from './queue';\n\n/** The per-task remark ask, shared by both harnesses' complete_task schemas. */\nexport const REMARK_ASK =\n 'What information or guidance would have been useful to have in the integration prompt or documentation for this task — specifically anything that would have prevented tool failures, erroneous edits, or other wasted turns.';\n\nexport interface OrchestratorToolsContext {\n store: QueueStore;\n /** Task types the registry knows about. enqueue_task rejects anything else. */\n validTypes: readonly string[];\n /**\n * Types marked `sink: true` — the ones that run last and must therefore\n * depend, transitively, on every other task in the queue. Enqueuing one with\n * an incomplete closure is rejected, so a task the runner seeded before the\n * planner ran can never be left un-awaited.\n */\n sinkTypes?: readonly string[];\n /**\n * Types the wizard queues itself, before the planner runs. They are deferred\n * to the end of the drain and may stop to ask the user for input, so the edge\n * to them is one-way: the sink depends on them, nothing else may. See\n * {@link seededDepViolations}.\n */\n runnerSeededTypes?: readonly string[];\n /**\n * The id of the task this tool server is bound to. Each task agent gets its\n * own wizard-tools server, so attribution holds when independent tasks run\n * in parallel. Absent for the seed, which is not a task.\n */\n currentTaskId?: string;\n}\n\nexport interface EnqueueArgs {\n type: string;\n label?: string;\n inputs?: Record<string, unknown>;\n dependsOn?: string[];\n model?: string;\n reason: string;\n}\n\nexport type GuardResult =\n | { ok: true }\n | { ok: false; guard: string; message: string };\n\nfunction stableStringify(value: unknown): string {\n if (value === null || typeof value !== 'object') return JSON.stringify(value);\n if (Array.isArray(value)) return `[${value.map(stableStringify).join(',')}]`;\n const entries = Object.entries(value as Record<string, unknown>).sort(\n ([a], [b]) => a.localeCompare(b),\n );\n return `{${entries\n .map(([k, v]) => `${JSON.stringify(k)}:${stableStringify(v)}`)\n .join(',')}}`;\n}\n\nfunction dedupKey(type: string, inputs: Record<string, unknown>): string {\n return `${type}::${stableStringify(inputs)}`;\n}\n\n/**\n * A backstop on total queue size. Tasks can enqueue tasks, so a misbehaving\n * type could grow the queue without bound. Keeping the graph small is the job\n * of good agent and skill design, not this number — it only stops a runaway.\n * The real flow is ~9 tasks, so this sits well clear of it.\n */\nconst MAX_QUEUE_TASKS = 30;\n\n/** Every task id reachable from `roots` through dependsOn edges, roots included. */\nexport function dependencyClosure(\n store: QueueStore,\n roots: readonly string[],\n): Set<string> {\n const seen = new Set<string>();\n const visit = (id: string): void => {\n if (seen.has(id)) return;\n seen.add(id);\n for (const dep of store.get(id)?.dependsOn ?? []) visit(dep);\n };\n for (const id of roots) visit(id);\n return seen;\n}\n\n/**\n * Tasks a sink would fail to wait for. A sink runs last by definition, so any\n * queued task outside its dependency closure is work whose handoff the sink\n * would miss — the exact failure mode of a task seeded before the planner ran.\n * Empty for a non-sink type, and for a sink that covers everything.\n */\nexport function uncoveredBySink(\n ctx: OrchestratorToolsContext,\n args: Pick<EnqueueArgs, 'type' | 'dependsOn'>,\n): QueuedTask[] {\n if (!ctx.sinkTypes?.includes(args.type)) return [];\n const covered = dependencyClosure(ctx.store, args.dependsOn ?? []);\n return ctx.store.list().filter((t) => !covered.has(t.id));\n}\n\n/**\n * Runner-seeded tasks a non-sink task would end up waiting for.\n *\n * The edge to a runner-seeded task is one-way. Such a task is deferred to the\n * end of the drain and may stop to ask the user for input, so anything that\n * waits on it inherits that wait — a coding step gated on someone typing a\n * database password. The sink is the sole exception: it runs last by\n * definition and must depend on every task, this one included.\n *\n * The whole closure is checked, not just direct dependencies, so the rule\n * cannot be reached through an intermediate hop.\n *\n * Empty for a sink, and for any task that does not reach a seeded task.\n */\nexport function seededDepViolations(\n ctx: OrchestratorToolsContext,\n args: Pick<EnqueueArgs, 'type' | 'dependsOn'>,\n): QueuedTask[] {\n const seeded = ctx.runnerSeededTypes ?? [];\n if (seeded.length === 0) return [];\n if (ctx.sinkTypes?.includes(args.type)) return [];\n const reached = dependencyClosure(ctx.store, args.dependsOn ?? []);\n return ctx.store\n .list()\n .filter((t) => reached.has(t.id) && seeded.includes(t.type));\n}\n\n/**\n * Validate an enqueue. Structural checks only — a real type, real dependencies,\n * a sink that waits for everything, no non-sink task waiting on a runner-seeded\n * one, not a literal duplicate, and not past the runaway backstop. How much\n * runs, and in what shape, is the task graph's business, not a knob's.\n */\nexport function checkEnqueueGuards(\n ctx: OrchestratorToolsContext,\n args: EnqueueArgs,\n): GuardResult {\n const tasks = ctx.store.list();\n\n if (tasks.length >= MAX_QUEUE_TASKS) {\n return {\n ok: false,\n guard: 'queue-full',\n message: `The queue already holds ${tasks.length} tasks (cap ${MAX_QUEUE_TASKS}). Refine the existing tasks rather than adding more.`,\n };\n }\n\n if (!ctx.validTypes.includes(args.type)) {\n return {\n ok: false,\n guard: 'unknown-type',\n message: `Unknown task type \"${\n args.type\n }\". Valid types: ${ctx.validTypes.join(', ')}.`,\n };\n }\n\n // Reject an agent pinning a task to a non-allow-listed model.\n if (args.model !== undefined && !isValidModel(args.model)) {\n return {\n ok: false,\n guard: 'invalid-model',\n message: `Model \"${\n args.model\n }\" is not allowed. Omit model to use the task default, or pick one of: ${[\n ...VALID_MODELS,\n ].join(', ')}.`,\n };\n }\n\n for (const dep of args.dependsOn ?? []) {\n if (!ctx.store.get(dep)) {\n return {\n ok: false,\n guard: 'unknown-dep',\n message: `Dependency \"${dep}\" is not a known task id.`,\n };\n }\n }\n\n const seededDeps = seededDepViolations(ctx, args);\n if (seededDeps.length > 0) {\n return {\n ok: false,\n guard: 'seeded-dep',\n message: `A \"${args.type}\" task must not wait for ${seededDeps\n .map((t) => `${t.type} (${t.id})`)\n .join(\n ', ',\n )} — directly or through its dependencies. The wizard placed those, runs them at the end, and they stop to ask the user for input, so anything waiting on one waits on a person. Remove them from dependsOn and enqueue it again; only the final reporting task may depend on them.`,\n };\n }\n\n const uncovered = uncoveredBySink(ctx, args);\n if (uncovered.length > 0) {\n const seededUncovered = uncovered.filter((t) =>\n (ctx.runnerSeededTypes ?? []).includes(t.type),\n );\n // \"Or to a task already in dependsOn\" is the right advice for an ordinary\n // task and exactly wrong for a runner-seeded one — routing it through an\n // intermediate satisfies the closure while breaking the one-way rule above.\n // So a seeded task is named with the only placement that is legal for it.\n const how =\n seededUncovered.length === uncovered.length\n ? \"Add them to this task's own dependsOn — for a task the wizard placed, that is the only legal spot — and enqueue it again.\"\n : 'Add them to dependsOn, or to a task already in dependsOn, and enqueue it again.';\n return {\n ok: false,\n guard: 'sink-closure',\n message: `A \"${\n args.type\n }\" task runs last, so it must depend on every other task — directly or through its dependencies. These are not covered: ${uncovered\n .map((t) => `${t.type} (${t.id})`)\n .join(', ')}. ${how}`,\n };\n }\n\n const key = dedupKey(args.type, args.inputs ?? {});\n if (\n tasks.some(\n (t) =>\n t.status !== TaskStatus.Failed && dedupKey(t.type, t.inputs) === key,\n )\n ) {\n return {\n ok: false,\n guard: 'dedup',\n message: `A \"${args.type}\" task with these inputs already exists.`,\n };\n }\n\n return { ok: true };\n}\n\nexport type EnqueueResult =\n | { ok: true; task: QueuedTask }\n | { ok: false; guard: string; message: string };\n\nexport function applyEnqueue(\n ctx: OrchestratorToolsContext,\n args: EnqueueArgs,\n): EnqueueResult {\n const guard = checkEnqueueGuards(ctx, args);\n if (!guard.ok) return guard;\n\n const task = ctx.store.enqueue({\n type: args.type,\n label: args.label,\n inputs: args.inputs ?? {},\n dependsOn: args.dependsOn ?? [],\n model: args.model,\n enqueuedBy: ctx.currentTaskId ?? 'orchestrator',\n });\n return { ok: true, task };\n}\n\nexport type CompleteResult = { ok: true } | { ok: false; message: string };\n\nexport function applyComplete(\n ctx: OrchestratorToolsContext,\n args: {\n status: 'done' | 'failed' | 'not needed';\n handoff: TaskHandoff;\n remark?: string;\n },\n): CompleteResult {\n const id = ctx.currentTaskId;\n if (!id) {\n return {\n ok: false,\n message: 'complete_task can only be called by a running task agent.',\n };\n }\n if (args.remark) {\n analytics.wizardCapture('orchestrator remark', {\n task_type: ctx.store.get(id)?.type,\n remark: args.remark,\n });\n }\n if (args.status === TaskStatus.Failed) {\n ctx.store.fail(\n id,\n { type: 'self-reported', message: args.handoff.forNextAgent },\n args.handoff,\n );\n } else if (args.status === TaskStatus.Skipped) {\n ctx.store.skip(id, args.handoff);\n } else {\n ctx.store.complete(id, args.handoff);\n }\n return { ok: true };\n}\n\nexport function applyReadHandoffs(\n ctx: OrchestratorToolsContext,\n args: { type?: string; taskId?: string },\n): TaskHandoff[] {\n if (args.taskId) {\n const h = ctx.store.readHandoff(args.taskId);\n return h ? [h] : [];\n }\n if (args.type) {\n return ctx.store.readHandoffsByType(args.type);\n }\n // No filter: every handoff of a dependency of the current task.\n const currentId = ctx.currentTaskId;\n const current = currentId ? ctx.store.get(currentId) : undefined;\n if (!current) return [];\n return current.dependsOn\n .map((depId) => ctx.store.readHandoff(depId))\n .filter((h): h is TaskHandoff => h !== null);\n}\n\n// Caps each LLM-authored free-text field; queue.json rewrites whole per transition.\nconst HANDOFF_TEXT_MAX = 8_000;\n\n/**\n * The one description of every handoff field, shared by both harnesses'\n * `complete_task` schemas — the zod shape below and the typebox mirror in\n * `harness/pi/orchestrator-tools.ts`. A field an agent cannot see is a field it\n * cannot fill, so the two drifting silently drops whatever the missing field\n * carried; `__tests__/handoff-schema-parity.test.ts` holds them level.\n */\nexport const HANDOFF_FIELDS = {\n goals: 'What this task was asked to achieve.',\n did: 'What you actually did — for each file you edited: the change, the intention behind it, and the analytics it should feed (the insight, funnel, or dashboard tile it becomes part of).',\n forNextAgent: 'What the next agent should know.',\n filesTouched: 'Paths of every file you edited.',\n evidence:\n 'How you know it worked — what you ran or observed, not what you expect.',\n assumptions: 'What you assumed about the app and could not verify.',\n conflict:\n 'A one-line summary of any conflict you could not cleanly resolve (e.g. a dependency or build conflict). Put full detail in your work; this line is surfaced to the user.',\n reportSection:\n 'A finished markdown section about your work for the run report, written only when your task instructions ask for one. The reporting task includes it as its own section instead of rewriting it.',\n} as const satisfies Record<keyof Required<TaskHandoff>, string>;\n\nconst HANDOFF_SHAPE = {\n goals: z.string().max(HANDOFF_TEXT_MAX).describe(HANDOFF_FIELDS.goals),\n did: z.string().max(HANDOFF_TEXT_MAX).describe(HANDOFF_FIELDS.did),\n forNextAgent: z\n .string()\n .max(HANDOFF_TEXT_MAX)\n .describe(HANDOFF_FIELDS.forNextAgent),\n filesTouched: z\n .array(z.string().max(1_000))\n .max(200)\n .optional()\n .describe(HANDOFF_FIELDS.filesTouched),\n evidence: z\n .string()\n .max(HANDOFF_TEXT_MAX)\n .optional()\n .describe(HANDOFF_FIELDS.evidence),\n assumptions: z\n .string()\n .max(HANDOFF_TEXT_MAX)\n .optional()\n .describe(HANDOFF_FIELDS.assumptions),\n conflict: z\n .string()\n .max(HANDOFF_TEXT_MAX)\n .optional()\n .describe(HANDOFF_FIELDS.conflict),\n reportSection: z\n .string()\n .max(HANDOFF_TEXT_MAX)\n .optional()\n .describe(HANDOFF_FIELDS.reportSection),\n};\n\n/** Exported so the parity test can compare both harnesses' field sets. */\nexport const HANDOFF_SHAPE_KEYS: readonly string[] = Object.keys(HANDOFF_SHAPE);\n\ntype SdkTool = (\n name: string,\n description: string,\n // The SDK accepts a plain object of zod fields as the schema.\n schema: Record<string, z.ZodTypeAny>,\n handler: (args: never) => unknown,\n) => unknown;\n\nfunction textResult(text: string, isError = false) {\n return { isError, content: [{ type: 'text' as const, text }] };\n}\n\n/**\n * Build the orchestrator tools in the SDK `tool()` shape. Called from\n * createWizardToolsServer only when a queue context is present.\n */\nexport function buildOrchestratorTools(\n tool: SdkTool,\n ctx: OrchestratorToolsContext,\n): unknown[] {\n const enqueueTask = tool(\n 'enqueue_task',\n 'Add a task to the orchestrator queue. Use it to seed work and to enqueue follow-up work you discover. Keep tasks small and discrete.',\n {\n type: z\n .string()\n .describe(`The task type. One of: ${ctx.validTypes.join(', ')}.`),\n label: z\n .string()\n .optional()\n .describe(\n 'A short label for the UI — the action in a few words (e.g. \"Add the PostHog SDK\", \"Initialize PostHog\"). Leave out file names, class names, and other specifics.',\n ),\n inputs: z.record(z.unknown()).optional(),\n dependsOn: z\n .array(z.string())\n .optional()\n .describe('Task ids that must be done before this task runs.'),\n model: z.string().optional(),\n reason: z.string().describe('One line on why this task is needed.'),\n },\n ((args: EnqueueArgs) => {\n const res = applyEnqueue(ctx, args);\n if (!res.ok) {\n analytics.wizardCapture('orchestrator guard tripped', {\n guard: res.guard,\n type: args.type,\n });\n return textResult(res.message, true);\n }\n return textResult(JSON.stringify({ id: res.task.id }));\n }) as (args: never) => unknown,\n );\n\n const completeTask = tool(\n 'complete_task',\n \"Report the outcome of your task. Always call this exactly once when you finish, with a structured handoff for the next agent. Use status 'not needed' when the task does not apply to this project and you cannot do it (say why in the handoff) — not 'done'.\",\n {\n status: z.enum(['done', 'failed', 'not needed']),\n handoff: z.object(HANDOFF_SHAPE),\n remark: z.string().optional().describe(REMARK_ASK),\n },\n ((args: {\n status: 'done' | 'failed' | 'not needed';\n handoff: TaskHandoff;\n remark?: string;\n }) => {\n const res = applyComplete(ctx, args);\n if (!res.ok) return textResult(res.message, true);\n return textResult('ok');\n }) as (args: never) => unknown,\n );\n\n const readHandoffs = tool(\n 'read_handoffs',\n 'Read structured handoffs from earlier tasks. With no argument, returns the handoffs of your dependencies.',\n {\n type: z.string().optional(),\n taskId: z.string().optional(),\n },\n ((args: { type?: string; taskId?: string }) => {\n const handoffs = applyReadHandoffs(ctx, args);\n return textResult(JSON.stringify(handoffs, null, 2));\n }) as (args: never) => unknown,\n );\n\n return [enqueueTask, completeTask, readHandoffs];\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAwBA,MAAM,kBAAkB;CACtB;CACA;CACA;CACA;CACA;CACA;CACD;;AAID,SAAgB,gBAAgB,OAAwC;AACtE,QAAQ,gBAAuC,SAAS,MAAM;;;AAiBhE,MAAa,qBAAwD;EAClE,sBAAsB,EAAE,WAAW,MAAM;EACzC,iBAAiB,EAAE,WAAW,MAAM;EACpC,aAAa,EAAE,WAAW,MAAM;EAChC,cAAc,EAAE,WAAW,MAAM;EAKjC,oBAAoB;EAAE,WAAW;EAAM,eAAe;EAAO;EAC7D,qBAAqB;EAAE,WAAW;EAAM,eAAe;EAAU;EACjE,mBAAmB;EAAE,WAAW;EAAM,eAAe;EAAO;CAC9D;;AAGD,MAAa,eAAoC,IAAI,IACnD,OAAO,KAAK,mBAAmB,CAChC;;AAGD,SAAgB,aAAa,OAAiC;AAC5D,QAAO,OAAO,UAAU,YAAY,aAAa,IAAI,MAAM;;;AAI7D,SAAgB,kBACd,GAAG,YACK;AACR,MAAK,MAAM,KAAK,WAAY,KAAI,aAAa,EAAE,CAAE,QAAO;AACxD,OAAM,IAAI,MACR,wCAAwC,KAAK,UAAU,WAAW,CAAC,cACrD,CAAC,GAAG,aAAa,CAAC,KAAK,KAAK,CAAC,GAC5C;;;;;;;;;AAUH,SAAS,YAAY,SAAoC;AACvD,QAAO,EAAE,WAAW,CAAC,QAAQ,WAAW,UAAU,EAAE;;;;;;;;AAStD,MAAa,gBAAyC;gBAC/B;SACP;CACf;AAED,SAAgB,eAAe,SAA0B;AACvD,QAAO,cAAc;;;;;;;;AASvB,SAAgB,kBACd,SACA,gBACmB;CACnB,MAAM,OAAO,mBAAmB,YAAY,YAAY,QAAQ;AAChE,KAAI,KAAK,aAAa,eACpB,QAAO;EAAE,GAAG;EAAM,eAAe;EAAgB;AAEnD,QAAO;;;;;;;;;;;;;ACrHT,SAAgB,gBAAgB,YAAoB,MAAqB;CACvE,MAAM,UAAU,GAAG,WAAW;AAC9B,MAAG,cAAc,SAAS,KAAK,UAAU,MAAM,MAAM,EAAE,EAAE,OAAO;AAChE,MAAG,WAAW,SAAS,WAAW;;;;;;AAOpC,SAAgB,YAAY;CAC1B,IAAI,QAA0B,QAAQ,SAAS;AAC/C,QAAO,eAAe,IAAO,IAAsC;EACjE,MAAM,OAAO,MAAM,WAAW,IAAI,CAAC;AACnC,UAAQ,KAAK,YAAY,KAAA,EAAU;AACnC,SAAO;;;;;;;;;;;;;;;;;;ACPX,MAAa,aAAa;CACxB,SAAS;CACT,SAAS;CACT,MAAM;CACN,SAAS;CACT,QAAQ;CACT;AAwED,MAAa,iBAAiB;AAC9B,MAAM,uBAAuB;AAE7B,SAAS,SAAiB;AACxB,yBAAO,IAAI,MAAM,EAAC,aAAa;;;AAIjC,MAAM,iBAAiB;AACvB,MAAM,iBAAiB;;;;kDAI2B,eAAe;;AAqBjE,IAAa,aAAb,MAAwB;CACtB,QAA8B,EAAE;CAChC;CAKA;CACA;CAEA,YAAY,YAAoB,OAAe,MAA0B;AACvE,OAAK,eAAe,MAAM;AAC1B,OAAK,QAAQ;EACb,MAAM,MAAMA,OAAK,KAAK,YAAY,eAAe;AACjD,OAAK,YAAYA,OAAK,KAAK,KAAK,aAAa;AAC7C,OAAG,UAAU,KAAK,EAAE,WAAW,MAAM,CAAC;AACtC,OAAG,cAAcA,OAAK,KAAK,KAAK,eAAe,EAAE,eAAe;;CAKlE,OAA8B;AAC5B,SAAO,KAAK;;CAGd,IAAI,IAAoC;AACtC,SAAO,KAAK,MAAM,MAAM,MAAM,EAAE,OAAO,GAAG;;;;;;CAO5C,eAA6B;EAE3B,MAAM,UAAU,IAAI,IAClB,KAAK,MACF,QACE,MACC,EAAE,WAAW,WAAW,QACxB,EAAE,WAAW,WAAW,WACvB,EAAE,WAAW,WAAW,UACvB,EAAE,aAAa,QACf,EAAE,YAAY,EAAE,YACrB,CACA,KAAK,MAAM,EAAE,GAAG,CACpB;AACD,SAAO,KAAK,MAAM,QACf,MACC,EAAE,WAAW,WAAW,WACxB,EAAE,UAAU,OAAO,MAAM,QAAQ,IAAI,EAAE,CAAC,CAC3C;;;;;;CAOH,YAAqB;AACnB,MAAI,KAAK,MAAM,MAAM,MAAM,EAAE,WAAW,WAAW,QAAQ,CAAE,QAAO;AACpE,SAAO,KAAK,cAAc,CAAC,WAAW;;CAGxC,UAA0D;EACxD,MAAM,SAAqC;IACxC,WAAW,UAAU;IACrB,WAAW,UAAU;IACrB,WAAW,OAAO;IAClB,WAAW,UAAU;IACrB,WAAW,SAAS;GACtB;AACD,OAAK,MAAM,KAAK,KAAK,MAAO,QAAO,EAAE,WAAW;AAChD,SAAO;GAAE,GAAG;GAAQ,OAAO,KAAK,MAAM;GAAQ;;CAGhD,YAAY,IAAgC;AAC1C,SAAO,KAAK,IAAI,GAAG,EAAE,WAAW;;;CAIlC,mBAAmB,MAA6B;AAC9C,SAAO,KAAK,MACT,QAAQ,MAAM,EAAE,SAAS,QAAQ,EAAE,QAAQ,CAC3C,KAAK,MAAM,EAAE,QAAuB;;CAKzC,QAAQ,OAAiC;EACvC,MAAM,OAAmB;GACvB,IAAI,YAAY;GAChB,MAAM,MAAM;GACZ,OAAO,MAAM;GACb,QAAQ,WAAW;GACnB,WAAW,MAAM,aAAa,EAAE;GAChC,QAAQ,MAAM,UAAU,EAAE;GAC1B,OAAO,MAAM;GACb,UAAU;GACV,aAAa,MAAM,eAAe;GAClC,YAAY,MAAM,cAAc;GAChC,UAAU,MAAM;GAChB,WAAW,QAAQ;GACpB;AACD,OAAK,MAAM,KAAK,KAAK;AACrB,OAAK,SAAS;AACd,OAAK,OAAO,WAAW,KAAK;AAC5B,SAAO;;;;;;;;;;;;;;;;;CAkBT,gBACE,IACA,QACwC;EACxC,MAAM,OAAO,KAAK,QAAQ,GAAG;AAC7B,MAAI,KAAK,WAAW,WAAW,QAC7B,QAAO;GAAE,OAAO,EAAE;GAAE,SAAS,CAAC,GAAG,OAAO;GAAE;EAG5C,MAAM,QAAkB,EAAE;EAC1B,MAAM,UAAoB,EAAE;AAC5B,OAAK,MAAM,SAAS,QAAQ;AAE1B,OAAI,UAAU,MAAM,KAAK,UAAU,SAAS,MAAM,CAAE;AAGpD,OAAI,CAAC,KAAK,IAAI,MAAM,IAAI,KAAK,QAAQ,OAAO,GAAG,EAAE;AAC/C,YAAQ,KAAK,MAAM;AACnB;;AAEF,QAAK,UAAU,KAAK,MAAM;AAC1B,SAAM,KAAK,MAAM;;AAEnB,MAAI,MAAM,SAAS,EAAG,MAAK,SAAS;AACpC,SAAO;GAAE;GAAO;GAAS;;CAG3B,MAAM,IAAwB;EAC5B,MAAM,IAAI,KAAK,QAAQ,GAAG;AAC1B,IAAE,SAAS,WAAW;AACtB,IAAE,YAAY,QAAQ;AACtB,IAAE,YAAY;AACd,OAAK,SAAS;AACd,OAAK,OAAO,SAAS,EAAE;AACvB,SAAO;;CAGT,SAAS,IAAY,SAAmC;AACtD,SAAO,KAAK,OAAO,IAAI,WAAW,MAAM,QAAQ;;;CAIlD,KAAK,IAAY,SAAmC;AAClD,SAAO,KAAK,OAAO,IAAI,WAAW,SAAS,QAAQ;;CAGrD,KACE,IACA,OACA,SACY;EACZ,MAAM,IAAI,KAAK,QAAQ,GAAG;AAC1B,IAAE,QAAQ;AACV,SAAO,KAAK,OAAO,IAAI,WAAW,QAAQ,QAAQ;;;CAIpD,QAAQ,IAAwB;EAC9B,MAAM,IAAI,KAAK,QAAQ,GAAG;AAC1B,IAAE,SAAS,WAAW;AACtB,IAAE,YAAY,KAAA;AACd,IAAE,aAAa,KAAA;AACf,OAAK,SAAS;AACd,OAAK,OAAO,WAAW,EAAE;AACzB,SAAO;;CAKT,OACE,IACA,QACA,SACY;EACZ,MAAM,IAAI,KAAK,QAAQ,GAAG;AAC1B,MAAI,QAAS,GAAE,UAAU;AACzB,IAAE,SAAS;AACX,IAAE,aAAa,QAAQ;AACvB,OAAK,SAAS;AACd,OAAK,OACH,WAAW,WAAW,OAClB,aACA,WAAW,WAAW,UACtB,SACA,QACJ,EACD;AACD,SAAO;;CAGT,UAAwB;EACtB,MAAM,OAAkB;GACtB,SAAS;GACT,OAAO,KAAK;GACZ,OAAO,KAAK;GACb;AACD,kBAAgB,KAAK,WAAW,KAAK;;CAGvC,OAAe,OAAwB,MAAwB;AAC7D,MAAI;AACF,QAAK,eAAe,OAAO,KAAK;WACzB,OAAO;AAEd,aAAU,iBACR,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,MAAM,CAAC,EACzD;IAAE,MAAM;IAA+B;IAAO,CAC/C;;;;CAKL,QAAgB,QAAgB,UAA2B;EACzD,MAAM,uBAAO,IAAI,KAAa;EAC9B,MAAM,QAAQ,CAAC,OAAO;AACtB,SAAO,MAAM,SAAS,GAAG;GACvB,MAAM,UAAU,MAAM,KAAK;AAC3B,OAAI,YAAY,SAAU,QAAO;AACjC,OAAI,KAAK,IAAI,QAAQ,CAAE;AACvB,QAAK,IAAI,QAAQ;AACjB,SAAM,KAAK,GAAI,KAAK,IAAI,QAAQ,EAAE,aAAa,EAAE,CAAE;;AAErD,SAAO;;CAGT,QAAgB,IAAwB;EACtC,MAAM,IAAI,KAAK,IAAI,GAAG;AACtB,MAAI,CAAC,EAAG,OAAM,IAAI,MAAM,WAAW,GAAG,eAAe;AACrD,SAAO;;;;;;;;;;;;;;ACzWX,MAAa,aACX;AAyCF,SAAS,gBAAgB,OAAwB;AAC/C,KAAI,UAAU,QAAQ,OAAO,UAAU,SAAU,QAAO,KAAK,UAAU,MAAM;AAC7E,KAAI,MAAM,QAAQ,MAAM,CAAE,QAAO,IAAI,MAAM,IAAI,gBAAgB,CAAC,KAAK,IAAI,CAAC;AAI1E,QAAO,IAHS,OAAO,QAAQ,MAAiC,CAAC,MAC9D,CAAC,IAAI,CAAC,OAAO,EAAE,cAAc,EAAE,CACjC,CAEE,KAAK,CAAC,GAAG,OAAO,GAAG,KAAK,UAAU,EAAE,CAAC,GAAG,gBAAgB,EAAE,GAAG,CAC7D,KAAK,IAAI,CAAC;;AAGf,SAAS,SAAS,MAAc,QAAyC;AACvE,QAAO,GAAG,KAAK,IAAI,gBAAgB,OAAO;;;;;;;;AAS5C,MAAM,kBAAkB;;AAGxB,SAAgB,kBACd,OACA,OACa;CACb,MAAM,uBAAO,IAAI,KAAa;CAC9B,MAAM,SAAS,OAAqB;AAClC,MAAI,KAAK,IAAI,GAAG,CAAE;AAClB,OAAK,IAAI,GAAG;AACZ,OAAK,MAAM,OAAO,MAAM,IAAI,GAAG,EAAE,aAAa,EAAE,CAAE,OAAM,IAAI;;AAE9D,MAAK,MAAM,MAAM,MAAO,OAAM,GAAG;AACjC,QAAO;;;;;;;;AAST,SAAgB,gBACd,KACA,MACc;AACd,KAAI,CAAC,IAAI,WAAW,SAAS,KAAK,KAAK,CAAE,QAAO,EAAE;CAClD,MAAM,UAAU,kBAAkB,IAAI,OAAO,KAAK,aAAa,EAAE,CAAC;AAClE,QAAO,IAAI,MAAM,MAAM,CAAC,QAAQ,MAAM,CAAC,QAAQ,IAAI,EAAE,GAAG,CAAC;;;;;;;;;;;;;;;;AAiB3D,SAAgB,oBACd,KACA,MACc;CACd,MAAM,SAAS,IAAI,qBAAqB,EAAE;AAC1C,KAAI,OAAO,WAAW,EAAG,QAAO,EAAE;AAClC,KAAI,IAAI,WAAW,SAAS,KAAK,KAAK,CAAE,QAAO,EAAE;CACjD,MAAM,UAAU,kBAAkB,IAAI,OAAO,KAAK,aAAa,EAAE,CAAC;AAClE,QAAO,IAAI,MACR,MAAM,CACN,QAAQ,MAAM,QAAQ,IAAI,EAAE,GAAG,IAAI,OAAO,SAAS,EAAE,KAAK,CAAC;;;;;;;;AAShE,SAAgB,mBACd,KACA,MACa;CACb,MAAM,QAAQ,IAAI,MAAM,MAAM;AAE9B,KAAI,MAAM,UAAU,gBAClB,QAAO;EACL,IAAI;EACJ,OAAO;EACP,SAAS,2BAA2B,MAAM,OAAO,cAAc,gBAAgB;EAChF;AAGH,KAAI,CAAC,IAAI,WAAW,SAAS,KAAK,KAAK,CACrC,QAAO;EACL,IAAI;EACJ,OAAO;EACP,SAAS,sBACP,KAAK,KACN,kBAAkB,IAAI,WAAW,KAAK,KAAK,CAAC;EAC9C;AAIH,KAAI,KAAK,UAAU,KAAA,KAAa,CAAC,aAAa,KAAK,MAAM,CACvD,QAAO;EACL,IAAI;EACJ,OAAO;EACP,SAAS,UACP,KAAK,MACN,wEAAwE,CACvE,GAAG,aACJ,CAAC,KAAK,KAAK,CAAC;EACd;AAGH,MAAK,MAAM,OAAO,KAAK,aAAa,EAAE,CACpC,KAAI,CAAC,IAAI,MAAM,IAAI,IAAI,CACrB,QAAO;EACL,IAAI;EACJ,OAAO;EACP,SAAS,eAAe,IAAI;EAC7B;CAIL,MAAM,aAAa,oBAAoB,KAAK,KAAK;AACjD,KAAI,WAAW,SAAS,EACtB,QAAO;EACL,IAAI;EACJ,OAAO;EACP,SAAS,MAAM,KAAK,KAAK,2BAA2B,WACjD,KAAK,MAAM,GAAG,EAAE,KAAK,IAAI,EAAE,GAAG,GAAG,CACjC,KACC,KACD,CAAC;EACL;CAGH,MAAM,YAAY,gBAAgB,KAAK,KAAK;AAC5C,KAAI,UAAU,SAAS,GAAG;EAQxB,MAAM,MAPkB,UAAU,QAAQ,OACvC,IAAI,qBAAqB,EAAE,EAAE,SAAS,EAAE,KAAK,CAC/C,CAMiB,WAAW,UAAU,SACjC,8HACA;AACN,SAAO;GACL,IAAI;GACJ,OAAO;GACP,SAAS,MACP,KAAK,KACN,yHAAyH,UACvH,KAAK,MAAM,GAAG,EAAE,KAAK,IAAI,EAAE,GAAG,GAAG,CACjC,KAAK,KAAK,CAAC,IAAI;GACnB;;CAGH,MAAM,MAAM,SAAS,KAAK,MAAM,KAAK,UAAU,EAAE,CAAC;AAClD,KACE,MAAM,MACH,MACC,EAAE,WAAW,WAAW,UAAU,SAAS,EAAE,MAAM,EAAE,OAAO,KAAK,IACpE,CAED,QAAO;EACL,IAAI;EACJ,OAAO;EACP,SAAS,MAAM,KAAK,KAAK;EAC1B;AAGH,QAAO,EAAE,IAAI,MAAM;;AAOrB,SAAgB,aACd,KACA,MACe;CACf,MAAM,QAAQ,mBAAmB,KAAK,KAAK;AAC3C,KAAI,CAAC,MAAM,GAAI,QAAO;AAUtB,QAAO;EAAE,IAAI;EAAM,MARN,IAAI,MAAM,QAAQ;GAC7B,MAAM,KAAK;GACX,OAAO,KAAK;GACZ,QAAQ,KAAK,UAAU,EAAE;GACzB,WAAW,KAAK,aAAa,EAAE;GAC/B,OAAO,KAAK;GACZ,YAAY,IAAI,iBAAiB;GAClC,CAAC;EACuB;;AAK3B,SAAgB,cACd,KACA,MAKgB;CAChB,MAAM,KAAK,IAAI;AACf,KAAI,CAAC,GACH,QAAO;EACL,IAAI;EACJ,SAAS;EACV;AAEH,KAAI,KAAK,OACP,WAAU,cAAc,uBAAuB;EAC7C,WAAW,IAAI,MAAM,IAAI,GAAG,EAAE;EAC9B,QAAQ,KAAK;EACd,CAAC;AAEJ,KAAI,KAAK,WAAW,WAAW,OAC7B,KAAI,MAAM,KACR,IACA;EAAE,MAAM;EAAiB,SAAS,KAAK,QAAQ;EAAc,EAC7D,KAAK,QACN;UACQ,KAAK,WAAW,WAAW,QACpC,KAAI,MAAM,KAAK,IAAI,KAAK,QAAQ;KAEhC,KAAI,MAAM,SAAS,IAAI,KAAK,QAAQ;AAEtC,QAAO,EAAE,IAAI,MAAM;;AAGrB,SAAgB,kBACd,KACA,MACe;AACf,KAAI,KAAK,QAAQ;EACf,MAAM,IAAI,IAAI,MAAM,YAAY,KAAK,OAAO;AAC5C,SAAO,IAAI,CAAC,EAAE,GAAG,EAAE;;AAErB,KAAI,KAAK,KACP,QAAO,IAAI,MAAM,mBAAmB,KAAK,KAAK;CAGhD,MAAM,YAAY,IAAI;CACtB,MAAM,UAAU,YAAY,IAAI,MAAM,IAAI,UAAU,GAAG,KAAA;AACvD,KAAI,CAAC,QAAS,QAAO,EAAE;AACvB,QAAO,QAAQ,UACZ,KAAK,UAAU,IAAI,MAAM,YAAY,MAAM,CAAC,CAC5C,QAAQ,MAAwB,MAAM,KAAK;;AAIhD,MAAM,mBAAmB;;;;;;;;AASzB,MAAa,iBAAiB;CAC5B,OAAO;CACP,KAAK;CACL,cAAc;CACd,cAAc;CACd,UACE;CACF,aAAa;CACb,UACE;CACF,eACE;CACH;AAED,MAAM,gBAAgB;CACpB,OAAO,EAAE,QAAQ,CAAC,IAAI,iBAAiB,CAAC,SAAS,eAAe,MAAM;CACtE,KAAK,EAAE,QAAQ,CAAC,IAAI,iBAAiB,CAAC,SAAS,eAAe,IAAI;CAClE,cAAc,EACX,QAAQ,CACR,IAAI,iBAAiB,CACrB,SAAS,eAAe,aAAa;CACxC,cAAc,EACX,MAAM,EAAE,QAAQ,CAAC,IAAI,IAAM,CAAC,CAC5B,IAAI,IAAI,CACR,UAAU,CACV,SAAS,eAAe,aAAa;CACxC,UAAU,EACP,QAAQ,CACR,IAAI,iBAAiB,CACrB,UAAU,CACV,SAAS,eAAe,SAAS;CACpC,aAAa,EACV,QAAQ,CACR,IAAI,iBAAiB,CACrB,UAAU,CACV,SAAS,eAAe,YAAY;CACvC,UAAU,EACP,QAAQ,CACR,IAAI,iBAAiB,CACrB,UAAU,CACV,SAAS,eAAe,SAAS;CACpC,eAAe,EACZ,QAAQ,CACR,IAAI,iBAAiB,CACrB,UAAU,CACV,SAAS,eAAe,cAAc;CAC1C;AAaD,SAAS,WAAW,MAAc,UAAU,OAAO;AACjD,QAAO;EAAE;EAAS,SAAS,CAAC;GAAE,MAAM;GAAiB;GAAM,CAAC;EAAE;;;;;;AAOhE,SAAgB,uBACd,MACA,KACW;AAmEX,QAAO;EAlEa,KAClB,gBACA,wIACA;GACE,MAAM,EACH,QAAQ,CACR,SAAS,0BAA0B,IAAI,WAAW,KAAK,KAAK,CAAC,GAAG;GACnE,OAAO,EACJ,QAAQ,CACR,UAAU,CACV,SACC,uKACD;GACH,QAAQ,EAAE,OAAO,EAAE,SAAS,CAAC,CAAC,UAAU;GACxC,WAAW,EACR,MAAM,EAAE,QAAQ,CAAC,CACjB,UAAU,CACV,SAAS,oDAAoD;GAChE,OAAO,EAAE,QAAQ,CAAC,UAAU;GAC5B,QAAQ,EAAE,QAAQ,CAAC,SAAS,uCAAuC;GACpE,IACC,SAAsB;GACtB,MAAM,MAAM,aAAa,KAAK,KAAK;AACnC,OAAI,CAAC,IAAI,IAAI;AACX,cAAU,cAAc,8BAA8B;KACpD,OAAO,IAAI;KACX,MAAM,KAAK;KACZ,CAAC;AACF,WAAO,WAAW,IAAI,SAAS,KAAK;;AAEtC,UAAO,WAAW,KAAK,UAAU,EAAE,IAAI,IAAI,KAAK,IAAI,CAAC,CAAC;KAEzD;EAEoB,KACnB,iBACA,kQACA;GACE,QAAQ,EAAE,KAAK;IAAC;IAAQ;IAAU;IAAa,CAAC;GAChD,SAAS,EAAE,OAAO,cAAc;GAChC,QAAQ,EAAE,QAAQ,CAAC,UAAU,CAAC,SAAS,WAAW;GACnD,IACC,SAII;GACJ,MAAM,MAAM,cAAc,KAAK,KAAK;AACpC,OAAI,CAAC,IAAI,GAAI,QAAO,WAAW,IAAI,SAAS,KAAK;AACjD,UAAO,WAAW,KAAK;KAE1B;EAEoB,KACnB,iBACA,6GACA;GACE,MAAM,EAAE,QAAQ,CAAC,UAAU;GAC3B,QAAQ,EAAE,QAAQ,CAAC,UAAU;GAC9B,IACC,SAA6C;GAC7C,MAAM,WAAW,kBAAkB,KAAK,KAAK;AAC7C,UAAO,WAAW,KAAK,UAAU,UAAU,MAAM,EAAE,CAAC;KAEvD;EAE+C"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"security-wc-naGq5.js","names":[],"sources":["../src/lib/agent/runner/harness/pi/security.ts"],"sourcesContent":["/**\n * Fail-closed security for the pi backend (#525). pi has no built-in\n * permission layer, so we attach an extension that intercepts every tool call\n * — built-in (bash/read/edit/write/grep) AND custom — through pi's `tool_call`\n * hook and reuses the EXACT anthropic policy: `wizardCanUseTool` (the bash\n * allowlist + .env fencing) plus the YARA pre-scan. A `tool_result` hook\n * post-scans output. Both fail closed: a scanner error blocks, and a critical\n * post-scan violation latches so every subsequent tool call is blocked and the\n * run terminates as a YARA violation.\n *\n * Scanning runs through @posthog/warlock via yara-hooks' `scanAndTriage` — the\n * same engine, rules, chunking, and LLM triage policy as the anthropic\n * harness. pi handlers are async (pi's ExtensionHandler accepts promises), so\n * the WASM scan awaits inline.\n *\n * This is the one fence. Subagents run their own pi session with the SAME\n * extension installed (see subagent.ts), so a child cannot escape it.\n */\n\nimport fs from 'fs';\nimport path from 'path';\nimport type { LLMProvider, ScanMatch } from '@posthog/warlock';\nimport { wizardCanUseTool } from '@lib/agent/agent-interface';\nimport {\n createRepeatBlockTracker,\n isWizardDocumentationPath,\n recordExternalScan,\n repeatBlockReason,\n scanAndTriage,\n type RepeatBlockTracker,\n} from '@lib/yara-hooks';\nimport { scanVerdict, type ScanContext } from '@lib/yara-policy';\nimport { logToFile } from '@utils/debug';\nimport { analytics } from '@utils/analytics';\n\n/** warlock ScanMatch → the report shape `recordExternalScan` expects. */\nfunction toReportViolation(m: ScanMatch) {\n return {\n rule: m.rule,\n severity: m.metadata.severity ?? 'unknown',\n category: m.metadata.category ?? 'unknown',\n description: m.metadata.description ?? '',\n };\n}\n\n/** Runaway backstop: hard cap on tool calls per (sub)agent session. */\nexport const MAX_TOOL_CALLS = 250;\n\n/**\n * Passive telemetry for an unfixed upstream failure: gpt-5.x streaming with\n * tools leaks its function-call grammar into string values, and the leak lands\n * on disk (run 9704a73e shipped `…functions.complete_task (commentary…` plus a\n * DEL byte inside a customer's global-error.tsx). Observe-only — never blocks.\n * Prior art: https://github.com/BerriAI/litellm/issues/14260 (closed stale),\n * https://community.openai.com/t/1386422 (gpt-5.6-luna, no fix).\n */\nconst TRANSPORT_LEAK_PATTERNS: readonly { pattern: RegExp; label: string }[] = [\n // The litellm#14260 signature, scoped to the wizard's own tool names — a\n // generic `functions.*` would false-positive on real code (Firebase).\n {\n pattern: /functions\\.(?:complete_task|enqueue_task|read_handoffs)/,\n label: 'leaked tool-call tokens',\n },\n {\n pattern: /<\\|(?:channel|constrain|message|call|end|start|return)\\|>/,\n label: 'leaked channel markers',\n },\n // C0 controls and DEL minus tab/newline/CR — never valid in source text.\n // eslint-disable-next-line no-control-regex\n { pattern: /[\\0-\\x08\\x0B\\f\\x0E-\\x1F\\x7F]/, label: 'control characters' },\n];\n\n/** Capture one event per leaking write/edit: which pattern fired and where it sat — no matched text, ever. */\nexport function observeTransportLeak(tool: string, content: string): void {\n for (const { pattern, label } of TRANSPORT_LEAK_PATTERNS) {\n const match = pattern.exec(content);\n if (!match) continue;\n analytics.wizardCapture('file content leak observed', {\n tool,\n leak: label,\n leak_offset: match.index,\n content_length: content.length,\n // End-of-string leaks are the upstream decoder signature.\n at_end: content.length - match.index < 80,\n });\n logToFile(\n `[transport-leak] observed ${tool}: ${label} at ${match.index}/${content.length}`,\n );\n return;\n }\n}\n\nexport interface ToolGateContext {\n disallowedTools?: readonly string[];\n /** True while a wizard_ask overlay is open (interactive); blocks Write/Edit. */\n getWizardAskPending?: () => boolean;\n /** Scan-triage classifier, resolved once in bootstrap. Absent → every flagged match is acted on. */\n triageProvider?: LLMProvider;\n /**\n * Per-run tracker of YARA-blocked payloads. When the agent retries content\n * that was already blocked, the block reason escalates (\"change the code,\n * not the retry\" → \"report it and move on\"). The extension wires one in;\n * absent (tests calling `evaluateToolCall` directly) every block reads as\n * a first attempt.\n */\n repeatTracker?: RepeatBlockTracker;\n /** Project root; rm is only allowed for files resolving strictly inside it. */\n workingDirectory?: string;\n}\n\nexport interface GateDecision {\n block: boolean;\n reason?: string;\n}\n\nconst str = (v: unknown): string => (typeof v === 'string' ? v : '');\n\n// Shell metacharacters that chain, substitute, or redirect. A command carrying\n// any of these is more than one action, so we never treat it as a plain `rm`.\nconst SHELL_OPERATORS = /[;&|`$(){}<>\\n'\"\\\\]/;\n\n/** True when a target resolves to a file strictly inside the project root. */\nfunction isDeletableProjectFile(\n target: string,\n root: string,\n p: typeof path,\n): boolean {\n if (target.startsWith('-')) return false; // a flag, not a file\n if (/[*?[\\]~]/.test(target)) return false; // glob / home expansion\n if (p.basename(target).startsWith('.env')) return false; // secrets\n\n const resolved = p.resolve(root, target);\n return resolved !== root && resolved.startsWith(root + p.sep);\n}\n\n/**\n * A plain `rm [-f] <file...>` whose every target is a file inside the project\n * root — the deletion shape the anthropic arm permits (there bash isn't\n * allowlisted; the shared YARA destructive-delete rules catch the dangerous\n * forms). pi rescues the same shape so the fall-through YARA scan can judge\n * it, but refuses any shell operator so it can't smuggle a second command.\n * Path checks run through the host's `path` (win32 on Windows, posix elsewhere);\n * pi always executes commands via a POSIX bash, so targets use forward slashes.\n */\nexport function isScopedFileRemoval(\n command: string,\n rawRoot: string | undefined,\n p: typeof path = path,\n): boolean {\n if (!rawRoot) return false; // no root to contain against → never rescue\n const root = p.resolve(rawRoot);\n const trimmed = command.trim();\n if (SHELL_OPERATORS.test(trimmed)) return false;\n\n const [executable, ...args] = trimmed.split(/\\s+/);\n if (executable !== 'rm') return false;\n\n if (args[0] === '-f') args.shift();\n if (args.length === 0) return false;\n\n return args.every((arg) => isDeletableProjectFile(arg, root, p));\n}\n\n/** A write that removes more than this fraction of an existing file's\n * non-whitespace content reads as a destructive whole-file rewrite. */\nconst OVERWRITE_SHRINK_LIMIT = 0.3;\n\n/** Existing files below this many non-whitespace chars are stubs; replacing one\n * wholesale is legitimate, so the shrink guard leaves them alone. */\nconst OVERWRITE_MIN_CHARS = 400;\n\nconst nonWhitespaceLength = (s: string): number => s.replace(/\\s+/g, '').length;\n\n/**\n * Refuse a write that replaces an existing file with far less content than it\n * held, steering the agent back to targeted edits. Prior art — the destructive\n * whole-file rewrite is a known model failure that targeted-edit formats exist to\n * prevent: Aider's search/replace and unified-diff, OpenAI's apply_patch,\n * Anthropic's str_replace with replace_all; pi's edit tool has no replace_all, so\n * a rejected scoped edit can fall back to a lossy full write. Additive by design:\n * the wizard only adds instrumentation, so a large shrink is the rewrite, not intent.\n */\nexport function overwriteShrinkReason(\n existing: string,\n next: string,\n): string | undefined {\n const before = nonWhitespaceLength(existing);\n if (before < OVERWRITE_MIN_CHARS) return undefined;\n const after = nonWhitespaceLength(next);\n if (after >= before * (1 - OVERWRITE_SHRINK_LIMIT)) return undefined;\n const removed = Math.round((1 - after / before) * 100);\n return `This write replaces an existing file and removes ~${removed}% of its content. Make targeted edits instead of rewriting the whole file; only write a file in full when creating it.`;\n}\n\n/**\n * Read the file a write would replace and flag a destructive shrink. A path that\n * does not exist yet is a new file, so it never triggers; with no project root to\n * resolve against (unit tests calling `evaluateToolCall` bare), it stays inert.\n */\nasync function overwriteShrinkBlock(\n input: Record<string, unknown>,\n workingDirectory: string | undefined,\n): Promise<string | undefined> {\n const target = str(input.path);\n if (!workingDirectory || !target) return undefined;\n let existing: string;\n try {\n existing = await fs.promises.readFile(\n path.resolve(workingDirectory, target),\n 'utf8',\n );\n } catch {\n return undefined; // new file (ENOENT) or unreadable — nothing to preserve\n }\n return overwriteShrinkReason(existing, str(input.content));\n}\n\n/**\n * Translate a pi tool name to the claude-cased name + input the shared policy\n * expects. pi field names (from the live tool stream): bash{command},\n * read/edit/write{path}, write adds {content}, edit adds {edits}, grep{path}.\n */\nfunction toClaudePolicyCall(\n toolName: string,\n input: Record<string, unknown>,\n): { name: string; input: Record<string, unknown> } {\n switch (toolName) {\n case 'bash':\n return { name: 'Bash', input: { command: str(input.command) } };\n case 'read':\n return { name: 'Read', input: { file_path: input.path } };\n case 'write':\n return { name: 'Write', input: { file_path: input.path } };\n case 'edit':\n return { name: 'Edit', input: { file_path: input.path } };\n case 'grep':\n return { name: 'Grep', input: { path: input.path } };\n default:\n // Custom tools (load_skill_menu, set_env_values, dispatch_agent, …) +\n // find/ls: no path/command, policy allows (their own handlers are fenced).\n return { name: toolName, input };\n }\n}\n\n/**\n * The agent-facing block message. Includes the rule's remediation so a blocked\n * agent can self-correct (e.g. \"move PII to identify()/$set\") instead of\n * retrying near-identical content.\n */\nfunction blockMessage(m: ScanMatch): string {\n const remediation = m.metadata.remediation\n ? ` Fix: ${m.metadata.remediation}.`\n : '';\n return `[YARA] ${m.rule}: ${\n m.metadata.description ?? 'security rule matched'\n }.${remediation} Blocked for security.`;\n}\n\n/**\n * YARA scan of the content a tool is about to act on, BEFORE it executes.\n * - bash → scan the command ('command'-context rules: exfiltration,\n * destructive, force-push). No triage — a flagged command always blocks,\n * same as the anthropic PreToolUse hook, so the LLM call would be wasted.\n * - write/edit → scan the content being written ('output'-context rules:\n * hardcoded keys, PII), WITH triage, and with the same wizard-doc\n * `posthog_pii` suppression the anthropic path uses so the agent's own\n * event-plan files aren't blocked.\n * Returns a block reason, or undefined to allow. Read/grep are post-scanned on\n * their output (in the tool_result hook), not here.\n */\nasync function preExecutionYaraBlock(\n toolName: string,\n input: Record<string, unknown>,\n llmProvider: LLMProvider | undefined,\n repeatTracker?: RepeatBlockTracker,\n): Promise<string | undefined> {\n let content: string;\n let ctx: ScanContext;\n let triage: LLMProvider | undefined;\n // Report labels for the scan report (phase/tool), matching the anthropic\n // hooks so pi and anthropic runs read the same in telemetry.\n let phase: string;\n let tool: string;\n switch (toolName) {\n case 'bash':\n content = str(input.command);\n ctx = 'command';\n triage = undefined;\n phase = 'PreToolUse';\n tool = 'Bash';\n break;\n case 'write':\n content = str(input.content);\n ctx = 'output';\n triage = llmProvider;\n phase = 'PostToolUse';\n tool = 'Write';\n break;\n case 'edit':\n // Scan ONLY the replacement text. `edits` is [{oldText, newText}] and\n // oldText is pre-existing file content the agent is changing — scanning\n // it blocks edits whose *surroundings* contain a violation, and even\n // edits that are removing one (field FPs: capture edits adjacent to\n // existing identify() PII; pre-existing violations blocking their own\n // replacement). Matches the anthropic path, which scans new_string only.\n content = (Array.isArray(input.edits) ? input.edits : [])\n .map((e) => str((e as Record<string, unknown>)?.newText))\n .join('\\n');\n ctx = 'output';\n triage = llmProvider;\n phase = 'PostToolUse';\n tool = 'Edit';\n break;\n default:\n return undefined;\n }\n if (!content) return undefined;\n if (ctx === 'output') observeTransportLeak(tool, content);\n\n let matches = await scanAndTriage(content, ctx, triage);\n if (ctx === 'output' && isWizardDocumentationPath(str(input.path))) {\n matches = matches.filter((m) => m.metadata.category !== 'posthog_pii');\n }\n recordExternalScan(phase, tool, matches.map(toReportViolation), 'blocked');\n if (matches.length === 0) return undefined;\n\n const attempt = repeatTracker?.attempt(toolName, content) ?? 1;\n return repeatBlockReason(attempt, toolName, blockMessage(matches[0]));\n}\n\n/**\n * The pure gate decision for a single tool call. Reuses `wizardCanUseTool`\n * (deny → block) then the YARA content scan (match → block). Fail-closed: any\n * thrown error blocks.\n */\nexport async function evaluateToolCall(\n toolName: string,\n input: Record<string, unknown>,\n ctx: ToolGateContext = {},\n llmProvider?: LLMProvider,\n): Promise<GateDecision> {\n try {\n const policy = toClaudePolicyCall(toolName, input);\n const decision = wizardCanUseTool(policy.name, policy.input, {\n disallowedTools: ctx.disallowedTools,\n wizardAskPending: ctx.getWizardAskPending?.() ?? false,\n });\n // The allowlist is a pi-only restriction; the anthropic arm runs bash\n // unrestricted and leans on the shared YARA scan. Let a plain `rm` of\n // project files through to that same scan so pi matches that behavior.\n const allowedLikeAnthropic =\n toolName === 'bash' &&\n isScopedFileRemoval(str(input.command), ctx.workingDirectory);\n\n if (decision.behavior === 'deny' && !allowedLikeAnthropic) {\n return { block: true, reason: decision.message };\n }\n\n const yaraReason = await preExecutionYaraBlock(\n toolName,\n input,\n llmProvider,\n ctx.repeatTracker,\n );\n if (yaraReason) return { block: true, reason: yaraReason };\n\n if (toolName === 'write') {\n const shrinkReason = await overwriteShrinkBlock(\n input,\n ctx.workingDirectory,\n );\n if (shrinkReason) return { block: true, reason: shrinkReason };\n }\n\n return { block: false };\n } catch (err) {\n logToFile('[pi-security] gate error — failing closed:', err);\n return {\n block: true,\n reason: 'Security check failed; tool blocked (fail-closed).',\n };\n }\n}\n\n/** pi result tool names whose OUTPUT gets scanned for 'input'-context rules. */\nfunction isPostScanTool(toolName: string): boolean {\n return toolName === 'read' || toolName === 'bash';\n}\n\n/** Mutable state the backend reads after the run to classify the outcome. */\nexport interface SecurityState {\n criticalViolation: boolean;\n blockedCount: number;\n toolCalls: number;\n}\n\n/**\n * Build the pi security extension + the shared state the backend inspects.\n * Install the returned factory via `extensionFactories`; pass the same factory\n * into every subagent session so the fence is inherited.\n */\nexport function createSecurityExtension(ctx: ToolGateContext = {}): {\n factory: (pi: PiExtensionApiLike) => void;\n state: SecurityState;\n} {\n const state: SecurityState = {\n criticalViolation: false,\n blockedCount: 0,\n toolCalls: 0,\n };\n\n // One triage provider per extension. Undefined (no gateway auth) means\n // triage is skipped and every flagged match is acted on — fail closed.\n const llmProvider = ctx.triageProvider;\n\n // One tracker per extension = per (sub)agent session, so an identical\n // payload retried after a block gets the escalating reason.\n const gateCtx: ToolGateContext = {\n ...ctx,\n repeatTracker: ctx.repeatTracker ?? createRepeatBlockTracker(),\n };\n\n const factory = (pi: PiExtensionApiLike): void => {\n pi.on('tool_call', async (event) => {\n // A latched post-scan violation blocks everything that follows.\n if (state.criticalViolation) {\n return {\n block: true,\n reason: 'Run terminated by a security violation.',\n };\n }\n state.toolCalls += 1;\n if (state.toolCalls > MAX_TOOL_CALLS) {\n return {\n block: true,\n reason: `Stopped: exceeded ${MAX_TOOL_CALLS} tool calls (runaway guard).`,\n };\n }\n const decision = await evaluateToolCall(\n event.toolName,\n event.input ?? {},\n gateCtx,\n llmProvider,\n );\n if (decision.block) {\n state.blockedCount += 1;\n logToFile(`[pi-security] BLOCK ${event.toolName}: ${decision.reason}`);\n return { block: true, reason: decision.reason };\n }\n return {};\n });\n\n pi.on('tool_result', async (event) => {\n if (!isPostScanTool(event.toolName)) return {};\n const text = (event.content ?? [])\n .map((c) => (c && c.type === 'text' ? c.text : ''))\n .join('\\n');\n if (!text) return {};\n try {\n // 'input'-context rules (prompt injection in read content), with\n // triage — same policy as the anthropic PostToolUse Read hook.\n const matches = await scanAndTriage(text, 'input', llmProvider);\n const verdict = scanVerdict(matches);\n // Count the scan even when clean, like the anthropic hooks do.\n recordExternalScan(\n 'PostToolUse',\n event.toolName === 'read' ? 'Read' : 'Bash',\n matches.map(toReportViolation),\n verdict?.action ?? 'warned',\n );\n if (!verdict) return {};\n if (verdict.terminal) {\n state.criticalViolation = true;\n logToFile(\n `[pi-security] POST-SCAN VIOLATION ${event.toolName}: ${verdict.match.rule}`,\n );\n } else {\n logToFile(\n `[pi-security] POST-SCAN WARNING ${event.toolName}: ${\n verdict.match.rule\n } (severity: ${verdict.match.metadata.severity ?? 'unknown'})`,\n );\n }\n } catch (err) {\n // Fail closed: a scanner error on output latches a violation.\n state.criticalViolation = true;\n logToFile('[pi-security] post-scan error — failing closed:', err);\n }\n return {};\n });\n };\n\n return { factory, state };\n}\n\n/**\n * Minimal structural type for pi's ExtensionAPI — just the `on` overloads we\n * use. Kept local so this module has no value import from the pi SDK (so the\n * CommonJS unit tests can load it directly). Handlers may return promises —\n * pi's real ExtensionHandler is `(event, ctx) => Promise<R | void> | R | void`.\n */\nexport interface PiExtensionApiLike {\n on(\n event: 'tool_call',\n handler: (event: {\n toolName: string;\n input?: Record<string, unknown>;\n }) =>\n | { block?: boolean; reason?: string }\n | Promise<{ block?: boolean; reason?: string }>,\n ): void;\n on(\n event: 'tool_result',\n handler: (event: {\n toolName: string;\n content?: Array<{ type: string; text?: string }>;\n isError?: boolean;\n }) => Record<string, never> | Promise<Record<string, never>>,\n ): void;\n}\n"],"mappings":"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"}