@posthog/wizard 2.24.1 → 2.26.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.
- package/dist/{add-mcp-server-to-clients-CfwEQT_z.js → add-mcp-server-to-clients-C58l_KpV.js} +4 -4
- package/dist/{add-mcp-server-to-clients-CfwEQT_z.js.map → add-mcp-server-to-clients-C58l_KpV.js.map} +1 -1
- package/dist/{agent-interface-D1vtN6Wn.js → agent-interface-Dq_4h2eN.js} +435 -45
- package/dist/agent-interface-Dq_4h2eN.js.map +1 -0
- package/dist/{agent-runner-CBbkS0Ro.js → agent-runner-BNGW3osc.js} +748 -132
- package/dist/agent-runner-BNGW3osc.js.map +1 -0
- package/dist/{analytics-CUr82BDl.js → analytics-BX3LKPch.js} +51 -17
- package/dist/analytics-BX3LKPch.js.map +1 -0
- package/dist/{api-CI3Z74NG.js → api-DCHci5SD.js} +9 -5
- package/dist/api-DCHci5SD.js.map +1 -0
- package/dist/bin.js +830 -120
- package/dist/bin.js.map +1 -1
- package/dist/{ci-install-D_kxNmbJ.js → ci-install-CHIbwXio.js} +5 -5
- package/dist/{ci-install-D_kxNmbJ.js.map → ci-install-CHIbwXio.js.map} +1 -1
- package/dist/{debug-DxA_f5QT.js → debug-BizeRFR0.js} +17 -8
- package/dist/debug-BizeRFR0.js.map +1 -0
- package/dist/{debug-zMvpNYb2.js → debug-fg4BAKKA.js} +1 -1
- package/dist/{environment-CyS37cmM.js → environment-DS5Pq9Wm.js} +3 -3
- package/dist/{environment-CyS37cmM.js.map → environment-DS5Pq9Wm.js.map} +1 -1
- package/dist/{interactive-CG6FFqSw.js → interactive-DE3WDjk7.js} +3 -3
- package/dist/{interactive-CG6FFqSw.js.map → interactive-DE3WDjk7.js.map} +1 -1
- package/dist/{mcp-prompt-streaming-DQz4FSb1.js → mcp-prompt-streaming-zsYd1zJx.js} +7 -26
- package/dist/mcp-prompt-streaming-zsYd1zJx.js.map +1 -0
- package/dist/{non-interactive-DWtHX3ZR.js → non-interactive-DNah9u3t.js} +2 -2
- package/dist/{non-interactive-DWtHX3ZR.js.map → non-interactive-DNah9u3t.js.map} +1 -1
- package/dist/{package-manager-BWUS4CP0.js → package-manager-Dma9-zGs.js} +2 -2
- package/dist/{package-manager-BWUS4CP0.js.map → package-manager-Dma9-zGs.js.map} +1 -1
- package/dist/{playground-D7AhMMF5.js → playground-Cwe0Q9HW.js} +146 -49
- package/dist/playground-Cwe0Q9HW.js.map +1 -0
- package/dist/{posthog-integration-DexZ2uHU.js → posthog-integration-CAYZdk0r.js} +11 -11
- package/dist/{posthog-integration-DexZ2uHU.js.map → posthog-integration-CAYZdk0r.js.map} +1 -1
- package/dist/{provisioning-9c-AQbsa.js → provisioning-BmL4ro-o.js} +10 -6
- package/dist/{provisioning-9c-AQbsa.js.map → provisioning-BmL4ro-o.js.map} +1 -1
- package/dist/{registry-CO7JVZyE.js → registry-C3wcDM3X.js} +4 -4
- package/dist/{registry-CO7JVZyE.js.map → registry-C3wcDM3X.js.map} +1 -1
- package/dist/{setup-utils-0U-_Md2G.js → setup-utils-CNWIMZ-d.js} +71 -16
- package/dist/setup-utils-CNWIMZ-d.js.map +1 -0
- package/dist/smoke-test.sh +36 -1
- package/dist/{start-tui-WNb3ET14.js → start-tui-CS802Ww9.js} +311 -54
- package/dist/start-tui-CS802Ww9.js.map +1 -0
- package/dist/{steps-BAUXDCC4.js → steps-BX44xr30.js} +6 -6
- package/dist/{steps-BAUXDCC4.js.map → steps-BX44xr30.js.map} +1 -1
- package/dist/{task-stream-CZawuzlz.js → task-stream-BQNSp0qR.js} +4 -3
- package/dist/task-stream-BQNSp0qR.js.map +1 -0
- package/dist/{telemetry-ycqCpNPr.js → telemetry-BH-MgWPT.js} +3 -3
- package/dist/{telemetry-ycqCpNPr.js.map → telemetry-BH-MgWPT.js.map} +1 -1
- package/dist/{AiOptInRequiredScreen-_33FOcVo.js → terminal-BSiupnOQ.js} +1058 -92
- package/dist/terminal-BSiupnOQ.js.map +1 -0
- package/dist/{urls-C8aJWvgh.js → urls-BuEABcmF.js} +2 -2
- package/dist/{urls-C8aJWvgh.js.map → urls-BuEABcmF.js.map} +1 -1
- package/dist/{wizard-abort-DWXyJdws.js → wizard-abort-CR3w2Efg.js} +1 -1
- package/dist/{wizard-abort-C6gRLxUE.js → wizard-abort-Dl2MJOP9.js} +3 -3
- package/dist/{wizard-abort-C6gRLxUE.js.map → wizard-abort-Dl2MJOP9.js.map} +1 -1
- package/dist/wizard-session-G3VWD6hv.js.map +1 -1
- package/dist/{wizard-ui-YdGFRyu_.js → wizard-ui-WZ48rUgr.js} +2 -1
- package/dist/wizard-ui-WZ48rUgr.js.map +1 -0
- package/package.json +1 -1
- package/dist/AiOptInRequiredScreen-_33FOcVo.js.map +0 -1
- package/dist/agent-interface-D1vtN6Wn.js.map +0 -1
- package/dist/agent-runner-CBbkS0Ro.js.map +0 -1
- package/dist/analytics-CUr82BDl.js.map +0 -1
- package/dist/api-CI3Z74NG.js.map +0 -1
- package/dist/debug-DxA_f5QT.js.map +0 -1
- package/dist/mcp-prompt-streaming-DQz4FSb1.js.map +0 -1
- package/dist/playground-D7AhMMF5.js.map +0 -1
- package/dist/setup-utils-0U-_Md2G.js.map +0 -1
- package/dist/start-tui-WNb3ET14.js.map +0 -1
- package/dist/task-stream-CZawuzlz.js.map +0 -1
- package/dist/wizard-ui-YdGFRyu_.js.map +0 -1
|
@@ -1,13 +1,695 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { n as groupsFromUser, t as analytics } from "./analytics-
|
|
3
|
-
import { t as getOrAskForProjectData } from "./setup-utils-
|
|
4
|
-
import { n as getCloudUrlFromRegion } from "./urls-
|
|
5
|
-
import { i as wizardAbort, n as registerCleanup, t as WizardError } from "./wizard-abort-
|
|
6
|
-
import {
|
|
7
|
-
import { r as
|
|
8
|
-
import
|
|
1
|
+
import { $ as getSkillsBaseUrl, _ as SIGNUP_WIZARD_READINESS_CONFIG, a as getLogFilePath, c as WIZARD_BENCHMARK_FILE, g as SERVICE_LABELS, i as enableDebugLogs, l as WIZARD_LOG_FILE, nt as runtimeEnv, o as initLogFile, p as getUI, s as logToFile, t as configureLogFile, v as evaluateWizardReadiness, y as getBlockingServiceKeys } from "./debug-BizeRFR0.js";
|
|
2
|
+
import { i as ciExcludedTaskTypes, n as groupsFromUser, t as analytics } from "./analytics-BX3LKPch.js";
|
|
3
|
+
import { t as getOrAskForProjectData } from "./setup-utils-CNWIMZ-d.js";
|
|
4
|
+
import { n as getCloudUrlFromRegion } from "./urls-BuEABcmF.js";
|
|
5
|
+
import { i as wizardAbort, n as registerCleanup, t as WizardError } from "./wizard-abort-Dl2MJOP9.js";
|
|
6
|
+
import { n as isNonInteractiveEnvironment } from "./environment-DS5Pq9Wm.js";
|
|
7
|
+
import { _ as QUEUE_DIR_NAME, a as runAgent$1, d as formatScanReport, f as writeScanReport, g as installSkillById, h as fetchSkillMenu, i as isOrchestratorEnabled, l as restoreClaudeSettings, n as buildWizardMetadata, o as backupAndFixClaudeSettings, r as initializeAgent, s as checkAllSettingsConflicts, u as AgentSignals, v as QueueStore, y as TaskStatus } from "./agent-interface-Dq_4h2eN.js";
|
|
8
|
+
import { r as detectNodePackageManagers } from "./package-manager-Dma9-zGs.js";
|
|
9
|
+
import fs, { existsSync, rmSync } from "fs";
|
|
10
|
+
import * as path$1 from "path";
|
|
9
11
|
import path from "path";
|
|
10
12
|
import { randomUUID } from "crypto";
|
|
13
|
+
//#region src/lib/agent/runner/orchestrator/executor.ts
|
|
14
|
+
/**
|
|
15
|
+
* The executor drains the queue. It starts every runnable task (dependencies
|
|
16
|
+
* satisfied) as soon as it becomes runnable — parallelism is decided by the
|
|
17
|
+
* task graph, not by an executor knob. Each task runs through an injected
|
|
18
|
+
* `runTask` function and reports its outcome via `complete_task`; a task that
|
|
19
|
+
* ends without reporting is retried while attempts remain, then failed. A
|
|
20
|
+
* `maxStarts` backstop guarantees termination.
|
|
21
|
+
*
|
|
22
|
+
* The drain loop is independent of how a task actually runs. `runTask` is
|
|
23
|
+
* injected: the real one spins up a fresh agent, the tests use a fake.
|
|
24
|
+
*/
|
|
25
|
+
const DEFAULT_DRAIN_OPTIONS = { maxStarts: 200 };
|
|
26
|
+
async function runOne(store, runTask, task) {
|
|
27
|
+
store.start(task.id);
|
|
28
|
+
try {
|
|
29
|
+
await runTask(task);
|
|
30
|
+
} catch (error) {
|
|
31
|
+
logToFile(`[executor] runTask threw for ${task.type}:`, error);
|
|
32
|
+
analytics.captureException(error instanceof Error ? error : new Error(String(error)), {
|
|
33
|
+
step: "orchestrator_run_task",
|
|
34
|
+
task_type: task.type
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
const after = store.get(task.id);
|
|
38
|
+
if (!after) return;
|
|
39
|
+
if (after.status === TaskStatus.Running) {
|
|
40
|
+
if (after.attempts < after.maxAttempts) store.requeue(task.id);
|
|
41
|
+
else store.fail(task.id, {
|
|
42
|
+
type: "no-report",
|
|
43
|
+
message: "Task ended without calling complete_task."
|
|
44
|
+
});
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
if (after.status === TaskStatus.Failed && after.attempts < after.maxAttempts) store.requeue(task.id);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Drain the queue to a terminal state. Every runnable task starts the moment
|
|
51
|
+
* its dependencies finish; independent branches run concurrently. Returns when
|
|
52
|
+
* every task is done, failed, or blocked by a failed dependency, or when the
|
|
53
|
+
* start backstop trips.
|
|
54
|
+
*/
|
|
55
|
+
async function drainQueue(store, runTask, opts = DEFAULT_DRAIN_OPTIONS) {
|
|
56
|
+
const running = /* @__PURE__ */ new Map();
|
|
57
|
+
let starts = 0;
|
|
58
|
+
for (;;) {
|
|
59
|
+
for (const task of store.nextRunnable()) {
|
|
60
|
+
if (++starts > opts.maxStarts) break;
|
|
61
|
+
const p = runOne(store, runTask, task).finally(() => running.delete(task.id));
|
|
62
|
+
running.set(task.id, p);
|
|
63
|
+
}
|
|
64
|
+
if (running.size === 0) break;
|
|
65
|
+
await Promise.race(running.values());
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
//#endregion
|
|
69
|
+
//#region src/lib/agent/runner/orchestrator/run-metrics.ts
|
|
70
|
+
var RunMetrics = class {
|
|
71
|
+
firstStartMs;
|
|
72
|
+
lastStartMs;
|
|
73
|
+
firstCompleteMs;
|
|
74
|
+
lastVisibleMs;
|
|
75
|
+
maxGapMs = 0;
|
|
76
|
+
constructor(runStartMs) {
|
|
77
|
+
this.runStartMs = runStartMs;
|
|
78
|
+
}
|
|
79
|
+
/** A task started. Returns the per-start-event timing for the start event. */
|
|
80
|
+
recordStart(nowMs) {
|
|
81
|
+
const timing = {
|
|
82
|
+
ms_since_run_start: nowMs - this.runStartMs,
|
|
83
|
+
gap_since_prev_start_ms: this.lastStartMs === void 0 ? void 0 : nowMs - this.lastStartMs
|
|
84
|
+
};
|
|
85
|
+
this.firstStartMs ??= nowMs;
|
|
86
|
+
this.lastStartMs = nowMs;
|
|
87
|
+
this.markVisible(nowMs);
|
|
88
|
+
return timing;
|
|
89
|
+
}
|
|
90
|
+
/** A task completed. */
|
|
91
|
+
recordComplete(nowMs) {
|
|
92
|
+
this.firstCompleteMs ??= nowMs;
|
|
93
|
+
this.markVisible(nowMs);
|
|
94
|
+
}
|
|
95
|
+
/** A task reached a terminal non-complete state the user sees (skip/fail). */
|
|
96
|
+
recordTerminal(nowMs) {
|
|
97
|
+
this.markVisible(nowMs);
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* The run-level responsiveness summary. Timings are `undefined` when the
|
|
101
|
+
* relevant transition never happened (e.g. a run that started no task), so a
|
|
102
|
+
* no-task run stays distinguishable from a genuine zero.
|
|
103
|
+
*/
|
|
104
|
+
summary() {
|
|
105
|
+
return {
|
|
106
|
+
time_to_first_task_ms: this.firstStartMs === void 0 ? void 0 : this.firstStartMs - this.runStartMs,
|
|
107
|
+
time_to_first_completion_ms: this.firstCompleteMs === void 0 ? void 0 : this.firstCompleteMs - this.runStartMs,
|
|
108
|
+
max_gap_ms: this.lastVisibleMs === void 0 ? void 0 : this.maxGapMs
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
/** requeue is not user-visible, so a retry stall counts as silence here. */
|
|
112
|
+
markVisible(nowMs) {
|
|
113
|
+
if (this.lastVisibleMs !== void 0) this.maxGapMs = Math.max(this.maxGapMs, nowMs - this.lastVisibleMs);
|
|
114
|
+
this.lastVisibleMs = nowMs;
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
//#endregion
|
|
118
|
+
//#region src/lib/agent/agent-prompt-loader.ts
|
|
119
|
+
function projectContext(ctx) {
|
|
120
|
+
return `You have access to the PostHog MCP server and the wizard tools.
|
|
121
|
+
|
|
122
|
+
Project context:
|
|
123
|
+
- PostHog Project ID: ${ctx.projectId}
|
|
124
|
+
- PostHog public token: ${ctx.projectApiKey}
|
|
125
|
+
- PostHog Host: ${ctx.host}`;
|
|
126
|
+
}
|
|
127
|
+
/** Points the agent at the framework's reference integration to learn patterns from. */
|
|
128
|
+
function exampleReference(ctx) {
|
|
129
|
+
if (!ctx.examplePath) return null;
|
|
130
|
+
return `A reference PostHog integration for this framework is at \`${ctx.examplePath}\`. It shows the target implementation pattern. Reference its patterns and conventions, adapting them to this codebase.`;
|
|
131
|
+
}
|
|
132
|
+
/** The framework's rules ship with the reference skill; every task follows them. */
|
|
133
|
+
function commandmentsReference(ctx) {
|
|
134
|
+
if (!ctx.commandmentsPath) return null;
|
|
135
|
+
return `Framework rules for this integration are at \`${ctx.commandmentsPath}\`. Read them before you edit and follow them.`;
|
|
136
|
+
}
|
|
137
|
+
const TASK_BASICS = `You are one isolated task in a larger PostHog workflow, run as a fresh agent with no memory of the other tasks beyond the context you are given. Do only your task, then report exactly once by calling complete_task with a structured handoff: what your goal was, what you did, and what the next agent should know. When you are given context from previous steps, trust it — those agents already did their work, so do not re-verify or re-read what their handoffs tell you. Build on it and move fast. Read a file before you edit it, so your own changes do not duplicate what is already there. Work only inside this project's own directory: never read, list, or search (find, ls, grep, glob) outside it — not the OS, not other projects, not global package caches. If your task seems to need something outside this directory, it does not — skip that part and say so in your handoff rather than hunting across the filesystem. If your task does not apply to this project — there is genuinely nothing for it to do — report it with status \`skipped\` and say why, rather than marking it done.`;
|
|
138
|
+
const SEED_BASICS = `You are the orchestrator. Plan the work and seed the queue with enqueue_task — each call returns an id you can pass as a dependency to a later task. Give each task a short label for the UI — the action in a few words, not file names, class names, or other specifics. You are not a task yourself: do not call complete_task and do not edit the project.`;
|
|
139
|
+
/**
|
|
140
|
+
* Points the agent at its installed task instructions (the HOW). They live under
|
|
141
|
+
* the wizard's run dir, not `.claude/skills/`, so the SDK does not auto-load
|
|
142
|
+
* them — the prompt has to name them.
|
|
143
|
+
*/
|
|
144
|
+
function skillReference(paths) {
|
|
145
|
+
if (paths.length === 0) return null;
|
|
146
|
+
return `Your task instructions are at ${paths.map((p) => `\`${p}\``).join(", ")}. Read them before you start and follow them. They are wizard scaffolding, not part of the project.`;
|
|
147
|
+
}
|
|
148
|
+
/** A task agent's full prompt: injected basics, then the authored intent. */
|
|
149
|
+
function assembleTaskPrompt(ctx, body, skillPaths = []) {
|
|
150
|
+
return [
|
|
151
|
+
projectContext(ctx),
|
|
152
|
+
exampleReference(ctx),
|
|
153
|
+
commandmentsReference(ctx),
|
|
154
|
+
skillReference(skillPaths),
|
|
155
|
+
TASK_BASICS,
|
|
156
|
+
body
|
|
157
|
+
].filter(Boolean).join("\n\n");
|
|
158
|
+
}
|
|
159
|
+
/** The seed agent's full prompt: injected basics, then the authored intent. */
|
|
160
|
+
function assembleSeedPrompt(ctx, body) {
|
|
161
|
+
return [
|
|
162
|
+
projectContext(ctx),
|
|
163
|
+
SEED_BASICS,
|
|
164
|
+
body
|
|
165
|
+
].join("\n\n");
|
|
166
|
+
}
|
|
167
|
+
/** Used when neither the enqueue call nor the prompt frontmatter names a model. */
|
|
168
|
+
const DEFAULT_TASK_MODEL = "claude-sonnet-4-6";
|
|
169
|
+
/** Orchestrator tools are MCP tools under the `posthog-wizard` server. Frontmatter
|
|
170
|
+
* names them short (e.g. `enqueue_task`); the SDK gates on the full name. */
|
|
171
|
+
const ORCHESTRATOR_TOOL_PREFIX = "mcp__posthog-wizard__";
|
|
172
|
+
const ORCHESTRATOR_TOOLS = new Set([
|
|
173
|
+
"enqueue_task",
|
|
174
|
+
"complete_task",
|
|
175
|
+
"read_handoffs"
|
|
176
|
+
]);
|
|
177
|
+
/** The registry for one flow's prompts. Pure; the loader feeds it the fetched set. */
|
|
178
|
+
function buildRegistry(prompts, flow, opts) {
|
|
179
|
+
const excluded = new Set(opts?.exclude ?? []);
|
|
180
|
+
const inFlow = prompts.filter((p) => p.flow === flow && !excluded.has(p.type));
|
|
181
|
+
const byType = new Map(inFlow.map((p) => [p.type, p]));
|
|
182
|
+
return {
|
|
183
|
+
types: inFlow.filter((p) => !p.seed).map((p) => p.type),
|
|
184
|
+
seed: inFlow.find((p) => p.seed),
|
|
185
|
+
get: (type) => byType.get(type)
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
/** A native tool passes through; an orchestrator tool gets its MCP-qualified name. */
|
|
189
|
+
function expandToolName(name) {
|
|
190
|
+
return ORCHESTRATOR_TOOLS.has(name) ? `${ORCHESTRATOR_TOOL_PREFIX}${name}` : name;
|
|
191
|
+
}
|
|
192
|
+
/** A prompt's allow/disallow lists with orchestrator tool names MCP-qualified. */
|
|
193
|
+
function agentRunTools(prompt) {
|
|
194
|
+
return {
|
|
195
|
+
allowedTools: prompt.allowedTools.map(expandToolName),
|
|
196
|
+
disallowedTools: prompt.disallowedTools.map(expandToolName)
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
function toStringArray(value) {
|
|
200
|
+
if (!Array.isArray(value)) return [];
|
|
201
|
+
return value.filter((v) => typeof v === "string");
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Parse the leading `---` frontmatter block and the markdown body. The
|
|
205
|
+
* frontmatter is a small, known schema (scalars and inline `[a, b]` arrays), so
|
|
206
|
+
* a tiny parser covers it without a YAML dependency. Inline `# comments` after a
|
|
207
|
+
* value are stripped. `fallbackType` is the menu id, used when the body omits
|
|
208
|
+
* `type:`.
|
|
209
|
+
*/
|
|
210
|
+
function parseAgentPrompt(text, fallbackType) {
|
|
211
|
+
const match = text.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/);
|
|
212
|
+
const frontmatter = match ? match[1] : "";
|
|
213
|
+
const body = (match ? match[2] : text).trim();
|
|
214
|
+
const fields = {};
|
|
215
|
+
for (const rawLine of frontmatter.split(/\r?\n/)) {
|
|
216
|
+
const line = rawLine.replace(/\s+#.*$/, "").trim();
|
|
217
|
+
if (!line || line.startsWith("#")) continue;
|
|
218
|
+
const kv = line.match(/^([\w-]+):\s*(.*)$/);
|
|
219
|
+
if (!kv) continue;
|
|
220
|
+
const [, key, raw] = kv;
|
|
221
|
+
if (raw.startsWith("[") && raw.endsWith("]")) fields[key] = raw.slice(1, -1).split(",").map((s) => s.trim().replace(/^['"]|['"]$/g, "")).filter(Boolean);
|
|
222
|
+
else fields[key] = raw.replace(/^['"]|['"]$/g, "");
|
|
223
|
+
}
|
|
224
|
+
const model = typeof fields.model === "string" ? fields.model : void 0;
|
|
225
|
+
return {
|
|
226
|
+
type: typeof fields.type === "string" ? fields.type : fallbackType,
|
|
227
|
+
label: typeof fields.label === "string" ? fields.label : void 0,
|
|
228
|
+
flow: typeof fields.flow === "string" ? fields.flow : void 0,
|
|
229
|
+
seed: fields.seed === "true",
|
|
230
|
+
model,
|
|
231
|
+
skills: toStringArray(fields.skills),
|
|
232
|
+
allowedTools: toStringArray(fields.allowedTools),
|
|
233
|
+
disallowedTools: toStringArray(fields.disallowedTools),
|
|
234
|
+
dependsOn: toStringArray(fields.dependsOn),
|
|
235
|
+
body
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
async function fetchText(url) {
|
|
239
|
+
const res = await fetch(url);
|
|
240
|
+
if (!res.ok) throw new Error(`Fetch ${url} failed: ${res.status} ${res.statusText}`);
|
|
241
|
+
return res.text();
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Fetch the agent menu and every agent prompt it lists, parse them, and build
|
|
245
|
+
* the registry for one flow. Throws if the menu cannot be fetched — the
|
|
246
|
+
* orchestrator cannot run without its prompts.
|
|
247
|
+
*/
|
|
248
|
+
async function loadAgentRegistry(skillsBaseUrl, flow, opts) {
|
|
249
|
+
const menuRaw = await fetchText(`${skillsBaseUrl}/agent-menu.json`);
|
|
250
|
+
const menu = JSON.parse(menuRaw);
|
|
251
|
+
return buildRegistry(await Promise.all((menu.agents ?? []).map(async (entry) => {
|
|
252
|
+
return parseAgentPrompt(await fetchText(entry.downloadUrl), entry.id);
|
|
253
|
+
})), flow, opts);
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Render a task's own inputs into a section, so a fanned-out task (e.g. one
|
|
257
|
+
* `capture` per event) sees the specific thing it owns. Empty when there are none.
|
|
258
|
+
*/
|
|
259
|
+
function renderInputs(task) {
|
|
260
|
+
const entries = Object.entries(task.inputs ?? {});
|
|
261
|
+
if (entries.length === 0) return "";
|
|
262
|
+
return `## Your task input\n\n${entries.map(([k, v]) => `- ${k}: ${formatInputValue(v)}`).join("\n")}`;
|
|
263
|
+
}
|
|
264
|
+
function formatInputValue(value) {
|
|
265
|
+
if (typeof value === "string") return value;
|
|
266
|
+
return JSON.stringify(value);
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* The ids of every task `task` transitively depends on — the full upstream
|
|
270
|
+
* chain, not just direct dependencies — ordered roots-first, each once. A `seen`
|
|
271
|
+
* set dedupes diamonds and guards against cycles.
|
|
272
|
+
*/
|
|
273
|
+
function ancestorIds(task, store) {
|
|
274
|
+
const seen = /* @__PURE__ */ new Set();
|
|
275
|
+
const ordered = [];
|
|
276
|
+
const visit = (id) => {
|
|
277
|
+
if (seen.has(id)) return;
|
|
278
|
+
seen.add(id);
|
|
279
|
+
const t = store.get(id);
|
|
280
|
+
if (!t) return;
|
|
281
|
+
for (const dep of t.dependsOn) visit(dep);
|
|
282
|
+
ordered.push(id);
|
|
283
|
+
};
|
|
284
|
+
for (const dep of task.dependsOn) visit(dep);
|
|
285
|
+
return ordered;
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Render the handoffs of every step `task` transitively depends on into a context
|
|
289
|
+
* section, so a fresh agent sees the whole upstream chain — not just its direct
|
|
290
|
+
* dependencies. Reliability over token economy: a step must never have to
|
|
291
|
+
* re-discover what any ancestor already established just because an intermediate
|
|
292
|
+
* handoff happened to omit it. Empty when there are no completed ancestors.
|
|
293
|
+
*/
|
|
294
|
+
function renderHandoffContext(task, store) {
|
|
295
|
+
const lines = [];
|
|
296
|
+
for (const id of ancestorIds(task, store)) {
|
|
297
|
+
const dep = store.get(id);
|
|
298
|
+
const handoff = store.readHandoff(id);
|
|
299
|
+
if (!dep || !handoff) continue;
|
|
300
|
+
lines.push(`### ${dep.type}`);
|
|
301
|
+
lines.push(`- did: ${handoff.did}`);
|
|
302
|
+
lines.push(`- for you: ${handoff.forNextAgent}`);
|
|
303
|
+
if (handoff.filesTouched?.length) lines.push(`- files: ${handoff.filesTouched.join(", ")}`);
|
|
304
|
+
lines.push("");
|
|
305
|
+
}
|
|
306
|
+
if (lines.length === 0) return "";
|
|
307
|
+
return `## Context from previous steps\n\n${lines.join("\n")}`.trim();
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* Resolve a queued task to its run config: the prompt body (with upstream
|
|
311
|
+
* handoffs appended), the model, and the tool lists with orchestrator tool names
|
|
312
|
+
* MCP-qualified. The model precedence is enqueue override, then prompt, then
|
|
313
|
+
* default. Throws if no prompt is registered for the task's type.
|
|
314
|
+
*/
|
|
315
|
+
function resolveTask(registry, task, store) {
|
|
316
|
+
const prompt = registry.get(task.type);
|
|
317
|
+
if (!prompt) throw new Error(`No agent prompt registered for task type "${task.type}"`);
|
|
318
|
+
const body = [
|
|
319
|
+
renderInputs(task),
|
|
320
|
+
prompt.body,
|
|
321
|
+
renderHandoffContext(task, store)
|
|
322
|
+
].filter(Boolean).join("\n\n");
|
|
323
|
+
return {
|
|
324
|
+
model: taskModel(registry, task),
|
|
325
|
+
...agentRunTools(prompt),
|
|
326
|
+
prompt: body,
|
|
327
|
+
skills: prompt.skills
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
/** The model a task runs on: enqueue override, then prompt frontmatter, then default. */
|
|
331
|
+
function taskModel(registry, task) {
|
|
332
|
+
return task.model ?? registry.get(task.type)?.model ?? DEFAULT_TASK_MODEL;
|
|
333
|
+
}
|
|
334
|
+
//#endregion
|
|
335
|
+
//#region src/lib/agent/runner/orchestrator/orchestrator-runner.ts
|
|
336
|
+
/**
|
|
337
|
+
* Experimental task-queue orchestrator runner.
|
|
338
|
+
*
|
|
339
|
+
* Branches from the linear runner when the `wizard-orchestrator` flag is on. An
|
|
340
|
+
* orchestrator agent inspects the repo and seeds an in-memory task queue; an
|
|
341
|
+
* executor drains it, running one fresh agent per task.
|
|
342
|
+
*
|
|
343
|
+
* Both the WHAT (agent prompts: model, goal, success criteria, tools) and the
|
|
344
|
+
* HOW (mini-skills) are markdown served from context-mill — the seed and every
|
|
345
|
+
* task resolve to a prompt fetched at startup into the registry. The wizard side
|
|
346
|
+
* stays product-ignorant: it is the queue, the executor, and the loader.
|
|
347
|
+
*/
|
|
348
|
+
function toTodoStatus(status) {
|
|
349
|
+
switch (status) {
|
|
350
|
+
case TaskStatus.Running: return "in_progress";
|
|
351
|
+
case TaskStatus.Done:
|
|
352
|
+
case TaskStatus.Failed: return "completed";
|
|
353
|
+
case TaskStatus.Skipped: return "skipped";
|
|
354
|
+
default: return "pending";
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
function sessionRunOptions(session) {
|
|
358
|
+
return {
|
|
359
|
+
installDir: session.installDir,
|
|
360
|
+
debug: session.debug,
|
|
361
|
+
default: false,
|
|
362
|
+
signup: session.signup,
|
|
363
|
+
localMcp: session.localMcp,
|
|
364
|
+
ci: session.ci,
|
|
365
|
+
benchmark: session.benchmark,
|
|
366
|
+
projectId: session.projectId,
|
|
367
|
+
apiKey: session.apiKey,
|
|
368
|
+
yaraReport: session.yaraReport
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* The framework reference is the full `integration` skill. `session.skillId` is
|
|
373
|
+
* the bare framework (e.g. `django`), but the skill menu ids it as
|
|
374
|
+
* `integration-<variant>`. Resolve to the menu id: exact `integration-<framework>`
|
|
375
|
+
* (the 1:1 frameworks — django, python, flask, …), else the first granular variant
|
|
376
|
+
* under it (e.g. `integration-nextjs-app-router`). Undefined when none exists.
|
|
377
|
+
*/
|
|
378
|
+
async function resolveReferenceSkillId(skillsBaseUrl, framework) {
|
|
379
|
+
const menu = await fetchSkillMenu(skillsBaseUrl);
|
|
380
|
+
if (!menu) return void 0;
|
|
381
|
+
const ids = Object.values(menu.categories).flat().map((s) => s.id);
|
|
382
|
+
const exact = `integration-${framework}`;
|
|
383
|
+
if (ids.includes(exact)) return exact;
|
|
384
|
+
return ids.find((id) => id.startsWith(`integration-${framework}-`));
|
|
385
|
+
}
|
|
386
|
+
async function runOrchestrator(session, programConfig, boot) {
|
|
387
|
+
const runId = randomUUID();
|
|
388
|
+
const options = sessionRunOptions(session);
|
|
389
|
+
const registry = await loadAgentRegistry(boot.skillsBaseUrl, programConfig.id, { exclude: ciExcludedTaskTypes() });
|
|
390
|
+
const seedPrompt = registry.seed;
|
|
391
|
+
if (!seedPrompt) throw new Error(`No seed agent prompt (frontmatter \`seed: true\`) for flow "${programConfig.id}" is available from ${boot.skillsBaseUrl}.`);
|
|
392
|
+
analytics.setTag("variant", "orchestrator");
|
|
393
|
+
const runStartMs = Date.now();
|
|
394
|
+
const metrics = new RunMetrics(runStartMs);
|
|
395
|
+
const durationMs = (t) => t.startedAt && t.finishedAt ? Date.parse(t.finishedAt) - Date.parse(t.startedAt) : void 0;
|
|
396
|
+
const store = new QueueStore(session.installDir, runId, { onTransition: (event, task) => {
|
|
397
|
+
const base = {
|
|
398
|
+
type: task.type,
|
|
399
|
+
model: taskModel(registry, task),
|
|
400
|
+
attempts: task.attempts
|
|
401
|
+
};
|
|
402
|
+
switch (event) {
|
|
403
|
+
case "enqueue":
|
|
404
|
+
analytics.wizardCapture("orchestrator task enqueued", {
|
|
405
|
+
type: task.type,
|
|
406
|
+
enqueued_by: task.enqueuedBy,
|
|
407
|
+
dynamic: task.enqueuedBy !== "orchestrator"
|
|
408
|
+
});
|
|
409
|
+
break;
|
|
410
|
+
case "start":
|
|
411
|
+
analytics.wizardCapture("orchestrator task started", {
|
|
412
|
+
...base,
|
|
413
|
+
...metrics.recordStart(Date.now())
|
|
414
|
+
});
|
|
415
|
+
break;
|
|
416
|
+
case "complete":
|
|
417
|
+
metrics.recordComplete(Date.now());
|
|
418
|
+
analytics.wizardCapture("orchestrator task completed", {
|
|
419
|
+
...base,
|
|
420
|
+
duration_ms: durationMs(task)
|
|
421
|
+
});
|
|
422
|
+
break;
|
|
423
|
+
case "skip":
|
|
424
|
+
metrics.recordTerminal(Date.now());
|
|
425
|
+
analytics.wizardCapture("orchestrator task skipped", {
|
|
426
|
+
...base,
|
|
427
|
+
duration_ms: durationMs(task)
|
|
428
|
+
});
|
|
429
|
+
break;
|
|
430
|
+
case "fail":
|
|
431
|
+
metrics.recordTerminal(Date.now());
|
|
432
|
+
analytics.wizardCapture("orchestrator task failed", {
|
|
433
|
+
...base,
|
|
434
|
+
duration_ms: durationMs(task),
|
|
435
|
+
error: task.error?.type
|
|
436
|
+
});
|
|
437
|
+
break;
|
|
438
|
+
case "requeue": break;
|
|
439
|
+
}
|
|
440
|
+
} });
|
|
441
|
+
let examplePath;
|
|
442
|
+
let commandmentsPath;
|
|
443
|
+
const referenceSkillId = session.skillId ? await resolveReferenceSkillId(boot.skillsBaseUrl, session.skillId) : void 0;
|
|
444
|
+
if (referenceSkillId) {
|
|
445
|
+
const ref = await installSkillById(referenceSkillId, session.installDir, boot.skillsBaseUrl, path$1.join(QUEUE_DIR_NAME, "reference"));
|
|
446
|
+
if (ref.kind === "ok") {
|
|
447
|
+
const example = path$1.join(ref.path, "references", "EXAMPLE.md");
|
|
448
|
+
if (existsSync(path$1.join(session.installDir, example))) examplePath = example;
|
|
449
|
+
const commandments = path$1.join(ref.path, "references", "COMMANDMENTS.md");
|
|
450
|
+
if (existsSync(path$1.join(session.installDir, commandments))) commandmentsPath = commandments;
|
|
451
|
+
} else logToFile(`[orchestrator] reference unavailable: ${ref.kind} (${referenceSkillId})`);
|
|
452
|
+
} else if (session.skillId) logToFile(`[orchestrator] no integration skill for framework "${session.skillId}"`);
|
|
453
|
+
const promptContext = {
|
|
454
|
+
projectId: boot.projectId,
|
|
455
|
+
projectApiKey: boot.projectApiKey,
|
|
456
|
+
host: boot.host,
|
|
457
|
+
examplePath,
|
|
458
|
+
commandmentsPath
|
|
459
|
+
};
|
|
460
|
+
logToFile(`[orchestrator] START program=${programConfig.id} dir=${session.installDir} run=${runId}`);
|
|
461
|
+
analytics.wizardCapture("orchestrator started", { program_id: programConfig.id });
|
|
462
|
+
getUI().startRun();
|
|
463
|
+
const labelFor = (t) => t.label ?? registry.get(t.type)?.label ?? t.type;
|
|
464
|
+
const renderQueue = () => getUI().syncTodos(store.list().map((t) => ({
|
|
465
|
+
content: labelFor(t),
|
|
466
|
+
status: toTodoStatus(t.status),
|
|
467
|
+
activeForm: labelFor(t)
|
|
468
|
+
})));
|
|
469
|
+
const agentConfigFor = (currentTaskId) => ({
|
|
470
|
+
workingDirectory: session.installDir,
|
|
471
|
+
posthogMcpUrl: boot.mcpUrl,
|
|
472
|
+
posthogApiKey: boot.accessToken,
|
|
473
|
+
posthogApiHost: boot.host,
|
|
474
|
+
detectPackageManager: detectNodePackageManagers,
|
|
475
|
+
skillsBaseUrl: boot.skillsBaseUrl,
|
|
476
|
+
wizardFlags: boot.wizardFlags,
|
|
477
|
+
wizardMetadata: {
|
|
478
|
+
...boot.wizardMetadata,
|
|
479
|
+
VARIANT: "orchestrator"
|
|
480
|
+
},
|
|
481
|
+
integrationLabel: programConfig.id,
|
|
482
|
+
orchestrator: {
|
|
483
|
+
store,
|
|
484
|
+
validTypes: registry.types,
|
|
485
|
+
currentTaskId
|
|
486
|
+
}
|
|
487
|
+
});
|
|
488
|
+
const spinner = getUI().spinner();
|
|
489
|
+
const seedAgent = await initializeAgent(agentConfigFor(), options);
|
|
490
|
+
const seedResult = await runAgent$1({
|
|
491
|
+
...seedAgent,
|
|
492
|
+
model: seedPrompt.model ?? seedAgent.model,
|
|
493
|
+
...agentRunTools(seedPrompt)
|
|
494
|
+
}, assembleSeedPrompt(promptContext, seedPrompt.body), options, spinner, {
|
|
495
|
+
spinnerMessage: "Planning the integration...",
|
|
496
|
+
successMessage: "Planned the integration",
|
|
497
|
+
additionalFeatureQueue: [],
|
|
498
|
+
requestRemark: false,
|
|
499
|
+
analyticsProperties: { task_type: "seed" }
|
|
500
|
+
});
|
|
501
|
+
if (seedResult.error) logToFile(`[orchestrator] seed error: ${seedResult.error} ${seedResult.message ?? ""}`);
|
|
502
|
+
analytics.wizardCapture("orchestrator seeded", {
|
|
503
|
+
task_count: store.list().length,
|
|
504
|
+
types: store.list().map((t) => t.type)
|
|
505
|
+
});
|
|
506
|
+
renderQueue();
|
|
507
|
+
const taskSkillsRoot = path$1.join(QUEUE_DIR_NAME, "skills");
|
|
508
|
+
let remarkRequested = false;
|
|
509
|
+
const runTask = async (task) => {
|
|
510
|
+
renderQueue();
|
|
511
|
+
try {
|
|
512
|
+
const resolved = resolveTask(registry, task, store);
|
|
513
|
+
const agent = await initializeAgent(agentConfigFor(task.id), options);
|
|
514
|
+
const skillPaths = [];
|
|
515
|
+
for (const skillId of resolved.skills) {
|
|
516
|
+
const result = await installSkillById(skillId, session.installDir, boot.skillsBaseUrl, taskSkillsRoot);
|
|
517
|
+
if (result.kind === "ok") skillPaths.push(path$1.join(result.path, "SKILL.md"));
|
|
518
|
+
else logToFile(`[orchestrator] skill install failed type=${task.type} skill=${skillId} ${result.kind}`);
|
|
519
|
+
}
|
|
520
|
+
const requestRemark = !store.list().some((t) => t.id !== task.id && (t.status === TaskStatus.Pending || t.status === TaskStatus.Running)) && !remarkRequested;
|
|
521
|
+
if (requestRemark) remarkRequested = true;
|
|
522
|
+
await runAgent$1({
|
|
523
|
+
...agent,
|
|
524
|
+
model: resolved.model,
|
|
525
|
+
allowedTools: resolved.allowedTools,
|
|
526
|
+
disallowedTools: resolved.disallowedTools
|
|
527
|
+
}, assembleTaskPrompt(promptContext, resolved.prompt, skillPaths), options, spinner, {
|
|
528
|
+
spinnerMessage: "",
|
|
529
|
+
successMessage: "",
|
|
530
|
+
additionalFeatureQueue: [],
|
|
531
|
+
requestRemark,
|
|
532
|
+
analyticsProperties: {
|
|
533
|
+
task_type: task.type,
|
|
534
|
+
task_id: task.id
|
|
535
|
+
}
|
|
536
|
+
});
|
|
537
|
+
} finally {
|
|
538
|
+
renderQueue();
|
|
539
|
+
}
|
|
540
|
+
};
|
|
541
|
+
try {
|
|
542
|
+
await drainQueue(store, runTask);
|
|
543
|
+
} finally {
|
|
544
|
+
try {
|
|
545
|
+
rmSync(path$1.join(session.installDir, QUEUE_DIR_NAME), {
|
|
546
|
+
recursive: true,
|
|
547
|
+
force: true
|
|
548
|
+
});
|
|
549
|
+
} catch (err) {
|
|
550
|
+
analytics.captureException(err instanceof Error ? err : new Error(String(err)), { step: "orchestrator_cache_cleanup" });
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
renderQueue();
|
|
554
|
+
const summary = store.summary();
|
|
555
|
+
logToFile(`[orchestrator] DONE done=${summary.done} failed=${summary.failed} total=${summary.total}`);
|
|
556
|
+
analytics.wizardCapture("orchestrator run finished", {
|
|
557
|
+
tasks_total: summary.total,
|
|
558
|
+
tasks_done: summary.done,
|
|
559
|
+
tasks_failed: summary.failed,
|
|
560
|
+
tasks_skipped: summary.skipped,
|
|
561
|
+
total_duration_ms: Date.now() - runStartMs,
|
|
562
|
+
...metrics.summary(),
|
|
563
|
+
dynamic_enqueue_count: store.list().filter((t) => t.enqueuedBy !== "orchestrator").length,
|
|
564
|
+
retried_task_count: store.list().filter((t) => t.attempts > 1).length
|
|
565
|
+
});
|
|
566
|
+
const buildTask = store.list().find((t) => t.type === "build");
|
|
567
|
+
const conflict = buildTask ? store.readHandoff(buildTask.id)?.conflict : void 0;
|
|
568
|
+
const reportFile = existsSync(path$1.join(session.installDir, "posthog-setup-report.md")) ? "posthog-setup-report.md" : store.queuePath;
|
|
569
|
+
const message = conflict ? "PostHog set up, with one conflict to review." : `PostHog set up: ${summary.done}/${summary.total} steps completed.`;
|
|
570
|
+
getUI().setOutroData({
|
|
571
|
+
kind: "success",
|
|
572
|
+
message,
|
|
573
|
+
body: conflict ? `⚠ Build conflict: ${conflict}\nFull details are in the report.` : void 0,
|
|
574
|
+
reportFile,
|
|
575
|
+
docsUrl: "https://posthog.com/docs/ai-engineering/ai-wizard"
|
|
576
|
+
});
|
|
577
|
+
getUI().outro(message);
|
|
578
|
+
await analytics.shutdown("success");
|
|
579
|
+
}
|
|
580
|
+
//#endregion
|
|
581
|
+
//#region src/lib/agent/runner/shared/bootstrap.ts
|
|
582
|
+
/**
|
|
583
|
+
* Decide whether the `wizard_ask` overlay should be wired for this run.
|
|
584
|
+
* Disabled in non-interactive modes (CI, signup) — there's no human to
|
|
585
|
+
* answer. Per-program disabling is done by adding WIZARD_ASK_TOOL_NAME to
|
|
586
|
+
* the program's `disallowedTools` so the SDK rejects calls outright.
|
|
587
|
+
* Extracted so the policy can be unit-tested directly.
|
|
588
|
+
*/
|
|
589
|
+
function shouldDisableAsk(session) {
|
|
590
|
+
return session.ci || session.signup;
|
|
591
|
+
}
|
|
592
|
+
function sessionToOptions(session) {
|
|
593
|
+
return {
|
|
594
|
+
installDir: session.installDir,
|
|
595
|
+
debug: session.debug,
|
|
596
|
+
default: false,
|
|
597
|
+
signup: session.signup,
|
|
598
|
+
localMcp: session.localMcp,
|
|
599
|
+
ci: session.ci,
|
|
600
|
+
benchmark: session.benchmark,
|
|
601
|
+
projectId: session.projectId,
|
|
602
|
+
apiKey: session.apiKey,
|
|
603
|
+
yaraReport: session.yaraReport
|
|
604
|
+
};
|
|
605
|
+
}
|
|
606
|
+
/**
|
|
607
|
+
* Shared setup for both arms: logging, health check, settings conflicts, OAuth
|
|
608
|
+
* and credentials, then the feature flags, variant metadata, and MCP url. Sets
|
|
609
|
+
* `session.credentials`, role, and user as a side effect. Returns the values the
|
|
610
|
+
* arms still need.
|
|
611
|
+
*/
|
|
612
|
+
async function bootstrapProgram(session, config, programConfig) {
|
|
613
|
+
initLogFile();
|
|
614
|
+
session.skillId = config.skillId ?? config.integrationLabel;
|
|
615
|
+
logToFile(`[agent-runner] START ${config.integrationLabel}`);
|
|
616
|
+
if (session.debug) enableDebugLogs();
|
|
617
|
+
const skillsBaseUrl = getSkillsBaseUrl(session.localMcp);
|
|
618
|
+
const hasHealthCheckScreen = programConfig.steps.some((s) => s.screenId === "health-check");
|
|
619
|
+
if (session.readinessResult) logToFile(`[agent-runner] readiness pre-computed by TUI: decision=${session.readinessResult.decision}${session.outageDismissed ? " (outage dismissed by user)" : ""} — skipping re-check`);
|
|
620
|
+
if (hasHealthCheckScreen && !session.readinessResult) {
|
|
621
|
+
logToFile("[agent-runner] evaluating wizard readiness");
|
|
622
|
+
const readinessConfig = session.signup ? SIGNUP_WIZARD_READINESS_CONFIG : void 0;
|
|
623
|
+
const readiness = await evaluateWizardReadiness(readinessConfig);
|
|
624
|
+
logToFile(`[agent-runner] readiness=${readiness.decision}`);
|
|
625
|
+
if (readiness.decision === "no") {
|
|
626
|
+
const blockingLabels = getBlockingServiceKeys(readiness.health, readinessConfig).map((k) => `${SERVICE_LABELS[k]} (${readiness.health[k].status})`);
|
|
627
|
+
logToFile(`[agent-runner] blocked by: ${blockingLabels.join(", ")}`);
|
|
628
|
+
await getUI().showBlockingOutage(readiness);
|
|
629
|
+
if (!isNonInteractiveEnvironment()) await wizardAbort({ message: "Cannot start — external services are down:\n" + blockingLabels.map((l) => ` - ${l}`).join("\n") + "\n\nPlease try again later." });
|
|
630
|
+
} else if (readiness.decision === "yes_with_warnings") getUI().setReadinessWarnings(readiness);
|
|
631
|
+
}
|
|
632
|
+
const settingsConflicts = checkAllSettingsConflicts(session.installDir);
|
|
633
|
+
logToFile(`[agent-runner] settings conflicts: ${settingsConflicts.length > 0 ? settingsConflicts.map((c) => `${c.source}(${c.keys.join(",")})`).join("; ") : "none"}`);
|
|
634
|
+
if (settingsConflicts.length > 0) {
|
|
635
|
+
for (const conflict of settingsConflicts) {
|
|
636
|
+
const level = conflict.source === "managed" ? "org" : conflict.source;
|
|
637
|
+
analytics.wizardCapture("settings conflict detected", {
|
|
638
|
+
level,
|
|
639
|
+
keys: conflict.keys
|
|
640
|
+
});
|
|
641
|
+
}
|
|
642
|
+
await getUI().showSettingsOverride(settingsConflicts, () => backupAndFixClaudeSettings(session.installDir));
|
|
643
|
+
logToFile("[agent-runner] settings override resolved");
|
|
644
|
+
}
|
|
645
|
+
analytics.wizardCapture("agent started", {
|
|
646
|
+
integration: config.integrationLabel,
|
|
647
|
+
program_id: programConfig.id,
|
|
648
|
+
skill_id: config.skillId ?? null
|
|
649
|
+
});
|
|
650
|
+
logToFile("[agent-runner] starting OAuth");
|
|
651
|
+
const { projectApiKey, host, accessToken, projectId, cloudRegion, roleAtOrganization, user, project } = await getOrAskForProjectData({
|
|
652
|
+
signup: session.signup,
|
|
653
|
+
ci: session.ci,
|
|
654
|
+
apiKey: session.apiKey,
|
|
655
|
+
projectId: session.projectId,
|
|
656
|
+
email: session.email,
|
|
657
|
+
region: session.region,
|
|
658
|
+
programId: programConfig.id
|
|
659
|
+
});
|
|
660
|
+
session.credentials = {
|
|
661
|
+
accessToken,
|
|
662
|
+
projectApiKey,
|
|
663
|
+
host,
|
|
664
|
+
projectId
|
|
665
|
+
};
|
|
666
|
+
session.roleAtOrganization = roleAtOrganization;
|
|
667
|
+
session.apiUser = user;
|
|
668
|
+
getUI().setCredentials(session.credentials);
|
|
669
|
+
getUI().setRoleAtOrganization(roleAtOrganization);
|
|
670
|
+
getUI().setApiUser(user);
|
|
671
|
+
if (user) analytics.identifyUser(user);
|
|
672
|
+
analytics.setGroups(groupsFromUser(user, host));
|
|
673
|
+
logToFile("[agent-runner] checking AI opt-in gate");
|
|
674
|
+
await getUI().waitForAiOptIn();
|
|
675
|
+
logToFile("[agent-runner] AI opt-in gate cleared");
|
|
676
|
+
const wizardFlags = await analytics.getAllFlagsForWizard();
|
|
677
|
+
const wizardMetadata = buildWizardMetadata(wizardFlags);
|
|
678
|
+
analytics.setTag("variant", wizardMetadata.VARIANT);
|
|
679
|
+
return {
|
|
680
|
+
skillsBaseUrl,
|
|
681
|
+
projectApiKey,
|
|
682
|
+
host,
|
|
683
|
+
accessToken,
|
|
684
|
+
projectId,
|
|
685
|
+
cloudRegion,
|
|
686
|
+
mcpUrl: session.localMcp ? "http://localhost:8787/mcp" : runtimeEnv("MCP_URL") || "https://mcp.posthog.com/mcp",
|
|
687
|
+
wizardFlags,
|
|
688
|
+
wizardMetadata,
|
|
689
|
+
project
|
|
690
|
+
};
|
|
691
|
+
}
|
|
692
|
+
//#endregion
|
|
11
693
|
//#region src/lib/middleware/phase-detector.ts
|
|
12
694
|
/** Phase transitions from [STATUS] in assistant text. Keep in sync with program "Status to report" bullets. */
|
|
13
695
|
const PHASES_ORDER = [
|
|
@@ -852,7 +1534,8 @@ function createWizardAskBridge(opts) {
|
|
|
852
1534
|
const pending = {
|
|
853
1535
|
id: randomUUID(),
|
|
854
1536
|
questions,
|
|
855
|
-
source: opts.getSource()
|
|
1537
|
+
source: opts.getSource(),
|
|
1538
|
+
richLinks: opts.richLinks ?? false
|
|
856
1539
|
};
|
|
857
1540
|
const startedAt = Date.now();
|
|
858
1541
|
let timer;
|
|
@@ -912,110 +1595,27 @@ function assemblePrompt(runDef, ctx) {
|
|
|
912
1595
|
return parts.join("\n\n");
|
|
913
1596
|
}
|
|
914
1597
|
//#endregion
|
|
915
|
-
//#region src/lib/agent/
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
}
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
default: false,
|
|
931
|
-
signup: session.signup,
|
|
932
|
-
localMcp: session.localMcp,
|
|
933
|
-
ci: session.ci,
|
|
934
|
-
benchmark: session.benchmark,
|
|
935
|
-
projectId: session.projectId,
|
|
936
|
-
apiKey: session.apiKey,
|
|
937
|
-
yaraReport: session.yaraReport
|
|
938
|
-
};
|
|
939
|
-
}
|
|
940
|
-
/**
|
|
941
|
-
* Resolve a ProgramConfig's agent run definition and execute the pipeline.
|
|
942
|
-
* Entry point for bin.ts — handles buildRunConfig, bootstrap, and (future) run field.
|
|
943
|
-
*/
|
|
944
|
-
async function runAgent(programConfig, session) {
|
|
945
|
-
if (!programConfig.run) throw new Error(`Program "${programConfig.id}" has no run configuration.`);
|
|
946
|
-
await runProgram(session, typeof programConfig.run === "function" ? await programConfig.run(session) : programConfig.run, programConfig);
|
|
947
|
-
}
|
|
948
|
-
/**
|
|
949
|
-
* Run a program's agent pipeline.
|
|
950
|
-
*
|
|
951
|
-
* This is the single execution path for all programs — both skill-based
|
|
952
|
-
* (revenue analytics) and framework-based (core integration). The
|
|
953
|
-
* `ProgramRun` controls what varies between them; `programConfig` carries
|
|
954
|
-
* the program-level static metadata (tool allow/disallow lists, etc.).
|
|
955
|
-
*/
|
|
956
|
-
async function runProgram(session, config, programConfig) {
|
|
957
|
-
initLogFile();
|
|
958
|
-
session.skillId = config.skillId ?? config.integrationLabel;
|
|
959
|
-
logToFile(`[agent-runner] START ${config.integrationLabel}`);
|
|
960
|
-
if (session.debug) enableDebugLogs();
|
|
961
|
-
const skillsBaseUrl = getSkillsBaseUrl(session.localMcp);
|
|
962
|
-
const hasHealthCheckScreen = programConfig.steps.some((s) => s.screenId === "health-check");
|
|
963
|
-
if (session.readinessResult) logToFile(`[agent-runner] readiness pre-computed by TUI: decision=${session.readinessResult.decision}${session.outageDismissed ? " (outage dismissed by user)" : ""} — skipping re-check`);
|
|
964
|
-
if (hasHealthCheckScreen && !session.readinessResult) {
|
|
965
|
-
logToFile("[agent-runner] evaluating wizard readiness");
|
|
966
|
-
const readinessConfig = session.signup ? SIGNUP_WIZARD_READINESS_CONFIG : void 0;
|
|
967
|
-
const readiness = await evaluateWizardReadiness(readinessConfig);
|
|
968
|
-
logToFile(`[agent-runner] readiness=${readiness.decision}`);
|
|
969
|
-
if (readiness.decision === "no") {
|
|
970
|
-
const blockingLabels = getBlockingServiceKeys(readiness.health, readinessConfig).map((k) => `${SERVICE_LABELS[k]} (${readiness.health[k].status})`);
|
|
971
|
-
logToFile(`[agent-runner] blocked by: ${blockingLabels.join(", ")}`);
|
|
972
|
-
await getUI().showBlockingOutage(readiness);
|
|
973
|
-
await wizardAbort({ message: "Cannot start — external services are down:\n" + blockingLabels.map((l) => ` - ${l}`).join("\n") + "\n\nPlease try again later." });
|
|
974
|
-
} else if (readiness.decision === "yes_with_warnings") getUI().setReadinessWarnings(readiness);
|
|
975
|
-
}
|
|
976
|
-
const settingsConflicts = checkAllSettingsConflicts(session.installDir);
|
|
977
|
-
logToFile(`[agent-runner] settings conflicts: ${settingsConflicts.length > 0 ? settingsConflicts.map((c) => `${c.source}(${c.keys.join(",")})`).join("; ") : "none"}`);
|
|
978
|
-
if (settingsConflicts.length > 0) {
|
|
979
|
-
for (const conflict of settingsConflicts) {
|
|
980
|
-
const level = conflict.source === "managed" ? "org" : conflict.source;
|
|
981
|
-
analytics.wizardCapture("settings conflict detected", {
|
|
982
|
-
level,
|
|
983
|
-
keys: conflict.keys
|
|
984
|
-
});
|
|
985
|
-
}
|
|
986
|
-
await getUI().showSettingsOverride(settingsConflicts, () => backupAndFixClaudeSettings(session.installDir));
|
|
987
|
-
logToFile("[agent-runner] settings override resolved");
|
|
988
|
-
}
|
|
989
|
-
analytics.wizardCapture("agent started", {
|
|
990
|
-
integration: config.integrationLabel,
|
|
991
|
-
program_id: programConfig.id,
|
|
992
|
-
skill_id: config.skillId ?? null
|
|
993
|
-
});
|
|
994
|
-
logToFile("[agent-runner] starting OAuth");
|
|
995
|
-
const { projectApiKey, host, accessToken, projectId, cloudRegion, roleAtOrganization, user } = await getOrAskForProjectData({
|
|
996
|
-
signup: session.signup,
|
|
997
|
-
ci: session.ci,
|
|
998
|
-
apiKey: session.apiKey,
|
|
999
|
-
projectId: session.projectId,
|
|
1000
|
-
email: session.email,
|
|
1001
|
-
region: session.region,
|
|
1002
|
-
programId: programConfig.id
|
|
1598
|
+
//#region src/lib/agent/runner/shared/errors.ts
|
|
1599
|
+
async function abortOnInstallFailure(integrationLabel, result) {
|
|
1600
|
+
if (result.kind === "ok") return;
|
|
1601
|
+
await wizardAbort({
|
|
1602
|
+
message: (() => {
|
|
1603
|
+
switch (result.kind) {
|
|
1604
|
+
case "menu-fetch-failed": return "Could not fetch the skill menu from context-mill.\nCheck your network connection and try again.";
|
|
1605
|
+
case "skill-not-found": return `Could not find the "${result.skillId}" skill in the context-mill menu.\nPlease try again later.`;
|
|
1606
|
+
case "download-failed": return `Failed to install skill: ${result.message}\nPlease try again.`;
|
|
1607
|
+
}
|
|
1608
|
+
})(),
|
|
1609
|
+
error: new WizardError(`Skill install failed: ${result.kind}`, {
|
|
1610
|
+
integration: integrationLabel,
|
|
1611
|
+
error_type: result.kind
|
|
1612
|
+
})
|
|
1003
1613
|
});
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
};
|
|
1010
|
-
session.roleAtOrganization = roleAtOrganization;
|
|
1011
|
-
session.apiUser = user;
|
|
1012
|
-
getUI().setCredentials(session.credentials);
|
|
1013
|
-
getUI().setRoleAtOrganization(roleAtOrganization);
|
|
1014
|
-
getUI().setApiUser(user);
|
|
1015
|
-
analytics.setGroups(groupsFromUser(user, host));
|
|
1016
|
-
logToFile("[agent-runner] checking AI opt-in gate");
|
|
1017
|
-
await getUI().waitForAiOptIn();
|
|
1018
|
-
logToFile("[agent-runner] AI opt-in gate cleared");
|
|
1614
|
+
}
|
|
1615
|
+
//#endregion
|
|
1616
|
+
//#region src/lib/agent/runner/linear.ts
|
|
1617
|
+
async function runLinearProgram(session, config, programConfig, boot) {
|
|
1618
|
+
const { skillsBaseUrl, projectApiKey, host, accessToken, projectId, cloudRegion, mcpUrl, wizardFlags, wizardMetadata, project } = boot;
|
|
1019
1619
|
let skillPath;
|
|
1020
1620
|
if (config.skillId) {
|
|
1021
1621
|
logToFile(`[agent-runner] installing skill ${config.skillId}`);
|
|
@@ -1028,9 +1628,6 @@ async function runProgram(session, config, programConfig) {
|
|
|
1028
1628
|
logToFile(`[agent-runner] skill installed at ${skillPath}`);
|
|
1029
1629
|
}
|
|
1030
1630
|
const spinner = getUI().spinner();
|
|
1031
|
-
const wizardFlags = await analytics.getAllFlagsForWizard();
|
|
1032
|
-
const wizardMetadata = buildWizardMetadata(wizardFlags);
|
|
1033
|
-
const mcpUrl = session.localMcp ? "http://localhost:8787/mcp" : runtimeEnv("MCP_URL") || (cloudRegion === "eu" ? "https://mcp-eu.posthog.com/mcp" : "https://mcp.posthog.com/mcp");
|
|
1034
1631
|
const restoreSettings = () => restoreClaudeSettings(session.installDir);
|
|
1035
1632
|
getUI().onEnterScreen("outro", restoreSettings);
|
|
1036
1633
|
if (session.yaraReport) registerCleanup(() => {
|
|
@@ -1044,8 +1641,10 @@ async function runProgram(session, config, programConfig) {
|
|
|
1044
1641
|
const askBridge = shouldDisableAsk(session) ? void 0 : createWizardAskBridge({
|
|
1045
1642
|
getSource: () => session.skillId ?? config.integrationLabel,
|
|
1046
1643
|
showQuestion: (q) => getUI().requestQuestion(q),
|
|
1644
|
+
richLinks: config.richLinks ?? false,
|
|
1047
1645
|
timeoutMs: config.askTimeoutMs
|
|
1048
1646
|
});
|
|
1647
|
+
getUI().log.step("Initializing Claude agent...");
|
|
1049
1648
|
const agent = await initializeAgent({
|
|
1050
1649
|
workingDirectory: session.installDir,
|
|
1051
1650
|
posthogMcpUrl: mcpUrl,
|
|
@@ -1063,13 +1662,21 @@ async function runProgram(session, config, programConfig) {
|
|
|
1063
1662
|
disallowedTools: programConfig.disallowedTools,
|
|
1064
1663
|
getPendingQuestion: () => session.pendingQuestion
|
|
1065
1664
|
}, sessionToOptions(session));
|
|
1665
|
+
getUI().log.step(`Verbose logs: ${getLogFilePath()}`);
|
|
1666
|
+
getUI().log.success("Agent initialized. Let's get cooking!");
|
|
1066
1667
|
logToFile("[agent-runner] agent initialized");
|
|
1067
1668
|
const middleware = session.benchmark ? createBenchmarkPipeline(spinner, sessionToOptions(session)) : void 0;
|
|
1068
1669
|
const prompt = assemblePrompt(config, {
|
|
1069
1670
|
projectId,
|
|
1070
1671
|
projectApiKey,
|
|
1071
1672
|
host,
|
|
1072
|
-
skillPath
|
|
1673
|
+
skillPath,
|
|
1674
|
+
orgAiDataProcessingApproved: session.apiUser?.organization?.is_ai_data_processing_approved ?? null,
|
|
1675
|
+
teamProductOptIns: project ? {
|
|
1676
|
+
sessionReplay: project.session_recording_opt_in ?? null,
|
|
1677
|
+
exceptionAutocapture: project.autocapture_exceptions_opt_in ?? null,
|
|
1678
|
+
surveys: project.surveys_opt_in ?? null
|
|
1679
|
+
} : null
|
|
1073
1680
|
});
|
|
1074
1681
|
logToFile(`[agent-runner] prompt assembled (${prompt.length} chars)`);
|
|
1075
1682
|
const agentResult = await runAgent$1(agent, prompt, sessionToOptions(session), spinner, {
|
|
@@ -1176,23 +1783,32 @@ Please try again, or check the documentation:\n${config.docsUrl}`,
|
|
|
1176
1783
|
getUI().outro(config.successMessage);
|
|
1177
1784
|
await analytics.shutdown("success");
|
|
1178
1785
|
}
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1786
|
+
//#endregion
|
|
1787
|
+
//#region src/lib/agent/runner/index.ts
|
|
1788
|
+
/**
|
|
1789
|
+
* Resolve a ProgramConfig's agent run definition and execute the pipeline.
|
|
1790
|
+
* Entry point for bin.ts — handles buildRunConfig, bootstrap, and (future) run field.
|
|
1791
|
+
*/
|
|
1792
|
+
async function runAgent(programConfig, session) {
|
|
1793
|
+
if (!programConfig.run) throw new Error(`Program "${programConfig.id}" has no run configuration.`);
|
|
1794
|
+
await runProgram(session, typeof programConfig.run === "function" ? await programConfig.run(session) : programConfig.run, programConfig);
|
|
1795
|
+
}
|
|
1796
|
+
/**
|
|
1797
|
+
* Run a program's agent pipeline.
|
|
1798
|
+
*
|
|
1799
|
+
* Runs the shared bootstrap, then forks on the `wizard-variant` flag. The
|
|
1800
|
+
* `orchestrator` variant routes to the experimental task-queue runner; every
|
|
1801
|
+
* other variant runs the linear pipeline.
|
|
1802
|
+
*/
|
|
1803
|
+
async function runProgram(session, config, programConfig) {
|
|
1804
|
+
const boot = await bootstrapProgram(session, config, programConfig);
|
|
1805
|
+
if (isOrchestratorEnabled(boot.wizardFlags)) {
|
|
1806
|
+
getUI().log.info("Task-queue orchestrator enabled.");
|
|
1807
|
+
return runOrchestrator(session, programConfig, boot);
|
|
1808
|
+
}
|
|
1809
|
+
return runLinearProgram(session, config, programConfig, boot);
|
|
1194
1810
|
}
|
|
1195
1811
|
//#endregion
|
|
1196
1812
|
export { runAgent };
|
|
1197
1813
|
|
|
1198
|
-
//# sourceMappingURL=agent-runner-
|
|
1814
|
+
//# sourceMappingURL=agent-runner-BNGW3osc.js.map
|