@bubblebrain-ai/bubble 0.0.20 → 0.0.21

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 (49) hide show
  1. package/dist/agent.d.ts +1 -0
  2. package/dist/agent.js +5 -1
  3. package/dist/checkpoints.d.ts +57 -0
  4. package/dist/checkpoints.js +0 -0
  5. package/dist/feishu/agent-host/run-driver.js +1 -0
  6. package/dist/main.js +54 -13
  7. package/dist/session.d.ts +31 -0
  8. package/dist/session.js +69 -0
  9. package/dist/slash-commands/commands.js +80 -0
  10. package/dist/slash-commands/types.d.ts +4 -0
  11. package/dist/tools/bash.js +4 -0
  12. package/dist/tools/edit.d.ts +2 -1
  13. package/dist/tools/edit.js +2 -1
  14. package/dist/tools/index.d.ts +7 -0
  15. package/dist/tools/index.js +2 -2
  16. package/dist/tools/write.d.ts +2 -1
  17. package/dist/tools/write.js +2 -1
  18. package/dist/tui/image-paste.d.ts +18 -0
  19. package/dist/tui/image-paste.js +60 -0
  20. package/dist/tui/run.js +309 -69
  21. package/dist/tui/trace-groups.d.ts +16 -0
  22. package/dist/tui/trace-groups.js +42 -1
  23. package/dist/tui/transcript-scroll.d.ts +25 -0
  24. package/dist/tui/transcript-scroll.js +20 -0
  25. package/dist/tui-ink/app.d.ts +4 -1
  26. package/dist/tui-ink/app.js +301 -247
  27. package/dist/tui-ink/display-history.d.ts +16 -1
  28. package/dist/tui-ink/display-history.js +50 -21
  29. package/dist/tui-ink/footer.d.ts +6 -12
  30. package/dist/tui-ink/footer.js +10 -29
  31. package/dist/tui-ink/image-paste.d.ts +59 -0
  32. package/dist/tui-ink/image-paste.js +277 -0
  33. package/dist/tui-ink/input-box.d.ts +26 -1
  34. package/dist/tui-ink/input-box.js +171 -41
  35. package/dist/tui-ink/message-list.d.ts +1 -1
  36. package/dist/tui-ink/message-list.js +46 -29
  37. package/dist/tui-ink/run.d.ts +7 -2
  38. package/dist/tui-ink/run.js +73 -23
  39. package/dist/tui-ink/terminal-mouse.d.ts +1 -0
  40. package/dist/tui-ink/terminal-mouse.js +4 -0
  41. package/dist/tui-ink/trace-groups.d.ts +16 -0
  42. package/dist/tui-ink/trace-groups.js +50 -2
  43. package/dist/tui-ink/transcript-viewport-math.d.ts +11 -0
  44. package/dist/tui-ink/transcript-viewport-math.js +17 -0
  45. package/dist/tui-ink/transcript-viewport.d.ts +24 -0
  46. package/dist/tui-ink/transcript-viewport.js +83 -0
  47. package/dist/tui-ink/welcome.d.ts +9 -7
  48. package/dist/tui-ink/welcome.js +7 -33
  49. package/package.json +1 -1
