@wrongstack/core 0.285.0 → 0.286.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/autophase/phase-orchestrator.d.ts.map +1 -1
- package/dist/boot.d.ts.map +1 -1
- package/dist/coordination/agent-monitor.d.ts +29 -0
- package/dist/coordination/agent-monitor.d.ts.map +1 -1
- package/dist/coordination/autonomous-brain.d.ts +0 -2
- package/dist/coordination/autonomous-brain.d.ts.map +1 -1
- package/dist/coordination/autonomous-coordinator.d.ts +4 -0
- package/dist/coordination/autonomous-coordinator.d.ts.map +1 -1
- package/dist/coordination/brain-ledger.d.ts +142 -0
- package/dist/coordination/brain-ledger.d.ts.map +1 -0
- package/dist/coordination/brain-monitor.d.ts +31 -2
- package/dist/coordination/brain-monitor.d.ts.map +1 -1
- package/dist/coordination/brain.d.ts +43 -0
- package/dist/coordination/brain.d.ts.map +1 -1
- package/dist/coordination/delegate-tool.d.ts.map +1 -1
- package/dist/coordination/director-construction.d.ts.map +1 -1
- package/dist/coordination/director.d.ts +1 -1
- package/dist/coordination/director.d.ts.map +1 -1
- package/dist/coordination/fleet.d.ts +4 -4
- package/dist/coordination/fleet.d.ts.map +1 -1
- package/dist/coordination/global-mailbox.d.ts +63 -2
- package/dist/coordination/global-mailbox.d.ts.map +1 -1
- package/dist/coordination/index.d.ts +2 -1
- package/dist/coordination/index.d.ts.map +1 -1
- package/dist/coordination/index.js +1566 -462
- package/dist/coordination/index.js.map +4 -4
- package/dist/coordination/knowledge-graph.d.ts +13 -1
- package/dist/coordination/knowledge-graph.d.ts.map +1 -1
- package/dist/coordination/mailbox-actions.d.ts +12 -13
- package/dist/coordination/mailbox-actions.d.ts.map +1 -1
- package/dist/coordination/mailbox-constants.d.ts +64 -0
- package/dist/coordination/mailbox-constants.d.ts.map +1 -0
- package/dist/coordination/mailbox-events.d.ts +32 -0
- package/dist/coordination/mailbox-events.d.ts.map +1 -0
- package/dist/coordination/mailbox-message-codec.d.ts +8 -0
- package/dist/coordination/mailbox-message-codec.d.ts.map +1 -0
- package/dist/coordination/mailbox-types.d.ts +88 -4
- package/dist/coordination/mailbox-types.d.ts.map +1 -1
- package/dist/coordination/mailbox.d.ts +21 -2
- package/dist/coordination/mailbox.d.ts.map +1 -1
- package/dist/coordination/package-author-tracker.d.ts.map +1 -1
- package/dist/coordination/subagent-result-tool.d.ts.map +1 -1
- package/dist/defaults/index.js +846 -523
- package/dist/defaults/index.js.map +4 -4
- package/dist/execution/autonomous-runner.d.ts +7 -1
- package/dist/execution/autonomous-runner.d.ts.map +1 -1
- package/dist/execution/autonomy-brain.d.ts +72 -11
- package/dist/execution/autonomy-brain.d.ts.map +1 -1
- package/dist/execution/brain-chain.d.ts +53 -0
- package/dist/execution/brain-chain.d.ts.map +1 -0
- package/dist/execution/brain-evaluation.d.ts +66 -0
- package/dist/execution/brain-evaluation.d.ts.map +1 -0
- package/dist/execution/brain-runtime.d.ts +171 -0
- package/dist/execution/brain-runtime.d.ts.map +1 -0
- package/dist/execution/council-brain.d.ts +79 -0
- package/dist/execution/council-brain.d.ts.map +1 -0
- package/dist/execution/eternal-autonomy.d.ts +12 -0
- package/dist/execution/eternal-autonomy.d.ts.map +1 -1
- package/dist/execution/index.d.ts +5 -1
- package/dist/execution/index.d.ts.map +1 -1
- package/dist/execution/index.js +1350 -377
- package/dist/execution/index.js.map +4 -4
- package/dist/execution/parallel-eternal-engine.d.ts +12 -0
- package/dist/execution/parallel-eternal-engine.d.ts.map +1 -1
- package/dist/execution/tool-executor.d.ts.map +1 -1
- package/dist/hq/auth-store.d.ts +21 -3
- package/dist/hq/auth-store.d.ts.map +1 -1
- package/dist/hq/factory.d.ts +3 -0
- package/dist/hq/factory.d.ts.map +1 -1
- package/dist/hq/index.js +628 -72
- package/dist/hq/index.js.map +4 -4
- package/dist/hq/protocol.d.ts +54 -2
- package/dist/hq/protocol.d.ts.map +1 -1
- package/dist/hq/publisher.d.ts +4 -0
- package/dist/hq/publisher.d.ts.map +1 -1
- package/dist/index.d.ts +12 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3482 -1495
- package/dist/index.js.map +4 -4
- package/dist/kernel/events.d.ts +22 -4
- package/dist/kernel/events.d.ts.map +1 -1
- package/dist/kernel/index.js.map +2 -2
- package/dist/mailbox-attach.d.ts.map +1 -1
- package/dist/observability/index.js +16 -15
- package/dist/observability/index.js.map +2 -2
- package/dist/security/index.d.ts +1 -0
- package/dist/security/index.d.ts.map +1 -1
- package/dist/security/index.js +253 -21
- package/dist/security/index.js.map +4 -4
- package/dist/security/permission-policy-schema.d.ts +77 -0
- package/dist/security/permission-policy-schema.d.ts.map +1 -0
- package/dist/security/permission-policy.d.ts +5 -0
- package/dist/security/permission-policy.d.ts.map +1 -1
- package/dist/security/secret-vault.d.ts +9 -0
- package/dist/security/secret-vault.d.ts.map +1 -1
- package/dist/session-registry.d.ts.map +1 -1
- package/dist/storage/config-loader.d.ts +10 -0
- package/dist/storage/config-loader.d.ts.map +1 -1
- package/dist/storage/goal-store.d.ts +1 -1
- package/dist/storage/goal-store.d.ts.map +1 -1
- package/dist/storage/index.js +147 -162
- package/dist/storage/index.js.map +2 -2
- package/dist/storage/plan-store.d.ts +1 -1
- package/dist/storage/plan-store.d.ts.map +1 -1
- package/dist/storage/queue-store.d.ts +4 -0
- package/dist/storage/queue-store.d.ts.map +1 -1
- package/dist/storage/session-store.d.ts +9 -0
- package/dist/storage/session-store.d.ts.map +1 -1
- package/dist/storage/task-store.d.ts +1 -1
- package/dist/storage/task-store.d.ts.map +1 -1
- package/dist/storage/todos-checkpoint.d.ts +2 -2
- package/dist/storage/todos-checkpoint.d.ts.map +1 -1
- package/dist/types/config.d.ts +209 -1
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/index.js +11 -0
- package/dist/types/index.js.map +2 -2
- package/dist/types/multi-agent.d.ts +8 -0
- package/dist/types/multi-agent.d.ts.map +1 -1
- package/dist/types/observability.d.ts +5 -0
- package/dist/types/observability.d.ts.map +1 -1
- package/dist/types/tool.d.ts +7 -0
- package/dist/types/tool.d.ts.map +1 -1
- package/dist/utils/glob-expand.d.ts.map +1 -1
- package/dist/utils/incoming-images.d.ts +39 -0
- package/dist/utils/incoming-images.d.ts.map +1 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +81 -3
- package/dist/utils/index.js.map +3 -3
- package/dist/worktree/worktree-manager.d.ts +13 -4
- package/dist/worktree/worktree-manager.d.ts.map +1 -1
- package/instructions/agents/browser.md +25 -16
- package/instructions/coordination/subagent-baseline.md +16 -2
- package/instructions/leader-after-task.md +3 -1
- package/instructions/sections/tool/common-patterns.md +7 -1
- package/instructions/system.md +47 -0
- package/package.json +2 -2
- package/skills/docker-deploy/SKILL.md +1 -1
- package/skills/wrongstack-mailbox/SKILL.md +51 -12
- package/dist/plugins/git-plugin.d.ts +0 -15
- package/dist/plugins/git-plugin.d.ts.map +0 -1
- package/dist/plugins/observability-plugin.d.ts +0 -19
- package/dist/plugins/observability-plugin.d.ts.map +0 -1
- package/dist/plugins/plan-plugin.d.ts +0 -16
- package/dist/plugins/plan-plugin.d.ts.map +0 -1
package/dist/hq/index.js
CHANGED
|
@@ -140,8 +140,57 @@ var KNOWN_HQ_EVENT_PAYLOAD_TYPES = /* @__PURE__ */ new Set([
|
|
|
140
140
|
"tool.started",
|
|
141
141
|
"tool.completed",
|
|
142
142
|
"session.usage",
|
|
143
|
-
"client.heartbeat"
|
|
143
|
+
"client.heartbeat",
|
|
144
|
+
"mcp.health.snapshot",
|
|
145
|
+
"mcp.operation"
|
|
144
146
|
]);
|
|
147
|
+
function isHqMcpLatencySummary(x) {
|
|
148
|
+
if (typeof x !== "object" || x === null) return false;
|
|
149
|
+
const v = x;
|
|
150
|
+
return typeof v.count === "number" && (v.lastMs === void 0 || typeof v.lastMs === "number") && (v.minMs === void 0 || typeof v.minMs === "number") && (v.maxMs === void 0 || typeof v.maxMs === "number") && (v.p50Ms === void 0 || typeof v.p50Ms === "number") && (v.p95Ms === void 0 || typeof v.p95Ms === "number");
|
|
151
|
+
}
|
|
152
|
+
function isHqMcpFailureCounts(x) {
|
|
153
|
+
if (typeof x !== "object" || x === null) return false;
|
|
154
|
+
const v = x;
|
|
155
|
+
return typeof v.transport === "number" && typeof v.protocol === "number" && typeof v.tool === "number";
|
|
156
|
+
}
|
|
157
|
+
function isHqMcpHealthCheck(x) {
|
|
158
|
+
if (typeof x !== "object" || x === null) return false;
|
|
159
|
+
const v = x;
|
|
160
|
+
return typeof v.name === "string" && typeof v.passed === "boolean" && (v.value === void 0 || typeof v.value === "number") && (v.threshold === void 0 || typeof v.threshold === "number");
|
|
161
|
+
}
|
|
162
|
+
var HQ_MCP_HEALTH_STATES = /* @__PURE__ */ new Set([
|
|
163
|
+
"disabled",
|
|
164
|
+
"dormant",
|
|
165
|
+
"connecting",
|
|
166
|
+
"healthy",
|
|
167
|
+
"degraded",
|
|
168
|
+
"failed"
|
|
169
|
+
]);
|
|
170
|
+
var HQ_MCP_CONNECTION_STATES = /* @__PURE__ */ new Set([
|
|
171
|
+
"idle",
|
|
172
|
+
"dormant",
|
|
173
|
+
"connecting",
|
|
174
|
+
"connected",
|
|
175
|
+
"reconnecting",
|
|
176
|
+
"disconnected",
|
|
177
|
+
"failed"
|
|
178
|
+
]);
|
|
179
|
+
function isHqMcpServerHealth(x) {
|
|
180
|
+
if (typeof x !== "object" || x === null) return false;
|
|
181
|
+
const v = x;
|
|
182
|
+
return typeof v.name === "string" && typeof v.projectId === "string" && typeof v.clientId === "string" && typeof v.connectionState === "string" && HQ_MCP_CONNECTION_STATES.has(v.connectionState) && typeof v.healthState === "string" && HQ_MCP_HEALTH_STATES.has(v.healthState) && (v.lastSuccessAt === void 0 || typeof v.lastSuccessAt === "string") && (v.lastFailureAt === void 0 || typeof v.lastFailureAt === "string") && (v.lastFailureKind === void 0 || v.lastFailureKind === "transport" || v.lastFailureKind === "protocol" || v.lastFailureKind === "tool") && typeof v.consecutiveFailures === "number" && isHqMcpFailureCounts(v.failures) && typeof v.reconnectCount === "number" && typeof v.wakeCount === "number" && typeof v.sleepCount === "number" && typeof v.restartCount === "number" && isHqMcpLatencySummary(v.connectionLatency) && isHqMcpLatencySummary(v.discoveryLatency) && isHqMcpLatencySummary(v.callLatency) && typeof v.inFlightCalls === "number" && typeof v.peakInFlightCalls === "number" && Array.isArray(v.healthChecks) && v.healthChecks.every(isHqMcpHealthCheck);
|
|
183
|
+
}
|
|
184
|
+
function isHqMcpHealthSnapshotPayload(x) {
|
|
185
|
+
if (typeof x !== "object" || x === null) return false;
|
|
186
|
+
const v = x;
|
|
187
|
+
return Array.isArray(v.servers) && v.servers.every(isHqMcpServerHealth);
|
|
188
|
+
}
|
|
189
|
+
function isHqMcpOperationPayload(x) {
|
|
190
|
+
if (typeof x !== "object" || x === null) return false;
|
|
191
|
+
const v = x;
|
|
192
|
+
return typeof v.operation === "object" && v.operation !== null && Array.isArray(v.servers) && v.servers.every(isHqMcpServerHealth);
|
|
193
|
+
}
|
|
145
194
|
function isHqMailboxMessageSummary(x) {
|
|
146
195
|
if (typeof x !== "object" || x === null) return false;
|
|
147
196
|
const v = x;
|
|
@@ -351,6 +400,10 @@ function parseHqEventPayload(eventType, payload) {
|
|
|
351
400
|
return isHqUsagePayload(payload) ? { ok: true, payload } : { ok: false, reason: "malformed-payload" };
|
|
352
401
|
case "client.heartbeat":
|
|
353
402
|
return isHqClientHeartbeatPayload(payload) ? { ok: true, payload } : { ok: false, reason: "malformed-payload" };
|
|
403
|
+
case "mcp.health.snapshot":
|
|
404
|
+
return isHqMcpHealthSnapshotPayload(payload) ? { ok: true, payload } : { ok: false, reason: "malformed-payload" };
|
|
405
|
+
case "mcp.operation":
|
|
406
|
+
return isHqMcpOperationPayload(payload) ? { ok: true, payload } : { ok: false, reason: "malformed-payload" };
|
|
354
407
|
default: {
|
|
355
408
|
const _exhaustive = eventType;
|
|
356
409
|
return _exhaustive;
|
|
@@ -941,6 +994,7 @@ var HqPublisher = class {
|
|
|
941
994
|
this.reconnectMaxMs = options.reconnectMaxMs ?? DEFAULT_RECONNECT_MAX_MS;
|
|
942
995
|
this.maxQueuedMessages = options.maxQueuedMessages ?? DEFAULT_MAX_QUEUED_MESSAGES;
|
|
943
996
|
this.resolvedRedactionPolicy = resolveHqRedactionPolicy(options.redactionPolicy);
|
|
997
|
+
this.logger = options.logger;
|
|
944
998
|
}
|
|
945
999
|
options;
|
|
946
1000
|
socketFactory;
|
|
@@ -952,6 +1006,7 @@ var HqPublisher = class {
|
|
|
952
1006
|
reconnectMaxMs;
|
|
953
1007
|
maxQueuedMessages;
|
|
954
1008
|
resolvedRedactionPolicy;
|
|
1009
|
+
logger;
|
|
955
1010
|
socket = null;
|
|
956
1011
|
seq = 0;
|
|
957
1012
|
queue = [];
|
|
@@ -1290,6 +1345,10 @@ var HqPublisher = class {
|
|
|
1290
1345
|
emitConnectWarning() {
|
|
1291
1346
|
const attempt = this.lastAttempt;
|
|
1292
1347
|
const message = `WrongStack HQ publisher: ${this.reconnectAttempt} consecutive connection failures${attempt !== null ? ` to ${attempt.url} (client token ${attempt.hadToken ? "present" : "absent"})` : ""}. Either the HQ server is unreachable or it rejected the token (401). If HQ runs in client-token mode, verify WRONGSTACK_HQ_TOKEN / auth.json. Retries continue with backoff.`;
|
|
1348
|
+
if (this.logger) {
|
|
1349
|
+
this.logger.warn(message, { event: "hq.publisher.connect_failed" });
|
|
1350
|
+
return;
|
|
1351
|
+
}
|
|
1293
1352
|
const warn = this.options.warn ?? ((msg) => console.warn(
|
|
1294
1353
|
JSON.stringify({
|
|
1295
1354
|
level: "warn",
|
|
@@ -1657,6 +1716,148 @@ async function renameWithRetry(from, to) {
|
|
|
1657
1716
|
throw lastErr;
|
|
1658
1717
|
}
|
|
1659
1718
|
|
|
1719
|
+
// src/coordination/mailbox-constants.ts
|
|
1720
|
+
var AGENT_STALE_MS = 6e4;
|
|
1721
|
+
var CLIENT_STALE_MS = 6e4;
|
|
1722
|
+
var HEARTBEAT_THROTTLE_MS = 5e3;
|
|
1723
|
+
var REGISTRY_CACHE_TTL_MS = 2e3;
|
|
1724
|
+
var LINE_SEPARATOR = "\n";
|
|
1725
|
+
var MESSAGE_CACHE_MAX_ENTRIES = 1e4;
|
|
1726
|
+
var AUTO_COMPACT_INTERVAL_MS = 3e5;
|
|
1727
|
+
var AUTO_COMPACT_READ_MAX_AGE_MS = 6e5;
|
|
1728
|
+
var AUTO_COMPACT_DEFAULT_TTL_MS = 864e5;
|
|
1729
|
+
|
|
1730
|
+
// src/coordination/mailbox-message-codec.ts
|
|
1731
|
+
var MESSAGE_TYPES = /* @__PURE__ */ new Set([
|
|
1732
|
+
"note",
|
|
1733
|
+
"ask",
|
|
1734
|
+
"assign",
|
|
1735
|
+
"steer",
|
|
1736
|
+
"btw",
|
|
1737
|
+
"broadcast",
|
|
1738
|
+
"status",
|
|
1739
|
+
"result",
|
|
1740
|
+
"review",
|
|
1741
|
+
"control"
|
|
1742
|
+
]);
|
|
1743
|
+
var PRIORITIES = /* @__PURE__ */ new Set(["low", "normal", "high"]);
|
|
1744
|
+
var TASK_STATUSES = /* @__PURE__ */ new Set([
|
|
1745
|
+
"pending",
|
|
1746
|
+
"in_progress",
|
|
1747
|
+
"completed",
|
|
1748
|
+
"failed",
|
|
1749
|
+
"idle",
|
|
1750
|
+
"running",
|
|
1751
|
+
"streaming",
|
|
1752
|
+
"waiting_user",
|
|
1753
|
+
"error",
|
|
1754
|
+
"offline",
|
|
1755
|
+
"busy"
|
|
1756
|
+
]);
|
|
1757
|
+
function isRecord(value) {
|
|
1758
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1759
|
+
}
|
|
1760
|
+
function requiredString(record, key) {
|
|
1761
|
+
const value = record[key];
|
|
1762
|
+
if (typeof value !== "string") {
|
|
1763
|
+
throw new TypeError(`mailbox message field "${key}" must be a string`);
|
|
1764
|
+
}
|
|
1765
|
+
return value;
|
|
1766
|
+
}
|
|
1767
|
+
function optionalString(record, key) {
|
|
1768
|
+
const value = record[key];
|
|
1769
|
+
if (value === void 0) return {};
|
|
1770
|
+
if (typeof value !== "string") {
|
|
1771
|
+
throw new TypeError(`mailbox message field "${key}" must be a string when present`);
|
|
1772
|
+
}
|
|
1773
|
+
return { [key]: value };
|
|
1774
|
+
}
|
|
1775
|
+
function parseMessageType(value) {
|
|
1776
|
+
if (value === "info") return "note";
|
|
1777
|
+
if (value === "task") return "assign";
|
|
1778
|
+
if (typeof value === "string" && MESSAGE_TYPES.has(value)) {
|
|
1779
|
+
return value;
|
|
1780
|
+
}
|
|
1781
|
+
throw new TypeError('mailbox message field "type" is invalid');
|
|
1782
|
+
}
|
|
1783
|
+
function normalizeMailboxMessageType(value) {
|
|
1784
|
+
return parseMessageType(value);
|
|
1785
|
+
}
|
|
1786
|
+
function parsePriority(value) {
|
|
1787
|
+
if (typeof value === "string" && PRIORITIES.has(value)) {
|
|
1788
|
+
return value;
|
|
1789
|
+
}
|
|
1790
|
+
if (typeof value === "string") return "normal";
|
|
1791
|
+
throw new TypeError('mailbox message field "priority" must be a string');
|
|
1792
|
+
}
|
|
1793
|
+
function parseReadReceipts(record, to) {
|
|
1794
|
+
const value = record["readBy"];
|
|
1795
|
+
if (value === void 0) {
|
|
1796
|
+
const legacyReadAt = record["readAt"];
|
|
1797
|
+
return record["read"] === true && typeof legacyReadAt === "string" ? { [to || "unknown"]: legacyReadAt } : {};
|
|
1798
|
+
}
|
|
1799
|
+
if (!isRecord(value)) {
|
|
1800
|
+
throw new TypeError('mailbox message field "readBy" must be an object');
|
|
1801
|
+
}
|
|
1802
|
+
const receipts = {};
|
|
1803
|
+
for (const [agentId, timestamp] of Object.entries(value)) {
|
|
1804
|
+
if (typeof timestamp !== "string") {
|
|
1805
|
+
throw new TypeError("mailbox message read receipt timestamps must be strings");
|
|
1806
|
+
}
|
|
1807
|
+
receipts[agentId] = timestamp;
|
|
1808
|
+
}
|
|
1809
|
+
return receipts;
|
|
1810
|
+
}
|
|
1811
|
+
function parseTaskContext(value) {
|
|
1812
|
+
if (value === void 0) return void 0;
|
|
1813
|
+
if (!isRecord(value)) {
|
|
1814
|
+
throw new TypeError('mailbox message field "taskContext" must be an object');
|
|
1815
|
+
}
|
|
1816
|
+
const status = value["status"];
|
|
1817
|
+
if (status !== void 0 && (typeof status !== "string" || !TASK_STATUSES.has(status))) {
|
|
1818
|
+
throw new TypeError("mailbox message taskContext status is invalid");
|
|
1819
|
+
}
|
|
1820
|
+
return {
|
|
1821
|
+
...optionalString(value, "agentRole"),
|
|
1822
|
+
...optionalString(value, "agentName"),
|
|
1823
|
+
...optionalString(value, "taskId"),
|
|
1824
|
+
...status === void 0 ? {} : { status }
|
|
1825
|
+
};
|
|
1826
|
+
}
|
|
1827
|
+
function parseMailboxMessage(value) {
|
|
1828
|
+
if (!isRecord(value)) throw new TypeError("mailbox message must be an object");
|
|
1829
|
+
const to = value["to"] === void 0 ? "" : requiredString(value, "to");
|
|
1830
|
+
const completed = value["completed"];
|
|
1831
|
+
if (typeof completed !== "boolean") {
|
|
1832
|
+
throw new TypeError('mailbox message field "completed" must be a boolean');
|
|
1833
|
+
}
|
|
1834
|
+
const taskContext = parseTaskContext(value["taskContext"]);
|
|
1835
|
+
return {
|
|
1836
|
+
id: requiredString(value, "id"),
|
|
1837
|
+
from: requiredString(value, "from"),
|
|
1838
|
+
to,
|
|
1839
|
+
type: parseMessageType(value["type"]),
|
|
1840
|
+
subject: requiredString(value, "subject"),
|
|
1841
|
+
body: requiredString(value, "body"),
|
|
1842
|
+
priority: parsePriority(value["priority"]),
|
|
1843
|
+
readBy: parseReadReceipts(value, to),
|
|
1844
|
+
completed,
|
|
1845
|
+
timestamp: requiredString(value, "timestamp"),
|
|
1846
|
+
...optionalString(value, "completedBy"),
|
|
1847
|
+
...optionalString(value, "outcome"),
|
|
1848
|
+
...optionalString(value, "completedAt"),
|
|
1849
|
+
...optionalString(value, "deletedAt"),
|
|
1850
|
+
...optionalString(value, "deletedBy"),
|
|
1851
|
+
...optionalString(value, "replyTo"),
|
|
1852
|
+
...optionalString(value, "senderSessionId"),
|
|
1853
|
+
...optionalString(value, "expiresAt"),
|
|
1854
|
+
...taskContext === void 0 ? {} : { taskContext }
|
|
1855
|
+
};
|
|
1856
|
+
}
|
|
1857
|
+
function parseMailboxMessageLine(line) {
|
|
1858
|
+
return parseMailboxMessage(JSON.parse(line));
|
|
1859
|
+
}
|
|
1860
|
+
|
|
1660
1861
|
// src/coordination/mailbox-types.ts
|
|
1661
1862
|
function normalizeRecipient(to) {
|
|
1662
1863
|
return to.trim().toLowerCase() === "all" ? "*" : to.trim();
|
|
@@ -1665,13 +1866,6 @@ function normalizeRecipient(to) {
|
|
|
1665
1866
|
// src/coordination/global-mailbox.ts
|
|
1666
1867
|
var MAILBOX_FILE = "_mailbox.jsonl";
|
|
1667
1868
|
var CLIENT_REGISTRY_FILE = "_mailbox.clients.json";
|
|
1668
|
-
var AGENT_STALE_MS = 6e4;
|
|
1669
|
-
var AGENT_PURGE_MS = 864e5;
|
|
1670
|
-
var CLIENT_STALE_MS = 6e4;
|
|
1671
|
-
var HEARTBEAT_THROTTLE_MS = 5e3;
|
|
1672
|
-
var REGISTRY_CACHE_TTL_MS = 2e3;
|
|
1673
|
-
var LINE_SEPARATOR = "\n";
|
|
1674
|
-
var MESSAGE_CACHE_MAX_ENTRIES = 1e4;
|
|
1675
1869
|
var GlobalMailbox = class {
|
|
1676
1870
|
/** Path to the JSONL message file. */
|
|
1677
1871
|
messagePath;
|
|
@@ -1683,6 +1877,12 @@ var GlobalMailbox = class {
|
|
|
1683
1877
|
_events;
|
|
1684
1878
|
/** Optional HQ publisher for cross-project command-center telemetry. */
|
|
1685
1879
|
_hqPublisher;
|
|
1880
|
+
/**
|
|
1881
|
+
* Optional SSE event emitter for real-time push to HTTP bridge clients.
|
|
1882
|
+
* When present, `send`/`ack`/`softDelete`/`restore` emit events that
|
|
1883
|
+
* connected SSE subscribers receive instantly.
|
|
1884
|
+
*/
|
|
1885
|
+
eventEmitter;
|
|
1686
1886
|
/**
|
|
1687
1887
|
* Local cache of the agent registry to avoid re-reading on every call.
|
|
1688
1888
|
* Time-bounded: the registry file is shared ACROSS PROCESSES (that's the
|
|
@@ -1728,17 +1928,44 @@ var GlobalMailbox = class {
|
|
|
1728
1928
|
* that follows the read. Pattern mirrors `DefaultMemoryStore.runSerialized`.
|
|
1729
1929
|
*/
|
|
1730
1930
|
_readChain = Promise.resolve([]);
|
|
1931
|
+
/**
|
|
1932
|
+
* Recipient → Set of indices into `_messageCache`. Maintained alongside
|
|
1933
|
+
* the cache so `query({ to })` and `unreadCount(agentId)` can skip the
|
|
1934
|
+
* O(N) full scan and iterate only the messages addressed to the target
|
|
1935
|
+
* recipient (plus broadcasts `*`).
|
|
1936
|
+
*
|
|
1937
|
+
* The index is rebuilt whenever the cache array is replaced
|
|
1938
|
+
* (`_setMessageCache`), extended when new messages are pushed
|
|
1939
|
+
* (`_pushToCache`, `_readNewMessagesOnly`), and cleared on `close()`.
|
|
1940
|
+
* It is `null` when the cache itself is null (> MESSAGE_CACHE_MAX_ENTRIES
|
|
1941
|
+
* or never populated).
|
|
1942
|
+
*
|
|
1943
|
+
* Safety: array indices are valid only as long as the cache array
|
|
1944
|
+
* reference hasn't changed. Since we rebuild the index in the same
|
|
1945
|
+
* synchronous step that replaces the cache, a reader that calls
|
|
1946
|
+
* `_readMessagesCached()` either gets the old array + old index, or
|
|
1947
|
+
* the new array + new index — never a mismatch.
|
|
1948
|
+
*/
|
|
1949
|
+
_recipientIndex = null;
|
|
1950
|
+
/**
|
|
1951
|
+
* Sender → Set of indices into `_messageCache`. Same lifecycle and
|
|
1952
|
+
* safety properties as `_recipientIndex`, but keyed on `msg.from`
|
|
1953
|
+
* so `query({ from })` can skip the full scan.
|
|
1954
|
+
*/
|
|
1955
|
+
_senderIndex = null;
|
|
1731
1956
|
/**
|
|
1732
1957
|
* @param projectDir — `~/.wrongstack/projects/<slug>/`
|
|
1733
1958
|
* @param events — optional EventBus for real-time TUI/WebUI notifications
|
|
1734
1959
|
* @param hqPublisher — optional HQ publisher, or getter, for cross-project telemetry
|
|
1960
|
+
* @param eventEmitter — optional SSE event emitter for HTTP bridge push
|
|
1735
1961
|
*/
|
|
1736
|
-
constructor(projectDir, events, hqPublisher) {
|
|
1962
|
+
constructor(projectDir, events, hqPublisher, eventEmitter) {
|
|
1737
1963
|
this.messagePath = path4.join(projectDir, MAILBOX_FILE);
|
|
1738
1964
|
this.registryPath = path4.join(projectDir, "_mailbox.registry.json");
|
|
1739
1965
|
this.clientRegistryPath = path4.join(projectDir, CLIENT_REGISTRY_FILE);
|
|
1740
1966
|
this._events = events;
|
|
1741
1967
|
this._hqPublisher = hqPublisher;
|
|
1968
|
+
this.eventEmitter = eventEmitter;
|
|
1742
1969
|
}
|
|
1743
1970
|
get hqMailboxId() {
|
|
1744
1971
|
return `${path4.basename(path4.dirname(this.messagePath))}:mailbox`;
|
|
@@ -1767,7 +1994,7 @@ var GlobalMailbox = class {
|
|
|
1767
1994
|
// "all" is an accepted spelling of the broadcast address — canonical
|
|
1768
1995
|
// form on disk is '*' so every query/checker matches it.
|
|
1769
1996
|
to: normalizeRecipient(input.to),
|
|
1770
|
-
type: input.type,
|
|
1997
|
+
type: normalizeMailboxMessageType(input.type),
|
|
1771
1998
|
subject: input.subject,
|
|
1772
1999
|
body: input.body,
|
|
1773
2000
|
priority: input.priority ?? "normal",
|
|
@@ -1775,7 +2002,8 @@ var GlobalMailbox = class {
|
|
|
1775
2002
|
completed: false,
|
|
1776
2003
|
timestamp: now,
|
|
1777
2004
|
replyTo: input.replyTo,
|
|
1778
|
-
taskContext: input.taskContext
|
|
2005
|
+
taskContext: input.taskContext,
|
|
2006
|
+
expiresAt: input.ttlMs !== void 0 ? new Date(Date.now() + input.ttlMs).toISOString() : void 0
|
|
1779
2007
|
};
|
|
1780
2008
|
const line = JSON.stringify(msg) + LINE_SEPARATOR;
|
|
1781
2009
|
await fsp.mkdir(path4.dirname(this.messagePath), { recursive: true });
|
|
@@ -1790,21 +2018,49 @@ var GlobalMailbox = class {
|
|
|
1790
2018
|
message: msg
|
|
1791
2019
|
});
|
|
1792
2020
|
this.publishHqMailboxSnapshot();
|
|
2021
|
+
this._events?.emitCustom("mailbox.message_sent", {
|
|
2022
|
+
messageId: msg.id,
|
|
2023
|
+
from: msg.from,
|
|
2024
|
+
to: msg.to,
|
|
2025
|
+
type: msg.type,
|
|
2026
|
+
subject: msg.subject
|
|
2027
|
+
});
|
|
2028
|
+
this.eventEmitter?.emit({
|
|
2029
|
+
type: "message.sent",
|
|
2030
|
+
messageId: msg.id,
|
|
2031
|
+
from: msg.from,
|
|
2032
|
+
to: msg.to,
|
|
2033
|
+
timestamp: now
|
|
2034
|
+
});
|
|
1793
2035
|
return msg;
|
|
1794
2036
|
}
|
|
1795
2037
|
async query(q) {
|
|
2038
|
+
const queryType = q.type === void 0 ? void 0 : normalizeMailboxMessageType(q.type);
|
|
1796
2039
|
const all = await this._readMessagesCached();
|
|
1797
2040
|
const limit = q.limit ?? 50;
|
|
1798
2041
|
const order = q.minPriority !== void 0 ? { low: 0, normal: 1, high: 2 } : null;
|
|
1799
2042
|
const minPriorityRank = order && q.minPriority !== void 0 ? order[q.minPriority] : 0;
|
|
1800
2043
|
const out = [];
|
|
1801
|
-
|
|
1802
|
-
|
|
2044
|
+
let candidates;
|
|
2045
|
+
if (q.to !== void 0 && this._recipientIndex !== null && q.from === void 0 && q.type === void 0 && q.minPriority === void 0) {
|
|
2046
|
+
const indices = /* @__PURE__ */ new Set();
|
|
2047
|
+
const direct = this._recipientIndex.get(q.to);
|
|
2048
|
+
if (direct !== void 0) for (const i of direct) indices.add(i);
|
|
2049
|
+
const broadcasts = this._recipientIndex.get("*");
|
|
2050
|
+
if (broadcasts !== void 0) for (const i of broadcasts) indices.add(i);
|
|
2051
|
+
candidates = [...indices].sort((a, b) => a - b).map((i) => all[i]);
|
|
2052
|
+
} else if (q.from !== void 0 && q.to === void 0 && this._senderIndex !== null && q.type === void 0 && q.minPriority === void 0) {
|
|
2053
|
+
const indices = this._senderIndex.get(q.from);
|
|
2054
|
+
candidates = indices !== void 0 ? [...indices].sort((a, b) => a - b).map((i) => all[i]) : [];
|
|
2055
|
+
} else {
|
|
2056
|
+
candidates = all;
|
|
2057
|
+
}
|
|
2058
|
+
for (const m of candidates) {
|
|
1803
2059
|
if (q.to !== void 0 && m.to !== q.to && m.to !== "*") continue;
|
|
1804
2060
|
if (q.from !== void 0 && m.from !== q.from) continue;
|
|
1805
2061
|
if (q.unreadBy !== void 0 && q.unreadBy in m.readBy) continue;
|
|
1806
2062
|
if (q.incompleteOnly && m.completed) continue;
|
|
1807
|
-
if (
|
|
2063
|
+
if (queryType !== void 0 && m.type !== queryType) continue;
|
|
1808
2064
|
if (order !== null && (order[m.priority] ?? 1) < minPriorityRank) {
|
|
1809
2065
|
continue;
|
|
1810
2066
|
}
|
|
@@ -1862,6 +2118,13 @@ var GlobalMailbox = class {
|
|
|
1862
2118
|
action: message.completed ? "message.completed" : "message.read",
|
|
1863
2119
|
message
|
|
1864
2120
|
});
|
|
2121
|
+
this.eventEmitter?.emit({
|
|
2122
|
+
type: "message.acked",
|
|
2123
|
+
messageId: message.id,
|
|
2124
|
+
from: message.from,
|
|
2125
|
+
to: message.to,
|
|
2126
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2127
|
+
});
|
|
1865
2128
|
}
|
|
1866
2129
|
if (updated.length > 0) this.publishHqMailboxSnapshot();
|
|
1867
2130
|
return updated;
|
|
@@ -1869,10 +2132,22 @@ var GlobalMailbox = class {
|
|
|
1869
2132
|
async unreadCount(forAgentId) {
|
|
1870
2133
|
const all = await this._readMessagesCached();
|
|
1871
2134
|
let count = 0;
|
|
1872
|
-
|
|
1873
|
-
const
|
|
1874
|
-
|
|
1875
|
-
|
|
2135
|
+
if (this._recipientIndex !== null) {
|
|
2136
|
+
const indices = /* @__PURE__ */ new Set();
|
|
2137
|
+
const direct = this._recipientIndex.get(forAgentId);
|
|
2138
|
+
if (direct !== void 0) for (const i of direct) indices.add(i);
|
|
2139
|
+
const broadcasts = this._recipientIndex.get("*");
|
|
2140
|
+
if (broadcasts !== void 0) for (const i of broadcasts) indices.add(i);
|
|
2141
|
+
for (const i of indices) {
|
|
2142
|
+
const m = all[i];
|
|
2143
|
+
if (!(forAgentId in m.readBy) && !m.completed) count++;
|
|
2144
|
+
}
|
|
2145
|
+
} else {
|
|
2146
|
+
for (let i = 0; i < all.length; i++) {
|
|
2147
|
+
const m = all[i];
|
|
2148
|
+
if ((m.to === forAgentId || m.to === "*") && !(forAgentId in m.readBy) && !m.completed) {
|
|
2149
|
+
count++;
|
|
2150
|
+
}
|
|
1876
2151
|
}
|
|
1877
2152
|
}
|
|
1878
2153
|
return count;
|
|
@@ -1882,6 +2157,7 @@ var GlobalMailbox = class {
|
|
|
1882
2157
|
await withFileLock(this.messagePath, async () => {
|
|
1883
2158
|
const all = await this._readMessagesFresh();
|
|
1884
2159
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2160
|
+
let changed = false;
|
|
1885
2161
|
for (const m of all) {
|
|
1886
2162
|
if (m.id !== mailId) continue;
|
|
1887
2163
|
if (m.deletedAt !== void 0) {
|
|
@@ -1891,19 +2167,30 @@ var GlobalMailbox = class {
|
|
|
1891
2167
|
m.deletedAt = now;
|
|
1892
2168
|
m.deletedBy = by;
|
|
1893
2169
|
updated = m;
|
|
2170
|
+
changed = true;
|
|
2171
|
+
}
|
|
2172
|
+
if (changed) {
|
|
2173
|
+
const serialized = all.map((m) => JSON.stringify(m)).join(LINE_SEPARATOR) + LINE_SEPARATOR;
|
|
2174
|
+
await fsp.writeFile(this.messagePath, serialized, "utf8");
|
|
1894
2175
|
}
|
|
1895
|
-
const serialized = all.map((m) => JSON.stringify(m)).join(LINE_SEPARATOR) + LINE_SEPARATOR;
|
|
1896
|
-
await fsp.writeFile(this.messagePath, serialized, "utf8");
|
|
1897
2176
|
const { mtimeMs, size } = await this._statMessageFile();
|
|
1898
2177
|
this._setMessageCache(all, mtimeMs, size);
|
|
1899
2178
|
});
|
|
1900
2179
|
if (updated !== null) {
|
|
2180
|
+
const msg = updated;
|
|
1901
2181
|
this.publishHqMailboxEvent({
|
|
1902
2182
|
mailboxId: this.hqMailboxId,
|
|
1903
2183
|
action: "message.updated",
|
|
1904
|
-
message:
|
|
2184
|
+
message: msg
|
|
1905
2185
|
});
|
|
1906
2186
|
this.publishHqMailboxSnapshot();
|
|
2187
|
+
this.eventEmitter?.emit({
|
|
2188
|
+
type: "message.deleted",
|
|
2189
|
+
messageId: msg.id,
|
|
2190
|
+
from: msg.from,
|
|
2191
|
+
to: msg.to,
|
|
2192
|
+
timestamp: msg.deletedAt ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
2193
|
+
});
|
|
1907
2194
|
}
|
|
1908
2195
|
return updated;
|
|
1909
2196
|
}
|
|
@@ -1911,24 +2198,40 @@ var GlobalMailbox = class {
|
|
|
1911
2198
|
let updated = null;
|
|
1912
2199
|
await withFileLock(this.messagePath, async () => {
|
|
1913
2200
|
const all = await this._readMessagesFresh();
|
|
2201
|
+
let changed = false;
|
|
1914
2202
|
for (const m of all) {
|
|
1915
2203
|
if (m.id !== mailId) continue;
|
|
2204
|
+
if (m.deletedAt === void 0 && m.deletedBy === void 0) {
|
|
2205
|
+
updated = m;
|
|
2206
|
+
continue;
|
|
2207
|
+
}
|
|
1916
2208
|
delete m.deletedAt;
|
|
1917
2209
|
delete m.deletedBy;
|
|
1918
2210
|
updated = m;
|
|
2211
|
+
changed = true;
|
|
2212
|
+
}
|
|
2213
|
+
if (changed) {
|
|
2214
|
+
const serialized = all.map((m) => JSON.stringify(m)).join(LINE_SEPARATOR) + LINE_SEPARATOR;
|
|
2215
|
+
await fsp.writeFile(this.messagePath, serialized, "utf8");
|
|
1919
2216
|
}
|
|
1920
|
-
const serialized = all.map((m) => JSON.stringify(m)).join(LINE_SEPARATOR) + LINE_SEPARATOR;
|
|
1921
|
-
await fsp.writeFile(this.messagePath, serialized, "utf8");
|
|
1922
2217
|
const { mtimeMs, size } = await this._statMessageFile();
|
|
1923
2218
|
this._setMessageCache(all, mtimeMs, size);
|
|
1924
2219
|
});
|
|
1925
2220
|
if (updated !== null) {
|
|
2221
|
+
const msg = updated;
|
|
1926
2222
|
this.publishHqMailboxEvent({
|
|
1927
2223
|
mailboxId: this.hqMailboxId,
|
|
1928
2224
|
action: "message.updated",
|
|
1929
|
-
message:
|
|
2225
|
+
message: msg
|
|
1930
2226
|
});
|
|
1931
2227
|
this.publishHqMailboxSnapshot();
|
|
2228
|
+
this.eventEmitter?.emit({
|
|
2229
|
+
type: "message.restored",
|
|
2230
|
+
messageId: msg.id,
|
|
2231
|
+
from: msg.from,
|
|
2232
|
+
to: msg.to,
|
|
2233
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2234
|
+
});
|
|
1932
2235
|
}
|
|
1933
2236
|
return updated;
|
|
1934
2237
|
}
|
|
@@ -2061,8 +2364,19 @@ var GlobalMailbox = class {
|
|
|
2061
2364
|
}
|
|
2062
2365
|
async getAgentStatuses() {
|
|
2063
2366
|
await this._ensureRegistry();
|
|
2064
|
-
|
|
2367
|
+
let registry = await this._readRegistry();
|
|
2368
|
+
const before = registry.size;
|
|
2065
2369
|
this._pruneStaleInPlace(registry);
|
|
2370
|
+
if (registry.size < before) {
|
|
2371
|
+
await withFileLock(this.registryPath, async () => {
|
|
2372
|
+
const fresh = await this._readRegistry({ fresh: true });
|
|
2373
|
+
this._pruneStaleInPlace(fresh);
|
|
2374
|
+
this._registryCache = fresh;
|
|
2375
|
+
this._registryCacheAt = Date.now();
|
|
2376
|
+
await this._writeRegistry(fresh);
|
|
2377
|
+
registry = fresh;
|
|
2378
|
+
});
|
|
2379
|
+
}
|
|
2066
2380
|
const now = Date.now();
|
|
2067
2381
|
return Array.from(registry.values()).map((a) => ({
|
|
2068
2382
|
agentId: a.agentId,
|
|
@@ -2114,6 +2428,20 @@ var GlobalMailbox = class {
|
|
|
2114
2428
|
});
|
|
2115
2429
|
this.publishHqMailboxSnapshot();
|
|
2116
2430
|
}
|
|
2431
|
+
async deregisterClient(clientId) {
|
|
2432
|
+
await this._ensureClientRegistry();
|
|
2433
|
+
await withFileLock(this.clientRegistryPath, async () => {
|
|
2434
|
+
const registry = await this._readClientRegistry({ fresh: true });
|
|
2435
|
+
this._pruneStaleClientsInPlace(registry);
|
|
2436
|
+
registry.delete(clientId);
|
|
2437
|
+
this._clientRegistryCache = registry;
|
|
2438
|
+
this._clientRegistryCacheAt = Date.now();
|
|
2439
|
+
await this._writeClientRegistry(registry);
|
|
2440
|
+
});
|
|
2441
|
+
this._lastClientHeartbeat.delete(clientId);
|
|
2442
|
+
this._events?.emitCustom("mailbox.client_deregistered", { clientId });
|
|
2443
|
+
this.publishHqMailboxSnapshot();
|
|
2444
|
+
}
|
|
2117
2445
|
async clientHeartbeat(input) {
|
|
2118
2446
|
const last = this._lastClientHeartbeat.get(input.clientId) ?? 0;
|
|
2119
2447
|
const now = Date.now();
|
|
@@ -2143,7 +2471,11 @@ var GlobalMailbox = class {
|
|
|
2143
2471
|
async getClientStatuses() {
|
|
2144
2472
|
await this._ensureClientRegistry();
|
|
2145
2473
|
const registry = await this._readClientRegistry();
|
|
2474
|
+
const before = registry.size;
|
|
2146
2475
|
this._pruneStaleClientsInPlace(registry);
|
|
2476
|
+
if (registry.size < before) {
|
|
2477
|
+
await this._writeClientRegistry(registry);
|
|
2478
|
+
}
|
|
2147
2479
|
const now = Date.now();
|
|
2148
2480
|
return Array.from(registry.values()).map((c) => ({
|
|
2149
2481
|
clientId: c.clientId,
|
|
@@ -2155,6 +2487,32 @@ var GlobalMailbox = class {
|
|
|
2155
2487
|
pid: c.pid
|
|
2156
2488
|
})).sort((a, b) => b.lastSeenAt.localeCompare(a.lastSeenAt));
|
|
2157
2489
|
}
|
|
2490
|
+
/**
|
|
2491
|
+
* Explicitly purge stale clients from the registry and write back to disk.
|
|
2492
|
+
* Removes client entries whose lastSeenAt is older than CLIENT_STALE_MS.
|
|
2493
|
+
* Returns the number of entries purged.
|
|
2494
|
+
*
|
|
2495
|
+
* Idempotent — safe to call regularly. The same pruning runs implicitly
|
|
2496
|
+
* inside registerClient, clientHeartbeat, and getClientStatuses, but if
|
|
2497
|
+
* no client registers or heartbeats for a long time, this public method
|
|
2498
|
+
* ensures the file gets cleaned up on demand.
|
|
2499
|
+
*/
|
|
2500
|
+
async purgeClients() {
|
|
2501
|
+
await this._ensureClientRegistry();
|
|
2502
|
+
let purged = 0;
|
|
2503
|
+
await withFileLock(this.clientRegistryPath, async () => {
|
|
2504
|
+
const registry = await this._readClientRegistry({ fresh: true });
|
|
2505
|
+
const before = registry.size;
|
|
2506
|
+
this._pruneStaleClientsInPlace(registry);
|
|
2507
|
+
purged = before - registry.size;
|
|
2508
|
+
if (purged > 0) {
|
|
2509
|
+
this._clientRegistryCache = registry;
|
|
2510
|
+
this._clientRegistryCacheAt = Date.now();
|
|
2511
|
+
await this._writeClientRegistry(registry);
|
|
2512
|
+
}
|
|
2513
|
+
});
|
|
2514
|
+
return purged;
|
|
2515
|
+
}
|
|
2158
2516
|
// ── Lifecycle ───────────────────────────────────────────────────────────
|
|
2159
2517
|
async close() {
|
|
2160
2518
|
this._registryCache = null;
|
|
@@ -2162,6 +2520,8 @@ var GlobalMailbox = class {
|
|
|
2162
2520
|
this._messageCache = null;
|
|
2163
2521
|
this._messageCacheMtime = -1;
|
|
2164
2522
|
this._messageCacheSize = -1;
|
|
2523
|
+
this._recipientIndex = null;
|
|
2524
|
+
this._senderIndex = null;
|
|
2165
2525
|
}
|
|
2166
2526
|
async clearAll() {
|
|
2167
2527
|
await withFileLock(this.messagePath, async () => {
|
|
@@ -2210,6 +2570,100 @@ var GlobalMailbox = class {
|
|
|
2210
2570
|
remaining
|
|
2211
2571
|
};
|
|
2212
2572
|
}
|
|
2573
|
+
// ── Auto-compaction ─────────────────────────────────────────────────────
|
|
2574
|
+
_autoCompactTimer = null;
|
|
2575
|
+
async autoCompact(opts) {
|
|
2576
|
+
const readMaxAgeMs = opts?.readMaxAgeMs ?? AUTO_COMPACT_READ_MAX_AGE_MS;
|
|
2577
|
+
const defaultTtlMs = opts?.defaultTtlMs ?? AUTO_COMPACT_DEFAULT_TTL_MS;
|
|
2578
|
+
const completedMaxAgeMs = opts?.completedMaxAgeMs ?? 864e5;
|
|
2579
|
+
const incompleteMaxAgeMs = opts?.incompleteMaxAgeMs ?? 6048e5;
|
|
2580
|
+
let expiredPurged = 0;
|
|
2581
|
+
let readByAllPurged = 0;
|
|
2582
|
+
let completedPurged = 0;
|
|
2583
|
+
let incompletePurged = 0;
|
|
2584
|
+
let remaining = 0;
|
|
2585
|
+
let onlineAgentIds = null;
|
|
2586
|
+
try {
|
|
2587
|
+
const statuses = await this.getAgentStatuses();
|
|
2588
|
+
const online = statuses.filter((s) => s.online);
|
|
2589
|
+
onlineAgentIds = online.length > 0 ? new Set(online.map((s) => s.agentId)) : null;
|
|
2590
|
+
} catch {
|
|
2591
|
+
onlineAgentIds = null;
|
|
2592
|
+
}
|
|
2593
|
+
await withFileLock(this.messagePath, async () => {
|
|
2594
|
+
const all = await this._readMessagesFresh();
|
|
2595
|
+
const now = Date.now();
|
|
2596
|
+
const cutoffReadAge = now - readMaxAgeMs;
|
|
2597
|
+
const cutoffCompleted = now - completedMaxAgeMs;
|
|
2598
|
+
const cutoffIncomplete = now - incompleteMaxAgeMs;
|
|
2599
|
+
const cutoffDefaultTtl = now - defaultTtlMs;
|
|
2600
|
+
const kept = [];
|
|
2601
|
+
for (const msg of all) {
|
|
2602
|
+
const msgTime = new Date(msg.timestamp).getTime();
|
|
2603
|
+
if (msg.expiresAt !== void 0) {
|
|
2604
|
+
if (new Date(msg.expiresAt).getTime() < now) {
|
|
2605
|
+
expiredPurged++;
|
|
2606
|
+
continue;
|
|
2607
|
+
}
|
|
2608
|
+
} else if (msgTime < cutoffDefaultTtl) {
|
|
2609
|
+
expiredPurged++;
|
|
2610
|
+
continue;
|
|
2611
|
+
}
|
|
2612
|
+
if (onlineAgentIds !== null && !msg.completed) {
|
|
2613
|
+
const readByAll = onlineAgentIds.size > 0 && [...onlineAgentIds].every((id) => id in msg.readBy);
|
|
2614
|
+
if (readByAll) {
|
|
2615
|
+
const readTimes = Object.values(msg.readBy).map((t) => new Date(t).getTime());
|
|
2616
|
+
const latestRead = Math.max(...readTimes);
|
|
2617
|
+
if (latestRead < cutoffReadAge) {
|
|
2618
|
+
readByAllPurged++;
|
|
2619
|
+
continue;
|
|
2620
|
+
}
|
|
2621
|
+
}
|
|
2622
|
+
}
|
|
2623
|
+
const completedTime = msg.completedAt ? new Date(msg.completedAt).getTime() : 0;
|
|
2624
|
+
if (msg.completed && completedTime < cutoffCompleted) {
|
|
2625
|
+
completedPurged++;
|
|
2626
|
+
continue;
|
|
2627
|
+
}
|
|
2628
|
+
if (!msg.completed && msgTime < cutoffIncomplete) {
|
|
2629
|
+
incompletePurged++;
|
|
2630
|
+
continue;
|
|
2631
|
+
}
|
|
2632
|
+
kept.push(msg);
|
|
2633
|
+
}
|
|
2634
|
+
remaining = kept.length;
|
|
2635
|
+
if (kept.length < all.length) {
|
|
2636
|
+
const content = kept.map((m) => JSON.stringify(m)).join(LINE_SEPARATOR) + LINE_SEPARATOR;
|
|
2637
|
+
await fsp.writeFile(this.messagePath, content, "utf8");
|
|
2638
|
+
}
|
|
2639
|
+
const { mtimeMs, size } = await this._statMessageFile();
|
|
2640
|
+
this._setMessageCache(kept, mtimeMs, size);
|
|
2641
|
+
});
|
|
2642
|
+
const totalRemoved = expiredPurged + readByAllPurged + completedPurged + incompletePurged;
|
|
2643
|
+
return {
|
|
2644
|
+
expiredRemoved: expiredPurged,
|
|
2645
|
+
readByAllRemoved: readByAllPurged,
|
|
2646
|
+
stalePurged: completedPurged + incompletePurged,
|
|
2647
|
+
totalRemoved,
|
|
2648
|
+
remaining
|
|
2649
|
+
};
|
|
2650
|
+
}
|
|
2651
|
+
startAutoCompactTimer(opts) {
|
|
2652
|
+
if (this._autoCompactTimer !== null) {
|
|
2653
|
+
clearInterval(this._autoCompactTimer);
|
|
2654
|
+
}
|
|
2655
|
+
const intervalMs = opts?.intervalMs ?? AUTO_COMPACT_INTERVAL_MS;
|
|
2656
|
+
const timer = setInterval(() => {
|
|
2657
|
+
this.autoCompact(opts).catch(() => {
|
|
2658
|
+
});
|
|
2659
|
+
}, intervalMs);
|
|
2660
|
+
timer.unref?.();
|
|
2661
|
+
this._autoCompactTimer = timer;
|
|
2662
|
+
return () => {
|
|
2663
|
+
clearInterval(timer);
|
|
2664
|
+
this._autoCompactTimer = null;
|
|
2665
|
+
};
|
|
2666
|
+
}
|
|
2213
2667
|
// ── Internal ────────────────────────────────────────────────────────────
|
|
2214
2668
|
/**
|
|
2215
2669
|
* Read all messages from the JSONL file. Always reads + parses the file.
|
|
@@ -2242,44 +2696,59 @@ var GlobalMailbox = class {
|
|
|
2242
2696
|
const buf = Buffer.alloc(tailLen);
|
|
2243
2697
|
await fd.read(buf, 0, tailLen, oldSize);
|
|
2244
2698
|
const tail = buf.toString("utf8");
|
|
2245
|
-
|
|
2699
|
+
if (!tail.endsWith(LINE_SEPARATOR)) {
|
|
2700
|
+
const lastSeparatorIdx = tail.lastIndexOf(LINE_SEPARATOR);
|
|
2701
|
+
if (lastSeparatorIdx === -1) {
|
|
2702
|
+
return this._messageCache;
|
|
2703
|
+
}
|
|
2704
|
+
const completeTail = tail.slice(0, lastSeparatorIdx + 1);
|
|
2705
|
+
this._appendIncrementalLines(completeTail);
|
|
2706
|
+
this._messageCacheSize = oldSize + lastSeparatorIdx + 1;
|
|
2707
|
+
return this._messageCache;
|
|
2708
|
+
}
|
|
2709
|
+
this._appendIncrementalLines(tail);
|
|
2710
|
+
this._messageCacheSize = newSize;
|
|
2711
|
+
return this._messageCache;
|
|
2712
|
+
}
|
|
2713
|
+
_appendIncrementalLines(raw) {
|
|
2714
|
+
for (const line of raw.split(LINE_SEPARATOR)) {
|
|
2246
2715
|
if (!line.trim()) continue;
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
delete parsed["readAt"];
|
|
2716
|
+
const message = this._parseLine(line);
|
|
2717
|
+
if (message === null) continue;
|
|
2718
|
+
this._messageCache.push(message);
|
|
2719
|
+
const idx = this._messageCache.length - 1;
|
|
2720
|
+
if (this._recipientIndex !== null) {
|
|
2721
|
+
let set = this._recipientIndex.get(message.to);
|
|
2722
|
+
if (set === void 0) {
|
|
2723
|
+
set = /* @__PURE__ */ new Set();
|
|
2724
|
+
this._recipientIndex.set(message.to, set);
|
|
2257
2725
|
}
|
|
2258
|
-
|
|
2259
|
-
}
|
|
2726
|
+
set.add(idx);
|
|
2727
|
+
}
|
|
2728
|
+
if (this._senderIndex !== null) {
|
|
2729
|
+
let set = this._senderIndex.get(message.from);
|
|
2730
|
+
if (set === void 0) {
|
|
2731
|
+
set = /* @__PURE__ */ new Set();
|
|
2732
|
+
this._senderIndex.set(message.from, set);
|
|
2733
|
+
}
|
|
2734
|
+
set.add(idx);
|
|
2260
2735
|
}
|
|
2261
2736
|
}
|
|
2262
|
-
|
|
2737
|
+
}
|
|
2738
|
+
_parseLine(line) {
|
|
2739
|
+
try {
|
|
2740
|
+
return parseMailboxMessageLine(line);
|
|
2741
|
+
} catch {
|
|
2742
|
+
return null;
|
|
2743
|
+
}
|
|
2263
2744
|
}
|
|
2264
2745
|
/** Parse a JSONL string into MailboxMessage[], including migration. */
|
|
2265
2746
|
_parseLines(raw) {
|
|
2266
2747
|
const lines = raw.split(LINE_SEPARATOR).filter((l) => l.trim().length > 0);
|
|
2267
2748
|
const messages = [];
|
|
2268
2749
|
for (const line of lines) {
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
if (!parsed["readBy"]) {
|
|
2272
|
-
const readBy = {};
|
|
2273
|
-
if (parsed["read"] && parsed["readAt"]) {
|
|
2274
|
-
readBy[parsed["to"]] = parsed["readAt"];
|
|
2275
|
-
}
|
|
2276
|
-
parsed["readBy"] = readBy;
|
|
2277
|
-
delete parsed["read"];
|
|
2278
|
-
delete parsed["readAt"];
|
|
2279
|
-
}
|
|
2280
|
-
messages.push(parsed);
|
|
2281
|
-
} catch {
|
|
2282
|
-
}
|
|
2750
|
+
const message = this._parseLine(line);
|
|
2751
|
+
if (message !== null) messages.push(message);
|
|
2283
2752
|
}
|
|
2284
2753
|
return messages;
|
|
2285
2754
|
}
|
|
@@ -2367,7 +2836,6 @@ var GlobalMailbox = class {
|
|
|
2367
2836
|
try {
|
|
2368
2837
|
const updated = await this._readNewMessagesOnly(fd, this._messageCacheSize, st.size);
|
|
2369
2838
|
this._messageCacheMtime = st.mtimeMs;
|
|
2370
|
-
this._messageCacheSize = st.size;
|
|
2371
2839
|
return updated;
|
|
2372
2840
|
} finally {
|
|
2373
2841
|
await fd.close();
|
|
@@ -2404,11 +2872,46 @@ var GlobalMailbox = class {
|
|
|
2404
2872
|
this._messageCache = null;
|
|
2405
2873
|
this._messageCacheMtime = -1;
|
|
2406
2874
|
this._messageCacheSize = -1;
|
|
2875
|
+
this._recipientIndex = null;
|
|
2876
|
+
this._senderIndex = null;
|
|
2407
2877
|
return;
|
|
2408
2878
|
}
|
|
2409
2879
|
this._messageCache = messages;
|
|
2410
2880
|
this._messageCacheMtime = mtime;
|
|
2411
2881
|
this._messageCacheSize = size;
|
|
2882
|
+
this._rebuildRecipientIndex();
|
|
2883
|
+
}
|
|
2884
|
+
/**
|
|
2885
|
+
* Build the recipient AND sender indexes from the current `_messageCache`.
|
|
2886
|
+
* Called only from `_setMessageCache` so both indexes are always in sync
|
|
2887
|
+
* with a freshly-replaced cache array.
|
|
2888
|
+
*/
|
|
2889
|
+
_rebuildRecipientIndex() {
|
|
2890
|
+
const cache = this._messageCache;
|
|
2891
|
+
if (cache === null) {
|
|
2892
|
+
this._recipientIndex = null;
|
|
2893
|
+
this._senderIndex = null;
|
|
2894
|
+
return;
|
|
2895
|
+
}
|
|
2896
|
+
const recIdx = /* @__PURE__ */ new Map();
|
|
2897
|
+
const sndIdx = /* @__PURE__ */ new Map();
|
|
2898
|
+
for (let i = 0; i < cache.length; i++) {
|
|
2899
|
+
const msg = cache[i];
|
|
2900
|
+
let rSet = recIdx.get(msg.to);
|
|
2901
|
+
if (rSet === void 0) {
|
|
2902
|
+
rSet = /* @__PURE__ */ new Set();
|
|
2903
|
+
recIdx.set(msg.to, rSet);
|
|
2904
|
+
}
|
|
2905
|
+
rSet.add(i);
|
|
2906
|
+
let sSet = sndIdx.get(msg.from);
|
|
2907
|
+
if (sSet === void 0) {
|
|
2908
|
+
sSet = /* @__PURE__ */ new Set();
|
|
2909
|
+
sndIdx.set(msg.from, sSet);
|
|
2910
|
+
}
|
|
2911
|
+
sSet.add(i);
|
|
2912
|
+
}
|
|
2913
|
+
this._recipientIndex = recIdx;
|
|
2914
|
+
this._senderIndex = sndIdx;
|
|
2412
2915
|
}
|
|
2413
2916
|
/**
|
|
2414
2917
|
* Append a single just-sent message to the in-memory cache without
|
|
@@ -2435,6 +2938,23 @@ var GlobalMailbox = class {
|
|
|
2435
2938
|
return;
|
|
2436
2939
|
}
|
|
2437
2940
|
this._messageCache.push(msg);
|
|
2941
|
+
const newIdx = this._messageCache.length - 1;
|
|
2942
|
+
if (this._recipientIndex !== null) {
|
|
2943
|
+
let set = this._recipientIndex.get(msg.to);
|
|
2944
|
+
if (set === void 0) {
|
|
2945
|
+
set = /* @__PURE__ */ new Set();
|
|
2946
|
+
this._recipientIndex.set(msg.to, set);
|
|
2947
|
+
}
|
|
2948
|
+
set.add(newIdx);
|
|
2949
|
+
}
|
|
2950
|
+
if (this._senderIndex !== null) {
|
|
2951
|
+
let set = this._senderIndex.get(msg.from);
|
|
2952
|
+
if (set === void 0) {
|
|
2953
|
+
set = /* @__PURE__ */ new Set();
|
|
2954
|
+
this._senderIndex.set(msg.from, set);
|
|
2955
|
+
}
|
|
2956
|
+
set.add(newIdx);
|
|
2957
|
+
}
|
|
2438
2958
|
this._messageCacheMtime = mtime;
|
|
2439
2959
|
this._messageCacheSize = size;
|
|
2440
2960
|
}
|
|
@@ -2466,22 +2986,13 @@ var GlobalMailbox = class {
|
|
|
2466
2986
|
}
|
|
2467
2987
|
}
|
|
2468
2988
|
_pruneStaleInPlace(registry) {
|
|
2469
|
-
const
|
|
2470
|
-
const purgeCutoff = Date.now() - AGENT_PURGE_MS;
|
|
2471
|
-
const toDelete = [];
|
|
2989
|
+
const cutoff = Date.now() - AGENT_STALE_MS;
|
|
2472
2990
|
for (const [id, agent] of registry) {
|
|
2473
|
-
const lastSeen =
|
|
2474
|
-
if (lastSeen <
|
|
2475
|
-
|
|
2476
|
-
continue;
|
|
2477
|
-
}
|
|
2478
|
-
if (lastSeen < staleCutoff) {
|
|
2479
|
-
agent.status = "idle";
|
|
2991
|
+
const lastSeen = Date.parse(agent.lastSeenAt);
|
|
2992
|
+
if (!Number.isFinite(lastSeen) || lastSeen < cutoff) {
|
|
2993
|
+
registry.delete(id);
|
|
2480
2994
|
}
|
|
2481
2995
|
}
|
|
2482
|
-
for (const id of toDelete) {
|
|
2483
|
-
registry.delete(id);
|
|
2484
|
-
}
|
|
2485
2996
|
}
|
|
2486
2997
|
async _writeRegistry(registry) {
|
|
2487
2998
|
const obj = {};
|
|
@@ -2522,9 +3033,10 @@ var GlobalMailbox = class {
|
|
|
2522
3033
|
}
|
|
2523
3034
|
_pruneStaleClientsInPlace(registry) {
|
|
2524
3035
|
const cutoff = Date.now() - CLIENT_STALE_MS;
|
|
2525
|
-
for (const client of registry
|
|
2526
|
-
|
|
2527
|
-
|
|
3036
|
+
for (const [id, client] of registry) {
|
|
3037
|
+
const lastSeen = Date.parse(client.lastSeenAt);
|
|
3038
|
+
if (!Number.isFinite(lastSeen) || lastSeen < cutoff) {
|
|
3039
|
+
registry.delete(id);
|
|
2528
3040
|
}
|
|
2529
3041
|
}
|
|
2530
3042
|
}
|
|
@@ -2540,7 +3052,7 @@ var GlobalMailbox = class {
|
|
|
2540
3052
|
};
|
|
2541
3053
|
|
|
2542
3054
|
// src/hq/auth-store.ts
|
|
2543
|
-
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
3055
|
+
import { randomBytes as randomBytes2, randomUUID as randomUUID3, scrypt, timingSafeEqual } from "node:crypto";
|
|
2544
3056
|
import * as fs2 from "node:fs/promises";
|
|
2545
3057
|
import * as syncFs from "node:fs";
|
|
2546
3058
|
import * as path5 from "node:path";
|
|
@@ -2654,6 +3166,10 @@ async function ensureHqFirstRunAuthFile(dataDir, opts = {}) {
|
|
|
2654
3166
|
browserTokens: [browserToken],
|
|
2655
3167
|
clientTokens: [clientToken]
|
|
2656
3168
|
};
|
|
3169
|
+
if (opts.password) {
|
|
3170
|
+
authFile.passwordHash = await hashHqPassword(opts.password);
|
|
3171
|
+
authFile.cookieSecret = mintHqCookieSecret();
|
|
3172
|
+
}
|
|
2657
3173
|
await writeHqAuthFile(dataDir, authFile);
|
|
2658
3174
|
return { authFile: await readHqAuthFile(dataDir, opts), created: true, browserToken, clientToken };
|
|
2659
3175
|
}
|
|
@@ -2663,6 +3179,42 @@ async function mutateHqAuthFile(dataDir, mutator, opts = {}) {
|
|
|
2663
3179
|
await writeHqAuthFile(dataDir, next);
|
|
2664
3180
|
return next;
|
|
2665
3181
|
}
|
|
3182
|
+
var HQ_PASSWORD_SALT_BYTES = 16;
|
|
3183
|
+
var HQ_PASSWORD_HASH_BYTES = 32;
|
|
3184
|
+
var HQ_PASSWORD_SCRYPT_PARAMS = { N: 16384, r: 8, p: 1 };
|
|
3185
|
+
function hashHqPassword(password) {
|
|
3186
|
+
return new Promise((resolve4, reject) => {
|
|
3187
|
+
const salt = randomBytes2(HQ_PASSWORD_SALT_BYTES);
|
|
3188
|
+
scrypt(password, salt, HQ_PASSWORD_HASH_BYTES, HQ_PASSWORD_SCRYPT_PARAMS, (err, derivedKey) => {
|
|
3189
|
+
if (err) return reject(err);
|
|
3190
|
+
const payload = `scrypt$${salt.toString("base64url")}$${derivedKey.toString("base64url")}`;
|
|
3191
|
+
resolve4(payload);
|
|
3192
|
+
});
|
|
3193
|
+
});
|
|
3194
|
+
}
|
|
3195
|
+
async function verifyHqPassword(password, hash) {
|
|
3196
|
+
const parts = hash.split("$");
|
|
3197
|
+
if (parts.length !== 3 || parts[0] !== "scrypt" || !parts[1] || !parts[2]) return false;
|
|
3198
|
+
let salt;
|
|
3199
|
+
let expected;
|
|
3200
|
+
try {
|
|
3201
|
+
salt = Buffer.from(parts[1], "base64url");
|
|
3202
|
+
expected = Buffer.from(parts[2], "base64url");
|
|
3203
|
+
} catch {
|
|
3204
|
+
return false;
|
|
3205
|
+
}
|
|
3206
|
+
if (salt.length === 0 || expected.length === 0) return false;
|
|
3207
|
+
const derived = await new Promise((resolve4, reject) => {
|
|
3208
|
+
scrypt(password, salt, expected.length, HQ_PASSWORD_SCRYPT_PARAMS, (err, key) => {
|
|
3209
|
+
if (err) reject(err);
|
|
3210
|
+
else resolve4(key);
|
|
3211
|
+
});
|
|
3212
|
+
});
|
|
3213
|
+
return timingSafeEqual(derived, expected);
|
|
3214
|
+
}
|
|
3215
|
+
function mintHqCookieSecret() {
|
|
3216
|
+
return randomBytes2(32).toString("base64url");
|
|
3217
|
+
}
|
|
2666
3218
|
function mintHqToken(label) {
|
|
2667
3219
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2668
3220
|
return {
|
|
@@ -2833,7 +3385,8 @@ function createHqPublisherFromEnv(options) {
|
|
|
2833
3385
|
// Auto-discovery: re-read the local HQ runtime marker + client token on
|
|
2834
3386
|
// every connect attempt so late-started/restarted HQs are picked up.
|
|
2835
3387
|
...config.discover ? { resolveEndpoint: () => discoverLocalHqEndpoint({ dataDir: discoveryDataDir }) } : {},
|
|
2836
|
-
...options.discoveryPollMs !== void 0 ? { discoveryPollMs: options.discoveryPollMs } : {}
|
|
3388
|
+
...options.discoveryPollMs !== void 0 ? { discoveryPollMs: options.discoveryPollMs } : {},
|
|
3389
|
+
...options.logger !== void 0 ? { logger: options.logger } : {}
|
|
2837
3390
|
});
|
|
2838
3391
|
}
|
|
2839
3392
|
function createGlobalMailbox(options) {
|
|
@@ -4099,6 +4652,7 @@ export {
|
|
|
4099
4652
|
discoverLocalHqEndpoint,
|
|
4100
4653
|
emptyHqAuthFile,
|
|
4101
4654
|
ensureHqFirstRunAuthFile,
|
|
4655
|
+
hashHqPassword,
|
|
4102
4656
|
hqAuthFilePath,
|
|
4103
4657
|
hqRuntimeFilePath,
|
|
4104
4658
|
mapMailboxAgentToHqSummary,
|
|
@@ -4106,6 +4660,7 @@ export {
|
|
|
4106
4660
|
mapSessionEventToEntries,
|
|
4107
4661
|
mergeToolResults,
|
|
4108
4662
|
mintHqBrowserToken,
|
|
4663
|
+
mintHqCookieSecret,
|
|
4109
4664
|
mintHqToken,
|
|
4110
4665
|
mutateHqAuthFile,
|
|
4111
4666
|
parseHqEventPayload,
|
|
@@ -4131,6 +4686,7 @@ export {
|
|
|
4131
4686
|
toAlertMessage,
|
|
4132
4687
|
tokenHasCapability,
|
|
4133
4688
|
validateHqCommand,
|
|
4689
|
+
verifyHqPassword,
|
|
4134
4690
|
watchHqAuthFile,
|
|
4135
4691
|
writeHqAuthFile,
|
|
4136
4692
|
writeHqRuntimeFile
|