@bubblebrain-ai/bubble 0.0.37 → 0.0.38

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 (51) hide show
  1. package/dist/agent/internal-reminder-sanitizer.js +2 -0
  2. package/dist/agent/task-lifecycle-reminder.d.ts +26 -0
  3. package/dist/agent/task-lifecycle-reminder.js +59 -0
  4. package/dist/agent.d.ts +26 -0
  5. package/dist/agent.js +39 -0
  6. package/dist/approval/controller.d.ts +3 -1
  7. package/dist/approval/controller.js +5 -2
  8. package/dist/approval/tool-helper.js +10 -1
  9. package/dist/approval/types.d.ts +15 -0
  10. package/dist/config.d.ts +6 -0
  11. package/dist/config.js +4 -0
  12. package/dist/loop/engine.d.ts +29 -0
  13. package/dist/loop/engine.js +63 -0
  14. package/dist/main.js +29 -0
  15. package/dist/orchestrator/default-hooks.js +16 -0
  16. package/dist/session-types.d.ts +1 -1
  17. package/dist/tasks/manager.d.ts +136 -0
  18. package/dist/tasks/manager.js +530 -0
  19. package/dist/tasks/process-utils.d.ts +10 -0
  20. package/dist/tasks/process-utils.js +49 -0
  21. package/dist/tasks/promotion.d.ts +17 -0
  22. package/dist/tasks/promotion.js +30 -0
  23. package/dist/tasks/wake.d.ts +53 -0
  24. package/dist/tasks/wake.js +125 -0
  25. package/dist/tools/bash.d.ts +15 -1
  26. package/dist/tools/bash.js +103 -3
  27. package/dist/tools/child-tools.js +3 -1
  28. package/dist/tools/edit.js +4 -12
  29. package/dist/tools/index.d.ts +13 -0
  30. package/dist/tools/index.js +8 -1
  31. package/dist/tools/server-manager.d.ts +6 -36
  32. package/dist/tools/server-manager.js +6 -234
  33. package/dist/tools/task-tools.d.ts +10 -0
  34. package/dist/tools/task-tools.js +129 -0
  35. package/dist/tools/write.js +6 -12
  36. package/dist/tui-ink/app.d.ts +20 -2
  37. package/dist/tui-ink/app.js +307 -19
  38. package/dist/tui-ink/approval/approval-dialog.js +4 -4
  39. package/dist/tui-ink/display-history.d.ts +1 -1
  40. package/dist/tui-ink/footer.d.ts +3 -3
  41. package/dist/tui-ink/footer.js +3 -3
  42. package/dist/tui-ink/input-box.d.ts +6 -0
  43. package/dist/tui-ink/markdown.d.ts +10 -2
  44. package/dist/tui-ink/markdown.js +13 -12
  45. package/dist/tui-ink/message-list.js +14 -3
  46. package/dist/tui-ink/run.d.ts +6 -0
  47. package/dist/tui-ink/run.js +1 -1
  48. package/dist/tui-ink/subagent-inspector.d.ts +5 -1
  49. package/dist/tui-ink/subagent-inspector.js +23 -7
  50. package/dist/tui-ink/welcome.js +4 -0
  51. package/package.json +1 -1
