@wrongstack/core 0.270.0 → 0.272.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-PcHQl_UQ.d.ts → agent-bridge-jVSZiygR.d.ts} +1 -1
- package/dist/{agent-subagent-runner-SHJW7t8q.d.ts → agent-subagent-runner-DOLIwBRo.d.ts} +7 -7
- package/dist/{brain-BYcK__Ym.d.ts → brain-CdbbJWi3.d.ts} +71 -1
- package/dist/{compactor-C2RKEBtC.d.ts → compactor-72ug-ZRB.d.ts} +1 -1
- package/dist/{config-C_ae2k86.d.ts → config-D2DGoGSQ.d.ts} +29 -2
- package/dist/{context-Dp87Bcaq.d.ts → context-Dw55zZ_Q.d.ts} +110 -1
- package/dist/coordination/index.d.ts +121 -17
- package/dist/coordination/index.js +738 -74
- package/dist/coordination/index.js.map +1 -1
- package/dist/defaults/index.d.ts +25 -25
- package/dist/defaults/index.js +599 -86
- 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-CQj_C9Dp.d.ts} +139 -3
- package/dist/{goal-preamble-CA_4yiGQ.d.ts → goal-preamble-ZXDjjR1y.d.ts} +9 -9
- package/dist/{goal-store-DhuJoUNG.d.ts → goal-store-CcJBd-g1.d.ts} +1 -1
- package/dist/hq/index.d.ts +93 -6
- package/dist/hq/index.js +616 -46
- package/dist/hq/index.js.map +1 -1
- package/dist/{index-whDfTANu.d.ts → index-2Lhk5v0o.d.ts} +2 -2
- package/dist/{index-CZQ6Pwbs.d.ts → index-BL7BAx0p.d.ts} +8 -8
- package/dist/{index-W4VJCzHa.d.ts → index-Qo4kTzgw.d.ts} +5 -5
- package/dist/index.d.ts +96 -56
- package/dist/index.js +1938 -349
- 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-DS-YUXvF.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-DP6pGHet.d.ts} +1 -1
- package/dist/{multi-agent-coordinator-CJSpTe5O.d.ts → multi-agent-coordinator-BvbdNQ14.d.ts} +1 -1
- package/dist/{null-fleet-bus-QVshIsDx.d.ts → null-fleet-bus-BxTfXBKo.d.ts} +6 -6
- package/dist/observability/index.d.ts +2 -2
- package/dist/{parallel-eternal-engine-D9y5Pkcc.d.ts → parallel-eternal-engine-Cf-GTegR.d.ts} +9 -9
- package/dist/{path-resolver-CnQ8SIfh.d.ts → path-resolver-DztfnFcv.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-sNIkhXeB.d.ts} +2 -2
- package/dist/{plan-templates-NtPgyeJA.d.ts → plan-templates-DYiKFmEb.d.ts} +11 -5
- package/dist/{provider-model-resolve-d5poT5y0.d.ts → provider-model-resolve-dYAbTs_i.d.ts} +3 -3
- package/dist/{provider-runner-gkctlQV_.d.ts → provider-runner-Dw8x0F7u.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 +45 -13
- package/dist/storage/index.js +374 -113
- 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';
|
|
@@ -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
|
}
|
|
@@ -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;
|
|
@@ -2324,15 +2335,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
2324
2335
|
});
|
|
2325
2336
|
return indexed.slice(0, limit);
|
|
2326
2337
|
}
|
|
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);
|
|
2338
|
+
return await this.listFromDirectoryScan(limit);
|
|
2336
2339
|
} catch {
|
|
2337
2340
|
return [];
|
|
2338
2341
|
}
|
|
@@ -2457,43 +2460,102 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
2457
2460
|
this._indexCache = null;
|
|
2458
2461
|
return valid.length;
|
|
2459
2462
|
}
|
|
2463
|
+
async listFromDirectoryScan(limit) {
|
|
2464
|
+
const refs = await this.collectSessionFiles(this.dir);
|
|
2465
|
+
const candidates = await mapWithConcurrency(
|
|
2466
|
+
refs,
|
|
2467
|
+
_DefaultSessionStore.LIST_SCAN_CONCURRENCY,
|
|
2468
|
+
async (ref) => {
|
|
2469
|
+
const manifest = await this.readSummaryManifest(ref.id);
|
|
2470
|
+
if (manifest) return { summary: manifest, needsBackfill: false };
|
|
2471
|
+
const summary = await this.summaryHeaderFor(ref);
|
|
2472
|
+
return summary ? { summary, needsBackfill: true } : null;
|
|
2473
|
+
}
|
|
2474
|
+
);
|
|
2475
|
+
const out = candidates.filter((s) => s !== null);
|
|
2476
|
+
out.sort((a, b) => compareSessionSummaries(a.summary, b.summary));
|
|
2477
|
+
const selected = out.slice(0, limit);
|
|
2478
|
+
const summaries = await mapWithConcurrency(
|
|
2479
|
+
selected,
|
|
2480
|
+
Math.min(_DefaultSessionStore.LIST_SCAN_CONCURRENCY, Math.max(1, limit)),
|
|
2481
|
+
async (candidate) => {
|
|
2482
|
+
if (!candidate.needsBackfill) return candidate.summary;
|
|
2483
|
+
return await this.summaryFor(candidate.summary.id).catch(() => candidate.summary);
|
|
2484
|
+
}
|
|
2485
|
+
);
|
|
2486
|
+
return summaries.filter((s) => s !== null);
|
|
2487
|
+
}
|
|
2488
|
+
async collectSessionFiles(dir, prefix = "", depth = 0) {
|
|
2489
|
+
let entries;
|
|
2490
|
+
try {
|
|
2491
|
+
entries = await fsp2.readdir(dir, { withFileTypes: true });
|
|
2492
|
+
} catch {
|
|
2493
|
+
return [];
|
|
2494
|
+
}
|
|
2495
|
+
const dirEntries = [];
|
|
2496
|
+
const files = [];
|
|
2497
|
+
for (const entry of entries) {
|
|
2498
|
+
if (entry.name.startsWith(".") && entry.name !== ".wrongstack") continue;
|
|
2499
|
+
if (entry.name === "shared" || entry.name === "subagents" || entry.name === "attachments")
|
|
2500
|
+
continue;
|
|
2501
|
+
if (entry.isDirectory()) {
|
|
2502
|
+
dirEntries.push(entry);
|
|
2503
|
+
} else if (entry.isFile() && entry.name.endsWith(".jsonl")) {
|
|
2504
|
+
if (entry.name === "_index.jsonl") continue;
|
|
2505
|
+
const base = entry.name.replace(/\.jsonl$/, "");
|
|
2506
|
+
const id = prefix ? `${prefix}/${base}` : base;
|
|
2507
|
+
files.push({ id, filePath: path4.join(dir, entry.name) });
|
|
2508
|
+
}
|
|
2509
|
+
}
|
|
2510
|
+
const childFileArrays = await Promise.all(
|
|
2511
|
+
dirEntries.map((entry) => {
|
|
2512
|
+
const childPrefix = depth === 0 ? entry.name : `${prefix}/${entry.name}`;
|
|
2513
|
+
return this.collectSessionFiles(path4.join(dir, entry.name), childPrefix, depth + 1);
|
|
2514
|
+
})
|
|
2515
|
+
);
|
|
2516
|
+
return [...childFileArrays.flat(), ...files];
|
|
2517
|
+
}
|
|
2460
2518
|
/** Recursively collect session IDs from date-shard subdirectories.
|
|
2461
2519
|
* IDs include the date-prefix path (e.g. "2026-06-06/17-46-57Z_…").
|
|
2462
2520
|
* Skips `.jsonl`/`.summary.json` root files, dot-files, and
|
|
2463
2521
|
* sub-directories that belong to fleet/subagent sessions. */
|
|
2464
2522
|
async collectSessionIds(dir, prefix = "", depth = 0) {
|
|
2465
|
-
const ids = [];
|
|
2466
2523
|
let entries;
|
|
2467
2524
|
try {
|
|
2468
2525
|
entries = await fsp2.readdir(dir, { withFileTypes: true });
|
|
2469
2526
|
} catch {
|
|
2470
|
-
return
|
|
2527
|
+
return [];
|
|
2471
2528
|
}
|
|
2529
|
+
const dirEntries = [];
|
|
2530
|
+
const fileIds = [];
|
|
2472
2531
|
for (const entry of entries) {
|
|
2473
2532
|
if (entry.name.startsWith(".") && entry.name !== ".wrongstack") continue;
|
|
2474
2533
|
if (entry.name === "shared" || entry.name === "subagents" || entry.name === "attachments")
|
|
2475
2534
|
continue;
|
|
2476
2535
|
if (entry.isDirectory()) {
|
|
2477
|
-
|
|
2478
|
-
ids.push(...await this.collectSessionIds(path4.join(dir, entry.name), childPrefix, depth + 1));
|
|
2536
|
+
dirEntries.push(entry);
|
|
2479
2537
|
} else if (entry.isFile() && entry.name.endsWith(".jsonl")) {
|
|
2480
2538
|
if (entry.name === "_index.jsonl") continue;
|
|
2481
2539
|
const base = entry.name.replace(/\.jsonl$/, "");
|
|
2482
|
-
|
|
2540
|
+
fileIds.push(prefix ? `${prefix}/${base}` : base);
|
|
2483
2541
|
}
|
|
2484
2542
|
}
|
|
2485
|
-
|
|
2543
|
+
const childIdArrays = await Promise.all(
|
|
2544
|
+
dirEntries.map((entry) => {
|
|
2545
|
+
const childPrefix = depth === 0 ? entry.name : `${prefix}/${entry.name}`;
|
|
2546
|
+
return this.collectSessionIds(path4.join(dir, entry.name), childPrefix, depth + 1);
|
|
2547
|
+
})
|
|
2548
|
+
);
|
|
2549
|
+
return [...childIdArrays.flat(), ...fileIds];
|
|
2486
2550
|
}
|
|
2487
2551
|
async summaryFor(id) {
|
|
2488
2552
|
const manifest = this.sessionPath(id, ".summary.json");
|
|
2489
2553
|
const t0 = Date.now();
|
|
2490
2554
|
let outcome = "success";
|
|
2491
2555
|
let errorMsg;
|
|
2556
|
+
const fromManifest = await this.readSummaryManifest(id, t0);
|
|
2557
|
+
if (fromManifest) return fromManifest;
|
|
2492
2558
|
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
2559
|
const full = this.sessionPath(id, ".jsonl");
|
|
2498
2560
|
const stat6 = await fsp2.stat(full);
|
|
2499
2561
|
const summary = await this.summarize(id, stat6.mtime.toISOString());
|
|
@@ -2512,6 +2574,78 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
2512
2574
|
errorMsg = "summary fallback \u2014 manifest rebuilt";
|
|
2513
2575
|
this.emitRead(id, manifest, "summary", outcome, Date.now() - t0, errorMsg);
|
|
2514
2576
|
return summary;
|
|
2577
|
+
} catch (err) {
|
|
2578
|
+
outcome = "failure";
|
|
2579
|
+
errorMsg = toErrorMessage(err);
|
|
2580
|
+
this.emitRead(id, manifest, "summary", outcome, Date.now() - t0, errorMsg);
|
|
2581
|
+
return {
|
|
2582
|
+
id,
|
|
2583
|
+
title: "(damaged)",
|
|
2584
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2585
|
+
model: "unknown",
|
|
2586
|
+
provider: "unknown",
|
|
2587
|
+
tokenTotal: 0
|
|
2588
|
+
};
|
|
2589
|
+
}
|
|
2590
|
+
}
|
|
2591
|
+
async readSummaryManifest(id, startTime = Date.now()) {
|
|
2592
|
+
const manifest = this.sessionPath(id, ".summary.json");
|
|
2593
|
+
try {
|
|
2594
|
+
const raw = await fsp2.readFile(manifest, "utf8");
|
|
2595
|
+
this.emitRead(id, manifest, "summary", "success", Date.now() - startTime);
|
|
2596
|
+
return JSON.parse(raw);
|
|
2597
|
+
} catch {
|
|
2598
|
+
return null;
|
|
2599
|
+
}
|
|
2600
|
+
}
|
|
2601
|
+
async summaryHeaderFor(ref) {
|
|
2602
|
+
let mtime = (/* @__PURE__ */ new Date(0)).toISOString();
|
|
2603
|
+
try {
|
|
2604
|
+
const stat6 = await fsp2.stat(ref.filePath);
|
|
2605
|
+
if (!stat6.isFile()) {
|
|
2606
|
+
return {
|
|
2607
|
+
id: ref.id,
|
|
2608
|
+
title: "(damaged)",
|
|
2609
|
+
startedAt: stat6.mtime.toISOString(),
|
|
2610
|
+
model: "unknown",
|
|
2611
|
+
provider: "unknown",
|
|
2612
|
+
tokenTotal: 0
|
|
2613
|
+
};
|
|
2614
|
+
}
|
|
2615
|
+
mtime = stat6.mtime.toISOString();
|
|
2616
|
+
} catch {
|
|
2617
|
+
return null;
|
|
2618
|
+
}
|
|
2619
|
+
try {
|
|
2620
|
+
for await (const event of this.iterSessionEvents(ref.filePath)) {
|
|
2621
|
+
if (event.type === "session_start") {
|
|
2622
|
+
return {
|
|
2623
|
+
id: ref.id,
|
|
2624
|
+
title: "(empty session)",
|
|
2625
|
+
startedAt: event.ts,
|
|
2626
|
+
model: event.model ?? "unknown",
|
|
2627
|
+
provider: event.provider ?? "unknown",
|
|
2628
|
+
tokenTotal: 0
|
|
2629
|
+
};
|
|
2630
|
+
}
|
|
2631
|
+
}
|
|
2632
|
+
return {
|
|
2633
|
+
id: ref.id,
|
|
2634
|
+
title: "(empty session)",
|
|
2635
|
+
startedAt: (/* @__PURE__ */ new Date(0)).toISOString(),
|
|
2636
|
+
model: "unknown",
|
|
2637
|
+
provider: "unknown",
|
|
2638
|
+
tokenTotal: 0
|
|
2639
|
+
};
|
|
2640
|
+
} catch {
|
|
2641
|
+
return {
|
|
2642
|
+
id: ref.id,
|
|
2643
|
+
title: "(damaged)",
|
|
2644
|
+
startedAt: mtime,
|
|
2645
|
+
model: "unknown",
|
|
2646
|
+
provider: "unknown",
|
|
2647
|
+
tokenTotal: 0
|
|
2648
|
+
};
|
|
2515
2649
|
}
|
|
2516
2650
|
}
|
|
2517
2651
|
/**
|
|
@@ -2636,39 +2770,62 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
2636
2770
|
}
|
|
2637
2771
|
async summarize(id, mtime) {
|
|
2638
2772
|
try {
|
|
2639
|
-
const
|
|
2640
|
-
|
|
2641
|
-
|
|
2773
|
+
const file = this.sessionPath(id, ".jsonl");
|
|
2774
|
+
let title = "(empty session)";
|
|
2775
|
+
let startedAt = (/* @__PURE__ */ new Date(0)).toISOString();
|
|
2776
|
+
let endedAt;
|
|
2777
|
+
let model = "unknown";
|
|
2778
|
+
let provider = "unknown";
|
|
2779
|
+
let tokenIn = 0;
|
|
2780
|
+
let tokenOut = 0;
|
|
2642
2781
|
let iterationCount = 0;
|
|
2643
2782
|
let toolCallCount = 0;
|
|
2644
2783
|
let toolErrorCount = 0;
|
|
2645
2784
|
let fileChangeCount = 0;
|
|
2646
2785
|
const toolBreakdown = {};
|
|
2647
2786
|
let outcome;
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2787
|
+
let lastEventType;
|
|
2788
|
+
let hasError = false;
|
|
2789
|
+
let sawStart = false;
|
|
2790
|
+
for await (const e of this.iterSessionEvents(file)) {
|
|
2791
|
+
lastEventType = e.type;
|
|
2792
|
+
if (e.type === "session_start") {
|
|
2793
|
+
if (!sawStart) {
|
|
2794
|
+
sawStart = true;
|
|
2795
|
+
startedAt = e.ts;
|
|
2796
|
+
model = e.model ?? "unknown";
|
|
2797
|
+
provider = e.provider ?? "unknown";
|
|
2798
|
+
}
|
|
2799
|
+
} else if (e.type === "session_end") {
|
|
2800
|
+
endedAt = e.ts;
|
|
2801
|
+
} else if (e.type === "user_input") {
|
|
2802
|
+
if (title === "(empty session)") title = userInputTitle(e.content);
|
|
2803
|
+
} else if (e.type === "llm_response") {
|
|
2804
|
+
tokenIn += e.usage.input ?? 0;
|
|
2805
|
+
tokenOut += e.usage.output ?? 0;
|
|
2806
|
+
} else if (e.type === "in_flight_start") iterationCount++;
|
|
2651
2807
|
else if (e.type === "tool_call_start") {
|
|
2652
2808
|
toolCallCount++;
|
|
2653
2809
|
toolBreakdown[e.name] = (toolBreakdown[e.name] ?? 0) + 1;
|
|
2654
2810
|
} else if (e.type === "tool_result" && e.isError) toolErrorCount++;
|
|
2655
2811
|
else if (e.type === "file_snapshot") fileChangeCount += e.files.length;
|
|
2812
|
+
else if (e.type === "error" || e.type === "provider_error") hasError = true;
|
|
2656
2813
|
}
|
|
2657
|
-
if (
|
|
2814
|
+
if (lastEventType === "session_end") {
|
|
2658
2815
|
outcome = "completed";
|
|
2659
|
-
} else if (
|
|
2816
|
+
} else if (lastEventType === "in_flight_start") {
|
|
2660
2817
|
outcome = "aborted";
|
|
2661
|
-
} else if (
|
|
2818
|
+
} else if (hasError) {
|
|
2662
2819
|
outcome = "error";
|
|
2663
2820
|
}
|
|
2664
2821
|
return {
|
|
2665
2822
|
id,
|
|
2666
2823
|
title,
|
|
2667
|
-
startedAt
|
|
2668
|
-
endedAt
|
|
2669
|
-
model
|
|
2670
|
-
provider
|
|
2671
|
-
tokenTotal:
|
|
2824
|
+
startedAt,
|
|
2825
|
+
endedAt,
|
|
2826
|
+
model,
|
|
2827
|
+
provider,
|
|
2828
|
+
tokenTotal: tokenIn + tokenOut,
|
|
2672
2829
|
iterationCount: iterationCount > 0 ? iterationCount : void 0,
|
|
2673
2830
|
toolCallCount: toolCallCount > 0 ? toolCallCount : void 0,
|
|
2674
2831
|
toolErrorCount: toolErrorCount > 0 ? toolErrorCount : void 0,
|
|
@@ -2687,6 +2844,25 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
2687
2844
|
};
|
|
2688
2845
|
}
|
|
2689
2846
|
}
|
|
2847
|
+
async *iterSessionEvents(file) {
|
|
2848
|
+
const stream = createReadStream(file, { encoding: "utf8" });
|
|
2849
|
+
const lines = createInterface({ input: stream, crlfDelay: Infinity });
|
|
2850
|
+
try {
|
|
2851
|
+
for await (const line of lines) {
|
|
2852
|
+
if (!line.trim()) continue;
|
|
2853
|
+
try {
|
|
2854
|
+
const parsed = JSON.parse(line);
|
|
2855
|
+
if (parsed !== null && typeof parsed === "object" && typeof parsed.type === "string" && typeof parsed.ts === "string") {
|
|
2856
|
+
yield parsed;
|
|
2857
|
+
}
|
|
2858
|
+
} catch {
|
|
2859
|
+
}
|
|
2860
|
+
}
|
|
2861
|
+
} finally {
|
|
2862
|
+
lines.close();
|
|
2863
|
+
stream.destroy();
|
|
2864
|
+
}
|
|
2865
|
+
}
|
|
2690
2866
|
metaFromEvents(id, events) {
|
|
2691
2867
|
const start = events.find((e) => e.type === "session_start");
|
|
2692
2868
|
const end = events.findLast((e) => e.type === "session_end");
|
|
@@ -3350,6 +3526,27 @@ function userInputTitle(content) {
|
|
|
3350
3526
|
const text = typeof content === "string" ? content : content.filter((b) => b.type === "text").map((b) => b.text).join(" ");
|
|
3351
3527
|
return (text || "(non-text input)").slice(0, 60);
|
|
3352
3528
|
}
|
|
3529
|
+
function compareSessionSummaries(a, b) {
|
|
3530
|
+
if (a.startedAt < b.startedAt) return 1;
|
|
3531
|
+
if (a.startedAt > b.startedAt) return -1;
|
|
3532
|
+
return a.id.localeCompare(b.id);
|
|
3533
|
+
}
|
|
3534
|
+
async function mapWithConcurrency(items, concurrency, fn) {
|
|
3535
|
+
if (items.length === 0) return [];
|
|
3536
|
+
const out = new Array(items.length);
|
|
3537
|
+
let next = 0;
|
|
3538
|
+
const workerCount = Math.min(Math.max(1, concurrency), items.length);
|
|
3539
|
+
const workers = Array.from({ length: workerCount }, async () => {
|
|
3540
|
+
for (; ; ) {
|
|
3541
|
+
const idx = next++;
|
|
3542
|
+
if (idx >= items.length) return;
|
|
3543
|
+
const item = items[idx];
|
|
3544
|
+
if (item !== void 0) out[idx] = await fn(item);
|
|
3545
|
+
}
|
|
3546
|
+
});
|
|
3547
|
+
await Promise.all(workers);
|
|
3548
|
+
return out;
|
|
3549
|
+
}
|
|
3353
3550
|
|
|
3354
3551
|
// src/storage/queue-store.ts
|
|
3355
3552
|
init_atomic_write();
|
|
@@ -4647,6 +4844,83 @@ var ALGO = "aes-256-gcm";
|
|
|
4647
4844
|
var KEY_FILE_MODE = 384;
|
|
4648
4845
|
var KEY_FILE_MAGIC = Buffer.from("WSKV", "ascii");
|
|
4649
4846
|
var VERSIONED_KEY_FILE_SIZE = KEY_FILE_MAGIC.length + 1 + KEY_BYTES;
|
|
4847
|
+
var KEK_MAGIC = Buffer.from("WSKW", "ascii");
|
|
4848
|
+
var KEK_SALT_BYTES = 16;
|
|
4849
|
+
var WRAPPED_KEY_FILE_SIZE = KEK_MAGIC.length + 1 + KEK_SALT_BYTES + IV_BYTES + TAG_BYTES + KEY_BYTES;
|
|
4850
|
+
var SCRYPT_N = 1 << 15;
|
|
4851
|
+
var SCRYPT_R = 8;
|
|
4852
|
+
var SCRYPT_P = 1;
|
|
4853
|
+
var SCRYPT_MAXMEM = 64 * 1024 * 1024;
|
|
4854
|
+
function getVaultPassphrase() {
|
|
4855
|
+
const v = process.env["WRONGSTACK_VAULT_PASSPHRASE"];
|
|
4856
|
+
return v && v.length > 0 ? v : void 0;
|
|
4857
|
+
}
|
|
4858
|
+
function isWrappedKeyFile(buf) {
|
|
4859
|
+
return buf.length === WRAPPED_KEY_FILE_SIZE && buf.subarray(0, KEK_MAGIC.length).equals(KEK_MAGIC);
|
|
4860
|
+
}
|
|
4861
|
+
function deriveKEK(passphrase, salt) {
|
|
4862
|
+
return scryptSync(passphrase, salt, KEY_BYTES, {
|
|
4863
|
+
N: SCRYPT_N,
|
|
4864
|
+
r: SCRYPT_R,
|
|
4865
|
+
p: SCRYPT_P,
|
|
4866
|
+
maxmem: SCRYPT_MAXMEM
|
|
4867
|
+
});
|
|
4868
|
+
}
|
|
4869
|
+
function wrapDataKey(dataKey, keyVersion, passphrase) {
|
|
4870
|
+
const salt = randomBytes(KEK_SALT_BYTES);
|
|
4871
|
+
const iv = randomBytes(IV_BYTES);
|
|
4872
|
+
const kek = deriveKEK(passphrase, salt);
|
|
4873
|
+
const cipher = createCipheriv(ALGO, kek, iv);
|
|
4874
|
+
const ct = Buffer.concat([cipher.update(dataKey), cipher.final()]);
|
|
4875
|
+
const tag = cipher.getAuthTag();
|
|
4876
|
+
const out = Buffer.alloc(WRAPPED_KEY_FILE_SIZE);
|
|
4877
|
+
let off = 0;
|
|
4878
|
+
KEK_MAGIC.copy(out, off);
|
|
4879
|
+
off += KEK_MAGIC.length;
|
|
4880
|
+
out[off] = keyVersion & 255;
|
|
4881
|
+
off += 1;
|
|
4882
|
+
salt.copy(out, off);
|
|
4883
|
+
off += KEK_SALT_BYTES;
|
|
4884
|
+
iv.copy(out, off);
|
|
4885
|
+
off += IV_BYTES;
|
|
4886
|
+
tag.copy(out, off);
|
|
4887
|
+
off += TAG_BYTES;
|
|
4888
|
+
ct.copy(out, off);
|
|
4889
|
+
return out;
|
|
4890
|
+
}
|
|
4891
|
+
function unwrapDataKey(buf, keyFile) {
|
|
4892
|
+
const passphrase = getVaultPassphrase();
|
|
4893
|
+
if (!passphrase) {
|
|
4894
|
+
throw new ConfigError({
|
|
4895
|
+
message: `SecretVault: key file ${keyFile} is passphrase-protected \u2014 set the WRONGSTACK_VAULT_PASSPHRASE environment variable to unlock it.`,
|
|
4896
|
+
code: ERROR_CODES.CONFIG_INVALID,
|
|
4897
|
+
context: { keyFile }
|
|
4898
|
+
});
|
|
4899
|
+
}
|
|
4900
|
+
let off = KEK_MAGIC.length;
|
|
4901
|
+
const version = buf[off];
|
|
4902
|
+
off += 1;
|
|
4903
|
+
const salt = buf.subarray(off, off + KEK_SALT_BYTES);
|
|
4904
|
+
off += KEK_SALT_BYTES;
|
|
4905
|
+
const iv = buf.subarray(off, off + IV_BYTES);
|
|
4906
|
+
off += IV_BYTES;
|
|
4907
|
+
const tag = buf.subarray(off, off + TAG_BYTES);
|
|
4908
|
+
off += TAG_BYTES;
|
|
4909
|
+
const ct = buf.subarray(off, off + KEY_BYTES);
|
|
4910
|
+
const kek = deriveKEK(passphrase, salt);
|
|
4911
|
+
const decipher = createDecipheriv(ALGO, kek, iv);
|
|
4912
|
+
decipher.setAuthTag(tag);
|
|
4913
|
+
try {
|
|
4914
|
+
const key = Buffer.concat([decipher.update(ct), decipher.final()]);
|
|
4915
|
+
return { key: Buffer.from(key), version };
|
|
4916
|
+
} catch {
|
|
4917
|
+
throw new ConfigError({
|
|
4918
|
+
message: `SecretVault: failed to unlock key file ${keyFile} \u2014 wrong WRONGSTACK_VAULT_PASSPHRASE (key unwrap authentication failed).`,
|
|
4919
|
+
code: ERROR_CODES.CONFIG_INVALID,
|
|
4920
|
+
context: { keyFile }
|
|
4921
|
+
});
|
|
4922
|
+
}
|
|
4923
|
+
}
|
|
4650
4924
|
function checkKeyFilePermissions(keyFile) {
|
|
4651
4925
|
if (process.platform === "win32") return;
|
|
4652
4926
|
try {
|
|
@@ -4739,25 +5013,56 @@ var DefaultSecretVault = class {
|
|
|
4739
5013
|
const oldVersion = this._keyVersion;
|
|
4740
5014
|
const newKey = randomBytes(KEY_BYTES);
|
|
4741
5015
|
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
5016
|
fs4.mkdirSync(path4.dirname(this.keyFile), { recursive: true });
|
|
4747
|
-
|
|
5017
|
+
const passphrase = getVaultPassphrase();
|
|
5018
|
+
if (passphrase) {
|
|
5019
|
+
fs4.writeFileSync(this.keyFile, wrapDataKey(newKey, newVersion, passphrase), { mode: 384 });
|
|
5020
|
+
} else {
|
|
5021
|
+
const keyFileBuf = Buffer.alloc(VERSIONED_KEY_FILE_SIZE);
|
|
5022
|
+
KEY_FILE_MAGIC.copy(keyFileBuf, 0);
|
|
5023
|
+
keyFileBuf[KEY_FILE_MAGIC.length] = newVersion;
|
|
5024
|
+
newKey.copy(keyFileBuf, KEY_FILE_MAGIC.length + 1);
|
|
5025
|
+
fs4.writeFileSync(this.keyFile, keyFileBuf, { mode: 384 });
|
|
5026
|
+
}
|
|
4748
5027
|
checkKeyFilePermissions(this.keyFile);
|
|
4749
5028
|
this.key = newKey;
|
|
4750
5029
|
this._keyVersion = newVersion;
|
|
4751
5030
|
return { oldVersion, newVersion };
|
|
4752
5031
|
}
|
|
5032
|
+
/**
|
|
5033
|
+
* If WRONGSTACK_VAULT_PASSPHRASE is set but the key on disk is still stored
|
|
5034
|
+
* unwrapped (legacy v1 / versioned v2), re-write it in passphrase-wrapped (v3)
|
|
5035
|
+
* form. The data key is preserved, so all existing ciphertext keeps
|
|
5036
|
+
* decrypting. Best-effort: a write failure leaves the working unwrapped file
|
|
5037
|
+
* in place and is not fatal to load.
|
|
5038
|
+
*/
|
|
5039
|
+
migrateToWrappedIfPassphrase() {
|
|
5040
|
+
const passphrase = getVaultPassphrase();
|
|
5041
|
+
if (!passphrase || !this.key) return;
|
|
5042
|
+
try {
|
|
5043
|
+
fs4.writeFileSync(this.keyFile, wrapDataKey(this.key, this._keyVersion, passphrase), {
|
|
5044
|
+
mode: 384
|
|
5045
|
+
});
|
|
5046
|
+
checkKeyFilePermissions(this.keyFile);
|
|
5047
|
+
} catch {
|
|
5048
|
+
}
|
|
5049
|
+
}
|
|
4753
5050
|
loadOrCreateKey() {
|
|
4754
5051
|
if (this.key) return this.key;
|
|
4755
5052
|
try {
|
|
4756
5053
|
const buf = fs4.readFileSync(this.keyFile);
|
|
5054
|
+
if (isWrappedKeyFile(buf)) {
|
|
5055
|
+
const { key: key2, version } = unwrapDataKey(buf, this.keyFile);
|
|
5056
|
+
this.key = key2;
|
|
5057
|
+
this._keyVersion = version;
|
|
5058
|
+
checkKeyFilePermissions(this.keyFile);
|
|
5059
|
+
return this.key;
|
|
5060
|
+
}
|
|
4757
5061
|
if (buf.length === KEY_BYTES) {
|
|
4758
5062
|
this.key = buf;
|
|
4759
5063
|
this._keyVersion = 1;
|
|
4760
5064
|
checkKeyFilePermissions(this.keyFile);
|
|
5065
|
+
this.migrateToWrappedIfPassphrase();
|
|
4761
5066
|
return this.key;
|
|
4762
5067
|
}
|
|
4763
5068
|
if (buf.length === VERSIONED_KEY_FILE_SIZE) {
|
|
@@ -4781,6 +5086,7 @@ var DefaultSecretVault = class {
|
|
|
4781
5086
|
this.key = Buffer.from(key2);
|
|
4782
5087
|
this._keyVersion = version;
|
|
4783
5088
|
checkKeyFilePermissions(this.keyFile);
|
|
5089
|
+
this.migrateToWrappedIfPassphrase();
|
|
4784
5090
|
return this.key;
|
|
4785
5091
|
}
|
|
4786
5092
|
throw new ConfigError({
|
|
@@ -4793,11 +5099,20 @@ var DefaultSecretVault = class {
|
|
|
4793
5099
|
}
|
|
4794
5100
|
fs4.mkdirSync(path4.dirname(this.keyFile), { recursive: true });
|
|
4795
5101
|
const key = randomBytes(KEY_BYTES);
|
|
5102
|
+
const passphrase = getVaultPassphrase();
|
|
5103
|
+
const initialBytes = passphrase ? wrapDataKey(key, 1, passphrase) : key;
|
|
4796
5104
|
try {
|
|
4797
|
-
fs4.writeFileSync(this.keyFile,
|
|
5105
|
+
fs4.writeFileSync(this.keyFile, initialBytes, { mode: 384, flag: "wx" });
|
|
4798
5106
|
} catch (err) {
|
|
4799
5107
|
if (err.code !== "EEXIST") throw err;
|
|
4800
5108
|
const buf = fs4.readFileSync(this.keyFile);
|
|
5109
|
+
if (isWrappedKeyFile(buf)) {
|
|
5110
|
+
const { key: winnerKey, version } = unwrapDataKey(buf, this.keyFile);
|
|
5111
|
+
this.key = winnerKey;
|
|
5112
|
+
this._keyVersion = version;
|
|
5113
|
+
checkKeyFilePermissions(this.keyFile);
|
|
5114
|
+
return this.key;
|
|
5115
|
+
}
|
|
4801
5116
|
if (buf.length === KEY_BYTES) {
|
|
4802
5117
|
this.key = buf;
|
|
4803
5118
|
this._keyVersion = 1;
|
|
@@ -5141,6 +5456,42 @@ var defaultIndexing = {
|
|
|
5141
5456
|
watchExternal: true,
|
|
5142
5457
|
debounceMs: 400
|
|
5143
5458
|
};
|
|
5459
|
+
var IN_PROJECT_FORBIDDEN_KEYS = /* @__PURE__ */ new Set([
|
|
5460
|
+
"provider",
|
|
5461
|
+
"apiKey",
|
|
5462
|
+
"baseUrl",
|
|
5463
|
+
"providers",
|
|
5464
|
+
"mcpServers",
|
|
5465
|
+
"hooks",
|
|
5466
|
+
"plugins",
|
|
5467
|
+
"sync",
|
|
5468
|
+
"yolo",
|
|
5469
|
+
"extensions"
|
|
5470
|
+
]);
|
|
5471
|
+
function stripUnsafeInProjectFields(inProject, sourcePath, warn = (msg) => console.warn(msg)) {
|
|
5472
|
+
const stripped = [];
|
|
5473
|
+
const out = {};
|
|
5474
|
+
for (const [k, v] of Object.entries(inProject)) {
|
|
5475
|
+
if (IN_PROJECT_FORBIDDEN_KEYS.has(k)) {
|
|
5476
|
+
stripped.push(k);
|
|
5477
|
+
continue;
|
|
5478
|
+
}
|
|
5479
|
+
out[k] = v;
|
|
5480
|
+
}
|
|
5481
|
+
if (stripped.length > 0) {
|
|
5482
|
+
warn(
|
|
5483
|
+
JSON.stringify({
|
|
5484
|
+
level: "warn",
|
|
5485
|
+
event: "config.in_project_unsafe_fields_ignored",
|
|
5486
|
+
path: sourcePath,
|
|
5487
|
+
ignoredKeys: stripped,
|
|
5488
|
+
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.`,
|
|
5489
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
5490
|
+
})
|
|
5491
|
+
);
|
|
5492
|
+
}
|
|
5493
|
+
return out;
|
|
5494
|
+
}
|
|
5144
5495
|
function deepMerge2(base, patch) {
|
|
5145
5496
|
const opts = { arrayMode: "concat-primitives" };
|
|
5146
5497
|
if (envBoolOptional(process.env.WRONGSTACK_DEBUG_CONFIG)) {
|
|
@@ -5176,7 +5527,7 @@ var DefaultConfigLoader = class {
|
|
|
5176
5527
|
]);
|
|
5177
5528
|
cfg = deepMerge2(cfg, global);
|
|
5178
5529
|
cfg = deepMerge2(cfg, local);
|
|
5179
|
-
cfg = deepMerge2(cfg, inProject);
|
|
5530
|
+
cfg = deepMerge2(cfg, stripUnsafeInProjectFields(inProject, this.paths.inProjectConfig));
|
|
5180
5531
|
for (const [key, fn] of Object.entries(ENV_MAP)) {
|
|
5181
5532
|
const v = process.env[key];
|
|
5182
5533
|
if (v) fn(cfg, v);
|
|
@@ -7057,6 +7408,9 @@ function isClearlyDestructiveBashCommand(command, projectRoot) {
|
|
|
7057
7408
|
}
|
|
7058
7409
|
|
|
7059
7410
|
// src/security/permission-policy.ts
|
|
7411
|
+
function matchesTrust(patterns, subject) {
|
|
7412
|
+
return patterns.includes(subject) || matchAny(patterns, subject);
|
|
7413
|
+
}
|
|
7060
7414
|
var DefaultPermissionPolicy = class {
|
|
7061
7415
|
policy = {};
|
|
7062
7416
|
loaded = false;
|
|
@@ -7193,7 +7547,7 @@ var DefaultPermissionPolicy = class {
|
|
|
7193
7547
|
this._evalCache.set(cacheKey, decision);
|
|
7194
7548
|
return decision;
|
|
7195
7549
|
}
|
|
7196
|
-
if (entry?.deny && subject &&
|
|
7550
|
+
if (entry?.deny && subject && matchesTrust(entry.deny, subject)) {
|
|
7197
7551
|
const decision = { permission: "deny", source: "deny", reason: "matched deny pattern" };
|
|
7198
7552
|
this._evalCache.set(cacheKey, decision);
|
|
7199
7553
|
return decision;
|
|
@@ -7203,7 +7557,7 @@ var DefaultPermissionPolicy = class {
|
|
|
7203
7557
|
this._evalCache.set(cacheKey, decision);
|
|
7204
7558
|
return decision;
|
|
7205
7559
|
}
|
|
7206
|
-
if (entry?.allow && subject &&
|
|
7560
|
+
if (entry?.allow && subject && matchesTrust(entry.allow, subject)) {
|
|
7207
7561
|
const decision = { permission: "auto", source: "trust", reason: "matched allow pattern" };
|
|
7208
7562
|
this._evalCache.set(cacheKey, decision);
|
|
7209
7563
|
return decision;
|
|
@@ -7927,6 +8281,16 @@ function handleMessageStop(state, ev) {
|
|
|
7927
8281
|
async function streamProviderToResponse(provider, req, signal, ctx, events, logger) {
|
|
7928
8282
|
const state = createStreamingState(req.model);
|
|
7929
8283
|
logger.debug("Stream started", { providerId: provider.id, model: req.model });
|
|
8284
|
+
const TEXT_BATCH_SIZE = 4;
|
|
8285
|
+
let pendingText = "";
|
|
8286
|
+
let pendingCount = 0;
|
|
8287
|
+
const flushText = () => {
|
|
8288
|
+
if (pendingCount > 0) {
|
|
8289
|
+
events.emit("provider.text_delta", { ctx, text: pendingText });
|
|
8290
|
+
pendingText = "";
|
|
8291
|
+
pendingCount = 0;
|
|
8292
|
+
}
|
|
8293
|
+
};
|
|
7930
8294
|
const iter = provider.stream(req, { signal })[Symbol.asyncIterator]();
|
|
7931
8295
|
try {
|
|
7932
8296
|
for (; ; ) {
|
|
@@ -7946,9 +8310,12 @@ async function streamProviderToResponse(provider, req, signal, ctx, events, logg
|
|
|
7946
8310
|
break;
|
|
7947
8311
|
case "text_delta":
|
|
7948
8312
|
handleTextDelta(state, ev.text);
|
|
7949
|
-
|
|
8313
|
+
pendingText += ev.text;
|
|
8314
|
+
pendingCount++;
|
|
8315
|
+
if (pendingCount >= TEXT_BATCH_SIZE) flushText();
|
|
7950
8316
|
break;
|
|
7951
8317
|
case "tool_use_start": {
|
|
8318
|
+
flushText();
|
|
7952
8319
|
const idVal = ev.id;
|
|
7953
8320
|
const nameVal = ev.name;
|
|
7954
8321
|
handleToolUseStart(state, { id: idVal, name: nameVal });
|
|
@@ -7960,6 +8327,7 @@ async function streamProviderToResponse(provider, req, signal, ctx, events, logg
|
|
|
7960
8327
|
handleToolUseInputDelta(state, ev);
|
|
7961
8328
|
break;
|
|
7962
8329
|
case "tool_use_stop": {
|
|
8330
|
+
flushText();
|
|
7963
8331
|
const stoppedName = state.tools.get(ev.id)?.name ?? "unknown";
|
|
7964
8332
|
handleToolUseStop(state, ev);
|
|
7965
8333
|
events.emit("provider.tool_use_stop", { ctx, id: ev.id, name: stoppedName });
|
|
@@ -7969,6 +8337,7 @@ async function streamProviderToResponse(provider, req, signal, ctx, events, logg
|
|
|
7969
8337
|
handleThinkingStart(state, ev);
|
|
7970
8338
|
break;
|
|
7971
8339
|
case "thinking_delta":
|
|
8340
|
+
flushText();
|
|
7972
8341
|
handleThinkingDelta(state, ev.text);
|
|
7973
8342
|
events.emit("provider.thinking_delta", { ctx, text: ev.text });
|
|
7974
8343
|
break;
|
|
@@ -8000,6 +8369,7 @@ async function streamProviderToResponse(provider, req, signal, ctx, events, logg
|
|
|
8000
8369
|
eventType: String(evAny.type),
|
|
8001
8370
|
errorMessage: errMsg
|
|
8002
8371
|
});
|
|
8372
|
+
flushText();
|
|
8003
8373
|
events.emit("provider.stream_error", {
|
|
8004
8374
|
ctx,
|
|
8005
8375
|
eventType: String(evAny.type),
|
|
@@ -8010,6 +8380,7 @@ async function streamProviderToResponse(provider, req, signal, ctx, events, logg
|
|
|
8010
8380
|
} catch (err) {
|
|
8011
8381
|
if (signal.aborted) {
|
|
8012
8382
|
state.stopReason = "end_turn";
|
|
8383
|
+
flushText();
|
|
8013
8384
|
logger.debug("Stream aborted \u2014 returning partial state", {
|
|
8014
8385
|
providerId: provider.id,
|
|
8015
8386
|
model: req.model,
|
|
@@ -8038,6 +8409,7 @@ async function streamProviderToResponse(provider, req, signal, ctx, events, logg
|
|
|
8038
8409
|
} catch {
|
|
8039
8410
|
}
|
|
8040
8411
|
}
|
|
8412
|
+
flushText();
|
|
8041
8413
|
logger.debug("Stream completed", {
|
|
8042
8414
|
providerId: provider.id,
|
|
8043
8415
|
model: req.model,
|
|
@@ -8224,17 +8596,9 @@ function findPreserveStart(messages, preserveK) {
|
|
|
8224
8596
|
const prev = messages[preserveStart - 1];
|
|
8225
8597
|
if (!first || !prev || first.role !== "user" || prev.role !== "assistant") break;
|
|
8226
8598
|
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;
|
|
8599
|
+
const pairCheck = hasMatchingToolPair(first.content, prev.content);
|
|
8600
|
+
pairRepairInnerIterations += pairCheck.iterations;
|
|
8601
|
+
if (!pairCheck.matched) break;
|
|
8238
8602
|
preserveStart--;
|
|
8239
8603
|
}
|
|
8240
8604
|
if (compactionDebugEnabled()) {
|
|
@@ -8253,9 +8617,34 @@ function findPreserveStart(messages, preserveK) {
|
|
|
8253
8617
|
}
|
|
8254
8618
|
return preserveStart;
|
|
8255
8619
|
}
|
|
8620
|
+
function hasMatchingToolPair(resultContent, useContent) {
|
|
8621
|
+
let iterations = 0;
|
|
8622
|
+
let firstResultId;
|
|
8623
|
+
let resultIds;
|
|
8624
|
+
for (const block of resultContent) {
|
|
8625
|
+
iterations++;
|
|
8626
|
+
if (block.type !== "tool_result") continue;
|
|
8627
|
+
if (firstResultId === void 0) {
|
|
8628
|
+
firstResultId = block.tool_use_id;
|
|
8629
|
+
} else {
|
|
8630
|
+
resultIds ??= /* @__PURE__ */ new Set([firstResultId]);
|
|
8631
|
+
resultIds.add(block.tool_use_id);
|
|
8632
|
+
}
|
|
8633
|
+
}
|
|
8634
|
+
if (firstResultId === void 0) return { matched: false, iterations };
|
|
8635
|
+
for (const block of useContent) {
|
|
8636
|
+
iterations++;
|
|
8637
|
+
if (block.type !== "tool_use") continue;
|
|
8638
|
+
if (resultIds ? resultIds.has(block.id) : block.id === firstResultId) {
|
|
8639
|
+
return { matched: true, iterations };
|
|
8640
|
+
}
|
|
8641
|
+
}
|
|
8642
|
+
return { matched: false, iterations };
|
|
8643
|
+
}
|
|
8256
8644
|
function eliseOldToolResults(messages, opts) {
|
|
8257
8645
|
const preserveStart = findPreserveStart(messages, opts.preserveK);
|
|
8258
8646
|
let hasOversized = false;
|
|
8647
|
+
let firstOversizedIndex = -1;
|
|
8259
8648
|
let fastPathIterations = 0;
|
|
8260
8649
|
let fastPathInnerIterations = 0;
|
|
8261
8650
|
for (let i = 0; i < preserveStart && !hasOversized; i++) {
|
|
@@ -8267,6 +8656,7 @@ function eliseOldToolResults(messages, opts) {
|
|
|
8267
8656
|
const oversized = b.type === "tool_result" && estimateToolResultTokens(b.content) >= opts.eliseThreshold || b.type === "tool_use" && estimateToolInputTokens(b.input) >= opts.eliseThreshold;
|
|
8268
8657
|
if (oversized) {
|
|
8269
8658
|
hasOversized = true;
|
|
8659
|
+
firstOversizedIndex = i;
|
|
8270
8660
|
break;
|
|
8271
8661
|
}
|
|
8272
8662
|
}
|
|
@@ -8289,26 +8679,29 @@ function eliseOldToolResults(messages, opts) {
|
|
|
8289
8679
|
let changed = false;
|
|
8290
8680
|
let fullPassIterations = 0;
|
|
8291
8681
|
let fullPassInnerIterations = 0;
|
|
8292
|
-
|
|
8293
|
-
for (let i =
|
|
8682
|
+
let next;
|
|
8683
|
+
for (let i = firstOversizedIndex; i < preserveStart; i++) {
|
|
8294
8684
|
fullPassIterations++;
|
|
8295
8685
|
const msg = messages[i];
|
|
8296
|
-
if (
|
|
8297
|
-
next[i] = msg;
|
|
8298
|
-
continue;
|
|
8299
|
-
}
|
|
8686
|
+
if (!msg || !Array.isArray(msg.content)) continue;
|
|
8300
8687
|
const original = msg.content;
|
|
8301
|
-
|
|
8688
|
+
let newContent;
|
|
8689
|
+
for (let idx = 0; idx < original.length; idx++) {
|
|
8690
|
+
fullPassInnerIterations++;
|
|
8691
|
+
const b = original[idx];
|
|
8692
|
+
if (!b) continue;
|
|
8302
8693
|
if (b.type === "tool_use") {
|
|
8303
8694
|
const tokens2 = estimateToolInputTokens(b.input);
|
|
8304
|
-
if (tokens2 < opts.eliseThreshold)
|
|
8695
|
+
if (tokens2 < opts.eliseThreshold) continue;
|
|
8305
8696
|
const elidedInput = summarizeToolUseInputElision(b, tokens2);
|
|
8306
8697
|
saved += Math.max(0, tokens2 - estimateToolInputTokens(elidedInput));
|
|
8307
|
-
|
|
8698
|
+
newContent ??= original.slice();
|
|
8699
|
+
newContent[idx] = { ...b, input: elidedInput };
|
|
8700
|
+
continue;
|
|
8308
8701
|
}
|
|
8309
|
-
if (b.type !== "tool_result")
|
|
8702
|
+
if (b.type !== "tool_result") continue;
|
|
8310
8703
|
const tokens = estimateToolResultTokens(b.content);
|
|
8311
|
-
if (tokens < opts.eliseThreshold)
|
|
8704
|
+
if (tokens < opts.eliseThreshold) continue;
|
|
8312
8705
|
saved += tokens;
|
|
8313
8706
|
const elided = {
|
|
8314
8707
|
type: "tool_result",
|
|
@@ -8316,15 +8709,14 @@ function eliseOldToolResults(messages, opts) {
|
|
|
8316
8709
|
content: summarizeToolResultElision(b, tokens),
|
|
8317
8710
|
is_error: b.is_error
|
|
8318
8711
|
};
|
|
8319
|
-
|
|
8320
|
-
|
|
8321
|
-
|
|
8322
|
-
|
|
8323
|
-
|
|
8712
|
+
newContent ??= original.slice();
|
|
8713
|
+
newContent[idx] = elided;
|
|
8714
|
+
}
|
|
8715
|
+
if (newContent) {
|
|
8716
|
+
next ??= messages.slice();
|
|
8324
8717
|
next[i] = { ...msg, content: newContent };
|
|
8325
8718
|
changed = true;
|
|
8326
8719
|
}
|
|
8327
|
-
fullPassInnerIterations += original.length;
|
|
8328
8720
|
if (compactionDebugEnabled()) {
|
|
8329
8721
|
const ratio = fullPassInnerIterations / fullPassIterations;
|
|
8330
8722
|
if (ratio > 10) {
|
|
@@ -8351,7 +8743,7 @@ function eliseOldToolResults(messages, opts) {
|
|
|
8351
8743
|
tokensSaved: saved,
|
|
8352
8744
|
changed
|
|
8353
8745
|
});
|
|
8354
|
-
return { messages: changed ? next : messages, saved, changed };
|
|
8746
|
+
return { messages: changed && next ? next : messages, saved, changed };
|
|
8355
8747
|
}
|
|
8356
8748
|
function summarizeToolUseInputElision(block, tokens) {
|
|
8357
8749
|
const fields = {};
|
|
@@ -10659,7 +11051,14 @@ var EternalAutonomyEngine = class {
|
|
|
10659
11051
|
try {
|
|
10660
11052
|
const reloaded = await loadGoal(this.goalPath, this.opts.events);
|
|
10661
11053
|
iterationIndex = reloaded?.iterations ?? 0;
|
|
10662
|
-
} catch {
|
|
11054
|
+
} catch (err) {
|
|
11055
|
+
console.error(JSON.stringify({
|
|
11056
|
+
level: "warn",
|
|
11057
|
+
event: "autonomy.goal_reload_failed",
|
|
11058
|
+
message: toErrorMessage(err),
|
|
11059
|
+
context: { goalPath: this.goalPath },
|
|
11060
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
11061
|
+
}));
|
|
10663
11062
|
}
|
|
10664
11063
|
this.opts.onIteration?.({
|
|
10665
11064
|
at: (this.opts.now?.() ?? /* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -10894,7 +11293,14 @@ ${lastFew}` : "No prior iterations yet.",
|
|
|
10894
11293
|
} finally {
|
|
10895
11294
|
clearTimeout(timer);
|
|
10896
11295
|
}
|
|
10897
|
-
} catch {
|
|
11296
|
+
} catch (err) {
|
|
11297
|
+
console.error(JSON.stringify({
|
|
11298
|
+
level: "warn",
|
|
11299
|
+
event: "autonomy.brainstorm_failed",
|
|
11300
|
+
message: toErrorMessage(err),
|
|
11301
|
+
context: { goal: goal.goal.slice(0, 100) },
|
|
11302
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
11303
|
+
}));
|
|
10898
11304
|
return null;
|
|
10899
11305
|
}
|
|
10900
11306
|
}
|
|
@@ -14774,6 +15180,68 @@ Working rules:
|
|
|
14774
15180
|
- When in doubt, flag as medium rather than ignoring potential issues`
|
|
14775
15181
|
// Budgets are set by the orchestrator per task — see fleet.ts header.
|
|
14776
15182
|
};
|
|
15183
|
+
var SHADOW_AGENT = {
|
|
15184
|
+
id: "shadow-agent",
|
|
15185
|
+
name: "Shadow",
|
|
15186
|
+
role: "shadow-agent",
|
|
15187
|
+
prompt: `You are the Shadow Agent \u2014 a silent background monitor for the WrongStack fleet.
|
|
15188
|
+
|
|
15189
|
+
Your job is to observe, detect anomalies, and be ready to intervene \u2014 but only when commanded.
|
|
15190
|
+
|
|
15191
|
+
## Core Responsibilities
|
|
15192
|
+
|
|
15193
|
+
1. **Fleet Monitoring** (every 30s)
|
|
15194
|
+
- Call \`fleet_status\` + \`fleet_health\` on each heartbeat
|
|
15195
|
+
- Track what each agent is doing (task descriptions)
|
|
15196
|
+
- Detect stuck agents (>5min no events), idle agents, crashed agents
|
|
15197
|
+
|
|
15198
|
+
2. **FleetBus Subscription**
|
|
15199
|
+
- Subscribe to \`subagent.*\` events to track lifecycle
|
|
15200
|
+
- Subscribe to \`tool.executed\` to monitor activity
|
|
15201
|
+
- Track agent joins (subagent.started) and leaves (subagent.stopped)
|
|
15202
|
+
|
|
15203
|
+
3. **Mailbox Surveillance**
|
|
15204
|
+
- Monitor for \`control\` type messages starting with "hoop"
|
|
15205
|
+
- Detect orphan assigns (assign without result within 5min)
|
|
15206
|
+
- Cross-session awareness via shared project mailbox
|
|
15207
|
+
|
|
15208
|
+
4. **Spike Detection**
|
|
15209
|
+
- Track task duration per agent
|
|
15210
|
+
- Flag agents that spawn and die within <5 seconds
|
|
15211
|
+
- Log spike events with reason (completed/error/killed/timeout)
|
|
15212
|
+
|
|
15213
|
+
5. **Intervention Commands**
|
|
15214
|
+
Parse these from mailbox control messages:
|
|
15215
|
+
- \`hoop <agentId>\` \u2014 terminate specific agent
|
|
15216
|
+
- \`hoop all\` \u2014 terminate all running agents
|
|
15217
|
+
- \`shadow status\` \u2014 report current fleet snapshot
|
|
15218
|
+
- \`shadow mute\` \u2014 pause heartbeat monitoring
|
|
15219
|
+
- \`shadow resume\` \u2014 resume heartbeat monitoring
|
|
15220
|
+
- \`shadow interval <ms>\` \u2014 change heartbeat interval
|
|
15221
|
+
- \`shadow model <model-id>\` \u2014 change analysis model
|
|
15222
|
+
|
|
15223
|
+
## Operating Rules
|
|
15224
|
+
|
|
15225
|
+
- **Silent by default**: Use DEBUG level logging unless anomaly detected
|
|
15226
|
+
- **Deterministic**: Same state always produces same actions \u2014 no randomness
|
|
15227
|
+
- **Report on anomaly**: When anomaly detected, use \`mail_send\` to broadcast warning
|
|
15228
|
+
- **Never auto-intervene**: Always report unless explicitly commanded
|
|
15229
|
+
- **Minimal footprint**: Small state, efficient snapshots
|
|
15230
|
+
|
|
15231
|
+
## Startup Sequence
|
|
15232
|
+
|
|
15233
|
+
1. Send broadcast: \`shadow:started { intervalMs, model, startTime }\`
|
|
15234
|
+
2. Subscribe to FleetBus for all relevant events
|
|
15235
|
+
3. Schedule heartbeat cron job at configured interval
|
|
15236
|
+
4. Wait for commands or anomalies
|
|
15237
|
+
|
|
15238
|
+
## Shutdown Sequence
|
|
15239
|
+
|
|
15240
|
+
1. Cancel all cron jobs (\`cron_cancel\`)
|
|
15241
|
+
2. Send broadcast: \`shadow:stopped { reason, finalState }\`
|
|
15242
|
+
3. Clean up FleetBus subscriptions`
|
|
15243
|
+
// Budgets are set by the orchestrator per task — see fleet.ts header.
|
|
15244
|
+
};
|
|
14777
15245
|
var CRITIC_AGENT = {
|
|
14778
15246
|
id: "critic",
|
|
14779
15247
|
name: "Critic",
|
|
@@ -14814,6 +15282,7 @@ var FLEET_ROSTER = {
|
|
|
14814
15282
|
"refactor-planner": REFACTOR_PLANNER_AGENT,
|
|
14815
15283
|
"security-scanner": SECURITY_SCANNER_AGENT,
|
|
14816
15284
|
"critic": CRITIC_AGENT,
|
|
15285
|
+
"shadow-agent": SHADOW_AGENT,
|
|
14817
15286
|
...Object.fromEntries(
|
|
14818
15287
|
ALL_AGENT_DEFINITIONS.map((d) => [d.config.role, d.config])
|
|
14819
15288
|
)
|
|
@@ -14825,6 +15294,8 @@ var FLEET_ROSTER_BUDGETS = {
|
|
|
14825
15294
|
"refactor-planner": { timeoutMs: 7.5 * 60 * 60 * 1e3, maxIterations: 6e3, maxToolCalls: 18e3 },
|
|
14826
15295
|
"security-scanner": { timeoutMs: 10 * 60 * 60 * 1e3, maxIterations: 8e3, maxToolCalls: 2e4 },
|
|
14827
15296
|
"critic": { timeoutMs: 5 * 60 * 60 * 1e3, maxIterations: 4e3, maxToolCalls: 12e3 },
|
|
15297
|
+
"shadow-agent": { timeoutMs: 24 * 60 * 60 * 1e3, maxIterations: 1e4, maxToolCalls: 5e3 },
|
|
15298
|
+
// Long-running background monitor
|
|
14828
15299
|
...Object.fromEntries(
|
|
14829
15300
|
ALL_AGENT_DEFINITIONS.map((d) => [d.config.role, d.budget])
|
|
14830
15301
|
)
|
|
@@ -16032,7 +16503,14 @@ ${personaLine}Task: ${task}
|
|
|
16032
16503
|
subagentIds.push(subagentId);
|
|
16033
16504
|
taskIds.push(taskId);
|
|
16034
16505
|
await coordinator.assign(spec);
|
|
16035
|
-
} catch {
|
|
16506
|
+
} catch (err) {
|
|
16507
|
+
console.error(JSON.stringify({
|
|
16508
|
+
level: "warn",
|
|
16509
|
+
event: "parallel_engine.spawn_failed",
|
|
16510
|
+
message: toErrorMessage(err),
|
|
16511
|
+
context: { slot: i, task, subagentId },
|
|
16512
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
16513
|
+
}));
|
|
16036
16514
|
}
|
|
16037
16515
|
})()
|
|
16038
16516
|
);
|
|
@@ -16057,7 +16535,14 @@ ${personaLine}Task: ${task}
|
|
|
16057
16535
|
} finally {
|
|
16058
16536
|
clearTimeout(timer);
|
|
16059
16537
|
}
|
|
16060
|
-
} catch {
|
|
16538
|
+
} catch (err) {
|
|
16539
|
+
console.error(JSON.stringify({
|
|
16540
|
+
level: "warn",
|
|
16541
|
+
event: "parallel_engine.brainstorm_results_failed",
|
|
16542
|
+
message: toErrorMessage(err),
|
|
16543
|
+
context: { slotCount, taskIds },
|
|
16544
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
16545
|
+
}));
|
|
16061
16546
|
results = coordinator.results().slice(-taskIds.length);
|
|
16062
16547
|
}
|
|
16063
16548
|
await Promise.allSettled(subagentIds.map((id) => coordinator.remove(id)));
|
|
@@ -16091,7 +16576,14 @@ ${personaLine}Task: ${task}
|
|
|
16091
16576
|
if (file) tasks.push(`[git] inspect and fix: ${file}`);
|
|
16092
16577
|
}
|
|
16093
16578
|
}
|
|
16094
|
-
} catch {
|
|
16579
|
+
} catch (err) {
|
|
16580
|
+
console.error(JSON.stringify({
|
|
16581
|
+
level: "warn",
|
|
16582
|
+
event: "parallel_engine.git_status_failed",
|
|
16583
|
+
message: toErrorMessage(err),
|
|
16584
|
+
context: { projectRoot: this.opts.projectRoot },
|
|
16585
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
16586
|
+
}));
|
|
16095
16587
|
}
|
|
16096
16588
|
}
|
|
16097
16589
|
if (tasks.length < this.slots) {
|
|
@@ -19705,6 +20197,7 @@ var NULL_FLEET_BUS = new FleetBus();
|
|
|
19705
20197
|
// src/models/models-registry.ts
|
|
19706
20198
|
init_atomic_write();
|
|
19707
20199
|
var DEFAULT_URL = "https://models.dev/api.json";
|
|
20200
|
+
var ENV_URL_KEY = "WRONGSTACK_MODELS_DEV_URL";
|
|
19708
20201
|
var DEFAULT_TTL_SECONDS = 24 * 3600;
|
|
19709
20202
|
var DEFAULT_REFRESH_TIMEOUT_MS = 15e3;
|
|
19710
20203
|
var FAMILY_BY_NPM = {
|
|
@@ -19750,7 +20243,7 @@ var DefaultModelsRegistry = class {
|
|
|
19750
20243
|
overlayCacheFile;
|
|
19751
20244
|
constructor(opts) {
|
|
19752
20245
|
this.cacheFile = opts.cacheFile;
|
|
19753
|
-
this.url = opts.url ?? DEFAULT_URL;
|
|
20246
|
+
this.url = opts.url ?? process.env[ENV_URL_KEY] ?? DEFAULT_URL;
|
|
19754
20247
|
this.ttlMs = (opts.ttlSeconds ?? DEFAULT_TTL_SECONDS) * 1e3;
|
|
19755
20248
|
this.fetchImpl = opts.fetchImpl ?? fetch;
|
|
19756
20249
|
this.seed = opts.seed;
|
|
@@ -19795,6 +20288,10 @@ var DefaultModelsRegistry = class {
|
|
|
19795
20288
|
const cached = await this.readCacheAt(this.cacheFile);
|
|
19796
20289
|
if (cached && this.isWithinMaxStaleAge(cached.fetchedAt)) {
|
|
19797
20290
|
this.fetchedAt = new Date(cached.fetchedAt);
|
|
20291
|
+
const ageSeconds = Math.floor((Date.now() - this.fetchedAt.getTime()) / 1e3);
|
|
20292
|
+
console.warn(
|
|
20293
|
+
`ModelsRegistry: models.dev unavailable (${toErrorMessage(err)}); using stale cache from ${formatAge(ageSeconds)} ago. Run \`wstack models refresh\` to retry.`
|
|
20294
|
+
);
|
|
19798
20295
|
return cached.payload;
|
|
19799
20296
|
}
|
|
19800
20297
|
if (overlayAvailable) {
|
|
@@ -19880,7 +20377,13 @@ var DefaultModelsRegistry = class {
|
|
|
19880
20377
|
return json;
|
|
19881
20378
|
} catch {
|
|
19882
20379
|
const cached = await this.readCacheAt(this.overlayCacheFile);
|
|
19883
|
-
if (cached && this.isWithinMaxStaleAge(cached.fetchedAt))
|
|
20380
|
+
if (cached && this.isWithinMaxStaleAge(cached.fetchedAt)) {
|
|
20381
|
+
const ageSeconds = Math.floor((Date.now() - new Date(cached.fetchedAt).getTime()) / 1e3);
|
|
20382
|
+
console.warn(
|
|
20383
|
+
`ModelsRegistry: overlay unavailable; using stale overlay from ${formatAge(ageSeconds)} ago.`
|
|
20384
|
+
);
|
|
20385
|
+
return cached.payload;
|
|
20386
|
+
}
|
|
19884
20387
|
return void 0;
|
|
19885
20388
|
}
|
|
19886
20389
|
}
|
|
@@ -19977,6 +20480,16 @@ var DefaultModelsRegistry = class {
|
|
|
19977
20480
|
return path4.resolve(this.cacheFile);
|
|
19978
20481
|
}
|
|
19979
20482
|
};
|
|
20483
|
+
function formatAge(seconds) {
|
|
20484
|
+
if (seconds < 60) return "<1m";
|
|
20485
|
+
if (seconds < 3600) return `${Math.floor(seconds / 60)}m`;
|
|
20486
|
+
if (seconds < 86400) {
|
|
20487
|
+
const h = Math.floor(seconds / 3600);
|
|
20488
|
+
const m = Math.floor(seconds % 3600 / 60);
|
|
20489
|
+
return m > 0 ? `${h}h ${m}m` : `${h}h`;
|
|
20490
|
+
}
|
|
20491
|
+
return `${Math.floor(seconds / 86400)}d`;
|
|
20492
|
+
}
|
|
19980
20493
|
function hasEntries(payload) {
|
|
19981
20494
|
return payload !== void 0 && Object.keys(payload).length > 0;
|
|
19982
20495
|
}
|