@askexenow/exe-os 0.9.7 → 0.9.9

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.
Files changed (101) hide show
  1. package/dist/bin/backfill-conversations.js +953 -105
  2. package/dist/bin/backfill-responses.js +952 -104
  3. package/dist/bin/backfill-vectors.js +956 -108
  4. package/dist/bin/cleanup-stale-review-tasks.js +802 -58
  5. package/dist/bin/cli.js +2292 -1070
  6. package/dist/bin/exe-agent-config.js +157 -101
  7. package/dist/bin/exe-agent.js +55 -29
  8. package/dist/bin/exe-assign.js +940 -92
  9. package/dist/bin/exe-boot.js +1424 -442
  10. package/dist/bin/exe-call.js +240 -141
  11. package/dist/bin/exe-cloud.js +198 -70
  12. package/dist/bin/exe-dispatch.js +951 -192
  13. package/dist/bin/exe-doctor.js +791 -51
  14. package/dist/bin/exe-export-behaviors.js +790 -42
  15. package/dist/bin/exe-forget.js +771 -31
  16. package/dist/bin/exe-gateway.js +1592 -521
  17. package/dist/bin/exe-heartbeat.js +850 -109
  18. package/dist/bin/exe-kill.js +783 -35
  19. package/dist/bin/exe-launch-agent.js +1030 -107
  20. package/dist/bin/exe-link.js +916 -110
  21. package/dist/bin/exe-new-employee.js +526 -217
  22. package/dist/bin/exe-pending-messages.js +1046 -62
  23. package/dist/bin/exe-pending-notifications.js +1318 -111
  24. package/dist/bin/exe-pending-reviews.js +1040 -72
  25. package/dist/bin/exe-rename.js +772 -59
  26. package/dist/bin/exe-review.js +772 -32
  27. package/dist/bin/exe-search.js +982 -128
  28. package/dist/bin/exe-session-cleanup.js +1180 -306
  29. package/dist/bin/exe-settings.js +185 -105
  30. package/dist/bin/exe-start-codex.js +886 -132
  31. package/dist/bin/exe-start-opencode.js +873 -119
  32. package/dist/bin/exe-status.js +803 -59
  33. package/dist/bin/exe-team.js +772 -32
  34. package/dist/bin/git-sweep.js +1046 -223
  35. package/dist/bin/graph-backfill.js +779 -31
  36. package/dist/bin/graph-export.js +785 -37
  37. package/dist/bin/install.js +632 -200
  38. package/dist/bin/scan-tasks.js +1055 -232
  39. package/dist/bin/setup.js +1419 -320
  40. package/dist/bin/shard-migrate.js +783 -35
  41. package/dist/bin/update.js +138 -49
  42. package/dist/bin/wiki-sync.js +782 -34
  43. package/dist/gateway/index.js +1444 -449
  44. package/dist/hooks/bug-report-worker.js +1141 -269
  45. package/dist/hooks/codex-stop-task-finalizer.js +4678 -0
  46. package/dist/hooks/commit-complete.js +1044 -221
  47. package/dist/hooks/error-recall.js +989 -135
  48. package/dist/hooks/exe-heartbeat-hook.js +99 -75
  49. package/dist/hooks/ingest-worker.js +4176 -3226
  50. package/dist/hooks/ingest.js +920 -168
  51. package/dist/hooks/instructions-loaded.js +874 -70
  52. package/dist/hooks/notification.js +860 -56
  53. package/dist/hooks/post-compact.js +881 -73
  54. package/dist/hooks/pre-compact.js +1050 -227
  55. package/dist/hooks/pre-tool-use.js +1084 -159
  56. package/dist/hooks/prompt-ingest-worker.js +1089 -164
  57. package/dist/hooks/prompt-submit.js +1469 -515
  58. package/dist/hooks/response-ingest-worker.js +1104 -179
  59. package/dist/hooks/session-end.js +1085 -251
  60. package/dist/hooks/session-start.js +1241 -231
  61. package/dist/hooks/stop.js +935 -109
  62. package/dist/hooks/subagent-stop.js +881 -73
  63. package/dist/hooks/summary-worker.js +1323 -307
  64. package/dist/index.js +1449 -452
  65. package/dist/lib/agent-config.js +28 -6
  66. package/dist/lib/cloud-sync.js +909 -115
  67. package/dist/lib/config.js +30 -10
  68. package/dist/lib/consolidation.js +42 -9
  69. package/dist/lib/database.js +739 -33
  70. package/dist/lib/db-daemon-client.js +73 -19
  71. package/dist/lib/db.js +2359 -0
  72. package/dist/lib/device-registry.js +760 -47
  73. package/dist/lib/embedder.js +201 -73
  74. package/dist/lib/employee-templates.js +30 -4
  75. package/dist/lib/employees.js +290 -86
  76. package/dist/lib/exe-daemon-client.js +187 -83
  77. package/dist/lib/exe-daemon.js +1696 -616
  78. package/dist/lib/hybrid-search.js +982 -128
  79. package/dist/lib/identity.js +43 -13
  80. package/dist/lib/license.js +133 -48
  81. package/dist/lib/messaging.js +167 -80
  82. package/dist/lib/reminders.js +35 -5
  83. package/dist/lib/schedules.js +772 -32
  84. package/dist/lib/skill-learning.js +54 -7
  85. package/dist/lib/store.js +779 -31
  86. package/dist/lib/task-router.js +94 -73
  87. package/dist/lib/tasks.js +298 -225
  88. package/dist/lib/tmux-routing.js +246 -172
  89. package/dist/lib/token-spend.js +52 -14
  90. package/dist/mcp/server.js +2893 -850
  91. package/dist/mcp/tools/complete-reminder.js +35 -5
  92. package/dist/mcp/tools/create-reminder.js +35 -5
  93. package/dist/mcp/tools/create-task.js +507 -323
  94. package/dist/mcp/tools/deactivate-behavior.js +40 -10
  95. package/dist/mcp/tools/list-reminders.js +35 -5
  96. package/dist/mcp/tools/list-tasks.js +277 -104
  97. package/dist/mcp/tools/send-message.js +129 -56
  98. package/dist/mcp/tools/update-task.js +1864 -188
  99. package/dist/runtime/index.js +1083 -259
  100. package/dist/tui/App.js +1501 -434
  101. package/package.json +3 -2
@@ -1,5 +1,7 @@
1
1
  var __defProp = Object.defineProperty;
2
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
2
3
  var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
3
5
  var __esm = (fn, res) => function __init() {
4
6
  return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
5
7
  };
@@ -7,6 +9,15 @@ var __export = (target, all) => {
7
9
  for (var name in all)
8
10
  __defProp(target, name, { get: all[name], enumerable: true });
9
11
  };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
10
21
 
11
22
  // src/lib/db-retry.ts
12
23
  function isBusyError(err) {
@@ -63,9 +74,34 @@ var init_db_retry = __esm({
63
74
  }
64
75
  });
65
76
 
