@bubblebrain-ai/bubble 0.0.20 → 0.0.22

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 (98) hide show
  1. package/dist/agent/abort-errors.d.ts +14 -0
  2. package/dist/agent/abort-errors.js +21 -0
  3. package/dist/agent/budget-ledger.d.ts +41 -0
  4. package/dist/agent/budget-ledger.js +64 -0
  5. package/dist/agent/child-runner.d.ts +55 -0
  6. package/dist/agent/child-runner.js +312 -0
  7. package/dist/agent/profiles.d.ts +8 -0
  8. package/dist/agent/profiles.js +27 -5
  9. package/dist/agent/result-integrator.d.ts +22 -0
  10. package/dist/agent/result-integrator.js +50 -0
  11. package/dist/agent/subagent-control.d.ts +31 -0
  12. package/dist/agent/subagent-control.js +27 -0
  13. package/dist/agent/subagent-lifecycle-reminder.js +11 -2
  14. package/dist/agent/subagent-scheduler.d.ts +95 -0
  15. package/dist/agent/subagent-scheduler.js +256 -0
  16. package/dist/agent/subagent-store.d.ts +41 -0
  17. package/dist/agent/subagent-store.js +149 -0
  18. package/dist/agent/subagent-summary.d.ts +30 -0
  19. package/dist/agent/subagent-summary.js +74 -0
  20. package/dist/agent/worktree.d.ts +29 -0
  21. package/dist/agent/worktree.js +73 -0
  22. package/dist/agent.d.ts +64 -5
  23. package/dist/agent.js +365 -288
  24. package/dist/approval/controller.js +9 -1
  25. package/dist/approval/tool-helper.js +2 -0
  26. package/dist/approval/types.d.ts +17 -1
  27. package/dist/checkpoints.d.ts +57 -0
  28. package/dist/checkpoints.js +0 -0
  29. package/dist/config.d.ts +8 -0
  30. package/dist/config.js +17 -0
  31. package/dist/feishu/agent-host/approval-card.js +9 -0
  32. package/dist/feishu/agent-host/run-driver.js +2 -0
  33. package/dist/main.js +88 -13
  34. package/dist/network/errors.d.ts +28 -0
  35. package/dist/network/errors.js +24 -0
  36. package/dist/orchestrator/default-hooks.js +5 -1
  37. package/dist/prompt/compose.js +3 -0
  38. package/dist/prompt/delegation.d.ts +14 -0
  39. package/dist/prompt/delegation.js +64 -0
  40. package/dist/prompt/task-reminders.d.ts +5 -1
  41. package/dist/prompt/task-reminders.js +10 -2
  42. package/dist/provider-anthropic.js +23 -0
  43. package/dist/provider.js +23 -3
  44. package/dist/session.d.ts +31 -0
  45. package/dist/session.js +69 -0
  46. package/dist/slash-commands/commands.js +109 -2
  47. package/dist/slash-commands/types.d.ts +6 -0
  48. package/dist/tools/agent-lifecycle.d.ts +29 -3
  49. package/dist/tools/agent-lifecycle.js +394 -40
  50. package/dist/tools/bash.js +4 -0
  51. package/dist/tools/child-tools.d.ts +31 -0
  52. package/dist/tools/child-tools.js +106 -0
  53. package/dist/tools/edit.d.ts +2 -1
  54. package/dist/tools/edit.js +2 -1
  55. package/dist/tools/index.d.ts +7 -0
  56. package/dist/tools/index.js +3 -3
  57. package/dist/tools/write.d.ts +2 -1
  58. package/dist/tools/write.js +2 -1
  59. package/dist/tui/image-paste.d.ts +18 -0
  60. package/dist/tui/image-paste.js +60 -0
  61. package/dist/tui/run.d.ts +11 -1
  62. package/dist/tui/run.js +399 -71
  63. package/dist/tui/session-picker-data.d.ts +18 -0
  64. package/dist/tui/session-picker-data.js +21 -0
  65. package/dist/tui/trace-groups.d.ts +16 -0
  66. package/dist/tui/trace-groups.js +42 -1
  67. package/dist/tui/transcript-scroll.d.ts +25 -0
  68. package/dist/tui/transcript-scroll.js +20 -0
  69. package/dist/tui/wordmark.d.ts +2 -0
  70. package/dist/tui/wordmark.js +31 -4
  71. package/dist/tui-ink/app.d.ts +4 -1
  72. package/dist/tui-ink/app.js +301 -247
  73. package/dist/tui-ink/approval/approval-dialog.js +10 -0
  74. package/dist/tui-ink/display-history.d.ts +16 -1
  75. package/dist/tui-ink/display-history.js +50 -21
  76. package/dist/tui-ink/footer.d.ts +6 -12
  77. package/dist/tui-ink/footer.js +10 -29
  78. package/dist/tui-ink/image-paste.d.ts +59 -0
  79. package/dist/tui-ink/image-paste.js +277 -0
  80. package/dist/tui-ink/input-box.d.ts +26 -1
  81. package/dist/tui-ink/input-box.js +171 -41
  82. package/dist/tui-ink/message-list.d.ts +1 -1
  83. package/dist/tui-ink/message-list.js +46 -29
  84. package/dist/tui-ink/run.d.ts +7 -2
  85. package/dist/tui-ink/run.js +73 -23
  86. package/dist/tui-ink/terminal-mouse.d.ts +1 -0
  87. package/dist/tui-ink/terminal-mouse.js +4 -0
  88. package/dist/tui-ink/trace-groups.d.ts +16 -0
  89. package/dist/tui-ink/trace-groups.js +50 -2
  90. package/dist/tui-ink/transcript-viewport-math.d.ts +11 -0
  91. package/dist/tui-ink/transcript-viewport-math.js +17 -0
  92. package/dist/tui-ink/transcript-viewport.d.ts +24 -0
  93. package/dist/tui-ink/transcript-viewport.js +83 -0
  94. package/dist/tui-ink/welcome.d.ts +9 -7
  95. package/dist/tui-ink/welcome.js +7 -33
  96. package/dist/tui-opentui/approval/approval-dialog.js +10 -0
  97. package/dist/types.d.ts +17 -0
  98. package/package.json +1 -1
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Per-child tool factory for write_worktree subagents (design doc §8).
3
+ *
4
+ * Parent tools close over the parent cwd at creation, so a write child needs
5
+ * fresh instances bound to its worktree — with their own FileStateTracker —
6
+ * plus a worktree-scoped approval policy: file operations are runtime-checked
7
+ * to stay under the worktree root (the tools' own workspace fence does this
8
+ * structurally), bash auto-approves inside the worktree when the command
9
+ * passes a deny-list of escaping operations, and everything else fails fast.
10
+ */
11
+ import { isAbsolute, resolve, sep } from "node:path";
12
+ import { createBashTool } from "./bash.js";
13
+ import { createEditTool } from "./edit.js";
14
+ import { createGlobTool } from "./glob.js";
15
+ import { createGrepTool } from "./grep.js";
16
+ import { createReadTool } from "./read.js";
17
+ import { createWriteTool } from "./write.js";
18
+ import { FileStateTracker } from "./file-state.js";
19
+ /** Operations a worktree child may never run, regardless of cwd. */
20
+ const WORKTREE_BASH_DENY_PATTERNS = [
21
+ { pattern: /\bgit\s+push\b/, reason: "pushing from a subagent worktree is not allowed; the parent reviews and applies changes." },
22
+ { pattern: /\bgit\s+remote\b/, reason: "remote configuration is not allowed inside a subagent worktree." },
23
+ { pattern: /\bgit\s+worktree\b/, reason: "managing worktrees from inside a subagent worktree is not allowed." },
24
+ { pattern: /\bsudo\b/, reason: "privileged commands are not allowed inside a subagent worktree." },
25
+ ];
26
+ export function isPathInsideWorktree(worktreeRoot, candidate) {
27
+ const resolved = isAbsolute(candidate) ? resolve(candidate) : resolve(worktreeRoot, candidate);
28
+ const root = resolve(worktreeRoot);
29
+ return resolved === root || resolved.startsWith(root + sep);
30
+ }
31
+ /**
32
+ * Approval policy for a worktree child: containment is enforced by code
33
+ * (path checks, deny-list), never by prompt text. There is no interactive
34
+ * fallback — anything outside the policy fails fast (design §11).
35
+ */
36
+ export class WorktreeApprovalController {
37
+ worktreeRoot;
38
+ constructor(worktreeRoot) {
39
+ this.worktreeRoot = worktreeRoot;
40
+ }
41
+ async request(req) {
42
+ switch (req.type) {
43
+ case "bash": {
44
+ for (const { pattern, reason } of WORKTREE_BASH_DENY_PATTERNS) {
45
+ if (pattern.test(req.command)) {
46
+ return { action: "reject", feedback: `Blocked by worktree policy: ${reason}` };
47
+ }
48
+ }
49
+ if (!isPathInsideWorktree(this.worktreeRoot, req.cwd)) {
50
+ return { action: "reject", feedback: "Blocked by worktree policy: commands must run inside the subagent worktree." };
51
+ }
52
+ // Absolute paths reaching outside the worktree are an escape attempt.
53
+ const absolutePaths = req.command.match(/(?<=^|[\s"'=])\/[^\s"';|&]+/g) ?? [];
54
+ for (const path of absolutePaths) {
55
+ if (path.startsWith("/dev/") || path.startsWith("/tmp/") || path.startsWith("/usr/") || path.startsWith("/bin/") || path.startsWith("/opt/") || path.startsWith("/etc/"))
56
+ continue;
57
+ if (!isPathInsideWorktree(this.worktreeRoot, path)) {
58
+ return {
59
+ action: "reject",
60
+ feedback: `Blocked by worktree policy: the command references a path outside the worktree (${path}).`,
61
+ };
62
+ }
63
+ }
64
+ return { action: "approve" };
65
+ }
66
+ case "edit":
67
+ case "write":
68
+ return isPathInsideWorktree(this.worktreeRoot, req.path)
69
+ ? { action: "approve" }
70
+ : { action: "reject", feedback: `Blocked by worktree policy: ${req.path} is outside the subagent worktree.` };
71
+ case "patch":
72
+ return req.paths.every((path) => isPathInsideWorktree(this.worktreeRoot, path))
73
+ ? { action: "approve" }
74
+ : { action: "reject", feedback: "Blocked by worktree policy: the patch touches paths outside the subagent worktree." };
75
+ case "lsp":
76
+ return { action: "approve" };
77
+ case "agent_profile":
78
+ return { action: "reject", feedback: "Subagents cannot approve agent profiles." };
79
+ }
80
+ }
81
+ checkRules() {
82
+ return { decision: "ask" };
83
+ }
84
+ }
85
+ const WORKTREE_TOOL_NAMES = new Set(["read", "glob", "grep", "edit", "write", "bash"]);
86
+ /**
87
+ * Builds the write child's toolset bound to its worktree: fresh instances
88
+ * with their own FileStateTracker and the worktree approval policy. A
89
+ * profile's tools list can narrow the set but never widen it.
90
+ */
91
+ export function createWorktreeChildTools(worktreeCwd, include) {
92
+ const approval = new WorktreeApprovalController(worktreeCwd);
93
+ const fileState = new FileStateTracker(worktreeCwd);
94
+ const tools = [
95
+ createReadTool(worktreeCwd, approval, undefined, fileState),
96
+ createGlobTool(worktreeCwd),
97
+ createGrepTool(worktreeCwd),
98
+ createEditTool(worktreeCwd, approval, undefined, fileState),
99
+ createWriteTool(worktreeCwd, {}, approval, undefined, fileState),
100
+ createBashTool(worktreeCwd, approval, fileState),
101
+ ];
102
+ if (!include || include.length === 0)
103
+ return tools;
104
+ const requested = new Set(include.filter((name) => WORKTREE_TOOL_NAMES.has(name)));
105
+ return requested.size > 0 ? tools.filter((tool) => requested.has(tool.name)) : tools;
106
+ }
@@ -4,6 +4,7 @@
4
4
  * This is the safest way to edit files: old_string must exist exactly once.
5
5
  */
6
6
  import type { ApprovalController } from "../approval/types.js";
7
+ import type { CheckpointStore } from "../checkpoints.js";
7
8
  import type { ToolRegistryEntry } from "../types.js";
8
9
  import { type LspService } from "../lsp/index.js";
9
10
  import { type FileStateTracker } from "./file-state.js";
@@ -14,4 +15,4 @@ export interface EditArgs {
14
15
  newText: string;
15
16
  }>;
16
17
  }
17
- export declare function createEditTool(cwd: string, approval?: ApprovalController, lsp?: LspService, fileState?: FileStateTracker): ToolRegistryEntry;
18
+ export declare function createEditTool(cwd: string, approval?: ApprovalController, lsp?: LspService, fileState?: FileStateTracker, checkpoints?: () => CheckpointStore | undefined): ToolRegistryEntry;
@@ -60,7 +60,7 @@ function firstString(...values) {
60
60
  }
61
61
  return undefined;
62
62
  }
63
- export function createEditTool(cwd, approval, lsp, fileState) {
63
+ export function createEditTool(cwd, approval, lsp, fileState, checkpoints) {
64
64
  return {
65
65
  name: "edit",
66
66
  effect: "write_direct",
@@ -178,6 +178,7 @@ export function createEditTool(cwd, approval, lsp, fileState) {
178
178
  },
179
179
  };
180
180
  }
181
+ await checkpoints?.()?.captureBefore(filePath, original);
181
182
  await writeFile(filePath, applied.content, "utf-8");
182
183
  await fileState?.observe(filePath, "edit", applied.content).catch(() => undefined);
183
184
  let output = `Edited ${filePath}${formatEditMatchNotes(applied.matches)}\n\nDiff:\n${diff}`;
@@ -28,6 +28,7 @@ import { type LspService } from "../lsp/index.js";
28
28
  import { type TodoStore } from "./todo.js";
29
29
  import { type ToolSearchController } from "./tool-search.js";
30
30
  import type { QuestionController } from "../question/index.js";
31
+ import type { CheckpointStore } from "../checkpoints.js";
31
32
  import { FileStateTracker } from "./file-state.js";
32
33
  export interface CreateAllToolsOptions {
33
34
  todoStore?: TodoStore;
@@ -37,5 +38,11 @@ export interface CreateAllToolsOptions {
37
38
  toolSearchController?: ToolSearchController;
38
39
  lspService?: LspService;
39
40
  fileStateTracker?: FileStateTracker;
41
+ /**
42
+ * Lazy accessor for the session's checkpoint store (the session manager may
43
+ * not exist yet when tools are created). Used by edit/write to snapshot
44
+ * files before mutating them so /rewind can restore.
45
+ */
46
+ checkpoints?: () => CheckpointStore | undefined;
40
47
  }
41
48
  export declare function createAllTools(cwd: string, skillRegistry?: SkillRegistry, options?: CreateAllToolsOptions): ToolRegistryEntry[];
@@ -48,8 +48,8 @@ export function createAllTools(cwd, skillRegistry, options = {}) {
48
48
  createReadTool(cwd, approval, lsp, fileState),
49
49
  createBashTool(cwd, approval, fileState),
50
50
  ...createManagedServerTools(cwd, approval),
51
- createWriteTool(cwd, {}, approval, lsp, fileState),
52
- createEditTool(cwd, approval, lsp, fileState),
51
+ createWriteTool(cwd, {}, approval, lsp, fileState, options.checkpoints),
52
+ createEditTool(cwd, approval, lsp, fileState, options.checkpoints),
53
53
  createGlobTool(cwd),
54
54
  createGrepTool(cwd),
55
55
  createLspTool(cwd, lsp, approval),
@@ -57,7 +57,7 @@ export function createAllTools(cwd, skillRegistry, options = {}) {
57
57
  createWebFetchTool(approval),
58
58
  createMemorySearchTool(cwd),
59
59
  createMemoryReadSummaryTool(cwd),
60
- ...createAgentLifecycleTools(),
60
+ ...createAgentLifecycleTools({ cwd, approval }),
61
61
  ...(options.questionController ? [createQuestionTool(options.questionController)] : []),
62
62
  ...(skillRegistry ? [createSkillSearchTool(skillRegistry), createSkillTool(skillRegistry)] : []),
63
63
  ...(options.todoStore ? [createTodoTool(options.todoStore)] : []),
@@ -2,8 +2,9 @@
2
2
  * Write tool - create files or replace full file contents.
3
3
  */
4
4
  import type { ApprovalController } from "../approval/types.js";
5
+ import type { CheckpointStore } from "../checkpoints.js";
5
6
  import type { ToolRegistryEntry } from "../types.js";
6
7
  import { type LspService } from "../lsp/index.js";
7
8
  import { type FileStateTracker } from "./file-state.js";
8
9
  export type WriteToolOptions = Record<string, never>;
9
- export declare function createWriteTool(cwd: string, _options?: WriteToolOptions, approval?: ApprovalController, lsp?: LspService, fileState?: FileStateTracker): ToolRegistryEntry;
10
+ export declare function createWriteTool(cwd: string, _options?: WriteToolOptions, approval?: ApprovalController, lsp?: LspService, fileState?: FileStateTracker, checkpoints?: () => CheckpointStore | undefined): ToolRegistryEntry;
@@ -18,7 +18,7 @@ function prepareWriteArguments(input) {
18
18
  }
19
19
  return args;
20
20
  }
21
- export function createWriteTool(cwd, _options = {}, approval, lsp, fileState) {
21
+ export function createWriteTool(cwd, _options = {}, approval, lsp, fileState, checkpoints) {
22
22
  return {
23
23
  name: "write",
24
24
  effect: "write_direct",
@@ -76,6 +76,7 @@ export function createWriteTool(cwd, _options = {}, approval, lsp, fileState) {
76
76
  return changedDuringApprovalResult(filePath, changed);
77
77
  }
78
78
  try {
79
+ await checkpoints?.()?.captureBefore(filePath, existed ? oldContent : null);
79
80
  await mkdir(dirname(filePath), { recursive: true });
80
81
  await writeFile(filePath, args.content, "utf-8");
81
82
  await fileState?.observe(filePath, "write", args.content).catch(() => undefined);
@@ -44,6 +44,12 @@ export declare function isImageFilePath(raw: string): boolean;
44
44
  export declare function extractImagePathTokens(input: string): ImagePathToken[];
45
45
  export declare function removeImagePathTokens(input: string, tokens: ImagePathToken[]): string;
46
46
  export declare function imageAttachmentLabel(att: ImageAttachment, index: number): string;
47
+ /**
48
+ * Label for an image path before ingestion runs. Matches what
49
+ * imageAttachmentLabel produces for the same file, so a label inserted at
50
+ * paste time stays a valid key once the attachment is registered.
51
+ */
52
+ export declare function imageLabelForPath(rawPath: string, index: number): string;
47
53
  export declare function imageAttachmentReference(att: ImageAttachment, index: number): string;
48
54
  export declare function imageAttachmentLabelPattern(): RegExp;
49
55
  export declare function buildImageContentParts(promptText: string, attachments: ImageAttachment[]): ContentPart[];
@@ -61,6 +67,18 @@ export declare function buildImageContentPartsFromLabels(input: string, attachme
61
67
  * only on a space that is followed by the start of a new absolute path.
62
68
  */
63
69
  export declare function splitPastedPaths(pasted: string): string[];
70
+ /**
71
+ * True when a pasted blob consists solely of image file paths (drag from
72
+ * Finder, or a terminal that converts clipboard images to temp-file paths).
73
+ */
74
+ export declare function isImagePathPaste(pasted: string): boolean;
75
+ /**
76
+ * Bare image filename with no directory, e.g. "Screenshot ... AM.png".
77
+ * Copying an image file in Finder puts only the file's NAME in the
78
+ * clipboard's plain-text flavor — the actual bits arrive as a file-url or
79
+ * image flavor that must be read from the clipboard separately.
80
+ */
81
+ export declare function bareImageFilenameFromPaste(pasted: string): string | null;
64
82
  export declare function readImageFromPath(rawPath: string): Promise<ImageAttachment | null>;
65
83
  /** macOS screenshot shortcut writes to these paths and they may be auto-cleaned. */
66
84
  export declare function isScreenshotTempPath(s: string): boolean;
@@ -65,6 +65,15 @@ export function removeImagePathTokens(input, tokens) {
65
65
  export function imageAttachmentLabel(att, index) {
66
66
  return `image#${index}${imageExtension(att)}`;
67
67
  }
68
+ /**
69
+ * Label for an image path before ingestion runs. Matches what
70
+ * imageAttachmentLabel produces for the same file, so a label inserted at
71
+ * paste time stays a valid key once the attachment is registered.
72
+ */
73
+ export function imageLabelForPath(rawPath, index) {
74
+ const ext = path.extname(unescapeShell(rawPath.trim())).toLowerCase() || ".png";
75
+ return `image#${index}${ext}`;
76
+ }
68
77
  export function imageAttachmentReference(att, index) {
69
78
  return `[${imageAttachmentLabel(att, index)}]`;
70
79
  }
@@ -155,6 +164,30 @@ export function splitPastedPaths(pasted) {
155
164
  }
156
165
  return out;
157
166
  }
167
+ /**
168
+ * True when a pasted blob consists solely of image file paths (drag from
169
+ * Finder, or a terminal that converts clipboard images to temp-file paths).
170
+ */
171
+ export function isImagePathPaste(pasted) {
172
+ const pieces = splitPastedPaths(pasted);
173
+ return pieces.length > 0 && pieces.every((piece) => isImageFilePath(piece));
174
+ }
175
+ /**
176
+ * Bare image filename with no directory, e.g. "Screenshot ... AM.png".
177
+ * Copying an image file in Finder puts only the file's NAME in the
178
+ * clipboard's plain-text flavor — the actual bits arrive as a file-url or
179
+ * image flavor that must be read from the clipboard separately.
180
+ */
181
+ export function bareImageFilenameFromPaste(pasted) {
182
+ const s = pasted.trim();
183
+ if (!s || s.length > 255)
184
+ return null;
185
+ if (/[\n\r/\\]/.test(s))
186
+ return null;
187
+ if (!IMAGE_EXT.test(s))
188
+ return null;
189
+ return s;
190
+ }
158
191
  function mediaTypeFromExt(p) {
159
192
  const ext = path.extname(p).toLowerCase();
160
193
  if (ext === ".jpg" || ext === ".jpeg")
@@ -384,6 +417,14 @@ export async function ingestImagePath(p) {
384
417
  return { attachment: sized };
385
418
  }
386
419
  export async function ingestClipboardImage() {
420
+ // A file reference wins over bitmap flavors: for a copied FILE, coercing
421
+ // the clipboard to PNGf yields the file's generic ICON, not the image.
422
+ const filePath = await getClipboardFilePath();
423
+ if (filePath) {
424
+ if (isImageFilePath(filePath))
425
+ return ingestImagePath(filePath);
426
+ return { error: `clipboard file is not an image: ${filePath}` };
427
+ }
387
428
  const raw = await getImageFromClipboard();
388
429
  if (!raw)
389
430
  return { error: "clipboard has no image" };
@@ -393,6 +434,25 @@ export async function ingestClipboardImage() {
393
434
  return { error: validation.reason };
394
435
  return { attachment: sized };
395
436
  }
437
+ async function getClipboardFilePath() {
438
+ if (process.platform !== "darwin")
439
+ return null;
440
+ try {
441
+ // Probe first — AppleScript happily coerces plain TEXT into a file URL,
442
+ // so only trust «class furl» when the clipboard really carries one.
443
+ const probe = await execFileAsync("osascript", ["-e", "clipboard info for «class furl»"], {
444
+ timeout: 5000,
445
+ });
446
+ if (!String(probe.stdout).includes("furl"))
447
+ return null;
448
+ const result = await execFileAsync("osascript", ["-e", "POSIX path of (the clipboard as «class furl»)"], { timeout: 5000 });
449
+ const p = String(result.stdout).trim();
450
+ return p || null;
451
+ }
452
+ catch {
453
+ return null;
454
+ }
455
+ }
396
456
  export async function resolveImageInput(input, options = {}) {
397
457
  const tokens = extractImagePathTokens(input);
398
458
  if (tokens.length === 0) {
package/dist/tui/run.d.ts CHANGED
@@ -2,7 +2,7 @@ import { type Agent } from "../agent.js";
2
2
  import type { CliArgs } from "../cli.js";
3
3
  import type { ThemeMode } from "../config.js";
4
4
  import type { ExternalHookController } from "../hooks/controller.js";
5
- import type { SessionManager } from "../session.js";
5
+ import { SessionManager } from "../session.js";
6
6
  import type { PlanDecision, Provider } from "../types.js";
7
7
  import type { ProviderRegistry } from "../provider-registry.js";
8
8
  import type { SkillRegistry } from "../skills/registry.js";
@@ -45,5 +45,15 @@ export interface RunTuiOptions {
45
45
  runMemoryRefresh?: (scope?: MemoryScope) => Promise<string>;
46
46
  /** One-line "update available" notice shown on the home screen, if any. */
47
47
  updateNotice?: string;
48
+ /**
49
+ * Swap the active session in place (driven by the /session picker).
50
+ * Rebinds persistence to the picked session file and replaces the agent's
51
+ * message history; the TUI rebuilds its transcript from the result.
52
+ */
53
+ switchSession?: (sessionFile: string) => {
54
+ manager: SessionManager;
55
+ } | {
56
+ error: string;
57
+ };
48
58
  }
49
59
  export declare function runTui(agent: Agent, args: CliArgs, options?: RunTuiOptions): Promise<void>;