@monoes/monomindcli 2.9.3 → 2.9.5
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/.claude/skills/mastermind-createorg/SKILL.md +2 -1
- package/README.md +9 -5
- package/dist/src/autopilot-state.d.ts +16 -4
- package/dist/src/autopilot-state.d.ts.map +1 -1
- package/dist/src/autopilot-state.js +42 -19
- package/dist/src/autopilot-state.js.map +1 -1
- package/dist/src/commands/agent-lifecycle.js +4 -4
- package/dist/src/commands/agent-lifecycle.js.map +1 -1
- package/dist/src/commands/autopilot.d.ts.map +1 -1
- package/dist/src/commands/autopilot.js +106 -25
- package/dist/src/commands/autopilot.js.map +1 -1
- package/dist/src/commands/doctor-project-checks.d.ts.map +1 -1
- package/dist/src/commands/doctor-project-checks.js +20 -3
- package/dist/src/commands/doctor-project-checks.js.map +1 -1
- package/dist/src/commands/hooks-coverage-commands.d.ts.map +1 -1
- package/dist/src/commands/hooks-coverage-commands.js +67 -75
- package/dist/src/commands/hooks-coverage-commands.js.map +1 -1
- package/dist/src/commands/hooks-routing-commands.d.ts.map +1 -1
- package/dist/src/commands/hooks-routing-commands.js +12 -26
- package/dist/src/commands/hooks-routing-commands.js.map +1 -1
- package/dist/src/commands/hooks-workers.d.ts.map +1 -1
- package/dist/src/commands/hooks-workers.js +16 -47
- package/dist/src/commands/hooks-workers.js.map +1 -1
- package/dist/src/commands/hooks.d.ts.map +1 -1
- package/dist/src/commands/hooks.js +96 -54
- package/dist/src/commands/hooks.js.map +1 -1
- package/dist/src/commands/memory-list.d.ts.map +1 -1
- package/dist/src/commands/memory-list.js +79 -36
- package/dist/src/commands/memory-list.js.map +1 -1
- package/dist/src/commands/org.js +2 -2
- package/dist/src/commands/org.js.map +1 -1
- package/dist/src/commands/security-cve.d.ts.map +1 -1
- package/dist/src/commands/security-cve.js +11 -1
- package/dist/src/commands/security-cve.js.map +1 -1
- package/dist/src/commands/security-misc.d.ts +9 -0
- package/dist/src/commands/security-misc.d.ts.map +1 -1
- package/dist/src/commands/security-misc.js +54 -33
- package/dist/src/commands/security-misc.js.map +1 -1
- package/dist/src/commands/security-scan.d.ts.map +1 -1
- package/dist/src/commands/security-scan.js +11 -3
- package/dist/src/commands/security-scan.js.map +1 -1
- package/dist/src/commands/swarm.d.ts.map +1 -1
- package/dist/src/commands/swarm.js +137 -96
- package/dist/src/commands/swarm.js.map +1 -1
- package/dist/src/init/statusline-generator.d.ts.map +1 -1
- package/dist/src/init/statusline-generator.js +47 -45
- package/dist/src/init/statusline-generator.js.map +1 -1
- package/dist/src/init/types.d.ts +1 -1
- package/dist/src/init/types.d.ts.map +1 -1
- package/dist/src/init/types.js +5 -1
- package/dist/src/init/types.js.map +1 -1
- package/dist/src/mcp-server.d.ts +6 -0
- package/dist/src/mcp-server.d.ts.map +1 -1
- package/dist/src/mcp-server.js +47 -22
- package/dist/src/mcp-server.js.map +1 -1
- package/dist/src/mcp-tools/embeddings-tools.js +2 -2
- package/dist/src/mcp-tools/embeddings-tools.js.map +1 -1
- package/dist/src/mcp-tools/hive-mind-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/hive-mind-tools.js +13 -11
- package/dist/src/mcp-tools/hive-mind-tools.js.map +1 -1
- package/dist/src/mcp-tools/hooks-intelligence.d.ts.map +1 -1
- package/dist/src/mcp-tools/hooks-intelligence.js +99 -68
- package/dist/src/mcp-tools/hooks-intelligence.js.map +1 -1
- package/dist/src/mcp-tools/hooks-routing.d.ts.map +1 -1
- package/dist/src/mcp-tools/hooks-routing.js +403 -215
- package/dist/src/mcp-tools/hooks-routing.js.map +1 -1
- package/dist/src/mcp-tools/monograph/impact-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/monograph/impact-tools.js +109 -44
- package/dist/src/mcp-tools/monograph/impact-tools.js.map +1 -1
- package/dist/src/mcp-tools/performance-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/performance-tools.js +10 -15
- package/dist/src/mcp-tools/performance-tools.js.map +1 -1
- package/dist/src/mcp-tools/system-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/system-tools.js +100 -38
- package/dist/src/mcp-tools/system-tools.js.map +1 -1
- package/dist/src/memory/memory-bridge.d.ts.map +1 -1
- package/dist/src/memory/memory-bridge.js +172 -69
- package/dist/src/memory/memory-bridge.js.map +1 -1
- package/dist/src/monovector/diff-classifier.js +3 -3
- package/dist/src/monovector/diff-classifier.js.map +1 -1
- package/dist/src/orgrt/daemon.d.ts +5 -3
- package/dist/src/orgrt/daemon.d.ts.map +1 -1
- package/dist/src/orgrt/daemon.js +291 -93
- package/dist/src/orgrt/daemon.js.map +1 -1
- package/dist/src/orgrt/kimicode-runner.js +1 -1
- package/dist/src/orgrt/kimicode-runner.js.map +1 -1
- package/dist/src/orgrt/opencode-runner.js +1 -1
- package/dist/src/orgrt/opencode-runner.js.map +1 -1
- package/dist/src/orgrt/remote.d.ts.map +1 -1
- package/dist/src/orgrt/remote.js +12 -6
- package/dist/src/orgrt/remote.js.map +1 -1
- package/dist/src/orgrt/server.d.ts.map +1 -1
- package/dist/src/orgrt/server.js +71 -14
- package/dist/src/orgrt/server.js.map +1 -1
- package/dist/src/types.d.ts +1 -1
- package/dist/src/types.d.ts.map +1 -1
- package/dist/src/ui/server.mjs +2647 -838
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +7 -7
package/dist/src/orgrt/daemon.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// packages/@monomind/cli/src/orgrt/daemon.ts
|
|
2
2
|
// monolean: single-process inter-org — upgrade path = daemon-to-daemon HTTP when multi-host is real
|
|
3
3
|
import { readFileSync, mkdirSync, existsSync, unlinkSync } from 'node:fs';
|
|
4
|
-
import {
|
|
4
|
+
import { execFileSync } from 'node:child_process';
|
|
5
5
|
import { join, isAbsolute } from 'node:path';
|
|
6
6
|
import { writeJsonFileAtomic } from '../utils/json-file.js';
|
|
7
7
|
import { reapOrphanedSdkProcesses } from '../utils/resource-governor.js';
|
|
@@ -12,9 +12,9 @@ import { runAgentSession } from './session.js';
|
|
|
12
12
|
import { attachForwarder } from './forwarder.js';
|
|
13
13
|
import { BrokerLease, normalizeCredential } from './broker.js';
|
|
14
14
|
import { drainInbox } from './inbox.js';
|
|
15
|
-
import { OrgDefSchema, ORG_DIR } from './types.js';
|
|
15
|
+
import { OrgDefSchema, ORG_DIR, } from './types.js';
|
|
16
16
|
import { TaskDag } from './task-dag.js';
|
|
17
|
-
import { summarizeRun, readRunEvents, readHistory, historyFile } from './reporting.js';
|
|
17
|
+
import { summarizeRun, readRunEvents, readHistory, historyFile, } from './reporting.js';
|
|
18
18
|
import { getResourceLimits, configureResourceLimits } from '../utils/resource-governor.js';
|
|
19
19
|
import { OpencodeAgentRunner } from './opencode-runner.js';
|
|
20
20
|
import { KimiCodeAgentRunner } from './kimicode-runner.js';
|
|
@@ -22,7 +22,7 @@ import { VercelAgentRunner } from './vercel-runner.js';
|
|
|
22
22
|
import { CodexAgentRunner } from './codex-runner.js';
|
|
23
23
|
import { AntigravityAgentRunner } from './antigravity-runner.js';
|
|
24
24
|
import { captureCheckpoint } from './checkpoint.js';
|
|
25
|
-
import { loadGlobalFenceConfig, mergeFenceConfigs, createFenceForRole } from './fence.js';
|
|
25
|
+
import { loadGlobalFenceConfig, mergeFenceConfigs, createFenceForRole, } from './fence.js';
|
|
26
26
|
// ── Extracted module imports ────────────────────────────────────────────
|
|
27
27
|
import * as approvalOps from './approvals.js';
|
|
28
28
|
import * as questionOps from './questions.js';
|
|
@@ -35,7 +35,9 @@ import * as checkpointOps from './checkpoint-ops.js';
|
|
|
35
35
|
class OtelTracer {
|
|
36
36
|
enabled = false;
|
|
37
37
|
spans = new Map();
|
|
38
|
-
enable() {
|
|
38
|
+
enable() {
|
|
39
|
+
this.enabled = true;
|
|
40
|
+
}
|
|
39
41
|
startSpan(name, metadata = {}) {
|
|
40
42
|
if (!this.enabled)
|
|
41
43
|
return;
|
|
@@ -72,7 +74,9 @@ function autoRuntimeFromProvider(kind) {
|
|
|
72
74
|
return undefined;
|
|
73
75
|
}
|
|
74
76
|
export function resolveRunner(orgRuntime, providerKind) {
|
|
75
|
-
const selected = orgRuntime ??
|
|
77
|
+
const selected = orgRuntime ??
|
|
78
|
+
autoRuntimeFromProvider(providerKind) ??
|
|
79
|
+
process.env.MONOMIND_RUNTIME;
|
|
76
80
|
if (selected === 'opencode')
|
|
77
81
|
return new OpencodeAgentRunner();
|
|
78
82
|
if (selected === 'kimicode')
|
|
@@ -99,7 +103,7 @@ export function resolveRoleRunner(roleRuntime, orgRuntime, roleProviderKind, org
|
|
|
99
103
|
/** Per-role token budget: a role's own `budget_tokens` wins; otherwise the
|
|
100
104
|
* even split of run_config.budget_tokens across all roles. */
|
|
101
105
|
export function roleTokenBudget(role, def) {
|
|
102
|
-
return role.budget_tokens ?? Math.floor((def.run_config.budget_tokens ?? 1_000_000) / def.roles.length);
|
|
106
|
+
return (role.budget_tokens ?? Math.floor((def.run_config.budget_tokens ?? 1_000_000) / def.roles.length));
|
|
103
107
|
}
|
|
104
108
|
/** Bounded ring buffer for agent terminal scrollback. */
|
|
105
109
|
export class ScrollbackBuffer {
|
|
@@ -113,8 +117,12 @@ export class ScrollbackBuffer {
|
|
|
113
117
|
if (this.lines.length > this.maxLines)
|
|
114
118
|
this.lines.splice(0, this.lines.length - this.maxLines);
|
|
115
119
|
}
|
|
116
|
-
snapshot() {
|
|
117
|
-
|
|
120
|
+
snapshot() {
|
|
121
|
+
return [...this.lines];
|
|
122
|
+
}
|
|
123
|
+
clear() {
|
|
124
|
+
this.lines.length = 0;
|
|
125
|
+
}
|
|
118
126
|
}
|
|
119
127
|
export class OrgDaemon {
|
|
120
128
|
root;
|
|
@@ -142,7 +150,9 @@ export class OrgDaemon {
|
|
|
142
150
|
/** @internal */ recallUsage = new Map();
|
|
143
151
|
/** @internal */ orgLearnedRuns = new Set();
|
|
144
152
|
/** @internal */ abandoned = new Map();
|
|
145
|
-
constructor(
|
|
153
|
+
constructor(
|
|
154
|
+
/** @internal */ root,
|
|
155
|
+
/** @internal */ opts = {}) {
|
|
146
156
|
this.root = root;
|
|
147
157
|
this.opts = opts;
|
|
148
158
|
}
|
|
@@ -157,8 +167,12 @@ export class OrgDaemon {
|
|
|
157
167
|
this.globalSubscribers.add(fn);
|
|
158
168
|
return () => this.globalSubscribers.delete(fn);
|
|
159
169
|
}
|
|
160
|
-
listOrgs() {
|
|
161
|
-
|
|
170
|
+
listOrgs() {
|
|
171
|
+
return [...this.orgs.values()];
|
|
172
|
+
}
|
|
173
|
+
getOrg(name) {
|
|
174
|
+
return this.orgs.get(name);
|
|
175
|
+
}
|
|
162
176
|
/** Hot-reload an org definition from disk without stopping running sessions.
|
|
163
177
|
* Applies: goal, run_config, schedule. New roles are added as pending (lazy-spawnable).
|
|
164
178
|
* Removed roles are NOT killed — they finish their current work and won't be re-spawned.
|
|
@@ -184,8 +198,8 @@ export class OrgDaemon {
|
|
|
184
198
|
changed.push(`run_config.${key}`);
|
|
185
199
|
}
|
|
186
200
|
}
|
|
187
|
-
const existingRoleIds = new Set(running.def.roles.map(r => r.id));
|
|
188
|
-
const newRoleIds = new Set(newDef.roles.map(r => r.id));
|
|
201
|
+
const existingRoleIds = new Set(running.def.roles.map((r) => r.id));
|
|
202
|
+
const newRoleIds = new Set(newDef.roles.map((r) => r.id));
|
|
189
203
|
for (const role of newDef.roles) {
|
|
190
204
|
if (!existingRoleIds.has(role.id)) {
|
|
191
205
|
running.def.roles.push(role);
|
|
@@ -199,16 +213,23 @@ export class OrgDaemon {
|
|
|
199
213
|
if (!newRoleIds.has(id))
|
|
200
214
|
removedRoles.push(id);
|
|
201
215
|
}
|
|
202
|
-
running.bus.emit({
|
|
216
|
+
running.bus.emit({
|
|
217
|
+
type: 'audit',
|
|
218
|
+
reason: 'hot-reload',
|
|
203
219
|
msg: `org def reloaded: ${changed.length} fields changed, ${newRoles.length} new roles, ${removedRoles.length} removed roles`,
|
|
204
|
-
data: { changed, newRoles, removedRoles }
|
|
220
|
+
data: { changed, newRoles, removedRoles },
|
|
221
|
+
});
|
|
205
222
|
return { changed, newRoles, removedRoles };
|
|
206
223
|
}
|
|
207
224
|
/** Names of the orgs this daemon currently has running. Snapshot — safe to
|
|
208
225
|
* iterate while stopOrg() mutates the underlying map. */
|
|
209
|
-
listRunning() {
|
|
226
|
+
listRunning() {
|
|
227
|
+
return [...this.orgs.keys()];
|
|
228
|
+
}
|
|
210
229
|
/** Hook for the SSE server — registers a listener for all bus events across all orgs. */
|
|
211
|
-
onBusEvent = (fn) => {
|
|
230
|
+
onBusEvent = (fn) => {
|
|
231
|
+
this.subscribe(fn);
|
|
232
|
+
};
|
|
212
233
|
/** Snapshot of all running orgs for dashboard initial load. */
|
|
213
234
|
getStatusSnapshot = () => {
|
|
214
235
|
const orgs = [];
|
|
@@ -216,13 +237,15 @@ export class OrgDaemon {
|
|
|
216
237
|
const roles = [];
|
|
217
238
|
for (const [roleId, agent] of running.agents) {
|
|
218
239
|
roles.push({
|
|
219
|
-
id: roleId,
|
|
240
|
+
id: roleId,
|
|
241
|
+
status: agent.status,
|
|
220
242
|
worktree: agent.worktreePath ?? null,
|
|
221
243
|
metrics: agent.metrics,
|
|
222
244
|
});
|
|
223
245
|
}
|
|
224
246
|
orgs.push({
|
|
225
|
-
name,
|
|
247
|
+
name,
|
|
248
|
+
run: running.run,
|
|
226
249
|
roles,
|
|
227
250
|
pendingRoles: running.pendingRoles ? [...running.pendingRoles.keys()] : [],
|
|
228
251
|
tasks: running.taskDag?.all() ?? [],
|
|
@@ -275,23 +298,37 @@ export class OrgDaemon {
|
|
|
275
298
|
let worktreePath;
|
|
276
299
|
if (ws === 'worktree') {
|
|
277
300
|
worktreePath = join(this.root, ORG_DIR, name, 'worktree');
|
|
278
|
-
const {
|
|
301
|
+
const { execFileSync } = await import('node:child_process');
|
|
279
302
|
try {
|
|
280
303
|
// Remove stale worktree from a previous run
|
|
281
304
|
if (existsSync(worktreePath)) {
|
|
282
305
|
// R4: bound the call — a wedged git hook (git-lfs, gc lock, gpg sign
|
|
283
306
|
// prompt) would otherwise hang the whole daemon indefinitely.
|
|
284
|
-
|
|
307
|
+
// SEC-5: execFileSync + argv — no shell interpolation of worktreePath.
|
|
308
|
+
execFileSync('git', ['worktree', 'remove', '--force', worktreePath], {
|
|
309
|
+
cwd: this.root,
|
|
310
|
+
stdio: 'ignore',
|
|
311
|
+
timeout: 30_000,
|
|
312
|
+
});
|
|
285
313
|
}
|
|
286
314
|
}
|
|
287
|
-
catch {
|
|
288
|
-
|
|
315
|
+
catch {
|
|
316
|
+
/* best-effort cleanup */
|
|
317
|
+
}
|
|
318
|
+
execFileSync('git', ['worktree', 'add', worktreePath, 'HEAD', '--detach'], {
|
|
319
|
+
cwd: this.root,
|
|
320
|
+
stdio: 'ignore',
|
|
321
|
+
timeout: 30_000,
|
|
322
|
+
});
|
|
289
323
|
cwd = worktreePath;
|
|
290
324
|
}
|
|
291
325
|
else {
|
|
292
|
-
cwd =
|
|
293
|
-
|
|
294
|
-
|
|
326
|
+
cwd =
|
|
327
|
+
ws === 'repo'
|
|
328
|
+
? this.root
|
|
329
|
+
: ws === 'isolated'
|
|
330
|
+
? join(this.root, ORG_DIR, name, 'workspace')
|
|
331
|
+
: ws;
|
|
295
332
|
}
|
|
296
333
|
mkdirSync(cwd, { recursive: true });
|
|
297
334
|
// An org must be able to seat its whole roster. maxSdkProcesses is sized for
|
|
@@ -306,7 +343,8 @@ export class OrgDaemon {
|
|
|
306
343
|
// OpencodeAgentRunner, and CodexAgentRunner each spawn one subprocess per role.
|
|
307
344
|
// The current sizing (def.roles.length) is therefore safe — it over-provisions
|
|
308
345
|
// for in-process runners but never under-provisions for subprocess runners.
|
|
309
|
-
if (!process.env.MONOMIND_MAX_SDK_PROCS &&
|
|
346
|
+
if (!process.env.MONOMIND_MAX_SDK_PROCS &&
|
|
347
|
+
getResourceLimits().maxSdkProcesses < def.roles.length) {
|
|
310
348
|
configureResourceLimits({ maxSdkProcesses: def.roles.length });
|
|
311
349
|
}
|
|
312
350
|
// Validate per-role providers before spawning anything (fail-fast: a
|
|
@@ -329,10 +367,8 @@ export class OrgDaemon {
|
|
|
329
367
|
const MAX_COLLECTED = 1000;
|
|
330
368
|
const collected = [];
|
|
331
369
|
let lastActivity = Date.now();
|
|
332
|
-
bus.subscribe(e => {
|
|
333
|
-
const slim = e.data?.content != null
|
|
334
|
-
? { ...e, data: { ...e.data, content: undefined } }
|
|
335
|
-
: e;
|
|
370
|
+
bus.subscribe((e) => {
|
|
371
|
+
const slim = e.data?.content != null ? { ...e, data: { ...e.data, content: undefined } } : e;
|
|
336
372
|
collected.push(slim);
|
|
337
373
|
if (collected.length > MAX_COLLECTED)
|
|
338
374
|
collected.splice(0, collected.length - MAX_COLLECTED);
|
|
@@ -347,7 +383,7 @@ export class OrgDaemon {
|
|
|
347
383
|
// against a concurrent manual stop.
|
|
348
384
|
if (e.type === 'status' && e.reason === 'org-complete') {
|
|
349
385
|
const t = setTimeout(() => {
|
|
350
|
-
this.stopOrg(name, { drainMs: COMPLETE_DRAIN_MS }).catch(err => console.error(`org ${name}: auto-stop after org_complete failed:`, err instanceof Error ? err.message : err));
|
|
386
|
+
this.stopOrg(name, { drainMs: COMPLETE_DRAIN_MS }).catch((err) => console.error(`org ${name}: auto-stop after org_complete failed:`, err instanceof Error ? err.message : err));
|
|
351
387
|
}, 1000);
|
|
352
388
|
t.unref?.();
|
|
353
389
|
}
|
|
@@ -381,7 +417,14 @@ export class OrgDaemon {
|
|
|
381
417
|
});
|
|
382
418
|
if (this.opts.forward !== false)
|
|
383
419
|
this.forwarders.set(name, attachForwarder(bus, this.opts.controlJson ?? join(this.root, '.monomind/control.json')));
|
|
384
|
-
const running = {
|
|
420
|
+
const running = {
|
|
421
|
+
def,
|
|
422
|
+
run,
|
|
423
|
+
bus,
|
|
424
|
+
agents: new Map(),
|
|
425
|
+
busEvents: () => [...collected],
|
|
426
|
+
workdir: cwd,
|
|
427
|
+
};
|
|
385
428
|
this.orgs.set(name, running);
|
|
386
429
|
// ── MonoFence guardrail: pre-create per-role instances ────────────────
|
|
387
430
|
const globalFence = loadGlobalFenceConfig(this.root);
|
|
@@ -404,7 +447,9 @@ export class OrgDaemon {
|
|
|
404
447
|
});
|
|
405
448
|
}
|
|
406
449
|
}
|
|
407
|
-
catch {
|
|
450
|
+
catch {
|
|
451
|
+
/* monofence-ai not installed — skip silently */
|
|
452
|
+
}
|
|
408
453
|
}
|
|
409
454
|
if (roleFences.size > 0)
|
|
410
455
|
running.fences = roleFences;
|
|
@@ -415,7 +460,7 @@ export class OrgDaemon {
|
|
|
415
460
|
// kickoff went to (type==='boss' || reports_to===null || roles[0]), so a
|
|
416
461
|
// fallback-selected boss could be told to call org_complete without having
|
|
417
462
|
// the tool.
|
|
418
|
-
const bossRole = def.roles.find(r => r.type === 'boss' || r.reports_to === null) ?? def.roles[0];
|
|
463
|
+
const bossRole = def.roles.find((r) => r.type === 'boss' || r.reports_to === null) ?? def.roles[0];
|
|
419
464
|
// Canonical entity names from the org KG — injected into the coordinator
|
|
420
465
|
// prompt so org_learn extractions reuse them instead of minting duplicates.
|
|
421
466
|
const glossary = await (async () => {
|
|
@@ -443,26 +488,53 @@ export class OrgDaemon {
|
|
|
443
488
|
if (ws === 'worktree-per-role' && role.id !== bossRole.id) {
|
|
444
489
|
const wtPath = join(this.root, ORG_DIR, name, `worktree-${role.id}`);
|
|
445
490
|
try {
|
|
446
|
-
// Q7: top-level `import {
|
|
491
|
+
// Q7: top-level `import { execFileSync }` replaces the inlined
|
|
447
492
|
// `require('node:child_process')` that broke ESM at runtime —
|
|
448
493
|
// vitest's CJS shim masked it in tests but the built package
|
|
449
494
|
// threw "require is not defined" in real Node ESM execution.
|
|
495
|
+
// SEC-5: argv-array form, no shell.
|
|
450
496
|
if (existsSync(wtPath)) {
|
|
451
497
|
try {
|
|
452
|
-
|
|
498
|
+
execFileSync('git', ['worktree', 'remove', '--force', wtPath], {
|
|
499
|
+
cwd: this.root,
|
|
500
|
+
stdio: 'ignore',
|
|
501
|
+
timeout: 30_000,
|
|
502
|
+
});
|
|
503
|
+
}
|
|
504
|
+
catch {
|
|
505
|
+
/* best-effort */
|
|
453
506
|
}
|
|
454
|
-
catch { /* best-effort */ }
|
|
455
507
|
}
|
|
456
|
-
|
|
508
|
+
execFileSync('git', ['worktree', 'add', wtPath, 'HEAD', '--detach'], {
|
|
509
|
+
cwd: this.root,
|
|
510
|
+
stdio: 'ignore',
|
|
511
|
+
timeout: 30_000,
|
|
512
|
+
});
|
|
457
513
|
roleCwd = wtPath;
|
|
458
514
|
}
|
|
459
|
-
catch {
|
|
515
|
+
catch {
|
|
516
|
+
/* fallback to shared cwd if git worktree fails */
|
|
517
|
+
}
|
|
460
518
|
}
|
|
461
519
|
const mailbox = new Mailbox();
|
|
462
520
|
const policy = new PolicyEngine(role.id, { maxTokens: role.budget_tokens ?? perRoleBudget, ...(role.policy ?? {}) }, bus, roleCwd);
|
|
463
|
-
const runtime = {
|
|
521
|
+
const runtime = {
|
|
522
|
+
mailbox,
|
|
523
|
+
policy,
|
|
524
|
+
status: 'running',
|
|
525
|
+
done: Promise.resolve(),
|
|
526
|
+
metrics: { tokens: 0, costUsd: 0 },
|
|
527
|
+
worktreePath: roleCwd !== cwd ? roleCwd : undefined,
|
|
528
|
+
scrollback: new ScrollbackBuffer(),
|
|
529
|
+
};
|
|
464
530
|
const sessionOpts = {
|
|
465
|
-
org: name,
|
|
531
|
+
org: name,
|
|
532
|
+
role,
|
|
533
|
+
bus,
|
|
534
|
+
policy,
|
|
535
|
+
mailbox,
|
|
536
|
+
cwd: roleCwd,
|
|
537
|
+
def,
|
|
466
538
|
// Pass the org state directory so runners that persist per-role state
|
|
467
539
|
// (VercelAgentRunner session files) write under .monomind/orgs/<name>
|
|
468
540
|
// instead of polluting the workspace cwd.
|
|
@@ -483,7 +555,13 @@ export class OrgDaemon {
|
|
|
483
555
|
fence: roleFences.get(role.id),
|
|
484
556
|
onComplete: role.id === bossRole.id
|
|
485
557
|
? (r, outcome, summary) => {
|
|
486
|
-
bus.emit({
|
|
558
|
+
bus.emit({
|
|
559
|
+
type: 'status',
|
|
560
|
+
from: r,
|
|
561
|
+
reason: 'org-complete',
|
|
562
|
+
msg: `run outcome: ${outcome}`,
|
|
563
|
+
data: { outcome, summary },
|
|
564
|
+
});
|
|
487
565
|
}
|
|
488
566
|
: undefined,
|
|
489
567
|
// #11: a boss that overflows its context window isn't a crash (it keeps
|
|
@@ -493,25 +571,50 @@ export class OrgDaemon {
|
|
|
493
571
|
onContextLimit: role.id === bossRole.id ? () => this.scheduleBossRestart(name) : undefined,
|
|
494
572
|
recall: async (r, q) => {
|
|
495
573
|
const answer = await this.recallOrgMemory(name, def, q, r);
|
|
496
|
-
bus.emit({
|
|
574
|
+
bus.emit({
|
|
575
|
+
type: 'status',
|
|
576
|
+
from: r,
|
|
577
|
+
reason: 'org-recall',
|
|
578
|
+
msg: `recall: ${q.slice(0, 80)}`,
|
|
579
|
+
data: { hits: answer.hits },
|
|
580
|
+
});
|
|
497
581
|
return answer.text;
|
|
498
582
|
},
|
|
499
583
|
searchKnowledge: async (r, q) => {
|
|
500
584
|
const answer = await this.searchProjectKnowledge(q);
|
|
501
|
-
bus.emit({
|
|
585
|
+
bus.emit({
|
|
586
|
+
type: 'status',
|
|
587
|
+
from: r,
|
|
588
|
+
reason: 'knowledge-search',
|
|
589
|
+
msg: `knowledge: ${q.slice(0, 80)}`,
|
|
590
|
+
data: { hits: answer.hits },
|
|
591
|
+
});
|
|
502
592
|
return answer.text;
|
|
503
593
|
},
|
|
504
594
|
glossary,
|
|
505
595
|
remember: async (r, content, scope) => {
|
|
506
596
|
const text = await this.rememberOrgMemory(name, def, r, content, scope, run);
|
|
507
|
-
bus.emit({
|
|
597
|
+
bus.emit({
|
|
598
|
+
type: 'status',
|
|
599
|
+
from: r,
|
|
600
|
+
reason: 'org-remember',
|
|
601
|
+
msg: `remember (${scope}): ${content.slice(0, 80)}`,
|
|
602
|
+
data: { scope },
|
|
603
|
+
});
|
|
508
604
|
return text;
|
|
509
605
|
},
|
|
510
606
|
learn: async (r, payload) => {
|
|
511
607
|
const text = await this.learnOrgKnowledge(name, run, payload);
|
|
512
608
|
bus.emit({
|
|
513
|
-
type: 'status',
|
|
514
|
-
|
|
609
|
+
type: 'status',
|
|
610
|
+
from: r,
|
|
611
|
+
reason: 'org-learn',
|
|
612
|
+
msg: `learn: ${text.slice(0, 120)}`,
|
|
613
|
+
data: {
|
|
614
|
+
nodes: payload.nodes?.length ?? 0,
|
|
615
|
+
edges: payload.edges?.length ?? 0,
|
|
616
|
+
rules: payload.rules?.length ?? 0,
|
|
617
|
+
},
|
|
515
618
|
});
|
|
516
619
|
return text;
|
|
517
620
|
},
|
|
@@ -532,7 +635,8 @@ export class OrgDaemon {
|
|
|
532
635
|
// Leaving it undefined for the default path is what keeps
|
|
533
636
|
// Claude/Antigravity orgs byte-for-byte unchanged. Session opts are
|
|
534
637
|
// built per role here in spawnRole, so each role gets its own runner.
|
|
535
|
-
runner: this.opts.runner ??
|
|
638
|
+
runner: this.opts.runner ??
|
|
639
|
+
resolveRoleRunner(role.runtime, def.runtime, role.provider?.kind, undefined),
|
|
536
640
|
};
|
|
537
641
|
// Supervised session: transient crashes (provider blips, network) restart
|
|
538
642
|
// with backoff; a crash with the mailbox already closed, or one that
|
|
@@ -562,7 +666,8 @@ export class OrgDaemon {
|
|
|
562
666
|
if (killedByStop) {
|
|
563
667
|
runtime.status = 'ended';
|
|
564
668
|
bus.emit({
|
|
565
|
-
type: 'status',
|
|
669
|
+
type: 'status',
|
|
670
|
+
from: role.id,
|
|
566
671
|
msg: `agent "${role.id}" terminated by stop (was still working when drain window expired)`,
|
|
567
672
|
reason: 'terminated-by-stop',
|
|
568
673
|
});
|
|
@@ -576,10 +681,16 @@ export class OrgDaemon {
|
|
|
576
681
|
mailbox.close();
|
|
577
682
|
const isContextLimit = OrgDaemon.CONTEXT_LIMIT_RE.test(message);
|
|
578
683
|
bus.emit({
|
|
579
|
-
type: 'audit',
|
|
684
|
+
type: 'audit',
|
|
685
|
+
from: role.id,
|
|
580
686
|
msg: `agent "${role.id}" crashed: ${message}`,
|
|
581
687
|
reason: isContextLimit ? 'agent-context-limit' : 'agent-session-crash',
|
|
582
|
-
data: {
|
|
688
|
+
data: {
|
|
689
|
+
agentId: role.id,
|
|
690
|
+
error: message,
|
|
691
|
+
restarts: attempt,
|
|
692
|
+
contextLimit: isContextLimit,
|
|
693
|
+
},
|
|
583
694
|
});
|
|
584
695
|
if (role.id !== bossRole.id) {
|
|
585
696
|
// #2/#3: a worker is gone for the rest of this run. Without this
|
|
@@ -594,8 +705,12 @@ export class OrgDaemon {
|
|
|
594
705
|
? ' This was a context-window overflow — re-dispatching the same task verbatim will fail identically. Break the work into smaller pieces (one file or section at a time) and do not paste large file contents in a single message.'
|
|
595
706
|
: '';
|
|
596
707
|
bossRt.mailbox.push(`[system] Worker "${role.id}" crashed and will not recover this run (${message}). It can no longer receive messages — stop messaging it. Reassign its outstanding work to another agent or take it on yourself.${guidance}`);
|
|
597
|
-
bus.emit({
|
|
598
|
-
|
|
708
|
+
bus.emit({
|
|
709
|
+
type: 'audit',
|
|
710
|
+
from: bossRole.id,
|
|
711
|
+
reason: 'worker-crashed',
|
|
712
|
+
msg: `worker "${role.id}" crashed (contextLimit=${isContextLimit}); coordinator notified to reassign`,
|
|
713
|
+
});
|
|
599
714
|
}
|
|
600
715
|
}
|
|
601
716
|
else {
|
|
@@ -613,7 +728,9 @@ export class OrgDaemon {
|
|
|
613
728
|
const fatal = err?.fatal === true;
|
|
614
729
|
if (fatal) {
|
|
615
730
|
bus.emit({
|
|
616
|
-
type: 'status',
|
|
731
|
+
type: 'status',
|
|
732
|
+
from: role.id,
|
|
733
|
+
reason: 'agent-fatal',
|
|
617
734
|
msg: `agent "${role.id}" hit a fatal (non-retryable) error — not restarting`,
|
|
618
735
|
});
|
|
619
736
|
crash();
|
|
@@ -624,10 +741,15 @@ export class OrgDaemon {
|
|
|
624
741
|
return;
|
|
625
742
|
}
|
|
626
743
|
bus.emit({
|
|
627
|
-
type: 'status',
|
|
744
|
+
type: 'status',
|
|
745
|
+
from: role.id,
|
|
746
|
+
reason: 'agent-restart',
|
|
628
747
|
msg: `agent "${role.id}" crashed (${message}) — restarting in ${BACKOFFS_MS[attempt]}ms (attempt ${attempt + 1}/${BACKOFFS_MS.length})`,
|
|
629
748
|
});
|
|
630
|
-
await new Promise(r => {
|
|
749
|
+
await new Promise((r) => {
|
|
750
|
+
const t = setTimeout(r, BACKOFFS_MS[attempt]);
|
|
751
|
+
t.unref?.();
|
|
752
|
+
});
|
|
631
753
|
if (mailbox.isClosed) {
|
|
632
754
|
crash();
|
|
633
755
|
return;
|
|
@@ -661,7 +783,9 @@ export class OrgDaemon {
|
|
|
661
783
|
// no-cjs-require-in-esm.test.ts.
|
|
662
784
|
reapOrphanedSdkProcesses(new Set(), process.pid);
|
|
663
785
|
}
|
|
664
|
-
catch {
|
|
786
|
+
catch {
|
|
787
|
+
/* best-effort */
|
|
788
|
+
}
|
|
665
789
|
};
|
|
666
790
|
process.on('exit', crashCleanup);
|
|
667
791
|
running._crashCleanup = crashCleanup;
|
|
@@ -672,17 +796,24 @@ export class OrgDaemon {
|
|
|
672
796
|
if (staleThreshold && staleThreshold > 0 && cwd === this.root) {
|
|
673
797
|
try {
|
|
674
798
|
const { execSync } = await import('node:child_process');
|
|
675
|
-
const behind = execSync('git rev-list --count HEAD..@{upstream} 2>/dev/null', {
|
|
799
|
+
const behind = execSync('git rev-list --count HEAD..@{upstream} 2>/dev/null', {
|
|
800
|
+
cwd,
|
|
801
|
+
encoding: 'utf8',
|
|
802
|
+
timeout: 10_000,
|
|
803
|
+
}).trim();
|
|
676
804
|
const count = parseInt(behind, 10);
|
|
677
805
|
if (!isNaN(count) && count > staleThreshold) {
|
|
678
806
|
bus.emit({
|
|
679
|
-
type: 'audit',
|
|
807
|
+
type: 'audit',
|
|
808
|
+
reason: 'stale-base',
|
|
680
809
|
msg: `working tree is ${count} commits behind upstream (threshold: ${staleThreshold}) — consider pulling before running`,
|
|
681
810
|
data: { behind: count, threshold: staleThreshold },
|
|
682
811
|
});
|
|
683
812
|
}
|
|
684
813
|
}
|
|
685
|
-
catch {
|
|
814
|
+
catch {
|
|
815
|
+
/* no upstream tracking or git unavailable — skip silently */
|
|
816
|
+
}
|
|
686
817
|
}
|
|
687
818
|
const boss = bossRole;
|
|
688
819
|
// Cross-run memory: brief the coordinator on the previous run so scheduled
|
|
@@ -695,9 +826,15 @@ export class OrgDaemon {
|
|
|
695
826
|
: `no recorded outcome (${prev.messages} messages, ${prev.assets.length} assets${prev.crashes.length ? `, ${prev.crashes.length} crashed agent(s)` : ''})`) +
|
|
696
827
|
`\nBuild on that work — do not redo what is already done.`
|
|
697
828
|
: '';
|
|
698
|
-
running.agents
|
|
829
|
+
running.agents
|
|
830
|
+
.get(boss.id)
|
|
831
|
+
.mailbox.push(`Org "${name}" started (run ${run}).\nGoal: ${taskOverride ?? def.goal}\n` +
|
|
699
832
|
`Coordinate your team via org_send. When the goal is achieved (or clearly can't be), record it with org_complete, then end your turn.${prevBrief}`);
|
|
700
|
-
bus.emit({
|
|
833
|
+
bus.emit({
|
|
834
|
+
type: 'status',
|
|
835
|
+
msg: `org started (${def.roles.length} agents)`,
|
|
836
|
+
data: { goal: taskOverride ?? def.goal },
|
|
837
|
+
});
|
|
701
838
|
this.persistState(name, 'running', run);
|
|
702
839
|
// Idle watchdog: a hung tool call (or a run that quietly finished without
|
|
703
840
|
// org_complete) produces no bus events, and every agent just waits. After
|
|
@@ -712,13 +849,13 @@ export class OrgDaemon {
|
|
|
712
849
|
let nudges = 0;
|
|
713
850
|
const idleStop = (msg) => {
|
|
714
851
|
bus.emit({ type: 'audit', reason: 'idle-stop', msg });
|
|
715
|
-
this.stopOrg(name).catch(err => console.error(`org ${name}: idle-stop failed:`, err instanceof Error ? err.message : err));
|
|
852
|
+
this.stopOrg(name).catch((err) => console.error(`org ${name}: idle-stop failed:`, err instanceof Error ? err.message : err));
|
|
716
853
|
};
|
|
717
854
|
const wd = setInterval(() => {
|
|
718
855
|
if (this.restarting.has(name))
|
|
719
856
|
return; // boss auto-restart in flight — don't nudge or stop
|
|
720
857
|
// A pending gate means the org is legitimately waiting for human input
|
|
721
|
-
const pendingGates = this.readGates(name).gates.filter(g => g.status === 'pending');
|
|
858
|
+
const pendingGates = this.readGates(name).gates.filter((g) => g.status === 'pending');
|
|
722
859
|
if (pendingGates.length > 0)
|
|
723
860
|
return;
|
|
724
861
|
const idleFor = Date.now() - lastActivity;
|
|
@@ -738,7 +875,12 @@ export class OrgDaemon {
|
|
|
738
875
|
}
|
|
739
876
|
nudges++;
|
|
740
877
|
nudgedAt = Date.now();
|
|
741
|
-
bus.emit({
|
|
878
|
+
bus.emit({
|
|
879
|
+
type: 'audit',
|
|
880
|
+
from: bossRole.id,
|
|
881
|
+
reason: 'idle-nudge',
|
|
882
|
+
msg: `no org activity for ${Math.round(idleFor / 60_000)}m — nudging boss (${nudges}/${MAX_IDLE_NUDGES})`,
|
|
883
|
+
});
|
|
742
884
|
bossRt.mailbox.push(`[watchdog] No activity in org "${name}" for ${Math.round(idleFor / 60_000)} minute(s). ` +
|
|
743
885
|
`If the goal is achieved (or clearly can't be), call org_complete now. Otherwise check on your team via org_send and reassign stalled work.`);
|
|
744
886
|
}
|
|
@@ -769,7 +911,13 @@ export class OrgDaemon {
|
|
|
769
911
|
}
|
|
770
912
|
const agent = running.agents.get(msg.toRole);
|
|
771
913
|
if (agent && !agent.mailbox.isClosed) {
|
|
772
|
-
bus.emit({
|
|
914
|
+
bus.emit({
|
|
915
|
+
type: 'xorg',
|
|
916
|
+
from: msg.fromQualified,
|
|
917
|
+
to: `${name}:${msg.toRole}`,
|
|
918
|
+
subject: msg.subject,
|
|
919
|
+
msg: msg.body,
|
|
920
|
+
});
|
|
773
921
|
agent.mailbox.push(this.mailBody(name, running, `[message from ${msg.fromQualified}] subject: ${msg.subject}`, msg.body, `inbox-${msg.ts}-${Math.random().toString(36).slice(2, 8)}`));
|
|
774
922
|
}
|
|
775
923
|
}
|
|
@@ -847,11 +995,15 @@ export class OrgDaemon {
|
|
|
847
995
|
// "crashed") and threw the work away. allSettled resolves as soon as every
|
|
848
996
|
// session ends, so a long drain is a ceiling, not a delay.
|
|
849
997
|
const stopWaitMs = drainMs ?? this.opts.stopWaitMs ?? 15_000;
|
|
850
|
-
const allDone = Promise.allSettled([...org.agents.values()].map(a => a.done)).then(() => false);
|
|
851
|
-
const timedOut = await Promise.race([
|
|
998
|
+
const allDone = Promise.allSettled([...org.agents.values()].map((a) => a.done)).then(() => false);
|
|
999
|
+
const timedOut = await Promise.race([
|
|
1000
|
+
allDone,
|
|
1001
|
+
new Promise((r) => setTimeout(() => r(true), stopWaitMs)),
|
|
1002
|
+
]);
|
|
852
1003
|
if (timedOut) {
|
|
853
1004
|
org.bus.emit({
|
|
854
|
-
type: 'audit',
|
|
1005
|
+
type: 'audit',
|
|
1006
|
+
msg: `org stop timed out after ${stopWaitMs}ms waiting for agent sessions to finish — proceeding anyway`,
|
|
855
1007
|
reason: 'stop-timeout',
|
|
856
1008
|
});
|
|
857
1009
|
// Reap only SDK processes spawned by THIS node process — ownerPid filter
|
|
@@ -859,9 +1011,15 @@ export class OrgDaemon {
|
|
|
859
1011
|
try {
|
|
860
1012
|
const reaped = reapOrphanedSdkProcesses(new Set(), process.pid);
|
|
861
1013
|
if (reaped > 0)
|
|
862
|
-
org.bus.emit({
|
|
1014
|
+
org.bus.emit({
|
|
1015
|
+
type: 'audit',
|
|
1016
|
+
reason: 'orphan-reap',
|
|
1017
|
+
msg: `reaped ${reaped} orphaned SDK process(es) after stop timeout`,
|
|
1018
|
+
});
|
|
1019
|
+
}
|
|
1020
|
+
catch {
|
|
1021
|
+
/* best-effort */
|
|
863
1022
|
}
|
|
864
|
-
catch { /* best-effort */ }
|
|
865
1023
|
}
|
|
866
1024
|
org.bus.emit({ type: 'status', msg: 'org stopped' });
|
|
867
1025
|
await org.bus.flush();
|
|
@@ -893,7 +1051,10 @@ export class OrgDaemon {
|
|
|
893
1051
|
if (forwarder) {
|
|
894
1052
|
await Promise.race([
|
|
895
1053
|
forwarder.settle(),
|
|
896
|
-
new Promise(r => {
|
|
1054
|
+
new Promise((r) => {
|
|
1055
|
+
const t = setTimeout(r, 5_000);
|
|
1056
|
+
t.unref?.();
|
|
1057
|
+
}),
|
|
897
1058
|
]);
|
|
898
1059
|
forwarder.unsubscribe();
|
|
899
1060
|
// Only remove from the map if it's still OURS — an autoWake-restart may
|
|
@@ -908,27 +1069,41 @@ export class OrgDaemon {
|
|
|
908
1069
|
this.persistState(name, 'stopped', org.run, org, stopCheckpoint);
|
|
909
1070
|
// Clean up git worktrees — shared (workspace: 'worktree') and per-role.
|
|
910
1071
|
try {
|
|
911
|
-
const {
|
|
1072
|
+
const { execFileSync } = await import('node:child_process');
|
|
912
1073
|
if (org.worktreePath) {
|
|
913
1074
|
try {
|
|
914
|
-
|
|
1075
|
+
execFileSync('git', ['worktree', 'remove', '--force', org.worktreePath], {
|
|
1076
|
+
cwd: this.root,
|
|
1077
|
+
stdio: 'ignore',
|
|
1078
|
+
timeout: 30_000,
|
|
1079
|
+
});
|
|
1080
|
+
}
|
|
1081
|
+
catch {
|
|
1082
|
+
/* best-effort */
|
|
915
1083
|
}
|
|
916
|
-
catch { /* best-effort */ }
|
|
917
1084
|
}
|
|
918
1085
|
for (const agent of org.agents.values()) {
|
|
919
1086
|
if (agent.worktreePath) {
|
|
920
1087
|
try {
|
|
921
|
-
|
|
1088
|
+
execFileSync('git', ['worktree', 'remove', '--force', agent.worktreePath], {
|
|
1089
|
+
cwd: this.root,
|
|
1090
|
+
stdio: 'ignore',
|
|
1091
|
+
timeout: 30_000,
|
|
1092
|
+
});
|
|
1093
|
+
}
|
|
1094
|
+
catch {
|
|
1095
|
+
/* best-effort */
|
|
922
1096
|
}
|
|
923
|
-
catch { /* best-effort */ }
|
|
924
1097
|
}
|
|
925
1098
|
}
|
|
926
1099
|
}
|
|
927
|
-
catch {
|
|
1100
|
+
catch {
|
|
1101
|
+
/* node:child_process unavailable — skip */
|
|
1102
|
+
}
|
|
928
1103
|
}
|
|
929
1104
|
async stopAll() {
|
|
930
1105
|
await Promise.all([
|
|
931
|
-
...[...this.orgs.keys()].map(n => this.stopOrg(n)),
|
|
1106
|
+
...[...this.orgs.keys()].map((n) => this.stopOrg(n)),
|
|
932
1107
|
...this.stopping.values(), // detached self-stops still flushing
|
|
933
1108
|
]);
|
|
934
1109
|
}
|
|
@@ -949,7 +1124,10 @@ export class OrgDaemon {
|
|
|
949
1124
|
// would brick every subsequent `org status` / isOrgRunning / scheduler
|
|
950
1125
|
// call. The state files in 6 other daemon paths already use this helper.
|
|
951
1126
|
writeJsonFileAtomic(p, {
|
|
952
|
-
status,
|
|
1127
|
+
status,
|
|
1128
|
+
run,
|
|
1129
|
+
pid: process.pid,
|
|
1130
|
+
updated: new Date().toISOString(),
|
|
953
1131
|
...(missing.length ? { abandonedRoles: missing } : {}),
|
|
954
1132
|
...(checkpoint ? { checkpoint } : {}),
|
|
955
1133
|
});
|
|
@@ -963,11 +1141,16 @@ export class OrgDaemon {
|
|
|
963
1141
|
// C4: atomic write — crash handler is the most likely place to hit
|
|
964
1142
|
// a partial write since the process is mid-teardown.
|
|
965
1143
|
writeJsonFileAtomic(p, {
|
|
966
|
-
status: 'crashed',
|
|
967
|
-
|
|
1144
|
+
status: 'crashed',
|
|
1145
|
+
run: org.run,
|
|
1146
|
+
pid: process.pid,
|
|
1147
|
+
updated: new Date().toISOString(),
|
|
1148
|
+
closedBy: 'crash-handler',
|
|
968
1149
|
});
|
|
969
1150
|
}
|
|
970
|
-
catch {
|
|
1151
|
+
catch {
|
|
1152
|
+
/* best effort — filesystem may be unavailable */
|
|
1153
|
+
}
|
|
971
1154
|
}
|
|
972
1155
|
}
|
|
973
1156
|
heartbeatPath() {
|
|
@@ -982,17 +1165,22 @@ export class OrgDaemon {
|
|
|
982
1165
|
// C4: atomic write — heartbeat corruption is how `org status` reports
|
|
983
1166
|
// a phantom daemon after a crash.
|
|
984
1167
|
writeJsonFileAtomic(p, {
|
|
985
|
-
pid: process.pid,
|
|
1168
|
+
pid: process.pid,
|
|
1169
|
+
updatedAt: new Date().toISOString(),
|
|
986
1170
|
running: this.listRunning(),
|
|
987
1171
|
});
|
|
988
1172
|
}
|
|
989
|
-
catch {
|
|
1173
|
+
catch {
|
|
1174
|
+
/* best effort */
|
|
1175
|
+
}
|
|
990
1176
|
}
|
|
991
1177
|
clearHeartbeat() {
|
|
992
1178
|
try {
|
|
993
1179
|
unlinkSync(this.heartbeatPath());
|
|
994
1180
|
}
|
|
995
|
-
catch {
|
|
1181
|
+
catch {
|
|
1182
|
+
/* already gone or never written */
|
|
1183
|
+
}
|
|
996
1184
|
}
|
|
997
1185
|
// ── Delegated methods — extracted to focused modules ──────────────────
|
|
998
1186
|
// approvals.ts
|
|
@@ -1043,16 +1231,26 @@ export class OrgDaemon {
|
|
|
1043
1231
|
}
|
|
1044
1232
|
// scheduler-integration.ts
|
|
1045
1233
|
/** @internal */
|
|
1046
|
-
autoWake(name) {
|
|
1047
|
-
|
|
1234
|
+
autoWake(name) {
|
|
1235
|
+
scheduler.autoWake(this, name);
|
|
1236
|
+
}
|
|
1237
|
+
scheduleBossRestart(name) {
|
|
1238
|
+
scheduler.scheduleBossRestart(this, name);
|
|
1239
|
+
}
|
|
1048
1240
|
/** @internal */
|
|
1049
1241
|
scheduleDeferredSpawn(name, running, role, spawnRole) {
|
|
1050
1242
|
scheduler.scheduleDeferredSpawn(this, name, running, role, spawnRole);
|
|
1051
1243
|
}
|
|
1052
1244
|
// org-memory.ts
|
|
1053
|
-
orgMemoryNamespace(name, def) {
|
|
1054
|
-
|
|
1055
|
-
|
|
1245
|
+
orgMemoryNamespace(name, def) {
|
|
1246
|
+
return orgMemory.orgMemoryNamespace(name, def);
|
|
1247
|
+
}
|
|
1248
|
+
orgMemoryDbPath() {
|
|
1249
|
+
return orgMemory.orgMemoryDbPath(this.root);
|
|
1250
|
+
}
|
|
1251
|
+
orgMemoryUsable() {
|
|
1252
|
+
return orgMemory.orgMemoryUsable(this.root);
|
|
1253
|
+
}
|
|
1056
1254
|
async rememberOrgMemory(name, def, role, content, scope, run) {
|
|
1057
1255
|
return orgMemory.rememberOrgMemory(this.root, name, def, role, content, scope, run);
|
|
1058
1256
|
}
|