@letta-ai/letta-code 0.22.0 → 0.22.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/letta.js +1118 -315
  2. package/package.json +1 -1
package/letta.js CHANGED
@@ -3269,7 +3269,7 @@ var package_default;
3269
3269
  var init_package = __esm(() => {
3270
3270
  package_default = {
3271
3271
  name: "@letta-ai/letta-code",
3272
- version: "0.22.0",
3272
+ version: "0.22.2",
3273
3273
  description: "Letta Code is a CLI tool for interacting with stateful Letta agents from the terminal.",
3274
3274
  type: "module",
3275
3275
  bin: {
@@ -5153,19 +5153,86 @@ __export(exports_oauth, {
5153
5153
  OAUTH_CONFIG: () => OAUTH_CONFIG,
5154
5154
  LETTA_CLOUD_API_URL: () => LETTA_CLOUD_API_URL
5155
5155
  });
5156
+ function getOAuthAuthHost() {
5157
+ try {
5158
+ return new URL(OAUTH_CONFIG.authBaseUrl).host;
5159
+ } catch {
5160
+ return OAUTH_CONFIG.authBaseUrl;
5161
+ }
5162
+ }
5163
+ function getErrorLikeMessage(value) {
5164
+ if (value instanceof Error) {
5165
+ return value.message.trim() || null;
5166
+ }
5167
+ if (!value || typeof value !== "object") {
5168
+ return null;
5169
+ }
5170
+ const message = value.message;
5171
+ return typeof message === "string" && message.trim().length > 0 ? message.trim() : null;
5172
+ }
5173
+ function getErrorLikeCode(value) {
5174
+ if (!value || typeof value !== "object") {
5175
+ return null;
5176
+ }
5177
+ const code = value.code;
5178
+ return typeof code === "string" && code.trim().length > 0 ? code.trim() : null;
5179
+ }
5180
+ function isGenericFetchFailureMessage(message) {
5181
+ const normalized = message.trim().toLowerCase();
5182
+ return normalized === "fetch failed" || normalized === "network request failed";
5183
+ }
5184
+ function isOAuthTransportError(error) {
5185
+ if (!(error instanceof Error)) {
5186
+ return false;
5187
+ }
5188
+ if (isGenericFetchFailureMessage(error.message)) {
5189
+ return true;
5190
+ }
5191
+ return error.name === "TypeError" && error.cause !== undefined;
5192
+ }
5193
+ function extractOAuthTransportDetail(error) {
5194
+ const directMessage = isGenericFetchFailureMessage(error.message) ? null : error.message.trim() || null;
5195
+ const causeMessage = getErrorLikeMessage(error.cause);
5196
+ const causeCode = getErrorLikeCode(error.cause);
5197
+ let detail = causeMessage ?? directMessage;
5198
+ if (!detail && causeCode) {
5199
+ detail = causeCode;
5200
+ }
5201
+ if (detail && causeCode && !detail.includes(causeCode)) {
5202
+ detail = `${detail} (${causeCode})`;
5203
+ }
5204
+ return detail;
5205
+ }
5206
+ function toOAuthActionError(action, error, options) {
5207
+ if (isOAuthTransportError(error)) {
5208
+ const host = getOAuthAuthHost();
5209
+ const detail = extractOAuthTransportDetail(error);
5210
+ const reachabilityHint = options?.browserHint ? "Browser authorization may have succeeded, but the CLI could not reach Letta auth servers from this machine." : "The CLI could not reach Letta auth servers from this machine.";
5211
+ return new Error(`Failed to ${action} from ${host}${detail ? `: ${detail}` : ""}. ${reachabilityHint} Check your network, DNS, proxy, VPN, or TLS settings.`);
5212
+ }
5213
+ if (error instanceof Error) {
5214
+ return error;
5215
+ }
5216
+ return new Error(`Failed to ${action}: ${String(error)}`);
5217
+ }
5156
5218
  async function requestDeviceCode() {
5157
- const response = await fetch(`${OAUTH_CONFIG.authBaseUrl}/api/oauth/device/code`, {
5158
- method: "POST",
5159
- headers: { "Content-Type": "application/json" },
5160
- body: JSON.stringify({
5161
- client_id: OAUTH_CONFIG.clientId
5162
- })
5163
- });
5164
- if (!response.ok) {
5165
- const error = await response.json();
5166
- throw new Error(`Failed to request device code: ${error.error_description || error.error}`);
5219
+ const authHost = getOAuthAuthHost();
5220
+ try {
5221
+ const response = await fetch(`${OAUTH_CONFIG.authBaseUrl}/api/oauth/device/code`, {
5222
+ method: "POST",
5223
+ headers: { "Content-Type": "application/json" },
5224
+ body: JSON.stringify({
5225
+ client_id: OAUTH_CONFIG.clientId
5226
+ })
5227
+ });
5228
+ if (!response.ok) {
5229
+ const error = await response.json();
5230
+ throw new Error(`Failed to request device code from ${authHost}: ${error.error_description || error.error}`);
5231
+ }
5232
+ return await response.json();
5233
+ } catch (error) {
5234
+ throw toOAuthActionError("request device code", error);
5167
5235
  }
5168
- return await response.json();
5169
5236
  }
5170
5237
  async function pollForToken(deviceCode, interval = 5, expiresIn = 900, deviceId, deviceName) {
5171
5238
  const startTime = Date.now();
@@ -5211,7 +5278,9 @@ async function pollForToken(deviceCode, interval = 5, expiresIn = 900, deviceId,
5211
5278
  context: "auth_oauth_token_poll"
5212
5279
  });
5213
5280
  if (error instanceof Error) {
5214
- throw error;
5281
+ throw toOAuthActionError("poll for OAuth token", error, {
5282
+ browserHint: true
5283
+ });
5215
5284
  }
5216
5285
  throw new Error(`Failed to poll for token: ${String(error)}`);
5217
5286
  }
@@ -5219,23 +5288,28 @@ async function pollForToken(deviceCode, interval = 5, expiresIn = 900, deviceId,
5219
5288
  throw new Error("Timeout waiting for authorization (15 minutes)");
5220
5289
  }
5221
5290
  async function refreshAccessToken(refreshToken, deviceId, deviceName) {
5222
- const response = await fetch(`${OAUTH_CONFIG.authBaseUrl}/api/oauth/token`, {
5223
- method: "POST",
5224
- headers: { "Content-Type": "application/json" },
5225
- body: JSON.stringify({
5226
- grant_type: "refresh_token",
5227
- client_id: OAUTH_CONFIG.clientId,
5228
- refresh_token: refreshToken,
5229
- refresh_token_mode: "new",
5230
- device_id: deviceId,
5231
- ...deviceName && { device_name: deviceName }
5232
- })
5233
- });
5234
- if (!response.ok) {
5235
- const error = await response.json();
5236
- throw new Error(`Failed to refresh access token: ${error.error_description || error.error}`);
5291
+ const authHost = getOAuthAuthHost();
5292
+ try {
5293
+ const response = await fetch(`${OAUTH_CONFIG.authBaseUrl}/api/oauth/token`, {
5294
+ method: "POST",
5295
+ headers: { "Content-Type": "application/json" },
5296
+ body: JSON.stringify({
5297
+ grant_type: "refresh_token",
5298
+ client_id: OAUTH_CONFIG.clientId,
5299
+ refresh_token: refreshToken,
5300
+ refresh_token_mode: "new",
5301
+ device_id: deviceId,
5302
+ ...deviceName && { device_name: deviceName }
5303
+ })
5304
+ });
5305
+ if (!response.ok) {
5306
+ const error = await response.json();
5307
+ throw new Error(`Failed to refresh access token from ${authHost}: ${error.error_description || error.error}`);
5308
+ }
5309
+ return await response.json();
5310
+ } catch (error) {
5311
+ throw toOAuthActionError("refresh access token", error);
5237
5312
  }
5238
- return await response.json();
5239
5313
  }
5240
5314
  async function revokeToken(refreshToken) {
5241
5315
  try {
@@ -10364,19 +10438,86 @@ __export(exports_oauth2, {
10364
10438
  OAUTH_CONFIG: () => OAUTH_CONFIG2,
10365
10439
  LETTA_CLOUD_API_URL: () => LETTA_CLOUD_API_URL2
10366
10440
  });
10441
+ function getOAuthAuthHost2() {
10442
+ try {
10443
+ return new URL(OAUTH_CONFIG2.authBaseUrl).host;
10444
+ } catch {
10445
+ return OAUTH_CONFIG2.authBaseUrl;
10446
+ }
10447
+ }
10448
+ function getErrorLikeMessage2(value) {
10449
+ if (value instanceof Error) {
10450
+ return value.message.trim() || null;
10451
+ }
10452
+ if (!value || typeof value !== "object") {
10453
+ return null;
10454
+ }
10455
+ const message = value.message;
10456
+ return typeof message === "string" && message.trim().length > 0 ? message.trim() : null;
10457
+ }
10458
+ function getErrorLikeCode2(value) {
10459
+ if (!value || typeof value !== "object") {
10460
+ return null;
10461
+ }
10462
+ const code = value.code;
10463
+ return typeof code === "string" && code.trim().length > 0 ? code.trim() : null;
10464
+ }
10465
+ function isGenericFetchFailureMessage2(message) {
10466
+ const normalized = message.trim().toLowerCase();
10467
+ return normalized === "fetch failed" || normalized === "network request failed";
10468
+ }
10469
+ function isOAuthTransportError2(error) {
10470
+ if (!(error instanceof Error)) {
10471
+ return false;
10472
+ }
10473
+ if (isGenericFetchFailureMessage2(error.message)) {
10474
+ return true;
10475
+ }
10476
+ return error.name === "TypeError" && error.cause !== undefined;
10477
+ }
10478
+ function extractOAuthTransportDetail2(error) {
10479
+ const directMessage = isGenericFetchFailureMessage2(error.message) ? null : error.message.trim() || null;
10480
+ const causeMessage = getErrorLikeMessage2(error.cause);
10481
+ const causeCode = getErrorLikeCode2(error.cause);
10482
+ let detail = causeMessage ?? directMessage;
10483
+ if (!detail && causeCode) {
10484
+ detail = causeCode;
10485
+ }
10486
+ if (detail && causeCode && !detail.includes(causeCode)) {
10487
+ detail = `${detail} (${causeCode})`;
10488
+ }
10489
+ return detail;
10490
+ }
10491
+ function toOAuthActionError2(action, error, options) {
10492
+ if (isOAuthTransportError2(error)) {
10493
+ const host = getOAuthAuthHost2();
10494
+ const detail = extractOAuthTransportDetail2(error);
10495
+ const reachabilityHint = options?.browserHint ? "Browser authorization may have succeeded, but the CLI could not reach Letta auth servers from this machine." : "The CLI could not reach Letta auth servers from this machine.";
10496
+ return new Error(`Failed to ${action} from ${host}${detail ? `: ${detail}` : ""}. ${reachabilityHint} Check your network, DNS, proxy, VPN, or TLS settings.`);
10497
+ }
10498
+ if (error instanceof Error) {
10499
+ return error;
10500
+ }
10501
+ return new Error(`Failed to ${action}: ${String(error)}`);
10502
+ }
10367
10503
  async function requestDeviceCode2() {
10368
- const response = await fetch(`${OAUTH_CONFIG2.authBaseUrl}/api/oauth/device/code`, {
10369
- method: "POST",
10370
- headers: { "Content-Type": "application/json" },
10371
- body: JSON.stringify({
10372
- client_id: OAUTH_CONFIG2.clientId
10373
- })
10374
- });
10375
- if (!response.ok) {
10376
- const error = await response.json();
10377
- throw new Error(`Failed to request device code: ${error.error_description || error.error}`);
10504
+ const authHost = getOAuthAuthHost2();
10505
+ try {
10506
+ const response = await fetch(`${OAUTH_CONFIG2.authBaseUrl}/api/oauth/device/code`, {
10507
+ method: "POST",
10508
+ headers: { "Content-Type": "application/json" },
10509
+ body: JSON.stringify({
10510
+ client_id: OAUTH_CONFIG2.clientId
10511
+ })
10512
+ });
10513
+ if (!response.ok) {
10514
+ const error = await response.json();
10515
+ throw new Error(`Failed to request device code from ${authHost}: ${error.error_description || error.error}`);
10516
+ }
10517
+ return await response.json();
10518
+ } catch (error) {
10519
+ throw toOAuthActionError2("request device code", error);
10378
10520
  }
10379
- return await response.json();
10380
10521
  }
10381
10522
  async function pollForToken2(deviceCode, interval = 5, expiresIn = 900, deviceId, deviceName) {
10382
10523
  const startTime = Date.now();
@@ -10422,7 +10563,9 @@ async function pollForToken2(deviceCode, interval = 5, expiresIn = 900, deviceId
10422
10563
  context: "auth_oauth_token_poll"
10423
10564
  });
10424
10565
  if (error instanceof Error) {
10425
- throw error;
10566
+ throw toOAuthActionError2("poll for OAuth token", error, {
10567
+ browserHint: true
10568
+ });
10426
10569
  }
10427
10570
  throw new Error(`Failed to poll for token: ${String(error)}`);
10428
10571
  }
@@ -10430,23 +10573,28 @@ async function pollForToken2(deviceCode, interval = 5, expiresIn = 900, deviceId
10430
10573
  throw new Error("Timeout waiting for authorization (15 minutes)");
10431
10574
  }
10432
10575
  async function refreshAccessToken2(refreshToken, deviceId, deviceName) {
10433
- const response = await fetch(`${OAUTH_CONFIG2.authBaseUrl}/api/oauth/token`, {
10434
- method: "POST",
10435
- headers: { "Content-Type": "application/json" },
10436
- body: JSON.stringify({
10437
- grant_type: "refresh_token",
10438
- client_id: OAUTH_CONFIG2.clientId,
10439
- refresh_token: refreshToken,
10440
- refresh_token_mode: "new",
10441
- device_id: deviceId,
10442
- ...deviceName && { device_name: deviceName }
10443
- })
10444
- });
10445
- if (!response.ok) {
10446
- const error = await response.json();
10447
- throw new Error(`Failed to refresh access token: ${error.error_description || error.error}`);
10576
+ const authHost = getOAuthAuthHost2();
10577
+ try {
10578
+ const response = await fetch(`${OAUTH_CONFIG2.authBaseUrl}/api/oauth/token`, {
10579
+ method: "POST",
10580
+ headers: { "Content-Type": "application/json" },
10581
+ body: JSON.stringify({
10582
+ grant_type: "refresh_token",
10583
+ client_id: OAUTH_CONFIG2.clientId,
10584
+ refresh_token: refreshToken,
10585
+ refresh_token_mode: "new",
10586
+ device_id: deviceId,
10587
+ ...deviceName && { device_name: deviceName }
10588
+ })
10589
+ });
10590
+ if (!response.ok) {
10591
+ const error = await response.json();
10592
+ throw new Error(`Failed to refresh access token from ${authHost}: ${error.error_description || error.error}`);
10593
+ }
10594
+ return await response.json();
10595
+ } catch (error) {
10596
+ throw toOAuthActionError2("refresh access token", error);
10448
10597
  }
10449
- return await response.json();
10450
10598
  }
10451
10599
  async function revokeToken2(refreshToken) {
10452
10600
  try {
@@ -38922,6 +39070,10 @@ for file in $(git diff --cached --name-only --diff-filter=ACM | grep -E '^(memor
38922
39070
  # Validate each line
38923
39071
  while IFS= read -r line; do
38924
39072
  [ -z "$line" ] && continue
39073
+ # Skip YAML multiline continuation lines (indented lines that continue a previous value)
39074
+ case "$line" in
39075
+ " "*|$' '*) continue ;;
39076
+ esac
38925
39077
 
38926
39078
  key=$(echo "$line" | cut -d: -f1 | tr -d ' ')
38927
39079
  value=$(echo "$line" | cut -d: -f2- | sed 's/^ *//;s/ *$//')
@@ -39842,11 +39994,227 @@ var init_routing = __esm(() => {
39842
39994
  routesByKey = new Map;
39843
39995
  });
39844
39996
 
39997
+ // src/cli/helpers/gitContext.ts
39998
+ import { execFileSync } from "node:child_process";
39999
+ function runGit3(args, cwd2) {
40000
+ try {
40001
+ return execFileSync("git", args, {
40002
+ cwd: cwd2,
40003
+ encoding: "utf-8",
40004
+ stdio: ["ignore", "pipe", "ignore"]
40005
+ }).trim();
40006
+ } catch {
40007
+ return null;
40008
+ }
40009
+ }
40010
+ function truncateLines(value, maxLines) {
40011
+ const lines = value.split(`
40012
+ `);
40013
+ if (lines.length <= maxLines) {
40014
+ return value;
40015
+ }
40016
+ return lines.slice(0, maxLines).join(`
40017
+ `) + `
40018
+ ... and ${lines.length - maxLines} more changes`;
40019
+ }
40020
+ function formatGitUser(name, email) {
40021
+ if (!name && !email) {
40022
+ return null;
40023
+ }
40024
+ if (name && email) {
40025
+ return `${name} <${email}>`;
40026
+ }
40027
+ return name || email;
40028
+ }
40029
+ function gatherGitContextSnapshot(options = {}) {
40030
+ const cwd2 = options.cwd ?? process.cwd();
40031
+ const recentCommitLimit = options.recentCommitLimit ?? 3;
40032
+ if (!runGit3(["rev-parse", "--git-dir"], cwd2)) {
40033
+ return {
40034
+ isGitRepo: false,
40035
+ branch: null,
40036
+ status: null,
40037
+ recentCommits: null,
40038
+ gitUser: null
40039
+ };
40040
+ }
40041
+ const branch = runGit3(["branch", "--show-current"], cwd2);
40042
+ const fullStatus = runGit3(["status", "--short"], cwd2);
40043
+ const status = typeof fullStatus === "string" && options.statusLineLimit ? truncateLines(fullStatus, options.statusLineLimit) : fullStatus;
40044
+ const recentCommits = options.recentCommitFormat ? runGit3([
40045
+ "log",
40046
+ `--format=${options.recentCommitFormat}`,
40047
+ "-n",
40048
+ String(recentCommitLimit)
40049
+ ], cwd2) : runGit3(["log", "--oneline", "-n", String(recentCommitLimit)], cwd2);
40050
+ const userConfig = runGit3(["config", "--get-regexp", "^user\\.(name|email)$"], cwd2);
40051
+ let userName = null;
40052
+ let userEmail = null;
40053
+ if (userConfig) {
40054
+ for (const line of userConfig.split(`
40055
+ `)) {
40056
+ if (line.startsWith("user.name "))
40057
+ userName = line.slice("user.name ".length);
40058
+ else if (line.startsWith("user.email "))
40059
+ userEmail = line.slice("user.email ".length);
40060
+ }
40061
+ }
40062
+ const gitUser = formatGitUser(userName, userEmail);
40063
+ return {
40064
+ isGitRepo: true,
40065
+ branch,
40066
+ status,
40067
+ recentCommits,
40068
+ gitUser
40069
+ };
40070
+ }
40071
+ function getGitContext(cwd2) {
40072
+ if (!runGit3(["rev-parse", "--git-dir"], cwd2)) {
40073
+ return null;
40074
+ }
40075
+ const branch = runGit3(["branch", "--show-current"], cwd2);
40076
+ const branchList = runGit3([
40077
+ "branch",
40078
+ "--sort=-committerdate",
40079
+ "--format=%(refname:short)",
40080
+ "--no-color"
40081
+ ], cwd2);
40082
+ const recentBranches = branchList ? branchList.split(`
40083
+ `).map((b) => b.trim()).filter((b) => b.length > 0 && b !== branch).slice(0, 10) : [];
40084
+ return { branch, recent_branches: recentBranches };
40085
+ }
40086
+ var init_gitContext = () => {};
40087
+
40088
+ // src/cli/helpers/sessionContext.ts
40089
+ import { platform as platform3 } from "node:os";
40090
+ function getLocalTime() {
40091
+ const now = new Date;
40092
+ return now.toLocaleString(undefined, {
40093
+ weekday: "long",
40094
+ year: "numeric",
40095
+ month: "long",
40096
+ day: "numeric",
40097
+ hour: "2-digit",
40098
+ minute: "2-digit",
40099
+ timeZoneName: "short"
40100
+ });
40101
+ }
40102
+ function getDeviceType() {
40103
+ const p = platform3();
40104
+ switch (p) {
40105
+ case "darwin":
40106
+ return "macOS";
40107
+ case "win32":
40108
+ return "Windows";
40109
+ case "linux":
40110
+ return "Linux";
40111
+ default:
40112
+ return p;
40113
+ }
40114
+ }
40115
+ function getGitInfo(cwd2) {
40116
+ const git = gatherGitContextSnapshot({
40117
+ cwd: cwd2,
40118
+ recentCommitLimit: 3,
40119
+ recentCommitFormat: "%h %s (%an)",
40120
+ statusLineLimit: 20
40121
+ });
40122
+ if (!git.isGitRepo) {
40123
+ return { isGitRepo: false };
40124
+ }
40125
+ return {
40126
+ isGitRepo: true,
40127
+ branch: git.branch ?? "(unknown)",
40128
+ recentCommits: git.recentCommits ?? "(failed to get commits)",
40129
+ status: git.status || "(clean working tree)",
40130
+ gitUser: git.gitUser ?? "(not configured)"
40131
+ };
40132
+ }
40133
+ function getIntroText(source, reason) {
40134
+ if (reason === "cwd_changed") {
40135
+ return "The working directory for this conversation has changed. Updated environment context follows.";
40136
+ }
40137
+ switch (source) {
40138
+ case "listen":
40139
+ return "This conversation is now connected to a Letta Code execution environment.";
40140
+ case "headless":
40141
+ return "The user has just initiated a new connection via the Letta Code headless client.";
40142
+ default:
40143
+ return "The user has just initiated a new connection via the [Letta Code CLI client](https://docs.letta.com/letta-code/index.md).";
40144
+ }
40145
+ }
40146
+ function buildSessionContext(options) {
40147
+ try {
40148
+ const cwd2 = options?.cwd ?? process.cwd();
40149
+ const source = options?.source ?? "interactive-cli";
40150
+ const reason = options?.reason ?? "initial_attach";
40151
+ let version = "unknown";
40152
+ try {
40153
+ version = getVersion();
40154
+ } catch {}
40155
+ let deviceType = "unknown";
40156
+ try {
40157
+ deviceType = getDeviceType();
40158
+ } catch {}
40159
+ let localTime = "unknown";
40160
+ try {
40161
+ localTime = getLocalTime();
40162
+ } catch {}
40163
+ const gitInfo = getGitInfo(cwd2);
40164
+ let context2 = `${SYSTEM_REMINDER_OPEN}
40165
+ This is an automated message providing context about the user's environment.
40166
+ ${getIntroText(source, reason)}
40167
+
40168
+ ## Device Information
40169
+ - **Local time**: ${localTime}
40170
+ - **Device type**: ${deviceType}
40171
+ - **Letta Code version**: ${version}
40172
+ - **Current working directory**: ${cwd2}
40173
+ `;
40174
+ if (gitInfo.isGitRepo) {
40175
+ context2 += `- **Git repository**: Yes (branch: ${gitInfo.branch})
40176
+ - **Git user**: ${gitInfo.gitUser}
40177
+
40178
+ ### Recent Commits
40179
+ \`\`\`
40180
+ ${gitInfo.recentCommits}
40181
+ \`\`\`
40182
+
40183
+ ### Git Status
40184
+ \`\`\`
40185
+ ${gitInfo.status}
40186
+ \`\`\`
40187
+ `;
40188
+ } else {
40189
+ context2 += `- **Git repository**: No
40190
+ `;
40191
+ }
40192
+ if (platform3() === "win32") {
40193
+ context2 += `
40194
+ ## Windows Shell Notes
40195
+ - The Bash tool uses PowerShell or cmd.exe on Windows
40196
+ - HEREDOC syntax (e.g., \`$(cat <<'EOF'...EOF)\`) does NOT work on Windows
40197
+ - For multiline strings (git commits, PR bodies), use simple quoted strings instead
40198
+ `;
40199
+ }
40200
+ context2 += SYSTEM_REMINDER_CLOSE;
40201
+ return context2;
40202
+ } catch {
40203
+ return "";
40204
+ }
40205
+ }
40206
+ var init_sessionContext = __esm(() => {
40207
+ init_constants();
40208
+ init_version();
40209
+ init_gitContext();
40210
+ });
40211
+
39845
40212
  // src/channels/xml.ts
39846
40213
  function escapeXml(text) {
39847
40214
  return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
39848
40215
  }
39849
40216
  function formatChannelNotification(msg) {
40217
+ const localTime = escapeXml(getLocalTime());
39850
40218
  const attrs = [
39851
40219
  `source="${escapeXml(msg.channel)}"`,
39852
40220
  `chat_id="${escapeXml(msg.chatId)}"`,
@@ -39860,10 +40228,27 @@ function formatChannelNotification(msg) {
39860
40228
  }
39861
40229
  const attrString = attrs.join(" ");
39862
40230
  const escapedText = escapeXml(msg.text);
39863
- return `<channel-notification ${attrString}>
40231
+ const escapedChannel = escapeXml(msg.channel);
40232
+ const escapedChatId = escapeXml(msg.chatId);
40233
+ const reminder = [
40234
+ SYSTEM_REMINDER_OPEN,
40235
+ `This message originated from an external ${escapedChannel} channel.`,
40236
+ `If you want the ensure the user on ${escapedChannel} will see your reply, you must call the MessageChannel tool to send a message back on the same channel.`,
40237
+ `Use channel="${escapedChannel}" and chat_id="${escapedChatId}" when calling MessageChannel.`,
40238
+ "Only pass reply_to_message_id if you intentionally want the platform's quote/reply UI.",
40239
+ `Current local time on this device: ${localTime}`,
40240
+ SYSTEM_REMINDER_CLOSE
40241
+ ].join(`
40242
+ `);
40243
+ return `${reminder}
40244
+ <channel-notification ${attrString}>
39864
40245
  ${escapedText}
39865
40246
  </channel-notification>`;
39866
40247
  }
40248
+ var init_xml = __esm(() => {
40249
+ init_sessionContext();
40250
+ init_constants();
40251
+ });
39867
40252
 
39868
40253
  // node_modules/grammy/out/filter.js
39869
40254
  var require_filter = __commonJS((exports) => {
@@ -48585,13 +48970,22 @@ function createTelegramAdapter(config) {
48585
48970
  await bot.init();
48586
48971
  const info = bot.botInfo;
48587
48972
  console.log(`[Telegram] Bot started as @${info.username} (dm_policy: ${config.dmPolicy})`);
48588
- bot.start({
48589
- onStart: () => {
48590
- running = true;
48591
- }
48592
- }).catch((error) => {
48593
- running = false;
48594
- console.error("[Telegram] Long-polling stopped unexpectedly:", error);
48973
+ await new Promise((resolve2, reject) => {
48974
+ let started = false;
48975
+ bot.start({
48976
+ onStart: () => {
48977
+ running = true;
48978
+ started = true;
48979
+ resolve2();
48980
+ }
48981
+ }).catch((error) => {
48982
+ running = false;
48983
+ if (!started) {
48984
+ reject(error);
48985
+ return;
48986
+ }
48987
+ console.error("[Telegram] Long-polling stopped unexpectedly:", error);
48988
+ });
48595
48989
  });
48596
48990
  },
48597
48991
  async stop() {
@@ -48635,12 +49029,16 @@ __export(exports_registry, {
48635
49029
  initializeChannels: () => initializeChannels,
48636
49030
  getChannelRegistry: () => getChannelRegistry,
48637
49031
  getActiveChannelIds: () => getActiveChannelIds,
49032
+ ensureChannelRegistry: () => ensureChannelRegistry,
48638
49033
  completePairing: () => completePairing,
48639
49034
  ChannelRegistry: () => ChannelRegistry
48640
49035
  });
48641
49036
  function getChannelRegistry() {
48642
49037
  return instance;
48643
49038
  }
49039
+ function ensureChannelRegistry() {
49040
+ return instance ?? new ChannelRegistry;
49041
+ }
48644
49042
  function getActiveChannelIds() {
48645
49043
  if (!instance)
48646
49044
  return [];
@@ -48651,6 +49049,7 @@ class ChannelRegistry {
48651
49049
  adapters = new Map;
48652
49050
  ready = false;
48653
49051
  messageHandler = null;
49052
+ eventHandler = null;
48654
49053
  buffer = [];
48655
49054
  constructor() {
48656
49055
  if (instance) {
@@ -48673,6 +49072,9 @@ class ChannelRegistry {
48673
49072
  setMessageHandler(handler) {
48674
49073
  this.messageHandler = handler;
48675
49074
  }
49075
+ setEventHandler(handler) {
49076
+ this.eventHandler = handler;
49077
+ }
48676
49078
  setReady() {
48677
49079
  this.ready = true;
48678
49080
  this.flushBuffer();
@@ -48683,6 +49085,39 @@ class ChannelRegistry {
48683
49085
  getRoute(channel, chatId) {
48684
49086
  return getRoute(channel, chatId);
48685
49087
  }
49088
+ async startChannel(channelId) {
49089
+ const config = readChannelConfig(channelId);
49090
+ if (!config) {
49091
+ return false;
49092
+ }
49093
+ loadRoutes(channelId);
49094
+ loadPairingStore(channelId);
49095
+ const existing = this.adapters.get(channelId);
49096
+ if (existing?.isRunning()) {
49097
+ await existing.stop();
49098
+ }
49099
+ this.adapters.delete(channelId);
49100
+ if (channelId === "telegram") {
49101
+ const { createTelegramAdapter: createTelegramAdapter2 } = await Promise.resolve().then(() => (init_adapter(), exports_adapter));
49102
+ const adapter = createTelegramAdapter2(config);
49103
+ this.registerAdapter(adapter);
49104
+ await adapter.start();
49105
+ return true;
49106
+ }
49107
+ console.error(`Unknown channel "${channelId}". Supported: telegram`);
49108
+ return false;
49109
+ }
49110
+ async stopChannel(channelId) {
49111
+ const adapter = this.adapters.get(channelId);
49112
+ if (!adapter) {
49113
+ return false;
49114
+ }
49115
+ if (adapter.isRunning()) {
49116
+ await adapter.stop();
49117
+ }
49118
+ this.adapters.delete(channelId);
49119
+ return true;
49120
+ }
48686
49121
  async startAll() {
48687
49122
  for (const adapter of Array.from(this.adapters.values())) {
48688
49123
  if (!adapter.isRunning()) {
@@ -48693,6 +49128,7 @@ class ChannelRegistry {
48693
49128
  pause() {
48694
49129
  this.ready = false;
48695
49130
  this.messageHandler = null;
49131
+ this.eventHandler = null;
48696
49132
  }
48697
49133
  async stopAll() {
48698
49134
  for (const adapter of Array.from(this.adapters.values())) {
@@ -48702,6 +49138,7 @@ class ChannelRegistry {
48702
49138
  }
48703
49139
  this.ready = false;
48704
49140
  this.messageHandler = null;
49141
+ this.eventHandler = null;
48705
49142
  instance = null;
48706
49143
  }
48707
49144
  async handleInboundMessage(msg) {
@@ -48722,11 +49159,15 @@ class ChannelRegistry {
48722
49159
  }
48723
49160
  if (!isUserApproved(msg.channel, msg.senderId)) {
48724
49161
  const code = createPairingCode(msg.channel, msg.senderId, msg.chatId, msg.senderName);
49162
+ this.eventHandler?.({
49163
+ type: "pairings_updated",
49164
+ channelId: msg.channel
49165
+ });
48725
49166
  await adapter.sendDirectReply(msg.chatId, `To connect this chat to a Letta Code agent, run:
48726
49167
 
48727
- ` + `/channels telegram pair ${code}
49168
+ /channels telegram pair ${code}
48728
49169
 
48729
- ` + `This code expires in 15 minutes.`);
49170
+ This code expires in 15 minutes.`);
48730
49171
  return;
48731
49172
  }
48732
49173
  }
@@ -48736,7 +49177,7 @@ class ChannelRegistry {
48736
49177
  route = getRoute(msg.channel, msg.chatId);
48737
49178
  }
48738
49179
  if (!route) {
48739
- await adapter.sendDirectReply(msg.chatId, `This chat isn't bound to an agent. ` + `Run \`/channels telegram enable --chat-id ${msg.chatId}\` ` + `on your Letta Code agent to connect.`);
49180
+ await adapter.sendDirectReply(msg.chatId, `This chat isn't bound to an agent. Run \`/channels telegram enable --chat-id ${msg.chatId}\` on your Letta Code agent to connect.`);
48740
49181
  return;
48741
49182
  }
48742
49183
  const xmlContent = formatChannelNotification(msg);
@@ -48758,7 +49199,7 @@ class ChannelRegistry {
48758
49199
  }
48759
49200
  }
48760
49201
  async function initializeChannels(channelNames) {
48761
- const registry = new ChannelRegistry;
49202
+ const registry = ensureChannelRegistry();
48762
49203
  for (const channelId of channelNames) {
48763
49204
  const config = readChannelConfig(channelId);
48764
49205
  if (!config) {
@@ -48769,17 +49210,8 @@ async function initializeChannels(channelNames) {
48769
49210
  console.log(`Channel "${channelId}" is disabled in config, skipping.`);
48770
49211
  continue;
48771
49212
  }
48772
- loadRoutes(channelId);
48773
- loadPairingStore(channelId);
48774
- if (channelId === "telegram") {
48775
- const { createTelegramAdapter: createTelegramAdapter2 } = await Promise.resolve().then(() => (init_adapter(), exports_adapter));
48776
- const adapter = createTelegramAdapter2(config);
48777
- registry.registerAdapter(adapter);
48778
- } else {
48779
- console.error(`Unknown channel: "${channelId}". Supported: telegram`);
48780
- }
49213
+ await registry.startChannel(channelId);
48781
49214
  }
48782
- await registry.startAll();
48783
49215
  return registry;
48784
49216
  }
48785
49217
  function completePairing(channelId, code, agentId, conversationId) {
@@ -48816,6 +49248,7 @@ var init_registry = __esm(() => {
48816
49248
  init_config();
48817
49249
  init_pairing();
48818
49250
  init_routing();
49251
+ init_xml();
48819
49252
  });
48820
49253
 
48821
49254
  // src/channels/telegram/setup.ts
@@ -49603,7 +50036,7 @@ var init_default_browser_id = __esm(() => {
49603
50036
  // node_modules/run-applescript/index.js
49604
50037
  import process17 from "node:process";
49605
50038
  import { promisify as promisify4 } from "node:util";
49606
- import { execFile as execFile4, execFileSync } from "node:child_process";
50039
+ import { execFile as execFile4, execFileSync as execFileSync2 } from "node:child_process";
49607
50040
  async function runAppleScript(script, { humanReadableOutput = true, signal } = {}) {
49608
50041
  if (process17.platform !== "darwin") {
49609
50042
  throw new Error("macOS only");
@@ -49743,16 +50176,16 @@ function detectArchBinary(binary) {
49743
50176
  }
49744
50177
  return archBinary;
49745
50178
  }
49746
- function detectPlatformBinary({ [platform3]: platformBinary }, { wsl }) {
50179
+ function detectPlatformBinary({ [platform4]: platformBinary }, { wsl }) {
49747
50180
  if (wsl && is_wsl_default) {
49748
50181
  return detectArchBinary(wsl);
49749
50182
  }
49750
50183
  if (!platformBinary) {
49751
- throw new Error(`${platform3} is not supported`);
50184
+ throw new Error(`${platform4} is not supported`);
49752
50185
  }
49753
50186
  return detectArchBinary(platformBinary);
49754
50187
  }
49755
- var execFile7, __dirname2, localXdgOpenPath, platform3, arch, pTryEach = async (array, mapper) => {
50188
+ var execFile7, __dirname2, localXdgOpenPath, platform4, arch, pTryEach = async (array, mapper) => {
49756
50189
  let latestError;
49757
50190
  for (const item of array) {
49758
50191
  try {
@@ -49824,7 +50257,7 @@ var execFile7, __dirname2, localXdgOpenPath, platform3, arch, pTryEach = async (
49824
50257
  let command;
49825
50258
  const cliArguments = [];
49826
50259
  const childProcessOptions = {};
49827
- if (platform3 === "darwin") {
50260
+ if (platform4 === "darwin") {
49828
50261
  command = "open";
49829
50262
  if (options.wait) {
49830
50263
  cliArguments.push("--wait-apps");
@@ -49838,7 +50271,7 @@ var execFile7, __dirname2, localXdgOpenPath, platform3, arch, pTryEach = async (
49838
50271
  if (app) {
49839
50272
  cliArguments.push("-a", app);
49840
50273
  }
49841
- } else if (platform3 === "win32" || is_wsl_default && !isInsideContainer() && !app) {
50274
+ } else if (platform4 === "win32" || is_wsl_default && !isInsideContainer() && !app) {
49842
50275
  command = await powerShellPath();
49843
50276
  cliArguments.push("-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-EncodedCommand");
49844
50277
  if (!is_wsl_default) {
@@ -49871,7 +50304,7 @@ var execFile7, __dirname2, localXdgOpenPath, platform3, arch, pTryEach = async (
49871
50304
  await fs6.access(localXdgOpenPath, fsConstants2.X_OK);
49872
50305
  exeLocalXdgOpen = true;
49873
50306
  } catch {}
49874
- const useSystemXdgOpen = process19.versions.electron ?? (platform3 === "android" || isBundled || !exeLocalXdgOpen);
50307
+ const useSystemXdgOpen = process19.versions.electron ?? (platform4 === "android" || isBundled || !exeLocalXdgOpen);
49875
50308
  command = useSystemXdgOpen ? "xdg-open" : localXdgOpenPath;
49876
50309
  }
49877
50310
  if (appArguments.length > 0) {
@@ -49882,7 +50315,7 @@ var execFile7, __dirname2, localXdgOpenPath, platform3, arch, pTryEach = async (
49882
50315
  childProcessOptions.detached = true;
49883
50316
  }
49884
50317
  }
49885
- if (platform3 === "darwin" && appArguments.length > 0) {
50318
+ if (platform4 === "darwin" && appArguments.length > 0) {
49886
50319
  cliArguments.push("--args", ...appArguments);
49887
50320
  }
49888
50321
  if (options.target) {
@@ -49934,7 +50367,7 @@ var init_open = __esm(() => {
49934
50367
  execFile7 = promisify7(childProcess.execFile);
49935
50368
  __dirname2 = path2.dirname(fileURLToPath(import.meta.url));
49936
50369
  localXdgOpenPath = path2.join(__dirname2, "xdg-open");
49937
- ({ platform: platform3, arch } = process19);
50370
+ ({ platform: platform4, arch } = process19);
49938
50371
  apps = {};
49939
50372
  defineLazyProperty(apps, "chrome", () => detectPlatformBinary({
49940
50373
  darwin: "google chrome",
@@ -53051,97 +53484,6 @@ var init_fileIndex = __esm(() => {
53051
53484
  indexRoot2 = process.cwd();
53052
53485
  });
53053
53486
 
53054
- // src/cli/helpers/gitContext.ts
53055
- import { execFileSync as execFileSync2 } from "node:child_process";
53056
- function runGit3(args, cwd2) {
53057
- try {
53058
- return execFileSync2("git", args, {
53059
- cwd: cwd2,
53060
- encoding: "utf-8",
53061
- stdio: ["ignore", "pipe", "ignore"]
53062
- }).trim();
53063
- } catch {
53064
- return null;
53065
- }
53066
- }
53067
- function truncateLines(value, maxLines) {
53068
- const lines = value.split(`
53069
- `);
53070
- if (lines.length <= maxLines) {
53071
- return value;
53072
- }
53073
- return lines.slice(0, maxLines).join(`
53074
- `) + `
53075
- ... and ${lines.length - maxLines} more changes`;
53076
- }
53077
- function formatGitUser(name, email) {
53078
- if (!name && !email) {
53079
- return null;
53080
- }
53081
- if (name && email) {
53082
- return `${name} <${email}>`;
53083
- }
53084
- return name || email;
53085
- }
53086
- function gatherGitContextSnapshot(options = {}) {
53087
- const cwd2 = options.cwd ?? process.cwd();
53088
- const recentCommitLimit = options.recentCommitLimit ?? 3;
53089
- if (!runGit3(["rev-parse", "--git-dir"], cwd2)) {
53090
- return {
53091
- isGitRepo: false,
53092
- branch: null,
53093
- status: null,
53094
- recentCommits: null,
53095
- gitUser: null
53096
- };
53097
- }
53098
- const branch = runGit3(["branch", "--show-current"], cwd2);
53099
- const fullStatus = runGit3(["status", "--short"], cwd2);
53100
- const status = typeof fullStatus === "string" && options.statusLineLimit ? truncateLines(fullStatus, options.statusLineLimit) : fullStatus;
53101
- const recentCommits = options.recentCommitFormat ? runGit3([
53102
- "log",
53103
- `--format=${options.recentCommitFormat}`,
53104
- "-n",
53105
- String(recentCommitLimit)
53106
- ], cwd2) : runGit3(["log", "--oneline", "-n", String(recentCommitLimit)], cwd2);
53107
- const userConfig = runGit3(["config", "--get-regexp", "^user\\.(name|email)$"], cwd2);
53108
- let userName = null;
53109
- let userEmail = null;
53110
- if (userConfig) {
53111
- for (const line of userConfig.split(`
53112
- `)) {
53113
- if (line.startsWith("user.name "))
53114
- userName = line.slice("user.name ".length);
53115
- else if (line.startsWith("user.email "))
53116
- userEmail = line.slice("user.email ".length);
53117
- }
53118
- }
53119
- const gitUser = formatGitUser(userName, userEmail);
53120
- return {
53121
- isGitRepo: true,
53122
- branch,
53123
- status,
53124
- recentCommits,
53125
- gitUser
53126
- };
53127
- }
53128
- function getGitContext(cwd2) {
53129
- if (!runGit3(["rev-parse", "--git-dir"], cwd2)) {
53130
- return null;
53131
- }
53132
- const branch = runGit3(["branch", "--show-current"], cwd2);
53133
- const branchList = runGit3([
53134
- "branch",
53135
- "--sort=-committerdate",
53136
- "--format=%(refname:short)",
53137
- "--no-color"
53138
- ], cwd2);
53139
- const recentBranches = branchList ? branchList.split(`
53140
- `).map((b) => b.trim()).filter((b) => b.length > 0 && b !== branch).slice(0, 10) : [];
53141
- return { branch, recent_branches: recentBranches };
53142
- }
53143
- var init_gitContext = () => {};
53144
-
53145
53487
  // src/cli/helpers/memoryReminder.ts
53146
53488
  function isValidStepCount(value) {
53147
53489
  return typeof value === "number" && Number.isFinite(value) && Number.isInteger(value) && value > 0;
@@ -64319,7 +64661,7 @@ var MemoryApplyPatch_default = "Apply a codex-style patch to memory files in `$M
64319
64661
  var init_MemoryApplyPatch = () => {};
64320
64662
 
64321
64663
  // src/tools/descriptions/MessageChannel.md
64322
- var MessageChannel_default = "# MessageChannel\n\nSend a message to an external channel (Telegram, Slack, etc.) in response to a channel notification.\n\nWhen you receive a `<channel-notification>`, use this tool to reply. Extract the `source` and `chat_id` from the notification attributes and pass them as `channel` and `chat_id`.\n\nParameters:\n- `channel`: The platform to send to (matches the `source` attribute)\n- `chat_id`: The chat ID to send to (matches the `chat_id` attribute)\n- `text`: The message text to send\n- `reply_to_message_id`: (Optional) Reply to a specific message by its `message_id`\n";
64664
+ var MessageChannel_default = "# MessageChannel\n\nSend a message to an external channel (Telegram, Slack, etc.) in response to a channel notification.\n\nWhen you receive a `<channel-notification>`, use this tool to reply directly to the user on the same external channel (a normal assistant response is not delivered back to Telegram/Slack/etc). Extract the `source` and `chat_id` from the notification attributes and pass them as `channel` and `chat_id`.\n\nParameters:\n- `channel`: The platform to send to (matches the `source` attribute)\n- `chat_id`: The chat ID to send to (matches the `chat_id` attribute)\n- `text`: The message text to send\n- `reply_to_message_id`: (Optional) Reply to a specific message by its `message_id`. Omit this unless you intentionally want the platform's quote/reply UI.\n";
64323
64665
  var init_MessageChannel = () => {};
64324
64666
 
64325
64667
  // src/tools/descriptions/MultiEdit.md
@@ -74462,7 +74804,7 @@ class Pattern {
74462
74804
  #isUNC;
74463
74805
  #isAbsolute;
74464
74806
  #followGlobstar = true;
74465
- constructor(patternList, globList, index, platform4) {
74807
+ constructor(patternList, globList, index, platform5) {
74466
74808
  if (!isPatternList(patternList)) {
74467
74809
  throw new TypeError("empty pattern list");
74468
74810
  }
@@ -74479,7 +74821,7 @@ class Pattern {
74479
74821
  this.#patternList = patternList;
74480
74822
  this.#globList = globList;
74481
74823
  this.#index = index;
74482
- this.#platform = platform4;
74824
+ this.#platform = platform5;
74483
74825
  if (this.#index === 0) {
74484
74826
  if (this.isUNC()) {
74485
74827
  const [p0, p1, p2, p3, ...prest] = this.#patternList;
@@ -74576,12 +74918,12 @@ class Ignore {
74576
74918
  absoluteChildren;
74577
74919
  platform;
74578
74920
  mmopts;
74579
- constructor(ignored, { nobrace, nocase, noext, noglobstar, platform: platform4 = defaultPlatform2 }) {
74921
+ constructor(ignored, { nobrace, nocase, noext, noglobstar, platform: platform5 = defaultPlatform2 }) {
74580
74922
  this.relative = [];
74581
74923
  this.absolute = [];
74582
74924
  this.relativeChildren = [];
74583
74925
  this.absoluteChildren = [];
74584
- this.platform = platform4;
74926
+ this.platform = platform5;
74585
74927
  this.mmopts = {
74586
74928
  dot: true,
74587
74929
  nobrace,
@@ -74589,7 +74931,7 @@ class Ignore {
74589
74931
  noext,
74590
74932
  noglobstar,
74591
74933
  optimizationLevel: 2,
74592
- platform: platform4,
74934
+ platform: platform5,
74593
74935
  nocomment: true,
74594
74936
  nonegate: true
74595
74937
  };
@@ -76674,7 +77016,7 @@ function resolveSubagentLauncher(cliArgs, options = {}) {
76674
77016
  const env4 = options.env ?? process.env;
76675
77017
  const argv = options.argv ?? process.argv;
76676
77018
  const execPath = options.execPath ?? process.execPath;
76677
- const platform4 = options.platform ?? process.platform;
77019
+ const platform5 = options.platform ?? process.platform;
76678
77020
  const invocation = resolveLettaInvocation(env4, argv, execPath);
76679
77021
  if (invocation) {
76680
77022
  return {
@@ -76689,7 +77031,7 @@ function resolveSubagentLauncher(cliArgs, options = {}) {
76689
77031
  args: [currentScript, ...cliArgs]
76690
77032
  };
76691
77033
  }
76692
- if (currentScript.endsWith(".js") && platform4 === "win32") {
77034
+ if (currentScript.endsWith(".js") && platform5 === "win32") {
76693
77035
  return {
76694
77036
  command: execPath,
76695
77037
  args: [currentScript, ...cliArgs]
@@ -87255,130 +87597,6 @@ var init_agentInfo = __esm(async () => {
87255
87597
  await init_settings_manager();
87256
87598
  });
87257
87599
 
87258
- // src/cli/helpers/sessionContext.ts
87259
- import { platform as platform4 } from "node:os";
87260
- function getLocalTime() {
87261
- const now = new Date;
87262
- return now.toLocaleString(undefined, {
87263
- weekday: "long",
87264
- year: "numeric",
87265
- month: "long",
87266
- day: "numeric",
87267
- hour: "2-digit",
87268
- minute: "2-digit",
87269
- timeZoneName: "short"
87270
- });
87271
- }
87272
- function getDeviceType() {
87273
- const p = platform4();
87274
- switch (p) {
87275
- case "darwin":
87276
- return "macOS";
87277
- case "win32":
87278
- return "Windows";
87279
- case "linux":
87280
- return "Linux";
87281
- default:
87282
- return p;
87283
- }
87284
- }
87285
- function getGitInfo(cwd2) {
87286
- const git = gatherGitContextSnapshot({
87287
- cwd: cwd2,
87288
- recentCommitLimit: 3,
87289
- recentCommitFormat: "%h %s (%an)",
87290
- statusLineLimit: 20
87291
- });
87292
- if (!git.isGitRepo) {
87293
- return { isGitRepo: false };
87294
- }
87295
- return {
87296
- isGitRepo: true,
87297
- branch: git.branch ?? "(unknown)",
87298
- recentCommits: git.recentCommits ?? "(failed to get commits)",
87299
- status: git.status || "(clean working tree)",
87300
- gitUser: git.gitUser ?? "(not configured)"
87301
- };
87302
- }
87303
- function getIntroText(source, reason) {
87304
- if (reason === "cwd_changed") {
87305
- return "The working directory for this conversation has changed. Updated environment context follows.";
87306
- }
87307
- switch (source) {
87308
- case "listen":
87309
- return "This conversation is now connected to a Letta Code execution environment.";
87310
- case "headless":
87311
- return "The user has just initiated a new connection via the Letta Code headless client.";
87312
- default:
87313
- return "The user has just initiated a new connection via the [Letta Code CLI client](https://docs.letta.com/letta-code/index.md).";
87314
- }
87315
- }
87316
- function buildSessionContext(options) {
87317
- try {
87318
- const cwd2 = options?.cwd ?? process.cwd();
87319
- const source = options?.source ?? "interactive-cli";
87320
- const reason = options?.reason ?? "initial_attach";
87321
- let version = "unknown";
87322
- try {
87323
- version = getVersion();
87324
- } catch {}
87325
- let deviceType = "unknown";
87326
- try {
87327
- deviceType = getDeviceType();
87328
- } catch {}
87329
- let localTime = "unknown";
87330
- try {
87331
- localTime = getLocalTime();
87332
- } catch {}
87333
- const gitInfo = getGitInfo(cwd2);
87334
- let context3 = `${SYSTEM_REMINDER_OPEN}
87335
- This is an automated message providing context about the user's environment.
87336
- ${getIntroText(source, reason)}
87337
-
87338
- ## Device Information
87339
- - **Local time**: ${localTime}
87340
- - **Device type**: ${deviceType}
87341
- - **Letta Code version**: ${version}
87342
- - **Current working directory**: ${cwd2}
87343
- `;
87344
- if (gitInfo.isGitRepo) {
87345
- context3 += `- **Git repository**: Yes (branch: ${gitInfo.branch})
87346
- - **Git user**: ${gitInfo.gitUser}
87347
-
87348
- ### Recent Commits
87349
- \`\`\`
87350
- ${gitInfo.recentCommits}
87351
- \`\`\`
87352
-
87353
- ### Git Status
87354
- \`\`\`
87355
- ${gitInfo.status}
87356
- \`\`\`
87357
- `;
87358
- } else {
87359
- context3 += `- **Git repository**: No
87360
- `;
87361
- }
87362
- if (platform4() === "win32") {
87363
- context3 += `
87364
- ## Windows Shell Notes
87365
- - The Bash tool uses PowerShell or cmd.exe on Windows
87366
- - HEREDOC syntax (e.g., \`$(cat <<'EOF'...EOF)\`) does NOT work on Windows
87367
- - For multiline strings (git commits, PR bodies), use simple quoted strings instead
87368
- `;
87369
- }
87370
- context3 += SYSTEM_REMINDER_CLOSE;
87371
- return context3;
87372
- } catch {
87373
- return "";
87374
- }
87375
- }
87376
- var init_sessionContext = __esm(() => {
87377
- init_constants();
87378
- init_version();
87379
- init_gitContext();
87380
- });
87381
-
87382
87600
  // src/reminders/catalog.ts
87383
87601
  var SHARED_REMINDER_CATALOG, SHARED_REMINDER_IDS, SHARED_REMINDER_BY_ID;
87384
87602
  var init_catalog = __esm(() => {
@@ -94145,6 +94363,67 @@ function isSetReflectionSettingsCommand(value) {
94145
94363
  const settings = c.settings;
94146
94364
  return (settings.trigger === "off" || settings.trigger === "step-count" || settings.trigger === "compaction-event") && typeof settings.step_count === "number" && Number.isInteger(settings.step_count) && settings.step_count > 0 && (c.scope === undefined || c.scope === "local_project" || c.scope === "global" || c.scope === "both");
94147
94365
  }
94366
+ function isChannelId(value) {
94367
+ return value === "telegram";
94368
+ }
94369
+ function isChannelsListCommand(value) {
94370
+ if (!value || typeof value !== "object")
94371
+ return false;
94372
+ const c = value;
94373
+ return c.type === "channels_list" && typeof c.request_id === "string";
94374
+ }
94375
+ function isChannelGetConfigCommand(value) {
94376
+ if (!value || typeof value !== "object")
94377
+ return false;
94378
+ const c = value;
94379
+ return c.type === "channel_get_config" && typeof c.request_id === "string" && isChannelId(c.channel_id);
94380
+ }
94381
+ function isChannelSetConfigCommand(value) {
94382
+ if (!value || typeof value !== "object")
94383
+ return false;
94384
+ const c = value;
94385
+ if (c.type !== "channel_set_config" || typeof c.request_id !== "string" || !isChannelId(c.channel_id) || !c.config || typeof c.config !== "object") {
94386
+ return false;
94387
+ }
94388
+ const config = c.config;
94389
+ return (config.token === undefined || typeof config.token === "string") && (config.dm_policy === undefined || config.dm_policy === "pairing" || config.dm_policy === "allowlist" || config.dm_policy === "open") && (config.allowed_users === undefined || Array.isArray(config.allowed_users) && config.allowed_users.every((entry) => typeof entry === "string"));
94390
+ }
94391
+ function isChannelStartCommand(value) {
94392
+ if (!value || typeof value !== "object")
94393
+ return false;
94394
+ const c = value;
94395
+ return c.type === "channel_start" && typeof c.request_id === "string" && isChannelId(c.channel_id);
94396
+ }
94397
+ function isChannelStopCommand(value) {
94398
+ if (!value || typeof value !== "object")
94399
+ return false;
94400
+ const c = value;
94401
+ return c.type === "channel_stop" && typeof c.request_id === "string" && isChannelId(c.channel_id);
94402
+ }
94403
+ function isChannelPairingsListCommand(value) {
94404
+ if (!value || typeof value !== "object")
94405
+ return false;
94406
+ const c = value;
94407
+ return c.type === "channel_pairings_list" && typeof c.request_id === "string" && isChannelId(c.channel_id);
94408
+ }
94409
+ function isChannelPairingBindCommand(value) {
94410
+ if (!value || typeof value !== "object")
94411
+ return false;
94412
+ const c = value;
94413
+ return c.type === "channel_pairing_bind" && typeof c.request_id === "string" && isChannelId(c.channel_id) && isRuntimeScope(c.runtime) && typeof c.code === "string" && c.code.length > 0;
94414
+ }
94415
+ function isChannelRoutesListCommand(value) {
94416
+ if (!value || typeof value !== "object")
94417
+ return false;
94418
+ const c = value;
94419
+ return c.type === "channel_routes_list" && typeof c.request_id === "string" && (c.channel_id === undefined || isChannelId(c.channel_id)) && (c.agent_id === undefined || typeof c.agent_id === "string") && (c.conversation_id === undefined || typeof c.conversation_id === "string");
94420
+ }
94421
+ function isChannelRouteRemoveCommand(value) {
94422
+ if (!value || typeof value !== "object")
94423
+ return false;
94424
+ const c = value;
94425
+ return c.type === "channel_route_remove" && typeof c.request_id === "string" && isChannelId(c.channel_id) && typeof c.chat_id === "string" && c.chat_id.length > 0;
94426
+ }
94148
94427
  function isSearchBranchesCommand(value) {
94149
94428
  if (!value || typeof value !== "object")
94150
94429
  return false;
@@ -94168,7 +94447,7 @@ function parseServerMessage(data) {
94168
94447
  try {
94169
94448
  const raw = typeof data === "string" ? data : data.toString();
94170
94449
  const parsed = JSON.parse(raw);
94171
- if (isInputCommand(parsed) || isChangeDeviceStateCommand(parsed) || isAbortMessageCommand(parsed) || isSyncCommand(parsed) || isTerminalSpawnCommand(parsed) || isTerminalInputCommand(parsed) || isTerminalResizeCommand(parsed) || isTerminalKillCommand(parsed) || isSearchFilesCommand(parsed) || isListInDirectoryCommand(parsed) || isReadFileCommand(parsed) || isWriteFileCommand(parsed) || isWatchFileCommand(parsed) || isUnwatchFileCommand(parsed) || isEditFileCommand(parsed) || isListMemoryCommand(parsed) || isMemoryHistoryCommand(parsed) || isMemoryFileAtRefCommand(parsed) || isEnableMemfsCommand(parsed) || isListModelsCommand(parsed) || isUpdateModelCommand(parsed) || isCronListCommand(parsed) || isCronAddCommand(parsed) || isCronGetCommand(parsed) || isCronDeleteCommand(parsed) || isCronDeleteAllCommand(parsed) || isSkillEnableCommand(parsed) || isSkillDisableCommand(parsed) || isCreateAgentCommand(parsed) || isGetReflectionSettingsCommand(parsed) || isSetReflectionSettingsCommand(parsed) || isExecuteCommandCommand(parsed) || isSearchBranchesCommand(parsed) || isCheckoutBranchCommand(parsed)) {
94450
+ if (isInputCommand(parsed) || isChangeDeviceStateCommand(parsed) || isAbortMessageCommand(parsed) || isSyncCommand(parsed) || isTerminalSpawnCommand(parsed) || isTerminalInputCommand(parsed) || isTerminalResizeCommand(parsed) || isTerminalKillCommand(parsed) || isSearchFilesCommand(parsed) || isListInDirectoryCommand(parsed) || isReadFileCommand(parsed) || isWriteFileCommand(parsed) || isWatchFileCommand(parsed) || isUnwatchFileCommand(parsed) || isEditFileCommand(parsed) || isListMemoryCommand(parsed) || isMemoryHistoryCommand(parsed) || isMemoryFileAtRefCommand(parsed) || isEnableMemfsCommand(parsed) || isListModelsCommand(parsed) || isUpdateModelCommand(parsed) || isCronListCommand(parsed) || isCronAddCommand(parsed) || isCronGetCommand(parsed) || isCronDeleteCommand(parsed) || isCronDeleteAllCommand(parsed) || isSkillEnableCommand(parsed) || isSkillDisableCommand(parsed) || isCreateAgentCommand(parsed) || isGetReflectionSettingsCommand(parsed) || isSetReflectionSettingsCommand(parsed) || isChannelsListCommand(parsed) || isChannelGetConfigCommand(parsed) || isChannelSetConfigCommand(parsed) || isChannelStartCommand(parsed) || isChannelStopCommand(parsed) || isChannelPairingsListCommand(parsed) || isChannelPairingBindCommand(parsed) || isChannelRoutesListCommand(parsed) || isChannelRouteRemoveCommand(parsed) || isExecuteCommandCommand(parsed) || isSearchBranchesCommand(parsed) || isCheckoutBranchCommand(parsed)) {
94172
94451
  return parsed;
94173
94452
  }
94174
94453
  const invalidInput = getInvalidInputReason(parsed);
@@ -94189,6 +94468,201 @@ var init_protocol_inbound = __esm(async () => {
94189
94468
  await init_approval();
94190
94469
  });
94191
94470
 
94471
+ // src/channels/service.ts
94472
+ var exports_service = {};
94473
+ __export(exports_service, {
94474
+ stopChannelLive: () => stopChannelLive,
94475
+ startChannelLive: () => startChannelLive,
94476
+ setChannelConfigLive: () => setChannelConfigLive,
94477
+ removeChannelRouteLive: () => removeChannelRouteLive,
94478
+ listPendingPairingSnapshots: () => listPendingPairingSnapshots,
94479
+ listChannelSummaries: () => listChannelSummaries,
94480
+ listChannelRouteSnapshots: () => listChannelRouteSnapshots,
94481
+ getChannelConfigSnapshot: () => getChannelConfigSnapshot,
94482
+ bindChannelPairing: () => bindChannelPairing,
94483
+ CHANNEL_DISPLAY_NAMES: () => CHANNEL_DISPLAY_NAMES
94484
+ });
94485
+ function assertSupportedChannelId(channelId) {
94486
+ if (channelId !== "telegram") {
94487
+ throw new Error(`Unsupported channel: ${channelId}`);
94488
+ }
94489
+ }
94490
+ function toConfigSnapshot(channelId, config) {
94491
+ return {
94492
+ channelId,
94493
+ enabled: config.enabled,
94494
+ dmPolicy: config.dmPolicy,
94495
+ allowedUsers: [...config.allowedUsers],
94496
+ hasToken: config.token.trim().length > 0
94497
+ };
94498
+ }
94499
+ function toPendingPairingSnapshot(pending) {
94500
+ return {
94501
+ code: pending.code,
94502
+ senderId: pending.telegramUserId,
94503
+ senderName: pending.telegramUsername,
94504
+ chatId: pending.chatId,
94505
+ createdAt: pending.createdAt,
94506
+ expiresAt: pending.expiresAt
94507
+ };
94508
+ }
94509
+ function toRouteSnapshot(channelId, route) {
94510
+ return {
94511
+ channelId,
94512
+ chatId: route.chatId,
94513
+ agentId: route.agentId,
94514
+ conversationId: route.conversationId,
94515
+ enabled: route.enabled,
94516
+ createdAt: route.createdAt
94517
+ };
94518
+ }
94519
+ function listChannelSummaries() {
94520
+ const registry = getChannelRegistry();
94521
+ const channelId = "telegram";
94522
+ const config = readChannelConfig(channelId);
94523
+ if (!config) {
94524
+ return [
94525
+ {
94526
+ channelId,
94527
+ displayName: CHANNEL_DISPLAY_NAMES[channelId],
94528
+ configured: false,
94529
+ enabled: false,
94530
+ running: false,
94531
+ dmPolicy: null,
94532
+ pendingPairingsCount: 0,
94533
+ approvedUsersCount: 0,
94534
+ routesCount: 0
94535
+ }
94536
+ ];
94537
+ }
94538
+ loadRoutes(channelId);
94539
+ loadPairingStore(channelId);
94540
+ return [
94541
+ {
94542
+ channelId,
94543
+ displayName: CHANNEL_DISPLAY_NAMES[channelId],
94544
+ configured: true,
94545
+ enabled: config.enabled,
94546
+ running: registry?.getAdapter(channelId)?.isRunning() ?? false,
94547
+ dmPolicy: config.dmPolicy,
94548
+ pendingPairingsCount: getPendingPairings(channelId).length,
94549
+ approvedUsersCount: getApprovedUsers(channelId).length,
94550
+ routesCount: getRoutesForChannel(channelId).length
94551
+ }
94552
+ ];
94553
+ }
94554
+ function getChannelConfigSnapshot(channelId) {
94555
+ assertSupportedChannelId(channelId);
94556
+ const config = readChannelConfig(channelId);
94557
+ if (!config) {
94558
+ return null;
94559
+ }
94560
+ return toConfigSnapshot(channelId, config);
94561
+ }
94562
+ async function setChannelConfigLive(channelId, patch) {
94563
+ assertSupportedChannelId(channelId);
94564
+ const existing = readChannelConfig(channelId);
94565
+ const merged = {
94566
+ channel: "telegram",
94567
+ enabled: existing?.enabled ?? false,
94568
+ token: patch.token ?? existing?.token ?? "",
94569
+ dmPolicy: patch.dmPolicy ?? existing?.dmPolicy ?? "pairing",
94570
+ allowedUsers: patch.allowedUsers ?? existing?.allowedUsers ?? []
94571
+ };
94572
+ writeChannelConfig(channelId, merged);
94573
+ if (merged.enabled) {
94574
+ const registry = ensureChannelRegistry();
94575
+ await registry.startChannel(channelId);
94576
+ }
94577
+ return toConfigSnapshot(channelId, merged);
94578
+ }
94579
+ async function startChannelLive(channelId) {
94580
+ assertSupportedChannelId(channelId);
94581
+ const existing = readChannelConfig(channelId);
94582
+ if (!existing) {
94583
+ throw new Error(`Channel "${channelId}" is not configured. Configure it first.`);
94584
+ }
94585
+ if (!existing.token.trim()) {
94586
+ throw new Error(`Channel "${channelId}" is missing a token. Configure it first.`);
94587
+ }
94588
+ if (!existing.enabled) {
94589
+ writeChannelConfig(channelId, {
94590
+ ...existing,
94591
+ enabled: true
94592
+ });
94593
+ }
94594
+ if (!getChannelRegistry()) {
94595
+ await initializeChannels([channelId]);
94596
+ } else {
94597
+ await ensureChannelRegistry().startChannel(channelId);
94598
+ }
94599
+ const summary = listChannelSummaries().find((entry) => entry.channelId === channelId);
94600
+ if (!summary) {
94601
+ throw new Error(`Channel "${channelId}" summary not found after start`);
94602
+ }
94603
+ return summary;
94604
+ }
94605
+ async function stopChannelLive(channelId) {
94606
+ assertSupportedChannelId(channelId);
94607
+ const existing = readChannelConfig(channelId);
94608
+ if (!existing) {
94609
+ throw new Error(`Channel "${channelId}" is not configured. Configure it first.`);
94610
+ }
94611
+ writeChannelConfig(channelId, {
94612
+ ...existing,
94613
+ enabled: false
94614
+ });
94615
+ await getChannelRegistry()?.stopChannel(channelId);
94616
+ const summary = listChannelSummaries().find((entry) => entry.channelId === channelId);
94617
+ if (!summary) {
94618
+ throw new Error(`Channel "${channelId}" summary not found after stop`);
94619
+ }
94620
+ return summary;
94621
+ }
94622
+ function listPendingPairingSnapshots(channelId) {
94623
+ assertSupportedChannelId(channelId);
94624
+ loadPairingStore(channelId);
94625
+ return getPendingPairings(channelId).map(toPendingPairingSnapshot);
94626
+ }
94627
+ function bindChannelPairing(channelId, code, agentId, conversationId) {
94628
+ assertSupportedChannelId(channelId);
94629
+ loadRoutes(channelId);
94630
+ loadPairingStore(channelId);
94631
+ const result = completePairing(channelId, code, agentId, conversationId);
94632
+ if (!result.success || !result.chatId) {
94633
+ throw new Error(result.error ?? "Failed to bind pairing");
94634
+ }
94635
+ const route = getRoute(channelId, result.chatId);
94636
+ if (!route) {
94637
+ throw new Error("Pairing succeeded but route was not found");
94638
+ }
94639
+ return {
94640
+ chatId: result.chatId,
94641
+ route: toRouteSnapshot(channelId, route)
94642
+ };
94643
+ }
94644
+ function listChannelRouteSnapshots(params) {
94645
+ const channelId = params?.channelId ?? "telegram";
94646
+ assertSupportedChannelId(channelId);
94647
+ loadRoutes(channelId);
94648
+ return getRoutesForChannel(channelId).filter((route) => params?.agentId ? route.agentId === params.agentId : true).filter((route) => params?.conversationId ? route.conversationId === params.conversationId : true).map((route) => toRouteSnapshot(channelId, route));
94649
+ }
94650
+ function removeChannelRouteLive(channelId, chatId) {
94651
+ assertSupportedChannelId(channelId);
94652
+ loadRoutes(channelId);
94653
+ return removeRoute(channelId, chatId);
94654
+ }
94655
+ var CHANNEL_DISPLAY_NAMES;
94656
+ var init_service = __esm(() => {
94657
+ init_config();
94658
+ init_pairing();
94659
+ init_registry();
94660
+ init_routing();
94661
+ CHANNEL_DISPLAY_NAMES = {
94662
+ telegram: "Telegram"
94663
+ };
94664
+ });
94665
+
94192
94666
  // src/agent/memoryScanner.ts
94193
94667
  var exports_memoryScanner = {};
94194
94668
  __export(exports_memoryScanner, {
@@ -94562,6 +95036,29 @@ function emitCronsUpdated(socket, scope) {
94562
95036
  ...scope?.conversation_id !== undefined ? { conversation_id: scope.conversation_id } : {}
94563
95037
  }, "listener_cron_send_failed", "listener_cron_command");
94564
95038
  }
95039
+ function emitChannelsUpdated(socket, channelId) {
95040
+ safeSocketSend(socket, {
95041
+ type: "channels_updated",
95042
+ timestamp: Date.now(),
95043
+ ...channelId ? { channel_id: channelId } : {}
95044
+ }, "listener_channels_send_failed", "listener_channels_command");
95045
+ }
95046
+ function emitChannelPairingsUpdated(socket, channelId) {
95047
+ safeSocketSend(socket, {
95048
+ type: "channel_pairings_updated",
95049
+ timestamp: Date.now(),
95050
+ channel_id: channelId
95051
+ }, "listener_channels_send_failed", "listener_channels_command");
95052
+ }
95053
+ function emitChannelRoutesUpdated(socket, params) {
95054
+ safeSocketSend(socket, {
95055
+ type: "channel_routes_updated",
95056
+ timestamp: Date.now(),
95057
+ channel_id: params.channelId,
95058
+ ...params.agentId ? { agent_id: params.agentId } : {},
95059
+ ...params.conversationId !== undefined ? { conversation_id: params.conversationId } : {}
95060
+ }, "listener_channels_send_failed", "listener_channels_command");
95061
+ }
94565
95062
  async function handleCronCommand(parsed, socket) {
94566
95063
  if (parsed.type === "cron_list") {
94567
95064
  try {
@@ -94699,6 +95196,299 @@ async function handleCronCommand(parsed, socket) {
94699
95196
  }
94700
95197
  return true;
94701
95198
  }
95199
+ async function handleChannelsProtocolCommand(parsed, socket, runtime, opts, processQueuedTurn) {
95200
+ const {
95201
+ bindChannelPairing: bindChannelPairing2,
95202
+ getChannelConfigSnapshot: getChannelConfigSnapshot2,
95203
+ listChannelRouteSnapshots: listChannelRouteSnapshots2,
95204
+ listChannelSummaries: listChannelSummaries2,
95205
+ listPendingPairingSnapshots: listPendingPairingSnapshots2,
95206
+ removeChannelRouteLive: removeChannelRouteLive2,
95207
+ setChannelConfigLive: setChannelConfigLive2,
95208
+ startChannelLive: startChannelLive2,
95209
+ stopChannelLive: stopChannelLive2
95210
+ } = await Promise.resolve().then(() => (init_service(), exports_service));
95211
+ if (parsed.type === "channels_list") {
95212
+ try {
95213
+ safeSocketSend(socket, {
95214
+ type: "channels_list_response",
95215
+ request_id: parsed.request_id,
95216
+ success: true,
95217
+ channels: listChannelSummaries2().map((summary) => ({
95218
+ channel_id: summary.channelId,
95219
+ display_name: summary.displayName,
95220
+ configured: summary.configured,
95221
+ enabled: summary.enabled,
95222
+ running: summary.running,
95223
+ dm_policy: summary.dmPolicy,
95224
+ pending_pairings_count: summary.pendingPairingsCount,
95225
+ approved_users_count: summary.approvedUsersCount,
95226
+ routes_count: summary.routesCount
95227
+ }))
95228
+ }, "listener_channels_send_failed", "listener_channels_command");
95229
+ } catch (err) {
95230
+ safeSocketSend(socket, {
95231
+ type: "channels_list_response",
95232
+ request_id: parsed.request_id,
95233
+ success: false,
95234
+ channels: [],
95235
+ error: err instanceof Error ? err.message : "Failed to list channels"
95236
+ }, "listener_channels_send_failed", "listener_channels_command");
95237
+ }
95238
+ return true;
95239
+ }
95240
+ if (parsed.type === "channel_get_config") {
95241
+ try {
95242
+ safeSocketSend(socket, {
95243
+ type: "channel_get_config_response",
95244
+ request_id: parsed.request_id,
95245
+ success: true,
95246
+ config: (() => {
95247
+ const snapshot = getChannelConfigSnapshot2(parsed.channel_id);
95248
+ return snapshot ? {
95249
+ channel_id: snapshot.channelId,
95250
+ enabled: snapshot.enabled,
95251
+ dm_policy: snapshot.dmPolicy,
95252
+ allowed_users: snapshot.allowedUsers,
95253
+ has_token: snapshot.hasToken
95254
+ } : null;
95255
+ })()
95256
+ }, "listener_channels_send_failed", "listener_channels_command");
95257
+ } catch (err) {
95258
+ safeSocketSend(socket, {
95259
+ type: "channel_get_config_response",
95260
+ request_id: parsed.request_id,
95261
+ success: false,
95262
+ config: null,
95263
+ error: err instanceof Error ? err.message : "Failed to read channel config"
95264
+ }, "listener_channels_send_failed", "listener_channels_command");
95265
+ }
95266
+ return true;
95267
+ }
95268
+ if (parsed.type === "channel_set_config") {
95269
+ try {
95270
+ const snapshot = await setChannelConfigLive2(parsed.channel_id, {
95271
+ token: parsed.config.token,
95272
+ dmPolicy: parsed.config.dm_policy,
95273
+ allowedUsers: parsed.config.allowed_users
95274
+ });
95275
+ if (snapshot.enabled) {
95276
+ wireChannelIngress(runtime, socket, opts, processQueuedTurn);
95277
+ }
95278
+ safeSocketSend(socket, {
95279
+ type: "channel_set_config_response",
95280
+ request_id: parsed.request_id,
95281
+ success: true,
95282
+ config: {
95283
+ channel_id: snapshot.channelId,
95284
+ enabled: snapshot.enabled,
95285
+ dm_policy: snapshot.dmPolicy,
95286
+ allowed_users: snapshot.allowedUsers,
95287
+ has_token: snapshot.hasToken
95288
+ }
95289
+ }, "listener_channels_send_failed", "listener_channels_command");
95290
+ emitChannelsUpdated(socket, parsed.channel_id);
95291
+ } catch (err) {
95292
+ safeSocketSend(socket, {
95293
+ type: "channel_set_config_response",
95294
+ request_id: parsed.request_id,
95295
+ success: false,
95296
+ config: null,
95297
+ error: err instanceof Error ? err.message : "Failed to update channel config"
95298
+ }, "listener_channels_send_failed", "listener_channels_command");
95299
+ }
95300
+ return true;
95301
+ }
95302
+ if (parsed.type === "channel_start") {
95303
+ try {
95304
+ const summary = await startChannelLive2(parsed.channel_id);
95305
+ wireChannelIngress(runtime, socket, opts, processQueuedTurn);
95306
+ safeSocketSend(socket, {
95307
+ type: "channel_start_response",
95308
+ request_id: parsed.request_id,
95309
+ success: true,
95310
+ channel: {
95311
+ channel_id: summary.channelId,
95312
+ display_name: summary.displayName,
95313
+ configured: summary.configured,
95314
+ enabled: summary.enabled,
95315
+ running: summary.running,
95316
+ dm_policy: summary.dmPolicy,
95317
+ pending_pairings_count: summary.pendingPairingsCount,
95318
+ approved_users_count: summary.approvedUsersCount,
95319
+ routes_count: summary.routesCount
95320
+ }
95321
+ }, "listener_channels_send_failed", "listener_channels_command");
95322
+ emitChannelsUpdated(socket, parsed.channel_id);
95323
+ } catch (err) {
95324
+ safeSocketSend(socket, {
95325
+ type: "channel_start_response",
95326
+ request_id: parsed.request_id,
95327
+ success: false,
95328
+ channel: null,
95329
+ error: err instanceof Error ? err.message : "Failed to start channel"
95330
+ }, "listener_channels_send_failed", "listener_channels_command");
95331
+ }
95332
+ return true;
95333
+ }
95334
+ if (parsed.type === "channel_stop") {
95335
+ try {
95336
+ const summary = await stopChannelLive2(parsed.channel_id);
95337
+ safeSocketSend(socket, {
95338
+ type: "channel_stop_response",
95339
+ request_id: parsed.request_id,
95340
+ success: true,
95341
+ channel: {
95342
+ channel_id: summary.channelId,
95343
+ display_name: summary.displayName,
95344
+ configured: summary.configured,
95345
+ enabled: summary.enabled,
95346
+ running: summary.running,
95347
+ dm_policy: summary.dmPolicy,
95348
+ pending_pairings_count: summary.pendingPairingsCount,
95349
+ approved_users_count: summary.approvedUsersCount,
95350
+ routes_count: summary.routesCount
95351
+ }
95352
+ }, "listener_channels_send_failed", "listener_channels_command");
95353
+ emitChannelsUpdated(socket, parsed.channel_id);
95354
+ } catch (err) {
95355
+ safeSocketSend(socket, {
95356
+ type: "channel_stop_response",
95357
+ request_id: parsed.request_id,
95358
+ success: false,
95359
+ channel: null,
95360
+ error: err instanceof Error ? err.message : "Failed to stop channel"
95361
+ }, "listener_channels_send_failed", "listener_channels_command");
95362
+ }
95363
+ return true;
95364
+ }
95365
+ if (parsed.type === "channel_pairings_list") {
95366
+ try {
95367
+ safeSocketSend(socket, {
95368
+ type: "channel_pairings_list_response",
95369
+ request_id: parsed.request_id,
95370
+ success: true,
95371
+ channel_id: parsed.channel_id,
95372
+ pending: listPendingPairingSnapshots2(parsed.channel_id).map((pending) => ({
95373
+ code: pending.code,
95374
+ sender_id: pending.senderId,
95375
+ sender_name: pending.senderName,
95376
+ chat_id: pending.chatId,
95377
+ created_at: pending.createdAt,
95378
+ expires_at: pending.expiresAt
95379
+ }))
95380
+ }, "listener_channels_send_failed", "listener_channels_command");
95381
+ } catch (err) {
95382
+ safeSocketSend(socket, {
95383
+ type: "channel_pairings_list_response",
95384
+ request_id: parsed.request_id,
95385
+ success: false,
95386
+ channel_id: parsed.channel_id,
95387
+ pending: [],
95388
+ error: err instanceof Error ? err.message : "Failed to list pending pairings"
95389
+ }, "listener_channels_send_failed", "listener_channels_command");
95390
+ }
95391
+ return true;
95392
+ }
95393
+ if (parsed.type === "channel_pairing_bind") {
95394
+ try {
95395
+ const result = bindChannelPairing2(parsed.channel_id, parsed.code, parsed.runtime.agent_id, parsed.runtime.conversation_id);
95396
+ safeSocketSend(socket, {
95397
+ type: "channel_pairing_bind_response",
95398
+ request_id: parsed.request_id,
95399
+ success: true,
95400
+ channel_id: parsed.channel_id,
95401
+ chat_id: result.chatId,
95402
+ route: {
95403
+ channel_id: result.route.channelId,
95404
+ chat_id: result.route.chatId,
95405
+ agent_id: result.route.agentId,
95406
+ conversation_id: result.route.conversationId,
95407
+ enabled: result.route.enabled,
95408
+ created_at: result.route.createdAt
95409
+ }
95410
+ }, "listener_channels_send_failed", "listener_channels_command");
95411
+ emitChannelPairingsUpdated(socket, parsed.channel_id);
95412
+ emitChannelRoutesUpdated(socket, {
95413
+ channelId: parsed.channel_id,
95414
+ agentId: parsed.runtime.agent_id,
95415
+ conversationId: parsed.runtime.conversation_id
95416
+ });
95417
+ emitChannelsUpdated(socket, parsed.channel_id);
95418
+ } catch (err) {
95419
+ safeSocketSend(socket, {
95420
+ type: "channel_pairing_bind_response",
95421
+ request_id: parsed.request_id,
95422
+ success: false,
95423
+ channel_id: parsed.channel_id,
95424
+ route: null,
95425
+ error: err instanceof Error ? err.message : "Failed to bind pairing"
95426
+ }, "listener_channels_send_failed", "listener_channels_command");
95427
+ }
95428
+ return true;
95429
+ }
95430
+ if (parsed.type === "channel_routes_list") {
95431
+ try {
95432
+ const channelId = parsed.channel_id ?? "telegram";
95433
+ safeSocketSend(socket, {
95434
+ type: "channel_routes_list_response",
95435
+ request_id: parsed.request_id,
95436
+ success: true,
95437
+ channel_id: channelId,
95438
+ routes: listChannelRouteSnapshots2({
95439
+ channelId,
95440
+ agentId: parsed.agent_id,
95441
+ conversationId: parsed.conversation_id
95442
+ }).map((route) => ({
95443
+ channel_id: route.channelId,
95444
+ chat_id: route.chatId,
95445
+ agent_id: route.agentId,
95446
+ conversation_id: route.conversationId,
95447
+ enabled: route.enabled,
95448
+ created_at: route.createdAt
95449
+ }))
95450
+ }, "listener_channels_send_failed", "listener_channels_command");
95451
+ } catch (err) {
95452
+ safeSocketSend(socket, {
95453
+ type: "channel_routes_list_response",
95454
+ request_id: parsed.request_id,
95455
+ success: false,
95456
+ channel_id: parsed.channel_id,
95457
+ routes: [],
95458
+ error: err instanceof Error ? err.message : "Failed to list routes"
95459
+ }, "listener_channels_send_failed", "listener_channels_command");
95460
+ }
95461
+ return true;
95462
+ }
95463
+ try {
95464
+ const found = removeChannelRouteLive2(parsed.channel_id, parsed.chat_id);
95465
+ safeSocketSend(socket, {
95466
+ type: "channel_route_remove_response",
95467
+ request_id: parsed.request_id,
95468
+ success: true,
95469
+ channel_id: parsed.channel_id,
95470
+ chat_id: parsed.chat_id,
95471
+ found
95472
+ }, "listener_channels_send_failed", "listener_channels_command");
95473
+ if (found) {
95474
+ emitChannelRoutesUpdated(socket, {
95475
+ channelId: parsed.channel_id
95476
+ });
95477
+ emitChannelsUpdated(socket, parsed.channel_id);
95478
+ }
95479
+ } catch (err) {
95480
+ safeSocketSend(socket, {
95481
+ type: "channel_route_remove_response",
95482
+ request_id: parsed.request_id,
95483
+ success: false,
95484
+ channel_id: parsed.channel_id,
95485
+ chat_id: parsed.chat_id,
95486
+ found: false,
95487
+ error: err instanceof Error ? err.message : "Failed to remove route"
95488
+ }, "listener_channels_send_failed", "listener_channels_command");
95489
+ }
95490
+ return true;
95491
+ }
94702
95492
  function emitSkillsUpdated(socket) {
94703
95493
  safeSocketSend(socket, {
94704
95494
  type: "skills_updated",
@@ -94958,6 +95748,12 @@ function wireChannelIngress(listener, socket, opts, processQueuedTurn) {
94958
95748
  enqueueChannelTurn(conversationRuntime, route, xmlContent);
94959
95749
  scheduleQueuePump(conversationRuntime, socket, opts, processQueuedTurn);
94960
95750
  });
95751
+ registry.setEventHandler((event) => {
95752
+ if (event.type === "pairings_updated") {
95753
+ emitChannelPairingsUpdated(socket, event.channelId);
95754
+ emitChannelsUpdated(socket, event.channelId);
95755
+ }
95756
+ });
94961
95757
  registry.setReady();
94962
95758
  }
94963
95759
  function enqueueChannelTurn(runtime, route, xmlContent) {
@@ -96196,6 +96992,12 @@ async function connectWithRetry(runtime, opts, attempt = 0, startTime = Date.now
96196
96992
  });
96197
96993
  return;
96198
96994
  }
96995
+ if (isChannelsListCommand(parsed) || isChannelGetConfigCommand(parsed) || isChannelSetConfigCommand(parsed) || isChannelStartCommand(parsed) || isChannelStopCommand(parsed) || isChannelPairingsListCommand(parsed) || isChannelPairingBindCommand(parsed) || isChannelRoutesListCommand(parsed) || isChannelRouteRemoveCommand(parsed)) {
96996
+ runDetachedListenerTask("channels_command", async () => {
96997
+ await handleChannelsProtocolCommand(parsed, socket, runtime, opts, processQueuedTurn);
96998
+ });
96999
+ return;
97000
+ }
96199
97001
  if (isSkillEnableCommand(parsed) || isSkillDisableCommand(parsed)) {
96200
97002
  runDetachedListenerTask("skill_command", async () => {
96201
97003
  await handleSkillCommand(parsed, socket);
@@ -96696,6 +97498,7 @@ var init_client4 = __esm(async () => {
96696
97498
  handleAbortMessageInput,
96697
97499
  handleChangeDeviceStateInput,
96698
97500
  handleCronCommand,
97501
+ handleChannelsProtocolCommand,
96699
97502
  handleSkillCommand,
96700
97503
  handleCreateAgentCommand,
96701
97504
  handleReflectionSettingsCommand,
@@ -115871,7 +116674,7 @@ function xml(hljs) {
115871
116674
  ]
115872
116675
  };
115873
116676
  }
115874
- var init_xml = () => {};
116677
+ var init_xml2 = () => {};
115875
116678
 
115876
116679
  // node_modules/highlight.js/es/languages/yaml.js
115877
116680
  function yaml(hljs) {
@@ -116091,7 +116894,7 @@ var init_common = __esm(() => {
116091
116894
  init_typescript2();
116092
116895
  init_vbnet();
116093
116896
  init_wasm();
116094
- init_xml();
116897
+ init_xml2();
116095
116898
  init_yaml();
116096
116899
  grammars = {
116097
116900
  arduino,
@@ -165182,4 +165985,4 @@ Error during initialization: ${message}`);
165182
165985
  }
165183
165986
  main();
165184
165987
 
165185
- //# debugId=CB456D0ECB3C77E564756E2164756E21
165988
+ //# debugId=177C3FD8089A6F3E64756E2164756E21