@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.
- package/dist/aibroker-client-BP74gGwD.mjs +215 -0
- package/dist/aibroker-client-BP74gGwD.mjs.map +1 -0
- package/dist/aibroker-client-C6m71p6O.mjs +3 -0
- package/dist/{auto-route-BwzmlW3W.mjs → auto-route-_5zzJz5q.mjs} +2 -2
- package/dist/{auto-route-BwzmlW3W.mjs.map → auto-route-_5zzJz5q.mjs.map} +1 -1
- package/dist/{chain-zMjCDW-R.mjs → chain-DsvSHnO9.mjs} +133 -361
- package/dist/chain-DsvSHnO9.mjs.map +1 -0
- package/dist/cli/index.mjs +12 -10
- package/dist/cli/index.mjs.map +1 -1
- package/dist/cli/program.mjs +12 -10
- package/dist/{clusters-DRHhfli9.mjs → clusters-j0ZuNYp9.mjs} +1 -1
- package/dist/{clusters-DRHhfli9.mjs.map → clusters-j0ZuNYp9.mjs.map} +1 -1
- package/dist/{config-D1G9IFpn.mjs → config-B64vFg14.mjs} +3 -50
- package/dist/config-B64vFg14.mjs.map +1 -0
- package/dist/{config-Dl8lT4Lu.mjs → config-Bi_F6lXf.mjs} +32 -3
- package/dist/config-Bi_F6lXf.mjs.map +1 -0
- package/dist/{config-DD52BPB-.mjs → config-C_ErGddD.mjs} +1 -1
- package/dist/{context-handover-cache-C6_rs7JT.mjs → context-handover-cache--bm6Siim.mjs} +28 -9
- package/dist/context-handover-cache--bm6Siim.mjs.map +1 -0
- package/dist/daemon/index.mjs +13 -12
- package/dist/daemon/index.mjs.map +1 -1
- package/dist/daemon-BOAPyL_b.mjs +23 -0
- package/dist/{daemon-CpdVUnl_.mjs → daemon-DvXih78C.mjs} +359 -27
- package/dist/daemon-DvXih78C.mjs.map +1 -0
- package/dist/daemon-mcp/index.mjs +10 -6
- package/dist/daemon-mcp/index.mjs.map +1 -1
- package/dist/detector-C-7KRi42.mjs +5 -0
- package/dist/{detector-smpCm7ku.mjs → detector-C73Nxm94.mjs} +1 -1
- package/dist/{detector-smpCm7ku.mjs.map → detector-C73Nxm94.mjs.map} +1 -1
- package/dist/{factory-Bc5EyN54.mjs → factory-DEb9NmHJ.mjs} +7 -7
- package/dist/{factory-Bc5EyN54.mjs.map → factory-DEb9NmHJ.mjs.map} +1 -1
- package/dist/factory-Dbo8yNPi.mjs +3 -0
- package/dist/{fallback-CgQ_x4I7.mjs → fallback-8bzmg6No.mjs} +6 -5
- package/dist/fallback-8bzmg6No.mjs.map +1 -0
- package/dist/hooks/route-agents-to-worker.mjs.map +2 -2
- package/dist/hooks/worker-proxy.mjs.map +2 -2
- package/dist/hooks/worker-status-line.mjs +13 -5
- package/dist/hooks/worker-status-line.mjs.map +3 -3
- package/dist/hooks/worker-supervision.mjs +593 -0
- package/dist/hooks/worker-supervision.mjs.map +7 -0
- package/dist/{indexer-backend-rohVVKrA.mjs → indexer-backend-BPTzKFZU.mjs} +1 -1
- package/dist/{indexer-backend-rohVVKrA.mjs.map → indexer-backend-BPTzKFZU.mjs.map} +1 -1
- package/dist/{ipc-client-C7AVpsyv.mjs → ipc-client-BmypMNYk.mjs} +2 -2
- package/dist/{ipc-client-C7AVpsyv.mjs.map → ipc-client-BmypMNYk.mjs.map} +1 -1
- package/dist/{kg-entity-DbOMPdF9.mjs → kg-entity-r8duqhi9.mjs} +1 -1
- package/dist/{kg-entity-DbOMPdF9.mjs.map → kg-entity-r8duqhi9.mjs.map} +1 -1
- package/dist/{latent-ideas-iNp5s55R.mjs → latent-ideas-CtSca4qG.mjs} +2 -2
- package/dist/{latent-ideas-iNp5s55R.mjs.map → latent-ideas-CtSca4qG.mjs.map} +1 -1
- package/dist/{link-boost-NNzADHX_.mjs → link-boost-Bucnz3n8.mjs} +1 -1
- package/dist/{link-boost-NNzADHX_.mjs.map → link-boost-Bucnz3n8.mjs.map} +1 -1
- package/dist/main-resolver-CqppP_Bw.mjs +7 -0
- package/dist/{main-resolver-CM1IHbuu.mjs → main-resolver-Dip2VLB5.mjs} +4 -214
- package/dist/main-resolver-Dip2VLB5.mjs.map +1 -0
- package/dist/{neighborhood-AGL7oXQ2.mjs → neighborhood-CM7Jgb51.mjs} +1 -1
- package/dist/{neighborhood-AGL7oXQ2.mjs.map → neighborhood-CM7Jgb51.mjs.map} +1 -1
- package/dist/{note-context-BHwYpSFf.mjs → note-context-DCwE2A-M.mjs} +1 -1
- package/dist/{note-context-BHwYpSFf.mjs.map → note-context-DCwE2A-M.mjs.map} +1 -1
- package/dist/{planner-DsmMRIZR.mjs → planner-ogy-Zy4O.mjs} +6 -5
- package/dist/{planner-DsmMRIZR.mjs.map → planner-ogy-Zy4O.mjs.map} +1 -1
- package/dist/{postgres-Dwxe290H.mjs → postgres-CtOzfoQU.mjs} +1 -1
- package/dist/{postgres-Dwxe290H.mjs.map → postgres-CtOzfoQU.mjs.map} +1 -1
- package/dist/{program-Bed-Pd-8.mjs → program-B7N5OIJR.mjs} +36 -33
- package/dist/{program-Bed-Pd-8.mjs.map → program-B7N5OIJR.mjs.map} +1 -1
- package/dist/query-feedback-DGoVRaWD.mjs +3 -0
- package/dist/{query-feedback-DZfjI34w.mjs → query-feedback-Dqz-HC__.mjs} +1 -1
- package/dist/{query-feedback-DZfjI34w.mjs.map → query-feedback-Dqz-HC__.mjs.map} +1 -1
- package/dist/router-BTIGZR2_.mjs +3 -0
- package/dist/{router-DHracTUC.mjs → router-DD6D4j31.mjs} +1 -1
- package/dist/{router-DHracTUC.mjs.map → router-DD6D4j31.mjs.map} +1 -1
- package/dist/runtime-paths-rni52zHX.mjs +50 -0
- package/dist/runtime-paths-rni52zHX.mjs.map +1 -0
- package/dist/scope-CfYnjy5R.mjs +360 -0
- package/dist/scope-CfYnjy5R.mjs.map +1 -0
- package/dist/{server-BOuAOj9b.mjs → server-DvN-IFIV.mjs} +42 -3
- package/dist/server-DvN-IFIV.mjs.map +1 -0
- package/dist/skills/Worker/SKILL.md +3 -0
- package/dist/{sources-ju6AA7lL.mjs → sources-CS43CpuS.mjs} +1 -1
- package/dist/{sources-ju6AA7lL.mjs.map → sources-CS43CpuS.mjs.map} +1 -1
- package/dist/{sqlite-kFZeWCJ2.mjs → sqlite-Cq3gJcq9.mjs} +1 -1
- package/dist/{sqlite-kFZeWCJ2.mjs.map → sqlite-Cq3gJcq9.mjs.map} +1 -1
- package/dist/{state-ahFAbD8b.mjs → state-BTa10SOM.mjs} +1 -1
- package/dist/{state-ahFAbD8b.mjs.map → state-BTa10SOM.mjs.map} +1 -1
- package/dist/{state-DH_mp5Xe.mjs → state-RXepvbnO.mjs} +1 -1
- package/dist/{themes-DqhNUUQk.mjs → themes-B7h9YDiy.mjs} +1 -1
- package/dist/{themes-DqhNUUQk.mjs.map → themes-B7h9YDiy.mjs.map} +1 -1
- package/dist/{tools-COna9vZR.mjs → tools-BW3NLMPk.mjs} +1 -1
- package/dist/{tools-CMlHa9lZ.mjs → tools-LlzcV_eK.mjs} +14 -14
- package/dist/{tools-CMlHa9lZ.mjs.map → tools-LlzcV_eK.mjs.map} +1 -1
- package/dist/{trace-DTP6k4B_.mjs → trace-u4hT-pLD.mjs} +1 -1
- package/dist/{trace-DTP6k4B_.mjs.map → trace-u4hT-pLD.mjs.map} +1 -1
- package/dist/{vault-indexer-YMH22mLU.mjs → vault-indexer-mata9S7b.mjs} +1 -1
- package/dist/{vault-indexer-YMH22mLU.mjs.map → vault-indexer-mata9S7b.mjs.map} +1 -1
- package/dist/work-queue-worker-19r0cpH4.mjs +14 -0
- package/dist/{work-queue-worker-B3nl8yyn.mjs → work-queue-worker-DXh_FnT5.mjs} +4 -4
- package/dist/{work-queue-worker-B3nl8yyn.mjs.map → work-queue-worker-DXh_FnT5.mjs.map} +1 -1
- package/dist/{zettelkasten-DDvKRm2V.mjs → zettelkasten-F0D_XfQ4.mjs} +3 -3
- package/dist/{zettelkasten-DDvKRm2V.mjs.map → zettelkasten-F0D_XfQ4.mjs.map} +1 -1
- package/docs/commands/worker.md +1 -1
- package/docs/merge-demo.md +1 -0
- package/docs/worker.md +40 -0
- package/package.json +1 -1
- package/plugins/productivity/hooks/hooks.json +5 -0
- package/src/hooks/ts/user-prompt/worker-supervision.ts +123 -0
- package/dist/chain-zMjCDW-R.mjs.map +0 -1
- package/dist/config-D1G9IFpn.mjs.map +0 -1
- package/dist/config-Dl8lT4Lu.mjs.map +0 -1
- package/dist/context-handover-cache-C6_rs7JT.mjs.map +0 -1
- package/dist/daemon-BSL9sXxm.mjs +0 -22
- package/dist/daemon-CpdVUnl_.mjs.map +0 -1
- package/dist/detector-BLVOCiJ_.mjs +0 -5
- package/dist/factory-C7ua_3ik.mjs +0 -3
- package/dist/fallback-CgQ_x4I7.mjs.map +0 -1
- package/dist/main-resolver-CM1IHbuu.mjs.map +0 -1
- package/dist/main-resolver-E5WlilhM.mjs +0 -6
- package/dist/query-feedback-CGcEiGtY.mjs +0 -3
- package/dist/router-CAtyVQud.mjs +0 -3
- package/dist/server-BOuAOj9b.mjs.map +0 -1
- 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-
|
|
7
|
-
import { n as getBackendOutage, t as createStorageBackend } from "./factory-
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import { t as
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
1711
|
+
//# sourceMappingURL=daemon-DvXih78C.mjs.map
|