77
+ // src/lib/secure-files.ts
78
+ import { chmodSync, existsSync, mkdirSync } from "fs";
79
+ import { chmod, mkdir } from "fs/promises";
80
+ async function ensurePrivateDir(dirPath) {
81
+ await mkdir(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
82
+ try {
83
+ await chmod(dirPath, PRIVATE_DIR_MODE);
84
+ } catch {
85
+ }
86
+ }
87
+ async function enforcePrivateFile(filePath) {
88
+ try {
89
+ await chmod(filePath, PRIVATE_FILE_MODE);
90
+ } catch {
91
+ }
92
+ }
93
+ var PRIVATE_DIR_MODE, PRIVATE_FILE_MODE;
94
+ var init_secure_files = __esm({
95
+ "src/lib/secure-files.ts"() {
96
+ "use strict";
97
+ PRIVATE_DIR_MODE = 448;
98
+ PRIVATE_FILE_MODE = 384;
99
+ }
100
+ });
101
+
66
102
  // src/lib/config.ts
67
- import { readFile, writeFile, mkdir, chmod } from "fs/promises";
68
- import { readFileSync, existsSync, renameSync } from "fs";
103
+ import { readFile, writeFile } from "fs/promises";
104
+ import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
69
105
  import path from "path";
70
106
  import os from "os";
71
107
  function resolveDataDir() {
@@ -73,7 +109,7 @@ function resolveDataDir() {
73
109
  if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
74
110
  const newDir = path.join(os.homedir(), ".exe-os");
75
111
  const legacyDir = path.join(os.homedir(), ".exe-mem");
76
- if (!existsSync(newDir) && existsSync(legacyDir)) {
112
+ if (!existsSync2(newDir) && existsSync2(legacyDir)) {
77
113
  try {
78
114
  renameSync(legacyDir, newDir);
79
115
  process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
@@ -136,9 +172,9 @@ function normalizeAutoUpdate(raw) {
136
172
  }
137
173
  async function loadConfig() {
138
174
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
139
- await mkdir(dir, { recursive: true });
175
+ await ensurePrivateDir(dir);
140
176
  const configPath = path.join(dir, "config.json");
141
- if (!existsSync(configPath)) {
177
+ if (!existsSync2(configPath)) {
142
178
  return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
143
179
  }
144
180
  const raw = await readFile(configPath, "utf-8");
@@ -151,6 +187,7 @@ async function loadConfig() {
151
187
  `);
152
188
  try {
153
189
  await writeFile(configPath, JSON.stringify(migratedCfg, null, 2) + "\n");
190
+ await enforcePrivateFile(configPath);
154
191
  } catch {
155
192
  }
156
193
  }
@@ -170,6 +207,7 @@ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CON
170
207
  var init_config = __esm({
171
208
  "src/lib/config.ts"() {
172
209
  "use strict";
210
+ init_secure_files();
173
211
  EXE_AI_DIR = resolveDataDir();
174
212
  DB_PATH = path.join(EXE_AI_DIR, "memories.db");
175
213
  MODELS_DIR = path.join(EXE_AI_DIR, "models");
@@ -248,7 +286,7 @@ var init_config = __esm({
248
286
 
249
287
  // src/lib/employees.ts
250
288
  import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
251
- import { existsSync as existsSync2, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
289
+ import { existsSync as existsSync3, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
252
290
  import { execSync } from "child_process";
253
291
  import path2 from "path";
254
292
  import os2 from "os";
@@ -265,14 +303,14 @@ function getCoordinatorName(employees = loadEmployeesSync()) {
265
303
  return getCoordinatorEmployee(employees)?.name ?? DEFAULT_COORDINATOR_TEMPLATE_NAME;
266
304
  }
267
305
  function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
268
- if (!existsSync2(employeesPath)) return [];
306
+ if (!existsSync3(employeesPath)) return [];
269
307
  try {
270
308
  return JSON.parse(readFileSync2(employeesPath, "utf-8"));
271
309
  } catch {
272
310
  return [];
273
311
  }
274
312
  }
275
- var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE;
313
+ var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, IDENTITY_DIR;
276
314
  var init_employees = __esm({
277
315
  "src/lib/employees.ts"() {
278
316
  "use strict";
@@ -280,12 +318,609 @@ var init_employees = __esm({
280
318
  EMPLOYEES_PATH = path2.join(EXE_AI_DIR, "exe-employees.json");
281
319
  DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
282
320
  COORDINATOR_ROLE = "COO";
321
+ IDENTITY_DIR = path2.join(EXE_AI_DIR, "identity");
322
+ }
323
+ });
324
+
325
+ // src/lib/database-adapter.ts
326
+ import os3 from "os";
327
+ import path3 from "path";
328
+ import { createRequire } from "module";
329
+ import { pathToFileURL } from "url";
330
+ function quotedIdentifier(identifier) {
331
+ return `"${identifier.replace(/"/g, '""')}"`;
332
+ }
333
+ function unqualifiedTableName(name) {
334
+ const raw = name.trim().replace(/^"|"$/g, "");
335
+ const parts = raw.split(".");
336
+ return parts[parts.length - 1].replace(/^"|"$/g, "").toLowerCase();
337
+ }
338
+ function stripTrailingSemicolon(sql) {
339
+ return sql.trim().replace(/;+\s*$/u, "");
340
+ }
341
+ function appendClause(sql, clause) {
342
+ const trimmed = stripTrailingSemicolon(sql);
343
+ const returningMatch = /\sRETURNING\b[\s\S]*$/iu.exec(trimmed);
344
+ if (!returningMatch) {
345
+ return `${trimmed}${clause}`;
346
+ }
347
+ const idx = returningMatch.index;
348
+ return `${trimmed.slice(0, idx)}${clause}${trimmed.slice(idx)}`;
349
+ }
350
+ function normalizeStatement(stmt) {
351
+ if (typeof stmt === "string") {
352
+ return { kind: "positional", sql: stmt, args: [] };
353
+ }
354
+ const sql = stmt.sql;
355
+ if (Array.isArray(stmt.args) || stmt.args === void 0) {
356
+ return { kind: "positional", sql, args: stmt.args ?? [] };
357
+ }
358
+ return { kind: "named", sql, args: stmt.args };
359
+ }
360
+ function rewriteBooleanLiterals(sql) {
361
+ let out = sql;
362
+ for (const column of BOOLEAN_COLUMN_NAMES) {
363
+ const scoped = `((?:\\b[a-z_][a-z0-9_]*\\.)?${column})`;
364
+ out = out.replace(new RegExp(`${scoped}\\s*=\\s*0\\b`, "giu"), "$1 = FALSE");
365
+ out = out.replace(new RegExp(`${scoped}\\s*=\\s*1\\b`, "giu"), "$1 = TRUE");
366
+ out = out.replace(new RegExp(`${scoped}\\s*!=\\s*0\\b`, "giu"), "$1 != FALSE");
367
+ out = out.replace(new RegExp(`${scoped}\\s*!=\\s*1\\b`, "giu"), "$1 != TRUE");
368
+ out = out.replace(new RegExp(`${scoped}\\s*<>\\s*0\\b`, "giu"), "$1 <> FALSE");
369
+ out = out.replace(new RegExp(`${scoped}\\s*<>\\s*1\\b`, "giu"), "$1 <> TRUE");
370
+ }
371
+ return out;
372
+ }
373
+ function rewriteInsertOrIgnore(sql) {
374
+ if (!/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu.test(sql)) {
375
+ return sql;
376
+ }
377
+ const replaced = sql.replace(/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu, "INSERT INTO");
378
+ return /\bON\s+CONFLICT\b/iu.test(replaced) ? replaced : appendClause(replaced, " ON CONFLICT DO NOTHING");
379
+ }
380
+ function rewriteInsertOrReplace(sql) {
381
+ const match = /^\s*INSERT\s+OR\s+REPLACE\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)([\s\S]*)$/iu.exec(sql);
382
+ if (!match) {
383
+ return sql;
384
+ }
385
+ const rawTable = match[1];
386
+ const rawColumns = match[2];
387
+ const remainder = match[3];
388
+ const tableName = unqualifiedTableName(rawTable);
389
+ const conflictKeys = UPSERT_KEYS[tableName];
390
+ if (!conflictKeys?.length) {
391
+ return sql;
392
+ }
393
+ const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
394
+ const updateColumns = columns.filter((col) => !conflictKeys.includes(col));
395
+ const conflictTarget = conflictKeys.map(quotedIdentifier).join(", ");
396
+ const updateClause = updateColumns.length === 0 ? " DO NOTHING" : ` DO UPDATE SET ${updateColumns.map((col) => `${quotedIdentifier(col)} = EXCLUDED.${quotedIdentifier(col)}`).join(", ")}`;
397
+ return `INSERT INTO ${rawTable} (${rawColumns})${appendClause(remainder, ` ON CONFLICT (${conflictTarget})${updateClause}`)}`;
398
+ }
399
+ function rewriteSql(sql) {
400
+ let out = sql;
401
+ out = out.replace(/\bdatetime\(\s*['"]now['"]\s*\)/giu, "CURRENT_TIMESTAMP");
402
+ out = out.replace(/\bvector32\s*\(\s*\?\s*\)/giu, "?");
403
+ out = rewriteBooleanLiterals(out);
404
+ out = rewriteInsertOrReplace(out);
405
+ out = rewriteInsertOrIgnore(out);
406
+ return stripTrailingSemicolon(out);
407
+ }
408
+ function toBoolean(value) {
409
+ if (value === null || value === void 0) return value;
410
+ if (typeof value === "boolean") return value;
411
+ if (typeof value === "number") return value !== 0;
412
+ if (typeof value === "bigint") return value !== 0n;
413
+ if (typeof value === "string") {
414
+ const normalized = value.trim().toLowerCase();
415
+ if (normalized === "0" || normalized === "false") return false;
416
+ if (normalized === "1" || normalized === "true") return true;
417
+ }
418
+ return Boolean(value);
419
+ }
420
+ function countQuestionMarks(sql, end) {
421
+ let count = 0;
422
+ let inSingle = false;
423
+ let inDouble = false;
424
+ let inLineComment = false;
425
+ let inBlockComment = false;
426
+ for (let i = 0; i < end; i++) {
427
+ const ch = sql[i];
428
+ const next = sql[i + 1];
429
+ if (inLineComment) {
430
+ if (ch === "\n") inLineComment = false;
431
+ continue;
432
+ }
433
+ if (inBlockComment) {
434
+ if (ch === "*" && next === "/") {
435
+ inBlockComment = false;
436
+ i += 1;
437
+ }
438
+ continue;
439
+ }
440
+ if (!inSingle && !inDouble && ch === "-" && next === "-") {
441
+ inLineComment = true;
442
+ i += 1;
443
+ continue;
444
+ }
445
+ if (!inSingle && !inDouble && ch === "/" && next === "*") {
446
+ inBlockComment = true;
447
+ i += 1;
448
+ continue;
449
+ }
450
+ if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
451
+ inSingle = !inSingle;
452
+ continue;
453
+ }
454
+ if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
455
+ inDouble = !inDouble;
456
+ continue;
457
+ }
458
+ if (!inSingle && !inDouble && ch === "?") {
459
+ count += 1;
460
+ }
461
+ }
462
+ return count;
463
+ }
464
+ function findBooleanPlaceholderIndexes(sql) {
465
+ const indexes = /* @__PURE__ */ new Set();
466
+ for (const column of BOOLEAN_COLUMN_NAMES) {
467
+ const pattern = new RegExp(`(?:\\b[a-z_][a-z0-9_]*\\.)?${column}\\s*=\\s*\\?`, "giu");
468
+ for (const match of sql.matchAll(pattern)) {
469
+ const matchText = match[0];
470
+ const qIndex = match.index + matchText.lastIndexOf("?");
471
+ indexes.add(countQuestionMarks(sql, qIndex + 1));
472
+ }
473
+ }
474
+ return indexes;
475
+ }
476
+ function coerceInsertBooleanArgs(sql, args) {
477
+ const match = /^\s*INSERT(?:\s+OR\s+(?:IGNORE|REPLACE))?\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)/iu.exec(sql);
478
+ if (!match) return;
479
+ const rawTable = match[1];
480
+ const rawColumns = match[2];
481
+ const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
482
+ if (!boolColumns?.size) return;
483
+ const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
484
+ for (const [index, column] of columns.entries()) {
485
+ if (boolColumns.has(column) && index < args.length) {
486
+ args[index] = toBoolean(args[index]);
487
+ }
488
+ }
489
+ }
490
+ function coerceUpdateBooleanArgs(sql, args) {
491
+ const match = /^\s*UPDATE\s+([A-Za-z0-9_."]+)\s+SET\s+([\s\S]+?)(?:\s+WHERE\b|$)/iu.exec(sql);
492
+ if (!match) return;
493
+ const rawTable = match[1];
494
+ const setClause = match[2];
495
+ const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
496
+ if (!boolColumns?.size) return;
497
+ const assignments = setClause.split(",");
498
+ let placeholderIndex = 0;
499
+ for (const assignment of assignments) {
500
+ if (!assignment.includes("?")) continue;
501
+ placeholderIndex += 1;
502
+ const colMatch = /^\s*(?:[A-Za-z_][A-Za-z0-9_]*\.)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*\?/iu.exec(assignment);
503
+ if (colMatch && boolColumns.has(colMatch[1])) {
504
+ args[placeholderIndex - 1] = toBoolean(args[placeholderIndex - 1]);
505
+ }
506
+ }
507
+ }
508
+ function coerceBooleanArgs(sql, args) {
509
+ const nextArgs = [...args];
510
+ coerceInsertBooleanArgs(sql, nextArgs);
511
+ coerceUpdateBooleanArgs(sql, nextArgs);
512
+ const placeholderIndexes = findBooleanPlaceholderIndexes(sql);
513
+ for (const index of placeholderIndexes) {
514
+ if (index > 0 && index <= nextArgs.length) {
515
+ nextArgs[index - 1] = toBoolean(nextArgs[index - 1]);
516
+ }
517
+ }
518
+ return nextArgs;
519
+ }
520
+ function convertQuestionMarksToDollarParams(sql) {
521
+ let out = "";
522
+ let placeholder = 0;
523
+ let inSingle = false;
524
+ let inDouble = false;
525
+ let inLineComment = false;
526
+ let inBlockComment = false;
527
+ for (let i = 0; i < sql.length; i++) {
528
+ const ch = sql[i];
529
+ const next = sql[i + 1];
530
+ if (inLineComment) {
531
+ out += ch;
532
+ if (ch === "\n") inLineComment = false;
533
+ continue;
534
+ }
535
+ if (inBlockComment) {
536
+ out += ch;
537
+ if (ch === "*" && next === "/") {
538
+ out += next;
539
+ inBlockComment = false;
540
+ i += 1;
541
+ }
542
+ continue;
543
+ }
544
+ if (!inSingle && !inDouble && ch === "-" && next === "-") {
545
+ out += ch + next;
546
+ inLineComment = true;
547
+ i += 1;
548
+ continue;
549
+ }
550
+ if (!inSingle && !inDouble && ch === "/" && next === "*") {
551
+ out += ch + next;
552
+ inBlockComment = true;
553
+ i += 1;
554
+ continue;
555
+ }
556
+ if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
557
+ inSingle = !inSingle;
558
+ out += ch;
559
+ continue;
560
+ }
561
+ if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
562
+ inDouble = !inDouble;
563
+ out += ch;
564
+ continue;
565
+ }
566
+ if (!inSingle && !inDouble && ch === "?") {
567
+ placeholder += 1;
568
+ out += `$${placeholder}`;
569
+ continue;
570
+ }
571
+ out += ch;
572
+ }
573
+ return out;
574
+ }
575
+ function translateStatementForPostgres(stmt) {
576
+ const normalized = normalizeStatement(stmt);
577
+ if (normalized.kind === "named") {
578
+ throw new Error("Named SQL parameters are not supported by the Prisma adapter.");
579
+ }
580
+ const rewrittenSql = rewriteSql(normalized.sql);
581
+ const coercedArgs = coerceBooleanArgs(rewrittenSql, normalized.args);
582
+ return {
583
+ sql: convertQuestionMarksToDollarParams(rewrittenSql),
584
+ args: coercedArgs
585
+ };
586
+ }
587
+ function shouldBypassPostgres(stmt) {
588
+ const normalized = normalizeStatement(stmt);
589
+ if (normalized.kind === "named") {
590
+ return true;
591
+ }
592
+ return IMMEDIATE_FALLBACK_PATTERNS.some((pattern) => pattern.test(normalized.sql));
593
+ }
594
+ function shouldFallbackOnError(error) {
595
+ const message = error instanceof Error ? error.message : String(error);
596
+ return /42P01|42883|42601|does not exist|syntax error|not supported|Named SQL parameters are not supported/iu.test(message);
597
+ }
598
+ function isReadQuery(sql) {
599
+ const trimmed = sql.trimStart();
600
+ return /^(SELECT|WITH|SHOW|EXPLAIN|VALUES)\b/iu.test(trimmed) || /\bRETURNING\b/iu.test(trimmed);
601
+ }
602
+ function buildRow(row, columns) {
603
+ const values = columns.map((column) => row[column]);
604
+ return Object.assign(values, row);
605
+ }
606
+ function buildResultSet(rows, rowsAffected = 0) {
607
+ const columns = rows[0] ? Object.keys(rows[0]) : [];
608
+ const resultRows = rows.map((row) => buildRow(row, columns));
609
+ return {
610
+ columns,
611
+ columnTypes: columns.map(() => ""),
612
+ rows: resultRows,
613
+ rowsAffected,
614
+ lastInsertRowid: void 0,
615
+ toJSON() {
616
+ return {
617
+ columns,
618
+ columnTypes: columns.map(() => ""),
619
+ rows,
620
+ rowsAffected,
621
+ lastInsertRowid: void 0
622
+ };
623
+ }
624
+ };
625
+ }
626
+ async function loadPrismaClient() {
627
+ if (!prismaClientPromise) {
628
+ prismaClientPromise = (async () => {
629
+ const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
630
+ if (explicitPath) {
631
+ const module2 = await import(pathToFileURL(explicitPath).href);
632
+ const PrismaClient2 = module2.PrismaClient ?? module2.default?.PrismaClient;
633
+ if (!PrismaClient2) {
634
+ throw new Error(`No PrismaClient export found at ${explicitPath}`);
635
+ }
636
+ return new PrismaClient2();
637
+ }
638
+ const exeDbRoot = process.env.EXE_DB_ROOT ?? path3.join(os3.homedir(), "exe-db");
639
+ const requireFromExeDb = createRequire(path3.join(exeDbRoot, "package.json"));
640
+ const prismaEntry = requireFromExeDb.resolve("@prisma/client");
641
+ const module = await import(pathToFileURL(prismaEntry).href);
642
+ const PrismaClient = module.PrismaClient ?? module.default?.PrismaClient;
643
+ if (!PrismaClient) {
644
+ throw new Error(`No PrismaClient export found in ${prismaEntry}`);
645
+ }
646
+ return new PrismaClient();
647
+ })();
648
+ }
649
+ return prismaClientPromise;
650
+ }
651
+ async function ensureCompatibilityViews(prisma) {
652
+ if (!compatibilityBootstrapPromise) {
653
+ compatibilityBootstrapPromise = (async () => {
654
+ for (const mapping of VIEW_MAPPINGS) {
655
+ const relation = mapping.source.replace(/"/g, "");
656
+ const rows = await prisma.$queryRawUnsafe(
657
+ "SELECT to_regclass($1) AS regclass",
658
+ relation
659
+ );
660
+ if (!rows[0]?.regclass) {
661
+ continue;
662
+ }
663
+ await prisma.$executeRawUnsafe(
664
+ `CREATE OR REPLACE VIEW public.${quotedIdentifier(mapping.view)} AS SELECT * FROM ${mapping.source}`
665
+ );
666
+ }
667
+ })();
668
+ }
669
+ return compatibilityBootstrapPromise;
670
+ }
671
+ async function executeOnPrisma(executor, stmt) {
672
+ const translated = translateStatementForPostgres(stmt);
673
+ if (isReadQuery(translated.sql)) {
674
+ const rows = await executor.$queryRawUnsafe(
675
+ translated.sql,
676
+ ...translated.args
677
+ );
678
+ return buildResultSet(rows, /\bRETURNING\b/iu.test(translated.sql) ? rows.length : 0);
679
+ }
680
+ const rowsAffected = await executor.$executeRawUnsafe(translated.sql, ...translated.args);
681
+ return buildResultSet([], rowsAffected);
682
+ }
683
+ function splitSqlStatements(sql) {
684
+ const parts = [];
685
+ let current = "";
686
+ let inSingle = false;
687
+ let inDouble = false;
688
+ let inLineComment = false;
689
+ let inBlockComment = false;
690
+ for (let i = 0; i < sql.length; i++) {
691
+ const ch = sql[i];
692
+ const next = sql[i + 1];
693
+ if (inLineComment) {
694
+ current += ch;
695
+ if (ch === "\n") inLineComment = false;
696
+ continue;
697
+ }
698
+ if (inBlockComment) {
699
+ current += ch;
700
+ if (ch === "*" && next === "/") {
701
+ current += next;
702
+ inBlockComment = false;
703
+ i += 1;
704
+ }
705
+ continue;
706
+ }
707
+ if (!inSingle && !inDouble && ch === "-" && next === "-") {
708
+ current += ch + next;
709
+ inLineComment = true;
710
+ i += 1;
711
+ continue;
712
+ }
713
+ if (!inSingle && !inDouble && ch === "/" && next === "*") {
714
+ current += ch + next;
715
+ inBlockComment = true;
716
+ i += 1;
717
+ continue;
718
+ }
719
+ if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
720
+ inSingle = !inSingle;
721
+ current += ch;
722
+ continue;
723
+ }
724
+ if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
725
+ inDouble = !inDouble;
726
+ current += ch;
727
+ continue;
728
+ }
729
+ if (!inSingle && !inDouble && ch === ";") {
730
+ if (current.trim()) {
731
+ parts.push(current.trim());
732
+ }
733
+ current = "";
734
+ continue;
735
+ }
736
+ current += ch;
737
+ }
738
+ if (current.trim()) {
739
+ parts.push(current.trim());
740
+ }
741
+ return parts;
742
+ }
743
+ async function createPrismaDbAdapter(fallbackClient) {
744
+ const prisma = await loadPrismaClient();
745
+ await ensureCompatibilityViews(prisma);
746
+ let closed = false;
747
+ let adapter;
748
+ const fallbackExecute = async (stmt, error) => {
749
+ if (!fallbackClient) {
750
+ if (error) throw error;
751
+ throw new Error("No fallback SQLite client is available for this Prisma-routed query.");
752
+ }
753
+ if (error) {
754
+ process.stderr.write(
755
+ `[database-adapter] Falling back to SQLite: ${error instanceof Error ? error.message : String(error)}
756
+ `
757
+ );
758
+ }
759
+ return fallbackClient.execute(stmt);
760
+ };
761
+ adapter = {
762
+ async execute(stmt) {
763
+ if (shouldBypassPostgres(stmt)) {
764
+ return fallbackExecute(stmt);
765
+ }
766
+ try {
767
+ return await executeOnPrisma(prisma, stmt);
768
+ } catch (error) {
769
+ if (shouldFallbackOnError(error)) {
770
+ return fallbackExecute(stmt, error);
771
+ }
772
+ throw error;
773
+ }
774
+ },
775
+ async batch(stmts, mode) {
776
+ if (stmts.some((stmt) => shouldBypassPostgres(stmt))) {
777
+ if (!fallbackClient) {
778
+ throw new Error("Cannot batch unsupported SQLite-only statements without a fallback client.");
779
+ }
780
+ return fallbackClient.batch(stmts, mode);
781
+ }
782
+ try {
783
+ if (prisma.$transaction) {
784
+ return await prisma.$transaction(async (tx) => {
785
+ const results2 = [];
786
+ for (const stmt of stmts) {
787
+ results2.push(await executeOnPrisma(tx, stmt));
788
+ }
789
+ return results2;
790
+ });
791
+ }
792
+ const results = [];
793
+ for (const stmt of stmts) {
794
+ results.push(await executeOnPrisma(prisma, stmt));
795
+ }
796
+ return results;
797
+ } catch (error) {
798
+ if (fallbackClient && shouldFallbackOnError(error)) {
799
+ process.stderr.write(
800
+ `[database-adapter] Falling back batch to SQLite: ${error instanceof Error ? error.message : String(error)}
801
+ `
802
+ );
803
+ return fallbackClient.batch(stmts, mode);
804
+ }
805
+ throw error;
806
+ }
807
+ },
808
+ async migrate(stmts) {
809
+ if (fallbackClient) {
810
+ return fallbackClient.migrate(stmts);
811
+ }
812
+ return adapter.batch(stmts, "deferred");
813
+ },
814
+ async transaction(mode) {
815
+ if (!fallbackClient) {
816
+ throw new Error("Interactive transactions are only supported on the SQLite fallback client.");
817
+ }
818
+ return fallbackClient.transaction(mode);
819
+ },
820
+ async executeMultiple(sql) {
821
+ if (fallbackClient && shouldBypassPostgres(sql)) {
822
+ return fallbackClient.executeMultiple(sql);
823
+ }
824
+ for (const statement of splitSqlStatements(sql)) {
825
+ await adapter.execute(statement);
826
+ }
827
+ },
828
+ async sync() {
829
+ if (fallbackClient) {
830
+ return fallbackClient.sync();
831
+ }
832
+ return { frame_no: 0, frames_synced: 0 };
833
+ },
834
+ close() {
835
+ closed = true;
836
+ prismaClientPromise = null;
837
+ compatibilityBootstrapPromise = null;
838
+ void prisma.$disconnect?.();
839
+ },
840
+ get closed() {
841
+ return closed;
842
+ },
843
+ get protocol() {
844
+ return "prisma-postgres";
845
+ }
846
+ };
847
+ return adapter;
848
+ }
849
+ var VIEW_MAPPINGS, UPSERT_KEYS, BOOLEAN_COLUMNS_BY_TABLE, BOOLEAN_COLUMN_NAMES, IMMEDIATE_FALLBACK_PATTERNS, prismaClientPromise, compatibilityBootstrapPromise;
850
+ var init_database_adapter = __esm({
851
+ "src/lib/database-adapter.ts"() {
852
+ "use strict";
853
+ VIEW_MAPPINGS = [
854
+ { view: "memories", source: "memory.memory_records" },
855
+ { view: "tasks", source: "memory.tasks" },
856
+ { view: "behaviors", source: "memory.behaviors" },
857
+ { view: "entities", source: "memory.entities" },
858
+ { view: "relationships", source: "memory.relationships" },
859
+ { view: "entity_memories", source: "memory.entity_memories" },
860
+ { view: "entity_aliases", source: "memory.entity_aliases" },
861
+ { view: "notifications", source: "memory.notifications" },
862
+ { view: "messages", source: "memory.messages" },
863
+ { view: "users", source: "wiki.users" },
864
+ { view: "workspaces", source: "wiki.workspaces" },
865
+ { view: "workspace_users", source: "wiki.workspace_users" },
866
+ { view: "documents", source: "wiki.workspace_documents" },
867
+ { view: "chats", source: "wiki.workspace_chats" }
868
+ ];
869
+ UPSERT_KEYS = {
870
+ memories: ["id"],
871
+ tasks: ["id"],
872
+ behaviors: ["id"],
873
+ entities: ["id"],
874
+ relationships: ["id"],
875
+ entity_aliases: ["alias"],
876
+ notifications: ["id"],
877
+ messages: ["id"],
878
+ users: ["id"],
879
+ workspaces: ["id"],
880
+ workspace_users: ["id"],
881
+ documents: ["id"],
882
+ chats: ["id"]
883
+ };
884
+ BOOLEAN_COLUMNS_BY_TABLE = {
885
+ memories: /* @__PURE__ */ new Set(["has_error", "draft"]),
886
+ behaviors: /* @__PURE__ */ new Set(["active"]),
887
+ notifications: /* @__PURE__ */ new Set(["read"]),
888
+ users: /* @__PURE__ */ new Set(["has_personal_memory"])
889
+ };
890
+ BOOLEAN_COLUMN_NAMES = new Set(
891
+ Object.values(BOOLEAN_COLUMNS_BY_TABLE).flatMap((cols) => [...cols])
892
+ );
893
+ IMMEDIATE_FALLBACK_PATTERNS = [
894
+ /\bPRAGMA\b/i,
895
+ /\bsqlite_master\b/i,
896
+ /(?:^|[.\s])(?:memories|conversations|entities)_fts\b/i,
897
+ /\bMATCH\b/i,
898
+ /\bvector_distance_cos\s*\(/i,
899
+ /\bjson_extract\s*\(/i,
900
+ /\bjulianday\s*\(/i,
901
+ /\bstrftime\s*\(/i,
902
+ /\blast_insert_rowid\s*\(/i
903
+ ];
904
+ prismaClientPromise = null;
905
+ compatibilityBootstrapPromise = null;
283
906
  }
284
907
  });
285
908
 
286
909
  // src/lib/database.ts
287
910
  import { createClient } from "@libsql/client";
288
911
  async function initDatabase(config) {
912
+ if (_walCheckpointTimer) {
913
+ clearInterval(_walCheckpointTimer);
914
+ _walCheckpointTimer = null;
915
+ }
916
+ if (_daemonClient) {
917
+ _daemonClient.close();
918
+ _daemonClient = null;
919
+ }
920
+ if (_adapterClient && _adapterClient !== _resilientClient) {
921
+ _adapterClient.close();
922
+ }
923
+ _adapterClient = null;
289
924
  if (_client) {
290
925
  _client.close();
291
926
  _client = null;
@@ -299,6 +934,7 @@ async function initDatabase(config) {
299
934
  }
300
935
  _client = createClient(opts);
301
936
  _resilientClient = wrapWithRetry(_client);
937
+ _adapterClient = _resilientClient;
302
938
  _client.execute("PRAGMA busy_timeout = 30000").catch(() => {
303
939
  });
304
940
  _client.execute("PRAGMA journal_mode = WAL").catch(() => {
@@ -309,11 +945,17 @@ async function initDatabase(config) {
309
945
  });
310
946
  }, 3e4);
311
947
  _walCheckpointTimer.unref();
948
+ if (process.env.DATABASE_URL) {
949
+ _adapterClient = await createPrismaDbAdapter(_resilientClient);
950
+ }
312
951
  }
313
952
  function getClient() {
314
- if (!_resilientClient) {
953
+ if (!_adapterClient) {
315
954
  throw new Error("Database client not initialized. Call initDatabase() first.");
316
955
  }
956
+ if (process.env.DATABASE_URL) {
957
+ return _adapterClient;
958
+ }
317
959
  if (process.env.EXE_IS_DAEMON === "1") {
318
960
  return _resilientClient;
319
961
  }
@@ -606,6 +1248,7 @@ async function ensureSchema() {
606
1248
  project TEXT NOT NULL,
607
1249
  summary TEXT NOT NULL,
608
1250
  task_file TEXT,
1251
+ session_scope TEXT,
609
1252
  read INTEGER NOT NULL DEFAULT 0,
610
1253
  created_at TEXT NOT NULL
611
1254
  );
@@ -614,7 +1257,7 @@ async function ensureSchema() {
614
1257
  ON notifications(read);
615
1258
 
616
1259
  CREATE INDEX IF NOT EXISTS idx_notifications_agent
617
- ON notifications(agent_id);
1260
+ ON notifications(agent_id, session_scope);
618
1261
 
619
1262
  CREATE INDEX IF NOT EXISTS idx_notifications_task_file
620
1263
  ON notifications(task_file);
@@ -652,6 +1295,7 @@ async function ensureSchema() {
652
1295
  target_agent TEXT NOT NULL,
653
1296
  target_project TEXT,
654
1297
  target_device TEXT NOT NULL DEFAULT 'local',
1298
+ session_scope TEXT,
655
1299
  content TEXT NOT NULL,
656
1300
  priority TEXT DEFAULT 'normal',
657
1301
  status TEXT DEFAULT 'pending',
@@ -665,10 +1309,31 @@ async function ensureSchema() {
665
1309
  );
666
1310
 
667
1311
  CREATE INDEX IF NOT EXISTS idx_messages_target
668
- ON messages(target_agent, status);
1312
+ ON messages(target_agent, session_scope, status);
669
1313
 
670
1314
  CREATE INDEX IF NOT EXISTS idx_messages_conversation_order
671
- ON messages(target_agent, from_agent, server_seq);
1315
+ ON messages(target_agent, session_scope, from_agent, server_seq);
1316
+ `);
1317
+ try {
1318
+ await client.execute({
1319
+ sql: `ALTER TABLE notifications ADD COLUMN session_scope TEXT`,
1320
+ args: []
1321
+ });
1322
+ } catch {
1323
+ }
1324
+ try {
1325
+ await client.execute({
1326
+ sql: `ALTER TABLE messages ADD COLUMN session_scope TEXT`,
1327
+ args: []
1328
+ });
1329
+ } catch {
1330
+ }
1331
+ await client.executeMultiple(`
1332
+ CREATE INDEX IF NOT EXISTS idx_notifications_agent_scope_read
1333
+ ON notifications(agent_id, session_scope, read, created_at);
1334
+
1335
+ CREATE INDEX IF NOT EXISTS idx_messages_target_scope_status
1336
+ ON messages(target_agent, session_scope, status, created_at);
672
1337
  `);
673
1338
  try {
674
1339
  await client.execute({
@@ -1252,17 +1917,26 @@ async function ensureSchema() {
1252
1917
  } catch {
1253
1918
  }
1254
1919
  }
1920
+ try {
1921
+ await client.execute({
1922
+ sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
1923
+ args: []
1924
+ });
1925
+ } catch {
1926
+ }
1255
1927
  }
1256
- var _client, _resilientClient, _walCheckpointTimer, _daemonClient, initTurso;
1928
+ var _client, _resilientClient, _walCheckpointTimer, _daemonClient, _adapterClient, initTurso;
1257
1929
  var init_database = __esm({
1258
1930
  "src/lib/database.ts"() {
1259
1931
  "use strict";
1260
1932
  init_db_retry();
1261
1933
  init_employees();
1934
+ init_database_adapter();
1262
1935
  _client = null;
1263
1936
  _resilientClient = null;
1264
1937
  _walCheckpointTimer = null;
1265
1938
  _daemonClient = null;
1939
+ _adapterClient = null;
1266
1940
  initTurso = initDatabase;
1267
1941
  }
1268
1942
  });
@@ -1327,6 +2001,7 @@ var shard_manager_exports = {};
1327
2001
  __export(shard_manager_exports, {
1328
2002
  disposeShards: () => disposeShards,
1329
2003
  ensureShardSchema: () => ensureShardSchema,
2004
+ getOpenShardCount: () => getOpenShardCount,
1330
2005
  getReadyShardClient: () => getReadyShardClient,
1331
2006
  getShardClient: () => getShardClient,
1332
2007
  getShardsDir: () => getShardsDir,
@@ -1335,15 +2010,18 @@ __export(shard_manager_exports, {
1335
2010
  listShards: () => listShards,
1336
2011
  shardExists: () => shardExists
1337
2012
  });
1338
- import path4 from "path";
1339
- import { existsSync as existsSync4, mkdirSync, readdirSync } from "fs";
2013
+ import path5 from "path";
2014
+ import { existsSync as existsSync5, mkdirSync as mkdirSync2, readdirSync } from "fs";
1340
2015
  import { createClient as createClient2 } from "@libsql/client";
1341
2016
  function initShardManager(encryptionKey) {
1342
2017
  _encryptionKey = encryptionKey;
1343
- if (!existsSync4(SHARDS_DIR)) {
1344
- mkdirSync(SHARDS_DIR, { recursive: true });
2018
+ if (!existsSync5(SHARDS_DIR)) {
2019
+ mkdirSync2(SHARDS_DIR, { recursive: true });
1345
2020
  }
1346
2021
  _shardingEnabled = true;
2022
+ if (_evictionTimer) clearInterval(_evictionTimer);
2023
+ _evictionTimer = setInterval(evictIdleShards, EVICTION_INTERVAL_MS);
2024
+ _evictionTimer.unref();
1347
2025
  }
1348
2026
  function isShardingEnabled() {
1349
2027
  return _shardingEnabled;
@@ -1360,21 +2038,28 @@ function getShardClient(projectName) {
1360
2038
  throw new Error(`Invalid project name for shard: "${projectName}"`);
1361
2039
  }
1362
2040
  const cached = _shards.get(safeName);
1363
- if (cached) return cached;
1364
- const dbPath = path4.join(SHARDS_DIR, `${safeName}.db`);
2041
+ if (cached) {
2042
+ _shardLastAccess.set(safeName, Date.now());
2043
+ return cached;
2044
+ }
2045
+ while (_shards.size >= MAX_OPEN_SHARDS) {
2046
+ evictLRU();
2047
+ }
2048
+ const dbPath = path5.join(SHARDS_DIR, `${safeName}.db`);
1365
2049
  const client = createClient2({
1366
2050
  url: `file:${dbPath}`,
1367
2051
  encryptionKey: _encryptionKey
1368
2052
  });
1369
2053
  _shards.set(safeName, client);
2054
+ _shardLastAccess.set(safeName, Date.now());
1370
2055
  return client;
1371
2056
  }
1372
2057
  function shardExists(projectName) {
1373
2058
  const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
1374
- return existsSync4(path4.join(SHARDS_DIR, `${safeName}.db`));
2059
+ return existsSync5(path5.join(SHARDS_DIR, `${safeName}.db`));
1375
2060
  }
1376
2061
  function listShards() {
1377
- if (!existsSync4(SHARDS_DIR)) return [];
2062
+ if (!existsSync5(SHARDS_DIR)) return [];
1378
2063
  return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
1379
2064
  }
1380
2065
  async function ensureShardSchema(client) {
@@ -1426,6 +2111,8 @@ async function ensureShardSchema(client) {
1426
2111
  for (const col of [
1427
2112
  "ALTER TABLE memories ADD COLUMN task_id TEXT",
1428
2113
  "ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0",
2114
+ "ALTER TABLE memories ADD COLUMN author_device_id TEXT",
2115
+ "ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'",
1429
2116
  "ALTER TABLE memories ADD COLUMN importance INTEGER DEFAULT 5",
1430
2117
  "ALTER TABLE memories ADD COLUMN status TEXT DEFAULT 'active'",
1431
2118
  "ALTER TABLE memories ADD COLUMN wiki_synced INTEGER DEFAULT 0",
@@ -1448,7 +2135,23 @@ async function ensureShardSchema(client) {
1448
2135
  // MS-11: draft staging, MS-6a: memory_type, MS-7: trajectory
1449
2136
  "ALTER TABLE memories ADD COLUMN draft INTEGER DEFAULT 0",
1450
2137
  "ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'",
1451
- "ALTER TABLE memories ADD COLUMN trajectory TEXT"
2138
+ "ALTER TABLE memories ADD COLUMN trajectory TEXT",
2139
+ // Metadata enrichment columns (must match database.ts)
2140
+ "ALTER TABLE memories ADD COLUMN intent TEXT",
2141
+ "ALTER TABLE memories ADD COLUMN outcome TEXT",
2142
+ "ALTER TABLE memories ADD COLUMN domain TEXT",
2143
+ "ALTER TABLE memories ADD COLUMN referenced_entities TEXT",
2144
+ "ALTER TABLE memories ADD COLUMN retrieval_count INTEGER DEFAULT 0",
2145
+ "ALTER TABLE memories ADD COLUMN chain_position TEXT",
2146
+ "ALTER TABLE memories ADD COLUMN review_status TEXT",
2147
+ "ALTER TABLE memories ADD COLUMN context_window_pct INTEGER",
2148
+ "ALTER TABLE memories ADD COLUMN file_paths TEXT",
2149
+ "ALTER TABLE memories ADD COLUMN commit_hash TEXT",
2150
+ "ALTER TABLE memories ADD COLUMN duration_ms INTEGER",
2151
+ "ALTER TABLE memories ADD COLUMN token_cost REAL",
2152
+ "ALTER TABLE memories ADD COLUMN audience TEXT",
2153
+ "ALTER TABLE memories ADD COLUMN language_type TEXT",
2154
+ "ALTER TABLE memories ADD COLUMN parent_memory_id TEXT"
1452
2155
  ]) {
1453
2156
  try {
1454
2157
  await client.execute(col);
@@ -1547,21 +2250,69 @@ async function getReadyShardClient(projectName) {
1547
2250
  await ensureShardSchema(client);
1548
2251
  return client;
1549
2252
  }
2253
+ function evictLRU() {
2254
+ let oldest = null;
2255
+ let oldestTime = Infinity;
2256
+ for (const [name, time] of _shardLastAccess) {
2257
+ if (time < oldestTime) {
2258
+ oldestTime = time;
2259
+ oldest = name;
2260
+ }
2261
+ }
2262
+ if (oldest) {
2263
+ const client = _shards.get(oldest);
2264
+ if (client) {
2265
+ client.close();
2266
+ }
2267
+ _shards.delete(oldest);
2268
+ _shardLastAccess.delete(oldest);
2269
+ }
2270
+ }
2271
+ function evictIdleShards() {
2272
+ const now = Date.now();
2273
+ const toEvict = [];
2274
+ for (const [name, lastAccess] of _shardLastAccess) {
2275
+ if (now - lastAccess > SHARD_IDLE_MS) {
2276
+ toEvict.push(name);
2277
+ }
2278
+ }
2279
+ for (const name of toEvict) {
2280
+ const client = _shards.get(name);
2281
+ if (client) {
2282
+ client.close();
2283
+ }
2284
+ _shards.delete(name);
2285
+ _shardLastAccess.delete(name);
2286
+ }
2287
+ }
2288
+ function getOpenShardCount() {
2289
+ return _shards.size;
2290
+ }
1550
2291
  function disposeShards() {
2292
+ if (_evictionTimer) {
2293
+ clearInterval(_evictionTimer);
2294
+ _evictionTimer = null;
2295
+ }
1551
2296
  for (const [, client] of _shards) {
1552
2297
  client.close();
1553
2298
  }
1554
2299
  _shards.clear();
2300
+ _shardLastAccess.clear();
1555
2301
  _shardingEnabled = false;
1556
2302
  _encryptionKey = null;
1557
2303
  }
1558
- var SHARDS_DIR, _shards, _encryptionKey, _shardingEnabled;
2304
+ var SHARDS_DIR, SHARD_IDLE_MS, MAX_OPEN_SHARDS, EVICTION_INTERVAL_MS, _shards, _shardLastAccess, _evictionTimer, _encryptionKey, _shardingEnabled;
1559
2305
  var init_shard_manager = __esm({
1560
2306
  "src/lib/shard-manager.ts"() {
1561
2307
  "use strict";
1562
2308
  init_config();
1563
- SHARDS_DIR = path4.join(EXE_AI_DIR, "shards");
2309
+ SHARDS_DIR = path5.join(EXE_AI_DIR, "shards");
2310
+ SHARD_IDLE_MS = 5 * 60 * 1e3;
2311
+ MAX_OPEN_SHARDS = 10;
2312
+ EVICTION_INTERVAL_MS = 60 * 1e3;
1564
2313
  _shards = /* @__PURE__ */ new Map();
2314
+ _shardLastAccess = /* @__PURE__ */ new Map();
2315
+ _evictionTimer = null;
1565
2316
  _encryptionKey = null;
1566
2317
  _shardingEnabled = false;
1567
2318
  }
@@ -1755,28 +2506,188 @@ ${p.content}`).join("\n\n");
1755
2506
  });
1756
2507
 
1757
2508
  // src/lib/session-registry.ts
1758
- import path5 from "path";
1759
- import os4 from "os";
2509
+ import path6 from "path";
2510
+ import os5 from "os";
1760
2511
  var REGISTRY_PATH;
1761
2512
  var init_session_registry = __esm({
1762
2513
  "src/lib/session-registry.ts"() {
1763
2514
  "use strict";
1764
- REGISTRY_PATH = path5.join(os4.homedir(), ".exe-os", "session-registry.json");
2515
+ REGISTRY_PATH = path6.join(os5.homedir(), ".exe-os", "session-registry.json");
1765
2516
  }
1766
2517
  });
1767
2518
 
1768
2519
  // src/lib/session-key.ts
1769
2520
  import { execSync as execSync2 } from "child_process";
2521
+ function normalizeCommand(command) {
2522
+ const trimmed = command.trim().toLowerCase();
2523
+ const parts = trimmed.split(/[\\/]/);
2524
+ return parts[parts.length - 1] ?? trimmed;
2525
+ }
2526
+ function detectRuntimeFromCommand(command) {
2527
+ const normalized = normalizeCommand(command);
2528
+ for (const [runtime, commands] of Object.entries(RUNTIME_COMMANDS)) {
2529
+ if (commands.includes(normalized)) {
2530
+ return runtime;
2531
+ }
2532
+ }
2533
+ return null;
2534
+ }
2535
+ function resolveRuntimeProcess() {
2536
+ let pid = process.ppid;
2537
+ for (let i = 0; i < 10; i++) {
2538
+ try {
2539
+ const info = execSync2(`ps -p ${pid} -o ppid=,comm=`, {
2540
+ encoding: "utf8",
2541
+ timeout: 2e3
2542
+ }).trim();
2543
+ const match = info.match(/^\s*(\d+)\s+(.+)$/);
2544
+ if (!match) break;
2545
+ const [, ppid, cmd] = match;
2546
+ const runtime = detectRuntimeFromCommand(cmd ?? "");
2547
+ if (runtime) {
2548
+ return { pid: String(pid), runtime };
2549
+ }
2550
+ pid = parseInt(ppid, 10);
2551
+ if (pid <= 1) break;
2552
+ } catch {
2553
+ break;
2554
+ }
2555
+ }
2556
+ return null;
2557
+ }
2558
+ function getSessionKey() {
2559
+ if (_cached) return _cached;
2560
+ if (process.env.EXE_SESSION_KEY) {
2561
+ _cached = process.env.EXE_SESSION_KEY;
2562
+ return _cached;
2563
+ }
2564
+ const resolved = resolveRuntimeProcess();
2565
+ if (resolved) {
2566
+ _cachedRuntime = resolved.runtime;
2567
+ _cached = resolved.pid;
2568
+ return _cached;
2569
+ }
2570
+ _cached = process.env.CLAUDE_CODE_SSE_PORT ?? String(process.ppid);
2571
+ return _cached;
2572
+ }
2573
+ var _cached, _cachedRuntime, RUNTIME_COMMANDS;
1770
2574
  var init_session_key = __esm({
1771
2575
  "src/lib/session-key.ts"() {
1772
2576
  "use strict";
2577
+ _cached = null;
2578
+ _cachedRuntime = null;
2579
+ RUNTIME_COMMANDS = {
2580
+ claude: ["claude", "claude.exe", "claude-native"],
2581
+ codex: ["codex"],
2582
+ opencode: ["opencode"]
2583
+ };
2584
+ }
2585
+ });
2586
+
2587
+ // src/lib/tmux-transport.ts
2588
+ var tmux_transport_exports = {};
2589
+ __export(tmux_transport_exports, {
2590
+ TmuxTransport: () => TmuxTransport
2591
+ });
2592
+ import { execFileSync } from "child_process";
2593
+ var QUIET, TmuxTransport;
2594
+ var init_tmux_transport = __esm({
2595
+ "src/lib/tmux-transport.ts"() {
2596
+ "use strict";
2597
+ QUIET = {
2598
+ encoding: "utf8",
2599
+ stdio: ["pipe", "pipe", "pipe"]
2600
+ };
2601
+ TmuxTransport = class {
2602
+ getMySession() {
2603
+ try {
2604
+ return execFileSync("tmux", ["display-message", "-p", "#{session_name}"], QUIET).trim() || null;
2605
+ } catch {
2606
+ return null;
2607
+ }
2608
+ }
2609
+ listSessions() {
2610
+ try {
2611
+ return execFileSync("tmux", ["list-sessions", "-F", "#{session_name}"], QUIET).trim().split("\n").filter(Boolean);
2612
+ } catch {
2613
+ return [];
2614
+ }
2615
+ }
2616
+ isAlive(target) {
2617
+ try {
2618
+ const sessions = this.listSessions();
2619
+ if (!sessions.includes(target)) return false;
2620
+ const paneStatus = execFileSync(
2621
+ "tmux",
2622
+ ["list-panes", "-t", target, "-F", "#{pane_dead}"],
2623
+ QUIET
2624
+ ).trim();
2625
+ return paneStatus !== "1";
2626
+ } catch {
2627
+ return false;
2628
+ }
2629
+ }
2630
+ sendKeys(target, keys) {
2631
+ execFileSync("tmux", ["send-keys", "-t", target, keys, "Enter"], QUIET);
2632
+ }
2633
+ capturePane(target, lines) {
2634
+ const args = ["capture-pane", "-t", target, "-p"];
2635
+ if (lines) args.push("-S", `-${lines}`);
2636
+ return execFileSync("tmux", args, { ...QUIET, timeout: 3e3 });
2637
+ }
2638
+ isPaneInCopyMode(target) {
2639
+ try {
2640
+ const result = execFileSync(
2641
+ "tmux",
2642
+ ["display-message", "-p", "-t", target, "#{pane_in_mode}"],
2643
+ { ...QUIET, timeout: 3e3 }
2644
+ ).trim();
2645
+ return result === "1";
2646
+ } catch {
2647
+ return false;
2648
+ }
2649
+ }
2650
+ spawn(name, config) {
2651
+ try {
2652
+ const args = ["new-session", "-d", "-s", name];
2653
+ if (config.cwd) args.push("-c", config.cwd);
2654
+ args.push(config.command);
2655
+ execFileSync("tmux", args);
2656
+ return { sessionName: name };
2657
+ } catch (e) {
2658
+ return { sessionName: name, error: `spawn failed: ${e}` };
2659
+ }
2660
+ }
2661
+ kill(target) {
2662
+ try {
2663
+ execFileSync("tmux", ["kill-session", "-t", target], QUIET);
2664
+ } catch {
2665
+ }
2666
+ }
2667
+ pipeLog(target, logFile) {
2668
+ try {
2669
+ const safePath = logFile.replace(/'/g, "'\\''");
2670
+ execFileSync("tmux", ["pipe-pane", "-t", target, `cat >> '${safePath}'`], QUIET);
2671
+ } catch {
2672
+ }
2673
+ }
2674
+ };
1773
2675
  }
1774
2676
  });
1775
2677
 
1776
2678
  // src/lib/transport.ts
2679
+ function getTransport() {
2680
+ if (!_transport) {
2681
+ const { TmuxTransport: TmuxTransport2 } = (init_tmux_transport(), __toCommonJS(tmux_transport_exports));
2682
+ _transport = new TmuxTransport2();
2683
+ }
2684
+ return _transport;
2685
+ }
2686
+ var _transport;
1777
2687
  var init_transport = __esm({
1778
2688
  "src/lib/transport.ts"() {
1779
2689
  "use strict";
2690
+ _transport = null;
1780
2691
  }
1781
2692
  });
1782
2693
 
@@ -1836,15 +2747,16 @@ var init_runtime_table = __esm({
1836
2747
  });
1837
2748
 
1838
2749
  // src/lib/agent-config.ts
1839
- import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, existsSync as existsSync5, mkdirSync as mkdirSync2 } from "fs";
1840
- import path6 from "path";
2750
+ import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, existsSync as existsSync6 } from "fs";
2751
+ import path7 from "path";
1841
2752
  var AGENT_CONFIG_PATH, DEFAULT_MODELS;
1842
2753
  var init_agent_config = __esm({
1843
2754
  "src/lib/agent-config.ts"() {
1844
2755
  "use strict";
1845
2756
  init_config();
1846
2757
  init_runtime_table();
1847
- AGENT_CONFIG_PATH = path6.join(EXE_AI_DIR, "agent-config.json");
2758
+ init_secure_files();
2759
+ AGENT_CONFIG_PATH = path7.join(EXE_AI_DIR, "agent-config.json");
1848
2760
  DEFAULT_MODELS = {
1849
2761
  claude: "claude-opus-4",
1850
2762
  codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
@@ -1854,38 +2766,41 @@ var init_agent_config = __esm({
1854
2766
  });
1855
2767
 
1856
2768
  // src/lib/intercom-queue.ts
1857
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, renameSync as renameSync3, existsSync as existsSync6, mkdirSync as mkdirSync3 } from "fs";
1858
- import path7 from "path";
1859
- import os5 from "os";
2769
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, renameSync as renameSync3, existsSync as existsSync7, mkdirSync as mkdirSync3 } from "fs";
2770
+ import path8 from "path";
2771
+ import os6 from "os";
1860
2772
  var QUEUE_PATH, TTL_MS, INTERCOM_LOG;
1861
2773
  var init_intercom_queue = __esm({
1862
2774
  "src/lib/intercom-queue.ts"() {
1863
2775
  "use strict";
1864
- QUEUE_PATH = path7.join(os5.homedir(), ".exe-os", "intercom-queue.json");
2776
+ QUEUE_PATH = path8.join(os6.homedir(), ".exe-os", "intercom-queue.json");
1865
2777
  TTL_MS = 60 * 60 * 1e3;
1866
- INTERCOM_LOG = path7.join(os5.homedir(), ".exe-os", "intercom.log");
2778
+ INTERCOM_LOG = path8.join(os6.homedir(), ".exe-os", "intercom.log");
1867
2779
  }
1868
2780
  });
1869
2781
 
1870
2782
  // src/lib/license.ts
1871
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, existsSync as existsSync7, mkdirSync as mkdirSync4 } from "fs";
2783
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, existsSync as existsSync8, mkdirSync as mkdirSync4 } from "fs";
1872
2784
  import { randomUUID as randomUUID2 } from "crypto";
1873
- import path8 from "path";
2785
+ import { createRequire as createRequire2 } from "module";
2786
+ import { pathToFileURL as pathToFileURL2 } from "url";
2787
+ import os7 from "os";
2788
+ import path9 from "path";
1874
2789
  import { jwtVerify, importSPKI } from "jose";
1875
2790
  var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH;
1876
2791
  var init_license = __esm({
1877
2792
  "src/lib/license.ts"() {
1878
2793
  "use strict";
1879
2794
  init_config();
1880
- LICENSE_PATH = path8.join(EXE_AI_DIR, "license.key");
1881
- CACHE_PATH = path8.join(EXE_AI_DIR, "license-cache.json");
1882
- DEVICE_ID_PATH = path8.join(EXE_AI_DIR, "device-id");
2795
+ LICENSE_PATH = path9.join(EXE_AI_DIR, "license.key");
2796
+ CACHE_PATH = path9.join(EXE_AI_DIR, "license-cache.json");
2797
+ DEVICE_ID_PATH = path9.join(EXE_AI_DIR, "device-id");
1883
2798
  }
1884
2799
  });
1885
2800
 
1886
2801
  // src/lib/plan-limits.ts
1887
- import { readFileSync as readFileSync6, existsSync as existsSync8 } from "fs";
1888
- import path9 from "path";
2802
+ import { readFileSync as readFileSync6, existsSync as existsSync9 } from "fs";
2803
+ import path10 from "path";
1889
2804
  var CACHE_PATH2;
1890
2805
  var init_plan_limits = __esm({
1891
2806
  "src/lib/plan-limits.ts"() {
@@ -1894,14 +2809,54 @@ var init_plan_limits = __esm({
1894
2809
  init_employees();
1895
2810
  init_license();
1896
2811
  init_config();
1897
- CACHE_PATH2 = path9.join(EXE_AI_DIR, "license-cache.json");
2812
+ CACHE_PATH2 = path10.join(EXE_AI_DIR, "license-cache.json");
1898
2813
  }
1899
2814
  });
1900
2815
 
1901
2816
  // src/lib/tmux-routing.ts
1902
- import path10 from "path";
1903
- import os6 from "os";
2817
+ import { readFileSync as readFileSync7, writeFileSync as writeFileSync5, mkdirSync as mkdirSync5, existsSync as existsSync10, appendFileSync, readdirSync as readdirSync2 } from "fs";
2818
+ import path11 from "path";
2819
+ import os8 from "os";
1904
2820
  import { fileURLToPath } from "url";
2821
+ function getMySession() {
2822
+ return getTransport().getMySession();
2823
+ }
2824
+ function extractRootExe(name) {
2825
+ if (!name) return null;
2826
+ if (!name.includes("-")) return name;
2827
+ const parts = name.split("-").filter(Boolean);
2828
+ return parts.length > 0 ? parts[parts.length - 1] : null;
2829
+ }
2830
+ function getParentExe(sessionKey) {
2831
+ try {
2832
+ const data = JSON.parse(readFileSync7(path11.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
2833
+ return data.parentExe || null;
2834
+ } catch {
2835
+ return null;
2836
+ }
2837
+ }
2838
+ function resolveExeSession() {
2839
+ const mySession = getMySession();
2840
+ if (!mySession) return null;
2841
+ const fromSessionName = extractRootExe(mySession);
2842
+ try {
2843
+ const key = getSessionKey();
2844
+ const parentExe = getParentExe(key);
2845
+ if (parentExe) {
2846
+ const fromCache = extractRootExe(parentExe) ?? parentExe;
2847
+ if (fromSessionName && fromCache !== fromSessionName) {
2848
+ process.stderr.write(
2849
+ `[tmux-routing] WARN: cache says "${fromCache}" but session name says "${fromSessionName}". Trusting session name.
2850
+ `
2851
+ );
2852
+ return fromSessionName;
2853
+ }
2854
+ return fromCache;
2855
+ }
2856
+ } catch {
2857
+ }
2858
+ return fromSessionName ?? mySession;
2859
+ }
1905
2860
  var SPAWN_LOCK_DIR, SESSION_CACHE, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS;
1906
2861
  var init_tmux_routing = __esm({
1907
2862
  "src/lib/tmux-routing.ts"() {
@@ -1917,30 +2872,54 @@ var init_tmux_routing = __esm({
1917
2872
  init_intercom_queue();
1918
2873
  init_plan_limits();
1919
2874
  init_employees();
1920
- SPAWN_LOCK_DIR = path10.join(os6.homedir(), ".exe-os", "spawn-locks");
1921
- SESSION_CACHE = path10.join(os6.homedir(), ".exe-os", "session-cache");
1922
- INTERCOM_LOG2 = path10.join(os6.homedir(), ".exe-os", "intercom.log");
1923
- DEBOUNCE_FILE = path10.join(SESSION_CACHE, "intercom-debounce.json");
2875
+ SPAWN_LOCK_DIR = path11.join(os8.homedir(), ".exe-os", "spawn-locks");
2876
+ SESSION_CACHE = path11.join(os8.homedir(), ".exe-os", "session-cache");
2877
+ INTERCOM_LOG2 = path11.join(os8.homedir(), ".exe-os", "intercom.log");
2878
+ DEBOUNCE_FILE = path11.join(SESSION_CACHE, "intercom-debounce.json");
1924
2879
  DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
1925
2880
  }
1926
2881
  });
1927
2882
 
2883
+ // src/lib/task-scope.ts
2884
+ function getCurrentSessionScope() {
2885
+ try {
2886
+ return resolveExeSession();
2887
+ } catch {
2888
+ return null;
2889
+ }
2890
+ }
2891
+ function strictSessionScopeFilter(sessionScope, tableAlias) {
2892
+ const scope = sessionScope !== void 0 ? sessionScope : getCurrentSessionScope();
2893
+ if (!scope) return { sql: "", args: [] };
2894
+ const col = tableAlias ? `${tableAlias}.session_scope` : "session_scope";
2895
+ return {
2896
+ sql: ` AND ${col} = ?`,
2897
+ args: [scope]
2898
+ };
2899
+ }
2900
+ var init_task_scope = __esm({
2901
+ "src/lib/task-scope.ts"() {
2902
+ "use strict";
2903
+ init_tmux_routing();
2904
+ }
2905
+ });
2906
+
1928
2907
  // src/lib/store.ts
1929
2908
  import { createHash } from "crypto";
1930
2909
  init_database();
1931
2910
 
1932
2911
  // src/lib/keychain.ts
1933
2912
  import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
1934
- import { existsSync as existsSync3 } from "fs";
1935
- import path3 from "path";
1936
- import os3 from "os";
2913
+ import { existsSync as existsSync4 } from "fs";
2914
+ import path4 from "path";
2915
+ import os4 from "os";
1937
2916
  var SERVICE = "exe-mem";
1938
2917
  var ACCOUNT = "master-key";
1939
2918
  function getKeyDir() {
1940
- return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path3.join(os3.homedir(), ".exe-os");
2919
+ return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path4.join(os4.homedir(), ".exe-os");
1941
2920
  }
1942
2921
  function getKeyPath() {
1943
- return path3.join(getKeyDir(), "master.key");
2922
+ return path4.join(getKeyDir(), "master.key");
1944
2923
  }
1945
2924
  async function tryKeytar() {
1946
2925
  try {
@@ -1961,9 +2940,9 @@ async function getMasterKey() {
1961
2940
  }
1962
2941
  }
1963
2942
  const keyPath = getKeyPath();
1964
- if (!existsSync3(keyPath)) {
2943
+ if (!existsSync4(keyPath)) {
1965
2944
  process.stderr.write(
1966
- `[keychain] Key not found at ${keyPath} (HOME=${os3.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
2945
+ `[keychain] Key not found at ${keyPath} (HOME=${os4.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
1967
2946
  `
1968
2947
  );
1969
2948
  return null;
@@ -2065,6 +3044,7 @@ async function initStore(options) {
2065
3044
  // src/lib/messaging.ts
2066
3045
  init_database();
2067
3046
  init_tmux_routing();
3047
+ init_task_scope();
2068
3048
  import crypto from "crypto";
2069
3049
  function rowToMessage(row) {
2070
3050
  return {
@@ -2074,6 +3054,7 @@ function rowToMessage(row) {
2074
3054
  targetAgent: row.target_agent,
2075
3055
  targetProject: row.target_project ?? null,
2076
3056
  targetDevice: row.target_device,
3057
+ sessionScope: row.session_scope ?? null,
2077
3058
  content: row.content,
2078
3059
  priority: row.priority ?? "normal",
2079
3060
  status: row.status ?? "pending",
@@ -2086,21 +3067,24 @@ function rowToMessage(row) {
2086
3067
  failureReason: row.failure_reason ?? null
2087
3068
  };
2088
3069
  }
2089
- async function getPendingMessages(targetAgent) {
3070
+ async function getPendingMessages(targetAgent, sessionScope) {
2090
3071
  const client = getClient();
3072
+ const scope = strictSessionScopeFilter(sessionScope);
2091
3073
  const result = await client.execute({
2092
3074
  sql: `SELECT * FROM messages
2093
- WHERE target_agent = ? AND status IN ('pending', 'delivered')
3075
+ WHERE target_agent = ? AND status IN ('pending', 'delivered')${scope.sql}
2094
3076
  ORDER BY id`,
2095
- args: [targetAgent]
3077
+ args: [targetAgent, ...scope.args]
2096
3078
  });
2097
3079
  return result.rows.map((row) => rowToMessage(row));
2098
3080
  }
2099
- async function markRead(messageId) {
3081
+ async function markRead(messageId, sessionScope) {
2100
3082
  const client = getClient();
3083
+ const scope = strictSessionScopeFilter(sessionScope);
2101
3084
  await client.execute({
2102
- sql: "UPDATE messages SET status = 'read' WHERE id = ? AND status IN ('pending', 'delivered')",
2103
- args: [messageId]
3085
+ sql: `UPDATE messages SET status = 'read'
3086
+ WHERE id = ? AND status IN ('pending', 'delivered')${scope.sql}`,
3087
+ args: [messageId, ...scope.args]
2104
3088
  });
2105
3089
  }
2106
3090