@wrongstack/core 0.291.0 → 0.291.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/boot.d.ts.map +1 -1
- package/dist/chronicle/index.js +20 -1
- package/dist/chronicle/index.js.map +2 -2
- package/dist/chronicle/tool-adapter.d.ts.map +1 -1
- package/dist/coordination/director.d.ts +18 -0
- package/dist/coordination/director.d.ts.map +1 -1
- package/dist/coordination/fleet-spawn.d.ts.map +1 -1
- package/dist/coordination/index.js +37 -15
- package/dist/coordination/index.js.map +2 -2
- package/dist/core/agent-response.d.ts.map +1 -1
- package/dist/defaults/index.js +660 -391
- package/dist/defaults/index.js.map +4 -4
- package/dist/execution/index.js +61 -12
- package/dist/execution/index.js.map +3 -3
- package/dist/execution/tool-executor.d.ts.map +1 -1
- package/dist/hq/index.js +17 -0
- package/dist/hq/index.js.map +2 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1304 -882
- package/dist/index.js.map +4 -4
- package/dist/infrastructure/index.js +15 -10
- package/dist/infrastructure/index.js.map +2 -2
- package/dist/kernel/events/tool-events.d.ts +24 -0
- package/dist/kernel/events/tool-events.d.ts.map +1 -1
- package/dist/plugins/auto-review-plugin.d.ts +10 -0
- package/dist/plugins/auto-review-plugin.d.ts.map +1 -1
- package/dist/security/index.js +148 -0
- package/dist/security/index.js.map +3 -3
- package/dist/security/permission-policy.d.ts +8 -1
- package/dist/security/permission-policy.d.ts.map +1 -1
- package/dist/storage/config-loader.d.ts +8 -0
- package/dist/storage/config-loader.d.ts.map +1 -1
- package/dist/storage/index.js +457 -379
- package/dist/storage/index.js.map +4 -4
- package/dist/storage/provider-config-watcher.d.ts +6 -0
- package/dist/storage/provider-config-watcher.d.ts.map +1 -1
- package/dist/types/permission.d.ts +38 -0
- package/dist/types/permission.d.ts.map +1 -1
- package/dist/utils/config-backup.d.ts +20 -0
- package/dist/utils/config-backup.d.ts.map +1 -0
- package/dist/utils/index.d.ts +2 -1
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +172 -106
- package/dist/utils/index.js.map +4 -4
- package/dist/utils/message-invariants.d.ts +12 -9
- package/dist/utils/message-invariants.d.ts.map +1 -1
- package/dist/utils/term.d.ts +6 -0
- package/dist/utils/term.d.ts.map +1 -1
- package/dist/utils/wstack-paths.d.ts +10 -0
- package/dist/utils/wstack-paths.d.ts.map +1 -1
- package/package.json +2 -2
package/dist/defaults/index.js
CHANGED
|
@@ -382,6 +382,37 @@ var color = {
|
|
|
382
382
|
bgGreen: wrap("42", "49")
|
|
383
383
|
};
|
|
384
384
|
|
|
385
|
+
// src/utils/config-backup.ts
|
|
386
|
+
import * as fs2 from "node:fs/promises";
|
|
387
|
+
import * as path2 from "node:path";
|
|
388
|
+
function configHistoryDir(globalRoot) {
|
|
389
|
+
return path2.join(globalRoot, "config-history");
|
|
390
|
+
}
|
|
391
|
+
function configSlug(absolutePath, globalRoot) {
|
|
392
|
+
const rel = path2.relative(globalRoot, absolutePath);
|
|
393
|
+
const normalized = rel.replace(/\\/g, "/").replace(/\.json$/i, "");
|
|
394
|
+
return normalized.replace(/\//g, "-");
|
|
395
|
+
}
|
|
396
|
+
async function backupConfigFile(filePath, paths) {
|
|
397
|
+
let currentContent;
|
|
398
|
+
try {
|
|
399
|
+
currentContent = await fs2.readFile(filePath, "utf8");
|
|
400
|
+
if (!currentContent.trim()) return;
|
|
401
|
+
} catch {
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
const now = /* @__PURE__ */ new Date();
|
|
405
|
+
const ts = now.toISOString().replace(/[:.]/g, "-").replace(/Z$/, "");
|
|
406
|
+
const slug = configSlug(filePath, paths.globalRoot);
|
|
407
|
+
const backupDir = configHistoryDir(paths.globalRoot);
|
|
408
|
+
const backupFile = path2.join(backupDir, `${slug}-${ts}.json`);
|
|
409
|
+
try {
|
|
410
|
+
await fs2.mkdir(backupDir, { recursive: true });
|
|
411
|
+
await fs2.writeFile(backupFile, currentContent, { mode: 384, encoding: "utf8" });
|
|
412
|
+
} catch {
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
385
416
|
// src/utils/context-evidence.ts
|
|
386
417
|
var MAX_DIGEST_CHARS = 4e3;
|
|
387
418
|
function createContextEvidenceState() {
|
|
@@ -841,10 +872,10 @@ function validateAgainstSchema(value, schema) {
|
|
|
841
872
|
return { ok: errors.length === 0, errors };
|
|
842
873
|
}
|
|
843
874
|
var MAX_SCHEMA_DEPTH = 64;
|
|
844
|
-
function walk(value, schema,
|
|
875
|
+
function walk(value, schema, path30, errors, depth) {
|
|
845
876
|
if (depth > MAX_SCHEMA_DEPTH) {
|
|
846
877
|
errors.push({
|
|
847
|
-
path:
|
|
878
|
+
path: path30 || "<root>",
|
|
848
879
|
message: `schema nesting exceeds maximum depth (${MAX_SCHEMA_DEPTH})`
|
|
849
880
|
});
|
|
850
881
|
return;
|
|
@@ -852,7 +883,7 @@ function walk(value, schema, path29, errors, depth) {
|
|
|
852
883
|
if (schema.enum !== void 0) {
|
|
853
884
|
if (!enumIncludes(schema.enum, value)) {
|
|
854
885
|
errors.push({
|
|
855
|
-
path:
|
|
886
|
+
path: path30 || "<root>",
|
|
856
887
|
message: `expected one of ${JSON.stringify(schema.enum)}, got ${JSON.stringify(value)}`
|
|
857
888
|
});
|
|
858
889
|
return;
|
|
@@ -861,7 +892,7 @@ function walk(value, schema, path29, errors, depth) {
|
|
|
861
892
|
if (typeof schema.type === "string") {
|
|
862
893
|
if (!checkType(value, schema.type)) {
|
|
863
894
|
errors.push({
|
|
864
|
-
path:
|
|
895
|
+
path: path30 || "<root>",
|
|
865
896
|
message: `expected ${schema.type}, got ${describeType(value)} (${previewValue(value)})`
|
|
866
897
|
});
|
|
867
898
|
return;
|
|
@@ -873,7 +904,7 @@ function walk(value, schema, path29, errors, depth) {
|
|
|
873
904
|
if (!(req in obj)) {
|
|
874
905
|
const expected = schema.properties?.[req]?.type;
|
|
875
906
|
errors.push({
|
|
876
|
-
path: joinPath(
|
|
907
|
+
path: joinPath(path30, req),
|
|
877
908
|
message: `required property missing${typeof expected === "string" ? ` (expected ${expected})` : ""}`
|
|
878
909
|
});
|
|
879
910
|
}
|
|
@@ -881,14 +912,14 @@ function walk(value, schema, path29, errors, depth) {
|
|
|
881
912
|
if (schema.properties) {
|
|
882
913
|
for (const [key, subSchema] of Object.entries(schema.properties)) {
|
|
883
914
|
if (key in obj) {
|
|
884
|
-
walk(obj[key], subSchema, joinPath(
|
|
915
|
+
walk(obj[key], subSchema, joinPath(path30, key), errors, depth + 1);
|
|
885
916
|
}
|
|
886
917
|
}
|
|
887
918
|
}
|
|
888
919
|
}
|
|
889
920
|
if (schema.type === "array" && Array.isArray(value) && schema.items) {
|
|
890
921
|
for (let i = 0; i < value.length; i++) {
|
|
891
|
-
walk(value[i], schema.items, `${
|
|
922
|
+
walk(value[i], schema.items, `${path30}[${i}]`, errors, depth + 1);
|
|
892
923
|
}
|
|
893
924
|
}
|
|
894
925
|
}
|
|
@@ -1082,16 +1113,6 @@ function stripUndefined(obj) {
|
|
|
1082
1113
|
}
|
|
1083
1114
|
|
|
1084
1115
|
// src/utils/message-invariants.ts
|
|
1085
|
-
function hasMeaningfulContent(content) {
|
|
1086
|
-
if (typeof content === "string") return content.trim().length > 0;
|
|
1087
|
-
return content.some((block) => {
|
|
1088
|
-
if (block.type === "text") return block.text.trim().length > 0;
|
|
1089
|
-
if (block.type === "thinking") {
|
|
1090
|
-
return block.thinking.trim().length > 0 || Boolean(block.signature);
|
|
1091
|
-
}
|
|
1092
|
-
return true;
|
|
1093
|
-
});
|
|
1094
|
-
}
|
|
1095
1116
|
function repairToolUseAdjacency(messages) {
|
|
1096
1117
|
const removedToolUses = [];
|
|
1097
1118
|
const removedToolResults = [];
|
|
@@ -1178,6 +1199,21 @@ function mapContent(msg, fn) {
|
|
|
1178
1199
|
}
|
|
1179
1200
|
return { ...msg, content: next };
|
|
1180
1201
|
}
|
|
1202
|
+
function hasMeaningfulContent(content) {
|
|
1203
|
+
if (typeof content === "string") return content.trim().length > 0;
|
|
1204
|
+
for (const block of content) {
|
|
1205
|
+
if (block.type === "text") {
|
|
1206
|
+
if (block.text.trim().length > 0) return true;
|
|
1207
|
+
continue;
|
|
1208
|
+
}
|
|
1209
|
+
if (block.type === "thinking") {
|
|
1210
|
+
if (block.thinking.trim().length > 0 || block.signature) return true;
|
|
1211
|
+
continue;
|
|
1212
|
+
}
|
|
1213
|
+
return true;
|
|
1214
|
+
}
|
|
1215
|
+
return false;
|
|
1216
|
+
}
|
|
1181
1217
|
function isEmptyMessage(msg) {
|
|
1182
1218
|
return !hasMeaningfulContent(msg.content);
|
|
1183
1219
|
}
|
|
@@ -1255,14 +1291,14 @@ function safeStringify(value, pretty = false) {
|
|
|
1255
1291
|
}
|
|
1256
1292
|
|
|
1257
1293
|
// src/utils/session-scoped-path.ts
|
|
1258
|
-
import * as
|
|
1294
|
+
import * as path3 from "node:path";
|
|
1259
1295
|
function sessionScopedPath(dir, sessionId, suffix) {
|
|
1260
1296
|
if (!sessionId || sessionId.includes("\\") || sessionId.includes("..")) {
|
|
1261
1297
|
throw invalid(sessionId);
|
|
1262
1298
|
}
|
|
1263
|
-
const resolved =
|
|
1264
|
-
const rel =
|
|
1265
|
-
if (rel.startsWith("..") ||
|
|
1299
|
+
const resolved = path3.resolve(dir, `${sessionId}${suffix}`);
|
|
1300
|
+
const rel = path3.relative(path3.resolve(dir), resolved);
|
|
1301
|
+
if (rel.startsWith("..") || path3.isAbsolute(rel)) {
|
|
1266
1302
|
throw invalid(sessionId);
|
|
1267
1303
|
}
|
|
1268
1304
|
return resolved;
|
|
@@ -2316,23 +2352,23 @@ function ulid(seedTime = Date.now()) {
|
|
|
2316
2352
|
|
|
2317
2353
|
// src/utils/wstack-paths.ts
|
|
2318
2354
|
import { createHash } from "node:crypto";
|
|
2319
|
-
import * as
|
|
2355
|
+
import * as fs3 from "node:fs";
|
|
2320
2356
|
import * as os from "node:os";
|
|
2321
|
-
import * as
|
|
2357
|
+
import * as path4 from "node:path";
|
|
2322
2358
|
function canonicalProjectRoot(absRoot) {
|
|
2323
|
-
const checkoutRoot =
|
|
2324
|
-
const dotGit =
|
|
2359
|
+
const checkoutRoot = path4.resolve(absRoot);
|
|
2360
|
+
const dotGit = path4.join(checkoutRoot, ".git");
|
|
2325
2361
|
try {
|
|
2326
|
-
if (!
|
|
2327
|
-
const gitDirLine =
|
|
2362
|
+
if (!fs3.statSync(dotGit).isFile()) return checkoutRoot;
|
|
2363
|
+
const gitDirLine = fs3.readFileSync(dotGit, "utf8").trim();
|
|
2328
2364
|
const match = /^gitdir:\s*(.+)$/i.exec(gitDirLine);
|
|
2329
2365
|
if (!match?.[1]) return checkoutRoot;
|
|
2330
|
-
const gitDir =
|
|
2331
|
-
const commonDirFile =
|
|
2332
|
-
if (!
|
|
2333
|
-
const commonDir =
|
|
2334
|
-
if (
|
|
2335
|
-
return
|
|
2366
|
+
const gitDir = path4.resolve(checkoutRoot, match[1].trim());
|
|
2367
|
+
const commonDirFile = path4.join(gitDir, "commondir");
|
|
2368
|
+
if (!fs3.statSync(commonDirFile).isFile()) return checkoutRoot;
|
|
2369
|
+
const commonDir = path4.resolve(gitDir, fs3.readFileSync(commonDirFile, "utf8").trim());
|
|
2370
|
+
if (path4.basename(commonDir).toLowerCase() !== ".git") return checkoutRoot;
|
|
2371
|
+
return path4.dirname(commonDir);
|
|
2336
2372
|
} catch {
|
|
2337
2373
|
return checkoutRoot;
|
|
2338
2374
|
}
|
|
@@ -2342,7 +2378,7 @@ function projectHash(absRoot) {
|
|
|
2342
2378
|
}
|
|
2343
2379
|
function projectSlug(absRoot) {
|
|
2344
2380
|
const identityRoot = canonicalProjectRoot(absRoot);
|
|
2345
|
-
const base = slugify2(
|
|
2381
|
+
const base = slugify2(path4.basename(identityRoot));
|
|
2346
2382
|
const hash2 = createHash("sha256").update(identityRoot).digest("hex").slice(0, 6);
|
|
2347
2383
|
return `${base}-${hash2}`;
|
|
2348
2384
|
}
|
|
@@ -2351,66 +2387,83 @@ function slugify2(name) {
|
|
|
2351
2387
|
}
|
|
2352
2388
|
function wstackGlobalRoot() {
|
|
2353
2389
|
const fromEnv = process.env["WRONGSTACK_HOME"];
|
|
2354
|
-
if (fromEnv && fromEnv.trim().length > 0) return
|
|
2355
|
-
return
|
|
2390
|
+
if (fromEnv && fromEnv.trim().length > 0) return path4.resolve(fromEnv);
|
|
2391
|
+
return path4.join(os.homedir(), ".wrongstack");
|
|
2356
2392
|
}
|
|
2357
2393
|
function resolveWstackPaths(opts) {
|
|
2358
|
-
const globalRoot = opts.globalRoot ?? (opts.userHome ?
|
|
2394
|
+
const globalRoot = opts.globalRoot ?? (opts.userHome ? path4.join(opts.userHome, ".wrongstack") : wstackGlobalRoot());
|
|
2359
2395
|
const homeDir = opts.userHome ?? os.homedir();
|
|
2360
2396
|
const hash2 = projectHash(opts.projectRoot);
|
|
2361
2397
|
const slug = projectSlug(opts.projectRoot);
|
|
2362
|
-
const projectDir =
|
|
2398
|
+
const projectDir = path4.join(globalRoot, "projects", slug);
|
|
2363
2399
|
return {
|
|
2364
2400
|
globalRoot,
|
|
2365
2401
|
projectRoot: opts.projectRoot,
|
|
2366
2402
|
homeDir,
|
|
2367
2403
|
configDir: globalRoot,
|
|
2368
|
-
globalConfig:
|
|
2369
|
-
profilesDir:
|
|
2404
|
+
globalConfig: path4.join(globalRoot, "config.json"),
|
|
2405
|
+
profilesDir: path4.join(globalRoot, "profiles"),
|
|
2370
2406
|
profileConfig: (name) => {
|
|
2371
2407
|
const safe = name.replace(/[/\\:]/g, "_").replace(/\.\./g, "_");
|
|
2372
|
-
return
|
|
2408
|
+
return path4.join(globalRoot, "profiles", safe || "default", "config.json");
|
|
2409
|
+
},
|
|
2410
|
+
profileStatuslineConfig: (name) => {
|
|
2411
|
+
const safe = name.replace(/[/\\:]/g, "_").replace(/\.\./g, "_");
|
|
2412
|
+
return path4.join(globalRoot, "profiles", safe || "default", "statusline.json");
|
|
2413
|
+
},
|
|
2414
|
+
profileModeConfig: (name) => {
|
|
2415
|
+
const safe = name.replace(/[/\\:]/g, "_").replace(/\.\./g, "_");
|
|
2416
|
+
return path4.join(globalRoot, "profiles", safe || "default", "mode.json");
|
|
2373
2417
|
},
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2418
|
+
profileProviderStatus: (name) => {
|
|
2419
|
+
const safe = name.replace(/[/\\:]/g, "_").replace(/\.\./g, "_");
|
|
2420
|
+
return path4.join(globalRoot, "profiles", safe || "default", "provider-status.json");
|
|
2421
|
+
},
|
|
2422
|
+
profileUpdateCache: (name) => {
|
|
2423
|
+
const safe = name.replace(/[/\\:]/g, "_").replace(/\.\./g, "_");
|
|
2424
|
+
return path4.join(globalRoot, "profiles", safe || "default", "update-cache.json");
|
|
2425
|
+
},
|
|
2426
|
+
secretsKey: path4.join(globalRoot, ".key"),
|
|
2427
|
+
globalMemory: path4.join(globalRoot, "memory.md"),
|
|
2428
|
+
globalSkills: path4.join(globalRoot, "skills"),
|
|
2429
|
+
globalClaudeSkills: path4.join(homeDir, ".claude", "skills"),
|
|
2430
|
+
globalDesignKits: path4.join(globalRoot, "design-kits"),
|
|
2431
|
+
globalPrompts: path4.join(globalRoot, "prompts"),
|
|
2432
|
+
globalInstructions: path4.join(globalRoot, "instructions"),
|
|
2433
|
+
promptUsage: path4.join(globalRoot, "prompt-usage.json"),
|
|
2434
|
+
cacheDir: path4.join(globalRoot, "cache"),
|
|
2435
|
+
modelsCache: path4.join(globalRoot, "cache", "models.dev.json"),
|
|
2436
|
+
modelsOverlayCache: path4.join(globalRoot, "cache", "models-overlay.json"),
|
|
2437
|
+
historyFile: path4.join(globalRoot, "history"),
|
|
2438
|
+
logFile: path4.join(globalRoot, "logs", "wrongstack.log"),
|
|
2387
2439
|
projectDir,
|
|
2388
|
-
projectCodebaseIndex:
|
|
2389
|
-
projectMemory:
|
|
2390
|
-
projectSessions:
|
|
2391
|
-
projectTrust:
|
|
2392
|
-
projectMeta:
|
|
2393
|
-
projectLocalConfig:
|
|
2394
|
-
inProjectConfig:
|
|
2395
|
-
inProjectAgentsFile:
|
|
2396
|
-
inProjectSkills:
|
|
2397
|
-
inProjectClaudeSkills:
|
|
2398
|
-
inProjectPrompts:
|
|
2399
|
-
inProjectInstructions:
|
|
2400
|
-
inProjectDesignKits:
|
|
2401
|
-
inProjectWorktrees:
|
|
2440
|
+
projectCodebaseIndex: path4.join(projectDir, "codebase-index"),
|
|
2441
|
+
projectMemory: path4.join(projectDir, "memory.md"),
|
|
2442
|
+
projectSessions: path4.join(projectDir, "sessions"),
|
|
2443
|
+
projectTrust: path4.join(projectDir, "trust.json"),
|
|
2444
|
+
projectMeta: path4.join(projectDir, "meta.json"),
|
|
2445
|
+
projectLocalConfig: path4.join(projectDir, "config.local.json"),
|
|
2446
|
+
inProjectConfig: path4.join(opts.projectRoot, ".wrongstack", "config.json"),
|
|
2447
|
+
inProjectAgentsFile: path4.join(opts.projectRoot, ".wrongstack", "AGENTS.md"),
|
|
2448
|
+
inProjectSkills: path4.join(opts.projectRoot, ".wrongstack", "skills"),
|
|
2449
|
+
inProjectClaudeSkills: path4.join(opts.projectRoot, ".claude", "skills"),
|
|
2450
|
+
inProjectPrompts: path4.join(opts.projectRoot, ".wrongstack", "prompts"),
|
|
2451
|
+
inProjectInstructions: path4.join(opts.projectRoot, ".wrongstack", "instructions"),
|
|
2452
|
+
inProjectDesignKits: path4.join(opts.projectRoot, ".wrongstack", "design-kits"),
|
|
2453
|
+
inProjectWorktrees: path4.join(opts.projectRoot, ".wrongstack", "worktrees"),
|
|
2402
2454
|
projectHash: hash2,
|
|
2403
2455
|
projectSlug: slug,
|
|
2404
|
-
projectGoal:
|
|
2405
|
-
projectInputHistory:
|
|
2406
|
-
projectSpecs:
|
|
2407
|
-
projectTaskGraphs:
|
|
2408
|
-
projectSddSession:
|
|
2409
|
-
projectPlan:
|
|
2410
|
-
projectAutophase:
|
|
2411
|
-
projectSddBoards:
|
|
2412
|
-
syncConfig:
|
|
2413
|
-
|
|
2456
|
+
projectGoal: path4.join(projectDir, "goal.json"),
|
|
2457
|
+
projectInputHistory: path4.join(projectDir, "input-history.json"),
|
|
2458
|
+
projectSpecs: path4.join(projectDir, "specs"),
|
|
2459
|
+
projectTaskGraphs: path4.join(projectDir, "task-graphs"),
|
|
2460
|
+
projectSddSession: path4.join(projectDir, "sdd-session.json"),
|
|
2461
|
+
projectPlan: path4.join(projectDir, "plan.json"),
|
|
2462
|
+
projectAutophase: path4.join(projectDir, "autophase"),
|
|
2463
|
+
projectSddBoards: path4.join(projectDir, "sdd-boards"),
|
|
2464
|
+
syncConfig: path4.join(globalRoot, "sync.json"),
|
|
2465
|
+
configHistoryDir: path4.join(globalRoot, "config-history"),
|
|
2466
|
+
projectStatus: (projectHash2) => path4.join(globalRoot, "projects", projectHash2, "status.json")
|
|
2414
2467
|
};
|
|
2415
2468
|
}
|
|
2416
2469
|
|
|
@@ -3719,7 +3772,7 @@ var TOOLS = {
|
|
|
3719
3772
|
// src/coordination/agents/agent-prompts.ts
|
|
3720
3773
|
import { readFileSync as readFileSync2, statSync as statSync2 } from "node:fs";
|
|
3721
3774
|
import * as os2 from "node:os";
|
|
3722
|
-
import * as
|
|
3775
|
+
import * as path5 from "node:path";
|
|
3723
3776
|
import { fileURLToPath } from "node:url";
|
|
3724
3777
|
var promptCache = /* @__PURE__ */ new Map();
|
|
3725
3778
|
var candidateCache = /* @__PURE__ */ new Map();
|
|
@@ -3732,7 +3785,7 @@ function agentPrompt(id) {
|
|
|
3732
3785
|
let resolved = "";
|
|
3733
3786
|
for (const dir of agentPromptDirCandidates(envDir)) {
|
|
3734
3787
|
try {
|
|
3735
|
-
resolved = readFileSync2(
|
|
3788
|
+
resolved = readFileSync2(path5.join(dir, fileName), "utf8").trimEnd();
|
|
3736
3789
|
break;
|
|
3737
3790
|
} catch {
|
|
3738
3791
|
}
|
|
@@ -3741,20 +3794,20 @@ function agentPrompt(id) {
|
|
|
3741
3794
|
return resolved;
|
|
3742
3795
|
}
|
|
3743
3796
|
function agentPromptDirCandidates(envDir) {
|
|
3744
|
-
const globalRoot = process.env["WRONGSTACK_HOME"] ||
|
|
3797
|
+
const globalRoot = process.env["WRONGSTACK_HOME"] || path5.join(os2.homedir(), ".wrongstack");
|
|
3745
3798
|
const candKey = `${envDir}\0${globalRoot}`;
|
|
3746
3799
|
const cached = candidateCache.get(candKey);
|
|
3747
3800
|
if (cached !== void 0) return cached;
|
|
3748
|
-
const here =
|
|
3801
|
+
const here = path5.dirname(fileURLToPath(import.meta.url));
|
|
3749
3802
|
const explicitDir = envDir || void 0;
|
|
3750
3803
|
const candidates = [
|
|
3751
|
-
...explicitDir ? [
|
|
3752
|
-
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
|
|
3756
|
-
|
|
3757
|
-
|
|
3804
|
+
...explicitDir ? [path5.resolve(explicitDir)] : [],
|
|
3805
|
+
path5.join(globalRoot, "instructions", "agents"),
|
|
3806
|
+
path5.resolve(here, "../../../../instructions/agents"),
|
|
3807
|
+
path5.resolve(here, "../../../instructions/agents"),
|
|
3808
|
+
path5.resolve(here, "../../instructions/agents"),
|
|
3809
|
+
path5.resolve(here, "../instructions/agents"),
|
|
3810
|
+
path5.resolve(here, "instructions/agents")
|
|
3758
3811
|
];
|
|
3759
3812
|
const ordered = candidates.sort((a, b) => Number(!isDirectory(a)) - Number(!isDirectory(b)));
|
|
3760
3813
|
candidateCache.set(candKey, ordered);
|
|
@@ -5357,7 +5410,7 @@ function attachAutoExtend(events, policy = {}) {
|
|
|
5357
5410
|
|
|
5358
5411
|
// src/coordination/delegate-tool.ts
|
|
5359
5412
|
import * as fsp2 from "node:fs/promises";
|
|
5360
|
-
import * as
|
|
5413
|
+
import * as path6 from "node:path";
|
|
5361
5414
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
5362
5415
|
|
|
5363
5416
|
// src/coordination/fleet.ts
|
|
@@ -5978,13 +6031,13 @@ async function readSubagentPartial(opts, subagentId) {
|
|
|
5978
6031
|
if (!opts.sessionsRoot) return void 0;
|
|
5979
6032
|
const candidates = [];
|
|
5980
6033
|
if (opts.directorRunId) {
|
|
5981
|
-
candidates.push(
|
|
6034
|
+
candidates.push(path6.join(opts.sessionsRoot, opts.directorRunId, `${subagentId}.jsonl`));
|
|
5982
6035
|
} else {
|
|
5983
6036
|
try {
|
|
5984
6037
|
const entries = await fsp2.readdir(opts.sessionsRoot, { withFileTypes: true });
|
|
5985
6038
|
for (const entry of entries) {
|
|
5986
6039
|
if (entry.isDirectory()) {
|
|
5987
|
-
candidates.push(
|
|
6040
|
+
candidates.push(path6.join(opts.sessionsRoot, entry.name, `${subagentId}.jsonl`));
|
|
5988
6041
|
}
|
|
5989
6042
|
}
|
|
5990
6043
|
} catch {
|
|
@@ -6031,7 +6084,7 @@ async function readSubagentPartial(opts, subagentId) {
|
|
|
6031
6084
|
// src/coordination/director.ts
|
|
6032
6085
|
import { randomUUID as randomUUID6 } from "node:crypto";
|
|
6033
6086
|
import * as fsp5 from "node:fs/promises";
|
|
6034
|
-
import * as
|
|
6087
|
+
import * as path8 from "node:path";
|
|
6035
6088
|
|
|
6036
6089
|
// src/storage/director-state.ts
|
|
6037
6090
|
import * as fsp3 from "node:fs/promises";
|
|
@@ -7101,7 +7154,7 @@ var DirectorBtwNotes = class {
|
|
|
7101
7154
|
|
|
7102
7155
|
// src/utils/instruction-file.ts
|
|
7103
7156
|
import { readFileSync as readFileSync3, statSync as statSync3 } from "node:fs";
|
|
7104
|
-
import * as
|
|
7157
|
+
import * as path7 from "node:path";
|
|
7105
7158
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
7106
7159
|
var textCache = /* @__PURE__ */ new Map();
|
|
7107
7160
|
var rootCandidates;
|
|
@@ -7111,7 +7164,7 @@ function readBundledInstructionText(relativePath) {
|
|
|
7111
7164
|
let resolved = "";
|
|
7112
7165
|
for (const root of instructionRootCandidates()) {
|
|
7113
7166
|
try {
|
|
7114
|
-
resolved = readFileSync3(
|
|
7167
|
+
resolved = readFileSync3(path7.join(root, relativePath), "utf8").trimEnd();
|
|
7115
7168
|
break;
|
|
7116
7169
|
} catch {
|
|
7117
7170
|
}
|
|
@@ -7127,11 +7180,11 @@ function renderInstructionTemplate(template, values) {
|
|
|
7127
7180
|
}
|
|
7128
7181
|
function instructionRootCandidates() {
|
|
7129
7182
|
if (rootCandidates !== void 0) return rootCandidates;
|
|
7130
|
-
const here =
|
|
7183
|
+
const here = path7.dirname(fileURLToPath2(import.meta.url));
|
|
7131
7184
|
const candidates = [
|
|
7132
|
-
|
|
7133
|
-
|
|
7134
|
-
|
|
7185
|
+
path7.resolve(here, "../../instructions"),
|
|
7186
|
+
path7.resolve(here, "../instructions"),
|
|
7187
|
+
path7.resolve(here, "instructions")
|
|
7135
7188
|
];
|
|
7136
7189
|
rootCandidates = candidates.sort((a, b) => Number(!isDirectory2(a)) - Number(!isDirectory2(b)));
|
|
7137
7190
|
return rootCandidates;
|
|
@@ -10679,6 +10732,10 @@ var Director = class _Director {
|
|
|
10679
10732
|
largeAnswerStore;
|
|
10680
10733
|
/** Shared provider/model status tracker, or undefined. */
|
|
10681
10734
|
statusTracker;
|
|
10735
|
+
/** Session/leader's provider id — absolute last-resort fallback for every spawn. */
|
|
10736
|
+
sessionProvider;
|
|
10737
|
+
/** Session/leader's model id — paired with sessionProvider above. */
|
|
10738
|
+
sessionModel;
|
|
10682
10739
|
constructor(opts) {
|
|
10683
10740
|
this.id = opts.config.coordinatorId || randomUUID6();
|
|
10684
10741
|
this.brain = opts.brain;
|
|
@@ -10719,6 +10776,8 @@ var Director = class _Director {
|
|
|
10719
10776
|
this.fleetManager = opts.fleetManager;
|
|
10720
10777
|
this.statusTracker = opts.statusTracker;
|
|
10721
10778
|
this.logger = opts.logger;
|
|
10779
|
+
this.sessionProvider = opts.sessionProvider;
|
|
10780
|
+
this.sessionModel = opts.sessionModel;
|
|
10722
10781
|
if (this.sharedScratchpadPath) {
|
|
10723
10782
|
void fsp5.mkdir(this.sharedScratchpadPath, { recursive: true }).catch((err) => this.logShutdownError("shared_scratchpad_mkdir", err));
|
|
10724
10783
|
}
|
|
@@ -11227,20 +11286,31 @@ var Director = class _Director {
|
|
|
11227
11286
|
return result.subagentId;
|
|
11228
11287
|
}
|
|
11229
11288
|
resolveSpawnModel(config) {
|
|
11289
|
+
if (config.provider?.trim() === "") config.provider = void 0;
|
|
11290
|
+
if (config.model?.trim() === "") config.model = void 0;
|
|
11230
11291
|
if (!config.model && this.modelMatrix) {
|
|
11231
11292
|
const matrix = typeof this.modelMatrix === "function" ? this.modelMatrix() : this.modelMatrix;
|
|
11232
11293
|
const resolution = resolveModelMatrixResolution(matrix, config.role);
|
|
11233
11294
|
const entry = resolution?.source === "default" && roleNeedsIndependentReviewModel(config.role) ? void 0 : resolution?.entry;
|
|
11234
|
-
if (entry
|
|
11235
|
-
config.model = entry.model;
|
|
11295
|
+
if (entry) {
|
|
11296
|
+
if (entry.model) config.model = entry.model;
|
|
11236
11297
|
if (entry.provider) config.provider = entry.provider;
|
|
11237
11298
|
if (entry.fallbackProfile) config.fallbackProfile = entry.fallbackProfile;
|
|
11238
11299
|
if (entry.modelRuntime) config.modelRuntime = entry.modelRuntime;
|
|
11239
|
-
} else if (entry?.fallbackProfile) {
|
|
11240
|
-
config.fallbackProfile = entry.fallbackProfile;
|
|
11241
|
-
if (entry.modelRuntime) config.modelRuntime = entry.modelRuntime;
|
|
11242
11300
|
}
|
|
11243
11301
|
}
|
|
11302
|
+
if (!config.provider && this.sessionProvider) {
|
|
11303
|
+
config.provider = this.sessionProvider;
|
|
11304
|
+
this.logger?.info(
|
|
11305
|
+
`spawn: provider="${config.provider}" for role "${config.role ?? "?"}" fell back to session provider (matrix resolution left it undefined)`
|
|
11306
|
+
);
|
|
11307
|
+
}
|
|
11308
|
+
if (!config.model && this.sessionModel) {
|
|
11309
|
+
config.model = this.sessionModel;
|
|
11310
|
+
this.logger?.info(
|
|
11311
|
+
`spawn: model="${config.model}" for role "${config.role ?? "?"}" fell back to session model (matrix resolution left it undefined)`
|
|
11312
|
+
);
|
|
11313
|
+
}
|
|
11244
11314
|
if (this.statusTracker && config.provider && config.model) {
|
|
11245
11315
|
if (!this.statusTracker.isAvailable(config.provider, config.model)) {
|
|
11246
11316
|
this.logger?.warn(
|
|
@@ -11455,7 +11525,7 @@ var Director = class _Director {
|
|
|
11455
11525
|
})),
|
|
11456
11526
|
usage: this.usage.snapshot()
|
|
11457
11527
|
};
|
|
11458
|
-
await fsp5.mkdir(
|
|
11528
|
+
await fsp5.mkdir(path8.dirname(this.manifestPath), { recursive: true });
|
|
11459
11529
|
await atomicWrite(this.manifestPath, JSON.stringify(manifest, null, 2), { mode: 384 });
|
|
11460
11530
|
return this.manifestPath;
|
|
11461
11531
|
}
|
|
@@ -11859,7 +11929,7 @@ var Director = class _Director {
|
|
|
11859
11929
|
*/
|
|
11860
11930
|
async readSession(subagentId, tail) {
|
|
11861
11931
|
if (!this.sessionsRoot) return null;
|
|
11862
|
-
const filePath =
|
|
11932
|
+
const filePath = path8.join(this.sessionsRoot, this.directorRunId, `${subagentId}.jsonl`);
|
|
11863
11933
|
let raw;
|
|
11864
11934
|
try {
|
|
11865
11935
|
raw = await fsp5.readFile(filePath, "utf8");
|
|
@@ -12013,19 +12083,19 @@ var Director = class _Director {
|
|
|
12013
12083
|
};
|
|
12014
12084
|
|
|
12015
12085
|
// src/coordination/director-session.ts
|
|
12016
|
-
import * as
|
|
12086
|
+
import * as path13 from "node:path";
|
|
12017
12087
|
|
|
12018
12088
|
// src/storage/session-store.ts
|
|
12019
12089
|
import { createHash as createHash4 } from "node:crypto";
|
|
12020
12090
|
import { createReadStream } from "node:fs";
|
|
12021
12091
|
import * as fsp9 from "node:fs/promises";
|
|
12022
|
-
import * as
|
|
12092
|
+
import * as path12 from "node:path";
|
|
12023
12093
|
import { createInterface } from "node:readline";
|
|
12024
12094
|
|
|
12025
12095
|
// src/storage/file-session-writer.ts
|
|
12026
12096
|
import { closeSync, fsyncSync, openSync, writeSync } from "node:fs";
|
|
12027
12097
|
import * as fsp6 from "node:fs/promises";
|
|
12028
|
-
import * as
|
|
12098
|
+
import * as path9 from "node:path";
|
|
12029
12099
|
|
|
12030
12100
|
// src/storage/session-helpers.ts
|
|
12031
12101
|
function userInputTitle(content) {
|
|
@@ -12042,7 +12112,7 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
12042
12112
|
this.meta = meta;
|
|
12043
12113
|
this.events = events;
|
|
12044
12114
|
this.resumed = opts.resumed ?? false;
|
|
12045
|
-
this.manifestFile = opts.dir ?
|
|
12115
|
+
this.manifestFile = opts.dir ? path9.join(opts.dir, `${path9.basename(id)}.summary.json`) : "";
|
|
12046
12116
|
this.filePath = opts.filePath ?? "";
|
|
12047
12117
|
this.secretScrubber = opts.secretScrubber;
|
|
12048
12118
|
this.checkpointCas = opts.checkpointCas;
|
|
@@ -12824,7 +12894,7 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
12824
12894
|
import { spawn } from "node:child_process";
|
|
12825
12895
|
import { createHash as createHash2, randomUUID as randomUUID7 } from "node:crypto";
|
|
12826
12896
|
import * as fsp7 from "node:fs/promises";
|
|
12827
|
-
import * as
|
|
12897
|
+
import * as path10 from "node:path";
|
|
12828
12898
|
|
|
12829
12899
|
// src/storage/storage-concurrency.ts
|
|
12830
12900
|
async function mapWithConcurrency(items, concurrency, mapper) {
|
|
@@ -12855,29 +12925,29 @@ function sha256(content) {
|
|
|
12855
12925
|
return createHash2("sha256").update(content).digest("hex");
|
|
12856
12926
|
}
|
|
12857
12927
|
function isInside(root, target) {
|
|
12858
|
-
const
|
|
12859
|
-
return
|
|
12928
|
+
const relative6 = path10.relative(root, target);
|
|
12929
|
+
return relative6 === "" || !relative6.startsWith("..") && !path10.isAbsolute(relative6);
|
|
12860
12930
|
}
|
|
12861
12931
|
function normalizeRelative(input) {
|
|
12862
|
-
if (!input ||
|
|
12932
|
+
if (!input || path10.isAbsolute(input)) return null;
|
|
12863
12933
|
const normalized = input.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
12864
|
-
const resolved =
|
|
12934
|
+
const resolved = path10.posix.normalize(normalized);
|
|
12865
12935
|
if (!resolved || resolved === "." || resolved === ".." || resolved.startsWith("../")) return null;
|
|
12866
12936
|
return resolved;
|
|
12867
12937
|
}
|
|
12868
12938
|
function parseNulPaths(output) {
|
|
12869
12939
|
return output.split("\0").map(normalizeRelative).filter((value) => value !== null);
|
|
12870
12940
|
}
|
|
12871
|
-
function isWrongStackWorktreePath(
|
|
12872
|
-
return
|
|
12941
|
+
function isWrongStackWorktreePath(relative6) {
|
|
12942
|
+
return relative6 === ".wrongstack/worktrees" || relative6.startsWith(".wrongstack/worktrees/");
|
|
12873
12943
|
}
|
|
12874
12944
|
var SessionCheckpointCas = class {
|
|
12875
12945
|
rootDir;
|
|
12876
12946
|
projectRoot;
|
|
12877
12947
|
runGit;
|
|
12878
12948
|
constructor(opts) {
|
|
12879
|
-
this.rootDir =
|
|
12880
|
-
this.projectRoot =
|
|
12949
|
+
this.rootDir = path10.resolve(opts.rootDir);
|
|
12950
|
+
this.projectRoot = path10.resolve(opts.projectRoot);
|
|
12881
12951
|
this.runGit = opts.runGit ?? defaultRunGit;
|
|
12882
12952
|
}
|
|
12883
12953
|
async capture(_sessionId, _promptIndex) {
|
|
@@ -12895,46 +12965,46 @@ var SessionCheckpointCas = class {
|
|
|
12895
12965
|
}
|
|
12896
12966
|
const relativePaths = [
|
|
12897
12967
|
.../* @__PURE__ */ new Set([...parseNulPaths(tracked.stdout), ...parseNulPaths(untracked.stdout)])
|
|
12898
|
-
].filter((
|
|
12968
|
+
].filter((relative6) => !isWrongStackWorktreePath(relative6)).sort();
|
|
12899
12969
|
const unresolved = [];
|
|
12900
12970
|
let scheduledBytes = 0;
|
|
12901
12971
|
const captured = await mapWithConcurrency(
|
|
12902
12972
|
relativePaths,
|
|
12903
12973
|
CAPTURE_CONCURRENCY,
|
|
12904
|
-
async (
|
|
12905
|
-
const absolute =
|
|
12974
|
+
async (relative6) => {
|
|
12975
|
+
const absolute = path10.resolve(this.projectRoot, ...relative6.split("/"));
|
|
12906
12976
|
if (!isInside(this.projectRoot, absolute)) {
|
|
12907
|
-
unresolved.push({ path:
|
|
12977
|
+
unresolved.push({ path: relative6, reason: "path escapes project root" });
|
|
12908
12978
|
return null;
|
|
12909
12979
|
}
|
|
12910
12980
|
try {
|
|
12911
12981
|
const stat13 = await fsp7.lstat(absolute);
|
|
12912
12982
|
if (stat13.isSymbolicLink()) {
|
|
12913
12983
|
const linkTarget = await fsp7.readlink(absolute);
|
|
12914
|
-
const resolvedLink =
|
|
12915
|
-
if (
|
|
12984
|
+
const resolvedLink = path10.resolve(path10.dirname(absolute), linkTarget);
|
|
12985
|
+
if (path10.isAbsolute(linkTarget) || !isInside(this.projectRoot, resolvedLink)) {
|
|
12916
12986
|
unresolved.push({
|
|
12917
|
-
path:
|
|
12987
|
+
path: relative6,
|
|
12918
12988
|
reason: "symlink target escapes project root"
|
|
12919
12989
|
});
|
|
12920
12990
|
return null;
|
|
12921
12991
|
}
|
|
12922
|
-
return { path:
|
|
12992
|
+
return { path: relative6, state: "symlink", linkTarget };
|
|
12923
12993
|
}
|
|
12924
12994
|
if (!stat13.isFile()) {
|
|
12925
|
-
unresolved.push({ path:
|
|
12995
|
+
unresolved.push({ path: relative6, reason: "changed path is not a regular file" });
|
|
12926
12996
|
return null;
|
|
12927
12997
|
}
|
|
12928
12998
|
if (stat13.size > MAX_BLOB_BYTES) {
|
|
12929
12999
|
unresolved.push({
|
|
12930
|
-
path:
|
|
13000
|
+
path: relative6,
|
|
12931
13001
|
reason: `file exceeds ${MAX_BLOB_BYTES}-byte checkpoint blob limit`
|
|
12932
13002
|
});
|
|
12933
13003
|
return null;
|
|
12934
13004
|
}
|
|
12935
13005
|
if (scheduledBytes + stat13.size > MAX_CHECKPOINT_BYTES) {
|
|
12936
13006
|
unresolved.push({
|
|
12937
|
-
path:
|
|
13007
|
+
path: relative6,
|
|
12938
13008
|
reason: `checkpoint exceeds ${MAX_CHECKPOINT_BYTES}-byte aggregate blob limit`
|
|
12939
13009
|
});
|
|
12940
13010
|
return null;
|
|
@@ -12943,12 +13013,12 @@ var SessionCheckpointCas = class {
|
|
|
12943
13013
|
const content = await fsp7.readFile(absolute);
|
|
12944
13014
|
const blobHash = sha256(content);
|
|
12945
13015
|
await this.putBlob(blobHash, content);
|
|
12946
|
-
return { path:
|
|
13016
|
+
return { path: relative6, state: "file", blobHash, mode: stat13.mode & 511 };
|
|
12947
13017
|
} catch (err) {
|
|
12948
13018
|
if (err.code === "ENOENT") {
|
|
12949
|
-
return { path:
|
|
13019
|
+
return { path: relative6, state: "absent" };
|
|
12950
13020
|
}
|
|
12951
|
-
unresolved.push({ path:
|
|
13021
|
+
unresolved.push({ path: relative6, reason: toErrorMessage(err) });
|
|
12952
13022
|
return null;
|
|
12953
13023
|
}
|
|
12954
13024
|
}
|
|
@@ -12974,7 +13044,7 @@ var SessionCheckpointCas = class {
|
|
|
12974
13044
|
};
|
|
12975
13045
|
}
|
|
12976
13046
|
async materialize(checkpoint, targetRoot) {
|
|
12977
|
-
const target =
|
|
13047
|
+
const target = path10.resolve(targetRoot);
|
|
12978
13048
|
if (target === this.projectRoot) {
|
|
12979
13049
|
throw new Error("Refusing to materialize a workspace checkpoint over the parent project root");
|
|
12980
13050
|
}
|
|
@@ -13014,10 +13084,10 @@ var SessionCheckpointCas = class {
|
|
|
13014
13084
|
try {
|
|
13015
13085
|
const output = await this.safeOutputPath(target, realTarget, entry.path);
|
|
13016
13086
|
if (entry.state === "symlink") {
|
|
13017
|
-
if (
|
|
13087
|
+
if (path10.isAbsolute(entry.linkTarget)) {
|
|
13018
13088
|
throw new Error("absolute symlink target refused");
|
|
13019
13089
|
}
|
|
13020
|
-
const resolvedLink =
|
|
13090
|
+
const resolvedLink = path10.resolve(path10.dirname(output), entry.linkTarget);
|
|
13021
13091
|
if (!isInside(target, resolvedLink)) throw new Error("symlink target escapes checkpoint root");
|
|
13022
13092
|
}
|
|
13023
13093
|
prepared.push({
|
|
@@ -13045,7 +13115,7 @@ var SessionCheckpointCas = class {
|
|
|
13045
13115
|
await fsp7.unlink(output).catch((err) => {
|
|
13046
13116
|
if (err.code !== "ENOENT") throw err;
|
|
13047
13117
|
});
|
|
13048
|
-
await fsp7.mkdir(
|
|
13118
|
+
await fsp7.mkdir(path10.dirname(output), { recursive: true });
|
|
13049
13119
|
await fsp7.symlink(entry.linkTarget, output);
|
|
13050
13120
|
writtenFiles.push(entry.path);
|
|
13051
13121
|
continue;
|
|
@@ -13062,15 +13132,15 @@ var SessionCheckpointCas = class {
|
|
|
13062
13132
|
}
|
|
13063
13133
|
objectPath(hash2) {
|
|
13064
13134
|
if (!HASH_RE.test(hash2)) throw new Error(`Invalid CAS object hash: ${hash2}`);
|
|
13065
|
-
return
|
|
13135
|
+
return path10.join(this.rootDir, "objects", hash2.slice(0, 2), hash2.slice(2));
|
|
13066
13136
|
}
|
|
13067
13137
|
manifestPath(hash2) {
|
|
13068
13138
|
if (!HASH_RE.test(hash2)) throw new Error(`Invalid checkpoint manifest hash: ${hash2}`);
|
|
13069
|
-
return
|
|
13139
|
+
return path10.join(this.rootDir, "manifests", `${hash2}.json`);
|
|
13070
13140
|
}
|
|
13071
13141
|
async putBlob(hash2, content) {
|
|
13072
13142
|
const target = this.objectPath(hash2);
|
|
13073
|
-
await fsp7.mkdir(
|
|
13143
|
+
await fsp7.mkdir(path10.dirname(target), { recursive: true });
|
|
13074
13144
|
try {
|
|
13075
13145
|
const existing = await fsp7.readFile(target);
|
|
13076
13146
|
if (sha256(existing) !== hash2) throw new Error(`Corrupt CAS object collision: ${hash2}`);
|
|
@@ -13078,9 +13148,9 @@ var SessionCheckpointCas = class {
|
|
|
13078
13148
|
} catch (err) {
|
|
13079
13149
|
if (err.code !== "ENOENT") throw err;
|
|
13080
13150
|
}
|
|
13081
|
-
const temp =
|
|
13082
|
-
|
|
13083
|
-
`.${
|
|
13151
|
+
const temp = path10.join(
|
|
13152
|
+
path10.dirname(target),
|
|
13153
|
+
`.${path10.basename(target)}.${process.pid}.${randomUUID7()}.tmp`
|
|
13084
13154
|
);
|
|
13085
13155
|
let handle;
|
|
13086
13156
|
try {
|
|
@@ -13138,10 +13208,10 @@ var SessionCheckpointCas = class {
|
|
|
13138
13208
|
}
|
|
13139
13209
|
return parsed;
|
|
13140
13210
|
}
|
|
13141
|
-
async safeOutputPath(target, realTarget,
|
|
13142
|
-
const normalized = normalizeRelative(
|
|
13211
|
+
async safeOutputPath(target, realTarget, relative6) {
|
|
13212
|
+
const normalized = normalizeRelative(relative6);
|
|
13143
13213
|
if (!normalized) throw new Error("invalid relative path");
|
|
13144
|
-
const output =
|
|
13214
|
+
const output = path10.resolve(target, ...normalized.split("/"));
|
|
13145
13215
|
if (!isInside(target, output)) throw new Error("path escapes checkpoint target");
|
|
13146
13216
|
let probe = output;
|
|
13147
13217
|
for (; ; ) {
|
|
@@ -13151,7 +13221,7 @@ var SessionCheckpointCas = class {
|
|
|
13151
13221
|
return output;
|
|
13152
13222
|
} catch (err) {
|
|
13153
13223
|
if (err.code !== "ENOENT") throw err;
|
|
13154
|
-
const parent =
|
|
13224
|
+
const parent = path10.dirname(probe);
|
|
13155
13225
|
if (parent === probe) throw err;
|
|
13156
13226
|
probe = parent;
|
|
13157
13227
|
}
|
|
@@ -13217,13 +13287,13 @@ function generateSessionId(startedAt, _model) {
|
|
|
13217
13287
|
// src/storage/session-resume-validation.ts
|
|
13218
13288
|
import { createHash as createHash3 } from "node:crypto";
|
|
13219
13289
|
import * as fsp8 from "node:fs/promises";
|
|
13220
|
-
import * as
|
|
13290
|
+
import * as path11 from "node:path";
|
|
13221
13291
|
var MAX_REVALIDATE_BYTES = 5 * 1024 * 1024;
|
|
13222
13292
|
var VALIDATION_CONCURRENCY = 8;
|
|
13223
13293
|
var NOTICE_PATH_LIMIT = 20;
|
|
13224
13294
|
function isInside2(root, target) {
|
|
13225
|
-
const
|
|
13226
|
-
return
|
|
13295
|
+
const relative6 = path11.relative(root, target);
|
|
13296
|
+
return relative6 === "" || !relative6.startsWith("..") && !path11.isAbsolute(relative6);
|
|
13227
13297
|
}
|
|
13228
13298
|
function errno(err) {
|
|
13229
13299
|
return err && typeof err === "object" && "code" in err ? String(err.code) : void 0;
|
|
@@ -13234,7 +13304,7 @@ function latestObservations(events, projectRoot) {
|
|
|
13234
13304
|
if (event.type !== "file_observation" || typeof event.path !== "string" || event.path.length === 0 || typeof event.hash !== "string" || !/^[a-f\d]{64}$/i.test(event.hash)) {
|
|
13235
13305
|
continue;
|
|
13236
13306
|
}
|
|
13237
|
-
const normalized =
|
|
13307
|
+
const normalized = path11.resolve(projectRoot, event.path);
|
|
13238
13308
|
latest.set(normalized, {
|
|
13239
13309
|
path: normalized,
|
|
13240
13310
|
hash: event.hash.toLowerCase(),
|
|
@@ -13292,7 +13362,7 @@ async function validateOne(observation, lexicalRoot, realRoot) {
|
|
|
13292
13362
|
}
|
|
13293
13363
|
}
|
|
13294
13364
|
async function validateResumeFileObservations(events, projectRoot) {
|
|
13295
|
-
const lexicalRoot =
|
|
13365
|
+
const lexicalRoot = path11.resolve(projectRoot);
|
|
13296
13366
|
const realRoot = await fsp8.realpath(lexicalRoot).catch(() => lexicalRoot);
|
|
13297
13367
|
const observations = latestObservations(events, lexicalRoot);
|
|
13298
13368
|
const results = await mapWithConcurrency(
|
|
@@ -13308,10 +13378,10 @@ async function validateResumeFileObservations(events, projectRoot) {
|
|
|
13308
13378
|
}
|
|
13309
13379
|
function formatResumeValidationNotice(validation, projectRoot) {
|
|
13310
13380
|
if (validation.staleFiles.length === 0) return null;
|
|
13311
|
-
const root =
|
|
13381
|
+
const root = path11.resolve(projectRoot);
|
|
13312
13382
|
const shown = validation.staleFiles.slice(0, NOTICE_PATH_LIMIT).map((entry) => {
|
|
13313
|
-
const
|
|
13314
|
-
const display = isInside2(root, entry.path) ?
|
|
13383
|
+
const relative6 = path11.relative(root, entry.path);
|
|
13384
|
+
const display = isInside2(root, entry.path) ? relative6 || "." : entry.path;
|
|
13315
13385
|
return `- ${JSON.stringify(display)} [${entry.status}]`;
|
|
13316
13386
|
});
|
|
13317
13387
|
const omitted = validation.staleFiles.length - shown.length;
|
|
@@ -13444,9 +13514,9 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
13444
13514
|
static LIST_SCAN_CONCURRENCY = 32;
|
|
13445
13515
|
constructor(opts) {
|
|
13446
13516
|
this.dir = opts.dir;
|
|
13447
|
-
this.projectRoot = opts.projectRoot ?
|
|
13517
|
+
this.projectRoot = opts.projectRoot ? path12.resolve(opts.projectRoot) : void 0;
|
|
13448
13518
|
this.checkpointCas = this.projectRoot ? new SessionCheckpointCas({
|
|
13449
|
-
rootDir:
|
|
13519
|
+
rootDir: path12.join(this.dir, "_cas"),
|
|
13450
13520
|
projectRoot: this.projectRoot
|
|
13451
13521
|
}) : void 0;
|
|
13452
13522
|
this.events = opts.events;
|
|
@@ -13512,17 +13582,17 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
13512
13582
|
}
|
|
13513
13583
|
/** Absolute path to the session index file. */
|
|
13514
13584
|
get indexFile() {
|
|
13515
|
-
return
|
|
13585
|
+
return path12.join(this.dir, "_index.jsonl");
|
|
13516
13586
|
}
|
|
13517
13587
|
/** Join session ID to its absolute path within the store directory. */
|
|
13518
13588
|
sessionPath(id, ext) {
|
|
13519
13589
|
return sessionScopedPath(this.dir, id, ext);
|
|
13520
13590
|
}
|
|
13521
13591
|
shardManifestPath(shardKey) {
|
|
13522
|
-
return shardKey ?
|
|
13592
|
+
return shardKey ? path12.join(this.dir, shardKey, "_manifest.json") : path12.join(this.dir, "_manifest.json");
|
|
13523
13593
|
}
|
|
13524
13594
|
shardKeyForSessionId(id) {
|
|
13525
|
-
const dirName =
|
|
13595
|
+
const dirName = path12.dirname(id);
|
|
13526
13596
|
return dirName === "." ? "" : dirName;
|
|
13527
13597
|
}
|
|
13528
13598
|
invalidateShardManifestBySessionId(id) {
|
|
@@ -13534,7 +13604,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
13534
13604
|
* subdirectory so sessions group naturally by day.
|
|
13535
13605
|
*/
|
|
13536
13606
|
async ensureShardDir(id) {
|
|
13537
|
-
const dirPath =
|
|
13607
|
+
const dirPath = path12.dirname(sessionScopedPath(this.dir, id, ""));
|
|
13538
13608
|
await ensureDir(dirPath);
|
|
13539
13609
|
return dirPath;
|
|
13540
13610
|
}
|
|
@@ -13698,7 +13768,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
13698
13768
|
// Shard directory (sessions/<date>/) — must match create() so the
|
|
13699
13769
|
// .summary.json sidecar lands next to the JSONL instead of the
|
|
13700
13770
|
// sessions root (where summaryFor() would never find it).
|
|
13701
|
-
dir:
|
|
13771
|
+
dir: path12.dirname(file),
|
|
13702
13772
|
filePath: file,
|
|
13703
13773
|
secretScrubber: this.secretScrubber,
|
|
13704
13774
|
checkpointCas: this.checkpointCas,
|
|
@@ -14319,7 +14389,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
14319
14389
|
return entry;
|
|
14320
14390
|
}
|
|
14321
14391
|
async collectSessionFilesInShard(shardKey) {
|
|
14322
|
-
const dir = shardKey ?
|
|
14392
|
+
const dir = shardKey ? path12.join(this.dir, shardKey) : this.dir;
|
|
14323
14393
|
const entries = await this.collectSessionFiles(dir, shardKey);
|
|
14324
14394
|
return shardKey ? entries.filter((entry) => entry.id.startsWith(`${shardKey}/`)) : entries.filter((entry) => !entry.id.includes("/"));
|
|
14325
14395
|
}
|
|
@@ -14342,13 +14412,13 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
14342
14412
|
if (entry.name === "_index.jsonl") continue;
|
|
14343
14413
|
const base = entry.name.replace(/\.jsonl$/, "");
|
|
14344
14414
|
const id = prefix ? `${prefix}/${base}` : base;
|
|
14345
|
-
files.push({ id, filePath:
|
|
14415
|
+
files.push({ id, filePath: path12.join(dir, entry.name) });
|
|
14346
14416
|
}
|
|
14347
14417
|
}
|
|
14348
14418
|
const childFileArrays = await Promise.all(
|
|
14349
14419
|
dirEntries.map((entry) => {
|
|
14350
14420
|
const childPrefix = depth === 0 ? entry.name : `${prefix}/${entry.name}`;
|
|
14351
|
-
return this.collectSessionFiles(
|
|
14421
|
+
return this.collectSessionFiles(path12.join(dir, entry.name), childPrefix, depth + 1);
|
|
14352
14422
|
})
|
|
14353
14423
|
);
|
|
14354
14424
|
return [...childFileArrays.flat(), ...files];
|
|
@@ -14381,7 +14451,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
14381
14451
|
const childIdArrays = await Promise.all(
|
|
14382
14452
|
dirEntries.map((entry) => {
|
|
14383
14453
|
const childPrefix = depth === 0 ? entry.name : `${prefix}/${entry.name}`;
|
|
14384
|
-
return this.collectSessionIds(
|
|
14454
|
+
return this.collectSessionIds(path12.join(dir, entry.name), childPrefix, depth + 1);
|
|
14385
14455
|
})
|
|
14386
14456
|
);
|
|
14387
14457
|
return [...childIdArrays.flat(), ...fileIds];
|
|
@@ -14496,9 +14566,9 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
14496
14566
|
async deleteSession(id) {
|
|
14497
14567
|
const jsonlPath = this.sessionPath(id, ".jsonl");
|
|
14498
14568
|
const summaryPath = this.sessionPath(id, ".summary.json");
|
|
14499
|
-
const shardDir =
|
|
14500
|
-
const base =
|
|
14501
|
-
const sessDir =
|
|
14569
|
+
const shardDir = path12.dirname(jsonlPath);
|
|
14570
|
+
const base = path12.basename(id);
|
|
14571
|
+
const sessDir = path12.join(shardDir, base);
|
|
14502
14572
|
const deletions = [
|
|
14503
14573
|
fsp9.unlink(jsonlPath),
|
|
14504
14574
|
fsp9.unlink(summaryPath),
|
|
@@ -14543,7 +14613,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
14543
14613
|
*/
|
|
14544
14614
|
async readActiveSessionId() {
|
|
14545
14615
|
try {
|
|
14546
|
-
const raw = await fsp9.readFile(
|
|
14616
|
+
const raw = await fsp9.readFile(path12.join(this.dir, "active.json"), "utf8");
|
|
14547
14617
|
const active = JSON.parse(raw);
|
|
14548
14618
|
return active.sessionId ?? null;
|
|
14549
14619
|
} catch {
|
|
@@ -14606,7 +14676,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
14606
14676
|
const activeSessionId = await this.readActiveSessionId();
|
|
14607
14677
|
const isPrunableJsonl = (name) => name.endsWith(".jsonl") && name !== "_index.jsonl" && name !== "_mailbox.jsonl" && !name.endsWith(".replay.jsonl") && !name.endsWith(".audit.jsonl");
|
|
14608
14678
|
const pruneFile = async (dir, name, prefix) => {
|
|
14609
|
-
const jsonlPath =
|
|
14679
|
+
const jsonlPath = path12.join(dir, name);
|
|
14610
14680
|
try {
|
|
14611
14681
|
const stat13 = await fsp9.stat(jsonlPath);
|
|
14612
14682
|
if (stat13.mtimeMs >= cutoff) return;
|
|
@@ -14626,7 +14696,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
14626
14696
|
continue;
|
|
14627
14697
|
}
|
|
14628
14698
|
if (!entry.isDirectory()) continue;
|
|
14629
|
-
const dateDir =
|
|
14699
|
+
const dateDir = path12.join(this.dir, entry.name);
|
|
14630
14700
|
const files = await fsp9.readdir(dateDir, { withFileTypes: true }).catch(() => []);
|
|
14631
14701
|
for (const file of files) {
|
|
14632
14702
|
if (!file.isFile() || !isPrunableJsonl(file.name)) continue;
|
|
@@ -14638,7 +14708,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
14638
14708
|
}
|
|
14639
14709
|
for (const entry of entries) {
|
|
14640
14710
|
if (!entry.isDirectory()) continue;
|
|
14641
|
-
const dateDir =
|
|
14711
|
+
const dateDir = path12.join(this.dir, entry.name);
|
|
14642
14712
|
try {
|
|
14643
14713
|
const remaining = await fsp9.readdir(dateDir);
|
|
14644
14714
|
if (remaining.length === 0) {
|
|
@@ -14769,9 +14839,9 @@ function makeDirectorSessionFactory(opts) {
|
|
|
14769
14839
|
let dir;
|
|
14770
14840
|
if (opts.store) {
|
|
14771
14841
|
store = opts.store;
|
|
14772
|
-
dir = opts.sessionsRoot ?
|
|
14842
|
+
dir = opts.sessionsRoot ? path13.join(opts.sessionsRoot, runId) : "(caller-managed)";
|
|
14773
14843
|
} else if (opts.sessionsRoot) {
|
|
14774
|
-
dir =
|
|
14844
|
+
dir = path13.join(opts.sessionsRoot, runId);
|
|
14775
14845
|
store = new DefaultSessionStore({ dir });
|
|
14776
14846
|
} else {
|
|
14777
14847
|
throw new Error("makeDirectorSessionFactory requires either `store` or `sessionsRoot`");
|
|
@@ -16117,8 +16187,8 @@ function isDesignStack(v) {
|
|
|
16117
16187
|
|
|
16118
16188
|
// src/execution/design-kit-loader.ts
|
|
16119
16189
|
import { existsSync } from "node:fs";
|
|
16120
|
-
import * as
|
|
16121
|
-
import * as
|
|
16190
|
+
import * as fs4 from "node:fs/promises";
|
|
16191
|
+
import * as path14 from "node:path";
|
|
16122
16192
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
16123
16193
|
var KIT_FILE = "KIT.md";
|
|
16124
16194
|
var TOKENS_FILE = "tokens.json";
|
|
@@ -16225,15 +16295,15 @@ var DefaultDesignKitLoader = class {
|
|
|
16225
16295
|
for (const { dir, source } of this.dirs) {
|
|
16226
16296
|
let entries;
|
|
16227
16297
|
try {
|
|
16228
|
-
entries = await
|
|
16298
|
+
entries = await fs4.readdir(dir, { withFileTypes: true });
|
|
16229
16299
|
} catch {
|
|
16230
16300
|
continue;
|
|
16231
16301
|
}
|
|
16232
16302
|
for (const e of entries) {
|
|
16233
16303
|
if (!e.isDirectory()) continue;
|
|
16234
|
-
const kitFile =
|
|
16304
|
+
const kitFile = path14.join(dir, e.name, KIT_FILE);
|
|
16235
16305
|
try {
|
|
16236
|
-
const raw = await
|
|
16306
|
+
const raw = await fs4.readFile(kitFile, "utf8");
|
|
16237
16307
|
const fm = parseKitFrontmatter(raw);
|
|
16238
16308
|
const id = fm.id ?? e.name;
|
|
16239
16309
|
if (!fm.name) continue;
|
|
@@ -16294,7 +16364,7 @@ var DefaultDesignKitLoader = class {
|
|
|
16294
16364
|
if (cached !== void 0) return cached;
|
|
16295
16365
|
const m = await this.find(id);
|
|
16296
16366
|
if (!m) throw new Error(`Design kit "${id}" not found`);
|
|
16297
|
-
const raw = await
|
|
16367
|
+
const raw = await fs4.readFile(m.path, "utf8");
|
|
16298
16368
|
const body = narrowStackSections(stripFrontmatter(raw), stack);
|
|
16299
16369
|
this.bodyCache.set(key, body);
|
|
16300
16370
|
return body;
|
|
@@ -16305,9 +16375,9 @@ var DefaultDesignKitLoader = class {
|
|
|
16305
16375
|
const m = await this.find(id);
|
|
16306
16376
|
let tokens;
|
|
16307
16377
|
if (m) {
|
|
16308
|
-
const tokensPath =
|
|
16378
|
+
const tokensPath = path14.join(path14.dirname(m.path), TOKENS_FILE);
|
|
16309
16379
|
try {
|
|
16310
|
-
const raw = await
|
|
16380
|
+
const raw = await fs4.readFile(tokensPath, "utf8");
|
|
16311
16381
|
const parsed = JSON.parse(raw);
|
|
16312
16382
|
tokens = parsed;
|
|
16313
16383
|
} catch {
|
|
@@ -16332,12 +16402,12 @@ var DefaultDesignKitLoader = class {
|
|
|
16332
16402
|
};
|
|
16333
16403
|
function resolveBundledDesignKitsDir() {
|
|
16334
16404
|
try {
|
|
16335
|
-
const here =
|
|
16405
|
+
const here = path14.dirname(fileURLToPath3(import.meta.url));
|
|
16336
16406
|
const candidates = [
|
|
16337
|
-
|
|
16338
|
-
|
|
16339
|
-
|
|
16340
|
-
|
|
16407
|
+
path14.join(here, "design-kits"),
|
|
16408
|
+
path14.join(here, "..", "design-kits"),
|
|
16409
|
+
path14.join(here, "..", "..", "design-kits"),
|
|
16410
|
+
path14.join(here, "..", "..", "..", "design-kits")
|
|
16341
16411
|
];
|
|
16342
16412
|
for (const c of candidates) {
|
|
16343
16413
|
if (existsSync(c)) return c;
|
|
@@ -16365,11 +16435,11 @@ function _resetDesignKitLoaderMemo() {
|
|
|
16365
16435
|
|
|
16366
16436
|
// src/execution/design-project-store.ts
|
|
16367
16437
|
import { existsSync as existsSync2 } from "node:fs";
|
|
16368
|
-
import * as
|
|
16369
|
-
import * as
|
|
16438
|
+
import * as fs5 from "node:fs/promises";
|
|
16439
|
+
import * as path15 from "node:path";
|
|
16370
16440
|
var DESIGN_DIR = ".design";
|
|
16371
16441
|
function designProjectDir(projectRoot) {
|
|
16372
|
-
return
|
|
16442
|
+
return path15.join(projectRoot, DESIGN_DIR);
|
|
16373
16443
|
}
|
|
16374
16444
|
var RULE_FILES = ["rules.md", "RULES.md", "design.md"];
|
|
16375
16445
|
var rulesCache = /* @__PURE__ */ new Map();
|
|
@@ -16378,7 +16448,7 @@ async function loadProjectDesignRules(projectRoot) {
|
|
|
16378
16448
|
let rules;
|
|
16379
16449
|
for (const name of RULE_FILES) {
|
|
16380
16450
|
try {
|
|
16381
|
-
const txt = await
|
|
16451
|
+
const txt = await fs5.readFile(path15.join(designProjectDir(projectRoot), name), "utf8");
|
|
16382
16452
|
if (txt.trim()) {
|
|
16383
16453
|
rules = txt.trim();
|
|
16384
16454
|
break;
|
|
@@ -16399,7 +16469,7 @@ function parseOverrides(value) {
|
|
|
16399
16469
|
}
|
|
16400
16470
|
async function loadActiveKit(projectRoot) {
|
|
16401
16471
|
try {
|
|
16402
|
-
const raw = await
|
|
16472
|
+
const raw = await fs5.readFile(path15.join(designProjectDir(projectRoot), "active.json"), "utf8");
|
|
16403
16473
|
const parsed = JSON.parse(raw);
|
|
16404
16474
|
if (parsed && typeof parsed.kit === "string") {
|
|
16405
16475
|
return {
|
|
@@ -16432,11 +16502,11 @@ function applyTokenOverrides(tokens, overrides) {
|
|
|
16432
16502
|
}
|
|
16433
16503
|
async function ensureDesignDir(projectRoot) {
|
|
16434
16504
|
const dir = designProjectDir(projectRoot);
|
|
16435
|
-
await
|
|
16436
|
-
const gi =
|
|
16505
|
+
await fs5.mkdir(dir, { recursive: true });
|
|
16506
|
+
const gi = path15.join(dir, ".gitignore");
|
|
16437
16507
|
if (!existsSync2(gi)) {
|
|
16438
16508
|
try {
|
|
16439
|
-
await
|
|
16509
|
+
await fs5.writeFile(gi, "*\n");
|
|
16440
16510
|
} catch {
|
|
16441
16511
|
}
|
|
16442
16512
|
}
|
|
@@ -16447,11 +16517,11 @@ async function recordKitChoice(projectRoot, kit, stack, source, isoTime, overrid
|
|
|
16447
16517
|
const dir = await ensureDesignDir(projectRoot);
|
|
16448
16518
|
const record = { kit, stack: stack ?? null };
|
|
16449
16519
|
if (overrides && Object.keys(overrides).length > 0) record.overrides = overrides;
|
|
16450
|
-
await
|
|
16520
|
+
await fs5.writeFile(path15.join(dir, "active.json"), `${JSON.stringify(record, null, 2)}
|
|
16451
16521
|
`);
|
|
16452
16522
|
const line = `- ${isoTime} \xB7 kit=${kit}${stack ? ` stack=${stack}` : ""} \xB7 via=${source}
|
|
16453
16523
|
`;
|
|
16454
|
-
await
|
|
16524
|
+
await fs5.appendFile(path15.join(dir, "decisions.md"), line);
|
|
16455
16525
|
} catch {
|
|
16456
16526
|
}
|
|
16457
16527
|
}
|
|
@@ -16467,11 +16537,11 @@ async function recordOverrides(projectRoot, patch, isoTime) {
|
|
|
16467
16537
|
const dir = await ensureDesignDir(projectRoot);
|
|
16468
16538
|
const record = { kit: active.kit, stack: active.stack ?? null };
|
|
16469
16539
|
if (Object.keys(merged).length > 0) record.overrides = merged;
|
|
16470
|
-
await
|
|
16540
|
+
await fs5.writeFile(path15.join(dir, "active.json"), `${JSON.stringify(record, null, 2)}
|
|
16471
16541
|
`);
|
|
16472
16542
|
const keys = Object.keys(patch).join(",");
|
|
16473
|
-
await
|
|
16474
|
-
|
|
16543
|
+
await fs5.appendFile(
|
|
16544
|
+
path15.join(dir, "decisions.md"),
|
|
16475
16545
|
`- ${isoTime} \xB7 kit=${active.kit} \xB7 override=${keys} \xB7 via=set
|
|
16476
16546
|
`
|
|
16477
16547
|
);
|
|
@@ -16481,7 +16551,7 @@ async function recordOverrides(projectRoot, patch, isoTime) {
|
|
|
16481
16551
|
}
|
|
16482
16552
|
async function clearPersistedActiveKit(projectRoot) {
|
|
16483
16553
|
try {
|
|
16484
|
-
await
|
|
16554
|
+
await fs5.rm(path15.join(designProjectDir(projectRoot), "active.json"), { force: true });
|
|
16485
16555
|
} catch {
|
|
16486
16556
|
}
|
|
16487
16557
|
}
|
|
@@ -16597,12 +16667,12 @@ function verifyFiles(tokens, files) {
|
|
|
16597
16667
|
const violations = [];
|
|
16598
16668
|
let onPalette = 0;
|
|
16599
16669
|
let offPalette = 0;
|
|
16600
|
-
for (const { path:
|
|
16670
|
+
for (const { path: path30, text } of files) {
|
|
16601
16671
|
const lines = text.split("\n");
|
|
16602
16672
|
lines.forEach((lineText, i) => {
|
|
16603
16673
|
const lineNo = i + 1;
|
|
16604
16674
|
const flag = (snippet, reason) => {
|
|
16605
|
-
violations.push({ file:
|
|
16675
|
+
violations.push({ file: path30, line: lineNo, snippet: snippet.slice(0, 80), reason });
|
|
16606
16676
|
};
|
|
16607
16677
|
for (const re of [HEX_RE, FUNC_COLOR_RE]) {
|
|
16608
16678
|
re.lastIndex = 0;
|
|
@@ -16654,14 +16724,14 @@ var SKIP_DIR = /* @__PURE__ */ new Set([
|
|
|
16654
16724
|
".design"
|
|
16655
16725
|
]);
|
|
16656
16726
|
async function walkUiFiles(root, max = 200) {
|
|
16657
|
-
const { default:
|
|
16727
|
+
const { default: fs16 } = await import("node:fs/promises");
|
|
16658
16728
|
const { default: nodePath } = await import("node:path");
|
|
16659
16729
|
const found = [];
|
|
16660
16730
|
async function rec(dir, depth) {
|
|
16661
16731
|
if (found.length >= max || depth > 8) return;
|
|
16662
16732
|
let entries;
|
|
16663
16733
|
try {
|
|
16664
|
-
entries = await
|
|
16734
|
+
entries = await fs16.readdir(dir, { withFileTypes: true });
|
|
16665
16735
|
} catch {
|
|
16666
16736
|
return;
|
|
16667
16737
|
}
|
|
@@ -16679,13 +16749,13 @@ async function walkUiFiles(root, max = 200) {
|
|
|
16679
16749
|
return found;
|
|
16680
16750
|
}
|
|
16681
16751
|
async function runDesignVerify(projectRoot, tokens, explicitFiles) {
|
|
16682
|
-
const { default:
|
|
16752
|
+
const { default: fs16 } = await import("node:fs/promises");
|
|
16683
16753
|
const { default: nodePath } = await import("node:path");
|
|
16684
16754
|
const abs = explicitFiles && explicitFiles.length > 0 ? explicitFiles.map((f) => nodePath.isAbsolute(f) ? f : nodePath.join(projectRoot, f)) : await walkUiFiles(projectRoot);
|
|
16685
16755
|
const files = [];
|
|
16686
16756
|
for (const a of abs) {
|
|
16687
16757
|
try {
|
|
16688
|
-
files.push({ path: nodePath.relative(projectRoot, a), text: await
|
|
16758
|
+
files.push({ path: nodePath.relative(projectRoot, a), text: await fs16.readFile(a, "utf8") });
|
|
16689
16759
|
} catch {
|
|
16690
16760
|
}
|
|
16691
16761
|
}
|
|
@@ -16816,10 +16886,10 @@ function makeDesignVerifyToolCallMiddleware() {
|
|
|
16816
16886
|
const p = typeof input?.path === "string" ? input.path : "";
|
|
16817
16887
|
if (!p || !detectFrontendFile(p)) return out;
|
|
16818
16888
|
const ctx = out.ctx;
|
|
16819
|
-
const { default:
|
|
16889
|
+
const { default: fs16 } = await import("node:fs/promises");
|
|
16820
16890
|
const { default: nodePath } = await import("node:path");
|
|
16821
16891
|
const abs = nodePath.isAbsolute(p) ? p : nodePath.join(ctx.projectRoot, p);
|
|
16822
|
-
const text = await
|
|
16892
|
+
const text = await fs16.readFile(abs, "utf8").catch(() => "");
|
|
16823
16893
|
if (!text) return out;
|
|
16824
16894
|
const loader = getDesignKitLoader(ctx.projectRoot);
|
|
16825
16895
|
const rawTokens = await loader.readTokens(state.activeKit);
|
|
@@ -19915,8 +19985,8 @@ Summarize the following message range:`;
|
|
|
19915
19985
|
};
|
|
19916
19986
|
|
|
19917
19987
|
// src/execution/skill-loader.ts
|
|
19918
|
-
import * as
|
|
19919
|
-
import * as
|
|
19988
|
+
import * as fs6 from "node:fs/promises";
|
|
19989
|
+
import * as path16 from "node:path";
|
|
19920
19990
|
|
|
19921
19991
|
// src/skills/foreign-sources.ts
|
|
19922
19992
|
var FOREIGN_SKILL_TOOLS = [
|
|
@@ -20146,7 +20216,7 @@ async function entryIsDirectory(dir, entry) {
|
|
|
20146
20216
|
if (entry.isDirectory()) return true;
|
|
20147
20217
|
if (entry.isSymbolicLink()) {
|
|
20148
20218
|
try {
|
|
20149
|
-
return (await
|
|
20219
|
+
return (await fs6.stat(path16.join(dir, entry.name))).isDirectory();
|
|
20150
20220
|
} catch {
|
|
20151
20221
|
return false;
|
|
20152
20222
|
}
|
|
@@ -20167,7 +20237,7 @@ var DefaultSkillLoader = class {
|
|
|
20167
20237
|
for (const tool of FOREIGN_SKILL_TOOLS) {
|
|
20168
20238
|
if (!foreignIds.includes(tool.id)) continue;
|
|
20169
20239
|
dirs.push({
|
|
20170
|
-
dir:
|
|
20240
|
+
dir: path16.join(root, "." + tool.id, tool.subdir),
|
|
20171
20241
|
source: "foreign",
|
|
20172
20242
|
originTool: tool.id
|
|
20173
20243
|
});
|
|
@@ -20189,14 +20259,14 @@ var DefaultSkillLoader = class {
|
|
|
20189
20259
|
const seen = /* @__PURE__ */ new Set();
|
|
20190
20260
|
for (const { dir, source, originTool } of this.dirs) {
|
|
20191
20261
|
try {
|
|
20192
|
-
const entries = (await
|
|
20262
|
+
const entries = (await fs6.readdir(dir, { withFileTypes: true })).sort(
|
|
20193
20263
|
(a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0
|
|
20194
20264
|
);
|
|
20195
20265
|
for (const e of entries) {
|
|
20196
20266
|
if (!await entryIsDirectory(dir, e)) continue;
|
|
20197
|
-
const skillFile =
|
|
20267
|
+
const skillFile = path16.join(dir, e.name, "SKILL.md");
|
|
20198
20268
|
try {
|
|
20199
|
-
const raw = await
|
|
20269
|
+
const raw = await fs6.readFile(skillFile, "utf8");
|
|
20200
20270
|
const fm = parseSkillFrontmatter(raw);
|
|
20201
20271
|
if (!fm.name || !fm.description) continue;
|
|
20202
20272
|
if (!isValidSkillNameFormat(fm.name)) continue;
|
|
@@ -20268,7 +20338,7 @@ var DefaultSkillLoader = class {
|
|
|
20268
20338
|
if (cached !== void 0) return cached;
|
|
20269
20339
|
const m = await this.find(name);
|
|
20270
20340
|
if (!m) throw new Error(`Skill "${name}" not found`);
|
|
20271
|
-
const body = await
|
|
20341
|
+
const body = await fs6.readFile(m.path, "utf8");
|
|
20272
20342
|
this.bodyCache.set(key, body);
|
|
20273
20343
|
return body;
|
|
20274
20344
|
}
|
|
@@ -20278,12 +20348,12 @@ var DefaultSkillLoader = class {
|
|
|
20278
20348
|
if (cached !== void 0) return cached;
|
|
20279
20349
|
const m = await this.find(name);
|
|
20280
20350
|
if (!m) throw new Error(`Skill "${name}" not found`);
|
|
20281
|
-
const savePath =
|
|
20351
|
+
const savePath = path16.join(path16.dirname(m.path), "SKILL.save.md");
|
|
20282
20352
|
let result;
|
|
20283
20353
|
try {
|
|
20284
|
-
result = await
|
|
20354
|
+
result = await fs6.readFile(savePath, "utf8");
|
|
20285
20355
|
} catch {
|
|
20286
|
-
const full = await
|
|
20356
|
+
const full = await fs6.readFile(m.path, "utf8");
|
|
20287
20357
|
const body = stripFrontmatter2(full);
|
|
20288
20358
|
const compact = compactSkillBody(body);
|
|
20289
20359
|
if (compact) {
|
|
@@ -20311,12 +20381,12 @@ function parseDescriptionFromText(desc) {
|
|
|
20311
20381
|
}
|
|
20312
20382
|
|
|
20313
20383
|
// src/execution/prompt-loader.ts
|
|
20314
|
-
import * as
|
|
20315
|
-
import * as
|
|
20384
|
+
import * as fs8 from "node:fs/promises";
|
|
20385
|
+
import * as path18 from "node:path";
|
|
20316
20386
|
|
|
20317
20387
|
// src/storage/prompt-store.ts
|
|
20318
|
-
import * as
|
|
20319
|
-
import * as
|
|
20388
|
+
import * as fs7 from "node:fs/promises";
|
|
20389
|
+
import * as path17 from "node:path";
|
|
20320
20390
|
var SCHEMA_VERSION = 2;
|
|
20321
20391
|
function migratePromptEntry(raw) {
|
|
20322
20392
|
if (!raw || typeof raw !== "object") return null;
|
|
@@ -20377,12 +20447,12 @@ var DefaultPromptStore = class {
|
|
|
20377
20447
|
await ensureDir(this.dir);
|
|
20378
20448
|
const entries = [];
|
|
20379
20449
|
try {
|
|
20380
|
-
const files = await
|
|
20450
|
+
const files = await fs7.readdir(this.dir);
|
|
20381
20451
|
for (const file of files) {
|
|
20382
20452
|
if (!file.endsWith(".json")) continue;
|
|
20383
20453
|
try {
|
|
20384
20454
|
const raw = JSON.parse(
|
|
20385
|
-
await
|
|
20455
|
+
await fs7.readFile(path17.join(this.dir, file), "utf8")
|
|
20386
20456
|
);
|
|
20387
20457
|
const migrated = migratePromptEntry(raw.entry);
|
|
20388
20458
|
if (migrated) entries.push(migrated);
|
|
@@ -20396,9 +20466,9 @@ var DefaultPromptStore = class {
|
|
|
20396
20466
|
);
|
|
20397
20467
|
}
|
|
20398
20468
|
async get(id) {
|
|
20399
|
-
const file =
|
|
20469
|
+
const file = path17.join(this.dir, `${id}.json`);
|
|
20400
20470
|
try {
|
|
20401
|
-
const raw = JSON.parse(await
|
|
20471
|
+
const raw = JSON.parse(await fs7.readFile(file, "utf8"));
|
|
20402
20472
|
return migratePromptEntry(raw.entry);
|
|
20403
20473
|
} catch {
|
|
20404
20474
|
return null;
|
|
@@ -20406,14 +20476,14 @@ var DefaultPromptStore = class {
|
|
|
20406
20476
|
}
|
|
20407
20477
|
async save(entry) {
|
|
20408
20478
|
await ensureDir(this.dir);
|
|
20409
|
-
const file =
|
|
20479
|
+
const file = path17.join(this.dir, `${entry.id}.json`);
|
|
20410
20480
|
const raw = { version: SCHEMA_VERSION, entry };
|
|
20411
20481
|
await atomicWrite(file, JSON.stringify(raw, null, 2));
|
|
20412
20482
|
}
|
|
20413
20483
|
async delete(id) {
|
|
20414
|
-
const file =
|
|
20484
|
+
const file = path17.join(this.dir, `${id}.json`);
|
|
20415
20485
|
try {
|
|
20416
|
-
await
|
|
20486
|
+
await fs7.unlink(file);
|
|
20417
20487
|
return true;
|
|
20418
20488
|
} catch {
|
|
20419
20489
|
return false;
|
|
@@ -20500,7 +20570,7 @@ var DefaultPromptLoader = class {
|
|
|
20500
20570
|
constructor(opts) {
|
|
20501
20571
|
this.projectStore = typeof opts.paths.inProjectPrompts === "string" ? new DefaultPromptStore(opts.paths.inProjectPrompts) : void 0;
|
|
20502
20572
|
this.userStore = typeof opts.paths.globalPrompts === "string" ? new DefaultPromptStore(opts.paths.globalPrompts) : void 0;
|
|
20503
|
-
this.builtinDir = opts.bundledDir ?
|
|
20573
|
+
this.builtinDir = opts.bundledDir ? path18.join(opts.bundledDir, "prompts") : void 0;
|
|
20504
20574
|
}
|
|
20505
20575
|
async list() {
|
|
20506
20576
|
if (this.cache) return this.cache;
|
|
@@ -20613,7 +20683,7 @@ var DefaultPromptLoader = class {
|
|
|
20613
20683
|
const files = await walkJson(dir);
|
|
20614
20684
|
for (const file of files) {
|
|
20615
20685
|
try {
|
|
20616
|
-
const parsed = JSON.parse(await
|
|
20686
|
+
const parsed = JSON.parse(await fs8.readFile(file, "utf8"));
|
|
20617
20687
|
const migrated = migratePromptEntry(parsed);
|
|
20618
20688
|
if (migrated) out.push({ ...migrated, source: "builtin" });
|
|
20619
20689
|
} catch {
|
|
@@ -20627,12 +20697,12 @@ async function walkJson(dir) {
|
|
|
20627
20697
|
const out = [];
|
|
20628
20698
|
let entries;
|
|
20629
20699
|
try {
|
|
20630
|
-
entries = await
|
|
20700
|
+
entries = await fs8.readdir(dir, { withFileTypes: true });
|
|
20631
20701
|
} catch {
|
|
20632
20702
|
return out;
|
|
20633
20703
|
}
|
|
20634
20704
|
for (const e of entries) {
|
|
20635
|
-
const full =
|
|
20705
|
+
const full = path18.join(dir, e.name);
|
|
20636
20706
|
if (e.isDirectory()) {
|
|
20637
20707
|
out.push(...await walkJson(full));
|
|
20638
20708
|
} else if (e.name.endsWith(".json") && e.name !== "index.json" && e.name !== "schema.json") {
|
|
@@ -20818,7 +20888,7 @@ function readPolicy(ctx) {
|
|
|
20818
20888
|
}
|
|
20819
20889
|
|
|
20820
20890
|
// src/execution/tool-executor.ts
|
|
20821
|
-
import { randomUUID as randomUUID11 } from "node:crypto";
|
|
20891
|
+
import { createHash as createHash6, randomUUID as randomUUID11 } from "node:crypto";
|
|
20822
20892
|
|
|
20823
20893
|
// src/observability/process-telemetry.ts
|
|
20824
20894
|
import { AsyncLocalStorage as AsyncLocalStorage2 } from "node:async_hooks";
|
|
@@ -20829,12 +20899,12 @@ function runWithProcessTelemetry(context, run) {
|
|
|
20829
20899
|
|
|
20830
20900
|
// src/execution/tool-executor.ts
|
|
20831
20901
|
import { isDeepStrictEqual } from "node:util";
|
|
20832
|
-
import * as
|
|
20833
|
-
import * as
|
|
20902
|
+
import * as fs9 from "node:fs/promises";
|
|
20903
|
+
import * as path20 from "node:path";
|
|
20834
20904
|
|
|
20835
20905
|
// src/security/kanban-boundary.ts
|
|
20836
20906
|
import { realpath as realpath3 } from "node:fs/promises";
|
|
20837
|
-
import * as
|
|
20907
|
+
import * as path19 from "node:path";
|
|
20838
20908
|
import {
|
|
20839
20909
|
evaluateKanbanBoundaryOpaque,
|
|
20840
20910
|
evaluateKanbanBoundaryPath,
|
|
@@ -20909,10 +20979,10 @@ function resolveKanbanIdentity(ctx) {
|
|
|
20909
20979
|
async function extractCandidatePaths(toolName, input, ctx) {
|
|
20910
20980
|
if (toolName === "patch" && typeof input["patch"] === "string") {
|
|
20911
20981
|
const directoryInput = stringValue(input["directory"]) ?? ctx.workingDir;
|
|
20912
|
-
const directory =
|
|
20982
|
+
const directory = path19.isAbsolute(directoryInput) ? directoryInput : path19.resolve(ctx.workingDir, directoryInput);
|
|
20913
20983
|
const strip = Math.max(1, numericValue(input["strip"]) ?? 1);
|
|
20914
20984
|
const targets = extractPatchTargets(input["patch"], strip).map(
|
|
20915
|
-
(target) => relativeToProject(
|
|
20985
|
+
(target) => relativeToProject(path19.resolve(directory, target), ctx.projectRoot)
|
|
20916
20986
|
);
|
|
20917
20987
|
return Promise.all(targets.map((target) => canonicalizeCandidatePath(target, ctx)));
|
|
20918
20988
|
}
|
|
@@ -20922,7 +20992,7 @@ async function extractCandidatePaths(toolName, input, ctx) {
|
|
|
20922
20992
|
collectPathValues(input, values, pathKeys);
|
|
20923
20993
|
if (toolName === "scaffold" && typeof input["name"] === "string") {
|
|
20924
20994
|
const cwd = stringValue(input["cwd"]) ?? ctx.workingDir;
|
|
20925
|
-
values.push(
|
|
20995
|
+
values.push(path19.join(cwd, input["name"]));
|
|
20926
20996
|
}
|
|
20927
20997
|
const candidates = [
|
|
20928
20998
|
...new Set(values.flatMap(splitPathList).map((value) => resolveInputPath(value, ctx)))
|
|
@@ -20930,21 +21000,21 @@ async function extractCandidatePaths(toolName, input, ctx) {
|
|
|
20930
21000
|
return Promise.all(candidates.map((candidate) => canonicalizeCandidatePath(candidate, ctx)));
|
|
20931
21001
|
}
|
|
20932
21002
|
async function canonicalizeCandidatePath(candidate, ctx) {
|
|
20933
|
-
if (
|
|
20934
|
-
const absolute =
|
|
21003
|
+
if (path19.isAbsolute(candidate)) return candidate;
|
|
21004
|
+
const absolute = path19.resolve(ctx.projectRoot, candidate);
|
|
20935
21005
|
const canonicalRoot = await realpath3(ctx.projectRoot).catch(() => ctx.projectRoot);
|
|
20936
21006
|
let probe = absolute;
|
|
20937
21007
|
const missingSegments = [];
|
|
20938
21008
|
while (true) {
|
|
20939
21009
|
try {
|
|
20940
|
-
const canonical =
|
|
21010
|
+
const canonical = path19.join(await realpath3(probe), ...missingSegments);
|
|
20941
21011
|
return relativeToProject(canonical, canonicalRoot);
|
|
20942
21012
|
} catch (cause) {
|
|
20943
21013
|
const code = cause.code;
|
|
20944
21014
|
if (code !== "ENOENT" && code !== "ENOTDIR") return absolute;
|
|
20945
|
-
const parent =
|
|
21015
|
+
const parent = path19.dirname(probe);
|
|
20946
21016
|
if (parent === probe) return absolute;
|
|
20947
|
-
missingSegments.unshift(
|
|
21017
|
+
missingSegments.unshift(path19.basename(probe));
|
|
20948
21018
|
probe = parent;
|
|
20949
21019
|
}
|
|
20950
21020
|
}
|
|
@@ -20967,12 +21037,12 @@ function splitPathList(value) {
|
|
|
20967
21037
|
return value.split(",").map((item) => item.trim()).filter(Boolean);
|
|
20968
21038
|
}
|
|
20969
21039
|
function resolveInputPath(value, ctx) {
|
|
20970
|
-
const absolute =
|
|
21040
|
+
const absolute = path19.isAbsolute(value) ? value : path19.resolve(ctx.workingDir, value);
|
|
20971
21041
|
return relativeToProject(absolute, ctx.projectRoot);
|
|
20972
21042
|
}
|
|
20973
21043
|
function relativeToProject(absolute, projectRoot) {
|
|
20974
|
-
const
|
|
20975
|
-
return
|
|
21044
|
+
const relative6 = path19.relative(projectRoot, absolute).replace(/\\/g, "/");
|
|
21045
|
+
return relative6.startsWith("../") || path19.isAbsolute(relative6) ? absolute : relative6 || ".";
|
|
20976
21046
|
}
|
|
20977
21047
|
function extractPatchTargets(patchText, strip) {
|
|
20978
21048
|
const targets = [];
|
|
@@ -21226,12 +21296,30 @@ ${errorDetails}`,
|
|
|
21226
21296
|
const policy = this.opts.permissionPolicy;
|
|
21227
21297
|
const yolo = policy.getYolo?.() === true;
|
|
21228
21298
|
const authoritativeAuto = decision.source === "yolo";
|
|
21229
|
-
|
|
21299
|
+
const capabilityDowngraded = toolDangerousCaps.length > 0 && effectivePermission === "auto" && !yolo && !authoritativeAuto;
|
|
21300
|
+
if (capabilityDowngraded) {
|
|
21230
21301
|
effectivePermission = "confirm";
|
|
21231
21302
|
}
|
|
21232
21303
|
if (boundary.decision === "confirm" && effectivePermission !== "deny") {
|
|
21233
21304
|
effectivePermission = "confirm";
|
|
21234
21305
|
}
|
|
21306
|
+
this.opts.events?.emit("permission.evaluated", {
|
|
21307
|
+
sessionId: ctx.session.id,
|
|
21308
|
+
...ctx.traceId ? { traceId: ctx.traceId } : {},
|
|
21309
|
+
...ctx.agentId ? { agentId: ctx.agentId } : {},
|
|
21310
|
+
name: tool.name,
|
|
21311
|
+
id: use.id,
|
|
21312
|
+
inputHash: hashPermissionInput(use.input, this.opts.secretScrubber),
|
|
21313
|
+
policyDecision: decision.permission,
|
|
21314
|
+
effectiveDecision: effectivePermission,
|
|
21315
|
+
decisionSource: decision.source,
|
|
21316
|
+
...decision.reason ? { reason: decision.reason } : {},
|
|
21317
|
+
...decision.riskTier ?? tool.riskTier ? { riskTier: decision.riskTier ?? tool.riskTier } : {},
|
|
21318
|
+
yoloEnabled: yolo,
|
|
21319
|
+
boundaryDecision: boundary.decision,
|
|
21320
|
+
...boundary.reason ? { boundaryReason: boundary.reason } : {},
|
|
21321
|
+
capabilityDowngraded
|
|
21322
|
+
});
|
|
21235
21323
|
if (effectivePermission === "deny") {
|
|
21236
21324
|
const result = this.deniedResult(use, decision.reason);
|
|
21237
21325
|
budget = this.budgetForString(result.content, budget);
|
|
@@ -21298,10 +21386,10 @@ ${errorDetails}`,
|
|
|
21298
21386
|
const inputPath = use.input && typeof use.input === "object" ? use.input.path : void 0;
|
|
21299
21387
|
const caps = tool.capabilities ?? [];
|
|
21300
21388
|
const hasFileCapability = caps.includes("fs.read") || caps.includes("fs.write");
|
|
21301
|
-
const absPath = hasFileCapability && typeof inputPath === "string" ?
|
|
21389
|
+
const absPath = hasFileCapability && typeof inputPath === "string" ? path20.isAbsolute(inputPath) ? inputPath : path20.resolve(ctx.projectRoot, inputPath) : void 0;
|
|
21302
21390
|
let writeTargetExisted;
|
|
21303
21391
|
if (tool.name === "write" && caps.includes("fs.write") && absPath) {
|
|
21304
|
-
writeTargetExisted = await
|
|
21392
|
+
writeTargetExisted = await fs9.stat(absPath).then(
|
|
21305
21393
|
(stat13) => stat13.isFile(),
|
|
21306
21394
|
(error2) => error2.code === "ENOENT" ? false : void 0
|
|
21307
21395
|
);
|
|
@@ -21890,12 +21978,12 @@ async function maybePersistLargeToolOutput(toolName, content, budget) {
|
|
|
21890
21978
|
return content;
|
|
21891
21979
|
}
|
|
21892
21980
|
try {
|
|
21893
|
-
const dir =
|
|
21894
|
-
await
|
|
21981
|
+
const dir = path20.join(wstackGlobalRoot(), "tool-output");
|
|
21982
|
+
await fs9.mkdir(dir, { recursive: true });
|
|
21895
21983
|
const safeTool = toolName.replace(/[^a-zA-Z0-9._-]+/g, "_").slice(0, 40) || "tool";
|
|
21896
21984
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
21897
|
-
const filePath =
|
|
21898
|
-
await
|
|
21985
|
+
const filePath = path20.join(dir, `${stamp}-${safeTool}-${randomUUID11()}.log`);
|
|
21986
|
+
await fs9.writeFile(filePath, content, "utf8");
|
|
21899
21987
|
const marker = `[full tool output: ${bytes} bytes at ${filePath}; read/grep that file selectively instead of re-running or requesting more output]`;
|
|
21900
21988
|
const fixedBytes = Buffer.byteLength(marker + TOOL_OUTPUT_ARTIFACT_OMISSION, "utf8");
|
|
21901
21989
|
const previewBytes = Math.min(
|
|
@@ -21913,6 +22001,15 @@ ${head}${TOOL_OUTPUT_ARTIFACT_OMISSION}${tail}`;
|
|
|
21913
22001
|
return content;
|
|
21914
22002
|
}
|
|
21915
22003
|
}
|
|
22004
|
+
function hashPermissionInput(input, scrubber) {
|
|
22005
|
+
let serialized;
|
|
22006
|
+
try {
|
|
22007
|
+
serialized = JSON.stringify(input) ?? "";
|
|
22008
|
+
} catch {
|
|
22009
|
+
serialized = String(input);
|
|
22010
|
+
}
|
|
22011
|
+
return createHash6("sha256").update(scrubber.scrub(serialized), "utf8").digest("hex");
|
|
22012
|
+
}
|
|
21916
22013
|
function sliceUtf8Prefix(text, maxBytes) {
|
|
21917
22014
|
if (maxBytes <= 0) return "";
|
|
21918
22015
|
if (Buffer.byteLength(text, "utf8") <= maxBytes) return text;
|
|
@@ -22206,7 +22303,7 @@ var contextManagerTool = createContextManagerTool();
|
|
|
22206
22303
|
|
|
22207
22304
|
// src/infrastructure/logger.ts
|
|
22208
22305
|
import * as fsp11 from "node:fs/promises";
|
|
22209
|
-
import * as
|
|
22306
|
+
import * as path21 from "node:path";
|
|
22210
22307
|
var LEVEL_RANK2 = {
|
|
22211
22308
|
error: 0,
|
|
22212
22309
|
warn: 1,
|
|
@@ -22270,7 +22367,7 @@ var DefaultLogger = class _DefaultLogger {
|
|
|
22270
22367
|
this.stderr = opts.stderr !== false;
|
|
22271
22368
|
this.maxFileBytes = opts.maxFileBytes ?? 10 * 1024 * 1024;
|
|
22272
22369
|
if (this.file) {
|
|
22273
|
-
const dir =
|
|
22370
|
+
const dir = path21.dirname(this.file);
|
|
22274
22371
|
this._tail = this._tail.then(async () => {
|
|
22275
22372
|
await fsp11.mkdir(dir, { recursive: true });
|
|
22276
22373
|
}).catch(() => void 0);
|
|
@@ -22591,28 +22688,28 @@ function codexModelMeta(id) {
|
|
|
22591
22688
|
}
|
|
22592
22689
|
|
|
22593
22690
|
// src/models/mode-store.ts
|
|
22594
|
-
import * as
|
|
22595
|
-
import * as
|
|
22691
|
+
import * as fs10 from "node:fs/promises";
|
|
22692
|
+
import * as path23 from "node:path";
|
|
22596
22693
|
|
|
22597
22694
|
// src/types/mode-prompts.ts
|
|
22598
22695
|
import { readFileSync as readFileSync4, statSync as statSync4 } from "node:fs";
|
|
22599
|
-
import * as
|
|
22696
|
+
import * as path22 from "node:path";
|
|
22600
22697
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
22601
22698
|
function modePrompt(id) {
|
|
22602
22699
|
for (const dir of modePromptDirCandidates()) {
|
|
22603
22700
|
try {
|
|
22604
|
-
return readFileSync4(
|
|
22701
|
+
return readFileSync4(path22.join(dir, `${id}.md`), "utf8").trimEnd();
|
|
22605
22702
|
} catch {
|
|
22606
22703
|
}
|
|
22607
22704
|
}
|
|
22608
22705
|
return "";
|
|
22609
22706
|
}
|
|
22610
22707
|
function modePromptDirCandidates() {
|
|
22611
|
-
const here =
|
|
22708
|
+
const here = path22.dirname(fileURLToPath4(import.meta.url));
|
|
22612
22709
|
const candidates = [
|
|
22613
|
-
|
|
22614
|
-
|
|
22615
|
-
|
|
22710
|
+
path22.resolve(here, "../../instructions/modes"),
|
|
22711
|
+
path22.resolve(here, "../instructions/modes"),
|
|
22712
|
+
path22.resolve(here, "instructions/modes")
|
|
22616
22713
|
];
|
|
22617
22714
|
return candidates.sort((a, b) => Number(!isDirectory3(a)) - Number(!isDirectory3(b)));
|
|
22618
22715
|
}
|
|
@@ -22844,8 +22941,8 @@ var DefaultModeStore = class {
|
|
|
22844
22941
|
}
|
|
22845
22942
|
async loadActiveMode() {
|
|
22846
22943
|
try {
|
|
22847
|
-
const configPath =
|
|
22848
|
-
const content = await
|
|
22944
|
+
const configPath = path23.join(this.configDir, "mode.json");
|
|
22945
|
+
const content = await fs10.readFile(configPath, "utf8");
|
|
22849
22946
|
const data = JSON.parse(content);
|
|
22850
22947
|
this.activeModeId = data.activeMode ?? null;
|
|
22851
22948
|
} catch {
|
|
@@ -22854,8 +22951,8 @@ var DefaultModeStore = class {
|
|
|
22854
22951
|
}
|
|
22855
22952
|
async saveActiveMode() {
|
|
22856
22953
|
try {
|
|
22857
|
-
await
|
|
22858
|
-
const configPath =
|
|
22954
|
+
await fs10.mkdir(this.configDir, { recursive: true });
|
|
22955
|
+
const configPath = path23.join(this.configDir, "mode.json");
|
|
22859
22956
|
await atomicWrite(
|
|
22860
22957
|
configPath,
|
|
22861
22958
|
JSON.stringify({ activeMode: this.activeModeId }, null, 2)
|
|
@@ -22867,14 +22964,14 @@ var DefaultModeStore = class {
|
|
|
22867
22964
|
async function loadProjectModes(modesDir) {
|
|
22868
22965
|
const modes = [];
|
|
22869
22966
|
try {
|
|
22870
|
-
const entries = await
|
|
22967
|
+
const entries = await fs10.readdir(modesDir);
|
|
22871
22968
|
for (const entry of entries) {
|
|
22872
22969
|
if (!entry.endsWith(".md") && !entry.endsWith(".txt")) continue;
|
|
22873
|
-
const filePath =
|
|
22874
|
-
const stat13 = await
|
|
22970
|
+
const filePath = path23.join(modesDir, entry);
|
|
22971
|
+
const stat13 = await fs10.stat(filePath);
|
|
22875
22972
|
if (!stat13.isFile()) continue;
|
|
22876
|
-
const content = await
|
|
22877
|
-
const id =
|
|
22973
|
+
const content = await fs10.readFile(filePath, "utf8");
|
|
22974
|
+
const id = path23.basename(entry, path23.extname(entry));
|
|
22878
22975
|
modes.push({
|
|
22879
22976
|
id,
|
|
22880
22977
|
name: id.replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()),
|
|
@@ -22890,8 +22987,8 @@ async function loadProjectModes(modesDir) {
|
|
|
22890
22987
|
async function loadUserModes(modesDir) {
|
|
22891
22988
|
const modes = [];
|
|
22892
22989
|
try {
|
|
22893
|
-
const manifestPath =
|
|
22894
|
-
const content = await
|
|
22990
|
+
const manifestPath = path23.join(modesDir, "modes.json");
|
|
22991
|
+
const content = await fs10.readFile(manifestPath, "utf8");
|
|
22895
22992
|
const manifest = JSON.parse(content);
|
|
22896
22993
|
for (const mode of manifest.modes) {
|
|
22897
22994
|
modes.push(mode);
|
|
@@ -22902,8 +22999,8 @@ async function loadUserModes(modesDir) {
|
|
|
22902
22999
|
}
|
|
22903
23000
|
|
|
22904
23001
|
// src/models/models-registry.ts
|
|
22905
|
-
import * as
|
|
22906
|
-
import * as
|
|
23002
|
+
import * as fs11 from "node:fs/promises";
|
|
23003
|
+
import * as path24 from "node:path";
|
|
22907
23004
|
var DEFAULT_URL = "https://models.dev/api.json";
|
|
22908
23005
|
var ENV_URL_KEY = "WRONGSTACK_MODELS_DEV_URL";
|
|
22909
23006
|
var DEFAULT_TTL_SECONDS = 24 * 3600;
|
|
@@ -22979,7 +23076,7 @@ var DefaultModelsRegistry = class {
|
|
|
22979
23076
|
this.overlay = opts.overlay;
|
|
22980
23077
|
this.overlayUrl = opts.overlayUrl;
|
|
22981
23078
|
this.overlayFile = opts.overlayFile;
|
|
22982
|
-
this.overlayCacheFile = opts.overlayCacheFile ?? (opts.overlayUrl ?
|
|
23079
|
+
this.overlayCacheFile = opts.overlayCacheFile ?? (opts.overlayUrl ? path24.join(path24.dirname(opts.cacheFile), "models-overlay-cache.json") : void 0);
|
|
22983
23080
|
}
|
|
22984
23081
|
async load(opts = {}) {
|
|
22985
23082
|
if (this.payload && !opts.force) return this.payload;
|
|
@@ -23144,7 +23241,7 @@ var DefaultModelsRegistry = class {
|
|
|
23144
23241
|
async readOverlayFile() {
|
|
23145
23242
|
if (!this.overlayFile) return void 0;
|
|
23146
23243
|
try {
|
|
23147
|
-
const raw = await
|
|
23244
|
+
const raw = await fs11.readFile(this.overlayFile, "utf8");
|
|
23148
23245
|
return JSON.parse(raw);
|
|
23149
23246
|
} catch {
|
|
23150
23247
|
return void 0;
|
|
@@ -23223,7 +23320,7 @@ var DefaultModelsRegistry = class {
|
|
|
23223
23320
|
}
|
|
23224
23321
|
async readCacheAt(file) {
|
|
23225
23322
|
try {
|
|
23226
|
-
const raw = await
|
|
23323
|
+
const raw = await fs11.readFile(file, "utf8");
|
|
23227
23324
|
return JSON.parse(raw);
|
|
23228
23325
|
} catch {
|
|
23229
23326
|
return void 0;
|
|
@@ -23231,7 +23328,7 @@ var DefaultModelsRegistry = class {
|
|
|
23231
23328
|
}
|
|
23232
23329
|
/** Used by `wstack models refresh` to expose where the cache lives. */
|
|
23233
23330
|
cacheLocation() {
|
|
23234
|
-
return
|
|
23331
|
+
return path24.resolve(this.cacheFile);
|
|
23235
23332
|
}
|
|
23236
23333
|
};
|
|
23237
23334
|
function formatAge(seconds) {
|
|
@@ -23655,7 +23752,7 @@ async function startMetricsServer(opts) {
|
|
|
23655
23752
|
const tls = opts.tls;
|
|
23656
23753
|
const useHttps = !!(tls?.cert && tls?.key);
|
|
23657
23754
|
const host = opts.host ?? "127.0.0.1";
|
|
23658
|
-
const
|
|
23755
|
+
const path30 = opts.path ?? "/metrics";
|
|
23659
23756
|
const healthPath = opts.healthPath ?? "/healthz";
|
|
23660
23757
|
const healthRegistry = opts.healthRegistry;
|
|
23661
23758
|
const listener = (req, res) => {
|
|
@@ -23665,7 +23762,7 @@ async function startMetricsServer(opts) {
|
|
|
23665
23762
|
return;
|
|
23666
23763
|
}
|
|
23667
23764
|
const url = req.url.split("?")[0];
|
|
23668
|
-
if (url ===
|
|
23765
|
+
if (url === path30) {
|
|
23669
23766
|
let body;
|
|
23670
23767
|
try {
|
|
23671
23768
|
body = renderPrometheus(opts.sink.snapshot());
|
|
@@ -23729,7 +23826,7 @@ async function startMetricsServer(opts) {
|
|
|
23729
23826
|
const protocol = useHttps ? "https" : "http";
|
|
23730
23827
|
return {
|
|
23731
23828
|
port: boundPort,
|
|
23732
|
-
url: `${protocol}://${host}:${boundPort}${
|
|
23829
|
+
url: `${protocol}://${host}:${boundPort}${path30}`,
|
|
23733
23830
|
close: () => new Promise((resolve14, reject) => {
|
|
23734
23831
|
server.close((err) => err ? reject(err) : resolve14());
|
|
23735
23832
|
})
|
|
@@ -24001,7 +24098,7 @@ function startOtlpTraceExporter(opts) {
|
|
|
24001
24098
|
}
|
|
24002
24099
|
|
|
24003
24100
|
// src/security/permission-policy.ts
|
|
24004
|
-
import * as
|
|
24101
|
+
import * as fs12 from "node:fs/promises";
|
|
24005
24102
|
|
|
24006
24103
|
// src/security/permission-policy-schema.ts
|
|
24007
24104
|
var TRUST_POLICY_LIMITS = Object.freeze({
|
|
@@ -24046,19 +24143,19 @@ var UNSAFE_PROPERTY_NAMES = /* @__PURE__ */ new Set(["__proto__", "prototype", "
|
|
|
24046
24143
|
function isRecord5(value) {
|
|
24047
24144
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
24048
24145
|
}
|
|
24049
|
-
function error(diagnostics, code,
|
|
24050
|
-
diagnostics.push({ severity: "error", code, path:
|
|
24146
|
+
function error(diagnostics, code, path30, message) {
|
|
24147
|
+
diagnostics.push({ severity: "error", code, path: path30, message });
|
|
24051
24148
|
}
|
|
24052
|
-
function validatePatterns(value,
|
|
24149
|
+
function validatePatterns(value, path30, diagnostics) {
|
|
24053
24150
|
if (!Array.isArray(value)) {
|
|
24054
|
-
error(diagnostics, "invalid_pattern_list",
|
|
24151
|
+
error(diagnostics, "invalid_pattern_list", path30, "must be an array of strings");
|
|
24055
24152
|
return void 0;
|
|
24056
24153
|
}
|
|
24057
24154
|
if (value.length > TRUST_POLICY_LIMITS.maxPatternsPerRule) {
|
|
24058
24155
|
error(
|
|
24059
24156
|
diagnostics,
|
|
24060
24157
|
"too_many_patterns",
|
|
24061
|
-
|
|
24158
|
+
path30,
|
|
24062
24159
|
`must contain at most ${TRUST_POLICY_LIMITS.maxPatternsPerRule} patterns`
|
|
24063
24160
|
);
|
|
24064
24161
|
return void 0;
|
|
@@ -24066,7 +24163,7 @@ function validatePatterns(value, path29, diagnostics) {
|
|
|
24066
24163
|
const patterns = [];
|
|
24067
24164
|
const seen = /* @__PURE__ */ new Set();
|
|
24068
24165
|
for (const [index, pattern] of value.entries()) {
|
|
24069
|
-
const itemPath = `${
|
|
24166
|
+
const itemPath = `${path30}[${index}]`;
|
|
24070
24167
|
if (typeof pattern !== "string" || pattern.length === 0 || pattern.length > TRUST_POLICY_LIMITS.maxPatternChars) {
|
|
24071
24168
|
error(
|
|
24072
24169
|
diagnostics,
|
|
@@ -24390,7 +24487,7 @@ var DefaultPermissionPolicy = class {
|
|
|
24390
24487
|
this.policyDiagnostics = [];
|
|
24391
24488
|
this.policyInvalid = false;
|
|
24392
24489
|
try {
|
|
24393
|
-
const raw = await
|
|
24490
|
+
const raw = await fs12.readFile(this.trustFile, "utf8");
|
|
24394
24491
|
const parsed = safeParse(raw);
|
|
24395
24492
|
if (!parsed.ok) {
|
|
24396
24493
|
this.policy = {};
|
|
@@ -24640,6 +24737,136 @@ var DefaultPermissionPolicy = class {
|
|
|
24640
24737
|
this.sessionAllowed.set(`${rule.tool}::${rule.pattern}`, true);
|
|
24641
24738
|
this._evalCache.clear();
|
|
24642
24739
|
}
|
|
24740
|
+
/**
|
|
24741
|
+
* Side-effect-free permission evaluation trace. Mirrors `evaluate()`
|
|
24742
|
+
* step-by-step without prompting the user, writing to trust files, or
|
|
24743
|
+
* mutating session state. Returns the full ordered trace and winner.
|
|
24744
|
+
*/
|
|
24745
|
+
async explain(tool, input, ctx) {
|
|
24746
|
+
if (!this.loaded) await this.reload();
|
|
24747
|
+
const subject = subjectForToolInput(tool.name, input, tool.subjectKey);
|
|
24748
|
+
const steps = [];
|
|
24749
|
+
let winnerIndex = -1;
|
|
24750
|
+
const add = (rule, matched, decision, source, detail) => {
|
|
24751
|
+
steps.push({ rule, matched, decision, source, detail });
|
|
24752
|
+
};
|
|
24753
|
+
if (this.policyInvalid) {
|
|
24754
|
+
add("policy invalid", true, "deny", "deny", "trust policy is invalid; all tools are denied");
|
|
24755
|
+
winnerIndex = 0;
|
|
24756
|
+
return { toolName: tool.name, subject, steps, winnerIndex, decision: { permission: "deny", source: "deny", reason: "trust policy is invalid" } };
|
|
24757
|
+
}
|
|
24758
|
+
add("policy valid", false, "auto", "default", "trust policy loaded successfully");
|
|
24759
|
+
const namespaceEntry = this.findNamespaceEntry(tool.name);
|
|
24760
|
+
const entry = this.policy[tool.name] ?? namespaceEntry;
|
|
24761
|
+
const namespaceSource = namespaceEntry ? `wildcard entry matched (${Object.keys(this.policy).find((k) => k.includes("*") && matchGlob(k, tool.name))})` : "no namespace match";
|
|
24762
|
+
const cacheKey = `${tool.name}::${subject ?? tool.name}`;
|
|
24763
|
+
if (this.sessionDenied.has(cacheKey)) {
|
|
24764
|
+
add("session soft deny", true, "deny", "deny", 'user pressed "no" earlier in this session \u2014 blocked until reload');
|
|
24765
|
+
winnerIndex = steps.length - 1;
|
|
24766
|
+
return { toolName: tool.name, subject, steps, winnerIndex, decision: { permission: "deny", source: "deny", reason: "session soft deny (user pressed no)" } };
|
|
24767
|
+
}
|
|
24768
|
+
add("session soft deny", false, "deny", "deny", "no session-level soft deny for this tool+subject");
|
|
24769
|
+
if (this.sessionAllowed.has(cacheKey)) {
|
|
24770
|
+
add("session soft allow", true, "auto", "trust", 'user pressed "yes" in current session \u2014 one-shot auto-approve (consumed)');
|
|
24771
|
+
winnerIndex = steps.length - 1;
|
|
24772
|
+
return { toolName: tool.name, subject, steps, winnerIndex, decision: { permission: "auto", source: "trust", reason: "session one-shot allow (user pressed yes)" } };
|
|
24773
|
+
}
|
|
24774
|
+
add("session soft allow", false, "auto", "trust", "no session-level one-shot allow for this tool+subject");
|
|
24775
|
+
if (entry?.deny && subject && matchesTrust(entry.deny, subject)) {
|
|
24776
|
+
add("trust deny", true, "deny", "deny", `subject "${subject}" matched a deny pattern in trust file`);
|
|
24777
|
+
winnerIndex = steps.length - 1;
|
|
24778
|
+
return { toolName: tool.name, subject, steps, winnerIndex, decision: { permission: "deny", source: "deny", reason: "matched deny pattern" } };
|
|
24779
|
+
}
|
|
24780
|
+
add("trust deny", false, "deny", "deny", `no deny pattern matched (namespace: ${namespaceSource})`);
|
|
24781
|
+
if (tool.permission === "deny") {
|
|
24782
|
+
add("tool default deny", true, "deny", "default", `tool "${tool.name}" has permission: deny by default`);
|
|
24783
|
+
winnerIndex = steps.length - 1;
|
|
24784
|
+
return { toolName: tool.name, subject, steps, winnerIndex, decision: { permission: "deny", source: "default", reason: "tool default deny" } };
|
|
24785
|
+
}
|
|
24786
|
+
add("tool default deny", false, "deny", "default", `tool "${tool.name}" has permission: "${tool.permission}"`);
|
|
24787
|
+
if (entry?.allow && subject && matchesTrust(entry.allow, subject)) {
|
|
24788
|
+
add("trust allow", true, "auto", "trust", `subject "${subject}" matched an allow pattern in trust file`);
|
|
24789
|
+
winnerIndex = steps.length - 1;
|
|
24790
|
+
return { toolName: tool.name, subject, steps, winnerIndex, decision: { permission: "auto", source: "trust", reason: "matched allow pattern" } };
|
|
24791
|
+
}
|
|
24792
|
+
add("trust allow", false, "auto", "trust", `no allow pattern matched (namespace: ${namespaceSource})`);
|
|
24793
|
+
if (entry?.auto) {
|
|
24794
|
+
add("trust auto", true, "auto", "trust", `trust file has auto: true for "${tool.name}"`);
|
|
24795
|
+
winnerIndex = steps.length - 1;
|
|
24796
|
+
return { toolName: tool.name, subject, steps, winnerIndex, decision: { permission: "auto", source: "trust", reason: "trust auto" } };
|
|
24797
|
+
}
|
|
24798
|
+
add("trust auto", false, "auto", "trust", `no auto flag for "${tool.name}" in trust file`);
|
|
24799
|
+
if (!this.yolo && this.isSensitiveReadCall(tool, input)) {
|
|
24800
|
+
const hasDelegate2 = this.promptDelegate !== void 0;
|
|
24801
|
+
add(
|
|
24802
|
+
"sensitive read",
|
|
24803
|
+
true,
|
|
24804
|
+
hasDelegate2 ? "confirm" : "confirm",
|
|
24805
|
+
"default",
|
|
24806
|
+
hasDelegate2 ? "sensitive file read detected \u2014 would prompt user for approval" : "sensitive file read detected \u2014 returns confirm (no prompt delegate)"
|
|
24807
|
+
);
|
|
24808
|
+
winnerIndex = steps.length - 1;
|
|
24809
|
+
return { toolName: tool.name, subject, steps, winnerIndex, decision: { permission: "confirm", source: "default", riskTier: "standard", reason: "sensitive file read needs explicit approval" } };
|
|
24810
|
+
}
|
|
24811
|
+
add("sensitive read", false, "confirm", "default", "not a sensitive read call (or YOLO bypasses this check)");
|
|
24812
|
+
if (this.yolo) {
|
|
24813
|
+
add("yolo", true, "auto", "yolo", "YOLO mode is active \u2014 auto-approving every non-denied call");
|
|
24814
|
+
winnerIndex = steps.length - 1;
|
|
24815
|
+
return { toolName: tool.name, subject, steps, winnerIndex, decision: { permission: "auto", source: "yolo" } };
|
|
24816
|
+
}
|
|
24817
|
+
add("yolo", false, "auto", "yolo", "YOLO mode is not active");
|
|
24818
|
+
if (tool.name === "write" && subject) {
|
|
24819
|
+
const hasRead = ctx.hasRead(subject);
|
|
24820
|
+
add(
|
|
24821
|
+
"write smart bypass",
|
|
24822
|
+
hasRead,
|
|
24823
|
+
"auto",
|
|
24824
|
+
"context",
|
|
24825
|
+
hasRead ? `file "${subject}" was already read in this session \u2014 auto-approving write` : `file "${subject}" was not read in this session \u2014 bypass does not apply`
|
|
24826
|
+
);
|
|
24827
|
+
if (hasRead) {
|
|
24828
|
+
winnerIndex = steps.length - 1;
|
|
24829
|
+
return { toolName: tool.name, subject, steps, winnerIndex, decision: { permission: "auto", source: "context", reason: "file already read in this session" } };
|
|
24830
|
+
}
|
|
24831
|
+
} else {
|
|
24832
|
+
add("write smart bypass", false, "auto", "context", 'tool is not "write" or has no subject \u2014 bypass does not apply');
|
|
24833
|
+
}
|
|
24834
|
+
const hasWriteCap = hasCapability(tool, ToolCapabilities.FS_WRITE);
|
|
24835
|
+
const hasShellCap = hasCapability(tool, [
|
|
24836
|
+
ToolCapabilities.SHELL_ARBITRARY,
|
|
24837
|
+
ToolCapabilities.SHELL_RESTRICTED,
|
|
24838
|
+
ToolCapabilities.SHELL_EXEC
|
|
24839
|
+
]);
|
|
24840
|
+
const hasInstallCap = hasCapability(tool, ToolCapabilities.PACKAGE_INSTALL);
|
|
24841
|
+
const hasConfigCap = hasCapability(tool, ToolCapabilities.CONFIG_MUTATE);
|
|
24842
|
+
const hasSubagentCap = hasCapability(tool, ToolCapabilities.SUBAGENT_SPAWN);
|
|
24843
|
+
const isMutating = tool.mutating || hasWriteCap || hasShellCap || hasInstallCap || hasConfigCap || hasSubagentCap;
|
|
24844
|
+
if (tool.permission === "auto" && !isMutating) {
|
|
24845
|
+
add("safe default auto", true, "auto", "default", `tool "${tool.name}" has auto permission and is not mutating \u2014 safe to auto-approve`);
|
|
24846
|
+
winnerIndex = steps.length - 1;
|
|
24847
|
+
return { toolName: tool.name, subject, steps, winnerIndex, decision: { permission: "auto", source: "default" } };
|
|
24848
|
+
}
|
|
24849
|
+
add(
|
|
24850
|
+
"mutating default confirm",
|
|
24851
|
+
true,
|
|
24852
|
+
"confirm",
|
|
24853
|
+
"default",
|
|
24854
|
+
isMutating ? `tool "${tool.name}" is mutating (default auto not enough) \u2014 needs confirmation` : `tool "${tool.name}" has "${tool.permission}" permission \u2014 needs confirmation`
|
|
24855
|
+
);
|
|
24856
|
+
winnerIndex = steps.length - 1;
|
|
24857
|
+
const hasDelegate = this.promptDelegate !== void 0;
|
|
24858
|
+
return {
|
|
24859
|
+
toolName: tool.name,
|
|
24860
|
+
subject,
|
|
24861
|
+
steps,
|
|
24862
|
+
winnerIndex,
|
|
24863
|
+
decision: {
|
|
24864
|
+
permission: "confirm",
|
|
24865
|
+
source: "default",
|
|
24866
|
+
...hasDelegate ? { reason: "would prompt user via delegate" } : {}
|
|
24867
|
+
}
|
|
24868
|
+
};
|
|
24869
|
+
}
|
|
24643
24870
|
findNamespaceEntry(toolName) {
|
|
24644
24871
|
for (const { pattern, value } of this.wildcardEntries) {
|
|
24645
24872
|
if (matchGlob(pattern, toolName)) return value;
|
|
@@ -24685,6 +24912,24 @@ var AutoApprovePermissionPolicy = class _AutoApprovePermissionPolicy {
|
|
|
24685
24912
|
}
|
|
24686
24913
|
allowOnce() {
|
|
24687
24914
|
}
|
|
24915
|
+
async explain(tool) {
|
|
24916
|
+
const decision = await this.evaluate(tool);
|
|
24917
|
+
return {
|
|
24918
|
+
toolName: tool.name,
|
|
24919
|
+
subject: null,
|
|
24920
|
+
steps: [
|
|
24921
|
+
{
|
|
24922
|
+
rule: "subagent auto",
|
|
24923
|
+
matched: decision.permission === "auto",
|
|
24924
|
+
decision: decision.permission,
|
|
24925
|
+
source: decision.source,
|
|
24926
|
+
detail: decision.reason ?? `subagent policy: ${decision.permission}`
|
|
24927
|
+
}
|
|
24928
|
+
],
|
|
24929
|
+
winnerIndex: 0,
|
|
24930
|
+
decision
|
|
24931
|
+
};
|
|
24932
|
+
}
|
|
24688
24933
|
async reload() {
|
|
24689
24934
|
}
|
|
24690
24935
|
};
|
|
@@ -24885,9 +25130,9 @@ var DefaultSecretScrubber = class {
|
|
|
24885
25130
|
|
|
24886
25131
|
// src/security/secret-vault.ts
|
|
24887
25132
|
import { createCipheriv, createDecipheriv, randomBytes as randomBytes4, scryptSync } from "node:crypto";
|
|
24888
|
-
import * as
|
|
25133
|
+
import * as fs13 from "node:fs";
|
|
24889
25134
|
import * as fsp12 from "node:fs/promises";
|
|
24890
|
-
import * as
|
|
25135
|
+
import * as path25 from "node:path";
|
|
24891
25136
|
|
|
24892
25137
|
// src/types/secret-vault.ts
|
|
24893
25138
|
var ENCRYPTED_PREFIX_PATTERN = /^enc:v(\d+):/;
|
|
@@ -25027,7 +25272,7 @@ function checkKeyFilePermissions(keyFile, opts) {
|
|
|
25027
25272
|
if (process.platform === "win32") return;
|
|
25028
25273
|
const warn = opts?.warn ?? ((msg) => console.warn(msg));
|
|
25029
25274
|
try {
|
|
25030
|
-
const stat13 =
|
|
25275
|
+
const stat13 = fs13.statSync(keyFile);
|
|
25031
25276
|
const actualMode = stat13.mode & 511;
|
|
25032
25277
|
if (actualMode !== KEY_FILE_MODE) {
|
|
25033
25278
|
warn(
|
|
@@ -25124,16 +25369,16 @@ var DefaultSecretVault = class {
|
|
|
25124
25369
|
const oldVersion = this._keyVersion;
|
|
25125
25370
|
const newKey = randomBytes4(KEY_BYTES);
|
|
25126
25371
|
const newVersion = oldVersion + 1;
|
|
25127
|
-
|
|
25372
|
+
fs13.mkdirSync(path25.dirname(this.keyFile), { recursive: true });
|
|
25128
25373
|
const passphrase = getVaultPassphrase();
|
|
25129
25374
|
if (passphrase) {
|
|
25130
|
-
|
|
25375
|
+
fs13.writeFileSync(this.keyFile, wrapDataKey(newKey, newVersion, passphrase), { mode: 384 });
|
|
25131
25376
|
} else {
|
|
25132
25377
|
const keyFileBuf = Buffer.alloc(VERSIONED_KEY_FILE_SIZE);
|
|
25133
25378
|
KEY_FILE_MAGIC.copy(keyFileBuf, 0);
|
|
25134
25379
|
keyFileBuf[KEY_FILE_MAGIC.length] = newVersion;
|
|
25135
25380
|
newKey.copy(keyFileBuf, KEY_FILE_MAGIC.length + 1);
|
|
25136
|
-
|
|
25381
|
+
fs13.writeFileSync(this.keyFile, keyFileBuf, { mode: 384 });
|
|
25137
25382
|
}
|
|
25138
25383
|
checkKeyFilePermissions(this.keyFile, { warn: (msg) => this.logWarn(msg) });
|
|
25139
25384
|
this.key = newKey;
|
|
@@ -25151,7 +25396,7 @@ var DefaultSecretVault = class {
|
|
|
25151
25396
|
const passphrase = getVaultPassphrase();
|
|
25152
25397
|
if (!passphrase || !this.key) return;
|
|
25153
25398
|
try {
|
|
25154
|
-
|
|
25399
|
+
fs13.writeFileSync(this.keyFile, wrapDataKey(this.key, this._keyVersion, passphrase), {
|
|
25155
25400
|
mode: 384
|
|
25156
25401
|
});
|
|
25157
25402
|
checkKeyFilePermissions(this.keyFile, { warn: (msg) => this.logWarn(msg) });
|
|
@@ -25161,7 +25406,7 @@ var DefaultSecretVault = class {
|
|
|
25161
25406
|
loadOrCreateKey() {
|
|
25162
25407
|
if (this.key) return this.key;
|
|
25163
25408
|
try {
|
|
25164
|
-
const buf =
|
|
25409
|
+
const buf = fs13.readFileSync(this.keyFile);
|
|
25165
25410
|
if (isWrappedKeyFile(buf)) {
|
|
25166
25411
|
const { key: key2, version } = unwrapDataKey(buf, this.keyFile);
|
|
25167
25412
|
this.key = key2;
|
|
@@ -25208,15 +25453,15 @@ var DefaultSecretVault = class {
|
|
|
25208
25453
|
} catch (err) {
|
|
25209
25454
|
if (err.code !== "ENOENT") throw err;
|
|
25210
25455
|
}
|
|
25211
|
-
|
|
25456
|
+
fs13.mkdirSync(path25.dirname(this.keyFile), { recursive: true });
|
|
25212
25457
|
const key = randomBytes4(KEY_BYTES);
|
|
25213
25458
|
const passphrase = getVaultPassphrase();
|
|
25214
25459
|
const initialBytes = passphrase ? wrapDataKey(key, 1, passphrase) : key;
|
|
25215
25460
|
try {
|
|
25216
|
-
|
|
25461
|
+
fs13.writeFileSync(this.keyFile, initialBytes, { mode: 384, flag: "wx" });
|
|
25217
25462
|
} catch (err) {
|
|
25218
25463
|
if (err.code !== "EEXIST") throw err;
|
|
25219
|
-
const buf =
|
|
25464
|
+
const buf = fs13.readFileSync(this.keyFile);
|
|
25220
25465
|
if (isWrappedKeyFile(buf)) {
|
|
25221
25466
|
const { key: winnerKey, version } = unwrapDataKey(buf, this.keyFile);
|
|
25222
25467
|
this.key = winnerKey;
|
|
@@ -25266,7 +25511,7 @@ async function rewriteConfigEncrypted(configPath, vault, patch) {
|
|
|
25266
25511
|
}
|
|
25267
25512
|
const merged = deepMerge(current, patch ?? {});
|
|
25268
25513
|
const encrypted = encryptConfigSecrets(merged, vault);
|
|
25269
|
-
await fsp12.mkdir(
|
|
25514
|
+
await fsp12.mkdir(path25.dirname(configPath), { recursive: true });
|
|
25270
25515
|
await atomicWrite(configPath, JSON.stringify(encrypted, null, 2), { mode: 384 });
|
|
25271
25516
|
await restrictFilePermissions(configPath);
|
|
25272
25517
|
}
|
|
@@ -25350,7 +25595,7 @@ function walkCount(node, vault, counter) {
|
|
|
25350
25595
|
// src/storage/attachment-store.ts
|
|
25351
25596
|
import { randomBytes as randomBytes5 } from "node:crypto";
|
|
25352
25597
|
import * as fsp13 from "node:fs/promises";
|
|
25353
|
-
import * as
|
|
25598
|
+
import * as path26 from "node:path";
|
|
25354
25599
|
var DEFAULT_SPOOL_THRESHOLD = 256 * 1024;
|
|
25355
25600
|
var PLACEHOLDER_RE = /\[(pasted|image|file) #(\d+)[^\]]*\]|\[file:([^\]]+)\]/g;
|
|
25356
25601
|
var DefaultAttachmentStore = class {
|
|
@@ -25371,7 +25616,7 @@ var DefaultAttachmentStore = class {
|
|
|
25371
25616
|
let data = input.data;
|
|
25372
25617
|
if (this.spoolDir && bytes >= this.spoolThreshold) {
|
|
25373
25618
|
await fsp13.mkdir(this.spoolDir, { recursive: true });
|
|
25374
|
-
spooledPath =
|
|
25619
|
+
spooledPath = path26.join(this.spoolDir, `${id}.bin`);
|
|
25375
25620
|
await atomicWrite(spooledPath, input.data, {
|
|
25376
25621
|
encoding: input.kind === "image" ? "base64" : "utf8"
|
|
25377
25622
|
});
|
|
@@ -25490,8 +25735,8 @@ function mergeAdjacentText(blocks) {
|
|
|
25490
25735
|
}
|
|
25491
25736
|
|
|
25492
25737
|
// src/storage/config-loader.ts
|
|
25493
|
-
import * as
|
|
25494
|
-
import * as
|
|
25738
|
+
import * as fs14 from "node:fs/promises";
|
|
25739
|
+
import * as path27 from "node:path";
|
|
25495
25740
|
|
|
25496
25741
|
// src/types/config.ts
|
|
25497
25742
|
var DEFAULT_TUI_THINKING_WORD = "thinking";
|
|
@@ -25975,8 +26220,8 @@ function stripUnsafeInProjectFields(inProject, sourcePath, warn = (msg) => conso
|
|
|
25975
26220
|
return out;
|
|
25976
26221
|
}
|
|
25977
26222
|
function samePath(a, b) {
|
|
25978
|
-
let ra =
|
|
25979
|
-
let rb =
|
|
26223
|
+
let ra = path27.resolve(a);
|
|
26224
|
+
let rb = path27.resolve(b);
|
|
25980
26225
|
if (process.platform === "win32" || process.platform === "darwin") {
|
|
25981
26226
|
ra = ra.toLowerCase();
|
|
25982
26227
|
rb = rb.toLowerCase();
|
|
@@ -25998,7 +26243,7 @@ function deepMerge2(base, patch) {
|
|
|
25998
26243
|
opts
|
|
25999
26244
|
);
|
|
26000
26245
|
}
|
|
26001
|
-
var DefaultConfigLoader = class {
|
|
26246
|
+
var DefaultConfigLoader = class _DefaultConfigLoader {
|
|
26002
26247
|
paths;
|
|
26003
26248
|
strict;
|
|
26004
26249
|
vault;
|
|
@@ -26105,6 +26350,11 @@ var DefaultConfigLoader = class {
|
|
|
26105
26350
|
}
|
|
26106
26351
|
return Object.freeze(cfg);
|
|
26107
26352
|
}
|
|
26353
|
+
/** Check whether a config object contains only the two bootstrap keys. */
|
|
26354
|
+
static isBootstrapOnly(config) {
|
|
26355
|
+
const BOOTSTRAP_KEYS = /* @__PURE__ */ new Set(["version", "activeProfile"]);
|
|
26356
|
+
return Object.keys(config).every((k) => BOOTSTRAP_KEYS.has(k));
|
|
26357
|
+
}
|
|
26108
26358
|
async ensureGlobalDefaults() {
|
|
26109
26359
|
const fp = this.paths.globalConfig;
|
|
26110
26360
|
const t0 = Date.now();
|
|
@@ -26113,7 +26363,7 @@ var DefaultConfigLoader = class {
|
|
|
26113
26363
|
let parsed;
|
|
26114
26364
|
let fileExisted = true;
|
|
26115
26365
|
try {
|
|
26116
|
-
const raw = await
|
|
26366
|
+
const raw = await fs14.readFile(fp, "utf8");
|
|
26117
26367
|
const result = safeParse(raw);
|
|
26118
26368
|
if (!result.ok || !isPlainRecord(result.value)) {
|
|
26119
26369
|
return;
|
|
@@ -26142,9 +26392,12 @@ var DefaultConfigLoader = class {
|
|
|
26142
26392
|
fileExisted = false;
|
|
26143
26393
|
parsed = {};
|
|
26144
26394
|
}
|
|
26395
|
+
const profileName = parsed.activeProfile ?? "default";
|
|
26396
|
+
const profileFp = this.paths.profileConfig(profileName);
|
|
26397
|
+
await this.ensureProfileConfig(profileFp, parsed, fileExisted);
|
|
26145
26398
|
const bootstrap = {
|
|
26146
26399
|
version: 1,
|
|
26147
|
-
activeProfile:
|
|
26400
|
+
activeProfile: profileName
|
|
26148
26401
|
};
|
|
26149
26402
|
let needsBootstrapWrite = false;
|
|
26150
26403
|
if (parsed.version !== 1) {
|
|
@@ -26153,7 +26406,11 @@ var DefaultConfigLoader = class {
|
|
|
26153
26406
|
if (parsed.activeProfile === void 0) {
|
|
26154
26407
|
needsBootstrapWrite = true;
|
|
26155
26408
|
}
|
|
26409
|
+
if (!_DefaultConfigLoader.isBootstrapOnly(parsed)) {
|
|
26410
|
+
needsBootstrapWrite = true;
|
|
26411
|
+
}
|
|
26156
26412
|
if (needsBootstrapWrite) {
|
|
26413
|
+
await backupConfigFile(fp, this.paths);
|
|
26157
26414
|
await atomicWrite(fp, JSON.stringify(bootstrap, null, 2), { mode: 384 });
|
|
26158
26415
|
this.events?.emit("storage.write", {
|
|
26159
26416
|
sessionId: "~config~",
|
|
@@ -26165,9 +26422,6 @@ var DefaultConfigLoader = class {
|
|
|
26165
26422
|
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
26166
26423
|
});
|
|
26167
26424
|
}
|
|
26168
|
-
const profileName = bootstrap.activeProfile ?? "default";
|
|
26169
|
-
const profileFp = this.paths.profileConfig(profileName);
|
|
26170
|
-
await this.ensureProfileConfig(profileFp, parsed, fileExisted);
|
|
26171
26425
|
});
|
|
26172
26426
|
} catch (err) {
|
|
26173
26427
|
this.events?.emit("storage.error", {
|
|
@@ -26193,77 +26447,92 @@ var DefaultConfigLoader = class {
|
|
|
26193
26447
|
* On first boot: migrate content from the old flat global config, or seed
|
|
26194
26448
|
* with behavior defaults. Subsequent boots: fill any missing keys from
|
|
26195
26449
|
* BEHAVIOR_DEFAULTS (keeping user settings intact).
|
|
26450
|
+
*
|
|
26451
|
+
* CRITICAL SAFETY NET: when the profile already exists but the old global
|
|
26452
|
+
* config has extra non-bootstrap keys (because WebUI/CLI persistence functions
|
|
26453
|
+
* mistakenly wrote settings to the root config), those keys are merged into
|
|
26454
|
+
* the profile so they are NOT silently destroyed by the subsequent bootstrap
|
|
26455
|
+
* trim. This prevents the "config keeps getting emptied" bug.
|
|
26196
26456
|
*/
|
|
26197
26457
|
async ensureProfileConfig(profileFp, oldGlobalParsed, oldFileExisted) {
|
|
26198
26458
|
const t0 = Date.now();
|
|
26459
|
+
let parsed;
|
|
26460
|
+
let existed = true;
|
|
26199
26461
|
try {
|
|
26200
|
-
await
|
|
26201
|
-
|
|
26202
|
-
|
|
26203
|
-
|
|
26204
|
-
|
|
26205
|
-
|
|
26206
|
-
|
|
26207
|
-
|
|
26208
|
-
|
|
26209
|
-
|
|
26210
|
-
|
|
26211
|
-
|
|
26212
|
-
|
|
26213
|
-
|
|
26462
|
+
const raw = await fs14.readFile(profileFp, "utf8");
|
|
26463
|
+
const result = safeParse(raw);
|
|
26464
|
+
if (!result.ok || !isPlainRecord(result.value)) {
|
|
26465
|
+
this.logWarn("Profile config parse failed \u2014 falling back to defaults", {
|
|
26466
|
+
event: "config.profile_parse_failed",
|
|
26467
|
+
path: profileFp
|
|
26468
|
+
});
|
|
26469
|
+
parsed = {};
|
|
26470
|
+
} else {
|
|
26471
|
+
parsed = result.value;
|
|
26472
|
+
}
|
|
26473
|
+
} catch (err) {
|
|
26474
|
+
if (err.code !== "ENOENT") {
|
|
26475
|
+
this.logWarn("Profile config read failed", {
|
|
26476
|
+
event: "config.profile_read_failed",
|
|
26477
|
+
path: profileFp,
|
|
26478
|
+
message: toErrorMessage(err)
|
|
26479
|
+
});
|
|
26480
|
+
return;
|
|
26481
|
+
}
|
|
26482
|
+
existed = false;
|
|
26483
|
+
parsed = {};
|
|
26484
|
+
}
|
|
26485
|
+
const profileHasContent = existed && Object.keys(parsed).some((k) => k !== "version" && k !== "activeProfile");
|
|
26486
|
+
let seed;
|
|
26487
|
+
if (!profileHasContent && oldFileExisted && Object.keys(oldGlobalParsed).length >= 1) {
|
|
26488
|
+
seed = { ...oldGlobalParsed };
|
|
26489
|
+
delete seed["activeProfile"];
|
|
26490
|
+
const filled = fillMissingDefaults(seed, BEHAVIOR_DEFAULTS);
|
|
26491
|
+
seed = filled.value;
|
|
26492
|
+
} else {
|
|
26493
|
+
if (existed && !_DefaultConfigLoader.isBootstrapOnly(oldGlobalParsed)) {
|
|
26494
|
+
const BOOTSTRAP_KEYS = /* @__PURE__ */ new Set(["version", "activeProfile"]);
|
|
26495
|
+
let merged = false;
|
|
26496
|
+
for (const [k, v] of Object.entries(oldGlobalParsed)) {
|
|
26497
|
+
if (!BOOTSTRAP_KEYS.has(k) && !(k in parsed)) {
|
|
26498
|
+
parsed[k] = v;
|
|
26499
|
+
merged = true;
|
|
26214
26500
|
}
|
|
26215
|
-
}
|
|
26216
|
-
|
|
26217
|
-
|
|
26218
|
-
|
|
26219
|
-
|
|
26220
|
-
|
|
26501
|
+
}
|
|
26502
|
+
if (merged) {
|
|
26503
|
+
const filled2 = fillMissingDefaults(parsed, BEHAVIOR_DEFAULTS);
|
|
26504
|
+
if (filled2.changed) {
|
|
26505
|
+
seed = filled2.value;
|
|
26506
|
+
await backupConfigFile(profileFp, this.paths);
|
|
26507
|
+
await atomicWrite(profileFp, JSON.stringify(seed, null, 2), { mode: 384 });
|
|
26508
|
+
this.events?.emit("storage.write", {
|
|
26509
|
+
sessionId: "~config~",
|
|
26510
|
+
store: "config",
|
|
26511
|
+
filePath: profileFp,
|
|
26512
|
+
operation: "ensure_profile_defaults",
|
|
26513
|
+
outcome: "success",
|
|
26514
|
+
durationMs: Date.now() - t0,
|
|
26515
|
+
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
26221
26516
|
});
|
|
26222
26517
|
return;
|
|
26223
26518
|
}
|
|
26224
|
-
existed = false;
|
|
26225
|
-
parsed = {};
|
|
26226
26519
|
}
|
|
26227
|
-
|
|
26228
|
-
|
|
26229
|
-
|
|
26230
|
-
|
|
26231
|
-
const filled = fillMissingDefaults(seed, BEHAVIOR_DEFAULTS);
|
|
26232
|
-
seed = filled.value;
|
|
26233
|
-
} else {
|
|
26234
|
-
const filled = fillMissingDefaults(parsed, BEHAVIOR_DEFAULTS);
|
|
26235
|
-
if (!filled.changed) return;
|
|
26236
|
-
seed = filled.value;
|
|
26237
|
-
}
|
|
26238
|
-
await atomicWrite(profileFp, JSON.stringify(seed, null, 2), { mode: 384 });
|
|
26239
|
-
this.events?.emit("storage.write", {
|
|
26240
|
-
sessionId: "~config~",
|
|
26241
|
-
store: "config",
|
|
26242
|
-
filePath: profileFp,
|
|
26243
|
-
operation: "ensure_profile_defaults",
|
|
26244
|
-
outcome: "success",
|
|
26245
|
-
durationMs: Date.now() - t0,
|
|
26246
|
-
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
26247
|
-
});
|
|
26248
|
-
});
|
|
26249
|
-
} catch (err) {
|
|
26250
|
-
this.events?.emit("storage.error", {
|
|
26251
|
-
sessionId: "~config~",
|
|
26252
|
-
store: "config",
|
|
26253
|
-
filePath: profileFp,
|
|
26254
|
-
operation: "ensure_profile_defaults",
|
|
26255
|
-
outcome: "failure",
|
|
26256
|
-
error: storageErrorString(err),
|
|
26257
|
-
recoverable: false,
|
|
26258
|
-
durationMs: Date.now() - t0,
|
|
26259
|
-
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
26260
|
-
});
|
|
26261
|
-
this.logWarn("Profile config defaults write failed", {
|
|
26262
|
-
event: "config.profile_write_failed",
|
|
26263
|
-
path: profileFp,
|
|
26264
|
-
message: toErrorMessage(err)
|
|
26265
|
-
});
|
|
26520
|
+
}
|
|
26521
|
+
const filled = fillMissingDefaults(parsed, BEHAVIOR_DEFAULTS);
|
|
26522
|
+
if (!filled.changed) return;
|
|
26523
|
+
seed = filled.value;
|
|
26266
26524
|
}
|
|
26525
|
+
await backupConfigFile(profileFp, this.paths);
|
|
26526
|
+
await atomicWrite(profileFp, JSON.stringify(seed, null, 2), { mode: 384 });
|
|
26527
|
+
this.events?.emit("storage.write", {
|
|
26528
|
+
sessionId: "~config~",
|
|
26529
|
+
store: "config",
|
|
26530
|
+
filePath: profileFp,
|
|
26531
|
+
operation: "ensure_profile_defaults",
|
|
26532
|
+
outcome: "success",
|
|
26533
|
+
durationMs: Date.now() - t0,
|
|
26534
|
+
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
26535
|
+
});
|
|
26267
26536
|
}
|
|
26268
26537
|
/**
|
|
26269
26538
|
* Persist a sync config to ~/.wrongstack/sync.json, with the token encrypted
|
|
@@ -26313,7 +26582,7 @@ var DefaultConfigLoader = class {
|
|
|
26313
26582
|
const fp = this.paths.syncConfig;
|
|
26314
26583
|
const t0 = Date.now();
|
|
26315
26584
|
try {
|
|
26316
|
-
const raw = await
|
|
26585
|
+
const raw = await fs14.readFile(fp, "utf8");
|
|
26317
26586
|
const parsed = safeParse(raw);
|
|
26318
26587
|
if (!parsed.ok || !parsed.value) {
|
|
26319
26588
|
this.events?.emit("storage.read", {
|
|
@@ -26375,7 +26644,7 @@ var DefaultConfigLoader = class {
|
|
|
26375
26644
|
const t0 = Date.now();
|
|
26376
26645
|
let mtimeMs = null;
|
|
26377
26646
|
try {
|
|
26378
|
-
const stat13 = await
|
|
26647
|
+
const stat13 = await fs14.stat(file);
|
|
26379
26648
|
mtimeMs = stat13.mtimeMs;
|
|
26380
26649
|
const cached = this.jsonCache.get(file);
|
|
26381
26650
|
if (cached && cached.mtimeMs === mtimeMs) {
|
|
@@ -26405,7 +26674,7 @@ var DefaultConfigLoader = class {
|
|
|
26405
26674
|
}
|
|
26406
26675
|
let raw;
|
|
26407
26676
|
try {
|
|
26408
|
-
raw = await
|
|
26677
|
+
raw = await fs14.readFile(file, "utf8");
|
|
26409
26678
|
} catch (err) {
|
|
26410
26679
|
if (err.code !== "ENOENT") {
|
|
26411
26680
|
this.events?.emit("storage.read", {
|
|
@@ -26936,7 +27205,7 @@ ${cat}:`);
|
|
|
26936
27205
|
|
|
26937
27206
|
// src/storage/queue-store.ts
|
|
26938
27207
|
import * as fsp15 from "node:fs/promises";
|
|
26939
|
-
import * as
|
|
27208
|
+
import * as path28 from "node:path";
|
|
26940
27209
|
var QueueStore = class {
|
|
26941
27210
|
file;
|
|
26942
27211
|
// Use `| undefined` (not `?`) so exactOptionalPropertyTypes doesn't
|
|
@@ -26945,7 +27214,7 @@ var QueueStore = class {
|
|
|
26945
27214
|
traceId;
|
|
26946
27215
|
logger;
|
|
26947
27216
|
constructor(opts) {
|
|
26948
|
-
this.file =
|
|
27217
|
+
this.file = path28.join(opts.dir, "queue.json");
|
|
26949
27218
|
this.events = opts.events;
|
|
26950
27219
|
this.traceId = opts.traceId;
|
|
26951
27220
|
this.logger = opts.logger;
|
|
@@ -27103,7 +27372,7 @@ function isPersistedQueueItem(v) {
|
|
|
27103
27372
|
// src/storage/recovery-lock.ts
|
|
27104
27373
|
import * as fsp16 from "node:fs/promises";
|
|
27105
27374
|
import * as os3 from "node:os";
|
|
27106
|
-
import * as
|
|
27375
|
+
import * as path29 from "node:path";
|
|
27107
27376
|
var LOCK_FILE = "active.json";
|
|
27108
27377
|
var DEFAULT_MAX_AGE_MS = 24 * 60 * 60 * 1e3;
|
|
27109
27378
|
var RecoveryLock = class {
|
|
@@ -27114,7 +27383,7 @@ var RecoveryLock = class {
|
|
|
27114
27383
|
sessionStore;
|
|
27115
27384
|
probe;
|
|
27116
27385
|
constructor(opts) {
|
|
27117
|
-
this.file =
|
|
27386
|
+
this.file = path29.join(opts.dir, LOCK_FILE);
|
|
27118
27387
|
this.pid = opts.pid ?? process.pid;
|
|
27119
27388
|
this.hostname = opts.hostname ?? os3.hostname();
|
|
27120
27389
|
this.maxAgeMs = opts.maxAgeMs ?? DEFAULT_MAX_AGE_MS;
|
|
@@ -27175,7 +27444,7 @@ var RecoveryLock = class {
|
|
|
27175
27444
|
* null return before calling this.
|
|
27176
27445
|
*/
|
|
27177
27446
|
async write(sessionId) {
|
|
27178
|
-
await ensureDir(
|
|
27447
|
+
await ensureDir(path29.dirname(this.file));
|
|
27179
27448
|
const lock = {
|
|
27180
27449
|
v: 1,
|
|
27181
27450
|
sessionId,
|
|
@@ -27474,7 +27743,7 @@ function resolveSessionLoggingConfig(cfg) {
|
|
|
27474
27743
|
}
|
|
27475
27744
|
|
|
27476
27745
|
// src/storage/session-reader.ts
|
|
27477
|
-
import * as
|
|
27746
|
+
import * as fs15 from "node:fs/promises";
|
|
27478
27747
|
var DefaultSessionReader = class _DefaultSessionReader {
|
|
27479
27748
|
store;
|
|
27480
27749
|
eventCache = /* @__PURE__ */ new Map();
|
|
@@ -27492,7 +27761,7 @@ var DefaultSessionReader = class _DefaultSessionReader {
|
|
|
27492
27761
|
const sessionPath = sessionScopedPath(rootDir, sessionId, ".jsonl");
|
|
27493
27762
|
let mtimeMs = null;
|
|
27494
27763
|
try {
|
|
27495
|
-
const stat13 = await
|
|
27764
|
+
const stat13 = await fs15.stat(sessionPath);
|
|
27496
27765
|
mtimeMs = stat13.mtimeMs;
|
|
27497
27766
|
} catch {
|
|
27498
27767
|
this.eventCache.delete(sessionId);
|