agent-sh 0.14.8 → 0.14.10

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.
Files changed (58) hide show
  1. package/dist/agent/agent-loop.d.ts +0 -4
  2. package/dist/agent/agent-loop.js +8 -166
  3. package/dist/agent/entry-format.d.ts +5 -0
  4. package/dist/agent/entry-format.js +9 -0
  5. package/dist/agent/extensions/rolling-history/constants.d.ts +1 -0
  6. package/dist/agent/extensions/rolling-history/constants.js +1 -0
  7. package/dist/agent/extensions/rolling-history/index.d.ts +4 -0
  8. package/dist/agent/extensions/rolling-history/index.js +203 -0
  9. package/dist/agent/extensions/rolling-history/recall.d.ts +4 -0
  10. package/dist/agent/extensions/rolling-history/recall.js +122 -0
  11. package/dist/agent/extensions/rolling-history/strategy.d.ts +70 -0
  12. package/dist/agent/extensions/rolling-history/strategy.js +336 -0
  13. package/dist/agent/host-types.d.ts +0 -3
  14. package/dist/agent/index.js +50 -5
  15. package/dist/agent/live-view.d.ts +57 -0
  16. package/dist/agent/live-view.js +238 -0
  17. package/dist/agent/llm-client.d.ts +1 -0
  18. package/dist/agent/llm-client.js +1 -1
  19. package/dist/agent/providers/ollama.d.ts +11 -0
  20. package/dist/agent/providers/ollama.js +72 -0
  21. package/dist/agent/providers/opencode.d.ts +10 -0
  22. package/dist/agent/providers/opencode.js +112 -0
  23. package/dist/agent/providers/zai-coding-plan.d.ts +5 -0
  24. package/dist/agent/providers/zai-coding-plan.js +26 -0
  25. package/dist/agent/session-store.d.ts +90 -0
  26. package/dist/agent/session-store.js +288 -0
  27. package/dist/agent/store.d.ts +74 -0
  28. package/dist/agent/store.js +284 -0
  29. package/dist/agent/subagent.js +2 -2
  30. package/dist/agent/tool-protocol.d.ts +11 -11
  31. package/dist/cli/args.js +2 -2
  32. package/dist/cli/index.js +4 -2
  33. package/dist/core/index.d.ts +0 -1
  34. package/dist/core/index.js +0 -1
  35. package/dist/core/settings.d.ts +5 -1
  36. package/dist/core/settings.js +62 -1
  37. package/dist/extensions/index.d.ts +1 -0
  38. package/dist/shell/events.d.ts +1 -0
  39. package/dist/shell/input-handler.js +4 -0
  40. package/dist/shell/tui-renderer.js +5 -2
  41. package/dist/utils/diff-renderer.js +9 -7
  42. package/examples/extensions/ads/index.ts +695 -0
  43. package/examples/extensions/ash-acp-bridge/src/index.ts +1 -2
  44. package/examples/extensions/ash-scheme/index.ts +77 -3
  45. package/examples/extensions/ashi/package.json +2 -2
  46. package/examples/extensions/ashi/src/capture.ts +1 -1
  47. package/examples/extensions/ashi/src/cli.ts +5 -6
  48. package/examples/extensions/ashi/src/compaction.ts +6 -2
  49. package/examples/extensions/ashi/src/frontend.ts +13 -13
  50. package/examples/extensions/ashi/src/multi-session-store.ts +35 -12
  51. package/examples/extensions/ashi/src/session-commands.ts +1 -1
  52. package/examples/extensions/ashi/src/user-shell-intents.ts +17 -0
  53. package/package.json +13 -1
  54. package/dist/agent/conversation-state.d.ts +0 -142
  55. package/dist/agent/conversation-state.js +0 -788
  56. package/dist/agent/history-file.d.ts +0 -81
  57. package/dist/agent/history-file.js +0 -271
  58. package/examples/extensions/ashi/src/session-store.ts +0 -363
