@swifty.js/swifty 0.0.35 → 0.0.37

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 (34) hide show
  1. package/README.md +8 -8
  2. package/dist/{acp-A4KRKOHD.js → acp-R22FJPVN.js} +1 -1
  3. package/dist/{agent-VDWIGFIV.js → agent-WOWGQDBG.js} +1 -1
  4. package/dist/anthropic-3DXT42SN.js +4 -0
  5. package/dist/{chunk-4B4SGVJH.js → chunk-ENZ5LHRN.js} +23 -23
  6. package/dist/chunk-FOPUR6OB.js +610 -0
  7. package/dist/{chunk-IOLGXPUQ.js → chunk-LW273HD5.js} +1 -1
  8. package/dist/{chunk-TNF3EHFY.js → chunk-QBGGDF62.js} +1 -1
  9. package/dist/{chunk-TOM34LJB.js → chunk-R62BGTT4.js} +17 -17
  10. package/dist/chunk-S2VEXEVV.js +4 -0
  11. package/dist/{chunk-H56UQALW.js → chunk-VN7HK3YD.js} +1 -1
  12. package/dist/lib/{agent-TC4HSVMS.js → agent-ZSFK66FQ.js} +2 -2
  13. package/dist/lib/{anthropic-FKIGL7NL.js → anthropic-GD3ILYG6.js} +1 -1
  14. package/dist/lib/{chunk-S5VWPTME.js → chunk-5OJV33Y2.js} +37 -14
  15. package/dist/lib/{chunk-RUDYC7OS.js → chunk-BHSNJT6Z.js} +1 -1
  16. package/dist/lib/{chunk-LRN4MOQ3.js → chunk-GWZDEU27.js} +0 -5
  17. package/dist/lib/{chunk-ALQHMYOZ.js → chunk-LKFJIK6F.js} +0 -3
  18. package/dist/lib/{chunk-OJTISIYU.js → chunk-O35GIE35.js} +1 -1
  19. package/dist/lib/{chunk-6V7RW6HA.js → chunk-TD7Z6L4D.js} +10 -13
  20. package/dist/lib/index.d.ts +689 -563
  21. package/dist/lib/index.js +1153 -729
  22. package/dist/lib/{openai-ZWIYIBLC.js → openai-N6X7XUQJ.js} +1 -1
  23. package/dist/lib/{permissions-BW4QPZXV.js → permissions-77UJQITR.js} +1 -1
  24. package/dist/lib/{providers-ROWSB6ND.js → providers-WQ3UOLMF.js} +1 -1
  25. package/dist/lib/{tool-filter-QU4LOZII.js → tool-filter-S3ZZOXFD.js} +1 -1
  26. package/dist/main.js +154 -153
  27. package/dist/{openai-7LMX5HKW.js → openai-XMJVVTTX.js} +1 -1
  28. package/dist/{providers-2J4JJYQD.js → providers-NWLGMQOZ.js} +1 -1
  29. package/dist/{server-ZYK4L22D.js → server-TINTK5DM.js} +1 -1
  30. package/dist/{tool-filter-U5SSL3LR.js → tool-filter-QETBQPHL.js} +1 -1
  31. package/package.json +2 -2
  32. package/dist/anthropic-AR2Y4FBR.js +0 -4
  33. package/dist/chunk-F4FOFPKL.js +0 -611
  34. package/dist/chunk-UJGMTIYO.js +0 -4
package/dist/lib/index.js CHANGED
@@ -10,8 +10,10 @@ import {
10
10
  ConversationManager,
11
11
  FileHistory,
12
12
  RecoveryState,
13
+ TOOL_RESULT_PREVIEW_CHARS,
13
14
  agent_exports,
14
15
  asImageMediaType,
16
+ buildPersistedOutputPreview,
15
17
  captureTelemetryError,
16
18
  compact_exports,
17
19
  conversation_exports,
@@ -27,6 +29,7 @@ import {
27
29
  loadSession,
28
30
  maybeResizeAndDownsampleImage,
29
31
  newSessionId,
32
+ persistLargeResult,
30
33
  plan_file_exports,
31
34
  plan_mode_exports,
32
35
  prompts_exports,
@@ -37,25 +40,26 @@ import {
37
40
  saveMessage,
38
41
  session_exports,
39
42
  shutdownTelemetry,
43
+ spillDir,
40
44
  telemetry_exports,
41
45
  tool_result_exports
42
- } from "./chunk-S5VWPTME.js";
46
+ } from "./chunk-5OJV33Y2.js";
43
47
  import {
44
48
  version,
45
49
  version_exports
46
- } from "./chunk-OJTISIYU.js";
50
+ } from "./chunk-O35GIE35.js";
47
51
  import {
48
52
  anthropic_exports,
49
53
  decideAndApply,
50
54
  strategy_exports
51
- } from "./chunk-6V7RW6HA.js";
55
+ } from "./chunk-TD7Z6L4D.js";
52
56
  import {
53
57
  SKIP_DIRS,
54
58
  types_exports
55
59
  } from "./chunk-BQFVUVLI.js";
56
60
  import {
57
61
  openai_exports
58
- } from "./chunk-ALQHMYOZ.js";
62
+ } from "./chunk-LKFJIK6F.js";
59
63
  import {
60
64
  DEFAULT_CONTEXT_WINDOW,
61
65
  DEFAULT_MAX_OUTPUT_TOKENS,
@@ -78,7 +82,7 @@ import {
78
82
  PermissionChecker,
79
83
  isSafeCommand,
80
84
  permissions_exports
81
- } from "./chunk-RUDYC7OS.js";
85
+ } from "./chunk-BHSNJT6Z.js";
82
86
  import {
83
87
  MCPToolWrapper,
84
88
  MCP_TOOL_PREFIX,
@@ -113,7 +117,7 @@ import {
113
117
  filterToolsForAgent,
114
118
  registry_exports,
115
119
  tool_filter_exports
116
- } from "./chunk-LRN4MOQ3.js";
120
+ } from "./chunk-GWZDEU27.js";
117
121
  import {
118
122
  bwrap_exports
119
123
  } from "./chunk-YIWNQ7OA.js";
@@ -347,9 +351,9 @@ __export(server_exports, {
347
351
  RemoteServer: () => RemoteServer,
348
352
  createRemoteAgent: () => createRemoteAgent
349
353
  });
350
- import { readFileSync as readFileSync16, existsSync as existsSync16, statSync as statSync9 } from "fs";
354
+ import { readFileSync as readFileSync16, existsSync as existsSync16, statSync as statSync12 } from "fs";
351
355
  import { createServer } from "http";
352
- import { join as join20, extname as extname2, normalize } from "path";
356
+ import { join as join21, extname as extname2, normalize } from "path";
353
357
  import { cwd } from "process";
354
358
  import Koa from "koa";
355
359
  import { WebSocketServer, WebSocket } from "ws";
