@falkenslab/agent-kit 0.17.0 → 0.19.0
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/assets/extension-launcher.mjs +24 -0
- package/dist/chat/chatController.d.ts +264 -0
- package/dist/chat/chatController.js +510 -0
- package/dist/chat/extensions.d.ts +14 -0
- package/dist/chat/extensions.js +41 -0
- package/dist/chat/history.d.ts +17 -0
- package/dist/chat/history.js +39 -0
- package/dist/{tui → chat}/runs.d.ts +8 -1
- package/dist/{tui → chat}/runs.js +1 -1
- package/dist/core/agentSpec.d.ts +41 -27
- package/dist/core/extensions.d.ts +119 -0
- package/dist/core/extensions.js +97 -0
- package/dist/core/externalExtensions.d.ts +131 -0
- package/dist/core/externalExtensions.js +340 -0
- package/dist/core/hooks/fileScopeGate.d.ts +17 -2
- package/dist/core/hooks/fileScopeGate.js +45 -10
- package/dist/core/hooks/planGate.d.ts +2 -0
- package/dist/core/hooks/planGate.js +1 -10
- package/dist/core/hooks/stepGate.d.ts +1 -1
- package/dist/core/hooks/stepGate.js +3 -3
- package/dist/core/language.js +1 -1
- package/dist/core/messages/de.js +12 -33
- package/dist/core/messages/en.d.ts +13 -16
- package/dist/core/messages/en.js +12 -33
- package/dist/core/messages/es.js +12 -33
- package/dist/core/messages/fr.js +12 -33
- package/dist/core/messages/index.d.ts +6 -0
- package/dist/core/messages/index.js +12 -1
- package/dist/core/packaged.d.ts +10 -0
- package/dist/core/packaged.js +38 -0
- package/dist/core/pluginAgents.d.ts +20 -0
- package/dist/core/pluginAgents.js +76 -0
- package/dist/core/runner.js +16 -9
- package/dist/core/session.d.ts +42 -5
- package/dist/core/session.js +203 -99
- package/dist/core/sessionFacts.d.ts +80 -0
- package/dist/core/sessionFacts.js +48 -0
- package/dist/core/toolLabels.d.ts +24 -6
- package/dist/core/toolLabels.js +24 -39
- package/dist/core/tools/humanApproval.js +3 -3
- package/dist/extensions/awareness/index.d.ts +13 -0
- package/dist/extensions/awareness/index.js +39 -0
- package/dist/extensions/awareness/labels.d.ts +3 -0
- package/dist/extensions/awareness/labels.js +12 -0
- package/dist/extensions/awareness/tools.d.ts +5 -0
- package/dist/extensions/awareness/tools.js +54 -0
- package/dist/{core → extensions/knowledge}/fileKnowledgeStore.d.ts +1 -3
- package/dist/{core → extensions/knowledge}/fileKnowledgeStore.js +23 -29
- package/dist/extensions/knowledge/index.d.ts +8 -0
- package/dist/extensions/knowledge/index.js +35 -0
- package/dist/{core → extensions/knowledge}/knowledgeStore.d.ts +5 -9
- package/dist/{core → extensions/knowledge}/knowledgeStore.js +16 -2
- package/dist/extensions/knowledge/labels.d.ts +3 -0
- package/dist/extensions/knowledge/labels.js +119 -0
- package/dist/extensions/knowledge/prompt.d.ts +28 -0
- package/dist/extensions/knowledge/prompt.js +59 -0
- package/dist/{core/tools/knowledgeTools.d.ts → extensions/knowledge/tools.d.ts} +7 -7
- package/dist/{core/tools/knowledgeTools.js → extensions/knowledge/tools.js} +20 -55
- package/dist/extensions/memory/index.d.ts +12 -0
- package/dist/extensions/memory/index.js +56 -0
- package/dist/extensions/memory/labels.d.ts +3 -0
- package/dist/extensions/memory/labels.js +58 -0
- package/dist/extensions/memory/memoryStore.d.ts +53 -0
- package/dist/extensions/memory/memoryStore.js +88 -0
- package/dist/extensions/memory/tools.d.ts +9 -0
- package/dist/extensions/memory/tools.js +82 -0
- package/dist/extensions/sources/index.d.ts +9 -0
- package/dist/extensions/sources/index.js +40 -0
- package/dist/extensions/sources/labels.d.ts +3 -0
- package/dist/extensions/sources/labels.js +82 -0
- package/dist/{core → extensions/sources}/sources.d.ts +24 -31
- package/dist/{core → extensions/sources}/sources.js +45 -108
- package/dist/{core/tools/saveToSources.d.ts → extensions/sources/tools.d.ts} +7 -3
- package/dist/{core/tools/saveToSources.js → extensions/sources/tools.js} +22 -17
- package/dist/index.d.ts +14 -9
- package/dist/index.js +11 -7
- package/dist/tui/chatTui.d.ts +11 -20
- package/dist/tui/chatTui.js +75 -230
- package/dist/tui/extensionCommand.d.ts +21 -0
- package/dist/tui/extensionCommand.js +133 -0
- package/dist/tui/ink/runChatInk.d.ts +4 -3
- package/dist/tui/ink/runChatInk.js +101 -217
- package/extensions/awareness/.claude-plugin/plugin.json +10 -0
- package/{assets/agent-help → extensions/awareness/skills/help}/SKILL.md +4 -3
- package/{assets/knowledge-plugin → extensions/knowledge}/.claude-plugin/plugin.json +7 -1
- package/extensions/knowledge/commands/ingest.md +7 -0
- package/{assets/knowledge-plugin → extensions/knowledge}/skills/knowledge-ingest/SKILL.md +8 -3
- package/{assets/knowledge-plugin → extensions/knowledge}/skills/knowledge-lint/SKILL.md +12 -5
- package/extensions/memory/.claude-plugin/plugin.json +10 -0
- package/extensions/memory/commands/forget.md +8 -0
- package/extensions/memory/commands/list.md +5 -0
- package/extensions/sources/.claude-plugin/plugin.json +10 -0
- package/package.json +3 -2
- package/assets/knowledge-plugin/commands/ingest.md +0 -7
- package/assets/knowledge-plugin/commands/query.md +0 -5
- package/assets/knowledge-plugin-files/.claude-plugin/plugin.json +0 -4
- package/assets/knowledge-plugin-files/commands/ingest.md +0 -7
- package/assets/knowledge-plugin-files/commands/lint.md +0 -5
- package/assets/knowledge-plugin-files/skills/knowledge-ingest/SKILL.md +0 -46
- package/assets/knowledge-plugin-files/skills/knowledge-lint/SKILL.md +0 -26
- package/assets/knowledge-plugin-files/skills/knowledge-pages/SKILL.md +0 -137
- package/assets/knowledge-plugin-files/skills/knowledge-query/SKILL.md +0 -14
- package/dist/core/agentHelp.d.ts +0 -30
- package/dist/core/agentHelp.js +0 -55
- package/dist/core/knowledge.d.ts +0 -25
- package/dist/core/knowledge.js +0 -84
- /package/dist/{core → extensions/sources}/extractText.d.ts +0 -0
- /package/dist/{core → extensions/sources}/extractText.js +0 -0
- /package/{assets/knowledge-plugin → extensions/knowledge}/commands/lint.md +0 -0
- /package/{assets/knowledge-plugin-files → extensions/knowledge}/commands/query.md +0 -0
- /package/{assets/knowledge-plugin → extensions/knowledge}/skills/knowledge-query/SKILL.md +0 -0
|
@@ -5,10 +5,11 @@ import path from "node:path";
|
|
|
5
5
|
/**
|
|
6
6
|
* The sources folder's bookkeeping: originals stay files (ADR-024), and the kit keeps a
|
|
7
7
|
* manifest next to them (`.agent-kit/sources.json`, inside `sourcesDir`, where the agent's
|
|
8
|
-
* file tools can't write) with each original's hash, size
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
8
|
+
* file tools can't write) with each original's hash, size, provenance, the version it replaces
|
|
9
|
+
* and when its content last changed. From that it refuses duplicates, tells present, missing
|
|
10
|
+
* and retired originals apart, and retires an original into `.agent-kit/retired/` instead of
|
|
11
|
+
* deleting it. It knows nothing of the knowledge base: each owns its data, and the model
|
|
12
|
+
* connects them through their tools (#30). Hashes never leave it.
|
|
12
13
|
*/
|
|
13
14
|
export const KIT_DIR = ".agent-kit";
|
|
14
15
|
const MANIFEST = "sources.json";
|
|
@@ -70,41 +71,20 @@ export async function scanSources(sourcesDir) {
|
|
|
70
71
|
await walk(sourcesDir);
|
|
71
72
|
return found.sort();
|
|
72
73
|
}
|
|
73
|
-
/**
|
|
74
|
+
/**
|
|
75
|
+
* An entry for `file`, reusing the recorded hash when its size and modification time haven't
|
|
76
|
+
* changed; `changedAt` moves only when the content (its hash) did. Entries from before
|
|
77
|
+
* `changedAt` existed take their `addedAt`.
|
|
78
|
+
*/
|
|
74
79
|
async function freshEntry(file, known, origin) {
|
|
75
80
|
const info = await stat(file);
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
if (!link || /^[a-z]+:\/\//i.test(link))
|
|
84
|
-
return undefined;
|
|
85
|
-
return path.resolve(pageDir, decodeURI(link));
|
|
86
|
-
}
|
|
87
|
-
/** The summary pages (knowledge base layout: `summaries/*.md`) by the absolute path of the original their frontmatter `file:` points to. */
|
|
88
|
-
export async function summariesByOriginal(knowledgeDir) {
|
|
89
|
-
const byOriginal = new Map();
|
|
90
|
-
const dir = path.join(knowledgeDir, "summaries");
|
|
91
|
-
let names;
|
|
92
|
-
try {
|
|
93
|
-
names = await readdir(dir);
|
|
94
|
-
}
|
|
95
|
-
catch {
|
|
96
|
-
return byOriginal;
|
|
97
|
-
}
|
|
98
|
-
for (const name of names.filter((n) => n.endsWith(".md"))) {
|
|
99
|
-
const page = path.join(dir, name);
|
|
100
|
-
const text = await readFile(page, "utf8");
|
|
101
|
-
const frontmatter = /^---\r?\n([\s\S]*?)\r?\n---/.exec(text)?.[1] ?? "";
|
|
102
|
-
const value = /^file:\s*(.+)$/m.exec(frontmatter)?.[1];
|
|
103
|
-
const original = value ? linkedFile(value, dir) : undefined;
|
|
104
|
-
if (original)
|
|
105
|
-
byOriginal.set(path.normalize(original), [...(byOriginal.get(path.normalize(original)) ?? []), page]);
|
|
106
|
-
}
|
|
107
|
-
return byOriginal;
|
|
81
|
+
const previous = known ? { ...known, changedAt: known.changedAt ?? known.addedAt } : undefined;
|
|
82
|
+
if (previous && previous.size === info.size && previous.mtimeMs === info.mtimeMs)
|
|
83
|
+
return previous;
|
|
84
|
+
const hash = await hashFile(file);
|
|
85
|
+
const stamp = now();
|
|
86
|
+
const base = previous ?? { addedAt: stamp, origin, changedAt: stamp };
|
|
87
|
+
return { ...base, hash, size: info.size, mtimeMs: info.mtimeMs, changedAt: previous && previous.hash === hash ? previous.changedAt : stamp };
|
|
108
88
|
}
|
|
109
89
|
/** Pages of a PDF, read from its page tree without a parser; undefined when it can't tell (e.g. compressed object streams). */
|
|
110
90
|
export function pdfPageCount(bytes) {
|
|
@@ -124,31 +104,19 @@ export function pptxSlideCount(bytes) {
|
|
|
124
104
|
}
|
|
125
105
|
const typeOf = (file) => path.extname(file).slice(1).toLowerCase() || "file";
|
|
126
106
|
/**
|
|
127
|
-
* Every original with its status
|
|
128
|
-
* the
|
|
129
|
-
* folder there's no ingest to compare with: originals are "present".
|
|
107
|
+
* Every original with its status and when its content last changed, recording in the manifest
|
|
108
|
+
* the ones copied in by hand; then the missing ones and the retired ones.
|
|
130
109
|
*/
|
|
131
|
-
export function listSources(sourcesDir
|
|
132
|
-
return serialized(sourcesDir, () => listSourcesNow(sourcesDir
|
|
110
|
+
export function listSources(sourcesDir) {
|
|
111
|
+
return serialized(sourcesDir, () => listSourcesNow(sourcesDir));
|
|
133
112
|
}
|
|
134
|
-
async function listSourcesNow(sourcesDir
|
|
113
|
+
async function listSourcesNow(sourcesDir) {
|
|
135
114
|
const manifest = await loadManifest(sourcesDir);
|
|
136
|
-
const summaries = knowledgeDir ? await summariesByOriginal(knowledgeDir) : new Map();
|
|
137
115
|
const files = await scanSources(sourcesDir);
|
|
138
116
|
const listing = [];
|
|
139
117
|
for (const rel of files) {
|
|
140
118
|
const full = path.join(sourcesDir, rel);
|
|
141
119
|
const entry = await freshEntry(full, manifest.files[rel], { kind: "manual" });
|
|
142
|
-
const pagesOf = summaries.get(path.normalize(full)) ?? [];
|
|
143
|
-
let status = "present";
|
|
144
|
-
if (knowledgeDir) {
|
|
145
|
-
if (pagesOf.length === 0)
|
|
146
|
-
status = "new";
|
|
147
|
-
else {
|
|
148
|
-
entry.ingestedHash ??= entry.hash;
|
|
149
|
-
status = entry.ingestedHash === entry.hash ? "ingested" : "changed";
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
120
|
manifest.files[rel] = entry;
|
|
153
121
|
const type = typeOf(rel);
|
|
154
122
|
const counts = type === "pdf" || type === "pptx"
|
|
@@ -156,18 +124,28 @@ async function listSourcesNow(sourcesDir, knowledgeDir, projectDir) {
|
|
|
156
124
|
: {};
|
|
157
125
|
listing.push({
|
|
158
126
|
path: rel,
|
|
159
|
-
status,
|
|
127
|
+
status: "present",
|
|
160
128
|
type,
|
|
129
|
+
changedAt: entry.changedAt,
|
|
161
130
|
size: entry.size,
|
|
162
131
|
...counts,
|
|
163
132
|
origin: entry.origin,
|
|
164
133
|
...(entry.replaces ? { replaces: entry.replaces } : {}),
|
|
165
|
-
summaries: pagesOf.map((page) => posix(path.relative(projectDir, page))),
|
|
166
134
|
});
|
|
167
135
|
}
|
|
168
136
|
for (const [rel, entry] of Object.entries(manifest.files)) {
|
|
169
137
|
if (!files.includes(rel))
|
|
170
|
-
listing.push({ path: rel, status: "missing", type: typeOf(rel), origin: entry.origin
|
|
138
|
+
listing.push({ path: rel, status: "missing", type: typeOf(rel), origin: entry.origin });
|
|
139
|
+
}
|
|
140
|
+
for (const retired of manifest.retired) {
|
|
141
|
+
listing.push({
|
|
142
|
+
path: retired.path,
|
|
143
|
+
status: "retired",
|
|
144
|
+
type: typeOf(retired.path),
|
|
145
|
+
retiredAt: retired.retiredAt,
|
|
146
|
+
why: retired.why,
|
|
147
|
+
...(retired.replacedBy ? { replacedBy: retired.replacedBy } : {}),
|
|
148
|
+
});
|
|
171
149
|
}
|
|
172
150
|
await saveManifest(sourcesDir, manifest);
|
|
173
151
|
return listing;
|
|
@@ -219,11 +197,13 @@ async function addSourceNow(sourcesDir, file, destination, origin, extra) {
|
|
|
219
197
|
}
|
|
220
198
|
const rel = posix(path.relative(sourcesDir, target));
|
|
221
199
|
const info = await stat(target);
|
|
200
|
+
const stamp = now();
|
|
222
201
|
manifest.files[rel] = {
|
|
223
202
|
hash,
|
|
224
203
|
size: info.size,
|
|
225
204
|
mtimeMs: info.mtimeMs,
|
|
226
|
-
addedAt:
|
|
205
|
+
addedAt: stamp,
|
|
206
|
+
changedAt: stamp,
|
|
227
207
|
origin,
|
|
228
208
|
...(extra.replaces ? { replaces: posix(path.relative(sourcesDir, resolveWithin(sourcesDir, extra.replaces))) } : {}),
|
|
229
209
|
...(extra.derivedFrom ? { derivedFrom: extra.derivedFrom } : {}),
|
|
@@ -231,26 +211,15 @@ async function addSourceNow(sourcesDir, file, destination, origin, extra) {
|
|
|
231
211
|
await saveManifest(sourcesDir, manifest);
|
|
232
212
|
return { path: rel };
|
|
233
213
|
}
|
|
234
|
-
/** Adds frontmatter fields and a notice under the frontmatter of a markdown page. */
|
|
235
|
-
async function markPage(page, fields, notice) {
|
|
236
|
-
const text = await readFile(page, "utf8");
|
|
237
|
-
const match = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/.exec(text);
|
|
238
|
-
const lines = Object.entries(fields).map(([key, value]) => `${key}: ${value}`);
|
|
239
|
-
const updated = match
|
|
240
|
-
? `---\n${match[1].split(/\r?\n/).filter((line) => !Object.keys(fields).some((key) => line.startsWith(`${key}:`))).concat(lines).join("\n")}\n---\n\n> ${notice}\n\n${text.slice(match[0].length).replace(/^\s+/, "")}`
|
|
241
|
-
: `---\n${lines.join("\n")}\n---\n\n> ${notice}\n\n${text}`;
|
|
242
|
-
await writeFile(page, updated);
|
|
243
|
-
}
|
|
244
214
|
/**
|
|
245
|
-
* Moves an original to `.agent-kit/retired/` (never deleted: the person empties that folder)
|
|
246
|
-
*
|
|
247
|
-
*
|
|
248
|
-
* when there is one.
|
|
215
|
+
* Moves an original to `.agent-kit/retired/` (never deleted: the person empties that folder) and
|
|
216
|
+
* records why. What was built from it (summary pages) is the knowledge base's to update: the
|
|
217
|
+
* model does it with its tools.
|
|
249
218
|
*/
|
|
250
|
-
export function retireSource(sourcesDir,
|
|
251
|
-
return serialized(sourcesDir, () => retireSourceNow(sourcesDir,
|
|
219
|
+
export function retireSource(sourcesDir, source, why, reason, replacedBy) {
|
|
220
|
+
return serialized(sourcesDir, () => retireSourceNow(sourcesDir, source, why, reason, replacedBy));
|
|
252
221
|
}
|
|
253
|
-
async function retireSourceNow(sourcesDir,
|
|
222
|
+
async function retireSourceNow(sourcesDir, source, why, reason, replacedBy) {
|
|
254
223
|
const full = resolveWithin(sourcesDir, source);
|
|
255
224
|
if (!full || !(await stat(full).catch(() => null))?.isFile())
|
|
256
225
|
throw new Error(`"${source}" isn't an original in sources/.`);
|
|
@@ -260,7 +229,6 @@ async function retireSourceNow(sourcesDir, knowledgeDir, projectDir, source, why
|
|
|
260
229
|
const replacement = replacedBy ? resolveWithin(sourcesDir, replacedBy) : undefined;
|
|
261
230
|
if (replacedBy && (!replacement || !(await stat(replacement).catch(() => null))))
|
|
262
231
|
throw new Error(`"${replacedBy}" isn't an original in sources/.`);
|
|
263
|
-
const summaries = knowledgeDir ? await summariesByOriginal(knowledgeDir) : new Map();
|
|
264
232
|
const manifest = await loadManifest(sourcesDir);
|
|
265
233
|
const stamp = now();
|
|
266
234
|
const movedTo = posix(path.join(KIT_DIR, RETIRED_DIR, `${stamp.replace(/[:.]/g, "-")}-${path.basename(rel)}`));
|
|
@@ -277,36 +245,5 @@ async function retireSourceNow(sourcesDir, knowledgeDir, projectDir, source, why
|
|
|
277
245
|
});
|
|
278
246
|
delete manifest.files[rel];
|
|
279
247
|
await saveManifest(sourcesDir, manifest);
|
|
280
|
-
|
|
281
|
-
const replacementSummary = replacement ? summaries.get(path.normalize(replacement))?.[0] : undefined;
|
|
282
|
-
const result = { movedTo, summaries: [] };
|
|
283
|
-
for (const page of summaries.get(path.normalize(full)) ?? []) {
|
|
284
|
-
if (why === "wrong") {
|
|
285
|
-
await markPage(page, { status: "retired", retired: date }, `Retired on ${date}: its original was wrong (${reason}). Don't rely on what it says.`);
|
|
286
|
-
result.summaries.push({ page: posix(path.relative(projectDir, page)), marked: "retired" });
|
|
287
|
-
}
|
|
288
|
-
else {
|
|
289
|
-
const link = replacementSummary ? posix(path.relative(path.dirname(page), replacementSummary)) : undefined;
|
|
290
|
-
await markPage(page, { status: "superseded", superseded: date, ...(link ? { superseded_by: link } : {}) }, `Superseded on ${date}: its original was replaced (${reason})${link ? `; see [the new summary](${link})` : ""}.`);
|
|
291
|
-
result.summaries.push({ page: posix(path.relative(projectDir, page)), marked: "superseded", ...(replacementSummary ? { supersededBy: posix(path.relative(projectDir, replacementSummary)) } : {}) });
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
return result;
|
|
295
|
-
}
|
|
296
|
-
/**
|
|
297
|
-
* Records `source` (relative to `sourcesDir`) as ingested now, with its current hash: a
|
|
298
|
-
* summary page was just written from it, so "changed" means changed after this.
|
|
299
|
-
*/
|
|
300
|
-
export function recordIngest(sourcesDir, source) {
|
|
301
|
-
return serialized(sourcesDir, async () => {
|
|
302
|
-
const full = resolveWithin(sourcesDir, source);
|
|
303
|
-
if (!full || !(await stat(full).catch(() => null))?.isFile())
|
|
304
|
-
return;
|
|
305
|
-
const rel = posix(path.relative(sourcesDir, full));
|
|
306
|
-
const manifest = await loadManifest(sourcesDir);
|
|
307
|
-
const entry = await freshEntry(full, manifest.files[rel], { kind: "manual" });
|
|
308
|
-
entry.ingestedHash = entry.hash;
|
|
309
|
-
manifest.files[rel] = entry;
|
|
310
|
-
await saveManifest(sourcesDir, manifest);
|
|
311
|
-
});
|
|
248
|
+
return { movedTo };
|
|
312
249
|
}
|
|
@@ -8,10 +8,14 @@ export declare const MAX_SOURCE_BYTES: number;
|
|
|
8
8
|
export declare function htmlToMarkdown(html: string, url: string): Promise<string | null>;
|
|
9
9
|
/** Downloads `url` into `file`, refusing other schemes and anything over `maxBytes`; returns its content type. */
|
|
10
10
|
export declare function downloadToFile(url: string, file: string, maxBytes: number): Promise<string>;
|
|
11
|
+
/**
|
|
12
|
+
* The "Sources" section appended to the system prompt whenever there's a sources folder, with or
|
|
13
|
+
* without a knowledge base: what the originals are and how they come and go. `projectDir` and
|
|
14
|
+
* `sourcesDir` give the folder's name as the model reads it.
|
|
15
|
+
*/
|
|
16
|
+
export declare function sourcesPromptSection(projectDir: string, sourcesDir: string): string;
|
|
11
17
|
/** Options of `createSaveToSourcesServer()`. */
|
|
12
18
|
export interface SourceToolsOptions {
|
|
13
|
-
/** The knowledge folder, to tell which originals have a summary page (`list_sources`). */
|
|
14
|
-
knowledgeDir?: string;
|
|
15
19
|
/** The project root: paths in the tools' answers are relative to it. Defaults to the parent of `sourcesDir`. */
|
|
16
20
|
projectDir?: string;
|
|
17
21
|
/** A person can be asked (not autonomous): adds `request_file` and `retire_source`, which ask first. */
|
|
@@ -24,6 +28,6 @@ export interface SourceToolsOptions {
|
|
|
24
28
|
* are scoped to the notes folder, see hooks/fileScopeGate.ts): `save_to_sources`,
|
|
25
29
|
* `list_sources` and `download_to_sources`, plus `request_file` and `retire_source` when a
|
|
26
30
|
* person can be asked. Nothing is ever overwritten or deleted; the bookkeeping is in
|
|
27
|
-
* sources.ts.
|
|
31
|
+
* sources.ts (this extension's).
|
|
28
32
|
*/
|
|
29
33
|
export declare function createSaveToSourcesServer(runDir: string, sourcesDir: string, description?: string, options?: SourceToolsOptions): import("@anthropic-ai/claude-agent-sdk").McpSdkServerConfigWithInstance;
|
|
@@ -2,10 +2,10 @@ import path from "node:path";
|
|
|
2
2
|
import { mkdir, readdir, readFile, rm, stat, writeFile } from "node:fs/promises";
|
|
3
3
|
import { tool, createSdkMcpServer } from "@anthropic-ai/claude-agent-sdk";
|
|
4
4
|
import { z } from "zod";
|
|
5
|
-
import { addSource, listSources, resolveWithin, retireSource } from "
|
|
6
|
-
import { askForDecision, askForText } from "
|
|
7
|
-
import { t } from "
|
|
8
|
-
import { EXTRACTABLE, extractText } from "
|
|
5
|
+
import { addSource, listSources, resolveWithin, retireSource } from "./sources.js";
|
|
6
|
+
import { askForDecision, askForText } from "../../core/hooks/humanInput.js";
|
|
7
|
+
import { t } from "../../core/messages/index.js";
|
|
8
|
+
import { EXTRACTABLE, extractText } from "./extractText.js";
|
|
9
9
|
/**
|
|
10
10
|
* `resolvedPath` as given if it exists; otherwise, a same-directory sibling whose
|
|
11
11
|
* filename is Unicode-equal under NFC normalization. Needed because a file downloaded via
|
|
@@ -104,12 +104,22 @@ export async function downloadToFile(url, file, maxBytes) {
|
|
|
104
104
|
await writeFile(file, Buffer.concat(chunks));
|
|
105
105
|
return response.headers.get("content-type") ?? "";
|
|
106
106
|
}
|
|
107
|
+
/**
|
|
108
|
+
* The "Sources" section appended to the system prompt whenever there's a sources folder, with or
|
|
109
|
+
* without a knowledge base: what the originals are and how they come and go. `projectDir` and
|
|
110
|
+
* `sourcesDir` give the folder's name as the model reads it.
|
|
111
|
+
*/
|
|
112
|
+
export function sourcesPromptSection(projectDir, sourcesDir) {
|
|
113
|
+
const folder = `${path.relative(projectDir, sourcesDir).split(path.sep).join("/") || "."}/`;
|
|
114
|
+
return `## Sources
|
|
115
|
+
- **Originals (read-only for you)**: \`${folder}\`. Never rewrite one; build on them. \`list_sources\` lists them with when each one's content last changed (\`changedAt\`), and the missing and retired ones; add a file only with \`save_to_sources\`, \`download_to_sources\` or by asking the person (\`request_file\`), and take out a wrong or superseded one only with \`retire_source\`. \`Read\` reads PDFs and images; \`extract_text\` reads DOCX, PPTX (with speaker notes) and XLSX.`;
|
|
116
|
+
}
|
|
107
117
|
/**
|
|
108
118
|
* The tools that keep `sourcesDir`, the originals the agent reads but never edits (Write/Edit
|
|
109
119
|
* are scoped to the notes folder, see hooks/fileScopeGate.ts): `save_to_sources`,
|
|
110
120
|
* `list_sources` and `download_to_sources`, plus `request_file` and `retire_source` when a
|
|
111
121
|
* person can be asked. Nothing is ever overwritten or deleted; the bookkeeping is in
|
|
112
|
-
* sources.ts.
|
|
122
|
+
* sources.ts (this extension's).
|
|
113
123
|
*/
|
|
114
124
|
export function createSaveToSourcesServer(runDir, sourcesDir, description = DEFAULT_DESCRIPTION, options = {}) {
|
|
115
125
|
const projectDir = options.projectDir ?? path.dirname(sourcesDir);
|
|
@@ -149,9 +159,9 @@ export function createSaveToSourcesServer(runDir, sourcesDir, description = DEFA
|
|
|
149
159
|
return fail(message(error));
|
|
150
160
|
}
|
|
151
161
|
});
|
|
152
|
-
const listSourcesTool = tool("list_sources", "List the originals in sources
|
|
162
|
+
const listSourcesTool = tool("list_sources", "List the originals in sources/: present, missing (deleted by hand) or retired (when, why, what replaced it); with changedAt (when its content last changed, ISO 8601 in UTC: a later date sorts after as text, to compare with when something was built from it), type, size, pages of a PDF or slides of a PPTX (read long ones in parts), provenance and the version it replaces. Use it instead of listing the folder.", {}, async () => {
|
|
153
163
|
try {
|
|
154
|
-
return ok(JSON.stringify(await listSources(sourcesDir
|
|
164
|
+
return ok(JSON.stringify(await listSources(sourcesDir), null, 2));
|
|
155
165
|
}
|
|
156
166
|
catch (error) {
|
|
157
167
|
return fail(message(error));
|
|
@@ -215,7 +225,7 @@ export function createSaveToSourcesServer(runDir, sourcesDir, description = DEFA
|
|
|
215
225
|
}
|
|
216
226
|
});
|
|
217
227
|
const requestFile = tool("request_file", "Ask the person for a file you need (a syllabus, a document they have) and copy it into sources/ with its provenance. They give a path or say they don't have it; never overwrites, and doesn't copy a file identical to one already there.", {
|
|
218
|
-
description: z.string().describe("What file you need and why, in one or two sentences, in the
|
|
228
|
+
description: z.string().describe("What file you need and why, in one or two sentences, in the language you reply in"),
|
|
219
229
|
destination: z.string().optional().describe("Path to save it under, relative to sources/; the file's own name if omitted"),
|
|
220
230
|
replaces,
|
|
221
231
|
}, async (args) => {
|
|
@@ -235,23 +245,18 @@ export function createSaveToSourcesServer(runDir, sourcesDir, description = DEFA
|
|
|
235
245
|
return fail(message(error));
|
|
236
246
|
}
|
|
237
247
|
});
|
|
238
|
-
const retire = tool("retire_source", 'Retire an original that was wrong ("wrong") or replaced by a better one ("replaced")
|
|
248
|
+
const retire = tool("retire_source", 'Retire an original that was wrong ("wrong") or replaced by a better one ("replaced"). It asks the person itself, with a panel: don\'t ask them first. Approved, it\'s moved to sources/.agent-kit/retired/ (never deleted), and list_sources shows it as retired. What you built from it is yours to review afterwards.', {
|
|
239
249
|
source: z.string().describe("The original, relative to sources/"),
|
|
240
250
|
why: z.enum(["wrong", "replaced"]),
|
|
241
|
-
reason: z.string().describe("Why, in one sentence, in the
|
|
251
|
+
reason: z.string().describe("Why, in one sentence, in the language you reply in"),
|
|
242
252
|
replacedBy: z.string().optional().describe('For "replaced": the original that replaces it, relative to sources/'),
|
|
243
253
|
}, async (args) => {
|
|
244
254
|
const answer = await askForDecision(runDir, { title: t().retireTitle, lines: t().retireLines(inFolder(args.source), args.why, args.reason, args.replacedBy && inFolder(args.replacedBy), folder) });
|
|
245
255
|
if (answer !== "" && answer !== "y" && answer !== "yes")
|
|
246
256
|
return ok(answer === "q" ? "The person stopped: don't retire it, and stop what you were doing." : "The person said no: the original stays.");
|
|
247
257
|
try {
|
|
248
|
-
const result = await retireSource(sourcesDir,
|
|
249
|
-
|
|
250
|
-
return ok([
|
|
251
|
-
`Retired: moved to sources/${result.movedTo}.`,
|
|
252
|
-
...(pages.length ? pages : ["It had no summary page."]),
|
|
253
|
-
"Now update index.md, and correct or re-ground the pages that cite it (search the knowledge base for its file name).",
|
|
254
|
-
].join("\n"));
|
|
258
|
+
const result = await retireSource(sourcesDir, inFolder(args.source), args.why, args.reason, args.replacedBy && inFolder(args.replacedBy));
|
|
259
|
+
return ok(`Retired: moved to sources/${result.movedTo}. If anything was built from it (a summary, notes that cite it), review it now: ${args.why === "wrong" ? "what it taught was wrong" : `it was replaced${args.replacedBy ? ` by sources/${inFolder(args.replacedBy)}` : ""}`}.`);
|
|
255
260
|
}
|
|
256
261
|
catch (error) {
|
|
257
262
|
return fail(message(error));
|
package/dist/index.d.ts
CHANGED
|
@@ -5,17 +5,21 @@ export { allowAnyMcpTool } from "./core/mcpPermissions.js";
|
|
|
5
5
|
export { createPromptLoader } from "./core/promptTemplate.js";
|
|
6
6
|
export { resolveClaudeAuth, type ClaudeAuthConfig } from "./core/claudeAuth.js";
|
|
7
7
|
export { ensureClaudeAuth } from "./tui/claudeAuth.js";
|
|
8
|
-
export { createFriendlyToolLabel, truncate, truncatePath, type ToolDescriber, } from "./core/toolLabels.js";
|
|
8
|
+
export { createFriendlyToolLabel, truncate, truncatePath, withToolLabels, withToolPhrases, type ToolDescriber, type ToolLabel, type ToolLabels, } from "./core/toolLabels.js";
|
|
9
9
|
export type { Mode, BaseSessionConfig, AgentSpec, AgentIdentity, PlanModeSpec } from "./core/agentSpec.js";
|
|
10
|
-
export { buildSessionOptions, createInputQueue, createDeferred, createModeControl, togglePlanMode, type ModeControl } from "./core/session.js";
|
|
10
|
+
export { buildSessionOptions, createInputQueue, createDeferred, createModeControl, togglePlanMode, type ModeControl, type ExtensionsStatus } from "./core/session.js";
|
|
11
11
|
export { runQuery, type AgentEvent, type AgentRun, type SessionUsage, type ContextUsage } from "./core/runner.js";
|
|
12
|
-
export {
|
|
12
|
+
export type { SessionFacts } from "./core/sessionFacts.js";
|
|
13
|
+
export { knowledgePluginRoot, knowledgePromptSection } from "./extensions/knowledge/prompt.js";
|
|
14
|
+
export { readExtensionManifest, type Extension, type ExtensionContext, type ExtensionContribution, type ExtensionManifest, } from "./core/extensions.js";
|
|
15
|
+
export { addExtension, removeExtension, setExtensionEnabled, listInstalled, readExternalManifest, type ExtensionAuthor, type ExtensionDirs, type ExtensionScope, type ExternalManifest, type ExternalToolLabel, type PluginServer, type InstalledExtension, type LockEntry, } from "./core/externalExtensions.js";
|
|
16
|
+
export { runExtensionCommand } from "./tui/extensionCommand.js";
|
|
13
17
|
export { agentKitVersion } from "./core/version.js";
|
|
14
|
-
export { BUILT_IN_PAGE_TYPES, isSlug, parseId, linkedIds, type KnowledgeStore, type PageType, type KnowledgePage, type PageInfo, type SearchHit, type CheckReport, type FieldChanges, type NewPage, } from "./
|
|
15
|
-
export { createFileKnowledgeStore, type FileKnowledgeStoreOptions } from "./
|
|
16
|
-
export { createKnowledgeServer, type KnowledgeToolsOptions } from "./
|
|
18
|
+
export { BUILT_IN_PAGE_TYPES, isSlug, parseId, linkedIds, type KnowledgeStore, type PageType, type KnowledgePage, type PageInfo, type SearchHit, type CheckReport, type FieldChanges, type NewPage, } from "./extensions/knowledge/knowledgeStore.js";
|
|
19
|
+
export { createFileKnowledgeStore, type FileKnowledgeStoreOptions } from "./extensions/knowledge/fileKnowledgeStore.js";
|
|
20
|
+
export { createKnowledgeServer, type KnowledgeToolsOptions } from "./extensions/knowledge/tools.js";
|
|
17
21
|
export { resolveLanguage, detectLanguage, SUPPORTED_LANGUAGES, type Language, type LanguageSources, type ResolvedLanguage, } from "./core/language.js";
|
|
18
|
-
export { setLanguage, getLanguage, messagesFor, type Messages } from "./core/messages/index.js";
|
|
22
|
+
export { setLanguage, switchLanguage, getLanguage, messagesFor, type Messages } from "./core/messages/index.js";
|
|
19
23
|
export { createRunStore, createRunFolder, listRuns, readConversation, type RunFolder, type RunSummary, type ConversationMessage, } from "./core/runs.js";
|
|
20
24
|
export type { Options, McpServerConfig, AgentDefinition } from "@anthropic-ai/claude-agent-sdk";
|
|
21
25
|
export { tool, createSdkMcpServer } from "@anthropic-ai/claude-agent-sdk";
|
|
@@ -31,9 +35,10 @@ export { setInteractionPort, getInteractionPort, type InteractionPort, type Appr
|
|
|
31
35
|
export { terminalInteractionPort, setSharedReadline, getSharedReadline } from "./tui/terminalInteraction.js";
|
|
32
36
|
export { createHumanApprovalServer, DEFAULT_HUMAN_APPROVAL_TEXTS, type HumanApprovalTexts } from "./core/tools/humanApproval.js";
|
|
33
37
|
export { createManualLoginServer, type ManualInterventionTexts } from "./core/tools/manualLogin.js";
|
|
34
|
-
export { createSaveToSourcesServer, type SourceToolsOptions } from "./
|
|
38
|
+
export { createSaveToSourcesServer, sourcesPromptSection, type SourceToolsOptions } from "./extensions/sources/tools.js";
|
|
35
39
|
export { runChatTui, type ChatTuiOptions } from "./tui/chatTui.js";
|
|
36
|
-
export type { SessionOpener } from "./
|
|
40
|
+
export type { SessionOpener } from "./chat/runs.js";
|
|
41
|
+
export { createChatController, type ChatController, type ChatSettings, type ChatState, type ChatEvent, type ChatPanel, type ChatQuestion, type TranscriptEntry, type TranscriptCall, type NoticeTone, } from "./chat/chatController.js";
|
|
37
42
|
export { createConsoleRenderer, type ConsoleRenderer, type ConsoleRendererOptions } from "./tui/consoleRenderer.js";
|
|
38
43
|
export { runChatInk, type InkChatOptions, type HeaderInfo } from "./tui/ink/runChatInk.js";
|
|
39
44
|
export type { ToolPhrase, ToolDetail, ResultFormatter } from "./tui/ink/toolGroup.js";
|
package/dist/index.js
CHANGED
|
@@ -7,16 +7,19 @@ export { allowAnyMcpTool } from "./core/mcpPermissions.js";
|
|
|
7
7
|
export { createPromptLoader } from "./core/promptTemplate.js";
|
|
8
8
|
export { resolveClaudeAuth } from "./core/claudeAuth.js";
|
|
9
9
|
export { ensureClaudeAuth } from "./tui/claudeAuth.js";
|
|
10
|
-
export { createFriendlyToolLabel, truncate, truncatePath, } from "./core/toolLabels.js";
|
|
10
|
+
export { createFriendlyToolLabel, truncate, truncatePath, withToolLabels, withToolPhrases, } from "./core/toolLabels.js";
|
|
11
11
|
export { buildSessionOptions, createInputQueue, createDeferred, createModeControl, togglePlanMode } from "./core/session.js";
|
|
12
12
|
export { runQuery } from "./core/runner.js";
|
|
13
|
-
export { knowledgePluginRoot, knowledgePromptSection } from "./
|
|
13
|
+
export { knowledgePluginRoot, knowledgePromptSection } from "./extensions/knowledge/prompt.js";
|
|
14
|
+
export { readExtensionManifest, } from "./core/extensions.js";
|
|
15
|
+
export { addExtension, removeExtension, setExtensionEnabled, listInstalled, readExternalManifest, } from "./core/externalExtensions.js";
|
|
16
|
+
export { runExtensionCommand } from "./tui/extensionCommand.js";
|
|
14
17
|
export { agentKitVersion } from "./core/version.js";
|
|
15
|
-
export { BUILT_IN_PAGE_TYPES, isSlug, parseId, linkedIds, } from "./
|
|
16
|
-
export { createFileKnowledgeStore } from "./
|
|
17
|
-
export { createKnowledgeServer } from "./
|
|
18
|
+
export { BUILT_IN_PAGE_TYPES, isSlug, parseId, linkedIds, } from "./extensions/knowledge/knowledgeStore.js";
|
|
19
|
+
export { createFileKnowledgeStore } from "./extensions/knowledge/fileKnowledgeStore.js";
|
|
20
|
+
export { createKnowledgeServer } from "./extensions/knowledge/tools.js";
|
|
18
21
|
export { resolveLanguage, detectLanguage, SUPPORTED_LANGUAGES, } from "./core/language.js";
|
|
19
|
-
export { setLanguage, getLanguage, messagesFor } from "./core/messages/index.js";
|
|
22
|
+
export { setLanguage, switchLanguage, getLanguage, messagesFor } from "./core/messages/index.js";
|
|
20
23
|
export { createRunStore, createRunFolder, listRuns, readConversation, } from "./core/runs.js";
|
|
21
24
|
// And the two functions an agent needs for tools of its own (an in-process MCP server,
|
|
22
25
|
// returned from AgentSpec.buildMcpServers()), so it doesn't need the SDK for those either.
|
|
@@ -36,8 +39,9 @@ export { terminalInteractionPort, setSharedReadline, getSharedReadline } from ".
|
|
|
36
39
|
setInteractionPort(terminalInteractionPort);
|
|
37
40
|
export { createHumanApprovalServer, DEFAULT_HUMAN_APPROVAL_TEXTS } from "./core/tools/humanApproval.js";
|
|
38
41
|
export { createManualLoginServer } from "./core/tools/manualLogin.js";
|
|
39
|
-
export { createSaveToSourcesServer } from "./
|
|
42
|
+
export { createSaveToSourcesServer, sourcesPromptSection } from "./extensions/sources/tools.js";
|
|
40
43
|
export { runChatTui } from "./tui/chatTui.js";
|
|
44
|
+
export { createChatController, } from "./chat/chatController.js";
|
|
41
45
|
export { createConsoleRenderer } from "./tui/consoleRenderer.js";
|
|
42
46
|
export { runChatInk } from "./tui/ink/runChatInk.js";
|
|
43
47
|
export { createProgressView } from "./tui/ink/progressView.js";
|
package/dist/tui/chatTui.d.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import type { Options } from "@anthropic-ai/claude-agent-sdk";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import type { ModeControl } from "../core/session.js";
|
|
3
|
+
import type { AgentEvent } from "../core/runner.js";
|
|
4
4
|
import { type Theme } from "./theme.js";
|
|
5
|
-
import {
|
|
5
|
+
import type { ToolLabels } from "../core/toolLabels.js";
|
|
6
|
+
import { type SessionOpener } from "../chat/runs.js";
|
|
7
|
+
export { capHistory, loadHistory, saveHistory, slashCommandToken } from "../chat/history.js";
|
|
6
8
|
/** Options of the chats (`runChatTui()`, and `runChatInk()` through `InkChatOptions`). */
|
|
7
9
|
export interface ChatTuiOptions {
|
|
8
10
|
/**
|
|
@@ -80,6 +82,12 @@ export interface ChatTuiOptions {
|
|
|
80
82
|
* returns is used instead.
|
|
81
83
|
*/
|
|
82
84
|
modeControl?: ModeControl;
|
|
85
|
+
/**
|
|
86
|
+
* How the chat shows the extensions' tools (`buildSessionOptions()` returns them as
|
|
87
|
+
* `toolLabels`), before `formatAction`. With a session opener, the ones it returns are used
|
|
88
|
+
* instead.
|
|
89
|
+
*/
|
|
90
|
+
toolLabels?: ToolLabels;
|
|
83
91
|
/**
|
|
84
92
|
* Colors for the kit's roles (see `Theme`), on top of the kit's defaults: only the roles
|
|
85
93
|
* given change, e.g. `{ toolResult: "yellow", selection: "#00ff00" }`. One theme per
|
|
@@ -87,22 +95,6 @@ export interface ChatTuiOptions {
|
|
|
87
95
|
*/
|
|
88
96
|
theme?: Partial<Theme>;
|
|
89
97
|
}
|
|
90
|
-
interface HistoryEntry {
|
|
91
|
-
text: string;
|
|
92
|
-
timestamp: string;
|
|
93
|
-
}
|
|
94
|
-
/** Keeps only the most recent `limit` entries (oldest-first order in, oldest-first out). */
|
|
95
|
-
export declare function capHistory(entries: readonly HistoryEntry[], limit: number): HistoryEntry[];
|
|
96
|
-
/** Oldest-first. Missing file reads as empty — nothing to load yet is the normal case. */
|
|
97
|
-
export declare function loadHistory(filePath: string): Promise<HistoryEntry[]>;
|
|
98
|
-
/** Overwrites the whole file (not appended) so a prior prune (oldest entries dropped) sticks. */
|
|
99
|
-
export declare function saveHistory(filePath: string, entries: readonly HistoryEntry[]): Promise<void>;
|
|
100
|
-
/**
|
|
101
|
-
* The command name a typed line would invoke (e.g. "chiste" for both "/chiste" and
|
|
102
|
-
* "/chiste con salsa"), or `null` for a line that isn't a slash command at all (doesn't
|
|
103
|
-
* start with "/") or is just a bare "/" with nothing after it.
|
|
104
|
-
*/
|
|
105
|
-
export declare function slashCommandToken(line: string): string | null;
|
|
106
98
|
/**
|
|
107
99
|
* Reads `events` one turn at a time — up to and including a `turn-end` — via manual
|
|
108
100
|
* `.next()` calls, calling `onEvent` for each. Deliberately never uses
|
|
@@ -139,4 +131,3 @@ export declare function drainTurn(events: AsyncIterator<AgentEvent>, onEvent: (e
|
|
|
139
131
|
* interface itself before returning.
|
|
140
132
|
*/
|
|
141
133
|
export declare function runChatTui(options: Options | SessionOpener, tuiOptions?: ChatTuiOptions): Promise<void>;
|
|
142
|
-
export {};
|