@posthog/wizard 2.79.0 → 2.79.1

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 (142) hide show
  1. package/dist/PickerMenu-dG5DQCZA.js +758 -0
  2. package/dist/PickerMenu-dG5DQCZA.js.map +1 -0
  3. package/dist/{add-mcp-server-to-clients-amlJWW7W.js → add-mcp-server-to-clients-DIaTRWMY.js} +7 -18
  4. package/dist/{add-mcp-server-to-clients-amlJWW7W.js.map → add-mcp-server-to-clients-DIaTRWMY.js.map} +1 -1
  5. package/dist/add-mcp-server-to-clients-artmN7mz.js +2 -0
  6. package/dist/agent-BEjARD2a.js +6 -0
  7. package/dist/{agent-CNrEC-9v.js → agent-BWFhuLgo.js} +18 -24
  8. package/dist/{agent-CNrEC-9v.js.map → agent-BWFhuLgo.js.map} +1 -1
  9. package/dist/{agent-interface-BXvmAm4c.js → agent-interface-FkbaBJwz.js} +29 -17
  10. package/dist/agent-interface-FkbaBJwz.js.map +1 -0
  11. package/dist/{agent-prompt-loader-DiKUbhi_.js → agent-prompt-loader-D0weQ-7k.js} +5 -5
  12. package/dist/{agent-prompt-loader-DiKUbhi_.js.map → agent-prompt-loader-D0weQ-7k.js.map} +1 -1
  13. package/dist/analytics-DFtbRjYh.js +2 -0
  14. package/dist/{analytics-VRYdebXW.js → analytics-DXK1gc12.js} +4 -4
  15. package/dist/{analytics-VRYdebXW.js.map → analytics-DXK1gc12.js.map} +1 -1
  16. package/dist/api-Cm9UtKZh.js +2 -0
  17. package/dist/{api-DGuJTzjr.js → api-CrbCIQOz.js} +4 -16
  18. package/dist/{api-DGuJTzjr.js.map → api-CrbCIQOz.js.map} +1 -1
  19. package/dist/bin.js +5 -2960
  20. package/dist/bin.js.map +1 -1
  21. package/dist/{bounded-fs-Cpn129A4.js → bounded-fs-vAq-T-K7.js} +3 -3
  22. package/dist/{bounded-fs-Cpn129A4.js.map → bounded-fs-vAq-T-K7.js.map} +1 -1
  23. package/dist/{ci-install-BPA6gzEq.js → ci-install-COcV8SY2.js} +8 -7
  24. package/dist/{ci-install-BPA6gzEq.js.map → ci-install-COcV8SY2.js.map} +1 -1
  25. package/dist/{constants-CuJfQEcC.js → constants-DHCBSfnx.js} +3 -3
  26. package/dist/{constants-CuJfQEcC.js.map → constants-DHCBSfnx.js.map} +1 -1
  27. package/dist/{debug-B9k4ideV.js → debug-BuYHGr6_.js} +1 -1
  28. package/dist/{debug-Cj-8vfXY.js → debug-kAHwWGg7.js} +2 -2
  29. package/dist/{debug-Cj-8vfXY.js.map → debug-kAHwWGg7.js.map} +1 -1
  30. package/dist/{defaults-C6J7yL4O.js → defaults-a9TelDhJ.js} +1 -1
  31. package/dist/{defaults-C6J7yL4O.js.map → defaults-a9TelDhJ.js.map} +1 -1
  32. package/dist/emit-BSetXzDV.js +69 -0
  33. package/dist/emit-BSetXzDV.js.map +1 -0
  34. package/dist/{endpoints-DnIX0pZH.js → endpoints-BVMYm9F4.js} +3 -3
  35. package/dist/{endpoints-DnIX0pZH.js.map → endpoints-BVMYm9F4.js.map} +1 -1
  36. package/dist/{env-api-key-BWdKpGEr.js → env-api-key-BP-NFFXt.js} +3 -5
  37. package/dist/{env-api-key-BWdKpGEr.js.map → env-api-key-BP-NFFXt.js.map} +1 -1
  38. package/dist/env-api-key-CaWEDj_9.js +2 -0
  39. package/dist/{environment-DZYUSCpV.js → environment-BATlQxcQ.js} +5 -10
  40. package/dist/{environment-DZYUSCpV.js.map → environment-BATlQxcQ.js.map} +1 -1
  41. package/dist/environment-CwD8OPYj.js +2 -0
  42. package/dist/{errors-ddDE6MbA.js → errors-C2domSkK.js} +3 -67
  43. package/dist/errors-C2domSkK.js.map +1 -0
  44. package/dist/file-C5JMsyR1.js +2 -0
  45. package/dist/{file-dEhr-l3n.js → file-DcRKycV8.js} +3 -3
  46. package/dist/{file-dEhr-l3n.js.map → file-DcRKycV8.js.map} +1 -1
  47. package/dist/{file-watcher-CPM-Nn9L.js → file-watcher-Brq2nnHV.js} +2 -2
  48. package/dist/{file-watcher-CPM-Nn9L.js.map → file-watcher-Brq2nnHV.js.map} +1 -1
  49. package/dist/{gateway-CVU1G0H3.js → gateway-C9-GlLO9.js} +8 -7
  50. package/dist/{gateway-CVU1G0H3.js.map → gateway-C9-GlLO9.js.map} +1 -1
  51. package/dist/{headless-ui-pY2xB5ug.js → headless-ui-CqI2gXlD.js} +2 -2
  52. package/dist/{headless-ui-pY2xB5ug.js.map → headless-ui-CqI2gXlD.js.map} +1 -1
  53. package/dist/{interactive-BOGlinZe.js → interactive-B_Ti_CsS.js} +3 -3
  54. package/dist/{interactive-BOGlinZe.js.map → interactive-B_Ti_CsS.js.map} +1 -1
  55. package/dist/{logging-ui-CUxNYF2t.js → logging-ui-Dza4D0MK.js} +5 -5
  56. package/dist/{logging-ui-CUxNYF2t.js.map → logging-ui-Dza4D0MK.js.map} +1 -1
  57. package/dist/main-CgKxbwMP.js +1396 -0
  58. package/dist/main-CgKxbwMP.js.map +1 -0
  59. package/dist/{mcp-JcsBBnWv.js → mcp-DnAyW0-w.js} +4 -4
  60. package/dist/{mcp-JcsBBnWv.js.map → mcp-DnAyW0-w.js.map} +1 -1
  61. package/dist/{mcp-prompt-streaming-B-p_Pvb2.js → mcp-prompt-streaming-DDWQ-FCA.js} +6 -6
  62. package/dist/{mcp-prompt-streaming-B-p_Pvb2.js.map → mcp-prompt-streaming-DDWQ-FCA.js.map} +1 -1
  63. package/dist/{mint-failure-CZFZpHpW.js → mint-failure-u8xQKNOn.js} +23 -22
  64. package/dist/{mint-failure-CZFZpHpW.js.map → mint-failure-u8xQKNOn.js.map} +1 -1
  65. package/dist/{models-DnAY67Lg.js → models-WcwUnSKb.js} +2 -2
  66. package/dist/{models-DnAY67Lg.js.map → models-WcwUnSKb.js.map} +1 -1
  67. package/dist/{non-interactive-DR1CWXY1.js → non-interactive-DyVC1AGc.js} +4 -3
  68. package/dist/{non-interactive-DR1CWXY1.js.map → non-interactive-DyVC1AGc.js.map} +1 -1
  69. package/dist/{oauth-session-CQTFOk-I.js → oauth-session-DYM2Hrl2.js} +2 -2
  70. package/dist/{oauth-session-CQTFOk-I.js.map → oauth-session-DYM2Hrl2.js.map} +1 -1
  71. package/dist/{orchestrator-tools-CBdt-SXO.js → orchestrator-tools-DbgNBFGA.js} +3 -3
  72. package/dist/{orchestrator-tools-CBdt-SXO.js.map → orchestrator-tools-DbgNBFGA.js.map} +1 -1
  73. package/dist/{package-json-DY70kkNp.js → package-json-Atg34fa4.js} +2 -8
  74. package/dist/{package-json-DY70kkNp.js.map → package-json-Atg34fa4.js.map} +1 -1
  75. package/dist/package-json-D8Ga03tK.js +2 -0
  76. package/dist/{pi-BtqcLX3y.js → pi-8KsYsPmM.js} +17 -16
  77. package/dist/{pi-BtqcLX3y.js.map → pi-8KsYsPmM.js.map} +1 -1
  78. package/dist/{playground-Dz-ZVjBk.js → playground-BlYRBMj7.js} +11 -11
  79. package/dist/{playground-Dz-ZVjBk.js.map → playground-BlYRBMj7.js.map} +1 -1
  80. package/dist/{posthog-6Uqw_Eux.js → posthog-Bt0Aplb8.js} +3 -8
  81. package/dist/{posthog-6Uqw_Eux.js.map → posthog-Bt0Aplb8.js.map} +1 -1
  82. package/dist/posthog-CUgCEhH0.js +2 -0
  83. package/dist/{provisioning-Zf7yPcEs.js → provisioning-Bo2NRxFz.js} +5 -11
  84. package/dist/{provisioning-Zf7yPcEs.js.map → provisioning-Bo2NRxFz.js.map} +1 -1
  85. package/dist/provisioning-OGFGlzf7.js +2 -0
  86. package/dist/{queue-tools-B7_aCyd-.js → queue-tools-C8mXOh7e.js} +3 -3
  87. package/dist/{queue-tools-B7_aCyd-.js.map → queue-tools-C8mXOh7e.js.map} +1 -1
  88. package/dist/registry-C06e_x-p.js +2 -0
  89. package/dist/{registry-B6iLGorH.js → registry-CD5RQt38.js} +7 -9
  90. package/dist/{registry-B6iLGorH.js.map → registry-CD5RQt38.js.map} +1 -1
  91. package/dist/run-agent-legacy-ChacZImU.js +2 -0
  92. package/dist/{run-agent-legacy-6WcYMaR9.js → run-agent-legacy-DOlM6DQu.js} +20 -21
  93. package/dist/{run-agent-legacy-6WcYMaR9.js.map → run-agent-legacy-DOlM6DQu.js.map} +1 -1
  94. package/dist/runners-ov3EXF1n.js +834 -0
  95. package/dist/runners-ov3EXF1n.js.map +1 -0
  96. package/dist/{security-lkBwWbrK.js → security-C71pkwqH.js} +5 -5
  97. package/dist/{security-lkBwWbrK.js.map → security-C71pkwqH.js.map} +1 -1
  98. package/dist/{setup-utils-dCViCC6s.js → setup-utils-B26Lf2yF.js} +11 -21
  99. package/dist/{setup-utils-dCViCC6s.js.map → setup-utils-B26Lf2yF.js.map} +1 -1
  100. package/dist/setup-utils-CVS86NYR.js +2 -0
  101. package/dist/{start-tui-oCWMgsDP.js → start-tui-Bo2L0bar.js} +28 -26
  102. package/dist/{start-tui-oCWMgsDP.js.map → start-tui-Bo2L0bar.js.map} +1 -1
  103. package/dist/{steps-gx0Pq4I4.js → steps-BPcg3_3Q.js} +7 -7
  104. package/dist/{steps-gx0Pq4I4.js.map → steps-BPcg3_3Q.js.map} +1 -1
  105. package/dist/{store-xYy_6Ruv.js → store-BTzcvRGT.js} +11 -17
  106. package/dist/{store-xYy_6Ruv.js.map → store-BTzcvRGT.js.map} +1 -1
  107. package/dist/store-BpqySPXm.js +5 -0
  108. package/dist/{subagent-Bfy0sfhU.js → subagent-D1TrTchE.js} +3 -3
  109. package/dist/{subagent-Bfy0sfhU.js.map → subagent-D1TrTchE.js.map} +1 -1
  110. package/dist/{task-BOfw7GPc.js → task-DqRlQBfs.js} +18 -16
  111. package/dist/task-DqRlQBfs.js.map +1 -0
  112. package/dist/{task-stream-kYlm9qd0.js → task-stream-Bw6JaLCX.js} +8 -8
  113. package/dist/{task-stream-kYlm9qd0.js.map → task-stream-Bw6JaLCX.js.map} +1 -1
  114. package/dist/{tasks-Bbv_lOBG.js → tasks-ChWufsdO.js} +1 -1
  115. package/dist/{tasks-Bbv_lOBG.js.map → tasks-ChWufsdO.js.map} +1 -1
  116. package/dist/{telemetry-Bpv-Mh5B.js → telemetry-B7zJdRmA.js} +3 -3
  117. package/dist/{telemetry-Bpv-Mh5B.js.map → telemetry-B7zJdRmA.js.map} +1 -1
  118. package/dist/{terminal-D07EQ2y0.js → terminal-zgkEfoCL.js} +15 -15
  119. package/dist/{terminal-D07EQ2y0.js.map → terminal-zgkEfoCL.js.map} +1 -1
  120. package/dist/{tools-Cc41af5W.js → tools-CkJ2y9Wt.js} +9 -9
  121. package/dist/tools-CkJ2y9Wt.js.map +1 -0
  122. package/dist/{ui-CGXtC7Vn.js → ui-Q6-RvERF.js} +3 -3
  123. package/dist/{ui-CGXtC7Vn.js.map → ui-Q6-RvERF.js.map} +1 -1
  124. package/dist/{version-Cadof0Z_.js → version-CHhJjz_H.js} +2 -2
  125. package/dist/{version-Cadof0Z_.js.map → version-CHhJjz_H.js.map} +1 -1
  126. package/dist/{wizard-abort-HDbP3YcS.js → wizard-abort-BPdv07np.js} +7 -6
  127. package/dist/{wizard-abort-HDbP3YcS.js.map → wizard-abort-BPdv07np.js.map} +1 -1
  128. package/dist/wizard-abort-Br1qMDNa.js +3 -0
  129. package/dist/{wizard-ui-DoyOZxK8.js → wizard-ui-DVqrYaOM.js} +1 -1
  130. package/dist/{wizard-ui-DoyOZxK8.js.map → wizard-ui-DVqrYaOM.js.map} +1 -1
  131. package/dist/{yara-hooks-DCfROpZt.js → yara-hooks-BnxejtXK.js} +5 -5
  132. package/dist/{yara-hooks-DCfROpZt.js.map → yara-hooks-BnxejtXK.js.map} +1 -1
  133. package/dist/yara-hooks-DSuExwb4.js +2 -0
  134. package/package.json +1 -1
  135. package/dist/agent-interface-BXvmAm4c.js.map +0 -1
  136. package/dist/analytics-DxwRi2Hf.js +0 -2
  137. package/dist/errors-ddDE6MbA.js.map +0 -1
  138. package/dist/rolldown-runtime-B_-DWIq7.js +0 -15
  139. package/dist/task-BOfw7GPc.js.map +0 -1
  140. package/dist/tools-Cc41af5W.js.map +0 -1
  141. package/dist/wizard-abort-Y-Ip2K4z.js +0 -3
  142. package/dist/yara-hooks-B49RIk5d.js +0 -2
