@bubblebrain-ai/bubble 0.0.49 → 0.0.50
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.
- package/dist/agent/abort-errors.d.ts +4 -0
- package/dist/agent/abort-errors.js +30 -0
- package/dist/agent/provider-fingerprint.d.ts +6 -0
- package/dist/agent/provider-fingerprint.js +89 -0
- package/dist/agent/resident-history.d.ts +7 -0
- package/dist/agent/resident-history.js +61 -0
- package/dist/agent/transcript-helpers.d.ts +20 -0
- package/dist/agent/transcript-helpers.js +62 -0
- package/dist/agent/update-queue.d.ts +13 -0
- package/dist/agent/update-queue.js +69 -0
- package/dist/agent.d.ts +32 -15
- package/dist/agent.js +79 -297
- package/dist/external-runtime/grok-runtime.js +1 -1
- package/dist/feishu/agent-host/run-driver.js +1 -1
- package/dist/lsp/service.js +2 -2
- package/dist/model-catalog.js +8 -6
- package/dist/permissions/settings.d.ts +0 -1
- package/dist/permissions/settings.js +0 -2
- package/dist/provider-registry.js +13 -1
- package/dist/provider-transform.js +7 -8
- package/dist/slash-commands/commands.js +13 -9
- package/dist/tools/grep.js +1 -1
- package/dist/tui-ink/app-helpers.d.ts +43 -0
- package/dist/tui-ink/app-helpers.js +150 -0
- package/dist/tui-ink/app.d.ts +4 -11
- package/dist/tui-ink/app.js +56 -645
- package/dist/tui-ink/command-palette.d.ts +32 -0
- package/dist/tui-ink/command-palette.js +158 -0
- package/dist/tui-ink/display-reconstruct.d.ts +3 -0
- package/dist/tui-ink/display-reconstruct.js +82 -0
- package/dist/tui-ink/rewind-picker.d.ts +8 -0
- package/dist/tui-ink/rewind-picker.js +102 -0
- package/dist/tui-ink/session-picker.js +1 -1
- package/dist/tui-ink/stats-panel.d.ts +11 -0
- package/dist/tui-ink/stats-panel.js +65 -0
- package/dist/tui-ink/waiting-indicator.d.ts +12 -0
- package/dist/tui-ink/waiting-indicator.js +129 -0
- package/package.json +1 -1
|
@@ -16,7 +16,7 @@ import { collectUsageStatsBundle, formatStatsText } from "../stats/usage.js";
|
|
|
16
16
|
import { buildMemoryPrompt, getMemoryStatus, isMemoryDisabled, resetMemory, searchMemory, } from "../memory/index.js";
|
|
17
17
|
import { feishuCommand } from "./feishu.js";
|
|
18
18
|
import { GROK_LOCAL_COMMAND_HELP } from "../external-runtime/grok-input-policy.js";
|
|
19
|
-
import { isGrokSubscriptionProviderId
|
|
19
|
+
import { isGrokSubscriptionProviderId } from "../external-runtime/grok-provider.js";
|
|
20
20
|
import { classifyExternalRuntimeBinding } from "../external-runtime/session-policy.js";
|
|
21
21
|
const VALID_SCOPES = ["user", "project", "local"];
|
|
22
22
|
const VALID_LISTS = ["allow", "deny"];
|
|
@@ -107,7 +107,7 @@ function persistSelectedModel(model, ctx) {
|
|
|
107
107
|
}
|
|
108
108
|
}
|
|
109
109
|
function syncSystemPrompt(ctx, model) {
|
|
110
|
-
const { providerId
|
|
110
|
+
const { providerId } = decodeModel(model);
|
|
111
111
|
const toolPromptOptions = typeof ctx.agent.getSystemPromptToolOptions === "function"
|
|
112
112
|
? ctx.agent.getSystemPromptToolOptions()
|
|
113
113
|
: {};
|
|
@@ -412,14 +412,14 @@ const builtinSlashCommandEntries = [
|
|
|
412
412
|
{
|
|
413
413
|
name: "skills",
|
|
414
414
|
description: "Open the searchable skills picker",
|
|
415
|
-
async handler(
|
|
415
|
+
async handler(_args, ctx) {
|
|
416
416
|
ctx.openPicker("skill");
|
|
417
417
|
},
|
|
418
418
|
},
|
|
419
419
|
{
|
|
420
420
|
name: "help",
|
|
421
421
|
description: "Show available slash commands",
|
|
422
|
-
async handler(
|
|
422
|
+
async handler(_args, ctx) {
|
|
423
423
|
if (hasExternalRuntimeSession(ctx)) {
|
|
424
424
|
const grok = isGrokSessionActive(ctx);
|
|
425
425
|
return [
|
|
@@ -448,14 +448,14 @@ const builtinSlashCommandEntries = [
|
|
|
448
448
|
{
|
|
449
449
|
name: "context",
|
|
450
450
|
description: "Show current context window usage and breakdown",
|
|
451
|
-
async handler(
|
|
451
|
+
async handler(_args, ctx) {
|
|
452
452
|
return `${formatContextUsage(ctx.agent.getContextUsageSnapshot())}\n\n${formatMcpContextStatus(ctx)}`;
|
|
453
453
|
},
|
|
454
454
|
},
|
|
455
455
|
{
|
|
456
456
|
name: "quit",
|
|
457
457
|
description: "Exit the application",
|
|
458
|
-
async handler(
|
|
458
|
+
async handler(_args, ctx) {
|
|
459
459
|
ctx.exit();
|
|
460
460
|
},
|
|
461
461
|
},
|
|
@@ -482,8 +482,12 @@ const builtinSlashCommandEntries = [
|
|
|
482
482
|
{
|
|
483
483
|
name: "clear",
|
|
484
484
|
description: "Clear the current conversation history",
|
|
485
|
-
async handler(
|
|
485
|
+
async handler(_args, ctx) {
|
|
486
486
|
ctx.agent.messages = ctx.agent.messages.filter((m) => m.role === "system" || m.role === "meta");
|
|
487
|
+
// The resident history just shrank: drop the incremental usage anchor
|
|
488
|
+
// too (same as /compact and /rewind), or budget accounting keeps
|
|
489
|
+
// measuring against the pre-clear message count.
|
|
490
|
+
ctx.agent.resetContextUsageAnchor();
|
|
487
491
|
ctx.sessionManager?.appendMarker("conversation_clear", "");
|
|
488
492
|
ctx.sessionManager?.clearTitleMetadata?.();
|
|
489
493
|
if (ctx.agent.getTodos().length > 0) {
|
|
@@ -845,7 +849,7 @@ const builtinSlashCommandEntries = [
|
|
|
845
849
|
{
|
|
846
850
|
name: "plan",
|
|
847
851
|
description: "Toggle plan mode on/off (Tab switches Build/Plan)",
|
|
848
|
-
async handler(
|
|
852
|
+
async handler(_args, ctx) {
|
|
849
853
|
const next = ctx.agent.mode === "plan" ? "default" : "plan";
|
|
850
854
|
ctx.agent.setMode(next);
|
|
851
855
|
return next === "plan"
|
|
@@ -1069,7 +1073,7 @@ const builtinSlashCommandEntries = [
|
|
|
1069
1073
|
{
|
|
1070
1074
|
name: "compact",
|
|
1071
1075
|
description: "Manually compact the current session context",
|
|
1072
|
-
async handler(
|
|
1076
|
+
async handler(_args, ctx) {
|
|
1073
1077
|
if (!ctx.sessionManager) {
|
|
1074
1078
|
return "Compaction requires session persistence. Start an interactive session first.";
|
|
1075
1079
|
}
|
package/dist/tools/grep.js
CHANGED
|
@@ -54,7 +54,7 @@ export function createGrepTool(cwd) {
|
|
|
54
54
|
}
|
|
55
55
|
rgArgs.push(searchPath);
|
|
56
56
|
return new Promise((resolve) => {
|
|
57
|
-
execFile("rg", rgArgs, { cwd, maxBuffer: 10 * 1024 * 1024 }, (
|
|
57
|
+
execFile("rg", rgArgs, { cwd, maxBuffer: 10 * 1024 * 1024 }, (_error, stdout, _stderr) => {
|
|
58
58
|
// rg returns exit code 1 when no matches found, which is not an error for us
|
|
59
59
|
const lines = stdout.split("\n").filter((l) => l.trim() !== "");
|
|
60
60
|
const matches = [];
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { Agent } from "../agent.js";
|
|
2
|
+
import { type ProviderRegistry } from "../provider-registry.js";
|
|
3
|
+
import type { BackgroundTaskInfo } from "../tasks/manager.js";
|
|
4
|
+
import { type DisplayMessage } from "./display-history.js";
|
|
5
|
+
export declare const TASK_WAKE_DEBOUNCE_MS = 2000;
|
|
6
|
+
export declare function buildTips(agent: Agent, registry: ProviderRegistry, grokActive?: boolean): string[];
|
|
7
|
+
/**
|
|
8
|
+
* Slash-command results arrive as plain strings; recognize the /model
|
|
9
|
+
* confirmations by shape so they render as accent-colored UI notices — the
|
|
10
|
+
* same treatment the picker path applies — instead of assistant prose.
|
|
11
|
+
*/
|
|
12
|
+
export declare function slashResultNoticeKind(result: string): DisplayMessage["syntheticKind"] | undefined;
|
|
13
|
+
/** Status-row summary for running background tasks (design §2.5). */
|
|
14
|
+
export declare function taskRowSummary(tasks: BackgroundTaskInfo[], nowTick: number): string;
|
|
15
|
+
export declare function friendlyCwd(cwd: string): string;
|
|
16
|
+
export declare function sessionBasename(sessionFile: string | undefined): string | undefined;
|
|
17
|
+
export declare function truncate(value: string, max: number): string;
|
|
18
|
+
export declare function formatCompactTokens(n: number): string;
|
|
19
|
+
export declare function formatContextUsageLabel(snapshot: {
|
|
20
|
+
usedTokens: number;
|
|
21
|
+
contextWindow?: number;
|
|
22
|
+
}): string;
|
|
23
|
+
/**
|
|
24
|
+
* Streaming tool arguments arrive as an incomplete JSON buffer. We can't
|
|
25
|
+
* JSON.parse() until the closing brace lands, but the user wants to see the
|
|
26
|
+
* short identifying fields (path, command, …) as soon as the model emits
|
|
27
|
+
* them so the tool row header reflects what's happening.
|
|
28
|
+
*
|
|
29
|
+
* Intentionally limited to short, single-line fields. Long fields like
|
|
30
|
+
* `content` are *not* surfaced live: rendering thousands of partial lines
|
|
31
|
+
* per delta floods the terminal and the partial value can break around
|
|
32
|
+
* unescaped sequences. The final value lands when the tool actually
|
|
33
|
+
* executes and tool_start delivers canonical args.
|
|
34
|
+
*/
|
|
35
|
+
export declare function parsePartialArgs(buffer: string, previous: Record<string, any>): Record<string, any>;
|
|
36
|
+
/**
|
|
37
|
+
* Coerce a freshly-constructed DisplayMessage into one that carries a stable
|
|
38
|
+
* `key`. Centralizes the safety net so callers don't have to remember to call
|
|
39
|
+
* nextDisplayMessageKey on every push.
|
|
40
|
+
*/
|
|
41
|
+
export declare function withMessageKey(message: DisplayMessage): DisplayMessage;
|
|
42
|
+
export declare const STREAMING_FLUSH_INTERVAL_MS = 40;
|
|
43
|
+
export declare function clampWindowStartForIndex(total: number, selectedIndex: number, maxVisible: number): number;
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure helpers for the Ink app: welcome tips, status-row summaries,
|
|
3
|
+
* cwd/session labels, truncation, context-usage formatting, partial
|
|
4
|
+
* streaming-arg extraction, display-message keying, and the scrolling-window
|
|
5
|
+
* clamp shared by the picker overlays.
|
|
6
|
+
*/
|
|
7
|
+
import os from "node:os";
|
|
8
|
+
import { displayModel } from "../provider-registry.js";
|
|
9
|
+
import { nextDisplayMessageKey } from "./display-history.js";
|
|
10
|
+
export const TASK_WAKE_DEBOUNCE_MS = 2000;
|
|
11
|
+
export function buildTips(agent, registry, grokActive = false) {
|
|
12
|
+
const tips = [];
|
|
13
|
+
if (grokActive) {
|
|
14
|
+
return [
|
|
15
|
+
"Grok Subscription is active with workspace tools and Bubble approvals",
|
|
16
|
+
"Use /model to choose the Grok model and reasoning effort",
|
|
17
|
+
"Use /logout grok to return to a fresh native session",
|
|
18
|
+
];
|
|
19
|
+
}
|
|
20
|
+
const hasProvider = registry.getEnabled().length > 0;
|
|
21
|
+
if (!hasProvider) {
|
|
22
|
+
tips.push("Run /login or /provider --add to configure a model");
|
|
23
|
+
}
|
|
24
|
+
else if (agent.model) {
|
|
25
|
+
tips.push(`Ready with ${displayModel(agent.model)}`);
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
tips.push("Run /model to pick a model");
|
|
29
|
+
}
|
|
30
|
+
tips.push("Type @ to reference a file");
|
|
31
|
+
tips.push("Type / for commands and skills");
|
|
32
|
+
return tips;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Slash-command results arrive as plain strings; recognize the /model
|
|
36
|
+
* confirmations by shape so they render as accent-colored UI notices — the
|
|
37
|
+
* same treatment the picker path applies — instead of assistant prose.
|
|
38
|
+
*/
|
|
39
|
+
export function slashResultNoticeKind(result) {
|
|
40
|
+
return /^(Grok m|M)odel switched to /.test(result) ? "ui_notice" : undefined;
|
|
41
|
+
}
|
|
42
|
+
/** Status-row summary for running background tasks (design §2.5). */
|
|
43
|
+
export function taskRowSummary(tasks, nowTick) {
|
|
44
|
+
const first = tasks[0];
|
|
45
|
+
const label = first.description?.trim() || first.command.replace(/\s+/g, " ").slice(0, 32);
|
|
46
|
+
const elapsed = Math.max(0, Math.round((nowTick - first.startedAt) / 1000));
|
|
47
|
+
return `${label} ${elapsed}s`;
|
|
48
|
+
}
|
|
49
|
+
export function friendlyCwd(cwd) {
|
|
50
|
+
const home = os.homedir();
|
|
51
|
+
if (cwd === home)
|
|
52
|
+
return "~";
|
|
53
|
+
if (cwd.startsWith(home + "/"))
|
|
54
|
+
return "~" + cwd.slice(home.length);
|
|
55
|
+
return cwd;
|
|
56
|
+
}
|
|
57
|
+
export function sessionBasename(sessionFile) {
|
|
58
|
+
if (!sessionFile)
|
|
59
|
+
return undefined;
|
|
60
|
+
const base = sessionFile.split("/").pop() ?? sessionFile;
|
|
61
|
+
return base.replace(/\.jsonl$/, "");
|
|
62
|
+
}
|
|
63
|
+
export function truncate(value, max) {
|
|
64
|
+
if (value.length <= max)
|
|
65
|
+
return value;
|
|
66
|
+
return `${value.slice(0, Math.max(0, max - 1))}…`;
|
|
67
|
+
}
|
|
68
|
+
export function formatCompactTokens(n) {
|
|
69
|
+
if (n < 1000)
|
|
70
|
+
return `${Math.round(n)}`;
|
|
71
|
+
if (n < 1_000_000)
|
|
72
|
+
return `${(n / 1000).toFixed(n >= 10000 ? 0 : 1)}K`;
|
|
73
|
+
return `${(n / 1_000_000).toFixed(n >= 10_000_000 ? 0 : 1)}M`;
|
|
74
|
+
}
|
|
75
|
+
export function formatContextUsageLabel(snapshot) {
|
|
76
|
+
if (!snapshot.contextWindow || snapshot.contextWindow <= 0) {
|
|
77
|
+
return `${formatCompactTokens(snapshot.usedTokens)} context`;
|
|
78
|
+
}
|
|
79
|
+
const percent = (snapshot.usedTokens / snapshot.contextWindow) * 100;
|
|
80
|
+
const label = percent >= 10
|
|
81
|
+
? `${Math.round(percent)}%`
|
|
82
|
+
: percent >= 0.05
|
|
83
|
+
? `${percent.toFixed(1)}%`
|
|
84
|
+
: "<0.1%";
|
|
85
|
+
return `${label} context`;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Streaming tool arguments arrive as an incomplete JSON buffer. We can't
|
|
89
|
+
* JSON.parse() until the closing brace lands, but the user wants to see the
|
|
90
|
+
* short identifying fields (path, command, …) as soon as the model emits
|
|
91
|
+
* them so the tool row header reflects what's happening.
|
|
92
|
+
*
|
|
93
|
+
* Intentionally limited to short, single-line fields. Long fields like
|
|
94
|
+
* `content` are *not* surfaced live: rendering thousands of partial lines
|
|
95
|
+
* per delta floods the terminal and the partial value can break around
|
|
96
|
+
* unescaped sequences. The final value lands when the tool actually
|
|
97
|
+
* executes and tool_start delivers canonical args.
|
|
98
|
+
*/
|
|
99
|
+
export function parsePartialArgs(buffer, previous) {
|
|
100
|
+
// If the buffer is now valid JSON, prefer the real parse.
|
|
101
|
+
try {
|
|
102
|
+
const parsed = JSON.parse(buffer);
|
|
103
|
+
if (parsed && typeof parsed === "object")
|
|
104
|
+
return parsed;
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
// fall through to partial extraction below
|
|
108
|
+
}
|
|
109
|
+
const result = { ...previous };
|
|
110
|
+
const FIELDS = ["path", "command", "pattern", "url", "query"];
|
|
111
|
+
for (const field of FIELDS) {
|
|
112
|
+
// Match a complete-looking quoted string. Requires a closing quote so we
|
|
113
|
+
// don't surface half-typed paths that may still change as bytes arrive.
|
|
114
|
+
const match = buffer.match(new RegExp(`"${field}"\\s*:\\s*"((?:\\\\.|[^"\\\\])*)"`));
|
|
115
|
+
if (match) {
|
|
116
|
+
const raw = match[1] ?? "";
|
|
117
|
+
result[field] = raw
|
|
118
|
+
.replace(/\\n/g, "\n")
|
|
119
|
+
.replace(/\\t/g, "\t")
|
|
120
|
+
.replace(/\\"/g, '"')
|
|
121
|
+
.replace(/\\\\/g, "\\");
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return result;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Coerce a freshly-constructed DisplayMessage into one that carries a stable
|
|
128
|
+
* `key`. Centralizes the safety net so callers don't have to remember to call
|
|
129
|
+
* nextDisplayMessageKey on every push.
|
|
130
|
+
*/
|
|
131
|
+
export function withMessageKey(message) {
|
|
132
|
+
if (message.key)
|
|
133
|
+
return message;
|
|
134
|
+
const prefix = message.role === "user" ? "user" : message.role === "error" ? "err" : "asst";
|
|
135
|
+
return { ...message, key: nextDisplayMessageKey(prefix) };
|
|
136
|
+
}
|
|
137
|
+
// Batch streaming text deltas before committing them to React state. Without
|
|
138
|
+
// <Static>, every commit re-renders the full-screen frame; per-token commits
|
|
139
|
+
// would make Yoga re-lay-out the transcript for every few bytes of output.
|
|
140
|
+
// 40ms keeps perceived latency invisible while capping layout work at 25fps.
|
|
141
|
+
export const STREAMING_FLUSH_INTERVAL_MS = 40;
|
|
142
|
+
export function clampWindowStartForIndex(total, selectedIndex, maxVisible) {
|
|
143
|
+
if (total <= maxVisible)
|
|
144
|
+
return 0;
|
|
145
|
+
const half = Math.floor(maxVisible / 2);
|
|
146
|
+
let start = Math.max(0, selectedIndex - half);
|
|
147
|
+
if (start + maxVisible > total)
|
|
148
|
+
start = total - maxVisible;
|
|
149
|
+
return Math.max(0, start);
|
|
150
|
+
}
|
package/dist/tui-ink/app.d.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { type Agent } from "../agent.js";
|
|
2
2
|
import type { CliArgs } from "../cli.js";
|
|
3
3
|
import { SessionManager } from "../session.js";
|
|
4
|
-
import type {
|
|
5
|
-
import { type DisplayMessage } from "./display-history.js";
|
|
4
|
+
import type { PlanDecision, Provider } from "../types.js";
|
|
6
5
|
import { type ResolvedTheme, type ThemeMode } from "./theme.js";
|
|
7
6
|
import { ProviderRegistry } from "../provider-registry.js";
|
|
8
7
|
import { SkillRegistry } from "../skills/registry.js";
|
|
@@ -18,6 +17,8 @@ import type { GoalStore } from "../goal/store.js";
|
|
|
18
17
|
import { isInternalBlockOnlyContent } from "../agent/internal-reminder-sanitizer.js";
|
|
19
18
|
import type { ProcessManager } from "../tasks/manager.js";
|
|
20
19
|
import type { PromotionChannel } from "../tasks/promotion.js";
|
|
20
|
+
export { reconstructDisplayMessages } from "./display-reconstruct.js";
|
|
21
|
+
export { INK_LOCAL_SLASH_COMMANDS } from "./command-palette.js";
|
|
21
22
|
import type { ExternalRuntimeManager } from "../external-runtime/types.js";
|
|
22
23
|
export interface PlanHandlerRef {
|
|
23
24
|
current?: (plan: string) => Promise<PlanDecision>;
|
|
@@ -72,12 +73,4 @@ export interface ExitSummary {
|
|
|
72
73
|
wallMs: number;
|
|
73
74
|
}
|
|
74
75
|
export { isInternalBlockOnlyContent };
|
|
75
|
-
export declare function
|
|
76
|
-
export declare const INK_LOCAL_SLASH_COMMANDS: readonly [{
|
|
77
|
-
readonly name: "goal";
|
|
78
|
-
readonly description: "Set/manage an autonomous goal (/goal <objective>|clear|pause|resume|edit)";
|
|
79
|
-
}, {
|
|
80
|
-
readonly name: "loop";
|
|
81
|
-
readonly description: "Run a prompt on a recurring interval (/loop 5m <prompt>|list|stop [id])";
|
|
82
|
-
}];
|
|
83
|
-
export declare function App({ agent, args, sessionManager: initialSessionManager, switchSession, createProvider, registry, skillRegistry, planHandlerRef, approvalHandlerRef, questionController, bashAllowlist, settingsManager, lspService, mcpManager, themeMode: initialThemeMode, themeOverrides, detectedTheme, onThemeModeChange, flushMemory, runMemoryCompaction, runMemorySummary, runMemoryRefresh, goalStore, processManager, tasksAutoResume, promotionChannel, bypassEnabled, updateNotice, updateNoticeRefresh, hookController, externalRuntime, onExit }: AppProps): import("react/jsx-runtime").JSX.Element;
|
|
76
|
+
export declare function App({ agent, args, sessionManager: initialSessionManager, switchSession, createProvider, registry, skillRegistry, planHandlerRef, approvalHandlerRef, questionController, bashAllowlist, settingsManager, lspService, mcpManager, themeMode: initialThemeMode, themeOverrides, detectedTheme, onThemeModeChange, flushMemory, runMemoryCompaction, runMemorySummary, runMemoryRefresh, goalStore, processManager, tasksAutoResume, promotionChannel, updateNotice, updateNoticeRefresh, hookController, externalRuntime, onExit }: AppProps): import("react/jsx-runtime").JSX.Element;
|