agent-sh 0.14.8 → 0.14.9

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 (49) 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 +44 -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/session-store.d.ts +90 -0
  20. package/dist/agent/session-store.js +288 -0
  21. package/dist/agent/store.d.ts +74 -0
  22. package/dist/agent/store.js +284 -0
  23. package/dist/agent/subagent.js +2 -2
  24. package/dist/agent/tool-protocol.d.ts +11 -11
  25. package/dist/cli/index.js +4 -2
  26. package/dist/core/index.d.ts +0 -1
  27. package/dist/core/index.js +0 -1
  28. package/dist/core/settings.d.ts +5 -1
  29. package/dist/core/settings.js +62 -1
  30. package/dist/extensions/index.d.ts +1 -0
  31. package/dist/shell/events.d.ts +1 -0
  32. package/dist/shell/input-handler.js +4 -0
  33. package/dist/shell/tui-renderer.js +5 -2
  34. package/dist/utils/diff-renderer.js +9 -7
  35. package/examples/extensions/ash-acp-bridge/src/index.ts +1 -2
  36. package/examples/extensions/ashi/package.json +2 -2
  37. package/examples/extensions/ashi/src/capture.ts +1 -1
  38. package/examples/extensions/ashi/src/cli.ts +3 -4
  39. package/examples/extensions/ashi/src/compaction.ts +6 -2
  40. package/examples/extensions/ashi/src/frontend.ts +13 -13
  41. package/examples/extensions/ashi/src/multi-session-store.ts +35 -12
  42. package/examples/extensions/ashi/src/session-commands.ts +1 -1
  43. package/examples/extensions/ashi/src/user-shell-intents.ts +17 -0
  44. package/package.json +13 -1
  45. package/dist/agent/conversation-state.d.ts +0 -142
  46. package/dist/agent/conversation-state.js +0 -788
  47. package/dist/agent/history-file.d.ts +0 -81
  48. package/dist/agent/history-file.js +0 -271
  49. package/examples/extensions/ashi/src/session-store.ts +0 -363
@@ -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
  }
@@ -11,7 +11,7 @@
11
11
  * In agent-shell (Emacs):
