@askexenow/exe-os 0.9.92 → 0.9.94

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 (88) hide show
  1. package/deploy/compose/docker-compose.yml +1 -0
  2. package/dist/bin/agentic-ontology-backfill.js +65 -8
  3. package/dist/bin/agentic-reflection-backfill.js +54 -3
  4. package/dist/bin/agentic-semantic-label.js +54 -3
  5. package/dist/bin/backfill-conversations.js +69 -9
  6. package/dist/bin/backfill-responses.js +69 -9
  7. package/dist/bin/backfill-vectors.js +54 -3
  8. package/dist/bin/bulk-sync-postgres.js +66 -8
  9. package/dist/bin/cleanup-stale-review-tasks.js +118 -13
  10. package/dist/bin/cli.js +1605 -456
  11. package/dist/bin/customer-readiness.js +51 -0
  12. package/dist/bin/exe-agent.js +17 -3
  13. package/dist/bin/exe-assign.js +75 -9
  14. package/dist/bin/exe-boot.js +111 -12
  15. package/dist/bin/exe-call.js +17 -3
  16. package/dist/bin/exe-cloud.js +76 -10
  17. package/dist/bin/exe-dispatch.js +133 -18
  18. package/dist/bin/exe-doctor.js +75 -9
  19. package/dist/bin/exe-export-behaviors.js +75 -9
  20. package/dist/bin/exe-forget.js +94 -9
  21. package/dist/bin/exe-gateway.js +132 -18
  22. package/dist/bin/exe-heartbeat.js +118 -13
  23. package/dist/bin/exe-kill.js +75 -9
  24. package/dist/bin/exe-launch-agent.js +75 -9
  25. package/dist/bin/exe-new-employee.js +18 -4
  26. package/dist/bin/exe-pending-messages.js +118 -13
  27. package/dist/bin/exe-pending-notifications.js +118 -13
  28. package/dist/bin/exe-pending-reviews.js +118 -13
  29. package/dist/bin/exe-rename.js +75 -9
  30. package/dist/bin/exe-review.js +75 -9
  31. package/dist/bin/exe-search.js +100 -9
  32. package/dist/bin/exe-session-cleanup.js +133 -18
  33. package/dist/bin/exe-settings.js +1 -0
  34. package/dist/bin/exe-start-codex.js +65 -8
  35. package/dist/bin/exe-start-opencode.js +65 -8
  36. package/dist/bin/exe-status.js +118 -13
  37. package/dist/bin/exe-support.js +1 -0
  38. package/dist/bin/exe-team.js +75 -9
  39. package/dist/bin/git-sweep.js +133 -18
  40. package/dist/bin/graph-backfill.js +65 -8
  41. package/dist/bin/graph-export.js +75 -9
  42. package/dist/bin/intercom-check.js +133 -18
  43. package/dist/bin/scan-tasks.js +133 -18
  44. package/dist/bin/setup.js +55 -4
  45. package/dist/bin/shard-migrate.js +65 -8
  46. package/dist/bin/stack-update.js +57 -1
  47. package/dist/bin/update.js +1 -1
  48. package/dist/gateway/index.js +133 -18
  49. package/dist/hooks/bug-report-worker.js +133 -18
  50. package/dist/hooks/codex-stop-task-finalizer.js +123 -14
  51. package/dist/hooks/commit-complete.js +133 -18
  52. package/dist/hooks/error-recall.js +100 -9
  53. package/dist/hooks/ingest.js +75 -9
  54. package/dist/hooks/instructions-loaded.js +75 -9
  55. package/dist/hooks/notification.js +75 -9
  56. package/dist/hooks/post-compact.js +310 -50
  57. package/dist/hooks/post-tool-combined.js +433 -13
  58. package/dist/hooks/pre-compact.js +133 -18
  59. package/dist/hooks/pre-tool-use.js +118 -13
  60. package/dist/hooks/prompt-submit.js +191 -19
  61. package/dist/hooks/session-end.js +133 -18
  62. package/dist/hooks/session-start.js +143 -13
  63. package/dist/hooks/stop.js +118 -13
  64. package/dist/hooks/subagent-stop.js +118 -13
  65. package/dist/hooks/summary-worker.js +96 -7
  66. package/dist/index.js +133 -18
  67. package/dist/lib/cloud-sync.js +38 -0
  68. package/dist/lib/consolidation.js +3 -1
  69. package/dist/lib/database.js +37 -0
  70. package/dist/lib/db.js +37 -0
  71. package/dist/lib/device-registry.js +37 -0
  72. package/dist/lib/employee-templates.js +17 -3
  73. package/dist/lib/exe-daemon.js +913 -42
  74. package/dist/lib/hybrid-search.js +100 -9
  75. package/dist/lib/license.js +1 -1
  76. package/dist/lib/messaging.js +40 -4
  77. package/dist/lib/schedules.js +54 -3
  78. package/dist/lib/store.js +75 -9
  79. package/dist/lib/tasks.js +58 -9
  80. package/dist/lib/tmux-routing.js +58 -9
  81. package/dist/mcp/server.js +875 -42
  82. package/dist/mcp/tools/create-task.js +67 -12
  83. package/dist/mcp/tools/list-tasks.js +46 -5
  84. package/dist/mcp/tools/send-message.js +40 -4
  85. package/dist/mcp/tools/update-task.js +58 -9
  86. package/dist/runtime/index.js +133 -18
  87. package/dist/tui/App.js +132 -18
  88. package/package.json +1 -1
@@ -3062,6 +3062,20 @@ async function ensureSchema() {
3062
3062
  });
3063
3063
  } catch {
3064
3064
  }