@@ -0,0 +1 @@
1
+ {"version":3,"file":"task-DqRlQBfs.js","names":[],"sources":["../src/agent/runner/harness/pi/task.ts"],"sourcesContent":["/**\n * Orchestrator-mode execution on pi: one fresh pi session per unit of work —\n * the seed plan, or one drained task. The linear pipeline's concerns (skill\n * menu, todo panel, event-plan cleanup) stay in `index.ts`; this module builds\n * the leaner per-task session: gateway model, security fence, the task's\n * allowed coding tools, the wizard env tools, and the in-process orchestrator\n * queue tools.\n *\n * The task's `allowedTools` / `disallowedTools` arrive in the wizard's tool\n * vocabulary (`Read`, `Edit`, `Glob`, …, plus MCP-qualified orchestrator names\n * from `agentRunTools`). pi is where they become real: allowed names decide\n * which pi tool definitions get registered at all, and the disallow list is\n * ALSO handed to the security fence, so a name that never got registered stays\n * blocked even if the model hallucinates it.\n *\n * Loaded lazily from `index.ts` (typebox/ESM constraint, same as tools.ts).\n */\n\nimport { logToFile } from '@utils/debug';\nimport { analytics } from '@utils/analytics';\nimport {\n Harness,\n Sequence,\n WIZARD_REMARK_EVENT_NAME,\n WIZARD_USER_AGENT,\n} from '@shared/constants';\nimport {\n allowsPostHogMcp,\n queueTools,\n renderToolInventory,\n} from '@agent/agent-prompt-loader';\nimport { AgentErrorType } from '@agent/agent-interface';\nimport { REMARK_INSTRUCTION } from '@agent/signals';\nimport { AgentOutputSignals } from '@agent/output-signals';\nimport { TaskStatus } from '../../sequence/orchestrator/queue';\nimport type { OrchestratorToolsContext } from '../../sequence/orchestrator/queue-tools';\nimport type { AgentResult, TaskRunInputs } from '../types';\nimport { gatewayAuth, type GatewayAuth } from '@agent/gateway-session';\nimport { currentAccessToken } from '@shared/oauth-session';\nimport {\n buildGatewayProvider,\n GATEWAY_PROVIDER,\n withGatewayRemint,\n} from './gateway';\nimport { runErrorType } from './completion';\nimport { bindPiCancellation } from './cancellation';\nimport { classifyRunFailure, ErrorCodes } from '@shared/errors';\nimport { assembleCommandments } from '../../switchboard/commandments';\nimport {\n applyOutroMarkers,\n buildScrubbedEnv,\n extractText,\n lastStatusLine,\n withMode,\n} from './index';\nimport { createAioCapture } from '@agent/aio-capture';\n\n/** wizard tool vocabulary → the pi tool definitions it unlocks. */\nconst CODING_TOOL_MAP: Record<string, readonly string[]> = {\n Read: ['read'],\n Edit: ['edit'],\n Write: ['write'],\n Bash: ['bash'],\n Glob: ['find', 'ls'],\n Grep: ['grep'],\n};\n\n/** `mcp__wizard-tools__wizard_ask` → `wizard_ask`; native names pass through. */\nfunction shortToolName(name: string): string {\n return name.replace(/^mcp__.+?__/, '');\n}\n\n/**\n * The pi coding tools a task may use. An empty allow list means \"no\n * restriction\" (mirrors the SDK), so every coding tool registers.\n */\nexport function allowedPiCodingTools(\n allowedTools: readonly string[] | undefined,\n): Set<string> {\n const allowed = (allowedTools ?? []).map(shortToolName);\n const names = allowed.length\n ? allowed.flatMap((name) => CODING_TOOL_MAP[name] ?? [])\n : Object.values(CODING_TOOL_MAP).flat();\n return new Set(names);\n}\n\n/**\n * The orchestrator queue tools this agent gets. Everything not disallowed:\n * the seed's frontmatter disallows `complete_task` (it is not a task), a\n * task's disallows `enqueue_task` (the seed owns the graph).\n */\nexport function allowedOrchestratorTools(\n disallowedTools: readonly string[] | undefined,\n): Set<string> {\n return new Set(queueTools(disallowedTools ?? []));\n}\n\n/**\n * The wizard tools a task gets. Four are always on — their handlers are fenced\n * and the coding tasks depend on them. The rest are opt-in per task through\n * its frontmatter: `wizard_ask` stops the run until a person answers, and the\n * skill-menu pair (`load_skill_menu`, `install_skill`) lets a task pull its\n * own skill variant.\n */\nconst ALWAYS_ON_WIZARD_TOOLS = [\n 'check_env_keys',\n 'set_env_values',\n 'detect_package_manager',\n 'publish_handoff',\n];\n\nconst OPT_IN_WIZARD_TOOLS = [\n 'wizard_ask',\n 'load_skill_menu',\n 'install_skill',\n // Audit programs only: they declare the ledger tools on `allowedTools`.\n 'audit_seed_checks',\n 'audit_add_checks',\n 'audit_resolve_checks',\n];\n\nexport function allowedPiWizardTools(\n allowedTools: readonly string[] | undefined,\n): Set<string> {\n const allowed = (allowedTools ?? []).map(shortToolName);\n return new Set([\n ...ALWAYS_ON_WIZARD_TOOLS,\n ...OPT_IN_WIZARD_TOOLS.filter((tool) => allowed.includes(tool)),\n ]);\n}\n\n/**\n * The disallow list for the security fence: the wizard-vocabulary names as\n * given (the fence translates pi built-ins to the same vocabulary) plus the\n * short names, so a disallowed orchestrator tool is blocked under the name pi\n * would call it by.\n */\nexport function fenceDisallowList(\n disallowedTools: readonly string[] | undefined,\n): string[] {\n const names = disallowedTools ?? [];\n return [...new Set([...names, ...names.map(shortToolName)])];\n}\n\n/** Nudges when the session returns without the work reaching a terminal state. */\nconst MAX_TASK_NUDGES = 3;\n\nconst TASK_NUDGE =\n 'You have not called complete_task yet. Finish your task now: if the work is done, call complete_task with your handoff; if it cannot be done, call it with status \"failed\" or \"not needed\" and say why.';\n\nconst SEED_NUDGE =\n 'The queue is still empty. Seed it now with enqueue_task calls for the task graph you planned.';\n\n/** Whether this unit of work has reached its terminal state. */\nfunction isSettled(ctx: OrchestratorToolsContext): boolean {\n if (!ctx.currentTaskId) {\n // The seed's job is a seeded queue, not a complete_task call.\n return ctx.store.list().length > 0;\n }\n const task = ctx.store.get(ctx.currentTaskId);\n return (\n !!task &&\n (task.status === TaskStatus.Done ||\n task.status === TaskStatus.Failed ||\n task.status === TaskStatus.Skipped)\n );\n}\n\nexport async function runPiTask(inputs: TaskRunInputs): Promise<AgentResult> {\n if (inputs.signal?.aborted) {\n return {\n kind: 'abort',\n classification: AgentErrorType.ABORT,\n message: 'Agent run cancelled',\n };\n }\n const {\n config,\n input,\n boot,\n emit,\n prompt,\n spinner,\n model: modelId,\n effort,\n allowedTools,\n disallowedTools,\n askBridge,\n orchestrator,\n spinnerMessage,\n successMessage,\n errorMessage,\n requestRemark,\n analyticsProperties,\n } = inputs;\n\n if (spinnerMessage) spinner.start(spinnerMessage);\n\n const capture = createAioCapture({\n enabled: input.flags.captureAio,\n projectApiKey: boot.credentials.projectApiKey,\n apiHost: boot.credentials.host.apiHost,\n runTags: boot.wizardMetadata,\n });\n\n const startTime = Date.now();\n const signals = new AgentOutputSignals();\n let assistantTurns = 0;\n const runDurations = () => {\n const durationMs = Date.now() - startTime;\n return {\n duration_ms: durationMs,\n duration_seconds: Math.round(durationMs / 1000),\n };\n };\n // How this task run failed: the closed AgentErrorType it is about to return.\n // Without it every terminal path below — a security termination, a rate\n // limit, a gateway error — arrived as the same unlabelled event, so a task\n // agent that died could be counted but not diagnosed. A security stop is not\n // an error, hence \"failure mode\" over \"error\".\n //\n // Not `reason`: the linear sequence emits this same event with a `reason`\n // holding the agent's free-text [ABORT] string, and one property cannot be\n // both a closed enum and unbounded prose without making either unreadable.\n const captureAborted = (failureMode: AgentErrorType) =>\n analytics.wizardCapture('agent aborted', {\n failure_mode: failureMode,\n ...runDurations(),\n model: modelId,\n ...analyticsProperties,\n });\n\n let mcpCleanup: (() => void) | undefined;\n let aioFailed = true;\n let cancellation: ReturnType<typeof bindPiCancellation> | undefined;\n try {\n const sdk = await import('@earendil-works/pi-coding-agent');\n const {\n createAgentSession,\n DefaultResourceLoader,\n SessionManager,\n AuthStorage,\n ModelRegistry,\n getAgentDir,\n createLsToolDefinition,\n createFindToolDefinition,\n createGrepToolDefinition,\n createBashToolDefinition,\n createReadToolDefinition,\n createEditToolDefinition,\n createWriteToolDefinition,\n } = sdk;\n\n // Reads the live OAuth token, so a mid-run rotation re-mints on the new one.\n const refreshAuth = async () =>\n gatewayAuth(\n boot.credentials.host,\n await currentAccessToken(boot.credentials),\n boot.programId,\n );\n const auth = await refreshAuth();\n const providerInputs = (current: GatewayAuth) => ({\n gatewayUrl: current.gatewayUrl,\n accessToken: current.token,\n teamId: current.teamId,\n wizardMetadata: boot.wizardMetadata,\n wizardFlags: boot.wizardFlags,\n modelId,\n // Per-task agents own their effort via the prompt frontmatter, falling\n // back to the model table.\n effort,\n });\n const { provider, caps } = buildGatewayProvider(providerInputs(auth));\n const registry = ModelRegistry.inMemory(AuthStorage.create());\n registry.registerProvider(GATEWAY_PROVIDER, provider as never);\n const model = registry.find(GATEWAY_PROVIDER, modelId);\n if (!model) {\n return {\n kind: 'failure',\n classification: AgentErrorType.API_ERROR,\n message: 'pi: gateway model could not be resolved',\n };\n }\n\n // Shared flag: true while a wizard_ask overlay is open. The ask tool sets\n // it (onAskPendingChange, below); the security fence reads it to pause\n // Write/Edit until the answer comes back. Wired the same way as the linear\n // pi run — without it the warehouse task could mutate files while its\n // credential prompt sits open (up to LONGER_ASK_TIMEOUT_MS).\n const askState = { pending: false };\n\n // The same fail-closed fence as the linear run, with the task's disallow\n // list layered in (both the wizard-vocabulary and pi-short names).\n const { createSecurityExtension } = await import('./security');\n const security = createSecurityExtension({\n disallowedTools: fenceDisallowList(disallowedTools),\n triageProvider: boot.triageProvider,\n getWizardAskPending: () => askState.pending,\n });\n const { prewarmYaraScanner } = await import('@agent/yara-hooks');\n void prewarmYaraScanner();\n\n // PostHog MCP, for the tasks whose prompt requests it. Tasks that never\n // asked skip the handshake and never see a posthog tool.\n const extensionFactories = [security.factory] as Array<\n (pi: unknown) => void\n >;\n let posthogMcp = false;\n if (allowsPostHogMcp(allowedTools)) {\n try {\n const { setupPostHogMcp } = await import('./mcp');\n const mcp = await setupPostHogMcp({\n mcpUrl: boot.credentials.host.mcpUrl,\n accessToken: await currentAccessToken(boot.credentials),\n userAgent: WIZARD_USER_AGENT,\n });\n extensionFactories.push(mcp.extensionFactory);\n mcpCleanup = mcp.cleanup;\n posthogMcp = true;\n } catch (err) {\n try {\n mcpCleanup?.();\n } catch {\n /* Setup cleanup is best effort. */\n }\n mcpCleanup = undefined;\n // Silent here reads as a task failure minutes later: a task that asked\n // for this tool can only skip or fail without it.\n logToFile(`[pi-task] PostHog MCP setup skipped: ${String(err)}`);\n analytics.wizardCapture('mcp setup failed', {\n harness: 'pi',\n scope: 'task',\n error: String(err).slice(0, 300),\n });\n }\n }\n\n const codingTools = allowedPiCodingTools(allowedTools);\n const orchestratorTools = allowedOrchestratorTools(disallowedTools);\n\n const resourceLoader = new DefaultResourceLoader({\n cwd: input.installDir,\n agentDir: getAgentDir(),\n systemPrompt: assembleCommandments({\n program: config.programId,\n sequence: Sequence.orchestrator,\n harness: Harness.pi,\n caps: { bash: codingTools.has('bash'), posthogMcp },\n }),\n noExtensions: true,\n noSkills: true,\n noContextFiles: true,\n noPromptTemplates: true,\n noThemes: true,\n extensionFactories,\n });\n await resourceLoader.reload();\n\n // The task's coding tools, gated by its allow list. Reads and searches run\n // in parallel; mutating tools stay sequential. Bash subprocesses get the\n // scrubbed env, same as the linear run.\n const dir = input.installDir;\n const codingToolFactories = {\n read: () => withMode(createReadToolDefinition(dir), 'parallel'),\n edit: () => withMode(createEditToolDefinition(dir), 'sequential'),\n write: () => withMode(createWriteToolDefinition(dir), 'sequential'),\n bash: () =>\n withMode(\n createBashToolDefinition(dir, {\n spawnHook: (ctx) => ({ ...ctx, env: buildScrubbedEnv() }),\n }),\n 'sequential',\n ),\n ls: () => withMode(createLsToolDefinition(dir), 'parallel'),\n find: () => withMode(createFindToolDefinition(dir), 'parallel'),\n grep: () => withMode(createGrepToolDefinition(dir), 'parallel'),\n } as const;\n const codingToolDefs = Object.entries(codingToolFactories)\n .filter(([name]) => codingTools.has(name))\n .map(([, make]) => make());\n\n // Wizard env + package-manager tools are always on — their handlers are\n // fenced, and init/build tasks depend on them. publish_handoff rides\n // along (it only emits) so the report task can publish the handoff; every\n // other task is told, on the call, that it still owes a complete_task.\n const { createWizardPiTools } = await import('./tools');\n const wizardToolNames = allowedPiWizardTools(allowedTools);\n const wizardTools = createWizardPiTools({\n workingDirectory: dir,\n skillsBaseUrl: boot.skillsBaseUrl,\n triageProvider: boot.triageProvider,\n emit,\n taskAgent: orchestrator.currentTaskId !== undefined,\n // Present only for a task allowed to ask; without it wizard_ask errors\n // instead of hanging on a prompt nobody will ever see.\n askBridge,\n // Pause Write/Edit while the ask overlay is open (see askState above).\n onAskPendingChange: (pending) => {\n askState.pending = pending;\n },\n }).filter((t) => wizardToolNames.has(t.name));\n\n const { createPiOrchestratorTools } = await import('./orchestrator-tools');\n const queueTools = createPiOrchestratorTools(orchestrator).filter((t) =>\n orchestratorTools.has(t.name),\n );\n\n const customTools = [...codingToolDefs, ...wizardTools, ...queueTools];\n const { session: agentSession } = await createAgentSession({\n model,\n modelRegistry: registry,\n thinkingLevel: caps.thinkingLevel,\n cwd: dir,\n sessionManager: SessionManager.inMemory(dir),\n resourceLoader,\n noTools: 'builtin',\n customTools,\n });\n cancellation = bindPiCancellation(inputs.signal, agentSession, (error) => {\n logToFile(`[pi-task] abort failed: ${String(error)}`);\n });\n await agentSession.bindExtensions({});\n\n // A turn that ends on a 401 from an aged bearer re-mints once and\n // continues with the nudge the task would get anyway.\n const turns = withGatewayRemint({\n signal: inputs.signal,\n session: agentSession,\n registry,\n auth,\n refreshAuth,\n providerInputs,\n continueText: () =>\n orchestrator.currentTaskId ? TASK_NUDGE : SEED_NUDGE,\n onRemint: () => {\n logToFile('[pi-task] gateway token renewed after a 401; continuing');\n analytics.wizardCapture('gateway token reminted', { harness: 'pi' });\n },\n });\n\n // The one complete list: exactly the tools registered on this session, in\n // the names the agent will call them by. posthog_exec binds as an extension.\n const toolNames = [\n ...customTools.map((t) => t.name),\n ...(posthogMcp ? ['posthog_exec'] : []),\n ];\n logToFile(`[pi-task] tools passed to model: ${toolNames.join(', ')}`);\n const taskPrompt = `${prompt}\\n\\n${renderToolInventory(toolNames)}`;\n\n const unsubscribe = agentSession.subscribe((event) => {\n // Mirror the turn into AIO. No-op when --capture-aio is off. Runs\n // before the role guard so the module's own filter is authoritative.\n capture.captureFromPiMessageEndEvent(event);\n\n switch (event.type) {\n case 'message_end': {\n // User prompts also emit message_end; only assistant turns count.\n if ((event.message as { role?: string })?.role !== 'assistant') {\n break;\n }\n assistantTurns += 1;\n turns.noteAssistantTurn(event.message);\n const assistant = extractText(event.message).trim();\n if (assistant) {\n logToFile(`[pi-task] assistant: ${assistant.slice(0, 1000)}`);\n applyOutroMarkers(assistant, emit);\n const statusText = lastStatusLine(assistant);\n if (statusText) {\n emit({ kind: 'status', message: statusText });\n spinner.message(statusText);\n }\n for (const line of assistant.split('\\n')) signals.push(line);\n }\n break;\n }\n case 'tool_execution_start': {\n const args = JSON.stringify(event.args ?? {}).slice(0, 200);\n logToFile(`[pi-task] → ${event.toolName} ${args}`);\n break;\n }\n case 'tool_execution_end': {\n if (event.isError) {\n const detail =\n typeof event.result === 'string'\n ? event.result\n : JSON.stringify(event.result ?? '');\n logToFile(`[pi-task] ✗ ${event.toolName}: ${detail.slice(0, 300)}`);\n }\n break;\n }\n default:\n break;\n }\n });\n\n // Seed AIO capture with this task's prompt — includes any handoff data\n // from prior tasks (the orchestrator bakes it into the prompt string\n // before it reaches this call site).\n capture.setInitialPrompt(taskPrompt);\n\n let terminal = turns.terminalFailure();\n try {\n if (inputs.signal?.aborted)\n return {\n kind: 'abort',\n classification: AgentErrorType.ABORT,\n message: 'Agent run cancelled',\n };\n await turns.prompt(taskPrompt);\n terminal = turns.terminalFailure();\n\n // pi's prompt() resolves the moment a turn carries no tool call — which\n // an agent mid-plan does emit. While the work has not reached its\n // terminal state (task not reported, seed queue still empty), nudge.\n let nudges = 0;\n while (\n nudges < MAX_TASK_NUDGES &&\n !security.state.criticalViolation &&\n !inputs.signal?.aborted &&\n !terminal &&\n !isSettled(orchestrator)\n ) {\n nudges += 1;\n logToFile(\n `[pi-task] completion guard: not settled, nudge ${nudges}/${MAX_TASK_NUDGES}`,\n );\n await turns.prompt(\n orchestrator.currentTaskId ? TASK_NUDGE : SEED_NUDGE,\n );\n terminal = turns.terminalFailure();\n }\n\n if (\n requestRemark &&\n !security.state.criticalViolation &&\n !terminal &&\n !inputs.signal?.aborted\n ) {\n try {\n await agentSession.prompt(REMARK_INSTRUCTION);\n } catch (err) {\n logToFile(`[pi-task] remark request failed: ${String(err)}`);\n }\n }\n } finally {\n try {\n unsubscribe();\n } catch {\n /* Keep the terminal result. */\n }\n }\n\n if (inputs.signal?.aborted) {\n return {\n kind: 'abort',\n classification: AgentErrorType.ABORT,\n message: 'Agent run cancelled',\n };\n }\n\n if (terminal && !security.state.criticalViolation) {\n if (errorMessage || spinnerMessage)\n spinner.stop(errorMessage ?? 'Task failed');\n captureAborted(terminal.classification);\n if (terminal.status === 401) {\n return {\n kind: 'decided_failure',\n failure: {\n code: ErrorCodes.AuthInvalidOrExpired,\n message: 'Authentication failed (401)',\n detail: { providerMessage: terminal.message },\n },\n };\n }\n return terminal.classification === AgentErrorType.ABORT\n ? {\n kind: 'abort',\n classification: AgentErrorType.ABORT,\n message: terminal.message,\n }\n : {\n kind: 'failure',\n classification: terminal.classification,\n message: terminal.message,\n };\n }\n\n if (security.state.criticalViolation) {\n spinner.stop('Security violation detected');\n logToFile(\n `[pi-task] terminated: YARA violation (blocked ${security.state.blockedCount} call(s))`,\n );\n captureAborted(AgentErrorType.YARA_VIOLATION);\n return { kind: 'failure', classification: AgentErrorType.YARA_VIOLATION };\n }\n\n const remark = signals.remark();\n if (remark) {\n analytics.capture(WIZARD_REMARK_EVENT_NAME, { remark });\n }\n\n const stats = agentSession.getSessionStats();\n const durations = runDurations();\n analytics.wizardCapture('agent completed', {\n ...durations,\n model: modelId,\n num_turns: assistantTurns,\n input_tokens: stats.tokens.input,\n output_tokens: stats.tokens.output,\n cache_creation_input_tokens: stats.tokens.cacheWrite,\n cache_read_input_tokens: stats.tokens.cacheRead,\n ...analyticsProperties,\n });\n // Per-task usage on one parseable line so a run's per-task time and cost are\n // observable from the log, not only from analytics.\n const taskType =\n typeof (analyticsProperties as { task_type?: unknown })?.task_type ===\n 'string'\n ? (analyticsProperties as { task_type: string }).task_type\n : modelId;\n logToFile(\n `[pi-task] usage task=${taskType} model=${modelId} effort=${caps.thinkingLevel} dur=${durations.duration_seconds}s turns=${assistantTurns} in=${stats.tokens.input} out=${stats.tokens.output} cacheR=${stats.tokens.cacheRead} cacheW=${stats.tokens.cacheWrite}`,\n );\n if (successMessage) spinner.stop(successMessage);\n aioFailed = false;\n return { kind: 'success' };\n } catch (err) {\n if (inputs.signal?.aborted) {\n return {\n kind: 'abort',\n classification: AgentErrorType.ABORT,\n message: 'Agent run cancelled',\n };\n }\n const message = err instanceof Error ? err.message : String(err);\n logToFile(`[pi-task] run error: ${message}`);\n if (errorMessage || spinnerMessage) {\n spinner.stop(errorMessage ?? 'Task failed');\n }\n const coded = classifyRunFailure(err);\n if (coded.coded && err instanceof Error) {\n captureAborted(AgentErrorType.API_ERROR);\n return {\n kind: 'decided_failure',\n failure: { code: coded.code, message: coded.message, error: err },\n };\n }\n const classification = runErrorType(message);\n captureAborted(classification);\n return {\n kind: 'failure',\n classification,\n message,\n error: err instanceof Error ? err : undefined,\n };\n } finally {\n await cancellation?.settle();\n try {\n mcpCleanup?.();\n } catch {\n /* Keep the terminal result. */\n }\n try {\n capture.finishPiRun(aioFailed);\n } catch {\n /* Telemetry is best effort. */\n }\n }\n}\n"],"mappings":"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"}
@@ -1,11 +1,11 @@
1
- import { n as sanitizeErrorDetail } from "./errors-ddDE6MbA.js";
2
- import { s as logToFile } from "./debug-Cj-8vfXY.js";
3
- import "./constants-CuJfQEcC.js";
1
+ import { s as logToFile } from "./debug-kAHwWGg7.js";
2
+ import "./constants-DHCBSfnx.js";
4
3
  import "./wizard-session-2w4Ru-Wz.js";
