@askexenow/exe-os 0.9.53 → 0.9.55
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/bin/backfill-conversations.js +103 -0
- package/dist/bin/backfill-responses.js +103 -0
- package/dist/bin/backfill-vectors.js +103 -0
- package/dist/bin/cleanup-stale-review-tasks.js +103 -0
- package/dist/bin/cli.js +121 -10
- package/dist/bin/exe-assign.js +103 -0
- package/dist/bin/exe-boot.js +96 -10
- package/dist/bin/exe-call.js +25 -0
- package/dist/bin/exe-cloud.js +31 -10
- package/dist/bin/exe-dispatch.js +103 -0
- package/dist/bin/exe-doctor.js +152 -3
- package/dist/bin/exe-export-behaviors.js +103 -0
- package/dist/bin/exe-forget.js +103 -0
- package/dist/bin/exe-gateway.js +103 -0
- package/dist/bin/exe-heartbeat.js +103 -0
- package/dist/bin/exe-kill.js +103 -0
- package/dist/bin/exe-launch-agent.js +103 -0
- package/dist/bin/exe-link.js +31 -10
- package/dist/bin/exe-new-employee.js +25 -0
- package/dist/bin/exe-pending-messages.js +103 -0
- package/dist/bin/exe-pending-notifications.js +103 -0
- package/dist/bin/exe-pending-reviews.js +103 -0
- package/dist/bin/exe-rename.js +103 -0
- package/dist/bin/exe-review.js +103 -0
- package/dist/bin/exe-search.js +103 -0
- package/dist/bin/exe-session-cleanup.js +103 -0
- package/dist/bin/exe-start-codex.js +103 -0
- package/dist/bin/exe-start-opencode.js +103 -0
- package/dist/bin/exe-status.js +103 -0
- package/dist/bin/exe-team.js +103 -0
- package/dist/bin/git-sweep.js +103 -0
- package/dist/bin/graph-backfill.js +103 -0
- package/dist/bin/graph-export.js +103 -0
- package/dist/bin/intercom-check.js +103 -0
- package/dist/bin/scan-tasks.js +103 -0
- package/dist/bin/setup.js +56 -10
- package/dist/bin/shard-migrate.js +103 -0
- package/dist/gateway/index.js +103 -0
- package/dist/hooks/bug-report-worker.js +103 -0
- package/dist/hooks/codex-stop-task-finalizer.js +103 -0
- package/dist/hooks/commit-complete.js +103 -0
- package/dist/hooks/error-recall.js +103 -0
- package/dist/hooks/ingest.js +103 -0
- package/dist/hooks/instructions-loaded.js +103 -0
- package/dist/hooks/notification.js +103 -0
- package/dist/hooks/post-compact.js +103 -0
- package/dist/hooks/post-tool-combined.js +103 -0
- package/dist/hooks/pre-compact.js +103 -0
- package/dist/hooks/pre-tool-use.js +103 -0
- package/dist/hooks/prompt-submit.js +103 -0
- package/dist/hooks/session-end.js +103 -0
- package/dist/hooks/session-start.js +103 -0
- package/dist/hooks/stop.js +103 -0
- package/dist/hooks/subagent-stop.js +103 -0
- package/dist/hooks/summary-worker.js +96 -10
- package/dist/index.js +103 -0
- package/dist/lib/cloud-sync.js +31 -10
- package/dist/lib/employee-templates.js +25 -0
- package/dist/lib/exe-daemon.js +165 -14
- package/dist/lib/hybrid-search.js +103 -0
- package/dist/lib/keychain.js +31 -10
- package/dist/lib/schedules.js +103 -0
- package/dist/lib/store.js +103 -0
- package/dist/mcp/server.js +164 -13
- package/dist/runtime/index.js +103 -0
- package/dist/tui/App.js +96 -10
- package/package.json +1 -1
package/dist/bin/exe-doctor.js
CHANGED
|
@@ -2589,6 +2589,7 @@ var init_database = __esm({
|
|
|
2589
2589
|
// src/lib/shard-manager.ts
|
|
2590
2590
|
var shard_manager_exports = {};
|
|
2591
2591
|
__export(shard_manager_exports, {
|
|
2592
|
+
auditShardHealth: () => auditShardHealth,
|
|
2592
2593
|
disposeShards: () => disposeShards,
|
|
2593
2594
|
ensureShardSchema: () => ensureShardSchema,
|
|
2594
2595
|
getOpenShardCount: () => getOpenShardCount,
|
|
@@ -2655,6 +2656,70 @@ function listShards() {
|
|
|
2655
2656
|
if (!existsSync7(SHARDS_DIR)) return [];
|
|
2656
2657
|
return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
|
|
2657
2658
|
}
|
|
2659
|
+
async function auditShardHealth(options = {}) {
|
|
2660
|
+
if (!_encryptionKey) {
|
|
2661
|
+
throw new Error("Shard manager not initialized. Call initShardManager() first.");
|
|
2662
|
+
}
|
|
2663
|
+
const repair = options.repair === true;
|
|
2664
|
+
const dryRun = options.dryRun === true;
|
|
2665
|
+
const names = listShards();
|
|
2666
|
+
const shards = [];
|
|
2667
|
+
for (const name of names) {
|
|
2668
|
+
const dbPath = path7.join(SHARDS_DIR, `${name}.db`);
|
|
2669
|
+
const stat = statSync2(dbPath);
|
|
2670
|
+
const item = {
|
|
2671
|
+
name,
|
|
2672
|
+
path: dbPath,
|
|
2673
|
+
ok: false,
|
|
2674
|
+
unreadable: false,
|
|
2675
|
+
error: null,
|
|
2676
|
+
size: stat.size,
|
|
2677
|
+
mtime: stat.mtime.toISOString(),
|
|
2678
|
+
memoryCount: null
|
|
2679
|
+
};
|
|
2680
|
+
const client = createClient2({
|
|
2681
|
+
url: `file:${dbPath}`,
|
|
2682
|
+
encryptionKey: _encryptionKey
|
|
2683
|
+
});
|
|
2684
|
+
try {
|
|
2685
|
+
await client.execute("SELECT COUNT(*) as cnt FROM sqlite_schema");
|
|
2686
|
+
const hasMemories = await client.execute(
|
|
2687
|
+
"SELECT COUNT(*) as cnt FROM sqlite_schema WHERE type = 'table' AND name = 'memories'"
|
|
2688
|
+
);
|
|
2689
|
+
if (Number(hasMemories.rows[0]?.cnt ?? 0) > 0) {
|
|
2690
|
+
const mem = await client.execute("SELECT COUNT(*) as cnt FROM memories");
|
|
2691
|
+
item.memoryCount = Number(mem.rows[0]?.cnt ?? 0);
|
|
2692
|
+
}
|
|
2693
|
+
item.ok = true;
|
|
2694
|
+
} catch (err) {
|
|
2695
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2696
|
+
item.error = message;
|
|
2697
|
+
item.unreadable = /SQLITE_NOTADB|file is not a database/i.test(message);
|
|
2698
|
+
if (item.unreadable && repair && !dryRun) {
|
|
2699
|
+
client.close();
|
|
2700
|
+
_shards.delete(name);
|
|
2701
|
+
_shardLastAccess.delete(name);
|
|
2702
|
+
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
2703
|
+
const archivedPath = path7.join(SHARDS_DIR, `${name}.db.broken-${stamp}`);
|
|
2704
|
+
renameSync3(dbPath, archivedPath);
|
|
2705
|
+
item.archivedPath = archivedPath;
|
|
2706
|
+
}
|
|
2707
|
+
} finally {
|
|
2708
|
+
try {
|
|
2709
|
+
client.close();
|
|
2710
|
+
} catch {
|
|
2711
|
+
}
|
|
2712
|
+
}
|
|
2713
|
+
shards.push(item);
|
|
2714
|
+
}
|
|
2715
|
+
return {
|
|
2716
|
+
total: shards.length,
|
|
2717
|
+
ok: shards.filter((s) => s.ok).length,
|
|
2718
|
+
unreadable: shards.filter((s) => s.unreadable).length,
|
|
2719
|
+
archived: shards.filter((s) => Boolean(s.archivedPath)).length,
|
|
2720
|
+
shards
|
|
2721
|
+
};
|
|
2722
|
+
}
|
|
2658
2723
|
async function ensureShardSchema(client) {
|
|
2659
2724
|
await client.execute("PRAGMA journal_mode = WAL");
|
|
2660
2725
|
await client.execute("PRAGMA busy_timeout = 30000");
|
|
@@ -3510,6 +3575,15 @@ function readMachineId() {
|
|
|
3510
3575
|
return "";
|
|
3511
3576
|
}
|
|
3512
3577
|
}
|
|
3578
|
+
function encryptWithMachineKey(plaintext, machineKey) {
|
|
3579
|
+
const crypto2 = __require("crypto");
|
|
3580
|
+
const iv = crypto2.randomBytes(12);
|
|
3581
|
+
const cipher = crypto2.createCipheriv("aes-256-gcm", machineKey, iv);
|
|
3582
|
+
let encrypted = cipher.update(plaintext, "utf-8", "base64");
|
|
3583
|
+
encrypted += cipher.final("base64");
|
|
3584
|
+
const authTag = cipher.getAuthTag().toString("base64");
|
|
3585
|
+
return `${ENCRYPTED_PREFIX}${iv.toString("base64")}:${authTag}:${encrypted}`;
|
|
3586
|
+
}
|
|
3513
3587
|
function decryptWithMachineKey(encrypted, machineKey) {
|
|
3514
3588
|
if (!encrypted.startsWith(ENCRYPTED_PREFIX)) return null;
|
|
3515
3589
|
try {
|
|
@@ -3528,6 +3602,21 @@ function decryptWithMachineKey(encrypted, machineKey) {
|
|
|
3528
3602
|
return null;
|
|
3529
3603
|
}
|
|
3530
3604
|
}
|
|
3605
|
+
async function writeMachineBoundFileFallback(b64) {
|
|
3606
|
+
const dir = getKeyDir();
|
|
3607
|
+
await mkdir3(dir, { recursive: true });
|
|
3608
|
+
const keyPath = getKeyPath();
|
|
3609
|
+
const machineKey = deriveMachineKey();
|
|
3610
|
+
if (machineKey) {
|
|
3611
|
+
const encrypted = encryptWithMachineKey(b64, machineKey);
|
|
3612
|
+
await writeFile3(keyPath, encrypted + "\n", "utf-8");
|
|
3613
|
+
await chmod2(keyPath, 384);
|
|
3614
|
+
return "encrypted";
|
|
3615
|
+
}
|
|
3616
|
+
await writeFile3(keyPath, b64 + "\n", "utf-8");
|
|
3617
|
+
await chmod2(keyPath, 384);
|
|
3618
|
+
return "plaintext";
|
|
3619
|
+
}
|
|
3531
3620
|
async function getMasterKey() {
|
|
3532
3621
|
const nativeValue = macKeychainGet() ?? linuxSecretGet();
|
|
3533
3622
|
if (nativeValue) {
|
|
@@ -3579,6 +3668,20 @@ async function getMasterKey() {
|
|
|
3579
3668
|
const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
|
|
3580
3669
|
if (migrated) {
|
|
3581
3670
|
process.stderr.write("[keychain] Migrated key from file to native keychain.\n");
|
|
3671
|
+
try {
|
|
3672
|
+
await unlink(keyPath);
|
|
3673
|
+
process.stderr.write("[keychain] Removed legacy master.key file after native keychain migration.\n");
|
|
3674
|
+
} catch {
|
|
3675
|
+
}
|
|
3676
|
+
} else if (!content.startsWith(ENCRYPTED_PREFIX)) {
|
|
3677
|
+
const fallback = await writeMachineBoundFileFallback(b64Value);
|
|
3678
|
+
if (fallback === "encrypted") {
|
|
3679
|
+
process.stderr.write("[keychain] Upgraded legacy plaintext master.key to machine-bound encrypted fallback.\n");
|
|
3680
|
+
} else {
|
|
3681
|
+
process.stderr.write(
|
|
3682
|
+
"[keychain] WARNING: Could not encrypt legacy master.key \u2014 plaintext fallback remains.\n"
|
|
3683
|
+
);
|
|
3684
|
+
}
|
|
3582
3685
|
}
|
|
3583
3686
|
return key;
|
|
3584
3687
|
} catch (err) {
|
|
@@ -4212,15 +4315,31 @@ function auditHookHealth() {
|
|
|
4212
4315
|
const topPatterns = [...patternCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 5).map(([pattern, count]) => ({ pattern, count }));
|
|
4213
4316
|
return { logExists: true, totalLines, errorsLastHour, topPatterns };
|
|
4214
4317
|
}
|
|
4318
|
+
async function auditShards() {
|
|
4319
|
+
try {
|
|
4320
|
+
const { auditShardHealth: auditShardHealth2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
|
|
4321
|
+
const report = await auditShardHealth2();
|
|
4322
|
+
return {
|
|
4323
|
+
total: report.total,
|
|
4324
|
+
ok: report.ok,
|
|
4325
|
+
unreadable: report.unreadable,
|
|
4326
|
+
archived: report.archived,
|
|
4327
|
+
unreadableNames: report.shards.filter((s) => s.unreadable).map((s) => s.name)
|
|
4328
|
+
};
|
|
4329
|
+
} catch {
|
|
4330
|
+
return { total: 0, ok: 0, unreadable: 0, archived: 0, unreadableNames: [] };
|
|
4331
|
+
}
|
|
4332
|
+
}
|
|
4215
4333
|
async function runAudit(client, flags) {
|
|
4216
4334
|
const runConflicts = flags.conflicts || process.env.EXE_AUDIT_CONFLICTS === "1";
|
|
4217
|
-
const [stats, nullVectors, duplicates, bloated, fts, orphanedProjects] = await Promise.all([
|
|
4335
|
+
const [stats, nullVectors, duplicates, bloated, fts, orphanedProjects, shards] = await Promise.all([
|
|
4218
4336
|
auditStats(client, flags),
|
|
4219
4337
|
auditNullVectors(client, flags),
|
|
4220
4338
|
auditDuplicates(client, flags),
|
|
4221
4339
|
auditBloated(client, flags),
|
|
4222
4340
|
auditFts(client),
|
|
4223
|
-
auditOrphanedProjects(client)
|
|
4341
|
+
auditOrphanedProjects(client),
|
|
4342
|
+
auditShards()
|
|
4224
4343
|
]);
|
|
4225
4344
|
let conflicts;
|
|
4226
4345
|
if (runConflicts) {
|
|
@@ -4240,7 +4359,7 @@ async function runAudit(client, flags) {
|
|
|
4240
4359
|
}
|
|
4241
4360
|
const duplicateCount = duplicates.reduce((sum, d) => sum + d.delete_ids.length, 0);
|
|
4242
4361
|
const hookHealth = auditHookHealth();
|
|
4243
|
-
return { stats, nullVectors, duplicates, duplicateCount, bloated, fts, orphanedProjects, conflicts, hookHealth };
|
|
4362
|
+
return { stats, nullVectors, duplicates, duplicateCount, bloated, fts, orphanedProjects, conflicts, hookHealth, shards };
|
|
4244
4363
|
}
|
|
4245
4364
|
function indicator(value, warn) {
|
|
4246
4365
|
if (value === 0) return "\u{1F7E2}";
|
|
@@ -4319,6 +4438,15 @@ function formatReport(report, flags) {
|
|
|
4319
4438
|
lines.push(` ${p.count}x: ${p.pattern}`);
|
|
4320
4439
|
}
|
|
4321
4440
|
}
|
|
4441
|
+
const sh = report.shards;
|
|
4442
|
+
if (sh.total > 0) {
|
|
4443
|
+
if (sh.unreadable === 0) {
|
|
4444
|
+
lines.push(`\u{1F7E2} Shards: ${fmtNum(sh.ok)} / ${fmtNum(sh.total)} readable`);
|
|
4445
|
+
} else {
|
|
4446
|
+
const names = sh.unreadableNames.slice(0, 5).join(", ");
|
|
4447
|
+
lines.push(`\u{1F534} Shards: ${fmtNum(sh.unreadable)} unreadable (${names}${sh.unreadableNames.length > 5 ? ", ..." : ""})`);
|
|
4448
|
+
}
|
|
4449
|
+
}
|
|
4322
4450
|
lines.push("");
|
|
4323
4451
|
if (flags.verbose) {
|
|
4324
4452
|
if (report.duplicates.length > 0) {
|
|
@@ -4366,6 +4494,9 @@ function formatReport(report, flags) {
|
|
|
4366
4494
|
if (!report.fts.inSync) {
|
|
4367
4495
|
recs.push("Run --fix to rebuild FTS index");
|
|
4368
4496
|
}
|
|
4497
|
+
if (report.shards.unreadable > 0) {
|
|
4498
|
+
recs.push(`Run --fix to archive ${fmtNum(report.shards.unreadable)} unreadable shard(s); global DB remains authoritative`);
|
|
4499
|
+
}
|
|
4369
4500
|
if (report.orphanedProjects.length > 0) {
|
|
4370
4501
|
const names = report.orphanedProjects.map((o) => `"${o.project_name}"`).join(", ");
|
|
4371
4502
|
recs.push(`Consider /exe-forget for orphaned project${report.orphanedProjects.length > 1 ? "s" : ""} ${names}`);
|
|
@@ -4471,6 +4602,17 @@ async function fixBloated(client, bloated, dryRun) {
|
|
|
4471
4602
|
}
|
|
4472
4603
|
return chunksCreated;
|
|
4473
4604
|
}
|
|
4605
|
+
async function fixShards(dryRun) {
|
|
4606
|
+
const { auditShardHealth: auditShardHealth2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
|
|
4607
|
+
const report = await auditShardHealth2({ repair: true, dryRun });
|
|
4608
|
+
return {
|
|
4609
|
+
total: report.total,
|
|
4610
|
+
ok: report.ok,
|
|
4611
|
+
unreadable: report.unreadable,
|
|
4612
|
+
archived: report.archived,
|
|
4613
|
+
unreadableNames: report.shards.filter((s) => s.unreadable).map((s) => s.name)
|
|
4614
|
+
};
|
|
4615
|
+
}
|
|
4474
4616
|
function splitAtSentences(text, maxChunkSize) {
|
|
4475
4617
|
if (text.length <= maxChunkSize) return [text];
|
|
4476
4618
|
const chunks = [];
|
|
@@ -4546,6 +4688,11 @@ ${mode} Applying repairs...
|
|
|
4546
4688
|
console.log(" Done.");
|
|
4547
4689
|
}
|
|
4548
4690
|
}
|
|
4691
|
+
if (report.shards.unreadable > 0) {
|
|
4692
|
+
console.log(`${mode} Archiving ${fmtNum(report.shards.unreadable)} unreadable shard(s)...`);
|
|
4693
|
+
const fixed = await fixShards(flags.dryRun);
|
|
4694
|
+
console.log(` ${flags.dryRun ? "Would archive" : "Archived"} ${fmtNum(flags.dryRun ? fixed.unreadable : fixed.archived)} shard(s).`);
|
|
4695
|
+
}
|
|
4549
4696
|
console.log(`
|
|
4550
4697
|
${mode} Complete.`);
|
|
4551
4698
|
}
|
|
@@ -4564,10 +4711,12 @@ export {
|
|
|
4564
4711
|
auditHookHealth,
|
|
4565
4712
|
auditNullVectors,
|
|
4566
4713
|
auditOrphanedProjects,
|
|
4714
|
+
auditShards,
|
|
4567
4715
|
auditStats,
|
|
4568
4716
|
fixBloated,
|
|
4569
4717
|
fixDuplicates,
|
|
4570
4718
|
fixNullVectors,
|
|
4719
|
+
fixShards,
|
|
4571
4720
|
formatReport,
|
|
4572
4721
|
main,
|
|
4573
4722
|
parseFlags,
|
|
@@ -2963,6 +2963,15 @@ function readMachineId() {
|
|
|
2963
2963
|
return "";
|
|
2964
2964
|
}
|
|
2965
2965
|
}
|
|
2966
|
+
function encryptWithMachineKey(plaintext, machineKey) {
|
|
2967
|
+
const crypto3 = __require("crypto");
|
|
2968
|
+
const iv = crypto3.randomBytes(12);
|
|
2969
|
+
const cipher = crypto3.createCipheriv("aes-256-gcm", machineKey, iv);
|
|
2970
|
+
let encrypted = cipher.update(plaintext, "utf-8", "base64");
|
|
2971
|
+
encrypted += cipher.final("base64");
|
|
2972
|
+
const authTag = cipher.getAuthTag().toString("base64");
|
|
2973
|
+
return `${ENCRYPTED_PREFIX}${iv.toString("base64")}:${authTag}:${encrypted}`;
|
|
2974
|
+
}
|
|
2966
2975
|
function decryptWithMachineKey(encrypted, machineKey) {
|
|
2967
2976
|
if (!encrypted.startsWith(ENCRYPTED_PREFIX)) return null;
|
|
2968
2977
|
try {
|
|
@@ -2981,6 +2990,21 @@ function decryptWithMachineKey(encrypted, machineKey) {
|
|
|
2981
2990
|
return null;
|
|
2982
2991
|
}
|
|
2983
2992
|
}
|
|
2993
|
+
async function writeMachineBoundFileFallback(b64) {
|
|
2994
|
+
const dir = getKeyDir();
|
|
2995
|
+
await mkdir3(dir, { recursive: true });
|
|
2996
|
+
const keyPath = getKeyPath();
|
|
2997
|
+
const machineKey = deriveMachineKey();
|
|
2998
|
+
if (machineKey) {
|
|
2999
|
+
const encrypted = encryptWithMachineKey(b64, machineKey);
|
|
3000
|
+
await writeFile3(keyPath, encrypted + "\n", "utf-8");
|
|
3001
|
+
await chmod2(keyPath, 384);
|
|
3002
|
+
return "encrypted";
|
|
3003
|
+
}
|
|
3004
|
+
await writeFile3(keyPath, b64 + "\n", "utf-8");
|
|
3005
|
+
await chmod2(keyPath, 384);
|
|
3006
|
+
return "plaintext";
|
|
3007
|
+
}
|
|
2984
3008
|
async function getMasterKey() {
|
|
2985
3009
|
const nativeValue = macKeychainGet() ?? linuxSecretGet();
|
|
2986
3010
|
if (nativeValue) {
|
|
@@ -3032,6 +3056,20 @@ async function getMasterKey() {
|
|
|
3032
3056
|
const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
|
|
3033
3057
|
if (migrated) {
|
|
3034
3058
|
process.stderr.write("[keychain] Migrated key from file to native keychain.\n");
|
|
3059
|
+
try {
|
|
3060
|
+
await unlink(keyPath);
|
|
3061
|
+
process.stderr.write("[keychain] Removed legacy master.key file after native keychain migration.\n");
|
|
3062
|
+
} catch {
|
|
3063
|
+
}
|
|
3064
|
+
} else if (!content.startsWith(ENCRYPTED_PREFIX)) {
|
|
3065
|
+
const fallback = await writeMachineBoundFileFallback(b64Value);
|
|
3066
|
+
if (fallback === "encrypted") {
|
|
3067
|
+
process.stderr.write("[keychain] Upgraded legacy plaintext master.key to machine-bound encrypted fallback.\n");
|
|
3068
|
+
} else {
|
|
3069
|
+
process.stderr.write(
|
|
3070
|
+
"[keychain] WARNING: Could not encrypt legacy master.key \u2014 plaintext fallback remains.\n"
|
|
3071
|
+
);
|
|
3072
|
+
}
|
|
3035
3073
|
}
|
|
3036
3074
|
return key;
|
|
3037
3075
|
} catch (err) {
|
|
@@ -3304,6 +3342,7 @@ var init_memory_write_governor = __esm({
|
|
|
3304
3342
|
// src/lib/shard-manager.ts
|
|
3305
3343
|
var shard_manager_exports = {};
|
|
3306
3344
|
__export(shard_manager_exports, {
|
|
3345
|
+
auditShardHealth: () => auditShardHealth,
|
|
3307
3346
|
disposeShards: () => disposeShards,
|
|
3308
3347
|
ensureShardSchema: () => ensureShardSchema,
|
|
3309
3348
|
getOpenShardCount: () => getOpenShardCount,
|
|
@@ -3370,6 +3409,70 @@ function listShards() {
|
|
|
3370
3409
|
if (!existsSync7(SHARDS_DIR)) return [];
|
|
3371
3410
|
return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
|
|
3372
3411
|
}
|
|
3412
|
+
async function auditShardHealth(options = {}) {
|
|
3413
|
+
if (!_encryptionKey) {
|
|
3414
|
+
throw new Error("Shard manager not initialized. Call initShardManager() first.");
|
|
3415
|
+
}
|
|
3416
|
+
const repair = options.repair === true;
|
|
3417
|
+
const dryRun = options.dryRun === true;
|
|
3418
|
+
const names = listShards();
|
|
3419
|
+
const shards = [];
|
|
3420
|
+
for (const name of names) {
|
|
3421
|
+
const dbPath = path7.join(SHARDS_DIR, `${name}.db`);
|
|
3422
|
+
const stat = statSync2(dbPath);
|
|
3423
|
+
const item = {
|
|
3424
|
+
name,
|
|
3425
|
+
path: dbPath,
|
|
3426
|
+
ok: false,
|
|
3427
|
+
unreadable: false,
|
|
3428
|
+
error: null,
|
|
3429
|
+
size: stat.size,
|
|
3430
|
+
mtime: stat.mtime.toISOString(),
|
|
3431
|
+
memoryCount: null
|
|
3432
|
+
};
|
|
3433
|
+
const client = createClient2({
|
|
3434
|
+
url: `file:${dbPath}`,
|
|
3435
|
+
encryptionKey: _encryptionKey
|
|
3436
|
+
});
|
|
3437
|
+
try {
|
|
3438
|
+
await client.execute("SELECT COUNT(*) as cnt FROM sqlite_schema");
|
|
3439
|
+
const hasMemories = await client.execute(
|
|
3440
|
+
"SELECT COUNT(*) as cnt FROM sqlite_schema WHERE type = 'table' AND name = 'memories'"
|
|
3441
|
+
);
|
|
3442
|
+
if (Number(hasMemories.rows[0]?.cnt ?? 0) > 0) {
|
|
3443
|
+
const mem = await client.execute("SELECT COUNT(*) as cnt FROM memories");
|
|
3444
|
+
item.memoryCount = Number(mem.rows[0]?.cnt ?? 0);
|
|
3445
|
+
}
|
|
3446
|
+
item.ok = true;
|
|
3447
|
+
} catch (err) {
|
|
3448
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
3449
|
+
item.error = message;
|
|
3450
|
+
item.unreadable = /SQLITE_NOTADB|file is not a database/i.test(message);
|
|
3451
|
+
if (item.unreadable && repair && !dryRun) {
|
|
3452
|
+
client.close();
|
|
3453
|
+
_shards.delete(name);
|
|
3454
|
+
_shardLastAccess.delete(name);
|
|
3455
|
+
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
3456
|
+
const archivedPath = path7.join(SHARDS_DIR, `${name}.db.broken-${stamp}`);
|
|
3457
|
+
renameSync3(dbPath, archivedPath);
|
|
3458
|
+
item.archivedPath = archivedPath;
|
|
3459
|
+
}
|
|
3460
|
+
} finally {
|
|
3461
|
+
try {
|
|
3462
|
+
client.close();
|
|
3463
|
+
} catch {
|
|
3464
|
+
}
|
|
3465
|
+
}
|
|
3466
|
+
shards.push(item);
|
|
3467
|
+
}
|
|
3468
|
+
return {
|
|
3469
|
+
total: shards.length,
|
|
3470
|
+
ok: shards.filter((s) => s.ok).length,
|
|
3471
|
+
unreadable: shards.filter((s) => s.unreadable).length,
|
|
3472
|
+
archived: shards.filter((s) => Boolean(s.archivedPath)).length,
|
|
3473
|
+
shards
|
|
3474
|
+
};
|
|
3475
|
+
}
|
|
3373
3476
|
async function ensureShardSchema(client) {
|
|
3374
3477
|
await client.execute("PRAGMA journal_mode = WAL");
|
|
3375
3478
|
await client.execute("PRAGMA busy_timeout = 30000");
|
package/dist/bin/exe-forget.js
CHANGED
|
@@ -2897,6 +2897,15 @@ function readMachineId() {
|
|
|
2897
2897
|
return "";
|
|
2898
2898
|
}
|
|
2899
2899
|
}
|
|
2900
|
+
function encryptWithMachineKey(plaintext, machineKey) {
|
|
2901
|
+
const crypto2 = __require("crypto");
|
|
2902
|
+
const iv = crypto2.randomBytes(12);
|
|
2903
|
+
const cipher = crypto2.createCipheriv("aes-256-gcm", machineKey, iv);
|
|
2904
|
+
let encrypted = cipher.update(plaintext, "utf-8", "base64");
|
|
2905
|
+
encrypted += cipher.final("base64");
|
|
2906
|
+
const authTag = cipher.getAuthTag().toString("base64");
|
|
2907
|
+
return `${ENCRYPTED_PREFIX}${iv.toString("base64")}:${authTag}:${encrypted}`;
|
|
2908
|
+
}
|
|
2900
2909
|
function decryptWithMachineKey(encrypted, machineKey) {
|
|
2901
2910
|
if (!encrypted.startsWith(ENCRYPTED_PREFIX)) return null;
|
|
2902
2911
|
try {
|
|
@@ -2915,6 +2924,21 @@ function decryptWithMachineKey(encrypted, machineKey) {
|
|
|
2915
2924
|
return null;
|
|
2916
2925
|
}
|
|
2917
2926
|
}
|
|
2927
|
+
async function writeMachineBoundFileFallback(b64) {
|
|
2928
|
+
const dir = getKeyDir();
|
|
2929
|
+
await mkdir3(dir, { recursive: true });
|
|
2930
|
+
const keyPath = getKeyPath();
|
|
2931
|
+
const machineKey = deriveMachineKey();
|
|
2932
|
+
if (machineKey) {
|
|
2933
|
+
const encrypted = encryptWithMachineKey(b64, machineKey);
|
|
2934
|
+
await writeFile3(keyPath, encrypted + "\n", "utf-8");
|
|
2935
|
+
await chmod2(keyPath, 384);
|
|
2936
|
+
return "encrypted";
|
|
2937
|
+
}
|
|
2938
|
+
await writeFile3(keyPath, b64 + "\n", "utf-8");
|
|
2939
|
+
await chmod2(keyPath, 384);
|
|
2940
|
+
return "plaintext";
|
|
2941
|
+
}
|
|
2918
2942
|
async function getMasterKey() {
|
|
2919
2943
|
const nativeValue = macKeychainGet() ?? linuxSecretGet();
|
|
2920
2944
|
if (nativeValue) {
|
|
@@ -2966,6 +2990,20 @@ async function getMasterKey() {
|
|
|
2966
2990
|
const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
|
|
2967
2991
|
if (migrated) {
|
|
2968
2992
|
process.stderr.write("[keychain] Migrated key from file to native keychain.\n");
|
|
2993
|
+
try {
|
|
2994
|
+
await unlink(keyPath);
|
|
2995
|
+
process.stderr.write("[keychain] Removed legacy master.key file after native keychain migration.\n");
|
|
2996
|
+
} catch {
|
|
2997
|
+
}
|
|
2998
|
+
} else if (!content.startsWith(ENCRYPTED_PREFIX)) {
|
|
2999
|
+
const fallback = await writeMachineBoundFileFallback(b64Value);
|
|
3000
|
+
if (fallback === "encrypted") {
|
|
3001
|
+
process.stderr.write("[keychain] Upgraded legacy plaintext master.key to machine-bound encrypted fallback.\n");
|
|
3002
|
+
} else {
|
|
3003
|
+
process.stderr.write(
|
|
3004
|
+
"[keychain] WARNING: Could not encrypt legacy master.key \u2014 plaintext fallback remains.\n"
|
|
3005
|
+
);
|
|
3006
|
+
}
|
|
2969
3007
|
}
|
|
2970
3008
|
return key;
|
|
2971
3009
|
} catch (err) {
|
|
@@ -3238,6 +3276,7 @@ var init_memory_write_governor = __esm({
|
|
|
3238
3276
|
// src/lib/shard-manager.ts
|
|
3239
3277
|
var shard_manager_exports = {};
|
|
3240
3278
|
__export(shard_manager_exports, {
|
|
3279
|
+
auditShardHealth: () => auditShardHealth,
|
|
3241
3280
|
disposeShards: () => disposeShards,
|
|
3242
3281
|
ensureShardSchema: () => ensureShardSchema,
|
|
3243
3282
|
getOpenShardCount: () => getOpenShardCount,
|
|
@@ -3304,6 +3343,70 @@ function listShards() {
|
|
|
3304
3343
|
if (!existsSync7(SHARDS_DIR)) return [];
|
|
3305
3344
|
return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
|
|
3306
3345
|
}
|
|
3346
|
+
async function auditShardHealth(options = {}) {
|
|
3347
|
+
if (!_encryptionKey) {
|
|
3348
|
+
throw new Error("Shard manager not initialized. Call initShardManager() first.");
|
|
3349
|
+
}
|
|
3350
|
+
const repair = options.repair === true;
|
|
3351
|
+
const dryRun = options.dryRun === true;
|
|
3352
|
+
const names = listShards();
|
|
3353
|
+
const shards = [];
|
|
3354
|
+
for (const name of names) {
|
|
3355
|
+
const dbPath = path7.join(SHARDS_DIR, `${name}.db`);
|
|
3356
|
+
const stat = statSync2(dbPath);
|
|
3357
|
+
const item = {
|
|
3358
|
+
name,
|
|
3359
|
+
path: dbPath,
|
|
3360
|
+
ok: false,
|
|
3361
|
+
unreadable: false,
|
|
3362
|
+
error: null,
|
|
3363
|
+
size: stat.size,
|
|
3364
|
+
mtime: stat.mtime.toISOString(),
|
|
3365
|
+
memoryCount: null
|
|
3366
|
+
};
|
|
3367
|
+
const client = createClient2({
|
|
3368
|
+
url: `file:${dbPath}`,
|
|
3369
|
+
encryptionKey: _encryptionKey
|
|
3370
|
+
});
|
|
3371
|
+
try {
|
|
3372
|
+
await client.execute("SELECT COUNT(*) as cnt FROM sqlite_schema");
|
|
3373
|
+
const hasMemories = await client.execute(
|
|
3374
|
+
"SELECT COUNT(*) as cnt FROM sqlite_schema WHERE type = 'table' AND name = 'memories'"
|
|
3375
|
+
);
|
|
3376
|
+
if (Number(hasMemories.rows[0]?.cnt ?? 0) > 0) {
|
|
3377
|
+
const mem = await client.execute("SELECT COUNT(*) as cnt FROM memories");
|
|
3378
|
+
item.memoryCount = Number(mem.rows[0]?.cnt ?? 0);
|
|
3379
|
+
}
|
|
3380
|
+
item.ok = true;
|
|
3381
|
+
} catch (err) {
|
|
3382
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
3383
|
+
item.error = message;
|
|
3384
|
+
item.unreadable = /SQLITE_NOTADB|file is not a database/i.test(message);
|
|
3385
|
+
if (item.unreadable && repair && !dryRun) {
|
|
3386
|
+
client.close();
|
|
3387
|
+
_shards.delete(name);
|
|
3388
|
+
_shardLastAccess.delete(name);
|
|
3389
|
+
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
3390
|
+
const archivedPath = path7.join(SHARDS_DIR, `${name}.db.broken-${stamp}`);
|
|
3391
|
+
renameSync3(dbPath, archivedPath);
|
|
3392
|
+
item.archivedPath = archivedPath;
|
|
3393
|
+
}
|
|
3394
|
+
} finally {
|
|
3395
|
+
try {
|
|
3396
|
+
client.close();
|
|
3397
|
+
} catch {
|
|
3398
|
+
}
|
|
3399
|
+
}
|
|
3400
|
+
shards.push(item);
|
|
3401
|
+
}
|
|
3402
|
+
return {
|
|
3403
|
+
total: shards.length,
|
|
3404
|
+
ok: shards.filter((s) => s.ok).length,
|
|
3405
|
+
unreadable: shards.filter((s) => s.unreadable).length,
|
|
3406
|
+
archived: shards.filter((s) => Boolean(s.archivedPath)).length,
|
|
3407
|
+
shards
|
|
3408
|
+
};
|
|
3409
|
+
}
|
|
3307
3410
|
async function ensureShardSchema(client) {
|
|
3308
3411
|
await client.execute("PRAGMA journal_mode = WAL");
|
|
3309
3412
|
await client.execute("PRAGMA busy_timeout = 30000");
|
package/dist/bin/exe-gateway.js
CHANGED
|
@@ -3611,6 +3611,15 @@ function readMachineId() {
|
|
|
3611
3611
|
return "";
|
|
3612
3612
|
}
|
|
3613
3613
|
}
|
|
3614
|
+
function encryptWithMachineKey(plaintext, machineKey) {
|
|
3615
|
+
const crypto10 = __require("crypto");
|
|
3616
|
+
const iv = crypto10.randomBytes(12);
|
|
3617
|
+
const cipher = crypto10.createCipheriv("aes-256-gcm", machineKey, iv);
|
|
3618
|
+
let encrypted = cipher.update(plaintext, "utf-8", "base64");
|
|
3619
|
+
encrypted += cipher.final("base64");
|
|
3620
|
+
const authTag = cipher.getAuthTag().toString("base64");
|
|
3621
|
+
return `${ENCRYPTED_PREFIX}${iv.toString("base64")}:${authTag}:${encrypted}`;
|
|
3622
|
+
}
|
|
3614
3623
|
function decryptWithMachineKey(encrypted, machineKey) {
|
|
3615
3624
|
if (!encrypted.startsWith(ENCRYPTED_PREFIX)) return null;
|
|
3616
3625
|
try {
|
|
@@ -3629,6 +3638,21 @@ function decryptWithMachineKey(encrypted, machineKey) {
|
|
|
3629
3638
|
return null;
|
|
3630
3639
|
}
|
|
3631
3640
|
}
|
|
3641
|
+
async function writeMachineBoundFileFallback(b64) {
|
|
3642
|
+
const dir = getKeyDir();
|
|
3643
|
+
await mkdir3(dir, { recursive: true });
|
|
3644
|
+
const keyPath = getKeyPath();
|
|
3645
|
+
const machineKey = deriveMachineKey();
|
|
3646
|
+
if (machineKey) {
|
|
3647
|
+
const encrypted = encryptWithMachineKey(b64, machineKey);
|
|
3648
|
+
await writeFile3(keyPath, encrypted + "\n", "utf-8");
|
|
3649
|
+
await chmod2(keyPath, 384);
|
|
3650
|
+
return "encrypted";
|
|
3651
|
+
}
|
|
3652
|
+
await writeFile3(keyPath, b64 + "\n", "utf-8");
|
|
3653
|
+
await chmod2(keyPath, 384);
|
|
3654
|
+
return "plaintext";
|
|
3655
|
+
}
|
|
3632
3656
|
async function getMasterKey() {
|
|
3633
3657
|
const nativeValue = macKeychainGet() ?? linuxSecretGet();
|
|
3634
3658
|
if (nativeValue) {
|
|
@@ -3680,6 +3704,20 @@ async function getMasterKey() {
|
|
|
3680
3704
|
const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
|
|
3681
3705
|
if (migrated) {
|
|
3682
3706
|
process.stderr.write("[keychain] Migrated key from file to native keychain.\n");
|
|
3707
|
+
try {
|
|
3708
|
+
await unlink(keyPath);
|
|
3709
|
+
process.stderr.write("[keychain] Removed legacy master.key file after native keychain migration.\n");
|
|
3710
|
+
} catch {
|
|
3711
|
+
}
|
|
3712
|
+
} else if (!content.startsWith(ENCRYPTED_PREFIX)) {
|
|
3713
|
+
const fallback = await writeMachineBoundFileFallback(b64Value);
|
|
3714
|
+
if (fallback === "encrypted") {
|
|
3715
|
+
process.stderr.write("[keychain] Upgraded legacy plaintext master.key to machine-bound encrypted fallback.\n");
|
|
3716
|
+
} else {
|
|
3717
|
+
process.stderr.write(
|
|
3718
|
+
"[keychain] WARNING: Could not encrypt legacy master.key \u2014 plaintext fallback remains.\n"
|
|
3719
|
+
);
|
|
3720
|
+
}
|
|
3683
3721
|
}
|
|
3684
3722
|
return key;
|
|
3685
3723
|
} catch (err) {
|
|
@@ -3897,6 +3935,7 @@ var init_memory_write_governor = __esm({
|
|
|
3897
3935
|
// src/lib/shard-manager.ts
|
|
3898
3936
|
var shard_manager_exports = {};
|
|
3899
3937
|
__export(shard_manager_exports, {
|
|
3938
|
+
auditShardHealth: () => auditShardHealth,
|
|
3900
3939
|
disposeShards: () => disposeShards,
|
|
3901
3940
|
ensureShardSchema: () => ensureShardSchema,
|
|
3902
3941
|
getOpenShardCount: () => getOpenShardCount,
|
|
@@ -3963,6 +4002,70 @@ function listShards() {
|
|
|
3963
4002
|
if (!existsSync8(SHARDS_DIR)) return [];
|
|
3964
4003
|
return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
|
|
3965
4004
|
}
|
|
4005
|
+
async function auditShardHealth(options = {}) {
|
|
4006
|
+
if (!_encryptionKey) {
|
|
4007
|
+
throw new Error("Shard manager not initialized. Call initShardManager() first.");
|
|
4008
|
+
}
|
|
4009
|
+
const repair = options.repair === true;
|
|
4010
|
+
const dryRun = options.dryRun === true;
|
|
4011
|
+
const names = listShards();
|
|
4012
|
+
const shards = [];
|
|
4013
|
+
for (const name of names) {
|
|
4014
|
+
const dbPath = path9.join(SHARDS_DIR, `${name}.db`);
|
|
4015
|
+
const stat = statSync2(dbPath);
|
|
4016
|
+
const item = {
|
|
4017
|
+
name,
|
|
4018
|
+
path: dbPath,
|
|
4019
|
+
ok: false,
|
|
4020
|
+
unreadable: false,
|
|
4021
|
+
error: null,
|
|
4022
|
+
size: stat.size,
|
|
4023
|
+
mtime: stat.mtime.toISOString(),
|
|
4024
|
+
memoryCount: null
|
|
4025
|
+
};
|
|
4026
|
+
const client = createClient2({
|
|
4027
|
+
url: `file:${dbPath}`,
|
|
4028
|
+
encryptionKey: _encryptionKey
|
|
4029
|
+
});
|
|
4030
|
+
try {
|
|
4031
|
+
await client.execute("SELECT COUNT(*) as cnt FROM sqlite_schema");
|
|
4032
|
+
const hasMemories = await client.execute(
|
|
4033
|
+
"SELECT COUNT(*) as cnt FROM sqlite_schema WHERE type = 'table' AND name = 'memories'"
|
|
4034
|
+
);
|
|
4035
|
+
if (Number(hasMemories.rows[0]?.cnt ?? 0) > 0) {
|
|
4036
|
+
const mem = await client.execute("SELECT COUNT(*) as cnt FROM memories");
|
|
4037
|
+
item.memoryCount = Number(mem.rows[0]?.cnt ?? 0);
|
|
4038
|
+
}
|
|
4039
|
+
item.ok = true;
|
|
4040
|
+
} catch (err) {
|
|
4041
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
4042
|
+
item.error = message;
|
|
4043
|
+
item.unreadable = /SQLITE_NOTADB|file is not a database/i.test(message);
|
|
4044
|
+
if (item.unreadable && repair && !dryRun) {
|
|
4045
|
+
client.close();
|
|
4046
|
+
_shards.delete(name);
|
|
4047
|
+
_shardLastAccess.delete(name);
|
|
4048
|
+
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
4049
|
+
const archivedPath = path9.join(SHARDS_DIR, `${name}.db.broken-${stamp}`);
|
|
4050
|
+
renameSync3(dbPath, archivedPath);
|
|
4051
|
+
item.archivedPath = archivedPath;
|
|
4052
|
+
}
|
|
4053
|
+
} finally {
|
|
4054
|
+
try {
|
|
4055
|
+
client.close();
|
|
4056
|
+
} catch {
|
|
4057
|
+
}
|
|
4058
|
+
}
|
|
4059
|
+
shards.push(item);
|
|
4060
|
+
}
|
|
4061
|
+
return {
|
|
4062
|
+
total: shards.length,
|
|
4063
|
+
ok: shards.filter((s) => s.ok).length,
|
|
4064
|
+
unreadable: shards.filter((s) => s.unreadable).length,
|
|
4065
|
+
archived: shards.filter((s) => Boolean(s.archivedPath)).length,
|
|
4066
|
+
shards
|
|
4067
|
+
};
|
|
4068
|
+
}
|
|
3966
4069
|
async function ensureShardSchema(client) {
|
|
3967
4070
|
await client.execute("PRAGMA journal_mode = WAL");
|
|
3968
4071
|
await client.execute("PRAGMA busy_timeout = 30000");
|