agent-sh 0.15.5 → 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/agent/types.d.ts +4 -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 +116 -174
- package/dist/utils/diff-renderer.js +65 -30
- 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 +56 -44
- 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 +90 -0
- package/examples/extensions/solarized-theme.ts +11 -0
- package/package.json +5 -5
- 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/agent/types.ts +4 -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 +131 -207
- package/src/utils/diff-renderer.ts +62 -29
- package/src/utils/executor.ts +17 -14
- package/src/utils/floating-panel.ts +24 -22
- package/src/utils/markdown.ts +49 -40
- package/src/utils/palette.ts +30 -5
|
@@ -28,17 +28,32 @@ export default function activate(ctx: AgentContext): void {
|
|
|
28
28
|
id,
|
|
29
29
|
apiKey,
|
|
30
30
|
baseURL,
|
|
31
|
-
defaultModel: models[0],
|
|
31
|
+
defaultModel: models[0]!.id,
|
|
32
32
|
models,
|
|
33
33
|
});
|
|
34
34
|
}).catch(() => { /* leave empty — user supplies via --model */ });
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
|
|
37
|
+
export interface CatalogModel {
|
|
38
|
+
id: string;
|
|
39
|
+
meta?: { n_ctx?: number };
|
|
40
|
+
max_model_len?: number;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function catalogContextWindow(m: CatalogModel): number | undefined {
|
|
44
|
+
if (typeof m.meta?.n_ctx === "number" && m.meta.n_ctx > 0) return m.meta.n_ctx;
|
|
45
|
+
if (typeof m.max_model_len === "number" && m.max_model_len > 0) return m.max_model_len;
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function fetchModels(
|
|
50
|
+
baseURL: string,
|
|
51
|
+
apiKey: string,
|
|
52
|
+
): Promise<{ id: string; contextWindow?: number }[]> {
|
|
38
53
|
const headers: Record<string, string> = {};
|
|
39
54
|
if (apiKey && apiKey !== "no-key") headers.Authorization = `Bearer ${apiKey}`;
|
|
40
55
|
const res = await fetch(`${baseURL.replace(/\/$/, "")}/models`, { headers });
|
|
41
56
|
if (!res.ok) return [];
|
|
42
|
-
const data = await res.json() as { data?:
|
|
43
|
-
return (data.data ?? []).map((m) => m.id);
|
|
57
|
+
const data = await res.json() as { data?: CatalogModel[] };
|
|
58
|
+
return (data.data ?? []).map((m) => ({ id: m.id, contextWindow: catalogContextWindow(m) }));
|
|
44
59
|
}
|
package/src/agent/store.ts
CHANGED
|
@@ -54,7 +54,11 @@ function escapeRegex(s: string): string {
|
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
function compileSearchRegex(query: string): RegExp {
|
|
57
|
-
|
|
57
|
+
try {
|
|
58
|
+
return new RegExp(query, "i");
|
|
59
|
+
} catch {
|
|
60
|
+
return new RegExp(escapeRegex(query), "i");
|
|
61
|
+
}
|
|
58
62
|
}
|
|
59
63
|
|
|
60
64
|
function matchEntry(entry: Entry, re: RegExp): SearchHit | null {
|
|
@@ -8,5 +8,14 @@
|
|
|
8
8
|
/** Response reserve — tokens reserved for the model's output. */
|
|
9
9
|
export const RESPONSE_RESERVE = 8192;
|
|
10
10
|
|
|
11
|
+
const FALLBACK_CONTEXT_WINDOW = 60_000;
|
|
12
|
+
|
|
13
|
+
export function resolveDefaultContextWindow(
|
|
14
|
+
env: Record<string, string | undefined> = process.env,
|
|
15
|
+
): number {
|
|
16
|
+
const n = Number(env.AGENT_SH_DEFAULT_CONTEXT_WINDOW);
|
|
17
|
+
return Number.isInteger(n) && n > 0 ? n : FALLBACK_CONTEXT_WINDOW;
|
|
18
|
+
}
|
|
19
|
+
|
|
11
20
|
/** Fallback when contextWindow is unknown. */
|
|
12
|
-
export const DEFAULT_CONTEXT_WINDOW =
|
|
21
|
+
export const DEFAULT_CONTEXT_WINDOW = resolveDefaultContextWindow();
|
package/src/agent/types.ts
CHANGED
|
@@ -54,7 +54,10 @@ export type ToolResultBody =
|
|
|
54
54
|
| { kind: "lines"; lines: string[]; maxLines?: number }
|
|
55
55
|
|
|
56
56
|
export interface ToolDisplayInfo {
|
|
57
|
-
|
|
57
|
+
/** Verb shown next to the detail (e.g. "execute foo.py"). Omit when a custom
|
|
58
|
+
* `icon` already makes the action self-evident — the renderer then shows
|
|
59
|
+
* icon + detail with no verb. */
|
|
60
|
+
kind?: "read" | "write" | "execute" | "search";
|
|
58
61
|
locations?: { path: string; line?: number | null }[];
|
|
59
62
|
/** Custom icon character for TUI display (e.g., "◆", "⌕"). When set, the TUI shows
|
|
60
63
|
* icon + detail only. When absent, the tool name is shown alongside the detail. */
|
package/src/cli/index.ts
CHANGED
|
@@ -128,7 +128,6 @@ async function main(): Promise<void> {
|
|
|
128
128
|
// Load before spawning the shell so PS1 lands below the banner.
|
|
129
129
|
const settings = getSettings();
|
|
130
130
|
await loadBuiltinExtensions(extCtx, settings.disabledBuiltins);
|
|
131
|
-
activateRollingHistory(extCtx);
|
|
132
131
|
const loadExtensionsTimeoutMs = 10000;
|
|
133
132
|
let loadedExtensions: string[] = [];
|
|
134
133
|
await Promise.race([
|
|
@@ -197,6 +196,7 @@ async function main(): Promise<void> {
|
|
|
197
196
|
}
|
|
198
197
|
|
|
199
198
|
await core.activateBackend(config.backend);
|
|
199
|
+
activateRollingHistory(extCtx);
|
|
200
200
|
|
|
201
201
|
// 100ms sidesteps macOS SIGTTOU during fg-pgrp handoff.
|
|
202
202
|
await new Promise(resolve => setTimeout(resolve, 100));
|
package/src/core/event-bus.ts
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { EventEmitter } from "node:events";
|
|
2
|
-
|
|
3
1
|
export interface BackendRegistration {
|
|
4
2
|
name: string;
|
|
5
3
|
kill: () => void;
|
|
@@ -49,6 +47,15 @@ export interface BusMeta {
|
|
|
49
47
|
|
|
50
48
|
export type AnyListener = (name: string, payload: unknown, meta: BusMeta) => void;
|
|
51
49
|
|
|
50
|
+
/** A listener fault routed to the error reporter; `phase` is the callback site. */
|
|
51
|
+
export interface BusFault {
|
|
52
|
+
phase: "on" | "any" | "pipe" | "pipe-async";
|
|
53
|
+
event: string;
|
|
54
|
+
err: unknown;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export type ErrorReporter = (fault: BusFault) => void;
|
|
58
|
+
|
|
52
59
|
/**
|
|
53
60
|
* Typed event bus with two modes:
|
|
54
61
|
* - emit/on/off: fire-and-forget notifications
|
|
@@ -56,18 +63,54 @@ export type AnyListener = (name: string, payload: unknown, meta: BusMeta) => voi
|
|
|
56
63
|
* can modify the payload before passing to the next
|
|
57
64
|
*/
|
|
58
65
|
export class EventBus {
|
|
59
|
-
private
|
|
66
|
+
private listeners = new Map<string, Listener<any>[]>();
|
|
60
67
|
private pipeListeners = new Map<string, PipeListener<any>[]>();
|
|
61
68
|
private asyncPipeListeners = new Map<string, AsyncPipeListener<any>[]>();
|
|
62
69
|
private source = "0000";
|
|
63
70
|
private nextSeq = 0;
|
|
64
71
|
private anyListeners: AnyListener[] = [];
|
|
65
72
|
|
|
73
|
+
/** Default fault sink, overridable via setErrorReporter: silent unless DEBUG. */
|
|
74
|
+
private reportError: ErrorReporter = ({ phase, event, err }) => {
|
|
75
|
+
if (process.env.DEBUG) {
|
|
76
|
+
const msg = err instanceof Error ? (err.stack ?? err.message) : String(err);
|
|
77
|
+
process.stderr.write(`[event-bus] ${phase} fault on "${event}": ${msg}\n`);
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
|
|
66
81
|
/** Set the source id stamped onto every emitted event. */
|
|
67
82
|
setSource(src: string): void {
|
|
68
83
|
this.source = src;
|
|
69
84
|
}
|
|
70
85
|
|
|
86
|
+
/** Install a fault reporter. */
|
|
87
|
+
setErrorReporter(fn: ErrorReporter): void {
|
|
88
|
+
this.reportError = fn;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Report a fault; guarded so a broken reporter can't break dispatch. */
|
|
92
|
+
private fault(phase: BusFault["phase"], event: string, err: unknown): void {
|
|
93
|
+
try {
|
|
94
|
+
this.reportError({ phase, event, err });
|
|
95
|
+
} catch {
|
|
96
|
+
/* swallow */
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Fire every listener for `name`, isolating faults. */
|
|
101
|
+
private notify(name: string, payload: unknown): void {
|
|
102
|
+
const arr = this.listeners.get(name);
|
|
103
|
+
if (!arr || arr.length === 0) return;
|
|
104
|
+
// snapshot so a listener that (un)subscribes mid-dispatch can't shift iteration
|
|
105
|
+
if (arr.length === 1) {
|
|
106
|
+
try { arr[0](payload); } catch (err) { this.fault("on", name, err); }
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
for (const fn of arr.slice()) {
|
|
110
|
+
try { fn(payload); } catch (err) { this.fault("on", name, err); }
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
71
114
|
/** Subscribe to every emitted event with full envelope. Returns unsubscribe. */
|
|
72
115
|
onAny(fn: AnyListener): () => void {
|
|
73
116
|
this.anyListeners.push(fn);
|
|
@@ -87,10 +130,10 @@ export class EventBus {
|
|
|
87
130
|
name,
|
|
88
131
|
};
|
|
89
132
|
for (const fn of this.anyListeners) {
|
|
90
|
-
try { fn(name, payload, meta); } catch {
|
|
133
|
+
try { fn(name, payload, meta); } catch (err) { this.fault("any", name, err); }
|
|
91
134
|
}
|
|
92
135
|
}
|
|
93
|
-
this.
|
|
136
|
+
this.notify(name, payload);
|
|
94
137
|
}
|
|
95
138
|
|
|
96
139
|
/** Subscribe to a fire-and-forget event. */
|
|
@@ -98,7 +141,12 @@ export class EventBus {
|
|
|
98
141
|
event: K,
|
|
99
142
|
fn: Listener<BusEvents[K]>,
|
|
100
143
|
): void {
|
|
101
|
-
this.
|
|
144
|
+
let arr = this.listeners.get(event);
|
|
145
|
+
if (!arr) {
|
|
146
|
+
arr = [];
|
|
147
|
+
this.listeners.set(event, arr);
|
|
148
|
+
}
|
|
149
|
+
arr.push(fn);
|
|
102
150
|
}
|
|
103
151
|
|
|
104
152
|
/** Unsubscribe from a fire-and-forget event. */
|
|
@@ -106,7 +154,10 @@ export class EventBus {
|
|
|
106
154
|
event: K,
|
|
107
155
|
fn: Listener<BusEvents[K]>,
|
|
108
156
|
): void {
|
|
109
|
-
this.
|
|
157
|
+
const arr = this.listeners.get(event);
|
|
158
|
+
if (!arr) return;
|
|
159
|
+
const idx = arr.indexOf(fn);
|
|
160
|
+
if (idx !== -1) arr.splice(idx, 1);
|
|
110
161
|
}
|
|
111
162
|
|
|
112
163
|
/** Emit a fire-and-forget event. */
|
|
@@ -123,10 +174,10 @@ export class EventBus {
|
|
|
123
174
|
relay(meta: BusMeta, payload: unknown): void {
|
|
124
175
|
if (this.anyListeners.length > 0) {
|
|
125
176
|
for (const fn of this.anyListeners) {
|
|
126
|
-
try { fn(meta.name, payload, meta); } catch {
|
|
177
|
+
try { fn(meta.name, payload, meta); } catch (err) { this.fault("any", meta.name, err); }
|
|
127
178
|
}
|
|
128
179
|
}
|
|
129
|
-
this.
|
|
180
|
+
this.notify(meta.name, payload);
|
|
130
181
|
}
|
|
131
182
|
|
|
132
183
|
/**
|
|
@@ -191,12 +242,12 @@ export class EventBus {
|
|
|
191
242
|
try {
|
|
192
243
|
const out = fn(result);
|
|
193
244
|
if (out && typeof (out as any).then === "function") {
|
|
194
|
-
|
|
245
|
+
this.fault("pipe", String(event), new Error("async handler in sync pipe — use onPipeAsync instead"));
|
|
195
246
|
continue;
|
|
196
247
|
}
|
|
197
248
|
result = out;
|
|
198
249
|
} catch (err) {
|
|
199
|
-
|
|
250
|
+
this.fault("pipe", String(event), err);
|
|
200
251
|
}
|
|
201
252
|
}
|
|
202
253
|
return result;
|
|
@@ -245,7 +296,11 @@ export class EventBus {
|
|
|
245
296
|
if (!listeners) return payload;
|
|
246
297
|
let result = payload;
|
|
247
298
|
for (const fn of listeners) {
|
|
248
|
-
|
|
299
|
+
try {
|
|
300
|
+
result = await fn(result);
|
|
301
|
+
} catch (err) {
|
|
302
|
+
this.fault("pipe-async", String(event), err);
|
|
303
|
+
}
|
|
249
304
|
}
|
|
250
305
|
return result;
|
|
251
306
|
}
|
package/src/core/index.ts
CHANGED
|
@@ -48,6 +48,24 @@ export function createCore(config: AppConfig): AgentShellCore {
|
|
|
48
48
|
// should accept ≥6 hex chars.
|
|
49
49
|
const instanceId = crypto.randomBytes(3).toString("hex");
|
|
50
50
|
bus.setSource(instanceId);
|
|
51
|
+
|
|
52
|
+
// Surface faults on ui:error; `surfacing` stops a faulting renderer from looping.
|
|
53
|
+
let surfacing = false;
|
|
54
|
+
bus.setErrorReporter(({ phase, event, err }) => {
|
|
55
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
56
|
+
if (process.env.DEBUG) {
|
|
57
|
+
const full = err instanceof Error ? (err.stack ?? err.message) : detail;
|
|
58
|
+
process.stderr.write(`[event-bus] ${phase} fault on "${event}": ${full}\n`);
|
|
59
|
+
}
|
|
60
|
+
if (surfacing) return;
|
|
61
|
+
surfacing = true;
|
|
62
|
+
try {
|
|
63
|
+
bus.emit("ui:error", { message: `Handler error on "${event}": ${detail}` });
|
|
64
|
+
} finally {
|
|
65
|
+
surfacing = false;
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
|
|
51
69
|
handlers.define("config:get-app-config", () => config);
|
|
52
70
|
handlers.define("cwd", () => process.cwd());
|
|
53
71
|
|