@askexenow/exe-os 0.8.0 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. package/README.md +178 -79
  2. package/dist/bin/backfill-responses.js +160 -8
  3. package/dist/bin/backfill-vectors.js +130 -1
  4. package/dist/bin/cleanup-stale-review-tasks.js +130 -1
  5. package/dist/bin/cli.js +10111 -7540
  6. package/dist/bin/exe-agent.js +159 -1
  7. package/dist/bin/exe-assign.js +235 -16
  8. package/dist/bin/exe-boot.js +344 -472
  9. package/dist/bin/exe-call.js +145 -1
  10. package/dist/bin/exe-cloud.js +11 -0
  11. package/dist/bin/exe-dispatch.js +37 -24
  12. package/dist/bin/exe-doctor.js +130 -1
  13. package/dist/bin/exe-export-behaviors.js +150 -7
  14. package/dist/bin/exe-forget.js +822 -665
  15. package/dist/bin/exe-gateway.js +470 -62
  16. package/dist/bin/exe-heartbeat.js +133 -2
  17. package/dist/bin/exe-kill.js +150 -7
  18. package/dist/bin/exe-launch-agent.js +150 -7
  19. package/dist/bin/exe-new-employee.js +756 -224
  20. package/dist/bin/exe-pending-messages.js +132 -2
  21. package/dist/bin/exe-pending-notifications.js +130 -1
  22. package/dist/bin/exe-pending-reviews.js +132 -2
  23. package/dist/bin/exe-review.js +160 -8
  24. package/dist/bin/exe-search.js +2473 -2008
  25. package/dist/bin/exe-session-cleanup.js +238 -51
  26. package/dist/bin/exe-settings.js +11 -0
  27. package/dist/bin/exe-status.js +130 -1
  28. package/dist/bin/exe-team.js +130 -1
  29. package/dist/bin/git-sweep.js +272 -16
  30. package/dist/bin/graph-backfill.js +150 -7
  31. package/dist/bin/graph-export.js +150 -7
  32. package/dist/bin/install.js +5 -0
  33. package/dist/bin/scan-tasks.js +238 -19
  34. package/dist/bin/setup.js +1776 -10
  35. package/dist/bin/shard-migrate.js +150 -7
  36. package/dist/bin/update.js +9 -6
  37. package/dist/bin/wiki-sync.js +150 -7
  38. package/dist/gateway/index.js +470 -62
  39. package/dist/hooks/bug-report-worker.js +195 -35
  40. package/dist/hooks/commit-complete.js +272 -16
  41. package/dist/hooks/error-recall.js +2313 -1847
  42. package/dist/hooks/exe-heartbeat-hook.js +5 -0
  43. package/dist/hooks/ingest-worker.js +330 -58
  44. package/dist/hooks/ingest.js +11 -0
  45. package/dist/hooks/instructions-loaded.js +199 -10
  46. package/dist/hooks/notification.js +199 -10
  47. package/dist/hooks/post-compact.js +199 -10
  48. package/dist/hooks/pre-compact.js +199 -10
  49. package/dist/hooks/pre-tool-use.js +199 -10
  50. package/dist/hooks/prompt-ingest-worker.js +179 -14
  51. package/dist/hooks/prompt-submit.js +781 -285
  52. package/dist/hooks/response-ingest-worker.js +1900 -1405
  53. package/dist/hooks/session-end.js +456 -12
  54. package/dist/hooks/session-start.js +2188 -1724
  55. package/dist/hooks/stop.js +200 -10
  56. package/dist/hooks/subagent-stop.js +199 -10
  57. package/dist/hooks/summary-worker.js +604 -334
  58. package/dist/index.js +554 -61
  59. package/dist/lib/cloud-sync.js +5 -0
  60. package/dist/lib/config.js +13 -0
  61. package/dist/lib/consolidation.js +5 -0
  62. package/dist/lib/database.js +104 -0
  63. package/dist/lib/device-registry.js +109 -0
  64. package/dist/lib/embedder.js +13 -0
  65. package/dist/lib/employee-templates.js +53 -26
  66. package/dist/lib/employees.js +5 -0
  67. package/dist/lib/exe-daemon-client.js +5 -0
  68. package/dist/lib/exe-daemon.js +493 -79
  69. package/dist/lib/file-grep.js +20 -4
  70. package/dist/lib/hybrid-search.js +1435 -190
  71. package/dist/lib/identity-templates.js +126 -5
  72. package/dist/lib/identity.js +5 -0
  73. package/dist/lib/license.js +5 -0
  74. package/dist/lib/messaging.js +37 -24
  75. package/dist/lib/schedules.js +130 -1
  76. package/dist/lib/skill-learning.js +11 -0
  77. package/dist/lib/status-brief.js +5 -0
  78. package/dist/lib/store.js +199 -10
  79. package/dist/lib/task-router.js +72 -6
  80. package/dist/lib/tasks.js +179 -50
  81. package/dist/lib/tmux-routing.js +179 -46
  82. package/dist/mcp/server.js +2129 -1855
  83. package/dist/mcp/tools/create-task.js +86 -36
  84. package/dist/mcp/tools/deactivate-behavior.js +5 -0
  85. package/dist/mcp/tools/list-tasks.js +39 -11
  86. package/dist/mcp/tools/send-message.js +37 -24
  87. package/dist/mcp/tools/update-task.js +153 -38
  88. package/dist/runtime/index.js +451 -59
  89. package/dist/tui/App.js +454 -59
  90. package/package.json +1 -1
package/dist/tui/App.js CHANGED
@@ -260,6 +260,27 @@ async function ensureSchema() {
260
260
  });
261
261
  } catch {
262
262
  }
