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.
@@ -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.surface.write("\x1b[>1u\x1b[?2004h");
56
+ this.emit("\x1b[>1u\x1b[?2004h");
24
57
  }
25
58
  disableModeKeys() {
26
- this.surface.write("\x1b[<u\x1b[?2004l");
59
+ this.emit("\x1b[<u\x1b[?2004l");
27
60
  }
28
61
  clearPromptArea() {
29
- if (this.cursorRowsBelow > 0) {
30
- this.surface.write(`\x1b[${this.cursorRowsBelow}A`);
31
- }
32
- this.surface.write("\r\x1b[J");
33
- this.cursorRowsBelow = 0;
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
- const termW = this.surface.columns;
37
- if (this.cursorRowsBelow > 0) {
38
- this.surface.write(`\x1b[${this.cursorRowsBelow}A`);
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
- let output = "";
80
- let cursorRowFromTop = 0;
81
- let rowsSoFar = 0;
82
- for (let li = 0; li < lines.length; li++) {
83
- const prefix = li === 0 ? promptPrefix : indent;
84
- const lineText = lines[li];
85
- const lineVisLen = promptVisLen + visibleLen(lineText);
86
- const lineTermRows = lineVisLen > 0 ? Math.ceil(lineVisLen / termW) : 1;
87
- if (li === cursorLine) {
88
- const before = lineText.slice(0, charsRemaining);
89
- const after = lineText.slice(charsRemaining);
90
- output += prefix + p.accent + before + p.reset;
91
- output += "\x1b7";
92
- output += p.accent + after + p.reset;
93
- const beforeVisCol = promptVisLen + visibleLen(before);
94
- cursorRowFromTop = rowsSoFar + (beforeVisCol > 0 ? Math.ceil(beforeVisCol / termW) - 1 : 0);
95
- this.cursorTermCol = beforeVisCol === 0 ? 1 : (beforeVisCol % termW === 0 ? termW : (beforeVisCol % termW) + 1);
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
- else {
98
- output += prefix + p.accent + lineText + p.reset;
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
- if (li < lines.length - 1)
101
- output += "\n";
102
- rowsSoFar += lineTermRows;
140
+ this.emit(output + "\x1b8");
141
+ this.cursorRowsBelow = cursorRowFromTop;
103
142
  }
104
- this.surface.write(output + "\x1b8");
105
- this.cursorRowsBelow = cursorRowFromTop;
106
- }
143
+ });
107
144
  }
108
145
  drawAutocomplete(vm) {
109
146
  if (vm.items.length === 0)
110
147
  return;
111
- const lines = [];
112
- for (let i = 0; i < vm.items.length; i++) {
113
- const item = vm.items[i];
114
- const selected = i === vm.selected;
115
- if (selected) {
116
- lines.push(` \x1b[7m ${p.accent}${item.name.padEnd(12)}${p.reset}\x1b[7m ${item.description} ${p.reset}`);
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
- else {
119
- lines.push(` ${p.muted}${item.name.padEnd(12)} ${item.description}${p.reset}`);
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
- this.surface.write("\n" + lines.join("\n"));
123
- this.autocompleteLines = lines.length;
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
- // CSI B (cursor down, bounded) so we don't scroll on the last row.
134
- for (let i = 0; i < this.autocompleteLines; i++) {
135
- this.surface.write("\x1b[B\x1b[2K");
136
- }
137
- this.surface.write(`\x1b[${this.autocompleteLines}A\x1b[${this.cursorTermCol}G`);
138
- this.autocompleteLines = 0;
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}:dismiss() -> void`
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
- /** Fully destroy the panel, resetting all state. */
200
- dismiss(): void;
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
- /** Handle scroll input. Returns true if consumed. */
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. */