@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
|
@@ -0,0 +1,518 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/workers/config.ts
|
|
4
|
+
import { homedir as homedir2 } from "node:os";
|
|
5
|
+
import { join as join2 } from "node:path";
|
|
6
|
+
|
|
7
|
+
// src/config/json-store.ts
|
|
8
|
+
import {
|
|
9
|
+
existsSync,
|
|
10
|
+
readFileSync,
|
|
11
|
+
writeFileSync,
|
|
12
|
+
copyFileSync,
|
|
13
|
+
renameSync,
|
|
14
|
+
unlinkSync,
|
|
15
|
+
mkdirSync
|
|
16
|
+
} from "node:fs";
|
|
17
|
+
function readJsonStrict(path, label = path) {
|
|
18
|
+
if (!existsSync(path)) return {};
|
|
19
|
+
let raw;
|
|
20
|
+
try {
|
|
21
|
+
raw = readFileSync(path, "utf8");
|
|
22
|
+
} catch (e) {
|
|
23
|
+
throw new Error(
|
|
24
|
+
`Could not read ${label}: ${e instanceof Error ? e.message : String(e)}
|
|
25
|
+
Refusing to continue \u2014 writing now would replace its contents with ours alone.`
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
try {
|
|
29
|
+
return JSON.parse(raw);
|
|
30
|
+
} catch (e) {
|
|
31
|
+
throw new Error(
|
|
32
|
+
`${label} exists but is not valid JSON: ${e instanceof Error ? e.message : String(e)}
|
|
33
|
+
Refusing to continue \u2014 overwriting it would destroy whatever it holds.
|
|
34
|
+
Repair the file, or move it aside and re-run this command.`
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// src/daemon/config.ts
|
|
40
|
+
import { homedir, userInfo } from "node:os";
|
|
41
|
+
import { join } from "node:path";
|
|
42
|
+
|
|
43
|
+
// src/notifications/types.ts
|
|
44
|
+
var DEFAULT_ROUTING = {
|
|
45
|
+
error: ["whatsapp", "macos", "ntfy", "cli"],
|
|
46
|
+
completion: ["whatsapp", "macos", "ntfy", "cli"],
|
|
47
|
+
info: ["cli"],
|
|
48
|
+
progress: ["cli"],
|
|
49
|
+
debug: []
|
|
50
|
+
};
|
|
51
|
+
var DEFAULT_CHANNELS = {
|
|
52
|
+
ntfy: {
|
|
53
|
+
enabled: false,
|
|
54
|
+
url: void 0,
|
|
55
|
+
priority: "default"
|
|
56
|
+
},
|
|
57
|
+
whatsapp: {
|
|
58
|
+
enabled: true,
|
|
59
|
+
recipient: void 0
|
|
60
|
+
},
|
|
61
|
+
macos: {
|
|
62
|
+
enabled: true
|
|
63
|
+
},
|
|
64
|
+
voice: {
|
|
65
|
+
enabled: false,
|
|
66
|
+
voiceName: "bm_george"
|
|
67
|
+
},
|
|
68
|
+
cli: {
|
|
69
|
+
enabled: true
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
var DEFAULT_NOTIFICATION_CONFIG = {
|
|
73
|
+
mode: "auto",
|
|
74
|
+
channels: DEFAULT_CHANNELS,
|
|
75
|
+
routing: DEFAULT_ROUTING
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
// src/tasks/types.ts
|
|
79
|
+
var DEFAULT_TASK_CONFIG = {
|
|
80
|
+
enabled: false,
|
|
81
|
+
providers: {
|
|
82
|
+
todoist: {
|
|
83
|
+
enabled: false
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
autoDispatch: false
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
// src/runtime-paths.ts
|
|
90
|
+
function paiSocketPath() {
|
|
91
|
+
return process.env.PAI_SOCKET_PATH ?? "/tmp/pai.sock";
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// src/daemon/config.ts
|
|
95
|
+
function perUserDbName() {
|
|
96
|
+
const username = userInfo().username;
|
|
97
|
+
const safe = username.replace(/[^a-zA-Z0-9_]/g, "_").toLowerCase();
|
|
98
|
+
return `pai_${safe}`;
|
|
99
|
+
}
|
|
100
|
+
function perUserConnectionString() {
|
|
101
|
+
const db = perUserDbName();
|
|
102
|
+
return `postgresql://pai:pai@localhost:5432/${db}`;
|
|
103
|
+
}
|
|
104
|
+
var DEFAULTS = {
|
|
105
|
+
socketPath: paiSocketPath(),
|
|
106
|
+
indexIntervalSecs: 300,
|
|
107
|
+
embedIntervalSecs: 600,
|
|
108
|
+
embedOnStartup: false,
|
|
109
|
+
storageBackend: "sqlite",
|
|
110
|
+
postgres: {
|
|
111
|
+
connectionString: perUserConnectionString(),
|
|
112
|
+
maxConnections: 5,
|
|
113
|
+
connectionTimeoutMs: 5e3
|
|
114
|
+
},
|
|
115
|
+
embeddingModel: "Snowflake/snowflake-arctic-embed-m-v1.5",
|
|
116
|
+
logLevel: "info",
|
|
117
|
+
notifications: DEFAULT_NOTIFICATION_CONFIG,
|
|
118
|
+
tasks: DEFAULT_TASK_CONFIG,
|
|
119
|
+
// Deliberately empty. An install must not guess who the user is: a wrong
|
|
120
|
+
// guess here is an address that can be mailed without review.
|
|
121
|
+
identity: { selfEmails: [] },
|
|
122
|
+
search: {
|
|
123
|
+
mode: "keyword",
|
|
124
|
+
rerank: true,
|
|
125
|
+
recencyBoostDays: 90,
|
|
126
|
+
defaultLimit: 10,
|
|
127
|
+
snippetLength: 200
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
var CONFIG_DIR = join(homedir(), ".config", "pai");
|
|
131
|
+
var CONFIG_FILE = join(CONFIG_DIR, "config.json");
|
|
132
|
+
|
|
133
|
+
// src/workers/config.ts
|
|
134
|
+
var PROVIDER_TAGS = [
|
|
135
|
+
"code",
|
|
136
|
+
"vision",
|
|
137
|
+
"image-gen",
|
|
138
|
+
"long-context",
|
|
139
|
+
"fast",
|
|
140
|
+
"reasoning"
|
|
141
|
+
];
|
|
142
|
+
var DEFAULT_LOG_DIR = "~/.claude/logs/workers";
|
|
143
|
+
var DEFAULT_PANE = {
|
|
144
|
+
enabled: true,
|
|
145
|
+
fontSize: 13,
|
|
146
|
+
autoExitSecs: 60
|
|
147
|
+
};
|
|
148
|
+
var DEFAULT_ROUTING2 = {
|
|
149
|
+
order: [],
|
|
150
|
+
cooldownMinutes: 30,
|
|
151
|
+
retryOnQuota: true
|
|
152
|
+
};
|
|
153
|
+
function defaultWorkersConfig() {
|
|
154
|
+
return {
|
|
155
|
+
enabled: false,
|
|
156
|
+
active: null,
|
|
157
|
+
providers: {},
|
|
158
|
+
classes: {},
|
|
159
|
+
mcpSets: {},
|
|
160
|
+
pane: { ...DEFAULT_PANE },
|
|
161
|
+
logDir: DEFAULT_LOG_DIR,
|
|
162
|
+
routing: { ...DEFAULT_ROUTING2, order: [] }
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
var WorkersConfigError = class extends Error {
|
|
166
|
+
};
|
|
167
|
+
function bad(path, why) {
|
|
168
|
+
throw new WorkersConfigError(`workers${path}: ${why}`);
|
|
169
|
+
}
|
|
170
|
+
function str(v) {
|
|
171
|
+
return typeof v === "string" ? v : "";
|
|
172
|
+
}
|
|
173
|
+
function parseProvider(name, raw) {
|
|
174
|
+
if (typeof raw !== "object" || raw === null) bad(`.providers.${name}`, "must be an object");
|
|
175
|
+
const p = raw;
|
|
176
|
+
const protocol = p.protocol === void 0 ? "anthropic" : str(p.protocol);
|
|
177
|
+
if (protocol !== "anthropic" && protocol !== "openai") {
|
|
178
|
+
bad(`.providers.${name}.protocol`, `"${str(p.protocol)}" is neither "anthropic" nor "openai"`);
|
|
179
|
+
}
|
|
180
|
+
const engine = p.engine === void 0 ? "claude" : str(p.engine);
|
|
181
|
+
if (engine !== "claude" && engine !== "codex") {
|
|
182
|
+
bad(`.providers.${name}.engine`, `"${str(p.engine)}" is neither "claude" nor "codex"`);
|
|
183
|
+
}
|
|
184
|
+
const baseUrl = str(p.baseUrl);
|
|
185
|
+
if (!baseUrl && protocol !== "openai") bad(`.providers.${name}.baseUrl`, "is required");
|
|
186
|
+
const keyFile = p.keyFile === void 0 || p.keyFile === null || str(p.keyFile) === "" ? null : str(p.keyFile);
|
|
187
|
+
const modelsRaw = p.models === void 0 ? {} : p.models;
|
|
188
|
+
if (typeof modelsRaw !== "object" || modelsRaw === null) {
|
|
189
|
+
bad(`.providers.${name}.models`, "must be an object");
|
|
190
|
+
}
|
|
191
|
+
const m = modelsRaw;
|
|
192
|
+
const defaultModel = str(m.default);
|
|
193
|
+
if (!defaultModel) bad(`.providers.${name}.models.default`, "is required");
|
|
194
|
+
const fast = m.fast === void 0 ? void 0 : str(m.fast);
|
|
195
|
+
const env = {};
|
|
196
|
+
if (p.env !== void 0) {
|
|
197
|
+
if (typeof p.env !== "object" || p.env === null || Array.isArray(p.env)) {
|
|
198
|
+
bad(`.providers.${name}.env`, "must be an object of string values");
|
|
199
|
+
}
|
|
200
|
+
for (const [k, v] of Object.entries(p.env)) {
|
|
201
|
+
if (typeof v !== "string") bad(`.providers.${name}.env.${k}`, "must be a string");
|
|
202
|
+
env[k] = v;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
const quotaSkipAt = p.quotaSkipAt === void 0 ? void 0 : p.quotaSkipAt;
|
|
206
|
+
if (quotaSkipAt !== void 0) {
|
|
207
|
+
if (typeof quotaSkipAt !== "number" || quotaSkipAt < 0 || quotaSkipAt > 100) {
|
|
208
|
+
bad(`.providers.${name}.quotaSkipAt`, "must be a number between 0 and 100");
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
const contextWindow = p.contextWindow === void 0 ? void 0 : p.contextWindow;
|
|
212
|
+
if (contextWindow !== void 0) {
|
|
213
|
+
if (typeof contextWindow !== "number" || contextWindow <= 0) {
|
|
214
|
+
bad(`.providers.${name}.contextWindow`, "must be a positive number of tokens");
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
const upstreamUrl = str(p.upstreamUrl);
|
|
218
|
+
if (protocol === "openai" && !upstreamUrl) {
|
|
219
|
+
bad(`.providers.${name}.upstreamUrl`, `is required for protocol "openai" (the Chat Completions base, e.g. "https://api.openai.com/v1")`);
|
|
220
|
+
}
|
|
221
|
+
const costTier = p.costTier === void 0 ? void 0 : p.costTier;
|
|
222
|
+
if (costTier !== void 0) {
|
|
223
|
+
if (typeof costTier !== "number" || !Number.isInteger(costTier) || costTier < 1 || costTier > 5) {
|
|
224
|
+
bad(`.providers.${name}.costTier`, "must be an integer 1 (cheapest) \u2026 5 (most expensive)");
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
let tags;
|
|
228
|
+
if (p.tags !== void 0) {
|
|
229
|
+
if (!Array.isArray(p.tags) || p.tags.some((x) => typeof x !== "string")) {
|
|
230
|
+
bad(`.providers.${name}.tags`, `must be an array of tags from: ${PROVIDER_TAGS.join(", ")}`);
|
|
231
|
+
}
|
|
232
|
+
for (const t of p.tags) {
|
|
233
|
+
if (!PROVIDER_TAGS.includes(t)) {
|
|
234
|
+
bad(`.providers.${name}.tags`, `"${t}" is not a tag (from: ${PROVIDER_TAGS.join(", ")})`);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
tags = p.tags;
|
|
238
|
+
}
|
|
239
|
+
return {
|
|
240
|
+
enabled: p.enabled === void 0 ? true : p.enabled === true,
|
|
241
|
+
protocol,
|
|
242
|
+
baseUrl,
|
|
243
|
+
keyFile,
|
|
244
|
+
models: fast ? { default: defaultModel, fast } : { default: defaultModel },
|
|
245
|
+
env,
|
|
246
|
+
...str(p.note) ? { note: str(p.note) } : {},
|
|
247
|
+
...upstreamUrl ? { upstreamUrl } : {},
|
|
248
|
+
...engine !== "claude" ? { engine } : {},
|
|
249
|
+
...str(p.quotaProbe) ? { quotaProbe: str(p.quotaProbe) } : {},
|
|
250
|
+
...quotaSkipAt !== void 0 ? { quotaSkipAt } : {},
|
|
251
|
+
...contextWindow !== void 0 ? { contextWindow } : {},
|
|
252
|
+
...costTier !== void 0 ? { costTier } : {},
|
|
253
|
+
...tags ? { tags } : {}
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
function parseWorkersConfig(raw) {
|
|
257
|
+
const d = defaultWorkersConfig();
|
|
258
|
+
if (raw === void 0 || raw === null) return d;
|
|
259
|
+
if (typeof raw !== "object" || Array.isArray(raw)) {
|
|
260
|
+
bad("", "section must be an object");
|
|
261
|
+
}
|
|
262
|
+
const w = raw;
|
|
263
|
+
const providers = {};
|
|
264
|
+
if (w.providers !== void 0) {
|
|
265
|
+
if (typeof w.providers !== "object" || w.providers === null || Array.isArray(w.providers)) {
|
|
266
|
+
bad(".providers", "must be an object keyed by provider name");
|
|
267
|
+
}
|
|
268
|
+
for (const [name, p] of Object.entries(w.providers)) {
|
|
269
|
+
providers[name] = parseProvider(name, p);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
const classes = {};
|
|
273
|
+
const classesRaw = w.classes !== void 0 ? w.classes : w.roles;
|
|
274
|
+
if (classesRaw !== void 0) {
|
|
275
|
+
if (typeof classesRaw !== "object" || classesRaw === null || Array.isArray(classesRaw)) {
|
|
276
|
+
bad(w.classes !== void 0 ? ".classes" : ".roles", "must be an object of class \u2192 provider[/alias] or {provider, mcp, maxCostTier, requireTags, order}");
|
|
277
|
+
}
|
|
278
|
+
for (const [cls, target] of Object.entries(classesRaw)) {
|
|
279
|
+
if (typeof target === "object" && target !== null && !Array.isArray(target)) {
|
|
280
|
+
const o = target;
|
|
281
|
+
const provider = str(o.provider);
|
|
282
|
+
if (o.provider !== void 0 && (!provider || provider.includes(" "))) {
|
|
283
|
+
bad(`.classes.${cls}.provider`, `invalid provider "${provider}"`);
|
|
284
|
+
}
|
|
285
|
+
let mcp;
|
|
286
|
+
if (o.mcp !== void 0) {
|
|
287
|
+
if (!Array.isArray(o.mcp) || o.mcp.some((x) => typeof x !== "string")) {
|
|
288
|
+
bad(`.classes.${cls}.mcp`, "must be an array of MCP server or set names");
|
|
289
|
+
}
|
|
290
|
+
mcp = o.mcp;
|
|
291
|
+
}
|
|
292
|
+
let maxCostTier;
|
|
293
|
+
if (o.maxCostTier !== void 0) {
|
|
294
|
+
if (typeof o.maxCostTier !== "number" || !Number.isInteger(o.maxCostTier) || o.maxCostTier < 1 || o.maxCostTier > 5) {
|
|
295
|
+
bad(`.classes.${cls}.maxCostTier`, "must be an integer 1 \u2026 5");
|
|
296
|
+
}
|
|
297
|
+
maxCostTier = o.maxCostTier;
|
|
298
|
+
}
|
|
299
|
+
let requireTags;
|
|
300
|
+
if (o.requireTags !== void 0) {
|
|
301
|
+
if (!Array.isArray(o.requireTags) || o.requireTags.some((x) => typeof x !== "string")) {
|
|
302
|
+
bad(`.classes.${cls}.requireTags`, `must be an array of tags from: ${PROVIDER_TAGS.join(", ")}`);
|
|
303
|
+
}
|
|
304
|
+
for (const t of o.requireTags) {
|
|
305
|
+
if (!PROVIDER_TAGS.includes(t)) {
|
|
306
|
+
bad(`.classes.${cls}.requireTags`, `"${t}" is not a tag (from: ${PROVIDER_TAGS.join(", ")})`);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
requireTags = o.requireTags;
|
|
310
|
+
}
|
|
311
|
+
let order;
|
|
312
|
+
if (o.order !== void 0) {
|
|
313
|
+
if (!Array.isArray(o.order) || o.order.some((x) => typeof x !== "string")) {
|
|
314
|
+
bad(`.classes.${cls}.order`, "must be an array of provider names");
|
|
315
|
+
}
|
|
316
|
+
order = o.order;
|
|
317
|
+
}
|
|
318
|
+
const obj = {
|
|
319
|
+
...provider ? { provider } : {},
|
|
320
|
+
...mcp ? { mcp } : {},
|
|
321
|
+
...maxCostTier !== void 0 ? { maxCostTier } : {},
|
|
322
|
+
...requireTags ? { requireTags } : {},
|
|
323
|
+
...order ? { order } : {}
|
|
324
|
+
};
|
|
325
|
+
classes[cls] = Object.keys(obj).length ? obj : {};
|
|
326
|
+
} else {
|
|
327
|
+
const t = str(target);
|
|
328
|
+
if (!t || t.includes(" ")) bad(`.classes.${cls}`, `invalid target "${t}"`);
|
|
329
|
+
classes[cls] = t;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
const mcpSets = {};
|
|
334
|
+
if (w.mcpSets !== void 0) {
|
|
335
|
+
if (typeof w.mcpSets !== "object" || w.mcpSets === null || Array.isArray(w.mcpSets)) {
|
|
336
|
+
bad(".mcpSets", "must be an object of set name \u2192 [server names]");
|
|
337
|
+
}
|
|
338
|
+
for (const [setName, servers] of Object.entries(w.mcpSets)) {
|
|
339
|
+
if (!Array.isArray(servers) || servers.some((x) => typeof x !== "string")) {
|
|
340
|
+
bad(`.mcpSets.${setName}`, "must be an array of MCP server names");
|
|
341
|
+
}
|
|
342
|
+
mcpSets[setName] = servers;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
let pane = { ...DEFAULT_PANE };
|
|
346
|
+
if (w.pane !== void 0) {
|
|
347
|
+
if (typeof w.pane !== "object" || w.pane === null) bad(".pane", "must be an object");
|
|
348
|
+
const pc = w.pane;
|
|
349
|
+
if (pc.enabled !== void 0 && typeof pc.enabled !== "boolean") bad(".pane.enabled", "must be boolean");
|
|
350
|
+
if (pc.fontSize !== void 0 && (typeof pc.fontSize !== "number" || pc.fontSize <= 0)) {
|
|
351
|
+
bad(".pane.fontSize", "must be a positive number of points");
|
|
352
|
+
}
|
|
353
|
+
if (pc.autoExitSecs !== void 0 && typeof pc.autoExitSecs !== "number") {
|
|
354
|
+
bad(".pane.autoExitSecs", "must be a number");
|
|
355
|
+
}
|
|
356
|
+
pane = {
|
|
357
|
+
enabled: pc.enabled === void 0 ? DEFAULT_PANE.enabled : pc.enabled === true,
|
|
358
|
+
fontSize: pc.fontSize === void 0 ? DEFAULT_PANE.fontSize : pc.fontSize,
|
|
359
|
+
autoExitSecs: pc.autoExitSecs === void 0 ? DEFAULT_PANE.autoExitSecs : pc.autoExitSecs
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
let routing = { ...DEFAULT_ROUTING2, order: [] };
|
|
363
|
+
if (w.routing !== void 0) {
|
|
364
|
+
if (typeof w.routing !== "object" || w.routing === null) bad(".routing", "must be an object");
|
|
365
|
+
const r = w.routing;
|
|
366
|
+
if (r.order !== void 0) {
|
|
367
|
+
if (!Array.isArray(r.order) || r.order.some((x) => typeof x !== "string")) {
|
|
368
|
+
bad(".routing.order", "must be an array of provider names");
|
|
369
|
+
}
|
|
370
|
+
routing.order = r.order;
|
|
371
|
+
}
|
|
372
|
+
if (r.cooldownMinutes !== void 0 && typeof r.cooldownMinutes !== "number") {
|
|
373
|
+
bad(".routing.cooldownMinutes", "must be a number");
|
|
374
|
+
}
|
|
375
|
+
if (r.retryOnQuota !== void 0 && typeof r.retryOnQuota !== "boolean") {
|
|
376
|
+
bad(".routing.retryOnQuota", "must be boolean");
|
|
377
|
+
}
|
|
378
|
+
routing = {
|
|
379
|
+
order: routing.order,
|
|
380
|
+
cooldownMinutes: r.cooldownMinutes === void 0 ? DEFAULT_ROUTING2.cooldownMinutes : r.cooldownMinutes,
|
|
381
|
+
retryOnQuota: r.retryOnQuota === void 0 ? DEFAULT_ROUTING2.retryOnQuota : r.retryOnQuota === true
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
const active = w.active === void 0 || w.active === null ? null : str(w.active);
|
|
385
|
+
if (active !== null && active !== "auto" && !(active in providers)) {
|
|
386
|
+
}
|
|
387
|
+
return {
|
|
388
|
+
enabled: w.enabled === void 0 ? d.enabled : w.enabled === true,
|
|
389
|
+
active,
|
|
390
|
+
providers,
|
|
391
|
+
classes,
|
|
392
|
+
mcpSets,
|
|
393
|
+
pane,
|
|
394
|
+
logDir: str(w.logDir) || d.logDir,
|
|
395
|
+
routing
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
function readWorkersSection() {
|
|
399
|
+
const raw = readJsonStrict(CONFIG_FILE, "~/.config/pai/config.json");
|
|
400
|
+
return { raw, workers: parseWorkersConfig(raw.workers) };
|
|
401
|
+
}
|
|
402
|
+
function expandHome(p) {
|
|
403
|
+
if (p === "~" || p.startsWith("~/")) return join2(homedir2(), p.slice(1));
|
|
404
|
+
return p;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// src/workers/paths.ts
|
|
408
|
+
import { join as join3 } from "node:path";
|
|
409
|
+
function workersLogDir(config) {
|
|
410
|
+
return expandHome(config.logDir);
|
|
411
|
+
}
|
|
412
|
+
function ledgerPath(logDir) {
|
|
413
|
+
return join3(logDir, "ledger.log");
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// src/workers/ledger.ts
|
|
417
|
+
import { appendFileSync, existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2 } from "node:fs";
|
|
418
|
+
import { dirname } from "node:path";
|
|
419
|
+
function ledgerStamp(d = /* @__PURE__ */ new Date()) {
|
|
420
|
+
const p = (n) => String(n).padStart(2, "0");
|
|
421
|
+
return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}:${p(d.getSeconds())}`;
|
|
422
|
+
}
|
|
423
|
+
function appendLedger(path, event, kv, now = /* @__PURE__ */ new Date()) {
|
|
424
|
+
const parts = [];
|
|
425
|
+
for (const [k, v] of Object.entries(kv)) {
|
|
426
|
+
if (v === void 0 || v === null) continue;
|
|
427
|
+
parts.push(`${k}=${String(v).replace(/\s+/g, " ")}`);
|
|
428
|
+
}
|
|
429
|
+
const dir = dirname(path);
|
|
430
|
+
if (!existsSync2(dir)) mkdirSync2(dir, { recursive: true });
|
|
431
|
+
const tail = parts.length ? " " + parts.join(" ") : "";
|
|
432
|
+
appendFileSync(path, `${ledgerStamp(now)} ${event}${tail}
|
|
433
|
+
`, "utf8");
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// src/hooks/ts/pre-tool-use/route-agents-to-worker.ts
|
|
437
|
+
var ALLOW = JSON.stringify({ hookSpecificOutput: { hookEventName: "PreToolUse", permissionDecision: "allow" } });
|
|
438
|
+
function deny(reason) {
|
|
439
|
+
return JSON.stringify({
|
|
440
|
+
hookSpecificOutput: {
|
|
441
|
+
hookEventName: "PreToolUse",
|
|
442
|
+
permissionDecision: "deny",
|
|
443
|
+
permissionDecisionReason: reason
|
|
444
|
+
}
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
function shortLabel(input) {
|
|
448
|
+
const desc = typeof input.tool_input === "object" && input.tool_input !== null ? input.tool_input.description ?? "" : "";
|
|
449
|
+
return desc.replace(/\s+/g, " ").trim().slice(0, 60);
|
|
450
|
+
}
|
|
451
|
+
async function main() {
|
|
452
|
+
let text = "";
|
|
453
|
+
try {
|
|
454
|
+
for await (const chunk of process.stdin) text += chunk;
|
|
455
|
+
} catch {
|
|
456
|
+
process.stdout.write(ALLOW);
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
if (!text.trim()) {
|
|
460
|
+
process.stdout.write(ALLOW);
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
let input;
|
|
464
|
+
try {
|
|
465
|
+
input = JSON.parse(text);
|
|
466
|
+
} catch {
|
|
467
|
+
process.stdout.write(ALLOW);
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
if (input.tool_name && input.tool_name !== "Agent") {
|
|
471
|
+
process.stdout.write(ALLOW);
|
|
472
|
+
return;
|
|
473
|
+
}
|
|
474
|
+
const label = shortLabel(input);
|
|
475
|
+
const cwd = input.cwd ?? process.cwd();
|
|
476
|
+
const ledger = (event) => {
|
|
477
|
+
try {
|
|
478
|
+
appendLedger(ledgerPath(workersLogDir(readWorkersSection().workers)), event, {
|
|
479
|
+
cwd,
|
|
480
|
+
...label ? { label } : {}
|
|
481
|
+
});
|
|
482
|
+
} catch {
|
|
483
|
+
}
|
|
484
|
+
};
|
|
485
|
+
if (process.env.ALLOW_ANTHROPIC_AGENTS === "1") {
|
|
486
|
+
ledger("ALLOWED-ANTHROPIC-AGENT");
|
|
487
|
+
process.stdout.write(ALLOW);
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
let workers;
|
|
491
|
+
try {
|
|
492
|
+
workers = readWorkersSection().workers;
|
|
493
|
+
} catch {
|
|
494
|
+
process.stdout.write(ALLOW);
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
497
|
+
if (!workers.enabled || Object.keys(workers.providers).length === 0) {
|
|
498
|
+
process.stdout.write(ALLOW);
|
|
499
|
+
return;
|
|
500
|
+
}
|
|
501
|
+
ledger("DENIED-ANTHROPIC-AGENT");
|
|
502
|
+
process.stdout.write(
|
|
503
|
+
deny(
|
|
504
|
+
`Agent tool is disabled: subagents run on the configured worker provider, not Anthropic. Delegate with Bash instead, in the background:
|
|
505
|
+
|
|
506
|
+
pai worker run --label "${label || "task"}" --role research -p '<full, self-contained task spec>' --allowedTools 'Read,Edit,Write,Bash,Grep,Glob' --output-format json
|
|
507
|
+
|
|
508
|
+
- Run it with run_in_background: true and always with a timeout.
|
|
509
|
+
- Use --role spotcheck (or implement) as the task demands.
|
|
510
|
+
- Web research: add WebSearch,WebFetch to --allowedTools.
|
|
511
|
+
- The answer is in the \`result\` field of the JSON it prints. Review the diff yourself.
|
|
512
|
+
- pai worker ps lists running workers; pai worker follow <id> shows one live.
|
|
513
|
+
Keep only orchestration, review and synthesis in this session. To run subagents on Anthropic for one session: start it with ALLOW_ANTHROPIC_AGENTS=1.`
|
|
514
|
+
)
|
|
515
|
+
);
|
|
516
|
+
}
|
|
517
|
+
main().catch(() => process.stdout.write(ALLOW));
|
|
518
|
+
//# sourceMappingURL=route-agents-to-worker.mjs.map
|