@falkenslab/agent-kit 0.18.0 → 0.20.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.
Files changed (102) hide show
  1. package/assets/extension-launcher.mjs +24 -0
  2. package/dist/chat/chatController.d.ts +267 -0
  3. package/dist/chat/chatController.js +536 -0
  4. package/dist/chat/extensions.d.ts +18 -0
  5. package/dist/chat/extensions.js +44 -0
  6. package/dist/chat/history.d.ts +17 -0
  7. package/dist/chat/history.js +39 -0
  8. package/dist/{tui → chat}/runs.d.ts +11 -1
  9. package/dist/{tui → chat}/runs.js +1 -1
  10. package/dist/core/agentSpec.d.ts +28 -50
  11. package/dist/core/extensions.d.ts +128 -0
  12. package/dist/core/extensions.js +87 -0
  13. package/dist/core/externalExtensions.d.ts +143 -0
  14. package/dist/core/externalExtensions.js +368 -0
  15. package/dist/core/hooks/planGate.d.ts +2 -0
  16. package/dist/core/hooks/planGate.js +1 -10
  17. package/dist/core/hooks/stepGate.d.ts +1 -1
  18. package/dist/core/hooks/stepGate.js +3 -3
  19. package/dist/core/liveExtensions.d.ts +51 -0
  20. package/dist/core/liveExtensions.js +70 -0
  21. package/dist/core/marketplaces.d.ts +118 -0
  22. package/dist/core/marketplaces.js +242 -0
  23. package/dist/core/messages/de.js +12 -32
  24. package/dist/core/messages/en.d.ts +15 -16
  25. package/dist/core/messages/en.js +12 -32
  26. package/dist/core/messages/es.js +12 -32
  27. package/dist/core/messages/fr.js +12 -32
  28. package/dist/core/messages/index.d.ts +6 -0
  29. package/dist/core/messages/index.js +12 -1
  30. package/dist/core/packaged.d.ts +10 -0
  31. package/dist/core/packaged.js +38 -0
  32. package/dist/core/pluginAgents.d.ts +20 -0
  33. package/dist/core/pluginAgents.js +76 -0
  34. package/dist/core/pluginDownloads.d.ts +14 -0
  35. package/dist/core/pluginDownloads.js +141 -0
  36. package/dist/core/runner.js +21 -9
  37. package/dist/core/session.d.ts +38 -6
  38. package/dist/core/session.js +200 -99
  39. package/dist/core/sessionFacts.d.ts +85 -0
  40. package/dist/core/sessionFacts.js +49 -0
  41. package/dist/core/toolLabels.d.ts +24 -6
  42. package/dist/core/toolLabels.js +24 -39
  43. package/dist/extensions/awareness/index.d.ts +22 -0
  44. package/dist/extensions/awareness/index.js +42 -0
  45. package/dist/extensions/awareness/labels.d.ts +3 -0
  46. package/dist/extensions/awareness/labels.js +12 -0
  47. package/dist/extensions/awareness/tools.d.ts +5 -0
  48. package/dist/extensions/awareness/tools.js +54 -0
  49. package/dist/extensions/knowledge/index.d.ts +22 -0
  50. package/dist/extensions/knowledge/index.js +38 -0
  51. package/dist/{core → extensions/knowledge}/knowledgeStore.d.ts +2 -1
  52. package/dist/extensions/knowledge/labels.d.ts +3 -0
  53. package/dist/extensions/knowledge/labels.js +119 -0
  54. package/dist/{core/knowledge.d.ts → extensions/knowledge/prompt.d.ts} +4 -4
  55. package/dist/{core/knowledge.js → extensions/knowledge/prompt.js} +10 -7
  56. package/dist/{core/tools/knowledgeTools.d.ts → extensions/knowledge/tools.d.ts} +1 -1
  57. package/dist/{core/tools/knowledgeTools.js → extensions/knowledge/tools.js} +2 -2
  58. package/dist/extensions/memory/index.d.ts +21 -0
  59. package/dist/extensions/memory/index.js +59 -0
  60. package/dist/extensions/memory/labels.d.ts +3 -0
  61. package/dist/extensions/memory/labels.js +58 -0
  62. package/dist/extensions/memory/memoryStore.d.ts +53 -0
  63. package/dist/extensions/memory/memoryStore.js +88 -0
  64. package/dist/extensions/memory/tools.d.ts +9 -0
  65. package/dist/extensions/memory/tools.js +82 -0
  66. package/dist/extensions/sources/index.d.ts +21 -0
  67. package/dist/extensions/sources/index.js +42 -0
  68. package/dist/extensions/sources/labels.d.ts +3 -0
  69. package/dist/extensions/sources/labels.js +82 -0
  70. package/dist/{core/tools/saveToSources.d.ts → extensions/sources/tools.d.ts} +1 -1
  71. package/dist/{core/tools/saveToSources.js → extensions/sources/tools.js} +5 -5
  72. package/dist/index.d.ts +19 -9
  73. package/dist/index.js +16 -7
  74. package/dist/tui/chatTui.d.ts +11 -20
  75. package/dist/tui/chatTui.js +75 -230
  76. package/dist/tui/extensionCommand.d.ts +30 -0
  77. package/dist/tui/extensionCommand.js +257 -0
  78. package/dist/tui/ink/runChatInk.d.ts +4 -3
  79. package/dist/tui/ink/runChatInk.js +101 -217
  80. package/extensions/awareness/.claude-plugin/plugin.json +10 -0
  81. package/{assets/agent-help → extensions/awareness/skills/help}/SKILL.md +4 -3
  82. package/{assets/knowledge-plugin → extensions/knowledge}/.claude-plugin/plugin.json +7 -1
  83. package/extensions/memory/.claude-plugin/plugin.json +10 -0
  84. package/extensions/memory/commands/forget.md +8 -0
  85. package/extensions/memory/commands/list.md +5 -0
  86. package/extensions/sources/.claude-plugin/plugin.json +10 -0
  87. package/package.json +3 -2
  88. package/dist/core/agentHelp.d.ts +0 -30
  89. package/dist/core/agentHelp.js +0 -55
  90. /package/dist/{core → extensions/knowledge}/fileKnowledgeStore.d.ts +0 -0
  91. /package/dist/{core → extensions/knowledge}/fileKnowledgeStore.js +0 -0
  92. /package/dist/{core → extensions/knowledge}/knowledgeStore.js +0 -0
  93. /package/dist/{core → extensions/sources}/extractText.d.ts +0 -0
  94. /package/dist/{core → extensions/sources}/extractText.js +0 -0
  95. /package/dist/{core → extensions/sources}/sources.d.ts +0 -0
  96. /package/dist/{core → extensions/sources}/sources.js +0 -0
  97. /package/{assets/knowledge-plugin → extensions/knowledge}/commands/ingest.md +0 -0
  98. /package/{assets/knowledge-plugin → extensions/knowledge}/commands/lint.md +0 -0
  99. /package/{assets/knowledge-plugin → extensions/knowledge}/commands/query.md +0 -0
  100. /package/{assets/knowledge-plugin → extensions/knowledge}/skills/knowledge-ingest/SKILL.md +0 -0
  101. /package/{assets/knowledge-plugin → extensions/knowledge}/skills/knowledge-lint/SKILL.md +0 -0
  102. /package/{assets/knowledge-plugin → extensions/knowledge}/skills/knowledge-query/SKILL.md +0 -0
