@north-light/crouter 0.3.163 → 0.3.164
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/builtin-memory/internal/INDEX.md +1 -0
- package/dist/builtin-memory/internal/memory-loading.md +45 -0
- package/dist/clients/attach/__tests__/crtr-output-coverage.test.js +1 -1
- package/dist/clients/attach/__tests__/edit-diff.test.js +21 -20
- package/dist/clients/attach/render/chat-view.js +46 -35
- package/dist/clients/attach/render/crtr-output.d.ts +2 -1
- package/dist/clients/attach/render/crtr-output.js +4 -4
- package/dist/clients/attach/render/edit-diff.d.ts +21 -6
- package/dist/clients/attach/render/edit-diff.js +90 -91
- package/dist/clients/attach/render/tool-calls.d.ts +14 -18
- package/dist/clients/attach/render/tool-calls.js +66 -41
- package/dist/clients/attach/viewer.js +376 -374
- package/dist/commands/memory/lint.js +1 -1
- package/dist/commands/memory/write.js +1 -1
- package/dist/pi-extensions/canvas-tool-guide.js +4 -1
- package/package.json +2 -2
- package/runtime.lock.json +6 -6
|
@@ -19,14 +19,22 @@ import { TOOL_ICON } from '../../../core/preview-registry.js';
|
|
|
19
19
|
function str(value) {
|
|
20
20
|
return typeof value === 'string' ? value : '';
|
|
21
21
|
}
|
|
22
|
-
/**
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
22
|
+
/** Per-tool fold state owned by the attach viewer. Tool definitions capture the
|
|
23
|
+
* call component they render while folded, so the viewer never reaches into
|
|
24
|
+
* `ToolExecutionComponent`'s private renderer fields to recover it. */
|
|
25
|
+
export class FoldedToolCallController {
|
|
26
|
+
folded = false;
|
|
27
|
+
component;
|
|
28
|
+
setFolded(folded) {
|
|
29
|
+
this.folded = folded;
|
|
30
|
+
}
|
|
31
|
+
capture(component) {
|
|
32
|
+
this.component = component;
|
|
33
|
+
return component;
|
|
34
|
+
}
|
|
35
|
+
callComponent() {
|
|
36
|
+
return this.component;
|
|
37
|
+
}
|
|
30
38
|
}
|
|
31
39
|
/** Count file lines the way a diff does: a trailing newline terminates the last
|
|
32
40
|
* line instead of creating an extra empty one. */
|
|
@@ -36,7 +44,7 @@ function countFileLines(content) {
|
|
|
36
44
|
const newlines = content.match(/\n/g)?.length ?? 0;
|
|
37
45
|
return newlines + (content.endsWith('\n') ? 0 : 1);
|
|
38
46
|
}
|
|
39
|
-
/** The green/red line
|
|
47
|
+
/** The green/red changed-line suffix shown after a folded edit path. */
|
|
40
48
|
function lineChangesDisplay(changes, theme) {
|
|
41
49
|
if (!changes)
|
|
42
50
|
return '';
|
|
@@ -47,6 +55,12 @@ function lineChangesDisplay(changes, theme) {
|
|
|
47
55
|
counts.push(theme.fg('toolDiffRemoved', `-${changes.removed}`));
|
|
48
56
|
return counts.length > 0 ? ` ${counts.join(' ')}` : '';
|
|
49
57
|
}
|
|
58
|
+
/** A write has no before/after diff, so report its output size neutrally. */
|
|
59
|
+
function lineCountDisplay(lines, theme) {
|
|
60
|
+
if (lines === undefined)
|
|
61
|
+
return '';
|
|
62
|
+
return ` ${theme.fg('muted', `${lines} ${lines === 1 ? 'line' : 'lines'}`)}`;
|
|
63
|
+
}
|
|
50
64
|
/** The one-line folded form of a file tool's call: `<icon> <label> <path>`,
|
|
51
65
|
* optionally followed by its green/red changed-line counts. */
|
|
52
66
|
export function foldedFileCallLine(tool, rawPath, theme, cwd, changes) {
|
|
@@ -72,18 +86,29 @@ function pathDisplay(rawPath, theme, cwd) {
|
|
|
72
86
|
function header(icon, label, theme) {
|
|
73
87
|
return `${theme.fg('accent', icon)} ${theme.fg('toolTitle', theme.bold(label))}`;
|
|
74
88
|
}
|
|
75
|
-
/**
|
|
76
|
-
*
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
89
|
+
/** Add an icon to a tool call without mutating the imported renderer's private
|
|
90
|
+
* text storage. The imported component remains responsible for its own body. */
|
|
91
|
+
class IconPrefixedToolCall {
|
|
92
|
+
child;
|
|
93
|
+
prefix;
|
|
94
|
+
constructor(child, prefix) {
|
|
95
|
+
this.child = child;
|
|
96
|
+
this.prefix = prefix;
|
|
97
|
+
}
|
|
98
|
+
update(child, prefix) {
|
|
99
|
+
this.child = child;
|
|
100
|
+
this.prefix = prefix;
|
|
101
|
+
}
|
|
102
|
+
invalidate() {
|
|
103
|
+
this.child.invalidate();
|
|
104
|
+
}
|
|
105
|
+
render(width) {
|
|
106
|
+
const prefixWidth = visibleWidth(this.prefix);
|
|
107
|
+
const lines = this.child.render(Math.max(1, width - prefixWidth));
|
|
108
|
+
if (lines.length === 0)
|
|
109
|
+
return [truncateToWidth(this.prefix.trimEnd(), width, '')];
|
|
110
|
+
return lines.map((line, index) => index === 0 ? truncateToWidth(`${this.prefix}${line}`, width, '') : line);
|
|
111
|
+
}
|
|
87
112
|
}
|
|
88
113
|
// ---------------------------------------------------------------------------
|
|
89
114
|
// read
|
|
@@ -91,7 +116,7 @@ export function prefixToolText(component, prefix) {
|
|
|
91
116
|
/** Attach-viewer `read`: an icon + the path (plus any `:offset-limit` range).
|
|
92
117
|
* The call is a single line by construction, so a folded read shows exactly
|
|
93
118
|
* what it read and nothing else. */
|
|
94
|
-
export function createCrtrReadToolDefinition(cwd) {
|
|
119
|
+
export function createCrtrReadToolDefinition(cwd, fold = new FoldedToolCallController()) {
|
|
95
120
|
const builtin = createReadToolDefinition(cwd);
|
|
96
121
|
return {
|
|
97
122
|
name: 'read',
|
|
@@ -110,7 +135,7 @@ export function createCrtrReadToolDefinition(cwd) {
|
|
|
110
135
|
const line = `${header(TOOL_ICON.read, 'read', theme)} ${pathDisplay(raw, theme, context.cwd ?? cwd)}${range}`;
|
|
111
136
|
const component = context.lastComponent ?? new Text('', 0, 0);
|
|
112
137
|
component.setText(line);
|
|
113
|
-
return component;
|
|
138
|
+
return fold.capture(component);
|
|
114
139
|
},
|
|
115
140
|
renderResult(result, options, theme, context) {
|
|
116
141
|
return builtin.renderResult(result, options, theme, context);
|
|
@@ -123,30 +148,30 @@ export function createCrtrReadToolDefinition(cwd) {
|
|
|
123
148
|
/** Attach-viewer `write`: pi's builtin call renderer (it owns the
|
|
124
149
|
* syntax-highlighted content preview and its expand hint) with the icon
|
|
125
150
|
* prefixed onto its header line. Folded, only that header survives. */
|
|
126
|
-
export function createCrtrWriteToolDefinition(cwd) {
|
|
151
|
+
export function createCrtrWriteToolDefinition(cwd, fold = new FoldedToolCallController()) {
|
|
127
152
|
const builtin = createWriteToolDefinition(cwd);
|
|
153
|
+
let builtinCall;
|
|
154
|
+
let decoratedCall;
|
|
128
155
|
return {
|
|
129
156
|
name: 'write',
|
|
130
157
|
label: 'write',
|
|
131
158
|
description: 'Render write calls with a crtr icon header in the attach viewer.',
|
|
132
159
|
parameters: {},
|
|
133
160
|
renderCall(args, theme, context) {
|
|
134
|
-
if (
|
|
161
|
+
if (fold.folded) {
|
|
135
162
|
const a = args;
|
|
136
|
-
const
|
|
137
|
-
const line = foldedFileCallLine('write', str(a?.file_path ?? a?.path), theme, context.cwd ?? cwd
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
const last = context.lastComponent;
|
|
141
|
-
if (last && typeof last.setText === 'function') {
|
|
142
|
-
last.setText(line);
|
|
143
|
-
return last;
|
|
144
|
-
}
|
|
145
|
-
return new Text(line, 0, 0);
|
|
163
|
+
const lines = context.isError ? undefined : countFileLines(str(a?.content));
|
|
164
|
+
const line = foldedFileCallLine('write', str(a?.file_path ?? a?.path), theme, context.cwd ?? cwd) +
|
|
165
|
+
lineCountDisplay(lines, theme);
|
|
166
|
+
return fold.capture(new Text(line, 0, 0));
|
|
146
167
|
}
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
168
|
+
builtinCall = builtin.renderCall(args, theme, { ...context, lastComponent: builtinCall });
|
|
169
|
+
const prefix = `${theme.fg('accent', TOOL_ICON.write)} `;
|
|
170
|
+
if (decoratedCall)
|
|
171
|
+
decoratedCall.update(builtinCall, prefix);
|
|
172
|
+
else
|
|
173
|
+
decoratedCall = new IconPrefixedToolCall(builtinCall, prefix);
|
|
174
|
+
return fold.capture(decoratedCall);
|
|
150
175
|
},
|
|
151
176
|
renderResult(result, options, theme, context) {
|
|
152
177
|
return builtin.renderResult(result, options, theme, context);
|
|
@@ -242,7 +267,7 @@ class BashCallComponent {
|
|
|
242
267
|
}
|
|
243
268
|
}
|
|
244
269
|
/** Attach-viewer `bash` for anything that is not a `crtr` invocation. */
|
|
245
|
-
export function createCrtrPlainBashToolDefinition(cwd) {
|
|
270
|
+
export function createCrtrPlainBashToolDefinition(cwd, fold = new FoldedToolCallController()) {
|
|
246
271
|
const builtin = createBashToolDefinition(cwd);
|
|
247
272
|
return {
|
|
248
273
|
name: 'bash',
|
|
@@ -262,8 +287,8 @@ export function createCrtrPlainBashToolDefinition(cwd) {
|
|
|
262
287
|
const component = context.lastComponent instanceof BashCallComponent ? context.lastComponent : new BashCallComponent();
|
|
263
288
|
component.update(theme, str(a?.command),
|
|
264
289
|
// Folded wins over ctrl+o expansion: a folded bash is always its one-line form.
|
|
265
|
-
Boolean(context.expanded) && !
|
|
266
|
-
return component;
|
|
290
|
+
Boolean(context.expanded) && !fold.folded, typeof a?.timeout === 'number' ? a.timeout : undefined);
|
|
291
|
+
return fold.capture(component);
|
|
267
292
|
},
|
|
268
293
|
renderResult(result, options, theme, context) {
|
|
269
294
|
return builtin.renderResult(result, options, theme, context);
|