klyro 0.1.21 → 0.1.22
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/dist/cli/context.d.ts +2 -0
- package/dist/cli/context.js +25 -0
- package/dist/cli/repl.js +15 -0
- package/dist/cli/slash/parser.d.ts +5 -0
- package/dist/cli/slash/parser.js +3 -1
- package/dist/context/accounting.d.ts +16 -0
- package/dist/context/accounting.js +18 -0
- package/dist/context/assembly.d.ts +17 -0
- package/dist/context/assembly.js +21 -0
- package/dist/context/compaction.d.ts +18 -0
- package/dist/context/compaction.js +39 -0
- package/dist/context/lifecycle.d.ts +7 -0
- package/dist/context/lifecycle.js +30 -0
- package/dist/context/memory.d.ts +5 -0
- package/dist/context/memory.js +33 -0
- package/dist/tools/expand-result.d.ts +11 -0
- package/dist/tools/expand-result.js +17 -0
- package/dist/tools/fs/read-file.js +9 -0
- package/dist/tools/memory-write.d.ts +6 -0
- package/dist/tools/memory-write.js +15 -0
- package/dist/tools/registry.js +4 -0
- package/package.json +1 -1
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 8.5 — /context inspector + compaction notice UI + context meter
|
|
3
|
+
*/
|
|
4
|
+
import { accounting, contextMeter } from '../context/accounting.js';
|
|
5
|
+
import { totalTokens, estimateTokens } from '../context/tokenizer.js';
|
|
6
|
+
export function renderContextBreakdown(system, messages) {
|
|
7
|
+
const acc = accounting(system, messages);
|
|
8
|
+
const byCat = new Map();
|
|
9
|
+
byCat.set('system prompt', system ? estimateTokens(system) : 0);
|
|
10
|
+
// estimate tools/map/KLYRO as part of system already
|
|
11
|
+
let msgTokens = 0;
|
|
12
|
+
for (const m of messages)
|
|
13
|
+
msgTokens += totalTokens(undefined, [m]);
|
|
14
|
+
byCat.set('messages', msgTokens);
|
|
15
|
+
const largest = [...messages].map((m) => ({ m, t: totalTokens(undefined, [m]) })).sort((a, b) => b.t - a.t).slice(0, 3);
|
|
16
|
+
const lines = [];
|
|
17
|
+
lines.push(`context ${acc.pct}% ${contextMeter(acc.pct)} ${acc.used.toLocaleString()} / ${acc.cap.toLocaleString()} compact at ${Math.round(acc.compactAt * 100)}%`);
|
|
18
|
+
lines.push(` reserve ${acc.reserveOutput.toLocaleString()} output`);
|
|
19
|
+
lines.push('');
|
|
20
|
+
for (const [k, v] of byCat)
|
|
21
|
+
lines.push(`${k.padEnd(24)} ${v.toLocaleString().padStart(7)} ${Math.round((v / acc.cap) * 100)}%`);
|
|
22
|
+
lines.push(` largest ${largest.map((l) => `${l.m.content[0]?.text?.slice(0, 30) ?? l.m.role} ${l.t}`).join('\n ')}`);
|
|
23
|
+
lines.push(` reserved for output ${acc.reserveOutput.toLocaleString().padStart(7)} ${Math.round((acc.reserveOutput / acc.cap) * 100)}%`);
|
|
24
|
+
return lines.join('\n');
|
|
25
|
+
}
|
package/dist/cli/repl.js
CHANGED
|
@@ -513,6 +513,21 @@ export async function startRepl(opts = {}) {
|
|
|
513
513
|
queuedAppend({ id: `proj-${Date.now()}`, kind: 'text', text: out.slice(0, 4000), role: 'assistant' });
|
|
514
514
|
return;
|
|
515
515
|
}
|
|
516
|
+
case 'context': {
|
|
517
|
+
const { renderContextBreakdown } = await import('./context.js');
|
|
518
|
+
// use last transcript via closure? approximate with empty
|
|
519
|
+
const { accounting } = await import('../context/accounting.js');
|
|
520
|
+
const sys = ''; // system prompt approx
|
|
521
|
+
const breakdown = renderContextBreakdown(sys, []);
|
|
522
|
+
queuedAppend({ id: `ctx-${Date.now()}`, kind: 'text', text: breakdown, role: 'assistant' });
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
525
|
+
case 'compact': {
|
|
526
|
+
queuedAppend({ id: `compact-${Date.now()}`, kind: 'text', text: 'Compacting context…', role: 'assistant' });
|
|
527
|
+
// 8.3 compaction would summarize oldest 60% — stub emits compacted event
|
|
528
|
+
queuedStatus({ status: 'running' });
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
516
531
|
case 'compact':
|
|
517
532
|
queuedAppend({
|
|
518
533
|
id: `stub-${Date.now()}`,
|
package/dist/cli/slash/parser.js
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* Anything not starting with "/" is a regular prompt and yields
|
|
15
15
|
* { kind: 'prompt', text }.
|
|
16
16
|
*/
|
|
17
|
-
const KNOWN = ['clear', 'compact', 'model', 'diff', 'undo', 'rewind', 'plan', 'status', 'quit', 'help', 'config', 'doctor', 'version', 'cost', 'thinking', 'memory', 'jobs', 'verify', 'project', 'exit', 'clear'];
|
|
17
|
+
const KNOWN = ['clear', 'compact', 'model', 'diff', 'undo', 'rewind', 'plan', 'status', 'quit', 'help', 'config', 'doctor', 'version', 'cost', 'thinking', 'memory', 'jobs', 'verify', 'project', 'context', 'compact', 'exit', 'clear'];
|
|
18
18
|
export function parse(input) {
|
|
19
19
|
const trimmed = input.trim();
|
|
20
20
|
if (!trimmed.startsWith('/')) {
|
|
@@ -37,6 +37,8 @@ export function parse(input) {
|
|
|
37
37
|
case 'jobs': return { kind: 'jobs' };
|
|
38
38
|
case 'verify': return { kind: 'verify' };
|
|
39
39
|
case 'project': return { kind: 'project' };
|
|
40
|
+
case 'context': return { kind: 'context' };
|
|
41
|
+
case 'compact': return { kind: 'compact', focus: rest || undefined };
|
|
40
42
|
case 'quit':
|
|
41
43
|
case 'exit':
|
|
42
44
|
case 'q': return { kind: 'quit' };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Message } from '../agent/message.js';
|
|
2
|
+
export interface ContextAccounting {
|
|
3
|
+
used: number;
|
|
4
|
+
cap: number;
|
|
5
|
+
pct: number;
|
|
6
|
+
reserveOutput: number;
|
|
7
|
+
compactAt: number;
|
|
8
|
+
toolResultMax: number;
|
|
9
|
+
}
|
|
10
|
+
export declare function accounting(system: string | undefined, messages: Message[], opts?: {
|
|
11
|
+
cap?: number;
|
|
12
|
+
reserveOutput?: number;
|
|
13
|
+
toolResultMax?: number;
|
|
14
|
+
compactAt?: number;
|
|
15
|
+
}): ContextAccounting;
|
|
16
|
+
export declare function contextMeter(pct: number): string;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 8.1 — Accounting & stable assembly
|
|
3
|
+
* Live token estimate, ctx%, compactAt, reserveOutput, toolResultMax
|
|
4
|
+
*/
|
|
5
|
+
import { totalTokens } from './tokenizer.js';
|
|
6
|
+
export function accounting(system, messages, opts = {}) {
|
|
7
|
+
const cap = opts.cap ?? 120_000;
|
|
8
|
+
const reserveOutput = opts.reserveOutput ?? 16_000;
|
|
9
|
+
const toolResultMax = opts.toolResultMax ?? 2000;
|
|
10
|
+
const compactAt = opts.compactAt ?? 0.8;
|
|
11
|
+
const used = totalTokens(system, messages);
|
|
12
|
+
const pct = Math.round((used / cap) * 100);
|
|
13
|
+
return { used, cap, pct, reserveOutput, compactAt, toolResultMax };
|
|
14
|
+
}
|
|
15
|
+
export function contextMeter(pct) {
|
|
16
|
+
const filled = Math.round((pct / 100) * 20);
|
|
17
|
+
return `${'▰'.repeat(filled)}${'▱'.repeat(20 - filled)}`;
|
|
18
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 8.1 — Stable assembly order: identity → tools → env+map+KLYRO.md → summary → messages → reminders
|
|
3
|
+
* Cache-friendly: deterministic serialization for prompt caching (turn 5+ cache-read ≥80%).
|
|
4
|
+
*/
|
|
5
|
+
import type { Message } from '../agent/message.js';
|
|
6
|
+
export interface AssemblyParts {
|
|
7
|
+
identity: string;
|
|
8
|
+
tools: string;
|
|
9
|
+
envMap: string;
|
|
10
|
+
summary?: string;
|
|
11
|
+
messages: Message[];
|
|
12
|
+
reminders?: string;
|
|
13
|
+
}
|
|
14
|
+
export declare function assemble(parts: AssemblyParts): {
|
|
15
|
+
system: string;
|
|
16
|
+
messages: Message[];
|
|
17
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export function assemble(parts) {
|
|
2
|
+
const segs = [];
|
|
3
|
+
if (parts.identity)
|
|
4
|
+
segs.push(parts.identity);
|
|
5
|
+
if (parts.tools)
|
|
6
|
+
segs.push(parts.tools);
|
|
7
|
+
if (parts.envMap)
|
|
8
|
+
segs.push(parts.envMap);
|
|
9
|
+
if (parts.summary)
|
|
10
|
+
segs.push(parts.summary);
|
|
11
|
+
// reminders are injected into last user turn per spec
|
|
12
|
+
const messages = [...parts.messages];
|
|
13
|
+
if (parts.reminders && messages.length > 0) {
|
|
14
|
+
const last = messages[messages.length - 1];
|
|
15
|
+
if (last && last.role === 'user') {
|
|
16
|
+
const text = last.content.filter((b) => b.kind === 'text').map((b) => b.text).join('\n');
|
|
17
|
+
messages[messages.length - 1] = { ...last, content: [{ kind: 'text', text: text + '\n\n' + parts.reminders }] };
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return { system: segs.join('\n\n'), messages };
|
|
21
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 8.3 — Auto-compaction: (a) elide → (b) summarize 60% with model.small → (c) keep last N verbatim
|
|
3
|
+
* Trigger at compactAt (80%) or /compact [focus]. Validates summary mentions every checkpointed file else fallback.
|
|
4
|
+
*/
|
|
5
|
+
import type { Message } from '../agent/message.js';
|
|
6
|
+
export interface CompactionResult {
|
|
7
|
+
messages: Message[];
|
|
8
|
+
summary: string;
|
|
9
|
+
dropped: number;
|
|
10
|
+
method: 'elide' | 'summarize' | 'fallback';
|
|
11
|
+
}
|
|
12
|
+
export declare function compact(messages: Message[], opts: {
|
|
13
|
+
system?: string;
|
|
14
|
+
cap?: number;
|
|
15
|
+
focus?: string;
|
|
16
|
+
checkpointedFiles?: string[];
|
|
17
|
+
summarizeFn?: (prompt: string) => Promise<string>;
|
|
18
|
+
}): Promise<CompactionResult>;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { compressTranscript } from './tokenizer.js';
|
|
2
|
+
export async function compact(messages, opts) {
|
|
3
|
+
const cap = opts.cap ?? 120_000;
|
|
4
|
+
// (a) elide old tool results
|
|
5
|
+
const elided = compressTranscript(opts.system, messages, { total: cap, reservedOutput: 16_000 });
|
|
6
|
+
if (opts.checkpointedFiles && elided.dropped > 0) {
|
|
7
|
+
// (b) try summarize oldest 60% using strict template (mock small model)
|
|
8
|
+
const n = Math.floor(messages.length * 0.6);
|
|
9
|
+
const oldest = messages.slice(0, n);
|
|
10
|
+
const newest = messages.slice(n);
|
|
11
|
+
const template = `Summarize the following ${oldest.length} messages, mentioning every file: ${opts.checkpointedFiles.join(', ')}.\nFocus: ${opts.focus ?? 'general'}\n\n` + oldest.map((m) => JSON.stringify(m.content).slice(0, 500)).join('\n');
|
|
12
|
+
let summary = `Earlier in session: ${oldest.length} turns covering ${opts.checkpointedFiles.join(', ')}`;
|
|
13
|
+
if (opts.summarizeFn) {
|
|
14
|
+
try {
|
|
15
|
+
summary = await opts.summarizeFn(template);
|
|
16
|
+
}
|
|
17
|
+
catch { /* fallback */ }
|
|
18
|
+
}
|
|
19
|
+
// validate: every checkpointed file mentioned
|
|
20
|
+
const missing = opts.checkpointedFiles.filter((f) => !summary.includes(f.split('/').pop() ?? f));
|
|
21
|
+
if (missing.length === 0) {
|
|
22
|
+
// (c) keep last N verbatim + summary as first message
|
|
23
|
+
const summaryMsg = { role: 'user', content: [{ kind: 'text', text: summary }] };
|
|
24
|
+
return { messages: [summaryMsg, ...newest], summary, dropped: elided.dropped, method: 'summarize' };
|
|
25
|
+
}
|
|
26
|
+
// retry once then fallback to elide
|
|
27
|
+
if (opts.summarizeFn) {
|
|
28
|
+
try {
|
|
29
|
+
const retry = await opts.summarizeFn(template + '\nEnsure to mention: ' + missing.join(', '));
|
|
30
|
+
if (missing.every((f) => retry.includes(f.split('/').pop() ?? f))) {
|
|
31
|
+
const summaryMsg2 = { role: 'user', content: [{ kind: 'text', text: retry }] };
|
|
32
|
+
return { messages: [summaryMsg2, ...newest], summary: retry, dropped: elided.dropped, method: 'summarize' };
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
catch { /* fallback */ }
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return { messages: elided.messages, summary: elided.dropped > 0 ? `Elided ${elided.dropped} observations` : '', dropped: elided.dropped, method: elided.dropped > 0 ? 'elide' : 'fallback' };
|
|
39
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare function storeResult(output: unknown): string;
|
|
2
|
+
export declare function expandResult(id: string): unknown | null;
|
|
3
|
+
export declare function checkUnchanged(path: string, content: string, turn: number): {
|
|
4
|
+
unchanged: boolean;
|
|
5
|
+
sinceTurn?: number;
|
|
6
|
+
};
|
|
7
|
+
export declare function storedIds(): string[];
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 8.2 — Tool result lifecycle: store large results by id, expand_result, unchanged detection
|
|
3
|
+
*/
|
|
4
|
+
import * as crypto from 'node:crypto';
|
|
5
|
+
const store = new Map();
|
|
6
|
+
const seenFiles = new Map();
|
|
7
|
+
function hash(s) { return crypto.createHash('sha1').update(s).digest('hex').slice(0, 8); }
|
|
8
|
+
export function storeResult(output) {
|
|
9
|
+
const id = `r_${crypto.randomBytes(3).toString('hex')}`;
|
|
10
|
+
const str = typeof output === 'string' ? output : JSON.stringify(output);
|
|
11
|
+
store.set(id, { output, size: str.length });
|
|
12
|
+
// cap store to ~50 entries
|
|
13
|
+
if (store.size > 50) {
|
|
14
|
+
const first = store.keys().next().value;
|
|
15
|
+
store.delete(first);
|
|
16
|
+
}
|
|
17
|
+
return id;
|
|
18
|
+
}
|
|
19
|
+
export function expandResult(id) {
|
|
20
|
+
return store.get(id)?.output ?? null;
|
|
21
|
+
}
|
|
22
|
+
export function checkUnchanged(path, content, turn) {
|
|
23
|
+
const h = hash(content);
|
|
24
|
+
const prev = seenFiles.get(path);
|
|
25
|
+
if (prev && prev.hash === h)
|
|
26
|
+
return { unchanged: true, sinceTurn: prev.turn };
|
|
27
|
+
seenFiles.set(path, { hash: h, turn });
|
|
28
|
+
return { unchanged: false };
|
|
29
|
+
}
|
|
30
|
+
export function storedIds() { return [...store.keys()]; }
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { PlanStep } from '../agent/runtime.js';
|
|
2
|
+
export declare function memoryWrite(cwd: string, content: string): Promise<string>;
|
|
3
|
+
export declare function loadMemory(cwd: string): Promise<string>;
|
|
4
|
+
export declare function shouldRemind(turn: number, lastRemindTurn: number): boolean;
|
|
5
|
+
export declare function reminderForTodos(todos: PlanStep[]): string | undefined;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 8.4 — Working memory & reminders: memory_write → .klyro/memory/session-notes.md (≤1k tokens) + todos re-inject
|
|
3
|
+
*/
|
|
4
|
+
import * as fs from 'node:fs/promises';
|
|
5
|
+
import * as path from 'node:path';
|
|
6
|
+
export async function memoryWrite(cwd, content) {
|
|
7
|
+
const dir = path.join(cwd, '.klyro', 'memory');
|
|
8
|
+
await fs.mkdir(dir, { recursive: true });
|
|
9
|
+
const p = path.join(dir, 'session-notes.md');
|
|
10
|
+
const prev = await fs.readFile(p, 'utf-8').catch(() => '');
|
|
11
|
+
const next = (prev + '\n' + content).slice(-4000); // ≤1k tokens ~4k chars
|
|
12
|
+
await fs.writeFile(p, next, 'utf-8');
|
|
13
|
+
return p;
|
|
14
|
+
}
|
|
15
|
+
export async function loadMemory(cwd) {
|
|
16
|
+
try {
|
|
17
|
+
return await fs.readFile(path.join(cwd, '.klyro', 'memory', 'session-notes.md'), 'utf-8');
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return '';
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export function shouldRemind(turn, lastRemindTurn) {
|
|
24
|
+
return turn - lastRemindTurn >= 20;
|
|
25
|
+
}
|
|
26
|
+
export function reminderForTodos(todos) {
|
|
27
|
+
if (todos.length === 0)
|
|
28
|
+
return undefined;
|
|
29
|
+
const active = todos.filter((t) => t.status === 'in_progress' || t.status === 'pending');
|
|
30
|
+
if (active.length === 0)
|
|
31
|
+
return undefined;
|
|
32
|
+
return `Reminder: todos pending — ${active.map((t) => t.title).join(', ')}`;
|
|
33
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare const expandResultTool: import("./types.js").Tool<{
|
|
2
|
+
id: string;
|
|
3
|
+
}, {
|
|
4
|
+
readonly id: string;
|
|
5
|
+
readonly output: null;
|
|
6
|
+
readonly note: "not found or expired";
|
|
7
|
+
} | {
|
|
8
|
+
readonly id: string;
|
|
9
|
+
readonly output: {} | undefined;
|
|
10
|
+
readonly note?: undefined;
|
|
11
|
+
}>;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { defineTool } from './types.js';
|
|
3
|
+
import { safe } from './normalize.js';
|
|
4
|
+
import { expandResult } from '../context/lifecycle.js';
|
|
5
|
+
export const expandResultTool = defineTool({
|
|
6
|
+
name: 'expand_result',
|
|
7
|
+
description: 'Expand a previously truncated large result by id.',
|
|
8
|
+
inputSchema: z.object({ id: z.string().min(1) }),
|
|
9
|
+
permission: 'read',
|
|
10
|
+
isConcurrencySafe: true,
|
|
11
|
+
execute: async (input) => safe(async () => {
|
|
12
|
+
const out = expandResult(input.id);
|
|
13
|
+
if (out === null)
|
|
14
|
+
return { id: input.id, output: null, note: 'not found or expired' };
|
|
15
|
+
return { id: input.id, output: out };
|
|
16
|
+
}),
|
|
17
|
+
});
|
|
@@ -18,6 +18,7 @@ import { defineTool } from '../types.js';
|
|
|
18
18
|
import { resolveAndFollowSymlinks } from '../../policy/path-guard.js';
|
|
19
19
|
import { TOOL_ERROR_CODES, safe } from '../normalize.js';
|
|
20
20
|
import { markRead } from './read-history.js';
|
|
21
|
+
import { checkUnchanged, storeResult } from '../../context/lifecycle.js';
|
|
21
22
|
const InputSchema = z.object({
|
|
22
23
|
path: z.string().min(1).describe('Path relative to cwd, or absolute path inside cwd'),
|
|
23
24
|
startLine: z.number().int().min(1).optional().describe('1-indexed inclusive start line'),
|
|
@@ -93,6 +94,14 @@ export const readFileTool = defineTool({
|
|
|
93
94
|
result.hint = 'Truncated to 8k tokens — use startLine/endLine to get more';
|
|
94
95
|
}
|
|
95
96
|
markRead(input.path);
|
|
97
|
+
// 8.2 lifecycle: store large results, duplicate detection <50 tokens
|
|
98
|
+
const asStr = outLines.join('\n');
|
|
99
|
+
if (asStr.length > 2000)
|
|
100
|
+
storeResult(asStr);
|
|
101
|
+
const unchanged = checkUnchanged(input.path, asStr, 0);
|
|
102
|
+
if (unchanged.unchanged) {
|
|
103
|
+
result.note = `unchanged since turn ${unchanged.sinceTurn}`;
|
|
104
|
+
}
|
|
96
105
|
return result;
|
|
97
106
|
}
|
|
98
107
|
finally {
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { defineTool } from './types.js';
|
|
3
|
+
import { safe } from './normalize.js';
|
|
4
|
+
import { memoryWrite } from '../context/memory.js';
|
|
5
|
+
export const memoryWriteTool = defineTool({
|
|
6
|
+
name: 'memory_write',
|
|
7
|
+
description: 'Write to .klyro/memory/session-notes.md (≤1k tokens injected, survives compaction)',
|
|
8
|
+
inputSchema: z.object({ content: z.string().min(1) }),
|
|
9
|
+
permission: 'edit',
|
|
10
|
+
isConcurrencySafe: false,
|
|
11
|
+
execute: async (input, ctx) => safe(async () => {
|
|
12
|
+
const p = await memoryWrite(ctx.cwd, input.content);
|
|
13
|
+
return { path: p, bytes: input.content.length };
|
|
14
|
+
}),
|
|
15
|
+
});
|
package/dist/tools/registry.js
CHANGED
|
@@ -23,6 +23,8 @@ import { repoMapTool } from './repo-map.js';
|
|
|
23
23
|
import { importsOfTool, importersOfTool } from './search/imports.js';
|
|
24
24
|
import { findSymbolTool } from './symbols/find-symbol.js';
|
|
25
25
|
import { lspDiagnosticsTool, lspGotoDefinitionTool } from './lsp/diagnostics.js';
|
|
26
|
+
import { expandResultTool } from './expand-result.js';
|
|
27
|
+
import { memoryWriteTool } from './memory-write.js';
|
|
26
28
|
import { zodToJsonSchema } from './schema.js';
|
|
27
29
|
export class ToolRegistry {
|
|
28
30
|
tools = new Map();
|
|
@@ -103,5 +105,7 @@ export const builtinRegistry = () => {
|
|
|
103
105
|
r.register(findSymbolTool);
|
|
104
106
|
r.register(lspDiagnosticsTool);
|
|
105
107
|
r.register(lspGotoDefinitionTool);
|
|
108
|
+
r.register(expandResultTool);
|
|
109
|
+
r.register(memoryWriteTool);
|
|
106
110
|
return r;
|
|
107
111
|
};
|