@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
package/dist/provider.js CHANGED
@@ -10,6 +10,7 @@ import { createOpenAICodexProvider, isOpenAICodexBaseUrl } from "./provider-open
10
10
  import { createProviderProtocolArtifactFilter } from "./provider-artifacts.js";
11
11
  import { resolveProviderRequestConfig } from "./provider-transform.js";
12
12
  import { debugReasoningStream, summarizeDebugText } from "./reasoning-debug.js";
13
+ import { RateLimitError } from "./network/errors.js";
13
14
  // Diagnostic logger for tool-args byte-loss investigation. Activate with
14
15
  // BUBBLE_DEBUG_TOOL_ARGS=/path/to/log.jsonl (any writable path)
15
16
  // Each line is a JSON record describing a transition. When debugging is off,
@@ -128,9 +129,28 @@ export function createProviderInstance(options) {
128
129
  if (requestConfig.reasoningEffort && requestConfig.reasoningEffort !== "off") {
129
130
  body.reasoning = { enabled: true };
130
131
  }
131
- const stream = (await client.chat.completions.create(body, {
132
- signal: chatOptions.abortSignal,
133
- }));
132
+ // Rate-limit contract (design §4.5): "defer" disables the SDK's own
133
+ // retries so the caller is the single 429 backoff layer; either policy
134
+ // surfaces a final 429 as a typed RateLimitError instead of a string.
135
+ let stream;
136
+ try {
137
+ stream = (await client.chat.completions.create(body, {
138
+ signal: chatOptions.abortSignal,
139
+ ...(chatOptions.rateLimitPolicy === "defer" ? { maxRetries: 0 } : {}),
140
+ }));
141
+ }
142
+ catch (error) {
143
+ if (error?.status === 429) {
144
+ const retryAfterHeader = error?.headers?.["retry-after"];
145
+ const retryAfterSeconds = Number(retryAfterHeader);
146
+ throw new RateLimitError(error?.message || "Rate limited (429)", {
147
+ status: 429,
148
+ retryAfterMs: Number.isFinite(retryAfterSeconds) ? Math.round(retryAfterSeconds * 1000) : undefined,
149
+ cause: error,
150
+ });
151
+ }
152
+ throw error;
153
+ }
134
154
  yield* translateOpenAIStream(stream, {
135
155
  toolArgsMergeMode: resolveToolArgsMergeMode(options.providerId || "", options.baseURL),
136
156
  reasoningMergeMode: resolveReasoningMergeMode(options.providerId || "", options.baseURL),
package/dist/session.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * Session Manager - Append-only JSONL persistence over a structured session log.
3
3
  */
4
+ import { CheckpointStore } from "./checkpoints.js";
4
5
  import { type CompactOptions, type CompactResult } from "./context/compact.js";
5
6
  import type { Message, Todo } from "./types.js";
6
7
  import type { SessionLogEntry, SessionMarkerKind, SessionMetadata } from "./session-types.js";
@@ -16,9 +17,25 @@ export interface SessionSummary {
16
17
  mtime: number;
17
18
  }
18
19
  export type { SessionLogEntry, SessionMarkerKind, SessionMetadata } from "./session-types.js";
20
+ export interface UserTurn {
21
+ /** Session log entry id of the user message that starts the turn. */
22
+ id: string;
23
+ /** Single-line preview of the user message. */
24
+ preview: string;
25
+ /** Full text of the user message. */
26
+ text: string;
27
+ timestamp: number;
28
+ }
29
+ export interface RewindResult {
30
+ /** Number of log entries removed. */
31
+ removedEntries: number;
32
+ /** Full text of the user message the session was rewound to (for re-editing). */
33
+ targetText: string;
34
+ }
19
35
  export declare class SessionManager {
20
36
  private sessionFile;
21
37
  private log;
38
+ private checkpoints?;
22
39
  constructor(sessionFile: string);
23
40
  static create(cwd: string, sessionName?: string): SessionManager;
24
41
  static resume(cwd: string, sessionName?: string): SessionManager | undefined;
@@ -41,6 +58,20 @@ export declare class SessionManager {
41
58
  getTodos(): Todo[];
42
59
  compact(options?: CompactOptions): CompactResult;
43
60
  getMessages(): Message[];
61
+ /**
62
+ * Pre-edit file snapshot store for this session, used by /rewind.
63
+ * Lives next to the session JSONL as `<session>.checkpoints/`.
64
+ */
65
+ getCheckpoints(): CheckpointStore;
66
+ /** Entry id of the most recent user message, or "0" before the first one. */
67
+ lastUserEntryId(): string;
68
+ /** User messages after the latest /clear, oldest first — the valid rewind anchors. */
69
+ listUserTurns(): UserTurn[];
70
+ /**
71
+ * Truncate the session to just before the user message with the given
72
+ * entry id. Returns undefined when the id does not name a user message.
73
+ */
74
+ rewindToEntry(entryId: string): RewindResult | undefined;
44
75
  getEntries(): SessionLogEntry[];
45
76
  getSessionFile(): string;
46
77
  private maybeAutoCompact;
package/dist/session.js CHANGED
@@ -5,6 +5,7 @@ import { randomUUID } from "node:crypto";
5
5
  import { mkdirSync, appendFileSync, existsSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
6
6
  import { basename, dirname, join } from "node:path";
7
7
  import { getBubbleHome } from "./bubble-home.js";
8
+ import { CheckpointStore } from "./checkpoints.js";
8
9
  import { compactSessionEntries } from "./context/compact.js";
9
10
  import { SessionLog } from "./session-log.js";
10
11
  import { normalizeSingleLine, truncateVisual } from "./text-display.js";
@@ -14,6 +15,7 @@ const AUTO_COMPACT_KEEP_RECENT_TURNS = 3;
14
15
  export class SessionManager {
15
16
  sessionFile;
16
17
  log = new SessionLog();
18
+ checkpoints;
17
19
  constructor(sessionFile) {
18
20
  this.sessionFile = sessionFile;
19
21
  if (existsSync(sessionFile)) {
@@ -163,6 +165,73 @@ export class SessionManager {
163
165
  getMessages() {
164
166
  return this.log.toMessages();
165
167
  }
168
+ /**
169
+ * Pre-edit file snapshot store for this session, used by /rewind.
170
+ * Lives next to the session JSONL as `<session>.checkpoints/`.
171
+ */
172
+ getCheckpoints() {
173
+ if (!this.checkpoints) {
174
+ this.checkpoints = new CheckpointStore(this.sessionFile.replace(/\.jsonl$/, "") + ".checkpoints", () => this.lastUserEntryId());
175
+ }
176
+ return this.checkpoints;
177
+ }
178
+ /** Entry id of the most recent user message, or "0" before the first one. */
179
+ lastUserEntryId() {
180
+ const entries = this.log.list();
181
+ for (let i = entries.length - 1; i >= 0; i--) {
182
+ const entry = entries[i];
183
+ if (entry.type === "user_message")
184
+ return entry.id;
185
+ }
186
+ return "0";
187
+ }
188
+ /** User messages after the latest /clear, oldest first — the valid rewind anchors. */
189
+ listUserTurns() {
190
+ const entries = this.log.list();
191
+ let start = 0;
192
+ for (let i = entries.length - 1; i >= 0; i--) {
193
+ const entry = entries[i];
194
+ if (entry.type === "marker" && entry.kind === "conversation_clear") {
195
+ start = i + 1;
196
+ break;
197
+ }
198
+ }
199
+ const turns = [];
200
+ for (let i = start; i < entries.length; i++) {
201
+ const entry = entries[i];
202
+ if (entry.type !== "user_message")
203
+ continue;
204
+ const text = messageText(entry.message);
205
+ turns.push({
206
+ id: entry.id,
207
+ text,
208
+ preview: truncateVisual(normalizeSingleLine(text), 80) || "(empty message)",
209
+ timestamp: entry.timestamp,
210
+ });
211
+ }
212
+ return turns;
213
+ }
214
+ /**
215
+ * Truncate the session to just before the user message with the given
216
+ * entry id. Returns undefined when the id does not name a user message.
217
+ */
218
+ rewindToEntry(entryId) {
219
+ const entries = this.log.list();
220
+ const index = entries.findIndex((entry) => entry.id === entryId && entry.type === "user_message");
221
+ if (index < 0)
222
+ return undefined;
223
+ const target = entries[index];
224
+ const removed = entries.slice(index);
225
+ this.rewrite(entries.slice(0, index));
226
+ const metadata = this.log.getMetadata();
227
+ if (metadata.titleUserMessageId && removed.some((entry) => entry.id === metadata.titleUserMessageId)) {
228
+ this.clearTitleMetadata();
229
+ }
230
+ return {
231
+ removedEntries: removed.length,
232
+ targetText: target.type === "user_message" ? messageText(target.message) : "",
233
+ };
234
+ }
166
235
  getEntries() {
167
236
  return this.log.list();
168
237
  }
@@ -5,7 +5,10 @@ import { normalizeNameForMCP } from "../mcp/name.js";
5
5
  import { parseRule } from "../permissions/rule.js";
6
6
  import { encodeModel, decodeModel, displayModel, BUILTIN_PROVIDERS, isUserVisibleProvider } from "../provider-registry.js";
7
7
  import { getAvailableThinkingLevels, getDefaultThinkingLevel, normalizeThinkingLevel } from "../provider-transform.js";
8
+ import { SessionManager } from "../session.js";
8
9
  import { buildSystemPrompt } from "../system-prompt.js";
10
+ import { normalizeSingleLine } from "../text-display.js";
11
+ import { formatRelativeTime } from "../tui/recent-activity.js";
9
12
  import { HOOK_EVENT_NAMES, isHookEventName } from "../hooks/index.js";
10
13
  import { isThinkingLevel } from "../variant/thinking-level.js";
11
14
  import { collectUsageStatsBundle, formatStatsText } from "../stats/usage.js";
@@ -399,11 +402,115 @@ const builtinSlashCommandEntries = [
399
402
  ctx.clearMessages();
400
403
  },
401
404
  },
405
+ {
406
+ name: "rewind",
407
+ description: "Rewind conversation and/or file edits to before an earlier message. Usage: /rewind [n] [--code|--chat]",
408
+ async handler(args, ctx) {
409
+ const session = ctx.sessionManager;
410
+ if (!session) {
411
+ return "Rewind requires an active session.";
412
+ }
413
+ const turns = session.listUserTurns();
414
+ if (turns.length === 0) {
415
+ return "Nothing to rewind: no user messages in this session.";
416
+ }
417
+ const tokens = args.trim().split(/\s+/).filter(Boolean);
418
+ const flags = tokens.filter((token) => token.startsWith("--"));
419
+ const positional = tokens.filter((token) => !token.startsWith("--"));
420
+ const checkpoints = session.getCheckpoints();
421
+ if (positional.length === 0) {
422
+ if (ctx.openRewindPicker) {
423
+ ctx.openRewindPicker();
424
+ return;
425
+ }
426
+ const lines = ["Rewind points (oldest first):", ""];
427
+ turns.forEach((turn, index) => {
428
+ const time = new Date(turn.timestamp).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
429
+ const files = checkpoints.filesTouchedAt(turn.id).length;
430
+ const fileNote = files > 0 ? ` [${files} file${files === 1 ? "" : "s"} changed]` : "";
431
+ lines.push(` ${index + 1}. ${time} ${turn.preview}${fileNote}`);
432
+ });
433
+ lines.push("", "Usage:", " /rewind <n> restore conversation AND files to just before message n", " /rewind <n> --chat conversation only", " /rewind <n> --code files only", "", "Note: only edits made by the edit/write tools are tracked; changes from", "bash commands are not. Checkpoints complement git, they don't replace it.");
434
+ return lines.join("\n");
435
+ }
436
+ const n = Number(positional[0]);
437
+ if (!Number.isInteger(n) || n < 1 || n > turns.length) {
438
+ return `Invalid rewind point "${positional[0]}". Run /rewind to list points (1-${turns.length}).`;
439
+ }
440
+ const target = turns[n - 1];
441
+ const codeOnly = flags.includes("--code");
442
+ const chatOnly = flags.includes("--chat") || flags.includes("--conversation");
443
+ if (codeOnly && chatOnly) {
444
+ return "Pick at most one of --code / --chat.";
445
+ }
446
+ // The "⏪" prefix is recognized by the TUIs: they rebuild the visible
447
+ // transcript from the rewound agent.messages before showing this text.
448
+ const lines = [
449
+ codeOnly
450
+ ? `Files restored to just before: ${target.preview}`
451
+ : `⏪ Rewound to before: ${target.preview}`,
452
+ ];
453
+ if (!chatOnly) {
454
+ const restore = await checkpoints.restoreTo(target.id);
455
+ const touched = restore.restored.length + restore.deleted.length;
456
+ if (touched === 0 && restore.failed.length === 0) {
457
+ lines.push("Files: no tracked edits to undo.");
458
+ }
459
+ else {
460
+ for (const file of restore.restored)
461
+ lines.push(`Restored ${file}`);
462
+ for (const file of restore.deleted)
463
+ lines.push(`Deleted ${file} (created after this point)`);
464
+ for (const file of restore.failed)
465
+ lines.push(`FAILED to restore ${file}`);
466
+ }
467
+ }
468
+ if (!codeOnly) {
469
+ session.rewindToEntry(target.id);
470
+ const head = ctx.agent.messages.filter((m) => m.role === "system" || m.role === "meta");
471
+ ctx.agent.messages = [...head, ...session.getMessages()];
472
+ ctx.agent.setTodos(session.getTodos());
473
+ ctx.agent.resetContextUsageAnchor();
474
+ if (ctx.fillComposer) {
475
+ // Put the rewound message back into the input box for re-editing.
476
+ ctx.fillComposer(target.text);
477
+ }
478
+ else {
479
+ lines.push("", "Rewound message (copy to re-edit):", target.text);
480
+ }
481
+ }
482
+ return lines.join("\n");
483
+ },
484
+ },
402
485
  {
403
486
  name: "session",
404
- description: "Show current session information",
487
+ description: "Browse recent sessions and resume one. /session to pick, /session --list to print",
405
488
  async handler(args, ctx) {
406
- return `Session info not implemented yet.`;
489
+ const flag = args.trim();
490
+ if (flag && flag !== "--list") {
491
+ return "Usage: /session (open the session picker) or /session --list";
492
+ }
493
+ if (!flag && ctx.openSessionPicker) {
494
+ ctx.openSessionPicker();
495
+ return;
496
+ }
497
+ const summaries = SessionManager.summarizeSessionsForCwd(ctx.cwd);
498
+ if (summaries.length === 0) {
499
+ return "No sessions recorded for this project yet.";
500
+ }
501
+ const activeFile = ctx.sessionManager?.getSessionFile();
502
+ const lines = ["Recent sessions:"];
503
+ for (const summary of summaries.slice(0, 15)) {
504
+ const current = summary.file === activeFile ? " (current)" : "";
505
+ const title = normalizeSingleLine(summary.title || summary.preview || summary.name);
506
+ const count = `${summary.messageCount} message${summary.messageCount === 1 ? "" : "s"}`;
507
+ lines.push(`- ${title} — ${count}, ${formatRelativeTime(summary.mtime)} (${summary.name})${current}`);
508
+ }
509
+ if (summaries.length > 15) {
510
+ lines.push(`- … and ${summaries.length - 15} more`);
511
+ }
512
+ lines.push("", "Resume one with: bubble --resume --session <name>");
513
+ return lines.join("\n");
407
514
  },
408
515
  },
409
516
  {
@@ -48,6 +48,12 @@ export interface SlashCommandContext {
48
48
  setSidebarMode?: (mode: SidebarMode) => SidebarCommandState;
49
49
  /** Open the feedback dialog. `initialDescription` prefills the description field. */
50
50
  openFeedback?: (initialDescription: string) => void;
51
+ /** Open the interactive rewind picker. When absent, /rewind falls back to a text listing. */
52
+ openRewindPicker?: () => void;
53
+ /** Open the interactive session picker. When absent, /session falls back to a text listing. */
54
+ openSessionPicker?: () => void;
55
+ /** Replace the composer/input box content (e.g. /rewind restores the rewound message for re-editing). */
56
+ fillComposer?: (text: string) => void;
51
57
  /** Open the interactive usage stats panel. */
52
58
  openStats?: () => void;
53
59
  }
@@ -1,6 +1,32 @@
1
- import type { ToolRegistryEntry } from "../types.js";
2
- export declare function createSpawnAgentTool(): ToolRegistryEntry;
1
+ import type { AgentProfile } from "../agent/profiles.js";
2
+ import type { ApprovalController } from "../approval/types.js";
3
+ import type { ToolRegistryEntry, ToolResult } from "../types.js";
4
+ export interface AgentLifecycleToolOptions {
5
+ /** Working directory used for profile discovery in tool descriptions. */
6
+ cwd?: string;
7
+ /** Trust gate for project-local .bubble/agents profiles (design §10.2). */
8
+ approval?: ApprovalController;
9
+ }
10
+ /**
11
+ * Session-scoped trust decisions for project profiles, keyed by file path +
12
+ * content hash so an edited file re-prompts (design §10.2). Shared across the
13
+ * lifecycle tools created by one factory call.
14
+ */
15
+ declare class ProjectProfileTrust {
16
+ private readonly approval?;
17
+ private readonly approved;
18
+ constructor(approval?: ApprovalController | undefined);
19
+ /** Returns undefined when trusted, else a blocked ToolResult. */
20
+ ensureTrusted(profile: AgentProfile): Promise<ToolResult | undefined>;
21
+ }
22
+ export declare function createSpawnAgentTool(options?: AgentLifecycleToolOptions, sharedTrust?: ProjectProfileTrust): ToolRegistryEntry;
3
23
  export declare function createWaitAgentTool(): ToolRegistryEntry;
4
24
  export declare function createSendInputTool(): ToolRegistryEntry;
5
25
  export declare function createCloseAgentTool(): ToolRegistryEntry;
6
- export declare function createAgentLifecycleTools(): ToolRegistryEntry[];
26
+ export declare function createListAgentsTool(): ToolRegistryEntry;
27
+ /** Items bound for one agent_team call (design §1.2). */
28
+ export declare const AGENT_TEAM_MIN_ITEMS = 2;
29
+ export declare const AGENT_TEAM_MAX_ITEMS = 32;
30
+ export declare function createAgentTeamTool(options?: AgentLifecycleToolOptions, sharedTrust?: ProjectProfileTrust): ToolRegistryEntry;
31
+ export declare function createAgentLifecycleTools(options?: AgentLifecycleToolOptions): ToolRegistryEntry[];
32
+ export {};