@@ -0,0 +1,257 @@
1
+ import { stat } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { createInterface } from "node:readline/promises";
4
+ import { addMarketplace, findPlugin, inspectMarketplace, installFromMarketplace, listMarketplaces, removeMarketplace, updateMarketplace, } from "../core/marketplaces.js";
5
+ import { addExtension, isGitSource, listInstalled, removeExtension, setExtensionEnabled, } from "../core/externalExtensions.js";
6
+ import { describe } from "../chat/extensions.js";
7
+ export { chatExtensionsCommand, describe } from "../chat/extensions.js";
8
+ /**
9
+ * The `extension` command an agent exposes in its own binary (#37), e.g. `captain extension add
10
+ * ./jokebook --project`: installs, removes, enables, disables and lists the extensions in its two
11
+ * scopes. Returns `false` when `argv` isn't this command (so the agent goes on), and sets
12
+ * `process.exitCode` to 1 on an error.
13
+ *
14
+ * - `extension list`
15
+ * - `extension info <name>`: its metadata, what it offers and where its README is
16
+ * - `extension add <folder | git URL[#ref] | plugin[@marketplace]> [--project] [--path <subfolder>] [--yes]`
17
+ * - `extension remove|enable|disable <name> [--project | --agent]`
18
+ * - `extension search [words]`: what the known marketplaces offer
19
+ * - `extension marketplace add <folder | git URL[#ref] | owner/repo> [--yes]`, `list`, `update [name]`, `remove <name>`
20
+ *
21
+ * Without a scope, `add` installs into the agent's (the project's when the agent has no scope of
22
+ * its own); the others act where the extension is, the project's first. The marketplaces live in
23
+ * the same folder. `official` is the agent's own marketplace (a folder or git URL): known without
24
+ * asking, and installed from without a confirmation. Any other is added after a warning and
25
+ * typing its name, and installing from it asks first; `--yes` answers for the person (a script).
26
+ */
27
+ export async function runExtensionCommand(argv, options) {
28
+ if (argv[0] !== "extension")
29
+ return false;
30
+ const write = options.write ?? ((line) => console.log(line));
31
+ const command = options.command ?? "agent";
32
+ const [action, ...rest] = argv.slice(1);
33
+ const flag = (name) => rest.includes(`--${name}`);
34
+ const value = (name) => {
35
+ const index = rest.indexOf(`--${name}`);
36
+ return index >= 0 ? rest[index + 1] : undefined;
37
+ };
38
+ const positional = rest.filter((arg, index) => !arg.startsWith("--") && rest[index - 1] !== "--path");
39
+ const askedScope = flag("project") ? "project" : flag("agent") ? "agent" : undefined;
40
+ const fail = (message) => {
41
+ write(message);
42
+ process.exitCode = 1;
43
+ return true;
44
+ };
45
+ const scopeDir = (scope) => options.dirs[scope];
46
+ // The marketplaces live with the agent's extensions (the project's when it has no scope of its own).
47
+ const marketsDir = options.dirs.agent ?? options.dirs.project;
48
+ const ask = options.ask ?? askOnTerminal;
49
+ const confirmed = async (question, accepts) => flag("yes") || accepts((await ask(question)).trim());
50
+ // The agent's own marketplace, known without asking; a folder is copied afresh each time (it's the agent's own code).
51
+ const knownMarketplaces = async () => {
52
+ if (!marketsDir)
53
+ throw new Error("This agent has no folder for extensions.");
54
+ const known = await listMarketplaces(marketsDir);
55
+ const official = options.official;
56
+ if (official && !(isGitSource(official) && known.some((marketplace) => marketplace.official))) {
57
+ await addMarketplace(official, marketsDir, { official: true });
58
+ return listMarketplaces(marketsDir);
59
+ }
60
+ return known;
61
+ };
62
+ try {
63
+ switch (action) {
64
+ case "list": {
65
+ const installed = await listInstalled(options.dirs);
66
+ if (!installed.length) {
67
+ write(`No extensions installed. \`${command} extension add <folder | git URL>\` installs one.`);
68
+ return true;
69
+ }
70
+ for (const extension of installed)
71
+ write(describe(extension));
72
+ return true;
73
+ }
74
+ case "info": {
75
+ const name = positional[0];
76
+ if (!name)
77
+ return fail(`Usage: ${command} extension info <name>`);
78
+ const found = (await listInstalled(options.dirs)).find((extension) => extension.name === name && !extension.shadowed);
79
+ if (!found)
80
+ return fail(`${name} isn't installed. \`${command} extension list\` lists them.`);
81
+ for (const line of await info(found))
82
+ write(line);
83
+ return true;
84
+ }
85
+ case "add": {
86
+ const source = positional[0];
87
+ if (!source)
88
+ return fail(`Usage: ${command} extension add <folder | git URL[#ref] | plugin[@marketplace]> [--project] [--path <subfolder>]`);
89
+ const scope = askedScope ?? (options.dirs.agent ? "agent" : "project");
90
+ const dir = scopeDir(scope);
91
+ if (!dir)
92
+ return fail(`This agent has no ${scope} scope for extensions.`);
93
+ // A plugin of a marketplace, unless it's a folder here or a git URL.
94
+ const isFolder = await stat(path.resolve(source)).then((found) => found.isDirectory(), () => false);
95
+ if (!isFolder && !isGitSource(source) && /^[A-Za-z0-9][\w.-]*(@[A-Za-z0-9][\w.-]*)?$/.test(source)) {
96
+ await knownMarketplaces();
97
+ const { marketplace, plugin } = await findPlugin(marketsDir, source);
98
+ if (!marketplace.official) {
99
+ write(`${plugin.name}${plugin.version ? ` ${plugin.version}` : ""}, from the marketplace ${marketplace.name} (${marketplace.source}): ${plugin.description ?? "no description"}`);
100
+ write("It will run on this computer with your permissions.");
101
+ if (!(await confirmed("Install it? (y/N) ", (typed) => /^(y|yes|s|si|sí)$/i.test(typed))))
102
+ return fail("Not installed.");
103
+ }
104
+ const result = await installFromMarketplace(marketsDir, source, dir);
105
+ write(`${result.replaced ? "Reinstalled" : "Installed"} ${result.name} from ${result.marketplace} in the ${scope} scope (${dir})${result.commit ? `, at ${result.commit.slice(0, 12)}` : ""}. It's enabled: it runs from the next session.`);
106
+ return true;
107
+ }
108
+ const subdir = value("path");
109
+ const result = await addExtension(source, dir, subdir ? { subdir } : {});
110
+ write(`${result.replaced ? "Reinstalled" : "Installed"} ${result.name} in the ${scope} scope (${dir})${result.commit ? `, at ${result.commit.slice(0, 12)}` : ""}. It's enabled: it runs from the next session.`);
111
+ return true;
112
+ }
113
+ case "remove":
114
+ case "enable":
115
+ case "disable": {
116
+ const name = positional[0];
117
+ if (!name)
118
+ return fail(`Usage: ${command} extension ${action} <name> [--project | --agent]`);
119
+ const found = (await listInstalled(options.dirs)).filter((extension) => extension.name === name && (!askedScope || extension.scope === askedScope));
120
+ if (!found.length)
121
+ return fail(`${name} isn't installed${askedScope ? ` in the ${askedScope} scope` : ""}. \`${command} extension list\` lists them.`);
122
+ if (found.length > 1 && !askedScope)
123
+ return fail(`${name} is installed in both scopes: say which, with --project or --agent.`);
124
+ const { scope } = found[0];
125
+ const dir = scopeDir(scope);
126
+ if (action === "remove")
127
+ await removeExtension(dir, name);
128
+ else
129
+ await setExtensionEnabled(dir, name, action === "enable");
130
+ write(`${name} ${action === "remove" ? "removed" : `${action}d`} in the ${scope} scope. It applies from the next session (in the chat, \`/extensions\` reopens it).`);
131
+ return true;
132
+ }
133
+ case "search": {
134
+ const words = positional.map((word) => word.toLowerCase());
135
+ const installed = new Set((await listInstalled(options.dirs)).map((extension) => extension.name));
136
+ const offered = (await knownMarketplaces()).flatMap((marketplace) => (marketplace.manifest?.plugins ?? []).map((plugin) => ({ marketplace, plugin })));
137
+ const matching = offered.filter(({ plugin }) => {
138
+ const text = [plugin.name, plugin.description, plugin.category, ...(plugin.tags ?? []), ...(plugin.keywords ?? [])].join(" ").toLowerCase();
139
+ return words.every((word) => text.includes(word));
140
+ });
141
+ if (!matching.length) {
142
+ write(offered.length ? "No extension matches." : `No marketplace offers extensions yet. \`${command} extension marketplace add <folder | git URL | owner/repo>\` adds one.`);
143
+ return true;
144
+ }
145
+ for (const { marketplace, plugin } of matching) {
146
+ write(`${plugin.name}@${marketplace.name}${plugin.version ? ` ${plugin.version}` : ""}${installed.has(plugin.name) ? " (installed)" : ""}${plugin.description ? ` ${plugin.description}` : ""}`);
147
+ }
148
+ return true;
149
+ }
150
+ case "marketplace":
151
+ return await marketplaceCommand();
152
+ default:
153
+ return fail([
154
+ `Usage: ${command} extension <action>`,
155
+ " list what's installed, in which scope, on or off",
156
+ " info <name> its metadata, what it offers, its README",
157
+ " add <folder | git URL[#ref] | plugin[@marketplace]> [--project] [--path <subfolder>] [--yes]",
158
+ " remove|enable|disable <name> [--project | --agent]",
159
+ " search [words] what the known marketplaces offer",
160
+ " marketplace add <folder | git URL[#ref] | owner/repo> [--yes]",
161
+ " marketplace list | update [name] | remove <name>",
162
+ ].join("\n"));
163
+ }
164
+ }
165
+ catch (error) {
166
+ return fail(error instanceof Error ? error.message : String(error));
167
+ }
168
+ /** `extension marketplace <add | list | update | remove>`. */
169
+ async function marketplaceCommand() {
170
+ const [what, name] = positional;
171
+ switch (what) {
172
+ case "add": {
173
+ if (!name)
174
+ return fail(`Usage: ${command} extension marketplace add <folder | git URL[#ref] | owner/repo> [--yes]`);
175
+ await knownMarketplaces();
176
+ const manifest = await inspectMarketplace(name);
177
+ write(`The marketplace ${manifest.name}, by ${manifest.owner.name}${manifest.description ? `: ${manifest.description}` : ""} (${manifest.plugins.length} extension${manifest.plugins.length === 1 ? "" : "s"}).`);
178
+ write("Its extensions run on this computer with your permissions, and it isn't this agent's own: add it only if you trust who publishes it.");
179
+ if (!(await confirmed(`Type its name (${manifest.name}) to add it: `, (typed) => typed === manifest.name)))
180
+ return fail("Not added.");
181
+ const result = await addMarketplace(name, marketsDir);
182
+ write(`${result.replaced ? "Updated" : "Added"} the marketplace ${result.name}${result.commit ? `, at ${result.commit.slice(0, 12)}` : ""}. \`${command} extension search\` lists what it offers.`);
183
+ return true;
184
+ }
185
+ case "list": {
186
+ const known = await knownMarketplaces();
187
+ if (!known.length) {
188
+ write(`No marketplaces. \`${command} extension marketplace add <folder | git URL | owner/repo>\` adds one.`);
189
+ return true;
190
+ }
191
+ for (const marketplace of known) {
192
+ write(`${marketplace.name}${marketplace.official ? " (official)" : ""} ${marketplace.manifest ? `${marketplace.manifest.plugins.length} extension${marketplace.manifest.plugins.length === 1 ? "" : "s"}` : "can't be read"} from ${marketplace.source}${marketplace.commit ? ` at ${marketplace.commit.slice(0, 12)}` : ""}, updated ${marketplace.updated.slice(0, 10)}`);
193
+ }
194
+ return true;
195
+ }
196
+ case "update": {
197
+ const known = await knownMarketplaces();
198
+ const chosen = name ? known.filter((marketplace) => marketplace.name === name) : known;
199
+ if (name && !chosen.length)
200
+ return fail(`No marketplace ${name}.`);
201
+ for (const marketplace of chosen) {
202
+ const { commit } = await updateMarketplace(marketsDir, marketplace.name);
203
+ write(`Updated ${marketplace.name}${commit ? `, at ${commit.slice(0, 12)}` : ""}. Its installed extensions stay as they are: \`${command} extension add <name>@${marketplace.name}\` installs one again.`);
204
+ }
205
+ return true;
206
+ }
207
+ case "remove": {
208
+ if (!name)
209
+ return fail(`Usage: ${command} extension marketplace remove <name>`);
210
+ if (!marketsDir || !(await removeMarketplace(marketsDir, name)))
211
+ return fail(`No marketplace ${name}.`);
212
+ write(`Removed the marketplace ${name}. The extensions installed from it stay.`);
213
+ return true;
214
+ }
215
+ default:
216
+ return fail(`Usage: ${command} extension marketplace <add | list | update | remove>`);
217
+ }
218
+ }
219
+ }
220
+ /** A question on the terminal, or no answer without one. */
221
+ async function askOnTerminal(question) {
222
+ if (!process.stdin.isTTY)
223
+ return "";
224
+ const terminal = createInterface({ input: process.stdin, output: process.stdout });
225
+ try {
226
+ return await terminal.question(question);
227
+ }
228
+ finally {
229
+ terminal.close();
230
+ }
231
+ }
232
+ /** Everything an installed extension's manifest says about it, for `extension info`. */
233
+ async function info(extension) {
234
+ const manifest = extension.manifest;
235
+ if (!manifest)
236
+ return [describe(extension), "Its manifest can't be read."];
237
+ const author = manifest.author ? [manifest.author.name, manifest.author.email && `<${manifest.author.email}>`, manifest.author.url && `(${manifest.author.url})`].filter(Boolean).join(" ") : undefined;
238
+ const readme = path.join(extension.dir, "README.md");
239
+ const field = (label, value) => (value ? [`${label.padEnd(13)}${value}`] : []);
240
+ return [
241
+ `${manifest.name}${manifest.version ? ` ${manifest.version}` : ""}: ${manifest.description}`,
242
+ ...field("Author", author),
243
+ ...field("License", manifest.license),
244
+ ...field("Homepage", manifest.homepage),
245
+ ...field("Repository", manifest.repository),
246
+ ...field("Keywords", manifest.keywords?.join(", ")),
247
+ ...field("Works with", manifest.kit && `agent-kit ${manifest.kit}`),
248
+ ...field("Provides", manifest.provides.join(", ") || undefined),
249
+ ...field("Requires", manifest.requires.join(", ") || undefined),
250
+ ...field("Needs", manifest.needs.join(", ") || undefined),
251
+ ...field("Servers", Object.keys(manifest.servers).join(", ") || undefined),
252
+ ...field("Only read", manifest.readOnlyTools.join(", ") || undefined),
253
+ ...field("Variables", Object.values(manifest.servers).flatMap((server) => Object.keys(server.env ?? {})).join(", ") || undefined),
254
+ ...field("Installed", `${extension.scope} scope, ${extension.lock.enabled ? "enabled" : "disabled"}, on ${extension.lock.installed}, from ${describe(extension).split(" from ")[1]}`),
255
+ ...field("README", (await stat(readme).catch(() => null))?.isFile() ? readme : undefined),
256
+ ];
257
+ }
@@ -1,8 +1,8 @@
1
1
  import type { Options } from "@anthropic-ai/claude-agent-sdk";
