@letta-ai/letta-code 0.28.12 → 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/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.12",
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/feedback.ts
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 = channelDisplayName(channelId);
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 = channelDisplayName(channelId);
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 = channelDisplayName(channelId);
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 = channelDisplayName(channelId);
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 = channelDisplayName(channelId);
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 channelDisplayName2(channelId) {
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 = channelDisplayName2(channelId);
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 = channelDisplayName2(channelId);
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 = channelDisplayName2(msg.channel);
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 = channelDisplayName2(channelId);
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 = channelDisplayName2(channelId);
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 `${channelDisplayName2(channelId)} agent routing is already paused for this chat. Send /resume here to turn replies back on.`;
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 = channelDisplayName2(channelId);
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 `${channelDisplayName2(channelId)} agent routing is already active for this chat.`;
168222
+ return `${channelDisplayName3(channelId)} agent routing is already active for this chat.`;
167616
168223
  }
167617
168224
  function buildChannelCancelUnavailableMessage(channelId) {
167618
- const displayName = channelDisplayName2(channelId);
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 = channelDisplayName2(channelId);
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 = channelDisplayName2(channelId);
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 = channelDisplayName2(channelId);
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 = channelDisplayName2(channelId);
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 = channelDisplayName2(channelId);
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 = channelDisplayName2(channelId);
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 = channelDisplayName2(channelId);
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 = channelDisplayName2(channelId);
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 = channelDisplayName2(channelId);
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 = channelDisplayName2(channelId);
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 = channelDisplayName2(channelId);
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 = channelDisplayName2(channelId);
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 = channelDisplayName2(channelId);
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 = channelDisplayName2(channelId);
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 = channelDisplayName2(channelId);
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 = channelDisplayName2(channelId);
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 = channelDisplayName2(channelId);
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 = channelDisplayName2(channelId);
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 mkdirSync5,
171606
+ mkdirSync as mkdirSync6,
170981
171607
  mkdtempSync as mkdtempSync2,
170982
- writeFileSync as writeFileSync3
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
- mkdirSync5(dir, { recursive: true, mode: 448 });
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
- writeFileSync3(filePath, "", { mode: 384 });
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 existsSync8, mkdirSync as mkdirSync6, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "node:fs";
171244
- function getStore2(channelId) {
171245
- let store = stores2.get(channelId);
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
- stores2.set(channelId, store);
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 (!existsSync8(path5)) {
171884
+ if (!existsSync9(path5)) {
171259
171885
  return;
171260
171886
  }
171261
171887
  try {
171262
- const text = readFileSync7(path5, "utf-8");
171888
+ const text = readFileSync8(path5, "utf-8");
171263
171889
  const parsed = JSON.parse(text);
171264
- stores2.set(channelId, {
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, getStore2(channelId));
171897
+ saveTargetStoreOverride(channelId, getStore3(channelId));
171272
171898
  return;
171273
171899
  }
171274
171900
  const dir = getChannelDir(channelId);
171275
- mkdirSync6(dir, { recursive: true });
171276
- writeFileSync4(getChannelTargetsPath(channelId), `${JSON.stringify(getStore2(channelId), null, 2)}
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 : normalizeAccountId(accountId);
171281
- return getStore2(channelId).targets.filter((target2) => normalizedAccountId === undefined || normalizeAccountId(target2.accountId) === normalizedAccountId);
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 = getStore2(channelId);
171285
- const normalizedAccountId = normalizeAccountId(target2.accountId);
171286
- const existingIndex = store.targets.findIndex((candidate) => candidate.targetId === target2.targetId && normalizeAccountId(candidate.accountId) === normalizedAccountId);
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 = getStore2(channelId);
171315
- const normalizedAccountId = normalizeAccountId(accountId);
171316
- const nextTargets = store.targets.filter((target2) => !(target2.targetId === targetId && normalizeAccountId(target2.accountId) === normalizedAccountId));
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 = getStore2(channelId);
171326
- const normalizedAccountId = normalizeAccountId(accountId);
171327
- const nextTargets = store.targets.filter((target2) => normalizeAccountId(target2.accountId) !== normalizedAccountId);
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 normalizeAccountId(accountId) {
171962
+ function normalizeAccountId2(accountId) {
171337
171963
  return accountId ?? LEGACY_CHANNEL_ACCOUNT_ID;
171338
171964
  }
171339
- var stores2, loadTargetStoreOverride = null, saveTargetStoreOverride = null;
171965
+ var stores3, loadTargetStoreOverride = null, saveTargetStoreOverride = null;
171340
171966
  var init_targets = __esm(() => {
171341
171967
  init_accounts();
171342
171968
  init_config2();
171343
- stores2 = new Map;
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 mkdirSync7 } from "node:fs";
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
- mkdirSync7(DISCORD_ATTACHMENTS_DIR, { recursive: true });
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 mkdirSync8, readFileSync as readFileSync8, rmSync as rmSync2, writeFileSync as writeFileSync5 } from "node:fs";
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(readFileSync8(join16(lockDir, "owner.json"), "utf8"));
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
- mkdirSync8(lockDir);
173709
- writeFileSync5(join16(lockDir, "owner.json"), `${JSON.stringify({
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
- mkdirSync8(authDir, { recursive: true });
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 mkdirSync9,
175462
+ mkdirSync as mkdirSync10,
175058
175463
  readdirSync as readdirSync2,
175059
- readFileSync as readFileSync9,
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
- mkdirSync9(inboundDir, { recursive: true });
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 = readFileSync9(localPath).toString("base64");
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 existsSync9 } from "node:fs";
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 && existsSync9(defaultDir) ? defaultDir : null;
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 existsSync10, readdirSync as readdirSync3, readFileSync as readFileSync10 } from "node:fs";
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 (!existsSync10(manifestPath)) {
177261
+ if (!existsSync11(manifestPath)) {
176857
177262
  return null;
176858
177263
  }
176859
177264
  try {
176860
- const parsed = JSON.parse(readFileSync10(manifestPath, "utf-8"));
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 (!existsSync10(channelsRoot)) {
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 channelDisplayName3(channelId) {
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 = channelDisplayName3(channelId);
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 = channelDisplayName3(channelId);
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 (config3.dmPolicy === "allowlist") {
178941
- if (!config3.allowedUsers.includes(msg.senderId)) {
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
 
@@ -441753,6 +441970,115 @@ var init_PasteAwareTextInput = __esm(async () => {
441753
441970
  OPTION_RIGHT_PATTERN = /^\u001b\[(?:1;)?(?:3|4|7|8|9)C$/;
441754
441971
  });
441755
441972
 
441973
+ // src/agent/favorites.ts
441974
+ function generateFavoriteTag(ownerId) {
441975
+ return `${LETTA_CHAT_FAVORITE_TAG_PREFIX}${ownerId}`;
441976
+ }
441977
+ async function fetchCurrentUserMetadata() {
441978
+ if (currentUserMetadataFetcherOverride) {
441979
+ return currentUserMetadataFetcherOverride();
441980
+ }
441981
+ return apiRequest("GET", "/v1/metadata/user");
441982
+ }
441983
+ async function getCurrentCloudFavoriteTag() {
441984
+ try {
441985
+ const user = await fetchCurrentUserMetadata();
441986
+ return typeof user.id === "string" && user.id ? generateFavoriteTag(user.id) : null;
441987
+ } catch {
441988
+ return null;
441989
+ }
441990
+ }
441991
+ async function getFavoriteTagForAgent(agentId) {
441992
+ if (isLocalAgentId(agentId)) {
441993
+ return LOCAL_DESKTOP_FAVORITE_TAG;
441994
+ }
441995
+ return getCurrentCloudFavoriteTag();
441996
+ }
441997
+ function getAgentTags(agent2) {
441998
+ return Array.isArray(agent2.tags) ? agent2.tags : [];
441999
+ }
442000
+ function addFavoriteTag(tags, favoriteTag) {
442001
+ if (tags.includes(favoriteTag))
442002
+ return tags;
442003
+ return [favoriteTag, ...tags];
442004
+ }
442005
+ function removeFavoriteTag(tags, favoriteTag) {
442006
+ return tags.filter((tag) => tag !== favoriteTag && tag !== LETTA_CHAT_FAVORITE_TAG_BASE);
442007
+ }
442008
+ function arraysEqual2(a2, b3) {
442009
+ return a2.length === b3.length && a2.every((value, index) => value === b3[index]);
442010
+ }
442011
+ async function setAgentFavoriteTag(backend4, agentId) {
442012
+ const favoriteTag = await getFavoriteTagForAgent(agentId);
442013
+ if (!favoriteTag)
442014
+ return "unavailable";
442015
+ const agent2 = await backend4.retrieveAgent(agentId, {
442016
+ include: ["agent.tags"]
442017
+ });
442018
+ const tags = getAgentTags(agent2);
442019
+ const nextTags = addFavoriteTag(tags, favoriteTag);
442020
+ if (arraysEqual2(tags, nextTags))
442021
+ return "unchanged";
442022
+ await backend4.updateAgent(agentId, { tags: nextTags });
442023
+ return "changed";
442024
+ }
442025
+ async function unsetAgentFavoriteTag(backend4, agentId) {
442026
+ const favoriteTag = await getFavoriteTagForAgent(agentId);
442027
+ if (!favoriteTag)
442028
+ return "unavailable";
442029
+ const agent2 = await backend4.retrieveAgent(agentId, {
442030
+ include: ["agent.tags"]
442031
+ });
442032
+ const tags = getAgentTags(agent2);
442033
+ const nextTags = removeFavoriteTag(tags, favoriteTag);
442034
+ if (arraysEqual2(tags, nextTags))
442035
+ return "unchanged";
442036
+ await backend4.updateAgent(agentId, { tags: nextTags });
442037
+ return "changed";
442038
+ }
442039
+ async function pinAgentForCurrentUser(agentId, backend4 = getBackend()) {
442040
+ const wasPinnedInSettings = settingsManager.isAgentPinned(agentId);
442041
+ try {
442042
+ const result = await setAgentFavoriteTag(backend4, agentId);
442043
+ if (result !== "unavailable") {
442044
+ if (wasPinnedInSettings) {
442045
+ settingsManager.unpinAgent(agentId);
442046
+ }
442047
+ return result === "unchanged" || wasPinnedInSettings ? "already-pinned" : "pinned";
442048
+ }
442049
+ } catch {}
442050
+ if (wasPinnedInSettings)
442051
+ return "already-pinned";
442052
+ settingsManager.pinAgent(agentId);
442053
+ return "pinned";
442054
+ }
442055
+ async function unpinAgentForCurrentUser(agentId, backend4 = getBackend()) {
442056
+ const wasPinnedInSettings = settingsManager.isAgentPinned(agentId);
442057
+ try {
442058
+ const result = await unsetAgentFavoriteTag(backend4, agentId);
442059
+ if (result !== "unavailable") {
442060
+ if (wasPinnedInSettings) {
442061
+ settingsManager.unpinAgent(agentId);
442062
+ }
442063
+ return result === "changed" || wasPinnedInSettings ? "unpinned" : "not-pinned";
442064
+ }
442065
+ } catch (error54) {
442066
+ if (!wasPinnedInSettings)
442067
+ throw error54;
442068
+ }
442069
+ if (!wasPinnedInSettings)
442070
+ return "not-pinned";
442071
+ settingsManager.unpinAgent(agentId);
442072
+ return "unpinned";
442073
+ }
442074
+ var LETTA_CHAT_FAVORITE_TAG_BASE = "view:letta-chat", LETTA_CHAT_FAVORITE_TAG_PREFIX = "favorite:user:", LOCAL_FAVORITE_OWNER_ID = "local", LOCAL_DESKTOP_FAVORITE_TAG, currentUserMetadataFetcherOverride = null;
442075
+ var init_favorites = __esm(() => {
442076
+ init_backend2();
442077
+ init_request();
442078
+ init_settings_manager();
442079
+ LOCAL_DESKTOP_FAVORITE_TAG = generateFavoriteTag(LOCAL_FAVORITE_OWNER_ID);
442080
+ });
442081
+
441756
442082
  // src/backend/local/index.ts
441757
442083
  var init_local = __esm(() => {
441758
442084
  init_context_window_overflow();
@@ -441796,6 +442122,78 @@ var init_local_agent_listing = __esm(() => {
441796
442122
  init_paths();
441797
442123
  });
441798
442124
 
442125
+ // src/cli/helpers/pinned-agent-listing.ts
442126
+ function hasCloudCredentials2() {
442127
+ if (process.env.LETTA_API_KEY)
442128
+ return true;
442129
+ const settings3 = settingsManager.getSettings();
442130
+ const cached3 = settingsManager.getCachedSecureTokens();
442131
+ return Boolean(cached3.apiKey || cached3.refreshToken || settings3.refreshToken || settings3.env?.LETTA_API_KEY);
442132
+ }
442133
+ async function listCloudFavoriteAgents2() {
442134
+ if (!hasCloudCredentials2())
442135
+ return [];
442136
+ try {
442137
+ const favoriteTag = await getCurrentCloudFavoriteTag();
442138
+ if (!favoriteTag)
442139
+ return [];
442140
+ const page = await getBackendForMode("api").listAgents({
442141
+ limit: PINNED_AGENT_LIMIT2,
442142
+ include: ["agent.blocks"],
442143
+ order: "desc",
442144
+ order_by: "last_run_completion",
442145
+ tags: [favoriteTag]
442146
+ });
442147
+ return Array.isArray(page) ? page : page.items ?? [];
442148
+ } catch {
442149
+ return [];
442150
+ }
442151
+ }
442152
+ async function retrieveLegacyPin2(agentId, backendMode) {
442153
+ if (backendMode === "api" && !hasCloudCredentials2()) {
442154
+ return { agentId, agent: null, error: "Not signed in", backendMode };
442155
+ }
442156
+ try {
442157
+ const agent2 = await getBackendForMode(backendMode).retrieveAgent(agentId, {
442158
+ include: ["agent.blocks"]
442159
+ });
442160
+ return { agentId, agent: agent2, error: null, backendMode };
442161
+ } catch {
442162
+ return { agentId, agent: null, error: "Agent not found", backendMode };
442163
+ }
442164
+ }
442165
+ async function listPinnedAgentsForCurrentUser2(backendModes = ["api", "local"]) {
442166
+ const modes = new Set(backendModes);
442167
+ const legacyPins = [...modes].flatMap((backendMode) => settingsManager.getPinnedAgentsForBackendMode(backendMode).map((agentId) => ({ agentId, backendMode })));
442168
+ const seen = new Set(legacyPins.map(({ agentId, backendMode }) => `${backendMode}:${agentId}`));
442169
+ const legacyData = await Promise.all(legacyPins.map(({ agentId, backendMode }) => retrieveLegacyPin2(agentId, backendMode)));
442170
+ const favoriteAgents = [];
442171
+ if (modes.has("local")) {
442172
+ favoriteAgents.push(...listLocalAgentsFromDisk().filter((agent2) => getAgentTags(agent2).includes(LOCAL_DESKTOP_FAVORITE_TAG)).map((agent2) => ({ agent: agent2, backendMode: "local" })));
442173
+ }
442174
+ if (modes.has("api")) {
442175
+ favoriteAgents.push(...(await listCloudFavoriteAgents2()).map((agent2) => ({
442176
+ agent: agent2,
442177
+ backendMode: "api"
442178
+ })));
442179
+ }
442180
+ const favoriteData = favoriteAgents.flatMap(({ agent: agent2, backendMode }) => {
442181
+ const key2 = `${backendMode}:${agent2.id}`;
442182
+ if (seen.has(key2))
442183
+ return [];
442184
+ seen.add(key2);
442185
+ return [{ agentId: agent2.id, agent: agent2, error: null, backendMode }];
442186
+ });
442187
+ return [...legacyData, ...favoriteData];
442188
+ }
442189
+ var PINNED_AGENT_LIMIT2 = 100;
442190
+ var init_pinned_agent_listing = __esm(() => {
442191
+ init_favorites();
442192
+ init_backend();
442193
+ init_settings_manager();
442194
+ init_local_agent_listing();
442195
+ });
442196
+
441799
442197
  // src/cli/components/ModelReasoningSelector.tsx
441800
442198
  function formatEffortLabel(effort, hasDistinctMaxTier) {
441801
442199
  if (effort === "none")
@@ -448069,6 +448467,100 @@ var init_cron = __esm(async () => {
448069
448467
  await init_scheduler();
448070
448468
  });
448071
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
+
448072
448564
  // src/backend/api/search.ts
448073
448565
  async function warmSearchCache(body3) {
448074
448566
  return apiRequest("POST", "/v1/_internal_search/cache-warm", body3);
@@ -450426,89 +450918,6 @@ var init_reflection_launcher = __esm(() => {
450426
450918
  pendingReflectionLaunches = new Map;
450427
450919
  });
450428
450920
 
450429
- // src/backend/api/environments.ts
450430
- async function listEnvironments(options3 = {}) {
450431
- return apiRequest("GET", "/v1/environments", undefined, {
450432
- query: {
450433
- limit: options3.limit,
450434
- after: options3.after,
450435
- onlineOnly: options3.onlineOnly
450436
- }
450437
- });
450438
- }
450439
- async function sendEnvironmentMessage(connectionId, body3) {
450440
- return apiRequest("POST", `/v1/environments/${encodeURIComponent(connectionId)}/messages`, body3);
450441
- }
450442
- async function getEnvironmentConnection(deviceId) {
450443
- return apiRequest("GET", `/v1/environments/${encodeURIComponent(deviceId)}`);
450444
- }
450445
- async function createAgentSandbox(agentId) {
450446
- return apiRequest("POST", `/v1/agents/${encodeURIComponent(agentId)}/sandboxes`, {});
450447
- }
450448
- function isEnvironmentOnline(environment2) {
450449
- return typeof environment2.connectionId === "string" && environment2.connectionId.length > 0 && typeof environment2.lastHeartbeat === "number" && Date.now() - environment2.lastHeartbeat < 120000;
450450
- }
450451
- function describeEnvironment(environment2) {
450452
- const status = isEnvironmentOnline(environment2) ? "online" : "offline";
450453
- return `${environment2.connectionName} (${environment2.deviceId}, ${status})`;
450454
- }
450455
- async function resolveEnvironmentConnectionId(selector) {
450456
- const trimmed = selector.trim();
450457
- if (!trimmed) {
450458
- throw new Error("Environment selector must not be empty");
450459
- }
450460
- const response = await listEnvironments({ limit: 100 });
450461
- const matches2 = response.connections.filter((environment3) => {
450462
- return environment3.connectionId === trimmed || environment3.id === trimmed || environment3.deviceId === trimmed || environment3.connectionName === trimmed;
450463
- });
450464
- if (matches2.length === 0) {
450465
- throw new Error(`Environment "${trimmed}" not found. Run \`letta environments list\` to discover available environments.`);
450466
- }
450467
- const onlineMatches = matches2.filter(isEnvironmentOnline);
450468
- if (onlineMatches.length === 0) {
450469
- throw new Error(`Environment "${trimmed}" is offline. Matched: ${matches2.map(describeEnvironment).join(", ")}`);
450470
- }
450471
- if (onlineMatches.length > 1) {
450472
- throw new Error(`Environment "${trimmed}" is ambiguous. Matched: ${onlineMatches.map(describeEnvironment).join(", ")}`);
450473
- }
450474
- const environment2 = onlineMatches[0];
450475
- if (!environment2) {
450476
- throw new Error(`Environment "${trimmed}" is offline`);
450477
- }
450478
- if (!environment2.connectionId) {
450479
- throw new Error(`Environment "${trimmed}" has no active connection id`);
450480
- }
450481
- return { connectionId: environment2.connectionId, environment: environment2 };
450482
- }
450483
- async function resolveAgentSandboxConnectionId(agentId, options3 = {}) {
450484
- const timeoutMs = options3.timeoutMs ?? 3 * 60000;
450485
- const pollIntervalMs = options3.pollIntervalMs ?? 2000;
450486
- const sandbox = await createAgentSandbox(agentId);
450487
- const deviceId = sandbox.deviceId || `sandbox-${agentId}`;
450488
- const deadline = Date.now() + timeoutMs;
450489
- let lastEnvironment = null;
450490
- let lastError = null;
450491
- while (Date.now() < deadline) {
450492
- try {
450493
- const environment2 = await getEnvironmentConnection(deviceId);
450494
- lastEnvironment = environment2;
450495
- if (isEnvironmentOnline(environment2) && environment2.connectionId) {
450496
- return { connectionId: environment2.connectionId, environment: environment2 };
450497
- }
450498
- } catch (error54) {
450499
- lastError = error54;
450500
- }
450501
- await new Promise((resolve31) => setTimeout(resolve31, pollIntervalMs));
450502
- }
450503
- if (lastEnvironment) {
450504
- throw new Error(`Timed out waiting for cloud sandbox ${sandbox.connectionName} to come online. Last status: ${describeEnvironment(lastEnvironment)}`);
450505
- }
450506
- throw new Error(`Timed out waiting for cloud sandbox ${sandbox.connectionName} to register${lastError instanceof Error ? `: ${lastError.message}` : ""}`);
450507
- }
450508
- var init_environments2 = __esm(() => {
450509
- init_request();
450510
- });
450511
-
450512
450921
  // node_modules/cli-spinners/spinners.json
450513
450922
  var require_spinners = __commonJS((exports, module3) => {
450514
450923
  module3.exports = {
@@ -473059,115 +473468,6 @@ var init_setup_ui = __esm(async () => {
473059
473468
  jsx_dev_runtime14 = __toESM(require_jsx_dev_runtime(), 1);
473060
473469
  });
473061
473470
 
473062
- // src/agent/favorites.ts
473063
- function generateFavoriteTag(ownerId) {
473064
- return `${LETTA_CHAT_FAVORITE_TAG_PREFIX}${ownerId}`;
473065
- }
473066
- async function fetchCurrentUserMetadata() {
473067
- if (currentUserMetadataFetcherOverride) {
473068
- return currentUserMetadataFetcherOverride();
473069
- }
473070
- return apiRequest("GET", "/v1/metadata/user");
473071
- }
473072
- async function getCurrentCloudFavoriteTag() {
473073
- try {
473074
- const user = await fetchCurrentUserMetadata();
473075
- return typeof user.id === "string" && user.id ? generateFavoriteTag(user.id) : null;
473076
- } catch {
473077
- return null;
473078
- }
473079
- }
473080
- async function getFavoriteTagForAgent(agentId) {
473081
- if (isLocalAgentId(agentId)) {
473082
- return LOCAL_DESKTOP_FAVORITE_TAG;
473083
- }
473084
- return getCurrentCloudFavoriteTag();
473085
- }
473086
- function getAgentTags(agent2) {
473087
- return Array.isArray(agent2.tags) ? agent2.tags : [];
473088
- }
473089
- function addFavoriteTag(tags, favoriteTag) {
473090
- if (tags.includes(favoriteTag))
473091
- return tags;
473092
- return [favoriteTag, ...tags];
473093
- }
473094
- function removeFavoriteTag(tags, favoriteTag) {
473095
- return tags.filter((tag) => tag !== favoriteTag && tag !== LETTA_CHAT_FAVORITE_TAG_BASE);
473096
- }
473097
- function arraysEqual2(a2, b3) {
473098
- return a2.length === b3.length && a2.every((value, index) => value === b3[index]);
473099
- }
473100
- async function setAgentFavoriteTag(backend4, agentId) {
473101
- const favoriteTag = await getFavoriteTagForAgent(agentId);
473102
- if (!favoriteTag)
473103
- return "unavailable";
473104
- const agent2 = await backend4.retrieveAgent(agentId, {
473105
- include: ["agent.tags"]
473106
- });
473107
- const tags = getAgentTags(agent2);
473108
- const nextTags = addFavoriteTag(tags, favoriteTag);
473109
- if (arraysEqual2(tags, nextTags))
473110
- return "unchanged";
473111
- await backend4.updateAgent(agentId, { tags: nextTags });
473112
- return "changed";
473113
- }
473114
- async function unsetAgentFavoriteTag(backend4, agentId) {
473115
- const favoriteTag = await getFavoriteTagForAgent(agentId);
473116
- if (!favoriteTag)
473117
- return "unavailable";
473118
- const agent2 = await backend4.retrieveAgent(agentId, {
473119
- include: ["agent.tags"]
473120
- });
473121
- const tags = getAgentTags(agent2);
473122
- const nextTags = removeFavoriteTag(tags, favoriteTag);
473123
- if (arraysEqual2(tags, nextTags))
473124
- return "unchanged";
473125
- await backend4.updateAgent(agentId, { tags: nextTags });
473126
- return "changed";
473127
- }
473128
- async function pinAgentForCurrentUser(agentId, backend4 = getBackend()) {
473129
- const wasPinnedInSettings = settingsManager.isAgentPinned(agentId);
473130
- try {
473131
- const result = await setAgentFavoriteTag(backend4, agentId);
473132
- if (result !== "unavailable") {
473133
- if (wasPinnedInSettings) {
473134
- settingsManager.unpinAgent(agentId);
473135
- }
473136
- return result === "unchanged" || wasPinnedInSettings ? "already-pinned" : "pinned";
473137
- }
473138
- } catch {}
473139
- if (wasPinnedInSettings)
473140
- return "already-pinned";
473141
- settingsManager.pinAgent(agentId);
473142
- return "pinned";
473143
- }
473144
- async function unpinAgentForCurrentUser(agentId, backend4 = getBackend()) {
473145
- const wasPinnedInSettings = settingsManager.isAgentPinned(agentId);
473146
- try {
473147
- const result = await unsetAgentFavoriteTag(backend4, agentId);
473148
- if (result !== "unavailable") {
473149
- if (wasPinnedInSettings) {
473150
- settingsManager.unpinAgent(agentId);
473151
- }
473152
- return result === "changed" || wasPinnedInSettings ? "unpinned" : "not-pinned";
473153
- }
473154
- } catch (error54) {
473155
- if (!wasPinnedInSettings)
473156
- throw error54;
473157
- }
473158
- if (!wasPinnedInSettings)
473159
- return "not-pinned";
473160
- settingsManager.unpinAgent(agentId);
473161
- return "unpinned";
473162
- }
473163
- var LETTA_CHAT_FAVORITE_TAG_BASE = "view:letta-chat", LETTA_CHAT_FAVORITE_TAG_PREFIX = "favorite:user:", LOCAL_FAVORITE_OWNER_ID = "local", LOCAL_DESKTOP_FAVORITE_TAG, currentUserMetadataFetcherOverride = null;
473164
- var init_favorites = __esm(() => {
473165
- init_backend2();
473166
- init_request();
473167
- init_settings_manager();
473168
- LOCAL_DESKTOP_FAVORITE_TAG = generateFavoriteTag(LOCAL_FAVORITE_OWNER_ID);
473169
- });
473170
-
473171
473471
  // src/cli/components/OverlayShell.tsx
473172
473472
  function OverlayShell({
473173
473473
  command,
@@ -473457,20 +473757,8 @@ var init_TabBar = __esm(async () => {
473457
473757
  // src/cli/components/AgentSelector.tsx
473458
473758
  var exports_AgentSelector = {};
473459
473759
  __export(exports_AgentSelector, {
473460
- getPinnedAgentBackendMode: () => getPinnedAgentBackendMode,
473461
473760
  AgentSelector: () => AgentSelector
473462
473761
  });
473463
- function getPinnedAgentBackendMode(agentId) {
473464
- return isLocalAgentId(agentId) ? "local" : "api";
473465
- }
473466
- function hasCloudCredentials() {
473467
- const apiKey = process.env.LETTA_API_KEY;
473468
- if (apiKey)
473469
- return true;
473470
- const settings3 = settingsManager.getSettings();
473471
- const cached3 = settingsManager.getCachedSecureTokens();
473472
- return Boolean(cached3.apiKey || cached3.refreshToken || settings3.refreshToken || settings3.env?.LETTA_API_KEY);
473473
- }
473474
473762
  function formatRelativeTime3(dateStr) {
473475
473763
  if (!dateStr)
473476
473764
  return "Never";
@@ -473576,77 +473864,7 @@ function AgentSelector({
473576
473864
  const loadPinnedAgents = import_react38.useCallback(async () => {
473577
473865
  setPinnedLoading(true);
473578
473866
  try {
473579
- const pinnedIds = settingsManager.getPinnedAgents();
473580
- const pinnedIdSet = new Set(pinnedIds);
473581
- const localFavoriteAgents = listLocalAgentsFromDisk().filter((agent2) => agent2.tags.includes(LOCAL_DESKTOP_FAVORITE_TAG));
473582
- let cloudFavoriteAgents = [];
473583
- if (hasCloudCredentials()) {
473584
- try {
473585
- const favoriteTag = await getCurrentCloudFavoriteTag();
473586
- if (favoriteTag) {
473587
- const { getClient: getClient2 } = await Promise.resolve().then(() => (init_client2(), exports_client));
473588
- const client = await getClient2();
473589
- const agentList = await client.agents.list({
473590
- limit: FETCH_PAGE_SIZE2,
473591
- include: ["agent.blocks"],
473592
- order: "desc",
473593
- order_by: "last_run_completion",
473594
- tags: [favoriteTag]
473595
- });
473596
- cloudFavoriteAgents = agentList.items;
473597
- }
473598
- } catch {}
473599
- }
473600
- let pinnedData = [];
473601
- if (pinnedIds.length > 0) {
473602
- pinnedData = await Promise.all(pinnedIds.map(async (agentId) => {
473603
- const backendMode = getPinnedAgentBackendMode(agentId);
473604
- try {
473605
- if (backendMode === "api" && !hasCloudCredentials()) {
473606
- return {
473607
- agentId,
473608
- agent: null,
473609
- error: "Not signed in",
473610
- backendMode
473611
- };
473612
- }
473613
- const agentBackend = getBackendForMode(backendMode);
473614
- const agent2 = await agentBackend.retrieveAgent(agentId, {
473615
- include: ["agent.blocks"]
473616
- });
473617
- return {
473618
- agentId,
473619
- agent: agent2,
473620
- error: null,
473621
- backendMode
473622
- };
473623
- } catch {
473624
- return {
473625
- agentId,
473626
- agent: null,
473627
- error: "Agent not found",
473628
- backendMode
473629
- };
473630
- }
473631
- }));
473632
- }
473633
- const favoriteAgents = [
473634
- ...localFavoriteAgents.map((agent2) => ({
473635
- agent: agent2,
473636
- backendMode: "local"
473637
- })),
473638
- ...cloudFavoriteAgents.map((agent2) => ({
473639
- agent: agent2,
473640
- backendMode: "api"
473641
- }))
473642
- ];
473643
- const favoriteData = favoriteAgents.filter(({ agent: agent2 }) => !pinnedIdSet.has(agent2.id)).map((agent2) => ({
473644
- agentId: agent2.agent.id,
473645
- agent: agent2.agent,
473646
- error: null,
473647
- backendMode: agent2.backendMode
473648
- }));
473649
- pinnedData = [...pinnedData, ...favoriteData];
473867
+ const pinnedData = await listPinnedAgentsForCurrentUser2();
473650
473868
  const validPinnedData = pinnedData.filter((p2) => p2.agent !== null);
473651
473869
  if (validPinnedData.length === 0) {
473652
473870
  setPinnedAgents([]);
@@ -473728,7 +473946,7 @@ function AgentSelector({
473728
473946
  activeQuery
473729
473947
  ]);
473730
473948
  import_react38.useEffect(() => {
473731
- setHasCloudAuth(hasCloudCredentials());
473949
+ setHasCloudAuth(hasCloudCredentials2());
473732
473950
  }, []);
473733
473951
  import_react38.useEffect(() => {
473734
473952
  loadPinnedAgents();
@@ -473985,13 +474203,13 @@ function AgentSelector({
473985
474203
  });
473986
474204
  const renderAgentItem = (agent2, _index, isSelected, extra) => {
473987
474205
  const isCurrent = agent2.id === currentAgentId;
473988
- const isLocalAgent = isLocalAgentId(agent2.id);
474206
+ const isLocalAgent2 = isLocalAgentId(agent2.id);
473989
474207
  const relativeTime = formatRelativeTime3(agent2.last_run_completion);
473990
474208
  const blockCount = agent2.blocks?.length ?? 0;
473991
474209
  const modelStr = formatModel2(agent2);
473992
474210
  const metadataParts = [
473993
474211
  relativeTime,
473994
- ...isLocalAgent ? [] : [`${blockCount} memory block${blockCount === 1 ? "" : "s"}`],
474212
+ ...isLocalAgent2 ? [] : [`${blockCount} memory block${blockCount === 1 ? "" : "s"}`],
473995
474213
  modelStr
473996
474214
  ];
473997
474215
  const nameLen = (agent2.name || "Unnamed").length;
@@ -474458,9 +474676,9 @@ var init_AgentSelector = __esm(async () => {
474458
474676
  init_model();
474459
474677
  init_backend();
474460
474678
  init_local_agent_listing();
474679
+ init_pinned_agent_listing();
474461
474680
  init_use_terminal_width();
474462
474681
  init_constants2();
474463
- init_settings_manager();
474464
474682
  init_colors();
474465
474683
  await __promiseAll([
474466
474684
  init_build4(),
@@ -481969,7 +482187,7 @@ var init_reflection_arena_hf_upload = __esm(() => {
481969
482187
 
481970
482188
  // src/cli/helpers/reflection-arena.ts
481971
482189
  import { execFile as execFileCb6 } from "node:child_process";
481972
- import { randomInt, randomUUID as randomUUID32 } from "node:crypto";
482190
+ import { randomInt as randomInt2, randomUUID as randomUUID32 } from "node:crypto";
481973
482191
  import { appendFile as appendFile3, mkdir as mkdir18, readFile as readFile26, writeFile as writeFile20 } from "node:fs/promises";
481974
482192
  import { homedir as homedir39 } from "node:os";
481975
482193
  import { join as join71 } from "node:path";
@@ -481980,7 +482198,7 @@ function sampleReflectionArenaComparisonModel(excludedModels = []) {
481980
482198
  const totalWeight = candidates2.reduce((sum, entry) => sum + Math.max(0, entry.weight), 0);
481981
482199
  if (totalWeight <= 0)
481982
482200
  return "letta/auto";
481983
- let offset = randomInt(totalWeight);
482201
+ let offset = randomInt2(totalWeight);
481984
482202
  for (const candidate of candidates2) {
481985
482203
  offset -= Math.max(0, candidate.weight);
481986
482204
  if (offset < 0)
@@ -482045,7 +482263,7 @@ async function updateReflectionArenaRun(runId, update2) {
482045
482263
  return updated;
482046
482264
  }
482047
482265
  function shuffledLabels() {
482048
- return randomInt(2) === 0 ? ["1", "2"] : ["2", "1"];
482266
+ return randomInt2(2) === 0 ? ["1", "2"] : ["2", "1"];
482049
482267
  }
482050
482268
  function truncateReport(report) {
482051
482269
  if (!report?.trim())
@@ -492784,8 +493002,8 @@ var init_AgentInfoBar = __esm(async () => {
492784
493002
  return settingsManager.isAgentPinned(agentId);
492785
493003
  }, [agentId]);
492786
493004
  const isCloudUser = serverUrl?.includes("api.letta.com");
492787
- const isLocalAgent = agentId ? isLocalAgentId2(agentId) : false;
492788
- const showCloudLinks = Boolean(isCloudUser && agentId && !isLocalAgent);
493005
+ const isLocalAgent2 = agentId ? isLocalAgentId2(agentId) : false;
493006
+ const showCloudLinks = Boolean(isCloudUser && agentId && !isLocalAgent2);
492789
493007
  const adeConversationUrl = showCloudLinks && agentId && agentId !== "loading" ? buildChatUrl(agentId, { conversationId }) : "";
492790
493008
  const usageUrl = buildChatWebUrl("/preferences/usage");
492791
493009
  const showBottomBar = agentId && agentId !== "loading";
@@ -501169,8 +501387,8 @@ function MemfsTreeViewer({
501169
501387
  const terminalWidth = useTerminalWidth();
501170
501388
  const solidLine = SOLID_LINE15.repeat(Math.max(terminalWidth, 10));
501171
501389
  const isTmux = Boolean(process.env.TMUX);
501172
- const isLocalAgent = isLocalAgentId2(agentId);
501173
- const adeUrl = isLocalAgent ? null : buildChatUrl(agentId, { view: "memory", conversationId });
501390
+ const isLocalAgent2 = isLocalAgentId2(agentId);
501391
+ const adeUrl = isLocalAgent2 ? null : buildChatUrl(agentId, { view: "memory", conversationId });
501174
501392
  const [selectedIndex, setSelectedIndex] = import_react90.useState(0);
501175
501393
  const [treeScrollOffset, setTreeScrollOffset] = import_react90.useState(0);
501176
501394
  const [viewMode, setViewMode] = import_react90.useState("split");
@@ -501661,8 +501879,8 @@ function MemoryTabViewer({
501661
501879
  const terminalWidth = useTerminalWidth();
501662
501880
  const solidLine = SOLID_LINE16.repeat(Math.max(terminalWidth, 10));
501663
501881
  const isTmux = Boolean(process.env.TMUX);
501664
- const isLocalAgent = isLocalAgentId2(agentId);
501665
- const adeUrl = isLocalAgent ? null : buildChatUrl(agentId, { view: "memory", conversationId });
501882
+ const isLocalAgent2 = isLocalAgentId2(agentId);
501883
+ const adeUrl = isLocalAgent2 ? null : buildChatUrl(agentId, { view: "memory", conversationId });
501666
501884
  const [selectedTabIndex, setSelectedTabIndex] = import_react91.useState(0);
501667
501885
  const [scrollOffset, setScrollOffset] = import_react91.useState(0);
501668
501886
  const [freshBlocks, setFreshBlocks] = import_react91.useState(null);
@@ -537640,7 +537858,7 @@ function buildStartupCommandHints(options3) {
537640
537858
  isPinned,
537641
537859
  isLocalBackend,
537642
537860
  hasMessages,
537643
- hasCloudCredentials: hasCloudCredentials2,
537861
+ hasCloudCredentials: hasCloudCredentials3,
537644
537862
  hasAvailableLocalModels
537645
537863
  } = options3;
537646
537864
  const baseHints = isResumingConversation ? [
@@ -537669,7 +537887,7 @@ function buildStartupCommandHints(options3) {
537669
537887
  if (!hasMessages) {
537670
537888
  onboardingHints.push("→ **/rename** name your agent", "→ **/init** initialize your agent's memory");
537671
537889
  }
537672
- if (!hasCloudCredentials2) {
537890
+ if (!hasCloudCredentials3) {
537673
537891
  onboardingHints.push("→ **/login** sign in to Constellation");
537674
537892
  }
537675
537893
  const dedupedHints = [];
@@ -541602,24 +541820,22 @@ function resolveStartupTarget(input) {
541602
541820
  if (input.forceNew) {
541603
541821
  return { action: "create", trigger: "force-new" };
541604
541822
  }
541605
- if (input.pinnedAgentId && input.pinnedAgentExists) {
541606
- const conversationId = input.pinnedAgentId === input.localAgentId ? input.localConversationId ?? undefined : undefined;
541823
+ if (input.localAgentId && input.localAgentExists) {
541607
541824
  return {
541608
541825
  action: "resume",
541609
- agentId: input.pinnedAgentId,
541610
- ...conversationId ? { conversationId } : {}
541826
+ agentId: input.localAgentId,
541827
+ conversationId: input.localConversationId ?? undefined
541611
541828
  };
541612
541829
  }
541613
- if (input.existingPinnedCount > 1) {
541614
- return { action: "select" };
541615
- }
541616
- if (input.localAgentId && input.localAgentExists) {
541830
+ if (input.pinnedAgentId && input.pinnedAgentExists) {
541617
541831
  return {
541618
541832
  action: "resume",
541619
- agentId: input.localAgentId,
541620
- conversationId: input.localConversationId ?? undefined
541833
+ agentId: input.pinnedAgentId
541621
541834
  };
541622
541835
  }
541836
+ if (input.existingPinnedCount > 1) {
541837
+ return { action: "select" };
541838
+ }
541623
541839
  if (input.globalAgentId && input.globalAgentExists) {
541624
541840
  return {
541625
541841
  action: "resume",
@@ -546400,6 +546616,76 @@ async function ensureFdPath() {
546400
546616
  return pendingFdPath;
546401
546617
  }
546402
546618
 
546619
+ // src/cli/helpers/pinned-agent-listing.ts
546620
+ init_favorites();
546621
+ init_backend();
546622
+ init_settings_manager();
546623
+ init_local_agent_listing();
546624
+ var PINNED_AGENT_LIMIT = 100;
546625
+ function hasCloudCredentials() {
546626
+ if (process.env.LETTA_API_KEY)
546627
+ return true;
546628
+ const settings3 = settingsManager.getSettings();
546629
+ const cached3 = settingsManager.getCachedSecureTokens();
546630
+ return Boolean(cached3.apiKey || cached3.refreshToken || settings3.refreshToken || settings3.env?.LETTA_API_KEY);
546631
+ }
546632
+ async function listCloudFavoriteAgents() {
546633
+ if (!hasCloudCredentials())
546634
+ return [];
546635
+ try {
546636
+ const favoriteTag = await getCurrentCloudFavoriteTag();
546637
+ if (!favoriteTag)
546638
+ return [];
546639
+ const page = await getBackendForMode("api").listAgents({
546640
+ limit: PINNED_AGENT_LIMIT,
546641
+ include: ["agent.blocks"],
546642
+ order: "desc",
546643
+ order_by: "last_run_completion",
546644
+ tags: [favoriteTag]
546645
+ });
546646
+ return Array.isArray(page) ? page : page.items ?? [];
546647
+ } catch {
546648
+ return [];
546649
+ }
546650
+ }
546651
+ async function retrieveLegacyPin(agentId, backendMode) {
546652
+ if (backendMode === "api" && !hasCloudCredentials()) {
546653
+ return { agentId, agent: null, error: "Not signed in", backendMode };
546654
+ }
546655
+ try {
546656
+ const agent2 = await getBackendForMode(backendMode).retrieveAgent(agentId, {
546657
+ include: ["agent.blocks"]
546658
+ });
546659
+ return { agentId, agent: agent2, error: null, backendMode };
546660
+ } catch {
546661
+ return { agentId, agent: null, error: "Agent not found", backendMode };
546662
+ }
546663
+ }
546664
+ async function listPinnedAgentsForCurrentUser(backendModes = ["api", "local"]) {
546665
+ const modes = new Set(backendModes);
546666
+ const legacyPins = [...modes].flatMap((backendMode) => settingsManager.getPinnedAgentsForBackendMode(backendMode).map((agentId) => ({ agentId, backendMode })));
546667
+ const seen = new Set(legacyPins.map(({ agentId, backendMode }) => `${backendMode}:${agentId}`));
546668
+ const legacyData = await Promise.all(legacyPins.map(({ agentId, backendMode }) => retrieveLegacyPin(agentId, backendMode)));
546669
+ const favoriteAgents = [];
546670
+ if (modes.has("local")) {
546671
+ favoriteAgents.push(...listLocalAgentsFromDisk().filter((agent2) => getAgentTags(agent2).includes(LOCAL_DESKTOP_FAVORITE_TAG)).map((agent2) => ({ agent: agent2, backendMode: "local" })));
546672
+ }
546673
+ if (modes.has("api")) {
546674
+ favoriteAgents.push(...(await listCloudFavoriteAgents()).map((agent2) => ({
546675
+ agent: agent2,
546676
+ backendMode: "api"
546677
+ })));
546678
+ }
546679
+ const favoriteData = favoriteAgents.flatMap(({ agent: agent2, backendMode }) => {
546680
+ const key2 = `${backendMode}:${agent2.id}`;
546681
+ if (seen.has(key2))
546682
+ return [];
546683
+ seen.add(key2);
546684
+ return [{ agentId: agent2.id, agent: agent2, error: null, backendMode }];
546685
+ });
546686
+ return [...legacyData, ...favoriteData];
546687
+ }
546688
+
546403
546689
  // src/cli/helpers/terminal-theme.ts
546404
546690
  var TERMINAL_THEME_STATE_KEY2 = Symbol.for("letta.terminalThemeState");
546405
546691
  function getTerminalThemeState2() {
@@ -546502,6 +546788,7 @@ async function initTerminalTheme() {
546502
546788
  // src/cli/profile-selection.tsx
546503
546789
  init_model();
546504
546790
  init_backend2();
546791
+ init_pinned_agent_listing();
546505
546792
  await init_build4();
546506
546793
  var import_react30 = __toESM(require_react(), 1);
546507
546794
 
@@ -546603,32 +546890,6 @@ function getLabel(option2, _freshRepoMode) {
546603
546890
  parts.push("pinned");
546604
546891
  return parts.length > 0 ? ` (${parts.join(", ")})` : "";
546605
546892
  }
546606
- function buildInitialProfileOptions(lruAgentId) {
546607
- const pinned = settingsManager.getPinnedAgents();
546608
- const options3 = [];
546609
- const seenAgentIds = new Set;
546610
- if (lruAgentId) {
546611
- options3.push({
546612
- name: null,
546613
- agentId: lruAgentId,
546614
- isLru: true,
546615
- agent: null
546616
- });
546617
- seenAgentIds.add(lruAgentId);
546618
- }
546619
- for (const agentId of pinned) {
546620
- if (seenAgentIds.has(agentId))
546621
- continue;
546622
- options3.push({
546623
- name: null,
546624
- agentId,
546625
- isLru: false,
546626
- agent: null
546627
- });
546628
- seenAgentIds.add(agentId);
546629
- }
546630
- return options3;
546631
- }
546632
546893
  function ProfileSelectionUI({
546633
546894
  lruAgentId,
546634
546895
  externalLoading,
@@ -546639,8 +546900,9 @@ function ProfileSelectionUI({
546639
546900
  serverBaseUrl,
546640
546901
  onComplete
546641
546902
  }) {
546642
- const [options3, setOptions] = import_react30.useState(() => externalLoading ? [] : buildInitialProfileOptions(lruAgentId));
546643
- const loading = externalLoading;
546903
+ const [options3, setOptions] = import_react30.useState([]);
546904
+ const [optionsLoading, setOptionsLoading] = import_react30.useState(true);
546905
+ const loading = externalLoading || optionsLoading;
546644
546906
  const [selectedIndex, setSelectedIndex] = import_react30.useState(0);
546645
546907
  const [showAll, setShowAll] = import_react30.useState(false);
546646
546908
  const [selectingModel, setSelectingModel] = import_react30.useState(!!(serverModelsForNewAgent && serverModelsForNewAgent.length > 0));
@@ -546648,45 +546910,45 @@ function ProfileSelectionUI({
546648
546910
  const [modelSearchQuery, setModelSearchQuery] = import_react30.useState("");
546649
546911
  const [modelReasoningPrompt, setModelReasoningPrompt] = import_react30.useState(null);
546650
546912
  const loadOptions = import_react30.useCallback(async () => {
546913
+ setOptionsLoading(true);
546651
546914
  try {
546652
- const pinned = settingsManager.getPinnedAgents();
546653
- const optionsToFetch = [];
546915
+ const pinned = await listPinnedAgentsForCurrentUser2();
546916
+ let fetchedOptions = [];
546654
546917
  const seenAgentIds = new Set;
546655
546918
  if (lruAgentId) {
546656
- optionsToFetch.push({
546657
- name: null,
546658
- agentId: lruAgentId,
546659
- isLru: true,
546660
- agent: null
546661
- });
546919
+ const pinnedLru = pinned.find(({ agentId, agent: agent3 }) => agentId === lruAgentId && agent3);
546920
+ let agent2 = pinnedLru?.agent ?? null;
546921
+ if (!agent2) {
546922
+ try {
546923
+ const backend4 = getBackendForMode(isLocalAgentId(lruAgentId) ? "local" : "api");
546924
+ agent2 = await backend4.retrieveAgent(lruAgentId, {
546925
+ include: ["agent.blocks"]
546926
+ });
546927
+ } catch {
546928
+ agent2 = null;
546929
+ }
546930
+ }
546931
+ if (agent2) {
546932
+ fetchedOptions.push({
546933
+ name: agent2.name,
546934
+ agentId: lruAgentId,
546935
+ isLru: true,
546936
+ agent: agent2
546937
+ });
546938
+ }
546662
546939
  seenAgentIds.add(lruAgentId);
546663
546940
  }
546664
- for (const agentId of pinned) {
546665
- if (!seenAgentIds.has(agentId)) {
546666
- optionsToFetch.push({
546667
- name: null,
546941
+ for (const { agentId, agent: agent2 } of pinned) {
546942
+ if (agent2 && !seenAgentIds.has(agentId)) {
546943
+ fetchedOptions.push({
546944
+ name: agent2.name,
546668
546945
  agentId,
546669
546946
  isLru: false,
546670
- agent: null
546947
+ agent: agent2
546671
546948
  });
546672
546949
  seenAgentIds.add(agentId);
546673
546950
  }
546674
546951
  }
546675
- let fetchedOptions = await Promise.all(optionsToFetch.map(async (opt) => {
546676
- if (opt.agent) {
546677
- return opt;
546678
- }
546679
- try {
546680
- const backend4 = getBackendForMode(isLocalAgentId(opt.agentId) ? "local" : "api");
546681
- const agent2 = await backend4.retrieveAgent(opt.agentId, {
546682
- include: ["agent.blocks"]
546683
- });
546684
- return { ...opt, agent: agent2 };
546685
- } catch {
546686
- return { ...opt, agent: null };
546687
- }
546688
- }));
546689
- fetchedOptions = fetchedOptions.filter((opt) => opt.agent !== null);
546690
546952
  if (fetchedOptions.length === 0) {
546691
546953
  const recentAgents = await getRecentAgentOptions({
546692
546954
  includeLocal: false,
@@ -546703,14 +546965,15 @@ function ProfileSelectionUI({
546703
546965
  setOptions(fetchedOptions);
546704
546966
  } catch {
546705
546967
  setOptions([]);
546968
+ } finally {
546969
+ setOptionsLoading(false);
546706
546970
  }
546707
546971
  }, [lruAgentId]);
546708
546972
  import_react30.useEffect(() => {
546709
546973
  if (externalLoading)
546710
546974
  return;
546711
- setOptions((current) => current.length > 0 ? current : buildInitialProfileOptions(lruAgentId));
546712
546975
  loadOptions();
546713
- }, [externalLoading, loadOptions, lruAgentId]);
546976
+ }, [externalLoading, loadOptions]);
546714
546977
  const displayOptions = showAll ? options3 : options3.slice(0, MAX_DISPLAY);
546715
546978
  const hasMore = options3.length > MAX_DISPLAY;
546716
546979
  const totalItems = displayOptions.length + 1 + (hasMore && !showAll ? 1 : 0);
@@ -547589,8 +547852,8 @@ async function resolveStartupBackendDisplay() {
547589
547852
  }
547590
547853
  const apiKey = process.env.LETTA_API_KEY || settings3.env?.LETTA_API_KEY;
547591
547854
  const baseURL = process.env.LETTA_BASE_URL || settings3.env?.LETTA_BASE_URL || LETTA_CLOUD_API_URL;
547592
- const hasCloudCredentials = Boolean(apiKey || settings3.refreshToken);
547593
- if (baseURL === LETTA_CLOUD_API_URL && !hasCloudCredentials) {
547855
+ const hasCloudCredentials3 = Boolean(apiKey || settings3.refreshToken);
547856
+ if (baseURL === LETTA_CLOUD_API_URL && !hasCloudCredentials3) {
547594
547857
  return "setup";
547595
547858
  }
547596
547859
  return "api";
@@ -548398,8 +548661,142 @@ Provider '${providerName}' saved.`);
548398
548661
  }
548399
548662
 
548400
548663
  // src/cli/subcommands/cron.ts
548401
- await init_cron();
548664
+ init_request();
548402
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
548403
548800
  function printUsage4() {
548404
548801
  console.log(`
548405
548802
  Usage:
@@ -548407,10 +548804,10 @@ Usage:
548407
548804
  letta cron add --prompt <text> --at <time> [--once] [options]
548408
548805
  letta cron add --prompt <text> --cron <expr> [options]
548409
548806
  letta cron list [options]
548410
- letta cron get <id>
548411
- letta cron runs --id <id> [--limit <n>]
548412
- letta cron delete <id>
548413
- 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]
548414
548811
 
548415
548812
  Add options:
548416
548813
  --prompt <text> Prompt to send to the agent (required)
@@ -548420,10 +548817,23 @@ Add options:
548420
548817
  --cron <expr> Raw 5-field cron expression
548421
548818
  --agent <id> Agent ID (defaults to LETTA_AGENT_ID)
548422
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.
548423
548832
 
548424
548833
  List/filter options:
548425
548834
  --agent <id> Filter by agent ID
548426
548835
  --conversation <id> Filter by conversation ID
548836
+ --runner <runner> Only show tasks owned by this runner
548427
548837
 
548428
548838
  Delete options:
548429
548839
  --all Delete all tasks for the given agent
@@ -548445,7 +548855,9 @@ var CRON_OPTIONS = {
548445
548855
  all: { type: "boolean" },
548446
548856
  id: { type: "string" },
548447
548857
  limit: { type: "string" },
548448
- "run-id": { type: "string" }
548858
+ "run-id": { type: "string" },
548859
+ runner: { type: "string" },
548860
+ computer: { type: "string" }
548449
548861
  };
548450
548862
  function parseCronArgs(argv) {
548451
548863
  return parseArgs5({
@@ -548461,7 +548873,75 @@ function getAgentId3(fromArgs) {
548461
548873
  function getConversationId3(fromArgs) {
548462
548874
  return fromArgs || process.env.LETTA_CONVERSATION_ID || "default";
548463
548875
  }
548464
- function handleAdd(values2) {
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) {
548465
548945
  const name = values2.name;
548466
548946
  if (!name || typeof name !== "string") {
548467
548947
  console.error("Error: --name is required.");
@@ -548533,6 +549013,37 @@ function handleAdd(values2) {
548533
549013
  console.error("Error: no schedule specified.");
548534
549014
  return 1;
548535
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
+ }
548536
549047
  try {
548537
549048
  const result = addTask({
548538
549049
  agent_id: agentId,
@@ -548546,6 +549057,7 @@ function handleAdd(values2) {
548546
549057
  });
548547
549058
  const output = {
548548
549059
  id: result.task.id,
549060
+ runner: "local",
548549
549061
  status: result.task.status,
548550
549062
  cron: result.task.cron,
548551
549063
  recurring: result.task.recurring,
@@ -548566,37 +549078,152 @@ function handleAdd(values2) {
548566
549078
  output.warning = result.warning;
548567
549079
  }
548568
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.");
548569
549082
  return 0;
548570
549083
  } catch (err) {
548571
549084
  console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
548572
549085
  return 1;
548573
549086
  }
548574
549087
  }
548575
- function handleList(values2) {
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
+ }
548576
549141
  const agentId = values2.agent || process.env.LETTA_AGENT_ID || undefined;
548577
549142
  const conversationId = values2.conversation || undefined;
548578
- const tasks2 = listTasks2({
548579
- agent_id: agentId,
548580
- conversation_id: conversationId
548581
- });
548582
- console.log(JSON.stringify(tasks2, null, 2));
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));
548583
549180
  return 0;
548584
549181
  }
548585
- 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
+ }
548586
549187
  const taskId = positionals[1];
548587
549188
  if (!taskId) {
548588
549189
  console.error("Error: task ID required. Usage: letta cron get <id>");
548589
549190
  return 1;
548590
549191
  }
548591
- const task2 = getTask2(taskId);
548592
- if (!task2) {
548593
- console.error(`Error: task ${taskId} not found.`);
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
+ }
548594
549219
  return 1;
548595
549220
  }
548596
- console.log(JSON.stringify(task2, null, 2));
548597
- return 0;
548598
549221
  }
548599
- 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
+ }
548600
549227
  const id2 = values2.id;
548601
549228
  if (!id2 || typeof id2 !== "string") {
548602
549229
  console.error("Error: --id is required. Usage: letta cron runs --id <id>");
@@ -548605,42 +549232,125 @@ function handleRuns(values2) {
548605
549232
  const limitRaw = Number.parseInt(String(values2.limit ?? "50"), 10);
548606
549233
  const limit3 = Number.isFinite(limitRaw) && limitRaw > 0 ? limitRaw : 50;
548607
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
+ }
548608
549259
  try {
548609
- const logPath = getCronRunLogPath(id2);
548610
- const page = readCronRunLogEntriesPage(logPath, {
548611
- jobId: id2,
548612
- limit: limit3,
548613
- ...typeof runId === "string" && runId.trim() ? { runId } : {}
548614
- });
548615
- console.log(JSON.stringify(page, null, 2));
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));
548616
549267
  return 0;
548617
549268
  } catch (err) {
548618
549269
  console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
548619
549270
  return 1;
548620
549271
  }
548621
549272
  }
548622
- 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
+ }
548623
549278
  if (values2.all) {
548624
- const agentId = getAgentId3(values2.agent);
548625
- if (!agentId) {
548626
- console.error("Error: --agent or LETTA_AGENT_ID required with --all.");
548627
- return 1;
548628
- }
548629
- const count = deleteAllTasks(agentId);
548630
- console.log(JSON.stringify({ deleted: count, agent_id: agentId }));
548631
- return 0;
549279
+ return handleDeleteAll(values2);
548632
549280
  }
548633
549281
  const taskId = positionals[1];
548634
549282
  if (!taskId) {
548635
549283
  console.error("Error: task ID required. Usage: letta cron delete <id> or --all --agent <id>");
548636
549284
  return 1;
548637
549285
  }
548638
- const found = deleteTask(taskId);
548639
- if (!found) {
548640
- console.error(`Error: task ${taskId} not found.`);
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.`);
548641
549300
  return 1;
548642
549301
  }
548643
- console.log(JSON.stringify({ deleted: taskId }));
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.");
549321
+ return 1;
549322
+ }
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
+ }));
548644
549354
  return 0;
548645
549355
  }
548646
549356
  async function runCronSubcommand(argv) {
@@ -548663,7 +549373,7 @@ async function runCronSubcommand(argv) {
548663
549373
  case "list":
548664
549374
  return handleList(parsed.values);
548665
549375
  case "get":
548666
- return handleGet(parsed.positionals);
549376
+ return handleGet(parsed.values, parsed.positionals);
548667
549377
  case "runs":
548668
549378
  return handleRuns(parsed.values);
548669
549379
  case "delete":
@@ -556327,19 +557037,6 @@ function getModelForToolLoading(specifiedModel, specifiedToolset) {
556327
557037
  }
556328
557038
  return specifiedModel;
556329
557039
  }
556330
- async function findLocalAgentsByName(name) {
556331
- const backend4 = getBackendForMode("local");
556332
- const normalizedName = name.toLowerCase();
556333
- try {
556334
- const page = await backend4.listAgents({
556335
- query_text: name,
556336
- limit: 100
556337
- });
556338
- return paginatedItems6(page).filter((agent2) => agent2.name?.toLowerCase() === normalizedName);
556339
- } catch {
556340
- return [];
556341
- }
556342
- }
556343
557040
  function getStartupTargetLookupOrderForCredentials({
556344
557041
  baseURL,
556345
557042
  explicitBackendMode,
@@ -556356,44 +557053,24 @@ function getStartupTargetLookupOrderForCredentials({
556356
557053
  }
556357
557054
  async function resolveAgentByName2(name, backendLookupOrder) {
556358
557055
  const normalizedSearchName = name.toLowerCase();
557056
+ const pinnedAgents = await listPinnedAgentsForCurrentUser(backendLookupOrder);
556359
557057
  for (const backendMode of backendLookupOrder) {
556360
- const backend4 = getBackendForMode(backendMode);
556361
- const pinnedAgents = settingsManager2.getPinnedAgentsForBackendMode(backendMode);
556362
- const matches3 = [];
556363
- if (pinnedAgents.length > 0) {
556364
- await Promise.all(pinnedAgents.map(async (id2) => {
556365
- try {
556366
- const agent2 = await backend4.retrieveAgent(id2);
556367
- if (agent2.name?.toLowerCase() === normalizedSearchName) {
556368
- matches3.push({ id: id2, name: agent2.name, agent: agent2, backendMode });
556369
- }
556370
- } catch {}
556371
- }));
556372
- }
556373
- if (backendMode === "local") {
556374
- const seen = new Set(matches3.map((match4) => match4.id));
556375
- for (const agent2 of await findLocalAgentsByName(name)) {
556376
- if (!seen.has(agent2.id)) {
556377
- matches3.push({
556378
- id: agent2.id,
556379
- name: agent2.name ?? agent2.id,
556380
- agent: agent2,
556381
- backendMode
556382
- });
556383
- seen.add(agent2.id);
556384
- }
557058
+ const matches3 = pinnedAgents.flatMap((pinned) => pinned.backendMode === backendMode && pinned.agent?.name?.toLowerCase() === normalizedSearchName ? [
557059
+ {
557060
+ id: pinned.agentId,
557061
+ name: pinned.agent.name,
557062
+ agent: pinned.agent,
557063
+ backendMode
556385
557064
  }
556386
- }
557065
+ ] : []);
556387
557066
  if (matches3.length === 0)
556388
557067
  continue;
556389
557068
  if (matches3.length === 1)
556390
557069
  return matches3[0] ?? null;
556391
- const localSettings = settingsManager2.getLocalProjectSettings();
556392
- const localMatch = matches3.find((m4) => m4.id === localSettings.lastAgent);
557070
+ const localMatch = matches3.find((match4) => match4.id === settingsManager2.getLocalLastAgentId());
556393
557071
  if (localMatch)
556394
557072
  return localMatch;
556395
- const settings3 = settingsManager2.getSettings();
556396
- const globalMatch = matches3.find((m4) => m4.id === settings3.lastAgent);
557073
+ const globalMatch = matches3.find((match4) => match4.id === settingsManager2.getGlobalLastAgentId());
556397
557074
  if (globalMatch)
556398
557075
  return globalMatch;
556399
557076
  return matches3[0] ?? null;
@@ -556401,18 +557078,8 @@ async function resolveAgentByName2(name, backendLookupOrder) {
556401
557078
  return null;
556402
557079
  }
556403
557080
  async function getPinnedAgentNames(backendLookupOrder) {
556404
- const agents = [];
556405
- for (const backendMode of backendLookupOrder) {
556406
- const backend4 = getBackendForMode(backendMode);
556407
- const pinnedAgents = settingsManager2.getPinnedAgentsForBackendMode(backendMode);
556408
- await Promise.all(pinnedAgents.map(async (id2) => {
556409
- try {
556410
- const agent2 = await backend4.retrieveAgent(id2);
556411
- agents.push({ id: id2, name: agent2.name || "(unnamed)" });
556412
- } catch {}
556413
- }));
556414
- }
556415
- return agents;
557081
+ const pinnedAgents = await listPinnedAgentsForCurrentUser(backendLookupOrder);
557082
+ return pinnedAgents.flatMap(({ agentId, agent: agent2 }) => agent2 ? [{ id: agentId, name: agent2.name || "(unnamed)" }] : []);
556416
557083
  }
556417
557084
  async function resolveConversationAcrossBackends(conversationId, backendLookupOrder) {
556418
557085
  for (const backendMode of backendLookupOrder) {
@@ -557256,54 +557923,54 @@ Error: ${message}`);
557256
557923
  setLoadingState("assembling");
557257
557924
  return;
557258
557925
  }
557259
- const rawLocalAgentId = settingsManager2.getLocalLastAgentId(process.cwd());
557260
- const rawGlobalAgentId = settingsManager2.getGlobalLastAgentId();
557261
- const pinnedAgentIds = settingsManager2.getPinnedAgentsForBackendMode(startupBackendMode);
557262
- const localAgentId = startupBackendMode === "local" ? rawLocalAgentId : null;
557263
- const globalAgentId = startupBackendMode === "api" ? rawGlobalAgentId : null;
557264
- const agentIdsToValidate = [
557265
- ...new Set([...pinnedAgentIds, localAgentId, globalAgentId].filter((agentId2) => Boolean(agentId2)))
557266
- ];
557267
- const validationResults = await Promise.allSettled(agentIdsToValidate.map(async (agentId2) => ({
557268
- agentId: agentId2,
557269
- agent: await backend4.retrieveAgent(agentId2, {
557270
- include: ["agent.tags"]
557271
- })
557272
- })));
557273
- const cachedAgents = new Map;
557274
- for (const result of validationResults) {
557275
- if (result.status === "fulfilled") {
557276
- cachedAgents.set(result.value.agentId, result.value.agent);
557277
- }
557278
- }
557279
- const existingPinnedIds = pinnedAgentIds.filter((id2) => cachedAgents.has(id2));
557280
- const pinnedAgentId = existingPinnedIds.length === 1 ? existingPinnedIds[0] ?? null : null;
557281
- const pinnedAgentExists = pinnedAgentId !== null;
557282
- let localAgentExists = false;
557283
- let globalAgentExists = false;
557926
+ const localAgentId = settingsManager2.getLocalLastAgentId(process.cwd());
557927
+ const globalAgentId = startupBackendMode === "api" ? settingsManager2.getGlobalLastAgentId() : null;
557928
+ const localSession = settingsManager2.getLocalLastSession(process.cwd());
557284
557929
  if (localAgentId) {
557285
- localAgentExists = cachedAgents.has(localAgentId);
557286
- if (!localAgentExists) {
557930
+ try {
557931
+ const localAgent = await backend4.retrieveAgent(localAgentId, {
557932
+ include: ["agent.tags"]
557933
+ });
557934
+ setSelectedGlobalAgentId(localAgentId);
557935
+ setValidatedAgent(localAgent);
557936
+ if (localSession?.conversationId && !forceNewConversation) {
557937
+ setSelectedConversationId(localSession.conversationId);
557938
+ }
557939
+ markMilestone2("STARTUP_LRU_FETCH_DONE");
557940
+ setLoadingState("assembling");
557941
+ return;
557942
+ } catch {
557287
557943
  setFailedAgentMessage(`Unable to locate recently used agent ${localAgentId}`);
557288
557944
  }
557289
557945
  }
557290
- if (globalAgentId) {
557291
- globalAgentExists = cachedAgents.has(globalAgentId);
557946
+ const pinnedAgents = await listPinnedAgentsForCurrentUser([
557947
+ startupBackendMode
557948
+ ]);
557949
+ const pinnedAgentIds = pinnedAgents.map(({ agentId: agentId2 }) => agentId2);
557950
+ const cachedAgents = new Map(pinnedAgents.flatMap(({ agentId: agentId2, agent: agent2 }) => agent2 ? [[agentId2, agent2]] : []));
557951
+ if (globalAgentId && !cachedAgents.has(globalAgentId)) {
557952
+ try {
557953
+ const globalAgent = await backend4.retrieveAgent(globalAgentId, {
557954
+ include: ["agent.tags"]
557955
+ });
557956
+ cachedAgents.set(globalAgentId, globalAgent);
557957
+ } catch {}
557292
557958
  }
557959
+ const existingPinnedIds = pinnedAgentIds.filter((id2) => cachedAgents.has(id2));
557960
+ const pinnedAgentId = existingPinnedIds.length === 1 ? existingPinnedIds[0] ?? null : null;
557961
+ const pinnedAgentExists = pinnedAgentId !== null;
557962
+ const globalAgentExists = globalAgentId ? cachedAgents.has(globalAgentId) : false;
557293
557963
  markMilestone2("STARTUP_LRU_FETCH_DONE");
557294
- const pinnedCount = pinnedAgentIds.length;
557295
- const existingPinnedCount = existingPinnedIds.length;
557296
- const fallbackSession = startupBackendMode === "local" && !localAgentExists && !globalAgentExists ? await getLocalBackendStartupFallbackSession(backend4) : null;
557964
+ const fallbackSession = startupBackendMode === "local" && !globalAgentExists ? await getLocalBackendStartupFallbackSession(backend4) : null;
557297
557965
  const { resolveStartupTarget: resolveStartupTarget2 } = await Promise.resolve().then(() => exports_resolve_startup_agent);
557298
- const localSession = settingsManager2.getLocalLastSession(process.cwd());
557299
557966
  const target2 = resolveStartupTarget2({
557300
557967
  pinnedAgentId,
557301
557968
  pinnedAgentExists,
557302
- pinnedCount,
557303
- existingPinnedCount,
557969
+ pinnedCount: pinnedAgentIds.length,
557970
+ existingPinnedCount: existingPinnedIds.length,
557304
557971
  localAgentId,
557305
557972
  localConversationId: localSession?.conversationId ?? null,
557306
- localAgentExists,
557973
+ localAgentExists: false,
557307
557974
  globalAgentId,
557308
557975
  globalAgentExists,
557309
557976
  fallbackAgentId: fallbackSession?.agentId ?? null,
@@ -557872,4 +558539,4 @@ Error during initialization: ${message}`);
557872
558539
  }
557873
558540
  main2();
557874
558541
 
557875
- //# debugId=34462D9552FF5A1A64756E2164756E21
558542
+ //# debugId=E8D52F75291B5E0B64756E2164756E21