@posthog/wizard 2.78.0 → 2.79.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 (130) hide show
  1. package/README.md +20 -11
  2. package/dist/{add-mcp-server-to-clients-JMv8LP-O.js → add-mcp-server-to-clients-amlJWW7W.js} +5 -5
  3. package/dist/{add-mcp-server-to-clients-JMv8LP-O.js.map → add-mcp-server-to-clients-amlJWW7W.js.map} +1 -1
  4. package/dist/{agent-bCZ3i3fh.js → agent-CNrEC-9v.js} +130 -31
  5. package/dist/agent-CNrEC-9v.js.map +1 -0
  6. package/dist/{agent-interface-BAOIHuXS.js → agent-interface-BXvmAm4c.js} +48 -22
  7. package/dist/agent-interface-BXvmAm4c.js.map +1 -0
  8. package/dist/{agent-prompt-loader-7Pd4CfZO.js → agent-prompt-loader-DiKUbhi_.js} +5 -5
  9. package/dist/{agent-prompt-loader-7Pd4CfZO.js.map → agent-prompt-loader-DiKUbhi_.js.map} +1 -1
  10. package/dist/analytics-DxwRi2Hf.js +2 -0
  11. package/dist/{analytics-CHCZ_qUR.js → analytics-VRYdebXW.js} +4 -4
  12. package/dist/{analytics-CHCZ_qUR.js.map → analytics-VRYdebXW.js.map} +1 -1
  13. package/dist/{api-Diy_2fp8.js → api-DGuJTzjr.js} +3 -3
  14. package/dist/{api-Diy_2fp8.js.map → api-DGuJTzjr.js.map} +1 -1
  15. package/dist/bin.js +56 -53
  16. package/dist/bin.js.map +1 -1
  17. package/dist/{bounded-fs-Bx9zTe6m.js → bounded-fs-Cpn129A4.js} +3 -3
  18. package/dist/{bounded-fs-Bx9zTe6m.js.map → bounded-fs-Cpn129A4.js.map} +1 -1
  19. package/dist/{ci-install-CDdQYsPe.js → ci-install-BPA6gzEq.js} +6 -6
  20. package/dist/ci-install-BPA6gzEq.js.map +1 -0
  21. package/dist/{constants-jFI2SaE1.js → constants-CuJfQEcC.js} +3 -3
  22. package/dist/{constants-jFI2SaE1.js.map → constants-CuJfQEcC.js.map} +1 -1
  23. package/dist/{debug-M2gV2iDq.js → debug-B9k4ideV.js} +1 -1
  24. package/dist/{debug-Ch4BDOtD.js → debug-Cj-8vfXY.js} +2 -2
  25. package/dist/{debug-Ch4BDOtD.js.map → debug-Cj-8vfXY.js.map} +1 -1
  26. package/dist/{endpoints-DCVbM06L.js → endpoints-DnIX0pZH.js} +3 -3
  27. package/dist/{endpoints-DCVbM06L.js.map → endpoints-DnIX0pZH.js.map} +1 -1
  28. package/dist/{env-api-key-Qx8NnYHm.js → env-api-key-BWdKpGEr.js} +2 -2
  29. package/dist/{env-api-key-Qx8NnYHm.js.map → env-api-key-BWdKpGEr.js.map} +1 -1
  30. package/dist/{environment-B07Bmxae.js → environment-DZYUSCpV.js} +4 -4
  31. package/dist/{environment-B07Bmxae.js.map → environment-DZYUSCpV.js.map} +1 -1
  32. package/dist/{errors-CQ9kttJD.js → errors-ddDE6MbA.js} +4 -46
  33. package/dist/errors-ddDE6MbA.js.map +1 -0
  34. package/dist/{file-BcLTjU16.js → file-dEhr-l3n.js} +3 -3
  35. package/dist/file-dEhr-l3n.js.map +1 -0
  36. package/dist/{file-watcher-DWc_bTs6.js → file-watcher-CPM-Nn9L.js} +2 -2
  37. package/dist/{file-watcher-DWc_bTs6.js.map → file-watcher-CPM-Nn9L.js.map} +1 -1
  38. package/dist/{gateway-CjoShdLa.js → gateway-CVU1G0H3.js} +38 -8
  39. package/dist/gateway-CVU1G0H3.js.map +1 -0
  40. package/dist/{headless-ui-BFFmYC4v.js → headless-ui-pY2xB5ug.js} +2 -2
  41. package/dist/{headless-ui-BFFmYC4v.js.map → headless-ui-pY2xB5ug.js.map} +1 -1
  42. package/dist/{interactive-D6zV7brJ.js → interactive-BOGlinZe.js} +2 -2
  43. package/dist/interactive-BOGlinZe.js.map +1 -0
  44. package/dist/{logging-ui-_TwoMNbG.js → logging-ui-CUxNYF2t.js} +4 -4
  45. package/dist/{logging-ui-_TwoMNbG.js.map → logging-ui-CUxNYF2t.js.map} +1 -1
  46. package/dist/{mcp-DAgaoGk5.js → mcp-JcsBBnWv.js} +4 -4
  47. package/dist/{mcp-DAgaoGk5.js.map → mcp-JcsBBnWv.js.map} +1 -1
  48. package/dist/{mcp-prompt-streaming-I2U_X7BC.js → mcp-prompt-streaming-B-p_Pvb2.js} +6 -6
  49. package/dist/mcp-prompt-streaming-B-p_Pvb2.js.map +1 -0
  50. package/dist/{mint-failure-D4nd2XmZ.js → mint-failure-CZFZpHpW.js} +687 -125
  51. package/dist/mint-failure-CZFZpHpW.js.map +1 -0
  52. package/dist/{models-DDdBJKwT.js → models-DnAY67Lg.js} +2 -2
  53. package/dist/{models-DDdBJKwT.js.map → models-DnAY67Lg.js.map} +1 -1
  54. package/dist/{non-interactive-Dzow7YKY.js → non-interactive-DR1CWXY1.js} +3 -3
  55. package/dist/{non-interactive-Dzow7YKY.js.map → non-interactive-DR1CWXY1.js.map} +1 -1
  56. package/dist/{oauth-session-DJ8h6GFa.js → oauth-session-CQTFOk-I.js} +2 -2
  57. package/dist/{oauth-session-DJ8h6GFa.js.map → oauth-session-CQTFOk-I.js.map} +1 -1
  58. package/dist/{orchestrator-tools-szcdscwn.js → orchestrator-tools-CBdt-SXO.js} +3 -3
  59. package/dist/{orchestrator-tools-szcdscwn.js.map → orchestrator-tools-CBdt-SXO.js.map} +1 -1
  60. package/dist/{pi-D3h5Ok0i.js → pi-BtqcLX3y.js} +143 -44
  61. package/dist/pi-BtqcLX3y.js.map +1 -0
  62. package/dist/{playground-5AB4WgNF.js → playground-Dz-ZVjBk.js} +9 -9
  63. package/dist/playground-Dz-ZVjBk.js.map +1 -0
  64. package/dist/{posthog-DNn574nW.js → posthog-6Uqw_Eux.js} +3 -3
  65. package/dist/posthog-6Uqw_Eux.js.map +1 -0
  66. package/dist/{provisioning-B8uSqRZk.js → provisioning-Zf7yPcEs.js} +4 -4
  67. package/dist/{provisioning-B8uSqRZk.js.map → provisioning-Zf7yPcEs.js.map} +1 -1
  68. package/dist/{queue-tools-9OOVx5TE.js → queue-tools-B7_aCyd-.js} +3 -3
  69. package/dist/{queue-tools-9OOVx5TE.js.map → queue-tools-B7_aCyd-.js.map} +1 -1
  70. package/dist/{registry-8YG-kuXk.js → registry-B6iLGorH.js} +49 -49
  71. package/dist/registry-B6iLGorH.js.map +1 -0
  72. package/dist/{run-agent-legacy-d7rWlzQR.js → run-agent-legacy-6WcYMaR9.js} +822 -1020
  73. package/dist/run-agent-legacy-6WcYMaR9.js.map +1 -0
  74. package/dist/{security-Cg_4a7vB.js → security-lkBwWbrK.js} +16 -6
  75. package/dist/security-lkBwWbrK.js.map +1 -0
  76. package/dist/{setup-utils-CmTCTPkX.js → setup-utils-dCViCC6s.js} +11 -11
  77. package/dist/setup-utils-dCViCC6s.js.map +1 -0
  78. package/dist/{start-tui-CB2PZs1x.js → start-tui-oCWMgsDP.js} +26 -26
  79. package/dist/start-tui-oCWMgsDP.js.map +1 -0
  80. package/dist/{steps-BZcXRJNY.js → steps-gx0Pq4I4.js} +7 -7
  81. package/dist/{steps-BZcXRJNY.js.map → steps-gx0Pq4I4.js.map} +1 -1
  82. package/dist/{store-UN7c-ASf.js → store-xYy_6Ruv.js} +10 -10
  83. package/dist/store-xYy_6Ruv.js.map +1 -0
  84. package/dist/{subagent-BofIDK-O.js → subagent-Bfy0sfhU.js} +3 -3
  85. package/dist/{subagent-BofIDK-O.js.map → subagent-Bfy0sfhU.js.map} +1 -1
  86. package/dist/{task-r9t1UhQP.js → task-BOfw7GPc.js} +16 -16
  87. package/dist/{task-r9t1UhQP.js.map → task-BOfw7GPc.js.map} +1 -1
  88. package/dist/{task-stream-CMUilu54.js → task-stream-kYlm9qd0.js} +9 -9
  89. package/dist/task-stream-kYlm9qd0.js.map +1 -0
  90. package/dist/{telemetry-D6ruCtnv.js → telemetry-Bpv-Mh5B.js} +3 -3
  91. package/dist/{telemetry-D6ruCtnv.js.map → telemetry-Bpv-Mh5B.js.map} +1 -1
  92. package/dist/{terminal-DrGyci_a.js → terminal-D07EQ2y0.js} +15 -15
  93. package/dist/terminal-D07EQ2y0.js.map +1 -0
  94. package/dist/{tools-Dyfh9aCD.js → tools-Cc41af5W.js} +8 -8
  95. package/dist/{tools-Dyfh9aCD.js.map → tools-Cc41af5W.js.map} +1 -1
  96. package/dist/{ui-BBVuTM1v.js → ui-CGXtC7Vn.js} +3 -3
  97. package/dist/{ui-BBVuTM1v.js.map → ui-CGXtC7Vn.js.map} +1 -1
  98. package/dist/{version-aXxpgRKx.js → version-Cadof0Z_.js} +2 -2
  99. package/dist/{version-aXxpgRKx.js.map → version-Cadof0Z_.js.map} +1 -1
  100. package/dist/{wizard-abort-BBkiUxfS.js → wizard-abort-HDbP3YcS.js} +6 -6
  101. package/dist/{wizard-abort-BBkiUxfS.js.map → wizard-abort-HDbP3YcS.js.map} +1 -1
  102. package/dist/wizard-abort-Y-Ip2K4z.js +3 -0
  103. package/dist/wizard-session-2w4Ru-Wz.js.map +1 -1
  104. package/dist/yara-hooks-B49RIk5d.js +2 -0
  105. package/dist/{yara-hooks-DCeMDd1o.js → yara-hooks-DCfROpZt.js} +5 -5
  106. package/dist/{yara-hooks-DCeMDd1o.js.map → yara-hooks-DCfROpZt.js.map} +1 -1
  107. package/package.json +2 -1
  108. package/dist/agent-bCZ3i3fh.js.map +0 -1
  109. package/dist/agent-interface-BAOIHuXS.js.map +0 -1
  110. package/dist/analytics-BcV-WCM4.js +0 -2
  111. package/dist/ci-install-CDdQYsPe.js.map +0 -1
  112. package/dist/errors-CQ9kttJD.js.map +0 -1
  113. package/dist/file-BcLTjU16.js.map +0 -1
  114. package/dist/gateway-CjoShdLa.js.map +0 -1
  115. package/dist/interactive-D6zV7brJ.js.map +0 -1
  116. package/dist/mcp-prompt-streaming-I2U_X7BC.js.map +0 -1
  117. package/dist/mint-failure-D4nd2XmZ.js.map +0 -1
  118. package/dist/pi-D3h5Ok0i.js.map +0 -1
  119. package/dist/playground-5AB4WgNF.js.map +0 -1
  120. package/dist/posthog-DNn574nW.js.map +0 -1
  121. package/dist/registry-8YG-kuXk.js.map +0 -1
  122. package/dist/run-agent-legacy-d7rWlzQR.js.map +0 -1
  123. package/dist/security-Cg_4a7vB.js.map +0 -1
  124. package/dist/setup-utils-CmTCTPkX.js.map +0 -1
  125. package/dist/start-tui-CB2PZs1x.js.map +0 -1
  126. package/dist/store-UN7c-ASf.js.map +0 -1
  127. package/dist/task-stream-CMUilu54.js.map +0 -1
  128. package/dist/terminal-DrGyci_a.js.map +0 -1
  129. package/dist/wizard-abort-BU4aLyvh.js +0 -3
  130. package/dist/yara-hooks-DSGuCU9A.js +0 -2
@@ -1,190 +1,31 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-B_-DWIq7.js";
2
- import { a as AgentSignals, d as ErrorCodes } from "./errors-CQ9kttJD.js";
3
- import { i as enableDebugLogs, o as initLogFile, s as logToFile } from "./debug-Ch4BDOtD.js";
4
- import { $ as getSkillsBaseUrl, G as WIZARD_ORCHESTRATOR_FLAG_KEY, W as WIZARD_OAUTH_SCOPES, Z as WIZARD_SELF_DRIVING_USE_PI_HARNESS_FLAG_KEY, b as HAIKU_MODEL, n as AGENTIC_DETECTION_RETRY_TIMEOUT_MS, t as AGENTIC_DETECTION_FIRST_ATTEMPT_TIMEOUT_MS, u as DETECTION_TIMEOUT_MS, w as Integration } from "./constants-jFI2SaE1.js";
2
+ import { s as ErrorCodes } from "./errors-ddDE6MbA.js";
3
+ import { i as enableDebugLogs, o as initLogFile, s as logToFile } from "./debug-Cj-8vfXY.js";
4
+ import { $ as getSkillsBaseUrl, G as WIZARD_ORCHESTRATOR_FLAG_KEY, W as WIZARD_OAUTH_SCOPES, Z as WIZARD_SELF_DRIVING_USE_PI_HARNESS_FLAG_KEY } from "./constants-CuJfQEcC.js";
5
5
  import { r as mayReportScanResults } from "./wizard-session-2w4Ru-Wz.js";
