agent-sh 0.9.0 → 0.10.1
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 +25 -30
- package/dist/agent/agent-loop.d.ts +43 -6
- package/dist/agent/agent-loop.js +817 -157
- package/dist/agent/conversation-state.d.ts +72 -21
- package/dist/agent/conversation-state.js +364 -151
- package/dist/agent/history-file.d.ts +13 -4
- package/dist/agent/history-file.js +110 -36
- package/dist/agent/nuclear-form.d.ts +28 -3
- package/dist/agent/nuclear-form.js +84 -3
- package/dist/agent/skills.d.ts +2 -4
- package/dist/agent/skills.js +10 -4
- package/dist/agent/subagent.d.ts +23 -0
- package/dist/agent/subagent.js +53 -11
- package/dist/agent/system-prompt.d.ts +34 -1
- package/dist/agent/system-prompt.js +96 -47
- package/dist/agent/token-budget.d.ts +10 -13
- package/dist/agent/token-budget.js +6 -46
- package/dist/agent/tool-protocol.d.ts +23 -1
- package/dist/agent/tool-protocol.js +169 -4
- package/dist/agent/tools/bash.js +3 -3
- package/dist/agent/tools/edit-file.js +9 -6
- package/dist/agent/tools/glob.js +4 -2
- package/dist/agent/tools/grep.js +27 -3
- package/dist/agent/tools/ls.js +5 -6
- package/dist/agent/types.d.ts +1 -2
- package/dist/context-manager.d.ts +16 -19
- package/dist/context-manager.js +48 -152
- package/dist/core.js +27 -6
- package/dist/event-bus.d.ts +59 -3
- package/dist/executor.d.ts +4 -3
- package/dist/executor.js +18 -15
- package/dist/extension-loader.js +75 -17
- package/dist/extensions/agent-backend.d.ts +8 -7
- package/dist/extensions/agent-backend.js +72 -50
- package/dist/extensions/index.js +0 -2
- package/dist/extensions/slash-commands.js +14 -9
- package/dist/extensions/tui-renderer.js +67 -80
- package/dist/index.js +25 -6
- package/dist/settings.d.ts +39 -16
- package/dist/settings.js +51 -11
- package/dist/shell/input-handler.d.ts +2 -1
- package/dist/shell/input-handler.js +84 -76
- package/dist/shell/shell.js +19 -2
- package/dist/types.d.ts +15 -0
- package/dist/utils/ansi.d.ts +7 -0
- package/dist/utils/ansi.js +69 -8
- package/dist/utils/box-frame.js +8 -2
- package/dist/utils/compositor.d.ts +5 -0
- package/dist/utils/compositor.js +31 -3
- package/dist/utils/diff-renderer.d.ts +9 -0
- package/dist/utils/diff-renderer.js +221 -143
- package/dist/utils/diff.d.ts +21 -2
- package/dist/utils/diff.js +165 -89
- package/dist/utils/handler-registry.d.ts +5 -0
- package/dist/utils/handler-registry.js +6 -0
- package/dist/utils/line-editor.d.ts +11 -1
- package/dist/utils/line-editor.js +44 -5
- package/dist/utils/markdown.js +23 -8
- 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/dist/utils/tool-display.d.ts +1 -1
- package/dist/utils/tool-display.js +4 -4
- package/examples/extensions/ash-acp-bridge/src/index.ts +4 -1
- package/examples/extensions/ash-mcp-bridge/index.ts +13 -3
- package/examples/extensions/claude-code-bridge/README.md +14 -0
- package/examples/extensions/claude-code-bridge/index.ts +204 -145
- package/examples/extensions/claude-code-bridge/package.json +1 -0
- package/examples/extensions/interactive-prompts.ts +39 -25
- package/examples/extensions/overlay-agent.ts +3 -3
- package/examples/extensions/peer-mesh.ts +115 -0
- package/examples/extensions/pi-bridge/README.md +16 -0
- package/examples/extensions/pi-bridge/index.ts +9 -155
- package/examples/extensions/questionnaire.ts +16 -5
- package/examples/extensions/subagents.ts +19 -4
- package/examples/extensions/terminal-buffer.ts +163 -0
- package/examples/extensions/user-shell.ts +136 -0
- package/examples/extensions/web-access.ts +8 -0
- package/package.json +36 -2
- package/dist/agent/tools/display.d.ts +0 -13
- package/dist/agent/tools/display.js +0 -70
- package/dist/agent/tools/user-shell.d.ts +0 -13
- package/dist/agent/tools/user-shell.js +0 -87
- package/dist/extensions/shell-recall.d.ts +0 -9
- package/dist/extensions/shell-recall.js +0 -8
- package/dist/extensions/terminal-buffer.d.ts +0 -14
- package/dist/extensions/terminal-buffer.js +0 -134
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
*/
|
|
13
13
|
import { highlight } from "cli-highlight";
|
|
14
14
|
import { MarkdownRenderer, wrapLine, MAX_CONTENT_WIDTH } from "../utils/markdown.js";
|
|
15
|
+
import { DEFAULT_CONTEXT_WINDOW } from "../agent/token-budget.js";
|
|
15
16
|
import { createFencedBlockTransform } from "../utils/stream-transform.js";
|
|
16
17
|
import { palette as p } from "../utils/palette.js";
|
|
17
18
|
import { renderToolCall, createSpinner, formatElapsed, SPINNER_FRAMES, } from "../utils/tool-display.js";
|
|
@@ -63,16 +64,21 @@ function createRenderState() {
|
|
|
63
64
|
isThinking: false,
|
|
64
65
|
showThinkingText: false,
|
|
65
66
|
thinkingPending: false,
|
|
66
|
-
lastTruncatedDiff: null,
|
|
67
67
|
};
|
|
68
68
|
}
|
|
69
69
|
export default function activate(ctx) {
|
|
70
70
|
const { bus, define, compositor } = ctx;
|
|
71
71
|
const s = createRenderState();
|
|
72
|
+
/** Track the shell's cwd so path shortening is relative to where the user actually is. */
|
|
73
|
+
let shellCwd = process.cwd();
|
|
74
|
+
bus.on("shell:cwd-change", (e) => { shellCwd = e.cwd; });
|
|
72
75
|
/** Shorthand — get the current agent surface. */
|
|
73
76
|
function out() { return compositor.surface("agent"); }
|
|
74
|
-
/** Capped width for borders, tool lines, and content — keeps everything aligned.
|
|
75
|
-
|
|
77
|
+
/** Capped width for borders, tool lines, and content — keeps everything aligned.
|
|
78
|
+
* MarkdownRenderer.writeLine prepends a 2-char indent (" ") to every line,
|
|
79
|
+
* so available width for actual content is columns - 2. Subtract an additional
|
|
80
|
+
* 1 to prevent terminal auto-wrap when a line lands exactly at the right edge. */
|
|
81
|
+
function cappedW() { return Math.min(MAX_CONTENT_WIDTH + 2, out().columns) - 2 - 1; }
|
|
76
82
|
// Gate: other extensions (e.g. overlay) can advise this to suppress
|
|
77
83
|
// TUI rendering of agent output while they own the display.
|
|
78
84
|
define("tui:should-render-agent", () => true);
|
|
@@ -226,7 +232,7 @@ export default function activate(ctx) {
|
|
|
226
232
|
s.isThinking = false;
|
|
227
233
|
if (pendingUsage && s.renderer) {
|
|
228
234
|
const { prompt_tokens, completion_tokens } = pendingUsage;
|
|
229
|
-
const maxTokens = backendInfo?.contextWindow ??
|
|
235
|
+
const maxTokens = backendInfo?.contextWindow ?? DEFAULT_CONTEXT_WINDOW;
|
|
230
236
|
s.renderer.writeLine("");
|
|
231
237
|
s.renderer.writeLine(ctx.call("tui:render-usage", prompt_tokens, completion_tokens, maxTokens));
|
|
232
238
|
drain();
|
|
@@ -379,17 +385,37 @@ export default function activate(ctx) {
|
|
|
379
385
|
s.renderer.flush();
|
|
380
386
|
drain();
|
|
381
387
|
}
|
|
388
|
+
// Diff rendering is handled in the async pipe below so it can yield
|
|
389
|
+
// to the event loop between hunks (keeping the spinner responsive).
|
|
390
|
+
});
|
|
391
|
+
// Async pipe: render diffs via the tui:render-diff handler (extensions can
|
|
392
|
+
// advise to customize). Runs after the sync `on` handler above (which
|
|
393
|
+
// flushes state) and before shell.ts's pipe (which pauses stdout).
|
|
394
|
+
bus.onPipeAsync("permission:request", async (e) => {
|
|
395
|
+
if (!shouldRender())
|
|
396
|
+
return e;
|
|
382
397
|
if (e.kind === "file-write" && e.metadata?.diff) {
|
|
383
398
|
showCollapsedThinking();
|
|
384
|
-
|
|
399
|
+
const lines = ctx.call("tui:render-diff", e.title, e.metadata.diff, cappedW());
|
|
400
|
+
if (lines.length > 0) {
|
|
401
|
+
if (!s.renderer)
|
|
402
|
+
startAgentResponse();
|
|
403
|
+
contentGap("diff");
|
|
404
|
+
for (const line of lines)
|
|
405
|
+
s.renderer.writeLine(line);
|
|
406
|
+
drain();
|
|
407
|
+
}
|
|
408
|
+
// The diff box IS the visual representation of the upcoming tool call.
|
|
409
|
+
// Mark lastContentKind as "tool" so the tool call line that follows
|
|
410
|
+
// doesn't inject an extra gap between the diff box and the checkmark.
|
|
411
|
+
s.lastContentKind = "tool";
|
|
385
412
|
}
|
|
386
413
|
// Don't endAgentResponse() here — permission requests that aren't
|
|
387
414
|
// file-write diffs are handled inline (auto-approved or by extensions).
|
|
388
415
|
// Closing the response prematurely causes double separator borders.
|
|
416
|
+
return e;
|
|
389
417
|
});
|
|
390
418
|
bus.on("input:keypress", (e) => {
|
|
391
|
-
if (e.key === "\x0f")
|
|
392
|
-
expandLastDiff(); // Ctrl+O
|
|
393
419
|
if (e.key === "\x14")
|
|
394
420
|
toggleThinkingDisplay(); // Ctrl+T
|
|
395
421
|
});
|
|
@@ -518,9 +544,23 @@ export default function activate(ctx) {
|
|
|
518
544
|
}
|
|
519
545
|
let highlighted;
|
|
520
546
|
try {
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
547
|
+
// highlight.js warns to console.error for unsupported languages (elisp, org, etc).
|
|
548
|
+
// Suppress so it doesn't leak into the terminal.
|
|
549
|
+
const origError = console.error;
|
|
550
|
+
console.error = (...args) => {
|
|
551
|
+
const msg = args.join(" ");
|
|
552
|
+
if (msg.includes("Could not find the language"))
|
|
553
|
+
return;
|
|
554
|
+
origError.apply(console, args);
|
|
555
|
+
};
|
|
556
|
+
try {
|
|
557
|
+
highlighted = language
|
|
558
|
+
? highlight(code, { language })
|
|
559
|
+
: highlight(code); // auto-detect
|
|
560
|
+
}
|
|
561
|
+
finally {
|
|
562
|
+
console.error = origError;
|
|
563
|
+
}
|
|
524
564
|
}
|
|
525
565
|
catch {
|
|
526
566
|
highlighted = code;
|
|
@@ -574,6 +614,17 @@ export default function activate(ctx) {
|
|
|
574
614
|
}
|
|
575
615
|
return [];
|
|
576
616
|
});
|
|
617
|
+
/**
|
|
618
|
+
* Default renderer for standalone diffs (e.g. permission prompts).
|
|
619
|
+
* Extensions can advise this to customize diff rendering:
|
|
620
|
+
*
|
|
621
|
+
* ctx.advise("tui:render-diff", (next, filePath, diff, width) => {
|
|
622
|
+
* return myCustomDiffBox(filePath, diff, width);
|
|
623
|
+
* });
|
|
624
|
+
*/
|
|
625
|
+
define("tui:render-diff", (filePath, diff, width) => {
|
|
626
|
+
return renderDiffBody(diff, filePath, width);
|
|
627
|
+
});
|
|
577
628
|
/** Render a diff as framed box lines (pure — no TUI state side effects). */
|
|
578
629
|
function renderDiffBody(diff, filePath, width) {
|
|
579
630
|
if (diff.isIdentical)
|
|
@@ -586,18 +637,8 @@ export default function activate(ctx) {
|
|
|
586
637
|
maxLines: getSettings().diffMaxLines,
|
|
587
638
|
trueColor: true,
|
|
588
639
|
});
|
|
589
|
-
const lastLine = diffLines[diffLines.length - 1] ?? "";
|
|
590
|
-
const isTruncated = lastLine.includes("… ");
|
|
591
|
-
if (isTruncated) {
|
|
592
|
-
s.lastTruncatedDiff = { filePath, diff, expanded: false };
|
|
593
|
-
}
|
|
594
|
-
else {
|
|
595
|
-
s.lastTruncatedDiff = null;
|
|
596
|
-
}
|
|
597
640
|
const body = diffLines.length > 1 ? ["", ...diffLines.slice(1), ""] : diffLines;
|
|
598
|
-
const footer =
|
|
599
|
-
? [` ${p.dim}ctrl+o to expand${p.reset}`]
|
|
600
|
-
: undefined;
|
|
641
|
+
const footer = undefined;
|
|
601
642
|
return renderBoxFrame(body, {
|
|
602
643
|
width: boxW,
|
|
603
644
|
style: "rounded",
|
|
@@ -625,11 +666,10 @@ export default function activate(ctx) {
|
|
|
625
666
|
function extractDetail(extra) {
|
|
626
667
|
if (extra.locations && extra.locations.length > 0) {
|
|
627
668
|
const loc = extra.locations[0];
|
|
628
|
-
const cwd = process.cwd();
|
|
629
669
|
const home = process.env.HOME;
|
|
630
670
|
let fp = loc.path;
|
|
631
|
-
if (fp.startsWith(
|
|
632
|
-
fp = fp.slice(
|
|
671
|
+
if (fp.startsWith(shellCwd + "/"))
|
|
672
|
+
fp = fp.slice(shellCwd.length + 1);
|
|
633
673
|
else if (home && fp.startsWith(home + "/"))
|
|
634
674
|
fp = "~/" + fp.slice(home.length + 1);
|
|
635
675
|
return loc.line ? `${fp}:${loc.line}` : fp;
|
|
@@ -642,11 +682,10 @@ export default function activate(ctx) {
|
|
|
642
682
|
if (typeof raw.pattern === "string")
|
|
643
683
|
return raw.pattern;
|
|
644
684
|
if (typeof raw.path === "string") {
|
|
645
|
-
const cwd = process.cwd();
|
|
646
685
|
const home = process.env.HOME;
|
|
647
686
|
let fp = raw.path;
|
|
648
|
-
if (fp.startsWith(
|
|
649
|
-
fp = fp.slice(
|
|
687
|
+
if (fp.startsWith(shellCwd + "/"))
|
|
688
|
+
fp = fp.slice(shellCwd.length + 1);
|
|
650
689
|
else if (home && fp.startsWith(home + "/"))
|
|
651
690
|
fp = "~/" + fp.slice(home.length + 1);
|
|
652
691
|
return fp;
|
|
@@ -674,7 +713,7 @@ export default function activate(ctx) {
|
|
|
674
713
|
locations: extra?.locations,
|
|
675
714
|
rawInput: extra?.rawInput,
|
|
676
715
|
displayDetail: extra?.displayDetail,
|
|
677
|
-
}, cappedW());
|
|
716
|
+
}, cappedW(), shellCwd);
|
|
678
717
|
if (extra?.groupContinuation && lines.length > 0) {
|
|
679
718
|
// Swap the colored kind icon for a muted tree connector,
|
|
680
719
|
// and strip the tool name prefix — show detail only.
|
|
@@ -879,58 +918,6 @@ export default function activate(ctx) {
|
|
|
879
918
|
: `${p.success}+${diff.added}${p.reset} ${p.error}-${diff.removed}${p.reset}`;
|
|
880
919
|
return `${p.dim}${filePath}${p.reset} ${stats}`;
|
|
881
920
|
}
|
|
882
|
-
function showFileDiff(filePath, diff) {
|
|
883
|
-
if (diff.isIdentical)
|
|
884
|
-
return;
|
|
885
|
-
contentGap("diff");
|
|
886
|
-
const lines = ctx.call("render:result-body", { kind: "diff", diff, filePath }, cappedW()) ?? [];
|
|
887
|
-
if (!s.renderer)
|
|
888
|
-
startAgentResponse();
|
|
889
|
-
for (const line of lines) {
|
|
890
|
-
s.renderer.writeLine(line);
|
|
891
|
-
}
|
|
892
|
-
drain();
|
|
893
|
-
}
|
|
894
|
-
function expandLastDiff() {
|
|
895
|
-
if (!s.lastTruncatedDiff)
|
|
896
|
-
return;
|
|
897
|
-
const entry = s.lastTruncatedDiff;
|
|
898
|
-
entry.expanded = !entry.expanded;
|
|
899
|
-
if (!entry.expanded) {
|
|
900
|
-
showFileDiffCached(entry);
|
|
901
|
-
return;
|
|
902
|
-
}
|
|
903
|
-
if (!entry.expandedLines) {
|
|
904
|
-
const { filePath, diff } = entry;
|
|
905
|
-
const boxW = Math.min(cappedW() - 2, out().columns - 2); // -2 for writeLine indent
|
|
906
|
-
const contentW = boxW - 4;
|
|
907
|
-
const diffLines = renderDiff(diff, {
|
|
908
|
-
width: contentW,
|
|
909
|
-
filePath,
|
|
910
|
-
maxLines: 500,
|
|
911
|
-
trueColor: true,
|
|
912
|
-
});
|
|
913
|
-
const body = diffLines.length > 1 ? ["", ...diffLines.slice(1), ""] : diffLines;
|
|
914
|
-
entry.expandedLines = renderBoxFrame(body, {
|
|
915
|
-
width: boxW,
|
|
916
|
-
style: "rounded",
|
|
917
|
-
borderColor: p.dim,
|
|
918
|
-
title: diffTitle(filePath, diff),
|
|
919
|
-
footer: [` ${p.dim}ctrl+o to collapse${p.reset}`],
|
|
920
|
-
});
|
|
921
|
-
}
|
|
922
|
-
out().write("\n");
|
|
923
|
-
for (const line of entry.expandedLines) {
|
|
924
|
-
out().write(line + "\n");
|
|
925
|
-
}
|
|
926
|
-
}
|
|
927
|
-
function showFileDiffCached(entry) {
|
|
928
|
-
const lines = ctx.call("render:result-body", { kind: "diff", diff: entry.diff, filePath: entry.filePath }, cappedW()) ?? [];
|
|
929
|
-
out().write("\n");
|
|
930
|
-
for (const line of lines) {
|
|
931
|
-
out().write(line + "\n");
|
|
932
|
-
}
|
|
933
|
-
}
|
|
934
921
|
function toggleThinkingDisplay() {
|
|
935
922
|
s.showThinkingText = !s.showThinkingText;
|
|
936
923
|
if (s.spinner) {
|
package/dist/index.js
CHANGED
|
@@ -15,6 +15,13 @@ import { discoverSkills } from "./agent/skills.js";
|
|
|
15
15
|
*/
|
|
16
16
|
async function captureShellEnvAsync(shell) {
|
|
17
17
|
return new Promise((resolve) => {
|
|
18
|
+
let settled = false;
|
|
19
|
+
const done = (result) => {
|
|
20
|
+
if (settled)
|
|
21
|
+
return;
|
|
22
|
+
settled = true;
|
|
23
|
+
resolve(result);
|
|
24
|
+
};
|
|
18
25
|
try {
|
|
19
26
|
const shellName = path.basename(shell);
|
|
20
27
|
const isZsh = shellName.includes("zsh");
|
|
@@ -30,8 +37,9 @@ async function captureShellEnvAsync(shell) {
|
|
|
30
37
|
output += data.toString("utf-8");
|
|
31
38
|
});
|
|
32
39
|
child.on("close", (code) => {
|
|
40
|
+
clearTimeout(timer);
|
|
33
41
|
if (code !== 0 || !output) {
|
|
34
|
-
|
|
42
|
+
done({});
|
|
35
43
|
return;
|
|
36
44
|
}
|
|
37
45
|
const env = {};
|
|
@@ -40,18 +48,19 @@ async function captureShellEnvAsync(shell) {
|
|
|
40
48
|
if (eq > 0)
|
|
41
49
|
env[entry.slice(0, eq)] = entry.slice(eq + 1);
|
|
42
50
|
}
|
|
43
|
-
|
|
51
|
+
done(env);
|
|
44
52
|
});
|
|
45
53
|
child.on("error", () => {
|
|
46
|
-
|
|
54
|
+
clearTimeout(timer);
|
|
55
|
+
done({});
|
|
47
56
|
});
|
|
48
|
-
setTimeout(() => {
|
|
57
|
+
const timer = setTimeout(() => {
|
|
49
58
|
child.kill("SIGTERM");
|
|
50
|
-
|
|
59
|
+
done({});
|
|
51
60
|
}, 5000);
|
|
52
61
|
}
|
|
53
62
|
catch {
|
|
54
|
-
|
|
63
|
+
done({});
|
|
55
64
|
}
|
|
56
65
|
});
|
|
57
66
|
}
|
|
@@ -245,6 +254,9 @@ async function main() {
|
|
|
245
254
|
if (process.env.DEBUG) {
|
|
246
255
|
console.error('[agent-sh] Extensions loaded');
|
|
247
256
|
}
|
|
257
|
+
// Tell deferred-init listeners (agent-backend) that the provider
|
|
258
|
+
// registry is now complete.
|
|
259
|
+
core.bus.emit("core:extensions-loaded", {});
|
|
248
260
|
// ── Discover skills ───────────────────────────────────────────
|
|
249
261
|
const skills = discoverSkills(process.cwd());
|
|
250
262
|
// ── Activate agent backend ────────────────────────────────────
|
|
@@ -281,6 +293,13 @@ async function main() {
|
|
|
281
293
|
sections += `\n ${p.dim}${s.name}${p.reset}`;
|
|
282
294
|
}
|
|
283
295
|
}
|
|
296
|
+
const extSections = bus.emitPipe("banner:collect", { sections: [] }).sections;
|
|
297
|
+
for (const sec of extSections) {
|
|
298
|
+
sections += `\n\n ${p.muted}${sec.label}:${p.reset}`;
|
|
299
|
+
for (const item of sec.items) {
|
|
300
|
+
sections += `\n ${p.dim}${item}${p.reset}`;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
284
303
|
const hint = `${p.muted}Type ${p.warning}>${p.muted} to ask AI · ${p.warning}>/help${p.muted} for commands${p.reset}`;
|
|
285
304
|
const borderLine = `${p.muted}${"─".repeat(bannerW)}${p.reset}`;
|
|
286
305
|
process.stdout.write("\n" + borderLine + "\n" +
|
package/dist/settings.d.ts
CHANGED
|
@@ -32,44 +32,56 @@ export interface Settings {
|
|
|
32
32
|
defaultProvider?: string;
|
|
33
33
|
/** Preferred agent backend (extension name, e.g. "pi", "claude-code"). */
|
|
34
34
|
defaultBackend?: string;
|
|
35
|
-
/**
|
|
36
|
-
contextWindowSize?: number;
|
|
37
|
-
/** Context budget in bytes (~4 chars per token). */
|
|
38
|
-
contextBudget?: number;
|
|
39
|
-
/** Shell output lines before truncation kicks in. */
|
|
35
|
+
/** Shell output lines before spill-to-tempfile kicks in. */
|
|
40
36
|
shellTruncateThreshold?: number;
|
|
41
|
-
/** Lines kept from start of
|
|
37
|
+
/** Lines kept from start of spilled shell output. */
|
|
42
38
|
shellHeadLines?: number;
|
|
43
|
-
/** Lines kept from end of
|
|
39
|
+
/** Lines kept from end of spilled shell output. */
|
|
44
40
|
shellTailLines?: number;
|
|
45
|
-
/** Max lines for recall expand before requiring line ranges. */
|
|
46
|
-
recallExpandMaxLines?: number;
|
|
47
|
-
/** Fraction of content budget allocated to shell context (0-1, default 0.35). */
|
|
48
|
-
shellContextRatio?: number;
|
|
49
41
|
/** Max history file size in bytes (default: 102400 = 100KB). */
|
|
50
42
|
historyMaxBytes?: number;
|
|
51
43
|
/** Number of prior history entries to load on startup (default: 50). */
|
|
52
44
|
historyStartupEntries?: number;
|
|
53
|
-
/** Max nuclear entries kept in-context before flushing to history file (default: 200). */
|
|
54
|
-
nuclearMaxEntries?: number;
|
|
55
45
|
/** Auto-compact threshold as fraction of conversation budget (0-1, default 0.5). */
|
|
56
46
|
autoCompactThreshold?: number;
|
|
57
47
|
/** Max command output lines shown inline in TUI. */
|
|
58
48
|
maxCommandOutputLines?: number;
|
|
59
49
|
/** Max read tool output lines shown inline in TUI (0 = hide). */
|
|
60
50
|
readOutputMaxLines?: number;
|
|
61
|
-
/** Max diff lines
|
|
51
|
+
/** Max diff lines rendered in the TUI (Infinity = no limit). */
|
|
62
52
|
diffMaxLines?: number;
|
|
63
|
-
/** Tool protocol:
|
|
64
|
-
|
|
53
|
+
/** Tool protocol:
|
|
54
|
+
* "api" — all tools sent with full schema.
|
|
55
|
+
* "deferred" — extensions dispatched through `use_extension(name, args)` meta-tool.
|
|
56
|
+
* "deferred-lookup" — extensions loaded on demand via `load_tool(names[])`; once loaded, callable as first-class tools.
|
|
57
|
+
* "inline" — tools described as text.
|
|
58
|
+
*/
|
|
59
|
+
toolMode?: "api" | "deferred" | "deferred-lookup" | "inline";
|
|
65
60
|
/** Additional directories to scan for skills (supports ~ expansion). */
|
|
66
61
|
skillPaths?: string[];
|
|
62
|
+
/**
|
|
63
|
+
* Enable the "diagnose" tool — lets the agent evaluate JavaScript
|
|
64
|
+
* expressions against its own runtime state. Powerful for introspection
|
|
65
|
+
* (e.g. this.conversation.turns.length) but grants arbitrary code
|
|
66
|
+
* execution within the agent process. Off by default because the
|
|
67
|
+
* agent already has unrestricted bash access — this is a convenience,
|
|
68
|
+
* not a new capability.
|
|
69
|
+
*/
|
|
70
|
+
diagnose?: boolean;
|
|
67
71
|
/** Show a startup banner when agent-sh launches. */
|
|
68
72
|
startupBanner?: boolean;
|
|
69
73
|
/** Show a subtle agent-sh indicator in the shell prompt. */
|
|
70
74
|
promptIndicator?: boolean;
|
|
71
75
|
/** Names of built-in extensions to disable (e.g. ["command-suggest"]). */
|
|
72
76
|
disabledBuiltins?: string[];
|
|
77
|
+
/**
|
|
78
|
+
* Names of user extensions in ~/.agent-sh/extensions/ to skip when
|
|
79
|
+
* auto-discovering. Match by basename without extension for files
|
|
80
|
+
* (e.g. "peer-mesh" matches peer-mesh.ts), or by directory name for
|
|
81
|
+
* directory-style extensions (e.g. "superash" matches superash/index.ts).
|
|
82
|
+
* Beats having to rename files to .disabled every time.
|
|
83
|
+
*/
|
|
84
|
+
disabledExtensions?: string[];
|
|
73
85
|
}
|
|
74
86
|
declare const DEFAULTS: Required<Settings>;
|
|
75
87
|
/** Load settings from disk (cached after first call). */
|
|
@@ -87,6 +99,17 @@ export declare function getSettings(): Settings & typeof DEFAULTS;
|
|
|
87
99
|
export declare function getExtensionSettings<T extends Record<string, unknown>>(namespace: string, defaults: T): T;
|
|
88
100
|
/** Reset cached settings (for testing or after external edit). */
|
|
89
101
|
export declare function reloadSettings(): void;
|
|
102
|
+
/**
|
|
103
|
+
* Deep-merge a patch into ~/.agent-sh/settings.json on disk.
|
|
104
|
+
*
|
|
105
|
+
* Reads the raw file (preserving unknown keys), merges the patch, writes back
|
|
106
|
+
* with 2-space indentation, and clears the cache so subsequent getSettings()
|
|
107
|
+
* calls see the new values.
|
|
108
|
+
*
|
|
109
|
+
* Used by runtime controls (`/model`, `/backend`) that want their selection
|
|
110
|
+
* to persist as the default across restarts.
|
|
111
|
+
*/
|
|
112
|
+
export declare function updateSettings(patch: Record<string, unknown>): void;
|
|
90
113
|
/**
|
|
91
114
|
* Expand $ENV_VAR references in a string.
|
|
92
115
|
* Supports $VAR and ${VAR} syntax.
|
package/dist/settings.js
CHANGED
|
@@ -16,24 +16,21 @@ const DEFAULTS = {
|
|
|
16
16
|
defaultProvider: undefined,
|
|
17
17
|
defaultBackend: "ash",
|
|
18
18
|
toolMode: "api",
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
recallExpandMaxLines: 100,
|
|
25
|
-
shellContextRatio: 0.35,
|
|
26
|
-
historyMaxBytes: 102400,
|
|
27
|
-
historyStartupEntries: 50,
|
|
28
|
-
nuclearMaxEntries: 200,
|
|
19
|
+
shellTruncateThreshold: 20,
|
|
20
|
+
shellHeadLines: 10,
|
|
21
|
+
shellTailLines: 10,
|
|
22
|
+
historyMaxBytes: 104857600, // 100MB — history is only accessed via search/expand, never loaded wholesale
|
|
23
|
+
historyStartupEntries: 100,
|
|
29
24
|
autoCompactThreshold: 0.5,
|
|
30
25
|
maxCommandOutputLines: 3,
|
|
31
26
|
readOutputMaxLines: 10,
|
|
32
|
-
diffMaxLines:
|
|
27
|
+
diffMaxLines: Infinity,
|
|
33
28
|
skillPaths: [],
|
|
29
|
+
diagnose: false,
|
|
34
30
|
startupBanner: true,
|
|
35
31
|
promptIndicator: true,
|
|
36
32
|
disabledBuiltins: [],
|
|
33
|
+
disabledExtensions: [],
|
|
37
34
|
};
|
|
38
35
|
let cached = null;
|
|
39
36
|
/** Load settings from disk (cached after first call). */
|
|
@@ -74,6 +71,49 @@ export function getExtensionSettings(namespace, defaults) {
|
|
|
74
71
|
export function reloadSettings() {
|
|
75
72
|
cached = null;
|
|
76
73
|
}
|
|
74
|
+
/**
|
|
75
|
+
* Deep-merge a patch into ~/.agent-sh/settings.json on disk.
|
|
76
|
+
*
|
|
77
|
+
* Reads the raw file (preserving unknown keys), merges the patch, writes back
|
|
78
|
+
* with 2-space indentation, and clears the cache so subsequent getSettings()
|
|
79
|
+
* calls see the new values.
|
|
80
|
+
*
|
|
81
|
+
* Used by runtime controls (`/model`, `/backend`) that want their selection
|
|
82
|
+
* to persist as the default across restarts.
|
|
83
|
+
*/
|
|
84
|
+
export function updateSettings(patch) {
|
|
85
|
+
let existing = {};
|
|
86
|
+
try {
|
|
87
|
+
const raw = fs.readFileSync(SETTINGS_PATH, "utf-8");
|
|
88
|
+
existing = JSON.parse(raw);
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
// file missing or unreadable — start fresh
|
|
92
|
+
}
|
|
93
|
+
const merged = deepMerge(existing, patch);
|
|
94
|
+
try {
|
|
95
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
96
|
+
fs.writeFileSync(SETTINGS_PATH, JSON.stringify(merged, null, 2) + "\n", "utf-8");
|
|
97
|
+
cached = null;
|
|
98
|
+
}
|
|
99
|
+
catch (err) {
|
|
100
|
+
console.error(`[agent-sh] Warning: failed to update ${SETTINGS_PATH}: ${err.message}`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
function deepMerge(target, source) {
|
|
104
|
+
const out = { ...target };
|
|
105
|
+
for (const [key, val] of Object.entries(source)) {
|
|
106
|
+
const existing = out[key];
|
|
107
|
+
if (val !== null && typeof val === "object" && !Array.isArray(val) &&
|
|
108
|
+
existing !== null && typeof existing === "object" && !Array.isArray(existing)) {
|
|
109
|
+
out[key] = deepMerge(existing, val);
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
out[key] = val;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return out;
|
|
116
|
+
}
|
|
77
117
|
/**
|
|
78
118
|
* Expand $ENV_VAR references in a string.
|
|
79
119
|
* Supports $VAR and ${VAR} syntax.
|