agent-sh 0.12.13 → 0.12.14
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.d.ts +0 -4
- package/dist/agent/agent-loop.js +23 -38
- package/dist/agent/subagent.js +5 -8
- package/dist/agent/system-prompt.d.ts +0 -27
- package/dist/agent/system-prompt.js +5 -24
- package/dist/core.d.ts +3 -4
- package/dist/core.js +30 -27
- package/dist/extension-loader.js +7 -0
- package/dist/extensions/agent-backend.js +0 -1
- package/dist/extensions/file-autocomplete.d.ts +1 -1
- package/dist/extensions/file-autocomplete.js +3 -3
- package/dist/extensions/index.d.ts +4 -0
- package/dist/extensions/index.js +1 -0
- package/dist/extensions/shell-context.d.ts +7 -0
- package/dist/extensions/shell-context.js +129 -0
- package/dist/extensions/slash-commands.js +2 -2
- package/dist/index.js +15 -16
- package/dist/shell/index.d.ts +35 -0
- package/dist/shell/index.js +47 -0
- package/dist/types.d.ts +26 -32
- package/dist/utils/message-utils.d.ts +13 -0
- package/dist/utils/message-utils.js +26 -0
- package/examples/extensions/overlay-agent.ts +9 -3
- package/examples/extensions/peer-mesh.ts +10 -7
- package/examples/extensions/subagents.ts +2 -2
- package/examples/extensions/terminal-buffer.ts +3 -2
- package/examples/extensions/tmux-pane.ts +5 -1
- package/examples/extensions/user-shell.ts +1 -1
- package/package.json +1 -1
- package/dist/context-manager.d.ts +0 -45
- package/dist/context-manager.js +0 -242
package/dist/context-manager.js
DELETED
|
@@ -1,242 +0,0 @@
|
|
|
1
|
-
import { getSettings } from "./settings.js";
|
|
2
|
-
import { spillOutput } from "./utils/shell-output-spill.js";
|
|
3
|
-
export class ContextManager {
|
|
4
|
-
exchanges = [];
|
|
5
|
-
nextId = 1;
|
|
6
|
-
currentCwd;
|
|
7
|
-
agentShellActive = false; // true while user_shell command is executing
|
|
8
|
-
constructor(bus, _handlers) {
|
|
9
|
-
this.currentCwd = process.cwd();
|
|
10
|
-
// ── Subscribe to shell events ──
|
|
11
|
-
bus.on("shell:command-done", (e) => {
|
|
12
|
-
const lines = e.output.split("\n");
|
|
13
|
-
const s = getSettings();
|
|
14
|
-
// Spill long outputs to a tempfile so the agent can `read_file` them
|
|
15
|
-
// on demand instead of carrying the full text in LLM context.
|
|
16
|
-
let output = e.output;
|
|
17
|
-
let spillPath;
|
|
18
|
-
if (lines.length > s.shellTruncateThreshold) {
|
|
19
|
-
// Reserve the id we're about to assign so the tempfile name matches.
|
|
20
|
-
const id = this.nextId;
|
|
21
|
-
try {
|
|
22
|
-
spillPath = spillOutput(id, e.output);
|
|
23
|
-
output = buildSpillStub(lines, s.shellHeadLines, s.shellTailLines, spillPath);
|
|
24
|
-
}
|
|
25
|
-
catch {
|
|
26
|
-
// If spill fails (e.g. disk full), fall back to keeping output in memory.
|
|
27
|
-
output = e.output;
|
|
28
|
-
spillPath = undefined;
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
this.addExchange({
|
|
32
|
-
type: "shell_command",
|
|
33
|
-
command: e.command,
|
|
34
|
-
output,
|
|
35
|
-
cwd: e.cwd,
|
|
36
|
-
exitCode: e.exitCode,
|
|
37
|
-
outputLines: lines.length,
|
|
38
|
-
outputBytes: e.output.length,
|
|
39
|
-
source: this.agentShellActive ? "agent" : "user",
|
|
40
|
-
spillPath,
|
|
41
|
-
});
|
|
42
|
-
});
|
|
43
|
-
bus.on("shell:cwd-change", (e) => {
|
|
44
|
-
this.currentCwd = e.cwd;
|
|
45
|
-
});
|
|
46
|
-
// Track agent-initiated shell commands (user_shell tool)
|
|
47
|
-
bus.on("shell:agent-exec-start", () => { this.agentShellActive = true; });
|
|
48
|
-
bus.on("shell:agent-exec-done", () => { this.agentShellActive = false; });
|
|
49
|
-
// ── Subscribe to agent events ──
|
|
50
|
-
// Only track queries (as markers). Agent responses and tool outputs
|
|
51
|
-
// live exclusively in ConversationState to avoid duplication.
|
|
52
|
-
bus.on("agent:query", (e) => {
|
|
53
|
-
this.addExchange({ type: "agent_query", query: e.query });
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
|
-
// ── Public query API ──────────────────────────────────────────
|
|
57
|
-
getCwd() {
|
|
58
|
-
return this.currentCwd;
|
|
59
|
-
}
|
|
60
|
-
/**
|
|
61
|
-
* Regex/keyword search across all exchanges. Returns formatted results.
|
|
62
|
-
*/
|
|
63
|
-
search(query) {
|
|
64
|
-
if (!query.trim())
|
|
65
|
-
return "No query provided.";
|
|
66
|
-
let regex;
|
|
67
|
-
try {
|
|
68
|
-
regex = new RegExp(query, "i");
|
|
69
|
-
}
|
|
70
|
-
catch {
|
|
71
|
-
// Fallback: treat as literal keywords with OR logic
|
|
72
|
-
const words = query.split(/\s+/).filter((w) => w.length > 0);
|
|
73
|
-
const pattern = words.map((w) => escapeRegex(w)).join("|");
|
|
74
|
-
regex = new RegExp(pattern, "i");
|
|
75
|
-
}
|
|
76
|
-
const matches = [];
|
|
77
|
-
for (const ex of this.exchanges) {
|
|
78
|
-
const text = this.exchangeSearchText(ex);
|
|
79
|
-
const lines = text.split("\n");
|
|
80
|
-
const matchingLineIndices = [];
|
|
81
|
-
for (let i = 0; i < lines.length; i++) {
|
|
82
|
-
if (regex.test(lines[i])) {
|
|
83
|
-
matchingLineIndices.push(i);
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
if (matchingLineIndices.length > 0) {
|
|
87
|
-
// Extract excerpts with 2 lines of context around each match
|
|
88
|
-
const excerpts = [];
|
|
89
|
-
for (const idx of matchingLineIndices.slice(0, 5)) {
|
|
90
|
-
const start = Math.max(0, idx - 2);
|
|
91
|
-
const end = Math.min(lines.length, idx + 3);
|
|
92
|
-
excerpts.push(lines.slice(start, end).join("\n"));
|
|
93
|
-
}
|
|
94
|
-
matches.push({ exchange: ex, excerpts });
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
if (matches.length === 0) {
|
|
98
|
-
return `No results found for "${query}".`;
|
|
99
|
-
}
|
|
100
|
-
const parts = [`Search results for "${query}" (${matches.length} exchanges):\n`];
|
|
101
|
-
for (const m of matches.slice(0, 20)) {
|
|
102
|
-
parts.push(`#${m.exchange.id} [${m.exchange.type}]`);
|
|
103
|
-
for (const excerpt of m.excerpts) {
|
|
104
|
-
parts.push(indent(excerpt, " "));
|
|
105
|
-
}
|
|
106
|
-
parts.push("");
|
|
107
|
-
}
|
|
108
|
-
return parts.join("\n");
|
|
109
|
-
}
|
|
110
|
-
/**
|
|
111
|
-
* Return shell events with id > afterId, formatted as an incremental
|
|
112
|
-
* delta suitable for injection into conversation history. Skips
|
|
113
|
-
* agent-source commands (already visible in tool results). Returns
|
|
114
|
-
* null when nothing new exists.
|
|
115
|
-
*
|
|
116
|
-
* The motivation: resending the full <shell_context> every turn wastes
|
|
117
|
-
* tokens — N turns × full history = O(N²) cost for O(N) information.
|
|
118
|
-
* Instead we inject only new events as regular conversation messages,
|
|
119
|
-
* so the provider's prefix cache amortizes them to O(N).
|
|
120
|
-
*/
|
|
121
|
-
getEventsSince(afterId) {
|
|
122
|
-
const fresh = this.exchanges.filter((e) => e.id > afterId && !(e.type === "shell_command" && e.source === "agent"));
|
|
123
|
-
if (fresh.length === 0)
|
|
124
|
-
return null;
|
|
125
|
-
const lastSeq = this.exchanges[this.exchanges.length - 1].id;
|
|
126
|
-
// Outputs already carry head+tail+spillPath stubs from capture time.
|
|
127
|
-
const parts = fresh.map((ex) => this.formatExchangeTruncated(ex)).filter((s) => s.length > 0);
|
|
128
|
-
if (parts.length === 0)
|
|
129
|
-
return null;
|
|
130
|
-
const body = parts.join("\n");
|
|
131
|
-
return {
|
|
132
|
-
text: `<shell-events>\n${body}</shell-events>`,
|
|
133
|
-
lastSeq,
|
|
134
|
-
};
|
|
135
|
-
}
|
|
136
|
-
/** Highest exchange id seen so far (0 if none). */
|
|
137
|
-
lastSeq() {
|
|
138
|
-
return this.exchanges.length === 0 ? 0 : this.exchanges[this.exchanges.length - 1].id;
|
|
139
|
-
}
|
|
140
|
-
/**
|
|
141
|
-
* One-line summaries of last N exchanges.
|
|
142
|
-
*/
|
|
143
|
-
getRecentSummary(n = 25) {
|
|
144
|
-
const recent = this.exchanges.slice(-n);
|
|
145
|
-
if (recent.length === 0)
|
|
146
|
-
return "No exchanges yet.";
|
|
147
|
-
return recent.map((ex) => this.exchangeOneLiner(ex)).join("\n");
|
|
148
|
-
}
|
|
149
|
-
/**
|
|
150
|
-
* Clear exchange history (used by /clear command).
|
|
151
|
-
*/
|
|
152
|
-
clear() {
|
|
153
|
-
this.exchanges = [];
|
|
154
|
-
// Don't reset nextId — IDs should be globally unique within a session
|
|
155
|
-
}
|
|
156
|
-
// ── Internal helpers ──────────────────────────────────────────
|
|
157
|
-
addExchange(partial) {
|
|
158
|
-
const exchange = {
|
|
159
|
-
...partial,
|
|
160
|
-
id: this.nextId++,
|
|
161
|
-
timestamp: Date.now(),
|
|
162
|
-
};
|
|
163
|
-
this.exchanges.push(exchange);
|
|
164
|
-
}
|
|
165
|
-
formatExchangeTruncated(ex) {
|
|
166
|
-
switch (ex.type) {
|
|
167
|
-
case "shell_command": {
|
|
168
|
-
const label = ex.source === "agent" ? "agent → shell" : "shell";
|
|
169
|
-
let s = `#${ex.id} [${label} cwd:${ex.cwd}] $ ${ex.command}\n`;
|
|
170
|
-
if (ex.output)
|
|
171
|
-
s += indent(ex.output, " ") + "\n";
|
|
172
|
-
if (ex.exitCode !== null)
|
|
173
|
-
s += ` exit ${ex.exitCode}\n`;
|
|
174
|
-
return s;
|
|
175
|
-
}
|
|
176
|
-
case "agent_query":
|
|
177
|
-
// Suppress: query already appears as the turn's user message.
|
|
178
|
-
// Kept in `exchanges` so search() can find it by id.
|
|
179
|
-
return "";
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
formatExchangeFull(ex) {
|
|
183
|
-
switch (ex.type) {
|
|
184
|
-
case "shell_command": {
|
|
185
|
-
const label = ex.source === "agent" ? "agent → shell" : "shell";
|
|
186
|
-
const output = ex.output;
|
|
187
|
-
let s = `#${ex.id} [${label}] $ ${ex.command} (${ex.outputLines} lines, ${ex.outputBytes} bytes)\n`;
|
|
188
|
-
if (output)
|
|
189
|
-
s += output + "\n";
|
|
190
|
-
if (ex.exitCode !== null)
|
|
191
|
-
s += `exit ${ex.exitCode}\n`;
|
|
192
|
-
return s;
|
|
193
|
-
}
|
|
194
|
-
case "agent_query":
|
|
195
|
-
return `#${ex.id} [you] > ${ex.query}`;
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
exchangeOneLiner(ex) {
|
|
199
|
-
switch (ex.type) {
|
|
200
|
-
case "shell_command": {
|
|
201
|
-
const label = ex.source === "agent" ? "agent → shell" : "shell";
|
|
202
|
-
return `#${ex.id} ${label} [cwd:${ex.cwd}]: ${ex.command} (${ex.outputLines} total lines, exit ${ex.exitCode ?? "?"})`;
|
|
203
|
-
}
|
|
204
|
-
case "agent_query":
|
|
205
|
-
return `#${ex.id} query: ${ex.query}`;
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
exchangeSearchText(ex) {
|
|
209
|
-
switch (ex.type) {
|
|
210
|
-
case "shell_command":
|
|
211
|
-
return `${ex.command}\n${ex.output}`;
|
|
212
|
-
case "agent_query":
|
|
213
|
-
return ex.query;
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
exchangeSize(ex) {
|
|
217
|
-
switch (ex.type) {
|
|
218
|
-
case "shell_command":
|
|
219
|
-
return ex.command.length + ex.output.length;
|
|
220
|
-
case "agent_query":
|
|
221
|
-
return ex.query.length;
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
// ── Utility functions ─────────────────────────────────────────
|
|
226
|
-
function buildSpillStub(lines, headLines, tailLines, spillPath) {
|
|
227
|
-
const omitted = lines.length - headLines - tailLines;
|
|
228
|
-
return [
|
|
229
|
-
...lines.slice(0, headLines),
|
|
230
|
-
`[... ${omitted} lines truncated — full output at ${spillPath}; use read_file to expand ...]`,
|
|
231
|
-
...lines.slice(-tailLines),
|
|
232
|
-
].join("\n");
|
|
233
|
-
}
|
|
234
|
-
function indent(text, prefix) {
|
|
235
|
-
return text
|
|
236
|
-
.split("\n")
|
|
237
|
-
.map((line) => prefix + line)
|
|
238
|
-
.join("\n");
|
|
239
|
-
}
|
|
240
|
-
function escapeRegex(str) {
|
|
241
|
-
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
242
|
-
}
|