@letta-ai/letta-code 0.22.1 → 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 +464 -293
  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.1",
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) => {
@@ -48863,6 +49248,7 @@ var init_registry = __esm(() => {
48863
49248
  init_config();
48864
49249
  init_pairing();
48865
49250
  init_routing();
49251
+ init_xml();
48866
49252
  });
48867
49253
 
48868
49254
  // src/channels/telegram/setup.ts
@@ -49650,7 +50036,7 @@ var init_default_browser_id = __esm(() => {
49650
50036
  // node_modules/run-applescript/index.js
49651
50037
  import process17 from "node:process";
49652
50038
  import { promisify as promisify4 } from "node:util";
49653
- import { execFile as execFile4, execFileSync } from "node:child_process";
50039
+ import { execFile as execFile4, execFileSync as execFileSync2 } from "node:child_process";
49654
50040
  async function runAppleScript(script, { humanReadableOutput = true, signal } = {}) {
49655
50041
  if (process17.platform !== "darwin") {
49656
50042
  throw new Error("macOS only");
@@ -49790,16 +50176,16 @@ function detectArchBinary(binary) {
49790
50176
  }
49791
50177
  return archBinary;
49792
50178
  }
49793
- function detectPlatformBinary({ [platform3]: platformBinary }, { wsl }) {
50179
+ function detectPlatformBinary({ [platform4]: platformBinary }, { wsl }) {
49794
50180
  if (wsl && is_wsl_default) {
49795
50181
  return detectArchBinary(wsl);
49796
50182
  }
49797
50183
  if (!platformBinary) {
49798
- throw new Error(`${platform3} is not supported`);
50184
+ throw new Error(`${platform4} is not supported`);
49799
50185
  }
49800
50186
  return detectArchBinary(platformBinary);
49801
50187
  }
49802
- var execFile7, __dirname2, localXdgOpenPath, platform3, arch, pTryEach = async (array, mapper) => {
50188
+ var execFile7, __dirname2, localXdgOpenPath, platform4, arch, pTryEach = async (array, mapper) => {
49803
50189
  let latestError;
49804
50190
  for (const item of array) {
49805
50191
  try {
@@ -49871,7 +50257,7 @@ var execFile7, __dirname2, localXdgOpenPath, platform3, arch, pTryEach = async (
49871
50257
  let command;
49872
50258
  const cliArguments = [];
49873
50259
  const childProcessOptions = {};
49874
- if (platform3 === "darwin") {
50260
+ if (platform4 === "darwin") {
49875
50261
  command = "open";
49876
50262
  if (options.wait) {
49877
50263
  cliArguments.push("--wait-apps");
@@ -49885,7 +50271,7 @@ var execFile7, __dirname2, localXdgOpenPath, platform3, arch, pTryEach = async (
49885
50271
  if (app) {
49886
50272
  cliArguments.push("-a", app);
49887
50273
  }
49888
- } else if (platform3 === "win32" || is_wsl_default && !isInsideContainer() && !app) {
50274
+ } else if (platform4 === "win32" || is_wsl_default && !isInsideContainer() && !app) {
49889
50275
  command = await powerShellPath();
49890
50276
  cliArguments.push("-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-EncodedCommand");
49891
50277
  if (!is_wsl_default) {
@@ -49918,7 +50304,7 @@ var execFile7, __dirname2, localXdgOpenPath, platform3, arch, pTryEach = async (
49918
50304
  await fs6.access(localXdgOpenPath, fsConstants2.X_OK);
49919
50305
  exeLocalXdgOpen = true;
49920
50306
  } catch {}
49921
- const useSystemXdgOpen = process19.versions.electron ?? (platform3 === "android" || isBundled || !exeLocalXdgOpen);
50307
+ const useSystemXdgOpen = process19.versions.electron ?? (platform4 === "android" || isBundled || !exeLocalXdgOpen);
49922
50308
  command = useSystemXdgOpen ? "xdg-open" : localXdgOpenPath;
49923
50309
  }
49924
50310
  if (appArguments.length > 0) {
@@ -49929,7 +50315,7 @@ var execFile7, __dirname2, localXdgOpenPath, platform3, arch, pTryEach = async (
49929
50315
  childProcessOptions.detached = true;
49930
50316
  }
49931
50317
  }
49932
- if (platform3 === "darwin" && appArguments.length > 0) {
50318
+ if (platform4 === "darwin" && appArguments.length > 0) {
49933
50319
  cliArguments.push("--args", ...appArguments);
49934
50320
  }
49935
50321
  if (options.target) {
@@ -49981,7 +50367,7 @@ var init_open = __esm(() => {
49981
50367
  execFile7 = promisify7(childProcess.execFile);
49982
50368
  __dirname2 = path2.dirname(fileURLToPath(import.meta.url));
49983
50369
  localXdgOpenPath = path2.join(__dirname2, "xdg-open");
49984
- ({ platform: platform3, arch } = process19);
50370
+ ({ platform: platform4, arch } = process19);
49985
50371
  apps = {};
49986
50372
  defineLazyProperty(apps, "chrome", () => detectPlatformBinary({
49987
50373
  darwin: "google chrome",
@@ -53098,97 +53484,6 @@ var init_fileIndex = __esm(() => {
53098
53484
  indexRoot2 = process.cwd();
53099
53485
  });
53100
53486
 
53101
- // src/cli/helpers/gitContext.ts
53102
- import { execFileSync as execFileSync2 } from "node:child_process";
53103
- function runGit3(args, cwd2) {
53104
- try {
53105
- return execFileSync2("git", args, {
53106
- cwd: cwd2,
53107
- encoding: "utf-8",
53108
- stdio: ["ignore", "pipe", "ignore"]
53109
- }).trim();
53110
- } catch {
53111
- return null;
53112
- }
53113
- }
53114
- function truncateLines(value, maxLines) {
53115
- const lines = value.split(`
53116
- `);
53117
- if (lines.length <= maxLines) {
53118
- return value;
53119
- }
53120
- return lines.slice(0, maxLines).join(`
53121
- `) + `
53122
- ... and ${lines.length - maxLines} more changes`;
53123
- }
53124
- function formatGitUser(name, email) {
53125
- if (!name && !email) {
53126
- return null;
53127
- }
53128
- if (name && email) {
53129
- return `${name} <${email}>`;
53130
- }
53131
- return name || email;
53132
- }
53133
- function gatherGitContextSnapshot(options = {}) {
53134
- const cwd2 = options.cwd ?? process.cwd();
53135
- const recentCommitLimit = options.recentCommitLimit ?? 3;
53136
- if (!runGit3(["rev-parse", "--git-dir"], cwd2)) {
53137
- return {
53138
- isGitRepo: false,
53139
- branch: null,
53140
- status: null,
53141
- recentCommits: null,
53142
- gitUser: null
53143
- };
53144
- }
53145
- const branch = runGit3(["branch", "--show-current"], cwd2);
53146
- const fullStatus = runGit3(["status", "--short"], cwd2);
53147
- const status = typeof fullStatus === "string" && options.statusLineLimit ? truncateLines(fullStatus, options.statusLineLimit) : fullStatus;
53148
- const recentCommits = options.recentCommitFormat ? runGit3([
53149
- "log",
53150
- `--format=${options.recentCommitFormat}`,
53151
- "-n",
53152
- String(recentCommitLimit)
53153
- ], cwd2) : runGit3(["log", "--oneline", "-n", String(recentCommitLimit)], cwd2);
53154
- const userConfig = runGit3(["config", "--get-regexp", "^user\\.(name|email)$"], cwd2);
53155
- let userName = null;
53156
- let userEmail = null;
53157
- if (userConfig) {
53158
- for (const line of userConfig.split(`
53159
- `)) {
53160
- if (line.startsWith("user.name "))
53161
- userName = line.slice("user.name ".length);
53162
- else if (line.startsWith("user.email "))
53163
- userEmail = line.slice("user.email ".length);
53164
- }
53165
- }
53166
- const gitUser = formatGitUser(userName, userEmail);
53167
- return {
53168
- isGitRepo: true,
53169
- branch,
53170
- status,
53171
- recentCommits,
53172
- gitUser
53173
- };
53174
- }
53175
- function getGitContext(cwd2) {
53176
- if (!runGit3(["rev-parse", "--git-dir"], cwd2)) {
53177
- return null;
53178
- }
53179
- const branch = runGit3(["branch", "--show-current"], cwd2);
53180
- const branchList = runGit3([
53181
- "branch",
53182
- "--sort=-committerdate",
53183
- "--format=%(refname:short)",
53184
- "--no-color"
53185
- ], cwd2);
53186
- const recentBranches = branchList ? branchList.split(`
53187
- `).map((b) => b.trim()).filter((b) => b.length > 0 && b !== branch).slice(0, 10) : [];
53188
- return { branch, recent_branches: recentBranches };
53189
- }
53190
- var init_gitContext = () => {};
53191
-
53192
53487
  // src/cli/helpers/memoryReminder.ts
53193
53488
  function isValidStepCount(value) {
53194
53489
  return typeof value === "number" && Number.isFinite(value) && Number.isInteger(value) && value > 0;
@@ -64366,7 +64661,7 @@ var MemoryApplyPatch_default = "Apply a codex-style patch to memory files in `$M
64366
64661
  var init_MemoryApplyPatch = () => {};
64367
64662
 
64368
64663
  // src/tools/descriptions/MessageChannel.md
64369
- 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";
64370
64665
  var init_MessageChannel = () => {};
64371
64666
 
64372
64667
  // src/tools/descriptions/MultiEdit.md
@@ -74509,7 +74804,7 @@ class Pattern {
74509
74804
  #isUNC;
74510
74805
  #isAbsolute;
74511
74806
  #followGlobstar = true;
74512
- constructor(patternList, globList, index, platform4) {
74807
+ constructor(patternList, globList, index, platform5) {
74513
74808
  if (!isPatternList(patternList)) {
74514
74809
  throw new TypeError("empty pattern list");
74515
74810
  }
@@ -74526,7 +74821,7 @@ class Pattern {
74526
74821
  this.#patternList = patternList;
74527
74822
  this.#globList = globList;
74528
74823
  this.#index = index;
74529
- this.#platform = platform4;
74824
+ this.#platform = platform5;
74530
74825
  if (this.#index === 0) {
74531
74826
  if (this.isUNC()) {
74532
74827
  const [p0, p1, p2, p3, ...prest] = this.#patternList;
@@ -74623,12 +74918,12 @@ class Ignore {
74623
74918
  absoluteChildren;
74624
74919
  platform;
74625
74920
  mmopts;
74626
- constructor(ignored, { nobrace, nocase, noext, noglobstar, platform: platform4 = defaultPlatform2 }) {
74921
+ constructor(ignored, { nobrace, nocase, noext, noglobstar, platform: platform5 = defaultPlatform2 }) {
74627
74922
  this.relative = [];
74628
74923
  this.absolute = [];
74629
74924
  this.relativeChildren = [];
74630
74925
  this.absoluteChildren = [];
74631
- this.platform = platform4;
74926
+ this.platform = platform5;
74632
74927
  this.mmopts = {
74633
74928
  dot: true,
74634
74929
  nobrace,
@@ -74636,7 +74931,7 @@ class Ignore {
74636
74931
  noext,
74637
74932
  noglobstar,
74638
74933
  optimizationLevel: 2,
74639
- platform: platform4,
74934
+ platform: platform5,
74640
74935
  nocomment: true,
74641
74936
  nonegate: true
74642
74937
  };
@@ -76721,7 +77016,7 @@ function resolveSubagentLauncher(cliArgs, options = {}) {
76721
77016
  const env4 = options.env ?? process.env;
76722
77017
  const argv = options.argv ?? process.argv;
76723
77018
  const execPath = options.execPath ?? process.execPath;
76724
- const platform4 = options.platform ?? process.platform;
77019
+ const platform5 = options.platform ?? process.platform;
76725
77020
  const invocation = resolveLettaInvocation(env4, argv, execPath);
76726
77021
  if (invocation) {
76727
77022
  return {
@@ -76736,7 +77031,7 @@ function resolveSubagentLauncher(cliArgs, options = {}) {
76736
77031
  args: [currentScript, ...cliArgs]
76737
77032
  };
76738
77033
  }
76739
- if (currentScript.endsWith(".js") && platform4 === "win32") {
77034
+ if (currentScript.endsWith(".js") && platform5 === "win32") {
76740
77035
  return {
76741
77036
  command: execPath,
76742
77037
  args: [currentScript, ...cliArgs]
@@ -87302,130 +87597,6 @@ var init_agentInfo = __esm(async () => {
87302
87597
  await init_settings_manager();
87303
87598
  });
87304
87599
 
87305
- // src/cli/helpers/sessionContext.ts
87306
- import { platform as platform4 } from "node:os";
87307
- function getLocalTime() {
87308
- const now = new Date;
87309
- return now.toLocaleString(undefined, {
87310
- weekday: "long",
87311
- year: "numeric",
87312
- month: "long",
87313
- day: "numeric",
87314
- hour: "2-digit",
87315
- minute: "2-digit",
87316
- timeZoneName: "short"
87317
- });
87318
- }
87319
- function getDeviceType() {
87320
- const p = platform4();
87321
- switch (p) {
87322
- case "darwin":
87323
- return "macOS";
87324
- case "win32":
87325
- return "Windows";
87326
- case "linux":
87327
- return "Linux";
87328
- default:
87329
- return p;
87330
- }
87331
- }
87332
- function getGitInfo(cwd2) {
87333
- const git = gatherGitContextSnapshot({
87334
- cwd: cwd2,
87335
- recentCommitLimit: 3,
87336
- recentCommitFormat: "%h %s (%an)",
87337
- statusLineLimit: 20
87338
- });
87339
- if (!git.isGitRepo) {
87340
- return { isGitRepo: false };
87341
- }
87342
- return {
87343
- isGitRepo: true,
87344
- branch: git.branch ?? "(unknown)",
87345
- recentCommits: git.recentCommits ?? "(failed to get commits)",
87346
- status: git.status || "(clean working tree)",
87347
- gitUser: git.gitUser ?? "(not configured)"
87348
- };
87349
- }
87350
- function getIntroText(source, reason) {
87351
- if (reason === "cwd_changed") {
87352
- return "The working directory for this conversation has changed. Updated environment context follows.";
87353
- }
87354
- switch (source) {
87355
- case "listen":
87356
- return "This conversation is now connected to a Letta Code execution environment.";
87357
- case "headless":
87358
- return "The user has just initiated a new connection via the Letta Code headless client.";
87359
- default:
87360
- return "The user has just initiated a new connection via the [Letta Code CLI client](https://docs.letta.com/letta-code/index.md).";
87361
- }
87362
- }
87363
- function buildSessionContext(options) {
87364
- try {
87365
- const cwd2 = options?.cwd ?? process.cwd();
87366
- const source = options?.source ?? "interactive-cli";
87367
- const reason = options?.reason ?? "initial_attach";
87368
- let version = "unknown";
87369
- try {
87370
- version = getVersion();
87371
- } catch {}
87372
- let deviceType = "unknown";
87373
- try {
87374
- deviceType = getDeviceType();
87375
- } catch {}
87376
- let localTime = "unknown";
87377
- try {
87378
- localTime = getLocalTime();
87379
- } catch {}
87380
- const gitInfo = getGitInfo(cwd2);
87381
- let context3 = `${SYSTEM_REMINDER_OPEN}
87382
- This is an automated message providing context about the user's environment.
87383
- ${getIntroText(source, reason)}
87384
-
87385
- ## Device Information
87386
- - **Local time**: ${localTime}
87387
- - **Device type**: ${deviceType}
87388
- - **Letta Code version**: ${version}
87389
- - **Current working directory**: ${cwd2}
87390
- `;
87391
- if (gitInfo.isGitRepo) {
87392
- context3 += `- **Git repository**: Yes (branch: ${gitInfo.branch})
87393
- - **Git user**: ${gitInfo.gitUser}
87394
-
87395
- ### Recent Commits
87396
- \`\`\`
87397
- ${gitInfo.recentCommits}
87398
- \`\`\`
87399
-
87400
- ### Git Status
87401
- \`\`\`
87402
- ${gitInfo.status}
87403
- \`\`\`
87404
- `;
87405
- } else {
87406
- context3 += `- **Git repository**: No
87407
- `;
87408
- }
87409
- if (platform4() === "win32") {
87410
- context3 += `
87411
- ## Windows Shell Notes
87412
- - The Bash tool uses PowerShell or cmd.exe on Windows
87413
- - HEREDOC syntax (e.g., \`$(cat <<'EOF'...EOF)\`) does NOT work on Windows
87414
- - For multiline strings (git commits, PR bodies), use simple quoted strings instead
87415
- `;
87416
- }
87417
- context3 += SYSTEM_REMINDER_CLOSE;
87418
- return context3;
87419
- } catch {
87420
- return "";
87421
- }
87422
- }
87423
- var init_sessionContext = __esm(() => {
87424
- init_constants();
87425
- init_version();
87426
- init_gitContext();
87427
- });
87428
-
87429
87600
  // src/reminders/catalog.ts
87430
87601
  var SHARED_REMINDER_CATALOG, SHARED_REMINDER_IDS, SHARED_REMINDER_BY_ID;
87431
87602
  var init_catalog = __esm(() => {
@@ -116503,7 +116674,7 @@ function xml(hljs) {
116503
116674
  ]
116504
116675
  };
116505
116676
  }
116506
- var init_xml = () => {};
116677
+ var init_xml2 = () => {};
116507
116678
 
116508
116679
  // node_modules/highlight.js/es/languages/yaml.js
116509
116680
  function yaml(hljs) {
@@ -116723,7 +116894,7 @@ var init_common = __esm(() => {
116723
116894
  init_typescript2();
116724
116895
  init_vbnet();
116725
116896
  init_wasm();
116726
- init_xml();
116897
+ init_xml2();
116727
116898
  init_yaml();
116728
116899
  grammars = {
116729
116900
  arduino,
@@ -165814,4 +165985,4 @@ Error during initialization: ${message}`);
165814
165985
  }
165815
165986
  main();
165816
165987
 
165817
- //# debugId=25D3963E52DABC3164756E2164756E21
165988
+ //# debugId=177C3FD8089A6F3E64756E2164756E21
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@letta-ai/letta-code",
3
- "version": "0.22.1",
3
+ "version": "0.22.2",
4
4
  "description": "Letta Code is a CLI tool for interacting with stateful Letta agents from the terminal.",
5
5
  "type": "module",
6
6
  "bin": {