@posthog/wizard 2.80.0 → 2.80.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +187 -57
- package/dist/{PickerMenu-m4CCPX4f.js → PickerMenu-BGGeYqd-.js} +104 -4
- package/dist/PickerMenu-BGGeYqd-.js.map +1 -0
- package/dist/README.md +9 -6
- package/dist/{agent-interface-CzyCmp7q.js → agent-interface-CemBvTAA.js} +115 -346
- package/dist/agent-interface-CemBvTAA.js.map +1 -0
- package/dist/{agent-prompt-loader-4wMxlYSX.js → agent-prompt-loader-DPnj6SwU.js} +6 -5
- package/dist/agent-prompt-loader-DPnj6SwU.js.map +1 -0
- package/dist/analytics-B3ROGuIr.js +2 -0
- package/dist/analytics-D21z3E5l.js +732 -0
- package/dist/analytics-D21z3E5l.js.map +1 -0
- package/dist/{api-C3Dc4LFl.js → api-DtGIqQ2U.js} +2 -3
- package/dist/api-DtGIqQ2U.js.map +1 -0
- package/dist/api-IhROOQNv.js +2 -0
- package/dist/api-key-login-OEntFvu5.js +2 -0
- package/dist/api-key-login-h3E2G3zu.js +55 -0
- package/dist/api-key-login-h3E2G3zu.js.map +1 -0
- package/dist/bin.js +2 -2
- package/dist/bin.js.map +1 -1
- package/dist/{bounded-fs-CC_NDtoP.js → bounded-fs-CAsuF6al.js} +3 -3
- package/dist/{bounded-fs-CC_NDtoP.js.map → bounded-fs-CAsuF6al.js.map} +1 -1
- package/dist/{ci-install-DfcAMhlu.js → ci-install-CEYE02t-.js} +21 -25
- package/dist/ci-install-CEYE02t-.js.map +1 -0
- package/dist/console-log-O828zyiq.js +32 -0
- package/dist/console-log-O828zyiq.js.map +1 -0
- package/dist/{debug-DyARf5TF.js → debug-BfQPD2Hg.js} +18 -52
- package/dist/debug-BfQPD2Hg.js.map +1 -0
- package/dist/{defaults-DpfMnH2q.js → defaults-DDVl3tKN.js} +2 -51
- package/dist/defaults-DDVl3tKN.js.map +1 -0
- package/dist/{emit-BSetXzDV.js → emit-RfAewcWv.js} +1 -1
- package/dist/{emit-BSetXzDV.js.map → emit-RfAewcWv.js.map} +1 -1
- package/dist/{errors-C2domSkK.js → errors-CHrJzAZb.js} +4 -4
- package/dist/{errors-C2domSkK.js.map → errors-CHrJzAZb.js.map} +1 -1
- package/dist/family-picker-BKs-tTyu.js +46 -0
- package/dist/family-picker-BKs-tTyu.js.map +1 -0
- package/dist/{gateway-BmjYUSua.js → gateway-D3gIlR6b.js} +122 -11
- package/dist/gateway-D3gIlR6b.js.map +1 -0
- package/dist/headless-BJjhP0SI.js +10 -0
- package/dist/headless-BJjhP0SI.js.map +1 -0
- package/dist/headless-DIX68UUA.js +3 -0
- package/dist/{host-resolution-DVSb7kXb.js → host-resolution-D8GhEVoX.js} +4 -4
- package/dist/{host-resolution-DVSb7kXb.js.map → host-resolution-D8GhEVoX.js.map} +1 -1
- package/dist/install-Bw8tvDvS.js +2 -0
- package/dist/{install-Bk5TNk27.js → install-paPCzPHI.js} +26 -93
- package/dist/install-paPCzPHI.js.map +1 -0
- package/dist/interactive-tdxZKWax.js +11 -0
- package/dist/interactive-tdxZKWax.js.map +1 -0
- package/dist/{links-9LWZPSCq.js → links-Cyj6v8kg.js} +3 -3
- package/dist/{links-9LWZPSCq.js.map → links-Cyj6v8kg.js.map} +1 -1
- package/dist/{main-DKPjFHEV.js → main-0p-YxUNa.js} +204 -469
- package/dist/main-0p-YxUNa.js.map +1 -0
- package/dist/{mcp-Co8VRF62.js → mcp-D3Z5MXhA.js} +3 -4
- package/dist/{mcp-Co8VRF62.js.map → mcp-D3Z5MXhA.js.map} +1 -1
- package/dist/mcp-install-smoke-test.ts +2 -2
- package/dist/{mcp-prompt-streaming-ljCVoQ9I.js → mcp-prompt-streaming-COVbWnHt.js} +12 -12
- package/dist/mcp-prompt-streaming-COVbWnHt.js.map +1 -0
- package/dist/mode-label-DB3ol_sj.js +9 -0
- package/dist/mode-label-DB3ol_sj.js.map +1 -0
- package/dist/{models-Dba8ITeo.js → models-D1SnCzvl.js} +2 -2
- package/dist/{models-Dba8ITeo.js.map → models-D1SnCzvl.js.map} +1 -1
- package/dist/non-interactive-DxRBc73O.js +18 -0
- package/dist/non-interactive-DxRBc73O.js.map +1 -0
- package/dist/{oauth-session-D-RfcVmr.js → oauth-session-Dde79Ycm.js} +3 -3
- package/dist/{oauth-session-D-RfcVmr.js.map → oauth-session-Dde79Ycm.js.map} +1 -1
- package/dist/{orchestrator-tools-CVJ1F9Ub.js → orchestrator-tools-BSNhzzkT.js} +3 -3
- package/dist/{orchestrator-tools-CVJ1F9Ub.js.map → orchestrator-tools-BSNhzzkT.js.map} +1 -1
- package/dist/{package-json-Cowya9-3.js → package-json-2UrhE6kP.js} +1 -1
- package/dist/{package-json-Cowya9-3.js.map → package-json-2UrhE6kP.js.map} +1 -1
- package/dist/package-json-Dblip2_X.js +2 -0
- package/dist/package-manager-BRXlXo0p.js +105 -0
- package/dist/package-manager-BRXlXo0p.js.map +1 -0
- package/dist/{pi-B5YTBRDv.js → pi-cM_4Gd_9.js} +16 -18
- package/dist/pi-cM_4Gd_9.js.map +1 -0
- package/dist/playground-DTKy0XHT.js +12 -0
- package/dist/playground-DTKy0XHT.js.map +1 -0
- package/dist/posthog-integration-bwHwE87a.js +10291 -0
- package/dist/posthog-integration-bwHwE87a.js.map +1 -0
- package/dist/programs-BhtyA3lE.js +3 -0
- package/dist/programs-SeT1qNik.js +5348 -0
- package/dist/programs-SeT1qNik.js.map +1 -0
- package/dist/provisioning-BcXfeCMc.js +2 -0
- package/dist/{provisioning-CLhuKqXH.js → provisioning-JbJtq7hq.js} +4 -5
- package/dist/{provisioning-CLhuKqXH.js.map → provisioning-JbJtq7hq.js.map} +1 -1
- package/dist/{queue-tools-knnPQP0e.js → queue-tools-BIZSJyt6.js} +6 -8
- package/dist/queue-tools-BIZSJyt6.js.map +1 -0
- package/dist/results-DtKICAdz.js +55 -0
- package/dist/results-DtKICAdz.js.map +1 -0
- package/dist/run-CDurA5tO.js +248 -0
- package/dist/run-CDurA5tO.js.map +1 -0
- package/dist/run-DDCvLILJ.js +246 -0
- package/dist/run-DDCvLILJ.js.map +1 -0
- package/dist/run-tool-CTq9OOdT.js +2 -0
- package/dist/run-tool-DML8JJvt.js +2420 -0
- package/dist/run-tool-DML8JJvt.js.map +1 -0
- package/dist/runners-Dhu4pZTN.js +253 -0
- package/dist/runners-Dhu4pZTN.js.map +1 -0
- package/dist/{security-BvdSFDuM.js → security-DEH8Rn0_.js} +5 -5
- package/dist/security-DEH8Rn0_.js.map +1 -0
- package/dist/setup-utils-C5-WM-hS.js +118 -0
- package/dist/setup-utils-C5-WM-hS.js.map +1 -0
- package/dist/smoke-test.sh +1 -1
- package/dist/{playground-BLsCfHmQ.js → start-playground-DL4mVEUx.js} +43 -34
- package/dist/start-playground-DL4mVEUx.js.map +1 -0
- package/dist/{subagent-D_Sqak94.js → subagent-CnFvAq6E.js} +3 -3
- package/dist/{subagent-D_Sqak94.js.map → subagent-CnFvAq6E.js.map} +1 -1
- package/dist/{task-DiTI6Sak.js → task-BOScSm4W.js} +16 -17
- package/dist/task-BOScSm4W.js.map +1 -0
- package/dist/{tasks-ChZ_F1YP.js → tasks-BfJhVky2.js} +1 -1
- package/dist/tasks-BfJhVky2.js.map +1 -0
- package/dist/{telemetry-BNKsDxNS.js → telemetry-CP4rGicd.js} +2 -2
- package/dist/{telemetry-BNKsDxNS.js.map → telemetry-CP4rGicd.js.map} +1 -1
- package/dist/terminal-J3OD3dN0.js +16162 -0
- package/dist/terminal-J3OD3dN0.js.map +1 -0
- package/dist/tools-CCMV3DmB.js +544 -0
- package/dist/tools-CCMV3DmB.js.map +1 -0
- package/dist/{tools-C8LQ8QFz.js → tools-Cz8tDptL.js} +9 -9
- package/dist/tools-Cz8tDptL.js.map +1 -0
- package/dist/tui-BUZ2PWqN.js +2 -0
- package/dist/tui-Csf7VJxt.js +25 -0
- package/dist/tui-Csf7VJxt.js.map +1 -0
- package/dist/{steps-Hc28zVc_.js → upload-step-CEacB2RV.js} +14 -16
- package/dist/upload-step-CEacB2RV.js.map +1 -0
- package/dist/{urls-BI4kJ4qQ.js → urls-BDELGZHq.js} +2 -2
- package/dist/{urls-BI4kJ4qQ.js.map → urls-BDELGZHq.js.map} +1 -1
- package/dist/warlock-smoke-test.ts +1 -2
- package/dist/{wizard-abort-DBKtfnBR.js → wizard-abort-D3-75KyV.js} +40 -33
- package/dist/wizard-abort-D3-75KyV.js.map +1 -0
- package/dist/yara-hooks-CZ1tKxy_.js +2 -0
- package/dist/{yara-hooks-C3L6lBij.js → yara-hooks-D8YDyX7o.js} +283 -21
- package/dist/yara-hooks-D8YDyX7o.js.map +1 -0
- package/package.json +6 -6
- package/dist/PickerMenu-m4CCPX4f.js.map +0 -1
- package/dist/agent-BkBbVuNq.js +0 -3189
- package/dist/agent-BkBbVuNq.js.map +0 -1
- package/dist/agent-QGK2YzlX.js +0 -6
- package/dist/agent-interface-CzyCmp7q.js.map +0 -1
- package/dist/agent-prompt-loader-4wMxlYSX.js.map +0 -1
- package/dist/analytics-B6q5PN7a.js +0 -301
- package/dist/analytics-B6q5PN7a.js.map +0 -1
- package/dist/analytics-BXtfK9o7.js +0 -2
- package/dist/api-C3Dc4LFl.js.map +0 -1
- package/dist/api-yNjrfZs2.js +0 -2
- package/dist/check-screens.tsx +0 -190
- package/dist/ci-install-DfcAMhlu.js.map +0 -1
- package/dist/constants-DO4X70po.js +0 -260
- package/dist/constants-DO4X70po.js.map +0 -1
- package/dist/debug-BueK5mRR.js +0 -2
- package/dist/debug-DyARf5TF.js.map +0 -1
- package/dist/defaults-DpfMnH2q.js.map +0 -1
- package/dist/endpoints-D-4KxZiB.js +0 -108
- package/dist/endpoints-D-4KxZiB.js.map +0 -1
- package/dist/env-api-key-CHGLe0iz.js +0 -2
- package/dist/env-api-key-COf0U-Au.js +0 -17
- package/dist/env-api-key-COf0U-Au.js.map +0 -1
- package/dist/environment-DrLaSsA8.js +0 -39
- package/dist/environment-DrLaSsA8.js.map +0 -1
- package/dist/environment-hBj7ngmO.js +0 -2
- package/dist/file-CVA9ocIC.js +0 -60
- package/dist/file-CVA9ocIC.js.map +0 -1
- package/dist/file-CmoKK_Pp.js +0 -2
- package/dist/file-watcher-C3Su8vXm.js +0 -123
- package/dist/file-watcher-C3Su8vXm.js.map +0 -1
- package/dist/gateway-BmjYUSua.js.map +0 -1
- package/dist/headless-ui-CUbv12Yx.js +0 -44
- package/dist/headless-ui-CUbv12Yx.js.map +0 -1
- package/dist/install-Bk5TNk27.js.map +0 -1
- package/dist/install-F4wm3RSl.js +0 -2
- package/dist/interactive-C5LNvDj6.js +0 -11
- package/dist/interactive-C5LNvDj6.js.map +0 -1
- package/dist/local-dev-McsurOC-.js +0 -135
- package/dist/local-dev-McsurOC-.js.map +0 -1
- package/dist/logging-ui-p6XKVmTx.js +0 -226
- package/dist/logging-ui-p6XKVmTx.js.map +0 -1
- package/dist/main-DKPjFHEV.js.map +0 -1
- package/dist/mcp-prompt-streaming-ljCVoQ9I.js.map +0 -1
- package/dist/mint-failure-C0HyvEwO.js +0 -6403
- package/dist/mint-failure-C0HyvEwO.js.map +0 -1
- package/dist/non-interactive-CLMJrj_7.js +0 -18
- package/dist/non-interactive-CLMJrj_7.js.map +0 -1
- package/dist/package-json--ppioh2F.js +0 -2
- package/dist/pi-B5YTBRDv.js.map +0 -1
- package/dist/playground-BLsCfHmQ.js.map +0 -1
- package/dist/posthog-B2E0hpZt.js +0 -2
- package/dist/posthog-B42rt_1S.js +0 -124
- package/dist/posthog-B42rt_1S.js.map +0 -1
- package/dist/project-data--L-WZyvj.js +0 -2
- package/dist/project-data-BO9IXn9y.js +0 -1131
- package/dist/project-data-BO9IXn9y.js.map +0 -1
- package/dist/provisioning-C01REIU2.js +0 -2
- package/dist/queue-tools-knnPQP0e.js.map +0 -1
- package/dist/registry-B3rC5U4b.js +0 -3464
- package/dist/registry-B3rC5U4b.js.map +0 -1
- package/dist/registry-BamsM7J9.js +0 -2
- package/dist/run-agent-legacy-CwUmPfQF.js +0 -2
- package/dist/run-agent-legacy-DWBajsMh.js +0 -3467
- package/dist/run-agent-legacy-DWBajsMh.js.map +0 -1
- package/dist/runners-D1PqqpjA.js +0 -834
- package/dist/runners-D1PqqpjA.js.map +0 -1
- package/dist/security-BvdSFDuM.js.map +0 -1
- package/dist/setup-utils-ChXXTi1m.js +0 -301
- package/dist/setup-utils-ChXXTi1m.js.map +0 -1
- package/dist/start-tui-DicLfgsj.js +0 -6964
- package/dist/start-tui-DicLfgsj.js.map +0 -1
- package/dist/steps-Hc28zVc_.js.map +0 -1
- package/dist/store-COCO6U0P.js +0 -5
- package/dist/store-D3509G87.js +0 -1030
- package/dist/store-D3509G87.js.map +0 -1
- package/dist/task-DiTI6Sak.js.map +0 -1
- package/dist/task-status-CmrrR7zo.js +0 -16
- package/dist/task-status-CmrrR7zo.js.map +0 -1
- package/dist/task-stream-BckhG_cB.js +0 -363
- package/dist/task-stream-BckhG_cB.js.map +0 -1
- package/dist/tasks-ChZ_F1YP.js.map +0 -1
- package/dist/terminal-PK9bCLRi.js +0 -7222
- package/dist/terminal-PK9bCLRi.js.map +0 -1
- package/dist/tools-C8LQ8QFz.js.map +0 -1
- package/dist/ui-tPNpM6vj.js +0 -15
- package/dist/ui-tPNpM6vj.js.map +0 -1
- package/dist/version-5_ydHkM6.js +0 -6
- package/dist/version-5_ydHkM6.js.map +0 -1
- package/dist/wizard-abort-BVjqLyeA.js +0 -3
- package/dist/wizard-abort-DBKtfnBR.js.map +0 -1
- package/dist/wizard-session-B7BHnay0.js +0 -2
- package/dist/wizard-session-BYw6LzpS.js +0 -133
- package/dist/wizard-session-BYw6LzpS.js.map +0 -1
- package/dist/yara-hooks-C3L6lBij.js.map +0 -1
- package/dist/yara-hooks-CF_IsoaA.js +0 -2
package/dist/agent-BkBbVuNq.js
DELETED
|
@@ -1,3189 +0,0 @@
|
|
|
1
|
-
import { U as installSkillById, ct as LONGER_ASK_TIMEOUT_MS, et as fetchSkillMenu, i as runAgent$1, lt as shouldDisableAsk, nt as createWizardAskBridge, r as initializeAgent, ut as createTriageLLMProvider } from "./agent-interface-CzyCmp7q.js";
|
|
2
|
-
import { n as ErrorCodes } from "./emit-BSetXzDV.js";
|
|
3
|
-
import { a as getLogFilePath, h as RUN_SURFACE, l as WIZARD_BENCHMARK_FILE, s as logToFile, t as configureLogFile, u as WIZARD_LOG_FILE, v as runtimeEnv } from "./debug-DyARf5TF.js";
|
|
4
|
-
import { t as VERSION } from "./version-5_ydHkM6.js";
|
|
5
|
-
import { B as WIZARD_CONTACT_EMAIL, G as WIZARD_ORCHESTRATOR_FLAG_KEY, J as WIZARD_PROVISIONING_SCOPES, K as WIZARD_ORCHESTRATOR_OVERRIDE_FLAG_KEY, L as SONNET_5_MODEL, R as Sequence, S as Harness, W as WIZARD_OAUTH_SCOPES, Z as WIZARD_SELF_DRIVING_USE_PI_HARNESS_FLAG_KEY, _ as GPT5_6_LUNA_MODEL, b as HAIKU_MODEL, l as DEFAULT_AGENT_MODEL, q as WIZARD_ORCHESTRATOR_SEEDED_TASKS_FLAG_KEY, v as GPT5_6_SOL_MODEL, y as GPT5_6_TERRA_MODEL } from "./constants-DO4X70po.js";
|
|
6
|
-
import "./wizard-session-BYw6LzpS.js";
|
|
7
|
-
import { a as ciExcludedTaskTypes, n as analytics } from "./analytics-B6q5PN7a.js";
|
|
8
|
-
import { a as WizardError, r as skillErrorCode, t as classifyRunFailure } from "./errors-C2domSkK.js";
|
|
9
|
-
import { s as detectNodePackageManagers } from "./setup-utils-ChXXTi1m.js";
|
|
10
|
-
import { n as currentAccessToken } from "./oauth-session-D-RfcVmr.js";
|
|
11
|
-
import { f as gatewayAuth, s as AgentSignals } from "./gateway-BmjYUSua.js";
|
|
12
|
-
import { a as requireKnownModel, o as triageModelFor, r as isValidModel } from "./models-Dba8ITeo.js";
|
|
13
|
-
import { n as mcpUrlFor } from "./host-resolution-DVSb7kXb.js";
|
|
14
|
-
import { a as piBackend, d as createEmitSpinner, f as createProgressCollector, l as createAioCapture, u as createEmitLog } from "./pi-B5YTBRDv.js";
|
|
15
|
-
import { a as flushScanReport, s as formatYaraAbortMessage } from "./yara-hooks-C3L6lBij.js";
|
|
16
|
-
import { _ as TaskStatus, d as uncoveredBySink, h as SkipReason, m as QueueStore, p as QUEUE_DIR_NAME, u as dependencyClosure } from "./queue-tools-knnPQP0e.js";
|
|
17
|
-
import { a as assembleTaskPrompt, d as taskModelSpec, i as assembleSeedPrompt, n as agentRunTools, o as loadAgentRegistry, s as promptModelFor, t as ASK_TOOL, u as resolveTask } from "./agent-prompt-loader-4wMxlYSX.js";
|
|
18
|
-
import fs, { cpSync, existsSync, mkdirSync, readdirSync, rmSync, writeFileSync } from "fs";
|
|
19
|
-
import * as path$1 from "path";
|
|
20
|
-
import path from "path";
|
|
21
|
-
import { randomUUID } from "crypto";
|
|
22
|
-
import { z } from "zod";
|
|
23
|
-
//#region src/agent/runner/shared/bootstrap.ts
|
|
24
|
-
/**
|
|
25
|
-
* Shared preparation for the runner pipeline.
|
|
26
|
-
*
|
|
27
|
-
* Runs before the fork into the linear or orchestrator arm: logging targets,
|
|
28
|
-
* the gateway mint and the scan-triage classifier built on it. Everything the
|
|
29
|
-
* caller must decide first — health gates, settings conflicts, authentication,
|
|
30
|
-
* the AI opt-in gate, post-auth gates, feature flags, run tags, token refresh —
|
|
31
|
-
* arrives already resolved in `RunConfig` and `RunInput`.
|
|
32
|
-
*/
|
|
33
|
-
/** The option bag the agent interface and the middleware read. */
|
|
34
|
-
function runOptions(input) {
|
|
35
|
-
return {
|
|
36
|
-
installDir: input.installDir,
|
|
37
|
-
debug: input.flags.debug,
|
|
38
|
-
signup: input.flags.signup,
|
|
39
|
-
ci: input.flags.ci,
|
|
40
|
-
benchmark: input.flags.benchmark,
|
|
41
|
-
projectId: input.host.projectId,
|
|
42
|
-
apiKey: input.host.apiKey,
|
|
43
|
-
yaraReport: input.flags.yaraReport
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
/**
|
|
47
|
-
* Shared setup for both arms: logging targets, then the gateway mint and the
|
|
48
|
-
* triage classifier. Throws when the mint is refused, so the run fails before
|
|
49
|
-
* any agent starts — the caller maps that the way it maps any unexpected error.
|
|
50
|
-
*/
|
|
51
|
-
async function prepareRun(config, input) {
|
|
52
|
-
const { skillsBaseUrl } = config;
|
|
53
|
-
logToFile(`[agent-runner] targets build=${VERSION} skills=${skillsBaseUrl} mcp=${mcpUrlFor(input.flags.localMcp)} posthog=${input.host.baseUrl ?? "region-resolved"}`);
|
|
54
|
-
const { credentials } = input;
|
|
55
|
-
const { wizardFlags, wizardFlagPayloads, wizardMetadata, programId } = config;
|
|
56
|
-
const currentGatewayAuth = () => currentAccessToken(credentials).then((token) => gatewayAuth(credentials.host, token, programId));
|
|
57
|
-
await currentGatewayAuth();
|
|
58
|
-
return {
|
|
59
|
-
skillsBaseUrl,
|
|
60
|
-
credentials,
|
|
61
|
-
programId,
|
|
62
|
-
wizardFlags,
|
|
63
|
-
wizardFlagPayloads,
|
|
64
|
-
wizardMetadata,
|
|
65
|
-
project: input.project,
|
|
66
|
-
triageProvider: createTriageLLMProvider(async () => {
|
|
67
|
-
const auth = await currentGatewayAuth();
|
|
68
|
-
return {
|
|
69
|
-
baseURL: auth.gatewayUrl,
|
|
70
|
-
authToken: auth.token,
|
|
71
|
-
teamId: auth.teamId,
|
|
72
|
-
wizardMetadata: {
|
|
73
|
-
...wizardMetadata,
|
|
74
|
-
call_type: "yara-triage"
|
|
75
|
-
},
|
|
76
|
-
wizardFlags
|
|
77
|
-
};
|
|
78
|
-
}, config.binding.harness)
|
|
79
|
-
};
|
|
80
|
-
}
|
|
81
|
-
const ACTIVITY_LINE_CHARS = 100;
|
|
82
|
-
function createTranscriptTail(emit) {
|
|
83
|
-
const collected = [];
|
|
84
|
-
let collectedChars = 0;
|
|
85
|
-
let resultText = "";
|
|
86
|
-
const collect = (text) => {
|
|
87
|
-
collected.push(text);
|
|
88
|
-
collectedChars += text.length;
|
|
89
|
-
while (collectedChars > 262144 && collected.length > 1) collectedChars -= collected.shift()?.length ?? 0;
|
|
90
|
-
};
|
|
91
|
-
const activity = (line) => emit({
|
|
92
|
-
kind: "activity",
|
|
93
|
-
line
|
|
94
|
-
});
|
|
95
|
-
return {
|
|
96
|
-
onMessage(message) {
|
|
97
|
-
if (message?.type === "assistant") {
|
|
98
|
-
const content = message.message?.content;
|
|
99
|
-
if (!Array.isArray(content)) return;
|
|
100
|
-
for (const block of content) if (block?.type === "text" && typeof block.text === "string") {
|
|
101
|
-
collect(block.text);
|
|
102
|
-
const line = block.text.trim();
|
|
103
|
-
if (line) activity(line.length > ACTIVITY_LINE_CHARS ? `${line.slice(0, ACTIVITY_LINE_CHARS)}…` : line);
|
|
104
|
-
} else if (block?.type === "tool_use") activity(formatToolUse(block));
|
|
105
|
-
} else if (message?.type === "result" && typeof message.result === "string") resultText = message.result;
|
|
106
|
-
},
|
|
107
|
-
finalize: () => void 0,
|
|
108
|
-
text: () => `${collected.join("\n")}\n${resultText}`
|
|
109
|
-
};
|
|
110
|
-
}
|
|
111
|
-
/** Put the tail in front of any other middleware, so both see every message. */
|
|
112
|
-
function withTranscript(middleware, transcript) {
|
|
113
|
-
if (!transcript) return middleware;
|
|
114
|
-
if (!middleware) return transcript;
|
|
115
|
-
return {
|
|
116
|
-
onMessage(message) {
|
|
117
|
-
transcript.onMessage(message);
|
|
118
|
-
middleware.onMessage(message);
|
|
119
|
-
},
|
|
120
|
-
finalize(resultMessage, totalDurationMs) {
|
|
121
|
-
transcript.finalize(resultMessage, totalDurationMs);
|
|
122
|
-
return middleware.finalize(resultMessage, totalDurationMs);
|
|
123
|
-
}
|
|
124
|
-
};
|
|
125
|
-
}
|
|
126
|
-
function formatToolUse(block) {
|
|
127
|
-
const name = typeof block?.name === "string" ? block.name : "tool";
|
|
128
|
-
const input = block?.input ?? {};
|
|
129
|
-
const detail = input.file_path || input.pattern || input.path || "";
|
|
130
|
-
return detail ? `${name} ${detail}` : name;
|
|
131
|
-
}
|
|
132
|
-
//#endregion
|
|
133
|
-
//#region src/agent/runner/harness/anthropic/index.ts
|
|
134
|
-
const anthropicBackend = {
|
|
135
|
-
name: "anthropic",
|
|
136
|
-
async run(inputs) {
|
|
137
|
-
const { config: runConfig, input, boot, emit, prompt, spinner, askBridge, middleware, model, structured } = inputs;
|
|
138
|
-
const config = runConfig.run;
|
|
139
|
-
const { skillsBaseUrl, credentials, wizardFlags, wizardMetadata } = boot;
|
|
140
|
-
const { accessToken, host, projectApiKey } = credentials;
|
|
141
|
-
const log = createEmitLog(emit);
|
|
142
|
-
const capture = createAioCapture({
|
|
143
|
-
enabled: input.flags.captureAio,
|
|
144
|
-
projectApiKey,
|
|
145
|
-
apiHost: host.apiHost,
|
|
146
|
-
runTags: wizardMetadata
|
|
147
|
-
});
|
|
148
|
-
if (!structured) log.step("Initializing Claude agent...");
|
|
149
|
-
const agent = await initializeAgent({
|
|
150
|
-
workingDirectory: input.installDir,
|
|
151
|
-
posthogMcpUrl: host.mcpUrl,
|
|
152
|
-
posthogApiKey: accessToken,
|
|
153
|
-
currentPosthogApiKey: () => currentAccessToken(credentials),
|
|
154
|
-
host,
|
|
155
|
-
additionalMcpServers: config.additionalMcpServers,
|
|
156
|
-
detectPackageManager: config.detectPackageManager ?? detectNodePackageManagers,
|
|
157
|
-
skillsBaseUrl,
|
|
158
|
-
wizardFlags,
|
|
159
|
-
wizardMetadata,
|
|
160
|
-
programId: boot.programId,
|
|
161
|
-
integrationLabel: config.integrationLabel,
|
|
162
|
-
askBridge,
|
|
163
|
-
getPendingQuestion: askBridge?.getPendingQuestion,
|
|
164
|
-
askMaxQuestions: config.maxQuestions,
|
|
165
|
-
readOnly: config.readOnly,
|
|
166
|
-
allowedTools: runConfig.allowedTools,
|
|
167
|
-
disallowedTools: runConfig.disallowedTools,
|
|
168
|
-
modelOverride: model,
|
|
169
|
-
outputFormat: structured && {
|
|
170
|
-
type: "json_schema",
|
|
171
|
-
schema: structured.schema
|
|
172
|
-
},
|
|
173
|
-
capture,
|
|
174
|
-
emit
|
|
175
|
-
}, runOptions(input));
|
|
176
|
-
if (!structured) {
|
|
177
|
-
log.step(`Verbose logs: ${getLogFilePath()}`);
|
|
178
|
-
log.success("Agent initialized. Let's get cooking!");
|
|
179
|
-
}
|
|
180
|
-
logToFile("[agent-runner] agent initialized");
|
|
181
|
-
return runAgent$1({
|
|
182
|
-
...agent,
|
|
183
|
-
signal: inputs.signal
|
|
184
|
-
}, prompt, runOptions(input), spinner, {
|
|
185
|
-
timeoutMs: structured?.timeoutMs,
|
|
186
|
-
estimatedDurationMinutes: config.estimatedDurationMinutes,
|
|
187
|
-
spinnerMessage: config.spinnerMessage,
|
|
188
|
-
successMessage: config.successMessage,
|
|
189
|
-
errorMessage: config.errorMessage ?? `${config.integrationLabel} failed`,
|
|
190
|
-
abortCases: config.abortCases,
|
|
191
|
-
emitStepEvents: config.trackStepProgress ?? false,
|
|
192
|
-
resolveStepKey: config.resolveStepKey,
|
|
193
|
-
requestRemark: structured ? false : config.requestRemark,
|
|
194
|
-
triageProvider: boot.triageProvider
|
|
195
|
-
}, middleware);
|
|
196
|
-
},
|
|
197
|
-
async runTask(inputs) {
|
|
198
|
-
const { config, input, boot, emit, prompt, spinner, model, allowedTools, disallowedTools, askBridge, orchestrator, spinnerMessage, successMessage, errorMessage, requestRemark, analyticsProperties } = inputs;
|
|
199
|
-
const options = runOptions(input);
|
|
200
|
-
const capture = createAioCapture({
|
|
201
|
-
enabled: input.flags.captureAio,
|
|
202
|
-
projectApiKey: boot.credentials.projectApiKey,
|
|
203
|
-
apiHost: boot.credentials.host.apiHost,
|
|
204
|
-
runTags: boot.wizardMetadata
|
|
205
|
-
});
|
|
206
|
-
return runAgent$1({
|
|
207
|
-
...await initializeAgent({
|
|
208
|
-
workingDirectory: input.installDir,
|
|
209
|
-
posthogMcpUrl: boot.credentials.host.mcpUrl,
|
|
210
|
-
posthogApiKey: boot.credentials.accessToken,
|
|
211
|
-
currentPosthogApiKey: () => currentAccessToken(boot.credentials),
|
|
212
|
-
host: boot.credentials.host,
|
|
213
|
-
detectPackageManager: detectNodePackageManagers,
|
|
214
|
-
skillsBaseUrl: boot.skillsBaseUrl,
|
|
215
|
-
programId: boot.programId,
|
|
216
|
-
wizardFlags: boot.wizardFlags,
|
|
217
|
-
wizardMetadata: boot.wizardMetadata,
|
|
218
|
-
integrationLabel: config.programId,
|
|
219
|
-
askBridge,
|
|
220
|
-
getPendingQuestion: askBridge?.getPendingQuestion,
|
|
221
|
-
orchestrator,
|
|
222
|
-
capture,
|
|
223
|
-
emit
|
|
224
|
-
}, options),
|
|
225
|
-
model,
|
|
226
|
-
allowedTools,
|
|
227
|
-
disallowedTools,
|
|
228
|
-
signal: inputs.signal
|
|
229
|
-
}, prompt, options, spinner, {
|
|
230
|
-
spinnerMessage,
|
|
231
|
-
successMessage,
|
|
232
|
-
errorMessage,
|
|
233
|
-
requestRemark,
|
|
234
|
-
analyticsProperties
|
|
235
|
-
});
|
|
236
|
-
}
|
|
237
|
-
};
|
|
238
|
-
//#endregion
|
|
239
|
-
//#region src/agent/runner/switchboard/flags/orchestrator.ts
|
|
240
|
-
/** Orchestrator gate: wizard-orchestrator on/off. On routes the declared programs to the orchestrator on pi; every model/effort comes from prompt frontmatter. */
|
|
241
|
-
const ORCHESTRATOR_SEQUENCE_ROUTE = {
|
|
242
|
-
programs: ["posthog-integration"],
|
|
243
|
-
flag: WIZARD_ORCHESTRATOR_FLAG_KEY,
|
|
244
|
-
sequence: "orchestrator"
|
|
245
|
-
};
|
|
246
|
-
/** Same flag, harness axis: the orchestrator drives tasks through pi. */
|
|
247
|
-
const ORCHESTRATOR_HARNESS_ROUTE = {
|
|
248
|
-
program: "posthog-integration",
|
|
249
|
-
flags: {
|
|
250
|
-
useFlag: WIZARD_ORCHESTRATOR_FLAG_KEY,
|
|
251
|
-
harness: "pi"
|
|
252
|
-
}
|
|
253
|
-
};
|
|
254
|
-
/** Same programs, per-stage overrides: the variant payload (`{stage: {model?, effort?}}`) outranks prompt frontmatter for the stages it names. */
|
|
255
|
-
const ORCHESTRATOR_STAGE_OVERRIDES = {
|
|
256
|
-
programs: ORCHESTRATOR_SEQUENCE_ROUTE.programs,
|
|
257
|
-
flag: WIZARD_ORCHESTRATOR_OVERRIDE_FLAG_KEY
|
|
258
|
-
};
|
|
259
|
-
/**
|
|
260
|
-
* Kill switch for the runner-seeded task mechanism (the optional warehouse
|
|
261
|
-
* step). Off or unset, the orchestrator queues nothing itself and the run is
|
|
262
|
-
* identical to a project with no detected sources.
|
|
263
|
-
*/
|
|
264
|
-
function areSeededTasksEnabled(flags = {}) {
|
|
265
|
-
return flags[WIZARD_ORCHESTRATOR_SEEDED_TASKS_FLAG_KEY] === "true";
|
|
266
|
-
}
|
|
267
|
-
/** Raw flag read — for telemetry/log lines only, never for routing. */
|
|
268
|
-
function isOrchestratorEnabled(flags = {}) {
|
|
269
|
-
return flags[ORCHESTRATOR_SEQUENCE_ROUTE.flag] === "true";
|
|
270
|
-
}
|
|
271
|
-
//#endregion
|
|
272
|
-
//#region src/agent/runner/switchboard/flags/self-driving.ts
|
|
273
|
-
/**
|
|
274
|
-
* Self-driving pi experiment.
|
|
275
|
-
*
|
|
276
|
-
* Flag: wizard-self-driving-use-pi-harness (bool; payload `{model, effort?,
|
|
277
|
-
* harness?, sequence?}`)
|
|
278
|
-
* Routes: ONLY `self-driving` — any axis the payload pins. A missing or
|
|
279
|
-
* invalid payload keeps the non-flagged binding default.
|
|
280
|
-
*/
|
|
281
|
-
const SELF_DRIVING_EXPERIMENT = {
|
|
282
|
-
program: "self-driving",
|
|
283
|
-
flags: { useFlag: WIZARD_SELF_DRIVING_USE_PI_HARNESS_FLAG_KEY }
|
|
284
|
-
};
|
|
285
|
-
//#endregion
|
|
286
|
-
//#region src/agent/runner/switchboard/flags/schemes.ts
|
|
287
|
-
/**
|
|
288
|
-
* Flag config schemes — the shapes a PostHog flag set can take to route a
|
|
289
|
-
* program to the pi harness, plus the shared vocabulary and the per-config
|
|
290
|
-
* resolution. Experiment modules in this folder declare *which* flags they
|
|
291
|
-
* use; this module owns *how* a config resolves to a route.
|
|
292
|
-
*/
|
|
293
|
-
/** Model variant key → gateway id. A key the gateway no longer mints is dropped
|
|
294
|
-
* rather than re-aimed: the payload then fails validation and the binding
|
|
295
|
-
* default stands, which is visible in the log instead of silently rerouting. */
|
|
296
|
-
const MODEL_FLAG_VARIANTS = {
|
|
297
|
-
"gpt-5-6-luna": GPT5_6_LUNA_MODEL,
|
|
298
|
-
"gpt-5-6-terra": GPT5_6_TERRA_MODEL,
|
|
299
|
-
"gpt-5-6-sol": GPT5_6_SOL_MODEL,
|
|
300
|
-
"sonnet-5": SONNET_5_MODEL
|
|
301
|
-
};
|
|
302
|
-
/** Valid effort variants; anything else leaves the model's table default. */
|
|
303
|
-
const EFFORT_FLAG_VARIANTS = [
|
|
304
|
-
"minimal",
|
|
305
|
-
"low",
|
|
306
|
-
"medium",
|
|
307
|
-
"high",
|
|
308
|
-
"xhigh"
|
|
309
|
-
];
|
|
310
|
-
/** Variant key validated against the shared vocabulary, transformed to its gateway id. */
|
|
311
|
-
const modelVariantSchema = z.string().refine((key) => key in MODEL_FLAG_VARIANTS).transform((key) => MODEL_FLAG_VARIANTS[key]);
|
|
312
|
-
/** `{model, effort?, harness?, sequence?}` payload shape; extra keys tolerated for forward compat. */
|
|
313
|
-
const payloadConfigFlagSchema = z.object({
|
|
314
|
-
model: modelVariantSchema,
|
|
315
|
-
effort: z.enum(EFFORT_FLAG_VARIANTS).optional(),
|
|
316
|
-
harness: z.nativeEnum(Harness).optional(),
|
|
317
|
-
sequence: z.nativeEnum(Sequence).optional()
|
|
318
|
-
});
|
|
319
|
-
/**
|
|
320
|
-
* PostHog serves a variant's payload as a JSON string, e.g. the
|
|
321
|
-
* wizard-orchestrator-override `terra-review` variant arrives as
|
|
322
|
-
* `'{"review":{"model":"gpt-5-6-terra","effort":"medium"}}'`; local CI
|
|
323
|
-
* overrides inject the same payloads as already-parsed objects. Coerce both
|
|
324
|
-
* to the value; anything unparseable is undefined.
|
|
325
|
-
*/
|
|
326
|
-
function coerceJson(raw) {
|
|
327
|
-
if (typeof raw !== "string") return raw;
|
|
328
|
-
try {
|
|
329
|
-
return JSON.parse(raw);
|
|
330
|
-
} catch {
|
|
331
|
-
return;
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
/** Per-stage overrides keyed by stage; a whole-payload parse failure fails closed to frontmatter everywhere. */
|
|
335
|
-
const stageOverridesSchema = z.record(z.object({
|
|
336
|
-
model: modelVariantSchema.optional(),
|
|
337
|
-
effort: z.enum(EFFORT_FLAG_VARIANTS).optional()
|
|
338
|
-
}));
|
|
339
|
-
/** Validate a stage-override payload (object or JSON string); undefined on any unexpected shape. */
|
|
340
|
-
function stageOverridesFromPayload(raw) {
|
|
341
|
-
const parsed = stageOverridesSchema.safeParse(coerceJson(raw));
|
|
342
|
-
return parsed.success ? parsed.data : void 0;
|
|
343
|
-
}
|
|
344
|
-
/** Validate a payload (object or JSON string) into a route; undefined on any unexpected shape. */
|
|
345
|
-
function parseFlagPayload(raw) {
|
|
346
|
-
const parsed = payloadConfigFlagSchema.safeParse(coerceJson(raw));
|
|
347
|
-
if (!parsed.success) return void 0;
|
|
348
|
-
return {
|
|
349
|
-
model: parsed.data.model,
|
|
350
|
-
thinkingLevel: parsed.data.effort,
|
|
351
|
-
harness: parsed.data.harness,
|
|
352
|
-
sequence: parsed.data.sequence
|
|
353
|
-
};
|
|
354
|
-
}
|
|
355
|
-
/**
|
|
356
|
-
* Resolve one config against the flag snapshot, or undefined when it doesn't
|
|
357
|
-
* validly route: use flag off, or a payload that fails validation — the
|
|
358
|
-
* caller then keeps the non-flagged binding default.
|
|
359
|
-
*/
|
|
360
|
-
function routeFromConfigFlag(cfg, flags, flagPayloads) {
|
|
361
|
-
if (flags[cfg.useFlag] !== "true") return void 0;
|
|
362
|
-
if (cfg.harness) return { harness: cfg.harness };
|
|
363
|
-
const route = parseFlagPayload(flagPayloads?.[cfg.useFlag]);
|
|
364
|
-
if (!route) logToFile(`[switchboard] ${cfg.useFlag} on but payload missing/invalid — keeping the non-flagged default`);
|
|
365
|
-
return route;
|
|
366
|
-
}
|
|
367
|
-
//#endregion
|
|
368
|
-
//#region src/agent/runner/switchboard/flags/index.ts
|
|
369
|
-
/**
|
|
370
|
-
* Switchboard flags, one module per experiment. Every experiment declares its
|
|
371
|
-
* own scope — which program(s) its flags route — so the flag→program mapping
|
|
372
|
-
* is readable per file, never implied. `schemes.ts` owns the shared config
|
|
373
|
-
* shapes and resolution.
|
|
374
|
-
*/
|
|
375
|
-
/** Every experiment on each axis. An experiment routes ONLY by being listed here, and only for the programs it declares. */
|
|
376
|
-
const HARNESS_EXPERIMENTS = [ORCHESTRATOR_HARNESS_ROUTE, SELF_DRIVING_EXPERIMENT];
|
|
377
|
-
const SEQUENCE_EXPERIMENTS = [ORCHESTRATOR_SEQUENCE_ROUTE];
|
|
378
|
-
/** The flag-driven route for a program, or undefined when no experiment covers it or its flags don't validly route. */
|
|
379
|
-
function resolveFlagRoute(program, flags, flagPayloads) {
|
|
380
|
-
const experiment = HARNESS_EXPERIMENTS.find((e) => e.program === program);
|
|
381
|
-
return experiment && routeFromConfigFlag(experiment.flags, flags, flagPayloads);
|
|
382
|
-
}
|
|
383
|
-
/** The flag-driven sequence for a program, or undefined when no sequence experiment covers it with its flag on. Surface/build scoping is the flag's own job (see `flagPersonProperties`). */
|
|
384
|
-
function resolveFlagSequence(program, flags) {
|
|
385
|
-
return SEQUENCE_EXPERIMENTS.find((e) => e.programs.includes(program) && flags[e.flag] === "true")?.sequence;
|
|
386
|
-
}
|
|
387
|
-
/** The per-stage overrides for a program's run, or undefined (prompt frontmatter stays). Applied once, where the agent prompts are loaded. */
|
|
388
|
-
function resolveStageOverrides(program, flags, flagPayloads) {
|
|
389
|
-
if (RUN_SURFACE === "cloud") return void 0;
|
|
390
|
-
const exp = ORCHESTRATOR_STAGE_OVERRIDES;
|
|
391
|
-
if (!exp.programs.includes(program)) return void 0;
|
|
392
|
-
const variant = flags[exp.flag];
|
|
393
|
-
if (!variant || variant === "false") return void 0;
|
|
394
|
-
const overrides = stageOverridesFromPayload(flagPayloads?.[exp.flag]);
|
|
395
|
-
if (!overrides) logToFile(`[switchboard] ${exp.flag}=${variant} but payload missing/invalid — keeping frontmatter`);
|
|
396
|
-
return overrides;
|
|
397
|
-
}
|
|
398
|
-
//#endregion
|
|
399
|
-
//#region src/agent/runner/switchboard/harness.ts
|
|
400
|
-
/**
|
|
401
|
-
* Harness axis: registry, middleware, resolver. Mirrors `sequence.ts`.
|
|
402
|
-
*/
|
|
403
|
-
const HARNESS_OPTIONS = {
|
|
404
|
-
["anthropic"]: anthropicBackend,
|
|
405
|
-
["pi"]: piBackend
|
|
406
|
-
};
|
|
407
|
-
function getHarness(name) {
|
|
408
|
-
const harness = HARNESS_OPTIONS[name];
|
|
409
|
-
if (!harness) throw new Error(`No harness registered for '${name}'.`);
|
|
410
|
-
return harness;
|
|
411
|
-
}
|
|
412
|
-
/**
|
|
413
|
-
* PostHog-flag routing to pi (see `./flags`). No valid route — flag off, no
|
|
414
|
-
* config, or an invalid payload — keeps the non-flagged binding default.
|
|
415
|
-
*/
|
|
416
|
-
const flagRunnerOverride = (ctx, next) => {
|
|
417
|
-
const pick = next();
|
|
418
|
-
const route = resolveFlagRoute(ctx.program, ctx.flags, ctx.flagPayloads);
|
|
419
|
-
if (!route) return pick;
|
|
420
|
-
if (ctx.trace) {
|
|
421
|
-
ctx.trace.harness = "flag";
|
|
422
|
-
if (route.model) ctx.trace.model = "flag";
|
|
423
|
-
}
|
|
424
|
-
return {
|
|
425
|
-
harness: route.harness ?? "pi",
|
|
426
|
-
model: route.model ?? pick.model,
|
|
427
|
-
thinkingLevel: route.thinkingLevel ?? pick.thinkingLevel
|
|
428
|
-
};
|
|
429
|
-
};
|
|
430
|
-
const HARNESS_MIDDLEWARE = [...[], flagRunnerOverride];
|
|
431
|
-
/**
|
|
432
|
-
* Resolve the harness for a role. Linear callers omit `role`; orchestrator
|
|
433
|
-
* callers pass `'seed'` or `task.type`. `contextMillOverride[role]` overlays.
|
|
434
|
-
*/
|
|
435
|
-
function resolveHarness(ctx, role = "default") {
|
|
436
|
-
const pick = runChain(HARNESS_MIDDLEWARE, ctx, () => {
|
|
437
|
-
if (ctx.trace) Object.assign(ctx.trace, {
|
|
438
|
-
harness: "binding",
|
|
439
|
-
model: "binding"
|
|
440
|
-
});
|
|
441
|
-
const binding = PROGRAM_BINDINGS[ctx.program] ?? DEFAULT_BINDING;
|
|
442
|
-
return {
|
|
443
|
-
harness: binding.harness,
|
|
444
|
-
model: binding.model,
|
|
445
|
-
thinkingLevel: binding.thinkingLevel,
|
|
446
|
-
...binding.contextMillOverride?.[role]
|
|
447
|
-
};
|
|
448
|
-
});
|
|
449
|
-
logToFile(`[switchboard] resolved: program=${ctx.program} harness=${pick.harness}${ctx.trace?.harness ? ` (${ctx.trace.harness})` : ""} model=${pick.model}${ctx.trace?.model ? ` (${ctx.trace.model})` : ""}`);
|
|
450
|
-
return pick;
|
|
451
|
-
}
|
|
452
|
-
//#endregion
|
|
453
|
-
//#region src/agent/middleware/phase-detector.ts
|
|
454
|
-
/** Phase transitions from [STATUS] in assistant text. Keep in sync with program "Status to report" bullets. */
|
|
455
|
-
const PHASES_ORDER = [
|
|
456
|
-
"1.0-begin",
|
|
457
|
-
"1.1-edit",
|
|
458
|
-
"1.2-revise",
|
|
459
|
-
"1.3-conclude"
|
|
460
|
-
];
|
|
461
|
-
const STATUS_PHRASES_BY_PHASE = {
|
|
462
|
-
"1.0-begin": [
|
|
463
|
-
"Checking project structure",
|
|
464
|
-
"Verifying PostHog dependencies",
|
|
465
|
-
"Generating events based on project"
|
|
466
|
-
],
|
|
467
|
-
"1.1-edit": ["Inserting PostHog capture code"],
|
|
468
|
-
"1.2-revise": [
|
|
469
|
-
"Finding and correcting errors",
|
|
470
|
-
"Report details of any errors you fix",
|
|
471
|
-
"Linting, building and prettying"
|
|
472
|
-
],
|
|
473
|
-
"1.3-conclude": ["Configured dashboard", "Created setup report"]
|
|
474
|
-
};
|
|
475
|
-
var PhaseDetector = class {
|
|
476
|
-
currentPhase = "setup";
|
|
477
|
-
detect(message) {
|
|
478
|
-
if (message.type !== "assistant") return null;
|
|
479
|
-
const nextPhase = this.getNextPhase();
|
|
480
|
-
if (nextPhase === null) return null;
|
|
481
|
-
const content = message.message?.content;
|
|
482
|
-
if (!Array.isArray(content)) return null;
|
|
483
|
-
for (const block of content) {
|
|
484
|
-
if (block.type !== "text" || typeof block.text !== "string") continue;
|
|
485
|
-
if (!block.text.includes("[STATUS]")) continue;
|
|
486
|
-
const phrases = STATUS_PHRASES_BY_PHASE[nextPhase];
|
|
487
|
-
for (const phrase of phrases) if (block.text.includes(phrase)) {
|
|
488
|
-
this.currentPhase = nextPhase;
|
|
489
|
-
return nextPhase;
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
return null;
|
|
493
|
-
}
|
|
494
|
-
getNextPhase() {
|
|
495
|
-
if (this.currentPhase === "setup") return "1.0-begin";
|
|
496
|
-
const i = PHASES_ORDER.indexOf(this.currentPhase);
|
|
497
|
-
if (i < 0 || i >= PHASES_ORDER.length - 1) return null;
|
|
498
|
-
return PHASES_ORDER[i + 1];
|
|
499
|
-
}
|
|
500
|
-
reset() {
|
|
501
|
-
this.currentPhase = "setup";
|
|
502
|
-
}
|
|
503
|
-
};
|
|
504
|
-
//#endregion
|
|
505
|
-
//#region src/agent/middleware/pipeline.ts
|
|
506
|
-
/**
|
|
507
|
-
* Middleware pipeline orchestrator.
|
|
508
|
-
*
|
|
509
|
-
* Implements the same { onMessage, finalize } interface that runAgent() expects,
|
|
510
|
-
* while internally dispatching to an ordered list of middleware plugins.
|
|
511
|
-
*/
|
|
512
|
-
var MiddlewarePipeline = class {
|
|
513
|
-
middlewares;
|
|
514
|
-
store = /* @__PURE__ */ new Map();
|
|
515
|
-
phaseDetector;
|
|
516
|
-
autoDetectPhases;
|
|
517
|
-
_currentPhase = "setup";
|
|
518
|
-
_currentPhaseFreshContext = true;
|
|
519
|
-
constructor(middlewares, opts) {
|
|
520
|
-
this.middlewares = middlewares;
|
|
521
|
-
this.phaseDetector = opts?.phaseDetector ?? new PhaseDetector();
|
|
522
|
-
this.autoDetectPhases = opts?.autoDetectPhases ?? true;
|
|
523
|
-
const ctx = this.createContext();
|
|
524
|
-
for (const mw of this.middlewares) mw.onInit?.(ctx);
|
|
525
|
-
}
|
|
526
|
-
/** Feed an SDK message through all middleware (satisfies tracker.onMessage) */
|
|
527
|
-
onMessage(message) {
|
|
528
|
-
if (this.autoDetectPhases) {
|
|
529
|
-
const newPhase = this.phaseDetector.detect(message);
|
|
530
|
-
if (newPhase && newPhase !== this._currentPhase) this.transitionPhase(newPhase, false);
|
|
531
|
-
}
|
|
532
|
-
const ctx = this.createContext();
|
|
533
|
-
const storeHandle = this.createStore();
|
|
534
|
-
for (const mw of this.middlewares) mw.onMessage?.(message, ctx, storeHandle);
|
|
535
|
-
}
|
|
536
|
-
/** Finalize the run (satisfies tracker.finalize) */
|
|
537
|
-
finalize(resultMessage, totalDurationMs) {
|
|
538
|
-
const ctx = this.createContext();
|
|
539
|
-
const storeHandle = this.createStore();
|
|
540
|
-
let result;
|
|
541
|
-
for (const mw of this.middlewares) {
|
|
542
|
-
const r = mw.onFinalize?.(resultMessage, totalDurationMs, ctx, storeHandle);
|
|
543
|
-
if (r !== void 0) result = r;
|
|
544
|
-
}
|
|
545
|
-
return result;
|
|
546
|
-
}
|
|
547
|
-
/** Explicit phase start (for phased runner support) */
|
|
548
|
-
startPhase(name, freshContext) {
|
|
549
|
-
this.transitionPhase(name, freshContext);
|
|
550
|
-
}
|
|
551
|
-
transitionPhase(newPhase, freshContext) {
|
|
552
|
-
const oldPhase = this._currentPhase;
|
|
553
|
-
this._currentPhase = newPhase;
|
|
554
|
-
this._currentPhaseFreshContext = freshContext;
|
|
555
|
-
const ctx = this.createContext();
|
|
556
|
-
const storeHandle = this.createStore();
|
|
557
|
-
for (const mw of this.middlewares) mw.onPhaseTransition?.(oldPhase, newPhase, ctx, storeHandle);
|
|
558
|
-
}
|
|
559
|
-
createContext() {
|
|
560
|
-
return {
|
|
561
|
-
currentPhase: this._currentPhase,
|
|
562
|
-
currentPhaseFreshContext: this._currentPhaseFreshContext,
|
|
563
|
-
get: (key) => this.store.get(key)
|
|
564
|
-
};
|
|
565
|
-
}
|
|
566
|
-
createStore() {
|
|
567
|
-
return { set: (key, value) => this.store.set(key, value) };
|
|
568
|
-
}
|
|
569
|
-
};
|
|
570
|
-
//#endregion
|
|
571
|
-
//#region src/agent/middleware/config.ts
|
|
572
|
-
/**
|
|
573
|
-
* Benchmark configuration loader.
|
|
574
|
-
*
|
|
575
|
-
* Loads .benchmark-config.json from the working directory with sensible defaults.
|
|
576
|
-
* All fields are optional — missing fields fall back to defaults.
|
|
577
|
-
*/
|
|
578
|
-
const DEFAULT_CONFIG = {
|
|
579
|
-
plugins: {
|
|
580
|
-
tokens: true,
|
|
581
|
-
cache: true,
|
|
582
|
-
turns: true,
|
|
583
|
-
compactions: true,
|
|
584
|
-
contextSize: true,
|
|
585
|
-
cost: true,
|
|
586
|
-
duration: true,
|
|
587
|
-
summary: true,
|
|
588
|
-
jsonWriter: true
|
|
589
|
-
},
|
|
590
|
-
output: {
|
|
591
|
-
benchmarkPath: WIZARD_BENCHMARK_FILE,
|
|
592
|
-
benchmarkEnabled: true,
|
|
593
|
-
logPath: WIZARD_LOG_FILE,
|
|
594
|
-
logEnabled: true,
|
|
595
|
-
suppressWizardLogs: false
|
|
596
|
-
}
|
|
597
|
-
};
|
|
598
|
-
function loadBenchmarkConfig(installDir) {
|
|
599
|
-
const configPath = runtimeEnv("POSTHOG_WIZARD_BENCHMARK_CONFIG") ?? path.join(installDir, ".benchmark-config.json");
|
|
600
|
-
try {
|
|
601
|
-
const raw = fs.readFileSync(configPath, "utf-8");
|
|
602
|
-
const parsed = JSON.parse(raw);
|
|
603
|
-
const config = {
|
|
604
|
-
plugins: {
|
|
605
|
-
...DEFAULT_CONFIG.plugins,
|
|
606
|
-
...parsed.plugins
|
|
607
|
-
},
|
|
608
|
-
output: {
|
|
609
|
-
...DEFAULT_CONFIG.output,
|
|
610
|
-
...parsed.output
|
|
611
|
-
}
|
|
612
|
-
};
|
|
613
|
-
const benchFile = runtimeEnv("POSTHOG_WIZARD_BENCHMARK_FILE");
|
|
614
|
-
if (benchFile) config.output.benchmarkPath = benchFile;
|
|
615
|
-
const logDir = runtimeEnv("POSTHOG_WIZARD_LOG_DIR");
|
|
616
|
-
if (logDir) config.output.logPath = path.join(logDir, "posthog-wizard.log");
|
|
617
|
-
if (!config.output.benchmarkEnabled) config.plugins.jsonWriter = false;
|
|
618
|
-
logToFile(`${AgentSignals.BENCHMARK} Loaded config from ${configPath}`);
|
|
619
|
-
return config;
|
|
620
|
-
} catch {
|
|
621
|
-
const config = structuredClone(DEFAULT_CONFIG);
|
|
622
|
-
const benchFile2 = runtimeEnv("POSTHOG_WIZARD_BENCHMARK_FILE");
|
|
623
|
-
if (benchFile2) config.output.benchmarkPath = benchFile2;
|
|
624
|
-
const logDir2 = runtimeEnv("POSTHOG_WIZARD_LOG_DIR");
|
|
625
|
-
if (logDir2) config.output.logPath = path.join(logDir2, "posthog-wizard.log");
|
|
626
|
-
return config;
|
|
627
|
-
}
|
|
628
|
-
}
|
|
629
|
-
//#endregion
|
|
630
|
-
//#region src/agent/middleware/benchmarks/turn-counter.ts
|
|
631
|
-
var TurnCounterPlugin = class {
|
|
632
|
-
name = "turns";
|
|
633
|
-
lastMessageId = null;
|
|
634
|
-
phaseTurns = 0;
|
|
635
|
-
totalTurns = 0;
|
|
636
|
-
isDuplicate = false;
|
|
637
|
-
phaseSnapshots = [];
|
|
638
|
-
currentPhase = "setup";
|
|
639
|
-
onMessage(message, _ctx, store) {
|
|
640
|
-
if (message.type !== "assistant") {
|
|
641
|
-
this.isDuplicate = false;
|
|
642
|
-
store.set("turns", this.getData());
|
|
643
|
-
return;
|
|
644
|
-
}
|
|
645
|
-
const msgId = message.message?.id;
|
|
646
|
-
this.isDuplicate = msgId != null && msgId === this.lastMessageId;
|
|
647
|
-
if (msgId) this.lastMessageId = msgId;
|
|
648
|
-
if (!this.isDuplicate) {
|
|
649
|
-
this.phaseTurns++;
|
|
650
|
-
this.totalTurns++;
|
|
651
|
-
}
|
|
652
|
-
store.set("turns", this.getData());
|
|
653
|
-
}
|
|
654
|
-
onPhaseTransition(fromPhase, _toPhase, _ctx, store) {
|
|
655
|
-
this.phaseSnapshots.push({
|
|
656
|
-
phase: fromPhase,
|
|
657
|
-
turns: this.phaseTurns
|
|
658
|
-
});
|
|
659
|
-
this.currentPhase = _toPhase;
|
|
660
|
-
this.phaseTurns = 0;
|
|
661
|
-
this.lastMessageId = null;
|
|
662
|
-
store.set("turns", this.getData());
|
|
663
|
-
}
|
|
664
|
-
onFinalize(_resultMessage, _totalDurationMs, _ctx, store) {
|
|
665
|
-
this.phaseSnapshots.push({
|
|
666
|
-
phase: this.currentPhase,
|
|
667
|
-
turns: this.phaseTurns
|
|
668
|
-
});
|
|
669
|
-
store.set("turns", this.getData());
|
|
670
|
-
}
|
|
671
|
-
getData() {
|
|
672
|
-
return {
|
|
673
|
-
isDuplicate: this.isDuplicate,
|
|
674
|
-
phaseTurns: this.phaseTurns,
|
|
675
|
-
totalTurns: this.totalTurns,
|
|
676
|
-
phaseSnapshots: [...this.phaseSnapshots]
|
|
677
|
-
};
|
|
678
|
-
}
|
|
679
|
-
};
|
|
680
|
-
//#endregion
|
|
681
|
-
//#region src/agent/middleware/benchmarks/token-tracker.ts
|
|
682
|
-
var TokenTrackerPlugin = class {
|
|
683
|
-
name = "tokens";
|
|
684
|
-
phaseInput = 0;
|
|
685
|
-
phaseOutput = 0;
|
|
686
|
-
totalInput = 0;
|
|
687
|
-
totalOutput = 0;
|
|
688
|
-
lastUsage = null;
|
|
689
|
-
phaseSnapshots = [];
|
|
690
|
-
currentPhase = "setup";
|
|
691
|
-
phaseMessagesWithUsage = 0;
|
|
692
|
-
onMessage(message, ctx, store) {
|
|
693
|
-
if (message.type !== "assistant") return;
|
|
694
|
-
if (ctx.get("turns")?.isDuplicate) return;
|
|
695
|
-
const usage = message.message?.usage;
|
|
696
|
-
if (usage) {
|
|
697
|
-
const input = Number(usage.input_tokens ?? 0) + Number(usage.cache_read_input_tokens ?? 0) + Number(usage.cache_creation_input_tokens ?? 0);
|
|
698
|
-
const output = Number(usage.output_tokens ?? 0);
|
|
699
|
-
this.phaseInput += input;
|
|
700
|
-
this.phaseOutput += output;
|
|
701
|
-
this.totalInput += input;
|
|
702
|
-
this.totalOutput += output;
|
|
703
|
-
this.lastUsage = usage;
|
|
704
|
-
this.phaseMessagesWithUsage += 1;
|
|
705
|
-
}
|
|
706
|
-
store.set("tokens", this.getData());
|
|
707
|
-
}
|
|
708
|
-
onPhaseTransition(fromPhase, toPhase, _ctx, store) {
|
|
709
|
-
this.phaseSnapshots.push({
|
|
710
|
-
phase: fromPhase,
|
|
711
|
-
inputTokens: this.phaseInput,
|
|
712
|
-
outputTokens: this.phaseOutput,
|
|
713
|
-
messagesWithUsage: this.phaseMessagesWithUsage
|
|
714
|
-
});
|
|
715
|
-
this.currentPhase = toPhase;
|
|
716
|
-
this.phaseInput = 0;
|
|
717
|
-
this.phaseOutput = 0;
|
|
718
|
-
this.phaseMessagesWithUsage = 0;
|
|
719
|
-
store.set("tokens", this.getData());
|
|
720
|
-
}
|
|
721
|
-
onFinalize(_resultMessage, _totalDurationMs, _ctx, store) {
|
|
722
|
-
this.phaseSnapshots.push({
|
|
723
|
-
phase: this.currentPhase,
|
|
724
|
-
inputTokens: this.phaseInput,
|
|
725
|
-
outputTokens: this.phaseOutput,
|
|
726
|
-
messagesWithUsage: this.phaseMessagesWithUsage
|
|
727
|
-
});
|
|
728
|
-
store.set("tokens", this.getData());
|
|
729
|
-
}
|
|
730
|
-
getData() {
|
|
731
|
-
return {
|
|
732
|
-
phaseInput: this.phaseInput,
|
|
733
|
-
phaseOutput: this.phaseOutput,
|
|
734
|
-
totalInput: this.totalInput,
|
|
735
|
-
totalOutput: this.totalOutput,
|
|
736
|
-
lastUsage: this.lastUsage,
|
|
737
|
-
phaseSnapshots: [...this.phaseSnapshots]
|
|
738
|
-
};
|
|
739
|
-
}
|
|
740
|
-
};
|
|
741
|
-
//#endregion
|
|
742
|
-
//#region src/agent/middleware/benchmarks/cache-tracker.ts
|
|
743
|
-
var CacheTrackerPlugin = class {
|
|
744
|
-
name = "cache";
|
|
745
|
-
phaseRead = 0;
|
|
746
|
-
phaseCreation = 0;
|
|
747
|
-
phaseCreation5m = 0;
|
|
748
|
-
phaseCreation1h = 0;
|
|
749
|
-
totalRead = 0;
|
|
750
|
-
totalCreation = 0;
|
|
751
|
-
totalCreation5m = 0;
|
|
752
|
-
totalCreation1h = 0;
|
|
753
|
-
phaseSnapshots = [];
|
|
754
|
-
currentPhase = "setup";
|
|
755
|
-
onMessage(message, ctx, store) {
|
|
756
|
-
if (message.type !== "assistant") return;
|
|
757
|
-
if (ctx.get("turns")?.isDuplicate) return;
|
|
758
|
-
const usage = message.message?.usage;
|
|
759
|
-
if (usage) {
|
|
760
|
-
const read = Number(usage.cache_read_input_tokens ?? 0);
|
|
761
|
-
const creation = Number(usage.cache_creation_input_tokens ?? 0);
|
|
762
|
-
const cc = usage.cache_creation;
|
|
763
|
-
const creation5m = Number(cc?.ephemeral_5m_input_tokens ?? 0);
|
|
764
|
-
const creation1h = Number(cc?.ephemeral_1h_input_tokens ?? 0);
|
|
765
|
-
this.phaseRead += read;
|
|
766
|
-
this.phaseCreation += creation;
|
|
767
|
-
this.phaseCreation5m += creation5m;
|
|
768
|
-
this.phaseCreation1h += creation1h;
|
|
769
|
-
this.totalRead += read;
|
|
770
|
-
this.totalCreation += creation;
|
|
771
|
-
}
|
|
772
|
-
store.set("cache", this.getData());
|
|
773
|
-
}
|
|
774
|
-
onPhaseTransition(fromPhase, toPhase, _ctx, store) {
|
|
775
|
-
this.phaseSnapshots.push({
|
|
776
|
-
phase: fromPhase,
|
|
777
|
-
cacheReadTokens: this.phaseRead,
|
|
778
|
-
cacheCreationTokens: this.phaseCreation,
|
|
779
|
-
cacheCreation5m: this.phaseCreation5m,
|
|
780
|
-
cacheCreation1h: this.phaseCreation1h
|
|
781
|
-
});
|
|
782
|
-
this.currentPhase = toPhase;
|
|
783
|
-
this.phaseRead = 0;
|
|
784
|
-
this.phaseCreation = 0;
|
|
785
|
-
this.phaseCreation5m = 0;
|
|
786
|
-
this.phaseCreation1h = 0;
|
|
787
|
-
store.set("cache", this.getData());
|
|
788
|
-
}
|
|
789
|
-
onFinalize(_resultMessage, _totalDurationMs, _ctx, store) {
|
|
790
|
-
this.phaseSnapshots.push({
|
|
791
|
-
phase: this.currentPhase,
|
|
792
|
-
cacheReadTokens: this.phaseRead,
|
|
793
|
-
cacheCreationTokens: this.phaseCreation,
|
|
794
|
-
cacheCreation5m: this.phaseCreation5m,
|
|
795
|
-
cacheCreation1h: this.phaseCreation1h
|
|
796
|
-
});
|
|
797
|
-
store.set("cache", this.getData());
|
|
798
|
-
}
|
|
799
|
-
getData() {
|
|
800
|
-
return {
|
|
801
|
-
phaseRead: this.phaseRead,
|
|
802
|
-
phaseCreation: this.phaseCreation,
|
|
803
|
-
totalRead: this.totalRead,
|
|
804
|
-
totalCreation: this.totalCreation,
|
|
805
|
-
totalCreation5m: this.totalCreation5m,
|
|
806
|
-
totalCreation1h: this.totalCreation1h,
|
|
807
|
-
phaseSnapshots: [...this.phaseSnapshots]
|
|
808
|
-
};
|
|
809
|
-
}
|
|
810
|
-
};
|
|
811
|
-
//#endregion
|
|
812
|
-
//#region src/agent/middleware/benchmarks/compaction-tracker.ts
|
|
813
|
-
var CompactionTrackerPlugin = class {
|
|
814
|
-
name = "compactions";
|
|
815
|
-
phaseCompactions = 0;
|
|
816
|
-
phasePreTokens = [];
|
|
817
|
-
totalCompactions = 0;
|
|
818
|
-
phaseSnapshots = [];
|
|
819
|
-
currentPhase = "setup";
|
|
820
|
-
onMessage(message, ctx, store) {
|
|
821
|
-
if (message.type !== "system" || message.subtype !== "compact_boundary") return;
|
|
822
|
-
const preTokens = message.compact_metadata?.pre_tokens ?? 0;
|
|
823
|
-
const trigger = message.compact_metadata?.trigger ?? "unknown";
|
|
824
|
-
this.phaseCompactions++;
|
|
825
|
-
this.totalCompactions++;
|
|
826
|
-
this.phasePreTokens.push(preTokens);
|
|
827
|
-
logToFile(`${AgentSignals.BENCHMARK} [COMPACTION] Context compacted during "${ctx.currentPhase}" (trigger: ${trigger}, pre_tokens: ${preTokens})`);
|
|
828
|
-
store.set("compactions", this.getData());
|
|
829
|
-
}
|
|
830
|
-
onPhaseTransition(fromPhase, toPhase, _ctx, store) {
|
|
831
|
-
this.phaseSnapshots.push({
|
|
832
|
-
phase: fromPhase,
|
|
833
|
-
compactions: this.phaseCompactions,
|
|
834
|
-
preTokens: [...this.phasePreTokens]
|
|
835
|
-
});
|
|
836
|
-
this.currentPhase = toPhase;
|
|
837
|
-
this.phaseCompactions = 0;
|
|
838
|
-
this.phasePreTokens = [];
|
|
839
|
-
store.set("compactions", this.getData());
|
|
840
|
-
}
|
|
841
|
-
onFinalize(_resultMessage, _totalDurationMs, _ctx, store) {
|
|
842
|
-
this.phaseSnapshots.push({
|
|
843
|
-
phase: this.currentPhase,
|
|
844
|
-
compactions: this.phaseCompactions,
|
|
845
|
-
preTokens: [...this.phasePreTokens]
|
|
846
|
-
});
|
|
847
|
-
store.set("compactions", this.getData());
|
|
848
|
-
}
|
|
849
|
-
getData() {
|
|
850
|
-
return {
|
|
851
|
-
phaseCompactions: this.phaseCompactions,
|
|
852
|
-
phasePreTokens: [...this.phasePreTokens],
|
|
853
|
-
totalCompactions: this.totalCompactions,
|
|
854
|
-
phaseSnapshots: [...this.phaseSnapshots]
|
|
855
|
-
};
|
|
856
|
-
}
|
|
857
|
-
};
|
|
858
|
-
//#endregion
|
|
859
|
-
//#region src/agent/middleware/benchmarks/context-size-tracker.ts
|
|
860
|
-
var ContextSizeTrackerPlugin = class {
|
|
861
|
-
name = "contextSize";
|
|
862
|
-
phaseSnapshots = [];
|
|
863
|
-
lastContextTokensOut;
|
|
864
|
-
onPhaseTransition(fromPhase, _toPhase, ctx, store) {
|
|
865
|
-
const tokens = ctx.get("tokens");
|
|
866
|
-
const contextTokensOut = this.computeContextTokensOut(tokens?.lastUsage);
|
|
867
|
-
this.phaseSnapshots.push({
|
|
868
|
-
phase: fromPhase,
|
|
869
|
-
contextTokensIn: ctx.currentPhaseFreshContext ? void 0 : this.lastContextTokensOut,
|
|
870
|
-
contextTokensOut,
|
|
871
|
-
freshContext: ctx.currentPhaseFreshContext
|
|
872
|
-
});
|
|
873
|
-
this.lastContextTokensOut = contextTokensOut;
|
|
874
|
-
store.set("contextSize", this.getData());
|
|
875
|
-
}
|
|
876
|
-
onFinalize(_resultMessage, _totalDurationMs, ctx, store) {
|
|
877
|
-
const tokens = ctx.get("tokens");
|
|
878
|
-
const contextTokensOut = this.computeContextTokensOut(tokens?.lastUsage);
|
|
879
|
-
this.phaseSnapshots.push({
|
|
880
|
-
phase: ctx.currentPhase,
|
|
881
|
-
contextTokensIn: ctx.currentPhaseFreshContext ? void 0 : this.lastContextTokensOut,
|
|
882
|
-
contextTokensOut,
|
|
883
|
-
freshContext: ctx.currentPhaseFreshContext
|
|
884
|
-
});
|
|
885
|
-
store.set("contextSize", this.getData());
|
|
886
|
-
}
|
|
887
|
-
computeContextTokensOut(usage) {
|
|
888
|
-
if (!usage) return void 0;
|
|
889
|
-
return Number(usage.input_tokens ?? 0) + Number(usage.cache_read_input_tokens ?? 0) + Number(usage.cache_creation_input_tokens ?? 0);
|
|
890
|
-
}
|
|
891
|
-
getData() {
|
|
892
|
-
return { phaseSnapshots: [...this.phaseSnapshots] };
|
|
893
|
-
}
|
|
894
|
-
};
|
|
895
|
-
//#endregion
|
|
896
|
-
//#region src/shared/token-pricing.ts
|
|
897
|
-
/**
|
|
898
|
-
* Shared exact-match USD-per-token pricing by model id, and the cost formula
|
|
899
|
-
* applied to a token usage delta.
|
|
900
|
-
*
|
|
901
|
-
* Single source of truth for both the benchmark's `CostTrackerPlugin`
|
|
902
|
-
* (`@lib/middleware/benchmarks/cost-tracker`) and the live token/cost HUD
|
|
903
|
-
* (`agent-interface.ts`'s per-turn usage accumulation), so the two estimates
|
|
904
|
-
* never drift apart. Both reconcile against the SDK's own authoritative
|
|
905
|
-
* `total_cost_usd` once the run finishes — this table only prices the
|
|
906
|
-
* *live-updating* estimate shown while the agent is still running.
|
|
907
|
-
*
|
|
908
|
-
* Model-aware because a run's model isn't fixed: `AgentConfig.modelOverride`
|
|
909
|
-
* switches specific programs to Haiku (e.g. source-map detection), and the
|
|
910
|
-
* SDK reports a `model` string on every individual assistant turn (subagents
|
|
911
|
-
* dispatched via the Agent tool can run on a different model than the main
|
|
912
|
-
* session).
|
|
913
|
-
*
|
|
914
|
-
* Deliberately matched by *exact* model id, not family name (no
|
|
915
|
-
* `model.includes('opus')`-style fallback): different versions of the same
|
|
916
|
-
* family are priced differently — e.g. Claude Opus 4.5 ($5/$25 per Mtok) is
|
|
917
|
-
* half of Opus 4.1's rate ($15/$75) — so matching on "opus" or "sonnet"
|
|
918
|
-
* alone silently mis-prices a newer/older sibling at the wrong rate. The
|
|
919
|
-
* only normalization applied is stripping a trailing release-date suffix
|
|
920
|
-
* (`stripDateSuffix`), since Anthropic's dated and undated ids for the
|
|
921
|
-
* *same* version always carry the same price (verified for every entry
|
|
922
|
-
* below). Claude Sonnet 5 is the one exception with a *time-dependent*
|
|
923
|
-
* price — see `sonnet5Price` — rather than a fixed table entry.
|
|
924
|
-
*
|
|
925
|
-
* Prices verified against https://models.dev/api.json (Anthropic section)
|
|
926
|
-
* on 2026-07-01 — re-verify there when adding an entry or bumping
|
|
927
|
-
* DEFAULT_AGENT_MODEL/HAIKU_MODEL, since this table isn't fetched live.
|
|
928
|
-
*/
|
|
929
|
-
/**
|
|
930
|
-
* `cacheCreation1h` isn't published by models.dev (it only has one
|
|
931
|
-
* cache-write rate); extrapolated at `input × 2`, the ratio that holds for
|
|
932
|
-
* every model below (mirrors `cacheCreation5m` = `input × 1.25`, `cacheRead`
|
|
933
|
-
* = `input × 0.1` — Anthropic's standard prompt-caching multipliers).
|
|
934
|
-
* Rounded to 6 decimal places to clean up binary float noise from the
|
|
935
|
-
* multiplication (e.g. `3 * 0.1` is `0.30000000000000004`, not `0.3`) — well
|
|
936
|
-
* past cent-level precision at the 1M-token scale these prices are quoted at.
|
|
937
|
-
*/
|
|
938
|
-
function pricingFromInput(input, output) {
|
|
939
|
-
const round = (n) => Math.round(n * 1e6) / 1e6;
|
|
940
|
-
return {
|
|
941
|
-
input,
|
|
942
|
-
output,
|
|
943
|
-
cacheRead: round(input * .1),
|
|
944
|
-
cacheCreation5m: round(input * 1.25),
|
|
945
|
-
cacheCreation1h: round(input * 2)
|
|
946
|
-
};
|
|
947
|
-
}
|
|
948
|
-
/**
|
|
949
|
-
* Exact-match price table, keyed by the *undated* model id prefix —
|
|
950
|
-
* `pricePerMtokForModel` strips a dated suffix before falling back to this
|
|
951
|
-
* table, so a dated Anthropic id resolves via its undated entry without
|
|
952
|
-
* needing a duplicate key. Ids the wizard no longer dispatches on stay
|
|
953
|
-
* priced, since a subagent turn or the SDK can still report one (a turn on
|
|
954
|
-
* an id NOT in this table contributes $0 to the live estimate rather than
|
|
955
|
-
* guessing, see `pricePerMtokForModel`).
|
|
956
|
-
*
|
|
957
|
-
* `SONNET_5_MODEL` is intentionally absent: its price is time-dependent
|
|
958
|
-
* (promotional launch discount), so `pricePerMtokForModel` resolves it via
|
|
959
|
-
* `sonnet5Price`. It is also `DEFAULT_AGENT_MODEL`, so the no-model fallback
|
|
960
|
-
* goes through that branch.
|
|
961
|
-
*/
|
|
962
|
-
const PRICE_TABLE = {
|
|
963
|
-
"claude-sonnet-4-6": pricingFromInput(3, 15),
|
|
964
|
-
[HAIKU_MODEL]: pricingFromInput(1, 5),
|
|
965
|
-
"claude-opus-4-5": pricingFromInput(5, 25)
|
|
966
|
-
};
|
|
967
|
-
/**
|
|
968
|
-
* Claude Sonnet 5 launched at a promotional discount through 2026-08-31
|
|
969
|
-
* UTC; from 2026-09-01 it reverts to the same rate as Sonnet 4.6 ($3/$15)
|
|
970
|
-
* — not a coincidence, Anthropic prices it identically to 4.6 once the
|
|
971
|
-
* promo ends. Re-verify against https://models.dev/api.json if this ever
|
|
972
|
-
* looks off, and fold this special case into a flat `PRICE_TABLE` entry at
|
|
973
|
-
* the post-promo rate once the window is well past.
|
|
974
|
-
*/
|
|
975
|
-
const SONNET_5_PROMO_ENDS_UTC = /* @__PURE__ */ new Date("2026-09-01T00:00:00Z");
|
|
976
|
-
function sonnet5Price(now) {
|
|
977
|
-
return now < SONNET_5_PROMO_ENDS_UTC ? pricingFromInput(2, 10) : pricingFromInput(3, 15);
|
|
978
|
-
}
|
|
979
|
-
/** Strips a trailing 8-digit release-date suffix (e.g. `-20251001`) — never
|
|
980
|
-
* the version segment, so differently-versioned models are never conflated.
|
|
981
|
-
* `claude-haiku-4-5-20251001` → `claude-haiku-4-5`; `claude-sonnet-4-6` (no
|
|
982
|
-
* date suffix) is returned unchanged. */
|
|
983
|
-
function stripDateSuffix(model) {
|
|
984
|
-
return model.replace(/-\d{8}$/, "");
|
|
985
|
-
}
|
|
986
|
-
/**
|
|
987
|
-
* Exact-match pricing lookup. `undefined` model (a caller with no model
|
|
988
|
-
* context, e.g. the benchmark's `CostTrackerPlugin`) falls back to
|
|
989
|
-
* `DEFAULT_AGENT_MODEL`'s price, the default for interactive runs. A
|
|
990
|
-
* *given* but unrecognized model id returns `undefined` rather than
|
|
991
|
-
* guessing from its family name — seeing `undefined` and skipping that
|
|
992
|
-
* delta is strictly more accurate than the family-fallback this replaced,
|
|
993
|
-
* which silently priced e.g. every Opus turn at Opus 4.1's rate.
|
|
994
|
-
*
|
|
995
|
-
* `now` is injectable (defaults to the real clock) only so
|
|
996
|
-
* `sonnet5Price`'s promo-window check is deterministic in tests — no
|
|
997
|
-
* other entry in this table is time-dependent.
|
|
998
|
-
*/
|
|
999
|
-
function pricePerMtokForModel(model, now = /* @__PURE__ */ new Date()) {
|
|
1000
|
-
const id = model ?? "claude-sonnet-5";
|
|
1001
|
-
const undated = stripDateSuffix(id);
|
|
1002
|
-
if (undated === "claude-sonnet-5") return sonnet5Price(now);
|
|
1003
|
-
return PRICE_TABLE[id] ?? PRICE_TABLE[undated];
|
|
1004
|
-
}
|
|
1005
|
-
/**
|
|
1006
|
-
* Cost in USD for one usage delta, priced at `usage.model`'s exact rates
|
|
1007
|
-
* (see `pricePerMtokForModel`) — 0 when `model` is given but not in the
|
|
1008
|
-
* table, rather than guessing. `cacheCreation5m`/`cacheCreation1h` come from
|
|
1009
|
-
* the SDK's `usage.cache_creation` breakdown, which is only reported on
|
|
1010
|
-
* some turns — when both are 0, `cacheCreationTokens` (the plain
|
|
1011
|
-
* `cache_creation_input_tokens` total) is priced at the 5m rate instead, so
|
|
1012
|
-
* a turn without the breakdown still gets a reasonable estimate rather than
|
|
1013
|
-
* being priced at $0.
|
|
1014
|
-
*
|
|
1015
|
-
* Takes the same shape as `TokenUsageDelta` (`@ui/wizard-ui`) so a caller
|
|
1016
|
-
* that already has one — `WizardStore.addTokenUsage` — can pass it straight
|
|
1017
|
-
* through instead of re-listing its fields in a fixed positional order.
|
|
1018
|
-
*/
|
|
1019
|
-
function computeTokenCostUsd(usage) {
|
|
1020
|
-
const price = pricePerMtokForModel(usage.model);
|
|
1021
|
-
if (!price) return 0;
|
|
1022
|
-
const hasBreakdown = usage.cacheCreation5m > 0 || usage.cacheCreation1h > 0;
|
|
1023
|
-
return usage.inputTokens * (price.input / 1e6) + usage.outputTokens * (price.output / 1e6) + usage.cacheReadTokens * (price.cacheRead / 1e6) + (hasBreakdown ? usage.cacheCreation5m * (price.cacheCreation5m / 1e6) + usage.cacheCreation1h * (price.cacheCreation1h / 1e6) : usage.cacheCreationTokens * (price.cacheCreation5m / 1e6));
|
|
1024
|
-
}
|
|
1025
|
-
/**
|
|
1026
|
-
* Shared display formatters for the hidden Ctrl+T HUD (`TokenCostHud`) and
|
|
1027
|
-
* the post-exit scrollback line (`exit-line.ts`) — kept here, not in either
|
|
1028
|
-
* of those files, since `exit-line.ts` is deliberately free of `ink`/React
|
|
1029
|
-
* imports (it must stay a pure, unit-testable function), and importing a
|
|
1030
|
-
* named export from a `.tsx` file would pull `ink` into its module graph
|
|
1031
|
-
* anyway. This module has no such dependency.
|
|
1032
|
-
*/
|
|
1033
|
-
function formatTokenCount(n) {
|
|
1034
|
-
if (n >= 1e6) return `${(n / 1e6).toFixed(2)}M`;
|
|
1035
|
-
if (n >= 1e3) return `${(n / 1e3).toFixed(1)}K`;
|
|
1036
|
-
return n.toLocaleString();
|
|
1037
|
-
}
|
|
1038
|
-
function formatCostUsd(usd) {
|
|
1039
|
-
if (usd > 0 && usd < .01) return `$${usd.toFixed(4)}`;
|
|
1040
|
-
return `$${usd.toFixed(2)}`;
|
|
1041
|
-
}
|
|
1042
|
-
//#endregion
|
|
1043
|
-
//#region src/agent/middleware/benchmarks/cost-tracker.ts
|
|
1044
|
-
var CostTrackerPlugin = class {
|
|
1045
|
-
name = "cost";
|
|
1046
|
-
phaseCosts = [];
|
|
1047
|
-
totalCost = 0;
|
|
1048
|
-
onPhaseTransition(fromPhase, _toPhase, ctx, store) {
|
|
1049
|
-
const tokens = ctx.get("tokens");
|
|
1050
|
-
const cache = ctx.get("cache");
|
|
1051
|
-
const tokenSnap = tokens?.phaseSnapshots.at(-1);
|
|
1052
|
-
const cacheSnap = cache?.phaseSnapshots.at(-1);
|
|
1053
|
-
const totalIn = tokenSnap?.inputTokens ?? 0;
|
|
1054
|
-
const read = cacheSnap?.cacheReadTokens ?? 0;
|
|
1055
|
-
const creation = cacheSnap?.cacheCreationTokens ?? 0;
|
|
1056
|
-
const c5m = cacheSnap?.cacheCreation5m ?? 0;
|
|
1057
|
-
const c1h = cacheSnap?.cacheCreation1h ?? 0;
|
|
1058
|
-
const phaseCost = computeTokenCostUsd({
|
|
1059
|
-
inputTokens: Math.max(0, totalIn - read - creation),
|
|
1060
|
-
outputTokens: tokenSnap?.outputTokens ?? 0,
|
|
1061
|
-
cacheReadTokens: read,
|
|
1062
|
-
cacheCreation5m: c5m,
|
|
1063
|
-
cacheCreation1h: c1h,
|
|
1064
|
-
cacheCreationTokens: creation
|
|
1065
|
-
});
|
|
1066
|
-
this.phaseCosts.push({
|
|
1067
|
-
phase: fromPhase,
|
|
1068
|
-
cost: phaseCost
|
|
1069
|
-
});
|
|
1070
|
-
this.totalCost += phaseCost;
|
|
1071
|
-
store.set("cost", this.getData());
|
|
1072
|
-
}
|
|
1073
|
-
onFinalize(resultMessage, _totalDurationMs, ctx, store) {
|
|
1074
|
-
const tokens = ctx.get("tokens");
|
|
1075
|
-
const cache = ctx.get("cache");
|
|
1076
|
-
const tokenSnap = tokens?.phaseSnapshots.at(-1);
|
|
1077
|
-
const cacheSnap = cache?.phaseSnapshots.at(-1);
|
|
1078
|
-
const totalIn = tokenSnap?.inputTokens ?? 0;
|
|
1079
|
-
const read = cacheSnap?.cacheReadTokens ?? 0;
|
|
1080
|
-
const creation = cacheSnap?.cacheCreationTokens ?? 0;
|
|
1081
|
-
const c5m = cacheSnap?.cacheCreation5m ?? 0;
|
|
1082
|
-
const c1h = cacheSnap?.cacheCreation1h ?? 0;
|
|
1083
|
-
const lastPhaseCost = computeTokenCostUsd({
|
|
1084
|
-
inputTokens: Math.max(0, totalIn - read - creation),
|
|
1085
|
-
outputTokens: tokenSnap?.outputTokens ?? 0,
|
|
1086
|
-
cacheReadTokens: read,
|
|
1087
|
-
cacheCreation5m: c5m,
|
|
1088
|
-
cacheCreation1h: c1h,
|
|
1089
|
-
cacheCreationTokens: creation
|
|
1090
|
-
});
|
|
1091
|
-
this.phaseCosts.push({
|
|
1092
|
-
phase: ctx.currentPhase,
|
|
1093
|
-
cost: lastPhaseCost
|
|
1094
|
-
});
|
|
1095
|
-
this.totalCost += lastPhaseCost;
|
|
1096
|
-
const sdkTotal = Number(resultMessage?.usage?.total_cost_usd ?? 0) || Number(resultMessage?.total_cost_usd ?? 0);
|
|
1097
|
-
if (sdkTotal > 0 && this.totalCost > 0) {
|
|
1098
|
-
const scale = sdkTotal / this.totalCost;
|
|
1099
|
-
this.phaseCosts = this.phaseCosts.map((p) => ({
|
|
1100
|
-
phase: p.phase,
|
|
1101
|
-
cost: p.cost * scale
|
|
1102
|
-
}));
|
|
1103
|
-
this.totalCost = sdkTotal;
|
|
1104
|
-
}
|
|
1105
|
-
store.set("cost", this.getData());
|
|
1106
|
-
}
|
|
1107
|
-
getData() {
|
|
1108
|
-
return {
|
|
1109
|
-
totalCost: this.totalCost,
|
|
1110
|
-
phaseCosts: [...this.phaseCosts]
|
|
1111
|
-
};
|
|
1112
|
-
}
|
|
1113
|
-
};
|
|
1114
|
-
//#endregion
|
|
1115
|
-
//#region src/agent/middleware/benchmarks/duration-tracker.ts
|
|
1116
|
-
var DurationTrackerPlugin = class {
|
|
1117
|
-
name = "duration";
|
|
1118
|
-
phaseStartTime = Date.now();
|
|
1119
|
-
phaseSnapshots = [];
|
|
1120
|
-
onPhaseTransition(fromPhase, _toPhase, _ctx, store) {
|
|
1121
|
-
const now = Date.now();
|
|
1122
|
-
this.phaseSnapshots.push({
|
|
1123
|
-
phase: fromPhase,
|
|
1124
|
-
startTime: this.phaseStartTime,
|
|
1125
|
-
endTime: now,
|
|
1126
|
-
durationMs: now - this.phaseStartTime
|
|
1127
|
-
});
|
|
1128
|
-
this.phaseStartTime = now;
|
|
1129
|
-
store.set("duration", {
|
|
1130
|
-
phaseSnapshots: [...this.phaseSnapshots],
|
|
1131
|
-
totalDurationMs: 0
|
|
1132
|
-
});
|
|
1133
|
-
}
|
|
1134
|
-
onFinalize(_resultMessage, totalDurationMs, ctx, store) {
|
|
1135
|
-
const now = Date.now();
|
|
1136
|
-
this.phaseSnapshots.push({
|
|
1137
|
-
phase: ctx.currentPhase,
|
|
1138
|
-
startTime: this.phaseStartTime,
|
|
1139
|
-
endTime: now,
|
|
1140
|
-
durationMs: now - this.phaseStartTime
|
|
1141
|
-
});
|
|
1142
|
-
store.set("duration", {
|
|
1143
|
-
phaseSnapshots: [...this.phaseSnapshots],
|
|
1144
|
-
totalDurationMs
|
|
1145
|
-
});
|
|
1146
|
-
}
|
|
1147
|
-
};
|
|
1148
|
-
//#endregion
|
|
1149
|
-
//#region src/agent/middleware/benchmarks/summary.ts
|
|
1150
|
-
function fmtDuration(ms) {
|
|
1151
|
-
const s = Math.round(ms / 1e3);
|
|
1152
|
-
const m = Math.floor(s / 60);
|
|
1153
|
-
return m > 0 ? `${m}m ${s % 60}s` : `${s}s`;
|
|
1154
|
-
}
|
|
1155
|
-
function fmtTok(n) {
|
|
1156
|
-
if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
|
|
1157
|
-
if (n >= 1e3) return `${(n / 1e3).toFixed(1)}K`;
|
|
1158
|
-
return n.toLocaleString();
|
|
1159
|
-
}
|
|
1160
|
-
function fmtCost(usd) {
|
|
1161
|
-
if (usd > 0 && usd < .01) return `$${usd.toFixed(4)}`;
|
|
1162
|
-
return `$${usd.toFixed(2)}`;
|
|
1163
|
-
}
|
|
1164
|
-
function printPhase(s) {
|
|
1165
|
-
const baseIn = Math.max(0, s.inputTokens - s.cacheRead - s.cacheCreation5m - s.cacheCreation1h);
|
|
1166
|
-
return [
|
|
1167
|
-
`${s.phase}: ${fmtDuration(s.durationMs)}, ${s.turns} turns, cost: ${fmtCost(s.cost)}`,
|
|
1168
|
-
` in: ${fmtTok(baseIn)}, out: ${fmtTok(s.outputTokens)}, cache_read: ${fmtTok(s.cacheRead)}, cache_5m: ${fmtTok(s.cacheCreation5m)}, cache_1h: ${fmtTok(s.cacheCreation1h)}`,
|
|
1169
|
-
s.compactions > 0 ? ` ${s.compactions} compaction(s)` : null,
|
|
1170
|
-
s.contextOut !== void 0 ? ` ctx_out: ${fmtTok(s.contextOut)}` : null
|
|
1171
|
-
].filter(Boolean).join("\n");
|
|
1172
|
-
}
|
|
1173
|
-
function getPhaseStats(i, ctx) {
|
|
1174
|
-
const dur = ctx.get("duration")?.phaseSnapshots[i];
|
|
1175
|
-
if (!dur) return null;
|
|
1176
|
-
const tokens = ctx.get("tokens");
|
|
1177
|
-
const turns = ctx.get("turns");
|
|
1178
|
-
const cost = ctx.get("cost");
|
|
1179
|
-
const compactions = ctx.get("compactions");
|
|
1180
|
-
const contextSize = ctx.get("contextSize");
|
|
1181
|
-
const cache = ctx.get("cache");
|
|
1182
|
-
return {
|
|
1183
|
-
phase: dur.phase,
|
|
1184
|
-
durationMs: dur.durationMs,
|
|
1185
|
-
turns: turns?.phaseSnapshots[i]?.turns ?? 0,
|
|
1186
|
-
inputTokens: tokens?.phaseSnapshots[i]?.inputTokens ?? 0,
|
|
1187
|
-
outputTokens: tokens?.phaseSnapshots[i]?.outputTokens ?? 0,
|
|
1188
|
-
cacheRead: cache?.phaseSnapshots[i]?.cacheReadTokens ?? 0,
|
|
1189
|
-
cacheCreation5m: cache?.phaseSnapshots[i]?.cacheCreation5m ?? 0,
|
|
1190
|
-
cacheCreation1h: cache?.phaseSnapshots[i]?.cacheCreation1h ?? 0,
|
|
1191
|
-
cost: cost?.phaseCosts[i]?.cost ?? 0,
|
|
1192
|
-
compactions: compactions?.phaseSnapshots[i]?.compactions ?? 0,
|
|
1193
|
-
contextOut: contextSize?.phaseSnapshots[i]?.contextTokensOut
|
|
1194
|
-
};
|
|
1195
|
-
}
|
|
1196
|
-
var SummaryPlugin = class {
|
|
1197
|
-
name = "summary";
|
|
1198
|
-
spinner;
|
|
1199
|
-
info;
|
|
1200
|
-
constructor(spinner, emit) {
|
|
1201
|
-
this.spinner = spinner;
|
|
1202
|
-
this.info = (message) => emit({
|
|
1203
|
-
kind: "log",
|
|
1204
|
-
level: "info",
|
|
1205
|
-
message
|
|
1206
|
-
});
|
|
1207
|
-
}
|
|
1208
|
-
onPhaseTransition(fromPhase, toPhase, ctx, _store) {
|
|
1209
|
-
const stats = getPhaseStats((ctx.get("duration")?.phaseSnapshots.length ?? 1) - 1, ctx);
|
|
1210
|
-
if (stats) this.spinner.stop(`${AgentSignals.BENCHMARK} ${printPhase(stats)}`);
|
|
1211
|
-
else this.spinner.stop(`${AgentSignals.BENCHMARK} ${fromPhase}`);
|
|
1212
|
-
this.info(`${AgentSignals.BENCHMARK} Starting phase: ${toPhase}`);
|
|
1213
|
-
this.spinner.start(`Integrating PostHog (${toPhase})...`);
|
|
1214
|
-
}
|
|
1215
|
-
onFinalize(_resultMessage, totalDurationMs, ctx, _store) {
|
|
1216
|
-
const duration = ctx.get("duration");
|
|
1217
|
-
const cost = ctx.get("cost");
|
|
1218
|
-
const tokens = ctx.get("tokens");
|
|
1219
|
-
const cache = ctx.get("cache");
|
|
1220
|
-
const phaseCount = duration?.phaseSnapshots.length ?? 0;
|
|
1221
|
-
const totalCost = cost?.totalCost ?? 0;
|
|
1222
|
-
this.info("");
|
|
1223
|
-
this.info(`◇ ${AgentSignals.BENCHMARK} ${phaseCount} phases in ${fmtDuration(totalDurationMs)}, cost: ${fmtCost(totalCost)}`);
|
|
1224
|
-
this.info(` total in: ${fmtTok(tokens?.totalInput ?? 0)}, out: ${fmtTok(tokens?.totalOutput ?? 0)}, cache_read: ${fmtTok(cache?.totalRead ?? 0)}, cache_5m: ${fmtTok(cache?.totalCreation5m ?? 0)}, cache_1h: ${fmtTok(cache?.totalCreation1h ?? 0)}`);
|
|
1225
|
-
this.info("");
|
|
1226
|
-
this.info(`● ${AgentSignals.BENCHMARK} Summary by phase:`);
|
|
1227
|
-
if (duration?.phaseSnapshots) for (let i = 0; i < duration.phaseSnapshots.length; i++) {
|
|
1228
|
-
const stats = getPhaseStats(i, ctx);
|
|
1229
|
-
if (stats) this.info(printPhase(stats));
|
|
1230
|
-
}
|
|
1231
|
-
this.info("");
|
|
1232
|
-
}
|
|
1233
|
-
};
|
|
1234
|
-
//#endregion
|
|
1235
|
-
//#region src/agent/middleware/benchmarks/json-writer.ts
|
|
1236
|
-
/**
|
|
1237
|
-
* JSON file output plugin.
|
|
1238
|
-
*
|
|
1239
|
-
* Assembles the BenchmarkData structure from all upstream plugin data
|
|
1240
|
-
* and writes it to a JSON file. Returns the BenchmarkData for backward compat.
|
|
1241
|
-
*/
|
|
1242
|
-
/**
|
|
1243
|
-
* Sum token usage across all models from the SDK's modelUsage field.
|
|
1244
|
-
*/
|
|
1245
|
-
function sumModelUsage(modelUsage) {
|
|
1246
|
-
let input_tokens = 0;
|
|
1247
|
-
let output_tokens = 0;
|
|
1248
|
-
let cache_creation_input_tokens = 0;
|
|
1249
|
-
let cache_read_input_tokens = 0;
|
|
1250
|
-
for (const model of Object.values(modelUsage)) {
|
|
1251
|
-
input_tokens += model.inputTokens ?? 0;
|
|
1252
|
-
output_tokens += model.outputTokens ?? 0;
|
|
1253
|
-
cache_creation_input_tokens += model.cacheCreationInputTokens ?? 0;
|
|
1254
|
-
cache_read_input_tokens += model.cacheReadInputTokens ?? 0;
|
|
1255
|
-
}
|
|
1256
|
-
return {
|
|
1257
|
-
input_tokens,
|
|
1258
|
-
output_tokens,
|
|
1259
|
-
cache_creation_input_tokens,
|
|
1260
|
-
cache_read_input_tokens
|
|
1261
|
-
};
|
|
1262
|
-
}
|
|
1263
|
-
var JsonWriterPlugin = class {
|
|
1264
|
-
name = "jsonWriter";
|
|
1265
|
-
outputPath;
|
|
1266
|
-
info;
|
|
1267
|
-
constructor(outputPath, emit) {
|
|
1268
|
-
this.outputPath = outputPath;
|
|
1269
|
-
this.info = (message) => emit({
|
|
1270
|
-
kind: "log",
|
|
1271
|
-
level: "info",
|
|
1272
|
-
message
|
|
1273
|
-
});
|
|
1274
|
-
}
|
|
1275
|
-
onFinalize(resultMessage, totalDurationMs, ctx, _store) {
|
|
1276
|
-
const tokens = ctx.get("tokens");
|
|
1277
|
-
const cache = ctx.get("cache");
|
|
1278
|
-
const turns = ctx.get("turns");
|
|
1279
|
-
const cost = ctx.get("cost");
|
|
1280
|
-
const duration = ctx.get("duration");
|
|
1281
|
-
const compactions = ctx.get("compactions");
|
|
1282
|
-
const contextSize = ctx.get("contextSize");
|
|
1283
|
-
const aggregateUsage = sumModelUsage(resultMessage?.modelUsage ?? {});
|
|
1284
|
-
const phaseCount = duration?.phaseSnapshots.length ?? 0;
|
|
1285
|
-
const steps = [];
|
|
1286
|
-
for (let i = 0; i < phaseCount; i++) {
|
|
1287
|
-
const dur = duration.phaseSnapshots[i];
|
|
1288
|
-
const tokenSnap = tokens?.phaseSnapshots[i];
|
|
1289
|
-
const cacheSnap = cache?.phaseSnapshots[i];
|
|
1290
|
-
const turnSnap = turns?.phaseSnapshots[i];
|
|
1291
|
-
const costSnap = cost?.phaseCosts[i];
|
|
1292
|
-
const compSnap = compactions?.phaseSnapshots[i];
|
|
1293
|
-
const ctxSnap = contextSize?.phaseSnapshots[i];
|
|
1294
|
-
const step = {
|
|
1295
|
-
name: dur.phase,
|
|
1296
|
-
usage: {
|
|
1297
|
-
input_tokens: tokenSnap?.inputTokens ?? 0,
|
|
1298
|
-
output_tokens: tokenSnap?.outputTokens ?? 0,
|
|
1299
|
-
cache_creation_input_tokens: cacheSnap?.cacheCreationTokens ?? 0,
|
|
1300
|
-
cache_read_input_tokens: cacheSnap?.cacheReadTokens ?? 0,
|
|
1301
|
-
...(cacheSnap?.cacheCreation5m ?? 0) + (cacheSnap?.cacheCreation1h ?? 0) > 0 && { cache_creation: {
|
|
1302
|
-
ephemeral_5m_input_tokens: cacheSnap?.cacheCreation5m ?? 0,
|
|
1303
|
-
ephemeral_1h_input_tokens: cacheSnap?.cacheCreation1h ?? 0
|
|
1304
|
-
} }
|
|
1305
|
-
},
|
|
1306
|
-
modelUsage: {},
|
|
1307
|
-
totalCostUsd: costSnap?.cost ?? 0,
|
|
1308
|
-
durationMs: dur.durationMs,
|
|
1309
|
-
durationApiMs: 0,
|
|
1310
|
-
numTurns: turnSnap?.turns ?? 0,
|
|
1311
|
-
...ctxSnap?.contextTokensIn !== void 0 && { contextTokensIn: ctxSnap.contextTokensIn },
|
|
1312
|
-
...ctxSnap?.contextTokensOut !== void 0 && { contextTokensOut: ctxSnap.contextTokensOut },
|
|
1313
|
-
...compSnap && compSnap.compactions > 0 ? {
|
|
1314
|
-
compactions: compSnap.compactions,
|
|
1315
|
-
compactionPreTokens: compSnap.preTokens
|
|
1316
|
-
} : {}
|
|
1317
|
-
};
|
|
1318
|
-
steps.push(step);
|
|
1319
|
-
}
|
|
1320
|
-
const totalTurns = turns?.totalTurns ?? 0;
|
|
1321
|
-
const totalCost = cost?.totalCost ?? 0;
|
|
1322
|
-
const totalCompactions = compactions?.totalCompactions ?? 0;
|
|
1323
|
-
const fromModelUsage = aggregateUsage.input_tokens + aggregateUsage.cache_read_input_tokens + aggregateUsage.cache_creation_input_tokens;
|
|
1324
|
-
const inputTokens = fromModelUsage > 0 ? fromModelUsage : (tokens?.totalInput ?? 0) + (cache?.totalRead ?? 0) + (cache?.totalCreation ?? 0);
|
|
1325
|
-
const outputTokens = aggregateUsage.output_tokens > 0 ? aggregateUsage.output_tokens : tokens?.totalOutput ?? 0;
|
|
1326
|
-
const benchmark = {
|
|
1327
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1328
|
-
steps,
|
|
1329
|
-
totals: {
|
|
1330
|
-
totalCostUsd: totalCost,
|
|
1331
|
-
durationMs: totalDurationMs,
|
|
1332
|
-
inputTokens,
|
|
1333
|
-
outputTokens,
|
|
1334
|
-
numTurns: resultMessage?.num_turns ?? totalTurns,
|
|
1335
|
-
totalCompactions,
|
|
1336
|
-
totalCacheReadTokens: cache?.totalRead ?? 0,
|
|
1337
|
-
totalCacheCreation5mTokens: cache?.totalCreation5m ?? 0,
|
|
1338
|
-
totalCacheCreation1hTokens: cache?.totalCreation1h ?? 0
|
|
1339
|
-
}
|
|
1340
|
-
};
|
|
1341
|
-
this.writeBenchmarkData(benchmark);
|
|
1342
|
-
return benchmark;
|
|
1343
|
-
}
|
|
1344
|
-
writeBenchmarkData(data) {
|
|
1345
|
-
try {
|
|
1346
|
-
fs.writeFileSync(this.outputPath, JSON.stringify(data, null, 2));
|
|
1347
|
-
logToFile(`Benchmark data written to ${this.outputPath}`);
|
|
1348
|
-
this.info(`● ${AgentSignals.BENCHMARK} Results written to ${this.outputPath}`);
|
|
1349
|
-
} catch (error) {
|
|
1350
|
-
logToFile("Failed to write benchmark data:", error);
|
|
1351
|
-
}
|
|
1352
|
-
}
|
|
1353
|
-
};
|
|
1354
|
-
//#endregion
|
|
1355
|
-
//#region src/agent/middleware/benchmarks/index.ts
|
|
1356
|
-
const PLUGIN_REGISTRY = {
|
|
1357
|
-
turns: () => new TurnCounterPlugin(),
|
|
1358
|
-
tokens: () => new TokenTrackerPlugin(),
|
|
1359
|
-
cache: () => new CacheTrackerPlugin(),
|
|
1360
|
-
compactions: () => new CompactionTrackerPlugin(),
|
|
1361
|
-
contextSize: () => new ContextSizeTrackerPlugin(),
|
|
1362
|
-
cost: () => new CostTrackerPlugin(),
|
|
1363
|
-
duration: () => new DurationTrackerPlugin(),
|
|
1364
|
-
summary: (opts) => new SummaryPlugin(opts.spinner, opts.emit),
|
|
1365
|
-
jsonWriter: (opts) => new JsonWriterPlugin(opts.outputPath, opts.emit)
|
|
1366
|
-
};
|
|
1367
|
-
/**
|
|
1368
|
-
* Execution order — data producers before consumers:
|
|
1369
|
-
* turns (dedup) -> tokens -> cache -> compactions -> contextSize -> cost -> duration -> summary -> jsonWriter
|
|
1370
|
-
*/
|
|
1371
|
-
const PLUGIN_ORDER = [
|
|
1372
|
-
"turns",
|
|
1373
|
-
"tokens",
|
|
1374
|
-
"cache",
|
|
1375
|
-
"compactions",
|
|
1376
|
-
"contextSize",
|
|
1377
|
-
"cost",
|
|
1378
|
-
"duration",
|
|
1379
|
-
"summary",
|
|
1380
|
-
"jsonWriter"
|
|
1381
|
-
];
|
|
1382
|
-
function createPluginsFromConfig(config, opts) {
|
|
1383
|
-
const resolvedOpts = {
|
|
1384
|
-
...opts,
|
|
1385
|
-
outputPath: opts.outputPath ?? config.output.benchmarkPath
|
|
1386
|
-
};
|
|
1387
|
-
const effectivePlugins = { ...config.plugins };
|
|
1388
|
-
if (config.output.suppressWizardLogs) effectivePlugins.summary = false;
|
|
1389
|
-
return PLUGIN_ORDER.filter((name) => effectivePlugins[name] !== false).map((name) => PLUGIN_REGISTRY[name]).filter(Boolean).map((factory) => factory(resolvedOpts));
|
|
1390
|
-
}
|
|
1391
|
-
//#endregion
|
|
1392
|
-
//#region src/agent/middleware/benchmark.ts
|
|
1393
|
-
/**
|
|
1394
|
-
* Create a middleware pipeline configured for benchmarking.
|
|
1395
|
-
* Loads .benchmark-config.json from the install dir, falls back to defaults.
|
|
1396
|
-
*/
|
|
1397
|
-
function createBenchmarkPipeline(emit, spinner, options, configOverride) {
|
|
1398
|
-
const config = configOverride ?? loadBenchmarkConfig(options.installDir);
|
|
1399
|
-
const info = (message) => emit({
|
|
1400
|
-
kind: "log",
|
|
1401
|
-
level: "info",
|
|
1402
|
-
message
|
|
1403
|
-
});
|
|
1404
|
-
configureLogFile({
|
|
1405
|
-
path: config.output.logPath,
|
|
1406
|
-
enabled: config.output.logEnabled
|
|
1407
|
-
});
|
|
1408
|
-
const plugins = createPluginsFromConfig(config, {
|
|
1409
|
-
emit,
|
|
1410
|
-
spinner,
|
|
1411
|
-
phased: false,
|
|
1412
|
-
outputPath: config.output.benchmarkPath
|
|
1413
|
-
});
|
|
1414
|
-
if (!config.output.suppressWizardLogs) {
|
|
1415
|
-
info(`${AgentSignals.BENCHMARK} Verbose logs: ${getLogFilePath()}`);
|
|
1416
|
-
info(`${AgentSignals.BENCHMARK} Benchmark data will be written to: ${config.output.benchmarkPath}`);
|
|
1417
|
-
}
|
|
1418
|
-
logToFile(`${AgentSignals.BENCHMARK} Tracking enabled, starting with setup phase`);
|
|
1419
|
-
return new MiddlewarePipeline(plugins, {
|
|
1420
|
-
phaseDetector: new PhaseDetector(),
|
|
1421
|
-
autoDetectPhases: true
|
|
1422
|
-
});
|
|
1423
|
-
}
|
|
1424
|
-
//#endregion
|
|
1425
|
-
//#region src/agent/error-map.ts
|
|
1426
|
-
const AGENT_ERROR_CODE = {
|
|
1427
|
-
["WIZARD_AGENTIC_DETECTION_TIMEOUT"]: ErrorCodes.AgenticDetectionTimeout,
|
|
1428
|
-
["WIZARD_MCP_MISSING"]: ErrorCodes.AgentMcpMissing,
|
|
1429
|
-
["WIZARD_RESOURCE_MISSING"]: ErrorCodes.AgentResourceMissing,
|
|
1430
|
-
["WIZARD_RATE_LIMIT"]: ErrorCodes.AgentRateLimit,
|
|
1431
|
-
["WIZARD_API_ERROR"]: ErrorCodes.AgentApiError,
|
|
1432
|
-
["WIZARD_YARA_VIOLATION"]: ErrorCodes.AgentYaraViolation,
|
|
1433
|
-
["WIZARD_ABORT"]: ErrorCodes.AgentAbort,
|
|
1434
|
-
["WIZARD_NO_PROGRESS"]: ErrorCodes.AgentNoProgress,
|
|
1435
|
-
["WIZARD_INVALID_STRUCTURED_OUTPUT"]: ErrorCodes.AgentInvalidStructuredOutput,
|
|
1436
|
-
["WIZARD_INCOMPLETE_TASKS"]: ErrorCodes.AgentIncompleteTasks
|
|
1437
|
-
};
|
|
1438
|
-
//#endregion
|
|
1439
|
-
//#region src/agent/agent-prompt.ts
|
|
1440
|
-
function defaultProjectPrompt(ctx) {
|
|
1441
|
-
return `You have access to the PostHog MCP server.
|
|
1442
|
-
|
|
1443
|
-
Project context:
|
|
1444
|
-
- PostHog Project ID: ${ctx.projectId}
|
|
1445
|
-
- PostHog public token: ${ctx.projectApiKey}
|
|
1446
|
-
- PostHog Host: ${ctx.host.apiHost}`;
|
|
1447
|
-
}
|
|
1448
|
-
function skillPrompt(skillPath, reportFile) {
|
|
1449
|
-
return `A PostHog skill has been installed at ${skillPath}/. Read ${skillPath}/SKILL.md and follow its instructions completely.
|
|
1450
|
-
|
|
1451
|
-
After completing the skill workflow, write a brief markdown report to ./${reportFile} summarizing:
|
|
1452
|
-
- What changes were made to the project
|
|
1453
|
-
- Which files were modified or created
|
|
1454
|
-
- Any manual steps the user should take next
|
|
1455
|
-
|
|
1456
|
-
Important: You must read a file immediately before attempting to write it, even if you have previously read it; failure to do so will cause a tool failure.`;
|
|
1457
|
-
}
|
|
1458
|
-
/**
|
|
1459
|
-
* Assemble the final agent prompt from the program's run config.
|
|
1460
|
-
*/
|
|
1461
|
-
function assemblePrompt(runDef, ctx) {
|
|
1462
|
-
const parts = [];
|
|
1463
|
-
parts.push(defaultProjectPrompt(ctx));
|
|
1464
|
-
if (runDef.customPrompt) parts.push(runDef.customPrompt(ctx));
|
|
1465
|
-
if (ctx.skillPath) parts.push(skillPrompt(ctx.skillPath, runDef.reportFile));
|
|
1466
|
-
return parts.join("\n\n");
|
|
1467
|
-
}
|
|
1468
|
-
//#endregion
|
|
1469
|
-
//#region src/agent/runner/shared/errors.ts
|
|
1470
|
-
const failed = (failure) => ({
|
|
1471
|
-
outcome: "failed",
|
|
1472
|
-
failure
|
|
1473
|
-
});
|
|
1474
|
-
/** The failure a skill install error decides. The caller reports and exits. */
|
|
1475
|
-
function installFailure(integrationLabel, result) {
|
|
1476
|
-
const code = skillErrorCode(result) ?? ErrorCodes.InternalUnhandled;
|
|
1477
|
-
return {
|
|
1478
|
-
message: (() => {
|
|
1479
|
-
switch (result.kind) {
|
|
1480
|
-
case "menu-fetch-failed": return "Could not fetch the skill menu from context-mill.\nCheck your network connection and try again.";
|
|
1481
|
-
case "skill-not-found": return `Could not find the "${result.skillId}" skill in the context-mill menu.\nPlease try again later.`;
|
|
1482
|
-
case "download-failed": return `Failed to install skill: ${result.message}\nPlease try again.`;
|
|
1483
|
-
}
|
|
1484
|
-
})(),
|
|
1485
|
-
code,
|
|
1486
|
-
detail: {
|
|
1487
|
-
integration: integrationLabel,
|
|
1488
|
-
error_type: result.kind,
|
|
1489
|
-
platform: process.platform,
|
|
1490
|
-
...result.kind === "download-failed" ? { error_detail: result.message.slice(0, 500) } : {}
|
|
1491
|
-
}
|
|
1492
|
-
};
|
|
1493
|
-
}
|
|
1494
|
-
//#endregion
|
|
1495
|
-
//#region src/agent/runner/shared/ask.ts
|
|
1496
|
-
/**
|
|
1497
|
-
* The ask bridge over an injected answerer.
|
|
1498
|
-
*
|
|
1499
|
-
* `createWizardAskBridge` already owns request ids, the timeout race, the
|
|
1500
|
-
* `__cancelled__` sentinel and the analytics; it only ever needed a
|
|
1501
|
-
* `showQuestion` that honours each question's own signal. Here that comes from
|
|
1502
|
-
* `AgentInteraction` instead of `getUI()`. With no answerer there is no bridge,
|
|
1503
|
-
* so `wizard_ask` reports its existing "not available" error rather than
|
|
1504
|
-
* hanging on a question nobody can see.
|
|
1505
|
-
*/
|
|
1506
|
-
function createAskBridge(interaction, options) {
|
|
1507
|
-
const ask = interaction?.ask;
|
|
1508
|
-
if (!ask) return void 0;
|
|
1509
|
-
return createWizardAskBridge({
|
|
1510
|
-
getSource: options.getSource,
|
|
1511
|
-
showQuestion: (question, context) => {
|
|
1512
|
-
options.beforeShow?.();
|
|
1513
|
-
return ask(question, context);
|
|
1514
|
-
},
|
|
1515
|
-
richLinks: options.richLinks,
|
|
1516
|
-
timeoutMs: options.timeoutMs,
|
|
1517
|
-
signal: options.signal
|
|
1518
|
-
});
|
|
1519
|
-
}
|
|
1520
|
-
//#endregion
|
|
1521
|
-
//#region src/agent/runner/sequence/linear.ts
|
|
1522
|
-
async function runLinearProgram(context) {
|
|
1523
|
-
const controller = new AbortController();
|
|
1524
|
-
const abortFromHost = () => controller.abort();
|
|
1525
|
-
context.signal?.addEventListener("abort", abortFromHost, { once: true });
|
|
1526
|
-
if (context.signal?.aborted) abortFromHost();
|
|
1527
|
-
try {
|
|
1528
|
-
return await executeLinear(context, controller.signal);
|
|
1529
|
-
} finally {
|
|
1530
|
-
context.signal?.removeEventListener("abort", abortFromHost);
|
|
1531
|
-
controller.abort();
|
|
1532
|
-
}
|
|
1533
|
-
}
|
|
1534
|
-
/** The caller's `signal` decides the outcome; `runSignal` also ends with the run. */
|
|
1535
|
-
async function executeLinear({ config, input, boot, emit, interaction, signal, transcript }, runSignal) {
|
|
1536
|
-
const { run, composed } = config;
|
|
1537
|
-
const { skillsBaseUrl, credentials, project } = boot;
|
|
1538
|
-
const { projectApiKey, host, projectId } = credentials;
|
|
1539
|
-
const aborted = () => ({
|
|
1540
|
-
outcome: "aborted",
|
|
1541
|
-
failure: {
|
|
1542
|
-
code: ErrorCodes.AgentAbort,
|
|
1543
|
-
message: "Agent run cancelled"
|
|
1544
|
-
}
|
|
1545
|
-
});
|
|
1546
|
-
if (signal?.aborted) return aborted();
|
|
1547
|
-
let skillPath;
|
|
1548
|
-
if (run.skillId) {
|
|
1549
|
-
logToFile(`[agent-runner] installing skill ${run.skillId}`);
|
|
1550
|
-
const installResult = await installSkillById(run.skillId, input.installDir, skillsBaseUrl, { triage: boot.triageProvider });
|
|
1551
|
-
if (signal?.aborted) return aborted();
|
|
1552
|
-
if (installResult.kind !== "ok") return failed(installFailure(run.integrationLabel, installResult));
|
|
1553
|
-
skillPath = installResult.path;
|
|
1554
|
-
logToFile(`[agent-runner] skill installed at ${skillPath}`);
|
|
1555
|
-
}
|
|
1556
|
-
const spinner = createEmitSpinner(emit);
|
|
1557
|
-
if (!run.structured) emit({
|
|
1558
|
-
kind: "lifecycle",
|
|
1559
|
-
phase: "started"
|
|
1560
|
-
});
|
|
1561
|
-
const ask = !!run.structured || shouldDisableAsk(input.flags) && process.env.WIZARD_ASK_AUTODRIVE !== "1" ? void 0 : createAskBridge(interaction, {
|
|
1562
|
-
getSource: () => input.skillId ?? run.integrationLabel,
|
|
1563
|
-
richLinks: run.richLinks ?? false,
|
|
1564
|
-
timeoutMs: run.askTimeoutMs,
|
|
1565
|
-
signal: runSignal
|
|
1566
|
-
});
|
|
1567
|
-
const middleware = withTranscript(input.flags.benchmark ? createBenchmarkPipeline(emit, spinner, runOptions(input)) : void 0, transcript);
|
|
1568
|
-
const promptContext = {
|
|
1569
|
-
projectId,
|
|
1570
|
-
projectApiKey,
|
|
1571
|
-
host,
|
|
1572
|
-
skillPath,
|
|
1573
|
-
orgAiDataProcessingApproved: input.apiUser?.organization?.is_ai_data_processing_approved ?? null,
|
|
1574
|
-
teamProductOptIns: project ? {
|
|
1575
|
-
sessionReplay: project.session_recording_opt_in ?? null,
|
|
1576
|
-
exceptionAutocapture: project.autocapture_exceptions_opt_in ?? null,
|
|
1577
|
-
surveys: project.surveys_opt_in ?? null
|
|
1578
|
-
} : null
|
|
1579
|
-
};
|
|
1580
|
-
const prompt = run.prompt ? run.prompt(promptContext) : assemblePrompt(run, promptContext);
|
|
1581
|
-
logToFile(`[agent-runner] prompt assembled (${prompt.length} chars)`);
|
|
1582
|
-
if (signal?.aborted) return aborted();
|
|
1583
|
-
const { harness, model, thinkingLevel } = config.binding;
|
|
1584
|
-
const agentResult = await getHarness(harness).run({
|
|
1585
|
-
config,
|
|
1586
|
-
input,
|
|
1587
|
-
boot,
|
|
1588
|
-
emit,
|
|
1589
|
-
prompt,
|
|
1590
|
-
skillPath,
|
|
1591
|
-
spinner,
|
|
1592
|
-
askBridge: ask,
|
|
1593
|
-
middleware,
|
|
1594
|
-
model,
|
|
1595
|
-
thinkingLevel,
|
|
1596
|
-
signal: runSignal,
|
|
1597
|
-
structured: run.structured
|
|
1598
|
-
});
|
|
1599
|
-
if (signal?.aborted && agentResult.kind === "success") return aborted();
|
|
1600
|
-
if (agentResult.kind === "decided_failure") return failed(agentResult.failure);
|
|
1601
|
-
if (agentResult.kind === "success") {} else if (agentResult.kind !== "abort" && agentResult.kind !== "failure") return agentResult;
|
|
1602
|
-
if (agentResult.kind === "abort") {
|
|
1603
|
-
const reason = agentResult.message ?? "";
|
|
1604
|
-
const matched = run.abortCases?.find((c) => c.match.test(reason));
|
|
1605
|
-
const abortCode = matched?.errorCode ?? ErrorCodes.AgentAbort;
|
|
1606
|
-
const outroData = matched ? {
|
|
1607
|
-
kind: "error",
|
|
1608
|
-
message: matched.message,
|
|
1609
|
-
body: matched.body,
|
|
1610
|
-
docsUrl: matched.docsUrl,
|
|
1611
|
-
errorCode: abortCode,
|
|
1612
|
-
errorDetail: { reason }
|
|
1613
|
-
} : {
|
|
1614
|
-
kind: "error",
|
|
1615
|
-
message: `${run.integrationLabel} aborted`,
|
|
1616
|
-
body: reason || "The agent aborted the program.",
|
|
1617
|
-
docsUrl: run.docsUrl,
|
|
1618
|
-
errorCode: abortCode,
|
|
1619
|
-
errorDetail: { reason }
|
|
1620
|
-
};
|
|
1621
|
-
analytics.wizardCapture("agent aborted", {
|
|
1622
|
-
integration: run.integrationLabel,
|
|
1623
|
-
reason,
|
|
1624
|
-
matched: matched?.message ?? null
|
|
1625
|
-
});
|
|
1626
|
-
return {
|
|
1627
|
-
outcome: signal?.aborted ? "aborted" : "failed",
|
|
1628
|
-
failure: {
|
|
1629
|
-
message: matched?.message ?? `${run.integrationLabel} aborted`,
|
|
1630
|
-
outroData,
|
|
1631
|
-
code: abortCode,
|
|
1632
|
-
error: agentResult.error
|
|
1633
|
-
}
|
|
1634
|
-
};
|
|
1635
|
-
}
|
|
1636
|
-
const classification = agentResult.kind === "failure" ? agentResult.classification : void 0;
|
|
1637
|
-
const failureMessage = agentResult.kind === "failure" ? agentResult.message : void 0;
|
|
1638
|
-
if (classification === "WIZARD_MCP_MISSING") return failed({
|
|
1639
|
-
code: AGENT_ERROR_CODE["WIZARD_MCP_MISSING"],
|
|
1640
|
-
message: `Could not access the PostHog MCP server
|
|
1641
|
-
|
|
1642
|
-
The wizard was unable to connect to the PostHog MCP server.
|
|
1643
|
-
This could be due to a network issue or a configuration problem.
|
|
1644
|
-
|
|
1645
|
-
Please try again, or check the documentation:\n${run.docsUrl}`,
|
|
1646
|
-
error: agentResult.kind === "failure" ? agentResult.error : void 0
|
|
1647
|
-
});
|
|
1648
|
-
if (classification === "WIZARD_RESOURCE_MISSING") return failed({
|
|
1649
|
-
code: AGENT_ERROR_CODE["WIZARD_RESOURCE_MISSING"],
|
|
1650
|
-
message: `Could not access the setup resource
|
|
1651
|
-
|
|
1652
|
-
This may indicate a version mismatch or a temporary service issue.
|
|
1653
|
-
|
|
1654
|
-
Please try again, or check the documentation:\n${run.docsUrl}`,
|
|
1655
|
-
error: agentResult.kind === "failure" ? agentResult.error : void 0
|
|
1656
|
-
});
|
|
1657
|
-
if (classification === "WIZARD_YARA_VIOLATION") return failed({
|
|
1658
|
-
code: AGENT_ERROR_CODE["WIZARD_YARA_VIOLATION"],
|
|
1659
|
-
message: formatYaraAbortMessage(),
|
|
1660
|
-
error: agentResult.kind === "failure" ? agentResult.error : void 0
|
|
1661
|
-
});
|
|
1662
|
-
if (classification === "WIZARD_NO_PROGRESS") {
|
|
1663
|
-
analytics.wizardCapture("agent no progress", {
|
|
1664
|
-
integration: run.integrationLabel,
|
|
1665
|
-
error_type: "WIZARD_NO_PROGRESS"
|
|
1666
|
-
});
|
|
1667
|
-
return failed({
|
|
1668
|
-
code: AGENT_ERROR_CODE["WIZARD_NO_PROGRESS"],
|
|
1669
|
-
message: "The Wizard exited without changing your project. Please contact the PostHog team with wizard@posthog.com about this error.",
|
|
1670
|
-
error: agentResult.kind === "failure" ? agentResult.error : void 0
|
|
1671
|
-
});
|
|
1672
|
-
}
|
|
1673
|
-
if (classification === "WIZARD_INCOMPLETE_TASKS") {
|
|
1674
|
-
analytics.wizardCapture("agent incomplete tasks", {
|
|
1675
|
-
integration: run.integrationLabel,
|
|
1676
|
-
error_type: "WIZARD_INCOMPLETE_TASKS"
|
|
1677
|
-
});
|
|
1678
|
-
return failed({
|
|
1679
|
-
code: AGENT_ERROR_CODE["WIZARD_INCOMPLETE_TASKS"],
|
|
1680
|
-
message: "The Wizard exited without completing its planned tasks. Please contact the PostHog team with wizard@posthog.com about this error.",
|
|
1681
|
-
error: agentResult.kind === "failure" ? agentResult.error : void 0
|
|
1682
|
-
});
|
|
1683
|
-
}
|
|
1684
|
-
if (classification === "WIZARD_RATE_LIMIT" || classification === "WIZARD_API_ERROR") {
|
|
1685
|
-
analytics.wizardCapture("agent api error", {
|
|
1686
|
-
integration: run.integrationLabel,
|
|
1687
|
-
error_type: classification,
|
|
1688
|
-
error_message: failureMessage
|
|
1689
|
-
});
|
|
1690
|
-
return failed({
|
|
1691
|
-
code: AGENT_ERROR_CODE[classification],
|
|
1692
|
-
message: `API Error\n\n${failureMessage || "Unknown error"}\n\nPlease report this to: wizard@posthog.com`,
|
|
1693
|
-
error: agentResult.kind === "failure" ? agentResult.error : void 0
|
|
1694
|
-
});
|
|
1695
|
-
}
|
|
1696
|
-
if (agentResult.kind === "failure") return failed({
|
|
1697
|
-
code: AGENT_ERROR_CODE[agentResult.classification],
|
|
1698
|
-
message: agentResult.message ?? "Agent failed",
|
|
1699
|
-
error: agentResult.error
|
|
1700
|
-
});
|
|
1701
|
-
if (run.structured) return {
|
|
1702
|
-
outcome: "success",
|
|
1703
|
-
structuredOutput: agentResult.structuredOutput
|
|
1704
|
-
};
|
|
1705
|
-
if (config.hooks?.postRun) {
|
|
1706
|
-
await config.hooks.postRun(credentials);
|
|
1707
|
-
if (signal?.aborted) return aborted();
|
|
1708
|
-
}
|
|
1709
|
-
if (composed) return { outcome: "success" };
|
|
1710
|
-
const outroData = config.hooks?.buildOutroData ? config.hooks.buildOutroData(credentials) : {
|
|
1711
|
-
kind: "success",
|
|
1712
|
-
message: run.successMessage,
|
|
1713
|
-
reportFile: run.reportFile,
|
|
1714
|
-
docsUrl: run.docsUrl,
|
|
1715
|
-
continueUrl: input.flags.signup ? `${host.appHost}/products?source=wizard` : void 0
|
|
1716
|
-
};
|
|
1717
|
-
if (outroData) emit({
|
|
1718
|
-
kind: "completion",
|
|
1719
|
-
outro: outroData
|
|
1720
|
-
});
|
|
1721
|
-
emit({
|
|
1722
|
-
kind: "lifecycle",
|
|
1723
|
-
phase: "completed",
|
|
1724
|
-
message: run.successMessage
|
|
1725
|
-
});
|
|
1726
|
-
return {
|
|
1727
|
-
outcome: "success",
|
|
1728
|
-
outro: outroData
|
|
1729
|
-
};
|
|
1730
|
-
}
|
|
1731
|
-
//#endregion
|
|
1732
|
-
//#region src/shared/utils/terminal-bell.ts
|
|
1733
|
-
/**
|
|
1734
|
-
* Ring the terminal bell (BEL, U+0007).
|
|
1735
|
-
*
|
|
1736
|
-
* The orchestrator takes consent for the warehouse step at the top of the run
|
|
1737
|
-
* and asks for the credentials at the end of it, several autonomous minutes
|
|
1738
|
-
* later. The person who agreed is often looking at something else by then. A
|
|
1739
|
-
* bell is the one attention signal a terminal in a background tab can still
|
|
1740
|
-
* deliver, and it costs a user who is watching nothing.
|
|
1741
|
-
*
|
|
1742
|
-
* Written to stderr, not stdout: Ink owns stdout and repaints whole frames, so
|
|
1743
|
-
* a byte written there can land inside one. Silent when stderr is not a TTY —
|
|
1744
|
-
* a pipe or a CI log cannot ring, and the byte would only pollute the capture.
|
|
1745
|
-
*/
|
|
1746
|
-
function ringTerminalBell() {
|
|
1747
|
-
if (!process.stderr.isTTY) return;
|
|
1748
|
-
try {
|
|
1749
|
-
process.stderr.write("\x07");
|
|
1750
|
-
} catch {}
|
|
1751
|
-
}
|
|
1752
|
-
//#endregion
|
|
1753
|
-
//#region src/agent/runner/sequence/orchestrator/executor.ts
|
|
1754
|
-
/**
|
|
1755
|
-
* The executor drains the queue. It starts every runnable task (dependencies
|
|
1756
|
-
* satisfied) as soon as it becomes runnable — parallelism is decided by the
|
|
1757
|
-
* task graph, not by an executor knob. Each task runs through an injected
|
|
1758
|
-
* `runTask` function and reports its outcome via `complete_task`; a task that
|
|
1759
|
-
* ends without reporting is retried while attempts remain, then failed. A
|
|
1760
|
-
* `maxStarts` backstop guarantees termination.
|
|
1761
|
-
*
|
|
1762
|
-
* The drain loop is independent of how a task actually runs. `runTask` is
|
|
1763
|
-
* injected: the real one spins up a fresh agent, the tests use a fake.
|
|
1764
|
-
*/
|
|
1765
|
-
/**
|
|
1766
|
-
* Thrown by a `RunTask` when its harness returned a decided failure that ends
|
|
1767
|
-
* the whole run (a 401 the auth screen already reported), not just the task.
|
|
1768
|
-
* `drainQueue` lets it through so the sequence can return the failure where
|
|
1769
|
-
* the harness used to exit the process.
|
|
1770
|
-
*/
|
|
1771
|
-
var RunTaskFatal = class extends Error {
|
|
1772
|
-
constructor(failure, outcome = "failed", taskType) {
|
|
1773
|
-
super(failure.message ?? "agent run failed");
|
|
1774
|
-
this.failure = failure;
|
|
1775
|
-
this.outcome = outcome;
|
|
1776
|
-
this.taskType = taskType;
|
|
1777
|
-
this.name = "RunTaskFatal";
|
|
1778
|
-
}
|
|
1779
|
-
};
|
|
1780
|
-
const DEFAULT_DRAIN_OPTIONS = { maxStarts: 200 };
|
|
1781
|
-
async function runOne(store, runTask, task, signal) {
|
|
1782
|
-
if (signal?.aborted) return;
|
|
1783
|
-
store.start(task.id);
|
|
1784
|
-
try {
|
|
1785
|
-
await runTask(task);
|
|
1786
|
-
} catch (error) {
|
|
1787
|
-
if (signal?.aborted) return;
|
|
1788
|
-
if (error instanceof RunTaskFatal) throw error;
|
|
1789
|
-
logToFile(`[executor] runTask threw for ${task.type}:`, error);
|
|
1790
|
-
analytics.captureException(error instanceof Error ? error : new Error(String(error)), {
|
|
1791
|
-
step: "orchestrator_run_task",
|
|
1792
|
-
task_type: task.type
|
|
1793
|
-
});
|
|
1794
|
-
}
|
|
1795
|
-
if (signal?.aborted) return;
|
|
1796
|
-
const after = store.get(task.id);
|
|
1797
|
-
if (!after) return;
|
|
1798
|
-
if (after.status === TaskStatus.Running) {
|
|
1799
|
-
if (after.attempts < after.maxAttempts) store.requeue(task.id);
|
|
1800
|
-
else store.fail(task.id, {
|
|
1801
|
-
type: "no-report",
|
|
1802
|
-
message: "Task ended without calling complete_task."
|
|
1803
|
-
});
|
|
1804
|
-
return;
|
|
1805
|
-
}
|
|
1806
|
-
if (after.status === TaskStatus.Failed && after.attempts < after.maxAttempts) store.requeue(task.id);
|
|
1807
|
-
}
|
|
1808
|
-
/**
|
|
1809
|
-
* Drain the queue to a terminal state. Every runnable task starts the moment
|
|
1810
|
-
* its dependencies finish; independent branches run concurrently. Returns when
|
|
1811
|
-
* every task is done, failed, or blocked by a failed dependency, or when the
|
|
1812
|
-
* start backstop trips.
|
|
1813
|
-
*/
|
|
1814
|
-
async function drainQueue(store, runTask, opts = DEFAULT_DRAIN_OPTIONS) {
|
|
1815
|
-
const running = /* @__PURE__ */ new Map();
|
|
1816
|
-
let starts = 0;
|
|
1817
|
-
let failure;
|
|
1818
|
-
try {
|
|
1819
|
-
for (;;) {
|
|
1820
|
-
if (opts.signal?.aborted) break;
|
|
1821
|
-
if (failure) throw failure.error;
|
|
1822
|
-
for (const task of store.nextRunnable()) {
|
|
1823
|
-
if (opts.signal?.aborted) break;
|
|
1824
|
-
if (++starts > opts.maxStarts) break;
|
|
1825
|
-
const p = runOne(store, runTask, task, opts.signal).catch((error) => {
|
|
1826
|
-
if (!failure) {
|
|
1827
|
-
failure = { error };
|
|
1828
|
-
try {
|
|
1829
|
-
opts.onFatal?.();
|
|
1830
|
-
} catch (abortError) {
|
|
1831
|
-
try {
|
|
1832
|
-
logToFile("[executor] fatal cancellation failed:", abortError);
|
|
1833
|
-
} catch {}
|
|
1834
|
-
}
|
|
1835
|
-
}
|
|
1836
|
-
}).finally(() => running.delete(task.id));
|
|
1837
|
-
running.set(task.id, p);
|
|
1838
|
-
}
|
|
1839
|
-
if (running.size === 0) break;
|
|
1840
|
-
await Promise.race(running.values());
|
|
1841
|
-
}
|
|
1842
|
-
} finally {
|
|
1843
|
-
await Promise.allSettled(running.values());
|
|
1844
|
-
}
|
|
1845
|
-
if (failure) throw failure.error;
|
|
1846
|
-
}
|
|
1847
|
-
//#endregion
|
|
1848
|
-
//#region src/agent/runner/sequence/orchestrator/run-metrics.ts
|
|
1849
|
-
var RunMetrics = class {
|
|
1850
|
-
firstStartMs;
|
|
1851
|
-
lastStartMs;
|
|
1852
|
-
firstCompleteMs;
|
|
1853
|
-
firstAskMs;
|
|
1854
|
-
lastVisibleMs;
|
|
1855
|
-
maxGapMs = 0;
|
|
1856
|
-
constructor(runStartMs) {
|
|
1857
|
-
this.runStartMs = runStartMs;
|
|
1858
|
-
}
|
|
1859
|
-
/** A task started. Returns the per-start-event timing for the start event. */
|
|
1860
|
-
recordStart(nowMs) {
|
|
1861
|
-
const timing = {
|
|
1862
|
-
ms_since_run_start: nowMs - this.runStartMs,
|
|
1863
|
-
gap_since_prev_start_ms: this.lastStartMs === void 0 ? void 0 : nowMs - this.lastStartMs
|
|
1864
|
-
};
|
|
1865
|
-
this.firstStartMs ??= nowMs;
|
|
1866
|
-
this.lastStartMs = nowMs;
|
|
1867
|
-
this.markVisible(nowMs);
|
|
1868
|
-
return timing;
|
|
1869
|
-
}
|
|
1870
|
-
/** A task completed. */
|
|
1871
|
-
recordComplete(nowMs) {
|
|
1872
|
-
this.firstCompleteMs ??= nowMs;
|
|
1873
|
-
this.markVisible(nowMs);
|
|
1874
|
-
}
|
|
1875
|
-
/** A task reached a terminal non-complete state the user sees (skip/fail). */
|
|
1876
|
-
recordTerminal(nowMs) {
|
|
1877
|
-
this.markVisible(nowMs);
|
|
1878
|
-
}
|
|
1879
|
-
/**
|
|
1880
|
-
* A question was put to the user. Only the first matters here, and it is not a
|
|
1881
|
-
* queue transition — waiting on a person is not the silence `max_gap_ms`
|
|
1882
|
-
* measures, so this deliberately does not mark visible progress.
|
|
1883
|
-
*/
|
|
1884
|
-
recordAsk(nowMs) {
|
|
1885
|
-
this.firstAskMs ??= nowMs;
|
|
1886
|
-
}
|
|
1887
|
-
/**
|
|
1888
|
-
* The run-level responsiveness summary. Timings are `undefined` when the
|
|
1889
|
-
* relevant transition never happened (e.g. a run that started no task), so a
|
|
1890
|
-
* no-task run stays distinguishable from a genuine zero.
|
|
1891
|
-
*/
|
|
1892
|
-
summary() {
|
|
1893
|
-
return {
|
|
1894
|
-
time_to_first_task_ms: this.firstStartMs === void 0 ? void 0 : this.firstStartMs - this.runStartMs,
|
|
1895
|
-
time_to_first_completion_ms: this.firstCompleteMs === void 0 ? void 0 : this.firstCompleteMs - this.runStartMs,
|
|
1896
|
-
max_gap_ms: this.lastVisibleMs === void 0 ? void 0 : this.maxGapMs,
|
|
1897
|
-
time_to_first_ask_ms: this.firstAskMs === void 0 ? void 0 : this.firstAskMs - this.runStartMs
|
|
1898
|
-
};
|
|
1899
|
-
}
|
|
1900
|
-
/** requeue is not user-visible, so a retry stall counts as silence here. */
|
|
1901
|
-
markVisible(nowMs) {
|
|
1902
|
-
if (this.lastVisibleMs !== void 0) this.maxGapMs = Math.max(this.maxGapMs, nowMs - this.lastVisibleMs);
|
|
1903
|
-
this.lastVisibleMs = nowMs;
|
|
1904
|
-
}
|
|
1905
|
-
};
|
|
1906
|
-
//#endregion
|
|
1907
|
-
//#region src/agent/runner/sequence/orchestrator/seeded-deps.ts
|
|
1908
|
-
/**
|
|
1909
|
-
* Resolve a runner-seeded task's dependencies against the planned queue.
|
|
1910
|
-
*
|
|
1911
|
-
* Two exclusions apply to both the declared and the default path:
|
|
1912
|
-
*
|
|
1913
|
-
* - **Sinks are never dependencies.** A sink runs last and must depend on the
|
|
1914
|
-
* whole queue, this task included. Depending on it back would invert the edge
|
|
1915
|
-
* and cost the report this task's `reportSection`. Leaving sinks out means a
|
|
1916
|
-
* planner that forgot the sink→seeded edge is still caught by the sink
|
|
1917
|
-
* invariant check rather than silently papered over here.
|
|
1918
|
-
* - **Anything already downstream of this task is never a dependency**, which is
|
|
1919
|
-
* what makes the result cycle-free by construction rather than by the queue's
|
|
1920
|
-
* per-edge refusal.
|
|
1921
|
-
*/
|
|
1922
|
-
function seededDependencies(store, seededTaskId, declaredTypes, sinkTypes) {
|
|
1923
|
-
const eligible = store.list().filter((task) => {
|
|
1924
|
-
if (task.id === seededTaskId) return false;
|
|
1925
|
-
if (sinkTypes.includes(task.type)) return false;
|
|
1926
|
-
return !dependencyClosure(store, [task.id]).has(seededTaskId);
|
|
1927
|
-
});
|
|
1928
|
-
if (declaredTypes.length === 0) return {
|
|
1929
|
-
depIds: eligible.map((t) => t.id),
|
|
1930
|
-
unresolvedTypes: [],
|
|
1931
|
-
declared: false
|
|
1932
|
-
};
|
|
1933
|
-
const depIds = [];
|
|
1934
|
-
const unresolvedTypes = [];
|
|
1935
|
-
for (const type of declaredTypes) {
|
|
1936
|
-
if (!store.list().some((t) => t.type === type)) {
|
|
1937
|
-
unresolvedTypes.push(type);
|
|
1938
|
-
continue;
|
|
1939
|
-
}
|
|
1940
|
-
for (const task of eligible) if (task.type === type && !depIds.includes(task.id)) depIds.push(task.id);
|
|
1941
|
-
}
|
|
1942
|
-
return {
|
|
1943
|
-
depIds,
|
|
1944
|
-
unresolvedTypes,
|
|
1945
|
-
declared: true
|
|
1946
|
-
};
|
|
1947
|
-
}
|
|
1948
|
-
/**
|
|
1949
|
-
* Give every runner-seeded task the dependencies it could not name at enqueue,
|
|
1950
|
-
* and report what happened per task.
|
|
1951
|
-
*
|
|
1952
|
-
* The runner calls this once, after the planner has seeded the queue and before
|
|
1953
|
-
* the sink invariant is checked. Kept here rather than inline in the runner so
|
|
1954
|
-
* the wiring — which prompt's types feed which task — is testable without
|
|
1955
|
-
* standing up a whole run.
|
|
1956
|
-
*/
|
|
1957
|
-
function deferSeededTasks(store, seededTasks, declaredTypesFor, sinkTypes) {
|
|
1958
|
-
return seededTasks.map((seeded) => {
|
|
1959
|
-
const declaredTypes = [...declaredTypesFor(seeded.type)];
|
|
1960
|
-
const resolved = seededDependencies(store, seeded.id, declaredTypes, sinkTypes);
|
|
1961
|
-
const { added, refused } = store.addDependencies(seeded.id, resolved.depIds);
|
|
1962
|
-
return {
|
|
1963
|
-
type: seeded.type,
|
|
1964
|
-
declaredTypes,
|
|
1965
|
-
declared: resolved.declared,
|
|
1966
|
-
added,
|
|
1967
|
-
refused,
|
|
1968
|
-
unresolvedTypes: resolved.unresolvedTypes
|
|
1969
|
-
};
|
|
1970
|
-
});
|
|
1971
|
-
}
|
|
1972
|
-
//#endregion
|
|
1973
|
-
//#region src/agent/runner/sequence/orchestrator/orchestrator-runner.ts
|
|
1974
|
-
/**
|
|
1975
|
-
* Experimental task-queue orchestrator runner.
|
|
1976
|
-
*
|
|
1977
|
-
* Branches from the linear runner when the `wizard-orchestrator` flag is on. An
|
|
1978
|
-
* orchestrator agent inspects the repo and seeds an in-memory task queue; an
|
|
1979
|
-
* executor drains it, running one fresh agent per task.
|
|
1980
|
-
*
|
|
1981
|
-
* Both the WHAT (agent prompts: model, goal, success criteria, tools) and the
|
|
1982
|
-
* HOW (mini-skills) are markdown served from context-mill — the seed and every
|
|
1983
|
-
* task resolve to a prompt fetched at startup into the registry. The wizard side
|
|
1984
|
-
* stays product-ignorant: it is the queue, the executor, and the loader.
|
|
1985
|
-
*/
|
|
1986
|
-
/** Docs page (`django.md`, `nuxt-js-3-6.md`) — steps start with a digit, agent artifacts (`SKILL.md`, `EXAMPLE*`, `COMMANDMENTS.md`) have uppercase. */
|
|
1987
|
-
const isDocPage = (name) => name.endsWith(".md") && name === name.toLowerCase() && !/^\d/.test(name);
|
|
1988
|
-
/** Copy only the framework docs pages out of the run cache into .claude/skills — the one durable artifact an orchestrator run leaves. Never clobbers an existing install. */
|
|
1989
|
-
function promoteReferenceSkill(referenceDir, claudeSkillsDir, referenceSkillId) {
|
|
1990
|
-
const target = path$1.join(claudeSkillsDir, referenceSkillId);
|
|
1991
|
-
const refs = path$1.join(referenceDir, "references");
|
|
1992
|
-
if (!existsSync(refs) || existsSync(target)) return;
|
|
1993
|
-
const docs = readdirSync(refs).filter(isDocPage);
|
|
1994
|
-
if (docs.length === 0) return;
|
|
1995
|
-
mkdirSync(path$1.join(target, "references"), { recursive: true });
|
|
1996
|
-
for (const f of docs) cpSync(path$1.join(refs, f), path$1.join(target, "references", f));
|
|
1997
|
-
writeFileSync(path$1.join(target, ".posthog-wizard"), "");
|
|
1998
|
-
}
|
|
1999
|
-
/**
|
|
2000
|
-
* Remove skills that task agents installed durably mid-run (load_skill):
|
|
2001
|
-
* wizard-marked, new this run, and not the framework reference docs.
|
|
2002
|
-
* User-authored and pre-existing skills stay.
|
|
2003
|
-
*/
|
|
2004
|
-
function sweepRunInstalledSkills(claudeSkillsDir, preexistingSkills, referenceSkillId) {
|
|
2005
|
-
if (!existsSync(claudeSkillsDir)) return;
|
|
2006
|
-
for (const id of readdirSync(claudeSkillsDir)) {
|
|
2007
|
-
if (preexistingSkills.has(id) || id === referenceSkillId) continue;
|
|
2008
|
-
if (!existsSync(path$1.join(claudeSkillsDir, id, ".posthog-wizard"))) continue;
|
|
2009
|
-
rmSync(path$1.join(claudeSkillsDir, id), {
|
|
2010
|
-
recursive: true,
|
|
2011
|
-
force: true
|
|
2012
|
-
});
|
|
2013
|
-
logToFile(`[orchestrator] removed run-installed skill ${id}`);
|
|
2014
|
-
}
|
|
2015
|
-
}
|
|
2016
|
-
function toTodoStatus(status) {
|
|
2017
|
-
switch (status) {
|
|
2018
|
-
case TaskStatus.Running: return "in_progress";
|
|
2019
|
-
case TaskStatus.Done: return "completed";
|
|
2020
|
-
case TaskStatus.Failed: return "failed";
|
|
2021
|
-
case TaskStatus.Skipped: return "skipped";
|
|
2022
|
-
default: return "pending";
|
|
2023
|
-
}
|
|
2024
|
-
}
|
|
2025
|
-
/**
|
|
2026
|
-
* Look up the harness impl for a resolved pick and enforce the `runTask`
|
|
2027
|
-
* capability. Pi trips this today with the honest impl-gap error instead of
|
|
2028
|
-
* silently downgrading to anthropic.
|
|
2029
|
-
*/
|
|
2030
|
-
function requireTaskHarness(pick) {
|
|
2031
|
-
const harness = getHarness(pick.harness);
|
|
2032
|
-
if (!harness.runTask) throw new Error(`Harness "${pick.harness}" does not implement runTask; orchestrator mode requires it.`);
|
|
2033
|
-
return harness;
|
|
2034
|
-
}
|
|
2035
|
-
function terminalResult(result) {
|
|
2036
|
-
switch (result.kind) {
|
|
2037
|
-
case "success": return;
|
|
2038
|
-
case "decided_failure": return {
|
|
2039
|
-
outcome: "failed",
|
|
2040
|
-
failure: result.failure
|
|
2041
|
-
};
|
|
2042
|
-
case "abort": return {
|
|
2043
|
-
outcome: "failed",
|
|
2044
|
-
failure: {
|
|
2045
|
-
code: AGENT_ERROR_CODE[result.classification],
|
|
2046
|
-
message: result.message ?? "Agent aborted",
|
|
2047
|
-
error: result.error
|
|
2048
|
-
}
|
|
2049
|
-
};
|
|
2050
|
-
case "failure": return {
|
|
2051
|
-
outcome: "failed",
|
|
2052
|
-
failure: {
|
|
2053
|
-
code: AGENT_ERROR_CODE[result.classification],
|
|
2054
|
-
message: result.message ?? "Agent failed",
|
|
2055
|
-
error: result.error
|
|
2056
|
-
}
|
|
2057
|
-
};
|
|
2058
|
-
}
|
|
2059
|
-
}
|
|
2060
|
-
function cancelledRun() {
|
|
2061
|
-
return {
|
|
2062
|
-
outcome: "aborted",
|
|
2063
|
-
failure: {
|
|
2064
|
-
code: ErrorCodes.AgentAbort,
|
|
2065
|
-
message: "Agent run cancelled"
|
|
2066
|
-
}
|
|
2067
|
-
};
|
|
2068
|
-
}
|
|
2069
|
-
/** Every skill entry the menu knows, across categories. */
|
|
2070
|
-
async function fetchSkillMenuEntries(skillsBaseUrl) {
|
|
2071
|
-
const menu = await fetchSkillMenu(skillsBaseUrl);
|
|
2072
|
-
if (!menu) return [];
|
|
2073
|
-
return Object.values(menu.categories).flat();
|
|
2074
|
-
}
|
|
2075
|
-
/** Menu id for a bare skill id + framework via the menu's declared group/framework/default fields; undefined when nothing matches. */
|
|
2076
|
-
function resolveSkillVariantId(entries, skillId, framework) {
|
|
2077
|
-
if (entries.some((e) => e.id === skillId)) return skillId;
|
|
2078
|
-
if (!framework) return void 0;
|
|
2079
|
-
const family = entries.filter((e) => e.group === skillId && e.framework === framework);
|
|
2080
|
-
return (family.find((e) => e.default) ?? family[0])?.id;
|
|
2081
|
-
}
|
|
2082
|
-
/**
|
|
2083
|
-
* The framework reference is the full `integration` skill. `input.skillId` is
|
|
2084
|
-
* the bare framework (e.g. `django`), but the skill menu ids it as
|
|
2085
|
-
* `integration-<variant>`.
|
|
2086
|
-
*/
|
|
2087
|
-
function resolveReferenceSkillId(entries, framework) {
|
|
2088
|
-
return resolveSkillVariantId(entries, "integration", framework);
|
|
2089
|
-
}
|
|
2090
|
-
/**
|
|
2091
|
-
* How long an optional step's notice waits for an answer.
|
|
2092
|
-
*
|
|
2093
|
-
* Much shorter than LONGER_ASK_TIMEOUT_MS, because it is asking for something
|
|
2094
|
-
* much smaller: one keypress to accept or decline, not "go mint a restricted
|
|
2095
|
-
* Stripe key". It cannot be unbounded either — the notice is a modal, and a run
|
|
2096
|
-
* that stops forever behind one nobody is looking at is worse than one that
|
|
2097
|
-
* takes the safe answer and carries on.
|
|
2098
|
-
*
|
|
2099
|
-
* The offer is made at seed time, seconds into the run, so five minutes is a
|
|
2100
|
-
* generous allowance for a person who is by then still watching the wizard
|
|
2101
|
-
* start. It was not: from 2.63.0 to 2.65.0 the offer was made at the moment the
|
|
2102
|
-
* step became runnable — a median seven minutes in, after every coding task —
|
|
2103
|
-
* and this timeout became the answer for about a quarter of all runs.
|
|
2104
|
-
*/
|
|
2105
|
-
const TASK_NOTICE_TIMEOUT_MS = 300 * 1e3;
|
|
2106
|
-
/**
|
|
2107
|
-
* Offer an optional step, defaulting to declining it if nobody answers.
|
|
2108
|
-
*
|
|
2109
|
-
* Skip is the right default on a timeout: continuing would send the step on to
|
|
2110
|
-
* ask for credentials that the same absent user cannot supply either, burning
|
|
2111
|
-
* {@link LONGER_ASK_TIMEOUT_MS} per question before falling back to the same
|
|
2112
|
-
* links declining gives immediately.
|
|
2113
|
-
*/
|
|
2114
|
-
async function offerSeededTask(notice, { timeoutMs = TASK_NOTICE_TIMEOUT_MS, interaction, signal } = {}) {
|
|
2115
|
-
if (signal?.aborted) return {
|
|
2116
|
-
keep: false,
|
|
2117
|
-
timedOut: false
|
|
2118
|
-
};
|
|
2119
|
-
if (!interaction?.taskNotice) return {
|
|
2120
|
-
keep: false,
|
|
2121
|
-
timedOut: false
|
|
2122
|
-
};
|
|
2123
|
-
const { taskNotice } = interaction;
|
|
2124
|
-
const controller = new AbortController();
|
|
2125
|
-
let timer;
|
|
2126
|
-
let timedOut = false;
|
|
2127
|
-
let cancelForAbort;
|
|
2128
|
-
const timeout = new Promise((resolve) => {
|
|
2129
|
-
timer = setTimeout(() => {
|
|
2130
|
-
timedOut = true;
|
|
2131
|
-
resolve(false);
|
|
2132
|
-
controller.abort();
|
|
2133
|
-
}, timeoutMs);
|
|
2134
|
-
});
|
|
2135
|
-
const aborted = new Promise((resolve) => {
|
|
2136
|
-
cancelForAbort = () => {
|
|
2137
|
-
resolve(false);
|
|
2138
|
-
controller.abort();
|
|
2139
|
-
};
|
|
2140
|
-
signal?.addEventListener("abort", cancelForAbort, { once: true });
|
|
2141
|
-
});
|
|
2142
|
-
try {
|
|
2143
|
-
return {
|
|
2144
|
-
keep: await Promise.race([
|
|
2145
|
-
taskNotice(notice, { signal: controller.signal }),
|
|
2146
|
-
timeout,
|
|
2147
|
-
aborted
|
|
2148
|
-
]),
|
|
2149
|
-
timedOut
|
|
2150
|
-
};
|
|
2151
|
-
} finally {
|
|
2152
|
-
if (timer) clearTimeout(timer);
|
|
2153
|
-
if (cancelForAbort) signal?.removeEventListener("abort", cancelForAbort);
|
|
2154
|
-
}
|
|
2155
|
-
}
|
|
2156
|
-
/**
|
|
2157
|
-
* Which runner-seeded task types finished successfully.
|
|
2158
|
-
*
|
|
2159
|
-
* Handed to the program's `buildOutroNextSteps` so it can drop a next step its
|
|
2160
|
-
* own seeded task already carried out. Only `Done` counts: a skipped, declined
|
|
2161
|
-
* or failed step left the work undone, and that is exactly when the outro
|
|
2162
|
-
* bullet pointing at the app is the one thing the user still needs.
|
|
2163
|
-
*/
|
|
2164
|
-
function completedSeededTypes(store, seededTasks) {
|
|
2165
|
-
return seededTasks.filter((task) => store.get(task.id)?.status === TaskStatus.Done).map((task) => task.type);
|
|
2166
|
-
}
|
|
2167
|
-
/** Which skip reason a negative {@link SeededConsent} carries onto the event. */
|
|
2168
|
-
function consentSkipReason(consent) {
|
|
2169
|
-
if (consent.errored) return SkipReason.NoticeError;
|
|
2170
|
-
return consent.timedOut ? SkipReason.NoticeTimeout : SkipReason.UserDeclined;
|
|
2171
|
-
}
|
|
2172
|
-
/**
|
|
2173
|
-
* Apply the seed-time answers to the queue, before the drain starts.
|
|
2174
|
-
*
|
|
2175
|
-
* A declined task is skipped rather than dropped, so the graph the planner saw
|
|
2176
|
-
* is the graph that ran — the sink already depends on this task, and
|
|
2177
|
-
* `nextRunnable` treats a skipped dependency as satisfied, so the report still
|
|
2178
|
-
* runs and can say the step was declined. The decline also stays in the funnel,
|
|
2179
|
-
* arriving as a `skipped` event carrying the reason that caused it rather than
|
|
2180
|
-
* as a task that silently never existed.
|
|
2181
|
-
*
|
|
2182
|
-
* It happens here rather than inside `runTask` because the executor marks a
|
|
2183
|
-
* task running — and fires `orchestrator task started` — before it hands the
|
|
2184
|
-
* task over. A decline applied on the far side of that emitted a start for a
|
|
2185
|
-
* task no agent ever ran, so every rate measured against starts counted the
|
|
2186
|
-
* declines twice: once in the denominator as a task that began, and again in
|
|
2187
|
-
* the numerator as a task that was skipped. Nothing upstream of the drain reads
|
|
2188
|
-
* task status, so applying the answers here changes only what the drain sees.
|
|
2189
|
-
*
|
|
2190
|
-
* Returns how many tasks were skipped, so the caller can redraw once.
|
|
2191
|
-
*/
|
|
2192
|
-
function skipDeclinedSeededTasks(store, consentByTaskId, labelFor) {
|
|
2193
|
-
let skipped = 0;
|
|
2194
|
-
for (const [taskId, consent] of consentByTaskId) {
|
|
2195
|
-
if (consent.keep) continue;
|
|
2196
|
-
const task = store.get(taskId);
|
|
2197
|
-
if (!task) continue;
|
|
2198
|
-
const reason = consentSkipReason(consent);
|
|
2199
|
-
logToFile(`[orchestrator] runner-seeded ${task.type} skipped: ${reason}`);
|
|
2200
|
-
const declinedByUser = reason === SkipReason.UserDeclined;
|
|
2201
|
-
store.skip(taskId, reason, {
|
|
2202
|
-
goals: labelFor(task),
|
|
2203
|
-
did: declinedByUser ? "Nothing — the user chose to skip this step when offered it." : "Nothing — the step was offered at the start of the run and never accepted.",
|
|
2204
|
-
forNextAgent: declinedByUser ? "This step was offered and declined, so it did no work. Report it as skipped at the user’s request, not as failed." : "This step was offered and never accepted, so it did no work. Report it as not set up, and point the user at how to do it later."
|
|
2205
|
-
});
|
|
2206
|
-
skipped += 1;
|
|
2207
|
-
}
|
|
2208
|
-
return skipped;
|
|
2209
|
-
}
|
|
2210
|
-
/**
|
|
2211
|
-
* Ask for one seeded task's consent, and record how the answer came about.
|
|
2212
|
-
*
|
|
2213
|
-
* Consent and execution are separate concerns, and this is the consent half.
|
|
2214
|
-
* It runs at seed time — seconds into the run, with the user still watching —
|
|
2215
|
-
* while `seeded-deps.ts` keeps the work itself at the end of the queue. Asking
|
|
2216
|
-
* at the moment of execution instead, as 2.63.0 did, put the question in front
|
|
2217
|
-
* of a user who had long since tabbed away.
|
|
2218
|
-
*
|
|
2219
|
-
* Fails closed. The step this gates goes on to ask for live database and API
|
|
2220
|
-
* credentials, so a question that could not be put to the user is never read as
|
|
2221
|
-
* a yes.
|
|
2222
|
-
*/
|
|
2223
|
-
async function askSeededConsent(type, notice, options = {}) {
|
|
2224
|
-
const consent = await offerSeededTask(notice, options).then((answer) => ({
|
|
2225
|
-
...answer,
|
|
2226
|
-
errored: false
|
|
2227
|
-
}), (err) => {
|
|
2228
|
-
logToFile(`[orchestrator] notice failed for ${type}, declining: ${String(err)}`);
|
|
2229
|
-
analytics.captureException(err instanceof Error ? err : new Error(String(err)), { step: "orchestrator_task_notice" });
|
|
2230
|
-
return {
|
|
2231
|
-
keep: false,
|
|
2232
|
-
timedOut: false,
|
|
2233
|
-
errored: true
|
|
2234
|
-
};
|
|
2235
|
-
});
|
|
2236
|
-
analytics.wizardCapture("orchestrator task notice answered", {
|
|
2237
|
-
type,
|
|
2238
|
-
kept: consent.keep,
|
|
2239
|
-
timed_out: consent.timedOut,
|
|
2240
|
-
errored: consent.errored
|
|
2241
|
-
});
|
|
2242
|
-
return consent;
|
|
2243
|
-
}
|
|
2244
|
-
/** Whether a task's prompt lets it ask the user, and so needs the ask bridge. */
|
|
2245
|
-
function canAsk(prompt) {
|
|
2246
|
-
return (prompt?.allowedTools ?? []).includes(ASK_TOOL);
|
|
2247
|
-
}
|
|
2248
|
-
/** Splits terminal failures into run-failing (required) and reported-only (optional). */
|
|
2249
|
-
function drainVerdict(tasks) {
|
|
2250
|
-
const failed = tasks.filter((t) => t.status === TaskStatus.Failed);
|
|
2251
|
-
const pending = tasks.filter((t) => t.status === TaskStatus.Pending);
|
|
2252
|
-
return {
|
|
2253
|
-
requiredFailedTypes: failed.filter((t) => t.optional !== true).map((t) => t.type),
|
|
2254
|
-
optionalFailedTypes: failed.filter((t) => t.optional === true).map((t) => t.type),
|
|
2255
|
-
blocked: pending.length,
|
|
2256
|
-
blockedTypes: pending.map((t) => t.type)
|
|
2257
|
-
};
|
|
2258
|
-
}
|
|
2259
|
-
/**
|
|
2260
|
-
* The one-line "what went wrong" the abort message leads with.
|
|
2261
|
-
*
|
|
2262
|
-
* Both halves are named. A drain that ends with work still pending used to
|
|
2263
|
-
* report only how many steps never ran, which is the least useful fact about
|
|
2264
|
-
* them: a user who agreed to connect their data sources and then read that
|
|
2265
|
-
* "2 steps never ran" had no way to tell whether that step was one of them.
|
|
2266
|
-
*/
|
|
2267
|
-
function describeDrainFailure(verdict) {
|
|
2268
|
-
const parts = [];
|
|
2269
|
-
if (verdict.requiredFailedTypes.length > 0) parts.push(`the ${verdict.requiredFailedTypes.join(", ")} step failed`);
|
|
2270
|
-
if (verdict.blockedTypes.length > 0) parts.push(`the ${verdict.blockedTypes.join(", ")} step${verdict.blockedTypes.length === 1 ? "" : "s"} never ran`);
|
|
2271
|
-
return parts.join(", so ");
|
|
2272
|
-
}
|
|
2273
|
-
/** One `orchestrator task blocked` event per pending task, best effort once the outcome is decided. */
|
|
2274
|
-
function reportBlockedTasks(tasks, failedTypes) {
|
|
2275
|
-
for (const task of tasks) {
|
|
2276
|
-
if (task.status !== TaskStatus.Pending) continue;
|
|
2277
|
-
try {
|
|
2278
|
-
analytics.wizardCapture("orchestrator task blocked", {
|
|
2279
|
-
type: task.type,
|
|
2280
|
-
optional: task.optional === true,
|
|
2281
|
-
failed_types: failedTypes.join(",") || "none"
|
|
2282
|
-
});
|
|
2283
|
-
} catch {}
|
|
2284
|
-
}
|
|
2285
|
-
}
|
|
2286
|
-
/** How many tasks deep in the graph a task sits — 0 when it depends on nothing. */
|
|
2287
|
-
function graphDepth(task, byId, memo) {
|
|
2288
|
-
const cached = memo.get(task.id);
|
|
2289
|
-
if (cached !== void 0) return cached;
|
|
2290
|
-
memo.set(task.id, 0);
|
|
2291
|
-
const depth = task.dependsOn.reduce((deepest, id) => {
|
|
2292
|
-
const dep = byId.get(id);
|
|
2293
|
-
return dep ? Math.max(deepest, graphDepth(dep, byId, memo) + 1) : deepest;
|
|
2294
|
-
}, 0);
|
|
2295
|
-
memo.set(task.id, depth);
|
|
2296
|
-
return depth;
|
|
2297
|
-
}
|
|
2298
|
-
/**
|
|
2299
|
-
* The queue in the order the user should read it, which is not the order tasks
|
|
2300
|
-
* were queued. Tasks the wizard seeded before the planner ran are queued first
|
|
2301
|
-
* but are optional side quests, so they sit at the end of the tier they run in
|
|
2302
|
-
* — the list reads as the run unfolds, and the first line is work that actually
|
|
2303
|
-
* started. Ordering only; nothing here changes what runs when.
|
|
2304
|
-
*/
|
|
2305
|
-
function displayOrder(tasks, isOptional) {
|
|
2306
|
-
const byId = new Map(tasks.map((t) => [t.id, t]));
|
|
2307
|
-
const memo = /* @__PURE__ */ new Map();
|
|
2308
|
-
return tasks.map((task, index) => ({
|
|
2309
|
-
task,
|
|
2310
|
-
depth: graphDepth(task, byId, memo),
|
|
2311
|
-
optional: isOptional(task) ? 1 : 0,
|
|
2312
|
-
index
|
|
2313
|
-
})).sort((a, b) => a.depth - b.depth || a.optional - b.optional || a.index - b.index).map((entry) => entry.task);
|
|
2314
|
-
}
|
|
2315
|
-
/**
|
|
2316
|
-
* The run's excluded task types: CI gates plus the program's flag mapping.
|
|
2317
|
-
* Exported so a test can pin the flag→exclusion hookup against the real
|
|
2318
|
-
* program config — the registry and seed note both read this one list.
|
|
2319
|
-
*/
|
|
2320
|
-
function effectiveExcludedTaskTypes(source, flags) {
|
|
2321
|
-
return [...ciExcludedTaskTypes(), ...source.excludedTaskTypes?.(flags) ?? []];
|
|
2322
|
-
}
|
|
2323
|
-
async function runOrchestrator(context) {
|
|
2324
|
-
const controller = new AbortController();
|
|
2325
|
-
const abortFromHost = () => controller.abort();
|
|
2326
|
-
context.signal?.addEventListener("abort", abortFromHost, { once: true });
|
|
2327
|
-
if (context.signal?.aborted) abortFromHost();
|
|
2328
|
-
let cleaned = false;
|
|
2329
|
-
const cleanupQueue = () => {
|
|
2330
|
-
if (cleaned) return;
|
|
2331
|
-
cleaned = true;
|
|
2332
|
-
try {
|
|
2333
|
-
rmSync(path$1.join(context.input.installDir, QUEUE_DIR_NAME), {
|
|
2334
|
-
recursive: true,
|
|
2335
|
-
force: true
|
|
2336
|
-
});
|
|
2337
|
-
} catch (error) {
|
|
2338
|
-
try {
|
|
2339
|
-
analytics.captureException(error instanceof Error ? error : new Error(String(error)), { step: "orchestrator_cache_cleanup" });
|
|
2340
|
-
} catch {}
|
|
2341
|
-
}
|
|
2342
|
-
};
|
|
2343
|
-
try {
|
|
2344
|
-
return await executeOrchestrator({
|
|
2345
|
-
...context,
|
|
2346
|
-
signal: controller.signal
|
|
2347
|
-
}, cleanupQueue, controller);
|
|
2348
|
-
} finally {
|
|
2349
|
-
context.signal?.removeEventListener("abort", abortFromHost);
|
|
2350
|
-
cleanupQueue();
|
|
2351
|
-
}
|
|
2352
|
-
}
|
|
2353
|
-
async function executeOrchestrator({ config, input, boot, emit, interaction, signal }, cleanupQueue, controller) {
|
|
2354
|
-
if (signal?.aborted) return cancelledRun();
|
|
2355
|
-
const runId = randomUUID();
|
|
2356
|
-
const { run } = config;
|
|
2357
|
-
const programId = config.programId;
|
|
2358
|
-
const switchboardCtx = {
|
|
2359
|
-
...config.switchboard,
|
|
2360
|
-
trace: void 0
|
|
2361
|
-
};
|
|
2362
|
-
const flow = config.agentFlow ?? programId;
|
|
2363
|
-
const registry = await loadAgentRegistry(boot.skillsBaseUrl, flow, {
|
|
2364
|
-
exclude: effectiveExcludedTaskTypes(config, boot.wizardFlags),
|
|
2365
|
-
overrides: resolveStageOverrides(programId, boot.wizardFlags, boot.wizardFlagPayloads)
|
|
2366
|
-
});
|
|
2367
|
-
if (signal?.aborted) return cancelledRun();
|
|
2368
|
-
const seedPrompt = registry.seed;
|
|
2369
|
-
if (!seedPrompt) throw new Error(`No seed agent prompt (frontmatter \`seed: true\`) for flow "${flow}" is available from ${boot.skillsBaseUrl}.`);
|
|
2370
|
-
const taskModels = Object.fromEntries(["seed", ...registry.types].map((type) => {
|
|
2371
|
-
const prompt = type === "seed" ? seedPrompt : registry.get(type);
|
|
2372
|
-
const pick = resolveHarness(switchboardCtx, type);
|
|
2373
|
-
const specModel = prompt && promptModelFor(prompt, pick.harness).model;
|
|
2374
|
-
return [type, isValidModel(specModel) ? specModel : pick.model];
|
|
2375
|
-
}));
|
|
2376
|
-
logToFile(`[orchestrator] task models: ${Object.entries(taskModels).map(([t, m]) => `${t}=${m}`).join(" ")}`);
|
|
2377
|
-
analytics.wizardCapture("orchestrator task models", taskModels);
|
|
2378
|
-
const runStartMs = Date.now();
|
|
2379
|
-
const metrics = new RunMetrics(runStartMs);
|
|
2380
|
-
const durationMs = (t) => t.startedAt && t.finishedAt ? Date.parse(t.finishedAt) - Date.parse(t.startedAt) : void 0;
|
|
2381
|
-
const store = new QueueStore(input.installDir, runId, { onTransition: (event, task) => {
|
|
2382
|
-
const pick = resolveHarness(switchboardCtx, task.type);
|
|
2383
|
-
const specModel = taskModelSpec(registry, task, pick.harness).model;
|
|
2384
|
-
const base = {
|
|
2385
|
-
type: task.type,
|
|
2386
|
-
model: isValidModel(specModel) ? specModel : pick.model,
|
|
2387
|
-
attempts: task.attempts,
|
|
2388
|
-
optional: task.optional === true
|
|
2389
|
-
};
|
|
2390
|
-
switch (event) {
|
|
2391
|
-
case "enqueue":
|
|
2392
|
-
analytics.wizardCapture("orchestrator task enqueued", {
|
|
2393
|
-
type: task.type,
|
|
2394
|
-
enqueued_by: task.enqueuedBy,
|
|
2395
|
-
dynamic: task.enqueuedBy !== "orchestrator"
|
|
2396
|
-
});
|
|
2397
|
-
break;
|
|
2398
|
-
case "start":
|
|
2399
|
-
analytics.wizardCapture("orchestrator task started", {
|
|
2400
|
-
...base,
|
|
2401
|
-
...metrics.recordStart(Date.now())
|
|
2402
|
-
});
|
|
2403
|
-
break;
|
|
2404
|
-
case "complete":
|
|
2405
|
-
metrics.recordComplete(Date.now());
|
|
2406
|
-
analytics.wizardCapture("orchestrator task completed", {
|
|
2407
|
-
...base,
|
|
2408
|
-
duration_ms: durationMs(task)
|
|
2409
|
-
});
|
|
2410
|
-
break;
|
|
2411
|
-
case "skip":
|
|
2412
|
-
metrics.recordTerminal(Date.now());
|
|
2413
|
-
analytics.wizardCapture("orchestrator task skipped", {
|
|
2414
|
-
...base,
|
|
2415
|
-
duration_ms: durationMs(task),
|
|
2416
|
-
reason: task.skipReason,
|
|
2417
|
-
not_needed_reason: task.notNeededReason
|
|
2418
|
-
});
|
|
2419
|
-
break;
|
|
2420
|
-
case "fail":
|
|
2421
|
-
metrics.recordTerminal(Date.now());
|
|
2422
|
-
analytics.wizardCapture("orchestrator task failed", {
|
|
2423
|
-
...base,
|
|
2424
|
-
duration_ms: durationMs(task),
|
|
2425
|
-
error: task.error?.type
|
|
2426
|
-
});
|
|
2427
|
-
break;
|
|
2428
|
-
case "requeue": break;
|
|
2429
|
-
}
|
|
2430
|
-
} });
|
|
2431
|
-
let examplePath;
|
|
2432
|
-
let commandmentsPath;
|
|
2433
|
-
let referenceInstallPath;
|
|
2434
|
-
const menuSkillEntries = await fetchSkillMenuEntries(boot.skillsBaseUrl);
|
|
2435
|
-
if (signal?.aborted) return cancelledRun();
|
|
2436
|
-
const framework = input.integration ?? input.skillId ?? void 0;
|
|
2437
|
-
const referenceSkillId = framework ? resolveReferenceSkillId(menuSkillEntries, framework) : void 0;
|
|
2438
|
-
if (referenceSkillId) {
|
|
2439
|
-
const ref = await installSkillById(referenceSkillId, input.installDir, boot.skillsBaseUrl, {
|
|
2440
|
-
skillsRoot: path$1.join(QUEUE_DIR_NAME, "reference"),
|
|
2441
|
-
triage: boot.triageProvider
|
|
2442
|
-
});
|
|
2443
|
-
if (signal?.aborted) return cancelledRun();
|
|
2444
|
-
if (ref.kind === "ok") {
|
|
2445
|
-
referenceInstallPath = ref.path;
|
|
2446
|
-
const example = path$1.join(ref.path, "references", "EXAMPLE.md");
|
|
2447
|
-
if (existsSync(path$1.join(input.installDir, example))) examplePath = example;
|
|
2448
|
-
const commandments = path$1.join(ref.path, "references", "COMMANDMENTS.md");
|
|
2449
|
-
if (existsSync(path$1.join(input.installDir, commandments))) commandmentsPath = commandments;
|
|
2450
|
-
} else logToFile(`[orchestrator] reference unavailable: ${ref.kind} (${referenceSkillId})`);
|
|
2451
|
-
} else if (framework) logToFile(`[orchestrator] no integration skill for framework "${framework}"`);
|
|
2452
|
-
const missingVariants = [];
|
|
2453
|
-
for (const type of registry.types) for (const skillId of registry.get(type)?.skills ?? []) {
|
|
2454
|
-
if (resolveSkillVariantId(menuSkillEntries, skillId, framework)) continue;
|
|
2455
|
-
missingVariants.push(`${type}/${skillId}`);
|
|
2456
|
-
logToFile(`[orchestrator] no skill variant type=${type} skill=${skillId} framework=${framework ?? "none"}`);
|
|
2457
|
-
analytics.wizardCapture("orchestrator skill variant missing", {
|
|
2458
|
-
task_type: type,
|
|
2459
|
-
skill: skillId,
|
|
2460
|
-
framework
|
|
2461
|
-
});
|
|
2462
|
-
}
|
|
2463
|
-
if (missingVariants.length > 0) {
|
|
2464
|
-
const docsUrl = framework ? input.frameworkDocsUrl : void 0;
|
|
2465
|
-
return failed({
|
|
2466
|
-
code: ErrorCodes.AgentOrchestratorSkillVariantMissing,
|
|
2467
|
-
message: `Setup instructions for this project failed to download.
|
|
2468
|
-
Please try again, or contact wizard@posthog.com.
|
|
2469
|
-
|
|
2470
|
-
You can also set up with your agent by downloading the skills here:
|
|
2471
|
-
https://github.com/PostHog/context-mill/releases
|
|
2472
|
-
or integrate manually here:
|
|
2473
|
-
${docsUrl ?? "https://posthog.com/docs"}`,
|
|
2474
|
-
error: new WizardError("Orchestrator preflight: skill variant missing", {
|
|
2475
|
-
missing: missingVariants.join(", "),
|
|
2476
|
-
framework
|
|
2477
|
-
}, ErrorCodes.AgentOrchestratorSkillVariantMissing)
|
|
2478
|
-
});
|
|
2479
|
-
}
|
|
2480
|
-
const promptContext = {
|
|
2481
|
-
projectId: boot.credentials.projectId,
|
|
2482
|
-
projectApiKey: boot.credentials.projectApiKey,
|
|
2483
|
-
host: boot.credentials.host,
|
|
2484
|
-
examplePath,
|
|
2485
|
-
commandmentsPath
|
|
2486
|
-
};
|
|
2487
|
-
logToFile(`[orchestrator] START program=${programId} dir=${input.installDir} run=${runId}`);
|
|
2488
|
-
analytics.wizardCapture("orchestrator started", { program_id: programId });
|
|
2489
|
-
emit({
|
|
2490
|
-
kind: "lifecycle",
|
|
2491
|
-
phase: "started"
|
|
2492
|
-
});
|
|
2493
|
-
const labelFor = (t) => t.label ?? registry.get(t.type)?.label ?? t.type;
|
|
2494
|
-
const renderQueue = () => emit({
|
|
2495
|
-
kind: "tasks",
|
|
2496
|
-
tasks: displayOrder(store.list(), (t) => registry.runnerSeededTypes.includes(t.type)).map((t) => ({
|
|
2497
|
-
id: `${runId}:${t.id}`,
|
|
2498
|
-
source: runId,
|
|
2499
|
-
content: labelFor(t),
|
|
2500
|
-
status: toTodoStatus(t.status),
|
|
2501
|
-
activeForm: labelFor(t)
|
|
2502
|
-
}))
|
|
2503
|
-
});
|
|
2504
|
-
const orchestratorCtx = (currentTaskId) => ({
|
|
2505
|
-
store,
|
|
2506
|
-
validTypes: registry.enqueueableTypes,
|
|
2507
|
-
sinkTypes: registry.sinkTypes,
|
|
2508
|
-
runnerSeededTypes: registry.runnerSeededTypes,
|
|
2509
|
-
optionalTypes: registry.optionalTypes,
|
|
2510
|
-
currentTaskId
|
|
2511
|
-
});
|
|
2512
|
-
const seedEntries = areSeededTasksEnabled(boot.wizardFlags) ? config.seedTasks?.() ?? [] : [];
|
|
2513
|
-
const seededTypes = [];
|
|
2514
|
-
const seededTasks = [];
|
|
2515
|
-
const seededConsent = /* @__PURE__ */ new Map();
|
|
2516
|
-
for (const seeded of seedEntries) {
|
|
2517
|
-
if (!registry.runnerSeededTypes.includes(seeded.type)) {
|
|
2518
|
-
logToFile(`[orchestrator] skipping runner-seeded task "${seeded.type}": not a runner-seeded type in this flow`);
|
|
2519
|
-
continue;
|
|
2520
|
-
}
|
|
2521
|
-
const task = store.enqueue({
|
|
2522
|
-
type: seeded.type,
|
|
2523
|
-
label: seeded.label,
|
|
2524
|
-
inputs: seeded.inputs,
|
|
2525
|
-
enqueuedBy: "orchestrator",
|
|
2526
|
-
optional: true
|
|
2527
|
-
});
|
|
2528
|
-
seededTypes.push(seeded.type);
|
|
2529
|
-
seededTasks.push(task);
|
|
2530
|
-
if (seeded.notice) {
|
|
2531
|
-
seededConsent.set(task.id, await askSeededConsent(seeded.type, seeded.notice, {
|
|
2532
|
-
interaction,
|
|
2533
|
-
signal
|
|
2534
|
-
}));
|
|
2535
|
-
if (signal?.aborted) return cancelledRun();
|
|
2536
|
-
}
|
|
2537
|
-
logToFile(`[orchestrator] runner-seeded task ${seeded.type}`);
|
|
2538
|
-
}
|
|
2539
|
-
const askingTypes = seededTasks.filter((task) => seededConsent.get(task.id)?.keep !== false).map((task) => task.type).filter((type) => canAsk(registry.get(type)));
|
|
2540
|
-
analytics.setTag("orchestrator_awaits_user", askingTypes.length > 0);
|
|
2541
|
-
analytics.setTag("orchestrator_runner_seeded_types", seededTypes.join(",") || "none");
|
|
2542
|
-
if (askingTypes.length > 0) analytics.wizardCapture("orchestrator awaits user", {
|
|
2543
|
-
task_types: askingTypes,
|
|
2544
|
-
ask_timeout_ms: LONGER_ASK_TIMEOUT_MS
|
|
2545
|
-
});
|
|
2546
|
-
const askBridge = shouldDisableAsk(input.flags) ? void 0 : createAskBridge(interaction, {
|
|
2547
|
-
signal,
|
|
2548
|
-
getSource: () => input.skillId ?? programId,
|
|
2549
|
-
beforeShow: () => {
|
|
2550
|
-
metrics.recordAsk(Date.now());
|
|
2551
|
-
ringTerminalBell();
|
|
2552
|
-
},
|
|
2553
|
-
richLinks: run.richLinks ?? false,
|
|
2554
|
-
timeoutMs: LONGER_ASK_TIMEOUT_MS
|
|
2555
|
-
});
|
|
2556
|
-
const spinner = createEmitSpinner(emit);
|
|
2557
|
-
const seedPick = resolveHarness(switchboardCtx, "seed");
|
|
2558
|
-
const seedHarness = requireTaskHarness(seedPick);
|
|
2559
|
-
const seedModel = promptModelFor(seedPrompt, seedPick.harness);
|
|
2560
|
-
const seedResult = await seedHarness.runTask({
|
|
2561
|
-
signal,
|
|
2562
|
-
config,
|
|
2563
|
-
input,
|
|
2564
|
-
boot,
|
|
2565
|
-
emit,
|
|
2566
|
-
prompt: assembleSeedPrompt(promptContext, seedPrompt.body, store.list(), registry.excludedTypes),
|
|
2567
|
-
spinner,
|
|
2568
|
-
model: requireKnownModel(seedModel.model, seedPick.model),
|
|
2569
|
-
effort: seedModel.effort,
|
|
2570
|
-
...agentRunTools(seedPrompt),
|
|
2571
|
-
orchestrator: orchestratorCtx(),
|
|
2572
|
-
spinnerMessage: "Planning the integration...",
|
|
2573
|
-
successMessage: "Planned the integration",
|
|
2574
|
-
requestRemark: false,
|
|
2575
|
-
analyticsProperties: {
|
|
2576
|
-
task_type: "seed",
|
|
2577
|
-
harness: seedPick.harness
|
|
2578
|
-
}
|
|
2579
|
-
});
|
|
2580
|
-
if (signal?.aborted) return cancelledRun();
|
|
2581
|
-
const seedTerminal = terminalResult(seedResult);
|
|
2582
|
-
if (seedTerminal) return seedTerminal;
|
|
2583
|
-
analytics.wizardCapture("orchestrator seeded", {
|
|
2584
|
-
task_count: store.list().length,
|
|
2585
|
-
types: store.list().map((t) => t.type)
|
|
2586
|
-
});
|
|
2587
|
-
renderQueue();
|
|
2588
|
-
const deferred = deferSeededTasks(store, seededTasks, (type) => registry.get(type)?.dependsOn ?? [], registry.sinkTypes);
|
|
2589
|
-
for (const entry of deferred) {
|
|
2590
|
-
logToFile(`[orchestrator] deferred runner-seeded ${entry.type}: ${entry.added.length} deps (${entry.declared ? `declared: ${entry.declaredTypes.join(", ")}` : "default"})${entry.refused.length > 0 ? `, ${entry.refused.length} refused` : ""}${entry.unresolvedTypes.length > 0 ? `, unresolved: ${entry.unresolvedTypes.join(", ")}` : ""}`);
|
|
2591
|
-
analytics.wizardCapture("orchestrator seeded task deferred", {
|
|
2592
|
-
type: entry.type,
|
|
2593
|
-
dep_count: entry.added.length,
|
|
2594
|
-
declared: entry.declared,
|
|
2595
|
-
declared_types: entry.declaredTypes,
|
|
2596
|
-
unresolved_types: entry.unresolvedTypes,
|
|
2597
|
-
refused_count: entry.refused.length
|
|
2598
|
-
});
|
|
2599
|
-
}
|
|
2600
|
-
if (deferred.length > 0) renderQueue();
|
|
2601
|
-
for (const seeded of seededTasks) {
|
|
2602
|
-
const dependants = store.list().filter((t) => !registry.sinkTypes.includes(t.type) && t.id !== seeded.id && dependencyClosure(store, [t.id]).has(seeded.id));
|
|
2603
|
-
if (dependants.length === 0) continue;
|
|
2604
|
-
logToFile(`[orchestrator] one-way rule broken: ${dependants.map((t) => t.type).join(", ")} depend on runner-seeded ${seeded.type}`);
|
|
2605
|
-
analytics.wizardCapture("orchestrator seeded task depended on", {
|
|
2606
|
-
type: seeded.type,
|
|
2607
|
-
dependant_types: dependants.map((t) => t.type)
|
|
2608
|
-
});
|
|
2609
|
-
}
|
|
2610
|
-
const unwaited = store.list().filter((t) => registry.sinkTypes.includes(t.type)).flatMap((sink) => uncoveredBySink({
|
|
2611
|
-
store,
|
|
2612
|
-
validTypes: registry.types,
|
|
2613
|
-
sinkTypes: registry.sinkTypes
|
|
2614
|
-
}, {
|
|
2615
|
-
type: sink.type,
|
|
2616
|
-
dependsOn: sink.dependsOn
|
|
2617
|
-
}).filter((t) => t.id !== sink.id));
|
|
2618
|
-
if (unwaited.length > 0) {
|
|
2619
|
-
analytics.wizardCapture("orchestrator sink invariant violated", { uncovered_types: unwaited.map((t) => t.type) });
|
|
2620
|
-
return failed({
|
|
2621
|
-
code: ErrorCodes.AgentOrchestratorSinkInvariant,
|
|
2622
|
-
message: `The wizard could not plan this setup: the final step would have skipped ${unwaited.map((t) => t.type).join(", ")}.\n\nPlease report this to: ${WIZARD_CONTACT_EMAIL}`,
|
|
2623
|
-
error: new WizardError("orchestrator sink does not cover the queue", {
|
|
2624
|
-
uncovered: unwaited.map((t) => `${t.type} (${t.id})`).join(", "),
|
|
2625
|
-
queue_state: JSON.stringify(store.list())
|
|
2626
|
-
}, ErrorCodes.AgentOrchestratorSinkInvariant)
|
|
2627
|
-
});
|
|
2628
|
-
}
|
|
2629
|
-
const taskSkillsRoot = path$1.join(QUEUE_DIR_NAME, "skills");
|
|
2630
|
-
const claudeSkillsDir = path$1.join(input.installDir, ".claude", "skills");
|
|
2631
|
-
const preexistingSkills = new Set(existsSync(claudeSkillsDir) ? readdirSync(claudeSkillsDir) : []);
|
|
2632
|
-
const runTask = async (task) => {
|
|
2633
|
-
if (signal?.aborted) return;
|
|
2634
|
-
renderQueue();
|
|
2635
|
-
try {
|
|
2636
|
-
const resolved = resolveTask(registry, task, store);
|
|
2637
|
-
const skillPaths = [];
|
|
2638
|
-
for (const skillId of resolved.skills) {
|
|
2639
|
-
if (signal?.aborted) return;
|
|
2640
|
-
const variantId = resolveSkillVariantId(menuSkillEntries, skillId, framework);
|
|
2641
|
-
if (!variantId) {
|
|
2642
|
-
logToFile(`[orchestrator] no skill variant type=${task.type} skill=${skillId} framework=${framework ?? "none"}`);
|
|
2643
|
-
continue;
|
|
2644
|
-
}
|
|
2645
|
-
const result = await installSkillById(variantId, input.installDir, boot.skillsBaseUrl, {
|
|
2646
|
-
skillsRoot: taskSkillsRoot,
|
|
2647
|
-
triage: boot.triageProvider
|
|
2648
|
-
});
|
|
2649
|
-
if (signal?.aborted) return;
|
|
2650
|
-
if (result.kind === "ok") skillPaths.push(path$1.join(result.path, "SKILL.md"));
|
|
2651
|
-
else {
|
|
2652
|
-
logToFile(`[orchestrator] skill install failed type=${task.type} skill=${variantId} ${result.kind}`);
|
|
2653
|
-
throw new Error(`Skill "${variantId}" for task "${task.type}" could not be installed (${result.kind}). If this is a permissions error, check that the project directory is writable.`);
|
|
2654
|
-
}
|
|
2655
|
-
}
|
|
2656
|
-
const taskPick = resolveHarness(switchboardCtx, task.type);
|
|
2657
|
-
const taskHarness = requireTaskHarness(taskPick);
|
|
2658
|
-
const taskModel = taskModelSpec(registry, task, taskPick.harness);
|
|
2659
|
-
let taskResult;
|
|
2660
|
-
try {
|
|
2661
|
-
taskResult = await taskHarness.runTask({
|
|
2662
|
-
signal,
|
|
2663
|
-
config,
|
|
2664
|
-
input,
|
|
2665
|
-
boot,
|
|
2666
|
-
emit,
|
|
2667
|
-
prompt: assembleTaskPrompt(promptContext, resolved.prompt, skillPaths),
|
|
2668
|
-
spinner,
|
|
2669
|
-
model: requireKnownModel(taskModel.model, taskPick.model),
|
|
2670
|
-
effort: taskModel.effort,
|
|
2671
|
-
allowedTools: resolved.allowedTools,
|
|
2672
|
-
disallowedTools: resolved.disallowedTools,
|
|
2673
|
-
askBridge: canAsk(registry.get(task.type)) ? askBridge : void 0,
|
|
2674
|
-
orchestrator: orchestratorCtx(task.id),
|
|
2675
|
-
spinnerMessage: "",
|
|
2676
|
-
successMessage: "",
|
|
2677
|
-
requestRemark: false,
|
|
2678
|
-
analyticsProperties: {
|
|
2679
|
-
task_type: task.type,
|
|
2680
|
-
task_id: task.id,
|
|
2681
|
-
harness: taskPick.harness
|
|
2682
|
-
}
|
|
2683
|
-
});
|
|
2684
|
-
} catch (error) {
|
|
2685
|
-
if (signal?.aborted) return;
|
|
2686
|
-
if (error instanceof RunTaskFatal) throw error;
|
|
2687
|
-
const failure = classifyRunFailure(error);
|
|
2688
|
-
throw new RunTaskFatal({
|
|
2689
|
-
code: failure.code,
|
|
2690
|
-
message: failure.message,
|
|
2691
|
-
error: error instanceof Error ? error : void 0
|
|
2692
|
-
}, "failed", task.type);
|
|
2693
|
-
}
|
|
2694
|
-
if (signal?.aborted) return;
|
|
2695
|
-
const terminal = terminalResult(taskResult);
|
|
2696
|
-
if (terminal) throw new RunTaskFatal(terminal.failure, terminal.outcome, task.type);
|
|
2697
|
-
} finally {
|
|
2698
|
-
try {
|
|
2699
|
-
sweepRunInstalledSkills(claudeSkillsDir, preexistingSkills, referenceSkillId);
|
|
2700
|
-
} catch (err) {
|
|
2701
|
-
try {
|
|
2702
|
-
logToFile("[orchestrator] per-task skill sweep failed:", err);
|
|
2703
|
-
} catch {}
|
|
2704
|
-
}
|
|
2705
|
-
try {
|
|
2706
|
-
renderQueue();
|
|
2707
|
-
} catch (err) {
|
|
2708
|
-
try {
|
|
2709
|
-
logToFile("[orchestrator] per-task queue render failed:", err);
|
|
2710
|
-
} catch {}
|
|
2711
|
-
}
|
|
2712
|
-
}
|
|
2713
|
-
};
|
|
2714
|
-
if (skipDeclinedSeededTasks(store, seededConsent, labelFor) > 0) renderQueue();
|
|
2715
|
-
let fatal;
|
|
2716
|
-
try {
|
|
2717
|
-
await drainQueue(store, runTask, {
|
|
2718
|
-
...DEFAULT_DRAIN_OPTIONS,
|
|
2719
|
-
signal,
|
|
2720
|
-
onFatal: () => controller.abort()
|
|
2721
|
-
});
|
|
2722
|
-
} catch (error) {
|
|
2723
|
-
if (!(error instanceof RunTaskFatal)) throw error;
|
|
2724
|
-
fatal = error;
|
|
2725
|
-
} finally {
|
|
2726
|
-
config.hooks?.recordTaskOutcomes?.(store.list().map((t) => ({
|
|
2727
|
-
type: t.type,
|
|
2728
|
-
status: t.status,
|
|
2729
|
-
optional: t.optional === true
|
|
2730
|
-
})));
|
|
2731
|
-
try {
|
|
2732
|
-
if (!signal?.aborted && referenceSkillId && referenceInstallPath) promoteReferenceSkill(path$1.join(input.installDir, referenceInstallPath), claudeSkillsDir, referenceSkillId);
|
|
2733
|
-
} catch (err) {
|
|
2734
|
-
try {
|
|
2735
|
-
analytics.captureException(err instanceof Error ? err : new Error(String(err)), { step: "orchestrator_reference_promote" });
|
|
2736
|
-
} catch {}
|
|
2737
|
-
}
|
|
2738
|
-
cleanupQueue();
|
|
2739
|
-
try {
|
|
2740
|
-
sweepRunInstalledSkills(claudeSkillsDir, preexistingSkills, referenceSkillId);
|
|
2741
|
-
} catch (err) {
|
|
2742
|
-
try {
|
|
2743
|
-
analytics.captureException(err instanceof Error ? err : new Error(String(err)), { step: "orchestrator_skill_sweep" });
|
|
2744
|
-
} catch {}
|
|
2745
|
-
}
|
|
2746
|
-
}
|
|
2747
|
-
if (fatal) {
|
|
2748
|
-
const stoppedBy = drainVerdict(store.list()).requiredFailedTypes;
|
|
2749
|
-
if (fatal.taskType && !stoppedBy.includes(fatal.taskType)) stoppedBy.push(fatal.taskType);
|
|
2750
|
-
reportBlockedTasks(store.list(), stoppedBy);
|
|
2751
|
-
return {
|
|
2752
|
-
outcome: fatal.outcome,
|
|
2753
|
-
failure: fatal.failure
|
|
2754
|
-
};
|
|
2755
|
-
}
|
|
2756
|
-
if (signal?.aborted) return cancelledRun();
|
|
2757
|
-
renderQueue();
|
|
2758
|
-
const summary = store.summary();
|
|
2759
|
-
logToFile(`[orchestrator] DONE done=${summary.done} failed=${summary.failed} total=${summary.total}`);
|
|
2760
|
-
analytics.wizardCapture("orchestrator run finished", {
|
|
2761
|
-
tasks_total: summary.total,
|
|
2762
|
-
tasks_done: summary.done,
|
|
2763
|
-
tasks_failed: summary.failed,
|
|
2764
|
-
tasks_skipped: summary[TaskStatus.Skipped],
|
|
2765
|
-
total_duration_ms: Date.now() - runStartMs,
|
|
2766
|
-
...metrics.summary(),
|
|
2767
|
-
dynamic_enqueue_count: store.list().filter((t) => t.enqueuedBy !== "orchestrator").length,
|
|
2768
|
-
retried_task_count: store.list().filter((t) => t.attempts > 1).length
|
|
2769
|
-
});
|
|
2770
|
-
const reviewTask = store.list().find((t) => t.type === "review");
|
|
2771
|
-
const conflict = reviewTask ? store.readHandoff(reviewTask.id)?.conflict : void 0;
|
|
2772
|
-
const notRequired = summary[TaskStatus.Skipped];
|
|
2773
|
-
const verdict = drainVerdict(store.list());
|
|
2774
|
-
const blocked = verdict.blocked;
|
|
2775
|
-
reportBlockedTasks(store.list(), verdict.requiredFailedTypes);
|
|
2776
|
-
if (verdict.requiredFailedTypes.length > 0 || blocked > 0) {
|
|
2777
|
-
const failedTypes = verdict.requiredFailedTypes.join(", ");
|
|
2778
|
-
const whatFailed = describeDrainFailure(verdict);
|
|
2779
|
-
const missingScopes = (boot.credentials.missingScopes ?? []).filter((scope) => WIZARD_PROVISIONING_SCOPES.some((required) => required === scope) || !WIZARD_OAUTH_SCOPES.some((requested) => requested === scope));
|
|
2780
|
-
const message = missingScopes.length > 0 ? `The wizard could not finish setup: ${whatFailed}, and this run was authorized without the following permission${missingScopes.length === 1 ? "" : "s"}: ${missingScopes.join(", ")}.\n\nPlease try again, approving all permissions on the PostHog authorization screen. If it still fails, report it to: ${WIZARD_CONTACT_EMAIL}` : `The wizard was unable to set up PostHog: ${whatFailed}.\n\nPlease report this to: ${WIZARD_CONTACT_EMAIL}`;
|
|
2781
|
-
return failed({
|
|
2782
|
-
code: ErrorCodes.AgentOrchestratorTasksFailed,
|
|
2783
|
-
message,
|
|
2784
|
-
error: new WizardError("orchestrator drain ended with failed tasks", {
|
|
2785
|
-
tasks_failed: summary.failed,
|
|
2786
|
-
tasks_blocked: blocked,
|
|
2787
|
-
failed_types: failedTypes,
|
|
2788
|
-
missing_oauth_scopes: missingScopes.join(" "),
|
|
2789
|
-
queue_state: JSON.stringify(store.list())
|
|
2790
|
-
}, ErrorCodes.AgentOrchestratorTasksFailed)
|
|
2791
|
-
});
|
|
2792
|
-
}
|
|
2793
|
-
if (summary.total === 0) return failed({
|
|
2794
|
-
code: ErrorCodes.AgentOrchestratorHollowRun,
|
|
2795
|
-
message: `The wizard was unable to set up PostHog: the planning step produced no work, so nothing ran.\n\nPlease try again — and if it happens again, report it to: ${WIZARD_CONTACT_EMAIL}`,
|
|
2796
|
-
error: new WizardError("orchestrator drain produced zero tasks", { queue_state: JSON.stringify(store.list()) }, ErrorCodes.AgentOrchestratorHollowRun)
|
|
2797
|
-
});
|
|
2798
|
-
const optionalFailedCount = verdict.optionalFailedTypes.length;
|
|
2799
|
-
const stepNotes = [notRequired > 0 ? `${notRequired} skipped as not required` : "", optionalFailedCount > 0 ? `${optionalFailedCount} optional step failed` : ""].filter(Boolean);
|
|
2800
|
-
const message = conflict ? "PostHog set up, with one conflict to review." : `PostHog set up: ${summary.done}/${summary.total - notRequired - optionalFailedCount} steps completed${stepNotes.length > 0 ? ` (${stepNotes.join(", ")})` : ""}.`;
|
|
2801
|
-
const outro = {
|
|
2802
|
-
kind: "success",
|
|
2803
|
-
message,
|
|
2804
|
-
body: conflict ? `⚠ Build conflict: ${conflict}\nFull details are in the setup report.` : void 0,
|
|
2805
|
-
docsUrl: "https://posthog.com/docs/ai-engineering/ai-wizard",
|
|
2806
|
-
nextSteps: config.hooks?.buildOutroNextSteps?.(boot.credentials, completedSeededTypes(store, seededTasks))
|
|
2807
|
-
};
|
|
2808
|
-
emit({
|
|
2809
|
-
kind: "completion",
|
|
2810
|
-
outro
|
|
2811
|
-
});
|
|
2812
|
-
emit({
|
|
2813
|
-
kind: "lifecycle",
|
|
2814
|
-
phase: "completed",
|
|
2815
|
-
message
|
|
2816
|
-
});
|
|
2817
|
-
return {
|
|
2818
|
-
outcome: "success",
|
|
2819
|
-
outro
|
|
2820
|
-
};
|
|
2821
|
-
}
|
|
2822
|
-
//#endregion
|
|
2823
|
-
//#region src/agent/runner/switchboard/sequence.ts
|
|
2824
|
-
/**
|
|
2825
|
-
* Sequence axis: gate helpers, registry, middleware, resolver.
|
|
2826
|
-
* Percentage rollouts are PostHog-side — the gate just reads the resolved bool.
|
|
2827
|
-
*/
|
|
2828
|
-
const SEQUENCE_OPTIONS = {
|
|
2829
|
-
["linear"]: {
|
|
2830
|
-
name: "linear",
|
|
2831
|
-
run: (ctx) => runLinearProgram(ctx)
|
|
2832
|
-
},
|
|
2833
|
-
["orchestrator"]: {
|
|
2834
|
-
name: "orchestrator",
|
|
2835
|
-
run: (ctx) => runOrchestrator(ctx)
|
|
2836
|
-
}
|
|
2837
|
-
};
|
|
2838
|
-
function getSequence(name) {
|
|
2839
|
-
const sequence = SEQUENCE_OPTIONS[name];
|
|
2840
|
-
if (!sequence) throw new Error(`No sequence registered for '${name}'.`);
|
|
2841
|
-
return sequence;
|
|
2842
|
-
}
|
|
2843
|
-
/**
|
|
2844
|
-
* A composed sub-run (integration inside self-driving) is structurally
|
|
2845
|
-
* linear: the orchestrator owns the full run lifecycle (queue, outro) and
|
|
2846
|
-
* cannot nest. Sits above every override, including CLI.
|
|
2847
|
-
*/
|
|
2848
|
-
const composedClampMw = (ctx, next) => {
|
|
2849
|
-
if (!ctx.composed) return next();
|
|
2850
|
-
if (ctx.trace) ctx.trace.sequence = "composed";
|
|
2851
|
-
return "linear";
|
|
2852
|
-
};
|
|
2853
|
-
/** A program's own flag route may pin the sequence; wins over the global orchestrator flag. Traced as 'payload' to stay distinguishable from sequence experiments ('flag'). */
|
|
2854
|
-
const flagRouteSequenceMw = (ctx, next) => {
|
|
2855
|
-
const route = resolveFlagRoute(ctx.program, ctx.flags, ctx.flagPayloads);
|
|
2856
|
-
if (!route?.sequence) return next();
|
|
2857
|
-
if (ctx.trace) ctx.trace.sequence = "payload";
|
|
2858
|
-
return route.sequence;
|
|
2859
|
-
};
|
|
2860
|
-
/** Sequence experiments (e.g. wizard-orchestrator), each inert outside its declared programs. */
|
|
2861
|
-
const sequenceExperimentMw = (ctx, next) => {
|
|
2862
|
-
const sequence = resolveFlagSequence(ctx.program, ctx.flags);
|
|
2863
|
-
if (!sequence) return next();
|
|
2864
|
-
if (ctx.trace) ctx.trace.sequence = "flag";
|
|
2865
|
-
return sequence;
|
|
2866
|
-
};
|
|
2867
|
-
/**
|
|
2868
|
-
* The orchestrator drives harnesses through `runTask`; a harness that has not
|
|
2869
|
-
* implemented it clamps the run to linear. A capability check, not a harness
|
|
2870
|
-
* identity check — a harness gains orchestrator support by implementing the
|
|
2871
|
-
* method, with no switchboard change. Sits below the CLI override so
|
|
2872
|
-
* `--sequence orchestrator` still reproduces the hard error in dev builds.
|
|
2873
|
-
*/
|
|
2874
|
-
const runTaskCapabilityClampMw = (ctx, next) => {
|
|
2875
|
-
const pick = resolveHarness(ctx);
|
|
2876
|
-
if (getHarness(pick.harness).runTask) return next();
|
|
2877
|
-
if (isOrchestratorEnabled(ctx.flags)) logToFile(`[switchboard] wizard-orchestrator ignored: ${pick.harness} has no runTask, clamping to linear`);
|
|
2878
|
-
if (ctx.trace) ctx.trace.sequence = "runtask-clamp";
|
|
2879
|
-
return "linear";
|
|
2880
|
-
};
|
|
2881
|
-
const SEQUENCE_MIDDLEWARE = [
|
|
2882
|
-
composedClampMw,
|
|
2883
|
-
...[],
|
|
2884
|
-
runTaskCapabilityClampMw,
|
|
2885
|
-
flagRouteSequenceMw,
|
|
2886
|
-
sequenceExperimentMw
|
|
2887
|
-
];
|
|
2888
|
-
/** CLI wins over `wizard-orchestrator` flag wins over binding default. */
|
|
2889
|
-
function resolveSequence(ctx) {
|
|
2890
|
-
const sequence = runChain(SEQUENCE_MIDDLEWARE, ctx, () => {
|
|
2891
|
-
if (ctx.trace) ctx.trace.sequence = "binding";
|
|
2892
|
-
return (PROGRAM_BINDINGS[ctx.program] ?? DEFAULT_BINDING).sequence;
|
|
2893
|
-
});
|
|
2894
|
-
logToFile(`[switchboard] resolved: program=${ctx.program} sequence=${sequence}${ctx.trace?.sequence ? ` (${ctx.trace.sequence})` : ""}`);
|
|
2895
|
-
return sequence;
|
|
2896
|
-
}
|
|
2897
|
-
//#endregion
|
|
2898
|
-
//#region src/agent/runner/switchboard/index.ts
|
|
2899
|
-
/**
|
|
2900
|
-
* Run a middleware chain over `ctx`. Each middleware receives `next` (which
|
|
2901
|
-
* runs the rest of the chain) and can either:
|
|
2902
|
-
* - defer: call `next()` and optionally modify its result (overlay pattern)
|
|
2903
|
-
* - short-circuit: return a value without calling `next()` (skip the rest)
|
|
2904
|
-
*
|
|
2905
|
-
* **Earlier in the array = higher precedence.** Index 0 runs first and can
|
|
2906
|
-
* short-circuit the rest; index 1 only runs if index 0 deferred. So
|
|
2907
|
-
* `[cliSequenceMw, orchestratorFeatureFlagMw]` means CLI takes precedence over the
|
|
2908
|
-
* flag, not the other way around.
|
|
2909
|
-
*
|
|
2910
|
-
* `fallback` runs at the end — reached only when every middleware deferred.
|
|
2911
|
-
* Typically the map read for the base value.
|
|
2912
|
-
*/
|
|
2913
|
-
function runChain(chain, ctx, fallback) {
|
|
2914
|
-
function step(index) {
|
|
2915
|
-
if (index >= chain.length) return fallback();
|
|
2916
|
-
const middleware = chain[index];
|
|
2917
|
-
const next = () => step(index + 1);
|
|
2918
|
-
return middleware(ctx, next);
|
|
2919
|
-
}
|
|
2920
|
-
return step(0);
|
|
2921
|
-
}
|
|
2922
|
-
const DEFAULT_BINDING = {
|
|
2923
|
-
sequence: "linear",
|
|
2924
|
-
harness: "pi",
|
|
2925
|
-
model: GPT5_6_SOL_MODEL,
|
|
2926
|
-
thinkingLevel: "medium"
|
|
2927
|
-
};
|
|
2928
|
-
/**
|
|
2929
|
-
* Per-program routing. Kept in lockstep with `PROGRAM_REGISTRY` by the
|
|
2930
|
-
* switchboard test. Anything absent falls back to `DEFAULT_BINDING`.
|
|
2931
|
-
*/
|
|
2932
|
-
const PROGRAM_BINDINGS = {
|
|
2933
|
-
"posthog-integration": DEFAULT_BINDING,
|
|
2934
|
-
"revenue-analytics-setup": DEFAULT_BINDING,
|
|
2935
|
-
"warehouse-source": DEFAULT_BINDING,
|
|
2936
|
-
"error-tracking-upload-source-maps": {
|
|
2937
|
-
sequence: "linear",
|
|
2938
|
-
harness: "pi",
|
|
2939
|
-
model: GPT5_6_SOL_MODEL,
|
|
2940
|
-
thinkingLevel: "medium"
|
|
2941
|
-
},
|
|
2942
|
-
audit: DEFAULT_BINDING,
|
|
2943
|
-
"events-audit": DEFAULT_BINDING,
|
|
2944
|
-
"posthog-doctor": DEFAULT_BINDING,
|
|
2945
|
-
"web-analytics-doctor": DEFAULT_BINDING,
|
|
2946
|
-
migration: DEFAULT_BINDING,
|
|
2947
|
-
"self-driving": DEFAULT_BINDING,
|
|
2948
|
-
"agent-skill": DEFAULT_BINDING,
|
|
2949
|
-
"mcp-add": DEFAULT_BINDING,
|
|
2950
|
-
"mcp-remove": DEFAULT_BINDING,
|
|
2951
|
-
"mcp-tutorial": DEFAULT_BINDING,
|
|
2952
|
-
"mcp-analytics": DEFAULT_BINDING,
|
|
2953
|
-
metrics: {
|
|
2954
|
-
sequence: "orchestrator",
|
|
2955
|
-
harness: "pi",
|
|
2956
|
-
model: DEFAULT_AGENT_MODEL
|
|
2957
|
-
},
|
|
2958
|
-
"replay-vision": {
|
|
2959
|
-
sequence: "orchestrator",
|
|
2960
|
-
harness: "anthropic",
|
|
2961
|
-
model: DEFAULT_AGENT_MODEL
|
|
2962
|
-
},
|
|
2963
|
-
"error-tracking": {
|
|
2964
|
-
sequence: "orchestrator",
|
|
2965
|
-
harness: "pi",
|
|
2966
|
-
model: DEFAULT_AGENT_MODEL
|
|
2967
|
-
},
|
|
2968
|
-
"ai-observability": {
|
|
2969
|
-
sequence: "linear",
|
|
2970
|
-
harness: "pi",
|
|
2971
|
-
model: GPT5_6_TERRA_MODEL,
|
|
2972
|
-
thinkingLevel: "high"
|
|
2973
|
-
},
|
|
2974
|
-
slack: DEFAULT_BINDING
|
|
2975
|
-
};
|
|
2976
|
-
/** Compose both axes. Callers needing only one axis use the per-axis resolver. */
|
|
2977
|
-
function resolveBinding(ctx, role = "default") {
|
|
2978
|
-
ctx.trace ??= {};
|
|
2979
|
-
const sequence = resolveSequence(ctx);
|
|
2980
|
-
const { harness, model, thinkingLevel } = resolveHarness(ctx, role);
|
|
2981
|
-
return {
|
|
2982
|
-
sequence,
|
|
2983
|
-
harness,
|
|
2984
|
-
model,
|
|
2985
|
-
thinkingLevel
|
|
2986
|
-
};
|
|
2987
|
-
}
|
|
2988
|
-
/** A scan's two attempts on triage models: the resolved harness, then the SDK as a second provider. */
|
|
2989
|
-
function resolveScanBindings(ctx) {
|
|
2990
|
-
const scan = (harness) => ({
|
|
2991
|
-
sequence: "linear",
|
|
2992
|
-
harness,
|
|
2993
|
-
model: triageModelFor(harness)
|
|
2994
|
-
});
|
|
2995
|
-
return [scan(resolveBinding(ctx).harness), scan("anthropic")];
|
|
2996
|
-
}
|
|
2997
|
-
//#endregion
|
|
2998
|
-
//#region src/agent/runner/index.ts
|
|
2999
|
-
/**
|
|
3000
|
-
* Run a program's agent pipeline.
|
|
3001
|
-
*
|
|
3002
|
-
* Prepare → dispatch to the sequence the binding names → return its result
|
|
3003
|
-
* with the agent's own snapshot of what it reported. Missing observers change
|
|
3004
|
-
* nothing; a throwing observer is logged and the run continues.
|
|
3005
|
-
*/
|
|
3006
|
-
async function runAgent(config, input, options = {}) {
|
|
3007
|
-
let collector;
|
|
3008
|
-
let transcript;
|
|
3009
|
-
const snapshot = () => {
|
|
3010
|
-
try {
|
|
3011
|
-
if (collector) {
|
|
3012
|
-
const collected = collector.snapshot();
|
|
3013
|
-
return transcript ? {
|
|
3014
|
-
...collected,
|
|
3015
|
-
transcriptTail: transcript.text()
|
|
3016
|
-
} : collected;
|
|
3017
|
-
}
|
|
3018
|
-
} catch {}
|
|
3019
|
-
return {
|
|
3020
|
-
tasks: [],
|
|
3021
|
-
statusMessages: [],
|
|
3022
|
-
usage: {
|
|
3023
|
-
inputTokens: 0,
|
|
3024
|
-
outputTokens: 0,
|
|
3025
|
-
cacheReadTokens: 0,
|
|
3026
|
-
cacheCreationTokens: 0
|
|
3027
|
-
}
|
|
3028
|
-
};
|
|
3029
|
-
};
|
|
3030
|
-
const flushReport = () => {
|
|
3031
|
-
if (config.scanReport === "defer") return;
|
|
3032
|
-
try {
|
|
3033
|
-
const report = flushScanReport({ yaraReport: input.flags.yaraReport });
|
|
3034
|
-
if (report) collector?.emit({
|
|
3035
|
-
kind: "log",
|
|
3036
|
-
level: "info",
|
|
3037
|
-
message: report
|
|
3038
|
-
});
|
|
3039
|
-
} catch {}
|
|
3040
|
-
};
|
|
3041
|
-
let result;
|
|
3042
|
-
try {
|
|
3043
|
-
collector = createProgressCollector(options.onProgress);
|
|
3044
|
-
const { emit } = collector;
|
|
3045
|
-
if (config.run.collectTranscript) transcript = createTranscriptTail(emit);
|
|
3046
|
-
const log = (message) => emit({
|
|
3047
|
-
kind: "log",
|
|
3048
|
-
level: "info",
|
|
3049
|
-
message
|
|
3050
|
-
});
|
|
3051
|
-
if (options.signal?.aborted) {
|
|
3052
|
-
flushReport();
|
|
3053
|
-
return {
|
|
3054
|
-
outcome: "aborted",
|
|
3055
|
-
skillId: input.skillId,
|
|
3056
|
-
failure: {
|
|
3057
|
-
code: ErrorCodes.AgentAbort,
|
|
3058
|
-
message: "Agent run cancelled"
|
|
3059
|
-
},
|
|
3060
|
-
snapshot: snapshot()
|
|
3061
|
-
};
|
|
3062
|
-
}
|
|
3063
|
-
const boot = await prepareRun(config, input);
|
|
3064
|
-
if (config.binding.sequence === "orchestrator") log("Task-queue orchestrator enabled.");
|
|
3065
|
-
try {
|
|
3066
|
-
logToFile(`[agent-runner] run program=${config.programId} sequence=${config.binding.sequence} harness=${config.binding.harness} composed=${config.composed}`);
|
|
3067
|
-
} catch {}
|
|
3068
|
-
const sequenceResult = await getSequence(config.binding.sequence).run({
|
|
3069
|
-
config,
|
|
3070
|
-
input,
|
|
3071
|
-
boot,
|
|
3072
|
-
emit,
|
|
3073
|
-
interaction: options.interaction,
|
|
3074
|
-
signal: options.signal,
|
|
3075
|
-
transcript
|
|
3076
|
-
});
|
|
3077
|
-
result = {
|
|
3078
|
-
...options.signal?.aborted && sequenceResult.outcome === "success" ? {
|
|
3079
|
-
outcome: "aborted",
|
|
3080
|
-
failure: {
|
|
3081
|
-
code: ErrorCodes.AgentAbort,
|
|
3082
|
-
message: "Agent run cancelled"
|
|
3083
|
-
}
|
|
3084
|
-
} : sequenceResult,
|
|
3085
|
-
skillId: input.skillId,
|
|
3086
|
-
snapshot: snapshot()
|
|
3087
|
-
};
|
|
3088
|
-
} catch (error) {
|
|
3089
|
-
const original = error instanceof Error ? error : new Error(safeErrorMessage(error));
|
|
3090
|
-
let failure;
|
|
3091
|
-
try {
|
|
3092
|
-
failure = classifyRunFailure(original);
|
|
3093
|
-
} catch {
|
|
3094
|
-
failure = {
|
|
3095
|
-
code: ErrorCodes.InternalUnhandled,
|
|
3096
|
-
message: "Unexpected agent error",
|
|
3097
|
-
coded: false
|
|
3098
|
-
};
|
|
3099
|
-
}
|
|
3100
|
-
try {
|
|
3101
|
-
logToFile("[agent-runner] run failed:", original);
|
|
3102
|
-
} catch {}
|
|
3103
|
-
let abortError = false;
|
|
3104
|
-
try {
|
|
3105
|
-
abortError = original.name === "AbortError";
|
|
3106
|
-
} catch {}
|
|
3107
|
-
if (failure.coded) result = {
|
|
3108
|
-
outcome: "failed",
|
|
3109
|
-
skillId: input?.skillId,
|
|
3110
|
-
failure: {
|
|
3111
|
-
code: failure.code,
|
|
3112
|
-
message: failure.message,
|
|
3113
|
-
error: original
|
|
3114
|
-
},
|
|
3115
|
-
snapshot: snapshot()
|
|
3116
|
-
};
|
|
3117
|
-
else if (options.signal?.aborted && abortError) result = {
|
|
3118
|
-
outcome: "aborted",
|
|
3119
|
-
skillId: input?.skillId,
|
|
3120
|
-
failure: {
|
|
3121
|
-
code: ErrorCodes.AgentAbort,
|
|
3122
|
-
message: "Agent run cancelled"
|
|
3123
|
-
},
|
|
3124
|
-
snapshot: snapshot()
|
|
3125
|
-
};
|
|
3126
|
-
else result = {
|
|
3127
|
-
outcome: "crashed",
|
|
3128
|
-
skillId: input?.skillId,
|
|
3129
|
-
failure: {
|
|
3130
|
-
code: failure.code,
|
|
3131
|
-
message: failure.message,
|
|
3132
|
-
error: original
|
|
3133
|
-
},
|
|
3134
|
-
snapshot: snapshot()
|
|
3135
|
-
};
|
|
3136
|
-
}
|
|
3137
|
-
flushReport();
|
|
3138
|
-
return result;
|
|
3139
|
-
}
|
|
3140
|
-
function safeErrorMessage(error) {
|
|
3141
|
-
try {
|
|
3142
|
-
return String(error);
|
|
3143
|
-
} catch {
|
|
3144
|
-
return "Unknown thrown value";
|
|
3145
|
-
}
|
|
3146
|
-
}
|
|
3147
|
-
//#endregion
|
|
3148
|
-
//#region src/agent/runner/switchboard/resolve-run.ts
|
|
3149
|
-
/**
|
|
3150
|
-
* One event + one log line per run: what entered the switchboard, which
|
|
3151
|
-
* precedence rung decided each axis, and the final pick.
|
|
3152
|
-
*/
|
|
3153
|
-
function captureSwitchboardDecision(ctx, binding) {
|
|
3154
|
-
const trace = ctx.trace ?? {};
|
|
3155
|
-
const perTaskModel = binding.sequence === "orchestrator" && trace.model === "binding";
|
|
3156
|
-
const model = perTaskModel ? "chosen-per-task" : binding.model;
|
|
3157
|
-
const modelSource = perTaskModel ? "agent-prompts" : trace.model;
|
|
3158
|
-
analytics.wizardCapture("switchboard resolved", {
|
|
3159
|
-
program: ctx.program,
|
|
3160
|
-
flag_self_driving_use_pi_harness: ctx.flags[WIZARD_SELF_DRIVING_USE_PI_HARNESS_FLAG_KEY],
|
|
3161
|
-
flag_self_driving_pi_payload: JSON.stringify(ctx.flagPayloads?.["wizard-self-driving-use-pi-harness"] ?? null),
|
|
3162
|
-
flag_orchestrator: ctx.flags[WIZARD_ORCHESTRATOR_FLAG_KEY],
|
|
3163
|
-
cli_harness: ctx.cliHarness,
|
|
3164
|
-
cli_sequence: ctx.cliSequence,
|
|
3165
|
-
cli_model: ctx.cliModel,
|
|
3166
|
-
harness_source: trace.harness,
|
|
3167
|
-
model_source: modelSource,
|
|
3168
|
-
sequence_source: trace.sequence,
|
|
3169
|
-
harness: binding.harness,
|
|
3170
|
-
model,
|
|
3171
|
-
thinking_level: binding.thinkingLevel,
|
|
3172
|
-
sequence: binding.sequence
|
|
3173
|
-
});
|
|
3174
|
-
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 ?? "?"})`);
|
|
3175
|
-
}
|
|
3176
|
-
//#endregion
|
|
3177
|
-
//#region src/agent/index.ts
|
|
3178
|
-
/**
|
|
3179
|
-
* Leaves in C2. The TUI receives agent data through program state. Until
|
|
3180
|
-
* then the suggested-prompts screen streams through this wrapper, which loads
|
|
3181
|
-
* the streaming module on first call so the startup chunk does not grow.
|
|
3182
|
-
*/
|
|
3183
|
-
async function* runMcpPromptViaSdk(args) {
|
|
3184
|
-
yield* (await import("./mcp-prompt-streaming-ljCVoQ9I.js")).runMcpPromptViaSdk(args);
|
|
3185
|
-
}
|
|
3186
|
-
//#endregion
|
|
3187
|
-
export { resolveScanBindings as a, formatTokenCount as c, resolveBinding as i, captureSwitchboardDecision as n, computeTokenCostUsd as o, runAgent as r, formatCostUsd as s, runMcpPromptViaSdk as t };
|
|
3188
|
-
|
|
3189
|
-
//# sourceMappingURL=agent-BkBbVuNq.js.map
|