@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
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,18 +10,19 @@ 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";
|
|
12
13
|
import { createTimeServer } from "./tools/time.js";
|
|
13
14
|
import { TODO_TOOL } from "./todos.js";
|
|
14
15
|
import { createModeControl } from "./modeControl.js";
|
|
15
|
-
import {
|
|
16
|
-
import { createKnowledgeServer } from "./tools/knowledgeTools.js";
|
|
16
|
+
import { extensionsPromptSection, resolveExtensions, skillsMissingCapabilities } from "./extensions.js";
|
|
17
17
|
import { allowAnyMcpTool } from "./mcpPermissions.js";
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
18
|
+
import { pluginAgents } from "./pluginAgents.js";
|
|
19
|
+
import { outsideArchive, unpackedClaudeExecutable } from "./packaged.js";
|
|
20
|
+
import { listInstalled, loadExternalExtensions } from "./externalExtensions.js";
|
|
20
21
|
import { replyLanguageInstruction } from "./language.js";
|
|
21
22
|
import { chooseLanguage } from "./messages/index.js";
|
|
22
23
|
import { createRunStore } from "./runs.js";
|
|
24
|
+
import { createSessionView, registerSessionView } from "./sessionFacts.js";
|
|
25
|
+
import { agentKitVersion } from "./version.js";
|
|
23
26
|
/**
|
|
24
27
|
* Builds the `options` object passed to the Agent SDK's `query()` — everything about
|
|
25
28
|
* *how* the agent runs a session (system prompt, tools, MCP servers, hooks), independent
|
|
@@ -27,9 +30,10 @@ import { createRunStore } from "./runs.js";
|
|
|
27
30
|
* one-shot run or an `AsyncIterable` for a multi-turn chat).
|
|
28
31
|
*
|
|
29
32
|
* Everything actually *about the domain* (which system prompt, which MCP servers besides
|
|
30
|
-
* the generic human-in-the-loop
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
+
* the generic human-in-the-loop ones below, which plugin roots, which subagents) is behind
|
|
34
|
+
* `spec` (see agentSpec.ts), and the kit's optional parts (the knowledge base, the sources
|
|
35
|
+
* folder) are extensions whose contributions this function puts together (extensions.ts,
|
|
36
|
+
* ADR-025): it names none of them.
|
|
33
37
|
*
|
|
34
38
|
* The per-mode behavioral differences (whether the approval/manual-intervention tools
|
|
35
39
|
* exist, whether the interactive step gate or the plan gate decides) all fall out of
|
|
@@ -55,57 +59,71 @@ export async function buildSessionOptions(config, runDir, spec, options = {}) {
|
|
|
55
59
|
// domain's own opt-in signal (see its doc comment in agentSpec.ts), not a generic
|
|
56
60
|
// boolean this kit could infer on its own.
|
|
57
61
|
const manualInterventionTexts = mode !== "autonomous" ? spec.manualInterventionTexts : undefined;
|
|
58
|
-
//
|
|
59
|
-
//
|
|
60
|
-
//
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
62
|
+
// What the enabled extensions bring (ADR-025): their tools, prompt sections, plugins and
|
|
63
|
+
// folders, put together here.
|
|
64
|
+
// `session()` reads the view built below, once the options are: an extension's tools call it later.
|
|
65
|
+
const late = {};
|
|
66
|
+
const extensionContext = {
|
|
67
|
+
config,
|
|
68
|
+
spec,
|
|
69
|
+
runDir,
|
|
70
|
+
mode,
|
|
71
|
+
interactive: mode !== "autonomous",
|
|
72
|
+
session: () => (late.view ? late.view.facts() : Promise.reject(new Error("The session isn't built yet: call session() from a tool, not from contribute()."))),
|
|
73
|
+
};
|
|
74
|
+
// The installed ones (#37), the project's over the agent's: those that can't load are off with
|
|
75
|
+
// why, and so is one named like an extension the spec enables.
|
|
76
|
+
const extensionDirs = config.extensionDirs ?? {};
|
|
77
|
+
const external = await loadExternalExtensions(extensionDirs);
|
|
78
|
+
const enabledNames = new Set((spec.extensions ?? []).map((entry) => (typeof entry === "string" ? entry : entry.name)));
|
|
79
|
+
const resolved = resolveExtensions([...(spec.extensions ?? []), ...external.extensions.filter((extension) => !enabledNames.has(extension.name))], extensionContext);
|
|
80
|
+
resolved.inactive.push(...external.off, ...external.extensions.filter((extension) => enabledNames.has(extension.name)).map((extension) => ({ name: extension.name, reason: "has the name of one this agent already has" })));
|
|
81
|
+
const externalPlugins = new Set(resolved.active.filter(({ extension }) => extension.external).map(({ extension }) => path.resolve(extension.plugin)));
|
|
82
|
+
const added = await Promise.all(resolved.active.map(({ extension }) => extension.contribute(extensionContext)));
|
|
83
|
+
const extensionPlugins = resolved.active.map(({ extension }) => extension.plugin);
|
|
84
|
+
const fromExtensions = (pick) => added.flatMap((contribution) => pick(contribution) ?? []);
|
|
85
|
+
const knowledgeStore = added.map((contribution) => contribution.api?.knowledgeStore).find(Boolean);
|
|
86
|
+
// An extension may claim `knowledgeDir` as reached only through its tools (the knowledge base,
|
|
87
|
+
// ADR-024); otherwise it's a folder of the agent's own notes, kept with the file tools under
|
|
88
|
+
// its own rules.
|
|
89
|
+
const claimed = (dir) => fromExtensions((contribution) => contribution.toolOnlyDirs).some((only) => path.resolve(only.dir) === path.resolve(dir));
|
|
90
|
+
const notesDir = config.knowledgeDir && !claimed(config.knowledgeDir) ? config.knowledgeDir : undefined;
|
|
69
91
|
const extraDirs = config.extraWritableDirs ?? [];
|
|
70
|
-
|
|
71
|
-
//
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
92
|
+
const readableExtras = config.extraReadableDirs ?? [];
|
|
93
|
+
// The file tools the session needs: all five for the notes and the extra writable folders,
|
|
94
|
+
// the reading ones for the extra readable folders, and whatever the extensions ask for (the
|
|
95
|
+
// sources' reading ones); without any of them, none.
|
|
96
|
+
const wantedFileTools = new Set([
|
|
97
|
+
...(notesDir || extraDirs.length ? FILE_TOOLS : []),
|
|
98
|
+
...(readableExtras.length ? ["Read", "Glob", "Grep"] : []),
|
|
99
|
+
...fromExtensions((contribution) => contribution.fileTools),
|
|
100
|
+
]);
|
|
101
|
+
const fileTools = FILE_TOOLS.filter((tool) => wantedFileTools.has(tool));
|
|
77
102
|
// Where Write/Edit may act and Grep may search (see hooks/fileScopeGate.ts): the
|
|
78
103
|
// project's own folders, never the whole cwd — which would include whatever else the
|
|
79
|
-
// project directory holds (the user's config...).
|
|
80
|
-
// deliberately NOT writable:
|
|
81
|
-
|
|
104
|
+
// project directory holds (the user's config...). An extension's folders (the sources) are
|
|
105
|
+
// searchable but deliberately NOT writable: the only way to add to them is its tools.
|
|
106
|
+
const readOnlyDirs = fromExtensions((contribution) => contribution.readOnlyDirs);
|
|
107
|
+
const toolOnlyDirs = fromExtensions((contribution) => contribution.toolOnlyDirs);
|
|
82
108
|
const writableDirs = [notesDir, ...extraDirs].filter((d) => Boolean(d));
|
|
83
|
-
const searchableDirs = [notesDir,
|
|
84
|
-
const readOnlyDirs = config.sourcesDir ? [config.sourcesDir] : [];
|
|
109
|
+
const searchableDirs = [notesDir, ...readOnlyDirs, ...extraDirs, ...readableExtras].filter((d) => Boolean(d));
|
|
85
110
|
// Skills/commands/plugins are a separate concern from the file tools: an agent that
|
|
86
111
|
// wants a plugin-provided skill/command but has no notes or sources folder shouldn't have
|
|
87
112
|
// to invent one just to get `cwd`/`skills: "all"`/`plugins` wired up. Gated on either
|
|
88
113
|
// signal, not on `pluginRoots` alone, so an agent with file tools keeps getting the SDK's
|
|
89
114
|
// own project-level `.claude/skills`/`.claude/commands` discovery.
|
|
90
|
-
//
|
|
91
|
-
|
|
92
|
-
const
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
...(includeKnowledgeBase ? [knowledgePluginRoot(knowledgeViaStore ? "tools" : "files")] : []),
|
|
103
|
-
...(helpPlugin ? [helpPlugin] : []),
|
|
104
|
-
];
|
|
105
|
-
const knowledgeStore = knowledgeViaStore && config.knowledgeDir
|
|
106
|
-
? (spec.knowledgeStore?.(config) ?? createFileKnowledgeStore(config.knowledgeDir, { pageTypes: spec.knowledgePageTypes, sourcesDir: config.sourcesDir }))
|
|
107
|
-
: undefined;
|
|
108
|
-
const includeSkillsAndPlugins = includeFileTools || pluginRoots.length > 0;
|
|
115
|
+
// Outside the archive when the agent runs packaged (Electron's app.asar): the CLI reads them (#42).
|
|
116
|
+
const pluginRoots = [...spec.pluginRoots(config), ...extensionPlugins].map(outsideArchive);
|
|
117
|
+
const includeSkillsAndPlugins = fileTools.length > 0 || pluginRoots.length > 0;
|
|
118
|
+
// "plugins": the skills of the plugins loaded, named as the SDK names them. A skill that
|
|
119
|
+
// `requires` a capability no active extension provides is left out; since the SDK's
|
|
120
|
+
// `skillOverrides` doesn't reach plugin skills (confirmed empirically), leaving one out takes
|
|
121
|
+
// a list: the plugins' skills and the project's, without it.
|
|
122
|
+
const missingCapabilities = await skillsMissingCapabilities(pluginRoots, resolved.capabilities);
|
|
123
|
+
const requested = spec.skills === "plugins" ? await pluginSkills(pluginRoots) : spec.skills;
|
|
124
|
+
const offeredSkills = missingCapabilities.length && (requested === undefined || requested === "all")
|
|
125
|
+
? [...(await pluginSkills(pluginRoots)), ...((spec.settingSources ?? ["project"]).includes("project") ? await projectSkills(config.projectDir) : [])]
|
|
126
|
+
: requested;
|
|
109
127
|
const skillTools = includeSkillsAndPlugins ? ["Skill"] : [];
|
|
110
128
|
// Whatever opt-in subagents `spec` wants for this config, or undefined if none apply.
|
|
111
129
|
// Subagents need the Agent tool (to delegate to them), and Bash when one of them uses it
|
|
@@ -116,33 +134,45 @@ export async function buildSessionOptions(config, runDir, spec, options = {}) {
|
|
|
116
134
|
// anything not already present here — which is why the hook, not just omitting "Bash"
|
|
117
135
|
// from this list, is what actually confines it to subagent use).
|
|
118
136
|
const subagents = spec.buildSubagents(config);
|
|
137
|
+
// The plugins' subagents too (agents/*.md, the agent's and its extensions'), registered as the
|
|
138
|
+
// spec's are: allowed to be spawned, counted in the Agent and Bash decision, and given the
|
|
139
|
+
// reply line (pluginAgents.ts; ADR-025).
|
|
140
|
+
// An installed extension's get no Bash (ADR-025): one without `tools` gets none.
|
|
141
|
+
const fromPlugins = {
|
|
142
|
+
...(await pluginAgents(pluginRoots.filter((root) => !externalPlugins.has(path.resolve(root))))),
|
|
143
|
+
...Object.fromEntries(Object.entries(await pluginAgents([...externalPlugins])).map(([name, agent]) => [name, { ...agent, tools: (agent.tools ?? []).filter((tool) => tool !== "Bash") }])),
|
|
144
|
+
};
|
|
145
|
+
const allAgents = { ...fromPlugins, ...(subagents?.agents ?? {}) };
|
|
119
146
|
// Each subagent gets the reply line too: its prompt doesn't include the main one's.
|
|
120
|
-
const subagentDefinitions =
|
|
121
|
-
? Object.fromEntries(Object.entries(
|
|
147
|
+
const subagentDefinitions = Object.keys(allAgents).length
|
|
148
|
+
? Object.fromEntries(Object.entries(allAgents).map(([name, agent]) => [name, { ...agent, prompt: withReplyLine(agent.prompt) }]))
|
|
122
149
|
: undefined;
|
|
123
|
-
const includeSubagentTools =
|
|
150
|
+
const includeSubagentTools = subagentDefinitions !== undefined;
|
|
124
151
|
// Bash only when a subagent can use it: one that lists it, or one without `tools`, which
|
|
125
152
|
// inherits every session tool. Its definition costs about 1.9k input tokens on every call
|
|
126
153
|
// (measured), and the main agent can't use it anyway.
|
|
127
154
|
const subagentTools = includeSubagentTools
|
|
128
|
-
? ["Agent", ...(Object.values(
|
|
155
|
+
? ["Agent", ...(Object.values(allAgents).some((agent) => !agent.tools || agent.tools.includes("Bash")) ? ["Bash"] : [])]
|
|
129
156
|
: [];
|
|
130
157
|
// The exact set of subagent_type values the Agent tool may spawn in this session — see
|
|
131
158
|
// createSubagentTypeGate() below for why this can't just be "whatever's in `agents`
|
|
132
159
|
// below": the SDK's own built-in "general-purpose" type is spawnable regardless of that
|
|
133
160
|
// map, and inherits the full session tools (Bash included) rather than the narrow list
|
|
134
161
|
// each declared subagent actually declares.
|
|
135
|
-
const allowedSubagentTypes = subagents?.allowedSubagentTypes ?? [];
|
|
162
|
+
const allowedSubagentTypes = [...new Set([...(subagents?.allowedSubagentTypes ?? []), ...Object.keys(fromPlugins)])];
|
|
136
163
|
const transcriptPath = path.join(runDir, "transcript.jsonl");
|
|
137
164
|
const transcriptLogger = createTranscriptLogger(transcriptPath, config.secrets ?? []);
|
|
138
165
|
const promptSections = [
|
|
139
166
|
spec.buildSystemPrompt(config),
|
|
140
|
-
|
|
141
|
-
...(
|
|
142
|
-
|
|
143
|
-
|
|
167
|
+
// What's on and what isn't, then the extensions' own sections.
|
|
168
|
+
...(resolved.active.length || resolved.inactive.length ? [extensionsPromptSection(resolved)] : []),
|
|
169
|
+
// The extensions' own sections, in their order (the sources' before the knowledge base's).
|
|
170
|
+
...fromExtensions((contribution) => (contribution.promptSection ? [contribution.promptSection] : [])),
|
|
144
171
|
];
|
|
172
|
+
// Packaged, the SDK would look for its CLI binary inside the archive, and hang (#42).
|
|
173
|
+
const claudeExecutable = unpackedClaudeExecutable();
|
|
145
174
|
const sdkOptions = {
|
|
175
|
+
...(claudeExecutable ? { pathToClaudeCodeExecutable: claudeExecutable } : {}),
|
|
146
176
|
systemPrompt: withReplyLine(promptSections.join("\n\n")),
|
|
147
177
|
// Not the SDK's default (every source): "user" hands the agent the runner's own Claude
|
|
148
178
|
// Code configuration, and its `language` setting outranked the agent's prompt.
|
|
@@ -153,8 +183,19 @@ export async function buildSessionOptions(config, runDir, spec, options = {}) {
|
|
|
153
183
|
// whose own memory is the knowledge base. includeGitInstructions: false leaves out the
|
|
154
184
|
// CLI's commit workflow and git context (the runner's git user name among it), which no
|
|
155
185
|
// agent here needs and which pulled replies towards the runner's language (confirmed
|
|
156
|
-
// empirically, see ADR-018).
|
|
157
|
-
|
|
186
|
+
// empirically, see ADR-018). disableClaudeAiConnectors: true keeps out the claude.ai
|
|
187
|
+
// connectors of the account the session runs with (Drive, Gmail…), which the CLI fetches
|
|
188
|
+
// whatever settingSources says (confirmed empirically): the runner's integrations, not the
|
|
189
|
+
// agent's. An MCP server the agent passes itself is unaffected.
|
|
190
|
+
settings: {
|
|
191
|
+
autoCompactEnabled: options.autoCompactEnabled ?? true,
|
|
192
|
+
autoMemoryEnabled: false,
|
|
193
|
+
includeGitInstructions: false,
|
|
194
|
+
disableClaudeAiConnectors: true,
|
|
195
|
+
// An installed extension's plugin may carry hooks: none run (ADR-025). The kit's own,
|
|
196
|
+
// passed in `hooks`, still do (confirmed empirically).
|
|
197
|
+
...(externalPlugins.size ? { disableAllHooks: true } : {}),
|
|
198
|
+
},
|
|
158
199
|
// No built-in tools except, if applicable, Read/Write/Edit/Glob/Grep scoped to the
|
|
159
200
|
// project's own folders (fileScopeGate below), and WebFetch/WebSearch
|
|
160
201
|
// with no domain restriction (not conditioned on includeFileTools: it's read-only,
|
|
@@ -195,9 +236,9 @@ export async function buildSessionOptions(config, runDir, spec, options = {}) {
|
|
|
195
236
|
additionalDirectories: searchableDirs,
|
|
196
237
|
// "skills" acts as a name whitelist, not an addition: "all" enables both the
|
|
197
238
|
// SDK's own official skills (pdf/docx), the project's own custom ones, and the
|
|
198
|
-
// plugin-provided built-ins below. A spec's own list gets the
|
|
199
|
-
//
|
|
200
|
-
skills: skillList(
|
|
239
|
+
// plugin-provided built-ins below. A spec's own list gets the extensions' skills
|
|
240
|
+
// added, so it only names its own.
|
|
241
|
+
skills: skillList(offeredSkills, await pluginSkills(extensionPlugins), missingCapabilities),
|
|
201
242
|
plugins: pluginRoots.map((pluginPath) => ({ type: "local", path: pluginPath, skipMcpDiscovery: true })),
|
|
202
243
|
}
|
|
203
244
|
: {}),
|
|
@@ -208,49 +249,36 @@ export async function buildSessionOptions(config, runDir, spec, options = {}) {
|
|
|
208
249
|
...spec.buildMcpServers(config, runDir),
|
|
209
250
|
...(includeApprovalTool ? { approvals: createHumanApprovalServer(runDir, spec.humanApprovalTexts, { modeControl }) } : {}),
|
|
210
251
|
...(manualInterventionTexts ? { manualLogin: createManualLoginServer(runDir, manualInterventionTexts) } : {}),
|
|
211
|
-
// The
|
|
212
|
-
|
|
213
|
-
...(config.sourcesDir
|
|
214
|
-
? {
|
|
215
|
-
sourceFiles: createSaveToSourcesServer(runDir, config.sourcesDir, spec.saveToSourcesDescription, {
|
|
216
|
-
knowledgeDir: config.knowledgeDir,
|
|
217
|
-
projectDir: config.projectDir,
|
|
218
|
-
interactive: mode !== "autonomous",
|
|
219
|
-
}),
|
|
220
|
-
}
|
|
221
|
-
: {}),
|
|
222
|
-
// The knowledge base's own tools, over its store (tools/knowledgeTools.ts, ADR-024).
|
|
223
|
-
...(knowledgeStore
|
|
224
|
-
? {
|
|
225
|
-
knowledge: createKnowledgeServer(knowledgeStore, {
|
|
226
|
-
runDir,
|
|
227
|
-
interactive: mode !== "autonomous",
|
|
228
|
-
sourcesDir: config.sourcesDir,
|
|
229
|
-
projectDir: config.projectDir,
|
|
230
|
-
knowledgeDir: config.knowledgeDir,
|
|
231
|
-
}),
|
|
232
|
-
}
|
|
233
|
-
: {}),
|
|
252
|
+
// The extensions' tools (the knowledge base's, the sources').
|
|
253
|
+
...Object.assign({}, ...added.map((contribution) => contribution.mcpServers ?? {})),
|
|
234
254
|
// The date and date arithmetic, in every session and mode: only reads (tools/time.ts).
|
|
235
255
|
time: createTimeServer(config.timeZone),
|
|
236
256
|
},
|
|
237
257
|
hooks: {
|
|
238
258
|
PreToolUse: [
|
|
239
259
|
{ hooks: [transcriptLogger.preToolUse] },
|
|
240
|
-
...(
|
|
260
|
+
...(fileTools.length || toolOnlyDirs.length
|
|
241
261
|
? [{ hooks: [createFileScopeGate({
|
|
242
262
|
projectDir: config.projectDir,
|
|
243
263
|
writableDirs,
|
|
244
264
|
searchableDirs,
|
|
245
265
|
readOnlyDirs,
|
|
246
|
-
|
|
247
|
-
...(
|
|
266
|
+
// The SDK's own credentials, whatever the agent denies.
|
|
267
|
+
deniedPaths: [...(config.deniedPaths ?? []), path.join(claudeConfigDir(), ".credentials.json")],
|
|
268
|
+
// Read and Glob only in the agent's folders, plus what only the kit knows it
|
|
269
|
+
// needs: the run folder, the plugins, the project's .claude/ (when loaded) and
|
|
270
|
+
// the SDK's large tool results. Never the rest of the disk (~/.ssh, other
|
|
271
|
+
// projects' .env...), which an agent reading untrusted content could be talked into.
|
|
272
|
+
readableDirs: searchableDirs,
|
|
273
|
+
alsoReadable: [runDir, ...pluginRoots, ...((spec.settingSources ?? ["project"]).includes("project") ? [path.join(config.projectDir, ".claude")] : [])],
|
|
274
|
+
toolResultsRoot: claudeProjectDir(config.projectDir),
|
|
275
|
+
toolOnlyDirs,
|
|
248
276
|
}),
|
|
249
277
|
] }]
|
|
250
278
|
: []),
|
|
251
279
|
// Registered wherever the approval tool is, and asking only while the current mode is
|
|
252
280
|
// "interactive": a guided session can switch to interactive and back (ModeControl).
|
|
253
|
-
...(includeApprovalTool ? [{ hooks: [createStepGate(runDir, () => modeControl.mode === "interactive")] }] : []),
|
|
281
|
+
...(includeApprovalTool ? [{ hooks: [createStepGate(runDir, () => modeControl.mode === "interactive", fromExtensions((contribution) => contribution.selfAskingTools))] }] : []),
|
|
254
282
|
// Likewise for "plan", which any non-autonomous session can switch into: the agent
|
|
255
283
|
// only reads and plans while it's on (hooks/planGate.ts).
|
|
256
284
|
...(includeApprovalTool
|
|
@@ -259,6 +287,7 @@ export async function buildSessionOptions(config, runDir, spec, options = {}) {
|
|
|
259
287
|
hooks: [
|
|
260
288
|
createPlanGate({
|
|
261
289
|
projectDir: config.projectDir,
|
|
290
|
+
readOnlyTools: fromExtensions((contribution) => contribution.readOnlyTools),
|
|
262
291
|
...(spec.planMode?.isPlanFile ? { isPlanFile: (filePath) => spec.planMode.isPlanFile(filePath, config) } : {}),
|
|
263
292
|
...(spec.planMode?.isReadOnlyTool ? { isReadOnlyTool: spec.planMode.isReadOnlyTool } : {}),
|
|
264
293
|
}, () => modeControl.mode === "plan"),
|
|
@@ -277,18 +306,93 @@ export async function buildSessionOptions(config, runDir, spec, options = {}) {
|
|
|
277
306
|
PostToolUse: [{ hooks: [transcriptLogger.postToolUse] }],
|
|
278
307
|
},
|
|
279
308
|
};
|
|
280
|
-
|
|
309
|
+
// The extensions' own hooks (the memory hears the person's messages), after the kit's.
|
|
310
|
+
for (const contribution of added) {
|
|
311
|
+
for (const [event, matchers] of Object.entries(contribution.hooks ?? {})) {
|
|
312
|
+
sdkOptions.hooks[event] = [...(sdkOptions.hooks[event] ?? []), ...matchers];
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
const toolLabels = Object.assign({}, ...added.map((contribution) => contribution.toolLabels ?? {}));
|
|
316
|
+
const extensions = {
|
|
317
|
+
dirs: extensionDirs,
|
|
318
|
+
installed: await listInstalled(extensionDirs),
|
|
319
|
+
active: resolved.active.map(({ extension }) => extension.name),
|
|
320
|
+
about: Object.fromEntries(resolved.active.map(({ extension, manifest }) => [extension.name, { description: manifest.description, provides: manifest.provides }])),
|
|
321
|
+
inactive: resolved.inactive,
|
|
322
|
+
};
|
|
323
|
+
// What the session is, for the extensions that ask (sessionFacts.ts, #43).
|
|
324
|
+
late.view = createSessionView({
|
|
325
|
+
...(spec.identity ? { identity: spec.identity } : {}),
|
|
326
|
+
kitVersion: agentKitVersion(),
|
|
327
|
+
language,
|
|
328
|
+
extensions: {
|
|
329
|
+
active: resolved.active.map(({ extension, manifest }, index) => ({
|
|
330
|
+
name: extension.name,
|
|
331
|
+
description: manifest.description,
|
|
332
|
+
provides: manifest.provides,
|
|
333
|
+
servers: Object.keys(added[index]?.mcpServers ?? {}),
|
|
334
|
+
help: added[index]?.helpLines ?? [],
|
|
335
|
+
})),
|
|
336
|
+
inactive: resolved.inactive,
|
|
337
|
+
},
|
|
338
|
+
subagents: Object.entries(allAgents).map(([name, agent]) => ({ name, description: agent.description })),
|
|
339
|
+
skills: sdkOptions.skills === "all" ? await pluginSkills(pluginRoots) : [...(sdkOptions.skills ?? [])],
|
|
340
|
+
runDir,
|
|
341
|
+
}, modeControl);
|
|
342
|
+
registerSessionView(sdkOptions, late.view);
|
|
343
|
+
const apis = Object.fromEntries(resolved.active.flatMap(({ extension }, index) => (added[index]?.api ? [[extension.name, added[index].api]] : [])));
|
|
344
|
+
return { options: sdkOptions, transcriptLogger, transcriptPath, modeControl, language, toolLabels, extensions, apis, ...(knowledgeStore ? { knowledgeStore } : {}) };
|
|
281
345
|
}
|
|
282
346
|
export { createModeControl, togglePlanMode } from "./modeControl.js";
|
|
283
|
-
/** The
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
347
|
+
/** The Claude Code configuration folder the SDK's CLI uses: `CLAUDE_CONFIG_DIR`, or `~/.claude`. */
|
|
348
|
+
function claudeConfigDir() {
|
|
349
|
+
return process.env.CLAUDE_CONFIG_DIR || path.join(os.homedir(), ".claude");
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* The CLI's folder for a project, where it keeps each session's transcript and large tool
|
|
353
|
+
* results (`<session>/tool-results/`): the project's path with every character but letters
|
|
354
|
+
* and digits as "-" (confirmed empirically).
|
|
355
|
+
*/
|
|
356
|
+
export function claudeProjectDir(projectDir) {
|
|
357
|
+
return path.join(claudeConfigDir(), "projects", path.resolve(projectDir).replace(/[^a-zA-Z0-9]/g, "-"));
|
|
358
|
+
}
|
|
359
|
+
/** The built-in file tools, in the order the session lists them. */
|
|
360
|
+
const FILE_TOOLS = ["Read", "Write", "Edit", "Glob", "Grep"];
|
|
361
|
+
/**
|
|
362
|
+
* The skills of the plugins in `roots`, as the SDK names them: `<plugin>:<folder>`, the
|
|
363
|
+
* plugin's name from its `.claude-plugin/plugin.json` and the skill's folder under `skills/`,
|
|
364
|
+
* whatever its frontmatter's `name` says (confirmed empirically).
|
|
365
|
+
*/
|
|
366
|
+
export async function pluginSkills(roots) {
|
|
367
|
+
const names = [];
|
|
368
|
+
for (const root of roots) {
|
|
369
|
+
let plugin;
|
|
370
|
+
try {
|
|
371
|
+
plugin = JSON.parse(await readFile(path.join(root, ".claude-plugin", "plugin.json"), "utf8")).name;
|
|
372
|
+
}
|
|
373
|
+
catch {
|
|
374
|
+
continue; // not a plugin the SDK would load
|
|
375
|
+
}
|
|
376
|
+
if (!plugin)
|
|
377
|
+
continue;
|
|
378
|
+
const folders = await readdir(path.join(root, "skills"), { withFileTypes: true }).catch(() => []);
|
|
379
|
+
for (const folder of folders.filter((entry) => entry.isDirectory())) {
|
|
380
|
+
const hasSkill = await readFile(path.join(root, "skills", folder.name, "SKILL.md")).then(() => true, () => false);
|
|
381
|
+
if (hasSkill)
|
|
382
|
+
names.push(`${plugin}:${folder.name}`);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
return names;
|
|
386
|
+
}
|
|
387
|
+
function skillList(skills, added, leftOut) {
|
|
289
388
|
if (skills === undefined || skills === "all")
|
|
290
389
|
return "all";
|
|
291
|
-
return [...new Set([...skills, ...
|
|
390
|
+
return [...new Set([...skills, ...added])].filter((skill) => !leftOut.includes(skill));
|
|
391
|
+
}
|
|
392
|
+
/** The project's own skills (`<projectDir>/.claude/skills/<name>/SKILL.md`), by name. */
|
|
393
|
+
async function projectSkills(projectDir) {
|
|
394
|
+
const folders = await readdir(path.join(projectDir, ".claude", "skills"), { withFileTypes: true }).catch(() => []);
|
|
395
|
+
return folders.filter((entry) => entry.isDirectory()).map((entry) => entry.name);
|
|
292
396
|
}
|
|
293
397
|
/**
|
|
294
398
|
* User message queue backed by a single long-lived generator, for any multi-turn
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import type { Options, SlashCommand } from "@anthropic-ai/claude-agent-sdk";
|
|
2
|
+
import type { AgentIdentity, Mode } from "./agentSpec.js";
|
|
3
|
+
import type { Language } from "./language.js";
|
|
4
|
+
import type { ModeControl } from "./modeControl.js";
|
|
5
|
+
import type { ContextUsage } from "./runner.js";
|
|
6
|
+
/**
|
|
7
|
+
* What a session is, at the moment it's asked (`ExtensionContext.session()`, #43): the core's
|
|
8
|
+
* facts, read-only, never another extension's data. The mode follows Shift+Tab and plan mode;
|
|
9
|
+
* the tools, skills, commands and context come from the running session once it has started.
|
|
10
|
+
*/
|
|
11
|
+
export interface SessionFacts {
|
|
12
|
+
/** Who the agent is (`AgentSpec.identity`). */
|
|
13
|
+
identity?: AgentIdentity;
|
|
14
|
+
/** The agent-kit version it runs on. */
|
|
15
|
+
kitVersion: string;
|
|
16
|
+
/** The kit's language for this session. */
|
|
17
|
+
language: Language;
|
|
18
|
+
/** The mode now, and the ones the person can switch to (Shift+Tab). */
|
|
19
|
+
mode: Mode;
|
|
20
|
+
switchableModes: Mode[];
|
|
21
|
+
/** The extensions running (with their tools and what they say about themselves here), and the ones off, with why. */
|
|
22
|
+
extensions: {
|
|
23
|
+
active: {
|
|
24
|
+
name: string;
|
|
25
|
+
description: string;
|
|
26
|
+
provides: string[];
|
|
27
|
+
tools: string[];
|
|
28
|
+
help: string[];
|
|
29
|
+
}[];
|
|
30
|
+
inactive: {
|
|
31
|
+
name: string;
|
|
32
|
+
reason: string;
|
|
33
|
+
}[];
|
|
34
|
+
};
|
|
35
|
+
/** The subagents it can delegate to. */
|
|
36
|
+
subagents: {
|
|
37
|
+
name: string;
|
|
38
|
+
description: string;
|
|
39
|
+
}[];
|
|
40
|
+
/** Every tool of the session, as the model names it (empty until the session has started). */
|
|
41
|
+
tools: string[];
|
|
42
|
+
/** The skills offered (the running session's list once started; until then, the ones asked for). */
|
|
43
|
+
skills: string[];
|
|
44
|
+
/** The slash commands the person can type, with what each does (empty until the session has started). */
|
|
45
|
+
commands: {
|
|
46
|
+
name: string;
|
|
47
|
+
description: string;
|
|
48
|
+
argumentHint: string;
|
|
49
|
+
}[];
|
|
50
|
+
/** How full the context window is, when the session can tell. */
|
|
51
|
+
context?: ContextUsage;
|
|
52
|
+
/** This run's folder. */
|
|
53
|
+
runDir: string;
|
|
54
|
+
}
|
|
55
|
+
/** What the running session tells the view: `runQuery()` attaches it on its own. */
|
|
56
|
+
export interface SessionLive {
|
|
57
|
+
tools?: string[];
|
|
58
|
+
skills?: string[];
|
|
59
|
+
supportedCommands?: () => Promise<SlashCommand[]>;
|
|
60
|
+
contextUsage?: () => Promise<ContextUsage | null>;
|
|
61
|
+
}
|
|
62
|
+
/** The facts known when the options are built: everything but the mode and what only the running session knows. */
|
|
63
|
+
export type StaticSessionFacts = Omit<SessionFacts, "mode" | "switchableModes" | "tools" | "commands" | "context" | "extensions"> & {
|
|
64
|
+
extensions: {
|
|
65
|
+
active: (Omit<SessionFacts["extensions"]["active"][number], "tools"> & {
|
|
66
|
+
servers: string[];
|
|
67
|
+
})[];
|
|
68
|
+
inactive: SessionFacts["extensions"]["inactive"];
|
|
69
|
+
};
|
|
70
|
+
};
|
|
71
|
+
export interface SessionView {
|
|
72
|
+
facts(): Promise<SessionFacts>;
|
|
73
|
+
attach(live: SessionLive): void;
|
|
74
|
+
}
|
|
75
|
+
/** The view of a session: its static facts, the mode read from `modeControl` on every call, and what the running session attaches. */
|
|
76
|
+
export declare function createSessionView(base: StaticSessionFacts, modeControl: ModeControl): SessionView;
|
|
77
|
+
/** Ties a view to the options `buildSessionOptions()` returns. */
|
|
78
|
+
export declare function registerSessionView(options: Options, view: SessionView): void;
|
|
79
|
+
/** The view tied to these options (or a spread copy of them), if `buildSessionOptions()` built them. */
|
|
80
|
+
export declare function sessionViewOf(options: Options): SessionView | undefined;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// A live call made from inside a tool, mid-turn: never let it hold the tool up.
|
|
2
|
+
const LIVE_TIMEOUT_MS = 3000;
|
|
3
|
+
const within = (promise, fallback) => Promise.race([promise.catch(() => fallback), new Promise((resolve) => setTimeout(() => resolve(fallback), LIVE_TIMEOUT_MS).unref())]);
|
|
4
|
+
/** The view of a session: its static facts, the mode read from `modeControl` on every call, and what the running session attaches. */
|
|
5
|
+
export function createSessionView(base, modeControl) {
|
|
6
|
+
let live = {};
|
|
7
|
+
return {
|
|
8
|
+
attach(more) {
|
|
9
|
+
live = { ...live, ...more };
|
|
10
|
+
},
|
|
11
|
+
async facts() {
|
|
12
|
+
const tools = live.tools ?? [];
|
|
13
|
+
const [commands, context] = await Promise.all([
|
|
14
|
+
live.supportedCommands ? within(live.supportedCommands(), []) : Promise.resolve([]),
|
|
15
|
+
live.contextUsage ? within(live.contextUsage(), null) : Promise.resolve(null),
|
|
16
|
+
]);
|
|
17
|
+
return {
|
|
18
|
+
...base,
|
|
19
|
+
mode: modeControl.mode,
|
|
20
|
+
switchableModes: [...modeControl.switchable],
|
|
21
|
+
extensions: {
|
|
22
|
+
active: base.extensions.active.map(({ servers, ...extension }) => ({
|
|
23
|
+
...extension,
|
|
24
|
+
// The running session's names when it has started; until then, each server's wildcard.
|
|
25
|
+
tools: tools.length ? tools.filter((tool) => servers.some((server) => tool.startsWith(`mcp__${server}__`))) : servers.map((server) => `mcp__${server}__*`),
|
|
26
|
+
})),
|
|
27
|
+
inactive: base.extensions.inactive,
|
|
28
|
+
},
|
|
29
|
+
tools,
|
|
30
|
+
skills: live.skills ?? base.skills,
|
|
31
|
+
commands: commands.map(({ name, description, argumentHint }) => ({ name, description, argumentHint })),
|
|
32
|
+
...(context ? { context } : {}),
|
|
33
|
+
};
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
// The view rides on the options under a symbol, for runQuery() to attach the running session to
|
|
38
|
+
// without the host passing anything: a spread (`{ ...options, more }`) keeps it, the SDK ignores
|
|
39
|
+
// it, and options built some other way just leave the live facts out.
|
|
40
|
+
const SESSION_VIEW = Symbol.for("agent-kit.sessionView");
|
|
41
|
+
/** Ties a view to the options `buildSessionOptions()` returns. */
|
|
42
|
+
export function registerSessionView(options, view) {
|
|
43
|
+
Object.defineProperty(options, SESSION_VIEW, { value: view, enumerable: true });
|
|
44
|
+
}
|
|
45
|
+
/** The view tied to these options (or a spread copy of them), if `buildSessionOptions()` built them. */
|
|
46
|
+
export function sessionViewOf(options) {
|
|
47
|
+
return options[SESSION_VIEW];
|
|
48
|
+
}
|
|
@@ -1,12 +1,13 @@
|
|
|
1
|
+
import { type ToolPhrase } from "./messages/index.js";
|
|
1
2
|
/**
|
|
2
3
|
* Turns a raw tool name + input into a short, readable console description, instead of
|
|
3
4
|
* the tool's technical name (e.g. "mcp__playwright__browser_click"). Covers the tools
|
|
4
5
|
* this kit itself provides (Read/Write/Edit/Glob/Grep/Bash/Agent/WebFetch/WebSearch/Skill,
|
|
5
|
-
* plus its own human-approval/manual-intervention/
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
6
|
+
* plus its own human-approval/manual-intervention/clock MCP tools) generically; an extension
|
|
7
|
+
* brings its tools' labels with its contribution (`ToolLabels`, ADR-025), and a concrete agent
|
|
8
|
+
* supplies a `describe` callback for its own domain-specific tools (e.g. Playwright's
|
|
9
|
+
* browser_* cases) via `createFriendlyToolLabel()`. The kit's own labels are in the current
|
|
10
|
+
* language (see messages/).
|
|
10
11
|
*/
|
|
11
12
|
/** Cuts `text` to `max` characters, with "…" at the end. */
|
|
12
13
|
export declare function truncate(text: string, max?: number): string;
|
|
@@ -15,13 +16,30 @@ export declare function truncate(text: string, max?: number): string;
|
|
|
15
16
|
* the tail off long freeform text), a path's most useful part, the file name, is at the end.
|
|
16
17
|
*/
|
|
17
18
|
export declare function truncatePath(text: string, max?: number): string;
|
|
19
|
+
/** How the chat shows one tool: an extension's, by its full name (`ExtensionContribution.toolLabels`). */
|
|
20
|
+
export interface ToolLabel {
|
|
21
|
+
/** Its line in the chat, from its input, in the kit's language: "Reading concept/llm-wiki". */
|
|
22
|
+
label(input: Record<string, unknown>): string;
|
|
23
|
+
/** How it counts in a folded group's summary, `[one, many]` with `{n}`: `["read {n} page", "read {n} pages"]`. */
|
|
24
|
+
phrase?: ToolPhrase;
|
|
25
|
+
}
|
|
26
|
+
/** Tools' labels by full name (`mcp__<server>__<tool>`): what the extensions of a session bring. */
|
|
27
|
+
export type ToolLabels = Readonly<Record<string, ToolLabel>>;
|
|
28
|
+
/**
|
|
29
|
+
* A `formatAction` that shows the tools in `labels()` with their label, and any other with
|
|
30
|
+
* `formatAction` (the kit's by default). `labels` is read on every call, so it can follow the
|
|
31
|
+
* session in use (`/resume` opens another).
|
|
32
|
+
*/
|
|
33
|
+
export declare function withToolLabels(labels: () => ToolLabels | undefined, formatAction?: (toolName: string, toolInput: unknown) => string): (toolName: string, toolInput: unknown) => string;
|
|
34
|
+
/** A `toolPhrase` that counts the tools in `labels()` with their phrase, and any other with `toolPhrase`. */
|
|
35
|
+
export declare function withToolPhrases(labels: () => ToolLabels | undefined, toolPhrase?: (toolName: string) => ToolPhrase | undefined): (toolName: string) => ToolPhrase | undefined;
|
|
18
36
|
/** An agent's labels for its own tools: the tool's short name and input, to a label, or `undefined` for the kit's default. */
|
|
19
37
|
export type ToolDescriber = (shortName: string, input: Record<string, unknown>) => string | undefined;
|
|
20
38
|
/**
|
|
21
39
|
* Builds a `friendlyToolLabel(toolName, toolInput)` — `describe` lets the host agent
|
|
22
40
|
* layer its own domain-specific cases (e.g. Playwright's browser_* tools) on top of this
|
|
23
41
|
* kit's generic ones; `extraLocalServers` names any *additional* MCP server (beyond this
|
|
24
|
-
* kit's own "approvals"/"manualLogin"/"
|
|
42
|
+
* kit's own "approvals"/"manualLogin"/"time") whose tools should be unwrapped
|
|
25
43
|
* without a "[server] " prefix, e.g. "playwright".
|
|
26
44
|
*/
|
|
27
45
|
export declare function createFriendlyToolLabel(options?: {
|