agent-sh 0.12.2 → 0.12.4
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/dist/agent/agent-loop.js +40 -7
- package/dist/agent/skills.js +2 -2
- package/dist/agent/system-prompt.js +2 -3
- package/dist/core.js +4 -3
- package/dist/event-bus.d.ts +46 -0
- package/dist/event-bus.js +51 -3
- package/dist/extension-loader.js +1 -0
- package/dist/extensions/agent-backend.js +4 -1
- package/dist/extensions/openrouter.js +32 -0
- package/dist/init.js +1 -2
- package/dist/settings.d.ts +8 -0
- package/dist/settings.js +7 -3
- package/dist/shell/input-handler.d.ts +8 -18
- package/dist/shell/input-handler.js +57 -227
- package/dist/shell/shell.js +1 -1
- package/dist/shell/tui-input-view.d.ts +37 -0
- package/dist/shell/tui-input-view.js +140 -0
- package/dist/types.d.ts +6 -0
- package/dist/utils/compositor.d.ts +7 -1
- package/dist/utils/compositor.js +13 -1
- package/dist/utils/floating-panel.d.ts +6 -2
- package/dist/utils/floating-panel.js +17 -17
- package/dist/utils/ref-counter.d.ts +9 -0
- package/dist/utils/ref-counter.js +9 -0
- package/package.json +1 -1
- package/dist/utils/frame-renderer.d.ts +0 -26
- package/dist/utils/frame-renderer.js +0 -76
- package/dist/utils/output-writer.d.ts +0 -36
- package/dist/utils/output-writer.js +0 -45
package/dist/agent/agent-loop.js
CHANGED
|
@@ -297,6 +297,18 @@ export class AgentLoop {
|
|
|
297
297
|
recallArchiveSize: this.conversation.getRecallArchiveSize(),
|
|
298
298
|
budgetTokens: this.currentMode.contextWindow ?? DEFAULT_CONTEXT_WINDOW,
|
|
299
299
|
}));
|
|
300
|
+
this.bus.onPipe("context:snapshot", (payload) => {
|
|
301
|
+
payload.messages = this.conversation.getMessages();
|
|
302
|
+
payload.contextWindow = this.currentMode.contextWindow ?? DEFAULT_CONTEXT_WINDOW;
|
|
303
|
+
payload.activeTokens = this.conversation.estimateTokens();
|
|
304
|
+
return payload;
|
|
305
|
+
});
|
|
306
|
+
this.bus.onPipeAsync("context:compact", async (payload) => {
|
|
307
|
+
const stats = await this.compactWithHooks(0, undefined, false, payload.strategy);
|
|
308
|
+
if (stats)
|
|
309
|
+
payload.stats = { before: stats.before, after: stats.after, evictedCount: stats.evictedCount };
|
|
310
|
+
return payload;
|
|
311
|
+
});
|
|
300
312
|
// Prior-session preamble (non-blocking). Both the read and the
|
|
301
313
|
// layout go through advisable handlers.
|
|
302
314
|
Promise.resolve(this.handlers.call("history:read-recent"))
|
|
@@ -469,11 +481,12 @@ export class AgentLoop {
|
|
|
469
481
|
* compaction, emit `conversation:after-compact` so listeners
|
|
470
482
|
* (metrics, UI, agent-awareness notes) can react.
|
|
471
483
|
*/
|
|
472
|
-
compactWithHooks(target, keepRecent, force) {
|
|
484
|
+
compactWithHooks(target, keepRecent, force, strategy) {
|
|
473
485
|
const stats = this.handlers.call("conversation:compact", {
|
|
474
486
|
target,
|
|
475
487
|
keepRecent,
|
|
476
488
|
force: !!force,
|
|
489
|
+
strategy,
|
|
477
490
|
});
|
|
478
491
|
if (stats) {
|
|
479
492
|
this.bus.emit("conversation:after-compact", {
|
|
@@ -819,7 +832,24 @@ export class AgentLoop {
|
|
|
819
832
|
// Compaction strategy — default delegates to the two-tier pin
|
|
820
833
|
// strategy in ConversationState; advisors replace wholesale.
|
|
821
834
|
h.define("conversation:compact", (opts) => {
|
|
822
|
-
|
|
835
|
+
const strategy = opts.strategy;
|
|
836
|
+
// Synthesize a CompactResult for manual edits so conversation:after-compact
|
|
837
|
+
// listeners (metrics, file-read cache, system-prompt cache) still run.
|
|
838
|
+
if (strategy?.kind === "rewind" || strategy?.kind === "replace") {
|
|
839
|
+
const before = this.conversation.estimatePromptTokens();
|
|
840
|
+
const beforeLen = this.conversation.getMessages().length;
|
|
841
|
+
const next = strategy.kind === "rewind"
|
|
842
|
+
? this.conversation.getMessages().slice(0, strategy.toIndex)
|
|
843
|
+
: strategy.messages;
|
|
844
|
+
this.conversation.replaceMessages(next);
|
|
845
|
+
const after = this.conversation.estimatePromptTokens();
|
|
846
|
+
const afterLen = this.conversation.getMessages().length;
|
|
847
|
+
return { before, after, evictedCount: Math.max(0, beforeLen - afterLen) };
|
|
848
|
+
}
|
|
849
|
+
const tgt = strategy?.kind === "two-tier-pin" ? strategy.target : opts.target;
|
|
850
|
+
const keep = strategy?.kind === "two-tier-pin" ? strategy.keepRecent : opts.keepRecent;
|
|
851
|
+
const force = strategy?.kind === "two-tier-pin" ? strategy.force : opts.force;
|
|
852
|
+
return this.conversation.compact(tgt, keep, force);
|
|
823
853
|
});
|
|
824
854
|
// Inject a system note mid-loop — used by extensions (subagents,
|
|
825
855
|
// peer messages) to deliver async results into the next iteration.
|
|
@@ -1599,12 +1629,15 @@ export class AgentLoop {
|
|
|
1599
1629
|
tc.argumentsJson = "{}";
|
|
1600
1630
|
}
|
|
1601
1631
|
}
|
|
1632
|
+
// Echo reasoning only for modes that opt in (e.g. DeepSeek-R1).
|
|
1602
1633
|
const extras = {};
|
|
1603
|
-
if (
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
.
|
|
1634
|
+
if (this.currentMode.echoReasoning) {
|
|
1635
|
+
if (reasoning && reasoningField)
|
|
1636
|
+
extras[reasoningField] = reasoning;
|
|
1637
|
+
if (reasoningDetailsByIndex.size > 0) {
|
|
1638
|
+
extras.reasoning_details = [...reasoningDetailsByIndex.entries()]
|
|
1639
|
+
.sort((a, b) => a[0] - b[0]).map(([, v]) => v);
|
|
1640
|
+
}
|
|
1608
1641
|
}
|
|
1609
1642
|
return {
|
|
1610
1643
|
text,
|
package/dist/agent/skills.js
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
import * as fs from "node:fs";
|
|
14
14
|
import * as path from "node:path";
|
|
15
15
|
import * as os from "node:os";
|
|
16
|
-
import { getSettings } from "../settings.js";
|
|
16
|
+
import { CONFIG_DIR, getSettings } from "../settings.js";
|
|
17
17
|
/** Parse YAML frontmatter from a SKILL.md file. */
|
|
18
18
|
function parseFrontmatter(content) {
|
|
19
19
|
const match = content.match(/^---\s*\n([\s\S]*?)\n---\s*\n([\s\S]*)$/);
|
|
@@ -133,7 +133,7 @@ export function discoverGlobalSkills() {
|
|
|
133
133
|
return _cachedGlobalSkills;
|
|
134
134
|
const seen = new Set();
|
|
135
135
|
const skills = [];
|
|
136
|
-
addUnique(skills, scanDir(path.join(
|
|
136
|
+
addUnique(skills, scanDir(path.join(CONFIG_DIR, "skills")), seen);
|
|
137
137
|
const settings = getSettings();
|
|
138
138
|
for (const p of settings.skillPaths ?? []) {
|
|
139
139
|
addUnique(skills, scanDir(path.resolve(expandHome(p))), seen);
|
|
@@ -14,9 +14,8 @@ export function formatSkillsBlock(skills) {
|
|
|
14
14
|
+ "Load a skill's full content with read_file on its file path when needed.\n\n"
|
|
15
15
|
+ skills.map(s => `- **${s.name}**: ${s.description}\n Path: ${s.filePath}`).join("\n\n");
|
|
16
16
|
}
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
const GLOBAL_AGENTS_MD = path.join(os.homedir(), ".agent-sh", "AGENTS.md");
|
|
17
|
+
import { CONFIG_DIR } from "../settings.js";
|
|
18
|
+
const GLOBAL_AGENTS_MD = path.join(CONFIG_DIR, "AGENTS.md");
|
|
20
19
|
// ── File caches ─────────────────────────────────────────────────────
|
|
21
20
|
// Convention files (CLAUDE.md/AGENT.md) are walked synchronously from
|
|
22
21
|
// CWD to root on every query. In practice they almost never change,
|
package/dist/core.js
CHANGED
|
@@ -27,9 +27,8 @@ import { TerminalBuffer } from "./utils/terminal-buffer.js";
|
|
|
27
27
|
import crypto from "node:crypto";
|
|
28
28
|
import * as fs from "node:fs";
|
|
29
29
|
import * as path from "node:path";
|
|
30
|
-
import * as os from "node:os";
|
|
31
30
|
import { DefaultCompositor, StdoutSurface } from "./utils/compositor.js";
|
|
32
|
-
|
|
31
|
+
import { CONFIG_DIR } from "./settings.js";
|
|
33
32
|
// Re-export types that library consumers need
|
|
34
33
|
export { EventBus } from "./event-bus.js";
|
|
35
34
|
export { palette, setPalette, resetPalette } from "./utils/palette.js";
|
|
@@ -43,6 +42,7 @@ export function createCore(config) {
|
|
|
43
42
|
// short enough to read/remember. Legacy content may have 16-char iids; any
|
|
44
43
|
// parsers should accept ≥6 hex chars.
|
|
45
44
|
const instanceId = crypto.randomBytes(3).toString("hex");
|
|
45
|
+
bus.setSource(instanceId);
|
|
46
46
|
const settings = settingsMod.getSettings();
|
|
47
47
|
// Expose raw CLI config so the agent backend extension can resolve
|
|
48
48
|
// providers and create the LLM client.
|
|
@@ -170,7 +170,7 @@ export function createCore(config) {
|
|
|
170
170
|
createFencedBlockTransform: (o) => streamTransform.createFencedBlockTransform(bus, o),
|
|
171
171
|
getExtensionSettings: settingsMod.getExtensionSettings,
|
|
172
172
|
getStoragePath: (namespace) => {
|
|
173
|
-
const dir = path.join(
|
|
173
|
+
const dir = path.join(CONFIG_DIR, namespace);
|
|
174
174
|
fs.mkdirSync(dir, { recursive: true });
|
|
175
175
|
return dir;
|
|
176
176
|
},
|
|
@@ -188,6 +188,7 @@ export function createCore(config) {
|
|
|
188
188
|
list: () => handlers.list(),
|
|
189
189
|
get terminalBuffer() { return getTerminalBuffer(); },
|
|
190
190
|
compositor,
|
|
191
|
+
onDispose: () => { },
|
|
191
192
|
createRemoteSession: (opts) => {
|
|
192
193
|
const { surface } = opts;
|
|
193
194
|
const cleanups = [];
|
package/dist/event-bus.d.ts
CHANGED
|
@@ -233,6 +233,30 @@ export interface ShellEvents {
|
|
|
233
233
|
totalTokens: number;
|
|
234
234
|
budgetTokens: number;
|
|
235
235
|
};
|
|
236
|
+
"context:snapshot": {
|
|
237
|
+
messages: unknown[];
|
|
238
|
+
contextWindow: number;
|
|
239
|
+
activeTokens: number;
|
|
240
|
+
};
|
|
241
|
+
"context:compact": {
|
|
242
|
+
strategy?: {
|
|
243
|
+
kind: "two-tier-pin";
|
|
244
|
+
target: number;
|
|
245
|
+
keepRecent?: number;
|
|
246
|
+
force?: boolean;
|
|
247
|
+
} | {
|
|
248
|
+
kind: "rewind";
|
|
249
|
+
toIndex: number;
|
|
250
|
+
} | {
|
|
251
|
+
kind: "replace";
|
|
252
|
+
messages: unknown[];
|
|
253
|
+
};
|
|
254
|
+
stats?: {
|
|
255
|
+
before: number;
|
|
256
|
+
after: number;
|
|
257
|
+
evictedCount: number;
|
|
258
|
+
};
|
|
259
|
+
};
|
|
236
260
|
"agent:register-backend": {
|
|
237
261
|
name: string;
|
|
238
262
|
kill: () => void;
|
|
@@ -291,6 +315,7 @@ export interface ShellEvents {
|
|
|
291
315
|
id: string;
|
|
292
316
|
reasoning?: boolean;
|
|
293
317
|
contextWindow?: number;
|
|
318
|
+
echoReasoning?: boolean;
|
|
294
319
|
})[];
|
|
295
320
|
/** Provider supports the reasoning_effort parameter. Default: true. */
|
|
296
321
|
supportsReasoningEffort?: boolean;
|
|
@@ -357,6 +382,14 @@ export type ContentBlock = {
|
|
|
357
382
|
type Listener<T> = (payload: T) => void;
|
|
358
383
|
type PipeListener<T> = (payload: T) => T;
|
|
359
384
|
type AsyncPipeListener<T> = (payload: T) => T | Promise<T>;
|
|
385
|
+
/** Envelope stamped on every emitted event. */
|
|
386
|
+
export interface BusMeta {
|
|
387
|
+
source: string;
|
|
388
|
+
ts: number;
|
|
389
|
+
id: string;
|
|
390
|
+
name: string;
|
|
391
|
+
}
|
|
392
|
+
export type AnyListener = (name: string, payload: unknown, meta: BusMeta) => void;
|
|
360
393
|
/**
|
|
361
394
|
* Typed event bus with two modes:
|
|
362
395
|
* - emit/on/off: fire-and-forget notifications
|
|
@@ -367,12 +400,25 @@ export declare class EventBus {
|
|
|
367
400
|
private emitter;
|
|
368
401
|
private pipeListeners;
|
|
369
402
|
private asyncPipeListeners;
|
|
403
|
+
private source;
|
|
404
|
+
private nextSeq;
|
|
405
|
+
private anyListeners;
|
|
406
|
+
/** Set the source id stamped onto every emitted event. */
|
|
407
|
+
setSource(src: string): void;
|
|
408
|
+
/** Subscribe to every emitted event with full envelope. Returns unsubscribe. */
|
|
409
|
+
onAny(fn: AnyListener): () => void;
|
|
410
|
+
/** Stamp + dispatch — used by every emit path. */
|
|
411
|
+
private dispatch;
|
|
370
412
|
/** Subscribe to a fire-and-forget event. */
|
|
371
413
|
on<K extends keyof ShellEvents>(event: K, fn: Listener<ShellEvents[K]>): void;
|
|
372
414
|
/** Unsubscribe from a fire-and-forget event. */
|
|
373
415
|
off<K extends keyof ShellEvents>(event: K, fn: Listener<ShellEvents[K]>): void;
|
|
374
416
|
/** Emit a fire-and-forget event. */
|
|
375
417
|
emit<K extends keyof ShellEvents>(event: K, payload: ShellEvents[K]): void;
|
|
418
|
+
/** Re-dispatch an event with externally-supplied meta. Used by bridges
|
|
419
|
+
* and replay tools to preserve the original source/ts/id of remote or
|
|
420
|
+
* recorded events instead of restamping them as locally originated. */
|
|
421
|
+
relay(meta: BusMeta, payload: unknown): void;
|
|
376
422
|
/**
|
|
377
423
|
* Transform-then-notify: run the payload through any registered pipe
|
|
378
424
|
* listeners (transforms), then emit the final result to regular `on`
|
package/dist/event-bus.js
CHANGED
|
@@ -9,6 +9,40 @@ export class EventBus {
|
|
|
9
9
|
emitter = new EventEmitter().setMaxListeners(0);
|
|
10
10
|
pipeListeners = new Map();
|
|
11
11
|
asyncPipeListeners = new Map();
|
|
12
|
+
source = "0000";
|
|
13
|
+
nextSeq = 0;
|
|
14
|
+
anyListeners = [];
|
|
15
|
+
/** Set the source id stamped onto every emitted event. */
|
|
16
|
+
setSource(src) {
|
|
17
|
+
this.source = src;
|
|
18
|
+
}
|
|
19
|
+
/** Subscribe to every emitted event with full envelope. Returns unsubscribe. */
|
|
20
|
+
onAny(fn) {
|
|
21
|
+
this.anyListeners.push(fn);
|
|
22
|
+
return () => {
|
|
23
|
+
const i = this.anyListeners.indexOf(fn);
|
|
24
|
+
if (i !== -1)
|
|
25
|
+
this.anyListeners.splice(i, 1);
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
/** Stamp + dispatch — used by every emit path. */
|
|
29
|
+
dispatch(name, payload) {
|
|
30
|
+
if (this.anyListeners.length > 0) {
|
|
31
|
+
const meta = {
|
|
32
|
+
source: this.source,
|
|
33
|
+
ts: Date.now(),
|
|
34
|
+
id: `${this.source}:${this.nextSeq++}`,
|
|
35
|
+
name,
|
|
36
|
+
};
|
|
37
|
+
for (const fn of this.anyListeners) {
|
|
38
|
+
try {
|
|
39
|
+
fn(name, payload, meta);
|
|
40
|
+
}
|
|
41
|
+
catch { /* swallow */ }
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
this.emitter.emit(name, payload);
|
|
45
|
+
}
|
|
12
46
|
/** Subscribe to a fire-and-forget event. */
|
|
13
47
|
on(event, fn) {
|
|
14
48
|
this.emitter.on(event, fn);
|
|
@@ -19,7 +53,21 @@ export class EventBus {
|
|
|
19
53
|
}
|
|
20
54
|
/** Emit a fire-and-forget event. */
|
|
21
55
|
emit(event, payload) {
|
|
22
|
-
this.
|
|
56
|
+
this.dispatch(event, payload);
|
|
57
|
+
}
|
|
58
|
+
/** Re-dispatch an event with externally-supplied meta. Used by bridges
|
|
59
|
+
* and replay tools to preserve the original source/ts/id of remote or
|
|
60
|
+
* recorded events instead of restamping them as locally originated. */
|
|
61
|
+
relay(meta, payload) {
|
|
62
|
+
if (this.anyListeners.length > 0) {
|
|
63
|
+
for (const fn of this.anyListeners) {
|
|
64
|
+
try {
|
|
65
|
+
fn(meta.name, payload, meta);
|
|
66
|
+
}
|
|
67
|
+
catch { /* swallow */ }
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
this.emitter.emit(meta.name, payload);
|
|
23
71
|
}
|
|
24
72
|
/**
|
|
25
73
|
* Transform-then-notify: run the payload through any registered pipe
|
|
@@ -38,7 +86,7 @@ export class EventBus {
|
|
|
38
86
|
}
|
|
39
87
|
transformed = payload; // fall back to untransformed
|
|
40
88
|
}
|
|
41
|
-
this.
|
|
89
|
+
this.dispatch(event, transformed);
|
|
42
90
|
}
|
|
43
91
|
/** Register a transform listener for a pipeline event. */
|
|
44
92
|
onPipe(event, fn) {
|
|
@@ -103,7 +151,7 @@ export class EventBus {
|
|
|
103
151
|
*/
|
|
104
152
|
async emitPipeAsync(event, payload) {
|
|
105
153
|
// Phase 1: notify (lets renderers prepare for interactive I/O)
|
|
106
|
-
this.
|
|
154
|
+
this.dispatch(event, payload);
|
|
107
155
|
// Phase 2: transform (extensions provide decisions)
|
|
108
156
|
const listeners = this.asyncPipeListeners.get(event);
|
|
109
157
|
if (!listeners)
|
package/dist/extension-loader.js
CHANGED
|
@@ -96,6 +96,7 @@ function createScopedContext(ctx, extensionName) {
|
|
|
96
96
|
registerTool: scopedRegisterTool,
|
|
97
97
|
unregisterTool: ctx.unregisterTool,
|
|
98
98
|
registerCommand: scopedRegisterCommand,
|
|
99
|
+
onDispose: (fn) => { cleanups.push(fn); },
|
|
99
100
|
};
|
|
100
101
|
const dispose = () => {
|
|
101
102
|
for (const fn of cleanups) {
|
|
@@ -32,6 +32,7 @@ export default function agentBackend(ctx) {
|
|
|
32
32
|
contextWindow: mc?.contextWindow ?? p.contextWindow,
|
|
33
33
|
reasoning: mc?.reasoning,
|
|
34
34
|
supportsReasoningEffort: p.supportsReasoningEffort,
|
|
35
|
+
echoReasoning: mc?.echoReasoning,
|
|
35
36
|
});
|
|
36
37
|
}
|
|
37
38
|
}
|
|
@@ -135,7 +136,7 @@ export default function agentBackend(ctx) {
|
|
|
135
136
|
}
|
|
136
137
|
else {
|
|
137
138
|
modelIds.push(m.id);
|
|
138
|
-
caps.set(m.id, { reasoning: m.reasoning, contextWindow: m.contextWindow });
|
|
139
|
+
caps.set(m.id, { reasoning: m.reasoning, contextWindow: m.contextWindow, echoReasoning: m.echoReasoning });
|
|
139
140
|
}
|
|
140
141
|
}
|
|
141
142
|
providerRegistry.set(p.id, {
|
|
@@ -156,6 +157,7 @@ export default function agentBackend(ctx) {
|
|
|
156
157
|
contextWindow: mc?.contextWindow,
|
|
157
158
|
reasoning: mc?.reasoning,
|
|
158
159
|
supportsReasoningEffort: p.supportsReasoningEffort,
|
|
160
|
+
echoReasoning: mc?.echoReasoning,
|
|
159
161
|
};
|
|
160
162
|
});
|
|
161
163
|
bus.emit("config:add-modes", { modes: addModes });
|
|
@@ -197,6 +199,7 @@ export default function agentBackend(ctx) {
|
|
|
197
199
|
contextWindow: mc?.contextWindow ?? p.contextWindow,
|
|
198
200
|
reasoning: mc?.reasoning,
|
|
199
201
|
supportsReasoningEffort: p.supportsReasoningEffort,
|
|
202
|
+
echoReasoning: mc?.echoReasoning,
|
|
200
203
|
};
|
|
201
204
|
});
|
|
202
205
|
bus.emit("config:set-modes", { modes: newModes });
|
|
@@ -1,5 +1,11 @@
|
|
|
1
|
+
import { getSettings } from "../settings.js";
|
|
1
2
|
const BASE_URL = "https://openrouter.ai/api/v1";
|
|
2
3
|
const DEFAULT_MODELS = ["anthropic/claude-sonnet-4.6"];
|
|
4
|
+
// Built-in defaults for models requiring reasoning_content echoed back
|
|
5
|
+
// (server 400s without it). Extend or override in settings.json:
|
|
6
|
+
// providers.openrouter.echoReasoningPatterns = ["deepseek", "..."]
|
|
7
|
+
// providers.openrouter.models[*].echoReasoning = true | false
|
|
8
|
+
const BUILTIN_ECHO_REASONING_PATTERNS = [/deepseek/i];
|
|
3
9
|
export default function activate(ctx) {
|
|
4
10
|
const apiKey = process.env.OPENROUTER_API_KEY;
|
|
5
11
|
if (!apiKey)
|
|
@@ -14,6 +20,8 @@ export default function activate(ctx) {
|
|
|
14
20
|
fetchModels(apiKey).then((models) => {
|
|
15
21
|
if (models.length === 0)
|
|
16
22
|
return;
|
|
23
|
+
const userOverrides = readUserOverrides();
|
|
24
|
+
const patterns = readEchoPatterns();
|
|
17
25
|
ctx.bus.emit("provider:register", {
|
|
18
26
|
id: "openrouter",
|
|
19
27
|
apiKey,
|
|
@@ -24,10 +32,34 @@ export default function activate(ctx) {
|
|
|
24
32
|
id: m.id,
|
|
25
33
|
reasoning: m.supported_parameters?.includes("reasoning") ?? false,
|
|
26
34
|
contextWindow: m.context_length,
|
|
35
|
+
echoReasoning: userOverrides.get(m.id) ?? patterns.some((re) => re.test(m.id)),
|
|
27
36
|
})),
|
|
28
37
|
});
|
|
29
38
|
}).catch(() => { });
|
|
30
39
|
}
|
|
40
|
+
function readEchoPatterns() {
|
|
41
|
+
const userPatterns = getSettings().providers?.openrouter?.echoReasoningPatterns ?? [];
|
|
42
|
+
const compiled = [];
|
|
43
|
+
for (const src of userPatterns) {
|
|
44
|
+
try {
|
|
45
|
+
compiled.push(new RegExp(src, "i"));
|
|
46
|
+
}
|
|
47
|
+
catch { /* skip invalid pattern */ }
|
|
48
|
+
}
|
|
49
|
+
return [...BUILTIN_ECHO_REASONING_PATTERNS, ...compiled];
|
|
50
|
+
}
|
|
51
|
+
function readUserOverrides() {
|
|
52
|
+
const out = new Map();
|
|
53
|
+
const models = getSettings().providers?.openrouter?.models;
|
|
54
|
+
if (!Array.isArray(models))
|
|
55
|
+
return out;
|
|
56
|
+
for (const m of models) {
|
|
57
|
+
if (typeof m === "object" && m && m.echoReasoning !== undefined) {
|
|
58
|
+
out.set(m.id, m.echoReasoning);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return out;
|
|
62
|
+
}
|
|
31
63
|
async function fetchModels(apiKey) {
|
|
32
64
|
const res = await fetch(`${BASE_URL}/models`, {
|
|
33
65
|
headers: { Authorization: `Bearer ${apiKey}` },
|
package/dist/init.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
|
-
import
|
|
4
|
-
const CONFIG_DIR = path.join(os.homedir(), ".agent-sh");
|
|
3
|
+
import { CONFIG_DIR } from "./settings.js";
|
|
5
4
|
const EXTENSIONS_DIR = path.join(CONFIG_DIR, "extensions");
|
|
6
5
|
const SETTINGS_PATH = path.join(CONFIG_DIR, "settings.json");
|
|
7
6
|
const EXAMPLE_PATH = path.join(CONFIG_DIR, "settings.example.json");
|
package/dist/settings.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
/** Root config directory. Override via AGENT_SH_HOME for isolated instances
|
|
2
|
+
* (testing, multi-agent setups). Path is resolved at module load. */
|
|
1
3
|
export declare const CONFIG_DIR: string;
|
|
2
4
|
/** Per-model capability overrides. */
|
|
3
5
|
export interface ModelCapabilityConfig {
|
|
@@ -7,6 +9,8 @@ export interface ModelCapabilityConfig {
|
|
|
7
9
|
reasoning?: boolean;
|
|
8
10
|
/** Context window size in tokens for this specific model. */
|
|
9
11
|
contextWindow?: number;
|
|
12
|
+
/** Echo reasoning_content back on assistant turns. Required by DeepSeek. */
|
|
13
|
+
echoReasoning?: boolean;
|
|
10
14
|
}
|
|
11
15
|
/** Provider profile — a named LLM configuration. */
|
|
12
16
|
export interface ProviderConfig {
|
|
@@ -20,6 +24,9 @@ export interface ProviderConfig {
|
|
|
20
24
|
models?: (string | ModelCapabilityConfig)[];
|
|
21
25
|
/** Context window size in tokens (e.g. 128000). Used for usage display. */
|
|
22
26
|
contextWindow?: number;
|
|
27
|
+
/** Case-insensitive regex sources matched against model id; matches default
|
|
28
|
+
* to echoReasoning=true. Per-model echoReasoning still wins. */
|
|
29
|
+
echoReasoningPatterns?: string[];
|
|
23
30
|
}
|
|
24
31
|
export interface Settings {
|
|
25
32
|
/** Extensions to load (npm packages or file paths). */
|
|
@@ -136,6 +143,7 @@ export interface ResolvedProvider {
|
|
|
136
143
|
modelCapabilities?: Map<string, {
|
|
137
144
|
reasoning?: boolean;
|
|
138
145
|
contextWindow?: number;
|
|
146
|
+
echoReasoning?: boolean;
|
|
139
147
|
}>;
|
|
140
148
|
}
|
|
141
149
|
/**
|
package/dist/settings.js
CHANGED
|
@@ -7,7 +7,11 @@
|
|
|
7
7
|
import * as fs from "node:fs";
|
|
8
8
|
import * as path from "node:path";
|
|
9
9
|
import * as os from "node:os";
|
|
10
|
-
|
|
10
|
+
/** Root config directory. Override via AGENT_SH_HOME for isolated instances
|
|
11
|
+
* (testing, multi-agent setups). Path is resolved at module load. */
|
|
12
|
+
export const CONFIG_DIR = process.env.AGENT_SH_HOME
|
|
13
|
+
? path.resolve(process.env.AGENT_SH_HOME)
|
|
14
|
+
: path.join(os.homedir(), ".agent-sh");
|
|
11
15
|
const SETTINGS_PATH = path.join(CONFIG_DIR, "settings.json");
|
|
12
16
|
const DEFAULTS = {
|
|
13
17
|
extensions: [],
|
|
@@ -143,8 +147,8 @@ export function resolveProvider(name) {
|
|
|
143
147
|
}
|
|
144
148
|
else {
|
|
145
149
|
modelIds.push(m.id);
|
|
146
|
-
if (m.reasoning !== undefined || m.contextWindow !== undefined) {
|
|
147
|
-
caps.set(m.id, { reasoning: m.reasoning, contextWindow: m.contextWindow });
|
|
150
|
+
if (m.reasoning !== undefined || m.contextWindow !== undefined || m.echoReasoning !== undefined) {
|
|
151
|
+
caps.set(m.id, { reasoning: m.reasoning, contextWindow: m.contextWindow, echoReasoning: m.echoReasoning });
|
|
148
152
|
}
|
|
149
153
|
}
|
|
150
154
|
}
|
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
import type { EventBus } from "../event-bus.js";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
* InputHandler never touches the PTY or EventBus directly —
|
|
5
|
-
* it goes through this interface for all cross-cutting concerns.
|
|
6
|
-
*/
|
|
2
|
+
import { TuiInputView } from "./tui-input-view.js";
|
|
3
|
+
/** Narrow contract between InputHandler and its host (Shell). */
|
|
7
4
|
export interface InputContext {
|
|
8
5
|
isForegroundBusy(): boolean;
|
|
9
6
|
getCwd(): string;
|
|
@@ -13,6 +10,7 @@ export interface InputContext {
|
|
|
13
10
|
redrawPrompt(): void;
|
|
14
11
|
freshPrompt(): void;
|
|
15
12
|
}
|
|
13
|
+
/** Line editor + shell-passthrough buffer. Delegates rendering to TuiInputView. */
|
|
16
14
|
export declare class InputHandler {
|
|
17
15
|
private ctx;
|
|
18
16
|
private lineBuffer;
|
|
@@ -24,15 +22,13 @@ export declare class InputHandler {
|
|
|
24
22
|
private autocompleteActive;
|
|
25
23
|
private autocompleteIndex;
|
|
26
24
|
private autocompleteItems;
|
|
27
|
-
private autocompleteLines;
|
|
28
25
|
private history;
|
|
29
26
|
private historyIndex;
|
|
30
27
|
private savedBuffer;
|
|
31
|
-
private cursorRowsBelow;
|
|
32
|
-
private cursorTermCol;
|
|
33
28
|
private escapeTimer;
|
|
34
29
|
private bus;
|
|
35
30
|
private onShowAgentInfo;
|
|
31
|
+
private view;
|
|
36
32
|
constructor(opts: {
|
|
37
33
|
ctx: InputContext;
|
|
38
34
|
bus: EventBus;
|
|
@@ -40,29 +36,23 @@ export declare class InputHandler {
|
|
|
40
36
|
info: string;
|
|
41
37
|
model?: string;
|
|
42
38
|
};
|
|
39
|
+
view?: TuiInputView;
|
|
43
40
|
});
|
|
44
41
|
private registerMode;
|
|
45
42
|
private loadHistory;
|
|
46
43
|
private saveHistory;
|
|
47
|
-
|
|
48
|
-
private writeModePromptLine;
|
|
44
|
+
private drawPrompt;
|
|
49
45
|
handleInput(data: string): void;
|
|
50
46
|
private enterMode;
|
|
51
47
|
private exitMode;
|
|
52
|
-
/** Move to the start of the prompt area and clear everything below. */
|
|
53
|
-
private clearPromptArea;
|
|
54
48
|
printPrompt(): void;
|
|
55
|
-
/**
|
|
56
|
-
*
|
|
57
|
-
* handler re-entered a mode (so caller should skip shell prompt).
|
|
58
|
-
*/
|
|
49
|
+
/** Called when agent processing completes. Returns true if the input
|
|
50
|
+
* handler re-entered a mode (so caller should skip shell prompt). */
|
|
59
51
|
handleProcessingDone(): boolean;
|
|
60
52
|
private renderModeInput;
|
|
61
53
|
private updateAutocomplete;
|
|
62
|
-
private renderAutocomplete;
|
|
63
54
|
private applyAutocomplete;
|
|
64
55
|
private dismissAutocomplete;
|
|
65
|
-
private clearAutocompleteLines;
|
|
66
56
|
private handleModeInput;
|
|
67
57
|
private processModeActions;
|
|
68
58
|
}
|