@wrongstack/core 0.298.0 → 0.298.2

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/index.js CHANGED
@@ -1007,6 +1007,328 @@ var init_session_registry = __esm({
1007
1007
  }
1008
1008
  });
1009
1009
 
1010
+ // src/plugins/review-report-types.ts
1011
+ function validateReportTransition(from, to) {
1012
+ if (from === to) return;
1013
+ const allowed = REPORT_TRANSITION_MATRIX[from];
1014
+ if (!allowed?.has(to)) {
1015
+ throw new InvalidReportTransitionError(from, to);
1016
+ }
1017
+ }
1018
+ function reportEventTypeFor(to) {
1019
+ switch (to) {
1020
+ case "actioned":
1021
+ return "actioned";
1022
+ case "completed":
1023
+ return "completed";
1024
+ case "skipped":
1025
+ return "skipped";
1026
+ default:
1027
+ return "reopened";
1028
+ }
1029
+ }
1030
+ var InvalidReportTransitionError, REPORT_TRANSITION_MATRIX;
1031
+ var init_review_report_types = __esm({
1032
+ "src/plugins/review-report-types.ts"() {
1033
+ "use strict";
1034
+ InvalidReportTransitionError = class extends Error {
1035
+ from;
1036
+ to;
1037
+ constructor(from, to, reason) {
1038
+ const msg = reason ?? `Invalid report transition from "${from}" to "${to}"`;
1039
+ super(msg);
1040
+ this.name = "InvalidReportTransitionError";
1041
+ this.from = from;
1042
+ this.to = to;
1043
+ }
1044
+ };
1045
+ REPORT_TRANSITION_MATRIX = {
1046
+ open: /* @__PURE__ */ new Set(["actioned", "completed", "skipped"]),
1047
+ actioned: /* @__PURE__ */ new Set(["completed", "skipped"]),
1048
+ completed: /* @__PURE__ */ new Set(["open"]),
1049
+ skipped: /* @__PURE__ */ new Set(["open"])
1050
+ };
1051
+ }
1052
+ });
1053
+
1054
+ // src/plugins/review-report-store.ts
1055
+ var review_report_store_exports = {};
1056
+ __export(review_report_store_exports, {
1057
+ JsonlReportStore: () => JsonlReportStore,
1058
+ REPORT_DEFAULT_PAGE_SIZE: () => REPORT_DEFAULT_PAGE_SIZE,
1059
+ REPORT_RETENTION_MS: () => REPORT_RETENTION_MS,
1060
+ REPORT_STORE_FILE: () => REPORT_STORE_FILE,
1061
+ resolveReportStorePath: () => resolveReportStorePath
1062
+ });
1063
+ import { randomUUID as randomUUID28 } from "node:crypto";
1064
+ import * as fsp28 from "node:fs/promises";
1065
+ import * as path67 from "node:path";
1066
+ function resolveReportStorePath(projectDir) {
1067
+ return path67.join(projectDir, REPORT_STORE_FILE);
1068
+ }
1069
+ var REPORT_RETENTION_MS, REPORT_DEFAULT_PAGE_SIZE, NL, REPORT_STORE_FILE, JsonlReportStore;
1070
+ var init_review_report_store = __esm({
1071
+ "src/plugins/review-report-store.ts"() {
1072
+ "use strict";
1073
+ init_file_permissions();
1074
+ init_atomic_write();
1075
+ init_review_report_types();
1076
+ REPORT_RETENTION_MS = 90 * 24 * 60 * 60 * 1e3;
1077
+ REPORT_DEFAULT_PAGE_SIZE = 25;
1078
+ NL = "\n";
1079
+ REPORT_STORE_FILE = "review-reports.jsonl";
1080
+ JsonlReportStore = class {
1081
+ filePath;
1082
+ constructor(projectDir) {
1083
+ this.filePath = resolveReportStorePath(projectDir);
1084
+ }
1085
+ get storePath() {
1086
+ return this.filePath;
1087
+ }
1088
+ // ── Persist (create or reopen) ───────────────────────────────────
1089
+ async persist(input) {
1090
+ return withFileLock(this.filePath, async () => {
1091
+ const existing = await this._findRecord(input.id);
1092
+ if (existing) {
1093
+ const updated = {
1094
+ ...existing,
1095
+ counts: input.counts,
1096
+ totalFindings: input.totalFindings,
1097
+ unparseableCount: input.unparseableCount,
1098
+ durationSeconds: input.durationSeconds ?? existing.durationSeconds,
1099
+ rawText: input.rawText || existing.rawText,
1100
+ files: input.files.length > 0 ? input.files : existing.files
1101
+ };
1102
+ await fsp28.appendFile(this.filePath, JSON.stringify({ __report: 1, data: updated }) + NL, {
1103
+ encoding: "utf8",
1104
+ mode: SECRET_FILE_MODE
1105
+ });
1106
+ return updated;
1107
+ }
1108
+ const report = {
1109
+ id: input.id,
1110
+ reviewedAt: (/* @__PURE__ */ new Date()).toISOString(),
1111
+ sessionId: input.sessionId,
1112
+ agentId: input.agentId,
1113
+ reviewerModel: input.reviewerModel,
1114
+ source: input.source,
1115
+ reviewStatus: input.reviewStatus,
1116
+ lifecycle: "open",
1117
+ files: input.files,
1118
+ counts: input.counts,
1119
+ totalFindings: input.totalFindings,
1120
+ unparseableCount: input.unparseableCount,
1121
+ durationSeconds: input.durationSeconds,
1122
+ rawText: input.rawText,
1123
+ ...input.cascadeDepth !== void 0 ? { cascadeDepth: input.cascadeDepth } : {}
1124
+ };
1125
+ const createdEvent = {
1126
+ id: randomUUID28(),
1127
+ reportId: report.id,
1128
+ eventType: "created",
1129
+ fromLifecycle: null,
1130
+ toLifecycle: "open",
1131
+ actorId: "system",
1132
+ actorKind: "system",
1133
+ timestamp: report.reviewedAt
1134
+ };
1135
+ const lines = JSON.stringify({ __report: 1, data: report }) + NL + JSON.stringify({ __reportEvent: 1, data: createdEvent }) + NL;
1136
+ await fsp28.appendFile(this.filePath, lines, { encoding: "utf8", mode: SECRET_FILE_MODE });
1137
+ return report;
1138
+ });
1139
+ }
1140
+ // ── Lifecycle transitions ────────────────────────────────────────
1141
+ async transition(reportId, to, actor, opts) {
1142
+ return withFileLock(this.filePath, async () => {
1143
+ const all = await this._readAll();
1144
+ const entry = all.find((e) => e.report.id === reportId);
1145
+ if (!entry) throw new Error(`Review report not found: ${reportId}`);
1146
+ const from = this._materialize(entry).lifecycle;
1147
+ validateReportTransition(from, to);
1148
+ const event = {
1149
+ id: randomUUID28(),
1150
+ reportId,
1151
+ eventType: reportEventTypeFor(to),
1152
+ fromLifecycle: from,
1153
+ toLifecycle: to,
1154
+ actorId: actor.id,
1155
+ actorKind: actor.kind,
1156
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1157
+ reason: opts?.reason
1158
+ };
1159
+ entry.report.lifecycle = to;
1160
+ await fsp28.appendFile(this.filePath, JSON.stringify({ __reportEvent: 1, data: event }) + NL, {
1161
+ encoding: "utf8",
1162
+ mode: SECRET_FILE_MODE
1163
+ });
1164
+ return { ...entry.report };
1165
+ });
1166
+ }
1167
+ async addNote(reportId, actor, note) {
1168
+ return withFileLock(this.filePath, async () => {
1169
+ const all = await this._readAll();
1170
+ const entry = all.find((e) => e.report.id === reportId);
1171
+ if (!entry) throw new Error(`Review report not found: ${reportId}`);
1172
+ const materialized = this._materialize(entry);
1173
+ const event = {
1174
+ id: randomUUID28(),
1175
+ reportId,
1176
+ eventType: "note_added",
1177
+ fromLifecycle: materialized.lifecycle,
1178
+ toLifecycle: materialized.lifecycle,
1179
+ actorId: actor.id,
1180
+ actorKind: actor.kind,
1181
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1182
+ reason: note
1183
+ };
1184
+ await fsp28.appendFile(this.filePath, JSON.stringify({ __reportEvent: 1, data: event }) + NL, {
1185
+ encoding: "utf8",
1186
+ mode: SECRET_FILE_MODE
1187
+ });
1188
+ return materialized;
1189
+ });
1190
+ }
1191
+ // ── Query ────────────────────────────────────────────────────────
1192
+ async list(opts) {
1193
+ const all = await this._readAll();
1194
+ let reports = all.map((e) => this._materialize(e));
1195
+ if (opts?.statuses && opts.statuses.length > 0) {
1196
+ const set = new Set(opts.statuses);
1197
+ reports = reports.filter((r) => set.has(r.lifecycle));
1198
+ }
1199
+ if (opts?.sources && opts.sources.length > 0) {
1200
+ const set = new Set(opts.sources);
1201
+ reports = reports.filter((r) => set.has(r.source));
1202
+ }
1203
+ reports.sort((a, b) => b.reviewedAt.localeCompare(a.reviewedAt));
1204
+ const limit = opts?.limit ?? REPORT_DEFAULT_PAGE_SIZE;
1205
+ return reports.slice(0, limit);
1206
+ }
1207
+ async get(id) {
1208
+ const all = await this._readAll();
1209
+ const entry = all.find((e) => e.report.id === id);
1210
+ return entry ? this._materialize(entry) : null;
1211
+ }
1212
+ async getEvents(reportId) {
1213
+ const all = await this._readAllLines();
1214
+ const events = [];
1215
+ for (const line of all) {
1216
+ try {
1217
+ const parsed = JSON.parse(line);
1218
+ if (this._isEventRecord(parsed) && parsed.data.reportId === reportId) {
1219
+ events.push(parsed.data);
1220
+ }
1221
+ } catch {
1222
+ }
1223
+ }
1224
+ return events.sort((a, b) => a.timestamp.localeCompare(b.timestamp));
1225
+ }
1226
+ // ── Compaction ───────────────────────────────────────────────────
1227
+ async compact(opts) {
1228
+ return withFileLock(this.filePath, async () => {
1229
+ const maxAge = opts?.maxAgeMs ?? REPORT_RETENTION_MS;
1230
+ const now = Date.now();
1231
+ const all = await this._readAll();
1232
+ const kept = [];
1233
+ let removed = 0;
1234
+ let eventsFolded = 0;
1235
+ for (const entry of all) {
1236
+ const age = now - new Date(entry.report.reviewedAt).getTime();
1237
+ const materialized = this._materialize(entry);
1238
+ const isTerminal = materialized.lifecycle === "completed" || materialized.lifecycle === "skipped";
1239
+ if (isTerminal && age > maxAge) {
1240
+ removed++;
1241
+ eventsFolded += entry.events.length;
1242
+ continue;
1243
+ }
1244
+ const oldEvents = entry.events.filter(
1245
+ (ev) => now - new Date(ev.timestamp).getTime() > maxAge
1246
+ );
1247
+ if (oldEvents.length > 1) {
1248
+ eventsFolded += oldEvents.length - 1;
1249
+ const newestOld = oldEvents.sort((a, b) => b.timestamp.localeCompare(a.timestamp))[0];
1250
+ entry.events = [
1251
+ newestOld,
1252
+ ...entry.events.filter((ev) => now - new Date(ev.timestamp).getTime() <= maxAge)
1253
+ ];
1254
+ }
1255
+ kept.push({ record: { __report: 1, data: entry.report }, events: entry.events });
1256
+ }
1257
+ const lines = [];
1258
+ for (const { record, events } of kept) {
1259
+ if (record) lines.push(JSON.stringify(record));
1260
+ for (const ev of events) {
1261
+ lines.push(JSON.stringify({ __reportEvent: 1, data: ev }));
1262
+ }
1263
+ }
1264
+ lines.push(
1265
+ JSON.stringify({
1266
+ __reportCompact: 1,
1267
+ compactedAt: (/* @__PURE__ */ new Date()).toISOString(),
1268
+ removedReports: removed,
1269
+ foldedEvents: eventsFolded
1270
+ })
1271
+ );
1272
+ await atomicWrite(this.filePath, lines.join(NL) + NL, { mode: 384 });
1273
+ return { removed, eventsFolded };
1274
+ });
1275
+ }
1276
+ // ── Private helpers ──────────────────────────────────────────────
1277
+ async _readAllLines() {
1278
+ try {
1279
+ const raw = await fsp28.readFile(this.filePath, "utf8");
1280
+ return raw.split(NL).filter((l) => l.trim().length > 0);
1281
+ } catch (err) {
1282
+ if (err.code === "ENOENT") return [];
1283
+ throw err;
1284
+ }
1285
+ }
1286
+ _isReportRecord(v) {
1287
+ return typeof v === "object" && v !== null && v["__report"] === 1;
1288
+ }
1289
+ _isEventRecord(v) {
1290
+ return typeof v === "object" && v !== null && v["__reportEvent"] === 1;
1291
+ }
1292
+ async _readAll() {
1293
+ const lines = await this._readAllLines();
1294
+ const reports = /* @__PURE__ */ new Map();
1295
+ const eventsMap = /* @__PURE__ */ new Map();
1296
+ for (const line of lines) {
1297
+ try {
1298
+ const parsed = JSON.parse(line);
1299
+ if (this._isReportRecord(parsed)) {
1300
+ reports.set(parsed.data.id, parsed.data);
1301
+ if (!eventsMap.has(parsed.data.id)) eventsMap.set(parsed.data.id, []);
1302
+ } else if (this._isEventRecord(parsed)) {
1303
+ const list = eventsMap.get(parsed.data.reportId);
1304
+ if (list) list.push(parsed.data);
1305
+ }
1306
+ } catch {
1307
+ }
1308
+ }
1309
+ return Array.from(reports.entries()).map(([id, report]) => ({
1310
+ report,
1311
+ events: eventsMap.get(id) ?? []
1312
+ }));
1313
+ }
1314
+ async _findRecord(id) {
1315
+ const all = await this._readAll();
1316
+ const entry = all.find((e) => e.report.id === id);
1317
+ return entry ? this._materialize(entry) : null;
1318
+ }
1319
+ _materialize(entry) {
1320
+ if (entry.events.length === 0) return { ...entry.report };
1321
+ const sorted = [...entry.events].sort((a, b) => a.timestamp.localeCompare(b.timestamp));
1322
+ const latest = sorted[sorted.length - 1];
1323
+ if (latest.eventType !== "note_added") {
1324
+ return { ...entry.report, lifecycle: latest.toLifecycle };
1325
+ }
1326
+ return { ...entry.report };
1327
+ }
1328
+ };
1329
+ }
1330
+ });
1331
+
1010
1332
  // src/plugins/review-finding-types.ts
1011
1333
  import { createHash as createHash32 } from "node:crypto";
