@dungle-scrubs/tallow 0.8.21 → 0.8.23

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 (217) hide show
  1. package/dist/cli.js +35 -4
  2. package/dist/cli.js.map +1 -1
  3. package/dist/config.d.ts +1 -1
  4. package/dist/config.js +1 -1
  5. package/dist/interactive-mode-patch.d.ts +2 -0
  6. package/dist/interactive-mode-patch.d.ts.map +1 -1
  7. package/dist/interactive-mode-patch.js +82 -0
  8. package/dist/interactive-mode-patch.js.map +1 -1
  9. package/dist/sdk.d.ts +17 -0
  10. package/dist/sdk.d.ts.map +1 -1
  11. package/dist/sdk.js +68 -1
  12. package/dist/sdk.js.map +1 -1
  13. package/dist/workspace-transition-relay.d.ts +40 -7
  14. package/dist/workspace-transition-relay.d.ts.map +1 -1
  15. package/dist/workspace-transition-relay.js +81 -16
  16. package/dist/workspace-transition-relay.js.map +1 -1
  17. package/extensions/__integration__/background-task-widget-ownership.test.ts +216 -0
  18. package/extensions/__integration__/claude-hooks-compat.test.ts +156 -0
  19. package/extensions/__integration__/slash-command-bridge.test.ts +169 -23
  20. package/extensions/_shared/atomic-write.ts +1 -1
  21. package/extensions/_shared/bordered-box.ts +102 -0
  22. package/extensions/_shared/interop-events.ts +5 -0
  23. package/extensions/_shared/pid-registry.ts +1 -1
  24. package/extensions/agent-commands-tool/index.ts +4 -1
  25. package/extensions/background-task-tool/__tests__/lifecycle.test.ts +50 -25
  26. package/extensions/background-task-tool/index.ts +139 -221
  27. package/extensions/bash-tool-enhanced/index.ts +1 -75
  28. package/extensions/cd-tool/index.ts +2 -2
  29. package/extensions/context-fork/spawn.ts +4 -1
  30. package/extensions/health/index.ts +6 -6
  31. package/extensions/hooks/__tests__/claude-compat.test.ts +35 -0
  32. package/extensions/hooks/__tests__/subprocess-hardening.test.ts +73 -0
  33. package/extensions/hooks/index.ts +27 -4
  34. package/extensions/loop/__tests__/loop.test.ts +168 -4
  35. package/extensions/loop/extension.json +6 -5
  36. package/extensions/loop/index.ts +242 -31
  37. package/extensions/plan-mode-tool/__tests__/agent-end-execution.test.ts +373 -0
  38. package/extensions/plan-mode-tool/index.ts +103 -41
  39. package/extensions/prompt-suggestions/__tests__/editor-compatibility.test.ts +42 -0
  40. package/extensions/prompt-suggestions/index.ts +41 -6
  41. package/extensions/slash-command-bridge/__tests__/slash-command-bridge.test.ts +267 -671
  42. package/extensions/slash-command-bridge/extension.json +1 -1
  43. package/extensions/slash-command-bridge/index.ts +230 -116
  44. package/extensions/subagent-tool/index.ts +2 -2
  45. package/extensions/subagent-tool/process.ts +4 -5
  46. package/extensions/tasks/commands/register-tasks-extension.ts +41 -0
  47. package/extensions/teams-tool/__tests__/peer-messaging.test.ts +29 -24
  48. package/extensions/teams-tool/dashboard.ts +3 -5
  49. package/extensions/teams-tool/dispatch/auto-dispatch.ts +18 -1
  50. package/extensions/teams-tool/tools/teammate-tools.ts +9 -6
  51. package/extensions/wezterm-pane-control/__tests__/index.test.ts +88 -4
  52. package/extensions/wezterm-pane-control/index.ts +113 -8
  53. package/package.json +6 -4
  54. package/packages/tallow-tui/README.md +51 -0
  55. package/packages/tallow-tui/dist/autocomplete.d.ts +48 -0
  56. package/packages/tallow-tui/dist/autocomplete.d.ts.map +1 -0
  57. package/packages/tallow-tui/dist/autocomplete.js +564 -0
  58. package/packages/tallow-tui/dist/autocomplete.js.map +1 -0
  59. package/packages/tallow-tui/dist/border-styles.d.ts +32 -0
  60. package/packages/tallow-tui/dist/border-styles.d.ts.map +1 -0
  61. package/packages/tallow-tui/dist/border-styles.js +46 -0
  62. package/packages/tallow-tui/dist/border-styles.js.map +1 -0
  63. package/packages/tallow-tui/dist/components/bordered-box.d.ts +52 -0
  64. package/packages/tallow-tui/dist/components/bordered-box.d.ts.map +1 -0
  65. package/packages/tallow-tui/dist/components/bordered-box.js +89 -0
  66. package/packages/tallow-tui/dist/components/bordered-box.js.map +1 -0
  67. package/packages/tallow-tui/dist/components/box.d.ts +22 -0
  68. package/packages/tallow-tui/dist/components/box.d.ts.map +1 -0
  69. package/packages/tallow-tui/dist/components/box.js +104 -0
  70. package/packages/tallow-tui/dist/components/box.js.map +1 -0
  71. package/packages/tallow-tui/dist/components/cancellable-loader.d.ts +22 -0
  72. package/packages/tallow-tui/dist/components/cancellable-loader.d.ts.map +1 -0
  73. package/packages/tallow-tui/dist/components/cancellable-loader.js +35 -0
  74. package/packages/tallow-tui/dist/components/cancellable-loader.js.map +1 -0
  75. package/packages/tallow-tui/dist/components/editor.d.ts +240 -0
  76. package/packages/tallow-tui/dist/components/editor.d.ts.map +1 -0
  77. package/packages/tallow-tui/dist/components/editor.js +1766 -0
  78. package/packages/tallow-tui/dist/components/editor.js.map +1 -0
  79. package/packages/tallow-tui/dist/components/image.d.ts +126 -0
  80. package/packages/tallow-tui/dist/components/image.d.ts.map +1 -0
  81. package/packages/tallow-tui/dist/components/image.js +245 -0
  82. package/packages/tallow-tui/dist/components/image.js.map +1 -0
  83. package/packages/tallow-tui/dist/components/input.d.ts +37 -0
  84. package/packages/tallow-tui/dist/components/input.d.ts.map +1 -0
  85. package/packages/tallow-tui/dist/components/input.js +439 -0
  86. package/packages/tallow-tui/dist/components/input.js.map +1 -0
  87. package/packages/tallow-tui/dist/components/loader.d.ts +88 -0
  88. package/packages/tallow-tui/dist/components/loader.d.ts.map +1 -0
  89. package/packages/tallow-tui/dist/components/loader.js +146 -0
  90. package/packages/tallow-tui/dist/components/loader.js.map +1 -0
  91. package/packages/tallow-tui/dist/components/markdown.d.ts +95 -0
  92. package/packages/tallow-tui/dist/components/markdown.d.ts.map +1 -0
  93. package/packages/tallow-tui/dist/components/markdown.js +633 -0
  94. package/packages/tallow-tui/dist/components/markdown.js.map +1 -0
  95. package/packages/tallow-tui/dist/components/select-list.d.ts +32 -0
  96. package/packages/tallow-tui/dist/components/select-list.d.ts.map +1 -0
  97. package/packages/tallow-tui/dist/components/select-list.js +156 -0
  98. package/packages/tallow-tui/dist/components/select-list.js.map +1 -0
  99. package/packages/tallow-tui/dist/components/settings-list.d.ts +50 -0
  100. package/packages/tallow-tui/dist/components/settings-list.d.ts.map +1 -0
  101. package/packages/tallow-tui/dist/components/settings-list.js +189 -0
  102. package/packages/tallow-tui/dist/components/settings-list.js.map +1 -0
  103. package/packages/tallow-tui/dist/components/spacer.d.ts +12 -0
  104. package/packages/tallow-tui/dist/components/spacer.d.ts.map +1 -0
  105. package/packages/tallow-tui/dist/components/spacer.js +23 -0
  106. package/packages/tallow-tui/dist/components/spacer.js.map +1 -0
  107. package/packages/tallow-tui/dist/components/text.d.ts +19 -0
  108. package/packages/tallow-tui/dist/components/text.d.ts.map +1 -0
  109. package/packages/tallow-tui/dist/components/text.js +91 -0
  110. package/packages/tallow-tui/dist/components/text.js.map +1 -0
  111. package/packages/tallow-tui/dist/components/truncated-text.d.ts +13 -0
  112. package/packages/tallow-tui/dist/components/truncated-text.d.ts.map +1 -0
  113. package/packages/tallow-tui/dist/components/truncated-text.js +51 -0
  114. package/packages/tallow-tui/dist/components/truncated-text.js.map +1 -0
  115. package/packages/tallow-tui/dist/editor-component.d.ts +50 -0
  116. package/packages/tallow-tui/dist/editor-component.d.ts.map +1 -0
  117. package/packages/tallow-tui/dist/editor-component.js +2 -0
  118. package/packages/tallow-tui/dist/editor-component.js.map +1 -0
  119. package/packages/tallow-tui/dist/fuzzy.d.ts +16 -0
  120. package/packages/tallow-tui/dist/fuzzy.d.ts.map +1 -0
  121. package/packages/tallow-tui/dist/fuzzy.js +107 -0
  122. package/packages/tallow-tui/dist/fuzzy.js.map +1 -0
  123. package/packages/tallow-tui/dist/index.d.ts +25 -0
  124. package/packages/tallow-tui/dist/index.d.ts.map +1 -0
  125. package/packages/tallow-tui/dist/index.js +35 -0
  126. package/packages/tallow-tui/dist/index.js.map +1 -0
  127. package/packages/tallow-tui/dist/keybindings.d.ts +39 -0
  128. package/packages/tallow-tui/dist/keybindings.d.ts.map +1 -0
  129. package/packages/tallow-tui/dist/keybindings.js +114 -0
  130. package/packages/tallow-tui/dist/keybindings.js.map +1 -0
  131. package/packages/tallow-tui/dist/keys.d.ts +168 -0
  132. package/packages/tallow-tui/dist/keys.d.ts.map +1 -0
  133. package/packages/tallow-tui/dist/keys.js +971 -0
  134. package/packages/tallow-tui/dist/keys.js.map +1 -0
  135. package/packages/tallow-tui/dist/kill-ring.d.ts +28 -0
  136. package/packages/tallow-tui/dist/kill-ring.d.ts.map +1 -0
  137. package/packages/tallow-tui/dist/kill-ring.js +44 -0
  138. package/packages/tallow-tui/dist/kill-ring.js.map +1 -0
  139. package/packages/tallow-tui/dist/stdin-buffer.d.ts +48 -0
  140. package/packages/tallow-tui/dist/stdin-buffer.d.ts.map +1 -0
  141. package/packages/tallow-tui/dist/stdin-buffer.js +317 -0
  142. package/packages/tallow-tui/dist/stdin-buffer.js.map +1 -0
  143. package/packages/tallow-tui/dist/terminal-image.d.ts +161 -0
  144. package/packages/tallow-tui/dist/terminal-image.d.ts.map +1 -0
  145. package/packages/tallow-tui/dist/terminal-image.js +460 -0
  146. package/packages/tallow-tui/dist/terminal-image.js.map +1 -0
  147. package/packages/tallow-tui/dist/terminal.d.ts +102 -0
  148. package/packages/tallow-tui/dist/terminal.d.ts.map +1 -0
  149. package/packages/tallow-tui/dist/terminal.js +263 -0
  150. package/packages/tallow-tui/dist/terminal.js.map +1 -0
  151. package/packages/tallow-tui/dist/test-utils/capability-env.d.ts +14 -0
  152. package/packages/tallow-tui/dist/test-utils/capability-env.d.ts.map +1 -0
  153. package/packages/tallow-tui/dist/test-utils/capability-env.js +55 -0
  154. package/packages/tallow-tui/dist/test-utils/capability-env.js.map +1 -0
  155. package/packages/tallow-tui/dist/tui.d.ts +239 -0
  156. package/packages/tallow-tui/dist/tui.d.ts.map +1 -0
  157. package/packages/tallow-tui/dist/tui.js +1058 -0
  158. package/packages/tallow-tui/dist/tui.js.map +1 -0
  159. package/packages/tallow-tui/dist/undo-stack.d.ts +17 -0
  160. package/packages/tallow-tui/dist/undo-stack.d.ts.map +1 -0
  161. package/packages/tallow-tui/dist/undo-stack.js +25 -0
  162. package/packages/tallow-tui/dist/undo-stack.js.map +1 -0
  163. package/packages/tallow-tui/dist/utils.d.ts +96 -0
  164. package/packages/tallow-tui/dist/utils.d.ts.map +1 -0
  165. package/packages/tallow-tui/dist/utils.js +843 -0
  166. package/packages/tallow-tui/dist/utils.js.map +1 -0
  167. package/packages/tallow-tui/package.json +24 -0
  168. package/packages/tallow-tui/src/__tests__/__snapshots__/render.test.ts.snap +121 -0
  169. package/packages/tallow-tui/src/__tests__/editor-border.test.ts +72 -0
  170. package/packages/tallow-tui/src/__tests__/editor-change-listener.test.ts +121 -0
  171. package/packages/tallow-tui/src/__tests__/editor-ghost-text.test.ts +112 -0
  172. package/packages/tallow-tui/src/__tests__/fuzzy.test.ts +91 -0
  173. package/packages/tallow-tui/src/__tests__/image-component.test.ts +113 -0
  174. package/packages/tallow-tui/src/__tests__/keys.test.ts +141 -0
  175. package/packages/tallow-tui/src/__tests__/render.test.ts +179 -0
  176. package/packages/tallow-tui/src/__tests__/stdin-buffer.test.ts +82 -0
  177. package/packages/tallow-tui/src/__tests__/terminal-image.test.ts +363 -0
  178. package/packages/tallow-tui/src/__tests__/tui-diff-regression.test.ts +454 -0
  179. package/packages/tallow-tui/src/__tests__/tui-render-scheduling.test.ts +256 -0
  180. package/packages/tallow-tui/src/__tests__/utils.test.ts +259 -0
  181. package/packages/tallow-tui/src/autocomplete.ts +716 -0
  182. package/packages/tallow-tui/src/border-styles.ts +60 -0
  183. package/packages/tallow-tui/src/components/bordered-box.ts +113 -0
  184. package/packages/tallow-tui/src/components/box.ts +137 -0
  185. package/packages/tallow-tui/src/components/cancellable-loader.ts +40 -0
  186. package/packages/tallow-tui/src/components/editor.ts +2143 -0
  187. package/packages/tallow-tui/src/components/image.ts +315 -0
  188. package/packages/tallow-tui/src/components/input.ts +522 -0
  189. package/packages/tallow-tui/src/components/loader.ts +187 -0
  190. package/packages/tallow-tui/src/components/markdown.ts +780 -0
  191. package/packages/tallow-tui/src/components/select-list.ts +197 -0
  192. package/packages/tallow-tui/src/components/settings-list.ts +264 -0
  193. package/packages/tallow-tui/src/components/spacer.ts +28 -0
  194. package/packages/tallow-tui/src/components/text.ts +113 -0
  195. package/packages/tallow-tui/src/components/truncated-text.ts +65 -0
  196. package/packages/tallow-tui/src/editor-component.ts +92 -0
  197. package/packages/tallow-tui/src/fuzzy.ts +133 -0
  198. package/packages/tallow-tui/src/index.ts +118 -0
  199. package/packages/tallow-tui/src/keybindings.ts +183 -0
  200. package/packages/tallow-tui/src/keys.ts +1189 -0
  201. package/packages/tallow-tui/src/kill-ring.ts +46 -0
  202. package/packages/tallow-tui/src/stdin-buffer.ts +386 -0
  203. package/packages/tallow-tui/src/terminal-image.ts +619 -0
  204. package/packages/tallow-tui/src/terminal.ts +350 -0
  205. package/packages/tallow-tui/src/test-utils/capability-env.ts +56 -0
  206. package/packages/tallow-tui/src/tui.ts +1336 -0
  207. package/packages/tallow-tui/src/undo-stack.ts +28 -0
  208. package/packages/tallow-tui/src/utils.ts +948 -0
  209. package/packages/tallow-tui/tsconfig.build.json +21 -0
  210. package/runtime/agent-runner.ts +20 -0
  211. package/runtime/atomic-write.ts +8 -0
  212. package/runtime/otel.ts +12 -0
  213. package/runtime/resolve-module.ts +23 -0
  214. package/runtime/runtime-path-provider.ts +12 -0
  215. package/runtime/runtime-provenance.ts +17 -0
  216. package/runtime/workspace-transition-relay.ts +21 -0
  217. package/runtime/workspace-transition.ts +29 -0
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Lightweight bordered-box renderer for bundled extensions.
3
+ *
4
+ * This avoids depending on fork-only `@mariozechner/pi-tui` exports in the
5
+ * published package while preserving the same visual style where needed.
6
+ */
7
+
8
+ import { truncateToWidth, visibleWidth } from "@mariozechner/pi-tui";
9
+
10
+ interface BorderStyle {
11
+ readonly bottomLeft: string;
12
+ readonly bottomRight: string;
13
+ readonly horizontal: string;
14
+ readonly topLeft: string;
15
+ readonly topRight: string;
16
+ readonly vertical: string;
17
+ }
18
+
19
+ export interface RenderBorderedBoxOptions {
20
+ readonly borderColorFn?: (str: string) => string;
21
+ readonly paddingX?: number;
22
+ readonly style?: "rounded" | "sharp";
23
+ readonly title?: string;
24
+ readonly titleColorFn?: (str: string) => string;
25
+ }
26
+
27
+ const BORDER_STYLES = {
28
+ rounded: {
29
+ bottomLeft: "╰",
30
+ bottomRight: "╯",
31
+ horizontal: "─",
32
+ topLeft: "╭",
33
+ topRight: "╮",
34
+ vertical: "│",
35
+ },
36
+ sharp: {
37
+ bottomLeft: "└",
38
+ bottomRight: "┘",
39
+ horizontal: "─",
40
+ topLeft: "┌",
41
+ topRight: "┐",
42
+ vertical: "│",
43
+ },
44
+ } as const satisfies Record<NonNullable<RenderBorderedBoxOptions["style"]>, BorderStyle>;
45
+
46
+ /**
47
+ * Render content lines inside a bordered box.
48
+ *
49
+ * @param contentLines - Pre-rendered content to wrap
50
+ * @param width - Available render width
51
+ * @param options - Title, padding, border style, and color callbacks
52
+ * @returns Rendered border and body lines
53
+ */
54
+ export function renderBorderedBox(
55
+ contentLines: readonly string[],
56
+ width: number,
57
+ options: RenderBorderedBoxOptions = {}
58
+ ): string[] {
59
+ const style = BORDER_STYLES[options.style ?? "sharp"];
60
+ const padX = options.paddingX ?? 1;
61
+ const colorBorder = options.borderColorFn ?? ((str: string) => str);
62
+ const colorTitle = options.titleColorFn ?? ((str: string) => str);
63
+ const innerWidth = width - 2 - padX * 2;
64
+ if (innerWidth < 1) return [...contentLines];
65
+
66
+ const pad = " ".repeat(padX);
67
+ let topBar: string;
68
+ if (options.title) {
69
+ const title = ` ${colorTitle(options.title)} `;
70
+ const rightFill = Math.max(0, width - 3 - visibleWidth(title));
71
+ topBar =
72
+ colorBorder(style.topLeft) +
73
+ colorBorder(style.horizontal) +
74
+ title +
75
+ colorBorder(style.horizontal.repeat(rightFill)) +
76
+ colorBorder(style.topRight);
77
+ } else {
78
+ topBar =
79
+ colorBorder(style.topLeft) +
80
+ colorBorder(style.horizontal.repeat(width - 2)) +
81
+ colorBorder(style.topRight);
82
+ }
83
+
84
+ const bodyLines = contentLines.map((line) => {
85
+ const clamped = visibleWidth(line) > innerWidth ? truncateToWidth(line, innerWidth) : line;
86
+ const fill = Math.max(0, innerWidth - visibleWidth(clamped));
87
+ return (
88
+ colorBorder(style.vertical) +
89
+ pad +
90
+ clamped +
91
+ " ".repeat(fill) +
92
+ pad +
93
+ colorBorder(style.vertical)
94
+ );
95
+ });
96
+
97
+ const bottomBar =
98
+ colorBorder(style.bottomLeft) +
99
+ colorBorder(style.horizontal.repeat(width - 2)) +
100
+ colorBorder(style.bottomRight);
101
+ return [topBar, ...bodyLines, bottomBar];
102
+ }
@@ -7,6 +7,7 @@ export const INTEROP_EVENT_SCHEMA_VERSION = 1 as const;
7
7
 