@@ -0,0 +1,125 @@
1
+ /**
2
+ * Auto-resume wake decision + coalescing (background-tasks design §2.3b).
3
+ *
4
+ * Kept pure and out of the TUI so the guard rails are unit-testable:
5
+ * - debounce-before-fire coalescing (the idle input queue drains in ~0ms, so
6
+ * merging can only happen BEFORE the wake is submitted);
7
+ * - user preemption (queued user input or a running turn suppresses the wake
8
+ * — the state-change reminder covers the completion on the next turn);
9
+ * - config kill switch (tasks.auto_resume = false);
10
+ * - killed tasks never wake (killing is an explicit decision, not news).
11
+ */
12
+ export function shouldFireTaskWake(input) {
13
+ if (!input.autoResume)
14
+ return false;
15
+ if (input.exiting)
16
+ return false;
17
+ if (input.turnRunning)
18
+ return false;
19
+ if (input.queuedInputs > 0)
20
+ return false;
21
+ return true;
22
+ }
23
+ export function taskEligibleForWake(task) {
24
+ return task.status === "completed" || task.status === "failed";
25
+ }
26
+ export function formatTaskWakeSummary(tasks, outputTail) {
27
+ const lines = [
28
+ tasks.length === 1
29
+ ? "A background task you started has finished."
30
+ : `${tasks.length} background tasks you started have finished.`,
31
+ ];
32
+ for (const task of tasks) {
33
+ const elapsed = Math.round(((task.endedAt ?? Date.now()) - task.startedAt) / 1000);
34
+ lines.push(`- ${task.id}${task.description ? ` (${task.description})` : ""}: ${task.status}${task.exitCode != null ? ` exit ${task.exitCode}` : ""} in ${elapsed}s`);
35
+ lines.push(` command: ${task.command}`);
36
+ const tail = outputTail(task.id)?.trim();
37
+ if (tail) {
38
+ lines.push(" output tail:");
39
+ lines.push(...tail.split("\n").slice(-30).map((line) => ` ${line}`));
40
+ }
41
+ }
42
+ // Field-test lesson (2026-07-19): an unconditional "continue the work"
43
+ // nudge pushed the model to invent follow-up work when nothing was left.
44
+ lines.push("If unfinished work depends on these results, continue it now. "
45
+ + "If nothing does, report the outcome to the user in one or two sentences and stop — do not invent new work. "
46
+ + "Never re-run what already finished.");
47
+ return lines.join("\n");
48
+ }
49
+ /**
50
+ * Resume-time orphan detection (design §2.2c): task_started markers with no
51
+ * matching task_finished/task_killed belong to a previous process. The caller
52
+ * probes pid liveness before reporting.
53
+ */
54
+ export function findDanglingTaskStarts(entries) {
55
+ const started = new Map();
56
+ for (const entry of entries) {
57
+ if (entry.type !== "marker" || !entry.value)
58
+ continue;
59
+ if (entry.kind === "task_started") {
60
+ const parsed = parseMarker(entry.value);
61
+ if (parsed?.id)
62
+ started.set(parsed.id, parsed);
63
+ }
64
+ else if (entry.kind === "task_finished" || entry.kind === "task_killed") {
65
+ const parsed = parseMarker(entry.value);
66
+ if (parsed?.id)
67
+ started.delete(parsed.id);
68
+ }
69
+ }
70
+ return [...started.values()];
71
+ }
72
+ export function isPidAlive(pid) {
73
+ if (!pid)
74
+ return false;
75
+ try {
76
+ process.kill(pid, 0);
77
+ return true;
78
+ }
79
+ catch {
80
+ return false;
81
+ }
82
+ }
83
+ function parseMarker(value) {
84
+ try {
85
+ const parsed = JSON.parse(value);
86
+ return parsed && typeof parsed === "object" ? parsed : undefined;
87
+ }
88
+ catch {
89
+ return undefined;
90
+ }
91
+ }
92
+ /**
93
+ * Debounce-then-fire coalescer: the first eligible completion arms a timer;
94
+ * completions inside the window fold into one wake payload.
95
+ */
96
+ export class TaskWakeCoalescer {
97
+ delayMs;
98
+ fire;
99
+ pending = [];
100
+ timer;
101
+ constructor(delayMs, fire) {
102
+ this.delayMs = delayMs;
103
+ this.fire = fire;
104
+ }
105
+ add(task) {
106
+ this.pending.push(task);
107
+ if (this.timer)
108
+ return;
109
+ this.timer = setTimeout(() => {
110
+ this.timer = undefined;
111
+ const tasks = this.pending.splice(0);
112
+ if (tasks.length > 0)
113
+ this.fire(tasks);
114
+ }, this.delayMs);
115
+ this.timer.unref?.();
116
+ }
117
+ /** Drop everything armed (session switch / exit). Returns what was pending. */
118
+ cancel() {
119
+ if (this.timer) {
120
+ clearTimeout(this.timer);
121
+ this.timer = undefined;
122
+ }
123
+ return this.pending.splice(0);
124
+ }
125
+ }
@@ -4,4 +4,18 @@
4
4
  import type { ApprovalController } from "../approval/types.js";
5
5
  import type { ToolRegistryEntry } from "../types.js";
6
6
  import type { FileStateTracker } from "./file-state.js";
