@letta-ai/letta-code 0.34.18 → 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 (53) hide show
  1. package/dist/gateway-core.js +2 -2
  2. package/dist/gateway-core.js.map +3 -3
  3. package/dist/mcp-client.js +2 -2
  4. package/dist/mcp-client.js.map +1 -1
  5. package/dist/mcp-oauth.js +2 -2
  6. package/dist/mcp-oauth.js.map +1 -1
  7. package/dist/types/agent/approval-execution.d.ts +33 -1
  8. package/dist/types/agent/approval-execution.d.ts.map +1 -1
  9. package/dist/types/agent/parent-memory-note.d.ts +3 -0
  10. package/dist/types/agent/parent-memory-note.d.ts.map +1 -0
  11. package/dist/types/agent/skills.d.ts.map +1 -1
  12. package/dist/types/agent/subagent-state.d.ts +53 -2
  13. package/dist/types/agent/subagent-state.d.ts.map +1 -1
  14. package/dist/types/channels/message-channel-executor.d.ts.map +1 -1
  15. package/dist/types/cli/helpers/agent-info.d.ts +1 -0
  16. package/dist/types/cli/helpers/agent-info.d.ts.map +1 -1
  17. package/dist/types/cli/subcommands/mcp.d.ts +3 -1
  18. package/dist/types/cli/subcommands/mcp.d.ts.map +1 -1
  19. package/dist/types/reminders/engine.d.ts.map +1 -1
  20. package/dist/types/telemetry/channel.d.ts.map +1 -1
  21. package/dist/types/tools/external-tool-background-eligibility.d.ts +2 -1
  22. package/dist/types/tools/external-tool-background-eligibility.d.ts.map +1 -1
  23. package/dist/types/tools/external-tool-background.d.ts +14 -1
  24. package/dist/types/tools/external-tool-background.d.ts.map +1 -1
  25. package/dist/types/tools/external-tool-types.d.ts +1 -0
  26. package/dist/types/tools/external-tool-types.d.ts.map +1 -1
  27. package/dist/types/tools/impl/external-coding-agent.d.ts.map +1 -1
  28. package/dist/types/tools/impl/monitor.d.ts +2 -0
  29. package/dist/types/tools/impl/monitor.d.ts.map +1 -1
  30. package/dist/types/tools/impl/task-abort-hook.d.ts +2 -0
  31. package/dist/types/tools/impl/task-abort-hook.d.ts.map +1 -0
  32. package/dist/types/tools/impl/task.d.ts.map +1 -1
  33. package/dist/types/tools/impl/watch-pr.d.ts +1 -1
  34. package/dist/types/tools/impl/watch-pr.d.ts.map +1 -1
  35. package/dist/types/tools/impl/workflow.d.ts +2 -0
  36. package/dist/types/tools/impl/workflow.d.ts.map +1 -1
  37. package/dist/types/tools/manager.d.ts +1 -1
  38. package/dist/types/tools/manager.d.ts.map +1 -1
  39. package/dist/types/tools/script-tool-scope.d.ts.map +1 -1
  40. package/dist/types/utils/subagent-scope.d.ts +13 -0
  41. package/dist/types/utils/subagent-scope.d.ts.map +1 -0
  42. package/dist/types/utils/task-notifications.d.ts +43 -0
  43. package/dist/types/utils/task-notifications.d.ts.map +1 -1
  44. package/letta.js +835 -288
  45. package/npm-shrinkwrap.json +14 -14
  46. package/package.json +2 -2
  47. package/scripts/claude-watch/docs-snapshot.test.ts +65 -0
  48. package/scripts/claude-watch/docs-snapshot.ts +10 -4
  49. package/scripts/isolated-unit-tests.json +5 -0
  50. package/scripts/source-file-size-baseline.json +8 -8
  51. package/skills/finding-agents/SKILL.md +0 -4
  52. package/skills/curating-memory-palace/SKILL.md +0 -147
  53. package/skills/migrating-memory/SKILL.md +0 -98