12
12
  * (setq agent-shell-agentsh-acp-command '("agent-sh-acp"))
13
13
  */
14
- import { createCore, NoopHistory, type AgentShellCore } from "agent-sh";
14
+ import { createCore, type AgentShellCore } from "agent-sh";
15
15
  import { loadExtensions } from "agent-sh/extension-loader";
16
16
  import { loadBuiltinExtensions } from "agent-sh/extensions";
17
17
  import { activateAgent } from "agent-sh/agent";
@@ -486,7 +486,6 @@ async function handleSessionNew(id: number | string, params: Record<string, unkn
486
486
  core = createCore({
487
487
  model: cliArgs.model,
488
488
  provider: cliArgs.provider,
489
- history: new NoopHistory(),
490
489
  });
491
490
  wireEvents(core);
492
491
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@guanyilun/ashi",
3
- "version": "0.1.7",
3
+ "version": "0.1.8",
4
4
  "description": "Ash in an interactive TUI — agent-sh's built-in agent without the shell underneath",
5
5
  "type": "module",
6
6
  "main": "dist/cli.js",
@@ -56,7 +56,7 @@
56
56
  },
57
57
  "dependencies": {
58
58
  "@earendil-works/pi-tui": "^0.74.0",
59
- "agent-sh": "^0.14.7",
59
+ "agent-sh": "^0.14.8",
60
60
  "chalk": "^5.5.0",
61
61
  "cli-highlight": "^2.1.11"
62
62
  },
@@ -1,6 +1,6 @@
1
1
  import type { ExtensionContext } from "agent-sh/types";
2
2
  import type { MultiSessionStore } from "./multi-session-store.js";
3
- import type { AgentMessage } from "./session-store.js";
3
+ import type { AgentShMessage as AgentMessage } from "agent-sh/session-store";
4
4
 
5
5
  /** Maintains an `(entryId | null)[]` parallel to the live messages array;
6
6
  * null slots are synthetics like compaction summaries that have no entry. */
@@ -2,7 +2,7 @@
2
2
  /**
3
3
  * ashi — ash (agent-sh's built-in agent) in an interactive TUI.
4
4
  */
5
- import { createCore, NoopHistory } from "agent-sh/core";
5
+ import { createCore } from "agent-sh/core";
6
6
  import { loadBuiltinExtensions } from "agent-sh/extensions";
7
7
  import { loadExtensions } from "agent-sh/extension-loader";
8
8
  import { activateAgent } from "agent-sh/agent";
@@ -134,7 +134,7 @@ async function main(): Promise<void> {
134
134
  const store = new MultiSessionStore(sessionsDir, cwd, { resumeSessionId: resumeId });
135
135
  const getStore = (): MultiSessionStore => store;
136
136
 
137
- const core = createCore({ ...config, history: new NoopHistory() });
137
+ const core = createCore(config);
138
138
 
139
139
  let stopFrontend: (() => void) | null = null;
140
140
 
@@ -153,7 +153,7 @@ async function main(): Promise<void> {
153
153
 
154
154
  activateAgent(ctx);
155
155
  activateShellContext(ctx);
156
- await loadBuiltinExtensions(ctx);
156
+ await loadBuiltinExtensions(ctx, ["rolling-history"]);
157
157
 
158
158
  const shell = new Shell({
159
159
  bus: core.bus,
@@ -183,7 +183,6 @@ async function main(): Promise<void> {
183
183
  registerRenderDefaults(ctx);
184
184
  registerDefaultSchemaRenderers(ctx);
185
185
 
186
- ctx.advise("conversation:format-prior-history", () => null);
187
186
  ctx.advise("system-prompt:build", (next) => `${next()}\n\n<cwd>${process.cwd()}</cwd>`);
188
187
 
189
188
  const handle = mountAshi(ctx, getStore, capture);
@@ -1,7 +1,7 @@
1
1
  import type { ExtensionContext } from "agent-sh/types";
2
2
  import type { MultiSessionStore } from "./multi-session-store.js";
3
3
  import type { Capture } from "./capture.js";
4
- import type { AgentMessage } from "./session-store.js";
4
+ import type { AgentShMessage as AgentMessage } from "agent-sh/session-store";
5
5
 
6
6
  const KEEP_RECENT_TOKEN_BUDGET = 20_000;
7
7
  const FORCE_KEEP_RECENT_TOKEN_BUDGET = 4_000;
@@ -81,6 +81,10 @@ export function isSafeCutPoint(messages: AgentMessage[], idx: number): boolean {
81
81
  export function estimateMessageTokens(m: AgentMessage): number {
82
82
  let chars = 0;
83
83
  if (typeof m.content === "string") chars += m.content.length;
84
- if (m.tool_calls) for (const t of m.tool_calls) chars += (t.function?.arguments?.length ?? 0);
84
+ if (m.role === "assistant" && m.tool_calls) {
85
+ for (const t of m.tool_calls) {
86
+ if (t.type === "function") chars += t.function.arguments.length;
87
+ }
88
+ }
85
89
  return Math.ceil(chars * APPROX_TOKENS_PER_CHAR) + 20;
86
90
  }
@@ -28,6 +28,7 @@ import type { ToolCallView, ToolResultView } from "./hooks.js";
28
28
  import { createToolHookResolver } from "./hooks.js";
29
29
  import { loadGroupMaxVisible } from "./display-config.js";
30
30
  import { classifySubmit, deriveChangeHandlerResult } from "./shell-mode.js";
31
+ import { UserShellIntents } from "./user-shell-intents.js";
31
32
 
32
33
  const GROUPABLE_KINDS = new Set(["read", "search"]);
33
34
  const TOOL_KIND: Record<string, string> = {
@@ -37,7 +38,7 @@ const TOOL_KIND: Record<string, string> = {
37
38
  import { BusAutocompleteProvider } from "./autocomplete.js";
38
39
  import { StatusFooter } from "./status-footer.js";
39
40
  import type { MultiSessionStore } from "./multi-session-store.js";
40
- import { stripContextWrappers, type SessionEntry } from "./session-store.js";
41
+ import { stripContextWrappers, type SessionEntry } from "agent-sh/session-store";
41
42
  import { formatSessionRow } from "./session-commands.js";
42
43
  import { resumeSession } from "./session-commands.js";
43
44
  import { applyBranchMessages } from "./commands.js";
@@ -300,8 +301,7 @@ export function mountAshi(
300
301
  let processing = false;
301
302
  const queuedQueries: string[] = [];
302
303
  const queuedShellLines: { line: string; private: boolean }[] = [];
303
- /** FIFO matching pty-writes to their shell:command-start events. */
304
- const pendingUserBlockPrivacy: boolean[] = [];
304
+ const pendingUserShell = new UserShellIntents();
305
305
 
306
306
  const renderQueueSlot = (): void => {
307
307
  queueSlot.clear();
@@ -325,7 +325,7 @@ export function mountAshi(
325
325
  tui.requestRender();
326
326
  return;
327
327
  }
328
- pendingUserBlockPrivacy.push(!!opts?.private);
328
+ pendingUserShell.push({ private: !!opts?.private });
329
329
  if (opts?.private) bus.emit("shell:user-exec-exclude-next", {});
330
330
  bus.emit("shell:pty-write", { data: line + "\n" });
331
331
  };
@@ -442,21 +442,22 @@ export function mountAshi(
442
442
  }
443
443
  if (m.tool_calls) {
444
444
  for (const tc of m.tool_calls) {
445
+ if (tc.type !== "function") continue;
445
446
  const id = tc.id ?? "";
446
- const name = tc.function?.name ?? "tool";
447
+ const name = tc.function.name ?? "tool";
447
448
  const kind = TOOL_KIND[name];
448
449
  if (kind && GROUPABLE_KINDS.has(kind)) {
449
450
  const mergeable = findMergeableGroup(kind);
450
451
  const group = mergeable
451
452
  ?? (() => { const g = new ToolGroup(kind, groupMaxVisible); chat.addChild(g); return g; })();
452
- group.addCall(id, name, detailFromArgs(tc.function?.arguments));
453
+ group.addCall(id, name, detailFromArgs(tc.function.arguments));
453
454
  if (id) toolMap.set(id, { kind: "group", group, name });
454
455
  continue;
455
456
  }
456
457
  const pair = renderToolPair({
457
458
  toolCallId: id, name, title: name, kind: undefined,
458
- displayDetail: detailFromArgs(tc.function?.arguments),
459
- rawInput: tc.function?.arguments,
459
+ displayDetail: detailFromArgs(tc.function.arguments),
460
+ rawInput: tc.function.arguments,
460
461
  });
461
462
  chat.addChild(pair.call);
462
463
  chat.addChild(pair.result);
@@ -636,12 +637,11 @@ export function mountAshi(
636
637
  let activeUserShell: { pair: ToolPair; command: string; isPrivate: boolean } | null = null;
637
638
  bus.on("shell:command-start", ({ command }) => {
638
639
  if (agentShellActive) return;
639
- // Defensive: bash DEBUG-trap integrations have been observed firing
640
- // before any user input, with an empty command body.
641
- if (!command.trim()) return;
640
+ const intent = pendingUserShell.consume();
641
+ if (!intent) return;
642
642
  finalizeThinking();
643
643
  if (activeAssistant) { activeAssistant.finalize(); activeAssistant = null; }
644
- const isPrivate = pendingUserBlockPrivacy.shift() ?? false;
644
+ const isPrivate = intent.private;
645
645
  const name = isPrivate ? "user_bash_private" : "user_bash";
646
646
  const pair = renderToolPair({
647
647
  toolCallId: `user-shell-${Date.now()}`, name, title: name,
@@ -681,7 +681,7 @@ export function mountAshi(
681
681
  // Shell queue drains first so its output lands in the next turn's <shell_events>.
682
682
  while (queuedShellLines.length > 0) {
683
683
  const item = queuedShellLines.shift()!;
684
- pendingUserBlockPrivacy.push(item.private);
684
+ pendingUserShell.push({ private: item.private });
685
685
  if (item.private) bus.emit("shell:user-exec-exclude-next", {});
686
686
  bus.emit("shell:pty-write", { data: item.line + "\n" });
687
687
  }
@@ -1,7 +1,7 @@
1
1
  import * as fs from "node:fs";
2
2
  import * as path from "node:path";
3
3
  import * as crypto from "node:crypto";
4
- import { SessionStore, type AgentMessage } from "./session-store.js";
4
+ import { SessionStore, type AgentShMessage as AgentMessage } from "agent-sh/session-store";
5
5
 
6
6
  export interface SessionInfo {
7
7
  id: string;
@@ -55,13 +55,10 @@ export class MultiSessionStore {
55
55
  for (const name of names) {
56
56
  if (!name.endsWith(".jsonl")) continue;
57
57
  const id = name.slice(0, -".jsonl".length);
58
- try {
59
- const raw = fs.readFileSync(path.join(dir, `${id}.jsonl.meta`), "utf-8");
60
- const meta = JSON.parse(raw) as { createdAt?: number };
61
- if (typeof meta.createdAt === "number" && (!best || meta.createdAt > best.createdAt)) {
62
- best = { id, createdAt: meta.createdAt };
63
- }
64
- } catch { /* skip unreadable meta */ }
58
+ const createdAt = readHeaderTimestamp(path.join(dir, name));
59
+ if (createdAt !== null && (!best || createdAt > best.createdAt)) {
60
+ best = { id, createdAt };
61
+ }
65
62
  }
66
63
  if (best) return best.id;
67
64
  }
@@ -69,6 +66,21 @@ export class MultiSessionStore {
69
66
  return undefined;
70
67
  }
71
68
 
69
+ setName(id: string, name: string): void {
70
+ fs.writeFileSync(this.metaFile(id), JSON.stringify({ name }));
71
+ }
72
+
73
+ private readName(id: string): string | undefined {
74
+ try {
75
+ const m = JSON.parse(fs.readFileSync(this.metaFile(id), "utf-8")) as { name?: string };
76
+ return typeof m.name === "string" ? m.name : undefined;
77
+ } catch { return undefined; }
78
+ }
79
+
80
+ private metaFile(id: string): string {
81
+ return path.join(this.dir, `${id}.jsonl.meta`);
82
+ }
83
+
72
84
  /** One-time import from the previous storage format (sessions stored as
73
85
  * directories with tree.jsonl + snapshots/). Each old session is replayed
74
86
  * from its most recent snapshot into a new flat `.jsonl` file, then the
@@ -140,12 +152,12 @@ export class MultiSessionStore {
140
152
  const filePath = path.join(this.dir, name);
141
153
  try {
142
154
  const store = new SessionStore(filePath);
143
- const meta = store.getMeta();
155
+ const root = store.getEntry(store.getRootId());
144
156
  result.push({
145
157
  id,
146
158
  filePath,
147
- createdAt: meta.createdAt,
148
- name: meta.name,
159
+ createdAt: root?.timestamp ?? 0,
160
+ name: this.readName(id),
149
161
  preview: store.getPreview(),
150
162
  entryCount: store.getAllEntries().length,
151
163
  });
@@ -172,6 +184,17 @@ function newSessionFileId(): string {
172
184
  return `${ts}_${suffix}`;
173
185
  }
174
186
 
187
+ function readHeaderTimestamp(filePath: string): number | null {
188
+ try {
189
+ const raw = fs.readFileSync(filePath, "utf-8");
190
+ const nl = raw.indexOf("\n");
191
+ const firstLine = nl < 0 ? raw : raw.slice(0, nl);
192
+ const e = JSON.parse(firstLine) as { type?: string; timestamp?: number };
193
+ if (e.type === "session" && typeof e.timestamp === "number") return e.timestamp;
194
+ } catch { /* unreadable */ }
195
+ return null;
196
+ }
197
+
175
198
  function writeImportedSession(
176
199
  newFile: string,
177
200
  id: string,
@@ -191,5 +214,5 @@ function writeImportedSession(
191
214
  }
192
215
  fs.writeFileSync(newFile, lines.join("\n") + "\n");
193
216
  fs.writeFileSync(newFile + ".leaf", parent);
194
- fs.writeFileSync(newFile + ".meta", JSON.stringify({ createdAt: ts, ...(name ? { name } : {}) }));
217
+ if (name) fs.writeFileSync(newFile + ".meta", JSON.stringify({ name }));
195
218
  }
@@ -35,7 +35,7 @@ export function registerSessionCommands(
35
35
  bus.emit("ui:error", { message: "name: expected a name" });
36
36
  return;
37
37
  }
38
- getStore().current().setName(name);
38
+ getStore().setName(getStore().current().id, name);
39
39
  bus.emit("ui:info", { message: `session named: ${name}` });
40
40
  });
41
41
 
@@ -0,0 +1,17 @@
1
+ /** Pending intents for ashi-issued shell pty-writes. shell:command-start fires
2
+ * for any OSC 9997 — orphans (bash DEBUG-trap noise) are dropped on consume. */
3
+ export interface UserShellIntent {
4
+ private: boolean;
5
+ }
6
+
7
+ export class UserShellIntents {
8
+ private q: UserShellIntent[] = [];
9
+
10
+ push(intent: UserShellIntent): void {
11
+ this.q.push(intent);
12
+ }
13
+
14
+ consume(): UserShellIntent | null {
15
+ return this.q.shift() ?? null;
16
+ }
17
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-sh",
3
- "version": "0.14.8",
3
+ "version": "0.14.9",
4
4
  "description": "A shell-first terminal where AI is one keystroke away",
5
5
  "type": "module",
6
6
  "workspaces": [
@@ -73,6 +73,18 @@
73
73
  "types": "./dist/agent/types.d.ts",
74
74
  "default": "./dist/agent/types.js"
75
75
  },
76
+ "./store": {
77
+ "types": "./dist/agent/store.d.ts",
78
+ "default": "./dist/agent/store.js"
79
+ },
80
+ "./session-store": {
81
+ "types": "./dist/agent/session-store.d.ts",
82
+ "default": "./dist/agent/session-store.js"
83
+ },
84
+ "./entry-format": {
85
+ "types": "./dist/agent/entry-format.d.ts",
86
+ "default": "./dist/agent/entry-format.js"
87
+ },
76
88
  "./agent/subagent": {
77
89
  "types": "./dist/agent/subagent.d.ts",
78
90
  "default": "./dist/agent/subagent.js"
@@ -1,142 +0,0 @@
1
- import type { ChatCompletionMessageParam } from "./llm-client.js";
2
- import type { ImageContent } from "./types.js";
3
- import { type NuclearEntry } from "./nuclear-form.js";
4
- import type { HandlerFunctions } from "../utils/handler-registry.js";
5
- /** Search hit shape returned by the `history:search` handler. */
6
- export interface HistoryHit {
7
- entry: NuclearEntry;
8
- line: string;
9
- }
10
- export interface CompactResult {
11
- before: number;
12
- after: number;
13
- evictedCount: number;
14
- [extra: string]: unknown;
15
- }
16
- /**
17
- * Conversation state with eager nucleation — shell-history shaped.
18
- *
19
- * Every add nucleates into a one-line NuclearEntry and flushes to disk.
20
- * Compaction evicts turns, replacing them with their nuclear one-liners
21
- * in context; the originals stay searchable via `conversation_recall`
22
- * and survive restarts in `~/.agent-sh/history`.
23
- *
24
- * Nucleation and history I/O go through advisable handlers — extensions
25
- * swap strategies without touching this class. When no handlers are
26
- * provided (subagents, tests), both become no-ops and this becomes a
27
- * plain message buffer.
28
- */
29
- export declare class ConversationState {
30
- private messages;
31
- private messagesDirty;
32
- private cachedMessagesJson;
33
- private toolErrors;
34
- private nuclearEntries;
35
- private nuclearBySeq;
36
- private recallArchive;
37
- readonly instanceId: string;
38
- private readonly handlers;
39
- private nextSeq;
40
- private lastApiTokenCount;
41
- private lastApiMessageCount;
42
- private pendingMessages;
43
- constructor(handlers?: HandlerFunctions, instanceId?: string);
44
- /** Get JSON.stringify of messages, cached until next mutation. */
45
- private getMessagesJson;
46
- private invalidateMessagesCache;
47
- addUserMessage(text: string): void;
48
- addAssistantMessage(content: string | null, toolCalls?: {
49
- id: string;
50
- function: {
51
- name: string;
52
- arguments: string;
53
- };
54
- }[], extras?: Record<string, unknown>): void;
55
- addToolResult(toolCallId: string, content: string | ImageContent[], isError?: boolean): void;
56
- /** Add tool results as a user message (for inline tool protocol). */
57
- addToolResultInline(content: string): void;
58
- /** Safe from any context: queues if mid-tool-pair, appends otherwise. */
59
- addSystemNote(text: string): void;
60
- appendUserMessage(text: string): void;
61
- private hasOpenToolCalls;
62
- private flushPendingMessages;
63
- getMessages(): ChatCompletionMessageParam[];
64
- /** Drop tool messages with no matching preceding tool_call — strict
65
- * providers (DeepSeek) 400, and compaction can leave such orphans. */
66
- private dropOrphanToolMessages;
67
- /**
68
- * If a stream was interrupted mid-tool-execution, an assistant message
69
- * with tool_calls can land in history without matching tool results.
70
- * Strict providers (DeepSeek) 400 on this. Stub each missing result
71
- * with a [cancelled] marker so the protocol stays valid.
72
- */
73
- private stubDanglingToolCalls;
74
- /**
75
- * DeepSeek 400s if any assistant in a thinking-mode conversation is
76
- * missing reasoning_content. Cross-alias here (OpenRouter streams as
77
- * `reasoning`, DeepSeek input expects `reasoning_content`) and stub
78
- * gaps (text-only turns, pre-fix messages) with empty string.
79
- */
80
- private normalizeReasoningConsistency;
81
- /**
82
- * Replace the messages array wholesale — the write side for custom
83
- * compaction strategies. Invalidates API token baseline since the
84
- * new array's token count is unknown.
85
- */
86
- replaceMessages(messages: ChatCompletionMessageParam[]): void;
87
- private pruneToolErrors;
88
- private eagerNucleateUser;
89
- /** Nucleate an agent text response. Called by agent-loop when the loop finishes without tool calls. */
90
- eagerNucleateAgent(text: string): void;
91
- /** Nucleate tool call results. One entry per tool call, enriched with result. */
92
- eagerNucleateTools(results: Array<{
93
- toolName: string;
94
- args: Record<string, unknown>;
95
- content: string | ImageContent[];
96
- isError: boolean;
97
- }>): void;
98
- /** Track an entry in memory (nuclear list + recall archive). */
99
- private recordNuclearEntry;
100
- private appendToHistory;
101
- /** Bump and return the global sequence counter. For extensions that
102
- * synthesize their own NuclearEntries (e.g. compaction summaries that
103
- * should land in the same sequence space as kernel-produced entries). */
104
- allocateSeq(): number;
105
- /** Clear nuclear bookkeeping and reset the seq counter. For extensions
106
- * that swap sessions (multi-session history adapters) so the in-memory
107
- * nuclear list, recall archive, and seq counter don't carry over from
108
- * the previous session's tree. */
109
- resetForSession(nextSeq: number): void;
110
- updateApiTokenCount(promptTokens: number): void;
111
- estimatePromptTokens(): number;
112
- estimateTokens(): number;
113
- /**
114
- * Two-tier pin compaction: evict lowest-priority turns (replaced by
115
- * their nuclear one-liners), slim the window before the last verbatim
116
- * turn, drop read-only tool results entirely. Extensions replace the
117
- * whole strategy by advising `conversation:compact` and skipping next.
118
- */
119
- compact(maxPromptTokens: number, recentTurnsToKeep?: number, force?: boolean): CompactResult | null;
120
- /**
121
- * Inject prior session history as a context preamble. The preamble
122
- * layout goes through the `conversation:format-prior-history` handler,
123
- * so extensions can swap the flat list for grouped/richer rendering.
124
- */
125
- loadPriorHistory(entries: NuclearEntry[]): void;
126
- search(query: string): Promise<string>;
127
- expand(seq: number): Promise<string>;
128
- browse(): Promise<string>;
129
- getNuclearEntries(): readonly NuclearEntry[];
130
- getNuclearEntryCount(): number;
131
- getNuclearSummary(): string | null;
132
- getRecallArchiveSize(): number;
133
- clear(): void;
134
- private buildNuclearBlock;
135
- /** Index of the nuclear block in messages[], or -1 if not present. */
136
- private nuclearBlockIdx;
137
- private updateNuclearBlockInMessages;
138
- private slimTurn;
139
- private parseTurns;
140
- private inferPriority;
141
- private turnToText;
142
- }