agent-sh 0.9.0 → 0.10.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 +14 -21
- package/dist/agent/agent-loop.d.ts +43 -3
- package/dist/agent/agent-loop.js +811 -128
- package/dist/agent/conversation-state.d.ts +72 -21
- package/dist/agent/conversation-state.js +357 -150
- package/dist/agent/history-file.d.ts +13 -4
- package/dist/agent/history-file.js +110 -36
- package/dist/agent/nuclear-form.d.ts +28 -3
- package/dist/agent/nuclear-form.js +84 -3
- package/dist/agent/skills.d.ts +2 -4
- package/dist/agent/skills.js +10 -4
- package/dist/agent/subagent.d.ts +23 -0
- package/dist/agent/subagent.js +53 -11
- package/dist/agent/system-prompt.d.ts +34 -1
- package/dist/agent/system-prompt.js +96 -47
- package/dist/agent/token-budget.d.ts +5 -4
- package/dist/agent/token-budget.js +14 -19
- package/dist/agent/tool-protocol.d.ts +23 -1
- package/dist/agent/tool-protocol.js +169 -4
- package/dist/agent/tools/bash.js +3 -3
- package/dist/agent/tools/edit-file.js +9 -6
- package/dist/agent/tools/glob.js +4 -2
- package/dist/agent/tools/grep.js +27 -3
- package/dist/agent/tools/ls.js +5 -6
- package/dist/agent/types.d.ts +1 -1
- package/dist/context-manager.d.ts +17 -0
- package/dist/context-manager.js +37 -4
- package/dist/core.js +27 -6
- package/dist/event-bus.d.ts +59 -2
- package/dist/executor.d.ts +4 -3
- package/dist/executor.js +18 -15
- package/dist/extension-loader.js +50 -13
- package/dist/extensions/agent-backend.d.ts +8 -7
- package/dist/extensions/agent-backend.js +69 -48
- package/dist/extensions/index.js +0 -1
- package/dist/extensions/slash-commands.js +14 -9
- package/dist/extensions/tui-renderer.js +62 -78
- package/dist/index.js +25 -6
- package/dist/settings.d.ts +36 -5
- package/dist/settings.js +53 -9
- package/dist/shell/input-handler.d.ts +2 -1
- package/dist/shell/input-handler.js +82 -73
- package/dist/shell/shell.js +19 -2
- package/dist/types.d.ts +12 -0
- package/dist/utils/ansi.d.ts +5 -0
- package/dist/utils/ansi.js +1 -1
- package/dist/utils/compositor.d.ts +5 -0
- package/dist/utils/compositor.js +31 -3
- package/dist/utils/diff-renderer.d.ts +9 -0
- package/dist/utils/diff-renderer.js +221 -143
- package/dist/utils/diff.d.ts +21 -2
- package/dist/utils/diff.js +165 -89
- package/dist/utils/handler-registry.d.ts +5 -0
- package/dist/utils/handler-registry.js +6 -0
- package/dist/utils/line-editor.d.ts +11 -1
- package/dist/utils/line-editor.js +44 -5
- package/dist/utils/tool-display.d.ts +1 -1
- package/dist/utils/tool-display.js +4 -4
- package/examples/extensions/ash-acp-bridge/src/index.ts +4 -1
- package/examples/extensions/ash-mcp-bridge/index.ts +13 -3
- package/examples/extensions/claude-code-bridge/index.ts +198 -51
- package/examples/extensions/claude-code-bridge/package.json +1 -0
- package/examples/extensions/interactive-prompts.ts +39 -25
- package/examples/extensions/overlay-agent.ts +3 -3
- package/examples/extensions/peer-mesh.ts +115 -0
- package/examples/extensions/pi-bridge/index.ts +2 -2
- package/examples/extensions/questionnaire.ts +16 -5
- package/examples/extensions/subagents.ts +19 -4
- package/examples/extensions/terminal-buffer.ts +163 -0
- package/examples/extensions/user-shell.ts +136 -0
- package/examples/extensions/web-access.ts +8 -0
- package/package.json +36 -2
- package/dist/agent/tools/display.d.ts +0 -13
- package/dist/agent/tools/display.js +0 -70
- package/dist/agent/tools/user-shell.d.ts +0 -13
- package/dist/agent/tools/user-shell.js +0 -87
- package/dist/extensions/terminal-buffer.d.ts +0 -14
- package/dist/extensions/terminal-buffer.js +0 -134
package/dist/settings.js
CHANGED
|
@@ -17,23 +17,24 @@ const DEFAULTS = {
|
|
|
17
17
|
defaultBackend: "ash",
|
|
18
18
|
toolMode: "api",
|
|
19
19
|
contextWindowSize: 20,
|
|
20
|
-
contextBudget:
|
|
21
|
-
shellTruncateThreshold:
|
|
22
|
-
shellHeadLines:
|
|
23
|
-
shellTailLines:
|
|
24
|
-
recallExpandMaxLines:
|
|
20
|
+
contextBudget: 32768,
|
|
21
|
+
shellTruncateThreshold: 20,
|
|
22
|
+
shellHeadLines: 10,
|
|
23
|
+
shellTailLines: 10,
|
|
24
|
+
recallExpandMaxLines: 500,
|
|
25
25
|
shellContextRatio: 0.35,
|
|
26
|
-
historyMaxBytes:
|
|
27
|
-
historyStartupEntries:
|
|
28
|
-
nuclearMaxEntries: 200,
|
|
26
|
+
historyMaxBytes: 104857600, // 100MB — history is only accessed via search/expand, never loaded wholesale
|
|
27
|
+
historyStartupEntries: 100,
|
|
29
28
|
autoCompactThreshold: 0.5,
|
|
30
29
|
maxCommandOutputLines: 3,
|
|
31
30
|
readOutputMaxLines: 10,
|
|
32
|
-
diffMaxLines:
|
|
31
|
+
diffMaxLines: Infinity,
|
|
33
32
|
skillPaths: [],
|
|
33
|
+
diagnose: false,
|
|
34
34
|
startupBanner: true,
|
|
35
35
|
promptIndicator: true,
|
|
36
36
|
disabledBuiltins: [],
|
|
37
|
+
disabledExtensions: [],
|
|
37
38
|
};
|
|
38
39
|
let cached = null;
|
|
39
40
|
/** Load settings from disk (cached after first call). */
|
|
@@ -74,6 +75,49 @@ export function getExtensionSettings(namespace, defaults) {
|
|
|
74
75
|
export function reloadSettings() {
|
|
75
76
|
cached = null;
|
|
76
77
|
}
|
|
78
|
+
/**
|
|
79
|
+
* Deep-merge a patch into ~/.agent-sh/settings.json on disk.
|
|
80
|
+
*
|
|
81
|
+
* Reads the raw file (preserving unknown keys), merges the patch, writes back
|
|
82
|
+
* with 2-space indentation, and clears the cache so subsequent getSettings()
|
|
83
|
+
* calls see the new values.
|
|
84
|
+
*
|
|
85
|
+
* Used by runtime controls (`/model`, `/backend`) that want their selection
|
|
86
|
+
* to persist as the default across restarts.
|
|
87
|
+
*/
|
|
88
|
+
export function updateSettings(patch) {
|
|
89
|
+
let existing = {};
|
|
90
|
+
try {
|
|
91
|
+
const raw = fs.readFileSync(SETTINGS_PATH, "utf-8");
|
|
92
|
+
existing = JSON.parse(raw);
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
// file missing or unreadable — start fresh
|
|
96
|
+
}
|
|
97
|
+
const merged = deepMerge(existing, patch);
|
|
98
|
+
try {
|
|
99
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
100
|
+
fs.writeFileSync(SETTINGS_PATH, JSON.stringify(merged, null, 2) + "\n", "utf-8");
|
|
101
|
+
cached = null;
|
|
102
|
+
}
|
|
103
|
+
catch (err) {
|
|
104
|
+
console.error(`[agent-sh] Warning: failed to update ${SETTINGS_PATH}: ${err.message}`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
function deepMerge(target, source) {
|
|
108
|
+
const out = { ...target };
|
|
109
|
+
for (const [key, val] of Object.entries(source)) {
|
|
110
|
+
const existing = out[key];
|
|
111
|
+
if (val !== null && typeof val === "object" && !Array.isArray(val) &&
|
|
112
|
+
existing !== null && typeof existing === "object" && !Array.isArray(existing)) {
|
|
113
|
+
out[key] = deepMerge(existing, val);
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
out[key] = val;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return out;
|
|
120
|
+
}
|
|
77
121
|
/**
|
|
78
122
|
* Expand $ENV_VAR references in a string.
|
|
79
123
|
* Supports $VAR and ${VAR} syntax.
|
|
@@ -4,7 +4,7 @@ import { visibleLen } from "../utils/ansi.js";
|
|
|
4
4
|
import { palette as p } from "../utils/palette.js";
|
|
5
5
|
import { LineEditor } from "../utils/line-editor.js";
|
|
6
6
|
import { CONFIG_DIR, getSettings } from "../settings.js";
|
|
7
|
-
const HISTORY_FILE = path.join(CONFIG_DIR, "history");
|
|
7
|
+
const HISTORY_FILE = path.join(CONFIG_DIR, "input-history");
|
|
8
8
|
export class InputHandler {
|
|
9
9
|
ctx;
|
|
10
10
|
lineBuffer = "";
|
|
@@ -20,7 +20,8 @@ export class InputHandler {
|
|
|
20
20
|
history = [];
|
|
21
21
|
historyIndex = -1; // -1 = not browsing history
|
|
22
22
|
savedBuffer = ""; // buffer saved when entering history
|
|
23
|
-
|
|
23
|
+
cursorRowsBelow = 0; // rows from prompt top to cursor row
|
|
24
|
+
cursorTermCol = 1; // 1-indexed terminal column of cursor
|
|
24
25
|
escapeTimer = null;
|
|
25
26
|
bus;
|
|
26
27
|
onShowAgentInfo;
|
|
@@ -72,9 +73,10 @@ export class InputHandler {
|
|
|
72
73
|
/** Write the mode prompt line with cursor at the correct position. */
|
|
73
74
|
writeModePromptLine(showBuffer = true) {
|
|
74
75
|
const termW = process.stdout.columns || 80;
|
|
75
|
-
// Move cursor to the start of the prompt area
|
|
76
|
-
|
|
77
|
-
|
|
76
|
+
// Move cursor to the start of the prompt area.
|
|
77
|
+
// We know exactly how many rows below the top the cursor currently sits.
|
|
78
|
+
if (this.cursorRowsBelow > 0) {
|
|
79
|
+
process.stdout.write(`\x1b[${this.cursorRowsBelow}A`);
|
|
78
80
|
}
|
|
79
81
|
// Clear from here to end of screen — removes current + all wrapped lines below
|
|
80
82
|
process.stdout.write("\r\x1b[J");
|
|
@@ -88,37 +90,36 @@ export class InputHandler {
|
|
|
88
90
|
const promptVisLen = visibleLen(infoPrefix) + visibleLen(icon) + 1; // icon + space
|
|
89
91
|
const display = showBuffer ? this.editor.displayText : "";
|
|
90
92
|
const dCursor = showBuffer ? this.editor.displayCursor : 0;
|
|
91
|
-
if (!showBuffer
|
|
92
|
-
//
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
93
|
+
if (!showBuffer) {
|
|
94
|
+
// No buffer — just write the prompt prefix, cursor stays at end
|
|
95
|
+
process.stdout.write(promptPrefix);
|
|
96
|
+
const N = promptVisLen;
|
|
97
|
+
this.cursorRowsBelow = N > 0 ? Math.ceil(N / termW) - 1 : 0;
|
|
98
|
+
this.cursorTermCol = N === 0 ? 1 : (N % termW === 0 ? termW : (N % termW) + 1);
|
|
99
|
+
}
|
|
100
|
+
else if (!display.includes("\n")) {
|
|
101
|
+
// Single-line: write up to cursor, save, write rest, restore.
|
|
102
|
+
// The terminal handles all wrapping — no manual row/col math needed.
|
|
103
|
+
const before = display.slice(0, dCursor);
|
|
104
|
+
const after = display.slice(dCursor);
|
|
105
|
+
process.stdout.write(promptPrefix + p.accent + before + p.reset +
|
|
106
|
+
"\x1b7" + // DECSC — save cursor position
|
|
107
|
+
p.accent + after + p.reset +
|
|
108
|
+
"\x1b8" // DECRC — restore cursor position
|
|
109
|
+
);
|
|
110
|
+
// Clearing on next redraw needs total rows, so measure the full
|
|
111
|
+
// content width — not just up to the cursor.
|
|
112
|
+
const totalVisLen = promptVisLen + visibleLen(display);
|
|
113
|
+
this.cursorRowsBelow = totalVisLen > 0 ? Math.ceil(totalVisLen / termW) - 1 : 0;
|
|
114
|
+
const cursorVisCol = promptVisLen + visibleLen(before);
|
|
115
|
+
this.cursorTermCol = cursorVisCol === 0 ? 1 : (cursorVisCol % termW === 0 ? termW : (cursorVisCol % termW) + 1);
|
|
103
116
|
}
|
|
104
117
|
else {
|
|
105
|
-
// Multi-line: render each line with continuation indent
|
|
118
|
+
// Multi-line: render each line with continuation indent.
|
|
119
|
+
// Same save/restore strategy — cursor position is never computed.
|
|
106
120
|
const lines = display.split("\n");
|
|
107
121
|
const indent = " ".repeat(promptVisLen);
|
|
108
|
-
|
|
109
|
-
for (let li = 0; li < lines.length; li++) {
|
|
110
|
-
const prefix = li === 0 ? promptPrefix : indent;
|
|
111
|
-
const prefixVisLen = li === 0 ? promptVisLen : promptVisLen;
|
|
112
|
-
const lineText = lines[li];
|
|
113
|
-
process.stdout.write(prefix + p.accent + lineText + p.reset);
|
|
114
|
-
if (li < lines.length - 1)
|
|
115
|
-
process.stdout.write("\n");
|
|
116
|
-
// Count terminal lines this logical line occupies
|
|
117
|
-
const lineVisLen = prefixVisLen + lineText.length;
|
|
118
|
-
totalTermLines += lineVisLen > 0 ? Math.ceil(lineVisLen / termW) : 1;
|
|
119
|
-
}
|
|
120
|
-
this.promptWrappedLines = totalTermLines - 1;
|
|
121
|
-
// Position cursor: find which display line and column the cursor is on
|
|
122
|
+
// Locate cursor: which logical line and offset within it.
|
|
122
123
|
let charsRemaining = dCursor;
|
|
123
124
|
let cursorLine = 0;
|
|
124
125
|
for (let li = 0; li < lines.length; li++) {
|
|
@@ -129,31 +130,35 @@ export class InputHandler {
|
|
|
129
130
|
charsRemaining -= lines[li].length + 1; // +1 for \n
|
|
130
131
|
cursorLine = li + 1;
|
|
131
132
|
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
const
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
133
|
+
let output = "";
|
|
134
|
+
let cursorRowFromTop = 0;
|
|
135
|
+
let rowsSoFar = 0;
|
|
136
|
+
for (let li = 0; li < lines.length; li++) {
|
|
137
|
+
const prefix = li === 0 ? promptPrefix : indent;
|
|
138
|
+
const lineText = lines[li];
|
|
139
|
+
const lineVisLen = promptVisLen + visibleLen(lineText);
|
|
140
|
+
const lineTermRows = lineVisLen > 0 ? Math.ceil(lineVisLen / termW) : 1;
|
|
141
|
+
if (li === cursorLine) {
|
|
142
|
+
// Split this line at the cursor.
|
|
143
|
+
const before = lineText.slice(0, charsRemaining);
|
|
144
|
+
const after = lineText.slice(charsRemaining);
|
|
145
|
+
output += prefix + p.accent + before + p.reset;
|
|
146
|
+
output += "\x1b7"; // DECSC — save cursor position
|
|
147
|
+
output += p.accent + after + p.reset;
|
|
148
|
+
const beforeVisCol = promptVisLen + visibleLen(before);
|
|
149
|
+
cursorRowFromTop = rowsSoFar + (beforeVisCol > 0 ? Math.ceil(beforeVisCol / termW) - 1 : 0);
|
|
150
|
+
this.cursorTermCol = beforeVisCol === 0 ? 1 : (beforeVisCol % termW === 0 ? termW : (beforeVisCol % termW) + 1);
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
output += prefix + p.accent + lineText + p.reset;
|
|
154
|
+
}
|
|
155
|
+
if (li < lines.length - 1)
|
|
156
|
+
output += "\n";
|
|
157
|
+
rowsSoFar += lineTermRows;
|
|
156
158
|
}
|
|
159
|
+
process.stdout.write(output + "\x1b8"); // DECRC — restore cursor position
|
|
160
|
+
// Total rows (not cursor row) so next redraw clears the whole area.
|
|
161
|
+
this.cursorRowsBelow = rowsSoFar - 1 > 0 ? rowsSoFar - 1 : 0;
|
|
157
162
|
}
|
|
158
163
|
}
|
|
159
164
|
handleInput(data) {
|
|
@@ -281,15 +286,17 @@ export class InputHandler {
|
|
|
281
286
|
// Disable kitty keyboard protocol and bracket paste mode
|
|
282
287
|
process.stdout.write("\x1b[<u\x1b[?2004l");
|
|
283
288
|
this.clearPromptArea();
|
|
289
|
+
this.cursorRowsBelow = 0;
|
|
290
|
+
this.cursorTermCol = 1;
|
|
284
291
|
this.printPrompt();
|
|
285
292
|
}
|
|
286
293
|
/** Move to the start of the prompt area and clear everything below. */
|
|
287
294
|
clearPromptArea() {
|
|
288
|
-
if (this.
|
|
289
|
-
process.stdout.write(`\x1b[${this.
|
|
295
|
+
if (this.cursorRowsBelow > 0) {
|
|
296
|
+
process.stdout.write(`\x1b[${this.cursorRowsBelow}A`);
|
|
290
297
|
}
|
|
291
298
|
process.stdout.write("\r\x1b[J");
|
|
292
|
-
this.
|
|
299
|
+
this.cursorRowsBelow = 0;
|
|
293
300
|
}
|
|
294
301
|
printPrompt() {
|
|
295
302
|
this.ctx.redrawPrompt();
|
|
@@ -363,16 +370,10 @@ export class InputHandler {
|
|
|
363
370
|
if (this.autocompleteLines > 0) {
|
|
364
371
|
process.stdout.write(`\x1b[${this.autocompleteLines}A`);
|
|
365
372
|
}
|
|
366
|
-
//
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
? `${agentInfo.info} ${indicator} `
|
|
371
|
-
: `${indicator} `;
|
|
372
|
-
const icon = this.activeMode?.promptIcon ?? "❯";
|
|
373
|
-
const promptVisLen = visibleLen(infoPrefix) + visibleLen(icon) + 1;
|
|
374
|
-
const col = promptVisLen + this.editor.displayCursor;
|
|
375
|
-
process.stdout.write(`\r\x1b[${col}C`);
|
|
373
|
+
// Restore cursor column — use explicit column set instead of DECRC
|
|
374
|
+
// because writing \n above may have scrolled the terminal, which
|
|
375
|
+
// invalidates the absolute position saved by DECSC.
|
|
376
|
+
process.stdout.write(`\x1b[${this.cursorTermCol}G`);
|
|
376
377
|
}
|
|
377
378
|
applyAutocomplete() {
|
|
378
379
|
if (!this.autocompleteActive || this.autocompleteItems.length === 0)
|
|
@@ -407,11 +408,12 @@ export class InputHandler {
|
|
|
407
408
|
clearAutocompleteLines() {
|
|
408
409
|
if (this.autocompleteLines <= 0)
|
|
409
410
|
return;
|
|
410
|
-
|
|
411
|
+
// Use CSI B (cursor down, bounded) instead of \n to avoid scroll
|
|
411
412
|
for (let i = 0; i < this.autocompleteLines; i++) {
|
|
412
|
-
process.stdout.write("\
|
|
413
|
+
process.stdout.write("\x1b[B\x1b[2K"); // move down, clear line
|
|
413
414
|
}
|
|
414
|
-
|
|
415
|
+
// Move back up and restore column with relative movement (scroll-safe)
|
|
416
|
+
process.stdout.write(`\x1b[${this.autocompleteLines}A\x1b[${this.cursorTermCol}G`);
|
|
415
417
|
this.autocompleteLines = 0;
|
|
416
418
|
}
|
|
417
419
|
handleModeInput(data) {
|
|
@@ -475,13 +477,20 @@ export class InputHandler {
|
|
|
475
477
|
const currentMode = this.activeMode;
|
|
476
478
|
this.activeMode = null;
|
|
477
479
|
this.editor.clear();
|
|
480
|
+
this.cursorRowsBelow = 0;
|
|
481
|
+
this.cursorTermCol = 1;
|
|
478
482
|
this.dismissAutocomplete();
|
|
479
483
|
if (query && query.startsWith("/")) {
|
|
480
484
|
const spaceIdx = query.indexOf(" ");
|
|
481
485
|
const name = spaceIdx === -1 ? query : query.slice(0, spaceIdx);
|
|
482
486
|
const args = spaceIdx === -1 ? "" : query.slice(spaceIdx + 1).trim();
|
|
483
487
|
this.bus.emit("command:execute", { name, args });
|
|
484
|
-
|
|
488
|
+
if (currentMode.returnToSelf) {
|
|
489
|
+
this.enterMode(currentMode);
|
|
490
|
+
}
|
|
491
|
+
else {
|
|
492
|
+
this.ctx.freshPrompt();
|
|
493
|
+
}
|
|
485
494
|
}
|
|
486
495
|
else if (query) {
|
|
487
496
|
this.pendingReturnMode = currentMode.returnToSelf ? currentMode.id : null;
|
package/dist/shell/shell.js
CHANGED
|
@@ -197,6 +197,11 @@ export class Shell {
|
|
|
197
197
|
* For bash, falls back to sending \n for a fresh prompt cycle.
|
|
198
198
|
*/
|
|
199
199
|
redrawPrompt() {
|
|
200
|
+
// A stale echoSkip or paused flag (left over from handleProcessingDone
|
|
201
|
+
// re-entering a mode) would swallow the redrawn prompt and make the
|
|
202
|
+
// terminal appear frozen. Reset both before emitting.
|
|
203
|
+
this.echoSkip = false;
|
|
204
|
+
this.paused = false;
|
|
200
205
|
const result = this.bus.emitPipe("shell:redraw-prompt", {
|
|
201
206
|
cwd: this.outputParser.getCwd(),
|
|
202
207
|
handled: false,
|
|
@@ -277,9 +282,13 @@ export class Shell {
|
|
|
277
282
|
this.paused = true;
|
|
278
283
|
});
|
|
279
284
|
this.handlers.define("shell:on-processing-done", () => {
|
|
280
|
-
this.paused = false;
|
|
281
285
|
this.agentActive = false;
|
|
286
|
+
// If handleProcessingDone re-entered a mode, leave stdout paused so
|
|
287
|
+
// stale PTY output doesn't overwrite the mode prompt (exitMode →
|
|
288
|
+
// redrawPrompt will unpause). Setting echoSkip here would swallow
|
|
289
|
+
// that PTY output since no \n was sent.
|
|
282
290
|
if (!this.inputHandler.handleProcessingDone()) {
|
|
291
|
+
this.paused = false;
|
|
283
292
|
if (this.freshPrompt()) {
|
|
284
293
|
this.echoSkip = true;
|
|
285
294
|
}
|
|
@@ -317,11 +326,19 @@ export class Shell {
|
|
|
317
326
|
const handler = (e) => {
|
|
318
327
|
clearTimeout(timeout);
|
|
319
328
|
this.bus.off("shell:command-done", handler);
|
|
329
|
+
// Re-pause stdout so the prompt text following the marker doesn't
|
|
330
|
+
// leak to the terminal while the agent is still processing.
|
|
331
|
+
this.paused = true;
|
|
320
332
|
resolve({ output: e.output, cwd: e.cwd, exitCode: e.exitCode });
|
|
321
333
|
};
|
|
322
334
|
this.bus.on("shell:command-done", handler);
|
|
323
335
|
this.outputParser.onCommandEntered(payload.command, this.outputParser.getCwd());
|
|
324
|
-
|
|
336
|
+
// Collapse literal newlines to spaces so the PTY receives a single-line
|
|
337
|
+
// command. Multi-line commands (e.g. git commit -m "...\n...") would
|
|
338
|
+
// cause the shell to execute prematurely, producing garbled output from
|
|
339
|
+
// syntax highlighting plugins (zsh syntax highlighting, etc).
|
|
340
|
+
const oneLine = payload.command.replace(/\n/g, " ");
|
|
341
|
+
this.ptyProcess.write(oneLine + "\r");
|
|
325
342
|
});
|
|
326
343
|
this.paused = true;
|
|
327
344
|
this.echoSkip = false;
|
package/dist/types.d.ts
CHANGED
|
@@ -80,6 +80,12 @@ export interface ExtensionContext {
|
|
|
80
80
|
createFencedBlockTransform: (opts: FencedBlockTransformOptions) => void;
|
|
81
81
|
/** Read extension-namespaced settings from ~/.agent-sh/settings.json. */
|
|
82
82
|
getExtensionSettings: <T extends Record<string, unknown>>(namespace: string, defaults: T) => T;
|
|
83
|
+
/**
|
|
84
|
+
* Get (and lazily create) a per-extension storage directory under
|
|
85
|
+
* ~/.agent-sh/<namespace>/. Returns the absolute path. Lets extensions
|
|
86
|
+
* persist state without each one re-deriving the location.
|
|
87
|
+
*/
|
|
88
|
+
getStoragePath: (namespace: string) => string;
|
|
83
89
|
/** Register a slash command available in any input mode. */
|
|
84
90
|
registerCommand: (name: string, description: string, handler: (args: string) => Promise<void> | void) => void;
|
|
85
91
|
/** Register a tool for the built-in agent. No-op when using bridge backends. */
|
|
@@ -92,12 +98,18 @@ export interface ExtensionContext {
|
|
|
92
98
|
registerInstruction: (name: string, text: string) => void;
|
|
93
99
|
/** Remove a named instruction block from the system prompt. */
|
|
94
100
|
removeInstruction: (name: string) => void;
|
|
101
|
+
/** Register a skill (on-demand reference material) for the agent. */
|
|
102
|
+
registerSkill: (name: string, description: string, filePath: string) => void;
|
|
103
|
+
/** Remove a registered skill by name. */
|
|
104
|
+
removeSkill: (name: string) => void;
|
|
95
105
|
/** Register a named handler. */
|
|
96
106
|
define: (name: string, fn: (...args: any[]) => any) => void;
|
|
97
107
|
/** Wrap a named handler. Receives `next` (original) + args. Returns an unadvise function. */
|
|
98
108
|
advise: (name: string, wrapper: (next: (...args: any[]) => any, ...args: any[]) => any) => () => void;
|
|
99
109
|
/** Call a named handler. */
|
|
100
110
|
call: (name: string, ...args: any[]) => any;
|
|
111
|
+
/** Names of all registered handlers — for diagnostic / introspection use. */
|
|
112
|
+
list: () => string[];
|
|
101
113
|
/**
|
|
102
114
|
* Shared headless terminal buffer mirroring PTY output.
|
|
103
115
|
* Lazily created on first access. Returns null if @xterm/headless is not installed.
|
package/dist/utils/ansi.d.ts
CHANGED
|
@@ -6,6 +6,11 @@ export declare const RED = "\u001B[31m";
|
|
|
6
6
|
export declare const GRAY = "\u001B[90m";
|
|
7
7
|
export declare const BOLD = "\u001B[1m";
|
|
8
8
|
export declare const RESET = "\u001B[0m";
|
|
9
|
+
/**
|
|
10
|
+
* Check if a Unicode code point is a wide character (CJK, fullwidth, emoji, etc.)
|
|
11
|
+
* Returns 2 for wide chars, 1 for normal chars.
|
|
12
|
+
*/
|
|
13
|
+
export declare function charWidth(codePoint: number): number;
|
|
9
14
|
/**
|
|
10
15
|
* Measure visible string length in terminal columns.
|
|
11
16
|
* Excludes SGR (color/style) sequences and accounts for CJK double-width chars.
|
package/dist/utils/ansi.js
CHANGED
|
@@ -12,7 +12,7 @@ export const RESET = "\x1b[0m";
|
|
|
12
12
|
* Check if a Unicode code point is a wide character (CJK, fullwidth, emoji, etc.)
|
|
13
13
|
* Returns 2 for wide chars, 1 for normal chars.
|
|
14
14
|
*/
|
|
15
|
-
function charWidth(codePoint) {
|
|
15
|
+
export function charWidth(codePoint) {
|
|
16
16
|
// CJK Unified Ideographs
|
|
17
17
|
if (codePoint >= 0x4e00 && codePoint <= 0x9fff)
|
|
18
18
|
return 2;
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
* compositor.redirect("agent:diff", diffPanelSurface);
|
|
25
25
|
* // "agent:text", "agent:tool" etc. still go to stdout
|
|
26
26
|
*/
|
|
27
|
+
import type { EventBus } from "../event-bus.js";
|
|
27
28
|
/**
|
|
28
29
|
* A surface accepts rendered output. Stdout is a surface.
|
|
29
30
|
* A floating panel's content area is a surface. A test buffer is a surface.
|
|
@@ -56,7 +57,11 @@ export declare class StdoutSurface implements RenderSurface {
|
|
|
56
57
|
export declare class DefaultCompositor implements Compositor {
|
|
57
58
|
private defaults;
|
|
58
59
|
private overrides;
|
|
60
|
+
private readonly bus?;
|
|
61
|
+
constructor(bus?: EventBus);
|
|
59
62
|
surface(stream: string): RenderSurface;
|
|
60
63
|
redirect(stream: string, target: RenderSurface): () => void;
|
|
61
64
|
setDefault(stream: string, target: RenderSurface): void;
|
|
65
|
+
/** Wrap a surface so writes emit `compositor:write` before delegating. */
|
|
66
|
+
private wrap;
|
|
62
67
|
}
|
package/dist/utils/compositor.js
CHANGED
|
@@ -50,6 +50,10 @@ export class StdoutSurface {
|
|
|
50
50
|
export class DefaultCompositor {
|
|
51
51
|
defaults = new Map();
|
|
52
52
|
overrides = new Map();
|
|
53
|
+
bus;
|
|
54
|
+
constructor(bus) {
|
|
55
|
+
this.bus = bus;
|
|
56
|
+
}
|
|
53
57
|
surface(stream) {
|
|
54
58
|
const stack = this.overrides.get(stream);
|
|
55
59
|
if (stack && stack.length > 0)
|
|
@@ -63,12 +67,13 @@ export class DefaultCompositor {
|
|
|
63
67
|
return nullSurface;
|
|
64
68
|
}
|
|
65
69
|
redirect(stream, target) {
|
|
70
|
+
const wrapped = this.wrap(stream, target);
|
|
66
71
|
let stack = this.overrides.get(stream);
|
|
67
72
|
if (!stack) {
|
|
68
73
|
stack = [];
|
|
69
74
|
this.overrides.set(stream, stack);
|
|
70
75
|
}
|
|
71
|
-
stack.push(
|
|
76
|
+
stack.push(wrapped);
|
|
72
77
|
let restored = false;
|
|
73
78
|
return () => {
|
|
74
79
|
if (restored)
|
|
@@ -77,12 +82,35 @@ export class DefaultCompositor {
|
|
|
77
82
|
const s = this.overrides.get(stream);
|
|
78
83
|
if (!s)
|
|
79
84
|
return;
|
|
80
|
-
const idx = s.indexOf(
|
|
85
|
+
const idx = s.indexOf(wrapped);
|
|
81
86
|
if (idx !== -1)
|
|
82
87
|
s.splice(idx, 1);
|
|
83
88
|
};
|
|
84
89
|
}
|
|
85
90
|
setDefault(stream, target) {
|
|
86
|
-
this.defaults.set(stream, target);
|
|
91
|
+
this.defaults.set(stream, this.wrap(stream, target));
|
|
92
|
+
}
|
|
93
|
+
/** Wrap a surface so writes emit `compositor:write` before delegating. */
|
|
94
|
+
wrap(stream, target) {
|
|
95
|
+
const bus = this.bus;
|
|
96
|
+
if (!bus)
|
|
97
|
+
return target;
|
|
98
|
+
return {
|
|
99
|
+
write: (text) => {
|
|
100
|
+
try {
|
|
101
|
+
bus.emit("compositor:write", { stream, text });
|
|
102
|
+
}
|
|
103
|
+
catch { }
|
|
104
|
+
target.write(text);
|
|
105
|
+
},
|
|
106
|
+
writeLine: (line) => {
|
|
107
|
+
try {
|
|
108
|
+
bus.emit("compositor:write", { stream, text: line + "\n" });
|
|
109
|
+
}
|
|
110
|
+
catch { }
|
|
111
|
+
target.writeLine(line);
|
|
112
|
+
},
|
|
113
|
+
get columns() { return target.columns; },
|
|
114
|
+
};
|
|
87
115
|
}
|
|
88
116
|
}
|
|
@@ -18,3 +18,12 @@ export interface DiffRenderOptions {
|
|
|
18
18
|
export declare function selectMode(width: number): DiffDisplayMode;
|
|
19
19
|
/** Render a diff result as an array of ANSI-formatted terminal lines. */
|
|
20
20
|
export declare function renderDiff(diff: DiffResult, opts: DiffRenderOptions): string[];
|
|
21
|
+
/**
|
|
22
|
+
* Async variant of renderDiff that yields to the event loop between hunks.
|
|
23
|
+
* Use when rendering in a context where a spinner or other UI needs to stay
|
|
24
|
+
* responsive (e.g. showing a large diff during a permission prompt).
|
|
25
|
+
*
|
|
26
|
+
* @param onLines - Callback invoked with each batch of rendered lines as they
|
|
27
|
+
* are produced. Allows progressive/streaming display.
|
|
28
|
+
*/
|
|
29
|
+
export declare function renderDiffAsync(diff: DiffResult, opts: DiffRenderOptions, onLines: (lines: string[]) => void): Promise<void>;
|