@wrongstack/core 0.270.0 → 0.272.1
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-PcHQl_UQ.d.ts → agent-bridge-DFQYEeXf.d.ts} +1 -1
- package/dist/{agent-subagent-runner-SHJW7t8q.d.ts → agent-subagent-runner-BZa_IEcd.d.ts} +7 -7
- package/dist/{brain-BYcK__Ym.d.ts → brain-etbcbRwV.d.ts} +95 -2
- package/dist/{compactor-C2RKEBtC.d.ts → compactor-72ug-ZRB.d.ts} +1 -1
- package/dist/{config-C_ae2k86.d.ts → config-rRS8yorV.d.ts} +71 -2
- package/dist/{context-Dp87Bcaq.d.ts → context-Dw55zZ_Q.d.ts} +110 -1
- package/dist/coordination/index.d.ts +181 -17
- package/dist/coordination/index.js +1018 -166
- package/dist/coordination/index.js.map +1 -1
- package/dist/defaults/index.d.ts +25 -25
- package/dist/defaults/index.js +804 -222
- package/dist/defaults/index.js.map +1 -1
- package/dist/execution/index.d.ts +23 -18
- package/dist/execution/index.js +136 -41
- package/dist/execution/index.js.map +1 -1
- package/dist/execution/prompt-enhancer.d.ts +36 -6
- package/dist/execution/prompt-enhancer.js +35 -9
- package/dist/execution/prompt-enhancer.js.map +1 -1
- package/dist/extension/index.d.ts +6 -6
- package/dist/{global-mailbox-Bvrz1P3f.d.ts → global-mailbox-DJ4EoRr0.d.ts} +145 -5
- package/dist/{goal-preamble-CA_4yiGQ.d.ts → goal-preamble-hM8BH7TK.d.ts} +9 -9
- package/dist/{goal-store-DhuJoUNG.d.ts → goal-store-CWlbT0TO.d.ts} +1 -1
- package/dist/hq/index.d.ts +95 -6
- package/dist/hq/index.js +628 -50
- package/dist/hq/index.js.map +1 -1
- package/dist/{index-whDfTANu.d.ts → index-2Lhk5v0o.d.ts} +2 -2
- package/dist/{index-W4VJCzHa.d.ts → index-DWm_PE9L.d.ts} +5 -5
- package/dist/{index-CZQ6Pwbs.d.ts → index-DqW4o62H.d.ts} +8 -8
- package/dist/index.d.ts +96 -56
- package/dist/index.js +2464 -519
- package/dist/index.js.map +1 -1
- package/dist/infrastructure/index.d.ts +6 -6
- package/dist/infrastructure/index.js +5 -3
- package/dist/infrastructure/index.js.map +1 -1
- package/dist/kernel/index.d.ts +9 -9
- package/dist/kernel/index.js.map +1 -1
- package/dist/{mcp-servers-DJdZiRcv.d.ts → mcp-servers-BpWHTKlE.d.ts} +3 -3
- package/dist/models/index.d.ts +5 -5
- package/dist/models/index.js +28 -5
- package/dist/models/index.js.map +1 -1
- package/dist/{models-registry-C3a-2-Yd.d.ts → models-registry-CXQFUn5t.d.ts} +1 -1
- package/dist/{multi-agent-coordinator-CJSpTe5O.d.ts → multi-agent-coordinator-jyimfo7D.d.ts} +1 -1
- package/dist/{null-fleet-bus-QVshIsDx.d.ts → null-fleet-bus-DOGQcvrY.d.ts} +6 -6
- package/dist/observability/index.d.ts +2 -2
- package/dist/{parallel-eternal-engine-D9y5Pkcc.d.ts → parallel-eternal-engine-rItJBYp9.d.ts} +9 -9
- package/dist/{path-resolver-CnQ8SIfh.d.ts → path-resolver-DrpF5MGK.d.ts} +3 -3
- package/dist/{permission-CvYQNUqZ.d.ts → permission-CC7XFYWG.d.ts} +1 -1
- package/dist/{permission-policy-D5Ss8j4B.d.ts → permission-policy-cYR4RJmw.d.ts} +2 -2
- package/dist/{pipeline-l_zzFRh3.d.ts → pipeline-Ckkn3AOA.d.ts} +2 -2
- package/dist/{plan-templates-NtPgyeJA.d.ts → plan-templates-BvHw5Znw.d.ts} +33 -9
- package/dist/{provider-model-resolve-d5poT5y0.d.ts → provider-model-resolve-nZqnCeaR.d.ts} +3 -3
- package/dist/{provider-runner-gkctlQV_.d.ts → provider-runner-zVOn1p67.d.ts} +3 -3
- package/dist/{retry-policy-CtFhfwa8.d.ts → retry-policy-BV7nzeAd.d.ts} +1 -1
- package/dist/sdd/index.d.ts +8 -8
- package/dist/sdd/index.js +2 -0
- package/dist/sdd/index.js.map +1 -1
- package/dist/{secret-vault-BLsVmTIK.d.ts → secret-vault-eMBKfheR.d.ts} +9 -1
- package/dist/security/index.d.ts +5 -5
- package/dist/security/index.js +137 -10
- package/dist/security/index.js.map +1 -1
- package/dist/{selector-CXl2_y9W.d.ts → selector-C4ORTOid.d.ts} +1 -1
- package/dist/{session-event-bridge-Ccud20CC.d.ts → session-event-bridge-CeNpUL9w.d.ts} +1 -1
- package/dist/{session-reader-ZeXQmsmE.d.ts → session-reader-BepLSnGL.d.ts} +1 -1
- package/dist/storage/index.d.ts +50 -13
- package/dist/storage/index.js +620 -220
- package/dist/storage/index.js.map +1 -1
- package/dist/tools/index.d.ts +2 -2
- package/dist/tools/index.js +9 -2
- package/dist/tools/index.js.map +1 -1
- package/dist/types/index.d.ts +19 -19
- package/dist/types/index.js +202 -41
- package/dist/types/index.js.map +1 -1
- package/dist/utils/index.d.ts +17 -4
- package/dist/utils/index.js +48 -9
- package/dist/utils/index.js.map +1 -1
- package/package.json +1 -1
package/dist/defaults/index.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import * as crypto2 from 'crypto';
|
|
2
|
-
import { randomBytes, createCipheriv, createDecipheriv, randomUUID, createHash } from 'crypto';
|
|
2
|
+
import { randomBytes, createCipheriv, createDecipheriv, randomUUID, scryptSync, createHash } from 'crypto';
|
|
3
3
|
import * as fsp2 from 'fs/promises';
|
|
4
4
|
import * as path4 from 'path';
|
|
5
5
|
import { isAbsolute, resolve } from 'path';
|
|
6
6
|
import * as fs4 from 'fs';
|
|
7
|
+
import { createReadStream } from 'fs';
|
|
8
|
+
import { createInterface } from 'readline';
|
|
7
9
|
import * as os from 'os';
|
|
8
10
|
import { hostname } from 'os';
|
|
9
11
|
import { execFile } from 'child_process';
|
|
@@ -105,7 +107,7 @@ async function withFileLock(targetPath, fn, opts = {}) {
|
|
|
105
107
|
if (Date.now() - started >= timeoutMs) {
|
|
106
108
|
throw new Error(`Timed out waiting for file lock: ${targetPath}`);
|
|
107
109
|
}
|
|
108
|
-
await new Promise((
|
|
110
|
+
await new Promise((resolve7) => setTimeout(resolve7, 25));
|
|
109
111
|
}
|
|
110
112
|
}
|
|
111
113
|
try {
|
|
@@ -138,7 +140,7 @@ async function renameWithRetry(from, to) {
|
|
|
138
140
|
if (!code || !TRANSIENT_RENAME_CODES.has(code) || i === delays.length) {
|
|
139
141
|
throw err;
|
|
140
142
|
}
|
|
141
|
-
await new Promise((
|
|
143
|
+
await new Promise((resolve7) => setTimeout(resolve7, delays[i]));
|
|
142
144
|
}
|
|
143
145
|
}
|
|
144
146
|
throw lastErr;
|
|
@@ -706,6 +708,7 @@ function escapeRegex(s) {
|
|
|
706
708
|
}
|
|
707
709
|
var COMPILED_GLOB_CACHE = /* @__PURE__ */ new Map();
|
|
708
710
|
var CACHE_MAX_SIZE = 2e3;
|
|
711
|
+
var NEVER_MATCH = /[^\s\S]/;
|
|
709
712
|
function getCachedGlob(pattern) {
|
|
710
713
|
const cached = COMPILED_GLOB_CACHE.get(pattern);
|
|
711
714
|
if (cached) return cached;
|
|
@@ -715,7 +718,12 @@ function getCachedGlob(pattern) {
|
|
|
715
718
|
COMPILED_GLOB_CACHE.delete(expectDefined(keys[i]));
|
|
716
719
|
}
|
|
717
720
|
}
|
|
718
|
-
|
|
721
|
+
let re;
|
|
722
|
+
try {
|
|
723
|
+
re = compileGlob(pattern);
|
|
724
|
+
} catch {
|
|
725
|
+
re = NEVER_MATCH;
|
|
726
|
+
}
|
|
719
727
|
COMPILED_GLOB_CACHE.set(pattern, re);
|
|
720
728
|
return re;
|
|
721
729
|
}
|
|
@@ -874,11 +882,11 @@ function validateAgainstSchema(value, schema) {
|
|
|
874
882
|
walk(value, schema, "", errors);
|
|
875
883
|
return { ok: errors.length === 0, errors };
|
|
876
884
|
}
|
|
877
|
-
function walk(value, schema,
|
|
885
|
+
function walk(value, schema, path22, errors) {
|
|
878
886
|
if (schema.enum !== void 0) {
|
|
879
887
|
if (!schema.enum.some((e) => deepEqual(e, value))) {
|
|
880
888
|
errors.push({
|
|
881
|
-
path:
|
|
889
|
+
path: path22 || "<root>",
|
|
882
890
|
message: `expected one of ${JSON.stringify(schema.enum)}, got ${JSON.stringify(value)}`
|
|
883
891
|
});
|
|
884
892
|
return;
|
|
@@ -887,7 +895,7 @@ function walk(value, schema, path21, errors) {
|
|
|
887
895
|
if (typeof schema.type === "string") {
|
|
888
896
|
if (!checkType(value, schema.type)) {
|
|
889
897
|
errors.push({
|
|
890
|
-
path:
|
|
898
|
+
path: path22 || "<root>",
|
|
891
899
|
message: `expected ${schema.type}, got ${describeType(value)}`
|
|
892
900
|
});
|
|
893
901
|
return;
|
|
@@ -897,20 +905,20 @@ function walk(value, schema, path21, errors) {
|
|
|
897
905
|
const obj = value;
|
|
898
906
|
for (const req of schema.required ?? []) {
|
|
899
907
|
if (!(req in obj)) {
|
|
900
|
-
errors.push({ path: joinPath(
|
|
908
|
+
errors.push({ path: joinPath(path22, req), message: "required property missing" });
|
|
901
909
|
}
|
|
902
910
|
}
|
|
903
911
|
if (schema.properties) {
|
|
904
912
|
for (const [key, subSchema] of Object.entries(schema.properties)) {
|
|
905
913
|
if (key in obj) {
|
|
906
|
-
walk(obj[key], subSchema, joinPath(
|
|
914
|
+
walk(obj[key], subSchema, joinPath(path22, key), errors);
|
|
907
915
|
}
|
|
908
916
|
}
|
|
909
917
|
}
|
|
910
918
|
}
|
|
911
919
|
if (schema.type === "array" && Array.isArray(value) && schema.items) {
|
|
912
920
|
for (let i = 0; i < value.length; i++) {
|
|
913
|
-
walk(value[i], schema.items, `${
|
|
921
|
+
walk(value[i], schema.items, `${path22}[${i}]`, errors);
|
|
914
922
|
}
|
|
915
923
|
}
|
|
916
924
|
}
|
|
@@ -1080,7 +1088,7 @@ function safeParse(input, maxBytes = 5e6) {
|
|
|
1080
1088
|
|
|
1081
1089
|
// src/utils/sleep.ts
|
|
1082
1090
|
function sleep(ms) {
|
|
1083
|
-
return new Promise((
|
|
1091
|
+
return new Promise((resolve7) => setTimeout(resolve7, ms));
|
|
1084
1092
|
}
|
|
1085
1093
|
|
|
1086
1094
|
// src/utils/string.ts
|
|
@@ -1218,18 +1226,20 @@ var MODEL_FAMILY_RATIO = {
|
|
|
1218
1226
|
deepseek: 3.5
|
|
1219
1227
|
};
|
|
1220
1228
|
var ESTIMATE_CACHE = /* @__PURE__ */ new Map();
|
|
1229
|
+
var _estimateCacheOrder = [];
|
|
1221
1230
|
var ESTIMATE_CACHE_MAX_SIZE = 5e4;
|
|
1222
1231
|
function getCachedEstimate(key, compute) {
|
|
1223
1232
|
const existing = ESTIMATE_CACHE.get(key);
|
|
1224
1233
|
if (existing !== void 0) return existing;
|
|
1225
1234
|
if (ESTIMATE_CACHE.size >= ESTIMATE_CACHE_MAX_SIZE) {
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
ESTIMATE_CACHE.delete(
|
|
1235
|
+
while (ESTIMATE_CACHE.size > Math.floor(ESTIMATE_CACHE_MAX_SIZE / 2)) {
|
|
1236
|
+
const oldest = _estimateCacheOrder.shift();
|
|
1237
|
+
if (oldest !== void 0) ESTIMATE_CACHE.delete(oldest);
|
|
1229
1238
|
}
|
|
1230
1239
|
}
|
|
1231
1240
|
const estimate = compute(key);
|
|
1232
1241
|
ESTIMATE_CACHE.set(key, estimate);
|
|
1242
|
+
_estimateCacheOrder.push(key);
|
|
1233
1243
|
return estimate;
|
|
1234
1244
|
}
|
|
1235
1245
|
function estimateToolInputTokens(input) {
|
|
@@ -2073,8 +2083,6 @@ function resolveWstackPaths(opts) {
|
|
|
2073
2083
|
projectStatus: (projectHash2) => path4.join(globalRoot, "projects", projectHash2, "status.json")
|
|
2074
2084
|
};
|
|
2075
2085
|
}
|
|
2076
|
-
|
|
2077
|
-
// src/storage/session-store.ts
|
|
2078
2086
|
function sanitizeModel(model) {
|
|
2079
2087
|
return model.replace(/[^a-zA-Z0-9_-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 40);
|
|
2080
2088
|
}
|
|
@@ -2085,6 +2093,8 @@ function generateSessionId(startedAt, model) {
|
|
|
2085
2093
|
const modelPart = model ? `_${sanitizeModel(model)}` : "";
|
|
2086
2094
|
return `${date}/${time}Z${modelPart}_${suffix}`;
|
|
2087
2095
|
}
|
|
2096
|
+
|
|
2097
|
+
// src/storage/session-store.ts
|
|
2088
2098
|
var DefaultSessionStore = class _DefaultSessionStore {
|
|
2089
2099
|
dir;
|
|
2090
2100
|
events;
|
|
@@ -2102,6 +2112,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
2102
2112
|
_loadCache = /* @__PURE__ */ new Map();
|
|
2103
2113
|
_indexCache = null;
|
|
2104
2114
|
static LOAD_CACHE_MAX_ENTRIES = 50;
|
|
2115
|
+
static LIST_SCAN_CONCURRENCY = 32;
|
|
2105
2116
|
constructor(opts) {
|
|
2106
2117
|
this.dir = opts.dir;
|
|
2107
2118
|
this.events = opts.events;
|
|
@@ -2118,7 +2129,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
2118
2129
|
this._loadCache.clear();
|
|
2119
2130
|
}
|
|
2120
2131
|
}
|
|
2121
|
-
//
|
|
2132
|
+
// ── Storage event helpers ───────────────────────────────────────────────────
|
|
2122
2133
|
emitRead(sessionId, filePath, operation, outcome, durationMs, error) {
|
|
2123
2134
|
this.events?.emit("storage.read", {
|
|
2124
2135
|
sessionId,
|
|
@@ -2233,7 +2244,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
2233
2244
|
this.events,
|
|
2234
2245
|
{
|
|
2235
2246
|
resumed: true,
|
|
2236
|
-
// Shard directory (sessions/<date>/)
|
|
2247
|
+
// Shard directory (sessions/<date>/) — must match create() so the
|
|
2237
2248
|
// .summary.json sidecar lands next to the JSONL instead of the
|
|
2238
2249
|
// sessions root (where summaryFor() would never find it).
|
|
2239
2250
|
dir: path4.dirname(file),
|
|
@@ -2274,19 +2285,93 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
2274
2285
|
const raw = await fsp2.readFile(file, "utf8");
|
|
2275
2286
|
const lines = raw.split("\n").filter((l) => l.trim());
|
|
2276
2287
|
const events = [];
|
|
2288
|
+
let sessionStartEvent;
|
|
2289
|
+
let sessionEndEvent;
|
|
2290
|
+
let sessionModel;
|
|
2291
|
+
let sessionProvider;
|
|
2292
|
+
let sessionPendingToolUses;
|
|
2293
|
+
const messages = [];
|
|
2294
|
+
const openToolUses = /* @__PURE__ */ new Set();
|
|
2295
|
+
let usage = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
|
|
2277
2296
|
for (const line of lines) {
|
|
2278
2297
|
try {
|
|
2279
2298
|
const parsed = JSON.parse(line);
|
|
2280
2299
|
if (parsed !== null && typeof parsed === "object" && typeof parsed.type === "string" && typeof parsed.ts === "string") {
|
|
2281
|
-
|
|
2300
|
+
const ev = parsed;
|
|
2301
|
+
events.push(ev);
|
|
2302
|
+
if (ev.type === "session_start" && !sessionStartEvent) {
|
|
2303
|
+
sessionStartEvent = ev;
|
|
2304
|
+
sessionModel = ev.model;
|
|
2305
|
+
sessionProvider = ev.provider;
|
|
2306
|
+
}
|
|
2307
|
+
if (ev.type === "session_end") {
|
|
2308
|
+
sessionEndEvent = ev;
|
|
2309
|
+
sessionPendingToolUses = ev.pendingToolUses;
|
|
2310
|
+
}
|
|
2311
|
+
if (ev.type === "user_input") {
|
|
2312
|
+
openToolUses.clear();
|
|
2313
|
+
messages.push({ role: "user", content: ev.content, ts: ev.ts });
|
|
2314
|
+
} else if (ev.type === "llm_response") {
|
|
2315
|
+
messages.push({ role: "assistant", content: ev.content, ts: ev.ts });
|
|
2316
|
+
for (const b of ev.content) {
|
|
2317
|
+
if (b.type === "tool_use") openToolUses.add(b.id);
|
|
2318
|
+
}
|
|
2319
|
+
usage = {
|
|
2320
|
+
input: usage.input + (ev.usage.input ?? 0),
|
|
2321
|
+
output: usage.output + (ev.usage.output ?? 0),
|
|
2322
|
+
cacheRead: (usage.cacheRead ?? 0) + (ev.usage.cacheRead ?? 0),
|
|
2323
|
+
cacheWrite: (usage.cacheWrite ?? 0) + (ev.usage.cacheWrite ?? 0)
|
|
2324
|
+
};
|
|
2325
|
+
} else if (ev.type === "tool_result") {
|
|
2326
|
+
if (!openToolUses.has(ev.id)) {
|
|
2327
|
+
this.events?.emit("session.damaged", {
|
|
2328
|
+
sessionId: id,
|
|
2329
|
+
detail: `Orphan tool_result "${ev.id}" has no matching tool_use`
|
|
2330
|
+
});
|
|
2331
|
+
continue;
|
|
2332
|
+
}
|
|
2333
|
+
openToolUses.delete(ev.id);
|
|
2334
|
+
const resultBlock = {
|
|
2335
|
+
type: "tool_result",
|
|
2336
|
+
tool_use_id: ev.id,
|
|
2337
|
+
content: typeof ev.content === "string" ? ev.content : JSON.stringify(ev.content),
|
|
2338
|
+
is_error: ev.isError
|
|
2339
|
+
};
|
|
2340
|
+
const last = messages[messages.length - 1];
|
|
2341
|
+
const lastIsToolResultUser = last?.role === "user" && Array.isArray(last.content) && last.content.every((b) => b.type === "tool_result");
|
|
2342
|
+
if (lastIsToolResultUser && Array.isArray(last.content)) {
|
|
2343
|
+
last.content.push(resultBlock);
|
|
2344
|
+
} else {
|
|
2345
|
+
messages.push({ role: "user", content: [resultBlock], ts: ev.ts });
|
|
2346
|
+
}
|
|
2347
|
+
}
|
|
2282
2348
|
}
|
|
2283
2349
|
} catch {
|
|
2284
2350
|
}
|
|
2285
2351
|
}
|
|
2286
|
-
|
|
2287
|
-
|
|
2352
|
+
if (openToolUses.size > 0) {
|
|
2353
|
+
this.events?.emit("session.damaged", {
|
|
2354
|
+
sessionId: id,
|
|
2355
|
+
detail: `${openToolUses.size} tool_use blocks without matching results - replay repaired`
|
|
2356
|
+
});
|
|
2357
|
+
}
|
|
2358
|
+
const repaired = repairToolUseAdjacency(messages);
|
|
2359
|
+
if (repaired.report.changed) {
|
|
2360
|
+
this.events?.emit("session.damaged", {
|
|
2361
|
+
sessionId: id,
|
|
2362
|
+
detail: `Repaired replay adjacency: removed ${repaired.report.removedToolUses.length} tool_use, ${repaired.report.removedToolResults.length} tool_result, ${repaired.report.removedMessages} empty messages`
|
|
2363
|
+
});
|
|
2364
|
+
}
|
|
2365
|
+
const meta = {
|
|
2366
|
+
id,
|
|
2367
|
+
startedAt: sessionStartEvent?.ts ?? (/* @__PURE__ */ new Date(0)).toISOString(),
|
|
2368
|
+
endedAt: sessionEndEvent?.ts,
|
|
2369
|
+
model: sessionModel,
|
|
2370
|
+
provider: sessionProvider,
|
|
2371
|
+
pendingToolUses: sessionPendingToolUses
|
|
2372
|
+
};
|
|
2288
2373
|
const toolCallEnds = extractToolCallEnds(events);
|
|
2289
|
-
const data = { metadata: meta, events, messages, usage, toolCallEnds };
|
|
2374
|
+
const data = { metadata: meta, events, messages: repaired.messages, usage, toolCallEnds };
|
|
2290
2375
|
if (this._loadCache.size >= _DefaultSessionStore.LOAD_CACHE_MAX_ENTRIES) {
|
|
2291
2376
|
const oldest = this._loadCache.keys().next().value;
|
|
2292
2377
|
if (oldest !== void 0) {
|
|
@@ -2324,20 +2409,12 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
2324
2409
|
});
|
|
2325
2410
|
return indexed.slice(0, limit);
|
|
2326
2411
|
}
|
|
2327
|
-
|
|
2328
|
-
const sessions = await Promise.all(ids.map((id) => this.summaryFor(id).catch(() => null)));
|
|
2329
|
-
const out = sessions.filter((s) => s !== null);
|
|
2330
|
-
out.sort((a, b) => {
|
|
2331
|
-
if (a.startedAt < b.startedAt) return 1;
|
|
2332
|
-
if (a.startedAt > b.startedAt) return -1;
|
|
2333
|
-
return a.id.localeCompare(b.id);
|
|
2334
|
-
});
|
|
2335
|
-
return out.slice(0, limit);
|
|
2412
|
+
return await this.listFromDirectoryScan(limit);
|
|
2336
2413
|
} catch {
|
|
2337
2414
|
return [];
|
|
2338
2415
|
}
|
|
2339
2416
|
}
|
|
2340
|
-
//
|
|
2417
|
+
// ── Session index (_index.jsonl) ─────────────────────────────────────────
|
|
2341
2418
|
//
|
|
2342
2419
|
// One JSON line per closed session, appended atomically on close().
|
|
2343
2420
|
// When a session is deleted, a tombstone {action:"delete",id:"..."} is
|
|
@@ -2457,43 +2534,102 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
2457
2534
|
this._indexCache = null;
|
|
2458
2535
|
return valid.length;
|
|
2459
2536
|
}
|
|
2537
|
+
async listFromDirectoryScan(limit) {
|
|
2538
|
+
const refs = await this.collectSessionFiles(this.dir);
|
|
2539
|
+
const candidates = await mapWithConcurrency(
|
|
2540
|
+
refs,
|
|
2541
|
+
_DefaultSessionStore.LIST_SCAN_CONCURRENCY,
|
|
2542
|
+
async (ref) => {
|
|
2543
|
+
const manifest = await this.readSummaryManifest(ref.id);
|
|
2544
|
+
if (manifest) return { summary: manifest, needsBackfill: false };
|
|
2545
|
+
const summary = await this.summaryHeaderFor(ref);
|
|
2546
|
+
return summary ? { summary, needsBackfill: true } : null;
|
|
2547
|
+
}
|
|
2548
|
+
);
|
|
2549
|
+
const out = candidates.filter((s) => s !== null);
|
|
2550
|
+
out.sort((a, b) => compareSessionSummaries(a.summary, b.summary));
|
|
2551
|
+
const selected = out.slice(0, limit);
|
|
2552
|
+
const summaries = await mapWithConcurrency(
|
|
2553
|
+
selected,
|
|
2554
|
+
Math.min(_DefaultSessionStore.LIST_SCAN_CONCURRENCY, Math.max(1, limit)),
|
|
2555
|
+
async (candidate) => {
|
|
2556
|
+
if (!candidate.needsBackfill) return candidate.summary;
|
|
2557
|
+
return await this.summaryFor(candidate.summary.id).catch(() => candidate.summary);
|
|
2558
|
+
}
|
|
2559
|
+
);
|
|
2560
|
+
return summaries.filter((s) => s !== null);
|
|
2561
|
+
}
|
|
2562
|
+
async collectSessionFiles(dir, prefix = "", depth = 0) {
|
|
2563
|
+
let entries;
|
|
2564
|
+
try {
|
|
2565
|
+
entries = await fsp2.readdir(dir, { withFileTypes: true });
|
|
2566
|
+
} catch {
|
|
2567
|
+
return [];
|
|
2568
|
+
}
|
|
2569
|
+
const dirEntries = [];
|
|
2570
|
+
const files = [];
|
|
2571
|
+
for (const entry of entries) {
|
|
2572
|
+
if (entry.name.startsWith(".") && entry.name !== ".wrongstack") continue;
|
|
2573
|
+
if (entry.name === "shared" || entry.name === "subagents" || entry.name === "attachments")
|
|
2574
|
+
continue;
|
|
2575
|
+
if (entry.isDirectory()) {
|
|
2576
|
+
dirEntries.push(entry);
|
|
2577
|
+
} else if (entry.isFile() && entry.name.endsWith(".jsonl")) {
|
|
2578
|
+
if (entry.name === "_index.jsonl") continue;
|
|
2579
|
+
const base = entry.name.replace(/\.jsonl$/, "");
|
|
2580
|
+
const id = prefix ? `${prefix}/${base}` : base;
|
|
2581
|
+
files.push({ id, filePath: path4.join(dir, entry.name) });
|
|
2582
|
+
}
|
|
2583
|
+
}
|
|
2584
|
+
const childFileArrays = await Promise.all(
|
|
2585
|
+
dirEntries.map((entry) => {
|
|
2586
|
+
const childPrefix = depth === 0 ? entry.name : `${prefix}/${entry.name}`;
|
|
2587
|
+
return this.collectSessionFiles(path4.join(dir, entry.name), childPrefix, depth + 1);
|
|
2588
|
+
})
|
|
2589
|
+
);
|
|
2590
|
+
return [...childFileArrays.flat(), ...files];
|
|
2591
|
+
}
|
|
2460
2592
|
/** Recursively collect session IDs from date-shard subdirectories.
|
|
2461
|
-
* IDs include the date-prefix path (e.g. "2026-06-06/17-46-57Z_
|
|
2593
|
+
* IDs include the date-prefix path (e.g. "2026-06-06/17-46-57Z_…").
|
|
2462
2594
|
* Skips `.jsonl`/`.summary.json` root files, dot-files, and
|
|
2463
2595
|
* sub-directories that belong to fleet/subagent sessions. */
|
|
2464
2596
|
async collectSessionIds(dir, prefix = "", depth = 0) {
|
|
2465
|
-
const ids = [];
|
|
2466
2597
|
let entries;
|
|
2467
2598
|
try {
|
|
2468
2599
|
entries = await fsp2.readdir(dir, { withFileTypes: true });
|
|
2469
2600
|
} catch {
|
|
2470
|
-
return
|
|
2601
|
+
return [];
|
|
2471
2602
|
}
|
|
2603
|
+
const dirEntries = [];
|
|
2604
|
+
const fileIds = [];
|
|
2472
2605
|
for (const entry of entries) {
|
|
2473
2606
|
if (entry.name.startsWith(".") && entry.name !== ".wrongstack") continue;
|
|
2474
2607
|
if (entry.name === "shared" || entry.name === "subagents" || entry.name === "attachments")
|
|
2475
2608
|
continue;
|
|
2476
2609
|
if (entry.isDirectory()) {
|
|
2477
|
-
|
|
2478
|
-
ids.push(...await this.collectSessionIds(path4.join(dir, entry.name), childPrefix, depth + 1));
|
|
2610
|
+
dirEntries.push(entry);
|
|
2479
2611
|
} else if (entry.isFile() && entry.name.endsWith(".jsonl")) {
|
|
2480
2612
|
if (entry.name === "_index.jsonl") continue;
|
|
2481
2613
|
const base = entry.name.replace(/\.jsonl$/, "");
|
|
2482
|
-
|
|
2614
|
+
fileIds.push(prefix ? `${prefix}/${base}` : base);
|
|
2483
2615
|
}
|
|
2484
2616
|
}
|
|
2485
|
-
|
|
2617
|
+
const childIdArrays = await Promise.all(
|
|
2618
|
+
dirEntries.map((entry) => {
|
|
2619
|
+
const childPrefix = depth === 0 ? entry.name : `${prefix}/${entry.name}`;
|
|
2620
|
+
return this.collectSessionIds(path4.join(dir, entry.name), childPrefix, depth + 1);
|
|
2621
|
+
})
|
|
2622
|
+
);
|
|
2623
|
+
return [...childIdArrays.flat(), ...fileIds];
|
|
2486
2624
|
}
|
|
2487
2625
|
async summaryFor(id) {
|
|
2488
2626
|
const manifest = this.sessionPath(id, ".summary.json");
|
|
2489
2627
|
const t0 = Date.now();
|
|
2490
2628
|
let outcome = "success";
|
|
2491
2629
|
let errorMsg;
|
|
2630
|
+
const fromManifest = await this.readSummaryManifest(id, t0);
|
|
2631
|
+
if (fromManifest) return fromManifest;
|
|
2492
2632
|
try {
|
|
2493
|
-
const raw = await fsp2.readFile(manifest, "utf8");
|
|
2494
|
-
this.emitRead(id, manifest, "summary", "success", Date.now() - t0);
|
|
2495
|
-
return JSON.parse(raw);
|
|
2496
|
-
} catch {
|
|
2497
2633
|
const full = this.sessionPath(id, ".jsonl");
|
|
2498
2634
|
const stat6 = await fsp2.stat(full);
|
|
2499
2635
|
const summary = await this.summarize(id, stat6.mtime.toISOString());
|
|
@@ -2509,9 +2645,81 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
2509
2645
|
}));
|
|
2510
2646
|
});
|
|
2511
2647
|
outcome = "failure";
|
|
2512
|
-
errorMsg = "summary fallback \
|
|
2648
|
+
errorMsg = "summary fallback \xE2\u20AC\u201D manifest rebuilt";
|
|
2513
2649
|
this.emitRead(id, manifest, "summary", outcome, Date.now() - t0, errorMsg);
|
|
2514
2650
|
return summary;
|
|
2651
|
+
} catch (err) {
|
|
2652
|
+
outcome = "failure";
|
|
2653
|
+
errorMsg = toErrorMessage(err);
|
|
2654
|
+
this.emitRead(id, manifest, "summary", outcome, Date.now() - t0, errorMsg);
|
|
2655
|
+
return {
|
|
2656
|
+
id,
|
|
2657
|
+
title: "(damaged)",
|
|
2658
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2659
|
+
model: "unknown",
|
|
2660
|
+
provider: "unknown",
|
|
2661
|
+
tokenTotal: 0
|
|
2662
|
+
};
|
|
2663
|
+
}
|
|
2664
|
+
}
|
|
2665
|
+
async readSummaryManifest(id, startTime = Date.now()) {
|
|
2666
|
+
const manifest = this.sessionPath(id, ".summary.json");
|
|
2667
|
+
try {
|
|
2668
|
+
const raw = await fsp2.readFile(manifest, "utf8");
|
|
2669
|
+
this.emitRead(id, manifest, "summary", "success", Date.now() - startTime);
|
|
2670
|
+
return JSON.parse(raw);
|
|
2671
|
+
} catch {
|
|
2672
|
+
return null;
|
|
2673
|
+
}
|
|
2674
|
+
}
|
|
2675
|
+
async summaryHeaderFor(ref) {
|
|
2676
|
+
let mtime = (/* @__PURE__ */ new Date(0)).toISOString();
|
|
2677
|
+
try {
|
|
2678
|
+
const stat6 = await fsp2.stat(ref.filePath);
|
|
2679
|
+
if (!stat6.isFile()) {
|
|
2680
|
+
return {
|
|
2681
|
+
id: ref.id,
|
|
2682
|
+
title: "(damaged)",
|
|
2683
|
+
startedAt: stat6.mtime.toISOString(),
|
|
2684
|
+
model: "unknown",
|
|
2685
|
+
provider: "unknown",
|
|
2686
|
+
tokenTotal: 0
|
|
2687
|
+
};
|
|
2688
|
+
}
|
|
2689
|
+
mtime = stat6.mtime.toISOString();
|
|
2690
|
+
} catch {
|
|
2691
|
+
return null;
|
|
2692
|
+
}
|
|
2693
|
+
try {
|
|
2694
|
+
for await (const event of this.iterSessionEvents(ref.filePath)) {
|
|
2695
|
+
if (event.type === "session_start") {
|
|
2696
|
+
return {
|
|
2697
|
+
id: ref.id,
|
|
2698
|
+
title: "(empty session)",
|
|
2699
|
+
startedAt: event.ts,
|
|
2700
|
+
model: event.model ?? "unknown",
|
|
2701
|
+
provider: event.provider ?? "unknown",
|
|
2702
|
+
tokenTotal: 0
|
|
2703
|
+
};
|
|
2704
|
+
}
|
|
2705
|
+
}
|
|
2706
|
+
return {
|
|
2707
|
+
id: ref.id,
|
|
2708
|
+
title: "(empty session)",
|
|
2709
|
+
startedAt: (/* @__PURE__ */ new Date(0)).toISOString(),
|
|
2710
|
+
model: "unknown",
|
|
2711
|
+
provider: "unknown",
|
|
2712
|
+
tokenTotal: 0
|
|
2713
|
+
};
|
|
2714
|
+
} catch {
|
|
2715
|
+
return {
|
|
2716
|
+
id: ref.id,
|
|
2717
|
+
title: "(damaged)",
|
|
2718
|
+
startedAt: mtime,
|
|
2719
|
+
model: "unknown",
|
|
2720
|
+
provider: "unknown",
|
|
2721
|
+
tokenTotal: 0
|
|
2722
|
+
};
|
|
2515
2723
|
}
|
|
2516
2724
|
}
|
|
2517
2725
|
/**
|
|
@@ -2636,39 +2844,62 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
2636
2844
|
}
|
|
2637
2845
|
async summarize(id, mtime) {
|
|
2638
2846
|
try {
|
|
2639
|
-
const
|
|
2640
|
-
|
|
2641
|
-
|
|
2847
|
+
const file = this.sessionPath(id, ".jsonl");
|
|
2848
|
+
let title = "(empty session)";
|
|
2849
|
+
let startedAt = (/* @__PURE__ */ new Date(0)).toISOString();
|
|
2850
|
+
let endedAt;
|
|
2851
|
+
let model = "unknown";
|
|
2852
|
+
let provider = "unknown";
|
|
2853
|
+
let tokenIn = 0;
|
|
2854
|
+
let tokenOut = 0;
|
|
2642
2855
|
let iterationCount = 0;
|
|
2643
2856
|
let toolCallCount = 0;
|
|
2644
2857
|
let toolErrorCount = 0;
|
|
2645
2858
|
let fileChangeCount = 0;
|
|
2646
2859
|
const toolBreakdown = {};
|
|
2647
2860
|
let outcome;
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2861
|
+
let lastEventType;
|
|
2862
|
+
let hasError = false;
|
|
2863
|
+
let sawStart = false;
|
|
2864
|
+
for await (const e of this.iterSessionEvents(file)) {
|
|
2865
|
+
lastEventType = e.type;
|
|
2866
|
+
if (e.type === "session_start") {
|
|
2867
|
+
if (!sawStart) {
|
|
2868
|
+
sawStart = true;
|
|
2869
|
+
startedAt = e.ts;
|
|
2870
|
+
model = e.model ?? "unknown";
|
|
2871
|
+
provider = e.provider ?? "unknown";
|
|
2872
|
+
}
|
|
2873
|
+
} else if (e.type === "session_end") {
|
|
2874
|
+
endedAt = e.ts;
|
|
2875
|
+
} else if (e.type === "user_input") {
|
|
2876
|
+
if (title === "(empty session)") title = userInputTitle(e.content);
|
|
2877
|
+
} else if (e.type === "llm_response") {
|
|
2878
|
+
tokenIn += e.usage.input ?? 0;
|
|
2879
|
+
tokenOut += e.usage.output ?? 0;
|
|
2880
|
+
} else if (e.type === "in_flight_start") iterationCount++;
|
|
2651
2881
|
else if (e.type === "tool_call_start") {
|
|
2652
2882
|
toolCallCount++;
|
|
2653
2883
|
toolBreakdown[e.name] = (toolBreakdown[e.name] ?? 0) + 1;
|
|
2654
2884
|
} else if (e.type === "tool_result" && e.isError) toolErrorCount++;
|
|
2655
2885
|
else if (e.type === "file_snapshot") fileChangeCount += e.files.length;
|
|
2886
|
+
else if (e.type === "error" || e.type === "provider_error") hasError = true;
|
|
2656
2887
|
}
|
|
2657
|
-
if (
|
|
2888
|
+
if (lastEventType === "session_end") {
|
|
2658
2889
|
outcome = "completed";
|
|
2659
|
-
} else if (
|
|
2890
|
+
} else if (lastEventType === "in_flight_start") {
|
|
2660
2891
|
outcome = "aborted";
|
|
2661
|
-
} else if (
|
|
2892
|
+
} else if (hasError) {
|
|
2662
2893
|
outcome = "error";
|
|
2663
2894
|
}
|
|
2664
2895
|
return {
|
|
2665
2896
|
id,
|
|
2666
2897
|
title,
|
|
2667
|
-
startedAt
|
|
2668
|
-
endedAt
|
|
2669
|
-
model
|
|
2670
|
-
provider
|
|
2671
|
-
tokenTotal:
|
|
2898
|
+
startedAt,
|
|
2899
|
+
endedAt,
|
|
2900
|
+
model,
|
|
2901
|
+
provider,
|
|
2902
|
+
tokenTotal: tokenIn + tokenOut,
|
|
2672
2903
|
iterationCount: iterationCount > 0 ? iterationCount : void 0,
|
|
2673
2904
|
toolCallCount: toolCallCount > 0 ? toolCallCount : void 0,
|
|
2674
2905
|
toolErrorCount: toolErrorCount > 0 ? toolErrorCount : void 0,
|
|
@@ -2687,75 +2918,24 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
2687
2918
|
};
|
|
2688
2919
|
}
|
|
2689
2920
|
}
|
|
2690
|
-
|
|
2691
|
-
const
|
|
2692
|
-
const
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
replay(events, sessionId = "unknown") {
|
|
2703
|
-
const messages = [];
|
|
2704
|
-
let usage = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
|
|
2705
|
-
const openToolUses = /* @__PURE__ */ new Set();
|
|
2706
|
-
for (const e of events) {
|
|
2707
|
-
if (e.type === "user_input") {
|
|
2708
|
-
openToolUses.clear();
|
|
2709
|
-
messages.push({ role: "user", content: e.content, ts: e.ts });
|
|
2710
|
-
} else if (e.type === "llm_response") {
|
|
2711
|
-
messages.push({ role: "assistant", content: e.content, ts: e.ts });
|
|
2712
|
-
for (const b of e.content) {
|
|
2713
|
-
if (b.type === "tool_use") openToolUses.add(b.id);
|
|
2714
|
-
}
|
|
2715
|
-
usage = {
|
|
2716
|
-
input: usage.input + (e.usage.input ?? 0),
|
|
2717
|
-
output: usage.output + (e.usage.output ?? 0),
|
|
2718
|
-
cacheRead: (usage.cacheRead ?? 0) + (e.usage.cacheRead ?? 0),
|
|
2719
|
-
cacheWrite: (usage.cacheWrite ?? 0) + (e.usage.cacheWrite ?? 0)
|
|
2720
|
-
};
|
|
2721
|
-
} else if (e.type === "tool_result") {
|
|
2722
|
-
if (!openToolUses.has(e.id)) {
|
|
2723
|
-
this.events?.emit("session.damaged", {
|
|
2724
|
-
sessionId,
|
|
2725
|
-
detail: `Orphan tool_result "${e.id}" has no matching tool_use`
|
|
2726
|
-
});
|
|
2727
|
-
continue;
|
|
2728
|
-
}
|
|
2729
|
-
openToolUses.delete(e.id);
|
|
2730
|
-
const resultBlock = {
|
|
2731
|
-
type: "tool_result",
|
|
2732
|
-
tool_use_id: e.id,
|
|
2733
|
-
content: typeof e.content === "string" ? e.content : JSON.stringify(e.content),
|
|
2734
|
-
is_error: e.isError
|
|
2735
|
-
};
|
|
2736
|
-
const last = messages[messages.length - 1];
|
|
2737
|
-
const lastIsToolResultUser = last?.role === "user" && Array.isArray(last.content) && last.content.every((b) => b.type === "tool_result");
|
|
2738
|
-
if (lastIsToolResultUser && Array.isArray(last.content)) {
|
|
2739
|
-
last.content.push(resultBlock);
|
|
2740
|
-
} else {
|
|
2741
|
-
messages.push({ role: "user", content: [resultBlock], ts: e.ts });
|
|
2921
|
+
async *iterSessionEvents(file) {
|
|
2922
|
+
const stream = createReadStream(file, { encoding: "utf8" });
|
|
2923
|
+
const lines = createInterface({ input: stream, crlfDelay: Infinity });
|
|
2924
|
+
try {
|
|
2925
|
+
for await (const line of lines) {
|
|
2926
|
+
if (!line.trim()) continue;
|
|
2927
|
+
try {
|
|
2928
|
+
const parsed = JSON.parse(line);
|
|
2929
|
+
if (parsed !== null && typeof parsed === "object" && typeof parsed.type === "string" && typeof parsed.ts === "string") {
|
|
2930
|
+
yield parsed;
|
|
2931
|
+
}
|
|
2932
|
+
} catch {
|
|
2742
2933
|
}
|
|
2743
2934
|
}
|
|
2935
|
+
} finally {
|
|
2936
|
+
lines.close();
|
|
2937
|
+
stream.destroy();
|
|
2744
2938
|
}
|
|
2745
|
-
if (openToolUses.size > 0) {
|
|
2746
|
-
this.events?.emit("session.damaged", {
|
|
2747
|
-
sessionId,
|
|
2748
|
-
detail: `${openToolUses.size} tool_use blocks without matching results - replay repaired`
|
|
2749
|
-
});
|
|
2750
|
-
}
|
|
2751
|
-
const repaired = repairToolUseAdjacency(messages);
|
|
2752
|
-
if (repaired.report.changed) {
|
|
2753
|
-
this.events?.emit("session.damaged", {
|
|
2754
|
-
sessionId,
|
|
2755
|
-
detail: `Repaired replay adjacency: removed ${repaired.report.removedToolUses.length} tool_use, ${repaired.report.removedToolResults.length} tool_result, ${repaired.report.removedMessages} empty messages`
|
|
2756
|
-
});
|
|
2757
|
-
}
|
|
2758
|
-
return { messages: repaired.messages, usage };
|
|
2759
2939
|
}
|
|
2760
2940
|
};
|
|
2761
2941
|
function extractToolCallEnds(events) {
|
|
@@ -2815,7 +2995,7 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
2815
2995
|
/**
|
|
2816
2996
|
* Lazy session_start/session_resumed init, shared by all appenders.
|
|
2817
2997
|
* A single promise (not a boolean) so a second append racing the first
|
|
2818
|
-
* can't push its event into the buffer BEFORE the first append's event
|
|
2998
|
+
* can't push its event into the buffer BEFORE the first append's event —
|
|
2819
2999
|
* every appender awaits the same init and resumes in FIFO call order.
|
|
2820
3000
|
*/
|
|
2821
3001
|
initPromise = null;
|
|
@@ -2828,24 +3008,24 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
2828
3008
|
lastAppendWarnAt = 0;
|
|
2829
3009
|
secretScrubber;
|
|
2830
3010
|
onCloseCb;
|
|
2831
|
-
/** Implements SessionWriter.traceId
|
|
3011
|
+
/** Implements SessionWriter.traceId — propagated from ContextInit.traceId. */
|
|
2832
3012
|
traceId;
|
|
2833
|
-
//
|
|
3013
|
+
// ── Write buffer — batches events to reduce per-event disk I/O ─────────
|
|
2834
3014
|
//
|
|
2835
3015
|
// Every append() pushes the scrubbed event into an in-memory buffer instead
|
|
2836
3016
|
// of calling handle.appendFile() synchronously. The buffer flushes to disk
|
|
2837
3017
|
// when it reaches FLUSH_SIZE events OR after FLUSH_INTERVAL_MS of inactivity.
|
|
2838
3018
|
// This cuts the number of disk writes by ~95% without changing the on-disk
|
|
2839
|
-
// format
|
|
3019
|
+
// format — the JSONL is still one JSON object per line.
|
|
2840
3020
|
writeBuffer = [];
|
|
2841
3021
|
flushTimer = null;
|
|
2842
3022
|
static FLUSH_INTERVAL_MS = 500;
|
|
2843
3023
|
static FLUSH_SIZE = 50;
|
|
2844
|
-
//
|
|
3024
|
+
// ── Write serialization ─────────────────────────────────────────────────
|
|
2845
3025
|
//
|
|
2846
3026
|
// All disk writes are funneled through a FIFO promise chain. Without it,
|
|
2847
3027
|
// a timer-driven flush racing an explicit flush()/close() issues two
|
|
2848
|
-
// concurrent appendFile() calls on the shared O_APPEND handle
|
|
3028
|
+
// concurrent appendFile() calls on the shared O_APPEND handle — the kernel
|
|
2849
3029
|
// may complete them out of order (chronology breaks) or, for large
|
|
2850
3030
|
// batches, interleave partial writes (torn JSONL lines). The chain keeps
|
|
2851
3031
|
// exactly one write in flight; failures don't break the chain.
|
|
@@ -2859,7 +3039,7 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
2859
3039
|
);
|
|
2860
3040
|
return write;
|
|
2861
3041
|
}
|
|
2862
|
-
//
|
|
3042
|
+
// ── Enriched summary tracking ──────────────────────────────────────────
|
|
2863
3043
|
iterationCount = 0;
|
|
2864
3044
|
toolCallCount = 0;
|
|
2865
3045
|
toolErrorCount = 0;
|
|
@@ -2951,7 +3131,7 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
2951
3131
|
* (user_input, llm_response) call this so they survive SIGKILL/crash
|
|
2952
3132
|
* instead of sitting in the in-memory buffer for up to 500ms.
|
|
2953
3133
|
*
|
|
2954
|
-
* Idempotent
|
|
3134
|
+
* Idempotent — cancels any pending timer and writes whatever has
|
|
2955
3135
|
* accumulated in the buffer. Safe to call even when the buffer
|
|
2956
3136
|
* is empty (no-op).
|
|
2957
3137
|
*/
|
|
@@ -2974,7 +3154,7 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
2974
3154
|
/**
|
|
2975
3155
|
* Flush all buffered events to disk as a single appendFile call.
|
|
2976
3156
|
* Errors use the same throttled-warning pattern the old per-event
|
|
2977
|
-
* append path used
|
|
3157
|
+
* append path used — one warning every 5s with a suppressed count.
|
|
2978
3158
|
* On failure the buffer is cleared (events are best-effort, same as
|
|
2979
3159
|
* the old per-event path where a failed write was silently dropped).
|
|
2980
3160
|
*/
|
|
@@ -3157,7 +3337,7 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
3157
3337
|
/**
|
|
3158
3338
|
* Truncate the session file to the checkpoint with the given promptIndex,
|
|
3159
3339
|
* removing all events that follow it. Uses a single-pass byte-offset scan
|
|
3160
|
-
* so post-checkpoint content is never read or parsed
|
|
3340
|
+
* so post-checkpoint content is never read or parsed — O(1) memory instead
|
|
3161
3341
|
* of O(N) JSON.parse calls over the full file.
|
|
3162
3342
|
*/
|
|
3163
3343
|
async truncateToCheckpoint(targetPromptIndex) {
|
|
@@ -3314,7 +3494,7 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
3314
3494
|
await fsp2.writeFile(this.filePath, record, "utf8");
|
|
3315
3495
|
}
|
|
3316
3496
|
/**
|
|
3317
|
-
* Idea #1
|
|
3497
|
+
* Idea #1 — write an in-flight marker. The agent loop should call
|
|
3318
3498
|
* this at the start of each long-running operation; a matching
|
|
3319
3499
|
* `clearInFlightMarker` follows on clean exit. A stale marker
|
|
3320
3500
|
* (no end) is what `SessionRecovery.detectStale` looks for.
|
|
@@ -3331,9 +3511,9 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
3331
3511
|
this.events?.emit("in_flight.started", { context, ts: (/* @__PURE__ */ new Date()).toISOString() });
|
|
3332
3512
|
}
|
|
3333
3513
|
/**
|
|
3334
|
-
* Idea #1
|
|
3514
|
+
* Idea #1 — close the in-flight marker. Idempotent in spirit
|
|
3335
3515
|
* (you can call it after a successful iteration even if you
|
|
3336
|
-
* didn't open one this round)
|
|
3516
|
+
* didn't open one this round) — but the session log records
|
|
3337
3517
|
* every call so postmortem tooling can see "the agent finished
|
|
3338
3518
|
* cleanly X times, then died without finishing Y".
|
|
3339
3519
|
*/
|
|
@@ -3350,6 +3530,27 @@ function userInputTitle(content) {
|
|
|
3350
3530
|
const text = typeof content === "string" ? content : content.filter((b) => b.type === "text").map((b) => b.text).join(" ");
|
|
3351
3531
|
return (text || "(non-text input)").slice(0, 60);
|
|
3352
3532
|
}
|
|
3533
|
+
function compareSessionSummaries(a, b) {
|
|
3534
|
+
if (a.startedAt < b.startedAt) return 1;
|
|
3535
|
+
if (a.startedAt > b.startedAt) return -1;
|
|
3536
|
+
return a.id.localeCompare(b.id);
|
|
3537
|
+
}
|
|
3538
|
+
async function mapWithConcurrency(items, concurrency, fn) {
|
|
3539
|
+
if (items.length === 0) return [];
|
|
3540
|
+
const out = new Array(items.length);
|
|
3541
|
+
let next = 0;
|
|
3542
|
+
const workerCount = Math.min(Math.max(1, concurrency), items.length);
|
|
3543
|
+
const workers = Array.from({ length: workerCount }, async () => {
|
|
3544
|
+
for (; ; ) {
|
|
3545
|
+
const idx = next++;
|
|
3546
|
+
if (idx >= items.length) return;
|
|
3547
|
+
const item = items[idx];
|
|
3548
|
+
if (item !== void 0) out[idx] = await fn(item);
|
|
3549
|
+
}
|
|
3550
|
+
});
|
|
3551
|
+
await Promise.all(workers);
|
|
3552
|
+
return out;
|
|
3553
|
+
}
|
|
3353
3554
|
|
|
3354
3555
|
// src/storage/queue-store.ts
|
|
3355
3556
|
init_atomic_write();
|
|
@@ -3937,6 +4138,20 @@ var DefaultMemoryStore = class {
|
|
|
3937
4138
|
*/
|
|
3938
4139
|
persistBackup;
|
|
3939
4140
|
backupDir;
|
|
4141
|
+
/**
|
|
4142
|
+
* Per-scope tracked byte sizes — incremented on `remember()`, decremented on
|
|
4143
|
+
* `forget()`, recalculated after `consolidate()`. Eliminates the redundant
|
|
4144
|
+
* readAll() call that previously checked the file size after every write.
|
|
4145
|
+
*/
|
|
4146
|
+
_trackedByteSizes = {};
|
|
4147
|
+
/** Result cache for scoreRelevant() — keyed by scope + context hash, TTL 30s. */
|
|
4148
|
+
_scoreCache = /* @__PURE__ */ new Map();
|
|
4149
|
+
/**
|
|
4150
|
+
* Per-entry cached lowercase strings — computed once per scoreRelevant() call,
|
|
4151
|
+
* stored here so repeated scoring of the same entries avoids re-computation.
|
|
4152
|
+
* Cleared on every mutation (remember/forget/consolidate/clear).
|
|
4153
|
+
*/
|
|
4154
|
+
_cachedLower = null;
|
|
3940
4155
|
constructor(opts) {
|
|
3941
4156
|
this.files = {
|
|
3942
4157
|
"project-agents": opts.paths.inProjectAgentsFile,
|
|
@@ -3970,6 +4185,20 @@ var DefaultMemoryStore = class {
|
|
|
3970
4185
|
}
|
|
3971
4186
|
}
|
|
3972
4187
|
}
|
|
4188
|
+
/**
|
|
4189
|
+
* Recalculate the tracked byte size for a scope by re-reading the file and
|
|
4190
|
+
* summing the serialized byte length of each line. Called after consolidate()
|
|
4191
|
+
* (which modifies the file) to keep the tracker accurate.
|
|
4192
|
+
*/
|
|
4193
|
+
async _recalcTrackedByteSize(scope) {
|
|
4194
|
+
const raw = await this.backend.readAll(scope, this.files[scope]);
|
|
4195
|
+
let total = 0;
|
|
4196
|
+
for (const line of raw.split("\n")) {
|
|
4197
|
+
if (line.trim()) total += Buffer.byteLength(line, "utf8");
|
|
4198
|
+
}
|
|
4199
|
+
this._trackedByteSizes[scope] = total;
|
|
4200
|
+
return total;
|
|
4201
|
+
}
|
|
3973
4202
|
async readAll() {
|
|
3974
4203
|
const parts = [];
|
|
3975
4204
|
for (const scope of ["project-agents", "project-memory", "user-memory"]) {
|
|
@@ -4070,6 +4299,7 @@ ${body.trim()}`);
|
|
|
4070
4299
|
const t0 = Date.now();
|
|
4071
4300
|
try {
|
|
4072
4301
|
await this.backend.remember(scope, entry, filePath);
|
|
4302
|
+
this._scoreCache.clear();
|
|
4073
4303
|
const dur = Date.now() - t0;
|
|
4074
4304
|
this.events?.emit("storage.write", {
|
|
4075
4305
|
sessionId: "~memory~",
|
|
@@ -4094,16 +4324,21 @@ ${body.trim()}`);
|
|
|
4094
4324
|
});
|
|
4095
4325
|
throw err;
|
|
4096
4326
|
}
|
|
4097
|
-
|
|
4098
|
-
if (
|
|
4327
|
+
let trackedSize = this._trackedByteSizes[scope];
|
|
4328
|
+
if (trackedSize === void 0) {
|
|
4329
|
+
trackedSize = await this._recalcTrackedByteSize(scope);
|
|
4330
|
+
}
|
|
4331
|
+
if (trackedSize > MAX_BYTES_TOTAL) {
|
|
4099
4332
|
const removed = await this.backend.consolidate(scope, this.files[scope]);
|
|
4100
4333
|
if (removed > 0) {
|
|
4101
4334
|
this.events?.emit("memory.consolidated", {
|
|
4102
4335
|
scope,
|
|
4103
4336
|
removed
|
|
4104
4337
|
});
|
|
4338
|
+
await this._recalcTrackedByteSize(scope);
|
|
4105
4339
|
}
|
|
4106
4340
|
}
|
|
4341
|
+
this._trackedByteSizes[scope] = (this._trackedByteSizes[scope] ?? 0) + Buffer.byteLength(JSON.stringify(entry), "utf8");
|
|
4107
4342
|
await this.mirrorBackup(scope);
|
|
4108
4343
|
this.events?.emit("memory.remembered", {
|
|
4109
4344
|
scope,
|
|
@@ -4122,16 +4357,30 @@ ${body.trim()}`);
|
|
|
4122
4357
|
async scoreRelevant(ctx, scope = "project-memory", limit = 8) {
|
|
4123
4358
|
const all = await this.list(scope);
|
|
4124
4359
|
if (all.length === 0) return [];
|
|
4360
|
+
const ctxHash = `${scope}|${ctx.currentTask}|${(ctx.activeSkills ?? []).join(",")}|${(ctx.toolNames ?? []).join(",")}`;
|
|
4361
|
+
const now = Date.now();
|
|
4362
|
+
const TTL_MS = 3e4;
|
|
4363
|
+
const cached = this._scoreCache.get(ctxHash);
|
|
4364
|
+
if (cached && cached.expiresAt > now && cached.entries === all) {
|
|
4365
|
+
return cached.scored.slice(0, Math.min(limit, 15));
|
|
4366
|
+
}
|
|
4125
4367
|
const taskWords = ctx.currentTask.toLowerCase().split(/\s+/).filter((w) => w.length > 2);
|
|
4126
4368
|
const skillWords = (ctx.activeSkills ?? []).flatMap((s) => s.split("-"));
|
|
4127
4369
|
const toolWords = (ctx.toolNames ?? []).flatMap((t) => t.toLowerCase().split("_"));
|
|
4128
|
-
|
|
4370
|
+
this._cachedLower = /* @__PURE__ */ new WeakMap();
|
|
4129
4371
|
const scored = [];
|
|
4130
4372
|
for (const entry of all) {
|
|
4131
4373
|
let score = 0;
|
|
4132
4374
|
const reasons = [];
|
|
4133
|
-
|
|
4134
|
-
|
|
4375
|
+
let cachedLower = this._cachedLower.get(entry);
|
|
4376
|
+
if (!cachedLower) {
|
|
4377
|
+
cachedLower = {
|
|
4378
|
+
textLower: entry.text.toLowerCase(),
|
|
4379
|
+
tagsLower: (entry.tags ?? []).map((t) => t.toLowerCase())
|
|
4380
|
+
};
|
|
4381
|
+
this._cachedLower.set(entry, cachedLower);
|
|
4382
|
+
}
|
|
4383
|
+
const { textLower, tagsLower } = cachedLower;
|
|
4135
4384
|
let taskHits = 0;
|
|
4136
4385
|
for (const w of taskWords) {
|
|
4137
4386
|
if (textLower.includes(w)) {
|
|
@@ -4217,6 +4466,7 @@ ${body.trim()}`);
|
|
|
4217
4466
|
const relevant = scored.filter(
|
|
4218
4467
|
(s) => s.score >= threshold || s.priority === "critical" || s.priority === "high"
|
|
4219
4468
|
);
|
|
4469
|
+
this._scoreCache.set(ctxHash, { entries: all, scored: relevant, expiresAt: now + TTL_MS });
|
|
4220
4470
|
return relevant.slice(0, Math.min(limit, 15));
|
|
4221
4471
|
}
|
|
4222
4472
|
async forget(query, scope = "project-memory") {
|
|
@@ -4226,6 +4476,7 @@ ${body.trim()}`);
|
|
|
4226
4476
|
let removed = 0;
|
|
4227
4477
|
try {
|
|
4228
4478
|
removed = await this.backend.forget(scope, query, filePath);
|
|
4479
|
+
this._scoreCache.clear();
|
|
4229
4480
|
const dur = Date.now() - t0;
|
|
4230
4481
|
this.events?.emit("storage.write", {
|
|
4231
4482
|
sessionId: "~memory~",
|
|
@@ -4257,6 +4508,7 @@ ${body.trim()}`);
|
|
|
4257
4508
|
removed
|
|
4258
4509
|
});
|
|
4259
4510
|
await this.mirrorBackup(scope);
|
|
4511
|
+
await this._recalcTrackedByteSize(scope);
|
|
4260
4512
|
}
|
|
4261
4513
|
return removed;
|
|
4262
4514
|
});
|
|
@@ -4268,6 +4520,7 @@ ${body.trim()}`);
|
|
|
4268
4520
|
let removed = 0;
|
|
4269
4521
|
try {
|
|
4270
4522
|
removed = await this.backend.consolidate(scope, filePath);
|
|
4523
|
+
this._scoreCache.clear();
|
|
4271
4524
|
const dur = Date.now() - t0;
|
|
4272
4525
|
this.events?.emit("storage.write", {
|
|
4273
4526
|
sessionId: "~memory~",
|
|
@@ -4298,6 +4551,7 @@ ${body.trim()}`);
|
|
|
4298
4551
|
removed
|
|
4299
4552
|
});
|
|
4300
4553
|
await this.mirrorBackup(scope);
|
|
4554
|
+
await this._recalcTrackedByteSize(scope);
|
|
4301
4555
|
}
|
|
4302
4556
|
});
|
|
4303
4557
|
}
|
|
@@ -4308,6 +4562,7 @@ ${body.trim()}`);
|
|
|
4308
4562
|
const t0 = Date.now();
|
|
4309
4563
|
try {
|
|
4310
4564
|
await this.backend.clear(scope, filePath);
|
|
4565
|
+
this._scoreCache.clear();
|
|
4311
4566
|
const dur = Date.now() - t0;
|
|
4312
4567
|
this.events?.emit("storage.write", {
|
|
4313
4568
|
sessionId: "~memory~",
|
|
@@ -4334,6 +4589,7 @@ ${body.trim()}`);
|
|
|
4334
4589
|
}
|
|
4335
4590
|
this.events?.emit("memory.cleared", { scope });
|
|
4336
4591
|
await this.mirrorBackup(scope);
|
|
4592
|
+
this._trackedByteSizes[scope] = 0;
|
|
4337
4593
|
});
|
|
4338
4594
|
return;
|
|
4339
4595
|
}
|
|
@@ -4647,6 +4903,83 @@ var ALGO = "aes-256-gcm";
|
|
|
4647
4903
|
var KEY_FILE_MODE = 384;
|
|
4648
4904
|
var KEY_FILE_MAGIC = Buffer.from("WSKV", "ascii");
|
|
4649
4905
|
var VERSIONED_KEY_FILE_SIZE = KEY_FILE_MAGIC.length + 1 + KEY_BYTES;
|
|
4906
|
+
var KEK_MAGIC = Buffer.from("WSKW", "ascii");
|
|
4907
|
+
var KEK_SALT_BYTES = 16;
|
|
4908
|
+
var WRAPPED_KEY_FILE_SIZE = KEK_MAGIC.length + 1 + KEK_SALT_BYTES + IV_BYTES + TAG_BYTES + KEY_BYTES;
|
|
4909
|
+
var SCRYPT_N = 1 << 15;
|
|
4910
|
+
var SCRYPT_R = 8;
|
|
4911
|
+
var SCRYPT_P = 1;
|
|
4912
|
+
var SCRYPT_MAXMEM = 64 * 1024 * 1024;
|
|
4913
|
+
function getVaultPassphrase() {
|
|
4914
|
+
const v = process.env["WRONGSTACK_VAULT_PASSPHRASE"];
|
|
4915
|
+
return v && v.length > 0 ? v : void 0;
|
|
4916
|
+
}
|
|
4917
|
+
function isWrappedKeyFile(buf) {
|
|
4918
|
+
return buf.length === WRAPPED_KEY_FILE_SIZE && buf.subarray(0, KEK_MAGIC.length).equals(KEK_MAGIC);
|
|
4919
|
+
}
|
|
4920
|
+
function deriveKEK(passphrase, salt) {
|
|
4921
|
+
return scryptSync(passphrase, salt, KEY_BYTES, {
|
|
4922
|
+
N: SCRYPT_N,
|
|
4923
|
+
r: SCRYPT_R,
|
|
4924
|
+
p: SCRYPT_P,
|
|
4925
|
+
maxmem: SCRYPT_MAXMEM
|
|
4926
|
+
});
|
|
4927
|
+
}
|
|
4928
|
+
function wrapDataKey(dataKey, keyVersion, passphrase) {
|
|
4929
|
+
const salt = randomBytes(KEK_SALT_BYTES);
|
|
4930
|
+
const iv = randomBytes(IV_BYTES);
|
|
4931
|
+
const kek = deriveKEK(passphrase, salt);
|
|
4932
|
+
const cipher = createCipheriv(ALGO, kek, iv);
|
|
4933
|
+
const ct = Buffer.concat([cipher.update(dataKey), cipher.final()]);
|
|
4934
|
+
const tag = cipher.getAuthTag();
|
|
4935
|
+
const out = Buffer.alloc(WRAPPED_KEY_FILE_SIZE);
|
|
4936
|
+
let off = 0;
|
|
4937
|
+
KEK_MAGIC.copy(out, off);
|
|
4938
|
+
off += KEK_MAGIC.length;
|
|
4939
|
+
out[off] = keyVersion & 255;
|
|
4940
|
+
off += 1;
|
|
4941
|
+
salt.copy(out, off);
|
|
4942
|
+
off += KEK_SALT_BYTES;
|
|
4943
|
+
iv.copy(out, off);
|
|
4944
|
+
off += IV_BYTES;
|
|
4945
|
+
tag.copy(out, off);
|
|
4946
|
+
off += TAG_BYTES;
|
|
4947
|
+
ct.copy(out, off);
|
|
4948
|
+
return out;
|
|
4949
|
+
}
|
|
4950
|
+
function unwrapDataKey(buf, keyFile) {
|
|
4951
|
+
const passphrase = getVaultPassphrase();
|
|
4952
|
+
if (!passphrase) {
|
|
4953
|
+
throw new ConfigError({
|
|
4954
|
+
message: `SecretVault: key file ${keyFile} is passphrase-protected \u2014 set the WRONGSTACK_VAULT_PASSPHRASE environment variable to unlock it.`,
|
|
4955
|
+
code: ERROR_CODES.CONFIG_INVALID,
|
|
4956
|
+
context: { keyFile }
|
|
4957
|
+
});
|
|
4958
|
+
}
|
|
4959
|
+
let off = KEK_MAGIC.length;
|
|
4960
|
+
const version = buf[off];
|
|
4961
|
+
off += 1;
|
|
4962
|
+
const salt = buf.subarray(off, off + KEK_SALT_BYTES);
|
|
4963
|
+
off += KEK_SALT_BYTES;
|
|
4964
|
+
const iv = buf.subarray(off, off + IV_BYTES);
|
|
4965
|
+
off += IV_BYTES;
|
|
4966
|
+
const tag = buf.subarray(off, off + TAG_BYTES);
|
|
4967
|
+
off += TAG_BYTES;
|
|
4968
|
+
const ct = buf.subarray(off, off + KEY_BYTES);
|
|
4969
|
+
const kek = deriveKEK(passphrase, salt);
|
|
4970
|
+
const decipher = createDecipheriv(ALGO, kek, iv);
|
|
4971
|
+
decipher.setAuthTag(tag);
|
|
4972
|
+
try {
|
|
4973
|
+
const key = Buffer.concat([decipher.update(ct), decipher.final()]);
|
|
4974
|
+
return { key: Buffer.from(key), version };
|
|
4975
|
+
} catch {
|
|
4976
|
+
throw new ConfigError({
|
|
4977
|
+
message: `SecretVault: failed to unlock key file ${keyFile} \u2014 wrong WRONGSTACK_VAULT_PASSPHRASE (key unwrap authentication failed).`,
|
|
4978
|
+
code: ERROR_CODES.CONFIG_INVALID,
|
|
4979
|
+
context: { keyFile }
|
|
4980
|
+
});
|
|
4981
|
+
}
|
|
4982
|
+
}
|
|
4650
4983
|
function checkKeyFilePermissions(keyFile) {
|
|
4651
4984
|
if (process.platform === "win32") return;
|
|
4652
4985
|
try {
|
|
@@ -4739,25 +5072,56 @@ var DefaultSecretVault = class {
|
|
|
4739
5072
|
const oldVersion = this._keyVersion;
|
|
4740
5073
|
const newKey = randomBytes(KEY_BYTES);
|
|
4741
5074
|
const newVersion = oldVersion + 1;
|
|
4742
|
-
const keyFileBuf = Buffer.alloc(VERSIONED_KEY_FILE_SIZE);
|
|
4743
|
-
KEY_FILE_MAGIC.copy(keyFileBuf, 0);
|
|
4744
|
-
keyFileBuf[KEY_FILE_MAGIC.length] = newVersion;
|
|
4745
|
-
newKey.copy(keyFileBuf, KEY_FILE_MAGIC.length + 1);
|
|
4746
5075
|
fs4.mkdirSync(path4.dirname(this.keyFile), { recursive: true });
|
|
4747
|
-
|
|
5076
|
+
const passphrase = getVaultPassphrase();
|
|
5077
|
+
if (passphrase) {
|
|
5078
|
+
fs4.writeFileSync(this.keyFile, wrapDataKey(newKey, newVersion, passphrase), { mode: 384 });
|
|
5079
|
+
} else {
|
|
5080
|
+
const keyFileBuf = Buffer.alloc(VERSIONED_KEY_FILE_SIZE);
|
|
5081
|
+
KEY_FILE_MAGIC.copy(keyFileBuf, 0);
|
|
5082
|
+
keyFileBuf[KEY_FILE_MAGIC.length] = newVersion;
|
|
5083
|
+
newKey.copy(keyFileBuf, KEY_FILE_MAGIC.length + 1);
|
|
5084
|
+
fs4.writeFileSync(this.keyFile, keyFileBuf, { mode: 384 });
|
|
5085
|
+
}
|
|
4748
5086
|
checkKeyFilePermissions(this.keyFile);
|
|
4749
5087
|
this.key = newKey;
|
|
4750
5088
|
this._keyVersion = newVersion;
|
|
4751
5089
|
return { oldVersion, newVersion };
|
|
4752
5090
|
}
|
|
5091
|
+
/**
|
|
5092
|
+
* If WRONGSTACK_VAULT_PASSPHRASE is set but the key on disk is still stored
|
|
5093
|
+
* unwrapped (legacy v1 / versioned v2), re-write it in passphrase-wrapped (v3)
|
|
5094
|
+
* form. The data key is preserved, so all existing ciphertext keeps
|
|
5095
|
+
* decrypting. Best-effort: a write failure leaves the working unwrapped file
|
|
5096
|
+
* in place and is not fatal to load.
|
|
5097
|
+
*/
|
|
5098
|
+
migrateToWrappedIfPassphrase() {
|
|
5099
|
+
const passphrase = getVaultPassphrase();
|
|
5100
|
+
if (!passphrase || !this.key) return;
|
|
5101
|
+
try {
|
|
5102
|
+
fs4.writeFileSync(this.keyFile, wrapDataKey(this.key, this._keyVersion, passphrase), {
|
|
5103
|
+
mode: 384
|
|
5104
|
+
});
|
|
5105
|
+
checkKeyFilePermissions(this.keyFile);
|
|
5106
|
+
} catch {
|
|
5107
|
+
}
|
|
5108
|
+
}
|
|
4753
5109
|
loadOrCreateKey() {
|
|
4754
5110
|
if (this.key) return this.key;
|
|
4755
5111
|
try {
|
|
4756
5112
|
const buf = fs4.readFileSync(this.keyFile);
|
|
5113
|
+
if (isWrappedKeyFile(buf)) {
|
|
5114
|
+
const { key: key2, version } = unwrapDataKey(buf, this.keyFile);
|
|
5115
|
+
this.key = key2;
|
|
5116
|
+
this._keyVersion = version;
|
|
5117
|
+
checkKeyFilePermissions(this.keyFile);
|
|
5118
|
+
return this.key;
|
|
5119
|
+
}
|
|
4757
5120
|
if (buf.length === KEY_BYTES) {
|
|
4758
5121
|
this.key = buf;
|
|
4759
5122
|
this._keyVersion = 1;
|
|
4760
5123
|
checkKeyFilePermissions(this.keyFile);
|
|
5124
|
+
this.migrateToWrappedIfPassphrase();
|
|
4761
5125
|
return this.key;
|
|
4762
5126
|
}
|
|
4763
5127
|
if (buf.length === VERSIONED_KEY_FILE_SIZE) {
|
|
@@ -4781,6 +5145,7 @@ var DefaultSecretVault = class {
|
|
|
4781
5145
|
this.key = Buffer.from(key2);
|
|
4782
5146
|
this._keyVersion = version;
|
|
4783
5147
|
checkKeyFilePermissions(this.keyFile);
|
|
5148
|
+
this.migrateToWrappedIfPassphrase();
|
|
4784
5149
|
return this.key;
|
|
4785
5150
|
}
|
|
4786
5151
|
throw new ConfigError({
|
|
@@ -4793,11 +5158,20 @@ var DefaultSecretVault = class {
|
|
|
4793
5158
|
}
|
|
4794
5159
|
fs4.mkdirSync(path4.dirname(this.keyFile), { recursive: true });
|
|
4795
5160
|
const key = randomBytes(KEY_BYTES);
|
|
5161
|
+
const passphrase = getVaultPassphrase();
|
|
5162
|
+
const initialBytes = passphrase ? wrapDataKey(key, 1, passphrase) : key;
|
|
4796
5163
|
try {
|
|
4797
|
-
fs4.writeFileSync(this.keyFile,
|
|
5164
|
+
fs4.writeFileSync(this.keyFile, initialBytes, { mode: 384, flag: "wx" });
|
|
4798
5165
|
} catch (err) {
|
|
4799
5166
|
if (err.code !== "EEXIST") throw err;
|
|
4800
5167
|
const buf = fs4.readFileSync(this.keyFile);
|
|
5168
|
+
if (isWrappedKeyFile(buf)) {
|
|
5169
|
+
const { key: winnerKey, version } = unwrapDataKey(buf, this.keyFile);
|
|
5170
|
+
this.key = winnerKey;
|
|
5171
|
+
this._keyVersion = version;
|
|
5172
|
+
checkKeyFilePermissions(this.keyFile);
|
|
5173
|
+
return this.key;
|
|
5174
|
+
}
|
|
4801
5175
|
if (buf.length === KEY_BYTES) {
|
|
4802
5176
|
this.key = buf;
|
|
4803
5177
|
this._keyVersion = 1;
|
|
@@ -5141,6 +5515,51 @@ var defaultIndexing = {
|
|
|
5141
5515
|
watchExternal: true,
|
|
5142
5516
|
debounceMs: 400
|
|
5143
5517
|
};
|
|
5518
|
+
var IN_PROJECT_FORBIDDEN_KEYS = /* @__PURE__ */ new Set([
|
|
5519
|
+
"provider",
|
|
5520
|
+
"apiKey",
|
|
5521
|
+
"baseUrl",
|
|
5522
|
+
"providers",
|
|
5523
|
+
"mcpServers",
|
|
5524
|
+
"hooks",
|
|
5525
|
+
"plugins",
|
|
5526
|
+
"sync",
|
|
5527
|
+
"yolo",
|
|
5528
|
+
"extensions"
|
|
5529
|
+
]);
|
|
5530
|
+
function stripUnsafeInProjectFields(inProject, sourcePath, warn = (msg) => console.warn(msg)) {
|
|
5531
|
+
const stripped = [];
|
|
5532
|
+
const out = {};
|
|
5533
|
+
for (const [k, v] of Object.entries(inProject)) {
|
|
5534
|
+
if (IN_PROJECT_FORBIDDEN_KEYS.has(k)) {
|
|
5535
|
+
stripped.push(k);
|
|
5536
|
+
continue;
|
|
5537
|
+
}
|
|
5538
|
+
out[k] = v;
|
|
5539
|
+
}
|
|
5540
|
+
if (stripped.length > 0) {
|
|
5541
|
+
warn(
|
|
5542
|
+
JSON.stringify({
|
|
5543
|
+
level: "warn",
|
|
5544
|
+
event: "config.in_project_unsafe_fields_ignored",
|
|
5545
|
+
path: sourcePath,
|
|
5546
|
+
ignoredKeys: stripped,
|
|
5547
|
+
message: `Ignored ${stripped.length} unsafe field(s) from the repo-committed config "${sourcePath}": ${stripped.join(", ")}. These can only be set in your personal ~/.wrongstack/config.json, not in a project-committed file.`,
|
|
5548
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
5549
|
+
})
|
|
5550
|
+
);
|
|
5551
|
+
}
|
|
5552
|
+
return out;
|
|
5553
|
+
}
|
|
5554
|
+
function samePath(a, b) {
|
|
5555
|
+
let ra = path4.resolve(a);
|
|
5556
|
+
let rb = path4.resolve(b);
|
|
5557
|
+
if (process.platform === "win32" || process.platform === "darwin") {
|
|
5558
|
+
ra = ra.toLowerCase();
|
|
5559
|
+
rb = rb.toLowerCase();
|
|
5560
|
+
}
|
|
5561
|
+
return ra === rb;
|
|
5562
|
+
}
|
|
5144
5563
|
function deepMerge2(base, patch) {
|
|
5145
5564
|
const opts = { arrayMode: "concat-primitives" };
|
|
5146
5565
|
if (envBoolOptional(process.env.WRONGSTACK_DEBUG_CONFIG)) {
|
|
@@ -5169,14 +5588,15 @@ var DefaultConfigLoader = class {
|
|
|
5169
5588
|
}
|
|
5170
5589
|
async load(opts = {}) {
|
|
5171
5590
|
let cfg = { ...BEHAVIOR_DEFAULTS };
|
|
5591
|
+
const inProjectCollides = samePath(this.paths.inProjectConfig, this.paths.globalConfig) || samePath(this.paths.inProjectConfig, this.paths.projectLocalConfig);
|
|
5172
5592
|
const [global, local, inProject] = await Promise.all([
|
|
5173
5593
|
this.readJson(this.paths.globalConfig),
|
|
5174
5594
|
this.readJson(this.paths.projectLocalConfig),
|
|
5175
|
-
this.readJson(this.paths.inProjectConfig)
|
|
5595
|
+
inProjectCollides ? Promise.resolve({}) : this.readJson(this.paths.inProjectConfig)
|
|
5176
5596
|
]);
|
|
5177
5597
|
cfg = deepMerge2(cfg, global);
|
|
5178
5598
|
cfg = deepMerge2(cfg, local);
|
|
5179
|
-
cfg = deepMerge2(cfg, inProject);
|
|
5599
|
+
cfg = deepMerge2(cfg, stripUnsafeInProjectFields(inProject, this.paths.inProjectConfig));
|
|
5180
5600
|
for (const [key, fn] of Object.entries(ENV_MAP)) {
|
|
5181
5601
|
const v = process.env[key];
|
|
5182
5602
|
if (v) fn(cfg, v);
|
|
@@ -7057,6 +7477,9 @@ function isClearlyDestructiveBashCommand(command, projectRoot) {
|
|
|
7057
7477
|
}
|
|
7058
7478
|
|
|
7059
7479
|
// src/security/permission-policy.ts
|
|
7480
|
+
function matchesTrust(patterns, subject) {
|
|
7481
|
+
return patterns.includes(subject) || matchAny(patterns, subject);
|
|
7482
|
+
}
|
|
7060
7483
|
var DefaultPermissionPolicy = class {
|
|
7061
7484
|
policy = {};
|
|
7062
7485
|
loaded = false;
|
|
@@ -7193,7 +7616,7 @@ var DefaultPermissionPolicy = class {
|
|
|
7193
7616
|
this._evalCache.set(cacheKey, decision);
|
|
7194
7617
|
return decision;
|
|
7195
7618
|
}
|
|
7196
|
-
if (entry?.deny && subject &&
|
|
7619
|
+
if (entry?.deny && subject && matchesTrust(entry.deny, subject)) {
|
|
7197
7620
|
const decision = { permission: "deny", source: "deny", reason: "matched deny pattern" };
|
|
7198
7621
|
this._evalCache.set(cacheKey, decision);
|
|
7199
7622
|
return decision;
|
|
@@ -7203,7 +7626,7 @@ var DefaultPermissionPolicy = class {
|
|
|
7203
7626
|
this._evalCache.set(cacheKey, decision);
|
|
7204
7627
|
return decision;
|
|
7205
7628
|
}
|
|
7206
|
-
if (entry?.allow && subject &&
|
|
7629
|
+
if (entry?.allow && subject && matchesTrust(entry.allow, subject)) {
|
|
7207
7630
|
const decision = { permission: "auto", source: "trust", reason: "matched allow pattern" };
|
|
7208
7631
|
this._evalCache.set(cacheKey, decision);
|
|
7209
7632
|
return decision;
|
|
@@ -7927,6 +8350,16 @@ function handleMessageStop(state, ev) {
|
|
|
7927
8350
|
async function streamProviderToResponse(provider, req, signal, ctx, events, logger) {
|
|
7928
8351
|
const state = createStreamingState(req.model);
|
|
7929
8352
|
logger.debug("Stream started", { providerId: provider.id, model: req.model });
|
|
8353
|
+
const TEXT_BATCH_SIZE = 4;
|
|
8354
|
+
let pendingText = "";
|
|
8355
|
+
let pendingCount = 0;
|
|
8356
|
+
const flushText = () => {
|
|
8357
|
+
if (pendingCount > 0) {
|
|
8358
|
+
events.emit("provider.text_delta", { ctx, text: pendingText });
|
|
8359
|
+
pendingText = "";
|
|
8360
|
+
pendingCount = 0;
|
|
8361
|
+
}
|
|
8362
|
+
};
|
|
7930
8363
|
const iter = provider.stream(req, { signal })[Symbol.asyncIterator]();
|
|
7931
8364
|
try {
|
|
7932
8365
|
for (; ; ) {
|
|
@@ -7946,9 +8379,12 @@ async function streamProviderToResponse(provider, req, signal, ctx, events, logg
|
|
|
7946
8379
|
break;
|
|
7947
8380
|
case "text_delta":
|
|
7948
8381
|
handleTextDelta(state, ev.text);
|
|
7949
|
-
|
|
8382
|
+
pendingText += ev.text;
|
|
8383
|
+
pendingCount++;
|
|
8384
|
+
if (pendingCount >= TEXT_BATCH_SIZE) flushText();
|
|
7950
8385
|
break;
|
|
7951
8386
|
case "tool_use_start": {
|
|
8387
|
+
flushText();
|
|
7952
8388
|
const idVal = ev.id;
|
|
7953
8389
|
const nameVal = ev.name;
|
|
7954
8390
|
handleToolUseStart(state, { id: idVal, name: nameVal });
|
|
@@ -7960,6 +8396,7 @@ async function streamProviderToResponse(provider, req, signal, ctx, events, logg
|
|
|
7960
8396
|
handleToolUseInputDelta(state, ev);
|
|
7961
8397
|
break;
|
|
7962
8398
|
case "tool_use_stop": {
|
|
8399
|
+
flushText();
|
|
7963
8400
|
const stoppedName = state.tools.get(ev.id)?.name ?? "unknown";
|
|
7964
8401
|
handleToolUseStop(state, ev);
|
|
7965
8402
|
events.emit("provider.tool_use_stop", { ctx, id: ev.id, name: stoppedName });
|
|
@@ -7969,6 +8406,7 @@ async function streamProviderToResponse(provider, req, signal, ctx, events, logg
|
|
|
7969
8406
|
handleThinkingStart(state, ev);
|
|
7970
8407
|
break;
|
|
7971
8408
|
case "thinking_delta":
|
|
8409
|
+
flushText();
|
|
7972
8410
|
handleThinkingDelta(state, ev.text);
|
|
7973
8411
|
events.emit("provider.thinking_delta", { ctx, text: ev.text });
|
|
7974
8412
|
break;
|
|
@@ -8000,6 +8438,7 @@ async function streamProviderToResponse(provider, req, signal, ctx, events, logg
|
|
|
8000
8438
|
eventType: String(evAny.type),
|
|
8001
8439
|
errorMessage: errMsg
|
|
8002
8440
|
});
|
|
8441
|
+
flushText();
|
|
8003
8442
|
events.emit("provider.stream_error", {
|
|
8004
8443
|
ctx,
|
|
8005
8444
|
eventType: String(evAny.type),
|
|
@@ -8010,6 +8449,7 @@ async function streamProviderToResponse(provider, req, signal, ctx, events, logg
|
|
|
8010
8449
|
} catch (err) {
|
|
8011
8450
|
if (signal.aborted) {
|
|
8012
8451
|
state.stopReason = "end_turn";
|
|
8452
|
+
flushText();
|
|
8013
8453
|
logger.debug("Stream aborted \u2014 returning partial state", {
|
|
8014
8454
|
providerId: provider.id,
|
|
8015
8455
|
model: req.model,
|
|
@@ -8028,8 +8468,8 @@ async function streamProviderToResponse(provider, req, signal, ctx, events, logg
|
|
|
8028
8468
|
});
|
|
8029
8469
|
await Promise.race([
|
|
8030
8470
|
drainPromise,
|
|
8031
|
-
new Promise((
|
|
8032
|
-
drainTimer = setTimeout(
|
|
8471
|
+
new Promise((resolve7) => {
|
|
8472
|
+
drainTimer = setTimeout(resolve7, STREAM_DRAIN_TIMEOUT_MS);
|
|
8033
8473
|
})
|
|
8034
8474
|
]);
|
|
8035
8475
|
} finally {
|
|
@@ -8038,6 +8478,7 @@ async function streamProviderToResponse(provider, req, signal, ctx, events, logg
|
|
|
8038
8478
|
} catch {
|
|
8039
8479
|
}
|
|
8040
8480
|
}
|
|
8481
|
+
flushText();
|
|
8041
8482
|
logger.debug("Stream completed", {
|
|
8042
8483
|
providerId: provider.id,
|
|
8043
8484
|
model: req.model,
|
|
@@ -8135,7 +8576,7 @@ async function runProviderWithRetry(opts) {
|
|
|
8135
8576
|
description
|
|
8136
8577
|
});
|
|
8137
8578
|
}
|
|
8138
|
-
await new Promise((
|
|
8579
|
+
await new Promise((resolve7, reject) => {
|
|
8139
8580
|
let settled = false;
|
|
8140
8581
|
const cleanup = () => {
|
|
8141
8582
|
clearTimeout(t);
|
|
@@ -8151,7 +8592,7 @@ async function runProviderWithRetry(opts) {
|
|
|
8151
8592
|
if (settled) return;
|
|
8152
8593
|
settled = true;
|
|
8153
8594
|
cleanup();
|
|
8154
|
-
|
|
8595
|
+
resolve7();
|
|
8155
8596
|
}, delay);
|
|
8156
8597
|
if (signal.aborted) {
|
|
8157
8598
|
onAbort();
|
|
@@ -8224,17 +8665,9 @@ function findPreserveStart(messages, preserveK) {
|
|
|
8224
8665
|
const prev = messages[preserveStart - 1];
|
|
8225
8666
|
if (!first || !prev || first.role !== "user" || prev.role !== "assistant") break;
|
|
8226
8667
|
if (typeof first.content === "string" || typeof prev.content === "string") break;
|
|
8227
|
-
const
|
|
8228
|
-
|
|
8229
|
-
|
|
8230
|
-
if (block.type === "tool_result") resultIds.add(block.tool_use_id);
|
|
8231
|
-
}
|
|
8232
|
-
if (resultIds.size === 0) break;
|
|
8233
|
-
const hasMatchingUse = prev.content.some((block) => {
|
|
8234
|
-
pairRepairInnerIterations++;
|
|
8235
|
-
return block.type === "tool_use" && resultIds.has(block.id);
|
|
8236
|
-
});
|
|
8237
|
-
if (!hasMatchingUse) break;
|
|
8668
|
+
const pairCheck = hasMatchingToolPair(first.content, prev.content);
|
|
8669
|
+
pairRepairInnerIterations += pairCheck.iterations;
|
|
8670
|
+
if (!pairCheck.matched) break;
|
|
8238
8671
|
preserveStart--;
|
|
8239
8672
|
}
|
|
8240
8673
|
if (compactionDebugEnabled()) {
|
|
@@ -8253,9 +8686,34 @@ function findPreserveStart(messages, preserveK) {
|
|
|
8253
8686
|
}
|
|
8254
8687
|
return preserveStart;
|
|
8255
8688
|
}
|
|
8689
|
+
function hasMatchingToolPair(resultContent, useContent) {
|
|
8690
|
+
let iterations = 0;
|
|
8691
|
+
let firstResultId;
|
|
8692
|
+
let resultIds;
|
|
8693
|
+
for (const block of resultContent) {
|
|
8694
|
+
iterations++;
|
|
8695
|
+
if (block.type !== "tool_result") continue;
|
|
8696
|
+
if (firstResultId === void 0) {
|
|
8697
|
+
firstResultId = block.tool_use_id;
|
|
8698
|
+
} else {
|
|
8699
|
+
resultIds ??= /* @__PURE__ */ new Set([firstResultId]);
|
|
8700
|
+
resultIds.add(block.tool_use_id);
|
|
8701
|
+
}
|
|
8702
|
+
}
|
|
8703
|
+
if (firstResultId === void 0) return { matched: false, iterations };
|
|
8704
|
+
for (const block of useContent) {
|
|
8705
|
+
iterations++;
|
|
8706
|
+
if (block.type !== "tool_use") continue;
|
|
8707
|
+
if (resultIds ? resultIds.has(block.id) : block.id === firstResultId) {
|
|
8708
|
+
return { matched: true, iterations };
|
|
8709
|
+
}
|
|
8710
|
+
}
|
|
8711
|
+
return { matched: false, iterations };
|
|
8712
|
+
}
|
|
8256
8713
|
function eliseOldToolResults(messages, opts) {
|
|
8257
8714
|
const preserveStart = findPreserveStart(messages, opts.preserveK);
|
|
8258
8715
|
let hasOversized = false;
|
|
8716
|
+
let firstOversizedIndex = -1;
|
|
8259
8717
|
let fastPathIterations = 0;
|
|
8260
8718
|
let fastPathInnerIterations = 0;
|
|
8261
8719
|
for (let i = 0; i < preserveStart && !hasOversized; i++) {
|
|
@@ -8267,6 +8725,7 @@ function eliseOldToolResults(messages, opts) {
|
|
|
8267
8725
|
const oversized = b.type === "tool_result" && estimateToolResultTokens(b.content) >= opts.eliseThreshold || b.type === "tool_use" && estimateToolInputTokens(b.input) >= opts.eliseThreshold;
|
|
8268
8726
|
if (oversized) {
|
|
8269
8727
|
hasOversized = true;
|
|
8728
|
+
firstOversizedIndex = i;
|
|
8270
8729
|
break;
|
|
8271
8730
|
}
|
|
8272
8731
|
}
|
|
@@ -8289,26 +8748,29 @@ function eliseOldToolResults(messages, opts) {
|
|
|
8289
8748
|
let changed = false;
|
|
8290
8749
|
let fullPassIterations = 0;
|
|
8291
8750
|
let fullPassInnerIterations = 0;
|
|
8292
|
-
|
|
8293
|
-
for (let i =
|
|
8751
|
+
let next;
|
|
8752
|
+
for (let i = firstOversizedIndex; i < preserveStart; i++) {
|
|
8294
8753
|
fullPassIterations++;
|
|
8295
8754
|
const msg = messages[i];
|
|
8296
|
-
if (
|
|
8297
|
-
next[i] = msg;
|
|
8298
|
-
continue;
|
|
8299
|
-
}
|
|
8755
|
+
if (!msg || !Array.isArray(msg.content)) continue;
|
|
8300
8756
|
const original = msg.content;
|
|
8301
|
-
|
|
8757
|
+
let newContent;
|
|
8758
|
+
for (let idx = 0; idx < original.length; idx++) {
|
|
8759
|
+
fullPassInnerIterations++;
|
|
8760
|
+
const b = original[idx];
|
|
8761
|
+
if (!b) continue;
|
|
8302
8762
|
if (b.type === "tool_use") {
|
|
8303
8763
|
const tokens2 = estimateToolInputTokens(b.input);
|
|
8304
|
-
if (tokens2 < opts.eliseThreshold)
|
|
8764
|
+
if (tokens2 < opts.eliseThreshold) continue;
|
|
8305
8765
|
const elidedInput = summarizeToolUseInputElision(b, tokens2);
|
|
8306
8766
|
saved += Math.max(0, tokens2 - estimateToolInputTokens(elidedInput));
|
|
8307
|
-
|
|
8767
|
+
newContent ??= original.slice();
|
|
8768
|
+
newContent[idx] = { ...b, input: elidedInput };
|
|
8769
|
+
continue;
|
|
8308
8770
|
}
|
|
8309
|
-
if (b.type !== "tool_result")
|
|
8771
|
+
if (b.type !== "tool_result") continue;
|
|
8310
8772
|
const tokens = estimateToolResultTokens(b.content);
|
|
8311
|
-
if (tokens < opts.eliseThreshold)
|
|
8773
|
+
if (tokens < opts.eliseThreshold) continue;
|
|
8312
8774
|
saved += tokens;
|
|
8313
8775
|
const elided = {
|
|
8314
8776
|
type: "tool_result",
|
|
@@ -8316,15 +8778,14 @@ function eliseOldToolResults(messages, opts) {
|
|
|
8316
8778
|
content: summarizeToolResultElision(b, tokens),
|
|
8317
8779
|
is_error: b.is_error
|
|
8318
8780
|
};
|
|
8319
|
-
|
|
8320
|
-
|
|
8321
|
-
|
|
8322
|
-
|
|
8323
|
-
|
|
8781
|
+
newContent ??= original.slice();
|
|
8782
|
+
newContent[idx] = elided;
|
|
8783
|
+
}
|
|
8784
|
+
if (newContent) {
|
|
8785
|
+
next ??= messages.slice();
|
|
8324
8786
|
next[i] = { ...msg, content: newContent };
|
|
8325
8787
|
changed = true;
|
|
8326
8788
|
}
|
|
8327
|
-
fullPassInnerIterations += original.length;
|
|
8328
8789
|
if (compactionDebugEnabled()) {
|
|
8329
8790
|
const ratio = fullPassInnerIterations / fullPassIterations;
|
|
8330
8791
|
if (ratio > 10) {
|
|
@@ -8351,7 +8812,7 @@ function eliseOldToolResults(messages, opts) {
|
|
|
8351
8812
|
tokensSaved: saved,
|
|
8352
8813
|
changed
|
|
8353
8814
|
});
|
|
8354
|
-
return { messages: changed ? next : messages, saved, changed };
|
|
8815
|
+
return { messages: changed && next ? next : messages, saved, changed };
|
|
8355
8816
|
}
|
|
8356
8817
|
function summarizeToolUseInputElision(block, tokens) {
|
|
8357
8818
|
const fields = {};
|
|
@@ -10659,7 +11120,14 @@ var EternalAutonomyEngine = class {
|
|
|
10659
11120
|
try {
|
|
10660
11121
|
const reloaded = await loadGoal(this.goalPath, this.opts.events);
|
|
10661
11122
|
iterationIndex = reloaded?.iterations ?? 0;
|
|
10662
|
-
} catch {
|
|
11123
|
+
} catch (err) {
|
|
11124
|
+
console.error(JSON.stringify({
|
|
11125
|
+
level: "warn",
|
|
11126
|
+
event: "autonomy.goal_reload_failed",
|
|
11127
|
+
message: toErrorMessage(err),
|
|
11128
|
+
context: { goalPath: this.goalPath },
|
|
11129
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
11130
|
+
}));
|
|
10663
11131
|
}
|
|
10664
11132
|
this.opts.onIteration?.({
|
|
10665
11133
|
at: (this.opts.now?.() ?? /* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -10894,7 +11362,14 @@ ${lastFew}` : "No prior iterations yet.",
|
|
|
10894
11362
|
} finally {
|
|
10895
11363
|
clearTimeout(timer);
|
|
10896
11364
|
}
|
|
10897
|
-
} catch {
|
|
11365
|
+
} catch (err) {
|
|
11366
|
+
console.error(JSON.stringify({
|
|
11367
|
+
level: "warn",
|
|
11368
|
+
event: "autonomy.brainstorm_failed",
|
|
11369
|
+
message: toErrorMessage(err),
|
|
11370
|
+
context: { goal: goal.goal.slice(0, 100) },
|
|
11371
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
11372
|
+
}));
|
|
10898
11373
|
return null;
|
|
10899
11374
|
}
|
|
10900
11375
|
}
|
|
@@ -11417,13 +11892,13 @@ var SubagentBudget = class _SubagentBudget {
|
|
|
11417
11892
|
if (!bus?.hasListenerFor("budget.threshold_reached")) {
|
|
11418
11893
|
return Promise.resolve("stop");
|
|
11419
11894
|
}
|
|
11420
|
-
return new Promise((
|
|
11895
|
+
return new Promise((resolve7) => {
|
|
11421
11896
|
let resolved = false;
|
|
11422
11897
|
const respond = (d) => {
|
|
11423
11898
|
if (resolved) return;
|
|
11424
11899
|
resolved = true;
|
|
11425
11900
|
clearTimeout(fallback);
|
|
11426
|
-
|
|
11901
|
+
resolve7(d);
|
|
11427
11902
|
};
|
|
11428
11903
|
const fallback = setTimeout(() => respond("stop"), _SubagentBudget.DECISION_TIMEOUT_MS);
|
|
11429
11904
|
bus.emit("budget.threshold_reached", {
|
|
@@ -14774,6 +15249,68 @@ Working rules:
|
|
|
14774
15249
|
- When in doubt, flag as medium rather than ignoring potential issues`
|
|
14775
15250
|
// Budgets are set by the orchestrator per task — see fleet.ts header.
|
|
14776
15251
|
};
|
|
15252
|
+
var SHADOW_AGENT = {
|
|
15253
|
+
id: "shadow-agent",
|
|
15254
|
+
name: "Shadow",
|
|
15255
|
+
role: "shadow-agent",
|
|
15256
|
+
prompt: `You are the Shadow Agent \u2014 a silent background monitor for the WrongStack fleet.
|
|
15257
|
+
|
|
15258
|
+
Your job is to observe, detect anomalies, and be ready to intervene \u2014 but only when commanded.
|
|
15259
|
+
|
|
15260
|
+
## Core Responsibilities
|
|
15261
|
+
|
|
15262
|
+
1. **Fleet Monitoring** (every 30s)
|
|
15263
|
+
- Call \`fleet_status\` + \`fleet_health\` on each heartbeat
|
|
15264
|
+
- Track what each agent is doing (task descriptions)
|
|
15265
|
+
- Detect stuck agents (>5min no events), idle agents, crashed agents
|
|
15266
|
+
|
|
15267
|
+
2. **FleetBus Subscription**
|
|
15268
|
+
- Subscribe to \`subagent.*\` events to track lifecycle
|
|
15269
|
+
- Subscribe to \`tool.executed\` to monitor activity
|
|
15270
|
+
- Track agent joins (subagent.started) and leaves (subagent.stopped)
|
|
15271
|
+
|
|
15272
|
+
3. **Mailbox Surveillance**
|
|
15273
|
+
- Monitor for \`control\` type messages starting with "hoop"
|
|
15274
|
+
- Detect orphan assigns (assign without result within 5min)
|
|
15275
|
+
- Cross-session awareness via shared project mailbox
|
|
15276
|
+
|
|
15277
|
+
4. **Spike Detection**
|
|
15278
|
+
- Track task duration per agent
|
|
15279
|
+
- Flag agents that spawn and die within <5 seconds
|
|
15280
|
+
- Log spike events with reason (completed/error/killed/timeout)
|
|
15281
|
+
|
|
15282
|
+
5. **Intervention Commands**
|
|
15283
|
+
Parse these from mailbox control messages:
|
|
15284
|
+
- \`hoop <agentId>\` \u2014 terminate specific agent
|
|
15285
|
+
- \`hoop all\` \u2014 terminate all running agents
|
|
15286
|
+
- \`shadow status\` \u2014 report current fleet snapshot
|
|
15287
|
+
- \`shadow mute\` \u2014 pause heartbeat monitoring
|
|
15288
|
+
- \`shadow resume\` \u2014 resume heartbeat monitoring
|
|
15289
|
+
- \`shadow interval <ms>\` \u2014 change heartbeat interval
|
|
15290
|
+
- \`shadow model <model-id>\` \u2014 change analysis model
|
|
15291
|
+
|
|
15292
|
+
## Operating Rules
|
|
15293
|
+
|
|
15294
|
+
- **Silent by default**: Use DEBUG level logging unless anomaly detected
|
|
15295
|
+
- **Deterministic**: Same state always produces same actions \u2014 no randomness
|
|
15296
|
+
- **Report on anomaly**: When anomaly detected, use \`mail_send\` to broadcast warning
|
|
15297
|
+
- **Never auto-intervene**: Always report unless explicitly commanded
|
|
15298
|
+
- **Minimal footprint**: Small state, efficient snapshots
|
|
15299
|
+
|
|
15300
|
+
## Startup Sequence
|
|
15301
|
+
|
|
15302
|
+
1. Send broadcast: \`shadow:started { intervalMs, model, startTime }\`
|
|
15303
|
+
2. Subscribe to FleetBus for all relevant events
|
|
15304
|
+
3. Schedule heartbeat cron job at configured interval
|
|
15305
|
+
4. Wait for commands or anomalies
|
|
15306
|
+
|
|
15307
|
+
## Shutdown Sequence
|
|
15308
|
+
|
|
15309
|
+
1. Cancel all cron jobs (\`cron_cancel\`)
|
|
15310
|
+
2. Send broadcast: \`shadow:stopped { reason, finalState }\`
|
|
15311
|
+
3. Clean up FleetBus subscriptions`
|
|
15312
|
+
// Budgets are set by the orchestrator per task — see fleet.ts header.
|
|
15313
|
+
};
|
|
14777
15314
|
var CRITIC_AGENT = {
|
|
14778
15315
|
id: "critic",
|
|
14779
15316
|
name: "Critic",
|
|
@@ -14814,6 +15351,7 @@ var FLEET_ROSTER = {
|
|
|
14814
15351
|
"refactor-planner": REFACTOR_PLANNER_AGENT,
|
|
14815
15352
|
"security-scanner": SECURITY_SCANNER_AGENT,
|
|
14816
15353
|
"critic": CRITIC_AGENT,
|
|
15354
|
+
"shadow-agent": SHADOW_AGENT,
|
|
14817
15355
|
...Object.fromEntries(
|
|
14818
15356
|
ALL_AGENT_DEFINITIONS.map((d) => [d.config.role, d.config])
|
|
14819
15357
|
)
|
|
@@ -14825,6 +15363,8 @@ var FLEET_ROSTER_BUDGETS = {
|
|
|
14825
15363
|
"refactor-planner": { timeoutMs: 7.5 * 60 * 60 * 1e3, maxIterations: 6e3, maxToolCalls: 18e3 },
|
|
14826
15364
|
"security-scanner": { timeoutMs: 10 * 60 * 60 * 1e3, maxIterations: 8e3, maxToolCalls: 2e4 },
|
|
14827
15365
|
"critic": { timeoutMs: 5 * 60 * 60 * 1e3, maxIterations: 4e3, maxToolCalls: 12e3 },
|
|
15366
|
+
"shadow-agent": { timeoutMs: 24 * 60 * 60 * 1e3, maxIterations: 1e4, maxToolCalls: 5e3 },
|
|
15367
|
+
// Long-running background monitor
|
|
14828
15368
|
...Object.fromEntries(
|
|
14829
15369
|
ALL_AGENT_DEFINITIONS.map((d) => [d.config.role, d.budget])
|
|
14830
15370
|
)
|
|
@@ -15263,7 +15803,7 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
|
|
|
15263
15803
|
taskIds.map((id) => {
|
|
15264
15804
|
const cached = this.completedResults.find((r) => r.taskId === id);
|
|
15265
15805
|
if (cached) return cached;
|
|
15266
|
-
return new Promise((
|
|
15806
|
+
return new Promise((resolve7, reject) => {
|
|
15267
15807
|
const timeout = setTimeout(() => {
|
|
15268
15808
|
this.off("task.completed", handler);
|
|
15269
15809
|
reject(new Error(`awaitTasks timed out waiting for task "${id}"`));
|
|
@@ -15272,7 +15812,7 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
|
|
|
15272
15812
|
if (result.taskId === id) {
|
|
15273
15813
|
clearTimeout(timeout);
|
|
15274
15814
|
this.off("task.completed", handler);
|
|
15275
|
-
|
|
15815
|
+
resolve7(result);
|
|
15276
15816
|
}
|
|
15277
15817
|
};
|
|
15278
15818
|
this.on("task.completed", handler);
|
|
@@ -15540,12 +16080,12 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
|
|
|
15540
16080
|
}
|
|
15541
16081
|
return new Promise((resolveDecision) => {
|
|
15542
16082
|
let settled = false;
|
|
15543
|
-
const
|
|
16083
|
+
const resolve7 = (d) => {
|
|
15544
16084
|
if (settled) return;
|
|
15545
16085
|
settled = true;
|
|
15546
16086
|
resolveDecision(d);
|
|
15547
16087
|
};
|
|
15548
|
-
const fallback = setTimeout(() =>
|
|
16088
|
+
const fallback = setTimeout(() => resolve7("stop"), DECISION_TIMEOUT_MS);
|
|
15549
16089
|
budget._events?.emit("budget.threshold_reached", {
|
|
15550
16090
|
kind: "timeout",
|
|
15551
16091
|
used,
|
|
@@ -15561,11 +16101,11 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
|
|
|
15561
16101
|
// disagreeing, resolves as a stop). Async grants still resolve.
|
|
15562
16102
|
extend: (extra) => {
|
|
15563
16103
|
clearTimeout(fallback);
|
|
15564
|
-
queueMicrotask(() =>
|
|
16104
|
+
queueMicrotask(() => resolve7({ extend: extra }));
|
|
15565
16105
|
},
|
|
15566
16106
|
deny: () => {
|
|
15567
16107
|
clearTimeout(fallback);
|
|
15568
|
-
|
|
16108
|
+
resolve7("stop");
|
|
15569
16109
|
}
|
|
15570
16110
|
});
|
|
15571
16111
|
});
|
|
@@ -16032,7 +16572,14 @@ ${personaLine}Task: ${task}
|
|
|
16032
16572
|
subagentIds.push(subagentId);
|
|
16033
16573
|
taskIds.push(taskId);
|
|
16034
16574
|
await coordinator.assign(spec);
|
|
16035
|
-
} catch {
|
|
16575
|
+
} catch (err) {
|
|
16576
|
+
console.error(JSON.stringify({
|
|
16577
|
+
level: "warn",
|
|
16578
|
+
event: "parallel_engine.spawn_failed",
|
|
16579
|
+
message: toErrorMessage(err),
|
|
16580
|
+
context: { slot: i, task, subagentId },
|
|
16581
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
16582
|
+
}));
|
|
16036
16583
|
}
|
|
16037
16584
|
})()
|
|
16038
16585
|
);
|
|
@@ -16057,7 +16604,14 @@ ${personaLine}Task: ${task}
|
|
|
16057
16604
|
} finally {
|
|
16058
16605
|
clearTimeout(timer);
|
|
16059
16606
|
}
|
|
16060
|
-
} catch {
|
|
16607
|
+
} catch (err) {
|
|
16608
|
+
console.error(JSON.stringify({
|
|
16609
|
+
level: "warn",
|
|
16610
|
+
event: "parallel_engine.brainstorm_results_failed",
|
|
16611
|
+
message: toErrorMessage(err),
|
|
16612
|
+
context: { slotCount, taskIds },
|
|
16613
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
16614
|
+
}));
|
|
16061
16615
|
results = coordinator.results().slice(-taskIds.length);
|
|
16062
16616
|
}
|
|
16063
16617
|
await Promise.allSettled(subagentIds.map((id) => coordinator.remove(id)));
|
|
@@ -16091,7 +16645,14 @@ ${personaLine}Task: ${task}
|
|
|
16091
16645
|
if (file) tasks.push(`[git] inspect and fix: ${file}`);
|
|
16092
16646
|
}
|
|
16093
16647
|
}
|
|
16094
|
-
} catch {
|
|
16648
|
+
} catch (err) {
|
|
16649
|
+
console.error(JSON.stringify({
|
|
16650
|
+
level: "warn",
|
|
16651
|
+
event: "parallel_engine.git_status_failed",
|
|
16652
|
+
message: toErrorMessage(err),
|
|
16653
|
+
context: { projectRoot: this.opts.projectRoot },
|
|
16654
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
16655
|
+
}));
|
|
16095
16656
|
}
|
|
16096
16657
|
}
|
|
16097
16658
|
if (tasks.length < this.slots) {
|
|
@@ -16454,7 +17015,7 @@ var InMemoryAgentBridge = class {
|
|
|
16454
17015
|
});
|
|
16455
17016
|
}
|
|
16456
17017
|
this.inflightGuards.add(correlationId);
|
|
16457
|
-
return new Promise((
|
|
17018
|
+
return new Promise((resolve7, reject) => {
|
|
16458
17019
|
const timer = setTimeout(() => {
|
|
16459
17020
|
this.inflightGuards.delete(correlationId);
|
|
16460
17021
|
this.pendingRequests.delete(correlationId);
|
|
@@ -16473,7 +17034,7 @@ var InMemoryAgentBridge = class {
|
|
|
16473
17034
|
return;
|
|
16474
17035
|
}
|
|
16475
17036
|
this.pendingRequests.set(correlationId, {
|
|
16476
|
-
resolve:
|
|
17037
|
+
resolve: resolve7,
|
|
16477
17038
|
reject,
|
|
16478
17039
|
timer
|
|
16479
17040
|
});
|
|
@@ -18935,11 +19496,11 @@ var Director = class _Director {
|
|
|
18935
19496
|
if (cached) return cached;
|
|
18936
19497
|
const existing = this.taskWaiters.get(id);
|
|
18937
19498
|
if (existing) return existing.promise;
|
|
18938
|
-
let
|
|
19499
|
+
let resolve7;
|
|
18939
19500
|
const promise = new Promise((res) => {
|
|
18940
|
-
|
|
19501
|
+
resolve7 = res;
|
|
18941
19502
|
});
|
|
18942
|
-
this.taskWaiters.set(id, { promise, resolve:
|
|
19503
|
+
this.taskWaiters.set(id, { promise, resolve: resolve7 });
|
|
18943
19504
|
return promise;
|
|
18944
19505
|
})
|
|
18945
19506
|
);
|
|
@@ -19335,7 +19896,7 @@ function createDelegateTool(opts) {
|
|
|
19335
19896
|
subagentId
|
|
19336
19897
|
});
|
|
19337
19898
|
const dir = director;
|
|
19338
|
-
const result = await new Promise((
|
|
19899
|
+
const result = await new Promise((resolve7) => {
|
|
19339
19900
|
let settled = false;
|
|
19340
19901
|
let timer;
|
|
19341
19902
|
const finish = (value) => {
|
|
@@ -19345,7 +19906,7 @@ function createDelegateTool(opts) {
|
|
|
19345
19906
|
offTool();
|
|
19346
19907
|
offIter();
|
|
19347
19908
|
offProgress();
|
|
19348
|
-
|
|
19909
|
+
resolve7(value);
|
|
19349
19910
|
};
|
|
19350
19911
|
const arm = () => {
|
|
19351
19912
|
if (timer) clearTimeout(timer);
|
|
@@ -19705,6 +20266,7 @@ var NULL_FLEET_BUS = new FleetBus();
|
|
|
19705
20266
|
// src/models/models-registry.ts
|
|
19706
20267
|
init_atomic_write();
|
|
19707
20268
|
var DEFAULT_URL = "https://models.dev/api.json";
|
|
20269
|
+
var ENV_URL_KEY = "WRONGSTACK_MODELS_DEV_URL";
|
|
19708
20270
|
var DEFAULT_TTL_SECONDS = 24 * 3600;
|
|
19709
20271
|
var DEFAULT_REFRESH_TIMEOUT_MS = 15e3;
|
|
19710
20272
|
var FAMILY_BY_NPM = {
|
|
@@ -19750,7 +20312,7 @@ var DefaultModelsRegistry = class {
|
|
|
19750
20312
|
overlayCacheFile;
|
|
19751
20313
|
constructor(opts) {
|
|
19752
20314
|
this.cacheFile = opts.cacheFile;
|
|
19753
|
-
this.url = opts.url ?? DEFAULT_URL;
|
|
20315
|
+
this.url = opts.url ?? process.env[ENV_URL_KEY] ?? DEFAULT_URL;
|
|
19754
20316
|
this.ttlMs = (opts.ttlSeconds ?? DEFAULT_TTL_SECONDS) * 1e3;
|
|
19755
20317
|
this.fetchImpl = opts.fetchImpl ?? fetch;
|
|
19756
20318
|
this.seed = opts.seed;
|
|
@@ -19795,6 +20357,10 @@ var DefaultModelsRegistry = class {
|
|
|
19795
20357
|
const cached = await this.readCacheAt(this.cacheFile);
|
|
19796
20358
|
if (cached && this.isWithinMaxStaleAge(cached.fetchedAt)) {
|
|
19797
20359
|
this.fetchedAt = new Date(cached.fetchedAt);
|
|
20360
|
+
const ageSeconds = Math.floor((Date.now() - this.fetchedAt.getTime()) / 1e3);
|
|
20361
|
+
console.warn(
|
|
20362
|
+
`ModelsRegistry: models.dev unavailable (${toErrorMessage(err)}); using stale cache from ${formatAge(ageSeconds)} ago. Run \`wstack models refresh\` to retry.`
|
|
20363
|
+
);
|
|
19798
20364
|
return cached.payload;
|
|
19799
20365
|
}
|
|
19800
20366
|
if (overlayAvailable) {
|
|
@@ -19880,7 +20446,13 @@ var DefaultModelsRegistry = class {
|
|
|
19880
20446
|
return json;
|
|
19881
20447
|
} catch {
|
|
19882
20448
|
const cached = await this.readCacheAt(this.overlayCacheFile);
|
|
19883
|
-
if (cached && this.isWithinMaxStaleAge(cached.fetchedAt))
|
|
20449
|
+
if (cached && this.isWithinMaxStaleAge(cached.fetchedAt)) {
|
|
20450
|
+
const ageSeconds = Math.floor((Date.now() - new Date(cached.fetchedAt).getTime()) / 1e3);
|
|
20451
|
+
console.warn(
|
|
20452
|
+
`ModelsRegistry: overlay unavailable; using stale overlay from ${formatAge(ageSeconds)} ago.`
|
|
20453
|
+
);
|
|
20454
|
+
return cached.payload;
|
|
20455
|
+
}
|
|
19884
20456
|
return void 0;
|
|
19885
20457
|
}
|
|
19886
20458
|
}
|
|
@@ -19977,6 +20549,16 @@ var DefaultModelsRegistry = class {
|
|
|
19977
20549
|
return path4.resolve(this.cacheFile);
|
|
19978
20550
|
}
|
|
19979
20551
|
};
|
|
20552
|
+
function formatAge(seconds) {
|
|
20553
|
+
if (seconds < 60) return "<1m";
|
|
20554
|
+
if (seconds < 3600) return `${Math.floor(seconds / 60)}m`;
|
|
20555
|
+
if (seconds < 86400) {
|
|
20556
|
+
const h = Math.floor(seconds / 3600);
|
|
20557
|
+
const m = Math.floor(seconds % 3600 / 60);
|
|
20558
|
+
return m > 0 ? `${h}h ${m}m` : `${h}h`;
|
|
20559
|
+
}
|
|
20560
|
+
return `${Math.floor(seconds / 86400)}d`;
|
|
20561
|
+
}
|
|
19980
20562
|
function hasEntries(payload) {
|
|
19981
20563
|
return payload !== void 0 && Object.keys(payload).length > 0;
|
|
19982
20564
|
}
|
|
@@ -21777,9 +22359,9 @@ var AISpecBuilder = class {
|
|
|
21777
22359
|
if (!this.sessionPath) return;
|
|
21778
22360
|
try {
|
|
21779
22361
|
const fsp16 = await import('fs/promises');
|
|
21780
|
-
const
|
|
22362
|
+
const path22 = await import('path');
|
|
21781
22363
|
const { atomicWrite: atomicWrite2 } = await Promise.resolve().then(() => (init_atomic_write(), atomic_write_exports));
|
|
21782
|
-
await fsp16.mkdir(
|
|
22364
|
+
await fsp16.mkdir(path22.dirname(this.sessionPath), { recursive: true });
|
|
21783
22365
|
await atomicWrite2(this.sessionPath, JSON.stringify(this.session, null, 2));
|
|
21784
22366
|
} catch {
|
|
21785
22367
|
}
|
|
@@ -22506,15 +23088,15 @@ function computeCriticalPath(graph, _topoOrder, blockedByMap) {
|
|
|
22506
23088
|
maxId = id;
|
|
22507
23089
|
}
|
|
22508
23090
|
}
|
|
22509
|
-
const
|
|
23091
|
+
const path22 = [];
|
|
22510
23092
|
let current = maxId;
|
|
22511
23093
|
const visited = /* @__PURE__ */ new Set();
|
|
22512
23094
|
while (current && !visited.has(current)) {
|
|
22513
23095
|
visited.add(current);
|
|
22514
|
-
|
|
23096
|
+
path22.unshift(current);
|
|
22515
23097
|
current = prev.get(current) ?? null;
|
|
22516
23098
|
}
|
|
22517
|
-
return
|
|
23099
|
+
return path22;
|
|
22518
23100
|
}
|
|
22519
23101
|
function computeParallelGroups(graph, blockedByMap) {
|
|
22520
23102
|
const groups = [];
|
|
@@ -23337,9 +23919,9 @@ var DefaultHealthRegistry = class {
|
|
|
23337
23919
|
}
|
|
23338
23920
|
async runOne(check) {
|
|
23339
23921
|
let timer = null;
|
|
23340
|
-
const timeout = new Promise((
|
|
23922
|
+
const timeout = new Promise((resolve7) => {
|
|
23341
23923
|
timer = setTimeout(
|
|
23342
|
-
() =>
|
|
23924
|
+
() => resolve7({ status: "unhealthy", detail: `timeout after ${this.timeoutMs}ms` }),
|
|
23343
23925
|
this.timeoutMs
|
|
23344
23926
|
);
|
|
23345
23927
|
});
|
|
@@ -23522,7 +24104,7 @@ async function startMetricsServer(opts) {
|
|
|
23522
24104
|
const tls = opts.tls;
|
|
23523
24105
|
const useHttps = !!(tls?.cert && tls?.key);
|
|
23524
24106
|
const host = opts.host ?? "127.0.0.1";
|
|
23525
|
-
const
|
|
24107
|
+
const path22 = opts.path ?? "/metrics";
|
|
23526
24108
|
const healthPath = opts.healthPath ?? "/healthz";
|
|
23527
24109
|
const healthRegistry = opts.healthRegistry;
|
|
23528
24110
|
const listener = (req, res) => {
|
|
@@ -23532,7 +24114,7 @@ async function startMetricsServer(opts) {
|
|
|
23532
24114
|
return;
|
|
23533
24115
|
}
|
|
23534
24116
|
const url = req.url.split("?")[0];
|
|
23535
|
-
if (url ===
|
|
24117
|
+
if (url === path22) {
|
|
23536
24118
|
let body;
|
|
23537
24119
|
try {
|
|
23538
24120
|
body = renderPrometheus(opts.sink.snapshot());
|
|
@@ -23578,14 +24160,14 @@ async function startMetricsServer(opts) {
|
|
|
23578
24160
|
const { createServer } = await import('http');
|
|
23579
24161
|
server = createServer(listener);
|
|
23580
24162
|
}
|
|
23581
|
-
await new Promise((
|
|
24163
|
+
await new Promise((resolve7, reject) => {
|
|
23582
24164
|
const onError = (err) => {
|
|
23583
24165
|
server.off("listening", onListening);
|
|
23584
24166
|
reject(err);
|
|
23585
24167
|
};
|
|
23586
24168
|
const onListening = () => {
|
|
23587
24169
|
server.off("error", onError);
|
|
23588
|
-
|
|
24170
|
+
resolve7();
|
|
23589
24171
|
};
|
|
23590
24172
|
server.once("error", onError);
|
|
23591
24173
|
server.once("listening", onListening);
|
|
@@ -23596,9 +24178,9 @@ async function startMetricsServer(opts) {
|
|
|
23596
24178
|
const protocol = useHttps ? "https" : "http";
|
|
23597
24179
|
return {
|
|
23598
24180
|
port: boundPort,
|
|
23599
|
-
url: `${protocol}://${host}:${boundPort}${
|
|
23600
|
-
close: () => new Promise((
|
|
23601
|
-
server.close((err) => err ? reject(err) :
|
|
24181
|
+
url: `${protocol}://${host}:${boundPort}${path22}`,
|
|
24182
|
+
close: () => new Promise((resolve7, reject) => {
|
|
24183
|
+
server.close((err) => err ? reject(err) : resolve7());
|
|
23602
24184
|
})
|
|
23603
24185
|
};
|
|
23604
24186
|
}
|