@tekmidian/pai 0.60.1 → 0.61.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-CGyAIHo4.mjs → chain-C5oeyUg4.mjs} +4 -4
- package/dist/{chain-CGyAIHo4.mjs.map → chain-C5oeyUg4.mjs.map} +1 -1
- package/dist/cli/index.mjs +9 -9
- package/dist/cli/program.mjs +9 -9
- package/dist/{context-handover-cache-BzM7Qudn.mjs → context-handover-cache-BdtJZpgv.mjs} +3 -3
- package/dist/{context-handover-cache-BzM7Qudn.mjs.map → context-handover-cache-BdtJZpgv.mjs.map} +1 -1
- package/dist/daemon/index.mjs +7 -7
- package/dist/{daemon-B_TodsIS.mjs → daemon-DSsIEqzW.mjs} +20 -13
- package/dist/{daemon-B_TodsIS.mjs.map → daemon-DSsIEqzW.mjs.map} +1 -1
- package/dist/{daemon-jviL3VVJ.mjs → daemon-MoFF4oeg.mjs} +7 -7
- package/dist/daemon-mcp/index.mjs +7 -9
- package/dist/daemon-mcp/index.mjs.map +1 -1
- package/dist/{fallback-CjdakQVu.mjs → fallback-DDbJVaR8.mjs} +11 -13
- package/dist/{fallback-CjdakQVu.mjs.map → fallback-DDbJVaR8.mjs.map} +1 -1
- package/dist/hooks/block-sleep-poll.mjs.map +2 -2
- package/dist/hooks/context-compression-hook.mjs.map +1 -1
- package/dist/hooks/post-compact-inject.mjs.map +1 -1
- package/dist/hooks/route-agents-to-worker.mjs +3 -3
- package/dist/hooks/route-agents-to-worker.mjs.map +2 -2
- package/dist/hooks/security-validator.mjs +58 -58
- package/dist/hooks/security-validator.mjs.map +3 -3
- package/dist/hooks/stop-hook.mjs +15 -15
- package/dist/hooks/stop-hook.mjs.map +3 -3
- package/dist/hooks/update-tab-on-action.mjs +14 -12
- package/dist/hooks/update-tab-on-action.mjs.map +3 -3
- package/dist/hooks/update-tab-titles.mjs +13 -9
- package/dist/hooks/update-tab-titles.mjs.map +3 -3
- package/dist/hooks/worker-proxy.mjs +3 -3
- package/dist/hooks/worker-proxy.mjs.map +2 -2
- package/dist/hooks/worker-status-line.mjs +3 -3
- package/dist/hooks/worker-status-line.mjs.map +2 -2
- package/dist/hooks/worker-supervision.mjs +3 -3
- package/dist/hooks/worker-supervision.mjs.map +2 -2
- package/dist/{main-resolver-VMBENifT.mjs → main-resolver-CxOsxtOr.mjs} +2 -2
- package/dist/{main-resolver-BFQfU5xo.mjs → main-resolver-DIQ6m_RA.mjs} +2 -2
- package/dist/{main-resolver-BFQfU5xo.mjs.map → main-resolver-DIQ6m_RA.mjs.map} +1 -1
- package/dist/{planner-DN967Lvm.mjs → planner-D8jAr_sx.mjs} +5 -5
- package/dist/{planner-DN967Lvm.mjs.map → planner-D8jAr_sx.mjs.map} +1 -1
- package/dist/{program-BnsjB57Q.mjs → program-BTdspOOa.mjs} +102 -52
- package/dist/program-BTdspOOa.mjs.map +1 -0
- package/dist/{run-FeM1_UVa.mjs → run-CxxZKEZw.mjs} +333 -172
- package/dist/run-CxxZKEZw.mjs.map +1 -0
- package/dist/{run-env-CEHTeIUM.mjs → run-env-BE5xyCwy.mjs} +25 -6
- package/dist/run-env-BE5xyCwy.mjs.map +1 -0
- package/dist/{server-CekGOJcp.mjs → server-BY6AedmC.mjs} +2 -2
- package/dist/{server-CekGOJcp.mjs.map → server-BY6AedmC.mjs.map} +1 -1
- package/dist/{session-keepalive-Bc9aT678.mjs → session-keepalive-urk--pfa.mjs} +4 -4
- package/dist/{session-keepalive-Bc9aT678.mjs.map → session-keepalive-urk--pfa.mjs.map} +1 -1
- package/dist/{work-queue-worker-DVOVcEnO.mjs → work-queue-worker-1_VBfI_k.mjs} +4 -4
- package/dist/{work-queue-worker-BL4dq5P7.mjs → work-queue-worker-DUvPiwGO.mjs} +2 -2
- package/dist/{work-queue-worker-BL4dq5P7.mjs.map → work-queue-worker-DUvPiwGO.mjs.map} +1 -1
- package/docs/commands/worker.md +1 -0
- package/package.json +1 -1
- package/src/hooks/ts/lib/tab-title.ts +18 -0
- package/src/hooks/ts/post-tool-use/update-tab-on-action.ts +3 -20
- package/src/hooks/ts/stop/stop-hook.ts +4 -15
- package/src/hooks/ts/user-prompt/update-tab-titles.ts +2 -10
- package/dist/program-BnsjB57Q.mjs.map +0 -1
- package/dist/run-FeM1_UVa.mjs.map +0 -1
- package/dist/run-env-CEHTeIUM.mjs.map +0 -1
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import "./factory-EUUhSSoq.mjs";
|
|
2
2
|
import "./config-CQCmH_gV.mjs";
|
|
3
|
-
import {
|
|
4
|
-
import { B as newWorkerId, E as mergeWorker, J as shortText, O as appendLedger, R as loadStatus, T as git, n as runWorker, q as parseRunnerArgs, t as printResult } from "./run-
|
|
5
|
-
import { h as ledgerPath, v as workersLogDir } from "./server-
|
|
6
|
-
import { n as swapPromptArg } from "./chain-
|
|
3
|
+
import { E as readWorkersSection } from "./run-env-BE5xyCwy.mjs";
|
|
4
|
+
import { B as newWorkerId, E as mergeWorker, J as shortText, O as appendLedger, R as loadStatus, T as git, n as runWorker, q as parseRunnerArgs, t as printResult } from "./run-CxxZKEZw.mjs";
|
|
5
|
+
import { h as ledgerPath, v as workersLogDir } from "./server-BY6AedmC.mjs";
|
|
6
|
+
import { n as swapPromptArg } from "./chain-C5oeyUg4.mjs";
|
|
7
7
|
import { existsSync, mkdirSync, readFileSync } from "node:fs";
|
|
8
8
|
import { join } from "node:path";
|
|
9
9
|
|
|
@@ -297,4 +297,4 @@ function printPlannerSummary(logDir, plannerId, results, merged, conflicted, fmt
|
|
|
297
297
|
|
|
298
298
|
//#endregion
|
|
299
299
|
export { runPlanner };
|
|
300
|
-
//# sourceMappingURL=planner-
|
|
300
|
+
//# sourceMappingURL=planner-D8jAr_sx.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"planner-DN967Lvm.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 { loadStatus, newWorkerId } 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 { git, mergeWorker } from \"./worktree.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. Each\n // child is spawned in the planner's own worktree (when it got one) rather\n // than the operator's cwd, so its branch is created off the planner's own\n // HEAD and can later be merged straight into it.\n const plannerStatus = loadStatus(logDir, plannerId);\n const plannerWorktreeDir = plannerStatus?.worktreeDir ?? null;\n const results: { task: PlannerTask; rc: number; id: string }[] = [];\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 waveIds: string[] = new Array(wave.length).fill(\"\");\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: plannerWorktreeDir ?? opts.cwd,\n worktreeFlag: opts.worktreeFlag,\n parent: plannerId,\n quiet: true,\n onWorkerStart: (wid) => {\n waveIds[w] = wid;\n },\n })\n )\n );\n for (let w = 0; w < wave.length; w++) results.push({ task: wave[w], rc: rcs[w], id: waveIds[w] });\n }\n\n // --- phase 3: the planner merges each finished child's branch into its\n // OWN worktree branch, in spawn order — never into the operator's checkout\n // — reusing `pai worker merge`'s own logic (mergeWorker). A conflicting\n // merge is aborted cleanly so the planner's branch stays clean and the\n // next child still gets a try; the child's branch is left intact either way.\n const merged: string[] = [];\n const conflicted: { id: string; branch: string; paths: string[] }[] = [];\n if (plannerWorktreeDir) {\n for (const r of results) {\n if (r.rc !== 0 || !r.id) continue;\n const st = loadStatus(logDir, r.id);\n if (!st?.branch) continue; // ran in place, or no worktree: nothing to merge\n try {\n mergeWorker(logDir, r.id);\n merged.push(r.id);\n } catch {\n let paths: string[] = [];\n try {\n git(plannerWorktreeDir, [\"rev-parse\", \"-q\", \"--verify\", \"MERGE_HEAD\"]);\n paths = git(plannerWorktreeDir, [\"diff\", \"--name-only\", \"--diff-filter=U\"])\n .split(\"\\n\")\n .filter(Boolean);\n git(plannerWorktreeDir, [\"merge\", \"--abort\"]);\n } catch {\n // no merge was left in progress: the failure was not a conflict\n }\n conflicted.push({ id: r.id, branch: st.branch, paths });\n }\n }\n }\n\n // --- phase 4: 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 || conflicted.length ? 1 : 0,\n failed,\n merged: merged.length,\n conflicted: conflicted.length,\n });\n printPlannerSummary(logDir, plannerId, results, merged, conflicted, parsed.outputFormat, opts.quiet === true);\n return failed || conflicted.length ? 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; id: string }[],\n merged: string[],\n conflicted: { id: string; branch: string; paths: string[] }[],\n fmt: \"text\" | \"json\" | \"stream-json\",\n quiet: boolean\n): void {\n const ok = results.filter((r) => r.rc === 0).length;\n const attempted = new Set([...merged, ...conflicted.map((c) => c.id)]);\n // a branch never attempted (no planner worktree to merge into) still needs\n // the old \"go merge me by hand\" hint\n const stillOpen: { id: string; branch: string }[] = [];\n for (const r of results) {\n if (r.rc !== 0 || !r.id || attempted.has(r.id)) continue;\n const st = loadStatus(logDir, r.id);\n if (st?.branch && !st.merged) stillOpen.push({ id: r.id, branch: st.branch });\n }\n const report: WorkerReport = {\n checks: results.map((r) => ({\n name: r.task.title,\n ok: r.rc === 0,\n detail: r.rc === 0 ? shortText(r.id ? loadStatus(logDir, r.id)?.last : \"\", 80) : `rc=${r.rc}`,\n })),\n ...(merged.length || conflicted.length || stillOpen.length\n ? {\n open: [\n ...merged.map((id) => `merged: ${id}`),\n ...conflicted.map(\n (c) =>\n `conflict: pai worker merge ${c.id} (${c.branch})` +\n (c.paths.length ? ` — ${c.paths.join(\", \")}` : \"\")\n ),\n ...stillOpen.map((c) => `branch to merge: pai worker merge ${c.id} (${c.branch})`),\n ],\n }\n : {}),\n notes:\n `${ok}/${results.length} sub-tasks ok` +\n (merged.length ? `; ${merged.length} merged` : \"\") +\n (conflicted.length ? `; ${conflicted.length} conflict(s)` : \"\") +\n (stillOpen.length ? `; ${stillOpen.length} branch(es) to merge` : \"\"),\n };\n if (quiet || fmt === \"stream-json\") return;\n const allOk = ok === results.length && conflicted.length === 0;\n const resultEvent: StreamEvent = {\n type: \"result\",\n result: [\n `planner ${plannerId}: ${report.notes}`,\n ...(conflicted.length ? [\"conflicts:\"] : []),\n ...conflicted.map(\n (c) =>\n ` pai worker merge ${c.id} # ${c.branch}, conflicting: ${c.paths.join(\", \") || \"unknown\"}`\n ),\n ...(stillOpen.length ? [\"branches to merge:\"] : []),\n ...stillOpen.map((c) => ` pai worker merge ${c.id} # ${c.branch}`),\n ].join(\"\\n\"),\n is_error: !allOk,\n };\n printResult(fmt, resultEvent, allOk ? 0 : 1, logDir, plannerId, report, {\n plan: results.length,\n ...(merged.length ? { mergedBranches: merged } : {}),\n ...(conflicted.length ? { conflictedBranches: conflicted.map((c) => c.branch) } : {}),\n ...(stillOpen.length ? { branches: stillOpen.map((c) => c.branch) } : {}),\n });\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"planner-D8jAr_sx.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 { loadStatus, newWorkerId } 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 { git, mergeWorker } from \"./worktree.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. Each\n // child is spawned in the planner's own worktree (when it got one) rather\n // than the operator's cwd, so its branch is created off the planner's own\n // HEAD and can later be merged straight into it.\n const plannerStatus = loadStatus(logDir, plannerId);\n const plannerWorktreeDir = plannerStatus?.worktreeDir ?? null;\n const results: { task: PlannerTask; rc: number; id: string }[] = [];\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 waveIds: string[] = new Array(wave.length).fill(\"\");\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: plannerWorktreeDir ?? opts.cwd,\n worktreeFlag: opts.worktreeFlag,\n parent: plannerId,\n quiet: true,\n onWorkerStart: (wid) => {\n waveIds[w] = wid;\n },\n })\n )\n );\n for (let w = 0; w < wave.length; w++) results.push({ task: wave[w], rc: rcs[w], id: waveIds[w] });\n }\n\n // --- phase 3: the planner merges each finished child's branch into its\n // OWN worktree branch, in spawn order — never into the operator's checkout\n // — reusing `pai worker merge`'s own logic (mergeWorker). A conflicting\n // merge is aborted cleanly so the planner's branch stays clean and the\n // next child still gets a try; the child's branch is left intact either way.\n const merged: string[] = [];\n const conflicted: { id: string; branch: string; paths: string[] }[] = [];\n if (plannerWorktreeDir) {\n for (const r of results) {\n if (r.rc !== 0 || !r.id) continue;\n const st = loadStatus(logDir, r.id);\n if (!st?.branch) continue; // ran in place, or no worktree: nothing to merge\n try {\n mergeWorker(logDir, r.id);\n merged.push(r.id);\n } catch {\n let paths: string[] = [];\n try {\n git(plannerWorktreeDir, [\"rev-parse\", \"-q\", \"--verify\", \"MERGE_HEAD\"]);\n paths = git(plannerWorktreeDir, [\"diff\", \"--name-only\", \"--diff-filter=U\"])\n .split(\"\\n\")\n .filter(Boolean);\n git(plannerWorktreeDir, [\"merge\", \"--abort\"]);\n } catch {\n // no merge was left in progress: the failure was not a conflict\n }\n conflicted.push({ id: r.id, branch: st.branch, paths });\n }\n }\n }\n\n // --- phase 4: 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 || conflicted.length ? 1 : 0,\n failed,\n merged: merged.length,\n conflicted: conflicted.length,\n });\n printPlannerSummary(logDir, plannerId, results, merged, conflicted, parsed.outputFormat, opts.quiet === true);\n return failed || conflicted.length ? 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; id: string }[],\n merged: string[],\n conflicted: { id: string; branch: string; paths: string[] }[],\n fmt: \"text\" | \"json\" | \"stream-json\",\n quiet: boolean\n): void {\n const ok = results.filter((r) => r.rc === 0).length;\n const attempted = new Set([...merged, ...conflicted.map((c) => c.id)]);\n // a branch never attempted (no planner worktree to merge into) still needs\n // the old \"go merge me by hand\" hint\n const stillOpen: { id: string; branch: string }[] = [];\n for (const r of results) {\n if (r.rc !== 0 || !r.id || attempted.has(r.id)) continue;\n const st = loadStatus(logDir, r.id);\n if (st?.branch && !st.merged) stillOpen.push({ id: r.id, branch: st.branch });\n }\n const report: WorkerReport = {\n checks: results.map((r) => ({\n name: r.task.title,\n ok: r.rc === 0,\n detail: r.rc === 0 ? shortText(r.id ? loadStatus(logDir, r.id)?.last : \"\", 80) : `rc=${r.rc}`,\n })),\n ...(merged.length || conflicted.length || stillOpen.length\n ? {\n open: [\n ...merged.map((id) => `merged: ${id}`),\n ...conflicted.map(\n (c) =>\n `conflict: pai worker merge ${c.id} (${c.branch})` +\n (c.paths.length ? ` — ${c.paths.join(\", \")}` : \"\")\n ),\n ...stillOpen.map((c) => `branch to merge: pai worker merge ${c.id} (${c.branch})`),\n ],\n }\n : {}),\n notes:\n `${ok}/${results.length} sub-tasks ok` +\n (merged.length ? `; ${merged.length} merged` : \"\") +\n (conflicted.length ? `; ${conflicted.length} conflict(s)` : \"\") +\n (stillOpen.length ? `; ${stillOpen.length} branch(es) to merge` : \"\"),\n };\n if (quiet || fmt === \"stream-json\") return;\n const allOk = ok === results.length && conflicted.length === 0;\n const resultEvent: StreamEvent = {\n type: \"result\",\n result: [\n `planner ${plannerId}: ${report.notes}`,\n ...(conflicted.length ? [\"conflicts:\"] : []),\n ...conflicted.map(\n (c) =>\n ` pai worker merge ${c.id} # ${c.branch}, conflicting: ${c.paths.join(\", \") || \"unknown\"}`\n ),\n ...(stillOpen.length ? [\"branches to merge:\"] : []),\n ...stillOpen.map((c) => ` pai worker merge ${c.id} # ${c.branch}`),\n ].join(\"\\n\"),\n is_error: !allOk,\n };\n printResult(fmt, resultEvent, allOk ? 0 : 1, logDir, plannerId, report, {\n plan: results.length,\n ...(merged.length ? { mergedBranches: merged } : {}),\n ...(conflicted.length ? { conflictedBranches: conflicted.map((c) => c.branch) } : {}),\n ...(stillOpen.length ? { branches: stillOpen.map((c) => c.branch) } : {}),\n });\n}\n"],"mappings":"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"}
|
|
@@ -5,25 +5,25 @@ import { t as STOP_WORDS } from "./stop-words-ClCwO8mZ.mjs";
|
|
|
5
5
|
import { t as applyRecencyBoost } from "./search-BaTbyWjK.mjs";
|
|
6
6
|
import { a as getStorageBackend, i as getRegistryBackend, r as createStorageBackend, s as humanDuration } from "./factory-EUUhSSoq.mjs";
|
|
7
7
|
import { a as formatDetectionJson, i as formatDetection, n as migrateIdentityFile, r as detectProject } from "./wakeup-C48hcN6O.mjs";
|
|
8
|
-
import { B as claudeProjectsDir, C as findLatestNote, H as scanTranscriptFolders, I as resolveHome, L as saveScanConfig, M as cmdScan, N as loadScanConfig, P as migrateScanConfig, R as upsertProject, S as applyContinue, T as readBodyFile, U as transcriptFiles, V as findMovedProjects, d as migrateWorkQueue, g as migrateSummaryCooldowns, t as readContextHandoverCache, v as extractAndStoreTriples, w as findNotesDir$1, x as appendCheckpointToNote, z as upsertSession } from "./context-handover-cache-
|
|
8
|
+
import { B as claudeProjectsDir, C as findLatestNote, H as scanTranscriptFolders, I as resolveHome, L as saveScanConfig, M as cmdScan, N as loadScanConfig, P as migrateScanConfig, R as upsertProject, S as applyContinue, T as readBodyFile, U as transcriptFiles, V as findMovedProjects, d as migrateWorkQueue, g as migrateSummaryCooldowns, t as readContextHandoverCache, v as extractAndStoreTriples, w as findNotesDir$1, x as appendCheckpointToNote, z as upsertSession } from "./context-handover-cache-BdtJZpgv.mjs";
|
|
9
9
|
import { a as paiHomePath, i as paiHomeDir, n as migratePaiDir, o as resolvePaiFile, r as migratePaiFile, t as PaiFileMigrationError } from "./pai-home-CeOIYltV.mjs";
|
|
10
10
|
import { b as OWNER_LABEL_PREFIX, c as migrateConfigFile, d as readMainConfigRaw, f as writeMainConfigRaw, h as resolvedMainConfigPath, l as paiConfigFilePath, m as migrateMainConfigToYaml, n as CONFIG_FILE$2, o as expandHome, p as MainConfigError, s as loadConfig, t as CONFIG_DIR, u as paiConfigYamlFilePath, v as readJsonStrict, x as UNROUTED, y as writeJsonAtomic } from "./config-CQCmH_gV.mjs";
|
|
11
11
|
import { a as schedulerLogPath, i as paiSocketPath, n as daemonLogPath, r as daemonPidPath } from "./runtime-paths-BRwDeBEg.mjs";
|
|
12
|
-
import {
|
|
13
|
-
import { A as UNLABELED, E as mergeWorker, G as waitForTerminalStatus, H as ownsPid, I as isChatPane, K as longInlinePromptHint, L as isLive, O as appendLedger, R as loadStatus, U as saveStatus, W as setWorkerLabel, X as expandMcpNames, Y as describeMcp, a as projectLaunchConfig, c as handoffFromInside, f as checkPaneForWorker, g as markControlsHeld, h as clickrControlsArgv, i as usageContextTokens, m as openPaneForWorker, n as runWorker, o as parseWorkerReport, p as openFollowPane, q as parseRunnerArgs, r as testProvider, u as sayToWorker, w as discardWorker, z as loadStatuses } from "./run-
|
|
12
|
+
import { B as contextWindowFromModelId, C as nativeAnthropicProvider, E as readWorkersSection, F as parseWorkersYamlDocument, I as relocateWorkersYaml, L as workersYamlLegacyNotice, M as inlineWorkersYamlKeys, N as migrateWorkersToYaml, P as needsWorkersYamlRelocation, R as workersYamlPath, S as maskKey, V as stripModelVariant, b as isModelCapability, f as PROVIDER_TAGS, j as initWorkersYaml, m as WorkersConfigError, r as ANTHROPIC_NATIVE, t as buildRunEnv, u as MODEL_CAPABILITIES, v as expandHome$1, y as getProviderOrNative, z as DEFAULT_CONTEXT_WINDOW } from "./run-env-BE5xyCwy.mjs";
|
|
13
|
+
import { A as UNLABELED, E as mergeWorker, G as waitForTerminalStatus, H as ownsPid, I as isChatPane, K as longInlinePromptHint, L as isLive, O as appendLedger, R as loadStatus, U as saveStatus, W as setWorkerLabel, X as expandMcpNames, Y as describeMcp, a as projectLaunchConfig, c as handoffFromInside, f as checkPaneForWorker, g as markControlsHeld, h as clickrControlsArgv, i as usageContextTokens, m as openPaneForWorker, n as runWorker, o as parseWorkerReport, p as openFollowPane, q as parseRunnerArgs, r as testProvider, u as sayToWorker, w as discardWorker, z as loadStatuses } from "./run-CxxZKEZw.mjs";
|
|
14
14
|
import { t as MergeError } from "./merge-xoyfZQ4W.mjs";
|
|
15
15
|
import { n as oldFederationPath, t as federationDbPath } from "./federation-db-w8e-sKjp.mjs";
|
|
16
16
|
import { r as registryDbPath, t as oldRegistryPath } from "./registry-db-Bkpbp7Gf.mjs";
|
|
17
17
|
import { i as indexProjectWithBackend } from "./async-CLvuY7b-.mjs";
|
|
18
18
|
import { t as PaiClient } from "./ipc-client-Dasm515c.mjs";
|
|
19
|
-
import { A as getConfigValueOp, C as psOutput, D as resolveSpecPath, E as readSpecPrompt, M as setConfigValueOp, N as unsetConfigValueOp, O as MainConfigOpsError, S as followWorkers, T as statusLineOutput, _ as setWorkersEnabled, a as addProvider, b as updateProvider, c as describeModels, d as removeProvider, f as resolveProviderName, g as setProviderModel, h as setProviderEnabled, i as fallbackStatusText, j as listConfigOp, k as formatConfigGetOutput, l as describeProviders, m as setClass, n as fallbackOn, o as classTargetText, p as setCapabilityPreference, r as fallbackStatus, s as describeCapabilities, t as fallbackOff, u as modelPrefsText, v as unsetCapabilityPreference, w as replayOutput, x as useProvider, y as unsetClass } from "./fallback-
|
|
20
|
-
import { _ as resolveSessionByNameOrId, a as buildDeduped, c as hasConversation, d as providerResumePlan, f as restoreTopLevel, g as migrateSessionScanCache, h as fmtAge, l as launchInDir, m as printExitDir, o as normalizeName$2, p as resumeArgvText, s as renderDedupedSessions, t as cmdMain, u as launchPrompt, v as scanCacheFilePath, y as scanSessions } from "./main-resolver-
|
|
19
|
+
import { A as getConfigValueOp, C as psOutput, D as resolveSpecPath, E as readSpecPrompt, M as setConfigValueOp, N as unsetConfigValueOp, O as MainConfigOpsError, S as followWorkers, T as statusLineOutput, _ as setWorkersEnabled, a as addProvider, b as updateProvider, c as describeModels, d as removeProvider, f as resolveProviderName, g as setProviderModel, h as setProviderEnabled, i as fallbackStatusText, j as listConfigOp, k as formatConfigGetOutput, l as describeProviders, m as setClass, n as fallbackOn, o as classTargetText, p as setCapabilityPreference, r as fallbackStatus, s as describeCapabilities, t as fallbackOff, u as modelPrefsText, v as unsetCapabilityPreference, w as replayOutput, x as useProvider, y as unsetClass } from "./fallback-DDbJVaR8.mjs";
|
|
20
|
+
import { _ as resolveSessionByNameOrId, a as buildDeduped, c as hasConversation, d as providerResumePlan, f as restoreTopLevel, g as migrateSessionScanCache, h as fmtAge, l as launchInDir, m as printExitDir, o as normalizeName$2, p as resumeArgvText, s as renderDedupedSessions, t as cmdMain, u as launchPrompt, v as scanCacheFilePath, y as scanSessions } from "./main-resolver-DIQ6m_RA.mjs";
|
|
21
21
|
import { n as queriesDirPath, t as migrateQueriesDir } from "./query-feedback-xz5axTj3.mjs";
|
|
22
|
-
import { h as ledgerPath, m as eventsPath, n as ensureProxyRunning, p as ensureNoMcpConfig, r as stopProxy, t as DEFAULT_PROXY_PORT, v as workersLogDir } from "./server-
|
|
22
|
+
import { h as ledgerPath, m as eventsPath, n as ensureProxyRunning, p as ensureNoMcpConfig, r as stopProxy, t as DEFAULT_PROXY_PORT, v as workersLogDir } from "./server-BY6AedmC.mjs";
|
|
23
23
|
import { i as sendToSession, n as fetchLiveSessions, r as revealItermSession, t as callAiBroker } from "./aibroker-client-BK1h--W_.mjs";
|
|
24
|
-
import { a as totalUsageTokens, i as parseSessionUsage, r as sessionKeepaliveLedgerSummary, t as loadSessionKeepaliveState } from "./session-keepalive-
|
|
25
|
-
import { t as runChain } from "./chain-
|
|
26
|
-
import { appendFileSync, chmodSync, copyFileSync, cpSync, createReadStream, existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, readlinkSync, realpathSync, renameSync, statSync, symlinkSync, unlinkSync, writeFileSync } from "node:fs";
|
|
24
|
+
import { a as totalUsageTokens, i as parseSessionUsage, r as sessionKeepaliveLedgerSummary, t as loadSessionKeepaliveState } from "./session-keepalive-urk--pfa.mjs";
|
|
25
|
+
import { t as runChain } from "./chain-C5oeyUg4.mjs";
|
|
26
|
+
import { appendFileSync, chmodSync, closeSync, copyFileSync, cpSync, createReadStream, existsSync, lstatSync, mkdirSync, openSync, readFileSync, readdirSync, readlinkSync, realpathSync, renameSync, statSync, symlinkSync, unlinkSync, writeFileSync } from "node:fs";
|
|
27
27
|
import { homedir, platform, tmpdir, userInfo } from "node:os";
|
|
28
28
|
import { basename, dirname, join, relative, resolve, sep } from "node:path";
|
|
29
29
|
import chalk from "chalk";
|
|
@@ -2035,7 +2035,7 @@ async function cmdUnregister(slug, opts = {}) {
|
|
|
2035
2035
|
//#region src/cli/commands/project/projects-index.ts
|
|
2036
2036
|
function registerProjectsCommands(projectsCmd) {
|
|
2037
2037
|
projectsCmd.command("list", { isDefault: true }).description("Show the unified deduped listing (same as `pai`).\nUse --all to include cold / zero-session / archived projects.").option("--all", "Show all entries including cold / zero-session / archived projects").option("-n, --n <count>", "Max rows to show", "20").action(async (opts) => {
|
|
2038
|
-
const { cmdMain } = await import("./main-resolver-
|
|
2038
|
+
const { cmdMain } = await import("./main-resolver-CxOsxtOr.mjs");
|
|
2039
2039
|
await cmdMain(void 0, void 0, opts);
|
|
2040
2040
|
});
|
|
2041
2041
|
projectsCmd.command("list-raw").description("Raw registry listing with slug, path, status, last_active columns").option("--all", "Include archived projects (default: active only)").option("--status <status>", "Filter by status: active | archived").option("--tag <tag>", "Filter by tag").option("--type <type>", "Filter by type").action(async (opts) => {
|
|
@@ -2630,13 +2630,13 @@ function migrateFederationDbOrphan(dryRun) {
|
|
|
2630
2630
|
if (existsSync(oldPath) && !dryRun) {
|
|
2631
2631
|
let heldOpen = false;
|
|
2632
2632
|
try {
|
|
2633
|
-
|
|
2633
|
+
execFileSync("lsof", [oldPath], { stdio: "pipe" });
|
|
2634
2634
|
heldOpen = true;
|
|
2635
2635
|
} catch {}
|
|
2636
2636
|
if (heldOpen) {
|
|
2637
2637
|
console.log(" federation database (orphan): held open by the daemon — restarting it first");
|
|
2638
|
-
|
|
2639
|
-
|
|
2638
|
+
execFileSync("pai", ["daemon", "restart"], { stdio: "inherit" });
|
|
2639
|
+
execFileSync("sleep", ["2"]);
|
|
2640
2640
|
}
|
|
2641
2641
|
}
|
|
2642
2642
|
return migrateOrphanFederationDb({ dryRun });
|
|
@@ -2644,12 +2644,12 @@ function migrateFederationDbOrphan(dryRun) {
|
|
|
2644
2644
|
function checkpointAndVerify(oldPath, newPath, label) {
|
|
2645
2645
|
let hasSqlite3 = true;
|
|
2646
2646
|
try {
|
|
2647
|
-
|
|
2647
|
+
execFileSync("which", ["sqlite3"], { stdio: "pipe" });
|
|
2648
2648
|
} catch {
|
|
2649
2649
|
hasSqlite3 = false;
|
|
2650
2650
|
}
|
|
2651
2651
|
if (hasSqlite3) try {
|
|
2652
|
-
|
|
2652
|
+
execFileSync("sqlite3", [oldPath, "PRAGMA wal_checkpoint(TRUNCATE);"], { stdio: "pipe" });
|
|
2653
2653
|
} catch {}
|
|
2654
2654
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10).replace(/-/g, "");
|
|
2655
2655
|
for (const suffix of ["-wal", "-shm"]) {
|
|
@@ -2659,7 +2659,7 @@ function checkpointAndVerify(oldPath, newPath, label) {
|
|
|
2659
2659
|
} catch {}
|
|
2660
2660
|
}
|
|
2661
2661
|
if (hasSqlite3) try {
|
|
2662
|
-
const check =
|
|
2662
|
+
const check = execFileSync("sqlite3", [newPath, "PRAGMA integrity_check;"], { stdio: "pipe" }).toString().trim();
|
|
2663
2663
|
if (check !== "ok") throw new PaiFileMigrationError(`${newPath}: PRAGMA integrity_check reported "${check}" — investigate before trusting this copy`);
|
|
2664
2664
|
} catch (e) {
|
|
2665
2665
|
if (e instanceof PaiFileMigrationError) throw e;
|
|
@@ -2682,13 +2682,13 @@ function migrateRegistryDbFile(dryRun) {
|
|
|
2682
2682
|
if (!existsSync(oldPath) || dryRun) return migratePaiFile(newPath, [oldPath], { dryRun });
|
|
2683
2683
|
let heldOpen = false;
|
|
2684
2684
|
try {
|
|
2685
|
-
|
|
2685
|
+
execFileSync("lsof", [oldPath], { stdio: "pipe" });
|
|
2686
2686
|
heldOpen = true;
|
|
2687
2687
|
} catch {}
|
|
2688
2688
|
if (heldOpen) {
|
|
2689
2689
|
console.log(" registry database: held open by the daemon — restarting it first");
|
|
2690
|
-
|
|
2691
|
-
|
|
2690
|
+
execFileSync("pai", ["daemon", "restart"], { stdio: "inherit" });
|
|
2691
|
+
execFileSync("sleep", ["2"]);
|
|
2692
2692
|
}
|
|
2693
2693
|
const result = migratePaiFile(newPath, [oldPath], { dryRun: false });
|
|
2694
2694
|
if (result.fromPath) checkpointAndVerify(oldPath, newPath, "registry database");
|
|
@@ -2711,13 +2711,13 @@ function migrateFederationDbFile(dryRun) {
|
|
|
2711
2711
|
if (!existsSync(oldPath) || dryRun) return migratePaiFile(newPath, [oldPath], { dryRun });
|
|
2712
2712
|
let heldOpen = false;
|
|
2713
2713
|
try {
|
|
2714
|
-
|
|
2714
|
+
execFileSync("lsof", [oldPath], { stdio: "pipe" });
|
|
2715
2715
|
heldOpen = true;
|
|
2716
2716
|
} catch {}
|
|
2717
2717
|
if (heldOpen) {
|
|
2718
2718
|
console.log(" federation database (live): held open by the daemon — restarting it first");
|
|
2719
|
-
|
|
2720
|
-
|
|
2719
|
+
execFileSync("pai", ["daemon", "restart"], { stdio: "inherit" });
|
|
2720
|
+
execFileSync("sleep", ["2"]);
|
|
2721
2721
|
}
|
|
2722
2722
|
const result = migratePaiFile(newPath, [oldPath], { dryRun: false });
|
|
2723
2723
|
if (result.fromPath) checkpointAndVerify(oldPath, newPath, "federation database");
|
|
@@ -4752,15 +4752,29 @@ function registerBackupCommands(program) {
|
|
|
4752
4752
|
const sqlDest = join(backupDir, "postgres-pai.sql");
|
|
4753
4753
|
console.log(dim$1(` Running pg_dump on ${DOCKER_CONTAINER$1} (this may take a moment)...`));
|
|
4754
4754
|
try {
|
|
4755
|
-
|
|
4756
|
-
|
|
4757
|
-
|
|
4758
|
-
|
|
4755
|
+
execFileSync("docker", [
|
|
4756
|
+
"inspect",
|
|
4757
|
+
DOCKER_CONTAINER$1,
|
|
4758
|
+
"--format={{.State.Status}}"
|
|
4759
|
+
], { stdio: "pipe" });
|
|
4760
|
+
const sqlFd = openSync(sqlDest, "w");
|
|
4761
|
+
try {
|
|
4762
|
+
const dump = spawnSync("docker", [
|
|
4763
|
+
"exec",
|
|
4764
|
+
DOCKER_CONTAINER$1,
|
|
4765
|
+
"pg_dump",
|
|
4766
|
+
"-U",
|
|
4767
|
+
PG_USER$1,
|
|
4768
|
+
PG_DATABASE$1
|
|
4769
|
+
], { stdio: [
|
|
4759
4770
|
"pipe",
|
|
4771
|
+
sqlFd,
|
|
4760
4772
|
"pipe"
|
|
4761
|
-
]
|
|
4762
|
-
|
|
4763
|
-
}
|
|
4773
|
+
] });
|
|
4774
|
+
if (dump.status !== 0) throw new Error(dump.stderr?.toString().trim() || `pg_dump exited with code ${dump.status}`);
|
|
4775
|
+
} finally {
|
|
4776
|
+
closeSync(sqlFd);
|
|
4777
|
+
}
|
|
4764
4778
|
results.push({
|
|
4765
4779
|
label: "Postgres DB",
|
|
4766
4780
|
path: sqlDest,
|
|
@@ -5650,7 +5664,7 @@ function cmdOpen(opts) {
|
|
|
5650
5664
|
const url = `obsidian://open?vault=${encodeURIComponent(vaultName)}`;
|
|
5651
5665
|
console.log(dim$1(` Opening: ${url}`));
|
|
5652
5666
|
try {
|
|
5653
|
-
|
|
5667
|
+
execFileSync("open", [url], { stdio: "ignore" });
|
|
5654
5668
|
console.log(ok$1(` Opened vault "${vaultName}" in Obsidian.`));
|
|
5655
5669
|
} catch (e) {
|
|
5656
5670
|
console.error(err(` Failed to open Obsidian: ${e}`));
|
|
@@ -7038,10 +7052,19 @@ function cmdLogs(opts) {
|
|
|
7038
7052
|
return;
|
|
7039
7053
|
}
|
|
7040
7054
|
if (opts.follow) try {
|
|
7041
|
-
|
|
7055
|
+
execFileSync("tail", [
|
|
7056
|
+
"-f",
|
|
7057
|
+
"-n",
|
|
7058
|
+
lines,
|
|
7059
|
+
DAEMON_LOG
|
|
7060
|
+
], { stdio: "inherit" });
|
|
7042
7061
|
} catch {}
|
|
7043
7062
|
else try {
|
|
7044
|
-
|
|
7063
|
+
execFileSync("tail", [
|
|
7064
|
+
"-n",
|
|
7065
|
+
lines,
|
|
7066
|
+
DAEMON_LOG
|
|
7067
|
+
], { stdio: "inherit" });
|
|
7045
7068
|
} catch (e) {
|
|
7046
7069
|
const msg = e instanceof Error ? e.message : String(e);
|
|
7047
7070
|
console.error(err(`Could not read log: ${msg}`));
|
|
@@ -7050,7 +7073,7 @@ function cmdLogs(opts) {
|
|
|
7050
7073
|
}
|
|
7051
7074
|
function registerDaemonCommands(daemonCmd) {
|
|
7052
7075
|
daemonCmd.command("serve").description("Start the PAI daemon in the foreground").action(async () => {
|
|
7053
|
-
const { serve } = await import("./daemon-
|
|
7076
|
+
const { serve } = await import("./daemon-MoFF4oeg.mjs");
|
|
7054
7077
|
const { loadConfig: lc, ensureConfigDir } = await import("./config-DjXq_W42.mjs");
|
|
7055
7078
|
ensureConfigDir();
|
|
7056
7079
|
await serve(lc());
|
|
@@ -7228,11 +7251,20 @@ function registerRestoreCommands(program) {
|
|
|
7228
7251
|
if (hasSql && opts.postgres) {
|
|
7229
7252
|
console.log(dim$1(" Restoring Postgres database (this may take a while)..."));
|
|
7230
7253
|
try {
|
|
7231
|
-
|
|
7232
|
-
|
|
7233
|
-
|
|
7234
|
-
|
|
7235
|
-
});
|
|
7254
|
+
execFileSync("docker", [
|
|
7255
|
+
"inspect",
|
|
7256
|
+
DOCKER_CONTAINER,
|
|
7257
|
+
"--format={{.State.Status}}"
|
|
7258
|
+
], { stdio: "pipe" });
|
|
7259
|
+
execFileSync("docker", [
|
|
7260
|
+
"exec",
|
|
7261
|
+
DOCKER_CONTAINER,
|
|
7262
|
+
"psql",
|
|
7263
|
+
"-U",
|
|
7264
|
+
PG_USER,
|
|
7265
|
+
"-c",
|
|
7266
|
+
`DROP DATABASE IF EXISTS ${PG_DATABASE}; CREATE DATABASE ${PG_DATABASE} OWNER ${PG_USER};`
|
|
7267
|
+
], { stdio: "pipe" });
|
|
7236
7268
|
const sqlContent = readFileSync(join(resolvedDir, "postgres-pai.sql"), "utf8");
|
|
7237
7269
|
const psqlResult = spawnSync("docker", [
|
|
7238
7270
|
"exec",
|
|
@@ -9304,7 +9336,7 @@ async function stepTaskBus(rl) {
|
|
|
9304
9336
|
if (await promptYesNo(rl, "Found TODOIST_API_KEY in the environment. Use it?", true)) apiKey = envToken;
|
|
9305
9337
|
}
|
|
9306
9338
|
while (!apiKey) {
|
|
9307
|
-
apiKey = (await prompt$1(rl, " Paste your Todoist API token: ")).trim();
|
|
9339
|
+
apiKey = (await prompt$1(rl, " Paste your Todoist API token here: ")).trim();
|
|
9308
9340
|
if (!apiKey) console.log(c.warn(" A token is required, or answer no to skip the task bus."));
|
|
9309
9341
|
}
|
|
9310
9342
|
line$1();
|
|
@@ -15574,20 +15606,32 @@ function pgDump(container, pgConfig, destPath) {
|
|
|
15574
15606
|
const database = resolvePgDatabaseName(pgConfig);
|
|
15575
15607
|
const user = pgConfig.user ?? "pai";
|
|
15576
15608
|
mkdirSync(dirname(destPath), { recursive: true });
|
|
15609
|
+
const fd = openSync(destPath, "w");
|
|
15577
15610
|
try {
|
|
15578
|
-
|
|
15579
|
-
|
|
15580
|
-
|
|
15581
|
-
|
|
15582
|
-
|
|
15583
|
-
|
|
15584
|
-
|
|
15585
|
-
|
|
15611
|
+
const dump = spawnSync("docker", [
|
|
15612
|
+
"exec",
|
|
15613
|
+
container,
|
|
15614
|
+
"pg_dump",
|
|
15615
|
+
"-U",
|
|
15616
|
+
user,
|
|
15617
|
+
"-Fc",
|
|
15618
|
+
database
|
|
15619
|
+
], { stdio: [
|
|
15620
|
+
"pipe",
|
|
15621
|
+
fd,
|
|
15622
|
+
"pipe"
|
|
15623
|
+
] });
|
|
15624
|
+
if (dump.status !== 0) return {
|
|
15625
|
+
ok: false,
|
|
15626
|
+
reason: `pg_dump failed: ${dump.stderr?.toString().trim() || `pg_dump exited with code ${dump.status}`}`
|
|
15627
|
+
};
|
|
15586
15628
|
} catch (e) {
|
|
15587
15629
|
return {
|
|
15588
15630
|
ok: false,
|
|
15589
15631
|
reason: `pg_dump failed: ${e instanceof Error ? e.message.split("\n")[0] : String(e)}`
|
|
15590
15632
|
};
|
|
15633
|
+
} finally {
|
|
15634
|
+
closeSync(fd);
|
|
15591
15635
|
}
|
|
15592
15636
|
if (!existsSync(destPath) || statSync(destPath).size === 0) return {
|
|
15593
15637
|
ok: false,
|
|
@@ -18802,9 +18846,15 @@ function fail$3(e) {
|
|
|
18802
18846
|
process.exitCode = 1;
|
|
18803
18847
|
}
|
|
18804
18848
|
function registerWorkerModelCommand(workerCmd) {
|
|
18805
|
-
workerCmd.command("model [what] [model]").description("Model ids per provider: no args lists them,\n`model <model-id>` sets the active provider's default model (back-compat),\n`model <capability>` shows one capability, `model <capability> <model-id>` sets it.\nCapability names are open (default, fast, image, … — any ^[a-z][a-z0-9-]*$ name);\n`pai worker capability` picks which provider serves one across the whole config.\n--provider targets another provider.").option("--provider <name>", "Provider to read or change (default: the active one)").action((what, model, opts) => {
|
|
18849
|
+
workerCmd.command("model [what] [model]").description("Model ids per provider: no args lists them,\n`model <model-id>` sets the active provider's default model (back-compat),\n`model <capability>` shows one capability, `model <capability> <model-id>` sets it.\nCapability names are open (default, fast, image, … — any ^[a-z][a-z0-9-]*$ name);\n`pai worker capability` picks which provider serves one across the whole config.\n--provider targets another provider.").option("--provider <name>", "Provider to read or change (default: the active one)").option("--capability <name>", "Capability to set/show, with a model id as the only argument (same as the MCP tool)").action((what, model, opts) => {
|
|
18806
18850
|
try {
|
|
18807
18851
|
const { workers } = readWorkersSection();
|
|
18852
|
+
if (opts.capability !== void 0) {
|
|
18853
|
+
if (!isModelCapability(opts.capability)) throw new WorkersConfigError(`"${opts.capability}" is not a valid capability name`);
|
|
18854
|
+
if (model !== void 0) throw new WorkersConfigError("with --capability pass only a model id");
|
|
18855
|
+
model = what;
|
|
18856
|
+
what = opts.capability;
|
|
18857
|
+
}
|
|
18808
18858
|
if (what === void 0) {
|
|
18809
18859
|
for (const line of describeModels(workers)) console.log(` ${line}`);
|
|
18810
18860
|
return;
|
|
@@ -18815,14 +18865,14 @@ function registerWorkerModelCommand(workerCmd) {
|
|
|
18815
18865
|
capability = what;
|
|
18816
18866
|
if (model === void 0) {
|
|
18817
18867
|
const name = resolveProviderName(workers, opts.provider);
|
|
18818
|
-
const p = workers
|
|
18868
|
+
const p = getProviderOrNative(workers, name);
|
|
18819
18869
|
if (p) console.log(` ${name} ${capability} ${p.models[capability] ?? "(none)"}`);
|
|
18820
18870
|
return;
|
|
18821
18871
|
}
|
|
18822
18872
|
id = model;
|
|
18823
18873
|
} else if (model !== void 0) throw new WorkersConfigError(`unexpected second argument "${model}" — usage: model [<capability>] <model-id>`);
|
|
18824
18874
|
const name = resolveProviderName(workers, opts.provider);
|
|
18825
|
-
const p = setProviderModel(name, capability, id)
|
|
18875
|
+
const p = getProviderOrNative(setProviderModel(name, capability, id), name);
|
|
18826
18876
|
if (p) {
|
|
18827
18877
|
console.log(ok$1(`${name} ${capability} model: ${p.models[capability]}`));
|
|
18828
18878
|
console.log(dim$1(` ${modelPrefsText(p)}`));
|
|
@@ -19179,7 +19229,7 @@ function registerWorkerCommands(workerCmd) {
|
|
|
19179
19229
|
const logDir = currentLogDir();
|
|
19180
19230
|
const term = process.env.ITERM_SESSION_ID ?? "";
|
|
19181
19231
|
if (id) {
|
|
19182
|
-
const msg = opts.check ? await checkPaneForWorker(id, workers.pane.fontSize, term) : await openPaneForWorker(logDir, workers, id, term);
|
|
19232
|
+
const msg = opts.check ? await checkPaneForWorker(id, workers.pane.fontSize, term) : (await openPaneForWorker(logDir, workers, id, term)).message;
|
|
19183
19233
|
console.log(msg);
|
|
19184
19234
|
} else console.log(await openFollowPane(logDir, workers, term, opts.check === true));
|
|
19185
19235
|
} catch (e) {
|
|
@@ -21749,4 +21799,4 @@ claude() {
|
|
|
21749
21799
|
|
|
21750
21800
|
//#endregion
|
|
21751
21801
|
export { drainStdio as n, buildProgram as t };
|
|
21752
|
-
//# sourceMappingURL=program-
|
|
21802
|
+
//# sourceMappingURL=program-BTdspOOa.mjs.map
|