@letta-ai/letta-code 0.30.32 → 0.31.0

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 (61) hide show
  1. package/dist/agent-presets-agent-presets.js +11 -0
  2. package/dist/agent-presets-agent-presets.js.map +9 -0
  3. package/dist/agent-presets-personality-asset-content.js +42 -0
  4. package/dist/agent-presets-personality-asset-content.js.map +10 -0
  5. package/dist/agent-presets.js +17 -2
  6. package/dist/agent-presets.js.map +3 -3
  7. package/dist/channels-public.js +16 -1
  8. package/dist/channels-public.js.map +5 -4
  9. package/dist/gateway-core.js +5 -4
  10. package/dist/gateway-core.js.map +3 -3
  11. package/dist/mcp-client.js +2 -2
  12. package/dist/mcp-client.js.map +1 -1
  13. package/dist/types/agent/create-agent-request.d.ts +3 -0
  14. package/dist/types/agent/create-agent-request.d.ts.map +1 -1
  15. package/dist/types/agent/model.d.ts.map +1 -1
  16. package/dist/types/agent/personality-asset-content.d.ts +3 -0
  17. package/dist/types/agent/personality-asset-content.d.ts.map +1 -0
  18. package/dist/types/agent/skills.d.ts.map +1 -1
  19. package/dist/types/channels/gateway-core.d.ts.map +1 -1
  20. package/dist/types/channels/route-thread-key.d.ts +20 -0
  21. package/dist/types/channels/route-thread-key.d.ts.map +1 -0
  22. package/dist/types/channels-public.d.ts +1 -0
  23. package/dist/types/channels-public.d.ts.map +1 -1
  24. package/dist/types/tools/impl/skill.d.ts.map +1 -1
  25. package/dist/types/types/protocol_v2.d.ts +3 -144
  26. package/dist/types/types/protocol_v2.d.ts.map +1 -1
  27. package/dist/types/types/schedule-protocol.d.ts +151 -1
  28. package/dist/types/types/schedule-protocol.d.ts.map +1 -1
  29. package/dist/types/utils/frontmatter.d.ts.map +1 -1
  30. package/dist/types/websocket/listener/cwd-change.d.ts.map +1 -1
  31. package/letta.js +238 -86
  32. package/package.json +4 -2
  33. package/scripts/builtin-skills-watch/agent-watch.test.ts +104 -0
  34. package/scripts/builtin-skills-watch/agent-watch.ts +337 -0
  35. package/scripts/builtin-skills-watch/aggregate-results.test.ts +79 -0
  36. package/scripts/builtin-skills-watch/aggregate-results.ts +326 -0
  37. package/scripts/builtin-skills-watch/analysis.test.ts +70 -0
  38. package/scripts/builtin-skills-watch/analysis.ts +228 -0
  39. package/scripts/builtin-skills-watch/evidence.test.ts +114 -0
  40. package/scripts/builtin-skills-watch/evidence.ts +145 -0
  41. package/scripts/builtin-skills-watch/github.ts +161 -0
  42. package/scripts/builtin-skills-watch/tracker.test.ts +249 -0
  43. package/scripts/builtin-skills-watch/tracker.ts +506 -0
  44. package/scripts/builtin-skills-watch/update-tracker.test.ts +234 -0
  45. package/scripts/builtin-skills-watch/update-tracker.ts +508 -0
  46. package/scripts/claude-watch/release-source.test.ts +16 -4
  47. package/scripts/claude-watch/release-source.ts +41 -2
  48. package/scripts/codex-watch/agent-watch.ts +8 -5
  49. package/scripts/codex-watch/release-analysis.test.ts +18 -16
  50. package/scripts/codex-watch/release-analysis.ts +31 -9
  51. package/scripts/codex-watch/tracker.test.ts +5 -5
  52. package/scripts/codex-watch/tracker.ts +7 -8
  53. package/scripts/pi-ai-watch/agent-watch.ts +9 -14
  54. package/scripts/pi-ai-watch/release-analysis.test.ts +24 -18
  55. package/scripts/pi-ai-watch/release-analysis.ts +85 -45
  56. package/scripts/pi-ai-watch/tracker.test.ts +25 -18
  57. package/scripts/pi-ai-watch/tracker.ts +7 -19
  58. package/scripts/pi-ai-watch/update-tracker.ts +2 -2
  59. package/scripts/source-file-size-baseline.json +2 -2
  60. package/skills/dispatching-coding-agents/SKILL.md +16 -7
  61. package/skills/syncing-memory-filesystem/SKILL.md +118 -226
