@wrongstack/core 0.286.0 → 0.287.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/boot.d.ts.map +1 -1
- package/dist/coordination/brain-monitor.d.ts +15 -0
- package/dist/coordination/brain-monitor.d.ts.map +1 -1
- package/dist/coordination/delegate-tool.d.ts +8 -14
- package/dist/coordination/delegate-tool.d.ts.map +1 -1
- package/dist/coordination/director/director-btw-notes.d.ts +20 -0
- package/dist/coordination/director/director-btw-notes.d.ts.map +1 -0
- package/dist/coordination/director/director-collab.d.ts +40 -0
- package/dist/coordination/director/director-collab.d.ts.map +1 -0
- package/dist/coordination/director.d.ts +27 -8
- package/dist/coordination/director.d.ts.map +1 -1
- package/dist/coordination/fleet-manager.d.ts +21 -0
- package/dist/coordination/fleet-manager.d.ts.map +1 -1
- package/dist/coordination/fleet.d.ts +2 -3
- package/dist/coordination/fleet.d.ts.map +1 -1
- package/dist/coordination/global-mailbox.d.ts +36 -22
- package/dist/coordination/global-mailbox.d.ts.map +1 -1
- package/dist/coordination/icoordinator.d.ts +1 -3
- package/dist/coordination/icoordinator.d.ts.map +1 -1
- package/dist/coordination/index.js +708 -442
- package/dist/coordination/index.js.map +4 -4
- package/dist/coordination/mail-tools.d.ts.map +1 -1
- package/dist/coordination/mailbox-message-codec.d.ts +21 -1
- package/dist/coordination/mailbox-message-codec.d.ts.map +1 -1
- package/dist/coordination/mailbox-types.d.ts +42 -1
- package/dist/coordination/mailbox-types.d.ts.map +1 -1
- package/dist/core/agent-loop.d.ts.map +1 -1
- package/dist/core/agent-response.d.ts +10 -1
- package/dist/core/agent-response.d.ts.map +1 -1
- package/dist/core/agent-tools.d.ts.map +1 -1
- package/dist/core/agent.d.ts +8 -0
- package/dist/core/agent.d.ts.map +1 -1
- package/dist/core/context.d.ts +25 -0
- package/dist/core/context.d.ts.map +1 -1
- package/dist/core/conversation-state.d.ts +6 -0
- package/dist/core/conversation-state.d.ts.map +1 -1
- package/dist/core/fallback-model.d.ts.map +1 -1
- package/dist/core/mailbox-loop.d.ts +12 -5
- package/dist/core/mailbox-loop.d.ts.map +1 -1
- package/dist/core/system-prompt-builder.d.ts +14 -6
- package/dist/core/system-prompt-builder.d.ts.map +1 -1
- package/dist/defaults/index.d.ts +0 -1
- package/dist/defaults/index.d.ts.map +1 -1
- package/dist/defaults/index.js +834 -1465
- package/dist/defaults/index.js.map +4 -4
- package/dist/execution/auto-compaction-middleware.d.ts +8 -1
- package/dist/execution/auto-compaction-middleware.d.ts.map +1 -1
- package/dist/execution/compaction-core.d.ts.map +1 -1
- package/dist/execution/council-brain.d.ts.map +1 -1
- package/dist/execution/council-orchestrator.d.ts +32 -0
- package/dist/execution/council-orchestrator.d.ts.map +1 -0
- package/dist/execution/council-personas.d.ts +19 -0
- package/dist/execution/council-personas.d.ts.map +1 -0
- package/dist/execution/council-profiles.d.ts +34 -0
- package/dist/execution/council-profiles.d.ts.map +1 -0
- package/dist/execution/council-prompts.d.ts +21 -0
- package/dist/execution/council-prompts.d.ts.map +1 -0
- package/dist/execution/council-resolution.d.ts +57 -0
- package/dist/execution/council-resolution.d.ts.map +1 -0
- package/dist/execution/design-kit-loader.d.ts.map +1 -1
- package/dist/execution/enhance-recovery.d.ts +35 -0
- package/dist/execution/enhance-recovery.d.ts.map +1 -0
- package/dist/execution/error-handler.d.ts.map +1 -1
- package/dist/execution/index.d.ts +6 -0
- package/dist/execution/index.d.ts.map +1 -1
- package/dist/execution/index.js +1738 -161
- package/dist/execution/index.js.map +4 -4
- package/dist/execution/intelligent-compactor.d.ts +18 -7
- package/dist/execution/intelligent-compactor.d.ts.map +1 -1
- package/dist/execution/one-shot-llm.d.ts +52 -0
- package/dist/execution/one-shot-llm.d.ts.map +1 -0
- package/dist/execution/prompt-enhancer.d.ts +23 -4
- package/dist/execution/prompt-enhancer.d.ts.map +1 -1
- package/dist/execution/prompt-enhancer.js +24 -6
- package/dist/execution/prompt-enhancer.js.map +2 -2
- package/dist/execution/selective-compactor.d.ts +4 -4
- package/dist/execution/strategy-compactor.d.ts +7 -0
- package/dist/execution/strategy-compactor.d.ts.map +1 -1
- package/dist/execution/tool-executor.d.ts +19 -5
- package/dist/execution/tool-executor.d.ts.map +1 -1
- package/dist/hooks/registry.d.ts.map +1 -1
- package/dist/hooks/runner.d.ts.map +1 -1
- package/dist/hooks/shell-executor.d.ts.map +1 -1
- package/dist/hq/alerts.d.ts +5 -0
- package/dist/hq/alerts.d.ts.map +1 -1
- package/dist/hq/auth-store.d.ts +17 -7
- package/dist/hq/auth-store.d.ts.map +1 -1
- package/dist/hq/commands.d.ts +8 -4
- package/dist/hq/commands.d.ts.map +1 -1
- package/dist/hq/cost-bridge.d.ts +5 -3
- package/dist/hq/cost-bridge.d.ts.map +1 -1
- package/dist/hq/fleet-bridge.d.ts.map +1 -1
- package/dist/hq/index.d.ts +0 -1
- package/dist/hq/index.d.ts.map +1 -1
- package/dist/hq/index.js +471 -294
- package/dist/hq/index.js.map +4 -4
- package/dist/hq/persistence.d.ts +42 -0
- package/dist/hq/persistence.d.ts.map +1 -1
- package/dist/hq/protocol/brain.d.ts +24 -0
- package/dist/hq/protocol/brain.d.ts.map +1 -0
- package/dist/hq/protocol/browser.d.ts +12 -0
- package/dist/hq/protocol/browser.d.ts.map +1 -0
- package/dist/hq/protocol/client.d.ts +69 -0
- package/dist/hq/protocol/client.d.ts.map +1 -0
- package/dist/hq/protocol/core.d.ts +130 -0
- package/dist/hq/protocol/core.d.ts.map +1 -0
- package/dist/hq/protocol/fleet.d.ts +73 -0
- package/dist/hq/protocol/fleet.d.ts.map +1 -0
- package/dist/hq/protocol/mailbox.d.ts +76 -0
- package/dist/hq/protocol/mailbox.d.ts.map +1 -0
- package/dist/hq/protocol/mcp.d.ts +51 -0
- package/dist/hq/protocol/mcp.d.ts.map +1 -0
- package/dist/hq/protocol/project.d.ts +26 -0
- package/dist/hq/protocol/project.d.ts.map +1 -0
- package/dist/hq/protocol/session.d.ts +153 -0
- package/dist/hq/protocol/session.d.ts.map +1 -0
- package/dist/hq/protocol/tool.d.ts +31 -0
- package/dist/hq/protocol/tool.d.ts.map +1 -0
- package/dist/hq/protocol.d.ts +10 -611
- package/dist/hq/protocol.d.ts.map +1 -1
- package/dist/index.d.ts +12 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4540 -2283
- package/dist/index.js.map +4 -4
- package/dist/infrastructure/index.js +13 -11
- package/dist/infrastructure/index.js.map +2 -2
- package/dist/infrastructure/token-counter.d.ts.map +1 -1
- package/dist/kernel/events/agent-events.d.ts +283 -0
- package/dist/kernel/events/agent-events.d.ts.map +1 -0
- package/dist/kernel/events/brain-events.d.ts +176 -0
- package/dist/kernel/events/brain-events.d.ts.map +1 -0
- package/dist/kernel/events/fleet-events.d.ts +39 -0
- package/dist/kernel/events/fleet-events.d.ts.map +1 -0
- package/dist/kernel/events/memory-events.d.ts +165 -0
- package/dist/kernel/events/memory-events.d.ts.map +1 -0
- package/dist/kernel/events/provider-events.d.ts +98 -0
- package/dist/kernel/events/provider-events.d.ts.map +1 -0
- package/dist/kernel/events/sdd-events.d.ts +118 -0
- package/dist/kernel/events/sdd-events.d.ts.map +1 -0
- package/dist/kernel/events/session-events.d.ts +227 -0
- package/dist/kernel/events/session-events.d.ts.map +1 -0
- package/dist/kernel/events/tool-events.d.ts +185 -0
- package/dist/kernel/events/tool-events.d.ts.map +1 -0
- package/dist/kernel/events/worktree-events.d.ts +58 -0
- package/dist/kernel/events/worktree-events.d.ts.map +1 -0
- package/dist/kernel/events.d.ts +10 -1298
- package/dist/kernel/events.d.ts.map +1 -1
- package/dist/kernel/index.js +22 -2
- package/dist/kernel/index.js.map +2 -2
- package/dist/kernel/run-controller.d.ts +15 -0
- package/dist/kernel/run-controller.d.ts.map +1 -1
- package/dist/mailbox-attach.d.ts.map +1 -1
- package/dist/models/index.js +19 -6
- package/dist/models/index.js.map +2 -2
- package/dist/models/llm-selector.d.ts +7 -0
- package/dist/models/llm-selector.d.ts.map +1 -1
- package/dist/plugins/auto-review-plugin.d.ts +36 -0
- package/dist/plugins/auto-review-plugin.d.ts.map +1 -0
- package/dist/plugins/chimera-plugin.d.ts +8 -0
- package/dist/plugins/chimera-plugin.d.ts.map +1 -1
- package/dist/security/index.js +7 -2
- package/dist/security/index.js.map +2 -2
- package/dist/security/secret-scrubber.d.ts.map +1 -1
- package/dist/skills/index.js.map +2 -2
- package/dist/storage/config-loader.d.ts.map +1 -1
- package/dist/storage/goal-kanban.d.ts +69 -0
- package/dist/storage/goal-kanban.d.ts.map +1 -0
- package/dist/storage/index.d.ts +1 -1
- package/dist/storage/index.d.ts.map +1 -1
- package/dist/storage/index.js +425 -717
- package/dist/storage/index.js.map +4 -4
- package/dist/storage/memory-consolidator.d.ts +9 -1
- package/dist/storage/memory-consolidator.d.ts.map +1 -1
- package/dist/tools/council-tool.d.ts +25 -0
- package/dist/tools/council-tool.d.ts.map +1 -0
- package/dist/tools/index.d.ts +2 -0
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +1884 -12
- package/dist/tools/index.js.map +4 -4
- package/dist/tools/one-shot-llm-tool.d.ts +51 -0
- package/dist/tools/one-shot-llm-tool.d.ts.map +1 -0
- package/dist/types/config.d.ts +27 -3
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/context-window.d.ts.map +1 -1
- package/dist/types/council.d.ts +134 -0
- package/dist/types/council.d.ts.map +1 -0
- package/dist/types/default-config.d.ts +3 -3
- package/dist/types/hooks.d.ts +14 -0
- package/dist/types/hooks.d.ts.map +1 -1
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +29 -8
- package/dist/types/index.js.map +3 -3
- package/dist/types/one-shot-llm.d.ts +128 -0
- package/dist/types/one-shot-llm.d.ts.map +1 -0
- package/dist/types/plugin.d.ts +6 -0
- package/dist/types/plugin.d.ts.map +1 -1
- package/dist/types/provider.d.ts +17 -0
- package/dist/types/provider.d.ts.map +1 -1
- package/dist/types/system-prompt.d.ts +2 -1
- package/dist/types/system-prompt.d.ts.map +1 -1
- package/dist/types/tool-executor.d.ts +10 -0
- package/dist/types/tool-executor.d.ts.map +1 -1
- package/dist/utils/atomic-write.d.ts.map +1 -1
- package/dist/utils/index.js +75 -8
- package/dist/utils/index.js.map +3 -3
- package/dist/utils/tool-wire-compact.d.ts +1 -0
- package/dist/utils/tool-wire-compact.d.ts.map +1 -1
- package/dist/utils/wstack-paths.d.ts +20 -4
- package/dist/utils/wstack-paths.d.ts.map +1 -1
- package/instructions/cli/next-task-predictor.md +5 -3
- package/instructions/coordination/subagent-baseline.md +8 -7
- package/instructions/leader-after-task.md +18 -9
- package/instructions/llm/council-judge.md +19 -0
- package/instructions/llm/council-voter.md +22 -0
- package/instructions/modes/architect.md +14 -9
- package/instructions/modes/audit-lite.md +9 -7
- package/instructions/modes/brief-system.md +12 -12
- package/instructions/modes/brief.md +7 -5
- package/instructions/modes/code-auditor.md +13 -8
- package/instructions/modes/code-reviewer.md +13 -8
- package/instructions/modes/debug-lite.md +7 -6
- package/instructions/modes/debugger.md +13 -9
- package/instructions/modes/devops.md +13 -9
- package/instructions/modes/plan-lite.md +7 -6
- package/instructions/modes/refactor-lite.md +7 -6
- package/instructions/modes/refactorer.md +14 -9
- package/instructions/modes/research-lite.md +8 -6
- package/instructions/modes/research-web.md +18 -55
- package/instructions/modes/review-lite.md +7 -6
- package/instructions/modes/teach-system.md +15 -15
- package/instructions/modes/teach.md +13 -12
- package/instructions/modes/test-lite.md +6 -5
- package/instructions/modes/tester.md +13 -9
- package/instructions/modes/ui-design.md +14 -8
- package/instructions/sections/tool/commit-hygiene.md +1 -7
- package/instructions/sections/tool/common-patterns.md +5 -5
- package/instructions/sections/tool/delegation-full.md +3 -31
- package/instructions/sections/tool/mailbox-compact.md +1 -1
- package/instructions/sections/tool/mailbox-full.md +6 -22
- package/instructions/system.md +278 -39
- package/package.json +2 -2
- package/skills/mailbox-bridge/SKILL.md +16 -7
- package/skills/output-standards/SKILL.md +16 -16
- package/skills/wrongstack-mailbox/SKILL.md +6 -0
- package/dist/coordination/director-construction.d.ts +0 -90
- package/dist/coordination/director-construction.d.ts.map +0 -1
- package/dist/hq/agent-bridge.d.ts +0 -27
- package/dist/hq/agent-bridge.d.ts.map +0 -1
- package/dist/storage/memory-store.d.ts +0 -119
- package/dist/storage/memory-store.d.ts.map +0 -1
|
@@ -4,6 +4,7 @@ import { randomUUID } from "node:crypto";
|
|
|
4
4
|
// src/utils/atomic-write.ts
|
|
5
5
|
import { randomBytes } from "node:crypto";
|
|
6
6
|
import * as fs from "node:fs/promises";
|
|
7
|
+
import { watch as watchDir } from "node:fs";
|
|
7
8
|
import * as path from "node:path";
|
|
8
9
|
async function atomicWrite(targetPath, content, opts = {}) {
|
|
9
10
|
const dir = path.dirname(targetPath);
|
|
@@ -71,7 +72,7 @@ async function withFileLock(targetPath, fn, opts = {}) {
|
|
|
71
72
|
await fs.mkdir(dir, { recursive: true });
|
|
72
73
|
continue;
|
|
73
74
|
}
|
|
74
|
-
if (code !== "EEXIST") throw err;
|
|
75
|
+
if (code !== "EEXIST" && code !== "EPERM") throw err;
|
|
75
76
|
try {
|
|
76
77
|
const stat9 = await fs.stat(lockPath);
|
|
77
78
|
if (Date.now() - stat9.mtimeMs > staleMs) {
|
|
@@ -81,7 +82,8 @@ async function withFileLock(targetPath, fn, opts = {}) {
|
|
|
81
82
|
} catch {
|
|
82
83
|
continue;
|
|
83
84
|
}
|
|
84
|
-
|
|
85
|
+
const elapsed = Date.now() - started;
|
|
86
|
+
if (elapsed >= timeoutMs) {
|
|
85
87
|
throw new FsError({
|
|
86
88
|
message: `Timed out waiting for file lock: ${targetPath}`,
|
|
87
89
|
code: "FS_ATOMIC_WRITE_FAILED",
|
|
@@ -89,7 +91,7 @@ async function withFileLock(targetPath, fn, opts = {}) {
|
|
|
89
91
|
context: { timeoutMs }
|
|
90
92
|
});
|
|
91
93
|
}
|
|
92
|
-
await
|
|
94
|
+
await waitForLockRelease(lockPath, timeoutMs - elapsed);
|
|
93
95
|
}
|
|
94
96
|
}
|
|
95
97
|
try {
|
|
@@ -105,6 +107,50 @@ async function withFileLock(targetPath, fn, opts = {}) {
|
|
|
105
107
|
}
|
|
106
108
|
}
|
|
107
109
|
}
|
|
110
|
+
async function waitForLockRelease(lockPath, remainingMs) {
|
|
111
|
+
const parentDir = path.dirname(lockPath);
|
|
112
|
+
const lockName = path.basename(lockPath);
|
|
113
|
+
const intervalMs = Math.min(remainingMs, 100);
|
|
114
|
+
return new Promise((resolve9) => {
|
|
115
|
+
let settled = false;
|
|
116
|
+
let watcher = null;
|
|
117
|
+
const timer = setTimeout(() => {
|
|
118
|
+
settled = true;
|
|
119
|
+
watcher?.close();
|
|
120
|
+
resolve9();
|
|
121
|
+
}, intervalMs);
|
|
122
|
+
try {
|
|
123
|
+
watcher = watchDir(parentDir, (eventType, filename) => {
|
|
124
|
+
if (settled) return;
|
|
125
|
+
if (filename === lockName && (eventType === "rename" || eventType === "change")) {
|
|
126
|
+
settled = true;
|
|
127
|
+
clearTimeout(timer);
|
|
128
|
+
watcher?.close();
|
|
129
|
+
resolve9();
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
} catch {
|
|
133
|
+
clearTimeout(timer);
|
|
134
|
+
if (!settled) {
|
|
135
|
+
settled = true;
|
|
136
|
+
setTimeout(resolve9, Math.min(remainingMs, 25));
|
|
137
|
+
}
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
fs.access(lockPath).then(
|
|
141
|
+
() => {
|
|
142
|
+
},
|
|
143
|
+
() => {
|
|
144
|
+
if (!settled) {
|
|
145
|
+
settled = true;
|
|
146
|
+
clearTimeout(timer);
|
|
147
|
+
watcher?.close();
|
|
148
|
+
resolve9();
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
);
|
|
152
|
+
});
|
|
153
|
+
}
|
|
108
154
|
var TRANSIENT_RENAME_CODES = /* @__PURE__ */ new Set(["EPERM", "EBUSY", "EACCES", "ENOTEMPTY"]);
|
|
109
155
|
async function renameWithRetry(from, to) {
|
|
110
156
|
if (process.platform !== "win32") {
|
|
@@ -587,11 +633,31 @@ function truncate(s, max) {
|
|
|
587
633
|
|
|
588
634
|
// src/utils/wstack-paths.ts
|
|
589
635
|
import { createHash } from "node:crypto";
|
|
636
|
+
import * as fs2 from "node:fs";
|
|
590
637
|
import * as os from "node:os";
|
|
591
638
|
import * as path3 from "node:path";
|
|
639
|
+
function canonicalProjectRoot(absRoot) {
|
|
640
|
+
const checkoutRoot = path3.resolve(absRoot);
|
|
641
|
+
const dotGit = path3.join(checkoutRoot, ".git");
|
|
642
|
+
try {
|
|
643
|
+
if (!fs2.statSync(dotGit).isFile()) return checkoutRoot;
|
|
644
|
+
const gitDirLine = fs2.readFileSync(dotGit, "utf8").trim();
|
|
645
|
+
const match = /^gitdir:\s*(.+)$/i.exec(gitDirLine);
|
|
646
|
+
if (!match?.[1]) return checkoutRoot;
|
|
647
|
+
const gitDir = path3.resolve(checkoutRoot, match[1].trim());
|
|
648
|
+
const commonDirFile = path3.join(gitDir, "commondir");
|
|
649
|
+
if (!fs2.statSync(commonDirFile).isFile()) return checkoutRoot;
|
|
650
|
+
const commonDir = path3.resolve(gitDir, fs2.readFileSync(commonDirFile, "utf8").trim());
|
|
651
|
+
if (path3.basename(commonDir).toLowerCase() !== ".git") return checkoutRoot;
|
|
652
|
+
return path3.dirname(commonDir);
|
|
653
|
+
} catch {
|
|
654
|
+
return checkoutRoot;
|
|
655
|
+
}
|
|
656
|
+
}
|
|
592
657
|
function projectSlug(absRoot) {
|
|
593
|
-
const
|
|
594
|
-
const
|
|
658
|
+
const identityRoot = canonicalProjectRoot(absRoot);
|
|
659
|
+
const base = slugify(path3.basename(identityRoot));
|
|
660
|
+
const hash = createHash("sha256").update(identityRoot).digest("hex").slice(0, 6);
|
|
595
661
|
return `${base}-${hash}`;
|
|
596
662
|
}
|
|
597
663
|
function slugify(name) {
|
|
@@ -1827,7 +1893,7 @@ var TOOLS = {
|
|
|
1827
1893
|
};
|
|
1828
1894
|
|
|
1829
1895
|
// src/coordination/agents/agent-prompts.ts
|
|
1830
|
-
import { readFileSync, statSync } from "node:fs";
|
|
1896
|
+
import { readFileSync as readFileSync2, statSync as statSync2 } from "node:fs";
|
|
1831
1897
|
import * as os2 from "node:os";
|
|
1832
1898
|
import * as path4 from "node:path";
|
|
1833
1899
|
import { fileURLToPath } from "node:url";
|
|
@@ -1842,7 +1908,7 @@ function agentPrompt(id) {
|
|
|
1842
1908
|
let resolved = "";
|
|
1843
1909
|
for (const dir of agentPromptDirCandidates(envDir)) {
|
|
1844
1910
|
try {
|
|
1845
|
-
resolved =
|
|
1911
|
+
resolved = readFileSync2(path4.join(dir, fileName), "utf8").trimEnd();
|
|
1846
1912
|
break;
|
|
1847
1913
|
} catch {
|
|
1848
1914
|
}
|
|
@@ -1872,7 +1938,7 @@ function agentPromptDirCandidates(envDir) {
|
|
|
1872
1938
|
}
|
|
1873
1939
|
function isDirectory(candidate) {
|
|
1874
1940
|
try {
|
|
1875
|
-
return
|
|
1941
|
+
return statSync2(candidate).isDirectory();
|
|
1876
1942
|
} catch {
|
|
1877
1943
|
return false;
|
|
1878
1944
|
}
|
|
@@ -3624,7 +3690,7 @@ function formatHumanPrompt(request) {
|
|
|
3624
3690
|
|
|
3625
3691
|
// src/coordination/brain-ledger.ts
|
|
3626
3692
|
import { appendFile, mkdir as mkdir2, readFile } from "node:fs/promises";
|
|
3627
|
-
import { dirname as
|
|
3693
|
+
import { dirname as dirname4 } from "node:path";
|
|
3628
3694
|
var QUESTION_MAX = 200;
|
|
3629
3695
|
var DETAIL_MAX = 240;
|
|
3630
3696
|
var truncate2 = (s, max) => s === void 0 ? void 0 : s.length > max ? `${s.slice(0, max - 1)}\u2026` : s;
|
|
@@ -3876,7 +3942,7 @@ var BrainDecisionLedger = class {
|
|
|
3876
3942
|
append(entry) {
|
|
3877
3943
|
this.writeChain = this.writeChain.then(async () => {
|
|
3878
3944
|
if (!this.dirReady) {
|
|
3879
|
-
await mkdir2(
|
|
3945
|
+
await mkdir2(dirname4(this.opts.filePath), { recursive: true });
|
|
3880
3946
|
this.dirReady = true;
|
|
3881
3947
|
}
|
|
3882
3948
|
await appendFile(this.opts.filePath, `${JSON.stringify(entry)}
|
|
@@ -3952,9 +4018,16 @@ var BrainMonitor = class {
|
|
|
3952
4018
|
fileChurnThreshold;
|
|
3953
4019
|
fileChurnWindowMs;
|
|
3954
4020
|
cooldownMs;
|
|
4021
|
+
/** Resolve the leader's own session id for event filtering. */
|
|
4022
|
+
resolveLeaderSessionId() {
|
|
4023
|
+
const id = this.opts.leaderSessionId;
|
|
4024
|
+
return typeof id === "function" ? id() : id;
|
|
4025
|
+
}
|
|
3955
4026
|
start() {
|
|
3956
4027
|
this.unsubscribers.push(
|
|
3957
4028
|
this.opts.events.on("tool.executed", (e) => {
|
|
4029
|
+
const leaderSid = this.resolveLeaderSessionId();
|
|
4030
|
+
if (leaderSid && e.sessionId && e.sessionId !== leaderSid) return;
|
|
3958
4031
|
this.lastProgressAt = Date.now();
|
|
3959
4032
|
this.trackFileChurn(e.name, e.ok, e.input);
|
|
3960
4033
|
if (e.ok) {
|
|
@@ -3978,17 +4051,25 @@ var BrainMonitor = class {
|
|
|
3978
4051
|
);
|
|
3979
4052
|
if (this.stallMs > 0) {
|
|
3980
4053
|
this.unsubscribers.push(
|
|
3981
|
-
this.opts.events.on("agent.run.started", () => {
|
|
4054
|
+
this.opts.events.on("agent.run.started", (e) => {
|
|
4055
|
+
const lsid = this.resolveLeaderSessionId();
|
|
4056
|
+
if (lsid && e.sessionId && e.sessionId !== lsid) return;
|
|
3982
4057
|
this.activeRuns += 1;
|
|
3983
4058
|
this.lastProgressAt = Date.now();
|
|
3984
4059
|
}),
|
|
3985
|
-
this.opts.events.on("agent.run.completed", () => {
|
|
4060
|
+
this.opts.events.on("agent.run.completed", (e) => {
|
|
4061
|
+
const lsid = this.resolveLeaderSessionId();
|
|
4062
|
+
if (lsid && e.sessionId && e.sessionId !== lsid) return;
|
|
3986
4063
|
this.activeRuns = Math.max(0, this.activeRuns - 1);
|
|
3987
4064
|
}),
|
|
3988
|
-
this.opts.events.on("agent.run.error", () => {
|
|
4065
|
+
this.opts.events.on("agent.run.error", (e) => {
|
|
4066
|
+
const lsid = this.resolveLeaderSessionId();
|
|
4067
|
+
if (lsid && e.sessionId && e.sessionId !== lsid) return;
|
|
3989
4068
|
this.activeRuns = Math.max(0, this.activeRuns - 1);
|
|
3990
4069
|
}),
|
|
3991
|
-
this.opts.events.on("iteration.started", () => {
|
|
4070
|
+
this.opts.events.on("iteration.started", (e) => {
|
|
4071
|
+
const lsid = this.resolveLeaderSessionId();
|
|
4072
|
+
if (lsid && e.sessionId && e.sessionId !== lsid) return;
|
|
3992
4073
|
this.lastProgressAt = Date.now();
|
|
3993
4074
|
})
|
|
3994
4075
|
);
|
|
@@ -4010,6 +4091,8 @@ var BrainMonitor = class {
|
|
|
4010
4091
|
}
|
|
4011
4092
|
this.unsubscribers.push(
|
|
4012
4093
|
this.opts.events.on("error", (e) => {
|
|
4094
|
+
const lsid = this.resolveLeaderSessionId();
|
|
4095
|
+
if (lsid && e.sessionId && e.sessionId !== lsid) return;
|
|
4013
4096
|
const now = Date.now();
|
|
4014
4097
|
this.errorTimestamps.push(now);
|
|
4015
4098
|
this.errorTimestamps = this.errorTimestamps.filter(
|
|
@@ -4895,19 +4978,19 @@ var FLEET_ROSTER = {
|
|
|
4895
4978
|
ALL_AGENT_DEFINITIONS.map((d) => [d.config.role, d.config])
|
|
4896
4979
|
)
|
|
4897
4980
|
};
|
|
4898
|
-
var DEFAULT_IDLE_TIMEOUT_MS =
|
|
4981
|
+
var DEFAULT_IDLE_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
4899
4982
|
var FLEET_ROSTER_BUDGETS = {
|
|
4900
|
-
"audit-log": { timeoutMs:
|
|
4901
|
-
"bug-hunter": { timeoutMs:
|
|
4902
|
-
"refactor-planner": { timeoutMs:
|
|
4903
|
-
"security-scanner": { timeoutMs:
|
|
4904
|
-
critic: { timeoutMs:
|
|
4983
|
+
"audit-log": { timeoutMs: 3 * 60 * 60 * 1e3, maxIterations: 2500, maxToolCalls: 7500 },
|
|
4984
|
+
"bug-hunter": { timeoutMs: 5 * 60 * 60 * 1e3, maxIterations: 4e3, maxToolCalls: 1e4 },
|
|
4985
|
+
"refactor-planner": { timeoutMs: 4 * 60 * 60 * 1e3, maxIterations: 3e3, maxToolCalls: 9e3 },
|
|
4986
|
+
"security-scanner": { timeoutMs: 5 * 60 * 60 * 1e3, maxIterations: 4e3, maxToolCalls: 1e4 },
|
|
4987
|
+
critic: { timeoutMs: 3 * 60 * 60 * 1e3, maxIterations: 2e3, maxToolCalls: 6e3 },
|
|
4905
4988
|
"shadow-agent": {
|
|
4906
4989
|
idleTimeoutMs: DEFAULT_IDLE_TIMEOUT_MS,
|
|
4907
|
-
maxIterations:
|
|
4908
|
-
maxToolCalls:
|
|
4909
|
-
maxTokens:
|
|
4910
|
-
maxCostUsd:
|
|
4990
|
+
maxIterations: 1e3,
|
|
4991
|
+
maxToolCalls: 2500,
|
|
4992
|
+
maxTokens: 3e4,
|
|
4993
|
+
maxCostUsd: 0.5
|
|
4911
4994
|
},
|
|
4912
4995
|
...Object.fromEntries(
|
|
4913
4996
|
ALL_AGENT_DEFINITIONS.map((d) => [d.config.role, d.budget])
|
|
@@ -4915,8 +4998,8 @@ var FLEET_ROSTER_BUDGETS = {
|
|
|
4915
4998
|
};
|
|
4916
4999
|
var GENERIC_SUBAGENT_BUDGET = {
|
|
4917
5000
|
idleTimeoutMs: DEFAULT_IDLE_TIMEOUT_MS,
|
|
4918
|
-
maxIterations:
|
|
4919
|
-
maxToolCalls:
|
|
5001
|
+
maxIterations: 2500,
|
|
5002
|
+
maxToolCalls: 7500
|
|
4920
5003
|
};
|
|
4921
5004
|
function applyRosterBudget(cfg) {
|
|
4922
5005
|
const roleBudget = cfg.role ? FLEET_ROSTER_BUDGETS[cfg.role] : void 0;
|
|
@@ -4972,72 +5055,71 @@ function createDelegateTool(opts) {
|
|
|
4972
5055
|
properties: {
|
|
4973
5056
|
task: {
|
|
4974
5057
|
type: "string",
|
|
4975
|
-
description: "What the subagent should do \u2014 natural language, complete sentence(s).
|
|
5058
|
+
description: "What the subagent should do \u2014 natural language, complete sentence(s)."
|
|
4976
5059
|
},
|
|
4977
5060
|
role: {
|
|
4978
5061
|
type: "string",
|
|
4979
|
-
description: rosterIds.length > 0 ?
|
|
4980
|
-
enum: rosterIds.length > 0 ? rosterIds : void 0
|
|
5062
|
+
description: rosterIds.length > 0 ? "Roster role id. Common: bug-hunter, security-scanner, refactor-planner, critic, audit-log, executor, shadow-agent, architect." : "No roster configured \u2014 pass `name` instead."
|
|
4981
5063
|
},
|
|
4982
5064
|
name: {
|
|
4983
5065
|
type: "string",
|
|
4984
|
-
description: "Display name for free-form subagents (
|
|
5066
|
+
description: "Display name for free-form subagents (no roster role). Required when `role` is omitted."
|
|
4985
5067
|
},
|
|
4986
5068
|
provider: {
|
|
4987
5069
|
type: "string",
|
|
4988
|
-
description: 'Provider id (e.g. "anthropic", "openai"). Defaults to
|
|
5070
|
+
description: 'Provider id (e.g. "anthropic", "openai"). Defaults to host provider.'
|
|
4989
5071
|
},
|
|
4990
5072
|
model: {
|
|
4991
5073
|
type: "string",
|
|
4992
|
-
description: "Model id within the provider. Defaults to
|
|
5074
|
+
description: "Model id within the provider. Defaults to host model."
|
|
4993
5075
|
},
|
|
4994
5076
|
systemPromptOverride: {
|
|
4995
5077
|
type: "string",
|
|
4996
|
-
description: "
|
|
5078
|
+
description: "Extra prompt text appended to the role baseline."
|
|
4997
5079
|
},
|
|
4998
5080
|
timeoutMs: {
|
|
4999
5081
|
type: "number",
|
|
5000
5082
|
minimum: 1,
|
|
5001
|
-
description: `Wall-clock budget
|
|
5083
|
+
description: `Wall-clock budget in ms (default ${Math.round(defaultTimeoutMs / 1e3 / 60)} min). No hard cap \u2014 set as high as the task needs.`
|
|
5002
5084
|
},
|
|
5003
5085
|
maxIterations: {
|
|
5004
5086
|
type: "number",
|
|
5005
5087
|
minimum: 1,
|
|
5006
|
-
description: "Maximum LLM iterations
|
|
5088
|
+
description: "Maximum LLM iterations. Unset = role default. Raise for deep multi-step tasks."
|
|
5007
5089
|
},
|
|
5008
5090
|
maxToolCalls: {
|
|
5009
5091
|
type: "number",
|
|
5010
5092
|
minimum: 1,
|
|
5011
|
-
description: "Maximum
|
|
5093
|
+
description: "Maximum tool invocations. Unset = role default. Raise for file-heavy tasks."
|
|
5012
5094
|
},
|
|
5013
5095
|
idleTimeoutMs: {
|
|
5014
5096
|
type: "number",
|
|
5015
5097
|
minimum: 1,
|
|
5016
|
-
description: "Idle timeout in ms
|
|
5098
|
+
description: "Idle timeout in ms. Resets on activity. Unset = role default."
|
|
5017
5099
|
},
|
|
5018
5100
|
maxTokens: {
|
|
5019
5101
|
type: "number",
|
|
5020
5102
|
minimum: 1,
|
|
5021
|
-
description: "
|
|
5103
|
+
description: "Max total tokens (input+output). Unset = role default."
|
|
5022
5104
|
},
|
|
5023
5105
|
maxCostUsd: {
|
|
5024
5106
|
type: "number",
|
|
5025
5107
|
minimum: 0,
|
|
5026
|
-
description: "
|
|
5108
|
+
description: "Max estimated USD cost. Unset = role default."
|
|
5027
5109
|
},
|
|
5028
5110
|
maxHandoffs: {
|
|
5029
5111
|
type: "number",
|
|
5030
5112
|
minimum: 0,
|
|
5031
5113
|
maximum: 8,
|
|
5032
|
-
description: "
|
|
5114
|
+
description: "Max fresh-worker continuations after budget exhaustion. Default 1. Each gets the prior partial report."
|
|
5033
5115
|
}
|
|
5034
5116
|
},
|
|
5035
5117
|
required: ["task"]
|
|
5036
5118
|
};
|
|
5037
5119
|
return {
|
|
5038
5120
|
name: "delegate",
|
|
5039
|
-
description: "Hand a
|
|
5040
|
-
usageHint: "Set `task` to a complete instruction.
|
|
5121
|
+
description: "Hand a piece of work to a subagent and wait for its result. Has own context, LLM call, auto-extending budget. Can continue remaining work with a fresh subagent on partial completion (maxHandoffs, default 1). Workers cannot recursively spawn. Use roster roles when possible; otherwise pass `name` + `task`.",
|
|
5122
|
+
usageHint: "Set `task` to a complete instruction. Pick `role` from roster or pass `name` for free-form. Override `timeoutMs`/`maxIterations`/`maxToolCalls` only when needed.",
|
|
5041
5123
|
permission: "auto",
|
|
5042
5124
|
mutating: false,
|
|
5043
5125
|
managesOwnTimeout: true,
|
|
@@ -5064,10 +5146,9 @@ function createDelegateTool(opts) {
|
|
|
5064
5146
|
director = await opts.host.promoteToDirector();
|
|
5065
5147
|
}
|
|
5066
5148
|
if (!director) {
|
|
5067
|
-
const reason = opts.host.getPromotionBlockReason?.();
|
|
5068
5149
|
return {
|
|
5069
5150
|
ok: false,
|
|
5070
|
-
error:
|
|
5151
|
+
error: "Director could not be activated \u2014 fleet orchestration is unavailable."
|
|
5071
5152
|
};
|
|
5072
5153
|
}
|
|
5073
5154
|
const timeoutMs = i.timeoutMs ?? defaultTimeoutMs;
|
|
@@ -5938,8 +6019,86 @@ var FleetContextOverflowError = class extends Error {
|
|
|
5938
6019
|
}
|
|
5939
6020
|
};
|
|
5940
6021
|
|
|
6022
|
+
// src/coordination/director/director-collab.ts
|
|
6023
|
+
var DirectorCollabController = class {
|
|
6024
|
+
constructor(deps) {
|
|
6025
|
+
this.deps = deps;
|
|
6026
|
+
}
|
|
6027
|
+
deps;
|
|
6028
|
+
/** Active collab sessions tracked by sessionId (see spawn).
|
|
6029
|
+
* The tuple holds the session and its Director-registered listener unsubs.
|
|
6030
|
+
* Calling the unsubs on cancel/premature-cleanup prevents listener accumulation
|
|
6031
|
+
* on CollabSession (EventEmitter) across many spawn() calls. */
|
|
6032
|
+
activeSessions = /* @__PURE__ */ new Map();
|
|
6033
|
+
async spawn(options) {
|
|
6034
|
+
const session = new CollabSession(this.deps.director, this.deps.fleet, {
|
|
6035
|
+
...options,
|
|
6036
|
+
onBudgetWarning: (alert) => {
|
|
6037
|
+
return options.onBudgetWarning?.(alert) ?? "ignore";
|
|
6038
|
+
}
|
|
6039
|
+
});
|
|
6040
|
+
const doneHandler = () => this.activeSessions.delete(session.sessionId);
|
|
6041
|
+
const errorHandler = () => this.activeSessions.delete(session.sessionId);
|
|
6042
|
+
session.on("session.done", doneHandler);
|
|
6043
|
+
session.on("session.error", errorHandler);
|
|
6044
|
+
const unsubs = [
|
|
6045
|
+
() => session.off("session.done", doneHandler),
|
|
6046
|
+
() => session.off("session.error", errorHandler)
|
|
6047
|
+
];
|
|
6048
|
+
this.activeSessions.set(session.sessionId, { session, unsubs });
|
|
6049
|
+
return session.start();
|
|
6050
|
+
}
|
|
6051
|
+
cancel(sessionId, reason = "Director cancelled") {
|
|
6052
|
+
const entry = this.activeSessions.get(sessionId);
|
|
6053
|
+
if (!entry || entry.session.isCancelled()) return;
|
|
6054
|
+
for (const unsub of entry.unsubs) unsub();
|
|
6055
|
+
entry.session.cancel(reason);
|
|
6056
|
+
for (const [_role, subagentId] of entry.session.getSubagentIds()) {
|
|
6057
|
+
this.deps.coordinator.stop(subagentId).catch((err) => {
|
|
6058
|
+
this.deps.logger?.debug(
|
|
6059
|
+
`stop subagent ${subagentId} failed (may have already completed)`,
|
|
6060
|
+
{
|
|
6061
|
+
subagentId,
|
|
6062
|
+
err: err instanceof Error ? err.message : String(err)
|
|
6063
|
+
}
|
|
6064
|
+
);
|
|
6065
|
+
});
|
|
6066
|
+
}
|
|
6067
|
+
}
|
|
6068
|
+
onAlert(handler) {
|
|
6069
|
+
return this.deps.fleet.filter("collab.warning", (e) => {
|
|
6070
|
+
handler(e.payload);
|
|
6071
|
+
});
|
|
6072
|
+
}
|
|
6073
|
+
activeSessionIds() {
|
|
6074
|
+
return Array.from(this.activeSessions.keys());
|
|
6075
|
+
}
|
|
6076
|
+
};
|
|
6077
|
+
|
|
6078
|
+
// src/coordination/director/director-btw-notes.ts
|
|
6079
|
+
var DirectorBtwNotes = class {
|
|
6080
|
+
notes = [];
|
|
6081
|
+
/** Stash a note (trimmed; empty ignored). Returns the pending count. Cap 20. */
|
|
6082
|
+
add(note) {
|
|
6083
|
+
const trimmed = note.trim();
|
|
6084
|
+
if (!trimmed) return this.notes.length;
|
|
6085
|
+
this.notes = [...this.notes, trimmed].slice(-20);
|
|
6086
|
+
return this.notes.length;
|
|
6087
|
+
}
|
|
6088
|
+
/** Read and clear all pending notes, in FIFO order (empty array when none). */
|
|
6089
|
+
drain() {
|
|
6090
|
+
const notes = this.notes;
|
|
6091
|
+
this.notes = [];
|
|
6092
|
+
return notes;
|
|
6093
|
+
}
|
|
6094
|
+
/** Peek at pending notes without consuming them. */
|
|
6095
|
+
peek() {
|
|
6096
|
+
return [...this.notes];
|
|
6097
|
+
}
|
|
6098
|
+
};
|
|
6099
|
+
|
|
5941
6100
|
// src/utils/instruction-file.ts
|
|
5942
|
-
import { readFileSync as
|
|
6101
|
+
import { readFileSync as readFileSync3, statSync as statSync3 } from "node:fs";
|
|
5943
6102
|
import * as path6 from "node:path";
|
|
5944
6103
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
5945
6104
|
var textCache = /* @__PURE__ */ new Map();
|
|
@@ -5950,7 +6109,7 @@ function readBundledInstructionText(relativePath) {
|
|
|
5950
6109
|
let resolved = "";
|
|
5951
6110
|
for (const root of instructionRootCandidates()) {
|
|
5952
6111
|
try {
|
|
5953
|
-
resolved =
|
|
6112
|
+
resolved = readFileSync3(path6.join(root, relativePath), "utf8").trimEnd();
|
|
5954
6113
|
break;
|
|
5955
6114
|
} catch {
|
|
5956
6115
|
}
|
|
@@ -5977,7 +6136,7 @@ function instructionRootCandidates() {
|
|
|
5977
6136
|
}
|
|
5978
6137
|
function isDirectory2(candidate) {
|
|
5979
6138
|
try {
|
|
5980
|
-
return
|
|
6139
|
+
return statSync3(candidate).isDirectory();
|
|
5981
6140
|
} catch {
|
|
5982
6141
|
return false;
|
|
5983
6142
|
}
|
|
@@ -9485,13 +9644,12 @@ var Director = class _Director {
|
|
|
9485
9644
|
* coordinator from keeping workers alive for tasks that will never arrive.
|
|
9486
9645
|
*/
|
|
9487
9646
|
workCompleteFlag = false;
|
|
9488
|
-
/** Pending /btw notes stashed by the leader agent (see setLeaderBtwNote).
|
|
9489
|
-
|
|
9490
|
-
|
|
9491
|
-
|
|
9492
|
-
*
|
|
9493
|
-
|
|
9494
|
-
_activeCollabSessions = /* @__PURE__ */ new Map();
|
|
9647
|
+
/** Pending /btw notes stashed by the leader agent (see setLeaderBtwNote).
|
|
9648
|
+
* Owned by DirectorBtwNotes (R4); the public btw methods delegate to it. */
|
|
9649
|
+
btwNotes = new DirectorBtwNotes();
|
|
9650
|
+
/** Owns active collab-debug sessions (spawn/cancel/alert/list). Extracted
|
|
9651
|
+
* from Director in R4; the public collab methods below delegate to it. */
|
|
9652
|
+
collab;
|
|
9495
9653
|
/** Prevents large `ask_subagent` answers from bloating the leader's context window. */
|
|
9496
9654
|
largeAnswerStore;
|
|
9497
9655
|
constructor(opts) {
|
|
@@ -9568,92 +9726,103 @@ var Director = class _Director {
|
|
|
9568
9726
|
);
|
|
9569
9727
|
this.coordinator.setFleetBus(this.fleet);
|
|
9570
9728
|
this.fleetManager?.setCoordinator(this.coordinator);
|
|
9571
|
-
this.taskCompletedListener = (payload) =>
|
|
9572
|
-
|
|
9573
|
-
|
|
9574
|
-
|
|
9575
|
-
|
|
9576
|
-
|
|
9577
|
-
|
|
9578
|
-
|
|
9579
|
-
|
|
9580
|
-
|
|
9581
|
-
|
|
9582
|
-
|
|
9583
|
-
|
|
9584
|
-
|
|
9585
|
-
|
|
9586
|
-
|
|
9587
|
-
|
|
9588
|
-
|
|
9589
|
-
|
|
9590
|
-
|
|
9591
|
-
|
|
9592
|
-
|
|
9593
|
-
|
|
9594
|
-
|
|
9595
|
-
|
|
9596
|
-
|
|
9597
|
-
|
|
9598
|
-
|
|
9599
|
-
|
|
9600
|
-
|
|
9601
|
-
|
|
9602
|
-
|
|
9603
|
-
|
|
9604
|
-
|
|
9605
|
-
|
|
9606
|
-
|
|
9607
|
-
|
|
9608
|
-
|
|
9609
|
-
|
|
9610
|
-
|
|
9611
|
-
|
|
9612
|
-
|
|
9613
|
-
|
|
9614
|
-
|
|
9615
|
-
|
|
9616
|
-
|
|
9617
|
-
|
|
9618
|
-
|
|
9619
|
-
|
|
9729
|
+
this.taskCompletedListener = (payload) => this.handleTaskCompleted(payload);
|
|
9730
|
+
this.coordinator.on("task.completed", this.taskCompletedListener);
|
|
9731
|
+
this.wireBudgetPolicy();
|
|
9732
|
+
this.largeAnswerStore = new LargeAnswerStore(2e3);
|
|
9733
|
+
this.collab = new DirectorCollabController({
|
|
9734
|
+
director: this,
|
|
9735
|
+
fleet: this.fleet,
|
|
9736
|
+
coordinator: this.coordinator,
|
|
9737
|
+
logger: this.logger
|
|
9738
|
+
});
|
|
9739
|
+
}
|
|
9740
|
+
handleTaskCompleted(payload) {
|
|
9741
|
+
const r = payload.result;
|
|
9742
|
+
const internalTask = this.internalTaskIds.delete(r.taskId);
|
|
9743
|
+
if (!internalTask) {
|
|
9744
|
+
this.completed.set(r.taskId, r);
|
|
9745
|
+
if (this.completed.size > _Director.MAX_COMPLETED) {
|
|
9746
|
+
const toDelete = this.completed.size - _Director.MAX_COMPLETED;
|
|
9747
|
+
const keys = [...this.completed.keys()].slice(0, toDelete);
|
|
9748
|
+
for (const k of keys) this.completed.delete(k);
|
|
9749
|
+
}
|
|
9750
|
+
}
|
|
9751
|
+
const waiter = this.taskWaiters.get(r.taskId);
|
|
9752
|
+
if (waiter) {
|
|
9753
|
+
waiter.resolve(r);
|
|
9754
|
+
this.taskWaiters.delete(r.taskId);
|
|
9755
|
+
}
|
|
9756
|
+
let anyConsumed = false;
|
|
9757
|
+
for (const aw of [...this.anyWaiters]) {
|
|
9758
|
+
if (!aw.ids.has(r.taskId)) continue;
|
|
9759
|
+
if (aw.timer) clearTimeout(aw.timer);
|
|
9760
|
+
this.anyWaiters.delete(aw);
|
|
9761
|
+
aw.resolve(r);
|
|
9762
|
+
anyConsumed = true;
|
|
9763
|
+
}
|
|
9764
|
+
const consumedInBand = !!waiter || anyConsumed;
|
|
9765
|
+
if (internalTask) return;
|
|
9766
|
+
const title = this.taskDescriptions.get(r.taskId) ?? payload.task.description ?? r.taskId;
|
|
9767
|
+
if (!consumedInBand && this.taskResultNotifier) {
|
|
9768
|
+
const resultText = typeof r.result === "string" ? r.result : r.result !== void 0 ? safeStringify(r.result) : void 0;
|
|
9769
|
+
try {
|
|
9770
|
+
void Promise.resolve(
|
|
9771
|
+
this.taskResultNotifier({
|
|
9772
|
+
taskId: r.taskId,
|
|
9773
|
+
title,
|
|
9774
|
+
status: r.status,
|
|
9775
|
+
subagentId: r.subagentId,
|
|
9776
|
+
subagentName: this.manifestEntries.get(r.subagentId)?.name,
|
|
9777
|
+
resultText,
|
|
9778
|
+
errorText: r.error ? `${r.error.kind}: ${r.error.message}` : void 0,
|
|
9779
|
+
partialText: r.partial?.text,
|
|
9780
|
+
report: r.report,
|
|
9781
|
+
iterations: r.iterations,
|
|
9782
|
+
toolCalls: r.toolCalls,
|
|
9783
|
+
durationMs: r.durationMs
|
|
9784
|
+
})
|
|
9785
|
+
).catch(() => {
|
|
9786
|
+
});
|
|
9787
|
+
} catch {
|
|
9620
9788
|
}
|
|
9621
|
-
|
|
9622
|
-
|
|
9623
|
-
|
|
9624
|
-
|
|
9625
|
-
|
|
9626
|
-
|
|
9627
|
-
|
|
9628
|
-
|
|
9629
|
-
|
|
9630
|
-
|
|
9631
|
-
|
|
9632
|
-
|
|
9633
|
-
|
|
9634
|
-
|
|
9635
|
-
|
|
9636
|
-
|
|
9637
|
-
|
|
9638
|
-
|
|
9639
|
-
|
|
9640
|
-
|
|
9641
|
-
|
|
9642
|
-
|
|
9643
|
-
|
|
9644
|
-
|
|
9645
|
-
);
|
|
9646
|
-
if (this.fleetManager) {
|
|
9647
|
-
void this.fleetManager.flushManifest();
|
|
9648
|
-
} else {
|
|
9649
|
-
this.scheduleManifest();
|
|
9789
|
+
}
|
|
9790
|
+
const failed = r.status !== "success";
|
|
9791
|
+
const errorString = r.error ? `${r.error.kind}: ${r.error.message}` : void 0;
|
|
9792
|
+
this.stateCheckpoint?.recordTaskStatus(r.taskId, {
|
|
9793
|
+
status: failed ? r.status : "completed",
|
|
9794
|
+
completedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9795
|
+
iterations: r.iterations,
|
|
9796
|
+
toolCalls: r.toolCalls,
|
|
9797
|
+
durationMs: r.durationMs,
|
|
9798
|
+
error: errorString
|
|
9799
|
+
});
|
|
9800
|
+
this.stateCheckpoint?.setUsage(this.usage.snapshot());
|
|
9801
|
+
void this.appendSessionEvent(
|
|
9802
|
+
failed ? {
|
|
9803
|
+
type: "task_failed",
|
|
9804
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9805
|
+
taskId: r.taskId,
|
|
9806
|
+
title,
|
|
9807
|
+
error: errorString ?? r.status
|
|
9808
|
+
} : {
|
|
9809
|
+
type: "task_completed",
|
|
9810
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9811
|
+
taskId: r.taskId,
|
|
9812
|
+
title
|
|
9650
9813
|
}
|
|
9651
|
-
|
|
9652
|
-
|
|
9653
|
-
|
|
9654
|
-
|
|
9655
|
-
|
|
9656
|
-
|
|
9814
|
+
);
|
|
9815
|
+
if (this.fleetManager) {
|
|
9816
|
+
void this.fleetManager.flushManifest();
|
|
9817
|
+
} else {
|
|
9818
|
+
this.scheduleManifest();
|
|
9819
|
+
}
|
|
9820
|
+
this.armSubagentIdleRetirement(
|
|
9821
|
+
r.subagentId,
|
|
9822
|
+
this.retireSubagentOnTaskComplete ? 0 : this.subagentIdleTimeoutMs
|
|
9823
|
+
);
|
|
9824
|
+
}
|
|
9825
|
+
wireBudgetPolicy() {
|
|
9657
9826
|
const extendCounts = /* @__PURE__ */ new Map();
|
|
9658
9827
|
const progressBySubagent = /* @__PURE__ */ new Map();
|
|
9659
9828
|
const lastTimeoutProgress = /* @__PURE__ */ new Map();
|
|
@@ -9773,7 +9942,6 @@ var Director = class _Director {
|
|
|
9773
9942
|
}
|
|
9774
9943
|
grantExtension();
|
|
9775
9944
|
});
|
|
9776
|
-
this.largeAnswerStore = new LargeAnswerStore(2e3);
|
|
9777
9945
|
}
|
|
9778
9946
|
/**
|
|
9779
9947
|
* Record a granted budget extension and broadcast it on the FleetBus so
|
|
@@ -9831,10 +9999,7 @@ var Director = class _Director {
|
|
|
9831
9999
|
* programmatically without going through the slash-command path.
|
|
9832
10000
|
*/
|
|
9833
10001
|
setLeaderBtwNote(note) {
|
|
9834
|
-
|
|
9835
|
-
if (!trimmed) return this._leaderBtwNotes.length;
|
|
9836
|
-
this._leaderBtwNotes = [...this._leaderBtwNotes, trimmed].slice(-20);
|
|
9837
|
-
return this._leaderBtwNotes.length;
|
|
10002
|
+
return this.btwNotes.add(note);
|
|
9838
10003
|
}
|
|
9839
10004
|
/**
|
|
9840
10005
|
* Read and clear all pending /btw notes the leader has stashed.
|
|
@@ -9845,16 +10010,14 @@ var Director = class _Director {
|
|
|
9845
10010
|
* to cancel the collab session or let it continue.
|
|
9846
10011
|
*/
|
|
9847
10012
|
getLeaderBtwNotes() {
|
|
9848
|
-
|
|
9849
|
-
this._leaderBtwNotes = [];
|
|
9850
|
-
return notes;
|
|
10013
|
+
return this.btwNotes.drain();
|
|
9851
10014
|
}
|
|
9852
10015
|
/**
|
|
9853
10016
|
* Peek at pending /btw notes without consuming them.
|
|
9854
10017
|
* Useful for UI to show "N pending notes" without clearing them.
|
|
9855
10018
|
*/
|
|
9856
10019
|
peekLeaderBtwNotes() {
|
|
9857
|
-
return
|
|
10020
|
+
return this.btwNotes.peek();
|
|
9858
10021
|
}
|
|
9859
10022
|
/**
|
|
9860
10023
|
* Drain (read + clear) all /btw notes in one call.
|
|
@@ -9874,18 +10037,7 @@ var Director = class _Director {
|
|
|
9874
10037
|
* The CollabDebugReport will reflect 'cancelled' disposition when awaited.
|
|
9875
10038
|
*/
|
|
9876
10039
|
cancelCollabSession(sessionId, reason = "Director cancelled") {
|
|
9877
|
-
|
|
9878
|
-
if (!entry || entry.session.isCancelled()) return;
|
|
9879
|
-
for (const unsub of entry.unsubs) unsub();
|
|
9880
|
-
entry.session.cancel(reason);
|
|
9881
|
-
for (const [_role, subagentId] of entry.session.getSubagentIds()) {
|
|
9882
|
-
this.coordinator.stop(subagentId).catch((err) => {
|
|
9883
|
-
this.logger?.debug(`stop subagent ${subagentId} failed (may have already completed)`, {
|
|
9884
|
-
subagentId,
|
|
9885
|
-
err: err instanceof Error ? err.message : String(err)
|
|
9886
|
-
});
|
|
9887
|
-
});
|
|
9888
|
-
}
|
|
10040
|
+
this.collab.cancel(sessionId, reason);
|
|
9889
10041
|
}
|
|
9890
10042
|
/**
|
|
9891
10043
|
* Subscribe a callback to be notified whenever a collab session raises
|
|
@@ -9894,9 +10046,7 @@ var Director = class _Director {
|
|
|
9894
10046
|
* Returns an unsubscribe function.
|
|
9895
10047
|
*/
|
|
9896
10048
|
onCollabAlert(handler) {
|
|
9897
|
-
return this.
|
|
9898
|
-
handler(e.payload);
|
|
9899
|
-
});
|
|
10049
|
+
return this.collab.onAlert(handler);
|
|
9900
10050
|
}
|
|
9901
10051
|
/**
|
|
9902
10052
|
* Returns all active (not yet settled) collab session ids.
|
|
@@ -9904,7 +10054,7 @@ var Director = class _Director {
|
|
|
9904
10054
|
* the host to know what can be cancelled.
|
|
9905
10055
|
*/
|
|
9906
10056
|
activeCollabSessions() {
|
|
9907
|
-
return
|
|
10057
|
+
return this.collab.activeSessionIds();
|
|
9908
10058
|
}
|
|
9909
10059
|
/** Best-effort session-writer append. Swallows failures — the director
|
|
9910
10060
|
* must not break a fleet run because the session JSONL handle closed. */
|
|
@@ -9970,6 +10120,77 @@ var Director = class _Director {
|
|
|
9970
10120
|
);
|
|
9971
10121
|
}
|
|
9972
10122
|
const config = { ...callerConfig };
|
|
10123
|
+
this.resolveSpawnModel(config);
|
|
10124
|
+
this.enforceSpawnCaps(config);
|
|
10125
|
+
let result;
|
|
10126
|
+
this.assignSpawnNickname(config);
|
|
10127
|
+
this.applySpawnLineage(config);
|
|
10128
|
+
result = await this.coordinator.spawn(config);
|
|
10129
|
+
if (this.fleetManager) {
|
|
10130
|
+
this.fleetManager.recordSpawn(result.subagentId, config, priceLookup);
|
|
10131
|
+
} else {
|
|
10132
|
+
this.spawnCount += 1;
|
|
10133
|
+
this.subagentMeta.set(result.subagentId, {
|
|
10134
|
+
provider: config.provider,
|
|
10135
|
+
model: config.model
|
|
10136
|
+
});
|
|
10137
|
+
if (priceLookup && config.provider && config.model) {
|
|
10138
|
+
this.priceLookups.set(`${config.provider}/${config.model}`, priceLookup);
|
|
10139
|
+
}
|
|
10140
|
+
}
|
|
10141
|
+
const subagentBridge = new InMemoryAgentBridge(
|
|
10142
|
+
{ agentId: result.subagentId, coordinatorId: this.id },
|
|
10143
|
+
this.transport
|
|
10144
|
+
);
|
|
10145
|
+
this.coordinator.setSubagentBridge(result.subagentId, subagentBridge);
|
|
10146
|
+
this.subagentBridges.set(result.subagentId, subagentBridge);
|
|
10147
|
+
this.fleet.emit({
|
|
10148
|
+
subagentId: result.subagentId,
|
|
10149
|
+
ts: Date.now(),
|
|
10150
|
+
type: "subagent.spawned",
|
|
10151
|
+
payload: {
|
|
10152
|
+
subagentId: result.subagentId,
|
|
10153
|
+
taskId: "",
|
|
10154
|
+
// taskId will be set when assign() is called
|
|
10155
|
+
name: config.name,
|
|
10156
|
+
role: config.role,
|
|
10157
|
+
provider: config.provider,
|
|
10158
|
+
model: config.model
|
|
10159
|
+
}
|
|
10160
|
+
});
|
|
10161
|
+
if (!this.fleetManager) {
|
|
10162
|
+
this.manifestEntries.set(result.subagentId, {
|
|
10163
|
+
subagentId: result.subagentId,
|
|
10164
|
+
name: config.name,
|
|
10165
|
+
role: config.role,
|
|
10166
|
+
provider: config.provider,
|
|
10167
|
+
model: config.model,
|
|
10168
|
+
taskIds: []
|
|
10169
|
+
});
|
|
10170
|
+
const spawnedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
10171
|
+
this.stateCheckpoint?.recordSpawn(
|
|
10172
|
+
{
|
|
10173
|
+
id: result.subagentId,
|
|
10174
|
+
name: config.name,
|
|
10175
|
+
role: config.role,
|
|
10176
|
+
provider: config.provider,
|
|
10177
|
+
model: config.model,
|
|
10178
|
+
spawnedAt
|
|
10179
|
+
},
|
|
10180
|
+
this.spawnCount
|
|
10181
|
+
);
|
|
10182
|
+
void this.appendSessionEvent({
|
|
10183
|
+
type: "agent_spawned",
|
|
10184
|
+
ts: spawnedAt,
|
|
10185
|
+
agentId: result.subagentId,
|
|
10186
|
+
role: config.role ?? config.name
|
|
10187
|
+
});
|
|
10188
|
+
this.scheduleManifest();
|
|
10189
|
+
}
|
|
10190
|
+
this.armSubagentIdleRetirement(result.subagentId, this.subagentIdleTimeoutMs);
|
|
10191
|
+
return result.subagentId;
|
|
10192
|
+
}
|
|
10193
|
+
resolveSpawnModel(config) {
|
|
9973
10194
|
if (!config.model && this.modelMatrix) {
|
|
9974
10195
|
const matrix = typeof this.modelMatrix === "function" ? this.modelMatrix() : this.modelMatrix;
|
|
9975
10196
|
const resolution = resolveModelMatrixResolution(matrix, config.role);
|
|
@@ -9980,6 +10201,8 @@ var Director = class _Director {
|
|
|
9980
10201
|
if (entry.modelRuntime) config.modelRuntime = entry.modelRuntime;
|
|
9981
10202
|
}
|
|
9982
10203
|
}
|
|
10204
|
+
}
|
|
10205
|
+
enforceSpawnCaps(config) {
|
|
9983
10206
|
if (this.fleetManager) {
|
|
9984
10207
|
const rejection = this.fleetManager.canSpawn(config);
|
|
9985
10208
|
if (rejection) {
|
|
@@ -10022,7 +10245,8 @@ var Director = class _Director {
|
|
|
10022
10245
|
}
|
|
10023
10246
|
}
|
|
10024
10247
|
}
|
|
10025
|
-
|
|
10248
|
+
}
|
|
10249
|
+
assignSpawnNickname(config) {
|
|
10026
10250
|
const needsNickname = config.name === config.role || !config.name || config.name === "subagent" || config.name === "adhoc";
|
|
10027
10251
|
if (needsNickname) {
|
|
10028
10252
|
const role = config.role ?? "subagent";
|
|
@@ -10034,6 +10258,8 @@ var Director = class _Director {
|
|
|
10034
10258
|
this._usedNicknames.add(key);
|
|
10035
10259
|
}
|
|
10036
10260
|
}
|
|
10261
|
+
}
|
|
10262
|
+
applySpawnLineage(config) {
|
|
10037
10263
|
const budget = this.fleetManager ? this.fleetManager.budgetSnapshot() : (() => {
|
|
10038
10264
|
const total = this.usage.snapshot().total;
|
|
10039
10265
|
const usedTokens = (total?.input ?? 0) + (total?.output ?? 0);
|
|
@@ -10062,70 +10288,6 @@ var Director = class _Director {
|
|
|
10062
10288
|
...Number.isFinite(budget.remainingCostUsd) ? { remainingCostUsd: budget.remainingCostUsd } : {}
|
|
10063
10289
|
}
|
|
10064
10290
|
};
|
|
10065
|
-
result = await this.coordinator.spawn(config);
|
|
10066
|
-
if (this.fleetManager) {
|
|
10067
|
-
this.fleetManager.recordSpawn(result.subagentId, config, priceLookup);
|
|
10068
|
-
} else {
|
|
10069
|
-
this.spawnCount += 1;
|
|
10070
|
-
this.subagentMeta.set(result.subagentId, {
|
|
10071
|
-
provider: config.provider,
|
|
10072
|
-
model: config.model
|
|
10073
|
-
});
|
|
10074
|
-
if (priceLookup && config.provider && config.model) {
|
|
10075
|
-
this.priceLookups.set(`${config.provider}/${config.model}`, priceLookup);
|
|
10076
|
-
}
|
|
10077
|
-
}
|
|
10078
|
-
const subagentBridge = new InMemoryAgentBridge(
|
|
10079
|
-
{ agentId: result.subagentId, coordinatorId: this.id },
|
|
10080
|
-
this.transport
|
|
10081
|
-
);
|
|
10082
|
-
this.coordinator.setSubagentBridge(result.subagentId, subagentBridge);
|
|
10083
|
-
this.subagentBridges.set(result.subagentId, subagentBridge);
|
|
10084
|
-
this.fleet.emit({
|
|
10085
|
-
subagentId: result.subagentId,
|
|
10086
|
-
ts: Date.now(),
|
|
10087
|
-
type: "subagent.spawned",
|
|
10088
|
-
payload: {
|
|
10089
|
-
subagentId: result.subagentId,
|
|
10090
|
-
taskId: "",
|
|
10091
|
-
// taskId will be set when assign() is called
|
|
10092
|
-
name: config.name,
|
|
10093
|
-
role: config.role,
|
|
10094
|
-
provider: config.provider,
|
|
10095
|
-
model: config.model
|
|
10096
|
-
}
|
|
10097
|
-
});
|
|
10098
|
-
if (!this.fleetManager) {
|
|
10099
|
-
this.manifestEntries.set(result.subagentId, {
|
|
10100
|
-
subagentId: result.subagentId,
|
|
10101
|
-
name: config.name,
|
|
10102
|
-
role: config.role,
|
|
10103
|
-
provider: config.provider,
|
|
10104
|
-
model: config.model,
|
|
10105
|
-
taskIds: []
|
|
10106
|
-
});
|
|
10107
|
-
const spawnedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
10108
|
-
this.stateCheckpoint?.recordSpawn(
|
|
10109
|
-
{
|
|
10110
|
-
id: result.subagentId,
|
|
10111
|
-
name: config.name,
|
|
10112
|
-
role: config.role,
|
|
10113
|
-
provider: config.provider,
|
|
10114
|
-
model: config.model,
|
|
10115
|
-
spawnedAt
|
|
10116
|
-
},
|
|
10117
|
-
this.spawnCount
|
|
10118
|
-
);
|
|
10119
|
-
void this.appendSessionEvent({
|
|
10120
|
-
type: "agent_spawned",
|
|
10121
|
-
ts: spawnedAt,
|
|
10122
|
-
agentId: result.subagentId,
|
|
10123
|
-
role: config.role ?? config.name
|
|
10124
|
-
});
|
|
10125
|
-
this.scheduleManifest();
|
|
10126
|
-
}
|
|
10127
|
-
this.armSubagentIdleRetirement(result.subagentId, this.subagentIdleTimeoutMs);
|
|
10128
|
-
return result.subagentId;
|
|
10129
10291
|
}
|
|
10130
10292
|
/**
|
|
10131
10293
|
* Synchronously ask a subagent something via the bridge. Sends a
|
|
@@ -10250,6 +10412,23 @@ var Director = class _Director {
|
|
|
10250
10412
|
await atomicWrite(this.manifestPath, JSON.stringify(manifest, null, 2), { mode: 384 });
|
|
10251
10413
|
return this.manifestPath;
|
|
10252
10414
|
}
|
|
10415
|
+
/**
|
|
10416
|
+
* Cancel the pending manifest debounce timer and drain any in-flight
|
|
10417
|
+
* manifest write so that, once this resolves, no Director-owned manifest
|
|
10418
|
+
* write is armed or running.
|
|
10419
|
+
*
|
|
10420
|
+
* Unlike `shutdown()`, this does NOT tear down the coordinator, bridges,
|
|
10421
|
+
* or waiters — it is the lightweight quiesce that `MultiAgentHost.stopAll()`
|
|
10422
|
+
* needs. `stopAll()` flushes the FleetManager's manifest but never touched
|
|
10423
|
+
* the Director's own writer, leaving its armed debounce timer to fire ~2s
|
|
10424
|
+
* later. Under CPU starvation that late, un-awaited `atomicWrite` (temp
|
|
10425
|
+
* sibling + rename) races a caller that deletes the manifest directory,
|
|
10426
|
+
* producing an `ENOTEMPTY` on the parent `rmdir` (Windows especially).
|
|
10427
|
+
*/
|
|
10428
|
+
async quiesceManifest() {
|
|
10429
|
+
this.clearManifestTimer();
|
|
10430
|
+
await this.manifestWriteChain.catch(() => void 0);
|
|
10431
|
+
}
|
|
10253
10432
|
/**
|
|
10254
10433
|
* Tear down the director: stop every subagent, close every bridge
|
|
10255
10434
|
* endpoint, and (when configured) write the final manifest. Idempotent
|
|
@@ -10261,8 +10440,17 @@ var Director = class _Director {
|
|
|
10261
10440
|
this.coordinator.off("task.completed", this.taskCompletedListener);
|
|
10262
10441
|
this.taskCompletedListener = null;
|
|
10263
10442
|
}
|
|
10264
|
-
for (const aw of this.anyWaiters) {
|
|
10443
|
+
for (const aw of [...this.anyWaiters]) {
|
|
10265
10444
|
if (aw.timer) clearTimeout(aw.timer);
|
|
10445
|
+
const firstId = [...aw.ids][0];
|
|
10446
|
+
aw.resolve({
|
|
10447
|
+
taskId: firstId ?? "unknown",
|
|
10448
|
+
subagentId: "director",
|
|
10449
|
+
status: "stopped",
|
|
10450
|
+
iterations: 0,
|
|
10451
|
+
toolCalls: 0,
|
|
10452
|
+
durationMs: 0
|
|
10453
|
+
});
|
|
10266
10454
|
}
|
|
10267
10455
|
this.anyWaiters.clear();
|
|
10268
10456
|
if (this.toolExecFilter) {
|
|
@@ -10276,6 +10464,17 @@ var Director = class _Director {
|
|
|
10276
10464
|
for (const timer of this.subagentIdleTimers.values()) clearTimeout(timer);
|
|
10277
10465
|
this.subagentIdleTimers.clear();
|
|
10278
10466
|
await this.coordinator.stopAll();
|
|
10467
|
+
for (const [id, waiter] of this.taskWaiters) {
|
|
10468
|
+
waiter.resolve({
|
|
10469
|
+
taskId: id,
|
|
10470
|
+
subagentId: "director",
|
|
10471
|
+
status: "stopped",
|
|
10472
|
+
iterations: 0,
|
|
10473
|
+
toolCalls: 0,
|
|
10474
|
+
durationMs: 0
|
|
10475
|
+
});
|
|
10476
|
+
}
|
|
10477
|
+
this.taskWaiters.clear();
|
|
10279
10478
|
for (const b of this.subagentBridges.values()) {
|
|
10280
10479
|
await b.stop().catch((err) => this.logShutdownError("subagent_bridge_stop", err));
|
|
10281
10480
|
}
|
|
@@ -10283,6 +10482,7 @@ var Director = class _Director {
|
|
|
10283
10482
|
await this.bridge.stop().catch((err) => this.logShutdownError("director_bridge_stop", err));
|
|
10284
10483
|
if (this.fleetManager) {
|
|
10285
10484
|
await this.fleetManager.flushManifest().catch((err) => this.logShutdownError("fleet_manifest_flush", err));
|
|
10485
|
+
await this.manifestWriteChain.catch(() => void 0);
|
|
10286
10486
|
} else if (this.manifestPath) {
|
|
10287
10487
|
await this.writeManifest().catch((err) => this.logShutdownError("manifest_write", err));
|
|
10288
10488
|
}
|
|
@@ -10748,22 +10948,7 @@ var Director = class _Director {
|
|
|
10748
10948
|
* three agents complete or the session times out.
|
|
10749
10949
|
*/
|
|
10750
10950
|
async spawnCollab(options) {
|
|
10751
|
-
|
|
10752
|
-
...options,
|
|
10753
|
-
onBudgetWarning: (alert) => {
|
|
10754
|
-
return options.onBudgetWarning?.(alert) ?? "ignore";
|
|
10755
|
-
}
|
|
10756
|
-
});
|
|
10757
|
-
const doneHandler = () => this._activeCollabSessions.delete(session.sessionId);
|
|
10758
|
-
const errorHandler = () => this._activeCollabSessions.delete(session.sessionId);
|
|
10759
|
-
session.on("session.done", doneHandler);
|
|
10760
|
-
session.on("session.error", errorHandler);
|
|
10761
|
-
const unsubs = [
|
|
10762
|
-
() => session.off("session.done", doneHandler),
|
|
10763
|
-
() => session.off("session.error", errorHandler)
|
|
10764
|
-
];
|
|
10765
|
-
this._activeCollabSessions.set(session.sessionId, { session, unsubs });
|
|
10766
|
-
return session.start();
|
|
10951
|
+
return this.collab.spawn(options);
|
|
10767
10952
|
}
|
|
10768
10953
|
/**
|
|
10769
10954
|
* Resume from a prior checkpoint snapshot (loaded via
|
|
@@ -13446,6 +13631,16 @@ var FleetManager = class {
|
|
|
13446
13631
|
manifestTimer = null;
|
|
13447
13632
|
manifestWriteChain = Promise.resolve();
|
|
13448
13633
|
disposed = false;
|
|
13634
|
+
/**
|
|
13635
|
+
* Teardown freeze. Set by `closeManifest()` after the final manifest is
|
|
13636
|
+
* flushed; makes any subsequent `scheduleManifest`/`writeManifest`/
|
|
13637
|
+
* `flushManifest` a no-op so a late fire-and-forget write (e.g. the
|
|
13638
|
+
* task-completion flush the Director issues via `void flushManifest()`)
|
|
13639
|
+
* cannot land an `atomicWrite` in the manifest directory while a caller is
|
|
13640
|
+
* deleting it. Cleared by `recordSpawn` so a stop-then-spawn-again host
|
|
13641
|
+
* resumes writing.
|
|
13642
|
+
*/
|
|
13643
|
+
closing = false;
|
|
13449
13644
|
manifestDebounceMs;
|
|
13450
13645
|
/** Fleet-wide cost cap. Infinity = no cap. Distinct from SubagentBudget limits,
|
|
13451
13646
|
* which track per-subagent spend — this field caps the entire fleet total. */
|
|
@@ -13627,6 +13822,7 @@ var FleetManager = class {
|
|
|
13627
13822
|
* @param priceLookup Optional per-subagent pricing data
|
|
13628
13823
|
*/
|
|
13629
13824
|
recordSpawn(subagentId, config, priceLookup) {
|
|
13825
|
+
this.closing = false;
|
|
13630
13826
|
this.spawnCount += 1;
|
|
13631
13827
|
this.subagentMeta.set(subagentId, {
|
|
13632
13828
|
provider: config.provider,
|
|
@@ -13664,6 +13860,7 @@ var FleetManager = class {
|
|
|
13664
13860
|
}
|
|
13665
13861
|
async writeManifest() {
|
|
13666
13862
|
if (!this.manifestPath) return null;
|
|
13863
|
+
if (this.closing) return this.manifestPath;
|
|
13667
13864
|
this.clearManifestTimer();
|
|
13668
13865
|
const write = this.manifestWriteChain.catch(() => void 0).then(() => this.writeManifestNow());
|
|
13669
13866
|
this.manifestWriteChain = write.catch(() => void 0);
|
|
@@ -13712,7 +13909,7 @@ var FleetManager = class {
|
|
|
13712
13909
|
* When `manifestDebounceMs` is 0, writes are synchronous (no debounce).
|
|
13713
13910
|
*/
|
|
13714
13911
|
scheduleManifest() {
|
|
13715
|
-
if (this.disposed) return;
|
|
13912
|
+
if (this.disposed || this.closing) return;
|
|
13716
13913
|
if (!this.manifestPath) return;
|
|
13717
13914
|
if (this.manifestDebounceMs === 0) {
|
|
13718
13915
|
void this.writeManifest().catch((err) => {
|
|
@@ -13743,6 +13940,22 @@ var FleetManager = class {
|
|
|
13743
13940
|
process.emitWarning(`FleetManager manifest write failed: ${detail}`, "FleetManagerWarning");
|
|
13744
13941
|
});
|
|
13745
13942
|
}
|
|
13943
|
+
/**
|
|
13944
|
+
* Teardown flush for callers that will delete the manifest directory next
|
|
13945
|
+
* (host `stopAll`/`dispose`). Writes the final manifest, then freezes the
|
|
13946
|
+
* writer so no later fire-and-forget write can start, and drains anything
|
|
13947
|
+
* still in flight. After this resolves it is safe to `rm -rf` the manifest
|
|
13948
|
+
* directory: no `atomicWrite` (temp sibling + rename) can race the removal.
|
|
13949
|
+
*
|
|
13950
|
+
* The freeze is lifted by the next `recordSpawn`, so a stop-then-spawn-again
|
|
13951
|
+
* host keeps persisting manifests.
|
|
13952
|
+
*/
|
|
13953
|
+
async closeManifest() {
|
|
13954
|
+
if (!this.manifestPath) return;
|
|
13955
|
+
await this.flushManifest();
|
|
13956
|
+
this.closing = true;
|
|
13957
|
+
await this.manifestWriteChain.catch(() => void 0);
|
|
13958
|
+
}
|
|
13746
13959
|
clearManifestTimer() {
|
|
13747
13960
|
if (!this.manifestTimer) return;
|
|
13748
13961
|
clearTimeout(this.manifestTimer);
|
|
@@ -13969,6 +14182,42 @@ function parseMailboxMessage(value) {
|
|
|
13969
14182
|
function parseMailboxMessageLine(line) {
|
|
13970
14183
|
return parseMailboxMessage(JSON.parse(line));
|
|
13971
14184
|
}
|
|
14185
|
+
function isAckRecord(value) {
|
|
14186
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) && value["__ack"] === true;
|
|
14187
|
+
}
|
|
14188
|
+
function parseMailboxLine(line) {
|
|
14189
|
+
try {
|
|
14190
|
+
const parsed = JSON.parse(line);
|
|
14191
|
+
if (isAckRecord(parsed)) return parsed;
|
|
14192
|
+
return parseMailboxMessage(parsed);
|
|
14193
|
+
} catch {
|
|
14194
|
+
return null;
|
|
14195
|
+
}
|
|
14196
|
+
}
|
|
14197
|
+
function applyAckToMessage(msg, ack) {
|
|
14198
|
+
if (ack.read && !(ack.readerId in msg.readBy)) {
|
|
14199
|
+
msg.readBy[ack.readerId] = ack.timestamp;
|
|
14200
|
+
}
|
|
14201
|
+
if (ack.completed && !msg.completed) {
|
|
14202
|
+
msg.completed = true;
|
|
14203
|
+
msg.completedBy = ack.completedBy ?? ack.readerId;
|
|
14204
|
+
msg.completedAt = ack.timestamp;
|
|
14205
|
+
}
|
|
14206
|
+
if (ack.outcome !== void 0 && msg.outcome !== ack.outcome) {
|
|
14207
|
+
msg.outcome = ack.outcome;
|
|
14208
|
+
}
|
|
14209
|
+
if (ack.deleted === true) {
|
|
14210
|
+
msg.deletedAt = ack.timestamp;
|
|
14211
|
+
msg.deletedBy = ack.deletedBy ?? ack.readerId;
|
|
14212
|
+
}
|
|
14213
|
+
if (ack.deleted === false) {
|
|
14214
|
+
delete msg.deletedAt;
|
|
14215
|
+
delete msg.deletedBy;
|
|
14216
|
+
}
|
|
14217
|
+
}
|
|
14218
|
+
function serializeAckRecord(ack) {
|
|
14219
|
+
return JSON.stringify(ack) + "\n";
|
|
14220
|
+
}
|
|
13972
14221
|
|
|
13973
14222
|
// src/coordination/mailbox-types.ts
|
|
13974
14223
|
function normalizeRecipient(to) {
|
|
@@ -14040,7 +14289,7 @@ var GlobalMailbox = class {
|
|
|
14040
14289
|
* appended message). The chain runs each read to completion before
|
|
14041
14290
|
* the next starts, in issue order — readers are read-only and never
|
|
14042
14291
|
* conflict with each other on content, only on the cache mutation
|
|
14043
|
-
* that follows the read. Pattern mirrors
|
|
14292
|
+
* that follows the read. Pattern mirrors a serialized mutation chain.
|
|
14044
14293
|
*/
|
|
14045
14294
|
_readChain = Promise.resolve([]);
|
|
14046
14295
|
/**
|
|
@@ -14051,7 +14300,7 @@ var GlobalMailbox = class {
|
|
|
14051
14300
|
*
|
|
14052
14301
|
* The index is rebuilt whenever the cache array is replaced
|
|
14053
14302
|
* (`_setMessageCache`), extended when new messages are pushed
|
|
14054
|
-
* (`_pushToCache
|
|
14303
|
+
* (`_pushToCache`), and cleared on `close()`.
|
|
14055
14304
|
* It is `null` when the cache itself is null (> MESSAGE_CACHE_MAX_ENTRIES
|
|
14056
14305
|
* or never populated).
|
|
14057
14306
|
*
|
|
@@ -14190,42 +14439,100 @@ var GlobalMailbox = class {
|
|
|
14190
14439
|
const updated = await this.ackMany({ acks: [input] });
|
|
14191
14440
|
return updated.length > 0 ? updated[0] : null;
|
|
14192
14441
|
}
|
|
14442
|
+
/** Diagnostic counters exposed for test/tool introspection. */
|
|
14443
|
+
diag = {
|
|
14444
|
+
ackManyCacheDesync: 0,
|
|
14445
|
+
ackManyPreLockMtime: 0,
|
|
14446
|
+
ackManyPostLockMtime: 0,
|
|
14447
|
+
ackManyPreLockSize: 0,
|
|
14448
|
+
ackManyPostLockSize: 0,
|
|
14449
|
+
ackManyMessageCount: 0,
|
|
14450
|
+
ackManyAckCount: 0,
|
|
14451
|
+
cacheHitCount: 0,
|
|
14452
|
+
cacheMissCount: 0,
|
|
14453
|
+
cacheDesyncNoOp: 0,
|
|
14454
|
+
applyAckTargetMissing: 0,
|
|
14455
|
+
setMessageCacheCount: 0,
|
|
14456
|
+
pushToCacheCount: 0
|
|
14457
|
+
};
|
|
14193
14458
|
async ackMany(input) {
|
|
14194
14459
|
if (input.acks.length === 0) return [];
|
|
14460
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
14461
|
+
const ackRecords = [];
|
|
14195
14462
|
const updated = [];
|
|
14463
|
+
const targetIds = /* @__PURE__ */ new Set();
|
|
14196
14464
|
const byId = /* @__PURE__ */ new Map();
|
|
14197
14465
|
for (const a of input.acks) {
|
|
14198
14466
|
byId.set(a.messageId, a);
|
|
14467
|
+
targetIds.add(a.messageId);
|
|
14199
14468
|
}
|
|
14200
|
-
|
|
14201
|
-
|
|
14202
|
-
|
|
14203
|
-
|
|
14204
|
-
|
|
14205
|
-
|
|
14206
|
-
|
|
14207
|
-
|
|
14208
|
-
|
|
14209
|
-
|
|
14210
|
-
|
|
14211
|
-
|
|
14212
|
-
|
|
14213
|
-
|
|
14214
|
-
msg.completedBy = a.readerId;
|
|
14215
|
-
msg.completedAt = now;
|
|
14216
|
-
changed = true;
|
|
14217
|
-
}
|
|
14218
|
-
if (a.outcome !== void 0 && msg.outcome !== a.outcome) {
|
|
14219
|
-
msg.outcome = a.outcome;
|
|
14220
|
-
changed = true;
|
|
14221
|
-
}
|
|
14469
|
+
const preLockMtime = this._messageCacheMtime;
|
|
14470
|
+
const preLockSize = this._messageCacheSize;
|
|
14471
|
+
this.diag.ackManyPreLockMtime = preLockMtime;
|
|
14472
|
+
this.diag.ackManyPreLockSize = preLockSize;
|
|
14473
|
+
const all = await this._readMessagesCached();
|
|
14474
|
+
for (const msg of all) {
|
|
14475
|
+
const a = byId.get(msg.id);
|
|
14476
|
+
if (!a) continue;
|
|
14477
|
+
const needsRead = a.read !== false && !(a.readerId in msg.readBy);
|
|
14478
|
+
const needsComplete = a.completed && !msg.completed;
|
|
14479
|
+
const needsOutcome = a.outcome !== void 0 && msg.outcome !== a.outcome;
|
|
14480
|
+
if (!needsRead && !needsComplete && !needsOutcome) {
|
|
14481
|
+
updated.push({ ...msg, readBy: { ...msg.readBy } });
|
|
14482
|
+
continue;
|
|
14222
14483
|
}
|
|
14223
|
-
|
|
14224
|
-
|
|
14225
|
-
|
|
14484
|
+
const copy = { ...msg, readBy: { ...msg.readBy } };
|
|
14485
|
+
if (needsRead) copy.readBy[a.readerId] = now;
|
|
14486
|
+
if (needsComplete) {
|
|
14487
|
+
copy.completed = true;
|
|
14488
|
+
copy.completedBy = a.readerId;
|
|
14489
|
+
copy.completedAt = now;
|
|
14490
|
+
}
|
|
14491
|
+
if (needsOutcome) copy.outcome = a.outcome;
|
|
14492
|
+
updated.push(copy);
|
|
14493
|
+
ackRecords.push({
|
|
14494
|
+
__ack: true,
|
|
14495
|
+
messageId: msg.id,
|
|
14496
|
+
readerId: a.readerId,
|
|
14497
|
+
timestamp: now,
|
|
14498
|
+
read: a.read !== false,
|
|
14499
|
+
completed: a.completed,
|
|
14500
|
+
completedBy: a.completed ? a.readerId : void 0,
|
|
14501
|
+
outcome: a.outcome
|
|
14502
|
+
});
|
|
14503
|
+
}
|
|
14504
|
+
if (ackRecords.length === 0) {
|
|
14505
|
+
if (updated.length > 0) {
|
|
14506
|
+
for (const message of updated) {
|
|
14507
|
+
this.publishHqMailboxEvent({
|
|
14508
|
+
mailboxId: this.hqMailboxId,
|
|
14509
|
+
action: message.completed ? "message.completed" : "message.read",
|
|
14510
|
+
message
|
|
14511
|
+
});
|
|
14512
|
+
}
|
|
14226
14513
|
}
|
|
14514
|
+
return updated;
|
|
14515
|
+
}
|
|
14516
|
+
const serialized = ackRecords.map((a) => serializeAckRecord(a)).join("");
|
|
14517
|
+
await withFileLock(this.messagePath, async () => {
|
|
14518
|
+
await fsp11.appendFile(this.messagePath, serialized, "utf8");
|
|
14227
14519
|
const { mtimeMs, size } = await this._statMessageFile();
|
|
14228
|
-
|
|
14520
|
+
for (const ack of ackRecords) {
|
|
14521
|
+
this._applyAckToCache(ack);
|
|
14522
|
+
}
|
|
14523
|
+
this._messageCacheMtime = mtimeMs;
|
|
14524
|
+
this._messageCacheSize = size;
|
|
14525
|
+
this.diag.ackManyPostLockMtime = mtimeMs;
|
|
14526
|
+
this.diag.ackManyPostLockSize = size;
|
|
14527
|
+
this.diag.ackManyMessageCount = this._messageCache?.length ?? -1;
|
|
14528
|
+
this.diag.ackManyAckCount = ackRecords.length;
|
|
14529
|
+
const expectedMinSize = preLockSize + serialized.length;
|
|
14530
|
+
if (preLockSize > 0 && size > expectedMinSize) {
|
|
14531
|
+
const extraBytes = size - expectedMinSize;
|
|
14532
|
+
console.error(
|
|
14533
|
+
`[MAILBOX-DIAG] ackMany: post-lock size ${size} exceeds expected ${expectedMinSize} by ${extraBytes}B (mtime ${preLockMtime}->${mtimeMs}). Cache may be missing cross-process messages. diag.ackManyCacheDesync=${++this.diag.ackManyCacheDesync}`
|
|
14534
|
+
);
|
|
14535
|
+
}
|
|
14229
14536
|
});
|
|
14230
14537
|
for (const message of updated) {
|
|
14231
14538
|
this.publishHqMailboxEvent({
|
|
@@ -14268,87 +14575,87 @@ var GlobalMailbox = class {
|
|
|
14268
14575
|
return count;
|
|
14269
14576
|
}
|
|
14270
14577
|
async softDelete(mailId, by) {
|
|
14271
|
-
|
|
14578
|
+
const all = await this._readMessagesCached();
|
|
14579
|
+
const target = all.find((m) => m.id === mailId);
|
|
14580
|
+
if (target === void 0) return null;
|
|
14581
|
+
if (target.deletedAt !== void 0) {
|
|
14582
|
+
return { ...target, readBy: { ...target.readBy } };
|
|
14583
|
+
}
|
|
14584
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
14585
|
+
const ack = {
|
|
14586
|
+
__ack: true,
|
|
14587
|
+
messageId: mailId,
|
|
14588
|
+
readerId: by,
|
|
14589
|
+
timestamp: now,
|
|
14590
|
+
read: true,
|
|
14591
|
+
deleted: true,
|
|
14592
|
+
deletedBy: by
|
|
14593
|
+
};
|
|
14272
14594
|
await withFileLock(this.messagePath, async () => {
|
|
14273
|
-
|
|
14274
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
14275
|
-
let changed = false;
|
|
14276
|
-
for (const m of all) {
|
|
14277
|
-
if (m.id !== mailId) continue;
|
|
14278
|
-
if (m.deletedAt !== void 0) {
|
|
14279
|
-
updated = m;
|
|
14280
|
-
continue;
|
|
14281
|
-
}
|
|
14282
|
-
m.deletedAt = now;
|
|
14283
|
-
m.deletedBy = by;
|
|
14284
|
-
updated = m;
|
|
14285
|
-
changed = true;
|
|
14286
|
-
}
|
|
14287
|
-
if (changed) {
|
|
14288
|
-
const serialized = all.map((m) => JSON.stringify(m)).join(LINE_SEPARATOR) + LINE_SEPARATOR;
|
|
14289
|
-
await fsp11.writeFile(this.messagePath, serialized, "utf8");
|
|
14290
|
-
}
|
|
14595
|
+
await fsp11.appendFile(this.messagePath, serializeAckRecord(ack), "utf8");
|
|
14291
14596
|
const { mtimeMs, size } = await this._statMessageFile();
|
|
14292
|
-
this.
|
|
14597
|
+
this._applyAckToCache(ack);
|
|
14598
|
+
this._messageCacheMtime = mtimeMs;
|
|
14599
|
+
this._messageCacheSize = size;
|
|
14293
14600
|
});
|
|
14294
|
-
|
|
14295
|
-
|
|
14296
|
-
|
|
14297
|
-
|
|
14298
|
-
|
|
14299
|
-
|
|
14300
|
-
|
|
14301
|
-
|
|
14302
|
-
|
|
14303
|
-
|
|
14304
|
-
|
|
14305
|
-
|
|
14306
|
-
|
|
14307
|
-
|
|
14308
|
-
|
|
14309
|
-
}
|
|
14310
|
-
return
|
|
14601
|
+
const msg = { ...target, readBy: { ...target.readBy } };
|
|
14602
|
+
msg.deletedAt = now;
|
|
14603
|
+
msg.deletedBy = by;
|
|
14604
|
+
this.publishHqMailboxEvent({
|
|
14605
|
+
mailboxId: this.hqMailboxId,
|
|
14606
|
+
action: "message.updated",
|
|
14607
|
+
message: msg
|
|
14608
|
+
});
|
|
14609
|
+
this.publishHqMailboxSnapshot();
|
|
14610
|
+
this.eventEmitter?.emit({
|
|
14611
|
+
type: "message.deleted",
|
|
14612
|
+
messageId: msg.id,
|
|
14613
|
+
from: msg.from,
|
|
14614
|
+
to: msg.to,
|
|
14615
|
+
timestamp: msg.deletedAt ?? now
|
|
14616
|
+
});
|
|
14617
|
+
return msg;
|
|
14311
14618
|
}
|
|
14312
14619
|
async restore(mailId) {
|
|
14313
|
-
|
|
14620
|
+
const all = await this._readMessagesCached();
|
|
14621
|
+
const target = all.find((m) => m.id === mailId);
|
|
14622
|
+
if (target === void 0) return null;
|
|
14623
|
+
if (target.deletedAt === void 0 && target.deletedBy === void 0) {
|
|
14624
|
+
return { ...target, readBy: { ...target.readBy } };
|
|
14625
|
+
}
|
|
14626
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
14627
|
+
const ack = {
|
|
14628
|
+
__ack: true,
|
|
14629
|
+
messageId: mailId,
|
|
14630
|
+
readerId: "",
|
|
14631
|
+
timestamp: now,
|
|
14632
|
+
read: true,
|
|
14633
|
+
deleted: false
|
|
14634
|
+
};
|
|
14314
14635
|
await withFileLock(this.messagePath, async () => {
|
|
14315
|
-
|
|
14316
|
-
let changed = false;
|
|
14317
|
-
for (const m of all) {
|
|
14318
|
-
if (m.id !== mailId) continue;
|
|
14319
|
-
if (m.deletedAt === void 0 && m.deletedBy === void 0) {
|
|
14320
|
-
updated = m;
|
|
14321
|
-
continue;
|
|
14322
|
-
}
|
|
14323
|
-
delete m.deletedAt;
|
|
14324
|
-
delete m.deletedBy;
|
|
14325
|
-
updated = m;
|
|
14326
|
-
changed = true;
|
|
14327
|
-
}
|
|
14328
|
-
if (changed) {
|
|
14329
|
-
const serialized = all.map((m) => JSON.stringify(m)).join(LINE_SEPARATOR) + LINE_SEPARATOR;
|
|
14330
|
-
await fsp11.writeFile(this.messagePath, serialized, "utf8");
|
|
14331
|
-
}
|
|
14636
|
+
await fsp11.appendFile(this.messagePath, serializeAckRecord(ack), "utf8");
|
|
14332
14637
|
const { mtimeMs, size } = await this._statMessageFile();
|
|
14333
|
-
this.
|
|
14638
|
+
this._applyAckToCache(ack);
|
|
14639
|
+
this._messageCacheMtime = mtimeMs;
|
|
14640
|
+
this._messageCacheSize = size;
|
|
14334
14641
|
});
|
|
14335
|
-
|
|
14336
|
-
|
|
14337
|
-
|
|
14338
|
-
|
|
14339
|
-
|
|
14340
|
-
|
|
14341
|
-
|
|
14342
|
-
|
|
14343
|
-
|
|
14344
|
-
|
|
14345
|
-
|
|
14346
|
-
|
|
14347
|
-
|
|
14348
|
-
|
|
14349
|
-
|
|
14350
|
-
}
|
|
14351
|
-
return
|
|
14642
|
+
const msg = { ...target, readBy: { ...target.readBy } };
|
|
14643
|
+
delete msg.deletedAt;
|
|
14644
|
+
delete msg.deletedBy;
|
|
14645
|
+
this.publishHqMailboxEvent({
|
|
14646
|
+
mailboxId: this.hqMailboxId,
|
|
14647
|
+
action: "message.updated",
|
|
14648
|
+
message: msg
|
|
14649
|
+
});
|
|
14650
|
+
this.publishHqMailboxSnapshot();
|
|
14651
|
+
this.eventEmitter?.emit({
|
|
14652
|
+
type: "message.restored",
|
|
14653
|
+
messageId: msg.id,
|
|
14654
|
+
from: msg.from,
|
|
14655
|
+
to: msg.to,
|
|
14656
|
+
timestamp: now
|
|
14657
|
+
});
|
|
14658
|
+
return msg;
|
|
14352
14659
|
}
|
|
14353
14660
|
// ── Agent registry ──────────────────────────────────────────────────────
|
|
14354
14661
|
async registerAgent(input) {
|
|
@@ -14585,11 +14892,18 @@ var GlobalMailbox = class {
|
|
|
14585
14892
|
}
|
|
14586
14893
|
async getClientStatuses() {
|
|
14587
14894
|
await this._ensureClientRegistry();
|
|
14588
|
-
|
|
14895
|
+
let registry = await this._readClientRegistry();
|
|
14589
14896
|
const before = registry.size;
|
|
14590
14897
|
this._pruneStaleClientsInPlace(registry);
|
|
14591
14898
|
if (registry.size < before) {
|
|
14592
|
-
await this.
|
|
14899
|
+
await withFileLock(this.clientRegistryPath, async () => {
|
|
14900
|
+
registry = await this._readClientRegistry({ fresh: true });
|
|
14901
|
+
const postLockBefore = registry.size;
|
|
14902
|
+
this._pruneStaleClientsInPlace(registry);
|
|
14903
|
+
if (registry.size < postLockBefore) {
|
|
14904
|
+
await this._writeClientRegistry(registry);
|
|
14905
|
+
}
|
|
14906
|
+
});
|
|
14593
14907
|
}
|
|
14594
14908
|
const now = Date.now();
|
|
14595
14909
|
return Array.from(registry.values()).map((c) => ({
|
|
@@ -14795,75 +15109,22 @@ var GlobalMailbox = class {
|
|
|
14795
15109
|
throw err;
|
|
14796
15110
|
}
|
|
14797
15111
|
}
|
|
14798
|
-
/**
|
|
14799
|
-
* Read only newly-appended bytes from the file and append them to the
|
|
14800
|
-
* in-memory cache. This avoids re-reading and re-parsing the entire file
|
|
14801
|
-
* when another process appended messages since our last read.
|
|
14802
|
-
*
|
|
14803
|
-
* Only safe when the file grew in size (messages are append-only). When
|
|
14804
|
-
* the file was rewritten (ack/purge changed existing content), callers
|
|
14805
|
-
* must fall back to {@link _readMessages}.
|
|
14806
|
-
*
|
|
14807
|
-
* @returns The (now up-to-date) message cache.
|
|
14808
|
-
*/
|
|
14809
|
-
async _readNewMessagesOnly(fd, oldSize, newSize) {
|
|
14810
|
-
const tailLen = newSize - oldSize;
|
|
14811
|
-
const buf = Buffer.alloc(tailLen);
|
|
14812
|
-
await fd.read(buf, 0, tailLen, oldSize);
|
|
14813
|
-
const tail = buf.toString("utf8");
|
|
14814
|
-
if (!tail.endsWith(LINE_SEPARATOR)) {
|
|
14815
|
-
const lastSeparatorIdx = tail.lastIndexOf(LINE_SEPARATOR);
|
|
14816
|
-
if (lastSeparatorIdx === -1) {
|
|
14817
|
-
return this._messageCache;
|
|
14818
|
-
}
|
|
14819
|
-
const completeTail = tail.slice(0, lastSeparatorIdx + 1);
|
|
14820
|
-
this._appendIncrementalLines(completeTail);
|
|
14821
|
-
this._messageCacheSize = oldSize + lastSeparatorIdx + 1;
|
|
14822
|
-
return this._messageCache;
|
|
14823
|
-
}
|
|
14824
|
-
this._appendIncrementalLines(tail);
|
|
14825
|
-
this._messageCacheSize = newSize;
|
|
14826
|
-
return this._messageCache;
|
|
14827
|
-
}
|
|
14828
|
-
_appendIncrementalLines(raw) {
|
|
14829
|
-
for (const line of raw.split(LINE_SEPARATOR)) {
|
|
14830
|
-
if (!line.trim()) continue;
|
|
14831
|
-
const message = this._parseLine(line);
|
|
14832
|
-
if (message === null) continue;
|
|
14833
|
-
this._messageCache.push(message);
|
|
14834
|
-
const idx = this._messageCache.length - 1;
|
|
14835
|
-
if (this._recipientIndex !== null) {
|
|
14836
|
-
let set = this._recipientIndex.get(message.to);
|
|
14837
|
-
if (set === void 0) {
|
|
14838
|
-
set = /* @__PURE__ */ new Set();
|
|
14839
|
-
this._recipientIndex.set(message.to, set);
|
|
14840
|
-
}
|
|
14841
|
-
set.add(idx);
|
|
14842
|
-
}
|
|
14843
|
-
if (this._senderIndex !== null) {
|
|
14844
|
-
let set = this._senderIndex.get(message.from);
|
|
14845
|
-
if (set === void 0) {
|
|
14846
|
-
set = /* @__PURE__ */ new Set();
|
|
14847
|
-
this._senderIndex.set(message.from, set);
|
|
14848
|
-
}
|
|
14849
|
-
set.add(idx);
|
|
14850
|
-
}
|
|
14851
|
-
}
|
|
14852
|
-
}
|
|
14853
|
-
_parseLine(line) {
|
|
14854
|
-
try {
|
|
14855
|
-
return parseMailboxMessageLine(line);
|
|
14856
|
-
} catch {
|
|
14857
|
-
return null;
|
|
14858
|
-
}
|
|
14859
|
-
}
|
|
14860
|
-
/** Parse a JSONL string into MailboxMessage[], including migration. */
|
|
15112
|
+
/** Parse a JSONL string into MailboxMessage[], applying ack records. */
|
|
14861
15113
|
_parseLines(raw) {
|
|
14862
15114
|
const lines = raw.split(LINE_SEPARATOR).filter((l) => l.trim().length > 0);
|
|
14863
15115
|
const messages = [];
|
|
15116
|
+
const acks = [];
|
|
14864
15117
|
for (const line of lines) {
|
|
14865
|
-
const
|
|
14866
|
-
if (
|
|
15118
|
+
const parsed = parseMailboxLine(line);
|
|
15119
|
+
if (isAckRecord(parsed)) {
|
|
15120
|
+
acks.push(parsed);
|
|
15121
|
+
} else if (parsed !== null) {
|
|
15122
|
+
messages.push(parsed);
|
|
15123
|
+
}
|
|
15124
|
+
}
|
|
15125
|
+
for (const ack of acks) {
|
|
15126
|
+
const target = messages.find((m) => m.id === ack.messageId);
|
|
15127
|
+
if (target) applyAckToMessage(target, ack);
|
|
14867
15128
|
}
|
|
14868
15129
|
return messages;
|
|
14869
15130
|
}
|
|
@@ -14946,16 +15207,6 @@ var GlobalMailbox = class {
|
|
|
14946
15207
|
if (this._messageCache !== null && this._messageCacheMtime === st.mtimeMs && this._messageCacheSize === st.size) {
|
|
14947
15208
|
return this._messageCache;
|
|
14948
15209
|
}
|
|
14949
|
-
if (this._messageCache !== null && this._messageCacheSize >= 0 && st.size > this._messageCacheSize) {
|
|
14950
|
-
const fd = await fsp11.open(this.messagePath, "r");
|
|
14951
|
-
try {
|
|
14952
|
-
const updated = await this._readNewMessagesOnly(fd, this._messageCacheSize, st.size);
|
|
14953
|
-
this._messageCacheMtime = st.mtimeMs;
|
|
14954
|
-
return updated;
|
|
14955
|
-
} finally {
|
|
14956
|
-
await fd.close();
|
|
14957
|
-
}
|
|
14958
|
-
}
|
|
14959
15210
|
const all = await this._readMessages();
|
|
14960
15211
|
this._setMessageCache(all, st.mtimeMs, st.size);
|
|
14961
15212
|
return all;
|
|
@@ -14996,6 +15247,21 @@ var GlobalMailbox = class {
|
|
|
14996
15247
|
this._messageCacheSize = size;
|
|
14997
15248
|
this._rebuildRecipientIndex();
|
|
14998
15249
|
}
|
|
15250
|
+
/**
|
|
15251
|
+
* Fold a single ack record into the in-memory cache, mutating the target
|
|
15252
|
+
* message in place. Called from `ackMany` after appending the record to
|
|
15253
|
+
* disk so the cache reflects the ack without a full re-read + re-parse.
|
|
15254
|
+
*
|
|
15255
|
+
* An ack only touches `readBy`/`completed*`/`outcome` — none of which
|
|
15256
|
+
* affect the recipient/sender indexes — so no index rebuild is needed.
|
|
15257
|
+
* No-op when the cache is empty or the target message isn't cached.
|
|
15258
|
+
*/
|
|
15259
|
+
_applyAckToCache(ack) {
|
|
15260
|
+
const cache = this._messageCache;
|
|
15261
|
+
if (cache === null) return;
|
|
15262
|
+
const target = cache.find((m) => m.id === ack.messageId);
|
|
15263
|
+
if (target !== void 0) applyAckToMessage(target, ack);
|
|
15264
|
+
}
|
|
14999
15265
|
/**
|
|
15000
15266
|
* Build the recipient AND sender indexes from the current `_messageCache`.
|
|
15001
15267
|
* Called only from `_setMessageCache` so both indexes are always in sync
|
|
@@ -15451,7 +15717,7 @@ function makeFleetStatusTool(opts = {}) {
|
|
|
15451
15717
|
const resolveMailbox = opts.resolveMailbox ?? ((ctx) => new GlobalMailbox(opts.projectDir ?? defaultResolveProjectDir(ctx), opts.events));
|
|
15452
15718
|
return {
|
|
15453
15719
|
name: "fleet_status",
|
|
15454
|
-
description: "Live snapshot of every agent working on this project (
|
|
15720
|
+
description: "Live snapshot of every agent working on this canonical project (all clients, processes, sessions, branches, and linked Git worktrees): who is online, what task each is on, which tool is running, and progress counters. Check it before starting work that might overlap with a peer, when deciding whether to wait for someone or proceed, or when a task mentions another agent. Read-only. To talk to a peer, use mail_send with the returned id.",
|
|
15455
15721
|
usageHint: "fleet_status (optionally: onlineOnly=false to include recently-offline agents)",
|
|
15456
15722
|
category: "Coordination",
|
|
15457
15723
|
permission: "auto",
|
|
@@ -16115,7 +16381,7 @@ function makeMailSendTool(opts = {}) {
|
|
|
16115
16381
|
const resolveMailbox = makeResolver(opts);
|
|
16116
16382
|
return {
|
|
16117
16383
|
name: "mail_send",
|
|
16118
|
-
description: 'Send
|
|
16384
|
+
description: 'Send mail to any agent on this canonical project, across clients, processes, sessions, branches, and linked Git worktrees (CLI, TUI, WebUI, ACP/MCP/HTTP). Use it to hand off work, ask questions, announce what you just did, or request a review (type="review" \u2014 passive ask, no immediate reply required). to="*" broadcasts to everyone; to="leader" reaches every leader process; an exact id like "leader@a1b2c3d4" reaches one agent. Recipients see your mail automatically before their next step. Pick the type that matches the intent: note (default), ask (blocking question), assign (task), steer (mid-task direction), result (completion notice), review (passive ask), btw/status/broadcast/control (informational).',
|
|
16119
16385
|
usageHint: 'mail_send to="<id>" type="review" body="please skim <file>"',
|
|
16120
16386
|
category: "Coordination",
|
|
16121
16387
|
permission: "auto",
|
|
@@ -16175,7 +16441,7 @@ function makeMailInboxTool(opts = {}) {
|
|
|
16175
16441
|
const resolveMailbox = makeResolver(opts);
|
|
16176
16442
|
return {
|
|
16177
16443
|
name: "mail_inbox",
|
|
16178
|
-
description: 'Read your unread mail from
|
|
16444
|
+
description: 'Read your unread project-wide mail from agents in any client, session, branch, or linked Git worktree and mark it read. Covers mail addressed to you directly, to your base name (e.g. "leader"), and broadcasts ("*"). Urgent steer/btw mail is already injected automatically \u2014 use this to catch up on notes, questions, handoffs, results, and review requests (type="review" \u2014 passive asks where no reply is required). Best called after a long stretch of tool work. Set completed=true to finish every returned message in the same call.',
|
|
16179
16445
|
usageHint: 'mail_inbox (optionally: limit=10, markRead=false to peek, completed=true outcome="handled")',
|
|
16180
16446
|
category: "Coordination",
|
|
16181
16447
|
permission: "auto",
|
|
@@ -16990,7 +17256,7 @@ function createMailboxHooks(opts) {
|
|
|
16990
17256
|
var NULL_FLEET_BUS = new FleetBus();
|
|
16991
17257
|
|
|
16992
17258
|
// src/coordination/package-author-tracker.ts
|
|
16993
|
-
import * as
|
|
17259
|
+
import * as fs3 from "node:fs/promises";
|
|
16994
17260
|
import * as path16 from "node:path";
|
|
16995
17261
|
var DEFAULT_MAX_ENTRIES = 1e4;
|
|
16996
17262
|
var LOG_FILENAME = "package-authors.json";
|
|
@@ -16999,7 +17265,7 @@ function logPath(storageDir) {
|
|
|
16999
17265
|
}
|
|
17000
17266
|
async function loadLog(storageDir, projectRoot) {
|
|
17001
17267
|
try {
|
|
17002
|
-
const raw = await
|
|
17268
|
+
const raw = await fs3.readFile(logPath(storageDir), "utf-8");
|
|
17003
17269
|
const parsed = JSON.parse(raw);
|
|
17004
17270
|
if (!parsed.entries || !Array.isArray(parsed.entries)) {
|
|
17005
17271
|
return { projectRoot, entries: [] };
|
|
@@ -17013,10 +17279,10 @@ async function loadLog(storageDir, projectRoot) {
|
|
|
17013
17279
|
}
|
|
17014
17280
|
}
|
|
17015
17281
|
async function saveLog(storageDir, log) {
|
|
17016
|
-
await
|
|
17282
|
+
await fs3.mkdir(storageDir, { recursive: true });
|
|
17017
17283
|
const tmp = `${logPath(storageDir)}.tmp.${Date.now()}`;
|
|
17018
|
-
await
|
|
17019
|
-
await
|
|
17284
|
+
await fs3.writeFile(tmp, JSON.stringify(log, null, 2) + "\n", "utf-8");
|
|
17285
|
+
await fs3.rename(tmp, logPath(storageDir));
|
|
17020
17286
|
}
|
|
17021
17287
|
function detectEcosystem(manifestPath) {
|
|
17022
17288
|
const name = path16.win32.basename(manifestPath).toLowerCase();
|
|
@@ -17586,7 +17852,7 @@ var AdaptiveConcurrencyController = class {
|
|
|
17586
17852
|
};
|
|
17587
17853
|
|
|
17588
17854
|
// src/coordination/agent-monitor.ts
|
|
17589
|
-
import * as
|
|
17855
|
+
import * as fs4 from "node:fs/promises";
|
|
17590
17856
|
import * as path18 from "node:path";
|
|
17591
17857
|
var AgentMonitorService = class _AgentMonitorService {
|
|
17592
17858
|
_fleetBus;
|
|
@@ -17830,9 +18096,9 @@ var AgentMonitorService = class _AgentMonitorService {
|
|
|
17830
18096
|
* firehose (or, worse, only the first word).
|
|
17831
18097
|
*/
|
|
17832
18098
|
_appendStreamDelta(subagentId, session, kind, text, iteration) {
|
|
17833
|
-
const
|
|
17834
|
-
if (
|
|
17835
|
-
|
|
18099
|
+
const open6 = this._openStreams.get(subagentId);
|
|
18100
|
+
if (open6 && open6.entry.kind === kind && open6.entry.iteration === iteration && open6.entry.content.length + text.length <= _AgentMonitorService._MAX_SEGMENT_CHARS) {
|
|
18101
|
+
open6.entry.content += text;
|
|
17836
18102
|
return;
|
|
17837
18103
|
}
|
|
17838
18104
|
this._closeStream(subagentId);
|
|
@@ -17856,12 +18122,12 @@ var AgentMonitorService = class _AgentMonitorService {
|
|
|
17856
18122
|
* announce the completed entry on the local bus + HQ callback.
|
|
17857
18123
|
*/
|
|
17858
18124
|
_closeStream(subagentId) {
|
|
17859
|
-
const
|
|
17860
|
-
if (!
|
|
18125
|
+
const open6 = this._openStreams.get(subagentId);
|
|
18126
|
+
if (!open6) return;
|
|
17861
18127
|
this._openStreams.delete(subagentId);
|
|
17862
|
-
this._appendToFile(subagentId,
|
|
18128
|
+
this._appendToFile(subagentId, open6.entry).catch(() => {
|
|
17863
18129
|
});
|
|
17864
|
-
this._emitEntry(
|
|
18130
|
+
this._emitEntry(open6.entry);
|
|
17865
18131
|
}
|
|
17866
18132
|
_addEntry(subagentId, entry) {
|
|
17867
18133
|
const session = this._sessions.get(subagentId);
|
|
@@ -17892,10 +18158,10 @@ var AgentMonitorService = class _AgentMonitorService {
|
|
|
17892
18158
|
}
|
|
17893
18159
|
async _appendToFile(subagentId, entry) {
|
|
17894
18160
|
const dir = path18.join(this._transcriptsDir, subagentId);
|
|
17895
|
-
await
|
|
18161
|
+
await fs4.mkdir(dir, { recursive: true });
|
|
17896
18162
|
const filePath = path18.join(dir, "transcript.jsonl");
|
|
17897
18163
|
const line = JSON.stringify(entry) + "\n";
|
|
17898
|
-
await
|
|
18164
|
+
await fs4.appendFile(filePath, line, { encoding: "utf8" });
|
|
17899
18165
|
}
|
|
17900
18166
|
_uid() {
|
|
17901
18167
|
return `${Date.now().toString(36)}${Math.random().toString(36).slice(2, 8)}`;
|