@tekmidian/pai 0.46.0 → 0.48.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chain-DAEC9AU7.mjs → chain-CNSa0A3G.mjs} +2 -2
- package/dist/{chain-DAEC9AU7.mjs.map → chain-CNSa0A3G.mjs.map} +1 -1
- package/dist/cli/index.mjs +4 -4
- package/dist/cli/program.mjs +4 -4
- package/dist/daemon/index.mjs +2 -2
- package/dist/{daemon-Dq2ZSdex.mjs → daemon-CYpVy_Dw.mjs} +2 -2
- package/dist/{daemon-DNBEFHaj.mjs → daemon-DZ4Q0jrs.mjs} +2 -2
- package/dist/{daemon-DNBEFHaj.mjs.map → daemon-DZ4Q0jrs.mjs.map} +1 -1
- package/dist/daemon-mcp/index.mjs +3 -3
- package/dist/{fallback-CxGDTXWt.mjs → fallback-IeR2FrFn.mjs} +6 -6
- package/dist/fallback-IeR2FrFn.mjs.map +1 -0
- package/dist/hooks/load-core-context.mjs +9 -11
- package/dist/hooks/load-core-context.mjs.map +3 -3
- package/dist/hooks/load-project-context.mjs +59 -6
- package/dist/hooks/load-project-context.mjs.map +3 -3
- package/dist/hooks/worker-status-line.mjs +23 -4
- package/dist/hooks/worker-status-line.mjs.map +3 -3
- package/dist/{planner-B4DvNpXd.mjs → planner-Cn8HmT7l.mjs} +3 -3
- package/dist/{planner-B4DvNpXd.mjs.map → planner-Cn8HmT7l.mjs.map} +1 -1
- package/dist/{program-Dk_EIMqC.mjs → program-Bgv1lFFA.mjs} +155 -16
- package/dist/program-Bgv1lFFA.mjs.map +1 -0
- package/dist/{run-BgExF8Xg.mjs → run-Cls0YGgV.mjs} +43 -6
- package/dist/run-Cls0YGgV.mjs.map +1 -0
- package/docs/commands/audit.md +1 -0
- package/package.json +1 -1
- package/src/hooks/ts/lib/frontmatter.test.ts +19 -0
- package/src/hooks/ts/lib/frontmatter.ts +10 -0
- package/src/hooks/ts/lib/handover-budget.test.ts +124 -0
- package/src/hooks/ts/lib/handover-budget.ts +94 -0
- package/src/hooks/ts/session-start/load-core-context.ts +6 -15
- package/src/hooks/ts/session-start/load-project-context.ts +9 -6
- package/dist/fallback-CxGDTXWt.mjs.map +0 -1
- package/dist/program-Dk_EIMqC.mjs.map +0 -1
- package/dist/run-BgExF8Xg.mjs.map +0 -1
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import "./db-Ddns80yf.mjs";
|
|
2
2
|
import "./config-DCu8THKi.mjs";
|
|
3
3
|
import { v as readWorkersSection } from "./config-DxMZjpvY.mjs";
|
|
4
|
-
import { M as
|
|
4
|
+
import { B as parseRunnerArgs, M as loadStatuses, N as newWorkerId, S as appendLedger, V as shortText, n as runWorker, t as printResult } from "./run-Cls0YGgV.mjs";
|
|
5
5
|
import { _ as workersLogDir, m as ledgerPath } from "./server-Ch99xt5K.mjs";
|
|
6
|
-
import { n as swapPromptArg } from "./chain-
|
|
6
|
+
import { n as swapPromptArg } from "./chain-CNSa0A3G.mjs";
|
|
7
7
|
import { existsSync, mkdirSync, readFileSync } from "node:fs";
|
|
8
8
|
import { join } from "node:path";
|
|
9
9
|
|
|
@@ -244,4 +244,4 @@ function statusesOfChildren(logDir, plannerId) {
|
|
|
244
244
|
|
|
245
245
|
//#endregion
|
|
246
246
|
export { runPlanner };
|
|
247
|
-
//# sourceMappingURL=planner-
|
|
247
|
+
//# sourceMappingURL=planner-Cn8HmT7l.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"planner-B4DvNpXd.mjs","names":[],"sources":["../src/workers/planner.ts"],"sourcesContent":["/**\n * planner.ts — the plan class: plan, spawn, integrate.\n *\n * `pai worker run --class plan -p \"<goal>\"` does not run one worker but a\n * small orchestration:\n *\n * 1. a planner worker (the plan class's provider) reads the repository and\n * writes `<logDir>/plans/<planner id>.json` — 5–50 sub-tasks, fewer only\n * when the goal itself names a smaller count, each with title, brief,\n * class, files and acceptance;\n * 2. the runner validates the plan and spawns the sub-tasks as children of\n * the planner worker (`parent` = the planner id, so `ps` shows the\n * tree), at most `workers.tree.maxChildren` at a time;\n * 3. each child that finishes delivers its structured report to the\n * planner's inbox as a `kind: \"result\"` handoff (see handoff.ts);\n * 4. the run finishes with a summary report and the branches to merge.\n *\n * The planner's prompt carries the prompt rules from the self-driving\n * codebases write-up: domain-specific instructions only, constraints over\n * step lists, explicit quantity ranges, no checkbox style.\n */\n\nimport { existsSync, mkdirSync, readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { readWorkersSection } from \"./config.js\";\nimport { appendLedger } from \"./ledger.js\";\nimport { ledgerPath, workersLogDir } from \"./paths.js\";\nimport { loadStatuses, newWorkerId, type WorkerStatus } from \"./status.js\";\nimport { parseRunnerArgs, shortText } from \"./args.js\";\nimport { swapPromptArg } from \"./chain.js\";\nimport { runWorker, printResult, type RunOptions, type StreamEvent } from \"./run.js\";\nimport type { WorkerReport } from \"./report.js\";\n\n/** Where a planner run's plan file lives: <logDir>/plans/<planner id>.json. */\nexport function planPathFor(logDir: string, plannerId: string): string {\n return join(logDir, \"plans\", `${plannerId}.json`);\n}\n\nexport interface PlannerTask {\n title: string;\n brief: string;\n /** Task class of the child (implement, research, …); default implement. */\n class?: string;\n /** Files the sub-task likely touches. */\n files?: string[];\n /** How to tell the sub-task is done. */\n acceptance?: string[];\n}\n\nexport const MIN_TASKS = 5;\nexport const MAX_TASKS = 50;\n\n/**\n * Validate a raw plan file into its tasks. Accepts 1–MAX_TASKS tasks: the\n * prompt asks for MIN_TASKS–MAX_TASKS, but a goal may itself name a smaller\n * explicit count (\"one sub-task per file\" with three files), and that\n * explicit range wins. Everything else — missing file, damaged JSON, tasks\n * without a title or brief — throws with a message the operator can act on.\n */\nexport function validatePlan(raw: unknown): PlannerTask[] {\n if (typeof raw !== \"object\" || raw === null || Array.isArray(raw)) {\n throw new Error(\"plan file must be a JSON object\");\n }\n const tasksRaw = (raw as Record<string, unknown>).tasks;\n if (!Array.isArray(tasksRaw)) throw new Error('plan file needs a \"tasks\" array');\n if (tasksRaw.length < 1) throw new Error(\"plan file has no tasks\");\n if (tasksRaw.length > MAX_TASKS) {\n throw new Error(`plan file has ${tasksRaw.length} tasks; the maximum is ${MAX_TASKS}`);\n }\n const tasks: PlannerTask[] = [];\n for (let i = 0; i < tasksRaw.length; i++) {\n const t = tasksRaw[i];\n if (typeof t !== \"object\" || t === null || Array.isArray(t)) {\n throw new Error(`task ${i + 1} must be an object`);\n }\n const o = t as Record<string, unknown>;\n const title = typeof o.title === \"string\" ? o.title.trim() : \"\";\n const brief = typeof o.brief === \"string\" ? o.brief.trim() : \"\";\n if (!title) throw new Error(`task ${i + 1} needs a non-empty \"title\"`);\n if (!brief) throw new Error(`task ${i + 1} needs a non-empty \"brief\"`);\n const strArr = (v: unknown): string[] | undefined =>\n Array.isArray(v) && v.every((x) => typeof x === \"string\") ? (v as string[]) : undefined;\n const files = strArr(o.files);\n const acceptance = strArr(o.acceptance);\n tasks.push({\n title,\n brief,\n ...(typeof o.class === \"string\" && o.class.trim() ? { class: o.class.trim() } : {}),\n ...(files?.length ? { files } : {}),\n ...(acceptance?.length ? { acceptance } : {}),\n });\n }\n return tasks;\n}\n\n/** The planner worker's instructions, goal and prompt rules included. */\nexport function plannerPrompt(goal: string, planFile: string, maxChildren: number): string {\n return [\n \"You are a PLANNER worker. Turn the goal below into a plan file and stop —\",\n \"you do not implement anything, and you do not spawn workers yourself:\",\n \"the runner executes your plan on your behalf (the sub-tasks run as your\",\n \"children and report back to you).\",\n \"\",\n `Write the plan with the Write tool to ${planFile} as one JSON object:`,\n '{\"tasks\":[{\"title\":\"…\",\"brief\":\"…\",\"class\":\"implement\",\"files\":[\"…\"],\"acceptance\":[\"…\"]}]}',\n \"\",\n \"Prompt rules for the plan itself (they are yours too):\",\n \"- Domain-specific instructions only: name real files, commands and\",\n \" constraints of this repository, never generic advice.\",\n \"- Constraints over step lists: state what must hold (and what must not\",\n \" change), not a numbered procedure to follow.\",\n \"- Explicit quantity ranges: how many, how much, how long — never\",\n ' \"several\", \"some\", \"as needed\".',\n \"- No checkbox style: no `[ ]` items, no step numbering theatre.\",\n \"\",\n `Emit between ${MIN_TASKS} and ${MAX_TASKS} sub-tasks — unless the goal itself`,\n 'names a smaller explicit count (\"one sub-task per file\" with three files',\n \"means three); an explicit count in the goal always wins.\",\n `At most ${maxChildren} of them run at a time, so independent tasks are better`,\n \"than long chains. Each task's class picks its provider: implement, draft,\",\n \"review, research, spotcheck, simple, complex, image.\",\n \"\",\n \"Read the repository first (Glob/Grep/Read) so tasks name real files.\",\n \"Every sub-task runs in its own worktree of the same repository: name files\",\n \"relative to the repository root and never an absolute path or a worktree\",\n \"directory in a title, brief, files or acceptance entry.\",\n \"\",\n \"## Goal\",\n \"\",\n goal,\n ].join(\"\\n\");\n}\n\n/** The child's prompt for one planned sub-task. */\nexport function taskPrompt(goal: string, task: PlannerTask, index: number, of: number): string {\n return [\n `You are sub-task ${index + 1} of ${of} of a planned goal. Do exactly this sub-task;`,\n \"the other sub-tasks are other workers' business.\",\n \"\",\n `# ${task.title}`,\n \"\",\n task.brief,\n ...(task.files?.length ? [\"\", \"Files likely touched: \" + task.files.join(\", \")] : []),\n ...(task.acceptance?.length\n ? [\"\", \"Done means:\", ...task.acceptance.map((a) => `- ${a}`)]\n : []),\n \"\",\n \"## The overall goal (context only — your scope is the sub-task above)\",\n \"\",\n goal,\n ].join(\"\\n\");\n}\n\nexport interface PlannerDeps {\n /** Stage runner; tests inject a mock, production uses runWorker. */\n runStage?: (opts: RunOptions) => Promise<number>;\n /** logDir override for tests. */\n logDir?: string;\n /** maxChildren override for tests. */\n maxChildren?: number;\n}\n\n/**\n * The planner orchestration; returns the process exit code. The planner id is\n * minted here and preset on the phase-1 run, so the plan file's name is known\n * before the worker starts and the prompt can name its exact path.\n */\nexport async function runPlanner(opts: RunOptions, deps: PlannerDeps = {}): Promise<number> {\n const runStage = deps.runStage ?? runWorker;\n const { workers: config } = readWorkersSection();\n const logDir = deps.logDir ?? workersLogDir(config);\n const maxChildren = deps.maxChildren ?? config.tree.maxChildren;\n const parsed = parseRunnerArgs(opts.claudeArgs);\n const goal = parsed.prompt ?? \"\";\n const plannerId = newWorkerId();\n const planFile = planPathFor(logDir, plannerId);\n mkdirSync(join(logDir, \"plans\"), { recursive: true });\n\n // --- phase 1: the planner worker writes the plan file\n process.stderr.write(`planner ${plannerId}: writing plan (${planFile})\\n`);\n const rc1 = await runStage({\n ...opts,\n id: plannerId,\n claudeArgs: swapPromptArg(opts.claudeArgs, plannerPrompt(goal, planFile, maxChildren)),\n quiet: true,\n _planner: true,\n });\n if (rc1 !== 0) return rc1;\n if (!existsSync(planFile)) {\n process.stderr.write(\n `planner ${plannerId}: no plan file at ${planFile} — the planner worker did not write one. ` +\n `Re-run, or write the plan yourself and run the tasks with pai worker run.\\n`\n );\n appendLedger(ledgerPath(logDir), \"WORKER-PLAN-END\", { planner: plannerId, rc: 1, failed: \"plan\" });\n return 1;\n }\n let tasks: PlannerTask[];\n try {\n tasks = validatePlan(JSON.parse(readFileSync(planFile, \"utf8\")));\n } catch (e) {\n process.stderr.write(`planner ${plannerId}: invalid plan file ${planFile}: ${(e as Error).message}\\n`);\n appendLedger(ledgerPath(logDir), \"WORKER-PLAN-END\", { planner: plannerId, rc: 1, failed: \"plan\" });\n return 1;\n }\n appendLedger(ledgerPath(logDir), \"WORKER-PLAN\", {\n planner: plannerId,\n tasks: tasks.length,\n plan: planFile,\n });\n\n // --- phase 2: run the sub-tasks as children, maxChildren at a time\n const results: { task: PlannerTask; rc: number }[] = [];\n for (let i = 0; i < tasks.length; i += maxChildren) {\n const wave = tasks.slice(i, i + maxChildren);\n process.stderr.write(\n `planner ${plannerId}: sub-tasks ${i + 1}–${i + wave.length} of ${tasks.length}\\n`\n );\n const rcs = await Promise.all(\n wave.map((task, w) =>\n runStage({\n className: task.class ?? \"implement\",\n providerFlag: opts.providerFlag,\n modelFlag: opts.modelFlag,\n label: shortText(task.title, 40),\n noPane: opts.noPane,\n mcpFlag: opts.mcpFlag,\n claudeArgs: swapPromptArg(opts.claudeArgs, taskPrompt(goal, task, i + w, tasks.length)),\n cwd: opts.cwd,\n worktreeFlag: opts.worktreeFlag,\n parent: plannerId,\n quiet: true,\n })\n )\n );\n for (let w = 0; w < wave.length; w++) results.push({ task: wave[w], rc: rcs[w] });\n }\n\n // --- phase 3: summary from the children's statuses and inbox handoffs\n const failed = results.filter((r) => r.rc !== 0).length;\n appendLedger(ledgerPath(logDir), \"WORKER-PLAN-END\", {\n planner: plannerId,\n rc: failed ? 1 : 0,\n failed,\n });\n printPlannerSummary(logDir, plannerId, results, parsed.outputFormat, opts.quiet === true);\n return failed ? 1 : 0;\n}\n\n/** Compose and print the planner's summary report (text or json). */\nfunction printPlannerSummary(\n logDir: string,\n plannerId: string,\n results: { task: PlannerTask; rc: number }[],\n fmt: \"text\" | \"json\" | \"stream-json\",\n quiet: boolean\n): void {\n const children = statusesOfChildren(logDir, plannerId);\n const ok = results.filter((r) => r.rc === 0).length;\n const unmerged = children.filter((c) => c.branch && !c.merged);\n const report: WorkerReport = {\n checks: results.map((r, i) => ({\n name: r.task.title,\n ok: r.rc === 0,\n detail: r.rc === 0 ? shortText(children[i]?.last, 80) : `rc=${r.rc}`,\n })),\n ...(unmerged.length\n ? { open: unmerged.map((c) => `branch to merge: pai worker merge ${c.id} (${c.branch})`) }\n : {}),\n notes: `${ok}/${results.length} sub-tasks ok${unmerged.length ? `; ${unmerged.length} branch(es) to merge` : \"\"}`,\n };\n if (quiet || fmt === \"stream-json\") return;\n const resultEvent: StreamEvent = {\n type: \"result\",\n result: [\n `planner ${plannerId}: ${report.notes}`,\n ...(unmerged.length ? [\"branches to merge:\"] : []),\n ...unmerged.map((c) => ` pai worker merge ${c.id} # ${c.branch}`),\n ].join(\"\\n\"),\n is_error: ok !== results.length,\n };\n printResult(fmt, resultEvent, ok === results.length ? 0 : 1, logDir, plannerId, report, {\n plan: results.length,\n ...(unmerged.length ? { branches: unmerged.map((c) => c.branch!) } : {}),\n });\n}\n\n/** Children of the planner, oldest first, from the status files. */\nfunction statusesOfChildren(logDir: string, plannerId: string): WorkerStatus[] {\n return loadStatuses(logDir).filter((s) => s.parent === plannerId);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkCA,SAAgB,YAAY,QAAgB,WAA2B;AACrE,QAAO,KAAK,QAAQ,SAAS,GAAG,UAAU,OAAO;;AAcnD,MAAa,YAAY;AACzB,MAAa,YAAY;;;;;;;;AASzB,SAAgB,aAAa,KAA6B;AACxD,KAAI,OAAO,QAAQ,YAAY,QAAQ,QAAQ,MAAM,QAAQ,IAAI,CAC/D,OAAM,IAAI,MAAM,kCAAkC;CAEpD,MAAM,WAAY,IAAgC;AAClD,KAAI,CAAC,MAAM,QAAQ,SAAS,CAAE,OAAM,IAAI,MAAM,oCAAkC;AAChF,KAAI,SAAS,SAAS,EAAG,OAAM,IAAI,MAAM,yBAAyB;AAClE,KAAI,SAAS,SAAS,UACpB,OAAM,IAAI,MAAM,iBAAiB,SAAS,OAAO,yBAAyB,YAAY;CAExF,MAAM,QAAuB,EAAE;AAC/B,MAAK,IAAI,IAAI,GAAG,IAAI,SAAS,QAAQ,KAAK;EACxC,MAAM,IAAI,SAAS;AACnB,MAAI,OAAO,MAAM,YAAY,MAAM,QAAQ,MAAM,QAAQ,EAAE,CACzD,OAAM,IAAI,MAAM,QAAQ,IAAI,EAAE,oBAAoB;EAEpD,MAAM,IAAI;EACV,MAAM,QAAQ,OAAO,EAAE,UAAU,WAAW,EAAE,MAAM,MAAM,GAAG;EAC7D,MAAM,QAAQ,OAAO,EAAE,UAAU,WAAW,EAAE,MAAM,MAAM,GAAG;AAC7D,MAAI,CAAC,MAAO,OAAM,IAAI,MAAM,QAAQ,IAAI,EAAE,4BAA4B;AACtE,MAAI,CAAC,MAAO,OAAM,IAAI,MAAM,QAAQ,IAAI,EAAE,4BAA4B;EACtE,MAAM,UAAU,MACd,MAAM,QAAQ,EAAE,IAAI,EAAE,OAAO,MAAM,OAAO,MAAM,SAAS,GAAI,IAAiB;EAChF,MAAM,QAAQ,OAAO,EAAE,MAAM;EAC7B,MAAM,aAAa,OAAO,EAAE,WAAW;AACvC,QAAM,KAAK;GACT;GACA;GACA,GAAI,OAAO,EAAE,UAAU,YAAY,EAAE,MAAM,MAAM,GAAG,EAAE,OAAO,EAAE,MAAM,MAAM,EAAE,GAAG,EAAE;GAClF,GAAI,OAAO,SAAS,EAAE,OAAO,GAAG,EAAE;GAClC,GAAI,YAAY,SAAS,EAAE,YAAY,GAAG,EAAE;GAC7C,CAAC;;AAEJ,QAAO;;;AAIT,SAAgB,cAAc,MAAc,UAAkB,aAA6B;AACzF,QAAO;EACL;EACA;EACA;EACA;EACA;EACA,yCAAyC,SAAS;EAClD;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,gBAAgB,UAAU,OAAO,UAAU;EAC3C;EACA;EACA,WAAW,YAAY;EACvB;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACD,CAAC,KAAK,KAAK;;;AAId,SAAgB,WAAW,MAAc,MAAmB,OAAe,IAAoB;AAC7F,QAAO;EACL,oBAAoB,QAAQ,EAAE,MAAM,GAAG;EACvC;EACA;EACA,KAAK,KAAK;EACV;EACA,KAAK;EACL,GAAI,KAAK,OAAO,SAAS,CAAC,IAAI,2BAA2B,KAAK,MAAM,KAAK,KAAK,CAAC,GAAG,EAAE;EACpF,GAAI,KAAK,YAAY,SACjB;GAAC;GAAI;GAAe,GAAG,KAAK,WAAW,KAAK,MAAM,KAAK,IAAI;GAAC,GAC5D,EAAE;EACN;EACA;EACA;EACA;EACD,CAAC,KAAK,KAAK;;;;;;;AAiBd,eAAsB,WAAW,MAAkB,OAAoB,EAAE,EAAmB;CAC1F,MAAM,WAAW,KAAK,YAAY;CAClC,MAAM,EAAE,SAAS,WAAW,oBAAoB;CAChD,MAAM,SAAS,KAAK,UAAU,cAAc,OAAO;CACnD,MAAM,cAAc,KAAK,eAAe,OAAO,KAAK;CACpD,MAAM,SAAS,gBAAgB,KAAK,WAAW;CAC/C,MAAM,OAAO,OAAO,UAAU;CAC9B,MAAM,YAAY,aAAa;CAC/B,MAAM,WAAW,YAAY,QAAQ,UAAU;AAC/C,WAAU,KAAK,QAAQ,QAAQ,EAAE,EAAE,WAAW,MAAM,CAAC;AAGrD,SAAQ,OAAO,MAAM,WAAW,UAAU,kBAAkB,SAAS,KAAK;CAC1E,MAAM,MAAM,MAAM,SAAS;EACzB,GAAG;EACH,IAAI;EACJ,YAAY,cAAc,KAAK,YAAY,cAAc,MAAM,UAAU,YAAY,CAAC;EACtF,OAAO;EACP,UAAU;EACX,CAAC;AACF,KAAI,QAAQ,EAAG,QAAO;AACtB,KAAI,CAAC,WAAW,SAAS,EAAE;AACzB,UAAQ,OAAO,MACb,WAAW,UAAU,oBAAoB,SAAS,sHAEnD;AACD,eAAa,WAAW,OAAO,EAAE,mBAAmB;GAAE,SAAS;GAAW,IAAI;GAAG,QAAQ;GAAQ,CAAC;AAClG,SAAO;;CAET,IAAI;AACJ,KAAI;AACF,UAAQ,aAAa,KAAK,MAAM,aAAa,UAAU,OAAO,CAAC,CAAC;UACzD,GAAG;AACV,UAAQ,OAAO,MAAM,WAAW,UAAU,sBAAsB,SAAS,IAAK,EAAY,QAAQ,IAAI;AACtG,eAAa,WAAW,OAAO,EAAE,mBAAmB;GAAE,SAAS;GAAW,IAAI;GAAG,QAAQ;GAAQ,CAAC;AAClG,SAAO;;AAET,cAAa,WAAW,OAAO,EAAE,eAAe;EAC9C,SAAS;EACT,OAAO,MAAM;EACb,MAAM;EACP,CAAC;CAGF,MAAM,UAA+C,EAAE;AACvD,MAAK,IAAI,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK,aAAa;EAClD,MAAM,OAAO,MAAM,MAAM,GAAG,IAAI,YAAY;AAC5C,UAAQ,OAAO,MACb,WAAW,UAAU,cAAc,IAAI,EAAE,GAAG,IAAI,KAAK,OAAO,MAAM,MAAM,OAAO,IAChF;EACD,MAAM,MAAM,MAAM,QAAQ,IACxB,KAAK,KAAK,MAAM,MACd,SAAS;GACP,WAAW,KAAK,SAAS;GACzB,cAAc,KAAK;GACnB,WAAW,KAAK;GAChB,OAAO,UAAU,KAAK,OAAO,GAAG;GAChC,QAAQ,KAAK;GACb,SAAS,KAAK;GACd,YAAY,cAAc,KAAK,YAAY,WAAW,MAAM,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC;GACvF,KAAK,KAAK;GACV,cAAc,KAAK;GACnB,QAAQ;GACR,OAAO;GACR,CAAC,CACH,CACF;AACD,OAAK,IAAI,IAAI,GAAG,IAAI,KAAK,QAAQ,IAAK,SAAQ,KAAK;GAAE,MAAM,KAAK;GAAI,IAAI,IAAI;GAAI,CAAC;;CAInF,MAAM,SAAS,QAAQ,QAAQ,MAAM,EAAE,OAAO,EAAE,CAAC;AACjD,cAAa,WAAW,OAAO,EAAE,mBAAmB;EAClD,SAAS;EACT,IAAI,SAAS,IAAI;EACjB;EACD,CAAC;AACF,qBAAoB,QAAQ,WAAW,SAAS,OAAO,cAAc,KAAK,UAAU,KAAK;AACzF,QAAO,SAAS,IAAI;;;AAItB,SAAS,oBACP,QACA,WACA,SACA,KACA,OACM;CACN,MAAM,WAAW,mBAAmB,QAAQ,UAAU;CACtD,MAAM,KAAK,QAAQ,QAAQ,MAAM,EAAE,OAAO,EAAE,CAAC;CAC7C,MAAM,WAAW,SAAS,QAAQ,MAAM,EAAE,UAAU,CAAC,EAAE,OAAO;CAC9D,MAAM,SAAuB;EAC3B,QAAQ,QAAQ,KAAK,GAAG,OAAO;GAC7B,MAAM,EAAE,KAAK;GACb,IAAI,EAAE,OAAO;GACb,QAAQ,EAAE,OAAO,IAAI,UAAU,SAAS,IAAI,MAAM,GAAG,GAAG,MAAM,EAAE;GACjE,EAAE;EACH,GAAI,SAAS,SACT,EAAE,MAAM,SAAS,KAAK,MAAM,qCAAqC,EAAE,GAAG,IAAI,EAAE,OAAO,GAAG,EAAE,GACxF,EAAE;EACN,OAAO,GAAG,GAAG,GAAG,QAAQ,OAAO,eAAe,SAAS,SAAS,KAAK,SAAS,OAAO,wBAAwB;EAC9G;AACD,KAAI,SAAS,QAAQ,cAAe;AAUpC,aAAY,KATqB;EAC/B,MAAM;EACN,QAAQ;GACN,WAAW,UAAU,IAAI,OAAO;GAChC,GAAI,SAAS,SAAS,CAAC,qBAAqB,GAAG,EAAE;GACjD,GAAG,SAAS,KAAK,MAAM,sBAAsB,EAAE,GAAG,OAAO,EAAE,SAAS;GACrE,CAAC,KAAK,KAAK;EACZ,UAAU,OAAO,QAAQ;EAC1B,EAC6B,OAAO,QAAQ,SAAS,IAAI,GAAG,QAAQ,WAAW,QAAQ;EACtF,MAAM,QAAQ;EACd,GAAI,SAAS,SAAS,EAAE,UAAU,SAAS,KAAK,MAAM,EAAE,OAAQ,EAAE,GAAG,EAAE;EACxE,CAAC;;;AAIJ,SAAS,mBAAmB,QAAgB,WAAmC;AAC7E,QAAO,aAAa,OAAO,CAAC,QAAQ,MAAM,EAAE,WAAW,UAAU"}
|
|
1
|
+
{"version":3,"file":"planner-Cn8HmT7l.mjs","names":[],"sources":["../src/workers/planner.ts"],"sourcesContent":["/**\n * planner.ts — the plan class: plan, spawn, integrate.\n *\n * `pai worker run --class plan -p \"<goal>\"` does not run one worker but a\n * small orchestration:\n *\n * 1. a planner worker (the plan class's provider) reads the repository and\n * writes `<logDir>/plans/<planner id>.json` — 5–50 sub-tasks, fewer only\n * when the goal itself names a smaller count, each with title, brief,\n * class, files and acceptance;\n * 2. the runner validates the plan and spawns the sub-tasks as children of\n * the planner worker (`parent` = the planner id, so `ps` shows the\n * tree), at most `workers.tree.maxChildren` at a time;\n * 3. each child that finishes delivers its structured report to the\n * planner's inbox as a `kind: \"result\"` handoff (see handoff.ts);\n * 4. the run finishes with a summary report and the branches to merge.\n *\n * The planner's prompt carries the prompt rules from the self-driving\n * codebases write-up: domain-specific instructions only, constraints over\n * step lists, explicit quantity ranges, no checkbox style.\n */\n\nimport { existsSync, mkdirSync, readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { readWorkersSection } from \"./config.js\";\nimport { appendLedger } from \"./ledger.js\";\nimport { ledgerPath, workersLogDir } from \"./paths.js\";\nimport { loadStatuses, newWorkerId, type WorkerStatus } from \"./status.js\";\nimport { parseRunnerArgs, shortText } from \"./args.js\";\nimport { swapPromptArg } from \"./chain.js\";\nimport { runWorker, printResult, type RunOptions, type StreamEvent } from \"./run.js\";\nimport type { WorkerReport } from \"./report.js\";\n\n/** Where a planner run's plan file lives: <logDir>/plans/<planner id>.json. */\nexport function planPathFor(logDir: string, plannerId: string): string {\n return join(logDir, \"plans\", `${plannerId}.json`);\n}\n\nexport interface PlannerTask {\n title: string;\n brief: string;\n /** Task class of the child (implement, research, …); default implement. */\n class?: string;\n /** Files the sub-task likely touches. */\n files?: string[];\n /** How to tell the sub-task is done. */\n acceptance?: string[];\n}\n\nexport const MIN_TASKS = 5;\nexport const MAX_TASKS = 50;\n\n/**\n * Validate a raw plan file into its tasks. Accepts 1–MAX_TASKS tasks: the\n * prompt asks for MIN_TASKS–MAX_TASKS, but a goal may itself name a smaller\n * explicit count (\"one sub-task per file\" with three files), and that\n * explicit range wins. Everything else — missing file, damaged JSON, tasks\n * without a title or brief — throws with a message the operator can act on.\n */\nexport function validatePlan(raw: unknown): PlannerTask[] {\n if (typeof raw !== \"object\" || raw === null || Array.isArray(raw)) {\n throw new Error(\"plan file must be a JSON object\");\n }\n const tasksRaw = (raw as Record<string, unknown>).tasks;\n if (!Array.isArray(tasksRaw)) throw new Error('plan file needs a \"tasks\" array');\n if (tasksRaw.length < 1) throw new Error(\"plan file has no tasks\");\n if (tasksRaw.length > MAX_TASKS) {\n throw new Error(`plan file has ${tasksRaw.length} tasks; the maximum is ${MAX_TASKS}`);\n }\n const tasks: PlannerTask[] = [];\n for (let i = 0; i < tasksRaw.length; i++) {\n const t = tasksRaw[i];\n if (typeof t !== \"object\" || t === null || Array.isArray(t)) {\n throw new Error(`task ${i + 1} must be an object`);\n }\n const o = t as Record<string, unknown>;\n const title = typeof o.title === \"string\" ? o.title.trim() : \"\";\n const brief = typeof o.brief === \"string\" ? o.brief.trim() : \"\";\n if (!title) throw new Error(`task ${i + 1} needs a non-empty \"title\"`);\n if (!brief) throw new Error(`task ${i + 1} needs a non-empty \"brief\"`);\n const strArr = (v: unknown): string[] | undefined =>\n Array.isArray(v) && v.every((x) => typeof x === \"string\") ? (v as string[]) : undefined;\n const files = strArr(o.files);\n const acceptance = strArr(o.acceptance);\n tasks.push({\n title,\n brief,\n ...(typeof o.class === \"string\" && o.class.trim() ? { class: o.class.trim() } : {}),\n ...(files?.length ? { files } : {}),\n ...(acceptance?.length ? { acceptance } : {}),\n });\n }\n return tasks;\n}\n\n/** The planner worker's instructions, goal and prompt rules included. */\nexport function plannerPrompt(goal: string, planFile: string, maxChildren: number): string {\n return [\n \"You are a PLANNER worker. Turn the goal below into a plan file and stop —\",\n \"you do not implement anything, and you do not spawn workers yourself:\",\n \"the runner executes your plan on your behalf (the sub-tasks run as your\",\n \"children and report back to you).\",\n \"\",\n `Write the plan with the Write tool to ${planFile} as one JSON object:`,\n '{\"tasks\":[{\"title\":\"…\",\"brief\":\"…\",\"class\":\"implement\",\"files\":[\"…\"],\"acceptance\":[\"…\"]}]}',\n \"\",\n \"Prompt rules for the plan itself (they are yours too):\",\n \"- Domain-specific instructions only: name real files, commands and\",\n \" constraints of this repository, never generic advice.\",\n \"- Constraints over step lists: state what must hold (and what must not\",\n \" change), not a numbered procedure to follow.\",\n \"- Explicit quantity ranges: how many, how much, how long — never\",\n ' \"several\", \"some\", \"as needed\".',\n \"- No checkbox style: no `[ ]` items, no step numbering theatre.\",\n \"\",\n `Emit between ${MIN_TASKS} and ${MAX_TASKS} sub-tasks — unless the goal itself`,\n 'names a smaller explicit count (\"one sub-task per file\" with three files',\n \"means three); an explicit count in the goal always wins.\",\n `At most ${maxChildren} of them run at a time, so independent tasks are better`,\n \"than long chains. Each task's class picks its provider: implement, draft,\",\n \"review, research, spotcheck, simple, complex, image.\",\n \"\",\n \"Read the repository first (Glob/Grep/Read) so tasks name real files.\",\n \"Every sub-task runs in its own worktree of the same repository: name files\",\n \"relative to the repository root and never an absolute path or a worktree\",\n \"directory in a title, brief, files or acceptance entry.\",\n \"\",\n \"## Goal\",\n \"\",\n goal,\n ].join(\"\\n\");\n}\n\n/** The child's prompt for one planned sub-task. */\nexport function taskPrompt(goal: string, task: PlannerTask, index: number, of: number): string {\n return [\n `You are sub-task ${index + 1} of ${of} of a planned goal. Do exactly this sub-task;`,\n \"the other sub-tasks are other workers' business.\",\n \"\",\n `# ${task.title}`,\n \"\",\n task.brief,\n ...(task.files?.length ? [\"\", \"Files likely touched: \" + task.files.join(\", \")] : []),\n ...(task.acceptance?.length\n ? [\"\", \"Done means:\", ...task.acceptance.map((a) => `- ${a}`)]\n : []),\n \"\",\n \"## The overall goal (context only — your scope is the sub-task above)\",\n \"\",\n goal,\n ].join(\"\\n\");\n}\n\nexport interface PlannerDeps {\n /** Stage runner; tests inject a mock, production uses runWorker. */\n runStage?: (opts: RunOptions) => Promise<number>;\n /** logDir override for tests. */\n logDir?: string;\n /** maxChildren override for tests. */\n maxChildren?: number;\n}\n\n/**\n * The planner orchestration; returns the process exit code. The planner id is\n * minted here and preset on the phase-1 run, so the plan file's name is known\n * before the worker starts and the prompt can name its exact path.\n */\nexport async function runPlanner(opts: RunOptions, deps: PlannerDeps = {}): Promise<number> {\n const runStage = deps.runStage ?? runWorker;\n const { workers: config } = readWorkersSection();\n const logDir = deps.logDir ?? workersLogDir(config);\n const maxChildren = deps.maxChildren ?? config.tree.maxChildren;\n const parsed = parseRunnerArgs(opts.claudeArgs);\n const goal = parsed.prompt ?? \"\";\n const plannerId = newWorkerId();\n const planFile = planPathFor(logDir, plannerId);\n mkdirSync(join(logDir, \"plans\"), { recursive: true });\n\n // --- phase 1: the planner worker writes the plan file\n process.stderr.write(`planner ${plannerId}: writing plan (${planFile})\\n`);\n const rc1 = await runStage({\n ...opts,\n id: plannerId,\n claudeArgs: swapPromptArg(opts.claudeArgs, plannerPrompt(goal, planFile, maxChildren)),\n quiet: true,\n _planner: true,\n });\n if (rc1 !== 0) return rc1;\n if (!existsSync(planFile)) {\n process.stderr.write(\n `planner ${plannerId}: no plan file at ${planFile} — the planner worker did not write one. ` +\n `Re-run, or write the plan yourself and run the tasks with pai worker run.\\n`\n );\n appendLedger(ledgerPath(logDir), \"WORKER-PLAN-END\", { planner: plannerId, rc: 1, failed: \"plan\" });\n return 1;\n }\n let tasks: PlannerTask[];\n try {\n tasks = validatePlan(JSON.parse(readFileSync(planFile, \"utf8\")));\n } catch (e) {\n process.stderr.write(`planner ${plannerId}: invalid plan file ${planFile}: ${(e as Error).message}\\n`);\n appendLedger(ledgerPath(logDir), \"WORKER-PLAN-END\", { planner: plannerId, rc: 1, failed: \"plan\" });\n return 1;\n }\n appendLedger(ledgerPath(logDir), \"WORKER-PLAN\", {\n planner: plannerId,\n tasks: tasks.length,\n plan: planFile,\n });\n\n // --- phase 2: run the sub-tasks as children, maxChildren at a time\n const results: { task: PlannerTask; rc: number }[] = [];\n for (let i = 0; i < tasks.length; i += maxChildren) {\n const wave = tasks.slice(i, i + maxChildren);\n process.stderr.write(\n `planner ${plannerId}: sub-tasks ${i + 1}–${i + wave.length} of ${tasks.length}\\n`\n );\n const rcs = await Promise.all(\n wave.map((task, w) =>\n runStage({\n className: task.class ?? \"implement\",\n providerFlag: opts.providerFlag,\n modelFlag: opts.modelFlag,\n label: shortText(task.title, 40),\n noPane: opts.noPane,\n mcpFlag: opts.mcpFlag,\n claudeArgs: swapPromptArg(opts.claudeArgs, taskPrompt(goal, task, i + w, tasks.length)),\n cwd: opts.cwd,\n worktreeFlag: opts.worktreeFlag,\n parent: plannerId,\n quiet: true,\n })\n )\n );\n for (let w = 0; w < wave.length; w++) results.push({ task: wave[w], rc: rcs[w] });\n }\n\n // --- phase 3: summary from the children's statuses and inbox handoffs\n const failed = results.filter((r) => r.rc !== 0).length;\n appendLedger(ledgerPath(logDir), \"WORKER-PLAN-END\", {\n planner: plannerId,\n rc: failed ? 1 : 0,\n failed,\n });\n printPlannerSummary(logDir, plannerId, results, parsed.outputFormat, opts.quiet === true);\n return failed ? 1 : 0;\n}\n\n/** Compose and print the planner's summary report (text or json). */\nfunction printPlannerSummary(\n logDir: string,\n plannerId: string,\n results: { task: PlannerTask; rc: number }[],\n fmt: \"text\" | \"json\" | \"stream-json\",\n quiet: boolean\n): void {\n const children = statusesOfChildren(logDir, plannerId);\n const ok = results.filter((r) => r.rc === 0).length;\n const unmerged = children.filter((c) => c.branch && !c.merged);\n const report: WorkerReport = {\n checks: results.map((r, i) => ({\n name: r.task.title,\n ok: r.rc === 0,\n detail: r.rc === 0 ? shortText(children[i]?.last, 80) : `rc=${r.rc}`,\n })),\n ...(unmerged.length\n ? { open: unmerged.map((c) => `branch to merge: pai worker merge ${c.id} (${c.branch})`) }\n : {}),\n notes: `${ok}/${results.length} sub-tasks ok${unmerged.length ? `; ${unmerged.length} branch(es) to merge` : \"\"}`,\n };\n if (quiet || fmt === \"stream-json\") return;\n const resultEvent: StreamEvent = {\n type: \"result\",\n result: [\n `planner ${plannerId}: ${report.notes}`,\n ...(unmerged.length ? [\"branches to merge:\"] : []),\n ...unmerged.map((c) => ` pai worker merge ${c.id} # ${c.branch}`),\n ].join(\"\\n\"),\n is_error: ok !== results.length,\n };\n printResult(fmt, resultEvent, ok === results.length ? 0 : 1, logDir, plannerId, report, {\n plan: results.length,\n ...(unmerged.length ? { branches: unmerged.map((c) => c.branch!) } : {}),\n });\n}\n\n/** Children of the planner, oldest first, from the status files. */\nfunction statusesOfChildren(logDir: string, plannerId: string): WorkerStatus[] {\n return loadStatuses(logDir).filter((s) => s.parent === plannerId);\n}\n"],"mappings":"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"}
|
|
@@ -12,7 +12,7 @@ import { B as claudeProjectsDir, C as findLatestNote, H as scanTranscriptFolders
|
|
|
12
12
|
import { a as expandHome, c as paiConfigFilePath, i as ensureConfigDir, l as OWNER_LABEL_PREFIX, n as CONFIG_FILE$2, o as loadConfig, s as migrateConfigFile, t as CONFIG_DIR, u as UNROUTED } from "./config-DCu8THKi.mjs";
|
|
13
13
|
import { a as schedulerLogPath, i as paiSocketPath, n as daemonLogPath, r as daemonPidPath } from "./runtime-paths-B0P1TvUr.mjs";
|
|
14
14
|
import { A as DEFAULT_CONTEXT_WINDOW, C as inlineWorkersYamlKeys, D as relocateWorkersYaml, E as parseWorkersYamlDocument, M as stripModelVariant, N as readJsonStrict, O as workersYamlLegacyNotice, P as writeJsonAtomic, S as initWorkersYaml, T as needsWorkersYamlRelocation, d as getProviderOrNative, f as isModelCapability, h as nativeAnthropicProvider, i as PROVIDER_TAGS, j as contextWindowFromModelId, k as workersYamlPath, m as maskKey, o as WorkersConfigError, r as MODEL_CAPABILITIES, t as ANTHROPIC_NATIVE, u as expandHome$1, v as readWorkersSection, w as migrateWorkersToYaml } from "./config-DxMZjpvY.mjs";
|
|
15
|
-
import { A as
|
|
15
|
+
import { A as isLive, B as parseRunnerArgs, F as ownsPid, H as describeMcp, I as saveStatus, L as setWorkerLabel, M as loadStatuses, R as waitForTerminalStatus, S as appendLedger, U as expandMcpNames, a as projectLaunchConfig, c as discardWorker, f as sayToWorker, g as openPaneForWorker, h as openFollowPane, i as usageContextTokens, j as loadStatus, k as isChatPane, l as mergeWorker, m as checkPaneForWorker, n as runWorker, o as parseWorkerReport, r as testProvider, u as handoffFromInside, w as UNLABELED, z as longInlinePromptHint } from "./run-Cls0YGgV.mjs";
|
|
16
16
|
import { t as PaiClient } from "./ipc-client-C6NryMMb.mjs";
|
|
17
17
|
import { r as humanDuration, t as createStorageBackend } from "./factory-DNYAqjH0.mjs";
|
|
18
18
|
import { n as migrateIdentityFile } from "./wakeup-UrhiW-KU.mjs";
|
|
@@ -21,8 +21,8 @@ import { n as queriesDirPath, t as migrateQueriesDir } from "./query-feedback-CY
|
|
|
21
21
|
import { s as kgQuery } from "./kg-entity-BSjM5xZh.mjs";
|
|
22
22
|
import { _ as workersLogDir, f as ensureNoMcpConfig, i as buildRunEnv, m as ledgerPath, n as ensureProxyRunning, p as eventsPath, r as stopProxy, t as DEFAULT_PROXY_PORT } from "./server-Ch99xt5K.mjs";
|
|
23
23
|
import { i as sendToSession, n as fetchLiveSessions, r as revealItermSession, t as callAiBroker } from "./aibroker-client-Bn9SnGMa.mjs";
|
|
24
|
-
import { t as runChain } from "./chain-
|
|
25
|
-
import { C as readSpecPrompt, S as statusLineOutput, _ as updateProvider, a as addProvider, b as psOutput, c as describeProviders, d as resolveProviderName, f as setClass, g as unsetClass, h as setWorkersEnabled, i as fallbackStatusText, l as modelPrefsText, m as setProviderModel, n as fallbackOn, o as classTargetText, p as setProviderEnabled, r as fallbackStatus, s as describeModels, t as fallbackOff, u as removeProvider, v as useProvider, w as resolveSpecPath, x as replayOutput, y as followWorkers } from "./fallback-
|
|
24
|
+
import { t as runChain } from "./chain-CNSa0A3G.mjs";
|
|
25
|
+
import { C as readSpecPrompt, S as statusLineOutput, _ as updateProvider, a as addProvider, b as psOutput, c as describeProviders, d as resolveProviderName, f as setClass, g as unsetClass, h as setWorkersEnabled, i as fallbackStatusText, l as modelPrefsText, m as setProviderModel, n as fallbackOn, o as classTargetText, p as setProviderEnabled, r as fallbackStatus, s as describeModels, t as fallbackOff, u as removeProvider, v as useProvider, w as resolveSpecPath, x as replayOutput, y as followWorkers } from "./fallback-IeR2FrFn.mjs";
|
|
26
26
|
import { appendFileSync, chmodSync, copyFileSync, cpSync, createReadStream, existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, readlinkSync, realpathSync, renameSync, statSync, symlinkSync, unlinkSync, writeFileSync } from "node:fs";
|
|
27
27
|
import { homedir, platform, tmpdir } from "node:os";
|
|
28
28
|
import { basename, dirname, join, relative, resolve, sep } from "node:path";
|
|
@@ -5664,7 +5664,7 @@ async function backfillKgFromNotes(options = {}) {
|
|
|
5664
5664
|
var WorkerLogsMigrationError = class extends Error {};
|
|
5665
5665
|
/** RUNNING-and-alive statuses in logDir, excluding the terminal's own interactive pane. */
|
|
5666
5666
|
function activeSpawnedWorkerCount(logDir) {
|
|
5667
|
-
return loadStatuses(logDir).filter((s) =>
|
|
5667
|
+
return loadStatuses(logDir).filter((s) => isLive(s) && !isChatPane(s)).length;
|
|
5668
5668
|
}
|
|
5669
5669
|
function totalFileCount(dir) {
|
|
5670
5670
|
if (!existsSync(dir)) return 0;
|
|
@@ -6598,7 +6598,7 @@ function cmdLogs(opts) {
|
|
|
6598
6598
|
}
|
|
6599
6599
|
function registerDaemonCommands(daemonCmd) {
|
|
6600
6600
|
daemonCmd.command("serve").description("Start the PAI daemon in the foreground").action(async () => {
|
|
6601
|
-
const { serve } = await import("./daemon-
|
|
6601
|
+
const { serve } = await import("./daemon-CYpVy_Dw.mjs");
|
|
6602
6602
|
const { loadConfig: lc, ensureConfigDir } = await import("./config-DeqFqa1N.mjs");
|
|
6603
6603
|
ensureConfigDir();
|
|
6604
6604
|
await serve(lc());
|
|
@@ -10076,6 +10076,24 @@ function parseCompactionEvent(line, turnIndex) {
|
|
|
10076
10076
|
};
|
|
10077
10077
|
}
|
|
10078
10078
|
/**
|
|
10079
|
+
* `type:"system"` `subtype:"model_refusal_fallback"` lines mark an
|
|
10080
|
+
* automatic model switch fired by an API safety refusal (e.g. "cyber"
|
|
10081
|
+
* category), not a user or config choice — worth flagging separately since
|
|
10082
|
+
* it can rebuild the whole prompt cache mid-session.
|
|
10083
|
+
*/
|
|
10084
|
+
function isModelFallback(line) {
|
|
10085
|
+
return line.type === "system" && line.subtype === "model_refusal_fallback";
|
|
10086
|
+
}
|
|
10087
|
+
function parseFallbackEvent(line, turnIndex) {
|
|
10088
|
+
return {
|
|
10089
|
+
turnIndex,
|
|
10090
|
+
from: line.originalModel ?? "unknown",
|
|
10091
|
+
to: line.fallbackModel ?? "unknown",
|
|
10092
|
+
category: line.apiRefusalCategory ?? "unknown",
|
|
10093
|
+
scope: line.scope ?? "unknown"
|
|
10094
|
+
};
|
|
10095
|
+
}
|
|
10096
|
+
/**
|
|
10079
10097
|
* Fold one already-parsed JSONL line into a report being accumulated.
|
|
10080
10098
|
* Exported separately so both the streaming file reader below and tests
|
|
10081
10099
|
* (which build fixtures as arrays of objects, not files) share one path.
|
|
@@ -10098,13 +10116,24 @@ function foldAssistantLine(report, seenIds, line, threshold) {
|
|
|
10098
10116
|
if (typeof v === "number") report.totals[key] += v;
|
|
10099
10117
|
}
|
|
10100
10118
|
const context = (Number(usage.cache_read_input_tokens) || 0) + (Number(usage.cache_creation_input_tokens) || 0) + (Number(usage.input_tokens) || 0);
|
|
10101
|
-
if (report.firstTurnContext === null)
|
|
10119
|
+
if (report.firstTurnContext === null) {
|
|
10120
|
+
report.firstTurnContext = context;
|
|
10121
|
+
report.firstTurnAt = line.timestamp ?? null;
|
|
10122
|
+
}
|
|
10102
10123
|
report.lastTurnContext = context;
|
|
10103
10124
|
report.maxContext = report.maxContext === null ? context : Math.max(report.maxContext, context);
|
|
10104
10125
|
if (context > threshold) report.turnsAboveThreshold++;
|
|
10105
10126
|
if ((Number(usage.cache_creation_input_tokens) || 0) > 2e4) report.cacheRebuildTurns++;
|
|
10106
10127
|
const model = message?.model ?? "unknown";
|
|
10107
10128
|
report.models[model] = (report.models[model] ?? 0) + 1;
|
|
10129
|
+
if (report.lastModel !== null && report.lastModel !== model) report.modelSwitches.push({
|
|
10130
|
+
turnIndex: report.turns,
|
|
10131
|
+
from: report.lastModel,
|
|
10132
|
+
to: model,
|
|
10133
|
+
cacheRead: Number(usage.cache_read_input_tokens) || 0,
|
|
10134
|
+
cacheCreation: Number(usage.cache_creation_input_tokens) || 0
|
|
10135
|
+
});
|
|
10136
|
+
report.lastModel = model;
|
|
10108
10137
|
const split = usage.cache_creation;
|
|
10109
10138
|
if (split) {
|
|
10110
10139
|
report.cacheCreationSplit.ephemeral5m += Number(split.ephemeral_5m_input_tokens) || 0;
|
|
@@ -10120,6 +10149,8 @@ function foldAssistantLine(report, seenIds, line, threshold) {
|
|
|
10120
10149
|
*/
|
|
10121
10150
|
function isRealUserPrompt(line) {
|
|
10122
10151
|
if (line.type !== "user") return false;
|
|
10152
|
+
if (line.isMeta) return false;
|
|
10153
|
+
if (line.origin?.kind && line.origin.kind !== "human") return false;
|
|
10123
10154
|
const content = line.message?.content;
|
|
10124
10155
|
if (typeof content === "string") return true;
|
|
10125
10156
|
if (Array.isArray(content)) return !content.some((block) => block?.type === "tool_result");
|
|
@@ -10144,7 +10175,12 @@ async function parseSessionUsage(path, threshold = 2e5) {
|
|
|
10144
10175
|
turnsAboveThreshold: 0,
|
|
10145
10176
|
cacheRebuildTurns: 0,
|
|
10146
10177
|
userPrompts: 0,
|
|
10147
|
-
|
|
10178
|
+
promptExposure: 0,
|
|
10179
|
+
compactions: [],
|
|
10180
|
+
firstTurnAt: null,
|
|
10181
|
+
lastModel: null,
|
|
10182
|
+
modelSwitches: [],
|
|
10183
|
+
fallbacks: []
|
|
10148
10184
|
};
|
|
10149
10185
|
const seenIds = /* @__PURE__ */ new Set();
|
|
10150
10186
|
let contextSum = 0;
|
|
@@ -10169,8 +10205,15 @@ async function parseSessionUsage(path, threshold = 2e5) {
|
|
|
10169
10205
|
if (event) report.compactions.push(event);
|
|
10170
10206
|
continue;
|
|
10171
10207
|
}
|
|
10208
|
+
if (isModelFallback(obj)) {
|
|
10209
|
+
report.fallbacks.push(parseFallbackEvent(obj, report.turns));
|
|
10210
|
+
continue;
|
|
10211
|
+
}
|
|
10172
10212
|
const context = foldAssistantLine(report, seenIds, obj, threshold);
|
|
10173
|
-
if (context !== null)
|
|
10213
|
+
if (context !== null) {
|
|
10214
|
+
contextSum += context;
|
|
10215
|
+
report.promptExposure += report.userPrompts;
|
|
10216
|
+
}
|
|
10174
10217
|
}
|
|
10175
10218
|
report.avgContext = report.turns > 0 ? Math.round(contextSum / report.turns) : null;
|
|
10176
10219
|
return report;
|
|
@@ -10235,6 +10278,44 @@ async function auditSession(path, threshold) {
|
|
|
10235
10278
|
percentages
|
|
10236
10279
|
};
|
|
10237
10280
|
}
|
|
10281
|
+
/**
|
|
10282
|
+
* First-turn context of every session transcript in this project's
|
|
10283
|
+
* `~/.claude/projects/<encoded-cwd>` directory, newest first — the readings
|
|
10284
|
+
* needed to see when a step change (model switch, hook growth) happened
|
|
10285
|
+
* across sessions without an ad-hoc script.
|
|
10286
|
+
*/
|
|
10287
|
+
async function sessionHistory(cwd, limit = 20) {
|
|
10288
|
+
const dir = join(homedir(), ".claude", "projects", encodeDir(cwd));
|
|
10289
|
+
let entries;
|
|
10290
|
+
try {
|
|
10291
|
+
entries = readdirSync(dir).filter((f) => f.endsWith(".jsonl"));
|
|
10292
|
+
} catch {
|
|
10293
|
+
return [];
|
|
10294
|
+
}
|
|
10295
|
+
const rows = [];
|
|
10296
|
+
for (const entry of entries) {
|
|
10297
|
+
const path = join(dir, entry);
|
|
10298
|
+
const report = await parseSessionUsage(path);
|
|
10299
|
+
if (report.turns === 0) continue;
|
|
10300
|
+
rows.push({
|
|
10301
|
+
path,
|
|
10302
|
+
sessionId: entry.replace(/\.jsonl$/, ""),
|
|
10303
|
+
firstTurnAt: report.firstTurnAt,
|
|
10304
|
+
firstTurnContext: report.firstTurnContext,
|
|
10305
|
+
turns: report.turns,
|
|
10306
|
+
models: report.models,
|
|
10307
|
+
switches: report.modelSwitches.length,
|
|
10308
|
+
fallbacks: report.fallbacks.length
|
|
10309
|
+
});
|
|
10310
|
+
}
|
|
10311
|
+
rows.sort((a, b) => {
|
|
10312
|
+
if (a.firstTurnAt === null && b.firstTurnAt === null) return 0;
|
|
10313
|
+
if (a.firstTurnAt === null) return 1;
|
|
10314
|
+
if (b.firstTurnAt === null) return -1;
|
|
10315
|
+
return b.firstTurnAt.localeCompare(a.firstTurnAt);
|
|
10316
|
+
});
|
|
10317
|
+
return rows.slice(0, limit);
|
|
10318
|
+
}
|
|
10238
10319
|
|
|
10239
10320
|
//#endregion
|
|
10240
10321
|
//#region src/audit/spawn.ts
|
|
@@ -11022,6 +11103,7 @@ const FIRST_TURN_CONTEXT_LIMIT = 3e4;
|
|
|
11022
11103
|
const DAEMON_FAILURE_RATE_LIMIT = .05;
|
|
11023
11104
|
const CONTEXT_GROWTH_AVG_RED = 1e5;
|
|
11024
11105
|
const CONTEXT_GROWTH_MAX_AMBER = 15e4;
|
|
11106
|
+
const MODEL_SWITCH_REBUILD_LIMIT = 2e4;
|
|
11025
11107
|
const SEVERITY_RANK = {
|
|
11026
11108
|
RED: 0,
|
|
11027
11109
|
AMBER: 1,
|
|
@@ -11129,15 +11211,16 @@ function buildFindings(input) {
|
|
|
11129
11211
|
evidence: `avg ${avg}, max ${max}, ${input.session.turnsAboveThreshold} turns > ${input.ctxThreshold}, ${input.session.turns} turns`
|
|
11130
11212
|
});
|
|
11131
11213
|
const perPromptHookCost = input.hooks.totalsByEvent.UserPromptSubmit;
|
|
11132
|
-
|
|
11214
|
+
const inputSent = input.session.totals.cache_read_input_tokens + input.session.totals.cache_creation_input_tokens + input.session.totals.input_tokens;
|
|
11215
|
+
if (perPromptHookCost !== void 0 && inputSent > 0) {
|
|
11133
11216
|
const prompts = input.session.userPrompts;
|
|
11134
|
-
const
|
|
11135
|
-
const total = perPromptHookCost *
|
|
11136
|
-
const pct =
|
|
11217
|
+
const turns = input.session.turns;
|
|
11218
|
+
const total = perPromptHookCost * input.session.promptExposure;
|
|
11219
|
+
const pct = total / inputSent * 100;
|
|
11137
11220
|
findings.push({
|
|
11138
11221
|
finding: "per-prompt hook cost",
|
|
11139
11222
|
severity: pct > PER_PROMPT_HOOK_COST_RED * 100 ? "RED" : pct > PER_PROMPT_HOOK_COST_AMBER * 100 ? "AMBER" : "GREEN",
|
|
11140
|
-
evidence: `${perPromptHookCost} tokens/prompt x ${prompts} prompts = ${total} tokens (${pct.toFixed(1)}% of
|
|
11223
|
+
evidence: `${perPromptHookCost} tokens/prompt x ${prompts} prompts, carried over ${turns} turns = ${total} tokens (${pct.toFixed(1)}% of ${inputSent} input tokens sent)`
|
|
11141
11224
|
});
|
|
11142
11225
|
}
|
|
11143
11226
|
const compactions = input.session.compactions;
|
|
@@ -11147,6 +11230,22 @@ function buildFindings(input) {
|
|
|
11147
11230
|
severity: overshoots.length > 0 ? "RED" : "GREEN",
|
|
11148
11231
|
evidence: overshoots.length > 0 ? `override not honoured: ${overshoots.map((c) => `preTokens=${c.preTokens}`).join(", ")} vs configured ${input.ctxThreshold}` : compactions.length === 0 ? `0 compactions in ${input.session.turns} turns` : `${compactions.length} compaction(s), all within 25% of configured ${input.ctxThreshold}`
|
|
11149
11232
|
});
|
|
11233
|
+
const switches = input.session.modelSwitches;
|
|
11234
|
+
const fallbacks = input.session.fallbacks;
|
|
11235
|
+
const rebuilds = switches.filter((s) => s.cacheCreation > MODEL_SWITCH_REBUILD_LIMIT);
|
|
11236
|
+
const short = (model) => model.replace(/^claude-/, "");
|
|
11237
|
+
let switchEvidence;
|
|
11238
|
+
if (switches.length === 0) switchEvidence = `0 switches in ${input.session.turns} turns, models ${JSON.stringify(input.session.models)}`;
|
|
11239
|
+
else {
|
|
11240
|
+
const list = switches.map((s) => `${short(s.from)}->${short(s.to)} at turn ${s.turnIndex} (cache_read ${s.cacheRead}, cache_creation ${s.cacheCreation})`).join("; ");
|
|
11241
|
+
switchEvidence = `${switches.length} switch(es): ${list}`;
|
|
11242
|
+
if (fallbacks.length > 0) switchEvidence += `; ${fallbacks.length} safeguard fallback(s): ${fallbacks.map((f) => f.category).join(",")}`;
|
|
11243
|
+
}
|
|
11244
|
+
findings.push({
|
|
11245
|
+
finding: "mid-session model switches",
|
|
11246
|
+
severity: switches.length === 0 ? "GREEN" : rebuilds.length > 0 ? "RED" : "AMBER",
|
|
11247
|
+
evidence: switchEvidence
|
|
11248
|
+
});
|
|
11150
11249
|
}
|
|
11151
11250
|
return sortFindings(findings);
|
|
11152
11251
|
}
|
|
@@ -11693,11 +11792,43 @@ function printSession(report) {
|
|
|
11693
11792
|
console.log(`context on last turn: ${report.lastTurnContext ?? "n/a"}`);
|
|
11694
11793
|
console.log(`cache_creation split: ephemeral_5m=${report.cacheCreationSplit.ephemeral5m}, ephemeral_1h=${report.cacheCreationSplit.ephemeral1h}`);
|
|
11695
11794
|
console.log(dim$1(`models: ${JSON.stringify(report.models)}`));
|
|
11696
|
-
console.log(`avg context: ${report.avgContext ?? "n/a"}, max context: ${report.maxContext ?? "n/a"}, turns above threshold: ${report.turnsAboveThreshold}, cache-rebuild turns: ${report.cacheRebuildTurns}, user prompts: ${report.userPrompts}`);
|
|
11795
|
+
console.log(`avg context: ${report.avgContext ?? "n/a"}, max context: ${report.maxContext ?? "n/a"}, turns above threshold: ${report.turnsAboveThreshold}, cache-rebuild turns: ${report.cacheRebuildTurns}, user prompts: ${report.userPrompts}, prompt exposure: ${report.promptExposure}`);
|
|
11697
11796
|
console.log(`compactions: ${report.compactions.length}`);
|
|
11698
11797
|
for (const c of report.compactions) console.log(dim$1(` ${c.trigger} at turn ${c.turnIndex}: preTokens=${c.preTokens}`));
|
|
11798
|
+
console.log(`model switches: ${report.modelSwitches.length}`);
|
|
11799
|
+
for (const s of report.modelSwitches) console.log(dim$1(` ${s.from} -> ${s.to} at turn ${s.turnIndex}: cache_read=${s.cacheRead}, cache_creation=${s.cacheCreation}`));
|
|
11800
|
+
if (report.fallbacks.length > 0) {
|
|
11801
|
+
console.log(`safeguard fallbacks: ${report.fallbacks.length}`);
|
|
11802
|
+
for (const f of report.fallbacks) console.log(dim$1(` ${f.from} -> ${f.to} before turn ${f.turnIndex}: ${f.category} (${f.scope})`));
|
|
11803
|
+
}
|
|
11804
|
+
}
|
|
11805
|
+
function printSessionHistory(rows) {
|
|
11806
|
+
console.log(header("Session history (newest first)"));
|
|
11807
|
+
const tableRows = rows.map((r) => [
|
|
11808
|
+
r.firstTurnAt ?? "n/a",
|
|
11809
|
+
r.sessionId,
|
|
11810
|
+
r.firstTurnContext === null ? "n/a" : String(r.firstTurnContext),
|
|
11811
|
+
String(r.turns),
|
|
11812
|
+
String(r.switches),
|
|
11813
|
+
Object.entries(r.models).map(([name, count]) => `${name}:${count}`).join(",")
|
|
11814
|
+
]);
|
|
11815
|
+
console.log(renderTable([
|
|
11816
|
+
"first turn (UTC)",
|
|
11817
|
+
"session",
|
|
11818
|
+
"first-turn ctx",
|
|
11819
|
+
"turns",
|
|
11820
|
+
"switches",
|
|
11821
|
+
"models"
|
|
11822
|
+
], tableRows));
|
|
11699
11823
|
}
|
|
11700
11824
|
async function cmdSession(path, opts) {
|
|
11825
|
+
if (opts.history !== void 0) {
|
|
11826
|
+
const limit = typeof opts.history === "string" ? parseInt(opts.history, 10) : 20;
|
|
11827
|
+
const rows = await sessionHistory(process.cwd(), limit);
|
|
11828
|
+
if (opts.json) return printJson(rows);
|
|
11829
|
+
printSessionHistory(rows);
|
|
11830
|
+
return;
|
|
11831
|
+
}
|
|
11701
11832
|
const report = await auditSession(path, opts.ctxThreshold ? parseInt(opts.ctxThreshold, 10) : void 0);
|
|
11702
11833
|
if (!report) {
|
|
11703
11834
|
console.error(err(" No session transcript found."));
|
|
@@ -12181,7 +12312,7 @@ function registerAuditCommands(auditCmd) {
|
|
|
12181
12312
|
await cmdFiles(paths, jsonOpt());
|
|
12182
12313
|
});
|
|
12183
12314
|
tokensCmd.command("hooks").description("Token cost of every SessionStart / UserPromptSubmit hook").action(() => cmdHooks(jsonOpt()));
|
|
12184
|
-
tokensCmd.command("session").description("Cache/input/output token split for a session transcript (default: newest)").argument("[path]", "Session JSONL path (default: newest under ~/.claude/projects)").option("--ctx-threshold <n>", "Per-turn context size above which a turn counts as 'above threshold' (default 200000)").action(async (path, opts) => {
|
|
12315
|
+
tokensCmd.command("session").description("Cache/input/output token split for a session transcript (default: newest)").argument("[path]", "Session JSONL path (default: newest under ~/.claude/projects)").option("--ctx-threshold <n>", "Per-turn context size above which a turn counts as 'above threshold' (default 200000)").option("--history [n]", "List first-turn context of the newest n sessions in this project's transcript directory (default 20)").action(async (path, opts) => {
|
|
12185
12316
|
await cmdSession(path, {
|
|
12186
12317
|
...jsonOpt(),
|
|
12187
12318
|
...opts
|
|
@@ -17667,6 +17798,14 @@ function registerWorkerCommands(workerCmd) {
|
|
|
17667
17798
|
fail(/* @__PURE__ */ new Error(`worker ${id} has no pid recorded`));
|
|
17668
17799
|
return;
|
|
17669
17800
|
}
|
|
17801
|
+
if (!ownsPid(status)) {
|
|
17802
|
+
status.state = "lost";
|
|
17803
|
+
status.last = `pid ${status.pid} no longer belongs to this worker (reused); marked lost`;
|
|
17804
|
+
status.rc = null;
|
|
17805
|
+
saveStatus(logDir, status);
|
|
17806
|
+
console.log(`worker ${id}: pid ${status.pid} is not this worker any more (started ${status.started}); marked lost, nothing signalled`);
|
|
17807
|
+
return;
|
|
17808
|
+
}
|
|
17670
17809
|
try {
|
|
17671
17810
|
process.kill(status.pid, "SIGTERM");
|
|
17672
17811
|
if (!await waitForTerminalStatus(logDir, id)) {
|
|
@@ -20051,4 +20190,4 @@ claude() {
|
|
|
20051
20190
|
|
|
20052
20191
|
//#endregion
|
|
20053
20192
|
export { drainStdio as n, buildProgram as t };
|
|
20054
|
-
//# sourceMappingURL=program-
|
|
20193
|
+
//# sourceMappingURL=program-Bgv1lFFA.mjs.map
|