@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
|
@@ -2729,6 +2729,7 @@ var init_database = __esm({
|
|
|
2729
2729
|
// src/lib/shard-manager.ts
|
|
2730
2730
|
var shard_manager_exports = {};
|
|
2731
2731
|
__export(shard_manager_exports, {
|
|
2732
|
+
auditShardHealth: () => auditShardHealth,
|
|
2732
2733
|
disposeShards: () => disposeShards,
|
|
2733
2734
|
ensureShardSchema: () => ensureShardSchema,
|
|
2734
2735
|
getOpenShardCount: () => getOpenShardCount,
|
|
@@ -2795,6 +2796,70 @@ function listShards() {
|
|
|
2795
2796
|
if (!existsSync7(SHARDS_DIR)) return [];
|
|
2796
2797
|
return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
|
|
2797
2798
|
}
|
|
2799
|
+
async function auditShardHealth(options = {}) {
|
|
2800
|
+
if (!_encryptionKey) {
|
|
2801
|
+
throw new Error("Shard manager not initialized. Call initShardManager() first.");
|
|
2802
|
+
}
|
|
2803
|
+
const repair = options.repair === true;
|
|
2804
|
+
const dryRun = options.dryRun === true;
|
|
2805
|
+
const names = listShards();
|
|
2806
|
+
const shards = [];
|
|
2807
|
+
for (const name of names) {
|
|
2808
|
+
const dbPath = path7.join(SHARDS_DIR, `${name}.db`);
|
|
2809
|
+
const stat2 = statSync2(dbPath);
|
|
2810
|
+
const item = {
|
|
2811
|
+
name,
|
|
2812
|
+
path: dbPath,
|
|
2813
|
+
ok: false,
|
|
2814
|
+
unreadable: false,
|
|
2815
|
+
error: null,
|
|
2816
|
+
size: stat2.size,
|
|
2817
|
+
mtime: stat2.mtime.toISOString(),
|
|
2818
|
+
memoryCount: null
|
|
2819
|
+
};
|
|
2820
|
+
const client = createClient2({
|
|
2821
|
+
url: `file:${dbPath}`,
|
|
2822
|
+
encryptionKey: _encryptionKey
|
|
2823
|
+
});
|
|
2824
|
+
try {
|
|
2825
|
+
await client.execute("SELECT COUNT(*) as cnt FROM sqlite_schema");
|
|
2826
|
+
const hasMemories = await client.execute(
|
|
2827
|
+
"SELECT COUNT(*) as cnt FROM sqlite_schema WHERE type = 'table' AND name = 'memories'"
|
|
2828
|
+
);
|
|
2829
|
+
if (Number(hasMemories.rows[0]?.cnt ?? 0) > 0) {
|
|
2830
|
+
const mem = await client.execute("SELECT COUNT(*) as cnt FROM memories");
|
|
2831
|
+
item.memoryCount = Number(mem.rows[0]?.cnt ?? 0);
|
|
2832
|
+
}
|
|
2833
|
+
item.ok = true;
|
|
2834
|
+
} catch (err) {
|
|
2835
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2836
|
+
item.error = message;
|
|
2837
|
+
item.unreadable = /SQLITE_NOTADB|file is not a database/i.test(message);
|
|
2838
|
+
if (item.unreadable && repair && !dryRun) {
|
|
2839
|
+
client.close();
|
|
2840
|
+
_shards.delete(name);
|
|
2841
|
+
_shardLastAccess.delete(name);
|
|
2842
|
+
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
2843
|
+
const archivedPath = path7.join(SHARDS_DIR, `${name}.db.broken-${stamp}`);
|
|
2844
|
+
renameSync3(dbPath, archivedPath);
|
|
2845
|
+
item.archivedPath = archivedPath;
|
|
2846
|
+
}
|
|
2847
|
+
} finally {
|
|
2848
|
+
try {
|
|
2849
|
+
client.close();
|
|
2850
|
+
} catch {
|
|
2851
|
+
}
|
|
2852
|
+
}
|
|
2853
|
+
shards.push(item);
|
|
2854
|
+
}
|
|
2855
|
+
return {
|
|
2856
|
+
total: shards.length,
|
|
2857
|
+
ok: shards.filter((s) => s.ok).length,
|
|
2858
|
+
unreadable: shards.filter((s) => s.unreadable).length,
|
|
2859
|
+
archived: shards.filter((s) => Boolean(s.archivedPath)).length,
|
|
2860
|
+
shards
|
|
2861
|
+
};
|
|
2862
|
+
}
|
|
2798
2863
|
async function ensureShardSchema(client) {
|
|
2799
2864
|
await client.execute("PRAGMA journal_mode = WAL");
|
|
2800
2865
|
await client.execute("PRAGMA busy_timeout = 30000");
|
|
@@ -3443,6 +3508,15 @@ function readMachineId() {
|
|
|
3443
3508
|
return "";
|
|
3444
3509
|
}
|
|
3445
3510
|
}
|
|
3511
|
+
function encryptWithMachineKey(plaintext, machineKey) {
|
|
3512
|
+
const crypto3 = __require("crypto");
|
|
3513
|
+
const iv = crypto3.randomBytes(12);
|
|
3514
|
+
const cipher = crypto3.createCipheriv("aes-256-gcm", machineKey, iv);
|
|
3515
|
+
let encrypted = cipher.update(plaintext, "utf-8", "base64");
|
|
3516
|
+
encrypted += cipher.final("base64");
|
|
3517
|
+
const authTag = cipher.getAuthTag().toString("base64");
|
|
3518
|
+
return `${ENCRYPTED_PREFIX}${iv.toString("base64")}:${authTag}:${encrypted}`;
|
|
3519
|
+
}
|
|
3446
3520
|
function decryptWithMachineKey(encrypted, machineKey) {
|
|
3447
3521
|
if (!encrypted.startsWith(ENCRYPTED_PREFIX)) return null;
|
|
3448
3522
|
try {
|
|
@@ -3461,6 +3535,21 @@ function decryptWithMachineKey(encrypted, machineKey) {
|
|
|
3461
3535
|
return null;
|
|
3462
3536
|
}
|
|
3463
3537
|
}
|
|
3538
|
+
async function writeMachineBoundFileFallback(b64) {
|
|
3539
|
+
const dir = getKeyDir();
|
|
3540
|
+
await mkdir3(dir, { recursive: true });
|
|
3541
|
+
const keyPath = getKeyPath();
|
|
3542
|
+
const machineKey = deriveMachineKey();
|
|
3543
|
+
if (machineKey) {
|
|
3544
|
+
const encrypted = encryptWithMachineKey(b64, machineKey);
|
|
3545
|
+
await writeFile3(keyPath, encrypted + "\n", "utf-8");
|
|
3546
|
+
await chmod2(keyPath, 384);
|
|
3547
|
+
return "encrypted";
|
|
3548
|
+
}
|
|
3549
|
+
await writeFile3(keyPath, b64 + "\n", "utf-8");
|
|
3550
|
+
await chmod2(keyPath, 384);
|
|
3551
|
+
return "plaintext";
|
|
3552
|
+
}
|
|
3464
3553
|
async function getMasterKey() {
|
|
3465
3554
|
const nativeValue = macKeychainGet() ?? linuxSecretGet();
|
|
3466
3555
|
if (nativeValue) {
|
|
@@ -3512,6 +3601,20 @@ async function getMasterKey() {
|
|
|
3512
3601
|
const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
|
|
3513
3602
|
if (migrated) {
|
|
3514
3603
|
process.stderr.write("[keychain] Migrated key from file to native keychain.\n");
|
|
3604
|
+
try {
|
|
3605
|
+
await unlink(keyPath);
|
|
3606
|
+
process.stderr.write("[keychain] Removed legacy master.key file after native keychain migration.\n");
|
|
3607
|
+
} catch {
|
|
3608
|
+
}
|
|
3609
|
+
} else if (!content.startsWith(ENCRYPTED_PREFIX)) {
|
|
3610
|
+
const fallback = await writeMachineBoundFileFallback(b64Value);
|
|
3611
|
+
if (fallback === "encrypted") {
|
|
3612
|
+
process.stderr.write("[keychain] Upgraded legacy plaintext master.key to machine-bound encrypted fallback.\n");
|
|
3613
|
+
} else {
|
|
3614
|
+
process.stderr.write(
|
|
3615
|
+
"[keychain] WARNING: Could not encrypt legacy master.key \u2014 plaintext fallback remains.\n"
|
|
3616
|
+
);
|
|
3617
|
+
}
|
|
3515
3618
|
}
|
|
3516
3619
|
return key;
|
|
3517
3620
|
} catch (err) {
|
|
@@ -2729,6 +2729,7 @@ var init_database = __esm({
|
|
|
2729
2729
|
// src/lib/shard-manager.ts
|
|
2730
2730
|
var shard_manager_exports = {};
|
|
2731
2731
|
__export(shard_manager_exports, {
|
|
2732
|
+
auditShardHealth: () => auditShardHealth,
|
|
2732
2733
|
disposeShards: () => disposeShards,
|
|
2733
2734
|
ensureShardSchema: () => ensureShardSchema,
|
|
2734
2735
|
getOpenShardCount: () => getOpenShardCount,
|
|
@@ -2795,6 +2796,70 @@ function listShards() {
|
|
|
2795
2796
|
if (!existsSync7(SHARDS_DIR)) return [];
|
|
2796
2797
|
return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
|
|
2797
2798
|
}
|
|
2799
|
+
async function auditShardHealth(options = {}) {
|
|
2800
|
+
if (!_encryptionKey) {
|
|
2801
|
+
throw new Error("Shard manager not initialized. Call initShardManager() first.");
|
|
2802
|
+
}
|
|
2803
|
+
const repair = options.repair === true;
|
|
2804
|
+
const dryRun = options.dryRun === true;
|
|
2805
|
+
const names = listShards();
|
|
2806
|
+
const shards = [];
|
|
2807
|
+
for (const name of names) {
|
|
2808
|
+
const dbPath = path7.join(SHARDS_DIR, `${name}.db`);
|
|
2809
|
+
const stat2 = statSync2(dbPath);
|
|
2810
|
+
const item = {
|
|
2811
|
+
name,
|
|
2812
|
+
path: dbPath,
|
|
2813
|
+
ok: false,
|
|
2814
|
+
unreadable: false,
|
|
2815
|
+
error: null,
|
|
2816
|
+
size: stat2.size,
|
|
2817
|
+
mtime: stat2.mtime.toISOString(),
|
|
2818
|
+
memoryCount: null
|
|
2819
|
+
};
|
|
2820
|
+
const client = createClient2({
|
|
2821
|
+
url: `file:${dbPath}`,
|
|
2822
|
+
encryptionKey: _encryptionKey
|
|
2823
|
+
});
|
|
2824
|
+
try {
|
|
2825
|
+
await client.execute("SELECT COUNT(*) as cnt FROM sqlite_schema");
|
|
2826
|
+
const hasMemories = await client.execute(
|
|
2827
|
+
"SELECT COUNT(*) as cnt FROM sqlite_schema WHERE type = 'table' AND name = 'memories'"
|
|
2828
|
+
);
|
|
2829
|
+
if (Number(hasMemories.rows[0]?.cnt ?? 0) > 0) {
|
|
2830
|
+
const mem = await client.execute("SELECT COUNT(*) as cnt FROM memories");
|
|
2831
|
+
item.memoryCount = Number(mem.rows[0]?.cnt ?? 0);
|
|
2832
|
+
}
|
|
2833
|
+
item.ok = true;
|
|
2834
|
+
} catch (err) {
|
|
2835
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2836
|
+
item.error = message;
|
|
2837
|
+
item.unreadable = /SQLITE_NOTADB|file is not a database/i.test(message);
|
|
2838
|
+
if (item.unreadable && repair && !dryRun) {
|
|
2839
|
+
client.close();
|
|
2840
|
+
_shards.delete(name);
|
|
2841
|
+
_shardLastAccess.delete(name);
|
|
2842
|
+
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
2843
|
+
const archivedPath = path7.join(SHARDS_DIR, `${name}.db.broken-${stamp}`);
|
|
2844
|
+
renameSync3(dbPath, archivedPath);
|
|
2845
|
+
item.archivedPath = archivedPath;
|
|
2846
|
+
}
|
|
2847
|
+
} finally {
|
|
2848
|
+
try {
|
|
2849
|
+
client.close();
|
|
2850
|
+
} catch {
|
|
2851
|
+
}
|
|
2852
|
+
}
|
|
2853
|
+
shards.push(item);
|
|
2854
|
+
}
|
|
2855
|
+
return {
|
|
2856
|
+
total: shards.length,
|
|
2857
|
+
ok: shards.filter((s) => s.ok).length,
|
|
2858
|
+
unreadable: shards.filter((s) => s.unreadable).length,
|
|
2859
|
+
archived: shards.filter((s) => Boolean(s.archivedPath)).length,
|
|
2860
|
+
shards
|
|
2861
|
+
};
|
|
2862
|
+
}
|
|
2798
2863
|
async function ensureShardSchema(client) {
|
|
2799
2864
|
await client.execute("PRAGMA journal_mode = WAL");
|
|
2800
2865
|
await client.execute("PRAGMA busy_timeout = 30000");
|
|
@@ -3442,6 +3507,15 @@ function readMachineId() {
|
|
|
3442
3507
|
return "";
|
|
3443
3508
|
}
|
|
3444
3509
|
}
|
|
3510
|
+
function encryptWithMachineKey(plaintext, machineKey) {
|
|
3511
|
+
const crypto3 = __require("crypto");
|
|
3512
|
+
const iv = crypto3.randomBytes(12);
|
|
3513
|
+
const cipher = crypto3.createCipheriv("aes-256-gcm", machineKey, iv);
|
|
3514
|
+
let encrypted = cipher.update(plaintext, "utf-8", "base64");
|
|
3515
|
+
encrypted += cipher.final("base64");
|
|
3516
|
+
const authTag = cipher.getAuthTag().toString("base64");
|
|
3517
|
+
return `${ENCRYPTED_PREFIX}${iv.toString("base64")}:${authTag}:${encrypted}`;
|
|
3518
|
+
}
|
|
3445
3519
|
function decryptWithMachineKey(encrypted, machineKey) {
|
|
3446
3520
|
if (!encrypted.startsWith(ENCRYPTED_PREFIX)) return null;
|
|
3447
3521
|
try {
|
|
@@ -3460,6 +3534,21 @@ function decryptWithMachineKey(encrypted, machineKey) {
|
|
|
3460
3534
|
return null;
|
|
3461
3535
|
}
|
|
3462
3536
|
}
|
|
3537
|
+
async function writeMachineBoundFileFallback(b64) {
|
|
3538
|
+
const dir = getKeyDir();
|
|
3539
|
+
await mkdir3(dir, { recursive: true });
|
|
3540
|
+
const keyPath = getKeyPath();
|
|
3541
|
+
const machineKey = deriveMachineKey();
|
|
3542
|
+
if (machineKey) {
|
|
3543
|
+
const encrypted = encryptWithMachineKey(b64, machineKey);
|
|
3544
|
+
await writeFile3(keyPath, encrypted + "\n", "utf-8");
|
|
3545
|
+
await chmod2(keyPath, 384);
|
|
3546
|
+
return "encrypted";
|
|
3547
|
+
}
|
|
3548
|
+
await writeFile3(keyPath, b64 + "\n", "utf-8");
|
|
3549
|
+
await chmod2(keyPath, 384);
|
|
3550
|
+
return "plaintext";
|
|
3551
|
+
}
|
|
3463
3552
|
async function getMasterKey() {
|
|
3464
3553
|
const nativeValue = macKeychainGet() ?? linuxSecretGet();
|
|
3465
3554
|
if (nativeValue) {
|
|
@@ -3511,6 +3600,20 @@ async function getMasterKey() {
|
|
|
3511
3600
|
const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
|
|
3512
3601
|
if (migrated) {
|
|
3513
3602
|
process.stderr.write("[keychain] Migrated key from file to native keychain.\n");
|
|
3603
|
+
try {
|
|
3604
|
+
await unlink(keyPath);
|
|
3605
|
+
process.stderr.write("[keychain] Removed legacy master.key file after native keychain migration.\n");
|
|
3606
|
+
} catch {
|
|
3607
|
+
}
|
|
3608
|
+
} else if (!content.startsWith(ENCRYPTED_PREFIX)) {
|
|
3609
|
+
const fallback = await writeMachineBoundFileFallback(b64Value);
|
|
3610
|
+
if (fallback === "encrypted") {
|
|
3611
|
+
process.stderr.write("[keychain] Upgraded legacy plaintext master.key to machine-bound encrypted fallback.\n");
|
|
3612
|
+
} else {
|
|
3613
|
+
process.stderr.write(
|
|
3614
|
+
"[keychain] WARNING: Could not encrypt legacy master.key \u2014 plaintext fallback remains.\n"
|
|
3615
|
+
);
|
|
3616
|
+
}
|
|
3514
3617
|
}
|
|
3515
3618
|
return key;
|
|
3516
3619
|
} catch (err) {
|
|
@@ -2725,6 +2725,7 @@ var init_database = __esm({
|
|
|
2725
2725
|
// src/lib/shard-manager.ts
|
|
2726
2726
|
var shard_manager_exports = {};
|
|
2727
2727
|
__export(shard_manager_exports, {
|
|
2728
|
+
auditShardHealth: () => auditShardHealth,
|
|
2728
2729
|
disposeShards: () => disposeShards,
|
|
2729
2730
|
ensureShardSchema: () => ensureShardSchema,
|
|
2730
2731
|
getOpenShardCount: () => getOpenShardCount,
|
|
@@ -2791,6 +2792,70 @@ function listShards() {
|
|
|
2791
2792
|
if (!existsSync7(SHARDS_DIR)) return [];
|
|
2792
2793
|
return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
|
|
2793
2794
|
}
|
|
2795
|
+
async function auditShardHealth(options = {}) {
|
|
2796
|
+
if (!_encryptionKey) {
|
|
2797
|
+
throw new Error("Shard manager not initialized. Call initShardManager() first.");
|
|
2798
|
+
}
|
|
2799
|
+
const repair = options.repair === true;
|
|
2800
|
+
const dryRun = options.dryRun === true;
|
|
2801
|
+
const names = listShards();
|
|
2802
|
+
const shards = [];
|
|
2803
|
+
for (const name of names) {
|
|
2804
|
+
const dbPath = path7.join(SHARDS_DIR, `${name}.db`);
|
|
2805
|
+
const stat = statSync2(dbPath);
|
|
2806
|
+
const item = {
|
|
2807
|
+
name,
|
|
2808
|
+
path: dbPath,
|
|
2809
|
+
ok: false,
|
|
2810
|
+
unreadable: false,
|
|
2811
|
+
error: null,
|
|
2812
|
+
size: stat.size,
|
|
2813
|
+
mtime: stat.mtime.toISOString(),
|
|
2814
|
+
memoryCount: null
|
|
2815
|
+
};
|
|
2816
|
+
const client = createClient2({
|
|
2817
|
+
url: `file:${dbPath}`,
|
|
2818
|
+
encryptionKey: _encryptionKey
|
|
2819
|
+
});
|
|
2820
|
+
try {
|
|
2821
|
+
await client.execute("SELECT COUNT(*) as cnt FROM sqlite_schema");
|
|
2822
|
+
const hasMemories = await client.execute(
|
|
2823
|
+
"SELECT COUNT(*) as cnt FROM sqlite_schema WHERE type = 'table' AND name = 'memories'"
|
|
2824
|
+
);
|
|
2825
|
+
if (Number(hasMemories.rows[0]?.cnt ?? 0) > 0) {
|
|
2826
|
+
const mem = await client.execute("SELECT COUNT(*) as cnt FROM memories");
|
|
2827
|
+
item.memoryCount = Number(mem.rows[0]?.cnt ?? 0);
|
|
2828
|
+
}
|
|
2829
|
+
item.ok = true;
|
|
2830
|
+
} catch (err) {
|
|
2831
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2832
|
+
item.error = message;
|
|
2833
|
+
item.unreadable = /SQLITE_NOTADB|file is not a database/i.test(message);
|
|
2834
|
+
if (item.unreadable && repair && !dryRun) {
|
|
2835
|
+
client.close();
|
|
2836
|
+
_shards.delete(name);
|
|
2837
|
+
_shardLastAccess.delete(name);
|
|
2838
|
+
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
2839
|
+
const archivedPath = path7.join(SHARDS_DIR, `${name}.db.broken-${stamp}`);
|
|
2840
|
+
renameSync3(dbPath, archivedPath);
|
|
2841
|
+
item.archivedPath = archivedPath;
|
|
2842
|
+
}
|
|
2843
|
+
} finally {
|
|
2844
|
+
try {
|
|
2845
|
+
client.close();
|
|
2846
|
+
} catch {
|
|
2847
|
+
}
|
|
2848
|
+
}
|
|
2849
|
+
shards.push(item);
|
|
2850
|
+
}
|
|
2851
|
+
return {
|
|
2852
|
+
total: shards.length,
|
|
2853
|
+
ok: shards.filter((s) => s.ok).length,
|
|
2854
|
+
unreadable: shards.filter((s) => s.unreadable).length,
|
|
2855
|
+
archived: shards.filter((s) => Boolean(s.archivedPath)).length,
|
|
2856
|
+
shards
|
|
2857
|
+
};
|
|
2858
|
+
}
|
|
2794
2859
|
async function ensureShardSchema(client) {
|
|
2795
2860
|
await client.execute("PRAGMA journal_mode = WAL");
|
|
2796
2861
|
await client.execute("PRAGMA busy_timeout = 30000");
|
|
@@ -3430,6 +3495,15 @@ function readMachineId() {
|
|
|
3430
3495
|
return "";
|
|
3431
3496
|
}
|
|
3432
3497
|
}
|
|
3498
|
+
function encryptWithMachineKey(plaintext, machineKey) {
|
|
3499
|
+
const crypto2 = __require("crypto");
|
|
3500
|
+
const iv = crypto2.randomBytes(12);
|
|
3501
|
+
const cipher = crypto2.createCipheriv("aes-256-gcm", machineKey, iv);
|
|
3502
|
+
let encrypted = cipher.update(plaintext, "utf-8", "base64");
|
|
3503
|
+
encrypted += cipher.final("base64");
|
|
3504
|
+
const authTag = cipher.getAuthTag().toString("base64");
|
|
3505
|
+
return `${ENCRYPTED_PREFIX}${iv.toString("base64")}:${authTag}:${encrypted}`;
|
|
3506
|
+
}
|
|
3433
3507
|
function decryptWithMachineKey(encrypted, machineKey) {
|
|
3434
3508
|
if (!encrypted.startsWith(ENCRYPTED_PREFIX)) return null;
|
|
3435
3509
|
try {
|
|
@@ -3448,6 +3522,21 @@ function decryptWithMachineKey(encrypted, machineKey) {
|
|
|
3448
3522
|
return null;
|
|
3449
3523
|
}
|
|
3450
3524
|
}
|
|
3525
|
+
async function writeMachineBoundFileFallback(b64) {
|
|
3526
|
+
const dir = getKeyDir();
|
|
3527
|
+
await mkdir3(dir, { recursive: true });
|
|
3528
|
+
const keyPath = getKeyPath();
|
|
3529
|
+
const machineKey = deriveMachineKey();
|
|
3530
|
+
if (machineKey) {
|
|
3531
|
+
const encrypted = encryptWithMachineKey(b64, machineKey);
|
|
3532
|
+
await writeFile3(keyPath, encrypted + "\n", "utf-8");
|
|
3533
|
+
await chmod2(keyPath, 384);
|
|
3534
|
+
return "encrypted";
|
|
3535
|
+
}
|
|
3536
|
+
await writeFile3(keyPath, b64 + "\n", "utf-8");
|
|
3537
|
+
await chmod2(keyPath, 384);
|
|
3538
|
+
return "plaintext";
|
|
3539
|
+
}
|
|
3451
3540
|
async function getMasterKey() {
|
|
3452
3541
|
const nativeValue = macKeychainGet() ?? linuxSecretGet();
|
|
3453
3542
|
if (nativeValue) {
|
|
@@ -3499,6 +3588,20 @@ async function getMasterKey() {
|
|
|
3499
3588
|
const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
|
|
3500
3589
|
if (migrated) {
|
|
3501
3590
|
process.stderr.write("[keychain] Migrated key from file to native keychain.\n");
|
|
3591
|
+
try {
|
|
3592
|
+
await unlink(keyPath);
|
|
3593
|
+
process.stderr.write("[keychain] Removed legacy master.key file after native keychain migration.\n");
|
|
3594
|
+
} catch {
|
|
3595
|
+
}
|
|
3596
|
+
} else if (!content.startsWith(ENCRYPTED_PREFIX)) {
|
|
3597
|
+
const fallback = await writeMachineBoundFileFallback(b64Value);
|
|
3598
|
+
if (fallback === "encrypted") {
|
|
3599
|
+
process.stderr.write("[keychain] Upgraded legacy plaintext master.key to machine-bound encrypted fallback.\n");
|
|
3600
|
+
} else {
|
|
3601
|
+
process.stderr.write(
|
|
3602
|
+
"[keychain] WARNING: Could not encrypt legacy master.key \u2014 plaintext fallback remains.\n"
|
|
3603
|
+
);
|
|
3604
|
+
}
|
|
3502
3605
|
}
|
|
3503
3606
|
return key;
|
|
3504
3607
|
} catch (err) {
|
|
@@ -2908,6 +2908,15 @@ function readMachineId() {
|
|
|
2908
2908
|
return "";
|
|
2909
2909
|
}
|
|
2910
2910
|
}
|
|
2911
|
+
function encryptWithMachineKey(plaintext, machineKey) {
|
|
2912
|
+
const crypto2 = __require("crypto");
|
|
2913
|
+
const iv = crypto2.randomBytes(12);
|
|
2914
|
+
const cipher = crypto2.createCipheriv("aes-256-gcm", machineKey, iv);
|
|
2915
|
+
let encrypted = cipher.update(plaintext, "utf-8", "base64");
|
|
2916
|
+
encrypted += cipher.final("base64");
|
|
2917
|
+
const authTag = cipher.getAuthTag().toString("base64");
|
|
2918
|
+
return `${ENCRYPTED_PREFIX}${iv.toString("base64")}:${authTag}:${encrypted}`;
|
|
2919
|
+
}
|
|
2911
2920
|
function decryptWithMachineKey(encrypted, machineKey) {
|
|
2912
2921
|
if (!encrypted.startsWith(ENCRYPTED_PREFIX)) return null;
|
|
2913
2922
|
try {
|
|
@@ -2926,6 +2935,21 @@ function decryptWithMachineKey(encrypted, machineKey) {
|
|
|
2926
2935
|
return null;
|
|
2927
2936
|
}
|
|
2928
2937
|
}
|
|
2938
|
+
async function writeMachineBoundFileFallback(b64) {
|
|
2939
|
+
const dir = getKeyDir();
|
|
2940
|
+
await mkdir3(dir, { recursive: true });
|
|
2941
|
+
const keyPath = getKeyPath();
|
|
2942
|
+
const machineKey = deriveMachineKey();
|
|
2943
|
+
if (machineKey) {
|
|
2944
|
+
const encrypted = encryptWithMachineKey(b64, machineKey);
|
|
2945
|
+
await writeFile3(keyPath, encrypted + "\n", "utf-8");
|
|
2946
|
+
await chmod2(keyPath, 384);
|
|
2947
|
+
return "encrypted";
|
|
2948
|
+
}
|
|
2949
|
+
await writeFile3(keyPath, b64 + "\n", "utf-8");
|
|
2950
|
+
await chmod2(keyPath, 384);
|
|
2951
|
+
return "plaintext";
|
|
2952
|
+
}
|
|
2929
2953
|
async function getMasterKey() {
|
|
2930
2954
|
const nativeValue = macKeychainGet() ?? linuxSecretGet();
|
|
2931
2955
|
if (nativeValue) {
|
|
@@ -2977,6 +3001,20 @@ async function getMasterKey() {
|
|
|
2977
3001
|
const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);
|
|
2978
3002
|
if (migrated) {
|
|
2979
3003
|
process.stderr.write("[keychain] Migrated key from file to native keychain.\n");
|
|
3004
|
+
try {
|
|
3005
|
+
await unlink(keyPath);
|
|
3006
|
+
process.stderr.write("[keychain] Removed legacy master.key file after native keychain migration.\n");
|
|
3007
|
+
} catch {
|
|
3008
|
+
}
|
|
3009
|
+
} else if (!content.startsWith(ENCRYPTED_PREFIX)) {
|
|
3010
|
+
const fallback = await writeMachineBoundFileFallback(b64Value);
|
|
3011
|
+
if (fallback === "encrypted") {
|
|
3012
|
+
process.stderr.write("[keychain] Upgraded legacy plaintext master.key to machine-bound encrypted fallback.\n");
|
|
3013
|
+
} else {
|
|
3014
|
+
process.stderr.write(
|
|
3015
|
+
"[keychain] WARNING: Could not encrypt legacy master.key \u2014 plaintext fallback remains.\n"
|
|
3016
|
+
);
|
|
3017
|
+
}
|
|
2980
3018
|
}
|
|
2981
3019
|
return key;
|
|
2982
3020
|
} catch (err) {
|
|
@@ -3249,6 +3287,7 @@ var init_memory_write_governor = __esm({
|
|
|
3249
3287
|
// src/lib/shard-manager.ts
|
|
3250
3288
|
var shard_manager_exports = {};
|
|
3251
3289
|
__export(shard_manager_exports, {
|
|
3290
|
+
auditShardHealth: () => auditShardHealth,
|
|
3252
3291
|
disposeShards: () => disposeShards,
|
|
3253
3292
|
ensureShardSchema: () => ensureShardSchema,
|
|
3254
3293
|
getOpenShardCount: () => getOpenShardCount,
|
|
@@ -3315,6 +3354,70 @@ function listShards() {
|
|
|
3315
3354
|
if (!existsSync7(SHARDS_DIR)) return [];
|
|
3316
3355
|
return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
|
|
3317
3356
|
}
|
|
3357
|
+
async function auditShardHealth(options = {}) {
|
|
3358
|
+
if (!_encryptionKey) {
|
|
3359
|
+
throw new Error("Shard manager not initialized. Call initShardManager() first.");
|
|
3360
|
+
}
|
|
3361
|
+
const repair = options.repair === true;
|
|
3362
|
+
const dryRun = options.dryRun === true;
|
|
3363
|
+
const names = listShards();
|
|
3364
|
+
const shards = [];
|
|
3365
|
+
for (const name of names) {
|
|
3366
|
+
const dbPath = path7.join(SHARDS_DIR, `${name}.db`);
|
|
3367
|
+
const stat = statSync2(dbPath);
|
|
3368
|
+
const item = {
|
|
3369
|
+
name,
|
|
3370
|
+
path: dbPath,
|
|
3371
|
+
ok: false,
|
|
3372
|
+
unreadable: false,
|
|
3373
|
+
error: null,
|
|
3374
|
+
size: stat.size,
|
|
3375
|
+
mtime: stat.mtime.toISOString(),
|
|
3376
|
+
memoryCount: null
|
|
3377
|
+
};
|
|
3378
|
+
const client = createClient2({
|
|
3379
|
+
url: `file:${dbPath}`,
|
|
3380
|
+
encryptionKey: _encryptionKey
|
|
3381
|
+
});
|
|
3382
|
+
try {
|
|
3383
|
+
await client.execute("SELECT COUNT(*) as cnt FROM sqlite_schema");
|
|
3384
|
+
const hasMemories = await client.execute(
|
|
3385
|
+
"SELECT COUNT(*) as cnt FROM sqlite_schema WHERE type = 'table' AND name = 'memories'"
|
|
3386
|
+
);
|
|
3387
|
+
if (Number(hasMemories.rows[0]?.cnt ?? 0) > 0) {
|
|
3388
|
+
const mem = await client.execute("SELECT COUNT(*) as cnt FROM memories");
|
|
3389
|
+
item.memoryCount = Number(mem.rows[0]?.cnt ?? 0);
|
|
3390
|
+
}
|
|
3391
|
+
item.ok = true;
|
|
3392
|
+
} catch (err) {
|
|
3393
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
3394
|
+
item.error = message;
|
|
3395
|
+
item.unreadable = /SQLITE_NOTADB|file is not a database/i.test(message);
|
|
3396
|
+
if (item.unreadable && repair && !dryRun) {
|
|
3397
|
+
client.close();
|
|
3398
|
+
_shards.delete(name);
|
|
3399
|
+
_shardLastAccess.delete(name);
|
|
3400
|
+
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
3401
|
+
const archivedPath = path7.join(SHARDS_DIR, `${name}.db.broken-${stamp}`);
|
|
3402
|
+
renameSync3(dbPath, archivedPath);
|
|
3403
|
+
item.archivedPath = archivedPath;
|
|
3404
|
+
}
|
|
3405
|
+
} finally {
|
|
3406
|
+
try {
|
|
3407
|
+
client.close();
|
|
3408
|
+
} catch {
|
|
3409
|
+
}
|
|
3410
|
+
}
|
|
3411
|
+
shards.push(item);
|
|
3412
|
+
}
|
|
3413
|
+
return {
|
|
3414
|
+
total: shards.length,
|
|
3415
|
+
ok: shards.filter((s) => s.ok).length,
|
|
3416
|
+
unreadable: shards.filter((s) => s.unreadable).length,
|
|
3417
|
+
archived: shards.filter((s) => Boolean(s.archivedPath)).length,
|
|
3418
|
+
shards
|
|
3419
|
+
};
|
|
3420
|
+
}
|
|
3318
3421
|
async function ensureShardSchema(client) {
|
|
3319
3422
|
await client.execute("PRAGMA journal_mode = WAL");
|
|
3320
3423
|
await client.execute("PRAGMA busy_timeout = 30000");
|