@tekmidian/pai 0.41.0 → 0.42.1

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 (118) hide show
  1. package/dist/aibroker-client-BP74gGwD.mjs +215 -0
  2. package/dist/aibroker-client-BP74gGwD.mjs.map +1 -0
  3. package/dist/aibroker-client-C6m71p6O.mjs +3 -0
  4. package/dist/{auto-route-BwzmlW3W.mjs → auto-route-_5zzJz5q.mjs} +2 -2
  5. package/dist/{auto-route-BwzmlW3W.mjs.map → auto-route-_5zzJz5q.mjs.map} +1 -1
  6. package/dist/{chain-zMjCDW-R.mjs → chain-DsvSHnO9.mjs} +133 -361
  7. package/dist/chain-DsvSHnO9.mjs.map +1 -0
  8. package/dist/cli/index.mjs +12 -10
  9. package/dist/cli/index.mjs.map +1 -1
  10. package/dist/cli/program.mjs +12 -10
  11. package/dist/{clusters-DRHhfli9.mjs → clusters-j0ZuNYp9.mjs} +1 -1
  12. package/dist/{clusters-DRHhfli9.mjs.map → clusters-j0ZuNYp9.mjs.map} +1 -1
  13. package/dist/{config-D1G9IFpn.mjs → config-B64vFg14.mjs} +3 -50
  14. package/dist/config-B64vFg14.mjs.map +1 -0
  15. package/dist/{config-Dl8lT4Lu.mjs → config-Bi_F6lXf.mjs} +32 -3
  16. package/dist/config-Bi_F6lXf.mjs.map +1 -0
  17. package/dist/{config-DD52BPB-.mjs → config-C_ErGddD.mjs} +1 -1
  18. package/dist/{context-handover-cache-C6_rs7JT.mjs → context-handover-cache--bm6Siim.mjs} +28 -9
  19. package/dist/context-handover-cache--bm6Siim.mjs.map +1 -0
  20. package/dist/daemon/index.mjs +13 -12
  21. package/dist/daemon/index.mjs.map +1 -1
  22. package/dist/daemon-BOAPyL_b.mjs +23 -0
  23. package/dist/{daemon-CpdVUnl_.mjs → daemon-DvXih78C.mjs} +359 -27
  24. package/dist/daemon-DvXih78C.mjs.map +1 -0
  25. package/dist/daemon-mcp/index.mjs +10 -6
  26. package/dist/daemon-mcp/index.mjs.map +1 -1
  27. package/dist/detector-C-7KRi42.mjs +5 -0
  28. package/dist/{detector-smpCm7ku.mjs → detector-C73Nxm94.mjs} +1 -1
  29. package/dist/{detector-smpCm7ku.mjs.map → detector-C73Nxm94.mjs.map} +1 -1
  30. package/dist/{factory-Bc5EyN54.mjs → factory-DEb9NmHJ.mjs} +7 -7
  31. package/dist/{factory-Bc5EyN54.mjs.map → factory-DEb9NmHJ.mjs.map} +1 -1
  32. package/dist/factory-Dbo8yNPi.mjs +3 -0
  33. package/dist/{fallback-CgQ_x4I7.mjs → fallback-8bzmg6No.mjs} +6 -5
  34. package/dist/fallback-8bzmg6No.mjs.map +1 -0
  35. package/dist/hooks/route-agents-to-worker.mjs.map +2 -2
  36. package/dist/hooks/worker-proxy.mjs.map +2 -2
  37. package/dist/hooks/worker-status-line.mjs +13 -5
  38. package/dist/hooks/worker-status-line.mjs.map +3 -3
  39. package/dist/hooks/worker-supervision.mjs +593 -0
  40. package/dist/hooks/worker-supervision.mjs.map +7 -0
  41. package/dist/{indexer-backend-rohVVKrA.mjs → indexer-backend-BPTzKFZU.mjs} +1 -1
  42. package/dist/{indexer-backend-rohVVKrA.mjs.map → indexer-backend-BPTzKFZU.mjs.map} +1 -1
  43. package/dist/{ipc-client-C7AVpsyv.mjs → ipc-client-BmypMNYk.mjs} +2 -2
  44. package/dist/{ipc-client-C7AVpsyv.mjs.map → ipc-client-BmypMNYk.mjs.map} +1 -1
  45. package/dist/{kg-entity-DbOMPdF9.mjs → kg-entity-r8duqhi9.mjs} +1 -1
  46. package/dist/{kg-entity-DbOMPdF9.mjs.map → kg-entity-r8duqhi9.mjs.map} +1 -1
  47. package/dist/{latent-ideas-iNp5s55R.mjs → latent-ideas-CtSca4qG.mjs} +2 -2
  48. package/dist/{latent-ideas-iNp5s55R.mjs.map → latent-ideas-CtSca4qG.mjs.map} +1 -1
  49. package/dist/{link-boost-NNzADHX_.mjs → link-boost-Bucnz3n8.mjs} +1 -1
  50. package/dist/{link-boost-NNzADHX_.mjs.map → link-boost-Bucnz3n8.mjs.map} +1 -1
  51. package/dist/main-resolver-CqppP_Bw.mjs +7 -0
  52. package/dist/{main-resolver-CM1IHbuu.mjs → main-resolver-Dip2VLB5.mjs} +4 -214
  53. package/dist/main-resolver-Dip2VLB5.mjs.map +1 -0
  54. package/dist/{neighborhood-AGL7oXQ2.mjs → neighborhood-CM7Jgb51.mjs} +1 -1
  55. package/dist/{neighborhood-AGL7oXQ2.mjs.map → neighborhood-CM7Jgb51.mjs.map} +1 -1
  56. package/dist/{note-context-BHwYpSFf.mjs → note-context-DCwE2A-M.mjs} +1 -1
  57. package/dist/{note-context-BHwYpSFf.mjs.map → note-context-DCwE2A-M.mjs.map} +1 -1
  58. package/dist/{planner-DsmMRIZR.mjs → planner-ogy-Zy4O.mjs} +6 -5
  59. package/dist/{planner-DsmMRIZR.mjs.map → planner-ogy-Zy4O.mjs.map} +1 -1
  60. package/dist/{postgres-Dwxe290H.mjs → postgres-CtOzfoQU.mjs} +1 -1
  61. package/dist/{postgres-Dwxe290H.mjs.map → postgres-CtOzfoQU.mjs.map} +1 -1
  62. package/dist/{program-Bed-Pd-8.mjs → program-B7N5OIJR.mjs} +36 -33
  63. package/dist/{program-Bed-Pd-8.mjs.map → program-B7N5OIJR.mjs.map} +1 -1
  64. package/dist/query-feedback-DGoVRaWD.mjs +3 -0
  65. package/dist/{query-feedback-DZfjI34w.mjs → query-feedback-Dqz-HC__.mjs} +1 -1
  66. package/dist/{query-feedback-DZfjI34w.mjs.map → query-feedback-Dqz-HC__.mjs.map} +1 -1
  67. package/dist/router-BTIGZR2_.mjs +3 -0
  68. package/dist/{router-DHracTUC.mjs → router-DD6D4j31.mjs} +1 -1
  69. package/dist/{router-DHracTUC.mjs.map → router-DD6D4j31.mjs.map} +1 -1
  70. package/dist/runtime-paths-rni52zHX.mjs +50 -0
  71. package/dist/runtime-paths-rni52zHX.mjs.map +1 -0
  72. package/dist/scope-CfYnjy5R.mjs +360 -0
  73. package/dist/scope-CfYnjy5R.mjs.map +1 -0
  74. package/dist/{server-BOuAOj9b.mjs → server-DvN-IFIV.mjs} +42 -3
  75. package/dist/server-DvN-IFIV.mjs.map +1 -0
  76. package/dist/skills/Worker/SKILL.md +3 -0
  77. package/dist/{sources-ju6AA7lL.mjs → sources-CS43CpuS.mjs} +1 -1
  78. package/dist/{sources-ju6AA7lL.mjs.map → sources-CS43CpuS.mjs.map} +1 -1
  79. package/dist/{sqlite-kFZeWCJ2.mjs → sqlite-Cq3gJcq9.mjs} +1 -1
  80. package/dist/{sqlite-kFZeWCJ2.mjs.map → sqlite-Cq3gJcq9.mjs.map} +1 -1
  81. package/dist/{state-ahFAbD8b.mjs → state-BTa10SOM.mjs} +1 -1
  82. package/dist/{state-ahFAbD8b.mjs.map → state-BTa10SOM.mjs.map} +1 -1
  83. package/dist/{state-DH_mp5Xe.mjs → state-RXepvbnO.mjs} +1 -1
  84. package/dist/{themes-DqhNUUQk.mjs → themes-B7h9YDiy.mjs} +1 -1
  85. package/dist/{themes-DqhNUUQk.mjs.map → themes-B7h9YDiy.mjs.map} +1 -1
  86. package/dist/{tools-COna9vZR.mjs → tools-BW3NLMPk.mjs} +1 -1
  87. package/dist/{tools-CMlHa9lZ.mjs → tools-LlzcV_eK.mjs} +14 -14
  88. package/dist/{tools-CMlHa9lZ.mjs.map → tools-LlzcV_eK.mjs.map} +1 -1
  89. package/dist/{trace-DTP6k4B_.mjs → trace-u4hT-pLD.mjs} +1 -1
  90. package/dist/{trace-DTP6k4B_.mjs.map → trace-u4hT-pLD.mjs.map} +1 -1
  91. package/dist/{vault-indexer-YMH22mLU.mjs → vault-indexer-mata9S7b.mjs} +1 -1
  92. package/dist/{vault-indexer-YMH22mLU.mjs.map → vault-indexer-mata9S7b.mjs.map} +1 -1
  93. package/dist/work-queue-worker-19r0cpH4.mjs +14 -0
  94. package/dist/{work-queue-worker-B3nl8yyn.mjs → work-queue-worker-DXh_FnT5.mjs} +4 -4
  95. package/dist/{work-queue-worker-B3nl8yyn.mjs.map → work-queue-worker-DXh_FnT5.mjs.map} +1 -1
  96. package/dist/{zettelkasten-DDvKRm2V.mjs → zettelkasten-F0D_XfQ4.mjs} +3 -3
  97. package/dist/{zettelkasten-DDvKRm2V.mjs.map → zettelkasten-F0D_XfQ4.mjs.map} +1 -1
  98. package/docs/commands/worker.md +1 -1
  99. package/docs/merge-demo.md +1 -0
  100. package/docs/worker.md +40 -0
  101. package/package.json +1 -1
  102. package/plugins/productivity/hooks/hooks.json +5 -0
  103. package/src/hooks/ts/user-prompt/worker-supervision.ts +123 -0
  104. package/dist/chain-zMjCDW-R.mjs.map +0 -1
  105. package/dist/config-D1G9IFpn.mjs.map +0 -1
  106. package/dist/config-Dl8lT4Lu.mjs.map +0 -1
  107. package/dist/context-handover-cache-C6_rs7JT.mjs.map +0 -1
  108. package/dist/daemon-BSL9sXxm.mjs +0 -22
  109. package/dist/daemon-CpdVUnl_.mjs.map +0 -1
  110. package/dist/detector-BLVOCiJ_.mjs +0 -5
  111. package/dist/factory-C7ua_3ik.mjs +0 -3
  112. package/dist/fallback-CgQ_x4I7.mjs.map +0 -1
  113. package/dist/main-resolver-CM1IHbuu.mjs.map +0 -1
  114. package/dist/main-resolver-E5WlilhM.mjs +0 -6
  115. package/dist/query-feedback-CGcEiGtY.mjs +0 -3
  116. package/dist/router-CAtyVQud.mjs +0 -3
  117. package/dist/server-BOuAOj9b.mjs.map +0 -1
  118. package/dist/work-queue-worker-BM1GcAAq.mjs +0 -14
