agent-sh 0.10.0 → 0.10.2
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 +12 -9
- package/dist/agent/agent-loop.d.ts +0 -3
- package/dist/agent/agent-loop.js +18 -35
- package/dist/agent/conversation-state.js +8 -2
- package/dist/agent/nuclear-form.d.ts +2 -0
- package/dist/agent/nuclear-form.js +11 -1
- package/dist/agent/system-prompt.js +1 -1
- package/dist/agent/token-budget.d.ts +8 -12
- package/dist/agent/token-budget.js +5 -40
- package/dist/agent/tool-registry.js +6 -0
- package/dist/agent/types.d.ts +3 -1
- package/dist/context-manager.d.ts +1 -21
- package/dist/context-manager.js +26 -163
- package/dist/event-bus.d.ts +0 -1
- package/dist/extension-loader.js +25 -4
- package/dist/extensions/agent-backend.js +3 -2
- package/dist/extensions/index.js +0 -1
- package/dist/extensions/tui-renderer.js +47 -29
- package/dist/settings.d.ts +3 -11
- package/dist/settings.js +0 -4
- package/dist/shell/input-handler.js +14 -9
- package/dist/types.d.ts +3 -0
- package/dist/utils/ansi.d.ts +6 -1
- package/dist/utils/ansi.js +114 -7
- package/dist/utils/box-frame.js +8 -2
- package/dist/utils/llm-client.d.ts +4 -0
- package/dist/utils/llm-client.js +8 -0
- package/dist/utils/markdown.d.ts +4 -0
- package/dist/utils/markdown.js +136 -48
- package/dist/utils/package-version.d.ts +1 -0
- package/dist/utils/package-version.js +10 -0
- package/dist/utils/shell-output-spill.d.ts +2 -0
- package/dist/utils/shell-output-spill.js +81 -0
- package/examples/extensions/claude-code-bridge/README.md +14 -0
- package/examples/extensions/claude-code-bridge/index.ts +13 -101
- package/examples/extensions/pi-bridge/README.md +16 -0
- package/examples/extensions/pi-bridge/index.ts +8 -154
- package/package.json +9 -1
- package/dist/extensions/shell-recall.d.ts +0 -9
- package/dist/extensions/shell-recall.js +0 -8
package/README.md
CHANGED
|
@@ -4,6 +4,7 @@ An agent that lives in a shell — not a shell that lives in an agent.
|
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/agent-sh)
|
|
6
6
|
[](https://github.com/guanyilun/agent-sh/blob/main/LICENSE)
|
|
7
|
+
[](https://agent-sh.dev)
|
|
7
8
|
|
|
8
9
|

|
|
9
10
|
|
|
@@ -44,7 +45,7 @@ Requires Node.js 18+.
|
|
|
44
45
|
|
|
45
46
|
**Context that just works.** Every query includes your cwd, recent commands, and their output. Run a failing test, type `> fix this`, and agent-sh knows exactly what happened. Context management works like shell history — continuous, persistent across restarts, no sessions to manage. See [Context Management](docs/context-management.md).
|
|
46
47
|
|
|
47
|
-
**Any LLM, any backend.** agent-sh works with any OpenAI-compatible API out of the box. Define multiple providers in settings and
|
|
48
|
+
**Any LLM, any backend.** agent-sh works with any OpenAI-compatible API out of the box. Define multiple providers in settings and switch models at runtime with `/model <name>`. Or swap in a completely different agent — [Claude Code](examples/extensions/claude-code-bridge/) and [pi](examples/extensions/pi-bridge/) run as drop-in backend extensions.
|
|
48
49
|
|
|
49
50
|
**Extensible by design.** The entire system is built on a typed event bus. Extensions can add custom input modes, content transforms (render LaTeX as images, Mermaid as diagrams), themes, slash commands, or replace the agent backend entirely. The built-in TUI renderer is itself just an extension.
|
|
50
51
|
|
|
@@ -52,14 +53,16 @@ Requires Node.js 18+.
|
|
|
52
53
|
|
|
53
54
|
## Documentation
|
|
54
55
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
56
|
+
Start with **Usage** to get running, then **Architecture** for the mental model.
|
|
57
|
+
|
|
58
|
+
1. [Usage Guide](docs/usage.md) — install, run, configure providers and models
|
|
59
|
+
2. [Architecture](docs/architecture.md) — pure kernel + extensions, the shell ↔ agent boundary
|
|
60
|
+
3. [The Built-in Agent: ash](docs/agent.md) — query flow, tools, system prompt, model switching
|
|
61
|
+
4. [Context Management](docs/context-management.md) — shell-output spill, three-tier conversation compaction, recall APIs
|
|
62
|
+
5. [Extensions](docs/extensions.md) — event bus, content transforms, custom agent backends, theming
|
|
63
|
+
6. [TUI Composition](docs/tui-composition.md) — compositor, render surfaces, stream routing
|
|
64
|
+
7. [Library Usage](docs/library.md) — embedding agent-sh in your own apps
|
|
65
|
+
8. [Troubleshooting](docs/troubleshooting.md) — common errors and debug mode
|
|
63
66
|
|
|
64
67
|
## Development
|
|
65
68
|
|
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
* Subscribes to bus events in constructor:
|
|
5
5
|
* - agent:submit → run query through LLM tool loop
|
|
6
6
|
* - agent:cancel-request → abort current loop
|
|
7
|
-
* - config:cycle → cycle through modes
|
|
8
7
|
*
|
|
9
8
|
* Emits bus events during execution:
|
|
10
9
|
* - agent:query, agent:processing-start/done, agent:response-chunk/done
|
|
@@ -36,7 +35,6 @@ export declare class AgentLoop implements AgentBackend {
|
|
|
36
35
|
private historyFile;
|
|
37
36
|
private conversation;
|
|
38
37
|
private fileReadCache;
|
|
39
|
-
private tokenBudget;
|
|
40
38
|
private modes;
|
|
41
39
|
private currentModeIndex;
|
|
42
40
|
private boundListeners;
|
|
@@ -104,7 +102,6 @@ export declare class AgentLoop implements AgentBackend {
|
|
|
104
102
|
private cancel;
|
|
105
103
|
/** Check if reasoning_effort should be sent for the current model/provider. */
|
|
106
104
|
private shouldSendReasoningEffort;
|
|
107
|
-
private cycleMode;
|
|
108
105
|
private get currentMode();
|
|
109
106
|
private get currentModel();
|
|
110
107
|
/**
|
package/dist/agent/agent-loop.js
CHANGED
|
@@ -8,7 +8,8 @@ import { HistoryFile } from "./history-file.js";
|
|
|
8
8
|
import { nucleate, formatNuclearLine, isReadOnly } from "./nuclear-form.js";
|
|
9
9
|
import { STATIC_SYSTEM_PROMPT, buildDynamicContext, buildStaticByCwd, formatSkillsBlock, loadGlobalAgentsMd } from "./system-prompt.js";
|
|
10
10
|
import { createToolUI } from "../utils/tool-interactive.js";
|
|
11
|
-
import {
|
|
11
|
+
import { RESPONSE_RESERVE, DEFAULT_CONTEXT_WINDOW } from "./token-budget.js";
|
|
12
|
+
import { PACKAGE_VERSION } from "../utils/package-version.js";
|
|
12
13
|
import { getSettings, updateSettings } from "../settings.js";
|
|
13
14
|
import { createToolProtocol } from "./tool-protocol.js";
|
|
14
15
|
// Core tool factories
|
|
@@ -40,7 +41,6 @@ export class AgentLoop {
|
|
|
40
41
|
historyFile;
|
|
41
42
|
conversation;
|
|
42
43
|
fileReadCache = new Map();
|
|
43
|
-
tokenBudget;
|
|
44
44
|
modes;
|
|
45
45
|
currentModeIndex = 0;
|
|
46
46
|
boundListeners = [];
|
|
@@ -105,8 +105,6 @@ export class AgentLoop {
|
|
|
105
105
|
? config.modes
|
|
106
106
|
: [{ model: config.llmClient.model }];
|
|
107
107
|
this.currentModeIndex = config.initialModeIndex ?? 0;
|
|
108
|
-
// Unified token budget — adapts to current model's context window
|
|
109
|
-
this.tokenBudget = new TokenBudget(this.currentMode.contextWindow);
|
|
110
108
|
// Tool protocol — controls how tools are presented to the LLM
|
|
111
109
|
this.toolProtocol = createToolProtocol(getSettings().toolMode ?? "api");
|
|
112
110
|
// Register core tools
|
|
@@ -115,8 +113,6 @@ export class AgentLoop {
|
|
|
115
113
|
const protocolTools = this.toolProtocol.getProtocolTools?.() ?? [];
|
|
116
114
|
for (const t of protocolTools)
|
|
117
115
|
this.registerTool(t);
|
|
118
|
-
// Update token budget with tool count
|
|
119
|
-
this.tokenBudget.update(undefined, this.toolRegistry.all().length);
|
|
120
116
|
// Register handlers — extensions can advise these
|
|
121
117
|
this.registerHandlers();
|
|
122
118
|
// Subscribe to bus-based tool/instruction registration from extensions.
|
|
@@ -165,7 +161,6 @@ export class AgentLoop {
|
|
|
165
161
|
else {
|
|
166
162
|
this.llmClient.model = m.model;
|
|
167
163
|
}
|
|
168
|
-
this.tokenBudget.update(m.contextWindow, this.toolRegistry.all().length);
|
|
169
164
|
this.bus.emit("config:changed", {});
|
|
170
165
|
});
|
|
171
166
|
const getToolsPipe = () => ({ tools: this.getTools() });
|
|
@@ -184,7 +179,6 @@ export class AgentLoop {
|
|
|
184
179
|
on("agent:cancel-request", (e) => {
|
|
185
180
|
this.abortController?.abort(e.silent ? "silent" : undefined);
|
|
186
181
|
});
|
|
187
|
-
on("config:cycle", () => this.cycleMode());
|
|
188
182
|
on("config:switch-model", ({ model: target }) => {
|
|
189
183
|
const idx = this.modes.findIndex((m) => m.model === target);
|
|
190
184
|
if (idx === -1) {
|
|
@@ -199,9 +193,8 @@ export class AgentLoop {
|
|
|
199
193
|
else {
|
|
200
194
|
this.llmClient.model = m.model;
|
|
201
195
|
}
|
|
202
|
-
this.tokenBudget.update(m.contextWindow, this.toolRegistry.all().length);
|
|
203
196
|
const label = m.provider ? `${m.provider}: ${m.model}` : m.model;
|
|
204
|
-
this.bus.emit("agent:info", { name: "ash", version:
|
|
197
|
+
this.bus.emit("agent:info", { name: "ash", version: PACKAGE_VERSION, model: m.model, provider: m.provider, contextWindow: m.contextWindow });
|
|
205
198
|
// Persist as the new default — selection survives restart.
|
|
206
199
|
// Safe even for dynamic providers: agent-backend defers mode
|
|
207
200
|
// resolution to `core:extensions-loaded`, so the extension gets
|
|
@@ -287,6 +280,9 @@ export class AgentLoop {
|
|
|
287
280
|
if (beforeTokens > this.peakConversationTokens) {
|
|
288
281
|
this.peakConversationTokens = beforeTokens;
|
|
289
282
|
}
|
|
283
|
+
// The "File unchanged" stub assumes the prior read output is still
|
|
284
|
+
// in context; compaction can evict it. Clear so the next read re-emits.
|
|
285
|
+
this.fileReadCache.clear();
|
|
290
286
|
});
|
|
291
287
|
on("shell:cwd-change", ({ cwd }) => {
|
|
292
288
|
const projectSkills = discoverProjectSkills(cwd);
|
|
@@ -428,30 +424,6 @@ export class AgentLoop {
|
|
|
428
424
|
return false;
|
|
429
425
|
return true;
|
|
430
426
|
}
|
|
431
|
-
cycleMode() {
|
|
432
|
-
const prevMode = this.modes[this.currentModeIndex];
|
|
433
|
-
this.currentModeIndex =
|
|
434
|
-
(this.currentModeIndex + 1) % this.modes.length;
|
|
435
|
-
const newMode = this.modes[this.currentModeIndex];
|
|
436
|
-
// Reconfigure LlmClient if provider changed
|
|
437
|
-
if (newMode.provider !== prevMode.provider && newMode.providerConfig) {
|
|
438
|
-
this.llmClient.reconfigure({
|
|
439
|
-
apiKey: newMode.providerConfig.apiKey,
|
|
440
|
-
baseURL: newMode.providerConfig.baseURL,
|
|
441
|
-
model: newMode.model,
|
|
442
|
-
});
|
|
443
|
-
}
|
|
444
|
-
else {
|
|
445
|
-
this.llmClient.model = newMode.model;
|
|
446
|
-
}
|
|
447
|
-
this.tokenBudget.update(newMode.contextWindow, this.toolRegistry.all().length);
|
|
448
|
-
const label = newMode.provider
|
|
449
|
-
? `${newMode.provider}: ${newMode.model}`
|
|
450
|
-
: newMode.model;
|
|
451
|
-
this.bus.emit("agent:info", { name: "ash", version: "0.4", model: newMode.model, provider: newMode.provider, contextWindow: newMode.contextWindow });
|
|
452
|
-
this.bus.emit("ui:info", { message: `Model: ${label}` });
|
|
453
|
-
this.bus.emit("config:changed", {});
|
|
454
|
-
}
|
|
455
427
|
get currentMode() {
|
|
456
428
|
return this.modes[this.currentModeIndex];
|
|
457
429
|
}
|
|
@@ -1066,7 +1038,18 @@ export class AgentLoop {
|
|
|
1066
1038
|
const contextWindow = this.currentMode.contextWindow ?? DEFAULT_CONTEXT_WINDOW;
|
|
1067
1039
|
const threshold = Math.floor((contextWindow - RESPONSE_RESERVE) * getSettings().autoCompactThreshold);
|
|
1068
1040
|
if (totalEstimate > threshold) {
|
|
1069
|
-
|
|
1041
|
+
// Compact deeply — shallow targets buy only 1–2 turns of runway on
|
|
1042
|
+
// tool-heavy workloads.
|
|
1043
|
+
const target = Math.floor(threshold * 0.25);
|
|
1044
|
+
const result = this.compactWithHooks(target, 6);
|
|
1045
|
+
if (!result) {
|
|
1046
|
+
// Auto-compact fired but nothing was evictable. This can happen
|
|
1047
|
+
// in short conversations with heavy tool output where the pin
|
|
1048
|
+
// fraction consumes all turns. Log it so it's not silent.
|
|
1049
|
+
this.bus.emit("ui:info", {
|
|
1050
|
+
message: `[auto-compact] above threshold (${totalEstimate.toLocaleString()} > ${threshold.toLocaleString()}) but nothing to evict — conversation may be too short`,
|
|
1051
|
+
});
|
|
1052
|
+
}
|
|
1070
1053
|
cachedSystemPrompt = undefined;
|
|
1071
1054
|
}
|
|
1072
1055
|
const currentCwd = this.contextManager.getCwd();
|
|
@@ -217,12 +217,18 @@ export class ConversationState {
|
|
|
217
217
|
if (!force && convEstimate <= convTarget)
|
|
218
218
|
return null;
|
|
219
219
|
const turns = this.parseTurns();
|
|
220
|
-
|
|
220
|
+
// With force, allow compacting down to 1 turn (the current response).
|
|
221
|
+
// Without force, keep at least 2 turns (user + agent) to avoid
|
|
222
|
+
// annihilating a young conversation.
|
|
223
|
+
if (turns.length <= (force ? 1 : 2))
|
|
221
224
|
return null;
|
|
222
225
|
// Cap the pinned window so enough turns remain evictable.
|
|
223
226
|
const maxPinnedFraction = force ? 0.4 : 0.6;
|
|
224
227
|
const maxPinned = Math.max(2, Math.floor(turns.length * maxPinnedFraction));
|
|
225
|
-
|
|
228
|
+
// Ensure at least 1 turn is evictable when force is true, even in
|
|
229
|
+
// very short conversations (e.g. 3 turns with heavy tool output).
|
|
230
|
+
const maxPinnedForced = force ? Math.min(maxPinned, turns.length - 2) : maxPinned;
|
|
231
|
+
const pinnedCount = Math.min(recentTurnsToKeep, turns.length - 1, Math.max(1, maxPinnedForced));
|
|
226
232
|
for (let i = 0; i < turns.length; i++) {
|
|
227
233
|
turns[i].priority = this.inferPriority(turns[i].messages);
|
|
228
234
|
}
|
|
@@ -43,6 +43,8 @@ export declare function createSessionMarker(iid: string, seq?: number): NuclearE
|
|
|
43
43
|
export declare function isSessionMarker(entry: NuclearEntry): boolean;
|
|
44
44
|
/** Read-only tools whose results are dropped at Tier 1→2 (agent can re-read). */
|
|
45
45
|
export declare const READ_ONLY_TOOLS: Set<string>;
|
|
46
|
+
export declare function registerReadOnlyTool(name: string): void;
|
|
47
|
+
export declare function unregisterReadOnlyTool(name: string): void;
|
|
46
48
|
/** State-changing tools whose summaries are kept in nuclear memory. */
|
|
47
49
|
export declare const WRITE_TOOLS: Set<string>;
|
|
48
50
|
/**
|
|
@@ -15,6 +15,14 @@ export function isSessionMarker(entry) {
|
|
|
15
15
|
export const READ_ONLY_TOOLS = new Set([
|
|
16
16
|
"read_file", "grep", "glob", "ls", "search",
|
|
17
17
|
]);
|
|
18
|
+
/** Extensions opt their tools in via ToolRegistry.register when readOnly is set. */
|
|
19
|
+
const extraReadOnlyTools = new Set();
|
|
20
|
+
export function registerReadOnlyTool(name) {
|
|
21
|
+
extraReadOnlyTools.add(name);
|
|
22
|
+
}
|
|
23
|
+
export function unregisterReadOnlyTool(name) {
|
|
24
|
+
extraReadOnlyTools.delete(name);
|
|
25
|
+
}
|
|
18
26
|
/** State-changing tools whose summaries are kept in nuclear memory. */
|
|
19
27
|
export const WRITE_TOOLS = new Set([
|
|
20
28
|
"write_file", "edit_file", "write", "edit", "patch",
|
|
@@ -188,7 +196,9 @@ export function deserializeEntry(line) {
|
|
|
188
196
|
// ── Classification helpers ────────────────────────────────────────
|
|
189
197
|
/** Check if a nuclear entry represents a read-only action (should be dropped). */
|
|
190
198
|
export function isReadOnly(entry) {
|
|
191
|
-
|
|
199
|
+
if (entry.kind !== "tool" || entry.tool == null)
|
|
200
|
+
return false;
|
|
201
|
+
return READ_ONLY_TOOLS.has(entry.tool) || extraReadOnlyTools.has(entry.tool);
|
|
192
202
|
}
|
|
193
203
|
// ── Internal helpers ──────────────────────────────────────────────
|
|
194
204
|
function truncate(text, maxLen) {
|
|
@@ -89,7 +89,7 @@ function loadConventionFiles(dir) {
|
|
|
89
89
|
* Static system prompt — identical across all queries, cacheable.
|
|
90
90
|
* Contains only identity and behavioral instructions.
|
|
91
91
|
*/
|
|
92
|
-
export const STATIC_SYSTEM_PROMPT = `You are an AI coding assistant running inside agent-sh, a terminal shell.
|
|
92
|
+
export const STATIC_SYSTEM_PROMPT = `You are ash, an AI coding assistant running inside agent-sh, a terminal shell.
|
|
93
93
|
You have access to the user's shell environment and can read, write, and execute code.
|
|
94
94
|
You share the user's working directory, environment variables, and shell history.
|
|
95
95
|
agent-sh documentation is at ${path.join(CODE_DIR, "docs")} — start with README.md for an index. Read the docs when you need to understand how the runtime works.
|
|
@@ -1,14 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared token-budget constants used by auto-compaction.
|
|
3
|
+
*
|
|
4
|
+
* RESPONSE_RESERVE: tokens reserved for the model's output.
|
|
5
|
+
* DEFAULT_CONTEXT_WINDOW: fallback when the active mode doesn't declare one.
|
|
6
|
+
*/
|
|
1
7
|
/** Response reserve — tokens reserved for the model's output. */
|
|
2
|
-
declare const RESPONSE_RESERVE = 8192;
|
|
8
|
+
export declare const RESPONSE_RESERVE = 8192;
|
|
3
9
|
/** Fallback when contextWindow is unknown. */
|
|
4
|
-
declare const DEFAULT_CONTEXT_WINDOW = 60000;
|
|
5
|
-
export { RESPONSE_RESERVE, DEFAULT_CONTEXT_WINDOW };
|
|
6
|
-
export declare class TokenBudget {
|
|
7
|
-
private contextWindow;
|
|
8
|
-
private toolCount;
|
|
9
|
-
constructor(contextWindow?: number, toolCount?: number);
|
|
10
|
-
/** Update when model or tool set changes. */
|
|
11
|
-
update(contextWindow?: number, toolCount?: number): void;
|
|
12
|
-
/** Token budget for the shell context stream. */
|
|
13
|
-
get shellBudgetTokens(): number;
|
|
14
|
-
}
|
|
10
|
+
export declare const DEFAULT_CONTEXT_WINDOW = 60000;
|
|
@@ -1,45 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Shared token-budget constants used by auto-compaction.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* space is for the conversation, system prompt, tools, and response.
|
|
7
|
-
*
|
|
8
|
-
* Shell context is sized loosely — chars/4 accuracy is fine for this.
|
|
9
|
-
* Conversation and compaction decisions use API-grounded token counts
|
|
10
|
-
* (see ConversationState.estimatePromptTokens).
|
|
4
|
+
* RESPONSE_RESERVE: tokens reserved for the model's output.
|
|
5
|
+
* DEFAULT_CONTEXT_WINDOW: fallback when the active mode doesn't declare one.
|
|
11
6
|
*/
|
|
12
|
-
import { getSettings } from "../settings.js";
|
|
13
|
-
const SYSTEM_PROMPT_OVERHEAD = 800;
|
|
14
|
-
const DYNAMIC_CONTEXT_OVERHEAD = 500; // conventions, metadata, skills list
|
|
15
|
-
const TOKENS_PER_TOOL_DEFINITION = 50;
|
|
16
7
|
/** Response reserve — tokens reserved for the model's output. */
|
|
17
|
-
const RESPONSE_RESERVE = 8192;
|
|
8
|
+
export const RESPONSE_RESERVE = 8192;
|
|
18
9
|
/** Fallback when contextWindow is unknown. */
|
|
19
|
-
const DEFAULT_CONTEXT_WINDOW = 60_000;
|
|
20
|
-
export { RESPONSE_RESERVE, DEFAULT_CONTEXT_WINDOW };
|
|
21
|
-
export class TokenBudget {
|
|
22
|
-
contextWindow;
|
|
23
|
-
toolCount;
|
|
24
|
-
constructor(contextWindow, toolCount = 0) {
|
|
25
|
-
this.contextWindow = contextWindow ?? DEFAULT_CONTEXT_WINDOW;
|
|
26
|
-
this.toolCount = toolCount;
|
|
27
|
-
}
|
|
28
|
-
/** Update when model or tool set changes. */
|
|
29
|
-
update(contextWindow, toolCount) {
|
|
30
|
-
if (contextWindow != null)
|
|
31
|
-
this.contextWindow = contextWindow;
|
|
32
|
-
if (toolCount != null)
|
|
33
|
-
this.toolCount = toolCount;
|
|
34
|
-
}
|
|
35
|
-
/** Token budget for the shell context stream. */
|
|
36
|
-
get shellBudgetTokens() {
|
|
37
|
-
const overhead = SYSTEM_PROMPT_OVERHEAD +
|
|
38
|
-
DYNAMIC_CONTEXT_OVERHEAD +
|
|
39
|
-
this.toolCount * TOKENS_PER_TOOL_DEFINITION +
|
|
40
|
-
RESPONSE_RESERVE;
|
|
41
|
-
const contentBudget = Math.max(0, this.contextWindow - overhead);
|
|
42
|
-
const ratio = getSettings().shellContextRatio;
|
|
43
|
-
return Math.floor(contentBudget * ratio);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
10
|
+
export const DEFAULT_CONTEXT_WINDOW = 60_000;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { registerReadOnlyTool, unregisterReadOnlyTool } from "./nuclear-form.js";
|
|
1
2
|
/**
|
|
2
3
|
* Registry for agent tools. Holds tool definitions and converts them
|
|
3
4
|
* to OpenAI-compatible function schemas for API calls.
|
|
@@ -6,9 +7,14 @@ export class ToolRegistry {
|
|
|
6
7
|
tools = new Map();
|
|
7
8
|
register(tool) {
|
|
8
9
|
this.tools.set(tool.name, tool);
|
|
10
|
+
if (tool.readOnly)
|
|
11
|
+
registerReadOnlyTool(tool.name);
|
|
12
|
+
else
|
|
13
|
+
unregisterReadOnlyTool(tool.name);
|
|
9
14
|
}
|
|
10
15
|
unregister(name) {
|
|
11
16
|
this.tools.delete(name);
|
|
17
|
+
unregisterReadOnlyTool(name);
|
|
12
18
|
}
|
|
13
19
|
get(name) {
|
|
14
20
|
return this.tools.get(name);
|
package/dist/agent/types.d.ts
CHANGED
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
* Backends self-wire to bus events in their constructor:
|
|
5
5
|
* - agent:submit → handle queries
|
|
6
6
|
* - agent:cancel-request → handle cancellation
|
|
7
|
-
* - config:cycle → handle mode switching
|
|
8
7
|
*
|
|
9
8
|
* They emit bus events for results:
|
|
10
9
|
* - agent:response-chunk, agent:tool-started, agent:tool-completed, etc.
|
|
@@ -78,6 +77,9 @@ export interface ToolDefinition {
|
|
|
78
77
|
showOutput?: boolean;
|
|
79
78
|
/** Whether this tool may modify files — triggers file watcher (default: false). */
|
|
80
79
|
modifiesFiles?: boolean;
|
|
80
|
+
/** Results are re-fetchable; nuclear compaction drops the tool_result
|
|
81
|
+
* body on eviction (like the builtin read_file/grep/ls). Default: false. */
|
|
82
|
+
readOnly?: boolean;
|
|
81
83
|
/** Whether to gate execution via permission:request (default: false). */
|
|
82
84
|
requiresPermission?: boolean;
|
|
83
85
|
/** Derive display metadata (icon kind, file paths) for the TUI. */
|
|
@@ -4,26 +4,13 @@ export declare class ContextManager {
|
|
|
4
4
|
private exchanges;
|
|
5
5
|
private nextId;
|
|
6
6
|
private currentCwd;
|
|
7
|
-
private sessionStart;
|
|
8
|
-
private firstPrompt;
|
|
9
7
|
private agentShellActive;
|
|
10
|
-
|
|
11
|
-
constructor(bus: EventBus, handlers?: HandlerRegistry);
|
|
8
|
+
constructor(bus: EventBus, _handlers?: HandlerRegistry);
|
|
12
9
|
getCwd(): string;
|
|
13
|
-
/**
|
|
14
|
-
* Build the <shell_context> block for the agent prompt.
|
|
15
|
-
* Pipeline: window → truncate → format
|
|
16
|
-
*/
|
|
17
|
-
getContext(budget?: number): string;
|
|
18
10
|
/**
|
|
19
11
|
* Regex/keyword search across all exchanges. Returns formatted results.
|
|
20
12
|
*/
|
|
21
13
|
search(query: string): string;
|
|
22
|
-
/**
|
|
23
|
-
* Return content for specific exchange IDs.
|
|
24
|
-
* Optional start/end restrict to a line range (1-indexed).
|
|
25
|
-
*/
|
|
26
|
-
expand(ids: number[], start?: number, end?: number): string;
|
|
27
14
|
/**
|
|
28
15
|
* Return shell events with id > afterId, formatted as an incremental
|
|
29
16
|
* delta suitable for injection into conversation history. Skips
|
|
@@ -45,17 +32,10 @@ export declare class ContextManager {
|
|
|
45
32
|
* One-line summaries of last N exchanges.
|
|
46
33
|
*/
|
|
47
34
|
getRecentSummary(n?: number): string;
|
|
48
|
-
/**
|
|
49
|
-
* Parse and handle shell_recall commands.
|
|
50
|
-
*/
|
|
51
|
-
handleRecallCommand(command: string): string;
|
|
52
35
|
/**
|
|
53
36
|
* Clear exchange history (used by /clear command).
|
|
54
37
|
*/
|
|
55
38
|
clear(): void;
|
|
56
|
-
private applyWindow;
|
|
57
|
-
private applyTruncation;
|
|
58
|
-
private formatContext;
|
|
59
39
|
private addExchange;
|
|
60
40
|
private formatExchangeTruncated;
|
|
61
41
|
private formatExchangeFull;
|