@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,545 @@
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
+ function workersLogDir(config) {
409
+ return expandHome(config.logDir);
410
+ }
411
+
412
+ // src/workers/status.ts
413
+ import { existsSync as existsSync2, readFileSync as readFileSync2, renameSync as renameSync2, writeFileSync as writeFileSync2, readdirSync } from "node:fs";
414
+ import { join as join3 } from "node:path";
415
+ function contextPercent(s) {
416
+ if (!s.contextTokens || !s.contextWindow) return null;
417
+ return Math.round(s.contextTokens / s.contextWindow * 100);
418
+ }
419
+ function loadStatuses(logDir) {
420
+ if (!existsSync2(logDir)) return [];
421
+ const out = [];
422
+ for (const name of readdirSync(logDir).sort()) {
423
+ if (!name.endsWith(".status")) continue;
424
+ try {
425
+ out.push(JSON.parse(readFileSync2(join3(logDir, name), "utf8")));
426
+ } catch {
427
+ }
428
+ }
429
+ return out;
430
+ }
431
+ function alive(pid) {
432
+ if (!pid || pid <= 0) return false;
433
+ try {
434
+ process.kill(pid, 0);
435
+ return true;
436
+ } catch {
437
+ return false;
438
+ }
439
+ }
440
+ function ageOf(ts, now = /* @__PURE__ */ new Date()) {
441
+ const t = Date.parse(ts.replace(" ", "T"));
442
+ if (Number.isNaN(t)) return "?";
443
+ const s = Math.max(0, Math.floor((now.getTime() - t) / 1e3));
444
+ return s < 90 ? `${s}s` : `${Math.floor(s / 60)}m`;
445
+ }
446
+
447
+ // src/workers/scope.ts
448
+ import { homedir as homedir3 } from "node:os";
449
+ import { join as join4 } from "node:path";
450
+ var AIBROKER_REGISTRY = join4(homedir3(), ".aibroker", "session-names.json");
451
+ function tabKey(term2) {
452
+ if (!term2) return "";
453
+ const head = term2.split("p", 1)[0];
454
+ const parts = head.slice(1).split("t");
455
+ if (head.startsWith("w") && parts.length === 2 && parts.every((p) => /^\d+$/.test(p))) {
456
+ return head;
457
+ }
458
+ return "";
459
+ }
460
+ function itermUuid(term2) {
461
+ if (!term2) return "";
462
+ return term2.split(":").pop() ?? "";
463
+ }
464
+ function workerInScope(worker, term2) {
465
+ if (!term2) return false;
466
+ const uuid = itermUuid(term2);
467
+ if (worker.session?.id && uuid) return worker.session.id === uuid;
468
+ return tabKey(worker.term) === tabKey(term2) && tabKey(term2) !== "";
469
+ }
470
+
471
+ // src/workers/report.ts
472
+ var WORKER_CONTRACT_PROMPT = [
473
+ "You are a headless implementation worker, run non-interactively by an orchestrating session.",
474
+ "No narration, no timestamps, no greetings, no summaries of what you read; act, verify, then stop.",
475
+ "Your ONE final message is a JSON object and nothing else \u2014 no prose before or after, no code fence:",
476
+ '{"changed":[{"path":"\u2026","summary":"\u2026"}],"commands":["\u2026"],"checks":[{"name":"\u2026","ok":true,"detail":"\u2026"}],"open":["\u2026"],"notes":"one line"}',
477
+ "changed: files you touched (path + one-line summary). commands: the commands that verify the work.",
478
+ "checks: each with ok true/false and the evidence in detail. open: what you could not finish, if anything.",
479
+ "notes: one line, the headline a reviewer reads first."
480
+ ].join("\n");
481
+
482
+ // src/workers/render.ts
483
+ var CODES = {
484
+ dim: "2",
485
+ bold: "1",
486
+ red: "31",
487
+ green: "32",
488
+ yellow: "33",
489
+ blue: "34",
490
+ mag: "35",
491
+ cyan: "36"
492
+ };
493
+ function makeColor(enabled) {
494
+ return (name, s) => enabled ? `\x1B[${CODES[name]}m${s}\x1B[0m` : s;
495
+ }
496
+ function fmtK(n) {
497
+ return n >= 1e3 ? `${Math.round(n / 1e3)}k` : String(n);
498
+ }
499
+ function contextMeter(c, s, minPct = 0) {
500
+ const pct = contextPercent(s);
501
+ if (pct === null || pct <= minPct) return null;
502
+ const label = `ctx ${fmtK(s.contextTokens ?? 0)}/${fmtK(s.contextWindow ?? 0)} (${pct}%)`;
503
+ if (pct > 85) return c("red", label);
504
+ if (pct > 70) return c("yellow", label);
505
+ return label;
506
+ }
507
+ function renderStatusLine(mine, now = /* @__PURE__ */ new Date(), c = makeColor(true)) {
508
+ if (!mine.length) return "";
509
+ const running = mine.filter((s) => s.state === "running" && alive(s.pid));
510
+ const today = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-${String(now.getDate()).padStart(2, "0")}`;
511
+ const doneToday = mine.filter((s) => s.state !== "running" && s.started.startsWith(today));
512
+ const ok = doneToday.filter((s) => s.state === "done").length;
513
+ const bad2 = doneToday.length - ok;
514
+ const providers = new Set(running.map((s) => s.provider));
515
+ const providerTag = providers.size === 1 ? [...providers][0] : "workers";
516
+ let head = `${providerTag} \u25B6${running.length}`;
517
+ const parts = running.slice(0, 3).map((s) => {
518
+ const meter = contextMeter(c, s, 60);
519
+ return `${s.id.slice(-4)} ${s.label.slice(0, 26)} ${ageOf(s.started, now)} \xB7 ${s.last.slice(0, 30)}` + (meter ? ` ${meter}` : "");
520
+ });
521
+ if (parts.length) head += " " + parts.join(" | ");
522
+ if (doneToday.length) head += ` \u2713${ok} \u2717${bad2} today`;
523
+ return head;
524
+ }
525
+
526
+ // src/workers/viewer.ts
527
+ function statusLineOutput(logDir, term2, cwd2, now = /* @__PURE__ */ new Date()) {
528
+ const statuses = loadStatuses(logDir);
529
+ const mine = statuses.filter((s) => {
530
+ const sameScope = term2 && workerInScope(s, term2);
531
+ const sameDir = cwd2 && s.cwd.startsWith(cwd2);
532
+ return sameScope || !term2 && sameDir;
533
+ });
534
+ return renderStatusLine(mine, now);
535
+ }
536
+
537
+ // src/workers/standalone/status-line.ts
538
+ var term = process.argv[2] ?? process.env.ITERM_SESSION_ID ?? "";
539
+ var cwd = process.argv[3] ?? process.cwd();
540
+ try {
541
+ const out = statusLineOutput(workersLogDir(readWorkersSection().workers), term, cwd);
542
+ if (out) process.stdout.write(out + "\n");
543
+ } catch {
544
+ }
545
+ //# sourceMappingURL=worker-status-line.mjs.map