6
- import { n as analytics, r as groupsFromUser } from "./analytics-CHCZ_qUR.js";
7
- import { a as safeReadFile, i as readProjectFile, o as walkProjectFiles } from "./bounded-fs-Bx9zTe6m.js";
8
- import { c as refreshAccessToken, l as OAuthError, p as detectNodePackageManagers, s as missingOAuthScopes, t as getOrAskForProjectData } from "./setup-utils-CmTCTPkX.js";
9
- import { a as getBlockingServiceKeys, i as evaluateWizardReadiness, n as SERVICE_LABELS, r as SIGNUP_WIZARD_READINESS_CONFIG } from "./logging-ui-_TwoMNbG.js";
10
- import { t as getUI } from "./ui-BBVuTM1v.js";
11
- import { i as wizardAbort, t as registerCleanup } from "./wizard-abort-BBkiUxfS.js";
12
- import { n as isNonInteractiveEnvironment } from "./environment-B07Bmxae.js";
13
- import { i as oauthCredentials, t as configureOAuthSession } from "./oauth-session-DJ8h6GFa.js";
14
- import { at as MAX_ENV_KEY_SET, c as checkAllSettingsConflicts, ct as toPromptSafeRelativePath, d as restoreClaudeSettings, h as markGrantRevoked, i as runAgent, l as classifySettingsConflicts, n as buildRunTags, ot as isEnvFileName, r as initializeAgent, s as backupAndFixClaudeSettings, st as parseEnvKeyNames } from "./agent-interface-BAOIHuXS.js";
15
- import { n as runAgent$1, r as resolveBinding } from "./agent-bCZ3i3fh.js";
16
- import { _ as coerceAuditChecks, a as flushScanReport } from "./yara-hooks-DCeMDd1o.js";
17
- import { g as TASK_OUTCOMES_KEY } from "./queue-tools-9OOVx5TE.js";
18
- import { t as FRAMEWORK_REGISTRY } from "./registry-8YG-kuXk.js";
19
- import { t as startFileWatcher } from "./file-watcher-DWc_bTs6.js";
20
- import * as semver from "semver";
6
+ import { n as analytics, r as groupsFromUser } from "./analytics-VRYdebXW.js";
7
+ import { a as safeReadFile, i as readProjectFile, o as walkProjectFiles } from "./bounded-fs-Cpn129A4.js";
8
+ import { c as refreshAccessToken, l as OAuthError, s as missingOAuthScopes, t as getOrAskForProjectData } from "./setup-utils-dCViCC6s.js";
9
+ import { a as getBlockingServiceKeys, i as evaluateWizardReadiness, n as SERVICE_LABELS, r as SIGNUP_WIZARD_READINESS_CONFIG } from "./logging-ui-CUxNYF2t.js";
10
+ import { t as getUI } from "./ui-CGXtC7Vn.js";
11
+ import { i as wizardAbort, t as registerCleanup } from "./wizard-abort-HDbP3YcS.js";
12
+ import { n as isNonInteractiveEnvironment } from "./environment-DZYUSCpV.js";
13
+ import { i as oauthCredentials, t as configureOAuthSession } from "./oauth-session-CQTFOk-I.js";
14
+ import { at as MAX_ENV_KEY_SET, c as checkAllSettingsConflicts, ct as toPromptSafeRelativePath, d as restoreClaudeSettings, h as markGrantRevoked, l as classifySettingsConflicts, n as buildRunTags, ot as isEnvFileName, s as backupAndFixClaudeSettings, st as parseEnvKeyNames } from "./agent-interface-BXvmAm4c.js";
15
+ import { n as runAgent, r as resolveBinding } from "./agent-CNrEC-9v.js";
16
+ import { _ as coerceAuditChecks, a as flushScanReport } from "./yara-hooks-DCfROpZt.js";
17
+ import { g as TASK_OUTCOMES_KEY } from "./queue-tools-B7_aCyd-.js";
18
+ import { t as FRAMEWORK_REGISTRY } from "./registry-B6iLGorH.js";
19
+ import { t as startFileWatcher } from "./file-watcher-CPM-Nn9L.js";
21
20
  import fs, { existsSync, statSync } from "fs";
22
- import path, { isAbsolute, join, relative, resolve, sep } from "path";
23
- //#region src/ui/agent-progress.ts
24
- /**
25
- * The inverse of the agent's former `getUI()` calls: one event, one
26
- * `WizardUI` method, synchronous, in emission order. Because each case maps
27
- * back to exactly the call the agent used to make, the frame and flow goldens
28
- * hold without regeneration.
29
- */
30
- function createUiReducer(ui) {
31
- let spinner;
32
- return (event) => {
33
- switch (event.kind) {
34
- case "lifecycle":
35
- if (event.phase === "started") ui.startRun();
36
- else ui.outro(event.message);
37
- break;
38
- case "spinner":
39
- (spinner ??= ui.spinner())[event.action](event.message);
40
- break;
41
- case "log":
42
- ui.log[event.level](event.message);
43
- break;
44
- case "status":
45
- ui.pushStatus(event.message);
46
- break;
47
- case "tasks":
48
- ui.syncTodos(event.tasks);
49
- break;
50
- case "stage":
51
- ui.setStage(event.stage);
52
- break;
53
- case "url":
54
- if (event.which === "dashboard") ui.setDashboardUrl(event.url);
55
- else ui.setNotebookUrl(event.url);
56
- break;
57
- case "usage":
58
- ui.addTokenUsage(event.delta);
59
- break;
60
- case "finalCost":
61
- ui.setFinalTokenCostUsd(event.usd);
62
- break;
63
- case "authError":
64
- ui.showAuthError(event.detail);
65
- break;
66
- case "handoff":
67
- ui.setHandoffText(event.text);
68
- break;
69
- case "completion":
70
- ui.setOutroData(event.outro);
71
- break;
72
- default: throw new Error(`Unhandled agent progress: ${JSON.stringify(event)}`);
73
- }
74
- };
75
- }
76
- /** The agent's questions, answered wherever `getUI()` answers them today. */
77
- function uiInteraction(ui) {
78
- return {
79
- ask: (question, { signal }) => dismissOnAbort(ui.requestQuestion(question), signal, () => ui.cancelPendingQuestion()),
80
- taskNotice: (notice, { signal }) => dismissOnAbort(ui.showTaskNotice(notice), signal, () => ui.cancelTaskNotice())
81
- };
82
- }
83
- /**
84
- * Dismiss one open request on abort; a settled one leaves the UI alone. A
85
- * throw inside an abort listener reaches no caller: Node rethrows it as an
86
- * uncaught exception, so a broken overlay is logged here instead.
87
- */
88
- function dismissOnAbort(open, signal, dismiss) {
89
- const onAbort = () => {
90
- try {
91
- dismiss();
92
- } catch (error) {
93
- logToFile("[agent-progress] dismissing an aborted request failed", error);
94
- }
95
- };
96
- if (signal.aborted) onAbort();
97
- else signal.addEventListener("abort", onAbort, { once: true });
98
- return open.finally(() => signal.removeEventListener("abort", onAbort));
99
- }
100
- //#endregion
101
- //#region src/lib/programs/program-step.ts
102
- /**
103
- * Project program steps into the narrower Screen shape the router consumes.
104
- *
105
- * Two things happen here:
106
- * 1. Headless steps (no `screenId`) are filtered out. The router walks
107
- * visible screens; gate-only steps like `detect` are store concerns.
108
- * 2. The step is narrowed to just { id, show, isComplete } — the
109
- * router has no business touching gate, onInit, or label.
110
- *
111
- * This intentional separation keeps the router focused on one question:
112
- * "Which screen should be rendered right now?"
113
- */
114
- /**
115
- * The gated steps the agent runner awaits after `auth` and before `run`, in
116
- * step order. Empty when a program has no auth step or runs before it.
117
- */
118
- function postAuthGateSteps(steps) {
119
- const authIndex = steps.findIndex((s) => s.screenId === "auth");
120
- const runIndex = steps.findIndex((s) => s.screenId === "run");
121
- if (authIndex === -1 || runIndex <= authIndex) return [];
122
- return steps.slice(authIndex + 1, runIndex).filter((s) => s.gate);
123
- }
124
- function createProgramSequence(steps) {
125
- const entries = steps.filter((step) => step.screenId != null).map((step) => ({
126
- id: step.screenId,
127
- show: step.show,
128
- isComplete: step.isComplete ?? step.gate
129
- }));
130
- entries.push({
131
- id: "exit",
132
- show: void 0,
133
- isComplete: void 0
134
- });
135
- return entries;
136
- }
137
- //#endregion
138
- //#region src/lib/programs/authenticate.ts
139
- async function authenticate(session, programId) {
140
- if (session.credentials) return;
141
- logToFile("[agent-runner] starting OAuth");
142
- const { projectApiKey, host, accessToken, refreshToken, expiresAt, oauthClientId, projectId, roleAtOrganization, user, project, missingScopes } = await getOrAskForProjectData({
143
- signup: session.signup,
144
- ci: session.ci,
145
- apiKey: session.apiKey,
146
- projectId: session.projectId,
147
- email: session.email,
148
- region: session.region,
149
- baseUrl: session.baseUrl,
150
- localMcp: session.localMcp,
151
- programId
152
- });
153
- session.credentials = {
154
- accessToken,
155
- refreshToken,
156
- expiresAt,
157
- oauthClientId,
158
- projectApiKey,
159
- host,
160
- projectId,
161
- missingScopes
162
- };
163
- session.apiProject = project;
164
- session.roleAtOrganization = roleAtOrganization;
165
- session.apiUser = user;
166
- getUI().setCredentials(session.credentials);
167
- getUI().setRoleAtOrganization(roleAtOrganization);
168
- getUI().setApiUser(user);
169
- if (user) analytics.identifyUser(user);
170
- analytics.setGroups(groupsFromUser(user, host.apiHost));
171
- }
21
+ import path, { join } from "path";
22
+ import { randomUUID } from "crypto";
23
+ //#region src/programs/credentials.ts
172
24
  /**
173
25
  * Grants the token endpoint refuses permanently. A dead grant means the login
174
26
  * is gone, not that the network blipped, so only these mark the session.
175
27
  */
176
28
  const DEAD_GRANT_CODES = new Set(["invalid_grant", "invalid_client"]);