package/letta.js CHANGED
@@ -5509,7 +5509,7 @@ var package_default;
5509
5509
  var init_package = __esm(() => {
5510
5510
  package_default = {
5511
5511
  name: "@letta-ai/letta-code",
5512
- version: "0.30.32",
5512
+ version: "0.31.0",
5513
5513
  description: "Letta Code is a CLI tool for interacting with stateful Letta agents from the terminal.",
5514
5514
  type: "module",
5515
5515
  packageManager: "bun@1.3.10",
@@ -5546,7 +5546,9 @@ var init_package = __esm(() => {
5546
5546
  "dist/channels-telegram.js",
5547
5547
  "dist/channels-telegram.js.map",
5548
5548
  "dist/types",
5549
- "docs"
5549
+ "docs",
5550
+ "dist/agent-presets-*.js",
5551
+ "dist/agent-presets-*.js.map"
5550
5552
  ],
5551
5553
  exports: {
5552
5554
  ".": "./letta.js",
@@ -79793,7 +79795,7 @@ var init_pi_provider_registry = __esm(() => {
79793
79795
  headers: () => ({
79794
79796
  "HTTP-Referer": "https://letta.com",
79795
79797
  "X-OpenRouter-Title": "Letta Code",
79796
- "X-OpenRouter-Categories": "cli-agent,personal-agent"
79798
+ "X-OpenRouter-Categories": "cloud-agent,personal-agent"
79797
79799
  })
79798
79800
  },
79799
79801
  zai: {
@@ -81320,14 +81322,15 @@ function getReasoningTierOptionsForHandle(modelHandle, contextWindow, reasoningC
81320
81322
  if (providerOptions.length > 0)
81321
81323
  return providerOptions;
81322
81324
  const byEffort = new Map;
81323
- const registryHandle = normalizeModelHandleForRegistry(modelHandle) ?? modelHandle;
81325
+ const normalizedHandle = normalizeModelHandleForRegistry(modelHandle) ?? modelHandle;
81326
+ const catalogHandle = [...new Set([modelHandle, normalizedHandle])].find((candidate) => models2.some((model) => model.handle === candidate)) ?? normalizedHandle;
81324
81327
  const effectiveContextWindow = contextWindow ?? (() => {
81325
- const contextWindows = models2.filter((model) => model.handle === registryHandle).map((model) => model.updateArgs?.context_window).filter((value) => typeof value === "number");
81328
+ const contextWindows = models2.filter((model) => model.handle === catalogHandle).map((model) => model.updateArgs?.context_window).filter((value) => typeof value === "number");
81326
81329
  const uniqueContextWindows = [...new Set(contextWindows)];
81327
81330
  return uniqueContextWindows.length > 1 ? Math.min(...uniqueContextWindows) : undefined;
81328
81331
  })();
81329
81332
  for (const model of models2) {
81330
- if (model.handle !== registryHandle)
81333
+ if (model.handle !== catalogHandle)
81331
81334
  continue;
81332
81335
  if (effectiveContextWindow !== undefined) {
81333
81336
  const mCtx = model.updateArgs?.context_window;
@@ -81341,10 +81344,10 @@ function getReasoningTierOptionsForHandle(modelHandle, contextWindow, reasoningC
81341
81344
  byEffort.set(effort, model.id);
81342
81345
  }
81343
81346
  }
81344
- if (byEffort.size === 0 && isLocalModelHandle(registryHandle)) {
81347
+ if (byEffort.size === 0 && isLocalModelHandle(catalogHandle)) {
81345
81348
  return LOCAL_REASONING_EFFORT_ORDER.map((effort) => ({
81346
81349
  effort,
81347
- modelId: registryHandle
81350
+ modelId: catalogHandle
81348
81351
  }));
81349
81352
  }
81350
81353
  return REASONING_EFFORT_ORDER.flatMap((effort) => {
@@ -81611,9 +81614,13 @@ function parseFrontmatter(content) {
81611
81614
  currentArray = [];
81612
81615
  };
81613
81616
  for (const line of lines) {
81614
- if (line.trim().startsWith("-") && currentKey) {
81615
- const value = line.trim().slice(1).trim();
81616
- currentArray.push(value);
81617
+ const trimmedLine = line.trim();
81618
+ const indentation = line.length - line.trimStart().length;
81619
+ if (indentation > 0) {
81620
+ if (indentation <= 2 && trimmedLine.startsWith("-") && currentKey) {
81621
+ const value = trimmedLine.slice(1).trim();
81622
+ currentArray.push(value);
81623
+ }
81617
81624
  continue;
81618
81625
  }
81619
81626
  savePendingKey();
@@ -97113,15 +97120,19 @@ async function switchConversationWorkingDirectory(params) {
97113
97120
  const { runtime, workingDirectory } = params;
97114
97121
  const agentId = normalizeCwdAgentId(params.agentId);
97115
97122
  const conversationId = normalizeConversationId(params.conversationId);
97123
+ const currentWorkingDirectory = getConversationWorkingDirectory(runtime, agentId, conversationId);
97124
+ const workingDirectoryChanged = canonicalizeRoot(currentWorkingDirectory) !== canonicalizeRoot(workingDirectory);
97116
97125
  await loadSettingsForWorkingDirectory(workingDirectory);
97117
- setConversationWorkingDirectory(runtime, agentId, conversationId, workingDirectory);
97126
+ if (workingDirectoryChanged) {
97127
+ setConversationWorkingDirectory(runtime, agentId, conversationId, workingDirectory);
97128
+ }
97118
97129
  if (params.updateCurrentRuntimeContext !== false) {
97119
97130
  updateRuntimeContext({ workingDirectory });
97120
97131
  }
97121
97132
  const conversationRuntime = getConversationRuntime(runtime, agentId, conversationId);
97122
97133
  const scopeKey = getWorkingDirectoryScopeKey(agentId, conversationId);
97123
97134
  const reminderState = conversationRuntime?.reminderState ?? runtime.reminderStateByConversation.get(scopeKey);
97124
- if (reminderState) {
97135
+ if (workingDirectoryChanged && reminderState) {
97125
97136
  reminderState.hasSentSessionContext = false;
97126
97137
  reminderState.pendingSessionContextReason = "cwd_changed";
97127
97138
  }
@@ -97134,6 +97145,7 @@ async function switchConversationWorkingDirectory(params) {
97134
97145
  }
97135
97146
  }
97136
97147
  var init_cwd_change = __esm(() => {
97148
+ init_sandbox_policy();
97137
97149
  init_runtime_context();
97138
97150
  init_settings_manager();
97139
97151
  init_connection();
@@ -111766,8 +111778,9 @@ async function parseSkillFile(filePath, rootPath, source) {
111766
111778
  const relativePath = filePath.slice(normalizedRoot.length + 1);
111767
111779
  const dirPath = relativePath.slice(0, -"/SKILL.MD".length);
111768
111780
  const defaultId = dirPath || "root";
111769
- const id2 = (typeof frontmatter.id === "string" ? frontmatter.id : null) || defaultId;
111770
- const name = (typeof frontmatter.name === "string" ? frontmatter.name : null) || (typeof frontmatter.title === "string" ? frontmatter.title : null) || (id2.split("/").pop() ?? "").replace(/-/g, " ").replace(/\b\w/g, (l) => l.toUpperCase());
111781
+ const frontmatterName = getFrontmatterString(frontmatter, "name");
111782
+ const id2 = getFrontmatterString(frontmatter, "id") || frontmatterName || defaultId;
111783
+ const name = frontmatterName || (typeof frontmatter.title === "string" ? frontmatter.title : null) || (id2.split("/").pop() ?? "").replace(/-/g, " ").replace(/\b\w/g, (l) => l.toUpperCase());
111771
111784
  let description = getFrontmatterString(frontmatter, "description") ?? null;
111772
111785
  if (!description) {
111773
111786
  const firstParagraph = body.trim().split(`
@@ -111979,6 +111992,22 @@ __export(exports_skill, {
111979
111992
  import { existsSync as existsSync19, readdirSync as readdirSync7 } from "node:fs";
111980
111993
  import { readFile as readFile12 } from "node:fs/promises";
111981
111994
  import { dirname as dirname16, join as join25 } from "node:path";
111995
+ async function readSkillFromRoot(skillsRoot, skillId) {
111996
+ const discovery = await discoverSkills(skillsRoot, undefined, {
111997
+ sources: ["project"],
111998
+ skipBundled: true
111999
+ });
112000
+ const discoveredSkill = discovery.skills.find((candidate) => candidate.id === skillId);
112001
+ if (!discoveredSkill) {
112002
+ return null;
112003
+ }
112004
+ try {
112005
+ const content = await readFile12(discoveredSkill.path, "utf-8");
112006
+ return { content, path: discoveredSkill.path };
112007
+ } catch {
112008
+ return null;
112009
+ }
112010
+ }
111982
112011
  function getMemorySkillsDirs(agentId) {
111983
112012
  const dirs = new Set;
111984
112013
  const scopedMemoryDir = resolveScopedMemoryDir({ agentId });
@@ -112031,25 +112060,22 @@ async function readSkillContent(skillId, skillsDir, agentId, options = {}) {
112031
112060
  skillsDir
112032
112061
  ]);
112033
112062
  for (const projectSkillsDir of projectSkillsDirs) {
112034
- const projectSkillPath = join25(projectSkillsDir, skillId, "SKILL.md");
112035
- try {
112036
- const content = await readFile12(projectSkillPath, "utf-8");
112037
- return { content, path: projectSkillPath };
112038
- } catch {}
112063
+ const result = await readSkillFromRoot(projectSkillsDir, skillId);
112064
+ if (result) {
112065
+ return result;
112066
+ }
112039
112067
  }
112040
112068
  if (agentId) {
112041
- const agentSkillPath = join25(getAgentSkillsDir(agentId), skillId, "SKILL.md");
112042
- try {
112043
- const content = await readFile12(agentSkillPath, "utf-8");
112044
- return { content, path: agentSkillPath };
112045
- } catch {}
112069
+ const result = await readSkillFromRoot(getAgentSkillsDir(agentId), skillId);
112070
+ if (result) {
112071
+ return result;
112072
+ }
112046
112073
  }
112047
112074
  for (const memorySkillsDir of getMemorySkillsDirs(agentId)) {
112048
- const memorySkillPath = join25(memorySkillsDir, skillId, "SKILL.md");
112049
- try {
112050
- const content = await readFile12(memorySkillPath, "utf-8");
112051
- return { content, path: memorySkillPath };
112052
- } catch {}
112075
+ const result = await readSkillFromRoot(memorySkillsDir, skillId);
112076
+ if (result) {
112077
+ return result;
112078
+ }
112053
112079
  }
112054
112080
  const sharedMemoryContext = await resolveSharedMemorySkillsContext({
112055
112081
  agentId,
@@ -112064,11 +112090,10 @@ async function readSkillContent(skillId, skillsDir, agentId, options = {}) {
112064
112090
  return { content, path: sharedSkill.path };
112065
112091
  } catch {}
112066
112092
  }
112067
- const globalSkillPath = join25(GLOBAL_SKILLS_DIR, skillId, "SKILL.md");
112068
- try {
112069
- const content = await readFile12(globalSkillPath, "utf-8");
112070
- return { content, path: globalSkillPath };
112071
- } catch {}
112093
+ const globalResult = await readSkillFromRoot(GLOBAL_SKILLS_DIR, skillId);
112094
+ if (globalResult) {
112095
+ return globalResult;
112096
+ }
112072
112097
  const bundledSkills = await getBundledSkills();
112073
112098
  const bundledSkill = bundledSkills.find((s2) => s2.id === skillId);
112074
112099
  if (bundledSkill?.path && isSkillAvailableForAgent(bundledSkill, agentId)) {
@@ -192293,6 +192318,13 @@ var init_plugin_registry = __esm(() => {
192293
192318
  loadedUserPlugins = new Map;
192294
192319
  });
192295
192320
 
192321
+ // src/channels/route-thread-key.ts
192322
+ function resolveChannelRouteThreadKey(threadId) {
192323
+ const trimmed = threadId?.trim();
192324
+ return trimmed && trimmed.length > 0 ? trimmed : CHANNEL_ROOT_THREAD_KEY;
192325
+ }
192326
+ var CHANNEL_ROOT_THREAD_KEY = "__root__";
192327
+
192296
192328
  // src/channels/routing.ts
192297
192329
  var exports_routing = {};
192298
192330
  __export(exports_routing, {
@@ -192327,12 +192359,8 @@ function notifyChannelRoutesChanged(channelId) {
192327
192359
  for (const listener of routeChangeListeners)
192328
192360
  listener(channelId);
192329
192361
  }
192330
- function normalizeThreadId(threadId) {
192331
- const trimmed = threadId?.trim();
192332
- return trimmed && trimmed.length > 0 ? trimmed : "__root__";
192333
- }
192334
192362
  function routeKey(channel, chatId, accountId, threadId) {
192335
- return `${channel}:${normalizeAccountId3(accountId)}:${chatId}:${normalizeThreadId(threadId)}`;
192363
+ return `${channel}:${normalizeAccountId3(accountId)}:${chatId}:${resolveChannelRouteThreadKey(threadId)}`;
192336
192364
  }
192337
192365
  function loadRoutes(channelId) {
192338
192366
  if (loadRoutesOverride) {
@@ -205581,9 +205609,9 @@ function addTask(input) {
205581
205609
  const data = readCronFile();
205582
205610
  const agentId = input.agent_id;
205583
205611
  const conversationId = input.conversation_id ?? "new";
205584
- const activeCount = data.tasks.filter((t2) => t2.agent_id === agentId && t2.status === "active").length;
205585
- if (activeCount >= MAX_ACTIVE_TASKS_PER_AGENT) {
205586
- throw new Error(`Agent ${agentId} has ${activeCount} active tasks (max ${MAX_ACTIVE_TASKS_PER_AGENT}). Delete some before adding more.`);
205612
+ const scheduledCount = data.tasks.filter((task3) => task3.agent_id === agentId && !TERMINAL_TASK_STATUSES.has(task3.status)).length;
205613
+ if (scheduledCount >= MAX_ACTIVE_TASKS_PER_AGENT) {
205614
+ throw new Error(`Agent ${agentId} has ${scheduledCount} active or paused tasks (max ${MAX_ACTIVE_TASKS_PER_AGENT}). Delete some before adding more.`);
205587
205615
  }
205588
205616
  assertValidCronForPersistence(input.cron);
205589
205617
  const now2 = new Date;
@@ -205718,13 +205746,97 @@ function updateTask2(taskId, updater) {
205718
205746
  return { ...task2 };
205719
205747
  });
205720
205748
  }
205749
+ function recordTaskQueued(taskId, trigger, queuedAt) {
205750
+ return updateTask2(taskId, (task2) => {
205751
+ const queuedAtIso = queuedAt.toISOString();
205752
+ if (trigger === "automatic" && !task2.recurring) {
205753
+ task2.status = "fired";
205754
+ task2.fired_at = queuedAtIso;
205755
+ }
205756
+ task2.last_fired_at = queuedAtIso;
205757
+ task2.fire_count += 1;
205758
+ task2.last_run_at = queuedAtIso;
205759
+ task2.last_run_outcome = "queued";
205760
+ task2.last_run_reason = task2.recurring ? "scheduled_time_matched" : "one_off_due";
205761
+ task2.last_run_error = null;
205762
+ });
205763
+ }
205764
+ function pauseTask(taskId) {
205765
+ return withLock(() => {
205766
+ const data = readCronFile();
205767
+ const task2 = data.tasks.find((candidate) => candidate.id === taskId);
205768
+ if (!task2) {
205769
+ return { success: false, found: false, error: "Schedule not found" };
205770
+ }
205771
+ if (task2.status === "paused") {
205772
+ return { success: true, found: true, task: { ...task2 } };
205773
+ }
205774
+ if (task2.status !== "active") {
205775
+ return {
205776
+ success: false,
205777
+ found: true,
205778
+ task: { ...task2 },
205779
+ error: "Completed schedules cannot be paused"
205780
+ };
205781
+ }
205782
+ task2.status = "paused";
205783
+ writeCronFile(data);
205784
+ return { success: true, found: true, task: { ...task2 } };
205785
+ });
205786
+ }
205787
+ function resumeTask(taskId, scheduledFor, now2 = new Date) {
205788
+ return withLock(() => {
205789
+ const data = readCronFile();
205790
+ const task2 = data.tasks.find((candidate) => candidate.id === taskId);
205791
+ if (!task2) {
205792
+ return { success: false, found: false, error: "Schedule not found" };
205793
+ }
205794
+ if (task2.status === "active") {
205795
+ return { success: true, found: true, task: { ...task2 } };
205796
+ }
205797
+ if (task2.status !== "paused") {
205798
+ return {
205799
+ success: false,
205800
+ found: true,
205801
+ task: { ...task2 },
205802
+ error: "Completed schedules cannot be resumed"
205803
+ };
205804
+ }
205805
+ if (!task2.recurring) {
205806
+ const resumeAt = scheduledFor ?? (task2.scheduled_for ? new Date(task2.scheduled_for) : null);
205807
+ if (!resumeAt || Number.isNaN(resumeAt.getTime())) {
205808
+ return {
205809
+ success: false,
205810
+ found: true,
205811
+ task: { ...task2 },
205812
+ error: "A one-off schedule requires a valid scheduled_for timestamp"
205813
+ };
205814
+ }
205815
+ if (resumeAt.getTime() <= now2.getTime()) {
205816
+ return {
205817
+ success: false,
205818
+ found: true,
205819
+ task: { ...task2 },
205820
+ error: "An overdue one-off schedule requires a new future scheduled_for timestamp"
205821
+ };
205822
+ }
205823
+ if (scheduledFor) {
205824
+ task2.scheduled_for = scheduledFor.toISOString();
205825
+ task2.jitter_offset_ms = computeJitter(task2.id, task2.cron, false, scheduledFor, new Date(task2.created_at));
205826
+ }
205827
+ }
205828
+ task2.status = "active";
205829
+ writeCronFile(data);
205830
+ return { success: true, found: true, task: { ...task2 } };
205831
+ });
205832
+ }
205721
205833
  function garbageCollect() {
205722
205834
  return withLock(() => {
205723
205835
  const data = readCronFile();
205724
205836
  const cutoff = Date.now() - GC_AGE_MS;
205725
205837
  const before2 = data.tasks.length;
205726
205838
  data.tasks = data.tasks.filter((t2) => {
205727
- if (t2.status === "active")
205839
+ if (!TERMINAL_TASK_STATUSES.has(t2.status))
205728
205840
  return true;
205729
205841
  const createdAt = new Date(t2.created_at).getTime();
205730
205842
  const terminalAt = Math.max(t2.last_fired_at ? new Date(t2.last_fired_at).getTime() : 0, t2.fired_at ? new Date(t2.fired_at).getTime() : 0, t2.missed_at ? new Date(t2.missed_at).getTime() : 0, createdAt);
@@ -205748,10 +205860,15 @@ function getCronFileMtime() {
205748
205860
  return 0;
205749
205861
  }
205750
205862
  }
205751
- var CRON_FILE_NAME = "crons.json", LOCK_DIR_NAME = "crons.lock", LOCK_TOKEN_FILE = "owner.json", LOCK_TIMEOUT_MS = 5000, LOCK_RETRY_MS = 50, LOCK_STALE_AGE_MS = 30000, MAX_ACTIVE_TASKS_PER_AGENT = 50, TASK_ID_BYTES = 4, GC_AGE_MS, readProcessIdentityOverride = null;
205863
+ var CRON_FILE_NAME = "crons.json", LOCK_DIR_NAME = "crons.lock", LOCK_TOKEN_FILE = "owner.json", LOCK_TIMEOUT_MS = 5000, LOCK_RETRY_MS = 50, LOCK_STALE_AGE_MS = 30000, MAX_ACTIVE_TASKS_PER_AGENT = 50, TASK_ID_BYTES = 4, GC_AGE_MS, readProcessIdentityOverride = null, TERMINAL_TASK_STATUSES;
205752
205864
  var init_cron_file = __esm(() => {
205753
205865
  init_parse_interval();
205754
205866
  GC_AGE_MS = 24 * 60 * 60 * 1000;
205867
+ TERMINAL_TASK_STATUSES = new Set([
205868
+ "fired",
205869
+ "missed",
205870
+ "cancelled"
205871
+ ]);
205755
205872
  });
205756
205873
 
205757
205874
  // src/cron/run-log.ts
@@ -206820,7 +206937,7 @@ function handleInvalidRecurringTask(task2, now2) {
206820
206937
  });
206821
206938
  return true;
206822
206939
  }
206823
- async function fireCronTask(task2, timing2, socket, opts, processQueuedTurn) {
206940
+ async function fireCronTask(task2, timing2, socket, opts, processQueuedTurn, trigger) {
206824
206941
  const listener = getActiveRuntime();
206825
206942
  if (!listener) {
206826
206943
  setLastRunOutcome(task2.id, {
@@ -206870,6 +206987,9 @@ async function fireCronTask(task2, timing2, socket, opts, processQueuedTurn) {
206870
206987
  return false;
206871
206988
  }
206872
206989
  const conversationRuntime = ensureConversationQueueRuntime(listener, rawRuntime);
206990
+ if (trigger === "automatic" && getTask2(task2.id)?.status !== "active") {
206991
+ return false;
206992
+ }
206873
206993
  const text2 = wrapCronPrompt(task2, timing2);
206874
206994
  const queuedItem = conversationRuntime.queueRuntime.enqueue({
206875
206995
  kind: "cron_prompt",
@@ -206898,31 +207018,12 @@ async function fireCronTask(task2, timing2, socket, opts, processQueuedTurn) {
206898
207018
  }
206899
207019
  scheduleQueuePump(conversationRuntime, socket, opts, processQueuedTurn);
206900
207020
  const nowIso = timing2.schedulerNow.toISOString();
206901
- if (task2.recurring) {
206902
- updateTask2(task2.id, (t2) => {
206903
- t2.last_fired_at = nowIso;
206904
- t2.fire_count += 1;
206905
- t2.last_run_at = nowIso;
206906
- t2.last_run_outcome = "queued";
206907
- t2.last_run_reason = "scheduled_time_matched";
206908
- t2.last_run_error = null;
206909
- });
206910
- } else {
206911
- updateTask2(task2.id, (t2) => {
206912
- t2.status = "fired";
206913
- t2.fired_at = nowIso;
206914
- t2.last_fired_at = nowIso;
206915
- t2.fire_count = 1;
206916
- t2.last_run_at = nowIso;
206917
- t2.last_run_outcome = "queued";
206918
- t2.last_run_reason = "one_off_due";
206919
- t2.last_run_error = null;
206920
- });
206921
- }
207021
+ recordTaskQueued(task2.id, trigger, timing2.schedulerNow);
207022
+ const runReason = task2.recurring ? "scheduled_time_matched" : "one_off_due";
206922
207023
  safeAppendCronRunLogForTask(task2, {
206923
207024
  status: "ok",
206924
207025
  outcome: "queued",
206925
- reason: task2.recurring ? "scheduled_time_matched" : "one_off_due",
207026
+ reason: runReason,
206926
207027
  runAtMs: timing2.schedulerNow.getTime(),
206927
207028
  queueItemId: queuedItem.id,
206928
207029
  scheduledFor: task2.scheduled_for,
@@ -206972,12 +207073,12 @@ async function runCronTaskNow(taskId) {
206972
207073
  if (!task2) {
206973
207074
  return { success: false, found: false, error: "Schedule not found" };
206974
207075
  }
206975
- if (task2.status !== "active") {
207076
+ if (task2.status !== "active" && task2.status !== "paused") {
206976
207077
  return {
206977
207078
  success: false,
206978
207079
  found: true,
206979
207080
  task: task2,
206980
- error: "Schedule is not active"
207081
+ error: "Completed schedules cannot run again"
206981
207082
  };
206982
207083
  }
206983
207084
  const ctx = schedulerState ?? listenerFireContext;
@@ -206993,7 +207094,7 @@ async function runCronTaskNow(taskId) {
206993
207094
  const fired = await fireCronTask(task2, {
206994
207095
  intendedOccurrence: getIntendedCronOccurrence(task2, schedulerNow),
206995
207096
  schedulerNow
206996
- }, ctx.socket, ctx.opts, ctx.processQueuedTurn);
207097
+ }, ctx.socket, ctx.opts, ctx.processQueuedTurn, "manual");
206997
207098
  if (!fired) {
206998
207099
  return {
206999
207100
  success: false,
@@ -207045,7 +207146,7 @@ function tick2(state, socket, opts, processQueuedTurn) {
207045
207146
  if (!freshTask || freshTask.status !== "active")
207046
207147
  return;
207047
207148
  const schedulerNow = new Date;
207048
- fireCronTask(freshTask, { intendedOccurrence, schedulerNow }, socket, opts, processQueuedTurn).catch((err) => {
207149
+ fireCronTask(freshTask, { intendedOccurrence, schedulerNow }, socket, opts, processQueuedTurn, "automatic").catch((err) => {
207049
207150
  console.error(`[Cron] Error firing task ${taskId}:`, err);
207050
207151
  setLastRunOutcome(freshTask.id, {
207051
207152
  outcome: "failed",
@@ -216944,6 +217045,26 @@ var init_channel_service_validation = __esm(() => {
216944
217045
  ]);
216945
217046
  });
216946
217047
 
217048
+ // src/websocket/listener/cron-protocol-inbound.ts
217049
+ function isCronTriggerCommand(value) {
217050
+ if (!value || typeof value !== "object")
217051
+ return false;
217052
+ const command = value;
217053
+ return command.type === "cron_trigger" && typeof command.request_id === "string" && typeof command.task_id === "string";
217054
+ }
217055
+ function isCronPauseCommand(value) {
217056
+ if (!value || typeof value !== "object")
217057
+ return false;
217058
+ const command = value;
217059
+ return command.type === "cron_pause" && typeof command.request_id === "string" && typeof command.task_id === "string";
217060
+ }
217061
+ function isCronResumeCommand(value) {
217062
+ if (!value || typeof value !== "object")
217063
+ return false;
217064
+ const command = value;
217065
+ return command.type === "cron_resume" && typeof command.request_id === "string" && typeof command.task_id === "string" && (command.scheduled_for === undefined || typeof command.scheduled_for === "string");
217066
+ }
217067
+
216947
217068
  // src/websocket/listener/cwd-protocol-inbound.ts
216948
217069
  function isGetCwdMapCommand(value) {
216949
217070
  if (!value || typeof value !== "object")
@@ -217468,12 +217589,6 @@ function isCronRunsCommand(value) {
217468
217589
  const c = value;
217469
217590
  return c.type === "cron_runs" && typeof c.request_id === "string" && typeof c.task_id === "string" && (c.limit === undefined || typeof c.limit === "number") && (c.offset === undefined || typeof c.offset === "number") && (c.run_id === undefined || typeof c.run_id === "string");
217470
217591
  }
217471
- function isCronTriggerCommand(value) {
217472
- if (!value || typeof value !== "object")
217473
- return false;
217474
- const c = value;
217475
- return c.type === "cron_trigger" && typeof c.request_id === "string" && typeof c.task_id === "string";
217476
- }
217477
217592
  function isCronUpdateCommand(value) {
217478
217593
  if (!value || typeof value !== "object")
217479
217594
  return false;
@@ -217825,7 +217940,7 @@ function parseServerMessage(data) {
217825
217940
  const teleportCommand = parseTeleportCommand(parsed);
217826
217941
  if (teleportCommand)
217827
217942
  return teleportCommand;
217828
- if (isInputCommand(parsed) || isChangeDeviceStateCommand(parsed) || isAbortMessageCommand(parsed) || isSyncCommand(parsed) || isRuntimeStartCommand(parsed) || isRuntimeExternalToolsUpdateCommand(parsed) || isExternalToolCallResponseCommand(parsed) || isTerminalSpawnCommand(parsed) || isTerminalInputCommand(parsed) || isTerminalResizeCommand(parsed) || isTerminalKillCommand(parsed) || isSearchFilesCommand(parsed) || isGrepInFilesCommand(parsed) || isListInDirectoryCommand(parsed) || isGetTreeCommand(parsed) || isReadFileCommand(parsed) || isWriteFileCommand(parsed) || isWatchFileCommand(parsed) || isUnwatchFileCommand(parsed) || isEditFileCommand(parsed) || isFileOpsCommand(parsed) || isListMemoryCommand(parsed) || isMemoryHistoryCommand(parsed) || isMemoryFileAtRefCommand(parsed) || isMemoryCommitDiffCommand(parsed) || isReadMemoryFileCommand(parsed) || isWriteMemoryFileCommand(parsed) || isDeleteMemoryFileCommand(parsed) || isEnableMemfsCommand(parsed) || isListModelsCommand(parsed) || isListConnectProvidersCommand(parsed) || isConnectProviderCommand(parsed) || isDisconnectProviderCommand(parsed) || isChatGPTUsageReadCommand(parsed) || isUpdateModelCommand(parsed) || isUpdateToolsetCommand(parsed) || isCronListCommand(parsed) || isCronAddCommand(parsed) || isCronGetCommand(parsed) || isCronRunsCommand(parsed) || isCronTriggerCommand(parsed) || isCronUpdateCommand(parsed) || isCronDeleteCommand(parsed) || isCronDeleteAllCommand(parsed) || isSkillEnableCommand(parsed) || isSkillDisableCommand(parsed) || isAppServerInfoCommand(parsed) || isCreateAgentCommand(parsed) || isAgentListCommand(parsed) || isAgentRetrieveCommand(parsed) || isAgentCreateCommand(parsed) || isAgentUpdateCommand(parsed) || isAgentDeleteCommand(parsed) || isConversationListCommand(parsed) || isConversationRetrieveCommand(parsed) || isConversationCreateCommand(parsed) || isConversationUpdateCommand(parsed) || isConversationRecompileCommand(parsed) || isConversationForkCommand(parsed) || isConversationMessagesListCommand(parsed) || isConversationCompactCommand(parsed) || isSetBootWorkingDirectoryCommand(parsed) || isGetCwdMapCommand(parsed) || isGetExperimentsCommand(parsed) || isSetExperimentCommand(parsed) || isGetReflectionSettingsCommand(parsed) || isSetReflectionSettingsCommand(parsed) || isChannelsListCommand(parsed) || isChannelAccountsListCommand(parsed) || isChannelAccountCreateCommand(parsed) || isChannelAccountUpdateCommand(parsed) || isChannelAccountBindCommand(parsed) || isChannelAccountUnbindCommand(parsed) || isChannelAccountDeleteCommand(parsed) || isChannelAccountStartCommand(parsed) || isChannelAccountStopCommand(parsed) || isChannelGetConfigCommand(parsed) || isChannelSetConfigCommand(parsed) || isChannelStartCommand(parsed) || isChannelStopCommand(parsed) || isChannelPairingsListCommand(parsed) || isChannelPairingBindCommand(parsed) || isChannelRoutesListCommand(parsed) || isChannelTargetsListCommand(parsed) || isChannelTargetBindCommand(parsed) || isChannelRouteUpdateCommand(parsed) || isChannelRouteRemoveCommand(parsed) || isExecuteCommandCommand(parsed) || isRemoveQueueItemCommand(parsed) || isSearchBranchesCommand(parsed) || isCheckoutBranchCommand(parsed) || isSecretListCommand(parsed) || isSecretApplyCommand(parsed)) {
217943
+ if (isInputCommand(parsed) || isChangeDeviceStateCommand(parsed) || isAbortMessageCommand(parsed) || isSyncCommand(parsed) || isRuntimeStartCommand(parsed) || isRuntimeExternalToolsUpdateCommand(parsed) || isExternalToolCallResponseCommand(parsed) || isTerminalSpawnCommand(parsed) || isTerminalInputCommand(parsed) || isTerminalResizeCommand(parsed) || isTerminalKillCommand(parsed) || isSearchFilesCommand(parsed) || isGrepInFilesCommand(parsed) || isListInDirectoryCommand(parsed) || isGetTreeCommand(parsed) || isReadFileCommand(parsed) || isWriteFileCommand(parsed) || isWatchFileCommand(parsed) || isUnwatchFileCommand(parsed) || isEditFileCommand(parsed) || isFileOpsCommand(parsed) || isListMemoryCommand(parsed) || isMemoryHistoryCommand(parsed) || isMemoryFileAtRefCommand(parsed) || isMemoryCommitDiffCommand(parsed) || isReadMemoryFileCommand(parsed) || isWriteMemoryFileCommand(parsed) || isDeleteMemoryFileCommand(parsed) || isEnableMemfsCommand(parsed) || isListModelsCommand(parsed) || isListConnectProvidersCommand(parsed) || isConnectProviderCommand(parsed) || isDisconnectProviderCommand(parsed) || isChatGPTUsageReadCommand(parsed) || isUpdateModelCommand(parsed) || isUpdateToolsetCommand(parsed) || isCronListCommand(parsed) || isCronAddCommand(parsed) || isCronGetCommand(parsed) || isCronRunsCommand(parsed) || isCronTriggerCommand(parsed) || isCronPauseCommand(parsed) || isCronResumeCommand(parsed) || isCronUpdateCommand(parsed) || isCronDeleteCommand(parsed) || isCronDeleteAllCommand(parsed) || isSkillEnableCommand(parsed) || isSkillDisableCommand(parsed) || isAppServerInfoCommand(parsed) || isCreateAgentCommand(parsed) || isAgentListCommand(parsed) || isAgentRetrieveCommand(parsed) || isAgentCreateCommand(parsed) || isAgentUpdateCommand(parsed) || isAgentDeleteCommand(parsed) || isConversationListCommand(parsed) || isConversationRetrieveCommand(parsed) || isConversationCreateCommand(parsed) || isConversationUpdateCommand(parsed) || isConversationRecompileCommand(parsed) || isConversationForkCommand(parsed) || isConversationMessagesListCommand(parsed) || isConversationCompactCommand(parsed) || isSetBootWorkingDirectoryCommand(parsed) || isGetCwdMapCommand(parsed) || isGetExperimentsCommand(parsed) || isSetExperimentCommand(parsed) || isGetReflectionSettingsCommand(parsed) || isSetReflectionSettingsCommand(parsed) || isChannelsListCommand(parsed) || isChannelAccountsListCommand(parsed) || isChannelAccountCreateCommand(parsed) || isChannelAccountUpdateCommand(parsed) || isChannelAccountBindCommand(parsed) || isChannelAccountUnbindCommand(parsed) || isChannelAccountDeleteCommand(parsed) || isChannelAccountStartCommand(parsed) || isChannelAccountStopCommand(parsed) || isChannelGetConfigCommand(parsed) || isChannelSetConfigCommand(parsed) || isChannelStartCommand(parsed) || isChannelStopCommand(parsed) || isChannelPairingsListCommand(parsed) || isChannelPairingBindCommand(parsed) || isChannelRoutesListCommand(parsed) || isChannelTargetsListCommand(parsed) || isChannelTargetBindCommand(parsed) || isChannelRouteUpdateCommand(parsed) || isChannelRouteRemoveCommand(parsed) || isExecuteCommandCommand(parsed) || isRemoveQueueItemCommand(parsed) || isSearchBranchesCommand(parsed) || isCheckoutBranchCommand(parsed) || isSecretListCommand(parsed) || isSecretApplyCommand(parsed)) {
217829
217944
  return parsed;
217830
217945
  }
217831
217946
  const invalidInput = getInvalidInputReason(parsed);
@@ -218346,6 +218461,42 @@ async function handleCronCommand(parsed, socket, safeSocketSend) {
218346
218461
  }
218347
218462
  return true;
218348
218463
  }
218464
+ if (parsed.type === "cron_pause" || parsed.type === "cron_resume") {
218465
+ const responseType = parsed.type === "cron_pause" ? "cron_pause_response" : "cron_resume_response";
218466
+ try {
218467
+ let scheduledFor;
218468
+ if (parsed.type === "cron_resume" && parsed.scheduled_for !== undefined) {
218469
+ scheduledFor = new Date(parsed.scheduled_for);
218470
+ if (Number.isNaN(scheduledFor.getTime())) {
218471
+ throw new Error("Invalid scheduled_for timestamp");
218472
+ }
218473
+ }
218474
+ const result2 = parsed.type === "cron_pause" ? pauseTask(parsed.task_id) : resumeTask(parsed.task_id, scheduledFor);
218475
+ safeSocketSend(socket, {
218476
+ type: responseType,
218477
+ request_id: parsed.request_id,
218478
+ success: result2.success,
218479
+ found: result2.found,
218480
+ ...result2.task ? { task: result2.task } : {},
218481
+ ...result2.error ? { error: result2.error } : {}
218482
+ }, "listener_cron_send_failed", "listener_cron_command");
218483
+ if (result2.success && result2.task) {
218484
+ emitCronsUpdated2(socket, safeSocketSend, {
218485
+ agent_id: result2.task.agent_id,
218486
+ conversation_id: result2.task.conversation_id
218487
+ });
218488
+ }
218489
+ } catch (err) {
218490
+ safeSocketSend(socket, {
218491
+ type: responseType,
218492
+ request_id: parsed.request_id,
218493
+ success: false,
218494
+ found: getTask2(parsed.task_id) !== null,
218495
+ error: err instanceof Error ? err.message : `Failed to ${parsed.type === "cron_pause" ? "pause" : "resume"} cron`
218496
+ }, "listener_cron_send_failed", "listener_cron_command");
218497
+ }
218498
+ return true;
218499
+ }
218349
218500
  if (parsed.type === "cron_update") {
218350
218501
  try {
218351
218502
  let scheduledForIso;
@@ -218456,7 +218607,7 @@ async function handleCronCommand(parsed, socket, safeSocketSend) {
218456
218607
  }
218457
218608
  function handleCronProtocolCommand(parsed, context3) {
218458
218609
  const { socket, safeSocketSend, runDetachedListenerTask } = context3;
218459
- if (isCronListCommand(parsed) || isCronAddCommand(parsed) || isCronGetCommand(parsed) || isCronRunsCommand(parsed) || isCronTriggerCommand(parsed) || isCronUpdateCommand(parsed) || isCronDeleteCommand(parsed) || isCronDeleteAllCommand(parsed)) {
218610
+ if (isCronListCommand(parsed) || isCronAddCommand(parsed) || isCronGetCommand(parsed) || isCronRunsCommand(parsed) || isCronTriggerCommand(parsed) || isCronPauseCommand(parsed) || isCronResumeCommand(parsed) || isCronUpdateCommand(parsed) || isCronDeleteCommand(parsed) || isCronDeleteAllCommand(parsed)) {
218460
218611
  runDetachedListenerTask("cron_command", async () => {
218461
218612
  await handleCronCommand(parsed, socket, safeSocketSend);
218462
218613
  });
@@ -421796,7 +421947,9 @@ class ChannelGateway {
421796
421947
  return false;
421797
421948
  }
421798
421949
  this.rememberAcceptedClientMessageId(state, delivery.clientMessageId);
421799
- const queuedEvents = delivery.sources.map((source) => this.enqueueHook(state, () => this.hooks.onLifecycle({ type: "queued", source })));
421950
+ for (const source of delivery.sources) {
421951
+ this.enqueueHook(state, () => this.hooks.onLifecycle({ type: "queued", source }));
421952
+ }
421800
421953
  if (response.disposition === "started") {
421801
421954
  this.activateSources(state, delivery.clientMessageId, delivery.sources);
421802
421955
  state.pendingSourcesByClientMessageId.delete(delivery.clientMessageId);
@@ -421807,7 +421960,6 @@ class ChannelGateway {
421807
421960
  }
421808
421961
  this.reconcileExplicitQueueRemovals(state);
421809
421962
  }
421810
- await Promise.all(queuedEvents);
421811
421963
  return true;
421812
421964
  } catch (error4) {
421813
421965
  state.pendingSourcesByClientMessageId.delete(delivery.clientMessageId);
@@ -421907,7 +422059,7 @@ class ChannelGateway {
421907
422059
  batchId,
421908
422060
  sources: routingSources
421909
422061
  }) ?? null;
421910
- await this.enqueueHook(state, () => this.hooks.onLifecycle({
422062
+ this.enqueueHook(state, () => this.hooks.onLifecycle({
421911
422063
  type: "processing",
421912
422064
  batchId,
421913
422065
  sources: active.lifecycleSources
@@ -442342,7 +442494,7 @@ var init_mcp_client = __esm(() => {
442342
442494
  init_streamableHttp();
442343
442495
  DEFAULT_CLIENT_INFO = {
442344
442496
  name: "letta-code",
442345
- version: "0.30.32"
442497
+ version: "0.31.0"
442346
442498
  };
442347
442499
  });
442348
442500
 
@@ -491587,7 +491739,7 @@ function useConfigurationHandlers(ctx) {
491587
491739
  registryHandle,
491588
491740
  contextWindow: selectedContextWindow,
491589
491741
  reasoningCapabilities
491590
- }) : getReasoningTierOptionsForHandle2(registryHandle, selectedContextWindow, reasoningCapabilities);
491742
+ }) : getReasoningTierOptionsForHandle2(modelHandle, selectedContextWindow, reasoningCapabilities);
491591
491743
  const reasoningTierOptions = baseReasoningTierOptions.map((option2) => {
491592
491744
  const optionModel = models3.find((entry) => entry.id === option2.modelId);
491593
491745
  const serviceTier = modelUpdateArgs?.service_tier;
@@ -509478,4 +509630,4 @@ function registerBunOAuthFlows() {
509478
509630
  registerBunOAuthFlows();
509479
509631
  await init_src5().then(() => exports_src2);
509480
509632
 
509481
- //# debugId=53B96ECAE454C8BC64756E2164756E21
509633
+ //# debugId=987215013A023F6264756E2164756E21
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@letta-ai/letta-code",
3
- "version": "0.30.32",
3
+ "version": "0.31.0",
4
4
  "description": "Letta Code is a CLI tool for interacting with stateful Letta agents from the terminal.",
5
5
  "type": "module",
6
6
  "packageManager": "bun@1.3.10",
@@ -37,7 +37,9 @@
37
37
  "dist/channels-telegram.js",
38
38
  "dist/channels-telegram.js.map",
39
39
  "dist/types",
40
- "docs"
40
+ "docs",
41
+ "dist/agent-presets-*.js",
42
+ "dist/agent-presets-*.js.map"
41
43
  ],
42
44
  "exports": {
43
45
  ".": "./letta.js",