@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.
- package/dist/coordination/index.js +92 -7
- package/dist/coordination/mail-tools.d.ts +3 -3
- package/dist/coordination/mailbox-codecs.d.ts +1 -1
- package/dist/coordination/mailbox-project-server.js +32 -1
- package/dist/coordination/mailbox-types.d.ts +158 -3
- package/dist/coordination/model-matrix.d.ts +16 -0
- package/dist/core/index.js +447 -43
- package/dist/defaults/index.js +21 -2
- package/dist/index.js +667 -519
- package/dist/infrastructure/index.js +20 -1
- package/dist/models/index.d.ts +1 -0
- package/dist/models/index.js +108 -1
- package/dist/models/models-dev-schema.d.ts +308 -0
- package/dist/plugin/index.js +14 -6
- package/dist/plugins/auto-review-plugin.d.ts +17 -2
- package/dist/storage/index.js +20 -1
- package/dist/tools/index.js +30 -2
- package/dist/types/config/providers.d.ts +13 -1
- package/dist/types/models-registry.d.ts +9 -4
- package/package.json +4 -3
package/dist/core/index.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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 =
|
|
4117
|
+
const abs = path9.isAbsolute(clean) ? path9.resolve(clean) : null;
|
|
3714
4118
|
if (abs) {
|
|
3715
|
-
const rel =
|
|
3716
|
-
if (!rel.startsWith("..") && !
|
|
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 =
|
|
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
|
|
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 =
|
|
5338
|
+
const resolved = path10.isAbsolute(dir) ? path10.resolve(dir) : path10.resolve(this.projectRoot, dir);
|
|
4935
5339
|
if (!this.allowOutsideProjectRoot) {
|
|
4936
|
-
const root =
|
|
4937
|
-
const rel =
|
|
4938
|
-
if (rel.startsWith("..") ||
|
|
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
|
|
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 ??
|
|
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
|
|
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 =
|
|
5987
|
+
const logicalRequestId = randomUUID5();
|
|
5584
5988
|
const promptManifest = createChroniclePromptManifest(request);
|
|
5585
5989
|
let attempt = 0;
|
|
5586
5990
|
for (; ; ) {
|
|
5587
|
-
const attemptId =
|
|
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
|
|
7664
|
-
return `[file:${
|
|
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
|
|
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 !==
|
|
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(
|
|
8693
|
-
readOptionalText(
|
|
8694
|
-
readOptionalText(
|
|
8695
|
-
readSections(
|
|
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 =
|
|
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 =
|
|
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 =
|
|
9180
|
+
const here = path11.dirname(fileURLToPath2(import.meta.url));
|
|
8777
9181
|
return firstExistingDirSync([
|
|
8778
|
-
|
|
8779
|
-
|
|
8780
|
-
|
|
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
|
|
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 =
|
|
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 =
|
|
9222
|
+
const here = path12.dirname(fileURLToPath3(import.meta.url));
|
|
8819
9223
|
const candidates = [
|
|
8820
|
-
|
|
8821
|
-
|
|
8822
|
-
|
|
8823
|
-
|
|
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
|
|
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
|
|
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(
|
|
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(
|
|
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)
|