agent-sh 0.12.13 → 0.12.14

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.
@@ -14,7 +14,7 @@ import { palette as p } from "../utils/palette.js";
14
14
  import { discoverSkills, loadSkillContent } from "../agent/skills.js";
15
15
  import { reloadExtensions } from "../extension-loader.js";
16
16
  export default function activate(ctx) {
17
- const { bus, contextManager } = ctx;
17
+ const { bus } = ctx;
18
18
  const commands = new Map();
19
19
  const register = (cmd) => {
20
20
  const name = cmd.name.startsWith("/") ? cmd.name : `/${cmd.name}`;
@@ -128,7 +128,7 @@ export default function activate(ctx) {
128
128
  });
129
129
  // ── Skill commands (/skill:<name>) ────────────────────────────
130
130
  const getSkills = () => {
131
- const cwd = contextManager?.getCwd() ?? process.cwd();
131
+ const cwd = ctx.call("cwd") ?? process.cwd();
132
132
  return discoverSkills(cwd);
133
133
  };
134
134
  const handleSkillCommand = (skillName, args) => {
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { spawn } from "node:child_process";
3
3
  import * as path from "node:path";
4
- import { Shell } from "./shell/shell.js";
4
+ import { activateShell } from "./shell/index.js";
5
5
  import { createCore } from "./core.js";
6
6
  import { palette as p } from "./utils/palette.js";
7
7
  import { loadBuiltinExtensions } from "./extensions/index.js";
@@ -199,26 +199,32 @@ async function main() {
199
199
  process.stdout.write(`\x1b]0;agent-sh\x07`);
200
200
  const cols = process.stdout.columns || 80;
201
201
  const rows = process.stdout.rows || 24;
202
+ // Bound after activateShell — cleanup is wired into extCtx.quit before the
203
+ // shell exists, so the closure captures the var by reference.
204
+ let shell = null;
202
205
  const cleanup = () => {
203
206
  core.kill();
204
- shell.kill();
207
+ shell?.kill();
205
208
  if (process.stdin.isTTY) {
206
209
  process.stdin.setRawMode(false);
207
210
  }
208
211
  process.exit(0);
209
212
  };
213
+ // ── Extension context (must precede shell activation) ────────
214
+ if (process.env.DEBUG) {
215
+ console.error('[agent-sh] Setting up extensions...');
216
+ }
217
+ const extCtx = core.extensionContext({ quit: cleanup });
218
+ // ── Shell frontend bootstrap (special-cased; see src/shell/index.ts) ──
210
219
  if (process.env.DEBUG) {
211
220
  console.error('[agent-sh] Creating Shell...');
212
221
  }
213
222
  await new Promise(resolve => setTimeout(resolve, 100));
214
- const shell = new Shell({
215
- bus,
216
- handlers: core.handlers,
223
+ shell = activateShell(extCtx, {
217
224
  cols,
218
225
  rows,
219
- shell: config.shell || process.env.SHELL || "/bin/bash",
226
+ shellPath: config.shell || process.env.SHELL || "/bin/bash",
220
227
  cwd: process.cwd(),
221
- instanceId: core.instanceId,
222
228
  onShowAgentInfo: () => {
223
229
  if (agentInfo) {
224
230
  return { info: `${p.dim}${agentInfo.name}${agentInfo.model ? ` (${agentInfo.model})` : ""}${p.reset}` };
@@ -241,11 +247,6 @@ async function main() {
241
247
  },
242
248
  returnToSelf: true,
243
249
  });
244
- // ── Extensions ────────────────────────────────────────────────
245
- if (process.env.DEBUG) {
246
- console.error('[agent-sh] Setting up extensions...');
247
- }
248
- const extCtx = core.extensionContext({ quit: cleanup });
249
250
  // Load built-in extensions (individually disableable via settings.disabledBuiltins)
250
251
  await loadBuiltinExtensions(extCtx, getSettings().disabledBuiltins);
251
252
  // Load user extensions (may register alternative agent backends)
@@ -273,7 +274,7 @@ async function main() {
273
274
  // If none did, the built-in AgentLoop gets wired to bus events.
274
275
  const { names: backendNames } = core.bus.emitPipe("config:get-backends", { names: [], active: null });
275
276
  if (backendNames.length === 0) {
276
- shell.kill();
277
+ shell?.kill();
277
278
  console.error("\nagent-sh: no agent backend available.\n\n" +
278
279
  " Export OPENROUTER_API_KEY or OPENAI_API_KEY for zero-config launch, or\n" +
279
280
  " pass --api-key on the command line, or\n" +
@@ -354,9 +355,7 @@ async function main() {
354
355
  }
355
356
  }
356
357
  });
357
- process.stdout.on("resize", () => {
358
- shell.resize(process.stdout.columns || 80, process.stdout.rows || 24);
359
- });
358
+ // resize forwarding is set up inside activateShell; nothing to wire here.
360
359
  shell.onExit((e) => {
361
360
  core.kill();
362
361
  if (process.stdin.isTTY) {
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Frontend bootstrap. Loaded directly from src/index.ts (not the built-in
3
+ * extensions manifest) because PTY + stdin raw mode ownership is order-
4
+ * critical. For pluggable capability extensions see `src/extensions/`.
5
+ */
6
+ import type { ExtensionContext } from "../types.js";
7
+ export interface ShellActivateOptions {
8
+ cols: number;
9
+ rows: number;
10
+ /** Path to the shell binary (zsh, bash, etc.). */
11
+ shellPath: string;
12
+ cwd: string;
13
+ /** Optional callback used by the inline status indicator. */
14
+ onShowAgentInfo?: () => {
15
+ info: string;
16
+ model?: string;
17
+ };
18
+ }
19
+ export interface ShellHandle {
20
+ /** Terminate the PTY. */
21
+ kill(): void;
22
+ /** Subscribe to PTY exit. The frontend uses this to clean up + exit. */
23
+ onExit(callback: (e: {
24
+ exitCode: number;
25
+ signal?: number;
26
+ }) => void): void;
27
+ /** Forward terminal size changes to the PTY. */
28
+ resize(cols: number, rows: number): void;
29
+ }
30
+ /**
31
+ * Construct the Shell, wire resize forwarding, and register cleanup with the
32
+ * provided ExtensionContext. Returns a handle the caller (typically
33
+ * `src/index.ts`) uses to drive lifecycle from process-level events.
34
+ */
35
+ export declare function activateShell(ctx: ExtensionContext, opts: ShellActivateOptions): ShellHandle;
@@ -0,0 +1,47 @@
1
+ import { Shell } from "./shell.js";
2
+ import { StdoutSurface } from "../utils/compositor.js";
3
+ import { TerminalBuffer } from "../utils/terminal-buffer.js";
4
+ /**
5
+ * Construct the Shell, wire resize forwarding, and register cleanup with the
6
+ * provided ExtensionContext. Returns a handle the caller (typically
7
+ * `src/index.ts`) uses to drive lifecycle from process-level events.
8
+ */
9
+ export function activateShell(ctx, opts) {
10
+ // Stdout-as-default is a frontend choice, not a kernel one — a hub or
11
+ // web bridge would point these at its own surfaces.
12
+ const stdoutSurface = new StdoutSurface();
13
+ ctx.compositor.setDefault("agent", stdoutSurface);
14
+ ctx.compositor.setDefault("query", stdoutSurface);
15
+ ctx.compositor.setDefault("status", stdoutSurface);
16
+ // Lazy because @xterm/headless is optional; null when not installed.
17
+ let terminalBufferSingleton;
18
+ ctx.define("terminal-buffer", () => {
19
+ if (terminalBufferSingleton !== undefined)
20
+ return terminalBufferSingleton;
21
+ terminalBufferSingleton = TerminalBuffer.createWired(ctx.bus);
22
+ return terminalBufferSingleton;
23
+ });
24
+ const shell = new Shell({
25
+ bus: ctx.bus,
26
+ handlers: { define: ctx.define, call: ctx.call },
27
+ cols: opts.cols,
28
+ rows: opts.rows,
29
+ shell: opts.shellPath,
30
+ cwd: opts.cwd,
31
+ instanceId: ctx.instanceId,
32
+ onShowAgentInfo: opts.onShowAgentInfo,
33
+ });
34
+ const onResize = () => {
35
+ shell.resize(process.stdout.columns || 80, process.stdout.rows || 24);
36
+ };
37
+ process.stdout.on("resize", onResize);
38
+ ctx.onDispose(() => {
39
+ process.stdout.off("resize", onResize);
40
+ shell.kill();
41
+ });
42
+ return {
43
+ kill: () => shell.kill(),
44
+ onExit: (callback) => shell.onExit(callback),
45
+ resize: (cols, rows) => shell.resize(cols, rows),
46
+ };
47
+ }
package/dist/types.d.ts CHANGED
@@ -1,9 +1,7 @@
1
1
  import type { EventBus } from "./event-bus.js";
2
- import type { ContextManager } from "./context-manager.js";
3
2
  import type { ColorPalette } from "./utils/palette.js";
4
3
  import type { BlockTransformOptions, FencedBlockTransformOptions } from "./utils/stream-transform.js";
5
4
  import type { ToolDefinition } from "./agent/types.js";
6
- import type { TerminalBuffer } from "./utils/terminal-buffer.js";
7
5
  import type { Compositor } from "./utils/compositor.js";
8
6
  import type { HistoryAdapter } from "./agent/history-file.js";
9
7
  export type { ContentBlock } from "./event-bus.js";
@@ -20,8 +18,6 @@ export interface RemoteSessionOptions {
20
18
  suppressQueryBox?: boolean;
21
19
  /** Suppress usage stats line (default: true). */
22
20
  suppressUsage?: boolean;
23
- /** Set interactive-session dynamic context (default: false). */
24
- interactive?: boolean;
25
21
  }
26
22
  export interface RemoteSession {
27
23
  /** Submit a query to the agent from this session. */
@@ -100,7 +96,6 @@ export interface AgentShellConfig {
100
96
  */
101
97
  export interface ExtensionContext {
102
98
  bus: EventBus;
103
- contextManager: ContextManager;
104
99
  /** Stable per-instance identifier (4-char hex). */
105
100
  readonly instanceId: string;
106
101
  quit: () => void;
@@ -134,6 +129,31 @@ export interface ExtensionContext {
134
129
  registerSkill: (name: string, description: string, filePath: string) => void;
135
130
  /** Remove a registered skill by name. */
136
131
  removeSkill: (name: string) => void;
132
+ /**
133
+ * Register a context producer — a function that contributes a string
134
+ * (or `null` to skip) into one of two lifecycles:
135
+ *
136
+ * - `mode: "per-request"` (default) — fires on **every LLM request**,
137
+ * including each tool-loop iteration. Output is ephemerally wrapped
138
+ * in `<dynamic_context>` onto the trailing message at request time;
139
+ * never persisted. Use for "current state" signals (in-flight work,
140
+ * active mode, threshold warnings).
141
+ *
142
+ * - `mode: "per-query"` — fires **once at user-query start** in
143
+ * handleQuery. Output is wrapped in `<query_context>` and frozen into
144
+ * the user message; persists in conversation history. Use for
145
+ * "what happened between turns" signals (shell events, accumulated
146
+ * notifications, calendar/inbox deltas).
147
+ *
148
+ * In both modes producers run in registration order, non-null outputs
149
+ * joined with blank lines. When nothing contributes, no envelope tag
150
+ * is emitted.
151
+ *
152
+ * Returns a dispose fn that unregisters the producer.
153
+ */
154
+ registerContextProducer: (name: string, producer: () => string | null, opts?: {
155
+ mode?: "per-request" | "per-query";
156
+ }) => () => void;
137
157
  providers: {
138
158
  configure: (id: string, opts: {
139
159
  reasoningParams?: (level: string) => Record<string, unknown>;
@@ -148,11 +168,6 @@ export interface ExtensionContext {
148
168
  call: (name: string, ...args: any[]) => any;
149
169
  /** Names of all registered handlers — for diagnostic / introspection use. */
150
170
  list: () => string[];
151
- /**
152
- * Shared headless terminal buffer mirroring PTY output.
153
- * Lazily created on first access. Returns null if @xterm/headless is not installed.
154
- */
155
- terminalBuffer: TerminalBuffer | null;
156
171
  /**
157
172
  * Routes named render streams ("agent", "query", "status") to surfaces.
158
173
  * Extensions use `compositor.redirect()` to capture output (e.g. overlay panels).
@@ -166,7 +181,7 @@ export interface ExtensionContext {
166
181
  * optionally accepts queries. Handles all compositor routing, shell
167
182
  * lifecycle advisors, and chrome suppression.
168
183
  *
169
- * const session = ctx.createRemoteSession({ surface, interactive: true });
184
+ * const session = ctx.createRemoteSession({ surface });
170
185
  * session.submit("what's on screen?");
171
186
  * session.close(); // restores everything
172
187
  */
@@ -193,24 +208,3 @@ export interface TerminalSession {
193
208
  done: boolean;
194
209
  resolve?: (value: void) => void;
195
210
  }
196
- export type Exchange = {
197
- type: "shell_command";
198
- id: number;
199
- timestamp: number;
200
- cwd: string;
201
- command: string;
202
- /** In-context representation: full text if short, head+tail+path stub if spilled. */
203
- output: string;
204
- exitCode: number | null;
205
- outputLines: number;
206
- outputBytes: number;
207
- /** Who initiated this command: "user" (typed) or "agent" (via user_shell). */
208
- source: "user" | "agent";
209
- /** Path to the tempfile holding the full captured output, if spilled. */
210
- spillPath?: string;
211
- } | {
212
- type: "agent_query";
213
- id: number;
214
- timestamp: number;
215
- query: string;
216
- };
@@ -20,6 +20,19 @@ export declare function findToolCallIds(messages: any[], toolName: string, argFi
20
20
  * replaced. Non-matching messages are passed through by reference.
21
21
  */
22
22
  export declare function stubToolResults(messages: any[], callIds: Set<string>, stub: string): any[];
23
+ /**
24
+ * Prepend a `<dynamic_context>` block onto the trailing message.
25
+ *
26
+ * Wrapping the *trailing* message (rather than inserting at the head) keeps
27
+ * the [system] + [prior history] prefix byte-stable across turns, so the
28
+ * provider's prefix cache holds. Caller passes a copy; conversation state
29
+ * is never mutated.
30
+ *
31
+ * No-op when both `dynamicContext` and `toolPrompt` are empty — i.e. no
32
+ * extension registered any per-turn signal — so a vanilla session sends
33
+ * exactly `[system, ...history]` with no synthetic envelope.
34
+ */
35
+ export declare function wrapTrailingWithDynamicContext(history: any[], dynamicContext: string, toolPrompt?: string): any[];
23
36
  /**
24
37
  * Deduplicate tool results: keep only the latest result for a given
25
38
  * tool name + argument filter, replace all older results with a stub.
@@ -53,6 +53,32 @@ export function stubToolResults(messages, callIds, stub) {
53
53
  return msg;
54
54
  });
55
55
  }
56
+ /**
57
+ * Prepend a `<dynamic_context>` block onto the trailing message.
58
+ *
59
+ * Wrapping the *trailing* message (rather than inserting at the head) keeps
60
+ * the [system] + [prior history] prefix byte-stable across turns, so the
61
+ * provider's prefix cache holds. Caller passes a copy; conversation state
62
+ * is never mutated.
63
+ *
64
+ * No-op when both `dynamicContext` and `toolPrompt` are empty — i.e. no
65
+ * extension registered any per-turn signal — so a vanilla session sends
66
+ * exactly `[system, ...history]` with no synthetic envelope.
67
+ */
68
+ export function wrapTrailingWithDynamicContext(history, dynamicContext, toolPrompt) {
69
+ const ctx = dynamicContext.trim();
70
+ const tp = (toolPrompt ?? "").trim();
71
+ if (!ctx && !tp)
72
+ return history;
73
+ if (history.length === 0)
74
+ return history;
75
+ const last = history[history.length - 1];
76
+ if (typeof last.content !== "string")
77
+ return history;
78
+ const blockBody = ctx && tp ? `${ctx}\n${tp}` : ctx || tp;
79
+ const wrappedContent = `<dynamic_context>\n${blockBody}\n</dynamic_context>\n\n${last.content}`;
80
+ return [...history.slice(0, -1), { ...last, content: wrappedContent }];
81
+ }
56
82
  /**
57
83
  * Deduplicate tool results: keep only the latest result for a given
58
84
  * tool name + argument filter, replace all older results with a stub.
@@ -48,7 +48,8 @@ function createPanelSurface(panel: FloatingPanel): RenderSurface {
48
48
  }
49
49
 
50
50
  export default function activate(ctx: ExtensionContext): void {
51
- const { bus, registerInstruction, createRemoteSession, terminalBuffer } = ctx;
51
+ const { bus, registerInstruction, createRemoteSession } = ctx;
52
+ const terminalBuffer = ctx.call("terminal-buffer");
52
53
 
53
54
  const panel = new FloatingPanel(bus, {
54
55
  trigger: "\x1c", // Ctrl+\
@@ -59,6 +60,13 @@ export default function activate(ctx: ExtensionContext): void {
59
60
  const panelSurface = createPanelSurface(panel);
60
61
  let session: RemoteSession | null = null;
61
62
 
63
+ // Tell the LLM it's running inside an overlay session. The matching
64
+ // system-prompt block (registered via registerInstruction below) describes
65
+ // how to behave in this mode.
66
+ ctx.registerContextProducer("interactive-session", () =>
67
+ session?.active ? "interactive-session: true" : null,
68
+ );
69
+
62
70
  registerInstruction("Interactive Overlay Sessions", [
63
71
  "When the dynamic context includes `interactive-session: true`, the user has summoned you",
64
72
  "via a hotkey overlay from inside their live terminal. They may be in the middle of using",
@@ -77,7 +85,6 @@ export default function activate(ctx: ExtensionContext): void {
77
85
  session = createRemoteSession({
78
86
  surface: panelSurface,
79
87
  suppressQueryBox: true,
80
- interactive: true,
81
88
  });
82
89
  }
83
90
  panel.setActive();
@@ -90,7 +97,6 @@ export default function activate(ctx: ExtensionContext): void {
90
97
  session = createRemoteSession({
91
98
  surface: panelSurface,
92
99
  suppressQueryBox: true,
93
- interactive: true,
94
100
  });
95
101
  }
96
102
  });
@@ -190,10 +190,11 @@ class PeerServer {
190
190
  }
191
191
 
192
192
  export default function activate(ctx: ExtensionContext): void {
193
- const { bus, contextManager, registerCommand, registerTool, registerInstruction, define } = ctx;
193
+ const { bus, registerCommand, registerTool, registerInstruction, define } = ctx;
194
+ const getCwd = () => ctx.call("cwd") as string;
194
195
  const startTime = Date.now();
195
196
 
196
- const server = new PeerServer(ctx.instanceId, contextManager.getCwd(), (...args) => ctx.call(...args));
197
+ const server = new PeerServer(ctx.instanceId, getCwd(), (...args) => ctx.call(...args));
197
198
  server.start();
198
199
 
199
200
  // Track PTY idle window so peer:terminal-send doesn't stomp on a busy shell.
@@ -203,13 +204,13 @@ export default function activate(ctx: ExtensionContext): void {
203
204
  define("peer:info", () => ({
204
205
  id: ctx.instanceId,
205
206
  pid: process.pid,
206
- cwd: contextManager.getCwd(),
207
+ cwd: getCwd(),
207
208
  uptime: Math.round((Date.now() - startTime) / 1000),
208
209
  }));
209
210
  server.expose("peer:info");
210
211
 
211
212
  define("peer:terminal-read", () => {
212
- const tb = ctx.terminalBuffer;
213
+ const tb = ctx.call("terminal-buffer");
213
214
  if (!tb) return { text: "(terminal buffer not available)", altScreen: false };
214
215
  return tb.readScreen({ includeScrollback: true });
215
216
  });
@@ -229,15 +230,17 @@ export default function activate(ctx: ExtensionContext): void {
229
230
  }
230
231
  bus.emit("shell:pty-write", { data: interpretEscapes(keys) });
231
232
  await new Promise((r) => setTimeout(r, typeof settleMs === "number" ? settleMs : SETTLE_MS));
232
- const tb = ctx.terminalBuffer;
233
+ const tb = ctx.call("terminal-buffer");
233
234
  return { sent: true, screen: tb ? tb.readScreen({ includeScrollback: false }) : null };
234
235
  });
235
236
  server.expose("peer:terminal-send");
236
237
 
237
- define("peer:context-recent", (n: number = 15) => contextManager.getRecentSummary(n));
238
+ // If shell-context isn't loaded, the underlying handler is undefined
239
+ // and these calls surface a clear error to the requesting peer.
240
+ define("peer:context-recent", (n: number = 15) => ctx.call("shell:context-recent", n));
238
241
  server.expose("peer:context-recent");
239
242
 
240
- define("peer:context-search", (query: string) => contextManager.search(query));
243
+ define("peer:context-search", (query: string) => ctx.call("shell:context-search", query));
241
244
  server.expose("peer:context-search");
242
245
 
243
246
  // ── Inbox + drained turn ──────────────────────────────────────
@@ -12,7 +12,7 @@ import type { ExtensionContext } from "agent-sh/types";
12
12
  import { runSubagent } from "agent-sh/agent/subagent";
13
13
 
14
14
  export default function activate(ctx: ExtensionContext): void {
15
- const { bus, llmClient, contextManager } = ctx;
15
+ const { bus, llmClient } = ctx;
16
16
  if (!llmClient) return;
17
17
 
18
18
  const allToolNames = () => ctx.getTools().map(t => t.name);
@@ -73,7 +73,7 @@ export default function activate(ctx: ExtensionContext): void {
73
73
 
74
74
  const systemPrompt =
75
75
  `You are a focused subagent. Complete the task and return a clear, concise result.\n` +
76
- `Working directory: ${contextManager.getCwd()}` +
76
+ `Working directory: ${ctx.call("cwd")}` +
77
77
  (nuclearSummary ? `\n\n[Parent session history]\n${nuclearSummary}` : "");
78
78
 
79
79
  try {
@@ -34,8 +34,9 @@ function settle(ms = 100): Promise<void> {
34
34
  }
35
35
 
36
36
  export default function activate(ctx: ExtensionContext): void {
37
- const { bus, terminalBuffer: tb, registerTool, registerInstruction } = ctx;
38
- if (!tb) return; // @xterm/headless not installed
37
+ const { bus, registerTool, registerInstruction } = ctx;
38
+ const tb = ctx.call("terminal-buffer");
39
+ if (!tb) return; // @xterm/headless not installed, or shell frontend not loaded
39
40
 
40
41
  registerTool({
41
42
  name: "terminal_read",
@@ -149,6 +149,11 @@ export default function activate(ctx: ExtensionContext): void {
149
149
 
150
150
  let state: PaneState | null = null;
151
151
 
152
+ // Tell the LLM it's running inside an interactive pane session.
153
+ ctx.registerContextProducer("interactive-session", () =>
154
+ state?.mode === "rsplit" ? "interactive-session: true" : null,
155
+ );
156
+
152
157
  registerInstruction("Tmux Interactive Session", [
153
158
  "When the dynamic context includes `interactive-session: true`, the user is chatting",
154
159
  "with you in a side pane next to their terminal. They may have a program running in",
@@ -229,7 +234,6 @@ export default function activate(ctx: ExtensionContext): void {
229
234
  const session = createRemoteSession({
230
235
  surface,
231
236
  suppressQueryBox: true,
232
- interactive: true,
233
237
  });
234
238
 
235
239
  state = { mode: "rsplit", paneId, ttyFd, session, server, client, sockPath, scriptPath };
@@ -18,7 +18,7 @@ import type { ToolDefinition } from "agent-sh/agent/types";
18
18
 
19
19
  export default function activate(ctx: ExtensionContext): void {
20
20
  const { bus, registerTool, registerInstruction } = ctx;
21
- const getCwd = () => ctx.contextManager.getCwd();
21
+ const getCwd = () => ctx.call("cwd") as string;
22
22
 
23
23
  // ── Tool ───────────────────────────────────────────────────────
24
24
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-sh",
3
- "version": "0.12.13",
3
+ "version": "0.12.14",
4
4
  "description": "A shell-first terminal where AI is one keystroke away",
5
5
  "type": "module",
6
6
  "main": "dist/core.js",
@@ -1,45 +0,0 @@
1
- import type { EventBus } from "./event-bus.js";
2
- import type { HandlerRegistry } from "./utils/handler-registry.js";
3
- export declare class ContextManager {
4
- private exchanges;
5
- private nextId;
6
- private currentCwd;
7
- private agentShellActive;
8
- constructor(bus: EventBus, _handlers?: HandlerRegistry);
9
- getCwd(): string;
10
- /**
11
- * Regex/keyword search across all exchanges. Returns formatted results.
12
- */
13
- search(query: string): string;
14
- /**
15
- * Return shell events with id > afterId, formatted as an incremental
16
- * delta suitable for injection into conversation history. Skips
17
- * agent-source commands (already visible in tool results). Returns
18
- * null when nothing new exists.
19
- *
20
- * The motivation: resending the full <shell_context> every turn wastes
21
- * tokens — N turns × full history = O(N²) cost for O(N) information.
22
- * Instead we inject only new events as regular conversation messages,
23
- * so the provider's prefix cache amortizes them to O(N).
24
- */
25
- getEventsSince(afterId: number): {
26
- text: string;
27
- lastSeq: number;
28
- } | null;
29
- /** Highest exchange id seen so far (0 if none). */
30
- lastSeq(): number;
31
- /**
32
- * One-line summaries of last N exchanges.
33
- */
34
- getRecentSummary(n?: number): string;
35
- /**
36
- * Clear exchange history (used by /clear command).
37
- */
38
- clear(): void;
39
- private addExchange;
40
- private formatExchangeTruncated;
41
- private formatExchangeFull;
42
- private exchangeOneLiner;
43
- private exchangeSearchText;
44
- private exchangeSize;
45
- }