@letta-ai/letta-code 0.11.2-next.4 → 0.12.1

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 +305 -38
  2. package/package.json +1 -1
package/letta.js CHANGED
@@ -3237,7 +3237,7 @@ var package_default;
3237
3237
  var init_package = __esm(() => {
3238
3238
  package_default = {
3239
3239
  name: "@letta-ai/letta-code",
3240
- version: "0.11.2-next.4",
3240
+ version: "0.12.1",
3241
3241
  description: "Letta Code is a CLI tool for interacting with stateful Letta agents from the terminal.",
3242
3242
  type: "module",
3243
3243
  bin: {
@@ -29951,6 +29951,10 @@ var init_WelcomeScreen = __esm(async () => {
29951
29951
  });
29952
29952
 
29953
29953
  // src/permissions/readOnlyShell.ts
29954
+ function isReadOnlySkillScript(scriptPath) {
29955
+ const normalized = scriptPath.replace(/\\/g, "/");
29956
+ return BUNDLED_READ_ONLY_SCRIPTS.some((pattern) => normalized.endsWith(pattern));
29957
+ }
29954
29958
  function isReadOnlyShellCommand(command) {
29955
29959
  if (!command) {
29956
29960
  return false;
@@ -30018,6 +30022,13 @@ function isSafeSegment(segment) {
30018
30022
  if (command === "sort") {
30019
30023
  return !/\s-o\b/.test(segment);
30020
30024
  }
30025
+ if (command === "npx" && tokens[1] === "tsx") {
30026
+ const scriptPath = tokens[2];
30027
+ if (scriptPath && isReadOnlySkillScript(scriptPath)) {
30028
+ return true;
30029
+ }
30030
+ return false;
30031
+ }
30021
30032
  return false;
30022
30033
  }
30023
30034
  return true;
@@ -30050,7 +30061,7 @@ function extractDashCArgument(tokens) {
30050
30061
  }
30051
30062
  return;
30052
30063
  }
30053
- var ALWAYS_SAFE_COMMANDS, SAFE_GIT_SUBCOMMANDS, DANGEROUS_OPERATOR_PATTERN;
30064
+ var ALWAYS_SAFE_COMMANDS, SAFE_GIT_SUBCOMMANDS, BUNDLED_READ_ONLY_SCRIPTS, DANGEROUS_OPERATOR_PATTERN;
30054
30065
  var init_readOnlyShell = __esm(() => {
30055
30066
  ALWAYS_SAFE_COMMANDS = new Set([
30056
30067
  "cat",
@@ -30113,6 +30124,12 @@ var init_readOnlyShell = __esm(() => {
30113
30124
  "tag",
30114
30125
  "remote"
30115
30126
  ]);
30127
+ BUNDLED_READ_ONLY_SCRIPTS = [
30128
+ "/skills/searching-messages/scripts/search-messages.ts",
30129
+ "/skills/searching-messages/scripts/get-messages.ts",
30130
+ "/skills/builtin/searching-messages/scripts/search-messages.ts",
30131
+ "/skills/builtin/searching-messages/scripts/get-messages.ts"
30132
+ ];
30116
30133
  DANGEROUS_OPERATOR_PATTERN = /(>>|>|\$\(|`)/;
30117
30134
  });
30118
30135
 
@@ -31629,6 +31646,46 @@ Remember: You're planning, not implementing. Don't make changes, just create a r
31629
31646
  `;
31630
31647
  var init_plan = () => {};
31631
31648
 
31649
+ // src/agent/subagents/builtin/recall.md
31650
+ var recall_default = `---
31651
+ name: recall
31652
+ description: Search conversation history to recall past discussions, decisions, and context
31653
+ tools: Skill, Bash, Read, BashOutput
31654
+ model: haiku
31655
+ memoryBlocks: human, persona
31656
+ mode: stateless
31657
+ ---
31658
+
31659
+ You are a subagent launched via the Task tool to search conversation history. You run autonomously and return a single final report when done. You CANNOT ask questions mid-execution.
31660
+
31661
+ ## Instructions
31662
+
31663
+ ### Step 1: Load the searching-messages skill
31664
+ \`\`\`
31665
+ Skill({ command: "load", skills: ["searching-messages"] })
31666
+ \`\`\`
31667
+
31668
+ The skill content will appear in your loaded_skills block with script paths and search strategies.
31669
+
31670
+ ### Step 2: Search the parent agent's history
31671
+
31672
+ **CRITICAL - Two rules:**
31673
+
31674
+ 1. **DO NOT use \`conversation_search\`** - That tool only searches YOUR history (empty). You MUST use the Bash scripts from the skill.
31675
+
31676
+ 2. **ALWAYS add \`--agent-id $LETTA_PARENT_AGENT_ID\`** - This searches the parent agent's history. The only exception is \`--all-agents\` searches.
31677
+
31678
+ Follow the strategies documented in the loaded skill.
31679
+
31680
+ ## Output Format
31681
+
31682
+ 1. **Direct answer** - What the user asked about
31683
+ 2. **Key findings** - Relevant quotes or summaries from past conversations
31684
+ 3. **When discussed** - Timestamps of relevant discussions
31685
+ 4. **Outcome/Decision** - What was decided or concluded (if applicable)
31686
+ `;
31687
+ var init_recall = () => {};
31688
+
31632
31689
  // src/agent/subagents/index.ts
31633
31690
  var exports_subagents = {};
31634
31691
  __export(exports_subagents, {
@@ -31793,7 +31850,13 @@ var init_subagents = __esm(() => {
31793
31850
  init_explore();
31794
31851
  init_general_purpose();
31795
31852
  init_plan();
31796
- BUILTIN_SOURCES = [explore_default, general_purpose_default, plan_default];
31853
+ init_recall();
31854
+ BUILTIN_SOURCES = [
31855
+ explore_default,
31856
+ general_purpose_default,
31857
+ plan_default,
31858
+ recall_default
31859
+ ];
31797
31860
  GLOBAL_AGENTS_DIR = join3(process.env.HOME || process.env.USERPROFILE || "~", ".letta/agents");
31798
31861
  VALID_MEMORY_BLOCKS = new Set(MEMORY_BLOCK_LABELS);
31799
31862
  cache3 = {
@@ -43281,11 +43344,16 @@ async function executeSubagent(type, config, model, userPrompt, baseURL, subagen
43281
43344
  try {
43282
43345
  const cliArgs = buildSubagentArgs(type, config, model, userPrompt);
43283
43346
  const lettaCmd = process.env.LETTA_CODE_BIN || "letta";
43347
+ let parentAgentId;
43348
+ try {
43349
+ parentAgentId = getCurrentAgentId();
43350
+ } catch {}
43284
43351
  const proc2 = spawn3(lettaCmd, cliArgs, {
43285
43352
  cwd: process.cwd(),
43286
43353
  env: {
43287
43354
  ...process.env,
43288
- LETTA_CODE_AGENT_ROLE: "subagent"
43355
+ LETTA_CODE_AGENT_ROLE: "subagent",
43356
+ ...parentAgentId && { LETTA_PARENT_AGENT_ID: parentAgentId }
43289
43357
  }
43290
43358
  });
43291
43359
  let wasAborted = false;
@@ -45644,7 +45712,13 @@ var init_subagents2 = __esm(() => {
45644
45712
  init_explore();
45645
45713
  init_general_purpose();
45646
45714
  init_plan();
45647
- BUILTIN_SOURCES2 = [explore_default, general_purpose_default, plan_default];
45715
+ init_recall();
45716
+ BUILTIN_SOURCES2 = [
45717
+ explore_default,
45718
+ general_purpose_default,
45719
+ plan_default,
45720
+ recall_default
45721
+ ];
45648
45722
  GLOBAL_AGENTS_DIR2 = join10(process.env.HOME || process.env.USERPROFILE || "~", ".letta/agents");
45649
45723
  VALID_MEMORY_BLOCKS2 = new Set(MEMORY_BLOCK_LABELS);
45650
45724
  cache4 = {
@@ -48326,7 +48400,9 @@ var init_checker = __esm(() => {
48326
48400
  "explore",
48327
48401
  "Explore",
48328
48402
  "plan",
48329
- "Plan"
48403
+ "Plan",
48404
+ "recall",
48405
+ "Recall"
48330
48406
  ]);
48331
48407
  });
48332
48408
 
@@ -49510,7 +49586,7 @@ var init_create = __esm(async () => {
49510
49586
  });
49511
49587
 
49512
49588
  // src/agent/message.ts
49513
- async function sendMessageStream(agentId, messages, opts = { streamTokens: true, background: true }) {
49589
+ async function sendMessageStream(agentId, messages, opts = { streamTokens: true, background: true }, requestOptions = { maxRetries: 0 }) {
49514
49590
  const client = await getClient2();
49515
49591
  return client.agents.messages.create(agentId, {
49516
49592
  messages,
@@ -49518,7 +49594,7 @@ async function sendMessageStream(agentId, messages, opts = { streamTokens: true,
49518
49594
  stream_tokens: opts.streamTokens ?? true,
49519
49595
  background: opts.background ?? true,
49520
49596
  client_tools: getClientToolsFromRegistry()
49521
- });
49597
+ }, requestOptions);
49522
49598
  }
49523
49599
  var init_message = __esm(async () => {
49524
49600
  await __promiseAll([
@@ -49636,8 +49712,10 @@ function markCurrentLineAsFinished(b) {
49636
49712
  markAsFinished(b, b.lastOtid);
49637
49713
  } else {}
49638
49714
  }
49639
- function markIncompleteToolsAsCancelled(b) {
49640
- b.interrupted = true;
49715
+ function markIncompleteToolsAsCancelled(b, setInterruptedFlag = true) {
49716
+ if (setInterruptedFlag) {
49717
+ b.interrupted = true;
49718
+ }
49641
49719
  let anyToolsCancelled = false;
49642
49720
  for (const [id, line] of b.byId.entries()) {
49643
49721
  if (line.kind === "tool_call" && line.phase !== "finished") {
@@ -50032,6 +50110,10 @@ async function drainStream(stream2, buffers, refresh, abortSignal, onFirstMessag
50032
50110
  queueMicrotask(refresh);
50033
50111
  break;
50034
50112
  }
50113
+ const errObj = chunk.error;
50114
+ if (errObj?.detail?.includes("No tool call is currently awaiting approval")) {
50115
+ continue;
50116
+ }
50035
50117
  onChunk(buffers, chunk);
50036
50118
  queueMicrotask(refresh);
50037
50119
  if (chunk.message_type === "stop_reason") {
@@ -50113,7 +50195,7 @@ async function drainStreamWithResume(stream2, buffers, refresh, abortSignal, onF
50113
50195
  const resumeStream = await client.runs.messages.stream(result.lastRunId, {
50114
50196
  starting_after: result.lastSeqId,
50115
50197
  batch_size: 1000
50116
- });
50198
+ }, { maxRetries: 0 });
50117
50199
  const resumeResult = await drainStream(resumeStream, buffers, refresh, abortSignal);
50118
50200
  result = resumeResult;
50119
50201
  } catch (_e) {
@@ -70528,6 +70610,7 @@ function App2({
70528
70610
  }, [agentId]);
70529
70611
  const [streaming, setStreaming, streamingRef] = useSyncedState(false);
70530
70612
  const processingConversationRef = import_react76.useRef(0);
70613
+ const conversationGenerationRef = import_react76.useRef(0);
70531
70614
  const [interruptRequested, setInterruptRequested] = import_react76.useState(false);
70532
70615
  const [commandRunning, setCommandRunning, commandRunningRef] = useSyncedState(false);
70533
70616
  const [profileConfirmPending, setProfileConfirmPending] = import_react76.useState(null);
@@ -70917,6 +71000,10 @@ function App2({
70917
71000
  const processConversation = import_react76.useCallback(async (initialInput, options) => {
70918
71001
  const currentInput = [...initialInput];
70919
71002
  const allowReentry = options?.allowReentry ?? false;
71003
+ const myGeneration = options?.submissionGeneration ?? conversationGenerationRef.current;
71004
+ if (myGeneration !== conversationGenerationRef.current) {
71005
+ return;
71006
+ }
70920
71007
  if (processingConversationRef.current > 0 && !allowReentry) {
70921
71008
  return;
70922
71009
  }
@@ -70930,20 +71017,29 @@ function App2({
70930
71017
  userCancelledRef.current = false;
70931
71018
  return;
70932
71019
  }
71020
+ if (myGeneration !== conversationGenerationRef.current) {
71021
+ return;
71022
+ }
70933
71023
  setStreaming(true);
70934
71024
  abortControllerRef.current = new AbortController;
70935
- markIncompleteToolsAsCancelled(buffersRef.current);
71025
+ markIncompleteToolsAsCancelled(buffersRef.current, false);
70936
71026
  buffersRef.current.interrupted = false;
70937
71027
  clearCompletedSubagents();
70938
71028
  while (true) {
70939
71029
  const signal = abortControllerRef.current?.signal;
70940
71030
  if (signal?.aborted) {
70941
- setStreaming(false);
71031
+ const isStaleAtAbort = myGeneration !== conversationGenerationRef.current;
71032
+ if (!isStaleAtAbort) {
71033
+ setStreaming(false);
71034
+ }
70942
71035
  return;
70943
71036
  }
70944
71037
  const stream2 = await sendMessageStream(agentIdRef.current, currentInput);
70945
71038
  if (signal?.aborted) {
70946
- setStreaming(false);
71039
+ const isStaleAtAbort = myGeneration !== conversationGenerationRef.current;
71040
+ if (!isStaleAtAbort) {
71041
+ setStreaming(false);
71042
+ }
70947
71043
  return;
70948
71044
  }
70949
71045
  const syncAgentState = async () => {
@@ -70987,6 +71083,10 @@ function App2({
70987
71083
  const wasInterrupted = !!buffersRef.current.interrupted;
70988
71084
  const wasAborted = !!signal?.aborted;
70989
71085
  let stopReasonToHandle = wasAborted ? "cancelled" : stopReason;
71086
+ const isStaleAfterDrain = myGeneration !== conversationGenerationRef.current;
71087
+ if (isStaleAfterDrain) {
71088
+ return;
71089
+ }
70990
71090
  if (!wasInterrupted) {
70991
71091
  refreshDerived();
70992
71092
  }
@@ -71306,7 +71406,8 @@ function App2({
71306
71406
  sendDesktopNotification();
71307
71407
  return;
71308
71408
  }
71309
- const isApprovalPayload = currentInput.length === 1 && currentInput[0]?.type === "approval";
71409
+ const hasApprovalInPayload = currentInput.some((item) => item?.type === "approval");
71410
+ const isApprovalOnlyPayload = hasApprovalInPayload && currentInput.length === 1;
71310
71411
  let latestErrorText = null;
71311
71412
  for (let i = buffersRef.current.order.length - 1;i >= 0; i -= 1) {
71312
71413
  const id = buffersRef.current.order[i];
@@ -71321,7 +71422,7 @@ function App2({
71321
71422
  const detailFromRun = await fetchRunErrorDetail(lastRunId);
71322
71423
  const desyncDetected = isApprovalStateDesyncError(detailFromRun) || isApprovalStateDesyncError(latestErrorText);
71323
71424
  const lastFailureMessage = latestErrorText || detailFromRun || null;
71324
- if (isApprovalPayload && desyncDetected) {
71425
+ if (hasApprovalInPayload && desyncDetected) {
71325
71426
  if (llmApiErrorRetriesRef.current < LLM_API_ERROR_MAX_RETRIES2) {
71326
71427
  llmApiErrorRetriesRef.current += 1;
71327
71428
  const statusId = uid4("status");
@@ -71334,10 +71435,20 @@ function App2({
71334
71435
  });
71335
71436
  buffersRef.current.order.push(statusId);
71336
71437
  refreshDerived();
71337
- currentInput.splice(0, currentInput.length, buildApprovalRecoveryMessage());
71438
+ if (isApprovalOnlyPayload) {
71439
+ currentInput.splice(0, currentInput.length, buildApprovalRecoveryMessage());
71440
+ } else {
71441
+ const messageItems = currentInput.filter((item) => item?.type !== "approval");
71442
+ if (messageItems.length > 0) {
71443
+ currentInput.splice(0, currentInput.length, ...messageItems);
71444
+ } else {
71445
+ currentInput.splice(0, currentInput.length, buildApprovalRecoveryMessage());
71446
+ }
71447
+ }
71338
71448
  buffersRef.current.byId.delete(statusId);
71339
71449
  buffersRef.current.order = buffersRef.current.order.filter((id) => id !== statusId);
71340
71450
  refreshDerived();
71451
+ buffersRef.current.interrupted = false;
71341
71452
  continue;
71342
71453
  }
71343
71454
  const errorToShow = lastFailureMessage || `An error occurred during agent execution
@@ -71377,6 +71488,7 @@ function App2({
71377
71488
  buffersRef.current.order = buffersRef.current.order.filter((id) => id !== statusId);
71378
71489
  refreshDerived();
71379
71490
  if (!cancelled) {
71491
+ buffersRef.current.interrupted = false;
71380
71492
  continue;
71381
71493
  }
71382
71494
  }
@@ -71449,8 +71561,11 @@ function App2({
71449
71561
  sendDesktopNotification();
71450
71562
  refreshDerived();
71451
71563
  } finally {
71564
+ const isStale = myGeneration !== conversationGenerationRef.current;
71452
71565
  abortControllerRef.current = null;
71453
- processingConversationRef.current = Math.max(0, processingConversationRef.current - 1);
71566
+ if (!isStale) {
71567
+ processingConversationRef.current = Math.max(0, processingConversationRef.current - 1);
71568
+ }
71454
71569
  }
71455
71570
  }, [
71456
71571
  appendError,
@@ -71493,8 +71608,9 @@ function App2({
71493
71608
  }, 50);
71494
71609
  return;
71495
71610
  }
71496
- if (!streaming || interruptRequested)
71611
+ if (!streaming || interruptRequested) {
71497
71612
  return;
71613
+ }
71498
71614
  if (waitingForQueueCancelRef.current) {
71499
71615
  setRestoreQueueOnCancel(true);
71500
71616
  }
@@ -71507,11 +71623,22 @@ function App2({
71507
71623
  abortControllerRef.current = null;
71508
71624
  }
71509
71625
  userCancelledRef.current = true;
71626
+ conversationGenerationRef.current += 1;
71627
+ processingConversationRef.current = 0;
71510
71628
  setStreaming(false);
71511
71629
  if (!toolsCancelled) {
71512
71630
  appendError(INTERRUPT_MESSAGE, true);
71513
71631
  }
71514
71632
  refreshDerived();
71633
+ if (pendingApprovals.length > 0) {
71634
+ const denialResults = pendingApprovals.map((approval) => ({
71635
+ type: "approval",
71636
+ tool_call_id: approval.toolCallId,
71637
+ approve: false,
71638
+ reason: "User interrupted the stream"
71639
+ }));
71640
+ setQueuedApprovalResults(denialResults);
71641
+ }
71515
71642
  setPendingApprovals([]);
71516
71643
  setApprovalContexts([]);
71517
71644
  setApprovalResults([]);
@@ -71544,7 +71671,8 @@ function App2({
71544
71671
  appendError,
71545
71672
  isExecutingTool,
71546
71673
  refreshDerived,
71547
- setStreaming
71674
+ setStreaming,
71675
+ pendingApprovals
71548
71676
  ]);
71549
71677
  const processConversationRef = import_react76.useRef(processConversation);
71550
71678
  import_react76.useEffect(() => {
@@ -71759,6 +71887,96 @@ function App2({
71759
71887
  }
71760
71888
  refreshDerived();
71761
71889
  }, [refreshDerived]);
71890
+ const checkPendingApprovalsForSlashCommand = import_react76.useCallback(async () => {
71891
+ if (!CHECK_PENDING_APPROVALS_BEFORE_SEND) {
71892
+ return { blocked: false };
71893
+ }
71894
+ try {
71895
+ const client = await getClient2();
71896
+ const agent = await client.agents.retrieve(agentId);
71897
+ const { pendingApprovals: existingApprovals } = await getResumeData2(client, agent);
71898
+ if (!existingApprovals || existingApprovals.length === 0) {
71899
+ return { blocked: false };
71900
+ }
71901
+ const approvalResults2 = await Promise.all(existingApprovals.map(async (approvalItem) => {
71902
+ if (!approvalItem.toolName) {
71903
+ return {
71904
+ approval: approvalItem,
71905
+ permission: {
71906
+ decision: "deny",
71907
+ reason: "Tool call incomplete - missing name"
71908
+ },
71909
+ context: null
71910
+ };
71911
+ }
71912
+ const parsedArgs = safeJsonParseOr(approvalItem.toolArgs, {});
71913
+ const permission = await checkToolPermission(approvalItem.toolName, parsedArgs);
71914
+ const context3 = await analyzeToolApproval(approvalItem.toolName, parsedArgs);
71915
+ return { approval: approvalItem, permission, context: context3 };
71916
+ }));
71917
+ const needsUserInput = [];
71918
+ const autoAllowed = [];
71919
+ const autoDenied = [];
71920
+ for (const ac of approvalResults2) {
71921
+ const { approval, permission } = ac;
71922
+ let decision = permission.decision;
71923
+ if (alwaysRequiresUserInput(approval.toolName) && decision === "allow") {
71924
+ decision = "ask";
71925
+ }
71926
+ if (decision === "ask") {
71927
+ needsUserInput.push(ac);
71928
+ } else if (decision === "deny") {
71929
+ autoDenied.push(ac);
71930
+ } else {
71931
+ autoAllowed.push(ac);
71932
+ }
71933
+ }
71934
+ if (needsUserInput.length > 0) {
71935
+ setPendingApprovals(needsUserInput.map((ac) => ac.approval));
71936
+ setApprovalContexts(needsUserInput.map((ac) => ac.context).filter((ctx) => ctx !== null));
71937
+ return { blocked: true };
71938
+ }
71939
+ const allResults = [];
71940
+ if (autoAllowed.length > 0) {
71941
+ const autoAllowedResults = await executeAutoAllowedTools(autoAllowed, (chunk) => onChunk(buffersRef.current, chunk));
71942
+ allResults.push(...autoAllowedResults.map((ar) => ({
71943
+ type: "tool",
71944
+ tool_call_id: ar.toolCallId,
71945
+ tool_return: ar.result.toolReturn,
71946
+ status: ar.result.status,
71947
+ stdout: ar.result.stdout,
71948
+ stderr: ar.result.stderr
71949
+ })));
71950
+ }
71951
+ for (const ac of autoDenied) {
71952
+ const reason = ac.permission.reason || "Permission denied";
71953
+ onChunk(buffersRef.current, {
71954
+ message_type: "tool_return_message",
71955
+ id: "dummy",
71956
+ date: new Date().toISOString(),
71957
+ tool_call_id: ac.approval.toolCallId,
71958
+ tool_return: `Error: request to call tool denied. User reason: ${reason}`,
71959
+ status: "error",
71960
+ stdout: null,
71961
+ stderr: null
71962
+ });
71963
+ allResults.push({
71964
+ type: "approval",
71965
+ tool_call_id: ac.approval.toolCallId,
71966
+ approve: false,
71967
+ reason
71968
+ });
71969
+ }
71970
+ if (allResults.length > 0) {
71971
+ await processConversation([
71972
+ { type: "approval", approvals: allResults }
71973
+ ]);
71974
+ }
71975
+ return { blocked: false };
71976
+ } catch {
71977
+ return { blocked: false };
71978
+ }
71979
+ }, [agentId, processConversation]);
71762
71980
  const onSubmit = import_react76.useCallback(async (message) => {
71763
71981
  const msg = message?.trim() ?? "";
71764
71982
  if (profileConfirmPending && !msg) {
@@ -71787,6 +72005,7 @@ function App2({
71787
72005
  }
71788
72006
  if (!msg)
71789
72007
  return { submitted: false };
72008
+ const submissionGeneration = conversationGenerationRef.current;
71790
72009
  telemetry2.trackUserInput(msg, "user", currentModelId || "unknown");
71791
72010
  if (pendingApprovals.length > 0) {
71792
72011
  return { submitted: false };
@@ -71799,6 +72018,9 @@ function App2({
71799
72018
  if (!isSlashCommand && streamingRef.current && !waitingForQueueCancelRef.current) {
71800
72019
  waitingForQueueCancelRef.current = true;
71801
72020
  queueSnapshotRef.current = [...newQueue];
72021
+ if (toolAbortControllerRef.current) {
72022
+ toolAbortControllerRef.current.abort();
72023
+ }
71802
72024
  getClient2().then((client) => client.agents.messages.cancel(agentId)).then(() => {}).catch(() => {
71803
72025
  waitingForQueueCancelRef.current = false;
71804
72026
  });
@@ -72477,6 +72699,10 @@ Press Enter to continue, or type anything to cancel.`, false, "running");
72477
72699
  return { submitted: true };
72478
72700
  }
72479
72701
  if (trimmed.startsWith("/skill")) {
72702
+ const approvalCheck = await checkPendingApprovalsForSlashCommand();
72703
+ if (approvalCheck.blocked) {
72704
+ return { submitted: false };
72705
+ }
72480
72706
  const cmdId2 = uid4("cmd");
72481
72707
  const [, ...rest] = trimmed.split(/\s+/);
72482
72708
  const description = rest.join(" ").trim();
@@ -72535,6 +72761,10 @@ ${SKILL_CREATOR_PROMPT3}${userDescriptionLine}
72535
72761
  return { submitted: true };
72536
72762
  }
72537
72763
  if (trimmed.startsWith("/remember")) {
72764
+ const approvalCheck = await checkPendingApprovalsForSlashCommand();
72765
+ if (approvalCheck.blocked) {
72766
+ return { submitted: false };
72767
+ }
72538
72768
  const cmdId2 = uid4("cmd");
72539
72769
  const [, ...rest] = trimmed.split(/\s+/);
72540
72770
  const userText = rest.join(" ").trim();
@@ -72591,6 +72821,10 @@ The user did not specify what to remember. Look at the recent conversation conte
72591
72821
  return { submitted: true };
72592
72822
  }
72593
72823
  if (trimmed === "/init") {
72824
+ const approvalCheck = await checkPendingApprovalsForSlashCommand();
72825
+ if (approvalCheck.blocked) {
72826
+ return { submitted: false };
72827
+ }
72594
72828
  const cmdId2 = uid4("cmd");
72595
72829
  buffersRef.current.byId.set(cmdId2, {
72596
72830
  kind: "command",
@@ -72707,6 +72941,10 @@ ${gitContext}
72707
72941
  const commandName = trimmed.split(/\s+/)[0]?.slice(1) || "";
72708
72942
  const matchedCustom = await findCustomCommand2(commandName);
72709
72943
  if (matchedCustom) {
72944
+ const approvalCheck = await checkPendingApprovalsForSlashCommand();
72945
+ if (approvalCheck.blocked) {
72946
+ return { submitted: false };
72947
+ }
72710
72948
  const cmdId2 = uid4("cmd");
72711
72949
  const args = trimmed.slice(`/${matchedCustom.id}`.length).trim();
72712
72950
  let prompt = substituteArguments2(matchedCustom.content, args);
@@ -73106,7 +73344,7 @@ DO NOT respond to these messages or otherwise consider them in your response unl
73106
73344
  role: "user",
73107
73345
  content: messageContent
73108
73346
  });
73109
- await processConversation(initialInput);
73347
+ await processConversation(initialInput, { submissionGeneration });
73110
73348
  clearPlaceholdersInText(msg);
73111
73349
  return { submitted: true };
73112
73350
  }, [
@@ -73228,6 +73466,8 @@ DO NOT respond to these messages or otherwise consider them in your response unl
73228
73466
  setQueuedApprovalResults(allResults);
73229
73467
  }
73230
73468
  setStreaming(false);
73469
+ waitingForQueueCancelRef.current = false;
73470
+ queueSnapshotRef.current = [];
73231
73471
  } else {
73232
73472
  await processConversation([
73233
73473
  {
@@ -73330,7 +73570,11 @@ DO NOT respond to these messages or otherwise consider them in your response unl
73330
73570
  }
73331
73571
  }
73332
73572
  setIsExecutingTool(true);
73573
+ const approvalResultsSnapshot = [...approvalResults];
73574
+ const autoHandledSnapshot = [...autoHandledResults];
73575
+ const autoDeniedSnapshot = [...autoDeniedApprovals];
73333
73576
  const allDecisions = [
73577
+ ...approvalResultsSnapshot,
73334
73578
  { type: "approve", approval: currentApproval2 },
73335
73579
  ...nowAutoAllowed.map((r) => ({
73336
73580
  type: "approve",
@@ -73350,12 +73594,29 @@ DO NOT respond to these messages or otherwise consider them in your response unl
73350
73594
  onChunk(buffersRef.current, chunk);
73351
73595
  refreshDerived();
73352
73596
  });
73597
+ const allResults = [
73598
+ ...autoHandledSnapshot.map((ar) => ({
73599
+ type: "tool",
73600
+ tool_call_id: ar.toolCallId,
73601
+ tool_return: ar.result.toolReturn,
73602
+ status: ar.result.status,
73603
+ stdout: ar.result.stdout,
73604
+ stderr: ar.result.stderr
73605
+ })),
73606
+ ...autoDeniedSnapshot.map((ad) => ({
73607
+ type: "approval",
73608
+ tool_call_id: ad.approval.toolCallId,
73609
+ approve: false,
73610
+ reason: ad.reason
73611
+ })),
73612
+ ...executedResults
73613
+ ];
73353
73614
  setThinkingMessage(getRandomThinkingVerb());
73354
73615
  refreshDerived();
73355
73616
  await processConversation([
73356
73617
  {
73357
73618
  type: "approval",
73358
- approvals: executedResults
73619
+ approvals: allResults
73359
73620
  }
73360
73621
  ]);
73361
73622
  } finally {
@@ -73369,6 +73630,8 @@ DO NOT respond to these messages or otherwise consider them in your response unl
73369
73630
  approvalResults,
73370
73631
  approvalContexts,
73371
73632
  pendingApprovals,
73633
+ autoHandledResults,
73634
+ autoDeniedApprovals,
73372
73635
  handleApproveCurrent,
73373
73636
  processConversation,
73374
73637
  refreshDerived,
@@ -73976,7 +74239,6 @@ Plan file path: ${planFilePath}`;
73976
74239
  ]);
73977
74240
  return /* @__PURE__ */ jsx_dev_runtime56.jsxDEV(Box_default, {
73978
74241
  flexDirection: "column",
73979
- gap: 1,
73980
74242
  children: [
73981
74243
  /* @__PURE__ */ jsx_dev_runtime56.jsxDEV(Static, {
73982
74244
  items: staticItems,
@@ -74022,7 +74284,6 @@ Plan file path: ${planFilePath}`;
74022
74284
  }, staticRenderEpoch, false, undefined, this),
74023
74285
  /* @__PURE__ */ jsx_dev_runtime56.jsxDEV(Box_default, {
74024
74286
  flexDirection: "column",
74025
- gap: 1,
74026
74287
  children: [
74027
74288
  loadingState !== "ready" && /* @__PURE__ */ jsx_dev_runtime56.jsxDEV(WelcomeScreen, {
74028
74289
  loadingState,
@@ -74217,7 +74478,7 @@ Plan file path: ${planFilePath}`;
74217
74478
  ]
74218
74479
  }, undefined, true, undefined, this),
74219
74480
  /* @__PURE__ */ jsx_dev_runtime56.jsxDEV(Box_default, {
74220
- marginTop: liveItems.length > 0 ? 0 : 1,
74481
+ marginTop: 1,
74221
74482
  children: /* @__PURE__ */ jsx_dev_runtime56.jsxDEV(Input, {
74222
74483
  visible: !showExitStats && pendingApprovals.length === 0 && !anySelectorOpen,
74223
74484
  streaming: streaming && !abortControllerRef.current?.signal.aborted,
@@ -77531,13 +77792,19 @@ Error: ${message}`);
77531
77792
  try {
77532
77793
  await client.agents.retrieve(localProjectSettings.lastAgent);
77533
77794
  resumingAgentId = localProjectSettings.lastAgent;
77534
- } catch {}
77795
+ } catch {
77796
+ setLoadingState("selecting_global");
77797
+ return;
77798
+ }
77535
77799
  }
77536
77800
  if (!resumingAgentId && continueSession && settings.lastAgent) {
77537
77801
  try {
77538
77802
  await client.agents.retrieve(settings.lastAgent);
77539
77803
  resumingAgentId = settings.lastAgent;
77540
- } catch {}
77804
+ } catch {
77805
+ setLoadingState("selecting_global");
77806
+ return;
77807
+ }
77541
77808
  }
77542
77809
  if (!resumingAgentId && selectedGlobalAgentId) {
77543
77810
  try {
@@ -77592,22 +77859,22 @@ Error: ${message}`);
77592
77859
  agent = result.agent;
77593
77860
  setAgentProvenance(result.provenance);
77594
77861
  }
77595
- if (!agent) {
77596
- await settingsManager2.loadLocalProjectSettings();
77597
- const localProjectSettings = settingsManager2.getLocalProjectSettings();
77598
- if (localProjectSettings?.lastAgent) {
77599
- try {
77600
- agent = await client.agents.retrieve(localProjectSettings.lastAgent);
77601
- } catch (error) {
77602
- console.error(`Project agent ${localProjectSettings.lastAgent} not found (error: ${JSON.stringify(error)}), creating new one...`);
77603
- }
77862
+ if (!agent && resumingAgentId) {
77863
+ try {
77864
+ agent = await client.agents.retrieve(resumingAgentId);
77865
+ } catch (error) {
77866
+ console.error(`Agent ${resumingAgentId} not found (error: ${JSON.stringify(error)})`);
77867
+ setLoadingState("selecting_global");
77868
+ return;
77604
77869
  }
77605
77870
  }
77606
77871
  if (!agent && continueSession && settings.lastAgent) {
77607
77872
  try {
77608
77873
  agent = await client.agents.retrieve(settings.lastAgent);
77609
77874
  } catch (error) {
77610
- console.error(`Previous agent ${settings.lastAgent} not found (error: ${JSON.stringify(error)}), creating new one...`);
77875
+ console.error(`Previous agent ${settings.lastAgent} not found (error: ${JSON.stringify(error)})`);
77876
+ setLoadingState("selecting_global");
77877
+ return;
77611
77878
  }
77612
77879
  }
77613
77880
  if (!agent) {
@@ -77770,4 +78037,4 @@ Error during initialization: ${message}`);
77770
78037
  }
77771
78038
  main();
77772
78039
 
77773
- //# debugId=91C0453F45E8BBAD64756E2164756E21
78040
+ //# debugId=D2E37E4C34BABD1E64756E2164756E21
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@letta-ai/letta-code",
3
- "version": "0.11.2-next.4",
3
+ "version": "0.12.1",
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": {