@wrongstack/core 0.298.1 → 0.298.3

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.
@@ -124,6 +124,22 @@ function toWrongStackError(err, code = ERROR_CODES.AGENT_RUN_FAILED) {
124
124
  cause: err
125
125
  });
126
126
  }
127
+ var FsError = class extends WrongStackError {
128
+ path;
129
+ constructor(opts) {
130
+ super({
131
+ message: opts.message,
132
+ code: opts.code,
133
+ subsystem: "fs",
134
+ severity: "error",
135
+ recoverable: opts.code !== ERROR_CODES.FS_READ_FAILED,
136
+ context: { path: opts.path, ...opts.context },
137
+ cause: opts.cause
138
+ });
139
+ this.name = "FsError";
140
+ this.path = opts.path;
141
+ }
142
+ };
127
143
 
128
144
  // src/extension/registry.ts
129
145
  var ExtensionRegistry = class {
@@ -1134,6 +1150,36 @@ var MAILBOX_TYPE_PROPERTIES = {
1134
1150
  };
1135
1151
 
1136
1152
  // src/coordination/mailbox-types.ts
1153
+ function isAffectedBySessionAffinity(message) {
1154
+ return message.sessionAffinity !== void 0;
1155
+ }
1156
+ async function acceptMailboxMessageForSession(message, currentSessionId, ctx) {
1157
+ if (!isAffectedBySessionAffinity(message)) return true;
1158
+ const affinity = message.sessionAffinity;
1159
+ if (affinity === null || typeof affinity !== "object" || Array.isArray(affinity)) {
1160
+ return false;
1161
+ }
1162
+ if (affinity.sessionId !== void 0 && typeof affinity.sessionId !== "string" || affinity.reportId !== void 0 && typeof affinity.reportId !== "string") {
1163
+ return false;
1164
+ }
1165
+ if (!currentSessionId) {
1166
+ return ctx?.allowUnscoped === true;
1167
+ }
1168
+ if (typeof affinity.sessionId === "string" && affinity.sessionId.length > 0) {
1169
+ if (affinity.sessionId !== currentSessionId) return false;
1170
+ return true;
1171
+ }
1172
+ if (affinity.reportId && ctx?.resolveChimeraReportSessionId) {
1173
+ try {
1174
+ const resolved = await ctx.resolveChimeraReportSessionId(affinity.reportId);
1175
+ if (resolved === currentSessionId) return true;
1176
+ if (resolved !== void 0) return false;
1177
+ } catch {
1178
+ }
1179
+ }
1180
+ if (ctx?.allowUnscoped === true) return true;
1181
+ return false;
1182
+ }
1137
1183
  var SESSION_RECIPIENT_PREFIX = "@session:";
1138
1184
 
1139
1185
  // src/coordination/sqlite-mailbox.ts
@@ -1522,6 +1568,23 @@ import { basename as basename4 } from "node:path";
1522
1568
  import * as fs3 from "node:fs";
1523
1569
  import * as path6 from "node:path";
1524
1570
 
1571
+ // src/utils/atomic-write.ts
1572
+ import {
1573
+ createPersistencePrimitives
1574
+ } from "@wrongstack/persistence";
1575
+ var primitives = createPersistencePrimitives({
1576
+ createLockTimeoutError: ({ targetPath, timeoutMs }) => new FsError({
1577
+ message: `Timed out waiting for file lock: ${targetPath}`,
1578
+ code: "FS_ATOMIC_WRITE_FAILED",
1579
+ path: targetPath,
1580
+ context: { timeoutMs }
1581
+ })
1582
+ });
1583
+ var atomicWrite = primitives.atomicWrite;
1584
+ var atomicReplaceWithWriter = primitives.atomicReplaceWithWriter;
1585
+ var ensureDir = primitives.ensureDir;
1586
+ var withFileLock = primitives.withFileLock;
1587
+
1525
1588
  // src/utils/ulid.ts
1526
1589
  var ENCODING = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
1527
1590
  var ENCODING_LEN = ENCODING.length;
@@ -1580,6 +1643,11 @@ function isMissingFileError(error) {
1580
1643
  // src/hq/auth-store.ts
1581
1644
  import * as syncFs from "node:fs";
1582
1645
  import * as path7 from "node:path";
1646
+
1647
+ // src/security/file-permissions.ts
1648
+ var SECRET_FILE_MODE = 384;
1649
+
1650
+ // src/hq/auth-store.ts
1583
1651
  function defaultHqDataDir() {
1584
1652
  return path7.join(wstackGlobalRoot(), "hq");
1585
1653
  }
@@ -2909,6 +2977,307 @@ function resolveMailboxIdentity(ctx, fallbackBase = "leader") {
2909
2977
  return { baseId, callerId, name, role, sessionId };
2910
2978
  }
2911
2979
 
2980
+ // src/plugins/review-report-store.ts
2981
+ import { randomUUID as randomUUID3 } from "node:crypto";
2982
+ import * as fsp from "node:fs/promises";
2983
+ import * as path8 from "node:path";
2984
+
2985
+ // src/plugins/review-report-types.ts
2986
+ var InvalidReportTransitionError = class extends Error {
2987
+ from;
2988
+ to;
2989
+ constructor(from, to, reason) {
2990
+ const msg = reason ?? `Invalid report transition from "${from}" to "${to}"`;
2991
+ super(msg);
2992
+ this.name = "InvalidReportTransitionError";
2993
+ this.from = from;
2994
+ this.to = to;
2995
+ }
2996
+ };
2997
+ var REPORT_TRANSITION_MATRIX = {
2998
+ open: /* @__PURE__ */ new Set(["actioned", "completed", "skipped"]),
2999
+ actioned: /* @__PURE__ */ new Set(["completed", "skipped"]),
3000
+ completed: /* @__PURE__ */ new Set(["open"]),
3001
+ skipped: /* @__PURE__ */ new Set(["open"])
3002
+ };
3003
+ function validateReportTransition(from, to) {
3004
+ if (from === to) return;
3005
+ const allowed = REPORT_TRANSITION_MATRIX[from];
3006
+ if (!allowed?.has(to)) {
3007
+ throw new InvalidReportTransitionError(from, to);
3008
+ }
3009
+ }
3010
+ function reportEventTypeFor(to) {
3011
+ switch (to) {
3012
+ case "actioned":
3013
+ return "actioned";
3014
+ case "completed":
3015
+ return "completed";
3016
+ case "skipped":
3017
+ return "skipped";
3018
+ default:
3019
+ return "reopened";
3020
+ }
3021
+ }
3022
+
3023
+ // src/plugins/review-report-store.ts
3024
+ var REPORT_RETENTION_MS = 90 * 24 * 60 * 60 * 1e3;
3025
+ var REPORT_DEFAULT_PAGE_SIZE = 25;
3026
+ var NL = "\n";
3027
+ var REPORT_STORE_FILE = "review-reports.jsonl";
3028
+ function resolveReportStorePath(projectDir) {
3029
+ return path8.join(projectDir, REPORT_STORE_FILE);
3030
+ }
3031
+ var JsonlReportStore = class {
3032
+ filePath;
3033
+ constructor(projectDir) {
3034
+ this.filePath = resolveReportStorePath(projectDir);
3035
+ }
3036
+ get storePath() {
3037
+ return this.filePath;
3038
+ }
3039
+ // ── Persist (create or reopen) ───────────────────────────────────
3040
+ async persist(input) {
3041
+ return withFileLock(this.filePath, async () => {
3042
+ const existing = await this._findRecord(input.id);
3043
+ if (existing) {
3044
+ const updated = {
3045
+ ...existing,
3046
+ counts: input.counts,
3047
+ totalFindings: input.totalFindings,
3048
+ unparseableCount: input.unparseableCount,
3049
+ durationSeconds: input.durationSeconds ?? existing.durationSeconds,
3050
+ rawText: input.rawText || existing.rawText,
3051
+ files: input.files.length > 0 ? input.files : existing.files
3052
+ };
3053
+ await fsp.appendFile(this.filePath, JSON.stringify({ __report: 1, data: updated }) + NL, {
3054
+ encoding: "utf8",
3055
+ mode: SECRET_FILE_MODE
3056
+ });
3057
+ return updated;
3058
+ }
3059
+ const report = {
3060
+ id: input.id,
3061
+ reviewedAt: (/* @__PURE__ */ new Date()).toISOString(),
3062
+ sessionId: input.sessionId,
3063
+ agentId: input.agentId,
3064
+ reviewerModel: input.reviewerModel,
3065
+ source: input.source,
3066
+ reviewStatus: input.reviewStatus,
3067
+ lifecycle: "open",
3068
+ files: input.files,
3069
+ counts: input.counts,
3070
+ totalFindings: input.totalFindings,
3071
+ unparseableCount: input.unparseableCount,
3072
+ durationSeconds: input.durationSeconds,
3073
+ rawText: input.rawText,
3074
+ ...input.cascadeDepth !== void 0 ? { cascadeDepth: input.cascadeDepth } : {}
3075
+ };
3076
+ const createdEvent = {
3077
+ id: randomUUID3(),
3078
+ reportId: report.id,
3079
+ eventType: "created",
3080
+ fromLifecycle: null,
3081
+ toLifecycle: "open",
3082
+ actorId: "system",
3083
+ actorKind: "system",
3084
+ timestamp: report.reviewedAt
3085
+ };
3086
+ const lines = JSON.stringify({ __report: 1, data: report }) + NL + JSON.stringify({ __reportEvent: 1, data: createdEvent }) + NL;
3087
+ await fsp.appendFile(this.filePath, lines, { encoding: "utf8", mode: SECRET_FILE_MODE });
3088
+ return report;
3089
+ });
3090
+ }
3091
+ // ── Lifecycle transitions ────────────────────────────────────────
3092
+ async transition(reportId, to, actor, opts) {
3093
+ return withFileLock(this.filePath, async () => {
3094
+ const all = await this._readAll();
3095
+ const entry = all.find((e) => e.report.id === reportId);
3096
+ if (!entry) throw new Error(`Review report not found: ${reportId}`);
3097
+ const from = this._materialize(entry).lifecycle;
3098
+ validateReportTransition(from, to);
3099
+ const event = {
3100
+ id: randomUUID3(),
3101
+ reportId,
3102
+ eventType: reportEventTypeFor(to),
3103
+ fromLifecycle: from,
3104
+ toLifecycle: to,
3105
+ actorId: actor.id,
3106
+ actorKind: actor.kind,
3107
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
3108
+ reason: opts?.reason
3109
+ };
3110
+ entry.report.lifecycle = to;
3111
+ await fsp.appendFile(this.filePath, JSON.stringify({ __reportEvent: 1, data: event }) + NL, {
3112
+ encoding: "utf8",
3113
+ mode: SECRET_FILE_MODE
3114
+ });
3115
+ return { ...entry.report };
3116
+ });
3117
+ }
3118
+ async addNote(reportId, actor, note) {
3119
+ return withFileLock(this.filePath, async () => {
3120
+ const all = await this._readAll();
3121
+ const entry = all.find((e) => e.report.id === reportId);
3122
+ if (!entry) throw new Error(`Review report not found: ${reportId}`);
3123
+ const materialized = this._materialize(entry);
3124
+ const event = {
3125
+ id: randomUUID3(),
3126
+ reportId,
3127
+ eventType: "note_added",
3128
+ fromLifecycle: materialized.lifecycle,
3129
+ toLifecycle: materialized.lifecycle,
3130
+ actorId: actor.id,
3131
+ actorKind: actor.kind,
3132
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
3133
+ reason: note
3134
+ };
3135
+ await fsp.appendFile(this.filePath, JSON.stringify({ __reportEvent: 1, data: event }) + NL, {
3136
+ encoding: "utf8",
3137
+ mode: SECRET_FILE_MODE
3138
+ });
3139
+ return materialized;
3140
+ });
3141
+ }
3142
+ // ── Query ────────────────────────────────────────────────────────
3143
+ async list(opts) {
3144
+ const all = await this._readAll();
3145
+ let reports = all.map((e) => this._materialize(e));
3146
+ if (opts?.statuses && opts.statuses.length > 0) {
3147
+ const set = new Set(opts.statuses);
3148
+ reports = reports.filter((r) => set.has(r.lifecycle));
3149
+ }
3150
+ if (opts?.sources && opts.sources.length > 0) {
3151
+ const set = new Set(opts.sources);
3152
+ reports = reports.filter((r) => set.has(r.source));
3153
+ }
3154
+ reports.sort((a, b) => b.reviewedAt.localeCompare(a.reviewedAt));
3155
+ const limit = opts?.limit ?? REPORT_DEFAULT_PAGE_SIZE;
3156
+ return reports.slice(0, limit);
3157
+ }
3158
+ async get(id) {
3159
+ const all = await this._readAll();
3160
+ const entry = all.find((e) => e.report.id === id);
3161
+ return entry ? this._materialize(entry) : null;
3162
+ }
3163
+ async getEvents(reportId) {
3164
+ const all = await this._readAllLines();
3165
+ const events = [];
3166
+ for (const line of all) {
3167
+ try {
3168
+ const parsed = JSON.parse(line);
3169
+ if (this._isEventRecord(parsed) && parsed.data.reportId === reportId) {
3170
+ events.push(parsed.data);
3171
+ }
3172
+ } catch {
3173
+ }
3174
+ }
3175
+ return events.sort((a, b) => a.timestamp.localeCompare(b.timestamp));
3176
+ }
3177
+ // ── Compaction ───────────────────────────────────────────────────
3178
+ async compact(opts) {
3179
+ return withFileLock(this.filePath, async () => {
3180
+ const maxAge = opts?.maxAgeMs ?? REPORT_RETENTION_MS;
3181
+ const now = Date.now();
3182
+ const all = await this._readAll();
3183
+ const kept = [];
3184
+ let removed = 0;
3185
+ let eventsFolded = 0;
3186
+ for (const entry of all) {
3187
+ const age = now - new Date(entry.report.reviewedAt).getTime();
3188
+ const materialized = this._materialize(entry);
3189
+ const isTerminal = materialized.lifecycle === "completed" || materialized.lifecycle === "skipped";
3190
+ if (isTerminal && age > maxAge) {
3191
+ removed++;
3192
+ eventsFolded += entry.events.length;
3193
+ continue;
3194
+ }
3195
+ const oldEvents = entry.events.filter(
3196
+ (ev) => now - new Date(ev.timestamp).getTime() > maxAge
3197
+ );
3198
+ if (oldEvents.length > 1) {
3199
+ eventsFolded += oldEvents.length - 1;
3200
+ const newestOld = oldEvents.sort((a, b) => b.timestamp.localeCompare(a.timestamp))[0];
3201
+ entry.events = [
3202
+ newestOld,
3203
+ ...entry.events.filter((ev) => now - new Date(ev.timestamp).getTime() <= maxAge)
3204
+ ];
3205
+ }
3206
+ kept.push({ record: { __report: 1, data: entry.report }, events: entry.events });
3207
+ }
3208
+ const lines = [];
3209
+ for (const { record, events } of kept) {
3210
+ if (record) lines.push(JSON.stringify(record));
3211
+ for (const ev of events) {
3212
+ lines.push(JSON.stringify({ __reportEvent: 1, data: ev }));
3213
+ }
3214
+ }
3215
+ lines.push(
3216
+ JSON.stringify({
3217
+ __reportCompact: 1,
3218
+ compactedAt: (/* @__PURE__ */ new Date()).toISOString(),
3219
+ removedReports: removed,
3220
+ foldedEvents: eventsFolded
3221
+ })
3222
+ );
3223
+ await atomicWrite(this.filePath, lines.join(NL) + NL, { mode: 384 });
3224
+ return { removed, eventsFolded };
3225
+ });
3226
+ }
3227
+ // ── Private helpers ──────────────────────────────────────────────
3228
+ async _readAllLines() {
3229
+ try {
3230
+ const raw = await fsp.readFile(this.filePath, "utf8");
3231
+ return raw.split(NL).filter((l) => l.trim().length > 0);
3232
+ } catch (err) {
3233
+ if (err.code === "ENOENT") return [];
3234
+ throw err;
3235
+ }
3236
+ }
3237
+ _isReportRecord(v) {
3238
+ return typeof v === "object" && v !== null && v["__report"] === 1;
3239
+ }
3240
+ _isEventRecord(v) {
3241
+ return typeof v === "object" && v !== null && v["__reportEvent"] === 1;
3242
+ }
3243
+ async _readAll() {
3244
+ const lines = await this._readAllLines();
3245
+ const reports = /* @__PURE__ */ new Map();
3246
+ const eventsMap = /* @__PURE__ */ new Map();
3247
+ for (const line of lines) {
3248
+ try {
3249
+ const parsed = JSON.parse(line);
3250
+ if (this._isReportRecord(parsed)) {
3251
+ reports.set(parsed.data.id, parsed.data);
3252
+ if (!eventsMap.has(parsed.data.id)) eventsMap.set(parsed.data.id, []);
3253
+ } else if (this._isEventRecord(parsed)) {
3254
+ const list = eventsMap.get(parsed.data.reportId);
3255
+ if (list) list.push(parsed.data);
3256
+ }
3257
+ } catch {
3258
+ }
3259
+ }
3260
+ return Array.from(reports.entries()).map(([id, report]) => ({
3261
+ report,
3262
+ events: eventsMap.get(id) ?? []
3263
+ }));
3264
+ }
3265
+ async _findRecord(id) {
3266
+ const all = await this._readAll();
3267
+ const entry = all.find((e) => e.report.id === id);
3268
+ return entry ? this._materialize(entry) : null;
3269
+ }
3270
+ _materialize(entry) {
3271
+ if (entry.events.length === 0) return { ...entry.report };
3272
+ const sorted = [...entry.events].sort((a, b) => a.timestamp.localeCompare(b.timestamp));
3273
+ const latest = sorted[sorted.length - 1];
3274
+ if (latest.eventType !== "note_added") {
3275
+ return { ...entry.report, lifecycle: latest.toLifecycle };
3276
+ }
3277
+ return { ...entry.report };
3278
+ }
3279
+ };
3280
+
2912
3281
  // src/core/mailbox-loop.ts
2913
3282
  function createMailboxChecker(opts) {
2914
3283
  const getMailbox = typeof opts.mailbox === "function" ? opts.mailbox : () => opts.mailbox;
@@ -3289,9 +3658,43 @@ function attachMailboxCheckerInner(a, source) {
3289
3658
  agentId: () => ensureRegistered(),
3290
3659
  role: () => resolveMailboxIdentity(a.ctx).role,
3291
3660
  aliases: [baseIdOf()],
3292
- sessionId: () => a.ctx.session.id
3661
+ sessionId: () => a.ctx.session.id,
3662
+ // ACK is deferred to the session-affinity wrapper so messages dropped
3663
+ // by the filter are NOT marked as read by this agent.
3664
+ ack: false
3293
3665
  };
3294
3666
  const checkMailbox = createMailboxChecker(mailboxCheckerOptions);
3667
+ const reportStore = new JsonlReportStore(
3668
+ resolveProjectDir(a.ctx.projectRoot, wstackGlobalRoot())
3669
+ );
3670
+ const sessionAffinityCtx = {
3671
+ resolveChimeraReportSessionId: async (reportId) => (await reportStore.get(reportId))?.sessionId
3672
+ };
3673
+ const applySessionAffinityFilter = (checker, ack) => {
3674
+ return async () => {
3675
+ const currentSessionId = a.ctx.session.id;
3676
+ const agentId = ack ? ensureRegistered() : null;
3677
+ const messages = await checker();
3678
+ const filtered = [];
3679
+ for (const m of messages) {
3680
+ if (await acceptMailboxMessageForSession(m, currentSessionId, sessionAffinityCtx)) {
3681
+ filtered.push(m);
3682
+ }
3683
+ }
3684
+ if (ack && filtered.length > 0 && agentId) {
3685
+ void getMailbox().ackMany({
3686
+ acks: filtered.map((m) => ({
3687
+ messageId: m.id,
3688
+ readerId: agentId,
3689
+ read: true
3690
+ }))
3691
+ }).catch(() => {
3692
+ });
3693
+ }
3694
+ return filtered;
3695
+ };
3696
+ };
3697
+ const sessionScopedCheckMailbox = applySessionAffinityFilter(checkMailbox, true);
3295
3698
  const checkMailboxAwareness = createMailboxChecker({
3296
3699
  ...mailboxCheckerOptions,
3297
3700
  // Exclude out-of-band types (control) from awareness polling.
@@ -3301,6 +3704,7 @@ function attachMailboxCheckerInner(a, source) {
3301
3704
  include: (m) => !MAILBOX_TYPE_PROPERTIES[m.type]?.outOfBand,
3302
3705
  ack: false
3303
3706
  });
3707
+ const sessionScopedCheckMailboxAwareness = applySessionAffinityFilter(checkMailboxAwareness, false);
3304
3708
  const AWARENESS_FALLBACK_INTERVAL_MS = MAILBOX_AWARENESS_INTERVAL_MS;
3305
3709
  const AWARENESS_PUSH_DEBOUNCE_MS = 500;
3306
3710
  let pollInFlight = false;
@@ -3310,7 +3714,7 @@ function attachMailboxCheckerInner(a, source) {
3310
3714
  if (awarenessDisposed || pollInFlight) return;
3311
3715
  pollInFlight = true;
3312
3716
  try {
3313
- const messages = await checkMailboxAwareness();
3717
+ const messages = await sessionScopedCheckMailboxAwareness();
3314
3718
  if (!awarenessDisposed && messages.length > 0 && a.ctx.meta["coordinationContextMode"] !== "background") {
3315
3719
  setBtwNote(a.ctx, buildMailboxBtwAwarenessBlock(messages).text);
3316
3720
  }
@@ -3343,7 +3747,7 @@ function attachMailboxCheckerInner(a, source) {
3343
3747
  if (pushDebounceTimer !== null) clearTimeout(pushDebounceTimer);
3344
3748
  pushUnsub?.();
3345
3749
  });
3346
- return checkMailbox;
3750
+ return sessionScopedCheckMailbox;
3347
3751
  }
3348
3752
  function attachFleetPulse(a, cfg) {
3349
3753
  if (!a.ctx.projectRoot || cfg?.enabled === false) {
@@ -3547,7 +3951,7 @@ function kindToCode(kind) {
3547
3951
  }
3548
3952
 
3549
3953
  // src/utils/context-evidence.ts
3550
- import * as path8 from "node:path";
3954
+ import * as path9 from "node:path";
3551
3955
  var MAX_TOOL_CALLS = 80;
3552
3956
  var MAX_FACTS = 40;
3553
3957
  var MAX_ERRORS = 20;
@@ -3710,10 +4114,10 @@ function addPath(ctx, out, raw) {
3710
4114
  if (!clean || clean.length > 260) return;
3711
4115
  let normalized = clean.replace(/\\/g, "/");
3712
4116
  try {
3713
- const abs = path8.isAbsolute(clean) ? path8.resolve(clean) : null;
4117
+ const abs = path9.isAbsolute(clean) ? path9.resolve(clean) : null;
3714
4118
  if (abs) {
3715
- const rel = path8.relative(ctx.projectRoot, abs);
3716
- if (!rel.startsWith("..") && !path8.isAbsolute(rel)) {
4119
+ const rel = path9.relative(ctx.projectRoot, abs);
4120
+ if (!rel.startsWith("..") && !path9.isAbsolute(rel)) {
3717
4121
  normalized = rel.replace(/\\/g, "/");
3718
4122
  }
3719
4123
  }
@@ -3831,7 +4235,7 @@ function implicitFactFor(metadata) {
3831
4235
  function metadataReferencedByText(metadata, haystack) {
3832
4236
  for (const file of metadata.files) {
3833
4237
  const f = file.toLowerCase();
3834
- const base = path8.basename(file).toLowerCase();
4238
+ const base = path9.basename(file).toLowerCase();
3835
4239
  if (f && haystack.includes(f)) return true;
3836
4240
  if (base && haystack.includes(base)) return true;
3837
4241
  }
@@ -4119,7 +4523,7 @@ function getCalibrationState(calibrationKey = CALIBRATION_GLOBAL_KEY) {
4119
4523
  }
4120
4524
 
4121
4525
  // src/core/context.ts
4122
- import * as path9 from "node:path";
4526
+ import * as path10 from "node:path";
4123
4527
 
4124
4528
  // src/core/conversation-state.ts
4125
4529
  var ConversationState = class {
@@ -4931,11 +5335,11 @@ var Context = class _Context {
4931
5335
  * Returns the resolved absolute path.
4932
5336
  */
4933
5337
  setWorkingDir(dir) {
4934
- const resolved = path9.isAbsolute(dir) ? path9.resolve(dir) : path9.resolve(this.projectRoot, dir);
5338
+ const resolved = path10.isAbsolute(dir) ? path10.resolve(dir) : path10.resolve(this.projectRoot, dir);
4935
5339
  if (!this.allowOutsideProjectRoot) {
4936
- const root = path9.resolve(this.projectRoot);
4937
- const rel = path9.relative(root, resolved);
4938
- if (rel.startsWith("..") || path9.isAbsolute(rel)) {
5340
+ const root = path10.resolve(this.projectRoot);
5341
+ const rel = path10.relative(root, resolved);
5342
+ if (rel.startsWith("..") || path10.isAbsolute(rel)) {
4939
5343
  throw new Error(`Working directory "${resolved}" is outside project root "${root}"`);
4940
5344
  }
4941
5345
  }
@@ -5043,7 +5447,7 @@ function requestLimitExtension(opts) {
5043
5447
  }
5044
5448
 
5045
5449
  // src/core/streaming-response-builder.ts
5046
- import { randomUUID as randomUUID3 } from "node:crypto";
5450
+ import { randomUUID as randomUUID4 } from "node:crypto";
5047
5451
 
5048
5452
  // src/utils/json-repair.ts
5049
5453
  function completePartialObject(s) {
@@ -5197,7 +5601,7 @@ function handleContentBlockStart(state, ev) {
5197
5601
  state.textBuffers.push("");
5198
5602
  state.blockOrder.push({ kind: "text", idx: state.currentTextIndex });
5199
5603
  } else if (kind === "tool_use") {
5200
- const id = ev.id ?? randomUUID3();
5604
+ const id = ev.id ?? randomUUID4();
5201
5605
  state.tools.set(id, { name: ev.name ?? "unknown", partial: "" });
5202
5606
  state.blockOrder.push({ kind: "tool", id });
5203
5607
  state.currentTextIndex = -1;
@@ -5568,7 +5972,7 @@ function runWithNetworkTelemetry(context, run) {
5568
5972
  }
5569
5973
 
5570
5974
  // src/core/provider-runner.ts
5571
- import { randomUUID as randomUUID4 } from "node:crypto";
5975
+ import { randomUUID as randomUUID5 } from "node:crypto";
5572
5976
  function providerLogCtx(p, r) {
5573
5977
  return {
5574
5978
  providerId: p.id,
@@ -5580,11 +5984,11 @@ function providerLogCtx(p, r) {
5580
5984
  }
5581
5985
  async function runProviderWithRetry(opts) {
5582
5986
  const { provider, request, signal, ctx, events, retry, logger, tracer } = opts;
5583
- const logicalRequestId = randomUUID4();
5987
+ const logicalRequestId = randomUUID5();
5584
5988
  const promptManifest = createChroniclePromptManifest(request);
5585
5989
  let attempt = 0;
5586
5990
  for (; ; ) {
5587
- const attemptId = randomUUID4();
5991
+ const attemptId = randomUUID5();
5588
5992
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
5589
5993
  const startedNs = process.hrtime.bigint();
5590
5994
  const correlation = {
@@ -7660,8 +8064,8 @@ var InputBuilder = class {
7660
8064
  async registerFile(input) {
7661
8065
  const ref = await this.store.add({ ...input, kind: "file" });
7662
8066
  this.refs.push(ref);
7663
- const path14 = ref.meta.filename ?? ref.meta.label ?? String(ref.seq);
7664
- return `[file:${path14}]`;
8067
+ const path15 = ref.meta.filename ?? ref.meta.label ?? String(ref.seq);
8068
+ return `[file:${path15}]`;
7665
8069
  }
7666
8070
  /**
7667
8071
  * Whether `appendPaste(text)` would collapse the text to a placeholder
@@ -8626,7 +9030,7 @@ function flattenSystemPromptRegions(regions) {
8626
9030
  // src/core/instruction-bundle.ts
8627
9031
  import { statSync as statSync2 } from "node:fs";
8628
9032
  import * as fs5 from "node:fs/promises";
8629
- import * as path10 from "node:path";
9033
+ import * as path11 from "node:path";
8630
9034
  import { fileURLToPath as fileURLToPath2 } from "node:url";
8631
9035
  async function loadInstructionBundle(paths) {
8632
9036
  let bundle = {};
@@ -8682,17 +9086,17 @@ function resolveSystemInstructionFile(paths) {
8682
9086
  }
8683
9087
  function sanitizeSystemInstructionFile(file) {
8684
9088
  const trimmed = file.trim();
8685
- if (trimmed.length === 0 || trimmed !== path10.basename(trimmed) || path10.extname(trimmed).toLowerCase() !== ".md") {
9089
+ if (trimmed.length === 0 || trimmed !== path11.basename(trimmed) || path11.extname(trimmed).toLowerCase() !== ".md") {
8686
9090
  throw new Error(`Invalid system instruction file: ${file}`);
8687
9091
  }
8688
9092
  return trimmed;
8689
9093
  }
8690
9094
  async function readInstructionDir(dir, options) {
8691
9095
  const [json, identity, leaderAfterTask, sections] = await Promise.all([
8692
- readInstructionJson(path10.join(dir, "instructions.json")),
8693
- readOptionalText(path10.join(dir, options.systemFile)),
8694
- readOptionalText(path10.join(dir, "leader-after-task.md")),
8695
- readSections(path10.join(dir, "sections"))
9096
+ readInstructionJson(path11.join(dir, "instructions.json")),
9097
+ readOptionalText(path11.join(dir, options.systemFile)),
9098
+ readOptionalText(path11.join(dir, "leader-after-task.md")),
9099
+ readSections(path11.join(dir, "sections"))
8696
9100
  ]);
8697
9101
  const fromMarkdown = {
8698
9102
  system: {
@@ -8717,7 +9121,7 @@ async function readSectionsInto(root, dir, out) {
8717
9121
  }
8718
9122
  await Promise.all(
8719
9123
  entries.map(async (entry) => {
8720
- const file = path10.join(dir, entry.name);
9124
+ const file = path11.join(dir, entry.name);
8721
9125
  if (entry.isDirectory()) {
8722
9126
  await readSectionsInto(root, file, out);
8723
9127
  return;
@@ -8725,7 +9129,7 @@ async function readSectionsInto(root, dir, out) {
8725
9129
  if (!entry.isFile() || !entry.name.endsWith(".md")) return;
8726
9130
  const text = await readOptionalText(file);
8727
9131
  if (text === void 0) return;
8728
- const rel = path10.relative(root, file).replace(/\\/g, "/").replace(/\.md$/i, "");
9132
+ const rel = path11.relative(root, file).replace(/\\/g, "/").replace(/\.md$/i, "");
8729
9133
  const key = rel.split("/").join(".").replace(/-/g, ".");
8730
9134
  out[key] = text;
8731
9135
  })
@@ -8773,11 +9177,11 @@ async function readOptionalText(file) {
8773
9177
  }
8774
9178
  }
8775
9179
  function defaultBundledInstructionDir() {
8776
- const here = path10.dirname(fileURLToPath2(import.meta.url));
9180
+ const here = path11.dirname(fileURLToPath2(import.meta.url));
8777
9181
  return firstExistingDirSync([
8778
- path10.resolve(here, "../../instructions"),
8779
- path10.resolve(here, "../instructions"),
8780
- path10.resolve(here, "instructions")
9182
+ path11.resolve(here, "../../instructions"),
9183
+ path11.resolve(here, "../instructions"),
9184
+ path11.resolve(here, "instructions")
8781
9185
  ]);
8782
9186
  }
8783
9187
  function definedPick(obj, keys) {
@@ -8800,13 +9204,13 @@ function firstExistingDirSync(candidates) {
8800
9204
 
8801
9205
  // src/core/modes/default.ts
8802
9206
  import { readFileSync as readFileSync6, statSync as statSync3 } from "node:fs";
8803
- import * as path11 from "node:path";
9207
+ import * as path12 from "node:path";
8804
9208
  import { fileURLToPath as fileURLToPath3 } from "node:url";
8805
9209
  var PROMPT = readBundledInstructionFile("system.md");
8806
9210
  var LEADER_AFTER_TASK_PROMPT = readBundledInstructionFile("leader-after-task.md");
8807
9211
  function readBundledInstructionFile(name) {
8808
9212
  for (const dir of bundledInstructionDirCandidates()) {
8809
- const file = path11.join(dir, name);
9213
+ const file = path12.join(dir, name);
8810
9214
  try {
8811
9215
  return readFileSync6(file, "utf8").trimEnd();
8812
9216
  } catch {
@@ -8815,12 +9219,12 @@ function readBundledInstructionFile(name) {
8815
9219
  return "";
8816
9220
  }
8817
9221
  function bundledInstructionDirCandidates() {
8818
- const here = path11.dirname(fileURLToPath3(import.meta.url));
9222
+ const here = path12.dirname(fileURLToPath3(import.meta.url));
8819
9223
  const candidates = [
8820
- path11.resolve(here, "../../../instructions"),
8821
- path11.resolve(here, "../../instructions"),
8822
- path11.resolve(here, "../instructions"),
8823
- path11.resolve(here, "instructions")
9224
+ path12.resolve(here, "../../../instructions"),
9225
+ path12.resolve(here, "../../instructions"),
9226
+ path12.resolve(here, "../instructions"),
9227
+ path12.resolve(here, "instructions")
8824
9228
  ];
8825
9229
  return candidates.sort((a, b) => Number(!isDirectory(a)) - Number(!isDirectory(b)));
8826
9230
  }
@@ -8886,12 +9290,12 @@ function agentsFingerprint(agents) {
8886
9290
 
8887
9291
  // src/core/system-prompt-environment.ts
8888
9292
  import * as os3 from "node:os";
8889
- import * as path13 from "node:path";
9293
+ import * as path14 from "node:path";
8890
9294
 
8891
9295
  // src/core/system-prompt-environment-probes.ts
8892
9296
  import { spawn as spawn2 } from "node:child_process";
8893
9297
  import * as fs6 from "node:fs/promises";
8894
- import * as path12 from "node:path";
9298
+ import * as path13 from "node:path";
8895
9299
 
8896
9300
  // src/utils/child-env.ts
8897
9301
  var ALLOWED_KEYS = /* @__PURE__ */ new Set([
@@ -9119,7 +9523,7 @@ async function detectLanguages(root) {
9119
9523
  const hits = await Promise.all(
9120
9524
  checks.map(async ([marker, lang]) => {
9121
9525
  try {
9122
- await fs6.access(path12.join(root, marker));
9526
+ await fs6.access(path13.join(root, marker));
9123
9527
  return lang;
9124
9528
  } catch {
9125
9529
  return null;
@@ -9202,7 +9606,7 @@ async function buildEnvironment(ctx, env) {
9202
9606
  const effShell = effectiveShell(os3.platform(), process.env["WRONGSTACK_SHELL"]);
9203
9607
  const shell = effShell === "posix" ? process.env.SHELL ?? process.env.ComSpec ?? "unknown" : SHELL_DISPLAY[effShell];
9204
9608
  const node = process.version;
9205
- const isGit = await dirExists(path13.join(ctx.projectRoot, ".git"));
9609
+ const isGit = await dirExists(path14.join(ctx.projectRoot, ".git"));
9206
9610
  const [git, langs] = await Promise.all([
9207
9611
  isGit ? gitStatus(ctx.projectRoot) : Promise.resolve("not a git repo"),
9208
9612
  detectLanguages(ctx.projectRoot)