package/letta.js CHANGED
@@ -5941,7 +5941,7 @@ var package_default;
5941
5941
  var init_package = __esm(() => {
5942
5942
  package_default = {
5943
5943
  name: "@letta-ai/letta-code",
5944
- version: "0.34.18",
5944
+ version: "0.34.19",
5945
5945
  lettaStartupLogProtocol: 1,
5946
5946
  description: "Letta Code is a CLI tool for interacting with stateful Letta agents from the terminal.",
5947
5947
  type: "module",
@@ -6216,7 +6216,7 @@ var init_package = __esm(() => {
6216
6216
  },
6217
6217
  lettaLifecycleProtocol: 1,
6218
6218
  lettaRuntimeGraphProtocol: 1,
6219
- lettaRuntimeGraphSha512: "sha512-cubBaizY8rbgiq9dfHU/uRyZBgkGvLfJc+8+ovpV1raZnQjE+tRsAKFAKP69UHWhNFRE49viVTyqmLIiSK8v8Q=="
6219
+ lettaRuntimeGraphSha512: "sha512-RWgzMK/RLJluHmJEAXwWV7NZe1gpOgg3BYpp8+G8QfqsXUUmzZgQpQz5E9CHogeIwfcGzCaHAUIdboH6Ir7KyA=="
6220
6220
  };
6221
6221
  });
6222
6222
 
@@ -6488,6 +6488,7 @@ function telemetryChannel(value) {
6488
6488
  return;
6489
6489
  return [
6490
6490
  "slack",
6491
+ "microsoftteams",
6491
6492
  "microsoftTeams",
6492
6493
  "telegram",
6493
6494
  "discord",
@@ -172634,6 +172635,16 @@ var init_message_queue_bridge = __esm(() => {
172634
172635
  pendingMessages = [];
172635
172636
  });
172636
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
+
172637
172648
  // src/utils/task-notifications.ts
172638
172649
  function escapeXml(str) {
172639
172650
  return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
@@ -172742,6 +172753,47 @@ function appendTaskNotificationEventsToBuffer(summaries, buffer, generateId, flu
172742
172753
  flush?.();
172743
172754
  return true;
172744
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
+ }
172745
172797
  var init_task_notifications = __esm(() => {
172746
172798
  init_context();
172747
172799
  init_constants2();
@@ -210828,7 +210880,7 @@ var init_mcp_client = __esm(() => {
210828
210880
  init_streamableHttp();
210829
210881
  DEFAULT_CLIENT_INFO = {
210830
210882
  name: "letta-code",
210831
- version: "0.34.18"
210883
+ version: "0.34.19"
210832
210884
  };
210833
210885
  });
210834
210886
 
@@ -214963,7 +215015,7 @@ Do not use this tool for root memory files (already loaded) or skills/ (use Skil
214963
215015
  var init_Memory = () => {};
214964
215016
 
214965
215017
  // src/tools/descriptions/Monitor.md
214966
- 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';
214967
215019
  var init_Monitor = () => {};
214968
215020
 
214969
215021
  // src/tools/descriptions/Read.md
@@ -215486,6 +215538,8 @@ var init_Wake = () => {};
215486
215538
  // src/tools/descriptions/WatchPR.md
215487
215539
  var WatchPR_default = `Watch one open github.com pull request until it merges, closes, is stopped with TaskStop, or the session ends.
215488
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
+
215489
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:
215490
215544
 
215491
215545
  - current-head check failures, recoveries, retries, and completion;
@@ -221142,12 +221196,55 @@ var init_disk_space = __esm(() => {
221142
221196
  var exports_approval_execution = {};
221143
221197
  __export(exports_approval_execution, {
221144
221198
  settleApprovalExecutionTasks: () => settleApprovalExecutionTasks,
221199
+ overlayPartialResults: () => overlayPartialResults,
221200
+ isUserAbortError: () => isUserAbortError,
221145
221201
  getResourceKey: () => getResourceKey,
221146
221202
  getDisplayableToolReturn: () => getDisplayableToolReturn,
221147
221203
  executeAutoAllowedTools: () => executeAutoAllowedTools,
221148
- executeApprovalBatch: () => executeApprovalBatch
221204
+ executeApprovalBatch: () => executeApprovalBatch,
221205
+ buildDeniedResults: () => buildDeniedResults,
221206
+ buildCompanionResults: () => buildCompanionResults,
221207
+ appendAbortPartials: () => appendAbortPartials,
221208
+ ABORT_GRACE_MS: () => ABORT_GRACE_MS
221149
221209
  });
221150
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
+ }
221151
221248
  function getDisplayableToolReturn(content) {
221152
221249
  if (typeof content === "string") {
221153
221250
  return content;
@@ -221284,8 +221381,61 @@ async function executeSingleDecision(decision, onChunk, options) {
221284
221381
  async function executeApprovalBatch(decisions, onChunk, options) {
221285
221382
  return watchDiskSpaceDuring(options?.workingDirectory ?? getCurrentWorkingDirectory(), (text2) => deliverDiskSpaceReminder(text2, options?.parentScope), () => executeApprovalBatchUnwatched(decisions, onChunk, options));
221286
221383
  }
221287
- async function settleApprovalExecutionTasks(tasks, partialResults = () => []) {
221288
- 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) {
221289
221439
  for (const result2 of settled) {
221290
221440
  if (result2.status !== "rejected")
221291
221441
  continue;
@@ -221299,6 +221449,19 @@ async function executeApprovalBatchUnwatched(decisions, onChunk, options) {
221299
221449
  workingDirectory: options.workingDirectory
221300
221450
  })).contextId : undefined);
221301
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;
221302
221465
  const parallelIndices = [];
221303
221466
  const writeToolsByResource = new Map;
221304
221467
  const denyIndices = [];
@@ -221334,11 +221497,11 @@ async function executeApprovalBatchUnwatched(decisions, onChunk, options) {
221334
221497
  const decision = decisions[i4];
221335
221498
  if (decision) {
221336
221499
  let crossedEffectBoundary = false;
221337
- const result2 = await executeSingleDecision(decision, onChunk, {
221338
- ...options,
221500
+ const result2 = await executeSingleDecision(decision, guardedOnChunk, {
221501
+ ...guardedOptions,
221339
221502
  toolContextId,
221340
221503
  beforeToolExecution: async (toolCallId) => {
221341
- await options?.beforeToolExecution?.(toolCallId);
221504
+ await guardedOptions?.beforeToolExecution?.(toolCallId);
221342
221505
  crossedEffectBoundary = true;
221343
221506
  }
221344
221507
  });
@@ -221348,15 +221511,20 @@ async function executeApprovalBatchUnwatched(decisions, onChunk, options) {
221348
221511
  }
221349
221512
  }
221350
221513
  };
221351
- await settleApprovalExecutionTasks([
221352
- ...parallelIndices.map(execute),
221353
- ...denyIndices.map(execute),
221354
- ...Array.from(writeToolsByResource.values()).map(async (indices) => {
221355
- for (const i4 of indices) {
221356
- await execute(i4);
221357
- }
221358
- })
221359
- ], () => 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
+ }
221360
221528
  return results.filter((r5) => r5 !== null);
221361
221529
  }
221362
221530
  async function executeAutoAllowedTools(autoAllowed, onChunk, options) {
@@ -221375,8 +221543,9 @@ async function executeAutoAllowedTools(autoAllowed, onChunk, options) {
221375
221543
  }
221376
221544
  }));
221377
221545
  }
221378
- 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;
221379
221547
  var init_approval_execution = __esm(async () => {
221548
+ init_error();
221380
221549
  init_constants2();
221381
221550
  init_disk_space();
221382
221551
  init_runtime_context();
@@ -222374,14 +222543,23 @@ function scheduleCompletedSubagentCleanup(id2) {
222374
222543
  return;
222375
222544
  }
222376
222545
  clearCompletedSubagentCleanup(id2);
222546
+ armCompletedSubagentCleanup(id2);
222547
+ }
222548
+ function armCompletedSubagentCleanup(id2) {
222549
+ const agent = store2.agents.get(id2);
222550
+ if (!agent)
222551
+ return;
222377
222552
  const timer = setTimeout(() => {
222553
+ completedSubagentCleanupTimers.delete(id2);
222378
222554
  const current = store2.agents.get(id2);
222379
222555
  if (!current || current.status !== "completed" && current.status !== "error") {
222380
- completedSubagentCleanupTimers.delete(id2);
222556
+ return;
222557
+ }
222558
+ if (current.cancelPending) {
222559
+ armCompletedSubagentCleanup(id2);
222381
222560
  return;
222382
222561
  }
222383
222562
  store2.agents.delete(id2);
222384
- completedSubagentCleanupTimers.delete(id2);
222385
222563
  notifyListeners();
222386
222564
  }, agent.result !== undefined ? publishedResultRetentionMs : completedSubagentRetentionMs);
222387
222565
  unrefTimer2(timer);
@@ -222471,6 +222649,7 @@ function completeSubagent(id2, result2) {
222471
222649
  ...agent,
222472
222650
  status: result2.success ? "completed" : "error",
222473
222651
  error: result2.error,
222652
+ stopFailed: undefined,
222474
222653
  ...result2.result !== undefined ? { result: result2.result } : {},
222475
222654
  durationMs: Date.now() - agent.startTime,
222476
222655
  totalTokens: result2.totalTokens ?? agent.totalTokens,
@@ -222527,6 +222706,8 @@ function clearCompletedSubagents() {
222527
222706
  for (const [id2, agent] of store2.agents.entries()) {
222528
222707
  if (agent.result !== undefined)
222529
222708
  continue;
222709
+ if (agent.cancelPending)
222710
+ continue;
222530
222711
  if (agent.status === "completed" || agent.status === "error") {
222531
222712
  clearCompletedSubagentCleanup(id2);
222532
222713
  store2.agents.delete(id2);
@@ -222540,6 +222721,11 @@ function clearCompletedSubagents() {
222540
222721
  function clearSubagentsByIds(ids) {
222541
222722
  let removedAny = false;
222542
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
+ }
222543
222729
  clearCompletedSubagentCleanup(id2);
222544
222730
  removedAny = store2.agents.delete(id2) || removedAny;
222545
222731
  }
@@ -222555,15 +222741,28 @@ function hasActiveSubagents() {
222555
222741
  }
222556
222742
  return false;
222557
222743
  }
222558
- 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) {
222559
222751
  let anyInterrupted = false;
222560
222752
  for (const [id2, agent] of store2.agents.entries()) {
222561
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);
222562
222759
  const updatedAgent = {
222563
222760
  ...agent,
222564
222761
  status: "error",
222565
222762
  error: errorMessage,
222566
- durationMs: Date.now() - agent.startTime
222763
+ durationMs: Date.now() - agent.startTime,
222764
+ ...hook ? { cancelPending: true, stopAttempts: (agent.stopAttempts ?? 0) + 1 } : {},
222765
+ stopFailed: undefined
222567
222766
  };
222568
222767
  store2.agents.set(id2, updatedAgent);
222569
222768
  scheduleCompletedSubagentCleanup(id2);
@@ -222574,6 +222773,50 @@ function interruptActiveSubagents(errorMessage) {
222574
222773
  notifyListeners();
222575
222774
  }
222576
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
+ }
222577
222820
  function subscribe2(listener) {
222578
222821
  store2.listeners.add(listener);
222579
222822
  return () => {
@@ -222594,7 +222837,7 @@ function emitStreamEvent(subagentId, event) {
222594
222837
  listener(subagentId, event);
222595
222838
  }
222596
222839
  }
222597
- 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;
222598
222841
  var init_subagent_state = __esm(() => {
222599
222842
  store2 = {
222600
222843
  agents: new Map,
@@ -222610,6 +222853,7 @@ var init_subagent_state = __esm(() => {
222610
222853
  DEFAULT_PUBLISHED_RESULT_RETENTION_MS = 30 * 60000;
222611
222854
  publishedResultRetentionMs = DEFAULT_PUBLISHED_RESULT_RETENTION_MS;
222612
222855
  completedSubagentCleanupTimers = new Map;
222856
+ subagentAbortHooks = new Map;
222613
222857
  streamEventListeners = new Set;
222614
222858
  });
222615
222859
 
@@ -223128,10 +223372,37 @@ var init_tool_registry = __esm(() => {
223128
223372
  });
223129
223373
 
223130
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
+ }
223131
223401
  function notificationText(toolReturn) {
223132
223402
  return scrubAmbientSecrets(String(toolReturn));
223133
223403
  }
223134
223404
  async function autoBackgroundExternalTool(toolName2, tool, operation, options) {
223405
+ const signal = options?.signal;
223135
223406
  const runtime = options?.runtimeScope;
223136
223407
  const scope = options?.scope ?? (runtime?.agentId && runtime.conversationId ? {
223137
223408
  agentId: runtime.agentId,
@@ -223140,24 +223411,36 @@ async function autoBackgroundExternalTool(toolName2, tool, operation, options) {
223140
223411
  } : undefined);
223141
223412
  const canBackground = () => typeof options?.canBackground === "function" ? options.canBackground() : options?.canBackground !== false;
223142
223413
  if (tool?.autoBackground !== true || !canBackground() || !scope || !options?.enqueue && !isQueueBridgeConnected())
223143
- return operation;
223414
+ return signal ? raceExternalOperationAbort(operation, signal, toolName2) : operation;
223144
223415
  const startedAt = Date.now();
223145
223416
  const settled = operation.then((result2) => ({ kind: "result", result: result2 }), (error5) => ({ kind: "error", error: error5 }));
223146
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;
223147
223423
  const first = await Promise.race([
223148
223424
  settled,
223149
223425
  new Promise((resolve21) => {
223150
223426
  timer = setTimeout(() => resolve21({ kind: "yield" }), options?.yieldMs ?? DEFAULT_EXTERNAL_TOOL_YIELD_MS);
223151
- })
223427
+ }),
223428
+ ...abortSettled ? [abortSettled] : []
223152
223429
  ]);
223153
223430
  if (timer !== undefined)
223154
223431
  clearTimeout(timer);
223432
+ if (onAbort !== undefined && signal) {
223433
+ signal.removeEventListener("abort", onAbort);
223434
+ }
223435
+ if (first.kind === "aborted")
223436
+ return interruptedExternalToolResult(toolName2);
223155
223437
  if (first.kind === "result")
223156
223438
  return first.result;
223157
223439
  if (first.kind === "error")
223158
223440
  throw first.error;
223159
- if (!canBackground())
223160
- return operation;
223441
+ if (!canBackground()) {
223442
+ return signal ? raceExternalOperationAbort(operation, signal, toolName2) : operation;
223443
+ }
223161
223444
  const taskId = `external_${crypto.randomUUID()}`;
223162
223445
  const enqueue = options?.enqueue ?? addToMessageQueue;
223163
223446
  settled.then(async (outcome) => {
@@ -223207,14 +223490,15 @@ function hasActiveToolEndHandlers(events) {
223207
223490
  return false;
223208
223491
  return events.hasHandlers?.("tool_end") ?? true;
223209
223492
  }
223210
- function listenerExternalToolBackgroundOptions(tool, events, runtimeScope) {
223493
+ function listenerExternalToolBackgroundOptions(tool, events, runtimeScope, signal) {
223211
223494
  const registration = tool ? listenerRegistrations.get(tool) : undefined;
223212
223495
  const notificationGuard = registration?.createNotificationGuard();
223213
223496
  return {
223214
223497
  canBackground: () => notificationGuard?.isCurrent() === true && !hasActiveToolEndHandlers(events),
223215
223498
  ...registration?.yieldMs !== undefined ? { yieldMs: registration.yieldMs } : {},
223216
223499
  ...notificationGuard ? { shouldEnqueue: notificationGuard.waitUntilCurrent } : {},
223217
- runtimeScope
223500
+ runtimeScope,
223501
+ ...signal ? { signal } : {}
223218
223502
  };
223219
223503
  }
223220
223504
  var listenerRegistrations;
@@ -228992,7 +229276,6 @@ var LOCAL_AGENT_EXCLUDED_BUNDLED_SKILLS, PROJECT_SKILLS_DIR, SKILLS_DIR = ".skil
228992
229276
  var init_skills5 = __esm(() => {
228993
229277
  init_skill_sources();
228994
229278
  LOCAL_AGENT_EXCLUDED_BUNDLED_SKILLS = new Set([
228995
- "curating-memory-palace",
228996
229279
  "image-generation",
228997
229280
  "managing-agent-webhooks",
228998
229281
  "managing-shared-memory",
@@ -230142,7 +230425,7 @@ function getExternalToolsAsClientTools() {
230142
230425
  parameters: tool.parameters
230143
230426
  }));
230144
230427
  }
230145
- async function executeExternalTool(toolCallId, toolName2, input, executorOverride, toolDefinition) {
230428
+ async function executeExternalTool(toolCallId, toolName2, input, executorOverride, toolDefinition, signal) {
230146
230429
  const executor = executorOverride ?? getExternalToolExecutor();
230147
230430
  if (!executor) {
230148
230431
  return {
@@ -230154,7 +230437,7 @@ async function executeExternalTool(toolCallId, toolName2, input, executorOverrid
230154
230437
  let success = false;
230155
230438
  try {
230156
230439
  const tool = toolDefinition ?? getExternalToolDefinition(toolName2);
230157
- const result2 = await executor(toolCallId, toolName2, input, tool ? { tool } : undefined);
230440
+ const result2 = await executor(toolCallId, toolName2, input, tool ? { tool, signal } : undefined);
230158
230441
  success = !result2.isError;
230159
230442
  return {
230160
230443
  toolReturn: clampToolReturnContent(normalizeExternalToolResultContent(result2.content), toolName2),
@@ -230921,7 +231204,7 @@ async function executeToolInner(name, args, options) {
230921
231204
  if (permissionDecision2?.decision !== undefined && permissionDecision2.decision !== "allow") {
230922
231205
  return createModPermissionToolResult(permissionDecision2);
230923
231206
  }
230924
- 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)));
230925
231208
  }
230926
231209
  const internalName = resolveInternalToolName(name, activeRegistry);
230927
231210
  const tool = internalName ? activeRegistry.get(internalName) : undefined;
@@ -232383,12 +232666,12 @@ async function runCall(parsed, deps, agentId, stdout) {
232383
232666
  await printJson(stdout, result2);
232384
232667
  return result2.isError === true ? 2 : 0;
232385
232668
  }
232386
- async function callCatalogTool(catalog, deps, agentId, toolName2, args) {
232669
+ async function callCatalogTool(catalog, deps, agentId, toolName2, args, options) {
232387
232670
  const tool = catalog.tools.find((candidate) => candidate.schema.name === toolName2);
232388
232671
  if (!tool) {
232389
232672
  throw new McpCliError("tool_not_found", `MCP tool '${toolName2}' is not available`);
232390
232673
  }
232391
- 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({
232392
232675
  client: await getServerClient(deps),
232393
232676
  agentId,
232394
232677
  mcpServerId: tool.target.serverId,
@@ -232417,7 +232700,7 @@ function createAgentMcpSession(agentId, deps = {}) {
232417
232700
  return catalog;
232418
232701
  };
232419
232702
  return {
232420
- 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),
232421
232704
  listTools: async () => (await load()).tools.map((tool) => tool.schema),
232422
232705
  close: async () => {
232423
232706
  closed = true;
@@ -232574,7 +232857,7 @@ async function createScriptToolScope(parentAgentId, signal) {
232574
232857
  isError: result2.status === "error"
232575
232858
  };
232576
232859
  }
232577
- return (await mcp()).callTool(toolName2, toolArgs);
232860
+ return (await mcp()).callTool(toolName2, toolArgs, signal ? { signal } : {});
232578
232861
  },
232579
232862
  cleanup: async () => {
232580
232863
  await mcpSession?.then((session3) => session3.close()).catch(() => {
@@ -239866,6 +240149,9 @@ var init_monitor_event_stream = __esm(() => {
239866
240149
  // src/tools/impl/monitor.ts
239867
240150
  import { appendFileSync as appendFileSync7, readFileSync as readFileSync35, writeFileSync as writeFileSync27 } from "node:fs";
239868
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
+ }
239869
240155
  function buildMonitorResult(taskId, timeoutMs, persistent, outputFile) {
239870
240156
  const lifetime = persistent ? "persistent — runs until TaskStop or session end" : `timeout ${timeoutMs}ms`;
239871
240157
  return {
@@ -239975,8 +240261,8 @@ function normalizeMonitorArgs(args) {
239975
240261
  if (typeof normalized.description !== "string") {
239976
240262
  throw new Error("Monitor description is required");
239977
240263
  }
239978
- 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) {
239979
- 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}`);
239980
240266
  }
239981
240267
  if (typeof normalized.persistent !== "boolean") {
239982
240268
  throw new Error("Monitor persistent must be a boolean");
@@ -240008,6 +240294,10 @@ function normalizeMonitorArgs(args) {
240008
240294
  }
240009
240295
  validateWebSocketSource(normalized.ws);
240010
240296
  }
240297
+ if (isManagedCloudRuntime() && normalized.persistent) {
240298
+ normalized.persistent = false;
240299
+ normalized.timeout_ms = MONITOR_MAX_TIMEOUT_MS;
240300
+ }
240011
240301
  return normalized;
240012
240302
  }
240013
240303
  function webSocketDisplayUrl(sourceUrl) {
@@ -240240,7 +240530,7 @@ function startCommandMonitor(args) {
240240
240530
  `);
240241
240531
  queueMonitorEvent({
240242
240532
  taskId,
240243
- event: "[Monitor timed out — re-arm if needed.]",
240533
+ event: getMonitorTimeoutEvent(args.timeout_ms),
240244
240534
  scope,
240245
240535
  actingUserId,
240246
240536
  secrets: secrets2
@@ -240422,7 +240712,7 @@ function startWebSocketMonitor(args) {
240422
240712
  return;
240423
240713
  queueMonitorEvent({
240424
240714
  taskId,
240425
- event: "[Monitor timed out — re-arm if needed.]",
240715
+ event: getMonitorTimeoutEvent(args.timeout_ms),
240426
240716
  scope,
240427
240717
  actingUserId,
240428
240718
  secrets: secrets2
@@ -240485,7 +240775,7 @@ function installMonitorProcessExitCleanup() {
240485
240775
  monitorProcessExitCleanupInstalled = true;
240486
240776
  process.once("exit", stopMonitorsForProcessExit);
240487
240777
  }
240488
- 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;
240489
240779
  var init_monitor = __esm(async () => {
240490
240780
  init_strip_ansi();
240491
240781
  init_runtime_context();
@@ -242270,7 +242560,7 @@ class CodexAppServerClient {
242270
242560
  await this.request("initialize", {
242271
242561
  clientInfo: {
242272
242562
  name: "letta-code",
242273
- version: "0.34.18"
242563
+ version: "0.34.19"
242274
242564
  },
242275
242565
  capabilities: null
242276
242566
  });
@@ -242689,6 +242979,22 @@ var init_codex_app_server = __esm(() => {
242689
242979
  DEFAULT_IDLE_TIMEOUT_MS = 5 * 60000;
242690
242980
  });
242691
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
+
242692
242998
  // src/cli/helpers/agent-info.ts
242693
242999
  function getRelativeTime(dateStr) {
242694
243000
  const date5 = new Date(dateStr);
@@ -242732,10 +243038,13 @@ function buildAgentInfo(options) {
242732
243038
  })();
242733
243039
  const memoryDirLine = showMemoryDir ? `
242734
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}` : "";
242735
243044
  const convLine = conversationId ? `
242736
243045
  - **Conversation ID (also stored in \`CONVERSATION_ID\` env var)**: ${conversationId}` : "";
242737
243046
  return `${SYSTEM_REMINDER_OPEN} This is an automated message providing information about you.
242738
- - **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}
242739
243048
  - **Agent name**: ${agentInfo.name || "(unnamed)"} (the user can change this with /rename)
242740
243049
  - **Agent description**: ${agentInfo.description || "(no description)"} (the user can change this with /description)
242741
243050
  - **Last message**: ${lastRunInfo}
@@ -242746,6 +243055,7 @@ ${SYSTEM_REMINDER_CLOSE}`;
242746
243055
  }
242747
243056
  var init_agent_info = __esm(() => {
242748
243057
  init_memory_filesystem2();
243058
+ init_parent_memory_note();
242749
243059
  init_paths();
242750
243060
  init_constants2();
242751
243061
  init_settings_manager();
@@ -243215,7 +243525,14 @@ async function buildAgentInfoReminder(context3) {
243215
243525
  if (!context3.systemInfoReminderEnabled || context3.state.hasSentAgentInfo) {
243216
243526
  return null;
243217
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 {}
243218
243534
  const reminder = buildAgentInfo({
243535
+ parentAgentId,
243219
243536
  agentInfo: {
243220
243537
  id: context3.agent.id,
243221
243538
  name: context3.agent.name,
@@ -244822,6 +245139,18 @@ async function resolveExternalCodingAgentSecretEnv(parentAgentId, lookupParent)
244822
245139
  return { ...inherited, ...loadSecrets2(parentAgentId) };
244823
245140
  }
244824
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
+ };
244825
245154
  const startedAt = Date.now();
244826
245155
  const secretEnv = await (deps.resolveSecretEnv ?? resolveExternalCodingAgentSecretEnv)(options.parentAgentId);
244827
245156
  const env6 = {
@@ -244988,8 +245317,10 @@ async function runExternalCodingAgent(options, deps = {}) {
244988
245317
  }
244989
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";
244990
245319
  var init_external_coding_agent = __esm(() => {
245320
+ init_parent_memory_note();
244991
245321
  init_subagent_depth();
244992
245322
  init_subagent_process();
245323
+ init_constants2();
244993
245324
  init_mcp_scope();
244994
245325
  init_engine3();
244995
245326
  init_runtime_context();
@@ -248836,6 +249167,184 @@ var init_subagent_computer = __esm(() => {
248836
249167
  LOCAL_COMPUTER_SELECTORS = new Set(["local", "self"]);
248837
249168
  });
248838
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
+
248839
249348
  // src/tools/impl/task-transcript.ts
248840
249349
  function buildTaskResultHeader(subagentType, subagentId, result3, status) {
248841
249350
  return [
@@ -248954,6 +249463,7 @@ function spawnBackgroundSubagentTask(args) {
248954
249463
  actingUserId
248955
249464
  };
248956
249465
  backgroundTasks.set(taskId, bgTask);
249466
+ registerSubagentAbortHook(subagentId, buildBackgroundTaskAbortHook(taskId, abortController));
248957
249467
  writeTaskTranscriptStart(outputFile, description, subagentType);
248958
249468
  const parentAgentIdForSpawn = resolvedParentScope?.agentId;
248959
249469
  const workerMemoryDir = subagentType === "memory" && resolvedParentScope ? memoryScope?.primaryRoot ?? getScopedMemoryFilesystemRoot(resolvedParentScope.agentId) : undefined;
@@ -248985,6 +249495,7 @@ function spawnBackgroundSubagentTask(args) {
248985
249495
  const header = buildTaskResultHeader(subagentType, subagentId, result3, result3.success ? "success" : "error");
248986
249496
  writeTaskTranscriptResult(outputFile, result3, header, !!memoryTask);
248987
249497
  scheduleBackgroundTaskCleanup(taskId);
249498
+ clearSubagentAbortHook(subagentId);
248988
249499
  completeSubagentFn(subagentId, {
248989
249500
  success: result3.success,
248990
249501
  error: result3.error,
@@ -249052,6 +249563,7 @@ Error: ${result3.error || "Subagent execution failed"}`;
249052
249563
  [Task failed]
249053
249564
  `);
249054
249565
  scheduleBackgroundTaskCleanup(taskId);
249566
+ clearSubagentAbortHook(subagentId);
249055
249567
  completeSubagentFn(subagentId, { success: false, error: errorMessage4 });
249056
249568
  try {
249057
249569
  await onComplete?.({
@@ -249410,6 +249922,7 @@ var init_task = __esm(async () => {
249410
249922
  init_process_manager();
249411
249923
  init_remote_external_coding_agent();
249412
249924
  init_subagent_computer();
249925
+ init_task_abort_hook();
249413
249926
  init_task_transcript();
249414
249927
  init_truncation();
249415
249928
  init_background_link();
@@ -250302,120 +250815,6 @@ var init_task_list = __esm(() => {
250302
250815
  init_store();
250303
250816
  });
250304
250817
 
250305
- // src/tools/impl/kill-bash.ts
250306
- function killBackgroundProcess(shell_id) {
250307
- const proc = backgroundProcesses.get(shell_id);
250308
- const retainsEntry = proc?.kind === "monitor" || proc?.kind === "workflow";
250309
- if (!proc || retainsEntry && proc.status !== "running") {
250310
- return false;
250311
- }
250312
- const previousStatus = proc.status;
250313
- const previousNotificationSuppression = proc.completionNotificationSuppressed;
250314
- try {
250315
- proc.completionNotificationSuppressed = true;
250316
- if (retainsEntry) {
250317
- proc.status = "failed";
250318
- }
250319
- proc.process.kill("SIGTERM");
250320
- if (proc.kind === "monitor")
250321
- cancelMonitorBackup(shell_id);
250322
- clearBackgroundProcessCleanup(shell_id);
250323
- if (retainsEntry) {
250324
- scheduleBackgroundProcessCleanup(shell_id);
250325
- notifyBackgroundProcessStateChanged(proc.runtimeScope);
250326
- } else {
250327
- backgroundProcesses.delete(shell_id);
250328
- }
250329
- return true;
250330
- } catch {
250331
- proc.status = previousStatus;
250332
- proc.completionNotificationSuppressed = previousNotificationSuppression;
250333
- return false;
250334
- }
250335
- }
250336
- var init_kill_bash = __esm(async () => {
250337
- init_process_manager();
250338
- await init_monitor_backup();
250339
- });
250340
-
250341
- // src/tools/impl/task-stop.ts
250342
- async function cancelRemoteRun(subagentId) {
250343
- const child = getSubagents().find((agent) => agent.id === subagentId);
250344
- const target2 = child?.conversationId === "default" ? child.agentId : child?.conversationId;
250345
- try {
250346
- if (!target2)
250347
- throw new Error("its conversation is not known yet");
250348
- const results = await getBackend().cancelConversation(target2);
250349
- const outcomes = Object.values(results);
250350
- if (outcomes.length === 0 || outcomes.some((status) => status !== "cancelled")) {
250351
- throw new Error("Cloud did not cancel every run");
250352
- }
250353
- return { killed: true, output: "Cancelled the remote run" };
250354
- } catch (error5) {
250355
- return {
250356
- killed: false,
250357
- output: `Couldn't cancel the remote run: ${getErrorMessage2(error5)}`
250358
- };
250359
- }
250360
- }
250361
- async function task_stop(args, deps = {}) {
250362
- validateRequiredParams(args, ["task_id"], "TaskStop");
250363
- const { task_id } = args;
250364
- const task2 = backgroundTasks.get(task_id);
250365
- if (task2) {
250366
- if (task2.status === "running" && task2.abortController) {
250367
- const child = getSubagents().find((agent) => agent.id === task2.subagentId);
250368
- const external3 = parseExternalCodingAgentId(child?.agentId ?? undefined);
250369
- let remote;
250370
- let remoteExternalStopped = false;
250371
- if (external3?.computer) {
250372
- try {
250373
- if (!task2.runtimeScope)
250374
- throw new Error("its parent conversation is not known");
250375
- const result3 = await (deps.stopRemoteExternal ?? stopRemoteExternalCodingAgent)({
250376
- agentId: child?.agentId,
250377
- parentScope: {
250378
- agentId: task2.runtimeScope.agentId,
250379
- conversationId: task2.runtimeScope.conversationId,
250380
- actingUserId: task2.actingUserId
250381
- }
250382
- });
250383
- remote = {
250384
- killed: result3.stopped,
250385
- output: result3.stopped ? "Stopped the remote external subagent" : "The remote external subagent was not running"
250386
- };
250387
- remoteExternalStopped = result3.stopped;
250388
- } catch (error5) {
250389
- remote = {
250390
- killed: false,
250391
- output: `Couldn't stop the remote external subagent: ${getErrorMessage2(error5)}`
250392
- };
250393
- }
250394
- } else if (task2.remote) {
250395
- remote = await cancelRemoteRun(task2.subagentId);
250396
- }
250397
- if (remote && !remote.killed)
250398
- return remote;
250399
- task2.abortController.abort(remoteExternalStopped ? REMOTE_EXTERNAL_WORKER_STOPPED : undefined);
250400
- task2.error = "Aborted by user";
250401
- await task2.completion;
250402
- task2.status = "failed";
250403
- scheduleBackgroundTaskCleanup(task_id);
250404
- return remote ?? { killed: true };
250405
- }
250406
- return { killed: false };
250407
- }
250408
- return { killed: killBackgroundProcess(task_id) };
250409
- }
250410
- var init_task_stop = __esm(async () => {
250411
- init_subagent_state();
250412
- init_backend2();
250413
- init_external_coding_agent();
250414
- init_process_manager();
250415
- init_remote_external_coding_agent();
250416
- await init_kill_bash();
250417
- });
250418
-
250419
250818
  // src/tools/impl/task-update.ts
250420
250819
  function validateStringArray(value, paramName) {
250421
250820
  if (value === undefined)
@@ -251470,6 +251869,8 @@ ${describeGitHubPullRequestSnapshot(initial2)}
251470
251869
  const controller = new AbortController;
251471
251870
  const secrets2 = {};
251472
251871
  let processState;
251872
+ const persistent = !isManagedCloudRuntime();
251873
+ let timeout;
251473
251874
  const events = createMonitorEventStream({
251474
251875
  emit(event) {
251475
251876
  queueMonitorEvent({
@@ -251508,6 +251909,7 @@ ${describeGitHubPullRequestSnapshot(initial2)}
251508
251909
  processState = {
251509
251910
  process: {
251510
251911
  kill() {
251912
+ clearTimeout(timeout);
251511
251913
  events.cancel();
251512
251914
  controller.abort();
251513
251915
  }
@@ -251522,11 +251924,29 @@ ${describeGitHubPullRequestSnapshot(initial2)}
251522
251924
  kind: "monitor",
251523
251925
  description,
251524
251926
  monitorSource: "github_pull_request",
251525
- persistent: true,
251927
+ persistent,
251526
251928
  secrets: secrets2
251527
251929
  };
251528
251930
  backgroundProcesses.set(taskId, processState);
251529
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
+ }
251530
251950
  const wait = deps.wait ?? defaultWait;
251531
251951
  (async () => {
251532
251952
  let previous = initial2;
@@ -251590,12 +252010,12 @@ ${describeGitHubPullRequestSnapshot(initial2)}
251590
252010
  if (processState.status === "running") {
251591
252011
  markMonitorFinished(taskId, processState, "failed", null);
251592
252012
  }
251593
- });
252013
+ }).finally(() => clearTimeout(timeout));
251594
252014
  return {
251595
252015
  content: [
251596
252016
  {
251597
252017
  type: "text",
251598
- 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."}
251599
252019
 
251600
252020
  Current state:
251601
252021
  ${describeGitHubPullRequestSnapshot(initial2)}
@@ -251606,7 +252026,7 @@ Output file: ${outputFile}`
251606
252026
  taskId,
251607
252027
  url: ref5.url,
251608
252028
  headSha: initial2.headSha,
251609
- persistent: true
252029
+ persistent
251610
252030
  };
251611
252031
  }
251612
252032
  var WATCH_POLL_MS = 30000, WATCH_MAX_POLL_MS;
@@ -252272,10 +252692,12 @@ async function createSdkSpawnerHandle(args, setStage = () => {}) {
252272
252692
  });
252273
252693
  throw error5;
252274
252694
  }
252275
- 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);
252276
252697
  return {
252277
252698
  spawner,
252278
252699
  ...toolScope,
252700
+ backgroundStopController,
252279
252701
  cleanup: async () => {
252280
252702
  await toolScope.cleanup();
252281
252703
  await client[Symbol.asyncDispose]?.().catch(() => {
@@ -252488,7 +252910,7 @@ async function workflow(args) {
252488
252910
  const taskId = getNextWorkflowId();
252489
252911
  const outputFile = createBackgroundOutputFile(taskId);
252490
252912
  const scope = resolveNotificationScope(args.parentScope);
252491
- const abortController = new AbortController;
252913
+ const abortController = handle2.backgroundStopController ?? new AbortController;
252492
252914
  const processState = {
252493
252915
  process: {
252494
252916
  kill() {
@@ -253097,7 +253519,7 @@ var init_Monitor2 = __esm(() => {
253097
253519
  persistent: {
253098
253520
  type: "boolean",
253099
253521
  default: false,
253100
- 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."
253101
253523
  },
253102
253524
  backup_after_seconds: {
253103
253525
  type: "integer",
@@ -254260,7 +254682,7 @@ import {
254260
254682
  readFileSync as readFileSync38,
254261
254683
  renameSync as renameSync10,
254262
254684
  rmSync as rmSync18,
254263
- statSync as statSync18,
254685
+ statSync as statSync19,
254264
254686
  writeFileSync as writeFileSync29
254265
254687
  } from "node:fs";
254266
254688
  import { join as join71 } from "node:path";
@@ -254579,11 +255001,11 @@ function createRecoveryLineageSidecarAccess(params) {
254579
255001
  ownerIsAlive = error5.code !== "ESRCH";
254580
255002
  }
254581
255003
  }
254582
- const ageMs = Date.now() - statSync18(join71(params.directory, file)).mtimeMs;
255004
+ const ageMs = Date.now() - statSync19(join71(params.directory, file)).mtimeMs;
254583
255005
  if (!ownerIsCurrentWriter && (!ownerIsAlive && ageMs >= staleTemporaryAgeMs || ageMs >= maximumTemporaryAgeMs)) {
254584
255006
  const release = acquireDurableFileLock(join71(params.directory, mainFile), { waitMs: params.lockWaitMs });
254585
255007
  try {
254586
- const currentAgeMs = Date.now() - statSync18(join71(params.directory, file)).mtimeMs;
255008
+ const currentAgeMs = Date.now() - statSync19(join71(params.directory, file)).mtimeMs;
254587
255009
  if (!ownerIsCurrentWriter && (!ownerIsAlive && currentAgeMs >= staleTemporaryAgeMs || currentAgeMs >= maximumTemporaryAgeMs)) {
254588
255010
  rmSync18(join71(params.directory, file), { force: true });
254589
255011
  params.syncDirectory(params.directory);
@@ -257075,7 +257497,7 @@ import {
257075
257497
  openSync as openSync7,
257076
257498
  readFileSync as readFileSync40,
257077
257499
  renameSync as renameSync11,
257078
- statSync as statSync19,
257500
+ statSync as statSync20,
257079
257501
  unlinkSync as unlinkSync10,
257080
257502
  writeFileSync as writeFileSync31
257081
257503
  } from "node:fs";
@@ -257215,7 +257637,7 @@ function validateDurableStore(value) {
257215
257637
  function readDurableStore(path32) {
257216
257638
  if (!existsSync50(path32))
257217
257639
  return emptyDurableStore();
257218
- const size2 = statSync19(path32).size;
257640
+ const size2 = statSync20(path32).size;
257219
257641
  if (size2 > MAX_INPUT_DISPOSITION_STORE_BYTES) {
257220
257642
  throw new Error("Accepted-input disposition store exceeds its size limit");
257221
257643
  }
@@ -264811,7 +265233,7 @@ function isBrowserDeviceMcpOAuthProtocolCommand(value) {
264811
265233
  return false;
264812
265234
  const record6 = value;
264813
265235
  if (record6.type === "browser_device_mcp_oauth") {
264814
- 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;
264815
265237
  }
264816
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);
264817
265239
  }
@@ -264823,7 +265245,7 @@ function hasControlCharacters(value) {
264823
265245
  }
264824
265246
  return false;
264825
265247
  }
264826
- 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;
264827
265249
  var init_browser_device_mcp_oauth_protocol_inbound = __esm(() => {
264828
265250
  REQUEST_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/;
264829
265251
  HANDOFF_KEY_PATTERN2 = /^[A-Za-z0-9_-]{43,128}$/;
@@ -266238,7 +266660,7 @@ __export(exports_memory_scanner, {
266238
266660
  readFileContent: () => readFileContent,
266239
266661
  getFileNodes: () => getFileNodes
266240
266662
  });
266241
- 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";
266242
266664
  import { join as join76, relative as relative7 } from "node:path";
266243
266665
  function scanMemoryFilesystem(memoryRoot) {
266244
266666
  const nodes = [];
@@ -266256,10 +266678,10 @@ function scanMemoryFilesystem(memoryRoot) {
266256
266678
  let aIsDir = false;
266257
266679
  let bIsDir = false;
266258
266680
  try {
266259
- aIsDir = statSync20(aPath).isDirectory();
266681
+ aIsDir = statSync21(aPath).isDirectory();
266260
266682
  } catch {}
266261
266683
  try {
266262
- bIsDir = statSync20(bPath).isDirectory();
266684
+ bIsDir = statSync21(bPath).isDirectory();
266263
266685
  } catch {}
266264
266686
  if (aIsDir !== bIsDir)
266265
266687
  return aIsDir ? -1 : 1;
@@ -266275,7 +266697,7 @@ function scanMemoryFilesystem(memoryRoot) {
266275
266697
  const fullPath = join76(dir, name);
266276
266698
  let isDir = false;
266277
266699
  try {
266278
- isDir = statSync20(fullPath).isDirectory();
266700
+ isDir = statSync21(fullPath).isDirectory();
266279
266701
  } catch {
266280
266702
  return;
266281
266703
  }
@@ -266374,7 +266796,7 @@ async function handleListMemoryCommand(parsed, socket, safeSocketSend, overrides
266374
266796
  const isMemfsEnabledOnServer2 = overrides.isMemfsEnabledOnServer ?? actualIsMemfsEnabledOnServer;
266375
266797
  const { scanMemoryFilesystem: scanMemoryFilesystem2, getFileNodes: getFileNodes2, readFileContent: readFileContent2 } = await Promise.resolve().then(() => (init_memory_scanner(), exports_memory_scanner));
266376
266798
  const { parseFrontmatter: parseFrontmatter2 } = await Promise.resolve().then(() => exports_frontmatter);
266377
- const { existsSync: existsSync52, statSync: statSync21 } = await import("node:fs");
266799
+ const { existsSync: existsSync52, statSync: statSync22 } = await import("node:fs");
266378
266800
  const { join: join77, posix: posix3 } = await import("node:path");
266379
266801
  const memoryRoot = getMemoryFilesystemRoot2(parsed.agent_id);
266380
266802
  let memfsInitialized = existsSync52(join77(memoryRoot, ".git"));
@@ -266471,7 +266893,7 @@ async function handleListMemoryCommand(parsed, socket, safeSocketSend, overrides
266471
266893
  if (imageMimeType) {
266472
266894
  let size2 = 0;
266473
266895
  try {
266474
- size2 = statSync21(node.fullPath).size;
266896
+ size2 = statSync22(node.fullPath).size;
266475
266897
  } catch {}
266476
266898
  return {
266477
266899
  relative_path: node.relativePath,
@@ -276020,7 +276442,7 @@ ${lanes.join(`
276020
276442
  return process.memoryUsage().heapUsed;
276021
276443
  },
276022
276444
  getFileSize(path35) {
276023
- const stat12 = statSync21(path35);
276445
+ const stat12 = statSync22(path35);
276024
276446
  if (stat12 == null ? undefined : stat12.isFile()) {
276025
276447
  return stat12.size;
276026
276448
  }
@@ -276063,7 +276485,7 @@ ${lanes.join(`
276063
276485
  }
276064
276486
  };
276065
276487
  return nodeSystem;
276066
- function statSync21(path35) {
276488
+ function statSync22(path35) {
276067
276489
  try {
276068
276490
  return _fs.statSync(path35, statSyncOptions);
276069
276491
  } catch {
@@ -276115,7 +276537,7 @@ ${lanes.join(`
276115
276537
  activeSession.post("Profiler.stop", (err, { profile }) => {
276116
276538
  var _a10;
276117
276539
  if (!err) {
276118
- if ((_a10 = statSync21(profilePath)) == null ? undefined : _a10.isDirectory()) {
276540
+ if ((_a10 = statSync22(profilePath)) == null ? undefined : _a10.isDirectory()) {
276119
276541
  profilePath = _path.join(profilePath, `${(/* @__PURE__ */ new Date()).toISOString().replace(/:/g, "-")}+P${process.pid}.cpuprofile`);
276120
276542
  }
276121
276543
  try {
@@ -276224,7 +276646,7 @@ ${lanes.join(`
276224
276646
  let stat12;
276225
276647
  if (typeof dirent === "string" || dirent.isSymbolicLink()) {
276226
276648
  const name = combinePaths(path35, entry);
276227
- stat12 = statSync21(name);
276649
+ stat12 = statSync22(name);
276228
276650
  if (!stat12) {
276229
276651
  continue;
276230
276652
  }
@@ -276248,7 +276670,7 @@ ${lanes.join(`
276248
276670
  return matchFiles(path35, extensions, excludes, includes2, useCaseSensitiveFileNames2, process.cwd(), depth, getAccessibleFileSystemEntries, realpath6);
276249
276671
  }
276250
276672
  function fileSystemEntryExists(path35, entryKind) {
276251
- const stat12 = statSync21(path35);
276673
+ const stat12 = statSync22(path35);
276252
276674
  if (!stat12) {
276253
276675
  return false;
276254
276676
  }
@@ -276282,7 +276704,7 @@ ${lanes.join(`
276282
276704
  }
276283
276705
  function getModifiedTime3(path35) {
276284
276706
  var _a10;
276285
- return (_a10 = statSync21(path35)) == null ? undefined : _a10.mtime;
276707
+ return (_a10 = statSync22(path35)) == null ? undefined : _a10.mtime;
276286
276708
  }
276287
276709
  function setModifiedTime(path35, time3) {
276288
276710
  try {
@@ -437757,7 +438179,7 @@ import {
437757
438179
  mkdirSync as mkdirSync42,
437758
438180
  readdirSync as readdirSync23,
437759
438181
  readFileSync as readFileSync46,
437760
- statSync as statSync21,
438182
+ statSync as statSync22,
437761
438183
  unlinkSync as unlinkSync13,
437762
438184
  writeFileSync as writeFileSync36
437763
438185
  } from "node:fs";
@@ -438586,7 +439008,7 @@ async function loadLocalMods(options) {
438586
439008
  recordLegacyExtensionLoadedDiagnostic(registry4, owner, source, options.onDiagnostic);
438587
439009
  }
438588
439010
  try {
438589
- const mtimeMs = statSync21(modPath).mtimeMs;
439011
+ const mtimeMs = statSync22(modPath).mtimeMs;
438590
439012
  const sourceText = readFileSync46(modPath, "utf8");
438591
439013
  recordDeprecatedContextApiSourceDiagnostics(sourceText, (diagnostic) => {
438592
439014
  recordModDiagnostic(registry4, {
@@ -453387,7 +453809,7 @@ var init_deepagents = __esm(() => {
453387
453809
  });
453388
453810
 
453389
453811
  // node_modules/@letta-ai/trajectory/dist/adapters/listing-shared.js
453390
- import { readdirSync as readdirSync25, statSync as statSync22 } from "node:fs";
453812
+ import { readdirSync as readdirSync25, statSync as statSync23 } from "node:fs";
453391
453813
  import { join as join83 } from "node:path";
453392
453814
  function sortListings(items3) {
453393
453815
  return items3.sort((left, right) => {
@@ -453411,7 +453833,7 @@ function safeReadDir(path42) {
453411
453833
  }
453412
453834
  function safeStat(path42) {
453413
453835
  try {
453414
- const stats = statSync22(path42);
453836
+ const stats = statSync23(path42);
453415
453837
  return { mtimeMs: stats.mtimeMs, sizeBytes: stats.size };
453416
453838
  } catch {
453417
453839
  return;
@@ -468690,7 +469112,7 @@ var init_memory_tokens = __esm(() => {
468690
469112
  });
468691
469113
 
468692
469114
  // src/cli/subcommands/memory.ts
468693
- 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";
468694
469116
  import { readdir as readdir12 } from "node:fs/promises";
468695
469117
  import { dirname as dirname35, join as join94 } from "node:path";
468696
469118
  import { parseArgs as parseArgs11 } from "node:util";
@@ -468765,7 +469187,7 @@ async function listBackups(agentId) {
468765
469187
  const path46 = join94(agentRoot, entry.name);
468766
469188
  let createdAt = null;
468767
469189
  try {
468768
- const stat15 = statSync23(path46);
469190
+ const stat15 = statSync24(path46);
468769
469191
  createdAt = stat15.mtime.toISOString();
468770
469192
  } catch {
468771
469193
  createdAt = null;
@@ -468892,7 +469314,7 @@ async function runMemorySubcommand(argv, deps = {}) {
468892
469314
  console.error(`Backup not found: ${backupPath}`);
468893
469315
  return 1;
468894
469316
  }
468895
- const stat15 = statSync23(backupPath);
469317
+ const stat15 = statSync24(backupPath);
468896
469318
  if (!stat15.isDirectory()) {
468897
469319
  console.error(`Backup path is not a directory: ${backupPath}`);
468898
469320
  return 1;
@@ -468915,7 +469337,7 @@ async function runMemorySubcommand(argv, deps = {}) {
468915
469337
  return 1;
468916
469338
  }
468917
469339
  if (existsSync64(out)) {
468918
- const stat15 = statSync23(out);
469340
+ const stat15 = statSync24(out);
468919
469341
  if (stat15.isDirectory()) {
468920
469342
  const contents = await readdir12(out);
468921
469343
  if (contents.length > 0) {
@@ -468963,7 +469385,7 @@ var init_memory7 = __esm(() => {
468963
469385
  });
468964
469386
 
468965
469387
  // src/backend/local/transcript-search.ts
468966
- 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";
468967
469389
  import { join as join95 } from "node:path";
468968
469390
  function readJsonFile3(path46) {
468969
469391
  if (!existsSync65(path46))
@@ -469251,7 +469673,7 @@ function conversationDirectories(storageDir) {
469251
469673
  if (!existsSync65(conversationsDir))
469252
469674
  return [];
469253
469675
  try {
469254
- 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());
469255
469677
  } catch {
469256
469678
  return [];
469257
469679
  }
@@ -474465,7 +474887,7 @@ import {
474465
474887
  mkdtempSync as mkdtempSync6,
474466
474888
  readFileSync as readFileSync50,
474467
474889
  rmSync as rmSync25,
474468
- statSync as statSync25,
474890
+ statSync as statSync26,
474469
474891
  writeFileSync as writeFileSync41
474470
474892
  } from "node:fs";
474471
474893
  import { mkdir as mkdir16, readdir as readdir13 } from "node:fs/promises";
@@ -474923,7 +475345,7 @@ async function installSkillDirectory(params) {
474923
475345
  if (!existsSync69(skillMdPath)) {
474924
475346
  throw new Error("No SKILL.md found in the skill directory.");
474925
475347
  }
474926
- if (!statSync25(sourceDir).isDirectory()) {
475348
+ if (!statSync26(sourceDir).isDirectory()) {
474927
475349
  throw new Error(`Skill source is not a directory: ${sourceDir}`);
474928
475350
  }
474929
475351
  const name = getSkillName(sourceDir);
@@ -474982,7 +475404,7 @@ async function deleteSkillDirectory(params) {
474982
475404
  if (!existsSync69(targetPath)) {
474983
475405
  throw new Error(`Skill "${name}" is not installed at ${targetPath}.`);
474984
475406
  }
474985
- if (!statSync25(targetPath).isDirectory()) {
475407
+ if (!statSync26(targetPath).isDirectory()) {
474986
475408
  throw new Error(`Skill path is not a directory: ${targetPath}`);
474987
475409
  }
474988
475410
  rmSync25(targetPath, { recursive: true, force: true });
@@ -476880,7 +477302,7 @@ function firstDefinedBoolean(...values4) {
476880
477302
  }
476881
477303
  function normalizeChatTarget(channel, value) {
476882
477304
  const trimmed = value.trim();
476883
- if (channel === "signal")
477305
+ if (channel === "signal" || channel === "microsoftteams")
476884
477306
  return trimmed;
476885
477307
  const parts = trimmed.split(":").map((part) => part.trim()).filter(Boolean);
476886
477308
  if (parts.length === 2 && /^[a-z_-]+$/i.test(parts[0] ?? "")) {
@@ -484346,6 +484768,9 @@ Current session AGENT_ID=${process.env.AGENT_ID}; --backend local switches to a
484346
484768
  await exitHeadless(1, "headless_stop_reason_error");
484347
484769
  }
484348
484770
  } catch (error5) {
484771
+ if (sigintSignal.aborted) {
484772
+ await exitInterrupted();
484773
+ }
484349
484774
  markIncompleteToolsAsCancelled(buffers, true, "stream_error");
484350
484775
  const errorDetails = formatErrorDetails2(error5, agent.id);
484351
484776
  trackHeadlessBoundaryError("headless_runtime_exception", error5, "headless_turn_execution");
@@ -484760,6 +485185,30 @@ async function runBidirectionalMode(agent, conversationId, effectiveModel, _outp
484760
485185
  lineResolver = resolve42;
484761
485186
  });
484762
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
+ }
484763
485212
  async function requestPermission(toolCallId, toolName2, toolInput) {
484764
485213
  const requestId = `perm-${toolCallId}`;
484765
485214
  const diffs = await computeDiffPreviews(toolName2, toolInput);
@@ -484920,7 +485369,14 @@ async function runBidirectionalMode(agent, conversationId, effectiveModel, _outp
484920
485369
  const toolsRequest = message2.request;
484921
485370
  const tools = toolsRequest.tools ?? [];
484922
485371
  registerExternalTools(tools);
484923
- 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
+ }
484924
485380
  const execRequest = {
484925
485381
  type: "control_request",
484926
485382
  request_id: `ext-${toolCallId}`,
@@ -484932,25 +485388,48 @@ async function runBidirectionalMode(agent, conversationId, effectiveModel, _outp
484932
485388
  }
484933
485389
  };
484934
485390
  writeWireMessage(execRequest);
484935
- while (true) {
484936
- const line2 = await getNextLine();
484937
- if (line2 === null) {
484938
- return {
484939
- content: [{ type: "text", text: "stdin closed" }],
484940
- isError: true
484941
- };
484942
- }
484943
- if (!line2.trim())
484944
- continue;
484945
- try {
484946
- const msg = JSON.parse(line2);
484947
- 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) {
484948
485396
  return {
484949
- content: msg.response.content ?? [{ type: "text", text: "" }],
484950
- isError: msg.response.is_error ?? false
485397
+ content: [{ type: "text", text: INTERRUPTED_BY_USER }],
485398
+ isError: true
484951
485399
  };
484952
485400
  }
484953
- } 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);
484954
485433
  }
484955
485434
  });
484956
485435
  const registerResponse = {
@@ -485370,10 +485849,18 @@ async function runBidirectionalMode(agent, conversationId, effectiveModel, _outp
485370
485849
  }
485371
485850
  const { executeApprovalBatch: executeApprovalBatch2 } = await init_approval_execution().then(() => exports_approval_execution);
485372
485851
  emitLocalToolCalls(decisions, sessionId);
485373
- const executedResults = await executeApprovalBatch2(decisions, undefined, {
485374
- toolContextId: turnToolContextId ?? undefined,
485375
- abortSignal: currentAbortController.signal
485376
- });
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
+ }
485377
485864
  emitLocalToolReturns(executedResults, sessionId);
485378
485865
  currentInput = responseState.prepare(executedResults, needsUserInput.length === 0);
485379
485866
  continue;
@@ -485491,6 +485978,7 @@ var LLM_API_ERROR_MAX_RETRIES2 = 3, EMPTY_RESPONSE_MAX_RETRIES2 = 2, HEADLESS_ST
485491
485978
  var init_headless = __esm(async () => {
485492
485979
  init_error();
485493
485980
  init_preloaded_skills();
485981
+ init_constants2();
485494
485982
  init_headless_message_sender();
485495
485983
  init_headless_response_state();
485496
485984
  init_headless_startup_backend();
@@ -497448,7 +497936,6 @@ var init_SlashCommandAutocomplete = __esm(async () => {
497448
497936
  "creating-skills",
497449
497937
  "customizing-statusline",
497450
497938
  "initializing-memory",
497451
- "migrating-memory",
497452
497939
  "syncing-memory-filesystem"
497453
497940
  ]);
497454
497941
  _allCommands = Object.entries(commands).filter(([, { hidden }]) => !hidden).map(([cmd, { desc, order }]) => ({
@@ -498445,6 +498932,7 @@ function Input({
498445
498932
  queueMode = "immediate",
498446
498933
  deferModeSupported = false,
498447
498934
  interruptRequested = false,
498935
+ hasStopFailedTasks = false,
498448
498936
  agentId,
498449
498937
  agentName,
498450
498938
  currentModel,
@@ -498744,7 +499232,7 @@ function Input({
498744
499232
  onBashInterrupt();
498745
499233
  return;
498746
499234
  }
498747
- if ((streaming3 || interruptRequested) && onInterrupt) {
499235
+ if ((streaming3 || interruptRequested || hasStopFailedTasks) && onInterrupt) {
498748
499236
  onInterrupt();
498749
499237
  return;
498750
499238
  }
@@ -514750,6 +515238,7 @@ function AppView(props) {
514750
515238
  inputEnabled,
514751
515239
  inputVisible,
514752
515240
  interruptRequested,
515241
+ hasStopFailedTasks,
514753
515242
  isAgentBusy,
514754
515243
  liveItems,
514755
515244
  liveTrajectoryElapsedBaseMs,
@@ -514978,6 +515467,7 @@ function AppView(props) {
514978
515467
  queueMode,
514979
515468
  deferModeSupported,
514980
515469
  interruptRequested,
515470
+ hasStopFailedTasks,
514981
515471
  agentId,
514982
515472
  agentName,
514983
515473
  currentModel: currentModelDisplay,
@@ -516136,6 +516626,12 @@ function useApprovalFlow(ctx) {
516136
516626
  startupApprovals
516137
516627
  ]);
516138
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);
516139
516635
  try {
516140
516636
  if (userCancelledRef.current || abortControllerRef.current?.signal.aborted) {
516141
516637
  setStreaming(false);
@@ -516145,12 +516641,13 @@ function useApprovalFlow(ctx) {
516145
516641
  setApprovalResults([]);
516146
516642
  setAutoHandledResults([]);
516147
516643
  setAutoDeniedApprovals([]);
516644
+ clearApprovalToolContext();
516645
+ toolAbortControllerRef.current = null;
516646
+ executingToolCallIdsRef.current = [];
516647
+ interruptQueuedRef.current = false;
516648
+ toolResultsInFlightRef.current = false;
516148
516649
  return;
516149
516650
  }
516150
- const approvalResultsSnapshot = [...approvalResults];
516151
- const autoHandledSnapshot = [...autoHandledResults];
516152
- const autoDeniedSnapshot = [...autoDeniedApprovals];
516153
- const pendingSnapshot = [...pendingApprovals];
516154
516651
  setPendingApprovals([]);
516155
516652
  setApprovalContexts([]);
516156
516653
  setApprovalResults([]);
@@ -516159,7 +516656,6 @@ function useApprovalFlow(ctx) {
516159
516656
  setStreaming(true);
516160
516657
  openTrajectorySegment();
516161
516658
  buffersRef.current.interrupted = false;
516162
- const approvalAbortController = new AbortController;
516163
516659
  toolAbortControllerRef.current = approvalAbortController;
516164
516660
  const allDecisions = [
516165
516661
  ...approvalResultsSnapshot,
@@ -516201,23 +516697,10 @@ function useApprovalFlow(ctx) {
516201
516697
  localToolMs: toolRunMs
516202
516698
  });
516203
516699
  }
516204
- const allResults = [
516205
- ...autoHandledSnapshot.map((ar) => ({
516206
- type: "tool",
516207
- tool_call_id: ar.toolCallId,
516208
- tool_return: ar.result.toolReturn,
516209
- status: ar.result.status,
516210
- stdout: ar.result.stdout,
516211
- stderr: ar.result.stderr
516212
- })),
516213
- ...autoDeniedSnapshot.map((ad) => ({
516214
- type: "approval",
516215
- tool_call_id: ad.approval.toolCallId,
516216
- approve: false,
516217
- reason: ad.reason
516218
- })),
516219
- ...executedResults
516220
- ];
516700
+ if (toolAbortControllerRef.current !== approvalAbortController) {
516701
+ return;
516702
+ }
516703
+ const allResults = [...companionResults, ...executedResults];
516221
516704
  if (true) {
516222
516705
  const expectedIds = new Set([
516223
516706
  ...autoHandledSnapshot.map((ar) => ar.toolCallId),
@@ -516236,9 +516719,7 @@ function useApprovalFlow(ctx) {
516236
516719
  const wasAborted = approvalAbortController.signal.aborted;
516237
516720
  const userCancelled = userCancelledRef.current;
516238
516721
  if (wasAborted || userCancelled) {
516239
- if (!interruptQueuedRef.current) {
516240
- queueApprovalResults(allResults);
516241
- }
516722
+ queueApprovalResults(allResults);
516242
516723
  setStreaming(false);
516243
516724
  closeTrajectorySegment();
516244
516725
  syncTrajectoryElapsedBase();
@@ -516259,6 +516740,24 @@ function useApprovalFlow(ctx) {
516259
516740
  queueApprovalResults(null);
516260
516741
  }
516261
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
+ }
516262
516761
  markIncompleteToolsAsCancelled(buffersRef.current, true, "stream_error");
516263
516762
  const errorDetails = formatErrorDetails2(error5, agentId);
516264
516763
  appendError(errorDetails, {
@@ -516270,12 +516769,14 @@ function useApprovalFlow(ctx) {
516270
516769
  syncTrajectoryElapsedBase();
516271
516770
  refreshDerived();
516272
516771
  } finally {
516273
- clearApprovalToolContext();
516274
- setIsExecutingTool(false);
516275
- toolAbortControllerRef.current = null;
516276
- executingToolCallIdsRef.current = [];
516277
- interruptQueuedRef.current = false;
516278
- 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
+ }
516279
516780
  }
516280
516781
  }, [
516281
516782
  agentId,
@@ -516603,7 +517104,10 @@ var init_use_approval_flow = __esm(async () => {
516603
517104
  init_debug();
516604
517105
  init_notifications();
516605
517106
  init_turn_input();
516606
- await init_manager2();
517107
+ await __promiseAll([
517108
+ init_approval_execution(),
517109
+ init_manager2()
517110
+ ]);
516607
517111
  import_react113 = __toESM(require_react(), 1);
516608
517112
  });
516609
517113
 
@@ -517902,6 +518406,7 @@ function useConversationLoop(ctx) {
517902
518406
  precomputedDiffsRef,
517903
518407
  prepareScopedToolExecutionContext,
517904
518408
  processingConversationRef,
518409
+ queuedApprovalResultsRef,
517905
518410
  queueApprovalResults,
517906
518411
  queueSnapshotRef,
517907
518412
  quotaAutoSwapAttemptedRef,
@@ -518799,7 +519304,11 @@ ${feedback}
518799
519304
  const autoAllowedAbortController = abortControllerRef.current ?? new AbortController;
518800
519305
  const shouldTrackAutoAllowed = autoAllowedToolCallIds.length > 0;
518801
519306
  let autoAllowedResults = [];
518802
- let autoDeniedResults = [];
519307
+ const autoDeniedResults = autoDenied.map((ac) => ({
519308
+ approval: ac.approval,
519309
+ reason: formatPermissionDenial(ac.permission)
519310
+ }));
519311
+ const prebuiltDeniedApprovalResults = buildDeniedResults(autoDeniedResults);
518803
519312
  if (shouldTrackAutoAllowed) {
518804
519313
  setIsExecutingTool(true);
518805
519314
  executingToolCallIdsRef.current = autoAllowedToolCallIds;
@@ -518816,29 +519325,35 @@ ${feedback}
518816
519325
  setToolCallsRunning(buffersRef.current, autoAllowedToolCallIds);
518817
519326
  refreshDerived();
518818
519327
  }
518819
- const approvalToolContextId = approvalToolContextIdRef.current ?? (await prepareScopedToolExecutionContext(tempModelOverrideRef.current ?? undefined)).preparedToolContext.contextId;
518820
- autoAllowedResults = autoAllowed.length > 0 ? await executeAutoAllowedTools(autoAllowed, (chunk2) => onChunk(buffersRef.current, chunk2), {
518821
- abortSignal: autoAllowedAbortController.signal,
518822
- onStreamingOutput: updateStreamingOutput,
518823
- toolContextId: approvalToolContextId
518824
- }) : [];
518825
- autoDeniedResults = autoDenied.map((ac) => {
518826
- 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) {
518827
519346
  onChunk(buffersRef.current, {
518828
519347
  message_type: "tool_return_message",
518829
519348
  id: "dummy",
518830
519349
  date: new Date().toISOString(),
518831
- tool_call_id: ac.approval.toolCallId,
518832
- 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}`,
518833
519352
  status: "error",
518834
519353
  stdout: null,
518835
519354
  stderr: null
518836
519355
  });
518837
- return {
518838
- approval: ac.approval,
518839
- reason
518840
- };
518841
- });
519356
+ }
518842
519357
  const allResults = [
518843
519358
  ...autoAllowedResults.map((ar) => ({
518844
519359
  type: "tool",
@@ -518848,12 +519363,7 @@ ${feedback}
518848
519363
  stdout: ar.result.stdout,
518849
519364
  stderr: ar.result.stderr
518850
519365
  })),
518851
- ...autoDeniedResults.map((ad) => ({
518852
- type: "approval",
518853
- tool_call_id: ad.approval.toolCallId,
518854
- approve: false,
518855
- reason: ad.reason
518856
- }))
519366
+ ...prebuiltDeniedApprovalResults
518857
519367
  ];
518858
519368
  if (autoAllowedExecutionRef.current) {
518859
519369
  autoAllowedExecutionRef.current.results = allResults;
@@ -518951,7 +519461,7 @@ ${feedback}
518951
519461
  return;
518952
519462
  }
518953
519463
  } finally {
518954
- if (shouldTrackAutoAllowed) {
519464
+ if (shouldTrackAutoAllowed && toolAbortControllerRef.current === autoAllowedAbortController) {
518955
519465
  setIsExecutingTool(false);
518956
519466
  toolAbortControllerRef.current = null;
518957
519467
  executingToolCallIdsRef.current = [];
@@ -519285,8 +519795,16 @@ ${feedback}
519285
519795
  }
519286
519796
  } catch (e2) {
519287
519797
  debugWarn("message_stream", "Unhandled conversation error: %s", e2 instanceof Error ? e2.stack ?? e2.message : String(e2));
519288
- markIncompleteToolsAsCancelled(buffersRef.current, true, e2 instanceof APIUserAbortError ? "user_interrupt" : "stream_error");
519289
- 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
+ }
519290
519808
  setStreaming(false);
519291
519809
  refreshDerived();
519292
519810
  return;
@@ -520045,18 +520563,23 @@ var init_use_feedback_handler = __esm(() => {
520045
520563
  });
520046
520564
 
520047
520565
  // src/cli/app/use-interrupt-handler.ts
520048
- function hasActiveReflectionSubagentForAgent(agentId) {
520566
+ function hasActiveReflectionSubagentForAgent(agentId, conversationId) {
520049
520567
  return getSubagents().some((agent) => {
520050
520568
  if (agent.type.toLowerCase() !== "reflection")
520051
520569
  return false;
520052
520570
  if (agent.status !== "pending" && agent.status !== "running")
520053
520571
  return false;
520054
- return agent.parentAgentId === agentId;
520572
+ if (agent.parentAgentId !== agentId)
520573
+ return false;
520574
+ return conversationId === undefined || agent.parentConversationId === undefined || agent.parentConversationId === conversationId;
520055
520575
  });
520056
520576
  }
520057
- function interruptSubagentsAndReleaseReflection(agentId) {
520058
- const interruptedReflection = hasActiveReflectionSubagentForAgent(agentId);
520059
- 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
+ });
520060
520583
  if (interruptedReflection) {
520061
520584
  releaseReflectionLaunch(agentId);
520062
520585
  }
@@ -520158,7 +520681,7 @@ function useInterruptHandler(ctx) {
520158
520681
  autoAllowedExecutionRef.current = null;
520159
520682
  buffersRef.current.abortGeneration = (buffersRef.current.abortGeneration || 0) + 1;
520160
520683
  const toolsCancelled = markIncompleteToolsAsCancelled(buffersRef.current, true, "user_interrupt");
520161
- interruptSubagentsAndReleaseReflection(agentId);
520684
+ interruptSubagentsAndReleaseReflection(agentId, conversationIdRef.current);
520162
520685
  if (!toolsCancelled) {
520163
520686
  appendError(INTERRUPT_MESSAGE, true);
520164
520687
  }
@@ -520188,6 +520711,16 @@ function useInterruptHandler(ctx) {
520188
520711
  bumpDequeueEpoch: () => setDequeueEpoch((e2) => e2 + 1)
520189
520712
  });
520190
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
+ }
520191
520724
  return;
520192
520725
  }
520193
520726
  if (waitingForQueueCancelRef.current) {
@@ -520198,7 +520731,7 @@ function useInterruptHandler(ctx) {
520198
520731
  setInterruptRequested(true);
520199
520732
  buffersRef.current.abortGeneration = (buffersRef.current.abortGeneration || 0) + 1;
520200
520733
  const toolsCancelled = markIncompleteToolsAsCancelled(buffersRef.current, true, "user_interrupt");
520201
- interruptSubagentsAndReleaseReflection(agentId);
520734
+ interruptSubagentsAndReleaseReflection(agentId, conversationIdRef.current);
520202
520735
  abortControllerRef.current?.abort();
520203
520736
  pendingInterruptRecoveryConversationIdRef.current = conversationIdRef.current;
520204
520737
  userCancelledRef.current = true;
@@ -529463,6 +529996,7 @@ Memory may be stale. Try running: git -C ${getScopedMemoryFilesystemRoot(agentId
529463
529996
  precomputedDiffsRef,
529464
529997
  prepareScopedToolExecutionContext,
529465
529998
  processingConversationRef,
529999
+ queuedApprovalResultsRef,
529466
530000
  queueApprovalResults,
529467
530001
  queueSnapshotRef,
529468
530002
  quotaAutoSwapAttemptedRef,
@@ -530219,6 +530753,18 @@ Memory may be stale. Try running: git -C ${getScopedMemoryFilesystemRoot(agentId
530219
530753
  deferredCommitAt
530220
530754
  ]);
530221
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]);
530222
530768
  const estimatedLiveHeight = import_react123.useMemo(() => {
530223
530769
  const countLines4 = (text3) => {
530224
530770
  if (!text3)
@@ -530466,6 +531012,7 @@ Memory may be stale. Try running: git -C ${getScopedMemoryFilesystemRoot(agentId
530466
531012
  inputEnabled,
530467
531013
  inputVisible,
530468
531014
  interruptRequested: interruptRequested || !!abortControllerRef.current?.signal.aborted,
531015
+ hasStopFailedTasks,
530469
531016
  isAgentBusy,
530470
531017
  liveItems,
530471
531018
  liveTrajectoryElapsedBaseMs,
@@ -534743,4 +535290,4 @@ function registerBunOAuthFlows() {
534743
535290
  registerBunOAuthFlows();
534744
535291
  await init_src5().then(() => exports_src2);
534745
535292
 
534746
- //# debugId=25D231F0C01DA6C764756E2164756E21
535293
+ //# debugId=9CD2133B459655FB64756E2164756E21