agent-sh 0.12.25 → 0.12.27
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent/agent-loop.js +2 -2
- package/dist/agent/conversation-state.js +22 -1
- package/dist/index.js +3 -1
- package/dist/install.js +84 -4
- package/dist/shell/index.d.ts +5 -0
- package/dist/shell/index.js +13 -8
- package/dist/shell/input-handler.js +75 -27
- package/dist/shell/tui-input-view.d.ts +5 -0
- package/dist/shell/tui-input-view.js +137 -96
- package/dist/utils/floating-panel.d.ts +16 -4
- package/dist/utils/floating-panel.js +209 -66
- package/dist/utils/terminal-buffer.d.ts +6 -9
- package/dist/utils/terminal-buffer.js +21 -53
- package/examples/extensions/emacs-buffer.ts +364 -0
- package/examples/extensions/opencode-bridge/index.ts +255 -37
- package/examples/extensions/overlay-agent.ts +28 -5
- package/examples/extensions/terminal-buffer.ts +174 -33
- package/examples/extensions/tunnel-vision.ts +405 -0
- package/examples/extensions/web-access.ts +3 -108
- package/package.json +1 -1
|
@@ -11,130 +11,171 @@ export class TuiInputView {
|
|
|
11
11
|
cursorTermCol = 1;
|
|
12
12
|
autocompleteLines = 0;
|
|
13
13
|
surface;
|
|
14
|
+
frameBuf = null;
|
|
14
15
|
constructor(surface) {
|
|
15
16
|
this.surface = surface ?? new StdoutSurface();
|
|
16
17
|
}
|
|
18
|
+
// Frame buffering: coalesces all emit() calls until endFrame() into one
|
|
19
|
+
// surface.write, bracketed by cursor hide/show so intermediate redraw
|
|
20
|
+
// states never flicker through.
|
|
21
|
+
beginFrame() {
|
|
22
|
+
if (this.frameBuf === null)
|
|
23
|
+
this.frameBuf = "\x1b[?25l";
|
|
24
|
+
}
|
|
25
|
+
endFrame() {
|
|
26
|
+
if (this.frameBuf === null)
|
|
27
|
+
return;
|
|
28
|
+
const out = this.frameBuf + "\x1b[?25h";
|
|
29
|
+
this.frameBuf = null;
|
|
30
|
+
this.surface.write(out);
|
|
31
|
+
}
|
|
32
|
+
emit(s) {
|
|
33
|
+
if (this.frameBuf !== null)
|
|
34
|
+
this.frameBuf += s;
|
|
35
|
+
else
|
|
36
|
+
this.surface.write(s);
|
|
37
|
+
}
|
|
38
|
+
autoFrame(fn) {
|
|
39
|
+
const owned = this.frameBuf === null;
|
|
40
|
+
if (owned)
|
|
41
|
+
this.beginFrame();
|
|
42
|
+
try {
|
|
43
|
+
return fn();
|
|
44
|
+
}
|
|
45
|
+
finally {
|
|
46
|
+
if (owned)
|
|
47
|
+
this.endFrame();
|
|
48
|
+
}
|
|
49
|
+
}
|
|
17
50
|
resetCursor() {
|
|
18
51
|
this.cursorRowsBelow = 0;
|
|
19
52
|
this.cursorTermCol = 1;
|
|
20
53
|
}
|
|
21
54
|
enableModeKeys() {
|
|
22
55
|
// Kitty progressive enhancement + bracket paste (Shift+Enter → \x1b[13;2u).
|
|
23
|
-
this.
|
|
56
|
+
this.emit("\x1b[>1u\x1b[?2004h");
|
|
24
57
|
}
|
|
25
58
|
disableModeKeys() {
|
|
26
|
-
this.
|
|
59
|
+
this.emit("\x1b[<u\x1b[?2004l");
|
|
27
60
|
}
|
|
28
61
|
clearPromptArea() {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
62
|
+
this.autoFrame(() => {
|
|
63
|
+
if (this.cursorRowsBelow > 0) {
|
|
64
|
+
this.emit(`\x1b[${this.cursorRowsBelow}A`);
|
|
65
|
+
}
|
|
66
|
+
this.emit("\r\x1b[J");
|
|
67
|
+
this.cursorRowsBelow = 0;
|
|
68
|
+
});
|
|
34
69
|
}
|
|
35
70
|
drawPrompt(vm) {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
this.surface.write("\r\x1b[J");
|
|
41
|
-
const infoPrefix = vm.agentInfo.info
|
|
42
|
-
? `${vm.agentInfo.info} ${p.success}${vm.indicator}${p.reset} `
|
|
43
|
-
: `${p.success}${vm.indicator}${p.reset} `;
|
|
44
|
-
const promptPrefix = infoPrefix + p.warning + p.bold + vm.promptIcon + " " + p.reset;
|
|
45
|
-
const promptVisLen = visibleLen(infoPrefix) + visibleLen(vm.promptIcon) + 1;
|
|
46
|
-
const display = vm.showBuffer ? vm.displayText : "";
|
|
47
|
-
const dCursor = vm.showBuffer ? vm.displayCursor : 0;
|
|
48
|
-
if (!vm.showBuffer) {
|
|
49
|
-
this.surface.write(promptPrefix);
|
|
50
|
-
const N = promptVisLen;
|
|
51
|
-
this.cursorRowsBelow = N > 0 ? Math.ceil(N / termW) - 1 : 0;
|
|
52
|
-
this.cursorTermCol = N === 0 ? 1 : (N % termW === 0 ? termW : (N % termW) + 1);
|
|
53
|
-
}
|
|
54
|
-
else if (!display.includes("\n")) {
|
|
55
|
-
// DECSC/DECRC bracket the after-cursor text so the cursor lands mid-line.
|
|
56
|
-
const before = display.slice(0, dCursor);
|
|
57
|
-
const after = display.slice(dCursor);
|
|
58
|
-
this.surface.write(promptPrefix + p.accent + before + p.reset +
|
|
59
|
-
"\x1b7" +
|
|
60
|
-
p.accent + after + p.reset +
|
|
61
|
-
"\x1b8");
|
|
62
|
-
const cursorVisCol = promptVisLen + visibleLen(before);
|
|
63
|
-
this.cursorRowsBelow = cursorVisCol > 0 ? Math.ceil(cursorVisCol / termW) - 1 : 0;
|
|
64
|
-
this.cursorTermCol = cursorVisCol === 0 ? 1 : (cursorVisCol % termW === 0 ? termW : (cursorVisCol % termW) + 1);
|
|
65
|
-
}
|
|
66
|
-
else {
|
|
67
|
-
const lines = display.split("\n");
|
|
68
|
-
const indent = " ".repeat(promptVisLen);
|
|
69
|
-
let charsRemaining = dCursor;
|
|
70
|
-
let cursorLine = 0;
|
|
71
|
-
for (let li = 0; li < lines.length; li++) {
|
|
72
|
-
if (charsRemaining <= lines[li].length) {
|
|
73
|
-
cursorLine = li;
|
|
74
|
-
break;
|
|
75
|
-
}
|
|
76
|
-
charsRemaining -= lines[li].length + 1;
|
|
77
|
-
cursorLine = li + 1;
|
|
71
|
+
this.autoFrame(() => {
|
|
72
|
+
const termW = this.surface.columns;
|
|
73
|
+
if (this.cursorRowsBelow > 0) {
|
|
74
|
+
this.emit(`\x1b[${this.cursorRowsBelow}A`);
|
|
78
75
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
76
|
+
this.emit("\r\x1b[J");
|
|
77
|
+
const infoPrefix = vm.agentInfo.info
|
|
78
|
+
? `${vm.agentInfo.info} ${p.success}${vm.indicator}${p.reset} `
|
|
79
|
+
: `${p.success}${vm.indicator}${p.reset} `;
|
|
80
|
+
const promptPrefix = infoPrefix + p.warning + p.bold + vm.promptIcon + " " + p.reset;
|
|
81
|
+
const promptVisLen = visibleLen(infoPrefix) + visibleLen(vm.promptIcon) + 1;
|
|
82
|
+
const display = vm.showBuffer ? vm.displayText : "";
|
|
83
|
+
const dCursor = vm.showBuffer ? vm.displayCursor : 0;
|
|
84
|
+
if (!vm.showBuffer) {
|
|
85
|
+
this.emit(promptPrefix);
|
|
86
|
+
const N = promptVisLen;
|
|
87
|
+
this.cursorRowsBelow = N > 0 ? Math.ceil(N / termW) - 1 : 0;
|
|
88
|
+
this.cursorTermCol = N === 0 ? 1 : (N % termW === 0 ? termW : (N % termW) + 1);
|
|
89
|
+
}
|
|
90
|
+
else if (!display.includes("\n")) {
|
|
91
|
+
// DECSC/DECRC bracket the after-cursor text so the cursor lands mid-line.
|
|
92
|
+
const before = display.slice(0, dCursor);
|
|
93
|
+
const after = display.slice(dCursor);
|
|
94
|
+
this.emit(promptPrefix + p.accent + before + p.reset +
|
|
95
|
+
"\x1b7" +
|
|
96
|
+
p.accent + after + p.reset +
|
|
97
|
+
"\x1b8");
|
|
98
|
+
const cursorVisCol = promptVisLen + visibleLen(before);
|
|
99
|
+
this.cursorRowsBelow = cursorVisCol > 0 ? Math.ceil(cursorVisCol / termW) - 1 : 0;
|
|
100
|
+
this.cursorTermCol = cursorVisCol === 0 ? 1 : (cursorVisCol % termW === 0 ? termW : (cursorVisCol % termW) + 1);
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
const lines = display.split("\n");
|
|
104
|
+
const indent = " ".repeat(promptVisLen);
|
|
105
|
+
let charsRemaining = dCursor;
|
|
106
|
+
let cursorLine = 0;
|
|
107
|
+
for (let li = 0; li < lines.length; li++) {
|
|
108
|
+
if (charsRemaining <= lines[li].length) {
|
|
109
|
+
cursorLine = li;
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
charsRemaining -= lines[li].length + 1;
|
|
113
|
+
cursorLine = li + 1;
|
|
96
114
|
}
|
|
97
|
-
|
|
98
|
-
|
|
115
|
+
let output = "";
|
|
116
|
+
let cursorRowFromTop = 0;
|
|
117
|
+
let rowsSoFar = 0;
|
|
118
|
+
for (let li = 0; li < lines.length; li++) {
|
|
119
|
+
const prefix = li === 0 ? promptPrefix : indent;
|
|
120
|
+
const lineText = lines[li];
|
|
121
|
+
const lineVisLen = promptVisLen + visibleLen(lineText);
|
|
122
|
+
const lineTermRows = lineVisLen > 0 ? Math.ceil(lineVisLen / termW) : 1;
|
|
123
|
+
if (li === cursorLine) {
|
|
124
|
+
const before = lineText.slice(0, charsRemaining);
|
|
125
|
+
const after = lineText.slice(charsRemaining);
|
|
126
|
+
output += prefix + p.accent + before + p.reset;
|
|
127
|
+
output += "\x1b7";
|
|
128
|
+
output += p.accent + after + p.reset;
|
|
129
|
+
const beforeVisCol = promptVisLen + visibleLen(before);
|
|
130
|
+
cursorRowFromTop = rowsSoFar + (beforeVisCol > 0 ? Math.ceil(beforeVisCol / termW) - 1 : 0);
|
|
131
|
+
this.cursorTermCol = beforeVisCol === 0 ? 1 : (beforeVisCol % termW === 0 ? termW : (beforeVisCol % termW) + 1);
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
output += prefix + p.accent + lineText + p.reset;
|
|
135
|
+
}
|
|
136
|
+
if (li < lines.length - 1)
|
|
137
|
+
output += "\n";
|
|
138
|
+
rowsSoFar += lineTermRows;
|
|
99
139
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
rowsSoFar += lineTermRows;
|
|
140
|
+
this.emit(output + "\x1b8");
|
|
141
|
+
this.cursorRowsBelow = cursorRowFromTop;
|
|
103
142
|
}
|
|
104
|
-
|
|
105
|
-
this.cursorRowsBelow = cursorRowFromTop;
|
|
106
|
-
}
|
|
143
|
+
});
|
|
107
144
|
}
|
|
108
145
|
drawAutocomplete(vm) {
|
|
109
146
|
if (vm.items.length === 0)
|
|
110
147
|
return;
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
148
|
+
this.autoFrame(() => {
|
|
149
|
+
const lines = [];
|
|
150
|
+
for (let i = 0; i < vm.items.length; i++) {
|
|
151
|
+
const item = vm.items[i];
|
|
152
|
+
const selected = i === vm.selected;
|
|
153
|
+
if (selected) {
|
|
154
|
+
lines.push(` \x1b[7m ${p.accent}${item.name.padEnd(12)}${p.reset}\x1b[7m ${item.description} ${p.reset}`);
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
lines.push(` ${p.muted}${item.name.padEnd(12)} ${item.description}${p.reset}`);
|
|
158
|
+
}
|
|
117
159
|
}
|
|
118
|
-
|
|
119
|
-
|
|
160
|
+
this.emit("\n" + lines.join("\n"));
|
|
161
|
+
this.autocompleteLines = lines.length;
|
|
162
|
+
if (this.autocompleteLines > 0) {
|
|
163
|
+
this.emit(`\x1b[${this.autocompleteLines}A`);
|
|
120
164
|
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
if (this.autocompleteLines > 0) {
|
|
125
|
-
this.surface.write(`\x1b[${this.autocompleteLines}A`);
|
|
126
|
-
}
|
|
127
|
-
// Absolute column set — preceding \n may have scrolled, invalidating DECSC.
|
|
128
|
-
this.surface.write(`\x1b[${this.cursorTermCol}G`);
|
|
165
|
+
// Absolute column set — preceding \n may have scrolled, invalidating DECSC.
|
|
166
|
+
this.emit(`\x1b[${this.cursorTermCol}G`);
|
|
167
|
+
});
|
|
129
168
|
}
|
|
130
169
|
clearAutocomplete() {
|
|
131
170
|
if (this.autocompleteLines <= 0)
|
|
132
171
|
return;
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
this.
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
172
|
+
this.autoFrame(() => {
|
|
173
|
+
// CSI B (cursor down, bounded) so we don't scroll on the last row.
|
|
174
|
+
for (let i = 0; i < this.autocompleteLines; i++) {
|
|
175
|
+
this.emit("\x1b[B\x1b[2K");
|
|
176
|
+
}
|
|
177
|
+
this.emit(`\x1b[${this.autocompleteLines}A\x1b[${this.cursorTermCol}G`);
|
|
178
|
+
this.autocompleteLines = 0;
|
|
179
|
+
});
|
|
139
180
|
}
|
|
140
181
|
}
|
|
@@ -147,7 +147,8 @@ export declare class FloatingPanel {
|
|
|
147
147
|
* - `{prefix}:render-border-bottom(ctx: FrameContext) -> string`
|
|
148
148
|
* - `{prefix}:composite-row(content: string, bgLine: string|null, boxLeft: number, boxW: number, cols: number) -> string`
|
|
149
149
|
* - `{prefix}:submit(query: string) -> void`
|
|
150
|
-
* - `{prefix}:
|
|
150
|
+
* - `{prefix}:hide() -> void` (screen down; conversation state preserved)
|
|
151
|
+
* - `{prefix}:reset() -> void` (conversation state cleared)
|
|
151
152
|
* - `{prefix}:show() -> void`
|
|
152
153
|
* - `{prefix}:input(data: string) -> boolean`
|
|
153
154
|
* - `{prefix}:build-row(content: string, width: number) -> string`
|
|
@@ -177,6 +178,9 @@ export declare class FloatingPanel {
|
|
|
177
178
|
private wrapCacheWidth;
|
|
178
179
|
private passthroughTimer;
|
|
179
180
|
private prevSerialized;
|
|
181
|
+
private autocompleteItems;
|
|
182
|
+
private autocompleteIndex;
|
|
183
|
+
private autocompleteActive;
|
|
180
184
|
constructor(bus: EventBus, config: FloatingPanelConfig, handlers?: HandlerRegistry);
|
|
181
185
|
private registerDefaultHandlers;
|
|
182
186
|
private wireEvents;
|
|
@@ -196,8 +200,10 @@ export declare class FloatingPanel {
|
|
|
196
200
|
hide(): void;
|
|
197
201
|
/** Show the panel again after hide(), preserving conversation. */
|
|
198
202
|
show(): void;
|
|
199
|
-
/**
|
|
200
|
-
|
|
203
|
+
/** End the conversation: screen down and all buffered state cleared. */
|
|
204
|
+
reset(): void;
|
|
205
|
+
/** Screen-only teardown; conversation state is left untouched. */
|
|
206
|
+
private teardownToHidden;
|
|
201
207
|
/** Common screen enter logic shared by open() and show(). */
|
|
202
208
|
private enterScreen;
|
|
203
209
|
appendText(text: string): void;
|
|
@@ -213,8 +219,14 @@ export declare class FloatingPanel {
|
|
|
213
219
|
scrollDown(lines?: number): void;
|
|
214
220
|
getInput(): string;
|
|
215
221
|
requestRender(): void;
|
|
222
|
+
private updateAutocomplete;
|
|
223
|
+
private applyAutocomplete;
|
|
224
|
+
private clearAutocomplete;
|
|
216
225
|
private handleIntercept;
|
|
217
|
-
/**
|
|
226
|
+
/**
|
|
227
|
+
* Handle scroll input. Returns true if consumed.
|
|
228
|
+
* Pass `includeArrows=false` in input phase so arrows reach the editor.
|
|
229
|
+
*/
|
|
218
230
|
private handleScroll;
|
|
219
231
|
private handleInputKey;
|
|
220
232
|
/** Compute box geometry from config + current viewport. */
|