2
2
  import type { Mode } from "../../core/agentSpec.js";
3
- import { type ModeControl } from "../../core/session.js";
3
+ import type { ModeControl } from "../../core/session.js";
4
4
  import { type ChatTuiOptions } from "../chatTui.js";
5
- import { type SessionOpener } from "../runs.js";
5
+ import { type SessionOpener } from "../../chat/runs.js";
6
6
  import { type RenderApproval } from "./SessionView.js";
7
7
  import type { ResultFormatter, ToolDetail, ToolPhrase } from "./toolGroup.js";
8
8
  import { type HeaderInfo } from "./header.js";
@@ -74,12 +74,13 @@ export interface InkChatOptions extends ChatTuiOptions {
74
74
  */
75
75
  firstPromptSuggestion?: string;
76
76
  }
77
- /** The chat's own commands, never sent to the model (the agent-help skill lists them; a test keeps both in step). */
77
+ /** The chat's own commands, never sent to the model (the awareness extension's help skill lists them; a test keeps both in step). */
78
78
  export declare const DEFAULT_EXIT_COMMANDS: readonly string[];
79
79
  export declare const LOCAL_COMMANDS: {
80
80
  readonly copy: "/copy";
81
81
  readonly resume: "/resume";
82
82
  readonly plan: "/plan";
83
+ readonly extensions: "/extensions";
83
84
  };
84
85
  /** A choice asked in place of the prompt (the /resume list). */
85
86
  export interface ChatPicker {