@wrongstack/core 0.267.0 → 0.269.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/{agent-bridge-STJ3JwwK.d.ts → agent-bridge-PcHQl_UQ.d.ts} +1 -1
- package/dist/{agent-subagent-runner-CzPGP3jA.d.ts → agent-subagent-runner-SHJW7t8q.d.ts} +8 -8
- package/dist/{brain-Cdg77tVN.d.ts → brain-BYcK__Ym.d.ts} +1 -1
- package/dist/{compactor-iMZ84CXq.d.ts → compactor-C2RKEBtC.d.ts} +1 -1
- package/dist/{config-Du3pYYln.d.ts → config-C_ae2k86.d.ts} +79 -2
- package/dist/{context-dT5Ueund.d.ts → context-Dp87Bcaq.d.ts} +47 -1
- package/dist/coordination/index.d.ts +62 -160
- package/dist/coordination/index.js +566 -149
- package/dist/coordination/index.js.map +1 -1
- package/dist/defaults/index.d.ts +26 -25
- package/dist/defaults/index.js +366 -137
- package/dist/defaults/index.js.map +1 -1
- package/dist/execution/index.d.ts +72 -16
- package/dist/execution/index.js +267 -55
- package/dist/execution/index.js.map +1 -1
- package/dist/execution/prompt-enhancer.d.ts +1 -1
- package/dist/extension/index.d.ts +7 -6
- package/dist/global-mailbox-Bvrz1P3f.d.ts +664 -0
- package/dist/{goal-preamble-SulMTowG.d.ts → goal-preamble-CA_4yiGQ.d.ts} +9 -9
- package/dist/{goal-store-CABDwdFE.d.ts → goal-store-DhuJoUNG.d.ts} +1 -1
- package/dist/hq/index.d.ts +204 -0
- package/dist/hq/index.js +1931 -0
- package/dist/hq/index.js.map +1 -0
- package/dist/{index-DtCVWel4.d.ts → index-CZQ6Pwbs.d.ts} +8 -8
- package/dist/{index-Bms0m4oy.d.ts → index-W4VJCzHa.d.ts} +5 -5
- package/dist/{index-IEuxQd-E.d.ts → index-whDfTANu.d.ts} +2 -2
- package/dist/index.d.ts +46 -42
- package/dist/index.js +3472 -1651
- package/dist/index.js.map +1 -1
- package/dist/infrastructure/index.d.ts +6 -6
- package/dist/infrastructure/index.js +48 -21
- package/dist/infrastructure/index.js.map +1 -1
- package/dist/kernel/index.d.ts +10 -9
- package/dist/{pipeline-BfD2k1rT.d.ts → mailbox-types-Ct2hJq0P.d.ts} +1 -244
- package/dist/{mcp-servers-C2cBTxUR.d.ts → mcp-servers-DJdZiRcv.d.ts} +10 -4
- package/dist/models/index.d.ts +5 -5
- package/dist/models/index.js +4 -3
- package/dist/models/index.js.map +1 -1
- package/dist/{models-registry-BqGZNJQ-.d.ts → models-registry-C3a-2-Yd.d.ts} +1 -1
- package/dist/{multi-agent-coordinator-B8R43uPz.d.ts → multi-agent-coordinator-CJSpTe5O.d.ts} +1 -1
- package/dist/{null-fleet-bus-CnXa5oTH.d.ts → null-fleet-bus-QVshIsDx.d.ts} +6 -6
- package/dist/observability/index.d.ts +2 -2
- package/dist/{parallel-eternal-engine-DdNnw9BQ.d.ts → parallel-eternal-engine-D9y5Pkcc.d.ts} +9 -15
- package/dist/{path-resolver-COIMLCQL.d.ts → path-resolver-CnQ8SIfh.d.ts} +4 -3
- package/dist/{permission-B75JAi3-.d.ts → permission-CvYQNUqZ.d.ts} +1 -1
- package/dist/{permission-policy-DlR9eJAM.d.ts → permission-policy-D5Ss8j4B.d.ts} +2 -3
- package/dist/pipeline-l_zzFRh3.d.ts +245 -0
- package/dist/{plan-templates-DSIKCXZN.d.ts → plan-templates-NtPgyeJA.d.ts} +6 -5
- package/dist/{provider-model-resolve-BNRsNuJx.d.ts → provider-model-resolve-d5poT5y0.d.ts} +3 -3
- package/dist/{provider-runner-CX7iIvox.d.ts → provider-runner-gkctlQV_.d.ts} +3 -3
- package/dist/{retry-policy-BilV1ujH.d.ts → retry-policy-CtFhfwa8.d.ts} +1 -1
- package/dist/sdd/index.d.ts +9 -8
- package/dist/sdd/index.js +33 -3
- package/dist/sdd/index.js.map +1 -1
- package/dist/{secret-vault-gkvEZZfE.d.ts → secret-vault-BLsVmTIK.d.ts} +1 -1
- package/dist/security/index.d.ts +5 -5
- package/dist/security/index.js +39 -29
- package/dist/security/index.js.map +1 -1
- package/dist/{selector-Bc7eWtT3.d.ts → selector-CXl2_y9W.d.ts} +1 -1
- package/dist/{session-event-bridge-D-araDEz.d.ts → session-event-bridge-Ccud20CC.d.ts} +1 -1
- package/dist/{session-reader-D7Dapswh.d.ts → session-reader-ZeXQmsmE.d.ts} +1 -1
- package/dist/skills/index.js.map +1 -1
- package/dist/storage/index.d.ts +16 -12
- package/dist/storage/index.js +273 -100
- package/dist/storage/index.js.map +1 -1
- package/dist/tools/index.d.ts +2 -2
- package/dist/tools/index.js +166 -31
- package/dist/tools/index.js.map +1 -1
- package/dist/types/index.d.ts +22 -21
- package/dist/types/index.js +178 -70
- package/dist/types/index.js.map +1 -1
- package/dist/utils/index.d.ts +22 -3
- package/dist/utils/index.js +197 -25
- package/dist/utils/index.js.map +1 -1
- package/package.json +5 -1
- package/skills/chimera/SKILL.md +1 -1
- package/skills/typescript-strict/SKILL.md +3 -3
- package/skills/typescript-strict/SKILL.save.md +1 -1
|
@@ -172,8 +172,8 @@ async function atomicWrite(targetPath, content, opts = {}) {
|
|
|
172
172
|
}
|
|
173
173
|
let mode;
|
|
174
174
|
try {
|
|
175
|
-
const
|
|
176
|
-
mode =
|
|
175
|
+
const stat7 = await fsp6.stat(targetPath);
|
|
176
|
+
mode = stat7.mode & 511;
|
|
177
177
|
} catch {
|
|
178
178
|
mode = opts.mode;
|
|
179
179
|
}
|
|
@@ -213,8 +213,8 @@ async function withFileLock(targetPath, fn, opts = {}) {
|
|
|
213
213
|
}
|
|
214
214
|
if (code !== "EEXIST") throw err;
|
|
215
215
|
try {
|
|
216
|
-
const
|
|
217
|
-
if (Date.now() -
|
|
216
|
+
const stat7 = await fsp6.stat(lockPath);
|
|
217
|
+
if (Date.now() - stat7.mtimeMs > staleMs) {
|
|
218
218
|
await fsp6.unlink(lockPath);
|
|
219
219
|
continue;
|
|
220
220
|
}
|
|
@@ -545,8 +545,8 @@ async function expandGlob(pattern) {
|
|
|
545
545
|
for (const e of entries) {
|
|
546
546
|
const full = `${dir}${SEP}${e}`;
|
|
547
547
|
try {
|
|
548
|
-
const
|
|
549
|
-
if (
|
|
548
|
+
const stat7 = await fsp6.stat(full);
|
|
549
|
+
if (stat7.isDirectory()) await walk(full, rest);
|
|
550
550
|
} catch {
|
|
551
551
|
}
|
|
552
552
|
}
|
|
@@ -563,8 +563,8 @@ async function expandGlob(pattern) {
|
|
|
563
563
|
if (entries.includes(seg)) {
|
|
564
564
|
const full = `${dir}${SEP}${seg}`;
|
|
565
565
|
try {
|
|
566
|
-
const
|
|
567
|
-
if (
|
|
566
|
+
const stat7 = await fsp6.stat(full);
|
|
567
|
+
if (stat7.isDirectory()) await walk(full, rest);
|
|
568
568
|
} catch {
|
|
569
569
|
}
|
|
570
570
|
}
|
|
@@ -636,7 +636,7 @@ function hasToolResult(msg) {
|
|
|
636
636
|
}
|
|
637
637
|
function toolUseIds(msg) {
|
|
638
638
|
const ids = /* @__PURE__ */ new Set();
|
|
639
|
-
if (
|
|
639
|
+
if (msg?.role !== "assistant") return ids;
|
|
640
640
|
for (const block of contentBlocks(msg)) {
|
|
641
641
|
if (block.type === "tool_use") ids.add(block.id);
|
|
642
642
|
}
|
|
@@ -644,7 +644,7 @@ function toolUseIds(msg) {
|
|
|
644
644
|
}
|
|
645
645
|
function toolResultIds(msg) {
|
|
646
646
|
const ids = /* @__PURE__ */ new Set();
|
|
647
|
-
if (
|
|
647
|
+
if (msg?.role !== "user") return ids;
|
|
648
648
|
for (const block of contentBlocks(msg)) {
|
|
649
649
|
if (block.type === "tool_result") ids.add(block.tool_use_id);
|
|
650
650
|
}
|
|
@@ -996,7 +996,7 @@ var CollabSession = class extends EventEmitter {
|
|
|
996
996
|
}
|
|
997
997
|
for (const filePath of allFiles) {
|
|
998
998
|
try {
|
|
999
|
-
const [content,
|
|
999
|
+
const [content, stat7] = await Promise.all([
|
|
1000
1000
|
fsp6.readFile(filePath, "utf8"),
|
|
1001
1001
|
fsp6.stat(filePath)
|
|
1002
1002
|
]);
|
|
@@ -1006,8 +1006,8 @@ var CollabSession = class extends EventEmitter {
|
|
|
1006
1006
|
path: filePath,
|
|
1007
1007
|
content,
|
|
1008
1008
|
language,
|
|
1009
|
-
snapshotMtimeMs:
|
|
1010
|
-
snapshotSizeBytes:
|
|
1009
|
+
snapshotMtimeMs: stat7.mtimeMs,
|
|
1010
|
+
snapshotSizeBytes: stat7.size
|
|
1011
1011
|
});
|
|
1012
1012
|
} catch {
|
|
1013
1013
|
this.snapshot.files.push({ path: filePath, content: "", language: void 0 });
|
|
@@ -1420,9 +1420,9 @@ Emit each evaluation immediately. Do not wait until you have read all reports.`;
|
|
|
1420
1420
|
for (const file of this.snapshot.files) {
|
|
1421
1421
|
if (file.snapshotMtimeMs === void 0 && file.snapshotSizeBytes === void 0) continue;
|
|
1422
1422
|
try {
|
|
1423
|
-
const
|
|
1424
|
-
const mtimeChanged = file.snapshotMtimeMs !== void 0 &&
|
|
1425
|
-
const sizeChanged = file.snapshotSizeBytes !== void 0 &&
|
|
1423
|
+
const stat7 = await fsp6.stat(file.path);
|
|
1424
|
+
const mtimeChanged = file.snapshotMtimeMs !== void 0 && stat7.mtimeMs > file.snapshotMtimeMs + 1;
|
|
1425
|
+
const sizeChanged = file.snapshotSizeBytes !== void 0 && stat7.size !== file.snapshotSizeBytes;
|
|
1426
1426
|
if (mtimeChanged || sizeChanged) {
|
|
1427
1427
|
warnings.push(`${file.path} changed after the collab snapshot was captured.`);
|
|
1428
1428
|
}
|
|
@@ -3765,7 +3765,27 @@ Working rules:
|
|
|
3765
3765
|
id: "devops",
|
|
3766
3766
|
name: "DevOps",
|
|
3767
3767
|
role: "devops",
|
|
3768
|
-
tools: [
|
|
3768
|
+
tools: [
|
|
3769
|
+
...TOOLS.build,
|
|
3770
|
+
"mcp__ssh__ssh_list_servers",
|
|
3771
|
+
"mcp__ssh__ssh_connection_status",
|
|
3772
|
+
"mcp__ssh__ssh_execute",
|
|
3773
|
+
"mcp__ssh__ssh_execute_sudo",
|
|
3774
|
+
"mcp__ssh__ssh_upload",
|
|
3775
|
+
"mcp__ssh__ssh_download",
|
|
3776
|
+
"mcp__ssh__ssh_sync",
|
|
3777
|
+
"mcp__ssh__ssh_deploy",
|
|
3778
|
+
"mcp__ssh__ssh_health_check",
|
|
3779
|
+
"mcp__ssh__ssh_service_status",
|
|
3780
|
+
"mcp__ssh__ssh_process_manager",
|
|
3781
|
+
"mcp__ssh__ssh_tunnel",
|
|
3782
|
+
"mcp__ssh__ssh_backup_create",
|
|
3783
|
+
"mcp__ssh__ssh_backup_list",
|
|
3784
|
+
"mcp__ssh__ssh_backup_restore",
|
|
3785
|
+
"mcp__ssh__ssh_db_list",
|
|
3786
|
+
"mcp__ssh__ssh_db_query",
|
|
3787
|
+
"mcp__ssh__ssh_profile"
|
|
3788
|
+
],
|
|
3769
3789
|
prompt: `You are the DevOps agent. Your job is CI/CD, containerization, and
|
|
3770
3790
|
deployment configuration: make builds reproducible and deploys safe.
|
|
3771
3791
|
|
|
@@ -3774,6 +3794,7 @@ Scope:
|
|
|
3774
3794
|
- Write Dockerfiles/compose and optimize image size and layer caching
|
|
3775
3795
|
- Configure deployment (env, secrets handling, health checks, rollback)
|
|
3776
3796
|
- Diagnose flaky/broken pipelines
|
|
3797
|
+
- Use optional SSH MCP tools for remote hosts when the 'ssh' MCP server is enabled: list servers, run health checks, inspect services, transfer/deploy files, and open tunnels
|
|
3777
3798
|
|
|
3778
3799
|
Input format you accept:
|
|
3779
3800
|
{ "task": "ci | container | deploy | fix-pipeline", "platform": "github-actions | gitlab | docker | k8s", "target": "<what>" }
|
|
@@ -3788,7 +3809,8 @@ Working rules:
|
|
|
3788
3809
|
- Never hardcode secrets in config; reference the secret store
|
|
3789
3810
|
- Pin versions for reproducible builds; avoid floating :latest
|
|
3790
3811
|
- Every deploy path needs a rollback and a health check
|
|
3791
|
-
- Treat CI/CD changes as high-risk \u2014 explain blast radius before applying
|
|
3812
|
+
- Treat CI/CD changes as high-risk \u2014 explain blast radius before applying
|
|
3813
|
+
- For remote SSH work, start with ssh_list_servers / ssh_connection_status, prefer read-only checks first, and do not run destructive commands without explicit user approval`
|
|
3792
3814
|
},
|
|
3793
3815
|
budget: MEDIUM_BUDGET,
|
|
3794
3816
|
capability: {
|
|
@@ -3805,6 +3827,13 @@ Working rules:
|
|
|
3805
3827
|
"kubernetes",
|
|
3806
3828
|
"k8s",
|
|
3807
3829
|
"deploy",
|
|
3830
|
+
"ssh",
|
|
3831
|
+
"remote ssh",
|
|
3832
|
+
"remote server",
|
|
3833
|
+
"sftp",
|
|
3834
|
+
"tunnel",
|
|
3835
|
+
"bastion",
|
|
3836
|
+
"jump host",
|
|
3808
3837
|
"github actions",
|
|
3809
3838
|
"container"
|
|
3810
3839
|
]
|
|
@@ -5403,7 +5432,7 @@ var SubagentBudget = class _SubagentBudget {
|
|
|
5403
5432
|
*/
|
|
5404
5433
|
_busRequestDecision(entry) {
|
|
5405
5434
|
const bus = this._events;
|
|
5406
|
-
if (!bus
|
|
5435
|
+
if (!bus?.hasListenerFor("budget.threshold_reached")) {
|
|
5407
5436
|
return Promise.resolve("stop");
|
|
5408
5437
|
}
|
|
5409
5438
|
return new Promise((resolve3) => {
|
|
@@ -6800,6 +6829,7 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
|
|
|
6800
6829
|
subagentId: result.subagentId,
|
|
6801
6830
|
taskId: result.taskId,
|
|
6802
6831
|
status: result.status,
|
|
6832
|
+
result: result.result,
|
|
6803
6833
|
iterations: result.iterations,
|
|
6804
6834
|
toolCalls: result.toolCalls,
|
|
6805
6835
|
durationMs: result.durationMs
|
|
@@ -8733,6 +8763,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
8733
8763
|
* processes. When the limit is reached, the oldest entry is evicted.
|
|
8734
8764
|
*/
|
|
8735
8765
|
_loadCache = /* @__PURE__ */ new Map();
|
|
8766
|
+
_indexCache = null;
|
|
8736
8767
|
static LOAD_CACHE_MAX_ENTRIES = 50;
|
|
8737
8768
|
constructor(opts) {
|
|
8738
8769
|
this.dir = opts.dir;
|
|
@@ -8894,16 +8925,13 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
8894
8925
|
let errorMsg;
|
|
8895
8926
|
let cacheHit = false;
|
|
8896
8927
|
try {
|
|
8897
|
-
|
|
8898
|
-
|
|
8899
|
-
const s = await fsp6.stat(file);
|
|
8900
|
-
stat6 = { mtimeMs: s.mtimeMs, size: s.size };
|
|
8901
|
-
} catch (err) {
|
|
8902
|
-
throw err;
|
|
8903
|
-
}
|
|
8928
|
+
const s = await fsp6.stat(file);
|
|
8929
|
+
const stat7 = { mtimeMs: s.mtimeMs, size: s.size };
|
|
8904
8930
|
const cached = this._loadCache.get(id);
|
|
8905
|
-
if (cached && cached.mtimeMs ===
|
|
8931
|
+
if (cached && cached.mtimeMs === stat7.mtimeMs && cached.size === stat7.size) {
|
|
8906
8932
|
cacheHit = true;
|
|
8933
|
+
this._loadCache.delete(id);
|
|
8934
|
+
this._loadCache.set(id, cached);
|
|
8907
8935
|
return cached.data;
|
|
8908
8936
|
}
|
|
8909
8937
|
const raw = await fsp6.readFile(file, "utf8");
|
|
@@ -8928,7 +8956,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
8928
8956
|
this._loadCache.delete(oldest);
|
|
8929
8957
|
}
|
|
8930
8958
|
}
|
|
8931
|
-
this._loadCache.set(id, { mtimeMs:
|
|
8959
|
+
this._loadCache.set(id, { mtimeMs: stat7.mtimeMs, size: stat7.size, data });
|
|
8932
8960
|
return data;
|
|
8933
8961
|
} catch (err) {
|
|
8934
8962
|
outcome = "failure";
|
|
@@ -8989,6 +9017,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
8989
9017
|
await ensureDir(this.dir);
|
|
8990
9018
|
const line = JSON.stringify(summary) + "\n";
|
|
8991
9019
|
await fsp6.appendFile(this.indexFile, line, "utf8");
|
|
9020
|
+
this._indexCache = null;
|
|
8992
9021
|
this.indexAppendCount++;
|
|
8993
9022
|
if (this.indexAppendCount >= _DefaultSessionStore.COMPACT_EVERY) {
|
|
8994
9023
|
await this.compactIndex();
|
|
@@ -9003,6 +9032,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
9003
9032
|
await ensureDir(this.dir);
|
|
9004
9033
|
const line = JSON.stringify({ action: "delete", id }) + "\n";
|
|
9005
9034
|
await fsp6.appendFile(this.indexFile, line, "utf8");
|
|
9035
|
+
this._indexCache = null;
|
|
9006
9036
|
this.indexAppendCount++;
|
|
9007
9037
|
} catch {
|
|
9008
9038
|
}
|
|
@@ -9022,6 +9052,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
9022
9052
|
const lines = entries.map((s) => JSON.stringify(s)).join("\n") + "\n";
|
|
9023
9053
|
await fsp6.writeFile(tmp, lines, "utf8");
|
|
9024
9054
|
await fsp6.rename(tmp, this.indexFile);
|
|
9055
|
+
this._indexCache = null;
|
|
9025
9056
|
} catch (err) {
|
|
9026
9057
|
outcome = "failure";
|
|
9027
9058
|
errorMsg = toErrorMessage(err);
|
|
@@ -9035,10 +9066,22 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
9035
9066
|
* Returns empty array when the index doesn't exist or is corrupt.
|
|
9036
9067
|
*/
|
|
9037
9068
|
async readIndex() {
|
|
9069
|
+
let stat7;
|
|
9070
|
+
try {
|
|
9071
|
+
const s = await fsp6.stat(this.indexFile);
|
|
9072
|
+
stat7 = { mtimeMs: s.mtimeMs, size: s.size };
|
|
9073
|
+
} catch {
|
|
9074
|
+
this._indexCache = null;
|
|
9075
|
+
return [];
|
|
9076
|
+
}
|
|
9077
|
+
if (this._indexCache !== null && this._indexCache.mtimeMs === stat7.mtimeMs && this._indexCache.size === stat7.size) {
|
|
9078
|
+
return [...this._indexCache.summaries];
|
|
9079
|
+
}
|
|
9038
9080
|
let raw;
|
|
9039
9081
|
try {
|
|
9040
9082
|
raw = await fsp6.readFile(this.indexFile, "utf8");
|
|
9041
9083
|
} catch {
|
|
9084
|
+
this._indexCache = null;
|
|
9042
9085
|
return [];
|
|
9043
9086
|
}
|
|
9044
9087
|
const deleted = /* @__PURE__ */ new Set();
|
|
@@ -9058,7 +9101,9 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
9058
9101
|
} catch {
|
|
9059
9102
|
}
|
|
9060
9103
|
}
|
|
9061
|
-
|
|
9104
|
+
const summaries = Array.from(seen.values());
|
|
9105
|
+
this._indexCache = { ...stat7, summaries };
|
|
9106
|
+
return [...summaries];
|
|
9062
9107
|
}
|
|
9063
9108
|
/**
|
|
9064
9109
|
* Rebuild the index from disk by scanning all sessions and writing a
|
|
@@ -9072,6 +9117,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
9072
9117
|
const lines = valid.map((s) => JSON.stringify(s)).join("\n") + "\n";
|
|
9073
9118
|
await fsp6.writeFile(tmp, lines, "utf8");
|
|
9074
9119
|
await fsp6.rename(tmp, this.indexFile);
|
|
9120
|
+
this._indexCache = null;
|
|
9075
9121
|
return valid.length;
|
|
9076
9122
|
}
|
|
9077
9123
|
/** Recursively collect session IDs from date-shard subdirectories.
|
|
@@ -9112,8 +9158,8 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
9112
9158
|
return JSON.parse(raw);
|
|
9113
9159
|
} catch {
|
|
9114
9160
|
const full = this.sessionPath(id, ".jsonl");
|
|
9115
|
-
const
|
|
9116
|
-
const summary = await this.summarize(id,
|
|
9161
|
+
const stat7 = await fsp6.stat(full);
|
|
9162
|
+
const summary = await this.summarize(id, stat7.mtime.toISOString());
|
|
9117
9163
|
await atomicWrite(manifest, JSON.stringify(summary), { mode: 384 }).catch((err) => {
|
|
9118
9164
|
const msg = toErrorMessage(err);
|
|
9119
9165
|
this.emitError(id, manifest, "summary_fallback", msg, true);
|
|
@@ -9195,8 +9241,8 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
9195
9241
|
const pruneFile = async (dir, name, prefix) => {
|
|
9196
9242
|
const jsonlPath = path5.join(dir, name);
|
|
9197
9243
|
try {
|
|
9198
|
-
const
|
|
9199
|
-
if (
|
|
9244
|
+
const stat7 = await fsp6.stat(jsonlPath);
|
|
9245
|
+
if (stat7.mtimeMs >= cutoff) return;
|
|
9200
9246
|
} catch {
|
|
9201
9247
|
return;
|
|
9202
9248
|
}
|
|
@@ -9771,6 +9817,12 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
9771
9817
|
files
|
|
9772
9818
|
});
|
|
9773
9819
|
}
|
|
9820
|
+
/**
|
|
9821
|
+
* Truncate the session file to the checkpoint with the given promptIndex,
|
|
9822
|
+
* removing all events that follow it. Uses a single-pass byte-offset scan
|
|
9823
|
+
* so post-checkpoint content is never read or parsed — O(1) memory instead
|
|
9824
|
+
* of O(N) JSON.parse calls over the full file.
|
|
9825
|
+
*/
|
|
9774
9826
|
async truncateToCheckpoint(targetPromptIndex) {
|
|
9775
9827
|
if (!this.filePath) return 0;
|
|
9776
9828
|
if (this.flushTimer) {
|
|
@@ -9779,51 +9831,118 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
9779
9831
|
}
|
|
9780
9832
|
await this.flushBuffer();
|
|
9781
9833
|
await this.writeChain;
|
|
9782
|
-
const
|
|
9783
|
-
|
|
9784
|
-
|
|
9834
|
+
const CHUNK_SIZE = 65536;
|
|
9835
|
+
let fd;
|
|
9836
|
+
let fileOffset = 0;
|
|
9837
|
+
let lineStartOffset = 0;
|
|
9838
|
+
let checkpointByteOffset = -1;
|
|
9785
9839
|
let removedCount = 0;
|
|
9786
|
-
let
|
|
9787
|
-
|
|
9788
|
-
|
|
9789
|
-
|
|
9790
|
-
|
|
9791
|
-
|
|
9792
|
-
|
|
9793
|
-
|
|
9794
|
-
|
|
9795
|
-
|
|
9796
|
-
|
|
9797
|
-
|
|
9798
|
-
|
|
9799
|
-
|
|
9800
|
-
|
|
9801
|
-
|
|
9802
|
-
|
|
9803
|
-
|
|
9840
|
+
let targetCheckpointSeen = false;
|
|
9841
|
+
try {
|
|
9842
|
+
fd = await fsp6.open(this.filePath, "r", 384);
|
|
9843
|
+
while (true) {
|
|
9844
|
+
const buf = Buffer.alloc(CHUNK_SIZE);
|
|
9845
|
+
const { bytesRead } = await fd.read(buf, 0, CHUNK_SIZE, fileOffset);
|
|
9846
|
+
if (bytesRead === 0) break;
|
|
9847
|
+
let chunkPos = 0;
|
|
9848
|
+
while (chunkPos < bytesRead) {
|
|
9849
|
+
const idx = buf.indexOf("\n", chunkPos);
|
|
9850
|
+
if (idx === -1) {
|
|
9851
|
+
lineStartOffset = fileOffset + chunkPos;
|
|
9852
|
+
break;
|
|
9853
|
+
}
|
|
9854
|
+
if (checkpointByteOffset !== -1) {
|
|
9855
|
+
removedCount++;
|
|
9856
|
+
} else {
|
|
9857
|
+
const lineBytes = buf.subarray(chunkPos, idx);
|
|
9858
|
+
const line = new TextDecoder("utf-8", { fatal: false }).decode(lineBytes);
|
|
9859
|
+
if (line.trim()) {
|
|
9860
|
+
try {
|
|
9861
|
+
const event = JSON.parse(line);
|
|
9862
|
+
if (event.type === "checkpoint") {
|
|
9863
|
+
if (event.promptIndex === targetPromptIndex) {
|
|
9864
|
+
checkpointByteOffset = lineStartOffset;
|
|
9865
|
+
targetCheckpointSeen = true;
|
|
9866
|
+
} else if (event.promptIndex !== void 0 && event.promptIndex > targetPromptIndex) {
|
|
9867
|
+
checkpointByteOffset = lineStartOffset;
|
|
9868
|
+
}
|
|
9869
|
+
} else if (targetCheckpointSeen && event.promptIndex !== void 0 && event.promptIndex > targetPromptIndex) {
|
|
9870
|
+
removedCount++;
|
|
9871
|
+
} else if (targetCheckpointSeen && event.promptIndex === void 0) {
|
|
9872
|
+
removedCount++;
|
|
9873
|
+
} else if (!targetCheckpointSeen && event.promptIndex === void 0) {
|
|
9874
|
+
removedCount++;
|
|
9875
|
+
} else if (!targetCheckpointSeen && event.promptIndex !== void 0 && event.promptIndex > targetPromptIndex) {
|
|
9876
|
+
removedCount++;
|
|
9877
|
+
}
|
|
9878
|
+
} catch {
|
|
9879
|
+
}
|
|
9880
|
+
}
|
|
9881
|
+
}
|
|
9882
|
+
chunkPos = idx + 1;
|
|
9883
|
+
lineStartOffset = fileOffset + chunkPos;
|
|
9804
9884
|
}
|
|
9805
|
-
|
|
9806
|
-
|
|
9807
|
-
|
|
9808
|
-
} else if (event.promptIndex === void 0) {
|
|
9809
|
-
if (!afterTarget || targetCheckpointLine === -1) {
|
|
9810
|
-
kept.push(line);
|
|
9811
|
-
} else {
|
|
9812
|
-
removedCount++;
|
|
9885
|
+
fileOffset += bytesRead;
|
|
9886
|
+
if (chunkPos >= bytesRead) {
|
|
9887
|
+
lineStartOffset = fileOffset;
|
|
9813
9888
|
}
|
|
9814
|
-
} else {
|
|
9815
|
-
kept.push(line);
|
|
9816
9889
|
}
|
|
9890
|
+
} finally {
|
|
9891
|
+
await fd?.close();
|
|
9817
9892
|
}
|
|
9818
|
-
|
|
9893
|
+
if (checkpointByteOffset === -1) return 0;
|
|
9894
|
+
await this.writeChain;
|
|
9895
|
+
await this.handle.close();
|
|
9819
9896
|
const tmpPath = `${this.filePath}.rewind.tmp`;
|
|
9820
|
-
await fsp6.
|
|
9897
|
+
const src = await fsp6.open(this.filePath, "r", 384);
|
|
9821
9898
|
try {
|
|
9822
|
-
await
|
|
9899
|
+
const statResult = await src.stat();
|
|
9900
|
+
const totalSize = statResult.size;
|
|
9901
|
+
const prefixBytes = checkpointByteOffset;
|
|
9902
|
+
let newlineAfterCheckpoint = prefixBytes;
|
|
9903
|
+
if (prefixBytes < totalSize) {
|
|
9904
|
+
const probeBuf = Buffer.alloc(Math.min(CHUNK_SIZE, totalSize - prefixBytes));
|
|
9905
|
+
const { bytesRead: probeRead } = await src.read(probeBuf, 0, probeBuf.length, prefixBytes);
|
|
9906
|
+
if (probeRead > 0) {
|
|
9907
|
+
const nl = probeBuf.indexOf("\n");
|
|
9908
|
+
newlineAfterCheckpoint = nl !== -1 ? prefixBytes + nl + 1 : totalSize;
|
|
9909
|
+
}
|
|
9910
|
+
} else {
|
|
9911
|
+
newlineAfterCheckpoint = totalSize;
|
|
9912
|
+
}
|
|
9913
|
+
const writeFd = await fsp6.open(tmpPath, "w", 384);
|
|
9914
|
+
try {
|
|
9915
|
+
let copied = 0;
|
|
9916
|
+
let readOffset = 0;
|
|
9917
|
+
while (readOffset < newlineAfterCheckpoint) {
|
|
9918
|
+
const toCopy = Math.min(CHUNK_SIZE, newlineAfterCheckpoint - readOffset);
|
|
9919
|
+
const copyBuf = Buffer.alloc(toCopy);
|
|
9920
|
+
const { bytesRead: r } = await src.read(copyBuf, 0, toCopy, readOffset);
|
|
9921
|
+
if (r === 0) break;
|
|
9922
|
+
await writeFd.write(copyBuf, 0, r);
|
|
9923
|
+
readOffset += r;
|
|
9924
|
+
copied += r;
|
|
9925
|
+
}
|
|
9926
|
+
const raw = await fsp6.readFile(this.filePath);
|
|
9927
|
+
const tail = raw.subarray(newlineAfterCheckpoint).toString("utf8");
|
|
9928
|
+
for (const line of tail.split("\n")) {
|
|
9929
|
+
if (!line.trim()) continue;
|
|
9930
|
+
try {
|
|
9931
|
+
JSON.parse(line);
|
|
9932
|
+
} catch {
|
|
9933
|
+
await writeFd.write(`${line}
|
|
9934
|
+
`, void 0, "utf8");
|
|
9935
|
+
}
|
|
9936
|
+
}
|
|
9937
|
+
} finally {
|
|
9938
|
+
await writeFd.close();
|
|
9939
|
+
}
|
|
9940
|
+
await src.close();
|
|
9823
9941
|
await fsp6.rename(tmpPath, this.filePath);
|
|
9824
9942
|
this.handle = await fsp6.open(this.filePath, "a", 384);
|
|
9825
9943
|
} catch (err) {
|
|
9826
9944
|
await fsp6.unlink(tmpPath).catch(() => void 0);
|
|
9945
|
+
this.handle = await fsp6.open(this.filePath, "a", 384).catch(() => this.handle);
|
|
9827
9946
|
throw err;
|
|
9828
9947
|
}
|
|
9829
9948
|
await this.append({
|
|
@@ -10362,8 +10481,12 @@ function normalizeRecipient(to) {
|
|
|
10362
10481
|
// src/coordination/mailbox.ts
|
|
10363
10482
|
var MAILBOX_FILE = "_mailbox.jsonl";
|
|
10364
10483
|
var LINE_SEPARATOR = "\n";
|
|
10484
|
+
var MESSAGE_CACHE_MAX_ENTRIES = 1e4;
|
|
10365
10485
|
var DefaultMailbox = class {
|
|
10366
10486
|
filePath;
|
|
10487
|
+
_messageCache = null;
|
|
10488
|
+
_messageCacheMtime = -1;
|
|
10489
|
+
_messageCacheSize = -1;
|
|
10367
10490
|
constructor(sessionDir) {
|
|
10368
10491
|
this.filePath = path5.join(sessionDir, MAILBOX_FILE);
|
|
10369
10492
|
}
|
|
@@ -10393,37 +10516,26 @@ var DefaultMailbox = class {
|
|
|
10393
10516
|
await fsp6.mkdir(path5.dirname(this.filePath), { recursive: true });
|
|
10394
10517
|
await withFileLock(this.filePath, async () => {
|
|
10395
10518
|
await fsp6.appendFile(this.filePath, line, "utf8");
|
|
10519
|
+
this._pushToCache(msg);
|
|
10396
10520
|
});
|
|
10397
10521
|
return msg;
|
|
10398
10522
|
}
|
|
10399
10523
|
// ── Query ─────────────────────────────────────────────────────────────
|
|
10400
10524
|
async query(q) {
|
|
10401
|
-
const all = await this.
|
|
10525
|
+
const all = await this._readAllCached();
|
|
10402
10526
|
const limit = q.limit ?? 50;
|
|
10403
|
-
|
|
10404
|
-
|
|
10405
|
-
|
|
10406
|
-
|
|
10407
|
-
|
|
10408
|
-
|
|
10409
|
-
|
|
10410
|
-
|
|
10411
|
-
|
|
10412
|
-
|
|
10413
|
-
|
|
10414
|
-
filtered
|
|
10415
|
-
}
|
|
10416
|
-
if (q.type !== void 0) {
|
|
10417
|
-
filtered = filtered.filter((m) => m.type === q.type);
|
|
10418
|
-
}
|
|
10419
|
-
if (q.minPriority !== void 0) {
|
|
10420
|
-
const order = { low: 0, normal: 1, high: 2 };
|
|
10421
|
-
const min = order[q.minPriority];
|
|
10422
|
-
filtered = filtered.filter((m) => (order[m.priority] ?? 1) >= min);
|
|
10423
|
-
}
|
|
10424
|
-
if (q.since !== void 0) {
|
|
10425
|
-
const since = q.since;
|
|
10426
|
-
filtered = filtered.filter((m) => m.timestamp > since);
|
|
10527
|
+
const order = q.minPriority !== void 0 ? { low: 0, normal: 1, high: 2 } : null;
|
|
10528
|
+
const minPriorityRank = order && q.minPriority !== void 0 ? order[q.minPriority] : 0;
|
|
10529
|
+
const filtered = [];
|
|
10530
|
+
for (const msg of all) {
|
|
10531
|
+
if (q.to !== void 0 && msg.to !== q.to && msg.to !== "*") continue;
|
|
10532
|
+
if (q.from !== void 0 && msg.from !== q.from) continue;
|
|
10533
|
+
if (q.unreadBy !== void 0 && q.unreadBy in msg.readBy) continue;
|
|
10534
|
+
if (q.incompleteOnly && msg.completed) continue;
|
|
10535
|
+
if (q.type !== void 0 && msg.type !== q.type) continue;
|
|
10536
|
+
if (order !== null && (order[msg.priority] ?? 1) < minPriorityRank) continue;
|
|
10537
|
+
if (q.since !== void 0 && msg.timestamp <= q.since) continue;
|
|
10538
|
+
filtered.push(msg);
|
|
10427
10539
|
}
|
|
10428
10540
|
filtered.sort((a, b) => b.timestamp.localeCompare(a.timestamp));
|
|
10429
10541
|
return filtered.slice(0, limit);
|
|
@@ -10465,12 +10577,13 @@ var DefaultMailbox = class {
|
|
|
10465
10577
|
const serialized = all.map((m) => JSON.stringify(m)).join(LINE_SEPARATOR) + LINE_SEPARATOR;
|
|
10466
10578
|
await fsp6.writeFile(this.filePath, serialized, "utf8");
|
|
10467
10579
|
}
|
|
10580
|
+
this._setMessageCache(all);
|
|
10468
10581
|
});
|
|
10469
10582
|
return updated;
|
|
10470
10583
|
}
|
|
10471
10584
|
// ── Agent statuses ────────────────────────────────────────────────────
|
|
10472
10585
|
async getAgentStatuses() {
|
|
10473
|
-
const all = await this.
|
|
10586
|
+
const all = await this._readAllCached();
|
|
10474
10587
|
const latest = /* @__PURE__ */ new Map();
|
|
10475
10588
|
for (const m of all) {
|
|
10476
10589
|
if (m.type !== "status") continue;
|
|
@@ -10506,16 +10619,20 @@ var DefaultMailbox = class {
|
|
|
10506
10619
|
async heartbeat(_input) {
|
|
10507
10620
|
}
|
|
10508
10621
|
async unreadCount(forAgentId) {
|
|
10509
|
-
const all = await this.
|
|
10622
|
+
const all = await this._readAllCached();
|
|
10510
10623
|
return all.filter(
|
|
10511
10624
|
(m) => (m.to === forAgentId || m.to === "*") && !(forAgentId in m.readBy) && !m.completed
|
|
10512
10625
|
).length;
|
|
10513
10626
|
}
|
|
10514
10627
|
async close() {
|
|
10628
|
+
this._messageCache = null;
|
|
10629
|
+
this._messageCacheMtime = -1;
|
|
10630
|
+
this._messageCacheSize = -1;
|
|
10515
10631
|
}
|
|
10516
10632
|
async clearAll() {
|
|
10517
10633
|
await withFileLock(this.filePath, async () => {
|
|
10518
10634
|
await fsp6.writeFile(this.filePath, "", "utf8");
|
|
10635
|
+
this._setMessageCache([]);
|
|
10519
10636
|
});
|
|
10520
10637
|
}
|
|
10521
10638
|
async purgeStale(opts) {
|
|
@@ -10523,13 +10640,14 @@ var DefaultMailbox = class {
|
|
|
10523
10640
|
const INCOMPLETE_MAX_AGE_MS = opts?.incompleteMaxAgeMs ?? 6048e5;
|
|
10524
10641
|
let completedPurged = 0;
|
|
10525
10642
|
let incompletePurged = 0;
|
|
10643
|
+
let remaining = 0;
|
|
10526
10644
|
await withFileLock(this.filePath, async () => {
|
|
10527
|
-
const
|
|
10645
|
+
const all = await this._readAll();
|
|
10528
10646
|
const now = Date.now();
|
|
10529
10647
|
const cutoffCompleted = now - COMPLETED_MAX_AGE_MS;
|
|
10530
10648
|
const cutoffIncomplete = now - INCOMPLETE_MAX_AGE_MS;
|
|
10531
10649
|
const kept = [];
|
|
10532
|
-
for (const msg of
|
|
10650
|
+
for (const msg of all) {
|
|
10533
10651
|
const msgTime = new Date(msg.timestamp).getTime();
|
|
10534
10652
|
const completedTime = msg.completedAt ? new Date(msg.completedAt).getTime() : 0;
|
|
10535
10653
|
if (msg.completed && completedTime < cutoffCompleted) {
|
|
@@ -10542,17 +10660,18 @@ var DefaultMailbox = class {
|
|
|
10542
10660
|
}
|
|
10543
10661
|
kept.push(msg);
|
|
10544
10662
|
}
|
|
10545
|
-
|
|
10663
|
+
remaining = kept.length;
|
|
10664
|
+
if (kept.length < all.length) {
|
|
10546
10665
|
const content = kept.map((m) => JSON.stringify(m)).join(LINE_SEPARATOR) + LINE_SEPARATOR;
|
|
10547
10666
|
await fsp6.writeFile(this.filePath, content, "utf8");
|
|
10548
10667
|
}
|
|
10668
|
+
this._setMessageCache(kept);
|
|
10549
10669
|
});
|
|
10550
|
-
const all = await this._readAll();
|
|
10551
10670
|
return {
|
|
10552
10671
|
completedPurged,
|
|
10553
10672
|
incompletePurged,
|
|
10554
10673
|
totalPurged: completedPurged + incompletePurged,
|
|
10555
|
-
remaining
|
|
10674
|
+
remaining
|
|
10556
10675
|
};
|
|
10557
10676
|
}
|
|
10558
10677
|
// ── Client registry stubs (not applicable per-session) ─────────────────
|
|
@@ -10591,6 +10710,52 @@ var DefaultMailbox = class {
|
|
|
10591
10710
|
throw err;
|
|
10592
10711
|
}
|
|
10593
10712
|
}
|
|
10713
|
+
async _readAllCached() {
|
|
10714
|
+
try {
|
|
10715
|
+
const st = await fsp6.stat(this.filePath);
|
|
10716
|
+
if (this._messageCache !== null && this._messageCacheMtime === st.mtimeMs && this._messageCacheSize === st.size) {
|
|
10717
|
+
return this._messageCache;
|
|
10718
|
+
}
|
|
10719
|
+
const all = await this._readAll();
|
|
10720
|
+
this._setMessageCache(all, st.mtimeMs, st.size);
|
|
10721
|
+
return all;
|
|
10722
|
+
} catch (err) {
|
|
10723
|
+
if (err.code === "ENOENT") {
|
|
10724
|
+
this._setMessageCache([], -1, -1);
|
|
10725
|
+
return [];
|
|
10726
|
+
}
|
|
10727
|
+
throw err;
|
|
10728
|
+
}
|
|
10729
|
+
}
|
|
10730
|
+
_setMessageCache(messages, mtime, size) {
|
|
10731
|
+
if (messages.length > MESSAGE_CACHE_MAX_ENTRIES) {
|
|
10732
|
+
this._messageCache = null;
|
|
10733
|
+
this._messageCacheMtime = -1;
|
|
10734
|
+
this._messageCacheSize = -1;
|
|
10735
|
+
return;
|
|
10736
|
+
}
|
|
10737
|
+
this._messageCache = messages;
|
|
10738
|
+
if (mtime !== void 0 && size !== void 0) {
|
|
10739
|
+
this._messageCacheMtime = mtime;
|
|
10740
|
+
this._messageCacheSize = size;
|
|
10741
|
+
return;
|
|
10742
|
+
}
|
|
10743
|
+
void fsp6.stat(this.filePath).then((st) => {
|
|
10744
|
+
this._messageCacheMtime = st.mtimeMs;
|
|
10745
|
+
this._messageCacheSize = st.size;
|
|
10746
|
+
}).catch(() => {
|
|
10747
|
+
});
|
|
10748
|
+
}
|
|
10749
|
+
_pushToCache(msg) {
|
|
10750
|
+
if (this._messageCache === null) return;
|
|
10751
|
+
if (this._messageCache.length >= MESSAGE_CACHE_MAX_ENTRIES) {
|
|
10752
|
+
this._messageCache = null;
|
|
10753
|
+
this._messageCacheMtime = -1;
|
|
10754
|
+
this._messageCacheSize = -1;
|
|
10755
|
+
return;
|
|
10756
|
+
}
|
|
10757
|
+
this._messageCache.push(msg);
|
|
10758
|
+
}
|
|
10594
10759
|
};
|
|
10595
10760
|
var BrainMonitor = class {
|
|
10596
10761
|
constructor(opts) {
|
|
@@ -10731,7 +10896,7 @@ var CLIENT_STALE_MS = 6e4;
|
|
|
10731
10896
|
var HEARTBEAT_THROTTLE_MS = 5e3;
|
|
10732
10897
|
var REGISTRY_CACHE_TTL_MS = 2e3;
|
|
10733
10898
|
var LINE_SEPARATOR2 = "\n";
|
|
10734
|
-
var
|
|
10899
|
+
var MESSAGE_CACHE_MAX_ENTRIES2 = 1e4;
|
|
10735
10900
|
function resolveProjectDir(projectRoot, globalRoot) {
|
|
10736
10901
|
return path5.join(globalRoot, "projects", projectSlug(projectRoot));
|
|
10737
10902
|
}
|
|
@@ -10744,6 +10909,8 @@ var GlobalMailbox = class {
|
|
|
10744
10909
|
clientRegistryPath;
|
|
10745
10910
|
/** Optional event bus for emitting agent registration/heartbeat events. */
|
|
10746
10911
|
_events;
|
|
10912
|
+
/** Optional HQ publisher for cross-project command-center telemetry. */
|
|
10913
|
+
_hqPublisher;
|
|
10747
10914
|
/**
|
|
10748
10915
|
* Local cache of the agent registry to avoid re-reading on every call.
|
|
10749
10916
|
* Time-bounded: the registry file is shared ACROSS PROCESSES (that's the
|
|
@@ -10781,12 +10948,28 @@ var GlobalMailbox = class {
|
|
|
10781
10948
|
/**
|
|
10782
10949
|
* @param projectDir — `~/.wrongstack/projects/<slug>/`
|
|
10783
10950
|
* @param events — optional EventBus for real-time TUI/WebUI notifications
|
|
10951
|
+
* @param hqPublisher — optional HQ publisher for cross-project telemetry
|
|
10784
10952
|
*/
|
|
10785
|
-
constructor(projectDir, events) {
|
|
10953
|
+
constructor(projectDir, events, hqPublisher) {
|
|
10786
10954
|
this.messagePath = path5.join(projectDir, MAILBOX_FILE2);
|
|
10787
10955
|
this.registryPath = path5.join(projectDir, "_mailbox.registry.json");
|
|
10788
10956
|
this.clientRegistryPath = path5.join(projectDir, CLIENT_REGISTRY_FILE);
|
|
10789
10957
|
this._events = events;
|
|
10958
|
+
this._hqPublisher = hqPublisher;
|
|
10959
|
+
}
|
|
10960
|
+
get hqMailboxId() {
|
|
10961
|
+
return `${path5.basename(path5.dirname(this.messagePath))}:mailbox`;
|
|
10962
|
+
}
|
|
10963
|
+
publishHqMailboxEvent(input) {
|
|
10964
|
+
try {
|
|
10965
|
+
this._hqPublisher?.publishMailboxEvent(input);
|
|
10966
|
+
} catch {
|
|
10967
|
+
}
|
|
10968
|
+
}
|
|
10969
|
+
publishHqMailboxSnapshot() {
|
|
10970
|
+
if (this._hqPublisher === void 0) return;
|
|
10971
|
+
void this._hqPublisher.publishMailboxSnapshot(this, { mailboxId: this.hqMailboxId }).catch(() => {
|
|
10972
|
+
});
|
|
10790
10973
|
}
|
|
10791
10974
|
// ── Messages ────────────────────────────────────────────────────────────
|
|
10792
10975
|
async send(input) {
|
|
@@ -10813,6 +10996,8 @@ var GlobalMailbox = class {
|
|
|
10813
10996
|
await fsp6.appendFile(this.messagePath, line, "utf8");
|
|
10814
10997
|
this._pushToCache(msg);
|
|
10815
10998
|
});
|
|
10999
|
+
this.publishHqMailboxEvent({ mailboxId: this.hqMailboxId, action: "message.sent", message: msg });
|
|
11000
|
+
this.publishHqMailboxSnapshot();
|
|
10816
11001
|
return msg;
|
|
10817
11002
|
}
|
|
10818
11003
|
async query(q) {
|
|
@@ -10879,6 +11064,14 @@ var GlobalMailbox = class {
|
|
|
10879
11064
|
cacheSnapshot = all;
|
|
10880
11065
|
});
|
|
10881
11066
|
if (cacheSnapshot) this._setMessageCache(cacheSnapshot);
|
|
11067
|
+
for (const message of updated) {
|
|
11068
|
+
this.publishHqMailboxEvent({
|
|
11069
|
+
mailboxId: this.hqMailboxId,
|
|
11070
|
+
action: message.completed ? "message.completed" : "message.read",
|
|
11071
|
+
message
|
|
11072
|
+
});
|
|
11073
|
+
}
|
|
11074
|
+
if (updated.length > 0) this.publishHqMailboxSnapshot();
|
|
10882
11075
|
return updated;
|
|
10883
11076
|
}
|
|
10884
11077
|
async unreadCount(forAgentId) {
|
|
@@ -10926,6 +11119,25 @@ var GlobalMailbox = class {
|
|
|
10926
11119
|
role: input.role,
|
|
10927
11120
|
source: input.source
|
|
10928
11121
|
});
|
|
11122
|
+
this.publishHqMailboxEvent({
|
|
11123
|
+
mailboxId: this.hqMailboxId,
|
|
11124
|
+
action: "agent.registered",
|
|
11125
|
+
agent: {
|
|
11126
|
+
agentId: input.agentId,
|
|
11127
|
+
name: input.name,
|
|
11128
|
+
...input.role !== void 0 ? { role: input.role } : {},
|
|
11129
|
+
sessionId: input.sessionId,
|
|
11130
|
+
status: "idle",
|
|
11131
|
+
iterations: 0,
|
|
11132
|
+
toolCalls: 0,
|
|
11133
|
+
lastActivityAt: now,
|
|
11134
|
+
lastSeenAt: now,
|
|
11135
|
+
online: true,
|
|
11136
|
+
pid: input.pid,
|
|
11137
|
+
...input.source !== void 0 ? { source: input.source } : {}
|
|
11138
|
+
}
|
|
11139
|
+
});
|
|
11140
|
+
this.publishHqMailboxSnapshot();
|
|
10929
11141
|
}
|
|
10930
11142
|
async heartbeat(input) {
|
|
10931
11143
|
const last = this._lastHeartbeat.get(input.agentId) ?? 0;
|
|
@@ -10956,6 +11168,12 @@ var GlobalMailbox = class {
|
|
|
10956
11168
|
currentTool: input.currentTool,
|
|
10957
11169
|
currentTask: input.currentTask
|
|
10958
11170
|
});
|
|
11171
|
+
this.publishHqMailboxEvent({
|
|
11172
|
+
mailboxId: this.hqMailboxId,
|
|
11173
|
+
action: "agent.heartbeat",
|
|
11174
|
+
summary: input.agentId
|
|
11175
|
+
});
|
|
11176
|
+
this.publishHqMailboxSnapshot();
|
|
10959
11177
|
}
|
|
10960
11178
|
async getAgentStatuses() {
|
|
10961
11179
|
await this._ensureRegistry();
|
|
@@ -11010,6 +11228,7 @@ var GlobalMailbox = class {
|
|
|
11010
11228
|
name: input.name,
|
|
11011
11229
|
source: input.source
|
|
11012
11230
|
});
|
|
11231
|
+
this.publishHqMailboxSnapshot();
|
|
11013
11232
|
}
|
|
11014
11233
|
async clientHeartbeat(input) {
|
|
11015
11234
|
const last = this._lastClientHeartbeat.get(input.clientId) ?? 0;
|
|
@@ -11031,6 +11250,7 @@ var GlobalMailbox = class {
|
|
|
11031
11250
|
this._events?.emitCustom("mailbox.client_heartbeat", {
|
|
11032
11251
|
clientId: input.clientId
|
|
11033
11252
|
});
|
|
11253
|
+
this.publishHqMailboxSnapshot();
|
|
11034
11254
|
}
|
|
11035
11255
|
async getClientStatuses() {
|
|
11036
11256
|
await this._ensureClientRegistry();
|
|
@@ -11177,7 +11397,7 @@ var GlobalMailbox = class {
|
|
|
11177
11397
|
* read or wrote it under the file lock).
|
|
11178
11398
|
*/
|
|
11179
11399
|
_setMessageCache(messages, mtime, size) {
|
|
11180
|
-
if (messages.length >
|
|
11400
|
+
if (messages.length > MESSAGE_CACHE_MAX_ENTRIES2) {
|
|
11181
11401
|
this._messageCache = null;
|
|
11182
11402
|
this._messageCacheMtime = -1;
|
|
11183
11403
|
this._messageCacheSize = -1;
|
|
@@ -11203,7 +11423,7 @@ var GlobalMailbox = class {
|
|
|
11203
11423
|
*/
|
|
11204
11424
|
_pushToCache(msg) {
|
|
11205
11425
|
if (this._messageCache === null) return;
|
|
11206
|
-
if (this._messageCache.length >=
|
|
11426
|
+
if (this._messageCache.length >= MESSAGE_CACHE_MAX_ENTRIES2) {
|
|
11207
11427
|
this._messageCache = null;
|
|
11208
11428
|
this._messageCacheMtime = -1;
|
|
11209
11429
|
this._messageCacheSize = -1;
|
|
@@ -11252,7 +11472,7 @@ var GlobalMailbox = class {
|
|
|
11252
11472
|
obj[id] = agent;
|
|
11253
11473
|
}
|
|
11254
11474
|
const tmp = `${this.registryPath}.${randomUUID().slice(0, 8)}.tmp`;
|
|
11255
|
-
await fsp6.writeFile(tmp, JSON.stringify(obj
|
|
11475
|
+
await fsp6.writeFile(tmp, JSON.stringify(obj), "utf8");
|
|
11256
11476
|
await fsp6.rename(tmp, this.registryPath);
|
|
11257
11477
|
}
|
|
11258
11478
|
// ── Client registry internals ───────────────────────────────────────────
|
|
@@ -11297,7 +11517,7 @@ var GlobalMailbox = class {
|
|
|
11297
11517
|
obj[id] = client;
|
|
11298
11518
|
}
|
|
11299
11519
|
const tmp = `${this.clientRegistryPath}.${randomUUID().slice(0, 8)}.tmp`;
|
|
11300
|
-
await fsp6.writeFile(tmp, JSON.stringify(obj
|
|
11520
|
+
await fsp6.writeFile(tmp, JSON.stringify(obj), "utf8");
|
|
11301
11521
|
await fsp6.rename(tmp, this.clientRegistryPath);
|
|
11302
11522
|
}
|
|
11303
11523
|
};
|
|
@@ -11768,13 +11988,13 @@ function makeDependencyWatcherConfig(opts) {
|
|
|
11768
11988
|
const globPatterns = patterns.filter((p) => p.includes("*"));
|
|
11769
11989
|
const plainPatterns = patterns.filter((p) => !p.includes("*"));
|
|
11770
11990
|
function matchesPattern(filePath) {
|
|
11771
|
-
const
|
|
11772
|
-
if (plainPatterns.includes(
|
|
11991
|
+
const basename6 = filePath.split("/").pop()?.split("\\").pop() ?? "";
|
|
11992
|
+
if (plainPatterns.includes(basename6)) return true;
|
|
11773
11993
|
for (const gp of globPatterns) {
|
|
11774
11994
|
const regex = new RegExp(
|
|
11775
11995
|
"^" + gp.replace(/\./g, "\\.").replace(/\*/g, ".*") + "$"
|
|
11776
11996
|
);
|
|
11777
|
-
if (regex.test(
|
|
11997
|
+
if (regex.test(basename6)) return true;
|
|
11778
11998
|
}
|
|
11779
11999
|
return false;
|
|
11780
12000
|
}
|
|
@@ -12490,7 +12710,7 @@ var TaskDAG = class {
|
|
|
12490
12710
|
}
|
|
12491
12711
|
for (const depId of node.dependents) {
|
|
12492
12712
|
const dep = this.nodes.get(depId);
|
|
12493
|
-
if (dep
|
|
12713
|
+
if (dep?.deps.every((d) => !this.nodes.has(d) || this.nodes.get(d).status === "done")) {
|
|
12494
12714
|
this._transition(depId, "pending", "ready");
|
|
12495
12715
|
}
|
|
12496
12716
|
}
|
|
@@ -12750,12 +12970,12 @@ var ConsensusProtocol = class {
|
|
|
12750
12970
|
* Initiate a vote on a proposed change. Updates the change node's status
|
|
12751
12971
|
* to 'proposed' and notifies eligible voters via FleetBus.
|
|
12752
12972
|
*/
|
|
12753
|
-
initiateVote(changeId) {
|
|
12973
|
+
async initiateVote(changeId) {
|
|
12754
12974
|
const change = this.graph.get(changeId);
|
|
12755
|
-
if (
|
|
12975
|
+
if (change?.type !== "change") {
|
|
12756
12976
|
throw new Error(`ConsensusProtocol: no change found with id "${changeId}"`);
|
|
12757
12977
|
}
|
|
12758
|
-
this.graph.update(changeId, { status: "proposed", votes: [] });
|
|
12978
|
+
await this.graph.update(changeId, { status: "proposed", votes: [] });
|
|
12759
12979
|
const eligible = this._eligibleVoters(change);
|
|
12760
12980
|
this._notifyVoters(change, eligible, "vote_initiated");
|
|
12761
12981
|
}
|
|
@@ -12763,9 +12983,9 @@ var ConsensusProtocol = class {
|
|
|
12763
12983
|
* Cast a vote. Updates the change node in the graph and re-evaluates
|
|
12764
12984
|
* consensus. If the vote triggers a resolution, updates the change status.
|
|
12765
12985
|
*/
|
|
12766
|
-
castVote(changeId, voterId, value, rationale) {
|
|
12986
|
+
async castVote(changeId, voterId, value, rationale) {
|
|
12767
12987
|
const change = this.graph.get(changeId);
|
|
12768
|
-
if (
|
|
12988
|
+
if (change?.type !== "change") {
|
|
12769
12989
|
throw new Error(`ConsensusProtocol: no change found for "${changeId}"`);
|
|
12770
12990
|
}
|
|
12771
12991
|
const voter = this.voters.get(voterId);
|
|
@@ -12786,7 +13006,7 @@ var ConsensusProtocol = class {
|
|
|
12786
13006
|
const existingIdx = change.votes.findIndex((v) => v.agentId === voterId);
|
|
12787
13007
|
const newVotes = existingIdx >= 0 ? change.votes.with(existingIdx, vote) : [...change.votes, vote];
|
|
12788
13008
|
const result = this._resolve(changeId, newVotes, eligible);
|
|
12789
|
-
this.graph.update(changeId, {
|
|
13009
|
+
await this.graph.update(changeId, {
|
|
12790
13010
|
votes: newVotes,
|
|
12791
13011
|
...result.outcome !== "pending" ? { status: this._toChangeStatus(result.outcome) } : {}
|
|
12792
13012
|
});
|
|
@@ -12797,13 +13017,13 @@ var ConsensusProtocol = class {
|
|
|
12797
13017
|
* Resolve the current vote without waiting for all eligible voters.
|
|
12798
13018
|
* Useful when a timeout fires or an agent decides to finalize early.
|
|
12799
13019
|
*/
|
|
12800
|
-
resolveNow(changeId) {
|
|
13020
|
+
async resolveNow(changeId) {
|
|
12801
13021
|
const change = this.graph.get(changeId);
|
|
12802
13022
|
if (!change) throw new Error(`ConsensusProtocol: unknown change "${changeId}"`);
|
|
12803
13023
|
const eligible = this._eligibleVoters(change);
|
|
12804
13024
|
const result = this._resolve(changeId, change.votes, eligible);
|
|
12805
13025
|
if (result.outcome !== "pending") {
|
|
12806
|
-
this.graph.update(changeId, { status: this._toChangeStatus(result.outcome) });
|
|
13026
|
+
await this.graph.update(changeId, { status: this._toChangeStatus(result.outcome) });
|
|
12807
13027
|
this._notifyVoters(change, eligible, "vote_resolved", { result });
|
|
12808
13028
|
}
|
|
12809
13029
|
return result;
|
|
@@ -12819,7 +13039,7 @@ var ConsensusProtocol = class {
|
|
|
12819
13039
|
*/
|
|
12820
13040
|
getStatus(changeId) {
|
|
12821
13041
|
const change = this.graph.get(changeId);
|
|
12822
|
-
if (
|
|
13042
|
+
if (change?.type !== "change") return null;
|
|
12823
13043
|
const eligible = this._eligibleVoters(change);
|
|
12824
13044
|
return this._resolve(changeId, change.votes, eligible);
|
|
12825
13045
|
}
|
|
@@ -13022,7 +13242,7 @@ var ChangeManager = class {
|
|
|
13022
13242
|
*/
|
|
13023
13243
|
async submitForReview(changeId) {
|
|
13024
13244
|
const change = this.graph.get(changeId);
|
|
13025
|
-
if (
|
|
13245
|
+
if (change?.type !== "change") {
|
|
13026
13246
|
throw new Error(`ChangeManager: no change found "${changeId}"`);
|
|
13027
13247
|
}
|
|
13028
13248
|
if (change.status !== "proposed") {
|
|
@@ -13084,7 +13304,7 @@ var ChangeManager = class {
|
|
|
13084
13304
|
*/
|
|
13085
13305
|
async proposeRollback(appliedChangeId, reason) {
|
|
13086
13306
|
const original = this.graph.get(appliedChangeId);
|
|
13087
|
-
if (
|
|
13307
|
+
if (original?.type !== "change") return null;
|
|
13088
13308
|
const rollbackFiles = original.files.map((f) => ({
|
|
13089
13309
|
path: f.path,
|
|
13090
13310
|
action: f.action === "create" ? "delete" : f.action === "delete" ? "create" : "modify"
|
|
@@ -13638,7 +13858,7 @@ var TaskAuctioneer = class {
|
|
|
13638
13858
|
*/
|
|
13639
13859
|
async bid(taskId, agent, rationale) {
|
|
13640
13860
|
const goal = this.graph.get(taskId);
|
|
13641
|
-
if (
|
|
13861
|
+
if (goal?.type !== "goal") return false;
|
|
13642
13862
|
if (goal.status !== "pending") return false;
|
|
13643
13863
|
const currentCount = this._getAgentTaskCount(agent.agentId);
|
|
13644
13864
|
if (currentCount >= this.maxTasksPerAgent) return false;
|
|
@@ -13687,7 +13907,7 @@ Score: ${score.toFixed(2)}`
|
|
|
13687
13907
|
*/
|
|
13688
13908
|
async claim(taskId, agentId, agentName) {
|
|
13689
13909
|
const goal = this.graph.get(taskId);
|
|
13690
|
-
if (
|
|
13910
|
+
if (goal?.type !== "goal") return false;
|
|
13691
13911
|
if (goal.status !== "pending") return false;
|
|
13692
13912
|
this._cancelBidWindow(taskId);
|
|
13693
13913
|
await this.graph.update(taskId, {
|
|
@@ -13726,7 +13946,8 @@ Priority: ${goal.priority}`,
|
|
|
13726
13946
|
this.agentTaskCount(agentId, -1);
|
|
13727
13947
|
await this.graph.update(taskId, {
|
|
13728
13948
|
status: "done",
|
|
13729
|
-
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
13949
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
13950
|
+
..._result !== void 0 ? { result: _result } : {}
|
|
13730
13951
|
});
|
|
13731
13952
|
this.bidRetryCounts.delete(taskId);
|
|
13732
13953
|
this.pendingBids.delete(taskId);
|
|
@@ -13971,7 +14192,7 @@ ${goal.description}`,
|
|
|
13971
14192
|
this.agentTaskCounts.set(agentId, next);
|
|
13972
14193
|
}
|
|
13973
14194
|
};
|
|
13974
|
-
var AutonomousCoordinator = class {
|
|
14195
|
+
var AutonomousCoordinator = class _AutonomousCoordinator {
|
|
13975
14196
|
graph;
|
|
13976
14197
|
dag;
|
|
13977
14198
|
auction;
|
|
@@ -13987,6 +14208,8 @@ var AutonomousCoordinator = class {
|
|
|
13987
14208
|
onCoordinatorEvent;
|
|
13988
14209
|
running = false;
|
|
13989
14210
|
iterationCount = 0;
|
|
14211
|
+
lastSyncAt = 0;
|
|
14212
|
+
static SYNC_INTERVAL_MS = 5e3;
|
|
13990
14213
|
/** Tasks already handled by _onSubagentTerminated (to avoid double goal:failed on fleet event). */
|
|
13991
14214
|
_handledBySubagent = /* @__PURE__ */ new Set();
|
|
13992
14215
|
/** FleetBus subscription disposers, detached in dispose(). */
|
|
@@ -14043,7 +14266,7 @@ var AutonomousCoordinator = class {
|
|
|
14043
14266
|
const taskId = payload?.taskId;
|
|
14044
14267
|
if (!taskId || this._handledBySubagent.has(taskId)) return;
|
|
14045
14268
|
this._handledBySubagent.add(taskId);
|
|
14046
|
-
this.
|
|
14269
|
+
this._recordTaskFailed(taskId, payload?.error ?? "Task failed");
|
|
14047
14270
|
});
|
|
14048
14271
|
if (offFailed) this.unsubs.push(offFailed);
|
|
14049
14272
|
this._emit({ type: "coordinator:mode", mode: this.fleet ? "fleet" : "standalone" });
|
|
@@ -14062,6 +14285,7 @@ var AutonomousCoordinator = class {
|
|
|
14062
14285
|
const maxCost = opts.maxCostUsd;
|
|
14063
14286
|
try {
|
|
14064
14287
|
await this.graph.load();
|
|
14288
|
+
this._rebuildDagFromGraph();
|
|
14065
14289
|
const goalConfigs = await this._decomposeGoal(goal);
|
|
14066
14290
|
for (const g of goalConfigs) {
|
|
14067
14291
|
const goalId = await this.auction.publishTask(g);
|
|
@@ -14069,23 +14293,49 @@ var AutonomousCoordinator = class {
|
|
|
14069
14293
|
this._emit({ type: "goal:added", goalId, title: g.title, text: g.description });
|
|
14070
14294
|
}
|
|
14071
14295
|
while (this.running) {
|
|
14296
|
+
if (this.dag.getRunning().length > 0 && this.auction.getPendingTasks().length === 0) {
|
|
14297
|
+
await this._waitForDagProgress(1e3);
|
|
14298
|
+
continue;
|
|
14299
|
+
}
|
|
14072
14300
|
this.iterationCount++;
|
|
14301
|
+
await this._maybeSyncFromGraph();
|
|
14073
14302
|
if (this.iterationCount >= maxIterations) break;
|
|
14074
14303
|
if (maxCost !== void 0) {
|
|
14075
14304
|
const cost = this.fleetManager?.snapshot()?.total?.cost ?? 0;
|
|
14076
14305
|
if (cost >= maxCost) break;
|
|
14077
14306
|
}
|
|
14078
14307
|
if (opts.runUntilComplete && this.dag.isDone()) break;
|
|
14308
|
+
const pendingTasks = this.auction.getPendingTasks();
|
|
14309
|
+
const dispatchable = pendingTasks.filter((task) => {
|
|
14310
|
+
const dagNode = this.dag.getNode(task.id);
|
|
14311
|
+
return !dagNode || dagNode.status === "ready";
|
|
14312
|
+
});
|
|
14313
|
+
if (dispatchable.length === 0) {
|
|
14314
|
+
if (this.dag.getRunning().length > 0 || this.dag.getReady().length > 0) {
|
|
14315
|
+
await this._waitForDagProgress(1e3);
|
|
14316
|
+
continue;
|
|
14317
|
+
}
|
|
14318
|
+
if (pendingTasks.length > 0) {
|
|
14319
|
+
await this._waitForDagProgress(2e3);
|
|
14320
|
+
continue;
|
|
14321
|
+
}
|
|
14322
|
+
if (this.dag.hasDeadlock()) {
|
|
14323
|
+
const blocked = this.dag.getBlocked();
|
|
14324
|
+
(this.events?.emit)("autonomous:deadlock", { blocked });
|
|
14325
|
+
this._emit({ type: "deadlock:detected", goalId: blocked[0]?.id ?? "", text: `Deadlock detected: ${blocked.map((n) => n.id).join(", ")}` });
|
|
14326
|
+
}
|
|
14327
|
+
break;
|
|
14328
|
+
}
|
|
14079
14329
|
const decision = await this.brain.decideAuto({
|
|
14080
14330
|
id: randomUUID(),
|
|
14081
14331
|
source: "system",
|
|
14082
14332
|
decisionType: "prioritize_goals",
|
|
14083
|
-
question: `What should we work on next? Open goals: ${
|
|
14333
|
+
question: `What should we work on next? Open goals: ${dispatchable.map((g) => g.title).join(", ")}`,
|
|
14084
14334
|
context: {
|
|
14085
|
-
goals:
|
|
14335
|
+
goals: dispatchable,
|
|
14086
14336
|
fleetStatus: this._fleetStatus()
|
|
14087
14337
|
},
|
|
14088
|
-
options: this._goalToOptions(
|
|
14338
|
+
options: this._goalToOptions(dispatchable),
|
|
14089
14339
|
risk: "medium",
|
|
14090
14340
|
requiresConsensus: false
|
|
14091
14341
|
});
|
|
@@ -14132,6 +14382,55 @@ var AutonomousCoordinator = class {
|
|
|
14132
14382
|
this.running = false;
|
|
14133
14383
|
console.error(`[AutonomousCoordinator] stop signal received \u2014 shutting down (iteration ${this.iterationCount})`);
|
|
14134
14384
|
}
|
|
14385
|
+
/**
|
|
14386
|
+
* Report that a terminal worker (not a Director subagent) completed a claimed
|
|
14387
|
+
* task. This updates the auction, DAG, publishes a task-result fact, and
|
|
14388
|
+
* extracts follow-up goals — the same path as subagent completion.
|
|
14389
|
+
*/
|
|
14390
|
+
async reportTaskCompletion(taskId, result) {
|
|
14391
|
+
this._handledBySubagent.add(taskId);
|
|
14392
|
+
await this._completeTask(taskId, result);
|
|
14393
|
+
}
|
|
14394
|
+
/**
|
|
14395
|
+
* Report that a terminal worker failed a claimed task.
|
|
14396
|
+
*/
|
|
14397
|
+
async reportTaskFailure(taskId, error) {
|
|
14398
|
+
this._handledBySubagent.add(taskId);
|
|
14399
|
+
await this._failTask(taskId, error);
|
|
14400
|
+
}
|
|
14401
|
+
/**
|
|
14402
|
+
* Reload the KnowledgeGraph from disk and sync the in-memory DAG with any
|
|
14403
|
+
* changes published by other terminal sessions. New goals are added to the
|
|
14404
|
+
* DAG; existing goals whose status changed (e.g. completed by another
|
|
14405
|
+
* terminal) are transitioned accordingly.
|
|
14406
|
+
*
|
|
14407
|
+
* Safe to call at any time — also used internally by the run loop.
|
|
14408
|
+
*/
|
|
14409
|
+
async syncFromGraph() {
|
|
14410
|
+
await this.graph.load();
|
|
14411
|
+
this._rebuildDagFromGraph();
|
|
14412
|
+
this._syncDagStatuses();
|
|
14413
|
+
}
|
|
14414
|
+
_syncDagStatuses() {
|
|
14415
|
+
const goals = this.graph.getGoals({});
|
|
14416
|
+
for (const goal of goals) {
|
|
14417
|
+
const dagNode = this.dag.getNode(goal.id);
|
|
14418
|
+
if (!dagNode) continue;
|
|
14419
|
+
if (goal.status === "done" && dagNode.status !== "done" && dagNode.status !== "failed") {
|
|
14420
|
+
this.dag.complete(goal.id, goal.result ?? "Completed by another session");
|
|
14421
|
+
} else if (goal.status === "failed" && dagNode.status !== "failed" && dagNode.status !== "done") {
|
|
14422
|
+
this.dag.fail(goal.id, goal.result ?? "Failed by another session");
|
|
14423
|
+
} else if (goal.status === "in_progress" && (dagNode.status === "ready" || dagNode.status === "pending")) {
|
|
14424
|
+
this.dag.start(goal.id, goal.assignee ?? "another-session");
|
|
14425
|
+
}
|
|
14426
|
+
}
|
|
14427
|
+
}
|
|
14428
|
+
async _maybeSyncFromGraph() {
|
|
14429
|
+
const now = Date.now();
|
|
14430
|
+
if (now - this.lastSyncAt < _AutonomousCoordinator.SYNC_INTERVAL_MS) return;
|
|
14431
|
+
this.lastSyncAt = now;
|
|
14432
|
+
await this.syncFromGraph();
|
|
14433
|
+
}
|
|
14135
14434
|
/**
|
|
14136
14435
|
* Tear down the coordinator for good: stop the loop and detach all FleetBus
|
|
14137
14436
|
* subscriptions (this coordinator's + the auctioneer's) plus any open bid
|
|
@@ -14228,6 +14527,65 @@ ${input.detail}`
|
|
|
14228
14527
|
return goal;
|
|
14229
14528
|
}
|
|
14230
14529
|
// ── Private ───────────────────────────────────────────────────────────
|
|
14530
|
+
_waitForDagProgress(timeoutMs) {
|
|
14531
|
+
const before = this._dagProgressKey();
|
|
14532
|
+
if (this.dag.isDone()) return Promise.resolve();
|
|
14533
|
+
return new Promise((resolve3) => {
|
|
14534
|
+
let off;
|
|
14535
|
+
const timer = setTimeout(() => {
|
|
14536
|
+
off?.();
|
|
14537
|
+
resolve3();
|
|
14538
|
+
}, timeoutMs);
|
|
14539
|
+
off = this.dag.onEvent(() => {
|
|
14540
|
+
if (this._dagProgressKey() === before) return;
|
|
14541
|
+
clearTimeout(timer);
|
|
14542
|
+
off?.();
|
|
14543
|
+
resolve3();
|
|
14544
|
+
});
|
|
14545
|
+
});
|
|
14546
|
+
}
|
|
14547
|
+
_dagProgressKey() {
|
|
14548
|
+
const s = this.dag.stats();
|
|
14549
|
+
return `${s.pending}:${s.ready}:${s.running}:${s.done}:${s.failed}:${s.skipped}`;
|
|
14550
|
+
}
|
|
14551
|
+
_rebuildDagFromGraph() {
|
|
14552
|
+
const goals = this.graph.getGoals({});
|
|
14553
|
+
const knownGoalIds = new Set(goals.map((goal) => goal.id));
|
|
14554
|
+
const added = /* @__PURE__ */ new Set();
|
|
14555
|
+
const remaining = new Map(goals.map((goal) => [goal.id, goal]));
|
|
14556
|
+
while (remaining.size > 0) {
|
|
14557
|
+
let progressed = false;
|
|
14558
|
+
for (const [id, goal] of Array.from(remaining.entries())) {
|
|
14559
|
+
const deps = goal.blockedBy.filter((depId) => knownGoalIds.has(depId));
|
|
14560
|
+
if (!deps.every((depId) => added.has(depId))) continue;
|
|
14561
|
+
this._rebuildDagNode(goal, deps);
|
|
14562
|
+
added.add(id);
|
|
14563
|
+
remaining.delete(id);
|
|
14564
|
+
progressed = true;
|
|
14565
|
+
}
|
|
14566
|
+
if (!progressed) {
|
|
14567
|
+
for (const [id, goal] of Array.from(remaining.entries())) {
|
|
14568
|
+
this._rebuildDagNode(goal, []);
|
|
14569
|
+
added.add(id);
|
|
14570
|
+
remaining.delete(id);
|
|
14571
|
+
}
|
|
14572
|
+
}
|
|
14573
|
+
}
|
|
14574
|
+
}
|
|
14575
|
+
_rebuildDagNode(goal, deps) {
|
|
14576
|
+
this.dag.addNode(goal.id, goal.description, deps, { tags: goal.tags });
|
|
14577
|
+
if (goal.status === "in_progress") {
|
|
14578
|
+
this.dag.start(goal.id, goal.assignee ?? "unknown");
|
|
14579
|
+
return;
|
|
14580
|
+
}
|
|
14581
|
+
if (goal.status === "done") {
|
|
14582
|
+
this.dag.complete(goal.id, goal.result ?? "Persisted completion");
|
|
14583
|
+
return;
|
|
14584
|
+
}
|
|
14585
|
+
if (goal.status === "failed") {
|
|
14586
|
+
this.dag.fail(goal.id, goal.result ?? "Persisted failure");
|
|
14587
|
+
}
|
|
14588
|
+
}
|
|
14231
14589
|
async _decomposeGoal(goalText) {
|
|
14232
14590
|
const category = this._inferCategory(goalText);
|
|
14233
14591
|
const subGoals = [];
|
|
@@ -14264,13 +14622,7 @@ ${input.detail}`
|
|
|
14264
14622
|
const goalNode = this.graph.get(goalId);
|
|
14265
14623
|
if (!goalNode) return;
|
|
14266
14624
|
const title = goalNode.title || dagNode.description;
|
|
14267
|
-
|
|
14268
|
-
title,
|
|
14269
|
-
description: goalNode.description,
|
|
14270
|
-
priority: this._dagPriorityToGoal(dagNode.priority),
|
|
14271
|
-
tags: dagNode.tags
|
|
14272
|
-
});
|
|
14273
|
-
this._emit({ type: "task:ready", goalId, taskId, title });
|
|
14625
|
+
this._emit({ type: "task:ready", goalId, taskId: goalId, title });
|
|
14274
14626
|
if (this.director) {
|
|
14275
14627
|
const config = {
|
|
14276
14628
|
name: `worker-${goalId.slice(0, 8)}`,
|
|
@@ -14280,7 +14632,7 @@ ${input.detail}`
|
|
|
14280
14632
|
// 10 minutes per goal
|
|
14281
14633
|
};
|
|
14282
14634
|
const subagentId = await this.director.spawn(config);
|
|
14283
|
-
await this.auction.claim(
|
|
14635
|
+
await this.auction.claim(goalId, subagentId, config.name);
|
|
14284
14636
|
await this.director.assign({
|
|
14285
14637
|
id: goalId,
|
|
14286
14638
|
subagentId,
|
|
@@ -14288,6 +14640,78 @@ ${input.detail}`
|
|
|
14288
14640
|
});
|
|
14289
14641
|
}
|
|
14290
14642
|
}
|
|
14643
|
+
_stringifyTaskResult(result) {
|
|
14644
|
+
if (typeof result === "string" && result.trim()) return result.trim();
|
|
14645
|
+
if (result === void 0 || result === null) return "Subagent completed successfully";
|
|
14646
|
+
try {
|
|
14647
|
+
return JSON.stringify(result);
|
|
14648
|
+
} catch {
|
|
14649
|
+
return String(result);
|
|
14650
|
+
}
|
|
14651
|
+
}
|
|
14652
|
+
async _completeTask(taskId, result) {
|
|
14653
|
+
await this.auction.complete(taskId, result);
|
|
14654
|
+
if (this.dag.getNode(taskId)) {
|
|
14655
|
+
this.dag.complete(taskId, result);
|
|
14656
|
+
}
|
|
14657
|
+
await this._publishTaskResultFact(taskId, result);
|
|
14658
|
+
await this._createFollowUpGoalsFromResult(taskId, result);
|
|
14659
|
+
this._emit({ type: "task:completed", goalId: taskId, taskId, text: result });
|
|
14660
|
+
}
|
|
14661
|
+
async _publishTaskResultFact(taskId, result) {
|
|
14662
|
+
const key = `task-result:${taskId}`;
|
|
14663
|
+
if (this.graph.getFacts({ category: "quality" }).some((fact2) => fact2.key === key)) return;
|
|
14664
|
+
const goal = this.graph.get(taskId);
|
|
14665
|
+
const subject = goal?.type === "goal" ? `Task completed: ${goal.title}` : `Task completed: ${taskId}`;
|
|
14666
|
+
const fact = await this.graph.add({
|
|
14667
|
+
type: "fact",
|
|
14668
|
+
category: "quality",
|
|
14669
|
+
subject,
|
|
14670
|
+
detail: result,
|
|
14671
|
+
discoveredBy: this.selfAgentId,
|
|
14672
|
+
discoveredAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
14673
|
+
tags: ["task-result", "autonomous-coordinator"],
|
|
14674
|
+
key,
|
|
14675
|
+
related: [taskId]
|
|
14676
|
+
});
|
|
14677
|
+
this._emit({ type: "knowledge:added", knowledgeId: fact.id, title: subject, text: result });
|
|
14678
|
+
}
|
|
14679
|
+
async _createFollowUpGoalsFromResult(taskId, result) {
|
|
14680
|
+
const followUps = this._extractFollowUps(result);
|
|
14681
|
+
if (followUps.length === 0) return;
|
|
14682
|
+
const existing = this.graph.getGoals({});
|
|
14683
|
+
for (const title of followUps) {
|
|
14684
|
+
if (existing.some((goal2) => goal2.title === title && goal2.tags.includes("follow-up"))) continue;
|
|
14685
|
+
const goal = await this.createGoal({
|
|
14686
|
+
title,
|
|
14687
|
+
description: title,
|
|
14688
|
+
priority: "medium",
|
|
14689
|
+
tags: ["follow-up", "task-result", taskId]
|
|
14690
|
+
});
|
|
14691
|
+
this._emit({ type: "goal:added", goalId: goal.id, title: goal.title, text: goal.description });
|
|
14692
|
+
}
|
|
14693
|
+
}
|
|
14694
|
+
_extractFollowUps(result) {
|
|
14695
|
+
const found = [];
|
|
14696
|
+
for (const line of result.split(/\r?\n/)) {
|
|
14697
|
+
const match = /^\s*(?:[-*]\s*)?(?:NEXT|TODO|FOLLOW-?UP):\s*(.+)$/i.exec(line);
|
|
14698
|
+
const text = match?.[1]?.trim();
|
|
14699
|
+
if (!text || found.includes(text)) continue;
|
|
14700
|
+
found.push(text);
|
|
14701
|
+
if (found.length >= 5) break;
|
|
14702
|
+
}
|
|
14703
|
+
return found;
|
|
14704
|
+
}
|
|
14705
|
+
async _failTask(taskId, error) {
|
|
14706
|
+
await this.auction.fail(taskId, error);
|
|
14707
|
+
this._recordTaskFailed(taskId, error);
|
|
14708
|
+
}
|
|
14709
|
+
_recordTaskFailed(taskId, error) {
|
|
14710
|
+
if (this.dag.getNode(taskId)) {
|
|
14711
|
+
this.dag.fail(taskId, error);
|
|
14712
|
+
}
|
|
14713
|
+
this._emit({ type: "goal:failed", goalId: taskId, text: error });
|
|
14714
|
+
}
|
|
14291
14715
|
async _handlePendingChange(change) {
|
|
14292
14716
|
const result = this.consensus.getStatus(change.id);
|
|
14293
14717
|
if (result?.outcome !== "pending") return;
|
|
@@ -14328,16 +14752,15 @@ ${input.detail}`
|
|
|
14328
14752
|
_onSubagentTerminated(e) {
|
|
14329
14753
|
const payload = e.payload;
|
|
14330
14754
|
const subagentId = payload?.subagentId ?? e.subagentId;
|
|
14331
|
-
const
|
|
14332
|
-
const
|
|
14755
|
+
const rawStatus = payload?.stopReason ?? payload?.status ?? "unknown";
|
|
14756
|
+
const succeeded = rawStatus === "end_turn" || rawStatus === "ok" || rawStatus === "success";
|
|
14757
|
+
const tasks = payload?.taskId ? this.auction.getTasksForAgent(subagentId).filter((task) => task.id === payload.taskId) : this.auction.getTasksForAgent(subagentId);
|
|
14333
14758
|
for (const task of tasks) {
|
|
14334
14759
|
this._handledBySubagent.add(task.id);
|
|
14335
|
-
if (
|
|
14336
|
-
void this.
|
|
14337
|
-
this._emit({ type: "task:completed", goalId: task.id, taskId: task.id, text: "Subagent completed successfully" });
|
|
14760
|
+
if (succeeded) {
|
|
14761
|
+
void this._completeTask(task.id, this._stringifyTaskResult(payload?.result));
|
|
14338
14762
|
} else {
|
|
14339
|
-
void this.
|
|
14340
|
-
this._emit({ type: "goal:failed", goalId: task.id, text: `Subagent terminated: ${stopReason}` });
|
|
14763
|
+
void this._failTask(task.id, `Subagent terminated: ${rawStatus}`);
|
|
14341
14764
|
}
|
|
14342
14765
|
}
|
|
14343
14766
|
}
|
|
@@ -14372,12 +14795,6 @@ ${input.detail}`
|
|
|
14372
14795
|
_optionToGoal(optionId) {
|
|
14373
14796
|
return this.graph.get(optionId);
|
|
14374
14797
|
}
|
|
14375
|
-
_dagPriorityToGoal(p) {
|
|
14376
|
-
if (p <= 1) return "critical";
|
|
14377
|
-
if (p <= 2) return "high";
|
|
14378
|
-
if (p <= 4) return "medium";
|
|
14379
|
-
return "low";
|
|
14380
|
-
}
|
|
14381
14798
|
async _mailboxBroadcast(msg) {
|
|
14382
14799
|
if (!this.mailbox) return;
|
|
14383
14800
|
try {
|