@@ -3,15 +3,19 @@ import { t as openFederation } from "./db-a1ixZQjr.mjs";
3
3
  import { d as sha256 } from "./helpers-IjZkXBhj.mjs";
4
4
  import { n as indexAll } from "./sync-BWbe8JTg.mjs";
5
5
  import { t as configureEmbeddingModel } from "./embeddings-DOLZnT1X.mjs";
6
- import { l as DEFAULT_NOTIFICATION_CONFIG, n as CONFIG_FILE, t as CONFIG_DIR } from "./config-D1G9IFpn.mjs";
7
- import { n as getBackendOutage, t as createStorageBackend } from "./factory-Bc5EyN54.mjs";
8
- import { C as setStorageBackend, D as storageBackend, E as startTime, O as vaultIndexInProgress, S as setStartTime, T as shutdownRequested, _ as setLastIndexTime, a as indexSchedulerTimer, b as setRegistryDb, c as lastVaultIndexTime, d as setDaemonConfig, f as setEmbedInProgress, g as setLastEmbedTime, h as setIndexSchedulerTimer, i as indexInProgress, l as notificationConfig, m as setIndexInProgress, n as embedInProgress, o as lastEmbedTime, p as setEmbedSchedulerTimer, r as embedSchedulerTimer, s as lastIndexTime, t as daemonConfig, u as registryDb, v as setLastVaultIndexTime, w as setVaultIndexInProgress, x as setShutdownRequested, y as setNotificationConfig } from "./state-ahFAbD8b.mjs";
9
- import { t as routeNotification } from "./router-DHracTUC.mjs";
10
- import { C as toolProjectInfo, D as toolMemoryGet, O as toolMemorySearch, S as toolProjectHealth, T as toolProjectTodo, a as toolMemoryWakeup, b as toolSessionRoute, i as toolMemoryTaxonomy, n as toolMemoryFeedback, t as toolMemoryKgSearch, v as toolRegistrySearch, w as toolProjectList, x as toolProjectDetect, y as toolSessionList } from "./tools-CMlHa9lZ.mjs";
11
- import { t as detectTopicShift } from "./detector-smpCm7ku.mjs";
12
- import { a as enqueue, i as stopWorker, n as notifyNewWork, o as getStats, r as startWorker, s as loadQueue } from "./work-queue-worker-B3nl8yyn.mjs";
13
- import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
6
+ import { l as DEFAULT_NOTIFICATION_CONFIG, n as CONFIG_FILE, t as CONFIG_DIR } from "./config-B64vFg14.mjs";
7
+ import { n as getBackendOutage, t as createStorageBackend } from "./factory-DEb9NmHJ.mjs";
8
+ import { h as readWorkersSection } from "./config-Bi_F6lXf.mjs";
9
+ import { _ as workersLogDir } from "./server-DvN-IFIV.mjs";
10
+ import { _ as loadStatuses, d as alive, h as isChatPane, n as itermForClaudeSession, y as nowStamp } from "./scope-CfYnjy5R.mjs";
11
+ import { C as setStorageBackend, D as storageBackend, E as startTime, O as vaultIndexInProgress, S as setStartTime, T as shutdownRequested, _ as setLastIndexTime, a as indexSchedulerTimer, b as setRegistryDb, c as lastVaultIndexTime, d as setDaemonConfig, f as setEmbedInProgress, g as setLastEmbedTime, h as setIndexSchedulerTimer, i as indexInProgress, l as notificationConfig, m as setIndexInProgress, n as embedInProgress, o as lastEmbedTime, p as setEmbedSchedulerTimer, r as embedSchedulerTimer, s as lastIndexTime, t as daemonConfig, u as registryDb, v as setLastVaultIndexTime, w as setVaultIndexInProgress, x as setShutdownRequested, y as setNotificationConfig } from "./state-BTa10SOM.mjs";
12
+ import { t as routeNotification } from "./router-DD6D4j31.mjs";
13
+ import { C as toolProjectInfo, D as toolMemoryGet, O as toolMemorySearch, S as toolProjectHealth, T as toolProjectTodo, a as toolMemoryWakeup, b as toolSessionRoute, i as toolMemoryTaxonomy, n as toolMemoryFeedback, t as toolMemoryKgSearch, v as toolRegistrySearch, w as toolProjectList, x as toolProjectDetect, y as toolSessionList } from "./tools-LlzcV_eK.mjs";
14
+ import { t as detectTopicShift } from "./detector-C73Nxm94.mjs";
15
+ import { a as enqueue, i as stopWorker, n as notifyNewWork, o as getStats, r as startWorker, s as loadQueue } from "./work-queue-worker-DXh_FnT5.mjs";
16
+ import { appendFileSync, existsSync, mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
14
17
  import { setPriority } from "node:os";
18
+ import { dirname, join } from "node:path";
15
19
  import { connect, createServer } from "node:net";
16
20
 
17
21
  //#region src/notifications/config.ts
@@ -83,6 +87,297 @@ function patchNotificationConfig(patch) {
83
87
  return current;
84
88
  }
85
89
 
90
+ //#endregion
91
+ //#region src/workers/supervision.ts
92
+ /**
93
+ * supervision.ts — daemon-side worker supervision, event push not polling.
94
+ *
95
+ * An orchestrating session must keep an eye on its workers without sleeping
96
+ * (the Worker skill forbids busy-waiting), the same way the AIBroker daemon
97
+ * keeps an eye on sessions. The PAI daemon already runs all day; this module
98
+ * gives it a tick that watches every worker's status file and pushes one-line
99
+ * events to the owning session when something happens:
100
+ *
101
+ * finished — the run ended clean (state done, rc 0)
102
+ * failed — the run ended badly (state failed/killed/lost, or rc != 0,
103
+ * or the runner pid vanished while state was still running)
104
+ * stalled — state running, runner alive, but no new turn for longer than
105
+ * the stall threshold (PAI_WORKER_STALL_MINUTES, default 10);
106
+ * never emitted for the interactive chat pane (origin "chat"),
107
+ * which idles between sessions by design
108
+ *
109
+ * Delivery, in order of preference, never both counted as required:
110
+ * 1. AIBroker `send_to_session` into the orchestrator's terminal — the
111
+ * one-line Agentish-style notification, arriving as a user turn without
112
+ * the orchestrator asking for it. Needs the iTerm identity of the owner,
113
+ * which the status line bridges into the session map (see scope.ts).
114
+ * 2. An append-only event file, <logDir>/supervision/<owner>.events, one
115
+ * JSON line per event. The UserPromptSubmit hook
116
+ * (src/hooks/ts/user-prompt/worker-supervision.ts) surfaces undelivered
117
+ * lines into the session that owns them, so the events reach the model
118
+ * on its next turn even with AIBroker absent.
119
+ *
120
+ * Exactly-once: every event carries a deterministic id and the set of
121
+ * delivered ids per worker is persisted (<logDir>/supervision/state.json), so
122
+ * a daemon restart replays nothing. A first run with no state adopts every
123
+ * already-terminal worker as delivered — supervision reports transitions it
124
+ * observes, not history. Chain stages are separate workers with their own
125
+ * status files and are supervised individually.
126
+ *
127
+ * No model is ever called from here — this is daemon code, and it must stay
128
+ * that way: supervision that bills tokens to watch tokens is a bug.
129
+ */
130
+ /**
131
+ * The owning session of a worker, or null when nobody claims it (and so
132
+ * nothing to notify): the spawner's claude session id first — that is what
133
+ * the hook knows a session by — else the AIBroker iTerm uuid.
134
+ */
135
+ function ownershipKey(s) {
136
+ if (s.spawnerSession) return s.spawnerSession;
137
+ if (s.session?.id) return s.session.id;
138
+ return null;
139
+ }
140
+ const TERMINAL = [
141
+ "done",
142
+ "failed",
143
+ "killed",
144
+ "lost"
145
+ ];
146
+ function isTerminal(s) {
147
+ return TERMINAL.includes(s.state);
148
+ }
149
+ /** Age of a "YYYY-MM-DD HH:MM:SS" stamp in ms, NaN-safe (unparsable → 0). */
150
+ function ageMs(ts, now) {
151
+ const t = Date.parse(ts.replace(" ", "T"));
152
+ if (Number.isNaN(t)) return 0;
153
+ return Math.max(0, now.getTime() - t);
154
+ }
155
+ /**
156
+ * The stall threshold in minutes from PAI_WORKER_STALL_MINUTES (default 10).
157
+ * Anything unparsible or non-positive falls back to the default — a broken
158
+ * value must not turn supervision off or make it fire every tick.
159
+ */
160
+ const DEFAULT_STALL_MINUTES = 10;
161
+ function stallMinutesFromEnv(env = process.env) {
162
+ const raw = env.PAI_WORKER_STALL_MINUTES;
163
+ if (raw === void 0 || raw === "") return DEFAULT_STALL_MINUTES;
164
+ const n = Number(raw);
165
+ return Number.isFinite(n) && n > 0 ? n : DEFAULT_STALL_MINUTES;
166
+ }
167
+ /**
168
+ * Event id: `<worker>#<kind>` for the once-per-run terminal kinds, and
169
+ * `<worker>#stalled@<turns>` for stalls — the turn count re-arms the id, so a
170
+ * worker that resumes and stalls again gets a second event while a restart
171
+ * cannot replay the first.
172
+ */
173
+ function eventId(worker, kind, turns = 0) {
174
+ return kind === "stalled" ? `${worker}#stalled@${turns}` : `${worker}#${kind}`;
175
+ }
176
+ /** One event's one-liner — the exact line the orchestrator reads. */
177
+ function eventText(kind, s, rc, stalledMin) {
178
+ const tail = `- see pai worker replay ${s.id}`;
179
+ if (kind === "finished") return `worker ${s.id} ${s.label} finished rc=0 ${tail}`;
180
+ if (kind === "stalled") return `worker ${s.id} ${s.label} stalled ${stalledMin}m no turns ${tail}`;
181
+ const why = rc === null ? "runner gone" : `rc=${rc}`;
182
+ return `worker ${s.id} ${s.label} failed ${why} ${tail}`;
183
+ }
184
+ /**
185
+ * Detect conditions across a ledger snapshot. Pure: statuses in, events out;
186
+ * dedup against delivered ids is the caller's job (see filterUndelivered).
187
+ * Workers without an owning session are skipped — nobody to notify.
188
+ */
189
+ function detectSupervisionEvents(statuses, opts) {
190
+ const isAlive = opts.isAlive ?? alive;
191
+ const deadGraceMs = opts.deadGraceMs ?? 15e3;
192
+ const out = [];
193
+ for (const s of statuses) {
194
+ const session = ownershipKey(s);
195
+ if (!session) continue;
196
+ let kind = null;
197
+ let rc = null;
198
+ let stalledMin = null;
199
+ if (isTerminal(s)) if (s.state === "done" && (s.rc ?? 0) === 0) {
200
+ kind = "finished";
201
+ rc = 0;
202
+ } else {
203
+ kind = "failed";
204
+ rc = s.state === "lost" ? null : s.rc;
205
+ }
206
+ else if (!isAlive(s.pid) && ageMs(s.updated, opts.now) > deadGraceMs) {
207
+ kind = "failed";
208
+ rc = null;
209
+ } else if (!isChatPane(s) && ageMs(s.updated, opts.now) > opts.stallMs) {
210
+ kind = "stalled";
211
+ stalledMin = Math.floor(ageMs(s.updated, opts.now) / 6e4);
212
+ }
213
+ if (!kind) continue;
214
+ out.push({
215
+ id: eventId(s.id, kind, s.turns),
216
+ ts: nowStamp(opts.now),
217
+ kind,
218
+ worker: s.id,
219
+ label: s.label,
220
+ session,
221
+ rc,
222
+ stalledMin,
223
+ text: eventText(kind, s, rc, stalledMin)
224
+ });
225
+ }
226
+ return out;
227
+ }
228
+ /** Cap on remembered workers: terminal and delivered is history, not state. */
229
+ const MAX_REMEMBERED_WORKERS = 500;
230
+ function supervisionDirPath(logDir) {
231
+ return join(logDir, "supervision");
232
+ }
233
+ function supervisionStatePath(logDir) {
234
+ return join(supervisionDirPath(logDir), "state.json");
235
+ }
236
+ function supervisionEventsPath(logDir, session) {
237
+ return join(supervisionDirPath(logDir), `${session}.events`);
238
+ }
239
+ /** Receipts of events already pushed into a terminal, one event id per line. */
240
+ function supervisionPushedPath(logDir, session) {
241
+ return join(supervisionDirPath(logDir), `${session}.pushed`);
242
+ }
243
+ function loadSupervisionState(path) {
244
+ if (!existsSync(path)) return {
245
+ state: { delivered: {} },
246
+ fresh: true
247
+ };
248
+ try {
249
+ const parsed = JSON.parse(readFileSync(path, "utf8"));
250
+ if (parsed && typeof parsed === "object" && typeof parsed.delivered === "object") return {
251
+ state: parsed,
252
+ fresh: false
253
+ };
254
+ } catch {}
255
+ return {
256
+ state: { delivered: {} },
257
+ fresh: true
258
+ };
259
+ }
260
+ function saveSupervisionState(path, state) {
261
+ const dir = dirname(path);
262
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
263
+ const tmp = `${path}.tmp`;
264
+ writeFileSync(tmp, JSON.stringify(state), "utf8");
265
+ renameSync(tmp, path);
266
+ }
267
+ /**
268
+ * Drop workers the ledger no longer lists and cap the rest (newest ids last —
269
+ * worker ids sort chronologically), so the persisted state stays bounded.
270
+ */
271
+ function pruneState(state, statuses) {
272
+ const present = new Set(statuses.map((s) => s.id));
273
+ const kept = Object.entries(state.delivered).filter(([id]) => present.has(id)).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).slice(-MAX_REMEMBERED_WORKERS);
274
+ return { delivered: Object.fromEntries(kept) };
275
+ }
276
+ /**
277
+ * First run: adopt every already-terminal worker as delivered. Supervision
278
+ * reports the transitions it observes from now on, never a replay of every
279
+ * worker that ever finished before it started.
280
+ */
281
+ function baselineState(statuses) {
282
+ const delivered = {};
283
+ for (const s of statuses) {
284
+ if (!isTerminal(s) || !ownershipKey(s)) continue;
285
+ delivered[s.id] = [eventId(s.id, "finished"), eventId(s.id, "failed")];
286
+ }
287
+ return { delivered };
288
+ }
289
+ /** The events whose ids the state has not seen yet. */
290
+ function filterUndelivered(events, state) {
291
+ return events.filter((e) => !(state.delivered[e.worker] ?? []).includes(e.id));
292
+ }
293
+ function markDelivered(state, events) {
294
+ for (const e of events) {
295
+ const ids = state.delivered[e.worker] ?? [];
296
+ if (!ids.includes(e.id)) ids.push(e.id);
297
+ state.delivered[e.worker] = ids;
298
+ }
299
+ }
300
+ /**
301
+ * Default push: AIBroker `send_to_session` into the orchestrator's terminal,
302
+ * using the iTerm uuid the worker recorded (`session.id`) or the one the
303
+ * session map bridges for the spawner's claude session. A timeout still
304
+ * delivered (see aibroker-client) counts as delivered; every failure is
305
+ * reported as not-delivered so only the event file carries the event.
306
+ */
307
+ const aibrokerPush = async (logDir, target, ev) => {
308
+ const iterm = target.iterm ?? (target.claudeSession ? itermForClaudeSession(logDir, target.claudeSession) : null);
309
+ if (!iterm) return false;
310
+ try {
311
+ const { sendToSession } = await import("./aibroker-client-C6m71p6O.mjs");
312
+ const r = await sendToSession(iterm, ev.text);
313
+ return r.ok || r.timedOut === true;
314
+ } catch {
315
+ return false;
316
+ }
317
+ };
318
+ function appendEventFile(logDir, ev) {
319
+ const path = supervisionEventsPath(logDir, ev.session);
320
+ const dir = supervisionDirPath(logDir);
321
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
322
+ appendFileSync(path, JSON.stringify(ev) + "\n", "utf8");
323
+ }
324
+ /** Record that an event reached its owner's terminal — the hook then skips it. */
325
+ function appendPushReceipt(logDir, ev) {
326
+ try {
327
+ appendFileSync(supervisionPushedPath(logDir, ev.session), `${ev.id}\n`, "utf8");
328
+ } catch {}
329
+ }
330
+ /**
331
+ * One supervision pass over the ledger: detect, append each new event to its
332
+ * owner's event file, push it best effort, persist the delivered ids. The
333
+ * event file is the durable record — an event is marked delivered only after
334
+ * its line landed there, so a failed tick retries, a repeated one replays
335
+ * nothing.
336
+ */
337
+ async function runSupervisionTick(logDir, opts = {}) {
338
+ const now = opts.now ?? /* @__PURE__ */ new Date();
339
+ const statuses = opts.statuses ?? loadStatuses(logDir);
340
+ const statePath = supervisionStatePath(logDir);
341
+ const loaded = loadSupervisionState(statePath);
342
+ let state = loaded.state;
343
+ if (loaded.fresh) state = baselineState(statuses);
344
+ const pending = filterUndelivered(detectSupervisionEvents(statuses, {
345
+ stallMs: opts.stallMs ?? DEFAULT_STALL_MINUTES * 6e4,
346
+ now,
347
+ ...opts.isAlive ? { isAlive: opts.isAlive } : {}
348
+ }), state);
349
+ const written = [];
350
+ for (const ev of pending) {
351
+ appendEventFile(logDir, ev);
352
+ written.push(ev);
353
+ }
354
+ const push = opts.push ?? aibrokerPush;
355
+ const pushed = [];
356
+ for (const ev of written) {
357
+ const owner = statuses.find((s) => s.id === ev.worker);
358
+ const target = {
359
+ claudeSession: owner?.spawnerSession ?? null,
360
+ iterm: owner?.session?.id ?? null
361
+ };
362
+ let ok = false;
363
+ try {
364
+ ok = await push(logDir, target, ev);
365
+ } catch {
366
+ ok = false;
367
+ }
368
+ if (ok) {
369
+ pushed.push(ev);
370
+ appendPushReceipt(logDir, ev);
371
+ }
372
+ }
373
+ markDelivered(state, written);
374
+ saveSupervisionState(statePath, pruneState(state, statuses));
375
+ return {
376
+ events: written,
377
+ pushed
378
+ };
379
+ }
380
+
86
381
  //#endregion