7
- export declare function createBashTool(cwd: string, approval?: ApprovalController, _fileState?: FileStateTracker): ToolRegistryEntry;
7
+ import type { ProcessManager } from "../tasks/manager.js";
8
+ import type { PromotionChannel } from "../tasks/promotion.js";
9
+ export interface BashToolOptions {
10
+ /**
11
+ * Background tasks are a per-host capability (background-tasks design
12
+ * §2.0): only the interactive TUI wires the manager AND enables the flag.
13
+ * Print mode, feishu/desktop hosts, and worktree/workflow children leave
14
+ * this off, so run_in_background is rejected there with a clear error.
15
+ */
16
+ processManager?: ProcessManager;
17
+ allowBackgroundTasks?: boolean;
18
+ /** Ctrl+G send-to-background requests, keyed by toolCall id (design §2.5). */
19
+ promotionChannel?: PromotionChannel;
20
+ }
21
+ export declare function createBashTool(cwd: string, approval?: ApprovalController, _fileState?: FileStateTracker, options?: BashToolOptions): ToolRegistryEntry;
@@ -11,12 +11,17 @@ const MAX_OUTPUT = 50 * 1024;
11
11
  const POST_EXIT_STDIO_GRACE_MS = 150;
12
12
  const FORCE_KILL_AFTER_MS = 750;
13
13
  const ABORT_SETTLE_AFTER_MS = 1500;
