@letta-ai/letta-code 0.28.13 → 0.28.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-presets.js +3 -1
- package/dist/agent-presets.js.map +2 -2
- package/dist/types/channels/types.d.ts +25 -0
- package/dist/types/channels/types.d.ts.map +1 -1
- package/letta.js +1353 -678
- package/package.json +1 -1
- package/skills/letta-guide/SKILL.md +74 -0
- package/skills/scheduling-tasks/SKILL.md +28 -3
package/letta.js
CHANGED
|
@@ -4853,7 +4853,7 @@ var package_default;
|
|
|
4853
4853
|
var init_package = __esm(() => {
|
|
4854
4854
|
package_default = {
|
|
4855
4855
|
name: "@letta-ai/letta-code",
|
|
4856
|
-
version: "0.28.
|
|
4856
|
+
version: "0.28.14",
|
|
4857
4857
|
description: "Letta Code is a CLI tool for interacting with stateful Letta agents from the terminal.",
|
|
4858
4858
|
type: "module",
|
|
4859
4859
|
packageManager: "bun@1.3.0",
|
|
@@ -159124,6 +159124,8 @@ Creating crons:
|
|
|
159124
159124
|
- Recurring monitoring/heartbeat: \`letta cron add --name <short-name> --description <description> --prompt <future-message> --every "2h"\` or \`--cron "0 9 * * *"\`
|
|
159125
159125
|
Always include \`--name\`, \`--description\`, and \`--prompt\`. \`$AGENT_ID\` is automatically injected into the shell environment, and \`letta cron\` uses it by default, so you do not need to specify which agent to invoke unless overriding the current agent intentionally.
|
|
159126
159126
|
|
|
159127
|
+
Where crons run: for cloud agents, schedules default to durable Cloud schedules that fire from the cloud and execute in your cloud sandbox — they survive local shutdown, so this is the right default. If the scheduled work must run on a specific computer (e.g. it needs that computer's filesystem or local services), add \`--computer <deviceId>\` (from \`letta environments list\`) to keep the durable Cloud schedule but execute on that computer, with sandbox fallback if it is offline. Use \`--runner local\` only when that fallback is unacceptable; local schedules only fire while a Letta session is running on that computer.
|
|
159128
|
+
|
|
159127
159129
|
# Harness Architecture
|
|
159128
159130
|
|
|
159129
159131
|
You run within the Letta Code CLI on some machine (the environment). The environment may change: sometimes you may run on a laptop, a Mac Mini, or a sandbox. Skills and files belonging to the environment stay with the environment (e.g. \`AGENTS.md\` or \`.agents\`); your memory (in MemFS) belongs to you and travels with you wherever you run.
|
|
@@ -166511,6 +166513,12 @@ function cloneAccount(account) {
|
|
|
166511
166513
|
...account,
|
|
166512
166514
|
allowedUsers: [...account.allowedUsers]
|
|
166513
166515
|
};
|
|
166516
|
+
if (account.adminUsers) {
|
|
166517
|
+
cloned.adminUsers = [...account.adminUsers];
|
|
166518
|
+
}
|
|
166519
|
+
if (account.userAllowedCommands) {
|
|
166520
|
+
cloned.userAllowedCommands = [...account.userAllowedCommands];
|
|
166521
|
+
}
|
|
166514
166522
|
if (isTelegramChannelAccount(account)) {
|
|
166515
166523
|
cloned.binding = { ...account.binding };
|
|
166516
166524
|
}
|
|
@@ -166882,9 +166890,12 @@ var init_accounts = __esm(() => {
|
|
|
166882
166890
|
init_types7();
|
|
166883
166891
|
SNAKE_TO_CAMEL = {
|
|
166884
166892
|
account_uuid: "accountUuid",
|
|
166893
|
+
admin_users: "adminUsers",
|
|
166885
166894
|
allowed_channels: "allowedChannels",
|
|
166886
166895
|
allowed_groups: "allowedGroups",
|
|
166887
166896
|
allow_bots: "allowBots",
|
|
166897
|
+
group_policy: "groupPolicy",
|
|
166898
|
+
user_allowed_commands: "userAllowedCommands",
|
|
166888
166899
|
auto_thread_on_mention: "autoThreadOnMention",
|
|
166889
166900
|
base_url: "baseUrl",
|
|
166890
166901
|
acknowledge_message_reaction: "acknowledgeMessageReaction",
|
|
@@ -167334,7 +167345,551 @@ var init_account_display2 = __esm(() => {
|
|
|
167334
167345
|
init_utils5();
|
|
167335
167346
|
});
|
|
167336
167347
|
|
|
167337
|
-
// src/channels/
|
|
167348
|
+
// src/channels/pairing.ts
|
|
167349
|
+
var exports_pairing = {};
|
|
167350
|
+
__export(exports_pairing, {
|
|
167351
|
+
rollbackPairingApproval: () => rollbackPairingApproval,
|
|
167352
|
+
removePairingStateForAccount: () => removePairingStateForAccount,
|
|
167353
|
+
loadPairingStore: () => loadPairingStore,
|
|
167354
|
+
isUserApproved: () => isUserApproved,
|
|
167355
|
+
getPendingPairings: () => getPendingPairings,
|
|
167356
|
+
getApprovedUsers: () => getApprovedUsers,
|
|
167357
|
+
createPairingCode: () => createPairingCode,
|
|
167358
|
+
consumePairingCode: () => consumePairingCode,
|
|
167359
|
+
clearPairingStores: () => clearPairingStores,
|
|
167360
|
+
__testOverrideSavePairingStore: () => __testOverrideSavePairingStore,
|
|
167361
|
+
__testOverrideLoadPairingStore: () => __testOverrideLoadPairingStore
|
|
167362
|
+
});
|
|
167363
|
+
import { randomInt } from "node:crypto";
|
|
167364
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync7, writeFileSync as writeFileSync3 } from "node:fs";
|
|
167365
|
+
function normalizeAccountId(accountId) {
|
|
167366
|
+
return accountId ?? LEGACY_CHANNEL_ACCOUNT_ID;
|
|
167367
|
+
}
|
|
167368
|
+
function getStore2(channelId) {
|
|
167369
|
+
let store = stores2.get(channelId);
|
|
167370
|
+
if (!store) {
|
|
167371
|
+
store = { pending: [], approved: [] };
|
|
167372
|
+
stores2.set(channelId, store);
|
|
167373
|
+
}
|
|
167374
|
+
return store;
|
|
167375
|
+
}
|
|
167376
|
+
function loadPairingStore(channelId) {
|
|
167377
|
+
if (loadPairingStoreOverride) {
|
|
167378
|
+
const overridden = loadPairingStoreOverride(channelId);
|
|
167379
|
+
if (overridden === null) {
|
|
167380
|
+
return;
|
|
167381
|
+
}
|
|
167382
|
+
stores2.set(channelId, {
|
|
167383
|
+
pending: [...overridden.pending],
|
|
167384
|
+
approved: [...overridden.approved]
|
|
167385
|
+
});
|
|
167386
|
+
return;
|
|
167387
|
+
}
|
|
167388
|
+
const path5 = getChannelPairingPath(channelId);
|
|
167389
|
+
if (!existsSync8(path5))
|
|
167390
|
+
return;
|
|
167391
|
+
try {
|
|
167392
|
+
const text = readFileSync7(path5, "utf-8");
|
|
167393
|
+
const parsed = JSON.parse(text);
|
|
167394
|
+
stores2.set(channelId, {
|
|
167395
|
+
pending: parsed.pending ?? [],
|
|
167396
|
+
approved: parsed.approved ?? []
|
|
167397
|
+
});
|
|
167398
|
+
} catch {}
|
|
167399
|
+
}
|
|
167400
|
+
function savePairingStore(channelId) {
|
|
167401
|
+
const store = getStore2(channelId);
|
|
167402
|
+
if (savePairingStoreOverride) {
|
|
167403
|
+
savePairingStoreOverride(channelId, {
|
|
167404
|
+
pending: [...store.pending],
|
|
167405
|
+
approved: [...store.approved]
|
|
167406
|
+
});
|
|
167407
|
+
return;
|
|
167408
|
+
}
|
|
167409
|
+
const dir = getChannelDir(channelId);
|
|
167410
|
+
mkdirSync5(dir, { recursive: true });
|
|
167411
|
+
writeFileSync3(getChannelPairingPath(channelId), `${JSON.stringify(store, null, 2)}
|
|
167412
|
+
`, "utf-8");
|
|
167413
|
+
}
|
|
167414
|
+
function generateCode(length = 6) {
|
|
167415
|
+
let code2 = "";
|
|
167416
|
+
for (let i2 = 0;i2 < length; i2++) {
|
|
167417
|
+
code2 += CODE_CHARS[randomInt(CODE_CHARS.length)];
|
|
167418
|
+
}
|
|
167419
|
+
return code2;
|
|
167420
|
+
}
|
|
167421
|
+
function isUserApproved(channelId, userId, accountId) {
|
|
167422
|
+
const store = getStore2(channelId);
|
|
167423
|
+
const normalizedAccountId = normalizeAccountId(accountId);
|
|
167424
|
+
return store.approved.some((u) => u.senderId === userId && normalizeAccountId(u.accountId) === normalizedAccountId);
|
|
167425
|
+
}
|
|
167426
|
+
function createPairingCode(channelId, userId, chatId, username, accountId) {
|
|
167427
|
+
const store = getStore2(channelId);
|
|
167428
|
+
const normalizedAccountId = normalizeAccountId(accountId);
|
|
167429
|
+
const nowMs = Date.now();
|
|
167430
|
+
const existing = store.pending.find((p) => p.senderId === userId && normalizeAccountId(p.accountId) === normalizedAccountId && new Date(p.expiresAt).getTime() > nowMs);
|
|
167431
|
+
if (existing) {
|
|
167432
|
+
return existing.code;
|
|
167433
|
+
}
|
|
167434
|
+
store.pending = store.pending.filter((p) => !(p.senderId === userId && normalizeAccountId(p.accountId) === normalizedAccountId));
|
|
167435
|
+
const now = Date.now();
|
|
167436
|
+
store.pending = store.pending.filter((p) => new Date(p.expiresAt).getTime() > now);
|
|
167437
|
+
while (store.pending.length >= MAX_PENDING_CODES) {
|
|
167438
|
+
store.pending.shift();
|
|
167439
|
+
}
|
|
167440
|
+
const code2 = generateCode();
|
|
167441
|
+
const pending = {
|
|
167442
|
+
accountId: normalizedAccountId,
|
|
167443
|
+
code: code2,
|
|
167444
|
+
senderId: userId,
|
|
167445
|
+
senderName: username,
|
|
167446
|
+
chatId,
|
|
167447
|
+
createdAt: new Date().toISOString(),
|
|
167448
|
+
expiresAt: new Date(now + PAIRING_CODE_TTL_MS).toISOString()
|
|
167449
|
+
};
|
|
167450
|
+
store.pending.push(pending);
|
|
167451
|
+
savePairingStore(channelId);
|
|
167452
|
+
return code2;
|
|
167453
|
+
}
|
|
167454
|
+
function consumePairingCode(channelId, code2, accountId) {
|
|
167455
|
+
const store = getStore2(channelId);
|
|
167456
|
+
const upperCode = code2.toUpperCase();
|
|
167457
|
+
const normalizedAccountId = accountId === undefined ? undefined : normalizeAccountId(accountId);
|
|
167458
|
+
const matches = store.pending.map((pending2, index2) => ({ pending: pending2, index: index2 })).filter(({ pending: pending2 }) => pending2.code === upperCode && (normalizedAccountId === undefined || normalizeAccountId(pending2.accountId) === normalizedAccountId));
|
|
167459
|
+
if (matches.length > 1) {
|
|
167460
|
+
return null;
|
|
167461
|
+
}
|
|
167462
|
+
const index = matches[0]?.index ?? -1;
|
|
167463
|
+
if (index === -1)
|
|
167464
|
+
return null;
|
|
167465
|
+
const pending = store.pending[index];
|
|
167466
|
+
const pendingAccountId = normalizeAccountId(pending.accountId);
|
|
167467
|
+
if (new Date(pending.expiresAt).getTime() < Date.now()) {
|
|
167468
|
+
store.pending.splice(index, 1);
|
|
167469
|
+
savePairingStore(channelId);
|
|
167470
|
+
return null;
|
|
167471
|
+
}
|
|
167472
|
+
store.pending.splice(index, 1);
|
|
167473
|
+
if (!store.approved.some((u) => u.senderId === pending.senderId && normalizeAccountId(u.accountId) === pendingAccountId)) {
|
|
167474
|
+
const approved = {
|
|
167475
|
+
accountId: pendingAccountId,
|
|
167476
|
+
senderId: pending.senderId,
|
|
167477
|
+
senderName: pending.senderName,
|
|
167478
|
+
approvedAt: new Date().toISOString()
|
|
167479
|
+
};
|
|
167480
|
+
store.approved.push(approved);
|
|
167481
|
+
}
|
|
167482
|
+
savePairingStore(channelId);
|
|
167483
|
+
return pending;
|
|
167484
|
+
}
|
|
167485
|
+
function getPendingPairings(channelId, accountId) {
|
|
167486
|
+
const store = getStore2(channelId);
|
|
167487
|
+
const now = Date.now();
|
|
167488
|
+
const normalizedAccountId = accountId === undefined ? undefined : normalizeAccountId(accountId);
|
|
167489
|
+
return store.pending.filter((p) => new Date(p.expiresAt).getTime() > now && (normalizedAccountId === undefined || normalizeAccountId(p.accountId) === normalizedAccountId));
|
|
167490
|
+
}
|
|
167491
|
+
function getApprovedUsers(channelId, accountId) {
|
|
167492
|
+
const normalizedAccountId = accountId === undefined ? undefined : normalizeAccountId(accountId);
|
|
167493
|
+
return getStore2(channelId).approved.filter((user) => normalizedAccountId === undefined || normalizeAccountId(user.accountId) === normalizedAccountId);
|
|
167494
|
+
}
|
|
167495
|
+
function rollbackPairingApproval(channelId, pending) {
|
|
167496
|
+
const store = getStore2(channelId);
|
|
167497
|
+
const normalizedAccountId = normalizeAccountId(pending.accountId);
|
|
167498
|
+
store.approved = store.approved.filter((u) => !(u.senderId === pending.senderId && normalizeAccountId(u.accountId) === normalizedAccountId));
|
|
167499
|
+
store.pending.push(pending);
|
|
167500
|
+
savePairingStore(channelId);
|
|
167501
|
+
}
|
|
167502
|
+
function removePairingStateForAccount(channelId, accountId) {
|
|
167503
|
+
const store = getStore2(channelId);
|
|
167504
|
+
const normalizedAccountId = normalizeAccountId(accountId);
|
|
167505
|
+
const nextPending = store.pending.filter((pending) => normalizeAccountId(pending.accountId) !== normalizedAccountId);
|
|
167506
|
+
const nextApproved = store.approved.filter((approved) => normalizeAccountId(approved.accountId) !== normalizedAccountId);
|
|
167507
|
+
const pendingRemoved = store.pending.length - nextPending.length;
|
|
167508
|
+
const approvedRemoved = store.approved.length - nextApproved.length;
|
|
167509
|
+
if (pendingRemoved === 0 && approvedRemoved === 0) {
|
|
167510
|
+
return { pendingRemoved, approvedRemoved };
|
|
167511
|
+
}
|
|
167512
|
+
store.pending = nextPending;
|
|
167513
|
+
store.approved = nextApproved;
|
|
167514
|
+
savePairingStore(channelId);
|
|
167515
|
+
return { pendingRemoved, approvedRemoved };
|
|
167516
|
+
}
|
|
167517
|
+
function clearPairingStores() {
|
|
167518
|
+
stores2.clear();
|
|
167519
|
+
}
|
|
167520
|
+
function __testOverrideLoadPairingStore(fn) {
|
|
167521
|
+
loadPairingStoreOverride = fn;
|
|
167522
|
+
}
|
|
167523
|
+
function __testOverrideSavePairingStore(fn) {
|
|
167524
|
+
savePairingStoreOverride = fn;
|
|
167525
|
+
}
|
|
167526
|
+
var PAIRING_CODE_TTL_MS, MAX_PENDING_CODES = 50, CODE_CHARS = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789", stores2, loadPairingStoreOverride = null, savePairingStoreOverride = null;
|
|
167527
|
+
var init_pairing = __esm(() => {
|
|
167528
|
+
init_accounts();
|
|
167529
|
+
init_config2();
|
|
167530
|
+
PAIRING_CODE_TTL_MS = 15 * 60 * 1000;
|
|
167531
|
+
stores2 = new Map;
|
|
167532
|
+
});
|
|
167533
|
+
|
|
167534
|
+
// src/channels/signal/target.ts
|
|
167535
|
+
function assertSignalHttpProtocol(url2) {
|
|
167536
|
+
if (url2.protocol !== "http:" && url2.protocol !== "https:") {
|
|
167537
|
+
throw new Error(`Signal base URL protocol must be http or https, got ${url2.protocol}`);
|
|
167538
|
+
}
|
|
167539
|
+
}
|
|
167540
|
+
function trimPrefix(value, prefix) {
|
|
167541
|
+
if (!value.toLowerCase().startsWith(prefix)) {
|
|
167542
|
+
return null;
|
|
167543
|
+
}
|
|
167544
|
+
return value.slice(prefix.length).trim();
|
|
167545
|
+
}
|
|
167546
|
+
function normalizeSignalBaseUrl(input) {
|
|
167547
|
+
const trimmed = input.trim();
|
|
167548
|
+
if (!trimmed) {
|
|
167549
|
+
return "";
|
|
167550
|
+
}
|
|
167551
|
+
const hasScheme = /^[a-z][a-z0-9+.-]*:\/\//i.test(trimmed);
|
|
167552
|
+
const withScheme = hasScheme ? trimmed : `http://${trimmed}`;
|
|
167553
|
+
const parsed = new URL(withScheme);
|
|
167554
|
+
assertSignalHttpProtocol(parsed);
|
|
167555
|
+
if (parsed.username || parsed.password) {
|
|
167556
|
+
throw new Error("Signal base URL must not include credentials.");
|
|
167557
|
+
}
|
|
167558
|
+
return withScheme.replace(/\/+$/, "");
|
|
167559
|
+
}
|
|
167560
|
+
function parseSignalTarget(input) {
|
|
167561
|
+
const trimmed = input.trim();
|
|
167562
|
+
if (!trimmed) {
|
|
167563
|
+
throw new Error("Signal target is required.");
|
|
167564
|
+
}
|
|
167565
|
+
const signalTarget = trimPrefix(trimmed, "signal:");
|
|
167566
|
+
const value = signalTarget !== null ? signalTarget : trimmed;
|
|
167567
|
+
if (!value) {
|
|
167568
|
+
throw new Error("Signal target is required.");
|
|
167569
|
+
}
|
|
167570
|
+
const groupId = trimPrefix(value, "group:");
|
|
167571
|
+
if (groupId) {
|
|
167572
|
+
return { kind: "group", groupId };
|
|
167573
|
+
}
|
|
167574
|
+
const username = trimPrefix(value, "username:");
|
|
167575
|
+
if (username) {
|
|
167576
|
+
return { kind: "username", username };
|
|
167577
|
+
}
|
|
167578
|
+
const usernameAlias = trimPrefix(value, "u:");
|
|
167579
|
+
if (usernameAlias) {
|
|
167580
|
+
return { kind: "username", username: `u:${usernameAlias}` };
|
|
167581
|
+
}
|
|
167582
|
+
const recipient = value;
|
|
167583
|
+
if (!recipient) {
|
|
167584
|
+
throw new Error("Signal recipient is required.");
|
|
167585
|
+
}
|
|
167586
|
+
return { kind: "recipient", recipient };
|
|
167587
|
+
}
|
|
167588
|
+
function signalTargetToSendRpcParams(target2) {
|
|
167589
|
+
switch (target2.kind) {
|
|
167590
|
+
case "group":
|
|
167591
|
+
return { groupId: target2.groupId };
|
|
167592
|
+
case "username":
|
|
167593
|
+
return { username: [target2.username] };
|
|
167594
|
+
case "recipient":
|
|
167595
|
+
return { recipient: [target2.recipient] };
|
|
167596
|
+
}
|
|
167597
|
+
}
|
|
167598
|
+
function signalTargetToReactionRpcParams(target2) {
|
|
167599
|
+
switch (target2.kind) {
|
|
167600
|
+
case "group":
|
|
167601
|
+
return { groupIds: [target2.groupId] };
|
|
167602
|
+
case "recipient":
|
|
167603
|
+
return { recipients: [target2.recipient] };
|
|
167604
|
+
case "username":
|
|
167605
|
+
throw new Error("Signal reactions require a recipient or group target.");
|
|
167606
|
+
}
|
|
167607
|
+
}
|
|
167608
|
+
function normalizeSignalSenderId(value) {
|
|
167609
|
+
if (!value) {
|
|
167610
|
+
return "";
|
|
167611
|
+
}
|
|
167612
|
+
const trimmed = value.trim();
|
|
167613
|
+
if (!trimmed) {
|
|
167614
|
+
return "";
|
|
167615
|
+
}
|
|
167616
|
+
return trimmed.toLowerCase();
|
|
167617
|
+
}
|
|
167618
|
+
function normalizeSignalPhone(value) {
|
|
167619
|
+
if (!value) {
|
|
167620
|
+
return "";
|
|
167621
|
+
}
|
|
167622
|
+
const trimmed = value.trim();
|
|
167623
|
+
if (!trimmed) {
|
|
167624
|
+
return "";
|
|
167625
|
+
}
|
|
167626
|
+
const withoutPrefix = trimmed.toLowerCase().startsWith("signal:") ? trimmed.slice("signal:".length) : trimmed;
|
|
167627
|
+
return withoutPrefix.replace(/[^0-9+]/g, "");
|
|
167628
|
+
}
|
|
167629
|
+
function signalAllowedUsersIncludes(allowedUsers, senderId) {
|
|
167630
|
+
const normalizedSender = normalizeSignalSenderId(senderId);
|
|
167631
|
+
const senderPhone = normalizeSignalPhone(senderId);
|
|
167632
|
+
return allowedUsers.some((entry) => {
|
|
167633
|
+
const normalizedEntry = normalizeSignalSenderId(entry);
|
|
167634
|
+
if (normalizedEntry === normalizedSender) {
|
|
167635
|
+
return true;
|
|
167636
|
+
}
|
|
167637
|
+
const entryPhone = normalizeSignalPhone(entry);
|
|
167638
|
+
return !!senderPhone && !!entryPhone && senderPhone === entryPhone;
|
|
167639
|
+
});
|
|
167640
|
+
}
|
|
167641
|
+
function isSignalGroupAllowed(allowedGroups, groupId) {
|
|
167642
|
+
if (!allowedGroups || allowedGroups.length === 0) {
|
|
167643
|
+
return true;
|
|
167644
|
+
}
|
|
167645
|
+
const normalized = groupId.trim();
|
|
167646
|
+
return allowedGroups.some((entry) => entry.trim() === normalized);
|
|
167647
|
+
}
|
|
167648
|
+
function matchesSignalMentionPatterns(text, mentionPatterns) {
|
|
167649
|
+
const normalizedText = text.toLowerCase();
|
|
167650
|
+
return (mentionPatterns ?? []).map((entry) => entry.trim().toLowerCase()).filter(Boolean).some((entry) => normalizedText.includes(entry));
|
|
167651
|
+
}
|
|
167652
|
+
var init_target2 = () => {};
|
|
167653
|
+
|
|
167654
|
+
// src/channels/whatsapp/jid.ts
|
|
167655
|
+
function stripDeviceSuffix(jid) {
|
|
167656
|
+
if (!jid)
|
|
167657
|
+
return "";
|
|
167658
|
+
return jid.replace(/:\d+(@|$)/, "$1");
|
|
167659
|
+
}
|
|
167660
|
+
function isLidJid(jid) {
|
|
167661
|
+
return !!jid && stripDeviceSuffix(jid).endsWith(WHATSAPP_LID_SUFFIX);
|
|
167662
|
+
}
|
|
167663
|
+
function isGroupJid(jid) {
|
|
167664
|
+
return !!jid && stripDeviceSuffix(jid).endsWith(WHATSAPP_GROUP_SUFFIX);
|
|
167665
|
+
}
|
|
167666
|
+
function isStatusOrBroadcastJid(jid) {
|
|
167667
|
+
if (!jid)
|
|
167668
|
+
return true;
|
|
167669
|
+
const normalized = stripDeviceSuffix(jid);
|
|
167670
|
+
return normalized === "status@broadcast" || normalized.endsWith("@broadcast") || normalized.endsWith("@newsletter");
|
|
167671
|
+
}
|
|
167672
|
+
function jidToDigits(jid) {
|
|
167673
|
+
if (!jid)
|
|
167674
|
+
return "";
|
|
167675
|
+
const base2 = stripDeviceSuffix(jid).split("@")[0] ?? "";
|
|
167676
|
+
return base2.replace(/\D/g, "");
|
|
167677
|
+
}
|
|
167678
|
+
function normalizePhoneLike(value) {
|
|
167679
|
+
if (!value)
|
|
167680
|
+
return "";
|
|
167681
|
+
return jidToDigits(value.trim());
|
|
167682
|
+
}
|
|
167683
|
+
function phoneDigitsToJid(phoneDigits) {
|
|
167684
|
+
const digits = normalizePhoneLike(phoneDigits);
|
|
167685
|
+
return digits ? `${digits}${WHATSAPP_PHONE_SUFFIX}` : "";
|
|
167686
|
+
}
|
|
167687
|
+
function normalizeMaybePhoneJid(value) {
|
|
167688
|
+
if (!value)
|
|
167689
|
+
return null;
|
|
167690
|
+
const trimmed = value.trim();
|
|
167691
|
+
if (!trimmed)
|
|
167692
|
+
return null;
|
|
167693
|
+
if (isLidJid(trimmed))
|
|
167694
|
+
return null;
|
|
167695
|
+
if (trimmed.includes("@"))
|
|
167696
|
+
return stripDeviceSuffix(trimmed);
|
|
167697
|
+
return phoneDigitsToJid(trimmed) || null;
|
|
167698
|
+
}
|
|
167699
|
+
function isSelfChat(remoteJid, selfPhoneJid, selfLid) {
|
|
167700
|
+
const remote = stripDeviceSuffix(remoteJid);
|
|
167701
|
+
if (!remote)
|
|
167702
|
+
return false;
|
|
167703
|
+
const phone = stripDeviceSuffix(selfPhoneJid);
|
|
167704
|
+
if (phone && remote === phone)
|
|
167705
|
+
return true;
|
|
167706
|
+
const lid = stripDeviceSuffix(selfLid);
|
|
167707
|
+
if (lid && remote === lid)
|
|
167708
|
+
return true;
|
|
167709
|
+
return false;
|
|
167710
|
+
}
|
|
167711
|
+
function senderIdFromJid(jid) {
|
|
167712
|
+
return jidToDigits(jid);
|
|
167713
|
+
}
|
|
167714
|
+
function allowedUsersIncludes(allowedUsers, senderId) {
|
|
167715
|
+
const normalizedSender = normalizePhoneLike(senderId);
|
|
167716
|
+
return allowedUsers.some((entry) => normalizePhoneLike(entry) === normalizedSender);
|
|
167717
|
+
}
|
|
167718
|
+
function resolveLidToPhoneJid(params) {
|
|
167719
|
+
const { lidJid, message, sock } = params;
|
|
167720
|
+
if (!isLidJid(lidJid))
|
|
167721
|
+
return normalizeMaybePhoneJid(lidJid);
|
|
167722
|
+
const msg = message;
|
|
167723
|
+
const senderPn = normalizeMaybePhoneJid(msg?.key?.senderPn ?? undefined);
|
|
167724
|
+
if (senderPn)
|
|
167725
|
+
return senderPn;
|
|
167726
|
+
const repo = sock?.signalRepository;
|
|
167727
|
+
const mapped3 = normalizeMaybePhoneJid(repo?.lidMapping?.get(stripDeviceSuffix(lidJid)));
|
|
167728
|
+
if (mapped3)
|
|
167729
|
+
return mapped3;
|
|
167730
|
+
return null;
|
|
167731
|
+
}
|
|
167732
|
+
function resolveSendJid(params) {
|
|
167733
|
+
const { chatId, selfPhoneJid, selfLid, lidToJid, sock } = params;
|
|
167734
|
+
if (!isLidJid(chatId))
|
|
167735
|
+
return stripDeviceSuffix(chatId);
|
|
167736
|
+
const normalized = stripDeviceSuffix(chatId);
|
|
167737
|
+
if (selfLid && normalized === stripDeviceSuffix(selfLid) && selfPhoneJid) {
|
|
167738
|
+
return stripDeviceSuffix(selfPhoneJid);
|
|
167739
|
+
}
|
|
167740
|
+
const mapped3 = normalizeMaybePhoneJid(lidToJid?.get(normalized));
|
|
167741
|
+
if (mapped3)
|
|
167742
|
+
return mapped3;
|
|
167743
|
+
const repo = sock?.signalRepository;
|
|
167744
|
+
const signalMapped = normalizeMaybePhoneJid(repo?.lidMapping?.get(normalized));
|
|
167745
|
+
if (signalMapped)
|
|
167746
|
+
return signalMapped;
|
|
167747
|
+
throw new Error(`Cannot send to unresolved WhatsApp LID: ${chatId}`);
|
|
167748
|
+
}
|
|
167749
|
+
function sanitizePathSegment(input) {
|
|
167750
|
+
const cleaned = input.replace(/[^A-Za-z0-9._-]/g, "_").replace(/^_+|_+$/g, "");
|
|
167751
|
+
return cleaned || "whatsapp";
|
|
167752
|
+
}
|
|
167753
|
+
var WHATSAPP_PHONE_SUFFIX = "@s.whatsapp.net", WHATSAPP_LID_SUFFIX = "@lid", WHATSAPP_GROUP_SUFFIX = "@g.us";
|
|
167754
|
+
|
|
167755
|
+
// src/channels/access-control.ts
|
|
167756
|
+
function resolveChannelAccessScope(chatType) {
|
|
167757
|
+
return chatType === "channel" ? "group" : "dm";
|
|
167758
|
+
}
|
|
167759
|
+
function channelEnvKey(channelId, suffix) {
|
|
167760
|
+
const normalized = channelId.toUpperCase().replace(/[^A-Z0-9]+/g, "_");
|
|
167761
|
+
return `LETTA_${normalized}_${suffix}`;
|
|
167762
|
+
}
|
|
167763
|
+
function parseUserList(raw) {
|
|
167764
|
+
if (!raw)
|
|
167765
|
+
return [];
|
|
167766
|
+
return raw.split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0);
|
|
167767
|
+
}
|
|
167768
|
+
function isEnvFlagEnabled(raw) {
|
|
167769
|
+
const normalized = (raw ?? "").trim().toLowerCase();
|
|
167770
|
+
return normalized === "1" || normalized === "true" || normalized === "yes";
|
|
167771
|
+
}
|
|
167772
|
+
function getChannelEnvAllowedUsers(channelId) {
|
|
167773
|
+
return [
|
|
167774
|
+
...parseUserList(process.env[GLOBAL_ALLOWED_USERS_ENV]),
|
|
167775
|
+
...parseUserList(process.env[channelEnvKey(channelId, "ALLOWED_USERS")])
|
|
167776
|
+
];
|
|
167777
|
+
}
|
|
167778
|
+
function getChannelEnvAdminUsers(channelId) {
|
|
167779
|
+
return [
|
|
167780
|
+
...parseUserList(process.env[GLOBAL_ADMIN_USERS_ENV]),
|
|
167781
|
+
...parseUserList(process.env[channelEnvKey(channelId, "ADMIN_USERS")])
|
|
167782
|
+
];
|
|
167783
|
+
}
|
|
167784
|
+
function isChannelAllowAllUsersEnabled(channelId) {
|
|
167785
|
+
return isEnvFlagEnabled(process.env[GLOBAL_ALLOW_ALL_ENV]) || isEnvFlagEnabled(process.env[channelEnvKey(channelId, "ALLOW_ALL_USERS")]);
|
|
167786
|
+
}
|
|
167787
|
+
function effectiveAllowedUsers(account, channelId) {
|
|
167788
|
+
return [
|
|
167789
|
+
...account.allowedUsers,
|
|
167790
|
+
...account.adminUsers ?? [],
|
|
167791
|
+
...getChannelEnvAllowedUsers(channelId),
|
|
167792
|
+
...getChannelEnvAdminUsers(channelId)
|
|
167793
|
+
];
|
|
167794
|
+
}
|
|
167795
|
+
function allowlistMatches(channelId, allowed, senderId) {
|
|
167796
|
+
if (allowed.includes("*")) {
|
|
167797
|
+
return true;
|
|
167798
|
+
}
|
|
167799
|
+
if (!senderId) {
|
|
167800
|
+
return false;
|
|
167801
|
+
}
|
|
167802
|
+
if (channelId === "whatsapp") {
|
|
167803
|
+
return allowedUsersIncludes(allowed, senderId);
|
|
167804
|
+
}
|
|
167805
|
+
if (channelId === "signal") {
|
|
167806
|
+
return signalAllowedUsersIncludes(allowed, senderId);
|
|
167807
|
+
}
|
|
167808
|
+
return allowed.includes(senderId);
|
|
167809
|
+
}
|
|
167810
|
+
function isPairedApproved(channelId, senderId, accountId) {
|
|
167811
|
+
if (isUserApproved(channelId, senderId, accountId)) {
|
|
167812
|
+
return true;
|
|
167813
|
+
}
|
|
167814
|
+
loadPairingStore(channelId);
|
|
167815
|
+
return isUserApproved(channelId, senderId, accountId);
|
|
167816
|
+
}
|
|
167817
|
+
function evaluateChannelSenderAccess(input) {
|
|
167818
|
+
const { account, channelId, senderId, chatType } = input;
|
|
167819
|
+
if (isChannelAllowAllUsersEnabled(channelId)) {
|
|
167820
|
+
return "allow";
|
|
167821
|
+
}
|
|
167822
|
+
const allowed = effectiveAllowedUsers(account, channelId);
|
|
167823
|
+
if (allowlistMatches(channelId, allowed, senderId)) {
|
|
167824
|
+
return "allow";
|
|
167825
|
+
}
|
|
167826
|
+
const envAllowlistConfigured = getChannelEnvAllowedUsers(channelId).length > 0;
|
|
167827
|
+
const scope = resolveChannelAccessScope(chatType);
|
|
167828
|
+
const restrictive = envAllowlistConfigured || (scope === "group" ? (account.groupPolicy ?? "open") === "allowlist" : account.dmPolicy !== "open");
|
|
167829
|
+
if (!restrictive) {
|
|
167830
|
+
return "allow";
|
|
167831
|
+
}
|
|
167832
|
+
if (senderId && isPairedApproved(channelId, senderId, account.accountId)) {
|
|
167833
|
+
return "allow";
|
|
167834
|
+
}
|
|
167835
|
+
if (scope === "group") {
|
|
167836
|
+
return "deny";
|
|
167837
|
+
}
|
|
167838
|
+
if (account.dmPolicy === "pairing" && !envAllowlistConfigured) {
|
|
167839
|
+
return channelId === "slack" ? "allow" : "pair";
|
|
167840
|
+
}
|
|
167841
|
+
return "deny";
|
|
167842
|
+
}
|
|
167843
|
+
function buildChannelAccessDeniedMessage(channelId) {
|
|
167844
|
+
const noun = ACCESS_DENIED_NOUNS[channelId] ?? "bot";
|
|
167845
|
+
return `You are not on the allowed users list for this ${noun}.`;
|
|
167846
|
+
}
|
|
167847
|
+
function floorOnlyChannelCommandGate() {
|
|
167848
|
+
return {
|
|
167849
|
+
enabled: true,
|
|
167850
|
+
isAdmin: false,
|
|
167851
|
+
allowedCommands: [],
|
|
167852
|
+
pairingPending: true
|
|
167853
|
+
};
|
|
167854
|
+
}
|
|
167855
|
+
function normalizeCommandName(name) {
|
|
167856
|
+
return name.trim().replace(/^\/+/, "").toLowerCase();
|
|
167857
|
+
}
|
|
167858
|
+
function canonicalizeChannelCommandName(name) {
|
|
167859
|
+
const normalized = normalizeCommandName(name);
|
|
167860
|
+
return normalized === "reflect" ? "reflection" : normalized;
|
|
167861
|
+
}
|
|
167862
|
+
function resolveChannelCommandGate(params) {
|
|
167863
|
+
const admins = [
|
|
167864
|
+
...params.account.adminUsers ?? [],
|
|
167865
|
+
...getChannelEnvAdminUsers(params.channelId)
|
|
167866
|
+
].filter((entry) => entry.length > 0);
|
|
167867
|
+
const enabled = admins.length > 0;
|
|
167868
|
+
const allowedCommands = (params.account.userAllowedCommands ?? []).map(canonicalizeChannelCommandName).filter((name) => name.length > 0);
|
|
167869
|
+
return {
|
|
167870
|
+
enabled,
|
|
167871
|
+
isAdmin: !enabled || allowlistMatches(params.channelId, admins, params.senderId),
|
|
167872
|
+
allowedCommands
|
|
167873
|
+
};
|
|
167874
|
+
}
|
|
167875
|
+
function canRunChannelCommand(gate, commandName) {
|
|
167876
|
+
if (!gate.enabled || gate.isAdmin) {
|
|
167877
|
+
return true;
|
|
167878
|
+
}
|
|
167879
|
+
const canonical = canonicalizeChannelCommandName(commandName);
|
|
167880
|
+
return CHANNEL_COMMAND_FLOOR.includes(canonical) || gate.allowedCommands.includes(canonical);
|
|
167881
|
+
}
|
|
167882
|
+
function runnableCommandsForUser(gate) {
|
|
167883
|
+
const seen = new Set;
|
|
167884
|
+
const runnable = [];
|
|
167885
|
+
for (const name of [...CHANNEL_COMMAND_FLOOR, ...gate.allowedCommands]) {
|
|
167886
|
+
if (!seen.has(name)) {
|
|
167887
|
+
seen.add(name);
|
|
167888
|
+
runnable.push(name);
|
|
167889
|
+
}
|
|
167890
|
+
}
|
|
167891
|
+
return runnable;
|
|
167892
|
+
}
|
|
167338
167893
|
function channelDisplayName(channelId) {
|
|
167339
167894
|
try {
|
|
167340
167895
|
return getChannelDisplayName(channelId);
|
|
@@ -167342,8 +167897,60 @@ function channelDisplayName(channelId) {
|
|
|
167342
167897
|
return channelId;
|
|
167343
167898
|
}
|
|
167344
167899
|
}
|
|
167900
|
+
function buildChannelCommandDeniedMessage(channelId, commandName, gate) {
|
|
167901
|
+
const runnable = runnableCommandsForUser(gate).map((name) => `/${name}`).join(", ");
|
|
167902
|
+
const restriction = gate.pairingPending ? "is available after pairing completes" : "is limited to admins here";
|
|
167903
|
+
return [
|
|
167904
|
+
`${channelDisplayName(channelId)}: /${normalizeCommandName(commandName)} ${restriction}.`,
|
|
167905
|
+
`Commands you can run: ${runnable}. Use /whoami to see your access.`
|
|
167906
|
+
].join(`
|
|
167907
|
+
`);
|
|
167908
|
+
}
|
|
167909
|
+
function buildChannelWhoamiMessage(msg, gate) {
|
|
167910
|
+
const scope = resolveChannelAccessScope(msg.chatType) === "dm" ? "DM" : "group/channel";
|
|
167911
|
+
const who = msg.senderName ? `${msg.senderName} (${msg.senderId})` : msg.senderId;
|
|
167912
|
+
const lines = [
|
|
167913
|
+
`You — ${channelDisplayName(msg.channel)} (${scope})`,
|
|
167914
|
+
`User ID: ${who}`
|
|
167915
|
+
];
|
|
167916
|
+
if (!gate || !gate.enabled) {
|
|
167917
|
+
lines.push("Tier: unrestricted (no admin list configured for this account)", "Commands: all available");
|
|
167918
|
+
} else if (gate.pairingPending) {
|
|
167919
|
+
const runnable = runnableCommandsForUser(gate).map((name) => `/${name}`).join(", ");
|
|
167920
|
+
lines.push("Tier: pending pairing", `Commands you can run: ${runnable}`);
|
|
167921
|
+
} else if (gate.isAdmin) {
|
|
167922
|
+
lines.push("Tier: admin", "Commands: all available");
|
|
167923
|
+
} else {
|
|
167924
|
+
const runnable = runnableCommandsForUser(gate).map((name) => `/${name}`).join(", ");
|
|
167925
|
+
lines.push("Tier: user", `Commands you can run: ${runnable}`);
|
|
167926
|
+
}
|
|
167927
|
+
return lines.join(`
|
|
167928
|
+
`);
|
|
167929
|
+
}
|
|
167930
|
+
var GLOBAL_ALLOWED_USERS_ENV = "LETTA_CHANNELS_ALLOWED_USERS", GLOBAL_ADMIN_USERS_ENV = "LETTA_CHANNELS_ADMIN_USERS", GLOBAL_ALLOW_ALL_ENV = "LETTA_CHANNELS_ALLOW_ALL_USERS", ACCESS_DENIED_NOUNS, CHANNEL_COMMAND_FLOOR;
|
|
167931
|
+
var init_access_control = __esm(() => {
|
|
167932
|
+
init_pairing();
|
|
167933
|
+
init_plugin_registry();
|
|
167934
|
+
init_target2();
|
|
167935
|
+
ACCESS_DENIED_NOUNS = {
|
|
167936
|
+
slack: "Slack app",
|
|
167937
|
+
discord: "Discord bot",
|
|
167938
|
+
whatsapp: "WhatsApp account",
|
|
167939
|
+
signal: "Signal account"
|
|
167940
|
+
};
|
|
167941
|
+
CHANNEL_COMMAND_FLOOR = ["help", "status", "whoami"];
|
|
167942
|
+
});
|
|
167943
|
+
|
|
167944
|
+
// src/channels/feedback.ts
|
|
167945
|
+
function channelDisplayName2(channelId) {
|
|
167946
|
+
try {
|
|
167947
|
+
return getChannelDisplayName(channelId);
|
|
167948
|
+
} catch {
|
|
167949
|
+
return channelId;
|
|
167950
|
+
}
|
|
167951
|
+
}
|
|
167345
167952
|
function buildChannelFeedbackUsageMessage(channelId) {
|
|
167346
|
-
const displayName =
|
|
167953
|
+
const displayName = channelDisplayName2(channelId);
|
|
167347
167954
|
return [
|
|
167348
167955
|
`${displayName} received /feedback without a message.`,
|
|
167349
167956
|
"Usage: /feedback <message>"
|
|
@@ -167352,11 +167959,11 @@ function buildChannelFeedbackUsageMessage(channelId) {
|
|
|
167352
167959
|
`);
|
|
167353
167960
|
}
|
|
167354
167961
|
function buildChannelFeedbackTooLongMessage(channelId, maxLength3 = CHANNEL_FEEDBACK_MESSAGE_MAX) {
|
|
167355
|
-
const displayName =
|
|
167962
|
+
const displayName = channelDisplayName2(channelId);
|
|
167356
167963
|
return `${displayName} feedback message is too long. Maximum is ${maxLength3.toLocaleString()} characters.`;
|
|
167357
167964
|
}
|
|
167358
167965
|
function buildChannelFeedbackNoRouteMessage(channelId) {
|
|
167359
|
-
const displayName =
|
|
167966
|
+
const displayName = channelDisplayName2(channelId);
|
|
167360
167967
|
const instruction = channelId === "slack" ? "Mention the app with a normal message in this chat or thread first so it can connect, then send /feedback <message> while mentioning the app." : "Send a normal message first and follow the pairing instructions, then try /feedback <message>.";
|
|
167361
167968
|
return [
|
|
167362
167969
|
`${displayName} cannot submit /feedback until this chat is connected to a Letta agent conversation.`,
|
|
@@ -167366,11 +167973,11 @@ function buildChannelFeedbackNoRouteMessage(channelId) {
|
|
|
167366
167973
|
`);
|
|
167367
167974
|
}
|
|
167368
167975
|
function buildChannelFeedbackSubmittedMessage(channelId) {
|
|
167369
|
-
const displayName =
|
|
167976
|
+
const displayName = channelDisplayName2(channelId);
|
|
167370
167977
|
return `${displayName} feedback submitted. Thanks for helping improve Letta Code.`;
|
|
167371
167978
|
}
|
|
167372
167979
|
function buildChannelFeedbackFailedMessage(channelId) {
|
|
167373
|
-
const displayName =
|
|
167980
|
+
const displayName = channelDisplayName2(channelId);
|
|
167374
167981
|
return `${displayName} could not submit feedback right now. Please try again later.`;
|
|
167375
167982
|
}
|
|
167376
167983
|
function withDefinedValues2(payload) {
|
|
@@ -167433,7 +168040,7 @@ var init_feedback2 = __esm(() => {
|
|
|
167433
168040
|
});
|
|
167434
168041
|
|
|
167435
168042
|
// src/channels/commands.ts
|
|
167436
|
-
function
|
|
168043
|
+
function channelDisplayName3(channelId) {
|
|
167437
168044
|
try {
|
|
167438
168045
|
return getChannelDisplayName(channelId);
|
|
167439
168046
|
} catch {
|
|
@@ -167516,7 +168123,7 @@ function isSlackMentionControlCommand(msg, command) {
|
|
|
167516
168123
|
return command.raw.startsWith("!") || isSlackMentionSlashCommand(msg, command);
|
|
167517
168124
|
}
|
|
167518
168125
|
function buildChannelHelpMessage(channelId) {
|
|
167519
|
-
const displayName =
|
|
168126
|
+
const displayName = channelDisplayName3(channelId);
|
|
167520
168127
|
if (channelId === "slack") {
|
|
167521
168128
|
return [
|
|
167522
168129
|
`${displayName} is connected to Letta Code.`,
|
|
@@ -167548,7 +168155,7 @@ function buildChannelHelpMessage(channelId) {
|
|
|
167548
168155
|
`);
|
|
167549
168156
|
}
|
|
167550
168157
|
function buildUnsupportedChannelCommandMessage(channelId, command) {
|
|
167551
|
-
const displayName =
|
|
168158
|
+
const displayName = channelDisplayName3(channelId);
|
|
167552
168159
|
const isBang = command.raw.startsWith("!");
|
|
167553
168160
|
const commandKind = isBang ? "bang" : "slash";
|
|
167554
168161
|
const supportedCommands = isBang ? supportedBangCommandsText() : channelId === "slack" ? supportedSlackMentionSlashCommandsText() : supportedCommandsText();
|
|
@@ -167562,7 +168169,7 @@ function buildUnsupportedChannelCommandMessage(channelId, command) {
|
|
|
167562
168169
|
`);
|
|
167563
168170
|
}
|
|
167564
168171
|
function buildChannelStatusMessage(msg, context3) {
|
|
167565
|
-
const displayName =
|
|
168172
|
+
const displayName = channelDisplayName3(msg.channel);
|
|
167566
168173
|
const route = context3.route;
|
|
167567
168174
|
const routeStatus = route ? "Connected to a Letta agent conversation." : "No route is connected for this chat yet.";
|
|
167568
168175
|
const accountStatus = !context3.accountConfigured ? "No channel account is configured for this receiver." : context3.accountEnabled === false ? "Channel account is configured but disabled." : "Channel account is configured and enabled.";
|
|
@@ -167590,7 +168197,7 @@ function buildChannelStatusMessage(msg, context3) {
|
|
|
167590
168197
|
`);
|
|
167591
168198
|
}
|
|
167592
168199
|
function buildChannelNoRouteMessage(channelId) {
|
|
167593
|
-
const displayName =
|
|
168200
|
+
const displayName = channelDisplayName3(channelId);
|
|
167594
168201
|
return [
|
|
167595
168202
|
`${displayName} could not find an existing route for this chat.`,
|
|
167596
168203
|
"Send a normal message first and follow the pairing instructions, then try again."
|
|
@@ -167599,23 +168206,23 @@ function buildChannelNoRouteMessage(channelId) {
|
|
|
167599
168206
|
`);
|
|
167600
168207
|
}
|
|
167601
168208
|
function buildChannelPausedMessage(channelId, route) {
|
|
167602
|
-
const displayName =
|
|
168209
|
+
const displayName = channelDisplayName3(channelId);
|
|
167603
168210
|
const conversation = route.conversationId ? ` Conversation: ${route.conversationId}.` : "";
|
|
167604
168211
|
return `${displayName} paused agent routing for this chat.${conversation} Send /resume here to turn replies back on.`;
|
|
167605
168212
|
}
|
|
167606
168213
|
function buildChannelAlreadyPausedMessage(channelId) {
|
|
167607
|
-
return `${
|
|
168214
|
+
return `${channelDisplayName3(channelId)} agent routing is already paused for this chat. Send /resume here to turn replies back on.`;
|
|
167608
168215
|
}
|
|
167609
168216
|
function buildChannelResumedMessage(channelId, route) {
|
|
167610
|
-
const displayName =
|
|
168217
|
+
const displayName = channelDisplayName3(channelId);
|
|
167611
168218
|
const conversation = route.conversationId ? ` Conversation: ${route.conversationId}.` : "";
|
|
167612
168219
|
return `${displayName} resumed agent routing for this chat.${conversation} Normal messages here will go to the connected agent again.`;
|
|
167613
168220
|
}
|
|
167614
168221
|
function buildChannelAlreadyActiveMessage(channelId) {
|
|
167615
|
-
return `${
|
|
168222
|
+
return `${channelDisplayName3(channelId)} agent routing is already active for this chat.`;
|
|
167616
168223
|
}
|
|
167617
168224
|
function buildChannelCancelUnavailableMessage(channelId) {
|
|
167618
|
-
const displayName =
|
|
168225
|
+
const displayName = channelDisplayName3(channelId);
|
|
167619
168226
|
return [
|
|
167620
168227
|
`${displayName} received /cancel, but this chat is not connected to an active Letta Code conversation yet.`,
|
|
167621
168228
|
"Send a normal message first to connect this chat to an agent."
|
|
@@ -167624,15 +168231,15 @@ function buildChannelCancelUnavailableMessage(channelId) {
|
|
|
167624
168231
|
`);
|
|
167625
168232
|
}
|
|
167626
168233
|
function buildChannelCancelNoActiveTurnMessage(channelId) {
|
|
167627
|
-
const displayName =
|
|
168234
|
+
const displayName = channelDisplayName3(channelId);
|
|
167628
168235
|
return `${displayName} received /cancel, but there is no in-progress agent turn to cancel for this chat.`;
|
|
167629
168236
|
}
|
|
167630
168237
|
function buildChannelCancelAcceptedMessage(channelId) {
|
|
167631
|
-
const displayName =
|
|
168238
|
+
const displayName = channelDisplayName3(channelId);
|
|
167632
168239
|
return `${displayName} cancelled the in-progress agent turn for this chat.`;
|
|
167633
168240
|
}
|
|
167634
168241
|
function buildChannelChatLinkMessage(channelId, route, chatUrl) {
|
|
167635
|
-
const displayName =
|
|
168242
|
+
const displayName = channelDisplayName3(channelId);
|
|
167636
168243
|
return [
|
|
167637
168244
|
`${displayName} chat for this route: ${chatUrl}`,
|
|
167638
168245
|
`Agent: ${route.agentId}.`,
|
|
@@ -167641,27 +168248,27 @@ function buildChannelChatLinkMessage(channelId, route, chatUrl) {
|
|
|
167641
168248
|
`);
|
|
167642
168249
|
}
|
|
167643
168250
|
function buildChannelChatUnavailableMessage(channelId, route) {
|
|
167644
|
-
const displayName =
|
|
168251
|
+
const displayName = channelDisplayName3(channelId);
|
|
167645
168252
|
return `${displayName} chat UI is not available for local backend agent ${route.agentId}.`;
|
|
167646
168253
|
}
|
|
167647
168254
|
function buildChannelDetachUnsupportedMessage(channelId) {
|
|
167648
|
-
const displayName =
|
|
168255
|
+
const displayName = channelDisplayName3(channelId);
|
|
167649
168256
|
return `${displayName} can only detach Slack channel threads.`;
|
|
167650
168257
|
}
|
|
167651
168258
|
function buildChannelDetachedMessage(channelId) {
|
|
167652
|
-
const displayName =
|
|
168259
|
+
const displayName = channelDisplayName3(channelId);
|
|
167653
168260
|
return `${displayName} detached this thread. I will ignore follow-up replies here until someone mentions the app again.`;
|
|
167654
168261
|
}
|
|
167655
168262
|
function buildChannelAlreadyDetachedMessage(channelId) {
|
|
167656
|
-
const displayName =
|
|
168263
|
+
const displayName = channelDisplayName3(channelId);
|
|
167657
168264
|
return `${displayName} is already detached from this thread. Mention the app again to reattach.`;
|
|
167658
168265
|
}
|
|
167659
168266
|
function buildChannelNewConversationMessage(channelId, route) {
|
|
167660
|
-
const displayName =
|
|
168267
|
+
const displayName = channelDisplayName3(channelId);
|
|
167661
168268
|
return `${displayName} started a new conversation for this chat. Conversation: ${route.conversationId}.`;
|
|
167662
168269
|
}
|
|
167663
168270
|
function buildChannelNewConversationUnavailableMessage(channelId) {
|
|
167664
|
-
const displayName =
|
|
168271
|
+
const displayName = channelDisplayName3(channelId);
|
|
167665
168272
|
return `${displayName} cannot start a new conversation for this chat because no agent is configured.`;
|
|
167666
168273
|
}
|
|
167667
168274
|
function getModelEntryRank(entry) {
|
|
@@ -167720,7 +168327,7 @@ function buildChannelModelNotFoundText(channelId) {
|
|
|
167720
168327
|
return `Model not found. Use ${modelCommandPrefix(channelId)} list to see available models.`;
|
|
167721
168328
|
}
|
|
167722
168329
|
function buildChannelCurrentModelMessage(channelId, params) {
|
|
167723
|
-
const displayName =
|
|
168330
|
+
const displayName = channelDisplayName3(channelId);
|
|
167724
168331
|
const scope = params.scope === "agent" ? "agent" : "conversation";
|
|
167725
168332
|
const handleText = params.modelHandle && params.modelHandle !== params.modelLabel ? ` (${params.modelHandle})` : "";
|
|
167726
168333
|
const switchCommand = modelCommandPrefix(channelId);
|
|
@@ -167748,7 +168355,7 @@ function appendModelEntrySection(lines, channelId, title, entries, limit3) {
|
|
|
167748
168355
|
}
|
|
167749
168356
|
}
|
|
167750
168357
|
function buildChannelModelListMessage(channelId, params) {
|
|
167751
|
-
const displayName =
|
|
168358
|
+
const displayName = channelDisplayName3(channelId);
|
|
167752
168359
|
const limit3 = params.limit ?? DEFAULT_CHANNEL_MODEL_LIST_LIMIT;
|
|
167753
168360
|
const entries = params.entries;
|
|
167754
168361
|
const byHandle = buildModelEntriesByHandle(entries);
|
|
@@ -167781,33 +168388,33 @@ function buildChannelModelListMessage(channelId, params) {
|
|
|
167781
168388
|
`);
|
|
167782
168389
|
}
|
|
167783
168390
|
function buildChannelModelListUnavailableMessage(channelId, error54) {
|
|
167784
|
-
const displayName =
|
|
168391
|
+
const displayName = channelDisplayName3(channelId);
|
|
167785
168392
|
return `${displayName} could not load the model list: ${error54}`;
|
|
167786
168393
|
}
|
|
167787
168394
|
function buildChannelCurrentModelUnavailableMessage(channelId, error54) {
|
|
167788
|
-
const displayName =
|
|
168395
|
+
const displayName = channelDisplayName3(channelId);
|
|
167789
168396
|
return `${displayName} could not load the current model: ${error54}`;
|
|
167790
168397
|
}
|
|
167791
168398
|
function buildChannelModelUpdatedMessage(channelId, params) {
|
|
167792
|
-
const displayName =
|
|
168399
|
+
const displayName = channelDisplayName3(channelId);
|
|
167793
168400
|
const scope = params.appliedTo === "agent" ? "agent" : "conversation";
|
|
167794
168401
|
const handleText = params.modelHandle === params.modelLabel ? "" : ` (${params.modelHandle})`;
|
|
167795
168402
|
return `${displayName} updated this ${scope}'s model to ${params.modelLabel}${handleText}.`;
|
|
167796
168403
|
}
|
|
167797
168404
|
function buildChannelModelUpdateFailedMessage(channelId, identifier2, error54) {
|
|
167798
|
-
const displayName =
|
|
168405
|
+
const displayName = channelDisplayName3(channelId);
|
|
167799
168406
|
return `${displayName} could not switch this chat's routed model to ${identifier2}: ${error54}`;
|
|
167800
168407
|
}
|
|
167801
168408
|
function buildChannelModelUnavailableMessage(channelId) {
|
|
167802
|
-
const displayName =
|
|
168409
|
+
const displayName = channelDisplayName3(channelId);
|
|
167803
168410
|
return `${displayName} cannot use /model because the listener is not ready yet. Try again in a moment.`;
|
|
167804
168411
|
}
|
|
167805
168412
|
function buildChannelReflectionUnavailableMessage(channelId) {
|
|
167806
|
-
const displayName =
|
|
168413
|
+
const displayName = channelDisplayName3(channelId);
|
|
167807
168414
|
return `${displayName} cannot start reflection for this chat because the listener is not ready yet. Try again in a moment.`;
|
|
167808
168415
|
}
|
|
167809
168416
|
function buildChannelReloadUnavailableMessage(channelId) {
|
|
167810
|
-
const displayName =
|
|
168417
|
+
const displayName = channelDisplayName3(channelId);
|
|
167811
168418
|
return `${displayName} cannot reload listener settings for this chat because the listener is not ready yet. Try again in a moment.`;
|
|
167812
168419
|
}
|
|
167813
168420
|
async function handleScopedCommand(params) {
|
|
@@ -167844,10 +168451,17 @@ async function tryHandleChannelSlashCommand(adapter, msg, options3 = {}) {
|
|
|
167844
168451
|
await adapter.sendDirectReply(msg.chatId, buildUnsupportedChannelCommandMessage(msg.channel, command), msg.threadId ? { replyToMessageId: msg.threadId } : undefined);
|
|
167845
168452
|
return true;
|
|
167846
168453
|
}
|
|
168454
|
+
const canonicalName = canonicalizeChannelCommandName(command.name);
|
|
168455
|
+
if (options3.commandGate && !canRunChannelCommand(options3.commandGate, canonicalName)) {
|
|
168456
|
+
await adapter.sendDirectReply(msg.chatId, buildChannelCommandDeniedMessage(msg.channel, canonicalName, options3.commandGate), msg.threadId ? { replyToMessageId: msg.threadId } : undefined);
|
|
168457
|
+
return true;
|
|
168458
|
+
}
|
|
167847
168459
|
const reply = normalizeDirectReplyPayload(await (async () => {
|
|
167848
168460
|
switch (command.name) {
|
|
167849
168461
|
case "help":
|
|
167850
168462
|
return buildChannelHelpMessage(msg.channel);
|
|
168463
|
+
case "whoami":
|
|
168464
|
+
return buildChannelWhoamiMessage(msg, options3.commandGate);
|
|
167851
168465
|
case "status":
|
|
167852
168466
|
return buildChannelStatusMessage(msg, options3.statusContext ?? {
|
|
167853
168467
|
adapterRunning: adapter.isRunning(),
|
|
@@ -167941,6 +168555,7 @@ async function tryHandleChannelSlashCommand(adapter, msg, options3 = {}) {
|
|
|
167941
168555
|
}
|
|
167942
168556
|
var CHANNEL_SLASH_COMMANDS, SLACK_MENTION_COMMAND_NAMES, SLACK_MENTION_SLASH_COMMAND_EXAMPLES, DEFAULT_CHANNEL_MODEL_LIST_LIMIT = 8;
|
|
167943
168557
|
var init_commands = __esm(() => {
|
|
168558
|
+
init_access_control();
|
|
167944
168559
|
init_feedback2();
|
|
167945
168560
|
init_plugin_registry();
|
|
167946
168561
|
CHANNEL_SLASH_COMMANDS = [
|
|
@@ -167954,6 +168569,11 @@ var init_commands = __esm(() => {
|
|
|
167954
168569
|
kind: "direct",
|
|
167955
168570
|
summary: "Show this chat's channel connection status."
|
|
167956
168571
|
},
|
|
168572
|
+
{
|
|
168573
|
+
name: "whoami",
|
|
168574
|
+
kind: "direct",
|
|
168575
|
+
summary: "Show your access tier and runnable commands here."
|
|
168576
|
+
},
|
|
167957
168577
|
{
|
|
167958
168578
|
name: "pause",
|
|
167959
168579
|
kind: "direct",
|
|
@@ -168001,6 +168621,7 @@ var init_commands = __esm(() => {
|
|
|
168001
168621
|
SLACK_MENTION_SLASH_COMMAND_EXAMPLES = [
|
|
168002
168622
|
"@agent /help",
|
|
168003
168623
|
"@agent /status",
|
|
168624
|
+
"@agent /whoami",
|
|
168004
168625
|
"@agent /model",
|
|
168005
168626
|
"@agent /model list",
|
|
168006
168627
|
"@agent /model <handle-or-id>",
|
|
@@ -168100,19 +168721,24 @@ Handle: ${escapeSlackMrkdwn(params.current.modelHandle)}` : "";
|
|
|
168100
168721
|
text: {
|
|
168101
168722
|
type: "mrkdwn",
|
|
168102
168723
|
text: "Choose a model for this routed conversation:"
|
|
168103
|
-
},
|
|
168104
|
-
accessory: {
|
|
168105
|
-
type: "static_select",
|
|
168106
|
-
action_id: SLACK_MODEL_SELECT_ACTION_ID,
|
|
168107
|
-
placeholder: {
|
|
168108
|
-
type: "plain_text",
|
|
168109
|
-
text: "Select a model",
|
|
168110
|
-
emoji: true
|
|
168111
|
-
},
|
|
168112
|
-
options: options3,
|
|
168113
|
-
...initialOption ? { initial_option: initialOption } : {}
|
|
168114
168724
|
}
|
|
168115
168725
|
},
|
|
168726
|
+
{
|
|
168727
|
+
type: "actions",
|
|
168728
|
+
elements: [
|
|
168729
|
+
{
|
|
168730
|
+
type: "static_select",
|
|
168731
|
+
action_id: SLACK_MODEL_SELECT_ACTION_ID,
|
|
168732
|
+
placeholder: {
|
|
168733
|
+
type: "plain_text",
|
|
168734
|
+
text: "Select a model",
|
|
168735
|
+
emoji: true
|
|
168736
|
+
},
|
|
168737
|
+
options: options3,
|
|
168738
|
+
...initialOption ? { initial_option: initialOption } : {}
|
|
168739
|
+
}
|
|
168740
|
+
]
|
|
168741
|
+
},
|
|
168116
168742
|
{
|
|
168117
168743
|
type: "context",
|
|
168118
168744
|
elements: [
|
|
@@ -170977,9 +171603,9 @@ __export(exports_process_manager, {
|
|
|
170977
171603
|
import {
|
|
170978
171604
|
appendFileSync as appendFileSync2,
|
|
170979
171605
|
chmodSync as chmodSync2,
|
|
170980
|
-
mkdirSync as
|
|
171606
|
+
mkdirSync as mkdirSync6,
|
|
170981
171607
|
mkdtempSync as mkdtempSync2,
|
|
170982
|
-
writeFileSync as
|
|
171608
|
+
writeFileSync as writeFileSync4
|
|
170983
171609
|
} from "node:fs";
|
|
170984
171610
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
170985
171611
|
import { join as join13 } from "node:path";
|
|
@@ -171138,7 +171764,7 @@ function __resetBackgroundOutputDirForTests() {
|
|
|
171138
171764
|
backgroundOutputDir = undefined;
|
|
171139
171765
|
}
|
|
171140
171766
|
function ensureBackgroundOutputDir(dir) {
|
|
171141
|
-
|
|
171767
|
+
mkdirSync6(dir, { recursive: true, mode: 448 });
|
|
171142
171768
|
if (!process.env.LETTA_SCRATCHPAD) {
|
|
171143
171769
|
chmodSync2(dir, 448);
|
|
171144
171770
|
}
|
|
@@ -171147,7 +171773,7 @@ function createBackgroundOutputFile(id2) {
|
|
|
171147
171773
|
const dir = getBackgroundOutputDir();
|
|
171148
171774
|
ensureBackgroundOutputDir(dir);
|
|
171149
171775
|
const filePath = join13(dir, `${id2}.log`);
|
|
171150
|
-
|
|
171776
|
+
writeFileSync4(filePath, "", { mode: 384 });
|
|
171151
171777
|
chmodSync2(filePath, 384);
|
|
171152
171778
|
return filePath;
|
|
171153
171779
|
}
|
|
@@ -171240,12 +171866,12 @@ var init_attachment_task = __esm(() => {
|
|
|
171240
171866
|
});
|
|
171241
171867
|
|
|
171242
171868
|
// src/channels/targets.ts
|
|
171243
|
-
import { existsSync as
|
|
171244
|
-
function
|
|
171245
|
-
let store =
|
|
171869
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync7, readFileSync as readFileSync8, writeFileSync as writeFileSync5 } from "node:fs";
|
|
171870
|
+
function getStore3(channelId) {
|
|
171871
|
+
let store = stores3.get(channelId);
|
|
171246
171872
|
if (!store) {
|
|
171247
171873
|
store = { targets: [] };
|
|
171248
|
-
|
|
171874
|
+
stores3.set(channelId, store);
|
|
171249
171875
|
}
|
|
171250
171876
|
return store;
|
|
171251
171877
|
}
|
|
@@ -171255,35 +171881,35 @@ function loadTargetStore(channelId) {
|
|
|
171255
171881
|
return;
|
|
171256
171882
|
}
|
|
171257
171883
|
const path5 = getChannelTargetsPath(channelId);
|
|
171258
|
-
if (!
|
|
171884
|
+
if (!existsSync9(path5)) {
|
|
171259
171885
|
return;
|
|
171260
171886
|
}
|
|
171261
171887
|
try {
|
|
171262
|
-
const text =
|
|
171888
|
+
const text = readFileSync8(path5, "utf-8");
|
|
171263
171889
|
const parsed = JSON.parse(text);
|
|
171264
|
-
|
|
171890
|
+
stores3.set(channelId, {
|
|
171265
171891
|
targets: parsed.targets ?? []
|
|
171266
171892
|
});
|
|
171267
171893
|
} catch {}
|
|
171268
171894
|
}
|
|
171269
171895
|
function saveTargetStore(channelId) {
|
|
171270
171896
|
if (saveTargetStoreOverride) {
|
|
171271
|
-
saveTargetStoreOverride(channelId,
|
|
171897
|
+
saveTargetStoreOverride(channelId, getStore3(channelId));
|
|
171272
171898
|
return;
|
|
171273
171899
|
}
|
|
171274
171900
|
const dir = getChannelDir(channelId);
|
|
171275
|
-
|
|
171276
|
-
|
|
171901
|
+
mkdirSync7(dir, { recursive: true });
|
|
171902
|
+
writeFileSync5(getChannelTargetsPath(channelId), `${JSON.stringify(getStore3(channelId), null, 2)}
|
|
171277
171903
|
`, "utf-8");
|
|
171278
171904
|
}
|
|
171279
171905
|
function listChannelTargets(channelId, accountId) {
|
|
171280
|
-
const normalizedAccountId = accountId === undefined ? undefined :
|
|
171281
|
-
return
|
|
171906
|
+
const normalizedAccountId = accountId === undefined ? undefined : normalizeAccountId2(accountId);
|
|
171907
|
+
return getStore3(channelId).targets.filter((target2) => normalizedAccountId === undefined || normalizeAccountId2(target2.accountId) === normalizedAccountId);
|
|
171282
171908
|
}
|
|
171283
171909
|
function upsertChannelTarget(channelId, target2) {
|
|
171284
|
-
const store =
|
|
171285
|
-
const normalizedAccountId =
|
|
171286
|
-
const existingIndex = store.targets.findIndex((candidate) => candidate.targetId === target2.targetId &&
|
|
171910
|
+
const store = getStore3(channelId);
|
|
171911
|
+
const normalizedAccountId = normalizeAccountId2(target2.accountId);
|
|
171912
|
+
const existingIndex = store.targets.findIndex((candidate) => candidate.targetId === target2.targetId && normalizeAccountId2(candidate.accountId) === normalizedAccountId);
|
|
171287
171913
|
if (existingIndex >= 0) {
|
|
171288
171914
|
const existing = store.targets[existingIndex];
|
|
171289
171915
|
if (!existing) {
|
|
@@ -171311,9 +171937,9 @@ function upsertChannelTarget(channelId, target2) {
|
|
|
171311
171937
|
};
|
|
171312
171938
|
}
|
|
171313
171939
|
function removeChannelTarget(channelId, targetId, accountId) {
|
|
171314
|
-
const store =
|
|
171315
|
-
const normalizedAccountId =
|
|
171316
|
-
const nextTargets = store.targets.filter((target2) => !(target2.targetId === targetId &&
|
|
171940
|
+
const store = getStore3(channelId);
|
|
171941
|
+
const normalizedAccountId = normalizeAccountId2(accountId);
|
|
171942
|
+
const nextTargets = store.targets.filter((target2) => !(target2.targetId === targetId && normalizeAccountId2(target2.accountId) === normalizedAccountId));
|
|
171317
171943
|
if (nextTargets.length === store.targets.length) {
|
|
171318
171944
|
return false;
|
|
171319
171945
|
}
|
|
@@ -171322,9 +171948,9 @@ function removeChannelTarget(channelId, targetId, accountId) {
|
|
|
171322
171948
|
return true;
|
|
171323
171949
|
}
|
|
171324
171950
|
function removeChannelTargetsForAccount(channelId, accountId) {
|
|
171325
|
-
const store =
|
|
171326
|
-
const normalizedAccountId =
|
|
171327
|
-
const nextTargets = store.targets.filter((target2) =>
|
|
171951
|
+
const store = getStore3(channelId);
|
|
171952
|
+
const normalizedAccountId = normalizeAccountId2(accountId);
|
|
171953
|
+
const nextTargets = store.targets.filter((target2) => normalizeAccountId2(target2.accountId) !== normalizedAccountId);
|
|
171328
171954
|
const removed = store.targets.length - nextTargets.length;
|
|
171329
171955
|
if (removed === 0) {
|
|
171330
171956
|
return 0;
|
|
@@ -171333,14 +171959,14 @@ function removeChannelTargetsForAccount(channelId, accountId) {
|
|
|
171333
171959
|
saveTargetStore(channelId);
|
|
171334
171960
|
return removed;
|
|
171335
171961
|
}
|
|
171336
|
-
function
|
|
171962
|
+
function normalizeAccountId2(accountId) {
|
|
171337
171963
|
return accountId ?? LEGACY_CHANNEL_ACCOUNT_ID;
|
|
171338
171964
|
}
|
|
171339
|
-
var
|
|
171965
|
+
var stores3, loadTargetStoreOverride = null, saveTargetStoreOverride = null;
|
|
171340
171966
|
var init_targets = __esm(() => {
|
|
171341
171967
|
init_accounts();
|
|
171342
171968
|
init_config2();
|
|
171343
|
-
|
|
171969
|
+
stores3 = new Map;
|
|
171344
171970
|
});
|
|
171345
171971
|
|
|
171346
171972
|
// src/channels/slack/target-resolution.ts
|
|
@@ -171933,12 +172559,12 @@ function resolveDiscordChannelMode(channelId, parentChannelId, isThread, allowed
|
|
|
171933
172559
|
|
|
171934
172560
|
// src/channels/discord/media.ts
|
|
171935
172561
|
import { randomUUID as randomUUID10 } from "node:crypto";
|
|
171936
|
-
import { mkdirSync as
|
|
172562
|
+
import { mkdirSync as mkdirSync8 } from "node:fs";
|
|
171937
172563
|
import { writeFile as writeFile6 } from "node:fs/promises";
|
|
171938
172564
|
import { tmpdir as tmpdir4 } from "node:os";
|
|
171939
172565
|
import { join as join14 } from "node:path";
|
|
171940
172566
|
function ensureAttachmentsDir() {
|
|
171941
|
-
|
|
172567
|
+
mkdirSync8(DISCORD_ATTACHMENTS_DIR, { recursive: true });
|
|
171942
172568
|
return DISCORD_ATTACHMENTS_DIR;
|
|
171943
172569
|
}
|
|
171944
172570
|
function sanitizeDiscordPathSegment(input) {
|
|
@@ -173183,107 +173809,6 @@ var init_plugin4 = __esm(() => {
|
|
|
173183
173809
|
};
|
|
173184
173810
|
});
|
|
173185
173811
|
|
|
173186
|
-
// src/channels/whatsapp/jid.ts
|
|
173187
|
-
function stripDeviceSuffix(jid) {
|
|
173188
|
-
if (!jid)
|
|
173189
|
-
return "";
|
|
173190
|
-
return jid.replace(/:\d+(@|$)/, "$1");
|
|
173191
|
-
}
|
|
173192
|
-
function isLidJid(jid) {
|
|
173193
|
-
return !!jid && stripDeviceSuffix(jid).endsWith(WHATSAPP_LID_SUFFIX);
|
|
173194
|
-
}
|
|
173195
|
-
function isGroupJid(jid) {
|
|
173196
|
-
return !!jid && stripDeviceSuffix(jid).endsWith(WHATSAPP_GROUP_SUFFIX);
|
|
173197
|
-
}
|
|
173198
|
-
function isStatusOrBroadcastJid(jid) {
|
|
173199
|
-
if (!jid)
|
|
173200
|
-
return true;
|
|
173201
|
-
const normalized = stripDeviceSuffix(jid);
|
|
173202
|
-
return normalized === "status@broadcast" || normalized.endsWith("@broadcast") || normalized.endsWith("@newsletter");
|
|
173203
|
-
}
|
|
173204
|
-
function jidToDigits(jid) {
|
|
173205
|
-
if (!jid)
|
|
173206
|
-
return "";
|
|
173207
|
-
const base2 = stripDeviceSuffix(jid).split("@")[0] ?? "";
|
|
173208
|
-
return base2.replace(/\D/g, "");
|
|
173209
|
-
}
|
|
173210
|
-
function normalizePhoneLike(value) {
|
|
173211
|
-
if (!value)
|
|
173212
|
-
return "";
|
|
173213
|
-
return jidToDigits(value.trim());
|
|
173214
|
-
}
|
|
173215
|
-
function phoneDigitsToJid(phoneDigits) {
|
|
173216
|
-
const digits = normalizePhoneLike(phoneDigits);
|
|
173217
|
-
return digits ? `${digits}${WHATSAPP_PHONE_SUFFIX}` : "";
|
|
173218
|
-
}
|
|
173219
|
-
function normalizeMaybePhoneJid(value) {
|
|
173220
|
-
if (!value)
|
|
173221
|
-
return null;
|
|
173222
|
-
const trimmed = value.trim();
|
|
173223
|
-
if (!trimmed)
|
|
173224
|
-
return null;
|
|
173225
|
-
if (isLidJid(trimmed))
|
|
173226
|
-
return null;
|
|
173227
|
-
if (trimmed.includes("@"))
|
|
173228
|
-
return stripDeviceSuffix(trimmed);
|
|
173229
|
-
return phoneDigitsToJid(trimmed) || null;
|
|
173230
|
-
}
|
|
173231
|
-
function isSelfChat(remoteJid, selfPhoneJid, selfLid) {
|
|
173232
|
-
const remote = stripDeviceSuffix(remoteJid);
|
|
173233
|
-
if (!remote)
|
|
173234
|
-
return false;
|
|
173235
|
-
const phone = stripDeviceSuffix(selfPhoneJid);
|
|
173236
|
-
if (phone && remote === phone)
|
|
173237
|
-
return true;
|
|
173238
|
-
const lid = stripDeviceSuffix(selfLid);
|
|
173239
|
-
if (lid && remote === lid)
|
|
173240
|
-
return true;
|
|
173241
|
-
return false;
|
|
173242
|
-
}
|
|
173243
|
-
function senderIdFromJid(jid) {
|
|
173244
|
-
return jidToDigits(jid);
|
|
173245
|
-
}
|
|
173246
|
-
function allowedUsersIncludes(allowedUsers, senderId) {
|
|
173247
|
-
const normalizedSender = normalizePhoneLike(senderId);
|
|
173248
|
-
return allowedUsers.some((entry) => normalizePhoneLike(entry) === normalizedSender);
|
|
173249
|
-
}
|
|
173250
|
-
function resolveLidToPhoneJid(params) {
|
|
173251
|
-
const { lidJid, message, sock } = params;
|
|
173252
|
-
if (!isLidJid(lidJid))
|
|
173253
|
-
return normalizeMaybePhoneJid(lidJid);
|
|
173254
|
-
const msg = message;
|
|
173255
|
-
const senderPn = normalizeMaybePhoneJid(msg?.key?.senderPn ?? undefined);
|
|
173256
|
-
if (senderPn)
|
|
173257
|
-
return senderPn;
|
|
173258
|
-
const repo = sock?.signalRepository;
|
|
173259
|
-
const mapped3 = normalizeMaybePhoneJid(repo?.lidMapping?.get(stripDeviceSuffix(lidJid)));
|
|
173260
|
-
if (mapped3)
|
|
173261
|
-
return mapped3;
|
|
173262
|
-
return null;
|
|
173263
|
-
}
|
|
173264
|
-
function resolveSendJid(params) {
|
|
173265
|
-
const { chatId, selfPhoneJid, selfLid, lidToJid, sock } = params;
|
|
173266
|
-
if (!isLidJid(chatId))
|
|
173267
|
-
return stripDeviceSuffix(chatId);
|
|
173268
|
-
const normalized = stripDeviceSuffix(chatId);
|
|
173269
|
-
if (selfLid && normalized === stripDeviceSuffix(selfLid) && selfPhoneJid) {
|
|
173270
|
-
return stripDeviceSuffix(selfPhoneJid);
|
|
173271
|
-
}
|
|
173272
|
-
const mapped3 = normalizeMaybePhoneJid(lidToJid?.get(normalized));
|
|
173273
|
-
if (mapped3)
|
|
173274
|
-
return mapped3;
|
|
173275
|
-
const repo = sock?.signalRepository;
|
|
173276
|
-
const signalMapped = normalizeMaybePhoneJid(repo?.lidMapping?.get(normalized));
|
|
173277
|
-
if (signalMapped)
|
|
173278
|
-
return signalMapped;
|
|
173279
|
-
throw new Error(`Cannot send to unresolved WhatsApp LID: ${chatId}`);
|
|
173280
|
-
}
|
|
173281
|
-
function sanitizePathSegment(input) {
|
|
173282
|
-
const cleaned = input.replace(/[^A-Za-z0-9._-]/g, "_").replace(/^_+|_+$/g, "");
|
|
173283
|
-
return cleaned || "whatsapp";
|
|
173284
|
-
}
|
|
173285
|
-
var WHATSAPP_PHONE_SUFFIX = "@s.whatsapp.net", WHATSAPP_LID_SUFFIX = "@lid", WHATSAPP_GROUP_SUFFIX = "@g.us";
|
|
173286
|
-
|
|
173287
173812
|
// src/channels/whatsapp/media.ts
|
|
173288
173813
|
import { randomUUID as randomUUID12 } from "node:crypto";
|
|
173289
173814
|
import { mkdir as mkdir5, writeFile as writeFile7 } from "node:fs/promises";
|
|
@@ -173612,7 +174137,7 @@ var init_state = __esm(() => {
|
|
|
173612
174137
|
});
|
|
173613
174138
|
|
|
173614
174139
|
// src/channels/whatsapp/session.ts
|
|
173615
|
-
import { mkdirSync as
|
|
174140
|
+
import { mkdirSync as mkdirSync9, readFileSync as readFileSync9, rmSync as rmSync2, writeFileSync as writeFileSync6 } from "node:fs";
|
|
173616
174141
|
import { homedir as homedir6 } from "node:os";
|
|
173617
174142
|
import { join as join16 } from "node:path";
|
|
173618
174143
|
function shouldDropLine(line) {
|
|
@@ -173686,7 +174211,7 @@ function defaultIsProcessAlive(pid) {
|
|
|
173686
174211
|
}
|
|
173687
174212
|
function readLeaseOwner(lockDir) {
|
|
173688
174213
|
try {
|
|
173689
|
-
const owner = JSON.parse(
|
|
174214
|
+
const owner = JSON.parse(readFileSync9(join16(lockDir, "owner.json"), "utf8"));
|
|
173690
174215
|
return {
|
|
173691
174216
|
pid: typeof owner.pid === "number" ? owner.pid : undefined,
|
|
173692
174217
|
command: typeof owner.command === "string" ? owner.command : undefined
|
|
@@ -173705,8 +174230,8 @@ function acquireWhatsAppSessionLease(accountId, options3 = {}) {
|
|
|
173705
174230
|
}
|
|
173706
174231
|
for (let attempt = 0;attempt < 2; attempt += 1) {
|
|
173707
174232
|
try {
|
|
173708
|
-
|
|
173709
|
-
|
|
174233
|
+
mkdirSync9(lockDir);
|
|
174234
|
+
writeFileSync6(join16(lockDir, "owner.json"), `${JSON.stringify({
|
|
173710
174235
|
accountId,
|
|
173711
174236
|
pid,
|
|
173712
174237
|
command: process.argv.join(" "),
|
|
@@ -173784,7 +174309,7 @@ function renderQrTerminal(qrMod, input) {
|
|
|
173784
174309
|
async function createWhatsAppSocket(params) {
|
|
173785
174310
|
installWhatsAppConsoleFilters();
|
|
173786
174311
|
const authDir = getWhatsAppAuthDir(params.accountId);
|
|
173787
|
-
|
|
174312
|
+
mkdirSync9(authDir, { recursive: true });
|
|
173788
174313
|
const sessionLease = acquireWhatsAppSessionLease(params.accountId);
|
|
173789
174314
|
setWhatsAppConnectionState(params.accountId, { status: "connecting" });
|
|
173790
174315
|
try {
|
|
@@ -174584,126 +175109,6 @@ var init_plugin5 = __esm(() => {
|
|
|
174584
175109
|
};
|
|
174585
175110
|
});
|
|
174586
175111
|
|
|
174587
|
-
// src/channels/signal/target.ts
|
|
174588
|
-
function assertSignalHttpProtocol(url2) {
|
|
174589
|
-
if (url2.protocol !== "http:" && url2.protocol !== "https:") {
|
|
174590
|
-
throw new Error(`Signal base URL protocol must be http or https, got ${url2.protocol}`);
|
|
174591
|
-
}
|
|
174592
|
-
}
|
|
174593
|
-
function trimPrefix(value, prefix) {
|
|
174594
|
-
if (!value.toLowerCase().startsWith(prefix)) {
|
|
174595
|
-
return null;
|
|
174596
|
-
}
|
|
174597
|
-
return value.slice(prefix.length).trim();
|
|
174598
|
-
}
|
|
174599
|
-
function normalizeSignalBaseUrl(input) {
|
|
174600
|
-
const trimmed = input.trim();
|
|
174601
|
-
if (!trimmed) {
|
|
174602
|
-
return "";
|
|
174603
|
-
}
|
|
174604
|
-
const hasScheme = /^[a-z][a-z0-9+.-]*:\/\//i.test(trimmed);
|
|
174605
|
-
const withScheme = hasScheme ? trimmed : `http://${trimmed}`;
|
|
174606
|
-
const parsed = new URL(withScheme);
|
|
174607
|
-
assertSignalHttpProtocol(parsed);
|
|
174608
|
-
if (parsed.username || parsed.password) {
|
|
174609
|
-
throw new Error("Signal base URL must not include credentials.");
|
|
174610
|
-
}
|
|
174611
|
-
return withScheme.replace(/\/+$/, "");
|
|
174612
|
-
}
|
|
174613
|
-
function parseSignalTarget(input) {
|
|
174614
|
-
const trimmed = input.trim();
|
|
174615
|
-
if (!trimmed) {
|
|
174616
|
-
throw new Error("Signal target is required.");
|
|
174617
|
-
}
|
|
174618
|
-
const signalTarget = trimPrefix(trimmed, "signal:");
|
|
174619
|
-
const value = signalTarget !== null ? signalTarget : trimmed;
|
|
174620
|
-
if (!value) {
|
|
174621
|
-
throw new Error("Signal target is required.");
|
|
174622
|
-
}
|
|
174623
|
-
const groupId = trimPrefix(value, "group:");
|
|
174624
|
-
if (groupId) {
|
|
174625
|
-
return { kind: "group", groupId };
|
|
174626
|
-
}
|
|
174627
|
-
const username = trimPrefix(value, "username:");
|
|
174628
|
-
if (username) {
|
|
174629
|
-
return { kind: "username", username };
|
|
174630
|
-
}
|
|
174631
|
-
const usernameAlias = trimPrefix(value, "u:");
|
|
174632
|
-
if (usernameAlias) {
|
|
174633
|
-
return { kind: "username", username: `u:${usernameAlias}` };
|
|
174634
|
-
}
|
|
174635
|
-
const recipient = value;
|
|
174636
|
-
if (!recipient) {
|
|
174637
|
-
throw new Error("Signal recipient is required.");
|
|
174638
|
-
}
|
|
174639
|
-
return { kind: "recipient", recipient };
|
|
174640
|
-
}
|
|
174641
|
-
function signalTargetToSendRpcParams(target2) {
|
|
174642
|
-
switch (target2.kind) {
|
|
174643
|
-
case "group":
|
|
174644
|
-
return { groupId: target2.groupId };
|
|
174645
|
-
case "username":
|
|
174646
|
-
return { username: [target2.username] };
|
|
174647
|
-
case "recipient":
|
|
174648
|
-
return { recipient: [target2.recipient] };
|
|
174649
|
-
}
|
|
174650
|
-
}
|
|
174651
|
-
function signalTargetToReactionRpcParams(target2) {
|
|
174652
|
-
switch (target2.kind) {
|
|
174653
|
-
case "group":
|
|
174654
|
-
return { groupIds: [target2.groupId] };
|
|
174655
|
-
case "recipient":
|
|
174656
|
-
return { recipients: [target2.recipient] };
|
|
174657
|
-
case "username":
|
|
174658
|
-
throw new Error("Signal reactions require a recipient or group target.");
|
|
174659
|
-
}
|
|
174660
|
-
}
|
|
174661
|
-
function normalizeSignalSenderId(value) {
|
|
174662
|
-
if (!value) {
|
|
174663
|
-
return "";
|
|
174664
|
-
}
|
|
174665
|
-
const trimmed = value.trim();
|
|
174666
|
-
if (!trimmed) {
|
|
174667
|
-
return "";
|
|
174668
|
-
}
|
|
174669
|
-
return trimmed.toLowerCase();
|
|
174670
|
-
}
|
|
174671
|
-
function normalizeSignalPhone(value) {
|
|
174672
|
-
if (!value) {
|
|
174673
|
-
return "";
|
|
174674
|
-
}
|
|
174675
|
-
const trimmed = value.trim();
|
|
174676
|
-
if (!trimmed) {
|
|
174677
|
-
return "";
|
|
174678
|
-
}
|
|
174679
|
-
const withoutPrefix = trimmed.toLowerCase().startsWith("signal:") ? trimmed.slice("signal:".length) : trimmed;
|
|
174680
|
-
return withoutPrefix.replace(/[^0-9+]/g, "");
|
|
174681
|
-
}
|
|
174682
|
-
function signalAllowedUsersIncludes(allowedUsers, senderId) {
|
|
174683
|
-
const normalizedSender = normalizeSignalSenderId(senderId);
|
|
174684
|
-
const senderPhone = normalizeSignalPhone(senderId);
|
|
174685
|
-
return allowedUsers.some((entry) => {
|
|
174686
|
-
const normalizedEntry = normalizeSignalSenderId(entry);
|
|
174687
|
-
if (normalizedEntry === normalizedSender) {
|
|
174688
|
-
return true;
|
|
174689
|
-
}
|
|
174690
|
-
const entryPhone = normalizeSignalPhone(entry);
|
|
174691
|
-
return !!senderPhone && !!entryPhone && senderPhone === entryPhone;
|
|
174692
|
-
});
|
|
174693
|
-
}
|
|
174694
|
-
function isSignalGroupAllowed(allowedGroups, groupId) {
|
|
174695
|
-
if (!allowedGroups || allowedGroups.length === 0) {
|
|
174696
|
-
return true;
|
|
174697
|
-
}
|
|
174698
|
-
const normalized = groupId.trim();
|
|
174699
|
-
return allowedGroups.some((entry) => entry.trim() === normalized);
|
|
174700
|
-
}
|
|
174701
|
-
function matchesSignalMentionPatterns(text, mentionPatterns) {
|
|
174702
|
-
const normalizedText = text.toLowerCase();
|
|
174703
|
-
return (mentionPatterns ?? []).map((entry) => entry.trim().toLowerCase()).filter(Boolean).some((entry) => normalizedText.includes(entry));
|
|
174704
|
-
}
|
|
174705
|
-
var init_target2 = () => {};
|
|
174706
|
-
|
|
174707
175112
|
// src/channels/signal/client.ts
|
|
174708
175113
|
import { randomUUID as randomUUID14 } from "node:crypto";
|
|
174709
175114
|
import { request as httpRequest } from "node:http";
|
|
@@ -175054,9 +175459,9 @@ var init_client5 = __esm(() => {
|
|
|
175054
175459
|
import { randomUUID as randomUUID15 } from "node:crypto";
|
|
175055
175460
|
import {
|
|
175056
175461
|
copyFileSync,
|
|
175057
|
-
mkdirSync as
|
|
175462
|
+
mkdirSync as mkdirSync10,
|
|
175058
175463
|
readdirSync as readdirSync2,
|
|
175059
|
-
readFileSync as
|
|
175464
|
+
readFileSync as readFileSync10,
|
|
175060
175465
|
realpathSync as realpathSync2,
|
|
175061
175466
|
statSync as statSync3
|
|
175062
175467
|
} from "node:fs";
|
|
@@ -175334,7 +175739,7 @@ function copySignalAttachment(params) {
|
|
|
175334
175739
|
const mimeType = normalizeSignalMimeType(params.attachment.contentType) ?? inferSignalMimeTypeFromName(fileName);
|
|
175335
175740
|
const kind = inferSignalAttachmentKind({ mimeType, fileName });
|
|
175336
175741
|
const inboundDir = join17(getChannelDir("signal"), "inbound", sanitizeSignalPathSegment(params.accountId));
|
|
175337
|
-
|
|
175742
|
+
mkdirSync10(inboundDir, { recursive: true });
|
|
175338
175743
|
const localPath = join17(inboundDir, `${Date.now()}-${randomUUID15()}-${sanitizeSignalPathSegment(fileName)}`);
|
|
175339
175744
|
copyFileSync(params.sourcePath, localPath);
|
|
175340
175745
|
const attachment = {
|
|
@@ -175346,7 +175751,7 @@ function copySignalAttachment(params) {
|
|
|
175346
175751
|
localPath
|
|
175347
175752
|
};
|
|
175348
175753
|
if (kind === "image" && sizeBytes <= MAX_SIGNAL_INLINE_IMAGE_BYTES) {
|
|
175349
|
-
attachment.imageDataBase64 =
|
|
175754
|
+
attachment.imageDataBase64 = readFileSync10(localPath).toString("base64");
|
|
175350
175755
|
}
|
|
175351
175756
|
return attachment;
|
|
175352
175757
|
}
|
|
@@ -176034,7 +176439,7 @@ var init_runtime5 = __esm(() => {
|
|
|
176034
176439
|
|
|
176035
176440
|
// src/channels/signal/setup-runtime.ts
|
|
176036
176441
|
import { execFileSync as execFileSync2, spawn as spawn2 } from "node:child_process";
|
|
176037
|
-
import { existsSync as
|
|
176442
|
+
import { existsSync as existsSync10 } from "node:fs";
|
|
176038
176443
|
function getSignalDockerRunCommand() {
|
|
176039
176444
|
return [
|
|
176040
176445
|
"docker run -d",
|
|
@@ -176076,7 +176481,7 @@ function detectNativeSignalCliConfigDir() {
|
|
|
176076
176481
|
}
|
|
176077
176482
|
} catch {}
|
|
176078
176483
|
const defaultDir = getDefaultSignalCliConfigDir();
|
|
176079
|
-
return defaultDir &&
|
|
176484
|
+
return defaultDir && existsSync10(defaultDir) ? defaultDir : null;
|
|
176080
176485
|
}
|
|
176081
176486
|
function runNativeSignalCli(args) {
|
|
176082
176487
|
try {
|
|
@@ -176842,7 +177247,7 @@ __export(exports_plugin_registry, {
|
|
|
176842
177247
|
getChannelDisplayName: () => getChannelDisplayName,
|
|
176843
177248
|
__testClearUserChannelPluginCache: () => __testClearUserChannelPluginCache
|
|
176844
177249
|
});
|
|
176845
|
-
import { existsSync as
|
|
177250
|
+
import { existsSync as existsSync11, readdirSync as readdirSync3, readFileSync as readFileSync11 } from "node:fs";
|
|
176846
177251
|
import { resolve as resolve4, sep } from "node:path";
|
|
176847
177252
|
import { pathToFileURL as pathToFileURL2 } from "node:url";
|
|
176848
177253
|
function isValidChannelId(value) {
|
|
@@ -176853,11 +177258,11 @@ function readStringArray(value) {
|
|
|
176853
177258
|
}
|
|
176854
177259
|
function readChannelManifest(channelDir) {
|
|
176855
177260
|
const manifestPath = resolve4(channelDir, "channel.json");
|
|
176856
|
-
if (!
|
|
177261
|
+
if (!existsSync11(manifestPath)) {
|
|
176857
177262
|
return null;
|
|
176858
177263
|
}
|
|
176859
177264
|
try {
|
|
176860
|
-
const parsed = JSON.parse(
|
|
177265
|
+
const parsed = JSON.parse(readFileSync11(manifestPath, "utf-8"));
|
|
176861
177266
|
if (!isRecord(parsed)) {
|
|
176862
177267
|
return null;
|
|
176863
177268
|
}
|
|
@@ -176933,7 +177338,7 @@ function createUserChannelRegistration(manifest) {
|
|
|
176933
177338
|
function discoverUserChannelRegistrations() {
|
|
176934
177339
|
const registrations = new Map;
|
|
176935
177340
|
const channelsRoot = getChannelsRoot();
|
|
176936
|
-
if (!
|
|
177341
|
+
if (!existsSync11(channelsRoot)) {
|
|
176937
177342
|
return registrations;
|
|
176938
177343
|
}
|
|
176939
177344
|
let entries;
|
|
@@ -177096,186 +177501,6 @@ var init_plugin_registry = __esm(() => {
|
|
|
177096
177501
|
loadedUserPlugins = new Map;
|
|
177097
177502
|
});
|
|
177098
177503
|
|
|
177099
|
-
// src/channels/pairing.ts
|
|
177100
|
-
var exports_pairing = {};
|
|
177101
|
-
__export(exports_pairing, {
|
|
177102
|
-
rollbackPairingApproval: () => rollbackPairingApproval,
|
|
177103
|
-
removePairingStateForAccount: () => removePairingStateForAccount,
|
|
177104
|
-
loadPairingStore: () => loadPairingStore,
|
|
177105
|
-
isUserApproved: () => isUserApproved,
|
|
177106
|
-
getPendingPairings: () => getPendingPairings,
|
|
177107
|
-
getApprovedUsers: () => getApprovedUsers,
|
|
177108
|
-
createPairingCode: () => createPairingCode,
|
|
177109
|
-
consumePairingCode: () => consumePairingCode,
|
|
177110
|
-
clearPairingStores: () => clearPairingStores,
|
|
177111
|
-
__testOverrideSavePairingStore: () => __testOverrideSavePairingStore,
|
|
177112
|
-
__testOverrideLoadPairingStore: () => __testOverrideLoadPairingStore
|
|
177113
|
-
});
|
|
177114
|
-
import { existsSync as existsSync11, mkdirSync as mkdirSync10, readFileSync as readFileSync11, writeFileSync as writeFileSync6 } from "node:fs";
|
|
177115
|
-
function normalizeAccountId2(accountId) {
|
|
177116
|
-
return accountId ?? LEGACY_CHANNEL_ACCOUNT_ID;
|
|
177117
|
-
}
|
|
177118
|
-
function getStore3(channelId) {
|
|
177119
|
-
let store = stores3.get(channelId);
|
|
177120
|
-
if (!store) {
|
|
177121
|
-
store = { pending: [], approved: [] };
|
|
177122
|
-
stores3.set(channelId, store);
|
|
177123
|
-
}
|
|
177124
|
-
return store;
|
|
177125
|
-
}
|
|
177126
|
-
function loadPairingStore(channelId) {
|
|
177127
|
-
if (loadPairingStoreOverride) {
|
|
177128
|
-
const overridden = loadPairingStoreOverride(channelId);
|
|
177129
|
-
if (overridden === null) {
|
|
177130
|
-
return;
|
|
177131
|
-
}
|
|
177132
|
-
stores3.set(channelId, {
|
|
177133
|
-
pending: [...overridden.pending],
|
|
177134
|
-
approved: [...overridden.approved]
|
|
177135
|
-
});
|
|
177136
|
-
return;
|
|
177137
|
-
}
|
|
177138
|
-
const path5 = getChannelPairingPath(channelId);
|
|
177139
|
-
if (!existsSync11(path5))
|
|
177140
|
-
return;
|
|
177141
|
-
try {
|
|
177142
|
-
const text = readFileSync11(path5, "utf-8");
|
|
177143
|
-
const parsed = JSON.parse(text);
|
|
177144
|
-
stores3.set(channelId, {
|
|
177145
|
-
pending: parsed.pending ?? [],
|
|
177146
|
-
approved: parsed.approved ?? []
|
|
177147
|
-
});
|
|
177148
|
-
} catch {}
|
|
177149
|
-
}
|
|
177150
|
-
function savePairingStore(channelId) {
|
|
177151
|
-
const store = getStore3(channelId);
|
|
177152
|
-
if (savePairingStoreOverride) {
|
|
177153
|
-
savePairingStoreOverride(channelId, {
|
|
177154
|
-
pending: [...store.pending],
|
|
177155
|
-
approved: [...store.approved]
|
|
177156
|
-
});
|
|
177157
|
-
return;
|
|
177158
|
-
}
|
|
177159
|
-
const dir = getChannelDir(channelId);
|
|
177160
|
-
mkdirSync10(dir, { recursive: true });
|
|
177161
|
-
writeFileSync6(getChannelPairingPath(channelId), `${JSON.stringify(store, null, 2)}
|
|
177162
|
-
`, "utf-8");
|
|
177163
|
-
}
|
|
177164
|
-
function generateCode(length = 6) {
|
|
177165
|
-
let code2 = "";
|
|
177166
|
-
for (let i2 = 0;i2 < length; i2++) {
|
|
177167
|
-
code2 += CODE_CHARS[Math.floor(Math.random() * CODE_CHARS.length)];
|
|
177168
|
-
}
|
|
177169
|
-
return code2;
|
|
177170
|
-
}
|
|
177171
|
-
function isUserApproved(channelId, userId, accountId) {
|
|
177172
|
-
const store = getStore3(channelId);
|
|
177173
|
-
const normalizedAccountId = normalizeAccountId2(accountId);
|
|
177174
|
-
return store.approved.some((u) => u.senderId === userId && normalizeAccountId2(u.accountId) === normalizedAccountId);
|
|
177175
|
-
}
|
|
177176
|
-
function createPairingCode(channelId, userId, chatId, username, accountId) {
|
|
177177
|
-
const store = getStore3(channelId);
|
|
177178
|
-
const normalizedAccountId = normalizeAccountId2(accountId);
|
|
177179
|
-
store.pending = store.pending.filter((p) => !(p.senderId === userId && normalizeAccountId2(p.accountId) === normalizedAccountId));
|
|
177180
|
-
const now = Date.now();
|
|
177181
|
-
store.pending = store.pending.filter((p) => new Date(p.expiresAt).getTime() > now);
|
|
177182
|
-
while (store.pending.length >= MAX_PENDING_CODES) {
|
|
177183
|
-
store.pending.shift();
|
|
177184
|
-
}
|
|
177185
|
-
const code2 = generateCode();
|
|
177186
|
-
const pending = {
|
|
177187
|
-
accountId: normalizedAccountId,
|
|
177188
|
-
code: code2,
|
|
177189
|
-
senderId: userId,
|
|
177190
|
-
senderName: username,
|
|
177191
|
-
chatId,
|
|
177192
|
-
createdAt: new Date().toISOString(),
|
|
177193
|
-
expiresAt: new Date(now + PAIRING_CODE_TTL_MS).toISOString()
|
|
177194
|
-
};
|
|
177195
|
-
store.pending.push(pending);
|
|
177196
|
-
savePairingStore(channelId);
|
|
177197
|
-
return code2;
|
|
177198
|
-
}
|
|
177199
|
-
function consumePairingCode(channelId, code2, accountId) {
|
|
177200
|
-
const store = getStore3(channelId);
|
|
177201
|
-
const upperCode = code2.toUpperCase();
|
|
177202
|
-
const normalizedAccountId = accountId === undefined ? undefined : normalizeAccountId2(accountId);
|
|
177203
|
-
const matches = store.pending.map((pending2, index2) => ({ pending: pending2, index: index2 })).filter(({ pending: pending2 }) => pending2.code === upperCode && (normalizedAccountId === undefined || normalizeAccountId2(pending2.accountId) === normalizedAccountId));
|
|
177204
|
-
if (matches.length > 1) {
|
|
177205
|
-
return null;
|
|
177206
|
-
}
|
|
177207
|
-
const index = matches[0]?.index ?? -1;
|
|
177208
|
-
if (index === -1)
|
|
177209
|
-
return null;
|
|
177210
|
-
const pending = store.pending[index];
|
|
177211
|
-
const pendingAccountId = normalizeAccountId2(pending.accountId);
|
|
177212
|
-
if (new Date(pending.expiresAt).getTime() < Date.now()) {
|
|
177213
|
-
store.pending.splice(index, 1);
|
|
177214
|
-
savePairingStore(channelId);
|
|
177215
|
-
return null;
|
|
177216
|
-
}
|
|
177217
|
-
store.pending.splice(index, 1);
|
|
177218
|
-
if (!store.approved.some((u) => u.senderId === pending.senderId && normalizeAccountId2(u.accountId) === pendingAccountId)) {
|
|
177219
|
-
const approved = {
|
|
177220
|
-
accountId: pendingAccountId,
|
|
177221
|
-
senderId: pending.senderId,
|
|
177222
|
-
senderName: pending.senderName,
|
|
177223
|
-
approvedAt: new Date().toISOString()
|
|
177224
|
-
};
|
|
177225
|
-
store.approved.push(approved);
|
|
177226
|
-
}
|
|
177227
|
-
savePairingStore(channelId);
|
|
177228
|
-
return pending;
|
|
177229
|
-
}
|
|
177230
|
-
function getPendingPairings(channelId, accountId) {
|
|
177231
|
-
const store = getStore3(channelId);
|
|
177232
|
-
const now = Date.now();
|
|
177233
|
-
const normalizedAccountId = accountId === undefined ? undefined : normalizeAccountId2(accountId);
|
|
177234
|
-
return store.pending.filter((p) => new Date(p.expiresAt).getTime() > now && (normalizedAccountId === undefined || normalizeAccountId2(p.accountId) === normalizedAccountId));
|
|
177235
|
-
}
|
|
177236
|
-
function getApprovedUsers(channelId, accountId) {
|
|
177237
|
-
const normalizedAccountId = accountId === undefined ? undefined : normalizeAccountId2(accountId);
|
|
177238
|
-
return getStore3(channelId).approved.filter((user) => normalizedAccountId === undefined || normalizeAccountId2(user.accountId) === normalizedAccountId);
|
|
177239
|
-
}
|
|
177240
|
-
function rollbackPairingApproval(channelId, pending) {
|
|
177241
|
-
const store = getStore3(channelId);
|
|
177242
|
-
const normalizedAccountId = normalizeAccountId2(pending.accountId);
|
|
177243
|
-
store.approved = store.approved.filter((u) => !(u.senderId === pending.senderId && normalizeAccountId2(u.accountId) === normalizedAccountId));
|
|
177244
|
-
store.pending.push(pending);
|
|
177245
|
-
savePairingStore(channelId);
|
|
177246
|
-
}
|
|
177247
|
-
function removePairingStateForAccount(channelId, accountId) {
|
|
177248
|
-
const store = getStore3(channelId);
|
|
177249
|
-
const normalizedAccountId = normalizeAccountId2(accountId);
|
|
177250
|
-
const nextPending = store.pending.filter((pending) => normalizeAccountId2(pending.accountId) !== normalizedAccountId);
|
|
177251
|
-
const nextApproved = store.approved.filter((approved) => normalizeAccountId2(approved.accountId) !== normalizedAccountId);
|
|
177252
|
-
const pendingRemoved = store.pending.length - nextPending.length;
|
|
177253
|
-
const approvedRemoved = store.approved.length - nextApproved.length;
|
|
177254
|
-
if (pendingRemoved === 0 && approvedRemoved === 0) {
|
|
177255
|
-
return { pendingRemoved, approvedRemoved };
|
|
177256
|
-
}
|
|
177257
|
-
store.pending = nextPending;
|
|
177258
|
-
store.approved = nextApproved;
|
|
177259
|
-
savePairingStore(channelId);
|
|
177260
|
-
return { pendingRemoved, approvedRemoved };
|
|
177261
|
-
}
|
|
177262
|
-
function clearPairingStores() {
|
|
177263
|
-
stores3.clear();
|
|
177264
|
-
}
|
|
177265
|
-
function __testOverrideLoadPairingStore(fn) {
|
|
177266
|
-
loadPairingStoreOverride = fn;
|
|
177267
|
-
}
|
|
177268
|
-
function __testOverrideSavePairingStore(fn) {
|
|
177269
|
-
savePairingStoreOverride = fn;
|
|
177270
|
-
}
|
|
177271
|
-
var PAIRING_CODE_TTL_MS, MAX_PENDING_CODES = 50, CODE_CHARS = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789", stores3, loadPairingStoreOverride = null, savePairingStoreOverride = null;
|
|
177272
|
-
var init_pairing = __esm(() => {
|
|
177273
|
-
init_accounts();
|
|
177274
|
-
init_config2();
|
|
177275
|
-
PAIRING_CODE_TTL_MS = 15 * 60 * 1000;
|
|
177276
|
-
stores3 = new Map;
|
|
177277
|
-
});
|
|
177278
|
-
|
|
177279
177504
|
// src/cli/helpers/app-urls.ts
|
|
177280
177505
|
function isLocalAgentId2(agentId) {
|
|
177281
177506
|
return isLocalAgentId(agentId);
|
|
@@ -177320,7 +177545,7 @@ var init_app_urls = __esm(() => {
|
|
|
177320
177545
|
});
|
|
177321
177546
|
|
|
177322
177547
|
// src/channels/registry-presentation.ts
|
|
177323
|
-
function
|
|
177548
|
+
function channelDisplayName4(channelId) {
|
|
177324
177549
|
try {
|
|
177325
177550
|
return getChannelDisplayName(channelId);
|
|
177326
177551
|
} catch {
|
|
@@ -177338,7 +177563,7 @@ function getConfiguredAgentId(config3) {
|
|
|
177338
177563
|
return normalizeAgentId(source2.agentId) ?? normalizeAgentId(source2.binding?.agentId);
|
|
177339
177564
|
}
|
|
177340
177565
|
function buildPairingInstructions(channelId, code2, options3 = {}) {
|
|
177341
|
-
const displayName =
|
|
177566
|
+
const displayName = channelDisplayName4(channelId);
|
|
177342
177567
|
const configuredAgentId = normalizeAgentId(options3.agentId);
|
|
177343
177568
|
const pairingCommand = `letta channels pair --channel ${channelId} --code ${code2} --agent ${configuredAgentId ?? "<agent-id>"}`;
|
|
177344
177569
|
const agentLookupLines = configuredAgentId ? [] : ["Find the target agent with: letta agents list"];
|
|
@@ -177372,7 +177597,7 @@ function buildPairingInstructions(channelId, code2, options3 = {}) {
|
|
|
177372
177597
|
`);
|
|
177373
177598
|
}
|
|
177374
177599
|
function buildUnboundRouteInstructions(channelId, chatId) {
|
|
177375
|
-
const displayName =
|
|
177600
|
+
const displayName = channelDisplayName4(channelId);
|
|
177376
177601
|
if (!isFirstPartyChannelPlugin(channelId)) {
|
|
177377
177602
|
return `This chat isn't connected to a Letta agent yet.
|
|
177378
177603
|
|
|
@@ -178220,6 +178445,9 @@ class ChannelControlRequests {
|
|
|
178220
178445
|
this.requestIdByScope.delete(scopeKey);
|
|
178221
178446
|
return false;
|
|
178222
178447
|
}
|
|
178448
|
+
if (pending.event.source.senderId && pending.event.source.senderId !== msg.senderId) {
|
|
178449
|
+
return false;
|
|
178450
|
+
}
|
|
178223
178451
|
if (msg.channel === "slack" && pending.event.kind === "generic_tool_approval") {
|
|
178224
178452
|
return false;
|
|
178225
178453
|
}
|
|
@@ -178789,10 +179017,27 @@ function createChannelInboundRouter(deps) {
|
|
|
178789
179017
|
const adapter = deps.getAdapter(msg.channel, accountId);
|
|
178790
179018
|
if (!adapter)
|
|
178791
179019
|
return;
|
|
179020
|
+
const config3 = getChannelAccount(msg.channel, accountId);
|
|
179021
|
+
const senderAccess = config3 ? evaluateChannelSenderAccess({
|
|
179022
|
+
account: config3,
|
|
179023
|
+
channelId: msg.channel,
|
|
179024
|
+
senderId: msg.senderId,
|
|
179025
|
+
chatType: msg.chatType
|
|
179026
|
+
}) : null;
|
|
179027
|
+
if (senderAccess === "deny") {
|
|
179028
|
+
if (msg.reaction) {
|
|
179029
|
+
return;
|
|
179030
|
+
}
|
|
179031
|
+
if (resolveChannelAccessScope(msg.chatType) === "dm") {
|
|
179032
|
+
await adapter.sendDirectReply(msg.chatId, buildChannelAccessDeniedMessage(msg.channel));
|
|
179033
|
+
} else {
|
|
179034
|
+
console.log(`[channels] Dropped ${msg.channel} group message from unauthorized sender ${msg.senderId} in chat ${msg.chatId}`);
|
|
179035
|
+
}
|
|
179036
|
+
return;
|
|
179037
|
+
}
|
|
178792
179038
|
if (await deps.controls.tryHandleInbound(adapter, msg)) {
|
|
178793
179039
|
return;
|
|
178794
179040
|
}
|
|
178795
|
-
const config3 = getChannelAccount(msg.channel, accountId);
|
|
178796
179041
|
if (deps.commands.shouldDropUnroutedSlackThreadInput(msg, accountId, config3)) {
|
|
178797
179042
|
return;
|
|
178798
179043
|
}
|
|
@@ -178822,7 +179067,12 @@ function createChannelInboundRouter(deps) {
|
|
|
178822
179067
|
reload: async (_command, commandMsg) => deps.commands.handleReloadSlashCommand(commandMsg),
|
|
178823
179068
|
resume: async () => deps.commands.handlePauseResumeSlashCommand("resume", msg)
|
|
178824
179069
|
},
|
|
178825
|
-
enableBangCommands: msg.channel === "slack" && msg.isMention === true
|
|
179070
|
+
enableBangCommands: msg.channel === "slack" && msg.isMention === true,
|
|
179071
|
+
commandGate: config3 ? senderAccess === "pair" ? floorOnlyChannelCommandGate() : resolveChannelCommandGate({
|
|
179072
|
+
account: config3,
|
|
179073
|
+
channelId: msg.channel,
|
|
179074
|
+
senderId: msg.senderId
|
|
179075
|
+
}) : undefined
|
|
178826
179076
|
})) {
|
|
178827
179077
|
return;
|
|
178828
179078
|
}
|
|
@@ -178937,32 +179187,19 @@ function createChannelInboundRouter(deps) {
|
|
|
178937
179187
|
});
|
|
178938
179188
|
return;
|
|
178939
179189
|
}
|
|
178940
|
-
if (
|
|
178941
|
-
if (
|
|
178942
|
-
if (msg.reaction) {
|
|
178943
|
-
return;
|
|
178944
|
-
}
|
|
178945
|
-
await adapter.sendDirectReply(msg.chatId, "You are not on the allowed users list for this bot.");
|
|
178946
|
-
return;
|
|
178947
|
-
}
|
|
178948
|
-
} else if (config3.dmPolicy === "pairing") {
|
|
178949
|
-
if (!isUserApproved(msg.channel, msg.senderId, accountId)) {
|
|
178950
|
-
loadPairingStore(msg.channel);
|
|
178951
|
-
}
|
|
178952
|
-
if (!isUserApproved(msg.channel, msg.senderId, accountId)) {
|
|
178953
|
-
if (msg.reaction) {
|
|
178954
|
-
return;
|
|
178955
|
-
}
|
|
178956
|
-
const code2 = createPairingCode(msg.channel, msg.senderId, msg.chatId, msg.senderName, accountId);
|
|
178957
|
-
deps.emitEvent({
|
|
178958
|
-
type: "pairings_updated",
|
|
178959
|
-
channelId: msg.channel
|
|
178960
|
-
});
|
|
178961
|
-
await adapter.sendDirectReply(msg.chatId, buildPairingInstructions(msg.channel, code2, {
|
|
178962
|
-
agentId: getConfiguredAgentId(config3)
|
|
178963
|
-
}));
|
|
179190
|
+
if (senderAccess === "pair") {
|
|
179191
|
+
if (msg.reaction) {
|
|
178964
179192
|
return;
|
|
178965
179193
|
}
|
|
179194
|
+
const code2 = createPairingCode(msg.channel, msg.senderId, msg.chatId, msg.senderName, accountId);
|
|
179195
|
+
deps.emitEvent({
|
|
179196
|
+
type: "pairings_updated",
|
|
179197
|
+
channelId: msg.channel
|
|
179198
|
+
});
|
|
179199
|
+
await adapter.sendDirectReply(msg.chatId, buildPairingInstructions(msg.channel, code2, {
|
|
179200
|
+
agentId: getConfiguredAgentId(config3)
|
|
179201
|
+
}));
|
|
179202
|
+
return;
|
|
178966
179203
|
}
|
|
178967
179204
|
let route = getRoute(msg.channel, msg.chatId, accountId, msg.threadId);
|
|
178968
179205
|
if (!route) {
|
|
@@ -178984,6 +179221,7 @@ function createChannelInboundRouter(deps) {
|
|
|
178984
179221
|
return { handleInboundMessage };
|
|
178985
179222
|
}
|
|
178986
179223
|
var init_registry_inbound = __esm(() => {
|
|
179224
|
+
init_access_control();
|
|
178987
179225
|
init_accounts();
|
|
178988
179226
|
init_commands();
|
|
178989
179227
|
init_pairing();
|
|
@@ -179040,12 +179278,6 @@ function createChannelRouteProvisioner(deps) {
|
|
|
179040
179278
|
await adapter.sendDirectReply(msg.chatId, buildSlackAppSetupInstructions(), buildDirectReplyOptions(msg));
|
|
179041
179279
|
return null;
|
|
179042
179280
|
}
|
|
179043
|
-
if (msg.chatType === "direct") {
|
|
179044
|
-
if (config3.dmPolicy === "allowlist" && !config3.allowedUsers.includes(msg.senderId)) {
|
|
179045
|
-
await adapter.sendDirectReply(msg.chatId, "You are not on the allowed users list for this Slack app.", buildDirectReplyOptions(msg));
|
|
179046
|
-
return null;
|
|
179047
|
-
}
|
|
179048
|
-
}
|
|
179049
179281
|
const accountId = msg.accountId ?? LEGACY_CHANNEL_ACCOUNT_ID;
|
|
179050
179282
|
const routeThreadId = msg.channel === "slack" ? msg.threadId ?? null : null;
|
|
179051
179283
|
let route = getRoute(msg.channel, msg.chatId, accountId, routeThreadId);
|
|
@@ -179194,10 +179426,6 @@ function createChannelRouteProvisioner(deps) {
|
|
|
179194
179426
|
}
|
|
179195
179427
|
return null;
|
|
179196
179428
|
}
|
|
179197
|
-
if (msg.chatType === "direct" && config3.dmPolicy === "allowlist" && !config3.allowedUsers.includes(msg.senderId)) {
|
|
179198
|
-
await adapter.sendDirectReply(msg.chatId, "You are not on the allowed users list for this Discord bot.");
|
|
179199
|
-
return null;
|
|
179200
|
-
}
|
|
179201
179429
|
const accountId = msg.accountId ?? LEGACY_CHANNEL_ACCOUNT_ID;
|
|
179202
179430
|
const routeThreadId = msg.threadId ?? null;
|
|
179203
179431
|
let route = getRoute(msg.channel, msg.chatId, accountId, routeThreadId);
|
|
@@ -179245,10 +179473,6 @@ function createChannelRouteProvisioner(deps) {
|
|
|
179245
179473
|
}
|
|
179246
179474
|
return null;
|
|
179247
179475
|
}
|
|
179248
|
-
if (msg.chatType === "direct" && config3.dmPolicy === "allowlist" && !allowedUsersIncludes(config3.allowedUsers, msg.senderId)) {
|
|
179249
|
-
await adapter.sendDirectReply(msg.chatId, "You are not on the allowed users list for this WhatsApp account.");
|
|
179250
|
-
return null;
|
|
179251
|
-
}
|
|
179252
179476
|
const accountId = msg.accountId ?? LEGACY_CHANNEL_ACCOUNT_ID;
|
|
179253
179477
|
let route = getRoute(msg.channel, msg.chatId, accountId, null);
|
|
179254
179478
|
if (!route) {
|
|
@@ -179310,12 +179534,6 @@ function createChannelRouteProvisioner(deps) {
|
|
|
179310
179534
|
}
|
|
179311
179535
|
return null;
|
|
179312
179536
|
}
|
|
179313
|
-
if (msg.chatType === "direct" && config3.dmPolicy === "allowlist" && !signalAllowedUsersIncludes(config3.allowedUsers, msg.senderId)) {
|
|
179314
|
-
if (!msg.reaction) {
|
|
179315
|
-
await adapter.sendDirectReply(msg.chatId, "You are not on the allowed users list for this Signal account.");
|
|
179316
|
-
}
|
|
179317
|
-
return null;
|
|
179318
|
-
}
|
|
179319
179537
|
const accountId = msg.accountId ?? LEGACY_CHANNEL_ACCOUNT_ID;
|
|
179320
179538
|
let route = getRoute(msg.channel, msg.chatId, accountId, null);
|
|
179321
179539
|
if (!route) {
|
|
@@ -179363,7 +179581,6 @@ var init_registry_routes = __esm(() => {
|
|
|
179363
179581
|
init_accounts();
|
|
179364
179582
|
init_registry_presentation();
|
|
179365
179583
|
init_routing();
|
|
179366
|
-
init_target2();
|
|
179367
179584
|
init_targets();
|
|
179368
179585
|
});
|
|
179369
179586
|
|
|
@@ -448250,6 +448467,100 @@ var init_cron = __esm(async () => {
|
|
|
448250
448467
|
await init_scheduler();
|
|
448251
448468
|
});
|
|
448252
448469
|
|
|
448470
|
+
// src/backend/api/environments.ts
|
|
448471
|
+
var exports_environments2 = {};
|
|
448472
|
+
__export(exports_environments2, {
|
|
448473
|
+
sendEnvironmentMessage: () => sendEnvironmentMessage,
|
|
448474
|
+
resolveEnvironmentConnectionId: () => resolveEnvironmentConnectionId,
|
|
448475
|
+
resolveAgentSandboxConnectionId: () => resolveAgentSandboxConnectionId,
|
|
448476
|
+
listEnvironments: () => listEnvironments,
|
|
448477
|
+
isEnvironmentOnline: () => isEnvironmentOnline,
|
|
448478
|
+
getEnvironmentConnection: () => getEnvironmentConnection,
|
|
448479
|
+
describeEnvironment: () => describeEnvironment,
|
|
448480
|
+
createAgentSandbox: () => createAgentSandbox
|
|
448481
|
+
});
|
|
448482
|
+
async function listEnvironments(options3 = {}) {
|
|
448483
|
+
return apiRequest("GET", "/v1/environments", undefined, {
|
|
448484
|
+
query: {
|
|
448485
|
+
limit: options3.limit,
|
|
448486
|
+
after: options3.after,
|
|
448487
|
+
onlineOnly: options3.onlineOnly
|
|
448488
|
+
}
|
|
448489
|
+
});
|
|
448490
|
+
}
|
|
448491
|
+
async function sendEnvironmentMessage(connectionId, body3) {
|
|
448492
|
+
return apiRequest("POST", `/v1/environments/${encodeURIComponent(connectionId)}/messages`, body3);
|
|
448493
|
+
}
|
|
448494
|
+
async function getEnvironmentConnection(deviceId) {
|
|
448495
|
+
return apiRequest("GET", `/v1/environments/${encodeURIComponent(deviceId)}`);
|
|
448496
|
+
}
|
|
448497
|
+
async function createAgentSandbox(agentId) {
|
|
448498
|
+
return apiRequest("POST", `/v1/agents/${encodeURIComponent(agentId)}/sandboxes`, {});
|
|
448499
|
+
}
|
|
448500
|
+
function isEnvironmentOnline(environment2) {
|
|
448501
|
+
return typeof environment2.connectionId === "string" && environment2.connectionId.length > 0 && typeof environment2.lastHeartbeat === "number" && Date.now() - environment2.lastHeartbeat < 120000;
|
|
448502
|
+
}
|
|
448503
|
+
function describeEnvironment(environment2) {
|
|
448504
|
+
const status = isEnvironmentOnline(environment2) ? "online" : "offline";
|
|
448505
|
+
return `${environment2.connectionName} (${environment2.deviceId}, ${status})`;
|
|
448506
|
+
}
|
|
448507
|
+
async function resolveEnvironmentConnectionId(selector) {
|
|
448508
|
+
const trimmed = selector.trim();
|
|
448509
|
+
if (!trimmed) {
|
|
448510
|
+
throw new Error("Environment selector must not be empty");
|
|
448511
|
+
}
|
|
448512
|
+
const response = await listEnvironments({ limit: 100 });
|
|
448513
|
+
const matches2 = response.connections.filter((environment3) => {
|
|
448514
|
+
return environment3.connectionId === trimmed || environment3.id === trimmed || environment3.deviceId === trimmed || environment3.connectionName === trimmed;
|
|
448515
|
+
});
|
|
448516
|
+
if (matches2.length === 0) {
|
|
448517
|
+
throw new Error(`Environment "${trimmed}" not found. Run \`letta environments list\` to discover available environments.`);
|
|
448518
|
+
}
|
|
448519
|
+
const onlineMatches = matches2.filter(isEnvironmentOnline);
|
|
448520
|
+
if (onlineMatches.length === 0) {
|
|
448521
|
+
throw new Error(`Environment "${trimmed}" is offline. Matched: ${matches2.map(describeEnvironment).join(", ")}`);
|
|
448522
|
+
}
|
|
448523
|
+
if (onlineMatches.length > 1) {
|
|
448524
|
+
throw new Error(`Environment "${trimmed}" is ambiguous. Matched: ${onlineMatches.map(describeEnvironment).join(", ")}`);
|
|
448525
|
+
}
|
|
448526
|
+
const environment2 = onlineMatches[0];
|
|
448527
|
+
if (!environment2) {
|
|
448528
|
+
throw new Error(`Environment "${trimmed}" is offline`);
|
|
448529
|
+
}
|
|
448530
|
+
if (!environment2.connectionId) {
|
|
448531
|
+
throw new Error(`Environment "${trimmed}" has no active connection id`);
|
|
448532
|
+
}
|
|
448533
|
+
return { connectionId: environment2.connectionId, environment: environment2 };
|
|
448534
|
+
}
|
|
448535
|
+
async function resolveAgentSandboxConnectionId(agentId, options3 = {}) {
|
|
448536
|
+
const timeoutMs = options3.timeoutMs ?? 3 * 60000;
|
|
448537
|
+
const pollIntervalMs = options3.pollIntervalMs ?? 2000;
|
|
448538
|
+
const sandbox = await createAgentSandbox(agentId);
|
|
448539
|
+
const deviceId = sandbox.deviceId || `sandbox-${agentId}`;
|
|
448540
|
+
const deadline = Date.now() + timeoutMs;
|
|
448541
|
+
let lastEnvironment = null;
|
|
448542
|
+
let lastError = null;
|
|
448543
|
+
while (Date.now() < deadline) {
|
|
448544
|
+
try {
|
|
448545
|
+
const environment2 = await getEnvironmentConnection(deviceId);
|
|
448546
|
+
lastEnvironment = environment2;
|
|
448547
|
+
if (isEnvironmentOnline(environment2) && environment2.connectionId) {
|
|
448548
|
+
return { connectionId: environment2.connectionId, environment: environment2 };
|
|
448549
|
+
}
|
|
448550
|
+
} catch (error54) {
|
|
448551
|
+
lastError = error54;
|
|
448552
|
+
}
|
|
448553
|
+
await new Promise((resolve30) => setTimeout(resolve30, pollIntervalMs));
|
|
448554
|
+
}
|
|
448555
|
+
if (lastEnvironment) {
|
|
448556
|
+
throw new Error(`Timed out waiting for cloud sandbox ${sandbox.connectionName} to come online. Last status: ${describeEnvironment(lastEnvironment)}`);
|
|
448557
|
+
}
|
|
448558
|
+
throw new Error(`Timed out waiting for cloud sandbox ${sandbox.connectionName} to register${lastError instanceof Error ? `: ${lastError.message}` : ""}`);
|
|
448559
|
+
}
|
|
448560
|
+
var init_environments2 = __esm(() => {
|
|
448561
|
+
init_request();
|
|
448562
|
+
});
|
|
448563
|
+
|
|
448253
448564
|
// src/backend/api/search.ts
|
|
448254
448565
|
async function warmSearchCache(body3) {
|
|
448255
448566
|
return apiRequest("POST", "/v1/_internal_search/cache-warm", body3);
|
|
@@ -450607,89 +450918,6 @@ var init_reflection_launcher = __esm(() => {
|
|
|
450607
450918
|
pendingReflectionLaunches = new Map;
|
|
450608
450919
|
});
|
|
450609
450920
|
|
|
450610
|
-
// src/backend/api/environments.ts
|
|
450611
|
-
async function listEnvironments(options3 = {}) {
|
|
450612
|
-
return apiRequest("GET", "/v1/environments", undefined, {
|
|
450613
|
-
query: {
|
|
450614
|
-
limit: options3.limit,
|
|
450615
|
-
after: options3.after,
|
|
450616
|
-
onlineOnly: options3.onlineOnly
|
|
450617
|
-
}
|
|
450618
|
-
});
|
|
450619
|
-
}
|
|
450620
|
-
async function sendEnvironmentMessage(connectionId, body3) {
|
|
450621
|
-
return apiRequest("POST", `/v1/environments/${encodeURIComponent(connectionId)}/messages`, body3);
|
|
450622
|
-
}
|
|
450623
|
-
async function getEnvironmentConnection(deviceId) {
|
|
450624
|
-
return apiRequest("GET", `/v1/environments/${encodeURIComponent(deviceId)}`);
|
|
450625
|
-
}
|
|
450626
|
-
async function createAgentSandbox(agentId) {
|
|
450627
|
-
return apiRequest("POST", `/v1/agents/${encodeURIComponent(agentId)}/sandboxes`, {});
|
|
450628
|
-
}
|
|
450629
|
-
function isEnvironmentOnline(environment2) {
|
|
450630
|
-
return typeof environment2.connectionId === "string" && environment2.connectionId.length > 0 && typeof environment2.lastHeartbeat === "number" && Date.now() - environment2.lastHeartbeat < 120000;
|
|
450631
|
-
}
|
|
450632
|
-
function describeEnvironment(environment2) {
|
|
450633
|
-
const status = isEnvironmentOnline(environment2) ? "online" : "offline";
|
|
450634
|
-
return `${environment2.connectionName} (${environment2.deviceId}, ${status})`;
|
|
450635
|
-
}
|
|
450636
|
-
async function resolveEnvironmentConnectionId(selector) {
|
|
450637
|
-
const trimmed = selector.trim();
|
|
450638
|
-
if (!trimmed) {
|
|
450639
|
-
throw new Error("Environment selector must not be empty");
|
|
450640
|
-
}
|
|
450641
|
-
const response = await listEnvironments({ limit: 100 });
|
|
450642
|
-
const matches2 = response.connections.filter((environment3) => {
|
|
450643
|
-
return environment3.connectionId === trimmed || environment3.id === trimmed || environment3.deviceId === trimmed || environment3.connectionName === trimmed;
|
|
450644
|
-
});
|
|
450645
|
-
if (matches2.length === 0) {
|
|
450646
|
-
throw new Error(`Environment "${trimmed}" not found. Run \`letta environments list\` to discover available environments.`);
|
|
450647
|
-
}
|
|
450648
|
-
const onlineMatches = matches2.filter(isEnvironmentOnline);
|
|
450649
|
-
if (onlineMatches.length === 0) {
|
|
450650
|
-
throw new Error(`Environment "${trimmed}" is offline. Matched: ${matches2.map(describeEnvironment).join(", ")}`);
|
|
450651
|
-
}
|
|
450652
|
-
if (onlineMatches.length > 1) {
|
|
450653
|
-
throw new Error(`Environment "${trimmed}" is ambiguous. Matched: ${onlineMatches.map(describeEnvironment).join(", ")}`);
|
|
450654
|
-
}
|
|
450655
|
-
const environment2 = onlineMatches[0];
|
|
450656
|
-
if (!environment2) {
|
|
450657
|
-
throw new Error(`Environment "${trimmed}" is offline`);
|
|
450658
|
-
}
|
|
450659
|
-
if (!environment2.connectionId) {
|
|
450660
|
-
throw new Error(`Environment "${trimmed}" has no active connection id`);
|
|
450661
|
-
}
|
|
450662
|
-
return { connectionId: environment2.connectionId, environment: environment2 };
|
|
450663
|
-
}
|
|
450664
|
-
async function resolveAgentSandboxConnectionId(agentId, options3 = {}) {
|
|
450665
|
-
const timeoutMs = options3.timeoutMs ?? 3 * 60000;
|
|
450666
|
-
const pollIntervalMs = options3.pollIntervalMs ?? 2000;
|
|
450667
|
-
const sandbox = await createAgentSandbox(agentId);
|
|
450668
|
-
const deviceId = sandbox.deviceId || `sandbox-${agentId}`;
|
|
450669
|
-
const deadline = Date.now() + timeoutMs;
|
|
450670
|
-
let lastEnvironment = null;
|
|
450671
|
-
let lastError = null;
|
|
450672
|
-
while (Date.now() < deadline) {
|
|
450673
|
-
try {
|
|
450674
|
-
const environment2 = await getEnvironmentConnection(deviceId);
|
|
450675
|
-
lastEnvironment = environment2;
|
|
450676
|
-
if (isEnvironmentOnline(environment2) && environment2.connectionId) {
|
|
450677
|
-
return { connectionId: environment2.connectionId, environment: environment2 };
|
|
450678
|
-
}
|
|
450679
|
-
} catch (error54) {
|
|
450680
|
-
lastError = error54;
|
|
450681
|
-
}
|
|
450682
|
-
await new Promise((resolve31) => setTimeout(resolve31, pollIntervalMs));
|
|
450683
|
-
}
|
|
450684
|
-
if (lastEnvironment) {
|
|
450685
|
-
throw new Error(`Timed out waiting for cloud sandbox ${sandbox.connectionName} to come online. Last status: ${describeEnvironment(lastEnvironment)}`);
|
|
450686
|
-
}
|
|
450687
|
-
throw new Error(`Timed out waiting for cloud sandbox ${sandbox.connectionName} to register${lastError instanceof Error ? `: ${lastError.message}` : ""}`);
|
|
450688
|
-
}
|
|
450689
|
-
var init_environments2 = __esm(() => {
|
|
450690
|
-
init_request();
|
|
450691
|
-
});
|
|
450692
|
-
|
|
450693
450921
|
// node_modules/cli-spinners/spinners.json
|
|
450694
450922
|
var require_spinners = __commonJS((exports, module3) => {
|
|
450695
450923
|
module3.exports = {
|
|
@@ -473975,13 +474203,13 @@ function AgentSelector({
|
|
|
473975
474203
|
});
|
|
473976
474204
|
const renderAgentItem = (agent2, _index, isSelected, extra) => {
|
|
473977
474205
|
const isCurrent = agent2.id === currentAgentId;
|
|
473978
|
-
const
|
|
474206
|
+
const isLocalAgent2 = isLocalAgentId(agent2.id);
|
|
473979
474207
|
const relativeTime = formatRelativeTime3(agent2.last_run_completion);
|
|
473980
474208
|
const blockCount = agent2.blocks?.length ?? 0;
|
|
473981
474209
|
const modelStr = formatModel2(agent2);
|
|
473982
474210
|
const metadataParts = [
|
|
473983
474211
|
relativeTime,
|
|
473984
|
-
...
|
|
474212
|
+
...isLocalAgent2 ? [] : [`${blockCount} memory block${blockCount === 1 ? "" : "s"}`],
|
|
473985
474213
|
modelStr
|
|
473986
474214
|
];
|
|
473987
474215
|
const nameLen = (agent2.name || "Unnamed").length;
|
|
@@ -481959,7 +482187,7 @@ var init_reflection_arena_hf_upload = __esm(() => {
|
|
|
481959
482187
|
|
|
481960
482188
|
// src/cli/helpers/reflection-arena.ts
|
|
481961
482189
|
import { execFile as execFileCb6 } from "node:child_process";
|
|
481962
|
-
import { randomInt, randomUUID as randomUUID32 } from "node:crypto";
|
|
482190
|
+
import { randomInt as randomInt2, randomUUID as randomUUID32 } from "node:crypto";
|
|
481963
482191
|
import { appendFile as appendFile3, mkdir as mkdir18, readFile as readFile26, writeFile as writeFile20 } from "node:fs/promises";
|
|
481964
482192
|
import { homedir as homedir39 } from "node:os";
|
|
481965
482193
|
import { join as join71 } from "node:path";
|
|
@@ -481970,7 +482198,7 @@ function sampleReflectionArenaComparisonModel(excludedModels = []) {
|
|
|
481970
482198
|
const totalWeight = candidates2.reduce((sum, entry) => sum + Math.max(0, entry.weight), 0);
|
|
481971
482199
|
if (totalWeight <= 0)
|
|
481972
482200
|
return "letta/auto";
|
|
481973
|
-
let offset =
|
|
482201
|
+
let offset = randomInt2(totalWeight);
|
|
481974
482202
|
for (const candidate of candidates2) {
|
|
481975
482203
|
offset -= Math.max(0, candidate.weight);
|
|
481976
482204
|
if (offset < 0)
|
|
@@ -482035,7 +482263,7 @@ async function updateReflectionArenaRun(runId, update2) {
|
|
|
482035
482263
|
return updated;
|
|
482036
482264
|
}
|
|
482037
482265
|
function shuffledLabels() {
|
|
482038
|
-
return
|
|
482266
|
+
return randomInt2(2) === 0 ? ["1", "2"] : ["2", "1"];
|
|
482039
482267
|
}
|
|
482040
482268
|
function truncateReport(report) {
|
|
482041
482269
|
if (!report?.trim())
|
|
@@ -492774,8 +493002,8 @@ var init_AgentInfoBar = __esm(async () => {
|
|
|
492774
493002
|
return settingsManager.isAgentPinned(agentId);
|
|
492775
493003
|
}, [agentId]);
|
|
492776
493004
|
const isCloudUser = serverUrl?.includes("api.letta.com");
|
|
492777
|
-
const
|
|
492778
|
-
const showCloudLinks = Boolean(isCloudUser && agentId && !
|
|
493005
|
+
const isLocalAgent2 = agentId ? isLocalAgentId2(agentId) : false;
|
|
493006
|
+
const showCloudLinks = Boolean(isCloudUser && agentId && !isLocalAgent2);
|
|
492779
493007
|
const adeConversationUrl = showCloudLinks && agentId && agentId !== "loading" ? buildChatUrl(agentId, { conversationId }) : "";
|
|
492780
493008
|
const usageUrl = buildChatWebUrl("/preferences/usage");
|
|
492781
493009
|
const showBottomBar = agentId && agentId !== "loading";
|
|
@@ -501159,8 +501387,8 @@ function MemfsTreeViewer({
|
|
|
501159
501387
|
const terminalWidth = useTerminalWidth();
|
|
501160
501388
|
const solidLine = SOLID_LINE15.repeat(Math.max(terminalWidth, 10));
|
|
501161
501389
|
const isTmux = Boolean(process.env.TMUX);
|
|
501162
|
-
const
|
|
501163
|
-
const adeUrl =
|
|
501390
|
+
const isLocalAgent2 = isLocalAgentId2(agentId);
|
|
501391
|
+
const adeUrl = isLocalAgent2 ? null : buildChatUrl(agentId, { view: "memory", conversationId });
|
|
501164
501392
|
const [selectedIndex, setSelectedIndex] = import_react90.useState(0);
|
|
501165
501393
|
const [treeScrollOffset, setTreeScrollOffset] = import_react90.useState(0);
|
|
501166
501394
|
const [viewMode, setViewMode] = import_react90.useState("split");
|
|
@@ -501651,8 +501879,8 @@ function MemoryTabViewer({
|
|
|
501651
501879
|
const terminalWidth = useTerminalWidth();
|
|
501652
501880
|
const solidLine = SOLID_LINE16.repeat(Math.max(terminalWidth, 10));
|
|
501653
501881
|
const isTmux = Boolean(process.env.TMUX);
|
|
501654
|
-
const
|
|
501655
|
-
const adeUrl =
|
|
501882
|
+
const isLocalAgent2 = isLocalAgentId2(agentId);
|
|
501883
|
+
const adeUrl = isLocalAgent2 ? null : buildChatUrl(agentId, { view: "memory", conversationId });
|
|
501656
501884
|
const [selectedTabIndex, setSelectedTabIndex] = import_react91.useState(0);
|
|
501657
501885
|
const [scrollOffset, setScrollOffset] = import_react91.useState(0);
|
|
501658
501886
|
const [freshBlocks, setFreshBlocks] = import_react91.useState(null);
|
|
@@ -548433,8 +548661,142 @@ Provider '${providerName}' saved.`);
|
|
|
548433
548661
|
}
|
|
548434
548662
|
|
|
548435
548663
|
// src/cli/subcommands/cron.ts
|
|
548436
|
-
|
|
548664
|
+
init_request();
|
|
548437
548665
|
import { parseArgs as parseArgs5 } from "node:util";
|
|
548666
|
+
|
|
548667
|
+
// src/backend/api/schedules.ts
|
|
548668
|
+
init_request();
|
|
548669
|
+
function schedulePath(agentId) {
|
|
548670
|
+
return `/v1/agents/${encodeURIComponent(agentId)}/schedule`;
|
|
548671
|
+
}
|
|
548672
|
+
async function createCloudSchedule(agentId, input) {
|
|
548673
|
+
return apiRequest("POST", schedulePath(agentId), {
|
|
548674
|
+
...input,
|
|
548675
|
+
use_sandbox: true
|
|
548676
|
+
});
|
|
548677
|
+
}
|
|
548678
|
+
async function listCloudSchedules(agentId, options3 = {}) {
|
|
548679
|
+
return apiRequest("GET", schedulePath(agentId), undefined, {
|
|
548680
|
+
query: {
|
|
548681
|
+
limit: options3.limit ?? 100,
|
|
548682
|
+
after: options3.after
|
|
548683
|
+
}
|
|
548684
|
+
});
|
|
548685
|
+
}
|
|
548686
|
+
async function getCloudSchedule(agentId, scheduleId) {
|
|
548687
|
+
return apiRequest("GET", `${schedulePath(agentId)}/${encodeURIComponent(scheduleId)}`);
|
|
548688
|
+
}
|
|
548689
|
+
async function deleteCloudSchedule(agentId, scheduleId) {
|
|
548690
|
+
await apiRequest("DELETE", `${schedulePath(agentId)}/${encodeURIComponent(scheduleId)}`, {});
|
|
548691
|
+
}
|
|
548692
|
+
async function listCloudScheduleHistory(agentId, scheduleId, options3 = {}) {
|
|
548693
|
+
return apiRequest("GET", `${schedulePath(agentId)}/${encodeURIComponent(scheduleId)}/history`, undefined, {
|
|
548694
|
+
query: {
|
|
548695
|
+
limit: options3.limit,
|
|
548696
|
+
offset: options3.offset
|
|
548697
|
+
}
|
|
548698
|
+
});
|
|
548699
|
+
}
|
|
548700
|
+
|
|
548701
|
+
// src/cli/subcommands/cron.ts
|
|
548702
|
+
init_backend_mode();
|
|
548703
|
+
await init_cron();
|
|
548704
|
+
|
|
548705
|
+
// src/cli/subcommands/cron-runner.ts
|
|
548706
|
+
var CLOUD_EXECUTION_TARGET = "cloud-sandbox";
|
|
548707
|
+
function isLocalAgent(agentId) {
|
|
548708
|
+
return agentId.startsWith("agent-local-");
|
|
548709
|
+
}
|
|
548710
|
+
function resolveCronRunner(params) {
|
|
548711
|
+
const { explicit, agentId, backendMode, cloudSchedulesSupported } = params;
|
|
548712
|
+
if (explicit !== undefined && explicit !== "local" && explicit !== "cloud") {
|
|
548713
|
+
return {
|
|
548714
|
+
error: `invalid --runner "${explicit}". Expected "local" or "cloud".`
|
|
548715
|
+
};
|
|
548716
|
+
}
|
|
548717
|
+
if (explicit === "local") {
|
|
548718
|
+
return { runner: "local", reason: "explicit --runner local" };
|
|
548719
|
+
}
|
|
548720
|
+
if (backendMode === "local" || isLocalAgent(agentId)) {
|
|
548721
|
+
if (explicit === "cloud") {
|
|
548722
|
+
return {
|
|
548723
|
+
error: "Cloud schedules are not available for local-backend agents. Use --runner local."
|
|
548724
|
+
};
|
|
548725
|
+
}
|
|
548726
|
+
return { runner: "local", reason: "local-backend agent" };
|
|
548727
|
+
}
|
|
548728
|
+
if (cloudSchedulesSupported === false) {
|
|
548729
|
+
if (explicit === "cloud") {
|
|
548730
|
+
return {
|
|
548731
|
+
error: "This Letta server does not serve Cloud schedule routes (self-hosted?). Use --runner local."
|
|
548732
|
+
};
|
|
548733
|
+
}
|
|
548734
|
+
return {
|
|
548735
|
+
runner: "local",
|
|
548736
|
+
reason: "server does not support Cloud schedules"
|
|
548737
|
+
};
|
|
548738
|
+
}
|
|
548739
|
+
return {
|
|
548740
|
+
runner: "cloud",
|
|
548741
|
+
reason: explicit === "cloud" ? "explicit --runner cloud" : "cloud agent defaults to durable Cloud schedules"
|
|
548742
|
+
};
|
|
548743
|
+
}
|
|
548744
|
+
var SYNTHETIC_CLOUD_DEVICE_ID = "__letta_cloud__";
|
|
548745
|
+
var SYNTHETIC_LOCAL_PLACEHOLDER_ID = "local";
|
|
548746
|
+
function validateTargetDevice(deviceId, environment2) {
|
|
548747
|
+
if (deviceId === SYNTHETIC_CLOUD_DEVICE_ID) {
|
|
548748
|
+
return {
|
|
548749
|
+
ok: false,
|
|
548750
|
+
error: `"Cloud" is the default execution target, not a computer. Omit --computer to run in the agent's cloud sandbox.`
|
|
548751
|
+
};
|
|
548752
|
+
}
|
|
548753
|
+
if (deviceId === SYNTHETIC_LOCAL_PLACEHOLDER_ID) {
|
|
548754
|
+
return {
|
|
548755
|
+
ok: false,
|
|
548756
|
+
error: '"local" is a placeholder entry, not a connected computer. Run `letta server` on the machine you want to target, then use its deviceId.'
|
|
548757
|
+
};
|
|
548758
|
+
}
|
|
548759
|
+
if (environment2?.organizationId === "local") {
|
|
548760
|
+
return {
|
|
548761
|
+
ok: false,
|
|
548762
|
+
error: `Device ${deviceId} is this computer's local desktop connection, not a computer connected to your Letta account. Cloud schedules can only target connected computers — run \`letta server\` on that machine (or enable remote access in the desktop app) to connect it.`
|
|
548763
|
+
};
|
|
548764
|
+
}
|
|
548765
|
+
return { ok: true };
|
|
548766
|
+
}
|
|
548767
|
+
var CLOUD_CRON_UTC_NOTE = "Recurring Cloud schedules currently interpret cron expressions in UTC (timezone support is tracked in LET-9815).";
|
|
548768
|
+
var CLOUD_DEVICE_FALLBACK_NOTE = "If the target computer is offline when the schedule fires, execution falls back to the agent's cloud sandbox.";
|
|
548769
|
+
function buildCloudScheduleInput(params) {
|
|
548770
|
+
const notes = [];
|
|
548771
|
+
let schedule;
|
|
548772
|
+
if (params.recurring) {
|
|
548773
|
+
schedule = { type: "recurring", cron_expression: params.cron };
|
|
548774
|
+
notes.push(CLOUD_CRON_UTC_NOTE);
|
|
548775
|
+
} else {
|
|
548776
|
+
const scheduledAt = params.scheduledFor?.getTime();
|
|
548777
|
+
if (!scheduledAt || Number.isNaN(scheduledAt)) {
|
|
548778
|
+
throw new Error("One-shot Cloud schedules require a resolved --at time.");
|
|
548779
|
+
}
|
|
548780
|
+
schedule = { type: "one-time", scheduled_at: scheduledAt };
|
|
548781
|
+
}
|
|
548782
|
+
const targetDeviceId = params.targetDeviceId?.trim();
|
|
548783
|
+
if (targetDeviceId) {
|
|
548784
|
+
notes.push(CLOUD_DEVICE_FALLBACK_NOTE);
|
|
548785
|
+
}
|
|
548786
|
+
return {
|
|
548787
|
+
input: {
|
|
548788
|
+
name: params.name,
|
|
548789
|
+
description: params.description,
|
|
548790
|
+
...params.conversationId && { conversation_id: params.conversationId },
|
|
548791
|
+
messages: [{ role: "user", content: params.prompt }],
|
|
548792
|
+
schedule,
|
|
548793
|
+
...targetDeviceId && { target_device_id: targetDeviceId }
|
|
548794
|
+
},
|
|
548795
|
+
notes
|
|
548796
|
+
};
|
|
548797
|
+
}
|
|
548798
|
+
|
|
548799
|
+
// src/cli/subcommands/cron.ts
|
|
548438
548800
|
function printUsage4() {
|
|
548439
548801
|
console.log(`
|
|
548440
548802
|
Usage:
|
|
@@ -548442,10 +548804,10 @@ Usage:
|
|
|
548442
548804
|
letta cron add --prompt <text> --at <time> [--once] [options]
|
|
548443
548805
|
letta cron add --prompt <text> --cron <expr> [options]
|
|
548444
548806
|
letta cron list [options]
|
|
548445
|
-
letta cron get <id>
|
|
548446
|
-
letta cron runs --id <id> [--limit <n>]
|
|
548447
|
-
letta cron delete <id>
|
|
548448
|
-
letta cron delete --all [--agent <id>]
|
|
548807
|
+
letta cron get <id> [--runner local|cloud]
|
|
548808
|
+
letta cron runs --id <id> [--limit <n>] [--runner local|cloud]
|
|
548809
|
+
letta cron delete <id> [--runner local|cloud]
|
|
548810
|
+
letta cron delete --all [--agent <id>] [--runner local|cloud]
|
|
548449
548811
|
|
|
548450
548812
|
Add options:
|
|
548451
548813
|
--prompt <text> Prompt to send to the agent (required)
|
|
@@ -548455,10 +548817,23 @@ Add options:
|
|
|
548455
548817
|
--cron <expr> Raw 5-field cron expression
|
|
548456
548818
|
--agent <id> Agent ID (defaults to LETTA_AGENT_ID)
|
|
548457
548819
|
--conversation <id> Conversation ID (defaults to LETTA_CONVERSATION_ID or "default")
|
|
548820
|
+
--runner <runner> Where the schedule lives and fires:
|
|
548821
|
+
cloud - durable Cloud schedule; executes in the
|
|
548822
|
+
agent's managed cloud sandbox (default for
|
|
548823
|
+
cloud agents)
|
|
548824
|
+
local - this device's scheduler (~/.letta/crons.json);
|
|
548825
|
+
only fires while a session runs here (default
|
|
548826
|
+
for local-backend agents / self-hosted)
|
|
548827
|
+
--computer <id> (cloud runner only) Execute on one of your
|
|
548828
|
+
connected computers (deviceId from
|
|
548829
|
+
\`letta environments list\`) instead of the agent's
|
|
548830
|
+
cloud sandbox. Falls back to the sandbox if the
|
|
548831
|
+
computer is offline at fire time.
|
|
548458
548832
|
|
|
548459
548833
|
List/filter options:
|
|
548460
548834
|
--agent <id> Filter by agent ID
|
|
548461
548835
|
--conversation <id> Filter by conversation ID
|
|
548836
|
+
--runner <runner> Only show tasks owned by this runner
|
|
548462
548837
|
|
|
548463
548838
|
Delete options:
|
|
548464
548839
|
--all Delete all tasks for the given agent
|
|
@@ -548480,7 +548855,9 @@ var CRON_OPTIONS = {
|
|
|
548480
548855
|
all: { type: "boolean" },
|
|
548481
548856
|
id: { type: "string" },
|
|
548482
548857
|
limit: { type: "string" },
|
|
548483
|
-
"run-id": { type: "string" }
|
|
548858
|
+
"run-id": { type: "string" },
|
|
548859
|
+
runner: { type: "string" },
|
|
548860
|
+
computer: { type: "string" }
|
|
548484
548861
|
};
|
|
548485
548862
|
function parseCronArgs(argv) {
|
|
548486
548863
|
return parseArgs5({
|
|
@@ -548496,7 +548873,75 @@ function getAgentId3(fromArgs) {
|
|
|
548496
548873
|
function getConversationId3(fromArgs) {
|
|
548497
548874
|
return fromArgs || process.env.LETTA_CONVERSATION_ID || "default";
|
|
548498
548875
|
}
|
|
548499
|
-
function
|
|
548876
|
+
async function probeCloudScheduleSupport(agentId) {
|
|
548877
|
+
try {
|
|
548878
|
+
await listCloudSchedules(agentId, { limit: 1 });
|
|
548879
|
+
return true;
|
|
548880
|
+
} catch (err) {
|
|
548881
|
+
if (err instanceof ApiRequestError && (err.status === 404 || err.status === 405)) {
|
|
548882
|
+
return false;
|
|
548883
|
+
}
|
|
548884
|
+
return true;
|
|
548885
|
+
}
|
|
548886
|
+
}
|
|
548887
|
+
async function ensureSettingsForCloud() {
|
|
548888
|
+
const { settingsManager: settingsManager2 } = await Promise.resolve().then(() => (init_settings_manager(), exports_settings_manager));
|
|
548889
|
+
await settingsManager2.initialize();
|
|
548890
|
+
}
|
|
548891
|
+
async function getRunnerForAgent(explicit, agentId) {
|
|
548892
|
+
const backendMode = resolveBackendMode();
|
|
548893
|
+
const preliminary = resolveCronRunner({ explicit, agentId, backendMode });
|
|
548894
|
+
if ("error" in preliminary || preliminary.runner === "local") {
|
|
548895
|
+
return preliminary;
|
|
548896
|
+
}
|
|
548897
|
+
await ensureSettingsForCloud();
|
|
548898
|
+
const cloudSchedulesSupported = await probeCloudScheduleSupport(agentId);
|
|
548899
|
+
return resolveCronRunner({
|
|
548900
|
+
explicit,
|
|
548901
|
+
agentId,
|
|
548902
|
+
backendMode,
|
|
548903
|
+
cloudSchedulesSupported
|
|
548904
|
+
});
|
|
548905
|
+
}
|
|
548906
|
+
function isRunnerFlagValid(value) {
|
|
548907
|
+
return value === undefined || value === "local" || value === "cloud";
|
|
548908
|
+
}
|
|
548909
|
+
async function lookupEnvironmentForTarget(deviceId) {
|
|
548910
|
+
try {
|
|
548911
|
+
const { getEnvironmentConnection: getEnvironmentConnection2 } = await Promise.resolve().then(() => (init_environments2(), exports_environments2));
|
|
548912
|
+
return await getEnvironmentConnection2(deviceId);
|
|
548913
|
+
} catch {
|
|
548914
|
+
return null;
|
|
548915
|
+
}
|
|
548916
|
+
}
|
|
548917
|
+
function extractPromptFromCloudSchedule(schedule) {
|
|
548918
|
+
const messages = schedule.message?.messages;
|
|
548919
|
+
if (!Array.isArray(messages))
|
|
548920
|
+
return null;
|
|
548921
|
+
const first = messages[0];
|
|
548922
|
+
if (!first || typeof first.content !== "string")
|
|
548923
|
+
return null;
|
|
548924
|
+
return first.content;
|
|
548925
|
+
}
|
|
548926
|
+
function formatCloudScheduleOutput(schedule) {
|
|
548927
|
+
const targetDeviceId = schedule.target_device_id ?? null;
|
|
548928
|
+
return {
|
|
548929
|
+
id: schedule.id,
|
|
548930
|
+
runner: "cloud",
|
|
548931
|
+
execution_target: targetDeviceId ?? CLOUD_EXECUTION_TARGET,
|
|
548932
|
+
...targetDeviceId && { target_device_id: targetDeviceId },
|
|
548933
|
+
agent_id: schedule.agent_id,
|
|
548934
|
+
conversation_id: schedule.conversation_id ?? "default",
|
|
548935
|
+
name: schedule.name ?? null,
|
|
548936
|
+
description: schedule.description ?? null,
|
|
548937
|
+
prompt: extractPromptFromCloudSchedule(schedule),
|
|
548938
|
+
schedule: schedule.schedule,
|
|
548939
|
+
recurring: schedule.schedule.type === "recurring",
|
|
548940
|
+
next_scheduled_time: schedule.next_scheduled_time,
|
|
548941
|
+
created_at: schedule.created_at ?? null
|
|
548942
|
+
};
|
|
548943
|
+
}
|
|
548944
|
+
async function handleAdd(values2) {
|
|
548500
548945
|
const name = values2.name;
|
|
548501
548946
|
if (!name || typeof name !== "string") {
|
|
548502
548947
|
console.error("Error: --name is required.");
|
|
@@ -548568,6 +549013,37 @@ function handleAdd(values2) {
|
|
|
548568
549013
|
console.error("Error: no schedule specified.");
|
|
548569
549014
|
return 1;
|
|
548570
549015
|
}
|
|
549016
|
+
const targetDeviceId = values2.computer?.trim() || undefined;
|
|
549017
|
+
const resolved = await getRunnerForAgent(values2.runner, agentId);
|
|
549018
|
+
if ("error" in resolved) {
|
|
549019
|
+
console.error(`Error: ${resolved.error}`);
|
|
549020
|
+
return 1;
|
|
549021
|
+
}
|
|
549022
|
+
if (targetDeviceId && resolved.runner !== "cloud") {
|
|
549023
|
+
console.error("Error: --computer requires the cloud runner. Run `letta cron add` on the target computer itself (with --runner local) to schedule there locally.");
|
|
549024
|
+
return 1;
|
|
549025
|
+
}
|
|
549026
|
+
if (targetDeviceId) {
|
|
549027
|
+
const validity = validateTargetDevice(targetDeviceId, await lookupEnvironmentForTarget(targetDeviceId));
|
|
549028
|
+
if (!validity.ok) {
|
|
549029
|
+
console.error(`Error: ${validity.error}`);
|
|
549030
|
+
return 1;
|
|
549031
|
+
}
|
|
549032
|
+
}
|
|
549033
|
+
if (resolved.runner === "cloud") {
|
|
549034
|
+
return handleCloudAdd({
|
|
549035
|
+
agentId,
|
|
549036
|
+
conversationId,
|
|
549037
|
+
name,
|
|
549038
|
+
description,
|
|
549039
|
+
prompt,
|
|
549040
|
+
cron,
|
|
549041
|
+
recurring,
|
|
549042
|
+
scheduledFor,
|
|
549043
|
+
note,
|
|
549044
|
+
targetDeviceId
|
|
549045
|
+
});
|
|
549046
|
+
}
|
|
548571
549047
|
try {
|
|
548572
549048
|
const result = addTask({
|
|
548573
549049
|
agent_id: agentId,
|
|
@@ -548581,6 +549057,7 @@ function handleAdd(values2) {
|
|
|
548581
549057
|
});
|
|
548582
549058
|
const output = {
|
|
548583
549059
|
id: result.task.id,
|
|
549060
|
+
runner: "local",
|
|
548584
549061
|
status: result.task.status,
|
|
548585
549062
|
cron: result.task.cron,
|
|
548586
549063
|
recurring: result.task.recurring,
|
|
@@ -548601,37 +549078,152 @@ function handleAdd(values2) {
|
|
|
548601
549078
|
output.warning = result.warning;
|
|
548602
549079
|
}
|
|
548603
549080
|
console.log(JSON.stringify(output, null, 2));
|
|
549081
|
+
console.error("Created local schedule: it only fires while a Letta session is running on this device.");
|
|
548604
549082
|
return 0;
|
|
548605
549083
|
} catch (err) {
|
|
548606
549084
|
console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
548607
549085
|
return 1;
|
|
548608
549086
|
}
|
|
548609
549087
|
}
|
|
548610
|
-
function
|
|
549088
|
+
async function handleCloudAdd(params) {
|
|
549089
|
+
let built;
|
|
549090
|
+
try {
|
|
549091
|
+
built = buildCloudScheduleInput({
|
|
549092
|
+
name: params.name,
|
|
549093
|
+
description: params.description,
|
|
549094
|
+
prompt: params.prompt,
|
|
549095
|
+
conversationId: params.conversationId,
|
|
549096
|
+
cron: params.cron,
|
|
549097
|
+
recurring: params.recurring,
|
|
549098
|
+
scheduledFor: params.scheduledFor,
|
|
549099
|
+
targetDeviceId: params.targetDeviceId
|
|
549100
|
+
});
|
|
549101
|
+
} catch (err) {
|
|
549102
|
+
console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
549103
|
+
return 1;
|
|
549104
|
+
}
|
|
549105
|
+
try {
|
|
549106
|
+
const result = await createCloudSchedule(params.agentId, built.input);
|
|
549107
|
+
const targetDeviceId = result.target_device_id ?? built.input.target_device_id ?? null;
|
|
549108
|
+
const output = {
|
|
549109
|
+
id: result.id,
|
|
549110
|
+
runner: "cloud",
|
|
549111
|
+
execution_target: targetDeviceId ?? CLOUD_EXECUTION_TARGET,
|
|
549112
|
+
...targetDeviceId && { target_device_id: targetDeviceId },
|
|
549113
|
+
agent_id: params.agentId,
|
|
549114
|
+
conversation_id: params.conversationId,
|
|
549115
|
+
recurring: params.recurring,
|
|
549116
|
+
schedule: built.input.schedule,
|
|
549117
|
+
...result.next_scheduled_at && {
|
|
549118
|
+
next_scheduled_at: result.next_scheduled_at
|
|
549119
|
+
}
|
|
549120
|
+
};
|
|
549121
|
+
const notes = [...built.notes];
|
|
549122
|
+
if (params.note)
|
|
549123
|
+
notes.unshift(params.note);
|
|
549124
|
+
if (notes.length > 0) {
|
|
549125
|
+
output.notes = notes;
|
|
549126
|
+
}
|
|
549127
|
+
console.log(JSON.stringify(output, null, 2));
|
|
549128
|
+
console.error(targetDeviceId ? `Created Cloud schedule: it fires from the cloud and runs on computer "${targetDeviceId}" (sandbox fallback if offline).` : "Created Cloud schedule: it fires from the cloud and runs in this agent's managed cloud sandbox (survives local shutdown).");
|
|
549129
|
+
return 0;
|
|
549130
|
+
} catch (err) {
|
|
549131
|
+
console.error(`Error: failed to create Cloud schedule: ${err instanceof Error ? err.message : String(err)}`);
|
|
549132
|
+
console.error("No schedule was created. Retry, or pass --runner local to schedule on this device instead.");
|
|
549133
|
+
return 1;
|
|
549134
|
+
}
|
|
549135
|
+
}
|
|
549136
|
+
async function handleList(values2) {
|
|
549137
|
+
if (!isRunnerFlagValid(values2.runner)) {
|
|
549138
|
+
console.error(`Error: invalid --runner "${values2.runner}". Expected "local" or "cloud".`);
|
|
549139
|
+
return 1;
|
|
549140
|
+
}
|
|
548611
549141
|
const agentId = values2.agent || process.env.LETTA_AGENT_ID || undefined;
|
|
548612
549142
|
const conversationId = values2.conversation || undefined;
|
|
548613
|
-
const
|
|
548614
|
-
|
|
548615
|
-
|
|
548616
|
-
|
|
548617
|
-
|
|
549143
|
+
const includeLocal = values2.runner !== "cloud";
|
|
549144
|
+
const includeCloud = values2.runner !== "local";
|
|
549145
|
+
const output = [];
|
|
549146
|
+
if (includeLocal) {
|
|
549147
|
+
const tasks2 = listTasks2({
|
|
549148
|
+
agent_id: agentId,
|
|
549149
|
+
conversation_id: conversationId
|
|
549150
|
+
});
|
|
549151
|
+
for (const task2 of tasks2) {
|
|
549152
|
+
output.push({ ...task2, runner: "local" });
|
|
549153
|
+
}
|
|
549154
|
+
}
|
|
549155
|
+
if (includeCloud && agentId) {
|
|
549156
|
+
const resolved = await getRunnerForAgent(undefined, agentId);
|
|
549157
|
+
const cloudCapable = !("error" in resolved) && resolved.runner === "cloud";
|
|
549158
|
+
const cloudExplicit = values2.runner === "cloud";
|
|
549159
|
+
if (cloudCapable || cloudExplicit) {
|
|
549160
|
+
try {
|
|
549161
|
+
const response = await listCloudSchedules(agentId);
|
|
549162
|
+
for (const schedule of response.scheduled_messages) {
|
|
549163
|
+
if (conversationId && (schedule.conversation_id ?? "default") !== conversationId) {
|
|
549164
|
+
continue;
|
|
549165
|
+
}
|
|
549166
|
+
output.push(formatCloudScheduleOutput(schedule));
|
|
549167
|
+
}
|
|
549168
|
+
} catch (err) {
|
|
549169
|
+
console.error(`Warning: failed to list Cloud schedules: ${err instanceof Error ? err.message : String(err)}`);
|
|
549170
|
+
if (cloudExplicit) {
|
|
549171
|
+
return 1;
|
|
549172
|
+
}
|
|
549173
|
+
}
|
|
549174
|
+
}
|
|
549175
|
+
} else if (includeCloud && values2.runner === "cloud" && !agentId) {
|
|
549176
|
+
console.error("Error: --agent or LETTA_AGENT_ID required to list Cloud schedules.");
|
|
549177
|
+
return 1;
|
|
549178
|
+
}
|
|
549179
|
+
console.log(JSON.stringify(output, null, 2));
|
|
548618
549180
|
return 0;
|
|
548619
549181
|
}
|
|
548620
|
-
function handleGet(positionals) {
|
|
549182
|
+
async function handleGet(values2, positionals) {
|
|
549183
|
+
if (!isRunnerFlagValid(values2.runner)) {
|
|
549184
|
+
console.error(`Error: invalid --runner "${values2.runner}". Expected "local" or "cloud".`);
|
|
549185
|
+
return 1;
|
|
549186
|
+
}
|
|
548621
549187
|
const taskId = positionals[1];
|
|
548622
549188
|
if (!taskId) {
|
|
548623
549189
|
console.error("Error: task ID required. Usage: letta cron get <id>");
|
|
548624
549190
|
return 1;
|
|
548625
549191
|
}
|
|
548626
|
-
|
|
548627
|
-
|
|
548628
|
-
|
|
549192
|
+
if (values2.runner !== "cloud") {
|
|
549193
|
+
const task2 = getTask2(taskId);
|
|
549194
|
+
if (task2) {
|
|
549195
|
+
console.log(JSON.stringify({ ...task2, runner: "local" }, null, 2));
|
|
549196
|
+
return 0;
|
|
549197
|
+
}
|
|
549198
|
+
if (values2.runner === "local") {
|
|
549199
|
+
console.error(`Error: task ${taskId} not found.`);
|
|
549200
|
+
return 1;
|
|
549201
|
+
}
|
|
549202
|
+
}
|
|
549203
|
+
const agentId = getAgentId3(values2.agent);
|
|
549204
|
+
if (!agentId) {
|
|
549205
|
+
console.error(`Error: task ${taskId} not found locally, and --agent or LETTA_AGENT_ID is required to look up Cloud schedules.`);
|
|
549206
|
+
return 1;
|
|
549207
|
+
}
|
|
549208
|
+
try {
|
|
549209
|
+
await ensureSettingsForCloud();
|
|
549210
|
+
const schedule = await getCloudSchedule(agentId, taskId);
|
|
549211
|
+
console.log(JSON.stringify(formatCloudScheduleOutput(schedule), null, 2));
|
|
549212
|
+
return 0;
|
|
549213
|
+
} catch (err) {
|
|
549214
|
+
if (err instanceof ApiRequestError && err.status === 404) {
|
|
549215
|
+
console.error(`Error: task ${taskId} not found.`);
|
|
549216
|
+
} else {
|
|
549217
|
+
console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
549218
|
+
}
|
|
548629
549219
|
return 1;
|
|
548630
549220
|
}
|
|
548631
|
-
console.log(JSON.stringify(task2, null, 2));
|
|
548632
|
-
return 0;
|
|
548633
549221
|
}
|
|
548634
|
-
function handleRuns(values2) {
|
|
549222
|
+
async function handleRuns(values2) {
|
|
549223
|
+
if (!isRunnerFlagValid(values2.runner)) {
|
|
549224
|
+
console.error(`Error: invalid --runner "${values2.runner}". Expected "local" or "cloud".`);
|
|
549225
|
+
return 1;
|
|
549226
|
+
}
|
|
548635
549227
|
const id2 = values2.id;
|
|
548636
549228
|
if (!id2 || typeof id2 !== "string") {
|
|
548637
549229
|
console.error("Error: --id is required. Usage: letta cron runs --id <id>");
|
|
@@ -548640,42 +549232,125 @@ function handleRuns(values2) {
|
|
|
548640
549232
|
const limitRaw = Number.parseInt(String(values2.limit ?? "50"), 10);
|
|
548641
549233
|
const limit3 = Number.isFinite(limitRaw) && limitRaw > 0 ? limitRaw : 50;
|
|
548642
549234
|
const runId = values2["run-id"];
|
|
549235
|
+
if (values2.runner !== "cloud" && getTask2(id2)) {
|
|
549236
|
+
try {
|
|
549237
|
+
const logPath = getCronRunLogPath(id2);
|
|
549238
|
+
const page = readCronRunLogEntriesPage(logPath, {
|
|
549239
|
+
jobId: id2,
|
|
549240
|
+
limit: limit3,
|
|
549241
|
+
...typeof runId === "string" && runId.trim() ? { runId } : {}
|
|
549242
|
+
});
|
|
549243
|
+
console.log(JSON.stringify(page, null, 2));
|
|
549244
|
+
return 0;
|
|
549245
|
+
} catch (err) {
|
|
549246
|
+
console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
549247
|
+
return 1;
|
|
549248
|
+
}
|
|
549249
|
+
}
|
|
549250
|
+
if (values2.runner === "local") {
|
|
549251
|
+
console.error(`Error: task ${id2} not found.`);
|
|
549252
|
+
return 1;
|
|
549253
|
+
}
|
|
549254
|
+
const agentId = getAgentId3(values2.agent);
|
|
549255
|
+
if (!agentId) {
|
|
549256
|
+
console.error(`Error: task ${id2} not found locally, and --agent or LETTA_AGENT_ID is required to look up Cloud schedule runs.`);
|
|
549257
|
+
return 1;
|
|
549258
|
+
}
|
|
548643
549259
|
try {
|
|
548644
|
-
|
|
548645
|
-
const
|
|
548646
|
-
|
|
548647
|
-
|
|
548648
|
-
|
|
548649
|
-
|
|
548650
|
-
|
|
549260
|
+
await ensureSettingsForCloud();
|
|
549261
|
+
const response = await listCloudScheduleHistory(agentId, id2, { limit: limit3 });
|
|
549262
|
+
console.log(JSON.stringify({
|
|
549263
|
+
runner: "cloud",
|
|
549264
|
+
entries: response.history,
|
|
549265
|
+
has_next_page: response.has_next_page
|
|
549266
|
+
}, null, 2));
|
|
548651
549267
|
return 0;
|
|
548652
549268
|
} catch (err) {
|
|
548653
549269
|
console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
548654
549270
|
return 1;
|
|
548655
549271
|
}
|
|
548656
549272
|
}
|
|
548657
|
-
function handleDelete(values2, positionals) {
|
|
549273
|
+
async function handleDelete(values2, positionals) {
|
|
549274
|
+
if (!isRunnerFlagValid(values2.runner)) {
|
|
549275
|
+
console.error(`Error: invalid --runner "${values2.runner}". Expected "local" or "cloud".`);
|
|
549276
|
+
return 1;
|
|
549277
|
+
}
|
|
548658
549278
|
if (values2.all) {
|
|
548659
|
-
|
|
548660
|
-
if (!agentId) {
|
|
548661
|
-
console.error("Error: --agent or LETTA_AGENT_ID required with --all.");
|
|
548662
|
-
return 1;
|
|
548663
|
-
}
|
|
548664
|
-
const count = deleteAllTasks(agentId);
|
|
548665
|
-
console.log(JSON.stringify({ deleted: count, agent_id: agentId }));
|
|
548666
|
-
return 0;
|
|
549279
|
+
return handleDeleteAll(values2);
|
|
548667
549280
|
}
|
|
548668
549281
|
const taskId = positionals[1];
|
|
548669
549282
|
if (!taskId) {
|
|
548670
549283
|
console.error("Error: task ID required. Usage: letta cron delete <id> or --all --agent <id>");
|
|
548671
549284
|
return 1;
|
|
548672
549285
|
}
|
|
548673
|
-
|
|
548674
|
-
|
|
548675
|
-
|
|
549286
|
+
if (values2.runner !== "cloud") {
|
|
549287
|
+
const found = deleteTask(taskId);
|
|
549288
|
+
if (found) {
|
|
549289
|
+
console.log(JSON.stringify({ deleted: taskId, runner: "local" }));
|
|
549290
|
+
return 0;
|
|
549291
|
+
}
|
|
549292
|
+
if (values2.runner === "local") {
|
|
549293
|
+
console.error(`Error: task ${taskId} not found.`);
|
|
549294
|
+
return 1;
|
|
549295
|
+
}
|
|
549296
|
+
}
|
|
549297
|
+
const agentId = getAgentId3(values2.agent);
|
|
549298
|
+
if (!agentId) {
|
|
549299
|
+
console.error(`Error: task ${taskId} not found locally, and --agent or LETTA_AGENT_ID is required to delete Cloud schedules.`);
|
|
549300
|
+
return 1;
|
|
549301
|
+
}
|
|
549302
|
+
try {
|
|
549303
|
+
await ensureSettingsForCloud();
|
|
549304
|
+
await getCloudSchedule(agentId, taskId);
|
|
549305
|
+
await deleteCloudSchedule(agentId, taskId);
|
|
549306
|
+
console.log(JSON.stringify({ deleted: taskId, runner: "cloud" }));
|
|
549307
|
+
return 0;
|
|
549308
|
+
} catch (err) {
|
|
549309
|
+
if (err instanceof ApiRequestError && err.status === 404) {
|
|
549310
|
+
console.error(`Error: task ${taskId} not found.`);
|
|
549311
|
+
} else {
|
|
549312
|
+
console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
549313
|
+
}
|
|
549314
|
+
return 1;
|
|
549315
|
+
}
|
|
549316
|
+
}
|
|
549317
|
+
async function handleDeleteAll(values2) {
|
|
549318
|
+
const agentId = getAgentId3(values2.agent);
|
|
549319
|
+
if (!agentId) {
|
|
549320
|
+
console.error("Error: --agent or LETTA_AGENT_ID required with --all.");
|
|
548676
549321
|
return 1;
|
|
548677
549322
|
}
|
|
548678
|
-
|
|
549323
|
+
const includeLocal = values2.runner !== "cloud";
|
|
549324
|
+
const includeCloud = values2.runner !== "local";
|
|
549325
|
+
let localDeleted = 0;
|
|
549326
|
+
if (includeLocal) {
|
|
549327
|
+
localDeleted = deleteAllTasks(agentId);
|
|
549328
|
+
}
|
|
549329
|
+
let cloudDeleted = 0;
|
|
549330
|
+
if (includeCloud) {
|
|
549331
|
+
const resolved = await getRunnerForAgent(undefined, agentId);
|
|
549332
|
+
const cloudCapable = !("error" in resolved) && resolved.runner === "cloud";
|
|
549333
|
+
const cloudExplicit = values2.runner === "cloud";
|
|
549334
|
+
if (cloudCapable || cloudExplicit) {
|
|
549335
|
+
try {
|
|
549336
|
+
const response = await listCloudSchedules(agentId);
|
|
549337
|
+
for (const schedule of response.scheduled_messages) {
|
|
549338
|
+
await deleteCloudSchedule(agentId, schedule.id);
|
|
549339
|
+
cloudDeleted += 1;
|
|
549340
|
+
}
|
|
549341
|
+
} catch (err) {
|
|
549342
|
+
console.error(`Error: failed to delete Cloud schedules: ${err instanceof Error ? err.message : String(err)}`);
|
|
549343
|
+
console.error(`Deleted so far: ${localDeleted} local, ${cloudDeleted} cloud.`);
|
|
549344
|
+
return 1;
|
|
549345
|
+
}
|
|
549346
|
+
}
|
|
549347
|
+
}
|
|
549348
|
+
console.log(JSON.stringify({
|
|
549349
|
+
deleted: localDeleted + cloudDeleted,
|
|
549350
|
+
local_deleted: localDeleted,
|
|
549351
|
+
cloud_deleted: cloudDeleted,
|
|
549352
|
+
agent_id: agentId
|
|
549353
|
+
}));
|
|
548679
549354
|
return 0;
|
|
548680
549355
|
}
|
|
548681
549356
|
async function runCronSubcommand(argv) {
|
|
@@ -548698,7 +549373,7 @@ async function runCronSubcommand(argv) {
|
|
|
548698
549373
|
case "list":
|
|
548699
549374
|
return handleList(parsed.values);
|
|
548700
549375
|
case "get":
|
|
548701
|
-
return handleGet(parsed.positionals);
|
|
549376
|
+
return handleGet(parsed.values, parsed.positionals);
|
|
548702
549377
|
case "runs":
|
|
548703
549378
|
return handleRuns(parsed.values);
|
|
548704
549379
|
case "delete":
|
|
@@ -557864,4 +558539,4 @@ Error during initialization: ${message}`);
|
|
|
557864
558539
|
}
|
|
557865
558540
|
main2();
|
|
557866
558541
|
|
|
557867
|
-
//# debugId=
|
|
558542
|
+
//# debugId=E8D52F75291B5E0B64756E2164756E21
|