@wrongstack/core 0.289.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/domain-adapter.d.ts.map +1 -1
- package/dist/chronicle/index.d.ts +1 -1
- package/dist/chronicle/index.d.ts.map +1 -1
- package/dist/chronicle/index.js +964 -245
- package/dist/chronicle/index.js.map +3 -3
- package/dist/chronicle/journal.d.ts +35 -3
- package/dist/chronicle/journal.d.ts.map +1 -1
- package/dist/chronicle/query.d.ts +8 -5
- package/dist/chronicle/query.d.ts.map +1 -1
- package/dist/chronicle/tool-adapter.d.ts.map +1 -1
- package/dist/coordination/agent-subagent-runner.d.ts +6 -0
- package/dist/coordination/agent-subagent-runner.d.ts.map +1 -1
- package/dist/coordination/agents/phase6-domain.d.ts.map +1 -1
- package/dist/coordination/brain.d.ts +2 -1
- package/dist/coordination/brain.d.ts.map +1 -1
- package/dist/coordination/collab-debug.d.ts +0 -9
- package/dist/coordination/collab-debug.d.ts.map +1 -1
- package/dist/coordination/coordinator/error-classifier.d.ts.map +1 -1
- package/dist/coordination/dep-watcher.d.ts +2 -1
- package/dist/coordination/dep-watcher.d.ts.map +1 -1
- package/dist/coordination/director/director-collab.d.ts +1 -1
- package/dist/coordination/director/director-collab.d.ts.map +1 -1
- package/dist/coordination/director-tools.d.ts.map +1 -1
- package/dist/coordination/director.d.ts +19 -1
- package/dist/coordination/director.d.ts.map +1 -1
- package/dist/coordination/fleet-event-validation.d.ts +0 -7
- package/dist/coordination/fleet-event-validation.d.ts.map +1 -1
- package/dist/coordination/fleet-spawn.d.ts.map +1 -1
- package/dist/coordination/fleet-supervisor.d.ts +1 -1
- package/dist/coordination/fleet-supervisor.d.ts.map +1 -1
- package/dist/coordination/fleet.d.ts +0 -44
- package/dist/coordination/fleet.d.ts.map +1 -1
- package/dist/coordination/index.d.ts +2 -2
- package/dist/coordination/index.d.ts.map +1 -1
- package/dist/coordination/index.js +733 -111
- package/dist/coordination/index.js.map +4 -4
- package/dist/coordination/knowledge-graph.d.ts +2 -1
- package/dist/coordination/knowledge-graph.d.ts.map +1 -1
- package/dist/coordination/large-answer-store.d.ts +0 -6
- package/dist/coordination/large-answer-store.d.ts.map +1 -1
- package/dist/coordination/mail-tools.d.ts.map +1 -1
- package/dist/coordination/mailbox-http-router.d.ts.map +1 -1
- package/dist/coordination/mailbox-message-codec.d.ts +35 -0
- package/dist/coordination/mailbox-message-codec.d.ts.map +1 -1
- package/dist/coordination/mailbox-tool.d.ts +1 -1
- package/dist/coordination/mailbox-tool.d.ts.map +1 -1
- package/dist/coordination/mailbox-types.d.ts +151 -0
- package/dist/coordination/mailbox-types.d.ts.map +1 -1
- package/dist/coordination/provider-status-tracker.d.ts +27 -2
- package/dist/coordination/provider-status-tracker.d.ts.map +1 -1
- package/dist/coordination/subagent-budget.d.ts +2 -1
- package/dist/coordination/subagent-budget.d.ts.map +1 -1
- package/dist/core/agent-response.d.ts +2 -1
- package/dist/core/agent-response.d.ts.map +1 -1
- package/dist/core/agent-tools.d.ts.map +1 -1
- package/dist/core/context.d.ts +51 -0
- package/dist/core/context.d.ts.map +1 -1
- package/dist/core/fallback-model.d.ts +1 -1
- package/dist/core/fallback-model.d.ts.map +1 -1
- package/dist/core/fallback-profile-manager.d.ts +1 -1
- package/dist/core/fallback-profile-manager.d.ts.map +1 -1
- package/dist/core/fleet-pulse.d.ts +2 -1
- package/dist/core/fleet-pulse.d.ts.map +1 -1
- package/dist/core/iteration-limit.d.ts +2 -1
- package/dist/core/iteration-limit.d.ts.map +1 -1
- package/dist/core/mailbox-loop.d.ts +11 -1
- package/dist/core/mailbox-loop.d.ts.map +1 -1
- package/dist/core/model-availability-calendar.d.ts +27 -0
- package/dist/core/model-availability-calendar.d.ts.map +1 -0
- package/dist/core/system-prompt-builder.d.ts +2 -1
- package/dist/core/system-prompt-builder.d.ts.map +1 -1
- package/dist/defaults/index.js +1459 -560
- package/dist/defaults/index.js.map +4 -4
- package/dist/execution/auto-compaction-middleware.d.ts +3 -2
- package/dist/execution/auto-compaction-middleware.d.ts.map +1 -1
- package/dist/execution/enhance-recovery.d.ts +8 -0
- package/dist/execution/enhance-recovery.d.ts.map +1 -1
- package/dist/execution/error-handler.d.ts +2 -2
- package/dist/execution/error-handler.d.ts.map +1 -1
- package/dist/execution/index.js +643 -134
- package/dist/execution/index.js.map +4 -4
- package/dist/execution/intelligent-compactor.d.ts +0 -16
- package/dist/execution/intelligent-compactor.d.ts.map +1 -1
- package/dist/execution/one-shot-llm.d.ts.map +1 -1
- package/dist/execution/retry-policy.d.ts +0 -13
- package/dist/execution/retry-policy.d.ts.map +1 -1
- package/dist/execution/skill-loader.d.ts.map +1 -1
- package/dist/execution/tool-executor.d.ts.map +1 -1
- package/dist/hq/auth-store.d.ts.map +1 -1
- package/dist/hq/index.js +39 -1
- package/dist/hq/index.js.map +3 -3
- package/dist/index.d.ts +32 -30
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +22263 -19999
- package/dist/index.js.map +4 -4
- package/dist/infrastructure/index.js +59 -5
- package/dist/infrastructure/index.js.map +2 -2
- package/dist/kernel/events/agent-events.d.ts +2 -0
- package/dist/kernel/events/agent-events.d.ts.map +1 -1
- package/dist/kernel/events/file-events.d.ts +46 -0
- package/dist/kernel/events/file-events.d.ts.map +1 -1
- package/dist/kernel/events/memory-events.d.ts +9 -0
- package/dist/kernel/events/memory-events.d.ts.map +1 -1
- package/dist/kernel/events/provider-events.d.ts +2 -1
- package/dist/kernel/events/provider-events.d.ts.map +1 -1
- package/dist/kernel/events/tool-events.d.ts +25 -0
- package/dist/kernel/events/tool-events.d.ts.map +1 -1
- package/dist/mailbox-attach.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/plugins/chimera-plugin.d.ts +5 -0
- package/dist/plugins/chimera-plugin.d.ts.map +1 -1
- package/dist/security/index.d.ts +1 -0
- package/dist/security/index.d.ts.map +1 -1
- package/dist/security/index.js +317 -9
- package/dist/security/index.js.map +4 -4
- package/dist/security/kanban-boundary.d.ts +10 -0
- package/dist/security/kanban-boundary.d.ts.map +1 -0
- 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 +15 -0
- package/dist/storage/config-loader.d.ts.map +1 -1
- package/dist/storage/index.d.ts +2 -2
- package/dist/storage/index.d.ts.map +1 -1
- package/dist/storage/index.js +627 -428
- package/dist/storage/index.js.map +4 -4
- package/dist/storage/memory-consolidator.d.ts +49 -4
- package/dist/storage/memory-consolidator.d.ts.map +1 -1
- package/dist/storage/memory-graph-backend.d.ts +11 -0
- package/dist/storage/memory-graph-backend.d.ts.map +1 -1
- package/dist/storage/provider-config-watcher.d.ts +17 -0
- package/dist/storage/provider-config-watcher.d.ts.map +1 -1
- package/dist/storage/session-event-bridge.d.ts +2 -2
- package/dist/storage/session-event-bridge.d.ts.map +1 -1
- package/dist/tools/fallback-manage-tools.d.ts.map +1 -1
- package/dist/tools/index.js +166 -26
- package/dist/tools/index.js.map +3 -3
- package/dist/types/config.d.ts +12 -4
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/file-event-record.d.ts +68 -0
- package/dist/types/file-event-record.d.ts.map +1 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +13 -8
- package/dist/types/index.js.map +3 -3
- package/dist/types/permission.d.ts +38 -0
- package/dist/types/permission.d.ts.map +1 -1
- package/dist/types/provider.d.ts +1 -1
- package/dist/types/provider.d.ts.map +1 -1
- package/dist/types/session.d.ts +27 -0
- package/dist/types/session.d.ts.map +1 -1
- package/dist/types/tool-executor.d.ts +2 -0
- package/dist/types/tool-executor.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 +12 -11
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +436 -290
- package/dist/utils/index.js.map +4 -4
- package/dist/utils/message-invariants.d.ts +12 -0
- package/dist/utils/message-invariants.d.ts.map +1 -1
- package/dist/utils/safe-json.d.ts +1 -1
- package/dist/utils/safe-json.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/tool-wire-compact.d.ts +9 -0
- package/dist/utils/tool-wire-compact.d.ts.map +1 -1
- package/dist/utils/wstack-paths.d.ts +21 -1
- package/dist/utils/wstack-paths.d.ts.map +1 -1
- package/instructions/agents/ios.md +113 -0
- package/instructions/llm/memory-consolidator.md +4 -12
- package/instructions/system.md +4 -1
- package/package.json +3 -3
- package/skills/auto-review/SKILL.md +176 -0
- package/skills/mnemosyne/SKILL.md +156 -0
- package/skills/mnemosyne/instructions/agent-prompt.md +200 -0
package/dist/storage/index.js
CHANGED
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
import { createHash as createHash4 } from "node:crypto";
|
|
3
3
|
import { createReadStream } from "node:fs";
|
|
4
4
|
import * as fsp4 from "node:fs/promises";
|
|
5
|
-
import * as
|
|
5
|
+
import * as path8 from "node:path";
|
|
6
6
|
import { createInterface } from "node:readline";
|
|
7
7
|
|
|
8
8
|
// src/utils/atomic-write.ts
|
|
9
9
|
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
10
|
-
import * as
|
|
10
|
+
import * as fs3 from "node:fs/promises";
|
|
11
11
|
import { watch as watchDir } from "node:fs";
|
|
12
|
-
import * as
|
|
12
|
+
import * as path4 from "node:path";
|
|
13
13
|
|
|
14
14
|
// src/utils/child-env.ts
|
|
15
15
|
var ALLOWED_KEYS = /* @__PURE__ */ new Set([
|
|
@@ -186,6 +186,37 @@ var color = {
|
|
|
186
186
|
bgGreen: wrap("42", "49")
|
|
187
187
|
};
|
|
188
188
|
|
|
189
|
+
// src/utils/config-backup.ts
|
|
190
|
+
import * as fs from "node:fs/promises";
|
|
191
|
+
import * as path from "node:path";
|
|
192
|
+
function configHistoryDir(globalRoot) {
|
|
193
|
+
return path.join(globalRoot, "config-history");
|
|
194
|
+
}
|
|
195
|
+
function configSlug(absolutePath, globalRoot) {
|
|
196
|
+
const rel = path.relative(globalRoot, absolutePath);
|
|
197
|
+
const normalized = rel.replace(/\\/g, "/").replace(/\.json$/i, "");
|
|
198
|
+
return normalized.replace(/\//g, "-");
|
|
199
|
+
}
|
|
200
|
+
async function backupConfigFile(filePath, paths) {
|
|
201
|
+
let currentContent;
|
|
202
|
+
try {
|
|
203
|
+
currentContent = await fs.readFile(filePath, "utf8");
|
|
204
|
+
if (!currentContent.trim()) return;
|
|
205
|
+
} catch {
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
const now = /* @__PURE__ */ new Date();
|
|
209
|
+
const ts = now.toISOString().replace(/[:.]/g, "-").replace(/Z$/, "");
|
|
210
|
+
const slug = configSlug(filePath, paths.globalRoot);
|
|
211
|
+
const backupDir = configHistoryDir(paths.globalRoot);
|
|
212
|
+
const backupFile = path.join(backupDir, `${slug}-${ts}.json`);
|
|
213
|
+
try {
|
|
214
|
+
await fs.mkdir(backupDir, { recursive: true });
|
|
215
|
+
await fs.writeFile(backupFile, currentContent, { mode: 384, encoding: "utf8" });
|
|
216
|
+
} catch {
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
189
220
|
// src/utils/deep-merge.ts
|
|
190
221
|
var FORBIDDEN_PROTO_KEYS = /* @__PURE__ */ new Set([
|
|
191
222
|
"__proto__",
|
|
@@ -347,9 +378,23 @@ function mapContent(msg, fn) {
|
|
|
347
378
|
}
|
|
348
379
|
return { ...msg, content: next };
|
|
349
380
|
}
|
|
381
|
+
function hasMeaningfulContent(content) {
|
|
382
|
+
if (typeof content === "string") return content.trim().length > 0;
|
|
383
|
+
for (const block of content) {
|
|
384
|
+
if (block.type === "text") {
|
|
385
|
+
if (block.text.trim().length > 0) return true;
|
|
386
|
+
continue;
|
|
387
|
+
}
|
|
388
|
+
if (block.type === "thinking") {
|
|
389
|
+
if (block.thinking.trim().length > 0 || block.signature) return true;
|
|
390
|
+
continue;
|
|
391
|
+
}
|
|
392
|
+
return true;
|
|
393
|
+
}
|
|
394
|
+
return false;
|
|
395
|
+
}
|
|
350
396
|
function isEmptyMessage(msg) {
|
|
351
|
-
|
|
352
|
-
return msg.content.length === 0;
|
|
397
|
+
return !hasMeaningfulContent(msg.content);
|
|
353
398
|
}
|
|
354
399
|
|
|
355
400
|
// src/utils/regex-guard.ts
|
|
@@ -390,7 +435,7 @@ function compileUserRegex(pattern, flags) {
|
|
|
390
435
|
|
|
391
436
|
// src/utils/safe-json.ts
|
|
392
437
|
function safeParse(input, maxBytes = 5e6) {
|
|
393
|
-
if (input
|
|
438
|
+
if (Buffer.byteLength(input, "utf8") > maxBytes) {
|
|
394
439
|
return { ok: false, error: `Input exceeds limit (${maxBytes} bytes)` };
|
|
395
440
|
}
|
|
396
441
|
try {
|
|
@@ -404,14 +449,14 @@ function safeParse(input, maxBytes = 5e6) {
|
|
|
404
449
|
}
|
|
405
450
|
|
|
406
451
|
// src/utils/session-scoped-path.ts
|
|
407
|
-
import * as
|
|
452
|
+
import * as path2 from "node:path";
|
|
408
453
|
function sessionScopedPath(dir, sessionId, suffix) {
|
|
409
454
|
if (!sessionId || sessionId.includes("\\") || sessionId.includes("..")) {
|
|
410
455
|
throw invalid(sessionId);
|
|
411
456
|
}
|
|
412
|
-
const resolved =
|
|
413
|
-
const rel =
|
|
414
|
-
if (rel.startsWith("..") ||
|
|
457
|
+
const resolved = path2.resolve(dir, `${sessionId}${suffix}`);
|
|
458
|
+
const rel = path2.relative(path2.resolve(dir), resolved);
|
|
459
|
+
if (rel.startsWith("..") || path2.isAbsolute(rel)) {
|
|
415
460
|
throw invalid(sessionId);
|
|
416
461
|
}
|
|
417
462
|
return resolved;
|
|
@@ -460,23 +505,23 @@ function ulid(seedTime = Date.now()) {
|
|
|
460
505
|
|
|
461
506
|
// src/utils/wstack-paths.ts
|
|
462
507
|
import { createHash } from "node:crypto";
|
|
463
|
-
import * as
|
|
508
|
+
import * as fs2 from "node:fs";
|
|
464
509
|
import * as os from "node:os";
|
|
465
|
-
import * as
|
|
510
|
+
import * as path3 from "node:path";
|
|
466
511
|
function canonicalProjectRoot(absRoot) {
|
|
467
|
-
const checkoutRoot =
|
|
468
|
-
const dotGit =
|
|
512
|
+
const checkoutRoot = path3.resolve(absRoot);
|
|
513
|
+
const dotGit = path3.join(checkoutRoot, ".git");
|
|
469
514
|
try {
|
|
470
|
-
if (!
|
|
471
|
-
const gitDirLine =
|
|
515
|
+
if (!fs2.statSync(dotGit).isFile()) return checkoutRoot;
|
|
516
|
+
const gitDirLine = fs2.readFileSync(dotGit, "utf8").trim();
|
|
472
517
|
const match = /^gitdir:\s*(.+)$/i.exec(gitDirLine);
|
|
473
518
|
if (!match?.[1]) return checkoutRoot;
|
|
474
|
-
const gitDir =
|
|
475
|
-
const commonDirFile =
|
|
476
|
-
if (!
|
|
477
|
-
const commonDir =
|
|
478
|
-
if (
|
|
479
|
-
return
|
|
519
|
+
const gitDir = path3.resolve(checkoutRoot, match[1].trim());
|
|
520
|
+
const commonDirFile = path3.join(gitDir, "commondir");
|
|
521
|
+
if (!fs2.statSync(commonDirFile).isFile()) return checkoutRoot;
|
|
522
|
+
const commonDir = path3.resolve(gitDir, fs2.readFileSync(commonDirFile, "utf8").trim());
|
|
523
|
+
if (path3.basename(commonDir).toLowerCase() !== ".git") return checkoutRoot;
|
|
524
|
+
return path3.dirname(commonDir);
|
|
480
525
|
} catch {
|
|
481
526
|
return checkoutRoot;
|
|
482
527
|
}
|
|
@@ -486,7 +531,7 @@ function projectHash(absRoot) {
|
|
|
486
531
|
}
|
|
487
532
|
function projectSlug(absRoot) {
|
|
488
533
|
const identityRoot = canonicalProjectRoot(absRoot);
|
|
489
|
-
const base = slugify2(
|
|
534
|
+
const base = slugify2(path3.basename(identityRoot));
|
|
490
535
|
const hash = createHash("sha256").update(identityRoot).digest("hex").slice(0, 6);
|
|
491
536
|
return `${base}-${hash}`;
|
|
492
537
|
}
|
|
@@ -495,61 +540,83 @@ function slugify2(name) {
|
|
|
495
540
|
}
|
|
496
541
|
function wstackGlobalRoot() {
|
|
497
542
|
const fromEnv = process.env["WRONGSTACK_HOME"];
|
|
498
|
-
if (fromEnv && fromEnv.trim().length > 0) return
|
|
499
|
-
return
|
|
543
|
+
if (fromEnv && fromEnv.trim().length > 0) return path3.resolve(fromEnv);
|
|
544
|
+
return path3.join(os.homedir(), ".wrongstack");
|
|
500
545
|
}
|
|
501
546
|
function resolveWstackPaths(opts) {
|
|
502
|
-
const globalRoot = opts.globalRoot ?? (opts.userHome ?
|
|
547
|
+
const globalRoot = opts.globalRoot ?? (opts.userHome ? path3.join(opts.userHome, ".wrongstack") : wstackGlobalRoot());
|
|
503
548
|
const homeDir = opts.userHome ?? os.homedir();
|
|
504
549
|
const hash = projectHash(opts.projectRoot);
|
|
505
550
|
const slug = projectSlug(opts.projectRoot);
|
|
506
|
-
const projectDir =
|
|
551
|
+
const projectDir = path3.join(globalRoot, "projects", slug);
|
|
507
552
|
return {
|
|
508
553
|
globalRoot,
|
|
509
554
|
projectRoot: opts.projectRoot,
|
|
510
555
|
homeDir,
|
|
511
556
|
configDir: globalRoot,
|
|
512
|
-
globalConfig:
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
557
|
+
globalConfig: path3.join(globalRoot, "config.json"),
|
|
558
|
+
profilesDir: path3.join(globalRoot, "profiles"),
|
|
559
|
+
profileConfig: (name) => {
|
|
560
|
+
const safe = name.replace(/[/\\:]/g, "_").replace(/\.\./g, "_");
|
|
561
|
+
return path3.join(globalRoot, "profiles", safe || "default", "config.json");
|
|
562
|
+
},
|
|
563
|
+
profileStatuslineConfig: (name) => {
|
|
564
|
+
const safe = name.replace(/[/\\:]/g, "_").replace(/\.\./g, "_");
|
|
565
|
+
return path3.join(globalRoot, "profiles", safe || "default", "statusline.json");
|
|
566
|
+
},
|
|
567
|
+
profileModeConfig: (name) => {
|
|
568
|
+
const safe = name.replace(/[/\\:]/g, "_").replace(/\.\./g, "_");
|
|
569
|
+
return path3.join(globalRoot, "profiles", safe || "default", "mode.json");
|
|
570
|
+
},
|
|
571
|
+
profileProviderStatus: (name) => {
|
|
572
|
+
const safe = name.replace(/[/\\:]/g, "_").replace(/\.\./g, "_");
|
|
573
|
+
return path3.join(globalRoot, "profiles", safe || "default", "provider-status.json");
|
|
574
|
+
},
|
|
575
|
+
profileUpdateCache: (name) => {
|
|
576
|
+
const safe = name.replace(/[/\\:]/g, "_").replace(/\.\./g, "_");
|
|
577
|
+
return path3.join(globalRoot, "profiles", safe || "default", "update-cache.json");
|
|
578
|
+
},
|
|
579
|
+
secretsKey: path3.join(globalRoot, ".key"),
|
|
580
|
+
globalMemory: path3.join(globalRoot, "memory.md"),
|
|
581
|
+
globalSkills: path3.join(globalRoot, "skills"),
|
|
582
|
+
globalClaudeSkills: path3.join(homeDir, ".claude", "skills"),
|
|
583
|
+
globalDesignKits: path3.join(globalRoot, "design-kits"),
|
|
584
|
+
globalPrompts: path3.join(globalRoot, "prompts"),
|
|
585
|
+
globalInstructions: path3.join(globalRoot, "instructions"),
|
|
586
|
+
promptUsage: path3.join(globalRoot, "prompt-usage.json"),
|
|
587
|
+
cacheDir: path3.join(globalRoot, "cache"),
|
|
588
|
+
modelsCache: path3.join(globalRoot, "cache", "models.dev.json"),
|
|
589
|
+
modelsOverlayCache: path3.join(globalRoot, "cache", "models-overlay.json"),
|
|
590
|
+
historyFile: path3.join(globalRoot, "history"),
|
|
591
|
+
logFile: path3.join(globalRoot, "logs", "wrongstack.log"),
|
|
526
592
|
projectDir,
|
|
527
|
-
projectCodebaseIndex:
|
|
528
|
-
projectMemory:
|
|
529
|
-
projectSessions:
|
|
530
|
-
projectTrust:
|
|
531
|
-
projectMeta:
|
|
532
|
-
projectLocalConfig:
|
|
533
|
-
inProjectConfig:
|
|
534
|
-
inProjectAgentsFile:
|
|
535
|
-
inProjectSkills:
|
|
536
|
-
inProjectClaudeSkills:
|
|
537
|
-
inProjectPrompts:
|
|
538
|
-
inProjectInstructions:
|
|
539
|
-
inProjectDesignKits:
|
|
540
|
-
inProjectWorktrees:
|
|
593
|
+
projectCodebaseIndex: path3.join(projectDir, "codebase-index"),
|
|
594
|
+
projectMemory: path3.join(projectDir, "memory.md"),
|
|
595
|
+
projectSessions: path3.join(projectDir, "sessions"),
|
|
596
|
+
projectTrust: path3.join(projectDir, "trust.json"),
|
|
597
|
+
projectMeta: path3.join(projectDir, "meta.json"),
|
|
598
|
+
projectLocalConfig: path3.join(projectDir, "config.local.json"),
|
|
599
|
+
inProjectConfig: path3.join(opts.projectRoot, ".wrongstack", "config.json"),
|
|
600
|
+
inProjectAgentsFile: path3.join(opts.projectRoot, ".wrongstack", "AGENTS.md"),
|
|
601
|
+
inProjectSkills: path3.join(opts.projectRoot, ".wrongstack", "skills"),
|
|
602
|
+
inProjectClaudeSkills: path3.join(opts.projectRoot, ".claude", "skills"),
|
|
603
|
+
inProjectPrompts: path3.join(opts.projectRoot, ".wrongstack", "prompts"),
|
|
604
|
+
inProjectInstructions: path3.join(opts.projectRoot, ".wrongstack", "instructions"),
|
|
605
|
+
inProjectDesignKits: path3.join(opts.projectRoot, ".wrongstack", "design-kits"),
|
|
606
|
+
inProjectWorktrees: path3.join(opts.projectRoot, ".wrongstack", "worktrees"),
|
|
541
607
|
projectHash: hash,
|
|
542
608
|
projectSlug: slug,
|
|
543
|
-
projectGoal:
|
|
544
|
-
projectInputHistory:
|
|
545
|
-
projectSpecs:
|
|
546
|
-
projectTaskGraphs:
|
|
547
|
-
projectSddSession:
|
|
548
|
-
projectPlan:
|
|
549
|
-
projectAutophase:
|
|
550
|
-
projectSddBoards:
|
|
551
|
-
syncConfig:
|
|
552
|
-
|
|
609
|
+
projectGoal: path3.join(projectDir, "goal.json"),
|
|
610
|
+
projectInputHistory: path3.join(projectDir, "input-history.json"),
|
|
611
|
+
projectSpecs: path3.join(projectDir, "specs"),
|
|
612
|
+
projectTaskGraphs: path3.join(projectDir, "task-graphs"),
|
|
613
|
+
projectSddSession: path3.join(projectDir, "sdd-session.json"),
|
|
614
|
+
projectPlan: path3.join(projectDir, "plan.json"),
|
|
615
|
+
projectAutophase: path3.join(projectDir, "autophase"),
|
|
616
|
+
projectSddBoards: path3.join(projectDir, "sdd-boards"),
|
|
617
|
+
syncConfig: path3.join(globalRoot, "sync.json"),
|
|
618
|
+
configHistoryDir: path3.join(globalRoot, "config-history"),
|
|
619
|
+
projectStatus: (projectHash2) => path3.join(globalRoot, "projects", projectHash2, "status.json")
|
|
553
620
|
};
|
|
554
621
|
}
|
|
555
622
|
|
|
@@ -687,17 +754,17 @@ var FsError = class extends WrongStackError {
|
|
|
687
754
|
|
|
688
755
|
// src/utils/atomic-write.ts
|
|
689
756
|
async function atomicWrite(targetPath, content, opts = {}) {
|
|
690
|
-
const dir =
|
|
691
|
-
await
|
|
692
|
-
const tmp =
|
|
757
|
+
const dir = path4.dirname(targetPath);
|
|
758
|
+
await fs3.mkdir(dir, { recursive: true });
|
|
759
|
+
const tmp = path4.join(dir, `.${path4.basename(targetPath)}.${randomBytes2(6).toString("hex")}.tmp`);
|
|
693
760
|
try {
|
|
694
761
|
if (typeof content === "string") {
|
|
695
|
-
await
|
|
762
|
+
await fs3.writeFile(tmp, content, { flag: "wx", encoding: opts.encoding ?? "utf8" });
|
|
696
763
|
} else {
|
|
697
|
-
await
|
|
764
|
+
await fs3.writeFile(tmp, content, { flag: "wx" });
|
|
698
765
|
}
|
|
699
766
|
try {
|
|
700
|
-
const fh = await
|
|
767
|
+
const fh = await fs3.open(tmp, "r+");
|
|
701
768
|
try {
|
|
702
769
|
await fh.sync();
|
|
703
770
|
} finally {
|
|
@@ -707,63 +774,63 @@ async function atomicWrite(targetPath, content, opts = {}) {
|
|
|
707
774
|
}
|
|
708
775
|
let mode;
|
|
709
776
|
try {
|
|
710
|
-
const stat13 = await
|
|
777
|
+
const stat13 = await fs3.stat(targetPath);
|
|
711
778
|
mode = stat13.mode & 511;
|
|
712
779
|
} catch {
|
|
713
780
|
mode = opts.mode;
|
|
714
781
|
}
|
|
715
782
|
if (mode !== void 0) {
|
|
716
|
-
await
|
|
783
|
+
await fs3.chmod(tmp, mode);
|
|
717
784
|
}
|
|
718
785
|
await renameWithRetry(tmp, targetPath);
|
|
719
786
|
if (mode !== void 0 && process.platform === "win32") {
|
|
720
787
|
try {
|
|
721
|
-
await
|
|
788
|
+
await fs3.chmod(targetPath, mode);
|
|
722
789
|
} catch {
|
|
723
790
|
}
|
|
724
791
|
}
|
|
725
792
|
} catch (err) {
|
|
726
793
|
try {
|
|
727
|
-
await
|
|
794
|
+
await fs3.unlink(tmp);
|
|
728
795
|
} catch {
|
|
729
796
|
}
|
|
730
797
|
throw err;
|
|
731
798
|
}
|
|
732
799
|
}
|
|
733
800
|
async function ensureDir(dir) {
|
|
734
|
-
await
|
|
801
|
+
await fs3.mkdir(dir, { recursive: true });
|
|
735
802
|
}
|
|
736
803
|
async function withFileLock(targetPath, fn, opts = {}) {
|
|
737
|
-
const dir =
|
|
738
|
-
await
|
|
739
|
-
const lockPath =
|
|
804
|
+
const dir = path4.dirname(targetPath);
|
|
805
|
+
await fs3.mkdir(dir, { recursive: true });
|
|
806
|
+
const lockPath = path4.join(dir, `.${path4.basename(targetPath)}.lock`);
|
|
740
807
|
const timeoutMs = opts.timeoutMs ?? 15e3;
|
|
741
808
|
const staleMs = opts.staleMs ?? 3e4;
|
|
742
809
|
const started = Date.now();
|
|
743
810
|
let handle;
|
|
744
811
|
for (; ; ) {
|
|
745
812
|
try {
|
|
746
|
-
handle = await
|
|
813
|
+
handle = await fs3.open(lockPath, "wx");
|
|
747
814
|
await handle.writeFile(`${process.pid}:${Date.now()}`);
|
|
748
815
|
break;
|
|
749
816
|
} catch (err) {
|
|
750
817
|
if (handle) {
|
|
751
818
|
await handle.close().catch(() => {
|
|
752
819
|
});
|
|
753
|
-
await
|
|
820
|
+
await fs3.unlink(lockPath).catch(() => {
|
|
754
821
|
});
|
|
755
822
|
handle = void 0;
|
|
756
823
|
}
|
|
757
824
|
const code = err.code;
|
|
758
825
|
if (code === "ENOENT") {
|
|
759
|
-
await
|
|
826
|
+
await fs3.mkdir(dir, { recursive: true });
|
|
760
827
|
continue;
|
|
761
828
|
}
|
|
762
829
|
if (code !== "EEXIST" && code !== "EPERM") throw err;
|
|
763
830
|
try {
|
|
764
|
-
const stat13 = await
|
|
831
|
+
const stat13 = await fs3.stat(lockPath);
|
|
765
832
|
if (Date.now() - stat13.mtimeMs > staleMs) {
|
|
766
|
-
await
|
|
833
|
+
await fs3.unlink(lockPath);
|
|
767
834
|
continue;
|
|
768
835
|
}
|
|
769
836
|
} catch {
|
|
@@ -789,14 +856,14 @@ async function withFileLock(targetPath, fn, opts = {}) {
|
|
|
789
856
|
} catch {
|
|
790
857
|
}
|
|
791
858
|
try {
|
|
792
|
-
await
|
|
859
|
+
await fs3.unlink(lockPath);
|
|
793
860
|
} catch {
|
|
794
861
|
}
|
|
795
862
|
}
|
|
796
863
|
}
|
|
797
864
|
async function waitForLockRelease(lockPath, remainingMs) {
|
|
798
|
-
const parentDir =
|
|
799
|
-
const lockName =
|
|
865
|
+
const parentDir = path4.dirname(lockPath);
|
|
866
|
+
const lockName = path4.basename(lockPath);
|
|
800
867
|
const intervalMs = Math.min(remainingMs, 100);
|
|
801
868
|
return new Promise((resolve11) => {
|
|
802
869
|
let settled = false;
|
|
@@ -824,7 +891,7 @@ async function waitForLockRelease(lockPath, remainingMs) {
|
|
|
824
891
|
}
|
|
825
892
|
return;
|
|
826
893
|
}
|
|
827
|
-
|
|
894
|
+
fs3.access(lockPath).then(
|
|
828
895
|
() => {
|
|
829
896
|
},
|
|
830
897
|
() => {
|
|
@@ -841,14 +908,14 @@ async function waitForLockRelease(lockPath, remainingMs) {
|
|
|
841
908
|
var TRANSIENT_RENAME_CODES = /* @__PURE__ */ new Set(["EPERM", "EBUSY", "EACCES", "ENOTEMPTY"]);
|
|
842
909
|
async function renameWithRetry(from, to) {
|
|
843
910
|
if (process.platform !== "win32") {
|
|
844
|
-
await
|
|
911
|
+
await fs3.rename(from, to);
|
|
845
912
|
return;
|
|
846
913
|
}
|
|
847
914
|
const delays = [10, 25, 60, 120, 250];
|
|
848
915
|
let lastErr;
|
|
849
916
|
for (let i = 0; i <= delays.length; i++) {
|
|
850
917
|
try {
|
|
851
|
-
await
|
|
918
|
+
await fs3.rename(from, to);
|
|
852
919
|
return;
|
|
853
920
|
} catch (err) {
|
|
854
921
|
lastErr = err;
|
|
@@ -865,7 +932,7 @@ async function renameWithRetry(from, to) {
|
|
|
865
932
|
// src/storage/file-session-writer.ts
|
|
866
933
|
import { closeSync, fsyncSync, openSync, writeSync } from "node:fs";
|
|
867
934
|
import * as fsp from "node:fs/promises";
|
|
868
|
-
import * as
|
|
935
|
+
import * as path5 from "node:path";
|
|
869
936
|
|
|
870
937
|
// src/storage/session-helpers.ts
|
|
871
938
|
function userInputTitle(content) {
|
|
@@ -882,7 +949,7 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
882
949
|
this.meta = meta;
|
|
883
950
|
this.events = events;
|
|
884
951
|
this.resumed = opts.resumed ?? false;
|
|
885
|
-
this.manifestFile = opts.dir ?
|
|
952
|
+
this.manifestFile = opts.dir ? path5.join(opts.dir, `${path5.basename(id)}.summary.json`) : "";
|
|
886
953
|
this.filePath = opts.filePath ?? "";
|
|
887
954
|
this.secretScrubber = opts.secretScrubber;
|
|
888
955
|
this.checkpointCas = opts.checkpointCas;
|
|
@@ -1664,7 +1731,7 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
1664
1731
|
import { spawn } from "node:child_process";
|
|
1665
1732
|
import { createHash as createHash2, randomUUID } from "node:crypto";
|
|
1666
1733
|
import * as fsp2 from "node:fs/promises";
|
|
1667
|
-
import * as
|
|
1734
|
+
import * as path6 from "node:path";
|
|
1668
1735
|
|
|
1669
1736
|
// src/storage/storage-concurrency.ts
|
|
1670
1737
|
async function mapWithConcurrency(items, concurrency, mapper) {
|
|
@@ -1695,29 +1762,29 @@ function sha256(content) {
|
|
|
1695
1762
|
return createHash2("sha256").update(content).digest("hex");
|
|
1696
1763
|
}
|
|
1697
1764
|
function isInside(root, target) {
|
|
1698
|
-
const
|
|
1699
|
-
return
|
|
1765
|
+
const relative7 = path6.relative(root, target);
|
|
1766
|
+
return relative7 === "" || !relative7.startsWith("..") && !path6.isAbsolute(relative7);
|
|
1700
1767
|
}
|
|
1701
1768
|
function normalizeRelative(input) {
|
|
1702
|
-
if (!input ||
|
|
1769
|
+
if (!input || path6.isAbsolute(input)) return null;
|
|
1703
1770
|
const normalized = input.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
1704
|
-
const resolved =
|
|
1771
|
+
const resolved = path6.posix.normalize(normalized);
|
|
1705
1772
|
if (!resolved || resolved === "." || resolved === ".." || resolved.startsWith("../")) return null;
|
|
1706
1773
|
return resolved;
|
|
1707
1774
|
}
|
|
1708
1775
|
function parseNulPaths(output) {
|
|
1709
1776
|
return output.split("\0").map(normalizeRelative).filter((value) => value !== null);
|
|
1710
1777
|
}
|
|
1711
|
-
function isWrongStackWorktreePath(
|
|
1712
|
-
return
|
|
1778
|
+
function isWrongStackWorktreePath(relative7) {
|
|
1779
|
+
return relative7 === ".wrongstack/worktrees" || relative7.startsWith(".wrongstack/worktrees/");
|
|
1713
1780
|
}
|
|
1714
1781
|
var SessionCheckpointCas = class {
|
|
1715
1782
|
rootDir;
|
|
1716
1783
|
projectRoot;
|
|
1717
1784
|
runGit;
|
|
1718
1785
|
constructor(opts) {
|
|
1719
|
-
this.rootDir =
|
|
1720
|
-
this.projectRoot =
|
|
1786
|
+
this.rootDir = path6.resolve(opts.rootDir);
|
|
1787
|
+
this.projectRoot = path6.resolve(opts.projectRoot);
|
|
1721
1788
|
this.runGit = opts.runGit ?? defaultRunGit;
|
|
1722
1789
|
}
|
|
1723
1790
|
async capture(_sessionId, _promptIndex) {
|
|
@@ -1735,46 +1802,46 @@ var SessionCheckpointCas = class {
|
|
|
1735
1802
|
}
|
|
1736
1803
|
const relativePaths = [
|
|
1737
1804
|
.../* @__PURE__ */ new Set([...parseNulPaths(tracked.stdout), ...parseNulPaths(untracked.stdout)])
|
|
1738
|
-
].filter((
|
|
1805
|
+
].filter((relative7) => !isWrongStackWorktreePath(relative7)).sort();
|
|
1739
1806
|
const unresolved = [];
|
|
1740
1807
|
let scheduledBytes = 0;
|
|
1741
1808
|
const captured = await mapWithConcurrency(
|
|
1742
1809
|
relativePaths,
|
|
1743
1810
|
CAPTURE_CONCURRENCY,
|
|
1744
|
-
async (
|
|
1745
|
-
const absolute =
|
|
1811
|
+
async (relative7) => {
|
|
1812
|
+
const absolute = path6.resolve(this.projectRoot, ...relative7.split("/"));
|
|
1746
1813
|
if (!isInside(this.projectRoot, absolute)) {
|
|
1747
|
-
unresolved.push({ path:
|
|
1814
|
+
unresolved.push({ path: relative7, reason: "path escapes project root" });
|
|
1748
1815
|
return null;
|
|
1749
1816
|
}
|
|
1750
1817
|
try {
|
|
1751
1818
|
const stat13 = await fsp2.lstat(absolute);
|
|
1752
1819
|
if (stat13.isSymbolicLink()) {
|
|
1753
1820
|
const linkTarget = await fsp2.readlink(absolute);
|
|
1754
|
-
const resolvedLink =
|
|
1755
|
-
if (
|
|
1821
|
+
const resolvedLink = path6.resolve(path6.dirname(absolute), linkTarget);
|
|
1822
|
+
if (path6.isAbsolute(linkTarget) || !isInside(this.projectRoot, resolvedLink)) {
|
|
1756
1823
|
unresolved.push({
|
|
1757
|
-
path:
|
|
1824
|
+
path: relative7,
|
|
1758
1825
|
reason: "symlink target escapes project root"
|
|
1759
1826
|
});
|
|
1760
1827
|
return null;
|
|
1761
1828
|
}
|
|
1762
|
-
return { path:
|
|
1829
|
+
return { path: relative7, state: "symlink", linkTarget };
|
|
1763
1830
|
}
|
|
1764
1831
|
if (!stat13.isFile()) {
|
|
1765
|
-
unresolved.push({ path:
|
|
1832
|
+
unresolved.push({ path: relative7, reason: "changed path is not a regular file" });
|
|
1766
1833
|
return null;
|
|
1767
1834
|
}
|
|
1768
1835
|
if (stat13.size > MAX_BLOB_BYTES) {
|
|
1769
1836
|
unresolved.push({
|
|
1770
|
-
path:
|
|
1837
|
+
path: relative7,
|
|
1771
1838
|
reason: `file exceeds ${MAX_BLOB_BYTES}-byte checkpoint blob limit`
|
|
1772
1839
|
});
|
|
1773
1840
|
return null;
|
|
1774
1841
|
}
|
|
1775
1842
|
if (scheduledBytes + stat13.size > MAX_CHECKPOINT_BYTES) {
|
|
1776
1843
|
unresolved.push({
|
|
1777
|
-
path:
|
|
1844
|
+
path: relative7,
|
|
1778
1845
|
reason: `checkpoint exceeds ${MAX_CHECKPOINT_BYTES}-byte aggregate blob limit`
|
|
1779
1846
|
});
|
|
1780
1847
|
return null;
|
|
@@ -1783,12 +1850,12 @@ var SessionCheckpointCas = class {
|
|
|
1783
1850
|
const content = await fsp2.readFile(absolute);
|
|
1784
1851
|
const blobHash = sha256(content);
|
|
1785
1852
|
await this.putBlob(blobHash, content);
|
|
1786
|
-
return { path:
|
|
1853
|
+
return { path: relative7, state: "file", blobHash, mode: stat13.mode & 511 };
|
|
1787
1854
|
} catch (err) {
|
|
1788
1855
|
if (err.code === "ENOENT") {
|
|
1789
|
-
return { path:
|
|
1856
|
+
return { path: relative7, state: "absent" };
|
|
1790
1857
|
}
|
|
1791
|
-
unresolved.push({ path:
|
|
1858
|
+
unresolved.push({ path: relative7, reason: toErrorMessage(err) });
|
|
1792
1859
|
return null;
|
|
1793
1860
|
}
|
|
1794
1861
|
}
|
|
@@ -1814,7 +1881,7 @@ var SessionCheckpointCas = class {
|
|
|
1814
1881
|
};
|
|
1815
1882
|
}
|
|
1816
1883
|
async materialize(checkpoint, targetRoot) {
|
|
1817
|
-
const target =
|
|
1884
|
+
const target = path6.resolve(targetRoot);
|
|
1818
1885
|
if (target === this.projectRoot) {
|
|
1819
1886
|
throw new Error("Refusing to materialize a workspace checkpoint over the parent project root");
|
|
1820
1887
|
}
|
|
@@ -1854,10 +1921,10 @@ var SessionCheckpointCas = class {
|
|
|
1854
1921
|
try {
|
|
1855
1922
|
const output = await this.safeOutputPath(target, realTarget, entry.path);
|
|
1856
1923
|
if (entry.state === "symlink") {
|
|
1857
|
-
if (
|
|
1924
|
+
if (path6.isAbsolute(entry.linkTarget)) {
|
|
1858
1925
|
throw new Error("absolute symlink target refused");
|
|
1859
1926
|
}
|
|
1860
|
-
const resolvedLink =
|
|
1927
|
+
const resolvedLink = path6.resolve(path6.dirname(output), entry.linkTarget);
|
|
1861
1928
|
if (!isInside(target, resolvedLink)) throw new Error("symlink target escapes checkpoint root");
|
|
1862
1929
|
}
|
|
1863
1930
|
prepared.push({
|
|
@@ -1885,7 +1952,7 @@ var SessionCheckpointCas = class {
|
|
|
1885
1952
|
await fsp2.unlink(output).catch((err) => {
|
|
1886
1953
|
if (err.code !== "ENOENT") throw err;
|
|
1887
1954
|
});
|
|
1888
|
-
await fsp2.mkdir(
|
|
1955
|
+
await fsp2.mkdir(path6.dirname(output), { recursive: true });
|
|
1889
1956
|
await fsp2.symlink(entry.linkTarget, output);
|
|
1890
1957
|
writtenFiles.push(entry.path);
|
|
1891
1958
|
continue;
|
|
@@ -1902,15 +1969,15 @@ var SessionCheckpointCas = class {
|
|
|
1902
1969
|
}
|
|
1903
1970
|
objectPath(hash) {
|
|
1904
1971
|
if (!HASH_RE.test(hash)) throw new Error(`Invalid CAS object hash: ${hash}`);
|
|
1905
|
-
return
|
|
1972
|
+
return path6.join(this.rootDir, "objects", hash.slice(0, 2), hash.slice(2));
|
|
1906
1973
|
}
|
|
1907
1974
|
manifestPath(hash) {
|
|
1908
1975
|
if (!HASH_RE.test(hash)) throw new Error(`Invalid checkpoint manifest hash: ${hash}`);
|
|
1909
|
-
return
|
|
1976
|
+
return path6.join(this.rootDir, "manifests", `${hash}.json`);
|
|
1910
1977
|
}
|
|
1911
1978
|
async putBlob(hash, content) {
|
|
1912
1979
|
const target = this.objectPath(hash);
|
|
1913
|
-
await fsp2.mkdir(
|
|
1980
|
+
await fsp2.mkdir(path6.dirname(target), { recursive: true });
|
|
1914
1981
|
try {
|
|
1915
1982
|
const existing = await fsp2.readFile(target);
|
|
1916
1983
|
if (sha256(existing) !== hash) throw new Error(`Corrupt CAS object collision: ${hash}`);
|
|
@@ -1918,9 +1985,9 @@ var SessionCheckpointCas = class {
|
|
|
1918
1985
|
} catch (err) {
|
|
1919
1986
|
if (err.code !== "ENOENT") throw err;
|
|
1920
1987
|
}
|
|
1921
|
-
const temp =
|
|
1922
|
-
|
|
1923
|
-
`.${
|
|
1988
|
+
const temp = path6.join(
|
|
1989
|
+
path6.dirname(target),
|
|
1990
|
+
`.${path6.basename(target)}.${process.pid}.${randomUUID()}.tmp`
|
|
1924
1991
|
);
|
|
1925
1992
|
let handle;
|
|
1926
1993
|
try {
|
|
@@ -1978,10 +2045,10 @@ var SessionCheckpointCas = class {
|
|
|
1978
2045
|
}
|
|
1979
2046
|
return parsed;
|
|
1980
2047
|
}
|
|
1981
|
-
async safeOutputPath(target, realTarget,
|
|
1982
|
-
const normalized = normalizeRelative(
|
|
2048
|
+
async safeOutputPath(target, realTarget, relative7) {
|
|
2049
|
+
const normalized = normalizeRelative(relative7);
|
|
1983
2050
|
if (!normalized) throw new Error("invalid relative path");
|
|
1984
|
-
const output =
|
|
2051
|
+
const output = path6.resolve(target, ...normalized.split("/"));
|
|
1985
2052
|
if (!isInside(target, output)) throw new Error("path escapes checkpoint target");
|
|
1986
2053
|
let probe = output;
|
|
1987
2054
|
for (; ; ) {
|
|
@@ -1991,7 +2058,7 @@ var SessionCheckpointCas = class {
|
|
|
1991
2058
|
return output;
|
|
1992
2059
|
} catch (err) {
|
|
1993
2060
|
if (err.code !== "ENOENT") throw err;
|
|
1994
|
-
const parent =
|
|
2061
|
+
const parent = path6.dirname(probe);
|
|
1995
2062
|
if (parent === probe) throw err;
|
|
1996
2063
|
probe = parent;
|
|
1997
2064
|
}
|
|
@@ -2060,13 +2127,13 @@ function generateSessionId(startedAt, _model) {
|
|
|
2060
2127
|
// src/storage/session-resume-validation.ts
|
|
2061
2128
|
import { createHash as createHash3 } from "node:crypto";
|
|
2062
2129
|
import * as fsp3 from "node:fs/promises";
|
|
2063
|
-
import * as
|
|
2130
|
+
import * as path7 from "node:path";
|
|
2064
2131
|
var MAX_REVALIDATE_BYTES = 5 * 1024 * 1024;
|
|
2065
2132
|
var VALIDATION_CONCURRENCY = 8;
|
|
2066
2133
|
var NOTICE_PATH_LIMIT = 20;
|
|
2067
2134
|
function isInside2(root, target) {
|
|
2068
|
-
const
|
|
2069
|
-
return
|
|
2135
|
+
const relative7 = path7.relative(root, target);
|
|
2136
|
+
return relative7 === "" || !relative7.startsWith("..") && !path7.isAbsolute(relative7);
|
|
2070
2137
|
}
|
|
2071
2138
|
function errno(err) {
|
|
2072
2139
|
return err && typeof err === "object" && "code" in err ? String(err.code) : void 0;
|
|
@@ -2077,7 +2144,7 @@ function latestObservations(events, projectRoot) {
|
|
|
2077
2144
|
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)) {
|
|
2078
2145
|
continue;
|
|
2079
2146
|
}
|
|
2080
|
-
const normalized =
|
|
2147
|
+
const normalized = path7.resolve(projectRoot, event.path);
|
|
2081
2148
|
latest.set(normalized, {
|
|
2082
2149
|
path: normalized,
|
|
2083
2150
|
hash: event.hash.toLowerCase(),
|
|
@@ -2135,7 +2202,7 @@ async function validateOne(observation, lexicalRoot, realRoot) {
|
|
|
2135
2202
|
}
|
|
2136
2203
|
}
|
|
2137
2204
|
async function validateResumeFileObservations(events, projectRoot) {
|
|
2138
|
-
const lexicalRoot =
|
|
2205
|
+
const lexicalRoot = path7.resolve(projectRoot);
|
|
2139
2206
|
const realRoot = await fsp3.realpath(lexicalRoot).catch(() => lexicalRoot);
|
|
2140
2207
|
const observations = latestObservations(events, lexicalRoot);
|
|
2141
2208
|
const results = await mapWithConcurrency(
|
|
@@ -2151,10 +2218,10 @@ async function validateResumeFileObservations(events, projectRoot) {
|
|
|
2151
2218
|
}
|
|
2152
2219
|
function formatResumeValidationNotice(validation, projectRoot) {
|
|
2153
2220
|
if (validation.staleFiles.length === 0) return null;
|
|
2154
|
-
const root =
|
|
2221
|
+
const root = path7.resolve(projectRoot);
|
|
2155
2222
|
const shown = validation.staleFiles.slice(0, NOTICE_PATH_LIMIT).map((entry) => {
|
|
2156
|
-
const
|
|
2157
|
-
const display = isInside2(root, entry.path) ?
|
|
2223
|
+
const relative7 = path7.relative(root, entry.path);
|
|
2224
|
+
const display = isInside2(root, entry.path) ? relative7 || "." : entry.path;
|
|
2158
2225
|
return `- ${JSON.stringify(display)} [${entry.status}]`;
|
|
2159
2226
|
});
|
|
2160
2227
|
const omitted = validation.staleFiles.length - shown.length;
|
|
@@ -2287,9 +2354,9 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
2287
2354
|
static LIST_SCAN_CONCURRENCY = 32;
|
|
2288
2355
|
constructor(opts) {
|
|
2289
2356
|
this.dir = opts.dir;
|
|
2290
|
-
this.projectRoot = opts.projectRoot ?
|
|
2357
|
+
this.projectRoot = opts.projectRoot ? path8.resolve(opts.projectRoot) : void 0;
|
|
2291
2358
|
this.checkpointCas = this.projectRoot ? new SessionCheckpointCas({
|
|
2292
|
-
rootDir:
|
|
2359
|
+
rootDir: path8.join(this.dir, "_cas"),
|
|
2293
2360
|
projectRoot: this.projectRoot
|
|
2294
2361
|
}) : void 0;
|
|
2295
2362
|
this.events = opts.events;
|
|
@@ -2355,17 +2422,17 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
2355
2422
|
}
|
|
2356
2423
|
/** Absolute path to the session index file. */
|
|
2357
2424
|
get indexFile() {
|
|
2358
|
-
return
|
|
2425
|
+
return path8.join(this.dir, "_index.jsonl");
|
|
2359
2426
|
}
|
|
2360
2427
|
/** Join session ID to its absolute path within the store directory. */
|
|
2361
2428
|
sessionPath(id, ext) {
|
|
2362
2429
|
return sessionScopedPath(this.dir, id, ext);
|
|
2363
2430
|
}
|
|
2364
2431
|
shardManifestPath(shardKey) {
|
|
2365
|
-
return shardKey ?
|
|
2432
|
+
return shardKey ? path8.join(this.dir, shardKey, "_manifest.json") : path8.join(this.dir, "_manifest.json");
|
|
2366
2433
|
}
|
|
2367
2434
|
shardKeyForSessionId(id) {
|
|
2368
|
-
const dirName =
|
|
2435
|
+
const dirName = path8.dirname(id);
|
|
2369
2436
|
return dirName === "." ? "" : dirName;
|
|
2370
2437
|
}
|
|
2371
2438
|
invalidateShardManifestBySessionId(id) {
|
|
@@ -2377,7 +2444,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
2377
2444
|
* subdirectory so sessions group naturally by day.
|
|
2378
2445
|
*/
|
|
2379
2446
|
async ensureShardDir(id) {
|
|
2380
|
-
const dirPath =
|
|
2447
|
+
const dirPath = path8.dirname(sessionScopedPath(this.dir, id, ""));
|
|
2381
2448
|
await ensureDir(dirPath);
|
|
2382
2449
|
return dirPath;
|
|
2383
2450
|
}
|
|
@@ -2541,7 +2608,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
2541
2608
|
// Shard directory (sessions/<date>/) — must match create() so the
|
|
2542
2609
|
// .summary.json sidecar lands next to the JSONL instead of the
|
|
2543
2610
|
// sessions root (where summaryFor() would never find it).
|
|
2544
|
-
dir:
|
|
2611
|
+
dir: path8.dirname(file),
|
|
2545
2612
|
filePath: file,
|
|
2546
2613
|
secretScrubber: this.secretScrubber,
|
|
2547
2614
|
checkpointCas: this.checkpointCas,
|
|
@@ -3162,7 +3229,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
3162
3229
|
return entry;
|
|
3163
3230
|
}
|
|
3164
3231
|
async collectSessionFilesInShard(shardKey) {
|
|
3165
|
-
const dir = shardKey ?
|
|
3232
|
+
const dir = shardKey ? path8.join(this.dir, shardKey) : this.dir;
|
|
3166
3233
|
const entries = await this.collectSessionFiles(dir, shardKey);
|
|
3167
3234
|
return shardKey ? entries.filter((entry) => entry.id.startsWith(`${shardKey}/`)) : entries.filter((entry) => !entry.id.includes("/"));
|
|
3168
3235
|
}
|
|
@@ -3185,13 +3252,13 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
3185
3252
|
if (entry.name === "_index.jsonl") continue;
|
|
3186
3253
|
const base = entry.name.replace(/\.jsonl$/, "");
|
|
3187
3254
|
const id = prefix ? `${prefix}/${base}` : base;
|
|
3188
|
-
files.push({ id, filePath:
|
|
3255
|
+
files.push({ id, filePath: path8.join(dir, entry.name) });
|
|
3189
3256
|
}
|
|
3190
3257
|
}
|
|
3191
3258
|
const childFileArrays = await Promise.all(
|
|
3192
3259
|
dirEntries.map((entry) => {
|
|
3193
3260
|
const childPrefix = depth === 0 ? entry.name : `${prefix}/${entry.name}`;
|
|
3194
|
-
return this.collectSessionFiles(
|
|
3261
|
+
return this.collectSessionFiles(path8.join(dir, entry.name), childPrefix, depth + 1);
|
|
3195
3262
|
})
|
|
3196
3263
|
);
|
|
3197
3264
|
return [...childFileArrays.flat(), ...files];
|
|
@@ -3224,7 +3291,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
3224
3291
|
const childIdArrays = await Promise.all(
|
|
3225
3292
|
dirEntries.map((entry) => {
|
|
3226
3293
|
const childPrefix = depth === 0 ? entry.name : `${prefix}/${entry.name}`;
|
|
3227
|
-
return this.collectSessionIds(
|
|
3294
|
+
return this.collectSessionIds(path8.join(dir, entry.name), childPrefix, depth + 1);
|
|
3228
3295
|
})
|
|
3229
3296
|
);
|
|
3230
3297
|
return [...childIdArrays.flat(), ...fileIds];
|
|
@@ -3339,9 +3406,9 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
3339
3406
|
async deleteSession(id) {
|
|
3340
3407
|
const jsonlPath = this.sessionPath(id, ".jsonl");
|
|
3341
3408
|
const summaryPath = this.sessionPath(id, ".summary.json");
|
|
3342
|
-
const shardDir =
|
|
3343
|
-
const base =
|
|
3344
|
-
const sessDir =
|
|
3409
|
+
const shardDir = path8.dirname(jsonlPath);
|
|
3410
|
+
const base = path8.basename(id);
|
|
3411
|
+
const sessDir = path8.join(shardDir, base);
|
|
3345
3412
|
const deletions = [
|
|
3346
3413
|
fsp4.unlink(jsonlPath),
|
|
3347
3414
|
fsp4.unlink(summaryPath),
|
|
@@ -3386,7 +3453,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
3386
3453
|
*/
|
|
3387
3454
|
async readActiveSessionId() {
|
|
3388
3455
|
try {
|
|
3389
|
-
const raw = await fsp4.readFile(
|
|
3456
|
+
const raw = await fsp4.readFile(path8.join(this.dir, "active.json"), "utf8");
|
|
3390
3457
|
const active = JSON.parse(raw);
|
|
3391
3458
|
return active.sessionId ?? null;
|
|
3392
3459
|
} catch {
|
|
@@ -3449,7 +3516,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
3449
3516
|
const activeSessionId = await this.readActiveSessionId();
|
|
3450
3517
|
const isPrunableJsonl = (name) => name.endsWith(".jsonl") && name !== "_index.jsonl" && name !== "_mailbox.jsonl" && !name.endsWith(".replay.jsonl") && !name.endsWith(".audit.jsonl");
|
|
3451
3518
|
const pruneFile = async (dir, name, prefix) => {
|
|
3452
|
-
const jsonlPath =
|
|
3519
|
+
const jsonlPath = path8.join(dir, name);
|
|
3453
3520
|
try {
|
|
3454
3521
|
const stat13 = await fsp4.stat(jsonlPath);
|
|
3455
3522
|
if (stat13.mtimeMs >= cutoff) return;
|
|
@@ -3469,7 +3536,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
3469
3536
|
continue;
|
|
3470
3537
|
}
|
|
3471
3538
|
if (!entry.isDirectory()) continue;
|
|
3472
|
-
const dateDir =
|
|
3539
|
+
const dateDir = path8.join(this.dir, entry.name);
|
|
3473
3540
|
const files = await fsp4.readdir(dateDir, { withFileTypes: true }).catch(() => []);
|
|
3474
3541
|
for (const file of files) {
|
|
3475
3542
|
if (!file.isFile() || !isPrunableJsonl(file.name)) continue;
|
|
@@ -3481,7 +3548,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
3481
3548
|
}
|
|
3482
3549
|
for (const entry of entries) {
|
|
3483
3550
|
if (!entry.isDirectory()) continue;
|
|
3484
|
-
const dateDir =
|
|
3551
|
+
const dateDir = path8.join(this.dir, entry.name);
|
|
3485
3552
|
try {
|
|
3486
3553
|
const remaining = await fsp4.readdir(dateDir);
|
|
3487
3554
|
if (remaining.length === 0) {
|
|
@@ -3606,7 +3673,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
3606
3673
|
|
|
3607
3674
|
// src/storage/queue-store.ts
|
|
3608
3675
|
import * as fsp5 from "node:fs/promises";
|
|
3609
|
-
import * as
|
|
3676
|
+
import * as path9 from "node:path";
|
|
3610
3677
|
var QueueStore = class {
|
|
3611
3678
|
file;
|
|
3612
3679
|
// Use `| undefined` (not `?`) so exactOptionalPropertyTypes doesn't
|
|
@@ -3615,7 +3682,7 @@ var QueueStore = class {
|
|
|
3615
3682
|
traceId;
|
|
3616
3683
|
logger;
|
|
3617
3684
|
constructor(opts) {
|
|
3618
|
-
this.file =
|
|
3685
|
+
this.file = path9.join(opts.dir, "queue.json");
|
|
3619
3686
|
this.events = opts.events;
|
|
3620
3687
|
this.traceId = opts.traceId;
|
|
3621
3688
|
this.logger = opts.logger;
|
|
@@ -3773,7 +3840,7 @@ function isPersistedQueueItem(v) {
|
|
|
3773
3840
|
// src/storage/attachment-store.ts
|
|
3774
3841
|
import { randomBytes as randomBytes3 } from "node:crypto";
|
|
3775
3842
|
import * as fsp6 from "node:fs/promises";
|
|
3776
|
-
import * as
|
|
3843
|
+
import * as path10 from "node:path";
|
|
3777
3844
|
var DEFAULT_SPOOL_THRESHOLD = 256 * 1024;
|
|
3778
3845
|
var PLACEHOLDER_RE = /\[(pasted|image|file) #(\d+)[^\]]*\]|\[file:([^\]]+)\]/g;
|
|
3779
3846
|
var DefaultAttachmentStore = class {
|
|
@@ -3794,7 +3861,7 @@ var DefaultAttachmentStore = class {
|
|
|
3794
3861
|
let data = input.data;
|
|
3795
3862
|
if (this.spoolDir && bytes >= this.spoolThreshold) {
|
|
3796
3863
|
await fsp6.mkdir(this.spoolDir, { recursive: true });
|
|
3797
|
-
spooledPath =
|
|
3864
|
+
spooledPath = path10.join(this.spoolDir, `${id}.bin`);
|
|
3798
3865
|
await atomicWrite(spooledPath, input.data, {
|
|
3799
3866
|
encoding: input.kind === "image" ? "base64" : "utf8"
|
|
3800
3867
|
});
|
|
@@ -3914,8 +3981,8 @@ function mergeAdjacentText(blocks) {
|
|
|
3914
3981
|
|
|
3915
3982
|
// src/storage/memory-backend.ts
|
|
3916
3983
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
3917
|
-
import * as
|
|
3918
|
-
import * as
|
|
3984
|
+
import * as fs4 from "node:fs/promises";
|
|
3985
|
+
import * as path11 from "node:path";
|
|
3919
3986
|
|
|
3920
3987
|
// src/types/memory.ts
|
|
3921
3988
|
var MEMORY_TYPE_LABELS = {
|
|
@@ -4082,7 +4149,7 @@ var FileMemoryBackend = class {
|
|
|
4082
4149
|
}
|
|
4083
4150
|
async getMtime(file) {
|
|
4084
4151
|
try {
|
|
4085
|
-
const stat13 = await
|
|
4152
|
+
const stat13 = await fs4.stat(file);
|
|
4086
4153
|
return stat13.mtimeMs;
|
|
4087
4154
|
} catch {
|
|
4088
4155
|
return 0;
|
|
@@ -4129,10 +4196,10 @@ var FileMemoryBackend = class {
|
|
|
4129
4196
|
}
|
|
4130
4197
|
async remember(scope, entry, filePath) {
|
|
4131
4198
|
const file = this.resolveFile(filePath, scope);
|
|
4132
|
-
await ensureDir(
|
|
4199
|
+
await ensureDir(path11.dirname(file));
|
|
4133
4200
|
let existing = "";
|
|
4134
4201
|
try {
|
|
4135
|
-
existing = await
|
|
4202
|
+
existing = await fs4.readFile(file, "utf8");
|
|
4136
4203
|
} catch {
|
|
4137
4204
|
}
|
|
4138
4205
|
const id = `mem_${Date.now()}_${randomUUID2().slice(0, 8)}`;
|
|
@@ -4150,7 +4217,7 @@ ${line}`;
|
|
|
4150
4217
|
return withFileLock(file, async () => {
|
|
4151
4218
|
let existing;
|
|
4152
4219
|
try {
|
|
4153
|
-
existing = await
|
|
4220
|
+
existing = await fs4.readFile(file, "utf8");
|
|
4154
4221
|
} catch {
|
|
4155
4222
|
return 0;
|
|
4156
4223
|
}
|
|
@@ -4187,7 +4254,7 @@ ${line}`;
|
|
|
4187
4254
|
async readAll(scope, filePath) {
|
|
4188
4255
|
const file = this.resolveFile(filePath, scope);
|
|
4189
4256
|
try {
|
|
4190
|
-
return await
|
|
4257
|
+
return await fs4.readFile(file, "utf8");
|
|
4191
4258
|
} catch {
|
|
4192
4259
|
return "";
|
|
4193
4260
|
}
|
|
@@ -4211,7 +4278,7 @@ ${line}`;
|
|
|
4211
4278
|
const file = this.resolveFile(filePath, scope);
|
|
4212
4279
|
let existing;
|
|
4213
4280
|
try {
|
|
4214
|
-
existing = await
|
|
4281
|
+
existing = await fs4.readFile(file, "utf8");
|
|
4215
4282
|
} catch {
|
|
4216
4283
|
return 0;
|
|
4217
4284
|
}
|
|
@@ -4231,7 +4298,7 @@ ${line}`;
|
|
|
4231
4298
|
const next = lines.join("\n");
|
|
4232
4299
|
const backup = `${file}.bak.${Date.now()}`;
|
|
4233
4300
|
try {
|
|
4234
|
-
await
|
|
4301
|
+
await fs4.copyFile(file, backup);
|
|
4235
4302
|
await pruneConsolidateBackups(file);
|
|
4236
4303
|
} catch {
|
|
4237
4304
|
}
|
|
@@ -4245,14 +4312,14 @@ ${line}`;
|
|
|
4245
4312
|
}
|
|
4246
4313
|
};
|
|
4247
4314
|
async function pruneConsolidateBackups(file) {
|
|
4248
|
-
const dir =
|
|
4249
|
-
const base =
|
|
4315
|
+
const dir = path11.dirname(file);
|
|
4316
|
+
const base = path11.basename(file);
|
|
4250
4317
|
const prefix = `${base}.bak.`;
|
|
4251
|
-
const backups = (await
|
|
4318
|
+
const backups = (await fs4.readdir(dir)).filter((name) => name.startsWith(prefix)).sort().reverse();
|
|
4252
4319
|
await Promise.all(
|
|
4253
4320
|
backups.slice(MAX_MEMORY_CONSOLIDATE_BACKUPS).map(async (name) => {
|
|
4254
4321
|
try {
|
|
4255
|
-
await
|
|
4322
|
+
await fs4.unlink(path11.join(dir, name));
|
|
4256
4323
|
} catch {
|
|
4257
4324
|
}
|
|
4258
4325
|
})
|
|
@@ -4268,7 +4335,7 @@ function parseEntries(raw, scope = "project-memory") {
|
|
|
4268
4335
|
}
|
|
4269
4336
|
|
|
4270
4337
|
// src/storage/memory-graph-backend.ts
|
|
4271
|
-
import * as
|
|
4338
|
+
import * as fs5 from "node:fs/promises";
|
|
4272
4339
|
var GraphMemoryBackend = class _GraphMemoryBackend {
|
|
4273
4340
|
kind = "graph";
|
|
4274
4341
|
file;
|
|
@@ -4436,7 +4503,7 @@ var GraphMemoryBackend = class _GraphMemoryBackend {
|
|
|
4436
4503
|
this.loadedScope = scope;
|
|
4437
4504
|
this.loaded = true;
|
|
4438
4505
|
try {
|
|
4439
|
-
await
|
|
4506
|
+
await fs5.unlink(this.graphFile);
|
|
4440
4507
|
} catch {
|
|
4441
4508
|
}
|
|
4442
4509
|
}
|
|
@@ -4476,7 +4543,7 @@ var GraphMemoryBackend = class _GraphMemoryBackend {
|
|
|
4476
4543
|
async loadGraph(scope) {
|
|
4477
4544
|
if (this.loaded && this.loadedScope === scope) return;
|
|
4478
4545
|
try {
|
|
4479
|
-
const raw = await
|
|
4546
|
+
const raw = await fs5.readFile(this.graphFile, "utf8");
|
|
4480
4547
|
const data = JSON.parse(raw);
|
|
4481
4548
|
this.nodes = new Map(data.nodes);
|
|
4482
4549
|
this.edges = data.edges;
|
|
@@ -4498,10 +4565,10 @@ var GraphMemoryBackend = class _GraphMemoryBackend {
|
|
|
4498
4565
|
edges: this.edges
|
|
4499
4566
|
};
|
|
4500
4567
|
const dir = this.graphFile.substring(0, this.graphFile.lastIndexOf("/"));
|
|
4501
|
-
await
|
|
4568
|
+
await fs5.mkdir(dir, { recursive: true });
|
|
4502
4569
|
const tmp = `${this.graphFile}.tmp`;
|
|
4503
|
-
await
|
|
4504
|
-
await
|
|
4570
|
+
await fs5.writeFile(tmp, JSON.stringify(data));
|
|
4571
|
+
await fs5.rename(tmp, this.graphFile);
|
|
4505
4572
|
} catch {
|
|
4506
4573
|
}
|
|
4507
4574
|
}
|
|
@@ -4620,7 +4687,7 @@ function simpleHash(s) {
|
|
|
4620
4687
|
|
|
4621
4688
|
// src/utils/instruction-file.ts
|
|
4622
4689
|
import { readFileSync as readFileSync2, statSync as statSync2 } from "node:fs";
|
|
4623
|
-
import * as
|
|
4690
|
+
import * as path12 from "node:path";
|
|
4624
4691
|
import { fileURLToPath } from "node:url";
|
|
4625
4692
|
var textCache = /* @__PURE__ */ new Map();
|
|
4626
4693
|
var rootCandidates;
|
|
@@ -4630,7 +4697,7 @@ function readBundledInstructionText(relativePath) {
|
|
|
4630
4697
|
let resolved = "";
|
|
4631
4698
|
for (const root of instructionRootCandidates()) {
|
|
4632
4699
|
try {
|
|
4633
|
-
resolved = readFileSync2(
|
|
4700
|
+
resolved = readFileSync2(path12.join(root, relativePath), "utf8").trimEnd();
|
|
4634
4701
|
break;
|
|
4635
4702
|
} catch {
|
|
4636
4703
|
}
|
|
@@ -4646,11 +4713,11 @@ function renderInstructionTemplate(template, values) {
|
|
|
4646
4713
|
}
|
|
4647
4714
|
function instructionRootCandidates() {
|
|
4648
4715
|
if (rootCandidates !== void 0) return rootCandidates;
|
|
4649
|
-
const here =
|
|
4716
|
+
const here = path12.dirname(fileURLToPath(import.meta.url));
|
|
4650
4717
|
const candidates = [
|
|
4651
|
-
|
|
4652
|
-
|
|
4653
|
-
|
|
4718
|
+
path12.resolve(here, "../../instructions"),
|
|
4719
|
+
path12.resolve(here, "../instructions"),
|
|
4720
|
+
path12.resolve(here, "instructions")
|
|
4654
4721
|
];
|
|
4655
4722
|
rootCandidates = candidates.sort((a, b) => Number(!isDirectory(a)) - Number(!isDirectory(b)));
|
|
4656
4723
|
return rootCandidates;
|
|
@@ -4664,6 +4731,43 @@ function isDirectory(candidate) {
|
|
|
4664
4731
|
}
|
|
4665
4732
|
|
|
4666
4733
|
// src/storage/memory-consolidator.ts
|
|
4734
|
+
function isSuperMemoryRecord(value) {
|
|
4735
|
+
return typeof value === "object" && value !== null && typeof value.text === "string";
|
|
4736
|
+
}
|
|
4737
|
+
function toPromptEntries(records, limit) {
|
|
4738
|
+
return records.filter(isSuperMemoryRecord).filter((record) => record.scope === "project").slice(0, limit).map((record) => ({
|
|
4739
|
+
scope: "project-memory",
|
|
4740
|
+
text: record.text,
|
|
4741
|
+
ts: record.updatedAt ?? record.createdAt ?? ""
|
|
4742
|
+
}));
|
|
4743
|
+
}
|
|
4744
|
+
function toSuperMemoryKind(type) {
|
|
4745
|
+
switch (type) {
|
|
4746
|
+
case "decision":
|
|
4747
|
+
case "convention":
|
|
4748
|
+
case "preference":
|
|
4749
|
+
case "anti_pattern":
|
|
4750
|
+
return type;
|
|
4751
|
+
case "reference":
|
|
4752
|
+
return "file_note";
|
|
4753
|
+
default:
|
|
4754
|
+
return "fact";
|
|
4755
|
+
}
|
|
4756
|
+
}
|
|
4757
|
+
function importanceFromPriority(priority) {
|
|
4758
|
+
switch (priority) {
|
|
4759
|
+
case "critical":
|
|
4760
|
+
return 0.95;
|
|
4761
|
+
case "high":
|
|
4762
|
+
return 0.8;
|
|
4763
|
+
case "medium":
|
|
4764
|
+
return 0.55;
|
|
4765
|
+
case "low":
|
|
4766
|
+
return 0.25;
|
|
4767
|
+
default:
|
|
4768
|
+
return 0.6;
|
|
4769
|
+
}
|
|
4770
|
+
}
|
|
4667
4771
|
function buildConsolidationPrompt(finalText, iterations, existingEntries) {
|
|
4668
4772
|
const existingBlock = existingEntries.length > 0 ? `
|
|
4669
4773
|
|
|
@@ -4675,36 +4779,11 @@ ${existingEntries.map((e) => `- [${e.ts.slice(0, 10)}] ${e.text}`).join("\n")}`
|
|
|
4675
4779
|
existingEntries: existingBlock
|
|
4676
4780
|
});
|
|
4677
4781
|
}
|
|
4678
|
-
function resolveDeletionBackend(store) {
|
|
4679
|
-
const backend = store.getBackend?.();
|
|
4680
|
-
if (!backend || typeof backend !== "object") return void 0;
|
|
4681
|
-
const candidate = backend;
|
|
4682
|
-
if (typeof candidate.deleteSuperMemory !== "function") return void 0;
|
|
4683
|
-
if (typeof candidate.listSuper !== "function") return void 0;
|
|
4684
|
-
return candidate;
|
|
4685
|
-
}
|
|
4686
|
-
async function deleteByQueryViaBackend(backend, query, reason) {
|
|
4687
|
-
const memories = await backend.listSuper(["active", "stale"]);
|
|
4688
|
-
const needle = query.toLowerCase();
|
|
4689
|
-
const matches = memories.filter((m) => {
|
|
4690
|
-
const legacyScope = m.legacyScope ?? m.scope;
|
|
4691
|
-
if (legacyScope !== "project" && legacyScope !== "project-memory") return false;
|
|
4692
|
-
return m.id === query || m.text.toLowerCase().includes(needle) || (m.tags?.some((t) => t.toLowerCase() === needle) ?? false) || (m.anchors?.some((a) => a.path?.toLowerCase().includes(needle)) ?? false);
|
|
4693
|
-
});
|
|
4694
|
-
let deleted = 0;
|
|
4695
|
-
for (const m of matches) {
|
|
4696
|
-
try {
|
|
4697
|
-
await backend.deleteSuperMemory(m.id, reason);
|
|
4698
|
-
deleted++;
|
|
4699
|
-
} catch {
|
|
4700
|
-
}
|
|
4701
|
-
}
|
|
4702
|
-
return deleted;
|
|
4703
|
-
}
|
|
4704
4782
|
var SessionMemoryConsolidator = class {
|
|
4705
4783
|
name = "session-memory-consolidator";
|
|
4706
4784
|
owner = "core";
|
|
4707
4785
|
memoryStore;
|
|
4786
|
+
superMemory;
|
|
4708
4787
|
provider;
|
|
4709
4788
|
model;
|
|
4710
4789
|
minIterations;
|
|
@@ -4712,6 +4791,7 @@ var SessionMemoryConsolidator = class {
|
|
|
4712
4791
|
oneShotOrchestrator;
|
|
4713
4792
|
constructor(opts) {
|
|
4714
4793
|
this.memoryStore = opts.memoryStore;
|
|
4794
|
+
this.superMemory = opts.superMemory;
|
|
4715
4795
|
this.provider = opts.provider;
|
|
4716
4796
|
this.model = opts.model;
|
|
4717
4797
|
this.minIterations = opts.minIterations ?? 2;
|
|
@@ -4727,9 +4807,20 @@ var SessionMemoryConsolidator = class {
|
|
|
4727
4807
|
const _finalText = result.finalText;
|
|
4728
4808
|
const _iterations = result.iterations;
|
|
4729
4809
|
const _model = this.model ?? ctx.model;
|
|
4810
|
+
const _sessionId = ctx.session.id;
|
|
4730
4811
|
void (async () => {
|
|
4731
4812
|
try {
|
|
4732
|
-
|
|
4813
|
+
let existingEntries;
|
|
4814
|
+
if (this.superMemory) {
|
|
4815
|
+
const records = this.superMemory.listSuper ? await this.superMemory.listSuper(["active"]) : await this.superMemory.searchSuper?.("", {
|
|
4816
|
+
limit: this.maxExistingEntries,
|
|
4817
|
+
scope: "project",
|
|
4818
|
+
includeStatuses: ["active"]
|
|
4819
|
+
}) ?? [];
|
|
4820
|
+
existingEntries = toPromptEntries(records, this.maxExistingEntries);
|
|
4821
|
+
} else {
|
|
4822
|
+
existingEntries = await this.memoryStore.list("project-memory", this.maxExistingEntries);
|
|
4823
|
+
}
|
|
4733
4824
|
const prompt = buildConsolidationPrompt(
|
|
4734
4825
|
_finalText,
|
|
4735
4826
|
_iterations,
|
|
@@ -4766,59 +4857,33 @@ var SessionMemoryConsolidator = class {
|
|
|
4766
4857
|
const parsed = JSON.parse(jsonMatch[0]);
|
|
4767
4858
|
if (!Array.isArray(parsed.operations) || parsed.operations.length === 0) return;
|
|
4768
4859
|
let added = 0;
|
|
4769
|
-
let edited = 0;
|
|
4770
|
-
let deleted = 0;
|
|
4771
4860
|
for (const op of parsed.operations) {
|
|
4772
|
-
|
|
4773
|
-
|
|
4774
|
-
|
|
4775
|
-
|
|
4776
|
-
|
|
4777
|
-
|
|
4778
|
-
|
|
4779
|
-
|
|
4780
|
-
|
|
4781
|
-
|
|
4782
|
-
|
|
4783
|
-
|
|
4784
|
-
|
|
4785
|
-
|
|
4786
|
-
|
|
4787
|
-
|
|
4788
|
-
|
|
4789
|
-
|
|
4790
|
-
|
|
4791
|
-
await this.memoryStore.forget(op.query);
|
|
4792
|
-
}
|
|
4793
|
-
await this.memoryStore.remember(op.text.trim(), void 0, {
|
|
4794
|
-
type: op.type,
|
|
4795
|
-
tags: op.tags,
|
|
4796
|
-
priority: op.priority
|
|
4797
|
-
});
|
|
4798
|
-
edited++;
|
|
4799
|
-
}
|
|
4800
|
-
break;
|
|
4801
|
-
}
|
|
4802
|
-
case "delete": {
|
|
4803
|
-
if (op.query) {
|
|
4804
|
-
const reason = `memory-consolidator delete: "${op.query}"`;
|
|
4805
|
-
const backend = resolveDeletionBackend(this.memoryStore);
|
|
4806
|
-
if (backend) {
|
|
4807
|
-
deleted += await deleteByQueryViaBackend(backend, op.query, reason);
|
|
4808
|
-
} else {
|
|
4809
|
-
deleted += await this.memoryStore.forget(op.query);
|
|
4810
|
-
}
|
|
4811
|
-
}
|
|
4812
|
-
break;
|
|
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
|
+
});
|
|
4813
4880
|
}
|
|
4881
|
+
added++;
|
|
4882
|
+
} catch {
|
|
4814
4883
|
}
|
|
4815
4884
|
}
|
|
4816
|
-
if (added > 0
|
|
4817
|
-
|
|
4818
|
-
if (added) parts.push(`${added} added`);
|
|
4819
|
-
if (edited) parts.push(`${edited} edited`);
|
|
4820
|
-
if (deleted) parts.push(`${deleted} deleted`);
|
|
4821
|
-
process.stderr.write(`[memory] Session consolidation: ${parts.join(", ")}
|
|
4885
|
+
if (added > 0) {
|
|
4886
|
+
process.stderr.write(`[memory] Session consolidation: ${added} added
|
|
4822
4887
|
`);
|
|
4823
4888
|
}
|
|
4824
4889
|
} catch {
|
|
@@ -4900,8 +4965,8 @@ function deepFreeze(obj) {
|
|
|
4900
4965
|
|
|
4901
4966
|
// src/storage/provider-config-watcher.ts
|
|
4902
4967
|
import * as syncFs from "node:fs";
|
|
4903
|
-
import * as
|
|
4904
|
-
import * as
|
|
4968
|
+
import * as fs6 from "node:fs/promises";
|
|
4969
|
+
import * as path13 from "node:path";
|
|
4905
4970
|
|
|
4906
4971
|
// src/security/config-secrets.ts
|
|
4907
4972
|
function decryptConfigSecrets(cfg, vault, opts) {
|
|
@@ -4947,7 +5012,7 @@ function isSecretField(name) {
|
|
|
4947
5012
|
async function readProviderSnapshot(configPath, vault, warn) {
|
|
4948
5013
|
let raw;
|
|
4949
5014
|
try {
|
|
4950
|
-
raw = await
|
|
5015
|
+
raw = await fs6.readFile(configPath, "utf8");
|
|
4951
5016
|
} catch (err) {
|
|
4952
5017
|
if (err.code !== "ENOENT") {
|
|
4953
5018
|
warn?.(`Could not read ${configPath}: ${err.message}`);
|
|
@@ -4963,7 +5028,8 @@ async function readProviderSnapshot(configPath, vault, warn) {
|
|
|
4963
5028
|
}
|
|
4964
5029
|
const decrypted = decryptConfigSecrets(parsed, vault, warn ? { warn } : {});
|
|
4965
5030
|
const snapshot = {
|
|
4966
|
-
providers: decrypted.providers ?? {}
|
|
5031
|
+
providers: decrypted.providers ?? {},
|
|
5032
|
+
snapshotHasProviders: decrypted.providers !== void 0
|
|
4967
5033
|
};
|
|
4968
5034
|
if (typeof decrypted.apiKey === "string") snapshot.apiKey = decrypted.apiKey;
|
|
4969
5035
|
if (typeof decrypted.baseUrl === "string") snapshot.baseUrl = decrypted.baseUrl;
|
|
@@ -4972,9 +5038,12 @@ async function readProviderSnapshot(configPath, vault, warn) {
|
|
|
4972
5038
|
if (Array.isArray(decrypted.fallbackModels)) snapshot.fallbackModels = decrypted.fallbackModels;
|
|
4973
5039
|
if (decrypted.fallbackProfiles) snapshot.fallbackProfiles = decrypted.fallbackProfiles;
|
|
4974
5040
|
if (Array.isArray(decrypted.favoriteModels)) snapshot.favoriteModels = decrypted.favoriteModels;
|
|
4975
|
-
if (typeof decrypted.favoriteModelsOnly === "boolean")
|
|
5041
|
+
if (typeof decrypted.favoriteModelsOnly === "boolean")
|
|
5042
|
+
snapshot.favoriteModelsOnly = decrypted.favoriteModelsOnly;
|
|
4976
5043
|
if (decrypted.modelMatrix) snapshot.modelMatrix = decrypted.modelMatrix;
|
|
4977
5044
|
if (typeof decrypted.fallbackAuto === "boolean") snapshot.fallbackAuto = decrypted.fallbackAuto;
|
|
5045
|
+
if (Array.isArray(decrypted.modelAvailabilitySchedule))
|
|
5046
|
+
snapshot.modelAvailabilitySchedule = decrypted.modelAvailabilitySchedule;
|
|
4978
5047
|
return snapshot;
|
|
4979
5048
|
}
|
|
4980
5049
|
function serializeSnapshot(s) {
|
|
@@ -4988,13 +5057,14 @@ function serializeSnapshot(s) {
|
|
|
4988
5057
|
favoriteModels: s.favoriteModels ?? null,
|
|
4989
5058
|
favoriteModelsOnly: s.favoriteModelsOnly ?? null,
|
|
4990
5059
|
modelMatrix: s.modelMatrix ?? null,
|
|
4991
|
-
fallbackAuto: s.fallbackAuto ?? null
|
|
5060
|
+
fallbackAuto: s.fallbackAuto ?? null,
|
|
5061
|
+
modelAvailabilitySchedule: s.modelAvailabilitySchedule ?? null
|
|
4992
5062
|
});
|
|
4993
5063
|
}
|
|
4994
5064
|
function watchProviderConfig(configPath, vault, onChange, opts = {}) {
|
|
4995
5065
|
const debounceMs = opts.debounceMs ?? 200;
|
|
4996
5066
|
const warn = opts.warn;
|
|
4997
|
-
const base =
|
|
5067
|
+
const base = path13.basename(configPath);
|
|
4998
5068
|
let timer;
|
|
4999
5069
|
let closed = false;
|
|
5000
5070
|
let lastSerialized;
|
|
@@ -5005,7 +5075,7 @@ function watchProviderConfig(configPath, vault, onChange, opts = {}) {
|
|
|
5005
5075
|
});
|
|
5006
5076
|
let watcher;
|
|
5007
5077
|
try {
|
|
5008
|
-
watcher = syncFs.watch(
|
|
5078
|
+
watcher = syncFs.watch(path13.dirname(configPath), { recursive: false });
|
|
5009
5079
|
} catch (err) {
|
|
5010
5080
|
warn?.(`Provider config watcher could not start: ${err.message}`);
|
|
5011
5081
|
return { close: () => {
|
|
@@ -5048,8 +5118,8 @@ function watchProviderConfig(configPath, vault, onChange, opts = {}) {
|
|
|
5048
5118
|
}
|
|
5049
5119
|
|
|
5050
5120
|
// src/storage/config-loader.ts
|
|
5051
|
-
import * as
|
|
5052
|
-
import * as
|
|
5121
|
+
import * as fs7 from "node:fs/promises";
|
|
5122
|
+
import * as path14 from "node:path";
|
|
5053
5123
|
|
|
5054
5124
|
// src/types/config.ts
|
|
5055
5125
|
var DEFAULT_TUI_THINKING_WORD = "thinking";
|
|
@@ -5349,6 +5419,7 @@ var defaultIndexing = {
|
|
|
5349
5419
|
};
|
|
5350
5420
|
var IN_PROJECT_ALLOWED_KEYS = /* @__PURE__ */ new Set([
|
|
5351
5421
|
"version",
|
|
5422
|
+
"activeProfile",
|
|
5352
5423
|
"model",
|
|
5353
5424
|
"cwd",
|
|
5354
5425
|
"context",
|
|
@@ -5371,6 +5442,7 @@ var IN_PROJECT_ALLOWED_KEYS = /* @__PURE__ */ new Set([
|
|
|
5371
5442
|
"fallbackProfiles",
|
|
5372
5443
|
"favoriteModels",
|
|
5373
5444
|
"favoriteModelsOnly",
|
|
5445
|
+
"modelAvailabilitySchedule",
|
|
5374
5446
|
"fallbackAuto",
|
|
5375
5447
|
"models",
|
|
5376
5448
|
"modelMatrix",
|
|
@@ -5406,6 +5478,7 @@ var KNOWN_DENIED_IN_PROJECT = [
|
|
|
5406
5478
|
];
|
|
5407
5479
|
var KNOWN_CONFIG_TOP_LEVEL_KEYS = /* @__PURE__ */ new Set([
|
|
5408
5480
|
"version",
|
|
5481
|
+
"activeProfile",
|
|
5409
5482
|
"provider",
|
|
5410
5483
|
"model",
|
|
5411
5484
|
"apiKey",
|
|
@@ -5422,6 +5495,7 @@ var KNOWN_CONFIG_TOP_LEVEL_KEYS = /* @__PURE__ */ new Set([
|
|
|
5422
5495
|
"fallbackProfiles",
|
|
5423
5496
|
"favoriteModels",
|
|
5424
5497
|
"favoriteModelsOnly",
|
|
5498
|
+
"modelAvailabilitySchedule",
|
|
5425
5499
|
"fallbackAuto",
|
|
5426
5500
|
"hooks",
|
|
5427
5501
|
"plugins",
|
|
@@ -5569,8 +5643,8 @@ function stripUnsafeInProjectFields(inProject, sourcePath, warn = (msg) => conso
|
|
|
5569
5643
|
return out;
|
|
5570
5644
|
}
|
|
5571
5645
|
function samePath(a, b) {
|
|
5572
|
-
let ra =
|
|
5573
|
-
let rb =
|
|
5646
|
+
let ra = path14.resolve(a);
|
|
5647
|
+
let rb = path14.resolve(b);
|
|
5574
5648
|
if (process.platform === "win32" || process.platform === "darwin") {
|
|
5575
5649
|
ra = ra.toLowerCase();
|
|
5576
5650
|
rb = rb.toLowerCase();
|
|
@@ -5592,7 +5666,7 @@ function deepMerge2(base, patch) {
|
|
|
5592
5666
|
opts
|
|
5593
5667
|
);
|
|
5594
5668
|
}
|
|
5595
|
-
var DefaultConfigLoader = class {
|
|
5669
|
+
var DefaultConfigLoader = class _DefaultConfigLoader {
|
|
5596
5670
|
paths;
|
|
5597
5671
|
strict;
|
|
5598
5672
|
vault;
|
|
@@ -5626,12 +5700,17 @@ var DefaultConfigLoader = class {
|
|
|
5626
5700
|
let cfg = { ...BEHAVIOR_DEFAULTS };
|
|
5627
5701
|
await this.ensureGlobalDefaults();
|
|
5628
5702
|
const inProjectCollides = samePath(this.paths.inProjectConfig, this.paths.globalConfig) || samePath(this.paths.inProjectConfig, this.paths.projectLocalConfig);
|
|
5629
|
-
const [
|
|
5703
|
+
const [bootstrap, local, inProject] = await Promise.all([
|
|
5630
5704
|
this.readJson(this.paths.globalConfig),
|
|
5631
5705
|
this.readJson(this.paths.projectLocalConfig),
|
|
5632
5706
|
inProjectCollides ? Promise.resolve({}) : this.readJson(this.paths.inProjectConfig)
|
|
5633
5707
|
]);
|
|
5634
|
-
cfg = deepMerge2(cfg,
|
|
5708
|
+
cfg = deepMerge2(cfg, bootstrap);
|
|
5709
|
+
const profileName = bootstrap.activeProfile ?? "default";
|
|
5710
|
+
const profileCfg = await this.readJson(this.paths.profileConfig(profileName));
|
|
5711
|
+
if (Object.keys(profileCfg).length > 0) {
|
|
5712
|
+
cfg = deepMerge2(cfg, profileCfg);
|
|
5713
|
+
}
|
|
5635
5714
|
cfg = deepMerge2(cfg, local);
|
|
5636
5715
|
cfg = deepMerge2(
|
|
5637
5716
|
cfg,
|
|
@@ -5657,10 +5736,11 @@ var DefaultConfigLoader = class {
|
|
|
5657
5736
|
cfg = deepMerge2(cfg, patch);
|
|
5658
5737
|
}
|
|
5659
5738
|
} catch (err) {
|
|
5660
|
-
this.logWarn(
|
|
5661
|
-
|
|
5662
|
-
|
|
5663
|
-
|
|
5739
|
+
this.logWarn("Config source load failed", {
|
|
5740
|
+
event: "config.source_load_failed",
|
|
5741
|
+
source: src.name,
|
|
5742
|
+
message: toErrorMessage(err)
|
|
5743
|
+
});
|
|
5664
5744
|
}
|
|
5665
5745
|
}
|
|
5666
5746
|
if (opts.cliFlags) {
|
|
@@ -5693,14 +5773,20 @@ var DefaultConfigLoader = class {
|
|
|
5693
5773
|
}
|
|
5694
5774
|
return Object.freeze(cfg);
|
|
5695
5775
|
}
|
|
5776
|
+
/** Check whether a config object contains only the two bootstrap keys. */
|
|
5777
|
+
static isBootstrapOnly(config) {
|
|
5778
|
+
const BOOTSTRAP_KEYS = /* @__PURE__ */ new Set(["version", "activeProfile"]);
|
|
5779
|
+
return Object.keys(config).every((k) => BOOTSTRAP_KEYS.has(k));
|
|
5780
|
+
}
|
|
5696
5781
|
async ensureGlobalDefaults() {
|
|
5697
5782
|
const fp = this.paths.globalConfig;
|
|
5698
5783
|
const t0 = Date.now();
|
|
5699
5784
|
try {
|
|
5700
5785
|
await withFileLock(fp, async () => {
|
|
5701
5786
|
let parsed;
|
|
5787
|
+
let fileExisted = true;
|
|
5702
5788
|
try {
|
|
5703
|
-
const raw = await
|
|
5789
|
+
const raw = await fs7.readFile(fp, "utf8");
|
|
5704
5790
|
const result = safeParse(raw);
|
|
5705
5791
|
if (!result.ok || !isPlainRecord(result.value)) {
|
|
5706
5792
|
return;
|
|
@@ -5719,29 +5805,46 @@ var DefaultConfigLoader = class {
|
|
|
5719
5805
|
durationMs: Date.now() - t0,
|
|
5720
5806
|
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
5721
5807
|
});
|
|
5722
|
-
this.logWarn(
|
|
5723
|
-
|
|
5724
|
-
|
|
5725
|
-
|
|
5808
|
+
this.logWarn("Config defaults read failed", {
|
|
5809
|
+
event: "config.defaults_read_failed",
|
|
5810
|
+
path: fp,
|
|
5811
|
+
message: toErrorMessage(err)
|
|
5812
|
+
});
|
|
5726
5813
|
return;
|
|
5727
5814
|
}
|
|
5815
|
+
fileExisted = false;
|
|
5728
5816
|
parsed = {};
|
|
5729
5817
|
}
|
|
5730
|
-
const
|
|
5731
|
-
|
|
5732
|
-
|
|
5733
|
-
|
|
5734
|
-
|
|
5735
|
-
|
|
5736
|
-
|
|
5737
|
-
|
|
5738
|
-
|
|
5739
|
-
|
|
5740
|
-
|
|
5741
|
-
|
|
5742
|
-
|
|
5743
|
-
|
|
5744
|
-
|
|
5818
|
+
const profileName = parsed.activeProfile ?? "default";
|
|
5819
|
+
const profileFp = this.paths.profileConfig(profileName);
|
|
5820
|
+
await this.ensureProfileConfig(profileFp, parsed, fileExisted);
|
|
5821
|
+
const bootstrap = {
|
|
5822
|
+
version: 1,
|
|
5823
|
+
activeProfile: profileName
|
|
5824
|
+
};
|
|
5825
|
+
let needsBootstrapWrite = false;
|
|
5826
|
+
if (parsed.version !== 1) {
|
|
5827
|
+
needsBootstrapWrite = true;
|
|
5828
|
+
}
|
|
5829
|
+
if (parsed.activeProfile === void 0) {
|
|
5830
|
+
needsBootstrapWrite = true;
|
|
5831
|
+
}
|
|
5832
|
+
if (!_DefaultConfigLoader.isBootstrapOnly(parsed)) {
|
|
5833
|
+
needsBootstrapWrite = true;
|
|
5834
|
+
}
|
|
5835
|
+
if (needsBootstrapWrite) {
|
|
5836
|
+
await backupConfigFile(fp, this.paths);
|
|
5837
|
+
await atomicWrite(fp, JSON.stringify(bootstrap, null, 2), { mode: 384 });
|
|
5838
|
+
this.events?.emit("storage.write", {
|
|
5839
|
+
sessionId: "~config~",
|
|
5840
|
+
store: "config",
|
|
5841
|
+
filePath: fp,
|
|
5842
|
+
operation: "ensure_defaults",
|
|
5843
|
+
outcome: "success",
|
|
5844
|
+
durationMs: Date.now() - t0,
|
|
5845
|
+
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
5846
|
+
});
|
|
5847
|
+
}
|
|
5745
5848
|
});
|
|
5746
5849
|
} catch (err) {
|
|
5747
5850
|
this.events?.emit("storage.error", {
|
|
@@ -5755,11 +5858,104 @@ var DefaultConfigLoader = class {
|
|
|
5755
5858
|
durationMs: Date.now() - t0,
|
|
5756
5859
|
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
5757
5860
|
});
|
|
5758
|
-
this.logWarn(
|
|
5759
|
-
|
|
5760
|
-
|
|
5761
|
-
|
|
5861
|
+
this.logWarn("Config defaults write failed", {
|
|
5862
|
+
event: "config.defaults_write_failed",
|
|
5863
|
+
path: fp,
|
|
5864
|
+
message: toErrorMessage(err)
|
|
5865
|
+
});
|
|
5866
|
+
}
|
|
5867
|
+
}
|
|
5868
|
+
/**
|
|
5869
|
+
* Ensure a profile config file exists at the given path.
|
|
5870
|
+
* On first boot: migrate content from the old flat global config, or seed
|
|
5871
|
+
* with behavior defaults. Subsequent boots: fill any missing keys from
|
|
5872
|
+
* BEHAVIOR_DEFAULTS (keeping user settings intact).
|
|
5873
|
+
*
|
|
5874
|
+
* CRITICAL SAFETY NET: when the profile already exists but the old global
|
|
5875
|
+
* config has extra non-bootstrap keys (because WebUI/CLI persistence functions
|
|
5876
|
+
* mistakenly wrote settings to the root config), those keys are merged into
|
|
5877
|
+
* the profile so they are NOT silently destroyed by the subsequent bootstrap
|
|
5878
|
+
* trim. This prevents the "config keeps getting emptied" bug.
|
|
5879
|
+
*/
|
|
5880
|
+
async ensureProfileConfig(profileFp, oldGlobalParsed, oldFileExisted) {
|
|
5881
|
+
const t0 = Date.now();
|
|
5882
|
+
let parsed;
|
|
5883
|
+
let existed = true;
|
|
5884
|
+
try {
|
|
5885
|
+
const raw = await fs7.readFile(profileFp, "utf8");
|
|
5886
|
+
const result = safeParse(raw);
|
|
5887
|
+
if (!result.ok || !isPlainRecord(result.value)) {
|
|
5888
|
+
this.logWarn("Profile config parse failed \u2014 falling back to defaults", {
|
|
5889
|
+
event: "config.profile_parse_failed",
|
|
5890
|
+
path: profileFp
|
|
5891
|
+
});
|
|
5892
|
+
parsed = {};
|
|
5893
|
+
} else {
|
|
5894
|
+
parsed = result.value;
|
|
5895
|
+
}
|
|
5896
|
+
} catch (err) {
|
|
5897
|
+
if (err.code !== "ENOENT") {
|
|
5898
|
+
this.logWarn("Profile config read failed", {
|
|
5899
|
+
event: "config.profile_read_failed",
|
|
5900
|
+
path: profileFp,
|
|
5901
|
+
message: toErrorMessage(err)
|
|
5902
|
+
});
|
|
5903
|
+
return;
|
|
5904
|
+
}
|
|
5905
|
+
existed = false;
|
|
5906
|
+
parsed = {};
|
|
5907
|
+
}
|
|
5908
|
+
const profileHasContent = existed && Object.keys(parsed).some((k) => k !== "version" && k !== "activeProfile");
|
|
5909
|
+
let seed;
|
|
5910
|
+
if (!profileHasContent && oldFileExisted && Object.keys(oldGlobalParsed).length >= 1) {
|
|
5911
|
+
seed = { ...oldGlobalParsed };
|
|
5912
|
+
delete seed["activeProfile"];
|
|
5913
|
+
const filled = fillMissingDefaults(seed, BEHAVIOR_DEFAULTS);
|
|
5914
|
+
seed = filled.value;
|
|
5915
|
+
} 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
|
+
const filled = fillMissingDefaults(parsed, BEHAVIOR_DEFAULTS);
|
|
5945
|
+
if (!filled.changed) return;
|
|
5946
|
+
seed = filled.value;
|
|
5762
5947
|
}
|
|
5948
|
+
await backupConfigFile(profileFp, this.paths);
|
|
5949
|
+
await atomicWrite(profileFp, JSON.stringify(seed, null, 2), { mode: 384 });
|
|
5950
|
+
this.events?.emit("storage.write", {
|
|
5951
|
+
sessionId: "~config~",
|
|
5952
|
+
store: "config",
|
|
5953
|
+
filePath: profileFp,
|
|
5954
|
+
operation: "ensure_profile_defaults",
|
|
5955
|
+
outcome: "success",
|
|
5956
|
+
durationMs: Date.now() - t0,
|
|
5957
|
+
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
5958
|
+
});
|
|
5763
5959
|
}
|
|
5764
5960
|
/**
|
|
5765
5961
|
* Persist a sync config to ~/.wrongstack/sync.json, with the token encrypted
|
|
@@ -5809,7 +6005,7 @@ var DefaultConfigLoader = class {
|
|
|
5809
6005
|
const fp = this.paths.syncConfig;
|
|
5810
6006
|
const t0 = Date.now();
|
|
5811
6007
|
try {
|
|
5812
|
-
const raw = await
|
|
6008
|
+
const raw = await fs7.readFile(fp, "utf8");
|
|
5813
6009
|
const parsed = safeParse(raw);
|
|
5814
6010
|
if (!parsed.ok || !parsed.value) {
|
|
5815
6011
|
this.events?.emit("storage.read", {
|
|
@@ -5860,10 +6056,10 @@ var DefaultConfigLoader = class {
|
|
|
5860
6056
|
error: storageErrorString(err),
|
|
5861
6057
|
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
5862
6058
|
});
|
|
5863
|
-
this.logWarn(
|
|
5864
|
-
|
|
5865
|
-
|
|
5866
|
-
);
|
|
6059
|
+
this.logWarn("Config sync load failed", {
|
|
6060
|
+
event: "config.sync_load_failed",
|
|
6061
|
+
message: toErrorMessage(err)
|
|
6062
|
+
});
|
|
5867
6063
|
return null;
|
|
5868
6064
|
}
|
|
5869
6065
|
}
|
|
@@ -5871,7 +6067,7 @@ var DefaultConfigLoader = class {
|
|
|
5871
6067
|
const t0 = Date.now();
|
|
5872
6068
|
let mtimeMs = null;
|
|
5873
6069
|
try {
|
|
5874
|
-
const stat13 = await
|
|
6070
|
+
const stat13 = await fs7.stat(file);
|
|
5875
6071
|
mtimeMs = stat13.mtimeMs;
|
|
5876
6072
|
const cached = this.jsonCache.get(file);
|
|
5877
6073
|
if (cached && cached.mtimeMs === mtimeMs) {
|
|
@@ -5892,15 +6088,16 @@ var DefaultConfigLoader = class {
|
|
|
5892
6088
|
error: storageErrorString(err),
|
|
5893
6089
|
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
5894
6090
|
});
|
|
5895
|
-
this.logWarn(
|
|
5896
|
-
|
|
5897
|
-
|
|
5898
|
-
|
|
6091
|
+
this.logWarn("Config read failed (stat)", {
|
|
6092
|
+
event: "config.read_failed",
|
|
6093
|
+
path: file,
|
|
6094
|
+
message: toErrorMessage(err)
|
|
6095
|
+
});
|
|
5899
6096
|
return {};
|
|
5900
6097
|
}
|
|
5901
6098
|
let raw;
|
|
5902
6099
|
try {
|
|
5903
|
-
raw = await
|
|
6100
|
+
raw = await fs7.readFile(file, "utf8");
|
|
5904
6101
|
} catch (err) {
|
|
5905
6102
|
if (err.code !== "ENOENT") {
|
|
5906
6103
|
this.events?.emit("storage.read", {
|
|
@@ -5913,10 +6110,11 @@ var DefaultConfigLoader = class {
|
|
|
5913
6110
|
error: storageErrorString(err),
|
|
5914
6111
|
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
5915
6112
|
});
|
|
5916
|
-
this.logWarn(
|
|
5917
|
-
|
|
5918
|
-
|
|
5919
|
-
|
|
6113
|
+
this.logWarn("Config read failed (read)", {
|
|
6114
|
+
event: "config.read_failed",
|
|
6115
|
+
path: file,
|
|
6116
|
+
message: toErrorMessage(err)
|
|
6117
|
+
});
|
|
5920
6118
|
}
|
|
5921
6119
|
this.jsonCache.set(file, { mtimeMs: null, value: {} });
|
|
5922
6120
|
return {};
|
|
@@ -5933,10 +6131,10 @@ var DefaultConfigLoader = class {
|
|
|
5933
6131
|
error: "parse error or empty file",
|
|
5934
6132
|
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
5935
6133
|
});
|
|
5936
|
-
this.logWarn(
|
|
5937
|
-
|
|
5938
|
-
|
|
5939
|
-
);
|
|
6134
|
+
this.logWarn("Config parse failed \u2014 falling back to defaults", {
|
|
6135
|
+
event: "config.parse_failed",
|
|
6136
|
+
path: file
|
|
6137
|
+
});
|
|
5940
6138
|
return {};
|
|
5941
6139
|
}
|
|
5942
6140
|
this.jsonCache.set(file, { mtimeMs, value: structuredClone(parsed.value) });
|
|
@@ -6062,7 +6260,7 @@ var DEFAULT_CONFIG_MIGRATIONS = [];
|
|
|
6062
6260
|
// src/storage/recovery-lock.ts
|
|
6063
6261
|
import * as fsp7 from "node:fs/promises";
|
|
6064
6262
|
import * as os2 from "node:os";
|
|
6065
|
-
import * as
|
|
6263
|
+
import * as path15 from "node:path";
|
|
6066
6264
|
var LOCK_FILE = "active.json";
|
|
6067
6265
|
var DEFAULT_MAX_AGE_MS = 24 * 60 * 60 * 1e3;
|
|
6068
6266
|
var RecoveryLock = class {
|
|
@@ -6073,7 +6271,7 @@ var RecoveryLock = class {
|
|
|
6073
6271
|
sessionStore;
|
|
6074
6272
|
probe;
|
|
6075
6273
|
constructor(opts) {
|
|
6076
|
-
this.file =
|
|
6274
|
+
this.file = path15.join(opts.dir, LOCK_FILE);
|
|
6077
6275
|
this.pid = opts.pid ?? process.pid;
|
|
6078
6276
|
this.hostname = opts.hostname ?? os2.hostname();
|
|
6079
6277
|
this.maxAgeMs = opts.maxAgeMs ?? DEFAULT_MAX_AGE_MS;
|
|
@@ -6134,7 +6332,7 @@ var RecoveryLock = class {
|
|
|
6134
6332
|
* null return before calling this.
|
|
6135
6333
|
*/
|
|
6136
6334
|
async write(sessionId) {
|
|
6137
|
-
await ensureDir(
|
|
6335
|
+
await ensureDir(path15.dirname(this.file));
|
|
6138
6336
|
const lock = {
|
|
6139
6337
|
v: 1,
|
|
6140
6338
|
sessionId,
|
|
@@ -6202,7 +6400,7 @@ function defaultIsPidAlive(pid) {
|
|
|
6202
6400
|
}
|
|
6203
6401
|
|
|
6204
6402
|
// src/storage/session-reader.ts
|
|
6205
|
-
import * as
|
|
6403
|
+
import * as fs8 from "node:fs/promises";
|
|
6206
6404
|
var DefaultSessionReader = class _DefaultSessionReader {
|
|
6207
6405
|
store;
|
|
6208
6406
|
eventCache = /* @__PURE__ */ new Map();
|
|
@@ -6220,7 +6418,7 @@ var DefaultSessionReader = class _DefaultSessionReader {
|
|
|
6220
6418
|
const sessionPath = sessionScopedPath(rootDir, sessionId, ".jsonl");
|
|
6221
6419
|
let mtimeMs = null;
|
|
6222
6420
|
try {
|
|
6223
|
-
const stat13 = await
|
|
6421
|
+
const stat13 = await fs8.stat(sessionPath);
|
|
6224
6422
|
mtimeMs = stat13.mtimeMs;
|
|
6225
6423
|
} catch {
|
|
6226
6424
|
this.eventCache.delete(sessionId);
|
|
@@ -6584,7 +6782,7 @@ function renderPlainText(meta, events) {
|
|
|
6584
6782
|
|
|
6585
6783
|
// src/storage/annotations-store.ts
|
|
6586
6784
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
6587
|
-
import * as
|
|
6785
|
+
import * as fs9 from "node:fs/promises";
|
|
6588
6786
|
var FILE_VERSION = 1;
|
|
6589
6787
|
var MAX_TEXT_LENGTH = 2e3;
|
|
6590
6788
|
var MAX_ANNOTATIONS = 1e3;
|
|
@@ -6811,7 +7009,7 @@ var AnnotationsStore = class {
|
|
|
6811
7009
|
const fp = this.filePath(sessionId);
|
|
6812
7010
|
let raw;
|
|
6813
7011
|
try {
|
|
6814
|
-
raw = await
|
|
7012
|
+
raw = await fs9.readFile(fp, "utf8");
|
|
6815
7013
|
} catch (err) {
|
|
6816
7014
|
if (err.code === "ENOENT") return null;
|
|
6817
7015
|
throw err;
|
|
@@ -6848,8 +7046,8 @@ var AnnotationsStore = class {
|
|
|
6848
7046
|
};
|
|
6849
7047
|
|
|
6850
7048
|
// src/storage/replay-log-store.ts
|
|
6851
|
-
import * as
|
|
6852
|
-
import * as
|
|
7049
|
+
import * as fs10 from "node:fs/promises";
|
|
7050
|
+
import * as path16 from "node:path";
|
|
6853
7051
|
|
|
6854
7052
|
// src/replay/hash.ts
|
|
6855
7053
|
import { createHash as createHash5 } from "node:crypto";
|
|
@@ -6938,12 +7136,12 @@ var ReplayLogStore = class {
|
|
|
6938
7136
|
const line = JSON.stringify(entry) + "\n";
|
|
6939
7137
|
let offset2 = 0;
|
|
6940
7138
|
try {
|
|
6941
|
-
const stat13 = await
|
|
7139
|
+
const stat13 = await fs10.stat(fp);
|
|
6942
7140
|
offset2 = stat13.size;
|
|
6943
7141
|
} catch (err) {
|
|
6944
7142
|
if (err.code !== "ENOENT") throw err;
|
|
6945
7143
|
}
|
|
6946
|
-
await
|
|
7144
|
+
await fs10.appendFile(fp, line, "utf8");
|
|
6947
7145
|
cache.set(hash, { entry, offset: offset2, length: Buffer.byteLength(line, "utf8") });
|
|
6948
7146
|
this.diskCount.set(input.sessionId, currentCount + 1);
|
|
6949
7147
|
this.events?.emit("storage.write", {
|
|
@@ -7071,7 +7269,7 @@ var ReplayLogStore = class {
|
|
|
7071
7269
|
const scan = async (dir, prefix, depth) => {
|
|
7072
7270
|
let entries;
|
|
7073
7271
|
try {
|
|
7074
|
-
entries = await
|
|
7272
|
+
entries = await fs10.readdir(dir, { withFileTypes: true });
|
|
7075
7273
|
} catch (err) {
|
|
7076
7274
|
if (depth === 0 && err.code !== "ENOENT") {
|
|
7077
7275
|
console.warn(JSON.stringify({
|
|
@@ -7087,13 +7285,13 @@ var ReplayLogStore = class {
|
|
|
7087
7285
|
for (const entry of entries) {
|
|
7088
7286
|
if (entry.name.startsWith(".")) continue;
|
|
7089
7287
|
if (entry.isDirectory()) {
|
|
7090
|
-
if (depth === 0) await scan(
|
|
7288
|
+
if (depth === 0) await scan(path16.join(dir, entry.name), entry.name, depth + 1);
|
|
7091
7289
|
continue;
|
|
7092
7290
|
}
|
|
7093
7291
|
if (!entry.isFile() || !entry.name.endsWith(".replay.jsonl")) continue;
|
|
7094
7292
|
const base = entry.name.slice(0, -".replay.jsonl".length);
|
|
7095
7293
|
const sessionId = prefix ? `${prefix}/${base}` : base;
|
|
7096
|
-
const fp =
|
|
7294
|
+
const fp = path16.join(dir, entry.name);
|
|
7097
7295
|
out.push({
|
|
7098
7296
|
sessionId,
|
|
7099
7297
|
entryCount: await this.countEntries(fp),
|
|
@@ -7109,7 +7307,7 @@ var ReplayLogStore = class {
|
|
|
7109
7307
|
return sessionScopedPath(this.dir, sessionId, ".replay.jsonl");
|
|
7110
7308
|
}
|
|
7111
7309
|
async countEntries(filePath) {
|
|
7112
|
-
const handle = await
|
|
7310
|
+
const handle = await fs10.open(filePath, "r");
|
|
7113
7311
|
const buffer = Buffer.allocUnsafe(64 * 1024);
|
|
7114
7312
|
let count = 0;
|
|
7115
7313
|
let hasNonWhitespace2 = false;
|
|
@@ -7150,7 +7348,7 @@ var ReplayLogStore = class {
|
|
|
7150
7348
|
async readAll(sessionId) {
|
|
7151
7349
|
const fp = this.filePath(sessionId);
|
|
7152
7350
|
try {
|
|
7153
|
-
const raw = await
|
|
7351
|
+
const raw = await fs10.readFile(fp, "utf8");
|
|
7154
7352
|
const out = [];
|
|
7155
7353
|
for (const line of raw.split("\n")) {
|
|
7156
7354
|
if (!line.trim()) continue;
|
|
@@ -7186,7 +7384,7 @@ var ReplayLogStore = class {
|
|
|
7186
7384
|
const fp = this.filePath(sessionId);
|
|
7187
7385
|
cache = /* @__PURE__ */ new Map();
|
|
7188
7386
|
try {
|
|
7189
|
-
const handle = await
|
|
7387
|
+
const handle = await fs10.open(fp, "r");
|
|
7190
7388
|
const CHUNK = 64 * 1024;
|
|
7191
7389
|
const buffer = Buffer.alloc(CHUNK);
|
|
7192
7390
|
let leftover = "";
|
|
@@ -7237,7 +7435,7 @@ var ReplayLogStore = class {
|
|
|
7237
7435
|
async hydrateEntry(sessionId, hash, location) {
|
|
7238
7436
|
if (location.entry) return location.entry;
|
|
7239
7437
|
const fp = this.filePath(sessionId);
|
|
7240
|
-
const handle = await
|
|
7438
|
+
const handle = await fs10.open(fp, "r");
|
|
7241
7439
|
try {
|
|
7242
7440
|
const buffer = Buffer.alloc(location.length);
|
|
7243
7441
|
const { bytesRead } = await handle.read(buffer, 0, location.length, location.offset);
|
|
@@ -7264,8 +7462,8 @@ var ReplayLogStore = class {
|
|
|
7264
7462
|
};
|
|
7265
7463
|
|
|
7266
7464
|
// src/storage/session-recovery.ts
|
|
7267
|
-
import * as
|
|
7268
|
-
import * as
|
|
7465
|
+
import * as fs11 from "node:fs/promises";
|
|
7466
|
+
import * as path17 from "node:path";
|
|
7269
7467
|
var SessionRecovery = class {
|
|
7270
7468
|
constructor(dir) {
|
|
7271
7469
|
this.dir = dir;
|
|
@@ -7291,7 +7489,7 @@ var SessionRecovery = class {
|
|
|
7291
7489
|
const fp = this.filePath(sessionId);
|
|
7292
7490
|
let stat13;
|
|
7293
7491
|
try {
|
|
7294
|
-
stat13 = await
|
|
7492
|
+
stat13 = await fs11.stat(fp);
|
|
7295
7493
|
} catch (err) {
|
|
7296
7494
|
if (err.code === "ENOENT") return null;
|
|
7297
7495
|
return null;
|
|
@@ -7325,7 +7523,7 @@ var SessionRecovery = class {
|
|
|
7325
7523
|
const fp = this.filePath(sessionId);
|
|
7326
7524
|
let raw;
|
|
7327
7525
|
try {
|
|
7328
|
-
raw = await
|
|
7526
|
+
raw = await fs11.readFile(fp, "utf8");
|
|
7329
7527
|
} catch (err) {
|
|
7330
7528
|
if (err.code === "ENOENT") return null;
|
|
7331
7529
|
return null;
|
|
@@ -7370,7 +7568,7 @@ var SessionRecovery = class {
|
|
|
7370
7568
|
const collect = async (dir, prefix, depth) => {
|
|
7371
7569
|
let entries;
|
|
7372
7570
|
try {
|
|
7373
|
-
entries = await
|
|
7571
|
+
entries = await fs11.readdir(dir, { withFileTypes: true });
|
|
7374
7572
|
} catch {
|
|
7375
7573
|
return;
|
|
7376
7574
|
}
|
|
@@ -7380,7 +7578,7 @@ var SessionRecovery = class {
|
|
|
7380
7578
|
continue;
|
|
7381
7579
|
if (entry.isDirectory()) {
|
|
7382
7580
|
if (depth === 0) {
|
|
7383
|
-
await collect(
|
|
7581
|
+
await collect(path17.join(dir, entry.name), entry.name, depth + 1);
|
|
7384
7582
|
}
|
|
7385
7583
|
continue;
|
|
7386
7584
|
}
|
|
@@ -7421,7 +7619,7 @@ function hasNonWhitespace(line) {
|
|
|
7421
7619
|
}
|
|
7422
7620
|
async function scanLatestLifecycleBoundary(filePath, size) {
|
|
7423
7621
|
const CHUNK_SIZE = 64 * 1024;
|
|
7424
|
-
const handle = await
|
|
7622
|
+
const handle = await fs11.open(filePath, "r");
|
|
7425
7623
|
let position = size;
|
|
7426
7624
|
let laterLineFragment = Buffer.alloc(0);
|
|
7427
7625
|
let latestBoundary = null;
|
|
@@ -7460,7 +7658,7 @@ async function scanLatestLifecycleBoundary(filePath, size) {
|
|
|
7460
7658
|
|
|
7461
7659
|
// src/storage/tool-audit-log.ts
|
|
7462
7660
|
import { createHash as createHash6, randomUUID as randomUUID4 } from "node:crypto";
|
|
7463
|
-
import * as
|
|
7661
|
+
import * as fs12 from "node:fs/promises";
|
|
7464
7662
|
var GENESIS_PREV = "0".repeat(64);
|
|
7465
7663
|
var DEFAULT_FSYNC_EVERY = 100;
|
|
7466
7664
|
var ToolAuditLog = class {
|
|
@@ -7530,9 +7728,9 @@ var ToolAuditLog = class {
|
|
|
7530
7728
|
isError: input.isError,
|
|
7531
7729
|
index
|
|
7532
7730
|
};
|
|
7533
|
-
await
|
|
7731
|
+
await fs12.appendFile(fp, JSON.stringify(entry) + "\n", "utf8");
|
|
7534
7732
|
try {
|
|
7535
|
-
const st = await
|
|
7733
|
+
const st = await fs12.stat(fp);
|
|
7536
7734
|
this.tailStat.set(input.sessionId, { mtimeMs: st.mtimeMs, size: st.size });
|
|
7537
7735
|
} catch {
|
|
7538
7736
|
}
|
|
@@ -7579,7 +7777,7 @@ var ToolAuditLog = class {
|
|
|
7579
7777
|
const cachedStat = this.tailStat.get(sessionId);
|
|
7580
7778
|
if (cachedHash !== void 0 && cachedIndex !== void 0 && cachedStat) {
|
|
7581
7779
|
try {
|
|
7582
|
-
const st = await
|
|
7780
|
+
const st = await fs12.stat(fp);
|
|
7583
7781
|
if (st.mtimeMs === cachedStat.mtimeMs && st.size === cachedStat.size) {
|
|
7584
7782
|
return { prevHash: cachedHash, nextIndex: cachedIndex };
|
|
7585
7783
|
}
|
|
@@ -7600,7 +7798,7 @@ var ToolAuditLog = class {
|
|
|
7600
7798
|
this.tailHash.set(sessionId, prevHash);
|
|
7601
7799
|
this.tailIndex.set(sessionId, nextIndex);
|
|
7602
7800
|
try {
|
|
7603
|
-
const st = await
|
|
7801
|
+
const st = await fs12.stat(fp);
|
|
7604
7802
|
this.tailStat.set(sessionId, { mtimeMs: st.mtimeMs, size: st.size });
|
|
7605
7803
|
} catch {
|
|
7606
7804
|
}
|
|
@@ -7721,7 +7919,7 @@ var ToolAuditLog = class {
|
|
|
7721
7919
|
async readAll(sessionId) {
|
|
7722
7920
|
const fp = this.filePath(sessionId);
|
|
7723
7921
|
try {
|
|
7724
|
-
const raw = await
|
|
7922
|
+
const raw = await fs12.readFile(fp, "utf8");
|
|
7725
7923
|
const out = [];
|
|
7726
7924
|
for (const line of raw.split("\n")) {
|
|
7727
7925
|
if (!line.trim()) continue;
|
|
@@ -7759,7 +7957,7 @@ var ToolAuditLog = class {
|
|
|
7759
7957
|
}
|
|
7760
7958
|
async sync(sessionId, fp) {
|
|
7761
7959
|
try {
|
|
7762
|
-
const fh = await
|
|
7960
|
+
const fh = await fs12.open(fp, "r+");
|
|
7763
7961
|
try {
|
|
7764
7962
|
await fh.sync();
|
|
7765
7963
|
} finally {
|
|
@@ -7898,8 +8096,8 @@ var SessionAnalyzer = class {
|
|
|
7898
8096
|
|
|
7899
8097
|
// src/session-registry.ts
|
|
7900
8098
|
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
7901
|
-
import * as
|
|
7902
|
-
import * as
|
|
8099
|
+
import * as fs13 from "node:fs/promises";
|
|
8100
|
+
import * as path18 from "node:path";
|
|
7903
8101
|
var REGISTRY_FILE = "session-registry.json";
|
|
7904
8102
|
var HEARTBEAT_INTERVAL_MS = 5e3;
|
|
7905
8103
|
var STALE_TIMEOUT_MS = 3e4;
|
|
@@ -7938,7 +8136,7 @@ var SessionRegistry = class {
|
|
|
7938
8136
|
*/
|
|
7939
8137
|
lastEntry = null;
|
|
7940
8138
|
constructor(globalRoot) {
|
|
7941
|
-
this.filePath =
|
|
8139
|
+
this.filePath = path18.join(globalRoot, REGISTRY_FILE);
|
|
7942
8140
|
}
|
|
7943
8141
|
// ── Public API ──────────────────────────────────────────────────────────
|
|
7944
8142
|
/**
|
|
@@ -8094,7 +8292,7 @@ var SessionRegistry = class {
|
|
|
8094
8292
|
}
|
|
8095
8293
|
async readAndPrune() {
|
|
8096
8294
|
try {
|
|
8097
|
-
const raw = await
|
|
8295
|
+
const raw = await fs13.readFile(this.filePath, "utf8");
|
|
8098
8296
|
const registry = parseRegistry(raw);
|
|
8099
8297
|
const now = Date.now();
|
|
8100
8298
|
let pruned = false;
|
|
@@ -8147,11 +8345,11 @@ var SessionRegistry = class {
|
|
|
8147
8345
|
const retryDelayMs = 20;
|
|
8148
8346
|
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
8149
8347
|
try {
|
|
8150
|
-
await
|
|
8151
|
-
let lockHandle = await
|
|
8348
|
+
await fs13.mkdir(path18.dirname(this.filePath), { recursive: true });
|
|
8349
|
+
let lockHandle = await fs13.open(lockPath, "wx").catch(() => null);
|
|
8152
8350
|
if (!lockHandle) {
|
|
8153
8351
|
if (await this.breakStaleLock(lockPath)) {
|
|
8154
|
-
lockHandle = await
|
|
8352
|
+
lockHandle = await fs13.open(lockPath, "wx").catch(() => null);
|
|
8155
8353
|
}
|
|
8156
8354
|
if (!lockHandle) {
|
|
8157
8355
|
await new Promise((r) => setTimeout(r, retryDelayMs * (attempt + 1)));
|
|
@@ -8160,14 +8358,14 @@ var SessionRegistry = class {
|
|
|
8160
8358
|
}
|
|
8161
8359
|
try {
|
|
8162
8360
|
await lockHandle.writeFile(String(process.pid)).catch(() => void 0);
|
|
8163
|
-
const raw = await
|
|
8361
|
+
const raw = await fs13.readFile(this.filePath, "utf8").catch(() => "{}");
|
|
8164
8362
|
const registry = parseRegistry(raw);
|
|
8165
8363
|
fn(registry);
|
|
8166
8364
|
await this.writeAtomicLocked(registry);
|
|
8167
8365
|
return;
|
|
8168
8366
|
} finally {
|
|
8169
8367
|
await lockHandle.close();
|
|
8170
|
-
await
|
|
8368
|
+
await fs13.unlink(lockPath).catch(() => void 0);
|
|
8171
8369
|
}
|
|
8172
8370
|
} catch {
|
|
8173
8371
|
return;
|
|
@@ -8184,16 +8382,16 @@ var SessionRegistry = class {
|
|
|
8184
8382
|
async breakStaleLock(lockPath) {
|
|
8185
8383
|
try {
|
|
8186
8384
|
const [stat13, content] = await Promise.all([
|
|
8187
|
-
|
|
8385
|
+
fs13.stat(lockPath),
|
|
8188
8386
|
/* v8 ignore start -- best-effort lock-content read; .catch only fires if the lock vanished */
|
|
8189
|
-
|
|
8387
|
+
fs13.readFile(lockPath, "utf8").catch(() => "")
|
|
8190
8388
|
/* v8 ignore stop */
|
|
8191
8389
|
]);
|
|
8192
8390
|
const ageMs = Date.now() - stat13.mtimeMs;
|
|
8193
8391
|
const ownerPid = Number.parseInt(content.trim(), 10);
|
|
8194
8392
|
const ownerDead = Number.isInteger(ownerPid) && ownerPid > 0 && ownerPid !== process.pid && !pidAlive(ownerPid);
|
|
8195
8393
|
if (ownerDead || ageMs > STALE_LOCK_MS) {
|
|
8196
|
-
await
|
|
8394
|
+
await fs13.unlink(lockPath).catch(() => void 0);
|
|
8197
8395
|
return true;
|
|
8198
8396
|
}
|
|
8199
8397
|
return false;
|
|
@@ -8211,41 +8409,41 @@ var SessionRegistry = class {
|
|
|
8211
8409
|
await this.writeAtomicFile(registry);
|
|
8212
8410
|
}
|
|
8213
8411
|
async writeAtomicFile(registry) {
|
|
8214
|
-
const tmp =
|
|
8215
|
-
|
|
8216
|
-
`.${
|
|
8412
|
+
const tmp = path18.join(
|
|
8413
|
+
path18.dirname(this.filePath),
|
|
8414
|
+
`.${path18.basename(this.filePath)}.${randomUUID5().slice(0, 8)}.tmp`
|
|
8217
8415
|
);
|
|
8218
8416
|
try {
|
|
8219
|
-
const handle = await
|
|
8417
|
+
const handle = await fs13.open(tmp, "w");
|
|
8220
8418
|
try {
|
|
8221
8419
|
await handle.writeFile(JSON.stringify(registry, null, 2), "utf8");
|
|
8222
8420
|
await handle.sync().catch(() => void 0);
|
|
8223
8421
|
} finally {
|
|
8224
8422
|
await handle.close();
|
|
8225
8423
|
}
|
|
8226
|
-
await
|
|
8424
|
+
await fs13.rename(tmp, this.filePath);
|
|
8227
8425
|
} catch (err) {
|
|
8228
|
-
await
|
|
8426
|
+
await fs13.unlink(tmp).catch(() => void 0);
|
|
8229
8427
|
throw err;
|
|
8230
8428
|
}
|
|
8231
8429
|
}
|
|
8232
8430
|
async pruneStaleTempFiles() {
|
|
8233
8431
|
try {
|
|
8234
|
-
const dir =
|
|
8235
|
-
const base =
|
|
8432
|
+
const dir = path18.dirname(this.filePath);
|
|
8433
|
+
const base = path18.basename(this.filePath);
|
|
8236
8434
|
const now = Date.now();
|
|
8237
8435
|
const stale = [];
|
|
8238
|
-
for (const name of await
|
|
8436
|
+
for (const name of await fs13.readdir(dir)) {
|
|
8239
8437
|
const isTemp = (name.startsWith(`${base}.`) || name.startsWith(`.${base}.`)) && name.endsWith(".tmp");
|
|
8240
8438
|
if (!isTemp) continue;
|
|
8241
|
-
const stat13 = await
|
|
8439
|
+
const stat13 = await fs13.stat(path18.join(dir, name)).catch(() => null);
|
|
8242
8440
|
if (!stat13) continue;
|
|
8243
8441
|
if (now - stat13.mtimeMs > STALE_TMP_MS) stale.push({ name, mtimeMs: stat13.mtimeMs });
|
|
8244
8442
|
}
|
|
8245
8443
|
stale.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
8246
8444
|
await Promise.all(
|
|
8247
8445
|
stale.slice(MAX_STALE_TMP_FILES).map(async ({ name }) => {
|
|
8248
|
-
await
|
|
8446
|
+
await fs13.unlink(path18.join(dir, name)).catch(() => void 0);
|
|
8249
8447
|
})
|
|
8250
8448
|
);
|
|
8251
8449
|
} catch {
|
|
@@ -8980,8 +9178,8 @@ var AgentStatusTracker = class {
|
|
|
8980
9178
|
};
|
|
8981
9179
|
|
|
8982
9180
|
// src/fleet-notifier.ts
|
|
8983
|
-
import * as
|
|
8984
|
-
import * as
|
|
9181
|
+
import * as fs14 from "node:fs/promises";
|
|
9182
|
+
import * as path19 from "node:path";
|
|
8985
9183
|
var INSTANCES_FILE = "webui-instances.json";
|
|
8986
9184
|
var DISCOVERY_TTL_MS = 2500;
|
|
8987
9185
|
var COALESCE_MS = 50;
|
|
@@ -8996,7 +9194,7 @@ function pidAlive2(pid) {
|
|
|
8996
9194
|
}
|
|
8997
9195
|
}
|
|
8998
9196
|
function normRoot(root) {
|
|
8999
|
-
const resolved =
|
|
9197
|
+
const resolved = path19.resolve(root);
|
|
9000
9198
|
return process.platform === "win32" ? resolved.toLowerCase() : resolved;
|
|
9001
9199
|
}
|
|
9002
9200
|
var FleetNotifier = class {
|
|
@@ -9043,7 +9241,7 @@ var FleetNotifier = class {
|
|
|
9043
9241
|
}
|
|
9044
9242
|
async discover() {
|
|
9045
9243
|
try {
|
|
9046
|
-
const raw = await
|
|
9244
|
+
const raw = await fs14.readFile(path19.join(this.baseDir, INSTANCES_FILE), "utf8");
|
|
9047
9245
|
const data = JSON.parse(raw);
|
|
9048
9246
|
const list = Array.isArray(data?.instances) ? data.instances : [];
|
|
9049
9247
|
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) => {
|
|
@@ -9064,7 +9262,7 @@ async function defaultPost(url) {
|
|
|
9064
9262
|
|
|
9065
9263
|
// src/storage/session-rewinder.ts
|
|
9066
9264
|
import * as fsp8 from "node:fs/promises";
|
|
9067
|
-
import * as
|
|
9265
|
+
import * as path20 from "node:path";
|
|
9068
9266
|
var DefaultSessionRewinder = class {
|
|
9069
9267
|
constructor(sessionsDir, projectRoot) {
|
|
9070
9268
|
this.sessionsDir = sessionsDir;
|
|
@@ -9202,10 +9400,10 @@ async function revertSnapshots(snapshots, projectRoot) {
|
|
|
9202
9400
|
for (const snapshot of [...snapshots].reverse()) {
|
|
9203
9401
|
for (const file of [...snapshot.files].reverse()) {
|
|
9204
9402
|
try {
|
|
9205
|
-
const absPath =
|
|
9206
|
-
const root =
|
|
9207
|
-
const rel =
|
|
9208
|
-
if (rel.startsWith("..") ||
|
|
9403
|
+
const absPath = path20.resolve(file.path);
|
|
9404
|
+
const root = path20.resolve(projectRoot);
|
|
9405
|
+
const rel = path20.relative(root, absPath);
|
|
9406
|
+
if (rel.startsWith("..") || path20.isAbsolute(rel)) {
|
|
9209
9407
|
errors.push(`${file.path}: path resolves outside project root \u2014 skipping`);
|
|
9210
9408
|
continue;
|
|
9211
9409
|
}
|
|
@@ -10730,8 +10928,8 @@ function normalizeTitle(value) {
|
|
|
10730
10928
|
|
|
10731
10929
|
// src/storage/prompt-store.ts
|
|
10732
10930
|
import { createHash as createHash7 } from "node:crypto";
|
|
10733
|
-
import * as
|
|
10734
|
-
import * as
|
|
10931
|
+
import * as fs15 from "node:fs/promises";
|
|
10932
|
+
import * as path21 from "node:path";
|
|
10735
10933
|
var SCHEMA_VERSION = 2;
|
|
10736
10934
|
function promptChecksum(content) {
|
|
10737
10935
|
return createHash7("sha256").update(content, "utf8").digest("hex");
|
|
@@ -10795,12 +10993,12 @@ var DefaultPromptStore = class {
|
|
|
10795
10993
|
await ensureDir(this.dir);
|
|
10796
10994
|
const entries = [];
|
|
10797
10995
|
try {
|
|
10798
|
-
const files = await
|
|
10996
|
+
const files = await fs15.readdir(this.dir);
|
|
10799
10997
|
for (const file of files) {
|
|
10800
10998
|
if (!file.endsWith(".json")) continue;
|
|
10801
10999
|
try {
|
|
10802
11000
|
const raw = JSON.parse(
|
|
10803
|
-
await
|
|
11001
|
+
await fs15.readFile(path21.join(this.dir, file), "utf8")
|
|
10804
11002
|
);
|
|
10805
11003
|
const migrated = migratePromptEntry(raw.entry);
|
|
10806
11004
|
if (migrated) entries.push(migrated);
|
|
@@ -10814,9 +11012,9 @@ var DefaultPromptStore = class {
|
|
|
10814
11012
|
);
|
|
10815
11013
|
}
|
|
10816
11014
|
async get(id) {
|
|
10817
|
-
const file =
|
|
11015
|
+
const file = path21.join(this.dir, `${id}.json`);
|
|
10818
11016
|
try {
|
|
10819
|
-
const raw = JSON.parse(await
|
|
11017
|
+
const raw = JSON.parse(await fs15.readFile(file, "utf8"));
|
|
10820
11018
|
return migratePromptEntry(raw.entry);
|
|
10821
11019
|
} catch {
|
|
10822
11020
|
return null;
|
|
@@ -10824,14 +11022,14 @@ var DefaultPromptStore = class {
|
|
|
10824
11022
|
}
|
|
10825
11023
|
async save(entry) {
|
|
10826
11024
|
await ensureDir(this.dir);
|
|
10827
|
-
const file =
|
|
11025
|
+
const file = path21.join(this.dir, `${entry.id}.json`);
|
|
10828
11026
|
const raw = { version: SCHEMA_VERSION, entry };
|
|
10829
11027
|
await atomicWrite(file, JSON.stringify(raw, null, 2));
|
|
10830
11028
|
}
|
|
10831
11029
|
async delete(id) {
|
|
10832
|
-
const file =
|
|
11030
|
+
const file = path21.join(this.dir, `${id}.json`);
|
|
10833
11031
|
try {
|
|
10834
|
-
await
|
|
11032
|
+
await fs15.unlink(file);
|
|
10835
11033
|
return true;
|
|
10836
11034
|
} catch {
|
|
10837
11035
|
return false;
|
|
@@ -10870,8 +11068,8 @@ var DefaultPromptStore = class {
|
|
|
10870
11068
|
};
|
|
10871
11069
|
|
|
10872
11070
|
// src/storage/prompt-usage-store.ts
|
|
10873
|
-
import * as
|
|
10874
|
-
import * as
|
|
11071
|
+
import * as fs16 from "node:fs/promises";
|
|
11072
|
+
import * as path22 from "node:path";
|
|
10875
11073
|
var PromptUsageStore = class {
|
|
10876
11074
|
constructor(file) {
|
|
10877
11075
|
this.file = file;
|
|
@@ -10879,7 +11077,7 @@ var PromptUsageStore = class {
|
|
|
10879
11077
|
file;
|
|
10880
11078
|
async load() {
|
|
10881
11079
|
try {
|
|
10882
|
-
const raw = JSON.parse(await
|
|
11080
|
+
const raw = JSON.parse(await fs16.readFile(this.file, "utf8"));
|
|
10883
11081
|
if (raw && typeof raw === "object" && raw.usage && typeof raw.usage === "object") {
|
|
10884
11082
|
return raw.usage;
|
|
10885
11083
|
}
|
|
@@ -10892,7 +11090,7 @@ var PromptUsageStore = class {
|
|
|
10892
11090
|
const prev = usage[slug];
|
|
10893
11091
|
const next = { count: (prev?.count ?? 0) + 1, lastUsedAt: at };
|
|
10894
11092
|
usage[slug] = next;
|
|
10895
|
-
await ensureDir(
|
|
11093
|
+
await ensureDir(path22.dirname(this.file));
|
|
10896
11094
|
await atomicWrite(this.file, JSON.stringify({ version: 1, usage }, null, 2));
|
|
10897
11095
|
return next;
|
|
10898
11096
|
}
|
|
@@ -10916,8 +11114,8 @@ var PromptUsageStore = class {
|
|
|
10916
11114
|
};
|
|
10917
11115
|
|
|
10918
11116
|
// src/storage/input-history-store.ts
|
|
10919
|
-
import * as
|
|
10920
|
-
import * as
|
|
11117
|
+
import * as fs17 from "node:fs/promises";
|
|
11118
|
+
import * as path23 from "node:path";
|
|
10921
11119
|
var INPUT_HISTORY_DEFAULT_MAX = 100;
|
|
10922
11120
|
var InputHistoryStore = class {
|
|
10923
11121
|
/**
|
|
@@ -10939,7 +11137,7 @@ var InputHistoryStore = class {
|
|
|
10939
11137
|
*/
|
|
10940
11138
|
async load() {
|
|
10941
11139
|
try {
|
|
10942
|
-
const raw = JSON.parse(await
|
|
11140
|
+
const raw = JSON.parse(await fs17.readFile(this.file, "utf8"));
|
|
10943
11141
|
if (raw && typeof raw === "object" && Array.isArray(raw.entries) && raw.entries.every((e) => typeof e === "string")) {
|
|
10944
11142
|
return raw.entries.slice(0, this.maxEntries);
|
|
10945
11143
|
}
|
|
@@ -10956,7 +11154,7 @@ var InputHistoryStore = class {
|
|
|
10956
11154
|
*/
|
|
10957
11155
|
async save(entries) {
|
|
10958
11156
|
const cleaned = this.scrubAndFilter(entries);
|
|
10959
|
-
await ensureDir(
|
|
11157
|
+
await ensureDir(path23.dirname(this.file));
|
|
10960
11158
|
const payload = {
|
|
10961
11159
|
version: 1,
|
|
10962
11160
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -10966,7 +11164,7 @@ var InputHistoryStore = class {
|
|
|
10966
11164
|
}
|
|
10967
11165
|
/** Truncate the file to an empty entry list (used by /clear). */
|
|
10968
11166
|
async clear() {
|
|
10969
|
-
await ensureDir(
|
|
11167
|
+
await ensureDir(path23.dirname(this.file));
|
|
10970
11168
|
const payload = { version: 1, updatedAt: (/* @__PURE__ */ new Date()).toISOString(), entries: [] };
|
|
10971
11169
|
await atomicWrite(this.file, JSON.stringify(payload, null, 2));
|
|
10972
11170
|
}
|
|
@@ -10996,8 +11194,8 @@ var InputHistoryStore = class {
|
|
|
10996
11194
|
};
|
|
10997
11195
|
|
|
10998
11196
|
// src/storage/cloud-sync.ts
|
|
10999
|
-
import * as
|
|
11000
|
-
import * as
|
|
11197
|
+
import * as fs18 from "node:fs/promises";
|
|
11198
|
+
import * as path24 from "node:path";
|
|
11001
11199
|
import { createHash as createHash8 } from "node:crypto";
|
|
11002
11200
|
var ALL_SYNC_CATEGORIES = ["settings", "skills", "prompts", "memory", "history"];
|
|
11003
11201
|
var CloudSync = class {
|
|
@@ -11005,7 +11203,7 @@ var CloudSync = class {
|
|
|
11005
11203
|
this.paths = paths;
|
|
11006
11204
|
this.getConfig = getConfig;
|
|
11007
11205
|
this.setConfig = setConfig;
|
|
11008
|
-
this.statePath =
|
|
11206
|
+
this.statePath = path24.join(paths.globalRoot, "sync-state.json");
|
|
11009
11207
|
}
|
|
11010
11208
|
paths;
|
|
11011
11209
|
getConfig;
|
|
@@ -11080,7 +11278,7 @@ var CloudSync = class {
|
|
|
11080
11278
|
lastSyncedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
11081
11279
|
localRev: rev
|
|
11082
11280
|
};
|
|
11083
|
-
await
|
|
11281
|
+
await fs18.writeFile(this.statePath, JSON.stringify(syncState, null, 2), "utf8");
|
|
11084
11282
|
this.state = syncState;
|
|
11085
11283
|
return {
|
|
11086
11284
|
ok: true,
|
|
@@ -11112,8 +11310,8 @@ var CloudSync = class {
|
|
|
11112
11310
|
const rel = segments.slice(2).join("/");
|
|
11113
11311
|
const destPath = resolvePulledCategoryPath(cat, localPath, rel, entry.path);
|
|
11114
11312
|
const blobData = await this.getBlob(token, owner, repoName, entry.sha);
|
|
11115
|
-
await
|
|
11116
|
-
await
|
|
11313
|
+
await fs18.mkdir(path24.dirname(destPath), { recursive: true });
|
|
11314
|
+
await fs18.writeFile(destPath, Buffer.from(blobData, "base64"));
|
|
11117
11315
|
}
|
|
11118
11316
|
const localRev = await this.hashLocalCategories(cfg.categories);
|
|
11119
11317
|
const syncState = {
|
|
@@ -11122,7 +11320,7 @@ var CloudSync = class {
|
|
|
11122
11320
|
lastSyncedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
11123
11321
|
localRev
|
|
11124
11322
|
};
|
|
11125
|
-
await
|
|
11323
|
+
await fs18.writeFile(this.statePath, JSON.stringify(syncState, null, 2), "utf8");
|
|
11126
11324
|
this.state = syncState;
|
|
11127
11325
|
return {
|
|
11128
11326
|
ok: true,
|
|
@@ -11141,7 +11339,7 @@ var CloudSync = class {
|
|
|
11141
11339
|
}
|
|
11142
11340
|
async loadState() {
|
|
11143
11341
|
try {
|
|
11144
|
-
const raw = await
|
|
11342
|
+
const raw = await fs18.readFile(this.statePath, "utf8");
|
|
11145
11343
|
this.state = JSON.parse(raw);
|
|
11146
11344
|
} catch {
|
|
11147
11345
|
this.state = null;
|
|
@@ -11219,17 +11417,17 @@ var CloudSync = class {
|
|
|
11219
11417
|
const localPath = this.categoryToPath(cat);
|
|
11220
11418
|
if (!localPath) continue;
|
|
11221
11419
|
try {
|
|
11222
|
-
const stat13 = await
|
|
11420
|
+
const stat13 = await fs18.stat(localPath);
|
|
11223
11421
|
if (stat13.isDirectory()) {
|
|
11224
11422
|
const files = await this.walkDir(localPath, localPath);
|
|
11225
11423
|
for (const file of files) {
|
|
11226
|
-
const content = await
|
|
11227
|
-
const rel =
|
|
11424
|
+
const content = await fs18.readFile(file, "utf8");
|
|
11425
|
+
const rel = path24.relative(localPath, file).replace(/\\/g, "/");
|
|
11228
11426
|
entries.push({ path: `data/${cat}/${rel}`, content, mode: "100644" });
|
|
11229
11427
|
hashes.push(content);
|
|
11230
11428
|
}
|
|
11231
11429
|
} else {
|
|
11232
|
-
const content = await
|
|
11430
|
+
const content = await fs18.readFile(localPath, "utf8");
|
|
11233
11431
|
entries.push({ path: `data/${cat}`, content, mode: "100644" });
|
|
11234
11432
|
hashes.push(content);
|
|
11235
11433
|
}
|
|
@@ -11245,15 +11443,15 @@ var CloudSync = class {
|
|
|
11245
11443
|
const localPath = this.categoryToPath(cat);
|
|
11246
11444
|
if (!localPath) continue;
|
|
11247
11445
|
try {
|
|
11248
|
-
const stat13 = await
|
|
11446
|
+
const stat13 = await fs18.stat(localPath);
|
|
11249
11447
|
if (stat13.isDirectory()) {
|
|
11250
11448
|
const files = await this.walkDir(localPath, localPath);
|
|
11251
11449
|
for (const file of files) {
|
|
11252
|
-
const content = await
|
|
11450
|
+
const content = await fs18.readFile(file);
|
|
11253
11451
|
hashes.push(content.toString("base64") + file);
|
|
11254
11452
|
}
|
|
11255
11453
|
} else {
|
|
11256
|
-
const content = await
|
|
11454
|
+
const content = await fs18.readFile(localPath);
|
|
11257
11455
|
hashes.push(content.toString("base64") + localPath);
|
|
11258
11456
|
}
|
|
11259
11457
|
} catch {
|
|
@@ -11280,9 +11478,9 @@ var CloudSync = class {
|
|
|
11280
11478
|
}
|
|
11281
11479
|
async walkDir(dir, base) {
|
|
11282
11480
|
const results = [];
|
|
11283
|
-
const entries = await
|
|
11481
|
+
const entries = await fs18.readdir(dir, { withFileTypes: true });
|
|
11284
11482
|
for (const entry of entries) {
|
|
11285
|
-
const full =
|
|
11483
|
+
const full = path24.join(dir, entry.name);
|
|
11286
11484
|
if (entry.isDirectory()) {
|
|
11287
11485
|
results.push(...await this.walkDir(full, base));
|
|
11288
11486
|
} else {
|
|
@@ -11304,9 +11502,9 @@ function resolvePulledCategoryPath(cat, localPath, rel, remotePath) {
|
|
|
11304
11502
|
return localPath;
|
|
11305
11503
|
}
|
|
11306
11504
|
if (!rel) return localPath;
|
|
11307
|
-
const normalizedRel =
|
|
11308
|
-
const traversesUp = normalizedRel === ".." || normalizedRel.startsWith(`..${
|
|
11309
|
-
if (
|
|
11505
|
+
const normalizedRel = path24.normalize(rel);
|
|
11506
|
+
const traversesUp = normalizedRel === ".." || normalizedRel.startsWith(`..${path24.sep}`);
|
|
11507
|
+
if (path24.isAbsolute(normalizedRel) || traversesUp) {
|
|
11310
11508
|
throw new FsError({
|
|
11311
11509
|
message: `Refusing CloudSync path traversal: ${remotePath}`,
|
|
11312
11510
|
code: ERROR_CODES.FS_DELETE_FAILED,
|
|
@@ -11314,10 +11512,10 @@ function resolvePulledCategoryPath(cat, localPath, rel, remotePath) {
|
|
|
11314
11512
|
context: { reason: "path_traversal", normalizedRel }
|
|
11315
11513
|
});
|
|
11316
11514
|
}
|
|
11317
|
-
const dest =
|
|
11318
|
-
const root =
|
|
11319
|
-
const
|
|
11320
|
-
if (
|
|
11515
|
+
const dest = path24.resolve(localPath, normalizedRel);
|
|
11516
|
+
const root = path24.resolve(localPath);
|
|
11517
|
+
const relative7 = path24.relative(root, dest);
|
|
11518
|
+
if (relative7.startsWith("..") || path24.isAbsolute(relative7)) {
|
|
11321
11519
|
throw new FsError({
|
|
11322
11520
|
message: `Refusing CloudSync path outside category root: ${remotePath}`,
|
|
11323
11521
|
code: ERROR_CODES.FS_DELETE_FAILED,
|
|
@@ -11546,6 +11744,7 @@ export {
|
|
|
11546
11744
|
parseEntries,
|
|
11547
11745
|
parseProgressFromText,
|
|
11548
11746
|
promptChecksum,
|
|
11747
|
+
readProviderSnapshot,
|
|
11549
11748
|
recomputeGoalProgress,
|
|
11550
11749
|
recordProgress,
|
|
11551
11750
|
removePlanItem,
|