@tekmidian/pai 0.36.2 → 0.38.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/{auto-route-Byf8ENXj.mjs → auto-route-DM7GhJ8y.mjs} +2 -2
- package/dist/{auto-route-Byf8ENXj.mjs.map → auto-route-DM7GhJ8y.mjs.map} +1 -1
- package/dist/cli/index.mjs +5 -4
- package/dist/cli/index.mjs.map +1 -1
- package/dist/cli/program.d.mts.map +1 -1
- package/dist/cli/program.mjs +5 -4
- package/dist/{clusters-wZgTCYCB.mjs → clusters-Do4tEGyc.mjs} +1 -1
- package/dist/{clusters-wZgTCYCB.mjs.map → clusters-Do4tEGyc.mjs.map} +1 -1
- package/dist/{context-handover-cache-PtNvj_8D.mjs → context-handover-cache-BpUojjsi.mjs} +2 -2
- package/dist/{context-handover-cache-PtNvj_8D.mjs.map → context-handover-cache-BpUojjsi.mjs.map} +1 -1
- package/dist/daemon/index.mjs +8 -8
- package/dist/{daemon-BZ93KRBo.mjs → daemon-CGg1VCbA.mjs} +25 -25
- package/dist/{daemon-BZ93KRBo.mjs.map → daemon-CGg1VCbA.mjs.map} +1 -1
- package/dist/daemon-DsGGiIJM.mjs +20 -0
- package/dist/daemon-mcp/index.mjs +549 -1
- package/dist/daemon-mcp/index.mjs.map +1 -1
- package/dist/{detector--Gg5JRN5.mjs → detector-CMap-9vw.mjs} +1 -1
- package/dist/{detector--Gg5JRN5.mjs.map → detector-CMap-9vw.mjs.map} +1 -1
- package/dist/detector-DtLExmHN.mjs +5 -0
- package/dist/factory-BXzqRYVZ.mjs +3 -0
- package/dist/{factory-Bsp7xOpO.mjs → factory-DD2T33C9.mjs} +5 -5
- package/dist/{factory-Bsp7xOpO.mjs.map → factory-DD2T33C9.mjs.map} +1 -1
- package/dist/hooks/context-compression-hook.mjs +164 -14
- package/dist/hooks/context-compression-hook.mjs.map +4 -4
- package/dist/hooks/initialize-session.mjs +6 -0
- package/dist/hooks/initialize-session.mjs.map +3 -3
- package/dist/hooks/load-core-context.mjs +6 -0
- package/dist/hooks/load-core-context.mjs.map +3 -3
- package/dist/hooks/load-project-context.mjs +6 -0
- package/dist/hooks/load-project-context.mjs.map +3 -3
- package/dist/hooks/post-compact-inject.mjs +6 -0
- package/dist/hooks/post-compact-inject.mjs.map +3 -3
- package/dist/hooks/route-agents-to-worker.mjs +518 -0
- package/dist/hooks/route-agents-to-worker.mjs.map +7 -0
- package/dist/hooks/status-line.mjs +400 -0
- package/dist/hooks/status-line.mjs.map +7 -0
- package/dist/hooks/stop-hook.mjs +6 -0
- package/dist/hooks/stop-hook.mjs.map +3 -3
- package/dist/hooks/subagent-stop-hook.mjs +6 -0
- package/dist/hooks/subagent-stop-hook.mjs.map +3 -3
- package/dist/hooks/sync-todo-to-md.mjs +6 -0
- package/dist/hooks/sync-todo-to-md.mjs.map +3 -3
- package/dist/hooks/update-tab-on-action.mjs +6 -0
- package/dist/hooks/update-tab-on-action.mjs.map +3 -3
- package/dist/hooks/update-tab-titles.mjs +6 -0
- package/dist/hooks/update-tab-titles.mjs.map +3 -3
- package/dist/hooks/worker-proxy.mjs +858 -0
- package/dist/hooks/worker-proxy.mjs.map +7 -0
- package/dist/hooks/worker-status-line.mjs +545 -0
- package/dist/hooks/worker-status-line.mjs.map +7 -0
- package/dist/{indexer-backend-nQZuEx6N.mjs → indexer-backend-Cox9BCo-.mjs} +1 -1
- package/dist/{indexer-backend-nQZuEx6N.mjs.map → indexer-backend-Cox9BCo-.mjs.map} +1 -1
- package/dist/{kg-entity-DbOMPdF9.mjs → kg-entity-r8duqhi9.mjs} +1 -1
- package/dist/{kg-entity-DbOMPdF9.mjs.map → kg-entity-r8duqhi9.mjs.map} +1 -1
- package/dist/{latent-ideas-Bn6A5-5P.mjs → latent-ideas-BC1oINZ-.mjs} +2 -2
- package/dist/{latent-ideas-Bn6A5-5P.mjs.map → latent-ideas-BC1oINZ-.mjs.map} +1 -1
- package/dist/{link-boost-fYjUnxCN.mjs → link-boost-QFLrJwD6.mjs} +1 -1
- package/dist/{link-boost-fYjUnxCN.mjs.map → link-boost-QFLrJwD6.mjs.map} +1 -1
- package/dist/{main-resolver-BAbhKpeX.mjs → main-resolver-DlaLOFBA.mjs} +1 -1
- package/dist/{main-resolver-BAbhKpeX.mjs.map → main-resolver-DlaLOFBA.mjs.map} +1 -1
- package/dist/{main-resolver-Dxh444GO.mjs → main-resolver-IhZo4pI0.mjs} +1 -1
- package/dist/{neighborhood-DpaEM991.mjs → neighborhood-D9MJ1c8f.mjs} +1 -1
- package/dist/{neighborhood-DpaEM991.mjs.map → neighborhood-D9MJ1c8f.mjs.map} +1 -1
- package/dist/{note-context-DrcY4cWm.mjs → note-context-d1wT_-GA.mjs} +1 -1
- package/dist/{note-context-DrcY4cWm.mjs.map → note-context-d1wT_-GA.mjs.map} +1 -1
- package/dist/{postgres-BVme6qX0.mjs → postgres--BjPtLa0.mjs} +1 -1
- package/dist/{postgres-BVme6qX0.mjs.map → postgres--BjPtLa0.mjs.map} +1 -1
- package/dist/{program-C-fUghPv.mjs → program-Y0hAiVy8.mjs} +851 -101
- package/dist/program-Y0hAiVy8.mjs.map +1 -0
- package/dist/providers-DUshcB-d.mjs +4031 -0
- package/dist/providers-DUshcB-d.mjs.map +1 -0
- package/dist/{query-feedback-C1T6kS18.mjs → query-feedback-BUJxgw5B.mjs} +1 -1
- package/dist/{query-feedback-C1T6kS18.mjs.map → query-feedback-BUJxgw5B.mjs.map} +1 -1
- package/dist/query-feedback-DhyLOe5S.mjs +3 -0
- package/dist/router-1zi8jiNF.mjs +3 -0
- package/dist/{router-CsDm7HvK.mjs → router-DK_sLsUL.mjs} +1 -1
- package/dist/{router-CsDm7HvK.mjs.map → router-DK_sLsUL.mjs.map} +1 -1
- package/dist/skills/Worker/SKILL.md +63 -0
- package/dist/{sources-D8ZdNfvK.mjs → sources-Bi7--33T.mjs} +1 -1
- package/dist/{sources-D8ZdNfvK.mjs.map → sources-Bi7--33T.mjs.map} +1 -1
- package/dist/{sqlite-D1IaR8Am.mjs → sqlite-DtaL1glm.mjs} +1 -1
- package/dist/{sqlite-D1IaR8Am.mjs.map → sqlite-DtaL1glm.mjs.map} +1 -1
- package/dist/{state-qtmrBWCm.mjs → state-8Hm9E4tW.mjs} +1 -1
- package/dist/{state-WaXhLr6R.mjs → state-BY2L6-vX.mjs} +1 -1
- package/dist/{state-WaXhLr6R.mjs.map → state-BY2L6-vX.mjs.map} +1 -1
- package/dist/{themes-XPkj_bfP.mjs → themes-BN0a2duq.mjs} +1 -1
- package/dist/{themes-XPkj_bfP.mjs.map → themes-BN0a2duq.mjs.map} +1 -1
- package/dist/{tools-DEt6YPfc.mjs → tools-CGPqpU3A.mjs} +1 -1
- package/dist/{tools-ceiy7ANX.mjs → tools-y2bJpKom.mjs} +14 -14
- package/dist/{tools-ceiy7ANX.mjs.map → tools-y2bJpKom.mjs.map} +1 -1
- package/dist/{trace-DfyGmMG_.mjs → trace-h23JCcFD.mjs} +1 -1
- package/dist/{trace-DfyGmMG_.mjs.map → trace-h23JCcFD.mjs.map} +1 -1
- package/dist/{vault-indexer-CFvlPUMB.mjs → vault-indexer-DgsPjMgs.mjs} +1 -1
- package/dist/{vault-indexer-CFvlPUMB.mjs.map → vault-indexer-DgsPjMgs.mjs.map} +1 -1
- package/dist/{work-queue-worker-B8W8_3Rn.mjs → work-queue-worker-Dva_v_pI.mjs} +4 -4
- package/dist/{work-queue-worker-B8W8_3Rn.mjs.map → work-queue-worker-Dva_v_pI.mjs.map} +1 -1
- package/dist/{work-queue-worker-HN2Ufg-L.mjs → work-queue-worker-gsKd2LJa.mjs} +4 -4
- package/dist/{zettelkasten-CvjmMghT.mjs → zettelkasten-vo7psPdT.mjs} +3 -3
- package/dist/{zettelkasten-CvjmMghT.mjs.map → zettelkasten-vo7psPdT.mjs.map} +1 -1
- package/docs/commands/README.md +29 -0
- package/docs/commands/backup.md +1 -1
- package/docs/commands/clear-names.md +1 -1
- package/docs/commands/daemon.md +1 -1
- package/docs/commands/db.md +1 -1
- package/docs/commands/end.md +1 -1
- package/docs/commands/help.md +1 -1
- package/docs/commands/identity.md +1 -1
- package/docs/commands/kg.md +1 -1
- package/docs/commands/mcp.md +1 -1
- package/docs/commands/memory.md +1 -1
- package/docs/commands/notify.md +1 -1
- package/docs/commands/observation.md +1 -1
- package/docs/commands/obsidian.md +1 -1
- package/docs/commands/pause.md +1 -1
- package/docs/commands/project.md +1 -1
- package/docs/commands/projects.md +1 -1
- package/docs/commands/registry.md +1 -1
- package/docs/commands/restore.md +1 -1
- package/docs/commands/session.md +1 -1
- package/docs/commands/sessions.md +1 -1
- package/docs/commands/setup.md +1 -1
- package/docs/commands/shell-init.md +1 -1
- package/docs/commands/skill.md +1 -1
- package/docs/commands/task.md +1 -1
- package/docs/commands/topic.md +1 -1
- package/docs/commands/update.md +1 -1
- package/docs/commands/worker.md +398 -0
- package/docs/commands/zettel.md +1 -1
- package/docs/worker.md +434 -0
- package/package.json +1 -1
- package/scripts/build-hooks.mjs +46 -1
- package/src/hooks/pre-compact.sh +1 -0
- package/src/hooks/session-autosave.sh +1 -0
- package/src/hooks/session-stop.sh +1 -0
- package/src/hooks/ts/lib/context-fill.test.ts +104 -0
- package/src/hooks/ts/lib/context-fill.ts +73 -0
- package/src/hooks/ts/lib/handover-evidence.ts +227 -0
- package/src/hooks/ts/lib/worker-session.test.ts +24 -0
- package/src/hooks/ts/lib/worker-session.ts +23 -0
- package/src/hooks/ts/post-tool-use/sync-todo-to-md.ts +2 -0
- package/src/hooks/ts/post-tool-use/update-tab-on-action.ts +2 -0
- package/src/hooks/ts/pre-compact/context-compression-hook.ts +17 -0
- package/src/hooks/ts/pre-tool-use/route-agents-to-worker.ts +128 -0
- package/src/hooks/ts/session-start/initialize-session.ts +2 -0
- package/src/hooks/ts/session-start/load-core-context.ts +2 -0
- package/src/hooks/ts/session-start/load-project-context.ts +2 -0
- package/src/hooks/ts/session-start/post-compact-inject.ts +2 -0
- package/src/hooks/ts/stop/stop-hook.ts +2 -0
- package/src/hooks/ts/subagent-stop/subagent-stop-hook.ts +2 -0
- package/src/hooks/ts/user-prompt/update-tab-titles.ts +2 -0
- package/statusline-command.sh +17 -0
- package/dist/daemon-DRdoA489.mjs +0 -20
- package/dist/detector-DGAk1iBR.mjs +0 -5
- package/dist/factory-CrokPMk2.mjs +0 -3
- package/dist/program-C-fUghPv.mjs.map +0 -1
- package/dist/query-feedback-BBMBp96K.mjs +0 -3
- package/dist/router-BMkOb62X.mjs +0 -3
|
@@ -7,13 +7,14 @@ import { i as indexProject, n as indexAll, t as embedChunks } from "./sync-BWbe8
|
|
|
7
7
|
import { t as STOP_WORDS } from "./stop-words-Hfu8u22w.mjs";
|
|
8
8
|
import { a as searchMemory, i as populateSlugs } from "./search-Rpk1cSBC.mjs";
|
|
9
9
|
import { n as formatDetection, r as formatDetectionJson, t as detectProject } from "./detect-CdaA48EI.mjs";
|
|
10
|
-
import { _ as findMovedProjects, a as findLatestNote, c as extractAndStoreTriples, f as resolveHome, g as claudeProjectsDir, h as upsertSession, i as applyContinue, l as cmdScan, m as upsertProject, o as findNotesDir$1, p as saveScanConfig, r as appendCheckpointToNote, s as readBodyFile, t as readContextHandoverCache, u as loadScanConfig, v as scanTranscriptFolders, y as transcriptFiles } from "./context-handover-cache-
|
|
10
|
+
import { _ as findMovedProjects, a as findLatestNote, c as extractAndStoreTriples, f as resolveHome, g as claudeProjectsDir, h as upsertSession, i as applyContinue, l as cmdScan, m as upsertProject, o as findNotesDir$1, p as saveScanConfig, r as appendCheckpointToNote, s as readBodyFile, t as readContextHandoverCache, u as loadScanConfig, v as scanTranscriptFolders, y as transcriptFiles } from "./context-handover-cache-BpUojjsi.mjs";
|
|
11
11
|
import { a as schedulerLogPath, i as paiSocketPath, n as daemonLogPath, r as daemonPidPath } from "./runtime-paths-rni52zHX.mjs";
|
|
12
12
|
import { t as PaiClient } from "./ipc-client-BmypMNYk.mjs";
|
|
13
13
|
import { a as expandHome, c as UNROUTED, i as ensureConfigDir, n as CONFIG_FILE$2, o as loadConfig, s as OWNER_LABEL_PREFIX, t as CONFIG_DIR } from "./config-B64vFg14.mjs";
|
|
14
|
-
import { r as humanDuration, t as createStorageBackend } from "./factory-
|
|
15
|
-
import { s as
|
|
16
|
-
import {
|
|
14
|
+
import { r as humanDuration, t as createStorageBackend } from "./factory-DD2T33C9.mjs";
|
|
15
|
+
import { A as ledgerPath, C as checkPaneForWorker, E as loadStatus, F as readJsonStrict, I as writeJsonAtomic, M as PROVIDER_TAGS, N as WorkersConfigError, O as parseRunnerArgs, P as readWorkersSection, S as describeMcp, T as openPaneForWorker, _ as testProvider, a as setClass, b as stopProxy, c as unsetClass, d as followWorkers, f as psOutput, g as runWorker, h as runChain, i as removeProvider, j as workersLogDir, k as eventsPath, l as updateProvider, m as statusLineOutput, n as classTargetText, o as setProviderEnabled, p as replayOutput, r as describeProviders, s as setWorkersEnabled, t as addProvider, u as useProvider, v as DEFAULT_PROXY_PORT, w as openFollowPane, x as sayToWorker, y as ensureProxyRunning } from "./providers-DUshcB-d.mjs";
|
|
16
|
+
import { s as kgQuery } from "./kg-entity-r8duqhi9.mjs";
|
|
17
|
+
import { _ as scanSessions, a as renderDedupedSessions, c as probeResume, d as callAiBroker, f as fetchLiveSessions, g as resolveSessionByNameOrId, h as fmtAge, i as normalizeName$2, l as restoreTopLevel, m as sendToSession, o as hasConversation, p as revealItermSession, r as buildDeduped, s as launchInDir, t as cmdMain, u as printExitDir } from "./main-resolver-DlaLOFBA.mjs";
|
|
17
18
|
import { appendFileSync, chmodSync, copyFileSync, createReadStream, existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, readlinkSync, realpathSync, renameSync, statSync, symlinkSync, unlinkSync, writeFileSync } from "node:fs";
|
|
18
19
|
import { homedir, platform, tmpdir } from "node:os";
|
|
19
20
|
import { basename, dirname, join, relative, resolve, sep } from "node:path";
|
|
@@ -1888,7 +1889,7 @@ function cmdUnregister(db, slug, opts = {}) {
|
|
|
1888
1889
|
//#region src/cli/commands/project/projects-index.ts
|
|
1889
1890
|
function registerProjectsCommands(projectsCmd, getDb) {
|
|
1890
1891
|
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) => {
|
|
1891
|
-
const { cmdMain } = await import("./main-resolver-
|
|
1892
|
+
const { cmdMain } = await import("./main-resolver-IhZo4pI0.mjs");
|
|
1892
1893
|
await cmdMain(getDb(), void 0, void 0, opts);
|
|
1893
1894
|
});
|
|
1894
1895
|
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((opts) => {
|
|
@@ -2960,9 +2961,9 @@ function registerStatsCommands(memoryCmd, getDb) {
|
|
|
2960
2961
|
//#region src/cli/commands/memory/sources-cmd.ts
|
|
2961
2962
|
function registerSourcesCommand(memoryCmd) {
|
|
2962
2963
|
memoryCmd.command("sources").description("Show what the indexer has taken in: composition by source, which roots\ncontent enters from, the heaviest single files, and how many chunks are\nrewritten per day (which distinguishes a finite backlog from a treadmill).").option("--limit <n>", "Rows per section (default 8)", "8").action(async (opts) => {
|
|
2963
|
-
const { createStorageBackend } = await import("./factory-
|
|
2964
|
+
const { createStorageBackend } = await import("./factory-BXzqRYVZ.mjs");
|
|
2964
2965
|
const { loadConfig } = await import("./config-C_ErGddD.mjs");
|
|
2965
|
-
const { cmdMemorySources } = await import("./sources-
|
|
2966
|
+
const { cmdMemorySources } = await import("./sources-Bi7--33T.mjs");
|
|
2966
2967
|
let backend;
|
|
2967
2968
|
try {
|
|
2968
2969
|
backend = await createStorageBackend(loadConfig());
|
|
@@ -3649,7 +3650,7 @@ function cmdLogs(opts) {
|
|
|
3649
3650
|
}
|
|
3650
3651
|
function registerDaemonCommands(daemonCmd) {
|
|
3651
3652
|
daemonCmd.command("serve").description("Start the PAI daemon in the foreground").action(async () => {
|
|
3652
|
-
const { serve } = await import("./daemon-
|
|
3653
|
+
const { serve } = await import("./daemon-DsGGiIJM.mjs");
|
|
3653
3654
|
const { loadConfig: lc, ensureConfigDir } = await import("./config-C_ErGddD.mjs");
|
|
3654
3655
|
ensureConfigDir();
|
|
3655
3656
|
await serve(lc());
|
|
@@ -4017,86 +4018,6 @@ function registerRestoreCommands(program) {
|
|
|
4017
4018
|
});
|
|
4018
4019
|
}
|
|
4019
4020
|
|
|
4020
|
-
//#endregion
|
|
4021
|
-
//#region src/config/json-store.ts
|
|
4022
|
-
/**
|
|
4023
|
-
* json-store.ts — read/write JSON config files without destroying them
|
|
4024
|
-
*
|
|
4025
|
-
* The failure this exists to prevent:
|
|
4026
|
-
*
|
|
4027
|
-
* try { return JSON.parse(read(path)); } catch { return {}; }
|
|
4028
|
-
* ... later ...
|
|
4029
|
-
* write(path, JSON.stringify(ourData));
|
|
4030
|
-
*
|
|
4031
|
-
* An unreadable file becomes an empty object, and the next write makes that
|
|
4032
|
-
* permanent. Silently, exit code 0. This shape appeared three times in this
|
|
4033
|
-
* repo against three different files, and twice in AIBroker.
|
|
4034
|
-
*
|
|
4035
|
-
* The distinction that matters is between *missing* and *unreadable*:
|
|
4036
|
-
*
|
|
4037
|
-
* missing — legitimate first run. Start fresh; writing is safe.
|
|
4038
|
-
* unreadable — the file exists and we could not parse it. Those bytes are
|
|
4039
|
-
* the only copy of something. Never overwrite them.
|
|
4040
|
-
*
|
|
4041
|
-
* Collapsing the second into the first is the bug.
|
|
4042
|
-
*
|
|
4043
|
-
* NOT everything deserves this guard. For a transient buffer — an undelivered
|
|
4044
|
-
* message queue, a cache — starting fresh IS the correct recovery, and
|
|
4045
|
-
* refusing to write would disable the feature permanently. Use `writeJsonAtomic`
|
|
4046
|
-
* alone there: it still prevents a crash from truncating a good file, without
|
|
4047
|
-
* blocking recovery. Reserve `readJsonStrict` for data a user cannot rebuild.
|
|
4048
|
-
*/
|
|
4049
|
-
/**
|
|
4050
|
-
* Read a JSON file, distinguishing "absent" from "damaged".
|
|
4051
|
-
*
|
|
4052
|
-
* @param path file to read
|
|
4053
|
-
* @param label how to name it to the user, e.g. "~/.claude.json"
|
|
4054
|
-
* @throws if the file exists but cannot be read or parsed
|
|
4055
|
-
*/
|
|
4056
|
-
function readJsonStrict(path, label = path) {
|
|
4057
|
-
if (!existsSync(path)) return {};
|
|
4058
|
-
let raw;
|
|
4059
|
-
try {
|
|
4060
|
-
raw = readFileSync(path, "utf8");
|
|
4061
|
-
} catch (e) {
|
|
4062
|
-
throw new Error(`Could not read ${label}: ${e instanceof Error ? e.message : String(e)}\nRefusing to continue — writing now would replace its contents with ours alone.`);
|
|
4063
|
-
}
|
|
4064
|
-
try {
|
|
4065
|
-
return JSON.parse(raw);
|
|
4066
|
-
} catch (e) {
|
|
4067
|
-
throw new Error(`${label} exists but is not valid JSON: ${e instanceof Error ? e.message : String(e)}\nRefusing to continue — overwriting it would destroy whatever it holds.\nRepair the file, or move it aside and re-run this command.`);
|
|
4068
|
-
}
|
|
4069
|
-
}
|
|
4070
|
-
/**
|
|
4071
|
-
* Write JSON without risking the existing file.
|
|
4072
|
-
*
|
|
4073
|
-
* Keeps a `.bak-pai` copy of the previous contents, then writes to a temp file
|
|
4074
|
-
* and renames. Rename is atomic within a filesystem, so a crash mid-write
|
|
4075
|
-
* leaves the original intact rather than truncated — which is how these files
|
|
4076
|
-
* become corrupt in the first place.
|
|
4077
|
-
*/
|
|
4078
|
-
function writeJsonAtomic(path, data, opts = {}) {
|
|
4079
|
-
const { backup = true, label = path } = opts;
|
|
4080
|
-
const serialized = JSON.stringify(data, null, 2) + "\n";
|
|
4081
|
-
const dir = dirname(path);
|
|
4082
|
-
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
4083
|
-
if (backup && existsSync(path)) try {
|
|
4084
|
-
copyFileSync(path, `${path}.bak-pai`);
|
|
4085
|
-
} catch (e) {
|
|
4086
|
-
throw new Error(`Could not back up ${label}: ${e instanceof Error ? e.message : String(e)}\nRefusing to write without a backup.`);
|
|
4087
|
-
}
|
|
4088
|
-
const tmp = `${path}.tmp-pai-${process.pid}`;
|
|
4089
|
-
try {
|
|
4090
|
-
writeFileSync(tmp, serialized, "utf8");
|
|
4091
|
-
renameSync(tmp, path);
|
|
4092
|
-
} catch (e) {
|
|
4093
|
-
try {
|
|
4094
|
-
if (existsSync(tmp)) unlinkSync(tmp);
|
|
4095
|
-
} catch {}
|
|
4096
|
-
throw new Error(`Failed to write ${label}: ${e instanceof Error ? e.message : String(e)}\nThe original is unchanged.`);
|
|
4097
|
-
}
|
|
4098
|
-
}
|
|
4099
|
-
|
|
4100
4021
|
//#endregion
|
|
4101
4022
|
//#region src/cli/lib/exit.ts
|
|
4102
4023
|
/**
|
|
@@ -6152,6 +6073,209 @@ async function stepTaskBus(rl) {
|
|
|
6152
6073
|
} };
|
|
6153
6074
|
}
|
|
6154
6075
|
|
|
6076
|
+
//#endregion
|
|
6077
|
+
//#region src/workers/install.ts
|
|
6078
|
+
/**
|
|
6079
|
+
* install.ts — `pai worker install`: one command, whole migration.
|
|
6080
|
+
*
|
|
6081
|
+
* Three things:
|
|
6082
|
+
*
|
|
6083
|
+
* 1. ~/.claude/settings.json: point the PreToolUse "Agent" hook at
|
|
6084
|
+
* ${PAI_DIR}/Hooks/route-agents-to-worker.mjs, replacing the old
|
|
6085
|
+
* route-agents-to-glm.sh registration if present.
|
|
6086
|
+
* 2. ~/.local/bin compatibility shims (glm, glm-run, glm-ps, glm-log) that
|
|
6087
|
+
* exec the pai equivalents, so every existing habit, alias and script
|
|
6088
|
+
* keeps working. Anything already at those paths is moved aside to
|
|
6089
|
+
* <name>.pre-pai once — never deleted.
|
|
6090
|
+
* 3. Remove the old route-agents-to-glm.sh from ~/.claude/Hooks (the
|
|
6091
|
+
* settings.json entry is gone, so the file is dead).
|
|
6092
|
+
*
|
|
6093
|
+
* Idempotent: running it twice changes nothing the second time.
|
|
6094
|
+
*/
|
|
6095
|
+
const HOOKS_DIR = join(join(homedir(), ".claude"), "Hooks");
|
|
6096
|
+
const LOCAL_BIN = join(homedir(), ".local", "bin");
|
|
6097
|
+
const NEW_AGENT_HOOK = "${PAI_DIR}/Hooks/route-agents-to-worker.mjs";
|
|
6098
|
+
const OLD_AGENT_HOOK_STEM = "route-agents-to-glm";
|
|
6099
|
+
/** Absolute path of the pai executable to hard-wire into the shims. */
|
|
6100
|
+
function paiPath() {
|
|
6101
|
+
try {
|
|
6102
|
+
return execFileSync("/usr/bin/which", ["pai"], { encoding: "utf8" }).trim() || "pai";
|
|
6103
|
+
} catch {
|
|
6104
|
+
return "pai";
|
|
6105
|
+
}
|
|
6106
|
+
}
|
|
6107
|
+
function patchAgentHook(lines) {
|
|
6108
|
+
const settings = readSettingsJson();
|
|
6109
|
+
const hooks = typeof settings["hooks"] === "object" && settings["hooks"] !== null ? settings["hooks"] : {};
|
|
6110
|
+
const preToolUse = Array.isArray(hooks["PreToolUse"]) ? hooks["PreToolUse"] : [];
|
|
6111
|
+
let changed = false;
|
|
6112
|
+
let replaced = false;
|
|
6113
|
+
for (const rule of preToolUse) {
|
|
6114
|
+
if (rule["matcher"] !== "Agent") continue;
|
|
6115
|
+
const entries = Array.isArray(rule["hooks"]) ? rule["hooks"] : [];
|
|
6116
|
+
for (const entry of entries) {
|
|
6117
|
+
const cmd = typeof entry["command"] === "string" ? entry["command"] : "";
|
|
6118
|
+
if (cmd === NEW_AGENT_HOOK) replaced = true;
|
|
6119
|
+
if (cmd.includes(OLD_AGENT_HOOK_STEM)) {
|
|
6120
|
+
entry["command"] = NEW_AGENT_HOOK;
|
|
6121
|
+
lines.push(`settings.json: Agent hook migrated to ${NEW_AGENT_HOOK}`);
|
|
6122
|
+
changed = true;
|
|
6123
|
+
replaced = true;
|
|
6124
|
+
}
|
|
6125
|
+
}
|
|
6126
|
+
}
|
|
6127
|
+
if (!replaced) {
|
|
6128
|
+
preToolUse.push({
|
|
6129
|
+
matcher: "Agent",
|
|
6130
|
+
hooks: [{
|
|
6131
|
+
type: "command",
|
|
6132
|
+
command: NEW_AGENT_HOOK
|
|
6133
|
+
}]
|
|
6134
|
+
});
|
|
6135
|
+
lines.push(`settings.json: Agent hook added → ${NEW_AGENT_HOOK}`);
|
|
6136
|
+
changed = true;
|
|
6137
|
+
}
|
|
6138
|
+
if (changed) {
|
|
6139
|
+
hooks["PreToolUse"] = preToolUse;
|
|
6140
|
+
settings["hooks"] = hooks;
|
|
6141
|
+
writeSettingsJson(settings);
|
|
6142
|
+
} else lines.push(`settings.json: Agent hook already current`);
|
|
6143
|
+
return changed;
|
|
6144
|
+
}
|
|
6145
|
+
const GLM_PS_SHIM = (pai) => `# pai worker shim — replaces the python glm-ps of the same name
|
|
6146
|
+
case "\${1-}" in
|
|
6147
|
+
""|"ps") exec ${pai} worker ps ;;
|
|
6148
|
+
follow|watch|pane|log) exec ${pai} worker "$@" ;;
|
|
6149
|
+
--status) shift; exec ${pai} worker status-line "\${ITERM_SESSION_ID:-}" "\${PWD:-$HOME}" ;;
|
|
6150
|
+
-*) exec ${pai} worker ps "$@" ;;
|
|
6151
|
+
*) exec ${pai} worker replay "$@" ;;
|
|
6152
|
+
esac
|
|
6153
|
+
`;
|
|
6154
|
+
function shim(name, pai) {
|
|
6155
|
+
if (name === "glm-ps") return GLM_PS_SHIM(pai);
|
|
6156
|
+
if (name === "glm-log") return `#!/bin/sh\n# pai worker shim — replaces the shell glm-log of the same name\nexec ${pai} worker log "$@"\n`;
|
|
6157
|
+
if (name === "worker-say") return `#!/bin/sh\n# pai worker shim — say one line to a running worker\nexec ${pai} worker say "$@"\n`;
|
|
6158
|
+
return `#!/bin/sh\n# pai worker shim — replaces the previous ${name} of the same name\nexec ${pai} worker run "$@"\n`;
|
|
6159
|
+
}
|
|
6160
|
+
function installShims(lines) {
|
|
6161
|
+
const pai = paiPath();
|
|
6162
|
+
let changed = false;
|
|
6163
|
+
for (const name of [
|
|
6164
|
+
"glm",
|
|
6165
|
+
"glm-run",
|
|
6166
|
+
"glm-ps",
|
|
6167
|
+
"glm-log",
|
|
6168
|
+
"worker-say"
|
|
6169
|
+
]) {
|
|
6170
|
+
const path = join(LOCAL_BIN, name);
|
|
6171
|
+
const ours = `# pai worker shim`;
|
|
6172
|
+
let current = null;
|
|
6173
|
+
if (existsSync(path)) try {
|
|
6174
|
+
current = execFileSync("head", [
|
|
6175
|
+
"-c",
|
|
6176
|
+
"64",
|
|
6177
|
+
path
|
|
6178
|
+
], { encoding: "utf8" });
|
|
6179
|
+
} catch {
|
|
6180
|
+
current = null;
|
|
6181
|
+
}
|
|
6182
|
+
if (current !== null && !current.includes(ours)) {
|
|
6183
|
+
const aside = join(LOCAL_BIN, `${name}.pre-pai`);
|
|
6184
|
+
if (!existsSync(aside)) {
|
|
6185
|
+
renameSync(path, aside);
|
|
6186
|
+
lines.push(`${name}: previous version kept as ${name}.pre-pai`);
|
|
6187
|
+
} else {
|
|
6188
|
+
unlinkSync(path);
|
|
6189
|
+
lines.push(`${name}: replaced (${name}.pre-pai already exists)`);
|
|
6190
|
+
}
|
|
6191
|
+
changed = true;
|
|
6192
|
+
} else if (current === null) changed = true;
|
|
6193
|
+
else {
|
|
6194
|
+
lines.push(`${name}: shim already current`);
|
|
6195
|
+
continue;
|
|
6196
|
+
}
|
|
6197
|
+
writeFileSync(path, shim(name, pai), {
|
|
6198
|
+
encoding: "utf8",
|
|
6199
|
+
mode: 493
|
|
6200
|
+
});
|
|
6201
|
+
try {
|
|
6202
|
+
chmodSync(path, 493);
|
|
6203
|
+
} catch {}
|
|
6204
|
+
lines.push(`${name}: shim installed → ${pai} worker …`);
|
|
6205
|
+
}
|
|
6206
|
+
return changed;
|
|
6207
|
+
}
|
|
6208
|
+
function removeOldHook(lines) {
|
|
6209
|
+
const old = join(HOOKS_DIR, "route-agents-to-glm.sh");
|
|
6210
|
+
if (!existsSync(old)) return false;
|
|
6211
|
+
unlinkSync(old);
|
|
6212
|
+
lines.push(`removed ${OLD_AGENT_HOOK_STEM}.sh (registration migrated above)`);
|
|
6213
|
+
return true;
|
|
6214
|
+
}
|
|
6215
|
+
function installWorkers() {
|
|
6216
|
+
const lines = [];
|
|
6217
|
+
let changed = patchAgentHook(lines);
|
|
6218
|
+
if (installShims(lines)) changed = true;
|
|
6219
|
+
if (removeOldHook(lines)) changed = true;
|
|
6220
|
+
if (!existsSync(LOCAL_BIN)) lines.push(`note: ${LOCAL_BIN} did not exist; make sure it is on PATH`);
|
|
6221
|
+
return {
|
|
6222
|
+
lines,
|
|
6223
|
+
changed
|
|
6224
|
+
};
|
|
6225
|
+
}
|
|
6226
|
+
|
|
6227
|
+
//#endregion
|
|
6228
|
+
//#region src/cli/commands/setup/steps/17-workers.ts
|
|
6229
|
+
/** Step 17: Worker providers — subagents on configured providers. */
|
|
6230
|
+
async function stepWorkers(rl) {
|
|
6231
|
+
section("Step 17: Worker Providers (Optional)");
|
|
6232
|
+
const current = readConfigRaw().workers;
|
|
6233
|
+
if (current?.providers && Object.keys(current.providers).length > 0) {
|
|
6234
|
+
if (current.enabled !== false) {
|
|
6235
|
+
console.log(c.ok("Worker providers already configured. Running install to refresh shims…"));
|
|
6236
|
+
for (const l of installWorkers().lines) line$1(` ${l}`);
|
|
6237
|
+
}
|
|
6238
|
+
return { workers: current };
|
|
6239
|
+
}
|
|
6240
|
+
line$1();
|
|
6241
|
+
line$1(" PAI can run every subagent (worker) on a provider you configure —");
|
|
6242
|
+
line$1(" a non-Anthropic endpoint via its Anthropic-compatible API.");
|
|
6243
|
+
line$1();
|
|
6244
|
+
line$1(" `pai worker run` is the runner; ps / follow / replay watch the runs;");
|
|
6245
|
+
line$1(" the Agent tool is denied and rewritten to it once providers exist.");
|
|
6246
|
+
line$1();
|
|
6247
|
+
line$1(" PAI works fully without this — Agent subagents then just run as before.");
|
|
6248
|
+
line$1();
|
|
6249
|
+
if (!await promptYesNo(rl, "Configure a worker provider now?", false)) {
|
|
6250
|
+
line$1();
|
|
6251
|
+
console.log(c.ok("Skipping worker providers."));
|
|
6252
|
+
console.log(c.dim(" Add later: pai worker providers add <name> … — see docs/worker.md"));
|
|
6253
|
+
return { workers: {
|
|
6254
|
+
enabled: false,
|
|
6255
|
+
providers: {},
|
|
6256
|
+
classes: {}
|
|
6257
|
+
} };
|
|
6258
|
+
}
|
|
6259
|
+
line$1();
|
|
6260
|
+
line$1(" Add the provider after setup finishes (this step only prepares the");
|
|
6261
|
+
line$1(" wiring — shims, hook registration, log dir):");
|
|
6262
|
+
line$1();
|
|
6263
|
+
line$1(c.dim(" pai worker providers add glm \\"));
|
|
6264
|
+
line$1(c.dim(" --base-url https://api.example.com/api/anthropic \\"));
|
|
6265
|
+
line$1(c.dim(" --key-file ~/.config/example/api_key \\"));
|
|
6266
|
+
line$1(c.dim(" --model example-4.7 --fast-model example-4.7-flash"));
|
|
6267
|
+
line$1();
|
|
6268
|
+
line$1(" The first provider turns workers on and seeds the classes.");
|
|
6269
|
+
line$1();
|
|
6270
|
+
const r = installWorkers();
|
|
6271
|
+
for (const l of r.lines) line$1(` ${l}`);
|
|
6272
|
+
return { workers: {
|
|
6273
|
+
enabled: false,
|
|
6274
|
+
providers: {},
|
|
6275
|
+
classes: {}
|
|
6276
|
+
} };
|
|
6277
|
+
}
|
|
6278
|
+
|
|
6155
6279
|
//#endregion
|
|
6156
6280
|
//#region src/cli/commands/setup/index.ts
|
|
6157
6281
|
async function runSetup() {
|
|
@@ -6188,10 +6312,12 @@ async function runSetup() {
|
|
|
6188
6312
|
const mcpRegistered = await stepMcp(rl);
|
|
6189
6313
|
await stepDirectories(rl);
|
|
6190
6314
|
const taskConfig = await stepTaskBus(rl);
|
|
6315
|
+
const workersConfig = await stepWorkers(rl);
|
|
6191
6316
|
const allUpdates = {
|
|
6192
6317
|
...storageConfig,
|
|
6193
6318
|
...embeddingConfig,
|
|
6194
|
-
...taskConfig
|
|
6319
|
+
...taskConfig,
|
|
6320
|
+
...workersConfig
|
|
6195
6321
|
};
|
|
6196
6322
|
mergeConfig(allUpdates);
|
|
6197
6323
|
line$1();
|
|
@@ -7102,7 +7228,7 @@ async function cmdExplore(note, opts) {
|
|
|
7102
7228
|
const depth = parseInt(opts.depth ?? "3", 10);
|
|
7103
7229
|
const direction = opts.direction ?? "both";
|
|
7104
7230
|
const mode = opts.mode ?? "all";
|
|
7105
|
-
const { zettelExplore } = await import("./zettelkasten-
|
|
7231
|
+
const { zettelExplore } = await import("./zettelkasten-vo7psPdT.mjs");
|
|
7106
7232
|
const result = zettelExplore(getFedDb(), {
|
|
7107
7233
|
startNote: note,
|
|
7108
7234
|
depth,
|
|
@@ -7167,7 +7293,7 @@ async function cmdHealth(opts) {
|
|
|
7167
7293
|
const projectPath = opts.project;
|
|
7168
7294
|
const recentDays = parseInt(opts.days ?? "30", 10);
|
|
7169
7295
|
const includeTypes = opts.include ? opts.include.split(",").map((s) => s.trim()) : void 0;
|
|
7170
|
-
const { zettelHealth } = await import("./zettelkasten-
|
|
7296
|
+
const { zettelHealth } = await import("./zettelkasten-vo7psPdT.mjs");
|
|
7171
7297
|
const result = zettelHealth(getFedDb(), {
|
|
7172
7298
|
scope,
|
|
7173
7299
|
projectPath,
|
|
@@ -7238,7 +7364,7 @@ async function cmdSurprise(note, opts) {
|
|
|
7238
7364
|
const limit = parseInt(opts.limit ?? "10", 10);
|
|
7239
7365
|
const minSimilarity = parseFloat(opts.minSimilarity ?? "0.3");
|
|
7240
7366
|
const minGraphDistance = parseInt(opts.minDistance ?? "3", 10);
|
|
7241
|
-
const { zettelSurprise } = await import("./zettelkasten-
|
|
7367
|
+
const { zettelSurprise } = await import("./zettelkasten-vo7psPdT.mjs");
|
|
7242
7368
|
const db = getFedDb();
|
|
7243
7369
|
console.log();
|
|
7244
7370
|
console.log(header(" PAI Zettel Surprise"));
|
|
@@ -7293,7 +7419,7 @@ async function cmdSuggest(note, opts) {
|
|
|
7293
7419
|
const vaultProjectId = parseInt(opts.vaultProjectId, 10);
|
|
7294
7420
|
const limit = parseInt(opts.limit ?? "5", 10);
|
|
7295
7421
|
const excludeLinked = opts.excludeLinked !== false;
|
|
7296
|
-
const { zettelSuggest } = await import("./zettelkasten-
|
|
7422
|
+
const { zettelSuggest } = await import("./zettelkasten-vo7psPdT.mjs");
|
|
7297
7423
|
const db = getFedDb();
|
|
7298
7424
|
console.log();
|
|
7299
7425
|
console.log(header(" PAI Zettel Suggest"));
|
|
@@ -7346,7 +7472,7 @@ async function cmdConverse(question, opts) {
|
|
|
7346
7472
|
const vaultProjectId = parseInt(opts.vaultProjectId, 10);
|
|
7347
7473
|
const depth = parseInt(opts.depth ?? "2", 10);
|
|
7348
7474
|
const limit = parseInt(opts.limit ?? "15", 10);
|
|
7349
|
-
const { zettelConverse } = await import("./zettelkasten-
|
|
7475
|
+
const { zettelConverse } = await import("./zettelkasten-vo7psPdT.mjs");
|
|
7350
7476
|
const db = getFedDb();
|
|
7351
7477
|
console.log();
|
|
7352
7478
|
console.log(header(" PAI Zettel Converse"));
|
|
@@ -7409,7 +7535,7 @@ async function cmdThemes(opts) {
|
|
|
7409
7535
|
const minClusterSize = parseInt(opts.minSize ?? "3", 10);
|
|
7410
7536
|
const maxThemes = parseInt(opts.maxThemes ?? "10", 10);
|
|
7411
7537
|
const similarityThreshold = parseFloat(opts.threshold ?? "0.65");
|
|
7412
|
-
const { zettelThemes } = await import("./zettelkasten-
|
|
7538
|
+
const { zettelThemes } = await import("./zettelkasten-vo7psPdT.mjs");
|
|
7413
7539
|
const db = getFedDb();
|
|
7414
7540
|
console.log();
|
|
7415
7541
|
console.log(header(" PAI Zettel Themes"));
|
|
@@ -8984,7 +9110,7 @@ async function escalate(task, headline, detail, state, now, opts) {
|
|
|
8984
9110
|
state.alarmedAt[task.id] = now;
|
|
8985
9111
|
let delivered = false;
|
|
8986
9112
|
try {
|
|
8987
|
-
const { routeNotification } = await import("./router-
|
|
9113
|
+
const { routeNotification } = await import("./router-1zi8jiNF.mjs");
|
|
8988
9114
|
const { loadConfig } = await import("./config-C_ErGddD.mjs");
|
|
8989
9115
|
delivered = (await routeNotification({
|
|
8990
9116
|
event: "error",
|
|
@@ -11601,9 +11727,9 @@ function cmdActive(db, opts) {
|
|
|
11601
11727
|
], rows));
|
|
11602
11728
|
}
|
|
11603
11729
|
async function cmdAutoRoute(opts) {
|
|
11604
|
-
const { autoRoute, formatAutoRoute, formatAutoRouteJson } = await import("./auto-route-
|
|
11730
|
+
const { autoRoute, formatAutoRoute, formatAutoRouteJson } = await import("./auto-route-DM7GhJ8y.mjs");
|
|
11605
11731
|
const { openRegistry } = await import("./db-XEwJbuGO.mjs");
|
|
11606
|
-
const { createStorageBackend } = await import("./factory-
|
|
11732
|
+
const { createStorageBackend } = await import("./factory-BXzqRYVZ.mjs");
|
|
11607
11733
|
const { loadConfig } = await import("./config-C_ErGddD.mjs");
|
|
11608
11734
|
const config = loadConfig();
|
|
11609
11735
|
const registryDb = openRegistry();
|
|
@@ -12412,6 +12538,40 @@ function listProjectTranscripts(cwd, projectsDir) {
|
|
|
12412
12538
|
collect(join(projectDir, "sessions"));
|
|
12413
12539
|
return paths;
|
|
12414
12540
|
}
|
|
12541
|
+
/** Placeholder model id the platform writes on synthetic assistant turns. */
|
|
12542
|
+
const SYNTHETIC_MODEL = "<synthetic>";
|
|
12543
|
+
function modelFamily(model) {
|
|
12544
|
+
if (typeof model !== "string" || model === "" || model === SYNTHETIC_MODEL) return "unknown";
|
|
12545
|
+
return model.startsWith("claude-") ? "claude" : "foreign";
|
|
12546
|
+
}
|
|
12547
|
+
/**
|
|
12548
|
+
* The family of the LAST assistant model in a transcript — scanned from the
|
|
12549
|
+
* end so a long transcript costs one read and a few lines of parsing.
|
|
12550
|
+
* Unreadable or model-less transcripts are "unknown", never "foreign":
|
|
12551
|
+
* the callers that skip work on "foreign" must not skip it on doubt.
|
|
12552
|
+
*/
|
|
12553
|
+
function transcriptModelFamily(path) {
|
|
12554
|
+
let raw;
|
|
12555
|
+
try {
|
|
12556
|
+
raw = readFileSync(path, "utf-8");
|
|
12557
|
+
} catch {
|
|
12558
|
+
return "unknown";
|
|
12559
|
+
}
|
|
12560
|
+
const lines = raw.split("\n");
|
|
12561
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
12562
|
+
const line = lines[i];
|
|
12563
|
+
if (!line.includes("\"model\"")) continue;
|
|
12564
|
+
try {
|
|
12565
|
+
const entry = JSON.parse(line);
|
|
12566
|
+
if (entry.type !== "assistant") continue;
|
|
12567
|
+
const model = entry.message?.model;
|
|
12568
|
+
if (typeof model === "string" && model !== SYNTHETIC_MODEL) return modelFamily(model);
|
|
12569
|
+
} catch {
|
|
12570
|
+
continue;
|
|
12571
|
+
}
|
|
12572
|
+
}
|
|
12573
|
+
return "unknown";
|
|
12574
|
+
}
|
|
12415
12575
|
/**
|
|
12416
12576
|
* Every DISTINCT compact_boundary sample found across ALL of a project's
|
|
12417
12577
|
* transcripts (live + archived), newest first by the event's OWN timestamp
|
|
@@ -12428,6 +12588,8 @@ function readCompactBoundarySamples(cwd, projectsDir) {
|
|
|
12428
12588
|
} catch {
|
|
12429
12589
|
continue;
|
|
12430
12590
|
}
|
|
12591
|
+
let lastModel = null;
|
|
12592
|
+
let foreignDiscards = 0;
|
|
12431
12593
|
for (const line of raw.split("\n")) {
|
|
12432
12594
|
if (!line.trim()) continue;
|
|
12433
12595
|
let entry;
|
|
@@ -12436,11 +12598,20 @@ function readCompactBoundarySamples(cwd, projectsDir) {
|
|
|
12436
12598
|
} catch {
|
|
12437
12599
|
continue;
|
|
12438
12600
|
}
|
|
12601
|
+
if (entry.type === "assistant") {
|
|
12602
|
+
const model = entry.message?.model;
|
|
12603
|
+
if (typeof model === "string" && model !== SYNTHETIC_MODEL) lastModel = model;
|
|
12604
|
+
continue;
|
|
12605
|
+
}
|
|
12439
12606
|
if (entry.type !== "system" || entry.subtype !== "compact_boundary") continue;
|
|
12440
12607
|
const preTokens = entry.compactMetadata?.preTokens;
|
|
12441
12608
|
if (typeof preTokens !== "number" || !Number.isFinite(preTokens)) continue;
|
|
12442
12609
|
const timestampMs = entry.timestamp ? Date.parse(entry.timestamp) : NaN;
|
|
12443
12610
|
if (!Number.isFinite(timestampMs)) continue;
|
|
12611
|
+
if (modelFamily(lastModel) === "foreign") {
|
|
12612
|
+
foreignDiscards++;
|
|
12613
|
+
continue;
|
|
12614
|
+
}
|
|
12444
12615
|
const key = entry.uuid ?? `${timestampMs}:${preTokens}`;
|
|
12445
12616
|
if (!byKey.has(key)) byKey.set(key, {
|
|
12446
12617
|
preTokens,
|
|
@@ -12449,6 +12620,7 @@ function readCompactBoundarySamples(cwd, projectsDir) {
|
|
|
12449
12620
|
uuid: entry.uuid
|
|
12450
12621
|
});
|
|
12451
12622
|
}
|
|
12623
|
+
if (foreignDiscards > 0) console.error(`[context-fill] ignored ${foreignDiscards} compaction sample(s) from a non-Claude transcript (model=${lastModel}): ${path}`);
|
|
12452
12624
|
}
|
|
12453
12625
|
return [...byKey.values()].sort((a, b) => b.timestampMs - a.timestampMs);
|
|
12454
12626
|
}
|
|
@@ -12592,6 +12764,32 @@ function isImmediate(usedTokens, thresholds) {
|
|
|
12592
12764
|
return usedTokens >= thresholds.immediateTokens;
|
|
12593
12765
|
}
|
|
12594
12766
|
|
|
12767
|
+
//#endregion
|
|
12768
|
+
//#region src/hooks/ts/lib/worker-session.ts
|
|
12769
|
+
/**
|
|
12770
|
+
* Worker-session detection.
|
|
12771
|
+
*
|
|
12772
|
+
* A disposable headless worker (a `claude -p` run started by an orchestrating
|
|
12773
|
+
* session, possibly against a different model provider with a different
|
|
12774
|
+
* context window) shares the project directory and the hook configuration
|
|
12775
|
+
* with the real session that spawned it. Left alone, the hooks treat it as a
|
|
12776
|
+
* session in its own right: they inject project context into it, create and
|
|
12777
|
+
* rename a numbered session note for it, autosave it, and enqueue a
|
|
12778
|
+
* model-written handover for it. Its compactions also land in the project's
|
|
12779
|
+
* transcript folder, where they are indistinguishable from a real session's
|
|
12780
|
+
* and drag the measured compaction trigger down (observed 2026-09-17: two
|
|
12781
|
+
* workers compacting at ~151k tokens pulled a project's trigger from ~784k
|
|
12782
|
+
* to ~151k, and the real session's handover fired at ~50k tokens).
|
|
12783
|
+
*
|
|
12784
|
+
* The launcher marks such sessions with `PAI_WORKER=1`. Every hook that does
|
|
12785
|
+
* per-session bookkeeping returns immediately when this predicate is true.
|
|
12786
|
+
* Deliberately NOT guarded: the security validator (a worker's shell
|
|
12787
|
+
* commands must still be checked) and observability capture.
|
|
12788
|
+
*/
|
|
12789
|
+
function isWorkerSession(env = process.env) {
|
|
12790
|
+
return env.PAI_WORKER === "1";
|
|
12791
|
+
}
|
|
12792
|
+
|
|
12595
12793
|
//#endregion
|
|
12596
12794
|
//#region src/session/context-handover-trigger.ts
|
|
12597
12795
|
/**
|
|
@@ -12705,6 +12903,14 @@ const NOTHING = {
|
|
|
12705
12903
|
* either alone would have.
|
|
12706
12904
|
*/
|
|
12707
12905
|
async function checkAndEnqueueContextHandover(input, deps = defaultDeps) {
|
|
12906
|
+
if (isWorkerSession()) {
|
|
12907
|
+
console.error(`[context-handover-trigger] session ${input.sessionId}: worker session (PAI_WORKER=1) — no handover.`);
|
|
12908
|
+
return NOTHING;
|
|
12909
|
+
}
|
|
12910
|
+
if (input.transcriptPath && transcriptModelFamily(input.transcriptPath) === "foreign") {
|
|
12911
|
+
console.error(`[context-handover-trigger] session ${input.sessionId}: transcript written by a non-Claude model — no handover.`);
|
|
12912
|
+
return NOTHING;
|
|
12913
|
+
}
|
|
12708
12914
|
let state = deps.loadState(input.sessionId);
|
|
12709
12915
|
if (ALL_THRESHOLDS.every((t) => state.confirmed.includes(t))) return NOTHING;
|
|
12710
12916
|
let confirmedThisCheck = [];
|
|
@@ -13371,6 +13577,549 @@ function registerSessionCommands(sessionCmd, getDb) {
|
|
|
13371
13577
|
});
|
|
13372
13578
|
}
|
|
13373
13579
|
|
|
13580
|
+
//#endregion
|
|
13581
|
+
//#region src/workers/agents.ts
|
|
13582
|
+
/**
|
|
13583
|
+
* agents.ts — run agent definitions (~/.claude/agents/<name>.md) as workers.
|
|
13584
|
+
*
|
|
13585
|
+
* The file format is the Claude Code agent one: YAML front matter (`model`,
|
|
13586
|
+
* `tools`, `description`) followed by a Markdown body that is the agent's
|
|
13587
|
+
* system prompt. `pai worker run --agent <name>` loads it and maps it onto
|
|
13588
|
+
* the worker runner:
|
|
13589
|
+
*
|
|
13590
|
+
* - body → --append-system-prompt (prepended, so an explicit caller
|
|
13591
|
+
* flag still wins);
|
|
13592
|
+
* - tools → --allowedTools;
|
|
13593
|
+
* - model → a task class (haiku→simple, sonnet→implement, opus→complex),
|
|
13594
|
+
* unless --class is given.
|
|
13595
|
+
*/
|
|
13596
|
+
/** Directory the agent library lives in. */
|
|
13597
|
+
function agentsDir() {
|
|
13598
|
+
return join(homedir(), ".claude", "agents");
|
|
13599
|
+
}
|
|
13600
|
+
function agentPath(name) {
|
|
13601
|
+
return join(agentsDir(), `${name}.md`);
|
|
13602
|
+
}
|
|
13603
|
+
/** `key: value` (bare) or `key: [a, b]` / `key:\n - a\n - b` (list). */
|
|
13604
|
+
function parseFrontMatter(text) {
|
|
13605
|
+
const out = {};
|
|
13606
|
+
let key = "";
|
|
13607
|
+
for (const raw of text.split("\n")) {
|
|
13608
|
+
const listItem = /^\s+-\s+(.*)$/.exec(raw);
|
|
13609
|
+
if (listItem && key) {
|
|
13610
|
+
const cur = out[key];
|
|
13611
|
+
const item = listItem[1].trim().replace(/^["']|["']$/g, "");
|
|
13612
|
+
out[key] = Array.isArray(cur) ? [...cur, item] : [item];
|
|
13613
|
+
continue;
|
|
13614
|
+
}
|
|
13615
|
+
const kv = /^([A-Za-z0-9_-]+):\s*(.*)$/.exec(raw);
|
|
13616
|
+
if (!kv) continue;
|
|
13617
|
+
key = kv[1];
|
|
13618
|
+
const val = kv[2].trim();
|
|
13619
|
+
if (!val) out[key] = [];
|
|
13620
|
+
else if (val.startsWith("[") && val.endsWith("]")) out[key] = val.slice(1, -1).split(",").map((s) => s.trim().replace(/^["']|["']$/g, "")).filter(Boolean);
|
|
13621
|
+
else out[key] = val.replace(/^["']|["']$/g, "");
|
|
13622
|
+
}
|
|
13623
|
+
return out;
|
|
13624
|
+
}
|
|
13625
|
+
/** Parse an agent file's text (front matter + body). Throws on empty body. */
|
|
13626
|
+
function parseAgentFile(name, text, path) {
|
|
13627
|
+
const m = /^---\n([\s\S]*?)\n---\n?([\s\S]*)$/.exec(text);
|
|
13628
|
+
if (!m) throw new Error(`${path}: expected YAML front matter (--- … ---) before the agent body`);
|
|
13629
|
+
const fm = parseFrontMatter(m[1]);
|
|
13630
|
+
const body = m[2].trim();
|
|
13631
|
+
if (!body) throw new Error(`${path}: the agent body (after the front matter) is empty`);
|
|
13632
|
+
const def = {
|
|
13633
|
+
name,
|
|
13634
|
+
body,
|
|
13635
|
+
path
|
|
13636
|
+
};
|
|
13637
|
+
if (typeof fm.model === "string" && fm.model) def.model = fm.model;
|
|
13638
|
+
if (Array.isArray(fm.tools) && fm.tools.length) def.tools = fm.tools;
|
|
13639
|
+
if (typeof fm.description === "string" && fm.description) def.description = fm.description;
|
|
13640
|
+
return def;
|
|
13641
|
+
}
|
|
13642
|
+
/** Load ~/.claude/agents/<name>.md; the error names the library when missing. */
|
|
13643
|
+
function loadAgent(name) {
|
|
13644
|
+
const path = agentPath(name);
|
|
13645
|
+
let text;
|
|
13646
|
+
try {
|
|
13647
|
+
text = readFileSync(path, "utf8");
|
|
13648
|
+
} catch {
|
|
13649
|
+
throw new Error(`no agent named "${name}" at ${path}. The agent library lives in ~/.claude/agents/<name>.md and runs on workers.`);
|
|
13650
|
+
}
|
|
13651
|
+
return parseAgentFile(name, text, path);
|
|
13652
|
+
}
|
|
13653
|
+
/** Map an agent's front-matter model to a worker class. */
|
|
13654
|
+
function modelToClass(model) {
|
|
13655
|
+
if (!model) return void 0;
|
|
13656
|
+
const m = model.toLowerCase();
|
|
13657
|
+
if (m.includes("haiku")) return "simple";
|
|
13658
|
+
if (m.includes("sonnet")) return "implement";
|
|
13659
|
+
if (m.includes("opus")) return "complex";
|
|
13660
|
+
}
|
|
13661
|
+
/** Extra claude args the definition contributes (before the caller's own). */
|
|
13662
|
+
function agentClaudeArgs(def) {
|
|
13663
|
+
const args = [];
|
|
13664
|
+
if (def.tools?.length) args.push("--allowedTools", def.tools.join(","));
|
|
13665
|
+
args.push("--append-system-prompt", def.body);
|
|
13666
|
+
return args;
|
|
13667
|
+
}
|
|
13668
|
+
/** `<agent>: <first 50 chars of prompt>` — the default label for a run. */
|
|
13669
|
+
function agentLabel(name, prompt) {
|
|
13670
|
+
return `${name}: ${prompt ?? "(no prompt)"}`.slice(0, name.length + 2 + 50);
|
|
13671
|
+
}
|
|
13672
|
+
|
|
13673
|
+
//#endregion
|
|
13674
|
+
//#region src/cli/commands/worker/providers.ts
|
|
13675
|
+
function fail$1(e) {
|
|
13676
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
13677
|
+
console.error(err("pai worker: ") + msg);
|
|
13678
|
+
process.exitCode = 1;
|
|
13679
|
+
}
|
|
13680
|
+
/** `--env K=V` (repeatable) → record. Throws on a value without "=". */
|
|
13681
|
+
function collectEnv(pairs) {
|
|
13682
|
+
const out = {};
|
|
13683
|
+
for (const p of pairs ?? []) {
|
|
13684
|
+
const eq = p.indexOf("=");
|
|
13685
|
+
if (eq <= 0) throw new WorkersConfigError(`--env expects NAME=VALUE, got "${p}"`);
|
|
13686
|
+
out[p.slice(0, eq)] = p.slice(eq + 1);
|
|
13687
|
+
}
|
|
13688
|
+
return out;
|
|
13689
|
+
}
|
|
13690
|
+
function parseIntArg$1(v) {
|
|
13691
|
+
const n = parseInt(v, 10);
|
|
13692
|
+
if (Number.isNaN(n) || n <= 0) throw new WorkersConfigError(`expected a positive number, got "${v}"`);
|
|
13693
|
+
return n;
|
|
13694
|
+
}
|
|
13695
|
+
function parseCostTier(v) {
|
|
13696
|
+
const n = parseInt(v, 10);
|
|
13697
|
+
if (!Number.isInteger(n) || n < 1 || n > 5) throw new WorkersConfigError(`cost tier must be an integer 1 (cheapest) … 5 (most expensive), got "${v}"`);
|
|
13698
|
+
return n;
|
|
13699
|
+
}
|
|
13700
|
+
/** `--tags a,b` (repeatable) → validated tag list. */
|
|
13701
|
+
function collectTags(vals) {
|
|
13702
|
+
const out = [];
|
|
13703
|
+
for (const v of vals ?? []) for (const t of v.split(",").map((s) => s.trim()).filter(Boolean)) {
|
|
13704
|
+
if (!PROVIDER_TAGS.includes(t)) throw new WorkersConfigError(`"${t}" is not a tag (from: ${PROVIDER_TAGS.join(", ")})`);
|
|
13705
|
+
if (!out.includes(t)) out.push(t);
|
|
13706
|
+
}
|
|
13707
|
+
return out;
|
|
13708
|
+
}
|
|
13709
|
+
/** Positional target + constraint flags → a ClassTarget for setClass. */
|
|
13710
|
+
function buildClassTarget(target, opts) {
|
|
13711
|
+
const hasConstraints = opts.provider !== void 0 || opts.mcp !== void 0 || opts.maxCostTier !== void 0 || opts.requireTags !== void 0 || opts.order !== void 0;
|
|
13712
|
+
if (target !== void 0 && !hasConstraints) return target;
|
|
13713
|
+
const obj = {};
|
|
13714
|
+
if (target !== void 0) obj.provider = target.split("/")[0];
|
|
13715
|
+
if (opts.provider !== void 0) obj.provider = opts.provider;
|
|
13716
|
+
if (opts.mcp !== void 0) obj.mcp = opts.mcp.split(",").map((s) => s.trim()).filter(Boolean);
|
|
13717
|
+
if (opts.maxCostTier !== void 0) obj.maxCostTier = opts.maxCostTier;
|
|
13718
|
+
if (opts.requireTags !== void 0) obj.requireTags = opts.requireTags;
|
|
13719
|
+
if (opts.order !== void 0) obj.order = opts.order.split(",").map((s) => s.trim()).filter(Boolean);
|
|
13720
|
+
if (target !== void 0 && target.includes("/") && obj.provider) {
|
|
13721
|
+
const alias = target.split("/")[1];
|
|
13722
|
+
if (alias && alias !== "default") throw new WorkersConfigError(`a provider alias ("${alias}") cannot be combined with constraint flags — use the plain "<target>" form for pinning`);
|
|
13723
|
+
}
|
|
13724
|
+
if (!Object.keys(obj).length) throw new WorkersConfigError("give a target (provider, provider/fast) or a constraint (--max-cost-tier, --require-tags, …)");
|
|
13725
|
+
return obj;
|
|
13726
|
+
}
|
|
13727
|
+
function registerWorkerProviderCommands(providersCmd) {
|
|
13728
|
+
providersCmd.description("Providers: list (default), add, remove, use, enable, disable, test").action(() => {
|
|
13729
|
+
const { workers } = readWorkersSection();
|
|
13730
|
+
console.log();
|
|
13731
|
+
for (const line of describeProviders(workers)) console.log(` ${line}`);
|
|
13732
|
+
console.log();
|
|
13733
|
+
console.log(dim$1(` workers are ${workers.enabled ? "on" : "off"} — pai worker ${workers.enabled ? "off" : "on"}`));
|
|
13734
|
+
console.log();
|
|
13735
|
+
});
|
|
13736
|
+
providersCmd.command("add <name>").description("Add a provider; the first one also turns workers on and seeds classes.\nExample: pai worker providers add glm --base-url https://…/anthropic \\\n --key-file ~/.config/zai/api_key --model glm-5.3 --fast-model glm-5.3-flash\nOpenAI-protocol: --protocol openai --upstream-url https://…/v1 (runs via the PAI proxy).\nCodex (ChatGPT plan): --engine codex — runs through the Codex CLI.").option("--base-url <url>", "Anthropic-compatible API base URL (required unless --protocol openai)").requiredOption("--model <model>", "Default model id for this provider").option("--key-file <path>", "File holding the API token (0600); omit for token \"local\"").option("--fast-model <model>", "Cheaper model for spotchecks and routing").option("--env <name=value>", "Extra env for runs (repeatable)", (v, acc) => [...acc, v], []).option("--note <text>", "Human note shown in `providers list`").option("--protocol <proto>", "anthropic (default) or openai — openai runs through the local PAI proxy").option("--upstream-url <url>", "Chat Completions base URL (required for --protocol openai)").option("--engine <engine>", "claude (default) or codex — codex runs `codex exec --json`").option("--context-window <tokens>", "Context window for the meter (default 200000; init event overrides)", parseIntArg$1).option("--quota-probe <url>", "URL whose JSON first number is the quota percent (0-100)").option("--cost-tier <1-5>", "Cost tier 1 (cheapest) … 5 (most expensive; default 3)", parseCostTier).option("--tags <tags>", "Capability tags, comma-separated (from: " + PROVIDER_TAGS.join(", ") + ")", (v, acc) => [...acc, v], []).action((name, opts) => {
|
|
13737
|
+
try {
|
|
13738
|
+
const protocol = opts.protocol;
|
|
13739
|
+
const engine = opts.engine;
|
|
13740
|
+
if (protocol && protocol !== "anthropic" && protocol !== "openai") throw new WorkersConfigError(`--protocol must be anthropic or openai, got "${protocol}"`);
|
|
13741
|
+
if (engine && engine !== "claude" && engine !== "codex") throw new WorkersConfigError(`--engine must be claude or codex, got "${engine}"`);
|
|
13742
|
+
if (protocol !== "openai" && !opts.baseUrl) throw new WorkersConfigError("--base-url is required (only --protocol openai goes without it)");
|
|
13743
|
+
const tags = collectTags(opts.tags);
|
|
13744
|
+
addProvider({
|
|
13745
|
+
name,
|
|
13746
|
+
baseUrl: opts.baseUrl ?? "",
|
|
13747
|
+
keyFile: opts.keyFile ?? null,
|
|
13748
|
+
model: opts.model,
|
|
13749
|
+
fastModel: opts.fastModel,
|
|
13750
|
+
env: collectEnv(opts.env),
|
|
13751
|
+
note: opts.note,
|
|
13752
|
+
...protocol ? { protocol } : {},
|
|
13753
|
+
...opts.upstreamUrl ? { upstreamUrl: opts.upstreamUrl } : {},
|
|
13754
|
+
...engine ? { engine } : {},
|
|
13755
|
+
...opts.contextWindow ? { contextWindow: opts.contextWindow } : {},
|
|
13756
|
+
quotaProbe: opts.quotaProbe,
|
|
13757
|
+
...opts.costTier !== void 0 ? { costTier: opts.costTier } : {},
|
|
13758
|
+
...tags.length ? { tags } : {}
|
|
13759
|
+
});
|
|
13760
|
+
const { workers } = readWorkersSection();
|
|
13761
|
+
console.log(ok$1(`provider ${name} added`));
|
|
13762
|
+
if (workers.active === name) console.log(` active: ${name} — workers ${workers.enabled ? "on" : "off"}`);
|
|
13763
|
+
for (const line of describeProviders(workers)) console.log(dim$1(` ${line}`));
|
|
13764
|
+
} catch (e) {
|
|
13765
|
+
fail$1(e);
|
|
13766
|
+
}
|
|
13767
|
+
});
|
|
13768
|
+
providersCmd.command("update <name>").description("Change cost tier and tags of a provider (routing constraints use these)").option("--cost-tier <1-5>", "Cost tier 1 (cheapest) … 5 (most expensive)", parseCostTier).option("--tags <tags>", "Capability tags, comma-separated (from: " + PROVIDER_TAGS.join(", ") + "); --tags '' clears", (v, acc) => [...acc, v], []).action((name, opts) => {
|
|
13769
|
+
try {
|
|
13770
|
+
if (opts.costTier === void 0 && opts.tags === void 0) throw new WorkersConfigError("nothing to update — pass --cost-tier and/or --tags");
|
|
13771
|
+
const tags = opts.tags === void 0 ? void 0 : collectTags(opts.tags);
|
|
13772
|
+
updateProvider(name, {
|
|
13773
|
+
...opts.costTier !== void 0 ? { costTier: opts.costTier } : {},
|
|
13774
|
+
...tags !== void 0 ? { tags } : {}
|
|
13775
|
+
});
|
|
13776
|
+
console.log(ok$1(`provider ${name} updated`));
|
|
13777
|
+
const { workers } = readWorkersSection();
|
|
13778
|
+
for (const line of describeProviders(workers)) console.log(dim$1(` ${line}`));
|
|
13779
|
+
} catch (e) {
|
|
13780
|
+
fail$1(e);
|
|
13781
|
+
}
|
|
13782
|
+
});
|
|
13783
|
+
providersCmd.command("remove <name>").description("Remove a provider and any classes pointing at it").action((name) => {
|
|
13784
|
+
try {
|
|
13785
|
+
removeProvider(name);
|
|
13786
|
+
console.log(ok$1(`provider ${name} removed`));
|
|
13787
|
+
} catch (e) {
|
|
13788
|
+
fail$1(e);
|
|
13789
|
+
}
|
|
13790
|
+
});
|
|
13791
|
+
providersCmd.command("use <name>").description("Make this provider the active one for runs without --provider/--class").action((name) => {
|
|
13792
|
+
try {
|
|
13793
|
+
useProvider(name);
|
|
13794
|
+
console.log(ok$1(`active provider: ${name}`));
|
|
13795
|
+
} catch (e) {
|
|
13796
|
+
fail$1(e);
|
|
13797
|
+
}
|
|
13798
|
+
});
|
|
13799
|
+
providersCmd.command("enable <name>").description("Enable a provider (also clears its cooldown)").action((name) => {
|
|
13800
|
+
try {
|
|
13801
|
+
setProviderEnabled(name, true);
|
|
13802
|
+
console.log(ok$1(`provider ${name} enabled`));
|
|
13803
|
+
} catch (e) {
|
|
13804
|
+
fail$1(e);
|
|
13805
|
+
}
|
|
13806
|
+
});
|
|
13807
|
+
providersCmd.command("disable <name>").description("Disable a provider (auto-routing skips it; --provider still works)").action((name) => {
|
|
13808
|
+
try {
|
|
13809
|
+
setProviderEnabled(name, false);
|
|
13810
|
+
console.log(ok$1(`provider ${name} disabled`));
|
|
13811
|
+
} catch (e) {
|
|
13812
|
+
fail$1(e);
|
|
13813
|
+
}
|
|
13814
|
+
});
|
|
13815
|
+
providersCmd.command("test [name]").description("One-word pong probe through a provider (default: the active one)").action(async (name) => {
|
|
13816
|
+
try {
|
|
13817
|
+
const { workers } = readWorkersSection();
|
|
13818
|
+
const pname = name ?? workers.active ?? "";
|
|
13819
|
+
if (!pname || !workers.providers[pname]) throw new WorkersConfigError(name ? `no provider named "${name}"` : "no active provider — name one explicitly");
|
|
13820
|
+
console.log(dim$1(` probing ${pname} (${workers.providers[pname].models.default}) …`));
|
|
13821
|
+
const r = await testProvider(pname, workers.providers[pname], workersLogDir(workers));
|
|
13822
|
+
const head = `${r.provider} ${r.model} ${(r.latencyMs / 1e3).toFixed(1)}s`;
|
|
13823
|
+
if (r.skipped) {
|
|
13824
|
+
console.log(dim$1(` ${head} ${r.skipped}`));
|
|
13825
|
+
console.log(` reply: ${r.result.slice(0, 200)}`);
|
|
13826
|
+
return;
|
|
13827
|
+
}
|
|
13828
|
+
console.log(r.ok ? ok$1(` ${head}`) : err(` ${head}`));
|
|
13829
|
+
console.log(` reply: ${r.result.slice(0, 200)}`);
|
|
13830
|
+
process.exitCode = r.ok ? 0 : 1;
|
|
13831
|
+
} catch (e) {
|
|
13832
|
+
fail$1(e);
|
|
13833
|
+
}
|
|
13834
|
+
});
|
|
13835
|
+
}
|
|
13836
|
+
function printClasses() {
|
|
13837
|
+
const { workers } = readWorkersSection();
|
|
13838
|
+
const entries = Object.entries(workers.classes);
|
|
13839
|
+
if (!entries.length) {
|
|
13840
|
+
console.log(dim$1(" no classes set — runs use the active provider (or auto routing)"));
|
|
13841
|
+
return;
|
|
13842
|
+
}
|
|
13843
|
+
for (const [cls, target] of entries) console.log(` ${cls.padEnd(12)} ${classTargetText(target)}`);
|
|
13844
|
+
}
|
|
13845
|
+
function registerClassSubcommands(classesCmd) {
|
|
13846
|
+
classesCmd.command("list").description("List classes and their targets (default action)").action(() => printClasses());
|
|
13847
|
+
classesCmd.command("set <class> [target]").description("Point a class at a provider (or provider/fast), or give only constraints:\nclasses set research --max-cost-tier 2 --require-tags long-context,reasoning").option("--provider <name>", "Pin the class to this provider (object form)").option("--mcp <names>", "MCP servers/sets for runs of this class (comma-separated)").option("--max-cost-tier <1-5>", "Auto-routing considers only providers up to this cost tier", parseCostTier).option("--require-tags <tags>", "Auto-routing needs these tags (comma-separated)", (v) => v.split(",").map((s) => s.trim()).filter(Boolean)).option("--order <providers>", "Per-class routing order overriding workers.routing.order (comma-separated)").action((cls, target, opts) => {
|
|
13848
|
+
try {
|
|
13849
|
+
const built = buildClassTarget(target, opts);
|
|
13850
|
+
setClass(cls, built);
|
|
13851
|
+
console.log(ok$1(`class ${cls} → ${classTargetText(built)}`));
|
|
13852
|
+
} catch (e) {
|
|
13853
|
+
fail$1(e);
|
|
13854
|
+
}
|
|
13855
|
+
});
|
|
13856
|
+
classesCmd.command("unset <class>").description("Remove a class (runs then use the active provider)").action((cls) => {
|
|
13857
|
+
try {
|
|
13858
|
+
unsetClass(cls);
|
|
13859
|
+
console.log(ok$1(`class ${cls} removed`));
|
|
13860
|
+
} catch (e) {
|
|
13861
|
+
fail$1(e);
|
|
13862
|
+
}
|
|
13863
|
+
});
|
|
13864
|
+
}
|
|
13865
|
+
/** `pai worker classes` — class → target assignment (roles is the old name). */
|
|
13866
|
+
function registerWorkerClassCommands(workerCmd) {
|
|
13867
|
+
registerClassSubcommands(workerCmd.command("classes").description("Classes: which provider serves draft / implement / review / …").action(() => printClasses()));
|
|
13868
|
+
registerClassSubcommands(workerCmd.command("roles", { hidden: true }).description("Alias of `classes` (roles was renamed to classes)").action(() => printClasses()));
|
|
13869
|
+
}
|
|
13870
|
+
|
|
13871
|
+
//#endregion
|
|
13872
|
+
//#region src/cli/commands/worker/index.ts
|
|
13873
|
+
function currentLogDir() {
|
|
13874
|
+
const { workers } = readWorkersSection();
|
|
13875
|
+
return workersLogDir(workers);
|
|
13876
|
+
}
|
|
13877
|
+
function fail(e) {
|
|
13878
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
13879
|
+
console.error(err("pai worker: ") + msg);
|
|
13880
|
+
process.exitCode = 1;
|
|
13881
|
+
}
|
|
13882
|
+
function registerWorkerCommands(workerCmd) {
|
|
13883
|
+
workerCmd.command("run").description("Run one claude-code worker through the configured provider.\nUnknown options are passed to claude verbatim (e.g. -p, --allowedTools);\n--output-format/--verbose are handled here.\n--chain draft,implement[,review] runs a spec-first pipeline;\n--agent <name> runs an agent definition from ~/.claude/agents.").allowUnknownOption(true).option("--provider <name>", "Provider to run on (default: active, else routing order)").option("--class <name>", "Use the provider of this class (draft, implement, review, research, spotcheck, simple, complex, image)").option("--role <name>", "Alias of --class (roles were renamed to classes)").option("--chain <stages>", "Comma-separated stage classes, e.g. draft,implement or draft,implement,review").option("--agent <name>", "Run the agent definition ~/.claude/agents/<name>.md on a worker").option("--model <model>", "Override the provider's model for this run").option("--label <text>", "Short task label shown in ps / follow / status line").option("--mcp <names>", "MCP servers/sets this worker may use (comma-separated; see `pai worker mcp`)").option("--no-pane", "Do not open a follow pane for this worker").argument("[args...]", "claude arguments, e.g. -p '<task>' --allowedTools 'Read,Edit,Bash'").action(async (args, opts) => {
|
|
13884
|
+
try {
|
|
13885
|
+
const className = opts.class ?? opts.role;
|
|
13886
|
+
let claudeArgs = args;
|
|
13887
|
+
let label = opts.label;
|
|
13888
|
+
let agentClass;
|
|
13889
|
+
if (opts.agent) {
|
|
13890
|
+
const def = loadAgent(opts.agent);
|
|
13891
|
+
claudeArgs = [...agentClaudeArgs(def), ...args];
|
|
13892
|
+
agentClass = modelToClass(def.model);
|
|
13893
|
+
if (!label) label = agentLabel(opts.agent, parseRunnerArgs(args).prompt);
|
|
13894
|
+
}
|
|
13895
|
+
if (opts.chain) {
|
|
13896
|
+
const brief = parseRunnerArgs(claudeArgs).prompt;
|
|
13897
|
+
if (!brief) throw new Error("--chain needs the task as -p '<brief>'");
|
|
13898
|
+
const rc = await runChain({
|
|
13899
|
+
stages: opts.chain.split(","),
|
|
13900
|
+
className: className ?? agentClass,
|
|
13901
|
+
providerFlag: opts.provider,
|
|
13902
|
+
modelFlag: opts.model,
|
|
13903
|
+
label,
|
|
13904
|
+
noPane: opts.pane === false,
|
|
13905
|
+
mcpFlag: opts.mcp,
|
|
13906
|
+
brief,
|
|
13907
|
+
claudeArgs
|
|
13908
|
+
});
|
|
13909
|
+
process.exitCode = rc;
|
|
13910
|
+
return;
|
|
13911
|
+
}
|
|
13912
|
+
const rc = await runWorker({
|
|
13913
|
+
providerFlag: opts.provider,
|
|
13914
|
+
className: className ?? agentClass,
|
|
13915
|
+
modelFlag: opts.model,
|
|
13916
|
+
label,
|
|
13917
|
+
mcpFlag: opts.mcp,
|
|
13918
|
+
noPane: opts.pane === false,
|
|
13919
|
+
claudeArgs
|
|
13920
|
+
});
|
|
13921
|
+
process.exitCode = rc;
|
|
13922
|
+
} catch (e) {
|
|
13923
|
+
fail(e);
|
|
13924
|
+
}
|
|
13925
|
+
});
|
|
13926
|
+
workerCmd.command("ps").description("List workers of this session (RUNNING + FINISHED); --all for every worker").option("--all", "Show workers of all sessions, not just this terminal's").action((opts) => {
|
|
13927
|
+
try {
|
|
13928
|
+
console.log(psOutput(currentLogDir(), opts.all === true));
|
|
13929
|
+
} catch (e) {
|
|
13930
|
+
fail(e);
|
|
13931
|
+
}
|
|
13932
|
+
});
|
|
13933
|
+
workerCmd.command("follow [id]").description("Live transcript of one worker, or of this session's running workers").option("--all", "Follow workers of all sessions").option("--auto-exit [secs]", "Exit secs after the workers end (pane mode; default from config)").action(async (id, opts) => {
|
|
13934
|
+
try {
|
|
13935
|
+
const { workers } = readWorkersSection();
|
|
13936
|
+
const autoExit = opts.autoExit === void 0 || opts.autoExit === false ? 0 : opts.autoExit === true ? workers.pane.autoExitSecs : Number(opts.autoExit);
|
|
13937
|
+
await followWorkers(currentLogDir(), id ?? null, opts.all === true, autoExit);
|
|
13938
|
+
} catch (e) {
|
|
13939
|
+
fail(e);
|
|
13940
|
+
}
|
|
13941
|
+
});
|
|
13942
|
+
workerCmd.command("replay <id>").description("Print the transcript of one finished or running worker").option("--tail <n>", "Only the last n rendered lines", parseIntArg).action((id, opts) => {
|
|
13943
|
+
try {
|
|
13944
|
+
console.log(replayOutput(currentLogDir(), id, process.stdout.isTTY === true, opts.tail));
|
|
13945
|
+
} catch (e) {
|
|
13946
|
+
fail(e);
|
|
13947
|
+
}
|
|
13948
|
+
});
|
|
13949
|
+
workerCmd.command("watch").description("ps refreshed every 2 seconds (plain `watch`, colors kept)").action(() => {
|
|
13950
|
+
const proc = spawn("watch", [
|
|
13951
|
+
"-n",
|
|
13952
|
+
"2",
|
|
13953
|
+
"pai worker ps --all"
|
|
13954
|
+
], { stdio: "inherit" });
|
|
13955
|
+
proc.on("error", (e) => fail(/* @__PURE__ */ new Error(`cannot run watch: ${e.message}`)));
|
|
13956
|
+
proc.on("close", (code) => {
|
|
13957
|
+
process.exitCode = code ?? 1;
|
|
13958
|
+
});
|
|
13959
|
+
});
|
|
13960
|
+
workerCmd.command("pane [id]").description("Open the follow pane for a worker (or one shared pane for this session)").option("--check", "Only report whether the pane is open, plus the profile file's path, font, and the hosting window's bounds").action(async (id, opts) => {
|
|
13961
|
+
try {
|
|
13962
|
+
const { workers } = readWorkersSection();
|
|
13963
|
+
const logDir = currentLogDir();
|
|
13964
|
+
const term = process.env.ITERM_SESSION_ID ?? "";
|
|
13965
|
+
if (id) {
|
|
13966
|
+
const msg = opts.check ? await checkPaneForWorker(id, workers.pane.fontSize, term) : await openPaneForWorker(logDir, workers, id, term);
|
|
13967
|
+
console.log(msg);
|
|
13968
|
+
} else console.log(await openFollowPane(logDir, workers, term, opts.check === true));
|
|
13969
|
+
} catch (e) {
|
|
13970
|
+
fail(e);
|
|
13971
|
+
}
|
|
13972
|
+
});
|
|
13973
|
+
workerCmd.command("log [what]").description("all = ledger, tail = last ledger lines, <id> = raw event stream, none = list").action((what) => {
|
|
13974
|
+
try {
|
|
13975
|
+
const logDir = currentLogDir();
|
|
13976
|
+
const ledger = ledgerPath(logDir);
|
|
13977
|
+
if (what === "all") {
|
|
13978
|
+
console.log(existsSync(ledger) ? readFileSync(ledger, "utf8") : dim$1("(no ledger yet)"));
|
|
13979
|
+
return;
|
|
13980
|
+
}
|
|
13981
|
+
if (what === "tail") {
|
|
13982
|
+
if (!existsSync(ledger)) {
|
|
13983
|
+
console.log(dim$1("(no ledger yet)"));
|
|
13984
|
+
return;
|
|
13985
|
+
}
|
|
13986
|
+
const lines = readFileSync(ledger, "utf8").trim().split("\n");
|
|
13987
|
+
console.log(lines.slice(-20).join("\n"));
|
|
13988
|
+
return;
|
|
13989
|
+
}
|
|
13990
|
+
if (what) {
|
|
13991
|
+
const path = eventsPath(logDir, what);
|
|
13992
|
+
if (!existsSync(path)) {
|
|
13993
|
+
fail(/* @__PURE__ */ new Error(`no event log for ${what}`));
|
|
13994
|
+
return;
|
|
13995
|
+
}
|
|
13996
|
+
const lines = readFileSync(path, "utf8").trim().split("\n");
|
|
13997
|
+
console.log(lines.slice(-30).join("\n"));
|
|
13998
|
+
return;
|
|
13999
|
+
}
|
|
14000
|
+
if (!existsSync(logDir)) {
|
|
14001
|
+
console.log(dim$1("(no workers yet)"));
|
|
14002
|
+
return;
|
|
14003
|
+
}
|
|
14004
|
+
for (const f of readdirSync(logDir).filter((x) => x.endsWith(".jsonl")).sort()) {
|
|
14005
|
+
const st = statSync(`${logDir}/${f}`);
|
|
14006
|
+
const mb = (st.size / 1024 / 1024).toFixed(1);
|
|
14007
|
+
console.log(` ${f.replace(".jsonl", "")} ${mb} MB ${st.mtime.toISOString().slice(0, 19).replace("T", " ")}`);
|
|
14008
|
+
}
|
|
14009
|
+
} catch (e) {
|
|
14010
|
+
fail(e);
|
|
14011
|
+
}
|
|
14012
|
+
});
|
|
14013
|
+
workerCmd.command("say <id> <text>").description("Send one message to a running worker (forwarded to its open stdin)").action(async (id, text) => {
|
|
14014
|
+
try {
|
|
14015
|
+
await sayToWorker(currentLogDir(), id, text);
|
|
14016
|
+
console.log("ok");
|
|
14017
|
+
} catch (e) {
|
|
14018
|
+
fail(e);
|
|
14019
|
+
}
|
|
14020
|
+
});
|
|
14021
|
+
workerCmd.command("resume <id> <text>").description("Continue a finished worker on the same provider: claude --resume <session>").option("--print-id", "Print the new worker id on its own line (pane follow handoff)").option("--no-pane", "Do not open a follow pane for the resumed worker").action(async (id, text, opts) => {
|
|
14022
|
+
try {
|
|
14023
|
+
const old = loadStatus(currentLogDir(), id);
|
|
14024
|
+
if (!old) {
|
|
14025
|
+
fail(/* @__PURE__ */ new Error(`no worker named "${id}"`));
|
|
14026
|
+
return;
|
|
14027
|
+
}
|
|
14028
|
+
if (!old.claudeSession) {
|
|
14029
|
+
fail(/* @__PURE__ */ new Error(`worker ${id} recorded no Claude session id — it predates resume support or ran through an engine that does not expose one`));
|
|
14030
|
+
return;
|
|
14031
|
+
}
|
|
14032
|
+
let newId = "";
|
|
14033
|
+
const rc = await runWorker({
|
|
14034
|
+
providerFlag: old.provider,
|
|
14035
|
+
modelFlag: old.model,
|
|
14036
|
+
label: `↩ ${old.label}`,
|
|
14037
|
+
noPane: opts.pane === false,
|
|
14038
|
+
claudeArgs: [
|
|
14039
|
+
"--resume",
|
|
14040
|
+
old.claudeSession,
|
|
14041
|
+
"-p",
|
|
14042
|
+
text
|
|
14043
|
+
],
|
|
14044
|
+
onWorkerStart: (wid) => {
|
|
14045
|
+
newId = wid;
|
|
14046
|
+
}
|
|
14047
|
+
});
|
|
14048
|
+
if (opts.printId === true && newId) console.log(newId);
|
|
14049
|
+
process.exitCode = rc;
|
|
14050
|
+
} catch (e) {
|
|
14051
|
+
fail(e);
|
|
14052
|
+
}
|
|
14053
|
+
});
|
|
14054
|
+
workerCmd.command("proxy [stop]").description("The local Anthropic↔OpenAI proxy (loopback only); started on demand by `run`,\n`proxy` starts/verifies it, `proxy stop` stops it again").option("--port <n>", `Port to listen on (default ${DEFAULT_PROXY_PORT})`, parseIntArg).action(async (stop, opts) => {
|
|
14055
|
+
try {
|
|
14056
|
+
const logDir = currentLogDir();
|
|
14057
|
+
if (stop === "stop") {
|
|
14058
|
+
console.log(stopProxy(logDir));
|
|
14059
|
+
return;
|
|
14060
|
+
}
|
|
14061
|
+
if (stop) {
|
|
14062
|
+
fail(/* @__PURE__ */ new Error(`unknown proxy argument "${stop}" (expected: stop)`));
|
|
14063
|
+
return;
|
|
14064
|
+
}
|
|
14065
|
+
const url = await ensureProxyRunning(opts.port ?? DEFAULT_PROXY_PORT, logDir);
|
|
14066
|
+
console.log(`proxy listening on ${url} — point openai providers at ${url}/<provider>`);
|
|
14067
|
+
} catch (e) {
|
|
14068
|
+
fail(e);
|
|
14069
|
+
}
|
|
14070
|
+
});
|
|
14071
|
+
workerCmd.command("mcp [list]").description("MCP servers workers may load via --mcp / roles, and the configured sets").action((what) => {
|
|
14072
|
+
if (what && what !== "list") {
|
|
14073
|
+
fail(/* @__PURE__ */ new Error(`unknown mcp argument "${what}" (expected: list)`));
|
|
14074
|
+
return;
|
|
14075
|
+
}
|
|
14076
|
+
try {
|
|
14077
|
+
const { workers } = readWorkersSection();
|
|
14078
|
+
console.log(describeMcp(workers));
|
|
14079
|
+
} catch (e) {
|
|
14080
|
+
fail(e);
|
|
14081
|
+
}
|
|
14082
|
+
});
|
|
14083
|
+
workerCmd.command("status-line [term] [cwd]").description("One-line worker summary for a status bar (empty when none in scope).\nCalled by statusline-command.sh with ITERM_SESSION_ID and the pane cwd.").action((term, cwd) => {
|
|
14084
|
+
try {
|
|
14085
|
+
const out = statusLineOutput(currentLogDir(), term ?? process.env.ITERM_SESSION_ID ?? "", cwd ?? process.cwd());
|
|
14086
|
+
if (out) console.log(out);
|
|
14087
|
+
} catch (e) {
|
|
14088
|
+
fail(e);
|
|
14089
|
+
}
|
|
14090
|
+
});
|
|
14091
|
+
workerCmd.command("on").description("Route Agent-tool subagents to workers (default when a provider exists)").action(() => {
|
|
14092
|
+
try {
|
|
14093
|
+
setWorkersEnabled(true);
|
|
14094
|
+
console.log("workers on — the Agent hook now denies and suggests `pai worker run`");
|
|
14095
|
+
} catch (e) {
|
|
14096
|
+
fail(e);
|
|
14097
|
+
}
|
|
14098
|
+
});
|
|
14099
|
+
workerCmd.command("off").description("Stop routing: Agent tool runs on Anthropic again").action(() => {
|
|
14100
|
+
try {
|
|
14101
|
+
setWorkersEnabled(false);
|
|
14102
|
+
console.log("workers off — Agent-tool subagents run on Anthropic");
|
|
14103
|
+
} catch (e) {
|
|
14104
|
+
fail(e);
|
|
14105
|
+
}
|
|
14106
|
+
});
|
|
14107
|
+
workerCmd.command("install").description("Migrate: Agent hook in settings.json, ~/.local/bin glm* shims, old script cleanup").action(() => {
|
|
14108
|
+
try {
|
|
14109
|
+
const r = installWorkers();
|
|
14110
|
+
for (const line of r.lines) console.log(` ${line}`);
|
|
14111
|
+
} catch (e) {
|
|
14112
|
+
fail(e);
|
|
14113
|
+
}
|
|
14114
|
+
});
|
|
14115
|
+
registerWorkerProviderCommands(workerCmd.command("providers").description("Providers: list, add, remove, use, enable, disable, test"));
|
|
14116
|
+
registerWorkerClassCommands(workerCmd);
|
|
14117
|
+
}
|
|
14118
|
+
function parseIntArg(v) {
|
|
14119
|
+
const n = parseInt(v, 10);
|
|
14120
|
+
return Number.isNaN(n) ? 0 : n;
|
|
14121
|
+
}
|
|
14122
|
+
|
|
13374
14123
|
//#endregion
|
|
13375
14124
|
//#region src/cli/commands/session-cleanup/types.ts
|
|
13376
14125
|
const TEMPLATE_INDICATORS = [
|
|
@@ -13735,7 +14484,7 @@ async function countVectorDbPaths(oldPaths) {
|
|
|
13735
14484
|
if (oldPaths.length === 0) return 0;
|
|
13736
14485
|
try {
|
|
13737
14486
|
const { loadConfig } = await import("./config-C_ErGddD.mjs");
|
|
13738
|
-
const { PostgresBackend } = await import("./postgres
|
|
14487
|
+
const { PostgresBackend } = await import("./postgres--BjPtLa0.mjs");
|
|
13739
14488
|
const config = loadConfig();
|
|
13740
14489
|
if (config.storageBackend !== "postgres") return 0;
|
|
13741
14490
|
const pgBackend = new PostgresBackend(config.postgres ?? {});
|
|
@@ -13756,7 +14505,7 @@ async function updateVectorDbPaths(moves) {
|
|
|
13756
14505
|
if (moves.length === 0) return 0;
|
|
13757
14506
|
try {
|
|
13758
14507
|
const { loadConfig } = await import("./config-C_ErGddD.mjs");
|
|
13759
|
-
const { PostgresBackend } = await import("./postgres
|
|
14508
|
+
const { PostgresBackend } = await import("./postgres--BjPtLa0.mjs");
|
|
13760
14509
|
const config = loadConfig();
|
|
13761
14510
|
if (config.storageBackend !== "postgres") return 0;
|
|
13762
14511
|
const pgBackend = new PostgresBackend(config.postgres ?? {});
|
|
@@ -15099,6 +15848,7 @@ Examples:
|
|
|
15099
15848
|
const sessionCmd = program.command("session").description("Session management: list, info, checkpoint, handover, cleanup, slug, tag, route.\nShort forms: `pai pause`, `pai end`, `pai resume <name>`.");
|
|
15100
15849
|
registerSessionCommands(sessionCmd, getDb);
|
|
15101
15850
|
registerSessionCleanupCommand(sessionCmd, getDb);
|
|
15851
|
+
registerWorkerCommands(program.command("worker").description("Run subagents on configured worker providers: run, ps, follow, replay,\npane, log, status-line, providers, roles, on, off, install"));
|
|
15102
15852
|
program.command("sessions").description("Alias for `pai` — show the unified deduped listing.").option("--all", "Show all entries including cold / zero-session / archived projects").option("-n, --n <count>", "Max candidates for history search", "20").action(async (opts) => {
|
|
15103
15853
|
await cmdMain(getDb(), void 0, void 0, opts);
|
|
15104
15854
|
});
|
|
@@ -15280,4 +16030,4 @@ claude() {
|
|
|
15280
16030
|
|
|
15281
16031
|
//#endregion
|
|
15282
16032
|
export { drainStdio as n, buildProgram as t };
|
|
15283
|
-
//# sourceMappingURL=program-
|
|
16033
|
+
//# sourceMappingURL=program-Y0hAiVy8.mjs.map
|