agent-sh 0.12.26 → 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/install.js +84 -4
- package/dist/utils/floating-panel.d.ts +16 -4
- package/dist/utils/floating-panel.js +209 -66
- package/examples/extensions/emacs-buffer.ts +364 -0
- 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
package/dist/agent/agent-loop.js
CHANGED
|
@@ -1105,7 +1105,7 @@ export class AgentLoop {
|
|
|
1105
1105
|
// Compact deeply — shallow targets buy only 1–2 turns of runway on
|
|
1106
1106
|
// tool-heavy workloads.
|
|
1107
1107
|
const target = Math.floor(threshold * 0.25);
|
|
1108
|
-
const result = this.compactWithHooks(target,
|
|
1108
|
+
const result = this.compactWithHooks(target, 1);
|
|
1109
1109
|
if (!result) {
|
|
1110
1110
|
// Auto-compact fired but nothing was evictable. This can happen
|
|
1111
1111
|
// in short conversations with heavy tool output where the pin
|
|
@@ -1488,7 +1488,7 @@ export class AgentLoop {
|
|
|
1488
1488
|
if (this.isContextOverflow(e)) {
|
|
1489
1489
|
const contextWindow = this.currentMode.contextWindow ?? DEFAULT_CONTEXT_WINDOW;
|
|
1490
1490
|
const target = Math.floor((contextWindow - RESPONSE_RESERVE) * 0.6);
|
|
1491
|
-
const stats = this.compactWithHooks(target,
|
|
1491
|
+
const stats = this.compactWithHooks(target, 1);
|
|
1492
1492
|
// If compaction freed nothing, retrying will hit the same error.
|
|
1493
1493
|
// Surface the real failure instead of looping until exhaustion.
|
|
1494
1494
|
if (!stats || stats.after >= stats.before) {
|
|
@@ -29,6 +29,19 @@ function firstMatchExcerpt(text, regex) {
|
|
|
29
29
|
function recencyWeight(idx, total) {
|
|
30
30
|
return Math.max(0.1, 1 - idx / total);
|
|
31
31
|
}
|
|
32
|
+
// Head+tail because the start (command, opening lines) and end (final
|
|
33
|
+
// result, exit code) are the informative parts of shell/file output.
|
|
34
|
+
function slimToolContent(content, maxLen) {
|
|
35
|
+
const exitMatch = content.match(/exit code:?\s*(\d+)/i);
|
|
36
|
+
const exitSuffix = exitMatch ? ` (exit ${exitMatch[1]})` : "";
|
|
37
|
+
const lines = content.split("\n");
|
|
38
|
+
if (lines.length > 6) {
|
|
39
|
+
const head = lines.slice(0, 3).join("\n");
|
|
40
|
+
const tail = lines.slice(-2).join("\n");
|
|
41
|
+
return `${head}\n... [${lines.length - 5} lines trimmed by compact]\n${tail}${exitSuffix}`;
|
|
42
|
+
}
|
|
43
|
+
return `${content.slice(0, maxLen)}\n... [${content.length - maxLen} chars trimmed by compact]${exitSuffix}`;
|
|
44
|
+
}
|
|
32
45
|
/**
|
|
33
46
|
* Conversation state with eager nucleation — shell-history shaped.
|
|
34
47
|
*
|
|
@@ -611,6 +624,7 @@ export class ConversationState {
|
|
|
611
624
|
// ── Internal: Two-tier pin for recent turns ────────────────────
|
|
612
625
|
slimTurn(messages) {
|
|
613
626
|
const MAX_RESULT_LEN = 1500;
|
|
627
|
+
const MAX_ASSISTANT_LEN = 1500;
|
|
614
628
|
const result = [];
|
|
615
629
|
const droppedToolIds = new Set();
|
|
616
630
|
for (const msg of messages) {
|
|
@@ -642,13 +656,20 @@ export class ConversationState {
|
|
|
642
656
|
continue;
|
|
643
657
|
const content = typeof msg.content === "string" ? msg.content : "";
|
|
644
658
|
if (content.length > MAX_RESULT_LEN) {
|
|
645
|
-
result.push({ ...msg, content: content
|
|
659
|
+
result.push({ ...msg, content: slimToolContent(content, MAX_RESULT_LEN) });
|
|
646
660
|
}
|
|
647
661
|
else {
|
|
648
662
|
result.push(msg);
|
|
649
663
|
}
|
|
650
664
|
continue;
|
|
651
665
|
}
|
|
666
|
+
if (msg.role === "assistant" && typeof msg.content === "string" && msg.content.length > MAX_ASSISTANT_LEN) {
|
|
667
|
+
const head = msg.content.slice(0, Math.floor(MAX_ASSISTANT_LEN * 0.6));
|
|
668
|
+
const tail = msg.content.slice(-Math.floor(MAX_ASSISTANT_LEN * 0.2));
|
|
669
|
+
const trimmed = msg.content.length - head.length - tail.length;
|
|
670
|
+
result.push({ ...msg, content: `${head}\n... [${trimmed} chars trimmed by compact]\n${tail}` });
|
|
671
|
+
continue;
|
|
672
|
+
}
|
|
652
673
|
result.push(msg);
|
|
653
674
|
}
|
|
654
675
|
return result;
|
package/dist/install.js
CHANGED
|
@@ -69,11 +69,13 @@ function pickResolver(spec) {
|
|
|
69
69
|
return r;
|
|
70
70
|
return bundledResolver;
|
|
71
71
|
}
|
|
72
|
-
function
|
|
72
|
+
function readPackageJson(target) {
|
|
73
73
|
const pkgJson = path.join(target, "package.json");
|
|
74
74
|
if (!fs.existsSync(pkgJson))
|
|
75
|
-
return;
|
|
76
|
-
|
|
75
|
+
return null;
|
|
76
|
+
return JSON.parse(fs.readFileSync(pkgJson, "utf-8"));
|
|
77
|
+
}
|
|
78
|
+
function maybeNpmInstall(target, pkg) {
|
|
77
79
|
const deps = { ...(pkg.dependencies ?? {}), ...(pkg.peerDependencies ?? {}) };
|
|
78
80
|
if (Object.keys(deps).length === 0)
|
|
79
81
|
return;
|
|
@@ -88,6 +90,56 @@ function maybeNpmInstall(target) {
|
|
|
88
90
|
throw new Error(`npm install failed in ${target}; run it manually.`);
|
|
89
91
|
}
|
|
90
92
|
}
|
|
93
|
+
function normalizeBin(pkg) {
|
|
94
|
+
if (!pkg.bin)
|
|
95
|
+
return {};
|
|
96
|
+
if (typeof pkg.bin === "string") {
|
|
97
|
+
const name = pkg.name?.startsWith("@") ? pkg.name.split("/")[1] : pkg.name;
|
|
98
|
+
return name ? { [name]: pkg.bin } : {};
|
|
99
|
+
}
|
|
100
|
+
return pkg.bin;
|
|
101
|
+
}
|
|
102
|
+
function maybeNpmBuild(target, pkg) {
|
|
103
|
+
if (!pkg.scripts?.build)
|
|
104
|
+
return;
|
|
105
|
+
const binPaths = Object.values(normalizeBin(pkg)).map((p) => path.join(target, p));
|
|
106
|
+
if (binPaths.length === 0)
|
|
107
|
+
return;
|
|
108
|
+
if (binPaths.every((p) => fs.existsSync(p)))
|
|
109
|
+
return;
|
|
110
|
+
console.log(`Running npm run build in ${target}...`);
|
|
111
|
+
const result = spawnSync("npm", ["run", "build"], { cwd: target, stdio: "inherit" });
|
|
112
|
+
if (result.status !== 0) {
|
|
113
|
+
throw new Error(`npm run build failed in ${target}; run it manually.`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
function linkBins(target, pkg) {
|
|
117
|
+
const bins = normalizeBin(pkg);
|
|
118
|
+
if (Object.keys(bins).length === 0)
|
|
119
|
+
return [];
|
|
120
|
+
const binDir = path.join(CONFIG_DIR, "bin");
|
|
121
|
+
fs.mkdirSync(binDir, { recursive: true });
|
|
122
|
+
const linked = [];
|
|
123
|
+
for (const [name, relPath] of Object.entries(bins)) {
|
|
124
|
+
const src = path.resolve(target, relPath);
|
|
125
|
+
if (!fs.existsSync(src)) {
|
|
126
|
+
console.error(`agent-sh: skipping bin "${name}" — ${src} not found`);
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
try {
|
|
130
|
+
fs.chmodSync(src, 0o755);
|
|
131
|
+
}
|
|
132
|
+
catch { /* ignore */ }
|
|
133
|
+
const linkPath = path.join(binDir, name);
|
|
134
|
+
try {
|
|
135
|
+
fs.unlinkSync(linkPath);
|
|
136
|
+
}
|
|
137
|
+
catch { /* ignore */ }
|
|
138
|
+
fs.symlinkSync(src, linkPath);
|
|
139
|
+
linked.push(name);
|
|
140
|
+
}
|
|
141
|
+
return linked;
|
|
142
|
+
}
|
|
91
143
|
export async function runInstall(spec, opts = {}) {
|
|
92
144
|
if (!spec) {
|
|
93
145
|
console.error("Usage: agent-sh install <name|file:|npm:|github:> [--force]\n\n" +
|
|
@@ -114,10 +166,16 @@ export async function runInstall(spec, opts = {}) {
|
|
|
114
166
|
}
|
|
115
167
|
fs.rmSync(target, { recursive: true, force: true });
|
|
116
168
|
}
|
|
169
|
+
let linkedBins = [];
|
|
117
170
|
if (resolved.isDirectory) {
|
|
118
171
|
fs.cpSync(resolved.sourcePath, target, { recursive: true });
|
|
119
172
|
try {
|
|
120
|
-
|
|
173
|
+
const pkg = readPackageJson(target);
|
|
174
|
+
if (pkg) {
|
|
175
|
+
maybeNpmInstall(target, pkg);
|
|
176
|
+
maybeNpmBuild(target, pkg);
|
|
177
|
+
linkedBins = linkBins(target, pkg);
|
|
178
|
+
}
|
|
121
179
|
}
|
|
122
180
|
catch (err) {
|
|
123
181
|
console.error(`agent-sh: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -128,6 +186,11 @@ export async function runInstall(spec, opts = {}) {
|
|
|
128
186
|
fs.copyFileSync(resolved.sourcePath, target);
|
|
129
187
|
}
|
|
130
188
|
console.log(`Installed: ${resolved.name} -> ${target}`);
|
|
189
|
+
if (linkedBins.length > 0) {
|
|
190
|
+
const binDir = path.join(CONFIG_DIR, "bin");
|
|
191
|
+
console.log(`Linked bins: ${linkedBins.join(", ")} -> ${binDir}`);
|
|
192
|
+
console.log(`Add to PATH: export PATH="${binDir}:$PATH"`);
|
|
193
|
+
}
|
|
131
194
|
}
|
|
132
195
|
export async function runUninstall(name) {
|
|
133
196
|
if (!name) {
|
|
@@ -146,6 +209,23 @@ export async function runUninstall(name) {
|
|
|
146
209
|
console.error(`agent-sh: not installed: ${name}`);
|
|
147
210
|
process.exit(1);
|
|
148
211
|
}
|
|
212
|
+
const pkg = readPackageJson(target);
|
|
213
|
+
if (pkg) {
|
|
214
|
+
const binDir = path.join(CONFIG_DIR, "bin");
|
|
215
|
+
const targetPrefix = path.resolve(target) + path.sep;
|
|
216
|
+
for (const binName of Object.keys(normalizeBin(pkg))) {
|
|
217
|
+
const linkPath = path.join(binDir, binName);
|
|
218
|
+
try {
|
|
219
|
+
const stat = fs.lstatSync(linkPath, { throwIfNoEntry: false });
|
|
220
|
+
if (!stat?.isSymbolicLink())
|
|
221
|
+
continue;
|
|
222
|
+
const dest = path.resolve(binDir, fs.readlinkSync(linkPath));
|
|
223
|
+
if (dest.startsWith(targetPrefix))
|
|
224
|
+
fs.unlinkSync(linkPath);
|
|
225
|
+
}
|
|
226
|
+
catch { /* ignore */ }
|
|
227
|
+
}
|
|
228
|
+
}
|
|
149
229
|
fs.rmSync(target, { recursive: true, force: true });
|
|
150
230
|
console.log(`Uninstalled: ${name}`);
|
|
151
231
|
}
|
|
@@ -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. */
|