3065
+ try {
3066
+ await client.execute({
3067
+ sql: `ALTER TABLE tasks ADD COLUMN spawn_runtime TEXT`,
3068
+ args: []
3069
+ });
3070
+ } catch {
3071
+ }
3072
+ try {
3073
+ await client.execute({
3074
+ sql: `ALTER TABLE tasks ADD COLUMN spawn_model TEXT`,
3075
+ args: []
3076
+ });
3077
+ } catch {
3078
+ }
3065
3079
  await client.executeMultiple(`
3066
3080
  CREATE VIRTUAL TABLE IF NOT EXISTS conversations_fts USING fts5(
3067
3081
  content_text,
@@ -3313,6 +3327,22 @@ async function ensureSchema() {
3313
3327
  );
3314
3328
  } catch {
3315
3329
  }
3330
+ for (const col of [
3331
+ "ALTER TABLE memories ADD COLUMN valid_from TEXT",
3332
+ "ALTER TABLE memories ADD COLUMN invalid_at TEXT"
3333
+ ]) {
3334
+ try {
3335
+ await client.execute(col);
3336
+ } catch {
3337
+ }
3338
+ }
3339
+ try {
3340
+ await client.execute({
3341
+ sql: `UPDATE memories SET valid_from = timestamp WHERE valid_from IS NULL`,
3342
+ args: []
3343
+ });
3344
+ } catch {
3345
+ }
3316
3346
  try {
3317
3347
  await client.execute({
3318
3348
  sql: `ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'`,
@@ -3355,6 +3385,13 @@ async function ensureSchema() {
3355
3385
  } catch {
3356
3386
  }
3357
3387
  }