14
- export function createBashTool(cwd, approval, _fileState) {
14
+ export function createBashTool(cwd, approval, _fileState, options = {}) {
15
+ const backgroundEnabled = options.allowBackgroundTasks === true && !!options.processManager;
15
16
  return {
16
17
  name: "bash",
17
18
  effect: "unknown",
18
19
  requiresApproval: true,
19
- description: "Execute a bounded bash command in the working directory. Use timeout for long-running commands. For persistent dev servers or watchers such as npm run dev, next dev, vite, or webpack --watch, use the managed server tools instead of backgrounding a bash command (deferred: load them first via tool_search with query 'select:start_server,server_status,server_logs,stop_server').",
20
+ description: "Execute a bounded bash command in the working directory. Use timeout for long-running commands. "
21
+ + (backgroundEnabled
22
+ ? "For long one-shot commands with a definite end (full test suites, release builds, downloads), pass run_in_background: true — the command keeps running while you continue working and you are notified when it finishes; never poll with foreground sleep. "
23
+ : "")
24
+ + "For persistent dev servers or watchers such as npm run dev, next dev, vite, or webpack --watch, use the managed server tools instead of backgrounding a bash command (deferred: load them first via tool_search with query 'select:start_server,server_status,server_logs,stop_server').",
20
25
  parameters: {
21
26
  type: "object",
22
27
  properties: {
@@ -26,6 +31,14 @@ export function createBashTool(cwd, approval, _fileState) {
26
31
  description: "One short sentence (5-10 words) describing what this command does, shown to the user in the UI. Write it in the same language the user is conversing in.",
27
32
  },
28
33
  timeout: { type: "number", description: "Timeout in seconds (optional)" },
34
+ ...(backgroundEnabled
35
+ ? {
36
+ run_in_background: {
37
+ type: "boolean",
38
+ description: "Run as a background task: returns a task id immediately and the command keeps running while you continue. Only for long one-shot commands with a definite end; not for quick commands or persistent servers.",
39
+ },
40
+ }
41
+ : {}),
29
42
  },
30
43
  required: ["command"],
31
44
  },
@@ -35,6 +48,7 @@ export function createBashTool(cwd, approval, _fileState) {
35
48
  }
36
49
  const command = String(args.command);
37
50
  const timeoutSec = typeof args.timeout === "number" ? args.timeout : 60;
51
+ const runInBackground = args.run_in_background === true;
38
52
  const parsedSearch = parseSearchBashCommand(command);
39
53
  const parsedRead = parsedSearch ? undefined : parseReadBashCommand(command);
40
54
  if (referencesSensitivePath(command)) {
@@ -48,12 +62,50 @@ export function createBashTool(cwd, approval, _fileState) {
48
62
  },
49
63
  };
50
64
  }
51
- const gate = await gateToolAction(approval, { type: "bash", command, cwd });
65
+ if (runInBackground && !backgroundEnabled) {
66
+ return {
67
+ content: "Error: run_in_background is unavailable in this session (non-interactive host or subagent). "
68
+ + "Run the command in the foreground with an explicit timeout instead.",
69
+ isError: true,
70
+ status: "blocked",
71
+ metadata: { kind: "shell", reason: "background_unavailable" },
72
+ };
73
+ }
74
+ const gate = await gateToolAction(approval, {
75
+ type: "bash",
76
+ command,
77
+ cwd,
78
+ ...(runInBackground ? { background: true } : {}),
79
+ });
52
80
  if (!gate.approved)
53
81
  return gate.result;
82
+ if (runInBackground) {
83
+ try {
84
+ const task = options.processManager.startTask({
85
+ command,
86
+ description: typeof args.description === "string" ? args.description : undefined,
87
+ cwd: ctx.cwd || cwd,
88
+ ownerSessionId: ctx.sessionID,
89
+ });
90
+ // Make the read/kill path available immediately — no tool_search hop
91
+ // (design §2.1: programmatic unlock at first spawn).
92
+ ctx.agent?.unlockDeferredTools?.(["task_output", "kill_task"]);
93
+ return {
94
+ content: `Started background task ${task.id}${task.description ? ` (${task.description})` : ""}. `
95
+ + "It runs while you continue working; you will be notified when it finishes. "
96
+ + "Use task_output to check on it, kill_task to stop it.",
97
+ status: "success",
98
+ metadata: { kind: "shell", command, taskId: task.id, background: true },
99
+ };
100
+ }
101
+ catch (error) {
102
+ return { content: `Error: ${error?.message ?? String(error)}`, isError: true };
103
+ }
104
+ }
54
105
  return new Promise((resolve) => {
55
106
  const shell = platform() === "win32" ? "cmd.exe" : "bash";
56
107
  const shellArgs = platform() === "win32" ? ["/c", command] : ["-c", command];
108
+ const spawnedAt = Date.now();
57
109
  const child = spawn(shell, shellArgs, {
58
110
  cwd,
59
111
  stdio: ["ignore", "pipe", "pipe"],
@@ -63,6 +115,8 @@ export function createBashTool(cwd, approval, _fileState) {
63
115
  });
64
116
  let stdout = "";
65
117
  let stderr = "";
118
+ let promotedTaskId;
119
+ let unregisterPromotion;
66
120
  let stdoutTruncated = false;
67
121
  let stderrTruncated = false;
68
122
  let stdoutEnded = child.stdout === null;
@@ -127,6 +181,7 @@ export function createBashTool(cwd, approval, _fileState) {
127
181
  if (postExitHandle)
128
182
  clearTimeout(postExitHandle);
129
183
  ctx.abortSignal?.removeEventListener("abort", abortChild);
184
+ unregisterPromotion?.();
130
185
  child.stdout?.removeListener("data", onStdoutData);
131
186
  child.stderr?.removeListener("data", onStderrData);
132
187
  child.stdout?.removeListener("end", onStdoutEnd);
@@ -152,6 +207,17 @@ export function createBashTool(cwd, approval, _fileState) {
152
207
  return;
153
208
  resolved = true;
154
209
  cleanup();
210
+ if (terminal === "promoted") {
211
+ // Ctrl+G promotion (design §2.5): the process lives on under the
212
+ // task manager — skip the group kill AND the stream destruction
213
+ // (the manager attached its own data listeners in adoptTask).
214
+ resolve({
215
+ content: buildOutput(`[Moved to background: ${promotedTaskId}. The command keeps running; check it with task_output.]`),
216
+ status: "success",
217
+ metadata: { kind: "shell", command, taskId: promotedTaskId, background: true },
218
+ });
219
+ return;
220
+ }
155
221
  cleanupBackgroundGroup();
156
222
  destroyStreams();
157
223
  if (terminal === "error") {
@@ -281,6 +347,40 @@ export function createBashTool(cwd, approval, _fileState) {
281
347
  terminal = "exit";
282
348
  finish();
283
349
  };
350
+ // Ctrl+G promotion (design §2.5): flag-flip into the task manager.
351
+ // Registered only for background-capable hosts; a no-op once any
352
+ // terminal state is set (benign race with exit/timeout/cancel).
353
+ const promoteToBackground = () => {
354
+ if (resolved || terminal)
355
+ return undefined;
356
+ if (!options.processManager)
357
+ return undefined;
358
+ try {
359
+ const task = options.processManager.adoptTask({
360
+ command,
361
+ description: typeof args.description === "string" ? args.description : undefined,
362
+ cwd: ctx.cwd || cwd,
363
+ ownerSessionId: ctx.sessionID,
364
+ child,
365
+ outputSoFar: [stdout && `stdout:\n${stdout}`, stderr && `stderr:\n${stderr}`]
366
+ .filter(Boolean)
367
+ .join(""),
368
+ startedAt: spawnedAt,
369
+ });
370
+ promotedTaskId = task.id;
371
+ }
372
+ catch {
373
+ // Task cap reached or adopt failed — keep running in the foreground.
374
+ return undefined;
375
+ }
376
+ terminal = "promoted";
377
+ ctx.agent?.unlockDeferredTools?.(["task_output", "kill_task"]);
378
+ finish();
379
+ return promotedTaskId;
380
+ };
381
+ if (backgroundEnabled && options.promotionChannel && ctx.toolCall?.id) {
382
+ unregisterPromotion = options.promotionChannel.register(ctx.toolCall.id, promoteToBackground);
383
+ }
284
384
  timeoutHandle = setTimeout(timeoutChild, timeoutSec * 1000);
285
385
  if (ctx.abortSignal?.aborted)
286
386
  abortChild();
@@ -65,9 +65,11 @@ export class WorktreeApprovalController {
65
65
  }
66
66
  case "edit":
67
67
  case "write":
68
+ // Reason before the path: subagent tool notes truncate long lines,
69
+ // and a long absolute path would push the reason off the end.
68
70
  return isPathInsideWorktree(this.worktreeRoot, req.path)
69
71
  ? { action: "approve" }
70
- : { action: "reject", feedback: `Blocked by worktree policy: ${req.path} is outside the subagent worktree.` };
72
+ : { action: "reject", feedback: `Blocked by worktree policy: the path is outside the subagent worktree (${req.path}).` };
71
73
  case "patch":
72
74
  return req.paths.every((path) => isPathInsideWorktree(this.worktreeRoot, path))
73
75
  ? { action: "approve" }
@@ -114,18 +114,9 @@ export function createEditTool(cwd, approval, lsp, fileState, checkpoints) {
114
114
  }
115
115
  }
116
116
  const filePath = resolveToolPath(cwd, args.path);
117
- if (!isWithinWorkspace(cwd, filePath)) {
118
- return {
119
- content: `Error: Edit path is outside the workspace: ${filePath}`,
120
- isError: true,
121
- status: "blocked",
122
- metadata: {
123
- kind: "security",
124
- path: filePath,
125
- reason: "Edit path is outside the workspace.",
126
- },
127
- };
128
- }
117
+ // Outside-workspace paths escalate the approval request instead of
118
+ // being blocked outright — see the matching note in write.ts.
119
+ const outsideWorkspace = !isWithinWorkspace(cwd, filePath);
129
120
  return withFileMutationQueue(filePath, async () => {
130
121
  try {
131
122
  await access(filePath, constants.R_OK | constants.W_OK);
@@ -161,6 +152,7 @@ export function createEditTool(cwd, approval, lsp, fileState, checkpoints) {
161
152
  path: filePath,
162
153
  diff,
163
154
  fileExists: true,
155
+ outsideWorkspace,
164
156
  });
165
157
  if (!gate.approved)
166
158
  return gate.result;
@@ -31,6 +31,8 @@ import type { QuestionController } from "../question/index.js";
31
31
  import type { CheckpointStore } from "../checkpoints.js";
32
32
  import { FileStateTracker } from "./file-state.js";
33
33
  import type { GoalStore } from "../goal/store.js";
34
+ import type { ProcessManager } from "../tasks/manager.js";
35
+ import type { PromotionChannel } from "../tasks/promotion.js";
34
36
  export interface CreateAllToolsOptions {
35
37
  todoStore?: TodoStore;
36
38
  planController?: PlanController;
@@ -47,5 +49,16 @@ export interface CreateAllToolsOptions {
47
49
  checkpoints?: () => CheckpointStore | undefined;
48
50
  /** Shared goal state; when present, registers the update_goal tool. */
49
51
  goalStore?: GoalStore;
52
+ /**
53
+ * Unified process manager (background tasks + managed servers). Background
54
+ * tasks are a per-host capability (background-tasks design §2.0): pass the
55
+ * manager AND set allowBackgroundTasks only in hosts that wire the full
56
+ * completion story (currently the interactive TUI). Without it, bash
57
+ * rejects run_in_background and the task tools are not registered.
58
+ */
59
+ processManager?: ProcessManager;
60
+ allowBackgroundTasks?: boolean;
61
+ /** Ctrl+G send-to-background requests from the TUI (design §2.5). */
62
+ promotionChannel?: PromotionChannel;
50
63
  }
51
64
  export declare function createAllTools(cwd: string, skillRegistry?: SkillRegistry, options?: CreateAllToolsOptions): ToolRegistryEntry[];
@@ -41,13 +41,20 @@ import { createQuestionTool } from "./question.js";
41
41
  import { createMemoryTool } from "./memory.js";
42
42
  import { FileStateTracker } from "./file-state.js";
43
43
  import { createGoalTools } from "../goal/tools.js";
44
+ import { createBackgroundTaskTools } from "./task-tools.js";
44
45
  export function createAllTools(cwd, skillRegistry, options = {}) {
45
46
  const approval = options.approvalController;
46
47
  const lsp = options.lspService ?? getLspService(cwd);
47
48
  const fileState = options.fileStateTracker ?? new FileStateTracker(cwd);
49
+ const backgroundTasks = options.allowBackgroundTasks === true && !!options.processManager;
48
50
  return [
49
51
  createReadTool(cwd, approval, lsp, fileState),
50
- createBashTool(cwd, approval, fileState),
52
+ createBashTool(cwd, approval, fileState, {
53
+ processManager: options.processManager,
54
+ allowBackgroundTasks: backgroundTasks,
55
+ promotionChannel: backgroundTasks ? options.promotionChannel : undefined,
56
+ }),
57
+ ...(backgroundTasks ? createBackgroundTaskTools(options.processManager) : []),
51
58
  ...createManagedServerTools(cwd, approval),
52
59
  createWriteTool(cwd, {}, approval, lsp, fileState, options.checkpoints),
53
60
  createEditTool(cwd, approval, lsp, fileState, options.checkpoints),
@@ -1,36 +1,6 @@
1
- export type ManagedServerPurpose = "preview" | "verification";
2
- export type ManagedServerLifecycle = "auto" | "keep_alive";
3
- export type ManagedServerStatus = "starting" | "ready" | "running" | "exited" | "failed" | "stopped";
4
- export interface ManagedServerInfo {
5
- id: string;
6
- command: string;
7
- cwd: string;
8
- port?: number;
9
- url?: string;
10
- ownerSessionId?: string;
11
- ownerRunId?: string;
12
- purpose: ManagedServerPurpose;
13
- lifecycle: ManagedServerLifecycle;
14
- startedAt: number;
15
- lastUsedAt: number;
16
- status: ManagedServerStatus;
17
- pid?: number;
18
- exitCode?: number | null;
19
- }
20
- export interface StartManagedServerInput {
21
- command: string;
22
- cwd: string;
23
- port?: number;
24
- readinessUrl?: string;
25
- timeoutSec?: number;
26
- ownerSessionId?: string;
27
- ownerRunId?: string;
28
- purpose?: ManagedServerPurpose;
29
- lifecycle?: ManagedServerLifecycle;
30
- }
31
- export declare function startManagedServer(input: StartManagedServerInput): Promise<ManagedServerInfo>;
32
- export declare function listManagedServers(): ManagedServerInfo[];
33
- export declare function getManagedServer(id: string): ManagedServerInfo | undefined;
34
- export declare function getManagedServerLogs(id: string, maxChars?: number): string | undefined;
35
- export declare function stopManagedServer(id: string): Promise<ManagedServerInfo | undefined>;
36
- export declare function stopAutoServersForSession(sessionID?: string): Promise<void>;
1
+ /**
2
+ * Compatibility shim: the managed-server runtime moved into the unified
3
+ * process manager (src/tasks/manager.ts, background-tasks design §2.2).
4
+ * Existing imports — server tools, agent.ts, tests — keep working unchanged.
5
+ */
6
+ export { getManagedServer, getManagedServerLogs, listManagedServers, startManagedServer, stopAutoServersForSession, stopManagedServer, type ManagedServerInfo, type ManagedServerLifecycle, type ManagedServerPurpose, type ManagedServerStatus, type StartManagedServerInput, } from "../tasks/manager.js";