@bubblebrain-ai/bubble 0.0.10 → 0.0.11

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 (153) hide show
  1. package/dist/agent.d.ts +1 -0
  2. package/dist/agent.js +5 -0
  3. package/dist/cli.d.ts +10 -0
  4. package/dist/cli.js +31 -3
  5. package/dist/feedback/collect.d.ts +7 -0
  6. package/dist/feedback/collect.js +119 -0
  7. package/dist/feedback/config.d.ts +14 -0
  8. package/dist/feedback/config.js +16 -0
  9. package/dist/feedback/redact.d.ts +1 -0
  10. package/dist/feedback/redact.js +25 -0
  11. package/dist/feedback/submit.d.ts +6 -0
  12. package/dist/feedback/submit.js +43 -0
  13. package/dist/feedback/types.d.ts +22 -0
  14. package/dist/feishu/agent-host/approval-card.d.ts +11 -0
  15. package/dist/feishu/agent-host/approval-card.js +46 -0
  16. package/dist/feishu/agent-host/approval-ui.d.ts +59 -0
  17. package/dist/feishu/agent-host/approval-ui.js +214 -0
  18. package/dist/feishu/agent-host/run-driver.d.ts +51 -0
  19. package/dist/feishu/agent-host/run-driver.js +295 -0
  20. package/dist/feishu/agent-host/runtime-deps.d.ts +33 -0
  21. package/dist/feishu/agent-host/runtime-deps.js +8 -0
  22. package/dist/feishu/card/budget.d.ts +40 -0
  23. package/dist/feishu/card/budget.js +134 -0
  24. package/dist/feishu/card/renderer.d.ts +29 -0
  25. package/dist/feishu/card/renderer.js +245 -0
  26. package/dist/feishu/card/run-state-types.d.ts +49 -0
  27. package/dist/feishu/card/run-state-types.js +15 -0
  28. package/dist/feishu/card/run-state.d.ts +21 -0
  29. package/dist/feishu/card/run-state.js +217 -0
  30. package/dist/feishu/channel/channel.d.ts +52 -0
  31. package/dist/feishu/channel/channel.js +74 -0
  32. package/dist/feishu/config.d.ts +24 -0
  33. package/dist/feishu/config.js +97 -0
  34. package/dist/feishu/format.d.ts +6 -0
  35. package/dist/feishu/format.js +14 -0
  36. package/dist/feishu/index.d.ts +4 -0
  37. package/dist/feishu/index.js +4 -0
  38. package/dist/feishu/logger.d.ts +31 -0
  39. package/dist/feishu/logger.js +62 -0
  40. package/dist/feishu/paths.d.ts +12 -0
  41. package/dist/feishu/paths.js +38 -0
  42. package/dist/feishu/process-registry.d.ts +29 -0
  43. package/dist/feishu/process-registry.js +90 -0
  44. package/dist/feishu/router/commands.d.ts +38 -0
  45. package/dist/feishu/router/commands.js +285 -0
  46. package/dist/feishu/router/event-router.d.ts +40 -0
  47. package/dist/feishu/router/event-router.js +208 -0
  48. package/dist/feishu/router/whitelist.d.ts +23 -0
  49. package/dist/feishu/router/whitelist.js +20 -0
  50. package/dist/feishu/runtime/active-runs.d.ts +32 -0
  51. package/dist/feishu/runtime/active-runs.js +84 -0
  52. package/dist/feishu/runtime/pending-queue.d.ts +36 -0
  53. package/dist/feishu/runtime/pending-queue.js +98 -0
  54. package/dist/feishu/runtime/process-pool.d.ts +29 -0
  55. package/dist/feishu/runtime/process-pool.js +49 -0
  56. package/dist/feishu/schema.d.ts +17 -0
  57. package/dist/feishu/schema.js +252 -0
  58. package/dist/feishu/scope/scope-registry.d.ts +39 -0
  59. package/dist/feishu/scope/scope-registry.js +148 -0
  60. package/dist/feishu/scope/session-binder.d.ts +44 -0
  61. package/dist/feishu/scope/session-binder.js +100 -0
  62. package/dist/feishu/scope/session-store.d.ts +24 -0
  63. package/dist/feishu/scope/session-store.js +73 -0
  64. package/dist/feishu/secrets.d.ts +37 -0
  65. package/dist/feishu/secrets.js +129 -0
  66. package/dist/feishu/serve.d.ts +12 -0
  67. package/dist/feishu/serve.js +288 -0
  68. package/dist/feishu/types.d.ts +75 -0
  69. package/dist/feishu/types.js +23 -0
  70. package/dist/feishu/wizard.d.ts +24 -0
  71. package/dist/feishu/wizard.js +121 -0
  72. package/dist/main.js +78 -29
  73. package/dist/model-catalog.js +3 -0
  74. package/dist/session.d.ts +11 -0
  75. package/dist/session.js +88 -2
  76. package/dist/slash-commands/commands.js +13 -0
  77. package/dist/slash-commands/feishu.d.ts +17 -0
  78. package/dist/slash-commands/feishu.js +400 -0
  79. package/dist/slash-commands/types.d.ts +3 -1
  80. package/dist/tui-ink/app.js +218 -60
  81. package/dist/tui-ink/code-highlight.js +2 -3
  82. package/dist/tui-ink/detect-theme.d.ts +1 -18
  83. package/dist/tui-ink/detect-theme.js +1 -37
  84. package/dist/tui-ink/display-history.d.ts +20 -3
  85. package/dist/tui-ink/display-history.js +26 -27
  86. package/dist/tui-ink/feedback-dialog.d.ts +19 -0
  87. package/dist/tui-ink/feedback-dialog.js +123 -0
  88. package/dist/tui-ink/feishu-setup-picker.d.ts +5 -0
  89. package/dist/tui-ink/feishu-setup-picker.js +261 -0
  90. package/dist/tui-ink/input-box.d.ts +3 -0
  91. package/dist/tui-ink/input-box.js +27 -0
  92. package/dist/tui-ink/input-history.js +3 -5
  93. package/dist/tui-ink/markdown.d.ts +32 -0
  94. package/dist/tui-ink/markdown.js +111 -4
  95. package/dist/tui-ink/message-list.d.ts +1 -6
  96. package/dist/tui-ink/message-list.js +85 -34
  97. package/dist/tui-ink/model-picker.js +1 -4
  98. package/dist/tui-ink/run-session-picker.d.ts +10 -0
  99. package/dist/tui-ink/run-session-picker.js +22 -0
  100. package/dist/tui-ink/run.js +7 -2
  101. package/dist/tui-ink/session-picker.d.ts +10 -0
  102. package/dist/tui-ink/session-picker.js +112 -0
  103. package/dist/tui-ink/terminal-mouse.d.ts +4 -0
  104. package/dist/tui-ink/terminal-mouse.js +23 -0
  105. package/dist/tui-ink/trace-groups.js +25 -2
  106. package/dist/tui-ink/welcome.js +2 -4
  107. package/package.json +4 -5
  108. package/dist/tui/clipboard.d.ts +0 -1
  109. package/dist/tui/clipboard.js +0 -53
  110. package/dist/tui/display-history.d.ts +0 -44
  111. package/dist/tui/display-history.js +0 -243
  112. package/dist/tui/escape-confirmation.d.ts +0 -15
  113. package/dist/tui/escape-confirmation.js +0 -30
  114. package/dist/tui/file-mentions.d.ts +0 -29
  115. package/dist/tui/file-mentions.js +0 -174
  116. package/dist/tui/global-key-router.d.ts +0 -3
  117. package/dist/tui/global-key-router.js +0 -87
  118. package/dist/tui/image-paste.d.ts +0 -95
  119. package/dist/tui/image-paste.js +0 -505
  120. package/dist/tui/markdown-inline.d.ts +0 -22
  121. package/dist/tui/markdown-inline.js +0 -68
  122. package/dist/tui/markdown-theme-rules.d.ts +0 -23
  123. package/dist/tui/markdown-theme-rules.js +0 -164
  124. package/dist/tui/markdown-theme.d.ts +0 -5
  125. package/dist/tui/markdown-theme.js +0 -27
  126. package/dist/tui/opencode-spinner.d.ts +0 -21
  127. package/dist/tui/opencode-spinner.js +0 -216
  128. package/dist/tui/prompt-keybindings.d.ts +0 -42
  129. package/dist/tui/prompt-keybindings.js +0 -35
  130. package/dist/tui/recent-activity.d.ts +0 -8
  131. package/dist/tui/recent-activity.js +0 -71
  132. package/dist/tui/render-signature.d.ts +0 -1
  133. package/dist/tui/render-signature.js +0 -7
  134. package/dist/tui/run.d.ts +0 -38
  135. package/dist/tui/run.js +0 -6996
  136. package/dist/tui/sidebar-mcp.d.ts +0 -31
  137. package/dist/tui/sidebar-mcp.js +0 -62
  138. package/dist/tui/sidebar-state.d.ts +0 -12
  139. package/dist/tui/sidebar-state.js +0 -69
  140. package/dist/tui/streaming-tool-args.d.ts +0 -15
  141. package/dist/tui/streaming-tool-args.js +0 -30
  142. package/dist/tui/tool-renderers/fallback.d.ts +0 -2
  143. package/dist/tui/tool-renderers/fallback.js +0 -75
  144. package/dist/tui/tool-renderers/registry.d.ts +0 -3
  145. package/dist/tui/tool-renderers/registry.js +0 -11
  146. package/dist/tui/tool-renderers/subagent.d.ts +0 -2
  147. package/dist/tui/tool-renderers/subagent.js +0 -114
  148. package/dist/tui/tool-renderers/types.d.ts +0 -36
  149. package/dist/tui/tool-renderers/write-preview.d.ts +0 -12
  150. package/dist/tui/tool-renderers/write-preview.js +0 -30
  151. package/dist/tui/tool-renderers/write.d.ts +0 -6
  152. package/dist/tui/tool-renderers/write.js +0 -88
  153. /package/dist/{tui/tool-renderers → feedback}/types.js +0 -0
