lsd-pi 1.3.2 → 1.3.7
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/cli.js +2 -1
- package/dist/lsd-settings-manager.d.ts +2 -0
- package/dist/lsd-settings-manager.js +5 -0
- package/dist/resource-loader.js +33 -3
- package/dist/resources/extensions/browser-tools/tools/codegen.js +5 -5
- package/dist/resources/extensions/browser-tools/tools/navigation.js +107 -178
- package/dist/resources/extensions/browser-tools/tools/network-mock.js +112 -167
- package/dist/resources/extensions/browser-tools/tools/pages.js +182 -234
- package/dist/resources/extensions/browser-tools/tools/refs.js +202 -461
- package/dist/resources/extensions/browser-tools/tools/session.js +176 -323
- package/dist/resources/extensions/browser-tools/tools/state-persistence.js +91 -154
- package/dist/resources/extensions/browser-tools/utils.js +1 -1
- package/dist/resources/extensions/cache-timer/index.js +3 -2
- package/dist/resources/extensions/slash-commands/extension-manifest.json +2 -2
- package/dist/resources/extensions/slash-commands/fast.js +73 -0
- package/dist/resources/extensions/slash-commands/index.js +2 -0
- package/dist/resources/extensions/slash-commands/plan.js +37 -12
- package/dist/resources/extensions/subagent/background-job-manager.js +13 -0
- package/dist/resources/extensions/subagent/in-process-runner.js +387 -0
- package/dist/resources/extensions/subagent/index.js +278 -626
- package/dist/resources/extensions/subagent/legacy-runner.js +503 -0
- package/dist/resources/extensions/voice/index.js +96 -36
- package/dist/resources/extensions/voice/push-to-talk.js +26 -0
- package/dist/welcome-screen.js +2 -2
- package/package.json +1 -1
- package/packages/pi-agent-core/dist/agent.d.ts +19 -0
- package/packages/pi-agent-core/dist/agent.d.ts.map +1 -1
- package/packages/pi-agent-core/dist/agent.js +16 -0
- package/packages/pi-agent-core/dist/agent.js.map +1 -1
- package/packages/pi-agent-core/src/agent.ts +32 -2
- package/packages/pi-ai/dist/providers/openai-codex-responses.d.ts +34 -1
- package/packages/pi-ai/dist/providers/openai-codex-responses.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/openai-codex-responses.js +32 -4
- package/packages/pi-ai/dist/providers/openai-codex-responses.js.map +1 -1
- package/packages/pi-ai/dist/providers/openai-codex-responses.test.js +127 -16
- package/packages/pi-ai/dist/providers/openai-codex-responses.test.js.map +1 -1
- package/packages/pi-ai/dist/providers/openai-responses.d.ts +8 -1
- package/packages/pi-ai/dist/providers/openai-responses.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/openai-responses.fast-mode.test.d.ts +2 -0
- package/packages/pi-ai/dist/providers/openai-responses.fast-mode.test.d.ts.map +1 -0
- package/packages/pi-ai/dist/providers/openai-responses.fast-mode.test.js +67 -0
- package/packages/pi-ai/dist/providers/openai-responses.fast-mode.test.js.map +1 -0
- package/packages/pi-ai/dist/providers/openai-responses.js +21 -3
- package/packages/pi-ai/dist/providers/openai-responses.js.map +1 -1
- package/packages/pi-ai/dist/providers/simple-options.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/simple-options.js +2 -0
- package/packages/pi-ai/dist/providers/simple-options.js.map +1 -1
- package/packages/pi-ai/dist/types.d.ts +5 -0
- package/packages/pi-ai/dist/types.d.ts.map +1 -1
- package/packages/pi-ai/dist/types.js.map +1 -1
- package/packages/pi-ai/src/providers/openai-codex-responses.test.ts +143 -20
- package/packages/pi-ai/src/providers/openai-codex-responses.ts +47 -4
- package/packages/pi-ai/src/providers/openai-responses.fast-mode.test.ts +73 -0
- package/packages/pi-ai/src/providers/openai-responses.ts +26 -3
- package/packages/pi-ai/src/providers/simple-options.ts +2 -0
- package/packages/pi-ai/src/types.ts +5 -0
- package/packages/pi-coding-agent/dist/core/keybindings.d.ts +1 -1
- package/packages/pi-coding-agent/dist/core/keybindings.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/keybindings.js +2 -0
- package/packages/pi-coding-agent/dist/core/keybindings.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/sdk.js +4 -2
- package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.collapse-tool-calls.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.collapse-tool-calls.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.collapse-tool-calls.test.js +35 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.collapse-tool-calls.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +12 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.fast-mode.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.fast-mode.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.fast-mode.test.js +35 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.fast-mode.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.js +24 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/slash-commands.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/slash-commands.js +1 -0
- package/packages/pi-coding-agent/dist/core/slash-commands.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/system-prompt.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/system-prompt.js +6 -1
- package/packages/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tool-priority.d.ts +4 -0
- package/packages/pi-coding-agent/dist/core/tool-priority.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/tool-priority.js +18 -0
- package/packages/pi-coding-agent/dist/core/tool-priority.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/tool-priority.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/tool-priority.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/tool-priority.test.js +27 -0
- package/packages/pi-coding-agent/dist/core/tool-priority.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/tools/edit-diff.d.ts +5 -0
- package/packages/pi-coding-agent/dist/core/tools/edit-diff.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/edit-diff.js +21 -0
- package/packages/pi-coding-agent/dist/core/tools/edit-diff.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/edit-diff.test.js +16 -1
- package/packages/pi-coding-agent/dist/core/tools/edit-diff.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-summary-line.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-summary-line.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-summary-line.test.js +34 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-summary-line.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/btw-overlay.d.ts +45 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/btw-overlay.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/btw-overlay.js +314 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/btw-overlay.js.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/btw-overlay.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/btw-overlay.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/btw-overlay.test.js +122 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/btw-overlay.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/diff.d.ts +7 -5
- package/packages/pi-coding-agent/dist/modes/interactive/components/diff.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/diff.js +86 -28
- package/packages/pi-coding-agent/dist/modes/interactive/components/diff.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/footer.d.ts +4 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js +23 -10
- package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.d.ts +8 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.js +52 -6
- package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts +19 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +127 -14
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-summary-line.d.ts +14 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-summary-line.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-summary-line.js +93 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-summary-line.js.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/__tests__/chat-controller.collapsed-tool-summary.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/__tests__/chat-controller.collapsed-tool-summary.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/__tests__/chat-controller.collapsed-tool-summary.test.js +328 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/__tests__/chat-controller.collapsed-tool-summary.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +123 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/extension-ui-controller.js +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/extension-ui-controller.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js +7 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts +4 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +9 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +103 -23
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.js +41 -0
- package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.js +4 -4
- package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.js.map +1 -1
- package/packages/pi-coding-agent/package.json +1 -1
- package/packages/pi-coding-agent/src/core/keybindings.ts +4 -1
- package/packages/pi-coding-agent/src/core/sdk.ts +4 -2
- package/packages/pi-coding-agent/src/core/settings-manager.collapse-tool-calls.test.ts +46 -0
- package/packages/pi-coding-agent/src/core/settings-manager.fast-mode.test.ts +46 -0
- package/packages/pi-coding-agent/src/core/settings-manager.ts +36 -0
- package/packages/pi-coding-agent/src/core/slash-commands.ts +1 -0
- package/packages/pi-coding-agent/src/core/system-prompt.ts +6 -1
- package/packages/pi-coding-agent/src/core/tool-priority.test.ts +30 -0
- package/packages/pi-coding-agent/src/core/tool-priority.ts +17 -0
- package/packages/pi-coding-agent/src/core/tools/edit-diff.test.ts +20 -0
- package/packages/pi-coding-agent/src/core/tools/edit-diff.ts +26 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/tool-summary-line.test.ts +41 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/btw-overlay.test.ts +172 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/btw-overlay.ts +402 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/diff.ts +105 -28
- package/packages/pi-coding-agent/src/modes/interactive/components/footer.ts +21 -6
- package/packages/pi-coding-agent/src/modes/interactive/components/settings-selector.ts +63 -6
- package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +1262 -1138
- package/packages/pi-coding-agent/src/modes/interactive/components/tool-summary-line.ts +120 -0
- package/packages/pi-coding-agent/src/modes/interactive/controllers/__tests__/chat-controller.collapsed-tool-summary.test.ts +396 -0
- package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +530 -398
- package/packages/pi-coding-agent/src/modes/interactive/controllers/extension-ui-controller.ts +1 -1
- package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.ts +7 -0
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode-state.ts +4 -0
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +109 -23
- package/packages/pi-coding-agent/src/modes/interactive/slash-command-handlers.ts +60 -1
- package/packages/pi-coding-agent/src/modes/interactive/theme/themes.ts +4 -4
- package/packages/pi-tui/dist/components/editor.js +3 -3
- package/packages/pi-tui/dist/components/editor.js.map +1 -1
- package/packages/pi-tui/src/components/editor.ts +3 -3
- package/pkg/dist/modes/interactive/theme/themes.js +4 -4
- package/pkg/dist/modes/interactive/theme/themes.js.map +1 -1
- package/pkg/package.json +1 -1
- package/src/resources/extensions/browser-tools/tools/codegen.ts +5 -5
- package/src/resources/extensions/browser-tools/tools/navigation.ts +118 -196
- package/src/resources/extensions/browser-tools/tools/network-mock.ts +114 -205
- package/src/resources/extensions/browser-tools/tools/pages.ts +183 -237
- package/src/resources/extensions/browser-tools/tools/refs.ts +193 -507
- package/src/resources/extensions/browser-tools/tools/session.ts +182 -321
- package/src/resources/extensions/browser-tools/tools/state-persistence.ts +94 -172
- package/src/resources/extensions/browser-tools/utils.ts +1 -1
- package/src/resources/extensions/cache-timer/index.ts +3 -2
- package/src/resources/extensions/slash-commands/extension-manifest.json +2 -2
- package/src/resources/extensions/slash-commands/fast.ts +89 -0
- package/src/resources/extensions/slash-commands/index.ts +2 -0
- package/src/resources/extensions/slash-commands/plan.ts +42 -12
- package/src/resources/extensions/subagent/background-job-manager.ts +28 -0
- package/src/resources/extensions/subagent/in-process-runner.ts +534 -0
- package/src/resources/extensions/subagent/index.ts +489 -799
- package/src/resources/extensions/subagent/legacy-runner.ts +607 -0
- package/src/resources/extensions/voice/index.ts +308 -238
- package/src/resources/extensions/voice/push-to-talk.ts +42 -0
- package/src/resources/extensions/voice/tests/push-to-talk.test.ts +109 -0
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
import type { AssistantMessage, Context, Message, Model, SimpleStreamOptions, StopReason, Usage } from "@gsd/pi-ai";
|
|
2
|
+
import { streamSimple } from "@gsd/pi-ai";
|
|
3
|
+
import {
|
|
4
|
+
Input,
|
|
5
|
+
Markdown,
|
|
6
|
+
type MarkdownTheme,
|
|
7
|
+
parseKey,
|
|
8
|
+
truncateToWidth,
|
|
9
|
+
type Component,
|
|
10
|
+
type Focusable,
|
|
11
|
+
type TUI,
|
|
12
|
+
visibleWidth,
|
|
13
|
+
} from "@gsd/pi-tui";
|
|
14
|
+
import { theme } from "../theme/theme.js";
|
|
15
|
+
|
|
16
|
+
const EMPTY_USAGE: Usage = {
|
|
17
|
+
input: 0,
|
|
18
|
+
output: 0,
|
|
19
|
+
cacheRead: 0,
|
|
20
|
+
cacheWrite: 0,
|
|
21
|
+
totalTokens: 0,
|
|
22
|
+
cost: {
|
|
23
|
+
input: 0,
|
|
24
|
+
output: 0,
|
|
25
|
+
cacheRead: 0,
|
|
26
|
+
cacheWrite: 0,
|
|
27
|
+
total: 0,
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
interface BtwTurn {
|
|
32
|
+
role: "user" | "assistant";
|
|
33
|
+
text: string;
|
|
34
|
+
isError?: boolean;
|
|
35
|
+
isStreaming?: boolean;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function clamp(value: number, min: number, max: number): number {
|
|
39
|
+
return Math.max(min, Math.min(max, value));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function fitWidth(text: string, width: number): string {
|
|
43
|
+
if (width <= 0) return "";
|
|
44
|
+
const visible = visibleWidth(text);
|
|
45
|
+
if (visible === width) return text;
|
|
46
|
+
if (visible < width) return text + " ".repeat(width - visible);
|
|
47
|
+
return truncateToWidth(text, width, "");
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function formatAssistantError(message: AssistantMessage): string {
|
|
51
|
+
if (message.errorMessage?.trim()) {
|
|
52
|
+
return message.errorMessage.trim();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const text = message.content
|
|
56
|
+
.map((part) => (part.type === "text" ? part.text.trim() : ""))
|
|
57
|
+
.filter(Boolean)
|
|
58
|
+
.join("\n")
|
|
59
|
+
.trim();
|
|
60
|
+
|
|
61
|
+
return text || `Request failed (${message.stopReason})`;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export class BtwOverlayComponent implements Component, Focusable {
|
|
65
|
+
private _focused = false;
|
|
66
|
+
|
|
67
|
+
private readonly markdownTheme: MarkdownTheme;
|
|
68
|
+
private readonly model: Model<any>;
|
|
69
|
+
private readonly systemPrompt: string | undefined;
|
|
70
|
+
private readonly baseMessages: Message[];
|
|
71
|
+
private readonly ui: TUI;
|
|
72
|
+
private readonly input = new Input();
|
|
73
|
+
private readonly onDismiss: () => void;
|
|
74
|
+
private readonly requestRender: () => void;
|
|
75
|
+
private readonly streamFn: typeof streamSimple;
|
|
76
|
+
private readonly streamOptions: Pick<SimpleStreamOptions, "apiKey" | "sessionId">;
|
|
77
|
+
|
|
78
|
+
private readonly btwHistory: Message[] = [];
|
|
79
|
+
private readonly turns: BtwTurn[] = [];
|
|
80
|
+
private currentAbortController: AbortController | undefined;
|
|
81
|
+
private disposed = false;
|
|
82
|
+
private isStreaming = false;
|
|
83
|
+
private scrollOffset = 0;
|
|
84
|
+
private followTail = true;
|
|
85
|
+
private lastWidth = 0;
|
|
86
|
+
|
|
87
|
+
get focused(): boolean {
|
|
88
|
+
return this._focused;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
set focused(value: boolean) {
|
|
92
|
+
this._focused = value;
|
|
93
|
+
this.input.focused = value;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
constructor(
|
|
97
|
+
question: string,
|
|
98
|
+
model: Model<any>,
|
|
99
|
+
systemPrompt: string | undefined,
|
|
100
|
+
messages: Message[],
|
|
101
|
+
markdownTheme: MarkdownTheme,
|
|
102
|
+
ui: TUI,
|
|
103
|
+
onDismiss: () => void,
|
|
104
|
+
requestRender: () => void,
|
|
105
|
+
streamer: typeof streamSimple = streamSimple,
|
|
106
|
+
streamOptions: Pick<SimpleStreamOptions, "apiKey" | "sessionId"> = {},
|
|
107
|
+
) {
|
|
108
|
+
this.model = model;
|
|
109
|
+
this.systemPrompt = systemPrompt;
|
|
110
|
+
this.baseMessages = messages;
|
|
111
|
+
this.markdownTheme = markdownTheme;
|
|
112
|
+
this.ui = ui;
|
|
113
|
+
this.onDismiss = onDismiss;
|
|
114
|
+
this.requestRender = requestRender;
|
|
115
|
+
this.streamFn = streamer;
|
|
116
|
+
this.streamOptions = streamOptions;
|
|
117
|
+
|
|
118
|
+
this.input.placeholder = "Ask follow-up...";
|
|
119
|
+
this.input.onEscape = () => this.dismiss();
|
|
120
|
+
this.input.onSubmit = (value) => {
|
|
121
|
+
void this.submitTurn(value);
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
void this.submitTurn(question);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
handleInput(data: string): void {
|
|
128
|
+
const key = parseKey(data);
|
|
129
|
+
switch (key) {
|
|
130
|
+
case "up":
|
|
131
|
+
this.scrollBy(-1);
|
|
132
|
+
return;
|
|
133
|
+
case "down":
|
|
134
|
+
this.scrollBy(1);
|
|
135
|
+
return;
|
|
136
|
+
case "pageUp":
|
|
137
|
+
this.scrollBy(-(this.getBodyHeight() - 1));
|
|
138
|
+
return;
|
|
139
|
+
case "pageDown":
|
|
140
|
+
this.scrollBy(this.getBodyHeight() - 1);
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
this.input.handleInput(data);
|
|
145
|
+
if (!this.disposed) {
|
|
146
|
+
this.requestRender();
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
invalidate(): void {
|
|
151
|
+
// No cached subtree state to invalidate.
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
dispose(): void {
|
|
155
|
+
if (this.disposed) return;
|
|
156
|
+
this.disposed = true;
|
|
157
|
+
this.currentAbortController?.abort();
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
render(width: number): string[] {
|
|
161
|
+
this.lastWidth = width;
|
|
162
|
+
|
|
163
|
+
const totalHeight = this.getTotalHeight();
|
|
164
|
+
const bodyHeight = Math.max(1, totalHeight - 4);
|
|
165
|
+
const contentWidth = Math.max(1, width - 4);
|
|
166
|
+
const bodyLines = this.getBodyLines(contentWidth);
|
|
167
|
+
const maxOffset = Math.max(0, bodyLines.length - bodyHeight);
|
|
168
|
+
if (this.followTail) {
|
|
169
|
+
this.scrollOffset = maxOffset;
|
|
170
|
+
} else {
|
|
171
|
+
this.scrollOffset = clamp(this.scrollOffset, 0, maxOffset);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const visibleBody = bodyLines.slice(this.scrollOffset, this.scrollOffset + bodyHeight);
|
|
175
|
+
while (visibleBody.length < bodyHeight) {
|
|
176
|
+
visibleBody.push("");
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const inputLines = this.input.render(contentWidth);
|
|
180
|
+
const inputLine = inputLines[0] ?? "";
|
|
181
|
+
|
|
182
|
+
return [
|
|
183
|
+
this.renderTopBorder(width),
|
|
184
|
+
...visibleBody.map((line) => this.renderBodyLine(line, width)),
|
|
185
|
+
this.renderBodyLine(inputLine, width),
|
|
186
|
+
this.renderFooterLine(width, bodyLines.length > bodyHeight),
|
|
187
|
+
this.renderBottomBorder(width),
|
|
188
|
+
];
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
private dismiss(): void {
|
|
192
|
+
this.dispose();
|
|
193
|
+
this.onDismiss();
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
private async submitTurn(rawQuestion: string): Promise<void> {
|
|
197
|
+
const question = rawQuestion.trim();
|
|
198
|
+
if (!question || this.disposed || this.isStreaming) {
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const userMessage = this.createUserMessage(question);
|
|
203
|
+
const context: Context = {
|
|
204
|
+
systemPrompt: this.systemPrompt,
|
|
205
|
+
messages: [...this.baseMessages, ...this.btwHistory, userMessage],
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
this.btwHistory.push(userMessage);
|
|
209
|
+
this.turns.push({ role: "user", text: question });
|
|
210
|
+
const assistantTurn: BtwTurn = { role: "assistant", text: "", isStreaming: true };
|
|
211
|
+
this.turns.push(assistantTurn);
|
|
212
|
+
this.input.setValue("");
|
|
213
|
+
this.isStreaming = true;
|
|
214
|
+
this.followTail = true;
|
|
215
|
+
this.requestRender();
|
|
216
|
+
|
|
217
|
+
const abortController = new AbortController();
|
|
218
|
+
this.currentAbortController = abortController;
|
|
219
|
+
|
|
220
|
+
try {
|
|
221
|
+
const eventStream = this.streamFn(this.model, context, {
|
|
222
|
+
signal: abortController.signal,
|
|
223
|
+
apiKey: this.streamOptions.apiKey,
|
|
224
|
+
sessionId: this.streamOptions.sessionId,
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
for await (const event of eventStream) {
|
|
228
|
+
if (this.disposed || this.currentAbortController !== abortController) {
|
|
229
|
+
break;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (event.type === "text_delta") {
|
|
233
|
+
assistantTurn.text += event.delta;
|
|
234
|
+
this.requestRender();
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (event.type === "done") {
|
|
239
|
+
this.finishAssistantTurn(assistantTurn, "stop");
|
|
240
|
+
this.requestRender();
|
|
241
|
+
break;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (event.type === "error") {
|
|
245
|
+
if (event.reason === "aborted") {
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
assistantTurn.isError = true;
|
|
250
|
+
if (!assistantTurn.text.trim()) {
|
|
251
|
+
assistantTurn.text = formatAssistantError(event.error);
|
|
252
|
+
}
|
|
253
|
+
this.finishAssistantTurn(assistantTurn, "error");
|
|
254
|
+
this.requestRender();
|
|
255
|
+
break;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
} catch (error: unknown) {
|
|
259
|
+
if (this.disposed || abortController.signal.aborted || this.currentAbortController !== abortController) {
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
assistantTurn.isError = true;
|
|
264
|
+
assistantTurn.text = error instanceof Error ? error.message : "Unknown error";
|
|
265
|
+
this.finishAssistantTurn(assistantTurn, "error");
|
|
266
|
+
this.requestRender();
|
|
267
|
+
} finally {
|
|
268
|
+
if (this.currentAbortController === abortController) {
|
|
269
|
+
this.currentAbortController = undefined;
|
|
270
|
+
this.isStreaming = false;
|
|
271
|
+
assistantTurn.isStreaming = false;
|
|
272
|
+
this.requestRender();
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
private finishAssistantTurn(turn: BtwTurn, stopReason: StopReason): void {
|
|
278
|
+
turn.isStreaming = false;
|
|
279
|
+
if (!turn.text.trim()) {
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
this.btwHistory.push({
|
|
284
|
+
role: "assistant",
|
|
285
|
+
content: [{ type: "text", text: turn.text }],
|
|
286
|
+
api: this.model.api,
|
|
287
|
+
provider: this.model.provider,
|
|
288
|
+
model: this.model.id,
|
|
289
|
+
usage: EMPTY_USAGE,
|
|
290
|
+
stopReason,
|
|
291
|
+
errorMessage: turn.isError ? turn.text : undefined,
|
|
292
|
+
timestamp: Date.now(),
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
private createUserMessage(question: string): Message {
|
|
297
|
+
return {
|
|
298
|
+
role: "user",
|
|
299
|
+
content: [{ type: "text", text: question }],
|
|
300
|
+
timestamp: Date.now(),
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
private getTotalHeight(): number {
|
|
305
|
+
return Math.max(6, Math.floor(this.ui.terminal.rows * 0.7));
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
private getBodyHeight(): number {
|
|
309
|
+
return Math.max(1, this.getTotalHeight() - 4);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
private scrollBy(delta: number): void {
|
|
313
|
+
const bodyHeight = this.getBodyHeight();
|
|
314
|
+
const bodyLines = this.getBodyLines(Math.max(1, this.lastWidth - 4));
|
|
315
|
+
const maxOffset = Math.max(0, bodyLines.length - bodyHeight);
|
|
316
|
+
this.scrollOffset = clamp(this.scrollOffset + delta, 0, maxOffset);
|
|
317
|
+
this.followTail = this.scrollOffset >= maxOffset;
|
|
318
|
+
this.requestRender();
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
private getBodyLines(contentWidth: number): string[] {
|
|
322
|
+
const lines: string[] = [];
|
|
323
|
+
|
|
324
|
+
for (const [index, turn] of this.turns.entries()) {
|
|
325
|
+
if (index > 0) {
|
|
326
|
+
lines.push("");
|
|
327
|
+
}
|
|
328
|
+
lines.push(turn.role === "user" ? theme.fg("accent", "You") : theme.fg("muted", "btw"));
|
|
329
|
+
lines.push(...this.renderTurnContent(turn, Math.max(1, contentWidth - 2)).map((line) => ` ${line}`));
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
if (lines.length === 0) {
|
|
333
|
+
lines.push(theme.fg("dim", "Awaiting response…"));
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
return lines;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
private renderTurnContent(turn: BtwTurn, contentWidth: number): string[] {
|
|
340
|
+
if (!turn.text.trim()) {
|
|
341
|
+
return [theme.fg("dim", turn.isStreaming ? "Awaiting response…" : "No response received.")];
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
if (turn.isError) {
|
|
345
|
+
return [theme.fg("error", `Error: ${turn.text}`)];
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
const markdown = new Markdown(turn.text, 0, 0, this.markdownTheme, {
|
|
349
|
+
color: (text: string) => theme.fg("text", text),
|
|
350
|
+
});
|
|
351
|
+
return markdown.render(contentWidth);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
private renderTopBorder(width: number): string {
|
|
355
|
+
if (width <= 2) {
|
|
356
|
+
return "┌┐".slice(0, width);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
const innerWidth = width - 2;
|
|
360
|
+
const title = theme.fg("accent", " btw ");
|
|
361
|
+
const titleWidth = visibleWidth(title);
|
|
362
|
+
|
|
363
|
+
if (innerWidth <= titleWidth) {
|
|
364
|
+
return fitWidth(`┌${truncateToWidth(title, innerWidth, "")}┐`, width);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
const dashCount = innerWidth - titleWidth;
|
|
368
|
+
const leftDashes = Math.floor(dashCount / 2);
|
|
369
|
+
const rightDashes = dashCount - leftDashes;
|
|
370
|
+
const border = theme.fg("border", "─");
|
|
371
|
+
return `┌${border.repeat(leftDashes)}${title}${border.repeat(rightDashes)}┐`;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
private renderBottomBorder(width: number): string {
|
|
375
|
+
if (width <= 2) {
|
|
376
|
+
return "└┘".slice(0, width);
|
|
377
|
+
}
|
|
378
|
+
return `└${theme.fg("border", "─").repeat(width - 2)}┘`;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
private renderBodyLine(text: string, width: number): string {
|
|
382
|
+
const innerWidth = Math.max(1, width - 4);
|
|
383
|
+
const fitted = fitWidth(truncateToWidth(text, innerWidth, ""), innerWidth);
|
|
384
|
+
return fitWidth(`│ ${fitted} │`, width);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
private renderFooterLine(width: number, canScroll: boolean): string {
|
|
388
|
+
const innerWidth = Math.max(1, width - 4);
|
|
389
|
+
const parts: string[] = ["Enter send", "Esc dismiss"];
|
|
390
|
+
if (canScroll) {
|
|
391
|
+
parts.push("↑↓/PgUp/PgDn scroll");
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
const status = this.isStreaming
|
|
395
|
+
? theme.fg("muted", "Streaming...")
|
|
396
|
+
: theme.fg("success", "Ready");
|
|
397
|
+
|
|
398
|
+
const footer = `${theme.fg("muted", parts.join(" • "))} ${status}`;
|
|
399
|
+
const fitted = fitWidth(truncateToWidth(footer, innerWidth, ""), innerWidth);
|
|
400
|
+
return fitWidth(`│ ${fitted} │`, width);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
@@ -1,6 +1,15 @@
|
|
|
1
|
+
import { visibleWidth, wrapTextWithAnsi } from "@gsd/pi-tui";
|
|
1
2
|
import * as Diff from "diff";
|
|
3
|
+
import chalk from "chalk";
|
|
2
4
|
import { theme } from "../theme/theme.js";
|
|
3
5
|
|
|
6
|
+
const DIFF_BG = {
|
|
7
|
+
addedLine: "#0f2f1a",
|
|
8
|
+
removedLine: "#3a1116",
|
|
9
|
+
addedToken: "#1b5e20",
|
|
10
|
+
removedToken: "#7f1d1d",
|
|
11
|
+
} as const;
|
|
12
|
+
|
|
4
13
|
/**
|
|
5
14
|
* Parse diff line to extract prefix, line number, and content.
|
|
6
15
|
* Format: "+123 content" or "-123 content" or " 123 content" or " ..."
|
|
@@ -18,10 +27,52 @@ function replaceTabs(text: string): string {
|
|
|
18
27
|
return text.replace(/\t/g, " ");
|
|
19
28
|
}
|
|
20
29
|
|
|
30
|
+
function formatLineNum(raw: string, width: number): string {
|
|
31
|
+
const trimmed = raw.trim();
|
|
32
|
+
if (!trimmed) return "".padStart(width, " ");
|
|
33
|
+
return trimmed.padStart(width, " ");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function padToWidth(text: string, width: number): string {
|
|
37
|
+
if (!Number.isFinite(width) || width > 10_000) {
|
|
38
|
+
return text;
|
|
39
|
+
}
|
|
40
|
+
const paddingNeeded = Math.max(0, width - visibleWidth(text));
|
|
41
|
+
return text + " ".repeat(paddingNeeded);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function styleRemovedPrefix(prefix: string): string {
|
|
45
|
+
return chalk.hex("#fb7185")(prefix);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function styleAddedPrefix(prefix: string): string {
|
|
49
|
+
return chalk.hex("#4ade80")(prefix);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function styleRemovedToken(value: string): string {
|
|
53
|
+
return chalk.bgHex(DIFF_BG.removedToken).whiteBright(value);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function styleAddedToken(value: string): string {
|
|
57
|
+
return chalk.bgHex(DIFF_BG.addedToken).whiteBright(value);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function styleAddedLine(text: string, width: number): string {
|
|
61
|
+
const prefix = text.startsWith("+") ? styleAddedPrefix("+") : "";
|
|
62
|
+
const rest = prefix ? text.slice(1) : text;
|
|
63
|
+
return chalk.bgHex(DIFF_BG.addedLine).whiteBright(padToWidth(`${prefix}${rest}`, width));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function styleRemovedLine(text: string, width: number): string {
|
|
67
|
+
const prefix = text.startsWith("-") ? styleRemovedPrefix("-") : "";
|
|
68
|
+
const rest = prefix ? text.slice(1) : text;
|
|
69
|
+
return chalk.bgHex(DIFF_BG.removedLine).whiteBright(padToWidth(`${prefix}${rest}`, width));
|
|
70
|
+
}
|
|
71
|
+
|
|
21
72
|
/**
|
|
22
|
-
* Compute word-level diff and render
|
|
73
|
+
* Compute word-level diff and render changed tokens with subtle emphasis.
|
|
23
74
|
* Uses diffWords which groups whitespace with adjacent words for cleaner highlighting.
|
|
24
|
-
* Strips leading whitespace from
|
|
75
|
+
* Strips leading whitespace from highlighted token spans.
|
|
25
76
|
*/
|
|
26
77
|
function renderIntraLineDiff(oldContent: string, newContent: string): { removedLine: string; addedLine: string } {
|
|
27
78
|
const wordDiff = Diff.diffWords(oldContent, newContent);
|
|
@@ -34,7 +85,6 @@ function renderIntraLineDiff(oldContent: string, newContent: string): { removedL
|
|
|
34
85
|
for (const part of wordDiff) {
|
|
35
86
|
if (part.removed) {
|
|
36
87
|
let value = part.value;
|
|
37
|
-
// Strip leading whitespace from the first removed part
|
|
38
88
|
if (isFirstRemoved) {
|
|
39
89
|
const leadingWs = value.match(/^(\s*)/)?.[1] || "";
|
|
40
90
|
value = value.slice(leadingWs.length);
|
|
@@ -42,11 +92,10 @@ function renderIntraLineDiff(oldContent: string, newContent: string): { removedL
|
|
|
42
92
|
isFirstRemoved = false;
|
|
43
93
|
}
|
|
44
94
|
if (value) {
|
|
45
|
-
removedLine +=
|
|
95
|
+
removedLine += styleRemovedToken(value);
|
|
46
96
|
}
|
|
47
97
|
} else if (part.added) {
|
|
48
98
|
let value = part.value;
|
|
49
|
-
// Strip leading whitespace from the first added part
|
|
50
99
|
if (isFirstAdded) {
|
|
51
100
|
const leadingWs = value.match(/^(\s*)/)?.[1] || "";
|
|
52
101
|
value = value.slice(leadingWs.length);
|
|
@@ -54,7 +103,7 @@ function renderIntraLineDiff(oldContent: string, newContent: string): { removedL
|
|
|
54
103
|
isFirstAdded = false;
|
|
55
104
|
}
|
|
56
105
|
if (value) {
|
|
57
|
-
addedLine +=
|
|
106
|
+
addedLine += styleAddedToken(value);
|
|
58
107
|
}
|
|
59
108
|
} else {
|
|
60
109
|
removedLine += part.value;
|
|
@@ -70,29 +119,52 @@ export interface RenderDiffOptions {
|
|
|
70
119
|
filePath?: string;
|
|
71
120
|
}
|
|
72
121
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
122
|
+
function renderStyledLine(
|
|
123
|
+
text: string,
|
|
124
|
+
width: number,
|
|
125
|
+
kind: "context" | "added" | "removed",
|
|
126
|
+
): string[] {
|
|
127
|
+
const wrapWidth = Math.max(1, width);
|
|
128
|
+
const wrapped = wrapTextWithAnsi(text, wrapWidth);
|
|
129
|
+
if (kind === "added") {
|
|
130
|
+
return wrapped.map((line) => styleAddedLine(line, width));
|
|
131
|
+
}
|
|
132
|
+
if (kind === "removed") {
|
|
133
|
+
return wrapped.map((line) => styleRemovedLine(line, width));
|
|
134
|
+
}
|
|
135
|
+
return wrapped.map((line) => padToWidth(theme.fg("toolDiffContext", line), width));
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export function renderDiffLines(diffText: string, width: number, _options: RenderDiffOptions = {}): string[] {
|
|
80
139
|
const lines = diffText.split("\n");
|
|
81
140
|
const result: string[] = [];
|
|
82
141
|
|
|
142
|
+
const parsedLines = lines.map(parseDiffLine).filter((p): p is { prefix: string; lineNum: string; content: string } => !!p);
|
|
143
|
+
const lineNumWidth = Math.max(
|
|
144
|
+
1,
|
|
145
|
+
...parsedLines
|
|
146
|
+
.map((p) => p.lineNum.trim())
|
|
147
|
+
.filter(Boolean)
|
|
148
|
+
.map((n) => n.length),
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
const formatLine = (prefix: "+" | "-" | " ", lineNum: string, content: string): string => {
|
|
152
|
+
const num = formatLineNum(lineNum, lineNumWidth);
|
|
153
|
+
return `${prefix}${num} ${replaceTabs(content)}`;
|
|
154
|
+
};
|
|
155
|
+
|
|
83
156
|
let i = 0;
|
|
84
157
|
while (i < lines.length) {
|
|
85
158
|
const line = lines[i];
|
|
86
159
|
const parsed = parseDiffLine(line);
|
|
87
160
|
|
|
88
161
|
if (!parsed) {
|
|
89
|
-
result.push(
|
|
162
|
+
result.push(...renderStyledLine(line, width, "context"));
|
|
90
163
|
i++;
|
|
91
164
|
continue;
|
|
92
165
|
}
|
|
93
166
|
|
|
94
167
|
if (parsed.prefix === "-") {
|
|
95
|
-
// Collect consecutive removed lines
|
|
96
168
|
const removedLines: { lineNum: string; content: string }[] = [];
|
|
97
169
|
while (i < lines.length) {
|
|
98
170
|
const p = parseDiffLine(lines[i]);
|
|
@@ -101,7 +173,6 @@ export function renderDiff(diffText: string, _options: RenderDiffOptions = {}):
|
|
|
101
173
|
i++;
|
|
102
174
|
}
|
|
103
175
|
|
|
104
|
-
// Collect consecutive added lines
|
|
105
176
|
const addedLines: { lineNum: string; content: string }[] = [];
|
|
106
177
|
while (i < lines.length) {
|
|
107
178
|
const p = parseDiffLine(lines[i]);
|
|
@@ -110,8 +181,6 @@ export function renderDiff(diffText: string, _options: RenderDiffOptions = {}):
|
|
|
110
181
|
i++;
|
|
111
182
|
}
|
|
112
183
|
|
|
113
|
-
// Only do intra-line diffing when there's exactly one removed and one added line
|
|
114
|
-
// (indicating a single line modification). Otherwise, show lines as-is.
|
|
115
184
|
if (removedLines.length === 1 && addedLines.length === 1) {
|
|
116
185
|
const removed = removedLines[0];
|
|
117
186
|
const added = addedLines[0];
|
|
@@ -121,27 +190,35 @@ export function renderDiff(diffText: string, _options: RenderDiffOptions = {}):
|
|
|
121
190
|
replaceTabs(added.content),
|
|
122
191
|
);
|
|
123
192
|
|
|
124
|
-
result.push(
|
|
125
|
-
result.push(
|
|
193
|
+
result.push(...renderStyledLine(formatLine("-", removed.lineNum, removedLine), width, "removed"));
|
|
194
|
+
result.push(...renderStyledLine(formatLine("+", added.lineNum, addedLine), width, "added"));
|
|
126
195
|
} else {
|
|
127
|
-
// Show all removed lines first, then all added lines
|
|
128
196
|
for (const removed of removedLines) {
|
|
129
|
-
result.push(
|
|
197
|
+
result.push(...renderStyledLine(formatLine("-", removed.lineNum, removed.content), width, "removed"));
|
|
130
198
|
}
|
|
131
199
|
for (const added of addedLines) {
|
|
132
|
-
result.push(
|
|
200
|
+
result.push(...renderStyledLine(formatLine("+", added.lineNum, added.content), width, "added"));
|
|
133
201
|
}
|
|
134
202
|
}
|
|
135
203
|
} else if (parsed.prefix === "+") {
|
|
136
|
-
|
|
137
|
-
result.push(theme.fg("toolDiffAdded", `+${parsed.lineNum} ${replaceTabs(parsed.content)}`));
|
|
204
|
+
result.push(...renderStyledLine(formatLine("+", parsed.lineNum, parsed.content), width, "added"));
|
|
138
205
|
i++;
|
|
139
206
|
} else {
|
|
140
|
-
|
|
141
|
-
result.push(theme.fg("toolDiffContext", ` ${parsed.lineNum} ${replaceTabs(parsed.content)}`));
|
|
207
|
+
result.push(...renderStyledLine(formatLine(" ", parsed.lineNum, parsed.content), width, "context"));
|
|
142
208
|
i++;
|
|
143
209
|
}
|
|
144
210
|
}
|
|
145
211
|
|
|
146
|
-
return result
|
|
212
|
+
return result;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Render a diff string with Claude-like colored lines.
|
|
217
|
+
* - Context lines: muted gray
|
|
218
|
+
* - Removed lines: red text on subtle red background
|
|
219
|
+
* - Added lines: green text on subtle green background
|
|
220
|
+
* - Changed tokens: slightly stronger background + bold
|
|
221
|
+
*/
|
|
222
|
+
export function renderDiff(diffText: string, options: RenderDiffOptions = {}): string {
|
|
223
|
+
return renderDiffLines(diffText, Number.MAX_SAFE_INTEGER, options).join("\n");
|
|
147
224
|
}
|
|
@@ -46,6 +46,8 @@ export function formatPromptCost(cost: number): string {
|
|
|
46
46
|
export class FooterComponent implements Component {
|
|
47
47
|
private autoCompactEnabled = true;
|
|
48
48
|
private permissionMode: PermissionMode = "danger-full-access";
|
|
49
|
+
private notificationSoundEnabled = false;
|
|
50
|
+
private verboseFooterEnabled = false;
|
|
49
51
|
|
|
50
52
|
constructor(
|
|
51
53
|
private session: AgentSession,
|
|
@@ -60,6 +62,14 @@ export class FooterComponent implements Component {
|
|
|
60
62
|
this.permissionMode = mode;
|
|
61
63
|
}
|
|
62
64
|
|
|
65
|
+
setNotificationSoundEnabled(enabled: boolean): void {
|
|
66
|
+
this.notificationSoundEnabled = enabled;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
setVerboseFooterEnabled(enabled: boolean): void {
|
|
70
|
+
this.verboseFooterEnabled = enabled;
|
|
71
|
+
}
|
|
72
|
+
|
|
63
73
|
/**
|
|
64
74
|
* No-op: git branch caching now handled by provider.
|
|
65
75
|
* Kept for compatibility with existing call sites in interactive-mode.
|
|
@@ -120,9 +130,12 @@ export class FooterComponent implements Component {
|
|
|
120
130
|
const extensionStatuses = this.footerData.getExtensionStatuses();
|
|
121
131
|
const cacheTimerStatusRaw = extensionStatuses.get("cache-timer");
|
|
122
132
|
const cacheTimerStatus = cacheTimerStatusRaw ? sanitizeStatusText(cacheTimerStatusRaw) : "";
|
|
123
|
-
const hotkeysHints = ["
|
|
133
|
+
const hotkeysHints = ["/hotkeys"];
|
|
124
134
|
const firstLineMinPadding = 2;
|
|
125
135
|
const firstLineRightParts = cacheTimerStatus ? [cacheTimerStatus] : [];
|
|
136
|
+
if (this.notificationSoundEnabled) {
|
|
137
|
+
firstLineRightParts.push(theme.fg("success", "🔔"));
|
|
138
|
+
}
|
|
126
139
|
const firstLineRightBase = firstLineRightParts.join(" ");
|
|
127
140
|
const hotkeysHint = hotkeysHints.find((hint) => {
|
|
128
141
|
const candidate = firstLineRightBase ? `${firstLineRightBase} ${hint}` : hint;
|
|
@@ -147,10 +160,12 @@ export class FooterComponent implements Component {
|
|
|
147
160
|
|
|
148
161
|
// Build stats line
|
|
149
162
|
const statsParts = [];
|
|
150
|
-
if (
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
163
|
+
if (this.verboseFooterEnabled) {
|
|
164
|
+
if (totalInput) statsParts.push(`↑${formatTokens(totalInput)}`);
|
|
165
|
+
if (totalOutput) statsParts.push(`↓${formatTokens(totalOutput)}`);
|
|
166
|
+
if (totalCacheRead) statsParts.push(`Cache ${formatTokens(totalCacheRead)}`);
|
|
167
|
+
if (totalCacheWrite) statsParts.push(`W${formatTokens(totalCacheWrite)}`);
|
|
168
|
+
}
|
|
154
169
|
|
|
155
170
|
// Show cost with "(sub)" indicator if using OAuth subscription
|
|
156
171
|
const usingSubscription = displayModel ? this.session.modelRegistry.isUsingOAuth(displayModel) : false;
|
|
@@ -160,7 +175,7 @@ export class FooterComponent implements Component {
|
|
|
160
175
|
}
|
|
161
176
|
|
|
162
177
|
// Per-prompt cost annotation (opt-in via show_token_cost preference, #1515)
|
|
163
|
-
if (process.env.GSD_SHOW_TOKEN_COST === "1") {
|
|
178
|
+
if (this.verboseFooterEnabled && process.env.GSD_SHOW_TOKEN_COST === "1") {
|
|
164
179
|
const lastTurnCost = this.session.getLastTurnCost();
|
|
165
180
|
if (lastTurnCost > 0) {
|
|
166
181
|
statsParts.push(`(last: ${formatPromptCost(lastTurnCost)})`);
|