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