@@ -11,7 +11,7 @@
11
11
  */
12
12
  import type { ChatCompletionTool } from "./llm-client.js";
13
13
  import { type ToolDefinition } from "./types.js";
14
- import type { ConversationState } from "./conversation-state.js";
14
+ import type { LiveView } from "./live-view.js";
15
15
  export interface PendingToolCall {
16
16
  id: string;
17
17
  name: string;
@@ -39,9 +39,9 @@ export interface ToolProtocol {
39
39
  /** Rewrite a tool call before execution (e.g., unwrap meta-tool). */
40
40
  rewriteToolCall(tc: PendingToolCall): PendingToolCall;
41
41
  /** Record the assistant turn in conversation state. */
42
- recordAssistant(conv: ConversationState, text: string, toolCalls: PendingToolCall[], extras?: Record<string, unknown>): void;
42
+ recordAssistant(conv: LiveView, text: string, toolCalls: PendingToolCall[], extras?: Record<string, unknown>): void;
43
43
  /** Record all tool results for a batch as conversation messages. */
44
- recordResults(conv: ConversationState, results: ToolResult[]): void;
44
+ recordResults(conv: LiveView, results: ToolResult[]): void;
45
45
  /** Create a stream filter for stripping tool calls from display. null = pass-through. */
46
46
  createStreamFilter(toolNames: string[]): StreamFilter | null;
47
47
  /**
@@ -57,8 +57,8 @@ export declare class ApiToolProtocol implements ToolProtocol {
57
57
  getToolPrompt(): string;
58
58
  extractToolCalls(_text: string, streamedCalls: PendingToolCall[]): PendingToolCall[];
59
59
  rewriteToolCall(tc: PendingToolCall): PendingToolCall;
60
- recordAssistant(conv: ConversationState, text: string, toolCalls: PendingToolCall[], extras?: Record<string, unknown>): void;
61
- recordResults(conv: ConversationState, results: ToolResult[]): void;
60
+ recordAssistant(conv: LiveView, text: string, toolCalls: PendingToolCall[], extras?: Record<string, unknown>): void;
61
+ recordResults(conv: LiveView, results: ToolResult[]): void;
62
62
  createStreamFilter(): null;
63
63
  }
64
64
  export declare class InlineToolProtocol implements ToolProtocol {
@@ -68,8 +68,8 @@ export declare class InlineToolProtocol implements ToolProtocol {
68
68
  getToolPrompt(tools: ToolDefinition[]): string;
69
69
  rewriteToolCall(tc: PendingToolCall): PendingToolCall;
70
70
  extractToolCalls(text: string, _streamedCalls: PendingToolCall[]): PendingToolCall[];
71
- recordAssistant(conv: ConversationState, text: string, _toolCalls: PendingToolCall[], extras?: Record<string, unknown>): void;
72
- recordResults(conv: ConversationState, results: ToolResult[]): void;
71
+ recordAssistant(conv: LiveView, text: string, _toolCalls: PendingToolCall[], extras?: Record<string, unknown>): void;
72
+ recordResults(conv: LiveView, results: ToolResult[]): void;
73
73
  createStreamFilter(_toolNames: string[]): StreamFilter;
74
74
  }
75
75
  export declare class DeferredToolProtocol implements ToolProtocol {
@@ -82,8 +82,8 @@ export declare class DeferredToolProtocol implements ToolProtocol {
82
82
  getToolPrompt(): string;
83
83
  extractToolCalls(_text: string, streamedCalls: PendingToolCall[]): PendingToolCall[];
84
84
  rewriteToolCall(tc: PendingToolCall): PendingToolCall;
85
- recordAssistant(conv: ConversationState, text: string, toolCalls: PendingToolCall[], extras?: Record<string, unknown>): void;
86
- recordResults(conv: ConversationState, results: ToolResult[]): void;
85
+ recordAssistant(conv: LiveView, text: string, toolCalls: PendingToolCall[], extras?: Record<string, unknown>): void;
86
+ recordResults(conv: LiveView, results: ToolResult[]): void;
87
87
  createStreamFilter(): null;
88
88
  }
89
89
  export declare class DeferredLookupProtocol implements ToolProtocol {
@@ -97,8 +97,8 @@ export declare class DeferredLookupProtocol implements ToolProtocol {
97
97
  getToolPrompt(): string;
98
98
  extractToolCalls(_text: string, streamedCalls: PendingToolCall[]): PendingToolCall[];
99
99
  rewriteToolCall(tc: PendingToolCall): PendingToolCall;
100
- recordAssistant(conv: ConversationState, text: string, toolCalls: PendingToolCall[], extras?: Record<string, unknown>): void;
101
- recordResults(conv: ConversationState, results: ToolResult[]): void;
100
+ recordAssistant(conv: LiveView, text: string, toolCalls: PendingToolCall[], extras?: Record<string, unknown>): void;
101
+ recordResults(conv: LiveView, results: ToolResult[]): void;
102
102
  createStreamFilter(): null;
103
103
  getProtocolTools(): ToolDefinition[];
104
104
  }
package/dist/cli/args.js CHANGED
@@ -53,8 +53,8 @@ export function parseArgs(argv, env = process.env) {
53
53
  let provider;
54
54
  let backend;
55
55
  let shell = env.SHELL || "/bin/bash";
56
- let apiKey = env.OPENAI_API_KEY;
57
- let baseURL = env.OPENAI_BASE_URL;
56
+ let apiKey;
57
+ let baseURL;
58
58
  for (let i = 0; i < argv.length; i++) {
59
59
  const arg = argv[i];
60
60
  if (arg === "--model" && argv[i + 1]) {
package/dist/cli/index.js CHANGED
@@ -4,6 +4,7 @@ import { activateAgent } from "../agent/index.js";
4
4
  import { createCore } from "../core/index.js";
5
5
  import { palette as p } from "../utils/palette.js";
6
6
  import { loadBuiltinExtensions } from "../extensions/index.js";
7
+ import activateRollingHistory from "../agent/extensions/rolling-history/index.js";
7
8
  import { loadExtensions } from "../core/extension-loader.js";
8
9
  import { getSettings } from "../core/settings.js";
9
10
  import { dispatchSubcommand } from "./subcommands.js";
@@ -95,7 +96,9 @@ async function main() {
95
96
  registerShellHandlers(extCtx);
96
97
  activateAgent(extCtx);
97
98
  // Load before spawning the shell so PS1 lands below the banner.
98
- await loadBuiltinExtensions(extCtx, getSettings().disabledBuiltins);
99
+ const settings = getSettings();
100
+ await loadBuiltinExtensions(extCtx, settings.disabledBuiltins);
101
+ activateRollingHistory(extCtx);
99
102
  const loadExtensionsTimeoutMs = 10000;
100
103
  let loadedExtensions = [];
101
104
  await Promise.race([
@@ -124,7 +127,6 @@ async function main() {
124
127
  hint);
125
128
  process.exit(1);
126
129
  }
127
- const settings = getSettings();
128
130
  if (settings.startupBanner !== false) {
129
131
  const termW = process.stdout.columns || 80;
130
132
  const bannerW = Math.min(termW, 60);
@@ -20,7 +20,6 @@ export type { ColorPalette } from "../utils/palette.js";
20
20
  export type { AgentBackend, ToolDefinition, ImageContent } from "../agent/types.js";
21
21
  export { runSubagent, type SubagentOptions } from "../agent/subagent.js";
22
22
  export { LlmClient } from "../agent/llm-client.js";
23
- export { HistoryFile, InMemoryHistory, NoopHistory, type HistoryAdapter } from "../agent/history-file.js";
24
23
  export type { NuclearEntry } from "../agent/nuclear-form.js";
25
24
  export { compileSearchRegex, matchEntry, formatNuclearLine } from "../agent/nuclear-form.js";
26
25
  export interface AgentShellCore {
@@ -19,7 +19,6 @@ export { EventBus } from "./event-bus.js";
19
19
  export { palette, setPalette, resetPalette } from "../utils/palette.js";
20
20
  export { runSubagent } from "../agent/subagent.js";
21
21
  export { LlmClient } from "../agent/llm-client.js";
22
- export { HistoryFile, InMemoryHistory, NoopHistory } from "../agent/history-file.js";
23
22
  export { compileSearchRegex, matchEntry, formatNuclearLine } from "../agent/nuclear-form.js";
24
23
  export function createCore(config) {
25
24
  const bus = new EventBus();
@@ -65,7 +65,7 @@ export interface Settings {
65
65
  * that boot agent-sh as a library against a specific working tree).
66
66
  */
67
67
  historyFilePath?: string;
68
- /** Auto-compact threshold as fraction of conversation budget (0-1, default 0.5). */
68
+ autoCompact?: boolean;
69
69
  autoCompactThreshold?: number;
70
70
  /** Max command output lines shown inline in TUI. */
71
71
  maxCommandOutputLines?: number;
@@ -116,8 +116,12 @@ export interface Settings {
116
116
  disabledExtensions?: string[];
117
117
  }
118
118
  declare const DEFAULTS: Required<Settings>;
119
+ export type SettingSource = "session" | "env" | "file" | "default";
119
120
  /** Load settings from disk (cached after first call). */
120
121
  export declare function getSettings(): Settings & typeof DEFAULTS;
122
+ export declare function setSessionOverlay(patch: Partial<Settings>): void;
123
+ export declare function clearSessionOverlay(...keys: (keyof Settings)[]): void;
124
+ export declare function getSettingSource(key: keyof Settings): SettingSource;
121
125
  /**
122
126
  * Get settings for an extension, namespaced under its key in settings.json.
123
127
  *
@@ -28,6 +28,7 @@ const DEFAULTS = {
28
28
  historyMaxBytes: 104857600, // 100MB — history is only accessed via search/expand, never loaded wholesale
29
29
  historyStartupEntries: 100,
30
30
  historyFilePath: undefined,
31
+ autoCompact: true,
31
32
  autoCompactThreshold: 0.5,
32
33
  maxCommandOutputLines: 3,
33
34
  readOutputMaxLines: 10,
@@ -41,6 +42,42 @@ const DEFAULTS = {
41
42
  disabledExtensions: [],
42
43
  };
43
44
  let cached = null;
45
+ let envOverrides = null;
46
+ let sessionOverlay = {};
47
+ function parseBoolEnv(raw, key) {
48
+ if (raw === undefined)
49
+ return undefined;
50
+ const v = raw.trim().toLowerCase();
51
+ if (v === "on" || v === "true" || v === "1")
52
+ return true;
53
+ if (v === "off" || v === "false" || v === "0")
54
+ return false;
55
+ console.error(`[agent-sh] Warning: ${key}="${raw}" is not a boolean (off|on|true|false|0|1); ignoring.`);
56
+ return undefined;
57
+ }
58
+ function parseUnitFloatEnv(raw, key) {
59
+ if (raw === undefined)
60
+ return undefined;
61
+ const n = Number(raw);
62
+ if (!Number.isFinite(n) || n < 0 || n > 1) {
63
+ console.error(`[agent-sh] Warning: ${key}="${raw}" is not a number in [0, 1]; ignoring.`);
64
+ return undefined;
65
+ }
66
+ return n;
67
+ }
68
+ function loadEnvOverrides() {
69
+ if (envOverrides)
70
+ return envOverrides;
71
+ const out = {};
72
+ const ac = parseBoolEnv(process.env.AGENT_SH_AUTO_COMPACT, "AGENT_SH_AUTO_COMPACT");
73
+ if (ac !== undefined)
74
+ out.autoCompact = ac;
75
+ const th = parseUnitFloatEnv(process.env.AGENT_SH_AUTO_COMPACT_THRESHOLD, "AGENT_SH_AUTO_COMPACT_THRESHOLD");
76
+ if (th !== undefined)
77
+ out.autoCompactThreshold = th;
78
+ envOverrides = out;
79
+ return envOverrides;
80
+ }
44
81
  /** Load settings from disk (cached after first call). */
45
82
  export function getSettings() {
46
83
  if (!cached) {
@@ -55,7 +92,29 @@ export function getSettings() {
55
92
  cached = {};
56
93
  }
57
94
  }
58
- return { ...DEFAULTS, ...cached };
95
+ return { ...DEFAULTS, ...cached, ...loadEnvOverrides(), ...sessionOverlay };
96
+ }
97
+ export function setSessionOverlay(patch) {
98
+ sessionOverlay = { ...sessionOverlay, ...patch };
99
+ }
100
+ export function clearSessionOverlay(...keys) {
101
+ if (keys.length === 0) {
102
+ sessionOverlay = {};
103
+ return;
104
+ }
105
+ const next = { ...sessionOverlay };
106
+ for (const k of keys)
107
+ delete next[k];
108
+ sessionOverlay = next;
109
+ }
110
+ export function getSettingSource(key) {
111
+ if (key in sessionOverlay)
112
+ return "session";
113
+ if (key in loadEnvOverrides())
114
+ return "env";
115
+ if (cached && key in cached)
116
+ return "file";
117
+ return "default";
59
118
  }
60
119
  /**
61
120
  * Get settings for an extension, namespaced under its key in settings.json.
@@ -78,6 +137,8 @@ export function getExtensionSettings(namespace, defaults) {
78
137
  /** Reset cached settings (for testing or after external edit). */
79
138
  export function reloadSettings() {
80
139
  cached = null;
140
+ envOverrides = null;
141
+ sessionOverlay = {};
81
142
  }
82
143
  /**
83
144
  * Deep-merge a patch into ~/.agent-sh/settings.json on disk.
@@ -3,6 +3,7 @@
3
3
  * Module-owned built-ins activate inline:
4
4
  * shell-context, tui-renderer → registerShellHandlers (src/shell/)
5
5
  * ash (a specific backend) → activateAgent (src/agent/)
6
+ * rolling-history → activateRollingHistory (src/cli/)
6
7
  * backend registry → createCore (src/core/)
7
8
  */
8
9
  import type { ExtensionContext } from "../shell/host-types.js";
@@ -67,6 +67,7 @@ declare module "../core/event-bus.js" {
67
67
  data: string;
68
68
  consumed: boolean;
69
69
  };
70
+ "input:redraw": Record<string, never>;
70
71
  "compositor:write": {
71
72
  stream: string;
72
73
  text: string;
@@ -35,6 +35,10 @@ export class InputHandler {
35
35
  if (this.activeMode)
36
36
  this.drawPrompt();
37
37
  });
38
+ this.bus.on("input:redraw", () => {
39
+ if (this.activeMode)
40
+ this.renderModeInput();
41
+ });
38
42
  this.bus.on("input-mode:register", (config) => {
39
43
  this.registerMode(config);
40
44
  });
@@ -408,11 +408,14 @@ export default function activate(ctx) {
408
408
  bus.on("ui:info", (e) => {
409
409
  stopCurrentSpinner();
410
410
  showInfo(e.message);
411
- // Restart spinner if agent is still processing
411
+ bus.emit("input:redraw", {});
412
412
  if (s.renderer)
413
413
  startThinkingSpinner();
414
414
  });
415
- bus.on("ui:error", (e) => showError(e.message));
415
+ bus.on("ui:error", (e) => {
416
+ showError(e.message);
417
+ bus.emit("input:redraw", {});
418
+ });
416
419
  bus.on("ui:suggestion", (e) => {
417
420
  compositor.surface("status").writeLine(`${p.dim}💡 ${e.text}${p.reset}`);
418
421
  });
@@ -249,13 +249,15 @@ function renderUnifiedHunk(hunk, layout) {
249
249
  const out = [];
250
250
  const pairs = findChangePairs(hunk);
251
251
  const renderedAsPartOfPair = new Set();
252
+ const bgWidth = Math.max(1, textWidth - noW - 3);
253
+ const gutter = (n) => `${p.dim}${n} │${p.reset} `;
252
254
  for (let i = 0; i < hunk.lines.length; i++) {
253
255
  const line = hunk.lines[i];
254
256
  const no = String(line.type === "removed" ? (line.oldNo ?? "") : (line.newNo ?? line.oldNo ?? "")).padStart(noW);
255
257
  if (line.type === "context") {
256
258
  const raw = truncateText(line.text, lineTextW);
257
259
  const text = lang ? highlightLine(raw, lang) : raw;
258
- out.push(` ${p.dim}${no} │${p.reset} ${p.dim}${text}${p.reset}`);
260
+ out.push(`${gutter(no)} ${p.dim}${text}${p.reset}`);
259
261
  continue;
260
262
  }
261
263
  if (line.type === "removed") {
@@ -275,17 +277,17 @@ function renderUnifiedHunk(hunk, layout) {
275
277
  removedText = lang ? highlightLine(raw, lang) : raw;
276
278
  }
277
279
  if (useTrueColor) {
278
- out.push(padToWidth(`${p.errorBg}${p.error}- ${no} │ ${preserveBg(removedText, p.errorBg)}`, textWidth) + p.reset);
280
+ out.push(gutter(no) + padToWidth(`${p.errorBg}${p.error}- ${preserveBg(removedText, p.errorBg)}`, bgWidth) + p.reset);
279
281
  }
280
282
  else {
281
- out.push(`${p.error}- ${no} │ ${removedText}${p.reset}`);
283
+ out.push(`${gutter(no)}${p.error}- ${removedText}${p.reset}`);
282
284
  }
283
285
  if (addedText !== null && addedNo !== null) {
284
286
  if (useTrueColor) {
285
- out.push(padToWidth(`${p.successBg}${p.success}+ ${addedNo} │ ${preserveBg(addedText, p.successBg)}`, textWidth) + p.reset);
287
+ out.push(gutter(addedNo) + padToWidth(`${p.successBg}${p.success}+ ${preserveBg(addedText, p.successBg)}`, bgWidth) + p.reset);
286
288
  }
287
289
  else {
288
- out.push(`${p.success}+ ${addedNo} │ ${addedText}${p.reset}`);
290
+ out.push(`${gutter(addedNo)}${p.success}+ ${addedText}${p.reset}`);
289
291
  }
290
292
  }
291
293
  continue;
@@ -296,10 +298,10 @@ function renderUnifiedHunk(hunk, layout) {
296
298
  const raw = truncateText(line.text, lineTextW);
297
299
  const text = lang ? highlightLine(raw, lang) : raw;
298
300
  if (useTrueColor) {
299
- out.push(padToWidth(`${p.successBg}${p.success}+ ${no} │ ${preserveBg(text, p.successBg)}`, textWidth) + p.reset);
301
+ out.push(gutter(no) + padToWidth(`${p.successBg}${p.success}+ ${preserveBg(text, p.successBg)}`, bgWidth) + p.reset);
300
302
  }
301
303
  else {
302
- out.push(`${p.success}+ ${no} │ ${text}${p.reset}`);
304
+ out.push(`${gutter(no)}${p.success}+ ${text}${p.reset}`);
303
305
  }
304
306
  }
305
307
  }