@@ -7,6 +7,10 @@ export interface TraceGroup {
7
7
  count?: number;
8
8
  noun?: string;
9
9
  command?: string;
10
+ /** Model-provided one-line summary of what the command does (bash `description` arg). */
11
+ description?: string;
12
+ /** Original command split into lines, line breaks preserved (execute groups only). */
13
+ commandLines?: string[];
10
14
  items: string[];
11
15
  previewLines: string[];
12
16
  errorLines: string[];
@@ -25,3 +29,15 @@ export declare function buildTraceGroups(toolCalls: DisplayToolCall[], options?:
25
29
  export declare function formatTracePath(value: unknown, homeDir?: string): string;
26
30
  export declare function formatElapsed(startedAt: number | undefined, now?: number): string | null;
27
31
  export declare function traceGroupLabel(group: TraceGroup): string;
32
+ /**
33
+ * An execute command is shown inline in the header only when nothing is lost:
34
+ * no description competing for the slot, a single logical line, and it fits
35
+ * the width budget. Otherwise the full command renders as a wrapped block
36
+ * below the header — commands are never clipped mid-line.
37
+ */
38
+ export declare function shouldInlineExecuteCommand(group: TraceGroup, widthBudget: number): boolean;
39
+ /** Visible command-block lines for compact rendering, capped at `maxLines`. */
40
+ export declare function executeCommandBlock(group: TraceGroup, maxLines: number): {
41
+ lines: string[];
42
+ omitted: number;
43
+ };
@@ -65,12 +65,36 @@ export function formatElapsed(startedAt, now = Date.now()) {
65
65
  return `${minutes}m${remainder.toString().padStart(2, "0")}s`;
66
66
  }
67
67
  export function traceGroupLabel(group) {
68
+ if (group.description)
69
+ return `${group.title} ${group.description}`;
68
70
  if (group.command)
69
71
  return `${group.title} ${group.command}`;
70
72
  if (group.count !== undefined && group.noun)
71
73
  return `${group.title} ${group.count} ${group.noun}`;
72
74
  return group.title;
73
75
  }
76
+ /**
77
+ * An execute command is shown inline in the header only when nothing is lost:
78
+ * no description competing for the slot, a single logical line, and it fits
79
+ * the width budget. Otherwise the full command renders as a wrapped block
80
+ * below the header — commands are never clipped mid-line.
81
+ */
82
+ export function shouldInlineExecuteCommand(group, widthBudget) {
83
+ if (group.kind !== "execute" || !group.command)
84
+ return false;
85
+ if (group.description)
86
+ return false;
87
+ const lines = group.commandLines ?? [];
88
+ if (lines.length > 1)
89
+ return false;
90
+ return group.command.length <= widthBudget;
91
+ }
92
+ /** Visible command-block lines for compact rendering, capped at `maxLines`. */
93
+ export function executeCommandBlock(group, maxLines) {
94
+ const lines = group.commandLines ?? [];
95
+ const shown = lines.slice(0, maxLines);
96
+ return { lines: shown, omitted: Math.max(0, lines.length - shown.length) };
97
+ }
74
98
  function classifyTool(toolCall) {
75
99
  if (toolCall.metadata?.kind === "subagent") {
76
100
  return { kind: "subagent", title: "Subagents", bucketKey: `subagent:${toolCall.id}`, groupable: false };
@@ -244,11 +268,15 @@ function buildSearchGroup(classifier, raw, options, pending, startedAt, hasError
244
268
  function buildExecuteGroup(classifier, tool, options, pending, startedAt, hasError, errorCount) {
245
269
  const lines = resultLines(tool.result).map((line) => formatTracePath(line, options.homeDir));
246
270
  const { shown, omitted } = take(lines, options.maxPreviewLines);
271
+ const rawCommand = String(tool.args.command ?? tool.args.cmd ?? commandFromRawArguments(tool.rawArguments) ?? "");
272
+ const description = String(tool.args.description ?? "").trim() || undefined;
247
273
  return {
248
274
  kind: "execute",
249
275
  title: classifier.title,
250
276
  raw: [tool],
251
- command: normalizeCommand(tool.args.command ?? tool.args.cmd ?? commandFromRawArguments(tool.rawArguments)),
277
+ command: normalizeCommand(rawCommand),
278
+ description,
279
+ commandLines: commandLinesOf(rawCommand),
252
280
  items: [],
253
281
  previewLines: shown,
254
282
  errorLines: [],
@@ -400,6 +428,19 @@ function normalizeCommand(value) {
400
428
  const command = String(value ?? "").replace(/\s+/g, " ").trim();
401
429
  return command;
402
430
  }
431
+ // Preserves the command's own line structure (heredocs, && chains the model
432
+ // formatted across lines); only trims trailing whitespace and outer blank lines.
433
+ function commandLinesOf(rawCommand) {
434
+ const lines = rawCommand
435
+ .replace(/\r\n/g, "\n")
436
+ .split("\n")
437
+ .map((line) => line.trimEnd());
438
+ while (lines.length > 0 && lines[0].trim() === "")
439
+ lines.shift();
440
+ while (lines.length > 0 && lines[lines.length - 1].trim() === "")
441
+ lines.pop();
442
+ return lines;
443
+ }
403
444
  function commandFromRawArguments(rawArguments) {
404
445
  if (!rawArguments)
405
446
  return "";
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Transcript scroll-follow policy.
3
+ *
4
+ * The transcript snaps to the bottom ("follows") while the user is at the
5
+ * bottom, and stays put while they read older history. Two events override a
6
+ * scrolled-up position and re-engage following:
7
+ * - the user sends a message (explicit intent to watch the newest turn)
8
+ * - an approval prompt appears (requires the user's attention)
9
+ *
10
+ * Those renders set `forcePending`, which must survive until the deferred
11
+ * scroll actually runs: streaming redraws in the interim recompute the follow
12
+ * flag from the (still unscrolled) position and would otherwise cancel the
13
+ * snap. A user mouse scroll clears the pending force — their latest gesture
14
+ * always wins.
15
+ */
16
+ export interface TranscriptScrollState {
17
+ /** A forceFollow render is waiting for its deferred scroll to execute. */
18
+ forcePending: boolean;
19
+ /** The viewport was at the bottom when the update was scheduled. */
20
+ shouldFollow: boolean;
21
+ /** Live follow flag, recomputed from the viewport position on each render. */
22
+ following: boolean;
23
+ }
24
+ export type TranscriptScrollAction = "scroll-bottom" | "sync-position";
25
+ export declare function resolveTranscriptScroll(state: TranscriptScrollState): TranscriptScrollAction;
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Transcript scroll-follow policy.
3
+ *
4
+ * The transcript snaps to the bottom ("follows") while the user is at the
5
+ * bottom, and stays put while they read older history. Two events override a
6
+ * scrolled-up position and re-engage following:
7
+ * - the user sends a message (explicit intent to watch the newest turn)
8
+ * - an approval prompt appears (requires the user's attention)
9
+ *
10
+ * Those renders set `forcePending`, which must survive until the deferred
11
+ * scroll actually runs: streaming redraws in the interim recompute the follow
12
+ * flag from the (still unscrolled) position and would otherwise cancel the
13
+ * snap. A user mouse scroll clears the pending force — their latest gesture
14
+ * always wins.
15
+ */
16
+ export function resolveTranscriptScroll(state) {
17
+ if (state.forcePending)
18
+ return "scroll-bottom";
19
+ return state.shouldFollow && state.following ? "scroll-bottom" : "sync-position";
20
+ }
@@ -12,6 +12,7 @@ import type { McpManager } from "../mcp/manager.js";
12
12
  import type { LspService } from "../lsp/index.js";
13
13
  import type { QuestionController } from "../question/index.js";
14
14
  import type { MemoryScope } from "../memory/index.js";
15
+ import type { ExternalHookController } from "../hooks/controller.js";
15
16
  export interface PlanHandlerRef {
16
17
  current?: (plan: string) => Promise<PlanDecision>;
17
18
  }
@@ -42,11 +43,13 @@ interface AppProps {
42
43
  runMemoryRefresh?: (scope?: MemoryScope) => Promise<string>;
43
44
  /** Whether the bypassPermissions mode is reachable via Shift+Tab cycling. */
44
45
  bypassEnabled?: boolean;
46
+ updateNotice?: string;
47
+ hookController?: ExternalHookController;
45
48
  onExit?: (summary: ExitSummary) => void;
46
49
  }
47
50
  export interface ExitSummary {
48
51
  /** Wall-clock duration of the session, in milliseconds. */
49
52
  wallMs: number;
50
53
  }
51
- export declare function App({ agent, args, sessionManager, createProvider, registry, skillRegistry, planHandlerRef, approvalHandlerRef, questionController, bashAllowlist, settingsManager, lspService, mcpManager, themeMode: initialThemeMode, themeOverrides, detectedTheme, onThemeModeChange, flushMemory, runMemoryCompaction, runMemorySummary, runMemoryRefresh, bypassEnabled, onExit }: AppProps): import("react/jsx-runtime").JSX.Element;
54
+ export declare function App({ agent, args, sessionManager, createProvider, registry, skillRegistry, planHandlerRef, approvalHandlerRef, questionController, bashAllowlist, settingsManager, lspService, mcpManager, themeMode: initialThemeMode, themeOverrides, detectedTheme, onThemeModeChange, flushMemory, runMemoryCompaction, runMemorySummary, runMemoryRefresh, bypassEnabled, updateNotice, hookController, onExit }: AppProps): import("react/jsx-runtime").JSX.Element;
52
55
  export {};