87
382
  //#region src/daemon/daemon/scheduler.ts
88
383
  /**
@@ -110,7 +405,7 @@ async function runIndex() {
110
405
  try {
111
406
  process.stderr.write("[pai-daemon] Starting scheduled index run...\n");
112
407
  if (storageBackend.backendType === "sqlite") {
113
- const { SQLiteBackend } = await import("./sqlite-kFZeWCJ2.mjs");
408
+ const { SQLiteBackend } = await import("./sqlite-Cq3gJcq9.mjs");
114
409
  if (storageBackend instanceof SQLiteBackend) {
115
410
  const { projects, result } = await indexAll(storageBackend.getRawDb(), registryDb);
116
411
  const elapsed = Date.now() - t0;
@@ -118,7 +413,7 @@ async function runIndex() {
118
413
  process.stderr.write(`[pai-daemon] Index complete: ${projects} projects, ${result.filesProcessed} files, ${result.chunksCreated} chunks (${elapsed}ms)\n`);
119
414
  }
120
415
  } else {
121
- const { indexAllWithBackend } = await import("./indexer-backend-rohVVKrA.mjs");
416
+ const { indexAllWithBackend } = await import("./indexer-backend-BPTzKFZU.mjs");
122
417
  const { projects, result } = await indexAllWithBackend(storageBackend, registryDb);
123
418
  const elapsed = Date.now() - t0;
124
419
  setLastIndexTime(Date.now());
@@ -145,7 +440,7 @@ async function runVaultIndex() {
145
440
  process.stderr.write("[pai-daemon] Index/embed in progress, deferring vault index.\n");
146
441
  return;
147
442
  }
148
- const { lastVaultIndexTime } = await import("./state-DH_mp5Xe.mjs");
443
+ const { lastVaultIndexTime } = await import("./state-RXepvbnO.mjs");
149
444
  if (lastVaultIndexTime > 0 && Date.now() - lastVaultIndexTime < VAULT_INDEX_MIN_INTERVAL_MS) return;
150
445
  let vaultProjectId = daemonConfig.vaultProjectId;
151
446
  if (!vaultProjectId) {
@@ -157,7 +452,7 @@ async function runVaultIndex() {
157
452
  const t0 = Date.now();
158
453
  process.stderr.write("[pai-daemon] Starting vault index run...\n");
159
454
  try {
160
- const { indexVault } = await import("./vault-indexer-YMH22mLU.mjs");
455
+ const { indexVault } = await import("./vault-indexer-mata9S7b.mjs");
161
456
  const r = await indexVault(storageBackend, vaultProjectId, daemonConfig.vaultPath);
162
457
  const elapsed = Date.now() - t0;
163
458
  setLastVaultIndexTime(Date.now());
@@ -228,11 +523,11 @@ async function runEmbed() {
228
523
  const rows = registryDb.prepare("SELECT id, slug FROM projects WHERE status = 'active'").all();
229
524
  for (const r of rows) projectNames.set(r.id, r.slug);
230
525
  } catch {}
231
- const { embedChunksWithBackend } = await import("./indexer-backend-rohVVKrA.mjs");
526
+ const { embedChunksWithBackend } = await import("./indexer-backend-BPTzKFZU.mjs");
232
527
  const count = await embedChunksWithBackend(storageBackend, () => shutdownRequested, projectNames, { maxMillis: 24e4 });
233
528
  let vaultEmbedCount = 0;
234
529
  if (daemonConfig.vaultPath) try {
235
- const { SQLiteBackend } = await import("./sqlite-kFZeWCJ2.mjs");
530
+ const { SQLiteBackend } = await import("./sqlite-Cq3gJcq9.mjs");
236
531
  const { openFederation } = await import("./db-O-cyAPfS.mjs");
237
532
  const federationDb = openFederation();
238
533
  const vaultSqliteBackend = new SQLiteBackend(federationDb);
@@ -292,17 +587,53 @@ const REGISTRY_SCAN_STARTUP_DELAY_MS = 1e4;
292
587
  function startRegistryScanScheduler() {
293
588
  process.stderr.write("[pai-daemon] Registry scan scheduler: every 30min (first in 10s)\n");
294
589
  setTimeout(() => {
295
- import("./work-queue-worker-BM1GcAAq.mjs").then(({ enqueueRegistryScan }) => enqueueRegistryScan()).catch((e) => {
590
+ import("./work-queue-worker-19r0cpH4.mjs").then(({ enqueueRegistryScan }) => enqueueRegistryScan()).catch((e) => {
296
591
  process.stderr.write(`[pai-daemon] Startup registry scan error: ${e}\n`);
297
592
  });
298
593
  }, REGISTRY_SCAN_STARTUP_DELAY_MS);
299
594
  const timer = setInterval(() => {
300
- import("./work-queue-worker-BM1GcAAq.mjs").then(({ enqueueRegistryScan }) => enqueueRegistryScan()).catch((e) => {
595
+ import("./work-queue-worker-19r0cpH4.mjs").then(({ enqueueRegistryScan }) => enqueueRegistryScan()).catch((e) => {
301
596
  process.stderr.write(`[pai-daemon] Scheduled registry scan error: ${e}\n`);
302
597
  });
303
598
  }, REGISTRY_SCAN_INTERVAL_MS);
304
599
  if (timer.unref) timer.unref();
305
600
  }
601
+ /** How often the daemon looks at the worker ledger for supervision events. */
602
+ const SUPERVISION_TICK_MS = 3e4;
603
+ /** First supervision pass waits for the first statuses to exist at all. */
604
+ const SUPERVISION_STARTUP_DELAY_MS = 15e3;
605
+ /**
606
+ * Start the worker supervisor: one tick every 30 s over the workers ledger
607
+ * (src/workers/supervision.ts) that pushes finished/failed/stalled events to
608
+ * the owning session — the daemon-side replacement for orchestrator polling.
609
+ * Daemon code only: no model call has any business being here, and none is
610
+ * made. Disabled workers disable the supervisor with it.
611
+ */
612
+ function startWorkerSupervisor() {
613
+ let enabled = false;
614
+ try {
615
+ enabled = readWorkersSection().workers.enabled;
616
+ } catch (e) {
617
+ process.stderr.write(`[pai-daemon] Worker supervisor: not started (${e instanceof Error ? e.message : String(e)})\n`);
618
+ return;
619
+ }
620
+ if (!enabled) {
621
+ process.stderr.write("[pai-daemon] Worker supervisor: disabled (workers are off)\n");
622
+ return;
623
+ }
624
+ const tick = () => {
625
+ runSupervisionTick(workersLogDir(readWorkersSection().workers), { stallMs: stallMinutesFromEnv() * 6e4 }).then((r) => {
626
+ for (const ev of r.events) process.stderr.write(`[pai-daemon] Supervision: ${ev.text}\n`);
627
+ }).catch((e) => {
628
+ process.stderr.write(`[pai-daemon] Supervision tick error: ${e instanceof Error ? e.message : String(e)}\n`);
629
+ });
630
+ };
631
+ const first = setTimeout(tick, SUPERVISION_STARTUP_DELAY_MS);
632
+ if (first.unref) first.unref();
633
+ const timer = setInterval(tick, SUPERVISION_TICK_MS);
634
+ if (timer.unref) timer.unref();
635
+ process.stderr.write(`[pai-daemon] Worker supervisor: every ${Math.round(SUPERVISION_TICK_MS / 1e3)}s\n`);
636
+ }
306
637
 
