@wrongstack/core 0.277.2 → 0.280.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-BFJ2ODzI.d.ts → agent-bridge-DXC6QDJ4.d.ts} +1 -1
- package/dist/{agent-subagent-runner-BimKihiC.d.ts → agent-subagent-runner-PoqNKiR4.d.ts} +563 -471
- package/dist/{compactor-D3BGw26y.d.ts → compactor-U3agvUIG.d.ts} +1 -1
- package/dist/{config-DAOjriz9.d.ts → config-Cr3312zc.d.ts} +102 -4
- package/dist/coordination/index.d.ts +1087 -998
- package/dist/coordination/index.js +12235 -12052
- package/dist/coordination/index.js.map +1 -1
- package/dist/defaults/index.d.ts +31 -30
- package/dist/defaults/index.js +403 -189
- package/dist/defaults/index.js.map +1 -1
- package/dist/{brain-CCfuEOdp.d.ts → events-Bs2fmldo.d.ts} +117 -112
- package/dist/execution/index.d.ts +27 -19
- package/dist/execution/index.js +216 -63
- package/dist/execution/index.js.map +1 -1
- package/dist/execution/prompt-enhancer.d.ts +1 -1
- package/dist/execution/prompt-enhancer.js.map +1 -1
- package/dist/extension/index.d.ts +8 -7
- package/dist/{global-mailbox-Dr4cTKqL.d.ts → global-mailbox-Ct7IorLJ.d.ts} +84 -6
- package/dist/{goal-store-C1uH4srH.d.ts → goal-store-C4F6DjC0.d.ts} +1 -1
- package/dist/hq/index.d.ts +504 -7
- package/dist/hq/index.js +1069 -20
- package/dist/hq/index.js.map +1 -1
- package/dist/{index-DJXj-dcr.d.ts → index-kidebiDh.d.ts} +8 -5
- package/dist/{index-cMEmzCVN.d.ts → index-nP09-oP2.d.ts} +2 -2
- package/dist/index.d.ts +153 -76
- package/dist/index.js +5791 -3163
- package/dist/index.js.map +1 -1
- package/dist/infrastructure/index.d.ts +7 -6
- package/dist/kernel/index.d.ts +14 -13
- package/dist/kernel/index.js +31 -15
- package/dist/kernel/index.js.map +1 -1
- package/dist/{mailbox-types-DTl7bRH3.d.ts → mailbox-types-BGZWrYTJ.d.ts} +38 -0
- package/dist/{mcp-servers-CFb60-pH.d.ts → mcp-servers-D910X5_r.d.ts} +3 -3
- package/dist/models/index.d.ts +5 -5
- package/dist/models/index.js.map +1 -1
- package/dist/{models-registry-5Ufn7f2m.d.ts → models-registry-CLkoOcHk.d.ts} +1 -1
- package/dist/{multi-agent-coordinator-CcrcncvG.d.ts → multi-agent-coordinator-CieyUoEL.d.ts} +1 -1
- package/dist/{null-fleet-bus-C9KsYyrI.d.ts → null-fleet-bus-DkdmZJ_W.d.ts} +464 -464
- package/dist/observability/index.d.ts +3 -2
- package/dist/{path-resolver-CEeX9I7O.d.ts → path-resolver-XfZ9eLxG.d.ts} +3 -3
- package/dist/{permission-DbsGOA1C.d.ts → permission-Dx6dIqS2.d.ts} +2 -7
- package/dist/{permission-policy-BpEea3r7.d.ts → permission-policy-C8vJcnX5.d.ts} +2 -2
- package/dist/{pipeline-CEjBjzVA.d.ts → pipeline-BwAP21_4.d.ts} +9 -4
- package/dist/{provider-model-resolve-BpfXp3Jj.d.ts → provider-model-resolve-CwQNZWt_.d.ts} +3 -3
- package/dist/{provider-runner-CnOSr5BN.d.ts → provider-runner-CYHFImzV.d.ts} +3 -3
- package/dist/{retry-policy-Git9WF6d.d.ts → retry-policy-D4feSLk3.d.ts} +1 -1
- package/dist/sdd/index.d.ts +11 -10
- package/dist/sdd/index.js +2 -2
- package/dist/sdd/index.js.map +1 -1
- package/dist/secret-scrubber-3MHDDAtm.d.ts +6 -0
- package/dist/{secret-vault-DDSMHqIm.d.ts → secret-vault-CImt2XrR.d.ts} +1 -1
- package/dist/security/index.d.ts +6 -5
- package/dist/security/index.js.map +1 -1
- package/dist/{selector-Cq72C0Oy.d.ts → selector-Dy-MzKp1.d.ts} +1 -1
- package/dist/{session-event-bridge-DG94B3Bk.d.ts → session-event-bridge-CqdiGnfU.d.ts} +1 -1
- package/dist/{session-reader-BzT-iMQT.d.ts → session-reader-Hk0WbNm9.d.ts} +1 -1
- package/dist/{skill-DGIXCtdv.d.ts → skill-DHniprNl.d.ts} +15 -1
- package/dist/skills/index.d.ts +472 -26
- package/dist/skills/index.js +872 -129
- package/dist/skills/index.js.map +1 -1
- package/dist/storage/index.d.ts +27 -14
- package/dist/storage/index.js +264 -85
- package/dist/storage/index.js.map +1 -1
- package/dist/{strategy-compactor-Bt_ZH6R0.d.ts → strategy-compactor-CQwhbErd.d.ts} +32 -17
- package/dist/{todos-checkpoint-CH1pcua9.d.ts → todos-checkpoint-Bk2uP7Ex.d.ts} +6 -6
- package/dist/{context-DPlA6kid.d.ts → tool-BkOgs_KL.d.ts} +306 -286
- package/dist/{tool-executor-SVFq7IOR.d.ts → tool-executor-SiE1wlZo.d.ts} +9 -9
- package/dist/tools/index.d.ts +2 -2
- package/dist/tools/index.js.map +1 -1
- package/dist/types/index.d.ts +22 -21
- package/dist/types/index.js +7 -9
- package/dist/types/index.js.map +1 -1
- package/dist/utils/index.d.ts +30 -4
- package/dist/utils/index.js +50 -1
- package/dist/utils/index.js.map +1 -1
- package/dist/{worktree-manager-C4YIf1Fa.d.ts → worktree-manager-BjOFF6bt.d.ts} +1 -1
- package/dist/{wstack-paths-_NrRovdr.d.ts → wstack-paths-CMl_cYgq.d.ts} +8 -0
- package/package.json +1 -1
- package/skills/mailbox-bridge/SKILL.md +1 -0
- package/skills/plugin-author/SKILL.md +350 -0
- package/skills/sdd/SKILL.md +134 -134
- package/skills/skill-creator/SKILL.md +45 -7
- package/skills/wrongstack-mailbox/SKILL.md +40 -21
package/dist/hq/index.js
CHANGED
|
@@ -96,7 +96,8 @@ function parseHqFrame(raw) {
|
|
|
96
96
|
clientId: obj.clientId,
|
|
97
97
|
projectId: obj.projectId,
|
|
98
98
|
commandId: obj.commandId,
|
|
99
|
-
status: obj.status
|
|
99
|
+
status: obj.status,
|
|
100
|
+
...typeof obj.message === "string" ? { message: obj.message } : {}
|
|
100
101
|
}
|
|
101
102
|
};
|
|
102
103
|
default: {
|
|
@@ -110,7 +111,14 @@ var KNOWN_HQ_EVENT_PAYLOAD_TYPES = /* @__PURE__ */ new Set([
|
|
|
110
111
|
"mailbox.event",
|
|
111
112
|
"session.snapshot",
|
|
112
113
|
"session.transcript",
|
|
113
|
-
"session.ended"
|
|
114
|
+
"session.ended",
|
|
115
|
+
"fleet.snapshot",
|
|
116
|
+
"fleet.event",
|
|
117
|
+
"brain.event",
|
|
118
|
+
"worktree.event",
|
|
119
|
+
"tool.started",
|
|
120
|
+
"tool.completed",
|
|
121
|
+
"session.usage"
|
|
114
122
|
]);
|
|
115
123
|
function isHqMailboxMessageSummary(x) {
|
|
116
124
|
if (typeof x !== "object" || x === null) return false;
|
|
@@ -147,7 +155,8 @@ var HQ_MAILBOX_EVENT_ACTIONS = /* @__PURE__ */ new Set([
|
|
|
147
155
|
"message.updated",
|
|
148
156
|
"agent.registered",
|
|
149
157
|
"agent.heartbeat",
|
|
150
|
-
"agent.offline"
|
|
158
|
+
"agent.offline",
|
|
159
|
+
"agent.deregistered"
|
|
151
160
|
]);
|
|
152
161
|
function isHqMailboxEventPayload(x) {
|
|
153
162
|
if (typeof x !== "object" || x === null) return false;
|
|
@@ -204,6 +213,79 @@ function isHqSessionEndedPayload(x) {
|
|
|
204
213
|
const v = x;
|
|
205
214
|
return typeof v.sessionId === "string" && typeof v.endedAt === "string";
|
|
206
215
|
}
|
|
216
|
+
var HQ_FLEET_SUBAGENT_STATUS = /* @__PURE__ */ new Set([
|
|
217
|
+
"pending",
|
|
218
|
+
"running",
|
|
219
|
+
"idle",
|
|
220
|
+
"completed",
|
|
221
|
+
"failed",
|
|
222
|
+
"stopped"
|
|
223
|
+
]);
|
|
224
|
+
function isHqSubagentSummary(x) {
|
|
225
|
+
if (typeof x !== "object" || x === null) return false;
|
|
226
|
+
const v = x;
|
|
227
|
+
return typeof v.subagentId === "string" && typeof v.status === "string" && HQ_FLEET_SUBAGENT_STATUS.has(v.status);
|
|
228
|
+
}
|
|
229
|
+
function isHqFleetSnapshotPayload(x) {
|
|
230
|
+
if (typeof x !== "object" || x === null) return false;
|
|
231
|
+
const v = x;
|
|
232
|
+
if (typeof v.runId !== "string" || typeof v.activeSubagents !== "number" || typeof v.queuedTasks !== "number" || typeof v.completedTasks !== "number" || typeof v.failedTasks !== "number" || !Array.isArray(v.subagents)) {
|
|
233
|
+
return false;
|
|
234
|
+
}
|
|
235
|
+
for (const s of v.subagents) {
|
|
236
|
+
if (!isHqSubagentSummary(s)) return false;
|
|
237
|
+
}
|
|
238
|
+
return true;
|
|
239
|
+
}
|
|
240
|
+
function isHqFleetEventPayload(x) {
|
|
241
|
+
if (typeof x !== "object" || x === null) return false;
|
|
242
|
+
const v = x;
|
|
243
|
+
return typeof v.runId === "string" && typeof v.event === "string";
|
|
244
|
+
}
|
|
245
|
+
var HQ_BRAIN_EVENT_KINDS = /* @__PURE__ */ new Set([
|
|
246
|
+
"decision_requested",
|
|
247
|
+
"decision_answered",
|
|
248
|
+
"decision_ask_human",
|
|
249
|
+
"human_answered",
|
|
250
|
+
"decision_denied",
|
|
251
|
+
"intervention"
|
|
252
|
+
]);
|
|
253
|
+
function isHqBrainEventPayload(x) {
|
|
254
|
+
if (typeof x !== "object" || x === null) return false;
|
|
255
|
+
const v = x;
|
|
256
|
+
if (typeof v.kind !== "string" || !HQ_BRAIN_EVENT_KINDS.has(v.kind)) return false;
|
|
257
|
+
if (typeof v.at !== "number") return false;
|
|
258
|
+
return true;
|
|
259
|
+
}
|
|
260
|
+
var HQ_WORKTREE_EVENT_KINDS = /* @__PURE__ */ new Set([
|
|
261
|
+
"allocated",
|
|
262
|
+
"committed",
|
|
263
|
+
"merged",
|
|
264
|
+
"conflict",
|
|
265
|
+
"released",
|
|
266
|
+
"failed"
|
|
267
|
+
]);
|
|
268
|
+
function isHqWorktreeEventPayload(x) {
|
|
269
|
+
if (typeof x !== "object" || x === null) return false;
|
|
270
|
+
const v = x;
|
|
271
|
+
if (typeof v.kind !== "string" || !HQ_WORKTREE_EVENT_KINDS.has(v.kind)) return false;
|
|
272
|
+
if (typeof v.handleId !== "string" || typeof v.ownerId !== "string") return false;
|
|
273
|
+
return true;
|
|
274
|
+
}
|
|
275
|
+
function isHqToolStartedPayload(x) {
|
|
276
|
+
if (typeof x !== "object" || x === null) return false;
|
|
277
|
+
const v = x;
|
|
278
|
+
return typeof v.toolName === "string";
|
|
279
|
+
}
|
|
280
|
+
function isHqToolCompletedPayload(x) {
|
|
281
|
+
if (typeof x !== "object" || x === null) return false;
|
|
282
|
+
const v = x;
|
|
283
|
+
return typeof v.toolName === "string" && typeof v.status === "string" && typeof v.durationMs === "number";
|
|
284
|
+
}
|
|
285
|
+
function isHqUsagePayload(x) {
|
|
286
|
+
if (typeof x !== "object" || x === null) return false;
|
|
287
|
+
return !Array.isArray(x);
|
|
288
|
+
}
|
|
207
289
|
function parseHqEventPayload(eventType, payload) {
|
|
208
290
|
if (!KNOWN_HQ_EVENT_PAYLOAD_TYPES.has(eventType)) {
|
|
209
291
|
return { ok: true, payload };
|
|
@@ -219,6 +301,20 @@ function parseHqEventPayload(eventType, payload) {
|
|
|
219
301
|
return isHqTranscriptAppendPayload(payload) ? { ok: true, payload } : { ok: false, reason: "malformed-payload" };
|
|
220
302
|
case "session.ended":
|
|
221
303
|
return isHqSessionEndedPayload(payload) ? { ok: true, payload } : { ok: false, reason: "malformed-payload" };
|
|
304
|
+
case "fleet.snapshot":
|
|
305
|
+
return isHqFleetSnapshotPayload(payload) ? { ok: true, payload } : { ok: false, reason: "malformed-payload" };
|
|
306
|
+
case "fleet.event":
|
|
307
|
+
return isHqFleetEventPayload(payload) ? { ok: true, payload } : { ok: false, reason: "malformed-payload" };
|
|
308
|
+
case "brain.event":
|
|
309
|
+
return isHqBrainEventPayload(payload) ? { ok: true, payload } : { ok: false, reason: "malformed-payload" };
|
|
310
|
+
case "worktree.event":
|
|
311
|
+
return isHqWorktreeEventPayload(payload) ? { ok: true, payload } : { ok: false, reason: "malformed-payload" };
|
|
312
|
+
case "tool.started":
|
|
313
|
+
return isHqToolStartedPayload(payload) ? { ok: true, payload } : { ok: false, reason: "malformed-payload" };
|
|
314
|
+
case "tool.completed":
|
|
315
|
+
return isHqToolCompletedPayload(payload) ? { ok: true, payload } : { ok: false, reason: "malformed-payload" };
|
|
316
|
+
case "session.usage":
|
|
317
|
+
return isHqUsagePayload(payload) ? { ok: true, payload } : { ok: false, reason: "malformed-payload" };
|
|
222
318
|
default: {
|
|
223
319
|
const _exhaustive = eventType;
|
|
224
320
|
return _exhaustive;
|
|
@@ -723,6 +819,7 @@ function createMailboxEventPayload(input) {
|
|
|
723
819
|
var OPEN_STATE = 1;
|
|
724
820
|
var DEFAULT_RECONNECT_BASE_MS = 1e3;
|
|
725
821
|
var DEFAULT_RECONNECT_MAX_MS = 3e4;
|
|
822
|
+
var DEFAULT_DISCOVERY_POLL_MS = 5e3;
|
|
726
823
|
var DEFAULT_MAX_QUEUED_MESSAGES = 2e3;
|
|
727
824
|
var DEFAULT_COMMAND_POLL_INTERVAL_MS = 2e3;
|
|
728
825
|
var DEFAULT_COMMAND_POLL_LIMIT = 25;
|
|
@@ -760,7 +857,7 @@ var HqPublisher = class {
|
|
|
760
857
|
this.socketFactory = options.socketFactory ?? defaultSocketFactory;
|
|
761
858
|
this.now = options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
|
|
762
859
|
this.idFactory = options.idFactory ?? randomUUID;
|
|
763
|
-
this.capabilities = options.capabilities ?? ["telemetry.publish", "mailbox.summary"];
|
|
860
|
+
this.capabilities = options.capabilities ?? ["telemetry.publish", "mailbox.summary", "fleet.summary", "session.summary"];
|
|
764
861
|
this.reconnect = options.reconnect ?? true;
|
|
765
862
|
this.reconnectBaseMs = options.reconnectBaseMs ?? DEFAULT_RECONNECT_BASE_MS;
|
|
766
863
|
this.reconnectMaxMs = options.reconnectMaxMs ?? DEFAULT_RECONNECT_MAX_MS;
|
|
@@ -785,10 +882,22 @@ var HqPublisher = class {
|
|
|
785
882
|
lastCommandId;
|
|
786
883
|
connect() {
|
|
787
884
|
if (this.socket !== null || this.stopped) return;
|
|
885
|
+
if (this.reconnectTimer !== null) return;
|
|
886
|
+
let url = this.options.url;
|
|
887
|
+
let token = this.options.token;
|
|
888
|
+
if (this.options.resolveEndpoint !== void 0) {
|
|
889
|
+
const endpoint = this.options.resolveEndpoint();
|
|
890
|
+
if (endpoint === void 0) {
|
|
891
|
+
this.scheduleDiscoveryPoll();
|
|
892
|
+
return;
|
|
893
|
+
}
|
|
894
|
+
url = endpoint.url;
|
|
895
|
+
token = endpoint.token ?? token;
|
|
896
|
+
}
|
|
788
897
|
let socket;
|
|
789
898
|
try {
|
|
790
|
-
socket = this.socketFactory(toClientUrl(
|
|
791
|
-
...
|
|
899
|
+
socket = this.socketFactory(toClientUrl(url, token), {
|
|
900
|
+
...token !== void 0 ? { token } : {}
|
|
792
901
|
});
|
|
793
902
|
} catch {
|
|
794
903
|
this.scheduleReconnect();
|
|
@@ -913,6 +1022,16 @@ var HqPublisher = class {
|
|
|
913
1022
|
...opts?.timestamp !== void 0 ? { timestamp: opts.timestamp } : {}
|
|
914
1023
|
});
|
|
915
1024
|
}
|
|
1025
|
+
/** Publish a fleet (multi-agent coordinator) snapshot. */
|
|
1026
|
+
publishFleetSnapshot(payload, opts) {
|
|
1027
|
+
return this.publishEvent({
|
|
1028
|
+
type: "fleet.snapshot",
|
|
1029
|
+
payload,
|
|
1030
|
+
runId: payload.runId,
|
|
1031
|
+
...opts?.sessionId !== void 0 ? { sessionId: opts.sessionId } : {},
|
|
1032
|
+
...opts?.timestamp !== void 0 ? { timestamp: opts.timestamp } : {}
|
|
1033
|
+
});
|
|
1034
|
+
}
|
|
916
1035
|
pollCommands() {
|
|
917
1036
|
this.sendFrame({
|
|
918
1037
|
type: "client.command_poll",
|
|
@@ -1028,6 +1147,21 @@ var HqPublisher = class {
|
|
|
1028
1147
|
}, delay);
|
|
1029
1148
|
this.reconnectTimer.unref?.();
|
|
1030
1149
|
}
|
|
1150
|
+
/**
|
|
1151
|
+
* Dormant re-check while no HQ endpoint is discoverable. Uses a FIXED
|
|
1152
|
+
* interval (not the exponential reconnect backoff): the check is a cheap
|
|
1153
|
+
* local file read, and backing off would delay attaching to an HQ the
|
|
1154
|
+
* user just started — the whole point of auto-discovery.
|
|
1155
|
+
*/
|
|
1156
|
+
scheduleDiscoveryPoll() {
|
|
1157
|
+
if (this.stopped || !this.reconnect || this.reconnectTimer !== null) return;
|
|
1158
|
+
this.reconnectAttempt = 0;
|
|
1159
|
+
this.reconnectTimer = setTimeout(() => {
|
|
1160
|
+
this.reconnectTimer = null;
|
|
1161
|
+
this.connect();
|
|
1162
|
+
}, this.options.discoveryPollMs ?? DEFAULT_DISCOVERY_POLL_MS);
|
|
1163
|
+
this.reconnectTimer.unref?.();
|
|
1164
|
+
}
|
|
1031
1165
|
};
|
|
1032
1166
|
function sessionScopedPath(dir, sessionId, suffix) {
|
|
1033
1167
|
if (!sessionId || sessionId.includes("\\") || sessionId.includes("..")) {
|
|
@@ -1066,16 +1200,20 @@ function wstackGlobalRoot() {
|
|
|
1066
1200
|
}
|
|
1067
1201
|
function resolveWstackPaths(opts) {
|
|
1068
1202
|
const globalRoot = opts.globalRoot ?? (opts.userHome ? path2.join(opts.userHome, ".wrongstack") : wstackGlobalRoot());
|
|
1203
|
+
const homeDir = opts.userHome ?? os.homedir();
|
|
1069
1204
|
const hash = projectHash(opts.projectRoot);
|
|
1070
1205
|
const slug = projectSlug(opts.projectRoot);
|
|
1071
1206
|
const projectDir = path2.join(globalRoot, "projects", slug);
|
|
1072
1207
|
return {
|
|
1073
1208
|
globalRoot,
|
|
1209
|
+
projectRoot: opts.projectRoot,
|
|
1210
|
+
homeDir,
|
|
1074
1211
|
configDir: globalRoot,
|
|
1075
1212
|
globalConfig: path2.join(globalRoot, "config.json"),
|
|
1076
1213
|
secretsKey: path2.join(globalRoot, ".key"),
|
|
1077
1214
|
globalMemory: path2.join(globalRoot, "memory.md"),
|
|
1078
1215
|
globalSkills: path2.join(globalRoot, "skills"),
|
|
1216
|
+
globalClaudeSkills: path2.join(homeDir, ".claude", "skills"),
|
|
1079
1217
|
globalDesignKits: path2.join(globalRoot, "design-kits"),
|
|
1080
1218
|
globalPrompts: path2.join(globalRoot, "prompts"),
|
|
1081
1219
|
globalInstructions: path2.join(globalRoot, "instructions"),
|
|
@@ -1095,6 +1233,7 @@ function resolveWstackPaths(opts) {
|
|
|
1095
1233
|
inProjectConfig: path2.join(opts.projectRoot, ".wrongstack", "config.json"),
|
|
1096
1234
|
inProjectAgentsFile: path2.join(opts.projectRoot, ".wrongstack", "AGENTS.md"),
|
|
1097
1235
|
inProjectSkills: path2.join(opts.projectRoot, ".wrongstack", "skills"),
|
|
1236
|
+
inProjectClaudeSkills: path2.join(opts.projectRoot, ".claude", "skills"),
|
|
1098
1237
|
inProjectPrompts: path2.join(opts.projectRoot, ".wrongstack", "prompts"),
|
|
1099
1238
|
inProjectInstructions: path2.join(opts.projectRoot, ".wrongstack", "instructions"),
|
|
1100
1239
|
inProjectDesignKits: path2.join(opts.projectRoot, ".wrongstack", "design-kits"),
|
|
@@ -1294,6 +1433,7 @@ function normalizeRecipient(to) {
|
|
|
1294
1433
|
var MAILBOX_FILE = "_mailbox.jsonl";
|
|
1295
1434
|
var CLIENT_REGISTRY_FILE = "_mailbox.clients.json";
|
|
1296
1435
|
var AGENT_STALE_MS = 6e4;
|
|
1436
|
+
var AGENT_PURGE_MS = 864e5;
|
|
1297
1437
|
var CLIENT_STALE_MS = 6e4;
|
|
1298
1438
|
var HEARTBEAT_THROTTLE_MS = 5e3;
|
|
1299
1439
|
var REGISTRY_CACHE_TTL_MS = 2e3;
|
|
@@ -1399,7 +1539,11 @@ var GlobalMailbox = class {
|
|
|
1399
1539
|
await fsp.appendFile(this.messagePath, line, "utf8");
|
|
1400
1540
|
this._pushToCache(msg);
|
|
1401
1541
|
});
|
|
1402
|
-
this.publishHqMailboxEvent({
|
|
1542
|
+
this.publishHqMailboxEvent({
|
|
1543
|
+
mailboxId: this.hqMailboxId,
|
|
1544
|
+
action: "message.sent",
|
|
1545
|
+
message: msg
|
|
1546
|
+
});
|
|
1403
1547
|
this.publishHqMailboxSnapshot();
|
|
1404
1548
|
return msg;
|
|
1405
1549
|
}
|
|
@@ -1420,6 +1564,7 @@ var GlobalMailbox = class {
|
|
|
1420
1564
|
continue;
|
|
1421
1565
|
}
|
|
1422
1566
|
if (q.since !== void 0 && m.timestamp <= q.since) continue;
|
|
1567
|
+
if (!q.includeDeleted && m.deletedAt !== void 0) continue;
|
|
1423
1568
|
out.push(m);
|
|
1424
1569
|
}
|
|
1425
1570
|
out.sort((a, b) => b.timestamp.localeCompare(a.timestamp));
|
|
@@ -1488,6 +1633,63 @@ var GlobalMailbox = class {
|
|
|
1488
1633
|
}
|
|
1489
1634
|
return count;
|
|
1490
1635
|
}
|
|
1636
|
+
async softDelete(mailId, by) {
|
|
1637
|
+
let updated = null;
|
|
1638
|
+
let cacheSnapshot = null;
|
|
1639
|
+
await withFileLock(this.messagePath, async () => {
|
|
1640
|
+
const all = await this._readMessagesFresh();
|
|
1641
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1642
|
+
for (const m of all) {
|
|
1643
|
+
if (m.id !== mailId) continue;
|
|
1644
|
+
if (m.deletedAt !== void 0) {
|
|
1645
|
+
updated = m;
|
|
1646
|
+
continue;
|
|
1647
|
+
}
|
|
1648
|
+
m.deletedAt = now;
|
|
1649
|
+
m.deletedBy = by;
|
|
1650
|
+
updated = m;
|
|
1651
|
+
}
|
|
1652
|
+
const serialized = all.map((m) => JSON.stringify(m)).join(LINE_SEPARATOR) + LINE_SEPARATOR;
|
|
1653
|
+
await fsp.writeFile(this.messagePath, serialized, "utf8");
|
|
1654
|
+
cacheSnapshot = all;
|
|
1655
|
+
});
|
|
1656
|
+
if (cacheSnapshot) this._setMessageCache(cacheSnapshot);
|
|
1657
|
+
if (updated !== null) {
|
|
1658
|
+
this.publishHqMailboxEvent({
|
|
1659
|
+
mailboxId: this.hqMailboxId,
|
|
1660
|
+
action: "message.updated",
|
|
1661
|
+
message: updated
|
|
1662
|
+
});
|
|
1663
|
+
this.publishHqMailboxSnapshot();
|
|
1664
|
+
}
|
|
1665
|
+
return updated;
|
|
1666
|
+
}
|
|
1667
|
+
async restore(mailId) {
|
|
1668
|
+
let updated = null;
|
|
1669
|
+
let cacheSnapshot = null;
|
|
1670
|
+
await withFileLock(this.messagePath, async () => {
|
|
1671
|
+
const all = await this._readMessagesFresh();
|
|
1672
|
+
for (const m of all) {
|
|
1673
|
+
if (m.id !== mailId) continue;
|
|
1674
|
+
delete m.deletedAt;
|
|
1675
|
+
delete m.deletedBy;
|
|
1676
|
+
updated = m;
|
|
1677
|
+
}
|
|
1678
|
+
const serialized = all.map((m) => JSON.stringify(m)).join(LINE_SEPARATOR) + LINE_SEPARATOR;
|
|
1679
|
+
await fsp.writeFile(this.messagePath, serialized, "utf8");
|
|
1680
|
+
cacheSnapshot = all;
|
|
1681
|
+
});
|
|
1682
|
+
if (cacheSnapshot) this._setMessageCache(cacheSnapshot);
|
|
1683
|
+
if (updated !== null) {
|
|
1684
|
+
this.publishHqMailboxEvent({
|
|
1685
|
+
mailboxId: this.hqMailboxId,
|
|
1686
|
+
action: "message.updated",
|
|
1687
|
+
message: updated
|
|
1688
|
+
});
|
|
1689
|
+
this.publishHqMailboxSnapshot();
|
|
1690
|
+
}
|
|
1691
|
+
return updated;
|
|
1692
|
+
}
|
|
1491
1693
|
// ── Agent registry ──────────────────────────────────────────────────────
|
|
1492
1694
|
async registerAgent(input) {
|
|
1493
1695
|
await this._ensureRegistry();
|
|
@@ -1542,6 +1744,43 @@ var GlobalMailbox = class {
|
|
|
1542
1744
|
});
|
|
1543
1745
|
this.publishHqMailboxSnapshot();
|
|
1544
1746
|
}
|
|
1747
|
+
async deregisterAgent(agentId) {
|
|
1748
|
+
await this._ensureRegistry();
|
|
1749
|
+
let removed;
|
|
1750
|
+
await withFileLock(this.registryPath, async () => {
|
|
1751
|
+
const registry = await this._readRegistry({ fresh: true });
|
|
1752
|
+
this._pruneStaleInPlace(registry);
|
|
1753
|
+
removed = registry.get(agentId);
|
|
1754
|
+
registry.delete(agentId);
|
|
1755
|
+
this._registryCache = registry;
|
|
1756
|
+
this._registryCacheAt = Date.now();
|
|
1757
|
+
await this._writeRegistry(registry);
|
|
1758
|
+
});
|
|
1759
|
+
this._events?.emitCustom("mailbox.agent_deregistered", {
|
|
1760
|
+
agentId
|
|
1761
|
+
});
|
|
1762
|
+
this.publishHqMailboxEvent({
|
|
1763
|
+
mailboxId: this.hqMailboxId,
|
|
1764
|
+
action: "agent.deregistered",
|
|
1765
|
+
agent: {
|
|
1766
|
+
agentId,
|
|
1767
|
+
name: removed?.name ?? agentId,
|
|
1768
|
+
...removed?.role !== void 0 ? { role: removed.role } : {},
|
|
1769
|
+
sessionId: removed?.sessionId ?? "",
|
|
1770
|
+
status: "offline",
|
|
1771
|
+
...removed?.currentTool !== void 0 ? { currentTool: removed.currentTool } : {},
|
|
1772
|
+
...removed?.currentTask !== void 0 ? { currentTask: removed.currentTask } : {},
|
|
1773
|
+
iterations: removed?.iterations ?? 0,
|
|
1774
|
+
toolCalls: removed?.toolCalls ?? 0,
|
|
1775
|
+
lastActivityAt: removed?.lastSeenAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
1776
|
+
lastSeenAt: removed?.lastSeenAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
1777
|
+
online: false,
|
|
1778
|
+
pid: removed?.pid ?? 0,
|
|
1779
|
+
...removed?.source !== void 0 ? { source: removed.source } : {}
|
|
1780
|
+
}
|
|
1781
|
+
});
|
|
1782
|
+
this.publishHqMailboxSnapshot();
|
|
1783
|
+
}
|
|
1545
1784
|
async heartbeat(input) {
|
|
1546
1785
|
const last = this._lastHeartbeat.get(input.agentId) ?? 0;
|
|
1547
1786
|
const now = Date.now();
|
|
@@ -1920,12 +2159,22 @@ var GlobalMailbox = class {
|
|
|
1920
2159
|
}
|
|
1921
2160
|
}
|
|
1922
2161
|
_pruneStaleInPlace(registry) {
|
|
1923
|
-
const
|
|
1924
|
-
|
|
1925
|
-
|
|
2162
|
+
const staleCutoff = Date.now() - AGENT_STALE_MS;
|
|
2163
|
+
const purgeCutoff = Date.now() - AGENT_PURGE_MS;
|
|
2164
|
+
const toDelete = [];
|
|
2165
|
+
for (const [id, agent] of registry) {
|
|
2166
|
+
const lastSeen = new Date(agent.lastSeenAt).getTime();
|
|
2167
|
+
if (lastSeen < purgeCutoff) {
|
|
2168
|
+
toDelete.push(id);
|
|
2169
|
+
continue;
|
|
2170
|
+
}
|
|
2171
|
+
if (lastSeen < staleCutoff) {
|
|
1926
2172
|
agent.status = "idle";
|
|
1927
2173
|
}
|
|
1928
2174
|
}
|
|
2175
|
+
for (const id of toDelete) {
|
|
2176
|
+
registry.delete(id);
|
|
2177
|
+
}
|
|
1929
2178
|
}
|
|
1930
2179
|
async _writeRegistry(registry) {
|
|
1931
2180
|
const obj = {};
|
|
@@ -1991,6 +2240,11 @@ function resolveHqDataDir(override, env = process.env) {
|
|
|
1991
2240
|
if (!raw) return defaultHqDataDir();
|
|
1992
2241
|
return path2.isAbsolute(raw) ? path2.resolve(raw) : path2.resolve(process.cwd(), raw);
|
|
1993
2242
|
}
|
|
2243
|
+
function tokenHasCapability(token, capability) {
|
|
2244
|
+
if (token === void 0) return false;
|
|
2245
|
+
if (token.capabilities === void 0) return true;
|
|
2246
|
+
return token.capabilities.includes(capability);
|
|
2247
|
+
}
|
|
1994
2248
|
function emptyHqAuthFile() {
|
|
1995
2249
|
return {
|
|
1996
2250
|
version: HQ_AUTH_FILE_VERSION,
|
|
@@ -2155,6 +2409,13 @@ function watchHqAuthFile(dataDir, onChange, opts = {}) {
|
|
|
2155
2409
|
}
|
|
2156
2410
|
|
|
2157
2411
|
// src/hq/factory.ts
|
|
2412
|
+
function discoverLocalHqEndpoint(options = {}) {
|
|
2413
|
+
const dataDir = resolveHqDataDir(options.dataDir, options.env ?? process.env);
|
|
2414
|
+
const runtime = readHqRuntimeFileSync(dataDir);
|
|
2415
|
+
if (runtime === void 0) return void 0;
|
|
2416
|
+
const token = readFirstClientTokenFromAuthFile(dataDir);
|
|
2417
|
+
return { url: runtime.url, ...token ? { token } : {} };
|
|
2418
|
+
}
|
|
2158
2419
|
function readFirstClientTokenFromAuthFile(dataDir) {
|
|
2159
2420
|
try {
|
|
2160
2421
|
const raw = syncFs.readFileSync(hqAuthFilePath(dataDir), "utf8");
|
|
@@ -2182,14 +2443,13 @@ function resolveHqConfig(options = {}) {
|
|
|
2182
2443
|
const url = envUrl || configUrl;
|
|
2183
2444
|
if (!url) {
|
|
2184
2445
|
if (enabled === false) return void 0;
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
}
|
|
2191
|
-
}
|
|
2192
|
-
return void 0;
|
|
2446
|
+
return {
|
|
2447
|
+
url: runtimeUrl || "http://127.0.0.1:3499",
|
|
2448
|
+
enabled: true,
|
|
2449
|
+
discover: true,
|
|
2450
|
+
dataDir,
|
|
2451
|
+
...token ? { token } : {}
|
|
2452
|
+
};
|
|
2193
2453
|
}
|
|
2194
2454
|
const rawContentEnv = env["WRONGSTACK_HQ_RAW_CONTENT"]?.trim();
|
|
2195
2455
|
const projectAliasEnv = env["WRONGSTACK_HQ_PROJECT_ALIAS"]?.trim();
|
|
@@ -2232,13 +2492,20 @@ function createHqPublisherFromEnv(options) {
|
|
|
2232
2492
|
...config.rawContent !== void 0 ? { rawContent: config.rawContent } : {},
|
|
2233
2493
|
...options.redactionPolicy ?? {}
|
|
2234
2494
|
} : void 0;
|
|
2495
|
+
const discoveryDataDir = config.dataDir;
|
|
2235
2496
|
return new HqPublisher({
|
|
2236
2497
|
url: config.url,
|
|
2237
2498
|
...config.token ? { token: config.token } : {},
|
|
2238
2499
|
client,
|
|
2239
2500
|
project,
|
|
2240
2501
|
...options.socketFactory ? { socketFactory: options.socketFactory } : {},
|
|
2241
|
-
...redactionPolicy !== void 0 ? { redactionPolicy } : {}
|
|
2502
|
+
...redactionPolicy !== void 0 ? { redactionPolicy } : {},
|
|
2503
|
+
...options.capabilities !== void 0 ? { capabilities: options.capabilities } : {},
|
|
2504
|
+
...options.onCommand !== void 0 ? { onCommand: options.onCommand } : {},
|
|
2505
|
+
// Auto-discovery: re-read the local HQ runtime marker + client token on
|
|
2506
|
+
// every connect attempt so late-started/restarted HQs are picked up.
|
|
2507
|
+
...config.discover ? { resolveEndpoint: () => discoverLocalHqEndpoint({ dataDir: discoveryDataDir }) } : {},
|
|
2508
|
+
...options.discoveryPollMs !== void 0 ? { discoveryPollMs: options.discoveryPollMs } : {}
|
|
2242
2509
|
});
|
|
2243
2510
|
}
|
|
2244
2511
|
function createGlobalMailbox(options) {
|
|
@@ -2620,6 +2887,788 @@ function startSessionTelemetryBridge(opts) {
|
|
|
2620
2887
|
};
|
|
2621
2888
|
}
|
|
2622
2889
|
|
|
2623
|
-
|
|
2890
|
+
// src/hq/fleet-bridge.ts
|
|
2891
|
+
function startFleetTelemetryBridge(opts) {
|
|
2892
|
+
const { events, publisher, runId } = opts;
|
|
2893
|
+
const now = opts.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
|
|
2894
|
+
let lastHash = "";
|
|
2895
|
+
function buildPayload(stats) {
|
|
2896
|
+
const subagents = stats.subagentStatuses.map((s) => ({
|
|
2897
|
+
subagentId: s.subagentId,
|
|
2898
|
+
...s.taskId ? { task: s.taskId } : {},
|
|
2899
|
+
status: normalizeSubagentStatus(s.status)
|
|
2900
|
+
}));
|
|
2901
|
+
return {
|
|
2902
|
+
runId,
|
|
2903
|
+
activeSubagents: stats.running + stats.idle,
|
|
2904
|
+
queuedTasks: stats.pending,
|
|
2905
|
+
completedTasks: stats.completed,
|
|
2906
|
+
failedTasks: stats.stopped,
|
|
2907
|
+
subagents
|
|
2908
|
+
};
|
|
2909
|
+
}
|
|
2910
|
+
const off = events.on("coordinator.stats", (stats) => {
|
|
2911
|
+
try {
|
|
2912
|
+
const payload = buildPayload(stats);
|
|
2913
|
+
const hash = JSON.stringify(payload);
|
|
2914
|
+
if (hash === lastHash) return;
|
|
2915
|
+
lastHash = hash;
|
|
2916
|
+
publisher.publishFleetSnapshot(payload, {
|
|
2917
|
+
...opts.sessionId !== void 0 ? { sessionId: opts.sessionId } : {},
|
|
2918
|
+
timestamp: now()
|
|
2919
|
+
});
|
|
2920
|
+
} catch {
|
|
2921
|
+
}
|
|
2922
|
+
});
|
|
2923
|
+
return () => {
|
|
2924
|
+
off();
|
|
2925
|
+
};
|
|
2926
|
+
}
|
|
2927
|
+
var FLEET_STATUS_MAP = {
|
|
2928
|
+
running: "running",
|
|
2929
|
+
idle: "idle",
|
|
2930
|
+
pending: "pending",
|
|
2931
|
+
completed: "completed",
|
|
2932
|
+
failed: "failed",
|
|
2933
|
+
stopped: "stopped",
|
|
2934
|
+
timeout: "stopped",
|
|
2935
|
+
budget_exhausted: "stopped"
|
|
2936
|
+
};
|
|
2937
|
+
function normalizeSubagentStatus(raw) {
|
|
2938
|
+
return FLEET_STATUS_MAP[raw] ?? "idle";
|
|
2939
|
+
}
|
|
2940
|
+
|
|
2941
|
+
// src/hq/brain-bridge.ts
|
|
2942
|
+
function extractRequestFields(req) {
|
|
2943
|
+
if (req === void 0) return {};
|
|
2944
|
+
const out = {};
|
|
2945
|
+
if (typeof req.id === "string") out.requestId = req.id;
|
|
2946
|
+
if (typeof req.question === "string") out.question = req.question;
|
|
2947
|
+
if (typeof req.source === "string") out.source = req.source;
|
|
2948
|
+
if (typeof req.risk === "string") out.risk = req.risk;
|
|
2949
|
+
return out;
|
|
2950
|
+
}
|
|
2951
|
+
function extractDecisionFields(dec) {
|
|
2952
|
+
if (dec === void 0) return {};
|
|
2953
|
+
const out = {};
|
|
2954
|
+
out.decision = dec.type;
|
|
2955
|
+
if (dec.type === "answer") {
|
|
2956
|
+
out.detail = dec.text;
|
|
2957
|
+
} else if (dec.type === "ask_human") {
|
|
2958
|
+
out.detail = dec.prompt;
|
|
2959
|
+
} else if (dec.type === "deny") {
|
|
2960
|
+
out.detail = dec.reason;
|
|
2961
|
+
}
|
|
2962
|
+
return out;
|
|
2963
|
+
}
|
|
2964
|
+
function startBrainTelemetryBridge(opts) {
|
|
2965
|
+
const { events, publisher } = opts;
|
|
2966
|
+
const now = opts.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
|
|
2967
|
+
function publish(kind, payload, at) {
|
|
2968
|
+
try {
|
|
2969
|
+
const full = { kind, at, ...payload };
|
|
2970
|
+
publisher.publishEvent({
|
|
2971
|
+
type: "brain.event",
|
|
2972
|
+
payload: full,
|
|
2973
|
+
...opts.sessionId !== void 0 ? { sessionId: opts.sessionId } : {},
|
|
2974
|
+
timestamp: now()
|
|
2975
|
+
});
|
|
2976
|
+
} catch {
|
|
2977
|
+
}
|
|
2978
|
+
}
|
|
2979
|
+
const offs = [];
|
|
2980
|
+
offs.push(
|
|
2981
|
+
events.on("brain.decision_requested", (p) => {
|
|
2982
|
+
publish("decision_requested", extractRequestFields(p.request), p.at);
|
|
2983
|
+
})
|
|
2984
|
+
);
|
|
2985
|
+
offs.push(
|
|
2986
|
+
events.on("brain.decision_answered", (p) => {
|
|
2987
|
+
publish("decision_answered", {
|
|
2988
|
+
...extractRequestFields(p.request),
|
|
2989
|
+
...extractDecisionFields(p.decision)
|
|
2990
|
+
}, p.at);
|
|
2991
|
+
})
|
|
2992
|
+
);
|
|
2993
|
+
offs.push(
|
|
2994
|
+
events.on("brain.decision_ask_human", (p) => {
|
|
2995
|
+
publish("decision_ask_human", {
|
|
2996
|
+
...extractRequestFields(p.request),
|
|
2997
|
+
...extractDecisionFields(p.decision)
|
|
2998
|
+
}, p.at);
|
|
2999
|
+
})
|
|
3000
|
+
);
|
|
3001
|
+
offs.push(
|
|
3002
|
+
events.on("brain.decision_denied", (p) => {
|
|
3003
|
+
publish("decision_denied", {
|
|
3004
|
+
...extractRequestFields(p.request),
|
|
3005
|
+
...extractDecisionFields(p.decision)
|
|
3006
|
+
}, p.at);
|
|
3007
|
+
})
|
|
3008
|
+
);
|
|
3009
|
+
offs.push(
|
|
3010
|
+
events.on("brain.human_answered", (p) => {
|
|
3011
|
+
const detail = typeof p.text === "string" ? p.text : p.optionId;
|
|
3012
|
+
publish("human_answered", {
|
|
3013
|
+
requestId: p.id,
|
|
3014
|
+
...detail !== void 0 ? { detail } : {},
|
|
3015
|
+
...p.deny === true ? { decision: "deny" } : {}
|
|
3016
|
+
}, p.at);
|
|
3017
|
+
})
|
|
3018
|
+
);
|
|
3019
|
+
offs.push(
|
|
3020
|
+
events.on("brain.intervention", (p) => {
|
|
3021
|
+
publish("intervention", {
|
|
3022
|
+
...extractRequestFields(p.request),
|
|
3023
|
+
...extractDecisionFields(p.decision),
|
|
3024
|
+
interventionKind: p.kind,
|
|
3025
|
+
intervened: p.intervened
|
|
3026
|
+
}, p.at);
|
|
3027
|
+
})
|
|
3028
|
+
);
|
|
3029
|
+
return () => {
|
|
3030
|
+
for (const off of offs) off();
|
|
3031
|
+
};
|
|
3032
|
+
}
|
|
3033
|
+
|
|
3034
|
+
// src/hq/worktree-bridge.ts
|
|
3035
|
+
function startWorktreeTelemetryBridge(opts) {
|
|
3036
|
+
const { events, publisher } = opts;
|
|
3037
|
+
const now = opts.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
|
|
3038
|
+
function publish(payload, sessionId) {
|
|
3039
|
+
try {
|
|
3040
|
+
publisher.publishEvent({
|
|
3041
|
+
type: "worktree.event",
|
|
3042
|
+
payload,
|
|
3043
|
+
...sessionId !== void 0 && sessionId !== "" ? { sessionId } : {},
|
|
3044
|
+
...opts.sessionId !== void 0 ? { sessionId: opts.sessionId } : {},
|
|
3045
|
+
timestamp: now()
|
|
3046
|
+
});
|
|
3047
|
+
} catch {
|
|
3048
|
+
}
|
|
3049
|
+
}
|
|
3050
|
+
const offs = [];
|
|
3051
|
+
offs.push(
|
|
3052
|
+
events.on("worktree.allocated", (p) => {
|
|
3053
|
+
publish(
|
|
3054
|
+
{
|
|
3055
|
+
kind: "allocated",
|
|
3056
|
+
handleId: p.handleId,
|
|
3057
|
+
ownerId: p.ownerId,
|
|
3058
|
+
ownerLabel: p.ownerLabel,
|
|
3059
|
+
slug: p.slug,
|
|
3060
|
+
branch: p.branch,
|
|
3061
|
+
baseBranch: p.baseBranch
|
|
3062
|
+
},
|
|
3063
|
+
p.sessionId
|
|
3064
|
+
);
|
|
3065
|
+
})
|
|
3066
|
+
);
|
|
3067
|
+
offs.push(
|
|
3068
|
+
events.on("worktree.committed", (p) => {
|
|
3069
|
+
publish(
|
|
3070
|
+
{
|
|
3071
|
+
kind: "committed",
|
|
3072
|
+
handleId: p.handleId,
|
|
3073
|
+
ownerId: p.ownerId,
|
|
3074
|
+
branch: p.branch,
|
|
3075
|
+
insertions: p.insertions,
|
|
3076
|
+
deletions: p.deletions,
|
|
3077
|
+
files: p.files,
|
|
3078
|
+
...p.sha !== void 0 ? { sha: p.sha } : {}
|
|
3079
|
+
},
|
|
3080
|
+
p.sessionId
|
|
3081
|
+
);
|
|
3082
|
+
})
|
|
3083
|
+
);
|
|
3084
|
+
offs.push(
|
|
3085
|
+
events.on("worktree.merged", (p) => {
|
|
3086
|
+
publish(
|
|
3087
|
+
{
|
|
3088
|
+
kind: "merged",
|
|
3089
|
+
handleId: p.handleId,
|
|
3090
|
+
ownerId: p.ownerId,
|
|
3091
|
+
branch: p.branch,
|
|
3092
|
+
baseBranch: p.baseBranch,
|
|
3093
|
+
squash: p.squash
|
|
3094
|
+
},
|
|
3095
|
+
p.sessionId
|
|
3096
|
+
);
|
|
3097
|
+
})
|
|
3098
|
+
);
|
|
3099
|
+
offs.push(
|
|
3100
|
+
events.on("worktree.conflict", (p) => {
|
|
3101
|
+
publish(
|
|
3102
|
+
{
|
|
3103
|
+
kind: "conflict",
|
|
3104
|
+
handleId: p.handleId,
|
|
3105
|
+
ownerId: p.ownerId,
|
|
3106
|
+
branch: p.branch,
|
|
3107
|
+
conflictFiles: p.conflictFiles
|
|
3108
|
+
},
|
|
3109
|
+
p.sessionId
|
|
3110
|
+
);
|
|
3111
|
+
})
|
|
3112
|
+
);
|
|
3113
|
+
offs.push(
|
|
3114
|
+
events.on("worktree.released", (p) => {
|
|
3115
|
+
publish(
|
|
3116
|
+
{
|
|
3117
|
+
kind: "released",
|
|
3118
|
+
handleId: p.handleId,
|
|
3119
|
+
ownerId: p.ownerId,
|
|
3120
|
+
branch: p.branch,
|
|
3121
|
+
kept: p.kept
|
|
3122
|
+
},
|
|
3123
|
+
p.sessionId
|
|
3124
|
+
);
|
|
3125
|
+
})
|
|
3126
|
+
);
|
|
3127
|
+
offs.push(
|
|
3128
|
+
events.on("worktree.failed", (p) => {
|
|
3129
|
+
publish(
|
|
3130
|
+
{
|
|
3131
|
+
kind: "failed",
|
|
3132
|
+
handleId: p.handleId,
|
|
3133
|
+
ownerId: p.ownerId,
|
|
3134
|
+
...p.branch !== void 0 ? { branch: p.branch } : {},
|
|
3135
|
+
error: p.error
|
|
3136
|
+
},
|
|
3137
|
+
p.sessionId
|
|
3138
|
+
);
|
|
3139
|
+
})
|
|
3140
|
+
);
|
|
3141
|
+
return () => {
|
|
3142
|
+
for (const off of offs) off();
|
|
3143
|
+
};
|
|
3144
|
+
}
|
|
3145
|
+
|
|
3146
|
+
// src/hq/tool-bridge.ts
|
|
3147
|
+
function startToolTelemetryBridge(opts) {
|
|
3148
|
+
const { events, publisher } = opts;
|
|
3149
|
+
const now = opts.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
|
|
3150
|
+
const inFlight = /* @__PURE__ */ new Map();
|
|
3151
|
+
function sessionIdTag(sessionId) {
|
|
3152
|
+
const tag = sessionId ?? opts.sessionId;
|
|
3153
|
+
return tag !== void 0 ? { sessionId: tag } : {};
|
|
3154
|
+
}
|
|
3155
|
+
const offStarted = events.on("tool.started", (p) => {
|
|
3156
|
+
inFlight.set(p.id, { name: p.name, startedAt: Date.now() });
|
|
3157
|
+
try {
|
|
3158
|
+
const payload = {
|
|
3159
|
+
toolName: p.name,
|
|
3160
|
+
...p.input !== void 0 ? {
|
|
3161
|
+
inputSummary: summarizeHqToolArgs(p.input, {
|
|
3162
|
+
...opts.projectRoot !== void 0 ? { projectRoot: opts.projectRoot } : {}
|
|
3163
|
+
})
|
|
3164
|
+
} : {}
|
|
3165
|
+
};
|
|
3166
|
+
publisher.publishEvent({
|
|
3167
|
+
type: "tool.started",
|
|
3168
|
+
payload,
|
|
3169
|
+
...sessionIdTag(p.sessionId),
|
|
3170
|
+
timestamp: now()
|
|
3171
|
+
});
|
|
3172
|
+
} catch {
|
|
3173
|
+
}
|
|
3174
|
+
});
|
|
3175
|
+
const offExecuted = events.on("tool.executed", (p) => {
|
|
3176
|
+
const id = p.id;
|
|
3177
|
+
if (id !== void 0) inFlight.delete(id);
|
|
3178
|
+
try {
|
|
3179
|
+
const payload = {
|
|
3180
|
+
toolName: p.name,
|
|
3181
|
+
status: p.ok ? "success" : "error",
|
|
3182
|
+
durationMs: p.durationMs,
|
|
3183
|
+
...p.output !== void 0 && p.output.length > 0 ? { outputSummary: truncateForSummary(p.output) } : {}
|
|
3184
|
+
};
|
|
3185
|
+
publisher.publishEvent({
|
|
3186
|
+
type: "tool.completed",
|
|
3187
|
+
payload,
|
|
3188
|
+
...sessionIdTag(p.sessionId),
|
|
3189
|
+
timestamp: now()
|
|
3190
|
+
});
|
|
3191
|
+
} catch {
|
|
3192
|
+
}
|
|
3193
|
+
});
|
|
3194
|
+
return () => {
|
|
3195
|
+
offStarted();
|
|
3196
|
+
offExecuted();
|
|
3197
|
+
inFlight.clear();
|
|
3198
|
+
};
|
|
3199
|
+
}
|
|
3200
|
+
function truncateForSummary(output, max = 280) {
|
|
3201
|
+
if (output.length <= max) return output;
|
|
3202
|
+
return `${output.slice(0, max)}\u2026[truncated:${output.length - max}]`;
|
|
3203
|
+
}
|
|
3204
|
+
|
|
3205
|
+
// src/hq/cost-bridge.ts
|
|
3206
|
+
function startCostTelemetryBridge(opts) {
|
|
3207
|
+
const { events, publisher } = opts;
|
|
3208
|
+
const now = opts.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
|
|
3209
|
+
const off = events.on("token.accounted", (p) => {
|
|
3210
|
+
try {
|
|
3211
|
+
const payload = {
|
|
3212
|
+
inputTokens: p.usage.input,
|
|
3213
|
+
outputTokens: p.usage.output,
|
|
3214
|
+
totalTokens: p.usage.input + p.usage.output,
|
|
3215
|
+
costUsd: p.cost.total
|
|
3216
|
+
};
|
|
3217
|
+
const sessionId = opts.sessionId ?? p.sessionId;
|
|
3218
|
+
publisher.publishEvent({
|
|
3219
|
+
type: "session.usage",
|
|
3220
|
+
payload,
|
|
3221
|
+
...sessionId !== void 0 ? { sessionId } : {},
|
|
3222
|
+
timestamp: now()
|
|
3223
|
+
});
|
|
3224
|
+
} catch {
|
|
3225
|
+
}
|
|
3226
|
+
});
|
|
3227
|
+
return () => {
|
|
3228
|
+
off();
|
|
3229
|
+
};
|
|
3230
|
+
}
|
|
3231
|
+
var DEFAULT_EVENT_LOG_MAX_LINES = 5e4;
|
|
3232
|
+
var DEFAULT_EVENT_LOG_ROTATE_KEEP = 2e4;
|
|
3233
|
+
var HqEventLog = class {
|
|
3234
|
+
filePath;
|
|
3235
|
+
maxLines;
|
|
3236
|
+
rotateKeep;
|
|
3237
|
+
writeChain = Promise.resolve();
|
|
3238
|
+
lineCount = 0;
|
|
3239
|
+
counted = false;
|
|
3240
|
+
constructor(opts) {
|
|
3241
|
+
this.filePath = path2.join(opts.dataDir, "events.jsonl");
|
|
3242
|
+
this.maxLines = opts.maxLines ?? DEFAULT_EVENT_LOG_MAX_LINES;
|
|
3243
|
+
this.rotateKeep = opts.rotateKeep ?? DEFAULT_EVENT_LOG_ROTATE_KEEP;
|
|
3244
|
+
}
|
|
3245
|
+
/** Append an event envelope as one JSON line. Best-effort, never rejects. */
|
|
3246
|
+
append(event) {
|
|
3247
|
+
this.writeChain = this.writeChain.then(() => this.appendInternal(event)).catch(() => {
|
|
3248
|
+
});
|
|
3249
|
+
}
|
|
3250
|
+
/** Resolves once all queued appends have settled. For tests. */
|
|
3251
|
+
async drain() {
|
|
3252
|
+
await this.writeChain.catch(() => {
|
|
3253
|
+
});
|
|
3254
|
+
}
|
|
3255
|
+
async appendInternal(event) {
|
|
3256
|
+
if (!this.counted) {
|
|
3257
|
+
this.lineCount = await this.countLines();
|
|
3258
|
+
this.counted = true;
|
|
3259
|
+
}
|
|
3260
|
+
const line = JSON.stringify(event) + "\n";
|
|
3261
|
+
await fsp.appendFile(this.filePath, line, { encoding: "utf8" });
|
|
3262
|
+
this.lineCount += 1;
|
|
3263
|
+
if (this.lineCount >= this.maxLines) {
|
|
3264
|
+
await this.rotate();
|
|
3265
|
+
}
|
|
3266
|
+
}
|
|
3267
|
+
async rotate() {
|
|
3268
|
+
await withFileLock(this.filePath, async () => {
|
|
3269
|
+
try {
|
|
3270
|
+
const content = await fsp.readFile(this.filePath, "utf8");
|
|
3271
|
+
const lines = content.split("\n").filter((l) => l.length > 0);
|
|
3272
|
+
if (lines.length <= this.rotateKeep) {
|
|
3273
|
+
this.lineCount = lines.length;
|
|
3274
|
+
return;
|
|
3275
|
+
}
|
|
3276
|
+
const kept = lines.slice(lines.length - this.rotateKeep);
|
|
3277
|
+
await atomicWrite(this.filePath, kept.join("\n") + "\n");
|
|
3278
|
+
this.lineCount = kept.length;
|
|
3279
|
+
} catch {
|
|
3280
|
+
}
|
|
3281
|
+
});
|
|
3282
|
+
}
|
|
3283
|
+
async countLines() {
|
|
3284
|
+
try {
|
|
3285
|
+
const content = await fsp.readFile(this.filePath, "utf8");
|
|
3286
|
+
return content.split("\n").filter((l) => l.length > 0).length;
|
|
3287
|
+
} catch {
|
|
3288
|
+
return 0;
|
|
3289
|
+
}
|
|
3290
|
+
}
|
|
3291
|
+
/**
|
|
3292
|
+
* Read the most recent `limit` events, optionally filtered by envelope
|
|
3293
|
+
* `type`. Newest first. Returns `[]` if the file doesn't exist yet.
|
|
3294
|
+
*/
|
|
3295
|
+
async recent(limit, typeFilter) {
|
|
3296
|
+
let content;
|
|
3297
|
+
try {
|
|
3298
|
+
content = await fsp.readFile(this.filePath, "utf8");
|
|
3299
|
+
} catch {
|
|
3300
|
+
return [];
|
|
3301
|
+
}
|
|
3302
|
+
const lines = content.split("\n").filter((l) => l.length > 0);
|
|
3303
|
+
const out = [];
|
|
3304
|
+
for (let i = lines.length - 1; i >= 0 && out.length < limit; i--) {
|
|
3305
|
+
try {
|
|
3306
|
+
const env = JSON.parse(lines[i]);
|
|
3307
|
+
if (typeFilter === void 0 || env.type === typeFilter) {
|
|
3308
|
+
out.push(env);
|
|
3309
|
+
}
|
|
3310
|
+
} catch {
|
|
3311
|
+
}
|
|
3312
|
+
}
|
|
3313
|
+
return out;
|
|
3314
|
+
}
|
|
3315
|
+
/** Initialize the line count cache from disk (call once at boot). */
|
|
3316
|
+
async hydrate() {
|
|
3317
|
+
this.lineCount = await this.countLines();
|
|
3318
|
+
this.counted = true;
|
|
3319
|
+
}
|
|
3320
|
+
};
|
|
3321
|
+
var HqSnapshotStore = class {
|
|
3322
|
+
filePath;
|
|
3323
|
+
writeChain = Promise.resolve();
|
|
3324
|
+
constructor(opts) {
|
|
3325
|
+
this.filePath = path2.join(opts.dataDir, "snapshot.json");
|
|
3326
|
+
}
|
|
3327
|
+
/** Persist a snapshot. Best-effort, never rejects. */
|
|
3328
|
+
save(snapshot) {
|
|
3329
|
+
this.writeChain = this.writeChain.then(() => atomicWrite(this.filePath, JSON.stringify(snapshot), { mode: 384 })).catch(() => {
|
|
3330
|
+
});
|
|
3331
|
+
}
|
|
3332
|
+
/** Resolves once all queued saves have settled. For tests. */
|
|
3333
|
+
async drain() {
|
|
3334
|
+
await this.writeChain.catch(() => {
|
|
3335
|
+
});
|
|
3336
|
+
}
|
|
3337
|
+
/** Read the last persisted snapshot, or `null` if none. */
|
|
3338
|
+
async load() {
|
|
3339
|
+
try {
|
|
3340
|
+
const content = await fsp.readFile(this.filePath, "utf8");
|
|
3341
|
+
return JSON.parse(content);
|
|
3342
|
+
} catch {
|
|
3343
|
+
return null;
|
|
3344
|
+
}
|
|
3345
|
+
}
|
|
3346
|
+
};
|
|
3347
|
+
var HqTimeseriesStore = class {
|
|
3348
|
+
filePath;
|
|
3349
|
+
bucketMs;
|
|
3350
|
+
maxBuckets;
|
|
3351
|
+
buckets = /* @__PURE__ */ new Map();
|
|
3352
|
+
flushChain = Promise.resolve();
|
|
3353
|
+
constructor(opts) {
|
|
3354
|
+
this.filePath = path2.join(opts.dataDir, "timeseries.jsonl");
|
|
3355
|
+
this.bucketMs = opts.bucketMs ?? 5 * 60 * 1e3;
|
|
3356
|
+
this.maxBuckets = opts.maxBuckets ?? 2016;
|
|
3357
|
+
}
|
|
3358
|
+
bucketStart(ts) {
|
|
3359
|
+
return Math.floor(ts / this.bucketMs) * this.bucketMs;
|
|
3360
|
+
}
|
|
3361
|
+
/** Fold a cost/tool signal into the current bucket. Best-effort. */
|
|
3362
|
+
record(signal) {
|
|
3363
|
+
const start = this.bucketStart(signal.ts ?? Date.now());
|
|
3364
|
+
let bucket = this.buckets.get(start);
|
|
3365
|
+
if (!bucket) {
|
|
3366
|
+
bucket = { ts: start, costUsd: 0, inputTokens: 0, outputTokens: 0, toolCalls: 0 };
|
|
3367
|
+
this.buckets.set(start, bucket);
|
|
3368
|
+
}
|
|
3369
|
+
if (signal.costUsd !== void 0) bucket.costUsd += signal.costUsd;
|
|
3370
|
+
if (signal.inputTokens !== void 0) bucket.inputTokens += signal.inputTokens;
|
|
3371
|
+
if (signal.outputTokens !== void 0) bucket.outputTokens += signal.outputTokens;
|
|
3372
|
+
if (signal.toolCalls !== void 0) bucket.toolCalls += signal.toolCalls;
|
|
3373
|
+
if (signal.activeAgents !== void 0) bucket.activeAgents = signal.activeAgents;
|
|
3374
|
+
if (this.buckets.size > this.maxBuckets) {
|
|
3375
|
+
const sorted = Array.from(this.buckets.keys()).sort((a, b) => a - b);
|
|
3376
|
+
while (this.buckets.size > this.maxBuckets && sorted.length > 0) {
|
|
3377
|
+
const oldest = sorted.shift();
|
|
3378
|
+
if (oldest === void 0) break;
|
|
3379
|
+
this.buckets.delete(oldest);
|
|
3380
|
+
}
|
|
3381
|
+
}
|
|
3382
|
+
}
|
|
3383
|
+
/** Persist accumulated buckets to disk (append under lock), prune to maxBuckets. */
|
|
3384
|
+
flush() {
|
|
3385
|
+
const snapshot = Array.from(this.buckets.values()).sort((a, b) => a.ts - b.ts);
|
|
3386
|
+
if (snapshot.length === 0) return;
|
|
3387
|
+
this.flushChain = this.flushChain.then(() => this.flushInternal(snapshot)).catch(() => {
|
|
3388
|
+
});
|
|
3389
|
+
}
|
|
3390
|
+
/** Resolves once all queued flushes have settled. For tests. */
|
|
3391
|
+
async drain() {
|
|
3392
|
+
await this.flushChain.catch(() => {
|
|
3393
|
+
});
|
|
3394
|
+
}
|
|
3395
|
+
async flushInternal(toWrite) {
|
|
3396
|
+
const lines = toWrite.map((b) => JSON.stringify(b)).join("\n") + "\n";
|
|
3397
|
+
await withFileLock(this.filePath, async () => {
|
|
3398
|
+
await fsp.appendFile(this.filePath, lines, { encoding: "utf8" });
|
|
3399
|
+
});
|
|
3400
|
+
const sorted = Array.from(this.buckets.keys()).sort((a, b) => a - b);
|
|
3401
|
+
while (this.buckets.size > this.maxBuckets) {
|
|
3402
|
+
const oldest = sorted.shift();
|
|
3403
|
+
if (oldest === void 0) break;
|
|
3404
|
+
this.buckets.delete(oldest);
|
|
3405
|
+
}
|
|
3406
|
+
}
|
|
3407
|
+
/** Read buckets within `[since, now]`, oldest-first. */
|
|
3408
|
+
async read(sinceMs) {
|
|
3409
|
+
if (this.buckets.size === 0) await this.load();
|
|
3410
|
+
const since = sinceMs ?? 0;
|
|
3411
|
+
return Array.from(this.buckets.values()).filter((b) => b.ts >= since).sort((a, b) => a.ts - b.ts);
|
|
3412
|
+
}
|
|
3413
|
+
/** Rehydrate buckets from disk (deduped, latest-per-bucket wins). */
|
|
3414
|
+
async load() {
|
|
3415
|
+
let content;
|
|
3416
|
+
try {
|
|
3417
|
+
content = await fsp.readFile(this.filePath, "utf8");
|
|
3418
|
+
} catch {
|
|
3419
|
+
return;
|
|
3420
|
+
}
|
|
3421
|
+
for (const line of content.split("\n")) {
|
|
3422
|
+
const trimmed = line.trim();
|
|
3423
|
+
if (!trimmed) continue;
|
|
3424
|
+
try {
|
|
3425
|
+
const sample = JSON.parse(trimmed);
|
|
3426
|
+
this.buckets.set(sample.ts, sample);
|
|
3427
|
+
} catch {
|
|
3428
|
+
}
|
|
3429
|
+
}
|
|
3430
|
+
const sorted = Array.from(this.buckets.keys()).sort((a, b) => a - b);
|
|
3431
|
+
while (this.buckets.size > this.maxBuckets) {
|
|
3432
|
+
const oldest = sorted.shift();
|
|
3433
|
+
if (oldest === void 0) break;
|
|
3434
|
+
this.buckets.delete(oldest);
|
|
3435
|
+
}
|
|
3436
|
+
}
|
|
3437
|
+
};
|
|
3438
|
+
function createHqPersistence(dataDir) {
|
|
3439
|
+
return {
|
|
3440
|
+
eventLog: new HqEventLog({ dataDir }),
|
|
3441
|
+
snapshotStore: new HqSnapshotStore({ dataDir }),
|
|
3442
|
+
timeseries: new HqTimeseriesStore({ dataDir })
|
|
3443
|
+
};
|
|
3444
|
+
}
|
|
3445
|
+
|
|
3446
|
+
// src/hq/commands.ts
|
|
3447
|
+
var HQ_COMMAND_TYPES = [
|
|
3448
|
+
"steer",
|
|
3449
|
+
"abort",
|
|
3450
|
+
"spawn",
|
|
3451
|
+
"broadcast",
|
|
3452
|
+
"run-command"
|
|
3453
|
+
];
|
|
3454
|
+
var HQ_COMMAND_TYPE_SET = new Set(HQ_COMMAND_TYPES);
|
|
3455
|
+
function validateHqCommand(queued) {
|
|
3456
|
+
if (!HQ_COMMAND_TYPE_SET.has(queued.type)) return null;
|
|
3457
|
+
const p = queued.payload;
|
|
3458
|
+
if (p === null || typeof p !== "object") return null;
|
|
3459
|
+
switch (queued.type) {
|
|
3460
|
+
case "steer": {
|
|
3461
|
+
if (typeof p["to"] !== "string" || typeof p["subject"] !== "string" || typeof p["body"] !== "string") {
|
|
3462
|
+
return null;
|
|
3463
|
+
}
|
|
3464
|
+
const result = { type: "steer", to: p["to"], subject: p["subject"], body: p["body"] };
|
|
3465
|
+
if (p["priority"] === "low" || p["priority"] === "normal" || p["priority"] === "high") {
|
|
3466
|
+
result.priority = p["priority"];
|
|
3467
|
+
}
|
|
3468
|
+
return result;
|
|
3469
|
+
}
|
|
3470
|
+
case "abort":
|
|
3471
|
+
if (typeof p["target"] !== "string") return null;
|
|
3472
|
+
return { type: "abort", target: p["target"] };
|
|
3473
|
+
case "spawn": {
|
|
3474
|
+
if (typeof p["role"] !== "string") return null;
|
|
3475
|
+
const result = { type: "spawn", role: p["role"] };
|
|
3476
|
+
if (typeof p["task"] === "string") result.task = p["task"];
|
|
3477
|
+
if (typeof p["maxIterations"] === "number") result.maxIterations = p["maxIterations"];
|
|
3478
|
+
return result;
|
|
3479
|
+
}
|
|
3480
|
+
case "broadcast": {
|
|
3481
|
+
if (typeof p["subject"] !== "string" || typeof p["body"] !== "string") return null;
|
|
3482
|
+
const result = { type: "broadcast", subject: p["subject"], body: p["body"] };
|
|
3483
|
+
if (p["priority"] === "low" || p["priority"] === "normal" || p["priority"] === "high") {
|
|
3484
|
+
result.priority = p["priority"];
|
|
3485
|
+
}
|
|
3486
|
+
return result;
|
|
3487
|
+
}
|
|
3488
|
+
case "run-command": {
|
|
3489
|
+
if (typeof p["command"] !== "string") return null;
|
|
3490
|
+
const result = { type: "run-command", command: p["command"] };
|
|
3491
|
+
if (typeof p["cwd"] === "string") result.cwd = p["cwd"];
|
|
3492
|
+
return result;
|
|
3493
|
+
}
|
|
3494
|
+
default:
|
|
3495
|
+
return null;
|
|
3496
|
+
}
|
|
3497
|
+
}
|
|
3498
|
+
var HqCommandAuditLog = class {
|
|
3499
|
+
entries = [];
|
|
3500
|
+
max;
|
|
3501
|
+
constructor(max = 1e3) {
|
|
3502
|
+
this.max = max;
|
|
3503
|
+
}
|
|
3504
|
+
record(entry) {
|
|
3505
|
+
this.entries.push(entry);
|
|
3506
|
+
if (this.entries.length > this.max) {
|
|
3507
|
+
this.entries.splice(0, this.entries.length - this.max);
|
|
3508
|
+
}
|
|
3509
|
+
}
|
|
3510
|
+
update(commandId, patch) {
|
|
3511
|
+
const entry = this.entries.find((e) => e.commandId === commandId);
|
|
3512
|
+
if (entry) Object.assign(entry, patch);
|
|
3513
|
+
}
|
|
3514
|
+
recent(limit = 200) {
|
|
3515
|
+
return this.entries.slice(-limit);
|
|
3516
|
+
}
|
|
3517
|
+
};
|
|
3518
|
+
|
|
3519
|
+
// src/hq/alerts.ts
|
|
3520
|
+
var DEFAULT_CONFIG = {
|
|
3521
|
+
costThresholdUsd: 50,
|
|
3522
|
+
staleMachineSeconds: 120,
|
|
3523
|
+
maxAgents: 0
|
|
3524
|
+
};
|
|
3525
|
+
function resolveConfig(config) {
|
|
3526
|
+
return { ...DEFAULT_CONFIG, ...config ?? {} };
|
|
3527
|
+
}
|
|
3528
|
+
var RULES = [
|
|
3529
|
+
{
|
|
3530
|
+
id: "fleet-cost-threshold",
|
|
3531
|
+
severity: "warn",
|
|
3532
|
+
evaluate: (snapshot, config) => {
|
|
3533
|
+
if (snapshot === null) return null;
|
|
3534
|
+
const cost = snapshot.totals.totalCostUsd ?? 0;
|
|
3535
|
+
if (cost >= config.costThresholdUsd) {
|
|
3536
|
+
return `Fleet cost $${cost.toFixed(2)} exceeded threshold $${config.costThresholdUsd.toFixed(2)}`;
|
|
3537
|
+
}
|
|
3538
|
+
return null;
|
|
3539
|
+
}
|
|
3540
|
+
},
|
|
3541
|
+
{
|
|
3542
|
+
id: "all-machines-stale",
|
|
3543
|
+
severity: "warn",
|
|
3544
|
+
evaluate: (snapshot, config, now) => {
|
|
3545
|
+
if (snapshot === null) return null;
|
|
3546
|
+
const machines = snapshot.machines ?? [];
|
|
3547
|
+
if (machines.length === 0) return null;
|
|
3548
|
+
const cutoff = now - config.staleMachineSeconds * 1e3;
|
|
3549
|
+
const stale = machines.filter((m) => Date.parse(m.lastActivityAt) < cutoff);
|
|
3550
|
+
if (stale.length === machines.length) {
|
|
3551
|
+
return `All ${machines.length} machine(s) silent for ${config.staleMachineSeconds}s`;
|
|
3552
|
+
}
|
|
3553
|
+
return null;
|
|
3554
|
+
}
|
|
3555
|
+
},
|
|
3556
|
+
{
|
|
3557
|
+
id: "high-concurrency",
|
|
3558
|
+
severity: "info",
|
|
3559
|
+
evaluate: (snapshot, config) => {
|
|
3560
|
+
if (snapshot === null || config.maxAgents <= 0) return null;
|
|
3561
|
+
const agents = snapshot.totals.activeAgents ?? 0;
|
|
3562
|
+
if (agents >= config.maxAgents) {
|
|
3563
|
+
return `High fleet concurrency: ${agents} active agents (limit ${config.maxAgents})`;
|
|
3564
|
+
}
|
|
3565
|
+
return null;
|
|
3566
|
+
}
|
|
3567
|
+
},
|
|
3568
|
+
{
|
|
3569
|
+
id: "fleet-failure-spike",
|
|
3570
|
+
severity: "warn",
|
|
3571
|
+
evaluate: (snapshot) => {
|
|
3572
|
+
if (snapshot === null) return null;
|
|
3573
|
+
const fleets = snapshot.fleets ?? [];
|
|
3574
|
+
const failed = fleets.reduce((sum, f) => sum + f.failedTasks, 0);
|
|
3575
|
+
if (failed >= 5) {
|
|
3576
|
+
return `${failed} failed task(s) across the fleet`;
|
|
3577
|
+
}
|
|
3578
|
+
return null;
|
|
3579
|
+
}
|
|
3580
|
+
}
|
|
3581
|
+
];
|
|
3582
|
+
var HqAlertEngine = class {
|
|
3583
|
+
active = /* @__PURE__ */ new Map();
|
|
3584
|
+
history = [];
|
|
3585
|
+
maxHistory;
|
|
3586
|
+
timer = null;
|
|
3587
|
+
onAlert;
|
|
3588
|
+
constructor(opts) {
|
|
3589
|
+
this.onAlert = opts.onAlert;
|
|
3590
|
+
this.maxHistory = opts.maxHistory ?? 500;
|
|
3591
|
+
}
|
|
3592
|
+
/**
|
|
3593
|
+
* Evaluate all rules against the snapshot. Emits (via the `onAlert`
|
|
3594
|
+
* callback) only for rules that newly transition to firing. Clears rules
|
|
3595
|
+
* that are no longer firing. Returns the list of newly-fired alerts.
|
|
3596
|
+
*/
|
|
3597
|
+
evaluate(snapshot, config, now = Date.now()) {
|
|
3598
|
+
const resolved = resolveConfig(config);
|
|
3599
|
+
const fired = [];
|
|
3600
|
+
const firingIds = /* @__PURE__ */ new Set();
|
|
3601
|
+
for (const rule of RULES) {
|
|
3602
|
+
const message = rule.evaluate(snapshot, resolved, now);
|
|
3603
|
+
if (message !== null) {
|
|
3604
|
+
firingIds.add(rule.id);
|
|
3605
|
+
const existing = this.active.get(rule.id);
|
|
3606
|
+
if (existing === void 0) {
|
|
3607
|
+
const alert = {
|
|
3608
|
+
id: `${rule.id}-${now}`,
|
|
3609
|
+
ruleId: rule.id,
|
|
3610
|
+
severity: rule.severity,
|
|
3611
|
+
message,
|
|
3612
|
+
firstFiredAt: now,
|
|
3613
|
+
lastFiredAt: now
|
|
3614
|
+
};
|
|
3615
|
+
this.active.set(rule.id, alert);
|
|
3616
|
+
this.history.push(alert);
|
|
3617
|
+
if (this.history.length > this.maxHistory) this.history.splice(0, this.history.length - this.maxHistory);
|
|
3618
|
+
fired.push(alert);
|
|
3619
|
+
this.onAlert(alert);
|
|
3620
|
+
} else {
|
|
3621
|
+
existing.lastFiredAt = now;
|
|
3622
|
+
}
|
|
3623
|
+
}
|
|
3624
|
+
}
|
|
3625
|
+
for (const id of Array.from(this.active.keys())) {
|
|
3626
|
+
if (!firingIds.has(id)) {
|
|
3627
|
+
this.active.delete(id);
|
|
3628
|
+
}
|
|
3629
|
+
}
|
|
3630
|
+
return fired;
|
|
3631
|
+
}
|
|
3632
|
+
/** Currently-active (firing) alerts. */
|
|
3633
|
+
activeAlerts() {
|
|
3634
|
+
return Array.from(this.active.values());
|
|
3635
|
+
}
|
|
3636
|
+
/** Historical alerts (newest-last), capped at maxHistory. */
|
|
3637
|
+
recentAlerts(limit = 100) {
|
|
3638
|
+
return this.history.slice(-limit);
|
|
3639
|
+
}
|
|
3640
|
+
/**
|
|
3641
|
+
* Start periodic evaluation against a snapshot getter. The timer is
|
|
3642
|
+
* unref'd so it never keeps the process alive. Returns a disposer.
|
|
3643
|
+
*/
|
|
3644
|
+
startPeriodic(getSnapshot, config, intervalMs = 15e3) {
|
|
3645
|
+
if (this.timer !== null) return () => void 0;
|
|
3646
|
+
const tick = () => {
|
|
3647
|
+
try {
|
|
3648
|
+
const cfg = typeof config === "function" ? config() : config;
|
|
3649
|
+
this.evaluate(getSnapshot(), cfg);
|
|
3650
|
+
} catch {
|
|
3651
|
+
}
|
|
3652
|
+
};
|
|
3653
|
+
this.timer = setInterval(tick, intervalMs);
|
|
3654
|
+
this.timer.unref?.();
|
|
3655
|
+
return () => {
|
|
3656
|
+
if (this.timer !== null) {
|
|
3657
|
+
clearInterval(this.timer);
|
|
3658
|
+
this.timer = null;
|
|
3659
|
+
}
|
|
3660
|
+
};
|
|
3661
|
+
}
|
|
3662
|
+
};
|
|
3663
|
+
function toAlertMessage(alert) {
|
|
3664
|
+
return {
|
|
3665
|
+
type: "hq.alert",
|
|
3666
|
+
severity: alert.severity,
|
|
3667
|
+
message: `[${alert.ruleId}] ${alert.message}`,
|
|
3668
|
+
timestamp: new Date(alert.lastFiredAt).toISOString()
|
|
3669
|
+
};
|
|
3670
|
+
}
|
|
3671
|
+
|
|
3672
|
+
export { DEFAULT_HQ_REDACTION_POLICY, HQ_AUTH_FILE_VERSION, HQ_COMMAND_TYPES, HQ_PROTOCOL_VERSION, HqAlertEngine, HqCommandAuditLog, HqEventLog, HqPublisher, HqSnapshotStore, HqTimeseriesStore, buildTranscriptFromEvents, createGlobalMailbox, createHqEventEnvelope, createHqPersistence, createHqPublisherFromEnv, createMailboxEventPayload, createMailboxSnapshotPayload, createMailboxSnapshotPayloadFromMailbox, defaultHqDataDir, discoverLocalHqEndpoint, emptyHqAuthFile, ensureHqFirstRunAuthFile, hqAuthFilePath, hqRuntimeFilePath, mapMailboxAgentToHqSummary, mapMailboxMessageToHqSummary, mapSessionEventToEntries, mergeToolResults, mintHqBrowserToken, mintHqToken, mutateHqAuthFile, parseHqEventPayload, parseHqFrame, readHqAuthFile, readHqRuntimeFileSync, redactHqEvent, redactHqValue, resolveHqConfig, resolveHqConfigFromEnv, resolveHqDataDir, scrubAndTruncateHqPreview, startAgentMonitorEventBridge, startBrainTelemetryBridge, startCostTelemetryBridge, startFleetTelemetryBridge, startSessionTelemetryBridge, startToolTelemetryBridge, startWorktreeTelemetryBridge, summarizeHqToolArgs, toAlertMessage, tokenHasCapability, validateHqCommand, watchHqAuthFile, writeHqAuthFile, writeHqRuntimeFile };
|
|
2624
3673
|
//# sourceMappingURL=index.js.map
|
|
2625
3674
|
//# sourceMappingURL=index.js.map
|