@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.
Files changed (34) hide show
  1. package/dist/{chain-DAEC9AU7.mjs → chain-CNSa0A3G.mjs} +2 -2
  2. package/dist/{chain-DAEC9AU7.mjs.map → chain-CNSa0A3G.mjs.map} +1 -1
  3. package/dist/cli/index.mjs +4 -4
  4. package/dist/cli/program.mjs +4 -4
  5. package/dist/daemon/index.mjs +2 -2
  6. package/dist/{daemon-Dq2ZSdex.mjs → daemon-CYpVy_Dw.mjs} +2 -2
  7. package/dist/{daemon-DNBEFHaj.mjs → daemon-DZ4Q0jrs.mjs} +2 -2
  8. package/dist/{daemon-DNBEFHaj.mjs.map → daemon-DZ4Q0jrs.mjs.map} +1 -1
  9. package/dist/daemon-mcp/index.mjs +3 -3
  10. package/dist/{fallback-CxGDTXWt.mjs → fallback-IeR2FrFn.mjs} +6 -6
  11. package/dist/fallback-IeR2FrFn.mjs.map +1 -0
  12. package/dist/hooks/load-core-context.mjs +9 -11
  13. package/dist/hooks/load-core-context.mjs.map +3 -3
  14. package/dist/hooks/load-project-context.mjs +59 -6
  15. package/dist/hooks/load-project-context.mjs.map +3 -3
  16. package/dist/hooks/worker-status-line.mjs +23 -4
  17. package/dist/hooks/worker-status-line.mjs.map +3 -3
  18. package/dist/{planner-B4DvNpXd.mjs → planner-Cn8HmT7l.mjs} +3 -3
  19. package/dist/{planner-B4DvNpXd.mjs.map → planner-Cn8HmT7l.mjs.map} +1 -1
  20. package/dist/{program-Dk_EIMqC.mjs → program-Bgv1lFFA.mjs} +155 -16
  21. package/dist/program-Bgv1lFFA.mjs.map +1 -0
  22. package/dist/{run-BgExF8Xg.mjs → run-Cls0YGgV.mjs} +43 -6
  23. package/dist/run-Cls0YGgV.mjs.map +1 -0
  24. package/docs/commands/audit.md +1 -0
  25. package/package.json +1 -1
  26. package/src/hooks/ts/lib/frontmatter.test.ts +19 -0
  27. package/src/hooks/ts/lib/frontmatter.ts +10 -0
  28. package/src/hooks/ts/lib/handover-budget.test.ts +124 -0
  29. package/src/hooks/ts/lib/handover-budget.ts +94 -0
  30. package/src/hooks/ts/session-start/load-core-context.ts +6 -15
  31. package/src/hooks/ts/session-start/load-project-context.ts +9 -6
  32. package/dist/fallback-CxGDTXWt.mjs.map +0 -1
  33. package/dist/program-Dk_EIMqC.mjs.map +0 -1
  34. 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 newWorkerId, R as parseRunnerArgs, S as appendLedger, j as loadStatuses, n as runWorker, t as printResult, z as shortText } from "./run-BgExF8Xg.mjs";
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-DAEC9AU7.mjs";
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-B4DvNpXd.mjs.map
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":"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"}
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 loadStatus, B as describeMcp, E as alive, F as setWorkerLabel, I as waitForTerminalStatus, L as longInlinePromptHint, P as saveStatus, R as parseRunnerArgs, S as appendLedger, V as expandMcpNames, a as projectLaunchConfig, c as discardWorker, f as sayToWorker, g as openPaneForWorker, h as openFollowPane, i as usageContextTokens, j as loadStatuses, k as isChatPane, l as mergeWorker, m as checkPaneForWorker, n as runWorker, o as parseWorkerReport, r as testProvider, u as handoffFromInside, w as UNLABELED } from "./run-BgExF8Xg.mjs";
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-DAEC9AU7.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-CxGDTXWt.mjs";
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) => s.state === "running" && alive(s.pid) && !isChatPane(s)).length;
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-Dq2ZSdex.mjs");
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) report.firstTurnContext = context;
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
- compactions: []
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) contextSum += context;
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
- if (perPromptHookCost !== void 0 && input.session.lastTurnContext !== null) {
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 ctx = input.session.lastTurnContext;
11135
- const total = perPromptHookCost * prompts;
11136
- const pct = ctx > 0 ? total / ctx * 100 : 0;
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 last-turn context ${ctx})`
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-Dk_EIMqC.mjs.map
20193
+ //# sourceMappingURL=program-Bgv1lFFA.mjs.map