@bubblebrain-ai/bubble 0.0.12 → 0.0.13
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/input-controller.d.ts +11 -0
- package/dist/agent/input-controller.js +30 -0
- package/dist/agent.d.ts +6 -4
- package/dist/agent.js +38 -0
- package/dist/main.js +58 -9
- package/dist/slash-commands/commands.js +27 -0
- package/dist/slash-commands/types.d.ts +10 -0
- package/dist/tui/clipboard.d.ts +1 -0
- package/dist/tui/clipboard.js +53 -0
- package/dist/tui/detect-theme.d.ts +2 -0
- package/dist/tui/detect-theme.js +87 -0
- package/dist/tui/display-history.d.ts +62 -0
- package/dist/tui/display-history.js +305 -0
- package/dist/tui/edit-diff.d.ts +11 -0
- package/dist/tui/edit-diff.js +52 -0
- package/dist/tui/escape-confirmation.d.ts +15 -0
- package/dist/tui/escape-confirmation.js +30 -0
- package/dist/tui/file-mentions.d.ts +29 -0
- package/dist/tui/file-mentions.js +174 -0
- package/dist/tui/global-key-router.d.ts +3 -0
- package/dist/tui/global-key-router.js +87 -0
- package/dist/tui/image-paste.d.ts +95 -0
- package/dist/tui/image-paste.js +505 -0
- package/dist/tui/input-history.d.ts +16 -0
- package/dist/tui/input-history.js +79 -0
- package/dist/tui/markdown-inline.d.ts +22 -0
- package/dist/tui/markdown-inline.js +68 -0
- package/dist/tui/markdown-theme-rules.d.ts +23 -0
- package/dist/tui/markdown-theme-rules.js +164 -0
- package/dist/tui/markdown-theme.d.ts +5 -0
- package/dist/tui/markdown-theme.js +27 -0
- package/dist/tui/opencode-spinner.d.ts +22 -0
- package/dist/tui/opencode-spinner.js +216 -0
- package/dist/tui/prompt-keybindings.d.ts +42 -0
- package/dist/tui/prompt-keybindings.js +35 -0
- package/dist/tui/recent-activity.d.ts +8 -0
- package/dist/tui/recent-activity.js +71 -0
- package/dist/tui/render-signature.d.ts +1 -0
- package/dist/tui/render-signature.js +7 -0
- package/dist/tui/run.d.ts +45 -0
- package/dist/tui/run.js +8816 -0
- package/dist/tui/session-display.d.ts +6 -0
- package/dist/tui/session-display.js +12 -0
- package/dist/tui/sidebar-mcp.d.ts +31 -0
- package/dist/tui/sidebar-mcp.js +62 -0
- package/dist/tui/sidebar-state.d.ts +12 -0
- package/dist/tui/sidebar-state.js +69 -0
- package/dist/tui/streaming-tool-args.d.ts +15 -0
- package/dist/tui/streaming-tool-args.js +30 -0
- package/dist/tui/tool-renderers/fallback.d.ts +2 -0
- package/dist/tui/tool-renderers/fallback.js +75 -0
- package/dist/tui/tool-renderers/registry.d.ts +3 -0
- package/dist/tui/tool-renderers/registry.js +11 -0
- package/dist/tui/tool-renderers/subagent.d.ts +2 -0
- package/dist/tui/tool-renderers/subagent.js +135 -0
- package/dist/tui/tool-renderers/types.d.ts +36 -0
- package/dist/tui/tool-renderers/types.js +1 -0
- package/dist/tui/tool-renderers/write-preview.d.ts +12 -0
- package/dist/tui/tool-renderers/write-preview.js +30 -0
- package/dist/tui/tool-renderers/write.d.ts +6 -0
- package/dist/tui/tool-renderers/write.js +88 -0
- package/dist/tui/trace-groups.d.ts +27 -0
- package/dist/tui/trace-groups.js +412 -0
- package/dist/tui/wordmark.d.ts +15 -0
- package/dist/tui/wordmark.js +179 -0
- package/dist/tui-ink/app.js +44 -5
- package/dist/tui-ink/message-list.js +9 -1
- package/dist/tui-ink/theme.d.ts +3 -9
- package/dist/tui-ink/theme.js +39 -45
- package/dist/tui-ink/welcome.js +22 -78
- package/dist/tui-opentui/app.d.ts +54 -0
- package/dist/tui-opentui/app.js +1363 -0
- package/dist/tui-opentui/approval/approval-dialog.d.ts +15 -0
- package/dist/tui-opentui/approval/approval-dialog.js +139 -0
- package/dist/tui-opentui/approval/diff-view.d.ts +9 -0
- package/dist/tui-opentui/approval/diff-view.js +43 -0
- package/dist/tui-opentui/approval/select.d.ts +37 -0
- package/dist/tui-opentui/approval/select.js +91 -0
- package/dist/tui-opentui/detect-theme.d.ts +2 -0
- package/dist/tui-opentui/detect-theme.js +87 -0
- package/dist/tui-opentui/display-history.d.ts +55 -0
- package/dist/tui-opentui/display-history.js +129 -0
- package/dist/tui-opentui/edit-diff.d.ts +11 -0
- package/dist/tui-opentui/edit-diff.js +52 -0
- package/dist/tui-opentui/feedback-dialog.d.ts +21 -0
- package/dist/tui-opentui/feedback-dialog.js +164 -0
- package/dist/tui-opentui/feishu-setup-picker.d.ts +7 -0
- package/dist/tui-opentui/feishu-setup-picker.js +272 -0
- package/dist/tui-opentui/file-mentions.d.ts +29 -0
- package/dist/tui-opentui/file-mentions.js +174 -0
- package/dist/tui-opentui/footer.d.ts +26 -0
- package/dist/tui-opentui/footer.js +40 -0
- package/dist/tui-opentui/image-paste.d.ts +54 -0
- package/dist/tui-opentui/image-paste.js +288 -0
- package/dist/tui-opentui/input-box.d.ts +34 -0
- package/dist/tui-opentui/input-box.js +471 -0
- package/dist/tui-opentui/input-history.d.ts +16 -0
- package/dist/tui-opentui/input-history.js +79 -0
- package/dist/tui-opentui/markdown.d.ts +66 -0
- package/dist/tui-opentui/markdown.js +127 -0
- package/dist/tui-opentui/message-list.d.ts +31 -0
- package/dist/tui-opentui/message-list.js +125 -0
- package/dist/tui-opentui/model-picker.d.ts +63 -0
- package/dist/tui-opentui/model-picker.js +450 -0
- package/dist/tui-opentui/plan-confirm.d.ts +9 -0
- package/dist/tui-opentui/plan-confirm.js +124 -0
- package/dist/tui-opentui/question-dialog.d.ts +10 -0
- package/dist/tui-opentui/question-dialog.js +110 -0
- package/dist/tui-opentui/recent-activity.d.ts +8 -0
- package/dist/tui-opentui/recent-activity.js +71 -0
- package/dist/tui-opentui/run-session-picker.d.ts +10 -0
- package/dist/tui-opentui/run-session-picker.js +28 -0
- package/dist/tui-opentui/run.d.ts +38 -0
- package/dist/tui-opentui/run.js +48 -0
- package/dist/tui-opentui/session-picker.d.ts +12 -0
- package/dist/tui-opentui/session-picker.js +120 -0
- package/dist/tui-opentui/theme.d.ts +89 -0
- package/dist/tui-opentui/theme.js +157 -0
- package/dist/tui-opentui/todos.d.ts +9 -0
- package/dist/tui-opentui/todos.js +45 -0
- package/dist/tui-opentui/trace-groups.d.ts +27 -0
- package/dist/tui-opentui/trace-groups.js +412 -0
- package/dist/tui-opentui/use-terminal-size.d.ts +4 -0
- package/dist/tui-opentui/use-terminal-size.js +5 -0
- package/dist/tui-opentui/welcome.d.ts +25 -0
- package/dist/tui-opentui/welcome.js +77 -0
- package/dist/types.d.ts +24 -0
- package/package.json +5 -1
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { AgentInputController, AgentRunInput } from "../types.js";
|
|
2
|
+
export declare class AgentRunInputQueue implements AgentInputController {
|
|
3
|
+
private readonly idPrefix;
|
|
4
|
+
private pending;
|
|
5
|
+
private nextInputId;
|
|
6
|
+
constructor(idPrefix?: string);
|
|
7
|
+
enqueue(content: string): AgentRunInput;
|
|
8
|
+
drainPendingInputs(): AgentRunInput[];
|
|
9
|
+
pendingInputCount(): number;
|
|
10
|
+
clear(): AgentRunInput[];
|
|
11
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export class AgentRunInputQueue {
|
|
2
|
+
idPrefix;
|
|
3
|
+
pending = [];
|
|
4
|
+
nextInputId = 0;
|
|
5
|
+
constructor(idPrefix = "input") {
|
|
6
|
+
this.idPrefix = idPrefix;
|
|
7
|
+
}
|
|
8
|
+
enqueue(content) {
|
|
9
|
+
const input = {
|
|
10
|
+
id: `${this.idPrefix}-${++this.nextInputId}`,
|
|
11
|
+
content,
|
|
12
|
+
submittedAt: Date.now(),
|
|
13
|
+
};
|
|
14
|
+
this.pending.push(input);
|
|
15
|
+
return input;
|
|
16
|
+
}
|
|
17
|
+
drainPendingInputs() {
|
|
18
|
+
if (this.pending.length === 0)
|
|
19
|
+
return [];
|
|
20
|
+
const inputs = this.pending;
|
|
21
|
+
this.pending = [];
|
|
22
|
+
return inputs;
|
|
23
|
+
}
|
|
24
|
+
pendingInputCount() {
|
|
25
|
+
return this.pending.length;
|
|
26
|
+
}
|
|
27
|
+
clear() {
|
|
28
|
+
return this.drainPendingInputs();
|
|
29
|
+
}
|
|
30
|
+
}
|
package/dist/agent.d.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* It maintains message state, calls the LLM, executes tools, and auto-continues.
|
|
4
4
|
*/
|
|
5
5
|
import { type ContextUsageSnapshot } from "./context/usage.js";
|
|
6
|
-
import type { AgentEvent, ContentPart, PermissionMode, Message, Provider, ThinkingLevel, Todo, ToolResult, ToolRegistryEntry, ToolUpdate } from "./types.js";
|
|
6
|
+
import type { AgentEvent, AgentInputController, ContentPart, PermissionMode, Message, Provider, ThinkingLevel, Todo, ToolResult, ToolRegistryEntry, ToolUpdate } from "./types.js";
|
|
7
7
|
import { type TurnHooks } from "./orchestrator/hooks.js";
|
|
8
8
|
import { type AgentCategoriesConfig, type ResolvedSubagentRoute } from "./agent/categories.js";
|
|
9
9
|
import { BudgetLedger } from "./agent/budget-ledger.js";
|
|
@@ -46,6 +46,10 @@ export interface AgentOptions {
|
|
|
46
46
|
agentCategories?: AgentCategoriesConfig;
|
|
47
47
|
providerFactory?: (route: ResolvedSubagentRoute) => Provider | Promise<Provider>;
|
|
48
48
|
}
|
|
49
|
+
export interface AgentRunOptions {
|
|
50
|
+
abortSignal?: AbortSignal;
|
|
51
|
+
inputController?: AgentInputController;
|
|
52
|
+
}
|
|
49
53
|
export declare class Agent {
|
|
50
54
|
messages: Message[];
|
|
51
55
|
private provider;
|
|
@@ -116,9 +120,7 @@ export declare class Agent {
|
|
|
116
120
|
/** Internal: snapshot counter that bumps on every setTodos. Used by run loop to detect mutations. */
|
|
117
121
|
get todosVersion(): number;
|
|
118
122
|
setSystemPrompt(prompt: string): void;
|
|
119
|
-
run(userInput: string | ContentPart[], cwd: string, options?:
|
|
120
|
-
abortSignal?: AbortSignal;
|
|
121
|
-
}): AsyncIterable<AgentEvent>;
|
|
123
|
+
run(userInput: string | ContentPart[], cwd: string, options?: AgentRunOptions): AsyncIterable<AgentEvent>;
|
|
122
124
|
private recoverFromOverflow;
|
|
123
125
|
compactResidentHistory(): void;
|
|
124
126
|
private maybeCompactWithLLM;
|
package/dist/agent.js
CHANGED
|
@@ -236,6 +236,7 @@ export class Agent {
|
|
|
236
236
|
}
|
|
237
237
|
async *run(userInput, cwd, options = {}) {
|
|
238
238
|
const abortSignal = composeAbortSignals([options.abortSignal, this.budgetLedger?.signal]);
|
|
239
|
+
const inputController = options.inputController;
|
|
239
240
|
throwIfAborted(abortSignal);
|
|
240
241
|
const hookBus = new HookBus();
|
|
241
242
|
for (const hooks of createDefaultHooks()) {
|
|
@@ -249,6 +250,39 @@ export class Agent {
|
|
|
249
250
|
const queueReminder = (reminder) => {
|
|
250
251
|
reminderQueue.push(reminder);
|
|
251
252
|
};
|
|
253
|
+
const pendingInputCount = () => inputController?.pendingInputCount() ?? 0;
|
|
254
|
+
const applyPendingInputs = () => {
|
|
255
|
+
const pendingInputs = inputController?.drainPendingInputs() ?? [];
|
|
256
|
+
if (pendingInputs.length === 0)
|
|
257
|
+
return [];
|
|
258
|
+
for (const input of pendingInputs) {
|
|
259
|
+
this.appendMessage({ role: "user", content: input.content });
|
|
260
|
+
}
|
|
261
|
+
return [
|
|
262
|
+
...pendingInputs.map((input) => ({
|
|
263
|
+
type: "input_applied",
|
|
264
|
+
id: input.id,
|
|
265
|
+
content: input.content,
|
|
266
|
+
target: "current_turn",
|
|
267
|
+
})),
|
|
268
|
+
{ type: "input_pending_changed", pending: pendingInputCount() },
|
|
269
|
+
];
|
|
270
|
+
};
|
|
271
|
+
const rejectPendingInputs = (reason) => {
|
|
272
|
+
const pendingInputs = inputController?.drainPendingInputs() ?? [];
|
|
273
|
+
if (pendingInputs.length === 0)
|
|
274
|
+
return [];
|
|
275
|
+
return [
|
|
276
|
+
...pendingInputs.map((input) => ({
|
|
277
|
+
type: "input_rejected",
|
|
278
|
+
id: input.id,
|
|
279
|
+
content: input.content,
|
|
280
|
+
reason,
|
|
281
|
+
target: "next_turn",
|
|
282
|
+
})),
|
|
283
|
+
{ type: "input_pending_changed", pending: pendingInputCount() },
|
|
284
|
+
];
|
|
285
|
+
};
|
|
252
286
|
const flushGovernorReminders = () => {
|
|
253
287
|
for (const reminder of reminderQueue.splice(0, reminderQueue.length)) {
|
|
254
288
|
this.injectSystemReminder(reminder);
|
|
@@ -275,6 +309,8 @@ export class Agent {
|
|
|
275
309
|
flushGovernorReminders();
|
|
276
310
|
for (const update of this.drainSubagentToolUpdates())
|
|
277
311
|
yield update;
|
|
312
|
+
for (const event of applyPendingInputs())
|
|
313
|
+
yield event;
|
|
278
314
|
yield { type: "turn_start" };
|
|
279
315
|
step += 1;
|
|
280
316
|
hookState.turnCount = step;
|
|
@@ -604,6 +640,8 @@ export class Agent {
|
|
|
604
640
|
delete hookState.forceContinuationReason;
|
|
605
641
|
continue;
|
|
606
642
|
}
|
|
643
|
+
for (const event of rejectPendingInputs("no_continuation"))
|
|
644
|
+
yield event;
|
|
607
645
|
break;
|
|
608
646
|
}
|
|
609
647
|
for (const update of this.drainSubagentToolUpdates())
|
package/dist/main.js
CHANGED
|
@@ -25,6 +25,8 @@ import { McpManager } from "./mcp/manager.js";
|
|
|
25
25
|
import { QuestionController } from "./question/index.js";
|
|
26
26
|
import { buildMemoryPrompt, formatMemoryStartupResult, recordMemoryCitations, runMemoryPhase2, runMemoryStartupPipeline, startMemoryStartupTask, } from "./memory/index.js";
|
|
27
27
|
import { basename } from "node:path";
|
|
28
|
+
import { normalizeSingleLine, truncateVisual } from "./text-display.js";
|
|
29
|
+
import { BUBBLE_WORDMARK } from "./tui/wordmark.js";
|
|
28
30
|
async function main() {
|
|
29
31
|
const args = parseArgs(process.argv.slice(2));
|
|
30
32
|
if (process.argv.includes("-h") || process.argv.includes("--help")) {
|
|
@@ -183,7 +185,7 @@ async function main() {
|
|
|
183
185
|
// - --resume (no name): show interactive picker
|
|
184
186
|
let sessionManager;
|
|
185
187
|
let resumedExistingSession = false;
|
|
186
|
-
// Resolved before any
|
|
188
|
+
// Resolved before any TUI render so picker and main TUI share the same value
|
|
187
189
|
// and we only run OSC 11 once.
|
|
188
190
|
let preResolvedTheme;
|
|
189
191
|
if (args.resume && !args.sessionName) {
|
|
@@ -195,13 +197,13 @@ async function main() {
|
|
|
195
197
|
else {
|
|
196
198
|
const themeConfig = userConfig.getTheme();
|
|
197
199
|
if (themeConfig.mode === "auto") {
|
|
198
|
-
const { detectTerminalTheme } = await import("./tui
|
|
200
|
+
const { detectTerminalTheme } = await import("./tui/detect-theme.js");
|
|
199
201
|
preResolvedTheme = await detectTerminalTheme();
|
|
200
202
|
}
|
|
201
203
|
else {
|
|
202
204
|
preResolvedTheme = themeConfig.mode;
|
|
203
205
|
}
|
|
204
|
-
const { runSessionPicker } = await import("./tui-
|
|
206
|
+
const { runSessionPicker } = await import("./tui-opentui/run-session-picker.js");
|
|
205
207
|
const picked = await runSessionPicker({
|
|
206
208
|
currentCwd: args.cwd,
|
|
207
209
|
currentSessions,
|
|
@@ -422,9 +424,9 @@ async function main() {
|
|
|
422
424
|
detectedTheme = preResolvedTheme;
|
|
423
425
|
}
|
|
424
426
|
else if (themeConfig.mode === "auto") {
|
|
425
|
-
// Probe before
|
|
426
|
-
// runtime
|
|
427
|
-
const { detectTerminalTheme } = await import("./tui
|
|
427
|
+
// Probe before OpenTUI owns stdin. OSC 11 needs raw mode, and the
|
|
428
|
+
// runtime renderer can consume the reply before startup code sees it.
|
|
429
|
+
const { detectTerminalTheme } = await import("./tui/detect-theme.js");
|
|
428
430
|
detectedTheme = await detectTerminalTheme();
|
|
429
431
|
}
|
|
430
432
|
else {
|
|
@@ -447,7 +449,7 @@ async function main() {
|
|
|
447
449
|
runMemorySummary,
|
|
448
450
|
runMemoryRefresh,
|
|
449
451
|
};
|
|
450
|
-
const { runTui } = await import("./tui
|
|
452
|
+
const { runTui } = await import("./tui/run.js");
|
|
451
453
|
await runTui(agent, args, {
|
|
452
454
|
...commonOptions,
|
|
453
455
|
themeMode: themeConfig.mode,
|
|
@@ -456,14 +458,61 @@ async function main() {
|
|
|
456
458
|
onThemeModeChange: (mode) => userConfig.setThemeMode(mode),
|
|
457
459
|
});
|
|
458
460
|
if (sessionManager) {
|
|
459
|
-
|
|
460
|
-
|
|
461
|
+
printOpenTuiExitSummary(sessionManager, {
|
|
462
|
+
resumed: resumedExistingSession,
|
|
463
|
+
theme: detectedTheme,
|
|
464
|
+
});
|
|
461
465
|
}
|
|
462
466
|
}
|
|
463
467
|
finally {
|
|
464
468
|
await shutdownRuntime();
|
|
465
469
|
}
|
|
466
470
|
}
|
|
471
|
+
function printOpenTuiExitSummary(sessionManager, options) {
|
|
472
|
+
if (!process.stdout.isTTY)
|
|
473
|
+
return;
|
|
474
|
+
const sessionName = basename(sessionManager.getSessionFile());
|
|
475
|
+
const sessionId = sessionName.replace(/\.jsonl$/, "");
|
|
476
|
+
const title = truncateVisual(normalizeSingleLine(sessionManager.getMetadata().title ?? ""), 64);
|
|
477
|
+
const sessionLabel = title || `${options.resumed ? "Session" : "New session"} - ${sessionId}`;
|
|
478
|
+
const continueCommand = `bubble --resume --session ${sessionName}`;
|
|
479
|
+
const colors = options.theme === "light"
|
|
480
|
+
? {
|
|
481
|
+
markMuted: chalk.hex("#8C8C8C"),
|
|
482
|
+
markStrong: chalk.hex("#1C1C1C"),
|
|
483
|
+
markBrand: chalk.hex("#8B4A00"),
|
|
484
|
+
label: chalk.hex("#6F7377"),
|
|
485
|
+
value: chalk.hex("#171717").bold,
|
|
486
|
+
}
|
|
487
|
+
: {
|
|
488
|
+
markMuted: chalk.hex("#9CA3AF"),
|
|
489
|
+
markStrong: chalk.hex("#F4F4F5"),
|
|
490
|
+
markBrand: chalk.hex("#F5A742"),
|
|
491
|
+
label: chalk.hex("#808080"),
|
|
492
|
+
value: chalk.hex("#EEEEEE").bold,
|
|
493
|
+
};
|
|
494
|
+
const label = (value) => colors.label(value.padEnd(10));
|
|
495
|
+
const logoColor = (tone) => {
|
|
496
|
+
switch (tone) {
|
|
497
|
+
case "brand": return colors.markBrand;
|
|
498
|
+
case "ink": return colors.markStrong;
|
|
499
|
+
case "stone": return colors.markMuted;
|
|
500
|
+
case "soft": return colors.label;
|
|
501
|
+
case "caption": return colors.label;
|
|
502
|
+
}
|
|
503
|
+
};
|
|
504
|
+
for (const line of BUBBLE_WORDMARK) {
|
|
505
|
+
if (line.segments) {
|
|
506
|
+
console.log(line.segments.map((segment) => logoColor(segment.tone)(segment.text)).join(""));
|
|
507
|
+
}
|
|
508
|
+
else {
|
|
509
|
+
console.log(logoColor(line.tone ?? "caption")(line.text ?? ""));
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
console.log();
|
|
513
|
+
console.log(`${label("Session")}${colors.value(sessionLabel)}`);
|
|
514
|
+
console.log(`${label("Continue")}${colors.value(continueCommand)}`);
|
|
515
|
+
}
|
|
467
516
|
async function readPipedStdin() {
|
|
468
517
|
if (process.stdin.isTTY)
|
|
469
518
|
return undefined;
|
|
@@ -312,6 +312,33 @@ const builtinSlashCommandEntries = [
|
|
|
312
312
|
return `Theme set to ${arg}${arg === "auto" ? ` (resolved to ${resolved})` : ""}.`;
|
|
313
313
|
},
|
|
314
314
|
},
|
|
315
|
+
{
|
|
316
|
+
name: "sidebar",
|
|
317
|
+
description: "Toggle the right sidebar. Usage: /sidebar [open|close|auto]",
|
|
318
|
+
async handler(args, ctx) {
|
|
319
|
+
if (!ctx.toggleSidebar || !ctx.setSidebarMode) {
|
|
320
|
+
return "Sidebar control is only available inside the TUI.";
|
|
321
|
+
}
|
|
322
|
+
const arg = args.trim().toLowerCase();
|
|
323
|
+
if (!arg) {
|
|
324
|
+
ctx.toggleSidebar();
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
if (["open", "show", "expand", "expanded", "on"].includes(arg)) {
|
|
328
|
+
ctx.setSidebarMode("expanded");
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
if (["close", "hide", "collapse", "collapsed", "off"].includes(arg)) {
|
|
332
|
+
ctx.setSidebarMode("collapsed");
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
if (arg === "auto") {
|
|
336
|
+
ctx.setSidebarMode("auto");
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
return "Usage: /sidebar [open|close|auto]";
|
|
340
|
+
},
|
|
341
|
+
},
|
|
315
342
|
{
|
|
316
343
|
name: "clear",
|
|
317
344
|
description: "Clear the current conversation history",
|
|
@@ -9,6 +9,12 @@ import type { McpManager } from "../mcp/manager.js";
|
|
|
9
9
|
import type { LspService } from "../lsp/index.js";
|
|
10
10
|
import type { MemoryScope } from "../memory/index.js";
|
|
11
11
|
import type { ThemeMode } from "../config.js";
|
|
12
|
+
export type SidebarMode = "auto" | "expanded" | "collapsed";
|
|
13
|
+
export interface SidebarCommandState {
|
|
14
|
+
mode: SidebarMode;
|
|
15
|
+
visible: boolean;
|
|
16
|
+
active: boolean;
|
|
17
|
+
}
|
|
12
18
|
export interface SlashCommandContext {
|
|
13
19
|
agent: Agent;
|
|
14
20
|
addMessage: (role: "user" | "assistant" | "error", content: string) => void;
|
|
@@ -34,6 +40,10 @@ export interface SlashCommandContext {
|
|
|
34
40
|
getResolvedTheme?: () => "light" | "dark";
|
|
35
41
|
/** Persist a new theme mode AND apply it to the running TUI. */
|
|
36
42
|
setThemeMode?: (mode: ThemeMode) => void;
|
|
43
|
+
/** Toggle the right session sidebar in the running TUI. */
|
|
44
|
+
toggleSidebar?: () => SidebarCommandState;
|
|
45
|
+
/** Set the right session sidebar mode in the running TUI. */
|
|
46
|
+
setSidebarMode?: (mode: SidebarMode) => SidebarCommandState;
|
|
37
47
|
/** Open the feedback dialog. `initialDescription` prefills the description field. */
|
|
38
48
|
openFeedback?: (initialDescription: string) => void;
|
|
39
49
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function copyTextToClipboard(text: string): Promise<void>;
|
|
@@ -0,0 +1,53 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
export async function detectTerminalTheme(timeoutMs = 150) {
|
|
2
|
+
const fromEnv = parseColorFgBg(process.env.COLORFGBG);
|
|
3
|
+
if (fromEnv)
|
|
4
|
+
return fromEnv;
|
|
5
|
+
if (process.stdout.isTTY && process.stdin.isTTY) {
|
|
6
|
+
const fromOsc = await queryOsc11(timeoutMs);
|
|
7
|
+
if (fromOsc)
|
|
8
|
+
return fromOsc;
|
|
9
|
+
}
|
|
10
|
+
return "dark";
|
|
11
|
+
}
|
|
12
|
+
function parseColorFgBg(value) {
|
|
13
|
+
if (!value)
|
|
14
|
+
return null;
|
|
15
|
+
const parts = value.split(";");
|
|
16
|
+
const last = parts[parts.length - 1];
|
|
17
|
+
if (!last)
|
|
18
|
+
return null;
|
|
19
|
+
const bg = parseInt(last, 10);
|
|
20
|
+
if (Number.isNaN(bg))
|
|
21
|
+
return null;
|
|
22
|
+
if (bg >= 0 && bg <= 6)
|
|
23
|
+
return "dark";
|
|
24
|
+
if (bg >= 7 && bg <= 15)
|
|
25
|
+
return "light";
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
function queryOsc11(timeoutMs) {
|
|
29
|
+
return new Promise((resolve) => {
|
|
30
|
+
const stdin = process.stdin;
|
|
31
|
+
const stdout = process.stdout;
|
|
32
|
+
let settled = false;
|
|
33
|
+
const originalRaw = stdin.isRaw;
|
|
34
|
+
let buffer = "";
|
|
35
|
+
const cleanup = () => {
|
|
36
|
+
stdin.removeListener("data", onData);
|
|
37
|
+
try {
|
|
38
|
+
stdin.setRawMode(originalRaw);
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
// ignore - terminal may have already restored
|
|
42
|
+
}
|
|
43
|
+
stdin.pause();
|
|
44
|
+
};
|
|
45
|
+
const finish = (result) => {
|
|
46
|
+
if (settled)
|
|
47
|
+
return;
|
|
48
|
+
settled = true;
|
|
49
|
+
clearTimeout(timer);
|
|
50
|
+
cleanup();
|
|
51
|
+
resolve(result);
|
|
52
|
+
};
|
|
53
|
+
const onData = (chunk) => {
|
|
54
|
+
buffer += chunk.toString("utf8");
|
|
55
|
+
const match = buffer.match(/\x1b\]11;rgb:([0-9a-fA-F]+)\/([0-9a-fA-F]+)\/([0-9a-fA-F]+)(?:\x07|\x1b\\)/);
|
|
56
|
+
if (!match)
|
|
57
|
+
return;
|
|
58
|
+
const [, r, g, b] = match;
|
|
59
|
+
const lum = relativeLuminance(parseHexChannel(r), parseHexChannel(g), parseHexChannel(b));
|
|
60
|
+
finish(lum > 0.5 ? "light" : "dark");
|
|
61
|
+
};
|
|
62
|
+
try {
|
|
63
|
+
stdin.setRawMode(true);
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
resolve(null);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
stdin.resume();
|
|
70
|
+
stdin.on("data", onData);
|
|
71
|
+
const timer = setTimeout(() => finish(null), timeoutMs);
|
|
72
|
+
try {
|
|
73
|
+
stdout.write("\x1b]11;?\x07");
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
finish(null);
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
function parseHexChannel(hex) {
|
|
81
|
+
const max = (1 << (hex.length * 4)) - 1;
|
|
82
|
+
return parseInt(hex, 16) / max;
|
|
83
|
+
}
|
|
84
|
+
function relativeLuminance(r, g, b) {
|
|
85
|
+
const channel = (c) => c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
|
|
86
|
+
return 0.2126 * channel(r) + 0.7152 * channel(g) + 0.0722 * channel(b);
|
|
87
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
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
|
+
clientId?: string;
|
|
17
|
+
queued?: boolean;
|
|
18
|
+
reasoning?: string;
|
|
19
|
+
toolCalls?: DisplayToolCall[];
|
|
20
|
+
parts?: DisplayMessagePart[];
|
|
21
|
+
status?: "thinking" | "responding";
|
|
22
|
+
streaming?: boolean;
|
|
23
|
+
syntheticKind?: "ui_compact_card";
|
|
24
|
+
hiddenCount?: number;
|
|
25
|
+
compactionMeta?: CompactionMeta;
|
|
26
|
+
turnStartedAt?: number;
|
|
27
|
+
turnCompletedAt?: number;
|
|
28
|
+
turnUsage?: TokenUsage;
|
|
29
|
+
taskElapsedMs?: number;
|
|
30
|
+
}
|
|
31
|
+
export type DisplayMessagePart = DisplayTextPart | DisplayToolsPart;
|
|
32
|
+
export interface DisplayTextPart {
|
|
33
|
+
type: "text";
|
|
34
|
+
content: string;
|
|
35
|
+
}
|
|
36
|
+
export interface DisplayToolsPart {
|
|
37
|
+
type: "tools";
|
|
38
|
+
toolCalls: DisplayToolCall[];
|
|
39
|
+
}
|
|
40
|
+
export interface DisplayToolCall {
|
|
41
|
+
id: string;
|
|
42
|
+
name: string;
|
|
43
|
+
args: Record<string, any>;
|
|
44
|
+
rawArguments?: string;
|
|
45
|
+
streamingArgs?: boolean;
|
|
46
|
+
/** During streaming, an approximate line count derived from `\n` escapes in rawArguments. */
|
|
47
|
+
streamingNewlineCount?: number;
|
|
48
|
+
status?: "pending" | "running" | "completed" | "error";
|
|
49
|
+
result?: string;
|
|
50
|
+
isError?: boolean;
|
|
51
|
+
metadata?: ToolResultMetadata;
|
|
52
|
+
startedAt?: number;
|
|
53
|
+
completedAt?: number;
|
|
54
|
+
}
|
|
55
|
+
export declare function appendTextPart(parts: DisplayMessagePart[], content: string): void;
|
|
56
|
+
export declare function appendToolPart(parts: DisplayMessagePart[], toolCall: DisplayToolCall): void;
|
|
57
|
+
export declare function snapshotDisplayParts(parts: DisplayMessagePart[]): DisplayMessagePart[];
|
|
58
|
+
export declare function contentFromParts(parts: DisplayMessagePart[]): string;
|
|
59
|
+
export declare function toolCallsFromParts(parts: DisplayMessagePart[]): DisplayToolCall[];
|
|
60
|
+
export declare function compactDisplayMessages(messages: DisplayMessage[]): DisplayMessage[];
|
|
61
|
+
export declare function truncateText(value: string, maxChars: number): string;
|
|
62
|
+
export declare function formatCompactNumber(n: number): string;
|