@monoes/monomindcli 2.9.3 → 2.9.4
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/helpers/intelligence.cjs +27 -6
- package/.claude/skills/agentic-jujutsu/SKILL.md +17 -15
- package/.claude/skills/hive-mind-advanced/SKILL.md +212 -559
- package/.claude/skills/hooks-automation/SKILL.md +1 -1
- package/.claude/skills/mastermind-createorg/SKILL.md +2 -1
- package/.claude/skills/pair-programming/SKILL.md +1 -1
- package/.claude/skills/performance-analysis/SKILL.md +228 -484
- package/.claude/skills/swarm-advanced/SKILL.md +2 -2
- package/.claude/skills/swarm-orchestration/SKILL.md +220 -150
- package/.claude/skills/verification-quality/SKILL.md +247 -571
- package/README.md +6 -2
- 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 +3 -3
- 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 +113 -26
- package/dist/src/commands/autopilot.js.map +1 -1
- package/dist/src/commands/doc.d.ts.map +1 -1
- package/dist/src/commands/doc.js +14 -3
- package/dist/src/commands/doc.js.map +1 -1
- package/dist/src/commands/doctor-env-checks.d.ts +1 -1
- package/dist/src/commands/doctor-env-checks.d.ts.map +1 -1
- package/dist/src/commands/doctor.d.ts.map +1 -1
- package/dist/src/commands/doctor.js +30 -1
- package/dist/src/commands/doctor.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.js +8 -8
- 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/init.d.ts.map +1 -1
- package/dist/src/commands/init.js +80 -7
- package/dist/src/commands/init.js.map +1 -1
- package/dist/src/commands/mcp.d.ts.map +1 -1
- package/dist/src/commands/mcp.js +78 -2
- package/dist/src/commands/mcp.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.d.ts.map +1 -1
- package/dist/src/commands/org.js +114 -1
- package/dist/src/commands/org.js.map +1 -1
- package/dist/src/commands/performance.js +1 -1
- package/dist/src/commands/performance.js.map +1 -1
- package/dist/src/commands/swarm.d.ts.map +1 -1
- package/dist/src/commands/swarm.js +144 -98
- package/dist/src/commands/swarm.js.map +1 -1
- package/dist/src/init/claudemd-generator.js +1 -1
- package/dist/src/init/executor.d.ts.map +1 -1
- package/dist/src/init/executor.js +13 -3
- package/dist/src/init/executor.js.map +1 -1
- package/dist/src/init/kimi-generator.d.ts +3 -2
- package/dist/src/init/kimi-generator.d.ts.map +1 -1
- package/dist/src/init/kimi-generator.js +45 -13
- package/dist/src/init/kimi-generator.js.map +1 -1
- package/dist/src/init/statusline-generator.d.ts +1 -1
- package/dist/src/init/statusline-generator.d.ts.map +1 -1
- package/dist/src/init/statusline-generator.js +48 -46
- 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/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 +97 -40
- 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 +406 -205
- 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/monograph/query-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/monograph/query-tools.js +48 -18
- package/dist/src/mcp-tools/monograph/query-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/embedding-operations.d.ts +4 -0
- package/dist/src/memory/embedding-operations.d.ts.map +1 -1
- package/dist/src/memory/embedding-operations.js +125 -32
- package/dist/src/memory/embedding-operations.js.map +1 -1
- package/dist/src/memory/hnsw-operations.d.ts +1 -1
- package/dist/src/memory/hnsw-operations.js +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/memory/memory-read.d.ts +1 -1
- package/dist/src/memory/memory-read.js +2 -2
- package/dist/src/memory/memory-read.js.map +1 -1
- package/dist/src/orgrt/checkpoint-ops.d.ts +11 -1
- package/dist/src/orgrt/checkpoint-ops.d.ts.map +1 -1
- package/dist/src/orgrt/checkpoint-ops.js +15 -1
- package/dist/src/orgrt/checkpoint-ops.js.map +1 -1
- package/dist/src/orgrt/checkpoint.js +1 -1
- package/dist/src/orgrt/checkpoint.js.map +1 -1
- package/dist/src/orgrt/daemon.d.ts +11 -3
- package/dist/src/orgrt/daemon.d.ts.map +1 -1
- package/dist/src/orgrt/daemon.js +317 -93
- package/dist/src/orgrt/daemon.js.map +1 -1
- package/dist/src/orgrt/decisions.d.ts +13 -0
- package/dist/src/orgrt/decisions.d.ts.map +1 -1
- package/dist/src/orgrt/decisions.js +95 -0
- package/dist/src/orgrt/decisions.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/orgrt/session.d.ts +27 -1
- package/dist/src/orgrt/session.d.ts.map +1 -1
- package/dist/src/orgrt/session.js +42 -3
- package/dist/src/orgrt/session.js.map +1 -1
- package/dist/src/orgrt/task-dag.d.ts +11 -1
- package/dist/src/orgrt/task-dag.d.ts.map +1 -1
- package/dist/src/orgrt/task-dag.js +93 -2
- package/dist/src/orgrt/task-dag.js.map +1 -1
- package/dist/src/orgrt/templates.d.ts.map +1 -1
- package/dist/src/orgrt/templates.js +24 -0
- package/dist/src/orgrt/templates.js.map +1 -1
- package/dist/src/orgrt/types.d.ts +70 -1
- package/dist/src/orgrt/types.d.ts.map +1 -1
- package/dist/src/orgrt/types.js +19 -0
- package/dist/src/orgrt/types.js.map +1 -1
- package/dist/src/orgrt/vercel-providers.d.ts.map +1 -1
- package/dist/src/orgrt/vercel-providers.js +9 -4
- package/dist/src/orgrt/vercel-providers.js.map +1 -1
- package/dist/src/orgrt/vercel-runner.d.ts +0 -23
- package/dist/src/orgrt/vercel-runner.d.ts.map +1 -1
- package/dist/src/orgrt/vercel-runner.js +26 -1
- package/dist/src/orgrt/vercel-runner.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/.claude/commands/mastermind/approvev1.md +0 -94
- package/.claude/commands/mastermind/architect.md +0 -52
- package/.claude/commands/mastermind/autodev.md +0 -28
- package/.claude/commands/mastermind/build.md +0 -23
- package/.claude/commands/mastermind/finish.md +0 -17
- package/.claude/commands/mastermind/runorgv1.md +0 -159
- package/.claude/commands/mastermind/taskdev.md +0 -23
- package/.claude/commands/mastermind/tdd.md +0 -19
- package/.claude/commands/mastermind/verify.md +0 -19
- package/.claude/skills/mastermind-approvev1/SKILL.md +0 -191
- package/.claude/skills/mastermind-architect/SKILL.md +0 -862
- package/.claude/skills/mastermind-autodev/SKILL.md +0 -360
- package/.claude/skills/mastermind-build/SKILL.md +0 -169
- package/.claude/skills/mastermind-companies/SKILL.md +0 -256
- package/.claude/skills/mastermind-content/SKILL.md +0 -197
- package/.claude/skills/mastermind-costs/SKILL.md +0 -151
- package/.claude/skills/mastermind-finance/SKILL.md +0 -166
- package/.claude/skills/mastermind-finish/SKILL.md +0 -251
- package/.claude/skills/mastermind-heartbeatv1/SKILL.md +0 -167
- package/.claude/skills/mastermind-instance-settings/SKILL.md +0 -315
- package/.claude/skills/mastermind-marketing/SKILL.md +0 -228
- package/.claude/skills/mastermind-marketing/references/copywriting-frameworks.md +0 -181
- package/.claude/skills/mastermind-marketing/references/persuasion-psychology.md +0 -158
- package/.claude/skills/mastermind-ops/SKILL.md +0 -168
- package/.claude/skills/mastermind-org-chart/SKILL.md +0 -209
- package/.claude/skills/mastermind-project-detail/SKILL.md +0 -249
- package/.claude/skills/mastermind-project-workspace/SKILL.md +0 -244
- package/.claude/skills/mastermind-projects/SKILL.md +0 -167
- package/.claude/skills/mastermind-runorgv1/SKILL.md +0 -731
- package/.claude/skills/mastermind-sales/SKILL.md +0 -170
- package/.claude/skills/mastermind-taskdev/SKILL.md +0 -377
- package/.claude/skills/mastermind-taskdev/code-quality-reviewer-prompt.md +0 -60
- package/.claude/skills/mastermind-taskdev/final-reviewer-prompt.md +0 -144
- package/.claude/skills/mastermind-taskdev/implementer-prompt.md +0 -114
- package/.claude/skills/mastermind-taskdev/spec-reviewer-prompt.md +0 -80
- package/.claude/skills/mastermind-tdd/SKILL.md +0 -424
- package/.claude/skills/mastermind-verify/SKILL.md +0 -196
- package/.claude/skills/mastermind-wiki/SKILL.md +0 -314
- package/.claude/skills/monolean-review/SKILL.md +0 -57
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,54 @@ 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
|
+
sessionId: undefined,
|
|
528
|
+
worktreePath: roleCwd !== cwd ? roleCwd : undefined,
|
|
529
|
+
scrollback: new ScrollbackBuffer(),
|
|
530
|
+
};
|
|
464
531
|
const sessionOpts = {
|
|
465
|
-
org: name,
|
|
532
|
+
org: name,
|
|
533
|
+
role,
|
|
534
|
+
bus,
|
|
535
|
+
policy,
|
|
536
|
+
mailbox,
|
|
537
|
+
cwd: roleCwd,
|
|
538
|
+
def,
|
|
466
539
|
// Pass the org state directory so runners that persist per-role state
|
|
467
540
|
// (VercelAgentRunner session files) write under .monomind/orgs/<name>
|
|
468
541
|
// instead of polluting the workspace cwd.
|
|
@@ -470,6 +543,7 @@ export class OrgDaemon {
|
|
|
470
543
|
maxTurns: role.max_turns_per_message ?? def.run_config.max_turns_per_message,
|
|
471
544
|
lastMessageId: () => runtime.lastMessageId,
|
|
472
545
|
onOutput: (line) => runtime.scrollback.push(line),
|
|
546
|
+
onSessionId: (id) => { runtime.sessionId = id; },
|
|
473
547
|
deliver: (from, to, subject, body) => this.deliver(name, from, to, subject, body),
|
|
474
548
|
askHuman: (r, question) => this.askHuman(name, r, question),
|
|
475
549
|
onGate: (r, gateName, gateDesc) => this.createGate(name, r, gateName, gateDesc),
|
|
@@ -483,7 +557,13 @@ export class OrgDaemon {
|
|
|
483
557
|
fence: roleFences.get(role.id),
|
|
484
558
|
onComplete: role.id === bossRole.id
|
|
485
559
|
? (r, outcome, summary) => {
|
|
486
|
-
bus.emit({
|
|
560
|
+
bus.emit({
|
|
561
|
+
type: 'status',
|
|
562
|
+
from: r,
|
|
563
|
+
reason: 'org-complete',
|
|
564
|
+
msg: `run outcome: ${outcome}`,
|
|
565
|
+
data: { outcome, summary },
|
|
566
|
+
});
|
|
487
567
|
}
|
|
488
568
|
: undefined,
|
|
489
569
|
// #11: a boss that overflows its context window isn't a crash (it keeps
|
|
@@ -493,25 +573,50 @@ export class OrgDaemon {
|
|
|
493
573
|
onContextLimit: role.id === bossRole.id ? () => this.scheduleBossRestart(name) : undefined,
|
|
494
574
|
recall: async (r, q) => {
|
|
495
575
|
const answer = await this.recallOrgMemory(name, def, q, r);
|
|
496
|
-
bus.emit({
|
|
576
|
+
bus.emit({
|
|
577
|
+
type: 'status',
|
|
578
|
+
from: r,
|
|
579
|
+
reason: 'org-recall',
|
|
580
|
+
msg: `recall: ${q.slice(0, 80)}`,
|
|
581
|
+
data: { hits: answer.hits },
|
|
582
|
+
});
|
|
497
583
|
return answer.text;
|
|
498
584
|
},
|
|
499
585
|
searchKnowledge: async (r, q) => {
|
|
500
586
|
const answer = await this.searchProjectKnowledge(q);
|
|
501
|
-
bus.emit({
|
|
587
|
+
bus.emit({
|
|
588
|
+
type: 'status',
|
|
589
|
+
from: r,
|
|
590
|
+
reason: 'knowledge-search',
|
|
591
|
+
msg: `knowledge: ${q.slice(0, 80)}`,
|
|
592
|
+
data: { hits: answer.hits },
|
|
593
|
+
});
|
|
502
594
|
return answer.text;
|
|
503
595
|
},
|
|
504
596
|
glossary,
|
|
505
597
|
remember: async (r, content, scope) => {
|
|
506
598
|
const text = await this.rememberOrgMemory(name, def, r, content, scope, run);
|
|
507
|
-
bus.emit({
|
|
599
|
+
bus.emit({
|
|
600
|
+
type: 'status',
|
|
601
|
+
from: r,
|
|
602
|
+
reason: 'org-remember',
|
|
603
|
+
msg: `remember (${scope}): ${content.slice(0, 80)}`,
|
|
604
|
+
data: { scope },
|
|
605
|
+
});
|
|
508
606
|
return text;
|
|
509
607
|
},
|
|
510
608
|
learn: async (r, payload) => {
|
|
511
609
|
const text = await this.learnOrgKnowledge(name, run, payload);
|
|
512
610
|
bus.emit({
|
|
513
|
-
type: 'status',
|
|
514
|
-
|
|
611
|
+
type: 'status',
|
|
612
|
+
from: r,
|
|
613
|
+
reason: 'org-learn',
|
|
614
|
+
msg: `learn: ${text.slice(0, 120)}`,
|
|
615
|
+
data: {
|
|
616
|
+
nodes: payload.nodes?.length ?? 0,
|
|
617
|
+
edges: payload.edges?.length ?? 0,
|
|
618
|
+
rules: payload.rules?.length ?? 0,
|
|
619
|
+
},
|
|
515
620
|
});
|
|
516
621
|
return text;
|
|
517
622
|
},
|
|
@@ -525,6 +630,18 @@ export class OrgDaemon {
|
|
|
525
630
|
const running = this.orgs.get(name);
|
|
526
631
|
return JSON.stringify(running?.taskDag?.all() ?? [], null, 2);
|
|
527
632
|
},
|
|
633
|
+
splitTask: (r, parentId, children) => {
|
|
634
|
+
return this.dagSplitTask(name, r, parentId, children);
|
|
635
|
+
},
|
|
636
|
+
mergeTask: (r, sourceId, targetId) => {
|
|
637
|
+
return this.dagMergeTask(name, r, sourceId, targetId);
|
|
638
|
+
},
|
|
639
|
+
cancelTask: (r, taskId, reason) => {
|
|
640
|
+
return this.dagCancelTask(name, r, taskId, reason);
|
|
641
|
+
},
|
|
642
|
+
planGraph: (r, specs) => {
|
|
643
|
+
return this.dagPlanGraph(name, r, specs);
|
|
644
|
+
},
|
|
528
645
|
queryFn: this.opts.queryFn,
|
|
529
646
|
// Runner resolution: explicit opts.runner > role `runtime` field >
|
|
530
647
|
// org def `runtime` field > MONOMIND_RUNTIME env (opencode/kimicode) >
|
|
@@ -532,7 +649,8 @@ export class OrgDaemon {
|
|
|
532
649
|
// Leaving it undefined for the default path is what keeps
|
|
533
650
|
// Claude/Antigravity orgs byte-for-byte unchanged. Session opts are
|
|
534
651
|
// built per role here in spawnRole, so each role gets its own runner.
|
|
535
|
-
runner: this.opts.runner ??
|
|
652
|
+
runner: this.opts.runner ??
|
|
653
|
+
resolveRoleRunner(role.runtime, def.runtime, role.provider?.kind, undefined),
|
|
536
654
|
};
|
|
537
655
|
// Supervised session: transient crashes (provider blips, network) restart
|
|
538
656
|
// with backoff; a crash with the mailbox already closed, or one that
|
|
@@ -562,7 +680,8 @@ export class OrgDaemon {
|
|
|
562
680
|
if (killedByStop) {
|
|
563
681
|
runtime.status = 'ended';
|
|
564
682
|
bus.emit({
|
|
565
|
-
type: 'status',
|
|
683
|
+
type: 'status',
|
|
684
|
+
from: role.id,
|
|
566
685
|
msg: `agent "${role.id}" terminated by stop (was still working when drain window expired)`,
|
|
567
686
|
reason: 'terminated-by-stop',
|
|
568
687
|
});
|
|
@@ -576,10 +695,16 @@ export class OrgDaemon {
|
|
|
576
695
|
mailbox.close();
|
|
577
696
|
const isContextLimit = OrgDaemon.CONTEXT_LIMIT_RE.test(message);
|
|
578
697
|
bus.emit({
|
|
579
|
-
type: 'audit',
|
|
698
|
+
type: 'audit',
|
|
699
|
+
from: role.id,
|
|
580
700
|
msg: `agent "${role.id}" crashed: ${message}`,
|
|
581
701
|
reason: isContextLimit ? 'agent-context-limit' : 'agent-session-crash',
|
|
582
|
-
data: {
|
|
702
|
+
data: {
|
|
703
|
+
agentId: role.id,
|
|
704
|
+
error: message,
|
|
705
|
+
restarts: attempt,
|
|
706
|
+
contextLimit: isContextLimit,
|
|
707
|
+
},
|
|
583
708
|
});
|
|
584
709
|
if (role.id !== bossRole.id) {
|
|
585
710
|
// #2/#3: a worker is gone for the rest of this run. Without this
|
|
@@ -594,8 +719,12 @@ export class OrgDaemon {
|
|
|
594
719
|
? ' 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
720
|
: '';
|
|
596
721
|
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
|
-
|
|
722
|
+
bus.emit({
|
|
723
|
+
type: 'audit',
|
|
724
|
+
from: bossRole.id,
|
|
725
|
+
reason: 'worker-crashed',
|
|
726
|
+
msg: `worker "${role.id}" crashed (contextLimit=${isContextLimit}); coordinator notified to reassign`,
|
|
727
|
+
});
|
|
599
728
|
}
|
|
600
729
|
}
|
|
601
730
|
else {
|
|
@@ -613,7 +742,9 @@ export class OrgDaemon {
|
|
|
613
742
|
const fatal = err?.fatal === true;
|
|
614
743
|
if (fatal) {
|
|
615
744
|
bus.emit({
|
|
616
|
-
type: 'status',
|
|
745
|
+
type: 'status',
|
|
746
|
+
from: role.id,
|
|
747
|
+
reason: 'agent-fatal',
|
|
617
748
|
msg: `agent "${role.id}" hit a fatal (non-retryable) error — not restarting`,
|
|
618
749
|
});
|
|
619
750
|
crash();
|
|
@@ -624,10 +755,15 @@ export class OrgDaemon {
|
|
|
624
755
|
return;
|
|
625
756
|
}
|
|
626
757
|
bus.emit({
|
|
627
|
-
type: 'status',
|
|
758
|
+
type: 'status',
|
|
759
|
+
from: role.id,
|
|
760
|
+
reason: 'agent-restart',
|
|
628
761
|
msg: `agent "${role.id}" crashed (${message}) — restarting in ${BACKOFFS_MS[attempt]}ms (attempt ${attempt + 1}/${BACKOFFS_MS.length})`,
|
|
629
762
|
});
|
|
630
|
-
await new Promise(r => {
|
|
763
|
+
await new Promise((r) => {
|
|
764
|
+
const t = setTimeout(r, BACKOFFS_MS[attempt]);
|
|
765
|
+
t.unref?.();
|
|
766
|
+
});
|
|
631
767
|
if (mailbox.isClosed) {
|
|
632
768
|
crash();
|
|
633
769
|
return;
|
|
@@ -661,7 +797,9 @@ export class OrgDaemon {
|
|
|
661
797
|
// no-cjs-require-in-esm.test.ts.
|
|
662
798
|
reapOrphanedSdkProcesses(new Set(), process.pid);
|
|
663
799
|
}
|
|
664
|
-
catch {
|
|
800
|
+
catch {
|
|
801
|
+
/* best-effort */
|
|
802
|
+
}
|
|
665
803
|
};
|
|
666
804
|
process.on('exit', crashCleanup);
|
|
667
805
|
running._crashCleanup = crashCleanup;
|
|
@@ -672,17 +810,24 @@ export class OrgDaemon {
|
|
|
672
810
|
if (staleThreshold && staleThreshold > 0 && cwd === this.root) {
|
|
673
811
|
try {
|
|
674
812
|
const { execSync } = await import('node:child_process');
|
|
675
|
-
const behind = execSync('git rev-list --count HEAD..@{upstream} 2>/dev/null', {
|
|
813
|
+
const behind = execSync('git rev-list --count HEAD..@{upstream} 2>/dev/null', {
|
|
814
|
+
cwd,
|
|
815
|
+
encoding: 'utf8',
|
|
816
|
+
timeout: 10_000,
|
|
817
|
+
}).trim();
|
|
676
818
|
const count = parseInt(behind, 10);
|
|
677
819
|
if (!isNaN(count) && count > staleThreshold) {
|
|
678
820
|
bus.emit({
|
|
679
|
-
type: 'audit',
|
|
821
|
+
type: 'audit',
|
|
822
|
+
reason: 'stale-base',
|
|
680
823
|
msg: `working tree is ${count} commits behind upstream (threshold: ${staleThreshold}) — consider pulling before running`,
|
|
681
824
|
data: { behind: count, threshold: staleThreshold },
|
|
682
825
|
});
|
|
683
826
|
}
|
|
684
827
|
}
|
|
685
|
-
catch {
|
|
828
|
+
catch {
|
|
829
|
+
/* no upstream tracking or git unavailable — skip silently */
|
|
830
|
+
}
|
|
686
831
|
}
|
|
687
832
|
const boss = bossRole;
|
|
688
833
|
// Cross-run memory: brief the coordinator on the previous run so scheduled
|
|
@@ -695,9 +840,15 @@ export class OrgDaemon {
|
|
|
695
840
|
: `no recorded outcome (${prev.messages} messages, ${prev.assets.length} assets${prev.crashes.length ? `, ${prev.crashes.length} crashed agent(s)` : ''})`) +
|
|
696
841
|
`\nBuild on that work — do not redo what is already done.`
|
|
697
842
|
: '';
|
|
698
|
-
running.agents
|
|
843
|
+
running.agents
|
|
844
|
+
.get(boss.id)
|
|
845
|
+
.mailbox.push(`Org "${name}" started (run ${run}).\nGoal: ${taskOverride ?? def.goal}\n` +
|
|
699
846
|
`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({
|
|
847
|
+
bus.emit({
|
|
848
|
+
type: 'status',
|
|
849
|
+
msg: `org started (${def.roles.length} agents)`,
|
|
850
|
+
data: { goal: taskOverride ?? def.goal },
|
|
851
|
+
});
|
|
701
852
|
this.persistState(name, 'running', run);
|
|
702
853
|
// Idle watchdog: a hung tool call (or a run that quietly finished without
|
|
703
854
|
// org_complete) produces no bus events, and every agent just waits. After
|
|
@@ -712,13 +863,13 @@ export class OrgDaemon {
|
|
|
712
863
|
let nudges = 0;
|
|
713
864
|
const idleStop = (msg) => {
|
|
714
865
|
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));
|
|
866
|
+
this.stopOrg(name).catch((err) => console.error(`org ${name}: idle-stop failed:`, err instanceof Error ? err.message : err));
|
|
716
867
|
};
|
|
717
868
|
const wd = setInterval(() => {
|
|
718
869
|
if (this.restarting.has(name))
|
|
719
870
|
return; // boss auto-restart in flight — don't nudge or stop
|
|
720
871
|
// A pending gate means the org is legitimately waiting for human input
|
|
721
|
-
const pendingGates = this.readGates(name).gates.filter(g => g.status === 'pending');
|
|
872
|
+
const pendingGates = this.readGates(name).gates.filter((g) => g.status === 'pending');
|
|
722
873
|
if (pendingGates.length > 0)
|
|
723
874
|
return;
|
|
724
875
|
const idleFor = Date.now() - lastActivity;
|
|
@@ -738,7 +889,12 @@ export class OrgDaemon {
|
|
|
738
889
|
}
|
|
739
890
|
nudges++;
|
|
740
891
|
nudgedAt = Date.now();
|
|
741
|
-
bus.emit({
|
|
892
|
+
bus.emit({
|
|
893
|
+
type: 'audit',
|
|
894
|
+
from: bossRole.id,
|
|
895
|
+
reason: 'idle-nudge',
|
|
896
|
+
msg: `no org activity for ${Math.round(idleFor / 60_000)}m — nudging boss (${nudges}/${MAX_IDLE_NUDGES})`,
|
|
897
|
+
});
|
|
742
898
|
bossRt.mailbox.push(`[watchdog] No activity in org "${name}" for ${Math.round(idleFor / 60_000)} minute(s). ` +
|
|
743
899
|
`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
900
|
}
|
|
@@ -769,7 +925,13 @@ export class OrgDaemon {
|
|
|
769
925
|
}
|
|
770
926
|
const agent = running.agents.get(msg.toRole);
|
|
771
927
|
if (agent && !agent.mailbox.isClosed) {
|
|
772
|
-
bus.emit({
|
|
928
|
+
bus.emit({
|
|
929
|
+
type: 'xorg',
|
|
930
|
+
from: msg.fromQualified,
|
|
931
|
+
to: `${name}:${msg.toRole}`,
|
|
932
|
+
subject: msg.subject,
|
|
933
|
+
msg: msg.body,
|
|
934
|
+
});
|
|
773
935
|
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
936
|
}
|
|
775
937
|
}
|
|
@@ -847,11 +1009,15 @@ export class OrgDaemon {
|
|
|
847
1009
|
// "crashed") and threw the work away. allSettled resolves as soon as every
|
|
848
1010
|
// session ends, so a long drain is a ceiling, not a delay.
|
|
849
1011
|
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([
|
|
1012
|
+
const allDone = Promise.allSettled([...org.agents.values()].map((a) => a.done)).then(() => false);
|
|
1013
|
+
const timedOut = await Promise.race([
|
|
1014
|
+
allDone,
|
|
1015
|
+
new Promise((r) => setTimeout(() => r(true), stopWaitMs)),
|
|
1016
|
+
]);
|
|
852
1017
|
if (timedOut) {
|
|
853
1018
|
org.bus.emit({
|
|
854
|
-
type: 'audit',
|
|
1019
|
+
type: 'audit',
|
|
1020
|
+
msg: `org stop timed out after ${stopWaitMs}ms waiting for agent sessions to finish — proceeding anyway`,
|
|
855
1021
|
reason: 'stop-timeout',
|
|
856
1022
|
});
|
|
857
1023
|
// Reap only SDK processes spawned by THIS node process — ownerPid filter
|
|
@@ -859,9 +1025,15 @@ export class OrgDaemon {
|
|
|
859
1025
|
try {
|
|
860
1026
|
const reaped = reapOrphanedSdkProcesses(new Set(), process.pid);
|
|
861
1027
|
if (reaped > 0)
|
|
862
|
-
org.bus.emit({
|
|
1028
|
+
org.bus.emit({
|
|
1029
|
+
type: 'audit',
|
|
1030
|
+
reason: 'orphan-reap',
|
|
1031
|
+
msg: `reaped ${reaped} orphaned SDK process(es) after stop timeout`,
|
|
1032
|
+
});
|
|
1033
|
+
}
|
|
1034
|
+
catch {
|
|
1035
|
+
/* best-effort */
|
|
863
1036
|
}
|
|
864
|
-
catch { /* best-effort */ }
|
|
865
1037
|
}
|
|
866
1038
|
org.bus.emit({ type: 'status', msg: 'org stopped' });
|
|
867
1039
|
await org.bus.flush();
|
|
@@ -893,7 +1065,10 @@ export class OrgDaemon {
|
|
|
893
1065
|
if (forwarder) {
|
|
894
1066
|
await Promise.race([
|
|
895
1067
|
forwarder.settle(),
|
|
896
|
-
new Promise(r => {
|
|
1068
|
+
new Promise((r) => {
|
|
1069
|
+
const t = setTimeout(r, 5_000);
|
|
1070
|
+
t.unref?.();
|
|
1071
|
+
}),
|
|
897
1072
|
]);
|
|
898
1073
|
forwarder.unsubscribe();
|
|
899
1074
|
// Only remove from the map if it's still OURS — an autoWake-restart may
|
|
@@ -908,27 +1083,41 @@ export class OrgDaemon {
|
|
|
908
1083
|
this.persistState(name, 'stopped', org.run, org, stopCheckpoint);
|
|
909
1084
|
// Clean up git worktrees — shared (workspace: 'worktree') and per-role.
|
|
910
1085
|
try {
|
|
911
|
-
const {
|
|
1086
|
+
const { execFileSync } = await import('node:child_process');
|
|
912
1087
|
if (org.worktreePath) {
|
|
913
1088
|
try {
|
|
914
|
-
|
|
1089
|
+
execFileSync('git', ['worktree', 'remove', '--force', org.worktreePath], {
|
|
1090
|
+
cwd: this.root,
|
|
1091
|
+
stdio: 'ignore',
|
|
1092
|
+
timeout: 30_000,
|
|
1093
|
+
});
|
|
1094
|
+
}
|
|
1095
|
+
catch {
|
|
1096
|
+
/* best-effort */
|
|
915
1097
|
}
|
|
916
|
-
catch { /* best-effort */ }
|
|
917
1098
|
}
|
|
918
1099
|
for (const agent of org.agents.values()) {
|
|
919
1100
|
if (agent.worktreePath) {
|
|
920
1101
|
try {
|
|
921
|
-
|
|
1102
|
+
execFileSync('git', ['worktree', 'remove', '--force', agent.worktreePath], {
|
|
1103
|
+
cwd: this.root,
|
|
1104
|
+
stdio: 'ignore',
|
|
1105
|
+
timeout: 30_000,
|
|
1106
|
+
});
|
|
1107
|
+
}
|
|
1108
|
+
catch {
|
|
1109
|
+
/* best-effort */
|
|
922
1110
|
}
|
|
923
|
-
catch { /* best-effort */ }
|
|
924
1111
|
}
|
|
925
1112
|
}
|
|
926
1113
|
}
|
|
927
|
-
catch {
|
|
1114
|
+
catch {
|
|
1115
|
+
/* node:child_process unavailable — skip */
|
|
1116
|
+
}
|
|
928
1117
|
}
|
|
929
1118
|
async stopAll() {
|
|
930
1119
|
await Promise.all([
|
|
931
|
-
...[...this.orgs.keys()].map(n => this.stopOrg(n)),
|
|
1120
|
+
...[...this.orgs.keys()].map((n) => this.stopOrg(n)),
|
|
932
1121
|
...this.stopping.values(), // detached self-stops still flushing
|
|
933
1122
|
]);
|
|
934
1123
|
}
|
|
@@ -949,7 +1138,10 @@ export class OrgDaemon {
|
|
|
949
1138
|
// would brick every subsequent `org status` / isOrgRunning / scheduler
|
|
950
1139
|
// call. The state files in 6 other daemon paths already use this helper.
|
|
951
1140
|
writeJsonFileAtomic(p, {
|
|
952
|
-
status,
|
|
1141
|
+
status,
|
|
1142
|
+
run,
|
|
1143
|
+
pid: process.pid,
|
|
1144
|
+
updated: new Date().toISOString(),
|
|
953
1145
|
...(missing.length ? { abandonedRoles: missing } : {}),
|
|
954
1146
|
...(checkpoint ? { checkpoint } : {}),
|
|
955
1147
|
});
|
|
@@ -963,11 +1155,16 @@ export class OrgDaemon {
|
|
|
963
1155
|
// C4: atomic write — crash handler is the most likely place to hit
|
|
964
1156
|
// a partial write since the process is mid-teardown.
|
|
965
1157
|
writeJsonFileAtomic(p, {
|
|
966
|
-
status: 'crashed',
|
|
967
|
-
|
|
1158
|
+
status: 'crashed',
|
|
1159
|
+
run: org.run,
|
|
1160
|
+
pid: process.pid,
|
|
1161
|
+
updated: new Date().toISOString(),
|
|
1162
|
+
closedBy: 'crash-handler',
|
|
968
1163
|
});
|
|
969
1164
|
}
|
|
970
|
-
catch {
|
|
1165
|
+
catch {
|
|
1166
|
+
/* best effort — filesystem may be unavailable */
|
|
1167
|
+
}
|
|
971
1168
|
}
|
|
972
1169
|
}
|
|
973
1170
|
heartbeatPath() {
|
|
@@ -982,17 +1179,22 @@ export class OrgDaemon {
|
|
|
982
1179
|
// C4: atomic write — heartbeat corruption is how `org status` reports
|
|
983
1180
|
// a phantom daemon after a crash.
|
|
984
1181
|
writeJsonFileAtomic(p, {
|
|
985
|
-
pid: process.pid,
|
|
1182
|
+
pid: process.pid,
|
|
1183
|
+
updatedAt: new Date().toISOString(),
|
|
986
1184
|
running: this.listRunning(),
|
|
987
1185
|
});
|
|
988
1186
|
}
|
|
989
|
-
catch {
|
|
1187
|
+
catch {
|
|
1188
|
+
/* best effort */
|
|
1189
|
+
}
|
|
990
1190
|
}
|
|
991
1191
|
clearHeartbeat() {
|
|
992
1192
|
try {
|
|
993
1193
|
unlinkSync(this.heartbeatPath());
|
|
994
1194
|
}
|
|
995
|
-
catch {
|
|
1195
|
+
catch {
|
|
1196
|
+
/* already gone or never written */
|
|
1197
|
+
}
|
|
996
1198
|
}
|
|
997
1199
|
// ── Delegated methods — extracted to focused modules ──────────────────
|
|
998
1200
|
// approvals.ts
|
|
@@ -1028,6 +1230,18 @@ export class OrgDaemon {
|
|
|
1028
1230
|
dagCompleteTask(org, role, taskId, result) {
|
|
1029
1231
|
return decisionOps.dagCompleteTask(this, org, role, taskId, result);
|
|
1030
1232
|
}
|
|
1233
|
+
dagSplitTask(org, role, parentId, children) {
|
|
1234
|
+
return decisionOps.dagSplitTask(this, org, role, parentId, children);
|
|
1235
|
+
}
|
|
1236
|
+
dagMergeTask(org, role, sourceId, targetId) {
|
|
1237
|
+
return decisionOps.dagMergeTask(this, org, role, sourceId, targetId);
|
|
1238
|
+
}
|
|
1239
|
+
dagCancelTask(org, role, taskId, reason) {
|
|
1240
|
+
return decisionOps.dagCancelTask(this, org, role, taskId, reason);
|
|
1241
|
+
}
|
|
1242
|
+
dagPlanGraph(org, role, specs) {
|
|
1243
|
+
return decisionOps.dagPlanGraph(this, org, role, specs);
|
|
1244
|
+
}
|
|
1031
1245
|
recordDecision(org, role, decision) {
|
|
1032
1246
|
decisionOps.recordDecision(this, org, role, decision);
|
|
1033
1247
|
}
|
|
@@ -1043,16 +1257,26 @@ export class OrgDaemon {
|
|
|
1043
1257
|
}
|
|
1044
1258
|
// scheduler-integration.ts
|
|
1045
1259
|
/** @internal */
|
|
1046
|
-
autoWake(name) {
|
|
1047
|
-
|
|
1260
|
+
autoWake(name) {
|
|
1261
|
+
scheduler.autoWake(this, name);
|
|
1262
|
+
}
|
|
1263
|
+
scheduleBossRestart(name) {
|
|
1264
|
+
scheduler.scheduleBossRestart(this, name);
|
|
1265
|
+
}
|
|
1048
1266
|
/** @internal */
|
|
1049
1267
|
scheduleDeferredSpawn(name, running, role, spawnRole) {
|
|
1050
1268
|
scheduler.scheduleDeferredSpawn(this, name, running, role, spawnRole);
|
|
1051
1269
|
}
|
|
1052
1270
|
// org-memory.ts
|
|
1053
|
-
orgMemoryNamespace(name, def) {
|
|
1054
|
-
|
|
1055
|
-
|
|
1271
|
+
orgMemoryNamespace(name, def) {
|
|
1272
|
+
return orgMemory.orgMemoryNamespace(name, def);
|
|
1273
|
+
}
|
|
1274
|
+
orgMemoryDbPath() {
|
|
1275
|
+
return orgMemory.orgMemoryDbPath(this.root);
|
|
1276
|
+
}
|
|
1277
|
+
orgMemoryUsable() {
|
|
1278
|
+
return orgMemory.orgMemoryUsable(this.root);
|
|
1279
|
+
}
|
|
1056
1280
|
async rememberOrgMemory(name, def, role, content, scope, run) {
|
|
1057
1281
|
return orgMemory.rememberOrgMemory(this.root, name, def, role, content, scope, run);
|
|
1058
1282
|
}
|