agent-sh 0.1.0 → 0.3.0
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 +66 -576
- package/dist/acp-client.d.ts +24 -0
- package/dist/acp-client.js +168 -35
- package/dist/context-manager.d.ts +6 -4
- package/dist/context-manager.js +75 -44
- package/dist/event-bus.d.ts +29 -0
- package/dist/extension-loader.js +3 -14
- package/dist/extensions/shell-exec.d.ts +24 -0
- package/dist/extensions/shell-exec.js +188 -0
- package/dist/extensions/tui-renderer.d.ts +1 -1
- package/dist/extensions/tui-renderer.js +133 -28
- package/dist/index.js +195 -6
- package/dist/input-handler.d.ts +13 -3
- package/dist/input-handler.js +259 -127
- package/dist/mcp-server.d.ts +13 -0
- package/dist/mcp-server.js +234 -0
- package/dist/output-parser.d.ts +5 -26
- package/dist/output-parser.js +16 -78
- package/dist/settings.d.ts +33 -0
- package/dist/settings.js +43 -0
- package/dist/shell.d.ts +9 -4
- package/dist/shell.js +88 -10
- package/dist/types.d.ts +4 -0
- package/dist/utils/ansi.d.ts +4 -1
- package/dist/utils/ansi.js +60 -2
- package/dist/utils/line-editor.d.ts +59 -0
- package/dist/utils/line-editor.js +381 -0
- package/dist/utils/markdown.js +4 -4
- package/dist/utils/tool-display.d.ts +11 -0
- package/dist/utils/tool-display.js +92 -9
- package/examples/pi-agent-sh.ts +166 -0
- package/package.json +1 -1
package/dist/input-handler.js
CHANGED
|
@@ -1,29 +1,119 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
1
3
|
import { visibleLen } from "./utils/ansi.js";
|
|
2
4
|
import { palette as p } from "./utils/palette.js";
|
|
5
|
+
import { LineEditor } from "./utils/line-editor.js";
|
|
6
|
+
import { CONFIG_DIR, getSettings } from "./settings.js";
|
|
7
|
+
const HISTORY_FILE = path.join(CONFIG_DIR, "history");
|
|
3
8
|
export class InputHandler {
|
|
4
9
|
ctx;
|
|
5
10
|
lineBuffer = "";
|
|
6
11
|
agentInputMode = false;
|
|
7
|
-
|
|
12
|
+
editor = new LineEditor();
|
|
8
13
|
autocompleteActive = false;
|
|
9
14
|
autocompleteIndex = 0;
|
|
10
15
|
autocompleteItems = [];
|
|
11
16
|
autocompleteLines = 0;
|
|
17
|
+
history = [];
|
|
18
|
+
historyIndex = -1; // -1 = not browsing history
|
|
19
|
+
savedBuffer = ""; // buffer saved when entering history
|
|
20
|
+
promptWrappedLines = 0; // extra lines from terminal wrapping
|
|
21
|
+
escapeTimer = null;
|
|
12
22
|
bus;
|
|
13
23
|
onShowAgentInfo;
|
|
14
24
|
constructor(opts) {
|
|
15
25
|
this.ctx = opts.ctx;
|
|
16
26
|
this.bus = opts.bus;
|
|
17
27
|
this.onShowAgentInfo = opts.onShowAgentInfo;
|
|
28
|
+
this.loadHistory();
|
|
29
|
+
// Re-render prompt when config changes (e.g. thinking level cycled)
|
|
30
|
+
this.bus.on("config:changed", () => {
|
|
31
|
+
if (this.agentInputMode)
|
|
32
|
+
this.writeAgentPromptLine();
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
loadHistory() {
|
|
36
|
+
try {
|
|
37
|
+
const data = fs.readFileSync(HISTORY_FILE, "utf-8");
|
|
38
|
+
this.history = data.split("\n").filter(Boolean);
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
// No history file yet
|
|
42
|
+
}
|
|
18
43
|
}
|
|
19
|
-
|
|
44
|
+
saveHistory() {
|
|
45
|
+
try {
|
|
46
|
+
const { historySize } = getSettings();
|
|
47
|
+
fs.mkdirSync(path.dirname(HISTORY_FILE), { recursive: true });
|
|
48
|
+
const lines = this.history.slice(-historySize);
|
|
49
|
+
fs.writeFileSync(HISTORY_FILE, lines.join("\n") + "\n");
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
// Non-critical — ignore write failures
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/** Write the agent prompt line with cursor at the correct position. */
|
|
20
56
|
writeAgentPromptLine(showBuffer = true) {
|
|
57
|
+
const termW = process.stdout.columns || 80;
|
|
58
|
+
// Move cursor to the start of the prompt area (first line of wrapped content)
|
|
59
|
+
if (this.promptWrappedLines > 0) {
|
|
60
|
+
process.stdout.write(`\x1b[${this.promptWrappedLines}A`);
|
|
61
|
+
}
|
|
62
|
+
// Clear from here to end of screen — removes current + all wrapped lines below
|
|
63
|
+
process.stdout.write("\r\x1b[J");
|
|
21
64
|
const agentInfo = this.onShowAgentInfo();
|
|
22
65
|
const infoPrefix = agentInfo.info ? `${agentInfo.info} ` : "";
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
66
|
+
const promptPrefix = infoPrefix + p.warning + p.bold + "❯ " + p.reset;
|
|
67
|
+
const promptVisLen = visibleLen(infoPrefix) + 2; // "❯ "
|
|
68
|
+
if (!showBuffer || !this.editor.buffer.includes("\n")) {
|
|
69
|
+
// Single-line: simple rendering
|
|
70
|
+
const bufferText = showBuffer ? p.accent + this.editor.buffer + p.reset : "";
|
|
71
|
+
process.stdout.write(promptPrefix + bufferText);
|
|
72
|
+
const bufferVisLen = showBuffer ? this.editor.buffer.length : 0;
|
|
73
|
+
const totalVisLen = promptVisLen + bufferVisLen;
|
|
74
|
+
this.promptWrappedLines = totalVisLen > 0 ? Math.floor((totalVisLen - 1) / termW) : 0;
|
|
75
|
+
// Position cursor within the buffer
|
|
76
|
+
if (showBuffer && this.editor.cursor < this.editor.buffer.length) {
|
|
77
|
+
const charsAfterCursor = this.editor.buffer.length - this.editor.cursor;
|
|
78
|
+
process.stdout.write(`\x1b[${charsAfterCursor}D`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
// Multi-line: render each line with continuation indent
|
|
83
|
+
const lines = this.editor.buffer.split("\n");
|
|
84
|
+
const indent = " ".repeat(promptVisLen);
|
|
85
|
+
let totalTermLines = 0;
|
|
86
|
+
for (let li = 0; li < lines.length; li++) {
|
|
87
|
+
const prefix = li === 0 ? promptPrefix : indent;
|
|
88
|
+
const prefixVisLen = li === 0 ? promptVisLen : promptVisLen;
|
|
89
|
+
const lineText = lines[li];
|
|
90
|
+
process.stdout.write(prefix + p.accent + lineText + p.reset);
|
|
91
|
+
if (li < lines.length - 1)
|
|
92
|
+
process.stdout.write("\n");
|
|
93
|
+
// Count terminal lines this logical line occupies
|
|
94
|
+
const lineVisLen = prefixVisLen + lineText.length;
|
|
95
|
+
totalTermLines += lineVisLen > 0 ? Math.ceil(lineVisLen / termW) : 1;
|
|
96
|
+
}
|
|
97
|
+
this.promptWrappedLines = totalTermLines - 1;
|
|
98
|
+
// Position cursor: find which line and column the cursor is on
|
|
99
|
+
let charsRemaining = this.editor.cursor;
|
|
100
|
+
let cursorLine = 0;
|
|
101
|
+
for (let li = 0; li < lines.length; li++) {
|
|
102
|
+
if (charsRemaining <= lines[li].length) {
|
|
103
|
+
cursorLine = li;
|
|
104
|
+
break;
|
|
105
|
+
}
|
|
106
|
+
charsRemaining -= lines[li].length + 1; // +1 for \n
|
|
107
|
+
cursorLine = li + 1;
|
|
108
|
+
}
|
|
109
|
+
// Move from end position to cursor position
|
|
110
|
+
const linesFromEnd = lines.length - 1 - cursorLine;
|
|
111
|
+
if (linesFromEnd > 0) {
|
|
112
|
+
process.stdout.write(`\x1b[${linesFromEnd}A`);
|
|
113
|
+
}
|
|
114
|
+
const cursorCol = (cursorLine === 0 ? promptVisLen : promptVisLen) + charsRemaining;
|
|
115
|
+
process.stdout.write(`\r\x1b[${cursorCol}C`);
|
|
116
|
+
}
|
|
27
117
|
}
|
|
28
118
|
handleInput(data) {
|
|
29
119
|
// If agent is running (processing a query), only Ctrl-C and control keys
|
|
@@ -36,10 +126,15 @@ export class InputHandler {
|
|
|
36
126
|
}
|
|
37
127
|
return;
|
|
38
128
|
}
|
|
39
|
-
//
|
|
129
|
+
// Intercept control chars for TUI (Ctrl+T, Ctrl+O) — don't pass to PTY
|
|
40
130
|
if (data.length === 1 && data.charCodeAt(0) < 32 && !this.agentInputMode) {
|
|
41
131
|
const code = data.charCodeAt(0);
|
|
42
|
-
//
|
|
132
|
+
// Keys consumed by TUI extensions
|
|
133
|
+
if (code === 0x14 || code === 0x0f) { // Ctrl+T, Ctrl+O
|
|
134
|
+
this.bus.emit("input:keypress", { key: data });
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
// Forward other control chars that shell mode doesn't handle
|
|
43
138
|
if (code !== 0x0d && code !== 0x03 && code !== 0x04 && code !== 0x09) {
|
|
44
139
|
this.bus.emit("input:keypress", { key: data });
|
|
45
140
|
}
|
|
@@ -89,16 +184,29 @@ export class InputHandler {
|
|
|
89
184
|
}
|
|
90
185
|
enterAgentInputMode() {
|
|
91
186
|
this.agentInputMode = true;
|
|
92
|
-
this.
|
|
187
|
+
this.editor.clear();
|
|
188
|
+
// Enable kitty keyboard protocol (progressive enhancement flag 1)
|
|
189
|
+
// so Shift+Enter sends \x1b[13;2u instead of plain \r
|
|
190
|
+
process.stdout.write("\x1b[>1u");
|
|
93
191
|
this.writeAgentPromptLine(false);
|
|
94
192
|
}
|
|
95
193
|
exitAgentInputMode() {
|
|
96
194
|
this.dismissAutocomplete();
|
|
97
195
|
this.agentInputMode = false;
|
|
98
|
-
this.
|
|
99
|
-
|
|
196
|
+
this.editor.clear();
|
|
197
|
+
// Disable kitty keyboard protocol
|
|
198
|
+
process.stdout.write("\x1b[<u");
|
|
199
|
+
this.clearPromptArea();
|
|
100
200
|
this.printPrompt();
|
|
101
201
|
}
|
|
202
|
+
/** Move to the start of the prompt area and clear everything below. */
|
|
203
|
+
clearPromptArea() {
|
|
204
|
+
if (this.promptWrappedLines > 0) {
|
|
205
|
+
process.stdout.write(`\x1b[${this.promptWrappedLines}A`);
|
|
206
|
+
}
|
|
207
|
+
process.stdout.write("\r\x1b[J");
|
|
208
|
+
this.promptWrappedLines = 0;
|
|
209
|
+
}
|
|
102
210
|
printPrompt() {
|
|
103
211
|
this.ctx.redrawPrompt();
|
|
104
212
|
}
|
|
@@ -109,7 +217,7 @@ export class InputHandler {
|
|
|
109
217
|
}
|
|
110
218
|
updateAutocomplete() {
|
|
111
219
|
const { items } = this.bus.emitPipe("autocomplete:request", {
|
|
112
|
-
buffer: this.
|
|
220
|
+
buffer: this.editor.buffer,
|
|
113
221
|
items: [],
|
|
114
222
|
});
|
|
115
223
|
if (items.length > 0) {
|
|
@@ -146,36 +254,27 @@ export class InputHandler {
|
|
|
146
254
|
}
|
|
147
255
|
const agentInfo = this.onShowAgentInfo();
|
|
148
256
|
const infoLength = visibleLen(agentInfo.info);
|
|
149
|
-
const col = infoLength + 2 + this.
|
|
257
|
+
const col = infoLength + 2 + this.editor.cursor;
|
|
150
258
|
process.stdout.write(`\r\x1b[${col}C`);
|
|
151
259
|
}
|
|
152
|
-
clearAutocompleteLines() {
|
|
153
|
-
if (this.autocompleteLines <= 0)
|
|
154
|
-
return;
|
|
155
|
-
process.stdout.write("\x1b7"); // save cursor
|
|
156
|
-
for (let i = 0; i < this.autocompleteLines; i++) {
|
|
157
|
-
process.stdout.write("\n\x1b[2K"); // move down, clear line
|
|
158
|
-
}
|
|
159
|
-
process.stdout.write("\x1b8"); // restore cursor
|
|
160
|
-
this.autocompleteLines = 0;
|
|
161
|
-
}
|
|
162
260
|
applyAutocomplete() {
|
|
163
261
|
if (!this.autocompleteActive || this.autocompleteItems.length === 0)
|
|
164
262
|
return;
|
|
165
263
|
const selected = this.autocompleteItems[this.autocompleteIndex];
|
|
166
264
|
if (!selected)
|
|
167
265
|
return;
|
|
168
|
-
const atPos = this.
|
|
266
|
+
const atPos = this.editor.buffer.lastIndexOf("@");
|
|
169
267
|
const isFileAc = atPos >= 0 &&
|
|
170
|
-
(atPos === 0 || this.
|
|
171
|
-
!this.
|
|
268
|
+
(atPos === 0 || this.editor.buffer[atPos - 1] === " ") &&
|
|
269
|
+
!this.editor.buffer.slice(atPos + 1).includes(" ");
|
|
172
270
|
if (isFileAc) {
|
|
173
|
-
this.
|
|
174
|
-
this.
|
|
271
|
+
this.editor.buffer =
|
|
272
|
+
this.editor.buffer.slice(0, atPos) + "@" + selected.name;
|
|
175
273
|
}
|
|
176
274
|
else {
|
|
177
|
-
this.
|
|
275
|
+
this.editor.buffer = selected.name;
|
|
178
276
|
}
|
|
277
|
+
this.editor.cursor = this.editor.buffer.length;
|
|
179
278
|
this.clearAutocompleteLines();
|
|
180
279
|
this.autocompleteActive = false;
|
|
181
280
|
this.autocompleteItems = [];
|
|
@@ -190,112 +289,145 @@ export class InputHandler {
|
|
|
190
289
|
this.autocompleteItems = [];
|
|
191
290
|
this.autocompleteIndex = 0;
|
|
192
291
|
}
|
|
292
|
+
clearAutocompleteLines() {
|
|
293
|
+
if (this.autocompleteLines <= 0)
|
|
294
|
+
return;
|
|
295
|
+
process.stdout.write("\x1b7"); // save cursor
|
|
296
|
+
for (let i = 0; i < this.autocompleteLines; i++) {
|
|
297
|
+
process.stdout.write("\n\x1b[2K"); // move down, clear line
|
|
298
|
+
}
|
|
299
|
+
process.stdout.write("\x1b8"); // restore cursor
|
|
300
|
+
this.autocompleteLines = 0;
|
|
301
|
+
}
|
|
193
302
|
handleAgentInput(data) {
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
this.
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
303
|
+
// Clear any pending escape timer — new data arrived
|
|
304
|
+
if (this.escapeTimer) {
|
|
305
|
+
clearTimeout(this.escapeTimer);
|
|
306
|
+
this.escapeTimer = null;
|
|
307
|
+
}
|
|
308
|
+
const actions = this.editor.feed(data);
|
|
309
|
+
// If the editor is waiting for more escape sequence data, set a short
|
|
310
|
+
// timer — if nothing arrives, treat it as a bare Escape keypress
|
|
311
|
+
if (this.editor.hasPendingEscape()) {
|
|
312
|
+
this.escapeTimer = setTimeout(() => {
|
|
313
|
+
this.escapeTimer = null;
|
|
314
|
+
const flushed = this.editor.flushPendingEscape();
|
|
315
|
+
if (flushed.length > 0)
|
|
316
|
+
this.processAgentActions(flushed);
|
|
317
|
+
}, 50);
|
|
318
|
+
}
|
|
319
|
+
this.processAgentActions(actions);
|
|
320
|
+
}
|
|
321
|
+
processAgentActions(actions) {
|
|
322
|
+
for (const act of actions) {
|
|
323
|
+
switch (act.action) {
|
|
324
|
+
case "changed":
|
|
325
|
+
this.historyIndex = -1;
|
|
326
|
+
this.autocompleteIndex = 0;
|
|
327
|
+
this.renderAgentInput();
|
|
328
|
+
break;
|
|
329
|
+
case "submit": {
|
|
330
|
+
if (this.autocompleteActive) {
|
|
331
|
+
this.applyAutocomplete();
|
|
332
|
+
}
|
|
333
|
+
const query = act.buffer.trim();
|
|
334
|
+
if (query) {
|
|
335
|
+
// Add to history (avoid consecutive duplicates)
|
|
336
|
+
if (this.history.length === 0 || this.history[this.history.length - 1] !== query) {
|
|
337
|
+
this.history.push(query);
|
|
338
|
+
this.saveHistory();
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
this.historyIndex = -1;
|
|
342
|
+
this.savedBuffer = "";
|
|
216
343
|
this.clearAutocompleteLines();
|
|
217
|
-
this.
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
}
|
|
222
|
-
else if (!this.autocompleteActive) {
|
|
223
|
-
// Escape without arrow: cancel agent input mode
|
|
344
|
+
this.clearPromptArea();
|
|
345
|
+
process.stdout.write("\x1b[<u"); // disable kitty keyboard protocol
|
|
346
|
+
this.agentInputMode = false;
|
|
347
|
+
this.editor.clear();
|
|
224
348
|
this.dismissAutocomplete();
|
|
225
|
-
|
|
349
|
+
if (query && query.startsWith("/")) {
|
|
350
|
+
const spaceIdx = query.indexOf(" ");
|
|
351
|
+
const name = spaceIdx === -1 ? query : query.slice(0, spaceIdx);
|
|
352
|
+
const args = spaceIdx === -1 ? "" : query.slice(spaceIdx + 1).trim();
|
|
353
|
+
this.bus.emit("command:execute", { name, args });
|
|
354
|
+
this.ctx.redrawPrompt();
|
|
355
|
+
}
|
|
356
|
+
else if (query) {
|
|
357
|
+
this.bus.emit("agent:submit", { query });
|
|
358
|
+
}
|
|
359
|
+
else {
|
|
360
|
+
this.exitAgentInputMode();
|
|
361
|
+
}
|
|
226
362
|
return;
|
|
227
363
|
}
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
else {
|
|
239
|
-
this.dismissAutocomplete();
|
|
240
|
-
this.exitAgentInputMode();
|
|
241
|
-
}
|
|
242
|
-
return;
|
|
243
|
-
}
|
|
244
|
-
if (ch === "\t") {
|
|
245
|
-
if (this.autocompleteActive) {
|
|
246
|
-
this.applyAutocomplete();
|
|
247
|
-
}
|
|
248
|
-
continue;
|
|
249
|
-
}
|
|
250
|
-
if (ch === "\r") {
|
|
251
|
-
if (this.autocompleteActive) {
|
|
252
|
-
this.applyAutocomplete();
|
|
253
|
-
}
|
|
254
|
-
const query = this.agentInputBuffer.trim();
|
|
255
|
-
this.clearAutocompleteLines();
|
|
256
|
-
process.stdout.write("\r\x1b[2K");
|
|
257
|
-
this.agentInputMode = false;
|
|
258
|
-
this.agentInputBuffer = "";
|
|
259
|
-
this.dismissAutocomplete();
|
|
260
|
-
if (query && query.startsWith("/")) {
|
|
261
|
-
const spaceIdx = query.indexOf(" ");
|
|
262
|
-
const name = spaceIdx === -1 ? query : query.slice(0, spaceIdx);
|
|
263
|
-
const args = spaceIdx === -1 ? "" : query.slice(spaceIdx + 1).trim();
|
|
264
|
-
this.bus.emit("command:execute", { name, args });
|
|
265
|
-
this.ctx.redrawPrompt();
|
|
266
|
-
}
|
|
267
|
-
else if (query) {
|
|
268
|
-
this.bus.emit("agent:submit", { query });
|
|
269
|
-
}
|
|
270
|
-
else {
|
|
271
|
-
this.exitAgentInputMode();
|
|
272
|
-
}
|
|
273
|
-
return;
|
|
274
|
-
}
|
|
275
|
-
else if (ch === "\x03") {
|
|
276
|
-
// Ctrl-C: cancel
|
|
277
|
-
this.dismissAutocomplete();
|
|
278
|
-
this.exitAgentInputMode();
|
|
279
|
-
return;
|
|
280
|
-
}
|
|
281
|
-
else if (ch === "\x7f" || ch === "\b") {
|
|
282
|
-
// Backspace
|
|
283
|
-
if (this.agentInputBuffer.length > 0) {
|
|
284
|
-
this.agentInputBuffer = this.agentInputBuffer.slice(0, -1);
|
|
285
|
-
this.autocompleteIndex = 0;
|
|
286
|
-
this.renderAgentInput();
|
|
287
|
-
}
|
|
288
|
-
else {
|
|
364
|
+
case "cancel":
|
|
365
|
+
if (this.autocompleteActive) {
|
|
366
|
+
this.dismissAutocomplete();
|
|
367
|
+
this.writeAgentPromptLine();
|
|
368
|
+
}
|
|
369
|
+
else {
|
|
370
|
+
this.exitAgentInputMode();
|
|
371
|
+
}
|
|
372
|
+
return;
|
|
373
|
+
case "delete-empty":
|
|
289
374
|
this.dismissAutocomplete();
|
|
290
375
|
this.exitAgentInputMode();
|
|
291
376
|
return;
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
377
|
+
case "tab":
|
|
378
|
+
if (this.autocompleteActive) {
|
|
379
|
+
this.applyAutocomplete();
|
|
380
|
+
}
|
|
381
|
+
break;
|
|
382
|
+
case "shift+tab":
|
|
383
|
+
this.bus.emit("config:cycle", {});
|
|
384
|
+
break;
|
|
385
|
+
case "arrow-up":
|
|
386
|
+
if (this.autocompleteActive) {
|
|
387
|
+
this.autocompleteIndex =
|
|
388
|
+
this.autocompleteIndex === 0
|
|
389
|
+
? this.autocompleteItems.length - 1
|
|
390
|
+
: this.autocompleteIndex - 1;
|
|
391
|
+
this.clearAutocompleteLines();
|
|
392
|
+
this.writeAgentPromptLine();
|
|
393
|
+
this.renderAutocomplete();
|
|
394
|
+
}
|
|
395
|
+
else if (this.history.length > 0) {
|
|
396
|
+
if (this.historyIndex === -1) {
|
|
397
|
+
this.savedBuffer = this.editor.buffer;
|
|
398
|
+
this.historyIndex = this.history.length - 1;
|
|
399
|
+
}
|
|
400
|
+
else if (this.historyIndex > 0) {
|
|
401
|
+
this.historyIndex--;
|
|
402
|
+
}
|
|
403
|
+
this.editor.buffer = this.history[this.historyIndex];
|
|
404
|
+
this.editor.cursor = this.editor.buffer.length;
|
|
405
|
+
this.renderAgentInput();
|
|
406
|
+
}
|
|
407
|
+
break;
|
|
408
|
+
case "arrow-down":
|
|
409
|
+
if (this.autocompleteActive) {
|
|
410
|
+
this.autocompleteIndex =
|
|
411
|
+
this.autocompleteIndex === this.autocompleteItems.length - 1
|
|
412
|
+
? 0
|
|
413
|
+
: this.autocompleteIndex + 1;
|
|
414
|
+
this.clearAutocompleteLines();
|
|
415
|
+
this.writeAgentPromptLine();
|
|
416
|
+
this.renderAutocomplete();
|
|
417
|
+
}
|
|
418
|
+
else if (this.historyIndex !== -1) {
|
|
419
|
+
if (this.historyIndex < this.history.length - 1) {
|
|
420
|
+
this.historyIndex++;
|
|
421
|
+
this.editor.buffer = this.history[this.historyIndex];
|
|
422
|
+
}
|
|
423
|
+
else {
|
|
424
|
+
this.historyIndex = -1;
|
|
425
|
+
this.editor.buffer = this.savedBuffer;
|
|
426
|
+
}
|
|
427
|
+
this.editor.cursor = this.editor.buffer.length;
|
|
428
|
+
this.renderAgentInput();
|
|
429
|
+
}
|
|
430
|
+
break;
|
|
299
431
|
}
|
|
300
432
|
}
|
|
301
433
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Minimal MCP server exposing a `user_shell` tool.
|
|
4
|
+
*
|
|
5
|
+
* Spawned by the ACP agent (pi-acp, claude-agent-acp, etc.) as an MCP
|
|
6
|
+
* stdio server. When the LLM calls `user_shell`, this process connects
|
|
7
|
+
* to agent-sh's Unix socket to execute the command in the user's live
|
|
8
|
+
* PTY shell.
|
|
9
|
+
*
|
|
10
|
+
* Protocol: MCP over stdio (newline-delimited JSON-RPC 2.0).
|
|
11
|
+
* No SDK dependency — the protocol surface is tiny.
|
|
12
|
+
*/
|
|
13
|
+
export {};
|