@posthog/wizard 2.52.0 → 2.53.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. package/dist/{add-mcp-server-to-clients-DJyWMMGj.js → add-mcp-server-to-clients-DmMHsYYM.js} +5 -5
  2. package/dist/{add-mcp-server-to-clients-DJyWMMGj.js.map → add-mcp-server-to-clients-DmMHsYYM.js.map} +1 -1
  3. package/dist/{agent-interface-DoFVHRFh.js → agent-interface-xWyATfJi.js} +330 -25
  4. package/dist/agent-interface-xWyATfJi.js.map +1 -0
  5. package/dist/{agent-prompt-loader-D-cPVoL5.js → agent-prompt-loader-i6miJlmN.js} +5 -5
  6. package/dist/{agent-prompt-loader-D-cPVoL5.js.map → agent-prompt-loader-i6miJlmN.js.map} +1 -1
  7. package/dist/{agent-runner-B1qCtjid.js → agent-runner-CPuubcP_.js} +157 -137
  8. package/dist/agent-runner-CPuubcP_.js.map +1 -0
  9. package/dist/{analytics-BXz4SIuS.js → analytics-BC3iiS1p.js} +5 -2
  10. package/dist/analytics-BC3iiS1p.js.map +1 -0
  11. package/dist/analytics-CLuGZo9D.js +2 -0
  12. package/dist/{api-BaacjH-Y.js → api-CXk-efCU.js} +3 -3
  13. package/dist/{api-BaacjH-Y.js.map → api-CXk-efCU.js.map} +1 -1
  14. package/dist/bin.js +316 -179
  15. package/dist/bin.js.map +1 -1
  16. package/dist/{bounded-fs-Cx-8nk-1.js → bounded-fs-B6Eqbgoj.js} +3 -3
  17. package/dist/{bounded-fs-Cx-8nk-1.js.map → bounded-fs-B6Eqbgoj.js.map} +1 -1
  18. package/dist/{ci-install-D1Mum8Ah.js → ci-install-CXD2meJ4.js} +5 -5
  19. package/dist/{ci-install-D1Mum8Ah.js.map → ci-install-CXD2meJ4.js.map} +1 -1
  20. package/dist/{debug-DsSCOPe8.js → debug-Bz-LaIU8.js} +1 -1
  21. package/dist/{debug-C3ZR1TVF.js → debug-tCr3A18e.js} +13 -4
  22. package/dist/debug-tCr3A18e.js.map +1 -0
  23. package/dist/{defaults-DAE9_QMn.js → defaults-BWLIffh5.js} +1 -1
  24. package/dist/{defaults-DAE9_QMn.js.map → defaults-BWLIffh5.js.map} +1 -1
  25. package/dist/{env-api-key-DDVkPQmo.js → env-api-key-cjD4yfGT.js} +1 -1
  26. package/dist/{env-api-key-DDVkPQmo.js.map → env-api-key-cjD4yfGT.js.map} +1 -1
  27. package/dist/{environment-Bi-zynk6.js → environment-JwRHoXao.js} +4 -4
  28. package/dist/{environment-Bi-zynk6.js.map → environment-JwRHoXao.js.map} +1 -1
  29. package/dist/{file-watcher-PmRIDAfM.js → file-watcher-Cm-B3jyb.js} +2 -2
  30. package/dist/{file-watcher-PmRIDAfM.js.map → file-watcher-Cm-B3jyb.js.map} +1 -1
  31. package/dist/{headless-ui-C4X9lAOY.js → headless-ui-o1cpGr1W.js} +2 -2
  32. package/dist/{headless-ui-C4X9lAOY.js.map → headless-ui-o1cpGr1W.js.map} +1 -1
  33. package/dist/{interactive-HM71vLQe.js → interactive-CM2fMt8l.js} +3 -3
  34. package/dist/{interactive-HM71vLQe.js.map → interactive-CM2fMt8l.js.map} +1 -1
  35. package/dist/{mcp-D0Hpb_nk.js → mcp-C4Xn8mq5.js} +2 -2
  36. package/dist/{mcp-D0Hpb_nk.js.map → mcp-C4Xn8mq5.js.map} +1 -1
  37. package/dist/{mcp-prompt-streaming-Crk8qFlQ.js → mcp-prompt-streaming-Dqrcns_Z.js} +5 -4
  38. package/dist/{mcp-prompt-streaming-Crk8qFlQ.js.map → mcp-prompt-streaming-Dqrcns_Z.js.map} +1 -1
  39. package/dist/{non-interactive-DdUe8lqX.js → non-interactive-DYvEgWZx.js} +2 -2
  40. package/dist/{non-interactive-DdUe8lqX.js.map → non-interactive-DYvEgWZx.js.map} +1 -1
  41. package/dist/{orchestrator-tools-CpRruqZl.js → orchestrator-tools-CAl7aLtB.js} +3 -3
  42. package/dist/{orchestrator-tools-CpRruqZl.js.map → orchestrator-tools-CAl7aLtB.js.map} +1 -1
  43. package/dist/{package-manager-wkSzPJqW.js → package-manager-DnU0j41n.js} +2 -2
  44. package/dist/{package-manager-wkSzPJqW.js.map → package-manager-DnU0j41n.js.map} +1 -1
  45. package/dist/pi-2hh_NXKT.js +409 -0
  46. package/dist/pi-2hh_NXKT.js.map +1 -0
  47. package/dist/{playground-lg7h9GHG.js → playground-rG007Ve9.js} +353 -18
  48. package/dist/playground-rG007Ve9.js.map +1 -0
  49. package/dist/{posthog-mgz-FQlS.js → posthog-DDLIrku0.js} +1 -1
  50. package/dist/{posthog-mgz-FQlS.js.map → posthog-DDLIrku0.js.map} +1 -1
  51. package/dist/{posthog-integration-pnxhTbxw.js → posthog-integration-Bd-kybes.js} +15 -15
  52. package/dist/{posthog-integration-pnxhTbxw.js.map → posthog-integration-Bd-kybes.js.map} +1 -1
  53. package/dist/{provisioning-T4jtJqBa.js → provisioning-tkKe6aM4.js} +3 -3
  54. package/dist/{provisioning-T4jtJqBa.js.map → provisioning-tkKe6aM4.js.map} +1 -1
  55. package/dist/{queue-tools-D7ctuzyo.js → queue-tools-kBa-8bPH.js} +45 -32
  56. package/dist/queue-tools-kBa-8bPH.js.map +1 -0
  57. package/dist/{registry-CsFuKtXn.js → registry-Cg_M-kov.js} +5 -5
  58. package/dist/{registry-CsFuKtXn.js.map → registry-Cg_M-kov.js.map} +1 -1
  59. package/dist/{security-YAaZfmHs.js → security-BtIAMdyk.js} +5 -5
  60. package/dist/security-BtIAMdyk.js.map +1 -0
  61. package/dist/{setup-utils-DDzEnDxJ.js → setup-utils-t0jzKwBE.js} +8 -8
  62. package/dist/{setup-utils-DDzEnDxJ.js.map → setup-utils-t0jzKwBE.js.map} +1 -1
  63. package/dist/{start-tui-pCi7WVAK.js → start-tui-CK3rpaKD.js} +35 -21
  64. package/dist/start-tui-CK3rpaKD.js.map +1 -0
  65. package/dist/{steps-DmsrBqb9.js → steps-dNXQiyOZ.js} +7 -7
  66. package/dist/{steps-DmsrBqb9.js.map → steps-dNXQiyOZ.js.map} +1 -1
  67. package/dist/{store-BCRdtfOH.js → store-qv5UdGQO.js} +5 -5
  68. package/dist/{store-BCRdtfOH.js.map → store-qv5UdGQO.js.map} +1 -1
  69. package/dist/{subagent-jNXS8IPU.js → subagent-DsfcNf0P.js} +2 -2
  70. package/dist/{subagent-jNXS8IPU.js.map → subagent-DsfcNf0P.js.map} +1 -1
  71. package/dist/{task-CSmrfyNf.js → task-BZ77d7rm.js} +30 -23
  72. package/dist/task-BZ77d7rm.js.map +1 -0
  73. package/dist/{task-stream-BZOjOhKu.js → task-stream-5k_cd_Wz.js} +4 -4
  74. package/dist/{task-stream-BZOjOhKu.js.map → task-stream-5k_cd_Wz.js.map} +1 -1
  75. package/dist/{tasks-BMgQt251.js → tasks-C9yxUVN7.js} +2 -2
  76. package/dist/{tasks-BMgQt251.js.map → tasks-C9yxUVN7.js.map} +1 -1
  77. package/dist/{telemetry-qeKIGkte.js → telemetry-5NqJBWNN.js} +3 -3
  78. package/dist/{telemetry-qeKIGkte.js.map → telemetry-5NqJBWNN.js.map} +1 -1
  79. package/dist/{terminal-tbxhJGlI.js → terminal-q-Sd8CdO.js} +806 -62
  80. package/dist/terminal-q-Sd8CdO.js.map +1 -0
  81. package/dist/{tools-DVnBIYFn.js → tools-2weX1XHC.js} +8 -10
  82. package/dist/tools-2weX1XHC.js.map +1 -0
  83. package/dist/warlock-smoke-test.ts +177 -1
  84. package/dist/{wizard-abort-AEhoDJ4P.js → wizard-abort-B1XewmPn.js} +3 -3
  85. package/dist/{wizard-abort-AEhoDJ4P.js.map → wizard-abort-B1XewmPn.js.map} +1 -1
  86. package/dist/{wizard-abort-DHC_XStW.js → wizard-abort-Cl8D3gW8.js} +1 -1
  87. package/dist/yara-hooks-Cj7xUAKE.js +2 -0
  88. package/dist/{yara-hooks-CKsVxWiN.js → yara-hooks-Cu0rEw1L.js} +39 -83
  89. package/dist/yara-hooks-Cu0rEw1L.js.map +1 -0
  90. package/package.json +2 -1
  91. package/dist/agent-interface-DoFVHRFh.js.map +0 -1
  92. package/dist/agent-runner-B1qCtjid.js.map +0 -1
  93. package/dist/analytics-BXz4SIuS.js.map +0 -1
  94. package/dist/analytics-D2xds3nw.js +0 -2
  95. package/dist/commandments-BagyrYRW.js +0 -2
  96. package/dist/commandments-DiMtIz1o.js +0 -42
  97. package/dist/commandments-DiMtIz1o.js.map +0 -1
  98. package/dist/debug-C3ZR1TVF.js.map +0 -1
  99. package/dist/pi-CGPjm4oE.js +0 -570
  100. package/dist/pi-CGPjm4oE.js.map +0 -1
  101. package/dist/playground-lg7h9GHG.js.map +0 -1
  102. package/dist/queue-tools-D7ctuzyo.js.map +0 -1
  103. package/dist/security-YAaZfmHs.js.map +0 -1
  104. package/dist/start-tui-pCi7WVAK.js.map +0 -1
  105. package/dist/task-CSmrfyNf.js.map +0 -1
  106. package/dist/terminal-tbxhJGlI.js.map +0 -1
  107. package/dist/tools-DVnBIYFn.js.map +0 -1
  108. package/dist/yara-hooks-CKsVxWiN.js.map +0 -1
  109. package/dist/yara-hooks-DWRdZQuG.js +0 -2