@@ -0,0 +1,4 @@
1
+ export type MouseWheelDirection = "up" | "down";
2
+ export declare function stripTerminalMouseSequences(input: string): string;
3
+ export declare function hasTerminalMouseSequence(input: string): boolean;
4
+ export declare function parseTerminalMouseWheel(input: string): MouseWheelDirection[];
@@ -0,0 +1,23 @@
1
+ const SGR_MOUSE_SEQUENCE_RE = /\x1b?\[?<\d+;\d+;\d+[mM]/g;
2
+ const SGR_MOUSE_WHEEL_RE = /\x1b?\[?<(\d+);\d+;\d+([mM])/g;
3
+ export function stripTerminalMouseSequences(input) {
4
+ return input.replace(SGR_MOUSE_SEQUENCE_RE, "");
5
+ }
6
+ export function hasTerminalMouseSequence(input) {
7
+ SGR_MOUSE_SEQUENCE_RE.lastIndex = 0;
8
+ return SGR_MOUSE_SEQUENCE_RE.test(input);
9
+ }
10
+ export function parseTerminalMouseWheel(input) {
11
+ const directions = [];
12
+ SGR_MOUSE_WHEEL_RE.lastIndex = 0;
13
+ for (const match of input.matchAll(SGR_MOUSE_WHEEL_RE)) {
14
+ if (match[2] !== "M")
15
+ continue;
16
+ const code = Number(match[1]);
17
+ if (code === 64)
18
+ directions.push("up");
19
+ if (code === 65)
20
+ directions.push("down");
21
+ }
22
+ return directions;
23
+ }
@@ -212,7 +212,7 @@ function buildExecuteGroup(classifier, tool, options, pending, startedAt, hasErr
212
212
  kind: "execute",
213
213
  title: classifier.title,
214
214
  raw: [tool],
215
- command: normalizeCommand(tool.args.command ?? ""),
215
+ command: normalizeCommand(tool.args.command ?? tool.args.cmd ?? commandFromRawArguments(tool.rawArguments)),
216
216
  items: [],
217
217
  previewLines: shown,
218
218
  errorLines: [],
@@ -362,7 +362,30 @@ function plural(count, singular, pluralValue) {
362
362
  }
363
363
  function normalizeCommand(value) {
364
364
  const command = String(value ?? "").replace(/\s+/g, " ").trim();
365
- return command || "(command)";
365
+ return command;
366
+ }
367
+ function commandFromRawArguments(rawArguments) {
368
+ if (!rawArguments)
369
+ return "";
370
+ try {
371
+ const parsed = JSON.parse(rawArguments);
372
+ if (parsed && typeof parsed === "object") {
373
+ const command = parsed.command ?? parsed.cmd;
374
+ return typeof command === "string" ? command : "";
375
+ }
376
+ }
377
+ catch {
378
+ const match = rawArguments.match(/"(?:command|cmd)"\s*:\s*"((?:\\.|[^"\\])*)/);
379
+ if (match?.[1]) {
380
+ try {
381
+ return JSON.parse(`"${match[1]}"`);
382
+ }
383
+ catch {
384
+ return match[1];
385
+ }
386
+ }
387
+ }
388
+ return "";
366
389
  }
