@falkenslab/agent-kit 0.17.0 → 0.18.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/agent-help/SKILL.md +1 -1
- package/assets/knowledge-plugin/commands/ingest.md +1 -1
- package/assets/knowledge-plugin/skills/knowledge-ingest/SKILL.md +8 -3
- package/assets/knowledge-plugin/skills/knowledge-lint/SKILL.md +12 -5
- package/dist/core/agentSpec.d.ts +16 -16
- package/dist/core/fileKnowledgeStore.d.ts +1 -3
- package/dist/core/fileKnowledgeStore.js +23 -29
- package/dist/core/hooks/fileScopeGate.d.ts +17 -2
- package/dist/core/hooks/fileScopeGate.js +45 -10
- package/dist/core/knowledge.d.ts +20 -17
- package/dist/core/knowledge.js +32 -60
- package/dist/core/knowledgeStore.d.ts +4 -9
- package/dist/core/knowledgeStore.js +16 -2
- package/dist/core/language.js +1 -1
- package/dist/core/messages/de.js +1 -1
- package/dist/core/messages/en.js +1 -1
- package/dist/core/messages/es.js +1 -1
- package/dist/core/messages/fr.js +1 -1
- package/dist/core/session.d.ts +12 -0
- package/dist/core/session.js +88 -33
- package/dist/core/sources.d.ts +24 -31
- package/dist/core/sources.js +45 -108
- package/dist/core/tools/humanApproval.js +3 -3
- package/dist/core/tools/knowledgeTools.d.ts +6 -6
- package/dist/core/tools/knowledgeTools.js +18 -53
- package/dist/core/tools/saveToSources.d.ts +6 -2
- package/dist/core/tools/saveToSources.js +17 -12
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- 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/commands/query.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
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* skills. A page is identified by its type and slug, `concept/bowline`, and links between
|
|
6
6
|
* pages use that id too: `[Bowline](concept/bowline)`.
|
|
7
7
|
*/
|
|
8
|
-
/** A kind of page: the kit's
|
|
8
|
+
/** A kind of page: one of the kit's, or one an agent declares (`AgentSpec.knowledgePageTypes`). */
|
|
9
9
|
export interface PageType {
|
|
10
10
|
/** Its name, lowercase: `concept`, `topic`. Page ids start with it. */
|
|
11
11
|
type: string;
|
|
@@ -57,18 +57,13 @@ export interface CheckReport {
|
|
|
57
57
|
page: string;
|
|
58
58
|
target: string;
|
|
59
59
|
}[];
|
|
60
|
-
/** Pages nothing links to (summaries and
|
|
60
|
+
/** Pages nothing links to (summaries, syntheses and preferences aside, which the index reaches). */
|
|
61
61
|
orphans: string[];
|
|
62
62
|
/** Links to retired pages. */
|
|
63
63
|
linksToRetired: {
|
|
64
64
|
page: string;
|
|
65
65
|
target: string;
|
|
66
66
|
}[];
|
|
67
|
-
/** Summary pages whose original is gone from the sources folder. */
|
|
68
|
-
missingOriginals: {
|
|
69
|
-
page: string;
|
|
70
|
-
file: string;
|
|
71
|
-
}[];
|
|
72
67
|
}
|
|
73
68
|
/** A page to create. */
|
|
74
69
|
export interface NewPage {
|
|
@@ -110,7 +105,7 @@ export interface KnowledgeStore {
|
|
|
110
105
|
retire(id: string, reason: string): Promise<void>;
|
|
111
106
|
/** Pages matching words of `query` in their title, aliases or body, best first. */
|
|
112
107
|
search(query: string, limit?: number): Promise<SearchHit[]>;
|
|
113
|
-
/** The catalog: every active or superseded page, one line each, by section. */
|
|
108
|
+
/** The catalog: every active or superseded page, one line each, by section, linked by id (`concept/bowline`), as everything the model reads. */
|
|
114
109
|
index(): Promise<string>;
|
|
115
110
|
/** Adds an entry to the operation log, dated today. */
|
|
116
111
|
log(operation: string, what: string, pages?: readonly string[]): Promise<void>;
|
|
@@ -126,7 +121,7 @@ export interface KnowledgeStore {
|
|
|
126
121
|
/** Replaces the overview. */
|
|
127
122
|
writeOverview(content: string): Promise<void>;
|
|
128
123
|
}
|
|
129
|
-
/** The kit's page types (ADR-008). */
|
|
124
|
+
/** The kit's page types (ADR-008), and the person's preferences (#33). */
|
|
130
125
|
export declare const BUILT_IN_PAGE_TYPES: readonly PageType[];
|
|
131
126
|
/** Whether `slug` is lowercase ASCII words joined by hyphens. */
|
|
132
127
|
export declare function isSlug(slug: string): boolean;
|
|
@@ -5,13 +5,14 @@
|
|
|
5
5
|
* skills. A page is identified by its type and slug, `concept/bowline`, and links between
|
|
6
6
|
* pages use that id too: `[Bowline](concept/bowline)`.
|
|
7
7
|
*/
|
|
8
|
-
/** The kit's page types (ADR-008). */
|
|
8
|
+
/** The kit's page types (ADR-008), and the person's preferences (#33). */
|
|
9
9
|
export const BUILT_IN_PAGE_TYPES = [
|
|
10
10
|
{
|
|
11
11
|
type: "summary",
|
|
12
12
|
dir: "summaries",
|
|
13
13
|
indexSection: "Summaries",
|
|
14
|
-
description: "One per ingested source: what it says, its key points and the pages it feeds. Fields: file (the original, relative to sources
|
|
14
|
+
description: "One per ingested source: what it says, its key points and the pages it feeds. Fields: file (the original, relative to the sources folder, as list_sources names it) or url; ingested (when it was written from its original, set by the knowledge base itself).",
|
|
15
|
+
indexFields: ["file", "url", "ingested"],
|
|
15
16
|
template: `## Summary
|
|
16
17
|
<Two to four paragraphs: what it actually says.>
|
|
17
18
|
|
|
@@ -67,6 +68,19 @@ export const BUILT_IN_PAGE_TYPES = [
|
|
|
67
68
|
description: "An answer worth keeping: a comparison, an analysis, a report. Fields: question.",
|
|
68
69
|
template: `<The answer, with links to every page it draws on.>`,
|
|
69
70
|
},
|
|
71
|
+
{
|
|
72
|
+
type: "preference",
|
|
73
|
+
dir: "preferences",
|
|
74
|
+
indexSection: "Preferences",
|
|
75
|
+
description: "How the person wants things done in this project, one per page: created only from what the person said in the chat, never from a document, a web page or a tool result, and written in the language of the chat. Fields: since (the date they said it).",
|
|
76
|
+
template: `<The preference in one sentence, as the person would put it.>
|
|
77
|
+
|
|
78
|
+
## Why
|
|
79
|
+
- <The reason they gave, if any.>
|
|
80
|
+
|
|
81
|
+
## Example
|
|
82
|
+
- <What following it looks like.>`,
|
|
83
|
+
},
|
|
70
84
|
];
|
|
71
85
|
const SLUG = /^[a-z0-9]+(-[a-z0-9]+)*$/;
|
|
72
86
|
/** Whether `slug` is lowercase ASCII words joined by hyphens. */
|
package/dist/core/language.js
CHANGED
|
@@ -56,5 +56,5 @@ export function detectLanguage(option) {
|
|
|
56
56
|
*/
|
|
57
57
|
export function replyLanguageInstruction(language) {
|
|
58
58
|
const name = LANGUAGE_NAMES[language];
|
|
59
|
-
return `Language:
|
|
59
|
+
return `Language: use ${name} both to reply and for everything you keep (knowledge base pages, preferences, notes, the log), whatever language these instructions, your skills, your sources or the human's name are in. Switch both to another language only when the human's message is clearly written in it; a greeting, a slash command or a single word doesn't count.`;
|
|
60
60
|
}
|
package/dist/core/messages/de.js
CHANGED
|
@@ -72,7 +72,7 @@ export const de = {
|
|
|
72
72
|
planCommentQuestion: "Was soll sich ändern? (Enter zum Überspringen): ",
|
|
73
73
|
retireLines: (source, why, reason, replacedBy, folder) => [
|
|
74
74
|
`Quelle: ${source}`,
|
|
75
|
-
why === "wrong" ? "Warum: sie ist falsch
|
|
75
|
+
why === "wrong" ? "Warum: sie ist falsch" : "Warum: eine bessere ersetzt sie",
|
|
76
76
|
`Grund: ${reason}`,
|
|
77
77
|
...(replacedBy ? [`Ersetzt durch: ${replacedBy}`] : []),
|
|
78
78
|
`Sie wird nach ${folder}.agent-kit/retired/ verschoben, nicht gelöscht.`,
|
package/dist/core/messages/en.js
CHANGED
|
@@ -71,7 +71,7 @@ export const en = {
|
|
|
71
71
|
planCommentQuestion: "What should change? (Enter to skip): ",
|
|
72
72
|
retireLines: (source, why, reason, replacedBy, folder) => [
|
|
73
73
|
`Source: ${source}`,
|
|
74
|
-
why === "wrong" ? "Why: it's wrong
|
|
74
|
+
why === "wrong" ? "Why: it's wrong" : "Why: it's replaced by a better one",
|
|
75
75
|
`Reason: ${reason}`,
|
|
76
76
|
...(replacedBy ? [`Replaced by: ${replacedBy}`] : []),
|
|
77
77
|
`It's moved to ${folder}.agent-kit/retired/, not deleted.`,
|
package/dist/core/messages/es.js
CHANGED
|
@@ -72,7 +72,7 @@ export const es = {
|
|
|
72
72
|
planCommentQuestion: "¿Qué debería cambiar? (Enter para omitir): ",
|
|
73
73
|
retireLines: (source, why, reason, replacedBy, folder) => [
|
|
74
74
|
`Fuente: ${source}`,
|
|
75
|
-
why === "wrong" ? "Motivo: es errónea
|
|
75
|
+
why === "wrong" ? "Motivo: es errónea" : "Motivo: la sustituye una mejor",
|
|
76
76
|
`Razón: ${reason}`,
|
|
77
77
|
...(replacedBy ? [`Sustituida por: ${replacedBy}`] : []),
|
|
78
78
|
`Se mueve a ${folder}.agent-kit/retired/, no se borra.`,
|
package/dist/core/messages/fr.js
CHANGED
|
@@ -72,7 +72,7 @@ export const fr = {
|
|
|
72
72
|
planCommentQuestion: "Que faut-il changer ? (Entrée pour passer) : ",
|
|
73
73
|
retireLines: (source, why, reason, replacedBy, folder) => [
|
|
74
74
|
`Source : ${source}`,
|
|
75
|
-
why === "wrong" ? "Motif : elle est erronée
|
|
75
|
+
why === "wrong" ? "Motif : elle est erronée" : "Motif : une meilleure la remplace",
|
|
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.`,
|
package/dist/core/session.d.ts
CHANGED
|
@@ -37,6 +37,18 @@ export declare function buildSessionOptions<TConfig extends BaseSessionConfig>(c
|
|
|
37
37
|
knowledgeStore?: KnowledgeStore;
|
|
38
38
|
}>;
|
|
39
39
|
export { createModeControl, togglePlanMode, type ModeControl } from "./modeControl.js";
|
|
40
|
+
/**
|
|
41
|
+
* The CLI's folder for a project, where it keeps each session's transcript and large tool
|
|
42
|
+
* results (`<session>/tool-results/`): the project's path with every character but letters
|
|
43
|
+
* and digits as "-" (confirmed empirically).
|
|
44
|
+
*/
|
|
45
|
+
export declare function claudeProjectDir(projectDir: string): string;
|
|
46
|
+
/**
|
|
47
|
+
* The skills of the plugins in `roots`, as the SDK names them: `<plugin>:<folder>`, the
|
|
48
|
+
* plugin's name from its `.claude-plugin/plugin.json` and the skill's folder under `skills/`,
|
|
49
|
+
* whatever its frontmatter's `name` says (confirmed empirically).
|
|
50
|
+
*/
|
|
51
|
+
export declare function pluginSkills(roots: readonly string[]): Promise<string[]>;
|
|
40
52
|
/**
|
|
41
53
|
* User message queue backed by a single long-lived generator, for any multi-turn
|
|
42
54
|
* (chat-shaped) caller of `query()`.
|
package/dist/core/session.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { readdir, readFile } from "node:fs/promises";
|
|
2
|
+
import os from "node:os";
|
|
1
3
|
import path from "node:path";
|
|
2
4
|
import { createTranscriptLogger } from "./hooks/transcriptLogger.js";
|
|
3
5
|
import { createStepGate } from "./hooks/stepGate.js";
|
|
@@ -8,7 +10,7 @@ import { createFileScopeGate } from "./hooks/fileScopeGate.js";
|
|
|
8
10
|
import { createPlanGate } from "./hooks/planGate.js";
|
|
9
11
|
import { createHumanApprovalServer } from "./tools/humanApproval.js";
|
|
10
12
|
import { createManualLoginServer } from "./tools/manualLogin.js";
|
|
11
|
-
import { createSaveToSourcesServer } from "./tools/saveToSources.js";
|
|
13
|
+
import { createSaveToSourcesServer, sourcesPromptSection } from "./tools/saveToSources.js";
|
|
12
14
|
import { createTimeServer } from "./tools/time.js";
|
|
13
15
|
import { TODO_TOOL } from "./todos.js";
|
|
14
16
|
import { createModeControl } from "./modeControl.js";
|
|
@@ -62,25 +64,26 @@ export async function buildSessionOptions(config, runDir, spec, options = {}) {
|
|
|
62
64
|
// The built-in knowledge base (rules in the system prompt + its skills/commands as a plugin) needs
|
|
63
65
|
// somewhere to keep the wiki, so it follows `knowledgeDir`; `spec.knowledgeBase: false` opts out.
|
|
64
66
|
const includeKnowledgeBase = Boolean(config.knowledgeDir) && spec.knowledgeBase !== false;
|
|
65
|
-
//
|
|
66
|
-
//
|
|
67
|
-
|
|
68
|
-
const notesDir =
|
|
67
|
+
// The agent reaches it only through the `knowledge_*` tools, over a store, never with the file
|
|
68
|
+
// tools (ADR-024). Without it (`knowledgeBase: false`), `knowledgeDir` is a folder of the
|
|
69
|
+
// agent's own notes, kept with the file tools under its own rules.
|
|
70
|
+
const notesDir = includeKnowledgeBase ? undefined : config.knowledgeDir;
|
|
69
71
|
const extraDirs = config.extraWritableDirs ?? [];
|
|
70
|
-
|
|
72
|
+
const readableExtras = config.extraReadableDirs ?? [];
|
|
73
|
+
// With the knowledge base, the file tools are only for the originals (reading) and the extra
|
|
71
74
|
// folders; without either, none.
|
|
72
75
|
const fileTools = !includeFileTools
|
|
73
76
|
? []
|
|
74
|
-
: !
|
|
77
|
+
: !includeKnowledgeBase
|
|
75
78
|
? ["Read", "Write", "Edit", "Glob", "Grep"]
|
|
76
|
-
: [...(config.sourcesDir || extraDirs.length ? ["Read", "Glob", "Grep"] : []), ...(extraDirs.length ? ["Write", "Edit"] : [])];
|
|
79
|
+
: [...(config.sourcesDir || extraDirs.length || readableExtras.length ? ["Read", "Glob", "Grep"] : []), ...(extraDirs.length ? ["Write", "Edit"] : [])];
|
|
77
80
|
// Where Write/Edit may act and Grep may search (see hooks/fileScopeGate.ts): the
|
|
78
81
|
// project's own folders, never the whole cwd — which would include whatever else the
|
|
79
82
|
// project directory holds (the user's config...). `sourcesDir` is searchable but
|
|
80
83
|
// deliberately NOT writable: originals stay as obtained, and the only way to add to it is
|
|
81
84
|
// the `save_to_sources` tool (which never overwrites).
|
|
82
85
|
const writableDirs = [notesDir, ...extraDirs].filter((d) => Boolean(d));
|
|
83
|
-
const searchableDirs = [notesDir, config.sourcesDir, ...extraDirs].filter((d) => Boolean(d));
|
|
86
|
+
const searchableDirs = [notesDir, config.sourcesDir, ...extraDirs, ...readableExtras].filter((d) => Boolean(d));
|
|
84
87
|
const readOnlyDirs = config.sourcesDir ? [config.sourcesDir] : [];
|
|
85
88
|
// Skills/commands/plugins are a separate concern from the file tools: an agent that
|
|
86
89
|
// wants a plugin-provided skill/command but has no notes or sources folder shouldn't have
|
|
@@ -99,13 +102,15 @@ export async function buildSessionOptions(config, runDir, spec, options = {}) {
|
|
|
99
102
|
: undefined;
|
|
100
103
|
const pluginRoots = [
|
|
101
104
|
...spec.pluginRoots(config),
|
|
102
|
-
...(includeKnowledgeBase ? [knowledgePluginRoot(
|
|
105
|
+
...(includeKnowledgeBase ? [knowledgePluginRoot()] : []),
|
|
103
106
|
...(helpPlugin ? [helpPlugin] : []),
|
|
104
107
|
];
|
|
105
|
-
const knowledgeStore =
|
|
106
|
-
? (spec.knowledgeStore?.(config) ?? createFileKnowledgeStore(config.knowledgeDir, { pageTypes: spec.knowledgePageTypes
|
|
108
|
+
const knowledgeStore = includeKnowledgeBase && config.knowledgeDir
|
|
109
|
+
? (spec.knowledgeStore?.(config) ?? createFileKnowledgeStore(config.knowledgeDir, { pageTypes: spec.knowledgePageTypes }))
|
|
107
110
|
: undefined;
|
|
108
111
|
const includeSkillsAndPlugins = includeFileTools || pluginRoots.length > 0;
|
|
112
|
+
// "plugins": the skills of the plugins loaded, named as the SDK names them.
|
|
113
|
+
const offeredSkills = spec.skills === "plugins" ? await pluginSkills(pluginRoots) : spec.skills;
|
|
109
114
|
const skillTools = includeSkillsAndPlugins ? ["Skill"] : [];
|
|
110
115
|
// Whatever opt-in subagents `spec` wants for this config, or undefined if none apply.
|
|
111
116
|
// Subagents need the Agent tool (to delegate to them), and Bash when one of them uses it
|
|
@@ -135,11 +140,17 @@ export async function buildSessionOptions(config, runDir, spec, options = {}) {
|
|
|
135
140
|
const allowedSubagentTypes = subagents?.allowedSubagentTypes ?? [];
|
|
136
141
|
const transcriptPath = path.join(runDir, "transcript.jsonl");
|
|
137
142
|
const transcriptLogger = createTranscriptLogger(transcriptPath, config.secrets ?? []);
|
|
143
|
+
// The person's preferences, by title, for the knowledge base's section (#33).
|
|
144
|
+
const preferences = knowledgeStore
|
|
145
|
+
? (await knowledgeStore.list()).filter((page) => page.type === "preference" && page.status === "active").map(({ id, title }) => ({ id, title }))
|
|
146
|
+
: [];
|
|
138
147
|
const promptSections = [
|
|
139
148
|
spec.buildSystemPrompt(config),
|
|
140
149
|
...(spec.identity ? [identityPromptSection(spec.identity)] : []),
|
|
150
|
+
// The sources' own section, with or without a knowledge base (#30).
|
|
151
|
+
...(config.sourcesDir ? [sourcesPromptSection(config.projectDir, config.sourcesDir)] : []),
|
|
141
152
|
...(includeKnowledgeBase && config.knowledgeDir
|
|
142
|
-
? [knowledgePromptSection(
|
|
153
|
+
? [knowledgePromptSection({ withSources: Boolean(config.sourcesDir), pageTypes: knowledgeStore?.types(), preferences })]
|
|
143
154
|
: []),
|
|
144
155
|
];
|
|
145
156
|
const sdkOptions = {
|
|
@@ -153,8 +164,16 @@ export async function buildSessionOptions(config, runDir, spec, options = {}) {
|
|
|
153
164
|
// whose own memory is the knowledge base. includeGitInstructions: false leaves out the
|
|
154
165
|
// CLI's commit workflow and git context (the runner's git user name among it), which no
|
|
155
166
|
// agent here needs and which pulled replies towards the runner's language (confirmed
|
|
156
|
-
// empirically, see ADR-018).
|
|
157
|
-
|
|
167
|
+
// empirically, see ADR-018). disableClaudeAiConnectors: true keeps out the claude.ai
|
|
168
|
+
// connectors of the account the session runs with (Drive, Gmail…), which the CLI fetches
|
|
169
|
+
// whatever settingSources says (confirmed empirically): the runner's integrations, not the
|
|
170
|
+
// agent's. An MCP server the agent passes itself is unaffected.
|
|
171
|
+
settings: {
|
|
172
|
+
autoCompactEnabled: options.autoCompactEnabled ?? true,
|
|
173
|
+
autoMemoryEnabled: false,
|
|
174
|
+
includeGitInstructions: false,
|
|
175
|
+
disableClaudeAiConnectors: true,
|
|
176
|
+
},
|
|
158
177
|
// No built-in tools except, if applicable, Read/Write/Edit/Glob/Grep scoped to the
|
|
159
178
|
// project's own folders (fileScopeGate below), and WebFetch/WebSearch
|
|
160
179
|
// with no domain restriction (not conditioned on includeFileTools: it's read-only,
|
|
@@ -197,7 +216,7 @@ export async function buildSessionOptions(config, runDir, spec, options = {}) {
|
|
|
197
216
|
// SDK's own official skills (pdf/docx), the project's own custom ones, and the
|
|
198
217
|
// plugin-provided built-ins below. A spec's own list gets the knowledge base's
|
|
199
218
|
// skills and agent-help added, so it only names its own.
|
|
200
|
-
skills: skillList(
|
|
219
|
+
skills: skillList(offeredSkills, includeKnowledgeBase, Boolean(helpPlugin)),
|
|
201
220
|
plugins: pluginRoots.map((pluginPath) => ({ type: "local", path: pluginPath, skipMcpDiscovery: true })),
|
|
202
221
|
}
|
|
203
222
|
: {}),
|
|
@@ -213,7 +232,6 @@ export async function buildSessionOptions(config, runDir, spec, options = {}) {
|
|
|
213
232
|
...(config.sourcesDir
|
|
214
233
|
? {
|
|
215
234
|
sourceFiles: createSaveToSourcesServer(runDir, config.sourcesDir, spec.saveToSourcesDescription, {
|
|
216
|
-
knowledgeDir: config.knowledgeDir,
|
|
217
235
|
projectDir: config.projectDir,
|
|
218
236
|
interactive: mode !== "autonomous",
|
|
219
237
|
}),
|
|
@@ -222,13 +240,7 @@ export async function buildSessionOptions(config, runDir, spec, options = {}) {
|
|
|
222
240
|
// The knowledge base's own tools, over its store (tools/knowledgeTools.ts, ADR-024).
|
|
223
241
|
...(knowledgeStore
|
|
224
242
|
? {
|
|
225
|
-
knowledge: createKnowledgeServer(knowledgeStore, {
|
|
226
|
-
runDir,
|
|
227
|
-
interactive: mode !== "autonomous",
|
|
228
|
-
sourcesDir: config.sourcesDir,
|
|
229
|
-
projectDir: config.projectDir,
|
|
230
|
-
knowledgeDir: config.knowledgeDir,
|
|
231
|
-
}),
|
|
243
|
+
knowledge: createKnowledgeServer(knowledgeStore, { runDir, interactive: mode !== "autonomous" }),
|
|
232
244
|
}
|
|
233
245
|
: {}),
|
|
234
246
|
// The date and date arithmetic, in every session and mode: only reads (tools/time.ts).
|
|
@@ -243,8 +255,16 @@ export async function buildSessionOptions(config, runDir, spec, options = {}) {
|
|
|
243
255
|
writableDirs,
|
|
244
256
|
searchableDirs,
|
|
245
257
|
readOnlyDirs,
|
|
246
|
-
|
|
247
|
-
...(
|
|
258
|
+
// The SDK's own credentials, whatever the agent denies.
|
|
259
|
+
deniedPaths: [...(config.deniedPaths ?? []), path.join(claudeConfigDir(), ".credentials.json")],
|
|
260
|
+
// Read and Glob only in the agent's folders, plus what only the kit knows it
|
|
261
|
+
// needs: the run folder, the plugins, the project's .claude/ (when loaded) and
|
|
262
|
+
// the SDK's large tool results. Never the rest of the disk (~/.ssh, other
|
|
263
|
+
// projects' .env...), which an agent reading untrusted content could be talked into.
|
|
264
|
+
readableDirs: searchableDirs,
|
|
265
|
+
alsoReadable: [runDir, ...pluginRoots, ...((spec.settingSources ?? ["project"]).includes("project") ? [path.join(config.projectDir, ".claude")] : [])],
|
|
266
|
+
toolResultsRoot: claudeProjectDir(config.projectDir),
|
|
267
|
+
...(includeKnowledgeBase && config.knowledgeDir ? { toolOnlyDirs: [{ dir: config.knowledgeDir, instead: "the knowledge_* tools" }] } : {}),
|
|
248
268
|
}),
|
|
249
269
|
] }]
|
|
250
270
|
: []),
|
|
@@ -280,15 +300,50 @@ export async function buildSessionOptions(config, runDir, spec, options = {}) {
|
|
|
280
300
|
return { options: sdkOptions, transcriptLogger, transcriptPath, modeControl, language, ...(knowledgeStore ? { knowledgeStore } : {}) };
|
|
281
301
|
}
|
|
282
302
|
export { createModeControl, togglePlanMode } from "./modeControl.js";
|
|
283
|
-
/** The
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
303
|
+
/** The Claude Code configuration folder the SDK's CLI uses: `CLAUDE_CONFIG_DIR`, or `~/.claude`. */
|
|
304
|
+
function claudeConfigDir() {
|
|
305
|
+
return process.env.CLAUDE_CONFIG_DIR || path.join(os.homedir(), ".claude");
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* The CLI's folder for a project, where it keeps each session's transcript and large tool
|
|
309
|
+
* results (`<session>/tool-results/`): the project's path with every character but letters
|
|
310
|
+
* and digits as "-" (confirmed empirically).
|
|
311
|
+
*/
|
|
312
|
+
export function claudeProjectDir(projectDir) {
|
|
313
|
+
return path.join(claudeConfigDir(), "projects", path.resolve(projectDir).replace(/[^a-zA-Z0-9]/g, "-"));
|
|
314
|
+
}
|
|
315
|
+
/** The knowledge plugin's skills, as the SDK names a plugin's skills ("plugin:skill"). */
|
|
316
|
+
const KNOWLEDGE_SKILLS = ["knowledge-ingest", "knowledge-query", "knowledge-lint"].map((skill) => `knowledge:${skill}`);
|
|
317
|
+
/**
|
|
318
|
+
* The skills of the plugins in `roots`, as the SDK names them: `<plugin>:<folder>`, the
|
|
319
|
+
* plugin's name from its `.claude-plugin/plugin.json` and the skill's folder under `skills/`,
|
|
320
|
+
* whatever its frontmatter's `name` says (confirmed empirically).
|
|
321
|
+
*/
|
|
322
|
+
export async function pluginSkills(roots) {
|
|
323
|
+
const names = [];
|
|
324
|
+
for (const root of roots) {
|
|
325
|
+
let plugin;
|
|
326
|
+
try {
|
|
327
|
+
plugin = JSON.parse(await readFile(path.join(root, ".claude-plugin", "plugin.json"), "utf8")).name;
|
|
328
|
+
}
|
|
329
|
+
catch {
|
|
330
|
+
continue; // not a plugin the SDK would load
|
|
331
|
+
}
|
|
332
|
+
if (!plugin)
|
|
333
|
+
continue;
|
|
334
|
+
const folders = await readdir(path.join(root, "skills"), { withFileTypes: true }).catch(() => []);
|
|
335
|
+
for (const folder of folders.filter((entry) => entry.isDirectory())) {
|
|
336
|
+
const hasSkill = await readFile(path.join(root, "skills", folder.name, "SKILL.md")).then(() => true, () => false);
|
|
337
|
+
if (hasSkill)
|
|
338
|
+
names.push(`${plugin}:${folder.name}`);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
return names;
|
|
342
|
+
}
|
|
343
|
+
function skillList(skills, knowledgeBase, agentHelp) {
|
|
289
344
|
if (skills === undefined || skills === "all")
|
|
290
345
|
return "all";
|
|
291
|
-
return [...new Set([...skills, ...(
|
|
346
|
+
return [...new Set([...skills, ...(knowledgeBase ? KNOWLEDGE_SKILLS : []), ...(agentHelp ? [AGENT_HELP_SKILL] : [])])];
|
|
292
347
|
}
|
|
293
348
|
/**
|
|
294
349
|
* User message queue backed by a single long-lived generator, for any multi-turn
|
package/dist/core/sources.d.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The sources folder's bookkeeping: originals stay files (ADR-024), and the kit keeps a
|
|
3
3
|
* manifest next to them (`.agent-kit/sources.json`, inside `sourcesDir`, where the agent's
|
|
4
|
-
* file tools can't write) with each original's hash, size
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* file tools can't write) with each original's hash, size, provenance, the version it replaces
|
|
5
|
+
* and when its content last changed. From that it refuses duplicates, tells present, missing
|
|
6
|
+
* and retired originals apart, and retires an original into `.agent-kit/retired/` instead of
|
|
7
|
+
* deleting it. It knows nothing of the knowledge base: each owns its data, and the model
|
|
8
|
+
* connects them through their tools (#30). Hashes never leave it.
|
|
8
9
|
*/
|
|
9
10
|
export declare const KIT_DIR = ".agent-kit";
|
|
10
11
|
/** Where an original came from. */
|
|
@@ -33,8 +34,8 @@ export interface SourceEntry {
|
|
|
33
34
|
replaces?: string;
|
|
34
35
|
/** The page this one was generated from (a web page's markdown, from its HTML). */
|
|
35
36
|
derivedFrom?: string;
|
|
36
|
-
/**
|
|
37
|
-
|
|
37
|
+
/** When its content last changed (ISO 8601): when it was added, or when its hash last differed. Not when it was only touched. */
|
|
38
|
+
changedAt: string;
|
|
38
39
|
}
|
|
39
40
|
export interface RetiredSource {
|
|
40
41
|
path: string;
|
|
@@ -50,18 +51,23 @@ export interface SourceManifest {
|
|
|
50
51
|
files: Record<string, SourceEntry>;
|
|
51
52
|
retired: RetiredSource[];
|
|
52
53
|
}
|
|
53
|
-
|
|
54
|
+
/** Present in the folder; missing (in the manifest, deleted by hand); or retired with `retire_source`. */
|
|
55
|
+
export type SourceStatus = "present" | "missing" | "retired";
|
|
54
56
|
export interface SourceListing {
|
|
55
57
|
path: string;
|
|
56
58
|
status: SourceStatus;
|
|
57
59
|
type: string;
|
|
60
|
+
/** When its content last changed (ISO 8601), to compare with when something was built from it. */
|
|
61
|
+
changedAt?: string;
|
|
58
62
|
size?: number;
|
|
59
63
|
pages?: number;
|
|
60
64
|
slides?: number;
|
|
61
65
|
origin?: SourceOrigin;
|
|
62
66
|
replaces?: string;
|
|
63
|
-
/**
|
|
64
|
-
|
|
67
|
+
/** For a retired one: when, why, and what replaced it. */
|
|
68
|
+
retiredAt?: string;
|
|
69
|
+
why?: "wrong" | "replaced";
|
|
70
|
+
replacedBy?: string;
|
|
65
71
|
}
|
|
66
72
|
export declare function hashFile(file: string): Promise<string>;
|
|
67
73
|
/** Resolves `candidate` against `base`; the absolute path only if it stays inside it. */
|
|
@@ -70,18 +76,15 @@ export declare function loadManifest(sourcesDir: string): Promise<SourceManifest
|
|
|
70
76
|
export declare function saveManifest(sourcesDir: string, manifest: SourceManifest): Promise<void>;
|
|
71
77
|
/** Every original under `sourcesDir` (paths relative to it), leaving out hidden files and folders (the kit's own among them). */
|
|
72
78
|
export declare function scanSources(sourcesDir: string): Promise<string[]>;
|
|
73
|
-
/** The summary pages (knowledge base layout: `summaries/*.md`) by the absolute path of the original their frontmatter `file:` points to. */
|
|
74
|
-
export declare function summariesByOriginal(knowledgeDir: string): Promise<Map<string, string[]>>;
|
|
75
79
|
/** Pages of a PDF, read from its page tree without a parser; undefined when it can't tell (e.g. compressed object streams). */
|
|
76
80
|
export declare function pdfPageCount(bytes: Buffer): number | undefined;
|
|
77
81
|
/** Slides of a PPTX, from the file names in the zip (stored uncompressed). */
|
|
78
82
|
export declare function pptxSlideCount(bytes: Buffer): number | undefined;
|
|
79
83
|
/**
|
|
80
|
-
* Every original with its status
|
|
81
|
-
* the
|
|
82
|
-
* folder there's no ingest to compare with: originals are "present".
|
|
84
|
+
* Every original with its status and when its content last changed, recording in the manifest
|
|
85
|
+
* the ones copied in by hand; then the missing ones and the retired ones.
|
|
83
86
|
*/
|
|
84
|
-
export declare function listSources(sourcesDir: string
|
|
87
|
+
export declare function listSources(sourcesDir: string): Promise<SourceListing[]>;
|
|
85
88
|
export interface AddedSource {
|
|
86
89
|
/** Where it is, relative to `sourcesDir`. */
|
|
87
90
|
path: string;
|
|
@@ -99,24 +102,14 @@ export declare function addSource(sourcesDir: string, file: string, destination:
|
|
|
99
102
|
derivedFrom?: string;
|
|
100
103
|
name?: string;
|
|
101
104
|
}): Promise<AddedSource>;
|
|
102
|
-
/**
|
|
105
|
+
/** Where a retired original went. */
|
|
103
106
|
export interface RetireResult {
|
|
107
|
+
/** Relative to `sourcesDir`. */
|
|
104
108
|
movedTo: string;
|
|
105
|
-
summaries: {
|
|
106
|
-
page: string;
|
|
107
|
-
marked: "retired" | "superseded";
|
|
108
|
-
supersededBy?: string;
|
|
109
|
-
}[];
|
|
110
109
|
}
|
|
111
110
|
/**
|
|
112
|
-
* Moves an original to `.agent-kit/retired/` (never deleted: the person empties that folder)
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
* when there is one.
|
|
116
|
-
*/
|
|
117
|
-
export declare function retireSource(sourcesDir: string, knowledgeDir: string | undefined, projectDir: string, source: string, why: "wrong" | "replaced", reason: string, replacedBy?: string): Promise<RetireResult>;
|
|
118
|
-
/**
|
|
119
|
-
* Records `source` (relative to `sourcesDir`) as ingested now, with its current hash: a
|
|
120
|
-
* summary page was just written from it, so "changed" means changed after this.
|
|
111
|
+
* Moves an original to `.agent-kit/retired/` (never deleted: the person empties that folder) and
|
|
112
|
+
* records why. What was built from it (summary pages) is the knowledge base's to update: the
|
|
113
|
+
* model does it with its tools.
|
|
121
114
|
*/
|
|
122
|
-
export declare function
|
|
115
|
+
export declare function retireSource(sourcesDir: string, source: string, why: "wrong" | "replaced", reason: string, replacedBy?: string): Promise<RetireResult>;
|