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
package/dist/settings.js
CHANGED
|
@@ -21,6 +21,10 @@ const DEFAULTS = {
|
|
|
21
21
|
shellHeadLines: 5,
|
|
22
22
|
shellTailLines: 5,
|
|
23
23
|
recallExpandMaxLines: 100,
|
|
24
|
+
shellContextRatio: 0.35,
|
|
25
|
+
historyMaxBytes: 102400,
|
|
26
|
+
historyStartupEntries: 50,
|
|
27
|
+
nuclearMaxEntries: 200,
|
|
24
28
|
maxCommandOutputLines: 3,
|
|
25
29
|
readOutputMaxLines: 10,
|
|
26
30
|
diffMaxLines: 20,
|
|
@@ -86,15 +90,29 @@ export function resolveProvider(name) {
|
|
|
86
90
|
const provider = settings.providers?.[name];
|
|
87
91
|
if (!provider)
|
|
88
92
|
return null;
|
|
89
|
-
const
|
|
90
|
-
const
|
|
93
|
+
const rawModels = provider.models ?? (provider.defaultModel ? [provider.defaultModel] : []);
|
|
94
|
+
const modelIds = [];
|
|
95
|
+
const caps = new Map();
|
|
96
|
+
for (const m of rawModels) {
|
|
97
|
+
if (typeof m === "string") {
|
|
98
|
+
modelIds.push(m);
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
modelIds.push(m.id);
|
|
102
|
+
if (m.reasoning !== undefined || m.contextWindow !== undefined) {
|
|
103
|
+
caps.set(m.id, { reasoning: m.reasoning, contextWindow: m.contextWindow });
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
const defaultModel = provider.defaultModel ?? modelIds[0];
|
|
91
108
|
return {
|
|
92
109
|
id: name,
|
|
93
110
|
apiKey: provider.apiKey ? expandEnvVars(provider.apiKey) : undefined,
|
|
94
111
|
baseURL: provider.baseURL,
|
|
95
112
|
defaultModel,
|
|
96
|
-
models:
|
|
113
|
+
models: modelIds.length ? modelIds : (defaultModel ? [defaultModel] : []),
|
|
97
114
|
contextWindow: provider.contextWindow,
|
|
115
|
+
modelCapabilities: caps.size > 0 ? caps : undefined,
|
|
98
116
|
};
|
|
99
117
|
}
|
|
100
118
|
/** Get all configured provider names. */
|
package/dist/shell.js
CHANGED
|
@@ -163,6 +163,10 @@ export class Shell {
|
|
|
163
163
|
this.bus.on("shell:pty-write", ({ data }) => {
|
|
164
164
|
this.ptyProcess.write(data);
|
|
165
165
|
});
|
|
166
|
+
// Allow extensions to resize the PTY (sends SIGWINCH to child)
|
|
167
|
+
this.bus.on("shell:pty-resize", ({ cols, rows }) => {
|
|
168
|
+
this.ptyProcess.resize(cols, rows);
|
|
169
|
+
});
|
|
166
170
|
// Ref-counted stdout hold — overlay extensions suppress PTY output
|
|
167
171
|
this.bus.on("shell:stdout-hold", () => { this.stdoutHold.increment(); });
|
|
168
172
|
this.bus.on("shell:stdout-release", () => { this.stdoutHold.decrement(); });
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export declare class TokenBudget {
|
|
2
|
+
private contextWindow;
|
|
3
|
+
private toolCount;
|
|
4
|
+
constructor(contextWindow?: number, toolCount?: number);
|
|
5
|
+
/** Update when model or tool set changes. */
|
|
6
|
+
update(contextWindow?: number, toolCount?: number): void;
|
|
7
|
+
/** Total tokens available for shell context + conversation content. */
|
|
8
|
+
get contentBudget(): number;
|
|
9
|
+
/** Token budget for the shell context stream. */
|
|
10
|
+
get shellBudgetTokens(): number;
|
|
11
|
+
/** Token budget for the conversation messages stream. */
|
|
12
|
+
get conversationBudgetTokens(): number;
|
|
13
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unified token budget manager.
|
|
3
|
+
*
|
|
4
|
+
* Splits a model's context window between two streams:
|
|
5
|
+
* - Shell context (user shell commands and outputs — situational awareness)
|
|
6
|
+
* - Conversation (agent messages and tool results — task continuity)
|
|
7
|
+
*
|
|
8
|
+
* The budget accounts for fixed overhead (system prompt, tool definitions,
|
|
9
|
+
* response reserve) and divides the remaining space by a configurable ratio.
|
|
10
|
+
*/
|
|
11
|
+
import { getSettings } from "./settings.js";
|
|
12
|
+
/** Overhead estimates (tokens). */
|
|
13
|
+
const SYSTEM_PROMPT_OVERHEAD = 800;
|
|
14
|
+
const DYNAMIC_CONTEXT_OVERHEAD = 500; // conventions, metadata, skills list
|
|
15
|
+
const TOKENS_PER_TOOL_DEFINITION = 50;
|
|
16
|
+
const RESPONSE_RESERVE = 8192; // matches llm-client.ts default max_tokens
|
|
17
|
+
/** Fallback when contextWindow is unknown. */
|
|
18
|
+
const DEFAULT_CONTEXT_WINDOW = 60_000;
|
|
19
|
+
export class TokenBudget {
|
|
20
|
+
contextWindow;
|
|
21
|
+
toolCount;
|
|
22
|
+
constructor(contextWindow, toolCount = 0) {
|
|
23
|
+
this.contextWindow = contextWindow ?? DEFAULT_CONTEXT_WINDOW;
|
|
24
|
+
this.toolCount = toolCount;
|
|
25
|
+
}
|
|
26
|
+
/** Update when model or tool set changes. */
|
|
27
|
+
update(contextWindow, toolCount) {
|
|
28
|
+
if (contextWindow != null)
|
|
29
|
+
this.contextWindow = contextWindow;
|
|
30
|
+
if (toolCount != null)
|
|
31
|
+
this.toolCount = toolCount;
|
|
32
|
+
}
|
|
33
|
+
/** Total tokens available for shell context + conversation content. */
|
|
34
|
+
get contentBudget() {
|
|
35
|
+
const overhead = SYSTEM_PROMPT_OVERHEAD +
|
|
36
|
+
DYNAMIC_CONTEXT_OVERHEAD +
|
|
37
|
+
this.toolCount * TOKENS_PER_TOOL_DEFINITION +
|
|
38
|
+
RESPONSE_RESERVE;
|
|
39
|
+
return Math.max(0, this.contextWindow - overhead);
|
|
40
|
+
}
|
|
41
|
+
/** Token budget for the shell context stream. */
|
|
42
|
+
get shellBudgetTokens() {
|
|
43
|
+
const ratio = getSettings().shellContextRatio;
|
|
44
|
+
return Math.floor(this.contentBudget * ratio);
|
|
45
|
+
}
|
|
46
|
+
/** Token budget for the conversation messages stream. */
|
|
47
|
+
get conversationBudgetTokens() {
|
|
48
|
+
return this.contentBudget - this.shellBudgetTokens;
|
|
49
|
+
}
|
|
50
|
+
}
|
package/dist/types.d.ts
CHANGED
|
@@ -101,12 +101,6 @@ export interface TerminalSession {
|
|
|
101
101
|
done: boolean;
|
|
102
102
|
resolve?: (value: void) => void;
|
|
103
103
|
}
|
|
104
|
-
export interface ToolCallRecord {
|
|
105
|
-
tool: string;
|
|
106
|
-
args: Record<string, unknown>;
|
|
107
|
-
output: string;
|
|
108
|
-
exitCode: number | null;
|
|
109
|
-
}
|
|
110
104
|
export type Exchange = {
|
|
111
105
|
type: "shell_command";
|
|
112
106
|
id: number;
|
|
@@ -124,20 +118,4 @@ export type Exchange = {
|
|
|
124
118
|
id: number;
|
|
125
119
|
timestamp: number;
|
|
126
120
|
query: string;
|
|
127
|
-
} | {
|
|
128
|
-
type: "agent_response";
|
|
129
|
-
id: number;
|
|
130
|
-
timestamp: number;
|
|
131
|
-
response: string;
|
|
132
|
-
toolCalls: ToolCallRecord[];
|
|
133
|
-
} | {
|
|
134
|
-
type: "tool_execution";
|
|
135
|
-
id: number;
|
|
136
|
-
timestamp: number;
|
|
137
|
-
tool: string;
|
|
138
|
-
args: Record<string, unknown>;
|
|
139
|
-
output: string;
|
|
140
|
-
exitCode: number | null;
|
|
141
|
-
outputLines: number;
|
|
142
|
-
outputBytes: number;
|
|
143
121
|
};
|
package/dist/utils/ansi.d.ts
CHANGED
|
@@ -11,5 +11,15 @@ export declare const RESET = "\u001B[0m";
|
|
|
11
11
|
* Excludes SGR (color/style) sequences and accounts for CJK double-width chars.
|
|
12
12
|
*/
|
|
13
13
|
export declare function visibleLen(str: string): number;
|
|
14
|
+
/**
|
|
15
|
+
* Truncate a string to fit within `maxWidth` visible columns.
|
|
16
|
+
* Accounts for CJK double-width characters. Appends `…` if truncated.
|
|
17
|
+
*/
|
|
18
|
+
export declare function truncateToWidth(str: string, maxWidth: number): string;
|
|
19
|
+
/**
|
|
20
|
+
* Pad a string with spaces to fill `targetWidth` visible columns.
|
|
21
|
+
* Accounts for CJK double-width characters.
|
|
22
|
+
*/
|
|
23
|
+
export declare function padEndToWidth(str: string, targetWidth: number): string;
|
|
14
24
|
/** Strip all ANSI escape sequences (SGR, OSC, CSI, private mode) and carriage returns. */
|
|
15
25
|
export declare function stripAnsi(str: string): string;
|
package/dist/utils/ansi.js
CHANGED
|
@@ -70,6 +70,33 @@ export function visibleLen(str) {
|
|
|
70
70
|
}
|
|
71
71
|
return width;
|
|
72
72
|
}
|
|
73
|
+
/**
|
|
74
|
+
* Truncate a string to fit within `maxWidth` visible columns.
|
|
75
|
+
* Accounts for CJK double-width characters. Appends `…` if truncated.
|
|
76
|
+
*/
|
|
77
|
+
export function truncateToWidth(str, maxWidth) {
|
|
78
|
+
const clean = str.replace(/\x1b\[[^m]*m/g, "");
|
|
79
|
+
let width = 0;
|
|
80
|
+
let i = 0;
|
|
81
|
+
for (const char of clean) {
|
|
82
|
+
const cw = charWidth(char.codePointAt(0) ?? 0);
|
|
83
|
+
if (width + cw > maxWidth - 1) {
|
|
84
|
+
// Need room for the "…" (1 column wide)
|
|
85
|
+
return clean.slice(0, i) + "…";
|
|
86
|
+
}
|
|
87
|
+
width += cw;
|
|
88
|
+
i += char.length;
|
|
89
|
+
}
|
|
90
|
+
return clean;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Pad a string with spaces to fill `targetWidth` visible columns.
|
|
94
|
+
* Accounts for CJK double-width characters.
|
|
95
|
+
*/
|
|
96
|
+
export function padEndToWidth(str, targetWidth) {
|
|
97
|
+
const gap = targetWidth - visibleLen(str);
|
|
98
|
+
return gap > 0 ? str + " ".repeat(gap) : str;
|
|
99
|
+
}
|
|
73
100
|
/** Strip all ANSI escape sequences (SGR, OSC, CSI, private mode) and carriage returns. */
|
|
74
101
|
export function stripAnsi(str) {
|
|
75
102
|
return str
|
|
@@ -20,7 +20,7 @@ export interface FloatingPanelConfig {
|
|
|
20
20
|
* Requires @xterm/headless — falls back to blank background if unavailable.
|
|
21
21
|
*/
|
|
22
22
|
dimBackground?: boolean;
|
|
23
|
-
/** Auto-dismiss delay in ms when done (0 =
|
|
23
|
+
/** Auto-dismiss delay in ms when done (0 = auto-prompt for follow-up). Default: 0. */
|
|
24
24
|
autoDismissMs?: number;
|
|
25
25
|
/** Icon shown before the input cursor. Default: "\u276f". */
|
|
26
26
|
promptIcon?: string;
|
|
@@ -144,6 +144,7 @@ export declare class FloatingPanel {
|
|
|
144
144
|
* - `{prefix}:composite-row(content: string, bgLine: string|null, boxLeft: number, boxW: number, cols: number) -> string`
|
|
145
145
|
* - `{prefix}:submit(query: string) -> void`
|
|
146
146
|
* - `{prefix}:dismiss() -> void`
|
|
147
|
+
* - `{prefix}:show() -> void`
|
|
147
148
|
* - `{prefix}:input(data: string) -> boolean`
|
|
148
149
|
* - `{prefix}:build-row(content: string, width: number) -> string`
|
|
149
150
|
*/
|
|
@@ -153,29 +154,47 @@ export declare class FloatingPanel {
|
|
|
153
154
|
/** All byte sequences that should be recognized as the trigger key. */
|
|
154
155
|
private readonly triggerSeqs;
|
|
155
156
|
private phase;
|
|
157
|
+
private _visible;
|
|
158
|
+
private _passthrough;
|
|
156
159
|
private editor;
|
|
157
160
|
private contentLines;
|
|
158
161
|
private currentPartialLine;
|
|
159
162
|
private scrollOffset;
|
|
163
|
+
private userScrolled;
|
|
160
164
|
private title;
|
|
161
165
|
private footer;
|
|
162
166
|
private renderTimer;
|
|
163
|
-
private autoDismissTimer;
|
|
164
167
|
private resizeHandler;
|
|
165
168
|
private prevFrame;
|
|
166
169
|
private suppressNextRedraw;
|
|
170
|
+
private autoDismissTimer;
|
|
167
171
|
private ptyBuffer;
|
|
168
172
|
private usedAltScreen;
|
|
173
|
+
private wrapCache;
|
|
174
|
+
private wrapCacheWidth;
|
|
175
|
+
private passthroughTimer;
|
|
176
|
+
private prevSerialized;
|
|
169
177
|
constructor(bus: EventBus, config: FloatingPanelConfig, handlers?: HandlerRegistry);
|
|
170
178
|
private registerDefaultHandlers;
|
|
171
179
|
private wireEvents;
|
|
172
180
|
/** Check whether data matches any encoding of the trigger key. */
|
|
173
181
|
private isTrigger;
|
|
174
182
|
private ensureBuffer;
|
|
183
|
+
/** Whether the panel has an active conversation (may be hidden). */
|
|
175
184
|
get active(): boolean;
|
|
185
|
+
/** Whether the panel is currently visible on screen. */
|
|
186
|
+
get visible(): boolean;
|
|
176
187
|
get terminalBuffer(): TerminalBuffer | null;
|
|
188
|
+
/** Open a fresh panel with a new conversation. */
|
|
177
189
|
open(): void;
|
|
190
|
+
/** Hide the panel without destroying conversation state. */
|
|
191
|
+
hide(): void;
|
|
192
|
+
/** Show the panel again after hide(), preserving conversation. */
|
|
193
|
+
show(): void;
|
|
194
|
+
/** Fully destroy the panel, resetting all state. */
|
|
178
195
|
dismiss(): void;
|
|
196
|
+
/** Common screen enter logic shared by open() and show(). */
|
|
197
|
+
private enterScreen;
|
|
179
198
|
appendText(text: string): void;
|
|
180
199
|
appendLine(line: string): void;
|
|
181
200
|
updateLastLine(fn: (line: string) => string): void;
|
|
@@ -184,15 +203,25 @@ export declare class FloatingPanel {
|
|
|
184
203
|
setFooter(footer: string): void;
|
|
185
204
|
setActive(): void;
|
|
186
205
|
setDone(): void;
|
|
206
|
+
scrollUp(lines?: number): void;
|
|
207
|
+
scrollDown(lines?: number): void;
|
|
187
208
|
getInput(): string;
|
|
188
209
|
requestRender(): void;
|
|
189
210
|
private handleIntercept;
|
|
211
|
+
/** Handle scroll input. Returns true if consumed. */
|
|
212
|
+
private handleScroll;
|
|
190
213
|
private handleInputKey;
|
|
191
214
|
/** Compute box geometry from config + current terminal size. */
|
|
192
215
|
computeGeometry(): BoxGeometry;
|
|
193
216
|
private buildFrame;
|
|
194
217
|
private scheduleRender;
|
|
195
218
|
private render;
|
|
196
|
-
|
|
219
|
+
/** Full screen teardown: exit alt screen, release stdout, force redraw. */
|
|
220
|
+
private teardownScreen;
|
|
221
|
+
/** Start rendering TerminalBuffer directly (no overlay box). */
|
|
222
|
+
private startPassthrough;
|
|
223
|
+
private stopPassthrough;
|
|
224
|
+
/** Render the TerminalBuffer's screen content directly (no overlay). */
|
|
225
|
+
private renderPassthrough;
|
|
197
226
|
private resolveSize;
|
|
198
227
|
}
|