307
638
  //#endregion
308
639
  //#region src/observations/store.ts
@@ -607,7 +938,7 @@ async function dispatchTool(method, params) {
607
938
  case "zettel_themes":
608
939
  case "zettel_god_notes":
609
940
  case "zettel_communities": {
610
- const { toolZettelExplore, toolZettelHealth, toolZettelSurprise, toolZettelSuggest, toolZettelConverse, toolZettelThemes, toolZettelGodNotes, toolZettelCommunities } = await import("./tools-COna9vZR.mjs");
941
+ const { toolZettelExplore, toolZettelHealth, toolZettelSurprise, toolZettelSuggest, toolZettelConverse, toolZettelThemes, toolZettelGodNotes, toolZettelCommunities } = await import("./tools-BW3NLMPk.mjs");
611
942
  switch (method) {
612
943
  case "zettel_explore": return toolZettelExplore(storageBackend, p);
613
944
  case "zettel_health": return toolZettelHealth(storageBackend, p);
@@ -621,27 +952,27 @@ async function dispatchTool(method, params) {
621
952
  break;
622
953
  }
623
954
  case "graph_clusters": {
624
- const { handleGraphClusters } = await import("./clusters-DRHhfli9.mjs");
955
+ const { handleGraphClusters } = await import("./clusters-j0ZuNYp9.mjs");
625
956
  return handleGraphClusters(storageBackend.getPool?.() ?? null, storageBackend, p);
626
957
  }
627
958
  case "graph_neighborhood": {
628
- const { handleGraphNeighborhood } = await import("./neighborhood-AGL7oXQ2.mjs");
959
+ const { handleGraphNeighborhood } = await import("./neighborhood-CM7Jgb51.mjs");
629
960
  return handleGraphNeighborhood(storageBackend.getPool?.() ?? null, storageBackend, p);
630
961
  }
631
962
  case "graph_note_context": {
632
- const { handleGraphNoteContext } = await import("./note-context-BHwYpSFf.mjs");
963
+ const { handleGraphNoteContext } = await import("./note-context-DCwE2A-M.mjs");
633
964
  return handleGraphNoteContext(storageBackend.getPool?.() ?? null, storageBackend, p);
634
965
  }
635
966
  case "graph_trace": {
636
- const { handleGraphTrace } = await import("./trace-DTP6k4B_.mjs");
967
+ const { handleGraphTrace } = await import("./trace-u4hT-pLD.mjs");
637
968
  return handleGraphTrace(storageBackend, p);
638
969
  }
639
970
  case "graph_latent_ideas": {
640
- const { handleGraphLatentIdeas } = await import("./latent-ideas-iNp5s55R.mjs");
971
+ const { handleGraphLatentIdeas } = await import("./latent-ideas-CtSca4qG.mjs");
641
972
  return handleGraphLatentIdeas(storageBackend, p);
642
973
  }
643
974
  case "idea_materialize": {
644
- const { handleIdeaMaterialize } = await import("./latent-ideas-iNp5s55R.mjs");
975
+ const { handleIdeaMaterialize } = await import("./latent-ideas-CtSca4qG.mjs");
645
976
  if (!daemonConfig.vaultPath) throw new Error("idea_materialize requires vaultPath to be configured in the daemon config");
646
977
  return handleIdeaMaterialize(p, daemonConfig.vaultPath);
647
978
  }
@@ -649,7 +980,7 @@ async function dispatchTool(method, params) {
649
980
  case "kg_query":
650
981
  case "kg_invalidate":
651
982
  case "kg_contradictions": {
652
- const { toolKgAdd, toolKgQuery, toolKgInvalidate, toolKgContradictions } = await import("./tools-COna9vZR.mjs");
983
+ const { toolKgAdd, toolKgQuery, toolKgInvalidate, toolKgContradictions } = await import("./tools-BW3NLMPk.mjs");
653
984
  const pgPool = storageBackend.getPool?.() ?? null;
654
985
  if (!pgPool) throw new Error(`${method} requires a Postgres storage backend`);
655
986
  switch (method) {
@@ -661,7 +992,7 @@ async function dispatchTool(method, params) {
661
992
  break;
662
993
  }
663
994
  case "memory_tunnels": {
664
- const { toolMemoryTunnels } = await import("./tools-COna9vZR.mjs");
995
+ const { toolMemoryTunnels } = await import("./tools-BW3NLMPk.mjs");
665
996
  return toolMemoryTunnels(registryDb, storageBackend, p);
666
997
  }
667
998
  case "memory_feedback": {
@@ -1312,7 +1643,7 @@ async function serve(config) {
1312
1643
  process.stderr.write("[pai-daemon] Starting daemon...\n");
1313
1644
  process.stderr.write(`[pai-daemon] Socket: ${config.socketPath}\n`);
1314
1645
  process.stderr.write(`[pai-daemon] Storage backend: ${config.storageBackend}\n`);
1315
- const { notificationConfig } = await import("./state-DH_mp5Xe.mjs");
1646
+ const { notificationConfig } = await import("./state-RXepvbnO.mjs");
1316
1647
  process.stderr.write(`[pai-daemon] Notification mode: ${notificationConfig.mode}\n`);
1317
1648
  try {
1318
1649
  setPriority(process.pid, 10);
@@ -1328,6 +1659,7 @@ async function serve(config) {
1328
1659
  }
1329
1660
  const server = await startIpcServer(config.socketPath);
1330
1661
  startRegistryScanScheduler();
1662
+ startWorkerSupervisor();
1331
1663
  createStorageBackend(config, { waitForPostgres: true }).then((backend) => {
1332
1664
  setStorageBackend(backend);
1333
1665
  process.stderr.write(`[pai-daemon] Federation backend: ${backend.backendType}\n`);
@@ -1376,4 +1708,4 @@ async function serve(config) {
1376
1708
 
1377
1709
  //#endregion
1378
1710
  export { serve as t };
1379
- //# sourceMappingURL=daemon-CpdVUnl_.mjs.map
1711
+ //# sourceMappingURL=daemon-DvXih78C.mjs.map