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
@@ -1,363 +0,0 @@
1
- import * as fs from "node:fs";
2
- import * as fsp from "node:fs/promises";
3
- import * as path from "node:path";
4
- import * as crypto from "node:crypto";
5
-
6
- export interface ToolCall {
7
- id?: string;
8
- function?: { name: string; arguments?: string };
9
- }
10
-
11
- export interface AgentMessage {
12
- role: "system" | "user" | "assistant" | "tool";
13
- content?: unknown;
14
- tool_calls?: ToolCall[];
15
- tool_call_id?: string;
16
- name?: string;
17
- meta?: Record<string, unknown>;
18
- }
19
-
20
- export interface SessionHeaderEntry {
21
- type: "session";
22
- id: string;
23
- parentId: null;
24
- timestamp: number;
25
- cwd: string;
26
- version: 1;
27
- }
28
-
29
- export interface MessageEntry {
30
- type: "message";
31
- id: string;
32
- parentId: string;
33
- timestamp: number;
34
- message: AgentMessage;
35
- }
36
-
37
- export interface CompactionEntry {
38
- type: "compaction";
39
- id: string;
40
- parentId: string;
41
- timestamp: number;
42
- summary?: string;
43
- firstKeptId: string;
44
- tokensBefore: number;
45
- }
46
-
47
- /** Omitted from buildMessages — the agent already saw it via <shell_events>
48
- * (or didn't, if private). The frontend replays it for scrollback fidelity. */
49
- export interface ShellExchangeEntry {
50
- type: "shell-exchange";
51
- id: string;
52
- parentId: string;
53
- timestamp: number;
54
- command: string;
55
- output: string;
56
- exitCode: number | null;
57
- cwd?: string;
58
- private?: boolean;
59
- }
60
-
61
- export type SessionEntry =
62
- | SessionHeaderEntry
63
- | MessageEntry
64
- | CompactionEntry
65
- | ShellExchangeEntry;
66
-
67
- export interface SessionMeta {
68
- name?: string;
69
- createdAt: number;
70
- }
71
-
72
- export function newEntryId(): string {
73
- return crypto.randomBytes(4).toString("hex");
74
- }
75
-
76
- function extractText(content: unknown): string {
77
- if (typeof content === "string") return content;
78
- if (Array.isArray(content)) {
79
- return content.map((p) => {
80
- if (typeof p === "string") return p;
81
- const part = p as { text?: string; content?: string };
82
- return part?.text ?? part?.content ?? "";
83
- }).join(" ");
84
- }
85
- return "";
86
- }
87
-
88
- function snippet(text: string, max: number): string {
89
- const cleaned = String(text ?? "").replace(/\s+/g, " ").trim();
90
- if (cleaned.length <= max) return cleaned || "(empty)";
91
- return cleaned.slice(0, max) + "…";
92
- }
93
-
94
- export function summarizeMessage(m: AgentMessage): string {
95
- const role = m.role ?? "?";
96
- if (role === "assistant" && Array.isArray(m.tool_calls) && m.tool_calls.length > 0) {
97
- const tools = m.tool_calls.map((tc) => {
98
- const name = tc.function?.name ?? "tool";
99
- const args = tc.function?.arguments;
100
- return args ? `${name}(${snippet(args, 200)})` : name;
101
- }).join(", ");
102
- const text = extractText(m.content);
103
- const prefix = text ? `${snippet(text, 400)} → ` : "";
104
- return `assistant: ${prefix}called ${tools}`;
105
- }
106
- if (role === "tool") {
107
- const text = typeof m.content === "string" ? m.content : extractText(m.content);
108
- const isErr = /^error\b|: error\b/i.test(text.slice(0, 200));
109
- return `tool result: ${snippet(text, isErr ? 1000 : 400)}`;
110
- }
111
- if (role === "user") {
112
- return `user: ${snippet(extractText(m.content), 1000)}`;
113
- }
114
- return `${role}: ${snippet(extractText(m.content), 500)}`;
115
- }
116
-
117
- /** For displayed user text. Loops because both wrappers can stack at the head. */
118
- export function stripContextWrappers(content: string): string {
119
- let out = content;
120
- for (;;) {
121
- const next = out.replace(/^\s*<(query_context|dynamic_context)>[\s\S]*?<\/\1>\s*/, "");
122
- if (next === out) return out;
123
- out = next;
124
- }
125
- }
126
-
127
- export function renderEvictedSummary(evicted: AgentMessage[]): string {
128
- const lines = evicted.map((m) => `- ${summarizeMessage(m)}`);
129
- return `${lines.length} message(s) elided\n${lines.join("\n")}`;
130
- }
131
-
132
- /** Tree is implicit via parentId pointers; entries are kept in memory after load. */
133
- export class SessionStore {
134
- private entriesPath: string;
135
- private leafPath: string;
136
- private metaPath: string;
137
- private entries = new Map<string, SessionEntry>();
138
- private rootId = "";
139
- private activeLeaf = "";
140
- private meta: SessionMeta;
141
- private pendingHeader: SessionHeaderEntry | null = null;
142
- readonly id: string;
143
-
144
- constructor(filePath: string, opts?: { create?: { cwd: string; sessionId: string } }) {
145
- this.entriesPath = filePath;
146
- this.leafPath = filePath + ".leaf";
147
- this.metaPath = filePath + ".meta";
148
- this.meta = { createdAt: 0 };
149
- fs.mkdirSync(path.dirname(filePath), { recursive: true });
150
-
151
- if (opts?.create) {
152
- this.id = opts.create.sessionId;
153
- const header: SessionHeaderEntry = {
154
- type: "session",
155
- id: opts.create.sessionId,
156
- parentId: null,
157
- timestamp: Date.now(),
158
- cwd: opts.create.cwd,
159
- version: 1,
160
- };
161
- this.entries.set(header.id, header);
162
- this.rootId = header.id;
163
- this.activeLeaf = header.id;
164
- this.meta = { createdAt: header.timestamp };
165
- this.pendingHeader = header;
166
- } else {
167
- this.id = "";
168
- this.load();
169
- if (!this.rootId) throw new Error(`session file lacks a session header: ${filePath}`);
170
- this.id = this.rootId;
171
- }
172
- }
173
-
174
- private flushHeader(): void {
175
- if (!this.pendingHeader) return;
176
- const headerLine = JSON.stringify(this.pendingHeader) + "\n";
177
- this.pendingHeader = null;
178
- fs.writeFileSync(this.entriesPath, headerLine);
179
- this.persistMeta();
180
- this.persistLeaf();
181
- }
182
-
183
- getActiveLeaf(): string { return this.activeLeaf; }
184
- setActiveLeaf(id: string): void {
185
- if (!this.entries.has(id)) throw new Error(`unknown entry: ${id}`);
186
- this.activeLeaf = id;
187
- this.persistLeaf();
188
- }
189
- getRootId(): string { return this.rootId; }
190
- getEntry(id: string): SessionEntry | undefined { return this.entries.get(id); }
191
- getAllEntries(): SessionEntry[] {
192
- return [...this.entries.values()];
193
- }
194
- getMeta(): SessionMeta { return { ...this.meta }; }
195
- setName(name: string): void {
196
- this.meta.name = name;
197
- this.persistMeta();
198
- }
199
-
200
- async appendMessages(messages: AgentMessage[]): Promise<string[]> {
201
- if (messages.length === 0) return [];
202
- this.flushHeader();
203
- let parent = this.activeLeaf;
204
- const lines: string[] = [];
205
- const newIds: string[] = [];
206
- for (const m of messages) {
207
- const e: MessageEntry = {
208
- type: "message",
209
- id: newEntryId(),
210
- parentId: parent,
211
- timestamp: Date.now(),
212
- message: m,
213
- };
214
- this.entries.set(e.id, e);
215
- lines.push(JSON.stringify(e));
216
- newIds.push(e.id);
217
- parent = e.id;
218
- }
219
- this.activeLeaf = parent;
220
- await fsp.appendFile(this.entriesPath, lines.join("\n") + "\n");
221
- this.persistLeaf();
222
- return newIds;
223
- }
224
-
225
- async appendShellExchange(e: {
226
- command: string;
227
- output: string;
228
- exitCode: number | null;
229
- cwd?: string;
230
- private?: boolean;
231
- }): Promise<string> {
232
- this.flushHeader();
233
- const entry: ShellExchangeEntry = {
234
- type: "shell-exchange",
235
- id: newEntryId(),
236
- parentId: this.activeLeaf,
237
- timestamp: Date.now(),
238
- command: e.command,
239
- output: e.output,
240
- exitCode: e.exitCode,
241
- ...(e.cwd !== undefined ? { cwd: e.cwd } : {}),
242
- ...(e.private ? { private: true } : {}),
243
- };
244
- this.entries.set(entry.id, entry);
245
- this.activeLeaf = entry.id;
246
- await fsp.appendFile(this.entriesPath, JSON.stringify(entry) + "\n");
247
- this.persistLeaf();
248
- return entry.id;
249
- }
250
-
251
- async appendCompaction(firstKeptId: string, tokensBefore: number, summary?: string): Promise<string> {
252
- if (!this.entries.has(firstKeptId)) throw new Error(`firstKeptId unknown: ${firstKeptId}`);
253
- this.flushHeader();
254
- const e: CompactionEntry = {
255
- type: "compaction",
256
- id: newEntryId(),
257
- parentId: this.activeLeaf,
258
- timestamp: Date.now(),
259
- firstKeptId,
260
- tokensBefore,
261
- ...(summary !== undefined ? { summary } : {}),
262
- };
263
- this.entries.set(e.id, e);
264
- this.activeLeaf = e.id;
265
- await fsp.appendFile(this.entriesPath, JSON.stringify(e) + "\n");
266
- this.persistLeaf();
267
- return e.id;
268
- }
269
-
270
- /** Oldest-first walk from leaf to root. */
271
- getBranch(leafId: string = this.activeLeaf): SessionEntry[] {
272
- const out: SessionEntry[] = [];
273
- const seen = new Set<string>();
274
- let cur: string | null = leafId;
275
- while (cur && !seen.has(cur)) {
276
- seen.add(cur);
277
- const e = this.entries.get(cur);
278
- if (!e) break;
279
- out.push(e);
280
- cur = e.parentId;
281
- }
282
- return out.reverse();
283
- }
284
-
285
- /** Honors the latest compaction on the branch (summary + kept tail). */
286
- buildMessages(leafId: string = this.activeLeaf): AgentMessage[] {
287
- const branch = this.getBranch(leafId);
288
- let compactionIdx = -1;
289
- for (let i = branch.length - 1; i >= 0; i--) {
290
- if (branch[i]!.type === "compaction") { compactionIdx = i; break; }
291
- }
292
- if (compactionIdx < 0) {
293
- return branch
294
- .filter((e): e is MessageEntry => e.type === "message")
295
- .map((e) => e.message);
296
- }
297
- const c = branch[compactionIdx] as CompactionEntry;
298
- const firstKeptIdx = branch.findIndex((e) => e.id === c.firstKeptId);
299
- const keepFrom = firstKeptIdx >= 0 ? firstKeptIdx : 0;
300
- const summary = c.summary ?? renderEvictedSummary(
301
- branch.slice(0, keepFrom)
302
- .filter((e): e is MessageEntry => e.type === "message")
303
- .map((e) => e.message),
304
- );
305
- const out: AgentMessage[] = [{
306
- role: "user",
307
- content: `[Compacted conversation summary]\n${summary}`,
308
- }];
309
- for (let i = keepFrom; i < branch.length; i++) {
310
- const e = branch[i]!;
311
- if (e.type === "message") out.push(e.message);
312
- }
313
- return out;
314
- }
315
-
316
- getPreview(): string {
317
- for (const e of this.entries.values()) {
318
- if (e.type === "message" && e.message.role === "user") {
319
- const raw = typeof e.message.content === "string" ? e.message.content : "";
320
- const txt = stripContextWrappers(raw);
321
- if (txt) return txt.slice(0, 80);
322
- }
323
- }
324
- return "(empty)";
325
- }
326
-
327
- private load(): void {
328
- try {
329
- this.meta = JSON.parse(fs.readFileSync(this.metaPath, "utf-8")) as SessionMeta;
330
- } catch { this.meta = { createdAt: 0 }; }
331
- let raw: string;
332
- try { raw = fs.readFileSync(this.entriesPath, "utf-8"); }
333
- catch { return; }
334
- for (const line of raw.split("\n")) {
335
- if (!line) continue;
336
- try {
337
- const e = JSON.parse(line) as SessionEntry;
338
- if (!e.id) continue;
339
- this.entries.set(e.id, e);
340
- if (e.type === "session") this.rootId = e.id;
341
- } catch { /* skip malformed */ }
342
- }
343
- try {
344
- this.activeLeaf = fs.readFileSync(this.leafPath, "utf-8").trim();
345
- if (!this.entries.has(this.activeLeaf)) this.activeLeaf = this.rootId;
346
- } catch { this.activeLeaf = this.lastEntryId(); }
347
- }
348
-
349
- private lastEntryId(): string {
350
- let lastId = this.rootId;
351
- for (const e of this.entries.values()) lastId = e.id;
352
- return lastId;
353
- }
354
-
355
- private persistLeaf(): void {
356
- if (this.pendingHeader) return;
357
- fs.writeFileSync(this.leafPath, this.activeLeaf);
358
- }
359
- private persistMeta(): void {
360
- if (this.pendingHeader) return;
361
- fs.writeFileSync(this.metaPath, JSON.stringify(this.meta));
362
- }
363
- }