@hasna/assistants 1.1.6 → 1.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/cli.js +8136 -1942
  2. package/dist/lib.js +3442 -120
  3. package/package.json +1 -1
package/dist/lib.js CHANGED
@@ -6918,6 +6918,34 @@ function mergeConfig(base, override) {
6918
6918
  ...override.webhooks?.security || {}
6919
6919
  }
6920
6920
  },
6921
+ channels: {
6922
+ ...base.channels || {},
6923
+ ...override.channels || {},
6924
+ injection: {
6925
+ ...base.channels?.injection || {},
6926
+ ...override.channels?.injection || {}
6927
+ },
6928
+ storage: {
6929
+ ...base.channels?.storage || {},
6930
+ ...override.channels?.storage || {}
6931
+ }
6932
+ },
6933
+ telephony: {
6934
+ ...base.telephony || {},
6935
+ ...override.telephony || {},
6936
+ injection: {
6937
+ ...base.telephony?.injection || {},
6938
+ ...override.telephony?.injection || {}
6939
+ },
6940
+ storage: {
6941
+ ...base.telephony?.storage || {},
6942
+ ...override.telephony?.storage || {}
6943
+ },
6944
+ voice: {
6945
+ ...base.telephony?.voice || {},
6946
+ ...override.telephony?.voice || {}
6947
+ }
6948
+ },
6921
6949
  memory: {
6922
6950
  ...base.memory || {},
6923
6951
  ...override.memory || {},
@@ -6951,6 +6979,36 @@ function mergeConfig(base, override) {
6951
6979
  ...override.input?.paste?.thresholds || {}
6952
6980
  }
6953
6981
  }
6982
+ },
6983
+ budget: {
6984
+ ...base.budget || {},
6985
+ ...override.budget || {},
6986
+ session: {
6987
+ ...base.budget?.session || {},
6988
+ ...override.budget?.session || {}
6989
+ },
6990
+ assistant: {
6991
+ ...base.budget?.assistant || {},
6992
+ ...override.budget?.assistant || {}
6993
+ },
6994
+ swarm: {
6995
+ ...base.budget?.swarm || {},
6996
+ ...override.budget?.swarm || {}
6997
+ },
6998
+ project: {
6999
+ ...base.budget?.project || {},
7000
+ ...override.budget?.project || {}
7001
+ }
7002
+ },
7003
+ guardrails: {
7004
+ ...base.guardrails || {},
7005
+ ...override.guardrails || {}
7006
+ },
7007
+ capabilities: {
7008
+ ...base.capabilities || {},
7009
+ ...override.capabilities || {},
7010
+ allowedTools: override.capabilities?.allowedTools ?? base.capabilities?.allowedTools,
7011
+ deniedTools: override.capabilities?.deniedTools ?? base.capabilities?.deniedTools
6954
7012
  }
6955
7013
  };
6956
7014
  }
@@ -7274,6 +7332,33 @@ var init_config = __esm(async () => {
7274
7332
  rateLimitPerMinute: 60
7275
7333
  }
7276
7334
  },