@@ -0,0 +1,409 @@
1
+ import { a as getLogFilePath, f as getUI, ft as WIZARD_USER_AGENT, s as logToFile, ut as WIZARD_REMARK_EVENT_NAME } from "./debug-tCr3A18e.js";
2
+ import { n as analytics } from "./analytics-BC3iiS1p.js";
3
+ import { M as GATEWAY_PROVIDER, N as buildGatewayProvider, f as AgentOutputSignals, h as assembleCommandments, m as REMARK_INSTRUCTION, p as AgentSignals } from "./agent-interface-xWyATfJi.js";
4
+ import fs from "fs";
5
+ import path from "path";
6
+ //#region src/lib/agent/runner/harness/pi/completion.ts
7
+ /** Which completion guard should fail a pi run, or undefined for a clean finish. */
8
+ function completionFailure(args) {
9
+ if (args.toolCalls === 0) return "WIZARD_NO_PROGRESS";
10
+ if (args.openTasks) return "WIZARD_INCOMPLETE_TASKS";
11
+ }
12
+ //#endregion
13
+ //#region src/lib/agent/runner/harness/pi/index.ts
14
+ /**
15
+ * The `pi` backend — the challenger. Drives pi.dev's coding agent
16
+ * (`@earendil-works/pi-coding-agent`) against the PostHog LLM gateway, behind
17
+ * `wizard-orchestrator`. It owns the agent loop and model transport; prompt
18
+ * assembly, error routing, and the outro stay in `linear.ts`, shared with the
19
+ * `anthropic` control.
20
+ *
21
+ * Transport: the gateway is registered as an `anthropic-messages` provider
22
+ * (same protocol the claude-agent-sdk path uses), bearer auth, Bedrock-fallback
23
+ * + wizard metadata/flag headers, model id matched to `anthropic` for a clean
24
+ * A/B. Security parity (canUseTool + YARA) and skills/MCP discovery are
25
+ * follow-ups (#525, #524 skills) — v1 uses pi's built-in coding tools.
26
+ */
27
+ /** Injects the MCP server `instructions` pi-mcp-adapter drops (project env, skill steer, tool domains) into the system prompt, falling back to a bootstrap-derived project block when the warm-connect captured none. */
28
+ function piMcpContext(boot, instructions) {
29
+ if (instructions) return [
30
+ "",
31
+ "## PostHog MCP server",
32
+ instructions
33
+ ].join("\n");
34
+ return [
35
+ "",
36
+ "## PostHog project",
37
+ "Your `posthog_exec` calls run against this project:",
38
+ `- Project: ${boot.project?.name ? `${boot.project.name} (id ${boot.credentials.projectId})` : `id ${boot.credentials.projectId}`}`,
39
+ `- Host: ${boot.credentials.host.apiHost}`,
40
+ `- Region: ${boot.credentials.host.region}`
41
+ ].join("\n");
42
+ }
43
+ /**
44
+ * The ONLY environment variables pi's tool subprocesses (bash → npm/pip/…) are
45
+ * allowed to see. Everything else — every secret (POSTHOG_PERSONAL_API_KEY,
46
+ * ANTHROPIC_*, AWS_*), every ambient credential, the parent process's whole env
47
+ * — is dropped before a child is spawned. pi's own gateway auth is programmatic
48
+ * (the access token never lives in env), so a minimal env costs the agent
49
+ * nothing while closing the leak that exposed the key before. Kept to what a
50
+ * package manager genuinely needs to run.
51
+ */
52
+ const ALLOWED_SUBPROCESS_ENV_KEYS = [
53
+ "PATH",
54
+ "HOME",
55
+ "SHELL",
56
+ "USER",
57
+ "LOGNAME",
58
+ "TMPDIR",
59
+ "TMP",
60
+ "TEMP",
61
+ "TERM",
62
+ "LANG",
63
+ "LC_ALL",
64
+ "LC_CTYPE",
65
+ "NODE_EXTRA_CA_CERTS",
66
+ "SSL_CERT_FILE",
67
+ "SSL_CERT_DIR",
68
+ "HTTP_PROXY",
69
+ "HTTPS_PROXY",
70
+ "NO_PROXY",
71
+ "http_proxy",
72
+ "https_proxy",
73
+ "no_proxy"
74
+ ];
75
+ /** A fresh subprocess env holding only the allowlisted keys present in process.env. */
76
+ function buildScrubbedEnv() {
77
+ const env = {};
78
+ for (const key of ALLOWED_SUBPROCESS_ENV_KEYS) {
79
+ const value = process.env[key];
80
+ if (value !== void 0) env[key] = value;
81
+ }
82
+ return env;
83
+ }
84
+ /**
85
+ * Tag a tool with an execution mode (mutates + returns it). Read-only tools are
86
+ * `parallel` so a single turn that batches independent reads/searches runs them
87
+ * at once; mutating/install tools are `sequential` so a batch never races writes
88
+ * or concurrent installs. pi-agent-core runs a batch in parallel only when no
89
+ * tool in it is `sequential`.
90
+ */
91
+ function withMode(tool, mode) {
92
+ tool.executionMode = mode;
93
+ return tool;
94
+ }
95
+ /** Pull plain text out of a pi AgentMessage (content is text/image blocks). */
96
+ function extractText(message) {
97
+ const content = message?.content;
98
+ if (typeof content === "string") return content;
99
+ if (Array.isArray(content)) return content.filter((c) => {
100
+ const block = c;
101
+ return block?.type === "text" && typeof block.text === "string";
102
+ }).map((c) => c.text).join("");
103
+ return "";
104
+ }
105
+ /**
106
+ * Surface `[DASHBOARD_URL]` / `[NOTEBOOK_URL]` markers the agent prints (after
107
+ * the MCP creates them) into the outro link, mirroring the anthropic path's
108
+ * signal parsing (#9). The marker carries the URL the MCP returned.
109
+ */
110
+ function applyOutroMarkers(textBlock) {
111
+ const markers = [[AgentSignals.DASHBOARD_URL, (url) => getUI().setDashboardUrl(url)], [AgentSignals.NOTEBOOK_URL, (url) => getUI().setNotebookUrl(url)]];
112
+ for (const [marker, apply] of markers) {
113
+ const idx = textBlock.indexOf(marker);
114
+ if (idx === -1) continue;
115
+ const url = textBlock.slice(idx + marker.length).trim().split(/\s/)[0];
116
+ if (url) apply(url);
117
+ }
118
+ }
119
+ /**
120
+ * The text of the last `[STATUS] …` line in a block, if any. Last wins so the
121
+ * spinner shows the most recent action when a turn prints several.
122
+ */
123
+ function lastStatusLine(textBlock) {
124
+ let status;
125
+ for (const line of textBlock.split("\n")) {
126
+ const idx = line.indexOf(AgentSignals.STATUS);
127
+ if (idx !== -1) status = line.slice(idx + AgentSignals.STATUS.length).trim();
128
+ }
129
+ return status || void 0;
130
+ }
131
+ /** Cap on completion-guard re-prompts while tasks remain open (see the run loop). */
132
+ const MAX_CONTINUE_NUDGES = 20;
133
+ /** Nudge re-sent when the agent stops early with tasks still open. */
134
+ const CONTINUE_INSTRUCTION = "You still have open tasks in your list (not all are marked `completed`). Do not stop — pick up the next `in_progress` or `pending` task and keep working until every task is `completed`. Continue now.";
135
+ /** True while any task in the store is not yet `completed`. */
136
+ function hasOpenTasks(store) {
137
+ for (const t of store.values()) if (t.status !== "completed") return true;
138
+ return false;
139
+ }
140
+ const piBackend = {
141
+ name: "pi",
142
+ async run(inputs) {
143
+ const { session, boot, prompt, spinner, config, programConfig } = inputs;
144
+ const modelId = inputs.model;
145
+ getUI().log.step("Initializing Wizard agent...");
146
+ getUI().log.step(`Verbose logs: ${getLogFilePath()}`);
147
+ getUI().log.success("Agent initialized. Let's get cooking!");
148
+ spinner.start(config.spinnerMessage ?? "Customizing your PostHog setup...");
149
+ const startTime = Date.now();
150
+ const signals = new AgentOutputSignals();
151
+ let assistantTurns = 0;
152
+ let toolCalls = 0;
153
+ const runDurations = () => {
154
+ const durationMs = Date.now() - startTime;
155
+ return {
156
+ duration_ms: durationMs,
157
+ duration_seconds: Math.round(durationMs / 1e3)
158
+ };
159
+ };
160
+ const captureAborted = () => analytics.wizardCapture("agent aborted", {
161
+ ...runDurations(),
162
+ model: modelId
163
+ });
164
+ try {
165
+ const { createAgentSession, DefaultResourceLoader, SessionManager, AuthStorage, ModelRegistry, getAgentDir, createLsToolDefinition, createFindToolDefinition, createGrepToolDefinition, createBashToolDefinition, createReadToolDefinition, createEditToolDefinition, createWriteToolDefinition } = await import("@earendil-works/pi-coding-agent");
166
+ const { provider, caps } = buildGatewayProvider({
167
+ gatewayUrl: boot.credentials.host.gatewayUrl,
168
+ accessToken: boot.credentials.accessToken,
169
+ wizardMetadata: boot.wizardMetadata,
170
+ wizardFlags: boot.wizardFlags,
171
+ modelId,
172
+ effort: inputs.thinkingLevel
173
+ });
174
+ const registry = ModelRegistry.inMemory(AuthStorage.create());
175
+ registry.registerProvider(GATEWAY_PROVIDER, provider);
176
+ const model = registry.find(GATEWAY_PROVIDER, modelId);
177
+ if (!model) return {
178
+ error: "WIZARD_API_ERROR",
179
+ message: "pi: gateway model could not be resolved"
180
+ };
181
+ const askState = { pending: false };
182
+ const { createSecurityExtension } = await import("./security-BtIAMdyk.js");
183
+ const security = createSecurityExtension({
184
+ disallowedTools: programConfig.disallowedTools,
185
+ getWizardAskPending: () => askState.pending,
186
+ triageProvider: boot.triageProvider,
187
+ workingDirectory: session.installDir
188
+ });
189
+ const { prewarmYaraScanner } = await import("./yara-hooks-Cj7xUAKE.js");
190
+ prewarmYaraScanner();
191
+ const extensionFactories = [security.factory];
192
+ let mcpCleanup;
193
+ let mcpInstructions;
194
+ try {
195
+ const { setupPostHogMcp, fetchInstructions } = await import("./mcp-C4Xn8mq5.js");
196
+ const instructionsPromise = fetchInstructions(boot.credentials.host.mcpUrl, boot.credentials.accessToken, WIZARD_USER_AGENT);
197
+ const mcp = await setupPostHogMcp({
198
+ mcpUrl: boot.credentials.host.mcpUrl,
199
+ accessToken: boot.credentials.accessToken,
200
+ userAgent: WIZARD_USER_AGENT
201
+ });
202
+ extensionFactories.push(mcp.extensionFactory);
203
+ mcpCleanup = mcp.cleanup;
204
+ mcpInstructions = await instructionsPromise;
205
+ } catch (err) {
206
+ logToFile(`[pi] PostHog MCP setup skipped: ${String(err)}`);
207
+ analytics.wizardCapture("mcp setup failed", {
208
+ harness: "pi",
209
+ scope: "run",
210
+ error: String(err).slice(0, 300)
211
+ });
212
+ }
213
+ const resourceLoader = new DefaultResourceLoader({
214
+ cwd: session.installDir,
215
+ agentDir: getAgentDir(),
216
+ systemPrompt: assembleCommandments({
217
+ program: programConfig.id,
218
+ sequence: "linear",
219
+ harness: "pi",
220
+ caps: {
221
+ bash: true,
222
+ posthogMcp: true
223
+ }
224
+ }) + "\n" + piMcpContext(boot, mcpInstructions),
225
+ noExtensions: true,
226
+ noSkills: true,
227
+ noContextFiles: true,
228
+ noPromptTemplates: true,
229
+ noThemes: true,
230
+ extensionFactories
231
+ });
232
+ await resourceLoader.reload();
233
+ const { createWizardPiTools } = await import("./tools-2weX1XHC.js");
234
+ const { createWizardPiTaskTools } = await import("./tasks-C9yxUVN7.js");
235
+ const { createDispatchAgentTool } = await import("./subagent-DsfcNf0P.js");
236
+ const wizardTaskTools = createWizardPiTaskTools();
237
+ const scrubbedBash = withMode(createBashToolDefinition(session.installDir, { spawnHook: (ctx) => ({
238
+ ...ctx,
239
+ env: buildScrubbedEnv()
240
+ }) }), "sequential");
241
+ const customTools = [
242
+ withMode(createReadToolDefinition(session.installDir), "parallel"),
243
+ withMode(createEditToolDefinition(session.installDir), "sequential"),
244
+ withMode(createWriteToolDefinition(session.installDir), "sequential"),
245
+ scrubbedBash,
246
+ withMode(createLsToolDefinition(session.installDir), "parallel"),
247
+ withMode(createFindToolDefinition(session.installDir), "parallel"),
248
+ withMode(createGrepToolDefinition(session.installDir), "parallel"),
249
+ ...createWizardPiTools({
250
+ workingDirectory: session.installDir,
251
+ skillsBaseUrl: boot.skillsBaseUrl,
252
+ triageProvider: boot.triageProvider,
253
+ detectPackageManager: config.detectPackageManager,
254
+ askBridge: inputs.askBridge,
255
+ maxQuestions: config.maxQuestions,
256
+ onAskPendingChange: (pending) => {
257
+ askState.pending = pending;
258
+ },
259
+ disallowedTools: programConfig.disallowedTools
260
+ }),
261
+ ...wizardTaskTools.tools,
262
+ createDispatchAgentTool({
263
+ model,
264
+ modelRegistry: registry,
265
+ cwd: session.installDir,
266
+ agentDir: getAgentDir(),
267
+ securityFactory: security.factory,
268
+ bashTool: scrubbedBash,
269
+ sdk: {
270
+ createAgentSession,
271
+ DefaultResourceLoader,
272
+ SessionManager
273
+ }
274
+ })
275
+ ];
276
+ const { session: agentSession } = await createAgentSession({
277
+ model,
278
+ modelRegistry: registry,
279
+ thinkingLevel: caps.thinkingLevel,
280
+ cwd: session.installDir,
281
+ sessionManager: SessionManager.inMemory(session.installDir),
282
+ resourceLoader,
283
+ noTools: "builtin",
284
+ customTools
285
+ });
286
+ await agentSession.bindExtensions({});
287
+ const unsubscribe = agentSession.subscribe((event) => {
288
+ switch (event.type) {
289
+ case "message_end": {
290
+ if (event.message?.role !== "assistant") break;
291
+ assistantTurns += 1;
292
+ const assistant = extractText(event.message).trim();
293
+ if (assistant) {
294
+ logToFile(`[pi] assistant: ${assistant.slice(0, 1e3)}`);
295
+ applyOutroMarkers(assistant);
296
+ const statusText = lastStatusLine(assistant);
297
+ if (statusText) {
298
+ getUI().pushStatus(statusText);
299
+ spinner.message(statusText);
300
+ }
301
+ for (const line of assistant.split("\n")) signals.push(line);
302
+ }
303
+ break;
304
+ }
305
+ case "tool_execution_start": {
306
+ toolCalls += 1;
307
+ const args = JSON.stringify(event.args ?? {}).slice(0, 200);
308
+ logToFile(`[pi] → ${event.toolName} ${args}`);
309
+ break;
310
+ }
311
+ case "tool_execution_end":
312
+ logToFile(`[pi] ${event.isError ? "✗" : "←"} ${event.toolName}: ${typeof event.result === "string" ? event.result : JSON.stringify(event.result ?? "")}`);
313
+ break;
314
+ case "agent_end":
315
+ logToFile(`[pi] agent_end (willRetry=${String(event.willRetry)})`);
316
+ break;
317
+ default: break;
318
+ }
319
+ });
320
+ try {
321
+ await agentSession.prompt(prompt);
322
+ let continueNudges = 0;
323
+ while (continueNudges < MAX_CONTINUE_NUDGES && !security.state.criticalViolation && hasOpenTasks(wizardTaskTools.store)) {
324
+ continueNudges += 1;
325
+ logToFile(`[pi] completion guard: tasks still open, nudge ${continueNudges}/${MAX_CONTINUE_NUDGES}`);
326
+ await agentSession.prompt(CONTINUE_INSTRUCTION);
327
+ }
328
+ if (!security.state.criticalViolation) try {
329
+ await agentSession.prompt(REMARK_INSTRUCTION);
330
+ } catch (err) {
331
+ logToFile(`[pi] remark request failed: ${String(err)}`);
332
+ }
333
+ } finally {
334
+ unsubscribe();
335
+ mcpCleanup?.();
336
+ }
337
+ if (security.state.criticalViolation) {
338
+ spinner.stop("Security violation detected");
339
+ logToFile(`[pi] terminated: YARA violation (blocked ${security.state.blockedCount} call(s))`);
340
+ captureAborted();
341
+ return { error: "WIZARD_YARA_VIOLATION" };
342
+ }
343
+ const openTasks = hasOpenTasks(wizardTaskTools.store);
344
+ const failure = completionFailure({
345
+ toolCalls,
346
+ openTasks
347
+ });
348
+ if (failure === "WIZARD_NO_PROGRESS") {
349
+ spinner.stop("Agent made no changes");
350
+ logToFile(`[pi] no progress: ${assistantTurns} assistant turn(s), 0 tool calls`);
351
+ analytics.wizardCapture("agent no progress", { assistant_turns: assistantTurns });
352
+ captureAborted();
353
+ return { error: failure };
354
+ }
355
+ if (failure === "WIZARD_INCOMPLETE_TASKS") {
356
+ spinner.stop("Agent stopped before finishing");
357
+ logToFile("[pi] incomplete: tasks left open");
358
+ analytics.wizardCapture("agent incomplete tasks", { open_tasks: true });
359
+ captureAborted();
360
+ return { error: failure };
361
+ }
362
+ const remark = signals.remark();
363
+ if (remark) analytics.capture(WIZARD_REMARK_EVENT_NAME, { remark });
364
+ const skillFailure = signals.skillInstallFailure();
365
+ if (skillFailure !== void 0) analytics.wizardCapture("agent continued without skill", { detail: skillFailure });
366
+ try {
367
+ const planFile = path.join(session.installDir, ".posthog-events.json");
368
+ if (fs.existsSync(planFile)) await fs.promises.rm(planFile);
369
+ } catch (err) {
370
+ logToFile(`[pi] .posthog-events.json cleanup skipped: ${String(err)}`);
371
+ }
372
+ const stats = agentSession.getSessionStats();
373
+ analytics.wizardCapture("agent completed", {
374
+ ...runDurations(),
375
+ model: modelId,
376
+ num_turns: assistantTurns,
377
+ input_tokens: stats.tokens.input,
378
+ output_tokens: stats.tokens.output,
379
+ cache_creation_input_tokens: stats.tokens.cacheWrite,
380
+ cache_read_input_tokens: stats.tokens.cacheRead
381
+ });
382
+ spinner.stop(config.successMessage ?? "PostHog integration complete");
383
+ return {};
384
+ } catch (err) {
385
+ const message = err instanceof Error ? err.message : String(err);
386
+ logToFile(`[pi] run error: ${message}`);
387
+ spinner.stop(config.errorMessage ?? `${config.integrationLabel} failed`);
388
+ getUI().log.error(`pi backend error: ${message}`);
389
+ captureAborted();
390
+ const lower = message.toLowerCase();
391
+ if (lower.includes("rate limit") || lower.includes("429")) return {
392
+ error: "WIZARD_RATE_LIMIT",
393
+ message
394
+ };
395
+ return {
396
+ error: "WIZARD_API_ERROR",
397
+ message
398
+ };
399
+ }
400
+ },
401
+ async runTask(inputs) {
402
+ const { runPiTask } = await import("./task-BZ77d7rm.js");
403
+ return runPiTask(inputs);
404
+ }
405
+ };
406
+ //#endregion
407
+ export { piBackend as a, lastStatusLine as i, buildScrubbedEnv as n, withMode as o, extractText as r, applyOutroMarkers as t };
408
+
409
+ //# sourceMappingURL=pi-2hh_NXKT.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pi-2hh_NXKT.js","names":[],"sources":["../src/lib/agent/runner/harness/pi/completion.ts","../src/lib/agent/runner/harness/pi/index.ts"],"sourcesContent":["import { AgentErrorType } from '@lib/agent/signals';\n\n/** Which completion guard should fail a pi run, or undefined for a clean finish. */\nexport function completionFailure(args: {\n toolCalls: number;\n openTasks: boolean;\n}): AgentErrorType | undefined {\n // No tool call at all — the project was never touched.\n if (args.toolCalls === 0) return AgentErrorType.NO_PROGRESS;\n // Acted but left tasks in its own plan unfinished (skill install not required).\n if (args.openTasks) return AgentErrorType.INCOMPLETE_TASKS;\n return undefined;\n}\n","/**\n * The `pi` backend — the challenger. Drives pi.dev's coding agent\n * (`@earendil-works/pi-coding-agent`) against the PostHog LLM gateway, behind\n * `wizard-orchestrator`. It owns the agent loop and model transport; prompt\n * assembly, error routing, and the outro stay in `linear.ts`, shared with the\n * `anthropic` control.\n *\n * Transport: the gateway is registered as an `anthropic-messages` provider\n * (same protocol the claude-agent-sdk path uses), bearer auth, Bedrock-fallback\n * + wizard metadata/flag headers, model id matched to `anthropic` for a clean\n * A/B. Security parity (canUseTool + YARA) and skills/MCP discovery are\n * follow-ups (#525, #524 skills) — v1 uses pi's built-in coding tools.\n */\n\nimport fs from 'fs';\nimport path from 'path';\nimport { getUI } from '@ui';\nimport { getLogFilePath, logToFile } from '@utils/debug';\nimport {\n Harness,\n Sequence,\n WIZARD_REMARK_EVENT_NAME,\n WIZARD_USER_AGENT,\n} from '@lib/constants';\nimport { analytics } from '@utils/analytics';\nimport { AgentErrorType } from '@lib/agent/agent-interface';\nimport { AgentSignals, REMARK_INSTRUCTION } from '@lib/agent/signals';\nimport { AgentOutputSignals } from '@lib/agent/output-signals';\nimport { assembleCommandments } from '../../switchboard/commandments';\nimport { buildGatewayProvider, GATEWAY_PROVIDER } from './gateway';\nimport type {\n AgentResult,\n AgentHarness,\n BackendRunInputs,\n TaskRunInputs,\n} from '../types';\nimport type { BootstrapResult } from '@lib/agent/runner/shared/types';\nimport type { TaskStore } from './tasks';\nimport { completionFailure } from './completion';\n\n/** Injects the MCP server `instructions` pi-mcp-adapter drops (project env, skill steer, tool domains) into the system prompt, falling back to a bootstrap-derived project block when the warm-connect captured none. */\nfunction piMcpContext(boot: BootstrapResult, instructions?: string): string {\n if (instructions) {\n // Heading + verbatim server instructions (see PR #862 for a full sample).\n return ['', '## PostHog MCP server', instructions].join('\\n');\n }\n const project = boot.project?.name\n ? `${boot.project.name} (id ${boot.credentials.projectId})`\n : `id ${boot.credentials.projectId}`;\n // Fallback: a `## PostHog project` block with name/id, host, region.\n return [\n '',\n '## PostHog project',\n 'Your `posthog_exec` calls run against this project:',\n `- Project: ${project}`,\n `- Host: ${boot.credentials.host.apiHost}`,\n `- Region: ${boot.credentials.host.region}`,\n ].join('\\n');\n}\n\n/**\n * The ONLY environment variables pi's tool subprocesses (bash → npm/pip/…) are\n * allowed to see. Everything else — every secret (POSTHOG_PERSONAL_API_KEY,\n * ANTHROPIC_*, AWS_*), every ambient credential, the parent process's whole env\n * — is dropped before a child is spawned. pi's own gateway auth is programmatic\n * (the access token never lives in env), so a minimal env costs the agent\n * nothing while closing the leak that exposed the key before. Kept to what a\n * package manager genuinely needs to run.\n */\nconst ALLOWED_SUBPROCESS_ENV_KEYS = [\n 'PATH',\n 'HOME',\n 'SHELL',\n 'USER',\n 'LOGNAME',\n 'TMPDIR',\n 'TMP',\n 'TEMP',\n 'TERM',\n 'LANG',\n 'LC_ALL',\n 'LC_CTYPE',\n 'NODE_EXTRA_CA_CERTS',\n 'SSL_CERT_FILE',\n 'SSL_CERT_DIR',\n 'HTTP_PROXY',\n 'HTTPS_PROXY',\n 'NO_PROXY',\n 'http_proxy',\n 'https_proxy',\n 'no_proxy',\n];\n\n/** A fresh subprocess env holding only the allowlisted keys present in process.env. */\nexport function buildScrubbedEnv(): NodeJS.ProcessEnv {\n const env: NodeJS.ProcessEnv = {};\n for (const key of ALLOWED_SUBPROCESS_ENV_KEYS) {\n const value = process.env[key];\n if (value !== undefined) env[key] = value;\n }\n return env;\n}\n\n/**\n * Tag a tool with an execution mode (mutates + returns it). Read-only tools are\n * `parallel` so a single turn that batches independent reads/searches runs them\n * at once; mutating/install tools are `sequential` so a batch never races writes\n * or concurrent installs. pi-agent-core runs a batch in parallel only when no\n * tool in it is `sequential`.\n */\nexport function withMode<T>(tool: T, mode: 'sequential' | 'parallel'): T {\n (tool as { executionMode?: 'sequential' | 'parallel' }).executionMode = mode;\n return tool;\n}\n\n/** Pull plain text out of a pi AgentMessage (content is text/image blocks). */\nexport function extractText(message: unknown): string {\n const content = (message as { content?: unknown })?.content;\n if (typeof content === 'string') return content;\n if (Array.isArray(content)) {\n return content\n .filter((c): c is { type: string; text: string } => {\n const block = c as { type?: string; text?: unknown };\n return block?.type === 'text' && typeof block.text === 'string';\n })\n .map((c) => c.text)\n .join('');\n }\n return '';\n}\n\n/**\n * Surface `[DASHBOARD_URL]` / `[NOTEBOOK_URL]` markers the agent prints (after\n * the MCP creates them) into the outro link, mirroring the anthropic path's\n * signal parsing (#9). The marker carries the URL the MCP returned.\n */\nexport function applyOutroMarkers(textBlock: string): void {\n const markers: Array<[string, (url: string) => void]> = [\n [AgentSignals.DASHBOARD_URL, (url) => getUI().setDashboardUrl(url)],\n [AgentSignals.NOTEBOOK_URL, (url) => getUI().setNotebookUrl(url)],\n ];\n for (const [marker, apply] of markers) {\n const idx = textBlock.indexOf(marker);\n if (idx === -1) continue;\n const url = textBlock\n .slice(idx + marker.length)\n .trim()\n .split(/\\s/)[0];\n if (url) apply(url);\n }\n}\n\n/**\n * The text of the last `[STATUS] …` line in a block, if any. Last wins so the\n * spinner shows the most recent action when a turn prints several.\n */\nexport function lastStatusLine(textBlock: string): string | undefined {\n let status: string | undefined;\n for (const line of textBlock.split('\\n')) {\n const idx = line.indexOf(AgentSignals.STATUS);\n if (idx !== -1) {\n status = line.slice(idx + AgentSignals.STATUS.length).trim();\n }\n }\n return status || undefined;\n}\n\n/** Cap on completion-guard re-prompts while tasks remain open (see the run loop). */\nconst MAX_CONTINUE_NUDGES = 20;\n\n/** Nudge re-sent when the agent stops early with tasks still open. */\nconst CONTINUE_INSTRUCTION =\n 'You still have open tasks in your list (not all are marked `completed`). Do not stop — pick up the next `in_progress` or `pending` task and keep working until every task is `completed`. Continue now.';\n\n/** True while any task in the store is not yet `completed`. */\nfunction hasOpenTasks(store: TaskStore): boolean {\n for (const t of store.values()) if (t.status !== 'completed') return true;\n return false;\n}\n\nexport const piBackend: AgentHarness = {\n name: Harness.pi,\n\n async run(inputs: BackendRunInputs): Promise<AgentResult> {\n const { session, boot, prompt, spinner, config, programConfig } = inputs;\n const modelId = inputs.model;\n\n // Init banner (parity #5).\n getUI().log.step('Initializing Wizard agent...');\n getUI().log.step(`Verbose logs: ${getLogFilePath()}`);\n getUI().log.success(\"Agent initialized. Let's get cooking!\");\n\n spinner.start(config.spinnerMessage ?? 'Customizing your PostHog setup...');\n\n // Same `agent completed`/`agent aborted` shape as anthropic.\n const startTime = Date.now();\n const signals = new AgentOutputSignals();\n let assistantTurns = 0;\n // Tool calls across the whole run. Zero means the agent only ever produced\n // text and never acted — a no-op that leaves the project untouched.\n let toolCalls = 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 const captureAborted = () =>\n analytics.wizardCapture('agent aborted', {\n ...runDurations(),\n model: modelId,\n });\n\n try {\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 } = await import('@earendil-works/pi-coding-agent');\n\n // the claude-agent-sdk path. The provider spec is shared with the\n // orchestrator's per-task sessions (gateway.ts).\n const { provider, caps } = buildGatewayProvider({\n gatewayUrl: boot.credentials.host.gatewayUrl,\n accessToken: boot.credentials.accessToken,\n wizardMetadata: boot.wizardMetadata,\n wizardFlags: boot.wizardFlags,\n modelId,\n effort: inputs.thinkingLevel,\n });\n const registry = ModelRegistry.inMemory(AuthStorage.create());\n registry.registerProvider(GATEWAY_PROVIDER, provider as never);\n\n const model = registry.find(GATEWAY_PROVIDER, modelId);\n if (!model) {\n return {\n error: AgentErrorType.API_ERROR,\n message: 'pi: gateway model could not be resolved',\n };\n }\n\n // System prompt = wizard commandments. Skip project context files /\n // user extensions / skills so the run is hermetic; skills discovery is a\n // follow-up (#524).\n //\n // Fail-closed security (#525): an extension intercepts EVERY tool call —\n // built-in and custom — and reuses the anthropic policy (canUseTool\n // allowlist + .env fencing + YARA). `noExtensions: true` only suppresses\n // disk-discovered extensions; explicit `extensionFactories` still load,\n // so the fence is on while the target project can't inject its own.\n // Shared flag: true while a wizard_ask overlay is open. The ask tool\n // flips it; the security gate reads it to block Write/Edit meanwhile.\n const askState = { pending: false };\n\n const { createSecurityExtension } = await import('./security');\n const security = createSecurityExtension({\n disallowedTools: programConfig.disallowedTools,\n getWizardAskPending: () => askState.pending,\n triageProvider: boot.triageProvider,\n // Where pi's bash runs; the rm allowance is confined to this tree.\n workingDirectory: session.installDir,\n });\n\n // Pay warlock's WASM-init + rule-compile cost now, off the tool-call\n // path, so the first scanned call doesn't eat cold-start latency.\n const { prewarmYaraScanner } = await import('@lib/yara-hooks');\n void prewarmYaraScanner();\n\n // Wire the real PostHog MCP into pi (#10): load pi's MCP adapter and point\n // it at the hosted MCP the anthropic path uses, so dashboards/insights are\n // created through the sanctioned MCP. Best-effort — if it can't load or\n // connect, the run continues (minus the dashboard step) rather than failing\n // the whole integration. The security factory is always first.\n const extensionFactories = [security.factory] as Array<\n (pi: unknown) => void\n >;\n let mcpCleanup: (() => void) | undefined;\n let mcpInstructions: string | undefined;\n try {\n const { setupPostHogMcp, fetchInstructions } = await import('./mcp');\n // Overlaps the network handshake with the adapter's jiti load.\n const instructionsPromise = fetchInstructions(\n boot.credentials.host.mcpUrl,\n boot.credentials.accessToken,\n WIZARD_USER_AGENT,\n );\n const mcp = await setupPostHogMcp({\n mcpUrl: boot.credentials.host.mcpUrl,\n accessToken: boot.credentials.accessToken,\n userAgent: WIZARD_USER_AGENT,\n });\n extensionFactories.push(mcp.extensionFactory);\n mcpCleanup = mcp.cleanup;\n mcpInstructions = await instructionsPromise;\n } catch (err) {\n logToFile(`[pi] PostHog MCP setup skipped: ${String(err)}`);\n analytics.wizardCapture('mcp setup failed', {\n harness: 'pi',\n scope: 'run',\n error: String(err).slice(0, 300),\n });\n }\n\n const resourceLoader = new DefaultResourceLoader({\n cwd: session.installDir,\n agentDir: getAgentDir(),\n systemPrompt:\n assembleCommandments({\n program: programConfig.id,\n sequence: Sequence.linear,\n harness: Harness.pi,\n caps: { bash: true, posthogMcp: true },\n }) +\n '\\n' +\n piMcpContext(boot, mcpInstructions),\n noExtensions: true,\n noSkills: true,\n noContextFiles: true,\n noPromptTemplates: true,\n noThemes: true,\n extensionFactories,\n });\n await resourceLoader.reload();\n\n // Wizard capabilities as custom tools (pi has no MCP): skill\n // discovery/install + fenced .env edits, same names as the MCP server so\n // the shared prompt is unchanged. pi's built-in Read/Write/Edit/Bash do\n // the code changes. Loaded lazily — it pulls in typebox (ESM), which must\n // stay out of the static module graph so CommonJS unit tests can load the\n // backend seam without parsing it.\n const { createWizardPiTools } = await import('./tools');\n const { createWizardPiTaskTools } = await import('./tasks');\n const { createDispatchAgentTool } = await import('./subagent');\n // Created once so the run loop can read the store for the completion guard.\n const wizardTaskTools = createWizardPiTaskTools();\n // The one bash the agent (and its subagents) may use: every subprocess it\n // spawns gets a scrubbed env, so no secret or ambient variable reaches an\n // `npm install`. Shared with the subagent so the lockdown is inherited.\n const scrubbedBash = withMode(\n createBashToolDefinition(session.installDir, {\n spawnHook: (ctx) => ({ ...ctx, env: buildScrubbedEnv() }),\n }),\n 'sequential',\n );\n\n const customTools = [\n // Built-ins re-registered explicitly. `noTools: 'builtin'` disables pi's\n // defaults so we can supply the env-scrubbed bash above; read/edit/write\n // are the stock definitions. Reads run in parallel so a batched turn of\n // independent reads executes at once; edit/write/bash stay sequential.\n withMode(createReadToolDefinition(session.installDir), 'parallel'),\n withMode(createEditToolDefinition(session.installDir), 'sequential'),\n withMode(createWriteToolDefinition(session.installDir), 'sequential'),\n scrubbedBash,\n // Native ls/find/grep so the agent explores with proper tools instead\n // of fence-blocked `bash {ls/find}` (the profiled retry-spirals came\n // from this gap). Parallel — exploration batches cleanly.\n withMode(createLsToolDefinition(session.installDir), 'parallel'),\n withMode(createFindToolDefinition(session.installDir), 'parallel'),\n withMode(createGrepToolDefinition(session.installDir), 'parallel'),\n ...createWizardPiTools({\n workingDirectory: session.installDir,\n skillsBaseUrl: boot.skillsBaseUrl,\n triageProvider: boot.triageProvider,\n detectPackageManager: config.detectPackageManager,\n // The host ask bridge — lets interactive programs (self-driving) ask\n // the user through pi. Threaded from the runner, same path as the\n // anthropic harness. Absent in CI → the tool errors on call.\n askBridge: inputs.askBridge,\n maxQuestions: config.maxQuestions,\n onAskPendingChange: (pending) => {\n askState.pending = pending;\n },\n // Skip wizard_ask when the program disallows it (bare pi tool names\n // don't match the MCP-prefixed disallow list at the security gate).\n disallowedTools: programConfig.disallowedTools,\n }),\n // Task/todo tools (#526): render the todo list live in the TUI, parity\n // with the anthropic path.\n ...wizardTaskTools.tools,\n // Controlled subagent dispatch (#526): a nested fenced session with a\n // read-only toolset and no dispatch_agent of its own, so it can't\n // escape the fence or recurse.\n createDispatchAgentTool({\n model,\n modelRegistry: registry,\n cwd: session.installDir,\n agentDir: getAgentDir(),\n securityFactory: security.factory as (pi: unknown) => void,\n bashTool: scrubbedBash,\n sdk: { createAgentSession, DefaultResourceLoader, SessionManager },\n }),\n ];\n\n const { session: agentSession } = await createAgentSession({\n model,\n modelRegistry: registry,\n // Reasoning effort from the switchboard capability matrix (undefined =\n // pi's default). Sent as `reasoning_effort` for openai-completions.\n thinkingLevel: caps.thinkingLevel,\n cwd: session.installDir,\n sessionManager: SessionManager.inMemory(session.installDir),\n resourceLoader,\n // Disable the default built-in tools; `customTools` re-registers\n // read/edit/write + an env-scrubbed bash, so no subprocess inherits the\n // host env. Custom + extension tools stay enabled.\n noTools: 'builtin',\n customTools,\n });\n\n // Fire the extension lifecycle — what interactive mode does via\n // rebindCurrentSession. createAgentSession builds the session but does not\n // emit session_start on its own, and the MCP adapter connects on that\n // event; without this its tools report \"MCP not initialized\".\n await agentSession.bindExtensions({});\n\n // Map pi events onto the run spinner + the log file, mirroring the\n // anthropic path's log shape (assistant turns + tool I/O) and driving the\n // single run spinner with one stable status at a time (no overlap).\n const unsubscribe = agentSession.subscribe((event) => {\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 const assistant = extractText(event.message).trim();\n if (assistant) {\n logToFile(`[pi] assistant: ${assistant.slice(0, 1000)}`);\n applyOutroMarkers(assistant);\n // Surface [STATUS] lines into the live spinner + status history,\n // mirroring the anthropic path — pi otherwise drops them.\n const statusText = lastStatusLine(assistant);\n if (statusText) {\n getUI().pushStatus(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 toolCalls += 1;\n const args = JSON.stringify(event.args ?? {}).slice(0, 200);\n logToFile(`[pi] → ${event.toolName} ${args}`);\n // Don't surface raw tool names in the spinner — the anthropic path\n // doesn't, and it reads as noise. The Task panel (syncTodos) is the\n // visible progress, matching the anthropic presentation.\n break;\n }\n case 'tool_execution_end': {\n // Log every result in full, matching the anthropic path's\n // SDK-message logging. Call-only logs make failed runs\n // undiagnosable: a tool can fail (or return something the model\n // misreads) with no trace of what came back.\n logToFile(\n `[pi] ${event.isError ? '✗' : '←'} ${event.toolName}: ${\n typeof event.result === 'string'\n ? event.result\n : JSON.stringify(event.result ?? '')\n }`,\n );\n break;\n }\n case 'agent_end': {\n logToFile(`[pi] agent_end (willRetry=${String(event.willRetry)})`);\n break;\n }\n default:\n break;\n }\n });\n\n try {\n // Non-streaming: resolves when the agent run completes. Throws if no\n // model/api key, or on a transport error.\n await agentSession.prompt(prompt);\n\n // Completion guard: pi's prompt() resolves the moment the model returns\n // a turn with no tool call (e.g. a lone [STATUS] line), even mid-plan.\n // While tasks remain open and we're under the cap, nudge it to continue.\n let continueNudges = 0;\n while (\n continueNudges < MAX_CONTINUE_NUDGES &&\n !security.state.criticalViolation &&\n hasOpenTasks(wizardTaskTools.store)\n ) {\n continueNudges += 1;\n logToFile(\n `[pi] completion guard: tasks still open, nudge ${continueNudges}/${MAX_CONTINUE_NUDGES}`,\n );\n await agentSession.prompt(CONTINUE_INSTRUCTION);\n }\n\n // Best-effort remark ask — a failed turn never fails a successful run.\n if (!security.state.criticalViolation) {\n try {\n await agentSession.prompt(REMARK_INSTRUCTION);\n } catch (err) {\n logToFile(`[pi] remark request failed: ${String(err)}`);\n }\n }\n } finally {\n unsubscribe();\n mcpCleanup?.();\n }\n\n // A latched post-scan violation terminates the run as a YARA violation,\n // matching the anthropic path's AgentErrorType.YARA_VIOLATION.\n if (security.state.criticalViolation) {\n spinner.stop('Security violation detected');\n logToFile(\n `[pi] terminated: YARA violation (blocked ${security.state.blockedCount} call(s))`,\n );\n captureAborted();\n return { error: AgentErrorType.YARA_VIOLATION };\n }\n\n // pi ends a run on any tool-call-less turn, so guard against a hollow\n // success reaching the outro (nothing done, or stopped mid-plan).\n const openTasks = hasOpenTasks(wizardTaskTools.store);\n const failure = completionFailure({ toolCalls, openTasks });\n if (failure === AgentErrorType.NO_PROGRESS) {\n spinner.stop('Agent made no changes');\n logToFile(\n `[pi] no progress: ${assistantTurns} assistant turn(s), 0 tool calls`,\n );\n analytics.wizardCapture('agent no progress', {\n assistant_turns: assistantTurns,\n });\n captureAborted();\n return { error: failure };\n }\n if (failure === AgentErrorType.INCOMPLETE_TASKS) {\n spinner.stop('Agent stopped before finishing');\n logToFile('[pi] incomplete: tasks left open');\n analytics.wizardCapture('agent incomplete tasks', { open_tasks: true });\n captureAborted();\n return { error: failure };\n }\n\n const remark = signals.remark();\n if (remark) {\n analytics.capture(WIZARD_REMARK_EVENT_NAME, { remark });\n }\n\n // A failed install_skill is non-fatal — the agent continues best-effort\n // without the skill — but every such run must be measurable.\n const skillFailure = signals.skillInstallFailure();\n if (skillFailure !== undefined) {\n analytics.wizardCapture('agent continued without skill', {\n detail: skillFailure,\n });\n }\n\n // The skill plans events into .posthog-events.json then asks to remove it\n // on completion; pi's `rm` is fence-blocked, so the agent can't — clean it\n // up host-side rather than leave a stale (often empty) artifact (#15).\n try {\n const planFile = path.join(session.installDir, '.posthog-events.json');\n if (fs.existsSync(planFile)) await fs.promises.rm(planFile);\n } catch (err) {\n logToFile(`[pi] .posthog-events.json cleanup skipped: ${String(err)}`);\n }\n\n const stats = agentSession.getSessionStats();\n analytics.wizardCapture('agent completed', {\n ...runDurations(),\n model: modelId,\n num_turns: assistantTurns,\n // API-reported tokens only; no total_cost_usd — the API returns no\n // cost, and $ai_generation already prices the run authoritatively.\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 });\n spinner.stop(config.successMessage ?? 'PostHog integration complete');\n return {};\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n logToFile(`[pi] run error: ${message}`);\n spinner.stop(config.errorMessage ?? `${config.integrationLabel} failed`);\n getUI().log.error(`pi backend error: ${message}`);\n captureAborted();\n\n const lower = message.toLowerCase();\n if (lower.includes('rate limit') || lower.includes('429')) {\n return { error: AgentErrorType.RATE_LIMIT, message };\n }\n return { error: AgentErrorType.API_ERROR, message };\n }\n },\n\n // Orchestrator mode: one fresh pi session per seed plan / drained task, with\n // the in-process queue tools registered as pi custom tools. Lazily imported —\n // task.ts pulls in typebox (ESM), which must stay out of the static module\n // graph so CommonJS unit tests can load the backend seam without parsing it.\n async runTask(inputs: TaskRunInputs): Promise<AgentResult> {\n const { runPiTask } = await import('./task');\n return runPiTask(inputs);\n },\n};\n"],"mappings":"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"}