@crouton-kit/crouter 0.3.70 → 0.3.78
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/README.md +1 -1
- package/dist/builtin-memory/00-runtime-base.md +3 -10
- package/dist/builtin-memory/04-base-worker.md +18 -0
- package/dist/builtin-memory/04-orchestration-kernel.md +1 -1
- package/dist/builtin-pi-packages/pi-crtr-extensions/README.md +13 -34
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/__tests__/provider-rotation.test.ts +622 -724
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.ts +231 -68
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/statusline.ts +2 -9
- package/dist/builtin-pi-packages/pi-crtr-extensions/lib/subscription-state.ts +361 -90
- package/dist/builtin-pi-packages/pi-crtr-extensions/package-lock.json +2 -2
- package/dist/builtin-pi-packages/pi-mode-switch/README.md +11 -39
- package/dist/builtin-pi-packages/pi-mode-switch/extensions/index.ts +20 -15
- package/dist/builtin-pi-packages/pi-mode-switch/package.json +1 -1
- package/dist/builtin-views/canvas/tui.mjs +8 -9
- package/dist/builtin-views/chat/tui.mjs +12 -12
- package/dist/builtin-views/git-pr/tui.mjs +7 -8
- package/dist/builtin-views/inbox/tui.mjs +27 -41
- package/dist/builtin-views/linkedin/tui.mjs +23 -37
- package/dist/builtin-views/prompt-review/tui.mjs +11 -11
- package/dist/builtin-views/settings/tui.mjs +11 -11
- package/dist/builtin-views/workspace-sidebar/tui.mjs +8 -9
- package/dist/clients/attach/__tests__/attach-chrome-remote.test.js +3 -3
- package/dist/clients/attach/__tests__/crtr-output-render.test.js +1 -1
- package/dist/clients/attach/__tests__/mermaid-render.test.d.ts +1 -0
- package/dist/clients/attach/__tests__/mermaid-render.test.js +20 -0
- package/dist/clients/attach/__tests__/oauth-dialog-lifecycle.test.d.ts +1 -0
- package/dist/clients/attach/__tests__/oauth-dialog-lifecycle.test.js +110 -0
- package/dist/clients/attach/attach-cmd.d.ts +1 -1
- package/dist/clients/attach/attach-cmd.js +738 -735
- package/dist/clients/attach/canvas-panels.js +2 -3
- package/dist/clients/attach/chat-view.d.ts +7 -8
- package/dist/clients/attach/chat-view.js +119 -79
- package/dist/clients/attach/edit-diff-render.d.ts +6 -0
- package/dist/clients/attach/edit-diff-render.js +263 -0
- package/dist/clients/attach/input-controller.d.ts +28 -2
- package/dist/clients/attach/input-controller.js +38 -5
- package/dist/clients/attach/mermaid-render.js +5 -2
- package/dist/clients/attach/pickers.d.ts +8 -7
- package/dist/clients/attach/pickers.js +11 -17
- package/dist/clients/attach/slash-commands.d.ts +9 -0
- package/dist/clients/attach/slash-commands.js +127 -7
- package/dist/clients/attach/titled-editor.d.ts +12 -1
- package/dist/clients/attach/titled-editor.js +103 -8
- package/dist/commands/canvas-browse.js +2 -2
- package/dist/commands/memory/lint.js +39 -5
- package/dist/commands/memory/write.js +1 -0
- package/dist/commands/node.js +9 -2
- package/dist/commands/surface-tmux-spread.js +1 -3
- package/dist/commands/sys/__tests__/config-keybindings.test.d.ts +1 -0
- package/dist/commands/sys/__tests__/config-keybindings.test.js +55 -0
- package/dist/commands/sys/__tests__/config-model-ladders.test.d.ts +1 -0
- package/dist/commands/sys/__tests__/config-model-ladders.test.js +121 -0
- package/dist/commands/sys/config.js +18 -21
- package/dist/commands/sys/setup-core.d.ts +12 -1
- package/dist/commands/sys/setup-core.js +23 -3
- package/dist/commands/sys/setup.js +42 -12
- package/dist/core/__tests__/base-worker-prompt.test.d.ts +1 -0
- package/dist/core/__tests__/base-worker-prompt.test.js +24 -0
- package/dist/core/__tests__/canvas-inbox-watcher-hold.test.js +232 -1
- package/dist/core/__tests__/fault-classifier.test.js +15 -0
- package/dist/core/__tests__/full/broker-dialogs.test.js +7 -2
- package/dist/core/__tests__/host-teardown-process-group.test.js +15 -4
- package/dist/core/__tests__/stream-watchdog.test.d.ts +1 -0
- package/dist/core/__tests__/stream-watchdog.test.js +70 -0
- package/dist/core/__tests__/tmux-surface.test.js +72 -0
- package/dist/core/canvas/browse/__tests__/model.test.js +23 -5
- package/dist/core/canvas/browse/__tests__/render.test.js +1 -0
- package/dist/core/canvas/browse/app.js +164 -5
- package/dist/core/canvas/browse/model.d.ts +10 -6
- package/dist/core/canvas/browse/model.js +20 -12
- package/dist/core/canvas/browse/pins.d.ts +4 -0
- package/dist/core/canvas/browse/pins.js +29 -0
- package/dist/core/canvas/browse/render.d.ts +13 -1
- package/dist/core/canvas/browse/render.js +72 -15
- package/dist/core/canvas/nav-model.js +5 -11
- package/dist/core/canvas/paths.d.ts +5 -0
- package/dist/core/canvas/paths.js +7 -0
- package/dist/core/config.d.ts +53 -2
- package/dist/core/config.js +228 -110
- package/dist/core/fault-classifier.js +1 -1
- package/dist/core/fs-utils.d.ts +1 -0
- package/dist/core/fs-utils.js +15 -1
- package/dist/core/keybindings/__tests__/resolve.test.d.ts +1 -0
- package/dist/core/keybindings/__tests__/resolve.test.js +224 -0
- package/dist/core/keybindings/catalog.d.ts +14 -0
- package/dist/core/keybindings/catalog.js +256 -0
- package/dist/core/keybindings/index.d.ts +5 -0
- package/dist/core/keybindings/index.js +4 -0
- package/dist/core/keybindings/match.d.ts +29 -0
- package/dist/core/keybindings/match.js +99 -0
- package/dist/core/keybindings/persistence.d.ts +14 -0
- package/dist/core/keybindings/persistence.js +31 -0
- package/dist/core/keybindings/resolve.d.ts +14 -0
- package/dist/core/keybindings/resolve.js +305 -0
- package/dist/core/keybindings/types.d.ts +37 -0
- package/dist/core/keybindings/types.js +1 -0
- package/dist/core/memory-resolver.d.ts +1 -1
- package/dist/core/memory-resolver.js +10 -3
- package/dist/core/predicate.d.ts +5 -3
- package/dist/core/predicate.js +5 -3
- package/dist/core/runtime/broker-protocol.d.ts +33 -5
- package/dist/core/runtime/broker.js +163 -14
- package/dist/core/runtime/launch.d.ts +39 -6
- package/dist/core/runtime/launch.js +78 -20
- package/dist/core/runtime/naming.js +3 -3
- package/dist/core/runtime/pi-cli.d.ts +6 -0
- package/dist/core/runtime/pi-cli.js +16 -2
- package/dist/core/runtime/placement.d.ts +2 -2
- package/dist/core/runtime/placement.js +4 -1
- package/dist/core/runtime/promote.js +4 -0
- package/dist/core/runtime/recap.d.ts +3 -3
- package/dist/core/runtime/recap.js +47 -44
- package/dist/core/runtime/recycle.js +6 -1
- package/dist/core/runtime/reset.js +5 -0
- package/dist/core/runtime/session-list-cache.d.ts +23 -0
- package/dist/core/runtime/session-list-cache.js +270 -0
- package/dist/core/runtime/spawn.js +51 -32
- package/dist/core/runtime/stream-watchdog.d.ts +26 -0
- package/dist/core/runtime/stream-watchdog.js +75 -0
- package/dist/core/runtime/tmux-chrome.d.ts +1 -1
- package/dist/core/runtime/tmux-chrome.js +2 -2
- package/dist/core/runtime/tmux.d.ts +18 -15
- package/dist/core/runtime/tmux.js +216 -110
- package/dist/core/tui/__tests__/host-keybindings.test.d.ts +1 -0
- package/dist/core/tui/__tests__/host-keybindings.test.js +112 -0
- package/dist/core/tui/host.d.ts +30 -1
- package/dist/core/tui/host.js +148 -34
- package/dist/core/view/contract.d.ts +17 -3
- package/dist/pi-extensions/__tests__/canvas-stophook-agentend.test.js +41 -0
- package/dist/pi-extensions/__tests__/canvas-stophook-context-nudge.test.d.ts +1 -0
- package/dist/pi-extensions/__tests__/canvas-stophook-context-nudge.test.js +121 -0
- package/dist/pi-extensions/canvas-inbox-watcher.d.ts +1 -1
- package/dist/pi-extensions/canvas-inbox-watcher.js +203 -25
- package/dist/pi-extensions/canvas-recap.d.ts +4 -0
- package/dist/pi-extensions/canvas-recap.js +38 -37
- package/dist/pi-extensions/canvas-stophook.d.ts +11 -0
- package/dist/pi-extensions/canvas-stophook.js +33 -4
- package/dist/types.d.ts +3 -13
- package/dist/types.js +2 -26
- package/dist/web-client/assets/{index-DiFuLcp6.js → index--md2ylfi.js} +1 -1
- package/dist/web-client/index.html +1 -1
- package/dist/web-client/sw.js +1 -1
- package/package.json +3 -3
- package/dist/pi-extensions/canvas-nav.d.ts +0 -43
- package/dist/pi-extensions/canvas-nav.js +0 -640
- package/dist/pi-extensions/widget-order-bus.d.ts +0 -6
- package/dist/pi-extensions/widget-order-bus.js +0 -34
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
// canvas-panels.ts — the subscribed-node panel for `crtr surface attach` (Unit Q).
|
|
2
2
|
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
// same canvas read authority. Produces the two line stacks the viewer paints
|
|
3
|
+
// Reads through the CanvasSource seam so the viewer chrome and graph overlay
|
|
4
|
+
// share the same canvas read authority. Produces the two line stacks the viewer paints
|
|
6
5
|
// into Containers around the editor:
|
|
7
6
|
// managers → ABOVE the editor: this node's manager (its first subscriber).
|
|
8
7
|
// reports → BELOW the editor: this node's live reports (active|idle
|
|
@@ -28,9 +28,10 @@ export declare class ChatView {
|
|
|
28
28
|
private readonly tui;
|
|
29
29
|
/** The chat scroll area handed in by the attach command. */
|
|
30
30
|
private readonly container;
|
|
31
|
-
/**
|
|
32
|
-
*
|
|
33
|
-
|
|
31
|
+
/** Settled history is cached independently from the live status area so picker
|
|
32
|
+
* navigation does not re-render a long conversation on every key repeat. */
|
|
33
|
+
private readonly historyContainer;
|
|
34
|
+
/** Activity/status area below history. Holds working/compaction/retry loaders. */
|
|
34
35
|
private readonly statusContainer;
|
|
35
36
|
private readonly cwd;
|
|
36
37
|
private readonly showImages;
|
|
@@ -54,8 +55,6 @@ export declare class ChatView {
|
|
|
54
55
|
private bashComponent;
|
|
55
56
|
/** The single active activity loader (working/compaction/retry), if any. */
|
|
56
57
|
private activityLoader;
|
|
57
|
-
/** While true, `append` skips re-pinning the status container (bulk rebuild). */
|
|
58
|
-
private bulkMode;
|
|
59
58
|
/** Text of the most recent assistant message, kept current from the welcome
|
|
60
59
|
* snapshot AND the live event stream so `/copy` (getLastAssistantText) works
|
|
61
60
|
* whether the message arrived before or after attach. */
|
|
@@ -91,15 +90,15 @@ export declare class ChatView {
|
|
|
91
90
|
private addMessageToChat;
|
|
92
91
|
/** Tool component with native viewer renderers for crouter-owned tool output. */
|
|
93
92
|
private makeToolComponent;
|
|
94
|
-
/** Append
|
|
93
|
+
/** Append conversation content above the independent live status area. */
|
|
95
94
|
private append;
|
|
96
95
|
/** Approximate render width for inline mermaid diagrams. Only affects
|
|
97
96
|
* termrender's trailing padding (stripped in the transform), so the terminal
|
|
98
97
|
* column count is close enough without threading the live pane width here. */
|
|
99
98
|
private renderWidth;
|
|
100
|
-
/** Number of
|
|
99
|
+
/** Number of rendered conversation components. */
|
|
101
100
|
private chatChildCount;
|
|
102
|
-
/** Clear all chat content
|
|
101
|
+
/** Clear all chat content. */
|
|
103
102
|
private resetChat;
|
|
104
103
|
private makeLoader;
|
|
105
104
|
/** Swap the single active activity indicator (working/compaction/retry). */
|
|
@@ -27,6 +27,7 @@ import { CRTR_OUTPUT_CUSTOM_TYPE, CrtrOutputMessageComponent, createCrtrBashTool
|
|
|
27
27
|
import { CRTR_CYCLE_DIVIDER_CUSTOM_TYPE } from '../../core/runtime/session-cycles.js';
|
|
28
28
|
import { SITUATIONAL_CONTEXT_CUSTOM_TYPE } from '../../core/runtime/situational-context.js';
|
|
29
29
|
import { transformMessageMermaid } from './mermaid-render.js';
|
|
30
|
+
import { createCrtrEditToolDefinition } from './edit-diff-render.js';
|
|
30
31
|
/** customType stamped on the injected `<crtr-context>` bearings block (mirrors
|
|
31
32
|
* CONTEXT_INTRO_CUSTOM_TYPE in canvas-context-intro.ts — kept as a local literal
|
|
32
33
|
* so the viewer never imports the in-process extension). */
|
|
@@ -51,13 +52,56 @@ function isExpandable(obj) {
|
|
|
51
52
|
typeof obj.setExpanded === 'function');
|
|
52
53
|
}
|
|
53
54
|
const defaultErrorStyle = (s) => `\x1b[31m${s}\x1b[39m`;
|
|
55
|
+
/** Keep settled conversation history out of picker/editor render hot paths. The
|
|
56
|
+
* root TUI still compares the cached lines, but it no longer re-renders every
|
|
57
|
+
* markdown/tool component for each repeated navigation key. */
|
|
58
|
+
class RenderCacheContainer extends Container {
|
|
59
|
+
cachedWidth;
|
|
60
|
+
cachedLines;
|
|
61
|
+
volatile = false;
|
|
62
|
+
markDirty() {
|
|
63
|
+
this.cachedLines = undefined;
|
|
64
|
+
}
|
|
65
|
+
setVolatile(value) {
|
|
66
|
+
this.volatile = value;
|
|
67
|
+
if (value)
|
|
68
|
+
this.markDirty();
|
|
69
|
+
}
|
|
70
|
+
addChild(component) {
|
|
71
|
+
super.addChild(component);
|
|
72
|
+
this.markDirty();
|
|
73
|
+
}
|
|
74
|
+
removeChild(component) {
|
|
75
|
+
super.removeChild(component);
|
|
76
|
+
this.markDirty();
|
|
77
|
+
}
|
|
78
|
+
clear() {
|
|
79
|
+
super.clear();
|
|
80
|
+
this.markDirty();
|
|
81
|
+
}
|
|
82
|
+
invalidate() {
|
|
83
|
+
super.invalidate();
|
|
84
|
+
this.markDirty();
|
|
85
|
+
}
|
|
86
|
+
render(width) {
|
|
87
|
+
if (!this.volatile && this.cachedLines !== undefined && this.cachedWidth === width)
|
|
88
|
+
return this.cachedLines;
|
|
89
|
+
const lines = super.render(width);
|
|
90
|
+
if (!this.volatile) {
|
|
91
|
+
this.cachedWidth = width;
|
|
92
|
+
this.cachedLines = lines;
|
|
93
|
+
}
|
|
94
|
+
return lines;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
54
97
|
export class ChatView {
|
|
55
98
|
tui;
|
|
56
99
|
/** The chat scroll area handed in by the attach command. */
|
|
57
100
|
container;
|
|
58
|
-
/**
|
|
59
|
-
*
|
|
60
|
-
|
|
101
|
+
/** Settled history is cached independently from the live status area so picker
|
|
102
|
+
* navigation does not re-render a long conversation on every key repeat. */
|
|
103
|
+
historyContainer = new RenderCacheContainer();
|
|
104
|
+
/** Activity/status area below history. Holds working/compaction/retry loaders. */
|
|
61
105
|
statusContainer = new Container();
|
|
62
106
|
cwd;
|
|
63
107
|
showImages;
|
|
@@ -81,8 +125,6 @@ export class ChatView {
|
|
|
81
125
|
bashComponent;
|
|
82
126
|
/** The single active activity loader (working/compaction/retry), if any. */
|
|
83
127
|
activityLoader;
|
|
84
|
-
/** While true, `append` skips re-pinning the status container (bulk rebuild). */
|
|
85
|
-
bulkMode = false;
|
|
86
128
|
/** Text of the most recent assistant message, kept current from the welcome
|
|
87
129
|
* snapshot AND the live event stream so `/copy` (getLastAssistantText) works
|
|
88
130
|
* whether the message arrived before or after attach. */
|
|
@@ -100,6 +142,7 @@ export class ChatView {
|
|
|
100
142
|
this.spinnerStyle = opts.palette?.active ?? defaultSpinnerStyle;
|
|
101
143
|
this.dimStyle = opts.palette?.muted ?? defaultDimStyle;
|
|
102
144
|
this.errorStyle = opts.palette?.error ?? defaultErrorStyle;
|
|
145
|
+
this.container.addChild(this.historyContainer);
|
|
103
146
|
this.container.addChild(this.statusContainer);
|
|
104
147
|
}
|
|
105
148
|
// -------------------------------------------------------------------------
|
|
@@ -115,69 +158,58 @@ export class ChatView {
|
|
|
115
158
|
const renderedPendingTools = new Map();
|
|
116
159
|
const messages = snapshot.messages;
|
|
117
160
|
const lastIndex = messages.length - 1;
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
161
|
+
for (let i = 0; i < messages.length; i++) {
|
|
162
|
+
const message = messages[i];
|
|
163
|
+
if (message.role === 'assistant') {
|
|
164
|
+
// Keep the `/copy` source current from the catch-up transcript too, not
|
|
165
|
+
// only the live stream (you usually attach AFTER the last reply landed).
|
|
166
|
+
const text = this.userMessageText(message);
|
|
167
|
+
if (text.trim())
|
|
168
|
+
this.lastAssistantText = text;
|
|
169
|
+
// If the snapshot caught an IN-FLIGHT assistant turn (state.isStreaming
|
|
170
|
+
// and this is the trailing, non-terminal assistant message), bind it as
|
|
171
|
+
// the live streaming component so the relayed message_update/message_end
|
|
172
|
+
// frames that follow `welcome` land on it. Otherwise the in-flight
|
|
173
|
+
// message would freeze at its snapshot value until the next turn — the
|
|
174
|
+
// common case, since you typically attach to a node that is working.
|
|
175
|
+
const isStreamingTail = i === lastIndex &&
|
|
176
|
+
snapshot.state.isStreaming &&
|
|
177
|
+
message.stopReason !== 'aborted' &&
|
|
178
|
+
message.stopReason !== 'error';
|
|
179
|
+
if (isStreamingTail) {
|
|
180
|
+
this.streamingComponent = new AssistantMessageComponent(undefined, this.hideThinking, getMarkdownTheme(), this.hiddenThinkingLabel);
|
|
181
|
+
this.append(this.streamingComponent);
|
|
182
|
+
this.streamingComponent.updateContent(message);
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
this.addMessageToChat(message);
|
|
186
|
+
}
|
|
187
|
+
for (const content of this.assistantToolCalls(message)) {
|
|
188
|
+
const component = this.makeToolComponent(content.name, content.id, content.arguments);
|
|
189
|
+
this.append(component);
|
|
190
|
+
// (isStreamingTail is never aborted/error, so its tools fall through to
|
|
191
|
+
// renderedPendingTools → pendingTools, where live tool events find them.)
|
|
192
|
+
if (message.stopReason === 'aborted' || message.stopReason === 'error') {
|
|
193
|
+
const errorMessage = message.stopReason === 'aborted'
|
|
194
|
+
? 'Operation aborted'
|
|
195
|
+
: (message.errorMessage ?? 'Error');
|
|
196
|
+
component.updateResult({ content: [{ type: 'text', text: errorMessage }], isError: true });
|
|
146
197
|
}
|
|
147
198
|
else {
|
|
148
|
-
|
|
149
|
-
}
|
|
150
|
-
for (const content of this.assistantToolCalls(message)) {
|
|
151
|
-
const component = this.makeToolComponent(content.name, content.id, content.arguments);
|
|
152
|
-
this.append(component);
|
|
153
|
-
// (isStreamingTail is never aborted/error, so its tools fall through to
|
|
154
|
-
// renderedPendingTools → pendingTools, where live tool events find them.)
|
|
155
|
-
if (message.stopReason === 'aborted' || message.stopReason === 'error') {
|
|
156
|
-
const errorMessage = message.stopReason === 'aborted'
|
|
157
|
-
? 'Operation aborted'
|
|
158
|
-
: (message.errorMessage ?? 'Error');
|
|
159
|
-
component.updateResult({ content: [{ type: 'text', text: errorMessage }], isError: true });
|
|
160
|
-
}
|
|
161
|
-
else {
|
|
162
|
-
renderedPendingTools.set(content.id, component);
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
else if (message.role === 'toolResult') {
|
|
167
|
-
const component = renderedPendingTools.get(message.toolCallId);
|
|
168
|
-
if (component) {
|
|
169
|
-
component.updateResult(message);
|
|
170
|
-
renderedPendingTools.delete(message.toolCallId);
|
|
199
|
+
renderedPendingTools.set(content.id, component);
|
|
171
200
|
}
|
|
172
201
|
}
|
|
173
|
-
|
|
174
|
-
|
|
202
|
+
}
|
|
203
|
+
else if (message.role === 'toolResult') {
|
|
204
|
+
const component = renderedPendingTools.get(message.toolCallId);
|
|
205
|
+
if (component) {
|
|
206
|
+
component.updateResult(message);
|
|
207
|
+
renderedPendingTools.delete(message.toolCallId);
|
|
175
208
|
}
|
|
176
209
|
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
this.container.addChild(this.statusContainer);
|
|
210
|
+
else {
|
|
211
|
+
this.addMessageToChat(message);
|
|
212
|
+
}
|
|
181
213
|
}
|
|
182
214
|
for (const [id, component] of renderedPendingTools) {
|
|
183
215
|
this.pendingTools.set(id, component);
|
|
@@ -185,6 +217,7 @@ export class ChatView {
|
|
|
185
217
|
// If we attached mid-turn, the `agent_start` that spins up the working loader
|
|
186
218
|
// already fired before we connected — restore it from the snapshot's streaming
|
|
187
219
|
// state so an actively-working node shows the spinner right away.
|
|
220
|
+
this.historyContainer.setVolatile(snapshot.state.isStreaming);
|
|
188
221
|
if (snapshot.state.isStreaming)
|
|
189
222
|
this.setActivity(this.makeLoader('Working...'));
|
|
190
223
|
this.tui.requestRender();
|
|
@@ -205,10 +238,11 @@ export class ChatView {
|
|
|
205
238
|
* the caller can surface a notice. */
|
|
206
239
|
toggleToolsExpanded() {
|
|
207
240
|
this.toolOutputExpanded = !this.toolOutputExpanded;
|
|
208
|
-
for (const child of this.
|
|
241
|
+
for (const child of this.historyContainer.children) {
|
|
209
242
|
if (isExpandable(child))
|
|
210
243
|
child.setExpanded(this.toolOutputExpanded);
|
|
211
244
|
}
|
|
245
|
+
this.historyContainer.markDirty();
|
|
212
246
|
this.tui.requestRender();
|
|
213
247
|
return this.toolOutputExpanded;
|
|
214
248
|
}
|
|
@@ -217,10 +251,11 @@ export class ChatView {
|
|
|
217
251
|
* the container, so the loop covers it too). Returns the new HIDDEN state. */
|
|
218
252
|
toggleThinking() {
|
|
219
253
|
this.hideThinking = !this.hideThinking;
|
|
220
|
-
for (const child of this.
|
|
254
|
+
for (const child of this.historyContainer.children) {
|
|
221
255
|
if (child instanceof AssistantMessageComponent)
|
|
222
256
|
child.setHideThinkingBlock(this.hideThinking);
|
|
223
257
|
}
|
|
258
|
+
this.historyContainer.markDirty();
|
|
224
259
|
this.tui.requestRender();
|
|
225
260
|
return this.hideThinking;
|
|
226
261
|
}
|
|
@@ -232,16 +267,18 @@ export class ChatView {
|
|
|
232
267
|
switch (event.type) {
|
|
233
268
|
case 'agent_start': {
|
|
234
269
|
this.pendingTools.clear();
|
|
270
|
+
this.historyContainer.setVolatile(true);
|
|
235
271
|
this.setActivity(this.makeLoader('Working...'));
|
|
236
272
|
break;
|
|
237
273
|
}
|
|
238
274
|
case 'agent_end': {
|
|
239
275
|
// Drop a dangling streaming component (e.g. aborted before message_end).
|
|
240
276
|
if (this.streamingComponent) {
|
|
241
|
-
this.
|
|
277
|
+
this.historyContainer.removeChild(this.streamingComponent);
|
|
242
278
|
this.streamingComponent = undefined;
|
|
243
279
|
}
|
|
244
280
|
this.pendingTools.clear();
|
|
281
|
+
this.historyContainer.setVolatile(false);
|
|
245
282
|
this.setActivity(undefined);
|
|
246
283
|
break;
|
|
247
284
|
}
|
|
@@ -393,6 +430,7 @@ export class ChatView {
|
|
|
393
430
|
default:
|
|
394
431
|
break;
|
|
395
432
|
}
|
|
433
|
+
this.historyContainer.markDirty();
|
|
396
434
|
this.tui.requestRender();
|
|
397
435
|
}
|
|
398
436
|
// -------------------------------------------------------------------------
|
|
@@ -404,16 +442,20 @@ export class ChatView {
|
|
|
404
442
|
bashStart(command, excludeFromContext) {
|
|
405
443
|
const component = new BashExecutionComponent(command, this.tui, excludeFromContext);
|
|
406
444
|
this.bashComponent = component;
|
|
445
|
+
this.historyContainer.setVolatile(true);
|
|
407
446
|
this.append(component);
|
|
408
447
|
this.tui.requestRender();
|
|
409
448
|
}
|
|
410
449
|
bashOutput(chunk) {
|
|
411
450
|
this.bashComponent?.appendOutput(chunk);
|
|
451
|
+
this.historyContainer.markDirty();
|
|
412
452
|
this.tui.requestRender();
|
|
413
453
|
}
|
|
414
454
|
bashEnd(result) {
|
|
415
455
|
this.bashComponent?.setComplete(result.exitCode, result.cancelled, result.truncated ? { truncated: true } : undefined, result.fullOutputPath);
|
|
416
456
|
this.bashComponent = undefined;
|
|
457
|
+
this.historyContainer.setVolatile(false);
|
|
458
|
+
this.historyContainer.markDirty();
|
|
417
459
|
this.tui.requestRender();
|
|
418
460
|
}
|
|
419
461
|
// -------------------------------------------------------------------------
|
|
@@ -524,20 +566,18 @@ export class ChatView {
|
|
|
524
566
|
// -------------------------------------------------------------------------
|
|
525
567
|
/** Tool component with native viewer renderers for crouter-owned tool output. */
|
|
526
568
|
makeToolComponent(name, id, args) {
|
|
527
|
-
const toolDefinition = name === 'bash'
|
|
569
|
+
const toolDefinition = name === 'bash'
|
|
570
|
+
? createCrtrBashToolDefinition(args)
|
|
571
|
+
: name === 'edit'
|
|
572
|
+
? createCrtrEditToolDefinition(this.cwd)
|
|
573
|
+
: undefined;
|
|
528
574
|
const component = new ToolExecutionComponent(name, id, args, { showImages: this.showImages, imageWidthCells: this.imageWidthCells }, toolDefinition, this.tui, this.cwd);
|
|
529
575
|
component.setExpanded(this.toolOutputExpanded);
|
|
530
576
|
return component;
|
|
531
577
|
}
|
|
532
|
-
/** Append
|
|
578
|
+
/** Append conversation content above the independent live status area. */
|
|
533
579
|
append(child) {
|
|
534
|
-
|
|
535
|
-
this.container.addChild(child);
|
|
536
|
-
return;
|
|
537
|
-
}
|
|
538
|
-
this.container.removeChild(this.statusContainer);
|
|
539
|
-
this.container.addChild(child);
|
|
540
|
-
this.container.addChild(this.statusContainer);
|
|
580
|
+
this.historyContainer.addChild(child);
|
|
541
581
|
}
|
|
542
582
|
/** Approximate render width for inline mermaid diagrams. Only affects
|
|
543
583
|
* termrender's trailing padding (stripped in the transform), so the terminal
|
|
@@ -545,18 +585,18 @@ export class ChatView {
|
|
|
545
585
|
renderWidth() {
|
|
546
586
|
return Math.max(40, process.stdout.columns ?? 80);
|
|
547
587
|
}
|
|
548
|
-
/** Number of
|
|
588
|
+
/** Number of rendered conversation components. */
|
|
549
589
|
chatChildCount() {
|
|
550
|
-
return this.
|
|
590
|
+
return this.historyContainer.children.length;
|
|
551
591
|
}
|
|
552
|
-
/** Clear all chat content
|
|
592
|
+
/** Clear all chat content. */
|
|
553
593
|
resetChat() {
|
|
554
594
|
this.setActivity(undefined);
|
|
555
595
|
this.streamingComponent = undefined;
|
|
556
596
|
this.pendingTools.clear();
|
|
557
597
|
this.lastAssistantText = undefined;
|
|
558
|
-
this.
|
|
559
|
-
this.
|
|
598
|
+
this.historyContainer.setVolatile(false);
|
|
599
|
+
this.historyContainer.clear();
|
|
560
600
|
}
|
|
561
601
|
makeLoader(message) {
|
|
562
602
|
return new Loader(this.tui, this.spinnerStyle, this.dimStyle, message);
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Attach-viewer tool definition for `edit`: pi's builtin definition drives
|
|
3
|
+
* all state (preview computation, header, success/error bg), then the diff
|
|
4
|
+
* body is re-skinned Claude-Code-style after every render pass.
|
|
5
|
+
*/
|
|
6
|
+
export declare function createCrtrEditToolDefinition(cwd: string): unknown;
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
// edit-diff-render.ts — Claude-Code-style diff rendering for the attach viewer.
|
|
2
|
+
//
|
|
3
|
+
// pi's builtin edit renderer (core/tools/edit.js → renderDiff) paints diff
|
|
4
|
+
// lines with colored FOREGROUND text plus inverse-video on changed tokens.
|
|
5
|
+
// This module re-skins that body the way Claude Code renders diffs: every
|
|
6
|
+
// added line gets a full-width tinted green BACKGROUND, every removed line a
|
|
7
|
+
// full-width tinted red background, with the changed span inside a 1↔1 line
|
|
8
|
+
// modification emphasized in a brighter tint of the same color.
|
|
9
|
+
//
|
|
10
|
+
// We deliberately keep pi's builtin edit definition as the engine — it owns
|
|
11
|
+
// the async preview computation (computeEditsDiff keyed off ctx.state) and
|
|
12
|
+
// the header/status-bg lifecycle — and only swap the rendered diff body.
|
|
13
|
+
// The raw diff string lives on the call component (`preview.diff`), so no
|
|
14
|
+
// ANSI re-parsing of pi's output is ever needed.
|
|
15
|
+
import { createEditToolDefinition } from '@earendil-works/pi-coding-agent';
|
|
16
|
+
import { Container, Spacer, Text } from '@earendil-works/pi-tui';
|
|
17
|
+
const FALLBACK_ADDED = { r: 181, g: 189, b: 104 }; // pi dark green
|
|
18
|
+
const FALLBACK_REMOVED = { r: 204, g: 102, b: 102 }; // pi dark red
|
|
19
|
+
const FALLBACK_CARD_BG = { r: 40, g: 50, b: 40 }; // pi dark toolSuccessBg
|
|
20
|
+
/** Standard xterm-256 palette → rgb (cube + grayscale ramps; 16 base approximated). */
|
|
21
|
+
function xterm256ToRgb(n) {
|
|
22
|
+
if (n < 16) {
|
|
23
|
+
const base = [
|
|
24
|
+
[0, 0, 0], [205, 0, 0], [0, 205, 0], [205, 205, 0],
|
|
25
|
+
[0, 0, 238], [205, 0, 205], [0, 205, 205], [229, 229, 229],
|
|
26
|
+
[127, 127, 127], [255, 0, 0], [0, 255, 0], [255, 255, 0],
|
|
27
|
+
[92, 92, 255], [255, 0, 255], [0, 255, 255], [255, 255, 255],
|
|
28
|
+
];
|
|
29
|
+
const [r, g, b] = base[n] ?? [128, 128, 128];
|
|
30
|
+
return { r, g, b };
|
|
31
|
+
}
|
|
32
|
+
if (n < 232) {
|
|
33
|
+
const i = n - 16;
|
|
34
|
+
const steps = [0, 95, 135, 175, 215, 255];
|
|
35
|
+
return { r: steps[Math.floor(i / 36) % 6], g: steps[Math.floor(i / 6) % 6], b: steps[i % 6] };
|
|
36
|
+
}
|
|
37
|
+
const v = 8 + (n - 232) * 10;
|
|
38
|
+
return { r: v, g: v, b: v };
|
|
39
|
+
}
|
|
40
|
+
/** rgb → nearest xterm-256 cube/gray index (for non-truecolor terminals). */
|
|
41
|
+
function rgbTo256(c) {
|
|
42
|
+
const toCube = (v) => (v < 48 ? 0 : v < 115 ? 1 : Math.min(5, Math.floor((v - 35) / 40)));
|
|
43
|
+
const steps = [0, 95, 135, 175, 215, 255];
|
|
44
|
+
const cr = toCube(c.r);
|
|
45
|
+
const cg = toCube(c.g);
|
|
46
|
+
const cb = toCube(c.b);
|
|
47
|
+
const cubeIdx = 16 + 36 * cr + 6 * cg + cb;
|
|
48
|
+
const cube = { r: steps[cr], g: steps[cg], b: steps[cb] };
|
|
49
|
+
const gray = Math.max(0, Math.min(23, Math.round((((c.r + c.g + c.b) / 3) - 8) / 10)));
|
|
50
|
+
const grayIdx = 232 + gray;
|
|
51
|
+
const grayRgb = xterm256ToRgb(grayIdx);
|
|
52
|
+
const dist = (a, b) => (a.r - b.r) ** 2 + (a.g - b.g) ** 2 + (a.b - b.b) ** 2;
|
|
53
|
+
return dist(c, cube) <= dist(c, grayRgb) ? cubeIdx : grayIdx;
|
|
54
|
+
}
|
|
55
|
+
/** Parse the rgb out of a theme SGR open code (`38/48;2;r;g;b` or `38/48;5;n`). */
|
|
56
|
+
function parseAnsiRgb(ansi) {
|
|
57
|
+
const truecolor = ansi.match(/\x1b\[[34]8;2;(\d+);(\d+);(\d+)m/);
|
|
58
|
+
if (truecolor)
|
|
59
|
+
return { r: Number(truecolor[1]), g: Number(truecolor[2]), b: Number(truecolor[3]) };
|
|
60
|
+
const indexed = ansi.match(/\x1b\[[34]8;5;(\d+)m/);
|
|
61
|
+
if (indexed)
|
|
62
|
+
return xterm256ToRgb(Number(indexed[1]));
|
|
63
|
+
return undefined;
|
|
64
|
+
}
|
|
65
|
+
function mix(a, b, t) {
|
|
66
|
+
return {
|
|
67
|
+
r: Math.round(a.r * t + b.r * (1 - t)),
|
|
68
|
+
g: Math.round(a.g * t + b.g * (1 - t)),
|
|
69
|
+
b: Math.round(a.b * t + b.b * (1 - t)),
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
function bgOpen(c, mode) {
|
|
73
|
+
if (mode === 'truecolor')
|
|
74
|
+
return `\x1b[48;2;${c.r};${c.g};${c.b}m`;
|
|
75
|
+
return `\x1b[48;5;${rgbTo256(c)}m`;
|
|
76
|
+
}
|
|
77
|
+
function themeFgRgb(theme, name, fallback) {
|
|
78
|
+
try {
|
|
79
|
+
return parseAnsiRgb(theme.getFgAnsi(name)) ?? fallback;
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
return fallback;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
function themeBgRgb(theme, name, fallback) {
|
|
86
|
+
try {
|
|
87
|
+
return parseAnsiRgb(theme.getBgAnsi(name)) ?? fallback;
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
return fallback;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
// ---------------------------------------------------------------------------
|
|
94
|
+
// Diff body rendering
|
|
95
|
+
// ---------------------------------------------------------------------------
|
|
96
|
+
/** pi's display-diff line format: "+123 content" / "-123 content" / " 123 content". */
|
|
97
|
+
function parseDiffLine(line) {
|
|
98
|
+
const match = line.match(/^([+-\s])(\s*\d*)\s(.*)$/);
|
|
99
|
+
if (!match)
|
|
100
|
+
return null;
|
|
101
|
+
return { prefix: match[1], lineNum: match[2], content: match[3] };
|
|
102
|
+
}
|
|
103
|
+
function replaceTabs(text) {
|
|
104
|
+
return text.replace(/\t/g, ' ');
|
|
105
|
+
}
|
|
106
|
+
/** Common prefix/suffix char-span diff for a single-line 1↔1 modification. */
|
|
107
|
+
function changedSpans(oldS, newS) {
|
|
108
|
+
const maxP = Math.min(oldS.length, newS.length);
|
|
109
|
+
let p = 0;
|
|
110
|
+
while (p < maxP && oldS[p] === newS[p])
|
|
111
|
+
p++;
|
|
112
|
+
let s = 0;
|
|
113
|
+
while (s < maxP - p && oldS[oldS.length - 1 - s] === newS[newS.length - 1 - s])
|
|
114
|
+
s++;
|
|
115
|
+
return { old: [p, oldS.length - s], new: [p, newS.length - s] };
|
|
116
|
+
}
|
|
117
|
+
/** Wrap the changed span in a brighter tint, restoring the line tint after. */
|
|
118
|
+
function emphasize(content, span, emphOpen, lineOpen) {
|
|
119
|
+
const [start, end] = span;
|
|
120
|
+
if (end <= start)
|
|
121
|
+
return content;
|
|
122
|
+
return content.slice(0, start) + emphOpen + content.slice(start, end) + lineOpen + content.slice(end);
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Build the Claude-Code-style diff body: one full-width background-tinted
|
|
126
|
+
* Text per added/removed line (continuation lines of a wrapped long line keep
|
|
127
|
+
* the tint, exactly like Claude Code), plain context lines in between.
|
|
128
|
+
*
|
|
129
|
+
* `restoreBg` is the SGR that must follow each tinted line so the host Box's
|
|
130
|
+
* own background resumes on its right padding column (a bare `\x1b[49m` would
|
|
131
|
+
* punch a default-bg notch into the card edge).
|
|
132
|
+
*/
|
|
133
|
+
function buildDiffBody(diffText, theme, restoreBg) {
|
|
134
|
+
const mode = theme.getColorMode();
|
|
135
|
+
const cardBg = themeBgRgb(theme, 'toolSuccessBg', FALLBACK_CARD_BG);
|
|
136
|
+
const addedFg = themeFgRgb(theme, 'toolDiffAdded', FALLBACK_ADDED);
|
|
137
|
+
const removedFg = themeFgRgb(theme, 'toolDiffRemoved', FALLBACK_REMOVED);
|
|
138
|
+
const addedLineBg = bgOpen(mix(addedFg, cardBg, 0.24), mode);
|
|
139
|
+
const addedEmphBg = bgOpen(mix(addedFg, cardBg, 0.48), mode);
|
|
140
|
+
const removedLineBg = bgOpen(mix(removedFg, cardBg, 0.24), mode);
|
|
141
|
+
const removedEmphBg = bgOpen(mix(removedFg, cardBg, 0.48), mode);
|
|
142
|
+
const container = new Container();
|
|
143
|
+
const pushLine = (styled, lineBg) => {
|
|
144
|
+
const bgFn = lineBg === undefined ? undefined : (padded) => lineBg + padded + restoreBg;
|
|
145
|
+
container.addChild(new Text(styled, 0, 0, bgFn));
|
|
146
|
+
};
|
|
147
|
+
const pushRemoved = (lineNum, content) => {
|
|
148
|
+
pushLine(`${theme.fg('toolDiffRemoved', `-${lineNum}`)} ${content}`, removedLineBg);
|
|
149
|
+
};
|
|
150
|
+
const pushAdded = (lineNum, content) => {
|
|
151
|
+
pushLine(`${theme.fg('toolDiffAdded', `+${lineNum}`)} ${content}`, addedLineBg);
|
|
152
|
+
};
|
|
153
|
+
const lines = diffText.split('\n');
|
|
154
|
+
let i = 0;
|
|
155
|
+
while (i < lines.length) {
|
|
156
|
+
const parsed = parseDiffLine(lines[i]);
|
|
157
|
+
if (!parsed) {
|
|
158
|
+
pushLine(theme.fg('toolDiffContext', lines[i]));
|
|
159
|
+
i++;
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
if (parsed.prefix === '-') {
|
|
163
|
+
const removed = [];
|
|
164
|
+
while (i < lines.length) {
|
|
165
|
+
const p = parseDiffLine(lines[i]);
|
|
166
|
+
if (!p || p.prefix !== '-')
|
|
167
|
+
break;
|
|
168
|
+
removed.push({ lineNum: p.lineNum, content: replaceTabs(p.content) });
|
|
169
|
+
i++;
|
|
170
|
+
}
|
|
171
|
+
const added = [];
|
|
172
|
+
while (i < lines.length) {
|
|
173
|
+
const p = parseDiffLine(lines[i]);
|
|
174
|
+
if (!p || p.prefix !== '+')
|
|
175
|
+
break;
|
|
176
|
+
added.push({ lineNum: p.lineNum, content: replaceTabs(p.content) });
|
|
177
|
+
i++;
|
|
178
|
+
}
|
|
179
|
+
// Single-line modification → emphasize the changed span in a brighter tint.
|
|
180
|
+
if (removed.length === 1 && added.length === 1) {
|
|
181
|
+
const spans = changedSpans(removed[0].content, added[0].content);
|
|
182
|
+
pushRemoved(removed[0].lineNum, emphasize(removed[0].content, spans.old, removedEmphBg, removedLineBg));
|
|
183
|
+
pushAdded(added[0].lineNum, emphasize(added[0].content, spans.new, addedEmphBg, addedLineBg));
|
|
184
|
+
}
|
|
185
|
+
else {
|
|
186
|
+
for (const line of removed)
|
|
187
|
+
pushRemoved(line.lineNum, line.content);
|
|
188
|
+
for (const line of added)
|
|
189
|
+
pushAdded(line.lineNum, line.content);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
else if (parsed.prefix === '+') {
|
|
193
|
+
pushAdded(parsed.lineNum, replaceTabs(parsed.content));
|
|
194
|
+
i++;
|
|
195
|
+
}
|
|
196
|
+
else {
|
|
197
|
+
pushLine(theme.fg('toolDiffContext', ` ${parsed.lineNum} ${replaceTabs(parsed.content)}`));
|
|
198
|
+
i++;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return container;
|
|
202
|
+
}
|
|
203
|
+
// ---------------------------------------------------------------------------
|
|
204
|
+
// Tool definition wrapper
|
|
205
|
+
// ---------------------------------------------------------------------------
|
|
206
|
+
/** Replace the pi-rendered diff body inside the edit call Box (children:
|
|
207
|
+
* [header, Spacer, body]) with the full-background rendering. No-op when
|
|
208
|
+
* there is no successful preview (header-only or error states keep pi's
|
|
209
|
+
* rendering untouched). */
|
|
210
|
+
function restyleEditCallBox(box, theme) {
|
|
211
|
+
const preview = box.preview;
|
|
212
|
+
if (!preview || 'error' in preview || box.children.length < 2)
|
|
213
|
+
return;
|
|
214
|
+
const header = box.children[0];
|
|
215
|
+
box.clear();
|
|
216
|
+
box.addChild(header);
|
|
217
|
+
box.addChild(new Spacer(1));
|
|
218
|
+
// With a successful preview the edit card bg is always toolSuccessBg
|
|
219
|
+
// (mirrors pi's getEditHeaderBg), so tinted lines restore to that color.
|
|
220
|
+
box.addChild(buildDiffBody(preview.diff, theme, theme.getBgAnsi('toolSuccessBg')));
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Attach-viewer tool definition for `edit`: pi's builtin definition drives
|
|
224
|
+
* all state (preview computation, header, success/error bg), then the diff
|
|
225
|
+
* body is re-skinned Claude-Code-style after every render pass.
|
|
226
|
+
*/
|
|
227
|
+
export function createCrtrEditToolDefinition(cwd) {
|
|
228
|
+
const builtin = createEditToolDefinition(cwd);
|
|
229
|
+
return {
|
|
230
|
+
name: 'edit',
|
|
231
|
+
label: 'edit',
|
|
232
|
+
description: 'Render edit diffs with full-line backgrounds in the attach viewer.',
|
|
233
|
+
parameters: {},
|
|
234
|
+
renderShell: 'self',
|
|
235
|
+
renderCall(args, theme, context) {
|
|
236
|
+
const component = builtin.renderCall(args, theme, context);
|
|
237
|
+
const callBox = context.state.callComponent;
|
|
238
|
+
if (callBox)
|
|
239
|
+
restyleEditCallBox(callBox, theme);
|
|
240
|
+
return component;
|
|
241
|
+
},
|
|
242
|
+
renderResult(result, options, theme, context) {
|
|
243
|
+
const component = builtin.renderResult(result, options, theme, context);
|
|
244
|
+
const callBox = context.state.callComponent;
|
|
245
|
+
if (callBox) {
|
|
246
|
+
// builtin renderResult re-ran buildEditCallComponent (pi styling) —
|
|
247
|
+
// restyle the refreshed body.
|
|
248
|
+
restyleEditCallBox(callBox, theme);
|
|
249
|
+
return component;
|
|
250
|
+
}
|
|
251
|
+
// Fallback path (no call box state, e.g. a bare result replay): render
|
|
252
|
+
// the result diff standalone. No host Box here, so restore to default bg.
|
|
253
|
+
const resultDiff = result?.details?.diff;
|
|
254
|
+
if (!context.isError && typeof resultDiff === 'string') {
|
|
255
|
+
const container = new Container();
|
|
256
|
+
container.addChild(new Spacer(1));
|
|
257
|
+
container.addChild(buildDiffBody(resultDiff, theme, '\x1b[49m'));
|
|
258
|
+
return container;
|
|
259
|
+
}
|
|
260
|
+
return component;
|
|
261
|
+
},
|
|
262
|
+
};
|
|
263
|
+
}
|