@askexenow/exe-os 0.9.19 → 0.9.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE CHANGED
@@ -1,21 +1,30 @@
1
- MIT License
1
+ Proprietary License
2
2
 
3
- Copyright (c) 2026 AskExe
3
+ Copyright (c) 2026 Ask Exe Now Pty Ltd. All rights reserved.
4
4
 
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
5
+ This software and associated documentation files (the "Software") are the
6
+ exclusive property of Ask Exe Now Pty Ltd ("AskExe"). The Software is
7
+ licensed, not sold.
11
8
 
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
9
+ RESTRICTIONS
10
+
11
+ You may not, without the prior written consent of AskExe:
12
+
13
+ 1. Copy, modify, merge, or create derivative works of the Software;
14
+ 2. Distribute, sublicense, sell, lease, or transfer the Software;
15
+ 3. Reverse engineer, decompile, or disassemble the Software;
16
+ 4. Remove or alter any proprietary notices or labels on the Software.
17
+
18
+ PERMITTED USE
19
+
20
+ Use of the Software is permitted only under a valid license agreement
21
+ with AskExe. See https://askexe.com/license for terms.
22
+
23
+ DISCLAIMER
14
24
 
15
25
  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
26
  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
27
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
28
+ ASKEXE BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
29
+ ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
30
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/dist/bin/cli.js CHANGED
@@ -917,18 +917,21 @@ async function registerMcpServer(packageRoot, homeDir = os5.homedir()) {
917
917
  args: [path6.join(packageRoot, "dist", "mcp", "server.js")],
918
918
  env: {}
919
919
  };