263
+ try {
264
+ await client.execute({
265
+ sql: `ALTER TABLE tasks ADD COLUMN checkpoint TEXT`,
266
+ args: []
267
+ });
268
+ } catch {
269
+ }
270
+ try {
271
+ await client.execute({
272
+ sql: `ALTER TABLE tasks ADD COLUMN checkpoint_count INTEGER NOT NULL DEFAULT 0`,
273
+ args: []
274
+ });
275
+ } catch {
276
+ }
277
+ try {
278
+ await client.execute({
279
+ sql: `ALTER TABLE tasks ADD COLUMN complexity TEXT NOT NULL DEFAULT 'standard'`,
280
+ args: []
281
+ });
282
+ } catch {
283
+ }
263
284
  try {
264
285
  await client.execute({
265
286
  sql: `ALTER TABLE memories ADD COLUMN task_id TEXT`,
@@ -670,6 +691,15 @@ async function ensureSchema() {
670
691
  } catch {
671
692
  }
672
693
  }
694
+ for (const col of [
695
+ "ALTER TABLE memories ADD COLUMN source_path TEXT",
696
+ "ALTER TABLE memories ADD COLUMN source_type TEXT DEFAULT 'text'"
697
+ ]) {
698
+ try {
699
+ await client.execute(col);
700
+ } catch {
701
+ }
702
+ }
673
703
  await client.executeMultiple(`
674
704
  CREATE INDEX IF NOT EXISTS idx_memories_workspace
675
705
  ON memories(workspace_id);
@@ -734,6 +764,34 @@ async function ensureSchema() {
734
764
  CREATE INDEX IF NOT EXISTS idx_conversations_channel
735
765
  ON conversations(channel_id);
736
766
  `);
767
+ try {
768
+ await client.execute({
769
+ sql: `ALTER TABLE tasks ADD COLUMN budget_tokens INTEGER`,
770
+ args: []
771
+ });
772
+ } catch {
773
+ }
774
+ try {
775
+ await client.execute({
776
+ sql: `ALTER TABLE tasks ADD COLUMN budget_fallback_model TEXT`,
777
+ args: []
778
+ });
779
+ } catch {
780
+ }
781
+ try {
782
+ await client.execute({
783
+ sql: `ALTER TABLE tasks ADD COLUMN tokens_used INTEGER DEFAULT 0`,
784
+ args: []
785
+ });
786
+ } catch {
787
+ }
788
+ try {
789
+ await client.execute({
790
+ sql: `ALTER TABLE tasks ADD COLUMN tokens_warned_at INTEGER`,
791
+ args: []
792
+ });
793
+ } catch {
794
+ }
737
795
  await client.executeMultiple(`
738
796
  CREATE VIRTUAL TABLE IF NOT EXISTS conversations_fts USING fts5(
739
797
  content_text,
@@ -760,6 +818,52 @@ async function ensureSchema() {
760
818
  VALUES (new.rowid, new.content_text, new.sender_name, new.agent_response);
761
819
  END;
762
820
  `);
821
+ try {
822
+ await client.execute({
823
+ sql: `ALTER TABLE memories ADD COLUMN tier INTEGER DEFAULT 3`,
824
+ args: []
825
+ });
826
+ } catch {
827
+ }
828
+ try {
829
+ await client.execute(
830
+ `CREATE INDEX IF NOT EXISTS idx_memories_tier ON memories(tier)`
831
+ );
832
+ } catch {
833
+ }
834
+ try {
835
+ await client.execute({
836
+ sql: `UPDATE memories SET tier = 1 WHERE tool_name = 'commit_to_long_term_memory' AND importance >= 8 AND tier = 3`,
837
+ args: []
838
+ });
839
+ await client.execute({
840
+ sql: `UPDATE memories SET tier = 2 WHERE tool_name IN ('store_memory', 'manual') AND importance >= 5 AND tier = 3`,
841
+ args: []
842
+ });
843
+ } catch {
844
+ }
845
+ try {
846
+ await client.execute({
847
+ sql: `ALTER TABLE memories ADD COLUMN supersedes_id TEXT`,
848
+ args: []
849
+ });
850
+ } catch {
851
+ }
852
+ try {
853
+ await client.execute(
854
+ `CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL`
855
+ );
856
+ } catch {
857
+ }
858
+ for (const col of [
859
+ "ALTER TABLE tasks ADD COLUMN checkpoint TEXT",
860
+ "ALTER TABLE tasks ADD COLUMN checkpoint_count INTEGER DEFAULT 0"
861
+ ]) {
862
+ try {
863
+ await client.execute(col);
864
+ } catch {
865
+ }
866
+ }
763
867
  }
764
868
  async function disposeDatabase() {
765
869
  if (_client) {
@@ -858,6 +962,11 @@ function normalizeSessionLifecycle(raw) {
858
962
  const userSL = raw.sessionLifecycle ?? {};
859
963
  raw.sessionLifecycle = { ...defaultSL, ...userSL };
860
964
  }
965
+ function normalizeAutoUpdate(raw) {
966
+ const defaultAU = DEFAULT_CONFIG.autoUpdate;
967
+ const userAU = raw.autoUpdate ?? {};
968
+ raw.autoUpdate = { ...defaultAU, ...userAU };
969
+ }
861
970
  async function loadConfig() {
862
971
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
863
972
  await mkdir(dir, { recursive: true });
@@ -880,6 +989,7 @@ async function loadConfig() {
880
989
  }
881
990
  normalizeScalingRoadmap(migratedCfg);
882
991
  normalizeSessionLifecycle(migratedCfg);
992
+ normalizeAutoUpdate(migratedCfg);
883
993
  const config = { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db"), ...migratedCfg };
884
994
  if (config.dbPath.startsWith("~")) {
885
995
  config.dbPath = config.dbPath.replace(/^~/, os.homedir());
@@ -902,6 +1012,7 @@ function loadConfigSync() {
902
1012
  const { config: migratedCfg } = migrateConfig(parsed);
903
1013
  normalizeScalingRoadmap(migratedCfg);
904
1014
  normalizeSessionLifecycle(migratedCfg);
1015
+ normalizeAutoUpdate(migratedCfg);
905
1016
  return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db"), ...migratedCfg };
906
1017
  } catch {
907
1018
  return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
@@ -921,6 +1032,7 @@ async function loadConfigFrom(configPath) {
921
1032
  const { config: migratedCfg } = migrateConfig(parsed);
922
1033
  normalizeScalingRoadmap(migratedCfg);
923
1034
  normalizeSessionLifecycle(migratedCfg);
1035
+ normalizeAutoUpdate(migratedCfg);
924
1036
  return { ...DEFAULT_CONFIG, ...migratedCfg };
925
1037
  } catch {
926
1038
  return { ...DEFAULT_CONFIG };
@@ -992,6 +1104,11 @@ var init_config = __esm({
992
1104
  idleKillTicksRequired: 3,
993
1105
  idleKillIntercomAckWindowMs: 1e4,
994
1106
  maxAutoInstances: 10
1107
+ },
1108
+ autoUpdate: {
1109
+ checkOnBoot: true,
1110
+ autoInstall: false,
1111
+ checkIntervalMs: 24 * 60 * 60 * 1e3
995
1112
  }
996
1113
  };
997
1114
  CONFIG_MIGRATIONS = [
@@ -2259,6 +2376,25 @@ async function* agentLoop(userMessage, history, config) {
2259
2376
  }
2260
2377
  totalUsage.inputTokens += response.usage.inputTokens;
2261
2378
  totalUsage.outputTokens += response.usage.outputTokens;
2379
+ if (config.tokenBudgetMiddleware) {
2380
+ const result = await config.tokenBudgetMiddleware.onTokenUsed(
2381
+ response.usage.inputTokens,
2382
+ response.usage.outputTokens
2383
+ );
2384
+ if (result.warned && config.hooks.onNotification) {
2385
+ await config.hooks.onNotification(
2386
+ `\u26A0\uFE0F Token budget at ${result.percentUsed}%. Fallback model: ${result.fallback ?? "none (task will terminate at 100%)"}.`
2387
+ );
2388
+ }
2389
+ if (result.exceeded) {
2390
+ if (config.hooks.onTokenBudgetExceeded) {
2391
+ await config.hooks.onTokenBudgetExceeded(context, result.fallback);
2392
+ }
2393
+ abortController.abort();
2394
+ yield { type: "error", error: new Error("Token budget exceeded. Task requires manual continuation.") };
2395
+ break;
2396
+ }
2397
+ }
2262
2398
  contextManager.updateFromApiUsage(response.usage.inputTokens, response.usage.outputTokens);
2263
2399
  contextManager.updateFromMessages(messages);
2264
2400
  await contextManager.checkPressure();
@@ -2857,6 +2993,11 @@ function composeHooks(...pipelines) {
2857
2993
  for (const p of pipelines) {
2858
2994
  if (p.onCrossAgentMessage) await p.onCrossAgentMessage(event);
2859
2995
  }
2996
+ },
2997
+ async onTokenBudgetExceeded(ctx, fallback) {
2998
+ for (const p of pipelines) {
2999
+ if (p.onTokenBudgetExceeded) await p.onTokenBudgetExceeded(ctx, fallback);
3000
+ }
2860
3001
  }
2861
3002
  };
2862
3003
  }
@@ -3708,6 +3849,16 @@ var init_employees = __esm({
3708
3849
 
3709
3850
  // src/lib/task-router.ts
3710
3851
  import { randomUUID as randomUUID3 } from "crypto";
3852
+ function resolveBloomRouting(complexity, config = DEFAULT_BLOOM_CONFIG) {
3853
+ const tier = config.complexityToTier[complexity];
3854
+ const rule = config.tierRules[tier];
3855
+ return {
3856
+ tier,
3857
+ reviewRequired: rule.reviewRequired,
3858
+ manualOnly: rule.manualOnly,
3859
+ eligible: rule.eligible
3860
+ };
3861
+ }
3711
3862
  async function scoreEmployee(taskVector, agentId, searchFn) {
3712
3863
  const results = await searchFn(taskVector, agentId, { limit: 5 });
3713
3864
  if (results.length === 0) {
@@ -3720,13 +3871,29 @@ async function scoreEmployee(taskVector, agentId, searchFn) {
3720
3871
  }
3721
3872
  return { agentId, score: results.length / 5 };
3722
3873
  }
3723
- async function routeTask(taskDescription, employees, embedFn, searchFn) {
3724
- const specialists = employees.filter((e) => e.name !== "exe");
3874
+ async function routeTask(taskDescription, employees, embedFn, searchFn, options) {
3875
+ let specialists = employees.filter((e) => e.name !== "exe");
3725
3876
  if (specialists.length === 0) {
3726
3877
  throw new Error(
3727
3878
  "No specialist employees available. Create one with /exe-new-employee."
3728
3879
  );
3729
3880
  }
3881
+ let bloomRouting;
3882
+ if (options?.complexity) {
3883
+ bloomRouting = resolveBloomRouting(options.complexity, options.bloomConfig);
3884
+ if (bloomRouting.manualOnly) {
3885
+ throw new Error(
3886
+ `Task complexity "${options.complexity}" requires manual assignment (tier: ${bloomRouting.tier}).`
3887
+ );
3888
+ }
3889
+ if (bloomRouting.eligible.length > 0) {
3890
+ const eligible = new Set(bloomRouting.eligible);
3891
+ const filtered = specialists.filter((e) => eligible.has(e.name));
3892
+ if (filtered.length > 0) {
3893
+ specialists = filtered;
3894
+ }
3895
+ }
3896
+ }
3730
3897
  const taskVector = await embedFn(taskDescription);
3731
3898
  const scored = await Promise.all(
3732
3899
  specialists.map(async (emp) => {
@@ -3735,11 +3902,43 @@ async function routeTask(taskDescription, employees, embedFn, searchFn) {
3735
3902
  })
3736
3903
  );
3737
3904
  scored.sort((a, b) => b.score - a.score);
3738
- return scored[0];
3905
+ return { ...scored[0], bloomRouting };
3739
3906
  }
3907
+ var DEFAULT_BLOOM_CONFIG;
3740
3908
  var init_task_router = __esm({
3741
3909
  "src/lib/task-router.ts"() {
3742
3910
  "use strict";
3911
+ DEFAULT_BLOOM_CONFIG = {
3912
+ complexityToTier: {
3913
+ routine: "junior",
3914
+ standard: "standard",
3915
+ complex: "senior",
3916
+ critical: "specialist"
3917
+ },
3918
+ tierRules: {
3919
+ junior: {
3920
+ eligible: ["tom"],
3921
+ reviewRequired: false,
3922
+ manualOnly: false
3923
+ },
3924
+ standard: {
3925
+ eligible: ["tom"],
3926
+ reviewRequired: false,
3927
+ manualOnly: false
3928
+ },
3929
+ senior: {
3930
+ eligible: [],
3931
+ // any specialist, but review required
3932
+ reviewRequired: true,
3933
+ manualOnly: false
3934
+ },
3935
+ specialist: {
3936
+ eligible: [],
3937
+ reviewRequired: true,
3938
+ manualOnly: true
3939
+ }
3940
+ }
3941
+ };
3743
3942
  }
3744
3943
  });
3745
3944
 
@@ -4005,11 +4204,12 @@ function queueIntercom(targetSession, reason) {
4005
4204
  }
4006
4205
  writeQueue(queue);
4007
4206
  }
4008
- var QUEUE_PATH, INTERCOM_LOG;
4207
+ var QUEUE_PATH, TTL_MS, INTERCOM_LOG;
4009
4208
  var init_intercom_queue = __esm({
4010
4209
  "src/lib/intercom-queue.ts"() {
4011
4210
  "use strict";
4012
4211
  QUEUE_PATH = path11.join(os4.homedir(), ".exe-os", "intercom-queue.json");
4212
+ TTL_MS = 60 * 60 * 1e3;
4013
4213
  INTERCOM_LOG = path11.join(os4.homedir(), ".exe-os", "intercom.log");
4014
4214
  }
4015
4215
  });
@@ -4109,6 +4309,17 @@ function getGitRoot(dir) {
4109
4309
  return null;
4110
4310
  }
4111
4311
  }
4312
+ function getMainRepoRoot(dir) {
4313
+ try {
4314
+ const commonDir = execSync6(
4315
+ "git rev-parse --path-format=absolute --git-common-dir",
4316
+ { cwd: dir, encoding: "utf-8", timeout: GIT_TIMEOUT_MS, stdio: ["pipe", "pipe", "pipe"] }
4317
+ ).trim();
4318
+ return realpath(path13.dirname(commonDir));
4319
+ } catch {
4320
+ return null;
4321
+ }
4322
+ }
4112
4323
  function worktreePath(repoRoot, employeeName, instance) {
4113
4324
  const label = instanceLabel(employeeName, instance);
4114
4325
  return path13.join(repoRoot, ".worktrees", label);
@@ -4300,6 +4511,36 @@ import path15 from "path";
4300
4511
  import { execSync as execSync7 } from "child_process";
4301
4512
  import { mkdir as mkdir3, writeFile as writeFile3, appendFile } from "fs/promises";
4302
4513
  import { existsSync as existsSync10, readFileSync as readFileSync9 } from "fs";
4514
+ async function writeCheckpoint(input) {
4515
+ const client = getClient();
4516
+ const row = await resolveTask(client, input.taskId);
4517
+ const taskId = String(row.id);
4518
+ const now = (/* @__PURE__ */ new Date()).toISOString();
4519
+ const blockedByIds = [];
4520
+ if (row.blocked_by) {
4521
+ blockedByIds.push(String(row.blocked_by));
4522
+ }
4523
+ const checkpoint = {
4524
+ step: input.step,
4525
+ context_summary: input.contextSummary,
4526
+ files_touched: input.filesTouched ?? [],
4527
+ blocked_by_ids: blockedByIds,
4528
+ last_checkpoint_at: now
4529
+ };
4530
+ const result = await client.execute({
4531
+ sql: `UPDATE tasks SET checkpoint = ?, checkpoint_count = checkpoint_count + 1, updated_at = ? WHERE id = ?`,
4532
+ args: [JSON.stringify(checkpoint), now, taskId]
4533
+ });
4534
+ if (result.rowsAffected === 0) {
4535
+ throw new Error(`Checkpoint write failed: task ${taskId} not found`);
4536
+ }
4537
+ const countResult = await client.execute({
4538
+ sql: "SELECT checkpoint_count FROM tasks WHERE id = ?",
4539
+ args: [taskId]
4540
+ });
4541
+ const checkpointCount = Number(countResult.rows[0]?.checkpoint_count ?? 1);
4542
+ return { checkpointCount };
4543
+ }
4303
4544
  function extractParentFromContext(contextBody) {
4304
4545
  if (!contextBody) return null;
4305
4546
  const match = contextBody.match(
@@ -4406,9 +4647,10 @@ async function createTaskCore(input) {
4406
4647
  } catch {
4407
4648
  }
4408
4649
  }
4650
+ const complexity = input.complexity ?? "standard";
4409
4651
  await client.execute({
4410
- sql: `INSERT INTO tasks (id, title, assigned_to, assigned_by, project_name, priority, status, task_file, blocked_by, parent_task_id, reviewer, context, created_at, updated_at)
4411
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
4652
+ sql: `INSERT INTO tasks (id, title, assigned_to, assigned_by, project_name, priority, status, task_file, blocked_by, parent_task_id, reviewer, context, complexity, budget_tokens, budget_fallback_model, tokens_used, tokens_warned_at, created_at, updated_at)
4653
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
4412
4654
  args: [
4413
4655
  id,
4414
4656
  input.title,
@@ -4422,6 +4664,11 @@ async function createTaskCore(input) {
4422
4664
  parentTaskId,
4423
4665
  input.reviewer ?? null,
4424
4666
  input.context,
4667
+ input.budgetTokens ?? null,
4668
+ input.budgetFallbackModel ?? null,
4669
+ 0,
4670
+ null,
4671
+ complexity,
4425
4672
  now,
4426
4673
  now
4427
4674
  ]
@@ -4437,7 +4684,11 @@ async function createTaskCore(input) {
4437
4684
  taskFile,
4438
4685
  createdAt: now,
4439
4686
  updatedAt: now,
4440
- warning
4687
+ warning,
4688
+ budgetTokens: input.budgetTokens ?? null,
4689
+ budgetFallbackModel: input.budgetFallbackModel ?? null,
4690
+ tokensUsed: 0,
4691
+ tokensWarnedAt: null
4441
4692
  };
4442
4693
  }
4443
4694
  async function listTasks(input) {
@@ -4477,7 +4728,12 @@ async function listTasks(input) {
4477
4728
  status: String(r.status),
4478
4729
  taskFile: String(r.task_file),
4479
4730
  createdAt: String(r.created_at),
4480
- updatedAt: String(r.updated_at)
4731
+ updatedAt: String(r.updated_at),
4732
+ checkpointCount: Number(r.checkpoint_count ?? 0),
4733
+ budgetTokens: r.budget_tokens !== null ? Number(r.budget_tokens) : null,
4734
+ budgetFallbackModel: r.budget_fallback_model !== null ? String(r.budget_fallback_model) : null,
4735
+ tokensUsed: Number(r.tokens_used ?? 0),
4736
+ tokensWarnedAt: r.tokens_warned_at !== null ? Number(r.tokens_warned_at) : null
4481
4737
  }));
4482
4738
  }
4483
4739
  function checkStaleCompletion(taskContext, taskCreatedAt) {
@@ -4485,8 +4741,13 @@ function checkStaleCompletion(taskContext, taskCreatedAt) {
4485
4741
  if (!DELEGATION_KEYWORDS.test(taskContext)) return null;
4486
4742
  try {
4487
4743
  const since = new Date(taskCreatedAt).toISOString();
4744
+ const branch = execSync7(
4745
+ "git rev-parse --abbrev-ref HEAD 2>/dev/null",
4746
+ { encoding: "utf8", timeout: 3e3 }
4747
+ ).trim();
4748
+ const branchArg = branch && branch !== "HEAD" ? branch : "";
4488
4749
  const commitCount = execSync7(
4489
- `git log --oneline --since="${since}" 2>/dev/null | wc -l`,
4750
+ `git log --oneline --since="${since}" ${branchArg} 2>/dev/null | wc -l`,
4490
4751
  { encoding: "utf8", timeout: 5e3 }
4491
4752
  ).trim();
4492
4753
  const count = parseInt(commitCount, 10);
@@ -4545,6 +4806,14 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
4545
4806
  const claimedBy = cur?.assigned_tmux ? ` (claimed by ${cur.assigned_tmux})` : "";
4546
4807
  throw new Error(`${TASK_ALREADY_CLAIMED_PREFIX}: task ${taskId} is ${status}${claimedBy}`);
4547
4808
  }
4809
+ try {
4810
+ await writeCheckpoint({
4811
+ taskId,
4812
+ step: "claimed",
4813
+ contextSummary: `Task claimed by session. Transitioning open \u2192 in_progress.`
4814
+ });
4815
+ } catch {
4816
+ }
4548
4817
  return { row, taskFile, now, taskId };
4549
4818
  }
4550
4819
  if (input.result) {
@@ -4558,6 +4827,14 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
4558
4827
  args: [input.status, now, taskId]
4559
4828
  });
4560
4829
  }
4830
+ try {
4831
+ await writeCheckpoint({
4832
+ taskId,
4833
+ step: `status_transition:${input.status}`,
4834
+ contextSummary: input.result ? `Transitioned to ${input.status}. Result: ${input.result.slice(0, 500)}` : `Transitioned to ${input.status}.`
4835
+ });
4836
+ } catch {
4837
+ }
4561
4838
  return { row, taskFile, now, taskId };
4562
4839
  }
4563
4840
  async function deleteTaskCore(taskId, _baseDir) {
@@ -4711,23 +4988,38 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
4711
4988
  if (String(row.assigned_by) !== "system" || !taskFile.includes("review-")) return;
4712
4989
  try {
4713
4990
  const client = getClient();
4714
- const fileName = taskFile.split("/").pop() ?? "";
4715
- const reviewPrefix = fileName.replace(".md", "");
4716
- const parts = reviewPrefix.split("-");
4717
- if (parts.length >= 3 && parts[0] === "review") {
4718
- const agent = parts[1];
4719
- const slug = parts.slice(2).join("-");
4720
- const originalTaskFile = `exe/${agent}/${slug}.md`;
4991
+ const now = (/* @__PURE__ */ new Date()).toISOString();
4992
+ const parentId = row.parent_task_id ? String(row.parent_task_id) : null;
4993
+ if (parentId) {
4721
4994
  const result = await client.execute({
4722
- sql: "UPDATE tasks SET status = 'done', updated_at = ? WHERE task_file = ? AND status = 'needs_review'",
4723
- args: [(/* @__PURE__ */ new Date()).toISOString(), originalTaskFile]
4995
+ sql: "UPDATE tasks SET status = 'done', updated_at = ? WHERE id = ? AND status = 'needs_review'",
4996
+ args: [now, parentId]
4724
4997
  });
4725
4998
  if (result.rowsAffected > 0) {
4726
4999
  process.stderr.write(
4727
- `[review-cleanup] Cascaded original task to done: ${originalTaskFile}
5000
+ `[review-cleanup] Cascaded original task to done via parent_task_id: ${parentId}
4728
5001
  `
4729
5002
  );
4730
5003
  }
5004
+ } else {
5005
+ const fileName = taskFile.split("/").pop() ?? "";
5006
+ const reviewPrefix = fileName.replace(".md", "");
5007
+ const parts = reviewPrefix.split("-");
5008
+ if (parts.length >= 3 && parts[0] === "review") {
5009
+ const agent = parts[1];
5010
+ const slug = parts.slice(2).join("-");
5011
+ const originalTaskFile = `exe/${agent}/${slug}.md`;
5012
+ const result = await client.execute({
5013
+ sql: "UPDATE tasks SET status = 'done', updated_at = ? WHERE task_file = ? AND status = 'needs_review'",
5014
+ args: [now, originalTaskFile]
5015
+ });
5016
+ if (result.rowsAffected > 0) {
5017
+ process.stderr.write(
5018
+ `[review-cleanup] Cascaded original task to done (legacy path): ${originalTaskFile}
5019
+ `
5020
+ );
5021
+ }
5022
+ }
4731
5023
  }
4732
5024
  } catch (err) {
4733
5025
  process.stderr.write(
@@ -4848,12 +5140,23 @@ function getProjectName(cwd2) {
4848
5140
  const dir = cwd2 ?? process.cwd();
4849
5141
  if (_cached2 && _cachedCwd === dir) return _cached2;
4850
5142
  try {
4851
- const repoRoot = execSync8("git rev-parse --show-toplevel", {
4852
- cwd: dir,
4853
- encoding: "utf8",
4854
- timeout: 2e3,
4855
- stdio: ["pipe", "pipe", "pipe"]
4856
- }).trim();
5143
+ let repoRoot;
5144
+ try {
5145
+ const gitCommonDir = execSync8("git rev-parse --path-format=absolute --git-common-dir", {
5146
+ cwd: dir,
5147
+ encoding: "utf8",
5148
+ timeout: 2e3,
5149
+ stdio: ["pipe", "pipe", "pipe"]
5150
+ }).trim();
5151
+ repoRoot = path18.dirname(gitCommonDir);
5152
+ } catch {
5153
+ repoRoot = execSync8("git rev-parse --show-toplevel", {
5154
+ cwd: dir,
5155
+ encoding: "utf8",
5156
+ timeout: 2e3,
5157
+ stdio: ["pipe", "pipe", "pipe"]
5158
+ }).trim();
5159
+ }
4857
5160
  _cached2 = path18.basename(repoRoot);
4858
5161
  _cachedCwd = dir;
4859
5162
  return _cached2;
@@ -4959,7 +5262,9 @@ async function dispatchTaskToEmployee(input) {
4959
5262
  return { dispatched, session: sessionName, crossProject };
4960
5263
  } else {
4961
5264
  const projectDir = input.projectDir ?? process.cwd();
4962
- const result = ensureEmployee(input.assignedTo, exeSession, projectDir);
5265
+ const result = ensureEmployee(input.assignedTo, exeSession, projectDir, {
5266
+ autoInstance: input.assignedTo === "tom" || input.assignedTo === "sasha"
5267
+ });
4963
5268
  if (result.status === "failed") {
4964
5269
  process.stderr.write(
4965
5270
  `[dispatch] Failed to spawn ${input.assignedTo}: ${result.error}
@@ -5323,7 +5628,8 @@ __export(tasks_exports, {
5323
5628
  resolveTask: () => resolveTask,
5324
5629
  slugify: () => slugify,
5325
5630
  updateTask: () => updateTask,
5326
- updateTaskStatus: () => updateTaskStatus
5631
+ updateTaskStatus: () => updateTaskStatus,
5632
+ writeCheckpoint: () => writeCheckpoint
5327
5633
  });
5328
5634
  import path19 from "path";
5329
5635
  import { writeFileSync as writeFileSync4, mkdirSync as mkdirSync5, unlinkSync as unlinkSync3 } from "fs";
@@ -5365,10 +5671,11 @@ async function updateTask(input) {
5365
5671
  try {
5366
5672
  const client = getClient();
5367
5673
  const taskTitle = String(row.title);
5674
+ const escaped = taskTitle.replace(/%/g, "\\%").replace(/_/g, "\\_");
5368
5675
  await client.execute({
5369
5676
  sql: `UPDATE tasks SET status = 'cancelled', updated_at = ?
5370
- WHERE title LIKE ? AND status IN ('open', 'in_progress')`,
5371
- args: [now, `%left%${taskTitle}%in_progress`]
5677
+ WHERE title LIKE ? ESCAPE '\\' AND status IN ('open', 'in_progress')`,
5678
+ args: [now, `%left '${escaped}' as in\\_progress%`]
5372
5679
  });
5373
5680
  } catch {
5374
5681
  }
@@ -5426,6 +5733,10 @@ async function updateTask(input) {
5426
5733
  taskFile,
5427
5734
  createdAt: String(row.created_at),
5428
5735
  updatedAt: now,
5736
+ budgetTokens: row.budget_tokens !== void 0 && row.budget_tokens !== null ? Number(row.budget_tokens) : null,
5737
+ budgetFallbackModel: row.budget_fallback_model !== void 0 && row.budget_fallback_model !== null ? String(row.budget_fallback_model) : null,
5738
+ tokensUsed: Number(row.tokens_used ?? 0),
5739
+ tokensWarnedAt: row.tokens_warned_at !== void 0 && row.tokens_warned_at !== null ? Number(row.tokens_warned_at) : null,
5429
5740
  nextTask
5430
5741
  };
5431
5742
  }
@@ -5939,6 +6250,11 @@ function getSessionState(sessionName) {
5939
6250
  if (!transport.isAlive(sessionName)) return "offline";
5940
6251
  try {
5941
6252
  const pane = transport.capturePane(sessionName, 5);
6253
+ if (!pane.includes("\u276F") && !pane.includes("Claude Code") && !BUSY_PATTERN.test(pane) && !/Running…/.test(pane)) {
6254
+ if (/\$\s*$/.test(pane) || /% $/.test(pane.trimEnd())) {
6255
+ return "no_claude";
6256
+ }
6257
+ }
5942
6258
  if (/Running…/.test(pane)) return "tool";
5943
6259
  if (BUSY_PATTERN.test(pane)) return "thinking";
5944
6260
  return "idle";
@@ -5969,7 +6285,14 @@ function sendIntercom(targetSession) {
5969
6285
  logIntercom(`SKIP \u2192 ${targetSession} (session not found)`);
5970
6286
  return "failed";
5971
6287
  }
5972
- if (isSessionBusy(targetSession)) {
6288
+ const sessionState = getSessionState(targetSession);
6289
+ if (sessionState === "no_claude") {
6290
+ queueIntercom(targetSession, "claude not running in session");
6291
+ recordDebounce(targetSession);
6292
+ logIntercom(`QUEUED \u2192 ${targetSession} (no claude process \u2014 raw shell detected)`);
6293
+ return "queued";
6294
+ }
6295
+ if (sessionState === "thinking" || sessionState === "tool") {
5973
6296
  queueIntercom(targetSession, "session busy at send time");
5974
6297
  recordDebounce(targetSession);
5975
6298
  logIntercom(`QUEUED \u2192 ${targetSession} (session busy, will retry from queue)`);
@@ -5981,18 +6304,7 @@ function sendIntercom(targetSession) {
5981
6304
  }
5982
6305
  transport.sendKeys(targetSession, "/exe-intercom");
5983
6306
  recordDebounce(targetSession);
5984
- for (let i = 0; i < INTERCOM_POLL_MAX_ATTEMPTS; i++) {
5985
- try {
5986
- execSync9(`sleep ${INTERCOM_POLL_INTERVAL_S}`);
5987
- } catch {
5988
- }
5989
- const state = getSessionState(targetSession);
5990
- if (state === "thinking" || state === "tool") {
5991
- logIntercom(`ACKNOWLEDGED \u2192 ${targetSession} (state=${state}, poll=${i + 1})`);
5992
- return "acknowledged";
5993
- }
5994
- }
5995
- logIntercom(`DELIVERED \u2192 ${targetSession} (no state transition after ${INTERCOM_POLL_MAX_ATTEMPTS}s)`);
6307
+ logIntercom(`DELIVERED \u2192 ${targetSession} (fire-and-forget)`);
5996
6308
  return "delivered";
5997
6309
  } catch {
5998
6310
  logIntercom(`FAIL \u2192 ${targetSession}`);
@@ -6009,7 +6321,17 @@ function notifyParentExe(sessionKey) {
6009
6321
  process.stderr.write(`[intercom] notifyParentExe \u2192 ${target}
6010
6322
  `);
6011
6323
  const result = sendIntercom(target);
6012
- return result !== "failed";
6324
+ if (result === "failed") {
6325
+ const rootExe = resolveExeSession();
6326
+ if (rootExe && rootExe !== target) {
6327
+ process.stderr.write(`[intercom] notifyParentExe: dispatcher ${target} dead, falling back to root exe ${rootExe}
6328
+ `);
6329
+ const fallback = sendIntercom(rootExe);
6330
+ return fallback !== "failed";
6331
+ }
6332
+ return false;
6333
+ }
6334
+ return true;
6013
6335
  }
6014
6336
  function ensureEmployee(employeeName, exeSession, projectDir, opts) {
6015
6337
  if (employeeName === "exe") {
@@ -6058,7 +6380,8 @@ function ensureEmployee(employeeName, exeSession, projectDir, opts) {
6058
6380
  return { status: "failed", sessionName, error: "intercom delivery failed" };
6059
6381
  }
6060
6382
  const spawnOpts = { ...opts, instance: effectiveInstance };
6061
- const wtPath = ensureWorktree(projectDir, employeeName, effectiveInstance);
6383
+ const mainRoot = getMainRepoRoot(projectDir) ?? projectDir;
6384
+ const wtPath = ensureWorktree(mainRoot, employeeName, effectiveInstance);
6062
6385
  if (wtPath) {
6063
6386
  spawnOpts.cwd = wtPath;
6064
6387
  }
@@ -6239,7 +6562,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
6239
6562
  let booted = false;
6240
6563
  for (let i = 0; i < 30; i++) {
6241
6564
  try {
6242
- execSync9("sleep 1");
6565
+ execSync9("sleep 0.5");
6243
6566
  } catch {
6244
6567
  }
6245
6568
  try {
@@ -6259,7 +6582,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
6259
6582
  }
6260
6583
  }
6261
6584
  if (!booted) {
6262
- return { sessionName, error: `${useExeAgent ? "exe-agent" : "claude"} did not boot within 30s` };
6585
+ return { sessionName, error: `${useExeAgent ? "exe-agent" : "claude"} did not boot within 15s` };
6263
6586
  }
6264
6587
  if (!useExeAgent) {
6265
6588
  try {
@@ -6277,7 +6600,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
6277
6600
  });
6278
6601
  return { sessionName };
6279
6602
  }
6280
- var SESSION_CACHE, BEHAVIORS_EXPORT_TIMEOUT_MS, VERIFY_PANE_LINES, INTERCOM_DEBOUNCE_MS, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS, BUSY_PATTERN, INTERCOM_POLL_INTERVAL_S, INTERCOM_POLL_MAX_ATTEMPTS;
6603
+ var SESSION_CACHE, BEHAVIORS_EXPORT_TIMEOUT_MS, VERIFY_PANE_LINES, INTERCOM_DEBOUNCE_MS, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS, BUSY_PATTERN;
6281
6604
  var init_tmux_routing = __esm({
6282
6605
  "src/lib/tmux-routing.ts"() {
6283
6606
  "use strict";
@@ -6298,8 +6621,6 @@ var init_tmux_routing = __esm({
6298
6621
  DEBOUNCE_FILE = path20.join(SESSION_CACHE, "intercom-debounce.json");
6299
6622
  DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
6300
6623
  BUSY_PATTERN = /[✻✽✶✳·].*…|Running…/;
6301
- INTERCOM_POLL_INTERVAL_S = 1;
6302
- INTERCOM_POLL_MAX_ATTEMPTS = 8;
6303
6624
  }
6304
6625
  });
6305
6626
 
@@ -7241,13 +7562,27 @@ async function ensureShardSchema(client) {
7241
7562
  "ALTER TABLE memories ADD COLUMN document_id TEXT",
7242
7563
  "ALTER TABLE memories ADD COLUMN user_id TEXT",
7243
7564
  "ALTER TABLE memories ADD COLUMN char_offset INTEGER",
7244
- "ALTER TABLE memories ADD COLUMN page_number INTEGER"
7565
+ "ALTER TABLE memories ADD COLUMN page_number INTEGER",
7566
+ // Source provenance columns (must match database.ts)
7567
+ "ALTER TABLE memories ADD COLUMN source_path TEXT",
7568
+ "ALTER TABLE memories ADD COLUMN source_type TEXT DEFAULT 'text'",
7569
+ "ALTER TABLE memories ADD COLUMN tier INTEGER DEFAULT 3",
7570
+ "ALTER TABLE memories ADD COLUMN supersedes_id TEXT"
7245
7571
  ]) {
7246
7572
  try {
7247
7573
  await client.execute(col);
7248
7574
  } catch {
7249
7575
  }
7250
7576
  }
7577
+ for (const idx of [
7578
+ "CREATE INDEX IF NOT EXISTS idx_memories_tier ON memories(tier)",
7579
+ "CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL"
7580
+ ]) {
7581
+ try {
7582
+ await client.execute(idx);
7583
+ } catch {
7584
+ }
7585
+ }
7251
7586
  try {
7252
7587
  await client.execute("CREATE INDEX IF NOT EXISTS idx_memories_status ON memories(status)");
7253
7588
  } catch {
@@ -7356,8 +7691,11 @@ var store_exports = {};
7356
7691
  __export(store_exports, {
7357
7692
  attachDocumentMetadata: () => attachDocumentMetadata,
7358
7693
  buildWikiScopeFilter: () => buildWikiScopeFilter,
7694
+ classifyTier: () => classifyTier,
7359
7695
  disposeStore: () => disposeStore,
7360
7696
  flushBatch: () => flushBatch,
7697
+ flushTier3: () => flushTier3,
7698
+ getMemoryCardinality: () => getMemoryCardinality,
7361
7699
  initStore: () => initStore,
7362
7700
  reserveVersions: () => reserveVersions,
7363
7701
  searchMemories: () => searchMemories,
@@ -7403,6 +7741,11 @@ async function initStore(options) {
7403
7741
  const vResult = await client.execute("SELECT MAX(version) as max_v FROM memories");
7404
7742
  _nextVersion = (Number(vResult.rows[0]?.max_v) || 0) + 1;
7405
7743
  }
7744
+ function classifyTier(record) {
7745
+ if (record.tool_name === "commit_to_long_term_memory" && (record.importance ?? 0) >= 8) return 1;
7746
+ if (["store_memory", "manual"].includes(record.tool_name ?? "") && (record.importance ?? 0) >= 5) return 2;
7747
+ return 3;
7748
+ }
7406
7749
  async function writeMemory(record) {
7407
7750
  if (record.vector !== null && record.vector.length !== EMBEDDING_DIM) {
7408
7751
  throw new Error(
@@ -7430,7 +7773,11 @@ async function writeMemory(record) {
7430
7773
  document_id: record.document_id ?? null,
7431
7774
  user_id: record.user_id ?? null,
7432
7775
  char_offset: record.char_offset ?? null,
7433
- page_number: record.page_number ?? null
7776
+ page_number: record.page_number ?? null,
7777
+ source_path: record.source_path ?? null,
7778
+ source_type: record.source_type ?? null,
7779
+ tier: record.tier ?? classifyTier(record),
7780
+ supersedes_id: record.supersedes_id ?? null
7434
7781
  };
7435
7782
  _pendingRecords.push(dbRow);
7436
7783
  if (_flushTimer === null) {
@@ -7462,20 +7809,26 @@ async function flushBatch() {
7462
7809
  const userId = row.user_id ?? null;
7463
7810
  const charOffset = row.char_offset ?? null;
7464
7811
  const pageNumber = row.page_number ?? null;
7812
+ const sourcePath = row.source_path ?? null;
7813
+ const sourceType = row.source_type ?? null;
7814
+ const tier = row.tier ?? 3;
7815
+ const supersedesId = row.supersedes_id ?? null;
7465
7816
  return {
7466
7817
  sql: hasVector ? `INSERT OR IGNORE INTO memories
7467
7818
  (id, agent_id, agent_role, session_id, timestamp,
7468
7819
  tool_name, project_name,
7469
7820
  has_error, raw_text, vector, version, task_id, importance, status,
7470
7821
  confidence, last_accessed,
7471
- workspace_id, document_id, user_id, char_offset, page_number)
7472
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories
7822
+ workspace_id, document_id, user_id, char_offset, page_number,
7823
+ source_path, source_type, tier, supersedes_id)
7824
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories
7473
7825
  (id, agent_id, agent_role, session_id, timestamp,
7474
7826
  tool_name, project_name,
7475
7827
  has_error, raw_text, vector, version, task_id, importance, status,
7476
7828
  confidence, last_accessed,
7477
- workspace_id, document_id, user_id, char_offset, page_number)
7478
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
7829
+ workspace_id, document_id, user_id, char_offset, page_number,
7830
+ source_path, source_type, tier, supersedes_id)
7831
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
7479
7832
  args: hasVector ? [
7480
7833
  row.id,
7481
7834
  row.agent_id,
@@ -7497,7 +7850,11 @@ async function flushBatch() {
7497
7850
  documentId,
7498
7851
  userId,
7499
7852
  charOffset,
7500
- pageNumber
7853
+ pageNumber,
7854
+ sourcePath,
7855
+ sourceType,
7856
+ tier,
7857
+ supersedesId
7501
7858
  ] : [
7502
7859
  row.id,
7503
7860
  row.agent_id,
@@ -7518,7 +7875,11 @@ async function flushBatch() {
7518
7875
  documentId,
7519
7876
  userId,
7520
7877
  charOffset,
7521
- pageNumber
7878
+ pageNumber,
7879
+ sourcePath,
7880
+ sourceType,
7881
+ tier,
7882
+ supersedesId
7522
7883
  ]
7523
7884
  };
7524
7885
  };
@@ -7592,7 +7953,8 @@ async function searchMemories(queryVector, agentId, options) {
7592
7953
  has_error, raw_text, vector, importance, status,
7593
7954
  confidence, last_accessed,
7594
7955
  workspace_id, document_id, user_id,
7595
- char_offset, page_number
7956
+ char_offset, page_number,
7957
+ source_path, source_type
7596
7958
  FROM memories
7597
7959
  WHERE agent_id = ?
7598
7960
  AND vector IS NOT NULL${statusFilter}
@@ -7641,7 +8003,9 @@ async function searchMemories(queryVector, agentId, options) {
7641
8003
  document_id: row.document_id ?? null,
7642
8004
  user_id: row.user_id ?? null,
7643
8005
  char_offset: row.char_offset ?? null,
7644
- page_number: row.page_number ?? null
8006
+ page_number: row.page_number ?? null,
8007
+ source_path: row.source_path ?? null,
8008
+ source_type: row.source_type ?? null
7645
8009
  }));
7646
8010
  }
7647
8011
  async function attachDocumentMetadata(records) {
@@ -7679,6 +8043,25 @@ async function attachDocumentMetadata(records) {
7679
8043
  }
7680
8044
  return records;
7681
8045
  }
8046
+ async function flushTier3(agentId, options) {
8047
+ const client = getClient();
8048
+ const maxAge = options?.maxAgeHours ?? 72;
8049
+ const cutoff = new Date(Date.now() - maxAge * 36e5).toISOString();
8050
+ if (options?.dryRun) {
8051
+ const result2 = await client.execute({
8052
+ sql: `SELECT COUNT(*) as cnt FROM memories
8053
+ WHERE agent_id = ? AND tier = 3 AND status = 'active' AND timestamp < ?`,
8054
+ args: [agentId, cutoff]
8055
+ });
8056
+ return { archived: Number(result2.rows[0]?.cnt ?? 0) };
8057
+ }
8058
+ const result = await client.execute({
8059
+ sql: `UPDATE memories SET status = 'archived'
8060
+ WHERE agent_id = ? AND tier = 3 AND status = 'active' AND timestamp < ?`,
8061
+ args: [agentId, cutoff]
8062
+ });
8063
+ return { archived: result.rowsAffected };
8064
+ }
7682
8065
  async function disposeStore() {
7683
8066
  if (_flushTimer !== null) {
7684
8067
  clearInterval(_flushTimer);
@@ -7709,6 +8092,18 @@ function reserveVersions(count) {
7709
8092
  }
7710
8093
  return reserved;
7711
8094
  }
8095
+ async function getMemoryCardinality(agentId) {
8096
+ try {
8097
+ const client = getClient();
8098
+ const result = await client.execute({
8099
+ sql: `SELECT COUNT(*) as cnt FROM memories WHERE agent_id = ? AND COALESCE(status, 'active') = 'active'`,
8100
+ args: [agentId]
8101
+ });
8102
+ return Number(result.rows[0]?.cnt) || 0;
8103
+ } catch {
8104
+ return 0;
8105
+ }
8106
+ }
7712
8107
  var _pendingRecords, _batchSize, _flushIntervalMs, _flushTimer, _flushing, _nextVersion;
7713
8108
  var init_store = __esm({
7714
8109
  "src/lib/store.ts"() {