@askexenow/exe-os 0.9.13 → 0.9.14

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 (70) hide show
  1. package/dist/bin/backfill-conversations.js +31 -4
  2. package/dist/bin/backfill-responses.js +31 -4
  3. package/dist/bin/backfill-vectors.js +25 -4
  4. package/dist/bin/cleanup-stale-review-tasks.js +1 -1
  5. package/dist/bin/cli.js +55 -8
  6. package/dist/bin/exe-assign.js +31 -4
  7. package/dist/bin/exe-boot.js +25 -4
  8. package/dist/bin/exe-dispatch.js +1 -1
  9. package/dist/bin/exe-doctor.js +1 -1
  10. package/dist/bin/exe-export-behaviors.js +1 -1
  11. package/dist/bin/exe-forget.js +1 -1
  12. package/dist/bin/exe-gateway.js +447 -21
  13. package/dist/bin/exe-heartbeat.js +1 -1
  14. package/dist/bin/exe-kill.js +1 -1
  15. package/dist/bin/exe-launch-agent.js +1 -1
  16. package/dist/bin/exe-link.js +31 -4
  17. package/dist/bin/exe-pending-messages.js +1 -1
  18. package/dist/bin/exe-pending-notifications.js +1 -1
  19. package/dist/bin/exe-pending-reviews.js +1 -1
  20. package/dist/bin/exe-rename.js +31 -4
  21. package/dist/bin/exe-review.js +1 -1
  22. package/dist/bin/exe-search.js +31 -4
  23. package/dist/bin/exe-session-cleanup.js +25 -4
  24. package/dist/bin/exe-start-codex.js +1 -1
  25. package/dist/bin/exe-start-opencode.js +1 -1
  26. package/dist/bin/exe-status.js +1 -1
  27. package/dist/bin/exe-team.js +1 -1
  28. package/dist/bin/git-sweep.js +25 -4
  29. package/dist/bin/graph-backfill.js +1 -1
  30. package/dist/bin/graph-export.js +1 -1
  31. package/dist/bin/scan-tasks.js +25 -4
  32. package/dist/bin/setup.js +46 -8
  33. package/dist/bin/shard-migrate.js +1 -1
  34. package/dist/bin/wiki-sync.js +1 -1
  35. package/dist/gateway/index.js +128 -125
  36. package/dist/hooks/bug-report-worker.js +1 -1
  37. package/dist/hooks/codex-stop-task-finalizer.js +1 -1
  38. package/dist/hooks/commit-complete.js +25 -4
  39. package/dist/hooks/error-recall.js +31 -4
  40. package/dist/hooks/ingest-worker.js +25 -4
  41. package/dist/hooks/ingest.js +1 -1
  42. package/dist/hooks/instructions-loaded.js +31 -4
  43. package/dist/hooks/notification.js +31 -4
  44. package/dist/hooks/post-compact.js +31 -4
  45. package/dist/hooks/pre-compact.js +25 -4
  46. package/dist/hooks/pre-tool-use.js +31 -4
  47. package/dist/hooks/prompt-ingest-worker.js +25 -4
  48. package/dist/hooks/prompt-submit.js +25 -4
  49. package/dist/hooks/response-ingest-worker.js +25 -4
  50. package/dist/hooks/session-end.js +25 -4
  51. package/dist/hooks/session-start.js +31 -4
  52. package/dist/hooks/stop.js +25 -4
  53. package/dist/hooks/subagent-stop.js +31 -4
  54. package/dist/hooks/summary-worker.js +25 -4
  55. package/dist/index.js +128 -125
  56. package/dist/lib/cloud-sync.js +31 -4
  57. package/dist/lib/database.js +31 -4
  58. package/dist/lib/db-daemon-client.js +31 -3
  59. package/dist/lib/db.js +31 -4
  60. package/dist/lib/device-registry.js +31 -4
  61. package/dist/lib/embedder.js +30 -3
  62. package/dist/lib/exe-daemon-client.js +31 -3
  63. package/dist/lib/exe-daemon.js +1958 -153
  64. package/dist/lib/hybrid-search.js +31 -4
  65. package/dist/lib/schedules.js +1 -1
  66. package/dist/lib/store.js +1 -1
  67. package/dist/mcp/server.js +25 -4
  68. package/dist/runtime/index.js +25 -4
  69. package/dist/tui/App.js +34 -4
  70. package/package.json +1 -1
