@falkenslab/agent-kit 0.16.0 → 0.16.1
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/README.md +3 -1
- package/assets/knowledge-plugin/skills/knowledge-ingest/SKILL.md +1 -1
- package/dist/core/fileKnowledgeStore.js +21 -4
- package/dist/core/knowledge.js +1 -1
- package/dist/core/knowledgeStore.d.ts +5 -0
- package/dist/core/messages/de.js +3 -3
- package/dist/core/messages/en.d.ts +1 -1
- package/dist/core/messages/en.js +3 -3
- package/dist/core/messages/es.js +3 -3
- package/dist/core/messages/fr.js +3 -3
- package/dist/core/session.js +5 -0
- package/dist/core/tools/knowledgeTools.js +8 -1
- package/dist/core/tools/saveToSources.js +27 -13
- package/dist/core/version.d.ts +5 -0
- package/dist/core/version.js +12 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -34,7 +34,9 @@ A foundation for building AI agents on top of the [Claude Agent SDK](https://www
|
|
|
34
34
|
|
|
35
35
|
[Captain Whiskers](./examples/captain-whiskers) is a toy agent to see the kit in action: a retired pirate cat who tells jokes in the terminal, in your language. It starts full screen with its logo and has a small crew of subagents: one looks for new jokes on the web, another rates them before they're told, and a third tells the time with a tool of the captain's own. It lets you try the different levels of oversight and is the best starting point for an agent of your own. Its README explains how to run it.
|
|
36
36
|
|
|
37
|
-

|
|
38
|
+
|
|
39
|
+
See [everything he can do](https://falkenslab.github.io/agent-kit/docs/next/examples/captain-whiskers#what-he-can-do), with screenshots.
|
|
38
40
|
|
|
39
41
|
### teacher-agent
|
|
40
42
|
|
|
@@ -13,7 +13,7 @@ A source is ingested once, and from then on the knowledge base holds what it tau
|
|
|
13
13
|
|
|
14
14
|
## 2. Read it for real
|
|
15
15
|
|
|
16
|
-
Read the whole source, not its first page (a long PDF in parts: `list_sources` gives its pages). Its path is
|
|
16
|
+
Read the whole source, not its first page (a long PDF in parts: `list_sources` gives its pages). Its path is the sources folder's (named in the system prompt) plus the path `list_sources` gives, from the project folder, not from this skill's folder. `Read` reads PDFs and images; `extract_text` reads DOCX, PPTX (with its speaker notes) and XLSX. If it exists only outside the sources folder, keep the original first: `download_to_sources` for a URL (a web page is kept as markdown too: read that one), `save_to_sources` for a file in the run's folder, `request_file` to ask the person for one.
|
|
17
17
|
|
|
18
18
|
## 3. Write the summary page
|
|
19
19
|
|
|
@@ -163,7 +163,12 @@ export function createFileKnowledgeStore(knowledgeDir, options = {}) {
|
|
|
163
163
|
async function storedField(key, value, file) {
|
|
164
164
|
if (key !== "file" || !options.sourcesDir || /^[a-z]+:\/\//i.test(value))
|
|
165
165
|
return value;
|
|
166
|
-
|
|
166
|
+
// Relative to the sources folder; a leading "sources/" or the folder's own name is dropped.
|
|
167
|
+
let relative = value.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
168
|
+
for (const prefix of ["sources/", `${path.basename(options.sourcesDir)}/`])
|
|
169
|
+
if (relative.startsWith(prefix))
|
|
170
|
+
relative = relative.slice(prefix.length);
|
|
171
|
+
const original = path.resolve(options.sourcesDir, relative);
|
|
167
172
|
if (!(await stat(original).catch(() => null))?.isFile())
|
|
168
173
|
throw new Error(`"${value}" isn't an original in sources/ (list_sources lists them).`);
|
|
169
174
|
return posix(path.relative(path.dirname(file), original));
|
|
@@ -321,6 +326,12 @@ export function createFileKnowledgeStore(knowledgeDir, options = {}) {
|
|
|
321
326
|
.map(({ id, title, snippet }) => ({ id, title, snippet }));
|
|
322
327
|
},
|
|
323
328
|
index: indexText,
|
|
329
|
+
async recentLog(limit = 10) {
|
|
330
|
+
const text = await readFile(path.join(knowledgeDir, "log.md"), "utf8").catch(() => "");
|
|
331
|
+
// Entries start with "## [date] operation | what"; the newest are at the bottom.
|
|
332
|
+
const entries = text.split(/\r?\n(?=## )/).filter((entry) => entry.startsWith("## ")).map((entry) => entry.trim());
|
|
333
|
+
return entries.slice(-limit).reverse();
|
|
334
|
+
},
|
|
324
335
|
async log(operation, what, pages = []) {
|
|
325
336
|
await mkdir(knowledgeDir, { recursive: true });
|
|
326
337
|
const file = path.join(knowledgeDir, "log.md");
|
|
@@ -331,6 +342,7 @@ export function createFileKnowledgeStore(knowledgeDir, options = {}) {
|
|
|
331
342
|
async check() {
|
|
332
343
|
await writeIndex(); // the one mechanical fix there is: an index.md out of date
|
|
333
344
|
const { pages, byFile } = await pagesByFile();
|
|
345
|
+
const reservedFiles = new Set([...RESERVED].map((name) => path.normalize(path.join(knowledgeDir, `${name}.md`))));
|
|
334
346
|
const known = new Map(pages.map((p) => [p.id, p]));
|
|
335
347
|
const inbound = new Map();
|
|
336
348
|
const report = { brokenLinks: [], orphans: [], linksToRetired: [], missingOriginals: [] };
|
|
@@ -338,11 +350,16 @@ export function createFileKnowledgeStore(knowledgeDir, options = {}) {
|
|
|
338
350
|
if (statusOf(page) === "retired")
|
|
339
351
|
continue;
|
|
340
352
|
const body = fromFile(page.body, page.file, byFile);
|
|
341
|
-
// Relative links to .md files that resolve to nothing are broken too.
|
|
353
|
+
// Relative links to .md files that resolve to nothing are broken too. The overview, the
|
|
354
|
+
// index and the log aren't pages but are files: a link to one that exists is fine (a
|
|
355
|
+
// knowledge base written with the file tools links the overview, #22).
|
|
342
356
|
for (const m of page.body.matchAll(/\]\(([^)\s:]+\.md)(?:#[^)]*)?\)/g)) {
|
|
343
357
|
const target = path.normalize(path.resolve(path.dirname(page.file), decodeURI(m[1])));
|
|
344
|
-
if (
|
|
345
|
-
|
|
358
|
+
if (byFile.has(target) || !target.startsWith(path.normalize(knowledgeDir)))
|
|
359
|
+
continue;
|
|
360
|
+
if (reservedFiles.has(target) && (await stat(target).catch(() => null)))
|
|
361
|
+
continue;
|
|
362
|
+
report.brokenLinks.push({ page: page.id, target: posix(path.relative(knowledgeDir, target)) });
|
|
346
363
|
}
|
|
347
364
|
for (const id of new Set(linkedIds(body))) {
|
|
348
365
|
const target = known.get(id);
|
package/dist/core/knowledge.js
CHANGED
|
@@ -30,7 +30,7 @@ function toolsSection(originals, pageTypes) {
|
|
|
30
30
|
Your memory across sessions is an interlinked knowledge base of pages that you write and maintain yourself — a wiki, not a pile of notes. A future session only knows what is written there, so anything worth remembering must end up in a page, not just in this turn's reply. You reach it only through the \`knowledge_*\` tools. Write page content in the language you are using with the human.
|
|
31
31
|
|
|
32
32
|
### Layers
|
|
33
|
-
${originals ? originalsLine(originals) : ""}- **The knowledge base**: pages identified by type and slug (\`concept/spring-tides\`), plus the \`overview\`, a living synthesis of the whole. Page types: ${pageTypes.map((type) => `\`${type.type}\` (${type.description})`).join("; ")}.
|
|
33
|
+
${originals ? originalsLine(originals) : ""}- **The knowledge base**: pages identified by type and slug (\`concept/spring-tides\`), plus the \`overview\`, a living synthesis of the whole, and the \`log\` of what was done to it (\`knowledge_read\` reads both). Page types: ${pageTypes.map((type) => `\`${type.type}\` (${type.description})`).join("; ")}.
|
|
34
34
|
- **The schema**: these rules plus the \`knowledge-ingest\`, \`knowledge-query\` and \`knowledge-lint\` skills. \`knowledge_create\` without content gives a type's template.
|
|
35
35
|
|
|
36
36
|
### Working rules
|
|
@@ -114,6 +114,11 @@ export interface KnowledgeStore {
|
|
|
114
114
|
index(): Promise<string>;
|
|
115
115
|
/** Adds an entry to the operation log, dated today. */
|
|
116
116
|
log(operation: string, what: string, pages?: readonly string[]): Promise<void>;
|
|
117
|
+
/**
|
|
118
|
+
* The latest entries of the operation log, newest first, as markdown (e.g. "## [2026-10-04]
|
|
119
|
+
* ingest | …"). Optional: a store without it can't show the log to the agent.
|
|
120
|
+
*/
|
|
121
|
+
recentLog?(limit?: number): Promise<string[]>;
|
|
117
122
|
/** The mechanical problems. */
|
|
118
123
|
check(): Promise<CheckReport>;
|
|
119
124
|
/** The overview: a living synthesis of the whole knowledge base ("" if none yet). */
|
package/dist/core/messages/de.js
CHANGED
|
@@ -70,12 +70,12 @@ export const de = {
|
|
|
70
70
|
keepPlanning: "Weiter planen",
|
|
71
71
|
cancelPlan: "Abbrechen",
|
|
72
72
|
planCommentQuestion: "Was soll sich ändern? (Enter zum Überspringen): ",
|
|
73
|
-
retireLines: (source, why, reason, replacedBy) => [
|
|
73
|
+
retireLines: (source, why, reason, replacedBy, folder) => [
|
|
74
74
|
`Quelle: ${source}`,
|
|
75
75
|
why === "wrong" ? "Warum: sie ist falsch; ihre Zusammenfassung wird zurückgezogen" : "Warum: sie wird ersetzt; ihre Zusammenfassung wird als ersetzt markiert",
|
|
76
76
|
`Grund: ${reason}`,
|
|
77
77
|
...(replacedBy ? [`Ersetzt durch: ${replacedBy}`] : []),
|
|
78
|
-
|
|
78
|
+
`Sie wird nach ${folder}.agent-kit/retired/ verschoben, nicht gelöscht.`,
|
|
79
79
|
],
|
|
80
80
|
toolPhrases: {
|
|
81
81
|
Read: ["hat {n} Datei gelesen", "hat {n} Dateien gelesen"],
|
|
@@ -120,7 +120,7 @@ export const de = {
|
|
|
120
120
|
labels: {
|
|
121
121
|
askingApproval: (summary) => `Bittet um menschliche Genehmigung: ${summary}`,
|
|
122
122
|
waitingManual: "Wartet auf einen manuellen Eingriff",
|
|
123
|
-
savingToSources: (destination) => `Speichert
|
|
123
|
+
savingToSources: (destination) => `Speichert ${destination} in den Quellen`,
|
|
124
124
|
checkingTime: "Datum und Uhrzeit abfragen",
|
|
125
125
|
calculatingDates: "Daten berechnen",
|
|
126
126
|
reading: (file) => `Liest ${file}`,
|
|
@@ -67,7 +67,7 @@ export interface Messages {
|
|
|
67
67
|
keepPlanning: string;
|
|
68
68
|
cancelPlan: string;
|
|
69
69
|
planCommentQuestion: string;
|
|
70
|
-
retireLines(source: string, why: "wrong" | "replaced", reason: string, replacedBy
|
|
70
|
+
retireLines(source: string, why: "wrong" | "replaced", reason: string, replacedBy: string | undefined, folder: string): string[];
|
|
71
71
|
toolPhrases: Record<string, ToolPhrase>;
|
|
72
72
|
otherTools: ToolPhrase;
|
|
73
73
|
noOutput: string;
|
package/dist/core/messages/en.js
CHANGED
|
@@ -69,12 +69,12 @@ export const en = {
|
|
|
69
69
|
keepPlanning: "Keep planning",
|
|
70
70
|
cancelPlan: "Cancel",
|
|
71
71
|
planCommentQuestion: "What should change? (Enter to skip): ",
|
|
72
|
-
retireLines: (source, why, reason, replacedBy) => [
|
|
72
|
+
retireLines: (source, why, reason, replacedBy, folder) => [
|
|
73
73
|
`Source: ${source}`,
|
|
74
74
|
why === "wrong" ? "Why: it's wrong; its summary will be retired" : "Why: it's replaced; its summary will be marked superseded",
|
|
75
75
|
`Reason: ${reason}`,
|
|
76
76
|
...(replacedBy ? [`Replaced by: ${replacedBy}`] : []),
|
|
77
|
-
|
|
77
|
+
`It's moved to ${folder}.agent-kit/retired/, not deleted.`,
|
|
78
78
|
],
|
|
79
79
|
toolPhrases: {
|
|
80
80
|
Read: ["read {n} file", "read {n} files"],
|
|
@@ -119,7 +119,7 @@ export const en = {
|
|
|
119
119
|
labels: {
|
|
120
120
|
askingApproval: (summary) => `Asking for human approval: ${summary}`,
|
|
121
121
|
waitingManual: "Waiting for a human to intervene manually",
|
|
122
|
-
savingToSources: (destination) => `Saving
|
|
122
|
+
savingToSources: (destination) => `Saving ${destination} to the sources`,
|
|
123
123
|
checkingTime: "Checking the date and time",
|
|
124
124
|
calculatingDates: "Calculating dates",
|
|
125
125
|
reading: (file) => `Reading ${file}`,
|
package/dist/core/messages/es.js
CHANGED
|
@@ -70,12 +70,12 @@ export const es = {
|
|
|
70
70
|
keepPlanning: "Seguir planificando",
|
|
71
71
|
cancelPlan: "Cancelar",
|
|
72
72
|
planCommentQuestion: "¿Qué debería cambiar? (Enter para omitir): ",
|
|
73
|
-
retireLines: (source, why, reason, replacedBy) => [
|
|
73
|
+
retireLines: (source, why, reason, replacedBy, folder) => [
|
|
74
74
|
`Fuente: ${source}`,
|
|
75
75
|
why === "wrong" ? "Motivo: es errónea; su resumen se retirará" : "Motivo: se sustituye; su resumen se marcará como sustituido",
|
|
76
76
|
`Razón: ${reason}`,
|
|
77
77
|
...(replacedBy ? [`Sustituida por: ${replacedBy}`] : []),
|
|
78
|
-
|
|
78
|
+
`Se mueve a ${folder}.agent-kit/retired/, no se borra.`,
|
|
79
79
|
],
|
|
80
80
|
toolPhrases: {
|
|
81
81
|
Read: ["leyó {n} fichero", "leyó {n} ficheros"],
|
|
@@ -120,7 +120,7 @@ export const es = {
|
|
|
120
120
|
labels: {
|
|
121
121
|
askingApproval: (summary) => `Pidiendo aprobación humana: ${summary}`,
|
|
122
122
|
waitingManual: "Esperando a que una persona intervenga a mano",
|
|
123
|
-
savingToSources: (destination) => `Guardando
|
|
123
|
+
savingToSources: (destination) => `Guardando ${destination} en las fuentes`,
|
|
124
124
|
checkingTime: "Consultando la fecha y la hora",
|
|
125
125
|
calculatingDates: "Calculando fechas",
|
|
126
126
|
reading: (file) => `Leyendo ${file}`,
|
package/dist/core/messages/fr.js
CHANGED
|
@@ -70,12 +70,12 @@ export const fr = {
|
|
|
70
70
|
keepPlanning: "Continuer à planifier",
|
|
71
71
|
cancelPlan: "Annuler",
|
|
72
72
|
planCommentQuestion: "Que faut-il changer ? (Entrée pour passer) : ",
|
|
73
|
-
retireLines: (source, why, reason, replacedBy) => [
|
|
73
|
+
retireLines: (source, why, reason, replacedBy, folder) => [
|
|
74
74
|
`Source : ${source}`,
|
|
75
75
|
why === "wrong" ? "Motif : elle est erronée ; son résumé sera retiré" : "Motif : elle est remplacée ; son résumé sera marqué comme remplacé",
|
|
76
76
|
`Raison : ${reason}`,
|
|
77
77
|
...(replacedBy ? [`Remplacée par : ${replacedBy}`] : []),
|
|
78
|
-
|
|
78
|
+
`Elle est déplacée dans ${folder}.agent-kit/retired/, pas supprimée.`,
|
|
79
79
|
],
|
|
80
80
|
toolPhrases: {
|
|
81
81
|
Read: ["a lu {n} fichier", "a lu {n} fichiers"],
|
|
@@ -120,7 +120,7 @@ export const fr = {
|
|
|
120
120
|
labels: {
|
|
121
121
|
askingApproval: (summary) => `Demande d'approbation humaine : ${summary}`,
|
|
122
122
|
waitingManual: "En attente d'une intervention manuelle",
|
|
123
|
-
savingToSources: (destination) => `Enregistrement
|
|
123
|
+
savingToSources: (destination) => `Enregistrement de ${destination} dans les sources`,
|
|
124
124
|
checkingTime: "Consultation de la date et de l'heure",
|
|
125
125
|
calculatingDates: "Calcul de dates",
|
|
126
126
|
reading: (file) => `Lecture de ${file}`,
|
package/dist/core/session.js
CHANGED
|
@@ -148,6 +148,11 @@ export async function buildSessionOptions(config, runDir, spec, options = {}) {
|
|
|
148
148
|
// name isn't known ahead of time.
|
|
149
149
|
canUseTool: allowAnyMcpTool,
|
|
150
150
|
disallowedTools: spec.disallowedTools ?? [],
|
|
151
|
+
// TodoWrite (todos.ts) only exists with CLAUDE_CODE_ENABLE_TASKS=0: by default the CLI
|
|
152
|
+
// offers its TaskCreate/TaskUpdate/TaskList/TaskGet tools instead, and drops TodoWrite from
|
|
153
|
+
// the session even when `tools` names it (confirmed empirically). `env` replaces the
|
|
154
|
+
// subprocess environment, so the rest is passed on as it is.
|
|
155
|
+
env: { ...process.env, CLAUDE_CODE_ENABLE_TASKS: "0" },
|
|
151
156
|
// Real text streaming instead of silently waiting for the turn's complete message —
|
|
152
157
|
// see the caller's message loop.
|
|
153
158
|
includePartialMessages: true,
|
|
@@ -22,6 +22,7 @@ async function attempt(work) {
|
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
24
|
const OVERVIEW = "overview";
|
|
25
|
+
const LOG = "log";
|
|
25
26
|
/** The `knowledge` MCP server over `store`. */
|
|
26
27
|
export function createKnowledgeServer(store, options) {
|
|
27
28
|
const typeNames = store.types().map((t) => t.type);
|
|
@@ -49,9 +50,15 @@ export function createKnowledgeServer(store, options) {
|
|
|
49
50
|
const hits = await store.search(args.query, args.limit ?? 10);
|
|
50
51
|
return hits.length ? hits.map((hit) => `- ${hit.id} — ${hit.title}: ${hit.snippet}`).join("\n") : "No page matches.";
|
|
51
52
|
}), { annotations: { readOnlyHint: true } });
|
|
52
|
-
const read = tool("knowledge_read", 'A page by its id ("concept/bowline")
|
|
53
|
+
const read = tool("knowledge_read", 'A page by its id ("concept/bowline"): its fields, its content (links to other pages as ids) and the pages that link to it. Or "overview" (the living synthesis of the whole knowledge base), or "log" (the latest entries of the operation log, newest first: what was done to the knowledge base, and when).', { page: z.string().describe('The page id, "overview" or "log"') }, async (args) => attempt(async () => {
|
|
53
54
|
if (args.page === OVERVIEW)
|
|
54
55
|
return (await store.overview()) || "There's no overview yet: write it with knowledge_rewrite (page \"overview\").";
|
|
56
|
+
if (args.page === LOG) {
|
|
57
|
+
if (!store.recentLog)
|
|
58
|
+
return "This knowledge base's store can't show its log.";
|
|
59
|
+
const entries = await store.recentLog(10);
|
|
60
|
+
return entries.length ? entries.join("\n\n") : "The log is empty: nothing has been logged yet.";
|
|
61
|
+
}
|
|
55
62
|
const page = await store.read(args.page);
|
|
56
63
|
if (!page)
|
|
57
64
|
throw new Error(`There's no page "${args.page}". knowledge_search or knowledge_index finds the right one.`);
|
|
@@ -46,8 +46,6 @@ const DEFAULT_DESCRIPTION = "Copy a file from this run's own folder (e.g. someth
|
|
|
46
46
|
"downloading a document you're about to study in depth, so later sessions have it too.";
|
|
47
47
|
/** The most `download_to_sources` and `request_file` copy in. */
|
|
48
48
|
export const MAX_SOURCE_BYTES = 50 * 1024 * 1024;
|
|
49
|
-
const ok = (text) => ({ content: [{ type: "text", text }] });
|
|
50
|
-
const fail = (text) => ({ content: [{ type: "text", text }], isError: true });
|
|
51
49
|
const message = (error) => (error instanceof Error ? error.message : String(error));
|
|
52
50
|
function added(result, extra = "") {
|
|
53
51
|
return result.duplicateOf
|
|
@@ -116,6 +114,20 @@ export async function downloadToFile(url, file, maxBytes) {
|
|
|
116
114
|
export function createSaveToSourcesServer(runDir, sourcesDir, description = DEFAULT_DESCRIPTION, options = {}) {
|
|
117
115
|
const projectDir = options.projectDir ?? path.dirname(sourcesDir);
|
|
118
116
|
const maxBytes = options.maxBytes ?? MAX_SOURCE_BYTES;
|
|
117
|
+
// The texts say "sources/"; the model reads the folder's real name (e.g. "treasure/"),
|
|
118
|
+
// since it reads and searches the originals by that path. Paths it gives may start with
|
|
119
|
+
// either.
|
|
120
|
+
const folder = `${path.relative(projectDir, sourcesDir).split(path.sep).join("/") || "."}/`;
|
|
121
|
+
const named = (text) => text.replaceAll("sources/", folder);
|
|
122
|
+
const inFolder = (relative) => {
|
|
123
|
+
const clean = relative.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
124
|
+
for (const prefix of [folder, "sources/"])
|
|
125
|
+
if (clean.startsWith(prefix))
|
|
126
|
+
return clean.slice(prefix.length);
|
|
127
|
+
return clean;
|
|
128
|
+
};
|
|
129
|
+
const ok = (text) => ({ content: [{ type: "text", text: named(text) }] });
|
|
130
|
+
const fail = (text) => ({ content: [{ type: "text", text: named(text) }], isError: true });
|
|
119
131
|
const replaces = z.string().optional().describe("For a new version: the original it replaces, relative to sources/ (kept, never overwritten)");
|
|
120
132
|
const saveToSources = tool("save_to_sources", description, {
|
|
121
133
|
source: z.string().describe("Path to the file, relative to this run's own folder - not a URL"),
|
|
@@ -131,7 +143,7 @@ export function createSaveToSourcesServer(runDir, sourcesDir, description = DEFA
|
|
|
131
143
|
}
|
|
132
144
|
try {
|
|
133
145
|
const relative = path.relative(runDir, resolvedSource).split(path.sep).join("/");
|
|
134
|
-
return ok(added(await addSource(sourcesDir, resolvedSource, args.destination, { kind: "run", from: relative }, { replaces: args.replaces })));
|
|
146
|
+
return ok(added(await addSource(sourcesDir, resolvedSource, inFolder(args.destination), { kind: "run", from: relative }, { replaces: args.replaces && inFolder(args.replaces) })));
|
|
135
147
|
}
|
|
136
148
|
catch (error) {
|
|
137
149
|
return fail(message(error));
|
|
@@ -150,7 +162,7 @@ export function createSaveToSourcesServer(runDir, sourcesDir, description = DEFA
|
|
|
150
162
|
from: z.number().int().positive().optional().describe("First slide (PPTX)"),
|
|
151
163
|
to: z.number().int().positive().optional().describe("Last slide (PPTX)"),
|
|
152
164
|
}, async (args) => {
|
|
153
|
-
const file = resolveWithin(sourcesDir, args.source);
|
|
165
|
+
const file = resolveWithin(sourcesDir, inFolder(args.source));
|
|
154
166
|
if (!file || !(await stat(file).catch(() => null))?.isFile())
|
|
155
167
|
return fail(`"${args.source}" isn't an original in sources/ (list_sources lists them).`);
|
|
156
168
|
if (!EXTRACTABLE.has(path.extname(file).slice(1).toLowerCase()))
|
|
@@ -171,7 +183,7 @@ export function createSaveToSourcesServer(runDir, sourcesDir, description = DEFA
|
|
|
171
183
|
try {
|
|
172
184
|
const type = await downloadToFile(args.url, temp, maxBytes);
|
|
173
185
|
const name = decodeURIComponent(new URL(args.url).pathname.split("/").filter(Boolean).pop() ?? "download");
|
|
174
|
-
const result = await addSource(sourcesDir, temp, args.destination, { kind: "url", from: args.url }, { replaces: args.replaces, name });
|
|
186
|
+
const result = await addSource(sourcesDir, temp, inFolder(args.destination), { kind: "url", from: args.url }, { replaces: args.replaces && inFolder(args.replaces), name });
|
|
175
187
|
let extra = "";
|
|
176
188
|
if (!result.duplicateOf && /html/i.test(type)) {
|
|
177
189
|
const html = await readFile(temp, "utf8");
|
|
@@ -217,7 +229,7 @@ export function createSaveToSourcesServer(runDir, sourcesDir, description = DEFA
|
|
|
217
229
|
if (info.size > maxBytes)
|
|
218
230
|
return fail(`"${answer}" is ${info.size} bytes, over the ${maxBytes}-byte limit.`);
|
|
219
231
|
try {
|
|
220
|
-
return ok(added(await addSource(sourcesDir, file, args.destination
|
|
232
|
+
return ok(added(await addSource(sourcesDir, file, args.destination ? inFolder(args.destination) : path.basename(file), { kind: "person", from: path.basename(file) }, { replaces: args.replaces && inFolder(args.replaces) })));
|
|
221
233
|
}
|
|
222
234
|
catch (error) {
|
|
223
235
|
return fail(message(error));
|
|
@@ -229,11 +241,11 @@ export function createSaveToSourcesServer(runDir, sourcesDir, description = DEFA
|
|
|
229
241
|
reason: z.string().describe("Why, in one sentence, in the person's language"),
|
|
230
242
|
replacedBy: z.string().optional().describe('For "replaced": the original that replaces it, relative to sources/'),
|
|
231
243
|
}, async (args) => {
|
|
232
|
-
const answer = await askForDecision(runDir, { title: t().retireTitle, lines: t().retireLines(args.source, args.why, args.reason, args.replacedBy) });
|
|
244
|
+
const answer = await askForDecision(runDir, { title: t().retireTitle, lines: t().retireLines(inFolder(args.source), args.why, args.reason, args.replacedBy && inFolder(args.replacedBy), folder) });
|
|
233
245
|
if (answer !== "" && answer !== "y" && answer !== "yes")
|
|
234
246
|
return ok(answer === "q" ? "The person stopped: don't retire it, and stop what you were doing." : "The person said no: the original stays.");
|
|
235
247
|
try {
|
|
236
|
-
const result = await retireSource(sourcesDir, options.knowledgeDir, projectDir, args.source, args.why, args.reason, args.replacedBy);
|
|
248
|
+
const result = await retireSource(sourcesDir, options.knowledgeDir, projectDir, inFolder(args.source), args.why, args.reason, args.replacedBy && inFolder(args.replacedBy));
|
|
237
249
|
const pages = result.summaries.map((s) => `${s.page} marked ${s.marked}${s.supersededBy ? ` (see ${s.supersededBy})` : ""}`);
|
|
238
250
|
return ok([
|
|
239
251
|
`Retired: moved to sources/${result.movedTo}.`,
|
|
@@ -245,9 +257,11 @@ export function createSaveToSourcesServer(runDir, sourcesDir, description = DEFA
|
|
|
245
257
|
return fail(message(error));
|
|
246
258
|
}
|
|
247
259
|
});
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
260
|
+
// The descriptions (the tool's and its parameters') with the folder's real name too.
|
|
261
|
+
const tools = [saveToSources, listSourcesTool, extractTextTool, downloadToSources, ...(options.interactive ? [requestFile, retire] : [])].map((definition) => ({
|
|
262
|
+
...definition,
|
|
263
|
+
description: named(definition.description),
|
|
264
|
+
inputSchema: Object.fromEntries(Object.entries(definition.inputSchema).map(([key, schema]) => [key, schema.description ? schema.describe(named(schema.description)) : schema])),
|
|
265
|
+
}));
|
|
266
|
+
return createSdkMcpServer({ name: "sourceFiles", version: "1.0.0", tools: tools });
|
|
253
267
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
let cached;
|
|
5
|
+
/**
|
|
6
|
+
* The version of agent-kit in use, read from its own `package.json` (next to `dist/`, or
|
|
7
|
+
* `src/` under tsx), e.g. to show it in an agent's header or log.
|
|
8
|
+
*/
|
|
9
|
+
export function agentKitVersion() {
|
|
10
|
+
cached ??= JSON.parse(readFileSync(path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "..", "package.json"), "utf8")).version;
|
|
11
|
+
return cached;
|
|
12
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -10,6 +10,7 @@ export type { Mode, BaseSessionConfig, AgentSpec, PlanModeSpec } from "./core/ag
|
|
|
10
10
|
export { buildSessionOptions, createInputQueue, createDeferred, createModeControl, togglePlanMode, type ModeControl } from "./core/session.js";
|
|
11
11
|
export { runQuery, type AgentEvent, type AgentRun, type SessionUsage, type ContextUsage } from "./core/runner.js";
|
|
12
12
|
export { knowledgePluginRoot, knowledgePromptSection } from "./core/knowledge.js";
|
|
13
|
+
export { agentKitVersion } from "./core/version.js";
|
|
13
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 "./core/knowledgeStore.js";
|
|
14
15
|
export { createFileKnowledgeStore, type FileKnowledgeStoreOptions } from "./core/fileKnowledgeStore.js";
|
|
15
16
|
export { createKnowledgeServer, type KnowledgeToolsOptions } from "./core/tools/knowledgeTools.js";
|
package/dist/index.js
CHANGED
|
@@ -11,6 +11,7 @@ export { createFriendlyToolLabel, truncate, truncatePath, } from "./core/toolLab
|
|
|
11
11
|
export { buildSessionOptions, createInputQueue, createDeferred, createModeControl, togglePlanMode } from "./core/session.js";
|
|
12
12
|
export { runQuery } from "./core/runner.js";
|
|
13
13
|
export { knowledgePluginRoot, knowledgePromptSection } from "./core/knowledge.js";
|
|
14
|
+
export { agentKitVersion } from "./core/version.js";
|
|
14
15
|
export { BUILT_IN_PAGE_TYPES, isSlug, parseId, linkedIds, } from "./core/knowledgeStore.js";
|
|
15
16
|
export { createFileKnowledgeStore } from "./core/fileKnowledgeStore.js";
|
|
16
17
|
export { createKnowledgeServer } from "./core/tools/knowledgeTools.js";
|