agent-sh 0.7.0 → 0.8.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 +5 -1
- package/dist/agent/agent-loop.d.ts +2 -2
- package/dist/agent/agent-loop.js +106 -13
- package/dist/agent/conversation-state.d.ts +39 -9
- package/dist/agent/conversation-state.js +336 -17
- package/dist/agent/history-file.d.ts +36 -0
- package/dist/agent/history-file.js +167 -0
- package/dist/agent/nuclear-form.d.ts +41 -0
- package/dist/agent/nuclear-form.js +175 -0
- package/dist/agent/system-prompt.d.ts +2 -2
- package/dist/agent/system-prompt.js +25 -4
- package/dist/agent/tools/user-shell.js +4 -1
- package/dist/context-manager.d.ts +0 -1
- package/dist/context-manager.js +5 -110
- package/dist/core.js +14 -0
- package/dist/event-bus.d.ts +14 -0
- package/dist/extensions/overlay-agent.d.ts +4 -1
- package/dist/extensions/overlay-agent.js +115 -11
- package/dist/extensions/slash-commands.js +28 -0
- package/dist/extensions/terminal-buffer.js +9 -4
- package/dist/extensions/tui-renderer.js +119 -84
- package/dist/settings.d.ts +19 -2
- package/dist/settings.js +21 -3
- package/dist/shell.js +4 -0
- package/dist/token-budget.d.ts +13 -0
- package/dist/token-budget.js +50 -0
- package/dist/types.d.ts +0 -22
- package/dist/utils/ansi.d.ts +10 -0
- package/dist/utils/ansi.js +27 -0
- package/dist/utils/floating-panel.d.ts +32 -3
- package/dist/utils/floating-panel.js +296 -79
- package/dist/utils/line-editor.d.ts +9 -0
- package/dist/utils/line-editor.js +44 -0
- package/dist/utils/markdown.js +3 -3
- package/dist/utils/terminal-buffer.d.ts +4 -0
- package/dist/utils/terminal-buffer.js +13 -0
- package/dist/utils/tool-display.d.ts +1 -0
- package/dist/utils/tool-display.js +1 -1
- package/examples/extensions/claude-code-bridge/index.ts +77 -1
- package/examples/extensions/pi-bridge/index.ts +87 -2
- package/package.json +1 -1
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
// ── Tool classification ───────────────────────────────────────────
|
|
2
|
+
/** Read-only tools whose results are dropped at Tier 1→2 (agent can re-read). */
|
|
3
|
+
export const READ_ONLY_TOOLS = new Set([
|
|
4
|
+
"read_file", "grep", "glob", "ls", "search",
|
|
5
|
+
]);
|
|
6
|
+
/** State-changing tools whose summaries are kept in nuclear memory. */
|
|
7
|
+
export const WRITE_TOOLS = new Set([
|
|
8
|
+
"write_file", "edit_file", "write", "edit", "patch",
|
|
9
|
+
]);
|
|
10
|
+
// ── Nuclear entry generation ──────────────────────────────────────
|
|
11
|
+
/**
|
|
12
|
+
* Generate nuclear entries from a logical turn (a sequence of messages
|
|
13
|
+
* starting with a user message, followed by assistant + tool messages).
|
|
14
|
+
*/
|
|
15
|
+
export function toNuclearEntries(messages, startSeq, instanceId) {
|
|
16
|
+
const entries = [];
|
|
17
|
+
let seq = startSeq;
|
|
18
|
+
const ts = Date.now();
|
|
19
|
+
for (const msg of messages) {
|
|
20
|
+
if (msg.role === "user") {
|
|
21
|
+
const text = typeof msg.content === "string" ? msg.content : "";
|
|
22
|
+
// Skip compaction markers
|
|
23
|
+
if (text.startsWith("["))
|
|
24
|
+
continue;
|
|
25
|
+
entries.push({
|
|
26
|
+
seq: seq++, ts, iid: instanceId,
|
|
27
|
+
kind: "user",
|
|
28
|
+
sum: `user: "${truncate(text, 80)}"`,
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
else if (msg.role === "assistant") {
|
|
32
|
+
// Process tool calls
|
|
33
|
+
if ("tool_calls" in msg && msg.tool_calls) {
|
|
34
|
+
for (const tc of msg.tool_calls) {
|
|
35
|
+
if (!("function" in tc))
|
|
36
|
+
continue;
|
|
37
|
+
const name = tc.function.name;
|
|
38
|
+
let args = {};
|
|
39
|
+
try {
|
|
40
|
+
args = JSON.parse(tc.function.arguments);
|
|
41
|
+
}
|
|
42
|
+
catch { }
|
|
43
|
+
// Store the tool call — we'll enrich it when we see the result
|
|
44
|
+
entries.push({
|
|
45
|
+
seq: seq++, ts, iid: instanceId,
|
|
46
|
+
kind: "tool",
|
|
47
|
+
tool: name,
|
|
48
|
+
sum: summarizeToolCall(name, args),
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
else if (typeof msg.content === "string" && msg.content) {
|
|
53
|
+
entries.push({
|
|
54
|
+
seq: seq++, ts, iid: instanceId,
|
|
55
|
+
kind: "agent",
|
|
56
|
+
sum: `agent: "${truncate(msg.content, 60)}"`,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
else if (msg.role === "tool") {
|
|
61
|
+
// Enrich the most recent tool entry with result info
|
|
62
|
+
const content = typeof msg.content === "string" ? msg.content : "";
|
|
63
|
+
const lastTool = findLastTool(entries);
|
|
64
|
+
if (lastTool) {
|
|
65
|
+
const isError = content.startsWith("Error:");
|
|
66
|
+
if (isError) {
|
|
67
|
+
lastTool.kind = "error";
|
|
68
|
+
lastTool.sum = `error: ${lastTool.tool} ${truncate(content.slice(7).trim(), 80)}`;
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
lastTool.sum = enrichWithResult(lastTool.tool ?? "", lastTool.sum, content);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return entries;
|
|
77
|
+
}
|
|
78
|
+
// ── Formatting ────────────────────────────────────────────────────
|
|
79
|
+
/** Format a nuclear entry as a display line (for in-context injection). */
|
|
80
|
+
export function formatNuclearLine(entry) {
|
|
81
|
+
const time = new Date(entry.ts).toLocaleTimeString("en-US", {
|
|
82
|
+
hour: "2-digit", minute: "2-digit", hour12: false,
|
|
83
|
+
});
|
|
84
|
+
return `#${entry.seq} ${time} ${entry.sum}`;
|
|
85
|
+
}
|
|
86
|
+
// ── Serialization (JSONL for history file) ────────────────────────
|
|
87
|
+
/** Serialize a nuclear entry to a JSONL line. */
|
|
88
|
+
export function serializeEntry(entry) {
|
|
89
|
+
return JSON.stringify(entry);
|
|
90
|
+
}
|
|
91
|
+
/** Deserialize a JSONL line to a nuclear entry. Returns null on parse failure. */
|
|
92
|
+
export function deserializeEntry(line) {
|
|
93
|
+
try {
|
|
94
|
+
const obj = JSON.parse(line);
|
|
95
|
+
if (typeof obj.seq === "number" && typeof obj.sum === "string") {
|
|
96
|
+
return obj;
|
|
97
|
+
}
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
// ── Classification helpers ────────────────────────────────────────
|
|
105
|
+
/** Check if a nuclear entry represents a read-only action (should be dropped). */
|
|
106
|
+
export function isReadOnly(entry) {
|
|
107
|
+
return entry.kind === "tool" && entry.tool != null && READ_ONLY_TOOLS.has(entry.tool);
|
|
108
|
+
}
|
|
109
|
+
// ── Internal helpers ──────────────────────────────────────────────
|
|
110
|
+
function truncate(text, maxLen) {
|
|
111
|
+
const oneLine = text.replace(/\n/g, " ").trim();
|
|
112
|
+
return oneLine.length > maxLen ? oneLine.slice(0, maxLen) + "..." : oneLine;
|
|
113
|
+
}
|
|
114
|
+
function findLastTool(entries) {
|
|
115
|
+
for (let i = entries.length - 1; i >= 0; i--) {
|
|
116
|
+
if (entries[i].kind === "tool")
|
|
117
|
+
return entries[i];
|
|
118
|
+
}
|
|
119
|
+
return undefined;
|
|
120
|
+
}
|
|
121
|
+
function summarizeToolCall(name, args) {
|
|
122
|
+
switch (name) {
|
|
123
|
+
case "bash":
|
|
124
|
+
return `bash: ${truncate(String(args.command ?? ""), 60)}`;
|
|
125
|
+
case "user_shell":
|
|
126
|
+
return `user_shell: ${truncate(String(args.command ?? ""), 60)}`;
|
|
127
|
+
case "edit_file":
|
|
128
|
+
return `edit_file ${args.path ?? ""}`;
|
|
129
|
+
case "write_file":
|
|
130
|
+
case "write":
|
|
131
|
+
return `write_file ${args.path ?? args.file_path ?? ""}`;
|
|
132
|
+
case "read_file":
|
|
133
|
+
return `read_file ${args.path ?? args.file_path ?? ""}`;
|
|
134
|
+
case "grep":
|
|
135
|
+
return `grep "${truncate(String(args.pattern ?? ""), 30)}"`;
|
|
136
|
+
case "glob":
|
|
137
|
+
return `glob ${args.pattern ?? ""}`;
|
|
138
|
+
case "ls":
|
|
139
|
+
return `ls ${args.path ?? "."}`;
|
|
140
|
+
case "display":
|
|
141
|
+
return `display: ${truncate(String(args.command ?? ""), 60)}`;
|
|
142
|
+
default:
|
|
143
|
+
return `${name}`;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
function enrichWithResult(toolName, summary, result) {
|
|
147
|
+
const lines = result.split("\n");
|
|
148
|
+
const lineCount = lines.length;
|
|
149
|
+
switch (toolName) {
|
|
150
|
+
case "bash":
|
|
151
|
+
case "user_shell": {
|
|
152
|
+
// Extract exit code from result if present
|
|
153
|
+
const exitMatch = result.match(/exit code[:\s]*(\d+)/i) ?? result.match(/exit\s+(\d+)/);
|
|
154
|
+
const exitCode = exitMatch ? exitMatch[1] : "0";
|
|
155
|
+
return `${summary} (exit ${exitCode}, ${lineCount} lines)`;
|
|
156
|
+
}
|
|
157
|
+
case "edit_file":
|
|
158
|
+
case "edit": {
|
|
159
|
+
// Try to extract +/- counts from result
|
|
160
|
+
const addMatch = result.match(/\+(\d+)/);
|
|
161
|
+
const delMatch = result.match(/-(\d+)/);
|
|
162
|
+
if (addMatch || delMatch) {
|
|
163
|
+
return `${summary} (+${addMatch?.[1] ?? 0}/-${delMatch?.[1] ?? 0})`;
|
|
164
|
+
}
|
|
165
|
+
return `${summary} (edited)`;
|
|
166
|
+
}
|
|
167
|
+
case "write_file":
|
|
168
|
+
case "write": {
|
|
169
|
+
const created = result.toLowerCase().includes("created") ? "created" : "written";
|
|
170
|
+
return `${summary} (${created}, ${lineCount} lines)`;
|
|
171
|
+
}
|
|
172
|
+
default:
|
|
173
|
+
return `${summary} (${lineCount} lines)`;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
@@ -4,11 +4,11 @@ import type { ContextManager } from "../context-manager.js";
|
|
|
4
4
|
* Static system prompt — identical across all queries, cacheable.
|
|
5
5
|
* Contains only identity and behavioral instructions.
|
|
6
6
|
*/
|
|
7
|
-
export declare const STATIC_SYSTEM_PROMPT = "You are an AI coding assistant embedded in agent-sh, a terminal shell.\nYou have access to the user's shell environment and can read, write, and execute code.\nYou share the user's working directory, environment variables, and shell history.\n\n# Tool Decision Guide\n\nYou have three categories of tools \u2014 choose based on who needs the output and\nwhether the command has lasting effects:\n\n**Scratchpad tools** (bash, read_file, grep, glob, ls, edit_file, write_file):\nUse these to investigate, search, read, and modify files. Output is returned\nto you for reasoning \u2014 the user doesn't see it directly.\n\n**Display** (display):\nUse this to show output to the user in their terminal. The user sees the\noutput directly, but it is NOT returned to you. Use when:\n- The user asks to see something (cat a file, git log, git diff, man page)\n- The output is for the user to read, not for you to process\n\n**Live shell** (user_shell):\nUse this to run
|
|
7
|
+
export declare const STATIC_SYSTEM_PROMPT = "You are an AI coding assistant embedded in agent-sh, a terminal shell.\nYou have access to the user's shell environment and can read, write, and execute code.\nYou share the user's working directory, environment variables, and shell history.\n\n# Tool Decision Guide\n\nYou have three categories of tools \u2014 choose based on who needs the output and\nwhether the command has lasting effects:\n\n**Scratchpad tools** (bash, read_file, grep, glob, ls, edit_file, write_file):\nUse these to investigate, search, read, and modify files. Output is returned\nto you for reasoning \u2014 the user doesn't see it directly.\n\n**Display** (display):\nUse this to show output to the user in their terminal. The user sees the\noutput directly, but it is NOT returned to you. Use when:\n- The user asks to see something (cat a file, git log, git diff, man page)\n- The output is for the user to read, not for you to process\n\n**Live shell** (user_shell):\nUse this to run complete, non-interactive commands in the user's real shell. Use for:\n- Commands that affect shell state (cd, export, source)\n- Installing packages, starting servers, running builds\n- Any command where the user wants real side effects\n- Set return_output=true only if you need to inspect the result\n\n**Terminal interaction** (terminal_read, terminal_keys):\nUse these to observe and interact with what is currently on the user's terminal screen.\n- terminal_read: see what the user sees (current screen contents, cursor position)\n- terminal_keys: send keystrokes as if the user typed them\nUse for: driving interactive programs (vim, htop, less, ssh, REPLs), answering questions\nabout what's on screen, or typing at the shell prompt when a program is already running.\nDo NOT use user_shell to interact with an already-running program \u2014 use these instead.\n\nDefault to scratchpad tools for your own investigation. Use display when the\nuser is the intended audience. Use user_shell when the command has real effects.\nUse terminal_read/terminal_keys when interacting with what's already on screen.\n\n# Interactive Overlay Sessions\n\nWhen the dynamic context includes `interactive-session: true`, the user has summoned you\nvia a hotkey overlay from inside their live terminal. They may be in the middle of using\na program (vim, ssh, a REPL, etc.) or at a shell prompt. In this mode:\n- Start with terminal_read if you need to understand what's on screen.\n- Prefer terminal_keys to interact with whatever is currently running.\n- Use user_shell only for running new, standalone commands \u2014 not for interacting with\n what's already on screen.\n- Keep responses concise \u2014 the user is in the middle of a workflow.\n\n# Tool Usage Guidelines\n- Use read_file before editing a file you haven't seen\n- Prefer edit_file over write_file for modifying existing files\n- Use grep/glob to find files before reading them\n- Keep bash commands focused; avoid long-running blocking commands\n- Always check command exit codes for errors";
|
|
8
8
|
/**
|
|
9
9
|
* Build the dynamic context — injected as a user message before each query.
|
|
10
10
|
* Contains everything that changes: tools, shell context, conventions, cwd.
|
|
11
11
|
*
|
|
12
12
|
* Runs through the "agent:dynamic-context" pipe so extensions can append.
|
|
13
13
|
*/
|
|
14
|
-
export declare function buildDynamicContext(tools: ToolDefinition[], contextManager: ContextManager): string;
|
|
14
|
+
export declare function buildDynamicContext(tools: ToolDefinition[], contextManager: ContextManager, shellBudgetTokens?: number): string;
|
|
@@ -56,14 +56,34 @@ output directly, but it is NOT returned to you. Use when:
|
|
|
56
56
|
- The output is for the user to read, not for you to process
|
|
57
57
|
|
|
58
58
|
**Live shell** (user_shell):
|
|
59
|
-
Use this to run
|
|
59
|
+
Use this to run complete, non-interactive commands in the user's real shell. Use for:
|
|
60
60
|
- Commands that affect shell state (cd, export, source)
|
|
61
61
|
- Installing packages, starting servers, running builds
|
|
62
62
|
- Any command where the user wants real side effects
|
|
63
63
|
- Set return_output=true only if you need to inspect the result
|
|
64
64
|
|
|
65
|
+
**Terminal interaction** (terminal_read, terminal_keys):
|
|
66
|
+
Use these to observe and interact with what is currently on the user's terminal screen.
|
|
67
|
+
- terminal_read: see what the user sees (current screen contents, cursor position)
|
|
68
|
+
- terminal_keys: send keystrokes as if the user typed them
|
|
69
|
+
Use for: driving interactive programs (vim, htop, less, ssh, REPLs), answering questions
|
|
70
|
+
about what's on screen, or typing at the shell prompt when a program is already running.
|
|
71
|
+
Do NOT use user_shell to interact with an already-running program — use these instead.
|
|
72
|
+
|
|
65
73
|
Default to scratchpad tools for your own investigation. Use display when the
|
|
66
74
|
user is the intended audience. Use user_shell when the command has real effects.
|
|
75
|
+
Use terminal_read/terminal_keys when interacting with what's already on screen.
|
|
76
|
+
|
|
77
|
+
# Interactive Overlay Sessions
|
|
78
|
+
|
|
79
|
+
When the dynamic context includes \`interactive-session: true\`, the user has summoned you
|
|
80
|
+
via a hotkey overlay from inside their live terminal. They may be in the middle of using
|
|
81
|
+
a program (vim, ssh, a REPL, etc.) or at a shell prompt. In this mode:
|
|
82
|
+
- Start with terminal_read if you need to understand what's on screen.
|
|
83
|
+
- Prefer terminal_keys to interact with whatever is currently running.
|
|
84
|
+
- Use user_shell only for running new, standalone commands — not for interacting with
|
|
85
|
+
what's already on screen.
|
|
86
|
+
- Keep responses concise — the user is in the middle of a workflow.
|
|
67
87
|
|
|
68
88
|
# Tool Usage Guidelines
|
|
69
89
|
- Use read_file before editing a file you haven't seen
|
|
@@ -77,7 +97,7 @@ user is the intended audience. Use user_shell when the command has real effects.
|
|
|
77
97
|
*
|
|
78
98
|
* Runs through the "agent:dynamic-context" pipe so extensions can append.
|
|
79
99
|
*/
|
|
80
|
-
export function buildDynamicContext(tools, contextManager) {
|
|
100
|
+
export function buildDynamicContext(tools, contextManager, shellBudgetTokens) {
|
|
81
101
|
const sections = [];
|
|
82
102
|
// Tools
|
|
83
103
|
sections.push("# Available Tools\n" +
|
|
@@ -92,8 +112,9 @@ export function buildDynamicContext(tools, contextManager) {
|
|
|
92
112
|
if (skills.length > 0) {
|
|
93
113
|
sections.push(`You have access to ${skills.length} skill(s). Use the list_skills tool to see them, then read_file to load one.`);
|
|
94
114
|
}
|
|
95
|
-
// Shell context
|
|
96
|
-
const
|
|
115
|
+
// Shell context — pass token budget converted to bytes (~4 chars/token)
|
|
116
|
+
const shellBudgetBytes = shellBudgetTokens != null ? shellBudgetTokens * 4 : undefined;
|
|
117
|
+
const shellContext = contextManager.getContext(shellBudgetBytes);
|
|
97
118
|
if (shellContext) {
|
|
98
119
|
sections.push(shellContext);
|
|
99
120
|
}
|
|
@@ -8,7 +8,10 @@
|
|
|
8
8
|
export function createUserShellTool(opts) {
|
|
9
9
|
return {
|
|
10
10
|
name: "user_shell",
|
|
11
|
-
description: "Run a
|
|
11
|
+
description: "Run a complete, non-interactive command in the user's live shell (cd, export, install packages, start servers, git commands). " +
|
|
12
|
+
"Use this for commands that have side effects or that the user wants to see. Output is shown directly to the user but NOT returned " +
|
|
13
|
+
"to you by default — set return_output=true if you need to inspect the result. " +
|
|
14
|
+
"Do NOT use this to interact with programs that are already running in the terminal — use terminal_keys/terminal_read instead.",
|
|
12
15
|
input_schema: {
|
|
13
16
|
type: "object",
|
|
14
17
|
properties: {
|
package/dist/context-manager.js
CHANGED
|
@@ -1,16 +1,9 @@
|
|
|
1
1
|
import { getSettings } from "./settings.js";
|
|
2
|
-
// Non-configurable thresholds (agent response and tool output follow shell settings)
|
|
3
|
-
const AGENT_RESPONSE_TRUNCATE_THRESHOLD = 20;
|
|
4
|
-
const AGENT_RESPONSE_HEAD_LINES = 15;
|
|
5
|
-
const TOOL_TRUNCATE_THRESHOLD = 20;
|
|
6
|
-
const TOOL_HEAD_LINES = 5;
|
|
7
|
-
const TOOL_TAIL_LINES = 5;
|
|
8
2
|
export class ContextManager {
|
|
9
3
|
exchanges = [];
|
|
10
4
|
nextId = 1;
|
|
11
5
|
currentCwd;
|
|
12
6
|
sessionStart;
|
|
13
|
-
pendingToolCalls = [];
|
|
14
7
|
firstPrompt = true;
|
|
15
8
|
agentShellActive = false; // true while user_shell command is executing
|
|
16
9
|
handlers = null;
|
|
@@ -43,46 +36,11 @@ export class ContextManager {
|
|
|
43
36
|
bus.on("shell:agent-exec-start", () => { this.agentShellActive = true; });
|
|
44
37
|
bus.on("shell:agent-exec-done", () => { this.agentShellActive = false; });
|
|
45
38
|
// ── Subscribe to agent events ──
|
|
39
|
+
// Only track queries (as markers). Agent responses and tool outputs
|
|
40
|
+
// live exclusively in ConversationState to avoid duplication.
|
|
46
41
|
bus.on("agent:query", (e) => {
|
|
47
|
-
this.pendingToolCalls = [];
|
|
48
42
|
this.addExchange({ type: "agent_query", query: e.query });
|
|
49
43
|
});
|
|
50
|
-
bus.on("agent:response-done", (e) => {
|
|
51
|
-
this.addExchange({
|
|
52
|
-
type: "agent_response",
|
|
53
|
-
response: e.response,
|
|
54
|
-
toolCalls: this.pendingToolCalls,
|
|
55
|
-
});
|
|
56
|
-
this.pendingToolCalls = [];
|
|
57
|
-
});
|
|
58
|
-
bus.on("agent:tool-call", (e) => {
|
|
59
|
-
// Accumulate tool calls for the agent_response summary
|
|
60
|
-
this.pendingToolCalls.push({
|
|
61
|
-
tool: e.tool,
|
|
62
|
-
args: e.args,
|
|
63
|
-
output: "",
|
|
64
|
-
exitCode: null,
|
|
65
|
-
});
|
|
66
|
-
});
|
|
67
|
-
bus.on("agent:tool-output", (e) => {
|
|
68
|
-
// Update the last pending tool call with output
|
|
69
|
-
const last = this.pendingToolCalls[this.pendingToolCalls.length - 1];
|
|
70
|
-
if (last) {
|
|
71
|
-
last.output = e.output;
|
|
72
|
-
last.exitCode = e.exitCode;
|
|
73
|
-
}
|
|
74
|
-
// Also store as a separate exchange for chronological log
|
|
75
|
-
const lines = e.output.split("\n");
|
|
76
|
-
this.addExchange({
|
|
77
|
-
type: "tool_execution",
|
|
78
|
-
tool: e.tool,
|
|
79
|
-
args: {},
|
|
80
|
-
output: e.output,
|
|
81
|
-
exitCode: e.exitCode,
|
|
82
|
-
outputLines: lines.length,
|
|
83
|
-
outputBytes: e.output.length,
|
|
84
|
-
});
|
|
85
|
-
});
|
|
86
44
|
}
|
|
87
45
|
// ── Public query API ──────────────────────────────────────────
|
|
88
46
|
getCwd() {
|
|
@@ -231,7 +189,6 @@ export class ContextManager {
|
|
|
231
189
|
*/
|
|
232
190
|
clear() {
|
|
233
191
|
this.exchanges = [];
|
|
234
|
-
this.pendingToolCalls = [];
|
|
235
192
|
this.firstPrompt = true;
|
|
236
193
|
// Don't reset nextId — IDs should be globally unique within a session
|
|
237
194
|
}
|
|
@@ -249,12 +206,7 @@ export class ContextManager {
|
|
|
249
206
|
const s = getSettings();
|
|
250
207
|
ex.output = truncateOutput(ex.output, s.shellTruncateThreshold, s.shellHeadLines, s.shellTailLines, ex.id);
|
|
251
208
|
}
|
|
252
|
-
|
|
253
|
-
ex.response = truncateHead(ex.response, AGENT_RESPONSE_TRUNCATE_THRESHOLD, AGENT_RESPONSE_HEAD_LINES, ex.id);
|
|
254
|
-
}
|
|
255
|
-
else if (ex.type === "tool_execution") {
|
|
256
|
-
ex.output = truncateOutput(ex.output, TOOL_TRUNCATE_THRESHOLD, TOOL_HEAD_LINES, TOOL_TAIL_LINES, ex.id);
|
|
257
|
-
}
|
|
209
|
+
// agent_query has no output to truncate
|
|
258
210
|
}
|
|
259
211
|
// Pass 2: budget enforcement — strip output from oldest if over budget
|
|
260
212
|
let totalSize = result.reduce((sum, ex) => sum + this.exchangeSize(ex), 0);
|
|
@@ -264,12 +216,6 @@ export class ContextManager {
|
|
|
264
216
|
if (ex.type === "shell_command") {
|
|
265
217
|
ex.output = `[output omitted, use shell_recall tool to expand id ${ex.id}]`;
|
|
266
218
|
}
|
|
267
|
-
else if (ex.type === "tool_execution") {
|
|
268
|
-
ex.output = `[output omitted, use shell_recall tool to expand id ${ex.id}]`;
|
|
269
|
-
}
|
|
270
|
-
else if (ex.type === "agent_response") {
|
|
271
|
-
ex.response = `[response omitted, use shell_recall tool to expand id ${ex.id}]`;
|
|
272
|
-
}
|
|
273
219
|
totalSize -= before - this.exchangeSize(ex);
|
|
274
220
|
}
|
|
275
221
|
return result;
|
|
@@ -288,7 +234,8 @@ export class ContextManager {
|
|
|
288
234
|
out += `- When the user asks to see, list, view, or display anything, ALWAYS use user_shell. NEVER use internal tools like ls/read/bash for display — the user won't see it.\n`;
|
|
289
235
|
out += `- Only use internal tools when YOU need to reason about content silently (e.g. reading a file to answer a question about it).\n`;
|
|
290
236
|
out += `- After a user_shell command, the user already saw the output. Do NOT repeat or summarize it.\n`;
|
|
291
|
-
out += `- You can browse or search
|
|
237
|
+
out += `- You can browse or search shell history with shell_recall.\n`;
|
|
238
|
+
out += `- You can browse or search evicted conversation turns with conversation_recall.\n`;
|
|
292
239
|
out += `\n`;
|
|
293
240
|
this.firstPrompt = false;
|
|
294
241
|
}
|
|
@@ -326,25 +273,6 @@ export class ContextManager {
|
|
|
326
273
|
}
|
|
327
274
|
case "agent_query":
|
|
328
275
|
return `#${ex.id} [you] > ${ex.query}\n`;
|
|
329
|
-
case "agent_response": {
|
|
330
|
-
let s = `#${ex.id} [agent] `;
|
|
331
|
-
if (ex.response)
|
|
332
|
-
s += ex.response.split("\n")[0] + "\n";
|
|
333
|
-
if (ex.response.includes("\n")) {
|
|
334
|
-
const rest = ex.response.slice(ex.response.indexOf("\n") + 1);
|
|
335
|
-
if (rest.trim())
|
|
336
|
-
s += indent(rest, " ") + "\n";
|
|
337
|
-
}
|
|
338
|
-
return s;
|
|
339
|
-
}
|
|
340
|
-
case "tool_execution": {
|
|
341
|
-
let s = `#${ex.id} [tool] ${ex.tool}\n`;
|
|
342
|
-
if (ex.output)
|
|
343
|
-
s += indent(ex.output, " ") + "\n";
|
|
344
|
-
if (ex.exitCode !== null)
|
|
345
|
-
s += ` exit ${ex.exitCode}\n`;
|
|
346
|
-
return s;
|
|
347
|
-
}
|
|
348
276
|
}
|
|
349
277
|
}
|
|
350
278
|
formatExchangeFull(ex) {
|
|
@@ -361,16 +289,6 @@ export class ContextManager {
|
|
|
361
289
|
}
|
|
362
290
|
case "agent_query":
|
|
363
291
|
return `#${ex.id} [you] > ${ex.query}`;
|
|
364
|
-
case "agent_response":
|
|
365
|
-
return `#${ex.id} [agent]\n${ex.response}`;
|
|
366
|
-
case "tool_execution": {
|
|
367
|
-
let s = `#${ex.id} [tool] ${ex.tool} (${ex.outputLines} lines, ${ex.outputBytes} bytes)\n`;
|
|
368
|
-
if (ex.output)
|
|
369
|
-
s += ex.output + "\n";
|
|
370
|
-
if (ex.exitCode !== null)
|
|
371
|
-
s += `exit ${ex.exitCode}\n`;
|
|
372
|
-
return s;
|
|
373
|
-
}
|
|
374
292
|
}
|
|
375
293
|
}
|
|
376
294
|
exchangeOneLiner(ex) {
|
|
@@ -381,12 +299,6 @@ export class ContextManager {
|
|
|
381
299
|
}
|
|
382
300
|
case "agent_query":
|
|
383
301
|
return `#${ex.id} query: ${ex.query}`;
|
|
384
|
-
case "agent_response": {
|
|
385
|
-
const preview = ex.response.split("\n")[0]?.slice(0, 80) ?? "";
|
|
386
|
-
return `#${ex.id} agent: ${preview}${ex.response.length > 80 ? "..." : ""}`;
|
|
387
|
-
}
|
|
388
|
-
case "tool_execution":
|
|
389
|
-
return `#${ex.id} tool: ${ex.tool} (${ex.outputLines} lines, exit ${ex.exitCode ?? "?"})`;
|
|
390
302
|
}
|
|
391
303
|
}
|
|
392
304
|
exchangeSearchText(ex) {
|
|
@@ -395,10 +307,6 @@ export class ContextManager {
|
|
|
395
307
|
return `${ex.command}\n${ex.output}`;
|
|
396
308
|
case "agent_query":
|
|
397
309
|
return ex.query;
|
|
398
|
-
case "agent_response":
|
|
399
|
-
return ex.response;
|
|
400
|
-
case "tool_execution":
|
|
401
|
-
return `${ex.tool}\n${ex.output}`;
|
|
402
310
|
}
|
|
403
311
|
}
|
|
404
312
|
exchangeSize(ex) {
|
|
@@ -407,10 +315,6 @@ export class ContextManager {
|
|
|
407
315
|
return ex.command.length + ex.output.length;
|
|
408
316
|
case "agent_query":
|
|
409
317
|
return ex.query.length;
|
|
410
|
-
case "agent_response":
|
|
411
|
-
return ex.response.length;
|
|
412
|
-
case "tool_execution":
|
|
413
|
-
return ex.tool.length + ex.output.length;
|
|
414
318
|
}
|
|
415
319
|
}
|
|
416
320
|
}
|
|
@@ -426,15 +330,6 @@ function truncateOutput(text, threshold, headLines, tailLines, id) {
|
|
|
426
330
|
...lines.slice(-tailLines),
|
|
427
331
|
].join("\n");
|
|
428
332
|
}
|
|
429
|
-
function truncateHead(text, threshold, headLines, id) {
|
|
430
|
-
const lines = text.split("\n");
|
|
431
|
-
if (lines.length <= threshold)
|
|
432
|
-
return text;
|
|
433
|
-
return [
|
|
434
|
-
...lines.slice(0, headLines),
|
|
435
|
-
`[... truncated, use shell_recall tool with expand and id ${id} for full response ...]`,
|
|
436
|
-
].join("\n");
|
|
437
|
-
}
|
|
438
333
|
function indent(text, prefix) {
|
|
439
334
|
return text
|
|
440
335
|
.split("\n")
|
package/dist/core.js
CHANGED
|
@@ -175,6 +175,20 @@ export function createCore(config) {
|
|
|
175
175
|
supportsReasoningEffort: p.supportsReasoningEffort,
|
|
176
176
|
modelCapabilities: caps.size > 0 ? caps : undefined,
|
|
177
177
|
});
|
|
178
|
+
// Push registered models into the agent loop so they appear in
|
|
179
|
+
// autocomplete and are selectable via /model.
|
|
180
|
+
const addModes = modelIds.map((m) => {
|
|
181
|
+
const mc = caps.get(m);
|
|
182
|
+
return {
|
|
183
|
+
model: m,
|
|
184
|
+
provider: p.id,
|
|
185
|
+
providerConfig: { apiKey: p.apiKey ?? "", baseURL: p.baseURL },
|
|
186
|
+
contextWindow: mc?.contextWindow,
|
|
187
|
+
reasoning: mc?.reasoning,
|
|
188
|
+
supportsReasoningEffort: p.supportsReasoningEffort,
|
|
189
|
+
};
|
|
190
|
+
});
|
|
191
|
+
bus.emit("config:add-modes", { modes: addModes });
|
|
178
192
|
});
|
|
179
193
|
bus.on("config:switch-provider", ({ provider: name }) => {
|
|
180
194
|
const p = providerRegistry.get(name);
|
package/dist/event-bus.d.ts
CHANGED
|
@@ -28,6 +28,10 @@ export interface ShellEvents {
|
|
|
28
28
|
"shell:pty-write": {
|
|
29
29
|
data: string;
|
|
30
30
|
};
|
|
31
|
+
"shell:pty-resize": {
|
|
32
|
+
cols: number;
|
|
33
|
+
rows: number;
|
|
34
|
+
};
|
|
31
35
|
"shell:buffer-request": Record<string, never>;
|
|
32
36
|
"shell:buffer-snapshot": {
|
|
33
37
|
text: string;
|
|
@@ -171,6 +175,13 @@ export interface ShellEvents {
|
|
|
171
175
|
contextWindow?: number;
|
|
172
176
|
};
|
|
173
177
|
"agent:reset-session": Record<string, never>;
|
|
178
|
+
"agent:compact-request": Record<string, never>;
|
|
179
|
+
"context:get-stats": {
|
|
180
|
+
activeTokens: number;
|
|
181
|
+
nuclearEntries: number;
|
|
182
|
+
recallArchiveSize: number;
|
|
183
|
+
budgetTokens: number;
|
|
184
|
+
};
|
|
174
185
|
"agent:register-backend": {
|
|
175
186
|
name: string;
|
|
176
187
|
kill: () => void;
|
|
@@ -210,6 +221,9 @@ export interface ShellEvents {
|
|
|
210
221
|
"config:set-modes": {
|
|
211
222
|
modes: AgentMode[];
|
|
212
223
|
};
|
|
224
|
+
"config:add-modes": {
|
|
225
|
+
modes: AgentMode[];
|
|
226
|
+
};
|
|
213
227
|
"provider:register": {
|
|
214
228
|
id: string;
|
|
215
229
|
apiKey?: string;
|
|
@@ -5,7 +5,10 @@
|
|
|
5
5
|
* inside vim, htop, or ssh. Composites a floating response box on top
|
|
6
6
|
* of the current terminal content.
|
|
7
7
|
*
|
|
8
|
+
* Rendering reuses the shared tui:render-* handlers so that extensions
|
|
9
|
+
* advising those handlers affect both the main TUI and the overlay.
|
|
10
|
+
*
|
|
8
11
|
* Requires: npm install @xterm/headless@5.5.0 @xterm/addon-serialize@0.13.0
|
|
9
12
|
*/
|
|
10
13
|
import type { ExtensionContext } from "../types.js";
|
|
11
|
-
export default function activate(
|
|
14
|
+
export default function activate(ctx: ExtensionContext): void;
|