@letta-ai/letta-code 0.34.17 → 0.34.19

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 (80) hide show
  1. package/README.md +12 -0
  2. package/dist/agent-presets.js +5 -1
  3. package/dist/agent-presets.js.map +2 -2
  4. package/dist/gateway-core.js +2 -2
  5. package/dist/gateway-core.js.map +3 -3
  6. package/dist/mcp-client.js +2 -2
  7. package/dist/mcp-client.js.map +1 -1
  8. package/dist/mcp-oauth.js +2 -2
  9. package/dist/mcp-oauth.js.map +1 -1
  10. package/dist/types/agent/approval-execution.d.ts +33 -1
  11. package/dist/types/agent/approval-execution.d.ts.map +1 -1
  12. package/dist/types/agent/client-skills.d.ts.map +1 -1
  13. package/dist/types/agent/parent-memory-note.d.ts +3 -0
  14. package/dist/types/agent/parent-memory-note.d.ts.map +1 -0
  15. package/dist/types/agent/preloaded-skills.d.ts +4 -0
  16. package/dist/types/agent/preloaded-skills.d.ts.map +1 -0
  17. package/dist/types/agent/skills.d.ts.map +1 -1
  18. package/dist/types/agent/subagent-state.d.ts +53 -2
  19. package/dist/types/agent/subagent-state.d.ts.map +1 -1
  20. package/dist/types/agent/subagents/child-send-tracking.d.ts.map +1 -1
  21. package/dist/types/agent/subagents/index.d.ts.map +1 -1
  22. package/dist/types/agent/subagents/remote-turn-wait.d.ts.map +1 -1
  23. package/dist/types/agent/subagents/subagent-stream.d.ts.map +1 -1
  24. package/dist/types/auth/oauth.d.ts +8 -1
  25. package/dist/types/auth/oauth.d.ts.map +1 -1
  26. package/dist/types/backend/api/conversation-enqueue.d.ts +4 -1
  27. package/dist/types/backend/api/conversation-enqueue.d.ts.map +1 -1
  28. package/dist/types/channels/message-channel-executor.d.ts.map +1 -1
  29. package/dist/types/cli/helpers/agent-info.d.ts +1 -0
  30. package/dist/types/cli/helpers/agent-info.d.ts.map +1 -1
  31. package/dist/types/cli/subcommands/mcp.d.ts +3 -1
  32. package/dist/types/cli/subcommands/mcp.d.ts.map +1 -1
  33. package/dist/types/headless-listener-launch.d.ts +5 -3
  34. package/dist/types/headless-listener-launch.d.ts.map +1 -1
  35. package/dist/types/headless-super-run-wait.d.ts +1 -1
  36. package/dist/types/headless-super-run-wait.d.ts.map +1 -1
  37. package/dist/types/reminders/engine.d.ts.map +1 -1
  38. package/dist/types/telemetry/channel.d.ts.map +1 -1
  39. package/dist/types/tools/external-tool-background-eligibility.d.ts +2 -1
  40. package/dist/types/tools/external-tool-background-eligibility.d.ts.map +1 -1
  41. package/dist/types/tools/external-tool-background.d.ts +14 -1
  42. package/dist/types/tools/external-tool-background.d.ts.map +1 -1
  43. package/dist/types/tools/external-tool-types.d.ts +1 -0
  44. package/dist/types/tools/external-tool-types.d.ts.map +1 -1
  45. package/dist/types/tools/impl/external-coding-agent.d.ts.map +1 -1
  46. package/dist/types/tools/impl/monitor.d.ts +2 -0
  47. package/dist/types/tools/impl/monitor.d.ts.map +1 -1
  48. package/dist/types/tools/impl/remote-external-coding-agent.d.ts +1 -0
  49. package/dist/types/tools/impl/remote-external-coding-agent.d.ts.map +1 -1
  50. package/dist/types/tools/impl/task-abort-hook.d.ts +2 -0
  51. package/dist/types/tools/impl/task-abort-hook.d.ts.map +1 -0
  52. package/dist/types/tools/impl/task-stop.d.ts.map +1 -1
  53. package/dist/types/tools/impl/task.d.ts.map +1 -1
  54. package/dist/types/tools/impl/watch-pr.d.ts +1 -1
  55. package/dist/types/tools/impl/watch-pr.d.ts.map +1 -1
  56. package/dist/types/tools/impl/workflow.d.ts +7 -0
  57. package/dist/types/tools/impl/workflow.d.ts.map +1 -1
  58. package/dist/types/tools/manager.d.ts +1 -1
  59. package/dist/types/tools/manager.d.ts.map +1 -1
  60. package/dist/types/tools/script-tool-scope.d.ts.map +1 -1
  61. package/dist/types/tools/workflow/sdk-spawner.d.ts.map +1 -1
  62. package/dist/types/tools/workflow/types.d.ts +2 -0
  63. package/dist/types/tools/workflow/types.d.ts.map +1 -1
  64. package/dist/types/utils/subagent-scope.d.ts +13 -0
  65. package/dist/types/utils/subagent-scope.d.ts.map +1 -0
  66. package/dist/types/utils/task-notifications.d.ts +43 -0
  67. package/dist/types/utils/task-notifications.d.ts.map +1 -1
  68. package/letta.js +1182 -692
  69. package/npm-shrinkwrap.json +98 -98
  70. package/package.json +2 -2
  71. package/scripts/claude-watch/docs-snapshot.test.ts +65 -0
  72. package/scripts/claude-watch/docs-snapshot.ts +10 -4
  73. package/scripts/isolated-unit-tests.json +5 -0
  74. package/scripts/source-file-size-baseline.json +9 -9
  75. package/skills/finding-agents/SKILL.md +0 -4
  76. package/skills/maintaining-core-memory/SKILL.md +101 -0
  77. package/skills/maintaining-deferred-memory/SKILL.md +84 -0
  78. package/skills/workflow-authoring/SKILL.md +3 -1
  79. package/skills/curating-memory-palace/SKILL.md +0 -147
  80. package/skills/migrating-memory/SKILL.md +0 -98
