agent-sh 0.15.6 → 0.15.7
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/LICENSE +21 -0
- package/README.md +1 -1
- package/dist/agent/agent-loop.js +2 -5
- package/dist/agent/extensions/rolling-history/index.js +20 -8
- package/dist/agent/extensions/rolling-history/recall.d.ts +2 -2
- package/dist/agent/extensions/rolling-history/recall.js +17 -7
- package/dist/agent/providers/openai-compatible.d.ts +8 -0
- package/dist/agent/providers/openai-compatible.js +9 -2
- package/dist/agent/store.js +6 -1
- package/dist/agent/token-budget.d.ts +2 -1
- package/dist/agent/token-budget.js +6 -1
- package/dist/cli/index.js +1 -1
- package/dist/core/event-bus.d.ts +16 -1
- package/dist/core/event-bus.js +73 -11
- package/dist/core/index.js +18 -0
- package/dist/shell/tui-renderer.js +115 -174
- package/dist/utils/executor.js +19 -11
- package/dist/utils/floating-panel.d.ts +1 -0
- package/dist/utils/floating-panel.js +28 -26
- package/dist/utils/markdown.js +19 -21
- package/dist/utils/palette.d.ts +11 -0
- package/dist/utils/palette.js +11 -0
- package/docs/agent.md +13 -11
- package/docs/architecture.md +3 -5
- package/docs/extensions.md +21 -20
- package/docs/library.md +6 -3
- package/docs/troubleshooting.md +2 -2
- package/docs/tui-composition.md +11 -3
- package/docs/usage.md +70 -50
- package/examples/extensions/ashi/src/chat/assistant.ts +6 -4
- package/examples/extensions/ashi/src/compaction.ts +4 -7
- package/examples/extensions/ashi/src/frontend.ts +2 -0
- package/examples/extensions/ashi/src/schema.ts +8 -2
- package/examples/extensions/command-suggest.ts +4 -0
- package/examples/extensions/solarized-theme.ts +11 -0
- package/package.json +1 -1
- package/src/agent/agent-loop.ts +2 -5
- package/src/agent/extensions/rolling-history/index.ts +20 -8
- package/src/agent/extensions/rolling-history/recall.ts +28 -7
- package/src/agent/providers/openai-compatible.ts +19 -4
- package/src/agent/store.ts +5 -1
- package/src/agent/token-budget.ts +10 -1
- package/src/cli/index.ts +1 -1
- package/src/core/event-bus.ts +67 -12
- package/src/core/index.ts +18 -0
- package/src/shell/tui-renderer.ts +130 -207
- package/src/utils/executor.ts +17 -14
- package/src/utils/floating-panel.ts +24 -22
- package/src/utils/markdown.ts +17 -20
- package/src/utils/palette.ts +30 -5
package/dist/utils/palette.d.ts
CHANGED
|
@@ -23,7 +23,18 @@ export interface ColorPalette {
|
|
|
23
23
|
dim: string;
|
|
24
24
|
italic: string;
|
|
25
25
|
underline: string;
|
|
26
|
+
strikethrough: string;
|
|
26
27
|
reset: string;
|
|
28
|
+
mdHeading: string;
|
|
29
|
+
mdLink: string;
|
|
30
|
+
mdLinkUrl: string;
|
|
31
|
+
mdCode: string;
|
|
32
|
+
mdCodeBlock: string;
|
|
33
|
+
mdCodeBlockBorder: string;
|
|
34
|
+
mdQuote: string;
|
|
35
|
+
mdQuoteBorder: string;
|
|
36
|
+
mdHr: string;
|
|
37
|
+
mdListBullet: string;
|
|
27
38
|
}
|
|
28
39
|
/** Active palette — import and use directly in components. */
|
|
29
40
|
export declare const palette: ColorPalette;
|
package/dist/utils/palette.js
CHANGED
|
@@ -23,7 +23,18 @@ const defaultPalette = {
|
|
|
23
23
|
dim: "\x1b[2m",
|
|
24
24
|
italic: "\x1b[3m",
|
|
25
25
|
underline: "\x1b[4m",
|
|
26
|
+
strikethrough: "\x1b[9m",
|
|
26
27
|
reset: "\x1b[0m",
|
|
28
|
+
mdHeading: "\x1b[38;2;240;198;116m", // #f0c674 gold
|
|
29
|
+
mdLink: "\x1b[38;2;129;162;190m", // #81a2be blue
|
|
30
|
+
mdLinkUrl: "\x1b[38;2;102;102;102m", // #666666 dim gray
|
|
31
|
+
mdCode: "\x1b[38;2;138;190;183m", // #8abeb7 teal
|
|
32
|
+
mdCodeBlock: "\x1b[38;2;181;189;104m", // #b5bd68 green
|
|
33
|
+
mdCodeBlockBorder: "\x1b[38;2;128;128;128m", // #808080 gray
|
|
34
|
+
mdQuote: "\x1b[38;2;128;128;128m", // #808080 gray
|
|
35
|
+
mdQuoteBorder: "\x1b[38;2;128;128;128m", // #808080 gray
|
|
36
|
+
mdHr: "\x1b[38;2;128;128;128m", // #808080 gray
|
|
37
|
+
mdListBullet: "\x1b[38;2;138;190;183m", // #8abeb7 teal
|
|
27
38
|
};
|
|
28
39
|
/** Active palette — import and use directly in components. */
|
|
29
40
|
export const palette = { ...defaultPalette };
|
package/docs/agent.md
CHANGED
|
@@ -43,14 +43,16 @@ Compaction is pluggable: the `conversation:compact` handler is advisable, so ext
|
|
|
43
43
|
|
|
44
44
|
The system prompt is assembled once per `cwd` and cached (invalidated when the working directory changes), so the prefix is stable for provider-side prompt caching. It includes:
|
|
45
45
|
|
|
46
|
-
1. **Identity** — "You are an AI coding assistant running inside agent-sh..."
|
|
47
|
-
2. **
|
|
48
|
-
3. **
|
|
49
|
-
4. **
|
|
50
|
-
5. **
|
|
51
|
-
6. **
|
|
52
|
-
7. **
|
|
53
|
-
8. **
|
|
46
|
+
1. **Identity** — "You are ash, an AI coding assistant running inside agent-sh..." (advisable via `system-prompt:identity`)
|
|
47
|
+
2. **Frontend surface** — the active frontend's self-description, placed right after the identity (advisable via `system-prompt:frontend`; omitted when none)
|
|
48
|
+
3. **Static guide** — agent-sh's own code map (paths to `docs/`, `src/`, `examples/extensions/`), generic tool guidance, and the `<query_context>`/`<dynamic_context>` envelope contract
|
|
49
|
+
4. **Global memory** — `~/.agent-sh/AGENTS.md`, if present
|
|
50
|
+
5. **Global skills** — discovered global skills (cwd-stable)
|
|
51
|
+
6. **Project conventions + skills** — `CLAUDE.md`/`AGENT.md` walked from cwd to root, plus discovered project skills (cwd-stable; see next section)
|
|
52
|
+
7. **Extension instructions** — blocks registered by extensions via `registerInstruction()` (e.g. proactive recall guidance)
|
|
53
|
+
8. **Image support** — appended when the active model accepts image input
|
|
54
|
+
|
|
55
|
+
Built-in tools are not inlined here — they're passed to the provider via the API `tools` parameter. Extensions can advise `system-prompt:build` directly to append further context (instance IDs, memory files, etc.).
|
|
54
56
|
|
|
55
57
|
Per-turn signals live in two symmetric handlers, both empty by default:
|
|
56
58
|
|
|
@@ -218,7 +220,7 @@ When the LLM requests multiple tool calls in a single response, the agent groups
|
|
|
218
220
|
|
|
219
221
|
2. **Parallel execution** — side-effect-free tools (`modifiesFiles` unset) run in parallel via `Promise.all`. Side-effecting tools run sequentially.
|
|
220
222
|
|
|
221
|
-
3. **Output truncation** — tool results over
|
|
223
|
+
3. **Output truncation** — tool results over the tool's `maxResultBytes` (default 100KB, ~25K tokens) are head+tail truncated (60/40 split) before being added to the conversation, preventing a single tool call from blowing through the context window.
|
|
222
224
|
|
|
223
225
|
### Structured result display
|
|
224
226
|
|
|
@@ -260,7 +262,7 @@ For OpenRouter, the flag is set automatically: model ids matching the built-in p
|
|
|
260
262
|
"echoReasoningPatterns": ["my-custom-deepseek-fork"],
|
|
261
263
|
"models": [
|
|
262
264
|
{ "id": "deepseek/deepseek-v3.2", "echoReasoning": false },
|
|
263
|
-
{ "id": "
|
|
265
|
+
{ "id": "z-ai/glm-5.1", "reasoning": true }
|
|
264
266
|
]
|
|
265
267
|
}
|
|
266
268
|
}
|
|
@@ -367,7 +369,7 @@ Each entry is a `(provider, model)` target — a serializable identity plus capa
|
|
|
367
369
|
|
|
368
370
|
```typescript
|
|
369
371
|
interface Model {
|
|
370
|
-
id: string; // model id, e.g. "
|
|
372
|
+
id: string; // model id, e.g. "deepseek/deepseek-v4-flash"
|
|
371
373
|
provider: string; // identity is the (provider, id) pair
|
|
372
374
|
contextWindow?: number; // per-model override for the auto-compact threshold
|
|
373
375
|
maxTokens?: number;
|
package/docs/architecture.md
CHANGED
|
@@ -20,7 +20,7 @@ index.ts — interactive terminal frontend:
|
|
|
20
20
|
├── Agent host (always activated via activateAgent(ctx) before built-ins load):
|
|
21
21
|
│ ash backend — provider resolution, LlmClient, lazy AgentLoop
|
|
22
22
|
│ core tools — bash/read/write/edit/grep/glob/ls/list_skills registered at activate time
|
|
23
|
-
│ built-in providers — openrouter, openai,
|
|
23
|
+
│ built-in providers — openrouter, openai, deepseek, ollama, zai-coding-plan, opencode (unconditional); openai-compatible when OPENAI_BASE_URL is set
|
|
24
24
|
│
|
|
25
25
|
├── Backend registry (owned by core; backends register via `agent:register-backend`):
|
|
26
26
|
│ core.activateBackend() — picks the named/persisted/first backend and calls its start()
|
|
@@ -28,7 +28,7 @@ index.ts — interactive terminal frontend:
|
|
|
28
28
|
├── Built-in extensions (loaded via declarative manifest, individually disableable):
|
|
29
29
|
│ shell-context — PTY exchange tracking, cwd advisor, <cwd>/<shell_events> producer
|
|
30
30
|
│ tui-renderer — markdown rendering, inline diffs, thinking display, spinner
|
|
31
|
-
│ slash-commands — /help, /model, /
|
|
31
|
+
│ slash-commands — /help, /model, /thinking, /backend, /reload (the ash backend adds /compact, /context)
|
|
32
32
|
│ file-autocomplete — @ file path completion
|
|
33
33
|
│
|
|
34
34
|
├── Shared utilities:
|
|
@@ -36,7 +36,6 @@ index.ts — interactive terminal frontend:
|
|
|
36
36
|
│ diff-renderer — syntax-highlighted diffs (split/unified/summary)
|
|
37
37
|
│ box-frame — bordered TUI panels
|
|
38
38
|
│ tool-display — width-adaptive tool call rendering + pure spinner
|
|
39
|
-
│ output-writer — OutputWriter interface (StdoutWriter, BufferWriter for tests)
|
|
40
39
|
│ stream-transform — content block transforms for response pipeline
|
|
41
40
|
│
|
|
42
41
|
└── User extensions (opt-in, loaded from -e flag / settings.json / extensions dir):
|
|
@@ -147,7 +146,7 @@ agent-sh/
|
|
|
147
146
|
│ │ ├── types.ts # AgentBackend, ToolDefinition, ToolResult
|
|
148
147
|
│ │ ├── agent-loop.ts # ash AgentLoop (constructed lazily in start())
|
|
149
148
|
│ │ ├── llm-client.ts, llm-facade.ts # ash LLM transport + ctx.agent.llm facade
|
|
150
|
-
│ │ ├── providers/ # openai, openrouter, deepseek, openai-compatible
|
|
149
|
+
│ │ ├── providers/ # openai, openrouter, deepseek, openai-compatible, ollama, zai-coding-plan, opencode
|
|
151
150
|
│ │ ├── token-budget.ts # Shared constants (RESPONSE_RESERVE, DEFAULT_CONTEXT_WINDOW)
|
|
152
151
|
│ │ ├── tool-registry.ts, tool-protocol.ts
|
|
153
152
|
│ │ ├── live-view.ts # In-memory messages array + compaction + recall archive
|
|
@@ -185,7 +184,6 @@ agent-sh/
|
|
|
185
184
|
│ ├── solarized-theme.ts # Theme example
|
|
186
185
|
│ ├── secret-guard.ts # Secret redaction
|
|
187
186
|
│ ├── latex-images.ts # LaTeX equation rendering
|
|
188
|
-
│ ├── ollama.ts # Ollama provider (local + cloud)
|
|
189
187
|
│ ├── claude-code-bridge/ # Claude Code SDK backend
|
|
190
188
|
│ ├── pi-bridge/ # Pi agent backend
|
|
191
189
|
│ ├── ash-mcp-bridge/ # MCP server bridge
|
package/docs/extensions.md
CHANGED
|
@@ -468,17 +468,17 @@ Per-request producers (`mode: "per-request"`) only fire under backends that expo
|
|
|
468
468
|
|
|
469
469
|
## Custom Providers
|
|
470
470
|
|
|
471
|
-
Providers describe the OpenAI-compatible endpoints the `ash` backend can talk to. The built-ins (openrouter, openai, openai-compatible, deepseek) register from `src/agent/providers/`; extensions can register their own — local daemons, hosted gateways, fine-tuned model catalogs — and they show up under `agent-sh auth list` and `/model`.
|
|
471
|
+
Providers describe the OpenAI-compatible endpoints the `ash` backend can talk to. The built-ins (openrouter, openai, openai-compatible, deepseek, ollama, zai-coding-plan, opencode) register from `src/agent/providers/`; extensions can register their own — local daemons, hosted gateways, fine-tuned model catalogs — and they show up under `agent-sh auth list` and `/model`.
|
|
472
472
|
|
|
473
473
|
```typescript
|
|
474
474
|
import type { AgentContext } from "agent-sh/types";
|
|
475
475
|
|
|
476
476
|
export default function activate(ctx: AgentContext): void {
|
|
477
477
|
ctx.agent.providers.register({
|
|
478
|
-
id: "
|
|
479
|
-
baseURL: "http://localhost:
|
|
480
|
-
defaultModel: "
|
|
481
|
-
models: ["
|
|
478
|
+
id: "llama-cpp",
|
|
479
|
+
baseURL: "http://localhost:8080/v1",
|
|
480
|
+
defaultModel: "gemma4",
|
|
481
|
+
models: ["gemma4"],
|
|
482
482
|
noAuth: true,
|
|
483
483
|
});
|
|
484
484
|
}
|
|
@@ -549,9 +549,6 @@ These are registered by AgentLoop (constructed when the ash backend's `start()`
|
|
|
549
549
|
| `conversation:estimate-tokens` | `() → number` | Local chars/4 estimate of the conversation size. |
|
|
550
550
|
| `conversation:estimate-prompt-tokens` | `() → number` | API-grounded estimate (last `prompt_tokens` + local delta since). Used by the auto-compact trigger. |
|
|
551
551
|
| `conversation:inject-note` | `(text) → void` | Inject a `role:"user"` note mid-loop — how extensions deliver async results (subagent output, peer messages) into the next iteration. |
|
|
552
|
-
| `conversation:nucleate-user` / `-agent` / `-tool` | `(msg) → NuclearEntry` | Turn a message into its one-line summary. Advise to extract extra metadata (e.g. `[why: ...]` annotations). |
|
|
553
|
-
| `conversation:format-prior-history` | `(entries) → string` | Render prior-session history into a preamble. Advise for session-grouped output. |
|
|
554
|
-
| `history:append` / `:search` / `:find-by-seq` / `:read-recent` | — | Shell-history-style persistent log at `~/.agent-sh/history`. Advise to add indexing, filtering, or external stores. |
|
|
555
552
|
| `tool:execute` | `(ctx) → ToolResult` | Wrap the full tool lifecycle: permission → execute → emit events. |
|
|
556
553
|
|
|
557
554
|
**`dynamic-context:build`** — Each advisor appends its own context. Multiple extensions compose independently:
|
|
@@ -717,7 +714,7 @@ agent-sh -e ./examples/extensions/latex-images.ts
|
|
|
717
714
|
|
|
718
715
|
Input modes change what happens when the user types and presses Enter. Each mode binds a trigger character (typed at the start of an empty line) to a custom `onSubmit` handler. The built-in mode (`>` for agent) is registered this way — it's not special.
|
|
719
716
|
|
|
720
|
-
The flow: user types trigger → prompt changes to show the mode → user types their input → presses Enter → `onSubmit` fires → your handler emits `agent:submit`.
|
|
717
|
+
The flow: user types trigger → prompt changes to show the mode → user types their input → presses Enter → `onSubmit` fires → your handler emits `agent:submit`. To steer the agent for this mode, build your instruction into the `query` string before emitting — `agent:submit` carries only `query` (and optional `images`).
|
|
721
718
|
|
|
722
719
|
```typescript
|
|
723
720
|
bus.emit("input-mode:register", {
|
|
@@ -728,8 +725,8 @@ bus.emit("input-mode:register", {
|
|
|
728
725
|
indicator: "🌐", // status indicator before the icon
|
|
729
726
|
onSubmit(query, bus) {
|
|
730
727
|
bus.emit("agent:submit", {
|
|
731
|
-
|
|
732
|
-
|
|
728
|
+
// prepend the mode instruction to what the user typed
|
|
729
|
+
query: `[mode: translate] Translate the following to Spanish.\n\n${query}`,
|
|
733
730
|
});
|
|
734
731
|
},
|
|
735
732
|
returnToSelf: true, // re-enter this mode after agent finishes
|
|
@@ -743,7 +740,7 @@ bus.emit("input-mode:register", {
|
|
|
743
740
|
| `label` | `string` | Shown in the prompt area |
|
|
744
741
|
| `promptIcon` | `string` | Chevron/icon character in the prompt |
|
|
745
742
|
| `indicator` | `string` | Status indicator before the icon |
|
|
746
|
-
| `onSubmit` | `(query, bus) => void` | Called on Enter. Emits `agent:submit` with `query`
|
|
743
|
+
| `onSubmit` | `(query, bus) => void` | Called on Enter. Emits `agent:submit` with `query` (build any mode instruction into the `query` string yourself) |
|
|
747
744
|
| `returnToSelf` | `boolean` | Re-enter this mode after the agent finishes |
|
|
748
745
|
|
|
749
746
|
Each trigger character can only be claimed by one mode. Slash commands and readline keybindings work in every mode.
|
|
@@ -826,7 +823,7 @@ If your extension wants to signal "this session is interactive — read the scre
|
|
|
826
823
|
Internally, a remote session:
|
|
827
824
|
|
|
828
825
|
1. **Redirects render streams** — `"agent"`, `"query"`, `"status"` all route to the provided surface
|
|
829
|
-
2. **Keeps the shell interactive** — advises `shell:on-processing-start` and `shell:on-processing-
|
|
826
|
+
2. **Keeps the shell interactive** — advises `shell:on-processing-start` and `shell:on-processing-redraw` to skip pause/redraw (it deliberately leaves `shell:on-processing-done` alone so the agent-turn state cleanup always runs)
|
|
830
827
|
3. **Suppresses chrome** — advises `tui:response-border`, `tui:render-user-query`, `tui:render-usage` based on options
|
|
831
828
|
|
|
832
829
|
Calling `session.close()` removes all advisors and restores all compositor routing in one call.
|
|
@@ -860,11 +857,11 @@ session.close();
|
|
|
860
857
|
|
|
861
858
|
## Shell Lifecycle Handlers
|
|
862
859
|
|
|
863
|
-
The shell's behavior during agent processing is controlled by
|
|
860
|
+
The shell's behavior during agent processing is controlled by three handlers. Two are advisable; the third runs unconditional cleanup and should not be suppressed.
|
|
864
861
|
|
|
865
862
|
### `shell:on-processing-start`
|
|
866
863
|
|
|
867
|
-
Default: pauses the shell (blocks PTY output and input) while the agent works. This is correct when agent output shares stdout with the terminal.
|
|
864
|
+
Default: pauses the shell (blocks PTY output and input) and acquires the agent-turn mute scope while the agent works. This is correct when agent output shares stdout with the terminal.
|
|
868
865
|
|
|
869
866
|
```typescript
|
|
870
867
|
// Skip pause — agent output goes to a separate surface
|
|
@@ -874,19 +871,23 @@ ctx.advise("shell:on-processing-start", (next) => {
|
|
|
874
871
|
});
|
|
875
872
|
```
|
|
876
873
|
|
|
877
|
-
### `shell:on-processing-
|
|
874
|
+
### `shell:on-processing-redraw`
|
|
878
875
|
|
|
879
|
-
Default:
|
|
876
|
+
Default: re-enters agent input mode or redraws the shell prompt. This is the advisable half of "agent finished" — advise it to skip the redraw when your extension already owns the screen.
|
|
880
877
|
|
|
881
878
|
```typescript
|
|
882
879
|
// Skip prompt redraw — already handled by the extension
|
|
883
|
-
ctx.advise("shell:on-processing-
|
|
880
|
+
ctx.advise("shell:on-processing-redraw", (next) => {
|
|
884
881
|
if (mySessionActive) return; // skip
|
|
885
|
-
return next(); // default:
|
|
882
|
+
return next(); // default: redraw / re-enter input mode
|
|
886
883
|
});
|
|
887
884
|
```
|
|
888
885
|
|
|
889
|
-
|
|
886
|
+
### `shell:on-processing-done`
|
|
887
|
+
|
|
888
|
+
Runs when the agent turn ends: it releases the agent-turn mute scope (unconditional state cleanup) and then calls `shell:on-processing-redraw`. **Don't advise this to return early** — skipping it would strand the mute scope past the end of the turn. Suppress the redraw via `shell:on-processing-redraw` instead.
|
|
889
|
+
|
|
890
|
+
> **Note:** `createRemoteSession()` advises `shell:on-processing-start` and `shell:on-processing-redraw` automatically. You only need to advise them directly if you're building custom lifecycle behavior without using remote sessions.
|
|
890
891
|
|
|
891
892
|
## Rendering Architecture
|
|
892
893
|
|
package/docs/library.md
CHANGED
|
@@ -23,8 +23,9 @@ import { activateAgent } from "agent-sh/agent";
|
|
|
23
23
|
import { loadBuiltinExtensions } from "agent-sh/extensions";
|
|
24
24
|
|
|
25
25
|
const core = createCore({
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
// These are ash-backend config, not kernel config — see note below.
|
|
27
|
+
provider: "deepseek", // built-in provider → DeepSeek endpoint + deepseek-v4-flash default
|
|
28
|
+
apiKey: process.env.DEEPSEEK_API_KEY,
|
|
28
29
|
});
|
|
29
30
|
|
|
30
31
|
const ctx = core.extensionContext({ quit: () => process.exit(0) });
|
|
@@ -44,6 +45,8 @@ core.bus.emit("agent:submit", { query: "explain this codebase" });
|
|
|
44
45
|
|
|
45
46
|
`createCore()` returns a headless kernel — the event bus and handler registry, with no terminal, shell, LLM, or agent attached. `activateAgent(ctx)` attaches the agent surface (tools, LLM client, providers) and registers the built-in `ash` backend; `loadBuiltinExtensions(ctx)` adds the abstract backend registry, slash commands, and file autocomplete. `core:extensions-loaded` triggers provider resolution; `activateBackend()` then starts ash (or whichever backend is configured). Send queries by emitting `agent:submit` and consume responses by listening to bus events.
|
|
46
47
|
|
|
48
|
+
> **The LLM fields are backend config, not kernel config.** `createCore()` doesn't read `provider`/`apiKey`/`model`/`baseURL` — it stores the config object opaquely and re-exposes it through the `config:get-app-config` handler. The **ash** backend is the only consumer (`src/agent/index.ts`); it resolves the provider, key, and model from those fields. Under a different backend they're inert: `pi` reads `~/.pi/agent/settings.json`, `claude-code` uses its own SDK config — for those you pass `{ backend: "pi" }` (a real kernel field) and configure the model the backend's own way. The `AppConfig` type bundles kernel + agent + shell config into one object for convenience; the kernel only owns the `extensions` and `backend` keys (`CoreConfig`).
|
|
49
|
+
|
|
47
50
|
Tools run without confirmation by default; to gate them, register tool advisors via `ctx.agent.adviseTool` (see examples/extensions/interactive-prompts.ts).
|
|
48
51
|
|
|
49
52
|
## AgentShellCore API
|
|
@@ -68,7 +71,7 @@ import { activateAgent } from "agent-sh/agent";
|
|
|
68
71
|
import { loadBuiltinExtensions } from "agent-sh/extensions";
|
|
69
72
|
import myTheme from "./my-theme";
|
|
70
73
|
|
|
71
|
-
const core = createCore({
|
|
74
|
+
const core = createCore({ provider: "deepseek", apiKey: process.env.DEEPSEEK_API_KEY });
|
|
72
75
|
const ctx = core.extensionContext({ quit: () => process.exit(0) });
|
|
73
76
|
|
|
74
77
|
activateAgent(ctx);
|
package/docs/troubleshooting.md
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
|
|
19
19
|
**Problem**: Tool calls not working (agent responds but doesn't use tools)
|
|
20
20
|
|
|
21
|
-
**Solution**: Some models have limited or no tool/function calling support. Try a more capable model (e.g.,
|
|
21
|
+
**Solution**: Some models have limited or no tool/function calling support. Try a more capable model (e.g., deepseek-v4-flash, or a larger model via OpenRouter).
|
|
22
22
|
|
|
23
23
|
**Problem**: Garbled output, startup banner overwritten, or messy prompt rendering
|
|
24
24
|
|
|
@@ -54,7 +54,7 @@ Your normal p10k prompt still works — only the "flash cached prompt then redra
|
|
|
54
54
|
Enable debug mode for detailed protocol logging:
|
|
55
55
|
|
|
56
56
|
```bash
|
|
57
|
-
DEBUG=1
|
|
57
|
+
DEBUG=1 DEEPSEEK_API_KEY="$KEY" agent-sh
|
|
58
58
|
```
|
|
59
59
|
|
|
60
60
|
## Getting Help
|
package/docs/tui-composition.md
CHANGED
|
@@ -43,9 +43,11 @@ A `RenderSurface` is anything that can accept rendered output:
|
|
|
43
43
|
|
|
44
44
|
```typescript
|
|
45
45
|
interface RenderSurface {
|
|
46
|
-
write(text: string): void;
|
|
46
|
+
write(text: string): void; // raw — supports \r, escape codes
|
|
47
47
|
writeLine(line: string): void; // line + newline
|
|
48
48
|
readonly columns: number; // available width
|
|
49
|
+
readonly rows: number; // available height
|
|
50
|
+
onResize(cb: (cols: number, rows: number) => void): () => void; // subscribe; returns unsubscribe
|
|
49
51
|
}
|
|
50
52
|
```
|
|
51
53
|
|
|
@@ -71,6 +73,12 @@ const panelSurface: RenderSurface = {
|
|
|
71
73
|
},
|
|
72
74
|
writeLine(line) { panel.appendLine(line); },
|
|
73
75
|
get columns() { return panel.computeGeometry().contentW; },
|
|
76
|
+
get rows() { return panel.computeGeometry().contentH; },
|
|
77
|
+
onResize(cb) {
|
|
78
|
+
const handler = () => { const g = panel.computeGeometry(); cb(g.contentW, g.contentH); };
|
|
79
|
+
process.stdout.on("resize", handler);
|
|
80
|
+
return () => process.stdout.off("resize", handler);
|
|
81
|
+
},
|
|
74
82
|
};
|
|
75
83
|
```
|
|
76
84
|
|
|
@@ -94,7 +102,7 @@ interface Compositor {
|
|
|
94
102
|
| `"query"` | User query display (the bordered input box) |
|
|
95
103
|
| `"status"` | Info messages, errors, suggestions |
|
|
96
104
|
|
|
97
|
-
The shell frontend (`src/shell/`) sets all three to `
|
|
105
|
+
The shell frontend (`src/shell/`) sets all three to a `surfaceFromTerminal(terminal)` surface (which writes through the host `Terminal`, ultimately stdout) during `activateShell`. A library or web consumer that doesn't load the shell frontend has no defaults — it must call `compositor.setDefault(...)` itself.
|
|
98
106
|
|
|
99
107
|
### Redirecting a stream
|
|
100
108
|
|
|
@@ -155,7 +163,7 @@ export default function activate(ctx: ExtensionContext): void {
|
|
|
155
163
|
bus.emit("agent:submit", { query });
|
|
156
164
|
});
|
|
157
165
|
|
|
158
|
-
panel.handlers.advise("panel:
|
|
166
|
+
panel.handlers.advise("panel:hide", (next) => {
|
|
159
167
|
next();
|
|
160
168
|
restoreAgent?.(); restoreAgent = null;
|
|
161
169
|
restoreQuery?.(); restoreQuery = null;
|
package/docs/usage.md
CHANGED
|
@@ -2,17 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
## Running agent-sh
|
|
4
4
|
|
|
5
|
-
The simplest way to run agent-sh — just provide an API key
|
|
5
|
+
The simplest way to run agent-sh — just provide an API key:
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
#
|
|
9
|
-
|
|
8
|
+
# DeepSeek is a built-in provider — set the key and go (defaults to deepseek-v4-flash)
|
|
9
|
+
DEEPSEEK_API_KEY="your-key" agent-sh
|
|
10
10
|
|
|
11
|
-
#
|
|
12
|
-
agent-sh --api-key "your-key" --base-url http://localhost:11434/v1 --model
|
|
11
|
+
# Any OpenAI-compatible endpoint via CLI flags (e.g. a local Ollama server)
|
|
12
|
+
agent-sh --api-key "your-key" --base-url http://localhost:11434/v1 --model gemma4
|
|
13
13
|
|
|
14
14
|
# Using npx
|
|
15
|
-
npx agent-sh --
|
|
15
|
+
DEEPSEEK_API_KEY="your-key" npx agent-sh --model deepseek-v4-flash
|
|
16
16
|
```
|
|
17
17
|
|
|
18
18
|
Environment variables `OPENAI_API_KEY` and `OPENAI_BASE_URL` are supported as alternatives to CLI flags.
|
|
@@ -30,13 +30,13 @@ agent-sh --backend pi
|
|
|
30
30
|
npm run dev
|
|
31
31
|
|
|
32
32
|
# Debug mode
|
|
33
|
-
DEBUG=1
|
|
33
|
+
DEBUG=1 DEEPSEEK_API_KEY="$KEY" agent-sh
|
|
34
34
|
```
|
|
35
35
|
|
|
36
36
|
### Subcommands
|
|
37
37
|
|
|
38
38
|
```bash
|
|
39
|
-
agent-sh init # scaffold ~/.agent-sh/ (settings
|
|
39
|
+
agent-sh init # scaffold ~/.agent-sh/ (settings.json + settings.example.json, extensions/ dir)
|
|
40
40
|
agent-sh install <name> # install a bundled extension (e.g. agent-sh install pi-bridge)
|
|
41
41
|
agent-sh install ./path/to/ext # install from a local path
|
|
42
42
|
agent-sh uninstall <name> # remove an installed extension
|
|
@@ -55,8 +55,8 @@ Any provider you declare under `providers` in `settings.json` is also accepted b
|
|
|
55
55
|
"providers": {
|
|
56
56
|
"my-llama": {
|
|
57
57
|
"baseURL": "http://localhost:8000/v1",
|
|
58
|
-
"defaultModel": "
|
|
59
|
-
"models": ["
|
|
58
|
+
"defaultModel": "gemma4",
|
|
59
|
+
"models": ["gemma4"]
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
62
|
}
|
|
@@ -84,26 +84,26 @@ For unreleased changes on `main`, use the clone-and-link flow from the [Quick St
|
|
|
84
84
|
|
|
85
85
|
agent-sh works with any OpenAI-compatible API. Here are common configurations:
|
|
86
86
|
|
|
87
|
-
###
|
|
87
|
+
### DeepSeek
|
|
88
88
|
|
|
89
89
|
```bash
|
|
90
|
-
export
|
|
91
|
-
agent-sh
|
|
92
|
-
# or: agent-sh --model gpt-4o-mini
|
|
90
|
+
export DEEPSEEK_API_KEY="sk-..."
|
|
91
|
+
agent-sh # defaults to deepseek-v4-flash
|
|
93
92
|
```
|
|
94
93
|
|
|
95
|
-
###
|
|
94
|
+
### OpenAI
|
|
96
95
|
|
|
97
96
|
```bash
|
|
98
|
-
export
|
|
99
|
-
agent-sh
|
|
97
|
+
export OPENAI_API_KEY="sk-..."
|
|
98
|
+
agent-sh --model gpt-5.4
|
|
99
|
+
# or: agent-sh --model gpt-5.4-mini
|
|
100
100
|
```
|
|
101
101
|
|
|
102
102
|
### Ollama (Local)
|
|
103
103
|
|
|
104
104
|
```bash
|
|
105
105
|
# No API key needed — Ollama doesn't require authentication
|
|
106
|
-
agent-sh --api-key dummy --base-url http://localhost:11434/v1 --model
|
|
106
|
+
agent-sh --api-key dummy --base-url http://localhost:11434/v1 --model gemma4
|
|
107
107
|
```
|
|
108
108
|
|
|
109
109
|
### OpenRouter
|
|
@@ -111,7 +111,7 @@ agent-sh --api-key dummy --base-url http://localhost:11434/v1 --model llama3
|
|
|
111
111
|
```bash
|
|
112
112
|
agent-sh --api-key "$OPENROUTER_KEY" \
|
|
113
113
|
--base-url https://openrouter.ai/api/v1 \
|
|
114
|
-
--model
|
|
114
|
+
--model deepseek/deepseek-v4-flash
|
|
115
115
|
```
|
|
116
116
|
|
|
117
117
|
### Together AI
|
|
@@ -119,7 +119,7 @@ agent-sh --api-key "$OPENROUTER_KEY" \
|
|
|
119
119
|
```bash
|
|
120
120
|
agent-sh --api-key "$TOGETHER_KEY" \
|
|
121
121
|
--base-url https://api.together.xyz/v1 \
|
|
122
|
-
--model
|
|
122
|
+
--model deepseek-ai/DeepSeek-V3
|
|
123
123
|
```
|
|
124
124
|
|
|
125
125
|
### Groq
|
|
@@ -127,7 +127,7 @@ agent-sh --api-key "$TOGETHER_KEY" \
|
|
|
127
127
|
```bash
|
|
128
128
|
agent-sh --api-key "$GROQ_KEY" \
|
|
129
129
|
--base-url https://api.groq.com/openai/v1 \
|
|
130
|
-
--model llama-
|
|
130
|
+
--model deepseek-r1-distill-llama-70b
|
|
131
131
|
```
|
|
132
132
|
|
|
133
133
|
### LM Studio
|
|
@@ -135,7 +135,7 @@ agent-sh --api-key "$GROQ_KEY" \
|
|
|
135
135
|
```bash
|
|
136
136
|
agent-sh --api-key dummy \
|
|
137
137
|
--base-url http://localhost:1234/v1 \
|
|
138
|
-
--model
|
|
138
|
+
--model mimo
|
|
139
139
|
```
|
|
140
140
|
|
|
141
141
|
### vLLM
|
|
@@ -143,7 +143,7 @@ agent-sh --api-key dummy \
|
|
|
143
143
|
```bash
|
|
144
144
|
agent-sh --api-key dummy \
|
|
145
145
|
--base-url http://localhost:8000/v1 \
|
|
146
|
-
--model
|
|
146
|
+
--model deepseek-v4-flash
|
|
147
147
|
```
|
|
148
148
|
|
|
149
149
|
## Using agent-sh as Your Default Shell
|
|
@@ -152,7 +152,7 @@ Add to the end of your `~/.zshrc` or `~/.bashrc`:
|
|
|
152
152
|
|
|
153
153
|
```bash
|
|
154
154
|
if [[ -z "$AGENT_SH" && $- == *i* && -t 0 ]]; then
|
|
155
|
-
exec agent-sh
|
|
155
|
+
exec agent-sh # uses DEEPSEEK_API_KEY from your env (deepseek-v4-flash)
|
|
156
156
|
fi
|
|
157
157
|
```
|
|
158
158
|
|
|
@@ -168,27 +168,29 @@ Instead of passing `--api-key` and `--base-url` every time, define named provide
|
|
|
168
168
|
|
|
169
169
|
```json
|
|
170
170
|
{
|
|
171
|
-
"defaultProvider": "
|
|
171
|
+
"defaultProvider": "deepseek",
|
|
172
172
|
"providers": {
|
|
173
|
-
"
|
|
174
|
-
"apiKey": "$
|
|
175
|
-
"defaultModel": "
|
|
176
|
-
"models": ["
|
|
177
|
-
"contextWindow": 128000
|
|
173
|
+
"deepseek": {
|
|
174
|
+
"apiKey": "$DEEPSEEK_API_KEY",
|
|
175
|
+
"defaultModel": "deepseek-v4-flash",
|
|
176
|
+
"models": ["deepseek-v4-flash", "deepseek-v4-pro"]
|
|
178
177
|
},
|
|
179
178
|
"ollama": {
|
|
180
179
|
"apiKey": "not-needed",
|
|
181
180
|
"baseURL": "http://localhost:11434/v1",
|
|
182
|
-
"defaultModel": "
|
|
183
|
-
"models": [
|
|
181
|
+
"defaultModel": "gemma4",
|
|
182
|
+
"models": [
|
|
183
|
+
"mimo",
|
|
184
|
+
{ "id": "gemma4", "contextWindow": 128000, "modalities": ["text", "image"] }
|
|
185
|
+
]
|
|
184
186
|
},
|
|
185
187
|
"openrouter": {
|
|
186
188
|
"apiKey": "$OPENROUTER_KEY",
|
|
187
189
|
"baseURL": "https://openrouter.ai/api/v1",
|
|
188
|
-
"defaultModel": "
|
|
190
|
+
"defaultModel": "deepseek/deepseek-v4-flash",
|
|
189
191
|
"models": [
|
|
190
|
-
{ "id": "
|
|
191
|
-
{ "id": "
|
|
192
|
+
{ "id": "deepseek/deepseek-v4-flash", "contextWindow": 1000000, "reasoning": true },
|
|
193
|
+
{ "id": "deepseek/deepseek-v4-pro", "contextWindow": 1048576, "reasoning": true }
|
|
192
194
|
]
|
|
193
195
|
}
|
|
194
196
|
}
|
|
@@ -198,32 +200,50 @@ Instead of passing `--api-key` and `--base-url` every time, define named provide
|
|
|
198
200
|
Then just run:
|
|
199
201
|
|
|
200
202
|
```bash
|
|
201
|
-
agent-sh # uses defaultProvider
|
|
203
|
+
agent-sh # uses defaultProvider (deepseek)
|
|
202
204
|
agent-sh --provider ollama # use a specific provider
|
|
203
|
-
agent-sh --provider
|
|
205
|
+
agent-sh --provider ollama --model gemma4 # override the default model
|
|
204
206
|
```
|
|
205
207
|
|
|
206
208
|
The `apiKey` field supports `$ENV_VAR` and `${ENV_VAR}` syntax — variables are expanded at runtime, so you don't store secrets in the file.
|
|
207
209
|
|
|
208
|
-
### Declaring
|
|
209
|
-
|
|
210
|
-
agent-sh adapts its auto-compaction trigger to the model's context window. There are two places to declare it:
|
|
211
|
-
|
|
212
|
-
- **Provider-level `contextWindow`** — applies to every model in that provider unless a more specific value is set.
|
|
213
|
-
- **Per-model `contextWindow`** (inside an entry of `models`) — overrides the provider-level value for a specific model, and also lets you tag reasoning-capable models via `reasoning: true`.
|
|
210
|
+
### Declaring model capabilities
|
|
214
211
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
Entries in `models` can be plain strings (just the model id, uses the provider-level `contextWindow`) or objects:
|
|
212
|
+
Entries in a provider's `models` list can be plain strings (just the id) or objects that declare what the model can do. agent-sh uses these to size its context budget, cap output, route reasoning, and enable image input. Every field except `id` is optional.
|
|
218
213
|
|
|
219
214
|
```json
|
|
220
215
|
"models": [
|
|
221
|
-
"
|
|
222
|
-
{
|
|
223
|
-
|
|
216
|
+
"deepseek-v4-flash",
|
|
217
|
+
{
|
|
218
|
+
"id": "gemma4",
|
|
219
|
+
"contextWindow": 128000,
|
|
220
|
+
"maxTokens": 8192,
|
|
221
|
+
"modalities": ["text", "image"]
|
|
222
|
+
},
|
|
223
|
+
{ "id": "mimo", "reasoning": true },
|
|
224
|
+
{ "id": "deepseek-v4-pro", "contextWindow": 1000000, "reasoning": true, "echoReasoning": true }
|
|
224
225
|
]
|
|
225
226
|
```
|
|
226
227
|
|
|
228
|
+
| Field | Type | Default | Effect |
|
|
229
|
+
|---|---|---|---|
|
|
230
|
+
| `id` | `string` | — | Model identifier sent to the API (required). |
|
|
231
|
+
| `contextWindow` | `number` | provider-level `contextWindow`, else `60000` | Total token budget. Drives the `/context` display and the `autoCompactThreshold` auto-compaction trigger. |
|
|
232
|
+
| `maxTokens` | `number` | 40% of this model's `contextWindow` capped at `65536`, else `65536` | Max output (completion) tokens requested per turn. |
|
|
233
|
+
| `reasoning` | `boolean` | `false` | Marks the model as thinking-capable, so `/thinking` levels apply to it. |
|
|
234
|
+
| `modalities` | `("text" \| "image")[]` | `["text"]` | Input modalities. Include `"image"` to let the agent read image files (PNG/JPEG/GIF/WebP) with `read_file`; without it, attached images are dropped before the request. |
|
|
235
|
+
| `echoReasoning` | `boolean` | `false` | Echo `reasoning_content` back on assistant turns. Required by DeepSeek's reasoner; leave off otherwise (leaky proxies may forward it to the model as malformed input). |
|
|
236
|
+
|
|
237
|
+
A plain-string entry inherits the provider-level values and the defaults above. These provider-level fields apply to every model unless a per-model entry overrides them:
|
|
238
|
+
|
|
239
|
+
| Provider field | Effect |
|
|
240
|
+
|---|---|
|
|
241
|
+
| `contextWindow` | Fallback context window for models that don't declare their own. |
|
|
242
|
+
| `reasoningShape` | Borrow another registered provider's reasoning-request shape by id (e.g. `"openrouter"`). Defaults to the OpenAI-compatible shape. |
|
|
243
|
+
| `echoReasoningPatterns` | Case-insensitive regex sources matched against model ids; a match defaults that model to `echoReasoning: true` (a per-model `echoReasoning` still wins). |
|
|
244
|
+
|
|
245
|
+
If neither level declares a `contextWindow`, agent-sh falls back to a conservative 60k-token budget. Override that fallback globally with the `AGENT_SH_DEFAULT_CONTEXT_WINDOW` environment variable (a positive integer; ignored otherwise).
|
|
246
|
+
|
|
227
247
|
### Switching models at runtime
|
|
228
248
|
|
|
229
249
|
- **`/model`** — show the current model
|
|
@@ -274,7 +294,7 @@ Switching mid-conversation preserves your conversation state — only the LLM en
|
|
|
274
294
|
On launch, agent-sh displays a structured startup banner showing:
|
|
275
295
|
|
|
276
296
|
- **Backend** — which agent backend is active (`ash`, `claude-code`, `pi`, etc.)
|
|
277
|
-
- **Model** — current model with provider in brackets (e.g. `
|
|
297
|
+
- **Model** — current model with provider in brackets (e.g. `deepseek-v4-flash [deepseek]`)
|
|
278
298
|
- **Extensions** — loaded extensions (from CLI `-e`, settings, or `~/.agent-sh/extensions/`)
|
|
279
299
|
- **Skills** — discovered skills (global + project)
|
|
280
300
|
|
|
@@ -7,6 +7,8 @@ export type RenderBlock =
|
|
|
7
7
|
|
|
8
8
|
export type ContentTransform = (blocks: RenderBlock[]) => RenderBlock[];
|
|
9
9
|
|
|
10
|
+
const stripTrailing = (s: string): string => s.replace(/\s+$/, "");
|
|
11
|
+
|
|
10
12
|
export class AssistantMessage {
|
|
11
13
|
readonly node: RenderNode;
|
|
12
14
|
private container: ContainerView;
|
|
@@ -23,20 +25,20 @@ export class AssistantMessage {
|
|
|
23
25
|
|
|
24
26
|
appendText(t: string): void {
|
|
25
27
|
this.buffer += t;
|
|
26
|
-
this.md.setText(this.buffer);
|
|
28
|
+
this.md.setText(stripTrailing(this.buffer));
|
|
27
29
|
}
|
|
28
30
|
|
|
29
31
|
appendCodeBlock(language: string, code: string): void {
|
|
30
32
|
const prefix = this.buffer && !this.buffer.endsWith("\n") ? "\n" : "";
|
|
31
33
|
this.buffer += `${prefix}\`\`\`${language}\n${code}\n\`\`\`\n`;
|
|
32
|
-
this.md.setText(this.buffer);
|
|
34
|
+
this.md.setText(stripTrailing(this.buffer));
|
|
33
35
|
}
|
|
34
36
|
|
|
35
37
|
finalize(): void {
|
|
36
38
|
if (this.buffer === "") this.buffer = " ";
|
|
37
39
|
const blocks = this.transform([{ type: "text", text: this.buffer }]);
|
|
38
40
|
if (blocks.every((b) => b.type === "text")) {
|
|
39
|
-
this.md.setText(this.buffer);
|
|
41
|
+
this.md.setText(stripTrailing(this.buffer));
|
|
40
42
|
return;
|
|
41
43
|
}
|
|
42
44
|
this.rebuild(blocks);
|
|
@@ -55,7 +57,7 @@ export class AssistantMessage {
|
|
|
55
57
|
this.container.addChild(m.node);
|
|
56
58
|
} else if (block.text.trim()) {
|
|
57
59
|
const m = this.nodes.markdown({ paddingX: 1 });
|
|
58
|
-
m.setText(block.text);
|
|
60
|
+
m.setText(stripTrailing(block.text));
|
|
59
61
|
this.container.addChild(m.node);
|
|
60
62
|
}
|
|
61
63
|
}
|