1012
1334
  function normalizeFingerprintTitle(title) {
@@ -1072,13 +1394,13 @@ __export(review_finding_store_exports, {
1072
1394
  JsonlFindingStore: () => JsonlFindingStore,
1073
1395
  resolveFindingStorePath: () => resolveFindingStorePath
1074
1396
  });
1075
- import { randomUUID as randomUUID35 } from "node:crypto";
1076
- import * as fsp38 from "node:fs/promises";
1077
- import * as path90 from "node:path";
1397
+ import { randomUUID as randomUUID36 } from "node:crypto";
1398
+ import * as fsp39 from "node:fs/promises";
1399
+ import * as path91 from "node:path";
1078
1400
  function resolveFindingStorePath(projectDir) {
1079
- return path90.join(projectDir, FINDING_STORE_FILE);
1401
+ return path91.join(projectDir, FINDING_STORE_FILE);
1080
1402
  }
1081
- var FINDING_RESOLVED_RETENTION_MS, FINDING_IGNORED_RETENTION_MS, FINDING_DEFAULT_PAGE_SIZE, NL, LINE_SEPARATOR, FINDING_STORE_FILE, JsonlFindingStore;
1403
+ var FINDING_RESOLVED_RETENTION_MS, FINDING_IGNORED_RETENTION_MS, FINDING_DEFAULT_PAGE_SIZE, NL2, LINE_SEPARATOR, FINDING_STORE_FILE, JsonlFindingStore;
1082
1404
  var init_review_finding_store = __esm({
1083
1405
  "src/plugins/review-finding-store.ts"() {
1084
1406
  "use strict";
@@ -1088,8 +1410,8 @@ var init_review_finding_store = __esm({
1088
1410
  FINDING_RESOLVED_RETENTION_MS = 30 * 24 * 60 * 60 * 1e3;
1089
1411
  FINDING_IGNORED_RETENTION_MS = 14 * 24 * 60 * 60 * 1e3;
1090
1412
  FINDING_DEFAULT_PAGE_SIZE = 50;
1091
- NL = "\n";
1092
- LINE_SEPARATOR = NL;
1413
+ NL2 = "\n";
1414
+ LINE_SEPARATOR = NL2;
1093
1415
  FINDING_STORE_FILE = "review-findings.jsonl";
1094
1416
  JsonlFindingStore = class {
1095
1417
  filePath;
@@ -1120,7 +1442,7 @@ var init_review_finding_store = __esm({
1120
1442
  match.finding.status = "active";
1121
1443
  match.finding.resolution = void 0;
1122
1444
  const updatedRecord = { __finding: 1, data: match.finding };
1123
- await fsp38.appendFile(
1445
+ await fsp39.appendFile(
1124
1446
  this.filePath,
1125
1447
  JSON.stringify(updatedRecord) + LINE_SEPARATOR + JSON.stringify({ __findingEvent: 1, data: reopenEvent }) + LINE_SEPARATOR,
1126
1448
  { encoding: "utf8", mode: SECRET_FILE_MODE }
@@ -1134,7 +1456,7 @@ var init_review_finding_store = __esm({
1134
1456
  match.finding.status,
1135
1457
  context
1136
1458
  );
1137
- await fsp38.appendFile(
1459
+ await fsp39.appendFile(
1138
1460
  this.filePath,
1139
1461
  JSON.stringify({ __findingEvent: 1, data: relinkEvent }) + LINE_SEPARATOR,
1140
1462
  { encoding: "utf8", mode: SECRET_FILE_MODE }
@@ -1145,7 +1467,7 @@ var init_review_finding_store = __esm({
1145
1467
  const record = { __finding: 1, data: finding };
1146
1468
  const createdEvent = this._makeEvent(finding.id, "created", null, "active", context);
1147
1469
  const lines = JSON.stringify(record) + LINE_SEPARATOR + JSON.stringify({ __findingEvent: 1, data: createdEvent }) + LINE_SEPARATOR;
1148
- await fsp38.appendFile(this.filePath, lines, { encoding: "utf8", mode: SECRET_FILE_MODE });
1470
+ await fsp39.appendFile(this.filePath, lines, { encoding: "utf8", mode: SECRET_FILE_MODE });
1149
1471
  result.created++;
1150
1472
  }
1151
1473
  }
@@ -1161,7 +1483,7 @@ var init_review_finding_store = __esm({
1161
1483
  validateTransition(from, to);
1162
1484
  validateResolution(to, opts?.outcome);
1163
1485
  const event = {
1164
- id: randomUUID35(),
1486
+ id: randomUUID36(),
1165
1487
  findingId,
1166
1488
  eventType: to === "resolved" ? "resolved" : to === "ignored" ? "ignored" : this._eventTypeFor(to),
1167
1489
  fromStatus: from,
@@ -1181,7 +1503,7 @@ var init_review_finding_store = __esm({
1181
1503
  };
1182
1504
  }
1183
1505
  const updatedRecord = { __finding: 1, data: entry.finding };
1184
- await fsp38.appendFile(
1506
+ await fsp39.appendFile(
1185
1507
  this.filePath,
1186
1508
  JSON.stringify(updatedRecord) + LINE_SEPARATOR + JSON.stringify({ __findingEvent: 1, data: event }) + LINE_SEPARATOR,
1187
1509
  { encoding: "utf8", mode: SECRET_FILE_MODE }
@@ -1298,7 +1620,7 @@ var init_review_finding_store = __esm({
1298
1620
  // ── Private helpers ──────────────────────────────────────────────
1299
1621
  async _readAllLines() {
1300
1622
  try {
1301
- const raw = await fsp38.readFile(this.filePath, "utf8");
1623
+ const raw = await fsp39.readFile(this.filePath, "utf8");
1302
1624
  return raw.split(LINE_SEPARATOR).filter((l) => l.trim().length > 0);
1303
1625
  } catch (err) {
1304
1626
  if (err.code === "ENOENT") return [];
@@ -1345,7 +1667,7 @@ var init_review_finding_store = __esm({
1345
1667
  }
1346
1668
  _makeEvent(findingId, eventType, fromStatus, toStatus, context) {
1347
1669
  return {
1348
- id: randomUUID35(),
1670
+ id: randomUUID36(),
1349
1671
  findingId,
1350
1672
  eventType,
1351
1673
  fromStatus,
@@ -1376,328 +1698,6 @@ var init_review_finding_store = __esm({
1376
1698
  }
1377
1699
  });
1378
1700
 
1379
- // src/plugins/review-report-types.ts
1380
- function validateReportTransition(from, to) {
1381
- if (from === to) return;
1382
- const allowed = REPORT_TRANSITION_MATRIX[from];
1383
- if (!allowed?.has(to)) {
1384
- throw new InvalidReportTransitionError(from, to);
1385
- }
1386
- }
1387
- function reportEventTypeFor(to) {
1388
- switch (to) {
1389
- case "actioned":
1390
- return "actioned";
1391
- case "completed":
1392
- return "completed";
1393
- case "skipped":
1394
- return "skipped";
1395
- default:
1396
- return "reopened";
1397
- }
1398
- }
1399
- var InvalidReportTransitionError, REPORT_TRANSITION_MATRIX;
1400
- var init_review_report_types = __esm({
1401
- "src/plugins/review-report-types.ts"() {
1402
- "use strict";
1403
- InvalidReportTransitionError = class extends Error {
1404
- from;
1405
- to;
1406
- constructor(from, to, reason) {
1407
- const msg = reason ?? `Invalid report transition from "${from}" to "${to}"`;
1408
- super(msg);
1409
- this.name = "InvalidReportTransitionError";
1410
- this.from = from;
1411
- this.to = to;
1412
- }
1413
- };
1414
- REPORT_TRANSITION_MATRIX = {
1415
- open: /* @__PURE__ */ new Set(["actioned", "completed", "skipped"]),
1416
- actioned: /* @__PURE__ */ new Set(["completed", "skipped"]),
1417
- completed: /* @__PURE__ */ new Set(["open"]),
1418
- skipped: /* @__PURE__ */ new Set(["open"])
1419
- };
1420
- }
1421
- });
1422
-
1423
- // src/plugins/review-report-store.ts
1424
- var review_report_store_exports = {};
1425
- __export(review_report_store_exports, {
1426
- JsonlReportStore: () => JsonlReportStore,
1427
- REPORT_DEFAULT_PAGE_SIZE: () => REPORT_DEFAULT_PAGE_SIZE,
1428
- REPORT_RETENTION_MS: () => REPORT_RETENTION_MS,
1429
- REPORT_STORE_FILE: () => REPORT_STORE_FILE,
1430
- resolveReportStorePath: () => resolveReportStorePath
1431
- });
1432
- import { randomUUID as randomUUID37 } from "node:crypto";
1433
- import * as fsp39 from "node:fs/promises";
1434
- import * as path91 from "node:path";
1435
- function resolveReportStorePath(projectDir) {
1436
- return path91.join(projectDir, REPORT_STORE_FILE);
1437
- }
1438
- var REPORT_RETENTION_MS, REPORT_DEFAULT_PAGE_SIZE, NL2, REPORT_STORE_FILE, JsonlReportStore;
1439
- var init_review_report_store = __esm({
1440
- "src/plugins/review-report-store.ts"() {
1441
- "use strict";
1442
- init_file_permissions();
1443
- init_atomic_write();
1444
- init_review_report_types();
1445
- REPORT_RETENTION_MS = 90 * 24 * 60 * 60 * 1e3;
1446
- REPORT_DEFAULT_PAGE_SIZE = 25;
1447
- NL2 = "\n";
1448
- REPORT_STORE_FILE = "review-reports.jsonl";
1449
- JsonlReportStore = class {
1450
- filePath;
1451
- constructor(projectDir) {
1452
- this.filePath = resolveReportStorePath(projectDir);
1453
- }
1454
- get storePath() {
1455
- return this.filePath;
1456
- }
1457
- // ── Persist (create or reopen) ───────────────────────────────────
1458
- async persist(input) {
1459
- return withFileLock(this.filePath, async () => {
1460
- const existing = await this._findRecord(input.id);
1461
- if (existing) {
1462
- const updated = {
1463
- ...existing,
1464
- counts: input.counts,
1465
- totalFindings: input.totalFindings,
1466
- unparseableCount: input.unparseableCount,
1467
- durationSeconds: input.durationSeconds ?? existing.durationSeconds,
1468
- rawText: input.rawText || existing.rawText,
1469
- files: input.files.length > 0 ? input.files : existing.files
1470
- };
1471
- await fsp39.appendFile(this.filePath, JSON.stringify({ __report: 1, data: updated }) + NL2, {
1472
- encoding: "utf8",
1473
- mode: SECRET_FILE_MODE
1474
- });
1475
- return updated;
1476
- }
1477
- const report = {
1478
- id: input.id,
1479
- reviewedAt: (/* @__PURE__ */ new Date()).toISOString(),
1480
- sessionId: input.sessionId,
1481
- agentId: input.agentId,
1482
- reviewerModel: input.reviewerModel,
1483
- source: input.source,
1484
- reviewStatus: input.reviewStatus,
1485
- lifecycle: "open",
1486
- files: input.files,
1487
- counts: input.counts,
1488
- totalFindings: input.totalFindings,
1489
- unparseableCount: input.unparseableCount,
1490
- durationSeconds: input.durationSeconds,
1491
- rawText: input.rawText,
1492
- ...input.cascadeDepth !== void 0 ? { cascadeDepth: input.cascadeDepth } : {}
1493
- };
1494
- const createdEvent = {
1495
- id: randomUUID37(),
1496
- reportId: report.id,
1497
- eventType: "created",
1498
- fromLifecycle: null,
1499
- toLifecycle: "open",
1500
- actorId: "system",
1501
- actorKind: "system",
1502
- timestamp: report.reviewedAt
1503
- };
1504
- const lines = JSON.stringify({ __report: 1, data: report }) + NL2 + JSON.stringify({ __reportEvent: 1, data: createdEvent }) + NL2;
1505
- await fsp39.appendFile(this.filePath, lines, { encoding: "utf8", mode: SECRET_FILE_MODE });
1506
- return report;
1507
- });
1508
- }
1509
- // ── Lifecycle transitions ────────────────────────────────────────
1510
- async transition(reportId, to, actor, opts) {
1511
- return withFileLock(this.filePath, async () => {
1512
- const all = await this._readAll();
1513
- const entry = all.find((e) => e.report.id === reportId);
1514
- if (!entry) throw new Error(`Review report not found: ${reportId}`);
1515
- const from = this._materialize(entry).lifecycle;
1516
- validateReportTransition(from, to);
1517
- const event = {
1518
- id: randomUUID37(),
1519
- reportId,
1520
- eventType: reportEventTypeFor(to),
1521
- fromLifecycle: from,
1522
- toLifecycle: to,
1523
- actorId: actor.id,
1524
- actorKind: actor.kind,
1525
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1526
- reason: opts?.reason
1527
- };
1528
- entry.report.lifecycle = to;
1529
- await fsp39.appendFile(this.filePath, JSON.stringify({ __reportEvent: 1, data: event }) + NL2, {
1530
- encoding: "utf8",
1531
- mode: SECRET_FILE_MODE
1532
- });
1533
- return { ...entry.report };
1534
- });
1535
- }
1536
- async addNote(reportId, actor, note) {
1537
- return withFileLock(this.filePath, async () => {
1538
- const all = await this._readAll();
1539
- const entry = all.find((e) => e.report.id === reportId);
1540
- if (!entry) throw new Error(`Review report not found: ${reportId}`);
1541
- const materialized = this._materialize(entry);
1542
- const event = {
1543
- id: randomUUID37(),
1544
- reportId,
1545
- eventType: "note_added",
1546
- fromLifecycle: materialized.lifecycle,
1547
- toLifecycle: materialized.lifecycle,
1548
- actorId: actor.id,
1549
- actorKind: actor.kind,
1550
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1551
- reason: note
1552
- };
1553
- await fsp39.appendFile(this.filePath, JSON.stringify({ __reportEvent: 1, data: event }) + NL2, {
1554
- encoding: "utf8",
1555
- mode: SECRET_FILE_MODE
1556
- });
1557
- return materialized;
1558
- });
1559
- }
1560
- // ── Query ────────────────────────────────────────────────────────
1561
- async list(opts) {
1562
- const all = await this._readAll();
1563
- let reports = all.map((e) => this._materialize(e));
1564
- if (opts?.statuses && opts.statuses.length > 0) {
1565
- const set = new Set(opts.statuses);
1566
- reports = reports.filter((r) => set.has(r.lifecycle));
1567
- }
1568
- if (opts?.sources && opts.sources.length > 0) {
1569
- const set = new Set(opts.sources);
1570
- reports = reports.filter((r) => set.has(r.source));
1571
- }
1572
- reports.sort((a, b) => b.reviewedAt.localeCompare(a.reviewedAt));
1573
- const limit = opts?.limit ?? REPORT_DEFAULT_PAGE_SIZE;
1574
- return reports.slice(0, limit);
1575
- }
1576
- async get(id) {
1577
- const all = await this._readAll();
1578
- const entry = all.find((e) => e.report.id === id);
1579
- return entry ? this._materialize(entry) : null;
1580
- }
1581
- async getEvents(reportId) {
1582
- const all = await this._readAllLines();
1583
- const events = [];
1584
- for (const line of all) {
1585
- try {
1586
- const parsed = JSON.parse(line);
1587
- if (this._isEventRecord(parsed) && parsed.data.reportId === reportId) {
1588
- events.push(parsed.data);
1589
- }
1590
- } catch {
1591
- }
1592
- }
1593
- return events.sort((a, b) => a.timestamp.localeCompare(b.timestamp));
1594
- }
1595
- // ── Compaction ───────────────────────────────────────────────────
1596
- async compact(opts) {
1597
- return withFileLock(this.filePath, async () => {
1598
- const maxAge = opts?.maxAgeMs ?? REPORT_RETENTION_MS;
1599
- const now = Date.now();
1600
- const all = await this._readAll();
1601
- const kept = [];
1602
- let removed = 0;
1603
- let eventsFolded = 0;
1604
- for (const entry of all) {
1605
- const age = now - new Date(entry.report.reviewedAt).getTime();
1606
- const materialized = this._materialize(entry);
1607
- const isTerminal = materialized.lifecycle === "completed" || materialized.lifecycle === "skipped";
1608
- if (isTerminal && age > maxAge) {
1609
- removed++;
1610
- eventsFolded += entry.events.length;
1611
- continue;
1612
- }
1613
- const oldEvents = entry.events.filter(
1614
- (ev) => now - new Date(ev.timestamp).getTime() > maxAge
1615
- );
1616
- if (oldEvents.length > 1) {
1617
- eventsFolded += oldEvents.length - 1;
1618
- const newestOld = oldEvents.sort((a, b) => b.timestamp.localeCompare(a.timestamp))[0];
1619
- entry.events = [
1620
- newestOld,
1621
- ...entry.events.filter((ev) => now - new Date(ev.timestamp).getTime() <= maxAge)
1622
- ];
1623
- }
1624
- kept.push({ record: { __report: 1, data: entry.report }, events: entry.events });
1625
- }
1626
- const lines = [];
1627
- for (const { record, events } of kept) {
1628
- if (record) lines.push(JSON.stringify(record));
1629
- for (const ev of events) {
1630
- lines.push(JSON.stringify({ __reportEvent: 1, data: ev }));
1631
- }
1632
- }
1633
- lines.push(
1634
- JSON.stringify({
1635
- __reportCompact: 1,
1636
- compactedAt: (/* @__PURE__ */ new Date()).toISOString(),
1637
- removedReports: removed,
1638
- foldedEvents: eventsFolded
1639
- })
1640
- );
1641
- await atomicWrite(this.filePath, lines.join(NL2) + NL2, { mode: 384 });
1642
- return { removed, eventsFolded };
1643
- });
1644
- }
1645
- // ── Private helpers ──────────────────────────────────────────────
1646
- async _readAllLines() {
1647
- try {
1648
- const raw = await fsp39.readFile(this.filePath, "utf8");
1649
- return raw.split(NL2).filter((l) => l.trim().length > 0);
1650
- } catch (err) {
1651
- if (err.code === "ENOENT") return [];
1652
- throw err;
1653
- }
1654
- }
1655
- _isReportRecord(v) {
1656
- return typeof v === "object" && v !== null && v["__report"] === 1;
1657
- }
1658
- _isEventRecord(v) {
1659
- return typeof v === "object" && v !== null && v["__reportEvent"] === 1;
1660
- }
1661
- async _readAll() {
1662
- const lines = await this._readAllLines();
1663
- const reports = /* @__PURE__ */ new Map();
1664
- const eventsMap = /* @__PURE__ */ new Map();
1665
- for (const line of lines) {
1666
- try {
1667
- const parsed = JSON.parse(line);
1668
- if (this._isReportRecord(parsed)) {
1669
- reports.set(parsed.data.id, parsed.data);
1670
- if (!eventsMap.has(parsed.data.id)) eventsMap.set(parsed.data.id, []);
1671
- } else if (this._isEventRecord(parsed)) {
1672
- const list = eventsMap.get(parsed.data.reportId);
1673
- if (list) list.push(parsed.data);
1674
- }
1675
- } catch {
1676
- }
1677
- }
1678
- return Array.from(reports.entries()).map(([id, report]) => ({
1679
- report,
1680
- events: eventsMap.get(id) ?? []
1681
- }));
1682
- }
1683
- async _findRecord(id) {
1684
- const all = await this._readAll();
1685
- const entry = all.find((e) => e.report.id === id);
1686
- return entry ? this._materialize(entry) : null;
1687
- }
1688
- _materialize(entry) {
1689
- if (entry.events.length === 0) return { ...entry.report };
1690
- const sorted = [...entry.events].sort((a, b) => a.timestamp.localeCompare(b.timestamp));
1691
- const latest = sorted[sorted.length - 1];
1692
- if (latest.eventType !== "note_added") {
1693
- return { ...entry.report, lifecycle: latest.toLifecycle };
1694
- }
1695
- return { ...entry.report };
1696
- }
1697
- };
1698
- }
1699
- });
1700
-
1701
1701
  // src/boot.ts
1702
1702
  import * as fs9 from "node:fs/promises";
1703
1703
  import * as os3 from "node:os";
@@ -3524,7 +3524,7 @@ var CONFIG_BEHAVIOR_DEFAULTS = {
3524
3524
  // DEFAULT_STATUSLINE_MODE (packages/tui/src/components/settings-picker-model.ts).
3525
3525
  statuslineMode: "minimum",
3526
3526
  thinkingWord: DEFAULT_TUI_THINKING_WORD,
3527
- showAgentSwarmPanel: true
3527
+ showAgentSwarmPanel: "bottom"
3528
3528
  },
3529
3529
  circuitBreaker: { ...DEFAULT_CIRCUIT_BREAKER_CONFIG },
3530
3530
  modelRuntime: {
@@ -4014,13 +4014,32 @@ function inlineDefinition(model) {
4014
4014
  if (maxOutput !== void 0) definition.maxOutput = maxOutput;
4015
4015
  const capabilities = readCapabilities(model);
4016
4016
  if (capabilities) definition.capabilities = capabilities;
4017
+ const { id: _id, ...rest } = model;
4018
+ if (Object.keys(rest).length > 0) {
4019
+ definition.modelsDev = rest;
4020
+ }
4017
4021
  return definition;
4018
4022
  }
4019
4023
  function mergeDefinitions(base, patch) {
4024
+ const mergedModelsDev = (() => {
4025
+ if (!base?.modelsDev && !patch.modelsDev) return void 0;
4026
+ const baseMd = base?.modelsDev ?? {};
4027
+ const patchMd = patch.modelsDev ?? {};
4028
+ const result = { ...baseMd, ...patchMd };
4029
+ for (const nestedKey of ["limit", "cost", "modalities"]) {
4030
+ const bn = baseMd?.[nestedKey];
4031
+ const pn = patchMd?.[nestedKey];
4032
+ if (bn || pn) {
4033
+ result[nestedKey] = { ...bn ?? {}, ...pn ?? {} };
4034
+ }
4035
+ }
4036
+ return result;
4037
+ })();
4020
4038
  return {
4021
4039
  ...base,
4022
4040
  ...patch,
4023
- ...base?.capabilities || patch.capabilities ? { capabilities: { ...base?.capabilities, ...patch.capabilities } } : {}
4041
+ ...base?.capabilities || patch.capabilities ? { capabilities: { ...base?.capabilities, ...patch.capabilities } } : {},
4042
+ ...mergedModelsDev ? { modelsDev: mergedModelsDev } : {}
4024
4043
  };
4025
4044
  }
4026
4045
  function normalizeInlineProviderModels(config, warn) {
@@ -15021,6 +15040,49 @@ function quoteWin32CmdArg(arg) {
15021
15040
  return `"${arg}"`;
15022
15041
  }
15023
15042
 
15043
+ // src/utils/sage-output-block.ts
15044
+ var SAGE_INJECTOR_HEADINGS = /* @__PURE__ */ new Set([
15045
+ "--- SAGE: task-aware project knowledge (Memory Injector) ---",
15046
+ "--- SAGE: related project knowledge (Memory Injector) ---"
15047
+ ]);
15048
+ var SAGE_MEMORY_LINE = /^- \[[^\]]+\](?:\[[^\]]+\])* <memory id="[^"]+">.*<\/memory>(?: .*)?$/;
15049
+ var SAGE_MEMORY_LINE_TRUNCATED = /^- \[[^\]]+\](?:\[[^\]]+\])* <memory id="[^"]+">.*…$/;
15050
+ function splitSageOutputBlock(output) {
15051
+ if (!output.includes("--- SAGE: ")) return { body: output, sageLines: [] };
15052
+ const lines = output.split("\n");
15053
+ for (let sageIdx = lines.length - 1; sageIdx >= 0; sageIdx--) {
15054
+ if (!SAGE_INJECTOR_HEADINGS.has(lines[sageIdx] ?? "")) continue;
15055
+ const candidate = lines.slice(sageIdx);
15056
+ if (candidate.length < 2) continue;
15057
+ const memoryLines = candidate.slice(1).filter((line) => line.trim().length > 0);
15058
+ if (memoryLines.length === 0) continue;
15059
+ const bodyOk = memoryLines.every(
15060
+ (line, index) => SAGE_MEMORY_LINE.test(line) || index === memoryLines.length - 1 && SAGE_MEMORY_LINE_TRUNCATED.test(line)
15061
+ );
15062
+ if (!bodyOk) continue;
15063
+ let end = candidate.length;
15064
+ while (end > 1 && candidate[end - 1].trim().length === 0) end--;
15065
+ return {
15066
+ body: lines.slice(0, sageIdx).join("\n").trimEnd(),
15067
+ sageLines: candidate.slice(0, end)
15068
+ };
15069
+ }
15070
+ return { body: output, sageLines: [] };
15071
+ }
15072
+ function capSageLines(sageLines, maxChars) {
15073
+ if (sageLines.length < 2) return [];
15074
+ const header = sageLines[0];
15075
+ if (header.length >= maxChars) return [];
15076
+ const out = [header];
15077
+ let used = header.length;
15078
+ for (const line of sageLines.slice(1)) {
15079
+ if (used + 1 + line.length > maxChars) break;
15080
+ out.push(line);
15081
+ used += 1 + line.length;
15082
+ }
15083
+ return out.length > 1 ? out : [];
15084
+ }
15085
+
15024
15086
  // src/chronicle/sqlite-query.ts
15025
15087
  var MAX_LIMIT = 1e4;
15026
15088
  function encodeCursor2(cursor) {
@@ -30654,8 +30716,16 @@ function resolveSubagentModelTarget(config, role, opts = {}) {
30654
30716
  const resolution = resolveModelMatrixResolution(config.modelMatrix, role);
30655
30717
  const matrixTarget = resolveModelTargetFromEntry(config, resolution?.entry);
30656
30718
  const implementationTarget = opts.implementationTarget ?? resolveImplementationModelTarget(config);
30719
+ const isAvailable = (providerId, model) => {
30720
+ if (!opts.statusTracker) return true;
30721
+ if (!providerId || !model) return true;
30722
+ return opts.statusTracker.isAvailable(providerId, model);
30723
+ };
30724
+ const matrixEffective = materializeTarget(config, matrixTarget);
30725
+ const matrixIsAvailable = !matrixEffective || isAvailable(matrixEffective.provider, matrixEffective.model);
30657
30726
  if (!roleNeedsIndependentReviewModel(role)) {
30658
30727
  if (!matrixTarget) return void 0;
30728
+ if (!matrixIsAvailable) return void 0;
30659
30729
  return {
30660
30730
  ...matrixTarget,
30661
30731
  source: "matrix",
@@ -30664,16 +30734,27 @@ function resolveSubagentModelTarget(config, role, opts = {}) {
30664
30734
  }
30665
30735
  const matrixRef = materializeTarget(config, matrixTarget);
30666
30736
  if (resolution?.source === "role" || resolution?.source === "phase") {
30667
- return matrixTarget ? { ...matrixTarget, source: "matrix", matrixSource: resolution.source } : void 0;
30737
+ if (!matrixTarget) return void 0;
30738
+ if (!matrixIsAvailable) return void 0;
30739
+ return {
30740
+ ...matrixTarget,
30741
+ source: "matrix",
30742
+ matrixSource: resolution.source
30743
+ };
30668
30744
  }
30669
30745
  if (matrixRef && !sameModelReference(matrixRef, implementationTarget)) {
30746
+ if (!matrixIsAvailable) return void 0;
30670
30747
  return {
30671
30748
  ...matrixTarget ?? {},
30672
30749
  source: "matrix",
30673
30750
  matrixSource: resolution?.source
30674
30751
  };
30675
30752
  }
30676
- const diverse = chooseDiverseModelTarget(config, implementationTarget);
30753
+ const diverse = chooseDiverseModelTargetWithTracker(
30754
+ config,
30755
+ implementationTarget,
30756
+ opts.statusTracker
30757
+ );
30677
30758
  if (diverse) {
30678
30759
  return {
30679
30760
  provider: diverse.provider,
@@ -30687,6 +30768,7 @@ function resolveSubagentModelTarget(config, role, opts = {}) {
30687
30768
  };
30688
30769
  }
30689
30770
  if (!matrixTarget) return void 0;
30771
+ if (!matrixIsAvailable) return void 0;
30690
30772
  return {
30691
30773
  ...matrixTarget,
30692
30774
  source: "matrix",
@@ -30723,6 +30805,14 @@ function chooseDiverseModelTarget(config, avoid) {
30723
30805
  candidates.sort((a, b) => modelDiversityScore(b, avoid) - modelDiversityScore(a, avoid));
30724
30806
  return candidates[0];
30725
30807
  }
30808
+ function chooseDiverseModelTargetWithTracker(config, avoid, tracker) {
30809
+ if (!tracker) return chooseDiverseModelTarget(config, avoid);
30810
+ const ranked = collectConfiguredModelTargets(config).filter((candidate) => !sameModelReference(candidate, avoid)).sort((a, b) => modelDiversityScore(b, avoid) - modelDiversityScore(a, avoid));
30811
+ for (const candidate of ranked) {
30812
+ if (tracker.isAvailable(candidate.provider, candidate.model)) return candidate;
30813
+ }
30814
+ return void 0;
30815
+ }
30726
30816
  function collectConfiguredModelTargets(config) {
30727
30817
  const seen = /* @__PURE__ */ new Set();
30728
30818
  const out = [];
@@ -37785,6 +37875,36 @@ function isMailboxLeader(agentId, role) {
37785
37875
  function isMailboxMessageVisibleTo(message, agentId, role) {
37786
37876
  return message.audience !== "leaders" || isMailboxLeader(agentId, role);
37787
37877
  }
37878
+ function isAffectedBySessionAffinity(message) {
37879
+ return message.sessionAffinity !== void 0;
37880
+ }
37881
+ async function acceptMailboxMessageForSession(message, currentSessionId, ctx) {
37882
+ if (!isAffectedBySessionAffinity(message)) return true;
37883
+ const affinity = message.sessionAffinity;
37884
+ if (affinity === null || typeof affinity !== "object" || Array.isArray(affinity)) {
37885
+ return false;
37886
+ }
37887
+ if (affinity.sessionId !== void 0 && typeof affinity.sessionId !== "string" || affinity.reportId !== void 0 && typeof affinity.reportId !== "string") {
37888
+ return false;
37889
+ }
37890
+ if (!currentSessionId) {
37891
+ return ctx?.allowUnscoped === true;
37892
+ }
37893
+ if (typeof affinity.sessionId === "string" && affinity.sessionId.length > 0) {
37894
+ if (affinity.sessionId !== currentSessionId) return false;
37895
+ return true;
37896
+ }
37897
+ if (affinity.reportId && ctx?.resolveChimeraReportSessionId) {
37898
+ try {
37899
+ const resolved = await ctx.resolveChimeraReportSessionId(affinity.reportId);
37900
+ if (resolved === currentSessionId) return true;
37901
+ if (resolved !== void 0) return false;
37902
+ } catch {
37903
+ }
37904
+ }
37905
+ if (ctx?.allowUnscoped === true) return true;
37906
+ return false;
37907
+ }
37788
37908
  function validateSendType(type, to) {
37789
37909
  if (type === "control") {
37790
37910
  throw new TypeError(
@@ -38409,7 +38529,7 @@ function makeMailboxTool(opts = {}) {
38409
38529
  case "ack":
38410
38530
  return executeAck(mb, callerId, i);
38411
38531
  case "query":
38412
- return executeQuery(mb, callerId, identity2.role, i);
38532
+ return executeQuery(mb, callerId, callerSessionId, identity2.role, i);
38413
38533
  case "status":
38414
38534
  return executeStatus(mb);
38415
38535
  case "online":
@@ -38438,12 +38558,18 @@ async function executeCheck(mb, agentId, sessionId, aliases, role, i) {
38438
38558
  )
38439
38559
  );
38440
38560
  const seen = /* @__PURE__ */ new Set();
38441
- const messages = batches.flat().filter((m) => {
38561
+ const candidates = batches.flat().filter((m) => {
38442
38562
  if (seen.has(m.id)) return false;
38443
38563
  if (!isMailboxMessageVisibleTo(m, agentId, role)) return false;
38444
38564
  seen.add(m.id);
38445
38565
  return true;
38446
38566
  });
38567
+ const messages = [];
38568
+ for (const m of candidates) {
38569
+ if (await acceptMailboxMessageForSession(m, sessionId)) {
38570
+ messages.push(m);
38571
+ }
38572
+ }
38447
38573
  const acked = markRead || completed ? await mb.ackMany({
38448
38574
  acks: messages.map((m) => ({
38449
38575
  messageId: m.id,
@@ -38524,7 +38650,7 @@ async function executeAck(mb, agentId, i) {
38524
38650
  summary: `Message ${messageId} acknowledged. Read by ${Object.keys(updated.readBy).length} agent(s), Completed: ${updated.completed}.`
38525
38651
  };
38526
38652
  }
38527
- async function executeQuery(mb, agentId, role, i) {
38653
+ async function executeQuery(mb, agentId, sessionId, role, i) {
38528
38654
  const limit = i.limit ?? 50;
38529
38655
  const messages = await mb.query({
38530
38656
  to: i.to,
@@ -38537,7 +38663,12 @@ async function executeQuery(mb, agentId, role, i) {
38537
38663
  sessionId: i.sessionId,
38538
38664
  limit
38539
38665
  });
38540
- const visible = messages.filter((message) => isMailboxMessageVisibleTo(message, agentId, role));
38666
+ const visible = [];
38667
+ for (const message of messages) {
38668
+ if (isMailboxMessageVisibleTo(message, agentId, role) && await acceptMailboxMessageForSession(message, sessionId)) {
38669
+ visible.push(message);
38670
+ }
38671
+ }
38541
38672
  return { ok: true, count: visible.length, messages: visible, summary: `${visible.length} message(s).` };
38542
38673
  }
38543
38674
  async function executeStatus(mb) {
@@ -39968,6 +40099,14 @@ var SEND_ALLOWED_FIELDS = /* @__PURE__ */ new Set([
39968
40099
  "senderSessionId",
39969
40100
  "ttlMs",
39970
40101
  "taskContext"
40102
+ // 'sessionAffinity' is NOT in the allow-list. The trust-line contract:
40103
+ // session-affinity tokens are stamped ONLY by trusted internal callers
40104
+ // (chimera/auto-review pipelines) that call `mailbox.send()` directly
40105
+ // and never go through this boundary codec. Allowing the field at the
40106
+ // boundary would let any actor with `mail.send.informational` stamp
40107
+ // another session's id and bypass the receiver-side filter. The
40108
+ // receiver trusts the sender-asserted `sessionId`; the boundary must
40109
+ // refuse the field entirely.
39971
40110
  ]);
39972
40111
  var ACK_ALLOWED_FIELDS = /* @__PURE__ */ new Set([
39973
40112
  "messageId",
@@ -40004,7 +40143,15 @@ function parseMailboxSendInput(payload, actor) {
40004
40143
  const priority = validatePriority(rawPriority);
40005
40144
  const audience = validateAudience(payload["audience"]);
40006
40145
  const replyTo = optionalString2(payload, "replyTo", "send");
40007
- return { to, type: typeResult.type, subject: subject2, body, priority, audience, replyTo };
40146
+ return {
40147
+ to,
40148
+ type: typeResult.type,
40149
+ subject: subject2,
40150
+ body,
40151
+ priority,
40152
+ audience,
40153
+ replyTo
40154
+ };
40008
40155
  }
40009
40156
  function parseMailboxQueryInput(payload, actor) {
40010
40157
  assertCapability(actor, "mail.read.self", "query");
@@ -48740,6 +48887,7 @@ function createHqPublisherFromEnv(options) {
48740
48887
 
48741
48888
  // src/mailbox-attach.ts
48742
48889
  init_error();
48890
+ init_review_report_store();
48743
48891
 
48744
48892
  // src/core/mailbox-loop.ts
48745
48893
  init_error();
@@ -49122,9 +49270,43 @@ function attachMailboxCheckerInner(a, source) {
49122
49270
  agentId: () => ensureRegistered(),
49123
49271
  role: () => resolveMailboxIdentity(a.ctx).role,
49124
49272
  aliases: [baseIdOf()],
49125
- sessionId: () => a.ctx.session.id
49273
+ sessionId: () => a.ctx.session.id,
49274
+ // ACK is deferred to the session-affinity wrapper so messages dropped
49275
+ // by the filter are NOT marked as read by this agent.
49276
+ ack: false
49126
49277
  };
49127
49278
  const checkMailbox2 = createMailboxChecker(mailboxCheckerOptions);
49279
+ const reportStore = new JsonlReportStore(
49280
+ resolveProjectDir(a.ctx.projectRoot, wstackGlobalRoot())
49281
+ );
49282
+ const sessionAffinityCtx = {
49283
+ resolveChimeraReportSessionId: async (reportId) => (await reportStore.get(reportId))?.sessionId
49284
+ };
49285
+ const applySessionAffinityFilter = (checker, ack) => {
49286
+ return async () => {
49287
+ const currentSessionId = a.ctx.session.id;
49288
+ const agentId = ack ? ensureRegistered() : null;
49289
+ const messages = await checker();
49290
+ const filtered = [];
49291
+ for (const m of messages) {
49292
+ if (await acceptMailboxMessageForSession(m, currentSessionId, sessionAffinityCtx)) {
49293
+ filtered.push(m);
49294
+ }
49295
+ }
49296
+ if (ack && filtered.length > 0 && agentId) {
49297
+ void getMailbox().ackMany({
49298
+ acks: filtered.map((m) => ({
49299
+ messageId: m.id,
49300
+ readerId: agentId,
49301
+ read: true
49302
+ }))
49303
+ }).catch(() => {
49304
+ });
49305
+ }
49306
+ return filtered;
49307
+ };
49308
+ };
49309
+ const sessionScopedCheckMailbox = applySessionAffinityFilter(checkMailbox2, true);
49128
49310
  const checkMailboxAwareness = createMailboxChecker({
49129
49311
  ...mailboxCheckerOptions,
49130
49312
  // Exclude out-of-band types (control) from awareness polling.
@@ -49134,6 +49316,7 @@ function attachMailboxCheckerInner(a, source) {
49134
49316
  include: (m) => !MAILBOX_TYPE_PROPERTIES[m.type]?.outOfBand,
49135
49317
  ack: false
49136
49318
  });
49319
+ const sessionScopedCheckMailboxAwareness = applySessionAffinityFilter(checkMailboxAwareness, false);
49137
49320
  const AWARENESS_FALLBACK_INTERVAL_MS = MAILBOX_AWARENESS_INTERVAL_MS;
49138
49321
  const AWARENESS_PUSH_DEBOUNCE_MS = 500;
49139
49322
  let pollInFlight = false;
@@ -49143,7 +49326,7 @@ function attachMailboxCheckerInner(a, source) {
49143
49326
  if (awarenessDisposed || pollInFlight) return;
49144
49327
  pollInFlight = true;
49145
49328
  try {
49146
- const messages = await checkMailboxAwareness();
49329
+ const messages = await sessionScopedCheckMailboxAwareness();
49147
49330
  if (!awarenessDisposed && messages.length > 0 && a.ctx.meta["coordinationContextMode"] !== "background") {
49148
49331
  setBtwNote(a.ctx, buildMailboxBtwAwarenessBlock(messages).text);
49149
49332
  }
@@ -49176,7 +49359,7 @@ function attachMailboxCheckerInner(a, source) {
49176
49359
  if (pushDebounceTimer !== null) clearTimeout(pushDebounceTimer);
49177
49360
  pushUnsub?.();
49178
49361
  });
49179
- return checkMailbox2;
49362
+ return sessionScopedCheckMailbox;
49180
49363
  }
49181
49364
  function attachFleetPulse(a, cfg) {
49182
49365
  if (!a.ctx.projectRoot || cfg?.enabled === false) {
@@ -49267,7 +49450,7 @@ function requestLimitExtension(opts) {
49267
49450
  init_errors();
49268
49451
 
49269
49452
  // src/core/streaming-response-builder.ts
49270
- import { randomUUID as randomUUID28 } from "node:crypto";
49453
+ import { randomUUID as randomUUID29 } from "node:crypto";
49271
49454
  var STREAM_DRAIN_TIMEOUT_MS = 500;
49272
49455
  function buildResponse(state) {
49273
49456
  const content = [];
@@ -49327,7 +49510,7 @@ function handleContentBlockStart(state, ev) {
49327
49510
  state.textBuffers.push("");
49328
49511
  state.blockOrder.push({ kind: "text", idx: state.currentTextIndex });
49329
49512
  } else if (kind === "tool_use") {
49330
- const id = ev.id ?? randomUUID28();
49513
+ const id = ev.id ?? randomUUID29();
49331
49514
  state.tools.set(id, { name: ev.name ?? "unknown", partial: "" });
49332
49515
  state.blockOrder.push({ kind: "tool", id });
49333
49516
  state.currentTextIndex = -1;
@@ -49562,7 +49745,7 @@ async function streamProviderToResponse(provider, req, signal, ctx, events, logg
49562
49745
 
49563
49746
  // src/observability/network-telemetry.ts
49564
49747
  import { AsyncLocalStorage } from "node:async_hooks";
49565
- import { createHash as createHash23, randomUUID as randomUUID29 } from "node:crypto";
49748
+ import { createHash as createHash23, randomUUID as randomUUID30 } from "node:crypto";
49566
49749
  import { channel } from "node:diagnostics_channel";
49567
49750
  var storage = new AsyncLocalStorage();
49568
49751
  var requests = /* @__PURE__ */ new WeakMap();
@@ -49599,7 +49782,7 @@ function subscribe() {
49599
49782
  const requestBytes = numberField2(request, "contentLength");
49600
49783
  const state = {
49601
49784
  ...context,
49602
- requestId: randomUUID29(),
49785
+ requestId: randomUUID30(),
49603
49786
  ...target,
49604
49787
  ...requestBytes !== void 0 ? { requestBytes } : {},
49605
49788
  startedAt,
@@ -49734,7 +49917,7 @@ function hash3(value) {
49734
49917
  }
49735
49918
 
49736
49919
  // src/core/provider-runner.ts
49737
- import { randomUUID as randomUUID30 } from "node:crypto";
49920
+ import { randomUUID as randomUUID31 } from "node:crypto";
49738
49921
  function providerLogCtx(p, r) {
49739
49922
  return {
49740
49923
  providerId: p.id,
@@ -49746,11 +49929,11 @@ function providerLogCtx(p, r) {
49746
49929
  }
49747
49930
  async function runProviderWithRetry(opts) {
49748
49931
  const { provider, request, signal, ctx, events, retry, logger, tracer } = opts;
49749
- const logicalRequestId = randomUUID30();
49932
+ const logicalRequestId = randomUUID31();
49750
49933
  const promptManifest = createChroniclePromptManifest(request);
49751
49934
  let attempt = 0;
49752
49935
  for (; ; ) {
49753
- const attemptId = randomUUID30();
49936
+ const attemptId = randomUUID31();
49754
49937
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
49755
49938
  const startedNs = process.hrtime.bigint();
49756
49939
  const correlation = {
@@ -50667,51 +50850,6 @@ ${text2}` : text2;
50667
50850
 
50668
50851
  // src/core/agent-tools.ts
50669
50852
  init_error();
50670
-
50671
- // src/utils/sage-output-block.ts
50672
- var SAGE_INJECTOR_HEADINGS = /* @__PURE__ */ new Set([
50673
- "--- SAGE: task-aware project knowledge (Memory Injector) ---",
50674
- "--- SAGE: related project knowledge (Memory Injector) ---"
50675
- ]);
50676
- var SAGE_MEMORY_LINE = /^- \[[^\]]+\](?:\[[^\]]+\])* <memory id="[^"]+">.*<\/memory>(?: .*)?$/;
50677
- var SAGE_MEMORY_LINE_TRUNCATED = /^- \[[^\]]+\](?:\[[^\]]+\])* <memory id="[^"]+">.*…$/;
50678
- function splitSageOutputBlock(output) {
50679
- if (!output.includes("--- SAGE: ")) return { body: output, sageLines: [] };
50680
- const lines = output.split("\n");
50681
- for (let sageIdx = lines.length - 1; sageIdx >= 0; sageIdx--) {
50682
- if (!SAGE_INJECTOR_HEADINGS.has(lines[sageIdx] ?? "")) continue;
50683
- const candidate = lines.slice(sageIdx);
50684
- if (candidate.length < 2) continue;
50685
- const memoryLines = candidate.slice(1).filter((line) => line.trim().length > 0);
50686
- if (memoryLines.length === 0) continue;
50687
- const bodyOk = memoryLines.every(
50688
- (line, index) => SAGE_MEMORY_LINE.test(line) || index === memoryLines.length - 1 && SAGE_MEMORY_LINE_TRUNCATED.test(line)
50689
- );
50690
- if (!bodyOk) continue;
50691
- let end = candidate.length;
50692
- while (end > 1 && candidate[end - 1].trim().length === 0) end--;
50693
- return {
50694
- body: lines.slice(0, sageIdx).join("\n").trimEnd(),
50695
- sageLines: candidate.slice(0, end)
50696
- };
50697
- }
50698
- return { body: output, sageLines: [] };
50699
- }
50700
- function capSageLines(sageLines, maxChars) {
50701
- if (sageLines.length < 2) return [];
50702
- const header = sageLines[0];
50703
- if (header.length >= maxChars) return [];
50704
- const out = [header];
50705
- let used = header.length;
50706
- for (const line of sageLines.slice(1)) {
50707
- if (used + 1 + line.length > maxChars) break;
50708
- out.push(line);
50709
- used += 1 + line.length;
50710
- }
50711
- return out.length > 1 ? out : [];
50712
- }
50713
-
50714
- // src/core/agent-tools.ts
50715
50853
  var DIFF_TOOL_NAMES = /* @__PURE__ */ new Set(["edit", "write", "replace", "patch", "diff"]);
50716
50854
  var DIFF_TOOL_EVENT_PREVIEW_MAX = 16e3;
50717
50855
  var SAGE_EVENT_MAX = 2e3;
@@ -53274,7 +53412,7 @@ var AutonomousRunner = class {
53274
53412
  // src/storage/goal-store.ts
53275
53413
  init_atomic_write();
53276
53414
  init_error();
53277
- import * as fsp28 from "node:fs/promises";
53415
+ import * as fsp29 from "node:fs/promises";
53278
53416
  init_errors();
53279
53417
  var MAX_JOURNAL_ENTRIES = 500;
53280
53418
  function goalFilePath(projectRoot) {
@@ -53284,7 +53422,7 @@ async function loadGoal(filePath, events, warn) {
53284
53422
  const t0 = Date.now();
53285
53423
  let raw;
53286
53424
  try {
53287
- raw = await fsp28.readFile(filePath, "utf8");
53425
+ raw = await fsp29.readFile(filePath, "utf8");
53288
53426
  } catch (err) {
53289
53427
  const code = err.code;
53290
53428
  if (code === "ENOENT") {
@@ -53593,7 +53731,7 @@ var KNOWN_TOKEN_NAMES = Object.values(KNOWN_TOKEN_GROUPS).flat();
53593
53731
  // src/execution/design-kit-loader.ts
53594
53732
  import { existsSync as existsSync7 } from "node:fs";
53595
53733
  import * as fs28 from "node:fs/promises";
53596
- import * as path67 from "node:path";
53734
+ import * as path68 from "node:path";
53597
53735
  import { fileURLToPath as fileURLToPath7 } from "node:url";
53598
53736
  var KIT_FILE = "KIT.md";
53599
53737
  var TOKENS_FILE = "tokens.json";
@@ -53699,7 +53837,7 @@ var DefaultDesignKitLoader = class {
53699
53837
  }
53700
53838
  for (const e of entries) {
53701
53839
  if (!e.isDirectory()) continue;
53702
- const kitFile = path67.join(dir, e.name, KIT_FILE);
53840
+ const kitFile = path68.join(dir, e.name, KIT_FILE);
53703
53841
  try {
53704
53842
  const raw = await fs28.readFile(kitFile, "utf8");
53705
53843
  const fm = parseKitFrontmatter(raw);
@@ -53774,7 +53912,7 @@ var DefaultDesignKitLoader = class {
53774
53912
  const m = await this.find(id);
53775
53913
  let tokens;
53776
53914
  if (m) {
53777
- const tokensPath = path67.join(path67.dirname(m.path), TOKENS_FILE);
53915
+ const tokensPath = path68.join(path68.dirname(m.path), TOKENS_FILE);
53778
53916
  try {
53779
53917
  const raw = await fs28.readFile(tokensPath, "utf8");
53780
53918
  tokens = JSON.parse(raw);
@@ -53836,12 +53974,12 @@ function mergeKitTokens(base, own) {
53836
53974
  }
53837
53975
  function resolveBundledDesignKitsDir() {
53838
53976
  try {
53839
- const here = path67.dirname(fileURLToPath7(import.meta.url));
53977
+ const here = path68.dirname(fileURLToPath7(import.meta.url));
53840
53978
  const candidates = [
53841
- path67.join(here, "design-kits"),
53842
- path67.join(here, "..", "design-kits"),
53843
- path67.join(here, "..", "..", "design-kits"),
53844
- path67.join(here, "..", "..", "..", "design-kits")
53979
+ path68.join(here, "design-kits"),
53980
+ path68.join(here, "..", "design-kits"),
53981
+ path68.join(here, "..", "..", "design-kits"),
53982
+ path68.join(here, "..", "..", "..", "design-kits")
53845
53983
  ];
53846
53984
  for (const c of candidates) {
53847
53985
  if (existsSync7(c)) return c;
@@ -53880,10 +54018,10 @@ function _resetDesignKitLoaderMemo() {
53880
54018
  // src/execution/design-project-store.ts
53881
54019
  import { existsSync as existsSync8 } from "node:fs";
53882
54020
  import * as fs29 from "node:fs/promises";
53883
- import * as path68 from "node:path";
54021
+ import * as path69 from "node:path";
53884
54022
  var DESIGN_DIR = ".design";
53885
54023
  function designProjectDir(projectRoot) {
53886
- return path68.join(projectRoot, DESIGN_DIR);
54024
+ return path69.join(projectRoot, DESIGN_DIR);
53887
54025
  }
53888
54026
  var RULE_FILES = ["rules.md", "RULES.md", "design.md"];
53889
54027
  var rulesCache = /* @__PURE__ */ new Map();
@@ -53898,7 +54036,7 @@ async function loadProjectDesignRules(projectRoot) {
53898
54036
  let rules;
53899
54037
  for (const name of RULE_FILES) {
53900
54038
  try {
53901
- const txt = await fs29.readFile(path68.join(designProjectDir(projectRoot), name), "utf8");
54039
+ const txt = await fs29.readFile(path69.join(designProjectDir(projectRoot), name), "utf8");
53902
54040
  if (txt.trim()) {
53903
54041
  rules = txt.trim();
53904
54042
  break;
@@ -53924,7 +54062,7 @@ function parseOverrides(value) {
53924
54062
  }
53925
54063
  async function loadActiveKit(projectRoot) {
53926
54064
  try {
53927
- const raw = await fs29.readFile(path68.join(designProjectDir(projectRoot), "active.json"), "utf8");
54065
+ const raw = await fs29.readFile(path69.join(designProjectDir(projectRoot), "active.json"), "utf8");
53928
54066
  const parsed = JSON.parse(raw);
53929
54067
  if (parsed && typeof parsed.kit === "string") {
53930
54068
  return {
@@ -53958,7 +54096,7 @@ function applyTokenOverrides(tokens, overrides) {
53958
54096
  async function ensureDesignDir(projectRoot) {
53959
54097
  const dir = designProjectDir(projectRoot);
53960
54098
  await fs29.mkdir(dir, { recursive: true });
53961
- const gi = path68.join(dir, ".gitignore");
54099
+ const gi = path69.join(dir, ".gitignore");
53962
54100
  if (!existsSync8(gi)) {
53963
54101
  try {
53964
54102
  await fs29.writeFile(gi, "*\n");
@@ -53972,11 +54110,11 @@ async function recordKitChoice(projectRoot, kit, stack, source, isoTime, overrid
53972
54110
  const dir = await ensureDesignDir(projectRoot);
53973
54111
  const record = { kit, stack: stack ?? null };
53974
54112
  if (overrides && Object.keys(overrides).length > 0) record.overrides = overrides;
53975
- await fs29.writeFile(path68.join(dir, "active.json"), `${JSON.stringify(record, null, 2)}
54113
+ await fs29.writeFile(path69.join(dir, "active.json"), `${JSON.stringify(record, null, 2)}
53976
54114
  `);
53977
54115
  const line = `- ${isoTime} \xB7 kit=${kit}${stack ? ` stack=${stack}` : ""} \xB7 via=${source}
53978
54116
  `;
53979
- await fs29.appendFile(path68.join(dir, "decisions.md"), line);
54117
+ await fs29.appendFile(path69.join(dir, "decisions.md"), line);
53980
54118
  } catch {
53981
54119
  }
53982
54120
  }
@@ -53992,11 +54130,11 @@ async function recordOverrides(projectRoot, patch, isoTime) {
53992
54130
  const dir = await ensureDesignDir(projectRoot);
53993
54131
  const record = { kit: active.kit, stack: active.stack ?? null };
53994
54132
  if (Object.keys(merged).length > 0) record.overrides = merged;
53995
- await fs29.writeFile(path68.join(dir, "active.json"), `${JSON.stringify(record, null, 2)}
54133
+ await fs29.writeFile(path69.join(dir, "active.json"), `${JSON.stringify(record, null, 2)}
53996
54134
  `);
53997
54135
  const keys = Object.keys(patch).join(",");
53998
54136
  await fs29.appendFile(
53999
- path68.join(dir, "decisions.md"),
54137
+ path69.join(dir, "decisions.md"),
54000
54138
  `- ${isoTime} \xB7 kit=${active.kit} \xB7 override=${keys} \xB7 via=set
54001
54139
  `
54002
54140
  );
@@ -54006,7 +54144,7 @@ async function recordOverrides(projectRoot, patch, isoTime) {
54006
54144
  }
54007
54145
  async function clearPersistedActiveKit(projectRoot) {
54008
54146
  try {
54009
- await fs29.rm(path68.join(designProjectDir(projectRoot), "active.json"), { force: true });
54147
+ await fs29.rm(path69.join(designProjectDir(projectRoot), "active.json"), { force: true });
54010
54148
  } catch {
54011
54149
  }
54012
54150
  }
@@ -56924,7 +57062,7 @@ ${summaryText}` : summaryText;
56924
57062
 
56925
57063
  // src/execution/parallel-eternal-engine.ts
56926
57064
  init_error();
56927
- import { randomUUID as randomUUID31 } from "node:crypto";
57065
+ import { randomUUID as randomUUID32 } from "node:crypto";
56928
57066
  var GOAL_COMPLETE_MARKER2 = /^\s*\[goal[_\s-]?complete\]\s*$/im;
56929
57067
  var ParallelEternalEngine = class {
56930
57068
  constructor(opts) {
@@ -57001,7 +57139,7 @@ var ParallelEternalEngine = class {
57001
57139
  this.state = "running";
57002
57140
  await this.persistState("running");
57003
57141
  const config = {
57004
- coordinatorId: `parallel-${randomUUID31().slice(0, 8)}`,
57142
+ coordinatorId: `parallel-${randomUUID32().slice(0, 8)}`,
57005
57143
  maxConcurrent: this.slots,
57006
57144
  doneCondition: { type: "all_tasks_done" }
57007
57145
  };
@@ -57055,7 +57193,7 @@ var ParallelEternalEngine = class {
57055
57193
  }
57056
57194
  if (!this.coordinator) {
57057
57195
  const config = {
57058
- coordinatorId: `parallel-${randomUUID31().slice(0, 8)}`,
57196
+ coordinatorId: `parallel-${randomUUID32().slice(0, 8)}`,
57059
57197
  maxConcurrent: this.slots,
57060
57198
  doneCondition: { type: "all_tasks_done" }
57061
57199
  };
@@ -57141,7 +57279,7 @@ ${recentJournal}` : "No prior iterations.",
57141
57279
  const task = expectDefined(tasks[i]);
57142
57280
  const route = routes[i] ?? null;
57143
57281
  const subagentId = `parallel-${this.iterations}-${i}`;
57144
- const taskId = randomUUID31();
57282
+ const taskId = randomUUID32();
57145
57283
  const personaLine = route ? `Acting agent: ${route.definition.config.name} \u2014 ${route.definition.capability.summary}
57146
57284
  ` : "";
57147
57285
  const spec = {
@@ -57964,7 +58102,7 @@ Summarize the following message range:`;
57964
58102
 
57965
58103
  // src/execution/skill-loader.ts
57966
58104
  import * as fs30 from "node:fs/promises";
57967
- import * as path69 from "node:path";
58105
+ import * as path70 from "node:path";
57968
58106
 
57969
58107
  // src/skills/foreign-sources.ts
57970
58108
  var FOREIGN_SKILL_TOOLS = [
@@ -58034,7 +58172,7 @@ async function entryIsDirectory(dir, entry) {
58034
58172
  if (entry.isDirectory()) return true;
58035
58173
  if (entry.isSymbolicLink()) {
58036
58174
  try {
58037
- return (await fs30.stat(path69.join(dir, entry.name))).isDirectory();
58175
+ return (await fs30.stat(path70.join(dir, entry.name))).isDirectory();
58038
58176
  } catch {
58039
58177
  return false;
58040
58178
  }
@@ -58058,7 +58196,7 @@ var DefaultSkillLoader = class {
58058
58196
  for (const tool of FOREIGN_SKILL_TOOLS) {
58059
58197
  if (!foreignIds.includes(tool.id)) continue;
58060
58198
  dirs.push({
58061
- dir: path69.join(root, "." + tool.id, tool.subdir),
58199
+ dir: path70.join(root, "." + tool.id, tool.subdir),
58062
58200
  source: "foreign",
58063
58201
  originTool: tool.id
58064
58202
  });
@@ -58088,7 +58226,7 @@ var DefaultSkillLoader = class {
58088
58226
  );
58089
58227
  for (const e of entries) {
58090
58228
  if (!await entryIsDirectory(dir, e)) continue;
58091
- const skillFile = path69.join(dir, e.name, "SKILL.md");
58229
+ const skillFile = path70.join(dir, e.name, "SKILL.md");
58092
58230
  let raw;
58093
58231
  try {
58094
58232
  raw = await fs30.readFile(skillFile, "utf8");
@@ -58217,7 +58355,7 @@ var DefaultSkillLoader = class {
58217
58355
  if (cached2 !== void 0) return cached2;
58218
58356
  const m = await this.find(name);
58219
58357
  if (!m) throw new Error(`Skill "${name}" not found`);
58220
- const savePath = path69.join(path69.dirname(m.path), "SKILL.save.md");
58358
+ const savePath = path70.join(path70.dirname(m.path), "SKILL.save.md");
58221
58359
  let result;
58222
58360
  try {
58223
58361
  result = await fs30.readFile(savePath, "utf8");
@@ -58251,13 +58389,13 @@ function parseDescriptionFromText(desc) {
58251
58389
 
58252
58390
  // src/execution/prompt-loader.ts
58253
58391
  import * as fs32 from "node:fs/promises";
58254
- import * as path71 from "node:path";
58392
+ import * as path72 from "node:path";
58255
58393
 
58256
58394
  // src/storage/prompt-store.ts
58257
58395
  init_atomic_write();
58258
58396
  import { createHash as createHash26 } from "node:crypto";
58259
58397
  import * as fs31 from "node:fs/promises";
58260
- import * as path70 from "node:path";
58398
+ import * as path71 from "node:path";
58261
58399
  var SCHEMA_VERSION3 = 2;
58262
58400
  function promptChecksum(content) {
58263
58401
  return createHash26("sha256").update(content, "utf8").digest("hex");
@@ -58326,7 +58464,7 @@ var DefaultPromptStore = class {
58326
58464
  if (!file.endsWith(".json")) continue;
58327
58465
  try {
58328
58466
  const raw = JSON.parse(
58329
- await fs31.readFile(path70.join(this.dir, file), "utf8")
58467
+ await fs31.readFile(path71.join(this.dir, file), "utf8")
58330
58468
  );
58331
58469
  const migrated = migratePromptEntry(raw.entry);
58332
58470
  if (migrated) entries.push(migrated);
@@ -58340,7 +58478,7 @@ var DefaultPromptStore = class {
58340
58478
  );
58341
58479
  }
58342
58480
  async get(id) {
58343
- const file = path70.join(this.dir, `${id}.json`);
58481
+ const file = path71.join(this.dir, `${id}.json`);
58344
58482
  try {
58345
58483
  const raw = JSON.parse(await fs31.readFile(file, "utf8"));
58346
58484
  return migratePromptEntry(raw.entry);
@@ -58350,12 +58488,12 @@ var DefaultPromptStore = class {
58350
58488
  }
58351
58489
  async save(entry) {
58352
58490
  await ensureDir(this.dir);
58353
- const file = path70.join(this.dir, `${entry.id}.json`);
58491
+ const file = path71.join(this.dir, `${entry.id}.json`);
58354
58492
  const raw = { version: SCHEMA_VERSION3, entry };
58355
58493
  await atomicWrite(file, JSON.stringify(raw, null, 2));
58356
58494
  }
58357
58495
  async delete(id) {
58358
- const file = path70.join(this.dir, `${id}.json`);
58496
+ const file = path71.join(this.dir, `${id}.json`);
58359
58497
  try {
58360
58498
  await fs31.unlink(file);
58361
58499
  return true;
@@ -58444,7 +58582,7 @@ var DefaultPromptLoader = class {
58444
58582
  constructor(opts) {
58445
58583
  this.projectStore = typeof opts.paths.inProjectPrompts === "string" ? new DefaultPromptStore(opts.paths.inProjectPrompts) : void 0;
58446
58584
  this.userStore = typeof opts.paths.globalPrompts === "string" ? new DefaultPromptStore(opts.paths.globalPrompts) : void 0;
58447
- this.builtinDir = opts.bundledDir ? path71.join(opts.bundledDir, "prompts") : void 0;
58585
+ this.builtinDir = opts.bundledDir ? path72.join(opts.bundledDir, "prompts") : void 0;
58448
58586
  }
58449
58587
  async list() {
58450
58588
  if (this.cache) return this.cache;
@@ -58576,7 +58714,7 @@ async function walkJson(dir) {
58576
58714
  return out;
58577
58715
  }
58578
58716
  for (const e of entries) {
58579
- const full = path71.join(dir, e.name);
58717
+ const full = path72.join(dir, e.name);
58580
58718
  if (e.isDirectory()) {
58581
58719
  out.push(...await walkJson(full));
58582
58720
  } else if (e.name.endsWith(".json") && e.name !== "index.json" && e.name !== "schema.json") {
@@ -58763,9 +58901,9 @@ function readPolicy(ctx) {
58763
58901
  }
58764
58902
 
58765
58903
  // src/execution/tool-executor.ts
58766
- import { randomUUID as randomUUID33 } from "node:crypto";
58904
+ import { randomUUID as randomUUID34 } from "node:crypto";
58767
58905
  import * as fs34 from "node:fs/promises";
58768
- import * as path74 from "node:path";
58906
+ import * as path75 from "node:path";
58769
58907
  import { isDeepStrictEqual } from "node:util";
58770
58908
 
58771
58909
  // src/observability/process-telemetry.ts
@@ -58857,7 +58995,7 @@ function emit(name, event) {
58857
58995
 
58858
58996
  // src/security/kanban-boundary.ts
58859
58997
  import { realpath as realpath3 } from "node:fs/promises";
58860
- import * as path72 from "node:path";
58998
+ import * as path73 from "node:path";
58861
58999
  import {
58862
59000
  evaluateKanbanBoundaryOpaque,
58863
59001
  evaluateKanbanBoundaryPath,
@@ -58946,10 +59084,10 @@ function resolveKanbanIdentity(ctx) {
58946
59084
  async function extractCandidatePaths(toolName, input, ctx) {
58947
59085
  if (toolName === "patch" && typeof input["patch"] === "string") {
58948
59086
  const directoryInput = stringValue(input["directory"]) ?? ctx.workingDir;
58949
- const directory = path72.isAbsolute(directoryInput) ? directoryInput : path72.resolve(ctx.workingDir, directoryInput);
59087
+ const directory = path73.isAbsolute(directoryInput) ? directoryInput : path73.resolve(ctx.workingDir, directoryInput);
58950
59088
  const strip = Math.max(1, numericValue(input["strip"]) ?? 1);
58951
59089
  const targets = extractPatchTargets(input["patch"], strip).map(
58952
- (target) => relativeToProject(path72.resolve(directory, target), ctx.projectRoot)
59090
+ (target) => relativeToProject(path73.resolve(directory, target), ctx.projectRoot)
58953
59091
  );
58954
59092
  return Promise.all(targets.map((target) => canonicalizeCandidatePath(target, ctx)));
58955
59093
  }
@@ -58959,7 +59097,7 @@ async function extractCandidatePaths(toolName, input, ctx) {
58959
59097
  collectPathValues(input, values, pathKeys);
58960
59098
  if (toolName === "scaffold" && typeof input["name"] === "string") {
58961
59099
  const cwd = stringValue(input["cwd"]) ?? ctx.workingDir;
58962
- values.push(path72.join(cwd, input["name"]));
59100
+ values.push(path73.join(cwd, input["name"]));
58963
59101
  }
58964
59102
  const candidates = [
58965
59103
  ...new Set(values.flatMap(splitPathList).map((value) => resolveInputPath(value, ctx)))
@@ -58967,21 +59105,21 @@ async function extractCandidatePaths(toolName, input, ctx) {
58967
59105
  return Promise.all(candidates.map((candidate) => canonicalizeCandidatePath(candidate, ctx)));
58968
59106
  }
58969
59107
  async function canonicalizeCandidatePath(candidate, ctx) {
58970
- if (path72.isAbsolute(candidate)) return candidate;
58971
- const absolute = path72.resolve(ctx.projectRoot, candidate);
59108
+ if (path73.isAbsolute(candidate)) return candidate;
59109
+ const absolute = path73.resolve(ctx.projectRoot, candidate);
58972
59110
  const canonicalRoot = await realpath3(ctx.projectRoot).catch(() => ctx.projectRoot);
58973
59111
  let probe2 = absolute;
58974
59112
  const missingSegments = [];
58975
59113
  while (true) {
58976
59114
  try {
58977
- const canonical = path72.join(await realpath3(probe2), ...missingSegments);
59115
+ const canonical = path73.join(await realpath3(probe2), ...missingSegments);
58978
59116
  return relativeToProject(canonical, canonicalRoot);
58979
59117
  } catch (cause) {
58980
59118
  const code = cause.code;
58981
59119
  if (code !== "ENOENT" && code !== "ENOTDIR") return absolute;
58982
- const parent = path72.dirname(probe2);
59120
+ const parent = path73.dirname(probe2);
58983
59121
  if (parent === probe2) return absolute;
58984
- missingSegments.unshift(path72.basename(probe2));
59122
+ missingSegments.unshift(path73.basename(probe2));
58985
59123
  probe2 = parent;
58986
59124
  }
58987
59125
  }
@@ -59004,12 +59142,12 @@ function splitPathList(value) {
59004
59142
  return value.split(",").map((item) => item.trim()).filter(Boolean);
59005
59143
  }
59006
59144
  function resolveInputPath(value, ctx) {
59007
- const absolute = path72.isAbsolute(value) ? value : path72.resolve(ctx.workingDir, value);
59145
+ const absolute = path73.isAbsolute(value) ? value : path73.resolve(ctx.workingDir, value);
59008
59146
  return relativeToProject(absolute, ctx.projectRoot);
59009
59147
  }
59010
59148
  function relativeToProject(absolute, projectRoot) {
59011
- const relative19 = path72.relative(projectRoot, absolute).replace(/\\/g, "/");
59012
- return relative19.startsWith("../") || path72.isAbsolute(relative19) ? absolute : relative19 || ".";
59149
+ const relative19 = path73.relative(projectRoot, absolute).replace(/\\/g, "/");
59150
+ return relative19.startsWith("../") || path73.isAbsolute(relative19) ? absolute : relative19 || ".";
59013
59151
  }
59014
59152
  function extractPatchTargets(patchText, strip) {
59015
59153
  const targets = [];
@@ -59041,9 +59179,9 @@ init_error();
59041
59179
  init_errors();
59042
59180
 
59043
59181
  // src/execution/tool-executor-support.ts
59044
- import { createHash as createHash28, randomUUID as randomUUID32 } from "node:crypto";
59182
+ import { createHash as createHash28, randomUUID as randomUUID33 } from "node:crypto";
59045
59183
  import * as fs33 from "node:fs/promises";
59046
- import * as path73 from "node:path";
59184
+ import * as path74 from "node:path";
59047
59185
 
59048
59186
  // src/types/tool.ts
59049
59187
  var ToolErrorCategory = /* @__PURE__ */ ((ToolErrorCategory2) => {
@@ -59181,11 +59319,11 @@ async function maybePersistLargeToolOutput(toolName, content, budget) {
59181
59319
  return content;
59182
59320
  }
59183
59321
  try {
59184
- const dir = path73.join(wstackGlobalRoot(), "tool-output");
59322
+ const dir = path74.join(wstackGlobalRoot(), "tool-output");
59185
59323
  await fs33.mkdir(dir, { recursive: true });
59186
59324
  const safeTool = toolName.replace(/[^a-zA-Z0-9._-]+/g, "_").slice(0, 40) || "tool";
59187
59325
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
59188
- const filePath = path73.join(dir, `${stamp}-${safeTool}-${randomUUID32()}.log`);
59326
+ const filePath = path74.join(dir, `${stamp}-${safeTool}-${randomUUID33()}.log`);
59189
59327
  await fs33.writeFile(filePath, content, "utf8");
59190
59328
  const marker = `[full tool output: ${bytes} bytes at ${filePath}; read/grep that file selectively instead of re-running or requesting more output]`;
59191
59329
  const fixedBytes = Buffer.byteLength(marker + TOOL_OUTPUT_ARTIFACT_OMISSION, "utf8");
@@ -59771,7 +59909,7 @@ ${errorDetails}`,
59771
59909
  const inputPath = use.input && typeof use.input === "object" ? use.input.path : void 0;
59772
59910
  const caps = tool.capabilities ?? [];
59773
59911
  const hasFileCapability = caps.includes("fs.read") || caps.includes("fs.write");
59774
- const absPath = hasFileCapability && typeof inputPath === "string" ? path74.isAbsolute(inputPath) ? inputPath : path74.resolve(ctx.projectRoot, inputPath) : void 0;
59912
+ const absPath = hasFileCapability && typeof inputPath === "string" ? path75.isAbsolute(inputPath) ? inputPath : path75.resolve(ctx.projectRoot, inputPath) : void 0;
59775
59913
  let writeTargetExisted;
59776
59914
  if (tool.name === "write" && caps.includes("fs.write") && absPath) {
59777
59915
  writeTargetExisted = await fs34.stat(absPath).then(
@@ -59926,7 +60064,7 @@ ${post.additionalContext}`;
59926
60064
  const bridge = async (toolName, input) => {
59927
60065
  const nestedUse = {
59928
60066
  type: "tool_use",
59929
- id: `nested-${randomUUID33()}`,
60067
+ id: `nested-${randomUUID34()}`,
59930
60068
  name: toolName,
59931
60069
  input
59932
60070
  };
@@ -60038,7 +60176,7 @@ ${post.additionalContext}`;
60038
60176
  ]);
60039
60177
  let output;
60040
60178
  const execute = () => typeof tool.executeStream === "function" ? this.runStreamedTool(tool, input, ctx, combined, toolUseId) : (async () => tool.execute(input, ctx, { signal: combined }))();
60041
- const telemetryToolCallId = toolUseId ?? `nested-${randomUUID33()}`;
60179
+ const telemetryToolCallId = toolUseId ?? `nested-${randomUUID34()}`;
60042
60180
  const toolPromise = this.opts.events ? runWithNetworkTelemetry(
60043
60181
  {
60044
60182
  events: this.opts.events,
@@ -60591,27 +60729,27 @@ function codexModelMeta(id) {
60591
60729
  // src/models/mode-store.ts
60592
60730
  init_atomic_write();
60593
60731
  import * as fs35 from "node:fs/promises";
60594
- import * as path76 from "node:path";
60732
+ import * as path77 from "node:path";
60595
60733
 
60596
60734
  // src/types/mode-prompts.ts
60597
60735
  import { readFileSync as readFileSync17, statSync as statSync7 } from "node:fs";
60598
- import * as path75 from "node:path";
60736
+ import * as path76 from "node:path";
60599
60737
  import { fileURLToPath as fileURLToPath8 } from "node:url";
60600
60738
  function modePrompt(id) {
60601
60739
  for (const dir of modePromptDirCandidates()) {
60602
60740
  try {
60603
- return readFileSync17(path75.join(dir, `${id}.md`), "utf8").trimEnd();
60741
+ return readFileSync17(path76.join(dir, `${id}.md`), "utf8").trimEnd();
60604
60742
  } catch {
60605
60743
  }
60606
60744
  }
60607
60745
  return "";
60608
60746
  }
60609
60747
  function modePromptDirCandidates() {
60610
- const here = path75.dirname(fileURLToPath8(import.meta.url));
60748
+ const here = path76.dirname(fileURLToPath8(import.meta.url));
60611
60749
  const candidates = [
60612
- path75.resolve(here, "../../instructions/modes"),
60613
- path75.resolve(here, "../instructions/modes"),
60614
- path75.resolve(here, "instructions/modes")
60750
+ path76.resolve(here, "../../instructions/modes"),
60751
+ path76.resolve(here, "../instructions/modes"),
60752
+ path76.resolve(here, "instructions/modes")
60615
60753
  ];
60616
60754
  return candidates.sort((a, b) => Number(!isDirectory4(a)) - Number(!isDirectory4(b)));
60617
60755
  }
@@ -60843,7 +60981,7 @@ var DefaultModeStore = class {
60843
60981
  }
60844
60982
  async loadActiveMode() {
60845
60983
  try {
60846
- const configPath = path76.join(this.configDir, "mode.json");
60984
+ const configPath = path77.join(this.configDir, "mode.json");
60847
60985
  const content = await fs35.readFile(configPath, "utf8");
60848
60986
  const data = JSON.parse(content);
60849
60987
  this.activeModeId = data.activeMode ?? null;
@@ -60854,7 +60992,7 @@ var DefaultModeStore = class {
60854
60992
  async saveActiveMode() {
60855
60993
  try {
60856
60994
  await fs35.mkdir(this.configDir, { recursive: true });
60857
- const configPath = path76.join(this.configDir, "mode.json");
60995
+ const configPath = path77.join(this.configDir, "mode.json");
60858
60996
  await atomicWrite(
60859
60997
  configPath,
60860
60998
  JSON.stringify({ activeMode: this.activeModeId }, null, 2)
@@ -60869,11 +61007,11 @@ async function loadProjectModes(modesDir) {
60869
61007
  const entries = await fs35.readdir(modesDir);
60870
61008
  for (const entry of entries) {
60871
61009
  if (!entry.endsWith(".md") && !entry.endsWith(".txt")) continue;
60872
- const filePath = path76.join(modesDir, entry);
61010
+ const filePath = path77.join(modesDir, entry);
60873
61011
  const stat33 = await fs35.stat(filePath);
60874
61012
  if (!stat33.isFile()) continue;
60875
61013
  const content = await fs35.readFile(filePath, "utf8");
60876
- const id = path76.basename(entry, path76.extname(entry));
61014
+ const id = path77.basename(entry, path77.extname(entry));
60877
61015
  modes.push({
60878
61016
  id,
60879
61017
  name: id.replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()),
@@ -60889,7 +61027,7 @@ async function loadProjectModes(modesDir) {
60889
61027
  async function loadUserModes(modesDir) {
60890
61028
  const modes = [];
60891
61029
  try {
60892
- const manifestPath = path76.join(modesDir, "modes.json");
61030
+ const manifestPath = path77.join(modesDir, "modes.json");
60893
61031
  const content = await fs35.readFile(manifestPath, "utf8");
60894
61032
  const manifest = JSON.parse(content);
60895
61033
  for (const mode of manifest.modes) {
@@ -60902,13 +61040,13 @@ async function loadUserModes(modesDir) {
60902
61040
 
60903
61041
  // src/models/models-registry.ts
60904
61042
  import * as fs36 from "node:fs/promises";
60905
- import * as path77 from "node:path";
61043
+ import * as path78 from "node:path";
60906
61044
  init_atomic_write();
60907
61045
  init_error();
60908
61046
  init_errors();
60909
61047
  var DEFAULT_URL = "https://models.dev/api.json";
60910
61048
  var ENV_URL_KEY = "WRONGSTACK_MODELS_DEV_URL";
60911
- var DEFAULT_TTL_SECONDS = 24 * 3600;
61049
+ var DEFAULT_TTL_SECONDS = 3 * 3600;
60912
61050
  var DEFAULT_REFRESH_TIMEOUT_MS = 15e3;
60913
61051
  var FAMILY_BY_NPM = {
60914
61052
  "@ai-sdk/anthropic": "anthropic",
@@ -60982,7 +61120,7 @@ var DefaultModelsRegistry = class {
60982
61120
  this.overlay = opts.overlay;
60983
61121
  this.overlayUrl = opts.overlayUrl;
60984
61122
  this.overlayFile = opts.overlayFile;
60985
- this.overlayCacheFile = opts.overlayCacheFile ?? (opts.overlayUrl ? path77.join(path77.dirname(opts.cacheFile), "models-overlay-cache.json") : void 0);
61123
+ this.overlayCacheFile = opts.overlayCacheFile ?? (opts.overlayUrl ? path78.join(path78.dirname(opts.cacheFile), "models-overlay-cache.json") : void 0);
60986
61124
  this.logger = opts.logger ?? noOpLogger;
60987
61125
  }
60988
61126
  async load(opts = {}) {
@@ -61257,7 +61395,7 @@ var DefaultModelsRegistry = class {
61257
61395
  }
61258
61396
  /** Used by `wstack models refresh` to expose where the cache lives. */
61259
61397
  cacheLocation() {
61260
- return path77.resolve(this.cacheFile);
61398
+ return path78.resolve(this.cacheFile);
61261
61399
  }
61262
61400
  };
61263
61401
  var REASONING_EFFORTS = /* @__PURE__ */ new Set([
@@ -62340,7 +62478,7 @@ function validateTrustPolicy(value) {
62340
62478
  }
62341
62479
 
62342
62480
  // src/security/yolo-risk.ts
62343
- import * as path78 from "node:path";
62481
+ import * as path79 from "node:path";
62344
62482
  var CATASTROPHIC_PATTERNS = [
62345
62483
  /\b(?:mkfs(?:\.[a-z0-9]+)?|mke2fs|newfs)\b/i,
62346
62484
  // make a filesystem — wipes a partition
@@ -62400,9 +62538,9 @@ function getInputString(input, key) {
62400
62538
  function pathLooksInsideProject(rawPath, projectRoot) {
62401
62539
  if (!projectRoot) return false;
62402
62540
  if (rawPath === "~" || rawPath.startsWith("~/") || rawPath.startsWith("~\\")) return false;
62403
- const resolved = path78.resolve(projectRoot, rawPath);
62404
- const relative19 = path78.relative(projectRoot, resolved);
62405
- return !!relative19 && !relative19.startsWith("..") && !path78.isAbsolute(relative19);
62541
+ const resolved = path79.resolve(projectRoot, rawPath);
62542
+ const relative19 = path79.relative(projectRoot, resolved);
62543
+ return !!relative19 && !relative19.startsWith("..") && !path79.isAbsolute(relative19);
62406
62544
  }
62407
62545
  function tokenizeShell(command) {
62408
62546
  return command.match(/"[^"]*"|'[^']*'|\S+/g)?.map((token) => token.replace(/^['"]|['"]$/g, "")) ?? [];
@@ -62589,7 +62727,68 @@ function isClearlyDestructiveBashCommand(command, projectRoot) {
62589
62727
  return false;
62590
62728
  }
62591
62729
 
62592
- // src/security/permission-policy.ts
62730
+ // src/security/auto-approve-policy.ts
62731
+ var AutoApprovePermissionPolicy = class _AutoApprovePermissionPolicy {
62732
+ allowedCapabilities;
62733
+ constructor(allowedCapabilities) {
62734
+ this.allowedCapabilities = allowedCapabilities ?? [
62735
+ ToolCapabilities.FS_READ,
62736
+ ToolCapabilities.NET_OUTBOUND
62737
+ ];
62738
+ }
62739
+ static isMcpTool(name) {
62740
+ return name.startsWith("mcp__");
62741
+ }
62742
+ async evaluate(tool) {
62743
+ const caps = tool.capabilities ?? [];
62744
+ const hasAllowedCap = caps.some((c) => this.allowedCapabilities.includes(c));
62745
+ const isMcp = _AutoApprovePermissionPolicy.isMcpTool(tool.name);
62746
+ const mcpProxyAllowed = this.allowedCapabilities.includes(ToolCapabilities.MCP_PROXY);
62747
+ const dangerousNotAllowed = getDangerousCapabilities(tool).filter(
62748
+ (c) => !this.allowedCapabilities.includes(c)
62749
+ );
62750
+ const blocked = tool.permission === "deny" || isMcp && !mcpProxyAllowed || !hasAllowedCap || dangerousNotAllowed.length > 0;
62751
+ if (blocked) {
62752
+ const reason = isMcp && !mcpProxyAllowed ? `MCP tool ${tool.name} is not auto-approved for subagents \u2014 ask the leader to allow mcp.proxy explicitly` : tool.permission === "deny" ? "tool default deny" : dangerousNotAllowed.length > 0 ? `tool requires un-granted dangerous capability (needs: ${dangerousNotAllowed.join(", ")}, allowed: ${this.allowedCapabilities.join(", ")})` : `tool lacks allowed capability (has: ${caps.join(", ") || "none"}, allowed: ${this.allowedCapabilities.join(", ")})`;
62753
+ return {
62754
+ permission: "deny",
62755
+ source: "subagent_guard",
62756
+ reason
62757
+ };
62758
+ }
62759
+ return { permission: "auto", source: "yolo" };
62760
+ }
62761
+ async trust() {
62762
+ }
62763
+ async deny() {
62764
+ }
62765
+ denyOnce() {
62766
+ }
62767
+ allowOnce() {
62768
+ }
62769
+ async explain(tool) {
62770
+ const decision = await this.evaluate(tool);
62771
+ return {
62772
+ toolName: tool.name,
62773
+ subject: null,
62774
+ steps: [
62775
+ {
62776
+ rule: "subagent auto",
62777
+ matched: decision.permission === "auto",
62778
+ decision: decision.permission,
62779
+ source: decision.source,
62780
+ detail: decision.reason ?? `subagent policy: ${decision.permission}`
62781
+ }
62782
+ ],
62783
+ winnerIndex: 0,
62784
+ decision
62785
+ };
62786
+ }
62787
+ async reload() {
62788
+ }
62789
+ };
62790
+
62791
+ // src/security/permission-helpers.ts
62593
62792
  function matchesTrust(patterns, subject2) {
62594
62793
  return patterns.includes(subject2) || matchAny(patterns, subject2);
62595
62794
  }
@@ -62684,6 +62883,8 @@ function shellCommandReadsSensitivePath(command) {
62684
62883
  }
62685
62884
  return false;
62686
62885
  }
62886
+
62887
+ // src/security/permission-policy.ts
62687
62888
  var DefaultPermissionPolicy = class {
62688
62889
  policy = {};
62689
62890
  loaded = false;
@@ -63426,71 +63627,12 @@ var DefaultPermissionPolicy = class {
63426
63627
  return void 0;
63427
63628
  }
63428
63629
  };
63429
- var AutoApprovePermissionPolicy = class _AutoApprovePermissionPolicy {
63430
- allowedCapabilities;
63431
- constructor(allowedCapabilities) {
63432
- this.allowedCapabilities = allowedCapabilities ?? [
63433
- ToolCapabilities.FS_READ,
63434
- ToolCapabilities.NET_OUTBOUND
63435
- ];
63436
- }
63437
- static isMcpTool(name) {
63438
- return name.startsWith("mcp__");
63439
- }
63440
- async evaluate(tool) {
63441
- const caps = tool.capabilities ?? [];
63442
- const hasAllowedCap = caps.some((c) => this.allowedCapabilities.includes(c));
63443
- const isMcp = _AutoApprovePermissionPolicy.isMcpTool(tool.name);
63444
- const mcpProxyAllowed = this.allowedCapabilities.includes(ToolCapabilities.MCP_PROXY);
63445
- const dangerousNotAllowed = getDangerousCapabilities(tool).filter(
63446
- (c) => !this.allowedCapabilities.includes(c)
63447
- );
63448
- const blocked = tool.permission === "deny" || isMcp && !mcpProxyAllowed || !hasAllowedCap || dangerousNotAllowed.length > 0;
63449
- if (blocked) {
63450
- const reason = isMcp && !mcpProxyAllowed ? `MCP tool ${tool.name} is not auto-approved for subagents \u2014 ask the leader to allow mcp.proxy explicitly` : tool.permission === "deny" ? "tool default deny" : dangerousNotAllowed.length > 0 ? `tool requires un-granted dangerous capability (needs: ${dangerousNotAllowed.join(", ")}, allowed: ${this.allowedCapabilities.join(", ")})` : `tool lacks allowed capability (has: ${caps.join(", ") || "none"}, allowed: ${this.allowedCapabilities.join(", ")})`;
63451
- return {
63452
- permission: "deny",
63453
- source: "subagent_guard",
63454
- reason
63455
- };
63456
- }
63457
- return { permission: "auto", source: "yolo" };
63458
- }
63459
- async trust() {
63460
- }
63461
- async deny() {
63462
- }
63463
- denyOnce() {
63464
- }
63465
- allowOnce() {
63466
- }
63467
- async explain(tool) {
63468
- const decision = await this.evaluate(tool);
63469
- return {
63470
- toolName: tool.name,
63471
- subject: null,
63472
- steps: [
63473
- {
63474
- rule: "subagent auto",
63475
- matched: decision.permission === "auto",
63476
- decision: decision.permission,
63477
- source: decision.source,
63478
- detail: decision.reason ?? `subagent policy: ${decision.permission}`
63479
- }
63480
- ],
63481
- winnerIndex: 0,
63482
- decision
63483
- };
63484
- }
63485
- async reload() {
63486
- }
63487
- };
63488
63630
 
63489
63631
  // src/storage/attachment-store.ts
63490
63632
  init_atomic_write();
63491
63633
  import { randomBytes as randomBytes6 } from "node:crypto";
63492
- import * as fsp29 from "node:fs/promises";
63493
- import * as path79 from "node:path";
63634
+ import * as fsp30 from "node:fs/promises";
63635
+ import * as path80 from "node:path";
63494
63636
  var DEFAULT_SPOOL_THRESHOLD = 256 * 1024;
63495
63637
  var PLACEHOLDER_RE = /\[(pasted|image|file) #(\d+)[^\]]*\]|\[file:([^\]]+)\]/g;
63496
63638
  var DefaultAttachmentStore = class {
@@ -63510,8 +63652,8 @@ var DefaultAttachmentStore = class {
63510
63652
  let spooledPath;
63511
63653
  let data = input.data;
63512
63654
  if (this.spoolDir && bytes >= this.spoolThreshold) {
63513
- await fsp29.mkdir(this.spoolDir, { recursive: true });
63514
- spooledPath = path79.join(this.spoolDir, `${id}.bin`);
63655
+ await fsp30.mkdir(this.spoolDir, { recursive: true });
63656
+ spooledPath = path80.join(this.spoolDir, `${id}.bin`);
63515
63657
  await atomicWrite(spooledPath, input.data, {
63516
63658
  encoding: input.kind === "image" ? "base64" : "utf8"
63517
63659
  });
@@ -63573,7 +63715,7 @@ var DefaultAttachmentStore = class {
63573
63715
  for (const att of this.items.values()) {
63574
63716
  if (att.path) toDelete.push(att.path);
63575
63717
  }
63576
- await Promise.all(toDelete.map((p) => fsp29.unlink(p).catch(() => void 0)));
63718
+ await Promise.all(toDelete.map((p) => fsp30.unlink(p).catch(() => void 0)));
63577
63719
  }
63578
63720
  this.items.clear();
63579
63721
  this.refs.length = 0;
@@ -63581,7 +63723,7 @@ var DefaultAttachmentStore = class {
63581
63723
  }
63582
63724
  async toBlock(att) {
63583
63725
  if (att.kind === "image") {
63584
- const data = att.data ?? (att.path ? await fsp29.readFile(att.path, { encoding: "base64" }) : "");
63726
+ const data = att.data ?? (att.path ? await fsp30.readFile(att.path, { encoding: "base64" }) : "");
63585
63727
  return {
63586
63728
  type: "image",
63587
63729
  source: {
@@ -63591,7 +63733,7 @@ var DefaultAttachmentStore = class {
63591
63733
  }
63592
63734
  };
63593
63735
  }
63594
- const raw = att.data ?? (att.path ? await fsp29.readFile(att.path, "utf8") : "");
63736
+ const raw = att.data ?? (att.path ? await fsp30.readFile(att.path, "utf8") : "");
63595
63737
  const label = att.meta.filename ? `<file path="${att.meta.filename}">` : "<pasted>";
63596
63738
  const close = att.meta.filename ? "</file>" : "</pasted>";
63597
63739
  return { type: "text", text: `${label}
@@ -63758,13 +63900,13 @@ function deepFreeze(obj) {
63758
63900
  init_atomic_write();
63759
63901
  init_error();
63760
63902
  init_errors();
63761
- import * as fsp30 from "node:fs/promises";
63762
- import { randomUUID as randomUUID34 } from "node:crypto";
63903
+ import * as fsp31 from "node:fs/promises";
63904
+ import { randomUUID as randomUUID35 } from "node:crypto";
63763
63905
  async function loadPlan(filePath, events) {
63764
63906
  const t0 = Date.now();
63765
63907
  let raw;
63766
63908
  try {
63767
- raw = await fsp30.readFile(filePath, "utf8");
63909
+ raw = await fsp31.readFile(filePath, "utf8");
63768
63910
  } catch (err) {
63769
63911
  events?.emit("storage.error", {
63770
63912
  sessionId: "~boot~",
@@ -63857,7 +63999,7 @@ function emptyPlan(sessionId, title) {
63857
63999
  function addPlanItem(plan, title, details) {
63858
64000
  const now = (/* @__PURE__ */ new Date()).toISOString();
63859
64001
  const item = {
63860
- id: `plan_${Date.now()}_${randomUUID34().slice(0, 6)}`,
64002
+ id: `plan_${Date.now()}_${randomUUID35().slice(0, 6)}`,
63861
64003
  title,
63862
64004
  details,
63863
64005
  status: "open",
@@ -63929,7 +64071,7 @@ function deriveTodosFromPlanItem(plan, idOrIndex, subtasks) {
63929
64071
  if (subtasks && subtasks.length > 0) {
63930
64072
  for (const st of subtasks) {
63931
64073
  todos.push({
63932
- id: `todo_${Date.now()}_${randomUUID34().slice(0, 6)}`,
64074
+ id: `todo_${Date.now()}_${randomUUID35().slice(0, 6)}`,
63933
64075
  content: st,
63934
64076
  status: "pending",
63935
64077
  promotedFromPlan: item.id
@@ -64074,8 +64216,8 @@ ${cat}:`);
64074
64216
  // src/storage/queue-store.ts
64075
64217
  init_atomic_write();
64076
64218
  init_error();
64077
- import * as fsp31 from "node:fs/promises";
64078
- import * as path80 from "node:path";
64219
+ import * as fsp32 from "node:fs/promises";
64220
+ import * as path81 from "node:path";
64079
64221
  var QUEUE_MAX_ITEMS = 100;
64080
64222
  var QUEUE_MAX_BYTES = 16 * 1024 * 1024;
64081
64223
  var QUEUE_MAX_ITEM_BYTES = 8 * 1024 * 1024;
@@ -64108,7 +64250,7 @@ var QueueStore = class {
64108
64250
  traceId;
64109
64251
  logger;
64110
64252
  constructor(opts) {
64111
- this.file = path80.join(opts.dir, "queue.json");
64253
+ this.file = path81.join(opts.dir, "queue.json");
64112
64254
  this.events = opts.events;
64113
64255
  this.traceId = opts.traceId;
64114
64256
  this.logger = opts.logger;
@@ -64177,7 +64319,7 @@ var QueueStore = class {
64177
64319
  const t0 = Date.now();
64178
64320
  let raw;
64179
64321
  try {
64180
- const stat33 = await fsp31.stat(this.file);
64322
+ const stat33 = await fsp32.stat(this.file);
64181
64323
  if (stat33.size > QUEUE_MAX_BYTES) {
64182
64324
  this.logWarn("Queue file exceeds the safe read budget; ignoring it", {
64183
64325
  event: "queue_store.read_budget_exceeded",
@@ -64197,7 +64339,7 @@ var QueueStore = class {
64197
64339
  });
64198
64340
  return [];
64199
64341
  }
64200
- raw = await fsp31.readFile(this.file, "utf8");
64342
+ raw = await fsp32.readFile(this.file, "utf8");
64201
64343
  } catch (err) {
64202
64344
  const code = err.code;
64203
64345
  if (code === "ENOENT") {
@@ -64284,7 +64426,7 @@ var QueueStore = class {
64284
64426
  async clear() {
64285
64427
  const t0 = Date.now();
64286
64428
  try {
64287
- await fsp31.unlink(this.file);
64429
+ await fsp32.unlink(this.file);
64288
64430
  this.events?.emit("storage.write", {
64289
64431
  sessionId: this.traceId ?? "~boot~",
64290
64432
  store: "queue",
@@ -64324,9 +64466,9 @@ function isPersistedQueueItem(v) {
64324
64466
  // src/storage/recovery-lock.ts
64325
64467
  init_atomic_write();
64326
64468
  init_pid();
64327
- import * as fsp32 from "node:fs/promises";
64469
+ import * as fsp33 from "node:fs/promises";
64328
64470
  import * as os10 from "node:os";
64329
- import * as path81 from "node:path";
64471
+ import * as path82 from "node:path";
64330
64472
  var LOCK_FILE = "active.json";
64331
64473
  var DEFAULT_MAX_AGE_MS = 24 * 60 * 60 * 1e3;
64332
64474
  var RecoveryLock = class {
@@ -64337,7 +64479,7 @@ var RecoveryLock = class {
64337
64479
  sessionStore;
64338
64480
  probe;
64339
64481
  constructor(opts) {
64340
- this.file = path81.join(opts.dir, LOCK_FILE);
64482
+ this.file = path82.join(opts.dir, LOCK_FILE);
64341
64483
  this.pid = opts.pid ?? process.pid;
64342
64484
  this.hostname = opts.hostname ?? os10.hostname();
64343
64485
  this.maxAgeMs = opts.maxAgeMs ?? DEFAULT_MAX_AGE_MS;
@@ -64411,7 +64553,7 @@ var RecoveryLock = class {
64411
64553
  * null return before calling this.
64412
64554
  */
64413
64555
  async write(sessionId) {
64414
- await ensureDir(path81.dirname(this.file));
64556
+ await ensureDir(path82.dirname(this.file));
64415
64557
  const lock = {
64416
64558
  v: 1,
64417
64559
  sessionId,
@@ -64420,7 +64562,7 @@ var RecoveryLock = class {
64420
64562
  startedAt: (/* @__PURE__ */ new Date()).toISOString()
64421
64563
  };
64422
64564
  try {
64423
- await fsp32.writeFile(this.file, JSON.stringify(lock), { flag: "wx", mode: 384 });
64565
+ await fsp33.writeFile(this.file, JSON.stringify(lock), { flag: "wx", mode: 384 });
64424
64566
  } catch (err) {
64425
64567
  const code = err.code;
64426
64568
  if (code === "EEXIST") {
@@ -64436,7 +64578,7 @@ var RecoveryLock = class {
64436
64578
  */
64437
64579
  async clear() {
64438
64580
  try {
64439
- await fsp32.unlink(this.file);
64581
+ await fsp33.unlink(this.file);
64440
64582
  } catch (err) {
64441
64583
  const code = err.code;
64442
64584
  if (code === "ENOENT") return;
@@ -64446,7 +64588,7 @@ var RecoveryLock = class {
64446
64588
  async readLock() {
64447
64589
  let raw;
64448
64590
  try {
64449
- raw = await fsp32.readFile(this.file, "utf8");
64591
+ raw = await fsp33.readFile(this.file, "utf8");
64450
64592
  } catch (err) {
64451
64593
  const code = err.code;
64452
64594
  if (code === "ENOENT") return null;
@@ -65058,12 +65200,12 @@ function renderPlainText(meta, events) {
65058
65200
  // src/storage/todos-checkpoint.ts
65059
65201
  init_atomic_write();
65060
65202
  init_error();
65061
- import * as fsp33 from "node:fs/promises";
65203
+ import * as fsp34 from "node:fs/promises";
65062
65204
  async function loadTodosCheckpoint(filePath, events, traceId, sessionId) {
65063
65205
  const t0 = Date.now();
65064
65206
  let raw;
65065
65207
  try {
65066
- raw = await fsp33.readFile(filePath, "utf8");
65208
+ raw = await fsp34.readFile(filePath, "utf8");
65067
65209
  } catch (err) {
65068
65210
  events?.emit("storage.error", {
65069
65211
  sessionId: sessionId ?? "~boot~",
@@ -70427,8 +70569,8 @@ var PHASE_EVENT_NAMES = [
70427
70569
 
70428
70570
  // src/goal/phase-store.ts
70429
70571
  init_atomic_write();
70430
- import * as fsp34 from "node:fs/promises";
70431
- import * as path82 from "node:path";
70572
+ import * as fsp35 from "node:fs/promises";
70573
+ import * as path83 from "node:path";
70432
70574
  var PHASE_STORE_VERSION = 1;
70433
70575
  var PhaseStore = class {
70434
70576
  baseDir;
@@ -70436,7 +70578,7 @@ var PhaseStore = class {
70436
70578
  constructor(opts) {
70437
70579
  this.baseDir = opts.baseDir;
70438
70580
  this.legacyBaseDirs = (opts.legacyBaseDirs ?? []).filter(
70439
- (dir) => path82.resolve(dir) !== path82.resolve(this.baseDir)
70581
+ (dir) => path83.resolve(dir) !== path83.resolve(this.baseDir)
70440
70582
  );
70441
70583
  }
70442
70584
  async save(graph) {
@@ -70451,7 +70593,7 @@ var PhaseStore = class {
70451
70593
  const current = await this.loadFromPath(filePath);
70452
70594
  if (current) return current;
70453
70595
  for (const legacyDir of this.legacyBaseDirs) {
70454
- const legacyPath = path82.join(legacyDir, `${graphId}.json`);
70596
+ const legacyPath = path83.join(legacyDir, `${graphId}.json`);
70455
70597
  const legacy = await this.loadFromPath(legacyPath);
70456
70598
  if (!legacy) continue;
70457
70599
  try {
@@ -70467,19 +70609,19 @@ var PhaseStore = class {
70467
70609
  async delete(graphId) {
70468
70610
  const paths = [
70469
70611
  this.getFilePath(graphId),
70470
- ...this.legacyBaseDirs.map((dir) => path82.join(dir, `${graphId}.json`))
70612
+ ...this.legacyBaseDirs.map((dir) => path83.join(dir, `${graphId}.json`))
70471
70613
  ];
70472
- await Promise.all(paths.map((filePath) => fsp34.unlink(filePath).catch(() => void 0)));
70614
+ await Promise.all(paths.map((filePath) => fsp35.unlink(filePath).catch(() => void 0)));
70473
70615
  }
70474
70616
  async list() {
70475
70617
  try {
70476
70618
  await this.migrateLegacyGraphs();
70477
- const entries = await fsp34.readdir(this.baseDir, { withFileTypes: true });
70619
+ const entries = await fsp35.readdir(this.baseDir, { withFileTypes: true });
70478
70620
  const graphs = [];
70479
70621
  for (const entry of entries) {
70480
70622
  if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
70481
70623
  try {
70482
- const raw = await fsp34.readFile(path82.join(this.baseDir, entry.name), "utf8");
70624
+ const raw = await fsp35.readFile(path83.join(this.baseDir, entry.name), "utf8");
70483
70625
  const serialized = JSON.parse(raw);
70484
70626
  const done = serialized.completedPhaseIds.length;
70485
70627
  const total = serialized.phases.length;
@@ -70498,11 +70640,11 @@ var PhaseStore = class {
70498
70640
  }
70499
70641
  }
70500
70642
  getFilePath(graphId) {
70501
- return path82.join(this.baseDir, `${graphId}.json`);
70643
+ return path83.join(this.baseDir, `${graphId}.json`);
70502
70644
  }
70503
70645
  async loadFromPath(filePath) {
70504
70646
  try {
70505
- const raw = await fsp34.readFile(filePath, "utf8");
70647
+ const raw = await fsp35.readFile(filePath, "utf8");
70506
70648
  const serialized = JSON.parse(raw);
70507
70649
  return this.deserializeGraph(serialized);
70508
70650
  } catch {
@@ -70511,11 +70653,11 @@ var PhaseStore = class {
70511
70653
  }
70512
70654
  async migrateLegacyGraphs() {
70513
70655
  for (const legacyDir of this.legacyBaseDirs) {
70514
- const entries = await fsp34.readdir(legacyDir, { withFileTypes: true }).catch(() => []);
70656
+ const entries = await fsp35.readdir(legacyDir, { withFileTypes: true }).catch(() => []);
70515
70657
  for (const entry of entries) {
70516
70658
  if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
70517
- const legacyPath = path82.join(legacyDir, entry.name);
70518
- const currentPath = this.getFilePath(path82.basename(entry.name, ".json"));
70659
+ const legacyPath = path83.join(legacyDir, entry.name);
70660
+ const currentPath = this.getFilePath(path83.basename(entry.name, ".json"));
70519
70661
  if (await this.pathExists(currentPath)) {
70520
70662
  await this.removeMigratedLegacyFile(legacyDir, legacyPath);
70521
70663
  continue;
@@ -70529,15 +70671,15 @@ var PhaseStore = class {
70529
70671
  }
70530
70672
  async pathExists(filePath) {
70531
70673
  try {
70532
- await fsp34.access(filePath);
70674
+ await fsp35.access(filePath);
70533
70675
  return true;
70534
70676
  } catch {
70535
70677
  return false;
70536
70678
  }
70537
70679
  }
70538
70680
  async removeMigratedLegacyFile(legacyDir, legacyPath) {
70539
- await fsp34.unlink(legacyPath).catch(() => void 0);
70540
- await fsp34.rmdir(legacyDir).catch(() => void 0);
70681
+ await fsp35.unlink(legacyPath).catch(() => void 0);
70682
+ await fsp35.rmdir(legacyDir).catch(() => void 0);
70541
70683
  }
70542
70684
  serializeGraph(graph) {
70543
70685
  return {
@@ -70663,8 +70805,8 @@ var PhaseStore = class {
70663
70805
 
70664
70806
  // src/goal/checkpoint.ts
70665
70807
  init_atomic_write();
70666
- import * as fsp35 from "node:fs/promises";
70667
- import * as path83 from "node:path";
70808
+ import * as fsp36 from "node:fs/promises";
70809
+ import * as path84 from "node:path";
70668
70810
  var CheckpointManager = class {
70669
70811
  store;
70670
70812
  maxCheckpoints;
@@ -70675,10 +70817,10 @@ var CheckpointManager = class {
70675
70817
  this.store = opts.store;
70676
70818
  this.maxCheckpoints = opts.maxCheckpoints ?? 10;
70677
70819
  this.maxCheckpointAgeMs = opts.maxCheckpointAgeMs ?? 7 * 24 * 60 * 60 * 1e3;
70678
- this.baseDir = opts.baseDir ?? path83.join(opts.store.baseDir, ".checkpoints");
70820
+ this.baseDir = opts.baseDir ?? path84.join(opts.store.baseDir, ".checkpoints");
70679
70821
  }
70680
70822
  async initialize() {
70681
- await fsp35.mkdir(this.baseDir, { recursive: true });
70823
+ await fsp36.mkdir(this.baseDir, { recursive: true });
70682
70824
  await this.loadFromDisk();
70683
70825
  }
70684
70826
  async saveCheckpoint(graph, label) {
@@ -70741,14 +70883,14 @@ var CheckpointManager = class {
70741
70883
  return true;
70742
70884
  }
70743
70885
  async saveToDisk(checkpoint) {
70744
- const filePath = path83.join(this.baseDir, `${checkpoint.graphId}.json`);
70886
+ const filePath = path84.join(this.baseDir, `${checkpoint.graphId}.json`);
70745
70887
  const serialized = {
70746
70888
  ...checkpoint
70747
70889
  };
70748
70890
  await withFileLock(filePath, async () => {
70749
70891
  let existing = [];
70750
70892
  try {
70751
- const raw = await fsp35.readFile(filePath, "utf8");
70893
+ const raw = await fsp36.readFile(filePath, "utf8");
70752
70894
  const parsed = JSON.parse(raw);
70753
70895
  if (Array.isArray(parsed)) {
70754
70896
  existing = parsed;
@@ -70762,23 +70904,23 @@ var CheckpointManager = class {
70762
70904
  async deleteFromDisk(checkpointId) {
70763
70905
  let entries;
70764
70906
  try {
70765
- entries = await fsp35.readdir(this.baseDir);
70907
+ entries = await fsp36.readdir(this.baseDir);
70766
70908
  } catch {
70767
70909
  return;
70768
70910
  }
70769
70911
  for (const filename of entries) {
70770
70912
  if (!filename.endsWith(".json")) continue;
70771
- const filePath = path83.join(this.baseDir, filename);
70913
+ const filePath = path84.join(this.baseDir, filename);
70772
70914
  try {
70773
70915
  await withFileLock(filePath, async () => {
70774
- const raw = await fsp35.readFile(filePath, "utf8");
70916
+ const raw = await fsp36.readFile(filePath, "utf8");
70775
70917
  const parsed = JSON.parse(raw);
70776
70918
  if (!Array.isArray(parsed)) return;
70777
70919
  const existing = parsed;
70778
70920
  const filtered = existing.filter((c) => c.id !== checkpointId);
70779
70921
  if (filtered.length !== existing.length) {
70780
70922
  if (filtered.length === 0) {
70781
- await fsp35.unlink(filePath);
70923
+ await fsp36.unlink(filePath);
70782
70924
  } else {
70783
70925
  await atomicWrite(filePath, JSON.stringify(filtered, null, 2), { mode: 384 });
70784
70926
  }
@@ -70791,15 +70933,15 @@ var CheckpointManager = class {
70791
70933
  async loadFromDisk() {
70792
70934
  let entries;
70793
70935
  try {
70794
- entries = await fsp35.readdir(this.baseDir);
70936
+ entries = await fsp36.readdir(this.baseDir);
70795
70937
  } catch {
70796
70938
  return;
70797
70939
  }
70798
70940
  for (const filename of entries) {
70799
70941
  if (!filename.endsWith(".json")) continue;
70800
- const filePath = path83.join(this.baseDir, filename);
70942
+ const filePath = path84.join(this.baseDir, filename);
70801
70943
  try {
70802
- const raw = await fsp35.readFile(filePath, "utf8");
70944
+ const raw = await fsp36.readFile(filePath, "utf8");
70803
70945
  const parsed = JSON.parse(raw);
70804
70946
  if (!Array.isArray(parsed)) continue;
70805
70947
  const checkpoints = parsed;
@@ -71676,7 +71818,7 @@ function countShellHooks(hooks) {
71676
71818
 
71677
71819
  // src/hq/session-bridge.ts
71678
71820
  import * as fs38 from "node:fs";
71679
- import * as fsp36 from "node:fs/promises";
71821
+ import * as fsp37 from "node:fs/promises";
71680
71822
 
71681
71823
  // src/types/session-markers.ts
71682
71824
  var SESSION_MARKER_EVENT_TYPES = /* @__PURE__ */ new Set([
@@ -72134,12 +72276,12 @@ function startSessionTelemetryBridge(opts) {
72134
72276
  if (disposed || tailing) return;
72135
72277
  tailing = true;
72136
72278
  try {
72137
- const stat33 = await fsp36.stat(sessionFile).catch(() => null);
72279
+ const stat33 = await fsp37.stat(sessionFile).catch(() => null);
72138
72280
  if (disposed) return;
72139
72281
  if (!stat33) return;
72140
72282
  setupWatcher2();
72141
72283
  if (stat33.size <= offset) return;
72142
- const fd = await fsp36.open(sessionFile, "r");
72284
+ const fd = await fsp37.open(sessionFile, "r");
72143
72285
  try {
72144
72286
  if (disposed) return;
72145
72287
  const len = stat33.size - offset;
@@ -72568,7 +72710,7 @@ function startCostTelemetryBridge(opts) {
72568
72710
  init_atomic_write();
72569
72711
  import { createHash as createHash29 } from "node:crypto";
72570
72712
  import * as fs39 from "node:fs/promises";
72571
- import * as path84 from "node:path";
72713
+ import * as path85 from "node:path";
72572
72714
 
72573
72715
  // src/hq/write-queues.ts
72574
72716
  var BestEffortBatchQueue = class _BestEffortBatchQueue {
@@ -72663,7 +72805,7 @@ var HqKanbanStore = class {
72663
72805
  writers = /* @__PURE__ */ new Map();
72664
72806
  mergeChains = /* @__PURE__ */ new Map();
72665
72807
  constructor(dataDir) {
72666
- this.dirPath = path84.join(dataDir, "kanban");
72808
+ this.dirPath = path85.join(dataDir, "kanban");
72667
72809
  }
72668
72810
  async load(projectId) {
72669
72811
  const cached2 = this.cache.get(projectId);
@@ -72771,7 +72913,7 @@ var HqKanbanStore = class {
72771
72913
  }
72772
72914
  filePath(projectId) {
72773
72915
  const safe = createHash29("sha256").update(projectId).digest("hex");
72774
- return path84.join(this.dirPath, `${safe}.json`);
72916
+ return path85.join(this.dirPath, `${safe}.json`);
72775
72917
  }
72776
72918
  };
72777
72919
  function emptyKanbanSnapshot(projectId) {
@@ -72790,7 +72932,7 @@ function compareKanbanRecord(a, b) {
72790
72932
  init_file_permissions();
72791
72933
  init_atomic_write();
72792
72934
  import * as fs41 from "node:fs/promises";
72793
- import * as path85 from "node:path";
72935
+ import * as path86 from "node:path";
72794
72936
 
72795
72937
  // src/hq/persistence/jsonl-io.ts
72796
72938
  import * as fs40 from "node:fs/promises";
@@ -73012,7 +73154,7 @@ var HqEventLog = class {
73012
73154
  counted = false;
73013
73155
  hydration;
73014
73156
  constructor(opts) {
73015
- this.filePath = path85.join(opts.dataDir, "events.jsonl");
73157
+ this.filePath = path86.join(opts.dataDir, "events.jsonl");
73016
73158
  this.maxLines = opts.maxLines ?? DEFAULT_EVENT_LOG_MAX_LINES;
73017
73159
  this.rotateKeep = opts.rotateKeep ?? DEFAULT_EVENT_LOG_ROTATE_KEEP;
73018
73160
  this.maxBytes = opts.maxBytes ?? DEFAULT_EVENT_LOG_MAX_BYTES;
@@ -73238,7 +73380,7 @@ async function readRecentEvents(filePath, limit, typeFilter) {
73238
73380
  // src/hq/persistence/simple-log.ts
73239
73381
  init_atomic_write();
73240
73382
  import * as fs42 from "node:fs/promises";
73241
- import * as path86 from "node:path";
73383
+ import * as path87 from "node:path";
73242
73384
  var HqSimpleLog = class {
73243
73385
  filePath;
73244
73386
  maxLines;
@@ -73249,7 +73391,7 @@ var HqSimpleLog = class {
73249
73391
  counted = false;
73250
73392
  hydration;
73251
73393
  constructor(opts) {
73252
- this.filePath = path86.join(opts.dataDir, opts.filename);
73394
+ this.filePath = path87.join(opts.dataDir, opts.filename);
73253
73395
  this.maxLines = opts.maxLines ?? Infinity;
73254
73396
  this.rotateKeep = opts.rotateKeep ?? this.maxLines;
73255
73397
  this.readLimit = opts.readLimit ?? Infinity;
@@ -73320,12 +73462,12 @@ var HqSimpleLog = class {
73320
73462
  // src/hq/persistence/snapshot-store.ts
73321
73463
  init_atomic_write();
73322
73464
  import * as fs43 from "node:fs/promises";
73323
- import * as path87 from "node:path";
73465
+ import * as path88 from "node:path";
73324
73466
  var HqSnapshotStore = class {
73325
73467
  filePath;
73326
73468
  writer;
73327
73469
  constructor(opts) {
73328
- this.filePath = path87.join(opts.dataDir, "snapshot.json");
73470
+ this.filePath = path88.join(opts.dataDir, "snapshot.json");
73329
73471
  this.writer = new BestEffortLatestQueue(
73330
73472
  (snapshot) => atomicWrite(this.filePath, JSON.stringify(snapshot), { mode: 384 })
73331
73473
  );
@@ -73353,7 +73495,7 @@ var HqSnapshotStore = class {
73353
73495
  init_file_permissions();
73354
73496
  init_atomic_write();
73355
73497
  import * as fs44 from "node:fs/promises";
73356
- import * as path88 from "node:path";
73498
+ import * as path89 from "node:path";
73357
73499
  var HqTimeseriesStore = class {
73358
73500
  filePath;
73359
73501
  bucketMs;
@@ -73366,7 +73508,7 @@ var HqTimeseriesStore = class {
73366
73508
  loaded = false;
73367
73509
  loadPromise;
73368
73510
  constructor(opts) {
73369
- this.filePath = path88.join(opts.dataDir, "timeseries.jsonl");
73511
+ this.filePath = path89.join(opts.dataDir, "timeseries.jsonl");
73370
73512
  this.bucketMs = opts.bucketMs ?? 5 * 60 * 1e3;
73371
73513
  this.maxBuckets = opts.maxBuckets ?? 2016;
73372
73514
  this.writer = new BestEffortLatestQueue((snapshot) => this.flushInternal(snapshot));
@@ -75571,8 +75713,8 @@ function definePlugin(metadata, factory) {
75571
75713
  // src/plugins/auto-review-plugin.ts
75572
75714
  import { spawn as spawn9 } from "node:child_process";
75573
75715
  import { createHash as createHash31 } from "node:crypto";
75574
- import * as fsp37 from "node:fs/promises";
75575
- import * as path89 from "node:path";
75716
+ import * as fsp38 from "node:fs/promises";
75717
+ import * as path90 from "node:path";
75576
75718
  init_error();
75577
75719
 
75578
75720
  // src/plugins/review-claim-registry.ts
@@ -75864,7 +76006,7 @@ function trimKnownFingerprints(map, max) {
75864
76006
  map.delete(oldest);
75865
76007
  }
75866
76008
  }
75867
- function buildReviewerModelPool(provider, model, fallbackModels = []) {
76009
+ function buildReviewerModelPool(provider, model, fallbackModels = [], statusTracker) {
75868
76010
  const primaryProvider = provider.trim();
75869
76011
  const primaryModel = model.trim();
75870
76012
  const primaryRef = primaryProvider && primaryModel ? `${primaryProvider}/${primaryModel}` : "";
@@ -75879,12 +76021,20 @@ function buildReviewerModelPool(provider, model, fallbackModels = []) {
75879
76021
  if (seen.has(normalized)) continue;
75880
76022
  seen.add(ref);
75881
76023
  seen.add(normalized);
76024
+ if (statusTracker && !statusTracker.isAvailable(parsed.provider.trim(), parsed.model.trim())) {
76025
+ continue;
76026
+ }
75882
76027
  pool.push(normalized);
75883
76028
  }
75884
76029
  return pool;
75885
76030
  }
75886
- function selectRoundRobinReviewerAssignment(pool, cursor, fallbackProvider = "", fallbackModel = "") {
75887
- if (pool.length === 0) {
76031
+ function selectRoundRobinReviewerAssignment(pool, cursor, fallbackProvider = "", fallbackModel = "", statusTracker) {
76032
+ const filteredPool = statusTracker ? pool.filter((ref) => {
76033
+ const parsed2 = parseModelRef(ref);
76034
+ if (!parsed2.provider?.trim() || !parsed2.model.trim()) return false;
76035
+ return statusTracker.isAvailable(parsed2.provider.trim(), parsed2.model.trim());
76036
+ }) : pool;
76037
+ if (filteredPool.length === 0) {
75888
76038
  return {
75889
76039
  provider: fallbackProvider,
75890
76040
  model: fallbackModel,
@@ -75892,13 +76042,13 @@ function selectRoundRobinReviewerAssignment(pool, cursor, fallbackProvider = "",
75892
76042
  nextCursor: cursor + 1
75893
76043
  };
75894
76044
  }
75895
- const len = pool.length;
76045
+ const len = filteredPool.length;
75896
76046
  const idx = (cursor % len + len) % len;
75897
- const primaryRef = pool[idx];
76047
+ const primaryRef = filteredPool[idx];
75898
76048
  const parsed = parseModelRef(primaryRef);
75899
76049
  const provider = parsed.provider?.trim() || fallbackProvider;
75900
76050
  const model = parsed.model.trim() || fallbackModel;
75901
- const fallbackModels = [...pool.slice(idx + 1), ...pool.slice(0, idx)];
76051
+ const fallbackModels = [...filteredPool.slice(idx + 1), ...filteredPool.slice(0, idx)];
75902
76052
  return {
75903
76053
  provider,
75904
76054
  model,
@@ -75940,57 +76090,49 @@ function parseReviewSeverity(text2) {
75940
76090
  const result = { critical: 0, high: 0, medium: 0 };
75941
76091
  if (!text2) return result;
75942
76092
  for (const level of ["critical", "high", "medium"]) {
75943
- const countRe = new RegExp(`###\\s*${level}\\s*\\((\\d+)\\)`, "i");
75944
- const countMatch = text2.match(countRe);
76093
+ const countMatch = text2.match(new RegExp(`###\\s*${level}\\s*\\((\\d+)\\)`, "i"));
75945
76094
  if (countMatch?.[1]) {
75946
76095
  result[level] = Number.parseInt(countMatch[1], 10);
75947
76096
  continue;
75948
76097
  }
75949
- const sectionRe = new RegExp(`###\\s*${level}[^\\n]*\\n([\\s\\S]*?)(?=###|$)`, "i");
75950
- const sectionMatch = text2.match(sectionRe);
75951
- if (sectionMatch?.[1]) {
75952
- const items = sectionMatch[1].match(/^\s*\d+\.\s/gm);
75953
- result[level] = items ? items.length : 0;
75954
- }
76098
+ const section = text2.match(
76099
+ new RegExp(`###\\s*${level}[^\\n]*\\n([\\s\\S]*?)(?=###|$)`, "i")
76100
+ )?.[1];
76101
+ result[level] = section?.match(/^\s*\d+\.\s/gm)?.length ?? 0;
75955
76102
  }
75956
76103
  return result;
75957
76104
  }
75958
- var SECURITY_KEYWORDS = [
75959
- "injection",
75960
- "xss",
75961
- "csrf",
75962
- "ssrf",
75963
- "sql",
75964
- "secret",
75965
- "credential",
75966
- "password",
75967
- "api key",
75968
- "token",
75969
- "auth",
75970
- "shell injection",
75971
- "command injection",
75972
- "innerhtml",
75973
- "deserialization",
75974
- "path traversal",
75975
- "hardcoded",
75976
- "privilege",
75977
- "owasp"
75978
- ];
75979
76105
  function decideCascadeAgents(text2, severities) {
75980
76106
  const agents = /* @__PURE__ */ new Set();
75981
- if (severities.critical > 0 || severities.high > 0) {
75982
- agents.add("bug-hunter");
75983
- }
75984
- const lower = text2.toLowerCase();
75985
- const criticalHighRe = /###\s*(?:critical|high)[^\n]*\n([\s\S]*?)(?=###|$)/gi;
75986
- let section = criticalHighRe.exec(lower);
75987
- while (section !== null) {
76107
+ if (severities.critical > 0 || severities.high > 0) agents.add("bug-hunter");
76108
+ const securityKeywords = [
76109
+ "injection",
76110
+ "xss",
76111
+ "csrf",
76112
+ "ssrf",
76113
+ "sql",
76114
+ "secret",
76115
+ "credential",
76116
+ "password",
76117
+ "api key",
76118
+ "token",
76119
+ "auth",
76120
+ "shell injection",
76121
+ "command injection",
76122
+ "innerhtml",
76123
+ "deserialization",
76124
+ "path traversal",
76125
+ "hardcoded",
76126
+ "privilege",
76127
+ "owasp"
76128
+ ];
76129
+ const criticalHighSections = text2.toLowerCase().matchAll(/###\s*(?:critical|high)[^\n]*\n([\s\S]*?)(?=###|$)/gi);
76130
+ for (const section of criticalHighSections) {
75988
76131
  const body = section[1] ?? "";
75989
- if (SECURITY_KEYWORDS.some((kw) => body.includes(kw))) {
76132
+ if (securityKeywords.some((keyword) => body.includes(keyword))) {
75990
76133
  agents.add("security-scanner");
75991
76134
  break;
75992
76135
  }
75993
- section = criticalHighRe.exec(lower);
75994
76136
  }
75995
76137
  return [...agents];
75996
76138
  }
@@ -76057,9 +76199,9 @@ async function snapshotChangedFiles(cwd) {
76057
76199
  if (file.path.startsWith(".wrongstack/")) continue;
76058
76200
  if (budget <= 0) break;
76059
76201
  try {
76060
- const stat33 = await fsp37.stat(path89.join(cwd, file.path));
76202
+ const stat33 = await fsp38.stat(path90.join(cwd, file.path));
76061
76203
  if (!stat33.isFile() || stat33.size > MAX_SNAPSHOT_FILE_BYTES) continue;
76062
- const content = await fsp37.readFile(path89.join(cwd, file.path), "utf8");
76204
+ const content = await fsp38.readFile(path90.join(cwd, file.path), "utf8");
76063
76205
  budget -= content.length;
76064
76206
  snapshots.push({
76065
76207
  ...file,
@@ -76083,9 +76225,8 @@ function buildAutoReviewCommand(getConfig, getInFlightCount) {
76083
76225
  "Detects git-tracked file edits and dispatches review subagents",
76084
76226
  "with configurable provider/model/fallback.",
76085
76227
  "",
76086
- "When a review report contains findings at or above the cascadeOn",
76087
- "threshold, follow-up agents (security-scanner, bug-hunter) are",
76088
- "automatically spawned to investigate.",
76228
+ "Reports are persisted and shown as passive notifications.",
76229
+ "They never wake the leader or spawn mutating follow-up agents.",
76089
76230
  "",
76090
76231
  "Commands:",
76091
76232
  " /auto-review Show current status and config",
@@ -76100,11 +76241,8 @@ function buildAutoReviewCommand(getConfig, getInFlightCount) {
76100
76241
  " debounceMs debounce window (default 15000)",
76101
76242
  " maxFilesPerBatch max files per review (default 15)",
76102
76243
  " maxConcurrentReviews max parallel reviews (default 2)",
76103
- ' cascadeOn "off" | "critical" | "high" (default off)',
76104
- " Spawns security-scanner/bug-hunter when",
76105
- ' findings reach this severity. "high" fires',
76106
- ' on any High+ finding; "critical" only on Critical.',
76107
- " maxCascadeDepth max fix+re-review cycles (default 2, 0=off)"
76244
+ " cascadeOn off | critical | high (default off)",
76245
+ " maxCascadeDepth max fix+re-review cycles (default 2)"
76108
76246
  ].join("\n"),
76109
76247
  async run(args) {
76110
76248
  const cfg = getConfig();
@@ -76120,7 +76258,6 @@ function buildAutoReviewCommand(getConfig, getInFlightCount) {
76120
76258
  };
76121
76259
  }
76122
76260
  const inFlight = getInFlightCount();
76123
- const cascadeDesc = cfg.cascadeOn === "off" ? "disabled" : `on ${cfg.cascadeOn}+ \u2192 spawns security-scanner/bug-hunter`;
76124
76261
  return {
76125
76262
  message: [
76126
76263
  `\u{1F4CB} Auto Review \u2014 ${cfg.enabled ? "\u2705 enabled" : "\u23F8\uFE0F disabled"}`,
@@ -76131,7 +76268,7 @@ function buildAutoReviewCommand(getConfig, getInFlightCount) {
76131
76268
  ` Debounce: ${cfg.debounceMs} ms`,
76132
76269
  ` Max files: ${cfg.maxFilesPerBatch}`,
76133
76270
  ` Max parallel: ${cfg.maxConcurrentReviews}`,
76134
- ` Cascade: ${cascadeDesc}`,
76271
+ ` Cascade: ${cfg.cascadeOn === "off" ? "off" : `on ${cfg.cascadeOn}+ \u2192 spawns security-scanner/bug-hunter`}`,
76135
76272
  ` Max depth: ${cfg.maxCascadeDepth} re-review cycle(s)`,
76136
76273
  ` In-flight: ${inFlight} review(s)`,
76137
76274
  "",
@@ -76409,7 +76546,7 @@ function createAutoReviewPlugin() {
76409
76546
  for (const f of allChanged) {
76410
76547
  if (f.path.startsWith(".wrongstack/")) continue;
76411
76548
  try {
76412
- await fsp37.access(path89.join(cwd, f.path));
76549
+ await fsp38.access(path90.join(cwd, f.path));
76413
76550
  existing.push(f);
76414
76551
  } catch {
76415
76552
  }
@@ -76419,8 +76556,8 @@ function createAutoReviewPlugin() {
76419
76556
  const filesWithContent = [];
76420
76557
  for (const f of toReview) {
76421
76558
  try {
76422
- const absPath = path89.join(cwd, f.path);
76423
- const content = await fsp37.readFile(absPath, "utf8");
76559
+ const absPath = path90.join(cwd, f.path);
76560
+ const content = await fsp38.readFile(absPath, "utf8");
76424
76561
  filesWithContent.push({ path: f.path, status: f.status, content });
76425
76562
  } catch {
76426
76563
  }
@@ -76526,7 +76663,7 @@ init_review_finding_store();
76526
76663
 
76527
76664
  // src/plugins/review-finding-parser.ts
76528
76665
  init_review_finding_types();
76529
- import { randomUUID as randomUUID36 } from "node:crypto";
76666
+ import { randomUUID as randomUUID37 } from "node:crypto";
76530
76667
  var SUGGEST_LINE = /^\s*(?:→|->|=>)\s*(.+)$/;
76531
76668
  var DURATION_LINE = /^Duration:\s*(\d+)s\s*$/im;
76532
76669
  function parseChimeraReviewReport(reportText, context = {}) {
@@ -76534,7 +76671,7 @@ function parseChimeraReviewReport(reportText, context = {}) {
76534
76671
  return { findings: [], unparseableCount: 0 };
76535
76672
  }
76536
76673
  const findings = [];
76537
- const reportId = context.reportId ?? randomUUID36();
76674
+ const reportId = context.reportId ?? randomUUID37();
76538
76675
  let unparseableCount = 0;
76539
76676
  let durationSeconds;
76540
76677
  let currentSeverity = null;
@@ -76635,7 +76772,7 @@ function parseFindingSegment(segment, severity, context) {
76635
76772
  const fullDesc = suggestions.length > 0 ? cleanDesc + "\n" + suggestions.map((s) => " \u2192 " + s).join("\n") : cleanDesc;
76636
76773
  const suggestedFix = suggestions.length > 0 ? suggestions.join("\n") : void 0;
76637
76774
  return {
76638
- id: randomUUID36(),
76775
+ id: randomUUID37(),
76639
76776
  fingerprint: computeFindingFingerprint(file ?? "", line ?? null, title),
76640
76777
  severity,
76641
76778
  source: normalizeFindingSource(context.reviewType),
@@ -76646,7 +76783,7 @@ function parseFindingSegment(segment, severity, context) {
76646
76783
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
76647
76784
  status: "active",
76648
76785
  originReport: {
76649
- reportId: context.reportId ?? randomUUID36(),
76786
+ reportId: context.reportId ?? randomUUID37(),
76650
76787
  sessionId: context.sessionId ?? "",
76651
76788
  agentId: context.agentId ?? "",
76652
76789
  reviewerModel: context.reviewerModel ?? ""
@@ -77439,7 +77576,9 @@ function createChimeraPlugin() {
77439
77576
  api.log.info("[chimera] skipped \u2014 changed files already have reviews in progress");
77440
77577
  return;
77441
77578
  }
77442
- api.log.info(`[chimera] emitted review_needed event (${emittedBundle.files.length} files)`);
77579
+ api.log.info(
77580
+ `[chimera] emitted review_needed event (${emittedBundle.files.length} files)`
77581
+ );
77443
77582
  } catch (err) {
77444
77583
  api.log.warn(`[chimera] session.ended handler failed: ${toErrorMessage(err)}`);
77445
77584
  }
@@ -87978,6 +88117,7 @@ export {
87978
88117
  ReplayLogStore,
87979
88118
  ReplayProviderRunner,
87980
88119
  RunController,
88120
+ SAGE_INJECTOR_HEADINGS,
87981
88121
  SECURITY_SCANNER_AGENT,
87982
88122
  SEMANTIC_TUNE_KNOBS,
87983
88123
  SESSION_MARKER_EVENT_TYPES,
@@ -88107,6 +88247,7 @@ export {
88107
88247
  buildWin32CmdShimInvocation,
88108
88248
  canCaptureNewLearned,
88109
88249
  canonicalProjectRoot,
88250
+ capSageLines,
88110
88251
  captureLearnedFromAgentOutput,
88111
88252
  captureLearnedFromAgentOutputDetailed,
88112
88253
  checkConnectivity,
@@ -88688,6 +88829,7 @@ export {
88688
88829
  slugify2 as slugify,
88689
88830
  slugifyProjectAgentRole,
88690
88831
  smartDefaultFallbackChain,
88832
+ splitSageOutputBlock,
88691
88833
  sqliteCachePragmas,
88692
88834
  sshManagerServer,
88693
88835
  stableStringify3 as stableStringify,