@@ -1372,7 +1372,7 @@ async function ensureCompatibilityViews(prisma) {
1372
1372
  for (const mapping of VIEW_MAPPINGS) {
1373
1373
  const relation = mapping.source.replace(/"/g, "");
1374
1374
  const rows = await prisma.$queryRawUnsafe(
1375
- "SELECT to_regclass($1) AS regclass",
1375
+ "SELECT to_regclass($1)::text AS regclass",
1376
1376
  relation
1377
1377
  );
1378
1378
  if (!rows[0]?.regclass) {
@@ -2811,8 +2811,29 @@ function findPackageRoot() {
2811
2811
  }
2812
2812
  return null;
2813
2813
  }
2814
+ function getAvailableMemoryGB() {
2815
+ if (process.platform === "darwin") {
2816
+ try {
2817
+ const { execSync: execSync7 } = __require("child_process");
2818
+ const vmstat = execSync7("vm_stat", { encoding: "utf8" });
2819
+ const pageSize = 16384;
2820
+ const pageSizeMatch = vmstat.match(/page size of (\d+) bytes/);
2821
+ const actualPageSize = pageSizeMatch ? parseInt(pageSizeMatch[1], 10) : pageSize;
2822
+ const free = vmstat.match(/Pages free:\s+(\d+)/);
2823
+ const inactive = vmstat.match(/Pages inactive:\s+(\d+)/);
2824
+ const speculative = vmstat.match(/Pages speculative:\s+(\d+)/);
2825
+ const freePages = free ? parseInt(free[1], 10) : 0;
2826
+ const inactivePages = inactive ? parseInt(inactive[1], 10) : 0;
2827
+ const speculativePages = speculative ? parseInt(speculative[1], 10) : 0;
2828
+ return (freePages + inactivePages + speculativePages) * actualPageSize / (1024 * 1024 * 1024);
2829
+ } catch {
2830
+ return os5.freemem() / (1024 * 1024 * 1024);
2831
+ }
2832
+ }
2833
+ return os5.freemem() / (1024 * 1024 * 1024);
2834
+ }
2814
2835
  function spawnDaemon() {
2815
- const freeGB = os5.freemem() / (1024 * 1024 * 1024);
2836
+ const freeGB = getAvailableMemoryGB();
2816
2837
  const totalGB = os5.totalmem() / (1024 * 1024 * 1024);
2817
2838
  if (totalGB <= 8) {
2818
2839
  process.stderr.write(
@@ -2821,9 +2842,9 @@ function spawnDaemon() {
2821
2842
  );
2822
2843
  return;
2823
2844
  }
2824
- if (totalGB <= 16 && freeGB < 4) {
2845
+ if (totalGB <= 16 && freeGB < 2) {
2825
2846
  process.stderr.write(
2826
- `[exed-client] SKIP: low memory (${freeGB.toFixed(1)}GB free / ${totalGB.toFixed(0)}GB total). Embedding daemon not started \u2014 using keyword search only.
2847
+ `[exed-client] SKIP: low memory (${freeGB.toFixed(1)}GB available / ${totalGB.toFixed(0)}GB total). Embedding daemon not started \u2014 using keyword search only.
2827
2848
  `
2828
2849
  );
2829
2850
  return;
@@ -6297,6 +6318,362 @@ var init_whatsapp = __esm({
6297
6318
  }
6298
6319
  });
6299
6320
 
6321
+ // src/gateway/adapters/signal.ts
6322
+ var signal_exports = {};
6323
+ __export(signal_exports, {
6324
+ SignalAdapter: () => SignalAdapter
6325
+ });
6326
+ import { randomUUID as randomUUID5 } from "crypto";
6327
+ var DEFAULT_TIMEOUT_MS, POLL_INTERVAL_MS, SignalAdapter;
6328
+ var init_signal = __esm({
6329
+ "src/gateway/adapters/signal.ts"() {
6330
+ "use strict";
6331
+ DEFAULT_TIMEOUT_MS = 1e4;
6332
+ POLL_INTERVAL_MS = 2e3;
6333
+ SignalAdapter = class {
6334
+ platform = "signal";
6335
+ baseUrl = "";
6336
+ account = "";
6337
+ abortController = null;
6338
+ messageHandler = null;
6339
+ _connected = false;
6340
+ pollTimer = null;
6341
+ normalizeBaseUrl(url) {
6342
+ const trimmed = url.trim();
6343
+ if (/^https?:\/\//i.test(trimmed)) {
6344
+ return trimmed.replace(/\/+$/, "");
6345
+ }
6346
+ return `http://${trimmed}`.replace(/\/+$/, "");
6347
+ }
6348
+ async connect(config2) {
6349
+ this.baseUrl = this.normalizeBaseUrl(
6350
+ config2.credentials.baseUrl ?? "localhost:8080"
6351
+ );
6352
+ this.account = config2.credentials.account ?? "";
6353
+ const check = await this.healthCheck();
6354
+ if (!check.connected) {
6355
+ throw new Error(
6356
+ `signal-cli REST API not reachable at ${this.baseUrl}. Ensure bbernhard/signal-cli-rest-api container is running.`
6357
+ );
6358
+ }
6359
+ this._connected = true;
6360
+ this.abortController = new AbortController();
6361
+ void this.startPolling();
6362
+ void this.syncContacts().catch((err) => {
6363
+ console.error("[signal] Contact sync failed:", err);
6364
+ });
6365
+ void this.syncGroups().catch((err) => {
6366
+ console.error("[signal] Group sync failed:", err);
6367
+ });
6368
+ }
6369
+ async disconnect() {
6370
+ this.abortController?.abort();
6371
+ this.abortController = null;
6372
+ if (this.pollTimer) {
6373
+ clearTimeout(this.pollTimer);
6374
+ this.pollTimer = null;
6375
+ }
6376
+ this._connected = false;
6377
+ }
6378
+ onMessage(handler) {
6379
+ this.messageHandler = handler;
6380
+ }
6381
+ async sendText(channelId, text, _options) {
6382
+ const isGroup = channelId.startsWith("group:");
6383
+ const body = {
6384
+ message: text,
6385
+ number: this.account,
6386
+ ...isGroup ? { recipients: [], group_id: channelId.slice("group:".length) } : { recipients: [channelId] }
6387
+ };
6388
+ const res = await fetch(`${this.baseUrl}/v2/send`, {
6389
+ method: "POST",
6390
+ headers: { "Content-Type": "application/json" },
6391
+ body: JSON.stringify(body),
6392
+ signal: AbortSignal.timeout(DEFAULT_TIMEOUT_MS)
6393
+ });
6394
+ if (!res.ok) {
6395
+ const errText = await res.text().catch(() => "");
6396
+ throw new Error(`Signal send failed (${res.status}): ${errText}`);
6397
+ }
6398
+ }
6399
+ async sendTyping(channelId) {
6400
+ try {
6401
+ const isGroup = channelId.startsWith("group:");
6402
+ const endpoint = isGroup ? `${this.baseUrl}/v1/typing-indicator/${this.account}` : `${this.baseUrl}/v1/typing-indicator/${this.account}`;
6403
+ await fetch(endpoint, {
6404
+ method: "PUT",
6405
+ headers: { "Content-Type": "application/json" },
6406
+ body: JSON.stringify({
6407
+ recipient: isGroup ? void 0 : channelId,
6408
+ group_id: isGroup ? channelId.slice("group:".length) : void 0
6409
+ }),
6410
+ signal: AbortSignal.timeout(DEFAULT_TIMEOUT_MS)
6411
+ });
6412
+ } catch {
6413
+ }
6414
+ }
6415
+ async healthCheck() {
6416
+ if (!this.baseUrl) return { connected: this._connected };
6417
+ const start = Date.now();
6418
+ try {
6419
+ const res = await fetch(`${this.baseUrl}/v1/about`, {
6420
+ method: "GET",
6421
+ signal: AbortSignal.timeout(DEFAULT_TIMEOUT_MS)
6422
+ });
6423
+ this._connected = res.ok;
6424
+ return {
6425
+ connected: this._connected,
6426
+ latencyMs: Date.now() - start
6427
+ };
6428
+ } catch {
6429
+ this._connected = false;
6430
+ return { connected: false };
6431
+ }
6432
+ }
6433
+ async startPolling() {
6434
+ while (this.abortController && !this.abortController.signal.aborted) {
6435
+ try {
6436
+ await this.pollMessages();
6437
+ } catch (err) {
6438
+ if (this.abortController?.signal.aborted) return;
6439
+ console.error("[signal] Poll error:", err);
6440
+ }
6441
+ await new Promise((resolve) => {
6442
+ this.pollTimer = setTimeout(resolve, POLL_INTERVAL_MS);
6443
+ });
6444
+ }
6445
+ }
6446
+ async pollMessages() {
6447
+ if (!this.account || !this.messageHandler) return;
6448
+ const res = await fetch(
6449
+ `${this.baseUrl}/v1/receive/${encodeURIComponent(this.account)}`,
6450
+ {
6451
+ method: "GET",
6452
+ signal: AbortSignal.timeout(DEFAULT_TIMEOUT_MS)
6453
+ }
6454
+ );
6455
+ if (!res.ok) {
6456
+ if (res.status === 400) {
6457
+ return;
6458
+ }
6459
+ throw new Error(`Signal receive failed (${res.status})`);
6460
+ }
6461
+ const entries = await res.json();
6462
+ if (!Array.isArray(entries) || entries.length === 0) return;
6463
+ for (const entry of entries) {
6464
+ await this.handleEntry(entry);
6465
+ }
6466
+ }
6467
+ async handleEntry(entry) {
6468
+ if (!this.messageHandler) return;
6469
+ const envelope = entry.envelope;
6470
+ if (!envelope) return;
6471
+ const senderId = envelope.sourceNumber ?? envelope.source ?? envelope.sourceUuid ?? "";
6472
+ if (envelope.dataMessage?.reaction) {
6473
+ const rm = envelope.dataMessage.reaction;
6474
+ if (rm.isRemove) return;
6475
+ const normalized2 = {
6476
+ messageId: randomUUID5(),
6477
+ platform: "signal",
6478
+ senderId,
6479
+ senderName: envelope.sourceName ?? void 0,
6480
+ channelId: senderId,
6481
+ chatType: "direct",
6482
+ text: "",
6483
+ timestamp: new Date(
6484
+ envelope.timestamp ?? Date.now()
6485
+ ).toISOString(),
6486
+ raw: entry,
6487
+ dataCategory: "reaction",
6488
+ reaction: {
6489
+ emoji: rm.emoji ?? "",
6490
+ targetMessageId: String(rm.targetSentTimestamp ?? ""),
6491
+ reactedBy: senderId,
6492
+ timestamp: new Date(
6493
+ envelope.timestamp ?? Date.now()
6494
+ ).toISOString()
6495
+ }
6496
+ };
6497
+ await this.emitMessage(normalized2);
6498
+ return;
6499
+ }
6500
+ if (envelope.receiptMessage) {
6501
+ const rcpt = envelope.receiptMessage;
6502
+ for (const ts2 of rcpt.timestamps ?? []) {
6503
+ const normalized2 = {
6504
+ messageId: randomUUID5(),
6505
+ platform: "signal",
6506
+ senderId,
6507
+ senderName: envelope.sourceName ?? void 0,
6508
+ channelId: senderId,
6509
+ chatType: "direct",
6510
+ text: "",
6511
+ timestamp: new Date(
6512
+ envelope.timestamp ?? Date.now()
6513
+ ).toISOString(),
6514
+ raw: entry,
6515
+ dataCategory: "read_receipt",
6516
+ readReceipt: {
6517
+ messageId: String(ts2),
6518
+ status: rcpt.type === "READ" ? "read" : "delivered",
6519
+ timestamp: new Date(
6520
+ envelope.timestamp ?? Date.now()
6521
+ ).toISOString(),
6522
+ readBy: senderId
6523
+ }
6524
+ };
6525
+ await this.emitMessage(normalized2);
6526
+ }
6527
+ return;
6528
+ }
6529
+ if (envelope.editMessage) {
6530
+ const em = envelope.editMessage;
6531
+ const dm2 = em.dataMessage;
6532
+ if (!dm2) return;
6533
+ const isGroup2 = !!dm2.groupInfo?.groupId;
6534
+ const normalized2 = {
6535
+ messageId: String(dm2.timestamp ?? randomUUID5()),
6536
+ platform: "signal",
6537
+ senderId,
6538
+ senderName: envelope.sourceName ?? void 0,
6539
+ channelId: isGroup2 ? `group:${dm2.groupInfo.groupId ?? ""}` : senderId,
6540
+ chatType: isGroup2 ? "group" : "direct",
6541
+ text: dm2.message ?? "",
6542
+ timestamp: new Date(
6543
+ envelope.timestamp ?? Date.now()
6544
+ ).toISOString(),
6545
+ media: this.extractMedia(dm2.attachments),
6546
+ raw: entry,
6547
+ dataCategory: "edit",
6548
+ replyTo: {
6549
+ messageId: String(em.targetSentTimestamp ?? ""),
6550
+ text: "",
6551
+ senderId
6552
+ }
6553
+ };
6554
+ await this.emitMessage(normalized2);
6555
+ return;
6556
+ }
6557
+ if (!envelope.dataMessage?.message) return;
6558
+ const dm = envelope.dataMessage;
6559
+ const isGroup = !!dm.groupInfo?.groupId;
6560
+ const normalized = {
6561
+ messageId: String(dm.timestamp ?? randomUUID5()),
6562
+ platform: "signal",
6563
+ senderId,
6564
+ senderName: envelope.sourceName ?? void 0,
6565
+ channelId: isGroup ? `group:${dm.groupInfo.groupId ?? ""}` : senderId,
6566
+ chatType: isGroup ? "group" : "direct",
6567
+ text: dm.message,
6568
+ timestamp: new Date(
6569
+ envelope.timestamp ?? Date.now()
6570
+ ).toISOString(),
6571
+ media: this.extractMedia(dm.attachments),
6572
+ replyTo: dm.quote ? {
6573
+ messageId: String(dm.quote.id ?? ""),
6574
+ text: dm.quote.text ?? "",
6575
+ senderId: dm.quote.author ?? ""
6576
+ } : void 0,
6577
+ raw: entry,
6578
+ dataCategory: "message"
6579
+ };
6580
+ await this.emitMessage(normalized);
6581
+ }
6582
+ async emitMessage(msg) {
6583
+ if (!this.messageHandler) return;
6584
+ try {
6585
+ await this.messageHandler(msg);
6586
+ } catch (err) {
6587
+ console.error("[signal] Message handler error:", err);
6588
+ }
6589
+ }
6590
+ /** Import all Signal contacts */
6591
+ async syncContacts() {
6592
+ if (!this.messageHandler || !this.account) return;
6593
+ const res = await fetch(
6594
+ `${this.baseUrl}/v1/contacts/${encodeURIComponent(this.account)}`,
6595
+ { signal: AbortSignal.timeout(DEFAULT_TIMEOUT_MS) }
6596
+ );
6597
+ if (!res.ok) return;
6598
+ const contacts = await res.json();
6599
+ if (!Array.isArray(contacts)) return;
6600
+ for (const contact of contacts) {
6601
+ const phone = contact.number ?? "";
6602
+ if (!phone) continue;
6603
+ const name = contact.name ?? contact.profileName ?? phone;
6604
+ const normalized = {
6605
+ messageId: randomUUID5(),
6606
+ platform: "signal",
6607
+ senderId: phone,
6608
+ senderName: name,
6609
+ channelId: phone,
6610
+ chatType: "direct",
6611
+ text: "",
6612
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
6613
+ raw: contact,
6614
+ dataCategory: "contact_sync",
6615
+ contactSync: {
6616
+ name,
6617
+ phone,
6618
+ pushName: contact.profileName ?? void 0
6619
+ }
6620
+ };
6621
+ await this.emitMessage(normalized);
6622
+ }
6623
+ console.log(`[signal] Synced ${contacts.length} contacts`);
6624
+ }
6625
+ /** Import all Signal groups */
6626
+ async syncGroups() {
6627
+ if (!this.messageHandler || !this.account) return;
6628
+ const res = await fetch(
6629
+ `${this.baseUrl}/v1/groups/${encodeURIComponent(this.account)}`,
6630
+ { signal: AbortSignal.timeout(DEFAULT_TIMEOUT_MS) }
6631
+ );
6632
+ if (!res.ok) return;
6633
+ const groups = await res.json();
6634
+ if (!Array.isArray(groups)) return;
6635
+ for (const group of groups) {
6636
+ const normalized = {
6637
+ messageId: randomUUID5(),
6638
+ platform: "signal",
6639
+ senderId: `group:${group.id}`,
6640
+ channelId: `group:${group.id}`,
6641
+ chatType: "group",
6642
+ text: "",
6643
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
6644
+ raw: group,
6645
+ dataCategory: "group",
6646
+ groupInfo: {
6647
+ groupId: group.id,
6648
+ groupName: group.name ?? "",
6649
+ participants: group.members ?? [],
6650
+ admins: group.admins ?? [],
6651
+ description: group.description ?? void 0
6652
+ }
6653
+ };
6654
+ await this.emitMessage(normalized);
6655
+ }
6656
+ console.log(`[signal] Synced ${groups.length} groups`);
6657
+ }
6658
+ extractMedia(attachments) {
6659
+ if (!attachments?.length) return void 0;
6660
+ return attachments.map((att) => {
6661
+ const ct = att.contentType ?? "";
6662
+ let type = "document";
6663
+ if (ct.startsWith("image/")) type = "image";
6664
+ else if (ct.startsWith("video/")) type = "video";
6665
+ else if (ct.startsWith("audio/")) type = "audio";
6666
+ return {
6667
+ type,
6668
+ fileName: att.filename ?? void 0,
6669
+ url: att.id ? `${this.baseUrl}/v1/attachments/${att.id}` : void 0
6670
+ };
6671
+ });
6672
+ }
6673
+ };
6674
+ }
6675
+ });
6676
+
6300
6677
  // src/gateway/adapters/telegram.ts
6301
6678
  var telegram_exports = {};
6302
6679
  __export(telegram_exports, {
@@ -6599,7 +6976,7 @@ var slack_exports = {};
6599
6976
  __export(slack_exports, {
6600
6977
  SlackAdapter: () => SlackAdapter
6601
6978
  });
6602
- import { randomUUID as randomUUID5 } from "crypto";
6979
+ import { randomUUID as randomUUID6 } from "crypto";
6603
6980
  var SlackAdapter;
6604
6981
  var init_slack = __esm({
6605
6982
  "src/gateway/adapters/slack.ts"() {
@@ -6641,7 +7018,7 @@ var init_slack = __esm({
6641
7018
  if (event.subtype) return;
6642
7019
  const isGroup = event.channel_type !== "im";
6643
7020
  const normalized = {
6644
- messageId: event.client_msg_id ?? event.ts ?? randomUUID5(),
7021
+ messageId: event.client_msg_id ?? event.ts ?? randomUUID6(),
6645
7022
  platform: "slack",
6646
7023
  senderId: event.user ?? "",
6647
7024
  channelId: event.channel ?? "",
@@ -6703,7 +7080,7 @@ var init_slack = __esm({
6703
7080
  if (!event.text) return;
6704
7081
  const isGroup = event.channel_type !== "im";
6705
7082
  const normalized = {
6706
- messageId: event.ts ?? randomUUID5(),
7083
+ messageId: event.ts ?? randomUUID6(),
6707
7084
  platform: "slack",
6708
7085
  senderId: event.user ?? "",
6709
7086
  senderName: event.user_profile?.display_name ?? event.user_profile?.real_name ?? void 0,
@@ -6750,12 +7127,12 @@ import { execFile } from "child_process";
6750
7127
  import { promisify } from "util";
6751
7128
  import os8 from "os";
6752
7129
  import path11 from "path";
6753
- var execFileAsync, POLL_INTERVAL_MS, MESSAGES_DB_PATH, IMessageAdapter;
7130
+ var execFileAsync, POLL_INTERVAL_MS2, MESSAGES_DB_PATH, IMessageAdapter;
6754
7131
  var init_imessage = __esm({
6755
7132
  "src/gateway/adapters/imessage.ts"() {
6756
7133
  "use strict";
6757
7134
  execFileAsync = promisify(execFile);
6758
- POLL_INTERVAL_MS = 5e3;
7135
+ POLL_INTERVAL_MS2 = 5e3;
6759
7136
  MESSAGES_DB_PATH = path11.join(
6760
7137
  process.env.HOME ?? os8.homedir(),
6761
7138
  "Library/Messages/chat.db"
@@ -6792,7 +7169,7 @@ var init_imessage = __esm({
6792
7169
  console.log("[imessage] Connected via AppleScript bridge");
6793
7170
  this.pollTimer = setInterval(() => {
6794
7171
  void this.pollMessages();
6795
- }, POLL_INTERVAL_MS);
7172
+ }, POLL_INTERVAL_MS2);
6796
7173
  }
6797
7174
  async disconnect() {
6798
7175
  if (this.pollTimer) {
@@ -6926,7 +7303,7 @@ var email_exports = {};
6926
7303
  __export(email_exports, {
6927
7304
  EmailAdapter: () => EmailAdapter
6928
7305
  });
6929
- import { randomUUID as randomUUID6 } from "crypto";
7306
+ import { randomUUID as randomUUID7 } from "crypto";
6930
7307
  import { createTransport } from "nodemailer";
6931
7308
  function extractEmailAddress(from) {
6932
7309
  const match = from.match(/<([^>]+)>/);
@@ -7008,7 +7385,7 @@ var init_email = __esm({
7008
7385
  const senderEmail = extractEmailAddress(from);
7009
7386
  const media = this.extractMedia(payload);
7010
7387
  const normalized = {
7011
- messageId: payload.message_id ?? randomUUID6(),
7388
+ messageId: payload.message_id ?? randomUUID7(),
7012
7389
  platform: "email",
7013
7390
  senderId: senderEmail,
7014
7391
  senderName: extractSenderName(from),
@@ -7069,7 +7446,7 @@ var webhook_exports = {};
7069
7446
  __export(webhook_exports, {
7070
7447
  WebhookAdapter: () => WebhookAdapter
7071
7448
  });
7072
- import { randomUUID as randomUUID7 } from "crypto";
7449
+ import { randomUUID as randomUUID8 } from "crypto";
7073
7450
  function resolvePath(obj, path24) {
7074
7451
  let current = obj;
7075
7452
  for (const segment of path24.split(".")) {
@@ -7125,7 +7502,7 @@ var init_webhook = __esm({
7125
7502
  if (!text || !senderId) return;
7126
7503
  const channelId = this.fieldMap.channelId ? String(resolvePath(rawPayload, this.fieldMap.channelId) ?? senderId) : senderId;
7127
7504
  const senderName = this.fieldMap.senderName ? String(resolvePath(rawPayload, this.fieldMap.senderName) ?? "") || void 0 : void 0;
7128
- const messageId = this.fieldMap.messageId ? String(resolvePath(rawPayload, this.fieldMap.messageId) ?? randomUUID7()) : randomUUID7();
7505
+ const messageId = this.fieldMap.messageId ? String(resolvePath(rawPayload, this.fieldMap.messageId) ?? randomUUID8()) : randomUUID8();
7129
7506
  const timestamp = this.fieldMap.timestamp ? String(resolvePath(rawPayload, this.fieldMap.timestamp) ?? (/* @__PURE__ */ new Date()).toISOString()) : (/* @__PURE__ */ new Date()).toISOString();
7130
7507
  const normalized = {
7131
7508
  messageId,
@@ -10888,7 +11265,7 @@ var init_messaging = __esm({
10888
11265
 
10889
11266
  // src/automation/trigger-engine.ts
10890
11267
  import { readFileSync as readFileSync14, writeFileSync as writeFileSync9, existsSync as existsSync17, mkdirSync as mkdirSync9 } from "fs";
10891
- import { randomUUID as randomUUID8 } from "crypto";
11268
+ import { randomUUID as randomUUID9 } from "crypto";
10892
11269
  import path22 from "path";
10893
11270
  import os14 from "os";
10894
11271
  function substituteTemplate(template, record) {
@@ -11251,6 +11628,7 @@ var init_crm_webhook = __esm({
11251
11628
  import { existsSync as existsSync18, readFileSync as readFileSync15 } from "fs";
11252
11629
  import path23 from "path";
11253
11630
  import os15 from "os";
11631
+ import { fileURLToPath as fileURLToPath3 } from "url";
11254
11632
 
11255
11633
  // src/gateway/webhook-server.ts
11256
11634
  import {
@@ -12448,6 +12826,25 @@ init_employees();
12448
12826
  var CONFIG_DIR = process.env.EXE_GATEWAY_HOME || path23.join(os15.homedir(), ".exe-os");
12449
12827
  var CONFIG_PATH3 = process.env.EXE_GATEWAY_CONFIG || path23.join(CONFIG_DIR, "gateway.json");
12450
12828
  var DEFAULT_PORT = 3100;
12829
+ function validateApiRouterProductionEnv(env = process.env, logger = console) {
12830
+ if (env.NODE_ENV !== "production") return;
12831
+ const apiRouterUrl = env.API_ROUTER_URL;
12832
+ const apiRouterKey = env.API_ROUTER_KEY;
12833
+ const byokEnabled = env.BYOK_ENABLED === "true";
12834
+ const rawAnthropicKey = env.ANTHROPIC_API_KEY;
12835
+ if (byokEnabled) return;
12836
+ if (!apiRouterUrl || !apiRouterKey) {
12837
+ logger.error(
12838
+ "[exe-gateway] FATAL: API_ROUTER_URL and API_ROUTER_KEY are required.\n Customer deploys must route AI requests through the Exe API Router.\n Set API_ROUTER_URL=https://gateway.askexe.com and API_ROUTER_KEY=<your-key>.\n To use your own API keys instead, set BYOK_ENABLED=true."
12839
+ );
12840
+ throw new Error("API_ROUTER_REQUIRED");
12841
+ }
12842
+ if (rawAnthropicKey) {
12843
+ logger.warn(
12844
+ "[exe-gateway] WARNING: ANTHROPIC_API_KEY is set but BYOK_ENABLED is false.\n AI requests will route through the API Router (API_ROUTER_URL), not the raw key.\n To use your own key, set BYOK_ENABLED=true."
12845
+ );
12846
+ }
12847
+ }
12451
12848
  function loadConfig2() {
12452
12849
  if (!existsSync18(CONFIG_PATH3)) {
12453
12850
  console.log(
@@ -12477,6 +12874,11 @@ async function main() {
12477
12874
  console.error(`[exe-gateway] ${err instanceof Error ? err.message : String(err)}`);
12478
12875
  process.exit(1);
12479
12876
  }
12877
+ try {
12878
+ validateApiRouterProductionEnv(process.env);
12879
+ } catch {
12880
+ process.exit(1);
12881
+ }
12480
12882
  const config2 = loadConfig2();
12481
12883
  const port = config2.port ?? DEFAULT_PORT;
12482
12884
  const server = new WebhookServer({
@@ -12510,6 +12912,21 @@ async function main() {
12510
12912
  });
12511
12913
  console.log("[exe-gateway] WhatsApp adapter registered");
12512
12914
  }
12915
+ if (adapters.signal?.enabled) {
12916
+ const { SignalAdapter: SignalAdapter2 } = await Promise.resolve().then(() => (init_signal(), signal_exports));
12917
+ const signal = new SignalAdapter2();
12918
+ gateway.registerAdapter(signal);
12919
+ const signalAccount = adapters.signal.accounts?.[0];
12920
+ platformConfigs.set("signal", {
12921
+ platform: "signal",
12922
+ permissions: { canRead: true, canWrite: true, canExecute: true },
12923
+ credentials: {
12924
+ baseUrl: signalAccount?.signalCliUrl ?? "http://exe-signal-cli:8080",
12925
+ account: signalAccount?.account ?? ""
12926
+ }
12927
+ });
12928
+ console.log("[exe-gateway] Signal adapter registered");
12929
+ }
12513
12930
  if (adapters.telegram?.enabled) {
12514
12931
  const { TelegramAdapter: TelegramAdapter2 } = await Promise.resolve().then(() => (init_telegram(), telegram_exports));
12515
12932
  const telegram = new TelegramAdapter2();
@@ -12600,9 +13017,18 @@ async function main() {
12600
13017
  await server.start();
12601
13018
  console.log(`[exe-gateway] Ready on port ${port}`);
12602
13019
  }
12603
- main().catch((err) => {
12604
- const msg = err instanceof Error ? err.message : String(err);
12605
- console.error(`[exe-gateway] Fatal: ${msg}`);
12606
- if (process.env.DEBUG) console.error(err);
12607
- process.exit(1);
12608
- });
13020
+ function isDirectExecution() {
13021
+ const entrypoint = process.argv[1];
13022
+ return Boolean(entrypoint && path23.resolve(entrypoint) === fileURLToPath3(import.meta.url));
13023
+ }
13024
+ if (isDirectExecution()) {
13025
+ main().catch((err) => {
13026
+ const msg = err instanceof Error ? err.message : String(err);
13027
+ console.error(`[exe-gateway] Fatal: ${msg}`);
13028
+ if (process.env.DEBUG) console.error(err);
13029
+ process.exit(1);
13030
+ });
13031
+ }
13032
+ export {
13033
+ validateApiRouterProductionEnv
13034
+ };
@@ -674,7 +674,7 @@ async function ensureCompatibilityViews(prisma) {
674
674
  for (const mapping of VIEW_MAPPINGS) {
675
675
  const relation = mapping.source.replace(/"/g, "");
676
676
  const rows = await prisma.$queryRawUnsafe(
677
- "SELECT to_regclass($1) AS regclass",
677
+ "SELECT to_regclass($1)::text AS regclass",
678
678
  relation
679
679
  );
680
680
  if (!rows[0]?.regclass) {
@@ -644,7 +644,7 @@ async function ensureCompatibilityViews(prisma) {
644
644
  for (const mapping of VIEW_MAPPINGS) {
645
645
  const relation = mapping.source.replace(/"/g, "");
646
646
  const rows = await prisma.$queryRawUnsafe(
647
- "SELECT to_regclass($1) AS regclass",
647
+ "SELECT to_regclass($1)::text AS regclass",
648
648
  relation
649
649
  );
650
650
  if (!rows[0]?.regclass) {
@@ -995,7 +995,7 @@ async function ensureCompatibilityViews(prisma) {
995
995
  for (const mapping of VIEW_MAPPINGS) {
996
996
  const relation = mapping.source.replace(/"/g, "");
997
997
  const rows = await prisma.$queryRawUnsafe(
998
- "SELECT to_regclass($1) AS regclass",
998
+ "SELECT to_regclass($1)::text AS regclass",
999
999
  relation
1000
1000
  );
1001
1001
  if (!rows[0]?.regclass) {
@@ -1,6 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
  var __defProp = Object.defineProperty;
3
3
  var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
5
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
6
+ }) : x)(function(x) {
7
+ if (typeof require !== "undefined") return require.apply(this, arguments);
8
+ throw Error('Dynamic require of "' + x + '" is not supported');
9
+ });
4
10
  var __esm = (fn, res) => function __init() {
5
11
  return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
6
12
  };
@@ -949,7 +955,7 @@ async function ensureCompatibilityViews(prisma) {
949
955
  for (const mapping of VIEW_MAPPINGS) {
950
956
  const relation = mapping.source.replace(/"/g, "");
951
957
  const rows = await prisma.$queryRawUnsafe(
952
- "SELECT to_regclass($1) AS regclass",
958
+ "SELECT to_regclass($1)::text AS regclass",
953
959
  relation
954
960
  );
955
961
  if (!rows[0]?.regclass) {
@@ -1303,8 +1309,29 @@ function findPackageRoot() {
1303
1309
  }
1304
1310
  return null;
1305
1311
  }
1312
+ function getAvailableMemoryGB() {
1313
+ if (process.platform === "darwin") {
1314
+ try {
1315
+ const { execSync: execSync2 } = __require("child_process");
1316
+ const vmstat = execSync2("vm_stat", { encoding: "utf8" });
1317
+ const pageSize = 16384;
1318
+ const pageSizeMatch = vmstat.match(/page size of (\d+) bytes/);
1319
+ const actualPageSize = pageSizeMatch ? parseInt(pageSizeMatch[1], 10) : pageSize;
1320
+ const free = vmstat.match(/Pages free:\s+(\d+)/);
1321
+ const inactive = vmstat.match(/Pages inactive:\s+(\d+)/);
1322
+ const speculative = vmstat.match(/Pages speculative:\s+(\d+)/);
1323
+ const freePages = free ? parseInt(free[1], 10) : 0;
1324
+ const inactivePages = inactive ? parseInt(inactive[1], 10) : 0;
1325
+ const speculativePages = speculative ? parseInt(speculative[1], 10) : 0;
1326
+ return (freePages + inactivePages + speculativePages) * actualPageSize / (1024 * 1024 * 1024);
1327
+ } catch {
1328
+ return os5.freemem() / (1024 * 1024 * 1024);
1329
+ }
1330
+ }
1331
+ return os5.freemem() / (1024 * 1024 * 1024);
1332
+ }
1306
1333
  function spawnDaemon() {
1307
- const freeGB = os5.freemem() / (1024 * 1024 * 1024);
1334
+ const freeGB = getAvailableMemoryGB();
1308
1335
  const totalGB = os5.totalmem() / (1024 * 1024 * 1024);
1309
1336
  if (totalGB <= 8) {
1310
1337
  process.stderr.write(
@@ -1313,9 +1340,9 @@ function spawnDaemon() {
1313
1340
  );
1314
1341
  return;
1315
1342
  }
1316
- if (totalGB <= 16 && freeGB < 4) {
1343
+ if (totalGB <= 16 && freeGB < 2) {
1317
1344
  process.stderr.write(
1318
- `[exed-client] SKIP: low memory (${freeGB.toFixed(1)}GB free / ${totalGB.toFixed(0)}GB total). Embedding daemon not started \u2014 using keyword search only.
1345
+ `[exed-client] SKIP: low memory (${freeGB.toFixed(1)}GB available / ${totalGB.toFixed(0)}GB total). Embedding daemon not started \u2014 using keyword search only.
1319
1346
  `
1320
1347
  );
1321
1348
  return;