@hasna/assistants 1.1.8 → 1.1.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/cli.js +8376 -4671
- package/dist/lib.js +2043 -47
- package/package.json +2 -2
package/dist/lib.js
CHANGED
|
@@ -110939,7 +110939,7 @@ await init_runtime();
|
|
|
110939
110939
|
|
|
110940
110940
|
// ../core/src/agent/loop.ts
|
|
110941
110941
|
init_src2();
|
|
110942
|
-
import { join as
|
|
110942
|
+
import { join as join48 } from "path";
|
|
110943
110943
|
|
|
110944
110944
|
// ../core/src/agent/context.ts
|
|
110945
110945
|
init_src2();
|
|
@@ -124618,7 +124618,9 @@ class BuiltinCommands {
|
|
|
124618
124618
|
loader.register(this.messagesCommand());
|
|
124619
124619
|
loader.register(this.webhooksCommand());
|
|
124620
124620
|
loader.register(this.channelsCommand());
|
|
124621
|
+
loader.register(this.phoneCommand());
|
|
124621
124622
|
loader.register(this.tasksCommand());
|
|
124623
|
+
loader.register(this.setupCommand());
|
|
124622
124624
|
loader.register(this.exitCommand());
|
|
124623
124625
|
}
|
|
124624
124626
|
voiceCommand() {
|
|
@@ -127904,6 +127906,208 @@ Configure the external source with the URL and secret above.
|
|
|
127904
127906
|
context.emit("text", `Unknown command: ${subcommand}
|
|
127905
127907
|
`);
|
|
127906
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.
|
|
127907
128111
|
`);
|
|
127908
128112
|
context.emit("done");
|
|
127909
128113
|
return { handled: true };
|
|
@@ -128391,6 +128595,20 @@ Unknown workspace command. Use /workspace help for available commands.
|
|
|
128391
128595
|
}
|
|
128392
128596
|
};
|
|
128393
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
|
+
}
|
|
128394
128612
|
exitCommand() {
|
|
128395
128613
|
return {
|
|
128396
128614
|
name: "exit",
|
|
@@ -150618,6 +150836,7 @@ class ChannelStore {
|
|
|
150618
150836
|
role TEXT NOT NULL DEFAULT 'member',
|
|
150619
150837
|
joined_at TEXT NOT NULL,
|
|
150620
150838
|
last_read_at TEXT,
|
|
150839
|
+
member_type TEXT NOT NULL DEFAULT 'assistant',
|
|
150621
150840
|
PRIMARY KEY (channel_id, assistant_id)
|
|
150622
150841
|
);
|
|
150623
150842
|
|
|
@@ -150635,6 +150854,16 @@ class ChannelStore {
|
|
|
150635
150854
|
CREATE INDEX IF NOT EXISTS idx_channel_members_assistant
|
|
150636
150855
|
ON channel_members(assistant_id);
|
|
150637
150856
|
`);
|
|
150857
|
+
this.migrateAddMemberType();
|
|
150858
|
+
}
|
|
150859
|
+
migrateAddMemberType() {
|
|
150860
|
+
try {
|
|
150861
|
+
const columns = this.db.prepare("PRAGMA table_info(channel_members)").all();
|
|
150862
|
+
const hasMemberType = columns.some((col) => String(col.name) === "member_type");
|
|
150863
|
+
if (!hasMemberType) {
|
|
150864
|
+
this.db.exec("ALTER TABLE channel_members ADD COLUMN member_type TEXT NOT NULL DEFAULT 'assistant'");
|
|
150865
|
+
}
|
|
150866
|
+
} catch {}
|
|
150638
150867
|
}
|
|
150639
150868
|
createChannel(name2, description, createdBy, createdByName) {
|
|
150640
150869
|
const normalizedName = name2.toLowerCase().replace(/^#/, "").replace(/[^a-z0-9_-]/g, "-");
|
|
@@ -150729,12 +150958,12 @@ class ChannelStore {
|
|
|
150729
150958
|
const result = stmt.run("archived", now2, id, "active");
|
|
150730
150959
|
return result.changes > 0;
|
|
150731
150960
|
}
|
|
150732
|
-
addMember(channelId, assistantId, assistantName, role = "member") {
|
|
150961
|
+
addMember(channelId, assistantId, assistantName, role = "member", memberType = "assistant") {
|
|
150733
150962
|
try {
|
|
150734
150963
|
const now2 = new Date().toISOString();
|
|
150735
|
-
const stmt = this.db.prepare(`INSERT OR IGNORE INTO channel_members (channel_id, assistant_id, assistant_name, role, joined_at)
|
|
150736
|
-
VALUES (?, ?, ?, ?, ?)`);
|
|
150737
|
-
const result = stmt.run(channelId, assistantId, assistantName, role, now2);
|
|
150964
|
+
const stmt = this.db.prepare(`INSERT OR IGNORE INTO channel_members (channel_id, assistant_id, assistant_name, role, joined_at, member_type)
|
|
150965
|
+
VALUES (?, ?, ?, ?, ?, ?)`);
|
|
150966
|
+
const result = stmt.run(channelId, assistantId, assistantName, role, now2, memberType);
|
|
150738
150967
|
return result.changes > 0;
|
|
150739
150968
|
} catch {
|
|
150740
150969
|
return false;
|
|
@@ -150874,7 +151103,8 @@ class ChannelStore {
|
|
|
150874
151103
|
assistantName: String(row.assistant_name),
|
|
150875
151104
|
role: String(row.role),
|
|
150876
151105
|
joinedAt: String(row.joined_at),
|
|
150877
|
-
lastReadAt: row.last_read_at ? String(row.last_read_at) : null
|
|
151106
|
+
lastReadAt: row.last_read_at ? String(row.last_read_at) : null,
|
|
151107
|
+
memberType: row.member_type ? String(row.member_type) : "assistant"
|
|
150878
151108
|
};
|
|
150879
151109
|
}
|
|
150880
151110
|
rowToMessage(row) {
|
|
@@ -150952,7 +151182,7 @@ class ChannelsManager {
|
|
|
150952
151182
|
this.store.removeMember(channel.id, this.assistantId);
|
|
150953
151183
|
return { success: true, message: `Left #${channel.name}.`, channelId: channel.id };
|
|
150954
151184
|
}
|
|
150955
|
-
invite(nameOrId, targetId, targetName) {
|
|
151185
|
+
invite(nameOrId, targetId, targetName, memberType = "assistant") {
|
|
150956
151186
|
const channel = this.store.resolveChannel(nameOrId);
|
|
150957
151187
|
if (!channel) {
|
|
150958
151188
|
return { success: false, message: `Channel "${nameOrId}" not found.` };
|
|
@@ -150963,7 +151193,7 @@ class ChannelsManager {
|
|
|
150963
151193
|
if (this.store.isMember(channel.id, targetId)) {
|
|
150964
151194
|
return { success: false, message: `${targetName} is already a member of #${channel.name}.` };
|
|
150965
151195
|
}
|
|
150966
|
-
this.store.addMember(channel.id, targetId, targetName);
|
|
151196
|
+
this.store.addMember(channel.id, targetId, targetName, "member", memberType);
|
|
150967
151197
|
return {
|
|
150968
151198
|
success: true,
|
|
150969
151199
|
message: `Invited ${targetName} to #${channel.name}.`,
|
|
@@ -150994,6 +151224,24 @@ class ChannelsManager {
|
|
|
150994
151224
|
channelId: channel.id
|
|
150995
151225
|
};
|
|
150996
151226
|
}
|
|
151227
|
+
sendAs(nameOrId, content, senderId, senderName) {
|
|
151228
|
+
const channel = this.store.resolveChannel(nameOrId);
|
|
151229
|
+
if (!channel) {
|
|
151230
|
+
return { success: false, message: `Channel "${nameOrId}" not found.` };
|
|
151231
|
+
}
|
|
151232
|
+
if (channel.status !== "active") {
|
|
151233
|
+
return { success: false, message: `Channel #${channel.name} is archived.` };
|
|
151234
|
+
}
|
|
151235
|
+
if (!this.store.isMember(channel.id, senderId)) {
|
|
151236
|
+
return { success: false, message: `${senderName} is not a member of #${channel.name}. Join first.` };
|
|
151237
|
+
}
|
|
151238
|
+
const messageId = this.store.sendMessage(channel.id, senderId, senderName, content);
|
|
151239
|
+
return {
|
|
151240
|
+
success: true,
|
|
151241
|
+
message: `Message sent to #${channel.name} (${messageId}).`,
|
|
151242
|
+
channelId: channel.id
|
|
151243
|
+
};
|
|
151244
|
+
}
|
|
150997
151245
|
readMessages(nameOrId, limit2) {
|
|
150998
151246
|
const channel = this.store.resolveChannel(nameOrId);
|
|
150999
151247
|
if (!channel)
|
|
@@ -151361,25 +151609,1728 @@ function registerChannelTools(registry2, getChannelsManager) {
|
|
|
151361
151609
|
registry2.register(tool, executors[tool.name]);
|
|
151362
151610
|
}
|
|
151363
151611
|
}
|
|
151612
|
+
// ../core/src/telephony/store.ts
|
|
151613
|
+
init_src2();
|
|
151614
|
+
await __promiseAll([
|
|
151615
|
+
init_config(),
|
|
151616
|
+
init_runtime()
|
|
151617
|
+
]);
|
|
151618
|
+
import { join as join44, dirname as dirname18 } from "path";
|
|
151619
|
+
import { existsSync as existsSync27, mkdirSync as mkdirSync17 } from "fs";
|
|
151620
|
+
function generatePhoneId() {
|
|
151621
|
+
return `ph_${generateId().slice(0, 12)}`;
|
|
151622
|
+
}
|
|
151623
|
+
function generateCallId() {
|
|
151624
|
+
return `call_${generateId().slice(0, 12)}`;
|
|
151625
|
+
}
|
|
151626
|
+
function generateSmsId() {
|
|
151627
|
+
return `sms_${generateId().slice(0, 12)}`;
|
|
151628
|
+
}
|
|
151629
|
+
function generateRuleId() {
|
|
151630
|
+
return `rule_${generateId().slice(0, 12)}`;
|
|
151631
|
+
}
|
|
151632
|
+
|
|
151633
|
+
class TelephonyStore {
|
|
151634
|
+
db;
|
|
151635
|
+
constructor(dbPath) {
|
|
151636
|
+
const baseDir = getConfigDir();
|
|
151637
|
+
const path2 = dbPath || join44(baseDir, "telephony.db");
|
|
151638
|
+
const dir = dirname18(path2);
|
|
151639
|
+
if (!existsSync27(dir)) {
|
|
151640
|
+
mkdirSync17(dir, { recursive: true });
|
|
151641
|
+
}
|
|
151642
|
+
const runtime = getRuntime();
|
|
151643
|
+
this.db = runtime.openDatabase(path2);
|
|
151644
|
+
this.initialize();
|
|
151645
|
+
}
|
|
151646
|
+
initialize() {
|
|
151647
|
+
this.db.exec(`
|
|
151648
|
+
CREATE TABLE IF NOT EXISTS phone_numbers (
|
|
151649
|
+
id TEXT PRIMARY KEY,
|
|
151650
|
+
number TEXT NOT NULL UNIQUE,
|
|
151651
|
+
friendly_name TEXT,
|
|
151652
|
+
twilio_sid TEXT,
|
|
151653
|
+
status TEXT NOT NULL DEFAULT 'active',
|
|
151654
|
+
voice_capable INTEGER NOT NULL DEFAULT 1,
|
|
151655
|
+
sms_capable INTEGER NOT NULL DEFAULT 1,
|
|
151656
|
+
whatsapp_capable INTEGER NOT NULL DEFAULT 0,
|
|
151657
|
+
created_at TEXT NOT NULL,
|
|
151658
|
+
updated_at TEXT NOT NULL
|
|
151659
|
+
);
|
|
151660
|
+
|
|
151661
|
+
CREATE TABLE IF NOT EXISTS call_logs (
|
|
151662
|
+
id TEXT PRIMARY KEY,
|
|
151663
|
+
call_sid TEXT,
|
|
151664
|
+
from_number TEXT NOT NULL,
|
|
151665
|
+
to_number TEXT NOT NULL,
|
|
151666
|
+
direction TEXT NOT NULL,
|
|
151667
|
+
status TEXT NOT NULL DEFAULT 'pending',
|
|
151668
|
+
assistant_id TEXT,
|
|
151669
|
+
duration INTEGER,
|
|
151670
|
+
recording_url TEXT,
|
|
151671
|
+
started_at TEXT,
|
|
151672
|
+
ended_at TEXT,
|
|
151673
|
+
created_at TEXT NOT NULL
|
|
151674
|
+
);
|
|
151675
|
+
|
|
151676
|
+
CREATE TABLE IF NOT EXISTS sms_logs (
|
|
151677
|
+
id TEXT PRIMARY KEY,
|
|
151678
|
+
message_sid TEXT,
|
|
151679
|
+
from_number TEXT NOT NULL,
|
|
151680
|
+
to_number TEXT NOT NULL,
|
|
151681
|
+
direction TEXT NOT NULL,
|
|
151682
|
+
message_type TEXT NOT NULL DEFAULT 'sms',
|
|
151683
|
+
body TEXT NOT NULL,
|
|
151684
|
+
status TEXT NOT NULL DEFAULT 'queued',
|
|
151685
|
+
assistant_id TEXT,
|
|
151686
|
+
created_at TEXT NOT NULL
|
|
151687
|
+
);
|
|
151688
|
+
|
|
151689
|
+
CREATE TABLE IF NOT EXISTS routing_rules (
|
|
151690
|
+
id TEXT PRIMARY KEY,
|
|
151691
|
+
name TEXT NOT NULL,
|
|
151692
|
+
priority INTEGER NOT NULL DEFAULT 100,
|
|
151693
|
+
from_pattern TEXT,
|
|
151694
|
+
to_pattern TEXT,
|
|
151695
|
+
message_type TEXT NOT NULL DEFAULT 'all',
|
|
151696
|
+
time_of_day TEXT,
|
|
151697
|
+
day_of_week TEXT,
|
|
151698
|
+
keyword TEXT,
|
|
151699
|
+
target_assistant_id TEXT NOT NULL,
|
|
151700
|
+
target_assistant_name TEXT NOT NULL,
|
|
151701
|
+
enabled INTEGER NOT NULL DEFAULT 1,
|
|
151702
|
+
created_at TEXT NOT NULL,
|
|
151703
|
+
updated_at TEXT NOT NULL
|
|
151704
|
+
);
|
|
151705
|
+
|
|
151706
|
+
CREATE INDEX IF NOT EXISTS idx_call_logs_assistant ON call_logs(assistant_id);
|
|
151707
|
+
CREATE INDEX IF NOT EXISTS idx_call_logs_status ON call_logs(status);
|
|
151708
|
+
CREATE INDEX IF NOT EXISTS idx_call_logs_created ON call_logs(created_at);
|
|
151709
|
+
CREATE INDEX IF NOT EXISTS idx_call_logs_sid ON call_logs(call_sid);
|
|
151710
|
+
CREATE INDEX IF NOT EXISTS idx_sms_logs_assistant ON sms_logs(assistant_id);
|
|
151711
|
+
CREATE INDEX IF NOT EXISTS idx_sms_logs_created ON sms_logs(created_at);
|
|
151712
|
+
CREATE INDEX IF NOT EXISTS idx_sms_logs_sid ON sms_logs(message_sid);
|
|
151713
|
+
CREATE INDEX IF NOT EXISTS idx_routing_rules_priority ON routing_rules(priority, enabled);
|
|
151714
|
+
`);
|
|
151715
|
+
}
|
|
151716
|
+
addPhoneNumber(number, friendlyName, twilioSid, capabilities) {
|
|
151717
|
+
const id = generatePhoneId();
|
|
151718
|
+
const now2 = new Date().toISOString();
|
|
151719
|
+
this.db.prepare(`INSERT INTO phone_numbers (id, number, friendly_name, twilio_sid, voice_capable, sms_capable, whatsapp_capable, created_at, updated_at)
|
|
151720
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(id, number, friendlyName, twilioSid, capabilities?.voice !== false ? 1 : 0, capabilities?.sms !== false ? 1 : 0, capabilities?.whatsapp ? 1 : 0, now2, now2);
|
|
151721
|
+
return this.getPhoneNumber(id);
|
|
151722
|
+
}
|
|
151723
|
+
getPhoneNumber(id) {
|
|
151724
|
+
const row = this.db.prepare("SELECT * FROM phone_numbers WHERE id = ?").get(id);
|
|
151725
|
+
return row ? this.rowToPhoneNumber(row) : null;
|
|
151726
|
+
}
|
|
151727
|
+
getPhoneNumberByNumber(number) {
|
|
151728
|
+
const row = this.db.prepare("SELECT * FROM phone_numbers WHERE number = ?").get(number);
|
|
151729
|
+
return row ? this.rowToPhoneNumber(row) : null;
|
|
151730
|
+
}
|
|
151731
|
+
listPhoneNumbers(status) {
|
|
151732
|
+
let query = "SELECT * FROM phone_numbers";
|
|
151733
|
+
const params = [];
|
|
151734
|
+
if (status) {
|
|
151735
|
+
query += " WHERE status = ?";
|
|
151736
|
+
params.push(status);
|
|
151737
|
+
}
|
|
151738
|
+
query += " ORDER BY created_at DESC";
|
|
151739
|
+
const rows = this.db.prepare(query).all(...params);
|
|
151740
|
+
return rows.map((r6) => this.rowToPhoneNumber(r6));
|
|
151741
|
+
}
|
|
151742
|
+
updatePhoneNumberStatus(id, status) {
|
|
151743
|
+
const now2 = new Date().toISOString();
|
|
151744
|
+
const result = this.db.prepare("UPDATE phone_numbers SET status = ?, updated_at = ? WHERE id = ?").run(status, now2, id);
|
|
151745
|
+
return result.changes > 0;
|
|
151746
|
+
}
|
|
151747
|
+
deletePhoneNumber(id) {
|
|
151748
|
+
const result = this.db.prepare("DELETE FROM phone_numbers WHERE id = ?").run(id);
|
|
151749
|
+
return result.changes > 0;
|
|
151750
|
+
}
|
|
151751
|
+
createCallLog(params) {
|
|
151752
|
+
const id = generateCallId();
|
|
151753
|
+
const now2 = new Date().toISOString();
|
|
151754
|
+
this.db.prepare(`INSERT INTO call_logs (id, call_sid, from_number, to_number, direction, status, assistant_id, created_at)
|
|
151755
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`).run(id, params.callSid || null, params.fromNumber, params.toNumber, params.direction, params.status || "pending", params.assistantId || null, now2);
|
|
151756
|
+
return this.getCallLog(id);
|
|
151757
|
+
}
|
|
151758
|
+
getCallLog(id) {
|
|
151759
|
+
const row = this.db.prepare("SELECT * FROM call_logs WHERE id = ?").get(id);
|
|
151760
|
+
return row ? this.rowToCallLog(row) : null;
|
|
151761
|
+
}
|
|
151762
|
+
getCallLogBySid(callSid) {
|
|
151763
|
+
const row = this.db.prepare("SELECT * FROM call_logs WHERE call_sid = ?").get(callSid);
|
|
151764
|
+
return row ? this.rowToCallLog(row) : null;
|
|
151765
|
+
}
|
|
151766
|
+
updateCallLog(id, updates) {
|
|
151767
|
+
const sets = [];
|
|
151768
|
+
const params = [];
|
|
151769
|
+
if (updates.status !== undefined) {
|
|
151770
|
+
sets.push("status = ?");
|
|
151771
|
+
params.push(updates.status);
|
|
151772
|
+
}
|
|
151773
|
+
if (updates.callSid !== undefined) {
|
|
151774
|
+
sets.push("call_sid = ?");
|
|
151775
|
+
params.push(updates.callSid);
|
|
151776
|
+
}
|
|
151777
|
+
if (updates.duration !== undefined) {
|
|
151778
|
+
sets.push("duration = ?");
|
|
151779
|
+
params.push(updates.duration);
|
|
151780
|
+
}
|
|
151781
|
+
if (updates.recordingUrl !== undefined) {
|
|
151782
|
+
sets.push("recording_url = ?");
|
|
151783
|
+
params.push(updates.recordingUrl);
|
|
151784
|
+
}
|
|
151785
|
+
if (updates.startedAt !== undefined) {
|
|
151786
|
+
sets.push("started_at = ?");
|
|
151787
|
+
params.push(updates.startedAt);
|
|
151788
|
+
}
|
|
151789
|
+
if (updates.endedAt !== undefined) {
|
|
151790
|
+
sets.push("ended_at = ?");
|
|
151791
|
+
params.push(updates.endedAt);
|
|
151792
|
+
}
|
|
151793
|
+
if (sets.length === 0)
|
|
151794
|
+
return false;
|
|
151795
|
+
params.push(id);
|
|
151796
|
+
const result = this.db.prepare(`UPDATE call_logs SET ${sets.join(", ")} WHERE id = ?`).run(...params);
|
|
151797
|
+
return result.changes > 0;
|
|
151798
|
+
}
|
|
151799
|
+
listCallLogs(options) {
|
|
151800
|
+
const conditions = [];
|
|
151801
|
+
const params = [];
|
|
151802
|
+
const limit2 = options?.limit || 50;
|
|
151803
|
+
if (options?.assistantId) {
|
|
151804
|
+
conditions.push("assistant_id = ?");
|
|
151805
|
+
params.push(options.assistantId);
|
|
151806
|
+
}
|
|
151807
|
+
if (options?.direction) {
|
|
151808
|
+
conditions.push("direction = ?");
|
|
151809
|
+
params.push(options.direction);
|
|
151810
|
+
}
|
|
151811
|
+
if (options?.status) {
|
|
151812
|
+
conditions.push("status = ?");
|
|
151813
|
+
params.push(options.status);
|
|
151814
|
+
}
|
|
151815
|
+
let query = "SELECT * FROM call_logs";
|
|
151816
|
+
if (conditions.length > 0) {
|
|
151817
|
+
query += " WHERE " + conditions.join(" AND ");
|
|
151818
|
+
}
|
|
151819
|
+
query += " ORDER BY created_at DESC LIMIT ?";
|
|
151820
|
+
params.push(limit2);
|
|
151821
|
+
const rows = this.db.prepare(query).all(...params);
|
|
151822
|
+
return rows.map((row) => ({
|
|
151823
|
+
id: String(row.id),
|
|
151824
|
+
fromNumber: String(row.from_number),
|
|
151825
|
+
toNumber: String(row.to_number),
|
|
151826
|
+
direction: String(row.direction),
|
|
151827
|
+
status: String(row.status),
|
|
151828
|
+
duration: row.duration != null ? Number(row.duration) : null,
|
|
151829
|
+
startedAt: row.started_at ? String(row.started_at) : null,
|
|
151830
|
+
createdAt: String(row.created_at)
|
|
151831
|
+
}));
|
|
151832
|
+
}
|
|
151833
|
+
createSmsLog(params) {
|
|
151834
|
+
const id = generateSmsId();
|
|
151835
|
+
const now2 = new Date().toISOString();
|
|
151836
|
+
this.db.prepare(`INSERT INTO sms_logs (id, message_sid, from_number, to_number, direction, message_type, body, status, assistant_id, created_at)
|
|
151837
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(id, params.messageSid || null, params.fromNumber, params.toNumber, params.direction, params.messageType || "sms", params.body, params.status || "queued", params.assistantId || null, now2);
|
|
151838
|
+
return this.getSmsLog(id);
|
|
151839
|
+
}
|
|
151840
|
+
getSmsLog(id) {
|
|
151841
|
+
const row = this.db.prepare("SELECT * FROM sms_logs WHERE id = ?").get(id);
|
|
151842
|
+
return row ? this.rowToSmsLog(row) : null;
|
|
151843
|
+
}
|
|
151844
|
+
updateSmsStatus(id, status) {
|
|
151845
|
+
const result = this.db.prepare("UPDATE sms_logs SET status = ? WHERE id = ?").run(status, id);
|
|
151846
|
+
return result.changes > 0;
|
|
151847
|
+
}
|
|
151848
|
+
listSmsLogs(options) {
|
|
151849
|
+
const conditions = [];
|
|
151850
|
+
const params = [];
|
|
151851
|
+
const limit2 = options?.limit || 50;
|
|
151852
|
+
if (options?.assistantId) {
|
|
151853
|
+
conditions.push("assistant_id = ?");
|
|
151854
|
+
params.push(options.assistantId);
|
|
151855
|
+
}
|
|
151856
|
+
if (options?.direction) {
|
|
151857
|
+
conditions.push("direction = ?");
|
|
151858
|
+
params.push(options.direction);
|
|
151859
|
+
}
|
|
151860
|
+
if (options?.messageType) {
|
|
151861
|
+
conditions.push("message_type = ?");
|
|
151862
|
+
params.push(options.messageType);
|
|
151863
|
+
}
|
|
151864
|
+
let query = "SELECT * FROM sms_logs";
|
|
151865
|
+
if (conditions.length > 0) {
|
|
151866
|
+
query += " WHERE " + conditions.join(" AND ");
|
|
151867
|
+
}
|
|
151868
|
+
query += " ORDER BY created_at DESC LIMIT ?";
|
|
151869
|
+
params.push(limit2);
|
|
151870
|
+
const rows = this.db.prepare(query).all(...params);
|
|
151871
|
+
return rows.map((row) => ({
|
|
151872
|
+
id: String(row.id),
|
|
151873
|
+
fromNumber: String(row.from_number),
|
|
151874
|
+
toNumber: String(row.to_number),
|
|
151875
|
+
direction: String(row.direction),
|
|
151876
|
+
messageType: String(row.message_type),
|
|
151877
|
+
bodyPreview: String(row.body).slice(0, 100),
|
|
151878
|
+
status: String(row.status),
|
|
151879
|
+
createdAt: String(row.created_at)
|
|
151880
|
+
}));
|
|
151881
|
+
}
|
|
151882
|
+
getUnreadInboundSms(assistantId, limit2) {
|
|
151883
|
+
const maxLimit = limit2 || 50;
|
|
151884
|
+
const rows = this.db.prepare(`SELECT * FROM sms_logs
|
|
151885
|
+
WHERE direction = 'inbound' AND assistant_id = ? AND status = 'received'
|
|
151886
|
+
ORDER BY created_at ASC LIMIT ?`).all(assistantId, maxLimit);
|
|
151887
|
+
return rows.map((r6) => this.rowToSmsLog(r6));
|
|
151888
|
+
}
|
|
151889
|
+
createRoutingRule(params) {
|
|
151890
|
+
const id = generateRuleId();
|
|
151891
|
+
const now2 = new Date().toISOString();
|
|
151892
|
+
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)
|
|
151893
|
+
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);
|
|
151894
|
+
return this.getRoutingRule(id);
|
|
151895
|
+
}
|
|
151896
|
+
getRoutingRule(id) {
|
|
151897
|
+
const row = this.db.prepare("SELECT * FROM routing_rules WHERE id = ?").get(id);
|
|
151898
|
+
return row ? this.rowToRoutingRule(row) : null;
|
|
151899
|
+
}
|
|
151900
|
+
listRoutingRules() {
|
|
151901
|
+
const rows = this.db.prepare("SELECT * FROM routing_rules ORDER BY priority ASC, created_at ASC").all();
|
|
151902
|
+
return rows.map((r6) => this.rowToRoutingRule(r6));
|
|
151903
|
+
}
|
|
151904
|
+
updateRoutingRule(id, updates) {
|
|
151905
|
+
const sets = [];
|
|
151906
|
+
const params = [];
|
|
151907
|
+
const now2 = new Date().toISOString();
|
|
151908
|
+
if (updates.name !== undefined) {
|
|
151909
|
+
sets.push("name = ?");
|
|
151910
|
+
params.push(updates.name);
|
|
151911
|
+
}
|
|
151912
|
+
if (updates.priority !== undefined) {
|
|
151913
|
+
sets.push("priority = ?");
|
|
151914
|
+
params.push(updates.priority);
|
|
151915
|
+
}
|
|
151916
|
+
if (updates.enabled !== undefined) {
|
|
151917
|
+
sets.push("enabled = ?");
|
|
151918
|
+
params.push(updates.enabled ? 1 : 0);
|
|
151919
|
+
}
|
|
151920
|
+
sets.push("updated_at = ?");
|
|
151921
|
+
params.push(now2);
|
|
151922
|
+
if (sets.length === 1)
|
|
151923
|
+
return false;
|
|
151924
|
+
params.push(id);
|
|
151925
|
+
const result = this.db.prepare(`UPDATE routing_rules SET ${sets.join(", ")} WHERE id = ?`).run(...params);
|
|
151926
|
+
return result.changes > 0;
|
|
151927
|
+
}
|
|
151928
|
+
deleteRoutingRule(id) {
|
|
151929
|
+
const result = this.db.prepare("DELETE FROM routing_rules WHERE id = ?").run(id);
|
|
151930
|
+
return result.changes > 0;
|
|
151931
|
+
}
|
|
151932
|
+
resolveRouting(params) {
|
|
151933
|
+
const rules = this.db.prepare(`SELECT * FROM routing_rules WHERE enabled = 1 ORDER BY priority ASC`).all();
|
|
151934
|
+
for (const row of rules) {
|
|
151935
|
+
const rule = this.rowToRoutingRule(row);
|
|
151936
|
+
if (rule.messageType !== "all" && rule.messageType !== params.messageType)
|
|
151937
|
+
continue;
|
|
151938
|
+
if (rule.fromPattern && !matchPattern2(rule.fromPattern, params.fromNumber))
|
|
151939
|
+
continue;
|
|
151940
|
+
if (rule.toPattern && !matchPattern2(rule.toPattern, params.toNumber))
|
|
151941
|
+
continue;
|
|
151942
|
+
if (rule.keyword && params.body) {
|
|
151943
|
+
if (!params.body.toLowerCase().includes(rule.keyword.toLowerCase()))
|
|
151944
|
+
continue;
|
|
151945
|
+
} else if (rule.keyword && !params.body) {
|
|
151946
|
+
continue;
|
|
151947
|
+
}
|
|
151948
|
+
if (rule.timeOfDay && !matchTimeOfDay(rule.timeOfDay))
|
|
151949
|
+
continue;
|
|
151950
|
+
if (rule.dayOfWeek && !matchDayOfWeek(rule.dayOfWeek))
|
|
151951
|
+
continue;
|
|
151952
|
+
return {
|
|
151953
|
+
assistantId: rule.targetAssistantId,
|
|
151954
|
+
assistantName: rule.targetAssistantName,
|
|
151955
|
+
ruleId: rule.id
|
|
151956
|
+
};
|
|
151957
|
+
}
|
|
151958
|
+
return null;
|
|
151959
|
+
}
|
|
151960
|
+
cleanup(maxAgeDays, maxCallLogs, maxSmsLogs) {
|
|
151961
|
+
let deleted = 0;
|
|
151962
|
+
const cutoff = new Date;
|
|
151963
|
+
cutoff.setDate(cutoff.getDate() - maxAgeDays);
|
|
151964
|
+
const cutoffStr = cutoff.toISOString();
|
|
151965
|
+
const callResult = this.db.prepare("DELETE FROM call_logs WHERE created_at < ?").run(cutoffStr);
|
|
151966
|
+
deleted += callResult.changes;
|
|
151967
|
+
const smsResult = this.db.prepare("DELETE FROM sms_logs WHERE created_at < ?").run(cutoffStr);
|
|
151968
|
+
deleted += smsResult.changes;
|
|
151969
|
+
const callCount = this.db.prepare("SELECT COUNT(*) as cnt FROM call_logs").get();
|
|
151970
|
+
if (Number(callCount.cnt) > maxCallLogs) {
|
|
151971
|
+
const excess = Number(callCount.cnt) - maxCallLogs;
|
|
151972
|
+
const trimResult = this.db.prepare(`DELETE FROM call_logs WHERE id IN (
|
|
151973
|
+
SELECT id FROM call_logs ORDER BY created_at ASC LIMIT ?
|
|
151974
|
+
)`).run(excess);
|
|
151975
|
+
deleted += trimResult.changes;
|
|
151976
|
+
}
|
|
151977
|
+
const smsCount = this.db.prepare("SELECT COUNT(*) as cnt FROM sms_logs").get();
|
|
151978
|
+
if (Number(smsCount.cnt) > maxSmsLogs) {
|
|
151979
|
+
const excess = Number(smsCount.cnt) - maxSmsLogs;
|
|
151980
|
+
const trimResult = this.db.prepare(`DELETE FROM sms_logs WHERE id IN (
|
|
151981
|
+
SELECT id FROM sms_logs ORDER BY created_at ASC LIMIT ?
|
|
151982
|
+
)`).run(excess);
|
|
151983
|
+
deleted += trimResult.changes;
|
|
151984
|
+
}
|
|
151985
|
+
return deleted;
|
|
151986
|
+
}
|
|
151987
|
+
close() {
|
|
151988
|
+
try {
|
|
151989
|
+
this.db.close();
|
|
151990
|
+
} catch {}
|
|
151991
|
+
}
|
|
151992
|
+
rowToPhoneNumber(row) {
|
|
151993
|
+
return {
|
|
151994
|
+
id: String(row.id),
|
|
151995
|
+
number: String(row.number),
|
|
151996
|
+
friendlyName: row.friendly_name ? String(row.friendly_name) : null,
|
|
151997
|
+
twilioSid: row.twilio_sid ? String(row.twilio_sid) : null,
|
|
151998
|
+
status: String(row.status),
|
|
151999
|
+
capabilities: {
|
|
152000
|
+
voice: Boolean(row.voice_capable),
|
|
152001
|
+
sms: Boolean(row.sms_capable),
|
|
152002
|
+
whatsapp: Boolean(row.whatsapp_capable)
|
|
152003
|
+
},
|
|
152004
|
+
createdAt: String(row.created_at),
|
|
152005
|
+
updatedAt: String(row.updated_at)
|
|
152006
|
+
};
|
|
152007
|
+
}
|
|
152008
|
+
rowToCallLog(row) {
|
|
152009
|
+
return {
|
|
152010
|
+
id: String(row.id),
|
|
152011
|
+
callSid: row.call_sid ? String(row.call_sid) : null,
|
|
152012
|
+
fromNumber: String(row.from_number),
|
|
152013
|
+
toNumber: String(row.to_number),
|
|
152014
|
+
direction: String(row.direction),
|
|
152015
|
+
status: String(row.status),
|
|
152016
|
+
assistantId: row.assistant_id ? String(row.assistant_id) : null,
|
|
152017
|
+
duration: row.duration != null ? Number(row.duration) : null,
|
|
152018
|
+
recordingUrl: row.recording_url ? String(row.recording_url) : null,
|
|
152019
|
+
startedAt: row.started_at ? String(row.started_at) : null,
|
|
152020
|
+
endedAt: row.ended_at ? String(row.ended_at) : null,
|
|
152021
|
+
createdAt: String(row.created_at)
|
|
152022
|
+
};
|
|
152023
|
+
}
|
|
152024
|
+
rowToSmsLog(row) {
|
|
152025
|
+
return {
|
|
152026
|
+
id: String(row.id),
|
|
152027
|
+
messageSid: row.message_sid ? String(row.message_sid) : null,
|
|
152028
|
+
fromNumber: String(row.from_number),
|
|
152029
|
+
toNumber: String(row.to_number),
|
|
152030
|
+
direction: String(row.direction),
|
|
152031
|
+
messageType: String(row.message_type),
|
|
152032
|
+
body: String(row.body),
|
|
152033
|
+
status: String(row.status),
|
|
152034
|
+
assistantId: row.assistant_id ? String(row.assistant_id) : null,
|
|
152035
|
+
createdAt: String(row.created_at)
|
|
152036
|
+
};
|
|
152037
|
+
}
|
|
152038
|
+
rowToRoutingRule(row) {
|
|
152039
|
+
return {
|
|
152040
|
+
id: String(row.id),
|
|
152041
|
+
name: String(row.name),
|
|
152042
|
+
priority: Number(row.priority),
|
|
152043
|
+
fromPattern: row.from_pattern ? String(row.from_pattern) : null,
|
|
152044
|
+
toPattern: row.to_pattern ? String(row.to_pattern) : null,
|
|
152045
|
+
messageType: String(row.message_type),
|
|
152046
|
+
timeOfDay: row.time_of_day ? String(row.time_of_day) : null,
|
|
152047
|
+
dayOfWeek: row.day_of_week ? String(row.day_of_week) : null,
|
|
152048
|
+
keyword: row.keyword ? String(row.keyword) : null,
|
|
152049
|
+
targetAssistantId: String(row.target_assistant_id),
|
|
152050
|
+
targetAssistantName: String(row.target_assistant_name),
|
|
152051
|
+
enabled: Boolean(row.enabled),
|
|
152052
|
+
createdAt: String(row.created_at),
|
|
152053
|
+
updatedAt: String(row.updated_at)
|
|
152054
|
+
};
|
|
152055
|
+
}
|
|
152056
|
+
}
|
|
152057
|
+
function matchPattern2(pattern, value) {
|
|
152058
|
+
if (pattern === "*")
|
|
152059
|
+
return true;
|
|
152060
|
+
if (pattern.endsWith("*")) {
|
|
152061
|
+
return value.startsWith(pattern.slice(0, -1));
|
|
152062
|
+
}
|
|
152063
|
+
if (pattern.startsWith("*")) {
|
|
152064
|
+
return value.endsWith(pattern.slice(1));
|
|
152065
|
+
}
|
|
152066
|
+
return pattern === value;
|
|
152067
|
+
}
|
|
152068
|
+
function matchTimeOfDay(timeRange) {
|
|
152069
|
+
const [start, end] = timeRange.split("-");
|
|
152070
|
+
if (!start || !end)
|
|
152071
|
+
return true;
|
|
152072
|
+
const now2 = new Date;
|
|
152073
|
+
const currentMinutes = now2.getHours() * 60 + now2.getMinutes();
|
|
152074
|
+
const [startH, startM] = start.split(":").map(Number);
|
|
152075
|
+
const [endH, endM] = end.split(":").map(Number);
|
|
152076
|
+
const startMinutes = startH * 60 + startM;
|
|
152077
|
+
const endMinutes = endH * 60 + endM;
|
|
152078
|
+
if (startMinutes <= endMinutes) {
|
|
152079
|
+
return currentMinutes >= startMinutes && currentMinutes <= endMinutes;
|
|
152080
|
+
}
|
|
152081
|
+
return currentMinutes >= startMinutes || currentMinutes <= endMinutes;
|
|
152082
|
+
}
|
|
152083
|
+
function matchDayOfWeek(dayList) {
|
|
152084
|
+
const days = ["sun", "mon", "tue", "wed", "thu", "fri", "sat"];
|
|
152085
|
+
const today = days[new Date().getDay()];
|
|
152086
|
+
const allowedDays = dayList.toLowerCase().split(",").map((d5) => d5.trim());
|
|
152087
|
+
return allowedDays.includes(today);
|
|
152088
|
+
}
|
|
152089
|
+
|
|
152090
|
+
// ../core/src/telephony/twilio-client.ts
|
|
152091
|
+
var TWILIO_API_BASE = "https://api.twilio.com/2010-04-01";
|
|
152092
|
+
|
|
152093
|
+
class TwilioClient {
|
|
152094
|
+
accountSid;
|
|
152095
|
+
authToken;
|
|
152096
|
+
authHeader;
|
|
152097
|
+
constructor(config) {
|
|
152098
|
+
this.accountSid = config.accountSid;
|
|
152099
|
+
this.authToken = config.authToken;
|
|
152100
|
+
this.authHeader = "Basic " + Buffer.from(`${this.accountSid}:${this.authToken}`).toString("base64");
|
|
152101
|
+
}
|
|
152102
|
+
isConfigured() {
|
|
152103
|
+
return Boolean(this.accountSid && this.authToken);
|
|
152104
|
+
}
|
|
152105
|
+
async makeCall(params) {
|
|
152106
|
+
const body = new URLSearchParams;
|
|
152107
|
+
body.append("To", params.to);
|
|
152108
|
+
body.append("From", params.from);
|
|
152109
|
+
if (params.url) {
|
|
152110
|
+
body.append("Url", params.url);
|
|
152111
|
+
} else if (params.twiml) {
|
|
152112
|
+
body.append("Twiml", params.twiml);
|
|
152113
|
+
}
|
|
152114
|
+
if (params.statusCallback) {
|
|
152115
|
+
body.append("StatusCallback", params.statusCallback);
|
|
152116
|
+
body.append("StatusCallbackEvent", "initiated ringing answered completed");
|
|
152117
|
+
body.append("StatusCallbackMethod", "POST");
|
|
152118
|
+
}
|
|
152119
|
+
if (params.record) {
|
|
152120
|
+
body.append("Record", "true");
|
|
152121
|
+
}
|
|
152122
|
+
return this.post(`/Accounts/${this.accountSid}/Calls.json`, body);
|
|
152123
|
+
}
|
|
152124
|
+
async updateCall(callSid, updates) {
|
|
152125
|
+
const body = new URLSearchParams;
|
|
152126
|
+
if (updates.status) {
|
|
152127
|
+
body.append("Status", updates.status);
|
|
152128
|
+
}
|
|
152129
|
+
if (updates.url) {
|
|
152130
|
+
body.append("Url", updates.url);
|
|
152131
|
+
}
|
|
152132
|
+
if (updates.twiml) {
|
|
152133
|
+
body.append("Twiml", updates.twiml);
|
|
152134
|
+
}
|
|
152135
|
+
return this.post(`/Accounts/${this.accountSid}/Calls/${callSid}.json`, body);
|
|
152136
|
+
}
|
|
152137
|
+
async getCall(callSid) {
|
|
152138
|
+
return this.get(`/Accounts/${this.accountSid}/Calls/${callSid}.json`);
|
|
152139
|
+
}
|
|
152140
|
+
async sendSms(params) {
|
|
152141
|
+
const body = new URLSearchParams;
|
|
152142
|
+
body.append("To", params.to);
|
|
152143
|
+
body.append("From", params.from);
|
|
152144
|
+
body.append("Body", params.body);
|
|
152145
|
+
if (params.statusCallback) {
|
|
152146
|
+
body.append("StatusCallback", params.statusCallback);
|
|
152147
|
+
}
|
|
152148
|
+
return this.post(`/Accounts/${this.accountSid}/Messages.json`, body);
|
|
152149
|
+
}
|
|
152150
|
+
async sendWhatsApp(params) {
|
|
152151
|
+
const to3 = params.to.startsWith("whatsapp:") ? params.to : `whatsapp:${params.to}`;
|
|
152152
|
+
const from = params.from.startsWith("whatsapp:") ? params.from : `whatsapp:${params.from}`;
|
|
152153
|
+
return this.sendSms({
|
|
152154
|
+
...params,
|
|
152155
|
+
to: to3,
|
|
152156
|
+
from
|
|
152157
|
+
});
|
|
152158
|
+
}
|
|
152159
|
+
async listPhoneNumbers() {
|
|
152160
|
+
return this.get(`/Accounts/${this.accountSid}/IncomingPhoneNumbers.json`);
|
|
152161
|
+
}
|
|
152162
|
+
async getPhoneNumber(phoneSid) {
|
|
152163
|
+
return this.get(`/Accounts/${this.accountSid}/IncomingPhoneNumbers/${phoneSid}.json`);
|
|
152164
|
+
}
|
|
152165
|
+
async updatePhoneNumber(phoneSid, updates) {
|
|
152166
|
+
const body = new URLSearchParams;
|
|
152167
|
+
if (updates.voiceUrl) {
|
|
152168
|
+
body.append("VoiceUrl", updates.voiceUrl);
|
|
152169
|
+
body.append("VoiceMethod", updates.voiceMethod || "POST");
|
|
152170
|
+
}
|
|
152171
|
+
if (updates.smsUrl) {
|
|
152172
|
+
body.append("SmsUrl", updates.smsUrl);
|
|
152173
|
+
body.append("SmsMethod", updates.smsMethod || "POST");
|
|
152174
|
+
}
|
|
152175
|
+
if (updates.statusCallback) {
|
|
152176
|
+
body.append("StatusCallback", updates.statusCallback);
|
|
152177
|
+
}
|
|
152178
|
+
return this.post(`/Accounts/${this.accountSid}/IncomingPhoneNumbers/${phoneSid}.json`, body);
|
|
152179
|
+
}
|
|
152180
|
+
async verifyCredentials() {
|
|
152181
|
+
return this.get(`/Accounts/${this.accountSid}.json`);
|
|
152182
|
+
}
|
|
152183
|
+
async get(path2) {
|
|
152184
|
+
try {
|
|
152185
|
+
const response = await fetch(`${TWILIO_API_BASE}${path2}`, {
|
|
152186
|
+
method: "GET",
|
|
152187
|
+
headers: {
|
|
152188
|
+
Authorization: this.authHeader,
|
|
152189
|
+
Accept: "application/json"
|
|
152190
|
+
}
|
|
152191
|
+
});
|
|
152192
|
+
const data = await response.json();
|
|
152193
|
+
if (!response.ok) {
|
|
152194
|
+
return {
|
|
152195
|
+
success: false,
|
|
152196
|
+
error: data.message || `HTTP ${response.status}`,
|
|
152197
|
+
statusCode: response.status
|
|
152198
|
+
};
|
|
152199
|
+
}
|
|
152200
|
+
return { success: true, data, statusCode: response.status };
|
|
152201
|
+
} catch (error2) {
|
|
152202
|
+
return {
|
|
152203
|
+
success: false,
|
|
152204
|
+
error: error2 instanceof Error ? error2.message : String(error2),
|
|
152205
|
+
statusCode: 0
|
|
152206
|
+
};
|
|
152207
|
+
}
|
|
152208
|
+
}
|
|
152209
|
+
async post(path2, body) {
|
|
152210
|
+
try {
|
|
152211
|
+
const response = await fetch(`${TWILIO_API_BASE}${path2}`, {
|
|
152212
|
+
method: "POST",
|
|
152213
|
+
headers: {
|
|
152214
|
+
Authorization: this.authHeader,
|
|
152215
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
152216
|
+
Accept: "application/json"
|
|
152217
|
+
},
|
|
152218
|
+
body: body.toString()
|
|
152219
|
+
});
|
|
152220
|
+
const data = await response.json();
|
|
152221
|
+
if (!response.ok) {
|
|
152222
|
+
return {
|
|
152223
|
+
success: false,
|
|
152224
|
+
error: data.message || `HTTP ${response.status}`,
|
|
152225
|
+
statusCode: response.status
|
|
152226
|
+
};
|
|
152227
|
+
}
|
|
152228
|
+
return { success: true, data, statusCode: response.status };
|
|
152229
|
+
} catch (error2) {
|
|
152230
|
+
return {
|
|
152231
|
+
success: false,
|
|
152232
|
+
error: error2 instanceof Error ? error2.message : String(error2),
|
|
152233
|
+
statusCode: 0
|
|
152234
|
+
};
|
|
152235
|
+
}
|
|
152236
|
+
}
|
|
152237
|
+
}
|
|
152238
|
+
|
|
152239
|
+
// ../core/src/telephony/call-manager.ts
|
|
152240
|
+
class CallManager {
|
|
152241
|
+
activeCalls = new Map;
|
|
152242
|
+
maxCallDurationMs;
|
|
152243
|
+
staleTimeoutMs;
|
|
152244
|
+
constructor(config) {
|
|
152245
|
+
this.maxCallDurationMs = (config?.maxCallDurationSeconds || 3600) * 1000;
|
|
152246
|
+
this.staleTimeoutMs = (config?.staleTimeoutSeconds || 300) * 1000;
|
|
152247
|
+
}
|
|
152248
|
+
addCall(params) {
|
|
152249
|
+
const call = {
|
|
152250
|
+
callSid: params.callSid,
|
|
152251
|
+
streamSid: null,
|
|
152252
|
+
fromNumber: params.fromNumber,
|
|
152253
|
+
toNumber: params.toNumber,
|
|
152254
|
+
direction: params.direction,
|
|
152255
|
+
state: "connecting",
|
|
152256
|
+
assistantId: params.assistantId || null,
|
|
152257
|
+
bridgeId: null,
|
|
152258
|
+
startedAt: Date.now(),
|
|
152259
|
+
lastActivityAt: Date.now()
|
|
152260
|
+
};
|
|
152261
|
+
this.activeCalls.set(params.callSid, call);
|
|
152262
|
+
return call;
|
|
152263
|
+
}
|
|
152264
|
+
getCall(callSid) {
|
|
152265
|
+
return this.activeCalls.get(callSid) || null;
|
|
152266
|
+
}
|
|
152267
|
+
updateState(callSid, state) {
|
|
152268
|
+
const call = this.activeCalls.get(callSid);
|
|
152269
|
+
if (!call)
|
|
152270
|
+
return false;
|
|
152271
|
+
const validTransitions = {
|
|
152272
|
+
connecting: ["ringing", "active", "ending"],
|
|
152273
|
+
ringing: ["bridging", "active", "ending"],
|
|
152274
|
+
bridging: ["active", "ending"],
|
|
152275
|
+
active: ["ending"],
|
|
152276
|
+
ending: []
|
|
152277
|
+
};
|
|
152278
|
+
if (!validTransitions[call.state].includes(state)) {
|
|
152279
|
+
return false;
|
|
152280
|
+
}
|
|
152281
|
+
call.state = state;
|
|
152282
|
+
call.lastActivityAt = Date.now();
|
|
152283
|
+
return true;
|
|
152284
|
+
}
|
|
152285
|
+
setStreamSid(callSid, streamSid) {
|
|
152286
|
+
const call = this.activeCalls.get(callSid);
|
|
152287
|
+
if (!call)
|
|
152288
|
+
return false;
|
|
152289
|
+
call.streamSid = streamSid;
|
|
152290
|
+
call.lastActivityAt = Date.now();
|
|
152291
|
+
return true;
|
|
152292
|
+
}
|
|
152293
|
+
setBridgeId(callSid, bridgeId) {
|
|
152294
|
+
const call = this.activeCalls.get(callSid);
|
|
152295
|
+
if (!call)
|
|
152296
|
+
return false;
|
|
152297
|
+
call.bridgeId = bridgeId;
|
|
152298
|
+
call.lastActivityAt = Date.now();
|
|
152299
|
+
return true;
|
|
152300
|
+
}
|
|
152301
|
+
touchCall(callSid) {
|
|
152302
|
+
const call = this.activeCalls.get(callSid);
|
|
152303
|
+
if (call) {
|
|
152304
|
+
call.lastActivityAt = Date.now();
|
|
152305
|
+
}
|
|
152306
|
+
}
|
|
152307
|
+
endCall(callSid) {
|
|
152308
|
+
const call = this.activeCalls.get(callSid);
|
|
152309
|
+
if (!call)
|
|
152310
|
+
return null;
|
|
152311
|
+
call.state = "ending";
|
|
152312
|
+
this.activeCalls.delete(callSid);
|
|
152313
|
+
return call;
|
|
152314
|
+
}
|
|
152315
|
+
getActiveCalls() {
|
|
152316
|
+
return Array.from(this.activeCalls.values());
|
|
152317
|
+
}
|
|
152318
|
+
getActiveCallCount() {
|
|
152319
|
+
return this.activeCalls.size;
|
|
152320
|
+
}
|
|
152321
|
+
getCallByStreamSid(streamSid) {
|
|
152322
|
+
for (const call of this.activeCalls.values()) {
|
|
152323
|
+
if (call.streamSid === streamSid)
|
|
152324
|
+
return call;
|
|
152325
|
+
}
|
|
152326
|
+
return null;
|
|
152327
|
+
}
|
|
152328
|
+
cleanupStaleCalls() {
|
|
152329
|
+
const now2 = Date.now();
|
|
152330
|
+
const removed = [];
|
|
152331
|
+
for (const [callSid, call] of this.activeCalls) {
|
|
152332
|
+
if (now2 - call.startedAt > this.maxCallDurationMs) {
|
|
152333
|
+
this.activeCalls.delete(callSid);
|
|
152334
|
+
removed.push(callSid);
|
|
152335
|
+
continue;
|
|
152336
|
+
}
|
|
152337
|
+
if (now2 - call.lastActivityAt > this.staleTimeoutMs) {
|
|
152338
|
+
this.activeCalls.delete(callSid);
|
|
152339
|
+
removed.push(callSid);
|
|
152340
|
+
}
|
|
152341
|
+
}
|
|
152342
|
+
return removed;
|
|
152343
|
+
}
|
|
152344
|
+
getCallDuration(callSid) {
|
|
152345
|
+
const call = this.activeCalls.get(callSid);
|
|
152346
|
+
if (!call)
|
|
152347
|
+
return null;
|
|
152348
|
+
return Math.floor((Date.now() - call.startedAt) / 1000);
|
|
152349
|
+
}
|
|
152350
|
+
endAllCalls() {
|
|
152351
|
+
const calls = Array.from(this.activeCalls.values());
|
|
152352
|
+
this.activeCalls.clear();
|
|
152353
|
+
return calls;
|
|
152354
|
+
}
|
|
152355
|
+
}
|
|
152356
|
+
|
|
152357
|
+
// ../core/src/telephony/voice-bridge.ts
|
|
152358
|
+
init_src2();
|
|
152359
|
+
|
|
152360
|
+
// ../core/src/telephony/audio-codec.ts
|
|
152361
|
+
var MULAW_MAX = 8191;
|
|
152362
|
+
var MULAW_BIAS = 33;
|
|
152363
|
+
function pcmSampleToMulaw(sample) {
|
|
152364
|
+
const sign2 = sample >> 8 & 128;
|
|
152365
|
+
if (sign2 !== 0)
|
|
152366
|
+
sample = -sample;
|
|
152367
|
+
if (sample > MULAW_MAX)
|
|
152368
|
+
sample = MULAW_MAX;
|
|
152369
|
+
sample += MULAW_BIAS;
|
|
152370
|
+
let exponent = 7;
|
|
152371
|
+
let mask = 16384;
|
|
152372
|
+
while (exponent > 0 && (sample & mask) === 0) {
|
|
152373
|
+
exponent--;
|
|
152374
|
+
mask >>= 1;
|
|
152375
|
+
}
|
|
152376
|
+
const mantissa = sample >> exponent + 3 & 15;
|
|
152377
|
+
const mulawByte = ~(sign2 | exponent << 4 | mantissa) & 255;
|
|
152378
|
+
return mulawByte;
|
|
152379
|
+
}
|
|
152380
|
+
function mulawSampleToPcm(mulawByte) {
|
|
152381
|
+
mulawByte = ~mulawByte & 255;
|
|
152382
|
+
const sign2 = mulawByte & 128;
|
|
152383
|
+
const exponent = mulawByte >> 4 & 7;
|
|
152384
|
+
const mantissa = mulawByte & 15;
|
|
152385
|
+
let sample = (mantissa << 3) + MULAW_BIAS << exponent;
|
|
152386
|
+
sample -= MULAW_BIAS;
|
|
152387
|
+
return sign2 !== 0 ? -sample : sample;
|
|
152388
|
+
}
|
|
152389
|
+
function pcmToMulaw(pcmBuffer) {
|
|
152390
|
+
const numSamples = Math.floor(pcmBuffer.length / 2);
|
|
152391
|
+
const mulawBuffer = Buffer.alloc(numSamples);
|
|
152392
|
+
for (let i5 = 0;i5 < numSamples; i5++) {
|
|
152393
|
+
const sample = pcmBuffer.readInt16LE(i5 * 2);
|
|
152394
|
+
mulawBuffer[i5] = pcmSampleToMulaw(sample);
|
|
152395
|
+
}
|
|
152396
|
+
return mulawBuffer;
|
|
152397
|
+
}
|
|
152398
|
+
function mulawToPcm(mulawBuffer) {
|
|
152399
|
+
const pcmBuffer = Buffer.alloc(mulawBuffer.length * 2);
|
|
152400
|
+
for (let i5 = 0;i5 < mulawBuffer.length; i5++) {
|
|
152401
|
+
const sample = mulawSampleToPcm(mulawBuffer[i5]);
|
|
152402
|
+
pcmBuffer.writeInt16LE(sample, i5 * 2);
|
|
152403
|
+
}
|
|
152404
|
+
return pcmBuffer;
|
|
152405
|
+
}
|
|
152406
|
+
function downsample16kTo8k(pcm16k) {
|
|
152407
|
+
const numSamples = Math.floor(pcm16k.length / 2);
|
|
152408
|
+
const outputSamples = Math.floor(numSamples / 2);
|
|
152409
|
+
const pcm8k = Buffer.alloc(outputSamples * 2);
|
|
152410
|
+
for (let i5 = 0;i5 < outputSamples; i5++) {
|
|
152411
|
+
const sample = pcm16k.readInt16LE(i5 * 2 * 2);
|
|
152412
|
+
pcm8k.writeInt16LE(sample, i5 * 2);
|
|
152413
|
+
}
|
|
152414
|
+
return pcm8k;
|
|
152415
|
+
}
|
|
152416
|
+
function upsample8kTo16k(pcm8k) {
|
|
152417
|
+
const numSamples = Math.floor(pcm8k.length / 2);
|
|
152418
|
+
const pcm16k = Buffer.alloc(numSamples * 2 * 2);
|
|
152419
|
+
for (let i5 = 0;i5 < numSamples; i5++) {
|
|
152420
|
+
const current = pcm8k.readInt16LE(i5 * 2);
|
|
152421
|
+
const next = i5 + 1 < numSamples ? pcm8k.readInt16LE((i5 + 1) * 2) : current;
|
|
152422
|
+
const interpolated = Math.round((current + next) / 2);
|
|
152423
|
+
pcm16k.writeInt16LE(current, i5 * 4);
|
|
152424
|
+
pcm16k.writeInt16LE(interpolated, i5 * 4 + 2);
|
|
152425
|
+
}
|
|
152426
|
+
return pcm16k;
|
|
152427
|
+
}
|
|
152428
|
+
function twilioToElevenLabs(mulawBuffer) {
|
|
152429
|
+
const pcm8k = mulawToPcm(mulawBuffer);
|
|
152430
|
+
return upsample8kTo16k(pcm8k);
|
|
152431
|
+
}
|
|
152432
|
+
function elevenLabsToTwilio(pcm16k) {
|
|
152433
|
+
const pcm8k = downsample16kTo8k(pcm16k);
|
|
152434
|
+
return pcmToMulaw(pcm8k);
|
|
152435
|
+
}
|
|
152436
|
+
function decodeTwilioPayload(base64Payload) {
|
|
152437
|
+
const mulawBuffer = Buffer.from(base64Payload, "base64");
|
|
152438
|
+
return twilioToElevenLabs(mulawBuffer);
|
|
152439
|
+
}
|
|
152440
|
+
function encodeTwilioPayload(pcm16k) {
|
|
152441
|
+
const mulawBuffer = elevenLabsToTwilio(pcm16k);
|
|
152442
|
+
return mulawBuffer.toString("base64");
|
|
152443
|
+
}
|
|
152444
|
+
|
|
152445
|
+
// ../core/src/telephony/voice-bridge.ts
|
|
152446
|
+
var ELEVENLABS_WS_BASE = "wss://api.elevenlabs.io/v1/convai/conversation";
|
|
152447
|
+
|
|
152448
|
+
class VoiceBridge {
|
|
152449
|
+
config;
|
|
152450
|
+
connections = new Map;
|
|
152451
|
+
constructor(config) {
|
|
152452
|
+
this.config = config;
|
|
152453
|
+
}
|
|
152454
|
+
isConfigured() {
|
|
152455
|
+
return Boolean(this.config.elevenLabsApiKey && this.config.elevenLabsAgentId);
|
|
152456
|
+
}
|
|
152457
|
+
async createBridge(callSid, streamSid, sendToTwilio) {
|
|
152458
|
+
const id = `bridge_${generateId().slice(0, 12)}`;
|
|
152459
|
+
const connection = new BridgeConnection({
|
|
152460
|
+
id,
|
|
152461
|
+
callSid,
|
|
152462
|
+
streamSid,
|
|
152463
|
+
config: this.config,
|
|
152464
|
+
sendToTwilio
|
|
152465
|
+
});
|
|
152466
|
+
this.connections.set(id, connection);
|
|
152467
|
+
try {
|
|
152468
|
+
await connection.connect();
|
|
152469
|
+
} catch (error2) {
|
|
152470
|
+
this.connections.delete(id);
|
|
152471
|
+
throw error2;
|
|
152472
|
+
}
|
|
152473
|
+
return id;
|
|
152474
|
+
}
|
|
152475
|
+
handleTwilioMedia(bridgeId, message) {
|
|
152476
|
+
const connection = this.connections.get(bridgeId);
|
|
152477
|
+
if (!connection)
|
|
152478
|
+
return;
|
|
152479
|
+
if (message.event === "media" && message.media?.payload) {
|
|
152480
|
+
connection.forwardToElevenLabs(message.media.payload);
|
|
152481
|
+
} else if (message.event === "stop") {
|
|
152482
|
+
connection.close();
|
|
152483
|
+
this.connections.delete(bridgeId);
|
|
152484
|
+
}
|
|
152485
|
+
}
|
|
152486
|
+
closeBridge(bridgeId) {
|
|
152487
|
+
const connection = this.connections.get(bridgeId);
|
|
152488
|
+
if (connection) {
|
|
152489
|
+
connection.close();
|
|
152490
|
+
this.connections.delete(bridgeId);
|
|
152491
|
+
}
|
|
152492
|
+
}
|
|
152493
|
+
getConnection(bridgeId) {
|
|
152494
|
+
const conn = this.connections.get(bridgeId);
|
|
152495
|
+
if (!conn)
|
|
152496
|
+
return null;
|
|
152497
|
+
return conn.getInfo();
|
|
152498
|
+
}
|
|
152499
|
+
getActiveConnections() {
|
|
152500
|
+
return Array.from(this.connections.values()).map((c6) => c6.getInfo());
|
|
152501
|
+
}
|
|
152502
|
+
closeAll() {
|
|
152503
|
+
for (const connection of this.connections.values()) {
|
|
152504
|
+
connection.close();
|
|
152505
|
+
}
|
|
152506
|
+
this.connections.clear();
|
|
152507
|
+
}
|
|
152508
|
+
}
|
|
152509
|
+
|
|
152510
|
+
class BridgeConnection {
|
|
152511
|
+
id;
|
|
152512
|
+
callSid;
|
|
152513
|
+
streamSid;
|
|
152514
|
+
config;
|
|
152515
|
+
sendToTwilio;
|
|
152516
|
+
elevenLabsWs = null;
|
|
152517
|
+
state = "connecting";
|
|
152518
|
+
startedAt;
|
|
152519
|
+
constructor(options) {
|
|
152520
|
+
this.id = options.id;
|
|
152521
|
+
this.callSid = options.callSid;
|
|
152522
|
+
this.streamSid = options.streamSid;
|
|
152523
|
+
this.config = options.config;
|
|
152524
|
+
this.sendToTwilio = options.sendToTwilio;
|
|
152525
|
+
this.startedAt = Date.now();
|
|
152526
|
+
}
|
|
152527
|
+
async connect() {
|
|
152528
|
+
const url = `${ELEVENLABS_WS_BASE}?agent_id=${this.config.elevenLabsAgentId}`;
|
|
152529
|
+
return new Promise((resolve5, reject) => {
|
|
152530
|
+
try {
|
|
152531
|
+
this.elevenLabsWs = new WebSocket(url, {
|
|
152532
|
+
headers: {
|
|
152533
|
+
"xi-api-key": this.config.elevenLabsApiKey
|
|
152534
|
+
}
|
|
152535
|
+
});
|
|
152536
|
+
this.elevenLabsWs.onopen = () => {
|
|
152537
|
+
this.state = "active";
|
|
152538
|
+
resolve5();
|
|
152539
|
+
};
|
|
152540
|
+
this.elevenLabsWs.onmessage = (event) => {
|
|
152541
|
+
this.handleElevenLabsMessage(event.data);
|
|
152542
|
+
};
|
|
152543
|
+
this.elevenLabsWs.onerror = (event) => {
|
|
152544
|
+
console.error(`[VoiceBridge ${this.id}] ElevenLabs WS error:`, event);
|
|
152545
|
+
if (this.state === "connecting") {
|
|
152546
|
+
reject(new Error("Failed to connect to ElevenLabs"));
|
|
152547
|
+
}
|
|
152548
|
+
};
|
|
152549
|
+
this.elevenLabsWs.onclose = () => {
|
|
152550
|
+
this.state = "closed";
|
|
152551
|
+
};
|
|
152552
|
+
setTimeout(() => {
|
|
152553
|
+
if (this.state === "connecting") {
|
|
152554
|
+
this.close();
|
|
152555
|
+
reject(new Error("ElevenLabs connection timeout"));
|
|
152556
|
+
}
|
|
152557
|
+
}, 1e4);
|
|
152558
|
+
} catch (error2) {
|
|
152559
|
+
reject(error2);
|
|
152560
|
+
}
|
|
152561
|
+
});
|
|
152562
|
+
}
|
|
152563
|
+
forwardToElevenLabs(base64Payload) {
|
|
152564
|
+
if (this.state !== "active" || !this.elevenLabsWs)
|
|
152565
|
+
return;
|
|
152566
|
+
try {
|
|
152567
|
+
const pcm16k = decodeTwilioPayload(base64Payload);
|
|
152568
|
+
const message = JSON.stringify({
|
|
152569
|
+
user_audio_chunk: pcm16k.toString("base64")
|
|
152570
|
+
});
|
|
152571
|
+
if (this.elevenLabsWs.readyState === WebSocket.OPEN) {
|
|
152572
|
+
this.elevenLabsWs.send(message);
|
|
152573
|
+
}
|
|
152574
|
+
} catch (error2) {
|
|
152575
|
+
console.error(`[VoiceBridge ${this.id}] Error forwarding to ElevenLabs:`, error2);
|
|
152576
|
+
}
|
|
152577
|
+
}
|
|
152578
|
+
handleElevenLabsMessage(data) {
|
|
152579
|
+
try {
|
|
152580
|
+
const message = JSON.parse(data);
|
|
152581
|
+
if (message.audio?.chunk) {
|
|
152582
|
+
const pcm16k = Buffer.from(message.audio.chunk, "base64");
|
|
152583
|
+
const twilioPayload = encodeTwilioPayload(pcm16k);
|
|
152584
|
+
const twilioMessage = JSON.stringify({
|
|
152585
|
+
event: "media",
|
|
152586
|
+
streamSid: this.streamSid,
|
|
152587
|
+
media: {
|
|
152588
|
+
payload: twilioPayload
|
|
152589
|
+
}
|
|
152590
|
+
});
|
|
152591
|
+
this.sendToTwilio(twilioMessage);
|
|
152592
|
+
}
|
|
152593
|
+
if (message.type === "conversation_initiation_metadata") {} else if (message.type === "agent_response") {} else if (message.type === "user_transcript") {}
|
|
152594
|
+
} catch (error2) {
|
|
152595
|
+
console.error(`[VoiceBridge ${this.id}] Error handling ElevenLabs message:`, error2);
|
|
152596
|
+
}
|
|
152597
|
+
}
|
|
152598
|
+
close() {
|
|
152599
|
+
if (this.state === "closed" || this.state === "closing")
|
|
152600
|
+
return;
|
|
152601
|
+
this.state = "closing";
|
|
152602
|
+
if (this.elevenLabsWs) {
|
|
152603
|
+
try {
|
|
152604
|
+
this.elevenLabsWs.close();
|
|
152605
|
+
} catch {}
|
|
152606
|
+
this.elevenLabsWs = null;
|
|
152607
|
+
}
|
|
152608
|
+
this.state = "closed";
|
|
152609
|
+
}
|
|
152610
|
+
getInfo() {
|
|
152611
|
+
return {
|
|
152612
|
+
id: this.id,
|
|
152613
|
+
callSid: this.callSid,
|
|
152614
|
+
streamSid: this.streamSid,
|
|
152615
|
+
state: this.state,
|
|
152616
|
+
startedAt: this.startedAt
|
|
152617
|
+
};
|
|
152618
|
+
}
|
|
152619
|
+
}
|
|
152620
|
+
|
|
152621
|
+
// ../core/src/telephony/manager.ts
|
|
152622
|
+
class TelephonyManager {
|
|
152623
|
+
assistantId;
|
|
152624
|
+
assistantName;
|
|
152625
|
+
config;
|
|
152626
|
+
store;
|
|
152627
|
+
twilioClient = null;
|
|
152628
|
+
callManager;
|
|
152629
|
+
voiceBridge = null;
|
|
152630
|
+
constructor(options) {
|
|
152631
|
+
this.assistantId = options.assistantId;
|
|
152632
|
+
this.assistantName = options.assistantName;
|
|
152633
|
+
this.config = options.config;
|
|
152634
|
+
this.store = new TelephonyStore;
|
|
152635
|
+
this.callManager = new CallManager({
|
|
152636
|
+
maxCallDurationSeconds: options.config.voice?.maxCallDurationSeconds
|
|
152637
|
+
});
|
|
152638
|
+
const accountSid = process.env.TWILIO_ACCOUNT_SID;
|
|
152639
|
+
const authToken = process.env.TWILIO_AUTH_TOKEN;
|
|
152640
|
+
if (accountSid && authToken) {
|
|
152641
|
+
this.twilioClient = new TwilioClient({ accountSid, authToken });
|
|
152642
|
+
}
|
|
152643
|
+
const elevenLabsApiKey = process.env.ELEVENLABS_API_KEY;
|
|
152644
|
+
const elevenLabsAgentId = this.config.elevenLabsAgentId || process.env.ELEVENLABS_AGENT_ID;
|
|
152645
|
+
if (elevenLabsApiKey && elevenLabsAgentId) {
|
|
152646
|
+
this.voiceBridge = new VoiceBridge({
|
|
152647
|
+
elevenLabsApiKey,
|
|
152648
|
+
elevenLabsAgentId
|
|
152649
|
+
});
|
|
152650
|
+
}
|
|
152651
|
+
}
|
|
152652
|
+
async sendSms(to3, body, from) {
|
|
152653
|
+
if (!this.twilioClient) {
|
|
152654
|
+
return {
|
|
152655
|
+
success: false,
|
|
152656
|
+
message: "Twilio is not configured. Set TWILIO_ACCOUNT_SID and TWILIO_AUTH_TOKEN."
|
|
152657
|
+
};
|
|
152658
|
+
}
|
|
152659
|
+
const fromNumber = from || this.config.defaultPhoneNumber || process.env.TWILIO_PHONE_NUMBER;
|
|
152660
|
+
if (!fromNumber) {
|
|
152661
|
+
return {
|
|
152662
|
+
success: false,
|
|
152663
|
+
message: "No phone number configured. Set telephony.defaultPhoneNumber or TWILIO_PHONE_NUMBER."
|
|
152664
|
+
};
|
|
152665
|
+
}
|
|
152666
|
+
const webhookUrl = this.config.webhookUrl || process.env.TELEPHONY_WEBHOOK_URL;
|
|
152667
|
+
const statusCallback = webhookUrl ? `${webhookUrl}/api/v1/telephony/webhooks/sms-status` : undefined;
|
|
152668
|
+
const result = await this.twilioClient.sendSms({
|
|
152669
|
+
to: to3,
|
|
152670
|
+
from: fromNumber,
|
|
152671
|
+
body,
|
|
152672
|
+
statusCallback
|
|
152673
|
+
});
|
|
152674
|
+
if (!result.success) {
|
|
152675
|
+
return { success: false, message: `Failed to send SMS: ${result.error}` };
|
|
152676
|
+
}
|
|
152677
|
+
const log2 = this.store.createSmsLog({
|
|
152678
|
+
messageSid: result.data?.sid,
|
|
152679
|
+
fromNumber,
|
|
152680
|
+
toNumber: to3,
|
|
152681
|
+
direction: "outbound",
|
|
152682
|
+
messageType: "sms",
|
|
152683
|
+
body,
|
|
152684
|
+
status: "queued",
|
|
152685
|
+
assistantId: this.assistantId
|
|
152686
|
+
});
|
|
152687
|
+
return {
|
|
152688
|
+
success: true,
|
|
152689
|
+
message: `SMS sent to ${to3}.`,
|
|
152690
|
+
messageSid: result.data?.sid,
|
|
152691
|
+
id: log2.id
|
|
152692
|
+
};
|
|
152693
|
+
}
|
|
152694
|
+
async sendWhatsApp(to3, body, from) {
|
|
152695
|
+
if (!this.twilioClient) {
|
|
152696
|
+
return {
|
|
152697
|
+
success: false,
|
|
152698
|
+
message: "Twilio is not configured. Set TWILIO_ACCOUNT_SID and TWILIO_AUTH_TOKEN."
|
|
152699
|
+
};
|
|
152700
|
+
}
|
|
152701
|
+
const fromNumber = from || this.config.defaultPhoneNumber || process.env.TWILIO_PHONE_NUMBER;
|
|
152702
|
+
if (!fromNumber) {
|
|
152703
|
+
return {
|
|
152704
|
+
success: false,
|
|
152705
|
+
message: "No phone number configured."
|
|
152706
|
+
};
|
|
152707
|
+
}
|
|
152708
|
+
const webhookUrl = this.config.webhookUrl || process.env.TELEPHONY_WEBHOOK_URL;
|
|
152709
|
+
const statusCallback = webhookUrl ? `${webhookUrl}/api/v1/telephony/webhooks/sms-status` : undefined;
|
|
152710
|
+
const result = await this.twilioClient.sendWhatsApp({
|
|
152711
|
+
to: to3,
|
|
152712
|
+
from: fromNumber,
|
|
152713
|
+
body,
|
|
152714
|
+
statusCallback
|
|
152715
|
+
});
|
|
152716
|
+
if (!result.success) {
|
|
152717
|
+
return { success: false, message: `Failed to send WhatsApp: ${result.error}` };
|
|
152718
|
+
}
|
|
152719
|
+
const log2 = this.store.createSmsLog({
|
|
152720
|
+
messageSid: result.data?.sid,
|
|
152721
|
+
fromNumber: `whatsapp:${fromNumber}`,
|
|
152722
|
+
toNumber: `whatsapp:${to3}`,
|
|
152723
|
+
direction: "outbound",
|
|
152724
|
+
messageType: "whatsapp",
|
|
152725
|
+
body,
|
|
152726
|
+
status: "queued",
|
|
152727
|
+
assistantId: this.assistantId
|
|
152728
|
+
});
|
|
152729
|
+
return {
|
|
152730
|
+
success: true,
|
|
152731
|
+
message: `WhatsApp message sent to ${to3}.`,
|
|
152732
|
+
messageSid: result.data?.sid,
|
|
152733
|
+
id: log2.id
|
|
152734
|
+
};
|
|
152735
|
+
}
|
|
152736
|
+
async makeCall(to3, from) {
|
|
152737
|
+
if (!this.twilioClient) {
|
|
152738
|
+
return {
|
|
152739
|
+
success: false,
|
|
152740
|
+
message: "Twilio is not configured. Set TWILIO_ACCOUNT_SID and TWILIO_AUTH_TOKEN."
|
|
152741
|
+
};
|
|
152742
|
+
}
|
|
152743
|
+
const fromNumber = from || this.config.defaultPhoneNumber || process.env.TWILIO_PHONE_NUMBER;
|
|
152744
|
+
if (!fromNumber) {
|
|
152745
|
+
return {
|
|
152746
|
+
success: false,
|
|
152747
|
+
message: "No phone number configured."
|
|
152748
|
+
};
|
|
152749
|
+
}
|
|
152750
|
+
const webhookUrl = this.config.webhookUrl || process.env.TELEPHONY_WEBHOOK_URL;
|
|
152751
|
+
if (!webhookUrl) {
|
|
152752
|
+
return {
|
|
152753
|
+
success: false,
|
|
152754
|
+
message: "No webhook URL configured. Set telephony.webhookUrl or TELEPHONY_WEBHOOK_URL."
|
|
152755
|
+
};
|
|
152756
|
+
}
|
|
152757
|
+
const result = await this.twilioClient.makeCall({
|
|
152758
|
+
to: to3,
|
|
152759
|
+
from: fromNumber,
|
|
152760
|
+
url: `${webhookUrl}/api/v1/telephony/webhooks/voice`,
|
|
152761
|
+
statusCallback: `${webhookUrl}/api/v1/telephony/webhooks/voice-status`,
|
|
152762
|
+
record: this.config.voice?.recordCalls
|
|
152763
|
+
});
|
|
152764
|
+
if (!result.success) {
|
|
152765
|
+
return { success: false, message: `Failed to make call: ${result.error}` };
|
|
152766
|
+
}
|
|
152767
|
+
const callSid = result.data?.sid;
|
|
152768
|
+
const log2 = this.store.createCallLog({
|
|
152769
|
+
callSid,
|
|
152770
|
+
fromNumber,
|
|
152771
|
+
toNumber: to3,
|
|
152772
|
+
direction: "outbound",
|
|
152773
|
+
status: "pending",
|
|
152774
|
+
assistantId: this.assistantId
|
|
152775
|
+
});
|
|
152776
|
+
this.callManager.addCall({
|
|
152777
|
+
callSid,
|
|
152778
|
+
fromNumber,
|
|
152779
|
+
toNumber: to3,
|
|
152780
|
+
direction: "outbound",
|
|
152781
|
+
assistantId: this.assistantId
|
|
152782
|
+
});
|
|
152783
|
+
return {
|
|
152784
|
+
success: true,
|
|
152785
|
+
message: `Calling ${to3}...`,
|
|
152786
|
+
callSid,
|
|
152787
|
+
id: log2.id
|
|
152788
|
+
};
|
|
152789
|
+
}
|
|
152790
|
+
getCallHistory(options) {
|
|
152791
|
+
return this.store.listCallLogs({
|
|
152792
|
+
assistantId: this.assistantId,
|
|
152793
|
+
limit: options?.limit || 20
|
|
152794
|
+
});
|
|
152795
|
+
}
|
|
152796
|
+
getSmsHistory(options) {
|
|
152797
|
+
return this.store.listSmsLogs({
|
|
152798
|
+
assistantId: this.assistantId,
|
|
152799
|
+
messageType: options?.messageType,
|
|
152800
|
+
limit: options?.limit || 20
|
|
152801
|
+
});
|
|
152802
|
+
}
|
|
152803
|
+
listPhoneNumbers() {
|
|
152804
|
+
return this.store.listPhoneNumbers("active");
|
|
152805
|
+
}
|
|
152806
|
+
async syncPhoneNumbers() {
|
|
152807
|
+
if (!this.twilioClient) {
|
|
152808
|
+
return { success: false, message: "Twilio is not configured." };
|
|
152809
|
+
}
|
|
152810
|
+
const result = await this.twilioClient.listPhoneNumbers();
|
|
152811
|
+
if (!result.success) {
|
|
152812
|
+
return { success: false, message: `Failed to list numbers: ${result.error}` };
|
|
152813
|
+
}
|
|
152814
|
+
const numbers = result.data?.incoming_phone_numbers || [];
|
|
152815
|
+
let synced = 0;
|
|
152816
|
+
for (const num of numbers) {
|
|
152817
|
+
const phoneNumber = String(num.phone_number || "");
|
|
152818
|
+
const existing = this.store.getPhoneNumberByNumber(phoneNumber);
|
|
152819
|
+
if (!existing && phoneNumber) {
|
|
152820
|
+
this.store.addPhoneNumber(phoneNumber, num.friendly_name ? String(num.friendly_name) : null, num.sid ? String(num.sid) : null, {
|
|
152821
|
+
voice: Boolean(num.capabilities?.voice),
|
|
152822
|
+
sms: Boolean(num.capabilities?.sms)
|
|
152823
|
+
});
|
|
152824
|
+
synced++;
|
|
152825
|
+
}
|
|
152826
|
+
}
|
|
152827
|
+
return {
|
|
152828
|
+
success: true,
|
|
152829
|
+
message: `Synced ${synced} phone number${synced !== 1 ? "s" : ""} from Twilio.`
|
|
152830
|
+
};
|
|
152831
|
+
}
|
|
152832
|
+
listRoutingRules() {
|
|
152833
|
+
return this.store.listRoutingRules();
|
|
152834
|
+
}
|
|
152835
|
+
createRoutingRule(params) {
|
|
152836
|
+
const rule = this.store.createRoutingRule(params);
|
|
152837
|
+
return {
|
|
152838
|
+
success: true,
|
|
152839
|
+
message: `Routing rule "${rule.name}" created (priority ${rule.priority}).`,
|
|
152840
|
+
id: rule.id
|
|
152841
|
+
};
|
|
152842
|
+
}
|
|
152843
|
+
deleteRoutingRule(id) {
|
|
152844
|
+
const success = this.store.deleteRoutingRule(id);
|
|
152845
|
+
return {
|
|
152846
|
+
success,
|
|
152847
|
+
message: success ? "Routing rule deleted." : "Routing rule not found."
|
|
152848
|
+
};
|
|
152849
|
+
}
|
|
152850
|
+
getStatus() {
|
|
152851
|
+
const phoneNumbers = this.store.listPhoneNumbers("active");
|
|
152852
|
+
const recentCalls = this.store.listCallLogs({ limit: 100 });
|
|
152853
|
+
const recentMessages = this.store.listSmsLogs({ limit: 100 });
|
|
152854
|
+
const routingRules = this.store.listRoutingRules();
|
|
152855
|
+
return {
|
|
152856
|
+
enabled: this.config.enabled !== false,
|
|
152857
|
+
twilioConfigured: this.twilioClient?.isConfigured() ?? false,
|
|
152858
|
+
elevenLabsConfigured: this.voiceBridge?.isConfigured() ?? false,
|
|
152859
|
+
phoneNumbers: phoneNumbers.length,
|
|
152860
|
+
activeCalls: this.callManager.getActiveCallCount(),
|
|
152861
|
+
routingRules: routingRules.length,
|
|
152862
|
+
recentCalls: recentCalls.length,
|
|
152863
|
+
recentMessages: recentMessages.length
|
|
152864
|
+
};
|
|
152865
|
+
}
|
|
152866
|
+
getUnreadForInjection() {
|
|
152867
|
+
const injectionConfig = this.config.injection || {};
|
|
152868
|
+
if (injectionConfig.enabled === false) {
|
|
152869
|
+
return [];
|
|
152870
|
+
}
|
|
152871
|
+
const maxPerTurn = injectionConfig.maxPerTurn || 5;
|
|
152872
|
+
return this.store.getUnreadInboundSms(this.assistantId, maxPerTurn);
|
|
152873
|
+
}
|
|
152874
|
+
buildInjectionContext(messages) {
|
|
152875
|
+
if (messages.length === 0)
|
|
152876
|
+
return "";
|
|
152877
|
+
const lines = [];
|
|
152878
|
+
lines.push("## Incoming Telephony Messages");
|
|
152879
|
+
lines.push("");
|
|
152880
|
+
for (const msg of messages) {
|
|
152881
|
+
const type = msg.messageType === "whatsapp" ? "WhatsApp" : "SMS";
|
|
152882
|
+
const ago = formatTimeAgo2(msg.createdAt);
|
|
152883
|
+
lines.push(`**${type} from ${msg.fromNumber}** (${ago}):`);
|
|
152884
|
+
lines.push(msg.body);
|
|
152885
|
+
lines.push("");
|
|
152886
|
+
}
|
|
152887
|
+
lines.push("Use telephony_send_sms or telephony_send_whatsapp to reply.");
|
|
152888
|
+
return lines.join(`
|
|
152889
|
+
`);
|
|
152890
|
+
}
|
|
152891
|
+
markInjected(messages) {
|
|
152892
|
+
for (const msg of messages) {
|
|
152893
|
+
this.store.updateSmsStatus(msg.id, "delivered");
|
|
152894
|
+
}
|
|
152895
|
+
}
|
|
152896
|
+
getStore() {
|
|
152897
|
+
return this.store;
|
|
152898
|
+
}
|
|
152899
|
+
getTwilioClient() {
|
|
152900
|
+
return this.twilioClient;
|
|
152901
|
+
}
|
|
152902
|
+
getCallManager() {
|
|
152903
|
+
return this.callManager;
|
|
152904
|
+
}
|
|
152905
|
+
getVoiceBridge() {
|
|
152906
|
+
return this.voiceBridge;
|
|
152907
|
+
}
|
|
152908
|
+
getAssistantId() {
|
|
152909
|
+
return this.assistantId;
|
|
152910
|
+
}
|
|
152911
|
+
getAssistantName() {
|
|
152912
|
+
return this.assistantName;
|
|
152913
|
+
}
|
|
152914
|
+
getConfig() {
|
|
152915
|
+
return this.config;
|
|
152916
|
+
}
|
|
152917
|
+
cleanup() {
|
|
152918
|
+
const maxAgeDays = this.config.storage?.maxAgeDays || 90;
|
|
152919
|
+
const maxCallLogs = this.config.storage?.maxCallLogs || 1000;
|
|
152920
|
+
const maxSmsLogs = this.config.storage?.maxSmsLogs || 5000;
|
|
152921
|
+
this.callManager.cleanupStaleCalls();
|
|
152922
|
+
return this.store.cleanup(maxAgeDays, maxCallLogs, maxSmsLogs);
|
|
152923
|
+
}
|
|
152924
|
+
close() {
|
|
152925
|
+
this.callManager.endAllCalls();
|
|
152926
|
+
this.voiceBridge?.closeAll();
|
|
152927
|
+
this.store.close();
|
|
152928
|
+
}
|
|
152929
|
+
}
|
|
152930
|
+
function formatTimeAgo2(isoDate) {
|
|
152931
|
+
const now2 = Date.now();
|
|
152932
|
+
const then = new Date(isoDate).getTime();
|
|
152933
|
+
const diffMs = now2 - then;
|
|
152934
|
+
if (diffMs < 60000) {
|
|
152935
|
+
const secs = Math.floor(diffMs / 1000);
|
|
152936
|
+
return `${secs}s ago`;
|
|
152937
|
+
}
|
|
152938
|
+
if (diffMs < 3600000) {
|
|
152939
|
+
const mins = Math.floor(diffMs / 60000);
|
|
152940
|
+
return `${mins}m ago`;
|
|
152941
|
+
}
|
|
152942
|
+
if (diffMs < 86400000) {
|
|
152943
|
+
const hours = Math.floor(diffMs / 3600000);
|
|
152944
|
+
return `${hours}h ago`;
|
|
152945
|
+
}
|
|
152946
|
+
const days = Math.floor(diffMs / 86400000);
|
|
152947
|
+
return `${days}d ago`;
|
|
152948
|
+
}
|
|
152949
|
+
function createTelephonyManager(assistantId, assistantName, config) {
|
|
152950
|
+
return new TelephonyManager({
|
|
152951
|
+
assistantId,
|
|
152952
|
+
assistantName,
|
|
152953
|
+
config
|
|
152954
|
+
});
|
|
152955
|
+
}
|
|
152956
|
+
// ../core/src/telephony/tools.ts
|
|
152957
|
+
var telephonySendSmsTool = {
|
|
152958
|
+
name: "telephony_send_sms",
|
|
152959
|
+
description: "Send an SMS text message to a phone number.",
|
|
152960
|
+
parameters: {
|
|
152961
|
+
type: "object",
|
|
152962
|
+
properties: {
|
|
152963
|
+
to: {
|
|
152964
|
+
type: "string",
|
|
152965
|
+
description: 'Recipient phone number in E.164 format (e.g., "+15551234567")'
|
|
152966
|
+
},
|
|
152967
|
+
body: {
|
|
152968
|
+
type: "string",
|
|
152969
|
+
description: "Message content to send"
|
|
152970
|
+
},
|
|
152971
|
+
from: {
|
|
152972
|
+
type: "string",
|
|
152973
|
+
description: "Sender phone number (optional, uses default if not set)"
|
|
152974
|
+
}
|
|
152975
|
+
},
|
|
152976
|
+
required: ["to", "body"]
|
|
152977
|
+
}
|
|
152978
|
+
};
|
|
152979
|
+
var telephonySendWhatsappTool = {
|
|
152980
|
+
name: "telephony_send_whatsapp",
|
|
152981
|
+
description: "Send a WhatsApp message to a phone number.",
|
|
152982
|
+
parameters: {
|
|
152983
|
+
type: "object",
|
|
152984
|
+
properties: {
|
|
152985
|
+
to: {
|
|
152986
|
+
type: "string",
|
|
152987
|
+
description: 'Recipient phone number in E.164 format (e.g., "+15551234567")'
|
|
152988
|
+
},
|
|
152989
|
+
body: {
|
|
152990
|
+
type: "string",
|
|
152991
|
+
description: "Message content to send"
|
|
152992
|
+
},
|
|
152993
|
+
from: {
|
|
152994
|
+
type: "string",
|
|
152995
|
+
description: "Sender phone number (optional, uses default if not set)"
|
|
152996
|
+
}
|
|
152997
|
+
},
|
|
152998
|
+
required: ["to", "body"]
|
|
152999
|
+
}
|
|
153000
|
+
};
|
|
153001
|
+
var telephonyCallTool = {
|
|
153002
|
+
name: "telephony_call",
|
|
153003
|
+
description: "Initiate an outbound voice call. The call will be connected to the AI voice agent.",
|
|
153004
|
+
parameters: {
|
|
153005
|
+
type: "object",
|
|
153006
|
+
properties: {
|
|
153007
|
+
to: {
|
|
153008
|
+
type: "string",
|
|
153009
|
+
description: 'Phone number to call in E.164 format (e.g., "+15551234567")'
|
|
153010
|
+
},
|
|
153011
|
+
from: {
|
|
153012
|
+
type: "string",
|
|
153013
|
+
description: "Caller phone number (optional, uses default if not set)"
|
|
153014
|
+
}
|
|
153015
|
+
},
|
|
153016
|
+
required: ["to"]
|
|
153017
|
+
}
|
|
153018
|
+
};
|
|
153019
|
+
var telephonyCallHistoryTool = {
|
|
153020
|
+
name: "telephony_call_history",
|
|
153021
|
+
description: "Get recent call history. Returns call logs with status, duration, and timestamps.",
|
|
153022
|
+
parameters: {
|
|
153023
|
+
type: "object",
|
|
153024
|
+
properties: {
|
|
153025
|
+
limit: {
|
|
153026
|
+
type: "number",
|
|
153027
|
+
description: "Maximum number of calls to return (default: 20)"
|
|
153028
|
+
}
|
|
153029
|
+
},
|
|
153030
|
+
required: []
|
|
153031
|
+
}
|
|
153032
|
+
};
|
|
153033
|
+
var telephonySmsHistoryTool = {
|
|
153034
|
+
name: "telephony_sms_history",
|
|
153035
|
+
description: "Get recent SMS and WhatsApp message history.",
|
|
153036
|
+
parameters: {
|
|
153037
|
+
type: "object",
|
|
153038
|
+
properties: {
|
|
153039
|
+
limit: {
|
|
153040
|
+
type: "number",
|
|
153041
|
+
description: "Maximum messages to return (default: 20)"
|
|
153042
|
+
},
|
|
153043
|
+
type: {
|
|
153044
|
+
type: "string",
|
|
153045
|
+
enum: ["sms", "whatsapp"],
|
|
153046
|
+
description: "Filter by message type (default: all)"
|
|
153047
|
+
}
|
|
153048
|
+
},
|
|
153049
|
+
required: []
|
|
153050
|
+
}
|
|
153051
|
+
};
|
|
153052
|
+
var telephonyPhoneNumbersTool = {
|
|
153053
|
+
name: "telephony_phone_numbers",
|
|
153054
|
+
description: "List available phone numbers with their capabilities (voice, SMS, WhatsApp).",
|
|
153055
|
+
parameters: {
|
|
153056
|
+
type: "object",
|
|
153057
|
+
properties: {},
|
|
153058
|
+
required: []
|
|
153059
|
+
}
|
|
153060
|
+
};
|
|
153061
|
+
var telephonyRoutingRulesTool = {
|
|
153062
|
+
name: "telephony_routing_rules",
|
|
153063
|
+
description: "View and manage routing rules that direct incoming calls/messages to specific assistants.",
|
|
153064
|
+
parameters: {
|
|
153065
|
+
type: "object",
|
|
153066
|
+
properties: {
|
|
153067
|
+
action: {
|
|
153068
|
+
type: "string",
|
|
153069
|
+
enum: ["list", "create", "delete"],
|
|
153070
|
+
description: "Action to perform (default: list)"
|
|
153071
|
+
},
|
|
153072
|
+
name: {
|
|
153073
|
+
type: "string",
|
|
153074
|
+
description: "Rule name (for create)"
|
|
153075
|
+
},
|
|
153076
|
+
priority: {
|
|
153077
|
+
type: "number",
|
|
153078
|
+
description: "Priority (lower = higher priority, for create)"
|
|
153079
|
+
},
|
|
153080
|
+
from_pattern: {
|
|
153081
|
+
type: "string",
|
|
153082
|
+
description: 'From number pattern (e.g., "+1555*", for create)'
|
|
153083
|
+
},
|
|
153084
|
+
message_type: {
|
|
153085
|
+
type: "string",
|
|
153086
|
+
enum: ["sms", "whatsapp", "voice", "all"],
|
|
153087
|
+
description: "Message type filter (for create)"
|
|
153088
|
+
},
|
|
153089
|
+
rule_id: {
|
|
153090
|
+
type: "string",
|
|
153091
|
+
description: "Rule ID (for delete)"
|
|
153092
|
+
}
|
|
153093
|
+
},
|
|
153094
|
+
required: []
|
|
153095
|
+
}
|
|
153096
|
+
};
|
|
153097
|
+
var telephonyStatusTool = {
|
|
153098
|
+
name: "telephony_status",
|
|
153099
|
+
description: "Get telephony system status including configured numbers, active calls, and connection health.",
|
|
153100
|
+
parameters: {
|
|
153101
|
+
type: "object",
|
|
153102
|
+
properties: {},
|
|
153103
|
+
required: []
|
|
153104
|
+
}
|
|
153105
|
+
};
|
|
153106
|
+
function createTelephonyToolExecutors(getTelephonyManager) {
|
|
153107
|
+
return {
|
|
153108
|
+
telephony_send_sms: 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
|
+
const body = String(input.body || "").trim();
|
|
153115
|
+
if (!to3)
|
|
153116
|
+
return "Error: Recipient phone number (to) is required.";
|
|
153117
|
+
if (!body)
|
|
153118
|
+
return "Error: Message body is required.";
|
|
153119
|
+
const from = input.from ? String(input.from).trim() : undefined;
|
|
153120
|
+
const result = await manager.sendSms(to3, body, from);
|
|
153121
|
+
return result.success ? result.message : `Error: ${result.message}`;
|
|
153122
|
+
},
|
|
153123
|
+
telephony_send_whatsapp: async (input) => {
|
|
153124
|
+
const manager = getTelephonyManager();
|
|
153125
|
+
if (!manager) {
|
|
153126
|
+
return "Error: Telephony is not enabled. Set telephony.enabled: true in config.";
|
|
153127
|
+
}
|
|
153128
|
+
const to3 = String(input.to || "").trim();
|
|
153129
|
+
const body = String(input.body || "").trim();
|
|
153130
|
+
if (!to3)
|
|
153131
|
+
return "Error: Recipient phone number (to) is required.";
|
|
153132
|
+
if (!body)
|
|
153133
|
+
return "Error: Message body is required.";
|
|
153134
|
+
const from = input.from ? String(input.from).trim() : undefined;
|
|
153135
|
+
const result = await manager.sendWhatsApp(to3, body, from);
|
|
153136
|
+
return result.success ? result.message : `Error: ${result.message}`;
|
|
153137
|
+
},
|
|
153138
|
+
telephony_call: async (input) => {
|
|
153139
|
+
const manager = getTelephonyManager();
|
|
153140
|
+
if (!manager) {
|
|
153141
|
+
return "Error: Telephony is not enabled. Set telephony.enabled: true in config.";
|
|
153142
|
+
}
|
|
153143
|
+
const to3 = String(input.to || "").trim();
|
|
153144
|
+
if (!to3)
|
|
153145
|
+
return "Error: Phone number (to) is required.";
|
|
153146
|
+
const from = input.from ? String(input.from).trim() : undefined;
|
|
153147
|
+
const result = await manager.makeCall(to3, from);
|
|
153148
|
+
return result.success ? result.message : `Error: ${result.message}`;
|
|
153149
|
+
},
|
|
153150
|
+
telephony_call_history: async (input) => {
|
|
153151
|
+
const manager = getTelephonyManager();
|
|
153152
|
+
if (!manager) {
|
|
153153
|
+
return "Error: Telephony is not enabled.";
|
|
153154
|
+
}
|
|
153155
|
+
const limit2 = typeof input.limit === "number" ? input.limit : 20;
|
|
153156
|
+
const calls = manager.getCallHistory({ limit: limit2 });
|
|
153157
|
+
if (calls.length === 0) {
|
|
153158
|
+
return "No call history found.";
|
|
153159
|
+
}
|
|
153160
|
+
const lines = [];
|
|
153161
|
+
lines.push(`## Call History (${calls.length})`);
|
|
153162
|
+
lines.push("");
|
|
153163
|
+
for (const call of calls) {
|
|
153164
|
+
const dir = call.direction === "inbound" ? "IN" : "OUT";
|
|
153165
|
+
const duration = call.duration != null ? `${call.duration}s` : "-";
|
|
153166
|
+
const date = new Date(call.createdAt).toLocaleString();
|
|
153167
|
+
lines.push(`[${dir}] ${call.fromNumber} \u2192 ${call.toNumber} | ${call.status} | ${duration} | ${date}`);
|
|
153168
|
+
}
|
|
153169
|
+
return lines.join(`
|
|
153170
|
+
`);
|
|
153171
|
+
},
|
|
153172
|
+
telephony_sms_history: async (input) => {
|
|
153173
|
+
const manager = getTelephonyManager();
|
|
153174
|
+
if (!manager) {
|
|
153175
|
+
return "Error: Telephony is not enabled.";
|
|
153176
|
+
}
|
|
153177
|
+
const limit2 = typeof input.limit === "number" ? input.limit : 20;
|
|
153178
|
+
const messageType = input.type;
|
|
153179
|
+
const messages = manager.getSmsHistory({ limit: limit2, messageType });
|
|
153180
|
+
if (messages.length === 0) {
|
|
153181
|
+
return "No message history found.";
|
|
153182
|
+
}
|
|
153183
|
+
const lines = [];
|
|
153184
|
+
lines.push(`## Message History (${messages.length})`);
|
|
153185
|
+
lines.push("");
|
|
153186
|
+
for (const msg of messages) {
|
|
153187
|
+
const dir = msg.direction === "inbound" ? "IN" : "OUT";
|
|
153188
|
+
const type = msg.messageType === "whatsapp" ? "WA" : "SMS";
|
|
153189
|
+
const date = new Date(msg.createdAt).toLocaleString();
|
|
153190
|
+
lines.push(`[${dir}/${type}] ${msg.fromNumber} \u2192 ${msg.toNumber} | ${msg.status}`);
|
|
153191
|
+
lines.push(` ${msg.bodyPreview}`);
|
|
153192
|
+
lines.push(` ${date}`);
|
|
153193
|
+
lines.push("");
|
|
153194
|
+
}
|
|
153195
|
+
return lines.join(`
|
|
153196
|
+
`);
|
|
153197
|
+
},
|
|
153198
|
+
telephony_phone_numbers: async () => {
|
|
153199
|
+
const manager = getTelephonyManager();
|
|
153200
|
+
if (!manager) {
|
|
153201
|
+
return "Error: Telephony is not enabled.";
|
|
153202
|
+
}
|
|
153203
|
+
const numbers = manager.listPhoneNumbers();
|
|
153204
|
+
if (numbers.length === 0) {
|
|
153205
|
+
return "No phone numbers configured. Use /phone sync to import from Twilio.";
|
|
153206
|
+
}
|
|
153207
|
+
const lines = [];
|
|
153208
|
+
lines.push(`## Phone Numbers (${numbers.length})`);
|
|
153209
|
+
lines.push("");
|
|
153210
|
+
for (const num of numbers) {
|
|
153211
|
+
const caps = [];
|
|
153212
|
+
if (num.capabilities.voice)
|
|
153213
|
+
caps.push("voice");
|
|
153214
|
+
if (num.capabilities.sms)
|
|
153215
|
+
caps.push("sms");
|
|
153216
|
+
if (num.capabilities.whatsapp)
|
|
153217
|
+
caps.push("whatsapp");
|
|
153218
|
+
const name2 = num.friendlyName ? ` (${num.friendlyName})` : "";
|
|
153219
|
+
lines.push(` ${num.number}${name2} [${caps.join(", ")}]`);
|
|
153220
|
+
}
|
|
153221
|
+
return lines.join(`
|
|
153222
|
+
`);
|
|
153223
|
+
},
|
|
153224
|
+
telephony_routing_rules: async (input) => {
|
|
153225
|
+
const manager = getTelephonyManager();
|
|
153226
|
+
if (!manager) {
|
|
153227
|
+
return "Error: Telephony is not enabled.";
|
|
153228
|
+
}
|
|
153229
|
+
const action = String(input.action || "list");
|
|
153230
|
+
if (action === "list") {
|
|
153231
|
+
const rules = manager.listRoutingRules();
|
|
153232
|
+
if (rules.length === 0) {
|
|
153233
|
+
return "No routing rules configured.";
|
|
153234
|
+
}
|
|
153235
|
+
const lines = [];
|
|
153236
|
+
lines.push(`## Routing Rules (${rules.length})`);
|
|
153237
|
+
lines.push("");
|
|
153238
|
+
for (const rule of rules) {
|
|
153239
|
+
const enabled = rule.enabled ? "" : " [DISABLED]";
|
|
153240
|
+
lines.push(`**${rule.name}** (priority: ${rule.priority})${enabled}`);
|
|
153241
|
+
lines.push(` ID: ${rule.id}`);
|
|
153242
|
+
lines.push(` Type: ${rule.messageType} | Target: ${rule.targetAssistantName}`);
|
|
153243
|
+
if (rule.fromPattern)
|
|
153244
|
+
lines.push(` From: ${rule.fromPattern}`);
|
|
153245
|
+
if (rule.toPattern)
|
|
153246
|
+
lines.push(` To: ${rule.toPattern}`);
|
|
153247
|
+
if (rule.keyword)
|
|
153248
|
+
lines.push(` Keyword: ${rule.keyword}`);
|
|
153249
|
+
lines.push("");
|
|
153250
|
+
}
|
|
153251
|
+
return lines.join(`
|
|
153252
|
+
`);
|
|
153253
|
+
}
|
|
153254
|
+
if (action === "create") {
|
|
153255
|
+
const name2 = String(input.name || "").trim();
|
|
153256
|
+
if (!name2)
|
|
153257
|
+
return "Error: Rule name is required.";
|
|
153258
|
+
const result = manager.createRoutingRule({
|
|
153259
|
+
name: name2,
|
|
153260
|
+
priority: typeof input.priority === "number" ? input.priority : undefined,
|
|
153261
|
+
fromPattern: input.from_pattern ? String(input.from_pattern) : undefined,
|
|
153262
|
+
messageType: input.message_type,
|
|
153263
|
+
targetAssistantId: manager.getAssistantId(),
|
|
153264
|
+
targetAssistantName: manager.getAssistantName()
|
|
153265
|
+
});
|
|
153266
|
+
return result.success ? result.message : `Error: ${result.message}`;
|
|
153267
|
+
}
|
|
153268
|
+
if (action === "delete") {
|
|
153269
|
+
const ruleId = String(input.rule_id || "").trim();
|
|
153270
|
+
if (!ruleId)
|
|
153271
|
+
return "Error: Rule ID is required.";
|
|
153272
|
+
const result = manager.deleteRoutingRule(ruleId);
|
|
153273
|
+
return result.success ? result.message : `Error: ${result.message}`;
|
|
153274
|
+
}
|
|
153275
|
+
return `Unknown action: ${action}. Use 'list', 'create', or 'delete'.`;
|
|
153276
|
+
},
|
|
153277
|
+
telephony_status: async () => {
|
|
153278
|
+
const manager = getTelephonyManager();
|
|
153279
|
+
if (!manager) {
|
|
153280
|
+
return "Telephony is not enabled. Set telephony.enabled: true in config.";
|
|
153281
|
+
}
|
|
153282
|
+
const status = manager.getStatus();
|
|
153283
|
+
const lines = [];
|
|
153284
|
+
lines.push("## Telephony Status");
|
|
153285
|
+
lines.push("");
|
|
153286
|
+
lines.push(`Enabled: ${status.enabled ? "Yes" : "No"}`);
|
|
153287
|
+
lines.push(`Twilio configured: ${status.twilioConfigured ? "Yes" : "No (set TWILIO_ACCOUNT_SID + TWILIO_AUTH_TOKEN)"}`);
|
|
153288
|
+
lines.push(`ElevenLabs AI: ${status.elevenLabsConfigured ? "Yes" : "No (set ELEVENLABS_API_KEY + ELEVENLABS_AGENT_ID)"}`);
|
|
153289
|
+
lines.push(`Phone numbers: ${status.phoneNumbers}`);
|
|
153290
|
+
lines.push(`Active calls: ${status.activeCalls}`);
|
|
153291
|
+
lines.push(`Routing rules: ${status.routingRules}`);
|
|
153292
|
+
lines.push(`Recent calls: ${status.recentCalls}`);
|
|
153293
|
+
lines.push(`Recent messages: ${status.recentMessages}`);
|
|
153294
|
+
return lines.join(`
|
|
153295
|
+
`);
|
|
153296
|
+
}
|
|
153297
|
+
};
|
|
153298
|
+
}
|
|
153299
|
+
var telephonyTools = [
|
|
153300
|
+
telephonySendSmsTool,
|
|
153301
|
+
telephonySendWhatsappTool,
|
|
153302
|
+
telephonyCallTool,
|
|
153303
|
+
telephonyCallHistoryTool,
|
|
153304
|
+
telephonySmsHistoryTool,
|
|
153305
|
+
telephonyPhoneNumbersTool,
|
|
153306
|
+
telephonyRoutingRulesTool,
|
|
153307
|
+
telephonyStatusTool
|
|
153308
|
+
];
|
|
153309
|
+
function registerTelephonyTools(registry2, getTelephonyManager) {
|
|
153310
|
+
const executors = createTelephonyToolExecutors(getTelephonyManager);
|
|
153311
|
+
for (const tool of telephonyTools) {
|
|
153312
|
+
registry2.register(tool, executors[tool.name]);
|
|
153313
|
+
}
|
|
153314
|
+
}
|
|
151364
153315
|
// ../core/src/sessions/store.ts
|
|
151365
|
-
import { join as
|
|
153316
|
+
import { join as join45 } from "path";
|
|
151366
153317
|
import { homedir as homedir21 } from "os";
|
|
151367
|
-
import { existsSync as
|
|
153318
|
+
import { existsSync as existsSync28, mkdirSync as mkdirSync18, writeFileSync as writeFileSync14, readFileSync as readFileSync17, readdirSync as readdirSync9, unlinkSync as unlinkSync6 } from "fs";
|
|
151368
153319
|
|
|
151369
153320
|
class SessionStore {
|
|
151370
153321
|
basePath;
|
|
151371
153322
|
constructor(basePath) {
|
|
151372
153323
|
const envHome = process.env.HOME || process.env.USERPROFILE || homedir21();
|
|
151373
|
-
this.basePath = basePath ||
|
|
153324
|
+
this.basePath = basePath || join45(envHome, ".assistants", "sessions");
|
|
151374
153325
|
this.ensureDir();
|
|
151375
153326
|
}
|
|
151376
153327
|
ensureDir() {
|
|
151377
|
-
if (!
|
|
151378
|
-
|
|
153328
|
+
if (!existsSync28(this.basePath)) {
|
|
153329
|
+
mkdirSync18(this.basePath, { recursive: true });
|
|
151379
153330
|
}
|
|
151380
153331
|
}
|
|
151381
153332
|
getSessionPath(id) {
|
|
151382
|
-
return
|
|
153333
|
+
return join45(this.basePath, `${id}.json`);
|
|
151383
153334
|
}
|
|
151384
153335
|
save(data) {
|
|
151385
153336
|
try {
|
|
@@ -151390,7 +153341,7 @@ class SessionStore {
|
|
|
151390
153341
|
load(id) {
|
|
151391
153342
|
try {
|
|
151392
153343
|
const filePath = this.getSessionPath(id);
|
|
151393
|
-
if (!
|
|
153344
|
+
if (!existsSync28(filePath))
|
|
151394
153345
|
return null;
|
|
151395
153346
|
return JSON.parse(readFileSync17(filePath, "utf-8"));
|
|
151396
153347
|
} catch {
|
|
@@ -151404,7 +153355,7 @@ class SessionStore {
|
|
|
151404
153355
|
const sessions = [];
|
|
151405
153356
|
for (const file of files) {
|
|
151406
153357
|
try {
|
|
151407
|
-
const data = JSON.parse(readFileSync17(
|
|
153358
|
+
const data = JSON.parse(readFileSync17(join45(this.basePath, file), "utf-8"));
|
|
151408
153359
|
sessions.push(data);
|
|
151409
153360
|
} catch {}
|
|
151410
153361
|
}
|
|
@@ -151416,7 +153367,7 @@ class SessionStore {
|
|
|
151416
153367
|
delete(id) {
|
|
151417
153368
|
try {
|
|
151418
153369
|
const filePath = this.getSessionPath(id);
|
|
151419
|
-
if (
|
|
153370
|
+
if (existsSync28(filePath)) {
|
|
151420
153371
|
unlinkSync6(filePath);
|
|
151421
153372
|
}
|
|
151422
153373
|
} catch {}
|
|
@@ -151733,6 +153684,12 @@ class EmbeddedClient {
|
|
|
151733
153684
|
}
|
|
151734
153685
|
return null;
|
|
151735
153686
|
}
|
|
153687
|
+
getTelephonyManager() {
|
|
153688
|
+
if (typeof this.assistantLoop.getTelephonyManager === "function") {
|
|
153689
|
+
return this.assistantLoop.getTelephonyManager();
|
|
153690
|
+
}
|
|
153691
|
+
return null;
|
|
153692
|
+
}
|
|
151736
153693
|
getWalletManager() {
|
|
151737
153694
|
if (typeof this.assistantLoop.getWalletManager === "function") {
|
|
151738
153695
|
return this.assistantLoop.getWalletManager();
|
|
@@ -153603,7 +155560,7 @@ function createSelfAwarenessToolExecutors(context) {
|
|
|
153603
155560
|
workspace_map: async (input) => {
|
|
153604
155561
|
const { readdir: readdir7, stat: stat5, access } = await import("fs/promises");
|
|
153605
155562
|
const { execSync } = await import("child_process");
|
|
153606
|
-
const { join:
|
|
155563
|
+
const { join: join46, basename: basename6 } = await import("path");
|
|
153607
155564
|
const depth = input.depth ?? 3;
|
|
153608
155565
|
const includeGitStatus = input.include_git_status !== false;
|
|
153609
155566
|
const includeRecentFiles = input.include_recent_files !== false;
|
|
@@ -153649,7 +155606,7 @@ function createSelfAwarenessToolExecutors(context) {
|
|
|
153649
155606
|
break;
|
|
153650
155607
|
}
|
|
153651
155608
|
if (entry.isDirectory()) {
|
|
153652
|
-
const children = await buildTree(
|
|
155609
|
+
const children = await buildTree(join46(dir, entry.name), currentDepth + 1);
|
|
153653
155610
|
nodes.push({
|
|
153654
155611
|
name: entry.name,
|
|
153655
155612
|
type: "directory",
|
|
@@ -153675,7 +155632,7 @@ function createSelfAwarenessToolExecutors(context) {
|
|
|
153675
155632
|
};
|
|
153676
155633
|
if (includeGitStatus) {
|
|
153677
155634
|
try {
|
|
153678
|
-
await access(
|
|
155635
|
+
await access(join46(cwd, ".git"));
|
|
153679
155636
|
gitStatus.isRepo = true;
|
|
153680
155637
|
try {
|
|
153681
155638
|
const branch = execSync("git branch --show-current", { cwd, encoding: "utf-8" }).trim();
|
|
@@ -153706,7 +155663,7 @@ function createSelfAwarenessToolExecutors(context) {
|
|
|
153706
155663
|
for (const entry of entries) {
|
|
153707
155664
|
if (shouldIgnore(entry.name))
|
|
153708
155665
|
continue;
|
|
153709
|
-
const fullPath =
|
|
155666
|
+
const fullPath = join46(dir, entry.name);
|
|
153710
155667
|
const relPath = relativePath ? `${relativePath}/${entry.name}` : entry.name;
|
|
153711
155668
|
if (entry.isFile()) {
|
|
153712
155669
|
try {
|
|
@@ -153740,12 +155697,12 @@ function createSelfAwarenessToolExecutors(context) {
|
|
|
153740
155697
|
let projectName = basename6(cwd);
|
|
153741
155698
|
for (const indicator of projectIndicators) {
|
|
153742
155699
|
try {
|
|
153743
|
-
await access(
|
|
155700
|
+
await access(join46(cwd, indicator.file));
|
|
153744
155701
|
projectType = indicator.type;
|
|
153745
155702
|
if (indicator.file === "package.json") {
|
|
153746
155703
|
try {
|
|
153747
155704
|
const { readFile: readFile16 } = await import("fs/promises");
|
|
153748
|
-
const pkg = JSON.parse(await readFile16(
|
|
155705
|
+
const pkg = JSON.parse(await readFile16(join46(cwd, indicator.file), "utf-8"));
|
|
153749
155706
|
projectName = pkg.name || projectName;
|
|
153750
155707
|
} catch {}
|
|
153751
155708
|
}
|
|
@@ -156334,8 +158291,8 @@ await __promiseAll([
|
|
|
156334
158291
|
init_config(),
|
|
156335
158292
|
init_runtime()
|
|
156336
158293
|
]);
|
|
156337
|
-
import { join as
|
|
156338
|
-
import { existsSync as
|
|
158294
|
+
import { join as join46, dirname as dirname20 } from "path";
|
|
158295
|
+
import { existsSync as existsSync29, mkdirSync as mkdirSync19 } from "fs";
|
|
156339
158296
|
var MAX_KEY_LENGTH2 = 256;
|
|
156340
158297
|
var MAX_VALUE_SIZE = 65536;
|
|
156341
158298
|
var MAX_SUMMARY_LENGTH2 = 500;
|
|
@@ -156348,10 +158305,10 @@ class GlobalMemoryManager {
|
|
|
156348
158305
|
config;
|
|
156349
158306
|
constructor(options = {}) {
|
|
156350
158307
|
const baseDir = getConfigDir();
|
|
156351
|
-
const path2 = options.dbPath ||
|
|
156352
|
-
const dir =
|
|
156353
|
-
if (!
|
|
156354
|
-
|
|
158308
|
+
const path2 = options.dbPath || join46(baseDir, "memory.db");
|
|
158309
|
+
const dir = dirname20(path2);
|
|
158310
|
+
if (!existsSync29(dir)) {
|
|
158311
|
+
mkdirSync19(dir, { recursive: true });
|
|
156355
158312
|
}
|
|
156356
158313
|
const runtime = getRuntime();
|
|
156357
158314
|
this.db = runtime.openDatabase(path2);
|
|
@@ -157755,8 +159712,8 @@ function createCapabilityChain(scope, capabilities) {
|
|
|
157755
159712
|
};
|
|
157756
159713
|
}
|
|
157757
159714
|
// ../core/src/capabilities/storage.ts
|
|
157758
|
-
import { existsSync as
|
|
157759
|
-
import { join as
|
|
159715
|
+
import { existsSync as existsSync30, mkdirSync as mkdirSync20, readFileSync as readFileSync18, writeFileSync as writeFileSync15 } from "fs";
|
|
159716
|
+
import { join as join47, dirname as dirname21 } from "path";
|
|
157760
159717
|
import { homedir as homedir22 } from "os";
|
|
157761
159718
|
var DEFAULT_STORAGE_CONFIG = {
|
|
157762
159719
|
enabled: true,
|
|
@@ -157777,14 +159734,14 @@ class CapabilityStorage {
|
|
|
157777
159734
|
return this.config.storagePath;
|
|
157778
159735
|
}
|
|
157779
159736
|
const home = process.env.HOME || process.env.USERPROFILE || homedir22();
|
|
157780
|
-
return
|
|
159737
|
+
return join47(home, ".assistants", "capabilities", "store.json");
|
|
157781
159738
|
}
|
|
157782
159739
|
load() {
|
|
157783
159740
|
if (!this.config.enabled)
|
|
157784
159741
|
return;
|
|
157785
159742
|
try {
|
|
157786
159743
|
const path2 = this.getStoragePath();
|
|
157787
|
-
if (!
|
|
159744
|
+
if (!existsSync30(path2))
|
|
157788
159745
|
return;
|
|
157789
159746
|
const data = JSON.parse(readFileSync18(path2, "utf-8"));
|
|
157790
159747
|
if (data.chains) {
|
|
@@ -157804,9 +159761,9 @@ class CapabilityStorage {
|
|
|
157804
159761
|
return;
|
|
157805
159762
|
try {
|
|
157806
159763
|
const path2 = this.getStoragePath();
|
|
157807
|
-
const dir =
|
|
157808
|
-
if (!
|
|
157809
|
-
|
|
159764
|
+
const dir = dirname21(path2);
|
|
159765
|
+
if (!existsSync30(dir)) {
|
|
159766
|
+
mkdirSync20(dir, { recursive: true });
|
|
157810
159767
|
}
|
|
157811
159768
|
const data = {
|
|
157812
159769
|
version: 1,
|
|
@@ -158192,6 +160149,7 @@ class AssistantLoop {
|
|
|
158192
160149
|
messagesManager = null;
|
|
158193
160150
|
webhooksManager = null;
|
|
158194
160151
|
channelsManager = null;
|
|
160152
|
+
telephonyManager = null;
|
|
158195
160153
|
memoryManager = null;
|
|
158196
160154
|
memoryInjector = null;
|
|
158197
160155
|
contextInjector = null;
|
|
@@ -158201,6 +160159,7 @@ class AssistantLoop {
|
|
|
158201
160159
|
pendingMessagesContext = null;
|
|
158202
160160
|
pendingWebhooksContext = null;
|
|
158203
160161
|
pendingChannelsContext = null;
|
|
160162
|
+
pendingTelephonyContext = null;
|
|
158204
160163
|
pendingMemoryContext = null;
|
|
158205
160164
|
identityContext = null;
|
|
158206
160165
|
projectContext = null;
|
|
@@ -158430,6 +160389,13 @@ class AssistantLoop {
|
|
|
158430
160389
|
this.channelsManager = createChannelsManager(assistantId, assistantName, this.config.channels);
|
|
158431
160390
|
registerChannelTools(this.toolRegistry, () => this.channelsManager);
|
|
158432
160391
|
}
|
|
160392
|
+
if (this.config?.telephony?.enabled) {
|
|
160393
|
+
const assistant = this.assistantManager?.getActive();
|
|
160394
|
+
const assistantId = assistant?.id || this.sessionId;
|
|
160395
|
+
const assistantName = assistant?.name || "assistant";
|
|
160396
|
+
this.telephonyManager = createTelephonyManager(assistantId, assistantName, this.config.telephony);
|
|
160397
|
+
registerTelephonyTools(this.toolRegistry, () => this.telephonyManager);
|
|
160398
|
+
}
|
|
158433
160399
|
const memoryConfig = this.config?.memory;
|
|
158434
160400
|
if (memoryConfig?.enabled !== false) {
|
|
158435
160401
|
const assistant = this.assistantManager?.getActive();
|
|
@@ -158761,6 +160727,7 @@ You are running in **autonomous mode**. You manage your own wakeup schedule.
|
|
|
158761
160727
|
await this.injectPendingMessages();
|
|
158762
160728
|
await this.injectPendingWebhookEvents();
|
|
158763
160729
|
await this.injectPendingChannelMessages();
|
|
160730
|
+
await this.injectPendingTelephonyMessages();
|
|
158764
160731
|
await this.injectMemoryContext(userMessage);
|
|
158765
160732
|
await this.injectContextInfo();
|
|
158766
160733
|
} catch (error2) {
|
|
@@ -159479,6 +161446,7 @@ You are running in **autonomous mode**. You manage your own wakeup schedule.
|
|
|
159479
161446
|
getMessagesManager: () => this.messagesManager,
|
|
159480
161447
|
getWebhooksManager: () => this.webhooksManager,
|
|
159481
161448
|
getChannelsManager: () => this.channelsManager,
|
|
161449
|
+
getTelephonyManager: () => this.telephonyManager,
|
|
159482
161450
|
getMemoryManager: () => this.memoryManager,
|
|
159483
161451
|
refreshIdentityContext: async () => {
|
|
159484
161452
|
if (this.identityManager) {
|
|
@@ -159720,6 +161688,8 @@ You are running in **autonomous mode**. You manage your own wakeup schedule.
|
|
|
159720
161688
|
this.webhooksManager?.stopWatching();
|
|
159721
161689
|
this.channelsManager?.close();
|
|
159722
161690
|
this.channelsManager = null;
|
|
161691
|
+
this.telephonyManager?.close();
|
|
161692
|
+
this.telephonyManager = null;
|
|
159723
161693
|
this.memoryManager?.close();
|
|
159724
161694
|
this.memoryManager = null;
|
|
159725
161695
|
this.memoryInjector = null;
|
|
@@ -159797,6 +161767,9 @@ You are running in **autonomous mode**. You manage your own wakeup schedule.
|
|
|
159797
161767
|
getChannelsManager() {
|
|
159798
161768
|
return this.channelsManager;
|
|
159799
161769
|
}
|
|
161770
|
+
getTelephonyManager() {
|
|
161771
|
+
return this.telephonyManager;
|
|
161772
|
+
}
|
|
159800
161773
|
getWalletManager() {
|
|
159801
161774
|
return this.walletManager;
|
|
159802
161775
|
}
|
|
@@ -160029,7 +162002,7 @@ ${content.trim()}`);
|
|
|
160029
162002
|
if (!heartbeatConfig)
|
|
160030
162003
|
return;
|
|
160031
162004
|
this.heartbeatRuntimeConfig = heartbeatConfig;
|
|
160032
|
-
const statePath =
|
|
162005
|
+
const statePath = join48(getConfigDir(), "state", `${this.sessionId}.json`);
|
|
160033
162006
|
this.heartbeatManager = new HeartbeatManager(heartbeatConfig);
|
|
160034
162007
|
this.heartbeatPersistence = new StatePersistence(statePath);
|
|
160035
162008
|
this.heartbeatRecovery = new RecoveryManager(this.heartbeatPersistence, heartbeatConfig.persistPath, heartbeatConfig.staleThresholdMs, {
|
|
@@ -160150,7 +162123,7 @@ ${content.trim()}`);
|
|
|
160150
162123
|
async startEnergySystem() {
|
|
160151
162124
|
if (!this.config || this.config.energy?.enabled === false)
|
|
160152
162125
|
return;
|
|
160153
|
-
const statePath =
|
|
162126
|
+
const statePath = join48(getConfigDir(), "energy", "state.json");
|
|
160154
162127
|
this.energyManager = new EnergyManager(this.config.energy, new EnergyStorage(statePath));
|
|
160155
162128
|
await this.energyManager.initialize();
|
|
160156
162129
|
this.refreshEnergyEffects();
|
|
@@ -160257,6 +162230,29 @@ ${effects.message}
|
|
|
160257
162230
|
this.pendingChannelsContext = null;
|
|
160258
162231
|
}
|
|
160259
162232
|
}
|
|
162233
|
+
async injectPendingTelephonyMessages() {
|
|
162234
|
+
if (!this.telephonyManager)
|
|
162235
|
+
return;
|
|
162236
|
+
try {
|
|
162237
|
+
if (this.pendingTelephonyContext) {
|
|
162238
|
+
const previous = this.pendingTelephonyContext.trim();
|
|
162239
|
+
this.context.removeSystemMessages((content) => content.trim() === previous);
|
|
162240
|
+
this.pendingTelephonyContext = null;
|
|
162241
|
+
}
|
|
162242
|
+
const pending = this.telephonyManager.getUnreadForInjection();
|
|
162243
|
+
if (pending.length === 0) {
|
|
162244
|
+
return;
|
|
162245
|
+
}
|
|
162246
|
+
this.pendingTelephonyContext = this.telephonyManager.buildInjectionContext(pending);
|
|
162247
|
+
if (this.pendingTelephonyContext) {
|
|
162248
|
+
this.context.addSystemMessage(this.pendingTelephonyContext);
|
|
162249
|
+
}
|
|
162250
|
+
this.telephonyManager.markInjected(pending);
|
|
162251
|
+
} catch (error2) {
|
|
162252
|
+
console.error("Failed to inject pending telephony messages:", error2);
|
|
162253
|
+
this.pendingTelephonyContext = null;
|
|
162254
|
+
}
|
|
162255
|
+
}
|
|
160260
162256
|
async injectMemoryContext(userMessage) {
|
|
160261
162257
|
if (!this.memoryInjector || !this.memoryInjector.isEnabled())
|
|
160262
162258
|
return;
|
|
@@ -160526,8 +162522,8 @@ ${this.identityContext}`);
|
|
|
160526
162522
|
return null;
|
|
160527
162523
|
const intervalMs = Math.max(1000, config.heartbeat?.intervalMs ?? 15000);
|
|
160528
162524
|
const staleThresholdMs = Math.max(intervalMs * 2, config.heartbeat?.staleThresholdMs ?? 120000);
|
|
160529
|
-
const persistPath = config.heartbeat?.persistPath ??
|
|
160530
|
-
const historyPath = config.heartbeat?.historyPath ??
|
|
162525
|
+
const persistPath = config.heartbeat?.persistPath ?? join48(getConfigDir(), "heartbeats", `${this.sessionId}.json`);
|
|
162526
|
+
const historyPath = config.heartbeat?.historyPath ?? join48(getConfigDir(), "heartbeats", "runs", `${this.sessionId}.jsonl`);
|
|
160531
162527
|
return {
|
|
160532
162528
|
intervalMs,
|
|
160533
162529
|
staleThresholdMs,
|
|
@@ -160916,9 +162912,9 @@ class StatsTracker {
|
|
|
160916
162912
|
}
|
|
160917
162913
|
}
|
|
160918
162914
|
// ../core/src/tools/connector-index.ts
|
|
160919
|
-
import { join as
|
|
162915
|
+
import { join as join49, dirname as dirname22 } from "path";
|
|
160920
162916
|
import { homedir as homedir23 } from "os";
|
|
160921
|
-
import { existsSync as
|
|
162917
|
+
import { existsSync as existsSync31, mkdirSync as mkdirSync21, writeFileSync as writeFileSync16, readFileSync as readFileSync19 } from "fs";
|
|
160922
162918
|
var TAG_KEYWORDS = {
|
|
160923
162919
|
email: ["email", "mail", "inbox", "send", "receive", "message", "compose"],
|
|
160924
162920
|
calendar: ["calendar", "event", "meeting", "schedule", "appointment"],
|
|
@@ -160955,13 +162951,13 @@ class ConnectorIndex {
|
|
|
160955
162951
|
return envHome && envHome.trim().length > 0 ? envHome : homedir23();
|
|
160956
162952
|
}
|
|
160957
162953
|
getCachePath() {
|
|
160958
|
-
return
|
|
162954
|
+
return join49(this.getHomeDir(), ".assistants", "cache", "connector-index.json");
|
|
160959
162955
|
}
|
|
160960
162956
|
loadDiskCache() {
|
|
160961
162957
|
ConnectorIndex.indexLoaded = true;
|
|
160962
162958
|
try {
|
|
160963
162959
|
const cachePath = this.getCachePath();
|
|
160964
|
-
if (!
|
|
162960
|
+
if (!existsSync31(cachePath))
|
|
160965
162961
|
return;
|
|
160966
162962
|
const data = JSON.parse(readFileSync19(cachePath, "utf-8"));
|
|
160967
162963
|
if (data.version !== INDEX_VERSION)
|
|
@@ -160977,9 +162973,9 @@ class ConnectorIndex {
|
|
|
160977
162973
|
saveDiskCache() {
|
|
160978
162974
|
try {
|
|
160979
162975
|
const cachePath = this.getCachePath();
|
|
160980
|
-
const cacheDir =
|
|
160981
|
-
if (!
|
|
160982
|
-
|
|
162976
|
+
const cacheDir = dirname22(cachePath);
|
|
162977
|
+
if (!existsSync31(cacheDir)) {
|
|
162978
|
+
mkdirSync21(cacheDir, { recursive: true });
|
|
160983
162979
|
}
|
|
160984
162980
|
const data = {
|
|
160985
162981
|
version: INDEX_VERSION,
|