367
390
  function displayToolName(name) {
368
391
  if (!name)
@@ -78,10 +78,8 @@ function logoColors(theme) {
78
78
  const COMPACT_LOGO = ["B", "U", "B", "B", "L", "E"];
79
79
  const WIDE_LOGO_MIN_WIDTH = 52;
80
80
  export function shouldShowWelcomeBanner({ startedWithVisibleHistory, }) {
81
- // Banner is committed to Static scrollback once at session start. Flipping
82
- // this flag back to false (e.g. when a picker opens) shrinks the Static
83
- // items list — when the items grow back, ink replays the banner a second
84
- // time into scrollback. Keep visibility decided purely by initial history.
81
+ // Keep banner visibility tied to the initial history, not transient overlays,
82
+ // so opening and closing a picker does not move it in the transcript.
85
83
  if (startedWithVisibleHistory)
86
84
  return false;
87
85
  return true;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bubblebrain-ai/bubble",
3
- "version": "0.0.10",
3
+ "version": "0.0.11",
4
4
  "description": "A terminal coding agent",
5
5
  "type": "module",
6
6
  "engines": {
@@ -24,8 +24,7 @@
24
24
  "test:watch": "vitest"
25
25
  },
26
26
  "dependencies": {
27
- "@opentui/core": "^0.1.99",
28
- "@opentui/solid": "^0.1.99",
27
+ "@larksuiteoapi/node-sdk": "^1.65.0",
29
28
  "@types/better-sqlite3": "^7.6.13",
30
29
  "@types/react": "^19.2.14",
31
30
  "@vue/language-server": "^3.2.7",
@@ -35,11 +34,10 @@
35
34
  "ink": "^7.0.3",
36
35
  "js-tiktoken": "^1.0.21",
37
36
  "openai": "^4.77.0",
38
- "opentui-spinner": "^0.0.6",
39
37
  "picomatch": "^4.0.4",
38
+ "qrcode-terminal": "^0.12.0",
40
39
  "react": "^19.2.6",
41
40
  "shiki": "^4.0.2",
42
- "solid-js": "^1.9.11",
43
41
  "string-width": "^8.2.1",
44
42
  "typescript-language-server": "^5.1.3",
45
43
  "vscode-jsonrpc": "^8.2.1",
@@ -49,6 +47,7 @@
49
47
  "@types/diff": "^7.0.0",
50
48
  "@types/node": "^22.0.0",
51
49
  "@types/picomatch": "^4.0.3",
50
+ "@types/qrcode-terminal": "^0.12.2",
52
51
  "@vitest/coverage-v8": "^4.1.4",
53
52
  "typescript": "^5.7.0",
54
53
  "vitest": "^4.1.4"
@@ -1 +0,0 @@
1
- export declare function copyTextToClipboard(text: string): Promise<void>;
@@ -1,53 +0,0 @@
1
- import { spawn } from "node:child_process";
2
- export async function copyTextToClipboard(text) {
3
- if (process.platform === "darwin") {
4
- await writeToProcess("pbcopy", [], text);
5
- return;
6
- }
7
- if (process.platform === "win32") {
8
- await writeToProcess("powershell", [
9
- "-NoProfile",
10
- "-Command",
11
- "Set-Clipboard -Value ([Console]::In.ReadToEnd())",
12
- ], text);
13
- return;
14
- }
15
- const candidates = [
16
- ["wl-copy", []],
17
- ["xclip", ["-selection", "clipboard"]],
18
- ["xsel", ["--clipboard", "--input"]],
19
- ];
20
- let lastError;
21
- for (const [command, args] of candidates) {
22
- try {
23
- await writeToProcess(command, args, text);
24
- return;
25
- }
26
- catch (error) {
27
- lastError = error;
28
- }
29
- }
30
- throw lastError instanceof Error ? lastError : new Error("No clipboard command available");
31
- }
32
- function writeToProcess(command, args, input) {
33
- return new Promise((resolve, reject) => {
34
- const child = spawn(command, args, {
35
- stdio: ["pipe", "ignore", "pipe"],
36
- windowsHide: true,
37
- });
38
- let stderr = "";
39
- child.stderr.setEncoding("utf8");
40
- child.stderr.on("data", (chunk) => {
41
- stderr += chunk;
42
- });
43
- child.on("error", reject);
44
- child.on("close", (code) => {
45
- if (code === 0) {
46
- resolve();
47
- return;
48
- }
49
- reject(new Error(stderr.trim() || `${command} exited with code ${code}`));
50
- });
51
- child.stdin.end(input);
52
- });
53
- }
@@ -1,44 +0,0 @@
1
- import type { ToolResultMetadata, TokenUsage } from "../types.js";
2
- export interface CompactionMeta {
3
- turns: number;
4
- messages: number;
5
- tokensSaved: number;
6
- summarySections: Array<{
7
- label: string;
8
- content: string;
9
- }>;
10
- contextWindow?: number;
11
- compactedAt: number;
12
- }
13
- export interface DisplayMessage {
14
- role: "user" | "assistant" | "error";
15
- content: string;
16
- reasoning?: string;
17
- toolCalls?: DisplayToolCall[];
18
- status?: "thinking" | "responding";
19
- streaming?: boolean;
20
- syntheticKind?: "ui_compact_card";
21
- hiddenCount?: number;
22
- compactionMeta?: CompactionMeta;
23
- turnStartedAt?: number;
24
- turnCompletedAt?: number;
25
- turnUsage?: TokenUsage;
26
- }
27
- export interface DisplayToolCall {
28
- id: string;
29
- name: string;
30
- args: Record<string, any>;
31
- rawArguments?: string;
32
- streamingArgs?: boolean;
33
- /** During streaming, an approximate line count derived from `\n` escapes in rawArguments. */
34
- streamingNewlineCount?: number;
35
- status?: "pending" | "running" | "completed" | "error";
36
- result?: string;
37
- isError?: boolean;
38
- metadata?: ToolResultMetadata;
39
- startedAt?: number;
40
- completedAt?: number;
41
- }
42
- export declare function compactDisplayMessages(messages: DisplayMessage[]): DisplayMessage[];
43
- export declare function truncateText(value: string, maxChars: number): string;
44
- export declare function formatCompactNumber(n: number): string;
@@ -1,243 +0,0 @@
1
- const MAX_VISIBLE_MESSAGES = 80;
2
- const FULL_DETAIL_WINDOW = 24;
3
- const MAX_OLD_CONTENT_CHARS = 1200;
4
- const MAX_OLD_REASONING_CHARS = 600;
5
- const MAX_OLD_TOOL_RESULT_CHARS = 800;
6
- const COMPACTION_SUMMARY_ITEMS = 6;
7
- const COMPACTION_FILE_LIMIT = 8;
8
- const TOOL_PATH_KEYS = ["file", "path", "paths", "filePath"];
9
- export function compactDisplayMessages(messages) {
10
- if (messages.length === 0) {
11
- return messages;
12
- }
13
- let hiddenCount = 0;
14
- let accumulatedTurns = 0;
15
- let accumulatedTokens = 0;
16
- const summarySections = [];
17
- const withoutSynthetic = messages.filter((message) => {
18
- if (message.syntheticKind !== "ui_compact_card") {
19
- return true;
20
- }
21
- hiddenCount += message.hiddenCount ?? 0;
22
- if (message.compactionMeta) {
23
- accumulatedTurns += message.compactionMeta.turns;
24
- accumulatedTokens += message.compactionMeta.tokensSaved;
25
- for (const section of message.compactionMeta.summarySections) {
26
- summarySections.push(section);
27
- }
28
- }
29
- return false;
30
- });
31
- const overflow = Math.max(0, withoutSynthetic.length - MAX_VISIBLE_MESSAGES);
32
- hiddenCount += overflow;
33
- const visible = overflow > 0 ? withoutSynthetic.slice(overflow) : withoutSynthetic;
34
- const detailStart = Math.max(0, visible.length - FULL_DETAIL_WINDOW);
35
- const compacted = visible.map((message, index) => {
36
- if (message.syntheticKind === "ui_compact_card") {
37
- return message;
38
- }
39
- return index < detailStart ? compactDisplayMessage(message) : message;
40
- });
41
- if (hiddenCount === 0) {
42
- return compacted;
43
- }
44
- const truncatedMessages = visible.slice(0, Math.max(1, detailStart));
45
- const extractedMeta = extractCompactionMeta(truncatedMessages, hiddenCount, accumulatedTurns, accumulatedTokens, summarySections);
46
- return [buildCompactCard(extractedMeta), ...compacted];
47
- }
48
- function extractCompactionMeta(truncatedMessages, hiddenCount, previousTurns, previousTokens, previousSections) {
49
- const turnsInBatch = countUserTurns(truncatedMessages);
50
- const totalTurns = previousTurns + turnsInBatch;
51
- const messagesInBatch = truncatedMessages.length;
52
- const totalMessages = hiddenCount;
53
- const estimatedTokens = estimateTokenSavings(truncatedMessages);
54
- const totalTokens = previousTokens + estimatedTokens;
55
- const sections = [
56
- ...previousSections,
57
- ...extractSummarySections(truncatedMessages),
58
- ];
59
- return {
60
- turns: totalTurns,
61
- messages: totalMessages,
62
- tokensSaved: totalTokens > 0 ? totalTokens : estimatedTokens,
63
- summarySections: mergeSummarySections(sections, COMPACTION_SUMMARY_ITEMS),
64
- compactedAt: Date.now(),
65
- };
66
- }
67
- function countUserTurns(messages) {
68
- return messages.filter((message) => message.role === "user").length;
69
- }
70
- function estimateTokenSavings(messages) {
71
- let chars = 0;
72
- for (const message of messages) {
73
- chars += message.content.length;
74
- chars += (message.reasoning?.length ?? 0);
75
- for (const tool of message.toolCalls ?? []) {
76
- chars += (tool.result?.length ?? 0);
77
- chars += JSON.stringify(tool.args).length;
78
- }
79
- }
80
- return Math.ceil(chars / 4);
81
- }
82
- function extractSummarySections(messages) {
83
- const sections = [];
84
- const userMessages = messages
85
- .filter((m) => m.role === "user")
86
- .map((m) => m.content);
87
- if (userMessages.length > 0) {
88
- sections.push({
89
- label: "Progress",
90
- content: userMessages.slice(0, 5).map((c) => `- ${shorten(c, 100)}`).join("\n"),
91
- });
92
- }
93
- const assistantInsights = messages
94
- .filter((m) => m.role === "assistant" && m.content.trim())
95
- .map((m) => m.content.trim());
96
- if (assistantInsights.length > 0) {
97
- sections.push({
98
- label: "Decisions",
99
- content: assistantInsights.slice(0, 3).map((c) => `- ${shorten(c, 120)}`).join("\n"),
100
- });
101
- }
102
- const files = collectFiles(messages);
103
- if (files.length > 0) {
104
- sections.push({
105
- label: "Files",
106
- content: files.slice(0, COMPACTION_FILE_LIMIT).join(", "),
107
- });
108
- }
109
- const toolFindings = collectToolFindings(messages);
110
- if (toolFindings.length > 0) {
111
- sections.push({
112
- label: "Tools",
113
- content: toolFindings.slice(0, 5).map((f) => `- ${f}`).join("\n"),
114
- });
115
- }
116
- return sections;
117
- }
118
- function collectFiles(messages) {
119
- const files = new Set();
120
- for (const message of messages) {
121
- for (const tool of message.toolCalls ?? []) {
122
- for (const key of TOOL_PATH_KEYS) {
123
- const value = tool.args[key];
124
- if (typeof value === "string" && value) {
125
- files.add(value);
126
- }
127
- if (Array.isArray(value)) {
128
- for (const item of value) {
129
- if (typeof item === "string" && item) {
130
- files.add(item);
131
- }
132
- }
133
- }
134
- }
135
- }
136
- }
137
- return [...files].slice(0, COMPACTION_FILE_LIMIT);
138
- }
139
- function collectToolFindings(messages) {
140
- const findings = [];
141
- for (const message of messages) {
142
- for (const tool of message.toolCalls ?? []) {
143
- if (tool.result && tool.result.length > 0) {
144
- findings.push(`${tool.name}: ${shorten(tool.result, 80)}`);
145
- if (findings.length >= 10)
146
- break;
147
- }
148
- }
149
- if (findings.length >= 10)
150
- break;
151
- }
152
- return findings;
153
- }
154
- function mergeSummarySections(sections, maxItems) {
155
- const merged = new Map();
156
- for (const section of sections) {
157
- const existing = merged.get(section.label);
158
- if (existing) {
159
- merged.set(section.label, `${existing}\n${section.content}`);
160
- }
161
- else {
162
- merged.set(section.label, section.content);
163
- }
164
- }
165
- return [...merged.entries()]
166
- .map(([label, content]) => ({ label, content }))
167
- .slice(0, maxItems);
168
- }
169
- function buildCompactCard(meta) {
170
- const formatNum = (n) => {
171
- if (n >= 1_000_000)
172
- return `${(n / 1_000_000).toFixed(1)}M`;
173
- if (n >= 1_000)
174
- return `${(n / 1_000).toFixed(1)}K`;
175
- return String(n);
176
- };
177
- const parts = [];
178
- if (meta.turns > 0) {
179
- parts.push(`${meta.turns} turn${meta.turns === 1 ? "" : "s"}`);
180
- }
181
- if (meta.messages > 0) {
182
- parts.push(`${meta.messages} message${meta.messages === 1 ? "" : "s"}`);
183
- }
184
- if (meta.tokensSaved > 0) {
185
- parts.push(`~${formatNum(meta.tokensSaved)} tokens`);
186
- }
187
- const statsLine = parts.length > 0 ? `┃ ${parts.join(" · ")}` : "";
188
- const sectionLines = [];
189
- for (const section of meta.summarySections) {
190
- sectionLines.push(`┃ ${section.label}: ${section.content.split("\n")[0]}`);
191
- }
192
- const content = [statsLine, ...sectionLines].filter(Boolean).join("\n");
193
- return {
194
- role: "assistant",
195
- content,
196
- syntheticKind: "ui_compact_card",
197
- hiddenCount: meta.messages,
198
- compactionMeta: meta,
199
- status: "responding",
200
- };
201
- }
202
- function compactDisplayMessage(message) {
203
- if (message.syntheticKind === "ui_compact_card") {
204
- return message;
205
- }
206
- return {
207
- ...message,
208
- content: truncateText(message.content, MAX_OLD_CONTENT_CHARS),
209
- reasoning: message.reasoning
210
- ? truncateText(message.reasoning, MAX_OLD_REASONING_CHARS)
211
- : message.reasoning,
212
- toolCalls: message.toolCalls?.map((toolCall) => ({
213
- ...toolCall,
214
- result: toolCall.result
215
- ? truncateText(toolCall.result, MAX_OLD_TOOL_RESULT_CHARS)
216
- : toolCall.result,
217
- })),
218
- };
219
- }
220
- export function truncateText(value, maxChars) {
221
- if (value.length <= maxChars) {
222
- return value;
223
- }
224
- const head = Math.max(1, Math.floor(maxChars * 0.7));
225
- const tail = Math.max(1, maxChars - head - 32);
226
- const omitted = value.length - head - tail;
227
- const separator = "─".repeat(12);
228
- return `${value.slice(0, head)}\n${separator} ✂ ${omitted} chars truncated ${separator}\n${value.slice(-tail)}`;
229
- }
230
- function shorten(text, maxChars) {
231
- const normalized = text.replace(/\s+/g, " ").trim();
232
- if (normalized.length <= maxChars) {
233
- return normalized;
234
- }
235
- return `${normalized.slice(0, maxChars - 1)}…`;
236
- }
237
- export function formatCompactNumber(n) {
238
- if (n >= 1_000_000)
239
- return `${(n / 1_000_000).toFixed(1)}M`;
240
- if (n >= 1_000)
241
- return `${(n / 1_000).toFixed(1)}K`;
242
- return String(n);
243
- }
@@ -1,15 +0,0 @@
1
- export type EscapeConfirmationDecision = {
2
- action: "arm";
3
- expiresAt: number;
4
- } | {
5
- action: "confirm";
6
- };
7
- export declare class EscapeConfirmationGate {
8
- private readonly windowMs;
9
- private armedRunId;
10
- private deadline;
11
- constructor(windowMs: number);
12
- press(runId: number, now?: number): EscapeConfirmationDecision;
13
- isArmed(runId: number, now?: number): boolean;
14
- clear(): void;
15
- }
@@ -1,30 +0,0 @@
1
- export class EscapeConfirmationGate {
2
- windowMs;
3
- armedRunId;
4
- deadline = 0;
5
- constructor(windowMs) {
6
- this.windowMs = windowMs;
7
- }
8
- press(runId, now = Date.now()) {
9
- if (this.armedRunId === runId && now <= this.deadline) {
10
- this.clear();
11
- return { action: "confirm" };
12
- }
13
- this.armedRunId = runId;
14
- this.deadline = now + this.windowMs;
15
- return { action: "arm", expiresAt: this.deadline };
16
- }
17
- isArmed(runId, now = Date.now()) {
18
- if (this.armedRunId !== runId)
19
- return false;
20
- if (now > this.deadline) {
21
- this.clear();
22
- return false;
23
- }
24
- return true;
25
- }
26
- clear() {
27
- this.armedRunId = undefined;
28
- this.deadline = 0;
29
- }
30
- }
@@ -1,29 +0,0 @@
1
- export interface AtContext {
2
- start: number;
3
- end: number;
4
- query: string;
5
- }
6
- export interface FileSuggestion {
7
- path: string;
8
- score: number;
9
- }
10
- export interface ExpandedMention {
11
- path: string;
12
- bytes: number;
13
- truncated: boolean;
14
- }
15
- export interface ExpandResult {
16
- text: string;
17
- expanded: ExpandedMention[];
18
- missing: string[];
19
- skipped: Array<{
20
- path: string;
21
- reason: string;
22
- bytes?: number;
23
- }>;
24
- }
25
- export declare function findAtContext(text: string, cursor: number): AtContext | null;
26
- export declare function filterFileSuggestions(files: string[], query: string, limit?: number): FileSuggestion[];
27
- export declare function listProjectFiles(cwd: string): Promise<string[]>;
28
- export declare function invalidateFileListCache(cwd?: string): void;
29
- export declare function expandAtMentions(text: string, cwd: string): Promise<ExpandResult>;