@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.
Files changed (157) hide show
  1. package/dist/{auto-route-Byf8ENXj.mjs → auto-route-DM7GhJ8y.mjs} +2 -2
  2. package/dist/{auto-route-Byf8ENXj.mjs.map → auto-route-DM7GhJ8y.mjs.map} +1 -1
  3. package/dist/cli/index.mjs +5 -4
  4. package/dist/cli/index.mjs.map +1 -1
  5. package/dist/cli/program.d.mts.map +1 -1
  6. package/dist/cli/program.mjs +5 -4
  7. package/dist/{clusters-wZgTCYCB.mjs → clusters-Do4tEGyc.mjs} +1 -1
  8. package/dist/{clusters-wZgTCYCB.mjs.map → clusters-Do4tEGyc.mjs.map} +1 -1
  9. package/dist/{context-handover-cache-PtNvj_8D.mjs → context-handover-cache-BpUojjsi.mjs} +2 -2
  10. package/dist/{context-handover-cache-PtNvj_8D.mjs.map → context-handover-cache-BpUojjsi.mjs.map} +1 -1
  11. package/dist/daemon/index.mjs +8 -8
  12. package/dist/{daemon-BZ93KRBo.mjs → daemon-CGg1VCbA.mjs} +25 -25
  13. package/dist/{daemon-BZ93KRBo.mjs.map → daemon-CGg1VCbA.mjs.map} +1 -1
  14. package/dist/daemon-DsGGiIJM.mjs +20 -0
  15. package/dist/daemon-mcp/index.mjs +549 -1
  16. package/dist/daemon-mcp/index.mjs.map +1 -1
  17. package/dist/{detector--Gg5JRN5.mjs → detector-CMap-9vw.mjs} +1 -1
  18. package/dist/{detector--Gg5JRN5.mjs.map → detector-CMap-9vw.mjs.map} +1 -1
  19. package/dist/detector-DtLExmHN.mjs +5 -0
  20. package/dist/factory-BXzqRYVZ.mjs +3 -0
  21. package/dist/{factory-Bsp7xOpO.mjs → factory-DD2T33C9.mjs} +5 -5
  22. package/dist/{factory-Bsp7xOpO.mjs.map → factory-DD2T33C9.mjs.map} +1 -1
  23. package/dist/hooks/context-compression-hook.mjs +164 -14
  24. package/dist/hooks/context-compression-hook.mjs.map +4 -4
  25. package/dist/hooks/initialize-session.mjs +6 -0
  26. package/dist/hooks/initialize-session.mjs.map +3 -3
  27. package/dist/hooks/load-core-context.mjs +6 -0
  28. package/dist/hooks/load-core-context.mjs.map +3 -3
  29. package/dist/hooks/load-project-context.mjs +6 -0
  30. package/dist/hooks/load-project-context.mjs.map +3 -3
  31. package/dist/hooks/post-compact-inject.mjs +6 -0
  32. package/dist/hooks/post-compact-inject.mjs.map +3 -3
  33. package/dist/hooks/route-agents-to-worker.mjs +518 -0
  34. package/dist/hooks/route-agents-to-worker.mjs.map +7 -0
  35. package/dist/hooks/status-line.mjs +400 -0
  36. package/dist/hooks/status-line.mjs.map +7 -0
  37. package/dist/hooks/stop-hook.mjs +6 -0
  38. package/dist/hooks/stop-hook.mjs.map +3 -3
  39. package/dist/hooks/subagent-stop-hook.mjs +6 -0
  40. package/dist/hooks/subagent-stop-hook.mjs.map +3 -3
  41. package/dist/hooks/sync-todo-to-md.mjs +6 -0
  42. package/dist/hooks/sync-todo-to-md.mjs.map +3 -3
  43. package/dist/hooks/update-tab-on-action.mjs +6 -0
  44. package/dist/hooks/update-tab-on-action.mjs.map +3 -3
  45. package/dist/hooks/update-tab-titles.mjs +6 -0
  46. package/dist/hooks/update-tab-titles.mjs.map +3 -3
  47. package/dist/hooks/worker-proxy.mjs +858 -0
  48. package/dist/hooks/worker-proxy.mjs.map +7 -0
  49. package/dist/hooks/worker-status-line.mjs +545 -0
  50. package/dist/hooks/worker-status-line.mjs.map +7 -0
  51. package/dist/{indexer-backend-nQZuEx6N.mjs → indexer-backend-Cox9BCo-.mjs} +1 -1
  52. package/dist/{indexer-backend-nQZuEx6N.mjs.map → indexer-backend-Cox9BCo-.mjs.map} +1 -1
  53. package/dist/{kg-entity-DbOMPdF9.mjs → kg-entity-r8duqhi9.mjs} +1 -1
  54. package/dist/{kg-entity-DbOMPdF9.mjs.map → kg-entity-r8duqhi9.mjs.map} +1 -1
  55. package/dist/{latent-ideas-Bn6A5-5P.mjs → latent-ideas-BC1oINZ-.mjs} +2 -2
  56. package/dist/{latent-ideas-Bn6A5-5P.mjs.map → latent-ideas-BC1oINZ-.mjs.map} +1 -1
  57. package/dist/{link-boost-fYjUnxCN.mjs → link-boost-QFLrJwD6.mjs} +1 -1
  58. package/dist/{link-boost-fYjUnxCN.mjs.map → link-boost-QFLrJwD6.mjs.map} +1 -1
  59. package/dist/{main-resolver-BAbhKpeX.mjs → main-resolver-DlaLOFBA.mjs} +1 -1
  60. package/dist/{main-resolver-BAbhKpeX.mjs.map → main-resolver-DlaLOFBA.mjs.map} +1 -1
  61. package/dist/{main-resolver-Dxh444GO.mjs → main-resolver-IhZo4pI0.mjs} +1 -1
  62. package/dist/{neighborhood-DpaEM991.mjs → neighborhood-D9MJ1c8f.mjs} +1 -1
  63. package/dist/{neighborhood-DpaEM991.mjs.map → neighborhood-D9MJ1c8f.mjs.map} +1 -1
  64. package/dist/{note-context-DrcY4cWm.mjs → note-context-d1wT_-GA.mjs} +1 -1
  65. package/dist/{note-context-DrcY4cWm.mjs.map → note-context-d1wT_-GA.mjs.map} +1 -1
  66. package/dist/{postgres-BVme6qX0.mjs → postgres--BjPtLa0.mjs} +1 -1
  67. package/dist/{postgres-BVme6qX0.mjs.map → postgres--BjPtLa0.mjs.map} +1 -1
  68. package/dist/{program-C-fUghPv.mjs → program-Y0hAiVy8.mjs} +851 -101
  69. package/dist/program-Y0hAiVy8.mjs.map +1 -0
  70. package/dist/providers-DUshcB-d.mjs +4031 -0
  71. package/dist/providers-DUshcB-d.mjs.map +1 -0
  72. package/dist/{query-feedback-C1T6kS18.mjs → query-feedback-BUJxgw5B.mjs} +1 -1
  73. package/dist/{query-feedback-C1T6kS18.mjs.map → query-feedback-BUJxgw5B.mjs.map} +1 -1
  74. package/dist/query-feedback-DhyLOe5S.mjs +3 -0
  75. package/dist/router-1zi8jiNF.mjs +3 -0
  76. package/dist/{router-CsDm7HvK.mjs → router-DK_sLsUL.mjs} +1 -1
  77. package/dist/{router-CsDm7HvK.mjs.map → router-DK_sLsUL.mjs.map} +1 -1
  78. package/dist/skills/Worker/SKILL.md +63 -0
  79. package/dist/{sources-D8ZdNfvK.mjs → sources-Bi7--33T.mjs} +1 -1
  80. package/dist/{sources-D8ZdNfvK.mjs.map → sources-Bi7--33T.mjs.map} +1 -1
  81. package/dist/{sqlite-D1IaR8Am.mjs → sqlite-DtaL1glm.mjs} +1 -1
  82. package/dist/{sqlite-D1IaR8Am.mjs.map → sqlite-DtaL1glm.mjs.map} +1 -1
  83. package/dist/{state-qtmrBWCm.mjs → state-8Hm9E4tW.mjs} +1 -1
  84. package/dist/{state-WaXhLr6R.mjs → state-BY2L6-vX.mjs} +1 -1
  85. package/dist/{state-WaXhLr6R.mjs.map → state-BY2L6-vX.mjs.map} +1 -1
  86. package/dist/{themes-XPkj_bfP.mjs → themes-BN0a2duq.mjs} +1 -1
  87. package/dist/{themes-XPkj_bfP.mjs.map → themes-BN0a2duq.mjs.map} +1 -1
  88. package/dist/{tools-DEt6YPfc.mjs → tools-CGPqpU3A.mjs} +1 -1
  89. package/dist/{tools-ceiy7ANX.mjs → tools-y2bJpKom.mjs} +14 -14
  90. package/dist/{tools-ceiy7ANX.mjs.map → tools-y2bJpKom.mjs.map} +1 -1
  91. package/dist/{trace-DfyGmMG_.mjs → trace-h23JCcFD.mjs} +1 -1
  92. package/dist/{trace-DfyGmMG_.mjs.map → trace-h23JCcFD.mjs.map} +1 -1
  93. package/dist/{vault-indexer-CFvlPUMB.mjs → vault-indexer-DgsPjMgs.mjs} +1 -1
  94. package/dist/{vault-indexer-CFvlPUMB.mjs.map → vault-indexer-DgsPjMgs.mjs.map} +1 -1
  95. package/dist/{work-queue-worker-B8W8_3Rn.mjs → work-queue-worker-Dva_v_pI.mjs} +4 -4
  96. package/dist/{work-queue-worker-B8W8_3Rn.mjs.map → work-queue-worker-Dva_v_pI.mjs.map} +1 -1
  97. package/dist/{work-queue-worker-HN2Ufg-L.mjs → work-queue-worker-gsKd2LJa.mjs} +4 -4
  98. package/dist/{zettelkasten-CvjmMghT.mjs → zettelkasten-vo7psPdT.mjs} +3 -3
  99. package/dist/{zettelkasten-CvjmMghT.mjs.map → zettelkasten-vo7psPdT.mjs.map} +1 -1
  100. package/docs/commands/README.md +29 -0
  101. package/docs/commands/backup.md +1 -1
  102. package/docs/commands/clear-names.md +1 -1
  103. package/docs/commands/daemon.md +1 -1
  104. package/docs/commands/db.md +1 -1
  105. package/docs/commands/end.md +1 -1
  106. package/docs/commands/help.md +1 -1
  107. package/docs/commands/identity.md +1 -1
  108. package/docs/commands/kg.md +1 -1
  109. package/docs/commands/mcp.md +1 -1
  110. package/docs/commands/memory.md +1 -1
  111. package/docs/commands/notify.md +1 -1
  112. package/docs/commands/observation.md +1 -1
  113. package/docs/commands/obsidian.md +1 -1
  114. package/docs/commands/pause.md +1 -1
  115. package/docs/commands/project.md +1 -1
  116. package/docs/commands/projects.md +1 -1
  117. package/docs/commands/registry.md +1 -1
  118. package/docs/commands/restore.md +1 -1
  119. package/docs/commands/session.md +1 -1
  120. package/docs/commands/sessions.md +1 -1
  121. package/docs/commands/setup.md +1 -1
  122. package/docs/commands/shell-init.md +1 -1
  123. package/docs/commands/skill.md +1 -1
  124. package/docs/commands/task.md +1 -1
  125. package/docs/commands/topic.md +1 -1
  126. package/docs/commands/update.md +1 -1
  127. package/docs/commands/worker.md +398 -0
  128. package/docs/commands/zettel.md +1 -1
  129. package/docs/worker.md +434 -0
  130. package/package.json +1 -1
  131. package/scripts/build-hooks.mjs +46 -1
  132. package/src/hooks/pre-compact.sh +1 -0
  133. package/src/hooks/session-autosave.sh +1 -0
  134. package/src/hooks/session-stop.sh +1 -0
  135. package/src/hooks/ts/lib/context-fill.test.ts +104 -0
  136. package/src/hooks/ts/lib/context-fill.ts +73 -0
  137. package/src/hooks/ts/lib/handover-evidence.ts +227 -0
  138. package/src/hooks/ts/lib/worker-session.test.ts +24 -0
  139. package/src/hooks/ts/lib/worker-session.ts +23 -0
  140. package/src/hooks/ts/post-tool-use/sync-todo-to-md.ts +2 -0
  141. package/src/hooks/ts/post-tool-use/update-tab-on-action.ts +2 -0
  142. package/src/hooks/ts/pre-compact/context-compression-hook.ts +17 -0
  143. package/src/hooks/ts/pre-tool-use/route-agents-to-worker.ts +128 -0
  144. package/src/hooks/ts/session-start/initialize-session.ts +2 -0
  145. package/src/hooks/ts/session-start/load-core-context.ts +2 -0
  146. package/src/hooks/ts/session-start/load-project-context.ts +2 -0
  147. package/src/hooks/ts/session-start/post-compact-inject.ts +2 -0
  148. package/src/hooks/ts/stop/stop-hook.ts +2 -0
  149. package/src/hooks/ts/subagent-stop/subagent-stop-hook.ts +2 -0
  150. package/src/hooks/ts/user-prompt/update-tab-titles.ts +2 -0
  151. package/statusline-command.sh +17 -0
  152. package/dist/daemon-DRdoA489.mjs +0 -20
  153. package/dist/detector-DGAk1iBR.mjs +0 -5
  154. package/dist/factory-CrokPMk2.mjs +0 -3
  155. package/dist/program-C-fUghPv.mjs.map +0 -1
  156. package/dist/query-feedback-BBMBp96K.mjs +0 -3
  157. 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