7335
+ channels: {
7336
+ enabled: false,
7337
+ injection: {
7338
+ enabled: true,
7339
+ maxPerTurn: 10
7340
+ },
7341
+ storage: {
7342
+ maxMessagesPerChannel: 5000,
7343
+ maxAgeDays: 90
7344
+ }
7345
+ },
7346
+ telephony: {
7347
+ enabled: false,
7348
+ injection: {
7349
+ enabled: true,
7350
+ maxPerTurn: 5
7351
+ },
7352
+ storage: {
7353
+ maxCallLogs: 1000,
7354
+ maxSmsLogs: 5000,
7355
+ maxAgeDays: 90
7356
+ },
7357
+ voice: {
7358
+ recordCalls: false,
7359
+ maxCallDurationSeconds: 3600
7360
+ }
7361
+ },
7277
7362
  memory: {
7278
7363
  enabled: true,
7279
7364
  injection: {
@@ -7479,6 +7564,35 @@ var init_models2 = __esm(() => {
7479
7564
  ];
7480
7565
  });
7481
7566
 
7567
+ // ../core/src/utils/atomic-write.ts
7568
+ import { writeFileSync as writeFileSync7, renameSync, unlinkSync as unlinkSync2 } from "fs";
7569
+ import { writeFile as writeFile3, rename, unlink as unlink3 } from "fs/promises";
7570
+ function atomicWriteFileSync(path, data) {
7571
+ const tmpPath = `${path}.${process.pid}.tmp`;
7572
+ try {
7573
+ writeFileSync7(tmpPath, data);
7574
+ renameSync(tmpPath, path);
7575
+ } catch (error) {
7576
+ try {
7577
+ unlinkSync2(tmpPath);
7578
+ } catch {}
7579
+ throw error;
7580
+ }
7581
+ }
7582
+ async function atomicWriteFile(path, data) {
7583
+ const tmpPath = `${path}.${process.pid}.tmp`;
7584
+ try {
7585
+ await writeFile3(tmpPath, data);
7586
+ await rename(tmpPath, path);
7587
+ } catch (error) {
7588
+ try {
7589
+ await unlink3(tmpPath);
7590
+ } catch {}
7591
+ throw error;
7592
+ }
7593
+ }
7594
+ var init_atomic_write = () => {};
7595
+
7482
7596
  // ../../node_modules/.bun/fast-glob@3.3.3/node_modules/fast-glob/out/utils/array.js
7483
7597
  var require_array = __commonJS((exports) => {
7484
7598
  Object.defineProperty(exports, "__esModule", { value: true });
@@ -12946,8 +13060,11 @@ var init_defaults = __esm(() => {
12946
13060
  function matchPattern(pattern, value) {
12947
13061
  if (pattern.startsWith("/") && pattern.endsWith("/")) {
12948
13062
  try {
12949
- const regex2 = new RegExp(pattern.slice(1, -1));
12950
- return regex2.test(value);
13063
+ const regexStr = pattern.slice(1, -1);
13064
+ if (regexStr.length > 500)
13065
+ return false;
13066
+ const regex2 = new RegExp(regexStr);
13067
+ return regex2.test(value.slice(0, 1e4));
12951
13068
  } catch {
12952
13069
  return false;
12953
13070
  }
@@ -12967,8 +13084,13 @@ function evaluateCondition(condition, context) {
12967
13084
  case "input_matches": {
12968
13085
  const input = JSON.stringify(context.toolInput || {});
12969
13086
  try {
12970
- const regex = new RegExp(String(condition.value));
12971
- result = regex.test(input);
13087
+ const regexStr = String(condition.value);
13088
+ if (regexStr.length > 500) {
13089
+ result = false;
13090
+ break;
13091
+ }
13092
+ const regex = new RegExp(regexStr);
13093
+ result = regex.test(input.slice(0, 1e4));
12972
13094
  } catch {
12973
13095
  result = false;
12974
13096
  }
@@ -13167,7 +13289,7 @@ var init_evaluator = __esm(() => {
13167
13289
  });
13168
13290
 
13169
13291
  // ../core/src/guardrails/store.ts
13170
- import { readFileSync as readFileSync7, writeFileSync as writeFileSync8, existsSync as existsSync14, mkdirSync as mkdirSync8 } from "fs";
13292
+ import { readFileSync as readFileSync7, writeFileSync as writeFileSync9, existsSync as existsSync14, mkdirSync as mkdirSync8 } from "fs";
13171
13293
  import { join as join22, dirname as dirname10 } from "path";
13172
13294
  import { createHash as createHash3 } from "crypto";
13173
13295
  function generatePolicyId(name, scope) {
@@ -13219,7 +13341,7 @@ class GuardrailsStore {
13219
13341
  mkdirSync8(dir, { recursive: true });
13220
13342
  }
13221
13343
  ensurePolicyIds(config);
13222
- writeFileSync8(filePath, JSON.stringify({ guardrails: config }, null, 2), "utf-8");
13344
+ writeFileSync9(filePath, JSON.stringify({ guardrails: config }, null, 2), "utf-8");
13223
13345
  }
13224
13346
  loadAll() {
13225
13347
  const userConfig = this.loadFrom("user");
@@ -51348,11 +51470,10 @@ import { homedir as homedir11 } from "os";
51348
51470
  import {
51349
51471
  existsSync as existsSync15,
51350
51472
  mkdirSync as mkdirSync9,
51351
- writeFileSync as writeFileSync9,
51352
51473
  readFileSync as readFileSync8,
51353
51474
  readdirSync as readdirSync6,
51354
51475
  rmSync,
51355
- renameSync
51476
+ renameSync as renameSync2
51356
51477
  } from "fs";
51357
51478
 
51358
51479
  class SharedWorkspaceManager {
@@ -51376,7 +51497,7 @@ class SharedWorkspaceManager {
51376
51497
  const oldAgentsDir = join23(wsPath, "agents");
51377
51498
  const newAssistantsDir = join23(wsPath, "assistants");
51378
51499
  if (existsSync15(oldAgentsDir) && !existsSync15(newAssistantsDir)) {
51379
- renameSync(oldAgentsDir, newAssistantsDir);
51500
+ renameSync2(oldAgentsDir, newAssistantsDir);
51380
51501
  }
51381
51502
  }
51382
51503
  } catch {}
@@ -51404,7 +51525,7 @@ class SharedWorkspaceManager {
51404
51525
  for (const assistantId of workspace.participants) {
51405
51526
  mkdirSync9(join23(wsPath, "assistants", assistantId), { recursive: true });
51406
51527
  }
51407
- writeFileSync9(this.getMetadataPath(id), JSON.stringify(workspace, null, 2));
51528
+ atomicWriteFileSync(this.getMetadataPath(id), JSON.stringify(workspace, null, 2));
51408
51529
  return workspace;
51409
51530
  }
51410
51531
  join(workspaceId, assistantId) {
@@ -51415,7 +51536,7 @@ class SharedWorkspaceManager {
51415
51536
  if (!workspace.participants.includes(assistantId)) {
51416
51537
  workspace.participants.push(assistantId);
51417
51538
  workspace.updatedAt = Date.now();
51418
- writeFileSync9(this.getMetadataPath(workspaceId), JSON.stringify(workspace, null, 2));
51539
+ atomicWriteFileSync(this.getMetadataPath(workspaceId), JSON.stringify(workspace, null, 2));
51419
51540
  }
51420
51541
  const assistantDir = join23(this.getWorkspacePath(workspaceId), "assistants", assistantId);
51421
51542
  if (!existsSync15(assistantDir)) {
@@ -51467,7 +51588,7 @@ class SharedWorkspaceManager {
51467
51588
  if (workspace) {
51468
51589
  workspace.status = "archived";
51469
51590
  workspace.updatedAt = Date.now();
51470
- writeFileSync9(this.getMetadataPath(workspaceId), JSON.stringify(workspace, null, 2));
51591
+ atomicWriteFileSync(this.getMetadataPath(workspaceId), JSON.stringify(workspace, null, 2));
51471
51592
  }
51472
51593
  }
51473
51594
  delete(workspaceId) {
@@ -51479,6 +51600,7 @@ class SharedWorkspaceManager {
51479
51600
  }
51480
51601
  var init_shared = __esm(() => {
51481
51602
  init_src2();
51603
+ init_atomic_write();
51482
51604
  });
51483
51605
 
51484
51606
  // ../core/src/workspace/index.ts
@@ -51534,7 +51656,7 @@ var init_defaults2 = __esm(() => {
51534
51656
  // ../core/src/budget/tracker.ts
51535
51657
  import { join as join24, dirname as dirname11 } from "path";
51536
51658
  import { homedir as homedir12 } from "os";
51537
- import { existsSync as existsSync16, mkdirSync as mkdirSync10, writeFileSync as writeFileSync10, readFileSync as readFileSync9 } from "fs";
51659
+ import { existsSync as existsSync16, mkdirSync as mkdirSync10, readFileSync as readFileSync9 } from "fs";
51538
51660
  function createEmptyUsage() {
51539
51661
  const now2 = new Date().toISOString();
51540
51662
  return {
@@ -51615,7 +51737,7 @@ class BudgetTracker {
51615
51737
  if (!existsSync16(stateDir)) {
51616
51738
  mkdirSync10(stateDir, { recursive: true });
51617
51739
  }
51618
- writeFileSync10(statePath, JSON.stringify(usage, null, 2));
51740
+ atomicWriteFileSync(statePath, JSON.stringify(usage, null, 2));
51619
51741
  } catch {}
51620
51742
  }
51621
51743
  saveState() {
@@ -51634,7 +51756,7 @@ class BudgetTracker {
51634
51756
  swarm: this.swarmUsage,
51635
51757
  projects: Object.fromEntries(this.projectUsages)
51636
51758
  };
51637
- writeFileSync10(statePath, JSON.stringify(state, null, 2));
51759
+ atomicWriteFileSync(statePath, JSON.stringify(state, null, 2));
51638
51760
  } catch {}
51639
51761
  }
51640
51762
  isEnabled() {
@@ -51943,6 +52065,7 @@ class BudgetTracker {
51943
52065
  var PERSISTENCE_VERSION = 2;
51944
52066
  var init_tracker = __esm(() => {
51945
52067
  init_defaults2();
52068
+ init_atomic_write();
51946
52069
  });
51947
52070
 
51948
52071
  // ../core/src/budget/tools.ts
@@ -110816,7 +110939,7 @@ await init_runtime();
110816
110939
 
110817
110940
  // ../core/src/agent/loop.ts
110818
110941
  init_src2();
110819
- import { join as join46 } from "path";
110942
+ import { join as join48 } from "path";
110820
110943
 
110821
110944
  // ../core/src/agent/context.ts
110822
110945
  init_src2();
@@ -121524,9 +121647,10 @@ Path: ${path}`;
121524
121647
  init_src2();
121525
121648
 
121526
121649
  // ../core/src/scheduler/store.ts
121650
+ init_atomic_write();
121527
121651
  await init_config();
121528
121652
  import { join as join15 } from "path";
121529
- import { mkdir as mkdir6, readdir as readdir2, readFile as readFile5, unlink as unlink3, writeFile as writeFile3, open as open2 } from "fs/promises";
121653
+ import { mkdir as mkdir6, readdir as readdir2, readFile as readFile5, unlink as unlink4, open as open2 } from "fs/promises";
121530
121654
  var DEFAULT_LOCK_TTL_MS = 10 * 60 * 1000;
121531
121655
  var SAFE_ID_PATTERN4 = /^[a-zA-Z0-9_-]+$/;
121532
121656
  function schedulesDir(cwd) {
@@ -121579,7 +121703,7 @@ async function saveSchedule(cwd, schedule) {
121579
121703
  if (!path) {
121580
121704
  throw new Error(`Invalid schedule id: ${schedule.id}`);
121581
121705
  }
121582
- await writeFile3(path, JSON.stringify(schedule, null, 2), "utf-8");
121706
+ await atomicWriteFile(path, JSON.stringify(schedule, null, 2));
121583
121707
  }
121584
121708
  async function getSchedule(cwd, id) {
121585
121709
  try {
@@ -121597,7 +121721,7 @@ async function deleteSchedule(cwd, id) {
121597
121721
  const path = schedulePath(cwd, id);
121598
121722
  if (!path)
121599
121723
  return false;
121600
- await unlink3(path);
121724
+ await unlink4(path);
121601
121725
  return true;
121602
121726
  } catch {
121603
121727
  return false;
@@ -121676,9 +121800,12 @@ async function updateSchedule(cwd, id, updater) {
121676
121800
  return null;
121677
121801
  }
121678
121802
  }
121679
- async function acquireScheduleLock(cwd, id, ownerId, ttlMs = DEFAULT_LOCK_TTL_MS, allowRetry = true) {
121803
+ var MAX_LOCK_RETRIES = 2;
121804
+ async function acquireScheduleLock(cwd, id, ownerId, ttlMs = DEFAULT_LOCK_TTL_MS, retryDepth = 0) {
121680
121805
  if (!isSafeId3(id))
121681
121806
  return false;
121807
+ if (retryDepth >= MAX_LOCK_RETRIES)
121808
+ return false;
121682
121809
  await ensureDirs(cwd);
121683
121810
  const path = lockPath(cwd, id);
121684
121811
  const now2 = Date.now();
@@ -121694,14 +121821,14 @@ async function acquireScheduleLock(cwd, id, ownerId, ttlMs = DEFAULT_LOCK_TTL_MS
121694
121821
  const updatedAt = lock?.updatedAt || lock?.createdAt || 0;
121695
121822
  const ttl = lock?.ttlMs ?? ttlMs;
121696
121823
  if (now2 - updatedAt > ttl) {
121697
- await unlink3(path);
121698
- return acquireScheduleLock(cwd, id, ownerId, ttlMs, false);
121824
+ await unlink4(path);
121825
+ return acquireScheduleLock(cwd, id, ownerId, ttlMs, retryDepth + 1);
121699
121826
  }
121700
121827
  } catch {
121701
- if (allowRetry) {
121828
+ if (retryDepth < MAX_LOCK_RETRIES) {
121702
121829
  try {
121703
- await unlink3(path);
121704
- return acquireScheduleLock(cwd, id, ownerId, ttlMs, false);
121830
+ await unlink4(path);
121831
+ return acquireScheduleLock(cwd, id, ownerId, ttlMs, retryDepth + 1);
121705
121832
  } catch {
121706
121833
  return false;
121707
121834
  }
@@ -121718,7 +121845,7 @@ async function releaseScheduleLock(cwd, id, ownerId) {
121718
121845
  const raw = await readFile5(path, "utf-8");
121719
121846
  const lock = JSON.parse(raw);
121720
121847
  if (lock?.ownerId === ownerId) {
121721
- await unlink3(path);
121848
+ await unlink4(path);
121722
121849
  }
121723
121850
  } catch {}
121724
121851
  }
@@ -121731,7 +121858,7 @@ async function refreshScheduleLock(cwd, id, ownerId) {
121731
121858
  const lock = JSON.parse(raw);
121732
121859
  if (lock?.ownerId === ownerId) {
121733
121860
  const updated = { ...lock, updatedAt: Date.now() };
121734
- await writeFile3(path, JSON.stringify(updated, null, 2), "utf-8");
121861
+ await atomicWriteFile(path, JSON.stringify(updated, null, 2));
121735
121862
  }
121736
121863
  } catch {}
121737
121864
  }
@@ -122079,7 +122206,7 @@ class SchedulerTool {
122079
122206
  // ../core/src/tools/image.ts
122080
122207
  init_src2();
122081
122208
  await init_runtime();
122082
- import { existsSync as existsSync12, writeFileSync as writeFileSync7, unlinkSync as unlinkSync2 } from "fs";
122209
+ import { existsSync as existsSync12, writeFileSync as writeFileSync8, unlinkSync as unlinkSync3 } from "fs";
122083
122210
  import { tmpdir } from "os";
122084
122211
  import { join as join16 } from "path";
122085
122212
  import { homedir as homedir8 } from "os";
@@ -122206,7 +122333,7 @@ class ImageDisplayTool {
122206
122333
  }
122207
122334
  const ext = contentType.split("/")[1]?.split(";")[0] || "png";
122208
122335
  tempFile = join16(tmpdir(), `assistants-image-${generateId()}.${ext}`);
122209
- writeFileSync7(tempFile, buffer);
122336
+ writeFileSync8(tempFile, buffer);
122210
122337
  localPath = tempFile;
122211
122338
  } catch (error) {
122212
122339
  if (error instanceof Error && error.name === "AbortError") {
@@ -122245,7 +122372,7 @@ class ImageDisplayTool {
122245
122372
  } finally {
122246
122373
  if (tempFile && existsSync12(tempFile)) {
122247
122374
  try {
122248
- unlinkSync2(tempFile);
122375
+ unlinkSync3(tempFile);
122249
122376
  } catch {}
122250
122377
  }
122251
122378
  }
@@ -123492,7 +123619,7 @@ init_src2();
123492
123619
  // ../core/src/jobs/job-store.ts
123493
123620
  await init_config();
123494
123621
  import { join as join20 } from "path";
123495
- import { mkdir as mkdir8, readdir as readdir3, readFile as readFile7, unlink as unlink4, writeFile as writeFile5 } from "fs/promises";
123622
+ import { mkdir as mkdir8, readdir as readdir3, readFile as readFile7, unlink as unlink5, writeFile as writeFile5 } from "fs/promises";
123496
123623
  var SAFE_ID_PATTERN5 = /^[a-zA-Z0-9_-]+$/;
123497
123624
  function jobsDir() {
123498
123625
  return join20(getConfigDir(), "jobs");
@@ -123535,7 +123662,7 @@ async function deleteJob(id) {
123535
123662
  const path = jobPath(id);
123536
123663
  if (!path)
123537
123664
  return false;
123538
- await unlink4(path);
123665
+ await unlink5(path);
123539
123666
  return true;
123540
123667
  } catch {
123541
123668
  return false;
@@ -124181,9 +124308,15 @@ async function getTasks(cwd) {
124181
124308
  const data = await loadTaskStore(cwd);
124182
124309
  return data.tasks;
124183
124310
  }
124184
- async function getTask(cwd, id) {
124311
+ async function resolveTaskId(cwd, idOrPrefix, filter) {
124185
124312
  const data = await loadTaskStore(cwd);
124186
- return data.tasks.find((t) => t.id === id) || null;
124313
+ const candidates = filter ? data.tasks.filter(filter) : data.tasks;
124314
+ const exact = candidates.find((t) => t.id === idOrPrefix);
124315
+ if (exact) {
124316
+ return { task: exact, matches: [exact] };
124317
+ }
124318
+ const matches = candidates.filter((t) => t.id.startsWith(idOrPrefix));
124319
+ return { task: matches.length === 1 ? matches[0] : null, matches };
124187
124320
  }
124188
124321
  function calculateNextRunAt(recurrence, fromTime) {
124189
124322
  if (recurrence.endAt && fromTime >= recurrence.endAt) {
@@ -124484,7 +124617,10 @@ class BuiltinCommands {
124484
124617
  loader.register(this.jobsCommand());
124485
124618
  loader.register(this.messagesCommand());
124486
124619
  loader.register(this.webhooksCommand());
124620
+ loader.register(this.channelsCommand());
124621
+ loader.register(this.phoneCommand());
124487
124622
  loader.register(this.tasksCommand());
124623
+ loader.register(this.setupCommand());
124488
124624
  loader.register(this.exitCommand());
124489
124625
  }
124490
124626
  voiceCommand() {
@@ -124579,10 +124715,10 @@ Usage: \`/say <text>\`
124579
124715
  }
124580
124716
  try {
124581
124717
  await context.speak(text);
124582
- context.emit("done");
124583
124718
  } catch (error) {
124584
124719
  context.emit("error", error instanceof Error ? error.message : String(error));
124585
124720
  }
124721
+ context.emit("done");
124586
124722
  return { handled: true };
124587
124723
  }
124588
124724
  };
@@ -124626,6 +124762,7 @@ Recording audio via microphone...
124626
124762
  return { handled: false, prompt: transcript };
124627
124763
  } catch (error) {
124628
124764
  context.emit("error", error instanceof Error ? error.message : String(error));
124765
+ context.emit("done");
124629
124766
  return { handled: true };
124630
124767
  }
124631
124768
  }
@@ -125220,7 +125357,7 @@ Usage: /identity create --template <name>
125220
125357
  }
125221
125358
  if (action === "edit") {
125222
125359
  context.emit("done");
125223
- return { handled: true, showPanel: "identity", panelInitialValue: `edit:${match.id}` };
125360
+ return { handled: true, showPanel: "identity", panelValue: `edit:${match.id}` };
125224
125361
  }
125225
125362
  context.emit("text", `
125226
125363
  ## Identity Details
@@ -126282,7 +126419,7 @@ To enable:
126282
126419
  context.emit("done");
126283
126420
  return { handled: true };
126284
126421
  }
126285
- const parts = args.trim().split(/\s+/);
126422
+ const parts = splitArgs(args.trim());
126286
126423
  const subcommand = parts[0]?.toLowerCase() || "";
126287
126424
  if (!subcommand || subcommand === "ui") {
126288
126425
  context.emit("done");
@@ -126614,8 +126751,14 @@ Copy these to your shell or .env file.
126614
126751
  context.emit("done");
126615
126752
  return { handled: true };
126616
126753
  }
126617
- context.emit("text", `Job ${job.id} marked for cancellation. Use job_cancel tool for full cancellation.
126754
+ const deleted = await deleteJob(job.id);
126755
+ if (deleted) {
126756
+ context.emit("text", `Job ${job.id} cancelled and removed.
126757
+ `);
126758
+ } else {
126759
+ context.emit("text", `Failed to cancel job ${job.id}.
126618
126760
  `);
126761
+ }
126619
126762
  context.emit("done");
126620
126763
  return { handled: true };
126621
126764
  }
@@ -126830,6 +126973,15 @@ To enable:
126830
126973
  return { handled: true };
126831
126974
  }
126832
126975
  try {
126976
+ const email = await inboxManager.read(emailId);
126977
+ if (email && email.attachments) {
126978
+ if (index >= email.attachments.length) {
126979
+ context.emit("text", `Invalid attachment index. Email has ${email.attachments.length} attachment(s) (0-${email.attachments.length - 1}).
126980
+ `);
126981
+ context.emit("done");
126982
+ return { handled: true };
126983
+ }
126984
+ }
126833
126985
  const path = await inboxManager.downloadAttachment(emailId, index);
126834
126986
  context.emit("text", `Downloaded to: ${path}
126835
126987
  `);
@@ -127485,6 +127637,477 @@ Configure the external source with the URL and secret above.
127485
127637
  context.emit("text", `Unknown command: ${subcommand}
127486
127638
  `);
127487
127639
  context.emit("text", `Use /webhooks help for available commands.
127640
+ `);
127641
+ context.emit("done");
127642
+ return { handled: true };
127643
+ }
127644
+ };
127645
+ }
127646
+ channelsCommand() {
127647
+ return {
127648
+ name: "channels",
127649
+ description: "Manage channels for agent collaboration",
127650
+ builtin: true,
127651
+ selfHandled: true,
127652
+ content: "",
127653
+ handler: async (args, context) => {
127654
+ const trimmed = args.trim();
127655
+ const [subcommand, ...rest] = trimmed.split(/\s+/);
127656
+ const subArgs = rest.join(" ");
127657
+ if (!subcommand || subcommand === "ui") {
127658
+ context.emit("done");
127659
+ return { handled: true, showPanel: "channels" };
127660
+ }
127661
+ const manager = context.getChannelsManager?.();
127662
+ if (!manager) {
127663
+ context.emit("text", `Channels are not enabled. Set channels.enabled: true in config.
127664
+ `);
127665
+ context.emit("done");
127666
+ return { handled: true };
127667
+ }
127668
+ if (subcommand === "list") {
127669
+ try {
127670
+ const channels = manager.listChannels();
127671
+ if (channels.length === 0) {
127672
+ context.emit("text", `No channels exist. Use /channels create <name> to create one.
127673
+ `);
127674
+ } else {
127675
+ context.emit("text", `Channels (${channels.length}):
127676
+
127677
+ `);
127678
+ for (const ch of channels) {
127679
+ const unread = ch.unreadCount > 0 ? ` (${ch.unreadCount} unread)` : "";
127680
+ context.emit("text", ` #${ch.name}${unread}
127681
+ `);
127682
+ if (ch.description) {
127683
+ context.emit("text", ` ${ch.description}
127684
+ `);
127685
+ }
127686
+ context.emit("text", ` Members: ${ch.memberCount} | Last: ${ch.lastMessagePreview || "no messages"}
127687
+ `);
127688
+ }
127689
+ }
127690
+ } catch (error) {
127691
+ context.emit("text", `Error: ${error instanceof Error ? error.message : String(error)}
127692
+ `);
127693
+ }
127694
+ context.emit("done");
127695
+ return { handled: true };
127696
+ }
127697
+ if (subcommand === "create") {
127698
+ const parts = splitArgs(subArgs);
127699
+ const name = parts[0];
127700
+ const description = parts.slice(1).join(" ") || undefined;
127701
+ if (!name) {
127702
+ context.emit("text", `Usage: /channels create <name> [description]
127703
+ `);
127704
+ context.emit("text", `Example: /channels create general "Team discussion"
127705
+ `);
127706
+ context.emit("done");
127707
+ return { handled: true };
127708
+ }
127709
+ try {
127710
+ const result = manager.createChannel(name, description);
127711
+ if (result.success) {
127712
+ context.emit("text", `Channel created!
127713
+
127714
+ `);
127715
+ context.emit("text", ` Name: #${name}
127716
+ `);
127717
+ context.emit("text", ` ID: ${result.channelId}
127718
+ `);
127719
+ if (description) {
127720
+ context.emit("text", ` Desc: ${description}
127721
+ `);
127722
+ }
127723
+ } else {
127724
+ context.emit("text", `Error: ${result.message}
127725
+ `);
127726
+ }
127727
+ } catch (error) {
127728
+ context.emit("text", `Error: ${error instanceof Error ? error.message : String(error)}
127729
+ `);
127730
+ }
127731
+ context.emit("done");
127732
+ return { handled: true };
127733
+ }
127734
+ if (subcommand === "join") {
127735
+ const channel = subArgs.trim();
127736
+ if (!channel) {
127737
+ context.emit("text", `Usage: /channels join <channel-name>
127738
+ `);
127739
+ context.emit("done");
127740
+ return { handled: true };
127741
+ }
127742
+ const result = manager.join(channel);
127743
+ context.emit("text", `${result.success ? result.message : `Error: ${result.message}`}
127744
+ `);
127745
+ context.emit("done");
127746
+ return { handled: true };
127747
+ }
127748
+ if (subcommand === "leave") {
127749
+ const channel = subArgs.trim();
127750
+ if (!channel) {
127751
+ context.emit("text", `Usage: /channels leave <channel-name>
127752
+ `);
127753
+ context.emit("done");
127754
+ return { handled: true };
127755
+ }
127756
+ const result = manager.leave(channel);
127757
+ context.emit("text", `${result.success ? result.message : `Error: ${result.message}`}
127758
+ `);
127759
+ context.emit("done");
127760
+ return { handled: true };
127761
+ }
127762
+ if (subcommand === "send") {
127763
+ const parts = splitArgs(subArgs);
127764
+ const channel = parts[0];
127765
+ const message = parts.slice(1).join(" ");
127766
+ if (!channel || !message) {
127767
+ context.emit("text", `Usage: /channels send <channel> <message>
127768
+ `);
127769
+ context.emit("done");
127770
+ return { handled: true };
127771
+ }
127772
+ const result = manager.send(channel, message);
127773
+ context.emit("text", `${result.success ? result.message : `Error: ${result.message}`}
127774
+ `);
127775
+ context.emit("done");
127776
+ return { handled: true };
127777
+ }
127778
+ if (subcommand === "read") {
127779
+ const parts = subArgs.trim().split(/\s+/);
127780
+ const channel = parts[0];
127781
+ const limit = parts[1] ? parseInt(parts[1], 10) : 20;
127782
+ if (!channel) {
127783
+ context.emit("text", `Usage: /channels read <channel> [limit]
127784
+ `);
127785
+ context.emit("done");
127786
+ return { handled: true };
127787
+ }
127788
+ try {
127789
+ const result = manager.readMessages(channel, limit);
127790
+ if (!result) {
127791
+ context.emit("text", `Channel "${channel}" not found.
127792
+ `);
127793
+ } else if (result.messages.length === 0) {
127794
+ context.emit("text", `No messages in #${result.channel.name}.
127795
+ `);
127796
+ } else {
127797
+ context.emit("text", `#${result.channel.name} \u2014 Recent (${result.messages.length}):
127798
+
127799
+ `);
127800
+ for (const msg of result.messages) {
127801
+ const date = new Date(msg.createdAt).toLocaleString();
127802
+ context.emit("text", ` [${msg.senderName}] (${date})
127803
+ `);
127804
+ context.emit("text", ` ${msg.content}
127805
+
127806
+ `);
127807
+ }
127808
+ }
127809
+ } catch (error) {
127810
+ context.emit("text", `Error: ${error instanceof Error ? error.message : String(error)}
127811
+ `);
127812
+ }
127813
+ context.emit("done");
127814
+ return { handled: true };
127815
+ }
127816
+ if (subcommand === "members") {
127817
+ const channel = subArgs.trim();
127818
+ if (!channel) {
127819
+ context.emit("text", `Usage: /channels members <channel>
127820
+ `);
127821
+ context.emit("done");
127822
+ return { handled: true };
127823
+ }
127824
+ try {
127825
+ const ch = manager.getChannel(channel);
127826
+ if (!ch) {
127827
+ context.emit("text", `Channel "${channel}" not found.
127828
+ `);
127829
+ } else {
127830
+ const members = manager.getMembers(channel);
127831
+ context.emit("text", `#${ch.name} Members (${members.length}):
127832
+
127833
+ `);
127834
+ for (const m of members) {
127835
+ const roleTag = m.role === "owner" ? " (owner)" : "";
127836
+ context.emit("text", ` ${m.assistantName}${roleTag}
127837
+ `);
127838
+ }
127839
+ }
127840
+ } catch (error) {
127841
+ context.emit("text", `Error: ${error instanceof Error ? error.message : String(error)}
127842
+ `);
127843
+ }
127844
+ context.emit("done");
127845
+ return { handled: true };
127846
+ }
127847
+ if (subcommand === "invite") {
127848
+ const parts = subArgs.trim().split(/\s+/);
127849
+ const channel = parts[0];
127850
+ const agent = parts[1];
127851
+ if (!channel || !agent) {
127852
+ context.emit("text", `Usage: /channels invite <channel> <agent-name>
127853
+ `);
127854
+ context.emit("done");
127855
+ return { handled: true };
127856
+ }
127857
+ const result = manager.invite(channel, agent, agent);
127858
+ context.emit("text", `${result.success ? result.message : `Error: ${result.message}`}
127859
+ `);
127860
+ context.emit("done");
127861
+ return { handled: true };
127862
+ }
127863
+ if (subcommand === "delete") {
127864
+ const channel = subArgs.trim();
127865
+ if (!channel) {
127866
+ context.emit("text", `Usage: /channels delete <channel>
127867
+ `);
127868
+ context.emit("done");
127869
+ return { handled: true };
127870
+ }
127871
+ const result = manager.archiveChannel(channel);
127872
+ context.emit("text", `${result.success ? result.message : `Error: ${result.message}`}
127873
+ `);
127874
+ context.emit("done");
127875
+ return { handled: true };
127876
+ }
127877
+ if (subcommand === "help") {
127878
+ context.emit("text", `Channel Commands:
127879
+
127880
+ `);
127881
+ context.emit("text", `/channels Open channels panel
127882
+ `);
127883
+ context.emit("text", `/channels list List all channels
127884
+ `);
127885
+ context.emit("text", `/channels create <name> [desc] Create a channel
127886
+ `);
127887
+ context.emit("text", `/channels join <channel> Join a channel
127888
+ `);
127889
+ context.emit("text", `/channels leave <channel> Leave a channel
127890
+ `);
127891
+ context.emit("text", `/channels send <ch> <msg> Send a message
127892
+ `);
127893
+ context.emit("text", `/channels read <ch> [limit] Read messages
127894
+ `);
127895
+ context.emit("text", `/channels members <ch> List members
127896
+ `);
127897
+ context.emit("text", `/channels invite <ch> <agent> Invite an agent
127898
+ `);
127899
+ context.emit("text", `/channels delete <ch> Archive a channel
127900
+ `);
127901
+ context.emit("text", `/channels help Show this help
127902
+ `);
127903
+ context.emit("done");
127904
+ return { handled: true };
127905
+ }
127906
+ context.emit("text", `Unknown command: ${subcommand}
127907
+ `);
127908
+ context.emit("text", `Use /channels help for available commands.
127909
+ `);
127910
+ context.emit("done");
127911
+ return { handled: true };
127912
+ }
127913
+ };
127914
+ }
127915
+ phoneCommand() {
127916
+ return {
127917
+ name: "phone",
127918
+ description: "Manage telephony: SMS, calls, WhatsApp, routing",
127919
+ builtin: true,
127920
+ selfHandled: true,
127921
+ content: "",
127922
+ handler: async (args, context) => {
127923
+ const trimmed = args.trim();
127924
+ const [subcommand, ...rest] = trimmed.split(/\s+/);
127925
+ const subArgs = rest.join(" ");
127926
+ if (!subcommand || subcommand === "ui") {
127927
+ context.emit("done");
127928
+ return { handled: true, showPanel: "telephony" };
127929
+ }
127930
+ const manager = context.getTelephonyManager?.();
127931
+ if (!manager) {
127932
+ context.emit("text", `Telephony is not enabled. Set telephony.enabled: true in config.
127933
+ `);
127934
+ context.emit("done");
127935
+ return { handled: true };
127936
+ }
127937
+ if (subcommand === "numbers") {
127938
+ const numbers = manager.listPhoneNumbers();
127939
+ if (numbers.length === 0) {
127940
+ context.emit("text", `No phone numbers configured. Use /phone sync to import from Twilio.
127941
+ `);
127942
+ } else {
127943
+ context.emit("text", `Phone Numbers (${numbers.length}):
127944
+
127945
+ `);
127946
+ for (const num of numbers) {
127947
+ const caps = [];
127948
+ if (num.capabilities.voice)
127949
+ caps.push("voice");
127950
+ if (num.capabilities.sms)
127951
+ caps.push("sms");
127952
+ if (num.capabilities.whatsapp)
127953
+ caps.push("whatsapp");
127954
+ const name = num.friendlyName ? ` (${num.friendlyName})` : "";
127955
+ context.emit("text", ` ${num.number}${name} [${caps.join(", ")}]
127956
+ `);
127957
+ }
127958
+ }
127959
+ context.emit("done");
127960
+ return { handled: true };
127961
+ }
127962
+ if (subcommand === "sync") {
127963
+ context.emit("text", `Syncing phone numbers from Twilio...
127964
+ `);
127965
+ const result = await manager.syncPhoneNumbers();
127966
+ context.emit("text", `${result.success ? result.message : `Error: ${result.message}`}
127967
+ `);
127968
+ context.emit("done");
127969
+ return { handled: true };
127970
+ }
127971
+ if (subcommand === "sms") {
127972
+ const smsParts = subArgs.trim().split(/\s+/);
127973
+ const smsAction = smsParts[0];
127974
+ if (smsAction === "send") {
127975
+ const to = smsParts[1];
127976
+ const body = smsParts.slice(2).join(" ");
127977
+ if (!to || !body) {
127978
+ context.emit("text", `Usage: /phone sms send <to> <body>
127979
+ `);
127980
+ context.emit("done");
127981
+ return { handled: true };
127982
+ }
127983
+ const result = await manager.sendSms(to, body);
127984
+ context.emit("text", `${result.success ? result.message : `Error: ${result.message}`}
127985
+ `);
127986
+ } else if (smsAction === "list" || !smsAction) {
127987
+ const messages = manager.getSmsHistory({ limit: 20 });
127988
+ if (messages.length === 0) {
127989
+ context.emit("text", `No SMS history.
127990
+ `);
127991
+ } else {
127992
+ context.emit("text", `Recent SMS (${messages.length}):
127993
+
127994
+ `);
127995
+ for (const msg of messages) {
127996
+ const dir = msg.direction === "inbound" ? "IN" : "OUT";
127997
+ context.emit("text", ` [${dir}] ${msg.fromNumber} \u2192 ${msg.toNumber}: ${msg.bodyPreview}
127998
+ `);
127999
+ }
128000
+ }
128001
+ } else {
128002
+ context.emit("text", `Usage: /phone sms [send <to> <body> | list]
128003
+ `);
128004
+ }
128005
+ context.emit("done");
128006
+ return { handled: true };
128007
+ }
128008
+ if (subcommand === "call") {
128009
+ const to = subArgs.trim();
128010
+ if (!to) {
128011
+ context.emit("text", `Usage: /phone call <to>
128012
+ `);
128013
+ context.emit("done");
128014
+ return { handled: true };
128015
+ }
128016
+ const result = await manager.makeCall(to);
128017
+ context.emit("text", `${result.success ? result.message : `Error: ${result.message}`}
128018
+ `);
128019
+ context.emit("done");
128020
+ return { handled: true };
128021
+ }
128022
+ if (subcommand === "calls") {
128023
+ const calls = manager.getCallHistory({ limit: 20 });
128024
+ if (calls.length === 0) {
128025
+ context.emit("text", `No call history.
128026
+ `);
128027
+ } else {
128028
+ context.emit("text", `Recent Calls (${calls.length}):
128029
+
128030
+ `);
128031
+ for (const call of calls) {
128032
+ const dir = call.direction === "inbound" ? "IN" : "OUT";
128033
+ const dur = call.duration != null ? `${call.duration}s` : "-";
128034
+ context.emit("text", ` [${dir}] ${call.fromNumber} \u2192 ${call.toNumber} | ${call.status} | ${dur}
128035
+ `);
128036
+ }
128037
+ }
128038
+ context.emit("done");
128039
+ return { handled: true };
128040
+ }
128041
+ if (subcommand === "routes") {
128042
+ const rules = manager.listRoutingRules();
128043
+ if (rules.length === 0) {
128044
+ context.emit("text", `No routing rules configured.
128045
+ `);
128046
+ } else {
128047
+ context.emit("text", `Routing Rules (${rules.length}):
128048
+
128049
+ `);
128050
+ for (const rule of rules) {
128051
+ const enabled = rule.enabled ? "" : " [DISABLED]";
128052
+ context.emit("text", ` ${rule.name} (priority: ${rule.priority})${enabled}
128053
+ `);
128054
+ context.emit("text", ` Target: ${rule.targetAssistantName} | Type: ${rule.messageType}
128055
+ `);
128056
+ }
128057
+ }
128058
+ context.emit("done");
128059
+ return { handled: true };
128060
+ }
128061
+ if (subcommand === "status") {
128062
+ const status = manager.getStatus();
128063
+ context.emit("text", `Telephony Status:
128064
+
128065
+ `);
128066
+ context.emit("text", ` Enabled: ${status.enabled ? "Yes" : "No"}
128067
+ `);
128068
+ context.emit("text", ` Twilio: ${status.twilioConfigured ? "Configured" : "Not configured"}
128069
+ `);
128070
+ context.emit("text", ` ElevenLabs: ${status.elevenLabsConfigured ? "Configured" : "Not configured"}
128071
+ `);
128072
+ context.emit("text", ` Numbers: ${status.phoneNumbers}
128073
+ `);
128074
+ context.emit("text", ` Active calls: ${status.activeCalls}
128075
+ `);
128076
+ context.emit("text", ` Routes: ${status.routingRules}
128077
+ `);
128078
+ context.emit("done");
128079
+ return { handled: true };
128080
+ }
128081
+ if (subcommand === "help") {
128082
+ context.emit("text", `Phone Commands:
128083
+
128084
+ `);
128085
+ context.emit("text", `/phone Open telephony panel
128086
+ `);
128087
+ context.emit("text", `/phone numbers List phone numbers
128088
+ `);
128089
+ context.emit("text", `/phone sync Sync numbers from Twilio
128090
+ `);
128091
+ context.emit("text", `/phone sms send <to> <body> Send SMS
128092
+ `);
128093
+ context.emit("text", `/phone sms list Recent SMS
128094
+ `);
128095
+ context.emit("text", `/phone call <to> Initiate call
128096
+ `);
128097
+ context.emit("text", `/phone calls Recent calls
128098
+ `);
128099
+ context.emit("text", `/phone routes Routing rules
128100
+ `);
128101
+ context.emit("text", `/phone status Status summary
128102
+ `);
128103
+ context.emit("text", `/phone help Show this help
128104
+ `);
128105
+ context.emit("done");
128106
+ return { handled: true };
128107
+ }
128108
+ context.emit("text", `Unknown command: ${subcommand}
128109
+ `);
128110
+ context.emit("text", `Use /phone help for available commands.
127488
128111
  `);
127489
128112
  context.emit("done");
127490
128113
  return { handled: true };
@@ -127502,6 +128125,24 @@ Configure the external source with the URL and secret above.
127502
128125
  handler: async (args, context) => {
127503
128126
  const parts = splitArgs(args);
127504
128127
  const sub = parts[0] || "";
128128
+ const formatTaskMatch = (task) => {
128129
+ const desc = task.description.length > 60 ? `${task.description.slice(0, 60)}...` : task.description;
128130
+ return `${task.id} - ${desc}`;
128131
+ };
128132
+ const emitResolveError = (id, matches, label) => {
128133
+ if (matches.length > 1) {
128134
+ const listed = matches.slice(0, 5).map(formatTaskMatch).join(`
128135
+ `);
128136
+ const more = matches.length > 5 ? `
128137
+ ...and ${matches.length - 5} more` : "";
128138
+ context.emit("text", `Multiple ${label} match "${id}". Use a longer ID prefix.
128139
+ ${listed}${more}
128140
+ `);
128141
+ return;
128142
+ }
128143
+ context.emit("text", `${label} not found: ${id}
128144
+ `);
128145
+ };
127505
128146
  if (!sub || sub === "ui") {
127506
128147
  context.emit("done");
127507
128148
  return { handled: true, showPanel: "tasks" };
@@ -127573,6 +128214,9 @@ Configure the external source with the URL and secret above.
127573
128214
  output += ` /tasks run Run next pending task
127574
128215
  `;
127575
128216
  output += ` /tasks help Show this help
128217
+ `;
128218
+ output += `
128219
+ Note: You can use a unique ID prefix from /tasks list.
127576
128220
  `;
127577
128221
  context.emit("text", output);
127578
128222
  context.emit("done");
@@ -127591,16 +128235,16 @@ Configure the external source with the URL and secret above.
127591
128235
  **Task Queue** ${paused ? "(Paused)" : ""}
127592
128236
 
127593
128237
  `;
127594
- output += `| Status | Pri | Description | Created |
128238
+ output += `| Status | Pri | ID | Description | Created |
127595
128239
  `;
127596
- output += `|--------|-----|-------------|----------|
128240
+ output += `|--------|-----|----|-------------|----------|
127597
128241
  `;
127598
128242
  for (const task of tasks) {
127599
128243
  const statusIcon = task.status === "pending" ? "\u25CB" : task.status === "in_progress" ? "\u25D0" : task.status === "completed" ? "\u25CF" : "\u2717";
127600
128244
  const priorityIcon = task.priority === "high" ? "\u2191" : task.priority === "low" ? "\u2193" : "-";
127601
128245
  const desc = task.description.slice(0, 40) + (task.description.length > 40 ? "..." : "");
127602
128246
  const created = new Date(task.createdAt).toLocaleDateString();
127603
- output += `| ${statusIcon} | ${priorityIcon} | ${desc} | ${created} |
128247
+ output += `| ${statusIcon} | ${priorityIcon} | ${task.id} | ${desc} | ${created} |
127604
128248
  `;
127605
128249
  }
127606
128250
  context.emit("text", output);
@@ -127642,10 +128286,9 @@ Configure the external source with the URL and secret above.
127642
128286
  context.emit("done");
127643
128287
  return { handled: true };
127644
128288
  }
127645
- const task = await getTask(context.cwd, id);
128289
+ const { task, matches } = await resolveTaskId(context.cwd, id);
127646
128290
  if (!task) {
127647
- context.emit("text", `Task not found: ${id}
127648
- `);
128291
+ emitResolveError(id, matches, "Task");
127649
128292
  context.emit("done");
127650
128293
  return { handled: true };
127651
128294
  }
@@ -127695,12 +128338,18 @@ Configure the external source with the URL and secret above.
127695
128338
  context.emit("done");
127696
128339
  return { handled: true };
127697
128340
  }
127698
- const deleted = await deleteTask(context.cwd, id);
128341
+ const { task, matches } = await resolveTaskId(context.cwd, id);
128342
+ if (!task) {
128343
+ emitResolveError(id, matches, "Task");
128344
+ context.emit("done");
128345
+ return { handled: true };
128346
+ }
128347
+ const deleted = await deleteTask(context.cwd, task.id);
127699
128348
  if (deleted) {
127700
- context.emit("text", `Task deleted: ${id}
128349
+ context.emit("text", `Task deleted: ${task.id}
127701
128350
  `);
127702
128351
  } else {
127703
- context.emit("text", `Task not found: ${id}
128352
+ context.emit("text", `Task not found: ${task.id}
127704
128353
  `);
127705
128354
  }
127706
128355
  context.emit("done");
@@ -127735,12 +128384,18 @@ Configure the external source with the URL and secret above.
127735
128384
  context.emit("done");
127736
128385
  return { handled: true };
127737
128386
  }
127738
- const task = await updateTask(context.cwd, id, { priority: newPriority });
128387
+ const { task: resolved, matches } = await resolveTaskId(context.cwd, id);
128388
+ if (!resolved) {
128389
+ emitResolveError(id, matches, "Task");
128390
+ context.emit("done");
128391
+ return { handled: true };
128392
+ }
128393
+ const task = await updateTask(context.cwd, resolved.id, { priority: newPriority });
127739
128394
  if (task) {
127740
128395
  context.emit("text", `Task priority updated to ${newPriority}: ${task.description}
127741
128396
  `);
127742
128397
  } else {
127743
- context.emit("text", `Task not found: ${id}
128398
+ context.emit("text", `Task not found: ${resolved.id}
127744
128399
  `);
127745
128400
  }
127746
128401
  context.emit("done");
@@ -127940,6 +128595,20 @@ Unknown workspace command. Use /workspace help for available commands.
127940
128595
  }
127941
128596
  };
127942
128597
  }
128598
+ setupCommand() {
128599
+ return {
128600
+ name: "setup",
128601
+ aliases: ["onboarding"],
128602
+ description: "Run the interactive setup wizard",
128603
+ builtin: true,
128604
+ selfHandled: true,
128605
+ content: "",
128606
+ handler: async (_args, context) => {
128607
+ context.emit("done");
128608
+ return { handled: true, showPanel: "setup" };
128609
+ }
128610
+ };
128611
+ }
127943
128612
  exitCommand() {
127944
128613
  return {
127945
128614
  name: "exit",
@@ -128099,7 +128768,7 @@ Usage: /session assign <agent-name>
128099
128768
  return {
128100
128769
  handled: true,
128101
128770
  showPanel: "resume",
128102
- panelInitialValue: showAll ? "all" : "cwd"
128771
+ panelValue: showAll ? "all" : "cwd"
128103
128772
  };
128104
128773
  }
128105
128774
  if (cleanedArgs === "list" || cleanedArgs === "--list") {
@@ -130563,7 +131232,11 @@ Switched to **${modelDef.name}** (${modelId})
130563
131232
  return { handled: true };
130564
131233
  }
130565
131234
  const [action, ...rest] = args.trim().split(/\s+/).filter(Boolean);
130566
- if (!action) {
131235
+ if (!action || action === "ui") {
131236
+ context.emit("done");
131237
+ return { handled: true, showPanel: "memory" };
131238
+ }
131239
+ if (action === "help") {
130567
131240
  const stats = await manager.getStats();
130568
131241
  context.emit("text", `
130569
131242
  /memory - Persistent Memory Management
@@ -130579,6 +131252,8 @@ Switched to **${modelDef.name}** (${modelId})
130579
131252
 
130580
131253
  `);
130581
131254
  context.emit("text", `Commands:
131255
+ `);
131256
+ context.emit("text", ` /memory Open interactive memory panel
130582
131257
  `);
130583
131258
  context.emit("text", ` /memory list [cat] [opts] List memories (filter by category/scope/tags)
130584
131259
  `);
@@ -131398,7 +132073,7 @@ Note: custom heartbeat historyPath does not include {sessionId}; showing current
131398
132073
  return {
131399
132074
  handled: true,
131400
132075
  showPanel: "connectors",
131401
- panelInitialValue: argWithoutFlag || undefined
132076
+ panelValue: argWithoutFlag || undefined
131402
132077
  };
131403
132078
  }
131404
132079
  const connectorName = argWithoutFlag;
@@ -131939,7 +132614,7 @@ class HeartbeatManager {
131939
132614
  // ../core/src/heartbeat/persistence.ts
131940
132615
  import { dirname as dirname14 } from "path";
131941
132616
  import { mkdirSync as mkdirSync14 } from "fs";
131942
- import { readFile as readFile10, writeFile as writeFile8, unlink as unlink5 } from "fs/promises";
132617
+ import { readFile as readFile10, writeFile as writeFile8, unlink as unlink6 } from "fs/promises";
131943
132618
 
131944
132619
  class StatePersistence {
131945
132620
  path;
@@ -131962,7 +132637,7 @@ class StatePersistence {
131962
132637
  }
131963
132638
  async clear() {
131964
132639
  try {
131965
- await unlink5(this.path);
132640
+ await unlink6(this.path);
131966
132641
  } catch {}
131967
132642
  }
131968
132643
  }
@@ -132531,7 +133206,7 @@ class SystemSTT {
132531
133206
  import { spawnSync as spawnSync2 } from "child_process";
132532
133207
  import { tmpdir as tmpdir2 } from "os";
132533
133208
  import { join as join31 } from "path";
132534
- import { readFileSync as readFileSync14, unlinkSync as unlinkSync4 } from "fs";
133209
+ import { readFileSync as readFileSync14, unlinkSync as unlinkSync5 } from "fs";
132535
133210
  class ElevenLabsTTS {
132536
133211
  apiKey;
132537
133212
  voiceId;
@@ -132634,7 +133309,7 @@ class SystemTTS {
132634
133309
  if (!say) {
132635
133310
  throw new Error('System TTS not available: missing "say" command.');
132636
133311
  }
132637
- const output = join31(tmpdir2(), `assistants-tts-${Date.now()}.aiff`);
133312
+ const output = join31(tmpdir2(), `assistants-tts-${Date.now()}-${Math.random().toString(36).slice(2, 8)}.aiff`);
132638
133313
  const args = [];
132639
133314
  if (this.voiceId) {
132640
133315
  args.push("-v", this.voiceId);
@@ -132648,7 +133323,7 @@ class SystemTTS {
132648
133323
  throw new Error(`System TTS failed: ${result.stderr || "unknown error"}`);
132649
133324
  }
132650
133325
  const audio = readFileSync14(output);
132651
- unlinkSync4(output);
133326
+ unlinkSync5(output);
132652
133327
  return {
132653
133328
  audio: audio.buffer.slice(audio.byteOffset, audio.byteOffset + audio.byteLength),
132654
133329
  format: "aiff"
@@ -132656,7 +133331,7 @@ class SystemTTS {
132656
133331
  }
132657
133332
  const espeak = findExecutable("espeak") || findExecutable("espeak-ng");
132658
133333
  if (espeak) {
132659
- const output = join31(tmpdir2(), `assistants-tts-${Date.now()}.wav`);
133334
+ const output = join31(tmpdir2(), `assistants-tts-${Date.now()}-${Math.random().toString(36).slice(2, 8)}.wav`);
132660
133335
  const args = ["-w", output];
132661
133336
  if (this.voiceId) {
132662
133337
  args.push("-v", this.voiceId);
@@ -132670,7 +133345,7 @@ class SystemTTS {
132670
133345
  throw new Error(`System TTS failed: ${result.stderr || "unknown error"}`);
132671
133346
  }
132672
133347
  const audio = readFileSync14(output);
132673
- unlinkSync4(output);
133348
+ unlinkSync5(output);
132674
133349
  return {
132675
133350
  audio: audio.buffer.slice(audio.byteOffset, audio.byteOffset + audio.byteLength),
132676
133351
  format: "wav"
@@ -132684,13 +133359,13 @@ class SystemTTS {
132684
133359
  import { spawn } from "child_process";
132685
133360
  import { tmpdir as tmpdir3 } from "os";
132686
133361
  import { join as join32 } from "path";
132687
- import { unlink as unlink6, writeFileSync as writeFileSync13 } from "fs";
133362
+ import { unlink as unlink7, writeFileSync as writeFileSync13, appendFileSync as appendFileSync3 } from "fs";
132688
133363
  class AudioPlayer {
132689
133364
  currentProcess = null;
132690
133365
  playing = false;
132691
133366
  async play(audio, options = {}) {
132692
133367
  const format = options.format ?? "mp3";
132693
- const tempFile = join32(tmpdir3(), `assistants-audio-${Date.now()}.${format}`);
133368
+ const tempFile = join32(tmpdir3(), `assistants-audio-${Date.now()}-${Math.random().toString(36).slice(2, 8)}.${format}`);
132694
133369
  writeFileSync13(tempFile, Buffer.from(audio));
132695
133370
  const player = this.resolvePlayer(format);
132696
133371
  if (!player) {
@@ -132702,25 +133377,51 @@ class AudioPlayer {
132702
133377
  this.currentProcess.on("close", () => {
132703
133378
  this.playing = false;
132704
133379
  this.currentProcess = null;
132705
- unlink6(tempFile, () => {});
133380
+ unlink7(tempFile, () => {});
132706
133381
  resolve5();
132707
133382
  });
132708
133383
  this.currentProcess.on("error", (error2) => {
132709
133384
  this.playing = false;
132710
- this.currentProcess = null;
132711
- unlink6(tempFile, () => {});
133385
+ if (this.currentProcess) {
133386
+ this.currentProcess.kill();
133387
+ this.currentProcess = null;
133388
+ }
133389
+ unlink7(tempFile, () => {});
132712
133390
  reject(error2);
132713
133391
  });
132714
133392
  });
132715
133393
  }
132716
133394
  async playStream(chunks, options = {}) {
132717
- const buffers = [];
133395
+ const format = options.format ?? "mp3";
133396
+ const tempFile = join32(tmpdir3(), `assistants-stream-${Date.now()}-${Math.random().toString(36).slice(2, 8)}.${format}`);
133397
+ writeFileSync13(tempFile, Buffer.alloc(0));
132718
133398
  for await (const chunk of chunks) {
132719
- buffers.push(Buffer.from(chunk));
133399
+ appendFileSync3(tempFile, Buffer.from(chunk));
133400
+ }
133401
+ const player = this.resolvePlayer(format);
133402
+ if (!player) {
133403
+ unlink7(tempFile, () => {});
133404
+ throw new Error("No supported audio player found.");
132720
133405
  }
132721
- const combined = Buffer.concat(buffers);
132722
- const arrayBuffer = combined.buffer.slice(combined.byteOffset, combined.byteOffset + combined.byteLength);
132723
- await this.play(arrayBuffer, options);
133406
+ return new Promise((resolve5, reject) => {
133407
+ this.playing = true;
133408
+ this.currentProcess = spawn(player.command, [...player.args, tempFile]);
133409
+ this.currentProcess.on("close", () => {
133410
+ this.playing = false;
133411
+ this.currentProcess = null;
133412
+ unlink7(tempFile, () => {});
133413
+ resolve5();
133414
+ });
133415
+ this.currentProcess.on("error", (error2) => {
133416
+ this.playing = false;
133417
+ if (this.currentProcess) {
133418
+ this.currentProcess.kill();
133419
+ this.currentProcess = null;
133420
+ }
133421
+ unlink7(tempFile, () => {});
133422
+ reject(error2);
133423
+ });
133424
+ });
132724
133425
  }
132725
133426
  stop() {
132726
133427
  if (this.currentProcess) {
@@ -132759,7 +133460,7 @@ class AudioPlayer {
132759
133460
  import { spawn as spawn2 } from "child_process";
132760
133461
  import { tmpdir as tmpdir4 } from "os";
132761
133462
  import { join as join33 } from "path";
132762
- import { readFileSync as readFileSync15, unlink as unlink7 } from "fs";
133463
+ import { readFileSync as readFileSync15, unlink as unlink8 } from "fs";
132763
133464
  class AudioRecorder {
132764
133465
  currentProcess = null;
132765
133466
  stoppedIntentionally = false;
@@ -132794,7 +133495,7 @@ class AudioRecorder {
132794
133495
  });
132795
133496
  });
132796
133497
  const data = readFileSync15(output);
132797
- unlink7(output, () => {});
133498
+ unlink8(output, () => {});
132798
133499
  this.currentOutputPath = null;
132799
133500
  return data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength);
132800
133501
  }
@@ -150087,25 +150788,2519 @@ function registerWebhookTools(registry2, getWebhooksManager) {
150087
150788
  registry2.register(tool, executors[tool.name]);
150088
150789
  }
150089
150790
  }
150791
+ // ../core/src/channels/store.ts
150792
+ init_src2();
150793
+ await __promiseAll([
150794
+ init_config(),
150795
+ init_runtime()
150796
+ ]);
150797
+ import { join as join43, dirname as dirname17 } from "path";
150798
+ import { existsSync as existsSync26, mkdirSync as mkdirSync16 } from "fs";
150799
+ function generateChannelId() {
150800
+ return `ch_${generateId().slice(0, 12)}`;
150801
+ }
150802
+ function generateMessageId2() {
150803
+ return `cmsg_${generateId().slice(0, 12)}`;
150804
+ }
150805
+
150806
+ class ChannelStore {
150807
+ db;
150808
+ constructor(dbPath) {
150809
+ const baseDir = getConfigDir();
150810
+ const path2 = dbPath || join43(baseDir, "channels.db");
150811
+ const dir = dirname17(path2);
150812
+ if (!existsSync26(dir)) {
150813
+ mkdirSync16(dir, { recursive: true });
150814
+ }
150815
+ const runtime = getRuntime();
150816
+ this.db = runtime.openDatabase(path2);
150817
+ this.initialize();
150818
+ }
150819
+ initialize() {
150820
+ this.db.exec(`
150821
+ CREATE TABLE IF NOT EXISTS channels (
150822
+ id TEXT PRIMARY KEY,
150823
+ name TEXT NOT NULL UNIQUE,
150824
+ description TEXT,
150825
+ created_by TEXT NOT NULL,
150826
+ created_by_name TEXT NOT NULL,
150827
+ status TEXT NOT NULL DEFAULT 'active',
150828
+ created_at TEXT NOT NULL,
150829
+ updated_at TEXT NOT NULL
150830
+ );
150831
+
150832
+ CREATE TABLE IF NOT EXISTS channel_members (
150833
+ channel_id TEXT NOT NULL,
150834
+ assistant_id TEXT NOT NULL,
150835
+ assistant_name TEXT NOT NULL,
150836
+ role TEXT NOT NULL DEFAULT 'member',
150837
+ joined_at TEXT NOT NULL,
150838
+ last_read_at TEXT,
150839
+ PRIMARY KEY (channel_id, assistant_id)
150840
+ );
150841
+
150842
+ CREATE TABLE IF NOT EXISTS channel_messages (
150843
+ id TEXT PRIMARY KEY,
150844
+ channel_id TEXT NOT NULL,
150845
+ sender_id TEXT NOT NULL,
150846
+ sender_name TEXT NOT NULL,
150847
+ content TEXT NOT NULL,
150848
+ created_at TEXT NOT NULL
150849
+ );
150850
+
150851
+ CREATE INDEX IF NOT EXISTS idx_channel_messages_channel
150852
+ ON channel_messages(channel_id, created_at);
150853
+ CREATE INDEX IF NOT EXISTS idx_channel_members_assistant
150854
+ ON channel_members(assistant_id);
150855
+ `);
150856
+ }
150857
+ createChannel(name2, description, createdBy, createdByName) {
150858
+ const normalizedName = name2.toLowerCase().replace(/^#/, "").replace(/[^a-z0-9_-]/g, "-");
150859
+ const existing = this.getChannelByName(normalizedName);
150860
+ if (existing) {
150861
+ return {
150862
+ success: false,
150863
+ message: `Channel #${normalizedName} already exists.`
150864
+ };
150865
+ }
150866
+ const id = generateChannelId();
150867
+ const now2 = new Date().toISOString();
150868
+ const stmt = this.db.prepare(`INSERT INTO channels (id, name, description, created_by, created_by_name, status, created_at, updated_at)
150869
+ VALUES (?, ?, ?, ?, ?, 'active', ?, ?)`);
150870
+ stmt.run(id, normalizedName, description, createdBy, createdByName, now2, now2);
150871
+ this.addMember(id, createdBy, createdByName, "owner");
150872
+ return {
150873
+ success: true,
150874
+ message: `Channel #${normalizedName} created.`,
150875
+ channelId: id
150876
+ };
150877
+ }
150878
+ getChannel(id) {
150879
+ const stmt = this.db.prepare("SELECT * FROM channels WHERE id = ?");
150880
+ const row = stmt.get(id);
150881
+ return row ? this.rowToChannel(row) : null;
150882
+ }
150883
+ getChannelByName(name2) {
150884
+ const normalizedName = name2.toLowerCase().replace(/^#/, "");
150885
+ const stmt = this.db.prepare("SELECT * FROM channels WHERE name = ?");
150886
+ const row = stmt.get(normalizedName);
150887
+ return row ? this.rowToChannel(row) : null;
150888
+ }
150889
+ resolveChannel(nameOrId) {
150890
+ return this.getChannel(nameOrId) || this.getChannelByName(nameOrId);
150891
+ }
150892
+ listChannels(options) {
150893
+ let query;
150894
+ const params = [];
150895
+ if (options?.assistantId) {
150896
+ query = `
150897
+ SELECT c.*,
150898
+ (SELECT COUNT(*) FROM channel_members WHERE channel_id = c.id) as member_count,
150899
+ (SELECT content FROM channel_messages WHERE channel_id = c.id ORDER BY created_at DESC LIMIT 1) as last_message_preview,
150900
+ (SELECT created_at FROM channel_messages WHERE channel_id = c.id ORDER BY created_at DESC LIMIT 1) as last_message_at,
150901
+ (SELECT COUNT(*) FROM channel_messages WHERE channel_id = c.id
150902
+ AND created_at > COALESCE(
150903
+ (SELECT last_read_at FROM channel_members WHERE channel_id = c.id AND assistant_id = ?),
150904
+ '1970-01-01'
150905
+ )
150906
+ ) as unread_count
150907
+ FROM channels c
150908
+ INNER JOIN channel_members cm ON c.id = cm.channel_id AND cm.assistant_id = ?
150909
+ `;
150910
+ params.push(options.assistantId, options.assistantId);
150911
+ if (options?.status) {
150912
+ query += " WHERE c.status = ?";
150913
+ params.push(options.status);
150914
+ }
150915
+ } else {
150916
+ query = `
150917
+ SELECT c.*,
150918
+ (SELECT COUNT(*) FROM channel_members WHERE channel_id = c.id) as member_count,
150919
+ (SELECT content FROM channel_messages WHERE channel_id = c.id ORDER BY created_at DESC LIMIT 1) as last_message_preview,
150920
+ (SELECT created_at FROM channel_messages WHERE channel_id = c.id ORDER BY created_at DESC LIMIT 1) as last_message_at,
150921
+ 0 as unread_count
150922
+ FROM channels c
150923
+ `;
150924
+ if (options?.status) {
150925
+ query += " WHERE c.status = ?";
150926
+ params.push(options.status);
150927
+ }
150928
+ }
150929
+ query += " ORDER BY last_message_at DESC NULLS LAST, c.created_at DESC";
150930
+ const stmt = this.db.prepare(query);
150931
+ const rows = stmt.all(...params);
150932
+ return rows.map((row) => ({
150933
+ id: String(row.id),
150934
+ name: String(row.name),
150935
+ description: row.description ? String(row.description) : null,
150936
+ status: String(row.status),
150937
+ memberCount: Number(row.member_count),
150938
+ lastMessageAt: row.last_message_at ? String(row.last_message_at) : null,
150939
+ lastMessagePreview: row.last_message_preview ? String(row.last_message_preview).slice(0, 100) : null,
150940
+ unreadCount: Number(row.unread_count),
150941
+ createdAt: String(row.created_at)
150942
+ }));
150943
+ }
150944
+ archiveChannel(id) {
150945
+ const now2 = new Date().toISOString();
150946
+ const stmt = this.db.prepare("UPDATE channels SET status = ?, updated_at = ? WHERE id = ? AND status = ?");
150947
+ const result = stmt.run("archived", now2, id, "active");
150948
+ return result.changes > 0;
150949
+ }
150950
+ addMember(channelId, assistantId, assistantName, role = "member") {
150951
+ try {
150952
+ const now2 = new Date().toISOString();
150953
+ const stmt = this.db.prepare(`INSERT OR IGNORE INTO channel_members (channel_id, assistant_id, assistant_name, role, joined_at)
150954
+ VALUES (?, ?, ?, ?, ?)`);
150955
+ const result = stmt.run(channelId, assistantId, assistantName, role, now2);
150956
+ return result.changes > 0;
150957
+ } catch {
150958
+ return false;
150959
+ }
150960
+ }
150961
+ removeMember(channelId, assistantId) {
150962
+ const stmt = this.db.prepare("DELETE FROM channel_members WHERE channel_id = ? AND assistant_id = ?");
150963
+ const result = stmt.run(channelId, assistantId);
150964
+ return result.changes > 0;
150965
+ }
150966
+ getMembers(channelId) {
150967
+ const stmt = this.db.prepare("SELECT * FROM channel_members WHERE channel_id = ? ORDER BY joined_at ASC");
150968
+ const rows = stmt.all(channelId);
150969
+ return rows.map((row) => this.rowToMember(row));
150970
+ }
150971
+ isMember(channelId, assistantId) {
150972
+ const stmt = this.db.prepare("SELECT 1 FROM channel_members WHERE channel_id = ? AND assistant_id = ?");
150973
+ const row = stmt.get(channelId, assistantId);
150974
+ return !!row;
150975
+ }
150976
+ sendMessage(channelId, senderId, senderName, content) {
150977
+ const id = generateMessageId2();
150978
+ const now2 = new Date().toISOString();
150979
+ const stmt = this.db.prepare(`INSERT INTO channel_messages (id, channel_id, sender_id, sender_name, content, created_at)
150980
+ VALUES (?, ?, ?, ?, ?, ?)`);
150981
+ stmt.run(id, channelId, senderId, senderName, content, now2);
150982
+ this.db.prepare("UPDATE channels SET updated_at = ? WHERE id = ?").run(now2, channelId);
150983
+ this.db.prepare("UPDATE channel_members SET last_read_at = ? WHERE channel_id = ? AND assistant_id = ?").run(now2, channelId, senderId);
150984
+ return id;
150985
+ }
150986
+ getMessages(channelId, options) {
150987
+ const limit2 = options?.limit || 50;
150988
+ let query = "SELECT * FROM channel_messages WHERE channel_id = ?";
150989
+ const params = [channelId];
150990
+ if (options?.before) {
150991
+ query += " AND created_at < ?";
150992
+ params.push(options.before);
150993
+ }
150994
+ query += " ORDER BY created_at DESC LIMIT ?";
150995
+ params.push(limit2);
150996
+ const stmt = this.db.prepare(query);
150997
+ const rows = stmt.all(...params);
150998
+ return rows.map((row) => this.rowToMessage(row)).reverse();
150999
+ }
151000
+ getUnreadMessages(channelId, assistantId) {
151001
+ const stmt = this.db.prepare(`
151002
+ SELECT m.* FROM channel_messages m
151003
+ INNER JOIN channel_members cm ON cm.channel_id = m.channel_id AND cm.assistant_id = ?
151004
+ WHERE m.channel_id = ?
151005
+ AND m.created_at > COALESCE(cm.last_read_at, '1970-01-01')
151006
+ AND m.sender_id != ?
151007
+ ORDER BY m.created_at ASC
151008
+ `);
151009
+ const rows = stmt.all(assistantId, channelId, assistantId);
151010
+ return rows.map((row) => this.rowToMessage(row));
151011
+ }
151012
+ getAllUnreadMessages(assistantId, maxTotal) {
151013
+ const limit2 = maxTotal || 50;
151014
+ const stmt = this.db.prepare(`
151015
+ SELECT m.* FROM channel_messages m
151016
+ INNER JOIN channel_members cm ON cm.channel_id = m.channel_id AND cm.assistant_id = ?
151017
+ WHERE m.created_at > COALESCE(cm.last_read_at, '1970-01-01')
151018
+ AND m.sender_id != ?
151019
+ ORDER BY m.created_at ASC
151020
+ LIMIT ?
151021
+ `);
151022
+ const rows = stmt.all(assistantId, assistantId, limit2);
151023
+ return rows.map((row) => this.rowToMessage(row));
151024
+ }
151025
+ markRead(channelId, assistantId) {
151026
+ const now2 = new Date().toISOString();
151027
+ this.db.prepare("UPDATE channel_members SET last_read_at = ? WHERE channel_id = ? AND assistant_id = ?").run(now2, channelId, assistantId);
151028
+ }
151029
+ getUnreadCounts(assistantId) {
151030
+ const stmt = this.db.prepare(`
151031
+ SELECT cm.channel_id, COUNT(m.id) as unread_count
151032
+ FROM channel_members cm
151033
+ LEFT JOIN channel_messages m ON m.channel_id = cm.channel_id
151034
+ AND m.created_at > COALESCE(cm.last_read_at, '1970-01-01')
151035
+ AND m.sender_id != ?
151036
+ WHERE cm.assistant_id = ?
151037
+ GROUP BY cm.channel_id
151038
+ `);
151039
+ const rows = stmt.all(assistantId, assistantId);
151040
+ const counts = new Map;
151041
+ for (const row of rows) {
151042
+ counts.set(String(row.channel_id), Number(row.unread_count));
151043
+ }
151044
+ return counts;
151045
+ }
151046
+ cleanup(maxAgeDays, maxMessagesPerChannel) {
151047
+ let deleted = 0;
151048
+ const cutoff = new Date;
151049
+ cutoff.setDate(cutoff.getDate() - maxAgeDays);
151050
+ const cutoffStr = cutoff.toISOString();
151051
+ const ageResult = this.db.prepare("DELETE FROM channel_messages WHERE created_at < ?").run(cutoffStr);
151052
+ deleted += ageResult.changes;
151053
+ const channels = this.db.prepare("SELECT id FROM channels").all();
151054
+ for (const ch2 of channels) {
151055
+ const channelId = String(ch2.id);
151056
+ const countResult = this.db.prepare("SELECT COUNT(*) as cnt FROM channel_messages WHERE channel_id = ?").get(channelId);
151057
+ const count = Number(countResult.cnt);
151058
+ if (count > maxMessagesPerChannel) {
151059
+ const excess = count - maxMessagesPerChannel;
151060
+ const trimResult = this.db.prepare(`
151061
+ DELETE FROM channel_messages WHERE id IN (
151062
+ SELECT id FROM channel_messages WHERE channel_id = ?
151063
+ ORDER BY created_at ASC LIMIT ?
151064
+ )
151065
+ `).run(channelId, excess);
151066
+ deleted += trimResult.changes;
151067
+ }
151068
+ }
151069
+ return deleted;
151070
+ }
151071
+ close() {
151072
+ try {
151073
+ this.db.close();
151074
+ } catch {}
151075
+ }
151076
+ rowToChannel(row) {
151077
+ return {
151078
+ id: String(row.id),
151079
+ name: String(row.name),
151080
+ description: row.description ? String(row.description) : null,
151081
+ createdBy: String(row.created_by),
151082
+ createdByName: String(row.created_by_name),
151083
+ status: String(row.status),
151084
+ createdAt: String(row.created_at),
151085
+ updatedAt: String(row.updated_at)
151086
+ };
151087
+ }
151088
+ rowToMember(row) {
151089
+ return {
151090
+ channelId: String(row.channel_id),
151091
+ assistantId: String(row.assistant_id),
151092
+ assistantName: String(row.assistant_name),
151093
+ role: String(row.role),
151094
+ joinedAt: String(row.joined_at),
151095
+ lastReadAt: row.last_read_at ? String(row.last_read_at) : null
151096
+ };
151097
+ }
151098
+ rowToMessage(row) {
151099
+ return {
151100
+ id: String(row.id),
151101
+ channelId: String(row.channel_id),
151102
+ senderId: String(row.sender_id),
151103
+ senderName: String(row.sender_name),
151104
+ content: String(row.content),
151105
+ createdAt: String(row.created_at)
151106
+ };
151107
+ }
151108
+ }
151109
+
151110
+ // ../core/src/channels/manager.ts
151111
+ class ChannelsManager {
151112
+ assistantId;
151113
+ assistantName;
151114
+ config;
151115
+ store;
151116
+ constructor(options) {
151117
+ this.assistantId = options.assistantId;
151118
+ this.assistantName = options.assistantName;
151119
+ this.config = options.config;
151120
+ this.store = new ChannelStore;
151121
+ }
151122
+ createChannel(name2, description) {
151123
+ return this.store.createChannel(name2, description || null, this.assistantId, this.assistantName);
151124
+ }
151125
+ listChannels() {
151126
+ return this.store.listChannels({ status: "active" });
151127
+ }
151128
+ listMyChannels() {
151129
+ return this.store.listChannels({
151130
+ status: "active",
151131
+ assistantId: this.assistantId
151132
+ });
151133
+ }
151134
+ getChannel(nameOrId) {
151135
+ return this.store.resolveChannel(nameOrId);
151136
+ }
151137
+ archiveChannel(nameOrId) {
151138
+ const channel = this.store.resolveChannel(nameOrId);
151139
+ if (!channel) {
151140
+ return { success: false, message: `Channel "${nameOrId}" not found.` };
151141
+ }
151142
+ const success = this.store.archiveChannel(channel.id);
151143
+ if (success) {
151144
+ return { success: true, message: `Channel #${channel.name} archived.`, channelId: channel.id };
151145
+ }
151146
+ return { success: false, message: `Failed to archive #${channel.name}. It may already be archived.` };
151147
+ }
151148
+ join(nameOrId) {
151149
+ const channel = this.store.resolveChannel(nameOrId);
151150
+ if (!channel) {
151151
+ return { success: false, message: `Channel "${nameOrId}" not found.` };
151152
+ }
151153
+ if (channel.status !== "active") {
151154
+ return { success: false, message: `Channel #${channel.name} is archived.` };
151155
+ }
151156
+ if (this.store.isMember(channel.id, this.assistantId)) {
151157
+ return { success: false, message: `Already a member of #${channel.name}.` };
151158
+ }
151159
+ this.store.addMember(channel.id, this.assistantId, this.assistantName);
151160
+ return { success: true, message: `Joined #${channel.name}.`, channelId: channel.id };
151161
+ }
151162
+ leave(nameOrId) {
151163
+ const channel = this.store.resolveChannel(nameOrId);
151164
+ if (!channel) {
151165
+ return { success: false, message: `Channel "${nameOrId}" not found.` };
151166
+ }
151167
+ if (!this.store.isMember(channel.id, this.assistantId)) {
151168
+ return { success: false, message: `Not a member of #${channel.name}.` };
151169
+ }
151170
+ this.store.removeMember(channel.id, this.assistantId);
151171
+ return { success: true, message: `Left #${channel.name}.`, channelId: channel.id };
151172
+ }
151173
+ invite(nameOrId, targetId, targetName) {
151174
+ const channel = this.store.resolveChannel(nameOrId);
151175
+ if (!channel) {
151176
+ return { success: false, message: `Channel "${nameOrId}" not found.` };
151177
+ }
151178
+ if (channel.status !== "active") {
151179
+ return { success: false, message: `Channel #${channel.name} is archived.` };
151180
+ }
151181
+ if (this.store.isMember(channel.id, targetId)) {
151182
+ return { success: false, message: `${targetName} is already a member of #${channel.name}.` };
151183
+ }
151184
+ this.store.addMember(channel.id, targetId, targetName);
151185
+ return {
151186
+ success: true,
151187
+ message: `Invited ${targetName} to #${channel.name}.`,
151188
+ channelId: channel.id
151189
+ };
151190
+ }
151191
+ getMembers(nameOrId) {
151192
+ const channel = this.store.resolveChannel(nameOrId);
151193
+ if (!channel)
151194
+ return [];
151195
+ return this.store.getMembers(channel.id);
151196
+ }
151197
+ send(nameOrId, content) {
151198
+ const channel = this.store.resolveChannel(nameOrId);
151199
+ if (!channel) {
151200
+ return { success: false, message: `Channel "${nameOrId}" not found.` };
151201
+ }
151202
+ if (channel.status !== "active") {
151203
+ return { success: false, message: `Channel #${channel.name} is archived.` };
151204
+ }
151205
+ if (!this.store.isMember(channel.id, this.assistantId)) {
151206
+ return { success: false, message: `You are not a member of #${channel.name}. Join first.` };
151207
+ }
151208
+ const messageId = this.store.sendMessage(channel.id, this.assistantId, this.assistantName, content);
151209
+ return {
151210
+ success: true,
151211
+ message: `Message sent to #${channel.name} (${messageId}).`,
151212
+ channelId: channel.id
151213
+ };
151214
+ }
151215
+ readMessages(nameOrId, limit2) {
151216
+ const channel = this.store.resolveChannel(nameOrId);
151217
+ if (!channel)
151218
+ return null;
151219
+ const messages = this.store.getMessages(channel.id, { limit: limit2 });
151220
+ this.store.markRead(channel.id, this.assistantId);
151221
+ return { channel, messages };
151222
+ }
151223
+ markRead(nameOrId) {
151224
+ const channel = this.store.resolveChannel(nameOrId);
151225
+ if (channel) {
151226
+ this.store.markRead(channel.id, this.assistantId);
151227
+ }
151228
+ }
151229
+ getUnreadForInjection() {
151230
+ const injectionConfig = this.config.injection || {};
151231
+ if (injectionConfig.enabled === false) {
151232
+ return [];
151233
+ }
151234
+ const maxPerTurn = injectionConfig.maxPerTurn || 10;
151235
+ return this.store.getAllUnreadMessages(this.assistantId, maxPerTurn);
151236
+ }
151237
+ buildInjectionContext(messages) {
151238
+ if (messages.length === 0) {
151239
+ return "";
151240
+ }
151241
+ const byChannel = new Map;
151242
+ for (const msg of messages) {
151243
+ const existing = byChannel.get(msg.channelId) || [];
151244
+ existing.push(msg);
151245
+ byChannel.set(msg.channelId, existing);
151246
+ }
151247
+ const lines = [];
151248
+ lines.push("## Unread Channel Messages");
151249
+ lines.push("");
151250
+ for (const [channelId, channelMessages] of byChannel) {
151251
+ const channel = this.store.getChannel(channelId);
151252
+ const channelName = channel ? `#${channel.name}` : channelId;
151253
+ lines.push(`### ${channelName} (${channelMessages.length} new)`);
151254
+ for (const msg of channelMessages) {
151255
+ const ago = formatTimeAgo(msg.createdAt);
151256
+ lines.push(`**${msg.senderName}** (${ago}): ${msg.content}`);
151257
+ }
151258
+ lines.push("");
151259
+ }
151260
+ lines.push("Use channel_read for history, channel_send to reply.");
151261
+ return lines.join(`
151262
+ `);
151263
+ }
151264
+ markInjected(messages) {
151265
+ const channelIds = new Set(messages.map((m5) => m5.channelId));
151266
+ for (const channelId of channelIds) {
151267
+ this.store.markRead(channelId, this.assistantId);
151268
+ }
151269
+ }
151270
+ cleanup() {
151271
+ const maxAgeDays = this.config.storage?.maxAgeDays || 90;
151272
+ const maxMessages = this.config.storage?.maxMessagesPerChannel || 5000;
151273
+ return this.store.cleanup(maxAgeDays, maxMessages);
151274
+ }
151275
+ close() {
151276
+ this.store.close();
151277
+ }
151278
+ }
151279
+ function formatTimeAgo(isoDate) {
151280
+ const now2 = Date.now();
151281
+ const then = new Date(isoDate).getTime();
151282
+ const diffMs = now2 - then;
151283
+ if (diffMs < 60000) {
151284
+ const secs = Math.floor(diffMs / 1000);
151285
+ return `${secs}s ago`;
151286
+ }
151287
+ if (diffMs < 3600000) {
151288
+ const mins = Math.floor(diffMs / 60000);
151289
+ return `${mins}m ago`;
151290
+ }
151291
+ if (diffMs < 86400000) {
151292
+ const hours = Math.floor(diffMs / 3600000);
151293
+ return `${hours}h ago`;
151294
+ }
151295
+ const days = Math.floor(diffMs / 86400000);
151296
+ return `${days}d ago`;
151297
+ }
151298
+ function createChannelsManager(assistantId, assistantName, config) {
151299
+ return new ChannelsManager({
151300
+ assistantId,
151301
+ assistantName,
151302
+ config
151303
+ });
151304
+ }
151305
+ // ../core/src/channels/tools.ts
151306
+ var channelListTool = {
151307
+ name: "channel_list",
151308
+ description: "List channels. By default shows only channels you are a member of. Set mine_only to false to see all channels.",
151309
+ parameters: {
151310
+ type: "object",
151311
+ properties: {
151312
+ mine_only: {
151313
+ type: "boolean",
151314
+ description: "Only list channels you are a member of (default: true)"
151315
+ }
151316
+ },
151317
+ required: []
151318
+ }
151319
+ };
151320
+ var channelJoinTool = {
151321
+ name: "channel_join",
151322
+ description: "Join a channel by name or ID to start receiving messages.",
151323
+ parameters: {
151324
+ type: "object",
151325
+ properties: {
151326
+ channel: {
151327
+ type: "string",
151328
+ description: 'Channel name (e.g., "general" or "#general") or channel ID'
151329
+ }
151330
+ },
151331
+ required: ["channel"]
151332
+ }
151333
+ };
151334
+ var channelLeaveTool = {
151335
+ name: "channel_leave",
151336
+ description: "Leave a channel. You will stop receiving messages from it.",
151337
+ parameters: {
151338
+ type: "object",
151339
+ properties: {
151340
+ channel: {
151341
+ type: "string",
151342
+ description: "Channel name or ID"
151343
+ }
151344
+ },
151345
+ required: ["channel"]
151346
+ }
151347
+ };
151348
+ var channelSendTool = {
151349
+ name: "channel_send",
151350
+ description: "Send a message to a channel. You must be a member of the channel.",
151351
+ parameters: {
151352
+ type: "object",
151353
+ properties: {
151354
+ channel: {
151355
+ type: "string",
151356
+ description: "Channel name or ID"
151357
+ },
151358
+ message: {
151359
+ type: "string",
151360
+ description: "Message content to send"
151361
+ }
151362
+ },
151363
+ required: ["channel", "message"]
151364
+ }
151365
+ };
151366
+ var channelReadTool = {
151367
+ name: "channel_read",
151368
+ description: "Read recent messages from a channel. Also marks the channel as read.",
151369
+ parameters: {
151370
+ type: "object",
151371
+ properties: {
151372
+ channel: {
151373
+ type: "string",
151374
+ description: "Channel name or ID"
151375
+ },
151376
+ limit: {
151377
+ type: "number",
151378
+ description: "Maximum number of messages to return (default: 20)"
151379
+ }
151380
+ },
151381
+ required: ["channel"]
151382
+ }
151383
+ };
151384
+ var channelMembersTool = {
151385
+ name: "channel_members",
151386
+ description: "List all members of a channel with their roles.",
151387
+ parameters: {
151388
+ type: "object",
151389
+ properties: {
151390
+ channel: {
151391
+ type: "string",
151392
+ description: "Channel name or ID"
151393
+ }
151394
+ },
151395
+ required: ["channel"]
151396
+ }
151397
+ };
151398
+ var channelInviteTool = {
151399
+ name: "channel_invite",
151400
+ description: "Invite another assistant to join a channel.",
151401
+ parameters: {
151402
+ type: "object",
151403
+ properties: {
151404
+ channel: {
151405
+ type: "string",
151406
+ description: "Channel name or ID"
151407
+ },
151408
+ assistant: {
151409
+ type: "string",
151410
+ description: "Assistant name or ID to invite"
151411
+ }
151412
+ },
151413
+ required: ["channel", "assistant"]
151414
+ }
151415
+ };
151416
+ function createChannelToolExecutors(getChannelsManager) {
151417
+ return {
151418
+ channel_list: async (input) => {
151419
+ const manager = getChannelsManager();
151420
+ if (!manager) {
151421
+ return "Error: Channels are not enabled or configured. Set channels.enabled: true in config.";
151422
+ }
151423
+ const mineOnly = input.mine_only !== false;
151424
+ try {
151425
+ const channels = mineOnly ? manager.listMyChannels() : manager.listChannels();
151426
+ if (channels.length === 0) {
151427
+ return mineOnly ? "You are not a member of any channels. Use channel_list with mine_only: false to see all, or channel_join to join one." : "No channels exist yet.";
151428
+ }
151429
+ const lines = [];
151430
+ const label = mineOnly ? "My Channels" : "All Channels";
151431
+ lines.push(`## ${label} (${channels.length})`);
151432
+ lines.push("");
151433
+ for (const ch2 of channels) {
151434
+ const unread = ch2.unreadCount > 0 ? ` (${ch2.unreadCount} unread)` : "";
151435
+ const lastMsg = ch2.lastMessagePreview ? `Last: ${ch2.lastMessagePreview}` : "No messages yet";
151436
+ lines.push(`**#${ch2.name}**${unread}`);
151437
+ if (ch2.description) {
151438
+ lines.push(` ${ch2.description}`);
151439
+ }
151440
+ lines.push(` Members: ${ch2.memberCount} | ${lastMsg}`);
151441
+ lines.push("");
151442
+ }
151443
+ return lines.join(`
151444
+ `);
151445
+ } catch (error2) {
151446
+ return `Error listing channels: ${error2 instanceof Error ? error2.message : String(error2)}`;
151447
+ }
151448
+ },
151449
+ channel_join: async (input) => {
151450
+ const manager = getChannelsManager();
151451
+ if (!manager) {
151452
+ return "Error: Channels are not enabled or configured.";
151453
+ }
151454
+ const channel = String(input.channel || "").trim();
151455
+ if (!channel) {
151456
+ return "Error: Channel name or ID is required.";
151457
+ }
151458
+ const result = manager.join(channel);
151459
+ return result.success ? result.message : `Error: ${result.message}`;
151460
+ },
151461
+ channel_leave: async (input) => {
151462
+ const manager = getChannelsManager();
151463
+ if (!manager) {
151464
+ return "Error: Channels are not enabled or configured.";
151465
+ }
151466
+ const channel = String(input.channel || "").trim();
151467
+ if (!channel) {
151468
+ return "Error: Channel name or ID is required.";
151469
+ }
151470
+ const result = manager.leave(channel);
151471
+ return result.success ? result.message : `Error: ${result.message}`;
151472
+ },
151473
+ channel_send: async (input) => {
151474
+ const manager = getChannelsManager();
151475
+ if (!manager) {
151476
+ return "Error: Channels are not enabled or configured.";
151477
+ }
151478
+ const channel = String(input.channel || "").trim();
151479
+ const message = String(input.message || "").trim();
151480
+ if (!channel)
151481
+ return "Error: Channel name or ID is required.";
151482
+ if (!message)
151483
+ return "Error: Message content is required.";
151484
+ const result = manager.send(channel, message);
151485
+ return result.success ? result.message : `Error: ${result.message}`;
151486
+ },
151487
+ channel_read: async (input) => {
151488
+ const manager = getChannelsManager();
151489
+ if (!manager) {
151490
+ return "Error: Channels are not enabled or configured.";
151491
+ }
151492
+ const channel = String(input.channel || "").trim();
151493
+ if (!channel)
151494
+ return "Error: Channel name or ID is required.";
151495
+ const limit2 = typeof input.limit === "number" ? input.limit : 20;
151496
+ try {
151497
+ const result = manager.readMessages(channel, limit2);
151498
+ if (!result) {
151499
+ return `Channel "${channel}" not found.`;
151500
+ }
151501
+ const { channel: ch2, messages } = result;
151502
+ if (messages.length === 0) {
151503
+ return `No messages in #${ch2.name}.`;
151504
+ }
151505
+ const lines = [];
151506
+ lines.push(`## #${ch2.name} \u2014 Recent Messages (${messages.length})`);
151507
+ lines.push("");
151508
+ for (const msg of messages) {
151509
+ const date = new Date(msg.createdAt).toLocaleString();
151510
+ lines.push(`**${msg.senderName}** (${date}):`);
151511
+ lines.push(msg.content);
151512
+ lines.push("");
151513
+ }
151514
+ return lines.join(`
151515
+ `);
151516
+ } catch (error2) {
151517
+ return `Error reading messages: ${error2 instanceof Error ? error2.message : String(error2)}`;
151518
+ }
151519
+ },
151520
+ channel_members: async (input) => {
151521
+ const manager = getChannelsManager();
151522
+ if (!manager) {
151523
+ return "Error: Channels are not enabled or configured.";
151524
+ }
151525
+ const channel = String(input.channel || "").trim();
151526
+ if (!channel)
151527
+ return "Error: Channel name or ID is required.";
151528
+ try {
151529
+ const ch2 = manager.getChannel(channel);
151530
+ if (!ch2) {
151531
+ return `Channel "${channel}" not found.`;
151532
+ }
151533
+ const members = manager.getMembers(channel);
151534
+ if (members.length === 0) {
151535
+ return `No members in #${ch2.name}.`;
151536
+ }
151537
+ const lines = [];
151538
+ lines.push(`## #${ch2.name} Members (${members.length})`);
151539
+ lines.push("");
151540
+ for (const member of members) {
151541
+ const roleTag = member.role === "owner" ? " (owner)" : "";
151542
+ const joined = new Date(member.joinedAt).toLocaleDateString();
151543
+ lines.push(`- **${member.assistantName}**${roleTag} \u2014 joined ${joined}`);
151544
+ }
151545
+ return lines.join(`
151546
+ `);
151547
+ } catch (error2) {
151548
+ return `Error listing members: ${error2 instanceof Error ? error2.message : String(error2)}`;
151549
+ }
151550
+ },
151551
+ channel_invite: async (input) => {
151552
+ const manager = getChannelsManager();
151553
+ if (!manager) {
151554
+ return "Error: Channels are not enabled or configured.";
151555
+ }
151556
+ const channel = String(input.channel || "").trim();
151557
+ const assistant = String(input.assistant || "").trim();
151558
+ if (!channel)
151559
+ return "Error: Channel name or ID is required.";
151560
+ if (!assistant)
151561
+ return "Error: Assistant name or ID is required.";
151562
+ const result = manager.invite(channel, assistant, assistant);
151563
+ return result.success ? result.message : `Error: ${result.message}`;
151564
+ }
151565
+ };
151566
+ }
151567
+ var channelTools = [
151568
+ channelListTool,
151569
+ channelJoinTool,
151570
+ channelLeaveTool,
151571
+ channelSendTool,
151572
+ channelReadTool,
151573
+ channelMembersTool,
151574
+ channelInviteTool
151575
+ ];
151576
+ function registerChannelTools(registry2, getChannelsManager) {
151577
+ const executors = createChannelToolExecutors(getChannelsManager);
151578
+ for (const tool of channelTools) {
151579
+ registry2.register(tool, executors[tool.name]);
151580
+ }
151581
+ }
151582
+ // ../core/src/telephony/store.ts
151583
+ init_src2();
151584
+ await __promiseAll([
151585
+ init_config(),
151586
+ init_runtime()
151587
+ ]);
151588
+ import { join as join44, dirname as dirname18 } from "path";
151589
+ import { existsSync as existsSync27, mkdirSync as mkdirSync17 } from "fs";
151590
+ function generatePhoneId() {
151591
+ return `ph_${generateId().slice(0, 12)}`;
151592
+ }
151593
+ function generateCallId() {
151594
+ return `call_${generateId().slice(0, 12)}`;
151595
+ }
151596
+ function generateSmsId() {
151597
+ return `sms_${generateId().slice(0, 12)}`;
151598
+ }
151599
+ function generateRuleId() {
151600
+ return `rule_${generateId().slice(0, 12)}`;
151601
+ }
151602
+
151603
+ class TelephonyStore {
151604
+ db;
151605
+ constructor(dbPath) {
151606
+ const baseDir = getConfigDir();
151607
+ const path2 = dbPath || join44(baseDir, "telephony.db");
151608
+ const dir = dirname18(path2);
151609
+ if (!existsSync27(dir)) {
151610
+ mkdirSync17(dir, { recursive: true });
151611
+ }
151612
+ const runtime = getRuntime();
151613
+ this.db = runtime.openDatabase(path2);
151614
+ this.initialize();
151615
+ }
151616
+ initialize() {
151617
+ this.db.exec(`
151618
+ CREATE TABLE IF NOT EXISTS phone_numbers (
151619
+ id TEXT PRIMARY KEY,
151620
+ number TEXT NOT NULL UNIQUE,
151621
+ friendly_name TEXT,
151622
+ twilio_sid TEXT,
151623
+ status TEXT NOT NULL DEFAULT 'active',
151624
+ voice_capable INTEGER NOT NULL DEFAULT 1,
151625
+ sms_capable INTEGER NOT NULL DEFAULT 1,
151626
+ whatsapp_capable INTEGER NOT NULL DEFAULT 0,
151627
+ created_at TEXT NOT NULL,
151628
+ updated_at TEXT NOT NULL
151629
+ );
151630
+
151631
+ CREATE TABLE IF NOT EXISTS call_logs (
151632
+ id TEXT PRIMARY KEY,
151633
+ call_sid TEXT,
151634
+ from_number TEXT NOT NULL,
151635
+ to_number TEXT NOT NULL,
151636
+ direction TEXT NOT NULL,
151637
+ status TEXT NOT NULL DEFAULT 'pending',
151638
+ assistant_id TEXT,
151639
+ duration INTEGER,
151640
+ recording_url TEXT,
151641
+ started_at TEXT,
151642
+ ended_at TEXT,
151643
+ created_at TEXT NOT NULL
151644
+ );
151645
+
151646
+ CREATE TABLE IF NOT EXISTS sms_logs (
151647
+ id TEXT PRIMARY KEY,
151648
+ message_sid TEXT,
151649
+ from_number TEXT NOT NULL,
151650
+ to_number TEXT NOT NULL,
151651
+ direction TEXT NOT NULL,
151652
+ message_type TEXT NOT NULL DEFAULT 'sms',
151653
+ body TEXT NOT NULL,
151654
+ status TEXT NOT NULL DEFAULT 'queued',
151655
+ assistant_id TEXT,
151656
+ created_at TEXT NOT NULL
151657
+ );
151658
+
151659
+ CREATE TABLE IF NOT EXISTS routing_rules (
151660
+ id TEXT PRIMARY KEY,
151661
+ name TEXT NOT NULL,
151662
+ priority INTEGER NOT NULL DEFAULT 100,
151663
+ from_pattern TEXT,
151664
+ to_pattern TEXT,
151665
+ message_type TEXT NOT NULL DEFAULT 'all',
151666
+ time_of_day TEXT,
151667
+ day_of_week TEXT,
151668
+ keyword TEXT,
151669
+ target_assistant_id TEXT NOT NULL,
151670
+ target_assistant_name TEXT NOT NULL,
151671
+ enabled INTEGER NOT NULL DEFAULT 1,
151672
+ created_at TEXT NOT NULL,
151673
+ updated_at TEXT NOT NULL
151674
+ );
151675
+
151676
+ CREATE INDEX IF NOT EXISTS idx_call_logs_assistant ON call_logs(assistant_id);
151677
+ CREATE INDEX IF NOT EXISTS idx_call_logs_status ON call_logs(status);
151678
+ CREATE INDEX IF NOT EXISTS idx_call_logs_created ON call_logs(created_at);
151679
+ CREATE INDEX IF NOT EXISTS idx_call_logs_sid ON call_logs(call_sid);
151680
+ CREATE INDEX IF NOT EXISTS idx_sms_logs_assistant ON sms_logs(assistant_id);
151681
+ CREATE INDEX IF NOT EXISTS idx_sms_logs_created ON sms_logs(created_at);
151682
+ CREATE INDEX IF NOT EXISTS idx_sms_logs_sid ON sms_logs(message_sid);
151683
+ CREATE INDEX IF NOT EXISTS idx_routing_rules_priority ON routing_rules(priority, enabled);
151684
+ `);
151685
+ }
151686
+ addPhoneNumber(number, friendlyName, twilioSid, capabilities) {
151687
+ const id = generatePhoneId();
151688
+ const now2 = new Date().toISOString();
151689
+ this.db.prepare(`INSERT INTO phone_numbers (id, number, friendly_name, twilio_sid, voice_capable, sms_capable, whatsapp_capable, created_at, updated_at)
151690
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(id, number, friendlyName, twilioSid, capabilities?.voice !== false ? 1 : 0, capabilities?.sms !== false ? 1 : 0, capabilities?.whatsapp ? 1 : 0, now2, now2);
151691
+ return this.getPhoneNumber(id);
151692
+ }
151693
+ getPhoneNumber(id) {
151694
+ const row = this.db.prepare("SELECT * FROM phone_numbers WHERE id = ?").get(id);
151695
+ return row ? this.rowToPhoneNumber(row) : null;
151696
+ }
151697
+ getPhoneNumberByNumber(number) {
151698
+ const row = this.db.prepare("SELECT * FROM phone_numbers WHERE number = ?").get(number);
151699
+ return row ? this.rowToPhoneNumber(row) : null;
151700
+ }
151701
+ listPhoneNumbers(status) {
151702
+ let query = "SELECT * FROM phone_numbers";
151703
+ const params = [];
151704
+ if (status) {
151705
+ query += " WHERE status = ?";
151706
+ params.push(status);
151707
+ }
151708
+ query += " ORDER BY created_at DESC";
151709
+ const rows = this.db.prepare(query).all(...params);
151710
+ return rows.map((r6) => this.rowToPhoneNumber(r6));
151711
+ }
151712
+ updatePhoneNumberStatus(id, status) {
151713
+ const now2 = new Date().toISOString();
151714
+ const result = this.db.prepare("UPDATE phone_numbers SET status = ?, updated_at = ? WHERE id = ?").run(status, now2, id);
151715
+ return result.changes > 0;
151716
+ }
151717
+ deletePhoneNumber(id) {
151718
+ const result = this.db.prepare("DELETE FROM phone_numbers WHERE id = ?").run(id);
151719
+ return result.changes > 0;
151720
+ }
151721
+ createCallLog(params) {
151722
+ const id = generateCallId();
151723
+ const now2 = new Date().toISOString();
151724
+ this.db.prepare(`INSERT INTO call_logs (id, call_sid, from_number, to_number, direction, status, assistant_id, created_at)
151725
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)`).run(id, params.callSid || null, params.fromNumber, params.toNumber, params.direction, params.status || "pending", params.assistantId || null, now2);
151726
+ return this.getCallLog(id);
151727
+ }
151728
+ getCallLog(id) {
151729
+ const row = this.db.prepare("SELECT * FROM call_logs WHERE id = ?").get(id);
151730
+ return row ? this.rowToCallLog(row) : null;
151731
+ }
151732
+ getCallLogBySid(callSid) {
151733
+ const row = this.db.prepare("SELECT * FROM call_logs WHERE call_sid = ?").get(callSid);
151734
+ return row ? this.rowToCallLog(row) : null;
151735
+ }
151736
+ updateCallLog(id, updates) {
151737
+ const sets = [];
151738
+ const params = [];
151739
+ if (updates.status !== undefined) {
151740
+ sets.push("status = ?");
151741
+ params.push(updates.status);
151742
+ }
151743
+ if (updates.callSid !== undefined) {
151744
+ sets.push("call_sid = ?");
151745
+ params.push(updates.callSid);
151746
+ }
151747
+ if (updates.duration !== undefined) {
151748
+ sets.push("duration = ?");
151749
+ params.push(updates.duration);
151750
+ }
151751
+ if (updates.recordingUrl !== undefined) {
151752
+ sets.push("recording_url = ?");
151753
+ params.push(updates.recordingUrl);
151754
+ }
151755
+ if (updates.startedAt !== undefined) {
151756
+ sets.push("started_at = ?");
151757
+ params.push(updates.startedAt);
151758
+ }
151759
+ if (updates.endedAt !== undefined) {
151760
+ sets.push("ended_at = ?");
151761
+ params.push(updates.endedAt);
151762
+ }
151763
+ if (sets.length === 0)
151764
+ return false;
151765
+ params.push(id);
151766
+ const result = this.db.prepare(`UPDATE call_logs SET ${sets.join(", ")} WHERE id = ?`).run(...params);
151767
+ return result.changes > 0;
151768
+ }
151769
+ listCallLogs(options) {
151770
+ const conditions = [];
151771
+ const params = [];
151772
+ const limit2 = options?.limit || 50;
151773
+ if (options?.assistantId) {
151774
+ conditions.push("assistant_id = ?");
151775
+ params.push(options.assistantId);
151776
+ }
151777
+ if (options?.direction) {
151778
+ conditions.push("direction = ?");
151779
+ params.push(options.direction);
151780
+ }
151781
+ if (options?.status) {
151782
+ conditions.push("status = ?");
151783
+ params.push(options.status);
151784
+ }
151785
+ let query = "SELECT * FROM call_logs";
151786
+ if (conditions.length > 0) {
151787
+ query += " WHERE " + conditions.join(" AND ");
151788
+ }
151789
+ query += " ORDER BY created_at DESC LIMIT ?";
151790
+ params.push(limit2);
151791
+ const rows = this.db.prepare(query).all(...params);
151792
+ return rows.map((row) => ({
151793
+ id: String(row.id),
151794
+ fromNumber: String(row.from_number),
151795
+ toNumber: String(row.to_number),
151796
+ direction: String(row.direction),
151797
+ status: String(row.status),
151798
+ duration: row.duration != null ? Number(row.duration) : null,
151799
+ startedAt: row.started_at ? String(row.started_at) : null,
151800
+ createdAt: String(row.created_at)
151801
+ }));
151802
+ }
151803
+ createSmsLog(params) {
151804
+ const id = generateSmsId();
151805
+ const now2 = new Date().toISOString();
151806
+ this.db.prepare(`INSERT INTO sms_logs (id, message_sid, from_number, to_number, direction, message_type, body, status, assistant_id, created_at)
151807
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(id, params.messageSid || null, params.fromNumber, params.toNumber, params.direction, params.messageType || "sms", params.body, params.status || "queued", params.assistantId || null, now2);
151808
+ return this.getSmsLog(id);
151809
+ }
151810
+ getSmsLog(id) {
151811
+ const row = this.db.prepare("SELECT * FROM sms_logs WHERE id = ?").get(id);
151812
+ return row ? this.rowToSmsLog(row) : null;
151813
+ }
151814
+ updateSmsStatus(id, status) {
151815
+ const result = this.db.prepare("UPDATE sms_logs SET status = ? WHERE id = ?").run(status, id);
151816
+ return result.changes > 0;
151817
+ }
151818
+ listSmsLogs(options) {
151819
+ const conditions = [];
151820
+ const params = [];
151821
+ const limit2 = options?.limit || 50;
151822
+ if (options?.assistantId) {
151823
+ conditions.push("assistant_id = ?");
151824
+ params.push(options.assistantId);
151825
+ }
151826
+ if (options?.direction) {
151827
+ conditions.push("direction = ?");
151828
+ params.push(options.direction);
151829
+ }
151830
+ if (options?.messageType) {
151831
+ conditions.push("message_type = ?");
151832
+ params.push(options.messageType);
151833
+ }
151834
+ let query = "SELECT * FROM sms_logs";
151835
+ if (conditions.length > 0) {
151836
+ query += " WHERE " + conditions.join(" AND ");
151837
+ }
151838
+ query += " ORDER BY created_at DESC LIMIT ?";
151839
+ params.push(limit2);
151840
+ const rows = this.db.prepare(query).all(...params);
151841
+ return rows.map((row) => ({
151842
+ id: String(row.id),
151843
+ fromNumber: String(row.from_number),
151844
+ toNumber: String(row.to_number),
151845
+ direction: String(row.direction),
151846
+ messageType: String(row.message_type),
151847
+ bodyPreview: String(row.body).slice(0, 100),
151848
+ status: String(row.status),
151849
+ createdAt: String(row.created_at)
151850
+ }));
151851
+ }
151852
+ getUnreadInboundSms(assistantId, limit2) {
151853
+ const maxLimit = limit2 || 50;
151854
+ const rows = this.db.prepare(`SELECT * FROM sms_logs
151855
+ WHERE direction = 'inbound' AND assistant_id = ? AND status = 'received'
151856
+ ORDER BY created_at ASC LIMIT ?`).all(assistantId, maxLimit);
151857
+ return rows.map((r6) => this.rowToSmsLog(r6));
151858
+ }
151859
+ createRoutingRule(params) {
151860
+ const id = generateRuleId();
151861
+ const now2 = new Date().toISOString();
151862
+ this.db.prepare(`INSERT INTO routing_rules (id, name, priority, from_pattern, to_pattern, message_type, time_of_day, day_of_week, keyword, target_assistant_id, target_assistant_name, created_at, updated_at)
151863
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(id, params.name, params.priority ?? 100, params.fromPattern || null, params.toPattern || null, params.messageType || "all", params.timeOfDay || null, params.dayOfWeek || null, params.keyword || null, params.targetAssistantId, params.targetAssistantName, now2, now2);
151864
+ return this.getRoutingRule(id);
151865
+ }
151866
+ getRoutingRule(id) {
151867
+ const row = this.db.prepare("SELECT * FROM routing_rules WHERE id = ?").get(id);
151868
+ return row ? this.rowToRoutingRule(row) : null;
151869
+ }
151870
+ listRoutingRules() {
151871
+ const rows = this.db.prepare("SELECT * FROM routing_rules ORDER BY priority ASC, created_at ASC").all();
151872
+ return rows.map((r6) => this.rowToRoutingRule(r6));
151873
+ }
151874
+ updateRoutingRule(id, updates) {
151875
+ const sets = [];
151876
+ const params = [];
151877
+ const now2 = new Date().toISOString();
151878
+ if (updates.name !== undefined) {
151879
+ sets.push("name = ?");
151880
+ params.push(updates.name);
151881
+ }
151882
+ if (updates.priority !== undefined) {
151883
+ sets.push("priority = ?");
151884
+ params.push(updates.priority);
151885
+ }
151886
+ if (updates.enabled !== undefined) {
151887
+ sets.push("enabled = ?");
151888
+ params.push(updates.enabled ? 1 : 0);
151889
+ }
151890
+ sets.push("updated_at = ?");
151891
+ params.push(now2);
151892
+ if (sets.length === 1)
151893
+ return false;
151894
+ params.push(id);
151895
+ const result = this.db.prepare(`UPDATE routing_rules SET ${sets.join(", ")} WHERE id = ?`).run(...params);
151896
+ return result.changes > 0;
151897
+ }
151898
+ deleteRoutingRule(id) {
151899
+ const result = this.db.prepare("DELETE FROM routing_rules WHERE id = ?").run(id);
151900
+ return result.changes > 0;
151901
+ }
151902
+ resolveRouting(params) {
151903
+ const rules = this.db.prepare(`SELECT * FROM routing_rules WHERE enabled = 1 ORDER BY priority ASC`).all();
151904
+ for (const row of rules) {
151905
+ const rule = this.rowToRoutingRule(row);
151906
+ if (rule.messageType !== "all" && rule.messageType !== params.messageType)
151907
+ continue;
151908
+ if (rule.fromPattern && !matchPattern2(rule.fromPattern, params.fromNumber))
151909
+ continue;
151910
+ if (rule.toPattern && !matchPattern2(rule.toPattern, params.toNumber))
151911
+ continue;
151912
+ if (rule.keyword && params.body) {
151913
+ if (!params.body.toLowerCase().includes(rule.keyword.toLowerCase()))
151914
+ continue;
151915
+ } else if (rule.keyword && !params.body) {
151916
+ continue;
151917
+ }
151918
+ if (rule.timeOfDay && !matchTimeOfDay(rule.timeOfDay))
151919
+ continue;
151920
+ if (rule.dayOfWeek && !matchDayOfWeek(rule.dayOfWeek))
151921
+ continue;
151922
+ return {
151923
+ assistantId: rule.targetAssistantId,
151924
+ assistantName: rule.targetAssistantName,
151925
+ ruleId: rule.id
151926
+ };
151927
+ }
151928
+ return null;
151929
+ }
151930
+ cleanup(maxAgeDays, maxCallLogs, maxSmsLogs) {
151931
+ let deleted = 0;
151932
+ const cutoff = new Date;
151933
+ cutoff.setDate(cutoff.getDate() - maxAgeDays);
151934
+ const cutoffStr = cutoff.toISOString();
151935
+ const callResult = this.db.prepare("DELETE FROM call_logs WHERE created_at < ?").run(cutoffStr);
151936
+ deleted += callResult.changes;
151937
+ const smsResult = this.db.prepare("DELETE FROM sms_logs WHERE created_at < ?").run(cutoffStr);
151938
+ deleted += smsResult.changes;
151939
+ const callCount = this.db.prepare("SELECT COUNT(*) as cnt FROM call_logs").get();
151940
+ if (Number(callCount.cnt) > maxCallLogs) {
151941
+ const excess = Number(callCount.cnt) - maxCallLogs;
151942
+ const trimResult = this.db.prepare(`DELETE FROM call_logs WHERE id IN (
151943
+ SELECT id FROM call_logs ORDER BY created_at ASC LIMIT ?
151944
+ )`).run(excess);
151945
+ deleted += trimResult.changes;
151946
+ }
151947
+ const smsCount = this.db.prepare("SELECT COUNT(*) as cnt FROM sms_logs").get();
151948
+ if (Number(smsCount.cnt) > maxSmsLogs) {
151949
+ const excess = Number(smsCount.cnt) - maxSmsLogs;
151950
+ const trimResult = this.db.prepare(`DELETE FROM sms_logs WHERE id IN (
151951
+ SELECT id FROM sms_logs ORDER BY created_at ASC LIMIT ?
151952
+ )`).run(excess);
151953
+ deleted += trimResult.changes;
151954
+ }
151955
+ return deleted;
151956
+ }
151957
+ close() {
151958
+ try {
151959
+ this.db.close();
151960
+ } catch {}
151961
+ }
151962
+ rowToPhoneNumber(row) {
151963
+ return {
151964
+ id: String(row.id),
151965
+ number: String(row.number),
151966
+ friendlyName: row.friendly_name ? String(row.friendly_name) : null,
151967
+ twilioSid: row.twilio_sid ? String(row.twilio_sid) : null,
151968
+ status: String(row.status),
151969
+ capabilities: {
151970
+ voice: Boolean(row.voice_capable),
151971
+ sms: Boolean(row.sms_capable),
151972
+ whatsapp: Boolean(row.whatsapp_capable)
151973
+ },
151974
+ createdAt: String(row.created_at),
151975
+ updatedAt: String(row.updated_at)
151976
+ };
151977
+ }
151978
+ rowToCallLog(row) {
151979
+ return {
151980
+ id: String(row.id),
151981
+ callSid: row.call_sid ? String(row.call_sid) : null,
151982
+ fromNumber: String(row.from_number),
151983
+ toNumber: String(row.to_number),
151984
+ direction: String(row.direction),
151985
+ status: String(row.status),
151986
+ assistantId: row.assistant_id ? String(row.assistant_id) : null,
151987
+ duration: row.duration != null ? Number(row.duration) : null,
151988
+ recordingUrl: row.recording_url ? String(row.recording_url) : null,
151989
+ startedAt: row.started_at ? String(row.started_at) : null,
151990
+ endedAt: row.ended_at ? String(row.ended_at) : null,
151991
+ createdAt: String(row.created_at)
151992
+ };
151993
+ }
151994
+ rowToSmsLog(row) {
151995
+ return {
151996
+ id: String(row.id),
151997
+ messageSid: row.message_sid ? String(row.message_sid) : null,
151998
+ fromNumber: String(row.from_number),
151999
+ toNumber: String(row.to_number),
152000
+ direction: String(row.direction),
152001
+ messageType: String(row.message_type),
152002
+ body: String(row.body),
152003
+ status: String(row.status),
152004
+ assistantId: row.assistant_id ? String(row.assistant_id) : null,
152005
+ createdAt: String(row.created_at)
152006
+ };
152007
+ }
152008
+ rowToRoutingRule(row) {
152009
+ return {
152010
+ id: String(row.id),
152011
+ name: String(row.name),
152012
+ priority: Number(row.priority),
152013
+ fromPattern: row.from_pattern ? String(row.from_pattern) : null,
152014
+ toPattern: row.to_pattern ? String(row.to_pattern) : null,
152015
+ messageType: String(row.message_type),
152016
+ timeOfDay: row.time_of_day ? String(row.time_of_day) : null,
152017
+ dayOfWeek: row.day_of_week ? String(row.day_of_week) : null,
152018
+ keyword: row.keyword ? String(row.keyword) : null,
152019
+ targetAssistantId: String(row.target_assistant_id),
152020
+ targetAssistantName: String(row.target_assistant_name),
152021
+ enabled: Boolean(row.enabled),
152022
+ createdAt: String(row.created_at),
152023
+ updatedAt: String(row.updated_at)
152024
+ };
152025
+ }
152026
+ }
152027
+ function matchPattern2(pattern, value) {
152028
+ if (pattern === "*")
152029
+ return true;
152030
+ if (pattern.endsWith("*")) {
152031
+ return value.startsWith(pattern.slice(0, -1));
152032
+ }
152033
+ if (pattern.startsWith("*")) {
152034
+ return value.endsWith(pattern.slice(1));
152035
+ }
152036
+ return pattern === value;
152037
+ }
152038
+ function matchTimeOfDay(timeRange) {
152039
+ const [start, end] = timeRange.split("-");
152040
+ if (!start || !end)
152041
+ return true;
152042
+ const now2 = new Date;
152043
+ const currentMinutes = now2.getHours() * 60 + now2.getMinutes();
152044
+ const [startH, startM] = start.split(":").map(Number);
152045
+ const [endH, endM] = end.split(":").map(Number);
152046
+ const startMinutes = startH * 60 + startM;
152047
+ const endMinutes = endH * 60 + endM;
152048
+ if (startMinutes <= endMinutes) {
152049
+ return currentMinutes >= startMinutes && currentMinutes <= endMinutes;
152050
+ }
152051
+ return currentMinutes >= startMinutes || currentMinutes <= endMinutes;
152052
+ }
152053
+ function matchDayOfWeek(dayList) {
152054
+ const days = ["sun", "mon", "tue", "wed", "thu", "fri", "sat"];
152055
+ const today = days[new Date().getDay()];
152056
+ const allowedDays = dayList.toLowerCase().split(",").map((d5) => d5.trim());
152057
+ return allowedDays.includes(today);
152058
+ }
152059
+
152060
+ // ../core/src/telephony/twilio-client.ts
152061
+ var TWILIO_API_BASE = "https://api.twilio.com/2010-04-01";
152062
+
152063
+ class TwilioClient {
152064
+ accountSid;
152065
+ authToken;
152066
+ authHeader;
152067
+ constructor(config) {
152068
+ this.accountSid = config.accountSid;
152069
+ this.authToken = config.authToken;
152070
+ this.authHeader = "Basic " + Buffer.from(`${this.accountSid}:${this.authToken}`).toString("base64");
152071
+ }
152072
+ isConfigured() {
152073
+ return Boolean(this.accountSid && this.authToken);
152074
+ }
152075
+ async makeCall(params) {
152076
+ const body = new URLSearchParams;
152077
+ body.append("To", params.to);
152078
+ body.append("From", params.from);
152079
+ if (params.url) {
152080
+ body.append("Url", params.url);
152081
+ } else if (params.twiml) {
152082
+ body.append("Twiml", params.twiml);
152083
+ }
152084
+ if (params.statusCallback) {
152085
+ body.append("StatusCallback", params.statusCallback);
152086
+ body.append("StatusCallbackEvent", "initiated ringing answered completed");
152087
+ body.append("StatusCallbackMethod", "POST");
152088
+ }
152089
+ if (params.record) {
152090
+ body.append("Record", "true");
152091
+ }
152092
+ return this.post(`/Accounts/${this.accountSid}/Calls.json`, body);
152093
+ }
152094
+ async updateCall(callSid, updates) {
152095
+ const body = new URLSearchParams;
152096
+ if (updates.status) {
152097
+ body.append("Status", updates.status);
152098
+ }
152099
+ if (updates.url) {
152100
+ body.append("Url", updates.url);
152101
+ }
152102
+ if (updates.twiml) {
152103
+ body.append("Twiml", updates.twiml);
152104
+ }
152105
+ return this.post(`/Accounts/${this.accountSid}/Calls/${callSid}.json`, body);
152106
+ }
152107
+ async getCall(callSid) {
152108
+ return this.get(`/Accounts/${this.accountSid}/Calls/${callSid}.json`);
152109
+ }
152110
+ async sendSms(params) {
152111
+ const body = new URLSearchParams;
152112
+ body.append("To", params.to);
152113
+ body.append("From", params.from);
152114
+ body.append("Body", params.body);
152115
+ if (params.statusCallback) {
152116
+ body.append("StatusCallback", params.statusCallback);
152117
+ }
152118
+ return this.post(`/Accounts/${this.accountSid}/Messages.json`, body);
152119
+ }
152120
+ async sendWhatsApp(params) {
152121
+ const to3 = params.to.startsWith("whatsapp:") ? params.to : `whatsapp:${params.to}`;
152122
+ const from = params.from.startsWith("whatsapp:") ? params.from : `whatsapp:${params.from}`;
152123
+ return this.sendSms({
152124
+ ...params,
152125
+ to: to3,
152126
+ from
152127
+ });
152128
+ }
152129
+ async listPhoneNumbers() {
152130
+ return this.get(`/Accounts/${this.accountSid}/IncomingPhoneNumbers.json`);
152131
+ }
152132
+ async getPhoneNumber(phoneSid) {
152133
+ return this.get(`/Accounts/${this.accountSid}/IncomingPhoneNumbers/${phoneSid}.json`);
152134
+ }
152135
+ async updatePhoneNumber(phoneSid, updates) {
152136
+ const body = new URLSearchParams;
152137
+ if (updates.voiceUrl) {
152138
+ body.append("VoiceUrl", updates.voiceUrl);
152139
+ body.append("VoiceMethod", updates.voiceMethod || "POST");
152140
+ }
152141
+ if (updates.smsUrl) {
152142
+ body.append("SmsUrl", updates.smsUrl);
152143
+ body.append("SmsMethod", updates.smsMethod || "POST");
152144
+ }
152145
+ if (updates.statusCallback) {
152146
+ body.append("StatusCallback", updates.statusCallback);
152147
+ }
152148
+ return this.post(`/Accounts/${this.accountSid}/IncomingPhoneNumbers/${phoneSid}.json`, body);
152149
+ }
152150
+ async verifyCredentials() {
152151
+ return this.get(`/Accounts/${this.accountSid}.json`);
152152
+ }
152153
+ async get(path2) {
152154
+ try {
152155
+ const response = await fetch(`${TWILIO_API_BASE}${path2}`, {
152156
+ method: "GET",
152157
+ headers: {
152158
+ Authorization: this.authHeader,
152159
+ Accept: "application/json"
152160
+ }
152161
+ });
152162
+ const data = await response.json();
152163
+ if (!response.ok) {
152164
+ return {
152165
+ success: false,
152166
+ error: data.message || `HTTP ${response.status}`,
152167
+ statusCode: response.status
152168
+ };
152169
+ }
152170
+ return { success: true, data, statusCode: response.status };
152171
+ } catch (error2) {
152172
+ return {
152173
+ success: false,
152174
+ error: error2 instanceof Error ? error2.message : String(error2),
152175
+ statusCode: 0
152176
+ };
152177
+ }
152178
+ }
152179
+ async post(path2, body) {
152180
+ try {
152181
+ const response = await fetch(`${TWILIO_API_BASE}${path2}`, {
152182
+ method: "POST",
152183
+ headers: {
152184
+ Authorization: this.authHeader,
152185
+ "Content-Type": "application/x-www-form-urlencoded",
152186
+ Accept: "application/json"
152187
+ },
152188
+ body: body.toString()
152189
+ });
152190
+ const data = await response.json();
152191
+ if (!response.ok) {
152192
+ return {
152193
+ success: false,
152194
+ error: data.message || `HTTP ${response.status}`,
152195
+ statusCode: response.status
152196
+ };
152197
+ }
152198
+ return { success: true, data, statusCode: response.status };
152199
+ } catch (error2) {
152200
+ return {
152201
+ success: false,
152202
+ error: error2 instanceof Error ? error2.message : String(error2),
152203
+ statusCode: 0
152204
+ };
152205
+ }
152206
+ }
152207
+ }
152208
+
152209
+ // ../core/src/telephony/call-manager.ts
152210
+ class CallManager {
152211
+ activeCalls = new Map;
152212
+ maxCallDurationMs;
152213
+ staleTimeoutMs;
152214
+ constructor(config) {
152215
+ this.maxCallDurationMs = (config?.maxCallDurationSeconds || 3600) * 1000;
152216
+ this.staleTimeoutMs = (config?.staleTimeoutSeconds || 300) * 1000;
152217
+ }
152218
+ addCall(params) {
152219
+ const call = {
152220
+ callSid: params.callSid,
152221
+ streamSid: null,
152222
+ fromNumber: params.fromNumber,
152223
+ toNumber: params.toNumber,
152224
+ direction: params.direction,
152225
+ state: "connecting",
152226
+ assistantId: params.assistantId || null,
152227
+ bridgeId: null,
152228
+ startedAt: Date.now(),
152229
+ lastActivityAt: Date.now()
152230
+ };
152231
+ this.activeCalls.set(params.callSid, call);
152232
+ return call;
152233
+ }
152234
+ getCall(callSid) {
152235
+ return this.activeCalls.get(callSid) || null;
152236
+ }
152237
+ updateState(callSid, state) {
152238
+ const call = this.activeCalls.get(callSid);
152239
+ if (!call)
152240
+ return false;
152241
+ const validTransitions = {
152242
+ connecting: ["ringing", "active", "ending"],
152243
+ ringing: ["bridging", "active", "ending"],
152244
+ bridging: ["active", "ending"],
152245
+ active: ["ending"],
152246
+ ending: []
152247
+ };
152248
+ if (!validTransitions[call.state].includes(state)) {
152249
+ return false;
152250
+ }
152251
+ call.state = state;
152252
+ call.lastActivityAt = Date.now();
152253
+ return true;
152254
+ }
152255
+ setStreamSid(callSid, streamSid) {
152256
+ const call = this.activeCalls.get(callSid);
152257
+ if (!call)
152258
+ return false;
152259
+ call.streamSid = streamSid;
152260
+ call.lastActivityAt = Date.now();
152261
+ return true;
152262
+ }
152263
+ setBridgeId(callSid, bridgeId) {
152264
+ const call = this.activeCalls.get(callSid);
152265
+ if (!call)
152266
+ return false;
152267
+ call.bridgeId = bridgeId;
152268
+ call.lastActivityAt = Date.now();
152269
+ return true;
152270
+ }
152271
+ touchCall(callSid) {
152272
+ const call = this.activeCalls.get(callSid);
152273
+ if (call) {
152274
+ call.lastActivityAt = Date.now();
152275
+ }
152276
+ }
152277
+ endCall(callSid) {
152278
+ const call = this.activeCalls.get(callSid);
152279
+ if (!call)
152280
+ return null;
152281
+ call.state = "ending";
152282
+ this.activeCalls.delete(callSid);
152283
+ return call;
152284
+ }
152285
+ getActiveCalls() {
152286
+ return Array.from(this.activeCalls.values());
152287
+ }
152288
+ getActiveCallCount() {
152289
+ return this.activeCalls.size;
152290
+ }
152291
+ getCallByStreamSid(streamSid) {
152292
+ for (const call of this.activeCalls.values()) {
152293
+ if (call.streamSid === streamSid)
152294
+ return call;
152295
+ }
152296
+ return null;
152297
+ }
152298
+ cleanupStaleCalls() {
152299
+ const now2 = Date.now();
152300
+ const removed = [];
152301
+ for (const [callSid, call] of this.activeCalls) {
152302
+ if (now2 - call.startedAt > this.maxCallDurationMs) {
152303
+ this.activeCalls.delete(callSid);
152304
+ removed.push(callSid);
152305
+ continue;
152306
+ }
152307
+ if (now2 - call.lastActivityAt > this.staleTimeoutMs) {
152308
+ this.activeCalls.delete(callSid);
152309
+ removed.push(callSid);
152310
+ }
152311
+ }
152312
+ return removed;
152313
+ }
152314
+ getCallDuration(callSid) {
152315
+ const call = this.activeCalls.get(callSid);
152316
+ if (!call)
152317
+ return null;
152318
+ return Math.floor((Date.now() - call.startedAt) / 1000);
152319
+ }
152320
+ endAllCalls() {
152321
+ const calls = Array.from(this.activeCalls.values());
152322
+ this.activeCalls.clear();
152323
+ return calls;
152324
+ }
152325
+ }
152326
+
152327
+ // ../core/src/telephony/voice-bridge.ts
152328
+ init_src2();
152329
+
152330
+ // ../core/src/telephony/audio-codec.ts
152331
+ var MULAW_MAX = 8191;
152332
+ var MULAW_BIAS = 33;
152333
+ function pcmSampleToMulaw(sample) {
152334
+ const sign2 = sample >> 8 & 128;
152335
+ if (sign2 !== 0)
152336
+ sample = -sample;
152337
+ if (sample > MULAW_MAX)
152338
+ sample = MULAW_MAX;
152339
+ sample += MULAW_BIAS;
152340
+ let exponent = 7;
152341
+ let mask = 16384;
152342
+ while (exponent > 0 && (sample & mask) === 0) {
152343
+ exponent--;
152344
+ mask >>= 1;
152345
+ }
152346
+ const mantissa = sample >> exponent + 3 & 15;
152347
+ const mulawByte = ~(sign2 | exponent << 4 | mantissa) & 255;
152348
+ return mulawByte;
152349
+ }
152350
+ function mulawSampleToPcm(mulawByte) {
152351
+ mulawByte = ~mulawByte & 255;
152352
+ const sign2 = mulawByte & 128;
152353
+ const exponent = mulawByte >> 4 & 7;
152354
+ const mantissa = mulawByte & 15;
152355
+ let sample = (mantissa << 3) + MULAW_BIAS << exponent;
152356
+ sample -= MULAW_BIAS;
152357
+ return sign2 !== 0 ? -sample : sample;
152358
+ }
152359
+ function pcmToMulaw(pcmBuffer) {
152360
+ const numSamples = Math.floor(pcmBuffer.length / 2);
152361
+ const mulawBuffer = Buffer.alloc(numSamples);
152362
+ for (let i5 = 0;i5 < numSamples; i5++) {
152363
+ const sample = pcmBuffer.readInt16LE(i5 * 2);
152364
+ mulawBuffer[i5] = pcmSampleToMulaw(sample);
152365
+ }
152366
+ return mulawBuffer;
152367
+ }
152368
+ function mulawToPcm(mulawBuffer) {
152369
+ const pcmBuffer = Buffer.alloc(mulawBuffer.length * 2);
152370
+ for (let i5 = 0;i5 < mulawBuffer.length; i5++) {
152371
+ const sample = mulawSampleToPcm(mulawBuffer[i5]);
152372
+ pcmBuffer.writeInt16LE(sample, i5 * 2);
152373
+ }
152374
+ return pcmBuffer;
152375
+ }
152376
+ function downsample16kTo8k(pcm16k) {
152377
+ const numSamples = Math.floor(pcm16k.length / 2);
152378
+ const outputSamples = Math.floor(numSamples / 2);
152379
+ const pcm8k = Buffer.alloc(outputSamples * 2);
152380
+ for (let i5 = 0;i5 < outputSamples; i5++) {
152381
+ const sample = pcm16k.readInt16LE(i5 * 2 * 2);
152382
+ pcm8k.writeInt16LE(sample, i5 * 2);
152383
+ }
152384
+ return pcm8k;
152385
+ }
152386
+ function upsample8kTo16k(pcm8k) {
152387
+ const numSamples = Math.floor(pcm8k.length / 2);
152388
+ const pcm16k = Buffer.alloc(numSamples * 2 * 2);
152389
+ for (let i5 = 0;i5 < numSamples; i5++) {
152390
+ const current = pcm8k.readInt16LE(i5 * 2);
152391
+ const next = i5 + 1 < numSamples ? pcm8k.readInt16LE((i5 + 1) * 2) : current;
152392
+ const interpolated = Math.round((current + next) / 2);
152393
+ pcm16k.writeInt16LE(current, i5 * 4);
152394
+ pcm16k.writeInt16LE(interpolated, i5 * 4 + 2);
152395
+ }
152396
+ return pcm16k;
152397
+ }
152398
+ function twilioToElevenLabs(mulawBuffer) {
152399
+ const pcm8k = mulawToPcm(mulawBuffer);
152400
+ return upsample8kTo16k(pcm8k);
152401
+ }
152402
+ function elevenLabsToTwilio(pcm16k) {
152403
+ const pcm8k = downsample16kTo8k(pcm16k);
152404
+ return pcmToMulaw(pcm8k);
152405
+ }
152406
+ function decodeTwilioPayload(base64Payload) {
152407
+ const mulawBuffer = Buffer.from(base64Payload, "base64");
152408
+ return twilioToElevenLabs(mulawBuffer);
152409
+ }
152410
+ function encodeTwilioPayload(pcm16k) {
152411
+ const mulawBuffer = elevenLabsToTwilio(pcm16k);
152412
+ return mulawBuffer.toString("base64");
152413
+ }
152414
+
152415
+ // ../core/src/telephony/voice-bridge.ts
152416
+ var ELEVENLABS_WS_BASE = "wss://api.elevenlabs.io/v1/convai/conversation";
152417
+
152418
+ class VoiceBridge {
152419
+ config;
152420
+ connections = new Map;
152421
+ constructor(config) {
152422
+ this.config = config;
152423
+ }
152424
+ isConfigured() {
152425
+ return Boolean(this.config.elevenLabsApiKey && this.config.elevenLabsAgentId);
152426
+ }
152427
+ async createBridge(callSid, streamSid, sendToTwilio) {
152428
+ const id = `bridge_${generateId().slice(0, 12)}`;
152429
+ const connection = new BridgeConnection({
152430
+ id,
152431
+ callSid,
152432
+ streamSid,
152433
+ config: this.config,
152434
+ sendToTwilio
152435
+ });
152436
+ this.connections.set(id, connection);
152437
+ try {
152438
+ await connection.connect();
152439
+ } catch (error2) {
152440
+ this.connections.delete(id);
152441
+ throw error2;
152442
+ }
152443
+ return id;
152444
+ }
152445
+ handleTwilioMedia(bridgeId, message) {
152446
+ const connection = this.connections.get(bridgeId);
152447
+ if (!connection)
152448
+ return;
152449
+ if (message.event === "media" && message.media?.payload) {
152450
+ connection.forwardToElevenLabs(message.media.payload);
152451
+ } else if (message.event === "stop") {
152452
+ connection.close();
152453
+ this.connections.delete(bridgeId);
152454
+ }
152455
+ }
152456
+ closeBridge(bridgeId) {
152457
+ const connection = this.connections.get(bridgeId);
152458
+ if (connection) {
152459
+ connection.close();
152460
+ this.connections.delete(bridgeId);
152461
+ }
152462
+ }
152463
+ getConnection(bridgeId) {
152464
+ const conn = this.connections.get(bridgeId);
152465
+ if (!conn)
152466
+ return null;
152467
+ return conn.getInfo();
152468
+ }
152469
+ getActiveConnections() {
152470
+ return Array.from(this.connections.values()).map((c6) => c6.getInfo());
152471
+ }
152472
+ closeAll() {
152473
+ for (const connection of this.connections.values()) {
152474
+ connection.close();
152475
+ }
152476
+ this.connections.clear();
152477
+ }
152478
+ }
152479
+
152480
+ class BridgeConnection {
152481
+ id;
152482
+ callSid;
152483
+ streamSid;
152484
+ config;
152485
+ sendToTwilio;
152486
+ elevenLabsWs = null;
152487
+ state = "connecting";
152488
+ startedAt;
152489
+ constructor(options) {
152490
+ this.id = options.id;
152491
+ this.callSid = options.callSid;
152492
+ this.streamSid = options.streamSid;
152493
+ this.config = options.config;
152494
+ this.sendToTwilio = options.sendToTwilio;
152495
+ this.startedAt = Date.now();
152496
+ }
152497
+ async connect() {
152498
+ const url = `${ELEVENLABS_WS_BASE}?agent_id=${this.config.elevenLabsAgentId}`;
152499
+ return new Promise((resolve5, reject) => {
152500
+ try {
152501
+ this.elevenLabsWs = new WebSocket(url, {
152502
+ headers: {
152503
+ "xi-api-key": this.config.elevenLabsApiKey
152504
+ }
152505
+ });
152506
+ this.elevenLabsWs.onopen = () => {
152507
+ this.state = "active";
152508
+ resolve5();
152509
+ };
152510
+ this.elevenLabsWs.onmessage = (event) => {
152511
+ this.handleElevenLabsMessage(event.data);
152512
+ };
152513
+ this.elevenLabsWs.onerror = (event) => {
152514
+ console.error(`[VoiceBridge ${this.id}] ElevenLabs WS error:`, event);
152515
+ if (this.state === "connecting") {
152516
+ reject(new Error("Failed to connect to ElevenLabs"));
152517
+ }
152518
+ };
152519
+ this.elevenLabsWs.onclose = () => {
152520
+ this.state = "closed";
152521
+ };
152522
+ setTimeout(() => {
152523
+ if (this.state === "connecting") {
152524
+ this.close();
152525
+ reject(new Error("ElevenLabs connection timeout"));
152526
+ }
152527
+ }, 1e4);
152528
+ } catch (error2) {
152529
+ reject(error2);
152530
+ }
152531
+ });
152532
+ }
152533
+ forwardToElevenLabs(base64Payload) {
152534
+ if (this.state !== "active" || !this.elevenLabsWs)
152535
+ return;
152536
+ try {
152537
+ const pcm16k = decodeTwilioPayload(base64Payload);
152538
+ const message = JSON.stringify({
152539
+ user_audio_chunk: pcm16k.toString("base64")
152540
+ });
152541
+ if (this.elevenLabsWs.readyState === WebSocket.OPEN) {
152542
+ this.elevenLabsWs.send(message);
152543
+ }
152544
+ } catch (error2) {
152545
+ console.error(`[VoiceBridge ${this.id}] Error forwarding to ElevenLabs:`, error2);
152546
+ }
152547
+ }
152548
+ handleElevenLabsMessage(data) {
152549
+ try {
152550
+ const message = JSON.parse(data);
152551
+ if (message.audio?.chunk) {
152552
+ const pcm16k = Buffer.from(message.audio.chunk, "base64");
152553
+ const twilioPayload = encodeTwilioPayload(pcm16k);
152554
+ const twilioMessage = JSON.stringify({
152555
+ event: "media",
152556
+ streamSid: this.streamSid,
152557
+ media: {
152558
+ payload: twilioPayload
152559
+ }
152560
+ });
152561
+ this.sendToTwilio(twilioMessage);
152562
+ }
152563
+ if (message.type === "conversation_initiation_metadata") {} else if (message.type === "agent_response") {} else if (message.type === "user_transcript") {}
152564
+ } catch (error2) {
152565
+ console.error(`[VoiceBridge ${this.id}] Error handling ElevenLabs message:`, error2);
152566
+ }
152567
+ }
152568
+ close() {
152569
+ if (this.state === "closed" || this.state === "closing")
152570
+ return;
152571
+ this.state = "closing";
152572
+ if (this.elevenLabsWs) {
152573
+ try {
152574
+ this.elevenLabsWs.close();
152575
+ } catch {}
152576
+ this.elevenLabsWs = null;
152577
+ }
152578
+ this.state = "closed";
152579
+ }
152580
+ getInfo() {
152581
+ return {
152582
+ id: this.id,
152583
+ callSid: this.callSid,
152584
+ streamSid: this.streamSid,
152585
+ state: this.state,
152586
+ startedAt: this.startedAt
152587
+ };
152588
+ }
152589
+ }
152590
+
152591
+ // ../core/src/telephony/manager.ts
152592
+ class TelephonyManager {
152593
+ assistantId;
152594
+ assistantName;
152595
+ config;
152596
+ store;
152597
+ twilioClient = null;
152598
+ callManager;
152599
+ voiceBridge = null;
152600
+ constructor(options) {
152601
+ this.assistantId = options.assistantId;
152602
+ this.assistantName = options.assistantName;
152603
+ this.config = options.config;
152604
+ this.store = new TelephonyStore;
152605
+ this.callManager = new CallManager({
152606
+ maxCallDurationSeconds: options.config.voice?.maxCallDurationSeconds
152607
+ });
152608
+ const accountSid = process.env.TWILIO_ACCOUNT_SID;
152609
+ const authToken = process.env.TWILIO_AUTH_TOKEN;
152610
+ if (accountSid && authToken) {
152611
+ this.twilioClient = new TwilioClient({ accountSid, authToken });
152612
+ }
152613
+ const elevenLabsApiKey = process.env.ELEVENLABS_API_KEY;
152614
+ const elevenLabsAgentId = this.config.elevenLabsAgentId || process.env.ELEVENLABS_AGENT_ID;
152615
+ if (elevenLabsApiKey && elevenLabsAgentId) {
152616
+ this.voiceBridge = new VoiceBridge({
152617
+ elevenLabsApiKey,
152618
+ elevenLabsAgentId
152619
+ });
152620
+ }
152621
+ }
152622
+ async sendSms(to3, body, from) {
152623
+ if (!this.twilioClient) {
152624
+ return {
152625
+ success: false,
152626
+ message: "Twilio is not configured. Set TWILIO_ACCOUNT_SID and TWILIO_AUTH_TOKEN."
152627
+ };
152628
+ }
152629
+ const fromNumber = from || this.config.defaultPhoneNumber || process.env.TWILIO_PHONE_NUMBER;
152630
+ if (!fromNumber) {
152631
+ return {
152632
+ success: false,
152633
+ message: "No phone number configured. Set telephony.defaultPhoneNumber or TWILIO_PHONE_NUMBER."
152634
+ };
152635
+ }
152636
+ const webhookUrl = this.config.webhookUrl || process.env.TELEPHONY_WEBHOOK_URL;
152637
+ const statusCallback = webhookUrl ? `${webhookUrl}/api/v1/telephony/webhooks/sms-status` : undefined;
152638
+ const result = await this.twilioClient.sendSms({
152639
+ to: to3,
152640
+ from: fromNumber,
152641
+ body,
152642
+ statusCallback
152643
+ });
152644
+ if (!result.success) {
152645
+ return { success: false, message: `Failed to send SMS: ${result.error}` };
152646
+ }
152647
+ const log2 = this.store.createSmsLog({
152648
+ messageSid: result.data?.sid,
152649
+ fromNumber,
152650
+ toNumber: to3,
152651
+ direction: "outbound",
152652
+ messageType: "sms",
152653
+ body,
152654
+ status: "queued",
152655
+ assistantId: this.assistantId
152656
+ });
152657
+ return {
152658
+ success: true,
152659
+ message: `SMS sent to ${to3}.`,
152660
+ messageSid: result.data?.sid,
152661
+ id: log2.id
152662
+ };
152663
+ }
152664
+ async sendWhatsApp(to3, body, from) {
152665
+ if (!this.twilioClient) {
152666
+ return {
152667
+ success: false,
152668
+ message: "Twilio is not configured. Set TWILIO_ACCOUNT_SID and TWILIO_AUTH_TOKEN."
152669
+ };
152670
+ }
152671
+ const fromNumber = from || this.config.defaultPhoneNumber || process.env.TWILIO_PHONE_NUMBER;
152672
+ if (!fromNumber) {
152673
+ return {
152674
+ success: false,
152675
+ message: "No phone number configured."
152676
+ };
152677
+ }
152678
+ const webhookUrl = this.config.webhookUrl || process.env.TELEPHONY_WEBHOOK_URL;
152679
+ const statusCallback = webhookUrl ? `${webhookUrl}/api/v1/telephony/webhooks/sms-status` : undefined;
152680
+ const result = await this.twilioClient.sendWhatsApp({
152681
+ to: to3,
152682
+ from: fromNumber,
152683
+ body,
152684
+ statusCallback
152685
+ });
152686
+ if (!result.success) {
152687
+ return { success: false, message: `Failed to send WhatsApp: ${result.error}` };
152688
+ }
152689
+ const log2 = this.store.createSmsLog({
152690
+ messageSid: result.data?.sid,
152691
+ fromNumber: `whatsapp:${fromNumber}`,
152692
+ toNumber: `whatsapp:${to3}`,
152693
+ direction: "outbound",
152694
+ messageType: "whatsapp",
152695
+ body,
152696
+ status: "queued",
152697
+ assistantId: this.assistantId
152698
+ });
152699
+ return {
152700
+ success: true,
152701
+ message: `WhatsApp message sent to ${to3}.`,
152702
+ messageSid: result.data?.sid,
152703
+ id: log2.id
152704
+ };
152705
+ }
152706
+ async makeCall(to3, from) {
152707
+ if (!this.twilioClient) {
152708
+ return {
152709
+ success: false,
152710
+ message: "Twilio is not configured. Set TWILIO_ACCOUNT_SID and TWILIO_AUTH_TOKEN."
152711
+ };
152712
+ }
152713
+ const fromNumber = from || this.config.defaultPhoneNumber || process.env.TWILIO_PHONE_NUMBER;
152714
+ if (!fromNumber) {
152715
+ return {
152716
+ success: false,
152717
+ message: "No phone number configured."
152718
+ };
152719
+ }
152720
+ const webhookUrl = this.config.webhookUrl || process.env.TELEPHONY_WEBHOOK_URL;
152721
+ if (!webhookUrl) {
152722
+ return {
152723
+ success: false,
152724
+ message: "No webhook URL configured. Set telephony.webhookUrl or TELEPHONY_WEBHOOK_URL."
152725
+ };
152726
+ }
152727
+ const result = await this.twilioClient.makeCall({
152728
+ to: to3,
152729
+ from: fromNumber,
152730
+ url: `${webhookUrl}/api/v1/telephony/webhooks/voice`,
152731
+ statusCallback: `${webhookUrl}/api/v1/telephony/webhooks/voice-status`,
152732
+ record: this.config.voice?.recordCalls
152733
+ });
152734
+ if (!result.success) {
152735
+ return { success: false, message: `Failed to make call: ${result.error}` };
152736
+ }
152737
+ const callSid = result.data?.sid;
152738
+ const log2 = this.store.createCallLog({
152739
+ callSid,
152740
+ fromNumber,
152741
+ toNumber: to3,
152742
+ direction: "outbound",
152743
+ status: "pending",
152744
+ assistantId: this.assistantId
152745
+ });
152746
+ this.callManager.addCall({
152747
+ callSid,
152748
+ fromNumber,
152749
+ toNumber: to3,
152750
+ direction: "outbound",
152751
+ assistantId: this.assistantId
152752
+ });
152753
+ return {
152754
+ success: true,
152755
+ message: `Calling ${to3}...`,
152756
+ callSid,
152757
+ id: log2.id
152758
+ };
152759
+ }
152760
+ getCallHistory(options) {
152761
+ return this.store.listCallLogs({
152762
+ assistantId: this.assistantId,
152763
+ limit: options?.limit || 20
152764
+ });
152765
+ }
152766
+ getSmsHistory(options) {
152767
+ return this.store.listSmsLogs({
152768
+ assistantId: this.assistantId,
152769
+ messageType: options?.messageType,
152770
+ limit: options?.limit || 20
152771
+ });
152772
+ }
152773
+ listPhoneNumbers() {
152774
+ return this.store.listPhoneNumbers("active");
152775
+ }
152776
+ async syncPhoneNumbers() {
152777
+ if (!this.twilioClient) {
152778
+ return { success: false, message: "Twilio is not configured." };
152779
+ }
152780
+ const result = await this.twilioClient.listPhoneNumbers();
152781
+ if (!result.success) {
152782
+ return { success: false, message: `Failed to list numbers: ${result.error}` };
152783
+ }
152784
+ const numbers = result.data?.incoming_phone_numbers || [];
152785
+ let synced = 0;
152786
+ for (const num of numbers) {
152787
+ const phoneNumber = String(num.phone_number || "");
152788
+ const existing = this.store.getPhoneNumberByNumber(phoneNumber);
152789
+ if (!existing && phoneNumber) {
152790
+ this.store.addPhoneNumber(phoneNumber, num.friendly_name ? String(num.friendly_name) : null, num.sid ? String(num.sid) : null, {
152791
+ voice: Boolean(num.capabilities?.voice),
152792
+ sms: Boolean(num.capabilities?.sms)
152793
+ });
152794
+ synced++;
152795
+ }
152796
+ }
152797
+ return {
152798
+ success: true,
152799
+ message: `Synced ${synced} phone number${synced !== 1 ? "s" : ""} from Twilio.`
152800
+ };
152801
+ }
152802
+ listRoutingRules() {
152803
+ return this.store.listRoutingRules();
152804
+ }
152805
+ createRoutingRule(params) {
152806
+ const rule = this.store.createRoutingRule(params);
152807
+ return {
152808
+ success: true,
152809
+ message: `Routing rule "${rule.name}" created (priority ${rule.priority}).`,
152810
+ id: rule.id
152811
+ };
152812
+ }
152813
+ deleteRoutingRule(id) {
152814
+ const success = this.store.deleteRoutingRule(id);
152815
+ return {
152816
+ success,
152817
+ message: success ? "Routing rule deleted." : "Routing rule not found."
152818
+ };
152819
+ }
152820
+ getStatus() {
152821
+ const phoneNumbers = this.store.listPhoneNumbers("active");
152822
+ const recentCalls = this.store.listCallLogs({ limit: 100 });
152823
+ const recentMessages = this.store.listSmsLogs({ limit: 100 });
152824
+ const routingRules = this.store.listRoutingRules();
152825
+ return {
152826
+ enabled: this.config.enabled !== false,
152827
+ twilioConfigured: this.twilioClient?.isConfigured() ?? false,
152828
+ elevenLabsConfigured: this.voiceBridge?.isConfigured() ?? false,
152829
+ phoneNumbers: phoneNumbers.length,
152830
+ activeCalls: this.callManager.getActiveCallCount(),
152831
+ routingRules: routingRules.length,
152832
+ recentCalls: recentCalls.length,
152833
+ recentMessages: recentMessages.length
152834
+ };
152835
+ }
152836
+ getUnreadForInjection() {
152837
+ const injectionConfig = this.config.injection || {};
152838
+ if (injectionConfig.enabled === false) {
152839
+ return [];
152840
+ }
152841
+ const maxPerTurn = injectionConfig.maxPerTurn || 5;
152842
+ return this.store.getUnreadInboundSms(this.assistantId, maxPerTurn);
152843
+ }
152844
+ buildInjectionContext(messages) {
152845
+ if (messages.length === 0)
152846
+ return "";
152847
+ const lines = [];
152848
+ lines.push("## Incoming Telephony Messages");
152849
+ lines.push("");
152850
+ for (const msg of messages) {
152851
+ const type = msg.messageType === "whatsapp" ? "WhatsApp" : "SMS";
152852
+ const ago = formatTimeAgo2(msg.createdAt);
152853
+ lines.push(`**${type} from ${msg.fromNumber}** (${ago}):`);
152854
+ lines.push(msg.body);
152855
+ lines.push("");
152856
+ }
152857
+ lines.push("Use telephony_send_sms or telephony_send_whatsapp to reply.");
152858
+ return lines.join(`
152859
+ `);
152860
+ }
152861
+ markInjected(messages) {
152862
+ for (const msg of messages) {
152863
+ this.store.updateSmsStatus(msg.id, "delivered");
152864
+ }
152865
+ }
152866
+ getStore() {
152867
+ return this.store;
152868
+ }
152869
+ getTwilioClient() {
152870
+ return this.twilioClient;
152871
+ }
152872
+ getCallManager() {
152873
+ return this.callManager;
152874
+ }
152875
+ getVoiceBridge() {
152876
+ return this.voiceBridge;
152877
+ }
152878
+ getAssistantId() {
152879
+ return this.assistantId;
152880
+ }
152881
+ getAssistantName() {
152882
+ return this.assistantName;
152883
+ }
152884
+ getConfig() {
152885
+ return this.config;
152886
+ }
152887
+ cleanup() {
152888
+ const maxAgeDays = this.config.storage?.maxAgeDays || 90;
152889
+ const maxCallLogs = this.config.storage?.maxCallLogs || 1000;
152890
+ const maxSmsLogs = this.config.storage?.maxSmsLogs || 5000;
152891
+ this.callManager.cleanupStaleCalls();
152892
+ return this.store.cleanup(maxAgeDays, maxCallLogs, maxSmsLogs);
152893
+ }
152894
+ close() {
152895
+ this.callManager.endAllCalls();
152896
+ this.voiceBridge?.closeAll();
152897
+ this.store.close();
152898
+ }
152899
+ }
152900
+ function formatTimeAgo2(isoDate) {
152901
+ const now2 = Date.now();
152902
+ const then = new Date(isoDate).getTime();
152903
+ const diffMs = now2 - then;
152904
+ if (diffMs < 60000) {
152905
+ const secs = Math.floor(diffMs / 1000);
152906
+ return `${secs}s ago`;
152907
+ }
152908
+ if (diffMs < 3600000) {
152909
+ const mins = Math.floor(diffMs / 60000);
152910
+ return `${mins}m ago`;
152911
+ }
152912
+ if (diffMs < 86400000) {
152913
+ const hours = Math.floor(diffMs / 3600000);
152914
+ return `${hours}h ago`;
152915
+ }
152916
+ const days = Math.floor(diffMs / 86400000);
152917
+ return `${days}d ago`;
152918
+ }
152919
+ function createTelephonyManager(assistantId, assistantName, config) {
152920
+ return new TelephonyManager({
152921
+ assistantId,
152922
+ assistantName,
152923
+ config
152924
+ });
152925
+ }
152926
+ // ../core/src/telephony/tools.ts
152927
+ var telephonySendSmsTool = {
152928
+ name: "telephony_send_sms",
152929
+ description: "Send an SMS text message to a phone number.",
152930
+ parameters: {
152931
+ type: "object",
152932
+ properties: {
152933
+ to: {
152934
+ type: "string",
152935
+ description: 'Recipient phone number in E.164 format (e.g., "+15551234567")'
152936
+ },
152937
+ body: {
152938
+ type: "string",
152939
+ description: "Message content to send"
152940
+ },
152941
+ from: {
152942
+ type: "string",
152943
+ description: "Sender phone number (optional, uses default if not set)"
152944
+ }
152945
+ },
152946
+ required: ["to", "body"]
152947
+ }
152948
+ };
152949
+ var telephonySendWhatsappTool = {
152950
+ name: "telephony_send_whatsapp",
152951
+ description: "Send a WhatsApp message to a phone number.",
152952
+ parameters: {
152953
+ type: "object",
152954
+ properties: {
152955
+ to: {
152956
+ type: "string",
152957
+ description: 'Recipient phone number in E.164 format (e.g., "+15551234567")'
152958
+ },
152959
+ body: {
152960
+ type: "string",
152961
+ description: "Message content to send"
152962
+ },
152963
+ from: {
152964
+ type: "string",
152965
+ description: "Sender phone number (optional, uses default if not set)"
152966
+ }
152967
+ },
152968
+ required: ["to", "body"]
152969
+ }
152970
+ };
152971
+ var telephonyCallTool = {
152972
+ name: "telephony_call",
152973
+ description: "Initiate an outbound voice call. The call will be connected to the AI voice agent.",
152974
+ parameters: {
152975
+ type: "object",
152976
+ properties: {
152977
+ to: {
152978
+ type: "string",
152979
+ description: 'Phone number to call in E.164 format (e.g., "+15551234567")'
152980
+ },
152981
+ from: {
152982
+ type: "string",
152983
+ description: "Caller phone number (optional, uses default if not set)"
152984
+ }
152985
+ },
152986
+ required: ["to"]
152987
+ }
152988
+ };
152989
+ var telephonyCallHistoryTool = {
152990
+ name: "telephony_call_history",
152991
+ description: "Get recent call history. Returns call logs with status, duration, and timestamps.",
152992
+ parameters: {
152993
+ type: "object",
152994
+ properties: {
152995
+ limit: {
152996
+ type: "number",
152997
+ description: "Maximum number of calls to return (default: 20)"
152998
+ }
152999
+ },
153000
+ required: []
153001
+ }
153002
+ };
153003
+ var telephonySmsHistoryTool = {
153004
+ name: "telephony_sms_history",
153005
+ description: "Get recent SMS and WhatsApp message history.",
153006
+ parameters: {
153007
+ type: "object",
153008
+ properties: {
153009
+ limit: {
153010
+ type: "number",
153011
+ description: "Maximum messages to return (default: 20)"
153012
+ },
153013
+ type: {
153014
+ type: "string",
153015
+ enum: ["sms", "whatsapp"],
153016
+ description: "Filter by message type (default: all)"
153017
+ }
153018
+ },
153019
+ required: []
153020
+ }
153021
+ };
153022
+ var telephonyPhoneNumbersTool = {
153023
+ name: "telephony_phone_numbers",
153024
+ description: "List available phone numbers with their capabilities (voice, SMS, WhatsApp).",
153025
+ parameters: {
153026
+ type: "object",
153027
+ properties: {},
153028
+ required: []
153029
+ }
153030
+ };
153031
+ var telephonyRoutingRulesTool = {
153032
+ name: "telephony_routing_rules",
153033
+ description: "View and manage routing rules that direct incoming calls/messages to specific assistants.",
153034
+ parameters: {
153035
+ type: "object",
153036
+ properties: {
153037
+ action: {
153038
+ type: "string",
153039
+ enum: ["list", "create", "delete"],
153040
+ description: "Action to perform (default: list)"
153041
+ },
153042
+ name: {
153043
+ type: "string",
153044
+ description: "Rule name (for create)"
153045
+ },
153046
+ priority: {
153047
+ type: "number",
153048
+ description: "Priority (lower = higher priority, for create)"
153049
+ },
153050
+ from_pattern: {
153051
+ type: "string",
153052
+ description: 'From number pattern (e.g., "+1555*", for create)'
153053
+ },
153054
+ message_type: {
153055
+ type: "string",
153056
+ enum: ["sms", "whatsapp", "voice", "all"],
153057
+ description: "Message type filter (for create)"
153058
+ },
153059
+ rule_id: {
153060
+ type: "string",
153061
+ description: "Rule ID (for delete)"
153062
+ }
153063
+ },
153064
+ required: []
153065
+ }
153066
+ };
153067
+ var telephonyStatusTool = {
153068
+ name: "telephony_status",
153069
+ description: "Get telephony system status including configured numbers, active calls, and connection health.",
153070
+ parameters: {
153071
+ type: "object",
153072
+ properties: {},
153073
+ required: []
153074
+ }
153075
+ };
153076
+ function createTelephonyToolExecutors(getTelephonyManager) {
153077
+ return {
153078
+ telephony_send_sms: async (input) => {
153079
+ const manager = getTelephonyManager();
153080
+ if (!manager) {
153081
+ return "Error: Telephony is not enabled. Set telephony.enabled: true in config.";
153082
+ }
153083
+ const to3 = String(input.to || "").trim();
153084
+ const body = String(input.body || "").trim();
153085
+ if (!to3)
153086
+ return "Error: Recipient phone number (to) is required.";
153087
+ if (!body)
153088
+ return "Error: Message body is required.";
153089
+ const from = input.from ? String(input.from).trim() : undefined;
153090
+ const result = await manager.sendSms(to3, body, from);
153091
+ return result.success ? result.message : `Error: ${result.message}`;
153092
+ },
153093
+ telephony_send_whatsapp: async (input) => {
153094
+ const manager = getTelephonyManager();
153095
+ if (!manager) {
153096
+ return "Error: Telephony is not enabled. Set telephony.enabled: true in config.";
153097
+ }
153098
+ const to3 = String(input.to || "").trim();
153099
+ const body = String(input.body || "").trim();
153100
+ if (!to3)
153101
+ return "Error: Recipient phone number (to) is required.";
153102
+ if (!body)
153103
+ return "Error: Message body is required.";
153104
+ const from = input.from ? String(input.from).trim() : undefined;
153105
+ const result = await manager.sendWhatsApp(to3, body, from);
153106
+ return result.success ? result.message : `Error: ${result.message}`;
153107
+ },
153108
+ telephony_call: async (input) => {
153109
+ const manager = getTelephonyManager();
153110
+ if (!manager) {
153111
+ return "Error: Telephony is not enabled. Set telephony.enabled: true in config.";
153112
+ }
153113
+ const to3 = String(input.to || "").trim();
153114
+ if (!to3)
153115
+ return "Error: Phone number (to) is required.";
153116
+ const from = input.from ? String(input.from).trim() : undefined;
153117
+ const result = await manager.makeCall(to3, from);
153118
+ return result.success ? result.message : `Error: ${result.message}`;
153119
+ },
153120
+ telephony_call_history: async (input) => {
153121
+ const manager = getTelephonyManager();
153122
+ if (!manager) {
153123
+ return "Error: Telephony is not enabled.";
153124
+ }
153125
+ const limit2 = typeof input.limit === "number" ? input.limit : 20;
153126
+ const calls = manager.getCallHistory({ limit: limit2 });
153127
+ if (calls.length === 0) {
153128
+ return "No call history found.";
153129
+ }
153130
+ const lines = [];
153131
+ lines.push(`## Call History (${calls.length})`);
153132
+ lines.push("");
153133
+ for (const call of calls) {
153134
+ const dir = call.direction === "inbound" ? "IN" : "OUT";
153135
+ const duration = call.duration != null ? `${call.duration}s` : "-";
153136
+ const date = new Date(call.createdAt).toLocaleString();
153137
+ lines.push(`[${dir}] ${call.fromNumber} \u2192 ${call.toNumber} | ${call.status} | ${duration} | ${date}`);
153138
+ }
153139
+ return lines.join(`
153140
+ `);
153141
+ },
153142
+ telephony_sms_history: async (input) => {
153143
+ const manager = getTelephonyManager();
153144
+ if (!manager) {
153145
+ return "Error: Telephony is not enabled.";
153146
+ }
153147
+ const limit2 = typeof input.limit === "number" ? input.limit : 20;
153148
+ const messageType = input.type;
153149
+ const messages = manager.getSmsHistory({ limit: limit2, messageType });
153150
+ if (messages.length === 0) {
153151
+ return "No message history found.";
153152
+ }
153153
+ const lines = [];
153154
+ lines.push(`## Message History (${messages.length})`);
153155
+ lines.push("");
153156
+ for (const msg of messages) {
153157
+ const dir = msg.direction === "inbound" ? "IN" : "OUT";
153158
+ const type = msg.messageType === "whatsapp" ? "WA" : "SMS";
153159
+ const date = new Date(msg.createdAt).toLocaleString();
153160
+ lines.push(`[${dir}/${type}] ${msg.fromNumber} \u2192 ${msg.toNumber} | ${msg.status}`);
153161
+ lines.push(` ${msg.bodyPreview}`);
153162
+ lines.push(` ${date}`);
153163
+ lines.push("");
153164
+ }
153165
+ return lines.join(`
153166
+ `);
153167
+ },
153168
+ telephony_phone_numbers: async () => {
153169
+ const manager = getTelephonyManager();
153170
+ if (!manager) {
153171
+ return "Error: Telephony is not enabled.";
153172
+ }
153173
+ const numbers = manager.listPhoneNumbers();
153174
+ if (numbers.length === 0) {
153175
+ return "No phone numbers configured. Use /phone sync to import from Twilio.";
153176
+ }
153177
+ const lines = [];
153178
+ lines.push(`## Phone Numbers (${numbers.length})`);
153179
+ lines.push("");
153180
+ for (const num of numbers) {
153181
+ const caps = [];
153182
+ if (num.capabilities.voice)
153183
+ caps.push("voice");
153184
+ if (num.capabilities.sms)
153185
+ caps.push("sms");
153186
+ if (num.capabilities.whatsapp)
153187
+ caps.push("whatsapp");
153188
+ const name2 = num.friendlyName ? ` (${num.friendlyName})` : "";
153189
+ lines.push(` ${num.number}${name2} [${caps.join(", ")}]`);
153190
+ }
153191
+ return lines.join(`
153192
+ `);
153193
+ },
153194
+ telephony_routing_rules: async (input) => {
153195
+ const manager = getTelephonyManager();
153196
+ if (!manager) {
153197
+ return "Error: Telephony is not enabled.";
153198
+ }
153199
+ const action = String(input.action || "list");
153200
+ if (action === "list") {
153201
+ const rules = manager.listRoutingRules();
153202
+ if (rules.length === 0) {
153203
+ return "No routing rules configured.";
153204
+ }
153205
+ const lines = [];
153206
+ lines.push(`## Routing Rules (${rules.length})`);
153207
+ lines.push("");
153208
+ for (const rule of rules) {
153209
+ const enabled = rule.enabled ? "" : " [DISABLED]";
153210
+ lines.push(`**${rule.name}** (priority: ${rule.priority})${enabled}`);
153211
+ lines.push(` ID: ${rule.id}`);
153212
+ lines.push(` Type: ${rule.messageType} | Target: ${rule.targetAssistantName}`);
153213
+ if (rule.fromPattern)
153214
+ lines.push(` From: ${rule.fromPattern}`);
153215
+ if (rule.toPattern)
153216
+ lines.push(` To: ${rule.toPattern}`);
153217
+ if (rule.keyword)
153218
+ lines.push(` Keyword: ${rule.keyword}`);
153219
+ lines.push("");
153220
+ }
153221
+ return lines.join(`
153222
+ `);
153223
+ }
153224
+ if (action === "create") {
153225
+ const name2 = String(input.name || "").trim();
153226
+ if (!name2)
153227
+ return "Error: Rule name is required.";
153228
+ const result = manager.createRoutingRule({
153229
+ name: name2,
153230
+ priority: typeof input.priority === "number" ? input.priority : undefined,
153231
+ fromPattern: input.from_pattern ? String(input.from_pattern) : undefined,
153232
+ messageType: input.message_type,
153233
+ targetAssistantId: manager.getAssistantId(),
153234
+ targetAssistantName: manager.getAssistantName()
153235
+ });
153236
+ return result.success ? result.message : `Error: ${result.message}`;
153237
+ }
153238
+ if (action === "delete") {
153239
+ const ruleId = String(input.rule_id || "").trim();
153240
+ if (!ruleId)
153241
+ return "Error: Rule ID is required.";
153242
+ const result = manager.deleteRoutingRule(ruleId);
153243
+ return result.success ? result.message : `Error: ${result.message}`;
153244
+ }
153245
+ return `Unknown action: ${action}. Use 'list', 'create', or 'delete'.`;
153246
+ },
153247
+ telephony_status: async () => {
153248
+ const manager = getTelephonyManager();
153249
+ if (!manager) {
153250
+ return "Telephony is not enabled. Set telephony.enabled: true in config.";
153251
+ }
153252
+ const status = manager.getStatus();
153253
+ const lines = [];
153254
+ lines.push("## Telephony Status");
153255
+ lines.push("");
153256
+ lines.push(`Enabled: ${status.enabled ? "Yes" : "No"}`);
153257
+ lines.push(`Twilio configured: ${status.twilioConfigured ? "Yes" : "No (set TWILIO_ACCOUNT_SID + TWILIO_AUTH_TOKEN)"}`);
153258
+ lines.push(`ElevenLabs AI: ${status.elevenLabsConfigured ? "Yes" : "No (set ELEVENLABS_API_KEY + ELEVENLABS_AGENT_ID)"}`);
153259
+ lines.push(`Phone numbers: ${status.phoneNumbers}`);
153260
+ lines.push(`Active calls: ${status.activeCalls}`);
153261
+ lines.push(`Routing rules: ${status.routingRules}`);
153262
+ lines.push(`Recent calls: ${status.recentCalls}`);
153263
+ lines.push(`Recent messages: ${status.recentMessages}`);
153264
+ return lines.join(`
153265
+ `);
153266
+ }
153267
+ };
153268
+ }
153269
+ var telephonyTools = [
153270
+ telephonySendSmsTool,
153271
+ telephonySendWhatsappTool,
153272
+ telephonyCallTool,
153273
+ telephonyCallHistoryTool,
153274
+ telephonySmsHistoryTool,
153275
+ telephonyPhoneNumbersTool,
153276
+ telephonyRoutingRulesTool,
153277
+ telephonyStatusTool
153278
+ ];
153279
+ function registerTelephonyTools(registry2, getTelephonyManager) {
153280
+ const executors = createTelephonyToolExecutors(getTelephonyManager);
153281
+ for (const tool of telephonyTools) {
153282
+ registry2.register(tool, executors[tool.name]);
153283
+ }
153284
+ }
150090
153285
  // ../core/src/sessions/store.ts
150091
- import { join as join43 } from "path";
153286
+ import { join as join45 } from "path";
150092
153287
  import { homedir as homedir21 } from "os";
150093
- import { existsSync as existsSync26, mkdirSync as mkdirSync16, writeFileSync as writeFileSync14, readFileSync as readFileSync17, readdirSync as readdirSync9, unlinkSync as unlinkSync5 } from "fs";
153288
+ import { existsSync as existsSync28, mkdirSync as mkdirSync18, writeFileSync as writeFileSync14, readFileSync as readFileSync17, readdirSync as readdirSync9, unlinkSync as unlinkSync6 } from "fs";
150094
153289
 
150095
153290
  class SessionStore {
150096
153291
  basePath;
150097
153292
  constructor(basePath) {
150098
153293
  const envHome = process.env.HOME || process.env.USERPROFILE || homedir21();
150099
- this.basePath = basePath || join43(envHome, ".assistants", "sessions");
153294
+ this.basePath = basePath || join45(envHome, ".assistants", "sessions");
150100
153295
  this.ensureDir();
150101
153296
  }
150102
153297
  ensureDir() {
150103
- if (!existsSync26(this.basePath)) {
150104
- mkdirSync16(this.basePath, { recursive: true });
153298
+ if (!existsSync28(this.basePath)) {
153299
+ mkdirSync18(this.basePath, { recursive: true });
150105
153300
  }
150106
153301
  }
150107
153302
  getSessionPath(id) {
150108
- return join43(this.basePath, `${id}.json`);
153303
+ return join45(this.basePath, `${id}.json`);
150109
153304
  }
150110
153305
  save(data) {
150111
153306
  try {
@@ -150116,7 +153311,7 @@ class SessionStore {
150116
153311
  load(id) {
150117
153312
  try {
150118
153313
  const filePath = this.getSessionPath(id);
150119
- if (!existsSync26(filePath))
153314
+ if (!existsSync28(filePath))
150120
153315
  return null;
150121
153316
  return JSON.parse(readFileSync17(filePath, "utf-8"));
150122
153317
  } catch {
@@ -150130,7 +153325,7 @@ class SessionStore {
150130
153325
  const sessions = [];
150131
153326
  for (const file of files) {
150132
153327
  try {
150133
- const data = JSON.parse(readFileSync17(join43(this.basePath, file), "utf-8"));
153328
+ const data = JSON.parse(readFileSync17(join45(this.basePath, file), "utf-8"));
150134
153329
  sessions.push(data);
150135
153330
  } catch {}
150136
153331
  }
@@ -150142,8 +153337,8 @@ class SessionStore {
150142
153337
  delete(id) {
150143
153338
  try {
150144
153339
  const filePath = this.getSessionPath(id);
150145
- if (existsSync26(filePath)) {
150146
- unlinkSync5(filePath);
153340
+ if (existsSync28(filePath)) {
153341
+ unlinkSync6(filePath);
150147
153342
  }
150148
153343
  } catch {}
150149
153344
  }
@@ -150430,6 +153625,12 @@ class EmbeddedClient {
150430
153625
  }
150431
153626
  return null;
150432
153627
  }
153628
+ getMemoryManager() {
153629
+ if (typeof this.assistantLoop.getMemoryManager === "function") {
153630
+ return this.assistantLoop.getMemoryManager();
153631
+ }
153632
+ return null;
153633
+ }
150433
153634
  async refreshIdentityContext() {
150434
153635
  if (typeof this.assistantLoop.refreshIdentityContext === "function") {
150435
153636
  await this.assistantLoop.refreshIdentityContext();
@@ -150447,6 +153648,18 @@ class EmbeddedClient {
150447
153648
  }
150448
153649
  return null;
150449
153650
  }
153651
+ getChannelsManager() {
153652
+ if (typeof this.assistantLoop.getChannelsManager === "function") {
153653
+ return this.assistantLoop.getChannelsManager();
153654
+ }
153655
+ return null;
153656
+ }
153657
+ getTelephonyManager() {
153658
+ if (typeof this.assistantLoop.getTelephonyManager === "function") {
153659
+ return this.assistantLoop.getTelephonyManager();
153660
+ }
153661
+ return null;
153662
+ }
150450
153663
  getWalletManager() {
150451
153664
  if (typeof this.assistantLoop.getWalletManager === "function") {
150452
153665
  return this.assistantLoop.getWalletManager();
@@ -152317,7 +155530,7 @@ function createSelfAwarenessToolExecutors(context) {
152317
155530
  workspace_map: async (input) => {
152318
155531
  const { readdir: readdir7, stat: stat5, access } = await import("fs/promises");
152319
155532
  const { execSync } = await import("child_process");
152320
- const { join: join44, basename: basename6 } = await import("path");
155533
+ const { join: join46, basename: basename6 } = await import("path");
152321
155534
  const depth = input.depth ?? 3;
152322
155535
  const includeGitStatus = input.include_git_status !== false;
152323
155536
  const includeRecentFiles = input.include_recent_files !== false;
@@ -152363,7 +155576,7 @@ function createSelfAwarenessToolExecutors(context) {
152363
155576
  break;
152364
155577
  }
152365
155578
  if (entry.isDirectory()) {
152366
- const children = await buildTree(join44(dir, entry.name), currentDepth + 1);
155579
+ const children = await buildTree(join46(dir, entry.name), currentDepth + 1);
152367
155580
  nodes.push({
152368
155581
  name: entry.name,
152369
155582
  type: "directory",
@@ -152389,7 +155602,7 @@ function createSelfAwarenessToolExecutors(context) {
152389
155602
  };
152390
155603
  if (includeGitStatus) {
152391
155604
  try {
152392
- await access(join44(cwd, ".git"));
155605
+ await access(join46(cwd, ".git"));
152393
155606
  gitStatus.isRepo = true;
152394
155607
  try {
152395
155608
  const branch = execSync("git branch --show-current", { cwd, encoding: "utf-8" }).trim();
@@ -152420,7 +155633,7 @@ function createSelfAwarenessToolExecutors(context) {
152420
155633
  for (const entry of entries) {
152421
155634
  if (shouldIgnore(entry.name))
152422
155635
  continue;
152423
- const fullPath = join44(dir, entry.name);
155636
+ const fullPath = join46(dir, entry.name);
152424
155637
  const relPath = relativePath ? `${relativePath}/${entry.name}` : entry.name;
152425
155638
  if (entry.isFile()) {
152426
155639
  try {
@@ -152454,12 +155667,12 @@ function createSelfAwarenessToolExecutors(context) {
152454
155667
  let projectName = basename6(cwd);
152455
155668
  for (const indicator of projectIndicators) {
152456
155669
  try {
152457
- await access(join44(cwd, indicator.file));
155670
+ await access(join46(cwd, indicator.file));
152458
155671
  projectType = indicator.type;
152459
155672
  if (indicator.file === "package.json") {
152460
155673
  try {
152461
155674
  const { readFile: readFile16 } = await import("fs/promises");
152462
- const pkg = JSON.parse(await readFile16(join44(cwd, indicator.file), "utf-8"));
155675
+ const pkg = JSON.parse(await readFile16(join46(cwd, indicator.file), "utf-8"));
152463
155676
  projectName = pkg.name || projectName;
152464
155677
  } catch {}
152465
155678
  }
@@ -154649,6 +157862,21 @@ var taskTools = [
154649
157862
  tasksRecurringCancelTool
154650
157863
  ];
154651
157864
  function createTaskToolExecutors(context) {
157865
+ const formatTaskMatch = (task) => {
157866
+ const desc = task.description.length > 60 ? `${task.description.slice(0, 60)}...` : task.description;
157867
+ return `${task.id} - ${desc}`;
157868
+ };
157869
+ const handleResolveError = (id, matches, label) => {
157870
+ if (matches.length > 1) {
157871
+ const listed = matches.slice(0, 5).map(formatTaskMatch).join(`
157872
+ `);
157873
+ const more = matches.length > 5 ? `
157874
+ ...and ${matches.length - 5} more` : "";
157875
+ return `Multiple ${label} match "${id}". Use a longer ID prefix.
157876
+ ${listed}${more}`;
157877
+ }
157878
+ return `${label} not found: ${id}`;
157879
+ };
154652
157880
  return {
154653
157881
  tasks_list: async (input) => {
154654
157882
  const tasks = await getTasks(context.cwd);
@@ -154660,16 +157888,16 @@ function createTaskToolExecutors(context) {
154660
157888
  const lines = filtered.map((t9) => {
154661
157889
  const statusIcon = t9.status === "pending" ? "\u25CB" : t9.status === "in_progress" ? "\u25D0" : t9.status === "completed" ? "\u25CF" : "\u2717";
154662
157890
  const priorityIcon = t9.priority === "high" ? "\u2191" : t9.priority === "low" ? "\u2193" : "-";
154663
- return `${statusIcon} [${priorityIcon}] ${t9.id.slice(0, 8)} - ${t9.description}`;
157891
+ return `${statusIcon} [${priorityIcon}] ${t9.id} - ${t9.description}`;
154664
157892
  });
154665
157893
  return `Tasks (${filtered.length}):
154666
157894
  ${lines.join(`
154667
157895
  `)}`;
154668
157896
  },
154669
157897
  tasks_get: async (input) => {
154670
- const task = await getTask(context.cwd, input.id);
157898
+ const { task, matches } = await resolveTaskId(context.cwd, input.id);
154671
157899
  if (!task) {
154672
- return `Task not found: ${input.id}`;
157900
+ return handleResolveError(input.id, matches, "Task");
154673
157901
  }
154674
157902
  const lines = [
154675
157903
  `ID: ${task.id}`,
@@ -154716,7 +157944,11 @@ Priority: ${task.priority}
154716
157944
  Description: ${task.description}`;
154717
157945
  },
154718
157946
  tasks_complete: async (input) => {
154719
- const task = await completeTask(context.cwd, input.id, input.result);
157947
+ const { task: resolved, matches } = await resolveTaskId(context.cwd, input.id);
157948
+ if (!resolved) {
157949
+ return handleResolveError(input.id, matches, "Task");
157950
+ }
157951
+ const task = await completeTask(context.cwd, resolved.id, input.result);
154720
157952
  if (!task) {
154721
157953
  return `Task not found: ${input.id}`;
154722
157954
  }
@@ -154724,7 +157956,11 @@ Description: ${task.description}`;
154724
157956
  Result: ${input.result}` : ""}`;
154725
157957
  },
154726
157958
  tasks_fail: async (input) => {
154727
- const task = await failTask(context.cwd, input.id, input.error);
157959
+ const { task: resolved, matches } = await resolveTaskId(context.cwd, input.id);
157960
+ if (!resolved) {
157961
+ return handleResolveError(input.id, matches, "Task");
157962
+ }
157963
+ const task = await failTask(context.cwd, resolved.id, input.error);
154728
157964
  if (!task) {
154729
157965
  return `Task not found: ${input.id}`;
154730
157966
  }
@@ -154755,7 +157991,7 @@ Error: ${input.error}` : ""}`;
154755
157991
  const nextRun = t9.nextRunAt ? new Date(t9.nextRunAt).toISOString() : "completed";
154756
157992
  const count = t9.recurrence?.occurrenceCount ?? 0;
154757
157993
  const statusIcon = t9.status === "pending" ? "\u25D0" : "\u25CF";
154758
- return `${statusIcon} ${t9.id.slice(0, 8)} - ${t9.description}
157994
+ return `${statusIcon} ${t9.id} - ${t9.description}
154759
157995
  ${schedule} | next: ${nextRun} | runs: ${count}`;
154760
157996
  });
154761
157997
  return `Recurring Tasks (${recurring.length}):
@@ -154788,7 +158024,11 @@ Schedule: ${schedule}
154788
158024
  Next run: ${task.nextRunAt ? new Date(task.nextRunAt).toISOString() : "calculating..."}`;
154789
158025
  },
154790
158026
  tasks_recurring_cancel: async (input) => {
154791
- const task = await cancelRecurringTask(context.cwd, input.id);
158027
+ const { task: resolved, matches } = await resolveTaskId(context.cwd, input.id, (t9) => t9.isRecurringTemplate === true);
158028
+ if (!resolved) {
158029
+ return handleResolveError(input.id, matches, "Recurring task");
158030
+ }
158031
+ const task = await cancelRecurringTask(context.cwd, resolved.id);
154792
158032
  if (!task) {
154793
158033
  return `Recurring task not found: ${input.id}`;
154794
158034
  }
@@ -155021,8 +158261,8 @@ await __promiseAll([
155021
158261
  init_config(),
155022
158262
  init_runtime()
155023
158263
  ]);
155024
- import { join as join44, dirname as dirname18 } from "path";
155025
- import { existsSync as existsSync27, mkdirSync as mkdirSync17 } from "fs";
158264
+ import { join as join46, dirname as dirname20 } from "path";
158265
+ import { existsSync as existsSync29, mkdirSync as mkdirSync19 } from "fs";
155026
158266
  var MAX_KEY_LENGTH2 = 256;
155027
158267
  var MAX_VALUE_SIZE = 65536;
155028
158268
  var MAX_SUMMARY_LENGTH2 = 500;
@@ -155035,10 +158275,10 @@ class GlobalMemoryManager {
155035
158275
  config;
155036
158276
  constructor(options = {}) {
155037
158277
  const baseDir = getConfigDir();
155038
- const path2 = options.dbPath || join44(baseDir, "memory.db");
155039
- const dir = dirname18(path2);
155040
- if (!existsSync27(dir)) {
155041
- mkdirSync17(dir, { recursive: true });
158278
+ const path2 = options.dbPath || join46(baseDir, "memory.db");
158279
+ const dir = dirname20(path2);
158280
+ if (!existsSync29(dir)) {
158281
+ mkdirSync19(dir, { recursive: true });
155042
158282
  }
155043
158283
  const runtime = getRuntime();
155044
158284
  this.db = runtime.openDatabase(path2);
@@ -156104,6 +159344,7 @@ ${hookResult.additionalContext}` : hookResult.additionalContext
156104
159344
  return new Promise((resolve5) => {
156105
159345
  const timerId = setTimeout(() => {
156106
159346
  this.activeTimeouts.delete(subassistantId);
159347
+ this.activeRunners.delete(subassistantId);
156107
159348
  runner.stop();
156108
159349
  resolve5({
156109
159350
  success: false,
@@ -156441,8 +159682,8 @@ function createCapabilityChain(scope, capabilities) {
156441
159682
  };
156442
159683
  }
156443
159684
  // ../core/src/capabilities/storage.ts
156444
- import { existsSync as existsSync28, mkdirSync as mkdirSync18, readFileSync as readFileSync18, writeFileSync as writeFileSync15 } from "fs";
156445
- import { join as join45, dirname as dirname19 } from "path";
159685
+ import { existsSync as existsSync30, mkdirSync as mkdirSync20, readFileSync as readFileSync18, writeFileSync as writeFileSync15 } from "fs";
159686
+ import { join as join47, dirname as dirname21 } from "path";
156446
159687
  import { homedir as homedir22 } from "os";
156447
159688
  var DEFAULT_STORAGE_CONFIG = {
156448
159689
  enabled: true,
@@ -156463,14 +159704,14 @@ class CapabilityStorage {
156463
159704
  return this.config.storagePath;
156464
159705
  }
156465
159706
  const home = process.env.HOME || process.env.USERPROFILE || homedir22();
156466
- return join45(home, ".assistants", "capabilities", "store.json");
159707
+ return join47(home, ".assistants", "capabilities", "store.json");
156467
159708
  }
156468
159709
  load() {
156469
159710
  if (!this.config.enabled)
156470
159711
  return;
156471
159712
  try {
156472
159713
  const path2 = this.getStoragePath();
156473
- if (!existsSync28(path2))
159714
+ if (!existsSync30(path2))
156474
159715
  return;
156475
159716
  const data = JSON.parse(readFileSync18(path2, "utf-8"));
156476
159717
  if (data.chains) {
@@ -156490,9 +159731,9 @@ class CapabilityStorage {
156490
159731
  return;
156491
159732
  try {
156492
159733
  const path2 = this.getStoragePath();
156493
- const dir = dirname19(path2);
156494
- if (!existsSync28(dir)) {
156495
- mkdirSync18(dir, { recursive: true });
159734
+ const dir = dirname21(path2);
159735
+ if (!existsSync30(dir)) {
159736
+ mkdirSync20(dir, { recursive: true });
156496
159737
  }
156497
159738
  const data = {
156498
159739
  version: 1,
@@ -156877,6 +160118,8 @@ class AssistantLoop {
156877
160118
  jobManager = null;
156878
160119
  messagesManager = null;
156879
160120
  webhooksManager = null;
160121
+ channelsManager = null;
160122
+ telephonyManager = null;
156880
160123
  memoryManager = null;
156881
160124
  memoryInjector = null;
156882
160125
  contextInjector = null;
@@ -156885,6 +160128,8 @@ class AssistantLoop {
156885
160128
  depth = 0;
156886
160129
  pendingMessagesContext = null;
156887
160130
  pendingWebhooksContext = null;
160131
+ pendingChannelsContext = null;
160132
+ pendingTelephonyContext = null;
156888
160133
  pendingMemoryContext = null;
156889
160134
  identityContext = null;
156890
160135
  projectContext = null;
@@ -157107,6 +160352,20 @@ class AssistantLoop {
157107
160352
  }
157108
160353
  });
157109
160354
  }
160355
+ if (this.config?.channels?.enabled) {
160356
+ const assistant = this.assistantManager?.getActive();
160357
+ const assistantId = assistant?.id || this.sessionId;
160358
+ const assistantName = assistant?.name || "assistant";
160359
+ this.channelsManager = createChannelsManager(assistantId, assistantName, this.config.channels);
160360
+ registerChannelTools(this.toolRegistry, () => this.channelsManager);
160361
+ }
160362
+ if (this.config?.telephony?.enabled) {
160363
+ const assistant = this.assistantManager?.getActive();
160364
+ const assistantId = assistant?.id || this.sessionId;
160365
+ const assistantName = assistant?.name || "assistant";
160366
+ this.telephonyManager = createTelephonyManager(assistantId, assistantName, this.config.telephony);
160367
+ registerTelephonyTools(this.toolRegistry, () => this.telephonyManager);
160368
+ }
157110
160369
  const memoryConfig = this.config?.memory;
157111
160370
  if (memoryConfig?.enabled !== false) {
157112
160371
  const assistant = this.assistantManager?.getActive();
@@ -157437,6 +160696,8 @@ You are running in **autonomous mode**. You manage your own wakeup schedule.
157437
160696
  try {
157438
160697
  await this.injectPendingMessages();
157439
160698
  await this.injectPendingWebhookEvents();
160699
+ await this.injectPendingChannelMessages();
160700
+ await this.injectPendingTelephonyMessages();
157440
160701
  await this.injectMemoryContext(userMessage);
157441
160702
  await this.injectContextInfo();
157442
160703
  } catch (error2) {
@@ -157494,7 +160755,7 @@ You are running in **autonomous mode**. You manage your own wakeup schedule.
157494
160755
  this.emit({
157495
160756
  type: "show_panel",
157496
160757
  panel: commandResult.showPanel,
157497
- panelValue: commandResult.panelInitialValue
160758
+ panelValue: commandResult.panelValue
157498
160759
  });
157499
160760
  }
157500
160761
  if (commandResult.sessionAction) {
@@ -157531,7 +160792,7 @@ You are running in **autonomous mode**. You manage your own wakeup schedule.
157531
160792
  this.emit({
157532
160793
  type: "show_panel",
157533
160794
  panel: commandResult.showPanel,
157534
- panelValue: commandResult.panelInitialValue
160795
+ panelValue: commandResult.panelValue
157535
160796
  });
157536
160797
  }
157537
160798
  return { ok: true, summary: `Handled ${userMessage}` };
@@ -158154,6 +161415,8 @@ You are running in **autonomous mode**. You manage your own wakeup schedule.
158154
161415
  getSecretsManager: () => this.secretsManager,
158155
161416
  getMessagesManager: () => this.messagesManager,
158156
161417
  getWebhooksManager: () => this.webhooksManager,
161418
+ getChannelsManager: () => this.channelsManager,
161419
+ getTelephonyManager: () => this.telephonyManager,
158157
161420
  getMemoryManager: () => this.memoryManager,
158158
161421
  refreshIdentityContext: async () => {
158159
161422
  if (this.identityManager) {
@@ -158393,6 +161656,10 @@ You are running in **autonomous mode**. You manage your own wakeup schedule.
158393
161656
  this.voiceManager?.stopListening();
158394
161657
  this.messagesManager?.stopWatching();
158395
161658
  this.webhooksManager?.stopWatching();
161659
+ this.channelsManager?.close();
161660
+ this.channelsManager = null;
161661
+ this.telephonyManager?.close();
161662
+ this.telephonyManager = null;
158396
161663
  this.memoryManager?.close();
158397
161664
  this.memoryManager = null;
158398
161665
  this.memoryInjector = null;
@@ -158458,12 +161725,21 @@ You are running in **autonomous mode**. You manage your own wakeup schedule.
158458
161725
  getIdentityManager() {
158459
161726
  return this.identityManager;
158460
161727
  }
161728
+ getMemoryManager() {
161729
+ return this.memoryManager;
161730
+ }
158461
161731
  getMessagesManager() {
158462
161732
  return this.messagesManager;
158463
161733
  }
158464
161734
  getWebhooksManager() {
158465
161735
  return this.webhooksManager;
158466
161736
  }
161737
+ getChannelsManager() {
161738
+ return this.channelsManager;
161739
+ }
161740
+ getTelephonyManager() {
161741
+ return this.telephonyManager;
161742
+ }
158467
161743
  getWalletManager() {
158468
161744
  return this.walletManager;
158469
161745
  }
@@ -158696,7 +161972,7 @@ ${content.trim()}`);
158696
161972
  if (!heartbeatConfig)
158697
161973
  return;
158698
161974
  this.heartbeatRuntimeConfig = heartbeatConfig;
158699
- const statePath = join46(getConfigDir(), "state", `${this.sessionId}.json`);
161975
+ const statePath = join48(getConfigDir(), "state", `${this.sessionId}.json`);
158700
161976
  this.heartbeatManager = new HeartbeatManager(heartbeatConfig);
158701
161977
  this.heartbeatPersistence = new StatePersistence(statePath);
158702
161978
  this.heartbeatRecovery = new RecoveryManager(this.heartbeatPersistence, heartbeatConfig.persistPath, heartbeatConfig.staleThresholdMs, {
@@ -158817,7 +162093,7 @@ ${content.trim()}`);
158817
162093
  async startEnergySystem() {
158818
162094
  if (!this.config || this.config.energy?.enabled === false)
158819
162095
  return;
158820
- const statePath = join46(getConfigDir(), "energy", "state.json");
162096
+ const statePath = join48(getConfigDir(), "energy", "state.json");
158821
162097
  this.energyManager = new EnergyManager(this.config.energy, new EnergyStorage(statePath));
158822
162098
  await this.energyManager.initialize();
158823
162099
  this.refreshEnergyEffects();
@@ -158901,6 +162177,52 @@ ${effects.message}
158901
162177
  this.pendingWebhooksContext = null;
158902
162178
  }
158903
162179
  }
162180
+ async injectPendingChannelMessages() {
162181
+ if (!this.channelsManager)
162182
+ return;
162183
+ try {
162184
+ if (this.pendingChannelsContext) {
162185
+ const previous = this.pendingChannelsContext.trim();
162186
+ this.context.removeSystemMessages((content) => content.trim() === previous);
162187
+ this.pendingChannelsContext = null;
162188
+ }
162189
+ const pending = this.channelsManager.getUnreadForInjection();
162190
+ if (pending.length === 0) {
162191
+ return;
162192
+ }
162193
+ this.pendingChannelsContext = this.channelsManager.buildInjectionContext(pending);
162194
+ if (this.pendingChannelsContext) {
162195
+ this.context.addSystemMessage(this.pendingChannelsContext);
162196
+ }
162197
+ this.channelsManager.markInjected(pending);
162198
+ } catch (error2) {
162199
+ console.error("Failed to inject pending channel messages:", error2);
162200
+ this.pendingChannelsContext = null;
162201
+ }
162202
+ }
162203
+ async injectPendingTelephonyMessages() {
162204
+ if (!this.telephonyManager)
162205
+ return;
162206
+ try {
162207
+ if (this.pendingTelephonyContext) {
162208
+ const previous = this.pendingTelephonyContext.trim();
162209
+ this.context.removeSystemMessages((content) => content.trim() === previous);
162210
+ this.pendingTelephonyContext = null;
162211
+ }
162212
+ const pending = this.telephonyManager.getUnreadForInjection();
162213
+ if (pending.length === 0) {
162214
+ return;
162215
+ }
162216
+ this.pendingTelephonyContext = this.telephonyManager.buildInjectionContext(pending);
162217
+ if (this.pendingTelephonyContext) {
162218
+ this.context.addSystemMessage(this.pendingTelephonyContext);
162219
+ }
162220
+ this.telephonyManager.markInjected(pending);
162221
+ } catch (error2) {
162222
+ console.error("Failed to inject pending telephony messages:", error2);
162223
+ this.pendingTelephonyContext = null;
162224
+ }
162225
+ }
158904
162226
  async injectMemoryContext(userMessage) {
158905
162227
  if (!this.memoryInjector || !this.memoryInjector.isEnabled())
158906
162228
  return;
@@ -159170,8 +162492,8 @@ ${this.identityContext}`);
159170
162492
  return null;
159171
162493
  const intervalMs = Math.max(1000, config.heartbeat?.intervalMs ?? 15000);
159172
162494
  const staleThresholdMs = Math.max(intervalMs * 2, config.heartbeat?.staleThresholdMs ?? 120000);
159173
- const persistPath = config.heartbeat?.persistPath ?? join46(getConfigDir(), "heartbeats", `${this.sessionId}.json`);
159174
- const historyPath = config.heartbeat?.historyPath ?? join46(getConfigDir(), "heartbeats", "runs", `${this.sessionId}.jsonl`);
162495
+ const persistPath = config.heartbeat?.persistPath ?? join48(getConfigDir(), "heartbeats", `${this.sessionId}.json`);
162496
+ const historyPath = config.heartbeat?.historyPath ?? join48(getConfigDir(), "heartbeats", "runs", `${this.sessionId}.jsonl`);
159175
162497
  return {
159176
162498
  intervalMs,
159177
162499
  staleThresholdMs,
@@ -159560,9 +162882,9 @@ class StatsTracker {
159560
162882
  }
159561
162883
  }
159562
162884
  // ../core/src/tools/connector-index.ts
159563
- import { join as join47, dirname as dirname20 } from "path";
162885
+ import { join as join49, dirname as dirname22 } from "path";
159564
162886
  import { homedir as homedir23 } from "os";
159565
- import { existsSync as existsSync29, mkdirSync as mkdirSync19, writeFileSync as writeFileSync16, readFileSync as readFileSync19 } from "fs";
162887
+ import { existsSync as existsSync31, mkdirSync as mkdirSync21, writeFileSync as writeFileSync16, readFileSync as readFileSync19 } from "fs";
159566
162888
  var TAG_KEYWORDS = {
159567
162889
  email: ["email", "mail", "inbox", "send", "receive", "message", "compose"],
159568
162890
  calendar: ["calendar", "event", "meeting", "schedule", "appointment"],
@@ -159599,13 +162921,13 @@ class ConnectorIndex {
159599
162921
  return envHome && envHome.trim().length > 0 ? envHome : homedir23();
159600
162922
  }
159601
162923
  getCachePath() {
159602
- return join47(this.getHomeDir(), ".assistants", "cache", "connector-index.json");
162924
+ return join49(this.getHomeDir(), ".assistants", "cache", "connector-index.json");
159603
162925
  }
159604
162926
  loadDiskCache() {
159605
162927
  ConnectorIndex.indexLoaded = true;
159606
162928
  try {
159607
162929
  const cachePath = this.getCachePath();
159608
- if (!existsSync29(cachePath))
162930
+ if (!existsSync31(cachePath))
159609
162931
  return;
159610
162932
  const data = JSON.parse(readFileSync19(cachePath, "utf-8"));
159611
162933
  if (data.version !== INDEX_VERSION)
@@ -159621,9 +162943,9 @@ class ConnectorIndex {
159621
162943
  saveDiskCache() {
159622
162944
  try {
159623
162945
  const cachePath = this.getCachePath();
159624
- const cacheDir = dirname20(cachePath);
159625
- if (!existsSync29(cacheDir)) {
159626
- mkdirSync19(cacheDir, { recursive: true });
162946
+ const cacheDir = dirname22(cachePath);
162947
+ if (!existsSync31(cacheDir)) {
162948
+ mkdirSync21(cacheDir, { recursive: true });
159627
162949
  }
159628
162950
  const data = {
159629
162951
  version: INDEX_VERSION,