920
- const currentMem = claudeJson.mcpServers[MCP_LEGACY_KEY];
921
- const memMatches = currentMem && JSON.stringify(currentMem) === JSON.stringify(newEntry);
922
- if (claudeJson.mcpServers[MCP_PRIMARY_KEY]) {
923
- delete claudeJson.mcpServers[MCP_PRIMARY_KEY];
920
+ if (claudeJson.mcpServers[MCP_LEGACY_KEY]) {
921
+ delete claudeJson.mcpServers[MCP_LEGACY_KEY];
922
+ process.stderr.write("exe-os: migrated MCP server key exe-mem \u2192 exe-os\n");
924
923
  }
925
- if (memMatches && !claudeJson.mcpServers[MCP_PRIMARY_KEY]) {
924
+ const currentOs = claudeJson.mcpServers[MCP_PRIMARY_KEY];
925
+ const osMatches = currentOs && JSON.stringify(currentOs) === JSON.stringify(newEntry);
926
+ if (osMatches) {
926
927
  await cleanSettingsJsonMcp(path6.join(homeDir, ".claude", "settings.json"));
928
+ await migratePermissionsToExeOs(path6.join(homeDir, ".claude", "settings.json"));
927
929
  return false;
928
930
  }
929
- claudeJson.mcpServers[MCP_LEGACY_KEY] = newEntry;
931
+ claudeJson.mcpServers[MCP_PRIMARY_KEY] = newEntry;
930
932
  await writeFile3(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
931
933
  await cleanSettingsJsonMcp(path6.join(homeDir, ".claude", "settings.json"));
934
+ await migratePermissionsToExeOs(path6.join(homeDir, ".claude", "settings.json"));
932
935
  return true;
933
936
  }
934
937
  async function cleanSettingsJsonMcp(settingsPath) {
@@ -952,6 +955,35 @@ async function cleanSettingsJsonMcp(settingsPath) {
952
955
  } catch {
953
956
  }
954
957
  }
958
+ async function migratePermissionsToExeOs(settingsPath) {
959
+ if (!existsSync7(settingsPath)) return;
960
+ try {
961
+ const settings = JSON.parse(await readFile3(settingsPath, "utf-8"));
962
+ const permissions = settings.permissions;
963
+ if (!permissions || !Array.isArray(permissions.allow)) return;
964
+ const allow = permissions.allow;
965
+ let migrated = 0;
966
+ for (let i = 0; i < allow.length; i++) {
967
+ if (allow[i].startsWith("mcp__exe-mem__")) {
968
+ const newName = allow[i].replace("mcp__exe-mem__", "mcp__exe-os__");
969
+ if (!allow.includes(newName)) {
970
+ allow[i] = newName;
971
+ } else {
972
+ allow[i] = "__REMOVE__";
973
+ }
974
+ migrated++;
975
+ }
976
+ }
977
+ if (migrated > 0) {
978
+ permissions.allow = allow.filter((e) => e !== "__REMOVE__");
979
+ permissions.allow = [...new Set(permissions.allow)];
980
+ await writeFile3(settingsPath, JSON.stringify(settings, null, 2) + "\n");
981
+ process.stderr.write(`exe-os: migrated ${migrated} permission(s) from exe-mem \u2192 exe-os
982
+ `);
983
+ }
984
+ } catch {
985
+ }
986
+ }
955
987
  async function mergeHooks(packageRoot, homeDir = os5.homedir()) {
956
988
  const settingsPath = path6.join(homeDir, ".claude", "settings.json");
957
989
  const logsDir = path6.join(homeDir, ".exe-os", "logs");
@@ -1212,9 +1244,10 @@ async function mergeHooks(packageRoot, homeDir = os5.homedir()) {
1212
1244
  "deploy_client"
1213
1245
  ];
1214
1246
  const allowList = permissions.allow;
1215
- for (const tool of expandDualPrefixTools(toolNames)) {
1216
- if (!allowList.includes(tool)) {
1217
- allowList.push(tool);
1247
+ for (const name of toolNames) {
1248
+ const fullName = `mcp__${MCP_PRIMARY_KEY}__${name}`;
1249
+ if (!allowList.includes(fullName)) {
1250
+ allowList.push(fullName);
1218
1251
  }
1219
1252
  }
1220
1253
  await mkdir3(path6.dirname(settingsPath), { recursive: true });
@@ -12333,11 +12366,7 @@ function notifyCoordinatorTaskCompletion(coordinatorSession, agentName, taskTitl
12333
12366
  if (pending instanceof Promise) {
12334
12367
  pending.then((count) => {
12335
12368
  if (count > 0) {
12336
- execSync8(
12337
- `tmux send-keys -t ${JSON.stringify(coordinatorSession)} '/exe-intercom' Enter`,
12338
- { timeout: 3e3 }
12339
- );
12340
- logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName} completed "${taskTitle.slice(0, 50)}", ${count} reviews pending)`);
12369
+ logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName} completed "${taskTitle.slice(0, 50)}", ${count} reviews pending \u2014 hook will surface)`);
12341
12370
  }
12342
12371
  }).catch(() => {
12343
12372
  });
@@ -15623,6 +15652,9 @@ async function runUpdate(cliArgs) {
15623
15652
  console.log(" Try: npm cache clean --force && npm install -g @askexenow/exe-os@latest");
15624
15653
  }
15625
15654
  console.log(" Hooks re-wired, daemon restarted automatically.");
15655
+ console.log("");
15656
+ console.log(" \x1B[33m\u26A1 Run /mcp in each active Claude Code session to pick up new tools.\x1B[0m");
15657
+ console.log(" \x1B[2m(MCP servers can't hot-reload \u2014 Claude Code needs to reconnect them.)\x1B[0m");
15626
15658
  try {
15627
15659
  const { existsSync: exists, readFileSync: readFile8 } = await import("fs");
15628
15660
  const p = await import("path");
@@ -7551,11 +7551,7 @@ function notifyCoordinatorTaskCompletion(coordinatorSession, agentName, taskTitl
7551
7551
  if (pending instanceof Promise) {
7552
7552
  pending.then((count) => {
7553
7553
  if (count > 0) {
7554
- execSync7(
7555
- `tmux send-keys -t ${JSON.stringify(coordinatorSession)} '/exe-intercom' Enter`,
7556
- { timeout: 3e3 }
7557
- );
7558
- logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName} completed "${taskTitle.slice(0, 50)}", ${count} reviews pending)`);
7554
+ logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName} completed "${taskTitle.slice(0, 50)}", ${count} reviews pending \u2014 hook will surface)`);
7559
7555
  }
7560
7556
  }).catch(() => {
7561
7557
  });
@@ -6131,11 +6131,7 @@ function notifyCoordinatorTaskCompletion(coordinatorSession, agentName, taskTitl
6131
6131
  if (pending instanceof Promise) {
6132
6132
  pending.then((count) => {
6133
6133
  if (count > 0) {
6134
- execSync6(
6135
- `tmux send-keys -t ${JSON.stringify(coordinatorSession)} '/exe-intercom' Enter`,
6136
- { timeout: 3e3 }
6137
- );
6138
- logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName} completed "${taskTitle.slice(0, 50)}", ${count} reviews pending)`);
6134
+ logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName} completed "${taskTitle.slice(0, 50)}", ${count} reviews pending \u2014 hook will surface)`);
6139
6135
  }
6140
6136
  }).catch(() => {
6141
6137
  });
@@ -10968,11 +10968,7 @@ function notifyCoordinatorTaskCompletion(coordinatorSession, agentName, taskTitl
10968
10968
  if (pending instanceof Promise) {
10969
10969
  pending.then((count) => {
10970
10970
  if (count > 0) {
10971
- execSync6(
10972
- `tmux send-keys -t ${JSON.stringify(coordinatorSession)} '/exe-intercom' Enter`,
10973
- { timeout: 3e3 }
10974
- );
10975
- logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName} completed "${taskTitle.slice(0, 50)}", ${count} reviews pending)`);
10971
+ logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName} completed "${taskTitle.slice(0, 50)}", ${count} reviews pending \u2014 hook will surface)`);
10976
10972
  }
10977
10973
  }).catch(() => {
10978
10974
  });
@@ -12265,7 +12261,7 @@ var WebhookServer = class {
12265
12261
  sendJson(res, 200, {
12266
12262
  status: "ok",
12267
12263
  uptime,
12268
- handlers: [...this.handlers.keys()]
12264
+ adapterCount: this.handlers.size
12269
12265
  });
12270
12266
  }
12271
12267
  async handleQuery(req, res) {
@@ -12418,13 +12414,24 @@ function matchesChannel(msgChannel, matchChannel) {
12418
12414
  const channels = Array.isArray(matchChannel) ? matchChannel : [matchChannel];
12419
12415
  return channels.includes(msgChannel);
12420
12416
  }
12417
+ var MAX_REGEX_LENGTH = 200;
12418
+ function safeRegExp(pattern, flags) {
12419
+ if (pattern.length > MAX_REGEX_LENGTH) return null;
12420
+ try {
12421
+ return new RegExp(pattern, flags);
12422
+ } catch {
12423
+ return null;
12424
+ }
12425
+ }
12421
12426
  function matchesSender(msgSender, matchSender) {
12422
12427
  if (!matchSender) return true;
12423
- return new RegExp(matchSender).test(msgSender);
12428
+ const re = safeRegExp(matchSender);
12429
+ return re ? re.test(msgSender) : false;
12424
12430
  }
12425
12431
  function matchesTextPattern(msgText, matchPattern) {
12426
12432
  if (!matchPattern) return true;
12427
- return new RegExp(matchPattern, "i").test(msgText);
12433
+ const re = safeRegExp(matchPattern, "i");
12434
+ return re ? re.test(msgText) : false;
12428
12435
  }
12429
12436
  function matchesRoute(msg, match) {
12430
12437
  return matchesPlatform(msg.platform, match.platform) && matchesChannel(msg.channelId, match.channelId) && matchesSender(msg.senderId, match.senderId) && matchesTextPattern(msg.text, match.textPattern);
@@ -1217,15 +1217,6 @@ var init_agent_symlinks = __esm({
1217
1217
  });
1218
1218
 
1219
1219
  // src/lib/mcp-prefix.ts
1220
- function expandDualPrefixTools(shortNames) {
1221
- const out = [];
1222
- for (const name of shortNames) {
1223
- for (const prefix of MCP_TOOL_PREFIXES) {
1224
- out.push(prefix + name);
1225
- }
1226
- }
1227
- return out;
1228
- }
1229
1220
  var MCP_PRIMARY_KEY, MCP_LEGACY_KEY, MCP_TOOL_PREFIXES;
1230
1221
  var init_mcp_prefix = __esm({
1231
1222
  "src/lib/mcp-prefix.ts"() {
@@ -1368,18 +1359,21 @@ async function registerMcpServer(packageRoot, homeDir = os7.homedir()) {
1368
1359
  args: [path11.join(packageRoot, "dist", "mcp", "server.js")],
1369
1360
  env: {}
1370
1361
  };
1371
- const currentMem = claudeJson.mcpServers[MCP_LEGACY_KEY];
1372
- const memMatches = currentMem && JSON.stringify(currentMem) === JSON.stringify(newEntry);
1373
- if (claudeJson.mcpServers[MCP_PRIMARY_KEY]) {
1374
- delete claudeJson.mcpServers[MCP_PRIMARY_KEY];
1362
+ if (claudeJson.mcpServers[MCP_LEGACY_KEY]) {
1363
+ delete claudeJson.mcpServers[MCP_LEGACY_KEY];
1364
+ process.stderr.write("exe-os: migrated MCP server key exe-mem \u2192 exe-os\n");
1375
1365
  }
1376
- if (memMatches && !claudeJson.mcpServers[MCP_PRIMARY_KEY]) {
1366
+ const currentOs = claudeJson.mcpServers[MCP_PRIMARY_KEY];
1367
+ const osMatches = currentOs && JSON.stringify(currentOs) === JSON.stringify(newEntry);
1368
+ if (osMatches) {
1377
1369
  await cleanSettingsJsonMcp(path11.join(homeDir, ".claude", "settings.json"));
1370
+ await migratePermissionsToExeOs(path11.join(homeDir, ".claude", "settings.json"));
1378
1371
  return false;
1379
1372
  }
1380
- claudeJson.mcpServers[MCP_LEGACY_KEY] = newEntry;
1373
+ claudeJson.mcpServers[MCP_PRIMARY_KEY] = newEntry;
1381
1374
  await writeFile3(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
1382
1375
  await cleanSettingsJsonMcp(path11.join(homeDir, ".claude", "settings.json"));
1376
+ await migratePermissionsToExeOs(path11.join(homeDir, ".claude", "settings.json"));
1383
1377
  return true;
1384
1378
  }
1385
1379
  async function cleanSettingsJsonMcp(settingsPath) {
@@ -1403,6 +1397,35 @@ async function cleanSettingsJsonMcp(settingsPath) {
1403
1397
  } catch {
1404
1398
  }
1405
1399
  }
1400
+ async function migratePermissionsToExeOs(settingsPath) {
1401
+ if (!existsSync11(settingsPath)) return;
1402
+ try {
1403
+ const settings = JSON.parse(await readFile3(settingsPath, "utf-8"));
1404
+ const permissions = settings.permissions;
1405
+ if (!permissions || !Array.isArray(permissions.allow)) return;
1406
+ const allow = permissions.allow;
1407
+ let migrated = 0;
1408
+ for (let i = 0; i < allow.length; i++) {
1409
+ if (allow[i].startsWith("mcp__exe-mem__")) {
1410
+ const newName = allow[i].replace("mcp__exe-mem__", "mcp__exe-os__");
1411
+ if (!allow.includes(newName)) {
1412
+ allow[i] = newName;
1413
+ } else {
1414
+ allow[i] = "__REMOVE__";
1415
+ }
1416
+ migrated++;
1417
+ }
1418
+ }
1419
+ if (migrated > 0) {
1420
+ permissions.allow = allow.filter((e) => e !== "__REMOVE__");
1421
+ permissions.allow = [...new Set(permissions.allow)];
1422
+ await writeFile3(settingsPath, JSON.stringify(settings, null, 2) + "\n");
1423
+ process.stderr.write(`exe-os: migrated ${migrated} permission(s) from exe-mem \u2192 exe-os
1424
+ `);
1425
+ }
1426
+ } catch {
1427
+ }
1428
+ }
1406
1429
  async function mergeHooks(packageRoot, homeDir = os7.homedir()) {
1407
1430
  const settingsPath = path11.join(homeDir, ".claude", "settings.json");
1408
1431
  const logsDir = path11.join(homeDir, ".exe-os", "logs");
@@ -1663,9 +1686,10 @@ async function mergeHooks(packageRoot, homeDir = os7.homedir()) {
1663
1686
  "deploy_client"
1664
1687
  ];
1665
1688
  const allowList = permissions.allow;
1666
- for (const tool of expandDualPrefixTools(toolNames)) {
1667
- if (!allowList.includes(tool)) {
1668
- allowList.push(tool);
1689
+ for (const name of toolNames) {
1690
+ const fullName = `mcp__${MCP_PRIMARY_KEY}__${name}`;
1691
+ if (!allowList.includes(fullName)) {
1692
+ allowList.push(fullName);
1669
1693
  }
1670
1694
  }
1671
1695
  await mkdir3(path11.dirname(settingsPath), { recursive: true });
@@ -7264,11 +7264,7 @@ function notifyCoordinatorTaskCompletion(coordinatorSession, agentName2, taskTit
7264
7264
  if (pending instanceof Promise) {
7265
7265
  pending.then((count) => {
7266
7266
  if (count > 0) {
7267
- execSync6(
7268
- `tmux send-keys -t ${JSON.stringify(coordinatorSession)} '/exe-intercom' Enter`,
7269
- { timeout: 3e3 }
7270
- );
7271
- logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName2} completed "${taskTitle.slice(0, 50)}", ${count} reviews pending)`);
7267
+ logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName2} completed "${taskTitle.slice(0, 50)}", ${count} reviews pending \u2014 hook will surface)`);
7272
7268
  }
7273
7269
  }).catch(() => {
7274
7270
  });
@@ -6048,11 +6048,7 @@ function notifyCoordinatorTaskCompletion(coordinatorSession, agentName, taskTitl
6048
6048
  if (pending instanceof Promise) {
6049
6049
  pending.then((count) => {
6050
6050
  if (count > 0) {
6051
- execSync6(
6052
- `tmux send-keys -t ${JSON.stringify(coordinatorSession)} '/exe-intercom' Enter`,
6053
- { timeout: 3e3 }
6054
- );
6055
- logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName} completed "${taskTitle.slice(0, 50)}", ${count} reviews pending)`);
6051
+ logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName} completed "${taskTitle.slice(0, 50)}", ${count} reviews pending \u2014 hook will surface)`);
6056
6052
  }
6057
6053
  }).catch(() => {
6058
6054
  });
@@ -573,15 +573,6 @@ var init_agent_symlinks = __esm({
573
573
  });
574
574
 
575
575
  // src/lib/mcp-prefix.ts
576
- function expandDualPrefixTools(shortNames) {
577
- const out = [];
578
- for (const name of shortNames) {
579
- for (const prefix of MCP_TOOL_PREFIXES) {
580
- out.push(prefix + name);
581
- }
582
- }
583
- return out;
584
- }
585
576
  var MCP_PRIMARY_KEY, MCP_LEGACY_KEY, MCP_TOOL_PREFIXES;
586
577
  var init_mcp_prefix = __esm({
587
578
  "src/lib/mcp-prefix.ts"() {
@@ -712,18 +703,21 @@ async function registerMcpServer(packageRoot, homeDir = os5.homedir()) {
712
703
  args: [path6.join(packageRoot, "dist", "mcp", "server.js")],
713
704
  env: {}
714
705
  };
715
- const currentMem = claudeJson.mcpServers[MCP_LEGACY_KEY];
716
- const memMatches = currentMem && JSON.stringify(currentMem) === JSON.stringify(newEntry);
717
- if (claudeJson.mcpServers[MCP_PRIMARY_KEY]) {
718
- delete claudeJson.mcpServers[MCP_PRIMARY_KEY];
706
+ if (claudeJson.mcpServers[MCP_LEGACY_KEY]) {
707
+ delete claudeJson.mcpServers[MCP_LEGACY_KEY];
708
+ process.stderr.write("exe-os: migrated MCP server key exe-mem \u2192 exe-os\n");
719
709
  }
720
- if (memMatches && !claudeJson.mcpServers[MCP_PRIMARY_KEY]) {
710
+ const currentOs = claudeJson.mcpServers[MCP_PRIMARY_KEY];
711
+ const osMatches = currentOs && JSON.stringify(currentOs) === JSON.stringify(newEntry);
712
+ if (osMatches) {
721
713
  await cleanSettingsJsonMcp(path6.join(homeDir, ".claude", "settings.json"));
714
+ await migratePermissionsToExeOs(path6.join(homeDir, ".claude", "settings.json"));
722
715
  return false;
723
716
  }
724
- claudeJson.mcpServers[MCP_LEGACY_KEY] = newEntry;
717
+ claudeJson.mcpServers[MCP_PRIMARY_KEY] = newEntry;
725
718
  await writeFile3(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
726
719
  await cleanSettingsJsonMcp(path6.join(homeDir, ".claude", "settings.json"));
720
+ await migratePermissionsToExeOs(path6.join(homeDir, ".claude", "settings.json"));
727
721
  return true;
728
722
  }
729
723
  async function cleanSettingsJsonMcp(settingsPath) {
@@ -747,6 +741,35 @@ async function cleanSettingsJsonMcp(settingsPath) {
747
741
  } catch {
748
742
  }
749
743
  }
744
+ async function migratePermissionsToExeOs(settingsPath) {
745
+ if (!existsSync7(settingsPath)) return;
746
+ try {
747
+ const settings = JSON.parse(await readFile3(settingsPath, "utf-8"));
748
+ const permissions = settings.permissions;
749
+ if (!permissions || !Array.isArray(permissions.allow)) return;
750
+ const allow = permissions.allow;
751
+ let migrated = 0;
752
+ for (let i = 0; i < allow.length; i++) {
753
+ if (allow[i].startsWith("mcp__exe-mem__")) {
754
+ const newName = allow[i].replace("mcp__exe-mem__", "mcp__exe-os__");
755
+ if (!allow.includes(newName)) {
756
+ allow[i] = newName;
757
+ } else {
758
+ allow[i] = "__REMOVE__";
759
+ }
760
+ migrated++;
761
+ }
762
+ }
763
+ if (migrated > 0) {
764
+ permissions.allow = allow.filter((e) => e !== "__REMOVE__");
765
+ permissions.allow = [...new Set(permissions.allow)];
766
+ await writeFile3(settingsPath, JSON.stringify(settings, null, 2) + "\n");
767
+ process.stderr.write(`exe-os: migrated ${migrated} permission(s) from exe-mem \u2192 exe-os
768
+ `);
769
+ }
770
+ } catch {
771
+ }
772
+ }
750
773
  async function mergeHooks(packageRoot, homeDir = os5.homedir()) {
751
774
  const settingsPath = path6.join(homeDir, ".claude", "settings.json");
752
775
  const logsDir = path6.join(homeDir, ".exe-os", "logs");
@@ -1007,9 +1030,10 @@ async function mergeHooks(packageRoot, homeDir = os5.homedir()) {
1007
1030
  "deploy_client"
1008
1031
  ];
1009
1032
  const allowList = permissions.allow;
1010
- for (const tool of expandDualPrefixTools(toolNames)) {
1011
- if (!allowList.includes(tool)) {
1012
- allowList.push(tool);
1033
+ for (const name of toolNames) {
1034
+ const fullName = `mcp__${MCP_PRIMARY_KEY}__${name}`;
1035
+ if (!allowList.includes(fullName)) {
1036
+ allowList.push(fullName);
1013
1037
  }
1014
1038
  }
1015
1039
  await mkdir3(path6.dirname(settingsPath), { recursive: true });
@@ -1597,7 +1621,7 @@ var init_installer2 = __esm({
1597
1621
 
1598
1622
  // src/bin/install.ts
1599
1623
  init_installer();
1600
- import { existsSync as existsSync10, readFileSync as readFileSync7, unlinkSync as unlinkSync3, readdirSync as readdirSync2, openSync, closeSync } from "fs";
1624
+ import { existsSync as existsSync10, readFileSync as readFileSync7, writeFileSync as writeFileSync6, unlinkSync as unlinkSync3, readdirSync as readdirSync2, openSync, closeSync } from "fs";
1601
1625
  import { spawn, execSync as execSync3 } from "child_process";
1602
1626
  import path9 from "path";
1603
1627
  import os7 from "os";
@@ -1777,6 +1801,13 @@ function restartDaemon() {
1777
1801
  }
1778
1802
  } catch {
1779
1803
  }
1804
+ try {
1805
+ const versionPath = path9.join(EXE_DIR, "mcp-version");
1806
+ writeFileSync6(versionPath, `deploy-${Date.now()}`);
1807
+ process.stderr.write(`exe-os: MCP version marker updated \u2014 servers will hot-reload within 10s
1808
+ `);
1809
+ } catch {
1810
+ }
1780
1811
  try {
1781
1812
  const wpDir = path9.join(EXE_DIR, "worker-pids");
1782
1813
  if (existsSync10(wpDir)) {
@@ -7041,11 +7041,7 @@ function notifyCoordinatorTaskCompletion(coordinatorSession, agentName, taskTitl
7041
7041
  if (pending instanceof Promise) {
7042
7042
  pending.then((count) => {
7043
7043
  if (count > 0) {
7044
- execSync6(
7045
- `tmux send-keys -t ${JSON.stringify(coordinatorSession)} '/exe-intercom' Enter`,
7046
- { timeout: 3e3 }
7047
- );
7048
- logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName} completed "${taskTitle.slice(0, 50)}", ${count} reviews pending)`);
7044
+ logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName} completed "${taskTitle.slice(0, 50)}", ${count} reviews pending \u2014 hook will surface)`);
7049
7045
  }
7050
7046
  }).catch(() => {
7051
7047
  });
@@ -6119,11 +6119,7 @@ function notifyCoordinatorTaskCompletion(coordinatorSession, agentName, taskTitl
6119
6119
  if (pending instanceof Promise) {
6120
6120
  pending.then((count) => {
6121
6121
  if (count > 0) {
6122
- execSync6(
6123
- `tmux send-keys -t ${JSON.stringify(coordinatorSession)} '/exe-intercom' Enter`,
6124
- { timeout: 3e3 }
6125
- );
6126
- logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName} completed "${taskTitle.slice(0, 50)}", ${count} reviews pending)`);
6122
+ logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName} completed "${taskTitle.slice(0, 50)}", ${count} reviews pending \u2014 hook will surface)`);
6127
6123
  }
6128
6124
  }).catch(() => {
6129
6125
  });
@@ -724,6 +724,9 @@ async function runUpdate(cliArgs) {
724
724
  console.log(" Try: npm cache clean --force && npm install -g @askexenow/exe-os@latest");
725
725
  }
726
726
  console.log(" Hooks re-wired, daemon restarted automatically.");
727
+ console.log("");
728
+ console.log(" \x1B[33m\u26A1 Run /mcp in each active Claude Code session to pick up new tools.\x1B[0m");
729
+ console.log(" \x1B[2m(MCP servers can't hot-reload \u2014 Claude Code needs to reconnect them.)\x1B[0m");
727
730
  try {
728
731
  const { existsSync: exists, readFileSync: readFile2 } = await import("fs");
729
732
  const p = await import("path");
@@ -8884,11 +8884,7 @@ function notifyCoordinatorTaskCompletion(coordinatorSession, agentName, taskTitl
8884
8884
  if (pending instanceof Promise) {
8885
8885
  pending.then((count) => {
8886
8886
  if (count > 0) {
8887
- execSync6(
8888
- `tmux send-keys -t ${JSON.stringify(coordinatorSession)} '/exe-intercom' Enter`,
8889
- { timeout: 3e3 }
8890
- );
8891
- logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName} completed "${taskTitle.slice(0, 50)}", ${count} reviews pending)`);
8887
+ logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName} completed "${taskTitle.slice(0, 50)}", ${count} reviews pending \u2014 hook will surface)`);
8892
8888
  }
8893
8889
  }).catch(() => {
8894
8890
  });
@@ -9552,13 +9548,24 @@ function matchesChannel(msgChannel, matchChannel) {
9552
9548
  const channels = Array.isArray(matchChannel) ? matchChannel : [matchChannel];
9553
9549
  return channels.includes(msgChannel);
9554
9550
  }
9551
+ var MAX_REGEX_LENGTH = 200;
9552
+ function safeRegExp(pattern, flags) {
9553
+ if (pattern.length > MAX_REGEX_LENGTH) return null;
9554
+ try {
9555
+ return new RegExp(pattern, flags);
9556
+ } catch {
9557
+ return null;
9558
+ }
9559
+ }
9555
9560
  function matchesSender(msgSender, matchSender) {
9556
9561
  if (!matchSender) return true;
9557
- return new RegExp(matchSender).test(msgSender);
9562
+ const re = safeRegExp(matchSender);
9563
+ return re ? re.test(msgSender) : false;
9558
9564
  }
9559
9565
  function matchesTextPattern(msgText, matchPattern) {
9560
9566
  if (!matchPattern) return true;
9561
- return new RegExp(matchPattern, "i").test(msgText);
9567
+ const re = safeRegExp(matchPattern, "i");
9568
+ return re ? re.test(msgText) : false;
9562
9569
  }
9563
9570
  function matchesRoute(msg, match) {
9564
9571
  return matchesPlatform(msg.platform, match.platform) && matchesChannel(msg.channelId, match.channelId) && matchesSender(msg.senderId, match.senderId) && matchesTextPattern(msg.text, match.textPattern);
@@ -9598,6 +9605,12 @@ function validateGatewayConfig(config2) {
9598
9605
  if (!route.target) {
9599
9606
  warnings.push(`Route "${route.name}" has no target employee`);
9600
9607
  }
9608
+ if (route.match.senderId && !safeRegExp(route.match.senderId)) {
9609
+ warnings.push(`Route "${route.name}" has invalid senderId regex: ${route.match.senderId}`);
9610
+ }
9611
+ if (route.match.textPattern && !safeRegExp(route.match.textPattern, "i")) {
9612
+ warnings.push(`Route "${route.name}" has invalid textPattern regex: ${route.match.textPattern}`);
9613
+ }
9601
9614
  const isEmptyMatch = !route.match.platform && !route.match.channelId && !route.match.senderId && !route.match.textPattern;
9602
9615
  if (isEmptyMatch && config2.routes.indexOf(route) !== config2.routes.length - 1) {
9603
9616
  warnings.push(
@@ -5315,11 +5315,7 @@ function notifyCoordinatorTaskCompletion(coordinatorSession, agentName, taskTitl
5315
5315
  if (pending instanceof Promise) {
5316
5316
  pending.then((count) => {
5317
5317
  if (count > 0) {
5318
- execSync4(
5319
- `tmux send-keys -t ${JSON.stringify(coordinatorSession)} '/exe-intercom' Enter`,
5320
- { timeout: 3e3 }
5321
- );
5322
- logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName} completed "${taskTitle.slice(0, 50)}", ${count} reviews pending)`);
5318
+ logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName} completed "${taskTitle.slice(0, 50)}", ${count} reviews pending \u2014 hook will surface)`);
5323
5319
  }
5324
5320
  }).catch(() => {
5325
5321
  });
@@ -6047,11 +6047,7 @@ function notifyCoordinatorTaskCompletion(coordinatorSession, agentName, taskTitl
6047
6047
  if (pending instanceof Promise) {
6048
6048
  pending.then((count) => {
6049
6049
  if (count > 0) {
6050
- execSync6(
6051
- `tmux send-keys -t ${JSON.stringify(coordinatorSession)} '/exe-intercom' Enter`,
6052
- { timeout: 3e3 }
6053
- );
6054
- logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName} completed "${taskTitle.slice(0, 50)}", ${count} reviews pending)`);
6050
+ logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName} completed "${taskTitle.slice(0, 50)}", ${count} reviews pending \u2014 hook will surface)`);
6055
6051
  }
6056
6052
  }).catch(() => {
6057
6053
  });
@@ -6031,11 +6031,7 @@ function notifyCoordinatorTaskCompletion(coordinatorSession, agentName, taskTitl
6031
6031
  if (pending instanceof Promise) {
6032
6032
  pending.then((count) => {
6033
6033
  if (count > 0) {
6034
- execSync7(
6035
- `tmux send-keys -t ${JSON.stringify(coordinatorSession)} '/exe-intercom' Enter`,
6036
- { timeout: 3e3 }
6037
- );
6038
- logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName} completed "${taskTitle.slice(0, 50)}", ${count} reviews pending)`);
6034
+ logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName} completed "${taskTitle.slice(0, 50)}", ${count} reviews pending \u2014 hook will surface)`);
6039
6035
  }
6040
6036
  }).catch(() => {
6041
6037
  });
@@ -8585,11 +8585,7 @@ function notifyCoordinatorTaskCompletion(coordinatorSession, agentName, taskTitl
8585
8585
  if (pending instanceof Promise) {
8586
8586
  pending.then((count) => {
8587
8587
  if (count > 0) {
8588
- execSync9(
8589
- `tmux send-keys -t ${JSON.stringify(coordinatorSession)} '/exe-intercom' Enter`,
8590
- { timeout: 3e3 }
8591
- );
8592
- logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName} completed "${taskTitle.slice(0, 50)}", ${count} reviews pending)`);
8588
+ logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName} completed "${taskTitle.slice(0, 50)}", ${count} reviews pending \u2014 hook will surface)`);
8593
8589
  }
8594
8590
  }).catch(() => {
8595
8591
  });
@@ -10152,7 +10148,8 @@ process.stdin.on("end", async () => {
10152
10148
  transport.sendKeys(session, nudgeMsg);
10153
10149
  if (rtConfig.runtime === "codex" || rtConfig.runtime === "opencode") {
10154
10150
  try {
10155
- execSync10(`tmux send-keys -t ${session} Tab`, { encoding: "utf8", timeout: 2e3 });
10151
+ const { execFileSync: efs } = __require("child_process");
10152
+ efs("tmux", ["send-keys", "-t", session, "Tab"], { encoding: "utf8", timeout: 2e3 });
10156
10153
  } catch {
10157
10154
  }
10158
10155
  }
@@ -6240,11 +6240,7 @@ function notifyCoordinatorTaskCompletion(coordinatorSession, agentName, taskTitl
6240
6240
  if (pending instanceof Promise) {
6241
6241
  pending.then((count) => {
6242
6242
  if (count > 0) {
6243
- execSync7(
6244
- `tmux send-keys -t ${JSON.stringify(coordinatorSession)} '/exe-intercom' Enter`,
6245
- { timeout: 3e3 }
6246
- );
6247
- logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName} completed "${taskTitle.slice(0, 50)}", ${count} reviews pending)`);
6243
+ logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName} completed "${taskTitle.slice(0, 50)}", ${count} reviews pending \u2014 hook will surface)`);
6248
6244
  }
6249
6245
  }).catch(() => {
6250
6246
  });
package/dist/index.js CHANGED
@@ -6420,11 +6420,7 @@ function notifyCoordinatorTaskCompletion(coordinatorSession, agentName, taskTitl
6420
6420
  if (pending instanceof Promise) {
6421
6421
  pending.then((count) => {
6422
6422
  if (count > 0) {
6423
- execSync7(
6424
- `tmux send-keys -t ${JSON.stringify(coordinatorSession)} '/exe-intercom' Enter`,
6425
- { timeout: 3e3 }
6426
- );
6427
- logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName} completed "${taskTitle.slice(0, 50)}", ${count} reviews pending)`);
6423
+ logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName} completed "${taskTitle.slice(0, 50)}", ${count} reviews pending \u2014 hook will surface)`);
6428
6424
  }
6429
6425
  }).catch(() => {
6430
6426
  });
@@ -12299,13 +12295,24 @@ function matchesChannel(msgChannel, matchChannel) {
12299
12295
  const channels = Array.isArray(matchChannel) ? matchChannel : [matchChannel];
12300
12296
  return channels.includes(msgChannel);
12301
12297
  }
12298
+ var MAX_REGEX_LENGTH = 200;
12299
+ function safeRegExp(pattern, flags) {
12300
+ if (pattern.length > MAX_REGEX_LENGTH) return null;
12301
+ try {
12302
+ return new RegExp(pattern, flags);
12303
+ } catch {
12304
+ return null;
12305
+ }
12306
+ }
12302
12307
  function matchesSender(msgSender, matchSender) {
12303
12308
  if (!matchSender) return true;
12304
- return new RegExp(matchSender).test(msgSender);
12309
+ const re = safeRegExp(matchSender);
12310
+ return re ? re.test(msgSender) : false;
12305
12311
  }
12306
12312
  function matchesTextPattern(msgText, matchPattern) {
12307
12313
  if (!matchPattern) return true;
12308
- return new RegExp(matchPattern, "i").test(msgText);
12314
+ const re = safeRegExp(matchPattern, "i");
12315
+ return re ? re.test(msgText) : false;
12309
12316
  }
12310
12317
  function matchesRoute(msg, match) {
12311
12318
  return matchesPlatform(msg.platform, match.platform) && matchesChannel(msg.channelId, match.channelId) && matchesSender(msg.senderId, match.senderId) && matchesTextPattern(msg.text, match.textPattern);
@@ -12345,6 +12352,12 @@ function validateGatewayConfig(config2) {
12345
12352
  if (!route.target) {
12346
12353
  warnings.push(`Route "${route.name}" has no target employee`);
12347
12354
  }
12355
+ if (route.match.senderId && !safeRegExp(route.match.senderId)) {
12356
+ warnings.push(`Route "${route.name}" has invalid senderId regex: ${route.match.senderId}`);
12357
+ }
12358
+ if (route.match.textPattern && !safeRegExp(route.match.textPattern, "i")) {
12359
+ warnings.push(`Route "${route.name}" has invalid textPattern regex: ${route.match.textPattern}`);
12360
+ }
12348
12361
  const isEmptyMatch = !route.match.platform && !route.match.channelId && !route.match.senderId && !route.match.textPattern;
12349
12362
  if (isEmptyMatch && config2.routes.indexOf(route) !== config2.routes.length - 1) {
12350
12363
  warnings.push(
@@ -8046,11 +8046,7 @@ function notifyCoordinatorTaskCompletion(coordinatorSession, agentName, taskTitl
8046
8046
  if (pending instanceof Promise) {
8047
8047
  pending.then((count) => {
8048
8048
  if (count > 0) {
8049
- execSync7(
8050
- `tmux send-keys -t ${JSON.stringify(coordinatorSession)} '/exe-intercom' Enter`,
8051
- { timeout: 3e3 }
8052
- );
8053
- logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName} completed "${taskTitle.slice(0, 50)}", ${count} reviews pending)`);
8049
+ logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName} completed "${taskTitle.slice(0, 50)}", ${count} reviews pending \u2014 hook will surface)`);
8054
8050
  }
8055
8051
  }).catch(() => {
8056
8052
  });
package/dist/lib/tasks.js CHANGED
@@ -2142,11 +2142,7 @@ function notifyCoordinatorTaskCompletion(coordinatorSession, agentName, taskTitl
2142
2142
  if (pending instanceof Promise) {
2143
2143
  pending.then((count) => {
2144
2144
  if (count > 0) {
2145
- execSync4(
2146
- `tmux send-keys -t ${JSON.stringify(coordinatorSession)} '/exe-intercom' Enter`,
2147
- { timeout: 3e3 }
2148
- );
2149
- logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName} completed "${taskTitle.slice(0, 50)}", ${count} reviews pending)`);
2145
+ logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName} completed "${taskTitle.slice(0, 50)}", ${count} reviews pending \u2014 hook will surface)`);
2150
2146
  }
2151
2147
  }).catch(() => {
2152
2148
  });
@@ -4167,11 +4167,7 @@ function notifyCoordinatorTaskCompletion(coordinatorSession, agentName, taskTitl
4167
4167
  if (pending instanceof Promise) {
4168
4168
  pending.then((count) => {
4169
4169
  if (count > 0) {
4170
- execSync6(
4171
- `tmux send-keys -t ${JSON.stringify(coordinatorSession)} '/exe-intercom' Enter`,
4172
- { timeout: 3e3 }
4173
- );
4174
- logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName} completed "${taskTitle.slice(0, 50)}", ${count} reviews pending)`);
4170
+ logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName} completed "${taskTitle.slice(0, 50)}", ${count} reviews pending \u2014 hook will surface)`);
4175
4171
  }
4176
4172
  }).catch(() => {
4177
4173
  });
@@ -5792,8 +5792,8 @@ async function hybridSearch(queryText, agentId, options) {
5792
5792
  try {
5793
5793
  const fs = await import("fs");
5794
5794
  const path44 = await import("path");
5795
- const os18 = await import("os");
5796
- const logPath = path44.join(os18.homedir(), ".exe-os", "search-quality.jsonl");
5795
+ const os19 = await import("os");
5796
+ const logPath = path44.join(os19.homedir(), ".exe-os", "search-quality.jsonl");
5797
5797
  fs.mkdirSync(path44.dirname(logPath), { recursive: true });
5798
5798
  fs.appendFileSync(logPath, JSON.stringify(logEntry) + "\n");
5799
5799
  } catch {
@@ -8491,11 +8491,7 @@ function notifyCoordinatorTaskCompletion(coordinatorSession, agentName, taskTitl
8491
8491
  if (pending instanceof Promise) {
8492
8492
  pending.then((count) => {
8493
8493
  if (count > 0) {
8494
- execSync7(
8495
- `tmux send-keys -t ${JSON.stringify(coordinatorSession)} '/exe-intercom' Enter`,
8496
- { timeout: 3e3 }
8497
- );
8498
- logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName} completed "${taskTitle.slice(0, 50)}", ${count} reviews pending)`);
8494
+ logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName} completed "${taskTitle.slice(0, 50)}", ${count} reviews pending \u2014 hook will surface)`);
8499
8495
  }
8500
8496
  }).catch(() => {
8501
8497
  });
@@ -12130,8 +12126,9 @@ init_database();
12130
12126
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
12131
12127
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
12132
12128
  import { spawn as spawn4 } from "child_process";
12133
- import { existsSync as existsSync33, openSync as openSync3, mkdirSync as mkdirSync17, closeSync as closeSync3 } from "fs";
12129
+ import { existsSync as existsSync33, openSync as openSync3, mkdirSync as mkdirSync17, closeSync as closeSync3, readFileSync as readFileSync28 } from "fs";
12134
12130
  import path43 from "path";
12131
+ import os18 from "os";
12135
12132
  import { fileURLToPath as fileURLToPath5 } from "url";
12136
12133
 
12137
12134
  // src/mcp/tools/recall-my-memory.ts
@@ -18920,8 +18917,8 @@ function registerExportGraph(server2) {
18920
18917
  const html = await exportGraphHTML(client);
18921
18918
  const fs = await import("fs");
18922
18919
  const path44 = await import("path");
18923
- const os18 = await import("os");
18924
- const outDir = path44.join(os18.homedir(), ".exe-os", "exports");
18920
+ const os19 = await import("os");
18921
+ const outDir = path44.join(os19.homedir(), ".exe-os", "exports");
18925
18922
  fs.mkdirSync(outDir, { recursive: true });
18926
18923
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
18927
18924
  const filePath = path44.join(outDir, `graph-${timestamp}.html`);
@@ -23072,6 +23069,28 @@ try {
23072
23069
  }
23073
23070
  }, 3e4);
23074
23071
  _ppidWatchdog.unref();
23072
+ const MCP_VERSION_PATH = path43.join(os18.homedir(), ".exe-os", "mcp-version");
23073
+ let _currentMcpVersion = null;
23074
+ try {
23075
+ _currentMcpVersion = existsSync33(MCP_VERSION_PATH) ? readFileSync28(MCP_VERSION_PATH, "utf8").trim() : null;
23076
+ } catch {
23077
+ }
23078
+ const _versionWatchdog = setInterval(() => {
23079
+ try {
23080
+ if (!existsSync33(MCP_VERSION_PATH)) return;
23081
+ const diskVersion = readFileSync28(MCP_VERSION_PATH, "utf8").trim();
23082
+ if (_currentMcpVersion && diskVersion !== _currentMcpVersion) {
23083
+ process.stderr.write(
23084
+ `[exe-os] MCP version changed (${_currentMcpVersion} \u2192 ${diskVersion}). Hot-reloading...
23085
+ `
23086
+ );
23087
+ void shutdown("hot_reload");
23088
+ }
23089
+ if (!_currentMcpVersion) _currentMcpVersion = diskVersion;
23090
+ } catch {
23091
+ }
23092
+ }, 1e4);
23093
+ _versionWatchdog.unref();
23075
23094
  const BACKFILL_CHECK_MS = 5 * 60 * 1e3;
23076
23095
  _backfillTimer = setInterval(async () => {
23077
23096
  try {
@@ -2381,11 +2381,7 @@ function notifyCoordinatorTaskCompletion(coordinatorSession, agentName, taskTitl
2381
2381
  if (pending instanceof Promise) {
2382
2382
  pending.then((count) => {
2383
2383
  if (count > 0) {
2384
- execSync4(
2385
- `tmux send-keys -t ${JSON.stringify(coordinatorSession)} '/exe-intercom' Enter`,
2386
- { timeout: 3e3 }
2387
- );
2388
- logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName} completed "${taskTitle.slice(0, 50)}", ${count} reviews pending)`);
2384
+ logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName} completed "${taskTitle.slice(0, 50)}", ${count} reviews pending \u2014 hook will surface)`);
2389
2385
  }
2390
2386
  }).catch(() => {
2391
2387
  });
@@ -2145,11 +2145,7 @@ function notifyCoordinatorTaskCompletion(coordinatorSession, agentName, taskTitl
2145
2145
  if (pending instanceof Promise) {
2146
2146
  pending.then((count) => {
2147
2147
  if (count > 0) {
2148
- execSync4(
2149
- `tmux send-keys -t ${JSON.stringify(coordinatorSession)} '/exe-intercom' Enter`,
2150
- { timeout: 3e3 }
2151
- );
2152
- logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName} completed "${taskTitle.slice(0, 50)}", ${count} reviews pending)`);
2148
+ logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName} completed "${taskTitle.slice(0, 50)}", ${count} reviews pending \u2014 hook will surface)`);
2153
2149
  }
2154
2150
  }).catch(() => {
2155
2151
  });
@@ -6181,11 +6181,7 @@ function notifyCoordinatorTaskCompletion(coordinatorSession, agentName, taskTitl
6181
6181
  if (pending instanceof Promise) {
6182
6182
  pending.then((count) => {
6183
6183
  if (count > 0) {
6184
- execSync7(
6185
- `tmux send-keys -t ${JSON.stringify(coordinatorSession)} '/exe-intercom' Enter`,
6186
- { timeout: 3e3 }
6187
- );
6188
- logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName} completed "${taskTitle.slice(0, 50)}", ${count} reviews pending)`);
6184
+ logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName} completed "${taskTitle.slice(0, 50)}", ${count} reviews pending \u2014 hook will surface)`);
6189
6185
  }
6190
6186
  }).catch(() => {
6191
6187
  });
package/dist/tui/App.js CHANGED
@@ -6785,11 +6785,7 @@ function notifyCoordinatorTaskCompletion(coordinatorSession, agentName, taskTitl
6785
6785
  if (pending instanceof Promise) {
6786
6786
  pending.then((count) => {
6787
6787
  if (count > 0) {
6788
- execSync7(
6789
- `tmux send-keys -t ${JSON.stringify(coordinatorSession)} '/exe-intercom' Enter`,
6790
- { timeout: 3e3 }
6791
- );
6792
- logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName} completed "${taskTitle.slice(0, 50)}", ${count} reviews pending)`);
6788
+ logIntercom(`COMPLETION \u2192 ${coordinatorSession} (${agentName} completed "${taskTitle.slice(0, 50)}", ${count} reviews pending \u2014 hook will surface)`);
6793
6789
  }
6794
6790
  }).catch(() => {
6795
6791
  });
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@askexenow/exe-os",
3
- "version": "0.9.19",
3
+ "version": "0.9.21",
4
4
  "description": "AI employee operating system — persistent memory, task management, and multi-agent coordination for Claude Code.",
5
- "license": "CC-BY-NC-4.0",
5
+ "license": "SEE LICENSE IN LICENSE",
6
6
  "type": "module",
7
7
  "repository": {
8
8
  "type": "git",
@@ -67,14 +67,14 @@
67
67
  "test:watch": "vitest",
68
68
  "typecheck": "tsc --noEmit",
69
69
  "build": "tsup && mkdir -p dist/assets && cp src/assets/tmux.conf dist/assets/ && cp src/assets/ghostty.conf dist/assets/ && cp src/assets/statusline-command.sh dist/assets/ && cp src/bin/exe-start.sh dist/bin/exe-start.sh",
70
- "deploy": "node dist/bin/pre-build-guard.js 2>/dev/null; (kill $(cat ~/.exe-os/exed.pid 2>/dev/null) 2>/dev/null; pgrep -f exe-daemon.js | xargs kill 2>/dev/null; true) && tsup && mkdir -p dist/assets && cp src/assets/tmux.conf dist/assets/ && cp src/assets/ghostty.conf dist/assets/ && cp src/assets/statusline-command.sh dist/assets/ && cp src/bin/exe-start.sh dist/bin/exe-start.sh && npm install -g . && node dist/bin/install.js --global && echo '[exe-os] Deploy complete. MCP servers will auto-reconnect on next tool call.'",
70
+ "deploy": "node dist/bin/pre-build-guard.js 2>/dev/null; (kill $(cat ~/.exe-os/exed.pid 2>/dev/null) 2>/dev/null; pgrep -f exe-daemon.js | xargs kill 2>/dev/null; true) && tsup && mkdir -p dist/assets && cp src/assets/tmux.conf dist/assets/ && cp src/assets/ghostty.conf dist/assets/ && cp src/assets/statusline-command.sh dist/assets/ && cp src/bin/exe-start.sh dist/bin/exe-start.sh && npm install -g . && node dist/bin/install.js --global && echo '[exe-os] Deploy complete. Run /mcp in active sessions to reconnect.'",
71
71
  "postinstall": "node dist/bin/install.js --global 2>/dev/null || true",
72
72
  "prepublishOnly": "npm run typecheck && npm run build && node dist/bin/customer-readiness.js",
73
73
  "test:publish": "npx vitest run --maxWorkers=4 --exclude 'tests/tui/**' --exclude 'tests/lib/tmux-routing.test.ts' --exclude 'tests/lib/intercom-routing.test.ts' --exclude 'tests/gateway/**' --exclude 'tests/installer/setup-wizard.test.ts' --exclude 'tests/mcp/ingest-document.test.ts' --exclude 'tests/lib/hybrid-search.test.ts' --exclude 'tests/lib/worker-gate.test.ts'",
74
74
  "benchmark:longmemeval": "npx tsx tests/benchmarks/longmemeval.ts"
75
75
  },
76
76
  "dependencies": {
77
- "@anthropic-ai/sdk": "^0.80.0",
77
+ "@anthropic-ai/sdk": "^0.95.1",
78
78
  "@discordjs/voice": "^0.19.2",
79
79
  "@libsql/client": "^0.14.0",
80
80
  "@modelcontextprotocol/sdk": "^1.27.1",