177
- async function refreshAccessTokenIfNeeded(session) {
178
- if (!session.credentials) return;
179
- configureOAuthSession(session.credentials, {
180
- rotate: (credentials) => rotateCredentials(credentials, session.baseUrl),
181
- onRefreshed: (refreshed) => {
182
- session.credentials = refreshed;
183
- getUI().setAccessToken(refreshed);
184
- }
185
- });
186
- await oauthCredentials();
187
- }
188
29
  async function rotateCredentials(credentials, baseUrl) {
189
30
  if (!credentials.refreshToken) return credentials;
190
31
  try {
@@ -206,492 +47,7 @@ async function rotateCredentials(credentials, baseUrl) {
206
47
  }
207
48
  }
208
49
  //#endregion
209
- //#region src/lib/detection/framework.ts
210
- /**
211
- * Framework detection — identify which PostHog-supported framework
212
- * is present in the project directory.
213
- *
214
- * Pure function: takes an install dir, returns the detected integration
215
- * (or undefined). No store mutations, no UI calls.
216
- */
217
- /**
218
- * Loop through all registered frameworks and return the first one
219
- * whose `detect()` predicate matches the given directory.
220
- * Returns undefined if no framework is detected or detection times out.
221
- */
222
- async function detectFramework(installDir) {
223
- for (const integration of Object.values(Integration)) {
224
- const config = FRAMEWORK_REGISTRY[integration];
225
- try {
226
- if (await Promise.race([config.detection.detect({ installDir }), new Promise((resolve) => setTimeout(() => resolve(false), 1e4))])) return integration;
227
- } catch {}
228
- }
229
- }
230
- //#endregion
231
- //#region src/lib/detection/features.ts
232
- /**
233
- * Feature discovery — scan project dependencies for known SDK patterns
234
- * that indicate additional PostHog programs are relevant.
235
- *
236
- * Pure function: takes an install dir, returns a set of discovered features.
237
- * No store mutations, no UI calls.
238
- */
239
- const STRIPE_PACKAGES = new Set(["stripe", "@stripe/stripe-js"]);
240
- const LLM_PACKAGES = new Set([
241
- "openai",
242
- "@anthropic-ai/sdk",
243
- "ai",
244
- "@ai-sdk/openai",
245
- "langchain",
246
- "@langchain/openai",
247
- "@langchain/langgraph",
248
- "@google/generative-ai",
249
- "@google/genai",
250
- "@instructor-ai/instructor",
251
- "@mastra/core",
252
- "portkey-ai"
253
- ]);
254
- const PYTHON_LLM_PACKAGES = new Set([
255
- "openai",
256
- "anthropic",
257
- "langchain",
258
- "langchain-openai",
259
- "langchain-anthropic",
260
- "langchain-google-genai",
261
- "langgraph",
262
- "litellm",
263
- "llama-index",
264
- "pydantic-ai",
265
- "crewai",
266
- "instructor",
267
- "dspy-ai",
268
- "mistralai",
269
- "cohere",
270
- "google-generativeai",
271
- "google-genai",
272
- "portkey-ai"
273
- ]);
274
- function discoverFeatures(installDir) {
275
- const features = [];
276
- discoverNodeFeatures(installDir, features);
277
- discoverPythonFeatures(installDir, features);
278
- return features;
279
- }
280
- function discoverNodeFeatures(installDir, features) {
281
- const packageJsonText = safeRead(installDir, "package.json");
282
- if (!packageJsonText) return;
283
- let packageJson;
284
- try {
285
- packageJson = JSON.parse(packageJsonText);
286
- } catch {
287
- return;
288
- }
289
- const depNames = Object.keys({
290
- ...packageJson.dependencies,
291
- ...packageJson.devDependencies
292
- });
293
- if (depNames.some((depName) => STRIPE_PACKAGES.has(depName))) features.push("stripe");
294
- if (depNames.some((depName) => LLM_PACKAGES.has(depName))) features.push("llm");
295
- }
296
- function discoverPythonFeatures(installDir, features) {
297
- if (features.includes("llm")) return;
298
- const depNames = [];
299
- const requirementsTxt = safeRead(installDir, "requirements.txt");
300
- if (requirementsTxt) depNames.push(...parseRequirementsTxt(requirementsTxt));
301
- const pyprojectToml = safeRead(installDir, "pyproject.toml");
302
- if (pyprojectToml) depNames.push(...parsePyprojectToml(pyprojectToml));
303
- const pipfile = safeRead(installDir, "Pipfile");
304
- if (pipfile) depNames.push(...parsePipfile(pipfile));
305
- if (depNames.some((depName) => PYTHON_LLM_PACKAGES.has(depName))) features.push("llm");
306
- }
307
- function safeRead(installDir, file) {
308
- return readProjectFile(join(installDir, file));
309
- }
310
- function normalizePyName(name) {
311
- return name.toLowerCase().replace(/_/g, "-");
312
- }
313
- function parseRequirementsTxt(content) {
314
- return content.split("\n").map((line) => line.trim()).filter((line) => line && !line.startsWith("#") && !line.startsWith("-")).map((line) => line.replace(/\[[^\]]*\]/, "")).map((line) => line.split(/[<>=!~;\s]/)[0]).filter(Boolean).map(normalizePyName);
315
- }
316
- function parsePyprojectToml(content) {
317
- const depNames = [];
318
- for (const arrayMatch of content.matchAll(/dependencies\s*=\s*\[([^\]]*)\]/g)) {
319
- const arrayBody = arrayMatch[1];
320
- for (const quotedMatch of arrayBody.matchAll(/["']([^"']+)["']/g)) {
321
- const name = quotedMatch[1].replace(/\[[^\]]*\]/, "").split(/[<>=!~;\s]/)[0];
322
- if (name) depNames.push(normalizePyName(name));
323
- }
324
- }
325
- for (const sectionMatch of content.matchAll(/\[tool\.poetry\.(?:dev-dependencies|dependencies|group\.[^.\]]+\.dependencies)\]([\s\S]*?)(?=\n\[|$)/g)) {
326
- const sectionBody = sectionMatch[1];
327
- depNames.push(...extractTomlSectionKeys(sectionBody, { skip: ["python"] }));
328
- }
329
- return depNames;
330
- }
331
- function parsePipfile(content) {
332
- const depNames = [];
333
- for (const sectionMatch of content.matchAll(/\[(packages|dev-packages)\]([\s\S]*?)(?=\n\[|$)/g)) {
334
- const sectionBody = sectionMatch[2];
335
- depNames.push(...extractTomlSectionKeys(sectionBody));
336
- }
337
- return depNames;
338
- }
339
- function extractTomlSectionKeys(sectionBody, opts = {}) {
340
- const skipNames = new Set(opts.skip ?? []);
341
- const depNames = [];
342
- for (const line of sectionBody.split("\n")) {
343
- const trimmed = line.trim();
344
- if (!trimmed || trimmed.startsWith("#")) continue;
345
- const equalsIndex = trimmed.indexOf("=");
346
- if (equalsIndex <= 0) continue;
347
- const name = trimmed.slice(0, equalsIndex).trim();
348
- if (!/^[a-zA-Z0-9._-]+$/.test(name)) continue;
349
- if (skipNames.has(name.toLowerCase())) continue;
350
- depNames.push(normalizePyName(name));
351
- }
352
- return depNames;
353
- }
354
- //#endregion
355
- //#region src/lib/detection/context.ts
356
- /**
357
- * Framework context gathering — run gatherContext and version checks
358
- * for a detected framework.
359
- *
360
- * Pure functions: take a framework config and options, return results.
361
- * No store mutations, no UI calls.
362
- */
363
- /**
364
- * Run a framework's `gatherContext()` to collect variant-specific
365
- * metadata (e.g., router type for Next.js, Expo vs bare for React Native).
366
- *
367
- * Returns the gathered context, or an empty object on failure/timeout.
368
- */
369
- async function gatherFrameworkContext(config, options) {
370
- if (!config.metadata.gatherContext) return {};
371
- try {
372
- return await Promise.race([config.metadata.gatherContext(options), new Promise((resolve) => setTimeout(() => resolve({}), DETECTION_TIMEOUT_MS))]);
373
- } catch {
374
- return {};
375
- }
376
- }
377
- /**
378
- * Check whether the installed framework version meets the minimum requirement.
379
- *
380
- * Returns `{ supported: true }` if the version is fine (or no check is needed).
381
- * Returns the version details if unsupported.
382
- */
383
- async function checkFrameworkVersion(config, options) {
384
- if (!config.detection.minimumVersion || !config.detection.getInstalledVersion) return { supported: true };
385
- const version = await config.detection.getInstalledVersion(options);
386
- if (!version) return { supported: true };
387
- const coerced = semver.coerce(version);
388
- if (coerced && semver.lt(coerced, config.detection.minimumVersion)) return { supported: {
389
- current: version,
390
- minimum: config.detection.minimumVersion,
391
- docsUrl: config.metadata.unsupportedVersionDocsUrl ?? config.metadata.docsUrl
392
- } };
393
- return { supported: true };
394
- }
395
- //#endregion
396
- //#region src/lib/detection/agentic.ts
397
- /**
398
- * Agentic project detection.
399
- *
400
- * A reusable detection tool that drives a Haiku agent over the repo: it finds
401
- * project roots, resolves monorepo/workspace markers, reads package manifests,
402
- * classifies each project against a caller-supplied set of targets, and reports
403
- * which projects already have a PostHog SDK installed.
404
- *
405
- * Product-knowledge-free by design — the caller passes the targets to classify
406
- * into (e.g. source-map skill variants) and maps the result back. Sits next to
407
- * the other detection tools (framework, features, package-manager) so it's
408
- * discoverable. Runs AFTER auth: it uses the same agent loop every program
409
- * uses, which needs credentials.
410
- */
411
- var AgenticDetectionTimeoutError = class extends Error {
412
- constructor(attempt, timeoutMs) {
413
- super(`Project scan attempt ${attempt} timed out after ${timeoutMs / 1e3}s`);
414
- this.name = "AgenticDetectionTimeoutError";
415
- }
416
- };
417
- /**
418
- * Every project-manifest / workspace-marker filename the wizard's frameworks
419
- * are identified by. One brace-expansion Glob over all of these locates every
420
- * project root in the repo in a single call, regardless of language.
421
- *
422
- * Counterpart: POSTHOG_MANIFESTS in @lib/programs/self-driving/detect (SDK
423
- * grep); keep the two in sync.
424
- */
425
- const PROJECT_MANIFESTS = [
426
- "package.json",
427
- "pnpm-workspace.yaml",
428
- "turbo.json",
429
- "nx.json",
430
- "lerna.json",
431
- "requirements.txt",
432
- "pyproject.toml",
433
- "setup.py",
434
- "Pipfile",
435
- "manage.py",
436
- "Gemfile",
437
- "composer.json",
438
- "pom.xml",
439
- "Cargo.toml",
440
- "mix.exs",
441
- "go.mod",
442
- "*.csproj",
443
- "Package.swift",
444
- "Podfile",
445
- "project.yml",
446
- "project.pbxproj",
447
- "pubspec.yaml",
448
- "build.gradle",
449
- "build.gradle.kts",
450
- "settings.gradle",
451
- "settings.gradle.kts",
452
- "gradle/libs.versions.toml"
453
- ];
454
- /** The single brace-expansion glob covering every supported manifest. */
455
- function manifestGlob() {
456
- return `**/{${PROJECT_MANIFESTS.join(",")}}`;
457
- }
458
- function buildPrompt(cwd, targets, purpose, recommend) {
459
- const targetList = targets.map((t) => `- ${t.id} → ${t.name}`).join("\n");
460
- const projectShape = `{"path":string,"framework":string,"matchingTargets":string[],"targetId":string|null,"hasPostHog":boolean,"evidence":string${recommend ? ",\"recommended\":boolean" : ""}}`;
461
- return [
462
- `You are scanning a code repository to ${purpose}. Be fast and mechanical — do not over-explore. Keep any reasoning to one short sentence, then run the tool calls and output the JSON.`,
463
- "",
464
- `Working directory: ${cwd}`,
465
- "",
466
- "A \"project\" is a directory containing one or more of the manifest files below. Find projects by their manifests, NOT by walking directories — never report a directory that has no manifest.",
467
- "",
468
- "Do exactly this:",
469
- `1. Run Glob ONCE with this pattern to find every project manifest in the repo in a single call: "${manifestGlob()}". Discard any result whose path contains node_modules/, dist/, build/, .next/, out/, coverage/, vendor/, .venv/, site-packages/, target/, Pods/, Carthage/, or DerivedData/. Group the remaining results by directory — each directory is one project. Three exceptions to "directory = project": a project.pbxproj lives inside a "<Name>.xcodeproj/" wrapper, so the project root is the PARENT of that .xcodeproj directory; a project.yml at a directory root is an XcodeGen-generated Xcode app rooted at that directory; a gradle/libs.versions.toml is a version catalog belonging to the gradle project rooted at the PARENT of that gradle/ directory (read it alongside the build.gradle when deciding hasPostHog), never its own project.`,
470
- "2. Decide repoType: \"monorepo\" if the root package.json has a \"workspaces\" field OR a pnpm-workspace.yaml / turbo.json / nx.json / lerna.json was found at the root, else \"single\".",
471
- `3. For EACH project directory, ONE AT A TIME: Read its manifest(s) ONCE, decide the fields below, then IMMEDIATELY — before reading any other project — write that project's verdict as one JSON line of shape ${projectShape}. Never write a verdict from memory of an earlier Read; the manifest you just read is the only source. Decide from its dependency lists:`,
472
- " - the human-readable framework name (e.g. \"Next.js\", \"Django\", \"Rails\"),",
473
- " - matchingTargets: EVERY target id from the list below whose technology appears in THIS manifest, in the priority order of the list. An id belongs here only if you can point at the dependency or setting in the manifest you just read — never carry one over from another project,",
474
- " - targetId: the FIRST entry of matchingTargets (the list is ordered by priority, most specific first). null when matchingTargets is empty,",
475
- ` - hasPostHog: true if any dependency is a PostHog SDK. This includes: a name containing "posthog" in any ecosystem (e.g. posthog-js, posthog-node, @posthog/*, posthog for pip/gem/hex, a com.posthog:* gradle/maven coordinate, a PostHog NuGet PackageReference); an SPM package named "PostHog" or a repositoryURL of github.com/PostHog/posthog-ios (in Package.swift or a .pbxproj); or a "pod 'PostHog'" line in a Podfile. Else false.`,
476
- " - evidence: the manifest fact that decided targetId, with the file it came from (e.g. \"rollup in devDependencies of backend/package.json\"). When targetId is null, name the closest fact you saw. One short clause, quoted from the manifest you just read.",
477
- " Do NOT read any file other than these manifests.",
478
- ...recommend ? ["4. Pick exactly ONE project as recommended — the main user-facing client application. Prefer a frontend web app or a mobile app over backend servers/APIs, libraries, CLIs, tooling, and example/demo/docs apps. If several client apps exist, pick the primary production app; if no client app exists, pick the primary application project."] : [],
479
- "",
480
- "Target ids (id → name), in priority order — most specific first; if several match a project, keep the one listed earliest:",
481
- targetList,
482
- "",
483
- "Output requirements:",
484
- "- After the last verdict line, respond with ONLY a single JSON object assembling the verdict lines you wrote, copied VERBATIM — same path, framework, targetId and evidence per project. No prose, no markdown code fences.",
485
- `- Shape: {"repoType":"monorepo"|"single","projects":[${projectShape}]}`,
486
- "- \"path\" is the project directory relative to the working directory; use \".\" for the repo root.",
487
- "- \"targetId\" MUST be exactly one of the target ids above when it matches; if several match, use the one listed earliest; otherwise null.",
488
- "- Include projects whose stack matches no target too (targetId: null).",
489
- ...recommend ? ["- Exactly one project has \"recommended\": true; every other project has \"recommended\": false."] : [],
490
- `- If there are no manifests at all, respond with exactly: ${AgentSignals.ABORT} detection failed`
491
- ].join("\n");
492
- }
493
- /**
494
- * Build the detection report from the agent's output, or null when it holds
495
- * no verdicts. Exported for testing.
496
- *
497
- * The verdict lines are far more reliable than the model's final assembly
498
- * (prose, pretty-printing, per-object fences), so every parseable line
499
- * contributes: objects with a `path` merge by path (last wins), and a
500
- * one-line assembly's `projects` merge the same way. `repoType` is
501
- * approximated from the count — it only feeds telemetry and display.
502
- */
503
- function deriveReportJson(text) {
504
- const byPath = /* @__PURE__ */ new Map();
505
- for (const line of text.split("\n")) {
506
- const start = line.indexOf("{");
507
- const end = line.lastIndexOf("}");
508
- if (start === -1 || end <= start) continue;
509
- let parsed;
510
- try {
511
- parsed = JSON.parse(line.slice(start, end + 1));
512
- } catch {
513
- continue;
514
- }
515
- const obj = parsed ?? {};
516
- const candidates = Array.isArray(obj.projects) ? obj.projects : [obj];
517
- for (const candidate of candidates) {
518
- const p = candidate ?? {};
519
- if (typeof p.path === "string") byPath.set(p.path, p);
520
- }
521
- }
522
- if (byPath.size === 0) return null;
523
- const projects = [...byPath.values()];
524
- return {
525
- repoType: projects.length > 1 ? "monorepo" : "single",
526
- projects
527
- };
528
- }
529
- /**
530
- * Clamp an agent-reported project path to a safe repo-relative dir. The path
531
- * is LLM output: absolute paths or `..` segments could steer later phases
532
- * (e.g. integrate-run's targetDir) outside the repo, so they clamp to '.'.
533
- */
534
- function coercePath(raw) {
535
- if (typeof raw !== "string" || raw.length === 0) return ".";
536
- if (isAbsolute(raw) || raw.split(/[\\/]/).includes("..")) return ".";
537
- return raw;
538
- }
539
- /** Resolve an agent-reported path to an absolute dir clamped to the root — the caller-side counterpart of coercePath. */
540
- function resolveProjectDir(installDir, rel) {
541
- if (typeof rel !== "string" || rel === ".") return installDir;
542
- const root = resolve(installDir);
543
- const dir = resolve(root, rel);
544
- return dir === root || dir.startsWith(root + sep) ? dir : root;
545
- }
546
- /**
547
- * Validate the agent's raw JSON into a report, clamping each targetId to the
548
- * supplied set and each path to a repo-relative dir. Exported for testing.
549
- * `recommended` is stripped unless requested; at most one (the first) survives.
550
- */
551
- function coerceAgenticReport(parsed, validTargetIds, options) {
552
- const recommend = options?.recommend === true;
553
- const rerankIds = options?.rerankIds ?? [];
554
- const obj = parsed ?? {};
555
- const repoType = obj.repoType === "monorepo" ? "monorepo" : "single";
556
- const rawProjects = Array.isArray(obj.projects) ? obj.projects : [];
557
- let recommendedSeen = false;
558
- return {
559
- repoType,
560
- projects: rawProjects.map((raw) => {
561
- const p = raw ?? {};
562
- const pick = typeof p.targetId === "string" && validTargetIds.includes(p.targetId) ? p.targetId : null;
563
- const enumerated = Array.isArray(p.matchingTargets) ? validTargetIds.find((id) => p.matchingTargets.includes(id)) ?? null : null;
564
- const targetId = pick === null ? enumerated : enumerated !== null && rerankIds.includes(enumerated) && rerankIds.includes(pick) ? enumerated : pick;
565
- const recommended = recommend && !recommendedSeen && p.recommended === true;
566
- recommendedSeen ||= recommended;
567
- return {
568
- path: coercePath(p.path),
569
- framework: typeof p.framework === "string" ? p.framework : "Unknown",
570
- targetId,
571
- hasPostHog: p.hasPostHog === true,
572
- ...recommend ? { recommended } : {}
573
- };
574
- })
575
- };
576
- }
577
- function formatToolUse(block) {
578
- const name = typeof block?.name === "string" ? block.name : "tool";
579
- const input = block?.input ?? {};
580
- const detail = input.file_path || input.pattern || input.path || "";
581
- return detail ? `${name} ${detail}` : name;
582
- }
583
- function sessionToWizardOptions(session) {
584
- return {
585
- installDir: session.installDir,
586
- ci: session.ci,
587
- debug: session.debug,
588
- benchmark: session.benchmark,
589
- yaraReport: session.yaraReport,
590
- signup: session.signup,
591
- apiKey: session.apiKey,
592
- projectId: session.projectId
593
- };
594
- }
595
- const NOOP_SPINNER = {
596
- start: () => void 0,
597
- stop: () => void 0,
598
- message: () => void 0
599
- };
600
- /**
601
- * Drive the wizard's agent loop on HAIKU_MODEL to scan the repo and return a
602
- * structured detection report. Reuses the same setup every program uses, so
603
- * MCP, tools, and credentials are wired identically.
604
- */
605
- async function detectProjectsWithAgent(session, options) {
606
- if (!session.credentials) throw new Error("Detection requires authenticated credentials.");
607
- const { targets, programId, purpose = "set up a PostHog integration", recommend = false, rerankIds, onEvent } = options;
608
- const { accessToken, host } = session.credentials;
609
- const cwd = session.installDir;
610
- const runOptions = sessionToWizardOptions(session);
611
- const wizardMetadata = {
612
- ...buildRunTags({
613
- programId,
614
- integration: "agentic-detect",
615
- runId: analytics.runId,
616
- build: analytics.build
617
- }),
618
- call_type: "detection"
619
- };
620
- const prompt = buildPrompt(cwd, targets, purpose, recommend);
621
- for (let attempt = 0; attempt < 2; attempt++) {
622
- const timeoutMs = attempt === 0 ? AGENTIC_DETECTION_FIRST_ATTEMPT_TIMEOUT_MS : AGENTIC_DETECTION_RETRY_TIMEOUT_MS;
623
- const agent = await initializeAgent({
624
- emit: createUiReducer(getUI()),
625
- workingDirectory: cwd,
626
- posthogMcpUrl: host.mcpUrl,
627
- posthogApiKey: accessToken,
628
- host,
629
- detectPackageManager: detectNodePackageManagers,
630
- skillsBaseUrl: getSkillsBaseUrl(),
631
- programId,
632
- integrationLabel: "agentic-detect",
633
- wizardMetadata,
634
- allowedTools: [
635
- "Read",
636
- "Grep",
637
- "Glob"
638
- ],
639
- modelOverride: HAIKU_MODEL
640
- }, runOptions);
641
- const MAX_TRANSCRIPT_CHARS = 256 * 1024;
642
- const collected = [];
643
- let collectedChars = 0;
644
- const collect = (text) => {
645
- collected.push(text);
646
- collectedChars += text.length;
647
- while (collectedChars > MAX_TRANSCRIPT_CHARS && collected.length > 1) collectedChars -= collected.shift().length;
648
- };
649
- let resultText = "";
650
- const result = await runAgent(agent, prompt, runOptions, NOOP_SPINNER, {
651
- spinnerMessage: "Scanning the repo...",
652
- successMessage: "Detection complete",
653
- errorMessage: "Detection failed",
654
- requestRemark: false,
655
- timeoutMs
656
- }, {
657
- onMessage: (message) => {
658
- if (message?.type === "assistant") {
659
- for (const block of message.message?.content ?? []) if (block?.type === "text" && typeof block.text === "string") {
660
- collect(block.text);
661
- const line = block.text.trim();
662
- if (line && onEvent) onEvent(line.length > 100 ? `${line.slice(0, 100)}…` : line);
663
- } else if (block?.type === "tool_use") onEvent?.(formatToolUse(block));
664
- } else if (message?.type === "result" && typeof message.result === "string") resultText = message.result;
665
- },
666
- finalize: (_resultMessage, _durationMs) => void 0
667
- });
668
- if (result.kind === "failure" && result.classification === "WIZARD_AGENTIC_DETECTION_TIMEOUT") {
669
- if (attempt === 0) {
670
- onEvent?.("Project scan timed out; retrying...");
671
- continue;
672
- }
673
- throw new AgenticDetectionTimeoutError(attempt + 1, timeoutMs);
674
- }
675
- if (result.kind !== "success") {
676
- if (result.kind === "decided_failure") throw result.failure.error ?? new Error(result.failure.message);
677
- throw result.error ?? new Error(result.message || `Agent error: ${result.classification}`);
678
- }
679
- const output = `${collected.join("\n")}\n${resultText}`;
680
- const derived = deriveReportJson(output);
681
- if (derived !== null) return coerceAgenticReport(derived, targets.map((t) => t.id), {
682
- recommend,
683
- rerankIds
684
- });
685
- if (output.includes(AgentSignals.ABORT)) return {
686
- repoType: "single",
687
- projects: []
688
- };
689
- if (attempt === 0) onEvent?.("Retrying project scan...");
690
- }
691
- throw new Error("Agent did not return a JSON object after retry");
692
- }
693
- //#endregion
694
- //#region src/lib/warehouse-sources/registry.ts
50
+ //#region src/programs/warehouse-sources/registry.ts
695
51
  const CORE_SOURCE_DETECTORS = [
696
52
  {
697
53
  kind: "Postgres",
@@ -3056,16 +2412,689 @@ const OTHER_SOURCE_DETECTORS = [
3056
2412
  envKeys: [/^DUB_API_KEY$/]
3057
2413
  }
3058
2414
  }
3059
- ];
3060
- const SOURCE_DETECTORS = [
3061
- ...CORE_SOURCE_DETECTORS,
3062
- ...LLM_SOURCE_DETECTORS,
3063
- ...OTHER_SOURCE_DETECTORS
3064
- ];
3065
- /** Kinds from the LLM / AI section above — the only ones that may set `wizard_ai_sdk_detected`. */
3066
- const AI_SOURCE_KINDS = new Set(LLM_SOURCE_DETECTORS.map((detector) => detector.kind));
2415
+ ];
2416
+ const SOURCE_DETECTORS = [
2417
+ ...CORE_SOURCE_DETECTORS,
2418
+ ...LLM_SOURCE_DETECTORS,
2419
+ ...OTHER_SOURCE_DETECTORS
2420
+ ];
2421
+ /** Kinds from the LLM / AI section above — the only ones that may set `wizard_ai_sdk_detected`. */
2422
+ const AI_SOURCE_KINDS = new Set(LLM_SOURCE_DETECTORS.map((detector) => detector.kind));
2423
+ //#endregion
2424
+ //#region src/programs/posthog-integration/ai-sdk-stamp.ts
2425
+ function hasAiSdkEvidence(evidence) {
2426
+ return evidence.warehouseSources.some((s) => AI_SOURCE_KINDS.has(s.kind)) || evidence.discoveredFeatures.includes("llm");
2427
+ }
2428
+ /**
2429
+ * Boolean only, on the org, never the list of kinds or any non-AI tool: a
2430
+ * decline must not leak even the shape of what local detection saw.
2431
+ */
2432
+ function stampAiSdkDetected(evidence) {
2433
+ if (!evidence.mayReportScanResults) return;
2434
+ const organizationId = evidence.apiUser?.organization?.id;
2435
+ if (!organizationId) return;
2436
+ if (!hasAiSdkEvidence(evidence)) return;
2437
+ analytics.groupIdentify("organization", organizationId, { wizard_ai_sdk_detected: true });
2438
+ }
2439
+ //#endregion
2440
+ //#region src/programs/program-store.ts
2441
+ const MAX_DIAGNOSTICS = 10;
2442
+ var ProgramStore = class {
2443
+ runs = [];
2444
+ diagnostics = [];
2445
+ data;
2446
+ onData;
2447
+ constructor(options = {}) {
2448
+ this.onData = options.onData;
2449
+ this.data = {
2450
+ credentials: null,
2451
+ apiProject: null,
2452
+ apiUser: null,
2453
+ detection: { frameworkContext: {} },
2454
+ binding: null,
2455
+ aiSdkStampReported: options.aiSdkStampReported ?? false
2456
+ };
2457
+ }
2458
+ readData() {
2459
+ return structuredClone(this.data);
2460
+ }
2461
+ setAuthenticated(auth) {
2462
+ Object.assign(this.data, structuredClone(auth));
2463
+ this.emitData();
2464
+ }
2465
+ setFrameworkContext(key, value) {
2466
+ this.data.detection.frameworkContext[key] = structuredClone(value);
2467
+ this.emitData();
2468
+ }
2469
+ setBinding(binding) {
2470
+ this.data.binding = structuredClone(binding);
2471
+ this.emitData();
2472
+ }
2473
+ setAiSdkStampReported() {
2474
+ if (this.data.aiSdkStampReported) return;
2475
+ this.data.aiSdkStampReported = true;
2476
+ this.emitData();
2477
+ }
2478
+ beginRun(runId, observer) {
2479
+ const run = { runId };
2480
+ this.runs.push(run);
2481
+ return {
2482
+ onProgress: (event) => {
2483
+ const source = {
2484
+ runId: run.runId,
2485
+ eventKind: event.kind
2486
+ };
2487
+ if (run.result) {
2488
+ this.recordDiagnostic(source, "progress after finish");
2489
+ return;
2490
+ }
2491
+ if (!observer) return;
2492
+ this.deliver(source, () => observer({
2493
+ kind: "run",
2494
+ runId,
2495
+ event: structuredClone(event)
2496
+ }));
2497
+ },
2498
+ finish: (result) => {
2499
+ run.result = result;
2500
+ }
2501
+ };
2502
+ }
2503
+ settledRuns() {
2504
+ return this.runs.flatMap(({ runId, result }) => result ? [{
2505
+ runId,
2506
+ result
2507
+ }] : []);
2508
+ }
2509
+ readDiagnostics() {
2510
+ return this.diagnostics.map((diagnostic) => ({ ...diagnostic }));
2511
+ }
2512
+ emitData() {
2513
+ const onData = this.onData;
2514
+ if (!onData) return;
2515
+ this.deliver({ eventKind: "data" }, () => onData({
2516
+ kind: "program",
2517
+ data: this.readData()
2518
+ }));
2519
+ }
2520
+ /** Never waits for an observer; a throw or a rejection becomes a diagnostic. */
2521
+ deliver(source, send) {
2522
+ try {
2523
+ const delivery = send();
2524
+ if (delivery && typeof delivery.then === "function") Promise.resolve(delivery).catch((error) => {
2525
+ this.recordDiagnostic(source, error);
2526
+ });
2527
+ } catch (error) {
2528
+ this.recordDiagnostic(source, error);
2529
+ }
2530
+ }
2531
+ recordDiagnostic(source, error) {
2532
+ this.diagnostics.push({
2533
+ ...source,
2534
+ message: error instanceof Error ? error.message : String(error)
2535
+ });
2536
+ if (this.diagnostics.length > MAX_DIAGNOSTICS) this.diagnostics.shift();
2537
+ }
2538
+ };
2539
+ //#endregion
2540
+ //#region src/programs/run-program.ts
2541
+ /** A caller-owned program invocation. No TUI store or session is required. */
2542
+ /** Handed to the caller's capabilities when it supplied no signal. */
2543
+ const NEVER_ABORTED = new AbortController().signal;
2544
+ const DEFAULT_FLAGS = {
2545
+ ci: false,
2546
+ signup: false,
2547
+ debug: false,
2548
+ e2eAsk: false,
2549
+ localMcp: false,
2550
+ captureAio: false,
2551
+ benchmark: false,
2552
+ yaraReport: false
2553
+ };
2554
+ /** Fields that carry functions or class instances; everything else is data. */
2555
+ const KEPT_BY_REFERENCE = [
2556
+ "credentials",
2557
+ "run",
2558
+ "program",
2559
+ "hooks",
2560
+ "seedTasks"
2561
+ ];
2562
+ /** Copy the caller's input, so a later caller write cannot reach the run or its hooks. */
2563
+ function snapshotProgramInput(input) {
2564
+ const data = { ...input };
2565
+ for (const key of KEPT_BY_REFERENCE) delete data[key];
2566
+ return {
2567
+ ...input,
2568
+ ...structuredClone(data)
2569
+ };
2570
+ }
2571
+ /** Run an existing program from explicit inputs, with invocation-owned state. */
2572
+ async function runProgram(programId, callerInput, options = {}) {
2573
+ const input = snapshotProgramInput(callerInput);
2574
+ const store = new ProgramStore({
2575
+ aiSdkStampReported: input.aiSdkStampReported,
2576
+ onData: options.onProgress
2577
+ });
2578
+ const { installDir, run } = input;
2579
+ const program = input.program ?? {};
2580
+ const artifacts = {};
2581
+ const runId = input.runId ?? randomUUID();
2582
+ const signal = options.signal ?? NEVER_ABORTED;
2583
+ const settle = (outcome, failure) => ({
2584
+ programId,
2585
+ outcome,
2586
+ data: store.readData(),
2587
+ settledRuns: store.settledRuns(),
2588
+ diagnostics: store.readDiagnostics(),
2589
+ artifacts,
2590
+ ...failure && { failure }
2591
+ });
2592
+ const fail = (message) => settle("failed", {
2593
+ code: ErrorCodes.InternalUnhandled,
2594
+ message
2595
+ });
2596
+ const abort = (message) => settle("aborted", {
2597
+ code: ErrorCodes.AgentAbort,
2598
+ message
2599
+ });
2600
+ const cancelled = () => abort("Run cancelled by the caller.");
2601
+ if (signal.aborted) return cancelled();
2602
+ analytics.wizardCapture("agent started", {
2603
+ integration: run.integrationLabel,
2604
+ program_id: programId,
2605
+ skill_id: run.skillId ?? null
2606
+ });
2607
+ /** Await a caller capability; a caller abort during the wait wins over its answer. */
2608
+ const park = async (work) => {
2609
+ const value = await work;
2610
+ signal.throwIfAborted();
2611
+ return value;
2612
+ };
2613
+ let credentials = input.credentials;
2614
+ const flags = {
2615
+ ...DEFAULT_FLAGS,
2616
+ ...input.flags
2617
+ };
2618
+ let flagSnapshot = {
2619
+ flags: { ...input.wizardFlags },
2620
+ payloads: { ...input.wizardFlagPayloads }
2621
+ };
2622
+ try {
2623
+ if (!credentials && options.credentials) credentials = await park(options.credentials.resolve(programId, { signal }));
2624
+ if (!credentials) return fail(`Credentials are required to run ${programId}.`);
2625
+ store.setAuthenticated({
2626
+ credentials: credentials.posthog,
2627
+ apiProject: credentials.project,
2628
+ apiUser: credentials.apiUser
2629
+ });
2630
+ if (credentials.apiUser) analytics.identifyUser(credentials.apiUser);
2631
+ analytics.setGroups(groupsFromUser(credentials.apiUser, credentials.posthog.host.apiHost));
2632
+ if (!store.readData().aiSdkStampReported) {
2633
+ store.setAiSdkStampReported();
2634
+ stampAiSdkDetected({
2635
+ apiUser: credentials.apiUser,
2636
+ discoveredFeatures: input.discoveredFeatures ?? [],
2637
+ warehouseSources: input.warehouseSources ?? [],
2638
+ mayReportScanResults: input.mayReportScanResults ?? false
2639
+ });
2640
+ }
2641
+ if (program.requiresAi !== false && !flags.ci && !flags.signup && credentials.apiUser?.organization?.is_ai_data_processing_approved !== true) {
2642
+ if (!options.awaitAiApproval) return fail("AI processing approval is required before this program can run.");
2643
+ if (!await park(options.awaitAiApproval({
2644
+ programId,
2645
+ signal
2646
+ }))) return abort("AI processing approval declined.");
2647
+ }
2648
+ const gates = program.postAuthGates ?? [];
2649
+ if (gates.length > 0 && options.awaitPostAuthGates) await park(options.awaitPostAuthGates({
2650
+ programId,
2651
+ gates,
2652
+ signal
2653
+ }));
2654
+ if (!input.wizardFlags && options.featureFlags) flagSnapshot = await park(options.featureFlags());
2655
+ const login = credentials;
2656
+ configureOAuthSession(login.posthog, {
2657
+ rotate: (held) => rotateCredentials(held, input.host?.baseUrl),
2658
+ onRefreshed: (refreshed) => store.setAuthenticated({
2659
+ credentials: refreshed,
2660
+ apiProject: login.project,
2661
+ apiUser: login.apiUser
2662
+ })
2663
+ });
2664
+ const posthog = await oauthCredentials() ?? login.posthog;
2665
+ credentials = {
2666
+ ...login,
2667
+ posthog
2668
+ };
2669
+ signal.throwIfAborted();
2670
+ } catch (error) {
2671
+ if (signal.aborted) return cancelled();
2672
+ return fail(error instanceof Error ? error.message : String(error));
2673
+ }
2674
+ const wizardFlags = { ...flagSnapshot.flags };
2675
+ const wizardFlagPayloads = { ...flagSnapshot.payloads };
2676
+ const switchboard = {
2677
+ program: programId,
2678
+ composed: input.composed ?? false,
2679
+ flags: wizardFlags,
2680
+ flagPayloads: wizardFlagPayloads,
2681
+ cliHarness: input.overrides?.harness,
2682
+ cliSequence: input.overrides?.sequence,
2683
+ cliModel: input.overrides?.model
2684
+ };
2685
+ const binding = resolveBinding(switchboard);
2686
+ analytics.setTag("sequence", binding.sequence);
2687
+ analytics.setTag("harness", binding.harness);
2688
+ captureSwitchboardDecision(switchboard, binding);
2689
+ store.setBinding(binding);
2690
+ const wizardMetadata = {
2691
+ ...buildRunTags({
2692
+ programId,
2693
+ integration: run.integrationLabel,
2694
+ runId: analytics.runId,
2695
+ build: analytics.build,
2696
+ skillId: run.skillId
2697
+ }),
2698
+ SEQUENCE: binding.sequence,
2699
+ HARNESS: binding.harness
2700
+ };
2701
+ artifacts.reportFile = path.resolve(installDir, run.reportFile);
2702
+ const adapter = store.beginRun(runId, options.onProgress);
2703
+ const result = await runAgent({
2704
+ programId,
2705
+ run,
2706
+ composed: input.composed ?? false,
2707
+ binding,
2708
+ switchboard,
2709
+ skillsBaseUrl: getSkillsBaseUrl(),
2710
+ wizardFlags,
2711
+ wizardFlagPayloads,
2712
+ wizardMetadata,
2713
+ allowedTools: program.allowedTools,
2714
+ disallowedTools: program.disallowedTools,
2715
+ agentFlow: program.agentFlow,
2716
+ excludedTaskTypes: program.excludedTaskTypes,
2717
+ seedTasks: input.seedTasks,
2718
+ hooks: input.hooks
2719
+ }, {
2720
+ installDir,
2721
+ credentials: credentials.posthog,
2722
+ project: credentials.project,
2723
+ apiUser: credentials.apiUser,
2724
+ skillId: input.skillId ?? run.skillId ?? run.integrationLabel,
2725
+ integration: input.integration,
2726
+ frameworkDocsUrl: input.frameworkDocsUrl,
2727
+ flags,
2728
+ host: { ...input.host }
2729
+ }, {
2730
+ interaction: options.interaction,
2731
+ onProgress: (event) => adapter.onProgress(event),
2732
+ signal: options.signal
2733
+ });
2734
+ adapter.finish(result);
2735
+ return settle(result.outcome, result.outcome === "success" ? void 0 : result.failure);
2736
+ }
2737
+ /**
2738
+ * One event + one log line per run: what entered the switchboard, which
2739
+ * precedence rung decided each axis, and the final pick.
2740
+ */
2741
+ function captureSwitchboardDecision(ctx, binding) {
2742
+ const trace = ctx.trace ?? {};
2743
+ const perTaskModel = binding.sequence === "orchestrator" && trace.model === "binding";
2744
+ const model = perTaskModel ? "chosen-per-task" : binding.model;
2745
+ const modelSource = perTaskModel ? "agent-prompts" : trace.model;
2746
+ analytics.wizardCapture("switchboard resolved", {
2747
+ program: ctx.program,
2748
+ flag_self_driving_use_pi_harness: ctx.flags[WIZARD_SELF_DRIVING_USE_PI_HARNESS_FLAG_KEY],
2749
+ flag_self_driving_pi_payload: JSON.stringify(ctx.flagPayloads?.["wizard-self-driving-use-pi-harness"] ?? null),
2750
+ flag_orchestrator: ctx.flags[WIZARD_ORCHESTRATOR_FLAG_KEY],
2751
+ cli_harness: ctx.cliHarness,
2752
+ cli_sequence: ctx.cliSequence,
2753
+ cli_model: ctx.cliModel,
2754
+ harness_source: trace.harness,
2755
+ model_source: modelSource,
2756
+ sequence_source: trace.sequence,
2757
+ harness: binding.harness,
2758
+ model,
2759
+ thinking_level: binding.thinkingLevel,
2760
+ sequence: binding.sequence
2761
+ });
2762
+ logToFile(`[switchboard] decision: program=${ctx.program} in(orchestrator=${ctx.flags["wizard-orchestrator"] ?? "-"}, cli=${ctx.cliHarness ?? "-"}/${ctx.cliSequence ?? "-"}/${ctx.cliModel ?? "-"}) → harness=${binding.harness} (${trace.harness ?? "?"}) model=${model} (${modelSource ?? "?"}) sequence=${binding.sequence} (${trace.sequence ?? "?"})`);
2763
+ }
2764
+ //#endregion
2765
+ //#region src/ui/agent-progress.ts
2766
+ /**
2767
+ * The inverse of the agent's former `getUI()` calls: one event, one
2768
+ * `WizardUI` method, synchronous, in emission order. Because each case maps
2769
+ * back to exactly the call the agent used to make, the frame and flow goldens
2770
+ * hold without regeneration.
2771
+ */
2772
+ function createUiReducer(ui) {
2773
+ let spinner;
2774
+ return (event) => {
2775
+ switch (event.kind) {
2776
+ case "lifecycle":
2777
+ if (event.phase === "started") ui.startRun();
2778
+ else ui.outro(event.message);
2779
+ break;
2780
+ case "spinner":
2781
+ (spinner ??= ui.spinner())[event.action](event.message);
2782
+ break;
2783
+ case "log":
2784
+ ui.log[event.level](event.message);
2785
+ break;
2786
+ case "status":
2787
+ ui.pushStatus(event.message);
2788
+ break;
2789
+ case "tasks":
2790
+ ui.syncTodos(event.tasks);
2791
+ break;
2792
+ case "stage":
2793
+ ui.setStage(event.stage);
2794
+ break;
2795
+ case "url":
2796
+ if (event.which === "dashboard") ui.setDashboardUrl(event.url);
2797
+ else ui.setNotebookUrl(event.url);
2798
+ break;
2799
+ case "usage":
2800
+ ui.addTokenUsage(event.delta);
2801
+ break;
2802
+ case "finalCost":
2803
+ ui.setFinalTokenCostUsd(event.usd);
2804
+ break;
2805
+ case "authError":
2806
+ ui.showAuthError(event.detail);
2807
+ break;
2808
+ case "handoff":
2809
+ ui.setHandoffText(event.text);
2810
+ break;
2811
+ case "completion":
2812
+ ui.setOutroData(event.outro);
2813
+ break;
2814
+ case "activity": break;
2815
+ default: throw new Error(`Unhandled agent progress: ${JSON.stringify(event)}`);
2816
+ }
2817
+ };
2818
+ }
2819
+ /** The agent's questions, answered wherever `getUI()` answers them today. */
2820
+ function uiInteraction(ui) {
2821
+ return {
2822
+ ask: (question, { signal }) => dismissOnAbort(ui.requestQuestion(question), signal, () => ui.cancelPendingQuestion()),
2823
+ taskNotice: (notice, { signal }) => dismissOnAbort(ui.showTaskNotice(notice), signal, () => ui.cancelTaskNotice())
2824
+ };
2825
+ }
2826
+ /**
2827
+ * Dismiss one open request on abort; a settled one leaves the UI alone. A
2828
+ * throw inside an abort listener reaches no caller: Node rethrows it as an
2829
+ * uncaught exception, so a broken overlay is logged here instead.
2830
+ */
2831
+ function dismissOnAbort(open, signal, dismiss) {
2832
+ const onAbort = () => {
2833
+ try {
2834
+ dismiss();
2835
+ } catch (error) {
2836
+ logToFile("[agent-progress] dismissing an aborted request failed", error);
2837
+ }
2838
+ };
2839
+ if (signal.aborted) onAbort();
2840
+ else signal.addEventListener("abort", onAbort, { once: true });
2841
+ return open.finally(() => signal.removeEventListener("abort", onAbort));
2842
+ }
2843
+ //#endregion
2844
+ //#region src/programs/program-step.ts
2845
+ /**
2846
+ * Project program steps into the narrower Screen shape the router consumes.
2847
+ *
2848
+ * Two things happen here:
2849
+ * 1. Headless steps (no `screenId`) are filtered out. The router walks
2850
+ * visible screens; gate-only steps like `detect` are store concerns.
2851
+ * 2. The step is narrowed to just { id, show, isComplete } — the
2852
+ * router has no business touching gate, onInit, or label.
2853
+ *
2854
+ * This intentional separation keeps the router focused on one question:
2855
+ * "Which screen should be rendered right now?"
2856
+ */
2857
+ /**
2858
+ * The gated steps the agent runner awaits after `auth` and before `run`, in
2859
+ * step order. Empty when a program has no auth step or runs before it.
2860
+ */
2861
+ function postAuthGateSteps(steps) {
2862
+ const authIndex = steps.findIndex((s) => s.screenId === "auth");
2863
+ const runIndex = steps.findIndex((s) => s.screenId === "run");
2864
+ if (authIndex === -1 || runIndex <= authIndex) return [];
2865
+ return steps.slice(authIndex + 1, runIndex).filter((s) => s.gate);
2866
+ }
2867
+ function createProgramSequence(steps) {
2868
+ const entries = steps.filter((step) => step.screenId != null).map((step) => ({
2869
+ id: step.screenId,
2870
+ show: step.show,
2871
+ isComplete: step.isComplete ?? step.gate
2872
+ }));
2873
+ entries.push({
2874
+ id: "exit",
2875
+ show: void 0,
2876
+ isComplete: void 0
2877
+ });
2878
+ return entries;
2879
+ }
2880
+ //#endregion
2881
+ //#region src/programs/authenticate.ts
2882
+ async function authenticate(session, programId) {
2883
+ if (session.credentials) return;
2884
+ logToFile("[agent-runner] starting OAuth");
2885
+ const { projectApiKey, host, accessToken, refreshToken, expiresAt, oauthClientId, projectId, roleAtOrganization, user, project, missingScopes } = await getOrAskForProjectData({
2886
+ signup: session.signup,
2887
+ ci: session.ci,
2888
+ apiKey: session.apiKey,
2889
+ projectId: session.projectId,
2890
+ email: session.email,
2891
+ region: session.region,
2892
+ baseUrl: session.baseUrl,
2893
+ localMcp: session.localMcp,
2894
+ programId
2895
+ });
2896
+ session.credentials = {
2897
+ accessToken,
2898
+ refreshToken,
2899
+ expiresAt,
2900
+ oauthClientId,
2901
+ projectApiKey,
2902
+ host,
2903
+ projectId,
2904
+ missingScopes
2905
+ };
2906
+ session.apiProject = project;
2907
+ session.roleAtOrganization = roleAtOrganization;
2908
+ session.apiUser = user;
2909
+ getUI().setCredentials(session.credentials);
2910
+ getUI().setRoleAtOrganization(roleAtOrganization);
2911
+ getUI().setApiUser(user);
2912
+ if (user) analytics.identifyUser(user);
2913
+ analytics.setGroups(groupsFromUser(user, host.apiHost));
2914
+ }
2915
+ //#endregion
2916
+ //#region src/programs/audit/types.ts
2917
+ /** Single source of truth for status glyph + color across audit views. */
2918
+ const AUDIT_SEVERITY_STYLE = {
2919
+ pending: {
2920
+ glyph: "◌",
2921
+ color: "gray"
2922
+ },
2923
+ pass: {
2924
+ glyph: "✔",
2925
+ color: "green"
2926
+ },
2927
+ error: {
2928
+ glyph: "✘",
2929
+ color: "red"
2930
+ },
2931
+ warning: {
2932
+ glyph: "⚠",
2933
+ color: "yellow"
2934
+ },
2935
+ suggestion: {
2936
+ glyph: "•",
2937
+ color: "cyan"
2938
+ }
2939
+ };
2940
+ const AUDIT_CHECKS_KEY = "auditChecks";
2941
+ function getAuditChecks(session) {
2942
+ const raw = session.frameworkContext[AUDIT_CHECKS_KEY];
2943
+ return Array.isArray(raw) ? raw : [];
2944
+ }
2945
+ //#endregion
2946
+ //#region src/programs/audit/ledger-watcher.ts
2947
+ /**
2948
+ * Mirrors the agent's `.posthog-audit-checks.json` into the session, so the TUI
2949
+ * screens and the task stream read one value. `runProgramAgent` owns the
2950
+ * lifecycle and removes the file at run end, so every path gets it — including
2951
+ * the e2e host, which builds no task stream.
2952
+ */
2953
+ const MAX_LEDGER_FILE_BYTES = 256 * 1024;
2954
+ function startAuditLedgerWatcher(installDir, file, options = {}) {
2955
+ const target = path.join(installDir, file);
2956
+ logToFile(`[audit-ledger] watching ${target}`);
2957
+ return startFileWatcher(target, (parsed) => getUI().setFrameworkContext(AUDIT_CHECKS_KEY, coerceAuditChecks(parsed)), {
2958
+ ignoreInitialFile: true,
2959
+ maxFileSizeBytes: MAX_LEDGER_FILE_BYTES,
2960
+ ...options
2961
+ });
2962
+ }
2963
+ /** The program declared the ledger, so it removes it, agent `rm` step or not. */
2964
+ function removeAuditLedger(installDir, file) {
2965
+ const target = path.join(installDir, file);
2966
+ try {
2967
+ fs.rmSync(target, { force: true });
2968
+ } catch (error) {
2969
+ logToFile(`[audit-ledger] could not remove ${target}: ${String(error)}`);
2970
+ }
2971
+ }
2972
+ //#endregion
2973
+ //#region src/programs/detection/features.ts
2974
+ /**
2975
+ * Feature discovery — scan project dependencies for known SDK patterns
2976
+ * that indicate additional PostHog programs are relevant.
2977
+ *
2978
+ * Pure function: takes an install dir, returns a set of discovered features.
2979
+ * No store mutations, no UI calls.
2980
+ */
2981
+ const STRIPE_PACKAGES = new Set(["stripe", "@stripe/stripe-js"]);
2982
+ const LLM_PACKAGES = new Set([
2983
+ "openai",
2984
+ "@anthropic-ai/sdk",
2985
+ "ai",
2986
+ "@ai-sdk/openai",
2987
+ "langchain",
2988
+ "@langchain/openai",
2989
+ "@langchain/langgraph",
2990
+ "@google/generative-ai",
2991
+ "@google/genai",
2992
+ "@instructor-ai/instructor",
2993
+ "@mastra/core",
2994
+ "portkey-ai"
2995
+ ]);
2996
+ const PYTHON_LLM_PACKAGES = new Set([
2997
+ "openai",
2998
+ "anthropic",
2999
+ "langchain",
3000
+ "langchain-openai",
3001
+ "langchain-anthropic",
3002
+ "langchain-google-genai",
3003
+ "langgraph",
3004
+ "litellm",
3005
+ "llama-index",
3006
+ "pydantic-ai",
3007
+ "crewai",
3008
+ "instructor",
3009
+ "dspy-ai",
3010
+ "mistralai",
3011
+ "cohere",
3012
+ "google-generativeai",
3013
+ "google-genai",
3014
+ "portkey-ai"
3015
+ ]);
3016
+ function discoverFeatures(installDir) {
3017
+ const features = [];
3018
+ discoverNodeFeatures(installDir, features);
3019
+ discoverPythonFeatures(installDir, features);
3020
+ return features;
3021
+ }
3022
+ function discoverNodeFeatures(installDir, features) {
3023
+ const packageJsonText = safeRead(installDir, "package.json");
3024
+ if (!packageJsonText) return;
3025
+ let packageJson;
3026
+ try {
3027
+ packageJson = JSON.parse(packageJsonText);
3028
+ } catch {
3029
+ return;
3030
+ }
3031
+ const depNames = Object.keys({
3032
+ ...packageJson.dependencies,
3033
+ ...packageJson.devDependencies
3034
+ });
3035
+ if (depNames.some((depName) => STRIPE_PACKAGES.has(depName))) features.push("stripe");
3036
+ if (depNames.some((depName) => LLM_PACKAGES.has(depName))) features.push("llm");
3037
+ }
3038
+ function discoverPythonFeatures(installDir, features) {
3039
+ if (features.includes("llm")) return;
3040
+ const depNames = [];
3041
+ const requirementsTxt = safeRead(installDir, "requirements.txt");
3042
+ if (requirementsTxt) depNames.push(...parseRequirementsTxt(requirementsTxt));
3043
+ const pyprojectToml = safeRead(installDir, "pyproject.toml");
3044
+ if (pyprojectToml) depNames.push(...parsePyprojectToml(pyprojectToml));
3045
+ const pipfile = safeRead(installDir, "Pipfile");
3046
+ if (pipfile) depNames.push(...parsePipfile(pipfile));
3047
+ if (depNames.some((depName) => PYTHON_LLM_PACKAGES.has(depName))) features.push("llm");
3048
+ }
3049
+ function safeRead(installDir, file) {
3050
+ return readProjectFile(join(installDir, file));
3051
+ }
3052
+ function normalizePyName(name) {
3053
+ return name.toLowerCase().replace(/_/g, "-");
3054
+ }
3055
+ function parseRequirementsTxt(content) {
3056
+ return content.split("\n").map((line) => line.trim()).filter((line) => line && !line.startsWith("#") && !line.startsWith("-")).map((line) => line.replace(/\[[^\]]*\]/, "")).map((line) => line.split(/[<>=!~;\s]/)[0]).filter(Boolean).map(normalizePyName);
3057
+ }
3058
+ function parsePyprojectToml(content) {
3059
+ const depNames = [];
3060
+ for (const arrayMatch of content.matchAll(/dependencies\s*=\s*\[([^\]]*)\]/g)) {
3061
+ const arrayBody = arrayMatch[1];
3062
+ for (const quotedMatch of arrayBody.matchAll(/["']([^"']+)["']/g)) {
3063
+ const name = quotedMatch[1].replace(/\[[^\]]*\]/, "").split(/[<>=!~;\s]/)[0];
3064
+ if (name) depNames.push(normalizePyName(name));
3065
+ }
3066
+ }
3067
+ for (const sectionMatch of content.matchAll(/\[tool\.poetry\.(?:dev-dependencies|dependencies|group\.[^.\]]+\.dependencies)\]([\s\S]*?)(?=\n\[|$)/g)) {
3068
+ const sectionBody = sectionMatch[1];
3069
+ depNames.push(...extractTomlSectionKeys(sectionBody, { skip: ["python"] }));
3070
+ }
3071
+ return depNames;
3072
+ }
3073
+ function parsePipfile(content) {
3074
+ const depNames = [];
3075
+ for (const sectionMatch of content.matchAll(/\[(packages|dev-packages)\]([\s\S]*?)(?=\n\[|$)/g)) {
3076
+ const sectionBody = sectionMatch[2];
3077
+ depNames.push(...extractTomlSectionKeys(sectionBody));
3078
+ }
3079
+ return depNames;
3080
+ }
3081
+ function extractTomlSectionKeys(sectionBody, opts = {}) {
3082
+ const skipNames = new Set(opts.skip ?? []);
3083
+ const depNames = [];
3084
+ for (const line of sectionBody.split("\n")) {
3085
+ const trimmed = line.trim();
3086
+ if (!trimmed || trimmed.startsWith("#")) continue;
3087
+ const equalsIndex = trimmed.indexOf("=");
3088
+ if (equalsIndex <= 0) continue;
3089
+ const name = trimmed.slice(0, equalsIndex).trim();
3090
+ if (!/^[a-zA-Z0-9._-]+$/.test(name)) continue;
3091
+ if (skipNames.has(name.toLowerCase())) continue;
3092
+ depNames.push(normalizePyName(name));
3093
+ }
3094
+ return depNames;
3095
+ }
3067
3096
  //#endregion
3068
- //#region src/lib/warehouse-sources/detect.ts
3097
+ //#region src/programs/warehouse-sources/detect.ts
3069
3098
  /**
3070
3099
  * Data warehouse source detection.
3071
3100
  *
@@ -3191,7 +3220,7 @@ function parseGemfile(content) {
3191
3220
  return gems;
3192
3221
  }
3193
3222
  //#endregion
3194
- //#region src/lib/programs/warehouse-source/detect.ts
3223
+ //#region src/programs/warehouse-source/detect.ts
3195
3224
  /**
3196
3225
  * Warehouse-source program detection step.
3197
3226
  *
@@ -3267,246 +3296,23 @@ function detectWarehousePrerequisites(session, setFrameworkContext) {
3267
3296
  setFrameworkContext(DETECTED_WAREHOUSE_SOURCES_KEY, sources);
3268
3297
  }
3269
3298
  //#endregion
3270
- //#region src/lib/programs/shared/package-scanning.ts
3271
- const POSTHOG_SDKS = new Set([
3272
- "posthog-js",
3273
- "posthog-node",
3274
- "posthog-react-native",
3275
- "posthog-android",
3276
- "posthog-ios"
3277
- ]);
3278
- const STRIPE_SDKS = new Set([
3279
- "stripe",
3280
- "@stripe/stripe-js",
3281
- "@stripe/react-stripe-js"
3282
- ]);
3283
- const MAX_PACKAGE_MATCHES = 500;
3284
- /**
3285
- * Find all package.json files under installDir via the shared bounded walk
3286
- * (depth cap, ignored dirs, symlink-loop protection, per-dir entry cap).
3287
- * Returns matches with detected SDKs.
3288
- */
3289
- function findPackageJsons(installDir, maxDepth = 3) {
3290
- const matches = [];
3291
- walkProjectFiles(installDir, (name, fullPath) => {
3292
- if (name !== "package.json" || matches.length >= MAX_PACKAGE_MATCHES) return;
3293
- const content = safeReadFile(fullPath);
3294
- if (content === null) return;
3295
- try {
3296
- const pkg = JSON.parse(content);
3297
- const depNames = [...Object.keys(pkg.dependencies ?? {}), ...Object.keys(pkg.devDependencies ?? {})];
3298
- const posthogSdks = depNames.filter((d) => POSTHOG_SDKS.has(d));
3299
- const stripeSdks = depNames.filter((d) => STRIPE_SDKS.has(d));
3300
- matches.push({
3301
- path: relative(installDir, fullPath) || "package.json",
3302
- posthogSdks,
3303
- stripeSdks
3304
- });
3305
- } catch {}
3306
- }, maxDepth);
3307
- return matches;
3308
- }
3309
- //#endregion
3310
- //#region src/lib/programs/posthog-integration/detect.ts
3311
- async function detectPostHogIntegration(ctx) {
3312
- const session = ctx.session;
3313
- const installDir = session.installDir;
3314
- const detectedIntegration = await detectFramework(installDir);
3315
- if (detectedIntegration) {
3316
- const config = FRAMEWORK_REGISTRY[detectedIntegration];
3317
- const sessionOptions = {
3318
- installDir,
3319
- debug: session.debug,
3320
- signup: session.signup,
3321
- ci: session.ci,
3322
- benchmark: session.benchmark,
3323
- yaraReport: session.yaraReport
3324
- };
3325
- const context = await gatherFrameworkContext(config, sessionOptions);
3326
- for (const [key, value] of Object.entries(context)) if (!(key in session.frameworkContext)) ctx.setFrameworkContext(key, value);
3327
- ctx.setFrameworkConfig(detectedIntegration, config);
3328
- ctx.setSkillId(detectedIntegration);
3329
- if (!session.detectedFrameworkLabel) ctx.setDetectedFramework(config.metadata.name);
3330
- const versionResult = await checkFrameworkVersion(config, sessionOptions);
3331
- if (versionResult.supported !== true) ctx.setUnsupportedVersion(versionResult.supported);
3332
- }
3333
- for (const feature of discoverFeatures(installDir)) ctx.addDiscoveredFeature(feature);
3334
- detectWarehouseSourcesForSuggestion(ctx, installDir);
3335
- detectExistingPostHog(ctx, installDir);
3336
- ctx.setDetectionComplete();
3337
- }
3338
- /**
3339
- * How the scan went, for the reporter to read later. Three states, and the
3340
- * absent one carries the most weight:
3341
- *
3342
- * absent this program never scanned. Only `posthog-integration` runs the
3343
- * scan below, but every program's intro screen resolves consent
3344
- * through the same store method, so the reporter is reached on runs
3345
- * where no scan ever happened.
3346
- * 'failed' the scan threw. A zero-count row would read as "scanned, found
3347
- * nothing", which is a different fact.
3348
- * 'ok' the scan ran. Zero sources is a real result and gets reported.
3349
- */
3350
- const WAREHOUSE_SCAN_STATE_KEY = "warehouseScanState";
3299
+ //#region src/programs/run-agent-legacy.ts
3351
3300
  /**
3352
- * Scan for data warehouse source signals (Postgres, Stripe, Hubspot, …) and,
3353
- * when found, stash them for a `wizard warehouse` suggestion on the outro.
3354
- * Runs unconditionally during `detect`, before the intro screen has asked
3355
- * about sharing. Reporting what this finds is a separate, later concern.
3356
- * See `reportWarehouseSourcesDetected` below.
3301
+ * The session-driven agent runner every existing caller uses.
3357
3302
  *
3358
- * Deliberately a suggestion, not an inline agent run: a second credential-
3359
- * collecting agent run before the outro could `process.exit()` on any of its
3360
- * failure paths and cost the user the success outro on a run where PostHog
3361
- * installed fine.
3303
+ * `runProgramAgent(programConfig, session)` runs the gates the TUI owns
3304
+ * (health, settings), then calls `runProgram` as its caller, backed by the
3305
+ * session and `getUI()`: credentials come from `authenticate`, the AI
3306
+ * opt-in and post-auth gates park on the UI, every progress event maps back
3307
+ * onto `getUI()`, and the invocation's data projects back onto the session.
3308
+ * It applies the result — `wizardAbort` with the outcome's terminal status for
3309
+ * a decided failure, the terminal analytics event for a finished top-level run.
3362
3310
  *
3363
- * Calls `detectWarehouseSources` (a pure scan) rather than the warehouse
3364
- * program's `detectWarehousePrerequisites`, which writes a `detectError` when
3365
- * it finds nothing — that's right for `wizard warehouse` but here would show a
3366
- * spurious error and trip the CI prerequisites abort. No sources is a no-op.
3367
- *
3368
- * Best-effort: never let a scan failure break the wizard flow.
3369
- */
3370
- function detectWarehouseSourcesForSuggestion(ctx, installDir) {
3371
- try {
3372
- const sources = detectWarehouseSources(installDir);
3373
- ctx.setFrameworkContext(WAREHOUSE_SCAN_STATE_KEY, "ok");
3374
- if (sources.length === 0) return;
3375
- ctx.setFrameworkContext(DETECTED_WAREHOUSE_SOURCES_KEY, sources);
3376
- } catch (error) {
3377
- ctx.setFrameworkContext(WAREHOUSE_SCAN_STATE_KEY, "failed");
3378
- analytics.captureException(error instanceof Error ? error : new Error(String(error)), { step: "detectWarehouseSourcesForSuggestion" });
3379
- }
3380
- }
3381
- function hasAiSdkEvidence(session, sources) {
3382
- return sources.some((s) => AI_SOURCE_KINDS.has(s.kind)) || session.discoveredFeatures.includes("llm");
3383
- }
3384
- /**
3385
- * Boolean only, on the org, never the list of kinds or any non-AI tool: a
3386
- * decline must not leak even the shape of what local detection saw.
3387
- */
3388
- function stampAiSdkDetected(session, sources) {
3389
- const organizationId = session.apiUser?.organization?.id;
3390
- if (!organizationId) return;
3391
- if (!hasAiSdkEvidence(session, sources)) return;
3392
- analytics.groupIdentify("organization", organizationId, { wizard_ai_sdk_detected: true });
3393
- }
3394
- /**
3395
- * Fires the org stamp once per session, right after `authenticate()` succeeds
3396
- * — never from the consent path, since consent on the intro screen resolves
3397
- * before login and `session.apiUser` is unset there. Called right after
3398
- * `authenticate()` from run-wizard.ts's auth step and from bootstrap.ts,
3399
- * whichever completes it first for a given program; a no-op on every call
3400
- * after that. In the `--ci` path, project-scope.ts authenticates first as a
3401
- * prerequisite (no evidence gathered yet, so nothing would stamp there
3402
- * anyway) and this only ever runs from the later, idempotent bootstrap.ts
3403
- * call — still correctly finding no evidence, since CI skips the detect step.
3404
- */
3405
- function maybeStampAiSdkDetected(session) {
3406
- if (session.aiSdkStampReported) return;
3407
- session.aiSdkStampReported = true;
3408
- if (!mayReportScanResults(session)) return;
3409
- stampAiSdkDetected(session, getDetectedWarehouseSources(session));
3410
- }
3411
- /**
3412
- * The single place scan results become telemetry. Called from
3413
- * `WizardStore.completeSetup()`, the point consent becomes final — the privacy
3414
- * panel's choice is reversible until then, so nothing may report earlier.
3415
- * The org stamp is a separate concern: see `maybeStampAiSdkDetected`, which
3416
- * runs post-auth rather than at consent resolution.
3311
+ * This is the only file that knows about `getUI()`, the session and
3312
+ * `wizardAbort` on the agent's behalf.
3417
3313
  *
3418
- * Returns true when consent has resolved and the caller should latch
3419
- * `warehouseSourcesReported`, which is not the same as "this sent something":
3420
- * a decline, a failed scan, and a program that never scanned all resolve
3421
- * without sending.
3422
- */
3423
- function reportWarehouseSourcesDetected(session) {
3424
- if (session.warehouseSourcesReported) return false;
3425
- if (session.scanConsent === "undecided") return false;
3426
- if (mayReportScanResults(session)) {
3427
- const sources = getDetectedWarehouseSources(session);
3428
- if (session.frameworkContext[WAREHOUSE_SCAN_STATE_KEY] !== "ok") return true;
3429
- analytics.wizardCapture("warehouse sources detected", {
3430
- warehouse_source_count: sources.length,
3431
- warehouse_source_kinds: sources.map((s) => s.kind),
3432
- warehouse_source_modes: sources.map((s) => s.mode)
3433
- });
3434
- if (sources.length > 0) {
3435
- analytics.setTag("warehouse_source_kinds", sources.map((s) => s.kind).join(","));
3436
- analytics.setTag("warehouse_source_count", sources.length);
3437
- }
3438
- }
3439
- return true;
3440
- }
3441
- /** Dependency-level signal, not a verified install. A failed scan reports false. */
3442
- function detectExistingPostHog(ctx, installDir) {
3443
- try {
3444
- const pkgJsons = findPackageJsons(installDir);
3445
- ctx.setPosthogSdkDetected(pkgJsons.some((p) => p.posthogSdks.length > 0));
3446
- } catch (error) {
3447
- analytics.captureException(error instanceof Error ? error : new Error(String(error)), { step: "detectExistingPosthog" });
3448
- ctx.setPosthogSdkDetected(false);
3449
- }
3450
- }
3451
- //#endregion
3452
- //#region src/lib/programs/audit/types.ts
3453
- /** Single source of truth for status glyph + color across audit views. */
3454
- const AUDIT_SEVERITY_STYLE = {
3455
- pending: {
3456
- glyph: "◌",
3457
- color: "gray"
3458
- },
3459
- pass: {
3460
- glyph: "✔",
3461
- color: "green"
3462
- },
3463
- error: {
3464
- glyph: "✘",
3465
- color: "red"
3466
- },
3467
- warning: {
3468
- glyph: "⚠",
3469
- color: "yellow"
3470
- },
3471
- suggestion: {
3472
- glyph: "•",
3473
- color: "cyan"
3474
- }
3475
- };
3476
- const AUDIT_CHECKS_KEY = "auditChecks";
3477
- function getAuditChecks(session) {
3478
- const raw = session.frameworkContext[AUDIT_CHECKS_KEY];
3479
- return Array.isArray(raw) ? raw : [];
3480
- }
3481
- //#endregion
3482
- //#region src/lib/programs/audit/ledger-watcher.ts
3483
- /**
3484
- * Mirrors the agent's `.posthog-audit-checks.json` into the session, so the TUI
3485
- * screens and the task stream read one value. `runProgramAgent` owns the
3486
- * lifecycle and removes the file at run end, so every path gets it — including
3487
- * the e2e host, which builds no task stream.
3314
+ * ⚠️ Temporary adapter. It is removed later in the refactor.
3488
3315
  */
3489
- const MAX_LEDGER_FILE_BYTES = 256 * 1024;
3490
- function startAuditLedgerWatcher(installDir, file, options = {}) {
3491
- const target = path.join(installDir, file);
3492
- logToFile(`[audit-ledger] watching ${target}`);
3493
- return startFileWatcher(target, (parsed) => getUI().setFrameworkContext(AUDIT_CHECKS_KEY, coerceAuditChecks(parsed)), {
3494
- ignoreInitialFile: true,
3495
- maxFileSizeBytes: MAX_LEDGER_FILE_BYTES,
3496
- ...options
3497
- });
3498
- }
3499
- /** The program declared the ledger, so it removes it, agent `rm` step or not. */
3500
- function removeAuditLedger(installDir, file) {
3501
- const target = path.join(installDir, file);
3502
- try {
3503
- fs.rmSync(target, { force: true });
3504
- } catch (error) {
3505
- logToFile(`[audit-ledger] could not remove ${target}: ${String(error)}`);
3506
- }
3507
- }
3508
- //#endregion
3509
- //#region src/lib/programs/run-agent-legacy.ts
3510
3316
  var run_agent_legacy_exports = /* @__PURE__ */ __exportAll({ runProgramAgent: () => runProgramAgent });
3511
3317
  /**
3512
3318
  * Resolve a ProgramConfig's agent run definition and execute the pipeline.
@@ -3523,98 +3329,45 @@ async function runProgramAgent(programConfig, session, options = {}) {
3523
3329
  };
3524
3330
  if (ledger) registerCleanup(releaseLedger);
3525
3331
  try {
3526
- await runProgram(session, typeof programConfig.run === "function" ? await programConfig.run(session) : programConfig.run, programConfig, options.composed ?? false);
3332
+ await runSessionProgram(session, typeof programConfig.run === "function" ? await programConfig.run(session, uiRunnerContext()) : programConfig.run, programConfig, options.composed ?? false);
3527
3333
  } finally {
3528
3334
  releaseLedger();
3529
3335
  }
3530
3336
  }
3531
- /**
3532
- * Gates → authenticate → flags → binding → the functional run → apply result.
3533
- * Every step happens in the order it did inside the agent's bootstrap.
3534
- */
3535
- async function runProgram(session, run, programConfig, composed) {
3337
+ /** The runner context each program effect reaches `getUI()` through, read at call time. */
3338
+ function uiRunnerContext() {
3339
+ return {
3340
+ getFrameworkContext: (key) => getUI().getFrameworkContext(key),
3341
+ setFrameworkContext: (key, value) => getUI().setFrameworkContext(key, value),
3342
+ log: { warn: (message) => getUI().log.warn(message) }
3343
+ };
3344
+ }
3345
+ /** Gates → runProgram on the session's behalf → apply result. */
3346
+ async function runSessionProgram(session, run, programConfig, composed) {
3536
3347
  initLogFile();
3537
3348
  session.skillId = run.skillId ?? run.integrationLabel;
3538
3349
  logToFile(`[agent-runner] START ${run.integrationLabel} build=${analytics.build}${session.ci ? " (non-interactive)" : ""}`);
3539
3350
  if (session.debug) enableDebugLogs();
3540
3351
  await runHealthGate(session, programConfig);
3541
3352
  await runSettingsGate(session);
3542
- analytics.wizardCapture("agent started", {
3543
- integration: run.integrationLabel,
3544
- program_id: programConfig.id,
3545
- skill_id: run.skillId ?? null
3546
- });
3547
- await authenticate(session, programConfig.id);
3548
- maybeStampAiSdkDetected(session);
3549
- logToFile("[agent-runner] checking AI opt-in gate");
3550
- await getUI().waitForAiOptIn();
3551
- logToFile("[agent-runner] AI opt-in gate cleared");
3552
- for (const step of postAuthGateSteps(programConfig.steps)) {
3553
- logToFile(`[agent-runner] awaiting post-auth gate: ${step.id}`);
3554
- await getUI().waitForGate(step.id);
3555
- logToFile(`[agent-runner] post-auth gate cleared: ${step.id}`);
3556
- }
3557
- const wizardFlags = await analytics.getAllFlagsForWizard();
3558
- const wizardFlagPayloads = analytics.getWizardFlagPayloads();
3559
- const wizardMetadata = buildRunTags({
3560
- programId: programConfig.id,
3561
- integration: run.integrationLabel,
3562
- runId: analytics.runId,
3563
- build: analytics.build,
3564
- skillId: run.skillId
3565
- });
3566
- await refreshAccessTokenIfNeeded(session);
3567
- const credentials = session.credentials;
3568
- const switchboard = {
3569
- program: programConfig.id,
3570
- composed,
3571
- flags: wizardFlags,
3572
- flagPayloads: wizardFlagPayloads,
3573
- cliHarness: session.harness,
3574
- cliSequence: session.sequence,
3575
- cliModel: session.model
3576
- };
3577
- const binding = resolveBinding(switchboard);
3578
- analytics.setTag("sequence", binding.sequence);
3579
- analytics.setTag("harness", binding.harness);
3580
- wizardMetadata.SEQUENCE = binding.sequence;
3581
- wizardMetadata.HARNESS = binding.harness;
3582
- captureSwitchboardDecision(switchboard, binding);
3583
3353
  const ui = getUI();
3584
- registerCleanup(() => {
3585
- const report = flushScanReport({ yaraReport: session.yaraReport });
3586
- if (report) ui.log.info(report);
3354
+ const reduceUi = createUiReducer(ui);
3355
+ const projectData = projectProgramData(ui, session);
3356
+ let capabilityFailure;
3357
+ const keepFailure = (work) => work.catch((error) => {
3358
+ capabilityFailure ??= { error };
3359
+ throw error;
3587
3360
  });
3588
- if (binding.sequence === "linear") ui.onEnterScreen("outro", () => restoreClaudeSettings(session.installDir));
3589
3361
  const framework = session.integration ?? session.skillId ?? void 0;
3590
- const result = await runAgent$1({
3591
- programId: programConfig.id,
3362
+ const result = await runProgram(programConfig.id, {
3363
+ installDir: session.installDir,
3592
3364
  run,
3593
3365
  composed,
3594
- binding,
3595
- switchboard,
3596
- skillsBaseUrl: getSkillsBaseUrl(),
3597
- wizardFlags,
3598
- wizardFlagPayloads,
3599
- wizardMetadata,
3600
- allowedTools: programConfig.allowedTools,
3601
- disallowedTools: programConfig.disallowedTools,
3602
- agentFlow: programConfig.agentFlow,
3603
- excludedTaskTypes: programConfig.excludedTaskTypes,
3604
- seedTasks: programConfig.seedTasks ? () => programConfig.seedTasks(session) : void 0,
3605
- hooks: {
3606
- postRun: run.postRun ? (creds) => run.postRun(session, creds) : void 0,
3607
- buildOutroData: run.buildOutroData ? (creds) => run.buildOutroData(session, creds) ?? void 0 : void 0,
3608
- buildOutroNextSteps: run.buildOutroNextSteps ? (creds, completed) => run.buildOutroNextSteps(session, creds, completed) : void 0,
3609
- recordTaskOutcomes: (outcomes) => {
3610
- session.frameworkContext[TASK_OUTCOMES_KEY] = outcomes;
3611
- }
3612
- }
3613
- }, {
3614
- installDir: session.installDir,
3615
- credentials,
3616
- project: session.apiProject,
3617
- apiUser: session.apiUser,
3366
+ overrides: {
3367
+ harness: session.harness,
3368
+ sequence: session.sequence,
3369
+ model: session.model
3370
+ },
3618
3371
  skillId: session.skillId ?? void 0,
3619
3372
  integration: session.integration,
3620
3373
  frameworkDocsUrl: framework ? FRAMEWORK_REGISTRY[framework]?.metadata.docsUrl : void 0,
@@ -3634,14 +3387,62 @@ async function runProgram(session, run, programConfig, composed) {
3634
3387
  email: session.email,
3635
3388
  projectId: session.projectId,
3636
3389
  apiKey: session.apiKey
3637
- }
3390
+ },
3391
+ seedTasks: programConfig.seedTasks ? () => programConfig.seedTasks(session) : void 0,
3392
+ hooks: {
3393
+ postRun: run.postRun ? (creds) => run.postRun(session, creds) : void 0,
3394
+ buildOutroData: run.buildOutroData ? (creds) => run.buildOutroData(session, creds) ?? void 0 : void 0,
3395
+ buildOutroNextSteps: run.buildOutroNextSteps ? (creds, completed) => run.buildOutroNextSteps(session, creds, completed) : void 0,
3396
+ recordTaskOutcomes: (outcomes) => {
3397
+ session.frameworkContext[TASK_OUTCOMES_KEY] = outcomes;
3398
+ }
3399
+ },
3400
+ program: {
3401
+ requiresAi: programConfig.requiresAi,
3402
+ agentFlow: programConfig.agentFlow,
3403
+ allowedTools: programConfig.allowedTools,
3404
+ disallowedTools: programConfig.disallowedTools,
3405
+ excludedTaskTypes: programConfig.excludedTaskTypes,
3406
+ postAuthGates: postAuthGateSteps(programConfig.steps).map((step) => step.id)
3407
+ },
3408
+ aiSdkStampReported: session.aiSdkStampReported,
3409
+ discoveredFeatures: session.discoveredFeatures,
3410
+ warehouseSources: getDetectedWarehouseSources(session),
3411
+ mayReportScanResults: mayReportScanResults(session)
3638
3412
  }, {
3639
- onProgress: createUiReducer(ui),
3413
+ credentials: { resolve: () => keepFailure(authenticate(session, programConfig.id).then(() => ({
3414
+ posthog: session.credentials,
3415
+ project: session.apiProject,
3416
+ apiUser: session.apiUser
3417
+ }))) },
3418
+ awaitAiApproval: async () => {
3419
+ logToFile("[agent-runner] checking AI opt-in gate");
3420
+ await ui.waitForAiOptIn();
3421
+ logToFile("[agent-runner] AI opt-in gate cleared");
3422
+ return true;
3423
+ },
3424
+ awaitPostAuthGates: async ({ gates }) => {
3425
+ for (const gate of gates) {
3426
+ logToFile(`[agent-runner] awaiting post-auth gate: ${gate}`);
3427
+ await ui.waitForGate(gate);
3428
+ logToFile(`[agent-runner] post-auth gate cleared: ${gate}`);
3429
+ }
3430
+ },
3431
+ featureFlags: () => keepFailure(loadWizardFlags()),
3432
+ onProgress: (progress) => {
3433
+ if (progress.kind === "run") reduceUi(progress.event);
3434
+ else projectData(progress.data);
3435
+ },
3640
3436
  interaction: uiInteraction(ui)
3641
3437
  });
3642
- if (result.outcome === "crashed") throw result.failure.error;
3438
+ for (const diagnostic of result.diagnostics) {
3439
+ const runLabel = "runId" in diagnostic ? ` run=${diagnostic.runId}` : "";
3440
+ logToFile(`[agent-runner] progress diagnostic (${diagnostic.eventKind}${runLabel}): ${diagnostic.message}`);
3441
+ }
3442
+ if (capabilityFailure) throw capabilityFailure.error;
3443
+ if (result.outcome === "crashed") throw result.failure?.error;
3643
3444
  if (result.outcome !== "success") {
3644
- if (result.failure.authErrorDetail) ui.showAuthError(result.failure.authErrorDetail);
3445
+ if (result.failure?.authErrorDetail) ui.showAuthError(result.failure.authErrorDetail);
3645
3446
  await wizardAbort({
3646
3447
  ...result.failure,
3647
3448
  status: result.outcome === "aborted" ? "cancelled" : "error"
@@ -3652,6 +3453,34 @@ async function runProgram(session, run, programConfig, composed) {
3652
3453
  logToFile("[agent-runner] analytics shutdown failed:", error);
3653
3454
  }
3654
3455
  }
3456
+ /** Mirror the invocation's data onto the session and the UI the TUI reads. */
3457
+ function projectProgramData(ui, session) {
3458
+ let bindingSeen = false;
3459
+ return (data) => {
3460
+ const current = session.credentials;
3461
+ if (current && data.credentials && data.credentials.accessToken !== current.accessToken) {
3462
+ session.credentials = {
3463
+ ...current,
3464
+ accessToken: data.credentials.accessToken,
3465
+ refreshToken: data.credentials.refreshToken,
3466
+ expiresAt: data.credentials.expiresAt
3467
+ };
3468
+ ui.setAccessToken(session.credentials);
3469
+ }
3470
+ if (data.aiSdkStampReported) session.aiSdkStampReported = true;
3471
+ if (!data.binding || bindingSeen) return;
3472
+ bindingSeen = true;
3473
+ registerCleanup(() => {
3474
+ const report = flushScanReport({ yaraReport: session.yaraReport });
3475
+ if (report) ui.log.info(report);
3476
+ });
3477
+ if (data.binding.sequence === "linear") ui.onEnterScreen("outro", () => restoreClaudeSettings(session.installDir));
3478
+ };
3479
+ }
3480
+ const loadWizardFlags = async () => ({
3481
+ flags: await analytics.getAllFlagsForWizard(),
3482
+ payloads: analytics.getWizardFlagPayloads()
3483
+ });
3655
3484
  async function runHealthGate(session, programConfig) {
3656
3485
  const hasHealthCheckScreen = programConfig.steps.some((s) => s.screenId === "health-check");
3657
3486
  if (session.readinessResult) logToFile(`[agent-runner] readiness pre-computed by TUI: decision=${session.readinessResult.decision}${session.outageDismissed ? " (outage dismissed by user)" : ""} — skipping re-check`);
@@ -3706,34 +3535,7 @@ async function runSettingsGate(session) {
3706
3535
  logToFile("[agent-runner] settings override resolved");
3707
3536
  }
3708
3537
  }
3709
- /**
3710
- * One event + one log line per run: what entered the switchboard, which
3711
- * precedence rung decided each axis, and the final pick.
3712
- */
3713
- function captureSwitchboardDecision(ctx, binding) {
3714
- const trace = ctx.trace ?? {};
3715
- const perTaskModel = binding.sequence === "orchestrator" && trace.model === "binding";
3716
- const model = perTaskModel ? "chosen-per-task" : binding.model;
3717
- const modelSource = perTaskModel ? "agent-prompts" : trace.model;
3718
- analytics.wizardCapture("switchboard resolved", {
3719
- program: ctx.program,
3720
- flag_self_driving_use_pi_harness: ctx.flags[WIZARD_SELF_DRIVING_USE_PI_HARNESS_FLAG_KEY],
3721
- flag_self_driving_pi_payload: JSON.stringify(ctx.flagPayloads?.["wizard-self-driving-use-pi-harness"] ?? null),
3722
- flag_orchestrator: ctx.flags[WIZARD_ORCHESTRATOR_FLAG_KEY],
3723
- cli_harness: ctx.cliHarness,
3724
- cli_sequence: ctx.cliSequence,
3725
- cli_model: ctx.cliModel,
3726
- harness_source: trace.harness,
3727
- model_source: modelSource,
3728
- sequence_source: trace.sequence,
3729
- harness: binding.harness,
3730
- model,
3731
- thinking_level: binding.thinkingLevel,
3732
- sequence: binding.sequence
3733
- });
3734
- logToFile(`[switchboard] decision: program=${ctx.program} in(orchestrator=${ctx.flags["wizard-orchestrator"] ?? "-"}, cli=${ctx.cliHarness ?? "-"}/${ctx.cliSequence ?? "-"}/${ctx.cliModel ?? "-"}) → harness=${binding.harness} (${trace.harness ?? "?"}) model=${model} (${modelSource ?? "?"}) sequence=${binding.sequence} (${trace.sequence ?? "?"})`);
3735
- }
3736
3538
  //#endregion
3737
- export { createProgramSequence as S, detectProjectsWithAgent as _, getAuditChecks as a, detectFramework as b, reportWarehouseSourcesDetected as c, findPackageJsons as d, WAREHOUSE_ABORT_CASES as f, AgenticDetectionTimeoutError as g, detectWarehouseSources as h, AUDIT_SEVERITY_STYLE as i, POSTHOG_SDKS as l, getDetectedWarehouseSources as m, run_agent_legacy_exports as n, detectPostHogIntegration as o, detectWarehousePrerequisites as p, AUDIT_CHECKS_KEY as r, maybeStampAiSdkDetected as s, runProgramAgent as t, STRIPE_SDKS as u, resolveProjectDir as v, authenticate as x, gatherFrameworkContext as y };
3539
+ export { detectWarehousePrerequisites as a, discoverFeatures as c, getAuditChecks as d, authenticate as f, stampAiSdkDetected as h, WAREHOUSE_ABORT_CASES as i, AUDIT_CHECKS_KEY as l, createUiReducer as m, run_agent_legacy_exports as n, getDetectedWarehouseSources as o, createProgramSequence as p, DETECTED_WAREHOUSE_SOURCES_KEY as r, detectWarehouseSources as s, runProgramAgent as t, AUDIT_SEVERITY_STYLE as u };
3738
3540
 
3739
- //# sourceMappingURL=run-agent-legacy-d7rWlzQR.js.map
3541
+ //# sourceMappingURL=run-agent-legacy-6WcYMaR9.js.map