5
- import "./wizard-ui-DoyOZxK8.js";
6
- import { t as startFileWatcher } from "./file-watcher-CPM-Nn9L.js";
7
- import { t as PostHogDestination } from "./posthog-6Uqw_Eux.js";
8
- import { createFileDestination } from "./file-dEhr-l3n.js";
4
+ import { n as sanitizeErrorDetail } from "./errors-C2domSkK.js";
5
+ import "./wizard-ui-DVqrYaOM.js";
6
+ import { t as startFileWatcher } from "./file-watcher-Brq2nnHV.js";
7
+ import { t as PostHogDestination } from "./posthog-Bt0Aplb8.js";
8
+ import { n as createFileDestination } from "./file-DcRKycV8.js";
9
9
  //#region src/programs/task-stream/event-plan-watcher.ts
10
10
  const MAX_EVENT_PLAN_FILE_BYTES = 256 * 1024;
11
11
  const MAX_EVENT_COUNT = 50;
@@ -360,4 +360,4 @@ var TaskStreamPush = class {
360
360
  //#endregion
361
361
  export { PostHogDestination, TaskStreamPush, createFileDestination };
362
362
 
363
- //# sourceMappingURL=task-stream-kYlm9qd0.js.map
363
+ //# sourceMappingURL=task-stream-Bw6JaLCX.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"task-stream-kYlm9qd0.js","names":[],"sources":["../src/programs/task-stream/event-plan-watcher.ts","../src/programs/task-stream/audit-areas.ts","../src/programs/task-stream/task-stream-push.ts"],"sourcesContent":["import type { PlannedEvent, WizardStore } from '@ui/tui/store';\nimport {\n startFileWatcher,\n type FileWatcherHandle,\n type FileWatcherOptions,\n} from '@lib/file-watcher';\n\nconst MAX_EVENT_PLAN_FILE_BYTES = 256 * 1024;\nconst MAX_EVENT_COUNT = 50;\nconst MAX_EVENT_NAME_LENGTH = 400;\nconst MAX_EVENT_DESCRIPTION_LENGTH = 4000;\n\nfunction firstString(...values: unknown[]): string | null {\n const value = values.find((candidate) => typeof candidate === 'string');\n return typeof value === 'string' ? value : null;\n}\n\nexport function normalizeEventPlan(parsed: unknown): PlannedEvent[] | null {\n if (!Array.isArray(parsed)) return null;\n\n const events: PlannedEvent[] = [];\n for (const value of parsed) {\n if (events.length >= MAX_EVENT_COUNT) break;\n\n const entry =\n value && typeof value === 'object'\n ? (value as Record<string, unknown>)\n : {};\n const name = firstString(entry.event_name, entry.name, entry.event);\n if (!name || !name.trim() || name.length > MAX_EVENT_NAME_LENGTH) continue;\n\n const description =\n firstString(entry.event_description, entry.description) ?? '';\n events.push({\n name,\n description: description.slice(0, MAX_EVENT_DESCRIPTION_LENGTH),\n });\n }\n\n return events;\n}\n\nexport class EventPlanWatcher {\n private handle: FileWatcherHandle | null = null;\n private captured = false;\n\n constructor(\n private readonly store: WizardStore,\n private readonly path: string,\n private readonly options: FileWatcherOptions = {},\n ) {}\n\n start(): void {\n if (this.handle || this.captured) return;\n\n this.handle = startFileWatcher(\n this.path,\n (parsed) => {\n const events = normalizeEventPlan(parsed);\n if (!events || events.length === 0) return;\n\n this.captured = true;\n this.store.setEventPlan(events);\n this.stop();\n },\n {\n ignoreInitialFile: true,\n maxFileSizeBytes: MAX_EVENT_PLAN_FILE_BYTES,\n ...this.options,\n },\n );\n }\n\n refresh(): void {\n if (this.captured) return;\n this.handle?.refresh();\n }\n\n stop(): void {\n this.handle?.stop();\n this.handle = null;\n }\n}\n","/**\n * Audit ledger → stream tasks: one row per audit *area*, never per check.\n *\n * A run resolves tens of checks, and each row carries a `file` path and a\n * `details` string derived from the user's code. Neither belongs on the wire.\n * A finding is progress, not a failure, so an area never reports `failed`.\n */\n\nimport type { AuditCheck, AuditStatus } from '@programs/audit/types';\nimport { StreamTaskStatus, type StreamTask } from './types';\n\nexport const MAX_AUDIT_AREAS = 24;\nconst MAX_AREA_TITLE_LENGTH = 80;\n\nconst RESOLVED: readonly AuditStatus[] = [\n 'pass',\n 'error',\n 'warning',\n 'suggestion',\n];\n\nfunction areaTitle(value: unknown): string | null {\n if (typeof value !== 'string' || !value.trim()) return null;\n return value.trim().slice(0, MAX_AREA_TITLE_LENGTH);\n}\n\n/** `idOffset` continues the program's task ids, which are stringified integers. */\nexport function rollUpAuditAreas(checks: unknown, idOffset = 0): StreamTask[] {\n if (!Array.isArray(checks)) return [];\n\n // Map keeps insertion order, which is the order the ledger seeds its areas in.\n const areas = new Map<string, { total: number; resolved: number }>();\n\n for (const entry of checks) {\n if (!entry || typeof entry !== 'object') continue;\n const title = areaTitle((entry as Partial<AuditCheck>).area);\n if (!title) continue;\n\n let counts = areas.get(title);\n if (!counts) {\n if (areas.size >= MAX_AUDIT_AREAS) continue;\n counts = { total: 0, resolved: 0 };\n areas.set(title, counts);\n }\n counts.total += 1;\n // The ledger is agent-written: anything unrecognized counts as unresolved.\n if (RESOLVED.includes((entry as Partial<AuditCheck>).status as AuditStatus))\n counts.resolved += 1;\n }\n\n return [...areas.entries()].map(([title, { total, resolved }], index) => ({\n id: String(idOffset + index),\n title,\n status:\n resolved === 0\n ? StreamTaskStatus.Pending\n : resolved < total\n ? StreamTaskStatus.InProgress\n : StreamTaskStatus.Completed,\n }));\n}\n","/**\n * Task-stream push — subscribes to WizardStore, builds payloads,\n * and fans out async to all registered destinations.\n *\n * Behaviour:\n * - `attach(store)` subscribe to store changes\n * - task updates debounced 250ms (trailing edge)\n * - phase transitions flush immediately, bypass debounce\n * - RunPhase.Idle skipped (no push)\n * - enabled === false destination delivery is disabled\n * - shutdown(timeoutMs) cancel pending, flush terminal phase\n * with timeout, never throw\n *\n * Concurrency: only one fan-out at a time. Emits during an in-flight\n * push are coalesced — at most one follow-up push fires with the\n * latest state once the current one settles.\n */\n\nimport type { WizardStore, TaskItem } from '@ui/tui/store';\nimport { TaskStatus } from '@ui/wizard-ui';\nimport {\n RunPhase,\n OutroKind,\n type OutroData,\n type PendingQuestion,\n} from '@lib/wizard-session';\nimport {\n type TaskStreamDestination,\n type TaskStreamUpdate,\n type StreamTask,\n type TaskStreamError,\n type StreamPendingInput,\n StreamTaskStatus,\n StreamEvent,\n} from './types';\nimport { EventPlanWatcher } from './event-plan-watcher';\nimport { rollUpAuditAreas } from './audit-areas';\nimport type { WizardRunSync, RunOutcome } from './wizard-run-sync';\nimport { logToFile } from '@utils/debug';\nimport { WIZARD_RUN_SYNC_FLAG_KEY } from '@shared/constants';\nimport { sanitizeErrorDetail } from '@shared/errors';\n\n/** Trailing-edge debounce window for non-phase-change emits. */\nconst DEBOUNCE_MS = 250;\n/** Default shutdown timeout for the final terminal flush. */\nconst DEFAULT_SHUTDOWN_TIMEOUT_MS = 2000;\n\nconst STATUS_MAP: Record<TaskStatus, StreamTaskStatus> = {\n [TaskStatus.Pending]: StreamTaskStatus.Pending,\n [TaskStatus.InProgress]: StreamTaskStatus.InProgress,\n [TaskStatus.Completed]: StreamTaskStatus.Completed,\n [TaskStatus.Failed]: StreamTaskStatus.Failed,\n // The stream has no skipped state; skipped is terminal, so report it resolved.\n [TaskStatus.Skipped]: StreamTaskStatus.Completed,\n};\n\nfunction buildTasks(items: TaskItem[]): StreamTask[] {\n return items.map((item, i) => ({\n id: String(i),\n title: item.label,\n status: STATUS_MAP[item.status] ?? StreamTaskStatus.Pending,\n }));\n}\n\n/** Drop \".SSSZ\" → \"Z\" so session_id segments stay routing-safe. */\nfunction secondPrecisionIso(d: Date): string {\n return d.toISOString().replace(/\\.\\d{3}Z$/, 'Z');\n}\n\n/**\n * `workflow_id` and `skill_id` end up unescaped in Redis pub/sub\n * channel names, so the backend rejects anything outside\n * `^[A-Za-z0-9_.-]{1,255}$` with a 400. All current values already\n * comply; this is defence in depth in case a future caller passes\n * something with `:`, spaces, or other separators.\n */\nfunction sanitizeChannelId(value: string): string {\n return value.replace(/[^A-Za-z0-9_.-]/g, '-').slice(0, 255);\n}\n\nfunction buildError(\n phase: RunPhase,\n outroData: OutroData | null,\n): TaskStreamError | undefined {\n if (phase !== RunPhase.Error) return undefined;\n if (outroData?.kind === OutroKind.Error) {\n const message = outroData.message ?? outroData.body ?? 'Wizard run failed';\n const error: TaskStreamError = { type: 'wizard_error', message };\n if (outroData.errorCode) error.code = outroData.errorCode;\n const safeDetail = sanitizeErrorDetail(outroData.errorDetail);\n if (safeDetail) error.detail = safeDetail;\n return error;\n }\n return { type: 'wizard_error', message: 'Wizard run failed' };\n}\n\n/**\n * Sensitive asks (secrets, API keys) publish only the fact that input is\n * required — never the prompt text. The session row is team-visible and\n * outlives the prompt.\n */\nfunction buildPendingInput(\n question: PendingQuestion | null | undefined,\n): StreamPendingInput | undefined {\n if (!question) return undefined;\n const sensitive = question.questions.some((q) => q.sensitive === true);\n return {\n id: question.id,\n asked_at: question.askedAt ?? new Date().toISOString(),\n question_count: question.questions.length,\n sensitive,\n prompts: sensitive ? undefined : question.questions.map((q) => q.prompt),\n };\n}\n\nexport interface TaskStreamPushOptions {\n store: WizardStore;\n runSync?: WizardRunSync;\n getFlags?: () => Readonly<Record<string, string>> | null;\n programId: string;\n destinations: TaskStreamDestination[];\n /** Optional absolute event-plan path to load into the store once. */\n eventPlanPath?: string;\n /** The run's audit ledger, when it has one. The runner owns the watcher. */\n auditChecks?: () => unknown;\n /** When false, destination subscription/delivery remains disabled. */\n enabled?: boolean;\n}\n\nexport class TaskStreamPush {\n private readonly store: WizardStore;\n private readonly destinations: TaskStreamDestination[];\n private readonly startedAt: string;\n private readonly programId: string;\n private readonly sessionId: string;\n private readonly eventPlanWatcher: EventPlanWatcher | null;\n private readonly auditChecks: (() => unknown) | null;\n\n private readonly runSync?: WizardRunSync;\n private readonly getFlags?: TaskStreamPushOptions['getFlags'];\n private remoteSync?: 'wizard-session' | 'wizard-run';\n private shutdownPromise?: Promise<void>;\n private enabled: boolean;\n private created = false;\n private lastPushedPhase: RunPhase | null = null;\n private lastPushedQuestionId: string | null = null;\n\n private unsubscribe: (() => void) | null = null;\n private debounceTimer: ReturnType<typeof setTimeout> | null = null;\n private inFlight: Promise<void> | null = null;\n private needsAnotherPush = false;\n private shuttingDown = false;\n\n constructor(opts: TaskStreamPushOptions) {\n this.store = opts.store;\n this.runSync = opts.runSync;\n this.getFlags = opts.getFlags;\n this.remoteSync = !opts.runSync\n ? 'wizard-session'\n : opts.getFlags\n ? undefined\n : 'wizard-run';\n this.programId = sanitizeChannelId(opts.programId);\n this.destinations = opts.destinations;\n this.enabled = opts.enabled ?? true;\n const startedAt = new Date();\n this.eventPlanWatcher = opts.eventPlanPath\n ? new EventPlanWatcher(this.store, opts.eventPlanPath)\n : null;\n this.auditChecks = opts.auditChecks ?? null;\n this.startedAt = secondPrecisionIso(startedAt);\n // skillId may not be set yet — fall back to programId so the\n // session_id is stable for the whole run regardless of when the\n // program metadata is populated.\n const skillId = sanitizeChannelId(\n this.store.session.skillId ?? this.programId,\n );\n this.sessionId = `${this.programId}-${skillId}-${this.startedAt}`;\n }\n\n /**\n * Load the event plan and subscribe to store changes. Destination delivery\n * remains disabled when `enabled === false`, but the plan still populates the\n * store for local and headless consumers.\n */\n attach(store?: WizardStore): void {\n this.eventPlanWatcher?.start();\n if (!this.enabled) return;\n if (this.unsubscribe) return;\n const target = store ?? this.store;\n this.unsubscribe = target.subscribe(() => this.onStoreChange());\n }\n\n /** Stop subscribing. Does not flush. */\n detach(): void {\n this.eventPlanWatcher?.stop();\n if (this.unsubscribe) {\n this.unsubscribe();\n this.unsubscribe = null;\n }\n if (this.debounceTimer) {\n clearTimeout(this.debounceTimer);\n this.debounceTimer = null;\n }\n }\n\n // Finalize execution while the legacy session continues through the outro.\n async finishRun(\n outcome: RunOutcome,\n timeoutMs = DEFAULT_SHUTDOWN_TIMEOUT_MS,\n ): Promise<void> {\n await this.runSync?.shutdown(outcome, timeoutMs);\n }\n\n shutdown(\n timeoutMs: number = DEFAULT_SHUTDOWN_TIMEOUT_MS,\n outcome: RunOutcome = this.store.session.runPhase === RunPhase.Completed\n ? 'completed'\n : 'failed',\n ): Promise<void> {\n this.shutdownPromise ??= Promise.all([\n this.runSync?.shutdown(outcome, timeoutMs),\n this.shutdownLegacy(timeoutMs),\n ]).then(() => undefined);\n return this.shutdownPromise;\n }\n\n private async shutdownLegacy(timeoutMs: number): Promise<void> {\n this.shuttingDown = true;\n this.eventPlanWatcher?.refresh();\n if (this.debounceTimer) {\n clearTimeout(this.debounceTimer);\n this.debounceTimer = null;\n }\n this.detach();\n if (!this.enabled) return;\n\n const phase = this.store.session.runPhase;\n const isTerminal = phase === RunPhase.Completed || phase === RunPhase.Error;\n if (!isTerminal) return;\n\n const flush = this.flush();\n if (timeoutMs <= 0) return;\n let timer: ReturnType<typeof setTimeout> | undefined;\n try {\n await Promise.race([\n flush,\n new Promise<void>((resolve) => {\n timer = setTimeout(resolve, timeoutMs);\n }),\n ]);\n } finally {\n clearTimeout(timer);\n }\n }\n\n /**\n * Imperative push — fires immediately regardless of phase. Kept as\n * the building block for both subscription-driven and direct calls.\n */\n async push(): Promise<void> {\n await this.flush();\n }\n\n // ── Internal ────────────────────────────────────────────────────\n\n private onStoreChange(): void {\n if (!this.enabled || this.shuttingDown) return;\n const phase = this.store.session.runPhase;\n if (phase === RunPhase.Idle) return;\n this.selectRemoteSync();\n if (this.remoteSync === 'wizard-run') {\n this.runSync?.capture(this.store.tasks);\n }\n\n // A push is already in flight — coalesce. The in-flight push's\n // settle handler will trigger one follow-up with the latest state.\n if (this.inFlight) {\n if (this.debounceTimer) {\n clearTimeout(this.debounceTimer);\n this.debounceTimer = null;\n }\n this.needsAnotherPush = true;\n return;\n }\n\n const phaseChanged = phase !== this.lastPushedPhase;\n const questionId = this.store.session.pendingQuestion?.id ?? null;\n const questionChanged = questionId !== this.lastPushedQuestionId;\n if (phaseChanged || questionChanged) {\n // Phase transitions bypass the debounce: the web app needs to\n // see Running → Completed as soon as it lands. A wizard_ask\n // opening or closing is the same shape — the whole point of\n // publishing it is that the user is looking at the web app, so\n // a 250ms-stale \"needs your input\" defeats the purpose.\n if (this.debounceTimer) {\n clearTimeout(this.debounceTimer);\n this.debounceTimer = null;\n }\n void this.flush();\n return;\n }\n\n // Task updates can arrive faster than we want to push. Debounce\n // them — the last update in a burst wins.\n if (this.debounceTimer) return;\n this.debounceTimer = setTimeout(() => {\n this.debounceTimer = null;\n void this.flush();\n }, DEBOUNCE_MS);\n }\n\n /**\n * Fan out the current state to every destination. Serialized — if\n * a flush is already running, mark \"needs another\" and let the\n * in-flight one schedule the follow-up when it settles.\n */\n private flush(): Promise<void> {\n if (this.inFlight) {\n this.needsAnotherPush = true;\n return this.inFlight;\n }\n\n const run = async (): Promise<void> => {\n try {\n await this.sendOnce();\n } finally {\n this.inFlight = null;\n if (this.needsAnotherPush) {\n this.needsAnotherPush = false;\n // Re-enter to push the latest snapshot.\n await this.flush();\n }\n }\n };\n\n this.inFlight = run();\n return this.inFlight;\n }\n\n private async sendOnce(): Promise<void> {\n this.selectRemoteSync();\n if (!this.remoteSync) return;\n const { session, tasks, eventPlan, handoffText } = this.store;\n const skillId = sanitizeChannelId(session.skillId ?? this.programId);\n const phase = session.runPhase;\n\n // Program rows carry the phase; the area rows carry the audit's progress.\n const programTasks = buildTasks(tasks);\n const auditAreas = this.auditChecks\n ? rollUpAuditAreas(this.auditChecks(), programTasks.length)\n : [];\n\n const payload: TaskStreamUpdate = {\n session_id: this.sessionId,\n workflow_id: this.programId,\n skill_id: skillId,\n started_at: this.startedAt,\n run_phase: phase,\n tasks: [...programTasks, ...auditAreas],\n event_plan: eventPlan.length > 0 ? { events: eventPlan } : undefined,\n error: buildError(phase, session.outroData),\n pending_input: buildPendingInput(session.pendingQuestion),\n // Included on every push once captured; the backend keeps it sticky, so\n // pushes that raced the capture cannot un-set it.\n handoff_text: handoffText ?? undefined,\n timestamp: new Date().toISOString(),\n };\n logToFile(\n `[task-stream-push] push phase=${phase} handoff_text=${\n handoffText === null ? 'absent' : `${handoffText.length} chars`\n }`,\n );\n\n let event: StreamEvent;\n if (!this.created) {\n this.created = true;\n event = StreamEvent.Create;\n } else if (phase === RunPhase.Completed) {\n event = StreamEvent.Complete;\n } else if (phase === RunPhase.Error) {\n event = StreamEvent.Error;\n } else {\n event = StreamEvent.Update;\n }\n\n this.lastPushedPhase = phase;\n this.lastPushedQuestionId = payload.pending_input?.id ?? null;\n\n await Promise.all(\n this.destinations\n .filter(\n (d) => d.name !== 'posthog' || this.remoteSync === 'wizard-session',\n )\n .map((d) =>\n // eslint-disable-next-line @typescript-eslint/no-empty-function\n d.send(event, payload).catch(() => {}),\n ),\n );\n }\n\n private selectRemoteSync(): void {\n if (this.remoteSync || !this.store.session.credentials) return;\n if (this.store.session.runPhase === RunPhase.Idle) return;\n const flags = this.getFlags?.();\n if (!flags) return;\n this.remoteSync =\n flags[WIZARD_RUN_SYNC_FLAG_KEY] === 'wizard-run'\n ? 'wizard-run'\n : 'wizard-session';\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"task-stream-Bw6JaLCX.js","names":[],"sources":["../src/programs/task-stream/event-plan-watcher.ts","../src/programs/task-stream/audit-areas.ts","../src/programs/task-stream/task-stream-push.ts"],"sourcesContent":["import type { PlannedEvent, WizardStore } from '@ui/tui/store';\nimport {\n startFileWatcher,\n type FileWatcherHandle,\n type FileWatcherOptions,\n} from '@lib/file-watcher';\n\nconst MAX_EVENT_PLAN_FILE_BYTES = 256 * 1024;\nconst MAX_EVENT_COUNT = 50;\nconst MAX_EVENT_NAME_LENGTH = 400;\nconst MAX_EVENT_DESCRIPTION_LENGTH = 4000;\n\nfunction firstString(...values: unknown[]): string | null {\n const value = values.find((candidate) => typeof candidate === 'string');\n return typeof value === 'string' ? value : null;\n}\n\nexport function normalizeEventPlan(parsed: unknown): PlannedEvent[] | null {\n if (!Array.isArray(parsed)) return null;\n\n const events: PlannedEvent[] = [];\n for (const value of parsed) {\n if (events.length >= MAX_EVENT_COUNT) break;\n\n const entry =\n value && typeof value === 'object'\n ? (value as Record<string, unknown>)\n : {};\n const name = firstString(entry.event_name, entry.name, entry.event);\n if (!name || !name.trim() || name.length > MAX_EVENT_NAME_LENGTH) continue;\n\n const description =\n firstString(entry.event_description, entry.description) ?? '';\n events.push({\n name,\n description: description.slice(0, MAX_EVENT_DESCRIPTION_LENGTH),\n });\n }\n\n return events;\n}\n\nexport class EventPlanWatcher {\n private handle: FileWatcherHandle | null = null;\n private captured = false;\n\n constructor(\n private readonly store: WizardStore,\n private readonly path: string,\n private readonly options: FileWatcherOptions = {},\n ) {}\n\n start(): void {\n if (this.handle || this.captured) return;\n\n this.handle = startFileWatcher(\n this.path,\n (parsed) => {\n const events = normalizeEventPlan(parsed);\n if (!events || events.length === 0) return;\n\n this.captured = true;\n this.store.setEventPlan(events);\n this.stop();\n },\n {\n ignoreInitialFile: true,\n maxFileSizeBytes: MAX_EVENT_PLAN_FILE_BYTES,\n ...this.options,\n },\n );\n }\n\n refresh(): void {\n if (this.captured) return;\n this.handle?.refresh();\n }\n\n stop(): void {\n this.handle?.stop();\n this.handle = null;\n }\n}\n","/**\n * Audit ledger → stream tasks: one row per audit *area*, never per check.\n *\n * A run resolves tens of checks, and each row carries a `file` path and a\n * `details` string derived from the user's code. Neither belongs on the wire.\n * A finding is progress, not a failure, so an area never reports `failed`.\n */\n\nimport type { AuditCheck, AuditStatus } from '@programs/audit/types';\nimport { StreamTaskStatus, type StreamTask } from './types';\n\nexport const MAX_AUDIT_AREAS = 24;\nconst MAX_AREA_TITLE_LENGTH = 80;\n\nconst RESOLVED: readonly AuditStatus[] = [\n 'pass',\n 'error',\n 'warning',\n 'suggestion',\n];\n\nfunction areaTitle(value: unknown): string | null {\n if (typeof value !== 'string' || !value.trim()) return null;\n return value.trim().slice(0, MAX_AREA_TITLE_LENGTH);\n}\n\n/** `idOffset` continues the program's task ids, which are stringified integers. */\nexport function rollUpAuditAreas(checks: unknown, idOffset = 0): StreamTask[] {\n if (!Array.isArray(checks)) return [];\n\n // Map keeps insertion order, which is the order the ledger seeds its areas in.\n const areas = new Map<string, { total: number; resolved: number }>();\n\n for (const entry of checks) {\n if (!entry || typeof entry !== 'object') continue;\n const title = areaTitle((entry as Partial<AuditCheck>).area);\n if (!title) continue;\n\n let counts = areas.get(title);\n if (!counts) {\n if (areas.size >= MAX_AUDIT_AREAS) continue;\n counts = { total: 0, resolved: 0 };\n areas.set(title, counts);\n }\n counts.total += 1;\n // The ledger is agent-written: anything unrecognized counts as unresolved.\n if (RESOLVED.includes((entry as Partial<AuditCheck>).status as AuditStatus))\n counts.resolved += 1;\n }\n\n return [...areas.entries()].map(([title, { total, resolved }], index) => ({\n id: String(idOffset + index),\n title,\n status:\n resolved === 0\n ? StreamTaskStatus.Pending\n : resolved < total\n ? StreamTaskStatus.InProgress\n : StreamTaskStatus.Completed,\n }));\n}\n","/**\n * Task-stream push — subscribes to WizardStore, builds payloads,\n * and fans out async to all registered destinations.\n *\n * Behaviour:\n * - `attach(store)` subscribe to store changes\n * - task updates debounced 250ms (trailing edge)\n * - phase transitions flush immediately, bypass debounce\n * - RunPhase.Idle skipped (no push)\n * - enabled === false destination delivery is disabled\n * - shutdown(timeoutMs) cancel pending, flush terminal phase\n * with timeout, never throw\n *\n * Concurrency: only one fan-out at a time. Emits during an in-flight\n * push are coalesced — at most one follow-up push fires with the\n * latest state once the current one settles.\n */\n\nimport type { WizardStore, TaskItem } from '@ui/tui/store';\nimport { TaskStatus } from '@ui/wizard-ui';\nimport {\n RunPhase,\n OutroKind,\n type OutroData,\n type PendingQuestion,\n} from '@lib/wizard-session';\nimport {\n type TaskStreamDestination,\n type TaskStreamUpdate,\n type StreamTask,\n type TaskStreamError,\n type StreamPendingInput,\n StreamTaskStatus,\n StreamEvent,\n} from './types';\nimport { EventPlanWatcher } from './event-plan-watcher';\nimport { rollUpAuditAreas } from './audit-areas';\nimport type { WizardRunSync, RunOutcome } from './wizard-run-sync';\nimport { logToFile } from '@utils/debug';\nimport { WIZARD_RUN_SYNC_FLAG_KEY } from '@shared/constants';\nimport { sanitizeErrorDetail } from '@shared/errors';\n\n/** Trailing-edge debounce window for non-phase-change emits. */\nconst DEBOUNCE_MS = 250;\n/** Default shutdown timeout for the final terminal flush. */\nconst DEFAULT_SHUTDOWN_TIMEOUT_MS = 2000;\n\nconst STATUS_MAP: Record<TaskStatus, StreamTaskStatus> = {\n [TaskStatus.Pending]: StreamTaskStatus.Pending,\n [TaskStatus.InProgress]: StreamTaskStatus.InProgress,\n [TaskStatus.Completed]: StreamTaskStatus.Completed,\n [TaskStatus.Failed]: StreamTaskStatus.Failed,\n // The stream has no skipped state; skipped is terminal, so report it resolved.\n [TaskStatus.Skipped]: StreamTaskStatus.Completed,\n};\n\nfunction buildTasks(items: TaskItem[]): StreamTask[] {\n return items.map((item, i) => ({\n id: String(i),\n title: item.label,\n status: STATUS_MAP[item.status] ?? StreamTaskStatus.Pending,\n }));\n}\n\n/** Drop \".SSSZ\" → \"Z\" so session_id segments stay routing-safe. */\nfunction secondPrecisionIso(d: Date): string {\n return d.toISOString().replace(/\\.\\d{3}Z$/, 'Z');\n}\n\n/**\n * `workflow_id` and `skill_id` end up unescaped in Redis pub/sub\n * channel names, so the backend rejects anything outside\n * `^[A-Za-z0-9_.-]{1,255}$` with a 400. All current values already\n * comply; this is defence in depth in case a future caller passes\n * something with `:`, spaces, or other separators.\n */\nfunction sanitizeChannelId(value: string): string {\n return value.replace(/[^A-Za-z0-9_.-]/g, '-').slice(0, 255);\n}\n\nfunction buildError(\n phase: RunPhase,\n outroData: OutroData | null,\n): TaskStreamError | undefined {\n if (phase !== RunPhase.Error) return undefined;\n if (outroData?.kind === OutroKind.Error) {\n const message = outroData.message ?? outroData.body ?? 'Wizard run failed';\n const error: TaskStreamError = { type: 'wizard_error', message };\n if (outroData.errorCode) error.code = outroData.errorCode;\n const safeDetail = sanitizeErrorDetail(outroData.errorDetail);\n if (safeDetail) error.detail = safeDetail;\n return error;\n }\n return { type: 'wizard_error', message: 'Wizard run failed' };\n}\n\n/**\n * Sensitive asks (secrets, API keys) publish only the fact that input is\n * required — never the prompt text. The session row is team-visible and\n * outlives the prompt.\n */\nfunction buildPendingInput(\n question: PendingQuestion | null | undefined,\n): StreamPendingInput | undefined {\n if (!question) return undefined;\n const sensitive = question.questions.some((q) => q.sensitive === true);\n return {\n id: question.id,\n asked_at: question.askedAt ?? new Date().toISOString(),\n question_count: question.questions.length,\n sensitive,\n prompts: sensitive ? undefined : question.questions.map((q) => q.prompt),\n };\n}\n\nexport interface TaskStreamPushOptions {\n store: WizardStore;\n runSync?: WizardRunSync;\n getFlags?: () => Readonly<Record<string, string>> | null;\n programId: string;\n destinations: TaskStreamDestination[];\n /** Optional absolute event-plan path to load into the store once. */\n eventPlanPath?: string;\n /** The run's audit ledger, when it has one. The runner owns the watcher. */\n auditChecks?: () => unknown;\n /** When false, destination subscription/delivery remains disabled. */\n enabled?: boolean;\n}\n\nexport class TaskStreamPush {\n private readonly store: WizardStore;\n private readonly destinations: TaskStreamDestination[];\n private readonly startedAt: string;\n private readonly programId: string;\n private readonly sessionId: string;\n private readonly eventPlanWatcher: EventPlanWatcher | null;\n private readonly auditChecks: (() => unknown) | null;\n\n private readonly runSync?: WizardRunSync;\n private readonly getFlags?: TaskStreamPushOptions['getFlags'];\n private remoteSync?: 'wizard-session' | 'wizard-run';\n private shutdownPromise?: Promise<void>;\n private enabled: boolean;\n private created = false;\n private lastPushedPhase: RunPhase | null = null;\n private lastPushedQuestionId: string | null = null;\n\n private unsubscribe: (() => void) | null = null;\n private debounceTimer: ReturnType<typeof setTimeout> | null = null;\n private inFlight: Promise<void> | null = null;\n private needsAnotherPush = false;\n private shuttingDown = false;\n\n constructor(opts: TaskStreamPushOptions) {\n this.store = opts.store;\n this.runSync = opts.runSync;\n this.getFlags = opts.getFlags;\n this.remoteSync = !opts.runSync\n ? 'wizard-session'\n : opts.getFlags\n ? undefined\n : 'wizard-run';\n this.programId = sanitizeChannelId(opts.programId);\n this.destinations = opts.destinations;\n this.enabled = opts.enabled ?? true;\n const startedAt = new Date();\n this.eventPlanWatcher = opts.eventPlanPath\n ? new EventPlanWatcher(this.store, opts.eventPlanPath)\n : null;\n this.auditChecks = opts.auditChecks ?? null;\n this.startedAt = secondPrecisionIso(startedAt);\n // skillId may not be set yet — fall back to programId so the\n // session_id is stable for the whole run regardless of when the\n // program metadata is populated.\n const skillId = sanitizeChannelId(\n this.store.session.skillId ?? this.programId,\n );\n this.sessionId = `${this.programId}-${skillId}-${this.startedAt}`;\n }\n\n /**\n * Load the event plan and subscribe to store changes. Destination delivery\n * remains disabled when `enabled === false`, but the plan still populates the\n * store for local and headless consumers.\n */\n attach(store?: WizardStore): void {\n this.eventPlanWatcher?.start();\n if (!this.enabled) return;\n if (this.unsubscribe) return;\n const target = store ?? this.store;\n this.unsubscribe = target.subscribe(() => this.onStoreChange());\n }\n\n /** Stop subscribing. Does not flush. */\n detach(): void {\n this.eventPlanWatcher?.stop();\n if (this.unsubscribe) {\n this.unsubscribe();\n this.unsubscribe = null;\n }\n if (this.debounceTimer) {\n clearTimeout(this.debounceTimer);\n this.debounceTimer = null;\n }\n }\n\n // Finalize execution while the legacy session continues through the outro.\n async finishRun(\n outcome: RunOutcome,\n timeoutMs = DEFAULT_SHUTDOWN_TIMEOUT_MS,\n ): Promise<void> {\n await this.runSync?.shutdown(outcome, timeoutMs);\n }\n\n shutdown(\n timeoutMs: number = DEFAULT_SHUTDOWN_TIMEOUT_MS,\n outcome: RunOutcome = this.store.session.runPhase === RunPhase.Completed\n ? 'completed'\n : 'failed',\n ): Promise<void> {\n this.shutdownPromise ??= Promise.all([\n this.runSync?.shutdown(outcome, timeoutMs),\n this.shutdownLegacy(timeoutMs),\n ]).then(() => undefined);\n return this.shutdownPromise;\n }\n\n private async shutdownLegacy(timeoutMs: number): Promise<void> {\n this.shuttingDown = true;\n this.eventPlanWatcher?.refresh();\n if (this.debounceTimer) {\n clearTimeout(this.debounceTimer);\n this.debounceTimer = null;\n }\n this.detach();\n if (!this.enabled) return;\n\n const phase = this.store.session.runPhase;\n const isTerminal = phase === RunPhase.Completed || phase === RunPhase.Error;\n if (!isTerminal) return;\n\n const flush = this.flush();\n if (timeoutMs <= 0) return;\n let timer: ReturnType<typeof setTimeout> | undefined;\n try {\n await Promise.race([\n flush,\n new Promise<void>((resolve) => {\n timer = setTimeout(resolve, timeoutMs);\n }),\n ]);\n } finally {\n clearTimeout(timer);\n }\n }\n\n /**\n * Imperative push — fires immediately regardless of phase. Kept as\n * the building block for both subscription-driven and direct calls.\n */\n async push(): Promise<void> {\n await this.flush();\n }\n\n // ── Internal ────────────────────────────────────────────────────\n\n private onStoreChange(): void {\n if (!this.enabled || this.shuttingDown) return;\n const phase = this.store.session.runPhase;\n if (phase === RunPhase.Idle) return;\n this.selectRemoteSync();\n if (this.remoteSync === 'wizard-run') {\n this.runSync?.capture(this.store.tasks);\n }\n\n // A push is already in flight — coalesce. The in-flight push's\n // settle handler will trigger one follow-up with the latest state.\n if (this.inFlight) {\n if (this.debounceTimer) {\n clearTimeout(this.debounceTimer);\n this.debounceTimer = null;\n }\n this.needsAnotherPush = true;\n return;\n }\n\n const phaseChanged = phase !== this.lastPushedPhase;\n const questionId = this.store.session.pendingQuestion?.id ?? null;\n const questionChanged = questionId !== this.lastPushedQuestionId;\n if (phaseChanged || questionChanged) {\n // Phase transitions bypass the debounce: the web app needs to\n // see Running → Completed as soon as it lands. A wizard_ask\n // opening or closing is the same shape — the whole point of\n // publishing it is that the user is looking at the web app, so\n // a 250ms-stale \"needs your input\" defeats the purpose.\n if (this.debounceTimer) {\n clearTimeout(this.debounceTimer);\n this.debounceTimer = null;\n }\n void this.flush();\n return;\n }\n\n // Task updates can arrive faster than we want to push. Debounce\n // them — the last update in a burst wins.\n if (this.debounceTimer) return;\n this.debounceTimer = setTimeout(() => {\n this.debounceTimer = null;\n void this.flush();\n }, DEBOUNCE_MS);\n }\n\n /**\n * Fan out the current state to every destination. Serialized — if\n * a flush is already running, mark \"needs another\" and let the\n * in-flight one schedule the follow-up when it settles.\n */\n private flush(): Promise<void> {\n if (this.inFlight) {\n this.needsAnotherPush = true;\n return this.inFlight;\n }\n\n const run = async (): Promise<void> => {\n try {\n await this.sendOnce();\n } finally {\n this.inFlight = null;\n if (this.needsAnotherPush) {\n this.needsAnotherPush = false;\n // Re-enter to push the latest snapshot.\n await this.flush();\n }\n }\n };\n\n this.inFlight = run();\n return this.inFlight;\n }\n\n private async sendOnce(): Promise<void> {\n this.selectRemoteSync();\n if (!this.remoteSync) return;\n const { session, tasks, eventPlan, handoffText } = this.store;\n const skillId = sanitizeChannelId(session.skillId ?? this.programId);\n const phase = session.runPhase;\n\n // Program rows carry the phase; the area rows carry the audit's progress.\n const programTasks = buildTasks(tasks);\n const auditAreas = this.auditChecks\n ? rollUpAuditAreas(this.auditChecks(), programTasks.length)\n : [];\n\n const payload: TaskStreamUpdate = {\n session_id: this.sessionId,\n workflow_id: this.programId,\n skill_id: skillId,\n started_at: this.startedAt,\n run_phase: phase,\n tasks: [...programTasks, ...auditAreas],\n event_plan: eventPlan.length > 0 ? { events: eventPlan } : undefined,\n error: buildError(phase, session.outroData),\n pending_input: buildPendingInput(session.pendingQuestion),\n // Included on every push once captured; the backend keeps it sticky, so\n // pushes that raced the capture cannot un-set it.\n handoff_text: handoffText ?? undefined,\n timestamp: new Date().toISOString(),\n };\n logToFile(\n `[task-stream-push] push phase=${phase} handoff_text=${\n handoffText === null ? 'absent' : `${handoffText.length} chars`\n }`,\n );\n\n let event: StreamEvent;\n if (!this.created) {\n this.created = true;\n event = StreamEvent.Create;\n } else if (phase === RunPhase.Completed) {\n event = StreamEvent.Complete;\n } else if (phase === RunPhase.Error) {\n event = StreamEvent.Error;\n } else {\n event = StreamEvent.Update;\n }\n\n this.lastPushedPhase = phase;\n this.lastPushedQuestionId = payload.pending_input?.id ?? null;\n\n await Promise.all(\n this.destinations\n .filter(\n (d) => d.name !== 'posthog' || this.remoteSync === 'wizard-session',\n )\n .map((d) =>\n // eslint-disable-next-line @typescript-eslint/no-empty-function\n d.send(event, payload).catch(() => {}),\n ),\n );\n }\n\n private selectRemoteSync(): void {\n if (this.remoteSync || !this.store.session.credentials) return;\n if (this.store.session.runPhase === RunPhase.Idle) return;\n const flags = this.getFlags?.();\n if (!flags) return;\n this.remoteSync =\n flags[WIZARD_RUN_SYNC_FLAG_KEY] === 'wizard-run'\n ? 'wizard-run'\n : 'wizard-session';\n }\n}\n"],"mappings":"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"}
@@ -113,4 +113,4 @@ function createWizardPiTaskTools(onSync = () => void 0) {
113
113
  //#endregion
114
114
  export { createWizardPiTaskTools };
115
115
 
116
- //# sourceMappingURL=tasks-Bbv_lOBG.js.map
116
+ //# sourceMappingURL=tasks-ChWufsdO.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"tasks-Bbv_lOBG.js","names":[],"sources":["../src/agent/runner/harness/pi/tasks.ts"],"sourcesContent":["/**\n * Task/todo parity for pi (#526). The same four Task tools the anthropic path\n * exposes (TaskCreate/Update/Get/List), as pi `defineTool` tools backed by a\n * shared in-memory store. Every mutation reports the list through `onSync`\n * (a `tasks` progress event), so the todo panel updates live under pi exactly\n * like the anthropic path — the thing that was missing before.\n */\n\nimport { randomUUID } from 'node:crypto';\nimport { Type } from 'typebox';\nimport { defineTool } from '@earendil-works/pi-coding-agent';\nimport type { ToolDefinition } from '@earendil-works/pi-coding-agent';\nimport type { TaskSnapshot } from '@agent/progress';\n\nexport type TaskStatus = 'pending' | 'in_progress' | 'completed';\nexport interface TaskEntry {\n content: string;\n status: TaskStatus;\n activeForm?: string;\n}\nexport type TaskStore = Map<string, TaskEntry>;\n\nfunction text(s: string): {\n content: [{ type: 'text'; text: string }];\n details: unknown;\n} {\n return { content: [{ type: 'text', text: s }], details: {} };\n}\n\nfunction snapshot(store: TaskStore, source: string): TaskSnapshot[] {\n return Array.from(store.entries()).map(([id, t]) => ({\n id: `${source}:${id}`,\n source,\n content: t.content,\n status: t.status,\n activeForm: t.activeForm,\n }));\n}\n\n/** Build the four Task tools over a fresh store. `onSync` gets the list after every mutation. */\nexport function createWizardPiTaskTools(\n onSync: (tasks: TaskSnapshot[]) => void = () => undefined,\n): {\n tools: ToolDefinition[];\n store: TaskStore;\n} {\n const store: TaskStore = new Map();\n const source = randomUUID();\n const syncToTui = (): void => onSync(snapshot(store, source));\n\n const taskCreate = defineTool({\n name: 'TaskCreate',\n label: 'Create task',\n description:\n 'Create a task in the shared todo list. Returns its assigned id.',\n promptSnippet:\n 'TaskCreate(content) — add a todo (surfaces progress in the UI)',\n parameters: Type.Object({\n content: Type.String({ description: 'Imperative task description' }),\n activeForm: Type.Optional(\n Type.String({ description: 'Present-continuous form for the spinner' }),\n ),\n }),\n // eslint-disable-next-line @typescript-eslint/require-await -- pi tool contract returns a Promise\n async execute(_id, args) {\n const id = `task-${store.size + 1}`;\n store.set(id, {\n content: args.content,\n status: 'pending',\n activeForm: args.activeForm,\n });\n syncToTui();\n return text(`Created ${id}`);\n },\n });\n\n const taskUpdate = defineTool({\n name: 'TaskUpdate',\n label: 'Update task',\n description:\n 'Update an existing task by id (status, content, or activeForm).',\n promptSnippet:\n 'TaskUpdate(taskId, status) — mark a todo in_progress/completed',\n parameters: Type.Object({\n taskId: Type.String(),\n status: Type.Optional(\n Type.Union([\n Type.Literal('pending'),\n Type.Literal('in_progress'),\n Type.Literal('completed'),\n ]),\n ),\n content: Type.Optional(Type.String()),\n activeForm: Type.Optional(Type.String()),\n }),\n // eslint-disable-next-line @typescript-eslint/require-await -- pi tool contract returns a Promise\n async execute(_id, args) {\n const existing = store.get(args.taskId);\n if (!existing) return text(`No such task: ${args.taskId}`);\n store.set(args.taskId, {\n content: args.content ?? existing.content,\n status: (args.status as TaskStatus) ?? existing.status,\n activeForm: args.activeForm ?? existing.activeForm,\n });\n syncToTui();\n return text(`Updated ${args.taskId}`);\n },\n });\n\n const taskGet = defineTool({\n name: 'TaskGet',\n label: 'Get task',\n description: 'Fetch a single task by id.',\n parameters: Type.Object({ taskId: Type.String() }),\n // eslint-disable-next-line @typescript-eslint/require-await -- pi tool contract returns a Promise\n async execute(_id, args) {\n const t = store.get(args.taskId);\n return text(\n t\n ? JSON.stringify({ id: args.taskId, ...t })\n : `No such task: ${args.taskId}`,\n );\n },\n });\n\n const taskList = defineTool({\n name: 'TaskList',\n label: 'List tasks',\n description: 'List all tasks in the shared todo list.',\n parameters: Type.Object({}),\n // eslint-disable-next-line @typescript-eslint/require-await -- pi tool contract returns a Promise\n async execute() {\n return text(\n JSON.stringify(\n Array.from(store.entries()).map(([id, t]) => ({ id, ...t })),\n ),\n );\n },\n });\n\n return { tools: [taskCreate, taskUpdate, taskGet, taskList], store };\n}\n"],"mappings":";;;;;;;;;;;AAsBA,SAAS,KAAK,GAGZ;AACA,QAAO;EAAE,SAAS,CAAC;GAAE,MAAM;GAAQ,MAAM;GAAG,CAAC;EAAE,SAAS,EAAE;EAAE;;AAG9D,SAAS,SAAS,OAAkB,QAAgC;AAClE,QAAO,MAAM,KAAK,MAAM,SAAS,CAAC,CAAC,KAAK,CAAC,IAAI,QAAQ;EACnD,IAAI,GAAG,OAAO,GAAG;EACjB;EACA,SAAS,EAAE;EACX,QAAQ,EAAE;EACV,YAAY,EAAE;EACf,EAAE;;;AAIL,SAAgB,wBACd,eAAgD,KAAA,GAIhD;CACA,MAAM,wBAAmB,IAAI,KAAK;CAClC,MAAM,SAAS,YAAY;CAC3B,MAAM,kBAAwB,OAAO,SAAS,OAAO,OAAO,CAAC;AA4F7D,QAAO;EAAE,OAAO;GA1FG,WAAW;IAC5B,MAAM;IACN,OAAO;IACP,aACE;IACF,eACE;IACF,YAAY,KAAK,OAAO;KACtB,SAAS,KAAK,OAAO,EAAE,aAAa,+BAA+B,CAAC;KACpE,YAAY,KAAK,SACf,KAAK,OAAO,EAAE,aAAa,2CAA2C,CAAC,CACxE;KACF,CAAC;IAEF,MAAM,QAAQ,KAAK,MAAM;KACvB,MAAM,KAAK,QAAQ,MAAM,OAAO;AAChC,WAAM,IAAI,IAAI;MACZ,SAAS,KAAK;MACd,QAAQ;MACR,YAAY,KAAK;MAClB,CAAC;AACF,gBAAW;AACX,YAAO,KAAK,WAAW,KAAK;;IAE/B,CAAC;GAEiB,WAAW;IAC5B,MAAM;IACN,OAAO;IACP,aACE;IACF,eACE;IACF,YAAY,KAAK,OAAO;KACtB,QAAQ,KAAK,QAAQ;KACrB,QAAQ,KAAK,SACX,KAAK,MAAM;MACT,KAAK,QAAQ,UAAU;MACvB,KAAK,QAAQ,cAAc;MAC3B,KAAK,QAAQ,YAAY;MAC1B,CAAC,CACH;KACD,SAAS,KAAK,SAAS,KAAK,QAAQ,CAAC;KACrC,YAAY,KAAK,SAAS,KAAK,QAAQ,CAAC;KACzC,CAAC;IAEF,MAAM,QAAQ,KAAK,MAAM;KACvB,MAAM,WAAW,MAAM,IAAI,KAAK,OAAO;AACvC,SAAI,CAAC,SAAU,QAAO,KAAK,iBAAiB,KAAK,SAAS;AAC1D,WAAM,IAAI,KAAK,QAAQ;MACrB,SAAS,KAAK,WAAW,SAAS;MAClC,QAAS,KAAK,UAAyB,SAAS;MAChD,YAAY,KAAK,cAAc,SAAS;MACzC,CAAC;AACF,gBAAW;AACX,YAAO,KAAK,WAAW,KAAK,SAAS;;IAExC,CAAC;GAEc,WAAW;IACzB,MAAM;IACN,OAAO;IACP,aAAa;IACb,YAAY,KAAK,OAAO,EAAE,QAAQ,KAAK,QAAQ,EAAE,CAAC;IAElD,MAAM,QAAQ,KAAK,MAAM;KACvB,MAAM,IAAI,MAAM,IAAI,KAAK,OAAO;AAChC,YAAO,KACL,IACI,KAAK,UAAU;MAAE,IAAI,KAAK;MAAQ,GAAG;MAAG,CAAC,GACzC,iBAAiB,KAAK,SAC3B;;IAEJ,CAAC;GAEe,WAAW;IAC1B,MAAM;IACN,OAAO;IACP,aAAa;IACb,YAAY,KAAK,OAAO,EAAE,CAAC;IAE3B,MAAM,UAAU;AACd,YAAO,KACL,KAAK,UACH,MAAM,KAAK,MAAM,SAAS,CAAC,CAAC,KAAK,CAAC,IAAI,QAAQ;MAAE;MAAI,GAAG;MAAG,EAAE,CAC7D,CACF;;IAEJ,CAAC;GAEyD;EAAE;EAAO"}
1
+ {"version":3,"file":"tasks-ChWufsdO.js","names":[],"sources":["../src/agent/runner/harness/pi/tasks.ts"],"sourcesContent":["/**\n * Task/todo parity for pi (#526). The same four Task tools the anthropic path\n * exposes (TaskCreate/Update/Get/List), as pi `defineTool` tools backed by a\n * shared in-memory store. Every mutation reports the list through `onSync`\n * (a `tasks` progress event), so the todo panel updates live under pi exactly\n * like the anthropic path — the thing that was missing before.\n */\n\nimport { randomUUID } from 'node:crypto';\nimport { Type } from 'typebox';\nimport { defineTool } from '@earendil-works/pi-coding-agent';\nimport type { ToolDefinition } from '@earendil-works/pi-coding-agent';\nimport type { TaskSnapshot } from '@agent/progress';\n\nexport type TaskStatus = 'pending' | 'in_progress' | 'completed';\nexport interface TaskEntry {\n content: string;\n status: TaskStatus;\n activeForm?: string;\n}\nexport type TaskStore = Map<string, TaskEntry>;\n\nfunction text(s: string): {\n content: [{ type: 'text'; text: string }];\n details: unknown;\n} {\n return { content: [{ type: 'text', text: s }], details: {} };\n}\n\nfunction snapshot(store: TaskStore, source: string): TaskSnapshot[] {\n return Array.from(store.entries()).map(([id, t]) => ({\n id: `${source}:${id}`,\n source,\n content: t.content,\n status: t.status,\n activeForm: t.activeForm,\n }));\n}\n\n/** Build the four Task tools over a fresh store. `onSync` gets the list after every mutation. */\nexport function createWizardPiTaskTools(\n onSync: (tasks: TaskSnapshot[]) => void = () => undefined,\n): {\n tools: ToolDefinition[];\n store: TaskStore;\n} {\n const store: TaskStore = new Map();\n const source = randomUUID();\n const syncToTui = (): void => onSync(snapshot(store, source));\n\n const taskCreate = defineTool({\n name: 'TaskCreate',\n label: 'Create task',\n description:\n 'Create a task in the shared todo list. Returns its assigned id.',\n promptSnippet:\n 'TaskCreate(content) — add a todo (surfaces progress in the UI)',\n parameters: Type.Object({\n content: Type.String({ description: 'Imperative task description' }),\n activeForm: Type.Optional(\n Type.String({ description: 'Present-continuous form for the spinner' }),\n ),\n }),\n // eslint-disable-next-line @typescript-eslint/require-await -- pi tool contract returns a Promise\n async execute(_id, args) {\n const id = `task-${store.size + 1}`;\n store.set(id, {\n content: args.content,\n status: 'pending',\n activeForm: args.activeForm,\n });\n syncToTui();\n return text(`Created ${id}`);\n },\n });\n\n const taskUpdate = defineTool({\n name: 'TaskUpdate',\n label: 'Update task',\n description:\n 'Update an existing task by id (status, content, or activeForm).',\n promptSnippet:\n 'TaskUpdate(taskId, status) — mark a todo in_progress/completed',\n parameters: Type.Object({\n taskId: Type.String(),\n status: Type.Optional(\n Type.Union([\n Type.Literal('pending'),\n Type.Literal('in_progress'),\n Type.Literal('completed'),\n ]),\n ),\n content: Type.Optional(Type.String()),\n activeForm: Type.Optional(Type.String()),\n }),\n // eslint-disable-next-line @typescript-eslint/require-await -- pi tool contract returns a Promise\n async execute(_id, args) {\n const existing = store.get(args.taskId);\n if (!existing) return text(`No such task: ${args.taskId}`);\n store.set(args.taskId, {\n content: args.content ?? existing.content,\n status: (args.status as TaskStatus) ?? existing.status,\n activeForm: args.activeForm ?? existing.activeForm,\n });\n syncToTui();\n return text(`Updated ${args.taskId}`);\n },\n });\n\n const taskGet = defineTool({\n name: 'TaskGet',\n label: 'Get task',\n description: 'Fetch a single task by id.',\n parameters: Type.Object({ taskId: Type.String() }),\n // eslint-disable-next-line @typescript-eslint/require-await -- pi tool contract returns a Promise\n async execute(_id, args) {\n const t = store.get(args.taskId);\n return text(\n t\n ? JSON.stringify({ id: args.taskId, ...t })\n : `No such task: ${args.taskId}`,\n );\n },\n });\n\n const taskList = defineTool({\n name: 'TaskList',\n label: 'List tasks',\n description: 'List all tasks in the shared todo list.',\n parameters: Type.Object({}),\n // eslint-disable-next-line @typescript-eslint/require-await -- pi tool contract returns a Promise\n async execute() {\n return text(\n JSON.stringify(\n Array.from(store.entries()).map(([id, t]) => ({ id, ...t })),\n ),\n );\n },\n });\n\n return { tools: [taskCreate, taskUpdate, taskGet, taskList], store };\n}\n"],"mappings":";;;;;;;;;;;AAsBA,SAAS,KAAK,GAGZ;AACA,QAAO;EAAE,SAAS,CAAC;GAAE,MAAM;GAAQ,MAAM;GAAG,CAAC;EAAE,SAAS,EAAE;EAAE;;AAG9D,SAAS,SAAS,OAAkB,QAAgC;AAClE,QAAO,MAAM,KAAK,MAAM,SAAS,CAAC,CAAC,KAAK,CAAC,IAAI,QAAQ;EACnD,IAAI,GAAG,OAAO,GAAG;EACjB;EACA,SAAS,EAAE;EACX,QAAQ,EAAE;EACV,YAAY,EAAE;EACf,EAAE;;;AAIL,SAAgB,wBACd,eAAgD,KAAA,GAIhD;CACA,MAAM,wBAAmB,IAAI,KAAK;CAClC,MAAM,SAAS,YAAY;CAC3B,MAAM,kBAAwB,OAAO,SAAS,OAAO,OAAO,CAAC;AA4F7D,QAAO;EAAE,OAAO;GA1FG,WAAW;IAC5B,MAAM;IACN,OAAO;IACP,aACE;IACF,eACE;IACF,YAAY,KAAK,OAAO;KACtB,SAAS,KAAK,OAAO,EAAE,aAAa,+BAA+B,CAAC;KACpE,YAAY,KAAK,SACf,KAAK,OAAO,EAAE,aAAa,2CAA2C,CAAC,CACxE;KACF,CAAC;IAEF,MAAM,QAAQ,KAAK,MAAM;KACvB,MAAM,KAAK,QAAQ,MAAM,OAAO;AAChC,WAAM,IAAI,IAAI;MACZ,SAAS,KAAK;MACd,QAAQ;MACR,YAAY,KAAK;MAClB,CAAC;AACF,gBAAW;AACX,YAAO,KAAK,WAAW,KAAK;;IAE/B,CAAC;GAEiB,WAAW;IAC5B,MAAM;IACN,OAAO;IACP,aACE;IACF,eACE;IACF,YAAY,KAAK,OAAO;KACtB,QAAQ,KAAK,QAAQ;KACrB,QAAQ,KAAK,SACX,KAAK,MAAM;MACT,KAAK,QAAQ,UAAU;MACvB,KAAK,QAAQ,cAAc;MAC3B,KAAK,QAAQ,YAAY;MAC1B,CAAC,CACH;KACD,SAAS,KAAK,SAAS,KAAK,QAAQ,CAAC;KACrC,YAAY,KAAK,SAAS,KAAK,QAAQ,CAAC;KACzC,CAAC;IAEF,MAAM,QAAQ,KAAK,MAAM;KACvB,MAAM,WAAW,MAAM,IAAI,KAAK,OAAO;AACvC,SAAI,CAAC,SAAU,QAAO,KAAK,iBAAiB,KAAK,SAAS;AAC1D,WAAM,IAAI,KAAK,QAAQ;MACrB,SAAS,KAAK,WAAW,SAAS;MAClC,QAAS,KAAK,UAAyB,SAAS;MAChD,YAAY,KAAK,cAAc,SAAS;MACzC,CAAC;AACF,gBAAW;AACX,YAAO,KAAK,WAAW,KAAK,SAAS;;IAExC,CAAC;GAEc,WAAW;IACzB,MAAM;IACN,OAAO;IACP,aAAa;IACb,YAAY,KAAK,OAAO,EAAE,QAAQ,KAAK,QAAQ,EAAE,CAAC;IAElD,MAAM,QAAQ,KAAK,MAAM;KACvB,MAAM,IAAI,MAAM,IAAI,KAAK,OAAO;AAChC,YAAO,KACL,IACI,KAAK,UAAU;MAAE,IAAI,KAAK;MAAQ,GAAG;MAAG,CAAC,GACzC,iBAAiB,KAAK,SAC3B;;IAEJ,CAAC;GAEe,WAAW;IAC1B,MAAM;IACN,OAAO;IACP,aAAa;IACb,YAAY,KAAK,OAAO,EAAE,CAAC;IAE3B,MAAM,UAAU;AACd,YAAO,KACL,KAAK,UACH,MAAM,KAAK,MAAM,SAAS,CAAC,CAAC,KAAK,CAAC,IAAI,QAAQ;MAAE;MAAI,GAAG;MAAG,EAAE,CAC7D,CACF;;IAEJ,CAAC;GAEyD;EAAE;EAAO"}
@@ -1,5 +1,5 @@
1
- import "./debug-Cj-8vfXY.js";
2
- import { n as analytics } from "./analytics-VRYdebXW.js";
1
+ import "./debug-kAHwWGg7.js";
2
+ import { n as analytics } from "./analytics-DXK1gc12.js";
3
3
  import opn from "opn";
4
4
  //#region src/shared/utils/links.ts
5
5
  /**
@@ -65,4 +65,4 @@ function updateProgress(step) {
65
65
  //#endregion
66
66
  export { withUtm as i, openTrackedLink as n, setEntryCommand as r, withProgress as t };
67
67
 
68
- //# sourceMappingURL=telemetry-Bpv-Mh5B.js.map
68
+ //# sourceMappingURL=telemetry-B7zJdRmA.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"telemetry-Bpv-Mh5B.js","names":[],"sources":["../src/shared/utils/links.ts","../src/shared/utils/telemetry.ts"],"sourcesContent":["/**\n * Outbound links: UTM tagging and tracked opens.\n *\n * Every URL the wizard sends a user to — auto-opened or printed in the TUI —\n * carries `utm_source=wizard`, `utm_medium=cli`, `utm_content=<which link>`.\n * The command dimension rides on every wizard event as the `command` tag.\n * Opening a link also captures a wizard event.\n */\nimport opn from 'opn';\nimport { NODE_ENV } from '@env';\nimport { analytics } from './analytics';\n\n/**\n * Record the CLI command this run was started with (e.g. `integrate`,\n * `slack`, `mcp-add`). Set once at dispatch; tagged onto every wizard\n * event so wizard-side events segment by command.\n */\nexport function setEntryCommand(command: string): void {\n analytics.setTag('command', command);\n}\n\n/**\n * Tag a URL with the wizard's UTM params. `content` names the specific link\n * (e.g. `oauth-signup`, `slack-connect-setup`). URLs that already carry a\n * utm_source — or don't parse — are returned untouched.\n */\nexport function withUtm(url: string, content: string): string {\n let parsed: URL;\n try {\n parsed = new URL(url);\n } catch {\n return url;\n }\n if (parsed.searchParams.has('utm_source')) return url;\n parsed.searchParams.set('utm_source', 'wizard');\n parsed.searchParams.set('utm_medium', 'cli');\n parsed.searchParams.set('utm_content', content);\n return parsed.toString();\n}\n\n/**\n * Open a URL in the user's browser, UTM-tagged (pass `skipUtm` for\n * destinations that aren't PostHog properties or are already final), and\n * capture the interaction. `auto` marks opens the wizard initiated itself,\n * as opposed to a user picking a link on screen. opn throws in headless\n * environments — the URL is always also printed on screen, so the failure\n * is swallowed.\n */\nexport function openTrackedLink(\n url: string,\n content: string,\n opts?: { auto?: boolean; skipUtm?: boolean },\n): void {\n const finalUrl = opts?.skipUtm ? url : withUtm(url, content);\n analytics.wizardCapture('link opened', {\n content,\n url: finalUrl,\n auto: opts?.auto ?? false,\n });\n if (NODE_ENV !== 'test') {\n opn(finalUrl, { wait: false }).catch(() => {\n // No browser available — the printed URL is the fallback.\n });\n }\n}\n","import { analytics } from './analytics';\n\nexport function withProgress<T>(step: string, callback: () => T): T {\n updateProgress(step);\n return callback();\n}\n\nexport function updateProgress(step: string) {\n analytics.setTag('progress', step);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAiBA,SAAgB,gBAAgB,SAAuB;AACrD,WAAU,OAAO,WAAW,QAAQ;;;;;;;AAQtC,SAAgB,QAAQ,KAAa,SAAyB;CAC5D,IAAI;AACJ,KAAI;AACF,WAAS,IAAI,IAAI,IAAI;SACf;AACN,SAAO;;AAET,KAAI,OAAO,aAAa,IAAI,aAAa,CAAE,QAAO;AAClD,QAAO,aAAa,IAAI,cAAc,SAAS;AAC/C,QAAO,aAAa,IAAI,cAAc,MAAM;AAC5C,QAAO,aAAa,IAAI,eAAe,QAAQ;AAC/C,QAAO,OAAO,UAAU;;;;;;;;;;AAW1B,SAAgB,gBACd,KACA,SACA,MACM;CACN,MAAM,WAAW,MAAM,UAAU,MAAM,QAAQ,KAAK,QAAQ;AAC5D,WAAU,cAAc,eAAe;EACrC;EACA,KAAK;EACL,MAAM,MAAM,QAAQ;EACrB,CAAC;AAEA,KAAI,UAAU,EAAE,MAAM,OAAO,CAAC,CAAC,YAAY,GAEzC;;;;AC5DN,SAAgB,aAAgB,MAAc,UAAsB;AAClE,gBAAe,KAAK;AACpB,QAAO,UAAU;;AAGnB,SAAgB,eAAe,MAAc;AAC3C,WAAU,OAAO,YAAY,KAAK"}
1
+ {"version":3,"file":"telemetry-B7zJdRmA.js","names":[],"sources":["../src/shared/utils/links.ts","../src/shared/utils/telemetry.ts"],"sourcesContent":["/**\n * Outbound links: UTM tagging and tracked opens.\n *\n * Every URL the wizard sends a user to — auto-opened or printed in the TUI —\n * carries `utm_source=wizard`, `utm_medium=cli`, `utm_content=<which link>`.\n * The command dimension rides on every wizard event as the `command` tag.\n * Opening a link also captures a wizard event.\n */\nimport opn from 'opn';\nimport { NODE_ENV } from '@env';\nimport { analytics } from './analytics';\n\n/**\n * Record the CLI command this run was started with (e.g. `integrate`,\n * `slack`, `mcp-add`). Set once at dispatch; tagged onto every wizard\n * event so wizard-side events segment by command.\n */\nexport function setEntryCommand(command: string): void {\n analytics.setTag('command', command);\n}\n\n/**\n * Tag a URL with the wizard's UTM params. `content` names the specific link\n * (e.g. `oauth-signup`, `slack-connect-setup`). URLs that already carry a\n * utm_source — or don't parse — are returned untouched.\n */\nexport function withUtm(url: string, content: string): string {\n let parsed: URL;\n try {\n parsed = new URL(url);\n } catch {\n return url;\n }\n if (parsed.searchParams.has('utm_source')) return url;\n parsed.searchParams.set('utm_source', 'wizard');\n parsed.searchParams.set('utm_medium', 'cli');\n parsed.searchParams.set('utm_content', content);\n return parsed.toString();\n}\n\n/**\n * Open a URL in the user's browser, UTM-tagged (pass `skipUtm` for\n * destinations that aren't PostHog properties or are already final), and\n * capture the interaction. `auto` marks opens the wizard initiated itself,\n * as opposed to a user picking a link on screen. opn throws in headless\n * environments — the URL is always also printed on screen, so the failure\n * is swallowed.\n */\nexport function openTrackedLink(\n url: string,\n content: string,\n opts?: { auto?: boolean; skipUtm?: boolean },\n): void {\n const finalUrl = opts?.skipUtm ? url : withUtm(url, content);\n analytics.wizardCapture('link opened', {\n content,\n url: finalUrl,\n auto: opts?.auto ?? false,\n });\n if (NODE_ENV !== 'test') {\n opn(finalUrl, { wait: false }).catch(() => {\n // No browser available — the printed URL is the fallback.\n });\n }\n}\n","import { analytics } from './analytics';\n\nexport function withProgress<T>(step: string, callback: () => T): T {\n updateProgress(step);\n return callback();\n}\n\nexport function updateProgress(step: string) {\n analytics.setTag('progress', step);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAiBA,SAAgB,gBAAgB,SAAuB;AACrD,WAAU,OAAO,WAAW,QAAQ;;;;;;;AAQtC,SAAgB,QAAQ,KAAa,SAAyB;CAC5D,IAAI;AACJ,KAAI;AACF,WAAS,IAAI,IAAI,IAAI;SACf;AACN,SAAO;;AAET,KAAI,OAAO,aAAa,IAAI,aAAa,CAAE,QAAO;AAClD,QAAO,aAAa,IAAI,cAAc,SAAS;AAC/C,QAAO,aAAa,IAAI,cAAc,MAAM;AAC5C,QAAO,aAAa,IAAI,eAAe,QAAQ;AAC/C,QAAO,OAAO,UAAU;;;;;;;;;;AAW1B,SAAgB,gBACd,KACA,SACA,MACM;CACN,MAAM,WAAW,MAAM,UAAU,MAAM,QAAQ,KAAK,QAAQ;AAC5D,WAAU,cAAc,eAAe;EACrC;EACA,KAAK;EACL,MAAM,MAAM,QAAQ;EACrB,CAAC;AAEA,KAAI,UAAU,EAAE,MAAM,OAAO,CAAC,CAAC,YAAY,GAEzC;;;;AC5DN,SAAgB,aAAgB,MAAc,UAAsB;AAClE,gBAAe,KAAK;AACpB,QAAO,UAAU;;AAGnB,SAAgB,eAAe,MAAc;AAC3C,WAAU,OAAO,YAAY,KAAK"}
@@ -1,18 +1,18 @@
1
- import { s as logToFile } from "./debug-Cj-8vfXY.js";
2
- import { $ as getSkillsBaseUrl, D as POSTHOG_APP_URL, Q as WIZARD_USER_AGENT } from "./constants-CuJfQEcC.js";
1
+ import { et as fetchSkillMenu } from "./agent-interface-FkbaBJwz.js";
2
+ import { s as logToFile } from "./debug-kAHwWGg7.js";
3
+ import { $ as getSkillsBaseUrl, D as POSTHOG_APP_URL, Q as WIZARD_USER_AGENT } from "./constants-DHCBSfnx.js";
3
4
  import "./wizard-session-2w4Ru-Wz.js";
4
- import { n as analytics } from "./analytics-VRYdebXW.js";
5
- import { i as withUtm, n as openTrackedLink } from "./telemetry-Bpv-Mh5B.js";
6
- import { t as getOrAskForProjectData } from "./setup-utils-dCViCC6s.js";
7
- import { n as SERVICE_LABELS } from "./logging-ui-CUxNYF2t.js";
8
- import { a as fetchSlackConnected, o as fetchUserData } from "./api-DGuJTzjr.js";
9
- import { et as fetchSkillMenu } from "./agent-interface-BXvmAm4c.js";
10
- import { o as formatCostUsd, s as formatTokenCount } from "./agent-CNrEC-9v.js";
11
- import { u as AUDIT_SEVERITY_STYLE } from "./run-agent-legacy-6WcYMaR9.js";
12
- import { $ as wordWrap, H as getKindMeta, Q as computeVisibleRange, Z as TextBlock, et as isClearBlock, it as Icons, nt as isObjectBlock, o as Program, rt as Colors, tt as isLinesBlock } from "./mint-failure-CZFZpHpW.js";
13
- import { a as useStdoutDimensions, i as useKeyboardHintsContext, n as useKeyBindings, o as ConfirmButton, r as KeyboardHintsProvider, s as PromptLabel, t as PickerMenu } from "./bin.js";
14
- import { r as totalTokenCount } from "./store-xYy_6Ruv.js";
15
- import { c as isOk, f as summarizeFailure, l as namesWithStatus, n as AVAILABLE_FEATURES, o as isAllFeaturesSelected, t as ALL_FEATURE_VALUES } from "./defaults-C6J7yL4O.js";
5
+ import { n as analytics } from "./analytics-DXK1gc12.js";
6
+ import { i as withUtm, n as openTrackedLink } from "./telemetry-B7zJdRmA.js";
7
+ import { r as getOrAskForProjectData } from "./setup-utils-B26Lf2yF.js";
8
+ import { n as SERVICE_LABELS } from "./logging-ui-Dza4D0MK.js";
9
+ import { c as fetchUserData, s as fetchSlackConnected } from "./api-CrbCIQOz.js";
10
+ import { o as formatCostUsd, s as formatTokenCount } from "./agent-BWFhuLgo.js";
11
+ import { l as AUDIT_SEVERITY_STYLE } from "./run-agent-legacy-DOlM6DQu.js";
12
+ import { $ as wordWrap, H as getKindMeta, Q as computeVisibleRange, Z as TextBlock, et as isClearBlock, it as Icons, nt as isObjectBlock, o as Program, rt as Colors, tt as isLinesBlock } from "./mint-failure-u8xQKNOn.js";
13
+ import { a as useStdoutDimensions, i as useKeyboardHintsContext, n as useKeyBindings, o as ConfirmButton, r as KeyboardHintsProvider, s as PromptLabel, t as PickerMenu } from "./PickerMenu-dG5DQCZA.js";
14
+ import { n as totalTokenCount } from "./store-BTzcvRGT.js";
15
+ import { c as isOk, f as summarizeFailure, l as namesWithStatus, n as AVAILABLE_FEATURES, o as isAllFeaturesSelected, t as ALL_FEATURE_VALUES } from "./defaults-a9TelDhJ.js";
16
16
  import opn from "opn";
17
17
  import * as fs$1 from "fs";
18
18
  import axios from "axios";
@@ -7193,4 +7193,4 @@ function releaseTerminal() {
7193
7193
  //#endregion
7194
7194
  export { ScreenContainer as A, LoadingBox as B, ServiceHealthList as C, ContentSequencer as D, LearnCard as E, extractUrls as F, CardLayout as H, ModalOverlay as I, ConfirmationInput as L, EventPlanViewer as M, LogViewer as N, HNViewer as O, LinkText as P, GroupedPickerMenu as R, SEVERITY_ORDER as S, TipsCard as T, SplitView as V, probeProjectData as _, useSkillEntry as a, IssueTable as b, SlackConnectScreen as c, AuditChecksViewer as d, countNoun as f, assembleProfile as g, McpSuggestedPromptsScreen as h, SkillSourceInfo as i, ViewportTooSmall as j, TabContainer as k, AUDIT_AREA_SLIDES as l, seededProfile as m, releaseTerminal as n, OutroScreen as o, seedDemoEvents as p, AiOptInRequiredScreen as r, useDismissOnAnyKey as s, enterDarkTerminal as t, VisualBox as u, TAILORED_ROLES as v, VisualizerTab as w, SEVERITY_LABEL as x, McpScreen as y, ProgressList as z };
7195
7195
 
7196
- //# sourceMappingURL=terminal-D07EQ2y0.js.map
7196
+ //# sourceMappingURL=terminal-zgkEfoCL.js.map