package/letta.js CHANGED
@@ -3411,6 +3411,7 @@ __export(exports_oauth, {
3411
3411
  validateCredentialsWithResult: () => validateCredentialsWithResult,
3412
3412
  validateCredentials: () => validateCredentials,
3413
3413
  revokeToken: () => revokeToken,
3414
+ resolveAuthBaseUrl: () => resolveAuthBaseUrl,
3414
3415
  requestDeviceCode: () => requestDeviceCode2,
3415
3416
  refreshAccessToken: () => refreshAccessToken3,
3416
3417
  pollForToken: () => pollForToken2,
@@ -3418,6 +3419,28 @@ __export(exports_oauth, {
3418
3419
  OAUTH_CONFIG: () => OAUTH_CONFIG,
3419
3420
  LETTA_CLOUD_API_URL: () => LETTA_CLOUD_API_URL
3420
3421
  });
3422
+ function resolveAuthBaseUrl() {
3423
+ const override = process.env.LETTA_AUTH_BASE_URL?.trim();
3424
+ if (!override) {
3425
+ return DEFAULT_AUTH_BASE_URL;
3426
+ }
3427
+ let warning = null;
3428
+ try {
3429
+ const url = new URL(override);
3430
+ const isLocalhost = url.hostname === "localhost" || url.hostname === "127.0.0.1";
3431
+ if (url.protocol === "https:" || url.protocol === "http:" && isLocalhost) {
3432
+ return url.origin;
3433
+ }
3434
+ warning = "must be an https origin (http is allowed for localhost)";
3435
+ } catch {
3436
+ warning = "is not a valid URL";
3437
+ }
3438
+ if (!hasWarnedAboutInvalidAuthBaseUrl) {
3439
+ hasWarnedAboutInvalidAuthBaseUrl = true;
3440
+ console.warn(`Ignoring invalid LETTA_AUTH_BASE_URL (${JSON.stringify(override)}): ${warning}. Falling back to ${DEFAULT_AUTH_BASE_URL}.`);
3441
+ }
3442
+ return DEFAULT_AUTH_BASE_URL;
3443
+ }
3421
3444
  function getOAuthAuthHost() {
3422
3445
  try {
3423
3446
  return new URL(OAUTH_CONFIG.authBaseUrl).host;
@@ -3776,7 +3799,7 @@ function classifyCredentialValidationError(error) {
3776
3799
  }
3777
3800
  return { ok: false, reason: "unknown", message };
3778
3801
  }
3779
- var LETTA_CLOUD_API_URL = "https://api.letta.com", OAUTH_CONFIG, OAuthRefreshError, DEVICE_CODE_REQUEST_MAX_ATTEMPTS = 2, DEVICE_CODE_RETRY_DELAY_MS = 100, TOKEN_POLL_RESPONSE_FAILURE_LIMIT = 2, OAUTH_REQUEST_ID_HEADERS, OAuthTransientResponseError;
3802
+ var LETTA_CLOUD_API_URL = "https://api.letta.com", DEFAULT_AUTH_BASE_URL = "https://app.letta.com", hasWarnedAboutInvalidAuthBaseUrl = false, OAUTH_CONFIG, OAuthRefreshError, DEVICE_CODE_REQUEST_MAX_ATTEMPTS = 2, DEVICE_CODE_RETRY_DELAY_MS = 100, TOKEN_POLL_RESPONSE_FAILURE_LIMIT = 2, OAUTH_REQUEST_ID_HEADERS, OAuthTransientResponseError;
3780
3803
  var init_oauth = __esm(() => {
3781
3804
  init_letta_client();
3782
3805
  init_error();
@@ -3784,7 +3807,9 @@ var init_oauth = __esm(() => {
3784
3807
  OAUTH_CONFIG = {
3785
3808
  clientId: "ci-let-724dea7e98f4af6f8f370f4b1466200c",
3786
3809
  clientSecret: "",
3787
- authBaseUrl: "https://app.letta.com",
3810
+ get authBaseUrl() {
3811
+ return resolveAuthBaseUrl();
3812
+ },
3788
3813
  apiBaseUrl: LETTA_CLOUD_API_URL
3789
3814
  };
3790
3815
  OAuthRefreshError = class OAuthRefreshError extends Error {
@@ -5916,7 +5941,7 @@ var package_default;
5916
5941
  var init_package = __esm(() => {
5917
5942
  package_default = {
5918
5943
  name: "@letta-ai/letta-code",
5919
- version: "0.34.17",
5944
+ version: "0.34.19",
5920
5945
  lettaStartupLogProtocol: 1,
5921
5946
  description: "Letta Code is a CLI tool for interacting with stateful Letta agents from the terminal.",
5922
5947
  type: "module",
@@ -6191,7 +6216,7 @@ var init_package = __esm(() => {
6191
6216
  },
6192
6217
  lettaLifecycleProtocol: 1,
6193
6218
  lettaRuntimeGraphProtocol: 1,
6194
- lettaRuntimeGraphSha512: "sha512-NGzNq0UmSHPiPqvc6bflUL57U2h5xZSgf9HvpbR98ocuoM1dHLN1hiXv6qNEa3lOlj2paYI/clNKR+U1YW0TiA=="
6219
+ lettaRuntimeGraphSha512: "sha512-RWgzMK/RLJluHmJEAXwWV7NZe1gpOgg3BYpp8+G8QfqsXUUmzZgQpQz5E9CHogeIwfcGzCaHAUIdboH6Ir7KyA=="
6195
6220
  };
6196
6221
  });
6197
6222
 
@@ -6463,6 +6488,7 @@ function telemetryChannel(value) {
6463
6488
  return;
6464
6489
  return [
6465
6490
  "slack",
6491
+ "microsoftteams",
6466
6492
  "microsoftTeams",
6467
6493
  "telegram",
6468
6494
  "discord",
@@ -99266,6 +99292,8 @@ You may also have access to shared memory: memory created independently of any s
99266
99292
 
99267
99293
  Unlike the rest of your external memory, shared memory is not scoped to *you* specifically (since it may be attached to multiple agents at the same time), so each shared memory repository will have a different local projection root and remote git origin.
99268
99294
 
99295
+ Attached shared-memory mounts are shallow checkouts containing only the latest commit. If you need past history, run \`git fetch --deepen=<n>\` or \`git fetch --unshallow\` inside the mount only when needed, since some repositories have very long histories.
99296
+
99269
99297
  Commit intended changes inside read/write attached shared-memory repositories. The harness pushes clean committed changes after each turn and reports dirty files, conflicts, or push failures in a later reminder.
99270
99298
 
99271
99299
  ### Syncing memory, state, and context
@@ -99823,6 +99851,8 @@ You may also have access to shared memory: memory created independently of any s
99823
99851
 
99824
99852
  Unlike the rest of your external memory, shared memory is not scoped to *you* specifically (since it may be attached to multiple agents at the same time), so each shared memory repository will have a different local projection root and remote git origin.
99825
99853
 
99854
+ Attached shared-memory mounts are shallow checkouts containing only the latest commit. If you need past history, run \`git fetch --deepen=<n>\` or \`git fetch --unshallow\` inside the mount only when needed, since some repositories have very long histories.
99855
+
99826
99856
  Commit intended changes inside read/write attached shared-memory repositories. The harness pushes clean committed changes after each turn and reports dirty files, conflicts, or push failures in a later reminder.
99827
99857
 
99828
99858
  ### Syncing memory, state, and context
@@ -103471,7 +103501,7 @@ async function syncRepoMount(args) {
103471
103501
  await withRepositoryCheckout(args.directory, async (directory, fresh) => {
103472
103502
  args = { ...args, publishedDirectory: args.directory, directory };
103473
103503
  if (fresh) {
103474
- await runGitWithRetry(directory, ["clone", args.remoteUrl, "."], args.token, {
103504
+ await runGitWithRetry(directory, ["clone", "--depth", "1", args.remoteUrl, "."], args.token, {
103475
103505
  operation: `clone repository ${args.repositoryName}`,
103476
103506
  timeoutMs: GIT_CLONE_TIMEOUT_MS
103477
103507
  });
@@ -106605,34 +106635,29 @@ name: memory
106605
106635
  description: Background memory upkeep during another task; when memory is the user's main request, edit it directly instead. Runs silently with no notification or result, so continue immediately. Make the assignment self-contained and quote the user's corrections and exceptions verbatim.
106606
106636
  tools: Bash, Read, Edit, Write
106607
106637
  model: inherit
106608
- skills: creating-skills
106638
+ skills: creating-skills, maintaining-core-memory, maintaining-deferred-memory
106609
106639
  fork: false
106610
106640
  launchProfile: memory-subagent
106611
106641
  ---
106612
106642
 
106613
- You are the primary agent's background memory subagent. The parent transcript is available as reference material when needed, not a task to continue. Work only on the memory request below. Never ask the primary agent or user questions, send messages, or launch further agents. Your final report stays in this background task.
106614
-
106615
- Your tools are Bash, Read, Edit, and Write. Your working memory repository is \`$MEMORY_DIR\`; the assignment also gives the exact path. For a memory update it is a private worktree of the agent's memory: the harness merges your commits into the main checkout when you finish, and anything left uncommitted is discarded. For a Git repair it is the checkout itself. Read current files before editing; memory may have changed since the assignment. Keep all writes inside this repository. Do not modify git configuration or hook files.
106616
-
106617
- ## Updating memory
106618
-
106619
- Capture the requested facts, preferences, corrections, or deletions. Use the assignment directly when it is sufficient. Consult the parent transcript only for a specific missing fact or ambiguity; do not read it for general orientation. Preserve the exact scope of facts and exceptions without adding inferred preferences. Keep quoted factual corrections and constraints verbatim rather than generalizing them. Update existing entries rather than duplicating them, and replace stale information at its source. Preserve unrelated content and established identity. Do not store secrets or ephemeral task logs. Update indexes when adding, moving, or deleting files.
106620
-
106621
- Make focused edits. Reorganize or defragment memory only when explicitly requested. Do not perform transcript reflection or unrelated skill maintenance. For requested skill changes, apply the preloaded \`creating-skills\` guidance in \`$MEMORY_DIR/skills/\`; maintain skills in place rather than packaging them for distribution.
106643
+ You are a background memory subagent. Make the memory change in the assignment below, following the preloaded skills; consult the parent transcript only for a specific missing fact or ambiguity.
106622
106644
 
106623
106645
  ## Git
106624
106646
 
106625
- Inspect \`git status\` before editing. If a merge or rebase is already in progress, resolve it first by reading both sides and preserving the intended memory. Never prefer one side wholesale. Stage only resolved files and finish the existing operation with a noninteractive editor. Do not abort, reset, stash, amend, discard, or commit unrelated changes. If the intended resolution is unclear, leave the unresolved state intact and describe the blocker in your final report.
106647
+ \`$MEMORY_DIR\` is a private worktree of the agent's memory. The harness merges your commits into the main checkout when you finish and handles sync, so commit but do not push; uncommitted edits are discarded. If a merge or rebase is already in progress, resolve it by reading both sides and preserving the intended memory, then finish the operation; do not abort, reset, stash, or amend.
106626
106648
 
106627
- For a repair-only request, stop after repairing the reported Git or validation state; if it has already been resolved, make no changes.
106649
+ Stage only the files you changed, by explicit path, and commit them in one call:
106628
106650
 
106629
- For an update or reorganization request, make the requested changes and commit only the files you changed: stage them by explicit path, never with \`git add -A\` or \`git add .\`, because the primary agent may be editing other files in this checkout at the same time. Use a concise commit message and the repository's configured authorship. Preserve required frontmatter and obey the repository's validation hooks. Do not push: the harness handles normal sync after you finish.
106630
-
106631
- Return a brief report of actual changes or the unresolved blocker. Do not claim that an uncommitted edit or failed operation succeeded.
106651
+ \`\`\`bash
106652
+ cd "$MEMORY_DIR"
106653
+ git add <file>...
106654
+ git commit -m "<type>(memory): <summary>
106632
106655
 
106633
- ## Memory layout
106656
+ Updates:
106657
+ - <what changed and why>"
106658
+ \`\`\`
106634
106659
 
106635
- Root \`MEMORY.md\` has no frontmatter and is an index linking core files and deferred indexes. Other root Markdown files are core memory and require exactly \`name\` and \`description\` frontmatter. Child directories contain memory only when they have a frontmatter-free \`MEMORY.md\`; other Markdown files in them also require \`name\` and \`description\`. \`skills/\` is separate procedural memory. Do not edit generated \`memory_filesystem.md\` or \`.sync-state.json\`.
106660
+ \`<type>\` is \`fix\` for correcting wrong or stale memory, \`feat\` for new memory, and \`chore\` for reorganization or minor edits. Do not change git configuration or bypass hooks; if the commit fails, report the failure rather than claiming success.
106636
106661
  `;
106637
106662
  var init_memory_v2 = () => {};
106638
106663
 
@@ -106862,121 +106887,28 @@ name: reflection
106862
106887
  description: Background agent that reflects on recent conversations to update memory and maintain skills
106863
106888
  tools: Bash, Edit
106864
106889
  model: inherit
106865
- skills: creating-skills
106890
+ skills: creating-skills, maintaining-core-memory, maintaining-deferred-memory
106866
106891
  launchProfile: memory-subagent
106867
106892
  ---
106893
+ You are a reflection subagent tasked with improving a primary agent's capabilities and knowledge to perform better on future tasks.
106894
+ Using recent conversation transcripts, identify potential repeatable workflows (which can be converted into skills), behavior failures (which require updates to prompts), and general information and knowledge.
106895
+ The primary agent's context (prompts, skills, and deferred memory) is stored in $MEMORY_DIR, which contains the "Memory Filesystem" of the primary agent.
106896
+ Read through the transcript to identify and make updates to memory. You run autonomously and return a single final report when done. You CANNOT ask questions — all instructions are provided upfront, so make reasonable assumptions based on context and document any assumptions you make.
106868
106897
 
106869
- You are a reflection subagent launched in the background to manage the primary agent's memory, context, and skills after recent conversation activity. You run autonomously and return a single final report when done. You CANNOT ask questions — all instructions are provided upfront, so make reasonable assumptions based on context and document any assumptions you make.
106870
-
106871
- **You are NOT the primary agent.** You are reviewing conversations that already happened:
106872
- - "system" messages are the primary agent's system prompt — use them only to understand the agent's identity and what's relevant to the user. They are not something you edit directly; memory edits flow through files in \`$MEMORY_DIR\`.
106873
- - "assistant" messages are from the primary agent
106874
- - "user" messages are from the primary agent's user
106875
-
106876
- Your job is to turn lasting learnings from those conversations into changes to the primary agent's memory filesystem: memory edits for facts, preferences, and corrections, and — only when a reusable procedure appears — skill changes. Then commit them.
106877
-
106878
- ## The Memory Filesystem
106879
-
106880
- The primary agent's context (its prompts, skills, and external memory files) is stored in a git-backed "memory filesystem" rooted at \`$MEMORY_DIR\`. Committed changes are reflected in the primary agent's context. Every piece of memory lives in one of three tiers, and the tier decides when the agent sees it and how it should be written:
106881
-
106882
- | | **Root files** | **Deferred memory** | **Skills** |
106883
- |---|---|---|---|
106884
- | **Location** | Markdown files directly in \`$MEMORY_DIR\` | Directories under \`$MEMORY_DIR\` (other than \`skills/\`) | \`$MEMORY_DIR/skills/<name>/\` |
106885
- | **When the agent sees it** | Every turn, in full — part of the system prompt | Only when the agent opens the directory with \`Memory(<dir>)\` or reads a file | Name and description every turn; the body only when the agent loads the skill |
106886
- | **Cost** | Paid on every turn | Paid only when opened | Description on every turn; body only when loaded |
106887
- | **What belongs here** | Identity, standing instructions, and facts that matter on every turn; the root \`MEMORY.md\` index of deferred memory | Knowledge about a subject (a person, project, repo, system, or incident) that the agent looks up when that subject comes up | Procedures with a clear "use when" trigger — usually a repeatable, multi-step workflow |
106888
- | **Written as** | Instructions to the agent | Reference notes: instructions about the subject first, then facts | A procedure: steps, exact commands, pitfalls |
106889
- | **Description says** | What belongs in the file | What the memory is *about* | What the skill does (third person) and when to use it |
106890
-
106891
- ### Choosing a tier
106892
-
106893
- Decide placement for each learning in this order:
106894
-
106895
- 1. **Should it change how the agent behaves or what it knows on every turn?** Put it in a root file, written as an instruction. Root is expensive, so this bar is high.
106896
- 2. **Can you write a clear "Use when the user wants to …" trigger for it, and is it a procedure?** Make it a skill (see "Skills" below for when a skill is warranted).
106897
- 3. **Otherwise it is knowledge about a subject.** Put it in deferred memory, in the directory for that subject.
106898
-
106899
- One-off task state belongs nowhere.
106900
-
106901
- ### Root files
106902
-
106903
- Root Markdown files are part of the primary agent's system prompt: always in context and visible to the agent. Use them to define the core behavior of the agent: what it always knows, and the prompting that controls how it behaves.
106904
-
106905
- Writing root files:
106906
- * Write instructions, not explicit feedback. User or environment feedback tells you how the agent's behavior or knowledge should change. Write a prompt to affect this change, rather than simply recording the feedback.
106907
- * Leave out the evidence or context for the edit. All context modifications are git-tracked with commit messages, so do not require an explanation for why an edit was made within the edit or details about the context under which the edit was made.
106908
- * Minimize bloat. Too many tokens can distract agents and add cost to every turn. Instructions that are only needed sometimes belong in a skill (if a trigger can be defined) or in deferred memory.
106909
- * Root files that define the agent's identity and behavior (such as \`persona.md\`) are load-bearing. Edit them surgically — append, modify specific entries, adjust wording. Never rewrite them wholesale or silently overwrite established identity.
106910
-
106911
- **The root \`MEMORY.md\` is the only entry point to deferred memory.** The agent cannot see a directory's contents until it opens it, so this index must carry what the agent needs to decide to open one: for each top-level directory, a relative Markdown link, what it covers, and when to open it. For example:
106912
-
106913
- \`\`\`
106914
- - [letta-code/](letta-code/MEMORY.md) — repo conventions, CI, release process. Open before working in letta-code.
106915
- \`\`\`
106916
-
106917
- This is where "when to use" belongs for deferred memory — in the parent's link, not in the memory's own description.
106898
+ You are not the primary agent and do not reply to anyone. In the transcript, "assistant" messages are the primary agent, "user" messages are its user, and "system" messages are its system prompt, compiled from the memory files you edit.
106918
106899
 
106919
- ### Deferred memory
106920
-
106921
- Directories contain *deferred memory*. Higher directory levels hold memory that is accessed more often; more deeply nested directories hold memory needed less often. Place related memory in the same directory so it can be loaded together. Each directory has a \`MEMORY.md\`, which is the directory's top level: when the primary agent calls \`Memory(<dir>)\`, it reads \`<dir>/MEMORY.md\` in full, plus a listing of the directory's other entries (each file's \`description\` and the first paragraph of each subdirectory's \`MEMORY.md\`). The listing shows at most 10 entries, most recently edited first. The agent can also read files directly with other tools.
106922
-
106923
- Decide what goes in a \`MEMORY.md\` by what the agent should get from opening that directory:
106924
- * **Small topic: put everything in \`MEMORY.md\`.** If the whole topic fits comfortably (well under 20,000 characters) and most of it is useful whenever the topic comes up, keep it in \`MEMORY.md\` with no other files. Splitting adds reads without saving context.
106925
- * **Large topic, or one rarely needed in full: \`MEMORY.md\` holds what is needed every time, plus a map.** Keep only what the agent needs whenever it opens the directory, and defer the rest into files, or into subdirectories when a subtopic is itself large. \`MEMORY.md\` must then tell the agent where to go next, or that it can stop: say what each deferred file or subdirectory covers and when it is worth opening, using ordinary relative Markdown links. Do not rely on the listing, since it shows only the 10 most recently edited entries and stable files fall out of it.
106926
- * **Either way, start \`MEMORY.md\` with a short plain-prose paragraph stating the directory's scope.** That paragraph is what the parent directory's listing shows, so do not open with a heading or a list.
106927
- * Aim for fewer than 10 entries (files plus subdirectories) per directory so the listing stays complete; group related files into subdirectories only within the configured \`maxDepth\` (see below). At the depth limit, use clearly scoped sibling files and explicit links in \`MEMORY.md\`, or reorganize topics into shallower directories. The depth limit takes precedence over the fewer-than-10-entries guideline.
106928
-
106929
- Writing deferred memory contents (in \`MEMORY.md\` and in other files):
106930
- * **One subject per file**, named the way a future request would name it. If two files cover the same subject, merge them.
106931
- * **Instructions first, then facts.** The agent opens deferred memory because it is about to act on that subject, so lead with what it should do differently — conventions, gotchas, things to avoid — written as instructions, like root files. Background facts follow.
106932
- * **Make each statement stand alone.** Name the subject ("The letta-code release workflow …") rather than "it" or "the repo"; the agent may read only part of a file.
106933
- * **Organize by topic under stable headings, not as a dated log.** Record the current state and fix contradictions in place. Use absolute dates only for facts that are genuinely time-bound.
106934
- * **Leave out evidence and history**, as with root files. Git keeps it.
106935
-
106936
- Writing memory descriptions (every deferred file other than \`MEMORY.md\`):
106937
- * Describe what the memory is *about*, not when to use it. Lead with the subject (a person, project, repo, system, or incident) using the names a future request would mention, then the subtopics it covers.
106938
- * If you can write a clear "use when the user wants to…" trigger, the content is procedural and belongs in a skill.
106939
- * Make siblings distinguishable by subject; if two files cover the same subject, merge them.
106940
- * Describe scope, not current contents: no entry lists, dates, status, or why the file was created.
106941
-
106942
- ### Skills
106943
-
106944
- The \`skills/\` folder contains skills (folders which adhere to the skills specification). Skills are loaded by the primary agent with a \`Skill\` tool. Skill names and descriptions are automatically provided to the agent in context, so skills do not need to be indexed by \`MEMORY.md\` unless they relate to a specific memory.
106945
-
106946
- Skills are not the default. A one-off task, a fact, or a preference belongs in memory, not a skill. Use a skill only when the conversation reveals a repeatable procedure that clearly generalizes beyond this session and has a trigger you can state.
106947
-
106948
- Apply the preloaded \`creating-skills\` guidance to skills in \`$MEMORY_DIR/skills/\`; maintain them in place rather than packaging them for distribution.
106949
-
106950
- ### Frontmatter, size, and nesting limits
106951
-
106952
- * \`MEMORY.md\` files (root and child) have no frontmatter.
106953
- * Every other memory Markdown file has exactly \`name\` and \`description\` frontmatter; the memory repo's pre-commit check rejects missing or extra keys.
106954
- * \`SKILL.md\` files are exempt from that check and follow the preloaded \`creating-skills\` guidance instead.
106955
- * By default the memory repo's pre-commit check blocks any memory file over 20,000 characters and root Markdown totaling over 65,536 characters (\`.memfs.config.json\` may override these). Root files are paid for on every turn, so keep them well below the limits and move detail into child directories rather than growing root.
106956
- * **Nesting is bounded by \`maxDepth\` in \`$MEMORY_DIR/.memfs.config.json\`.** The default configuration sets \`maxDepth: 2\`. Depth counts directories between the repo root and a memory file: \`persona.md\` is depth 0, \`topic/MEMORY.md\` is depth 1, and \`topic/subtopic/notes.md\` is depth 2. With that default, \`topic/subtopic/detail/notes.md\` is rejected at depth 3. Read the repo's configuration before adding or moving memory; an omitted \`maxDepth\` imposes no depth limit. This constraint excludes \`skills/\`. Fit memory to the configured limit rather than changing the configuration to permit deeper nesting.
106957
-
106958
- You can create, delete, or modify files (contents, names, descriptions). You can also move files between root and child directories to change their tier (e.g., root → child directory removes it from in-context).
106959
-
106960
- ## Process
106900
+ ## Trajectory Format
106961
106901
 
106962
106902
  The payload is at \`$TRANSCRIPT_PATH\`. It may be either:
106963
106903
 
106964
106904
  1. a JSON message array for one conversation, or
106965
106905
  2. a \`multi_transcript_reflection_payload\` manifest. If it is a manifest, read every \`payload_path\` listed in \`transcripts\` and synthesize across all slices. Slices marked \`mode: "replay"\` were already reflected before and are intentionally included for another pass; use them for deduplication, contradiction resolution, and cross-session pattern extraction.
106966
106906
 
106967
- When reviewing multiple transcripts, prefer patterns supported across sessions, resolve contradictions in favor of the latest evidence, and avoid recording one-off task state. Follow the phases below in order.
106968
-
106969
- ### Phase 1 — Investigate
106970
-
106971
- Understand the current memory landscape before changing anything. Your user prompt already includes a \`<memory_filesystem>\` tree of memory file names (no descriptions; \`skills/\` is omitted) and the full content of every root file inlined in \`<memory>\` blocks — start there, since those are the parent agent's in-context prompts.
106907
+ When reviewing multiple transcripts, prefer patterns supported across sessions, resolve contradictions in favor of the latest evidence, and avoid recording one-off task state.
106972
106908
 
106973
- Read \`$MEMORY_DIR/.memfs.config.json\` if present to identify the repo's nesting and size limits before planning its structure.
106909
+ Read transcripts in bounded chunks: check the size first, read files up to 15000 bytes in full, and use targeted reads on larger ones (\`head\`, \`tail\`, \`grep\`, \`sed -n\` on Unix; \`Get-Content\`, \`Select-String\` in PowerShell, which the Bash tool runs on Windows).
106974
106910
 
106975
- For deferred memory, read the relevant child \`MEMORY.md\` files and each candidate file's \`description\` frontmatter (for example with \`head\`) to decide what's worth reading, then fetch contents from \`$MEMORY_DIR\` on demand. Follow ordinary relative Markdown links from \`MEMORY.md\` when relevant. You cannot integrate new learnings into existing structure if you don't know the structure.
106976
-
106977
- For skills, list \`$MEMORY_DIR/skills/\` and read each \`SKILL.md\` frontmatter description to triage adjacency to the candidate procedure, then read the full \`SKILL.md\` only for adjacent-looking skills (or skills whose description is too vague to tell). If no description looks adjacent, you don't need to read any \`SKILL.md\`. When unsure about adjacency, err on the side of reading.
106978
-
106979
- ### Phase 2 — Extract
106911
+ ## Trajectory review
106980
106912
 
106981
106913
  Review the conversation and identify candidate learnings worth persisting. Prioritize in this order:
106982
106914
 
@@ -106986,132 +106918,59 @@ Review the conversation and identify candidate learnings worth persisting. Prior
106986
106918
  4. **Contradictions** — anything that conflicts with what's currently stored in memory
106987
106919
  5. **Reusable procedures** — repeatable, multi-step workflows that may belong in skills
106988
106920
 
106989
- For each candidate, apply these filters before acting:
106990
-
106991
- - **Lasting or ephemeral?** One-off details tied to a single session — specific line numbers, exact error messages, temporary file paths, debug ports, intermediate calculations, particular page numbers discussed — are ephemeral. Don't store them.
106992
- - **Already captured?** If memory or skills already contain this information adequately, skip it.
106993
- - **Generalizable?** Distill reusable patterns, not event transcripts. "Write short chapters that end on cliffhangers" is worth storing; "User edited chapter 3 paragraph 2 on Tuesday" is not. "Hedge FX exposure on quarterly positions" is worth storing; "Sold 500 shares of AAPL at $187.50" is not. "Write table-driven tests with testify" is worth storing; "User ran tests at 3pm on Tuesday" is not. The raw conversation is already searchable — don't re-record it.
106994
- - **Temporal references?** Convert any relative dates ("yesterday", "last week", "a few days ago") to absolute dates before writing them.
106995
- - **Which tier?** Apply "Choosing a tier" above.
106996
-
106997
- If nothing survives filtering, make no changes and skip to Phase 5 with no commit.
106998
-
106999
- ### Phase 3 — Update
107000
-
107001
- For each learning that survived Phase 2, make surgical, well-placed changes.
107002
-
107003
- #### Memory edits
107004
-
107005
- - **Placement**: Put the learning in the tier chosen in Phase 2, written the way that tier's section above describes.
107006
- - **Integration**: If an existing file already covers this subject, update it. Only create a new file when the subject is genuinely distinct and has no natural home in existing files. Fragmentation makes memory harder to navigate.
107007
- - **Contradiction resolution**: If new information contradicts existing memory, fix the stale entry at the source. Do not append the new version alongside the old.
107008
- - **Discovery paths**: When adding or moving content, update the links in root \`MEMORY.md\` and any affected child \`MEMORY.md\` files so every deferred file stays reachable. Keep \`name\` and \`description\` frontmatter accurate.
107009
-
107010
- #### Skills (only when a reusable procedure appears)
107011
-
107012
- Only make a skill change when the conversation demonstrates a repeatable, multi-step workflow with enough concrete detail to be actionable. Pick **at most one** operation, listed in rough order of preference (prefer modifying an existing skill over creating a new one):
107013
-
107014
- - \`update\` — an existing skill covers the workflow, but the conversation revealed a wrong, dangerous, or outdated step. Read the existing file, then fix that step in place with Edit; preserve the rest.
107015
- - \`extend\` — an existing skill covers a similar workflow, and the conversation revealed a new variant or edge case. Add a new section with Edit rather than duplicating the skill or rewriting existing sections.
107016
- - \`deprecate\` — an existing skill is obsolete, harmful, or replaced. Either \`rm -r skills/<name>/\` (delete mode), or add \`deprecated: "true"\` (and optionally \`replaced_by: <skill-name>\`) under the frontmatter \`metadata\` map, start the description with "Deprecated:" followed by the replacement skill's name, and add a short note at the top of the body explaining why (marker mode).
107017
- - \`split\` — an existing skill has drifted to bundle two distinct procedures and the conversation makes that painful. Write both replacement \`SKILL.md\` files (plus relevant companion files for each) with Bash, then delete the source skill with Bash or trim it with Edit. Use sparingly.
107018
- - \`create\` — a genuinely novel, repeatable procedure with concrete detail (commands, tool patterns, config values), and no existing skill covers it even partially.
107019
- - \`none\` — one-off, trivial, informational, already covered, or better stored as ordinary memory. Make no skill changes.
107020
-
107021
- As a heuristic, when unsure between \`create\` and \`none\`, choose \`none\`. When unsure between \`create\` and a modify op, choose the modify op.
107022
-
107023
- For \`update\`/\`extend\`, preserve the existing frontmatter; change the description only if the skill's scope or triggers changed.
107024
-
107025
- Do not report a \`create\`, \`update\`, \`extend\`, or \`split\` as merely "intended" if Bash can perform the write. The final report must describe actual filesystem changes.
107026
-
107027
- ### Phase 4 — Review
107028
-
107029
- Quick sanity pass before committing.
107030
-
107031
- - **No secrets or junk**: Do not persist sensitive values, raw logs, or ephemeral transcript details.
107032
-
107033
- #### Memory
106921
+ Skip anything ephemeral (one-off task state, line numbers, exact errors, temporary paths) or already captured. Convert relative dates to absolute ones. If nothing survives, make no changes and do not commit.
107034
106922
 
107035
- - **Stale content**: Did the conversation make anything in existing memory obsolete or superseded? Remove or update it now.
107036
- - **Cross-reference integrity**: If you deleted or moved a file, check whether any ordinary relative Markdown links point to the old location and update them.
107037
- - **Tier check**: Did you add anything to root that's really deferred material? Move it to a child directory. Did you leave something in a child directory that the agent needs on every turn? Promote it to root.
107038
- - **Nesting check**: Confirm every added or moved memory file stays within the configured \`maxDepth\`, including each directory's \`MEMORY.md\`.
107039
- - **Discoverability**: For each deferred file you added or moved, confirm the primary agent can reach it from root \`MEMORY.md\` through each intermediate directory's \`MEMORY.md\`, that each link says when the target is worth opening, and that its \`description\` says what it is about.
106923
+ ## Making memory updates
107040
106924
 
107041
- #### Skills (only if you made a skill change)
106925
+ Write each change the way the preloaded skills describe. Before editing, read the relevant existing files so you integrate rather than duplicate. For skills, prefer updating or extending an existing skill over creating one, make at most one skill change per run, and when unsure whether a procedure deserves a skill, make none.
107042
106926
 
107043
- - **No near-duplicates**: For \`create\`, scan the tree once more — is there really no existing skill that covers this? If you spot a partial overlap you missed, switch to \`extend\`.
107044
- - **Companion file completeness**: For \`create\`/\`split\`, if \`SKILL.md\` references files under \`scripts/\`, \`references/\`, or \`templates/\`, verify those paths actually exist.
107045
- - **Stale skill references**: For \`deprecate\` (delete mode) or \`split\`, check whether any memory or skill file references the old skill path, and update those references.
106927
+ Use Edit for files that already exist. Create new files with a quoted heredoc on Unix, or on Windows a single-quoted here-string piped to \`Set-Content -Encoding utf8\`; memory files must stay UTF-8. Edit does not expand environment variables: resolve absolute paths first (\`printf "%s/persona.md\\n" "$MEMORY_DIR"\`, or \`Join-Path $env:MEMORY_DIR "persona.md"\` in PowerShell) and pass the printed path. Keep all writes inside \`$MEMORY_DIR\`.
107046
106928
 
107047
- ### Phase 5 — Commit
106929
+ ## Commit
107048
106930
 
107049
106931
  Before writing the commit, resolve the actual ID values:
107050
106932
  \`\`\`bash
107051
106933
  echo "CHILD_AGENT_ID=$LETTA_AGENT_ID"
107052
- echo "PARENT_AGENT_ID=$LETTA_PARENT_AGENT_ID"
107053
106934
  \`\`\`
107054
106935
 
107055
- Use the printed values (e.g., \`agent-abc123...\`) in the trailers. If a variable is empty or unset, omit that trailer. Never write a literal variable name like \`$LETTA_AGENT_ID\` in the commit message. Run git commands only from \`$MEMORY_DIR\`. Use plain \`-m "..."\` with an embedded multi-line string exactly as shown below:
106936
+ Use the printed value (e.g., \`agent-abc123...\`) in the trailer. If it is empty or unset, omit the trailer. Never write a literal variable name like \`$LETTA_AGENT_ID\` in the commit message. Run git commands only from \`$MEMORY_DIR\`. Use plain \`-m "..."\` with an embedded multi-line string exactly as shown below:
107056
106937
 
107057
106938
  \`\`\`bash
107058
106939
  cd $MEMORY_DIR
107059
106940
  git add -A
107060
106941
  git commit --author="Reflection Subagent <<CHILD_AGENT_ID>@letta.com>" -m "<type>(reflection): <summary> 🔮
107061
106942
 
107062
- Reviewed transcript: <transcript_filepath>
106943
+ Reviewed transcripts:
106944
+ - <transcript_filepath>
106945
+ - ...
107063
106946
 
107064
106947
  Updates:
107065
106948
  - <what changed and why>
107066
106949
 
107067
- Generated-By: Letta Code
107068
- Agent-ID: <CHILD_AGENT_ID>
107069
- Parent-Agent-ID: <PARENT_AGENT_ID>"
106950
+ Generated-By: Reflection
106951
+ Agent-ID: <CHILD_AGENT_ID>"
107070
106952
  \`\`\`
107071
106953
 
107072
106954
  **Commit type** — pick the one that fits:
107073
- - \`fix\` — correcting a mistake or bad memory, or fixing a wrong/obsolete skill (\`update\`/\`deprecate\`)
107074
- - \`feat\` — adding wholly new memory content, or new skill content/structure (\`create\`/\`extend\`/\`split\`)
106955
+ - \`fix\` — correcting a mistake or bad memory, or fixing a wrong/obsolete skill
106956
+ - \`feat\` — adding wholly new memory content, or new skill content/structure
107075
106957
  - \`chore\` — routine updates, adding context, minor doc-only skill edits
107076
106958
 
107077
- In the commit message body, explain what changed and why, drawing from the categories you identified in Phase 2. If the change is skill-related, include the operation in the subject, e.g. \`feat(reflection): create docker-debugging skill 🔮\`.
106959
+ In the commit message body, explain what changed and why, drawing from the categories in the trajectory review. If the change is skill-related, say so in the subject, e.g. \`feat(reflection): create docker-debugging skill 🔮\`.
107078
106960
 
107079
106961
  If no changes were needed, do NOT commit. Report that the conversation contained no memory worth persisting.
107080
106962
 
107081
106963
  If \`git add\` or \`git commit\` fails, stop after one reasonable retry and report the failure. Do not run \`git config\`, mutate \`.git\`, use \`git reset\`, or assume the harness will persist uncommitted filesystem edits; uncommitted edits are not successful memory persistence.
107082
106964
 
107083
- ## Tools and Paths
107084
-
107085
- You only have access to **Bash** and **Edit**. Do not call \`Read\`, \`Write\`, memory tools, recall tools, or conversation search, even if those names appear in the transcript.
107086
-
107087
- Your memory repo root is \`$MEMORY_DIR\`. The terminal tool can expand environment variables; use \`$MEMORY_DIR\` on Unix or \`$env:MEMORY_DIR\` in PowerShell. Edit cannot expand either form. Keep all filesystem writes under the memory repo and run all git commands from inside it. Do not inspect or modify \`.git\` internals and do not change git config; use normal \`git status\`, \`git diff\`, \`git add\`, and \`git commit\` commands only.
107088
-
107089
- Use **Edit** for every modification to a file that already exists (memory or skill). Do not rewrite existing files with terminal heredocs, scripts, or redirection. Edit paths must be absolute paths under the memory repo, never literal \`$MEMORY_DIR/...\` or \`$env:MEMORY_DIR/...\` strings. To get an Edit path, resolve it with the terminal first (for example, \`printf "%s/persona.md\\n" "$MEMORY_DIR"\` on Unix or \`Join-Path $env:MEMORY_DIR "persona.md"\` in PowerShell) and then use the printed path.
107090
-
107091
- Use the **Bash** terminal tool for reading, git, and filesystem/bulk operations — not for editing the contents of existing files. Follow the shell semantics in the tool description; despite its name, the tool runs native PowerShell or cmd.exe on Windows.
107092
-
107093
- - Inspect transcripts with bounded reads. Determine file size first: on Unix, \`wc -c "$TRANSCRIPT_PATH"\`; in PowerShell, \`(Get-Item -LiteralPath $env:TRANSCRIPT_PATH).Length\`. If a file is <= 15000 bytes, a full read is okay; otherwise use targeted reads (\`head\`, \`tail\`, \`grep\`, and \`sed -n\` on Unix, or \`Get-Content\`, \`Select-String\`, and \`-TotalCount\` in PowerShell).
107094
- - Inspect memory with concise, platform-native commands. Use \`find\`, \`grep\`, \`head\`, and targeted \`cat\` on Unix; use \`Get-ChildItem\`, \`Select-String\`, and \`Get-Content\` in PowerShell.
107095
- - Create new files with the shell-native method. On Unix, use a quoted heredoc. In PowerShell, use a single-quoted here-string piped to \`Set-Content -LiteralPath <path> -Encoding utf8\`. Move, rename, or delete only under \`$MEMORY_DIR\` (\`mv\`, \`rm\`, and \`mkdir -p\` on Unix; \`Move-Item\`, \`Remove-Item\`, and \`New-Item\` in PowerShell). If a temp file is needed, put it under \`$MEMORY_DIR/.tmp/\` and remove it before committing.
107096
- - Memory Markdown files must remain UTF-8. On Windows, never use PowerShell redirection, \`Out-File\`, or \`Set-Content\` without explicit UTF-8 encoding; prefer the Edit tool.
107097
- - On Windows, avoid \`&&\` for failure-stopping chains so the command also works with Windows PowerShell; issue dependent commands in separate tool calls or check \`$LASTEXITCODE\` explicitly.
107098
-
107099
106965
  ## Output Format
107100
106966
 
107101
106967
  Return a report with:
107102
106968
 
107103
106969
  1. **Summary** — What you reviewed and what you concluded (2-3 sentences)
107104
- 2. **Memory changes** — Files created/modified/deleted/moved with a brief reason
107105
- 3. **Skill changes** — Operation selected (\`update\`, \`extend\`, \`deprecate\`, \`split\`, \`create\`, or \`none\`) and files changed
107106
- 4. **Skipped** — Anything considered but not persisted, and why
107107
- 5. **Commit** — Confirm the commit, or "no commit" if nothing was persisted
107108
- 6. **Issues** — Any problems encountered or information that couldn't be determined. If something broke, say what happened and suggest a fix.
107109
-
107110
- ## Critical Reminders
107111
-
107112
- 1. **Not the primary agent** — Don't respond to messages.
107113
- 2. **Be selective** — Few meaningful changes > many trivial ones; few high-quality skills > many trivial ones.
107114
- 3. **Commit or it didn't happen** — Uncommitted edits are lost; if nothing memory-worthy changed, do not commit.
106970
+ 2. **Memory changes** — Files created/modified/deleted/moved with a brief reason, including any skill changes
106971
+ 3. **Skipped** — Anything considered but not persisted, and why
106972
+ 4. **Commit** — Confirm the commit, or "no commit" if nothing was persisted
106973
+ 5. **Issues** — Any problems encountered or information that couldn't be determined. If something broke, say what happened and suggest a fix.
107115
106974
  `;
107116
106975
  var init_reflection_v2 = () => {};
107117
106976
 
@@ -107271,7 +107130,11 @@ function resolveSubagentConfigForMemoryFormat(config, memoryFormat, localMemfs)
107271
107130
  if (!v1Builtin || !v2Builtin || config.systemPrompt !== v1Builtin.systemPrompt) {
107272
107131
  return config;
107273
107132
  }
107274
- return { ...config, systemPrompt: v2Builtin.systemPrompt };
107133
+ return {
107134
+ ...config,
107135
+ systemPrompt: v2Builtin.systemPrompt,
107136
+ skills: config.skills.join(",") === v1Builtin.skills.join(",") ? [...v2Builtin.skills] : config.skills
107137
+ };
107275
107138
  }
107276
107139
  function getBuiltinSubagentNames() {
107277
107140
  return new Set(Object.keys(getBuiltinSubagents()));
@@ -172772,6 +172635,16 @@ var init_message_queue_bridge = __esm(() => {
172772
172635
  pendingMessages = [];
172773
172636
  });
172774
172637
 
172638
+ // src/utils/subagent-scope.ts
172639
+ function subagentScopeMatches(parentAgentId, parentConversationId, scope) {
172640
+ if (scope.agentId !== undefined && parentAgentId !== scope.agentId) {
172641
+ return false;
172642
+ }
172643
+ if (scope.conversationId === undefined)
172644
+ return true;
172645
+ return parentConversationId === undefined || parentConversationId === scope.conversationId;
172646
+ }
172647
+
172775
172648
  // src/utils/task-notifications.ts
172776
172649
  function escapeXml(str) {
172777
172650
  return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
@@ -172880,6 +172753,47 @@ function appendTaskNotificationEventsToBuffer(summaries, buffer, generateId, flu
172880
172753
  flush?.();
172881
172754
  return true;
172882
172755
  }
172756
+ function selectStopFailedNotifications(rows, seenIds, scope) {
172757
+ const failures = [];
172758
+ const stops = [];
172759
+ for (const row of rows) {
172760
+ if (!subagentScopeMatches(row.parentAgentId, row.parentConversationId, scope)) {
172761
+ continue;
172762
+ }
172763
+ if (row.stopFailed && (row.status === "pending" || row.status === "running")) {
172764
+ const key2 = `${row.id}:${row.stopOutcome?.attempt ?? 0}`;
172765
+ if (!seenIds.has(key2)) {
172766
+ seenIds.add(key2);
172767
+ failures.push(`Background task "${row.description}" could not be stopped — still running remotely. Press Esc to retry.`);
172768
+ }
172769
+ continue;
172770
+ }
172771
+ if (row.stopOutcome?.kind === "stopped" && row.status === "error") {
172772
+ const key2 = `${row.id}:${row.stopOutcome.attempt}`;
172773
+ if (!seenIds.has(key2)) {
172774
+ let surfacedEarlierFailure = false;
172775
+ for (let attempt2 = 0;attempt2 < row.stopOutcome.attempt; attempt2 += 1) {
172776
+ if (seenIds.has(`${row.id}:${attempt2}`)) {
172777
+ surfacedEarlierFailure = true;
172778
+ break;
172779
+ }
172780
+ }
172781
+ if (surfacedEarlierFailure) {
172782
+ seenIds.add(key2);
172783
+ stops.push(`Background task "${row.description}" stopped.`);
172784
+ }
172785
+ }
172786
+ }
172787
+ }
172788
+ const present = new Set(rows.map((row) => row.id));
172789
+ for (const key2 of seenIds) {
172790
+ const rowId = key2.slice(0, key2.lastIndexOf(":"));
172791
+ if (!present.has(rowId)) {
172792
+ seenIds.delete(key2);
172793
+ }
172794
+ }
172795
+ return { failures, stops };
172796
+ }
172883
172797
  var init_task_notifications = __esm(() => {
172884
172798
  init_context();
172885
172799
  init_constants2();
@@ -210966,7 +210880,7 @@ var init_mcp_client = __esm(() => {
210966
210880
  init_streamableHttp();
210967
210881
  DEFAULT_CLIENT_INFO = {
210968
210882
  name: "letta-code",
210969
- version: "0.34.17"
210883
+ version: "0.34.19"
210970
210884
  };
210971
210885
  });
210972
210886
 
@@ -215101,7 +215015,7 @@ Do not use this tool for root memory files (already loaded) or skills/ (use Skil
215101
215015
  var init_Memory = () => {};
215102
215016
 
215103
215017
  // src/tools/descriptions/Monitor.md
215104
- var Monitor_default = 'Start a background monitor that streams events from a long-running script. Each stdout line is an event — you keep working and notifications arrive in the chat. Events arrive on their own schedule and are not replies from the user, even if one lands while you\'re waiting for the user to answer a question.\n\nPick by how many notifications you need:\n- **One** ("tell me when the server is ready / the build finishes") → use **Bash with `run_in_background`** and a command that exits when the condition is true, e.g. `until grep -q "Ready in" dev.log; do sleep 0.5; done`. You get a single completion notification when it exits.\n- **One per occurrence, indefinitely** ("tell me every time an ERROR line appears") → Monitor with an unbounded command (`tail -f`, `inotifywait -m`, `while true`).\n- **One per occurrence, until a known end** ("emit each CI step result, stop when the run completes") → Monitor with a command that emits lines and then exits.\n\nYour script\'s stdout is the event stream. Each line becomes a notification. Exit ends the watch.\n # Each matching log line is an event\n tail -f /var/log/app.log | grep --line-buffered "ERROR"\n # Each file change is an event\n inotifywait -m --format \'%e %f\' /watched/dir\n # Node script that emits events as they arrive (e.g. WebSocket listener)\n node watch-for-events.js\n\nFor open github.com pull requests, use WatchPR instead. It covers the whole PR surface and keeps watching after CI finishes. Use Monitor only when WatchPR does not apply.\n\n**Don\'t use an unbounded command for a single notification.** `tail -f`, `inotifywait -m`, and `while true` never exit on their own, so the monitor stays armed until timeout even after the event has fired. For "tell me when X is ready," use Bash `run_in_background` with an `until` loop instead (one notification, ends in seconds). Note that `tail -f log | grep -m 1 ...` does *not* fix this: if the log goes quiet after the match, `tail` never receives SIGPIPE and the pipeline hangs anyway.\n\n- Every pipe stage must flush per line or matches sit in its buffer unseen: `grep` needs `--line-buffered`, `awk` needs `fflush()`. `head` cannot flush at all — `| head -N` delivers nothing until N matches accumulate, then ends the stream.\n- In poll loops, handle transient failures (`curl ... || true`) — one failed request shouldn\'t kill the monitor.\n- Poll intervals: 30s+ for remote APIs (rate limits), 0.5–1s for local checks.\n- Write a specific `description` — it appears in every notification ("errors in deploy.log" not "watching logs").\n- Only stdout is the event stream. Stderr goes to the output file (readable via Read) but does not trigger notifications — for a command you run directly (e.g. `python train.py 2>&1 | grep --line-buffered ...`), merge stderr with `2>&1` so its failures reach your filter. (No effect on `tail -f` of an existing log — that file only contains what its writer redirected.)\n\n**Coverage — silence is not success.** When watching a job or process for an outcome, your filter must match every terminal state, not just the happy path. A monitor that greps only for the success marker stays silent through a crashloop, a hung process, or an unexpected exit — and silence looks identical to "still running." Before arming, ask: *if this process crashed right now, would my filter emit anything?* If not, widen it.\n # Wrong — silent on crash, hang, or any non-success exit\n tail -f run.log | grep --line-buffered "elapsed_steps="\n # Right — one alternation covering progress + the failure signatures you\'d act on\n tail -f run.log | grep -E --line-buffered "elapsed_steps=|Traceback|Error|FAILED|assert|Killed|OOM"\n\nFor poll loops checking job state, emit on every terminal status (`succeeded|failed|cancelled|timeout`), not just success. If you cannot confidently enumerate the failure signatures, broaden the grep alternation rather than narrow it — some extra noise is better than missing a crashloop.\n\n**Output volume**: Every stdout line is a conversation message, so the filter should be selective — but selective means "the lines you\'d act on," not "only good news." Never pipe raw logs; filter to exactly the success and failure signals you care about. Monitors that produce too many events are automatically stopped; restart with a tighter filter if this happens.\n\nStdout lines within 200ms are batched into a single notification, so multiline output from a single event groups naturally.\n\nThe script runs in the same shell environment as Bash. Exit ends the watch (exit code is reported). Timeout → killed. Set `persistent: true` for session-length watches (log tails, long-lived event streams) — the monitor runs until you call TaskStop or the session ends. Use TaskStop to cancel early.\n\nWhen someone is waiting on the result, set `backup_after_seconds` (e.g. 900). If the monitor goes that long without an event, a durable Wake brings you back to check the real state, so a queued, hung, or dead watch can\'t leave them waiting in silence.\n\n**ws source** — open a WebSocket and stream each incoming text frame as an event. No shell, no polling: the server pushes, you get notified.\n ws: {url: \'wss://events.example.com/stream\', protocols: [\'v1\']},\n description: \'deploy events\',\n\nEach text frame becomes one notification (multiline frames stay as one event). Binary frames are reported as `[binary frame, N bytes]` rather than passed through. Socket close ends the watch with the close code surfaced; errors are surfaced before close. Same rate limiting as bash — a firehose will be suppressed and eventually stopped, so subscribe to a filtered feed where one exists.\n\nPrefer this over `command: \'websocat wss://…\'` — it avoids the extra process and line-buffering pitfalls. Use bash when you need to transform or filter frames with shell tools before they become events.\n';
215018
+ var Monitor_default = 'Start a background monitor that streams events from a long-running script. Each stdout line is an event — you keep working and notifications arrive in the chat. Events arrive on their own schedule and are not replies from the user, even if one lands while you\'re waiting for the user to answer a question.\n\nPick by how many notifications you need:\n- **One** ("tell me when the server is ready / the build finishes") → use **Bash with `run_in_background`** and a command that exits when the condition is true, e.g. `until grep -q "Ready in" dev.log; do sleep 0.5; done`. You get a single completion notification when it exits.\n- **One per occurrence, indefinitely** ("tell me every time an ERROR line appears") → Monitor with an unbounded command (`tail -f`, `inotifywait -m`, `while true`).\n- **One per occurrence, until a known end** ("emit each CI step result, stop when the run completes") → Monitor with a command that emits lines and then exits.\n\nYour script\'s stdout is the event stream. Each line becomes a notification. Exit ends the watch.\n # Each matching log line is an event\n tail -f /var/log/app.log | grep --line-buffered "ERROR"\n # Each file change is an event\n inotifywait -m --format \'%e %f\' /watched/dir\n # Node script that emits events as they arrive (e.g. WebSocket listener)\n node watch-for-events.js\n\nFor open github.com pull requests, use WatchPR instead. It covers the whole PR surface and keeps watching after CI finishes. Use Monitor only when WatchPR does not apply.\n\n**Don\'t use an unbounded command for a single notification.** `tail -f`, `inotifywait -m`, and `while true` never exit on their own, so the monitor stays armed until timeout even after the event has fired. For "tell me when X is ready," use Bash `run_in_background` with an `until` loop instead (one notification, ends in seconds). Note that `tail -f log | grep -m 1 ...` does *not* fix this: if the log goes quiet after the match, `tail` never receives SIGPIPE and the pipeline hangs anyway.\n\n- Every pipe stage must flush per line or matches sit in its buffer unseen: `grep` needs `--line-buffered`, `awk` needs `fflush()`. `head` cannot flush at all — `| head -N` delivers nothing until N matches accumulate, then ends the stream.\n- In poll loops, handle transient failures (`curl ... || true`) — one failed request shouldn\'t kill the monitor.\n- Poll intervals: 30s+ for remote APIs (rate limits), 0.5–1s for local checks.\n- Write a specific `description` — it appears in every notification ("errors in deploy.log" not "watching logs").\n- Only stdout is the event stream. Stderr goes to the output file (readable via Read) but does not trigger notifications — for a command you run directly (e.g. `python train.py 2>&1 | grep --line-buffered ...`), merge stderr with `2>&1` so its failures reach your filter. (No effect on `tail -f` of an existing log — that file only contains what its writer redirected.)\n\n**Coverage — silence is not success.** When watching a job or process for an outcome, your filter must match every terminal state, not just the happy path. A monitor that greps only for the success marker stays silent through a crashloop, a hung process, or an unexpected exit — and silence looks identical to "still running." Before arming, ask: *if this process crashed right now, would my filter emit anything?* If not, widen it.\n # Wrong — silent on crash, hang, or any non-success exit\n tail -f run.log | grep --line-buffered "elapsed_steps="\n # Right — one alternation covering progress + the failure signatures you\'d act on\n tail -f run.log | grep -E --line-buffered "elapsed_steps=|Traceback|Error|FAILED|assert|Killed|OOM"\n\nFor poll loops checking job state, emit on every terminal status (`succeeded|failed|cancelled|timeout`), not just success. If you cannot confidently enumerate the failure signatures, broaden the grep alternation rather than narrow it — some extra noise is better than missing a crashloop.\n\n**Output volume**: Every stdout line is a conversation message, so the filter should be selective — but selective means "the lines you\'d act on," not "only good news." Never pipe raw logs; filter to exactly the success and failure signals you care about. Monitors that produce too many events are automatically stopped; restart with a tighter filter if this happens.\n\nStdout lines within 200ms are batched into a single notification, so multiline output from a single event groups naturally.\n\nThe script runs in the same shell environment as Bash. Exit ends the watch (exit code is reported). Timeout → killed. Set `persistent: true` for session-length watches (log tails, long-lived event streams). In managed Cloud sandboxes, every monitor stops after at most 1 hour, including persistent monitors; elsewhere persistent monitors run until TaskStop or session end. For longer Cloud waits, schedule a Wake or cron (which does not keep the sandbox running between turns) and combine it with shorter monitors. Use TaskStop to cancel early.\n\nWhen someone is waiting on the result, set `backup_after_seconds` (e.g. 900). If the monitor goes that long without an event, a durable Wake brings you back to check the real state, so a queued, hung, or dead watch can\'t leave them waiting in silence.\n\n**ws source** — open a WebSocket and stream each incoming text frame as an event. No shell, no polling: the server pushes, you get notified.\n ws: {url: \'wss://events.example.com/stream\', protocols: [\'v1\']},\n description: \'deploy events\',\n\nEach text frame becomes one notification (multiline frames stay as one event). Binary frames are reported as `[binary frame, N bytes]` rather than passed through. Socket close ends the watch with the close code surfaced; errors are surfaced before close. Same rate limiting as bash — a firehose will be suppressed and eventually stopped, so subscribe to a filtered feed where one exists.\n\nPrefer this over `command: \'websocat wss://…\'` — it avoids the extra process and line-buffering pitfalls. Use bash when you need to transform or filter frames with shell tools before they become events.\n';
215105
215019
  var init_Monitor = () => {};
215106
215020
 
215107
215021
  // src/tools/descriptions/Read.md
@@ -215624,6 +215538,8 @@ var init_Wake = () => {};
215624
215538
  // src/tools/descriptions/WatchPR.md
215625
215539
  var WatchPR_default = `Watch one open github.com pull request until it merges, closes, is stopped with TaskStop, or the session ends.
215626
215540
 
215541
+ In managed Cloud sandboxes, WatchPR also stops after a hard 1-hour limit and sends a timeout notification. For checks beyond that window, use Wake or cron (which does not keep the sandbox running between turns) and combine it with shorter watches. Local watches have no duration limit.
215542
+
215627
215543
  Use WatchPR instead of writing a Monitor or shell polling loop when you need ongoing notifications about a PR. Start at most one watch per PR. It reports:
215628
215544
 
215629
215545
  - current-head check failures, recoveries, retries, and completion;
@@ -221280,12 +221196,55 @@ var init_disk_space = __esm(() => {
221280
221196
  var exports_approval_execution = {};
221281
221197
  __export(exports_approval_execution, {
221282
221198
  settleApprovalExecutionTasks: () => settleApprovalExecutionTasks,
221199
+ overlayPartialResults: () => overlayPartialResults,
221200
+ isUserAbortError: () => isUserAbortError,
221283
221201
  getResourceKey: () => getResourceKey,
221284
221202
  getDisplayableToolReturn: () => getDisplayableToolReturn,
221285
221203
  executeAutoAllowedTools: () => executeAutoAllowedTools,
221286
- executeApprovalBatch: () => executeApprovalBatch
221204
+ executeApprovalBatch: () => executeApprovalBatch,
221205
+ buildDeniedResults: () => buildDeniedResults,
221206
+ buildCompanionResults: () => buildCompanionResults,
221207
+ appendAbortPartials: () => appendAbortPartials,
221208
+ ABORT_GRACE_MS: () => ABORT_GRACE_MS
221287
221209
  });
221288
221210
  import * as path16 from "node:path";
221211
+ function isUserAbortError(error5) {
221212
+ return error5 instanceof APIUserAbortError || error5 instanceof Error && (error5.name === "AbortError" || error5.message === INTERRUPTED_BY_USER || error5.message === "The operation was aborted");
221213
+ }
221214
+ function overlayPartialResults(queued, partials) {
221215
+ const realById = new Map((partials ?? []).map((r5) => [r5.tool_call_id, r5]));
221216
+ const overlaid = (queued ?? []).map((r5) => realById.get(r5.tool_call_id) ?? r5);
221217
+ const queuedIds = new Set((queued ?? []).map((r5) => r5.tool_call_id));
221218
+ const extras = (partials ?? []).filter((r5) => !queuedIds.has(r5.tool_call_id));
221219
+ return [...overlaid, ...extras];
221220
+ }
221221
+ function buildDeniedResults(autoDenied) {
221222
+ return autoDenied.map((ad) => ({
221223
+ type: "approval",
221224
+ tool_call_id: ad.approval.toolCallId,
221225
+ approve: false,
221226
+ reason: ad.reason
221227
+ }));
221228
+ }
221229
+ function buildCompanionResults(autoHandled, autoDenied) {
221230
+ return [
221231
+ ...autoHandled.map((ar) => ({
221232
+ type: "tool",
221233
+ tool_call_id: ar.toolCallId,
221234
+ tool_return: ar.result.toolReturn,
221235
+ status: ar.result.status,
221236
+ stdout: ar.result.stdout,
221237
+ stderr: ar.result.stderr
221238
+ })),
221239
+ ...buildDeniedResults(autoDenied)
221240
+ ];
221241
+ }
221242
+ function appendAbortPartials(error5, extras) {
221243
+ if (extras.length === 0 || !isUserAbortError(error5))
221244
+ return;
221245
+ const abortError = error5;
221246
+ abortError.partialResults = [...abortError.partialResults ?? [], ...extras];
221247
+ }
221289
221248
  function getDisplayableToolReturn(content) {
221290
221249
  if (typeof content === "string") {
221291
221250
  return content;
@@ -221422,8 +221381,61 @@ async function executeSingleDecision(decision, onChunk, options) {
221422
221381
  async function executeApprovalBatch(decisions, onChunk, options) {
221423
221382
  return watchDiskSpaceDuring(options?.workingDirectory ?? getCurrentWorkingDirectory(), (text2) => deliverDiskSpaceReminder(text2, options?.parentScope), () => executeApprovalBatchUnwatched(decisions, onChunk, options));
221424
221383
  }
221425
- async function settleApprovalExecutionTasks(tasks, partialResults = () => []) {
221426
- const settled = await Promise.allSettled(tasks);
221384
+ async function settleApprovalExecutionTasks(tasks, partialResults = () => [], options) {
221385
+ const signal = options?.abortSignal;
221386
+ if (!signal || signal.aborted) {
221387
+ throwFirstSettledRejection(await Promise.allSettled(tasks), partialResults);
221388
+ return;
221389
+ }
221390
+ const settled = new Array(tasks.length);
221391
+ let remaining = tasks.length;
221392
+ const tracked = tasks.map((task, index) => task.then(() => {
221393
+ settled[index] = { status: "fulfilled", value: undefined };
221394
+ }, (reason) => {
221395
+ settled[index] = { status: "rejected", reason };
221396
+ }).finally(() => {
221397
+ remaining -= 1;
221398
+ }));
221399
+ let onAbort = () => {};
221400
+ const aborted2 = new Promise((resolve20) => {
221401
+ onAbort = resolve20;
221402
+ signal.addEventListener("abort", onAbort, { once: true });
221403
+ });
221404
+ try {
221405
+ await Promise.race([Promise.all(tracked).then(() => {
221406
+ return;
221407
+ }), aborted2]);
221408
+ } finally {
221409
+ signal.removeEventListener("abort", onAbort);
221410
+ }
221411
+ if (remaining === 0) {
221412
+ throwFirstSettledRejection(settled, partialResults);
221413
+ return;
221414
+ }
221415
+ let graceTimer;
221416
+ const graceExpired = new Promise((resolve20) => {
221417
+ graceTimer = setTimeout(resolve20, ABORT_GRACE_MS);
221418
+ });
221419
+ try {
221420
+ await Promise.race([
221421
+ Promise.all(tracked).then(() => {
221422
+ return;
221423
+ }),
221424
+ graceExpired
221425
+ ]);
221426
+ } finally {
221427
+ clearTimeout(graceTimer);
221428
+ }
221429
+ if (remaining === 0) {
221430
+ throwFirstSettledRejection(settled, partialResults);
221431
+ return;
221432
+ }
221433
+ throwFirstSettledRejection(settled.filter((result2) => result2 !== undefined), partialResults);
221434
+ const error5 = signal.reason instanceof Error ? signal.reason : new Error(INTERRUPTED_BY_USER);
221435
+ Object.assign(error5, { partialResults: partialResults() });
221436
+ throw error5;
221437
+ }
221438
+ function throwFirstSettledRejection(settled, partialResults) {
221427
221439
  for (const result2 of settled) {
221428
221440
  if (result2.status !== "rejected")
221429
221441
  continue;
@@ -221437,6 +221449,19 @@ async function executeApprovalBatchUnwatched(decisions, onChunk, options) {
221437
221449
  workingDirectory: options.workingDirectory
221438
221450
  })).contextId : undefined);
221439
221451
  const results = new Array(decisions.length).fill(null);
221452
+ let batchDiscarded = false;
221453
+ const guardedOnChunk = onChunk ? (chunk2) => {
221454
+ if (!batchDiscarded)
221455
+ onChunk(chunk2);
221456
+ } : undefined;
221457
+ const guardedOptions = options?.onStreamingOutput ? {
221458
+ ...options,
221459
+ onStreamingOutput: (toolCallId, chunk2, isStderr) => {
221460
+ if (!batchDiscarded) {
221461
+ options.onStreamingOutput?.(toolCallId, chunk2, isStderr);
221462
+ }
221463
+ }
221464
+ } : options;
221440
221465
  const parallelIndices = [];
221441
221466
  const writeToolsByResource = new Map;
221442
221467
  const denyIndices = [];
@@ -221472,11 +221497,11 @@ async function executeApprovalBatchUnwatched(decisions, onChunk, options) {
221472
221497
  const decision = decisions[i4];
221473
221498
  if (decision) {
221474
221499
  let crossedEffectBoundary = false;
221475
- const result2 = await executeSingleDecision(decision, onChunk, {
221476
- ...options,
221500
+ const result2 = await executeSingleDecision(decision, guardedOnChunk, {
221501
+ ...guardedOptions,
221477
221502
  toolContextId,
221478
221503
  beforeToolExecution: async (toolCallId) => {
221479
- await options?.beforeToolExecution?.(toolCallId);
221504
+ await guardedOptions?.beforeToolExecution?.(toolCallId);
221480
221505
  crossedEffectBoundary = true;
221481
221506
  }
221482
221507
  });
@@ -221486,15 +221511,20 @@ async function executeApprovalBatchUnwatched(decisions, onChunk, options) {
221486
221511
  }
221487
221512
  }
221488
221513
  };
221489
- await settleApprovalExecutionTasks([
221490
- ...parallelIndices.map(execute),
221491
- ...denyIndices.map(execute),
221492
- ...Array.from(writeToolsByResource.values()).map(async (indices) => {
221493
- for (const i4 of indices) {
221494
- await execute(i4);
221495
- }
221496
- })
221497
- ], () => results.filter((result2) => result2 !== null));
221514
+ try {
221515
+ await settleApprovalExecutionTasks([
221516
+ ...parallelIndices.map(execute),
221517
+ ...denyIndices.map(execute),
221518
+ ...Array.from(writeToolsByResource.values()).map(async (indices) => {
221519
+ for (const i4 of indices) {
221520
+ await execute(i4);
221521
+ }
221522
+ })
221523
+ ], () => results.filter((result2) => result2 !== null), { abortSignal: options?.abortSignal });
221524
+ } catch (error5) {
221525
+ batchDiscarded = true;
221526
+ throw error5;
221527
+ }
221498
221528
  return results.filter((r5) => r5 !== null);
221499
221529
  }
221500
221530
  async function executeAutoAllowedTools(autoAllowed, onChunk, options) {
@@ -221513,8 +221543,9 @@ async function executeAutoAllowedTools(autoAllowed, onChunk, options) {
221513
221543
  }
221514
221544
  }));
221515
221545
  }
221516
- var PARALLEL_SAFE_TOOLS, FILE_PATH_TOOLS, GLOBAL_LOCK_TOOLS;
221546
+ var ABORT_GRACE_MS = 500, PARALLEL_SAFE_TOOLS, FILE_PATH_TOOLS, GLOBAL_LOCK_TOOLS;
221517
221547
  var init_approval_execution = __esm(async () => {
221548
+ init_error();
221518
221549
  init_constants2();
221519
221550
  init_disk_space();
221520
221551
  init_runtime_context();
@@ -222512,14 +222543,23 @@ function scheduleCompletedSubagentCleanup(id2) {
222512
222543
  return;
222513
222544
  }
222514
222545
  clearCompletedSubagentCleanup(id2);
222546
+ armCompletedSubagentCleanup(id2);
222547
+ }
222548
+ function armCompletedSubagentCleanup(id2) {
222549
+ const agent = store2.agents.get(id2);
222550
+ if (!agent)
222551
+ return;
222515
222552
  const timer = setTimeout(() => {
222553
+ completedSubagentCleanupTimers.delete(id2);
222516
222554
  const current = store2.agents.get(id2);
222517
222555
  if (!current || current.status !== "completed" && current.status !== "error") {
222518
- completedSubagentCleanupTimers.delete(id2);
222556
+ return;
222557
+ }
222558
+ if (current.cancelPending) {
222559
+ armCompletedSubagentCleanup(id2);
222519
222560
  return;
222520
222561
  }
222521
222562
  store2.agents.delete(id2);
222522
- completedSubagentCleanupTimers.delete(id2);
222523
222563
  notifyListeners();
222524
222564
  }, agent.result !== undefined ? publishedResultRetentionMs : completedSubagentRetentionMs);
222525
222565
  unrefTimer2(timer);
@@ -222609,6 +222649,7 @@ function completeSubagent(id2, result2) {
222609
222649
  ...agent,
222610
222650
  status: result2.success ? "completed" : "error",
222611
222651
  error: result2.error,
222652
+ stopFailed: undefined,
222612
222653
  ...result2.result !== undefined ? { result: result2.result } : {},
222613
222654
  durationMs: Date.now() - agent.startTime,
222614
222655
  totalTokens: result2.totalTokens ?? agent.totalTokens,
@@ -222665,6 +222706,8 @@ function clearCompletedSubagents() {
222665
222706
  for (const [id2, agent] of store2.agents.entries()) {
222666
222707
  if (agent.result !== undefined)
222667
222708
  continue;
222709
+ if (agent.cancelPending)
222710
+ continue;
222668
222711
  if (agent.status === "completed" || agent.status === "error") {
222669
222712
  clearCompletedSubagentCleanup(id2);
222670
222713
  store2.agents.delete(id2);
@@ -222678,6 +222721,11 @@ function clearCompletedSubagents() {
222678
222721
  function clearSubagentsByIds(ids) {
222679
222722
  let removedAny = false;
222680
222723
  for (const id2 of ids) {
222724
+ const agent = store2.agents.get(id2);
222725
+ if (agent?.cancelPending) {
222726
+ updateSubagent(id2, { silent: true });
222727
+ continue;
222728
+ }
222681
222729
  clearCompletedSubagentCleanup(id2);
222682
222730
  removedAny = store2.agents.delete(id2) || removedAny;
222683
222731
  }
@@ -222693,15 +222741,28 @@ function hasActiveSubagents() {
222693
222741
  }
222694
222742
  return false;
222695
222743
  }
222696
- function interruptActiveSubagents(errorMessage) {
222744
+ function registerSubagentAbortHook(id2, hook) {
222745
+ subagentAbortHooks.set(id2, hook);
222746
+ }
222747
+ function clearSubagentAbortHook(id2) {
222748
+ subagentAbortHooks.delete(id2);
222749
+ }
222750
+ function interruptActiveSubagents(errorMessage, scope) {
222697
222751
  let anyInterrupted = false;
222698
222752
  for (const [id2, agent] of store2.agents.entries()) {
222699
222753
  if (agent.status === "pending" || agent.status === "running") {
222754
+ if (scope && !subagentMatchesScope(agent, scope))
222755
+ continue;
222756
+ const hook = subagentAbortHooks.get(id2);
222757
+ hook?.();
222758
+ subagentAbortHooks.delete(id2);
222700
222759
  const updatedAgent = {
222701
222760
  ...agent,
222702
222761
  status: "error",
222703
222762
  error: errorMessage,
222704
- durationMs: Date.now() - agent.startTime
222763
+ durationMs: Date.now() - agent.startTime,
222764
+ ...hook ? { cancelPending: true, stopAttempts: (agent.stopAttempts ?? 0) + 1 } : {},
222765
+ stopFailed: undefined
222705
222766
  };
222706
222767
  store2.agents.set(id2, updatedAgent);
222707
222768
  scheduleCompletedSubagentCleanup(id2);
@@ -222712,6 +222773,50 @@ function interruptActiveSubagents(errorMessage) {
222712
222773
  notifyListeners();
222713
222774
  }
222714
222775
  }
222776
+ function interruptStopFailedSubagents(errorMessage, scope) {
222777
+ const interrupted = [];
222778
+ for (const [id2, agent] of store2.agents.entries()) {
222779
+ if (!agent.stopFailed)
222780
+ continue;
222781
+ if (agent.status !== "pending" && agent.status !== "running")
222782
+ continue;
222783
+ if (scope && !subagentMatchesScope(agent, scope))
222784
+ continue;
222785
+ const hook = subagentAbortHooks.get(id2);
222786
+ hook?.();
222787
+ subagentAbortHooks.delete(id2);
222788
+ const updatedAgent = {
222789
+ ...agent,
222790
+ status: "error",
222791
+ error: errorMessage,
222792
+ durationMs: Date.now() - agent.startTime,
222793
+ ...hook ? { cancelPending: true, stopAttempts: (agent.stopAttempts ?? 0) + 1 } : {},
222794
+ stopFailed: undefined
222795
+ };
222796
+ store2.agents.set(id2, updatedAgent);
222797
+ scheduleCompletedSubagentCleanup(id2);
222798
+ interrupted.push(id2);
222799
+ }
222800
+ if (interrupted.length > 0) {
222801
+ notifyListeners();
222802
+ }
222803
+ return interrupted;
222804
+ }
222805
+ function subagentMatchesScope(agent, scope) {
222806
+ return subagentScopeMatches(agent.parentAgentId, agent.parentConversationId, scope);
222807
+ }
222808
+ function hasStopFailedSubagents(scope) {
222809
+ for (const agent of store2.agents.values()) {
222810
+ if (!agent.stopFailed)
222811
+ continue;
222812
+ if (agent.status !== "pending" && agent.status !== "running")
222813
+ continue;
222814
+ if (scope && !subagentMatchesScope(agent, scope))
222815
+ continue;
222816
+ return true;
222817
+ }
222818
+ return false;
222819
+ }
222715
222820
  function subscribe2(listener) {
222716
222821
  store2.listeners.add(listener);
222717
222822
  return () => {
@@ -222732,7 +222837,7 @@ function emitStreamEvent(subagentId, event) {
222732
222837
  listener(subagentId, event);
222733
222838
  }
222734
222839
  }
222735
- var store2, cachedSnapshot, cachedLifecycleItems, DEFAULT_COMPLETED_SUBAGENT_RETENTION_MS = 30000, completedSubagentRetentionMs, DEFAULT_PUBLISHED_RESULT_RETENTION_MS, publishedResultRetentionMs, completedSubagentCleanupTimers, subagentCounter = 0, streamEventListeners;
222840
+ var store2, cachedSnapshot, cachedLifecycleItems, DEFAULT_COMPLETED_SUBAGENT_RETENTION_MS = 30000, completedSubagentRetentionMs, DEFAULT_PUBLISHED_RESULT_RETENTION_MS, publishedResultRetentionMs, completedSubagentCleanupTimers, subagentCounter = 0, subagentAbortHooks, streamEventListeners;
222736
222841
  var init_subagent_state = __esm(() => {
222737
222842
  store2 = {
222738
222843
  agents: new Map,
@@ -222748,6 +222853,7 @@ var init_subagent_state = __esm(() => {
222748
222853
  DEFAULT_PUBLISHED_RESULT_RETENTION_MS = 30 * 60000;
222749
222854
  publishedResultRetentionMs = DEFAULT_PUBLISHED_RESULT_RETENTION_MS;
222750
222855
  completedSubagentCleanupTimers = new Map;
222856
+ subagentAbortHooks = new Map;
222751
222857
  streamEventListeners = new Set;
222752
222858
  });
222753
222859
 
@@ -223266,10 +223372,37 @@ var init_tool_registry = __esm(() => {
223266
223372
  });
223267
223373
 
223268
223374
  // src/tools/external-tool-background.ts
223375
+ function interruptedExternalToolResult(toolName2) {
223376
+ return {
223377
+ status: "error",
223378
+ toolReturn: `External tool call interrupted by user: ${toolName2}. The remote tool may still finish; its outcome is unknown. Check the destination before retrying a write.`
223379
+ };
223380
+ }
223381
+ function raceExternalOperationAbort(operation, signal, toolName2) {
223382
+ if (signal.aborted) {
223383
+ return Promise.resolve(interruptedExternalToolResult(toolName2));
223384
+ }
223385
+ return new Promise((resolve21, reject2) => {
223386
+ const cleanup = () => signal.removeEventListener("abort", onAbort);
223387
+ const onAbort = () => {
223388
+ cleanup();
223389
+ resolve21(interruptedExternalToolResult(toolName2));
223390
+ };
223391
+ signal.addEventListener("abort", onAbort);
223392
+ operation.then((result2) => {
223393
+ cleanup();
223394
+ resolve21(result2);
223395
+ }, (error5) => {
223396
+ cleanup();
223397
+ reject2(error5);
223398
+ });
223399
+ });
223400
+ }
223269
223401
  function notificationText(toolReturn) {
223270
223402
  return scrubAmbientSecrets(String(toolReturn));
223271
223403
  }
223272
223404
  async function autoBackgroundExternalTool(toolName2, tool, operation, options) {
223405
+ const signal = options?.signal;
223273
223406
  const runtime = options?.runtimeScope;
223274
223407
  const scope = options?.scope ?? (runtime?.agentId && runtime.conversationId ? {
223275
223408
  agentId: runtime.agentId,
@@ -223278,24 +223411,36 @@ async function autoBackgroundExternalTool(toolName2, tool, operation, options) {
223278
223411
  } : undefined);
223279
223412
  const canBackground = () => typeof options?.canBackground === "function" ? options.canBackground() : options?.canBackground !== false;
223280
223413
  if (tool?.autoBackground !== true || !canBackground() || !scope || !options?.enqueue && !isQueueBridgeConnected())
223281
- return operation;
223414
+ return signal ? raceExternalOperationAbort(operation, signal, toolName2) : operation;
223282
223415
  const startedAt = Date.now();
223283
223416
  const settled = operation.then((result2) => ({ kind: "result", result: result2 }), (error5) => ({ kind: "error", error: error5 }));
223284
223417
  let timer;
223418
+ let onAbort;
223419
+ const abortSettled = signal ? new Promise((resolve21) => {
223420
+ onAbort = () => resolve21({ kind: "aborted" });
223421
+ signal?.addEventListener("abort", onAbort);
223422
+ }) : undefined;
223285
223423
  const first = await Promise.race([
223286
223424
  settled,
223287
223425
  new Promise((resolve21) => {
223288
223426
  timer = setTimeout(() => resolve21({ kind: "yield" }), options?.yieldMs ?? DEFAULT_EXTERNAL_TOOL_YIELD_MS);
223289
- })
223427
+ }),
223428
+ ...abortSettled ? [abortSettled] : []
223290
223429
  ]);
223291
223430
  if (timer !== undefined)
223292
223431
  clearTimeout(timer);
223432
+ if (onAbort !== undefined && signal) {
223433
+ signal.removeEventListener("abort", onAbort);
223434
+ }
223435
+ if (first.kind === "aborted")
223436
+ return interruptedExternalToolResult(toolName2);
223293
223437
  if (first.kind === "result")
223294
223438
  return first.result;
223295
223439
  if (first.kind === "error")
223296
223440
  throw first.error;
223297
- if (!canBackground())
223298
- return operation;
223441
+ if (!canBackground()) {
223442
+ return signal ? raceExternalOperationAbort(operation, signal, toolName2) : operation;
223443
+ }
223299
223444
  const taskId = `external_${crypto.randomUUID()}`;
223300
223445
  const enqueue = options?.enqueue ?? addToMessageQueue;
223301
223446
  settled.then(async (outcome) => {
@@ -223345,14 +223490,15 @@ function hasActiveToolEndHandlers(events) {
223345
223490
  return false;
223346
223491
  return events.hasHandlers?.("tool_end") ?? true;
223347
223492
  }
223348
- function listenerExternalToolBackgroundOptions(tool, events, runtimeScope) {
223493
+ function listenerExternalToolBackgroundOptions(tool, events, runtimeScope, signal) {
223349
223494
  const registration = tool ? listenerRegistrations.get(tool) : undefined;
223350
223495
  const notificationGuard = registration?.createNotificationGuard();
223351
223496
  return {
223352
223497
  canBackground: () => notificationGuard?.isCurrent() === true && !hasActiveToolEndHandlers(events),
223353
223498
  ...registration?.yieldMs !== undefined ? { yieldMs: registration.yieldMs } : {},
223354
223499
  ...notificationGuard ? { shouldEnqueue: notificationGuard.waitUntilCurrent } : {},
223355
- runtimeScope
223500
+ runtimeScope,
223501
+ ...signal ? { signal } : {}
223356
223502
  };
223357
223503
  }
223358
223504
  var listenerRegistrations;
@@ -229130,7 +229276,6 @@ var LOCAL_AGENT_EXCLUDED_BUNDLED_SKILLS, PROJECT_SKILLS_DIR, SKILLS_DIR = ".skil
229130
229276
  var init_skills5 = __esm(() => {
229131
229277
  init_skill_sources();
229132
229278
  LOCAL_AGENT_EXCLUDED_BUNDLED_SKILLS = new Set([
229133
- "curating-memory-palace",
229134
229279
  "image-generation",
229135
229280
  "managing-agent-webhooks",
229136
229281
  "managing-shared-memory",
@@ -229651,7 +229796,7 @@ async function buildClientSkillsPayload(options = {}) {
229651
229796
  path: skill.path,
229652
229797
  source: skill.source
229653
229798
  })),
229654
- skillPathById: Object.fromEntries(sortedSkills.filter((skill) => typeof skill.path === "string" && skill.path.length > 0).map((skill) => [skill.id, skill.path])),
229799
+ skillPathById: Object.fromEntries(discovery.skills.filter((skill) => typeof skill.path === "string" && skill.path.length > 0).map((skill) => [skill.id, skill.path])),
229655
229800
  errors: errors6
229656
229801
  };
229657
229802
  if (useCache && generationBeforeDiscovery === getCacheGeneration() && checkoutGenerationBeforeDiscovery === getCheckoutGeneration()) {
@@ -230280,7 +230425,7 @@ function getExternalToolsAsClientTools() {
230280
230425
  parameters: tool.parameters
230281
230426
  }));
230282
230427
  }
230283
- async function executeExternalTool(toolCallId, toolName2, input, executorOverride, toolDefinition) {
230428
+ async function executeExternalTool(toolCallId, toolName2, input, executorOverride, toolDefinition, signal) {
230284
230429
  const executor = executorOverride ?? getExternalToolExecutor();
230285
230430
  if (!executor) {
230286
230431
  return {
@@ -230292,7 +230437,7 @@ async function executeExternalTool(toolCallId, toolName2, input, executorOverrid
230292
230437
  let success = false;
230293
230438
  try {
230294
230439
  const tool = toolDefinition ?? getExternalToolDefinition(toolName2);
230295
- const result2 = await executor(toolCallId, toolName2, input, tool ? { tool } : undefined);
230440
+ const result2 = await executor(toolCallId, toolName2, input, tool ? { tool, signal } : undefined);
230296
230441
  success = !result2.isError;
230297
230442
  return {
230298
230443
  toolReturn: clampToolReturnContent(normalizeExternalToolResultContent(result2.content), toolName2),
@@ -231059,7 +231204,7 @@ async function executeToolInner(name, args, options) {
231059
231204
  if (permissionDecision2?.decision !== undefined && permissionDecision2.decision !== "allow") {
231060
231205
  return createModPermissionToolResult(permissionDecision2);
231061
231206
  }
231062
- return runWithRuntimeContext(executionScope, () => autoBackgroundExternalTool(name, externalTool, executeExternalTool(options?.toolCallId ?? `ext-${Date.now()}`, name, eventArgs2, externalTool?.executor ?? activeExternalExecutor, externalTool), listenerExternalToolBackgroundOptions(externalTool, modEvents, executionScope)));
231207
+ return runWithRuntimeContext(executionScope, () => autoBackgroundExternalTool(name, externalTool, executeExternalTool(options?.toolCallId ?? `ext-${Date.now()}`, name, eventArgs2, externalTool?.executor ?? activeExternalExecutor, externalTool, options?.signal), listenerExternalToolBackgroundOptions(externalTool, modEvents, executionScope, options?.signal)));
231063
231208
  }
231064
231209
  const internalName = resolveInternalToolName(name, activeRegistry);
231065
231210
  const tool = internalName ? activeRegistry.get(internalName) : undefined;
@@ -232521,12 +232666,12 @@ async function runCall(parsed, deps, agentId, stdout) {
232521
232666
  await printJson(stdout, result2);
232522
232667
  return result2.isError === true ? 2 : 0;
232523
232668
  }
232524
- async function callCatalogTool(catalog, deps, agentId, toolName2, args) {
232669
+ async function callCatalogTool(catalog, deps, agentId, toolName2, args, options) {
232525
232670
  const tool = catalog.tools.find((candidate) => candidate.schema.name === toolName2);
232526
232671
  if (!tool) {
232527
232672
  throw new McpCliError("tool_not_found", `MCP tool '${toolName2}' is not available`);
232528
232673
  }
232529
- return tool.target.kind === "client" ? await tool.target.connection.callTool(tool.target.rawName, args) : mcpToolResultFromServer(await runUnifiedMcpTool({
232674
+ return tool.target.kind === "client" ? await tool.target.connection.callTool(tool.target.rawName, args, options) : mcpToolResultFromServer(await runUnifiedMcpTool({
232530
232675
  client: await getServerClient(deps),
232531
232676
  agentId,
232532
232677
  mcpServerId: tool.target.serverId,
@@ -232555,7 +232700,7 @@ function createAgentMcpSession(agentId, deps = {}) {
232555
232700
  return catalog;
232556
232701
  };
232557
232702
  return {
232558
- callTool: async (toolName2, args) => callCatalogTool(await load(), deps, agentId, toolName2, args),
232703
+ callTool: async (toolName2, args, options) => callCatalogTool(await load(), deps, agentId, toolName2, args, options),
232559
232704
  listTools: async () => (await load()).tools.map((tool) => tool.schema),
232560
232705
  close: async () => {
232561
232706
  closed = true;
@@ -232712,7 +232857,7 @@ async function createScriptToolScope(parentAgentId, signal) {
232712
232857
  isError: result2.status === "error"
232713
232858
  };
232714
232859
  }
232715
- return (await mcp()).callTool(toolName2, toolArgs);
232860
+ return (await mcp()).callTool(toolName2, toolArgs, signal ? { signal } : {});
232716
232861
  },
232717
232862
  cleanup: async () => {
232718
232863
  await mcpSession?.then((session3) => session3.close()).catch(() => {
@@ -233255,6 +233400,9 @@ async function executeScript(spawner, options, meta2) {
233255
233400
  if (opts.maxToolCalls !== undefined && (!Number.isSafeInteger(opts.maxToolCalls) || opts.maxToolCalls <= 0)) {
233256
233401
  throw new Error("agent() maxToolCalls must be a positive safe integer.");
233257
233402
  }
233403
+ if (opts.skills !== undefined && (!Array.isArray(opts.skills) || !opts.skills.every((id2) => typeof id2 === "string" && id2.trim()))) {
233404
+ throw new Error("agent() skills must be an array of skill names.");
233405
+ }
233258
233406
  const callIndex = callCounter++;
233259
233407
  const label = opts.label ?? defaultLabel(prompt);
233260
233408
  const phase2 = opts.phase ?? currentPhase;
@@ -240001,6 +240149,9 @@ var init_monitor_event_stream = __esm(() => {
240001
240149
  // src/tools/impl/monitor.ts
240002
240150
  import { appendFileSync as appendFileSync7, readFileSync as readFileSync35, writeFileSync as writeFileSync27 } from "node:fs";
240003
240151
  import { WebSocket as WebSocket4 } from "ws";
240152
+ function getMonitorTimeoutEvent(timeoutMs) {
240153
+ return isManagedCloudRuntime() && timeoutMs === MONITOR_MAX_TIMEOUT_MS ? "Monitor stopped at the 1-hour Cloud sandbox limit. Cloud monitors have a hard 1-hour limit to avoid keeping sandbox resources unnecessarily running. For later checks, schedule a Wake or cron, which does not keep the sandbox running between turns, and combine it with shorter monitors when needed." : "[Monitor timed out — re-arm if needed.]";
240154
+ }
240004
240155
  function buildMonitorResult(taskId, timeoutMs, persistent, outputFile) {
240005
240156
  const lifetime = persistent ? "persistent — runs until TaskStop or session end" : `timeout ${timeoutMs}ms`;
240006
240157
  return {
@@ -240110,8 +240261,8 @@ function normalizeMonitorArgs(args) {
240110
240261
  if (typeof normalized.description !== "string") {
240111
240262
  throw new Error("Monitor description is required");
240112
240263
  }
240113
- if (typeof normalized.timeout_ms !== "number" || !Number.isFinite(normalized.timeout_ms) || normalized.timeout_ms < MIN_TIMEOUT_MS || !normalized.persistent && normalized.timeout_ms > MAX_TIMEOUT_MS) {
240114
- throw new Error(`Monitor timeout_ms must be between ${MIN_TIMEOUT_MS} and ${MAX_TIMEOUT_MS}`);
240264
+ if (typeof normalized.timeout_ms !== "number" || !Number.isFinite(normalized.timeout_ms) || normalized.timeout_ms < MIN_TIMEOUT_MS || !normalized.persistent && normalized.timeout_ms > MONITOR_MAX_TIMEOUT_MS) {
240265
+ throw new Error(`Monitor timeout_ms must be between ${MIN_TIMEOUT_MS} and ${MONITOR_MAX_TIMEOUT_MS}`);
240115
240266
  }
240116
240267
  if (typeof normalized.persistent !== "boolean") {
240117
240268
  throw new Error("Monitor persistent must be a boolean");
@@ -240143,6 +240294,10 @@ function normalizeMonitorArgs(args) {
240143
240294
  }
240144
240295
  validateWebSocketSource(normalized.ws);
240145
240296
  }
240297
+ if (isManagedCloudRuntime() && normalized.persistent) {
240298
+ normalized.persistent = false;
240299
+ normalized.timeout_ms = MONITOR_MAX_TIMEOUT_MS;
240300
+ }
240146
240301
  return normalized;
240147
240302
  }
240148
240303
  function webSocketDisplayUrl(sourceUrl) {
@@ -240375,7 +240530,7 @@ function startCommandMonitor(args) {
240375
240530
  `);
240376
240531
  queueMonitorEvent({
240377
240532
  taskId,
240378
- event: "[Monitor timed out — re-arm if needed.]",
240533
+ event: getMonitorTimeoutEvent(args.timeout_ms),
240379
240534
  scope,
240380
240535
  actingUserId,
240381
240536
  secrets: secrets2
@@ -240557,7 +240712,7 @@ function startWebSocketMonitor(args) {
240557
240712
  return;
240558
240713
  queueMonitorEvent({
240559
240714
  taskId,
240560
- event: "[Monitor timed out — re-arm if needed.]",
240715
+ event: getMonitorTimeoutEvent(args.timeout_ms),
240561
240716
  scope,
240562
240717
  actingUserId,
240563
240718
  secrets: secrets2
@@ -240620,7 +240775,7 @@ function installMonitorProcessExitCleanup() {
240620
240775
  monitorProcessExitCleanupInstalled = true;
240621
240776
  process.once("exit", stopMonitorsForProcessExit);
240622
240777
  }
240623
- var MIN_TIMEOUT_MS = 1000, MAX_TIMEOUT_MS = 3600000, DEFAULT_TIMEOUT_MS4 = 300000, MONITOR_OUTPUT_FILE_BYTES, WEBSOCKET_MAX_PAYLOAD_BYTES, WEBSOCKET_PROTOCOL_PATTERN, monitorProcessExitCleanupInstalled = false;
240778
+ var MIN_TIMEOUT_MS = 1000, MONITOR_MAX_TIMEOUT_MS = 3600000, DEFAULT_TIMEOUT_MS4 = 300000, MONITOR_OUTPUT_FILE_BYTES, WEBSOCKET_MAX_PAYLOAD_BYTES, WEBSOCKET_PROTOCOL_PATTERN, monitorProcessExitCleanupInstalled = false;
240624
240779
  var init_monitor = __esm(async () => {
240625
240780
  init_strip_ansi();
240626
240781
  init_runtime_context();
@@ -241429,6 +241584,12 @@ async function waitForShutdownRetry(delayMs, signal) {
241429
241584
  onAbort();
241430
241585
  });
241431
241586
  }
241587
+ function getEnqueueAgentId(receipt) {
241588
+ const id2 = receipt.agent_id ?? receipt.parent_agent_id;
241589
+ if (!id2)
241590
+ throw new Error("Queued conversation has no agent or parent agent.");
241591
+ return id2;
241592
+ }
241432
241593
  async function enqueueConversationMessage(input, signal, request = apiRequest) {
241433
241594
  const body3 = {
241434
241595
  agent_id: input.agentId,
@@ -241543,14 +241704,14 @@ function assistantText(messages) {
241543
241704
  return text2.trim() ? text2 : null;
241544
241705
  }
241545
241706
  function noReplyMessage(receipt) {
241546
- return `Remote task finished. Its reply was not collected. Read the conversation with: letta messages list --agent ${receipt.agent_id} --conversation ${receipt.conversation_id}. Do not launch the task again to retrieve its result.`;
241707
+ return `Remote task finished. Its reply was not collected. Read the conversation with: letta messages list --agent ${getEnqueueAgentId(receipt)} --conversation ${receipt.conversation_id}. Do not launch the task again to retrieve its result.`;
241547
241708
  }
241548
241709
  async function waitForAcceptedSuperRun(receipt, signal, deps) {
241549
241710
  const pollMs = deps.pollMs ?? 1000;
241550
241711
  const sleep11 = deps.sleep ?? ((ms, waitSignal) => delay3(ms, undefined, { signal: waitSignal }));
241551
241712
  while (true) {
241552
241713
  signal.throwIfAborted();
241553
- const exact = await retryRead(() => deps.exact(receipt.agent_id, receipt.super_run_id, AbortSignal.any([signal, AbortSignal.timeout(30000)])), signal, sleep11, `Cloud status read for accepted Super Run ${receipt.super_run_id} failed`);
241714
+ const exact = await retryRead(() => deps.exact(getEnqueueAgentId(receipt), receipt.super_run_id, AbortSignal.any([signal, AbortSignal.timeout(30000)])), signal, sleep11, `Cloud status read for accepted Super Run ${receipt.super_run_id} failed`);
241554
241715
  if (exact.id !== receipt.super_run_id) {
241555
241716
  throw new RemoteExecutionFailed(`Exact Super Run read returned ${exact.id} for ${receipt.super_run_id}.`);
241556
241717
  }
@@ -241588,6 +241749,7 @@ async function waitForAcceptedSuperRun(receipt, signal, deps) {
241588
241749
  var RemoteExecutionFailed;
241589
241750
  var init_headless_super_run_wait = __esm(() => {
241590
241751
  init_letta_client();
241752
+ init_conversation_enqueue();
241591
241753
  init_request();
241592
241754
  RemoteExecutionFailed = class RemoteExecutionFailed extends Error {
241593
241755
  };
@@ -241632,10 +241794,10 @@ function trackChildSend(input) {
241632
241794
  const subagentId = generateSubagentId();
241633
241795
  registerSubagent(subagentId, child.type, child.name, undefined, true, false, parentScope, input.prompt, false);
241634
241796
  updateSubagent(subagentId, {
241635
- agentId: receipt.agent_id,
241797
+ agentId: receipt.agent_id ?? undefined,
241636
241798
  conversationId: receipt.conversation_id,
241637
241799
  superRunId: receipt.super_run_id,
241638
- agentURL: buildAgentReference(receipt.agent_id, {
241800
+ agentURL: buildAgentReference(getEnqueueAgentId(receipt), {
241639
241801
  conversationId: receipt.conversation_id
241640
241802
  })
241641
241803
  });
@@ -242398,7 +242560,7 @@ class CodexAppServerClient {
242398
242560
  await this.request("initialize", {
242399
242561
  clientInfo: {
242400
242562
  name: "letta-code",
242401
- version: "0.34.17"
242563
+ version: "0.34.19"
242402
242564
  },
242403
242565
  capabilities: null
242404
242566
  });
@@ -242817,6 +242979,22 @@ var init_codex_app_server = __esm(() => {
242817
242979
  DEFAULT_IDLE_TIMEOUT_MS = 5 * 60000;
242818
242980
  });
242819
242981
 
242982
+ // src/agent/parent-memory-note.ts
242983
+ import { statSync as statSync18 } from "node:fs";
242984
+ function buildParentMemoryNote(parentAgentId) {
242985
+ const directory = getScopedMemoryFilesystemRoot(parentAgentId);
242986
+ try {
242987
+ if (!statSync18(directory).isDirectory())
242988
+ return;
242989
+ } catch {
242990
+ return;
242991
+ }
242992
+ return `Your parent agent (${parentAgentId}) keeps its memory as Markdown files at \`${directory}\`; attached shared-memory repos sit next to it. If the task depends on context about the user, project, people or conventions, check there first: start with \`MEMORY.md\` and the directory indexes, then open only the files that look relevant. Don't edit or commit there unless asked.`;
242993
+ }
242994
+ var init_parent_memory_note = __esm(() => {
242995
+ init_memory_filesystem2();
242996
+ });
242997
+
242820
242998
  // src/cli/helpers/agent-info.ts
242821
242999
  function getRelativeTime(dateStr) {
242822
243000
  const date5 = new Date(dateStr);
@@ -242860,10 +243038,13 @@ function buildAgentInfo(options) {
242860
243038
  })();
242861
243039
  const memoryDirLine = showMemoryDir ? `
242862
243040
  - **Memory directory (also stored in \`MEMORY_DIR\` env var)**: \`${getScopedMemoryFilesystemRoot(agentInfo.id)}\`` : "";
243041
+ const parentMemoryNote = options.parentAgentId && options.parentAgentId !== agentInfo.id ? buildParentMemoryNote(options.parentAgentId) : undefined;
243042
+ const parentMemoryLine = parentMemoryNote ? `
243043
+ - ${parentMemoryNote}` : "";
242863
243044
  const convLine = conversationId ? `
242864
243045
  - **Conversation ID (also stored in \`CONVERSATION_ID\` env var)**: ${conversationId}` : "";
242865
243046
  return `${SYSTEM_REMINDER_OPEN} This is an automated message providing information about you.
242866
- - **Agent ID (also stored in \`AGENT_ID\` env var)**: ${agentInfo.id}${convLine}${memoryDirLine}
243047
+ - **Agent ID (also stored in \`AGENT_ID\` env var)**: ${agentInfo.id}${convLine}${memoryDirLine}${parentMemoryLine}
242867
243048
  - **Agent name**: ${agentInfo.name || "(unnamed)"} (the user can change this with /rename)
242868
243049
  - **Agent description**: ${agentInfo.description || "(no description)"} (the user can change this with /description)
242869
243050
  - **Last message**: ${lastRunInfo}
@@ -242874,6 +243055,7 @@ ${SYSTEM_REMINDER_CLOSE}`;
242874
243055
  }
242875
243056
  var init_agent_info = __esm(() => {
242876
243057
  init_memory_filesystem2();
243058
+ init_parent_memory_note();
242877
243059
  init_paths();
242878
243060
  init_constants2();
242879
243061
  init_settings_manager();
@@ -243343,7 +243525,14 @@ async function buildAgentInfoReminder(context3) {
243343
243525
  if (!context3.systemInfoReminderEnabled || context3.state.hasSentAgentInfo) {
243344
243526
  return null;
243345
243527
  }
243528
+ let parentAgentId;
243529
+ try {
243530
+ const scopeAgentId = await getMcpScopeAgentId(context3.agent.id);
243531
+ if (scopeAgentId !== context3.agent.id)
243532
+ parentAgentId = scopeAgentId;
243533
+ } catch {}
243346
243534
  const reminder = buildAgentInfo({
243535
+ parentAgentId,
243347
243536
  agentInfo: {
243348
243537
  id: context3.agent.id,
243349
243538
  name: context3.agent.name,
@@ -244950,6 +245139,18 @@ async function resolveExternalCodingAgentSecretEnv(parentAgentId, lookupParent)
244950
245139
  return { ...inherited, ...loadSecrets2(parentAgentId) };
244951
245140
  }
244952
245141
  async function runExternalCodingAgent(options, deps = {}) {
245142
+ const parentMemoryNote = buildParentMemoryNote(options.parentAgentId);
245143
+ options = {
245144
+ ...options,
245145
+ mcpReminder: [
245146
+ parentMemoryNote ? `${SYSTEM_REMINDER_OPEN}
245147
+ ${parentMemoryNote}
245148
+ ${SYSTEM_REMINDER_CLOSE}` : undefined,
245149
+ options.mcpReminder
245150
+ ].filter(Boolean).join(`
245151
+
245152
+ `) || undefined
245153
+ };
244953
245154
  const startedAt = Date.now();
244954
245155
  const secretEnv = await (deps.resolveSecretEnv ?? resolveExternalCodingAgentSecretEnv)(options.parentAgentId);
244955
245156
  const env6 = {
@@ -245116,8 +245317,10 @@ async function runExternalCodingAgent(options, deps = {}) {
245116
245317
  }
245117
245318
  var EXTERNAL_CODING_AGENT_TYPES, EXTERNAL_AGENT_ID_PREFIXES, NATIVE_SESSION_ID_PATTERN, MISSING_EXECUTABLE_PREFIX = "Required executable '", CODEX_NOT_READY_PREFIX = "Codex executable is not ready", CLAUDE_NOT_SIGNED_IN_PREFIX = "claude-code authentication is not ready";
245118
245319
  var init_external_coding_agent = __esm(() => {
245320
+ init_parent_memory_note();
245119
245321
  init_subagent_depth();
245120
245322
  init_subagent_process();
245323
+ init_constants2();
245121
245324
  init_mcp_scope();
245122
245325
  init_engine3();
245123
245326
  init_runtime_context();
@@ -246327,8 +246530,11 @@ async function createListenerClient(connectionId, scope) {
246327
246530
  });
246328
246531
  }
246329
246532
  async function cancelListenerInput(params) {
246533
+ const agentId = params.scope.agent_id ?? params.parentAgentId;
246534
+ if (!agentId)
246535
+ throw new Error("Listener cancellation requires an agent or parent agent.");
246330
246536
  const removed = await (params.dequeue ?? dequeueConversationMessage)({
246331
- agentId: params.scope.agent_id,
246537
+ agentId,
246332
246538
  conversationId: params.scope.conversation_id,
246333
246539
  clientMessageId: params.clientMessageId,
246334
246540
  actingUserId: params.scope.acting_user_id
@@ -246352,7 +246558,7 @@ async function cancelListenerInput(params) {
246352
246558
  }
246353
246559
  async function cancelAcceptedListenerInput(receipt) {
246354
246560
  const removed = await dequeueConversationMessage({
246355
- agentId: receipt.agent_id,
246561
+ agentId: getEnqueueAgentId(receipt),
246356
246562
  conversationId: receipt.conversation_id,
246357
246563
  clientMessageId: receipt.client_message_id,
246358
246564
  actingUserId: receipt.acting_user_id
@@ -246376,6 +246582,7 @@ async function cancelAcceptedListenerInput(receipt) {
246376
246582
  return await cancelListenerInput({
246377
246583
  client,
246378
246584
  scope,
246585
+ parentAgentId: receipt.parent_agent_id,
246379
246586
  clientMessageId: receipt.client_message_id,
246380
246587
  dequeue: async () => removed,
246381
246588
  readState: () => ({ loop, cancelled: false })
@@ -246386,6 +246593,9 @@ async function cancelAcceptedListenerInput(receipt) {
246386
246593
  }
246387
246594
  }
246388
246595
  async function launchListenerConversation(params, deps = {}) {
246596
+ const agentId = params.scope.agent_id ?? params.parentAgentId;
246597
+ if (!agentId)
246598
+ throw new Error("Listener delivery requires an agent or parent agent.");
246389
246599
  const client = deps.client ?? await createListenerClient(params.connectionId, params.scope);
246390
246600
  const clientMessageId = params.clientMessageId ?? randomUUID37();
246391
246601
  const runIds = new Set;
@@ -246472,7 +246682,7 @@ async function launchListenerConversation(params, deps = {}) {
246472
246682
  if (interrupted)
246473
246683
  throw new Error("Launch cancelled before input was sent");
246474
246684
  const accepted = await (deps.enqueue ?? enqueueConversationMessage)({
246475
- agentId: params.scope.agent_id,
246685
+ agentId,
246476
246686
  conversationId: params.scope.conversation_id,
246477
246687
  clientMessageId,
246478
246688
  content: params.content,
@@ -246482,7 +246692,12 @@ async function launchListenerConversation(params, deps = {}) {
246482
246692
  if (params.noWait)
246483
246693
  return {
246484
246694
  status: "queued",
246485
- receipt: { ...accepted, connection_id: params.connectionId }
246695
+ receipt: {
246696
+ ...accepted,
246697
+ connection_id: params.connectionId,
246698
+ agent_id: params.scope.agent_id,
246699
+ ...params.scope.agent_id === null ? { parent_agent_id: agentId } : {}
246700
+ }
246486
246701
  };
246487
246702
  while (true) {
246488
246703
  if (disconnected)
@@ -246496,6 +246711,7 @@ async function launchListenerConversation(params, deps = {}) {
246496
246711
  if (await cancelListenerInput({
246497
246712
  client,
246498
246713
  scope: params.scope,
246714
+ parentAgentId: params.parentAgentId,
246499
246715
  clientMessageId,
246500
246716
  dequeue: deps.dequeue,
246501
246717
  readState: () => ({ loop, cancelled })
@@ -246615,7 +246831,7 @@ function handleToolReturnEvent(event, state2) {
246615
246831
  }
246616
246832
  }
246617
246833
  function isEnqueueReceipt(event) {
246618
- return event.status === "queued" && typeof event.agent_id === "string" && typeof event.conversation_id === "string" && typeof event.client_message_id === "string" && typeof event.super_run_id === "string" && typeof event.workflow_id === "string" && (event.acting_user_id === undefined || event.acting_user_id === null || typeof event.acting_user_id === "string");
246834
+ return event.status === "queued" && (typeof event.agent_id === "string" || event.agent_id === null && typeof event.parent_agent_id === "string") && (event.parent_agent_id === undefined || typeof event.parent_agent_id === "string") && typeof event.conversation_id === "string" && typeof event.client_message_id === "string" && typeof event.super_run_id === "string" && typeof event.workflow_id === "string" && (event.acting_user_id === undefined || event.acting_user_id === null || typeof event.acting_user_id === "string");
246619
246835
  }
246620
246836
  function getResultEnvelopeError(event) {
246621
246837
  return event.error || event.result || "Unknown error";
@@ -246626,6 +246842,7 @@ function handleResultEvent(event, state2, subagentId) {
246626
246842
  state2.enqueueReceipt = {
246627
246843
  status: "queued",
246628
246844
  agent_id: event.agent_id,
246845
+ ...event.parent_agent_id ? { parent_agent_id: event.parent_agent_id } : {},
246629
246846
  conversation_id: event.conversation_id,
246630
246847
  client_message_id: event.client_message_id,
246631
246848
  super_run_id: event.super_run_id,
@@ -246757,9 +246974,9 @@ var init_subagent_stream = __esm(() => {
246757
246974
  async function collectRemoteTurnResult(receipt, state2, subagentId, signal = new AbortController().signal) {
246758
246975
  const startedAt = Date.now();
246759
246976
  updateSubagent(subagentId, {
246760
- agentId: receipt.agent_id,
246977
+ agentId: receipt.agent_id ?? undefined,
246761
246978
  conversationId: receipt.conversation_id,
246762
- agentURL: buildAgentReference(receipt.agent_id, {
246979
+ agentURL: buildAgentReference(getEnqueueAgentId(receipt), {
246763
246980
  conversationId: receipt.conversation_id
246764
246981
  })
246765
246982
  });
@@ -246779,7 +246996,7 @@ async function collectRemoteTurnResult(receipt, state2, subagentId, signal = new
246779
246996
  }
246780
246997
  });
246781
246998
  return {
246782
- agentId: receipt.agent_id,
246999
+ agentId: receipt.agent_id ?? "",
246783
247000
  conversationId: receipt.conversation_id,
246784
247001
  report: reply.text,
246785
247002
  success: true,
@@ -246792,7 +247009,7 @@ async function collectRemoteTurnResult(receipt, state2, subagentId, signal = new
246792
247009
  detail = cancelled ? INTERRUPTED_BY_USER : `${INTERRUPTED_BY_USER} (could not confirm cancellation of remote Super Run ${receipt.super_run_id})`;
246793
247010
  }
246794
247011
  return {
246795
- agentId: receipt.agent_id,
247012
+ agentId: receipt.agent_id ?? "",
246796
247013
  conversationId: receipt.conversation_id,
246797
247014
  report: "",
246798
247015
  success: false,
@@ -248730,12 +248947,13 @@ async function launchRemoteExternalCodingAgent(params, deps) {
248730
248947
  durationMs: final.duration_ms || Date.now() - startedAt
248731
248948
  };
248732
248949
  } catch (error5) {
248950
+ const remoteWorkerStopped = signal?.reason === REMOTE_EXTERNAL_WORKER_STOPPED;
248733
248951
  return {
248734
248952
  agentId: parentScope.agentId,
248735
248953
  model,
248736
248954
  report: "",
248737
248955
  success: false,
248738
- error: error5 instanceof RemoteWorkerLostError && !signal?.aborted ? `The ${type3} worker on ${computer} is no longer listed: it may have finished while this session was disconnected (results are kept for 30 minutes), or the listener restarted. Check ${computer} or launch it again if the work is still needed.` : `${signal?.aborted ? "Stopped following this subagent" : errorMessage3(error5)}. The ${type3} worker may still be running on ${computer}.`,
248956
+ error: error5 instanceof RemoteWorkerLostError && !signal?.aborted ? `The ${type3} worker on ${computer} is no longer listed: it may have finished while this session was disconnected (results are kept for 30 minutes), or the listener restarted. Check ${computer} or launch it again if the work is still needed.` : remoteWorkerStopped ? `Stopped the ${type3} worker on ${computer}.` : `${signal?.aborted ? "Stopped following this subagent" : errorMessage3(error5)}. The ${type3} worker may still be running on ${computer}.`,
248739
248957
  durationMs: Date.now() - startedAt
248740
248958
  };
248741
248959
  } finally {
@@ -248880,7 +249098,7 @@ async function sendRemoteExternalCodingAgentMessage(params, deps = {}) {
248880
249098
  outputFile: tracked.outputFile
248881
249099
  };
248882
249100
  }
248883
- var DISPLAY_NAMES, RemoteWorkerLostError;
249101
+ var REMOTE_EXTERNAL_WORKER_STOPPED, DISPLAY_NAMES, RemoteWorkerLostError;
248884
249102
  var init_remote_external_coding_agent = __esm(() => {
248885
249103
  init_subagent_state();
248886
249104
  init_app_server_client();
@@ -248890,6 +249108,7 @@ var init_remote_external_coding_agent = __esm(() => {
248890
249108
  init_headless_environment_response();
248891
249109
  init_headless_listener_launch();
248892
249110
  init_external_coding_agent();
249111
+ REMOTE_EXTERNAL_WORKER_STOPPED = Symbol("remote external worker stopped");
248893
249112
  DISPLAY_NAMES = {
248894
249113
  "claude-code": "Claude Code",
248895
249114
  codex: "Codex",
@@ -248948,6 +249167,184 @@ var init_subagent_computer = __esm(() => {
248948
249167
  LOCAL_COMPUTER_SELECTORS = new Set(["local", "self"]);
248949
249168
  });
248950
249169
 
249170
+ // src/tools/impl/kill-bash.ts
249171
+ function killBackgroundProcess(shell_id) {
249172
+ const proc = backgroundProcesses.get(shell_id);
249173
+ const retainsEntry = proc?.kind === "monitor" || proc?.kind === "workflow";
249174
+ if (!proc || retainsEntry && proc.status !== "running") {
249175
+ return false;
249176
+ }
249177
+ const previousStatus = proc.status;
249178
+ const previousNotificationSuppression = proc.completionNotificationSuppressed;
249179
+ try {
249180
+ proc.completionNotificationSuppressed = true;
249181
+ if (retainsEntry) {
249182
+ proc.status = "failed";
249183
+ }
249184
+ proc.process.kill("SIGTERM");
249185
+ if (proc.kind === "monitor")
249186
+ cancelMonitorBackup(shell_id);
249187
+ clearBackgroundProcessCleanup(shell_id);
249188
+ if (retainsEntry) {
249189
+ scheduleBackgroundProcessCleanup(shell_id);
249190
+ notifyBackgroundProcessStateChanged(proc.runtimeScope);
249191
+ } else {
249192
+ backgroundProcesses.delete(shell_id);
249193
+ }
249194
+ return true;
249195
+ } catch {
249196
+ proc.status = previousStatus;
249197
+ proc.completionNotificationSuppressed = previousNotificationSuppression;
249198
+ return false;
249199
+ }
249200
+ }
249201
+ var init_kill_bash = __esm(async () => {
249202
+ init_process_manager();
249203
+ await init_monitor_backup();
249204
+ });
249205
+
249206
+ // src/tools/impl/task-stop.ts
249207
+ var exports_task_stop = {};
249208
+ __export(exports_task_stop, {
249209
+ task_stop: () => task_stop
249210
+ });
249211
+ async function cancelRemoteRun(subagentId) {
249212
+ const child = getSubagents().find((agent) => agent.id === subagentId);
249213
+ const target2 = child?.conversationId === "default" ? child.agentId : child?.conversationId;
249214
+ try {
249215
+ if (!target2)
249216
+ throw new Error("its conversation is not known yet");
249217
+ const results = await getBackend().cancelConversation(target2);
249218
+ const outcomes = Object.values(results);
249219
+ if (outcomes.length === 0 || outcomes.some((status) => status !== "cancelled")) {
249220
+ throw new Error("Cloud did not cancel every run");
249221
+ }
249222
+ return { killed: true, output: "Cancelled the remote run" };
249223
+ } catch (error5) {
249224
+ return {
249225
+ killed: false,
249226
+ output: `Couldn't cancel the remote run: ${getErrorMessage2(error5)}`
249227
+ };
249228
+ }
249229
+ }
249230
+ async function task_stop(args, deps = {}) {
249231
+ validateRequiredParams(args, ["task_id"], "TaskStop");
249232
+ const { task_id } = args;
249233
+ const task = backgroundTasks.get(task_id);
249234
+ if (task) {
249235
+ if (task.status === "running" && task.abortController) {
249236
+ const child = getSubagents().find((agent) => agent.id === task.subagentId);
249237
+ const external3 = parseExternalCodingAgentId(child?.agentId ?? undefined);
249238
+ let remote;
249239
+ let remoteExternalStopped = false;
249240
+ if (external3?.computer) {
249241
+ try {
249242
+ if (!task.runtimeScope)
249243
+ throw new Error("its parent conversation is not known");
249244
+ const result3 = await (deps.stopRemoteExternal ?? stopRemoteExternalCodingAgent)({
249245
+ agentId: child?.agentId,
249246
+ parentScope: {
249247
+ agentId: task.runtimeScope.agentId,
249248
+ conversationId: task.runtimeScope.conversationId,
249249
+ actingUserId: task.actingUserId
249250
+ }
249251
+ });
249252
+ remote = {
249253
+ killed: result3.stopped,
249254
+ output: result3.stopped ? "Stopped the remote external subagent" : "The remote external subagent was not running"
249255
+ };
249256
+ remoteExternalStopped = result3.stopped;
249257
+ } catch (error5) {
249258
+ remote = {
249259
+ killed: false,
249260
+ output: `Couldn't stop the remote external subagent: ${getErrorMessage2(error5)}`
249261
+ };
249262
+ }
249263
+ } else if (task.remote) {
249264
+ remote = await cancelRemoteRun(task.subagentId);
249265
+ }
249266
+ if (remote && !remote.killed)
249267
+ return remote;
249268
+ task.abortController.abort(remoteExternalStopped ? REMOTE_EXTERNAL_WORKER_STOPPED : undefined);
249269
+ task.error = "Aborted by user";
249270
+ await task.completion;
249271
+ task.status = "failed";
249272
+ scheduleBackgroundTaskCleanup(task_id);
249273
+ return remote ?? { killed: true };
249274
+ }
249275
+ return { killed: false };
249276
+ }
249277
+ return { killed: killBackgroundProcess(task_id) };
249278
+ }
249279
+ var init_task_stop = __esm(async () => {
249280
+ init_subagent_state();
249281
+ init_backend2();
249282
+ init_external_coding_agent();
249283
+ init_process_manager();
249284
+ init_remote_external_coding_agent();
249285
+ await init_kill_bash();
249286
+ });
249287
+
249288
+ // src/tools/impl/task-abort-hook.ts
249289
+ function buildBackgroundTaskAbortHook(taskId, abortController) {
249290
+ const bgTask = backgroundTasks.get(taskId);
249291
+ const remote = bgTask?.remote ?? false;
249292
+ const failSafeAbort = () => !remote && abortController.abort();
249293
+ const hook = () => {
249294
+ init_task_stop().then(() => exports_task_stop).then((m4) => m4.task_stop({ task_id: taskId })).then((result3) => {
249295
+ if (!result3.killed)
249296
+ failSafeAbort();
249297
+ settleInterruptedCancel(bgTask, remote, result3.killed, hook);
249298
+ }).catch(() => {
249299
+ failSafeAbort();
249300
+ settleInterruptedCancel(bgTask, remote, false, hook);
249301
+ });
249302
+ };
249303
+ return hook;
249304
+ }
249305
+ function settleInterruptedCancel(bgTask, remote, killed, hook) {
249306
+ if (!bgTask)
249307
+ return;
249308
+ const taskLive = bgTask.status === "running";
249309
+ if (remote && !killed && taskLive) {
249310
+ restoreInterruptedSubagentRow(bgTask.subagentId, hook);
249311
+ return;
249312
+ }
249313
+ const row = getSubagents().find((agent) => agent.id === bgTask.subagentId);
249314
+ updateSubagent(bgTask.subagentId, {
249315
+ cancelPending: undefined,
249316
+ stopFailed: undefined,
249317
+ stopOutcome: {
249318
+ attempt: row?.stopAttempts ?? 0,
249319
+ kind: killed ? "stopped" : "natural"
249320
+ }
249321
+ });
249322
+ }
249323
+ function restoreInterruptedSubagentRow(subagentId, hook) {
249324
+ const row = getSubagents().find((agent) => agent.id === subagentId);
249325
+ if (!row || row.status !== "error" || row.result !== undefined) {
249326
+ if (row) {
249327
+ updateSubagent(subagentId, {
249328
+ cancelPending: undefined,
249329
+ stopOutcome: { attempt: row.stopAttempts ?? 0, kind: "natural" }
249330
+ });
249331
+ }
249332
+ return;
249333
+ }
249334
+ updateSubagent(subagentId, {
249335
+ status: "running",
249336
+ error: undefined,
249337
+ cancelPending: undefined,
249338
+ stopFailed: true,
249339
+ stopOutcome: { attempt: row.stopAttempts ?? 0, kind: "failed" }
249340
+ });
249341
+ registerSubagentAbortHook(subagentId, hook);
249342
+ }
249343
+ var init_task_abort_hook = __esm(() => {
249344
+ init_subagent_state();
249345
+ init_process_manager();
249346
+ });
249347
+
248951
249348
  // src/tools/impl/task-transcript.ts
248952
249349
  function buildTaskResultHeader(subagentType, subagentId, result3, status) {
248953
249350
  return [
@@ -249066,6 +249463,7 @@ function spawnBackgroundSubagentTask(args) {
249066
249463
  actingUserId
249067
249464
  };
249068
249465
  backgroundTasks.set(taskId, bgTask);
249466
+ registerSubagentAbortHook(subagentId, buildBackgroundTaskAbortHook(taskId, abortController));
249069
249467
  writeTaskTranscriptStart(outputFile, description, subagentType);
249070
249468
  const parentAgentIdForSpawn = resolvedParentScope?.agentId;
249071
249469
  const workerMemoryDir = subagentType === "memory" && resolvedParentScope ? memoryScope?.primaryRoot ?? getScopedMemoryFilesystemRoot(resolvedParentScope.agentId) : undefined;
@@ -249097,6 +249495,7 @@ function spawnBackgroundSubagentTask(args) {
249097
249495
  const header = buildTaskResultHeader(subagentType, subagentId, result3, result3.success ? "success" : "error");
249098
249496
  writeTaskTranscriptResult(outputFile, result3, header, !!memoryTask);
249099
249497
  scheduleBackgroundTaskCleanup(taskId);
249498
+ clearSubagentAbortHook(subagentId);
249100
249499
  completeSubagentFn(subagentId, {
249101
249500
  success: result3.success,
249102
249501
  error: result3.error,
@@ -249164,6 +249563,7 @@ Error: ${result3.error || "Subagent execution failed"}`;
249164
249563
  [Task failed]
249165
249564
  `);
249166
249565
  scheduleBackgroundTaskCleanup(taskId);
249566
+ clearSubagentAbortHook(subagentId);
249167
249567
  completeSubagentFn(subagentId, { success: false, error: errorMessage4 });
249168
249568
  try {
249169
249569
  await onComplete?.({
@@ -249522,6 +249922,7 @@ var init_task = __esm(async () => {
249522
249922
  init_process_manager();
249523
249923
  init_remote_external_coding_agent();
249524
249924
  init_subagent_computer();
249925
+ init_task_abort_hook();
249525
249926
  init_task_transcript();
249526
249927
  init_truncation();
249527
249928
  init_background_link();
@@ -250414,118 +250815,6 @@ var init_task_list = __esm(() => {
250414
250815
  init_store();
250415
250816
  });
250416
250817
 
250417
- // src/tools/impl/kill-bash.ts
250418
- function killBackgroundProcess(shell_id) {
250419
- const proc = backgroundProcesses.get(shell_id);
250420
- const retainsEntry = proc?.kind === "monitor" || proc?.kind === "workflow";
250421
- if (!proc || retainsEntry && proc.status !== "running") {
250422
- return false;
250423
- }
250424
- const previousStatus = proc.status;
250425
- const previousNotificationSuppression = proc.completionNotificationSuppressed;
250426
- try {
250427
- proc.completionNotificationSuppressed = true;
250428
- if (retainsEntry) {
250429
- proc.status = "failed";
250430
- }
250431
- proc.process.kill("SIGTERM");
250432
- if (proc.kind === "monitor")
250433
- cancelMonitorBackup(shell_id);
250434
- clearBackgroundProcessCleanup(shell_id);
250435
- if (retainsEntry) {
250436
- scheduleBackgroundProcessCleanup(shell_id);
250437
- notifyBackgroundProcessStateChanged(proc.runtimeScope);
250438
- } else {
250439
- backgroundProcesses.delete(shell_id);
250440
- }
250441
- return true;
250442
- } catch {
250443
- proc.status = previousStatus;
250444
- proc.completionNotificationSuppressed = previousNotificationSuppression;
250445
- return false;
250446
- }
250447
- }
250448
- var init_kill_bash = __esm(async () => {
250449
- init_process_manager();
250450
- await init_monitor_backup();
250451
- });
250452
-
250453
- // src/tools/impl/task-stop.ts
250454
- async function cancelRemoteRun(subagentId) {
250455
- const child = getSubagents().find((agent) => agent.id === subagentId);
250456
- const target2 = child?.conversationId === "default" ? child.agentId : child?.conversationId;
250457
- try {
250458
- if (!target2)
250459
- throw new Error("its conversation is not known yet");
250460
- const results = await getBackend().cancelConversation(target2);
250461
- const outcomes = Object.values(results);
250462
- if (outcomes.length === 0 || outcomes.some((status) => status !== "cancelled")) {
250463
- throw new Error("Cloud did not cancel every run");
250464
- }
250465
- return { killed: true, output: "Cancelled the remote run" };
250466
- } catch (error5) {
250467
- return {
250468
- killed: false,
250469
- output: `Couldn't cancel the remote run: ${getErrorMessage2(error5)}`
250470
- };
250471
- }
250472
- }
250473
- async function task_stop(args, deps = {}) {
250474
- validateRequiredParams(args, ["task_id"], "TaskStop");
250475
- const { task_id } = args;
250476
- const task2 = backgroundTasks.get(task_id);
250477
- if (task2) {
250478
- if (task2.status === "running" && task2.abortController) {
250479
- const child = getSubagents().find((agent) => agent.id === task2.subagentId);
250480
- const external3 = parseExternalCodingAgentId(child?.agentId ?? undefined);
250481
- let remote;
250482
- if (external3?.computer) {
250483
- try {
250484
- if (!task2.runtimeScope)
250485
- throw new Error("its parent conversation is not known");
250486
- const result3 = await (deps.stopRemoteExternal ?? stopRemoteExternalCodingAgent)({
250487
- agentId: child?.agentId,
250488
- parentScope: {
250489
- agentId: task2.runtimeScope.agentId,
250490
- conversationId: task2.runtimeScope.conversationId,
250491
- actingUserId: task2.actingUserId
250492
- }
250493
- });
250494
- remote = {
250495
- killed: result3.stopped,
250496
- output: result3.stopped ? "Stopped the remote external subagent" : "The remote external subagent was not running"
250497
- };
250498
- } catch (error5) {
250499
- remote = {
250500
- killed: false,
250501
- output: `Couldn't stop the remote external subagent: ${getErrorMessage2(error5)}`
250502
- };
250503
- }
250504
- } else if (task2.remote) {
250505
- remote = await cancelRemoteRun(task2.subagentId);
250506
- }
250507
- if (remote && !remote.killed)
250508
- return remote;
250509
- task2.abortController.abort();
250510
- task2.error = "Aborted by user";
250511
- await task2.completion;
250512
- task2.status = "failed";
250513
- scheduleBackgroundTaskCleanup(task_id);
250514
- return remote ?? { killed: true };
250515
- }
250516
- return { killed: false };
250517
- }
250518
- return { killed: killBackgroundProcess(task_id) };
250519
- }
250520
- var init_task_stop = __esm(async () => {
250521
- init_subagent_state();
250522
- init_backend2();
250523
- init_external_coding_agent();
250524
- init_process_manager();
250525
- init_remote_external_coding_agent();
250526
- await init_kill_bash();
250527
- });
250528
-
250529
250818
  // src/tools/impl/task-update.ts
250530
250819
  function validateStringArray(value, paramName) {
250531
250820
  if (value === undefined)
@@ -251580,6 +251869,8 @@ ${describeGitHubPullRequestSnapshot(initial2)}
251580
251869
  const controller = new AbortController;
251581
251870
  const secrets2 = {};
251582
251871
  let processState;
251872
+ const persistent = !isManagedCloudRuntime();
251873
+ let timeout;
251583
251874
  const events = createMonitorEventStream({
251584
251875
  emit(event) {
251585
251876
  queueMonitorEvent({
@@ -251618,6 +251909,7 @@ ${describeGitHubPullRequestSnapshot(initial2)}
251618
251909
  processState = {
251619
251910
  process: {
251620
251911
  kill() {
251912
+ clearTimeout(timeout);
251621
251913
  events.cancel();
251622
251914
  controller.abort();
251623
251915
  }
@@ -251632,11 +251924,29 @@ ${describeGitHubPullRequestSnapshot(initial2)}
251632
251924
  kind: "monitor",
251633
251925
  description,
251634
251926
  monitorSource: "github_pull_request",
251635
- persistent: true,
251927
+ persistent,
251636
251928
  secrets: secrets2
251637
251929
  };
251638
251930
  backgroundProcesses.set(taskId, processState);
251639
251931
  notifyBackgroundProcessStateChanged(scope);
251932
+ if (!persistent) {
251933
+ timeout = setTimeout(() => {
251934
+ if (processState.status !== "running")
251935
+ return;
251936
+ events.finish();
251937
+ if (processState.status !== "running")
251938
+ return;
251939
+ const event = getMonitorTimeoutEvent(MONITOR_MAX_TIMEOUT_MS);
251940
+ output.append(`
251941
+ ${event}
251942
+ `);
251943
+ queueMonitorEvent({ taskId, event, scope, actingUserId, secrets: secrets2 });
251944
+ processState.completionNotificationSuppressed = true;
251945
+ markMonitorFinished(taskId, processState, "failed", null);
251946
+ processState.process.kill();
251947
+ }, MONITOR_MAX_TIMEOUT_MS);
251948
+ unrefTimer(timeout);
251949
+ }
251640
251950
  const wait = deps.wait ?? defaultWait;
251641
251951
  (async () => {
251642
251952
  let previous = initial2;
@@ -251700,12 +252010,12 @@ ${describeGitHubPullRequestSnapshot(initial2)}
251700
252010
  if (processState.status === "running") {
251701
252011
  markMonitorFinished(taskId, processState, "failed", null);
251702
252012
  }
251703
- });
252013
+ }).finally(() => clearTimeout(timeout));
251704
252014
  return {
251705
252015
  content: [
251706
252016
  {
251707
252017
  type: "text",
251708
- text: `WatchPR started (task ${taskId}). It will keep watching after CI finishes and report new failures, reviews, comments, conflicts, head changes, merge-state changes, and watcher errors. It stops when the PR merges/closes, on TaskStop, or when this session ends.
252018
+ text: `WatchPR started (task ${taskId}). It will keep watching after CI finishes and report new failures, reviews, comments, conflicts, head changes, merge-state changes, and watcher errors. It stops when the PR merges/closes, on TaskStop, or when this session ends.${persistent ? "" : " A hard 1-hour limit applies in this Cloud sandbox; use Wake or cron for later checks."}
251709
252019
 
251710
252020
  Current state:
251711
252021
  ${describeGitHubPullRequestSnapshot(initial2)}
@@ -251716,7 +252026,7 @@ Output file: ${outputFile}`
251716
252026
  taskId,
251717
252027
  url: ref5.url,
251718
252028
  headSha: initial2.headSha,
251719
- persistent: true
252029
+ persistent
251720
252030
  };
251721
252031
  }
251722
252032
  var WATCH_POLL_MS = 30000, WATCH_MAX_POLL_MS;
@@ -251874,6 +252184,36 @@ var init_sdk_loader = __esm(() => {
251874
252184
  SDK_PACKAGE = ["@letta-ai", "letta-agent-sdk"].join("/");
251875
252185
  });
251876
252186
 
252187
+ // src/agent/preloaded-skills.ts
252188
+ import { readFile as readFile18 } from "node:fs/promises";
252189
+ async function loadPreloadedSkills(ids, options = {}) {
252190
+ if (ids.length === 0)
252191
+ return "";
252192
+ const { skillPathById } = await buildClientSkillsPayload({
252193
+ ...options,
252194
+ skillSources: options.skillSources?.length === 0 ? ["bundled"] : options.skillSources
252195
+ });
252196
+ const contents = [];
252197
+ for (const id2 of ids) {
252198
+ const path31 = skillPathById[id2];
252199
+ if (!path31)
252200
+ continue;
252201
+ try {
252202
+ contents.push(`<${id2}>
252203
+ ${await readFile18(path31, "utf-8")}
252204
+ </${id2}>`);
252205
+ } catch {}
252206
+ }
252207
+ return contents.length ? `<loaded_skills>
252208
+ ${contents.join(`
252209
+
252210
+ `)}
252211
+ </loaded_skills>` : "";
252212
+ }
252213
+ var init_preloaded_skills = __esm(() => {
252214
+ init_client_skills();
252215
+ });
252216
+
251877
252217
  // src/tools/workflow/sdk-spawner.ts
251878
252218
  async function latestWorkerRun(conversationId) {
251879
252219
  const backend3 = getBackend();
@@ -252115,6 +252455,19 @@ function createSdkSpawner(client, config2) {
252115
252455
  error: `Unknown model "${options.model}". Run \`letta model list\` for valid handles.`
252116
252456
  };
252117
252457
  }
252458
+ const loadedSkills = options.skills?.length ? await loadPreloadedSkills(options.skills, {
252459
+ agentId: config2.parentAgentId,
252460
+ ...config2.cwd ? { workingDirectory: config2.cwd } : {}
252461
+ }) : "";
252462
+ const missingSkills = (options.skills ?? []).filter((id2) => !loadedSkills.includes(`<${id2}>
252463
+ `));
252464
+ if (missingSkills.length) {
252465
+ return {
252466
+ value: null,
252467
+ failed: true,
252468
+ error: `Unknown skill(s): ${missingSkills.join(", ")}.`
252469
+ };
252470
+ }
252118
252471
  if (signal.aborted) {
252119
252472
  return {
252120
252473
  value: null,
@@ -252133,7 +252486,9 @@ function createSdkSpawner(client, config2) {
252133
252486
  let query3;
252134
252487
  try {
252135
252488
  query3 = client.query({
252136
- prompt,
252489
+ prompt: loadedSkills ? `${loadedSkills}
252490
+
252491
+ ${prompt}` : prompt,
252137
252492
  options: {
252138
252493
  ...buildQueryOptions(options, model, config2, request2.callIndex),
252139
252494
  ...resumeId ? { conversationId: resumeId } : {}
@@ -252259,6 +252614,7 @@ function createSdkSpawner(client, config2) {
252259
252614
  }
252260
252615
  var DEFAULT_SUBAGENT_TIMEOUT_MS, DEFAULT_MAX_SUBAGENT_TOOL_CALLS = 1000, MAX_IDENTICAL_TOOL_CALLS = 3, SUBAGENT_PREAMBLE = `You are a subagent inside a deterministic workflow. You are not talking to a human: your final output is consumed by a script. Return raw data with no preamble, no markdown framing, and no questions.`, JSON_PREAMBLE = `Your final message must be a single JSON value and nothing else: no prose, no markdown code fence.`;
252261
252616
  var init_sdk_spawner = __esm(async () => {
252617
+ init_preloaded_skills();
252262
252618
  init_backend2();
252263
252619
  init_tool_registry();
252264
252620
  init_tool_name_mapping();
@@ -252336,10 +252692,12 @@ async function createSdkSpawnerHandle(args, setStage = () => {}) {
252336
252692
  });
252337
252693
  throw error5;
252338
252694
  }
252339
- const toolScope = await createScriptToolScope(parentAgentId);
252695
+ const backgroundStopController = args.wait ? undefined : new AbortController;
252696
+ const toolScope = await createScriptToolScope(parentAgentId, args.wait ? args.signal : backgroundStopController?.signal);
252340
252697
  return {
252341
252698
  spawner,
252342
252699
  ...toolScope,
252700
+ backgroundStopController,
252343
252701
  cleanup: async () => {
252344
252702
  await toolScope.cleanup();
252345
252703
  await client[Symbol.asyncDispose]?.().catch(() => {
@@ -252399,6 +252757,14 @@ function jsonSafe(value) {
252399
252757
  return String(value);
252400
252758
  }
252401
252759
  }
252760
+ function formatFailureResult(error5, agentsDone, journalPath) {
252761
+ const lines = [`Workflow failed: ${error5 ?? "unknown error"}`];
252762
+ if (agentsDone > 0) {
252763
+ lines.push(`${agentsDone} agent call${agentsDone === 1 ? "" : "s"} completed before the failure. Their full results are saved in ${journalPath} (one line per completed call; the return value is outcome.value).`);
252764
+ }
252765
+ return lines.join(`
252766
+ `);
252767
+ }
252402
252768
  function formatCompletionResult(run, executionDir) {
252403
252769
  const payload = JSON.stringify({
252404
252770
  result: jsonSafe(run.result),
@@ -252544,7 +252910,7 @@ async function workflow(args) {
252544
252910
  const taskId = getNextWorkflowId();
252545
252911
  const outputFile = createBackgroundOutputFile(taskId);
252546
252912
  const scope = resolveNotificationScope(args.parentScope);
252547
- const abortController = new AbortController;
252913
+ const abortController = handle2.backgroundStopController ?? new AbortController;
252548
252914
  const processState = {
252549
252915
  process: {
252550
252916
  kill() {
@@ -252590,7 +252956,7 @@ async function workflow(args) {
252590
252956
  processState.status = outcome.run ? "completed" : "failed";
252591
252957
  processState.exitCode = outcome.run ? 0 : 1;
252592
252958
  }
252593
- const result3 = outcome.run ? formatCompletionResult(outcome.run, executionDir) : `Workflow failed: ${outcome.error ?? "unknown error"}`;
252959
+ const result3 = outcome.run ? formatCompletionResult(outcome.run, executionDir) : formatFailureResult(outcome.error, getWorkflowExecution(taskId)?.agentsDone ?? 0, journalPath);
252594
252960
  appendToOutputFile(outputFile, outcome.run ? `
252595
252961
  [result]
252596
252962
  ${result3}
@@ -253153,7 +253519,7 @@ var init_Monitor2 = __esm(() => {
253153
253519
  persistent: {
253154
253520
  type: "boolean",
253155
253521
  default: false,
253156
- description: "Run for the lifetime of the session (no timeout). Use for session-length watches like PR monitoring or log tails. Stop with TaskStop."
253522
+ description: "Run until TaskStop or session end. In managed Cloud sandboxes, a hard 1-hour limit still applies; elsewhere there is no timeout. For longer waits in Cloud, use Wake/cron rather than keeping a monitor running."
253157
253523
  },
253158
253524
  backup_after_seconds: {
253159
253525
  type: "integer",
@@ -254316,7 +254682,7 @@ import {
254316
254682
  readFileSync as readFileSync38,
254317
254683
  renameSync as renameSync10,
254318
254684
  rmSync as rmSync18,
254319
- statSync as statSync18,
254685
+ statSync as statSync19,
254320
254686
  writeFileSync as writeFileSync29
254321
254687
  } from "node:fs";
254322
254688
  import { join as join71 } from "node:path";
@@ -254635,11 +255001,11 @@ function createRecoveryLineageSidecarAccess(params) {
254635
255001
  ownerIsAlive = error5.code !== "ESRCH";
254636
255002
  }
254637
255003
  }
254638
- const ageMs = Date.now() - statSync18(join71(params.directory, file)).mtimeMs;
255004
+ const ageMs = Date.now() - statSync19(join71(params.directory, file)).mtimeMs;
254639
255005
  if (!ownerIsCurrentWriter && (!ownerIsAlive && ageMs >= staleTemporaryAgeMs || ageMs >= maximumTemporaryAgeMs)) {
254640
255006
  const release = acquireDurableFileLock(join71(params.directory, mainFile), { waitMs: params.lockWaitMs });
254641
255007
  try {
254642
- const currentAgeMs = Date.now() - statSync18(join71(params.directory, file)).mtimeMs;
255008
+ const currentAgeMs = Date.now() - statSync19(join71(params.directory, file)).mtimeMs;
254643
255009
  if (!ownerIsCurrentWriter && (!ownerIsAlive && currentAgeMs >= staleTemporaryAgeMs || currentAgeMs >= maximumTemporaryAgeMs)) {
254644
255010
  rmSync18(join71(params.directory, file), { force: true });
254645
255011
  params.syncDirectory(params.directory);
@@ -257131,7 +257497,7 @@ import {
257131
257497
  openSync as openSync7,
257132
257498
  readFileSync as readFileSync40,
257133
257499
  renameSync as renameSync11,
257134
- statSync as statSync19,
257500
+ statSync as statSync20,
257135
257501
  unlinkSync as unlinkSync10,
257136
257502
  writeFileSync as writeFileSync31
257137
257503
  } from "node:fs";
@@ -257271,7 +257637,7 @@ function validateDurableStore(value) {
257271
257637
  function readDurableStore(path32) {
257272
257638
  if (!existsSync50(path32))
257273
257639
  return emptyDurableStore();
257274
- const size2 = statSync19(path32).size;
257640
+ const size2 = statSync20(path32).size;
257275
257641
  if (size2 > MAX_INPUT_DISPOSITION_STORE_BYTES) {
257276
257642
  throw new Error("Accepted-input disposition store exceeds its size limit");
257277
257643
  }
@@ -261164,7 +261530,7 @@ var init_environments4 = __esm(() => {
261164
261530
  });
261165
261531
 
261166
261532
  // src/cli/subcommands/feedback.ts
261167
- import { readFile as readFile18 } from "node:fs/promises";
261533
+ import { readFile as readFile19 } from "node:fs/promises";
261168
261534
  import { parseArgs as parseArgs8 } from "node:util";
261169
261535
  function printUsage6(stdout = console.log) {
261170
261536
  stdout(`
@@ -261214,7 +261580,7 @@ async function runFeedbackSubcommand(argv, deps = {}) {
261214
261580
  let rawMessage = typeof inlineMessage === "string" ? inlineMessage : "";
261215
261581
  if (typeof messageFile === "string") {
261216
261582
  try {
261217
- rawMessage = await (deps.readMessageFile ?? ((path32) => readFile18(path32, "utf8")))(messageFile);
261583
+ rawMessage = await (deps.readMessageFile ?? ((path32) => readFile19(path32, "utf8")))(messageFile);
261218
261584
  } catch {
261219
261585
  stderr(`Could not read feedback file: ${messageFile}`);
261220
261586
  return 1;
@@ -263427,7 +263793,7 @@ var init_listen_log = __esm(() => {
263427
263793
 
263428
263794
  // src/utils/machine-metadata.ts
263429
263795
  import { execFile as execFile19 } from "node:child_process";
263430
- import { readFile as readFile19 } from "node:fs/promises";
263796
+ import { readFile as readFile20 } from "node:fs/promises";
263431
263797
  import { cpus, totalmem } from "node:os";
263432
263798
  function getMachineMetadata() {
263433
263799
  machineMetadata ??= collectMachineMetadata().catch(() => ({
@@ -263442,7 +263808,7 @@ function localSources() {
263442
263808
  architecture: process.arch,
263443
263809
  cpu: cpus()[0]?.model,
263444
263810
  memoryBytes: Math.min(totalmem(), limit4 || Number.POSITIVE_INFINITY),
263445
- read: (path32, signal) => readFile19(path32, { encoding: "utf8", signal }),
263811
+ read: (path32, signal) => readFile20(path32, { encoding: "utf8", signal }),
263446
263812
  run: (command, args, signal) => new Promise((resolve39, reject2) => {
263447
263813
  execFile19(command, args, {
263448
263814
  encoding: "utf8",
@@ -264021,7 +264387,7 @@ var init_auth3 = __esm(() => {
264021
264387
 
264022
264388
  // src/websocket/listener/manual-instance-lock.ts
264023
264389
  import { createHash as createHash14, randomUUID as randomUUID47 } from "node:crypto";
264024
- import { link as link4, mkdir as mkdir13, readFile as readFile20, rm as rm11, unlink as unlink3, writeFile as writeFile15 } from "node:fs/promises";
264390
+ import { link as link4, mkdir as mkdir13, readFile as readFile21, rm as rm11, unlink as unlink3, writeFile as writeFile15 } from "node:fs/promises";
264025
264391
  import { homedir as homedir38 } from "node:os";
264026
264392
  import path32 from "node:path";
264027
264393
  function hasErrorCode3(error5, code2) {
@@ -264108,7 +264474,7 @@ async function recoverDeadOwner(lockPath, staleContents, recoveryOwnerContents,
264108
264474
  }
264109
264475
  let claimContents;
264110
264476
  try {
264111
- claimContents = await readFile20(claimPath, "utf-8");
264477
+ claimContents = await readFile21(claimPath, "utf-8");
264112
264478
  } catch (error5) {
264113
264479
  if (hasErrorCode3(error5, "ENOENT")) {
264114
264480
  continue;
@@ -264128,7 +264494,7 @@ async function recoverDeadOwner(lockPath, staleContents, recoveryOwnerContents,
264128
264494
  }
264129
264495
  let currentContents;
264130
264496
  try {
264131
- currentContents = await readFile20(lockPath, "utf-8");
264497
+ currentContents = await readFile21(lockPath, "utf-8");
264132
264498
  } catch (error5) {
264133
264499
  return hasErrorCode3(error5, "ENOENT");
264134
264500
  }
@@ -264178,7 +264544,7 @@ async function acquireManualListenerLock(scope, overrides = {}) {
264178
264544
  return;
264179
264545
  let currentContents;
264180
264546
  try {
264181
- currentContents = await readFile20(lockPath, "utf-8");
264547
+ currentContents = await readFile21(lockPath, "utf-8");
264182
264548
  } catch (error5) {
264183
264549
  if (hasErrorCode3(error5, "ENOENT")) {
264184
264550
  released = true;
@@ -264198,7 +264564,7 @@ async function acquireManualListenerLock(scope, overrides = {}) {
264198
264564
  }
264199
264565
  let incumbentContents;
264200
264566
  try {
264201
- incumbentContents = await readFile20(lockPath, "utf-8");
264567
+ incumbentContents = await readFile21(lockPath, "utf-8");
264202
264568
  } catch (error5) {
264203
264569
  if (hasErrorCode3(error5, "ENOENT")) {
264204
264570
  continue;
@@ -264867,7 +265233,7 @@ function isBrowserDeviceMcpOAuthProtocolCommand(value) {
264867
265233
  return false;
264868
265234
  const record6 = value;
264869
265235
  if (record6.type === "browser_device_mcp_oauth") {
264870
- return Object.keys(record6).length === 6 && typeof record6.request_id === "string" && REQUEST_ID_PATTERN.test(record6.request_id) && typeof record6.handoff_key === "string" && HANDOFF_KEY_PATTERN2.test(record6.handoff_key) && typeof record6.service === "string" && SERVICE_PATTERN.test(record6.service) && typeof record6.server_url === "string" && record6.server_url.length > 0 && record6.server_url.length <= MAX_SERVER_URL_LENGTH && !hasControlCharacters(record6.server_url) && typeof record6.timeout_ms === "number" && Number.isSafeInteger(record6.timeout_ms) && record6.timeout_ms > 0 && record6.timeout_ms <= MAX_TIMEOUT_MS2;
265236
+ return Object.keys(record6).length === 6 && typeof record6.request_id === "string" && REQUEST_ID_PATTERN.test(record6.request_id) && typeof record6.handoff_key === "string" && HANDOFF_KEY_PATTERN2.test(record6.handoff_key) && typeof record6.service === "string" && SERVICE_PATTERN.test(record6.service) && typeof record6.server_url === "string" && record6.server_url.length > 0 && record6.server_url.length <= MAX_SERVER_URL_LENGTH && !hasControlCharacters(record6.server_url) && typeof record6.timeout_ms === "number" && Number.isSafeInteger(record6.timeout_ms) && record6.timeout_ms > 0 && record6.timeout_ms <= MAX_TIMEOUT_MS;
264871
265237
  }
264872
265238
  return record6.type === "browser_device_mcp_oauth_cancel" && Object.keys(record6).length === 2 && typeof record6.operation_id === "string" && REQUEST_ID_PATTERN.test(record6.operation_id);
264873
265239
  }
@@ -264879,7 +265245,7 @@ function hasControlCharacters(value) {
264879
265245
  }
264880
265246
  return false;
264881
265247
  }
264882
- var REQUEST_ID_PATTERN, HANDOFF_KEY_PATTERN2, SERVICE_PATTERN, MAX_SERVER_URL_LENGTH = 2048, MAX_TIMEOUT_MS2 = 285000;
265248
+ var REQUEST_ID_PATTERN, HANDOFF_KEY_PATTERN2, SERVICE_PATTERN, MAX_SERVER_URL_LENGTH = 2048, MAX_TIMEOUT_MS = 285000;
264883
265249
  var init_browser_device_mcp_oauth_protocol_inbound = __esm(() => {
264884
265250
  REQUEST_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/;
264885
265251
  HANDOFF_KEY_PATTERN2 = /^[A-Za-z0-9_-]{43,128}$/;
@@ -266294,7 +266660,7 @@ __export(exports_memory_scanner, {
266294
266660
  readFileContent: () => readFileContent,
266295
266661
  getFileNodes: () => getFileNodes
266296
266662
  });
266297
- import { readdirSync as readdirSync21, readFileSync as readFileSync42, statSync as statSync20 } from "node:fs";
266663
+ import { readdirSync as readdirSync21, readFileSync as readFileSync42, statSync as statSync21 } from "node:fs";
266298
266664
  import { join as join76, relative as relative7 } from "node:path";
266299
266665
  function scanMemoryFilesystem(memoryRoot) {
266300
266666
  const nodes = [];
@@ -266312,10 +266678,10 @@ function scanMemoryFilesystem(memoryRoot) {
266312
266678
  let aIsDir = false;
266313
266679
  let bIsDir = false;
266314
266680
  try {
266315
- aIsDir = statSync20(aPath).isDirectory();
266681
+ aIsDir = statSync21(aPath).isDirectory();
266316
266682
  } catch {}
266317
266683
  try {
266318
- bIsDir = statSync20(bPath).isDirectory();
266684
+ bIsDir = statSync21(bPath).isDirectory();
266319
266685
  } catch {}
266320
266686
  if (aIsDir !== bIsDir)
266321
266687
  return aIsDir ? -1 : 1;
@@ -266331,7 +266697,7 @@ function scanMemoryFilesystem(memoryRoot) {
266331
266697
  const fullPath = join76(dir, name);
266332
266698
  let isDir = false;
266333
266699
  try {
266334
- isDir = statSync20(fullPath).isDirectory();
266700
+ isDir = statSync21(fullPath).isDirectory();
266335
266701
  } catch {
266336
266702
  return;
266337
266703
  }
@@ -266430,7 +266796,7 @@ async function handleListMemoryCommand(parsed, socket, safeSocketSend, overrides
266430
266796
  const isMemfsEnabledOnServer2 = overrides.isMemfsEnabledOnServer ?? actualIsMemfsEnabledOnServer;
266431
266797
  const { scanMemoryFilesystem: scanMemoryFilesystem2, getFileNodes: getFileNodes2, readFileContent: readFileContent2 } = await Promise.resolve().then(() => (init_memory_scanner(), exports_memory_scanner));
266432
266798
  const { parseFrontmatter: parseFrontmatter2 } = await Promise.resolve().then(() => exports_frontmatter);
266433
- const { existsSync: existsSync52, statSync: statSync21 } = await import("node:fs");
266799
+ const { existsSync: existsSync52, statSync: statSync22 } = await import("node:fs");
266434
266800
  const { join: join77, posix: posix3 } = await import("node:path");
266435
266801
  const memoryRoot = getMemoryFilesystemRoot2(parsed.agent_id);
266436
266802
  let memfsInitialized = existsSync52(join77(memoryRoot, ".git"));
@@ -266527,7 +266893,7 @@ async function handleListMemoryCommand(parsed, socket, safeSocketSend, overrides
266527
266893
  if (imageMimeType) {
266528
266894
  let size2 = 0;
266529
266895
  try {
266530
- size2 = statSync21(node.fullPath).size;
266896
+ size2 = statSync22(node.fullPath).size;
266531
266897
  } catch {}
266532
266898
  return {
266533
266899
  relative_path: node.relativePath,
@@ -266750,7 +267116,7 @@ function handleMemoryProtocolCommand(parsed, context3) {
266750
267116
  ensureLocalMemfsCheckout: ensureLocalMemfsCheckout2,
266751
267117
  isMemfsEnabledOnServer: isMemfsEnabledOnServer2
266752
267118
  } = await Promise.resolve().then(() => (init_memory_filesystem2(), exports_memory_filesystem));
266753
- const { readFile: readFile21 } = await import("node:fs/promises");
267119
+ const { readFile: readFile22 } = await import("node:fs/promises");
266754
267120
  const { existsSync: existsSync52 } = await import("node:fs");
266755
267121
  const { isAbsolute: isAbsolute25, join: join77, normalize: normalize4, relative: relative8, sep: sep8 } = await import("node:path");
266756
267122
  if (isAbsolute25(parsed.path) || parsed.path.length === 0) {
@@ -266776,7 +267142,7 @@ function handleMemoryProtocolCommand(parsed, context3) {
266776
267142
  return;
266777
267143
  }
266778
267144
  }
266779
- const buffer = await readFile21(absolutePath);
267145
+ const buffer = await readFile22(absolutePath);
266780
267146
  const content = encoding === "base64" ? buffer.toString("base64") : buffer.toString("utf-8");
266781
267147
  const pathspec = rel.split(sep8).join("/");
266782
267148
  safeSocketSend(socket, {
@@ -276035,7 +276401,7 @@ ${lanes.join(`
276035
276401
  writeOutputIsTTY() {
276036
276402
  return process.stdout.isTTY;
276037
276403
  },
276038
- readFile: readFile21,
276404
+ readFile: readFile22,
276039
276405
  writeFile: writeFile22,
276040
276406
  watchFile: watchFile2,
276041
276407
  watchDirectory,
@@ -276076,7 +276442,7 @@ ${lanes.join(`
276076
276442
  return process.memoryUsage().heapUsed;
276077
276443
  },
276078
276444
  getFileSize(path35) {
276079
- const stat12 = statSync21(path35);
276445
+ const stat12 = statSync22(path35);
276080
276446
  if (stat12 == null ? undefined : stat12.isFile()) {
276081
276447
  return stat12.size;
276082
276448
  }
@@ -276119,7 +276485,7 @@ ${lanes.join(`
276119
276485
  }
276120
276486
  };
276121
276487
  return nodeSystem;
276122
- function statSync21(path35) {
276488
+ function statSync22(path35) {
276123
276489
  try {
276124
276490
  return _fs.statSync(path35, statSyncOptions);
276125
276491
  } catch {
@@ -276171,7 +276537,7 @@ ${lanes.join(`
276171
276537
  activeSession.post("Profiler.stop", (err, { profile }) => {
276172
276538
  var _a10;
276173
276539
  if (!err) {
276174
- if ((_a10 = statSync21(profilePath)) == null ? undefined : _a10.isDirectory()) {
276540
+ if ((_a10 = statSync22(profilePath)) == null ? undefined : _a10.isDirectory()) {
276175
276541
  profilePath = _path.join(profilePath, `${(/* @__PURE__ */ new Date()).toISOString().replace(/:/g, "-")}+P${process.pid}.cpuprofile`);
276176
276542
  }
276177
276543
  try {
@@ -276228,7 +276594,7 @@ ${lanes.join(`
276228
276594
  function fsWatchWorker(fileOrDirectory, recursive, callback) {
276229
276595
  return _fs.watch(fileOrDirectory, fsSupportsRecursiveFsWatch ? { persistent: true, recursive: !!recursive } : { persistent: true }, callback);
276230
276596
  }
276231
- function readFile21(fileName, _encoding) {
276597
+ function readFile22(fileName, _encoding) {
276232
276598
  let buffer;
276233
276599
  try {
276234
276600
  buffer = _fs.readFileSync(fileName);
@@ -276280,7 +276646,7 @@ ${lanes.join(`
276280
276646
  let stat12;
276281
276647
  if (typeof dirent === "string" || dirent.isSymbolicLink()) {
276282
276648
  const name = combinePaths(path35, entry);
276283
- stat12 = statSync21(name);
276649
+ stat12 = statSync22(name);
276284
276650
  if (!stat12) {
276285
276651
  continue;
276286
276652
  }
@@ -276304,7 +276670,7 @@ ${lanes.join(`
276304
276670
  return matchFiles(path35, extensions, excludes, includes2, useCaseSensitiveFileNames2, process.cwd(), depth, getAccessibleFileSystemEntries, realpath6);
276305
276671
  }
276306
276672
  function fileSystemEntryExists(path35, entryKind) {
276307
- const stat12 = statSync21(path35);
276673
+ const stat12 = statSync22(path35);
276308
276674
  if (!stat12) {
276309
276675
  return false;
276310
276676
  }
@@ -276338,7 +276704,7 @@ ${lanes.join(`
276338
276704
  }
276339
276705
  function getModifiedTime3(path35) {
276340
276706
  var _a10;
276341
- return (_a10 = statSync21(path35)) == null ? undefined : _a10.mtime;
276707
+ return (_a10 = statSync22(path35)) == null ? undefined : _a10.mtime;
276342
276708
  }
276343
276709
  function setModifiedTime(path35, time3) {
276344
276710
  try {
@@ -307175,7 +307541,7 @@ ${lanes.join(`
307175
307541
  const possibleOption = getSpellingSuggestion(unknownOption, diagnostics2.optionDeclarations, getOptionName);
307176
307542
  return possibleOption ? createDiagnosticForNodeInSourceFileOrCompilerDiagnostic(sourceFile, node, diagnostics2.unknownDidYouMeanDiagnostic, unknownOptionErrorText || unknownOption, possibleOption.name) : createDiagnosticForNodeInSourceFileOrCompilerDiagnostic(sourceFile, node, diagnostics2.unknownOptionDiagnostic, unknownOptionErrorText || unknownOption);
307177
307543
  }
307178
- function parseCommandLineWorker(diagnostics2, commandLine, readFile21) {
307544
+ function parseCommandLineWorker(diagnostics2, commandLine, readFile22) {
307179
307545
  const options = {};
307180
307546
  let watchOptions;
307181
307547
  const fileNames = [];
@@ -307213,7 +307579,7 @@ ${lanes.join(`
307213
307579
  }
307214
307580
  }
307215
307581
  function parseResponseFile(fileName) {
307216
- const text3 = tryReadFile(fileName, readFile21 || ((fileName2) => sys.readFile(fileName2)));
307582
+ const text3 = tryReadFile(fileName, readFile22 || ((fileName2) => sys.readFile(fileName2)));
307217
307583
  if (!isString7(text3)) {
307218
307584
  errors6.push(text3);
307219
307585
  return;
@@ -307316,8 +307682,8 @@ ${lanes.join(`
307316
307682
  unknownDidYouMeanDiagnostic: Diagnostics.Unknown_compiler_option_0_Did_you_mean_1,
307317
307683
  optionTypeMismatchDiagnostic: Diagnostics.Compiler_option_0_expects_an_argument
307318
307684
  };
307319
- function parseCommandLine2(commandLine, readFile21) {
307320
- return parseCommandLineWorker(compilerOptionsDidYouMeanDiagnostics, commandLine, readFile21);
307685
+ function parseCommandLine2(commandLine, readFile22) {
307686
+ return parseCommandLineWorker(compilerOptionsDidYouMeanDiagnostics, commandLine, readFile22);
307321
307687
  }
307322
307688
  function getOptionFromName(optionName, allowShort) {
307323
307689
  return getOptionDeclarationFromName(getOptionsNameMap, optionName, allowShort);
@@ -307385,8 +307751,8 @@ ${lanes.join(`
307385
307751
  result3.originalFileName = result3.fileName;
307386
307752
  return parseJsonSourceFileConfigFileContent(result3, host, getNormalizedAbsolutePath(getDirectoryPath(configFileName), cwd2), optionsToExtend, getNormalizedAbsolutePath(configFileName, cwd2), undefined, extraFileExtensions, extendedConfigCache, watchOptionsToExtend);
307387
307753
  }
307388
- function readConfigFile(fileName, readFile21) {
307389
- const textOrDiagnostic = tryReadFile(fileName, readFile21);
307754
+ function readConfigFile(fileName, readFile22) {
307755
+ const textOrDiagnostic = tryReadFile(fileName, readFile22);
307390
307756
  return isString7(textOrDiagnostic) ? parseConfigFileTextToJson(fileName, textOrDiagnostic) : { config: {}, error: textOrDiagnostic };
307391
307757
  }
307392
307758
  function parseConfigFileTextToJson(fileName, jsonText) {
@@ -307396,14 +307762,14 @@ ${lanes.join(`
307396
307762
  error: jsonSourceFile.parseDiagnostics.length ? jsonSourceFile.parseDiagnostics[0] : undefined
307397
307763
  };
307398
307764
  }
307399
- function readJsonConfigFile(fileName, readFile21) {
307400
- const textOrDiagnostic = tryReadFile(fileName, readFile21);
307765
+ function readJsonConfigFile(fileName, readFile22) {
307766
+ const textOrDiagnostic = tryReadFile(fileName, readFile22);
307401
307767
  return isString7(textOrDiagnostic) ? parseJsonText(fileName, textOrDiagnostic) : { fileName, parseDiagnostics: [textOrDiagnostic] };
307402
307768
  }
307403
- function tryReadFile(fileName, readFile21) {
307769
+ function tryReadFile(fileName, readFile22) {
307404
307770
  let text3;
307405
307771
  try {
307406
- text3 = readFile21(fileName);
307772
+ text3 = readFile22(fileName);
307407
307773
  } catch (e2) {
307408
307774
  return createCompilerDiagnostic(Diagnostics.Cannot_read_file_0_Colon_1, fileName, e2.message);
307409
307775
  }
@@ -374833,12 +375199,12 @@ ${lanes.join(`
374833
375199
  function createCompilerHost(options, setParentNodes) {
374834
375200
  return createCompilerHostWorker(options, setParentNodes);
374835
375201
  }
374836
- function createGetSourceFile(readFile21, setParentNodes) {
375202
+ function createGetSourceFile(readFile22, setParentNodes) {
374837
375203
  return (fileName, languageVersionOrOptions, onError) => {
374838
375204
  let text3;
374839
375205
  try {
374840
375206
  mark2("beforeIORead");
374841
- text3 = readFile21(fileName);
375207
+ text3 = readFile22(fileName);
374842
375208
  mark2("afterIORead");
374843
375209
  measure("I/O Read", "beforeIORead", "afterIORead");
374844
375210
  } catch (e2) {
@@ -375629,7 +375995,7 @@ ${lanes.join(`
375629
375995
  getRedirectFromOutput,
375630
375996
  forEachResolvedProjectReference: forEachResolvedProjectReference2
375631
375997
  });
375632
- const readFile21 = host.readFile.bind(host);
375998
+ const readFile22 = host.readFile.bind(host);
375633
375999
  (_e = tracing) == null || _e.push(tracing.Phase.Program, "shouldProgramCreateNewSourceFiles", { hasOldProgram: !!oldProgram });
375634
376000
  const shouldCreateNewSourceFile = shouldProgramCreateNewSourceFiles(oldProgram, options);
375635
376001
  (_f = tracing) == null || _f.pop();
@@ -375805,7 +376171,7 @@ ${lanes.join(`
375805
376171
  shouldTransformImportCall,
375806
376172
  emitBuildInfo,
375807
376173
  fileExists,
375808
- readFile: readFile21,
376174
+ readFile: readFile22,
375809
376175
  directoryExists: directoryExists2,
375810
376176
  getSymlinkCache,
375811
376177
  realpath: (_o = host.realpath) == null ? undefined : _o.bind(host),
@@ -437813,7 +438179,7 @@ import {
437813
438179
  mkdirSync as mkdirSync42,
437814
438180
  readdirSync as readdirSync23,
437815
438181
  readFileSync as readFileSync46,
437816
- statSync as statSync21,
438182
+ statSync as statSync22,
437817
438183
  unlinkSync as unlinkSync13,
437818
438184
  writeFileSync as writeFileSync36
437819
438185
  } from "node:fs";
@@ -438642,7 +439008,7 @@ async function loadLocalMods(options) {
438642
439008
  recordLegacyExtensionLoadedDiagnostic(registry4, owner, source, options.onDiagnostic);
438643
439009
  }
438644
439010
  try {
438645
- const mtimeMs = statSync21(modPath).mtimeMs;
439011
+ const mtimeMs = statSync22(modPath).mtimeMs;
438646
439012
  const sourceText = readFileSync46(modPath, "utf8");
438647
439013
  recordDeprecatedContextApiSourceDiagnostics(sourceText, (diagnostic) => {
438648
439014
  recordModDiagnostic(registry4, {
@@ -443139,6 +443505,94 @@ var init_chatgpt_plan_rotation = __esm(() => {
443139
443505
  init_backend2();
443140
443506
  });
443141
443507
 
443508
+ // src/agent/ephemeral-conversation.ts
443509
+ async function buildEphemeralConversationCreateBody(options) {
443510
+ const system2 = options.systemPromptCustom ? options.systemPromptCustom : await resolveAndBuildSystemPrompt(options.systemPromptPreset, options.memoryPromptMode ?? "standard");
443511
+ const request2 = await buildCreateAgentRequest({
443512
+ model: options.model,
443513
+ system: system2,
443514
+ memoryPromptMode: "standard",
443515
+ enableMemfs: false,
443516
+ isSubagent: true,
443517
+ baseTools: []
443518
+ });
443519
+ const modelSettings = options.model ? getModelUpdateArgs(options.model) : undefined;
443520
+ const contextWindow = modelSettings?.context_window ?? await getModelContextWindow(request2.model);
443521
+ return {
443522
+ model: request2.model,
443523
+ system: request2.system ?? system2,
443524
+ ...modelSettings ? { model_settings: modelSettings } : {},
443525
+ ...contextWindow ? { context_window_limit: contextWindow } : {}
443526
+ };
443527
+ }
443528
+ function projectConversationState(conversation) {
443529
+ if (!conversation.model) {
443530
+ throw new Error("Cannot resume a conversation without a persisted model.");
443531
+ }
443532
+ return {
443533
+ id: conversation.id,
443534
+ name: "Ephemeral conversation",
443535
+ system: conversation.system ?? "",
443536
+ tools: [],
443537
+ memory: { blocks: [] },
443538
+ llm_config: {
443539
+ handle: conversation.model,
443540
+ model: conversation.model,
443541
+ context_window: conversation.context_window_limit ?? undefined,
443542
+ model_settings: conversation.model_settings ?? {}
443543
+ },
443544
+ model_settings: conversation.model_settings ?? {},
443545
+ message_buffer_autoclear: false
443546
+ };
443547
+ }
443548
+ async function createEphemeralConversation2(options) {
443549
+ const body3 = await buildEphemeralConversationCreateBody(options);
443550
+ const conversation = await createEphemeralConversation(body3);
443551
+ return {
443552
+ agent: projectConversationState({ ...body3, id: conversation.id }),
443553
+ conversationId: conversation.id
443554
+ };
443555
+ }
443556
+ async function retrieveResumeState(agentId, conversationId, backend3 = getBackend()) {
443557
+ return agentId ? backend3.retrieveAgent(agentId) : projectConversationState(await backend3.retrieveConversation(conversationId));
443558
+ }
443559
+ async function createLocalEphemeralConversation(options) {
443560
+ const body3 = await buildEphemeralConversationCreateBody(options);
443561
+ const backend3 = getBackend();
443562
+ const internalAgent = await backend3.createAgent({
443563
+ agent_type: "letta_v1_agent",
443564
+ name: "Ephemeral conversation",
443565
+ model: body3.model,
443566
+ system: body3.system,
443567
+ memory_blocks: [],
443568
+ tags: [],
443569
+ tools: [],
443570
+ include_base_tools: false,
443571
+ include_base_tool_rules: false,
443572
+ initial_message_sequence: [],
443573
+ parallel_tool_calls: true,
443574
+ hidden: true
443575
+ });
443576
+ const conversation = await backend3.createConversation({
443577
+ agent_id: internalAgent.id,
443578
+ model: body3.model,
443579
+ ...body3.model_settings ? { model_settings: body3.model_settings } : {},
443580
+ ...body3.context_window_limit ? { context_window_limit: body3.context_window_limit } : {}
443581
+ });
443582
+ return {
443583
+ agent: internalAgent,
443584
+ conversationId: conversation.id
443585
+ };
443586
+ }
443587
+ var init_ephemeral_conversation = __esm(() => {
443588
+ init_available_models();
443589
+ init_create_agent_request();
443590
+ init_model();
443591
+ init_system_prompt_resolution();
443592
+ init_backend2();
443593
+ init_ephemeral_conversations();
443594
+ });
443595
+
443142
443596
  // src/cli/helpers/compaction-utils.ts
443143
443597
  function extractCompactionSummary(text3) {
443144
443598
  try {
@@ -447193,7 +447647,7 @@ async function tryResumeBusyConversationStream(params) {
447193
447647
  }
447194
447648
  }
447195
447649
  async function resolveStaleApprovals(runtime, socket, turnLease, deps = {}) {
447196
- if (!runtime.agentId)
447650
+ if (!runtime.agentId && runtime.conversationId === "default")
447197
447651
  return null;
447198
447652
  const getResumeDataImpl = deps.getResumeData ?? getResumeDataFromBackend;
447199
447653
  const prepareToolExecutionContext = deps.prepareToolExecutionContext ?? prepareToolExecutionContextForScope;
@@ -447208,7 +447662,10 @@ async function resolveStaleApprovals(runtime, socket, turnLease, deps = {}) {
447208
447662
  const backend3 = getBackend();
447209
447663
  let agent;
447210
447664
  try {
447211
- agent = await (deps.retrieveAgent ? deps.retrieveAgent(runtime.agentId) : backend3.retrieveAgent(runtime.agentId));
447665
+ agent = await retrieveResumeState(runtime.agentId, runtime.conversationId, {
447666
+ retrieveAgent: deps.retrieveAgent ?? backend3.retrieveAgent.bind(backend3),
447667
+ retrieveConversation: deps.retrieveConversation ?? backend3.retrieveConversation.bind(backend3)
447668
+ });
447212
447669
  } catch (err) {
447213
447670
  assertCurrentTurnLease();
447214
447671
  if (isBackendNotFoundError(err)) {
@@ -447241,14 +447698,14 @@ async function resolveStaleApprovals(runtime, socket, turnLease, deps = {}) {
447241
447698
  agent_id: runtime.agentId,
447242
447699
  conversation_id: recoveryConversationId
447243
447700
  };
447244
- const modAdapters = await ensureListenerModAdaptersForAgent(runtime.listener, runtime.agentId);
447701
+ const modAdapters = runtime.agentId ? await ensureListenerModAdaptersForAgent(runtime.listener, runtime.agentId) : [];
447245
447702
  const preparedToolContext = await prepareToolExecutionContext({
447246
447703
  connectionId: resolveTurnExecutionConnectionId(runtime.listener, scope, runtime.activeConnectionId ?? undefined),
447247
447704
  agentId: runtime.agentId,
447248
447705
  conversationId: recoveryConversationId,
447249
447706
  workingDirectory: recoveryWorkingDirectory,
447250
447707
  permissionModeState: getOrCreateConversationPermissionModeStateRef(runtime.listener, runtime.agentId, runtime.conversationId),
447251
- modContext: createListenerAgentModContext(runtime.agentId),
447708
+ ...runtime.agentId ? { modContext: createListenerAgentModContext(runtime.agentId) } : {},
447252
447709
  modAdapters,
447253
447710
  modEvents: createListenerModEvents(modAdapters)
447254
447711
  });
@@ -447641,6 +448098,7 @@ var init_send = __esm(async () => {
447641
448098
  init_error();
447642
448099
  init_approval_recovery();
447643
448100
  init_check_approval();
448101
+ init_ephemeral_conversation();
447644
448102
  init_turn_recovery_policy();
447645
448103
  init_backend2();
447646
448104
  init_error_formatter();
@@ -451706,12 +452164,12 @@ function rowToolCalls(row, index, diagnostics2) {
451706
452164
  }
451707
452165
  return calls;
451708
452166
  }
451709
- function contentText3(content) {
452167
+ function contentText2(content) {
451710
452168
  if (typeof content === "string") {
451711
452169
  if (content.startsWith(CONTENT_JSON_PREFIX)) {
451712
452170
  const encoded = content.slice(CONTENT_JSON_PREFIX.length);
451713
452171
  try {
451714
- return contentText3(JSON.parse(encoded));
452172
+ return contentText2(JSON.parse(encoded));
451715
452173
  } catch {
451716
452174
  return encoded;
451717
452175
  }
@@ -451797,7 +452255,7 @@ var init_hermes = __esm(() => {
451797
452255
  });
451798
452256
  };
451799
452257
  if (row.role === "user") {
451800
- const content = contentText3(row.content);
452258
+ const content = contentText2(row.content);
451801
452259
  if (content) {
451802
452260
  emit({
451803
452261
  type: "message",
@@ -451817,7 +452275,7 @@ var init_hermes = __esm(() => {
451817
452275
  ...timestamp ? { timestamp } : {}
451818
452276
  });
451819
452277
  }
451820
- const content = contentText3(row.content);
452278
+ const content = contentText2(row.content);
451821
452279
  if (content) {
451822
452280
  emit({
451823
452281
  type: "message",
@@ -451840,7 +452298,7 @@ var init_hermes = __esm(() => {
451840
452298
  if (row.role === "tool") {
451841
452299
  emit({
451842
452300
  type: "tool_result",
451843
- content: contentText3(row.content),
452301
+ content: contentText2(row.content),
451844
452302
  ...typeof row.tool_call_id === "string" && row.tool_call_id ? { callId: row.tool_call_id } : {},
451845
452303
  ...timestamp ? { timestamp } : {}
451846
452304
  });
@@ -453351,7 +453809,7 @@ var init_deepagents = __esm(() => {
453351
453809
  });
453352
453810
 
453353
453811
  // node_modules/@letta-ai/trajectory/dist/adapters/listing-shared.js
453354
- import { readdirSync as readdirSync25, statSync as statSync22 } from "node:fs";
453812
+ import { readdirSync as readdirSync25, statSync as statSync23 } from "node:fs";
453355
453813
  import { join as join83 } from "node:path";
453356
453814
  function sortListings(items3) {
453357
453815
  return items3.sort((left, right) => {
@@ -453375,7 +453833,7 @@ function safeReadDir(path42) {
453375
453833
  }
453376
453834
  function safeStat(path42) {
453377
453835
  try {
453378
- const stats = statSync22(path42);
453836
+ const stats = statSync23(path42);
453379
453837
  return { mtimeMs: stats.mtimeMs, sizeBytes: stats.size };
453380
453838
  } catch {
453381
453839
  return;
@@ -453832,7 +454290,7 @@ var exports_reflection_memory_v2 = {};
453832
454290
  __export(exports_reflection_memory_v2, {
453833
454291
  buildMemfsV2ParentMemorySnapshot: () => buildMemfsV2ParentMemorySnapshot
453834
454292
  });
453835
- import { readFile as readFile21 } from "node:fs/promises";
454293
+ import { readFile as readFile22 } from "node:fs/promises";
453836
454294
  function renderProjectedTree(files) {
453837
454295
  const root2 = { directories: new Map, files: [] };
453838
454296
  for (const file of files) {
@@ -453885,7 +454343,7 @@ async function buildMemfsV2ParentMemorySnapshot(memoryDir, requestedMaxChars) {
453885
454343
  `).length - prefix.length - suffix.length - 20;
453886
454344
  if (remaining <= 0)
453887
454345
  break;
453888
- const content = (await readFile21(file.fullPath, "utf-8")).slice(0, remaining);
454346
+ const content = (await readFile22(file.fullPath, "utf-8")).slice(0, remaining);
453889
454347
  lines.push(`${prefix}${content}${suffix}`);
453890
454348
  }
453891
454349
  lines.push("</parent_memory>");
@@ -453903,7 +454361,7 @@ import {
453903
454361
  appendFile,
453904
454362
  mkdir as mkdir14,
453905
454363
  readdir as readdir9,
453906
- readFile as readFile22,
454364
+ readFile as readFile23,
453907
454365
  stat as stat13,
453908
454366
  writeFile as writeFile16
453909
454367
  } from "node:fs/promises";
@@ -453951,7 +454409,7 @@ async function collectParentMemoryFiles(memoryDir) {
453951
454409
  continue;
453952
454410
  }
453953
454411
  try {
453954
- const content = await readFile22(entryPath, "utf-8");
454412
+ const content = await readFile23(entryPath, "utf-8");
453955
454413
  const { frontmatter } = parseFrontmatter(content);
453956
454414
  const description = typeof frontmatter.description === "string" ? frontmatter.description : undefined;
453957
454415
  files.push({
@@ -454254,7 +454712,7 @@ async function ensurePaths(paths) {
454254
454712
  }
454255
454713
  async function readTranscriptLines(paths) {
454256
454714
  try {
454257
- const raw2 = await readFile22(paths.transcriptPath, "utf-8");
454715
+ const raw2 = await readFile23(paths.transcriptPath, "utf-8");
454258
454716
  return raw2.split(`
454259
454717
  `).map((line) => line.trim()).filter((line) => line.length > 0);
454260
454718
  } catch {
@@ -454327,7 +454785,7 @@ function buildUnreflectedStateFromTranscript(lines) {
454327
454785
  async function readState(paths) {
454328
454786
  let raw2 = null;
454329
454787
  try {
454330
- raw2 = await readFile22(paths.statePath, "utf-8");
454788
+ raw2 = await readFile23(paths.statePath, "utf-8");
454331
454789
  } catch {
454332
454790
  raw2 = null;
454333
454791
  }
@@ -454659,7 +455117,7 @@ function isReflectionAutoPriority(value) {
454659
455117
  return value === "high" || value === "medium" || value === "low";
454660
455118
  }
454661
455119
  async function readReflectionAutoSelection(options) {
454662
- const raw2 = options.selectionReport ?? (options.selectionOutputPath ? await readFile22(options.selectionOutputPath, "utf-8") : "");
455120
+ const raw2 = options.selectionReport ?? (options.selectionOutputPath ? await readFile23(options.selectionOutputPath, "utf-8") : "");
454663
455121
  const parsed = parseReflectionAutoSelectionJson(raw2);
454664
455122
  if (!parsed || typeof parsed !== "object") {
454665
455123
  throw new Error("Reflection selector did not return valid JSON.");
@@ -456368,36 +456826,6 @@ var init_turn_send = __esm(async () => {
456368
456826
  ]);
456369
456827
  });
456370
456828
 
456371
- // src/agent/preloaded-skills.ts
456372
- import { readFile as readFile23 } from "node:fs/promises";
456373
- async function loadPreloadedSkills(ids, options = {}) {
456374
- if (ids.length === 0)
456375
- return "";
456376
- const { skillPathById } = await buildClientSkillsPayload({
456377
- ...options,
456378
- skillSources: options.skillSources?.length === 0 ? ["bundled"] : options.skillSources
456379
- });
456380
- const contents = [];
456381
- for (const id2 of ids) {
456382
- const path42 = skillPathById[id2];
456383
- if (!path42)
456384
- continue;
456385
- try {
456386
- contents.push(`<${id2}>
456387
- ${await readFile23(path42, "utf-8")}
456388
- </${id2}>`);
456389
- } catch {}
456390
- }
456391
- return contents.length ? `<loaded_skills>
456392
- ${contents.join(`
456393
-
456394
- `)}
456395
- </loaded_skills>` : "";
456396
- }
456397
- var init_preloaded_skills = __esm(() => {
456398
- init_client_skills();
456399
- });
456400
-
456401
456829
  // src/reminders/listen-context.ts
456402
456830
  function buildListenReminderContext(params) {
456403
456831
  return {
@@ -457222,7 +457650,7 @@ async function handleIncomingMessageInner(msg, socket, runtime, onStatusChange,
457222
457650
  conversationId
457223
457651
  });
457224
457652
  try {
457225
- const agent = await getBackend().retrieveAgent(agentId || "");
457653
+ const agent = await retrieveResumeState(agentId, conversationId);
457226
457654
  const { pendingApprovals: existingApprovals } = await getResumeDataFromBackend(agent, requestedConversationId);
457227
457655
  turnInput = rebuildTurnInputWithFreshDenials(turnInput, existingApprovals ?? [], STALE_APPROVAL_RECOVERY_DENIAL_REASON, isApprovalPendingError(errorDetail3));
457228
457656
  } catch {
@@ -457635,8 +458063,8 @@ var init_turn = __esm(async () => {
457635
458063
  init_approval_recovery();
457636
458064
  init_chatgpt_plan_rotation();
457637
458065
  init_check_approval();
458066
+ init_ephemeral_conversation();
457638
458067
  init_turn_recovery_policy();
457639
- init_backend2();
457640
458068
  init_accumulator();
457641
458069
  init_telemetry();
457642
458070
  init_error_reporting();
@@ -468684,7 +469112,7 @@ var init_memory_tokens = __esm(() => {
468684
469112
  });
468685
469113
 
468686
469114
  // src/cli/subcommands/memory.ts
468687
- import { cpSync, existsSync as existsSync64, mkdirSync as mkdirSync46, rmSync as rmSync22, statSync as statSync23 } from "node:fs";
469115
+ import { cpSync, existsSync as existsSync64, mkdirSync as mkdirSync46, rmSync as rmSync22, statSync as statSync24 } from "node:fs";
468688
469116
  import { readdir as readdir12 } from "node:fs/promises";
468689
469117
  import { dirname as dirname35, join as join94 } from "node:path";
468690
469118
  import { parseArgs as parseArgs11 } from "node:util";
@@ -468759,7 +469187,7 @@ async function listBackups(agentId) {
468759
469187
  const path46 = join94(agentRoot, entry.name);
468760
469188
  let createdAt = null;
468761
469189
  try {
468762
- const stat15 = statSync23(path46);
469190
+ const stat15 = statSync24(path46);
468763
469191
  createdAt = stat15.mtime.toISOString();
468764
469192
  } catch {
468765
469193
  createdAt = null;
@@ -468886,7 +469314,7 @@ async function runMemorySubcommand(argv, deps = {}) {
468886
469314
  console.error(`Backup not found: ${backupPath}`);
468887
469315
  return 1;
468888
469316
  }
468889
- const stat15 = statSync23(backupPath);
469317
+ const stat15 = statSync24(backupPath);
468890
469318
  if (!stat15.isDirectory()) {
468891
469319
  console.error(`Backup path is not a directory: ${backupPath}`);
468892
469320
  return 1;
@@ -468909,7 +469337,7 @@ async function runMemorySubcommand(argv, deps = {}) {
468909
469337
  return 1;
468910
469338
  }
468911
469339
  if (existsSync64(out)) {
468912
- const stat15 = statSync23(out);
469340
+ const stat15 = statSync24(out);
468913
469341
  if (stat15.isDirectory()) {
468914
469342
  const contents = await readdir12(out);
468915
469343
  if (contents.length > 0) {
@@ -468957,7 +469385,7 @@ var init_memory7 = __esm(() => {
468957
469385
  });
468958
469386
 
468959
469387
  // src/backend/local/transcript-search.ts
468960
- import { existsSync as existsSync65, readdirSync as readdirSync27, readFileSync as readFileSync48, statSync as statSync24 } from "node:fs";
469388
+ import { existsSync as existsSync65, readdirSync as readdirSync27, readFileSync as readFileSync48, statSync as statSync25 } from "node:fs";
468961
469389
  import { join as join95 } from "node:path";
468962
469390
  function readJsonFile3(path46) {
468963
469391
  if (!existsSync65(path46))
@@ -469245,7 +469673,7 @@ function conversationDirectories(storageDir) {
469245
469673
  if (!existsSync65(conversationsDir))
469246
469674
  return [];
469247
469675
  try {
469248
- return readdirSync27(conversationsDir).map((entry) => join95(conversationsDir, entry)).filter((entryPath) => statSync24(entryPath).isDirectory());
469676
+ return readdirSync27(conversationsDir).map((entry) => join95(conversationsDir, entry)).filter((entryPath) => statSync25(entryPath).isDirectory());
469249
469677
  } catch {
469250
469678
  return [];
469251
469679
  }
@@ -474459,7 +474887,7 @@ import {
474459
474887
  mkdtempSync as mkdtempSync6,
474460
474888
  readFileSync as readFileSync50,
474461
474889
  rmSync as rmSync25,
474462
- statSync as statSync25,
474890
+ statSync as statSync26,
474463
474891
  writeFileSync as writeFileSync41
474464
474892
  } from "node:fs";
474465
474893
  import { mkdir as mkdir16, readdir as readdir13 } from "node:fs/promises";
@@ -474917,7 +475345,7 @@ async function installSkillDirectory(params) {
474917
475345
  if (!existsSync69(skillMdPath)) {
474918
475346
  throw new Error("No SKILL.md found in the skill directory.");
474919
475347
  }
474920
- if (!statSync25(sourceDir).isDirectory()) {
475348
+ if (!statSync26(sourceDir).isDirectory()) {
474921
475349
  throw new Error(`Skill source is not a directory: ${sourceDir}`);
474922
475350
  }
474923
475351
  const name = getSkillName(sourceDir);
@@ -474976,7 +475404,7 @@ async function deleteSkillDirectory(params) {
474976
475404
  if (!existsSync69(targetPath)) {
474977
475405
  throw new Error(`Skill "${name}" is not installed at ${targetPath}.`);
474978
475406
  }
474979
- if (!statSync25(targetPath).isDirectory()) {
475407
+ if (!statSync26(targetPath).isDirectory()) {
474980
475408
  throw new Error(`Skill path is not a directory: ${targetPath}`);
474981
475409
  }
474982
475410
  rmSync25(targetPath, { recursive: true, force: true });
@@ -476874,7 +477302,7 @@ function firstDefinedBoolean(...values4) {
476874
477302
  }
476875
477303
  function normalizeChatTarget(channel, value) {
476876
477304
  const trimmed = value.trim();
476877
- if (channel === "signal")
477305
+ if (channel === "signal" || channel === "microsoftteams")
476878
477306
  return trimmed;
476879
477307
  const parts = trimmed.split(":").map((part) => part.trim()).filter(Boolean);
476880
477308
  if (parts.length === 2 && /^[a-z_-]+$/i.test(parts[0] ?? "")) {
@@ -482031,88 +482459,6 @@ var init_headless_cloud_send = __esm(() => {
482031
482459
  init_headless_environment_response();
482032
482460
  });
482033
482461
 
482034
- // src/agent/ephemeral-conversation.ts
482035
- async function buildEphemeralConversationCreateBody(options) {
482036
- const system2 = options.systemPromptCustom ? options.systemPromptCustom : await resolveAndBuildSystemPrompt(options.systemPromptPreset, options.memoryPromptMode ?? "standard");
482037
- const request3 = await buildCreateAgentRequest({
482038
- model: options.model,
482039
- system: system2,
482040
- memoryPromptMode: "standard",
482041
- enableMemfs: false,
482042
- isSubagent: true,
482043
- baseTools: []
482044
- });
482045
- const modelSettings = options.model ? getModelUpdateArgs(options.model) : undefined;
482046
- const contextWindow = modelSettings?.context_window ?? await getModelContextWindow(request3.model);
482047
- return {
482048
- model: request3.model,
482049
- system: request3.system ?? system2,
482050
- ...modelSettings ? { model_settings: modelSettings } : {},
482051
- ...contextWindow ? { context_window_limit: contextWindow } : {}
482052
- };
482053
- }
482054
- function projectEphemeralAgent(conversationId, body3) {
482055
- return {
482056
- id: conversationId,
482057
- name: "Ephemeral conversation",
482058
- system: body3.system,
482059
- tools: [],
482060
- memory: { blocks: [] },
482061
- llm_config: {
482062
- handle: body3.model,
482063
- model: body3.model,
482064
- context_window: body3.context_window_limit ?? undefined,
482065
- model_settings: body3.model_settings ?? {}
482066
- },
482067
- model_settings: body3.model_settings ?? {},
482068
- message_buffer_autoclear: false
482069
- };
482070
- }
482071
- async function createEphemeralConversation2(options) {
482072
- const body3 = await buildEphemeralConversationCreateBody(options);
482073
- const conversation = await createEphemeralConversation(body3);
482074
- return {
482075
- agent: projectEphemeralAgent(conversation.id, body3),
482076
- conversationId: conversation.id
482077
- };
482078
- }
482079
- async function createLocalEphemeralConversation(options) {
482080
- const body3 = await buildEphemeralConversationCreateBody(options);
482081
- const backend3 = getBackend();
482082
- const internalAgent = await backend3.createAgent({
482083
- agent_type: "letta_v1_agent",
482084
- name: "Ephemeral conversation",
482085
- model: body3.model,
482086
- system: body3.system,
482087
- memory_blocks: [],
482088
- tags: [],
482089
- tools: [],
482090
- include_base_tools: false,
482091
- include_base_tool_rules: false,
482092
- initial_message_sequence: [],
482093
- parallel_tool_calls: true,
482094
- hidden: true
482095
- });
482096
- const conversation = await backend3.createConversation({
482097
- agent_id: internalAgent.id,
482098
- model: body3.model,
482099
- ...body3.model_settings ? { model_settings: body3.model_settings } : {},
482100
- ...body3.context_window_limit ? { context_window_limit: body3.context_window_limit } : {}
482101
- });
482102
- return {
482103
- agent: internalAgent,
482104
- conversationId: conversation.id
482105
- };
482106
- }
482107
- var init_ephemeral_conversation = __esm(() => {
482108
- init_available_models();
482109
- init_create_agent_request();
482110
- init_model();
482111
- init_system_prompt_resolution();
482112
- init_backend2();
482113
- init_ephemeral_conversations();
482114
- });
482115
-
482116
482462
  // src/headless-ephemeral-startup.ts
482117
482463
  function prepareHeadlessEphemeralBackend(enabled) {
482118
482464
  if (enabled && isLocalBackendEnabled()) {
@@ -482144,6 +482490,18 @@ function clearHeadlessClientToolRules(agent) {
482144
482490
  debugWarn("headless startup", `Failed to clear persisted client tool rules for ${agent.id}: ${error5 instanceof Error ? error5.message : String(error5)}`);
482145
482491
  });
482146
482492
  }
482493
+ async function retrieveHeadlessConversation(backend3, conversationId) {
482494
+ const conversation = await backend3.retrieveConversation(conversationId);
482495
+ const detached = conversation.agent_id === null;
482496
+ const parentId = Reflect.get(conversation, "parent_agent_id");
482497
+ return {
482498
+ agent: detached ? projectConversationState(conversation) : await backend3.retrieveAgent(conversation.agent_id, {
482499
+ include: ["agent.tools", "agent.tags"]
482500
+ }),
482501
+ detached,
482502
+ parentAgentId: detached && typeof parentId === "string" ? parentId : null
482503
+ };
482504
+ }
482147
482505
  var init_headless_ephemeral_startup = __esm(async () => {
482148
482506
  init_ephemeral_conversation();
482149
482507
  init_backend2();
@@ -482929,6 +483287,8 @@ In headless mode, use:
482929
483287
  let specifiedAgentIdFromAmbient = false;
482930
483288
  const forceNew = values4["new-agent"];
482931
483289
  const ephemeralFlag = values4.ephemeral;
483290
+ let isDetached = Boolean(ephemeralFlag);
483291
+ let parentAgentId = null;
482932
483292
  const systemPromptPreset = values4.system;
482933
483293
  const systemCustom = values4["system-custom"];
482934
483294
  const personalityInput = values4.personality;
@@ -483089,10 +483449,11 @@ In headless mode, use:
483089
483449
  if (specifiedConversationId && specifiedConversationId !== "default") {
483090
483450
  try {
483091
483451
  debugLog("conversations", `retrieve(${specifiedConversationId}) [headless conv→agent lookup]`);
483092
- const conversation = await startupBackend.retrieveConversation(specifiedConversationId);
483093
- agent = await startupBackend.retrieveAgent(conversation.agent_id, {
483094
- include: ["agent.tools", "agent.tags"]
483095
- });
483452
+ ({
483453
+ agent,
483454
+ detached: isDetached,
483455
+ parentAgentId
483456
+ } = await retrieveHeadlessConversation(startupBackend, specifiedConversationId));
483096
483457
  } catch (error5) {
483097
483458
  trackHeadlessBoundaryError("headless_conversation_lookup_failed", error5, "headless_startup_conversation_lookup");
483098
483459
  console.error(`Conversation ${specifiedConversationId} not found`);
@@ -483215,9 +483576,9 @@ Current session AGENT_ID=${process.env.AGENT_ID}; --backend local switches to a
483215
483576
  process.exit(1);
483216
483577
  }
483217
483578
  markMilestone("HEADLESS_AGENT_RESOLVED");
483218
- const publicAgentId = ephemeralFlag ? null : agent.id;
483579
+ const publicAgentId = isDetached ? null : agent.id;
483219
483580
  telemetry.setCurrentAgent(publicAgentId, agent.tags);
483220
- const isResumingAgent = !ephemeralFlag && !!(specifiedAgentId || !forceNew);
483581
+ const isResumingAgent = !isDetached && !!(specifiedAgentId || !forceNew);
483221
483582
  let conversationModel;
483222
483583
  if (isResumingAgent) {
483223
483584
  const modelHandle = model ? resolveModel(model) : undefined;
@@ -483240,7 +483601,7 @@ Current session AGENT_ID=${process.env.AGENT_ID}; --backend local switches to a
483240
483601
  let effectiveReflectionSettings;
483241
483602
  const isSubagent = process.env.LETTA_CODE_AGENT_ROLE === "subagent";
483242
483603
  let startupMemfsFlag = autoEnableMemfsForFreshAgent ? true : memfsFlag;
483243
- if (!isStatelessSession && backend3.capabilities.remoteMemfs && !autoEnableMemfsForFreshAgent) {
483604
+ if (!isStatelessSession && !isDetached && backend3.capabilities.remoteMemfs && !autoEnableMemfsForFreshAgent) {
483244
483605
  const { hydrateMemfsSettingFromAgent: hydrateMemfsSettingFromAgent2, isLettaCloud: isLettaCloud2 } = await Promise.resolve().then(() => (init_memory_filesystem2(), exports_memory_filesystem));
483245
483606
  const memfsEnabled = await hydrateMemfsSettingFromAgent2(agent);
483246
483607
  if (!memfsEnabled && await isLettaCloud2()) {
@@ -483248,9 +483609,9 @@ Current session AGENT_ID=${process.env.AGENT_ID}; --backend local switches to a
483248
483609
  }
483249
483610
  }
483250
483611
  let memfsBgPromise;
483251
- const secretsAgentId = ephemeralFlag ? undefined : agent?.id;
483612
+ const secretsAgentId = isDetached ? undefined : agent?.id;
483252
483613
  const secretsInitPromise = secretsAgentId ? Promise.resolve().then(() => (init_secrets_store(), exports_secrets_store)).then(({ initSecretsFromServer: initSecretsFromServer2 }) => initSecretsFromServer2(secretsAgentId)) : Promise.resolve();
483253
- if (isStatelessSession) {
483614
+ if (isStatelessSession || isDetached) {
483254
483615
  settingsManager.setMemfsEnabled(agent.id, false);
483255
483616
  } else if (!backend3.capabilities.remoteMemfs) {
483256
483617
  if (backend3.capabilities.localMemfs) {
@@ -483332,11 +483693,11 @@ Current session AGENT_ID=${process.env.AGENT_ID}; --backend local switches to a
483332
483693
  memoryMode: getMemoryPromptModeForAgent2(taggedAgent.id)
483333
483694
  });
483334
483695
  }
483335
- if (!ephemeralFlag) {
483696
+ if (!isDetached) {
483336
483697
  clearHeadlessClientToolRules(agent);
483337
483698
  }
483338
483699
  try {
483339
- if (ephemeralFlag) {
483700
+ if (isDetached) {
483340
483701
  effectiveReflectionSettings = { trigger: "off", stepCount: 0 };
483341
483702
  } else {
483342
483703
  const resolvedReflectionSettings = await applyHeadlessReflectionOverrides(agent.id, reflectionOverrides);
@@ -483388,7 +483749,7 @@ Current session AGENT_ID=${process.env.AGENT_ID}; --backend local switches to a
483388
483749
  }
483389
483750
  markMilestone("HEADLESS_CONVERSATION_READY");
483390
483751
  setConversationId(conversationId);
483391
- if (!ephemeralFlag && shouldPersistSessionState()) {
483752
+ if (!isDetached && shouldPersistSessionState()) {
483392
483753
  await settingsManager.loadLocalProjectSettings();
483393
483754
  settingsManager.persistSession(agent.id, conversationId);
483394
483755
  }
@@ -483530,7 +483891,7 @@ Current session AGENT_ID=${process.env.AGENT_ID}; --backend local switches to a
483530
483891
  const resolveAllPendingApprovals = async (mode = "send_immediately") => {
483531
483892
  const { getResumeDataFromBackend: getResumeDataFromBackend2 } = await Promise.resolve().then(() => (init_check_approval(), exports_check_approval));
483532
483893
  while (true) {
483533
- const freshAgent = ephemeralFlag ? agent : await backend3.retrieveAgent(agent.id);
483894
+ const freshAgent = isDetached ? agent : await backend3.retrieveAgent(agent.id);
483534
483895
  let resume;
483535
483896
  try {
483536
483897
  resume = await getResumeDataFromBackend2(freshAgent, conversationId);
@@ -483590,7 +483951,7 @@ Current session AGENT_ID=${process.env.AGENT_ID}; --backend local switches to a
483590
483951
  }
483591
483952
  }
483592
483953
  };
483593
- if (isResumingAgent) {
483954
+ if (isResumingAgent || !usesRemoteEnvironment && isDetached && !ephemeralFlag) {
483594
483955
  try {
483595
483956
  await resolveAllPendingApprovals("queue_for_next_turn");
483596
483957
  } catch (approvalError) {
@@ -483650,9 +484011,12 @@ Current session AGENT_ID=${process.env.AGENT_ID}; --backend local switches to a
483650
484011
  pushPart(prompt);
483651
484012
  telemetry.trackUserInput(prompt, "user", reportModel ?? "unknown");
483652
484013
  if (usesRemoteEnvironment) {
484014
+ const agentId = publicAgentId ?? parentAgentId;
484015
+ if (!agentId)
484016
+ throw new Error("Listener delivery requires a conversation with an agent or parent agent.");
483653
484017
  const environmentSelector = explicitEnvironmentSelector ?? "";
483654
484018
  const useCloudSandbox = isCloudEnvironmentSelector(environmentSelector);
483655
- const environmentRouting = environmentSelector ? useCloudSandbox ? await resolveAgentSandboxConnectionId(agent.id, { conversationId }) : await resolveEnvironmentConnectionId(environmentSelector) : null;
484019
+ const environmentRouting = environmentSelector ? useCloudSandbox ? await resolveAgentSandboxConnectionId(agentId, { conversationId }) : await resolveEnvironmentConnectionId(environmentSelector) : null;
483656
484020
  const connectionId = environmentRouting?.connectionId ?? inheritedListenerConnectionId;
483657
484021
  if (!connectionId)
483658
484022
  throw new Error("No listener connection was resolved for this launch");
@@ -483666,8 +484030,9 @@ Current session AGENT_ID=${process.env.AGENT_ID}; --backend local switches to a
483666
484030
  noWait: isAgentLaunch || Boolean(values4["no-wait"]),
483667
484031
  clientMessageId: values4["client-message-id"],
483668
484032
  connectionId,
484033
+ parentAgentId: parentAgentId ?? undefined,
483669
484034
  scope: {
483670
- agent_id: agent.id,
484035
+ agent_id: publicAgentId,
483671
484036
  conversation_id: conversationId,
483672
484037
  acting_user_id: resolveActingUserId()
483673
484038
  },
@@ -484403,6 +484768,9 @@ Current session AGENT_ID=${process.env.AGENT_ID}; --backend local switches to a
484403
484768
  await exitHeadless(1, "headless_stop_reason_error");
484404
484769
  }
484405
484770
  } catch (error5) {
484771
+ if (sigintSignal.aborted) {
484772
+ await exitInterrupted();
484773
+ }
484406
484774
  markIncompleteToolsAsCancelled(buffers, true, "stream_error");
484407
484775
  const errorDetails = formatErrorDetails2(error5, agent.id);
484408
484776
  trackHeadlessBoundaryError("headless_runtime_exception", error5, "headless_turn_execution");
@@ -484817,6 +485185,30 @@ async function runBidirectionalMode(agent, conversationId, effectiveModel, _outp
484817
485185
  lineResolver = resolve42;
484818
485186
  });
484819
485187
  }
485188
+ const EXTERNAL_TOOL_WAIT_ABORTED = Symbol("externalToolWaitAborted");
485189
+ async function getNextLineOrAbort(signal) {
485190
+ if (signal?.aborted)
485191
+ return EXTERNAL_TOOL_WAIT_ABORTED;
485192
+ if (lineQueue.length > 0)
485193
+ return lineQueue.shift() ?? null;
485194
+ if (!signal)
485195
+ return getNextLine();
485196
+ return new Promise((resolve42) => {
485197
+ const cleanup = () => signal.removeEventListener("abort", onAbort);
485198
+ const onAbort = () => {
485199
+ cleanup();
485200
+ if (lineResolver === settle3)
485201
+ lineResolver = null;
485202
+ resolve42(EXTERNAL_TOOL_WAIT_ABORTED);
485203
+ };
485204
+ const settle3 = (line) => {
485205
+ cleanup();
485206
+ resolve42(line);
485207
+ };
485208
+ signal.addEventListener("abort", onAbort);
485209
+ lineResolver = settle3;
485210
+ });
485211
+ }
484820
485212
  async function requestPermission(toolCallId, toolName2, toolInput) {
484821
485213
  const requestId = `perm-${toolCallId}`;
484822
485214
  const diffs = await computeDiffPreviews(toolName2, toolInput);
@@ -484977,7 +485369,14 @@ async function runBidirectionalMode(agent, conversationId, effectiveModel, _outp
484977
485369
  const toolsRequest = message2.request;
484978
485370
  const tools = toolsRequest.tools ?? [];
484979
485371
  registerExternalTools(tools);
484980
- setExternalToolExecutor(async (toolCallId, toolName2, input) => {
485372
+ setExternalToolExecutor(async (toolCallId, toolName2, input, context3) => {
485373
+ const signal = context3?.signal;
485374
+ if (signal?.aborted) {
485375
+ return {
485376
+ content: [{ type: "text", text: INTERRUPTED_BY_USER }],
485377
+ isError: true
485378
+ };
485379
+ }
484981
485380
  const execRequest = {
484982
485381
  type: "control_request",
484983
485382
  request_id: `ext-${toolCallId}`,
@@ -484989,25 +485388,48 @@ async function runBidirectionalMode(agent, conversationId, effectiveModel, _outp
484989
485388
  }
484990
485389
  };
484991
485390
  writeWireMessage(execRequest);
484992
- while (true) {
484993
- const line2 = await getNextLine();
484994
- if (line2 === null) {
484995
- return {
484996
- content: [{ type: "text", text: "stdin closed" }],
484997
- isError: true
484998
- };
484999
- }
485000
- if (!line2.trim())
485001
- continue;
485002
- try {
485003
- const msg = JSON.parse(line2);
485004
- if (msg.type === "control_response" && msg.response?.subtype === "external_tool_result" && msg.response?.tool_call_id === toolCallId) {
485391
+ const deferredLines = [];
485392
+ try {
485393
+ while (true) {
485394
+ const line2 = await getNextLineOrAbort(signal);
485395
+ if (line2 === EXTERNAL_TOOL_WAIT_ABORTED) {
485005
485396
  return {
485006
- content: msg.response.content ?? [{ type: "text", text: "" }],
485007
- isError: msg.response.is_error ?? false
485397
+ content: [{ type: "text", text: INTERRUPTED_BY_USER }],
485398
+ isError: true
485008
485399
  };
485009
485400
  }
485010
- } catch {}
485401
+ if (line2 === null) {
485402
+ return {
485403
+ content: [
485404
+ {
485405
+ type: "text",
485406
+ text: currentAbortController?.signal.aborted ? INTERRUPTED_BY_USER : "stdin closed"
485407
+ }
485408
+ ],
485409
+ isError: true
485410
+ };
485411
+ }
485412
+ if (!line2.trim())
485413
+ continue;
485414
+ try {
485415
+ const msg = JSON.parse(line2);
485416
+ if (msg.type === "control_response" && msg.response?.subtype === "external_tool_result") {
485417
+ if (msg.response.tool_call_id === toolCallId) {
485418
+ return {
485419
+ content: msg.response.content ?? [
485420
+ { type: "text", text: "" }
485421
+ ],
485422
+ isError: msg.response.is_error ?? false
485423
+ };
485424
+ }
485425
+ continue;
485426
+ }
485427
+ } catch {}
485428
+ deferredLines.push(line2);
485429
+ }
485430
+ } finally {
485431
+ if (deferredLines.length > 0)
485432
+ lineQueue.unshift(...deferredLines);
485011
485433
  }
485012
485434
  });
485013
485435
  const registerResponse = {
@@ -485427,10 +485849,18 @@ async function runBidirectionalMode(agent, conversationId, effectiveModel, _outp
485427
485849
  }
485428
485850
  const { executeApprovalBatch: executeApprovalBatch2 } = await init_approval_execution().then(() => exports_approval_execution);
485429
485851
  emitLocalToolCalls(decisions, sessionId);
485430
- const executedResults = await executeApprovalBatch2(decisions, undefined, {
485431
- toolContextId: turnToolContextId ?? undefined,
485432
- abortSignal: currentAbortController.signal
485433
- });
485852
+ let executedResults;
485853
+ try {
485854
+ executedResults = await executeApprovalBatch2(decisions, undefined, {
485855
+ toolContextId: turnToolContextId ?? undefined,
485856
+ abortSignal: currentAbortController.signal
485857
+ });
485858
+ } catch (error5) {
485859
+ if (currentAbortController?.signal.aborted) {
485860
+ break;
485861
+ }
485862
+ throw error5;
485863
+ }
485434
485864
  emitLocalToolReturns(executedResults, sessionId);
485435
485865
  currentInput = responseState.prepare(executedResults, needsUserInput.length === 0);
485436
485866
  continue;
@@ -485548,6 +485978,7 @@ var LLM_API_ERROR_MAX_RETRIES2 = 3, EMPTY_RESPONSE_MAX_RETRIES2 = 2, HEADLESS_ST
485548
485978
  var init_headless = __esm(async () => {
485549
485979
  init_error();
485550
485980
  init_preloaded_skills();
485981
+ init_constants2();
485551
485982
  init_headless_message_sender();
485552
485983
  init_headless_response_state();
485553
485984
  init_headless_startup_backend();
@@ -497505,7 +497936,6 @@ var init_SlashCommandAutocomplete = __esm(async () => {
497505
497936
  "creating-skills",
497506
497937
  "customizing-statusline",
497507
497938
  "initializing-memory",
497508
- "migrating-memory",
497509
497939
  "syncing-memory-filesystem"
497510
497940
  ]);
497511
497941
  _allCommands = Object.entries(commands).filter(([, { hidden }]) => !hidden).map(([cmd, { desc, order }]) => ({
@@ -498502,6 +498932,7 @@ function Input({
498502
498932
  queueMode = "immediate",
498503
498933
  deferModeSupported = false,
498504
498934
  interruptRequested = false,
498935
+ hasStopFailedTasks = false,
498505
498936
  agentId,
498506
498937
  agentName,
498507
498938
  currentModel,
@@ -498801,7 +499232,7 @@ function Input({
498801
499232
  onBashInterrupt();
498802
499233
  return;
498803
499234
  }
498804
- if ((streaming3 || interruptRequested) && onInterrupt) {
499235
+ if ((streaming3 || interruptRequested || hasStopFailedTasks) && onInterrupt) {
498805
499236
  onInterrupt();
498806
499237
  return;
498807
499238
  }
@@ -514807,6 +515238,7 @@ function AppView(props) {
514807
515238
  inputEnabled,
514808
515239
  inputVisible,
514809
515240
  interruptRequested,
515241
+ hasStopFailedTasks,
514810
515242
  isAgentBusy,
514811
515243
  liveItems,
514812
515244
  liveTrajectoryElapsedBaseMs,
@@ -515035,6 +515467,7 @@ function AppView(props) {
515035
515467
  queueMode,
515036
515468
  deferModeSupported,
515037
515469
  interruptRequested,
515470
+ hasStopFailedTasks,
515038
515471
  agentId,
515039
515472
  agentName,
515040
515473
  currentModel: currentModelDisplay,
@@ -516193,6 +516626,12 @@ function useApprovalFlow(ctx) {
516193
516626
  startupApprovals
516194
516627
  ]);
516195
516628
  const sendAllResults = import_react113.useCallback(async (additionalDecision) => {
516629
+ const approvalAbortController = new AbortController;
516630
+ const approvalResultsSnapshot = [...approvalResults];
516631
+ const autoHandledSnapshot = [...autoHandledResults];
516632
+ const autoDeniedSnapshot = [...autoDeniedApprovals];
516633
+ const pendingSnapshot = [...pendingApprovals];
516634
+ const companionResults = buildCompanionResults(autoHandledSnapshot, autoDeniedSnapshot);
516196
516635
  try {
516197
516636
  if (userCancelledRef.current || abortControllerRef.current?.signal.aborted) {
516198
516637
  setStreaming(false);
@@ -516202,12 +516641,13 @@ function useApprovalFlow(ctx) {
516202
516641
  setApprovalResults([]);
516203
516642
  setAutoHandledResults([]);
516204
516643
  setAutoDeniedApprovals([]);
516644
+ clearApprovalToolContext();
516645
+ toolAbortControllerRef.current = null;
516646
+ executingToolCallIdsRef.current = [];
516647
+ interruptQueuedRef.current = false;
516648
+ toolResultsInFlightRef.current = false;
516205
516649
  return;
516206
516650
  }
516207
- const approvalResultsSnapshot = [...approvalResults];
516208
- const autoHandledSnapshot = [...autoHandledResults];
516209
- const autoDeniedSnapshot = [...autoDeniedApprovals];
516210
- const pendingSnapshot = [...pendingApprovals];
516211
516651
  setPendingApprovals([]);
516212
516652
  setApprovalContexts([]);
516213
516653
  setApprovalResults([]);
@@ -516216,7 +516656,6 @@ function useApprovalFlow(ctx) {
516216
516656
  setStreaming(true);
516217
516657
  openTrajectorySegment();
516218
516658
  buffersRef.current.interrupted = false;
516219
- const approvalAbortController = new AbortController;
516220
516659
  toolAbortControllerRef.current = approvalAbortController;
516221
516660
  const allDecisions = [
516222
516661
  ...approvalResultsSnapshot,
@@ -516258,23 +516697,10 @@ function useApprovalFlow(ctx) {
516258
516697
  localToolMs: toolRunMs
516259
516698
  });
516260
516699
  }
516261
- const allResults = [
516262
- ...autoHandledSnapshot.map((ar) => ({
516263
- type: "tool",
516264
- tool_call_id: ar.toolCallId,
516265
- tool_return: ar.result.toolReturn,
516266
- status: ar.result.status,
516267
- stdout: ar.result.stdout,
516268
- stderr: ar.result.stderr
516269
- })),
516270
- ...autoDeniedSnapshot.map((ad) => ({
516271
- type: "approval",
516272
- tool_call_id: ad.approval.toolCallId,
516273
- approve: false,
516274
- reason: ad.reason
516275
- })),
516276
- ...executedResults
516277
- ];
516700
+ if (toolAbortControllerRef.current !== approvalAbortController) {
516701
+ return;
516702
+ }
516703
+ const allResults = [...companionResults, ...executedResults];
516278
516704
  if (true) {
516279
516705
  const expectedIds = new Set([
516280
516706
  ...autoHandledSnapshot.map((ar) => ar.toolCallId),
@@ -516293,9 +516719,7 @@ function useApprovalFlow(ctx) {
516293
516719
  const wasAborted = approvalAbortController.signal.aborted;
516294
516720
  const userCancelled = userCancelledRef.current;
516295
516721
  if (wasAborted || userCancelled) {
516296
- if (!interruptQueuedRef.current) {
516297
- queueApprovalResults(allResults);
516298
- }
516722
+ queueApprovalResults(allResults);
516299
516723
  setStreaming(false);
516300
516724
  closeTrajectorySegment();
516301
516725
  syncTrajectoryElapsedBase();
@@ -516316,6 +516740,24 @@ function useApprovalFlow(ctx) {
516316
516740
  queueApprovalResults(null);
516317
516741
  }
516318
516742
  } catch (error5) {
516743
+ if (toolAbortControllerRef.current !== approvalAbortController) {
516744
+ return;
516745
+ }
516746
+ if (isUserAbortError(error5)) {
516747
+ markIncompleteToolsAsCancelled(buffersRef.current, true, "user_interrupt");
516748
+ const merged = overlayPartialResults(queuedApprovalResultsRef.current, [
516749
+ ...error5.partialResults ?? [],
516750
+ ...companionResults
516751
+ ]);
516752
+ if (merged.length > 0) {
516753
+ queueApprovalResults(merged);
516754
+ }
516755
+ setStreaming(false);
516756
+ closeTrajectorySegment();
516757
+ syncTrajectoryElapsedBase();
516758
+ refreshDerived();
516759
+ return;
516760
+ }
516319
516761
  markIncompleteToolsAsCancelled(buffersRef.current, true, "stream_error");
516320
516762
  const errorDetails = formatErrorDetails2(error5, agentId);
516321
516763
  appendError(errorDetails, {
@@ -516327,12 +516769,14 @@ function useApprovalFlow(ctx) {
516327
516769
  syncTrajectoryElapsedBase();
516328
516770
  refreshDerived();
516329
516771
  } finally {
516330
- clearApprovalToolContext();
516331
- setIsExecutingTool(false);
516332
- toolAbortControllerRef.current = null;
516333
- executingToolCallIdsRef.current = [];
516334
- interruptQueuedRef.current = false;
516335
- toolResultsInFlightRef.current = false;
516772
+ if (toolAbortControllerRef.current === approvalAbortController) {
516773
+ clearApprovalToolContext();
516774
+ setIsExecutingTool(false);
516775
+ toolAbortControllerRef.current = null;
516776
+ executingToolCallIdsRef.current = [];
516777
+ interruptQueuedRef.current = false;
516778
+ toolResultsInFlightRef.current = false;
516779
+ }
516336
516780
  }
516337
516781
  }, [
516338
516782
  agentId,
@@ -516660,7 +517104,10 @@ var init_use_approval_flow = __esm(async () => {
516660
517104
  init_debug();
516661
517105
  init_notifications();
516662
517106
  init_turn_input();
516663
- await init_manager2();
517107
+ await __promiseAll([
517108
+ init_approval_execution(),
517109
+ init_manager2()
517110
+ ]);
516664
517111
  import_react113 = __toESM(require_react(), 1);
516665
517112
  });
516666
517113
 
@@ -517959,6 +518406,7 @@ function useConversationLoop(ctx) {
517959
518406
  precomputedDiffsRef,
517960
518407
  prepareScopedToolExecutionContext,
517961
518408
  processingConversationRef,
518409
+ queuedApprovalResultsRef,
517962
518410
  queueApprovalResults,
517963
518411
  queueSnapshotRef,
517964
518412
  quotaAutoSwapAttemptedRef,
@@ -518856,7 +519304,11 @@ ${feedback}
518856
519304
  const autoAllowedAbortController = abortControllerRef.current ?? new AbortController;
518857
519305
  const shouldTrackAutoAllowed = autoAllowedToolCallIds.length > 0;
518858
519306
  let autoAllowedResults = [];
518859
- let autoDeniedResults = [];
519307
+ const autoDeniedResults = autoDenied.map((ac) => ({
519308
+ approval: ac.approval,
519309
+ reason: formatPermissionDenial(ac.permission)
519310
+ }));
519311
+ const prebuiltDeniedApprovalResults = buildDeniedResults(autoDeniedResults);
518860
519312
  if (shouldTrackAutoAllowed) {
518861
519313
  setIsExecutingTool(true);
518862
519314
  executingToolCallIdsRef.current = autoAllowedToolCallIds;
@@ -518873,29 +519325,35 @@ ${feedback}
518873
519325
  setToolCallsRunning(buffersRef.current, autoAllowedToolCallIds);
518874
519326
  refreshDerived();
518875
519327
  }
518876
- const approvalToolContextId = approvalToolContextIdRef.current ?? (await prepareScopedToolExecutionContext(tempModelOverrideRef.current ?? undefined)).preparedToolContext.contextId;
518877
- autoAllowedResults = autoAllowed.length > 0 ? await executeAutoAllowedTools(autoAllowed, (chunk2) => onChunk(buffersRef.current, chunk2), {
518878
- abortSignal: autoAllowedAbortController.signal,
518879
- onStreamingOutput: updateStreamingOutput,
518880
- toolContextId: approvalToolContextId
518881
- }) : [];
518882
- autoDeniedResults = autoDenied.map((ac) => {
518883
- const reason = formatPermissionDenial(ac.permission);
519328
+ try {
519329
+ const approvalToolContextId = approvalToolContextIdRef.current ?? (await prepareScopedToolExecutionContext(tempModelOverrideRef.current ?? undefined)).preparedToolContext.contextId;
519330
+ autoAllowedResults = autoAllowed.length > 0 ? await executeAutoAllowedTools(autoAllowed, (chunk2) => onChunk(buffersRef.current, chunk2), {
519331
+ abortSignal: autoAllowedAbortController.signal,
519332
+ onStreamingOutput: updateStreamingOutput,
519333
+ toolContextId: approvalToolContextId
519334
+ }) : [];
519335
+ } catch (error5) {
519336
+ if (shouldTrackAutoAllowed && toolAbortControllerRef.current !== autoAllowedAbortController) {
519337
+ return;
519338
+ }
519339
+ appendAbortPartials(error5, prebuiltDeniedApprovalResults);
519340
+ throw error5;
519341
+ }
519342
+ if (shouldTrackAutoAllowed && toolAbortControllerRef.current !== autoAllowedAbortController) {
519343
+ return;
519344
+ }
519345
+ for (const ad of autoDeniedResults) {
518884
519346
  onChunk(buffersRef.current, {
518885
519347
  message_type: "tool_return_message",
518886
519348
  id: "dummy",
518887
519349
  date: new Date().toISOString(),
518888
- tool_call_id: ac.approval.toolCallId,
518889
- tool_return: `Error: request to call tool denied. User reason: ${reason}`,
519350
+ tool_call_id: ad.approval.toolCallId,
519351
+ tool_return: `Error: request to call tool denied. User reason: ${ad.reason}`,
518890
519352
  status: "error",
518891
519353
  stdout: null,
518892
519354
  stderr: null
518893
519355
  });
518894
- return {
518895
- approval: ac.approval,
518896
- reason
518897
- };
518898
- });
519356
+ }
518899
519357
  const allResults = [
518900
519358
  ...autoAllowedResults.map((ar) => ({
518901
519359
  type: "tool",
@@ -518905,12 +519363,7 @@ ${feedback}
518905
519363
  stdout: ar.result.stdout,
518906
519364
  stderr: ar.result.stderr
518907
519365
  })),
518908
- ...autoDeniedResults.map((ad) => ({
518909
- type: "approval",
518910
- tool_call_id: ad.approval.toolCallId,
518911
- approve: false,
518912
- reason: ad.reason
518913
- }))
519366
+ ...prebuiltDeniedApprovalResults
518914
519367
  ];
518915
519368
  if (autoAllowedExecutionRef.current) {
518916
519369
  autoAllowedExecutionRef.current.results = allResults;
@@ -519008,7 +519461,7 @@ ${feedback}
519008
519461
  return;
519009
519462
  }
519010
519463
  } finally {
519011
- if (shouldTrackAutoAllowed) {
519464
+ if (shouldTrackAutoAllowed && toolAbortControllerRef.current === autoAllowedAbortController) {
519012
519465
  setIsExecutingTool(false);
519013
519466
  toolAbortControllerRef.current = null;
519014
519467
  executingToolCallIdsRef.current = [];
@@ -519342,8 +519795,16 @@ ${feedback}
519342
519795
  }
519343
519796
  } catch (e2) {
519344
519797
  debugWarn("message_stream", "Unhandled conversation error: %s", e2 instanceof Error ? e2.stack ?? e2.message : String(e2));
519345
- markIncompleteToolsAsCancelled(buffersRef.current, true, e2 instanceof APIUserAbortError ? "user_interrupt" : "stream_error");
519346
- if (EAGER_CANCEL && e2 instanceof APIUserAbortError) {
519798
+ if (abortControllerRef.current !== turnAbortController) {
519799
+ return;
519800
+ }
519801
+ markIncompleteToolsAsCancelled(buffersRef.current, true, isUserAbortError(e2) ? "user_interrupt" : "stream_error");
519802
+ if (EAGER_CANCEL && isUserAbortError(e2)) {
519803
+ const partials = e2.partialResults;
519804
+ const overlaid = partials ? overlayPartialResults(queuedApprovalResultsRef.current, partials) : [];
519805
+ if (overlaid.length > 0) {
519806
+ queueApprovalResults(overlaid);
519807
+ }
519347
519808
  setStreaming(false);
519348
519809
  refreshDerived();
519349
519810
  return;
@@ -520102,18 +520563,23 @@ var init_use_feedback_handler = __esm(() => {
520102
520563
  });
520103
520564
 
520104
520565
  // src/cli/app/use-interrupt-handler.ts
520105
- function hasActiveReflectionSubagentForAgent(agentId) {
520566
+ function hasActiveReflectionSubagentForAgent(agentId, conversationId) {
520106
520567
  return getSubagents().some((agent) => {
520107
520568
  if (agent.type.toLowerCase() !== "reflection")
520108
520569
  return false;
520109
520570
  if (agent.status !== "pending" && agent.status !== "running")
520110
520571
  return false;
520111
- return agent.parentAgentId === agentId;
520572
+ if (agent.parentAgentId !== agentId)
520573
+ return false;
520574
+ return conversationId === undefined || agent.parentConversationId === undefined || agent.parentConversationId === conversationId;
520112
520575
  });
520113
520576
  }
520114
- function interruptSubagentsAndReleaseReflection(agentId) {
520115
- const interruptedReflection = hasActiveReflectionSubagentForAgent(agentId);
520116
- interruptActiveSubagents(INTERRUPTED_BY_USER);
520577
+ function interruptSubagentsAndReleaseReflection(agentId, conversationId) {
520578
+ const interruptedReflection = hasActiveReflectionSubagentForAgent(agentId, conversationId);
520579
+ interruptActiveSubagents(INTERRUPTED_BY_USER, {
520580
+ agentId,
520581
+ conversationId
520582
+ });
520117
520583
  if (interruptedReflection) {
520118
520584
  releaseReflectionLaunch(agentId);
520119
520585
  }
@@ -520215,7 +520681,7 @@ function useInterruptHandler(ctx) {
520215
520681
  autoAllowedExecutionRef.current = null;
520216
520682
  buffersRef.current.abortGeneration = (buffersRef.current.abortGeneration || 0) + 1;
520217
520683
  const toolsCancelled = markIncompleteToolsAsCancelled(buffersRef.current, true, "user_interrupt");
520218
- interruptSubagentsAndReleaseReflection(agentId);
520684
+ interruptSubagentsAndReleaseReflection(agentId, conversationIdRef.current);
520219
520685
  if (!toolsCancelled) {
520220
520686
  appendError(INTERRUPT_MESSAGE, true);
520221
520687
  }
@@ -520245,6 +520711,16 @@ function useInterruptHandler(ctx) {
520245
520711
  bumpDequeueEpoch: () => setDequeueEpoch((e2) => e2 + 1)
520246
520712
  });
520247
520713
  }
520714
+ const retried = interruptStopFailedSubagents(INTERRUPTED_BY_USER, {
520715
+ agentId,
520716
+ conversationId: conversationIdRef.current
520717
+ });
520718
+ if (retried.length > 0) {
520719
+ if (retried.some((id2) => getSubagents().find((agent) => agent.id === id2)?.type.toLowerCase() === "reflection")) {
520720
+ releaseReflectionLaunch(agentId);
520721
+ }
520722
+ appendError("Retrying stop of background task...", true);
520723
+ }
520248
520724
  return;
520249
520725
  }
520250
520726
  if (waitingForQueueCancelRef.current) {
@@ -520255,7 +520731,7 @@ function useInterruptHandler(ctx) {
520255
520731
  setInterruptRequested(true);
520256
520732
  buffersRef.current.abortGeneration = (buffersRef.current.abortGeneration || 0) + 1;
520257
520733
  const toolsCancelled = markIncompleteToolsAsCancelled(buffersRef.current, true, "user_interrupt");
520258
- interruptSubagentsAndReleaseReflection(agentId);
520734
+ interruptSubagentsAndReleaseReflection(agentId, conversationIdRef.current);
520259
520735
  abortControllerRef.current?.abort();
520260
520736
  pendingInterruptRecoveryConversationIdRef.current = conversationIdRef.current;
520261
520737
  userCancelledRef.current = true;
@@ -529520,6 +529996,7 @@ Memory may be stale. Try running: git -C ${getScopedMemoryFilesystemRoot(agentId
529520
529996
  precomputedDiffsRef,
529521
529997
  prepareScopedToolExecutionContext,
529522
529998
  processingConversationRef,
529999
+ queuedApprovalResultsRef,
529523
530000
  queueApprovalResults,
529524
530001
  queueSnapshotRef,
529525
530002
  quotaAutoSwapAttemptedRef,
@@ -530276,6 +530753,18 @@ Memory may be stale. Try running: git -C ${getScopedMemoryFilesystemRoot(agentId
530276
530753
  deferredCommitAt
530277
530754
  ]);
530278
530755
  const { agents: subagents } = import_react123.useSyncExternalStore(subscribe2, getSnapshot2);
530756
+ const hasStopFailedTasks = hasStopFailedSubagents({
530757
+ agentId,
530758
+ conversationId
530759
+ });
530760
+ const stopFailedNotifiedRef = import_react123.useRef(new Set);
530761
+ import_react123.useEffect(() => {
530762
+ const { failures, stops } = selectStopFailedNotifications(subagents, stopFailedNotifiedRef.current, { agentId, conversationId });
530763
+ const messages = [...failures, ...stops];
530764
+ if (messages.length > 0) {
530765
+ appendTaskNotificationEvents(messages);
530766
+ }
530767
+ }, [subagents, conversationId, appendTaskNotificationEvents, agentId]);
530279
530768
  const estimatedLiveHeight = import_react123.useMemo(() => {
530280
530769
  const countLines4 = (text3) => {
530281
530770
  if (!text3)
@@ -530523,6 +531012,7 @@ Memory may be stale. Try running: git -C ${getScopedMemoryFilesystemRoot(agentId
530523
531012
  inputEnabled,
530524
531013
  inputVisible,
530525
531014
  interruptRequested: interruptRequested || !!abortControllerRef.current?.signal.aborted,
531015
+ hasStopFailedTasks,
530526
531016
  isAgentBusy,
530527
531017
  liveItems,
530528
531018
  liveTrajectoryElapsedBaseMs,
@@ -534800,4 +535290,4 @@ function registerBunOAuthFlows() {
534800
535290
  registerBunOAuthFlows();
534801
535291
  await init_src5().then(() => exports_src2);
534802
535292
 
534803
- //# debugId=BF59442F8ED1752A64756E2164756E21
535293
+ //# debugId=9CD2133B459655FB64756E2164756E21