@wrongstack/core 0.291.2 → 0.292.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/coordination/director-tools.d.ts.map +1 -1
- package/dist/coordination/index.js +113 -113
- package/dist/coordination/index.js.map +3 -3
- package/dist/core/agent-loop.d.ts.map +1 -1
- package/dist/core/agent-response.d.ts.map +1 -1
- package/dist/core/context.d.ts +10 -0
- package/dist/core/context.d.ts.map +1 -1
- package/dist/core/system-prompt-builder.d.ts +20 -1
- package/dist/core/system-prompt-builder.d.ts.map +1 -1
- package/dist/defaults/index.d.ts +1 -0
- package/dist/defaults/index.d.ts.map +1 -1
- package/dist/defaults/index.js +3132 -2440
- package/dist/defaults/index.js.map +4 -4
- package/dist/execution/auto-compaction-middleware.d.ts +34 -2
- package/dist/execution/auto-compaction-middleware.d.ts.map +1 -1
- package/dist/execution/compaction-core.d.ts +59 -0
- package/dist/execution/compaction-core.d.ts.map +1 -1
- package/dist/execution/compactor.d.ts.map +1 -1
- package/dist/execution/error-handler.d.ts +7 -0
- package/dist/execution/error-handler.d.ts.map +1 -1
- package/dist/execution/index.d.ts +1 -0
- package/dist/execution/index.d.ts.map +1 -1
- package/dist/execution/index.js +4677 -3924
- package/dist/execution/index.js.map +4 -4
- package/dist/execution/intelligent-compactor.d.ts.map +1 -1
- package/dist/execution/model-runtime.d.ts.map +1 -1
- package/dist/execution/selective-compactor.d.ts +10 -0
- package/dist/execution/selective-compactor.d.ts.map +1 -1
- package/dist/execution/subagent-compaction.d.ts +24 -0
- package/dist/execution/subagent-compaction.d.ts.map +1 -0
- package/dist/hq/alerts.d.ts +5 -0
- package/dist/hq/alerts.d.ts.map +1 -1
- package/dist/hq/auth-audit.d.ts +115 -0
- package/dist/hq/auth-audit.d.ts.map +1 -0
- package/dist/hq/auth-store.d.ts +98 -2
- package/dist/hq/auth-store.d.ts.map +1 -1
- package/dist/hq/brain-bridge.d.ts +2 -8
- package/dist/hq/brain-bridge.d.ts.map +1 -1
- package/dist/hq/bridge-context.d.ts +74 -0
- package/dist/hq/bridge-context.d.ts.map +1 -0
- package/dist/hq/cost-bridge.d.ts +4 -10
- package/dist/hq/cost-bridge.d.ts.map +1 -1
- package/dist/hq/fleet-bridge.d.ts +2 -8
- package/dist/hq/fleet-bridge.d.ts.map +1 -1
- package/dist/hq/index.d.ts +2 -0
- package/dist/hq/index.d.ts.map +1 -1
- package/dist/hq/index.js +358 -222
- package/dist/hq/index.js.map +4 -4
- package/dist/hq/protocol/session.d.ts +13 -0
- package/dist/hq/protocol/session.d.ts.map +1 -1
- package/dist/hq/tool-bridge.d.ts +2 -8
- package/dist/hq/tool-bridge.d.ts.map +1 -1
- package/dist/hq/worktree-bridge.d.ts +2 -8
- package/dist/hq/worktree-bridge.d.ts.map +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6054 -4741
- package/dist/index.js.map +4 -4
- package/dist/infrastructure/index.js +32 -15
- package/dist/infrastructure/index.js.map +2 -2
- package/dist/infrastructure/provider-cache-ledger.d.ts +48 -0
- package/dist/infrastructure/provider-cache-ledger.d.ts.map +1 -0
- package/dist/infrastructure/token-counter.d.ts +4 -2
- package/dist/infrastructure/token-counter.d.ts.map +1 -1
- package/dist/kernel/events/brain-events.d.ts +2 -0
- package/dist/kernel/events/brain-events.d.ts.map +1 -1
- package/dist/kernel/events/memory-events.d.ts +102 -0
- package/dist/kernel/events/memory-events.d.ts.map +1 -1
- package/dist/kernel/events/session-events.d.ts +35 -0
- package/dist/kernel/events/session-events.d.ts.map +1 -1
- package/dist/models/index.js +161 -78
- package/dist/models/index.js.map +3 -3
- package/dist/models/llm-selector.d.ts.map +1 -1
- package/dist/notifications/index.d.ts +40 -0
- package/dist/notifications/index.d.ts.map +1 -0
- package/dist/notifications/notifier-impl.d.ts +34 -0
- package/dist/notifications/notifier-impl.d.ts.map +1 -0
- package/dist/notifications/notifier.d.ts +95 -0
- package/dist/notifications/notifier.d.ts.map +1 -0
- package/dist/notifications/types.d.ts +111 -0
- package/dist/notifications/types.d.ts.map +1 -0
- package/dist/plugin/api.d.ts +10 -1
- package/dist/plugin/api.d.ts.map +1 -1
- package/dist/registry/provider-registry.d.ts +1 -1
- package/dist/registry/provider-registry.d.ts.map +1 -1
- package/dist/security/index.js +15 -15
- package/dist/security/index.js.map +3 -3
- package/dist/skills/index.js +80 -80
- package/dist/skills/index.js.map +3 -3
- package/dist/storage/config-loader.d.ts +3 -5
- package/dist/storage/config-loader.d.ts.map +1 -1
- package/dist/storage/index.js +325 -259
- package/dist/storage/index.js.map +4 -4
- package/dist/storage/memory-consolidator.d.ts +14 -2
- package/dist/storage/memory-consolidator.d.ts.map +1 -1
- package/dist/storage/queue-store.d.ts +2 -0
- package/dist/storage/queue-store.d.ts.map +1 -1
- package/dist/tools/index.js +6 -6
- package/dist/tools/index.js.map +3 -3
- package/dist/tools/mcp-control.d.ts +1 -1
- package/dist/types/config.d.ts +64 -20
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/index.js +60 -34
- package/dist/types/index.js.map +3 -3
- package/dist/types/plugin.d.ts +11 -0
- package/dist/types/plugin.d.ts.map +1 -1
- package/dist/types/provider.d.ts +31 -0
- package/dist/types/provider.d.ts.map +1 -1
- package/dist/types/token-counter.d.ts +8 -1
- package/dist/types/token-counter.d.ts.map +1 -1
- package/dist/utils/cache-key.d.ts +19 -0
- package/dist/utils/cache-key.d.ts.map +1 -0
- package/dist/utils/compaction-preview.d.ts +8 -0
- package/dist/utils/compaction-preview.d.ts.map +1 -0
- package/dist/utils/context-breakdown.d.ts +59 -0
- package/dist/utils/context-breakdown.d.ts.map +1 -0
- package/dist/utils/context-evidence.d.ts +22 -0
- package/dist/utils/context-evidence.d.ts.map +1 -1
- package/dist/utils/index.d.ts +2 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +1730 -1543
- package/dist/utils/index.js.map +4 -4
- package/dist/utils/token-estimate.d.ts +27 -0
- package/dist/utils/token-estimate.d.ts.map +1 -1
- package/instructions/llm/memory-consolidator.md +19 -3
- package/instructions/system.md +10 -2
- package/package.json +2 -2
package/dist/storage/index.js
CHANGED
|
@@ -217,65 +217,6 @@ async function backupConfigFile(filePath, paths) {
|
|
|
217
217
|
}
|
|
218
218
|
}
|
|
219
219
|
|
|
220
|
-
// src/utils/deep-merge.ts
|
|
221
|
-
var FORBIDDEN_PROTO_KEYS = /* @__PURE__ */ new Set([
|
|
222
|
-
"__proto__",
|
|
223
|
-
"constructor",
|
|
224
|
-
"prototype",
|
|
225
|
-
"__defineGetter__",
|
|
226
|
-
"__defineSetter__",
|
|
227
|
-
"__lookupGetter__",
|
|
228
|
-
"__lookupSetter__"
|
|
229
|
-
]);
|
|
230
|
-
function isPrimitiveArray(a) {
|
|
231
|
-
return a.every((v) => v === null || typeof v !== "object" && typeof v !== "function");
|
|
232
|
-
}
|
|
233
|
-
function deepMerge(base, patch, options = {}) {
|
|
234
|
-
const {
|
|
235
|
-
conflictResolution = "prefer-patch",
|
|
236
|
-
arrayMode = "replace",
|
|
237
|
-
protectProto = true,
|
|
238
|
-
onNonPrimitiveArrayReplace
|
|
239
|
-
} = options;
|
|
240
|
-
if (typeof base !== "object" || base === null) {
|
|
241
|
-
return conflictResolution === "prefer-patch" ? patch : base;
|
|
242
|
-
}
|
|
243
|
-
if (typeof patch !== "object" || patch === null) {
|
|
244
|
-
return conflictResolution === "prefer-patch" ? patch : base;
|
|
245
|
-
}
|
|
246
|
-
if (Array.isArray(base) && Array.isArray(patch)) {
|
|
247
|
-
if (arrayMode === "concat-primitives" && isPrimitiveArray(base) && isPrimitiveArray(patch)) {
|
|
248
|
-
return [.../* @__PURE__ */ new Set([...base, ...patch])];
|
|
249
|
-
}
|
|
250
|
-
return conflictResolution === "prefer-patch" ? patch : base;
|
|
251
|
-
}
|
|
252
|
-
if (Array.isArray(base) || Array.isArray(patch)) {
|
|
253
|
-
return conflictResolution === "prefer-patch" ? patch : base;
|
|
254
|
-
}
|
|
255
|
-
const baseObj = base;
|
|
256
|
-
const patchObj = patch;
|
|
257
|
-
const out = { ...baseObj };
|
|
258
|
-
for (const [k, v] of Object.entries(patchObj)) {
|
|
259
|
-
if (protectProto && FORBIDDEN_PROTO_KEYS.has(k)) continue;
|
|
260
|
-
const existing = out[k];
|
|
261
|
-
if (v !== null && typeof v === "object" && !Array.isArray(v) && existing !== null && typeof existing === "object" && !Array.isArray(existing)) {
|
|
262
|
-
out[k] = deepMerge(existing, v, options);
|
|
263
|
-
} else if (Array.isArray(v) && Array.isArray(existing)) {
|
|
264
|
-
if (onNonPrimitiveArrayReplace && !isPrimitiveArray(v)) {
|
|
265
|
-
onNonPrimitiveArrayReplace(k, existing.length, v.length);
|
|
266
|
-
}
|
|
267
|
-
out[k] = deepMerge(existing, v, options);
|
|
268
|
-
} else if (v !== void 0) {
|
|
269
|
-
if (onNonPrimitiveArrayReplace && Array.isArray(v) && !isPrimitiveArray(v)) {
|
|
270
|
-
const existingLen = Array.isArray(existing) ? existing.length : 0;
|
|
271
|
-
onNonPrimitiveArrayReplace(k, existingLen, v.length);
|
|
272
|
-
}
|
|
273
|
-
out[k] = v;
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
return out;
|
|
277
|
-
}
|
|
278
|
-
|
|
279
220
|
// src/utils/error.ts
|
|
280
221
|
function toErrorMessage(err) {
|
|
281
222
|
return err instanceof Error ? err.message : String(err);
|
|
@@ -397,6 +338,68 @@ function isEmptyMessage(msg) {
|
|
|
397
338
|
return !hasMeaningfulContent(msg.content);
|
|
398
339
|
}
|
|
399
340
|
|
|
341
|
+
// src/types/config.ts
|
|
342
|
+
var DEFAULT_TUI_THINKING_WORD = "thinking";
|
|
343
|
+
|
|
344
|
+
// src/utils/deep-merge.ts
|
|
345
|
+
var FORBIDDEN_PROTO_KEYS = /* @__PURE__ */ new Set([
|
|
346
|
+
"__proto__",
|
|
347
|
+
"constructor",
|
|
348
|
+
"prototype",
|
|
349
|
+
"__defineGetter__",
|
|
350
|
+
"__defineSetter__",
|
|
351
|
+
"__lookupGetter__",
|
|
352
|
+
"__lookupSetter__"
|
|
353
|
+
]);
|
|
354
|
+
function isPrimitiveArray(a) {
|
|
355
|
+
return a.every((v) => v === null || typeof v !== "object" && typeof v !== "function");
|
|
356
|
+
}
|
|
357
|
+
function deepMerge(base, patch, options = {}) {
|
|
358
|
+
const {
|
|
359
|
+
conflictResolution = "prefer-patch",
|
|
360
|
+
arrayMode = "replace",
|
|
361
|
+
protectProto = true,
|
|
362
|
+
onNonPrimitiveArrayReplace
|
|
363
|
+
} = options;
|
|
364
|
+
if (typeof base !== "object" || base === null) {
|
|
365
|
+
return conflictResolution === "prefer-patch" ? patch : base;
|
|
366
|
+
}
|
|
367
|
+
if (typeof patch !== "object" || patch === null) {
|
|
368
|
+
return conflictResolution === "prefer-patch" ? patch : base;
|
|
369
|
+
}
|
|
370
|
+
if (Array.isArray(base) && Array.isArray(patch)) {
|
|
371
|
+
if (arrayMode === "concat-primitives" && isPrimitiveArray(base) && isPrimitiveArray(patch)) {
|
|
372
|
+
return [.../* @__PURE__ */ new Set([...base, ...patch])];
|
|
373
|
+
}
|
|
374
|
+
return conflictResolution === "prefer-patch" ? patch : base;
|
|
375
|
+
}
|
|
376
|
+
if (Array.isArray(base) || Array.isArray(patch)) {
|
|
377
|
+
return conflictResolution === "prefer-patch" ? patch : base;
|
|
378
|
+
}
|
|
379
|
+
const baseObj = base;
|
|
380
|
+
const patchObj = patch;
|
|
381
|
+
const out = { ...baseObj };
|
|
382
|
+
for (const [k, v] of Object.entries(patchObj)) {
|
|
383
|
+
if (protectProto && FORBIDDEN_PROTO_KEYS.has(k)) continue;
|
|
384
|
+
const existing = out[k];
|
|
385
|
+
if (v !== null && typeof v === "object" && !Array.isArray(v) && existing !== null && typeof existing === "object" && !Array.isArray(existing)) {
|
|
386
|
+
out[k] = deepMerge(existing, v, options);
|
|
387
|
+
} else if (Array.isArray(v) && Array.isArray(existing)) {
|
|
388
|
+
if (onNonPrimitiveArrayReplace && !isPrimitiveArray(v)) {
|
|
389
|
+
onNonPrimitiveArrayReplace(k, existing.length, v.length);
|
|
390
|
+
}
|
|
391
|
+
out[k] = deepMerge(existing, v, options);
|
|
392
|
+
} else if (v !== void 0) {
|
|
393
|
+
if (onNonPrimitiveArrayReplace && Array.isArray(v) && !isPrimitiveArray(v)) {
|
|
394
|
+
const existingLen = Array.isArray(existing) ? existing.length : 0;
|
|
395
|
+
onNonPrimitiveArrayReplace(k, existingLen, v.length);
|
|
396
|
+
}
|
|
397
|
+
out[k] = v;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
return out;
|
|
401
|
+
}
|
|
402
|
+
|
|
400
403
|
// src/utils/regex-guard.ts
|
|
401
404
|
var MAX_PATTERN_LEN = 512;
|
|
402
405
|
var DANGEROUS_PATTERNS = [
|
|
@@ -1762,8 +1765,8 @@ function sha256(content) {
|
|
|
1762
1765
|
return createHash2("sha256").update(content).digest("hex");
|
|
1763
1766
|
}
|
|
1764
1767
|
function isInside(root, target) {
|
|
1765
|
-
const
|
|
1766
|
-
return
|
|
1768
|
+
const relative8 = path6.relative(root, target);
|
|
1769
|
+
return relative8 === "" || !relative8.startsWith("..") && !path6.isAbsolute(relative8);
|
|
1767
1770
|
}
|
|
1768
1771
|
function normalizeRelative(input) {
|
|
1769
1772
|
if (!input || path6.isAbsolute(input)) return null;
|
|
@@ -1775,8 +1778,8 @@ function normalizeRelative(input) {
|
|
|
1775
1778
|
function parseNulPaths(output) {
|
|
1776
1779
|
return output.split("\0").map(normalizeRelative).filter((value) => value !== null);
|
|
1777
1780
|
}
|
|
1778
|
-
function isWrongStackWorktreePath(
|
|
1779
|
-
return
|
|
1781
|
+
function isWrongStackWorktreePath(relative8) {
|
|
1782
|
+
return relative8 === ".wrongstack/worktrees" || relative8.startsWith(".wrongstack/worktrees/");
|
|
1780
1783
|
}
|
|
1781
1784
|
var SessionCheckpointCas = class {
|
|
1782
1785
|
rootDir;
|
|
@@ -1802,16 +1805,16 @@ var SessionCheckpointCas = class {
|
|
|
1802
1805
|
}
|
|
1803
1806
|
const relativePaths = [
|
|
1804
1807
|
.../* @__PURE__ */ new Set([...parseNulPaths(tracked.stdout), ...parseNulPaths(untracked.stdout)])
|
|
1805
|
-
].filter((
|
|
1808
|
+
].filter((relative8) => !isWrongStackWorktreePath(relative8)).sort();
|
|
1806
1809
|
const unresolved = [];
|
|
1807
1810
|
let scheduledBytes = 0;
|
|
1808
1811
|
const captured = await mapWithConcurrency(
|
|
1809
1812
|
relativePaths,
|
|
1810
1813
|
CAPTURE_CONCURRENCY,
|
|
1811
|
-
async (
|
|
1812
|
-
const absolute = path6.resolve(this.projectRoot, ...
|
|
1814
|
+
async (relative8) => {
|
|
1815
|
+
const absolute = path6.resolve(this.projectRoot, ...relative8.split("/"));
|
|
1813
1816
|
if (!isInside(this.projectRoot, absolute)) {
|
|
1814
|
-
unresolved.push({ path:
|
|
1817
|
+
unresolved.push({ path: relative8, reason: "path escapes project root" });
|
|
1815
1818
|
return null;
|
|
1816
1819
|
}
|
|
1817
1820
|
try {
|
|
@@ -1821,27 +1824,27 @@ var SessionCheckpointCas = class {
|
|
|
1821
1824
|
const resolvedLink = path6.resolve(path6.dirname(absolute), linkTarget);
|
|
1822
1825
|
if (path6.isAbsolute(linkTarget) || !isInside(this.projectRoot, resolvedLink)) {
|
|
1823
1826
|
unresolved.push({
|
|
1824
|
-
path:
|
|
1827
|
+
path: relative8,
|
|
1825
1828
|
reason: "symlink target escapes project root"
|
|
1826
1829
|
});
|
|
1827
1830
|
return null;
|
|
1828
1831
|
}
|
|
1829
|
-
return { path:
|
|
1832
|
+
return { path: relative8, state: "symlink", linkTarget };
|
|
1830
1833
|
}
|
|
1831
1834
|
if (!stat13.isFile()) {
|
|
1832
|
-
unresolved.push({ path:
|
|
1835
|
+
unresolved.push({ path: relative8, reason: "changed path is not a regular file" });
|
|
1833
1836
|
return null;
|
|
1834
1837
|
}
|
|
1835
1838
|
if (stat13.size > MAX_BLOB_BYTES) {
|
|
1836
1839
|
unresolved.push({
|
|
1837
|
-
path:
|
|
1840
|
+
path: relative8,
|
|
1838
1841
|
reason: `file exceeds ${MAX_BLOB_BYTES}-byte checkpoint blob limit`
|
|
1839
1842
|
});
|
|
1840
1843
|
return null;
|
|
1841
1844
|
}
|
|
1842
1845
|
if (scheduledBytes + stat13.size > MAX_CHECKPOINT_BYTES) {
|
|
1843
1846
|
unresolved.push({
|
|
1844
|
-
path:
|
|
1847
|
+
path: relative8,
|
|
1845
1848
|
reason: `checkpoint exceeds ${MAX_CHECKPOINT_BYTES}-byte aggregate blob limit`
|
|
1846
1849
|
});
|
|
1847
1850
|
return null;
|
|
@@ -1850,12 +1853,12 @@ var SessionCheckpointCas = class {
|
|
|
1850
1853
|
const content = await fsp2.readFile(absolute);
|
|
1851
1854
|
const blobHash = sha256(content);
|
|
1852
1855
|
await this.putBlob(blobHash, content);
|
|
1853
|
-
return { path:
|
|
1856
|
+
return { path: relative8, state: "file", blobHash, mode: stat13.mode & 511 };
|
|
1854
1857
|
} catch (err) {
|
|
1855
1858
|
if (err.code === "ENOENT") {
|
|
1856
|
-
return { path:
|
|
1859
|
+
return { path: relative8, state: "absent" };
|
|
1857
1860
|
}
|
|
1858
|
-
unresolved.push({ path:
|
|
1861
|
+
unresolved.push({ path: relative8, reason: toErrorMessage(err) });
|
|
1859
1862
|
return null;
|
|
1860
1863
|
}
|
|
1861
1864
|
}
|
|
@@ -2045,8 +2048,8 @@ var SessionCheckpointCas = class {
|
|
|
2045
2048
|
}
|
|
2046
2049
|
return parsed;
|
|
2047
2050
|
}
|
|
2048
|
-
async safeOutputPath(target, realTarget,
|
|
2049
|
-
const normalized = normalizeRelative(
|
|
2051
|
+
async safeOutputPath(target, realTarget, relative8) {
|
|
2052
|
+
const normalized = normalizeRelative(relative8);
|
|
2050
2053
|
if (!normalized) throw new Error("invalid relative path");
|
|
2051
2054
|
const output = path6.resolve(target, ...normalized.split("/"));
|
|
2052
2055
|
if (!isInside(target, output)) throw new Error("path escapes checkpoint target");
|
|
@@ -2132,8 +2135,8 @@ var MAX_REVALIDATE_BYTES = 5 * 1024 * 1024;
|
|
|
2132
2135
|
var VALIDATION_CONCURRENCY = 8;
|
|
2133
2136
|
var NOTICE_PATH_LIMIT = 20;
|
|
2134
2137
|
function isInside2(root, target) {
|
|
2135
|
-
const
|
|
2136
|
-
return
|
|
2138
|
+
const relative8 = path7.relative(root, target);
|
|
2139
|
+
return relative8 === "" || !relative8.startsWith("..") && !path7.isAbsolute(relative8);
|
|
2137
2140
|
}
|
|
2138
2141
|
function errno(err) {
|
|
2139
2142
|
return err && typeof err === "object" && "code" in err ? String(err.code) : void 0;
|
|
@@ -2220,8 +2223,8 @@ function formatResumeValidationNotice(validation, projectRoot) {
|
|
|
2220
2223
|
if (validation.staleFiles.length === 0) return null;
|
|
2221
2224
|
const root = path7.resolve(projectRoot);
|
|
2222
2225
|
const shown = validation.staleFiles.slice(0, NOTICE_PATH_LIMIT).map((entry) => {
|
|
2223
|
-
const
|
|
2224
|
-
const display = isInside2(root, entry.path) ?
|
|
2226
|
+
const relative8 = path7.relative(root, entry.path);
|
|
2227
|
+
const display = isInside2(root, entry.path) ? relative8 || "." : entry.path;
|
|
2225
2228
|
return `- ${JSON.stringify(display)} [${entry.status}]`;
|
|
2226
2229
|
});
|
|
2227
2230
|
const omitted = validation.staleFiles.length - shown.length;
|
|
@@ -4685,6 +4688,9 @@ function simpleHash(s) {
|
|
|
4685
4688
|
return Math.abs(h).toString(36);
|
|
4686
4689
|
}
|
|
4687
4690
|
|
|
4691
|
+
// src/storage/memory-consolidator.ts
|
|
4692
|
+
import * as path13 from "node:path";
|
|
4693
|
+
|
|
4688
4694
|
// src/utils/instruction-file.ts
|
|
4689
4695
|
import { readFileSync as readFileSync2, statSync as statSync2 } from "node:fs";
|
|
4690
4696
|
import * as path12 from "node:path";
|
|
@@ -4747,6 +4753,12 @@ function toSuperMemoryKind(type) {
|
|
|
4747
4753
|
case "convention":
|
|
4748
4754
|
case "preference":
|
|
4749
4755
|
case "anti_pattern":
|
|
4756
|
+
case "warning":
|
|
4757
|
+
case "workflow":
|
|
4758
|
+
case "bug_root_cause":
|
|
4759
|
+
case "file_note":
|
|
4760
|
+
case "symbol_note":
|
|
4761
|
+
case "command_note":
|
|
4750
4762
|
return type;
|
|
4751
4763
|
case "reference":
|
|
4752
4764
|
return "file_note";
|
|
@@ -4768,7 +4780,7 @@ function importanceFromPriority(priority) {
|
|
|
4768
4780
|
return 0.6;
|
|
4769
4781
|
}
|
|
4770
4782
|
}
|
|
4771
|
-
function buildConsolidationPrompt(finalText, iterations, existingEntries) {
|
|
4783
|
+
function buildConsolidationPrompt(finalText, iterations, existingEntries, evidence) {
|
|
4772
4784
|
const existingBlock = existingEntries.length > 0 ? `
|
|
4773
4785
|
|
|
4774
4786
|
Existing memory entries:
|
|
@@ -4776,9 +4788,82 @@ ${existingEntries.map((e) => `- [${e.ts.slice(0, 10)}] ${e.text}`).join("\n")}`
|
|
|
4776
4788
|
return renderInstructionTemplate(readBundledInstructionText("llm/memory-consolidator.md"), {
|
|
4777
4789
|
iterations: String(iterations),
|
|
4778
4790
|
summary: finalText.slice(0, 3e3),
|
|
4791
|
+
evidence,
|
|
4779
4792
|
existingEntries: existingBlock
|
|
4780
4793
|
});
|
|
4781
4794
|
}
|
|
4795
|
+
function relativeEvidencePath(projectRoot, filePath) {
|
|
4796
|
+
const relative8 = path13.relative(projectRoot, filePath);
|
|
4797
|
+
return relative8 && !relative8.startsWith("..") && !path13.isAbsolute(relative8) ? relative8.replaceAll("\\", "/") : filePath.replaceAll("\\", "/");
|
|
4798
|
+
}
|
|
4799
|
+
function safeCommandEvidence(value) {
|
|
4800
|
+
if (typeof value !== "string") return void 0;
|
|
4801
|
+
const command = value.trim().replace(/\s+/g, " ").slice(0, 300);
|
|
4802
|
+
if (!command) return void 0;
|
|
4803
|
+
if (/(?:password|passwd|secret|token|api[_-]?key|authorization|bearer)\s*[:=]/i.test(command)) {
|
|
4804
|
+
return "[redacted sensitive command]";
|
|
4805
|
+
}
|
|
4806
|
+
return command;
|
|
4807
|
+
}
|
|
4808
|
+
function buildSessionEvidence(ctx) {
|
|
4809
|
+
const projectRoot = ctx.projectRoot ?? ctx.cwd ?? "";
|
|
4810
|
+
const read = [...ctx.readFiles ?? []].slice(-20).map((file) => relativeEvidencePath(projectRoot, file));
|
|
4811
|
+
const written = [...ctx.writtenFiles ?? []].slice(-20).map((file) => relativeEvidencePath(projectRoot, file));
|
|
4812
|
+
const commands = (ctx.sideEffects ?? []).slice(-10).map((effect) => ({
|
|
4813
|
+
tool: effect.toolName,
|
|
4814
|
+
risk: effect.risk,
|
|
4815
|
+
command: safeCommandEvidence(effect.input["command"]),
|
|
4816
|
+
outcome: effect.outcome?.slice(0, 160)
|
|
4817
|
+
}));
|
|
4818
|
+
const completedTodos = (ctx.todos ?? []).filter((todo) => todo.status === "completed").slice(-10).map((todo) => todo.content.slice(0, 240));
|
|
4819
|
+
return JSON.stringify(
|
|
4820
|
+
{
|
|
4821
|
+
projectRoot: projectRoot.replaceAll("\\", "/"),
|
|
4822
|
+
readFiles: read,
|
|
4823
|
+
writtenFiles: written,
|
|
4824
|
+
commands,
|
|
4825
|
+
completedTodos,
|
|
4826
|
+
kanban: ctx.currentKanbanTaskId ? { boardId: ctx.currentKanbanBoardId, taskId: ctx.currentKanbanTaskId } : void 0
|
|
4827
|
+
},
|
|
4828
|
+
null,
|
|
4829
|
+
2
|
|
4830
|
+
).slice(0, 6e3);
|
|
4831
|
+
}
|
|
4832
|
+
function normalizeConsolidatorAnchors(value) {
|
|
4833
|
+
if (!Array.isArray(value)) return void 0;
|
|
4834
|
+
const allowed = /* @__PURE__ */ new Set([
|
|
4835
|
+
"file",
|
|
4836
|
+
"directory",
|
|
4837
|
+
"symbol",
|
|
4838
|
+
"package",
|
|
4839
|
+
"command",
|
|
4840
|
+
"test",
|
|
4841
|
+
"git"
|
|
4842
|
+
]);
|
|
4843
|
+
const anchors = [];
|
|
4844
|
+
for (const raw of value.slice(0, 5)) {
|
|
4845
|
+
if (!raw || typeof raw !== "object") continue;
|
|
4846
|
+
const candidate = raw;
|
|
4847
|
+
if (!allowed.has(candidate["type"])) continue;
|
|
4848
|
+
const anchor = {
|
|
4849
|
+
type: candidate["type"]
|
|
4850
|
+
};
|
|
4851
|
+
if (typeof candidate["path"] === "string" && candidate["path"].trim()) {
|
|
4852
|
+
anchor.path = candidate["path"].trim().slice(0, 500);
|
|
4853
|
+
}
|
|
4854
|
+
if (typeof candidate["symbol"] === "string" && candidate["symbol"].trim()) {
|
|
4855
|
+
anchor.symbol = candidate["symbol"].trim().slice(0, 300);
|
|
4856
|
+
}
|
|
4857
|
+
if (typeof candidate["command"] === "string" && candidate["command"].trim()) {
|
|
4858
|
+
anchor.command = safeCommandEvidence(candidate["command"]);
|
|
4859
|
+
}
|
|
4860
|
+
anchors.push(anchor);
|
|
4861
|
+
}
|
|
4862
|
+
return anchors.length > 0 ? anchors : void 0;
|
|
4863
|
+
}
|
|
4864
|
+
function normalizeConfidence(value) {
|
|
4865
|
+
return typeof value === "number" && Number.isFinite(value) ? Math.max(0.5, Math.min(1, value)) : 0.78;
|
|
4866
|
+
}
|
|
4782
4867
|
var SessionMemoryConsolidator = class {
|
|
4783
4868
|
name = "session-memory-consolidator";
|
|
4784
4869
|
owner = "core";
|
|
@@ -4794,11 +4879,11 @@ var SessionMemoryConsolidator = class {
|
|
|
4794
4879
|
this.superMemory = opts.superMemory;
|
|
4795
4880
|
this.provider = opts.provider;
|
|
4796
4881
|
this.model = opts.model;
|
|
4797
|
-
this.minIterations = opts.minIterations ??
|
|
4882
|
+
this.minIterations = opts.minIterations ?? 1;
|
|
4798
4883
|
this.maxExistingEntries = opts.maxExistingEntries ?? 15;
|
|
4799
4884
|
this.oneShotOrchestrator = opts.oneShotOrchestrator;
|
|
4800
4885
|
}
|
|
4801
|
-
afterRun = (ctx, result) => {
|
|
4886
|
+
afterRun = async (ctx, result) => {
|
|
4802
4887
|
if (result.status !== "done") return;
|
|
4803
4888
|
if (!result.finalText || result.finalText.trim().length < 20) return;
|
|
4804
4889
|
if (result.iterations < this.minIterations) return;
|
|
@@ -4808,87 +4893,85 @@ var SessionMemoryConsolidator = class {
|
|
|
4808
4893
|
const _iterations = result.iterations;
|
|
4809
4894
|
const _model = this.model ?? ctx.model;
|
|
4810
4895
|
const _sessionId = ctx.session.id;
|
|
4811
|
-
|
|
4812
|
-
|
|
4813
|
-
|
|
4814
|
-
|
|
4815
|
-
|
|
4816
|
-
|
|
4817
|
-
|
|
4818
|
-
|
|
4819
|
-
|
|
4820
|
-
|
|
4821
|
-
|
|
4822
|
-
|
|
4823
|
-
|
|
4824
|
-
|
|
4825
|
-
|
|
4826
|
-
|
|
4827
|
-
|
|
4896
|
+
const _evidence = buildSessionEvidence(ctx);
|
|
4897
|
+
try {
|
|
4898
|
+
let existingEntries;
|
|
4899
|
+
if (this.superMemory) {
|
|
4900
|
+
const records = this.superMemory.listSuper ? await this.superMemory.listSuper(["active"]) : await this.superMemory.searchSuper?.("", {
|
|
4901
|
+
limit: this.maxExistingEntries,
|
|
4902
|
+
scope: "project",
|
|
4903
|
+
includeStatuses: ["active"]
|
|
4904
|
+
}) ?? [];
|
|
4905
|
+
existingEntries = toPromptEntries(records, this.maxExistingEntries);
|
|
4906
|
+
} else {
|
|
4907
|
+
existingEntries = await this.memoryStore.list("project-memory", this.maxExistingEntries);
|
|
4908
|
+
}
|
|
4909
|
+
const prompt = buildConsolidationPrompt(_finalText, _iterations, existingEntries, _evidence);
|
|
4910
|
+
let text;
|
|
4911
|
+
if (this.oneShotOrchestrator) {
|
|
4912
|
+
const oneShotResult = await this.oneShotOrchestrator.call({
|
|
4913
|
+
system: prompt,
|
|
4914
|
+
userPrompt: "Review the session and return memory operations as JSON.",
|
|
4915
|
+
model: _model ?? "deepseek-chat",
|
|
4916
|
+
maxTokens: 500,
|
|
4917
|
+
timeoutMs: 15e3
|
|
4918
|
+
});
|
|
4919
|
+
text = oneShotResult.text;
|
|
4920
|
+
} else {
|
|
4921
|
+
const signal = AbortSignal.timeout(15e3);
|
|
4922
|
+
const response = await provider.complete(
|
|
4923
|
+
{
|
|
4924
|
+
model: _model,
|
|
4925
|
+
system: [{ type: "text", text: prompt }],
|
|
4926
|
+
messages: [
|
|
4927
|
+
{ role: "user", content: "Review the session and return memory operations as JSON." }
|
|
4928
|
+
],
|
|
4929
|
+
maxTokens: 500
|
|
4930
|
+
},
|
|
4931
|
+
{ signal }
|
|
4828
4932
|
);
|
|
4829
|
-
|
|
4830
|
-
|
|
4831
|
-
|
|
4832
|
-
|
|
4833
|
-
|
|
4834
|
-
|
|
4835
|
-
|
|
4836
|
-
|
|
4837
|
-
|
|
4838
|
-
|
|
4839
|
-
|
|
4840
|
-
|
|
4841
|
-
const
|
|
4842
|
-
|
|
4843
|
-
|
|
4844
|
-
|
|
4845
|
-
|
|
4846
|
-
|
|
4847
|
-
|
|
4848
|
-
|
|
4849
|
-
|
|
4850
|
-
|
|
4851
|
-
|
|
4852
|
-
|
|
4853
|
-
|
|
4854
|
-
|
|
4855
|
-
|
|
4856
|
-
|
|
4857
|
-
|
|
4858
|
-
|
|
4859
|
-
|
|
4860
|
-
for (const op of parsed.operations) {
|
|
4861
|
-
try {
|
|
4862
|
-
if (!op || typeof op !== "object" || op.action !== "add") continue;
|
|
4863
|
-
if (typeof op.text !== "string" || !op.text.trim()) continue;
|
|
4864
|
-
const memoryText = op.text.trim();
|
|
4865
|
-
if (this.superMemory) {
|
|
4866
|
-
await this.superMemory.rememberSuper({
|
|
4867
|
-
text: memoryText,
|
|
4868
|
-
scope: "project",
|
|
4869
|
-
kind: toSuperMemoryKind(op.type),
|
|
4870
|
-
tags: op.tags,
|
|
4871
|
-
importance: importanceFromPriority(op.priority),
|
|
4872
|
-
sources: [{ type: "session", sessionId: _sessionId }]
|
|
4873
|
-
});
|
|
4874
|
-
} else {
|
|
4875
|
-
await this.memoryStore.remember(memoryText, void 0, {
|
|
4876
|
-
type: op.type,
|
|
4877
|
-
tags: op.tags,
|
|
4878
|
-
priority: op.priority
|
|
4879
|
-
});
|
|
4880
|
-
}
|
|
4881
|
-
added++;
|
|
4882
|
-
} catch {
|
|
4933
|
+
text = response.content.filter((b) => b.type === "text").map((b) => b.text).join("").trim();
|
|
4934
|
+
}
|
|
4935
|
+
if (!text) return;
|
|
4936
|
+
const jsonMatch = text.match(/\{[\s\S]*\}/);
|
|
4937
|
+
if (!jsonMatch) return;
|
|
4938
|
+
const parsed = JSON.parse(jsonMatch[0]);
|
|
4939
|
+
if (!Array.isArray(parsed.operations) || parsed.operations.length === 0) return;
|
|
4940
|
+
let added = 0;
|
|
4941
|
+
for (const op of parsed.operations.slice(0, 5)) {
|
|
4942
|
+
try {
|
|
4943
|
+
if (!op || typeof op !== "object" || op.action !== "add") continue;
|
|
4944
|
+
if (typeof op.text !== "string" || !op.text.trim()) continue;
|
|
4945
|
+
const memoryText = op.text.trim();
|
|
4946
|
+
if (this.superMemory) {
|
|
4947
|
+
await this.superMemory.rememberSuper({
|
|
4948
|
+
text: memoryText,
|
|
4949
|
+
scope: "project",
|
|
4950
|
+
kind: toSuperMemoryKind(op.type),
|
|
4951
|
+
tags: op.tags,
|
|
4952
|
+
importance: importanceFromPriority(op.priority),
|
|
4953
|
+
confidence: normalizeConfidence(op.confidence),
|
|
4954
|
+
persistence: "long_lived",
|
|
4955
|
+
anchors: normalizeConsolidatorAnchors(op.anchors),
|
|
4956
|
+
sources: [{ type: "session", sessionId: _sessionId }]
|
|
4957
|
+
});
|
|
4958
|
+
} else {
|
|
4959
|
+
await this.memoryStore.remember(memoryText, void 0, {
|
|
4960
|
+
type: op.type,
|
|
4961
|
+
tags: op.tags,
|
|
4962
|
+
priority: op.priority
|
|
4963
|
+
});
|
|
4883
4964
|
}
|
|
4965
|
+
added++;
|
|
4966
|
+
} catch {
|
|
4884
4967
|
}
|
|
4885
|
-
|
|
4886
|
-
|
|
4968
|
+
}
|
|
4969
|
+
if (added > 0) {
|
|
4970
|
+
process.stderr.write(`[memory] Session consolidation: ${added} added
|
|
4887
4971
|
`);
|
|
4888
|
-
}
|
|
4889
|
-
} catch {
|
|
4890
4972
|
}
|
|
4891
|
-
}
|
|
4973
|
+
} catch {
|
|
4974
|
+
}
|
|
4892
4975
|
};
|
|
4893
4976
|
};
|
|
4894
4977
|
|
|
@@ -4966,7 +5049,7 @@ function deepFreeze(obj) {
|
|
|
4966
5049
|
// src/storage/provider-config-watcher.ts
|
|
4967
5050
|
import * as syncFs from "node:fs";
|
|
4968
5051
|
import * as fs6 from "node:fs/promises";
|
|
4969
|
-
import * as
|
|
5052
|
+
import * as path14 from "node:path";
|
|
4970
5053
|
|
|
4971
5054
|
// src/security/config-secrets.ts
|
|
4972
5055
|
function decryptConfigSecrets(cfg, vault, opts) {
|
|
@@ -5064,7 +5147,7 @@ function serializeSnapshot(s) {
|
|
|
5064
5147
|
function watchProviderConfig(configPath, vault, onChange, opts = {}) {
|
|
5065
5148
|
const debounceMs = opts.debounceMs ?? 200;
|
|
5066
5149
|
const warn = opts.warn;
|
|
5067
|
-
const base =
|
|
5150
|
+
const base = path14.basename(configPath);
|
|
5068
5151
|
let timer;
|
|
5069
5152
|
let closed = false;
|
|
5070
5153
|
let lastSerialized;
|
|
@@ -5075,7 +5158,7 @@ function watchProviderConfig(configPath, vault, onChange, opts = {}) {
|
|
|
5075
5158
|
});
|
|
5076
5159
|
let watcher;
|
|
5077
5160
|
try {
|
|
5078
|
-
watcher = syncFs.watch(
|
|
5161
|
+
watcher = syncFs.watch(path14.dirname(configPath), { recursive: false });
|
|
5079
5162
|
} catch (err) {
|
|
5080
5163
|
warn?.(`Provider config watcher could not start: ${err.message}`);
|
|
5081
5164
|
return { close: () => {
|
|
@@ -5119,10 +5202,7 @@ function watchProviderConfig(configPath, vault, onChange, opts = {}) {
|
|
|
5119
5202
|
|
|
5120
5203
|
// src/storage/config-loader.ts
|
|
5121
5204
|
import * as fs7 from "node:fs/promises";
|
|
5122
|
-
import * as
|
|
5123
|
-
|
|
5124
|
-
// src/types/config.ts
|
|
5125
|
-
var DEFAULT_TUI_THINKING_WORD = "thinking";
|
|
5205
|
+
import * as path15 from "node:path";
|
|
5126
5206
|
|
|
5127
5207
|
// src/types/context-window.ts
|
|
5128
5208
|
var DEFAULT_CONTEXT_WINDOW_MODE_ID = "frugal";
|
|
@@ -5256,7 +5336,12 @@ var BEHAVIOR_DEFAULTS = {
|
|
|
5256
5336
|
modelsRegistry: true,
|
|
5257
5337
|
skills: true,
|
|
5258
5338
|
prompts: true,
|
|
5259
|
-
|
|
5339
|
+
// 'auto' → resolveTokenSavingTier picks a concrete tier from the model's
|
|
5340
|
+
// context window ONCE per session (cache-safe): lean prompt on small
|
|
5341
|
+
// windows (<32k medium, <96k light) where the fixed identity+tool prose is
|
|
5342
|
+
// a big fraction; full prompt (off) on 96k+ so nothing changes for the
|
|
5343
|
+
// common large-window case. Explicit tiers are respected verbatim.
|
|
5344
|
+
tokenSavingMode: "auto",
|
|
5260
5345
|
allowOutsideProjectRoot: true
|
|
5261
5346
|
},
|
|
5262
5347
|
superMemory: {
|
|
@@ -5266,10 +5351,14 @@ var BEHAVIOR_DEFAULTS = {
|
|
|
5266
5351
|
directory: ".wrongstack/memories"
|
|
5267
5352
|
},
|
|
5268
5353
|
inject: {
|
|
5269
|
-
|
|
5354
|
+
// Keep the ordinary turn prompt clean. Project memory is surfaced
|
|
5355
|
+
// on-demand beside relevant tool results, where the path/query that
|
|
5356
|
+
// caused retrieval is known and the hint can be independently capped.
|
|
5357
|
+
turnContext: false,
|
|
5270
5358
|
toolResults: true,
|
|
5271
|
-
|
|
5272
|
-
|
|
5359
|
+
taskAware: true,
|
|
5360
|
+
maxHintsPerTool: 8,
|
|
5361
|
+
maxCharsPerTool: 2800,
|
|
5273
5362
|
maxTurnMemories: 8,
|
|
5274
5363
|
maxCharsPerTurn: 2400,
|
|
5275
5364
|
minScore: 0.65,
|
|
@@ -5635,7 +5724,7 @@ function stripUnsafeInProjectFields(inProject, sourcePath, warn = (msg) => conso
|
|
|
5635
5724
|
event: "config.in_project_unsafe_fields_ignored",
|
|
5636
5725
|
path: sourcePath,
|
|
5637
5726
|
ignoredKeys: stripped,
|
|
5638
|
-
message: `Ignored ${stripped.length} field(s) from the repo-committed config "${sourcePath}": ${stripped.join(", ")}. Only a small allow-list of benign preferences (model, context, tools limits, features, \u2026) may be set by <project>/.wrongstack/config.json. Everything else must live in your
|
|
5727
|
+
message: `Ignored ${stripped.length} field(s) from the repo-committed config "${sourcePath}": ${stripped.join(", ")}. Only a small allow-list of benign preferences (model, context, tools limits, features, \u2026) may be set by <project>/.wrongstack/config.json. Everything else must live in your active profile config.`,
|
|
5639
5728
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
5640
5729
|
})
|
|
5641
5730
|
);
|
|
@@ -5643,8 +5732,8 @@ function stripUnsafeInProjectFields(inProject, sourcePath, warn = (msg) => conso
|
|
|
5643
5732
|
return out;
|
|
5644
5733
|
}
|
|
5645
5734
|
function samePath(a, b) {
|
|
5646
|
-
let ra =
|
|
5647
|
-
let rb =
|
|
5735
|
+
let ra = path15.resolve(a);
|
|
5736
|
+
let rb = path15.resolve(b);
|
|
5648
5737
|
if (process.platform === "win32" || process.platform === "darwin") {
|
|
5649
5738
|
ra = ra.toLowerCase();
|
|
5650
5739
|
rb = rb.toLowerCase();
|
|
@@ -5705,11 +5794,18 @@ var DefaultConfigLoader = class _DefaultConfigLoader {
|
|
|
5705
5794
|
this.readJson(this.paths.projectLocalConfig),
|
|
5706
5795
|
inProjectCollides ? Promise.resolve({}) : this.readJson(this.paths.inProjectConfig)
|
|
5707
5796
|
]);
|
|
5708
|
-
|
|
5797
|
+
const bootstrapMetadata = {
|
|
5798
|
+
...typeof bootstrap.version === "number" ? { version: bootstrap.version } : {},
|
|
5799
|
+
...typeof bootstrap.activeProfile === "string" ? { activeProfile: bootstrap.activeProfile } : {}
|
|
5800
|
+
};
|
|
5801
|
+
cfg = deepMerge2(cfg, bootstrapMetadata);
|
|
5709
5802
|
const profileName = bootstrap.activeProfile ?? "default";
|
|
5710
5803
|
const profileCfg = await this.readJson(this.paths.profileConfig(profileName));
|
|
5711
5804
|
if (Object.keys(profileCfg).length > 0) {
|
|
5712
|
-
|
|
5805
|
+
const profileSettings = { ...profileCfg };
|
|
5806
|
+
delete profileSettings["version"];
|
|
5807
|
+
delete profileSettings["activeProfile"];
|
|
5808
|
+
cfg = deepMerge2(cfg, profileSettings);
|
|
5713
5809
|
}
|
|
5714
5810
|
cfg = deepMerge2(cfg, local);
|
|
5715
5811
|
cfg = deepMerge2(
|
|
@@ -5815,6 +5911,7 @@ var DefaultConfigLoader = class _DefaultConfigLoader {
|
|
|
5815
5911
|
fileExisted = false;
|
|
5816
5912
|
parsed = {};
|
|
5817
5913
|
}
|
|
5914
|
+
if (parsed["version"] !== void 0 && parsed["version"] !== 1) return;
|
|
5818
5915
|
const profileName = parsed.activeProfile ?? "default";
|
|
5819
5916
|
const profileFp = this.paths.profileConfig(profileName);
|
|
5820
5917
|
await this.ensureProfileConfig(profileFp, parsed, fileExisted);
|
|
@@ -5871,11 +5968,9 @@ var DefaultConfigLoader = class _DefaultConfigLoader {
|
|
|
5871
5968
|
* with behavior defaults. Subsequent boots: fill any missing keys from
|
|
5872
5969
|
* BEHAVIOR_DEFAULTS (keeping user settings intact).
|
|
5873
5970
|
*
|
|
5874
|
-
*
|
|
5875
|
-
*
|
|
5876
|
-
*
|
|
5877
|
-
* the profile so they are NOT silently destroyed by the subsequent bootstrap
|
|
5878
|
-
* trim. This prevents the "config keeps getting emptied" bug.
|
|
5971
|
+
* Once a profile file exists, settings found in the root bootstrap are
|
|
5972
|
+
* ignored. This prevents stale or accidentally-written root values from
|
|
5973
|
+
* becoming a hidden settings source alongside profiles.
|
|
5879
5974
|
*/
|
|
5880
5975
|
async ensureProfileConfig(profileFp, oldGlobalParsed, oldFileExisted) {
|
|
5881
5976
|
const t0 = Date.now();
|
|
@@ -5905,42 +6000,13 @@ var DefaultConfigLoader = class _DefaultConfigLoader {
|
|
|
5905
6000
|
existed = false;
|
|
5906
6001
|
parsed = {};
|
|
5907
6002
|
}
|
|
5908
|
-
const profileHasContent = existed && Object.keys(parsed).some((k) => k !== "version" && k !== "activeProfile");
|
|
5909
6003
|
let seed;
|
|
5910
|
-
if (!
|
|
6004
|
+
if (!existed && oldFileExisted && Object.keys(oldGlobalParsed).length >= 1) {
|
|
5911
6005
|
seed = { ...oldGlobalParsed };
|
|
5912
6006
|
delete seed["activeProfile"];
|
|
5913
6007
|
const filled = fillMissingDefaults(seed, BEHAVIOR_DEFAULTS);
|
|
5914
6008
|
seed = filled.value;
|
|
5915
6009
|
} else {
|
|
5916
|
-
if (existed && !_DefaultConfigLoader.isBootstrapOnly(oldGlobalParsed)) {
|
|
5917
|
-
const BOOTSTRAP_KEYS = /* @__PURE__ */ new Set(["version", "activeProfile"]);
|
|
5918
|
-
let merged = false;
|
|
5919
|
-
for (const [k, v] of Object.entries(oldGlobalParsed)) {
|
|
5920
|
-
if (!BOOTSTRAP_KEYS.has(k) && !(k in parsed)) {
|
|
5921
|
-
parsed[k] = v;
|
|
5922
|
-
merged = true;
|
|
5923
|
-
}
|
|
5924
|
-
}
|
|
5925
|
-
if (merged) {
|
|
5926
|
-
const filled2 = fillMissingDefaults(parsed, BEHAVIOR_DEFAULTS);
|
|
5927
|
-
if (filled2.changed) {
|
|
5928
|
-
seed = filled2.value;
|
|
5929
|
-
await backupConfigFile(profileFp, this.paths);
|
|
5930
|
-
await atomicWrite(profileFp, JSON.stringify(seed, null, 2), { mode: 384 });
|
|
5931
|
-
this.events?.emit("storage.write", {
|
|
5932
|
-
sessionId: "~config~",
|
|
5933
|
-
store: "config",
|
|
5934
|
-
filePath: profileFp,
|
|
5935
|
-
operation: "ensure_profile_defaults",
|
|
5936
|
-
outcome: "success",
|
|
5937
|
-
durationMs: Date.now() - t0,
|
|
5938
|
-
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
5939
|
-
});
|
|
5940
|
-
return;
|
|
5941
|
-
}
|
|
5942
|
-
}
|
|
5943
|
-
}
|
|
5944
6010
|
const filled = fillMissingDefaults(parsed, BEHAVIOR_DEFAULTS);
|
|
5945
6011
|
if (!filled.changed) return;
|
|
5946
6012
|
seed = filled.value;
|
|
@@ -6260,7 +6326,7 @@ var DEFAULT_CONFIG_MIGRATIONS = [];
|
|
|
6260
6326
|
// src/storage/recovery-lock.ts
|
|
6261
6327
|
import * as fsp7 from "node:fs/promises";
|
|
6262
6328
|
import * as os2 from "node:os";
|
|
6263
|
-
import * as
|
|
6329
|
+
import * as path16 from "node:path";
|
|
6264
6330
|
var LOCK_FILE = "active.json";
|
|
6265
6331
|
var DEFAULT_MAX_AGE_MS = 24 * 60 * 60 * 1e3;
|
|
6266
6332
|
var RecoveryLock = class {
|
|
@@ -6271,7 +6337,7 @@ var RecoveryLock = class {
|
|
|
6271
6337
|
sessionStore;
|
|
6272
6338
|
probe;
|
|
6273
6339
|
constructor(opts) {
|
|
6274
|
-
this.file =
|
|
6340
|
+
this.file = path16.join(opts.dir, LOCK_FILE);
|
|
6275
6341
|
this.pid = opts.pid ?? process.pid;
|
|
6276
6342
|
this.hostname = opts.hostname ?? os2.hostname();
|
|
6277
6343
|
this.maxAgeMs = opts.maxAgeMs ?? DEFAULT_MAX_AGE_MS;
|
|
@@ -6332,7 +6398,7 @@ var RecoveryLock = class {
|
|
|
6332
6398
|
* null return before calling this.
|
|
6333
6399
|
*/
|
|
6334
6400
|
async write(sessionId) {
|
|
6335
|
-
await ensureDir(
|
|
6401
|
+
await ensureDir(path16.dirname(this.file));
|
|
6336
6402
|
const lock = {
|
|
6337
6403
|
v: 1,
|
|
6338
6404
|
sessionId,
|
|
@@ -7047,7 +7113,7 @@ var AnnotationsStore = class {
|
|
|
7047
7113
|
|
|
7048
7114
|
// src/storage/replay-log-store.ts
|
|
7049
7115
|
import * as fs10 from "node:fs/promises";
|
|
7050
|
-
import * as
|
|
7116
|
+
import * as path17 from "node:path";
|
|
7051
7117
|
|
|
7052
7118
|
// src/replay/hash.ts
|
|
7053
7119
|
import { createHash as createHash5 } from "node:crypto";
|
|
@@ -7285,13 +7351,13 @@ var ReplayLogStore = class {
|
|
|
7285
7351
|
for (const entry of entries) {
|
|
7286
7352
|
if (entry.name.startsWith(".")) continue;
|
|
7287
7353
|
if (entry.isDirectory()) {
|
|
7288
|
-
if (depth === 0) await scan(
|
|
7354
|
+
if (depth === 0) await scan(path17.join(dir, entry.name), entry.name, depth + 1);
|
|
7289
7355
|
continue;
|
|
7290
7356
|
}
|
|
7291
7357
|
if (!entry.isFile() || !entry.name.endsWith(".replay.jsonl")) continue;
|
|
7292
7358
|
const base = entry.name.slice(0, -".replay.jsonl".length);
|
|
7293
7359
|
const sessionId = prefix ? `${prefix}/${base}` : base;
|
|
7294
|
-
const fp =
|
|
7360
|
+
const fp = path17.join(dir, entry.name);
|
|
7295
7361
|
out.push({
|
|
7296
7362
|
sessionId,
|
|
7297
7363
|
entryCount: await this.countEntries(fp),
|
|
@@ -7463,7 +7529,7 @@ var ReplayLogStore = class {
|
|
|
7463
7529
|
|
|
7464
7530
|
// src/storage/session-recovery.ts
|
|
7465
7531
|
import * as fs11 from "node:fs/promises";
|
|
7466
|
-
import * as
|
|
7532
|
+
import * as path18 from "node:path";
|
|
7467
7533
|
var SessionRecovery = class {
|
|
7468
7534
|
constructor(dir) {
|
|
7469
7535
|
this.dir = dir;
|
|
@@ -7578,7 +7644,7 @@ var SessionRecovery = class {
|
|
|
7578
7644
|
continue;
|
|
7579
7645
|
if (entry.isDirectory()) {
|
|
7580
7646
|
if (depth === 0) {
|
|
7581
|
-
await collect(
|
|
7647
|
+
await collect(path18.join(dir, entry.name), entry.name, depth + 1);
|
|
7582
7648
|
}
|
|
7583
7649
|
continue;
|
|
7584
7650
|
}
|
|
@@ -8097,7 +8163,7 @@ var SessionAnalyzer = class {
|
|
|
8097
8163
|
// src/session-registry.ts
|
|
8098
8164
|
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
8099
8165
|
import * as fs13 from "node:fs/promises";
|
|
8100
|
-
import * as
|
|
8166
|
+
import * as path19 from "node:path";
|
|
8101
8167
|
var REGISTRY_FILE = "session-registry.json";
|
|
8102
8168
|
var HEARTBEAT_INTERVAL_MS = 5e3;
|
|
8103
8169
|
var STALE_TIMEOUT_MS = 3e4;
|
|
@@ -8136,7 +8202,7 @@ var SessionRegistry = class {
|
|
|
8136
8202
|
*/
|
|
8137
8203
|
lastEntry = null;
|
|
8138
8204
|
constructor(globalRoot) {
|
|
8139
|
-
this.filePath =
|
|
8205
|
+
this.filePath = path19.join(globalRoot, REGISTRY_FILE);
|
|
8140
8206
|
}
|
|
8141
8207
|
// ── Public API ──────────────────────────────────────────────────────────
|
|
8142
8208
|
/**
|
|
@@ -8345,7 +8411,7 @@ var SessionRegistry = class {
|
|
|
8345
8411
|
const retryDelayMs = 20;
|
|
8346
8412
|
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
8347
8413
|
try {
|
|
8348
|
-
await fs13.mkdir(
|
|
8414
|
+
await fs13.mkdir(path19.dirname(this.filePath), { recursive: true });
|
|
8349
8415
|
let lockHandle = await fs13.open(lockPath, "wx").catch(() => null);
|
|
8350
8416
|
if (!lockHandle) {
|
|
8351
8417
|
if (await this.breakStaleLock(lockPath)) {
|
|
@@ -8409,9 +8475,9 @@ var SessionRegistry = class {
|
|
|
8409
8475
|
await this.writeAtomicFile(registry);
|
|
8410
8476
|
}
|
|
8411
8477
|
async writeAtomicFile(registry) {
|
|
8412
|
-
const tmp =
|
|
8413
|
-
|
|
8414
|
-
`.${
|
|
8478
|
+
const tmp = path19.join(
|
|
8479
|
+
path19.dirname(this.filePath),
|
|
8480
|
+
`.${path19.basename(this.filePath)}.${randomUUID5().slice(0, 8)}.tmp`
|
|
8415
8481
|
);
|
|
8416
8482
|
try {
|
|
8417
8483
|
const handle = await fs13.open(tmp, "w");
|
|
@@ -8429,21 +8495,21 @@ var SessionRegistry = class {
|
|
|
8429
8495
|
}
|
|
8430
8496
|
async pruneStaleTempFiles() {
|
|
8431
8497
|
try {
|
|
8432
|
-
const dir =
|
|
8433
|
-
const base =
|
|
8498
|
+
const dir = path19.dirname(this.filePath);
|
|
8499
|
+
const base = path19.basename(this.filePath);
|
|
8434
8500
|
const now = Date.now();
|
|
8435
8501
|
const stale = [];
|
|
8436
8502
|
for (const name of await fs13.readdir(dir)) {
|
|
8437
8503
|
const isTemp = (name.startsWith(`${base}.`) || name.startsWith(`.${base}.`)) && name.endsWith(".tmp");
|
|
8438
8504
|
if (!isTemp) continue;
|
|
8439
|
-
const stat13 = await fs13.stat(
|
|
8505
|
+
const stat13 = await fs13.stat(path19.join(dir, name)).catch(() => null);
|
|
8440
8506
|
if (!stat13) continue;
|
|
8441
8507
|
if (now - stat13.mtimeMs > STALE_TMP_MS) stale.push({ name, mtimeMs: stat13.mtimeMs });
|
|
8442
8508
|
}
|
|
8443
8509
|
stale.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
8444
8510
|
await Promise.all(
|
|
8445
8511
|
stale.slice(MAX_STALE_TMP_FILES).map(async ({ name }) => {
|
|
8446
|
-
await fs13.unlink(
|
|
8512
|
+
await fs13.unlink(path19.join(dir, name)).catch(() => void 0);
|
|
8447
8513
|
})
|
|
8448
8514
|
);
|
|
8449
8515
|
} catch {
|
|
@@ -9179,7 +9245,7 @@ var AgentStatusTracker = class {
|
|
|
9179
9245
|
|
|
9180
9246
|
// src/fleet-notifier.ts
|
|
9181
9247
|
import * as fs14 from "node:fs/promises";
|
|
9182
|
-
import * as
|
|
9248
|
+
import * as path20 from "node:path";
|
|
9183
9249
|
var INSTANCES_FILE = "webui-instances.json";
|
|
9184
9250
|
var DISCOVERY_TTL_MS = 2500;
|
|
9185
9251
|
var COALESCE_MS = 50;
|
|
@@ -9194,7 +9260,7 @@ function pidAlive2(pid) {
|
|
|
9194
9260
|
}
|
|
9195
9261
|
}
|
|
9196
9262
|
function normRoot(root) {
|
|
9197
|
-
const resolved =
|
|
9263
|
+
const resolved = path20.resolve(root);
|
|
9198
9264
|
return process.platform === "win32" ? resolved.toLowerCase() : resolved;
|
|
9199
9265
|
}
|
|
9200
9266
|
var FleetNotifier = class {
|
|
@@ -9241,7 +9307,7 @@ var FleetNotifier = class {
|
|
|
9241
9307
|
}
|
|
9242
9308
|
async discover() {
|
|
9243
9309
|
try {
|
|
9244
|
-
const raw = await fs14.readFile(
|
|
9310
|
+
const raw = await fs14.readFile(path20.join(this.baseDir, INSTANCES_FILE), "utf8");
|
|
9245
9311
|
const data = JSON.parse(raw);
|
|
9246
9312
|
const list = Array.isArray(data?.instances) ? data.instances : [];
|
|
9247
9313
|
return list.filter((i) => i && typeof i.httpPort === "number").filter((i) => i.pid !== this.selfPid).filter((i) => normRoot(i.projectRoot) === this.projectRoot).filter((i) => pidAlive2(i.pid)).map((i) => {
|
|
@@ -9262,7 +9328,7 @@ async function defaultPost(url) {
|
|
|
9262
9328
|
|
|
9263
9329
|
// src/storage/session-rewinder.ts
|
|
9264
9330
|
import * as fsp8 from "node:fs/promises";
|
|
9265
|
-
import * as
|
|
9331
|
+
import * as path21 from "node:path";
|
|
9266
9332
|
var DefaultSessionRewinder = class {
|
|
9267
9333
|
constructor(sessionsDir, projectRoot) {
|
|
9268
9334
|
this.sessionsDir = sessionsDir;
|
|
@@ -9400,10 +9466,10 @@ async function revertSnapshots(snapshots, projectRoot) {
|
|
|
9400
9466
|
for (const snapshot of [...snapshots].reverse()) {
|
|
9401
9467
|
for (const file of [...snapshot.files].reverse()) {
|
|
9402
9468
|
try {
|
|
9403
|
-
const absPath =
|
|
9404
|
-
const root =
|
|
9405
|
-
const rel =
|
|
9406
|
-
if (rel.startsWith("..") ||
|
|
9469
|
+
const absPath = path21.resolve(file.path);
|
|
9470
|
+
const root = path21.resolve(projectRoot);
|
|
9471
|
+
const rel = path21.relative(root, absPath);
|
|
9472
|
+
if (rel.startsWith("..") || path21.isAbsolute(rel)) {
|
|
9407
9473
|
errors.push(`${file.path}: path resolves outside project root \u2014 skipping`);
|
|
9408
9474
|
continue;
|
|
9409
9475
|
}
|
|
@@ -10929,7 +10995,7 @@ function normalizeTitle(value) {
|
|
|
10929
10995
|
// src/storage/prompt-store.ts
|
|
10930
10996
|
import { createHash as createHash7 } from "node:crypto";
|
|
10931
10997
|
import * as fs15 from "node:fs/promises";
|
|
10932
|
-
import * as
|
|
10998
|
+
import * as path22 from "node:path";
|
|
10933
10999
|
var SCHEMA_VERSION = 2;
|
|
10934
11000
|
function promptChecksum(content) {
|
|
10935
11001
|
return createHash7("sha256").update(content, "utf8").digest("hex");
|
|
@@ -10998,7 +11064,7 @@ var DefaultPromptStore = class {
|
|
|
10998
11064
|
if (!file.endsWith(".json")) continue;
|
|
10999
11065
|
try {
|
|
11000
11066
|
const raw = JSON.parse(
|
|
11001
|
-
await fs15.readFile(
|
|
11067
|
+
await fs15.readFile(path22.join(this.dir, file), "utf8")
|
|
11002
11068
|
);
|
|
11003
11069
|
const migrated = migratePromptEntry(raw.entry);
|
|
11004
11070
|
if (migrated) entries.push(migrated);
|
|
@@ -11012,7 +11078,7 @@ var DefaultPromptStore = class {
|
|
|
11012
11078
|
);
|
|
11013
11079
|
}
|
|
11014
11080
|
async get(id) {
|
|
11015
|
-
const file =
|
|
11081
|
+
const file = path22.join(this.dir, `${id}.json`);
|
|
11016
11082
|
try {
|
|
11017
11083
|
const raw = JSON.parse(await fs15.readFile(file, "utf8"));
|
|
11018
11084
|
return migratePromptEntry(raw.entry);
|
|
@@ -11022,12 +11088,12 @@ var DefaultPromptStore = class {
|
|
|
11022
11088
|
}
|
|
11023
11089
|
async save(entry) {
|
|
11024
11090
|
await ensureDir(this.dir);
|
|
11025
|
-
const file =
|
|
11091
|
+
const file = path22.join(this.dir, `${entry.id}.json`);
|
|
11026
11092
|
const raw = { version: SCHEMA_VERSION, entry };
|
|
11027
11093
|
await atomicWrite(file, JSON.stringify(raw, null, 2));
|
|
11028
11094
|
}
|
|
11029
11095
|
async delete(id) {
|
|
11030
|
-
const file =
|
|
11096
|
+
const file = path22.join(this.dir, `${id}.json`);
|
|
11031
11097
|
try {
|
|
11032
11098
|
await fs15.unlink(file);
|
|
11033
11099
|
return true;
|
|
@@ -11069,7 +11135,7 @@ var DefaultPromptStore = class {
|
|
|
11069
11135
|
|
|
11070
11136
|
// src/storage/prompt-usage-store.ts
|
|
11071
11137
|
import * as fs16 from "node:fs/promises";
|
|
11072
|
-
import * as
|
|
11138
|
+
import * as path23 from "node:path";
|
|
11073
11139
|
var PromptUsageStore = class {
|
|
11074
11140
|
constructor(file) {
|
|
11075
11141
|
this.file = file;
|
|
@@ -11090,7 +11156,7 @@ var PromptUsageStore = class {
|
|
|
11090
11156
|
const prev = usage[slug];
|
|
11091
11157
|
const next = { count: (prev?.count ?? 0) + 1, lastUsedAt: at };
|
|
11092
11158
|
usage[slug] = next;
|
|
11093
|
-
await ensureDir(
|
|
11159
|
+
await ensureDir(path23.dirname(this.file));
|
|
11094
11160
|
await atomicWrite(this.file, JSON.stringify({ version: 1, usage }, null, 2));
|
|
11095
11161
|
return next;
|
|
11096
11162
|
}
|
|
@@ -11115,7 +11181,7 @@ var PromptUsageStore = class {
|
|
|
11115
11181
|
|
|
11116
11182
|
// src/storage/input-history-store.ts
|
|
11117
11183
|
import * as fs17 from "node:fs/promises";
|
|
11118
|
-
import * as
|
|
11184
|
+
import * as path24 from "node:path";
|
|
11119
11185
|
var INPUT_HISTORY_DEFAULT_MAX = 100;
|
|
11120
11186
|
var InputHistoryStore = class {
|
|
11121
11187
|
/**
|
|
@@ -11154,7 +11220,7 @@ var InputHistoryStore = class {
|
|
|
11154
11220
|
*/
|
|
11155
11221
|
async save(entries) {
|
|
11156
11222
|
const cleaned = this.scrubAndFilter(entries);
|
|
11157
|
-
await ensureDir(
|
|
11223
|
+
await ensureDir(path24.dirname(this.file));
|
|
11158
11224
|
const payload = {
|
|
11159
11225
|
version: 1,
|
|
11160
11226
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -11164,7 +11230,7 @@ var InputHistoryStore = class {
|
|
|
11164
11230
|
}
|
|
11165
11231
|
/** Truncate the file to an empty entry list (used by /clear). */
|
|
11166
11232
|
async clear() {
|
|
11167
|
-
await ensureDir(
|
|
11233
|
+
await ensureDir(path24.dirname(this.file));
|
|
11168
11234
|
const payload = { version: 1, updatedAt: (/* @__PURE__ */ new Date()).toISOString(), entries: [] };
|
|
11169
11235
|
await atomicWrite(this.file, JSON.stringify(payload, null, 2));
|
|
11170
11236
|
}
|
|
@@ -11195,7 +11261,7 @@ var InputHistoryStore = class {
|
|
|
11195
11261
|
|
|
11196
11262
|
// src/storage/cloud-sync.ts
|
|
11197
11263
|
import * as fs18 from "node:fs/promises";
|
|
11198
|
-
import * as
|
|
11264
|
+
import * as path25 from "node:path";
|
|
11199
11265
|
import { createHash as createHash8 } from "node:crypto";
|
|
11200
11266
|
var ALL_SYNC_CATEGORIES = ["settings", "skills", "prompts", "memory", "history"];
|
|
11201
11267
|
var CloudSync = class {
|
|
@@ -11203,7 +11269,7 @@ var CloudSync = class {
|
|
|
11203
11269
|
this.paths = paths;
|
|
11204
11270
|
this.getConfig = getConfig;
|
|
11205
11271
|
this.setConfig = setConfig;
|
|
11206
|
-
this.statePath =
|
|
11272
|
+
this.statePath = path25.join(paths.globalRoot, "sync-state.json");
|
|
11207
11273
|
}
|
|
11208
11274
|
paths;
|
|
11209
11275
|
getConfig;
|
|
@@ -11310,7 +11376,7 @@ var CloudSync = class {
|
|
|
11310
11376
|
const rel = segments.slice(2).join("/");
|
|
11311
11377
|
const destPath = resolvePulledCategoryPath(cat, localPath, rel, entry.path);
|
|
11312
11378
|
const blobData = await this.getBlob(token, owner, repoName, entry.sha);
|
|
11313
|
-
await fs18.mkdir(
|
|
11379
|
+
await fs18.mkdir(path25.dirname(destPath), { recursive: true });
|
|
11314
11380
|
await fs18.writeFile(destPath, Buffer.from(blobData, "base64"));
|
|
11315
11381
|
}
|
|
11316
11382
|
const localRev = await this.hashLocalCategories(cfg.categories);
|
|
@@ -11422,7 +11488,7 @@ var CloudSync = class {
|
|
|
11422
11488
|
const files = await this.walkDir(localPath, localPath);
|
|
11423
11489
|
for (const file of files) {
|
|
11424
11490
|
const content = await fs18.readFile(file, "utf8");
|
|
11425
|
-
const rel =
|
|
11491
|
+
const rel = path25.relative(localPath, file).replace(/\\/g, "/");
|
|
11426
11492
|
entries.push({ path: `data/${cat}/${rel}`, content, mode: "100644" });
|
|
11427
11493
|
hashes.push(content);
|
|
11428
11494
|
}
|
|
@@ -11480,7 +11546,7 @@ var CloudSync = class {
|
|
|
11480
11546
|
const results = [];
|
|
11481
11547
|
const entries = await fs18.readdir(dir, { withFileTypes: true });
|
|
11482
11548
|
for (const entry of entries) {
|
|
11483
|
-
const full =
|
|
11549
|
+
const full = path25.join(dir, entry.name);
|
|
11484
11550
|
if (entry.isDirectory()) {
|
|
11485
11551
|
results.push(...await this.walkDir(full, base));
|
|
11486
11552
|
} else {
|
|
@@ -11502,9 +11568,9 @@ function resolvePulledCategoryPath(cat, localPath, rel, remotePath) {
|
|
|
11502
11568
|
return localPath;
|
|
11503
11569
|
}
|
|
11504
11570
|
if (!rel) return localPath;
|
|
11505
|
-
const normalizedRel =
|
|
11506
|
-
const traversesUp = normalizedRel === ".." || normalizedRel.startsWith(`..${
|
|
11507
|
-
if (
|
|
11571
|
+
const normalizedRel = path25.normalize(rel);
|
|
11572
|
+
const traversesUp = normalizedRel === ".." || normalizedRel.startsWith(`..${path25.sep}`);
|
|
11573
|
+
if (path25.isAbsolute(normalizedRel) || traversesUp) {
|
|
11508
11574
|
throw new FsError({
|
|
11509
11575
|
message: `Refusing CloudSync path traversal: ${remotePath}`,
|
|
11510
11576
|
code: ERROR_CODES.FS_DELETE_FAILED,
|
|
@@ -11512,10 +11578,10 @@ function resolvePulledCategoryPath(cat, localPath, rel, remotePath) {
|
|
|
11512
11578
|
context: { reason: "path_traversal", normalizedRel }
|
|
11513
11579
|
});
|
|
11514
11580
|
}
|
|
11515
|
-
const dest =
|
|
11516
|
-
const root =
|
|
11517
|
-
const
|
|
11518
|
-
if (
|
|
11581
|
+
const dest = path25.resolve(localPath, normalizedRel);
|
|
11582
|
+
const root = path25.resolve(localPath);
|
|
11583
|
+
const relative8 = path25.relative(root, dest);
|
|
11584
|
+
if (relative8.startsWith("..") || path25.isAbsolute(relative8)) {
|
|
11519
11585
|
throw new FsError({
|
|
11520
11586
|
message: `Refusing CloudSync path outside category root: ${remotePath}`,
|
|
11521
11587
|
code: ERROR_CODES.FS_DELETE_FAILED,
|