8
8
  /** Event channels used for typed cross-extension state synchronization. */
9
9
  export const INTEROP_EVENT_NAMES = {
10
+ backgroundTasksPresenterState: "interop.v1.background-tasks.presenter-state",
10
11
  backgroundTasksSnapshot: "interop.v1.background-tasks.snapshot",
11
12
  stateRequest: "interop.v1.state.request",
12
13
  subagentsSnapshot: "interop.v1.subagents.snapshot",
@@ -117,6 +118,10 @@ const InteropTeamViewSchema = Type.Object({
117
118
  });
118
119
 
119
120
  const InteropEventSchemas = {
121
+ [INTEROP_EVENT_NAMES.backgroundTasksPresenterState]: Type.Object({
122
+ active: Type.Boolean(),
123
+ schemaVersion: Type.Literal(INTEROP_EVENT_SCHEMA_VERSION),
124
+ }),
120
125
  [INTEROP_EVENT_NAMES.backgroundTasksSnapshot]: Type.Object({
121
126
  schemaVersion: Type.Literal(INTEROP_EVENT_SCHEMA_VERSION),
122
127
  tasks: Type.Array(InteropBackgroundTaskViewSchema),
@@ -15,7 +15,7 @@ import { dirname, join } from "node:path";
15
15
  import {
16
16
  createRuntimePathProvider,
17
17
  type RuntimePathProvider,
18
- } from "../../src/runtime-path-provider.js";
18
+ } from "../../runtime/runtime-path-provider.js";
19
19
  import { atomicWriteFileSync } from "./atomic-write.js";
20
20
  import { acquireFileLock } from "./file-lock.js";
21
21
 
@@ -11,7 +11,10 @@ import * as fs from "node:fs";
11
11
  import * as os from "node:os";
12
12
  import * as path from "node:path";
13
13
  import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
14
- import { DEFAULT_AGENT_RUNNER_ENV, spawnWithResolvedAgentRunner } from "../../src/agent-runner.js";
14
+ import {
15
+ DEFAULT_AGENT_RUNNER_ENV,
16
+ spawnWithResolvedAgentRunner,
17
+ } from "../../runtime/agent-runner.js";
15
18
  import { isProjectTrusted } from "../_shared/project-trust.js";
16
19
  import { getTallowHomeDir } from "../_shared/tallow-paths.js";
17
20
 
@@ -196,54 +196,70 @@ describe("Background task tool registration", () => {
196
196
  });
197
197
  });
198
198
 
199
- // ── bg_bash streaming mode ───────────────────────────────────────────────────
199
+ // ── bg_bash always-async mode ─────────────────────────────────────────────────
200
200
 
201
- describe("bg_bash streaming mode", () => {
202
- it("runs a simple command and captures output", async () => {
201
+ describe("bg_bash always-async mode", () => {
202
+ it("returns immediately with task ID", async () => {
203
203
  const bgBash = getTool(harness, "bg_bash");
204
204
  const result = await execTool(bgBash, { command: "echo hello world" });
205
205
 
206
- expect(firstText(result)).toContain("hello world");
207
- const details = result.details as { status?: string; exitCode?: number };
208
- expect(details.status).toBe("completed");
209
- expect(details.exitCode).toBe(0);
206
+ const details = result.details as { taskId?: string };
207
+ expect(details.taskId).toBeDefined();
208
+ expect(details.taskId).toMatch(/^bg_/);
209
+ expect(firstText(result)).toContain("Task ID:");
210
210
  });
211
211
 
212
- it("captures exit code from failing commands", async () => {
212
+ it("captures output retrievable via task_output", async () => {
213
213
  const bgBash = getTool(harness, "bg_bash");
214
- const result = await execTool(bgBash, { command: "exit 42" });
214
+ const taskOutput = getTool(harness, "task_output");
215
215
 
216
- const details = result.details as { status?: string; exitCode?: number };
217
- expect(details.status).toBe("failed");
218
- expect(details.exitCode).toBe(42);
216
+ const run = await execTool(bgBash, { command: "echo hello world" });
217
+ const taskId = (run.details as { taskId: string }).taskId;
218
+
219
+ // Wait for process to complete
220
+ await new Promise((resolve) => setTimeout(resolve, 100));
221
+
222
+ const output = await execTool(taskOutput, { taskId });
223
+ expect(firstText(output)).toContain("hello world");
219
224
  });
220
225
 
221
- it("reports duration in result details", async () => {
226
+ it("captures exit code from failing commands", async () => {
222
227
  const bgBash = getTool(harness, "bg_bash");
223
- const result = await execTool(bgBash, { command: "echo fast" });
228
+ const taskStatus = getTool(harness, "task_status");
224
229
 
225
- const details = result.details as { duration?: string };
226
- expect(details.duration).toBeDefined();
227
- expect(details.duration).toContain("s");
230
+ const run = await execTool(bgBash, { command: "exit 42" });
231
+ const taskId = (run.details as { taskId: string }).taskId;
232
+
233
+ await new Promise((resolve) => setTimeout(resolve, 100));
234
+
235
+ const status = await execTool(taskStatus, { taskId });
236
+ const details = status.details as { status?: string; exitCode?: number };
237
+ expect(details.status).toBe("failed");
238
+ expect(details.exitCode).toBe(42);
228
239
  });
229
240
 
230
241
  it("captures stderr in output", async () => {
231
242
  const bgBash = getTool(harness, "bg_bash");
232
- const result = await execTool(bgBash, { command: "echo error >&2" });
243
+ const taskOutput = getTool(harness, "task_output");
244
+
245
+ const run = await execTool(bgBash, { command: "echo error >&2" });
246
+ const taskId = (run.details as { taskId: string }).taskId;
247
+
248
+ await new Promise((resolve) => setTimeout(resolve, 100));
233
249
 
234
- expect(firstText(result)).toContain("error");
250
+ const output = await execTool(taskOutput, { taskId });
251
+ expect(firstText(output)).toContain("error");
235
252
  });
236
253
  });
237
254
 
238
255
  // ── bg_bash fire-and-forget mode ─────────────────────────────────────────────
239
256
 
240
- describe("bg_bash fire-and-forget mode", () => {
241
- it("returns immediately with task ID", async () => {
257
+ describe("bg_bash background flag (deprecated, always async)", () => {
258
+ it("background=true still returns immediately with task ID", async () => {
242
259
  const bgBash = getTool(harness, "bg_bash");
243
260
  const result = await execTool(bgBash, { command: "sleep 0.1", background: true });
244
261
 
245
- const details = result.details as { taskId?: string; fireAndForget?: boolean };
246
- expect(details.fireAndForget).toBe(true);
262
+ const details = result.details as { taskId?: string };
247
263
  expect(details.taskId).toBeDefined();
248
264
  expect(details.taskId).toMatch(/^bg_/);
249
265
  expect(firstText(result)).toContain("Task ID:");
@@ -255,7 +271,7 @@ describe("bg_bash fire-and-forget mode", () => {
255
271
  const taskStatus = getTool(harness, "task_status");
256
272
  const taskOutput = getTool(harness, "task_output");
257
273
 
258
- const run = await execTool(bgBash, { command: "echo ignored", background: true });
274
+ const run = await execTool(bgBash, { command: "echo ignored" });
259
275
  const taskId = (run.details as { taskId: string }).taskId;
260
276
 
261
277
  await new Promise((resolve) => setTimeout(resolve, 5));
@@ -271,7 +287,7 @@ describe("bg_bash fire-and-forget mode", () => {
271
287
  const bgBash = getTool(harness, "bg_bash");
272
288
  const taskStatus = getTool(harness, "task_status");
273
289
 
274
- const result = await execTool(bgBash, { command: "sleep 0.05", background: true });
290
+ const result = await execTool(bgBash, { command: "sleep 0.05" });
275
291
  const taskId = (result.details as { taskId: string }).taskId;
276
292
 
277
293
  const status = await execTool(taskStatus, { taskId });
@@ -291,6 +307,8 @@ describe("task_output", () => {
291
307
  const run = await execTool(bgBash, { command: "echo line1; echo line2" });
292
308
  const taskId = (run.details as { taskId: string }).taskId;
293
309
 
310
+ await new Promise((resolve) => setTimeout(resolve, 100));
311
+
294
312
  const output = await execTool(taskOutput, { taskId });
295
313
  expect(firstText(output)).toContain("line1");
296
314
  expect(firstText(output)).toContain("line2");
@@ -312,6 +330,8 @@ describe("task_output", () => {
312
330
  });
313
331
  const taskId = (run.details as { taskId: string }).taskId;
314
332
 
333
+ await new Promise((resolve) => setTimeout(resolve, 100));
334
+
315
335
  const output = await execTool(taskOutput, { taskId, tail: 3 });
316
336
  const text = firstText(output);
317
337
  expect(text).toContain("line10");
@@ -329,6 +349,8 @@ describe("task_status", () => {
329
349
  const run = await execTool(bgBash, { command: "echo done" });
330
350
  const taskId = (run.details as { taskId: string }).taskId;
331
351
 
352
+ await new Promise((resolve) => setTimeout(resolve, 100));
353
+
332
354
  const status = await execTool(taskStatus, { taskId });
333
355
  const details = status.details as { status?: string; exitCode?: number };
334
356
  expect(details.status).toBe("completed");
@@ -360,6 +382,9 @@ describe("task_kill", () => {
360
382
  const run = await execTool(bgBash, { command: "echo done" });
361
383
  const taskId = (run.details as { taskId: string }).taskId;
362
384
 
385
+ // Wait for the async process to complete
386
+ await new Promise((resolve) => setTimeout(resolve, 100));
387
+
363
388
  const result = await execTool(taskKill, { taskId });
364
389
  expect(firstText(result)).toContain("not running");
365
390
  });