3388
+ try {
3389
+ await client.execute({
3390
+ sql: `ALTER TABLE memories ADD COLUMN procedure_for TEXT`,
3391
+ args: []
3392
+ });
3393
+ } catch {
3394
+ }
3358
3395
  try {
3359
3396
  await client.execute({
3360
3397
  sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
@@ -3415,7 +3452,7 @@ import { pathToFileURL as pathToFileURL2 } from "url";
3415
3452
  import os7 from "os";
3416
3453
  import path10 from "path";
3417
3454
  import { jwtVerify, importSPKI } from "jose";
3418
- var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH;
3455
+ var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, API_BASE;
3419
3456
  var init_license = __esm({
3420
3457
  "src/lib/license.ts"() {
3421
3458
  "use strict";
@@ -3423,6 +3460,7 @@ var init_license = __esm({
3423
3460
  LICENSE_PATH = path10.join(EXE_AI_DIR, "license.key");
3424
3461
  CACHE_PATH = path10.join(EXE_AI_DIR, "license-cache.json");
3425
3462
  DEVICE_ID_PATH = path10.join(EXE_AI_DIR, "device-id");
3463
+ API_BASE = process.env.EXE_CLOUD_ENDPOINT ?? "https://askexe.com/cloud";
3426
3464
  }
3427
3465
  });
3428
3466
 
@@ -3466,6 +3504,9 @@ import { fileURLToPath as fileURLToPath2 } from "url";
3466
3504
  function getMySession() {
3467
3505
  return getTransport().getMySession();
3468
3506
  }
3507
+ function isRootSession(name) {
3508
+ return name.length > 0 && !name.includes("-");
3509
+ }
3469
3510
  function extractRootExe(name) {
3470
3511
  if (!name) return null;
3471
3512
  if (!name.includes("-")) return name;
@@ -3484,6 +3525,7 @@ function resolveExeSession() {
3484
3525
  const mySession = getMySession();
3485
3526
  if (!mySession) return null;
3486
3527
  const fromSessionName = extractRootExe(mySession);
3528
+ let candidate = null;
3487
3529
  try {
3488
3530
  const key = getSessionKey();
3489
3531
  const parentExe = getParentExe(key);
@@ -3494,13 +3536,47 @@ function resolveExeSession() {
3494
3536
  `[tmux-routing] WARN: cache says "${fromCache}" but session name says "${fromSessionName}". Trusting session name.
3495
3537
  `
3496
3538
  );
3497
- return fromSessionName;
3539
+ candidate = fromSessionName;
3540
+ } else {
3541
+ candidate = fromCache;
3498
3542
  }
3499
- return fromCache;
3500
3543
  }
3501
3544
  } catch {
3502
3545
  }
3503
- return fromSessionName ?? mySession;
3546
+ if (!candidate) {
3547
+ candidate = fromSessionName ?? mySession;
3548
+ }
3549
+ if (candidate && isRootSession(candidate)) {
3550
+ try {
3551
+ const transport = getTransport();
3552
+ const liveSessions = transport.listSessions();
3553
+ if (!liveSessions.includes(candidate)) {
3554
+ const liveRoots = liveSessions.filter((s) => isRootSession(s));
3555
+ if (liveRoots.length === 1) {
3556
+ process.stderr.write(
3557
+ `[tmux-routing] WARN: resolved session "${candidate}" is dead. Using live coordinator "${liveRoots[0]}".
3558
+ `
3559
+ );
3560
+ return liveRoots[0];
3561
+ } else if (liveRoots.length > 1) {
3562
+ const base = candidate.replace(/\d+$/, "");
3563
+ const match = liveRoots.find((s) => s.startsWith(base));
3564
+ const chosen = match ?? liveRoots[0];
3565
+ process.stderr.write(
3566
+ `[tmux-routing] WARN: resolved session "${candidate}" is dead. ${liveRoots.length} live roots found, using "${chosen}".
3567
+ `
3568
+ );
3569
+ return chosen;
3570
+ }
3571
+ process.stderr.write(
3572
+ `[tmux-routing] WARN: resolved session "${candidate}" is dead and no live coordinator found.
3573
+ `
3574
+ );
3575
+ }
3576
+ } catch {
3577
+ }
3578
+ }
3579
+ return candidate;
3504
3580
  }
3505
3581
  var SPAWN_LOCK_DIR, SESSION_CACHE, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS;
3506
3582
  var init_tmux_routing = __esm({
@@ -3550,17 +3626,157 @@ var init_task_scope = __esm({
3550
3626
  }
3551
3627
  });
3552
3628
 
3629
+ // src/lib/identity.ts
3630
+ var identity_exports = {};
3631
+ __export(identity_exports, {
3632
+ getIdentity: () => getIdentity,
3633
+ getIdentityInjection: () => getIdentityInjection,
3634
+ identityPath: () => identityPath,
3635
+ listIdentities: () => listIdentities,
3636
+ updateIdentity: () => updateIdentity
3637
+ });
3638
+ import { existsSync as existsSync12, mkdirSync as mkdirSync7, readFileSync as readFileSync11, writeFileSync as writeFileSync8 } from "fs";
3639
+ import { readdirSync as readdirSync3 } from "fs";
3640
+ import path14 from "path";
3641
+ import { createHash } from "crypto";
3642
+ function ensureDir() {
3643
+ if (!existsSync12(IDENTITY_DIR2)) {
3644
+ mkdirSync7(IDENTITY_DIR2, { recursive: true });
3645
+ }
3646
+ }
3647
+ function identityPath(agentId) {
3648
+ return path14.join(IDENTITY_DIR2, `${agentId}.md`);
3649
+ }
3650
+ function sanitizeIdentityBody(body) {
3651
+ return body.replace(/<!--[\s\S]*?-->/g, "").trim();
3652
+ }
3653
+ function parseFrontmatter(raw) {
3654
+ const match = raw.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
3655
+ if (!match) {
3656
+ return {
3657
+ frontmatter: {
3658
+ role: "unknown",
3659
+ title: "Unknown",
3660
+ agent_id: "unknown",
3661
+ org_level: "specialist",
3662
+ created_by: "system",
3663
+ updated_at: (/* @__PURE__ */ new Date()).toISOString()
3664
+ },
3665
+ body: sanitizeIdentityBody(raw)
3666
+ };
3667
+ }
3668
+ const yamlStr = match[1];
3669
+ const body = sanitizeIdentityBody(match[2]);
3670
+ const fm = {};
3671
+ for (const line of yamlStr.split("\n")) {
3672
+ const kv = line.match(/^(\w+):\s*(.+)$/);
3673
+ if (kv) fm[kv[1]] = kv[2].trim();
3674
+ }
3675
+ return {
3676
+ frontmatter: {
3677
+ role: fm.role ?? "unknown",
3678
+ title: fm.title ?? "Unknown",
3679
+ agent_id: fm.agent_id ?? "unknown",
3680
+ org_level: fm.org_level ?? "specialist",
3681
+ created_by: fm.created_by ?? "system",
3682
+ updated_at: fm.updated_at ?? (/* @__PURE__ */ new Date()).toISOString()
3683
+ },
3684
+ body
3685
+ };
3686
+ }
3687
+ function contentHash(content) {
3688
+ return createHash("sha256").update(content).digest("hex").slice(0, 16);
3689
+ }
3690
+ function getIdentity(agentId) {
3691
+ const filePath = identityPath(agentId);
3692
+ if (!existsSync12(filePath)) return null;
3693
+ const raw = readFileSync11(filePath, "utf-8");
3694
+ const { frontmatter, body } = parseFrontmatter(raw);
3695
+ return {
3696
+ agentId,
3697
+ frontmatter,
3698
+ body,
3699
+ raw,
3700
+ contentHash: contentHash(raw)
3701
+ };
3702
+ }
3703
+ async function updateIdentity(agentId, content, updatedBy) {
3704
+ ensureDir();
3705
+ const filePath = identityPath(agentId);
3706
+ const hash = contentHash(content);
3707
+ writeFileSync8(filePath, content, "utf-8");
3708
+ try {
3709
+ const client = getClient();
3710
+ await client.execute({
3711
+ sql: `INSERT INTO identity (agent_id, content_hash, updated_at, updated_by)
3712
+ VALUES (?, ?, ?, ?)
3713
+ ON CONFLICT(agent_id) DO UPDATE SET
3714
+ content_hash = excluded.content_hash,
3715
+ updated_at = excluded.updated_at,
3716
+ updated_by = excluded.updated_by`,
3717
+ args: [agentId, hash, (/* @__PURE__ */ new Date()).toISOString(), updatedBy]
3718
+ });
3719
+ } catch {
3720
+ }
3721
+ }
3722
+ function listIdentities() {
3723
+ ensureDir();
3724
+ const files = readdirSync3(IDENTITY_DIR2).filter((f) => f.endsWith(".md"));
3725
+ const results = [];
3726
+ for (const file of files) {
3727
+ const agentId = file.replace(".md", "");
3728
+ const identity = getIdentity(agentId);
3729
+ if (!identity) continue;
3730
+ const lines = identity.body.split("\n").filter((l) => l.trim() && !l.startsWith("#"));
3731
+ const summary = lines[0]?.trim().slice(0, 120) ?? identity.frontmatter.title;
3732
+ results.push({
3733
+ agentId,
3734
+ // User-facing/team-facing title only. `frontmatter.role` is internal
3735
+ // routing metadata and must not leak as an external title.
3736
+ title: identity.frontmatter.title,
3737
+ summary
3738
+ });
3739
+ }
3740
+ return results;
3741
+ }
3742
+ function getIdentityInjection(agentId) {
3743
+ const own = getIdentity(agentId);
3744
+ const all = listIdentities();
3745
+ const parts = [];
3746
+ if (own) {
3747
+ parts.push(`## Your Identity (exe.md)
3748
+ These define WHO YOU ARE. Non-negotiable. Permanent.
3749
+
3750
+ ${own.body}`);
3751
+ }
3752
+ const teamLines = all.filter((a) => a.agentId !== agentId).map((a) => `- ${a.agentId} (${a.title}): ${a.summary}`);
3753
+ if (teamLines.length > 0) {
3754
+ parts.push(`## Team Identities
3755
+ ${teamLines.join("\n")}`);
3756
+ }
3757
+ return parts.join("\n\n");
3758
+ }
3759
+ var IDENTITY_DIR2;
3760
+ var init_identity = __esm({
3761
+ "src/lib/identity.ts"() {
3762
+ "use strict";
3763
+ init_config();
3764
+ init_database();
3765
+ IDENTITY_DIR2 = path14.join(EXE_AI_DIR, "identity");
3766
+ }
3767
+ });
3768
+
3553
3769
  // src/lib/keychain.ts
3554
3770
  import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
3555
- import { existsSync as existsSync12, statSync as statSync2 } from "fs";
3771
+ import { existsSync as existsSync13, statSync as statSync2 } from "fs";
3556
3772
  import { execSync as execSync6 } from "child_process";
3557
- import path14 from "path";
3773
+ import path15 from "path";
3558
3774
  import os10 from "os";
3559
3775
  function getKeyDir() {
3560
- return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path14.join(os10.homedir(), ".exe-os");
3776
+ return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path15.join(os10.homedir(), ".exe-os");
3561
3777
  }
3562
3778
  function getKeyPath() {
3563
- return path14.join(getKeyDir(), "master.key");
3779
+ return path15.join(getKeyDir(), "master.key");
3564
3780
  }
3565
3781
  function nativeKeychainAllowed() {
3566
3782
  return process.env.EXE_OS_DISABLE_NATIVE_KEYCHAIN !== "1";
@@ -3591,7 +3807,7 @@ function isRootOnlyTrustedServerKeyFile(keyPath) {
3591
3807
  if (!st.isFile() || (st.mode & 63) !== 0) return false;
3592
3808
  if (uid === 0) return true;
3593
3809
  const exeOsDir = process.env.EXE_OS_DIR;
3594
- return Boolean(exeOsDir && path14.resolve(keyPath).startsWith(path14.resolve(exeOsDir) + path14.sep));
3810
+ return Boolean(exeOsDir && path15.resolve(keyPath).startsWith(path15.resolve(exeOsDir) + path15.sep));
3595
3811
  } catch {
3596
3812
  return false;
3597
3813
  }
@@ -3703,8 +3919,8 @@ function deriveMachineKey() {
3703
3919
  }
3704
3920
  function readMachineId() {
3705
3921
  try {
3706
- const { readFileSync: readFileSync11 } = __require("fs");
3707
- return readFileSync11("/etc/machine-id", "utf-8").trim();
3922
+ const { readFileSync: readFileSync12 } = __require("fs");
3923
+ return readFileSync12("/etc/machine-id", "utf-8").trim();
3708
3924
  } catch {
3709
3925
  return "";
3710
3926
  }
@@ -3788,7 +4004,7 @@ async function getMasterKey() {
3788
4004
  }
3789
4005
  }
3790
4006
  const keyPath = getKeyPath();
3791
- if (!existsSync12(keyPath)) {
4007
+ if (!existsSync13(keyPath)) {
3792
4008
  process.stderr.write(
3793
4009
  `[keychain] Key not found at ${keyPath} (HOME=${os10.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
3794
4010
  `
@@ -3914,7 +4130,7 @@ var init_state_bus = __esm({
3914
4130
  });
3915
4131
 
3916
4132
  // src/lib/memory-write-governor.ts
3917
- import { createHash } from "crypto";
4133
+ import { createHash as createHash2 } from "crypto";
3918
4134
  function normalizeMemoryText(text) {
3919
4135
  return text.replace(/\r\n/g, "\n").replace(/[ \t]+$/gm, "").replace(/\n{4,}/g, "\n\n\n").trim();
3920
4136
  }
@@ -3949,7 +4165,7 @@ function shouldSkipEmbedding(input2) {
3949
4165
  return false;
3950
4166
  }
3951
4167
  function hashMemoryContent(text) {
3952
- return createHash("sha256").update(normalizeMemoryText(text)).digest("hex");
4168
+ return createHash2("sha256").update(normalizeMemoryText(text)).digest("hex");
3953
4169
  }
3954
4170
  function scopedDedupArgs(input2) {
3955
4171
  return [input2.contentHash, input2.agentId, input2.projectName, input2.memoryType];
@@ -4021,10 +4237,10 @@ async function runPostWriteMemoryHygiene(memoryId) {
4021
4237
  const row = current.rows[0];
4022
4238
  if (!row) return;
4023
4239
  const memoryType = String(row.memory_type ?? "raw");
4024
- const contentHash = row.content_hash ? String(row.content_hash) : null;
4240
+ const contentHash2 = row.content_hash ? String(row.content_hash) : null;
4025
4241
  const agentId = String(row.agent_id);
4026
4242
  const projectName = String(row.project_name);
4027
- if (contentHash) {
4243
+ if (contentHash2) {
4028
4244
  await client.execute({
4029
4245
  sql: `UPDATE memories
4030
4246
  SET status = 'deleted',
@@ -4035,7 +4251,7 @@ async function runPostWriteMemoryHygiene(memoryId) {
4035
4251
  AND project_name = ?
4036
4252
  AND COALESCE(memory_type, 'raw') = ?
4037
4253
  AND COALESCE(status, 'active') = 'active'`,
4038
- args: [memoryId, contentHash, agentId, projectName, memoryType]
4254
+ args: [memoryId, contentHash2, agentId, projectName, memoryType]
4039
4255
  });
4040
4256
  }
4041
4257
  const supersedesId = row.supersedes_id ? String(row.supersedes_id) : null;
@@ -4122,13 +4338,13 @@ __export(shard_manager_exports, {
4122
4338
  listShards: () => listShards,
4123
4339
  shardExists: () => shardExists
4124
4340
  });
4125
- import path15 from "path";
4126
- import { existsSync as existsSync13, mkdirSync as mkdirSync7, readdirSync as readdirSync3, renameSync as renameSync4, statSync as statSync3 } from "fs";
4341
+ import path16 from "path";
4342
+ import { existsSync as existsSync14, mkdirSync as mkdirSync8, readdirSync as readdirSync4, renameSync as renameSync4, statSync as statSync3 } from "fs";
4127
4343
  import { createClient as createClient2 } from "@libsql/client";
4128
4344
  function initShardManager(encryptionKey) {
4129
4345
  _encryptionKey = encryptionKey;
4130
- if (!existsSync13(SHARDS_DIR)) {
4131
- mkdirSync7(SHARDS_DIR, { recursive: true });
4346
+ if (!existsSync14(SHARDS_DIR)) {
4347
+ mkdirSync8(SHARDS_DIR, { recursive: true });
4132
4348
  }
4133
4349
  _shardingEnabled = true;
4134
4350
  if (_evictionTimer) clearInterval(_evictionTimer);
@@ -4157,7 +4373,7 @@ function getShardClient(projectName) {
4157
4373
  while (_shards.size >= MAX_OPEN_SHARDS) {
4158
4374
  evictLRU();
4159
4375
  }
4160
- const dbPath = path15.join(SHARDS_DIR, `${safeName}.db`);
4376
+ const dbPath = path16.join(SHARDS_DIR, `${safeName}.db`);
4161
4377
  const client = createClient2({
4162
4378
  url: `file:${dbPath}`,
4163
4379
  encryptionKey: _encryptionKey
@@ -4168,14 +4384,14 @@ function getShardClient(projectName) {
4168
4384
  }
4169
4385
  function shardExists(projectName) {
4170
4386
  const safeName = safeShardName(projectName);
4171
- return existsSync13(path15.join(SHARDS_DIR, `${safeName}.db`));
4387
+ return existsSync14(path16.join(SHARDS_DIR, `${safeName}.db`));
4172
4388
  }
4173
4389
  function safeShardName(projectName) {
4174
4390
  return projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
4175
4391
  }
4176
4392
  function listShards() {
4177
- if (!existsSync13(SHARDS_DIR)) return [];
4178
- return readdirSync3(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
4393
+ if (!existsSync14(SHARDS_DIR)) return [];
4394
+ return readdirSync4(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
4179
4395
  }
4180
4396
  async function auditShardHealth(options = {}) {
4181
4397
  if (!_encryptionKey) {
@@ -4186,7 +4402,7 @@ async function auditShardHealth(options = {}) {
4186
4402
  const names = listShards();
4187
4403
  const shards = [];
4188
4404
  for (const name of names) {
4189
- const dbPath = path15.join(SHARDS_DIR, `${name}.db`);
4405
+ const dbPath = path16.join(SHARDS_DIR, `${name}.db`);
4190
4406
  const stat = statSync3(dbPath);
4191
4407
  const item = {
4192
4408
  name,
@@ -4221,7 +4437,7 @@ async function auditShardHealth(options = {}) {
4221
4437
  _shards.delete(name);
4222
4438
  _shardLastAccess.delete(name);
4223
4439
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
4224
- const archivedPath = path15.join(SHARDS_DIR, `${name}.db.broken-${stamp}`);
4440
+ const archivedPath = path16.join(SHARDS_DIR, `${name}.db.broken-${stamp}`);
4225
4441
  renameSync4(dbPath, archivedPath);
4226
4442
  item.archivedPath = archivedPath;
4227
4443
  }
@@ -4438,11 +4654,11 @@ async function getReadyShardClient(projectName) {
4438
4654
  client.close();
4439
4655
  _shards.delete(safeName);
4440
4656
  _shardLastAccess.delete(safeName);
4441
- const dbPath = path15.join(SHARDS_DIR, `${safeName}.db`);
4442
- if (existsSync13(dbPath)) {
4657
+ const dbPath = path16.join(SHARDS_DIR, `${safeName}.db`);
4658
+ if (existsSync14(dbPath)) {
4443
4659
  const stat = statSync3(dbPath);
4444
4660
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
4445
- const archivedPath = path15.join(SHARDS_DIR, `${safeName}.db.broken-${stamp}`);
4661
+ const archivedPath = path16.join(SHARDS_DIR, `${safeName}.db.broken-${stamp}`);
4446
4662
  renameSync4(dbPath, archivedPath);
4447
4663
  process.stderr.write(
4448
4664
  `[shard-manager] Archived unreadable shard ${safeName}: ${archivedPath} (${stat.size} bytes, mtime ${stat.mtime.toISOString()})
@@ -4510,7 +4726,7 @@ var init_shard_manager = __esm({
4510
4726
  "src/lib/shard-manager.ts"() {
4511
4727
  "use strict";
4512
4728
  init_config();
4513
- SHARDS_DIR = path15.join(EXE_AI_DIR, "shards");
4729
+ SHARDS_DIR = path16.join(EXE_AI_DIR, "shards");
4514
4730
  SHARD_IDLE_MS = 5 * 60 * 1e3;
4515
4731
  MAX_OPEN_SHARDS = 10;
4516
4732
  EVICTION_INTERVAL_MS = 60 * 1e3;
@@ -4636,6 +4852,20 @@ var init_platform_procedures = __esm({
4636
4852
  priority: "p1",
4637
4853
  content: "Once per session (COO boot only, never repeat), call list_my_feature_requests to check if any previously filed feature requests have been shipped by AskExe. If any request has status 'shipped' with a shipped_version, surface it to the founder immediately: '\u{1F680} N feature(s) shipped \u2014 run exe-os update to get version X.Y.Z'. This is a one-time check at boot, not a recurring poll. If no requests exist or none are shipped, skip silently. If the MCP tool is unavailable or the network call fails, skip silently \u2014 this is informational, not blocking."
4638
4854
  },
4855
+ // --- Tool guidance ---
4856
+ {
4857
+ title: "How to use company_actions \u2014 execute business actions through gateway connectors",
4858
+ domain: "tools",
4859
+ priority: "p2",
4860
+ content: "The company_actions tool executes business actions through gateway connectors (e.g. send WhatsApp, trigger workflows, update CRM). It routes through the exe-gateway on the VPS. Actions are defined by the customer's gateway configuration \u2014 each connector (WhatsApp, Shopify, email, etc.) exposes specific actions. Use query_company_brain to find available data first, then company_actions to act on it. Requires gateway auth token. Read-only founders should NOT use this \u2014 it mutates external state."
4861
+ },
4862
+ // --- Release awareness ---
4863
+ {
4864
+ title: "What's New check \u2014 surface new features after update",
4865
+ domain: "support",
4866
+ priority: "p1",
4867
+ content: "Once per session (COO boot only, never repeat), check if the installed exe-os version is newer than the last session. If it is, read the bundled release-notes.json (at the package root) and surface a brief summary to the founder: 'Updated to exe-os vX.Y.Z \u2014 N new features, M fixes.' List the top 3 features by name. This helps the founder know what they got from the update. If release-notes.json doesn't exist or the version hasn't changed, skip silently. Never repeat this check in the same session."
4868
+ },
4639
4869
  // --- Platform vs Customer ownership ---
4640
4870
  {
4641
4871
  title: "What the platform provides vs what you customize",
@@ -4724,13 +4954,13 @@ var init_platform_procedures = __esm({
4724
4954
  title: "MCP tools \u2014 memory, decision, and search",
4725
4955
  domain: "tool-use",
4726
4956
  priority: "p1",
4727
- content: `memory(action="recall") / recall_my_memory: search your own memories (semantic + FTS). memory(action="ask_team") / ask_team_memory: search a colleague's memories by agent name. memory(action="store") / store_memory: persist a memory. memory(action="commit") / commit_memory: high-importance memory that survives consolidation. Requires summary. memory(action="search") / search_everything: unified search across memories, tasks, entities, conversations. memory(action="session_context") / get_session_context: temporal memory window. Requires session_id + target_timestamp. memory(action="consolidate") / consolidate_memories: merge duplicate/related memories. memory(action="cardinality") / get_memory_cardinality: count memories per agent. decision(action="store") / store_decision: record an architectural decision (domain, decision, rationale). decision(action="get") / get_decision: retrieve a past decision by domain or query.`
4957
+ content: `memory(action="recall") / recall_my_memory: search your own memories (semantic + FTS). Supports as_of param for bi-temporal queries (what did I know at time X?), kind param to filter by memory type (decision, procedure, observation, raw, conversation, behavior). memory(action="ask_team") / ask_team_memory: search a colleague's memories by agent name. memory(action="store") / store_memory: persist a memory. Supports kind param and procedure_for domain tag for procedure-type memories. memory(action="commit") / commit_memory: high-importance memory that survives consolidation. Requires summary. memory(action="search") / search_everything: unified search across memories, tasks, entities, conversations. memory(action="session_context") / get_session_context: temporal memory window. Requires session_id + target_timestamp. memory(action="consolidate") / consolidate_memories: merge duplicate/related memories. memory(action="cardinality") / get_memory_cardinality: count memories per agent. memory(action="supersede") / supersede: replace an old memory with a new version (old_id + new text). decision(action="store") / store_decision: record an architectural decision (domain, decision, rationale). decision(action="get") / get_decision: retrieve a past decision by domain or query.`
4728
4958
  },
4729
4959
  {
4730
4960
  title: "MCP tools \u2014 task orchestration",
4731
4961
  domain: "tool-use",
4732
4962
  priority: "p1",
4733
- content: 'task(action="create") / create_task: dispatch work (title, assigned_to, context). The ONLY dispatch path. Auto-spawns session. task(action="list") / list_tasks: query tasks by status, assignee, project. task(action="get") / get_task: fetch full task details by task_id. task(action="update") / update_task: change status (in_progress, done, blocked, cancelled) + result summary. task(action="close") / close_task: finalize a reviewed task (COO only). task(action="checkpoint") / checkpoint_task: save progress state for crash recovery. task(action="resume") / resume_employee: re-spawn an employee session for an existing task.'
4963
+ content: `task(action="create") / create_task: dispatch work (title, assigned_to, context). The ONLY dispatch path. Auto-spawns session. Supports spawn_runtime and spawn_model params to override the agent's default runtime/model for a specific task. task(action="list") / list_tasks: query tasks by status, assignee, project. task(action="get") / get_task: fetch full task details by task_id. task(action="update") / update_task: change status (in_progress, done, blocked, cancelled) + result summary. task(action="close") / close_task: finalize a reviewed task (COO only). task(action="checkpoint") / checkpoint_task: save progress state for crash recovery. task(action="resume") / resume_employee: re-spawn an employee session for an existing task.`
4734
4964
  },
4735
4965
  {
4736
4966
  title: "MCP tools \u2014 knowledge graph (GraphRAG)",
@@ -4760,7 +4990,7 @@ var init_platform_procedures = __esm({
4760
4990
  title: "MCP tools \u2014 admin, config, and operations",
4761
4991
  domain: "tool-use",
4762
4992
  priority: "p1",
4763
- content: 'config(action="list_employees"): view roster. config(action="agent_spend"): token usage per agent. config(action="daemon_health"): check exed status. config(action="license_status"): check license. config(action="cloud_sync"): force sync. config(action="memory_audit"): health check (dupes, null vectors). config(action="run_consolidation"): trigger memory consolidation. config(action="company_procedure", subaction="store|list|deactivate"): manage company procedures. config(action="global_procedure"): list all procedures (platform + company). config(action="create_trigger|list_triggers"): scheduled agent jobs. config(action="export_orchestration|import_orchestration"): portable org state. diagnostics(action="healthcheck|doctor|status_brief|check_update|cloud_status"): system diagnostics. mcp_ping(): daemon health + license status + tool usage stats.'
4993
+ content: 'config(action="list_employees"): view roster. config(action="agent_spend"): token usage per agent. config(action="daemon_health"): check exed status. config(action="license_status"): check license. config(action="cloud_sync"): force sync. config(action="memory_audit"): health check (dupes, null vectors). config(action="run_consolidation"): trigger memory consolidation. config(action="company_procedure", subaction="store|list|deactivate"): manage company procedures. config(action="global_procedure"): list all procedures (platform + company). config(action="create_trigger|list_triggers"): scheduled agent jobs. config(action="export_orchestration|import_orchestration"): portable org state. diagnostics(action="healthcheck|doctor|status_brief|check_update|cloud_status"): system diagnostics. diagnostics(action="tool_search"): semantic tool discovery \u2014 find relevant MCP tools by natural language query. Returns top-K tools ranked by relevance. diagnostics(action="drift"): identity drift detection \u2014 score how far an agent has drifted from its role identity. Returns drift score + recommendations. mcp_ping(): daemon health + license status + tool usage stats.'
4764
4994
  }
4765
4995
  ];
4766
4996
  PLATFORM_PROCEDURE_TITLES = new Set(
@@ -4847,9 +5077,9 @@ __export(memory_cards_exports, {
4847
5077
  insertMemoryCardsForBatch: () => insertMemoryCardsForBatch,
4848
5078
  searchMemoryCards: () => searchMemoryCards
4849
5079
  });
4850
- import { createHash as createHash2 } from "crypto";
5080
+ import { createHash as createHash3 } from "crypto";
4851
5081
  function stableId(memoryId, type, content) {
4852
- return createHash2("sha256").update(`${memoryId}:${type}:${content}`).digest("hex").slice(0, 32);
5082
+ return createHash3("sha256").update(`${memoryId}:${type}:${content}`).digest("hex").slice(0, 32);
4853
5083
  }
4854
5084
  function cleanText(text) {
4855
5085
  return text.replace(/```[\s\S]*?```/g, " ").replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
@@ -5012,9 +5242,9 @@ __export(agentic_ontology_exports, {
5012
5242
  ontologyPayload: () => ontologyPayload,
5013
5243
  stableId: () => stableId2
5014
5244
  });
5015
- import { createHash as createHash3 } from "crypto";
5245
+ import { createHash as createHash4 } from "crypto";
5016
5246
  function stableId2(...parts) {
5017
- return createHash3("sha256").update(parts.map((p) => String(p ?? "")).join("::")).digest("hex").slice(0, 32);
5247
+ return createHash4("sha256").update(parts.map((p) => String(p ?? "")).join("::")).digest("hex").slice(0, 32);
5018
5248
  }
5019
5249
  function clean(text, max = 240) {
5020
5250
  return text.replace(/\u0000/g, "").replace(/```[\s\S]*?```/g, " ").replace(/\s+/g, " ").trim().slice(0, max);
@@ -5400,16 +5630,16 @@ async function writeMemory(record) {
5400
5630
  const governed = governMemoryRecord(record);
5401
5631
  if (governed.shouldDrop) return;
5402
5632
  record = governed.record;
5403
- const contentHash = governed.contentHash;
5633
+ const contentHash2 = governed.contentHash;
5404
5634
  const memoryType = record.memory_type ?? "raw";
5405
5635
  if (_pendingRecords.some(
5406
- (r) => r.content_hash === contentHash && r.agent_id === record.agent_id && r.project_name === record.project_name && (r.memory_type ?? "raw") === memoryType
5636
+ (r) => r.content_hash === contentHash2 && r.agent_id === record.agent_id && r.project_name === record.project_name && (r.memory_type ?? "raw") === memoryType
5407
5637
  )) {
5408
5638
  return;
5409
5639
  }
5410
5640
  try {
5411
5641
  const existing = await findScopedDuplicate({
5412
- contentHash,
5642
+ contentHash: contentHash2,
5413
5643
  agentId: record.agent_id,
5414
5644
  projectName: record.project_name,
5415
5645
  memoryType
@@ -5444,10 +5674,12 @@ async function writeMemory(record) {
5444
5674
  source_type: record.source_type ?? null,
5445
5675
  tier: record.tier ?? classifyTier(record),
5446
5676
  supersedes_id: record.supersedes_id ?? null,
5677
+ valid_from: record.valid_from ?? record.timestamp,
5678
+ invalid_at: record.invalid_at ?? null,
5447
5679
  draft: record.draft ? 1 : 0,
5448
5680
  memory_type: memoryType,
5449
5681
  trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null,
5450
- content_hash: contentHash,
5682
+ content_hash: contentHash2,
5451
5683
  intent: record.intent ?? null,
5452
5684
  outcome: record.outcome ?? null,
5453
5685
  domain: record.domain ?? inferDomain(record),
@@ -5462,7 +5694,8 @@ async function writeMemory(record) {
5462
5694
  token_cost: record.token_cost ?? null,
5463
5695
  audience: record.audience ?? null,
5464
5696
  language_type: record.language_type ?? inferLanguageType(record),
5465
- parent_memory_id: record.parent_memory_id ?? null
5697
+ parent_memory_id: record.parent_memory_id ?? null,
5698
+ procedure_for: record.procedure_for ?? null
5466
5699
  };
5467
5700
  _pendingRecords.push(dbRow);
5468
5701
  orgBus.emit({
@@ -5517,10 +5750,12 @@ async function flushBatch() {
5517
5750
  const sourceType = row.source_type ?? null;
5518
5751
  const tier = row.tier ?? 3;
5519
5752
  const supersedesId = row.supersedes_id ?? null;
5753
+ const validFrom = row.valid_from ?? row.timestamp;
5754
+ const invalidAt = row.invalid_at ?? null;
5520
5755
  const draft = row.draft ? 1 : 0;
5521
5756
  const memoryType = row.memory_type ?? "raw";
5522
5757
  const trajectory = row.trajectory ?? null;
5523
- const contentHash = row.content_hash ?? null;
5758
+ const contentHash2 = row.content_hash ?? null;
5524
5759
  const intent = row.intent ?? null;
5525
5760
  const outcome = row.outcome ?? null;
5526
5761
  const domain = row.domain ?? null;
@@ -5536,15 +5771,16 @@ async function flushBatch() {
5536
5771
  const audience = row.audience ?? null;
5537
5772
  const languageType = row.language_type ?? null;
5538
5773
  const parentMemoryId = row.parent_memory_id ?? null;
5774
+ const procedureFor = row.procedure_for ?? null;
5539
5775
  const cols = `id, agent_id, agent_role, session_id, timestamp,
5540
5776
  tool_name, project_name,
5541
5777
  has_error, raw_text, vector, version, task_id, importance, status,
5542
5778
  confidence, last_accessed,
5543
5779
  workspace_id, document_id, user_id, char_offset, page_number,
5544
- source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory, content_hash,
5780
+ source_path, source_type, tier, supersedes_id, valid_from, invalid_at, draft, memory_type, trajectory, content_hash,
5545
5781
  intent, outcome, domain, referenced_entities, retrieval_count,
5546
5782
  chain_position, review_status, context_window_pct, file_paths, commit_hash,
5547
- duration_ms, token_cost, audience, language_type, parent_memory_id`;
5783
+ duration_ms, token_cost, audience, language_type, parent_memory_id, procedure_for`;
5548
5784
  const metaArgs = [
5549
5785
  intent,
5550
5786
  outcome,
@@ -5560,7 +5796,8 @@ async function flushBatch() {
5560
5796
  tokenCost,
5561
5797
  audience,
5562
5798
  languageType,
5563
- parentMemoryId
5799
+ parentMemoryId,
5800
+ procedureFor
5564
5801
  ];
5565
5802
  const baseArgs = [
5566
5803
  row.id,
@@ -5589,15 +5826,17 @@ async function flushBatch() {
5589
5826
  sourceType,
5590
5827
  tier,
5591
5828
  supersedesId,
5829
+ validFrom,
5830
+ invalidAt,
5592
5831
  draft,
5593
5832
  memoryType,
5594
5833
  trajectory,
5595
- contentHash
5834
+ contentHash2
5596
5835
  ];
5597
5836
  return {
5598
5837
  sql: hasVector ? `INSERT OR IGNORE INTO memories (${cols})
5599
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories (${cols})
5600
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
5838
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories (${cols})
5839
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
5601
5840
  args: hasVector ? [...baseArgs, vectorToBlob(row.vector), ...sharedArgs, ...metaArgs] : [...baseArgs, ...sharedArgs, ...metaArgs]
5602
5841
  };
5603
5842
  };
@@ -5713,6 +5952,12 @@ async function searchMemories(queryVector, agentId, options) {
5713
5952
  AND vector IS NOT NULL${statusFilter}${draftFilter}
5714
5953
  AND COALESCE(confidence, 0.7) >= 0.3`;
5715
5954
  const args = [agentId];
5955
+ if (options?.asOf) {
5956
+ sql += ` AND (valid_from IS NULL OR valid_from <= ?) AND (invalid_at IS NULL OR invalid_at > ?)`;
5957
+ args.push(options.asOf, options.asOf);
5958
+ } else {
5959
+ sql += ` AND invalid_at IS NULL`;
5960
+ }
5716
5961
  const scope = buildWikiScopeFilter(options, "");
5717
5962
  sql += scope.clause;
5718
5963
  args.push(...scope.args);
@@ -6090,6 +6335,21 @@ process.stdin.on("end", async () => {
6090
6335
  JSON.parse(input);
6091
6336
  const agent = getActiveAgent();
6092
6337
  const sections = [];
6338
+ if (agent.agentId !== "default") {
6339
+ try {
6340
+ const { getIdentityInjection: getIdentityInjection2 } = await Promise.resolve().then(() => (init_identity(), identity_exports));
6341
+ const identity = getIdentityInjection2(agent.agentId);
6342
+ if (identity) {
6343
+ sections.push(`## Role Re-Injection (post-compaction)
6344
+ You are **${agent.agentId}** (${agent.agentRole}). Your identity and responsibilities:
6345
+
6346
+ ${identity}`);
6347
+ process.stderr.write(`[post-compact] Re-injecting identity for ${agent.agentId}
6348
+ `);
6349
+ }
6350
+ } catch {
6351
+ }
6352
+ }
6093
6353
  try {
6094
6354
  const { fastDbInit: fastDbInit2 } = await Promise.resolve().then(() => (init_fast_db_init(), fast_db_init_exports));
6095
6355
  await fastDbInit2();