@@ -1400,21 +1404,21 @@ __export(client_exports, {
1400
1404
  async function createClient(config, systemPrompt) {
1401
1405
  switch (config.protocol) {
1402
1406
  case "anthropic": {
1403
- const { AnthropicClient } = await import("./anthropic-FKIGL7NL.js");
1407
+ const { AnthropicClient } = await import("./anthropic-GD3ILYG6.js");
1404
1408
  return registerLlmClient(new AnthropicClient(config, systemPrompt), {
1405
1409
  model: config.model,
1406
1410
  protocol: config.protocol
1407
1411
  });
1408
1412
  }
1409
1413
  case "openai": {
1410
- const { OpenAIClient } = await import("./openai-ZWIYIBLC.js");
1414
+ const { OpenAIClient } = await import("./openai-N6X7XUQJ.js");
1411
1415
  return registerLlmClient(new OpenAIClient(config, systemPrompt), {
1412
1416
  model: config.model,
1413
1417
  protocol: config.protocol
1414
1418
  });
1415
1419
  }
1416
1420
  case "openai-compat": {
1417
- const { OpenAICompatClient } = await import("./openai-ZWIYIBLC.js");
1421
+ const { OpenAICompatClient } = await import("./openai-N6X7XUQJ.js");
1418
1422
  return registerLlmClient(new OpenAICompatClient(config, systemPrompt), {
1419
1423
  model: config.model,
1420
1424
  protocol: config.protocol
@@ -1927,11 +1931,14 @@ import { resolve as resolve4 } from "path";
1927
1931
  // src/tools/descriptions.ts
1928
1932
  var descriptions_exports = {};
1929
1933
  __export(descriptions_exports, {
1934
+ BASH_BACKGROUND_DESCRIPTION: () => BASH_BACKGROUND_DESCRIPTION,
1930
1935
  BASH_DESCRIPTION: () => BASH_DESCRIPTION,
1931
1936
  EDIT_FILE_DESCRIPTION: () => EDIT_FILE_DESCRIPTION,
1932
1937
  GLOB_DESCRIPTION: () => GLOB_DESCRIPTION,
1933
1938
  GREP_DESCRIPTION: () => GREP_DESCRIPTION,
1939
+ JAVASCRIPT_BACKGROUND_DESCRIPTION: () => JAVASCRIPT_BACKGROUND_DESCRIPTION,
1934
1940
  JAVASCRIPT_DESCRIPTION: () => JAVASCRIPT_DESCRIPTION,
1941
+ POWERSHELL_BACKGROUND_DESCRIPTION: () => POWERSHELL_BACKGROUND_DESCRIPTION,
1935
1942
  POWERSHELL_DESCRIPTION: () => POWERSHELL_DESCRIPTION,
1936
1943
  READ_FILE_DESCRIPTION: () => READ_FILE_DESCRIPTION,
1937
1944
  WEB_FETCH_DESCRIPTION: () => WEB_FETCH_DESCRIPTION,
@@ -1943,15 +1950,18 @@ var BASH_DESCRIPTION = `Execute command in Bash; return stdout and stderr. Prefe
1943
1950
  - Quote paths with spaces. To change directory, use cd "path" && command in the same call. Separate independent commands; chain dependent commands with &&, not ;.
1944
1951
  - Prefer dedicated file/search tools over cat, head, tail, sed, awk, echo, or find. Scope searches to a directory, never the filesystem root. Diagnose failures rather than retrying in sleep loops.
1945
1952
  ${GIT_GUIDANCE}`;
1953
+ var BASH_BACKGROUND_DESCRIPTION = `- Set run_in_background to true to run the command in the background. The call returns a task ID immediately and the result arrives later as a task notification; do not poll or sleep waiting for it. Use this for long-running commands you do not need the result of right away. Use TaskStop with the task_id to kill a background command early. A foreground command that exceeds its timeout is moved to the background automatically instead of being killed, unless it is a bare sleep.`;
1946
1954
  var POWERSHELL_DESCRIPTION = `Execute command in PowerShell; return stdout and stderr. Recommended on Windows (powershell.exe); uses pwsh elsewhere.
1947
1955
  - timeout is in seconds: default 120, maximum 600. Each call starts a fresh, independent shell in the Agent's working directory; location, variables, and options do not persist.
1948
1956
  - Quote paths with spaces; use Set-Location -LiteralPath "path" in the same call. Separate independent commands. For dependencies, check $LASTEXITCODE for native commands and use -ErrorAction Stop for cmdlets; ; does not stop on failure. Do not assume PowerShell 7 syntax.
1949
1957
  - Prefer dedicated file/search tools over Get-Content, Select-String, or Write-Output. Scope recursion to a directory, not a drive root. Diagnose failures rather than retrying in Start-Sleep loops.
1950
1958
  ${GIT_GUIDANCE}`;
1959
+ var POWERSHELL_BACKGROUND_DESCRIPTION = `- Set run_in_background to true to run the command in the background. The call returns a task ID immediately and the result arrives later as a task notification; do not poll or Start-Sleep waiting for it. Use this for long-running commands you do not need the result of right away. Use TaskStop with the task_id to kill a background command early. A foreground command that exceeds its timeout is moved to the background automatically instead of being killed, unless it is a bare Start-Sleep.`;
1951
1960
  var JAVASCRIPT_DESCRIPTION = `Execute JavaScript in a fresh isolated V8 context for pure computation.
1952
1961
  - Provide a function body and use return to produce a JSON-compatible result. Optional input is available as globalThis.input.
1953
1962
  - No filesystem, network, process, require, imports, timers, or host callbacks are available. Use Bash for Node.js or system operations.
1954
1963
  - timeout_ms defaults to 1000 (max 10000); memory_limit_mb defaults to 64 (max 256). Requires Node.js 24 or newer.`;
1964
+ var JAVASCRIPT_BACKGROUND_DESCRIPTION = `- Set run_in_background to true to evaluate in the background. The call returns a task ID immediately and the result arrives later as a task notification; do not poll. timeout_ms still caps the evaluation (max 10000). Use TaskStop with the task_id to abort it early.`;
1955
1965
  var READ_FILE_DESCRIPTION = `Read text with 1-based display line numbers, or images (png, jpg, jpeg, gif, webp) as visual content; not directories.
1956
1966
  - file_path is absolute or relative to the Agent's working directory. offset skips lines (0-based, default 0); limit defaults to 2000 lines, with a 50KB text output cap. Displayed line 101 starts at offset=100. Follow continuation/readback instructions for partial output.
1957
1967
  - Images ignore offset/limit. A successful read refreshes the file-state cache used by EditFile/WriteFile; re-read after an external-change error.`;
@@ -3178,7 +3188,9 @@ var MemoryManager = class {
3178
3188
  return this.loadAll();
3179
3189
  }
3180
3190
  /**
3181
- * Builds the memory index injected into the system prompt.
3191
+ * Builds the memory index injected into the conversation as a
3192
+ * system-reminder message (not the system prompt, which stays
3193
+ * project-independent to preserve prompt caching).
3182
3194
  *
3183
3195
  * This content is re-sent to the model on every conversation turn, so each additional index line
3184
3196
  * is a recurring cost. We therefore enforce both a line-count and a byte-size cap at the output
@@ -3268,8 +3280,9 @@ ${body}
3268
3280
  // ── Feature 2: findRelevantMemories ────────────────────────────────
3269
3281
  /**
3270
3282
  * Scans all memory headers from both dirs, asks the LLM to select the
3271
- * top 5 most relevant ones for the query, and returns the full content
3272
- * of those files. Best-effort: selector failures return an empty array.
3283
+ * top 5 most relevant ones for the query, and returns their paths (with
3284
+ * mtimes); the file contents are read later by renderReminder.
3285
+ * Best-effort: selector failures return an empty array.
3273
3286
  */
3274
3287
  async findRelevantMemories(query, client, recentTools = [], alreadySurfaced = /* @__PURE__ */ new Set()) {
3275
3288
  const allHeaders = [];
@@ -4543,27 +4556,37 @@ function parseAgentDefinition(content) {
4543
4556
  // src/subagent/task-manager.ts
4544
4557
  var task_manager_exports = {};
4545
4558
  __export(task_manager_exports, {
4559
+ TaskFailure: () => TaskFailure,
4546
4560
  TaskManager: () => TaskManager,
4547
4561
  formatAgentTaskNotification: () => formatAgentTaskNotification
4548
4562
  });
4563
+ var TaskFailure = class extends Error {
4564
+ constructor(output) {
4565
+ super("task failed");
4566
+ this.output = output;
4567
+ }
4568
+ output;
4569
+ };
4549
4570
  var TaskManager = class {
4550
4571
  tasks = /* @__PURE__ */ new Map();
4551
4572
  notifiedTaskIds = /* @__PURE__ */ new Set();
4552
4573
  listeners = /* @__PURE__ */ new Set();
4553
4574
  nextId = 1;
4575
+ pendingTaskIds = /* @__PURE__ */ new Set();
4554
4576
  create(name, runner, cancel, options = {}) {
4555
- const id = `agent-${String(this.nextId++)}`;
4577
+ const id = `${options.idPrefix ?? "agent"}-${String(this.nextId++)}`;
4556
4578
  const task = {
4557
4579
  id,
4558
4580
  name,
4559
4581
  ...options.originToolCallId ? { originToolCallId: options.originToolCallId } : {},
4582
+ ...options.kind ? { kind: options.kind } : {},
4560
4583
  status: "running",
4561
4584
  output: "",
4562
4585
  cancel,
4563
4586
  done: Promise.resolve()
4564
4587
  };
4565
4588
  this.tasks.set(id, task);
4566
- this.emitChange();
4589
+ this.pendingTaskIds.add(id);
4567
4590
  task.done = Promise.resolve().then(() => runner(task)).then((output) => {
4568
4591
  if (task.status === "running") {
4569
4592
  task.status = "completed";
@@ -4573,10 +4596,17 @@ var TaskManager = class {
4573
4596
  }).catch((error) => {
4574
4597
  if (task.status === "running") {
4575
4598
  task.status = "failed";
4576
- task.output = `Error: ${asErrorString(error)}`;
4599
+ task.output = error instanceof TaskFailure ? error.output : `Error: ${asErrorString(error)}`;
4600
+ this.emitChange();
4601
+ } else if (task.status === "cancelled" && error instanceof TaskFailure) {
4602
+ task.output = error.output;
4577
4603
  this.emitChange();
4578
4604
  }
4605
+ }).finally(() => {
4606
+ this.pendingTaskIds.delete(id);
4607
+ this.emitChange();
4579
4608
  });
4609
+ this.emitChange();
4580
4610
  return task;
4581
4611
  }
4582
4612
  get(id) {
@@ -4614,25 +4644,32 @@ var TaskManager = class {
4614
4644
  }
4615
4645
  async stopAndWait(id) {
4616
4646
  const task = this.tasks.get(id);
4617
- if (!task || !this.stop(id)) {
4647
+ if (!task) {
4618
4648
  return false;
4619
4649
  }
4650
+ const stopped = this.stop(id);
4620
4651
  await task.done;
4621
- return true;
4652
+ return stopped;
4622
4653
  }
4623
4654
  async stopAll() {
4624
- const running = this.list().filter((task) => task.status === "running");
4655
+ const running = this.list().filter((task) => this.pendingTaskIds.has(task.id));
4625
4656
  for (const task of running) {
4626
4657
  this.stop(task.id);
4627
4658
  }
4628
4659
  await Promise.allSettled(running.map((task) => task.done));
4629
4660
  }
4630
- async waitAll() {
4631
- await Promise.allSettled(this.list().map((task) => task.done));
4661
+ /**
4662
+ * Wait for tasks to settle. An optional filter selects which tasks to wait
4663
+ * for (e.g. print-mode waits only for agent-kind tasks: shell/js tasks may
4664
+ * run indefinitely and are stopped, not awaited, at exit).
4665
+ */
4666
+ async waitAll(filter) {
4667
+ const tasks = filter ? this.list().filter(filter) : this.list();
4668
+ await Promise.allSettled(tasks.map((task) => task.done));
4632
4669
  }
4633
4670
  drainNotifications() {
4634
4671
  const completed = this.list().filter(
4635
- (task) => task.status !== "running" && !this.notifiedTaskIds.has(task.id)
4672
+ (task) => task.status !== "running" && !this.pendingTaskIds.has(task.id) && !this.notifiedTaskIds.has(task.id)
4636
4673
  );
4637
4674
  for (const task of completed) {
4638
4675
  this.notifiedTaskIds.add(task.id);
@@ -6129,7 +6166,7 @@ ${prompt}`;
6129
6166
  permissionChecker: ctx.permissionChecker?.forWorkDir(worktree.path)
6130
6167
  };
6131
6168
  }
6132
- const { cloneRegistryForFork } = await import("./tool-filter-QU4LOZII.js");
6169
+ const { cloneRegistryForFork } = await import("./tool-filter-S3ZZOXFD.js");
6133
6170
  const forkedRegistry = cloneRegistryForFork(this.registry);
6134
6171
  const snapshot = conversationSnapshot ?? this.conversation.fork();
6135
6172
  const output = await this.forkHandler(
@@ -6218,6 +6255,7 @@ async function spawnSubagent(definition, prompt, parentClient, parentRegistry, p
6218
6255
  const conversation = options.conversation ?? new ConversationManager();
6219
6256
  conversation.addSystemReminder(buildSubagentInstructions(definition));
6220
6257
  conversation.addUserMessage(prompt);
6258
+ const taskManager = options.backgroundTasks === false ? null : new TaskManager();
6221
6259
  const agent2 = new Agent({
6222
6260
  client,
6223
6261
  registry,
@@ -6230,53 +6268,59 @@ async function spawnSubagent(definition, prompt, parentClient, parentRegistry, p
6230
6268
  fileStateCache: new FileStateCache(),
6231
6269
  instructions: loadInstructions(workDir),
6232
6270
  contextWindow: getContextWindow(provider),
6233
- maxOutput: getMaxOutputTokens(provider)
6271
+ maxOutput: getMaxOutputTokens(provider),
6272
+ taskManager,
6273
+ notificationFn: taskManager ? () => taskManager.drainNotifications().map(formatAgentTaskNotification) : void 0
6234
6274
  });
6235
6275
  let output = "";
6236
6276
  let turn = 0;
6237
- for await (const event of agent2.run()) {
6238
- switch (event.type) {
6239
- case "stream_text":
6240
- output += event.text;
6241
- break;
6242
- case "tool_use":
6243
- onProgress?.({ lastTool: event.toolName });
6244
- onEvent?.({
6245
- type: "tool_use",
6246
- toolId: event.toolId,
6247
- toolName: event.toolName,
6248
- args: event.args
6249
- });
6250
- break;
6251
- case "tool_result":
6252
- onEvent?.({
6253
- type: "tool_result",
6254
- toolId: event.toolId
6255
- });
6256
- break;
6257
- case "usage":
6258
- onEvent?.({
6259
- type: "usage",
6260
- usage: {
6261
- inputTokens: event.usage.inputTokens,
6262
- outputTokens: event.usage.outputTokens
6277
+ try {
6278
+ for await (const event of agent2.run()) {
6279
+ switch (event.type) {
6280
+ case "stream_text":
6281
+ output += event.text;
6282
+ break;
6283
+ case "tool_use":
6284
+ onProgress?.({ lastTool: event.toolName });
6285
+ onEvent?.({
6286
+ type: "tool_use",
6287
+ toolId: event.toolId,
6288
+ toolName: event.toolName,
6289
+ args: event.args
6290
+ });
6291
+ break;
6292
+ case "tool_result":
6293
+ onEvent?.({
6294
+ type: "tool_result",
6295
+ toolId: event.toolId
6296
+ });
6297
+ break;
6298
+ case "usage":
6299
+ onEvent?.({
6300
+ type: "usage",
6301
+ usage: {
6302
+ inputTokens: event.usage.inputTokens,
6303
+ outputTokens: event.usage.outputTokens
6304
+ }
6305
+ });
6306
+ break;
6307
+ case "turn_complete":
6308
+ onProgress?.({ turn: ++turn });
6309
+ onEvent?.({ type: "turn_complete" });
6310
+ break;
6311
+ case "loop_complete":
6312
+ if (event.stopReason === "interrupted") {
6313
+ return `${output}${output ? "\n\n" : ""}${SUBAGENT_INTERRUPTED_MARKER}`;
6263
6314
  }
6264
- });
6265
- break;
6266
- case "turn_complete":
6267
- onProgress?.({ turn: ++turn });
6268
- onEvent?.({ type: "turn_complete" });
6269
- break;
6270
- case "loop_complete":
6271
- if (event.stopReason === "interrupted") {
6272
- return `${output}${output ? "\n\n" : ""}${SUBAGENT_INTERRUPTED_MARKER}`;
6273
- }
6274
- return output || "[No output]";
6275
- case "error":
6276
- throw event.error;
6315
+ return output || "[No output]";
6316
+ case "error":
6317
+ throw event.error;
6318
+ }
6277
6319
  }
6320
+ return output || "[No output]";
6321
+ } finally {
6322
+ await taskManager?.stopAll();
6278
6323
  }
6279
- return output || "[No output]";
6280
6324
  }
6281
6325
 
6282
6326
  // src/teams/coordinator.ts
@@ -7063,7 +7107,7 @@ var TaskStopTool = class {
7063
7107
  teamManager;
7064
7108
  taskManager;
7065
7109
  name = "TaskStop";
7066
- description = "Stop a running teammate or background Agent task. Pass exactly one of teammate or task_id.";
7110
+ description = "Stop a running teammate or background task (Agent, Bash, PowerShell or JavaScript). Pass exactly one of teammate or task_id.";
7067
7111
  category = "command";
7068
7112
  schema() {
7069
7113
  return {
@@ -7078,20 +7122,21 @@ var TaskStopTool = class {
7078
7122
  },
7079
7123
  task_id: {
7080
7124
  type: "string",
7081
- description: "ID of a background Agent task"
7125
+ description: "ID of a background task (Agent, Bash, PowerShell or JavaScript)"
7082
7126
  }
7083
7127
  }
7084
7128
  }
7085
7129
  };
7086
7130
  }
7087
- async execute(_ctx, args) {
7131
+ async execute(ctx, args) {
7088
7132
  const name = strArg(args, "teammate", "");
7089
7133
  const taskId = strArg(args, "task_id", "");
7090
7134
  if (!name && !taskId || name && taskId) {
7091
7135
  return { output: "Error: pass exactly one of teammate or task_id", isError: true };
7092
7136
  }
7093
7137
  if (taskId) {
7094
- const task = this.taskManager?.get(taskId);
7138
+ const manager = ctx.taskManager?.get(taskId) ? ctx.taskManager : this.taskManager;
7139
+ const task = manager?.get(taskId);
7095
7140
  if (!task) {
7096
7141
  return { output: `Error: background task '${taskId}' not found`, isError: true };
7097
7142
  }
@@ -7101,7 +7146,7 @@ var TaskStopTool = class {
7101
7146
  isError: false
7102
7147
  };
7103
7148
  }
7104
- await this.taskManager?.stopAndWait(taskId);
7149
+ await manager?.stopAndWait(taskId);
7105
7150
  return { output: `Background task '${taskId}' stopped.`, isError: false };
7106
7151
  }
7107
7152
  for (const team of this.teamManager.list()) {
@@ -7425,16 +7470,7 @@ ${prompt}` };
7425
7470
  shutdown: shutdownRequest("lead", "member deactivated")
7426
7471
  };
7427
7472
  }
7428
- /**
7429
- * Sends the teammate's completed plan to the Lead, blocks until approval is received,
7430
- * and returns the prompt to feed the model on the next turn.
7431
- *
7432
- * The teammate holds read-only permissions at this point, so no matter how long the
7433
- * wait, no damage can occur — hence no timeout is set here. Rather than timing out and
7434
- * autonomously modifying files, it is better to wait indefinitely and let the user
7435
- * drive progress from the Lead side. Returns null when the teammate has been
7436
- * deactivated; the caller should exit the main loop.
7437
- */
7473
+ /** Reads the teammate's plan file for review; returns a fallback note when empty or unreadable. */
7438
7474
  readPlanForReview() {
7439
7475
  try {
7440
7476
  const text = readFileSync14(getOrCreatePlanPath(this.workDir), "utf-8");
@@ -7445,6 +7481,16 @@ ${prompt}` };
7445
7481
  }
7446
7482
  return "(Plan file is empty \u2014 the teammate may not have written the plan as expected)";
7447
7483
  }
7484
+ /**
7485
+ * Sends the teammate's completed plan to the Lead, blocks until approval is received,
7486
+ * and returns the prompt to feed the model on the next turn.
7487
+ *
7488
+ * The teammate holds read-only permissions at this point, so no matter how long the
7489
+ * wait, no damage can occur — hence no timeout is set here. Rather than timing out and
7490
+ * autonomously modifying files, it is better to wait indefinitely and let the user
7491
+ * drive progress from the Lead side. Returns null when the teammate has been
7492
+ * deactivated; the caller should exit the main loop.
7493
+ */
7448
7494
  async runPlanApproval(member, plan) {
7449
7495
  const req = planApprovalRequest(member.name, plan);
7450
7496
  await this.leadMailbox.send(member.name, req.text, req);
@@ -8139,8 +8185,50 @@ __export(bash_exports, {
8139
8185
  BashTool: () => BashTool
8140
8186
  });
8141
8187
  import { spawn as spawn2 } from "child_process";
8188
+ import { statSync as statSync10 } from "fs";
8189
+
8190
+ // src/tools/shell-background.ts
8191
+ var shell_background_exports = {};
8192
+ __export(shell_background_exports, {
8193
+ BACKGROUNDABLE_TOOL_NAMES: () => BACKGROUNDABLE_TOOL_NAMES,
8194
+ BACKGROUND_MAX_OUTPUT_BYTES: () => BACKGROUND_MAX_OUTPUT_BYTES,
8195
+ BACKGROUND_NOTIFICATION_CHARS: () => BACKGROUND_NOTIFICATION_CHARS,
8196
+ SIZE_WATCHDOG_INTERVAL_MS: () => SIZE_WATCHDOG_INTERVAL_MS,
8197
+ attachBackgroundTaskManager: () => attachBackgroundTaskManager,
8198
+ backgroundAllForegroundTasks: () => backgroundAllForegroundTasks,
8199
+ backgroundMessage: () => backgroundMessage,
8200
+ backgroundTaskName: () => backgroundTaskName,
8201
+ buildBackgroundBody: () => buildBackgroundBody,
8202
+ createShellOutputFile: () => createShellOutputFile,
8203
+ discardFd: () => discardFd,
8204
+ formatFinalResult: () => formatFinalResult,
8205
+ hasAnyForegroundTasks: () => hasAnyForegroundTasks,
8206
+ isAutobackgroundingAllowed: () => isAutobackgroundingAllowed,
8207
+ openOutputFd: () => openOutputFd,
8208
+ readOutputFile: () => readOutputFile,
8209
+ sliceUtf8Safe: () => sliceUtf8Safe,
8210
+ unlinkQuiet: () => unlinkQuiet
8211
+ });
8212
+ import { randomBytes as randomBytes3 } from "crypto";
8213
+ import {
8214
+ closeSync as closeSync2,
8215
+ constants as fsConstants,
8216
+ fstatSync,
8217
+ mkdirSync as mkdirSync12,
8218
+ openSync as openSync2,
8219
+ readSync,
8220
+ statSync as statSync9,
8221
+ unlinkSync as unlinkSync5
8222
+ } from "fs";
8223
+ import { tmpdir } from "os";
8224
+ import { join as join19 } from "path";
8142
8225
 
8143
8226
  // src/tools/exit-code-hints.ts
8227
+ var exit_code_hints_exports = {};
8228
+ __export(exit_code_hints_exports, {
8229
+ exitCodeHint: () => exitCodeHint,
8230
+ extractBaseCmd: () => extractBaseCmd
8231
+ });
8144
8232
  function extractBaseCmd(command) {
8145
8233
  const lastSegment = command.split("|").pop()?.trim() ?? command;
8146
8234
  const tokens = lastSegment.split(/\s+/);
@@ -8372,9 +8460,201 @@ function exitCodeHint(command, exitCode) {
8372
8460
  return "";
8373
8461
  }
8374
8462
 
8463
+ // src/tools/shell-background.ts
8464
+ var BACKGROUND_NOTIFICATION_CHARS = 3e4;
8465
+ var BACKGROUND_MAX_OUTPUT_BYTES = 5 * 1024 * 1024 * 1024;
8466
+ var SIZE_WATCHDOG_INTERVAL_MS = 500;
8467
+ function isAutobackgroundingAllowed(command, disallowed) {
8468
+ const first = /^\S+/.exec(command.trimStart())?.[0] ?? "";
8469
+ const base = first.replace(/^.*\//, "").replace(/^["']|["']$/g, "").toLowerCase();
8470
+ return !disallowed.has(base);
8471
+ }
8472
+ function backgroundTaskName(command) {
8473
+ const flat = command.replace(/\s+/g, " ").trim();
8474
+ return flat.length > 80 ? `${flat.slice(0, 77)}...` : flat;
8475
+ }
8476
+ function backgroundMessage(reason, taskId, timeout) {
8477
+ switch (reason) {
8478
+ case "explicit":
8479
+ return `Command running in background (task_id: ${taskId}). You will be notified when it completes; do not poll. Use TaskStop with task_id to kill it early.`;
8480
+ case "timeout":
8481
+ return `Command exceeded its ${String(timeout)}s timeout and was moved to the background (task_id: ${taskId}). It is still running \u2014 you will be notified when it completes.`;
8482
+ case "user":
8483
+ return `Command was manually backgrounded by the user (task_id: ${taskId}). It is still running \u2014 you will be notified when it completes.`;
8484
+ }
8485
+ }
8486
+ function sliceUtf8Safe(buf, maxBytes) {
8487
+ if (buf.length <= maxBytes) {
8488
+ return buf;
8489
+ }
8490
+ let end = maxBytes;
8491
+ while (end > 0 && (buf[end] & 192) === 128) {
8492
+ end--;
8493
+ }
8494
+ return buf.subarray(0, end);
8495
+ }
8496
+ function readOutputFile(path, maxBytes) {
8497
+ let fd;
8498
+ try {
8499
+ fd = openSync2(path, "r");
8500
+ } catch {
8501
+ return { text: "", size: 0, truncated: false };
8502
+ }
8503
+ try {
8504
+ const size = fstatSync(fd).size;
8505
+ const readLen = Math.min(size, maxBytes + 1);
8506
+ const buf = Buffer.alloc(readLen);
8507
+ let bytesRead = 0;
8508
+ while (bytesRead < readLen) {
8509
+ const count = readSync(fd, buf, bytesRead, readLen - bytesRead, bytesRead);
8510
+ if (count === 0) {
8511
+ break;
8512
+ }
8513
+ bytesRead += count;
8514
+ }
8515
+ return {
8516
+ text: sliceUtf8Safe(buf.subarray(0, bytesRead), maxBytes).toString("utf-8"),
8517
+ size,
8518
+ truncated: size > maxBytes
8519
+ };
8520
+ } finally {
8521
+ closeSync2(fd);
8522
+ }
8523
+ }
8524
+ function unlinkQuiet(path) {
8525
+ try {
8526
+ unlinkSync5(path);
8527
+ } catch {
8528
+ }
8529
+ }
8530
+ function discardFd(fd) {
8531
+ try {
8532
+ closeSync2(fd);
8533
+ } catch {
8534
+ }
8535
+ }
8536
+ function openOutputFd(path) {
8537
+ const flags = process.platform === "win32" ? "w" : fsConstants.O_WRONLY | fsConstants.O_CREAT | fsConstants.O_APPEND | fsConstants.O_NOFOLLOW;
8538
+ return openSync2(path, flags, 384);
8539
+ }
8540
+ function createShellOutputFile(workDir, sessionId) {
8541
+ const fileName = `shell-${randomBytes3(8).toString("hex")}.output`;
8542
+ const candidates = [];
8543
+ try {
8544
+ const dir = spillDir(workDir, sessionId);
8545
+ mkdirSync12(dir, { recursive: true });
8546
+ candidates.push(join19(dir, fileName));
8547
+ } catch {
8548
+ }
8549
+ candidates.push(join19(tmpdir(), fileName));
8550
+ let lastError = new Error("no candidate output directory");
8551
+ for (const path of candidates) {
8552
+ try {
8553
+ return { path, fd: openOutputFd(path) };
8554
+ } catch (error) {
8555
+ lastError = error;
8556
+ }
8557
+ }
8558
+ throw lastError instanceof Error ? lastError : new Error(String(lastError));
8559
+ }
8560
+ function formatFinalResult(prompt, command, exit, merged, truncated, timeout) {
8561
+ if (exit.spawnError) {
8562
+ return {
8563
+ output: `Error executing command: ${exit.spawnError}`,
8564
+ isError: true
8565
+ };
8566
+ }
8567
+ if (exit.aborted || exit.timedOut) {
8568
+ const captured = merged || truncated ? formatShellOutput(prompt, command, merged, "", truncated) : "";
8569
+ const error = exit.aborted ? "Error: command interrupted" : `Error: command timed out after ${String(timeout)}s`;
8570
+ return {
8571
+ output: captured ? `${captured}
8572
+ ${error}` : error,
8573
+ isError: true
8574
+ };
8575
+ }
8576
+ const exitCode = exit.code ?? 0;
8577
+ let output = formatShellOutput(prompt, command, merged, "", truncated);
8578
+ if (!truncated) {
8579
+ if (exitCode !== 0) {
8580
+ const hint = exitCodeHint(command, exitCode);
8581
+ output += hint ? `
8582
+ Exit code ${String(exitCode)} (${hint})` : `
8583
+ Exit code ${String(exitCode)}`;
8584
+ }
8585
+ if (exit.code === null) {
8586
+ output += `
8587
+ Process terminated${exit.signal ? ` by ${exit.signal}` : " unexpectedly"}`;
8588
+ }
8589
+ }
8590
+ return { output, isError: truncated || exitCode !== 0 || exit.code === null };
8591
+ }
8592
+ function buildBackgroundBody(prompt, command, exit, outputPath, timeout, annotate) {
8593
+ let size = 0;
8594
+ try {
8595
+ size = statSync9(outputPath).size;
8596
+ } catch {
8597
+ }
8598
+ let result;
8599
+ if (size <= BACKGROUND_NOTIFICATION_CHARS) {
8600
+ const read = readOutputFile(outputPath, MAX_SHELL_OUTPUT_BYTES);
8601
+ const merged = annotate ? annotate(read.text) : read.text;
8602
+ result = formatFinalResult(prompt, command, exit, merged, read.truncated, timeout);
8603
+ unlinkQuiet(outputPath);
8604
+ } else {
8605
+ const header = formatFinalResult(prompt, command, exit, "", false, timeout);
8606
+ const preview2 = readOutputFile(outputPath, TOOL_RESULT_PREVIEW_CHARS).text;
8607
+ result = {
8608
+ output: `${header.output}
8609
+ ${buildPersistedOutputPreview(size, annotate ? annotate(preview2) : preview2, outputPath)}`,
8610
+ isError: header.isError
8611
+ };
8612
+ }
8613
+ if (exit.sizeKilled) {
8614
+ result.output += "\nBackground command killed: output file exceeded 5GB";
8615
+ result.isError = true;
8616
+ }
8617
+ return result;
8618
+ }
8619
+ var BACKGROUNDABLE_TOOL_NAMES = ["Bash", "PowerShell", "JavaScript"];
8620
+ function asBackgroundable(tool) {
8621
+ if (typeof tool === "object" && tool !== null && "taskManager" in tool && "backgroundForegroundTasks" in tool) {
8622
+ return tool;
8623
+ }
8624
+ return null;
8625
+ }
8626
+ function attachBackgroundTaskManager(registry, manager) {
8627
+ for (const name of BACKGROUNDABLE_TOOL_NAMES) {
8628
+ const tool = asBackgroundable(registry.get(name));
8629
+ if (tool) {
8630
+ tool.taskManager = manager;
8631
+ }
8632
+ }
8633
+ }
8634
+ function hasAnyForegroundTasks(registry) {
8635
+ for (const name of BACKGROUNDABLE_TOOL_NAMES) {
8636
+ const tool = asBackgroundable(registry.get(name));
8637
+ if (tool?.hasForegroundTasks()) {
8638
+ return true;
8639
+ }
8640
+ }
8641
+ return false;
8642
+ }
8643
+ function backgroundAllForegroundTasks(registry) {
8644
+ let count = 0;
8645
+ for (const name of BACKGROUNDABLE_TOOL_NAMES) {
8646
+ const tool = asBackgroundable(registry.get(name));
8647
+ if (tool) {
8648
+ count += tool.backgroundForegroundTasks();
8649
+ }
8650
+ }
8651
+ return count;
8652
+ }
8653
+
8375
8654
  // src/tools/bash.ts
8376
8655
  var MAX_TIMEOUT = 600;
8377
8656
  var KILL_GRACE_MS = 3e3;
8657
+ var DISALLOWED_AUTO_BACKGROUND_COMMANDS = /* @__PURE__ */ new Set(["sleep"]);
8378
8658
  var BashTool = class {
8379
8659
  // Use a hardcoded string instead of BashTool.name.replace("Tool", "")
8380
8660
  // because class names are not stable after minification — bundlers like
@@ -8390,6 +8670,18 @@ var BashTool = class {
8390
8670
  denyWrite: [],
8391
8671
  networkEnabled: true
8392
8672
  };
8673
+ /**
8674
+ * Background task registry, injected by the host (UI / print mode / remote
8675
+ * server) — the same instance the Agent tool uses, so completion
8676
+ * notifications share one drain and TaskStop covers both. When null, Bash is
8677
+ * foreground-only: run_in_background disappears from the schema, timeouts
8678
+ * kill, and Ctrl+B is a no-op. Calls running inside a subagent loop carry
8679
+ * that loop's own manager in ctx.taskManager, which takes precedence.
8680
+ */
8681
+ taskManager = null;
8682
+ /** Running foreground executions eligible for manual backgrounding (Ctrl+B). */
8683
+ foreground = /* @__PURE__ */ new Map();
8684
+ nextForegroundId = 1;
8393
8685
  /**
8394
8686
  * Read-only commands can run concurrently with other read-only tools;
8395
8687
  * mutating commands must run exclusively.
@@ -8404,26 +8696,62 @@ var BashTool = class {
8404
8696
  const command = args.command;
8405
8697
  return typeof command === "string" && isSafeCommand(command);
8406
8698
  }
8699
+ /**
8700
+ * The background subsystem needs a task manager and can be disabled
8701
+ * wholesale with SWIFTY_DISABLE_BACKGROUND_TASKS=1 (schema parameter
8702
+ * removed, timeouts kill, Ctrl+B becomes a no-op). Instance-level gate used
8703
+ * by the schema; execute() re-checks with the ctx-resolved manager.
8704
+ */
8705
+ backgroundEnabled() {
8706
+ return this.taskManager !== null && process.env.SWIFTY_DISABLE_BACKGROUND_TASKS !== "1";
8707
+ }
8708
+ /** True while at least one foreground Bash command runs (gates the Ctrl+B handler). */
8709
+ hasForegroundTasks() {
8710
+ return this.foreground.size > 0;
8711
+ }
8712
+ /**
8713
+ * Move every running foreground Bash command to the background (Ctrl+B).
8714
+ * Returns how many commands were actually backgrounded.
8715
+ */
8716
+ backgroundForegroundTasks() {
8717
+ let count = 0;
8718
+ for (const entry of [...this.foreground.values()]) {
8719
+ if (entry.background()) {
8720
+ count++;
8721
+ }
8722
+ }
8723
+ return count;
8724
+ }
8407
8725
  schema() {
8408
- const inputSchema = {
8409
- type: "object",
8410
- properties: {
8411
- command: {
8412
- type: "string",
8413
- description: "Shell command to execute"
8414
- },
8415
- timeout: {
8416
- type: "integer",
8417
- description: "Timeout in seconds (max 600)",
8418
- default: 120
8419
- }
8726
+ const properties = {
8727
+ command: {
8728
+ type: "string",
8729
+ description: "Shell command to execute"
8420
8730
  },
8421
- required: ["command"]
8731
+ timeout: {
8732
+ type: "integer",
8733
+ description: "Timeout in seconds (max 600)",
8734
+ default: 120
8735
+ }
8422
8736
  };
8737
+ let description = this.description;
8738
+ if (this.backgroundEnabled()) {
8739
+ properties.run_in_background = {
8740
+ type: "boolean",
8741
+ description: "Run the command in the background. Returns a task ID immediately; the result arrives later as a task notification.",
8742
+ default: false
8743
+ };
8744
+ description = `${this.description}
8745
+ ${BASH_BACKGROUND_DESCRIPTION}`;
8746
+ }
8423
8747
  return {
8424
8748
  name: this.name,
8425
- description: this.description,
8426
- input_schema: inputSchema
8749
+ description,
8750
+ input_schema: {
8751
+ type: "object",
8752
+ properties,
8753
+ required: ["command"]
8754
+ }
8427
8755
  };
8428
8756
  }
8429
8757
  async execute(ctx, args) {
@@ -8444,12 +8772,29 @@ var BashTool = class {
8444
8772
  if (timeout > MAX_TIMEOUT) {
8445
8773
  timeout = MAX_TIMEOUT;
8446
8774
  }
8775
+ const manager = ctx.taskManager !== void 0 ? ctx.taskManager : this.taskManager;
8776
+ const backgroundAvailable = manager !== null && process.env.SWIFTY_DISABLE_BACKGROUND_TASKS !== "1";
8777
+ const runInBackground = boolArg(args, "run_in_background") && backgroundAvailable;
8778
+ let outputFile;
8779
+ try {
8780
+ outputFile = createShellOutputFile(ctx.workDir, ctx.sessionId ?? "");
8781
+ } catch (error) {
8782
+ return {
8783
+ output: `Error creating output file: ${asErrorString(error)}`,
8784
+ isError: true
8785
+ };
8786
+ }
8787
+ const discardOutputFile = () => {
8788
+ discardFd(outputFile.fd);
8789
+ unlinkQuiet(outputFile.path);
8790
+ };
8447
8791
  let prepared = {
8448
8792
  executable: "bash",
8449
8793
  args: ["-c", command]
8450
8794
  };
8451
8795
  if (this.sandboxRequired || this.sandbox) {
8452
8796
  if (!this.sandbox) {
8797
+ discardOutputFile();
8453
8798
  return {
8454
8799
  output: "Error: sandbox is enabled but unavailable; command was not executed",
8455
8800
  isError: true
@@ -8457,17 +8802,28 @@ var BashTool = class {
8457
8802
  }
8458
8803
  try {
8459
8804
  if (!await this.sandbox.available()) {
8805
+ discardOutputFile();
8460
8806
  return {
8461
8807
  output: `Error: ${this.sandbox.implementation} sandbox is unavailable; command was not executed`,
8462
8808
  isError: true
8463
8809
  };
8464
8810
  }
8465
- prepared = await this.sandbox.prepare(command, this.sandboxConfig, {
8466
- cwd: ctx.workDir,
8467
- abortSignal: ctx.abortSignal,
8468
- commandId: ctx.toolCallId
8469
- });
8811
+ prepared = await this.sandbox.prepare(
8812
+ command,
8813
+ {
8814
+ ...this.sandboxConfig,
8815
+ // The child writes its output file directly; grant write access to
8816
+ // that path even under a strict allowWrite config.
8817
+ allowWrite: [...this.sandboxConfig.allowWrite, outputFile.path]
8818
+ },
8819
+ {
8820
+ cwd: ctx.workDir,
8821
+ abortSignal: ctx.abortSignal,
8822
+ commandId: ctx.toolCallId
8823
+ }
8824
+ );
8470
8825
  } catch (error) {
8826
+ discardOutputFile();
8471
8827
  return {
8472
8828
  output: `Error preparing sandbox: ${asErrorString(error)}`,
8473
8829
  isError: true
@@ -8475,6 +8831,7 @@ var BashTool = class {
8475
8831
  }
8476
8832
  }
8477
8833
  if (ctx.abortSignal?.aborted) {
8834
+ discardOutputFile();
8478
8835
  try {
8479
8836
  await prepared.cleanup?.();
8480
8837
  } catch {
@@ -8484,22 +8841,58 @@ var BashTool = class {
8484
8841
  isError: true
8485
8842
  };
8486
8843
  }
8487
- return new Promise((resolve12) => {
8488
- let timedOut = false;
8844
+ const handle = this.startCommand(ctx, prepared, command, timeout, outputFile, manager);
8845
+ if (runInBackground) {
8846
+ const taskId = handle.background("explicit");
8847
+ if (taskId !== null) {
8848
+ return {
8849
+ output: backgroundMessage("explicit", taskId, timeout),
8850
+ isError: false
8851
+ };
8852
+ }
8853
+ }
8854
+ return handle.result;
8855
+ }
8856
+ /**
8857
+ * Spawn the command with stdout+stderr writing directly into the output
8858
+ * file (ccb's file-descriptor mode): output never flows through JS, so
8859
+ * backgrounding is a bookkeeping switch — no re-spawn, no buffer handover.
8860
+ * The returned handle exposes the tool-call promise plus a background()
8861
+ * trigger that transitions the running command into a TaskManager task.
8862
+ */
8863
+ startCommand(ctx, prepared, command, timeout, outputFile, manager) {
8864
+ let backgroundFn;
8865
+ const result = new Promise((resolve12) => {
8489
8866
  let aborted = false;
8490
8867
  let terminating = false;
8491
8868
  let settled = false;
8869
+ let backgrounded = false;
8870
+ let sizeKilled = false;
8492
8871
  let escalateTimer = null;
8493
- const child = spawn2(prepared.executable, prepared.args, {
8494
- cwd: ctx.workDir,
8495
- detached: true,
8496
- env: prepared.env,
8497
- stdio: ["ignore", "pipe", "pipe"]
8872
+ let child;
8873
+ try {
8874
+ child = spawn2(prepared.executable, prepared.args, {
8875
+ cwd: ctx.workDir,
8876
+ detached: true,
8877
+ env: prepared.env,
8878
+ // stdout and stderr share one O_APPEND fd: each write lands on disk
8879
+ // atomically and the streams interleave chronologically.
8880
+ stdio: ["ignore", outputFile.fd, outputFile.fd]
8881
+ });
8882
+ } catch (error) {
8883
+ discardFd(outputFile.fd);
8884
+ unlinkQuiet(outputFile.path);
8885
+ resolve12({
8886
+ output: `Error executing command: ${asErrorString(error)}`,
8887
+ isError: true
8888
+ });
8889
+ return;
8890
+ }
8891
+ discardFd(outputFile.fd);
8892
+ let doneResolve;
8893
+ const done = new Promise((resolveDone) => {
8894
+ doneResolve = resolveDone;
8498
8895
  });
8499
- let stdout = "";
8500
- let stderr = "";
8501
- let total = 0;
8502
- let outputTruncated = false;
8503
8896
  const killTree = (signal) => {
8504
8897
  if (typeof child.pid !== "number") {
8505
8898
  return;
@@ -8521,41 +8914,35 @@ var BashTool = class {
8521
8914
  killTree("SIGTERM");
8522
8915
  escalateTimer = setTimeout(() => {
8523
8916
  killTree("SIGKILL");
8524
- child.stdout.destroy();
8525
- child.stderr.destroy();
8526
8917
  }, KILL_GRACE_MS);
8527
8918
  escalateTimer.unref();
8528
8919
  };
8529
- const appendChunk = (chunk, target) => {
8530
- if (outputTruncated) {
8920
+ const watchdog = setInterval(() => {
8921
+ let size = 0;
8922
+ try {
8923
+ size = statSync10(outputFile.path).size;
8924
+ } catch {
8531
8925
  return;
8532
8926
  }
8533
- const remaining = MAX_SHELL_OUTPUT_BYTES - total;
8534
- const piece = takeUtf8Prefix(chunk, remaining);
8535
- total += utf8ByteLength(piece);
8536
- if (piece.length < chunk.length) {
8537
- outputTruncated = true;
8927
+ const cap = backgrounded ? BACKGROUND_MAX_OUTPUT_BYTES : MAX_SHELL_OUTPUT_BYTES;
8928
+ if (size > cap) {
8929
+ sizeKilled = backgrounded;
8930
+ clearInterval(watchdog);
8538
8931
  terminate();
8539
8932
  }
8540
- if (target === "stdout") {
8541
- stdout += piece;
8542
- } else {
8543
- stderr += piece;
8544
- }
8545
- };
8546
- child.stdout.setEncoding("utf-8");
8547
- child.stdout.on("data", (chunk) => {
8548
- appendChunk(chunk, "stdout");
8549
- });
8550
- child.stderr.setEncoding("utf-8");
8551
- child.stderr.on("data", (chunk) => {
8552
- appendChunk(chunk, "stderr");
8553
- });
8933
+ }, SIZE_WATCHDOG_INTERVAL_MS);
8934
+ watchdog.unref();
8554
8935
  const onAbort = () => {
8555
8936
  aborted = true;
8556
8937
  terminate();
8557
8938
  };
8939
+ const backgroundAvailableHere = manager !== null && process.env.SWIFTY_DISABLE_BACKGROUND_TASKS !== "1";
8940
+ const autoBackgroundAllowed = backgroundAvailableHere && isAutobackgroundingAllowed(command, DISALLOWED_AUTO_BACKGROUND_COMMANDS);
8941
+ let timedOut = false;
8558
8942
  const timeoutTimer = setTimeout(() => {
8943
+ if (autoBackgroundAllowed && backgroundExecution("timeout") !== null) {
8944
+ return;
8945
+ }
8559
8946
  timedOut = true;
8560
8947
  terminate();
8561
8948
  }, timeout * 1e3);
@@ -8564,14 +8951,17 @@ var BashTool = class {
8564
8951
  if (ctx.abortSignal?.aborted) {
8565
8952
  onAbort();
8566
8953
  }
8954
+ const foregroundKey = `bash-${String(this.nextForegroundId++)}`;
8567
8955
  const cleanup = () => {
8568
8956
  clearTimeout(timeoutTimer);
8957
+ clearInterval(watchdog);
8569
8958
  if (escalateTimer) {
8570
8959
  clearTimeout(escalateTimer);
8571
8960
  }
8572
8961
  ctx.abortSignal?.removeEventListener("abort", onAbort);
8962
+ this.foreground.delete(foregroundKey);
8573
8963
  };
8574
- const settle = (result) => {
8964
+ const settle = (finalResult) => {
8575
8965
  if (settled) {
8576
8966
  return;
8577
8967
  }
@@ -8582,54 +8972,105 @@ var BashTool = class {
8582
8972
  };
8583
8973
  void finalize().then(
8584
8974
  () => {
8585
- resolve12(result);
8975
+ resolve12(finalResult);
8586
8976
  },
8587
8977
  (error) => {
8588
8978
  resolve12({
8589
- output: `${result.output}
8979
+ output: `${finalResult.output}
8590
8980
  Error cleaning up sandbox: ${asErrorString(error)}`,
8591
8981
  isError: true
8592
8982
  });
8593
8983
  }
8594
8984
  );
8595
8985
  };
8596
- child.on("error", (error) => {
8597
- settle({
8598
- output: `Error executing command: ${error.message}`,
8599
- isError: true
8600
- });
8601
- });
8602
- child.on("close", (code, signal) => {
8603
- stderr = prepared.annotateStderr?.(stderr) ?? stderr;
8604
- if (aborted || timedOut) {
8605
- const captured = stdout || stderr || outputTruncated ? formatShellOutput("$ ", command, stdout, stderr, outputTruncated) : "";
8606
- const error = aborted ? "Error: command interrupted" : `Error: command timed out after ${String(timeout)}s`;
8607
- settle({
8608
- output: captured ? `${captured}
8609
- ${error}` : error,
8610
- isError: true
8611
- });
8986
+ const settleExit = (exit) => {
8987
+ if (backgrounded) {
8612
8988
  return;
8613
8989
  }
8614
- const exitCode = code ?? 0;
8615
- let output = formatShellOutput("$ ", command, stdout, stderr, outputTruncated);
8616
- if (outputTruncated) {
8617
- settle({ output, isError: true });
8618
- return;
8619
- }
8620
- if (exitCode !== 0) {
8621
- const hint = exitCodeHint(command, exitCode);
8622
- output += hint ? `
8623
- Exit code ${String(exitCode)} (${hint})` : `
8624
- Exit code ${String(exitCode)}`;
8990
+ const read = readOutputFile(outputFile.path, MAX_SHELL_OUTPUT_BYTES);
8991
+ const merged = prepared.annotateStderr?.(read.text) ?? read.text;
8992
+ const finalResult = formatFinalResult("$ ", command, exit, merged, read.truncated, timeout);
8993
+ unlinkQuiet(outputFile.path);
8994
+ settle(finalResult);
8995
+ };
8996
+ const backgroundExecution = (reason) => {
8997
+ if (backgrounded || settled || terminating || !manager || !backgroundAvailableHere) {
8998
+ return null;
8625
8999
  }
8626
- if (code === null) {
8627
- output += `
8628
- Process terminated${signal ? ` by ${signal}` : " unexpectedly"}`;
9000
+ backgrounded = true;
9001
+ settled = true;
9002
+ clearTimeout(timeoutTimer);
9003
+ ctx.abortSignal?.removeEventListener("abort", onAbort);
9004
+ this.foreground.delete(foregroundKey);
9005
+ const task = manager.create(
9006
+ backgroundTaskName(command),
9007
+ async () => {
9008
+ const exit = await done;
9009
+ try {
9010
+ await prepared.cleanup?.();
9011
+ } catch {
9012
+ }
9013
+ const annotate = prepared.annotateStderr ? (text) => prepared.annotateStderr?.(text) ?? text : void 0;
9014
+ const body = buildBackgroundBody(
9015
+ "$ ",
9016
+ command,
9017
+ exit,
9018
+ outputFile.path,
9019
+ timeout,
9020
+ annotate
9021
+ );
9022
+ if (body.isError) {
9023
+ throw new TaskFailure(body.output);
9024
+ }
9025
+ return body.output;
9026
+ },
9027
+ () => {
9028
+ killTree("SIGKILL");
9029
+ },
9030
+ { originToolCallId: ctx.toolCallId, idPrefix: "bash", kind: "shell" }
9031
+ );
9032
+ resolve12({
9033
+ output: backgroundMessage(reason, task.id, timeout),
9034
+ isError: false
9035
+ });
9036
+ return task.id;
9037
+ };
9038
+ if (backgroundAvailableHere) {
9039
+ this.foreground.set(foregroundKey, {
9040
+ background: () => backgroundExecution("user") !== null
9041
+ });
9042
+ }
9043
+ const stopTimers = () => {
9044
+ clearInterval(watchdog);
9045
+ if (escalateTimer) {
9046
+ clearTimeout(escalateTimer);
8629
9047
  }
8630
- settle({ output, isError: exitCode !== 0 || code === null });
9048
+ };
9049
+ child.on("error", (error) => {
9050
+ stopTimers();
9051
+ const exit = {
9052
+ code: null,
9053
+ signal: null,
9054
+ aborted,
9055
+ timedOut,
9056
+ sizeKilled,
9057
+ spawnError: error.message
9058
+ };
9059
+ doneResolve?.(exit);
9060
+ settleExit(exit);
8631
9061
  });
9062
+ child.on("close", (code, signal) => {
9063
+ stopTimers();
9064
+ const exit = { code, signal, aborted, timedOut, sizeKilled };
9065
+ doneResolve?.(exit);
9066
+ settleExit(exit);
9067
+ });
9068
+ backgroundFn = backgroundExecution;
8632
9069
  });
9070
+ return {
9071
+ result,
9072
+ background: (reason) => backgroundFn?.(reason) ?? null
9073
+ };
8633
9074
  }
8634
9075
  };
8635
9076
 
@@ -8640,8 +9081,8 @@ __export(computer_use_exports, {
8640
9081
  });
8641
9082
  import { spawn as spawn3 } from "child_process";
8642
9083
  import { mkdtemp, readFile as readFile4, rm, writeFile as writeFile2 } from "fs/promises";
8643
- import { tmpdir } from "os";
8644
- import { join as join19 } from "path";
9084
+ import { tmpdir as tmpdir2 } from "os";
9085
+ import { join as join20 } from "path";
8645
9086
  import { setTimeout as delay } from "timers/promises";
8646
9087
  import sharp from "sharp";
8647
9088
  import { safeParse as safeParse2, z as z14 } from "zod";
@@ -9359,22 +9800,6 @@ var ComputerUseTool = class {
9359
9800
  isConcurrencySafe(_args) {
9360
9801
  return false;
9361
9802
  }
9362
- providerSchema(protocol) {
9363
- if (protocol === "anthropic") {
9364
- return {
9365
- type: "computer_20251124",
9366
- name: "computer",
9367
- display_width_px: this.displayWidthPx,
9368
- display_height_px: this.displayHeightPx,
9369
- ...this.displayNumber !== void 0 ? { display_number: this.displayNumber } : {},
9370
- ...this.enableZoom ? { enable_zoom: true } : {}
9371
- };
9372
- }
9373
- if (protocol === "openai") {
9374
- return { type: "computer" };
9375
- }
9376
- return void 0;
9377
- }
9378
9803
  schema() {
9379
9804
  return {
9380
9805
  name: this.name,
@@ -9718,9 +10143,9 @@ var ComputerUseTool = class {
9718
10143
  }
9719
10144
  }
9720
10145
  async compileMacHelper(signal) {
9721
- const directory = await mkdtemp(join19(tmpdir(), "swifty-computer-helper-"));
9722
- const sourcePath = join19(directory, "main.swift");
9723
- const executablePath = join19(directory, "computer-helper");
10146
+ const directory = await mkdtemp(join20(tmpdir2(), "swifty-computer-helper-"));
10147
+ const sourcePath = join20(directory, "main.swift");
10148
+ const executablePath = join20(directory, "computer-helper");
9724
10149
  try {
9725
10150
  await writeFile2(sourcePath, MACOS_SNIPPET, "utf8");
9726
10151
  const result = await this.run(
@@ -9937,8 +10362,8 @@ var ComputerUseTool = class {
9937
10362
  return { output, contentBlocks: [imageBlock], isError: false };
9938
10363
  }
9939
10364
  async captureScreenshot(signal) {
9940
- const directory = await mkdtemp(join19(tmpdir(), "swifty-computer-"));
9941
- const screenshotPath = join19(directory, "screenshot.png");
10365
+ const directory = await mkdtemp(join20(tmpdir2(), "swifty-computer-"));
10366
+ const screenshotPath = join20(directory, "screenshot.png");
9942
10367
  try {
9943
10368
  switch (this.platform) {
9944
10369
  case "darwin": {
@@ -10215,8 +10640,10 @@ __export(powershell_exports, {
10215
10640
  PowerShellTool: () => PowerShellTool
10216
10641
  });
10217
10642
  import { execFile as execFile2, spawn as spawn4 } from "child_process";
10643
+ import { statSync as statSync11 } from "fs";
10218
10644
  var MAX_TIMEOUT2 = 600;
10219
10645
  var KILL_GRACE_MS2 = 3e3;
10646
+ var DISALLOWED_AUTO_BACKGROUND_COMMANDS2 = /* @__PURE__ */ new Set(["start-sleep", "sleep"]);
10220
10647
  var PowerShellTool = class {
10221
10648
  // Use a hardcoded string instead of PowerShellTool.name.replace("Tool", "")
10222
10649
  // because class names are not stable after minification — bundlers like
@@ -10224,57 +10651,130 @@ var PowerShellTool = class {
10224
10651
  name = "PowerShell";
10225
10652
  description = POWERSHELL_DESCRIPTION;
10226
10653
  category = "command";
10654
+ /**
10655
+ * Background task registry, injected by the host — same contract as
10656
+ * BashTool.taskManager (schema gating, timeout auto-background, Ctrl+B).
10657
+ * Calls running inside a subagent loop carry that loop's own manager in
10658
+ * ctx.taskManager, which takes precedence.
10659
+ */
10660
+ taskManager = null;
10661
+ /** Running foreground executions eligible for manual backgrounding (Ctrl+B). */
10662
+ foreground = /* @__PURE__ */ new Map();
10663
+ nextForegroundId = 1;
10664
+ /** Instance-level background gate; see BashTool.backgroundEnabled. */
10665
+ backgroundEnabled() {
10666
+ return this.taskManager !== null && process.env.SWIFTY_DISABLE_BACKGROUND_TASKS !== "1";
10667
+ }
10668
+ /** True while at least one foreground PowerShell command runs. */
10669
+ hasForegroundTasks() {
10670
+ return this.foreground.size > 0;
10671
+ }
10672
+ /** Move every running foreground command to the background (Ctrl+B). */
10673
+ backgroundForegroundTasks() {
10674
+ let count = 0;
10675
+ for (const entry of [...this.foreground.values()]) {
10676
+ if (entry.background()) {
10677
+ count++;
10678
+ }
10679
+ }
10680
+ return count;
10681
+ }
10227
10682
  schema() {
10228
- const inputSchema = {
10229
- type: "object",
10230
- properties: {
10231
- command: {
10232
- type: "string",
10233
- description: "PowerShell command to execute"
10234
- },
10235
- timeout: {
10236
- type: "integer",
10237
- description: "Timeout in seconds (max 600)",
10238
- default: 120
10239
- }
10683
+ const properties = {
10684
+ command: {
10685
+ type: "string",
10686
+ description: "PowerShell command to execute"
10240
10687
  },
10241
- required: ["command"]
10688
+ timeout: {
10689
+ type: "integer",
10690
+ description: "Timeout in seconds (max 600)",
10691
+ default: 120
10692
+ }
10242
10693
  };
10694
+ let description = this.description;
10695
+ if (this.backgroundEnabled()) {
10696
+ properties.run_in_background = {
10697
+ type: "boolean",
10698
+ description: "Run the command in the background. Returns a task ID immediately; the result arrives later as a task notification.",
10699
+ default: false
10700
+ };
10701
+ description = `${this.description}
10702
+ ${POWERSHELL_BACKGROUND_DESCRIPTION}`;
10703
+ }
10243
10704
  return {
10244
10705
  name: this.name,
10245
- description: this.description,
10246
- input_schema: inputSchema
10706
+ description,
10707
+ input_schema: {
10708
+ type: "object",
10709
+ properties,
10710
+ required: ["command"]
10711
+ }
10247
10712
  };
10248
10713
  }
10249
- execute(ctx, args) {
10714
+ async execute(ctx, args) {
10250
10715
  const command = strArg(args, "command");
10251
10716
  if (!command) {
10252
- return Promise.resolve({
10717
+ return {
10253
10718
  output: "Error: command is required",
10254
10719
  isError: true
10255
- });
10720
+ };
10256
10721
  }
10257
10722
  let timeout = intArg(args, "timeout", 120);
10258
10723
  if (!Number.isFinite(timeout) || timeout <= 0) {
10259
- return Promise.resolve({
10724
+ return {
10260
10725
  output: "Error: timeout must be a finite number greater than 0 seconds",
10261
10726
  isError: true
10262
- });
10727
+ };
10263
10728
  }
10264
10729
  if (timeout > MAX_TIMEOUT2) {
10265
10730
  timeout = MAX_TIMEOUT2;
10266
10731
  }
10732
+ const manager = ctx.taskManager !== void 0 ? ctx.taskManager : this.taskManager;
10733
+ const backgroundAvailable = manager !== null && process.env.SWIFTY_DISABLE_BACKGROUND_TASKS !== "1";
10734
+ const runInBackground = boolArg(args, "run_in_background") && backgroundAvailable;
10267
10735
  const shell = process.platform === "win32" ? "powershell.exe" : "pwsh";
10736
+ let outputFile;
10737
+ try {
10738
+ outputFile = createShellOutputFile(ctx.workDir, ctx.sessionId ?? "");
10739
+ } catch (error) {
10740
+ return {
10741
+ output: `Error creating output file: ${asErrorString(error)}`,
10742
+ isError: true
10743
+ };
10744
+ }
10268
10745
  if (ctx.abortSignal?.aborted) {
10269
- return Promise.resolve({
10746
+ discardFd(outputFile.fd);
10747
+ unlinkQuiet(outputFile.path);
10748
+ return {
10270
10749
  output: "Error: command interrupted",
10271
10750
  isError: true
10272
- });
10751
+ };
10273
10752
  }
10274
- return new Promise((resolve12) => {
10275
- let timedOut = false;
10753
+ const handle = this.startCommand(ctx, shell, command, timeout, outputFile, manager);
10754
+ if (runInBackground) {
10755
+ const taskId = handle.background("explicit");
10756
+ if (taskId !== null) {
10757
+ return {
10758
+ output: backgroundMessage("explicit", taskId, timeout),
10759
+ isError: false
10760
+ };
10761
+ }
10762
+ }
10763
+ return handle.result;
10764
+ }
10765
+ /**
10766
+ * Spawn PowerShell with stdout+stderr writing directly into the output file
10767
+ * (fd mode, shared with BashTool): output never flows through JS, so
10768
+ * backgrounding is a bookkeeping switch — no re-spawn, no buffer handover.
10769
+ */
10770
+ startCommand(ctx, shell, command, timeout, outputFile, manager) {
10771
+ let backgroundFn;
10772
+ const result = new Promise((resolve12) => {
10276
10773
  let aborted = false;
10277
10774
  let terminating = false;
10775
+ let settled = false;
10776
+ let backgrounded = false;
10777
+ let sizeKilled = false;
10278
10778
  let escalateTimer = null;
10279
10779
  const shellCommand = process.platform === "win32" ? "try { [Console]::OutputEncoding=[System.Text.Encoding]::UTF8 } catch {}\n" + command : command;
10280
10780
  const shellArgs = process.platform === "win32" ? [
@@ -10285,17 +10785,31 @@ var PowerShellTool = class {
10285
10785
  "-Command",
10286
10786
  shellCommand
10287
10787
  ] : ["-NoProfile", "-NonInteractive", "-Command", shellCommand];
10288
- const child = spawn4(shell, shellArgs, {
10289
- cwd: ctx.workDir,
10290
- // Only POSIX needs its own process group for kill(-pid); the Windows
10291
- // tree kill goes through taskkill and needs no new group.
10292
- detached: process.platform !== "win32",
10293
- stdio: ["ignore", "pipe", "pipe"]
10788
+ let child;
10789
+ try {
10790
+ child = spawn4(shell, shellArgs, {
10791
+ cwd: ctx.workDir,
10792
+ // Only POSIX needs its own process group for kill(-pid); the Windows
10793
+ // tree kill goes through taskkill and needs no new group.
10794
+ detached: process.platform !== "win32",
10795
+ // stdout and stderr share one O_APPEND fd: each write lands on disk
10796
+ // atomically and the streams interleave chronologically.
10797
+ stdio: ["ignore", outputFile.fd, outputFile.fd]
10798
+ });
10799
+ } catch (error) {
10800
+ discardFd(outputFile.fd);
10801
+ unlinkQuiet(outputFile.path);
10802
+ resolve12({
10803
+ output: `Error executing command: ${asErrorString(error)}`,
10804
+ isError: true
10805
+ });
10806
+ return;
10807
+ }
10808
+ discardFd(outputFile.fd);
10809
+ let doneResolve;
10810
+ const done = new Promise((resolveDone) => {
10811
+ doneResolve = resolveDone;
10294
10812
  });
10295
- let stdout = "";
10296
- let stderr = "";
10297
- let total = 0;
10298
- let outputTruncated = false;
10299
10813
  const alreadyExited = () => child.exitCode !== null || child.signalCode !== null;
10300
10814
  const killTree = (signal) => {
10301
10815
  if (typeof child.pid !== "number") {
@@ -10333,41 +10847,35 @@ var PowerShellTool = class {
10333
10847
  killTree("SIGTERM");
10334
10848
  escalateTimer = setTimeout(() => {
10335
10849
  killTree("SIGKILL");
10336
- child.stdout.destroy();
10337
- child.stderr.destroy();
10338
10850
  }, KILL_GRACE_MS2);
10339
10851
  escalateTimer.unref();
10340
10852
  };
10341
- const appendChunk = (chunk, target) => {
10342
- if (outputTruncated) {
10853
+ const watchdog = setInterval(() => {
10854
+ let size = 0;
10855
+ try {
10856
+ size = statSync11(outputFile.path).size;
10857
+ } catch {
10343
10858
  return;
10344
10859
  }
10345
- const remaining = MAX_SHELL_OUTPUT_BYTES - total;
10346
- const piece = takeUtf8Prefix(chunk, remaining);
10347
- total += utf8ByteLength(piece);
10348
- if (piece.length < chunk.length) {
10349
- outputTruncated = true;
10860
+ const cap = backgrounded ? BACKGROUND_MAX_OUTPUT_BYTES : MAX_SHELL_OUTPUT_BYTES;
10861
+ if (size > cap) {
10862
+ sizeKilled = backgrounded;
10863
+ clearInterval(watchdog);
10350
10864
  terminate();
10351
10865
  }
10352
- if (target === "stdout") {
10353
- stdout += piece;
10354
- } else {
10355
- stderr += piece;
10356
- }
10357
- };
10358
- child.stdout.setEncoding("utf-8");
10359
- child.stdout.on("data", (chunk) => {
10360
- appendChunk(chunk, "stdout");
10361
- });
10362
- child.stderr.setEncoding("utf-8");
10363
- child.stderr.on("data", (chunk) => {
10364
- appendChunk(chunk, "stderr");
10365
- });
10866
+ }, SIZE_WATCHDOG_INTERVAL_MS);
10867
+ watchdog.unref();
10366
10868
  const onAbort = () => {
10367
10869
  aborted = true;
10368
10870
  terminate();
10369
10871
  };
10872
+ const backgroundAvailableHere = manager !== null && process.env.SWIFTY_DISABLE_BACKGROUND_TASKS !== "1";
10873
+ const autoBackgroundAllowed = backgroundAvailableHere && isAutobackgroundingAllowed(command, DISALLOWED_AUTO_BACKGROUND_COMMANDS2);
10874
+ let timedOut = false;
10370
10875
  const timeoutTimer = setTimeout(() => {
10876
+ if (autoBackgroundAllowed && backgroundExecution("timeout") !== null) {
10877
+ return;
10878
+ }
10371
10879
  timedOut = true;
10372
10880
  terminate();
10373
10881
  }, timeout * 1e3);
@@ -10376,60 +10884,107 @@ var PowerShellTool = class {
10376
10884
  if (ctx.abortSignal?.aborted) {
10377
10885
  onAbort();
10378
10886
  }
10887
+ const foregroundKey = `ps-${String(this.nextForegroundId++)}`;
10379
10888
  const cleanup = () => {
10380
10889
  clearTimeout(timeoutTimer);
10890
+ clearInterval(watchdog);
10381
10891
  if (escalateTimer) {
10382
10892
  clearTimeout(escalateTimer);
10383
10893
  }
10384
10894
  ctx.abortSignal?.removeEventListener("abort", onAbort);
10895
+ this.foreground.delete(foregroundKey);
10385
10896
  };
10386
- child.on("error", (err) => {
10387
- cleanup();
10388
- const hint = strArg(asRecord(err), "code") === "ENOENT" && process.platform !== "win32" ? " (pwsh is required on macOS/Linux \u2014 install PowerShell Core)" : "";
10389
- resolve12({
10390
- output: `Error executing command: ${err.message}${hint}`,
10391
- isError: true
10392
- });
10393
- });
10394
- child.on("close", (code, signal) => {
10395
- cleanup();
10396
- if (aborted) {
10397
- const captured = stdout || stderr || outputTruncated ? formatShellOutput("PS> ", command, stdout, stderr, outputTruncated) : "";
10398
- resolve12({
10399
- output: captured ? `${captured}
10400
- Error: command interrupted` : "Error: command interrupted",
10401
- isError: true
10402
- });
10403
- return;
10404
- }
10405
- if (timedOut) {
10406
- const captured = stdout || stderr || outputTruncated ? formatShellOutput("PS> ", command, stdout, stderr, outputTruncated) : "";
10407
- resolve12({
10408
- output: captured ? `${captured}
10409
- Error: command timed out after ${String(timeout)}s` : `Error: command timed out after ${String(timeout)}s`,
10410
- isError: true
10411
- });
10897
+ const settle = (finalResult) => {
10898
+ if (settled) {
10412
10899
  return;
10413
10900
  }
10414
- const exitCode = code ?? 0;
10415
- let output = formatShellOutput("PS> ", command, stdout, stderr, outputTruncated);
10416
- if (outputTruncated) {
10417
- resolve12({ output, isError: true });
10901
+ settled = true;
10902
+ cleanup();
10903
+ resolve12(finalResult);
10904
+ };
10905
+ const settleExit = (exit) => {
10906
+ if (backgrounded) {
10418
10907
  return;
10419
10908
  }
10420
- if (exitCode !== 0) {
10421
- const hint = exitCodeHint(command, exitCode);
10422
- output += hint ? `
10423
- Exit code ${String(exitCode)} (${hint})` : `
10424
- Exit code ${String(exitCode)}`;
10909
+ const read = readOutputFile(outputFile.path, MAX_SHELL_OUTPUT_BYTES);
10910
+ const finalResult = formatFinalResult(
10911
+ "PS> ",
10912
+ command,
10913
+ exit,
10914
+ read.text,
10915
+ read.truncated,
10916
+ timeout
10917
+ );
10918
+ unlinkQuiet(outputFile.path);
10919
+ settle(finalResult);
10920
+ };
10921
+ const backgroundExecution = (reason) => {
10922
+ if (backgrounded || settled || terminating || !manager || !backgroundAvailableHere) {
10923
+ return null;
10425
10924
  }
10426
- if (code === null) {
10427
- output += `
10428
- Process terminated${signal ? ` by ${signal}` : " unexpectedly"}`;
10925
+ backgrounded = true;
10926
+ settled = true;
10927
+ clearTimeout(timeoutTimer);
10928
+ ctx.abortSignal?.removeEventListener("abort", onAbort);
10929
+ this.foreground.delete(foregroundKey);
10930
+ const task = manager.create(
10931
+ backgroundTaskName(command),
10932
+ async () => {
10933
+ const exit = await done;
10934
+ const body = buildBackgroundBody("PS> ", command, exit, outputFile.path, timeout);
10935
+ if (body.isError) {
10936
+ throw new TaskFailure(body.output);
10937
+ }
10938
+ return body.output;
10939
+ },
10940
+ () => {
10941
+ killTree("SIGKILL");
10942
+ },
10943
+ { originToolCallId: ctx.toolCallId, idPrefix: "ps", kind: "shell" }
10944
+ );
10945
+ resolve12({
10946
+ output: backgroundMessage(reason, task.id, timeout),
10947
+ isError: false
10948
+ });
10949
+ return task.id;
10950
+ };
10951
+ if (backgroundAvailableHere) {
10952
+ this.foreground.set(foregroundKey, {
10953
+ background: () => backgroundExecution("user") !== null
10954
+ });
10955
+ }
10956
+ const stopTimers = () => {
10957
+ clearInterval(watchdog);
10958
+ if (escalateTimer) {
10959
+ clearTimeout(escalateTimer);
10429
10960
  }
10430
- resolve12({ output, isError: exitCode !== 0 || code === null });
10961
+ };
10962
+ child.on("error", (err) => {
10963
+ stopTimers();
10964
+ const hint = strArg(asRecord(err), "code") === "ENOENT" && process.platform !== "win32" ? " (pwsh is required on macOS/Linux \u2014 install PowerShell Core)" : "";
10965
+ const exit = {
10966
+ code: null,
10967
+ signal: null,
10968
+ aborted,
10969
+ timedOut,
10970
+ sizeKilled,
10971
+ spawnError: `${err.message}${hint}`
10972
+ };
10973
+ doneResolve?.(exit);
10974
+ settleExit(exit);
10975
+ });
10976
+ child.on("close", (code, signal) => {
10977
+ stopTimers();
10978
+ const exit = { code, signal, aborted, timedOut, sizeKilled };
10979
+ doneResolve?.(exit);
10980
+ settleExit(exit);
10431
10981
  });
10982
+ backgroundFn = backgroundExecution;
10432
10983
  });
10984
+ return {
10985
+ result,
10986
+ background: (reason) => backgroundFn?.(reason) ?? null
10987
+ };
10433
10988
  }
10434
10989
  };
10435
10990
 
@@ -10621,7 +11176,7 @@ var AskUserResponseSchema = z15.object({
10621
11176
  id: z15.string(),
10622
11177
  answers: z15.record(z15.string(), z15.string())
10623
11178
  });
10624
- var FE_DIST = join20(import.meta.dirname, "fe", "dist");
11179
+ var FE_DIST = join21(import.meta.dirname, "fe", "dist");
10625
11180
  var MIME_TYPES = {
10626
11181
  ".html": "text/html; charset=utf-8",
10627
11182
  ".js": "application/javascript; charset=utf-8",
@@ -10634,11 +11189,11 @@ var MIME_TYPES = {
10634
11189
  };
10635
11190
  function serveStatic(path) {
10636
11191
  const cleanPath = normalize(path).replace(/^(\.\.[/\\])+/, "");
10637
- const fullPath = join20(FE_DIST, cleanPath);
11192
+ const fullPath = join21(FE_DIST, cleanPath);
10638
11193
  if (!fullPath.startsWith(FE_DIST)) {
10639
11194
  return null;
10640
11195
  }
10641
- if (!existsSync16(fullPath) || !statSync9(fullPath).isFile()) {
11196
+ if (!existsSync16(fullPath) || !statSync12(fullPath).isFile()) {
10642
11197
  return null;
10643
11198
  }
10644
11199
  const body = readFileSync16(fullPath);
@@ -10814,31 +11369,38 @@ async function createRemoteAgent(opts) {
10814
11369
  if (!client) {
10815
11370
  throw new Error("no llm client (provider not initialized)");
10816
11371
  }
10817
- const { PermissionChecker: PC } = await import("./permissions-BW4QPZXV.js");
10818
- const { Agent: AgentClass } = await import("./agent-TC4HSVMS.js");
11372
+ const { PermissionChecker: PC } = await import("./permissions-77UJQITR.js");
11373
+ const { Agent: AgentClass } = await import("./agent-ZSFK66FQ.js");
10819
11374
  const subConv = new ConversationManager();
10820
11375
  subConv.addUserMessage(prompt);
11376
+ const taskManager = new TaskManager();
10821
11377
  const subAgent = new AgentClass({
10822
11378
  client,
10823
11379
  registry: filterToolsForAgent(registry, void 0, void 0, false),
10824
11380
  checker: new PC(workDir, "acceptEdits"),
10825
11381
  conversation: subConv,
10826
11382
  workDir,
10827
- maxIterations: 200
11383
+ maxIterations: 200,
11384
+ taskManager,
11385
+ notificationFn: () => taskManager.drainNotifications().map(formatAgentTaskNotification)
10828
11386
  });
10829
11387
  let output = "";
10830
- for await (const event of subAgent.run()) {
10831
- switch (event.type) {
10832
- case "stream_text":
10833
- output += event.text;
10834
- break;
10835
- case "loop_complete":
10836
- return output || "[No output]";
10837
- case "error":
10838
- throw event.error;
11388
+ try {
11389
+ for await (const event of subAgent.run()) {
11390
+ switch (event.type) {
11391
+ case "stream_text":
11392
+ output += event.text;
11393
+ break;
11394
+ case "loop_complete":
11395
+ return output || "[No output]";
11396
+ case "error":
11397
+ throw event.error;
11398
+ }
10839
11399
  }
11400
+ return output || "[No output]";
11401
+ } finally {
11402
+ await taskManager.stopAll();
10840
11403
  }
10841
- return output || "[No output]";
10842
11404
  }
10843
11405
  };
10844
11406
  registry.register(new LoadSkillTool(catalog, skillHost, skillForkHost));
@@ -10856,10 +11418,12 @@ async function createRemoteAgent(opts) {
10856
11418
  onEvent,
10857
11419
  void 0,
10858
11420
  teamChecker,
10859
- { abortSignal }
11421
+ // Teammates stay purely foreground: see SubagentRunOptions.backgroundTasks.
11422
+ { abortSignal, backgroundTasks: false }
10860
11423
  );
10861
11424
  const teamManager = new TeamManager(workDir);
10862
11425
  const backgroundTaskManager = new TaskManager();
11426
+ attachBackgroundTaskManager(registry, backgroundTaskManager);
10863
11427
  registry.register(new TeamCreateTool(teamManager));
10864
11428
  registry.register(new SendMessageTool(teamManager));
10865
11429
  registry.register(new TeamDeleteTool(teamManager));
@@ -10898,6 +11462,7 @@ async function createRemoteAgent(opts) {
10898
11462
  const forkClient = modelOverride ? await createClient({ ...provider, model: resolvedModel }, forkSystemPrompt) : client;
10899
11463
  const checker = context?.permissionChecker ?? new PermissionChecker(forkWorkDir, "acceptEdits");
10900
11464
  forkConv.addUserMessage(prompt);
11465
+ const forkTaskManager = new TaskManager();
10901
11466
  const agent2 = new Agent({
10902
11467
  client: forkClient,
10903
11468
  registry: forkRegistry,
@@ -10909,23 +11474,29 @@ async function createRemoteAgent(opts) {
10909
11474
  onPermissionRequest: context?.onPermissionRequest,
10910
11475
  fileStateCache: new FileStateCache(),
10911
11476
  instructions,
10912
- memoryContent: memReminder
11477
+ memoryContent: memReminder,
11478
+ taskManager: forkTaskManager,
11479
+ notificationFn: () => forkTaskManager.drainNotifications().map(formatAgentTaskNotification)
10913
11480
  });
10914
11481
  let output = "";
10915
- for await (const event of agent2.run()) {
10916
- switch (event.type) {
10917
- case "stream_text":
10918
- output += event.text;
10919
- break;
10920
- case "loop_complete":
10921
- return output || "[No output]";
10922
- case "error":
10923
- return output ? `${output}
11482
+ try {
11483
+ for await (const event of agent2.run()) {
11484
+ switch (event.type) {
11485
+ case "stream_text":
11486
+ output += event.text;
11487
+ break;
11488
+ case "loop_complete":
11489
+ return output || "[No output]";
11490
+ case "error":
11491
+ return output ? `${output}
10924
11492
 
10925
11493
  [Error: ${event.error.message}]` : `Error: ${event.error.message}`;
11494
+ }
10926
11495
  }
11496
+ return output || "[No output]";
11497
+ } finally {
11498
+ await forkTaskManager.stopAll();
10927
11499
  }
10928
- return output || "[No output]";
10929
11500
  },
10930
11501
  backgroundTaskManager
10931
11502
  );
@@ -12423,6 +12994,7 @@ __export(javascript_exports, {
12423
12994
  evaluateJavaScript: () => evaluateJavaScript,
12424
12995
  isolatedVmSupported: () => isolatedVmSupported
12425
12996
  });
12997
+ import { randomBytes as randomBytes4 } from "crypto";
12426
12998
  import { z as z16 } from "zod";
12427
12999
  var DEFAULT_TIMEOUT_MS = 1e3;
12428
13000
  var MAX_TIMEOUT_MS = 1e4;
@@ -12435,7 +13007,10 @@ var JavaScriptArgumentsSchema = z16.object({
12435
13007
  code: z16.string().min(1),
12436
13008
  input: z16.json().optional(),
12437
13009
  timeout_ms: z16.number().int().min(1).max(MAX_TIMEOUT_MS).default(DEFAULT_TIMEOUT_MS),
12438
- memory_limit_mb: z16.number().int().min(8).max(MAX_MEMORY_LIMIT_MB).default(DEFAULT_MEMORY_LIMIT_MB)
13010
+ memory_limit_mb: z16.number().int().min(8).max(MAX_MEMORY_LIMIT_MB).default(DEFAULT_MEMORY_LIMIT_MB),
13011
+ // Accepted even when backgrounding is unavailable (then simply ignored):
13012
+ // the JSON schema gates what the model sees per host.
13013
+ run_in_background: z16.boolean().optional()
12439
13014
  });
12440
13015
  var JavaScriptEvaluationOptionsSchema = z16.object({
12441
13016
  input: z16.json().optional(),
@@ -12471,14 +13046,22 @@ async function runJavaScript(code, input, timeoutMs, memoryLimitMb, abortSignal)
12471
13046
  }
12472
13047
  abortSignal?.throwIfAborted();
12473
13048
  const module = await import("isolated-vm");
13049
+ abortSignal?.throwIfAborted();
12474
13050
  const ivm = module.default;
12475
13051
  const isolate = new ivm.Isolate({ memoryLimit: memoryLimitMb });
12476
13052
  const context = await isolate.createContext();
12477
13053
  const onAbort = () => {
12478
- isolate.dispose();
13054
+ if (!isolate.isDisposed) {
13055
+ isolate.dispose();
13056
+ }
12479
13057
  };
12480
13058
  abortSignal?.addEventListener("abort", onAbort, { once: true });
13059
+ let wallTimer;
12481
13060
  try {
13061
+ if (abortSignal?.aborted) {
13062
+ onAbort();
13063
+ abortSignal.throwIfAborted();
13064
+ }
12482
13065
  await context.global.set("input", input ?? null, { copy: true });
12483
13066
  const source = `
12484
13067
  (async () => {
@@ -12523,12 +13106,19 @@ ${code}
12523
13106
  return __payload;
12524
13107
  })()
12525
13108
  `;
12526
- const raw = await context.eval(source, {
13109
+ const evaluation = context.eval(source, {
12527
13110
  timeout: timeoutMs,
12528
13111
  promise: true,
12529
13112
  copy: true,
12530
13113
  filename: "swifty-sandbox.js"
12531
13114
  });
13115
+ const deadline = new Promise((_, reject) => {
13116
+ wallTimer = setTimeout(() => {
13117
+ reject(new Error(`JavaScript evaluation timed out after ${String(timeoutMs)}ms`));
13118
+ onAbort();
13119
+ }, timeoutMs);
13120
+ });
13121
+ const raw = await Promise.race([evaluation, deadline]);
12532
13122
  if (typeof raw !== "string") {
12533
13123
  throw new Error("JavaScript sandbox returned an invalid result");
12534
13124
  }
@@ -12538,6 +13128,7 @@ ${code}
12538
13128
  const decoded = JSON.parse(raw);
12539
13129
  return JavaScriptEvaluationSchema.parse(decoded);
12540
13130
  } finally {
13131
+ clearTimeout(wallTimer);
12541
13132
  abortSignal?.removeEventListener("abort", onAbort);
12542
13133
  try {
12543
13134
  context.release();
@@ -12548,37 +13139,94 @@ ${code}
12548
13139
  }
12549
13140
  }
12550
13141
  }
13142
+ function formatEvaluation(result) {
13143
+ const sections = [];
13144
+ if (result.logs.length > 0) {
13145
+ sections.push(`Console:
13146
+ ${result.logs.join("\n")}`);
13147
+ }
13148
+ if (result.logsTruncated) {
13149
+ sections.push("Console output truncated");
13150
+ }
13151
+ sections.push(`Result:
13152
+ ${JSON.stringify(result.value, null, 2)}`);
13153
+ return sections.join("\n\n");
13154
+ }
13155
+ function jsBackgroundMessage(reason, taskId) {
13156
+ return reason === "user" ? `JavaScript evaluation was manually backgrounded by the user (task_id: ${taskId}). It is still running \u2014 you will be notified when it completes.` : `JavaScript running in background (task_id: ${taskId}). You will be notified when it completes; do not poll. Use TaskStop with task_id to abort it early.`;
13157
+ }
12551
13158
  var JavaScriptTool = class {
12552
13159
  name = "JavaScript";
12553
13160
  description = JAVASCRIPT_DESCRIPTION;
12554
13161
  category = "command";
13162
+ /**
13163
+ * Background task registry, injected by the host — same contract as
13164
+ * BashTool.taskManager. Unlike the shell tools there is no timeout
13165
+ * auto-background: timeout_ms is the isolate's hard safety cap — V8 kills a
13166
+ * synchronous script at the cap, and a wall-clock deadline disposes the
13167
+ * isolate when an evaluation is suspended on a promise — so nothing is left
13168
+ * to keep running.
13169
+ */
13170
+ taskManager = null;
13171
+ /** Running foreground evaluations eligible for manual backgrounding (Ctrl+B). */
13172
+ foreground = /* @__PURE__ */ new Map();
13173
+ nextForegroundId = 1;
13174
+ /** Instance-level background gate; see BashTool.backgroundEnabled. */
13175
+ backgroundEnabled() {
13176
+ return this.taskManager !== null && process.env.SWIFTY_DISABLE_BACKGROUND_TASKS !== "1";
13177
+ }
13178
+ /** True while at least one foreground evaluation runs. */
13179
+ hasForegroundTasks() {
13180
+ return this.foreground.size > 0;
13181
+ }
13182
+ /** Move every running foreground evaluation to the background (Ctrl+B). */
13183
+ backgroundForegroundTasks() {
13184
+ let count = 0;
13185
+ for (const entry of [...this.foreground.values()]) {
13186
+ if (entry.background()) {
13187
+ count++;
13188
+ }
13189
+ }
13190
+ return count;
13191
+ }
12555
13192
  schema() {
13193
+ const properties = {
13194
+ code: {
13195
+ type: "string",
13196
+ description: "JavaScript function body to execute; use return to produce a result"
13197
+ },
13198
+ input: {
13199
+ description: "Optional JSON-compatible value exposed as globalThis.input"
13200
+ },
13201
+ timeout_ms: {
13202
+ type: "integer",
13203
+ minimum: 1,
13204
+ maximum: MAX_TIMEOUT_MS,
13205
+ default: DEFAULT_TIMEOUT_MS
13206
+ },
13207
+ memory_limit_mb: {
13208
+ type: "integer",
13209
+ minimum: 8,
13210
+ maximum: MAX_MEMORY_LIMIT_MB,
13211
+ default: DEFAULT_MEMORY_LIMIT_MB
13212
+ }
13213
+ };
13214
+ let description = this.description;
13215
+ if (this.backgroundEnabled()) {
13216
+ properties.run_in_background = {
13217
+ type: "boolean",
13218
+ description: "Run the evaluation in the background. Returns a task ID immediately; the result arrives later as a task notification.",
13219
+ default: false
13220
+ };
13221
+ description = `${this.description}
13222
+ ${JAVASCRIPT_BACKGROUND_DESCRIPTION}`;
13223
+ }
12556
13224
  return {
12557
13225
  name: this.name,
12558
- description: this.description,
13226
+ description,
12559
13227
  input_schema: {
12560
13228
  type: "object",
12561
- properties: {
12562
- code: {
12563
- type: "string",
12564
- description: "JavaScript function body to execute; use return to produce a result"
12565
- },
12566
- input: {
12567
- description: "Optional JSON-compatible value exposed as globalThis.input"
12568
- },
12569
- timeout_ms: {
12570
- type: "integer",
12571
- minimum: 1,
12572
- maximum: MAX_TIMEOUT_MS,
12573
- default: DEFAULT_TIMEOUT_MS
12574
- },
12575
- memory_limit_mb: {
12576
- type: "integer",
12577
- minimum: 8,
12578
- maximum: MAX_MEMORY_LIMIT_MB,
12579
- default: DEFAULT_MEMORY_LIMIT_MB
12580
- }
12581
- },
13229
+ properties,
12582
13230
  required: ["code"],
12583
13231
  additionalProperties: false
12584
13232
  }
@@ -12589,32 +13237,128 @@ var JavaScriptTool = class {
12589
13237
  if (!parsed.success) {
12590
13238
  return { output: `Error: ${parsed.error.message}`, isError: true };
12591
13239
  }
12592
- try {
12593
- const result = await runJavaScript(
12594
- parsed.data.code,
12595
- parsed.data.input,
12596
- parsed.data.timeout_ms,
12597
- parsed.data.memory_limit_mb,
12598
- ctx.abortSignal
12599
- );
12600
- const sections = [];
12601
- if (result.logs.length > 0) {
12602
- sections.push(`Console:
12603
- ${result.logs.join("\n")}`);
12604
- }
12605
- if (result.logsTruncated) {
12606
- sections.push("Console output truncated");
13240
+ const manager = ctx.taskManager !== void 0 ? ctx.taskManager : this.taskManager;
13241
+ const backgroundAvailable = manager !== null && process.env.SWIFTY_DISABLE_BACKGROUND_TASKS !== "1";
13242
+ const runInBackground = parsed.data.run_in_background === true && backgroundAvailable;
13243
+ const handle = this.startEvaluation(ctx, parsed.data, manager, backgroundAvailable);
13244
+ if (runInBackground) {
13245
+ const taskId = handle.background("explicit");
13246
+ if (taskId !== null) {
13247
+ return {
13248
+ output: jsBackgroundMessage("explicit", taskId),
13249
+ isError: false
13250
+ };
12607
13251
  }
12608
- sections.push(`Result:
12609
- ${JSON.stringify(result.value, null, 2)}`);
12610
- return { output: sections.join("\n\n"), isError: false };
12611
- } catch (error) {
12612
- return { output: `Error executing JavaScript: ${asErrorString(error)}`, isError: true };
12613
13252
  }
13253
+ return handle.result;
13254
+ }
13255
+ /**
13256
+ * Start the isolated evaluation. The returned handle mirrors the shell
13257
+ * tools: background() transitions the pending evaluation into a TaskManager
13258
+ * task — the isolate keeps running on its own thread, the tool call
13259
+ * resolves immediately, and completion arrives as a task notification.
13260
+ * While foreground, the caller's abort signal (Esc) disposes the isolate;
13261
+ * once backgrounded, only TaskStop or session shutdown can abort it.
13262
+ */
13263
+ startEvaluation(ctx, data, manager, backgroundAvailable) {
13264
+ let backgroundFn;
13265
+ const result = new Promise((resolve12) => {
13266
+ let settled = false;
13267
+ let backgrounded = false;
13268
+ const controller = new AbortController();
13269
+ const onCallerAbort = () => {
13270
+ controller.abort();
13271
+ };
13272
+ if (ctx.abortSignal?.aborted) {
13273
+ resolve12({ output: "Error: command interrupted", isError: true });
13274
+ return;
13275
+ }
13276
+ ctx.abortSignal?.addEventListener("abort", onCallerAbort, { once: true });
13277
+ const evalPromise = runJavaScript(
13278
+ data.code,
13279
+ data.input,
13280
+ data.timeout_ms,
13281
+ data.memory_limit_mb,
13282
+ controller.signal
13283
+ );
13284
+ const foregroundKey = `js-${String(this.nextForegroundId++)}`;
13285
+ const settle = (finalResult) => {
13286
+ if (settled) {
13287
+ return;
13288
+ }
13289
+ settled = true;
13290
+ ctx.abortSignal?.removeEventListener("abort", onCallerAbort);
13291
+ this.foreground.delete(foregroundKey);
13292
+ resolve12(finalResult);
13293
+ };
13294
+ const backgroundExecution = (reason) => {
13295
+ if (backgrounded || settled || controller.signal.aborted || !manager || !backgroundAvailable) {
13296
+ return null;
13297
+ }
13298
+ backgrounded = true;
13299
+ settled = true;
13300
+ ctx.abortSignal?.removeEventListener("abort", onCallerAbort);
13301
+ this.foreground.delete(foregroundKey);
13302
+ const task = manager.create(
13303
+ backgroundTaskName(data.code),
13304
+ async (backgroundTask) => {
13305
+ const cap = (text) => text.length > BACKGROUND_NOTIFICATION_CHARS ? persistLargeResult(
13306
+ ctx.workDir,
13307
+ ctx.sessionId ?? "",
13308
+ `${backgroundTask.id}-${randomBytes4(4).toString("hex")}`,
13309
+ text
13310
+ ) : text;
13311
+ try {
13312
+ const evaluation = await evalPromise;
13313
+ return cap(formatEvaluation(evaluation));
13314
+ } catch (error) {
13315
+ if (backgroundTask.status === "cancelled") {
13316
+ throw error;
13317
+ }
13318
+ throw new TaskFailure(cap(`Error executing JavaScript: ${asErrorString(error)}`));
13319
+ }
13320
+ },
13321
+ () => {
13322
+ controller.abort();
13323
+ },
13324
+ { originToolCallId: ctx.toolCallId, idPrefix: "js", kind: "js" }
13325
+ );
13326
+ resolve12({
13327
+ output: jsBackgroundMessage(reason, task.id),
13328
+ isError: false
13329
+ });
13330
+ return task.id;
13331
+ };
13332
+ if (backgroundAvailable) {
13333
+ this.foreground.set(foregroundKey, {
13334
+ background: () => backgroundExecution("user") !== null
13335
+ });
13336
+ }
13337
+ evalPromise.then(
13338
+ (evaluation) => {
13339
+ settle({ output: formatEvaluation(evaluation), isError: false });
13340
+ },
13341
+ (error) => {
13342
+ settle({
13343
+ output: `Error executing JavaScript: ${asErrorString(error)}`,
13344
+ isError: true
13345
+ });
13346
+ }
13347
+ );
13348
+ backgroundFn = backgroundExecution;
13349
+ });
13350
+ return {
13351
+ result,
13352
+ background: (reason) => backgroundFn?.(reason) ?? null
13353
+ };
12614
13354
  }
12615
13355
  };
12616
13356
 
12617
13357
  // src/tools/web-fetch.ts
13358
+ var web_fetch_exports = {};
13359
+ __export(web_fetch_exports, {
13360
+ WebFetchTool: () => WebFetchTool
13361
+ });
12618
13362
  var log27 = createChildLogger({ module: "tools" });
12619
13363
  var MAX_CONTENT_BYTES = 10 * 1024 * 1024;
12620
13364
  var FETCH_TIMEOUT_MS = 6e4;
@@ -13615,8 +14359,8 @@ __export(manager_exports3, {
13615
14359
  CodeReviewManager: () => CodeReviewManager,
13616
14360
  createDefaultCodeReviewTeam: () => createDefaultCodeReviewTeam
13617
14361
  });
13618
- import { readFileSync as readFileSync17, writeFileSync as writeFileSync11, existsSync as existsSync18, mkdirSync as mkdirSync12 } from "fs";
13619
- import { join as join21 } from "path";
14362
+ import { readFileSync as readFileSync17, writeFileSync as writeFileSync11, existsSync as existsSync18, mkdirSync as mkdirSync13 } from "fs";
14363
+ import { join as join22 } from "path";
13620
14364
  import z17 from "zod";
13621
14365
  var log30 = createChildLogger({ module: "code-review" });
13622
14366
  var CodeReviewMemberSchema = z17.object({
@@ -13639,7 +14383,7 @@ var CodeReviewManager = class {
13639
14383
  workDir;
13640
14384
  constructor(workDir, teamManager) {
13641
14385
  this.workDir = workDir;
13642
- this.configPath = join21(workDir, ".swifty", "code-review-teams.json");
14386
+ this.configPath = join22(workDir, ".swifty", "code-review-teams.json");
13643
14387
  this.teamManager = teamManager;
13644
14388
  this.loadTeams();
13645
14389
  }
@@ -13658,9 +14402,9 @@ var CodeReviewManager = class {
13658
14402
  }
13659
14403
  }
13660
14404
  saveTeams() {
13661
- const dir = join21(this.workDir, ".swifty");
14405
+ const dir = join22(this.workDir, ".swifty");
13662
14406
  if (!existsSync18(dir)) {
13663
- mkdirSync12(dir, { recursive: true });
14407
+ mkdirSync13(dir, { recursive: true });
13664
14408
  }
13665
14409
  const teams = [...this.teams.values()];
13666
14410
  writeFileSync11(this.configPath, JSON.stringify(teams, null, 2));
@@ -13806,8 +14550,8 @@ var usage_tracker_exports = {};
13806
14550
  __export(usage_tracker_exports, {
13807
14551
  CommandUsageTracker: () => CommandUsageTracker
13808
14552
  });
13809
- import { readFileSync as readFileSync18, writeFileSync as writeFileSync12, mkdirSync as mkdirSync13, existsSync as existsSync19 } from "fs";
13810
- import { join as join22, dirname as dirname9 } from "path";
14553
+ import { readFileSync as readFileSync18, writeFileSync as writeFileSync12, mkdirSync as mkdirSync14, existsSync as existsSync19 } from "fs";
14554
+ import { join as join23, dirname as dirname9 } from "path";
13811
14555
  import { safeParse as safeParse3, z as z18 } from "zod";
13812
14556
  var log31 = createChildLogger({ module: "commands" });
13813
14557
  var UsageEntrySchema = z18.object({
@@ -13818,10 +14562,10 @@ var CommandUsageTracker = class {
13818
14562
  usage = /* @__PURE__ */ new Map();
13819
14563
  filePath;
13820
14564
  constructor(workDir) {
13821
- const dir = join22(workDir, ".swifty");
13822
- this.filePath = join22(dir, "command_usage.json");
14565
+ const dir = join23(workDir, ".swifty");
14566
+ this.filePath = join23(dir, "command_usage.json");
13823
14567
  if (!existsSync19(dir)) {
13824
- mkdirSync13(dir, { recursive: true });
14568
+ mkdirSync14(dir, { recursive: true });
13825
14569
  }
13826
14570
  this.load();
13827
14571
  }
@@ -13862,7 +14606,7 @@ var CommandUsageTracker = class {
13862
14606
  }
13863
14607
  save() {
13864
14608
  try {
13865
- mkdirSync13(dirname9(this.filePath), { recursive: true });
14609
+ mkdirSync14(dirname9(this.filePath), { recursive: true });
13866
14610
  writeFileSync12(this.filePath, JSON.stringify(Object.fromEntries(this.usage), null, 2));
13867
14611
  } catch (err) {
13868
14612
  log31.error({ err }, "commands operation failed");
@@ -13885,15 +14629,15 @@ __export(history_exports, {
13885
14629
  append: () => append,
13886
14630
  load: () => load
13887
14631
  });
13888
- import { readFileSync as readFileSync19, writeFileSync as writeFileSync13, mkdirSync as mkdirSync14, existsSync as existsSync20 } from "fs";
13889
- import { join as join23 } from "path";
14632
+ import { readFileSync as readFileSync19, writeFileSync as writeFileSync13, mkdirSync as mkdirSync15, existsSync as existsSync20 } from "fs";
14633
+ import { join as join24 } from "path";
13890
14634
  import { parse as parse8, z as z19 } from "zod";
13891
14635
  var log32 = createChildLogger({ module: "history" });
13892
14636
  var MAX_HISTORY_ENTRIES = 200;
13893
14637
  var FILENAME = "prompt_history.jsonl";
13894
14638
  var JSONLSchema = z19.looseObject({ text: z19.string() });
13895
14639
  function load(dir) {
13896
- const filePath = join23(dir, FILENAME);
14640
+ const filePath = join24(dir, FILENAME);
13897
14641
  if (!existsSync20(filePath)) {
13898
14642
  return [];
13899
14643
  }
@@ -13915,8 +14659,8 @@ function load(dir) {
13915
14659
  }
13916
14660
  }
13917
14661
  function append(dir, text) {
13918
- const filePath = join23(dir, FILENAME);
13919
- mkdirSync14(dir, { recursive: true });
14662
+ const filePath = join24(dir, FILENAME);
14663
+ mkdirSync15(dir, { recursive: true });
13920
14664
  const entries = load(dir);
13921
14665
  if (entries.length === 0 || entries[entries.length - 1] !== text) {
13922
14666
  entries.push(text);
@@ -14132,17 +14876,17 @@ __export(install_tool_exports, {
14132
14876
  });
14133
14877
  import { randomUUID as randomUUID2 } from "crypto";
14134
14878
  import {
14135
- mkdirSync as mkdirSync15,
14879
+ mkdirSync as mkdirSync16,
14136
14880
  writeFileSync as writeFileSync14,
14137
14881
  readFileSync as readFileSync20,
14138
14882
  lstatSync,
14139
14883
  realpathSync,
14140
- openSync as openSync2,
14141
- closeSync as closeSync2,
14884
+ openSync as openSync3,
14885
+ closeSync as closeSync3,
14142
14886
  renameSync as renameSync2,
14143
14887
  rmSync as rmSync2
14144
14888
  } from "fs";
14145
- import { join as join24, resolve as resolve10 } from "path";
14889
+ import { join as join25, resolve as resolve10 } from "path";
14146
14890
  import yaml7 from "js-yaml";
14147
14891
  var log33 = createChildLogger({ module: "skills" });
14148
14892
  var InstallSkillTool = class {
@@ -14234,28 +14978,28 @@ ${parsed.body}
14234
14978
  ctx.abortSignal?.throwIfAborted();
14235
14979
  let dir = realpathSync(this.workDir);
14236
14980
  for (const segment of [".agents", "skills", name]) {
14237
- dir = join24(dir, segment);
14981
+ dir = join25(dir, segment);
14238
14982
  const stat4 = lstatSync(dir, { throwIfNoEntry: false });
14239
14983
  if (stat4) {
14240
14984
  if (stat4.isSymbolicLink() || !stat4.isDirectory()) {
14241
14985
  throw new Error(`Installation directory must be a real directory: ${dir}`);
14242
14986
  }
14243
14987
  } else {
14244
- mkdirSync15(dir);
14988
+ mkdirSync16(dir);
14245
14989
  }
14246
14990
  }
14247
- const destination = join24(dir, "SKILL.md");
14991
+ const destination = join25(dir, "SKILL.md");
14248
14992
  const stat3 = lstatSync(destination, { throwIfNoEntry: false });
14249
14993
  if (stat3 && (stat3.isSymbolicLink() || !stat3.isFile())) {
14250
14994
  throw new Error(`Installation target must be a regular file: ${destination}`);
14251
14995
  }
14252
- const temporary = join24(dir, `.SKILL-${randomUUID2()}.tmp`);
14253
- const fd = openSync2(temporary, "wx", stat3?.mode ?? 438);
14996
+ const temporary = join25(dir, `.SKILL-${randomUUID2()}.tmp`);
14997
+ const fd = openSync3(temporary, "wx", stat3?.mode ?? 438);
14254
14998
  try {
14255
14999
  try {
14256
15000
  writeFileSync14(fd, content, "utf-8");
14257
15001
  } finally {
14258
- closeSync2(fd);
15002
+ closeSync3(fd);
14259
15003
  }
14260
15004
  renameSync2(temporary, destination);
14261
15005
  } finally {
@@ -14298,6 +15042,7 @@ __export(tools_exports3, {
14298
15042
  Diff: () => diff_exports,
14299
15043
  EditFile: () => edit_file_exports,
14300
15044
  EnterWorktree: () => enter_worktree_exports,
15045
+ ExitCodeHints: () => exit_code_hints_exports,
14301
15046
  ExitPlanMode: () => exit_plan_mode_exports,
14302
15047
  ExitWorktree: () => exit_worktree_exports,
14303
15048
  FileMutationQueue: () => file_mutation_queue_exports,
@@ -14310,11 +15055,13 @@ __export(tools_exports3, {
14310
15055
  PowerShell: () => powershell_exports,
14311
15056
  ReadFile: () => read_file_exports,
14312
15057
  Registry: () => registry_exports,
15058
+ ShellBackground: () => shell_background_exports,
14313
15059
  ShellOutput: () => shell_output_exports,
14314
15060
  Snippets: () => snippets_exports,
14315
15061
  SyntheticOutput: () => synthetic_output_exports,
14316
15062
  ToolSearch: () => tool_search_exports,
14317
15063
  Types: () => types_exports,
15064
+ WebFetchTool: () => web_fetch_exports,
14318
15065
  WriteFile: () => write_file_exports
14319
15066
  });
14320
15067
 
@@ -14327,262 +15074,13 @@ function isDiffTool(toolName) {
14327
15074
  return toolName === "EditFile";
14328
15075
  }
14329
15076
 
14330
- // src/ui/index.ts
14331
- var ui_exports = {};
14332
- __export(ui_exports, {
14333
- Styles: () => styles_exports,
14334
- UseFollowUpQueue: () => use_follow_up_queue_exports,
14335
- UseIdeInput: () => use_ide_input_exports,
14336
- UseTeammateStates: () => use_teammate_states_exports
14337
- });
14338
-
14339
- // src/ui/styles.ts
14340
- var styles_exports = {};
14341
- __export(styles_exports, {
14342
- DARK_THEME: () => DARK_THEME,
14343
- ICONS: () => ICONS,
14344
- LIGHT_THEME: () => LIGHT_THEME,
14345
- THEME: () => THEME,
14346
- activityStatusColor: () => activityStatusColor,
14347
- setThemeMode: () => setThemeMode,
14348
- thinkingLevelColor: () => thinkingLevelColor
14349
- });
14350
- var DARK_THEME = {
14351
- accent: "#8abeb7",
14352
- bashMode: "#b5bd68",
14353
- border: "#5f87ff",
14354
- borderAccent: "#00d7ff",
14355
- borderMuted: "#505050",
14356
- customMessageBg: "#2d2838",
14357
- customMessageLabel: "#9575cd",
14358
- dim: "#666666",
14359
- error: "#cc6666",
14360
- mdCode: "#8abeb7",
14361
- mdCodeBlock: "#b5bd68",
14362
- mdCodeBlockBorder: "#808080",
14363
- mdHeading: "#f0c674",
14364
- mdHr: "#808080",
14365
- mdLink: "#81a2be",
14366
- mdLinkUrl: "#666666",
14367
- mdListBullet: "#8abeb7",
14368
- mdQuote: "#808080",
14369
- mdQuoteBorder: "#808080",
14370
- muted: "#808080",
14371
- searchMatchBg: "#3a3a4a",
14372
- searchMatchText: "#d4d4d4",
14373
- selectedBg: "#3a3a4a",
14374
- success: "#b5bd68",
14375
- syntaxComment: "#6A9955",
14376
- syntaxFunction: "#DCDCAA",
14377
- syntaxKeyword: "#569CD6",
14378
- syntaxNumber: "#B5CEA8",
14379
- syntaxOperator: "#D4D4D4",
14380
- syntaxPunctuation: "#D4D4D4",
14381
- syntaxString: "#CE9178",
14382
- syntaxType: "#4EC9B0",
14383
- syntaxVariable: "#9CDCFE",
14384
- text: "#d4d4d4",
14385
- thinking: "#808080",
14386
- thinkingText: "#808080",
14387
- thinkingHigh: "#b294bb",
14388
- thinkingLow: "#5f87af",
14389
- thinkingMax: "#ff5fff",
14390
- thinkingMedium: "#81a2be",
14391
- thinkingMinimal: "#6e6e6e",
14392
- thinkingOff: "#505050",
14393
- thinkingXHigh: "#d183e8",
14394
- toolDiffAdded: "#b5bd68",
14395
- toolDiffContext: "#808080",
14396
- toolDiffRemoved: "#cc6666",
14397
- toolErrorBg: "#3c2828",
14398
- toolOutput: "#808080",
14399
- toolPendingBg: "#282832",
14400
- toolSuccessBg: "#283228",
14401
- toolTitle: "#d4d4d4",
14402
- customMessageText: "#d4d4d4",
14403
- userMessageText: "#d4d4d4",
14404
- userMessageBg: "#343541",
14405
- warning: "#ffff00"
14406
- };
14407
- var LIGHT_THEME = {
14408
- accent: "#5a8080",
14409
- bashMode: "#588458",
14410
- border: "#547da7",
14411
- borderAccent: "#5a8080",
14412
- borderMuted: "#b0b0b0",
14413
- customMessageBg: "#ede7f6",
14414
- customMessageLabel: "#7e57c2",
14415
- dim: "#767676",
14416
- error: "#aa5555",
14417
- mdCode: "#5a8080",
14418
- mdCodeBlock: "#588458",
14419
- mdCodeBlockBorder: "#6c6c6c",
14420
- mdHeading: "#9a7326",
14421
- mdHr: "#6c6c6c",
14422
- mdLink: "#547da7",
14423
- mdLinkUrl: "#767676",
14424
- mdListBullet: "#588458",
14425
- mdQuote: "#6c6c6c",
14426
- mdQuoteBorder: "#6c6c6c",
14427
- muted: "#6c6c6c",
14428
- searchMatchBg: "#d0d0e0",
14429
- searchMatchText: "#1f2328",
14430
- selectedBg: "#d0d0e0",
14431
- success: "#588458",
14432
- syntaxComment: "#008000",
14433
- syntaxFunction: "#795E26",
14434
- syntaxKeyword: "#0000FF",
14435
- syntaxNumber: "#098658",
14436
- syntaxOperator: "#000000",
14437
- syntaxPunctuation: "#000000",
14438
- syntaxString: "#A31515",
14439
- syntaxType: "#267F99",
14440
- syntaxVariable: "#001080",
14441
- text: "#1f2328",
14442
- thinking: "#6c6c6c",
14443
- thinkingText: "#6c6c6c",
14444
- thinkingHigh: "#875f87",
14445
- thinkingLow: "#547da7",
14446
- thinkingMax: "#af005f",
14447
- thinkingMedium: "#5a8080",
14448
- thinkingMinimal: "#767676",
14449
- thinkingOff: "#b0b0b0",
14450
- thinkingXHigh: "#8b008b",
14451
- toolDiffAdded: "#588458",
14452
- toolDiffContext: "#6c6c6c",
14453
- toolDiffRemoved: "#aa5555",
14454
- toolErrorBg: "#f0e8e8",
14455
- toolOutput: "#6c6c6c",
14456
- toolPendingBg: "#e8e8f0",
14457
- toolSuccessBg: "#e8f0e8",
14458
- toolTitle: "#1f2328",
14459
- customMessageText: "#1f2328",
14460
- userMessageText: "#1f2328",
14461
- userMessageBg: "#e8e8e8",
14462
- warning: "#9a7326"
14463
- };
14464
- var THEME = { ...DARK_THEME };
14465
- function setThemeMode(mode) {
14466
- Object.assign(THEME, mode === "light" ? LIGHT_THEME : DARK_THEME);
14467
- }
14468
- function thinkingLevelColor(level) {
14469
- switch (level) {
14470
- case "off":
14471
- return THEME.thinkingOff;
14472
- case "minimal":
14473
- return THEME.thinkingMinimal;
14474
- case "low":
14475
- return THEME.thinkingLow;
14476
- case "medium":
14477
- return THEME.thinkingMedium;
14478
- case "high":
14479
- return THEME.thinkingHigh;
14480
- case "xhigh":
14481
- return THEME.thinkingXHigh;
14482
- case "max":
14483
- return THEME.thinkingMax;
14484
- }
14485
- }
14486
- function activityStatusColor(status) {
14487
- switch (status) {
14488
- case "working":
14489
- return THEME.thinkingHigh;
14490
- case "retry":
14491
- return THEME.warning;
14492
- case "compacting":
14493
- return THEME.accent;
14494
- case "error":
14495
- return THEME.error;
14496
- case "idle":
14497
- return THEME.borderMuted;
14498
- }
14499
- }
14500
- var ICONS = {
14501
- active: "\u25C9",
14502
- arrow: "\u2192",
14503
- branch: "\u251C\u2500",
14504
- collapsed: "\u229E",
14505
- expanded: "\u229F",
14506
- inactive: "\u25CB",
14507
- lastBranch: "\u2514\u2500",
14508
- prompt: ">",
14509
- success: "\u2713",
14510
- tree: "\u2502"
14511
- };
14512
-
14513
- // src/ui/use-follow-up-queue.ts
14514
- var use_follow_up_queue_exports = {};
14515
- __export(use_follow_up_queue_exports, {
14516
- useFollowUpQueue: () => useFollowUpQueue
14517
- });
14518
- import { useCallback, useEffect, useRef, useState } from "react";
14519
- function useFollowUpQueue({ blocked, send, onError }) {
14520
- const pending = useRef([]);
14521
- const active = useRef(false);
14522
- const mounted = useRef(true);
14523
- const callbacks = useRef({ send, onError });
14524
- callbacks.current = { send, onError };
14525
- const [messages, setMessages] = useState([]);
14526
- const [processing, setProcessing] = useState(false);
14527
- const [paused, setPaused] = useState(false);
14528
- useEffect(() => {
14529
- mounted.current = true;
14530
- return () => {
14531
- mounted.current = false;
14532
- };
14533
- }, []);
14534
- const enqueue = useCallback((message) => {
14535
- if (!message.trim()) {
14536
- return;
14537
- }
14538
- pending.current = [...pending.current, message];
14539
- setMessages(pending.current);
14540
- setPaused(false);
14541
- }, []);
14542
- const takeLast = useCallback(() => {
14543
- const message = pending.current.at(-1);
14544
- if (message === void 0) {
14545
- return void 0;
14546
- }
14547
- pending.current = pending.current.slice(0, -1);
14548
- setMessages(pending.current);
14549
- return message;
14550
- }, []);
14551
- useEffect(() => {
14552
- if (blocked || paused || active.current || pending.current.length === 0) {
14553
- return;
14554
- }
14555
- const next = pending.current[0];
14556
- pending.current = pending.current.slice(1);
14557
- active.current = true;
14558
- setMessages(pending.current);
14559
- setProcessing(true);
14560
- void (async () => {
14561
- try {
14562
- await callbacks.current.send(next);
14563
- } catch (error) {
14564
- if (mounted.current) {
14565
- setPaused(true);
14566
- callbacks.current.onError(error);
14567
- }
14568
- } finally {
14569
- active.current = false;
14570
- if (mounted.current) {
14571
- setProcessing(false);
14572
- }
14573
- }
14574
- })();
14575
- }, [blocked, messages, paused, processing]);
14576
- return { messages, enqueue, takeLast, processing, paused };
14577
- }
14578
-
14579
- // src/ui/use-ide-input.ts
14580
- var use_ide_input_exports = {};
14581
- __export(use_ide_input_exports, {
14582
- useIdeInput: () => useIdeInput
15077
+ // src/vscode/index.ts
15078
+ var vscode_exports = {};
15079
+ __export(vscode_exports, {
15080
+ IdeClient: () => ide_client_exports,
15081
+ Lockfile: () => lockfile_exports,
15082
+ WsTransport: () => ws_transport_exports
14583
15083
  });
14584
- import { relative as relative4 } from "path";
14585
- import { useEffect as useEffect2, useRef as useRef2 } from "react";
14586
15084
 
14587
15085
  // src/vscode/ide-client.ts
14588
15086
  var ide_client_exports = {};
@@ -14600,7 +15098,7 @@ __export(lockfile_exports, {
14600
15098
  });
14601
15099
  import { readdir as readdir2, readFile as readFile5 } from "fs/promises";
14602
15100
  import { homedir as homedir10 } from "os";
14603
- import { join as join25, resolve as resolve11, sep as sep2 } from "path";
15101
+ import { join as join26, resolve as resolve11, sep as sep2 } from "path";
14604
15102
  import { z as z21 } from "zod";
14605
15103
  var log34 = createChildLogger({ module: "vscode" });
14606
15104
  var LockfileSchema = z21.object({
@@ -14611,8 +15109,8 @@ var LockfileSchema = z21.object({
14611
15109
  authToken: z21.string().optional()
14612
15110
  });
14613
15111
  function ideLockDir() {
14614
- const configDir = process.env.CLAUDE_CONFIG_DIR ?? join25(homedir10(), ".claude");
14615
- return join25(configDir, "ide");
15112
+ const configDir = process.env.CLAUDE_CONFIG_DIR ?? join26(homedir10(), ".claude");
15113
+ return join26(configDir, "ide");
14616
15114
  }
14617
15115
  function cwdInWorkspace(cwd2, workspaceFolders) {
14618
15116
  const normalizedCwd = cwd2.normalize("NFC");
@@ -14630,7 +15128,7 @@ async function readLockfile(dir, filename) {
14630
15128
  return null;
14631
15129
  }
14632
15130
  try {
14633
- const content = await readFile5(join25(dir, filename), "utf-8");
15131
+ const content = await readFile5(join26(dir, filename), "utf-8");
14634
15132
  const parsed = LockfileSchema.parse(JSON.parse(content));
14635
15133
  if (parsed.transport !== "ws") {
14636
15134
  return null;
@@ -14847,81 +15345,6 @@ async function connectToIde(opts) {
14847
15345
  };
14848
15346
  }
14849
15347
 
14850
- // src/ui/use-ide-input.ts
14851
- function useIdeInput(workDir) {
14852
- const insertInputTextRef = useRef2(null);
14853
- const clearInputRef = useRef2(null);
14854
- useEffect2(() => {
14855
- let connection = null;
14856
- let cancelled = false;
14857
- const controller = new AbortController();
14858
- void connectToIde({
14859
- cwd: workDir,
14860
- signal: controller.signal,
14861
- onAtMentioned: ({ filePath, lineStart, lineEnd }) => {
14862
- const rel = relative4(workDir, filePath);
14863
- const shown = rel && !rel.startsWith("..") ? rel : filePath;
14864
- let mention = `@${shown}`;
14865
- if (lineStart !== void 0) {
14866
- mention += `#L${String(lineStart)}`;
14867
- if (lineEnd !== void 0 && lineEnd !== lineStart) {
14868
- mention += `-${String(lineEnd)}`;
14869
- }
14870
- }
14871
- insertInputTextRef.current?.(mention + " ");
14872
- }
14873
- }).then((connected) => {
14874
- if (!connected) {
14875
- return;
14876
- }
14877
- if (cancelled) {
14878
- void connected.close();
14879
- return;
14880
- }
14881
- connection = connected;
14882
- });
14883
- return () => {
14884
- cancelled = true;
14885
- controller.abort();
14886
- void connection?.close();
14887
- };
14888
- }, [workDir]);
14889
- return { insertInputTextRef, clearInputRef };
14890
- }
14891
-
14892
- // src/ui/use-teammate-states.ts
14893
- var use_teammate_states_exports = {};
14894
- __export(use_teammate_states_exports, {
14895
- useTeammateStates: () => useTeammateStates
14896
- });
14897
- import { useEffect as useEffect3, useState as useState2 } from "react";
14898
- function useTeammateStates(manager) {
14899
- const [states, setStates] = useState2([]);
14900
- useEffect3(() => {
14901
- let signature = "";
14902
- const timer = setInterval(() => {
14903
- const next = manager.getAllTeammateStates();
14904
- const nextSignature = JSON.stringify(next);
14905
- if (nextSignature !== signature) {
14906
- signature = nextSignature;
14907
- setStates(next);
14908
- }
14909
- }, 500);
14910
- return () => {
14911
- clearInterval(timer);
14912
- };
14913
- }, [manager]);
14914
- return states;
14915
- }
14916
-
14917
- // src/vscode/index.ts
14918
- var vscode_exports = {};
14919
- __export(vscode_exports, {
14920
- IdeClient: () => ide_client_exports,
14921
- Lockfile: () => lockfile_exports,
14922
- WsTransport: () => ws_transport_exports
14923
- });
14924
-
14925
15348
  // src/print-mode.ts
14926
15349
  var print_mode_exports = {};
14927
15350
  __export(print_mode_exports, {
@@ -14975,6 +15398,7 @@ async function runPrintMode(args) {
14975
15398
  registry.register(new ToolSearchTool(registry));
14976
15399
  const teamManager = new TeamManager(workDir);
14977
15400
  const backgroundTaskManager = new TaskManager();
15401
+ attachBackgroundTaskManager(registry, backgroundTaskManager);
14978
15402
  registry.register(new TeamCreateTool(teamManager));
14979
15403
  registry.register(new SendMessageTool(teamManager));
14980
15404
  registry.register(new TeamDeleteTool(teamManager));
@@ -15036,7 +15460,8 @@ async function runPrintMode(args) {
15036
15460
  onEvent,
15037
15461
  void 0,
15038
15462
  teamChecker,
15039
- { abortSignal }
15463
+ // Teammates stay purely foreground: see SubagentRunOptions.backgroundTasks.
15464
+ { abortSignal, backgroundTasks: false }
15040
15465
  ),
15041
15466
  provider.base_url
15042
15467
  );
@@ -15124,7 +15549,7 @@ Error: ${event.error.message}`);
15124
15549
  break;
15125
15550
  }
15126
15551
  }
15127
- await backgroundTaskManager.waitAll();
15552
+ await backgroundTaskManager.waitAll((task) => (task.kind ?? "agent") === "agent");
15128
15553
  const backgroundNotifications = backgroundTaskManager.drainNotifications();
15129
15554
  const durationMs = Date.now() - startTime;
15130
15555
  if (args.outputFormat === "text" && resultText && !resultText.endsWith("\n")) {
@@ -15217,13 +15642,13 @@ __export(recover_exports, {
15217
15642
  recordExit: () => recordExit,
15218
15643
  recover: () => recover
15219
15644
  });
15220
- import { appendFileSync, mkdirSync as mkdirSync16 } from "fs";
15221
- import { join as join26 } from "path";
15645
+ import { appendFileSync, mkdirSync as mkdirSync17 } from "fs";
15646
+ import { join as join27 } from "path";
15222
15647
  var LOG_DIR = ".swifty";
15223
- var LOG_PATH = join26(LOG_DIR, "crash.log");
15648
+ var LOG_PATH = join27(LOG_DIR, "crash.log");
15224
15649
  function record(text) {
15225
15650
  try {
15226
- mkdirSync16(LOG_DIR, { recursive: true });
15651
+ mkdirSync17(LOG_DIR, { recursive: true });
15227
15652
  appendFileSync(LOG_PATH, `[${(/* @__PURE__ */ new Date()).toISOString()}] ${text}
15228
15653
  `, "utf8");
15229
15654
  } catch {
@@ -15303,7 +15728,7 @@ __export(teammate_exports, {
15303
15728
  parseTeammateFlags: () => parseTeammateFlags,
15304
15729
  runTeammate: () => runTeammate
15305
15730
  });
15306
- import { basename as basename6, dirname as dirname10, join as join27 } from "path";
15731
+ import { basename as basename6, dirname as dirname10, join as join28 } from "path";
15307
15732
  function parseTeammateFlags(args) {
15308
15733
  if (!args.includes("--teammate")) {
15309
15734
  return null;
@@ -15399,7 +15824,7 @@ async function runTeammate(args) {
15399
15824
  initLogger({
15400
15825
  sessionId: `teammate-${safeMemberName}-${Date.now().toString(36)}`,
15401
15826
  mode: "teammate",
15402
- logDir: join27(dirname10(args.teamDir), "logs"),
15827
+ logDir: join28(dirname10(args.teamDir), "logs"),
15403
15828
  skipCleanup: true
15404
15829
  });
15405
15830
  process.on("exit", closeLogger);
@@ -15538,7 +15963,6 @@ export {
15538
15963
  todo_exports as Todo,
15539
15964
  tool_result_exports as ToolResult,
15540
15965
  tools_exports3 as Tools,
15541
- ui_exports as UI,
15542
15966
  utils_exports as Utils,
15543
15967
  vscode_exports as VSCode,
15544
15968
  version_exports as Version,