@falkenslab/agent-kit 0.21.0 → 0.23.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/README.md +1 -4
- package/assets/extension-launcher.mjs +28 -5
- package/dist/chat/chatController.d.ts +60 -0
- package/dist/chat/chatController.js +212 -2
- package/dist/chat/extensions.d.ts +36 -2
- package/dist/chat/extensions.js +87 -3
- package/dist/chat/runs.d.ts +3 -1
- package/dist/chat/runs.js +1 -1
- package/dist/core/agentSpec.d.ts +11 -0
- package/dist/core/elicitation.d.ts +54 -0
- package/dist/core/elicitation.js +120 -0
- package/dist/core/extensionCatalog.d.ts +103 -0
- package/dist/core/extensionCatalog.js +235 -0
- package/dist/core/extensionOptions.d.ts +56 -0
- package/dist/core/extensionOptions.js +139 -0
- package/dist/core/extensionRequirements.d.ts +66 -0
- package/dist/core/extensionRequirements.js +112 -0
- package/dist/core/extensions.d.ts +75 -2
- package/dist/core/extensions.js +78 -6
- package/dist/core/externalExtensions.d.ts +101 -11
- package/dist/core/externalExtensions.js +263 -51
- package/dist/core/hooks/planGate.d.ts +2 -0
- package/dist/core/hooks/planGate.js +3 -0
- package/dist/core/interaction.d.ts +7 -0
- package/dist/core/liveExtensions.d.ts +15 -0
- package/dist/core/liveExtensions.js +33 -2
- package/dist/core/marketplaces.d.ts +7 -2
- package/dist/core/marketplaces.js +6 -4
- package/dist/core/messages/de.js +37 -1
- package/dist/core/messages/en.d.ts +44 -0
- package/dist/core/messages/en.js +37 -1
- package/dist/core/messages/es.js +37 -1
- package/dist/core/messages/fr.js +37 -1
- package/dist/core/npxPackages.d.ts +48 -0
- package/dist/core/npxPackages.js +121 -0
- package/dist/core/pluginDownloads.d.ts +2 -1
- package/dist/core/pluginDownloads.js +2 -2
- package/dist/core/readOnlyHints.d.ts +13 -0
- package/dist/core/readOnlyHints.js +26 -0
- package/dist/core/runner.js +2 -0
- package/dist/core/session.d.ts +17 -5
- package/dist/core/session.js +44 -11
- package/dist/core/sessionFacts.d.ts +11 -1
- package/dist/core/workspaceTrust.d.ts +12 -0
- package/dist/core/workspaceTrust.js +60 -0
- package/dist/extensions/awareness/tools.js +6 -2
- package/dist/index.d.ts +9 -2
- package/dist/index.js +7 -2
- package/dist/tui/extensionCommand.d.ts +10 -2
- package/dist/tui/extensionCommand.js +199 -34
- package/extensions/awareness/.claude-plugin/plugin.json +13 -5
- package/extensions/knowledge/.claude-plugin/plugin.json +13 -5
- package/extensions/memory/.claude-plugin/plugin.json +13 -5
- package/extensions/sources/.claude-plugin/plugin.json +13 -5
- package/package.json +7 -5
- package/schemas/agent-kit-metadata.schema.json +120 -0
- package/schemas/marketplace-catalog.schema.json +74 -0
package/README.md
CHANGED
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
<h1>
|
|
2
|
-
<
|
|
3
|
-
<source media="(prefers-color-scheme: dark)" srcset="docs/assets/logo-readme-dark.svg">
|
|
4
|
-
<img src="docs/assets/logo-readme.svg" alt="agent-kit" height="64">
|
|
5
|
-
</picture>
|
|
2
|
+
<img src="https://raw.githubusercontent.com/falkenslab/agent-kit/main/docs/assets/logo-readme-any.svg" alt="agent-kit" height="64">
|
|
6
3
|
</h1>
|
|
7
4
|
|
|
8
5
|
[](https://www.npmjs.com/package/@falkenslab/agent-kit)
|
|
@@ -1,16 +1,21 @@
|
|
|
1
1
|
// Starts an external extension's MCP server (ADR-025, #37) with a clean environment: the SDK
|
|
2
2
|
// merges a server's `env` with the agent's own (confirmed empirically), so the kit starts this
|
|
3
3
|
// launcher instead, which keeps only the variables the system needs and those the extension
|
|
4
|
-
// declares, then loads the server in this same process.
|
|
4
|
+
// declares, then loads the server in this same process. Its options' values (#55) are read here,
|
|
5
|
+
// when the server starts, so one changed while a session runs reaches the server started again.
|
|
5
6
|
//
|
|
6
|
-
// Usage: node extension-launcher.mjs <server script> <
|
|
7
|
+
// Usage: node extension-launcher.mjs <server script> <spec> [its arguments…]
|
|
8
|
+
// where <spec> is JSON: { "keep": [variables it may see], "options"?: { "lock", "secrets", "name", "defaults" } }.
|
|
9
|
+
import { readFileSync } from "node:fs";
|
|
7
10
|
import { pathToFileURL } from "node:url";
|
|
8
11
|
|
|
9
|
-
const [entry,
|
|
12
|
+
const [entry, specText = "{}", ...args] = process.argv.slice(2);
|
|
10
13
|
if (!entry) {
|
|
11
14
|
console.error("extension-launcher: no server entry given");
|
|
12
15
|
process.exit(1);
|
|
13
16
|
}
|
|
17
|
+
const spec = JSON.parse(specText);
|
|
18
|
+
const declared = spec.keep ?? [];
|
|
14
19
|
|
|
15
20
|
// What any process may need to run, on Windows and elsewhere; never a credential. Where programs
|
|
16
21
|
// are installed and the desktop session too: a browser is found through PROGRAMFILES on Windows
|
|
@@ -21,11 +26,29 @@ const SYSTEM = [
|
|
|
21
26
|
"HOMEDRIVE", "HOMEPATH", "USERNAME", "USER", "LOGNAME", "OS", "PROCESSOR_ARCHITECTURE", "NUMBER_OF_PROCESSORS",
|
|
22
27
|
"DISPLAY", "WAYLAND_DISPLAY", "XDG_RUNTIME_DIR", "XDG_CONFIG_HOME", "XDG_CACHE_HOME", "XDG_DATA_HOME", "DBUS_SESSION_BUS_ADDRESS",
|
|
23
28
|
];
|
|
24
|
-
const allowed = new Set([...SYSTEM, ...
|
|
29
|
+
const allowed = new Set([...SYSTEM, ...declared].map((name) => name.toUpperCase()));
|
|
25
30
|
for (const name of Object.keys(process.env)) {
|
|
26
31
|
if (!allowed.has(name.toUpperCase())) delete process.env[name];
|
|
27
32
|
}
|
|
28
33
|
|
|
34
|
+
// Its options' values now: the lock's, the secrets', else the defaults; `${user_config.KEY}` in
|
|
35
|
+
// its arguments and declared variables takes them (a list comma-separated, none as empty).
|
|
36
|
+
const readJson = (file) => {
|
|
37
|
+
try {
|
|
38
|
+
return JSON.parse(readFileSync(file, "utf8"));
|
|
39
|
+
} catch {
|
|
40
|
+
return {};
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
const options = spec.options;
|
|
44
|
+
const values = options ? { ...options.defaults, ...readJson(options.lock)[options.name]?.options, ...readJson(options.secrets) } : {};
|
|
45
|
+
const fill = (text) =>
|
|
46
|
+
text.replace(/\$\{user_config\.([A-Za-z_][A-Za-z0-9_]*)\}/g, (_, key) => {
|
|
47
|
+
const value = values[key];
|
|
48
|
+
return value === undefined ? "" : Array.isArray(value) ? value.join(",") : String(value);
|
|
49
|
+
});
|
|
50
|
+
for (const name of declared) if (process.env[name] !== undefined) process.env[name] = fill(process.env[name]);
|
|
51
|
+
|
|
29
52
|
// The server reads its own arguments from argv: its own, none of the launcher's.
|
|
30
|
-
process.argv = [process.argv[0], entry, ...args];
|
|
53
|
+
process.argv = [process.argv[0], entry, ...args.map(fill)];
|
|
31
54
|
await import(pathToFileURL(entry).href);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Options, SDKUserMessage } from "@anthropic-ai/claude-agent-sdk";
|
|
2
2
|
import type { Mode } from "../core/agentSpec.js";
|
|
3
3
|
import { type ApprovalPrompt, type ChoiceSettings } from "../core/interaction.js";
|
|
4
|
+
import type { FormRequest } from "../core/elicitation.js";
|
|
4
5
|
import { type Language } from "../core/language.js";
|
|
5
6
|
import { type AgentEvent, type AgentRun, type SessionUsage } from "../core/runner.js";
|
|
6
7
|
import { type RunFolder } from "../core/runs.js";
|
|
@@ -8,6 +9,8 @@ import { type ExtensionsStatus, type ModeControl } from "../core/session.js";
|
|
|
8
9
|
import { type Todo } from "../core/todos.js";
|
|
9
10
|
import { type ToolLabels } from "../core/toolLabels.js";
|
|
10
11
|
import type { ToolPhrase } from "../core/messages/index.js";
|
|
12
|
+
import { type ExtensionOptionState, type ExtensionScope } from "../core/externalExtensions.js";
|
|
13
|
+
import { type CatalogEntry } from "../core/extensionCatalog.js";
|
|
11
14
|
import { type SessionOpener } from "./runs.js";
|
|
12
15
|
/**
|
|
13
16
|
* The chat's logic without an interface (ADR-026, #39): opening, reopening and resuming the
|
|
@@ -104,6 +107,12 @@ export type ChatQuestion = {
|
|
|
104
107
|
kind: "choice";
|
|
105
108
|
prompt: ApprovalPrompt;
|
|
106
109
|
choice: ChoiceSettings;
|
|
110
|
+
}
|
|
111
|
+
/** A form an MCP server asks for (#58): answered with `answer(id, JSON.stringify({ action, content }))`. */
|
|
112
|
+
| {
|
|
113
|
+
kind: "form";
|
|
114
|
+
prompt: ApprovalPrompt;
|
|
115
|
+
form: FormRequest;
|
|
107
116
|
};
|
|
108
117
|
/** A checkpoint waiting for the person's answer, with `panels: "state"`. */
|
|
109
118
|
export type ChatPanel = ChatQuestion & {
|
|
@@ -243,6 +252,57 @@ export declare function createChatController(options: Options | SessionOpener, s
|
|
|
243
252
|
* the session opened (#49), by opening the session again with the same conversation otherwise.
|
|
244
253
|
*/
|
|
245
254
|
setExtension(name: string, enabled: boolean): Promise<void>;
|
|
255
|
+
/**
|
|
256
|
+
* The catalog (#56): what the agent's marketplaces offer (its `officialMarketplace` among
|
|
257
|
+
* them), matching `words` when given, with what's installed and what has an update.
|
|
258
|
+
*/
|
|
259
|
+
extensionCatalog(words?: readonly string[]): Promise<CatalogEntry[]>;
|
|
260
|
+
/**
|
|
261
|
+
* A catalog entry's logo or banner (#59), as a file for a view to serve; `undefined` when it
|
|
262
|
+
* has none, or isn't offered. From the catalog the view last listed, when it has.
|
|
263
|
+
*/
|
|
264
|
+
extensionImage(spec: string, kind: "logo" | "banner"): Promise<string | undefined>;
|
|
265
|
+
/**
|
|
266
|
+
* Installs an extension from the catalog (`<name>@<marketplace>`, or a name only one offers)
|
|
267
|
+
* into the agent's scope, or `scope`. One from a marketplace that isn't the agent's own isn't
|
|
268
|
+
* installed until `confirmed`: the result says which (`confirm`), for the view to ask the
|
|
269
|
+
* person. The running session is untouched: it's there in a new conversation, or with `reopen()`.
|
|
270
|
+
*/
|
|
271
|
+
installExtension(spec: string, options?: {
|
|
272
|
+
scope?: ExtensionScope;
|
|
273
|
+
confirmed?: boolean;
|
|
274
|
+
}): Promise<{
|
|
275
|
+
name?: string;
|
|
276
|
+
missing?: string[];
|
|
277
|
+
confirm?: {
|
|
278
|
+
marketplace: string;
|
|
279
|
+
description?: string;
|
|
280
|
+
};
|
|
281
|
+
}>;
|
|
282
|
+
/** Updates one installed extension from its marketplace, or every one with a newer version there; the names updated. */
|
|
283
|
+
updateExtension(name?: string): Promise<string[]>;
|
|
284
|
+
/**
|
|
285
|
+
* Uninstalls an extension, and its data with `deleteData`: turned off in the running session
|
|
286
|
+
* first when it's running (#49), the session opened again when that can't be done.
|
|
287
|
+
*/
|
|
288
|
+
removeExtension(name: string, options?: {
|
|
289
|
+
deleteData?: boolean;
|
|
290
|
+
}): Promise<void>;
|
|
291
|
+
/** An installed extension's options as they are now (a secret's value is never given). */
|
|
292
|
+
extensionOptions(name: string): Promise<ExtensionOptionState[]>;
|
|
293
|
+
/**
|
|
294
|
+
* Sets an installed extension's options from a view's form (`undefined` clears one), secrets
|
|
295
|
+
* too: a form isn't kept in the chat's history, as a typed line is. Its servers restart with
|
|
296
|
+
* them in the running session (#55), or the session opens again.
|
|
297
|
+
*/
|
|
298
|
+
setExtensionOptions(name: string, values: Record<string, string | undefined>): Promise<void>;
|
|
299
|
+
/**
|
|
300
|
+
* Trusts the workspace's extensions as they are now (#57) and opens the session again with
|
|
301
|
+
* them, keeping the conversation: what the person answers when the chat asks, or later.
|
|
302
|
+
*/
|
|
303
|
+
trustWorkspace(): Promise<void>;
|
|
304
|
+
/** Opens the session again with what's installed now, keeping the conversation: an extension just installed or updated, now. */
|
|
305
|
+
reopen(): Promise<void>;
|
|
246
306
|
/**
|
|
247
307
|
* Switches the kit's language (`switchLanguage()`) and opens the session again in it, keeping
|
|
248
308
|
* the conversation: the agent's next reply, the labels and the kit's notices come in it.
|
|
@@ -10,7 +10,10 @@ import { hasOpenTodos, parseTodos, TODO_TOOL } from "../core/todos.js";
|
|
|
10
10
|
import { withToolLabels, withToolPhrases } from "../core/toolLabels.js";
|
|
11
11
|
import { createConsoleRenderer } from "../tui/consoleRenderer.js";
|
|
12
12
|
import { chatExtensionsCommand } from "./extensions.js";
|
|
13
|
-
import {
|
|
13
|
+
import { trustWorkspace } from "../core/workspaceTrust.js";
|
|
14
|
+
import { askForDecision } from "../core/hooks/humanInput.js";
|
|
15
|
+
import { extensionOptionStates, listInstalled, removeExtension as removeInstalled, setExtensionEnabled, setExtensionOptions as storeExtensionOptions } from "../core/externalExtensions.js";
|
|
16
|
+
import { extensionCatalog as readCatalog, installExtension as installFromCatalog, needsConfirmation, updateExtension as updateFromCatalog } from "../core/extensionCatalog.js";
|
|
14
17
|
import { capHistory, loadHistory, saveHistory, slashCommandToken } from "./history.js";
|
|
15
18
|
import { firstRun, openSession, runFolderOf, runLabel } from "./runs.js";
|
|
16
19
|
const DEFAULT_EXIT_COMMANDS = ["/exit", "/quit"];
|
|
@@ -175,7 +178,7 @@ export async function createChatController(options, settings = {}) {
|
|
|
175
178
|
...viewCommands,
|
|
176
179
|
...(runsDir ? [{ name: "resume", description: t().commandResume, argumentHint: "" }] : []),
|
|
177
180
|
...(control?.switchable.includes("plan") ? [{ name: "plan", description: t().commandPlan, argumentHint: "" }] : []),
|
|
178
|
-
{ name: "extensions", description: t().commandExtensions, argumentHint: "[enable|disable
|
|
181
|
+
{ name: "extensions", description: t().commandExtensions, argumentHint: "[enable|disable|config|search|install|update|remove|reload]" },
|
|
179
182
|
];
|
|
180
183
|
const details = new Map();
|
|
181
184
|
for (const command of [...(commands ?? []).map(({ name, description, argumentHint }) => ({ name, description: description ?? "", argumentHint: argumentHint ?? "" })), ...local])
|
|
@@ -278,6 +281,47 @@ export async function createChatController(options, settings = {}) {
|
|
|
278
281
|
notice(t().extensionToggled(name, enabled));
|
|
279
282
|
await switchTo({ dir: current.run.dir, sessionId: await readRunSession(current.run.dir) }, false);
|
|
280
283
|
}
|
|
284
|
+
/** An extension whose options just changed: its servers restarted in the running session if they can be, the session opened again if not (#55). */
|
|
285
|
+
async function applyOptions(name) {
|
|
286
|
+
if (await current.restartExtension?.(name).catch(() => false)) {
|
|
287
|
+
notice(t().extensionRestarted(name));
|
|
288
|
+
extensionNotices.push(`<system-reminder>The user changed the ${name} extension's settings: its server restarted with them. A call that failed just now may work again.</system-reminder>`);
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
notice(t().extensionReconfiguredReopen(name));
|
|
292
|
+
await switchTo({ dir: current.run.dir, sessionId: await readRunSession(current.run.dir) }, false);
|
|
293
|
+
}
|
|
294
|
+
/** The catalog as the view last listed it (#59): what its images are served from. */
|
|
295
|
+
let lastCatalog;
|
|
296
|
+
/** The installed extensions as they are now on disk, into the state (after installing, updating or removing one). */
|
|
297
|
+
async function refreshInstalled() {
|
|
298
|
+
if (!current.extensions)
|
|
299
|
+
return;
|
|
300
|
+
// In place: the session's live switch keeps a hold of the same status (liveExtensions.ts).
|
|
301
|
+
current.extensions.installed = await listInstalled(current.extensions.dirs);
|
|
302
|
+
update({ extensions: { ...current.extensions } });
|
|
303
|
+
}
|
|
304
|
+
/** What `/extensions install|update|remove|reload` asks for, through the controller's own actions. */
|
|
305
|
+
async function runExtensionAction(action) {
|
|
306
|
+
try {
|
|
307
|
+
if (action.type === "install") {
|
|
308
|
+
const result = await controller.installExtension(action.spec, { confirmed: action.confirmed });
|
|
309
|
+
if (result.confirm)
|
|
310
|
+
notice(t().extensionConfirm(action.spec, result.confirm.marketplace));
|
|
311
|
+
}
|
|
312
|
+
else if (action.type === "update")
|
|
313
|
+
await controller.updateExtension(action.name);
|
|
314
|
+
else if (action.type === "remove")
|
|
315
|
+
await controller.removeExtension(action.name, { deleteData: action.deleteData });
|
|
316
|
+
else if (action.type === "trust")
|
|
317
|
+
await controller.trustWorkspace();
|
|
318
|
+
else
|
|
319
|
+
await controller.reopen();
|
|
320
|
+
}
|
|
321
|
+
catch (error) {
|
|
322
|
+
notice(`(${error instanceof Error ? error.message : String(error)})`, "warn");
|
|
323
|
+
}
|
|
324
|
+
}
|
|
281
325
|
async function switchTo(folder, fromResume) {
|
|
282
326
|
queue.end();
|
|
283
327
|
run.close();
|
|
@@ -288,6 +332,28 @@ export async function createChatController(options, settings = {}) {
|
|
|
288
332
|
emit({ type: "session" });
|
|
289
333
|
if (fromResume)
|
|
290
334
|
await showConversation(current.run, true);
|
|
335
|
+
void askTrust();
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* A session opened with extensions the workspace brings that the person hasn't trusted here
|
|
339
|
+
* (#57): asked once (no by default); trusted, the session opens again with them. Declined, not
|
|
340
|
+
* asked again in this chat: `/extensions trust` trusts them later.
|
|
341
|
+
*/
|
|
342
|
+
let trustDeclined = false;
|
|
343
|
+
async function askTrust() {
|
|
344
|
+
const untrusted = current.extensions?.untrusted ?? [];
|
|
345
|
+
if (!untrusted.length || trustDeclined || !opener || !current.run)
|
|
346
|
+
return;
|
|
347
|
+
const answer = await askForDecision(current.run.dir, {
|
|
348
|
+
title: t().trustTitle,
|
|
349
|
+
lines: [t().trustBody, "", ...untrusted.map((extension) => `- ${extension.name}${extension.version ? ` ${extension.version}` : ""} (${extension.source})`)],
|
|
350
|
+
question: t().trustQuestion,
|
|
351
|
+
});
|
|
352
|
+
if (answer !== "y") {
|
|
353
|
+
trustDeclined = true;
|
|
354
|
+
return notice(t().trustDeclined);
|
|
355
|
+
}
|
|
356
|
+
await controller.trustWorkspace();
|
|
291
357
|
}
|
|
292
358
|
// A choice in place of the prompt, when the view doesn't ask it itself.
|
|
293
359
|
let choiceResolve = null;
|
|
@@ -324,6 +390,17 @@ export async function createChatController(options, settings = {}) {
|
|
|
324
390
|
askManualIntervention: (prompt, signal) => ask({ kind: "manual", prompt }, signal),
|
|
325
391
|
askText: (prompt, signal) => ask({ kind: "text", prompt }, signal),
|
|
326
392
|
askChoice: (prompt, choice, signal) => ask({ kind: "choice", prompt, choice }, signal),
|
|
393
|
+
// The answer is the form's, JSON; anything else (the panel dropped) is a cancel.
|
|
394
|
+
askForm: async (form, signal) => {
|
|
395
|
+
const raw = await ask({ kind: "form", prompt: { title: form.server, lines: [form.message] }, form }, signal);
|
|
396
|
+
try {
|
|
397
|
+
const answer = JSON.parse(raw);
|
|
398
|
+
return answer.action === "accept" ? { action: "accept", content: answer.content ?? {} } : { action: answer.action === "decline" ? "decline" : "cancel" };
|
|
399
|
+
}
|
|
400
|
+
catch {
|
|
401
|
+
return { action: "cancel" };
|
|
402
|
+
}
|
|
403
|
+
},
|
|
327
404
|
notify: (message) => notice(message),
|
|
328
405
|
};
|
|
329
406
|
const previousPort = settings.panels === "state" ? getInteractionPort() : null;
|
|
@@ -352,6 +429,8 @@ export async function createChatController(options, settings = {}) {
|
|
|
352
429
|
async start() {
|
|
353
430
|
if (current.run)
|
|
354
431
|
await showConversation(current.run, false);
|
|
432
|
+
// Asked alongside, not before: a view draws its panels once the chat has started.
|
|
433
|
+
void askTrust();
|
|
355
434
|
if (settings.initialPrompt && !current.run?.sessionId) {
|
|
356
435
|
// Logged but not shown: it's the agent's cue, not something the person typed.
|
|
357
436
|
log(`${promptLabel}${settings.initialPrompt}\n`);
|
|
@@ -390,6 +469,10 @@ export async function createChatController(options, settings = {}) {
|
|
|
390
469
|
notice(text);
|
|
391
470
|
if (extensions.toggled)
|
|
392
471
|
await applyExtension(extensions.toggled.name, extensions.toggled.enabled);
|
|
472
|
+
if (extensions.reconfigured)
|
|
473
|
+
await applyOptions(extensions.reconfigured);
|
|
474
|
+
if (extensions.action)
|
|
475
|
+
await runExtensionAction(extensions.action);
|
|
393
476
|
return;
|
|
394
477
|
}
|
|
395
478
|
const token = slashCommandToken(line);
|
|
@@ -486,6 +569,133 @@ export async function createChatController(options, settings = {}) {
|
|
|
486
569
|
await setExtensionEnabled(dir, name, enabled);
|
|
487
570
|
await applyExtension(name, enabled);
|
|
488
571
|
},
|
|
572
|
+
/**
|
|
573
|
+
* The catalog (#56): what the agent's marketplaces offer (its `officialMarketplace` among
|
|
574
|
+
* them), matching `words` when given, with what's installed and what has an update.
|
|
575
|
+
*/
|
|
576
|
+
async extensionCatalog(words = []) {
|
|
577
|
+
if (!current.extensions)
|
|
578
|
+
return [];
|
|
579
|
+
const entries = await readCatalog(current.extensions.dirs, { ...(current.extensions.official ? { official: current.extensions.official } : {}), ...(current.extensions.catalogTopics ? { topics: current.extensions.catalogTopics } : {}), words });
|
|
580
|
+
if (!words.length)
|
|
581
|
+
lastCatalog = entries;
|
|
582
|
+
return entries;
|
|
583
|
+
},
|
|
584
|
+
/**
|
|
585
|
+
* A catalog entry's logo or banner (#59), as a file for a view to serve; `undefined` when it
|
|
586
|
+
* has none, or isn't offered. From the catalog the view last listed, when it has.
|
|
587
|
+
*/
|
|
588
|
+
async extensionImage(spec, kind) {
|
|
589
|
+
const entries = lastCatalog ?? (await controller.extensionCatalog());
|
|
590
|
+
return entries.find((entry) => entry.spec === spec)?.[kind];
|
|
591
|
+
},
|
|
592
|
+
/**
|
|
593
|
+
* Installs an extension from the catalog (`<name>@<marketplace>`, or a name only one offers)
|
|
594
|
+
* into the agent's scope, or `scope`. One from a marketplace that isn't the agent's own isn't
|
|
595
|
+
* installed until `confirmed`: the result says which (`confirm`), for the view to ask the
|
|
596
|
+
* person. The running session is untouched: it's there in a new conversation, or with `reopen()`.
|
|
597
|
+
*/
|
|
598
|
+
async installExtension(spec, options = {}) {
|
|
599
|
+
if (!current.extensions)
|
|
600
|
+
throw new Error("This chat's session has no extensions folder.");
|
|
601
|
+
const official = current.extensions.official ? { official: current.extensions.official } : {};
|
|
602
|
+
const foreign = await needsConfirmation(current.extensions.dirs, spec, current.extensions.official);
|
|
603
|
+
if (foreign && !options.confirmed)
|
|
604
|
+
return { confirm: { marketplace: foreign.marketplace.name, ...(foreign.plugin.description ? { description: foreign.plugin.description } : {}) } };
|
|
605
|
+
const result = await installFromCatalog(current.extensions.dirs, spec, { ...official, ...(options.scope ? { scope: options.scope } : {}) });
|
|
606
|
+
await refreshInstalled();
|
|
607
|
+
notice(t().extensionInstalled(result.name));
|
|
608
|
+
// What it needs and this computer lacks (#63): the person installs it, the kit never does.
|
|
609
|
+
for (const line of result.missing)
|
|
610
|
+
notice(t().extensionLacks(result.name, line));
|
|
611
|
+
return { name: result.name, missing: result.missing };
|
|
612
|
+
},
|
|
613
|
+
/** Updates one installed extension from its marketplace, or every one with a newer version there; the names updated. */
|
|
614
|
+
async updateExtension(name) {
|
|
615
|
+
if (!current.extensions)
|
|
616
|
+
return [];
|
|
617
|
+
const official = current.extensions.official ? { official: current.extensions.official } : {};
|
|
618
|
+
const names = name ? [name] : (await readCatalog(current.extensions.dirs, official)).filter((entry) => entry.update).map((entry) => entry.plugin.name);
|
|
619
|
+
// Updating what's installed isn't offering: every installed one, whatever its topics.
|
|
620
|
+
if (!names.length)
|
|
621
|
+
notice(t().extensionsUpToDate);
|
|
622
|
+
for (const each of names) {
|
|
623
|
+
const result = await updateFromCatalog(current.extensions.dirs, each, official);
|
|
624
|
+
notice(t().extensionUpdated(each, result.from ?? "?", result.to ?? "?"));
|
|
625
|
+
}
|
|
626
|
+
await refreshInstalled();
|
|
627
|
+
return names;
|
|
628
|
+
},
|
|
629
|
+
/**
|
|
630
|
+
* Uninstalls an extension, and its data with `deleteData`: turned off in the running session
|
|
631
|
+
* first when it's running (#49), the session opened again when that can't be done.
|
|
632
|
+
*/
|
|
633
|
+
async removeExtension(name, options = {}) {
|
|
634
|
+
const installed = (current.extensions?.installed ?? []).find((extension) => extension.name === name && !extension.shadowed);
|
|
635
|
+
if (!installed)
|
|
636
|
+
return notice(t().extensionNotInstalled(name));
|
|
637
|
+
const running = current.extensions?.active.includes(name) ?? false;
|
|
638
|
+
const switched = running ? await current.switchExtension?.(name, false).catch(() => false) : true;
|
|
639
|
+
await removeInstalled(installed.scopeDir, name, { deleteData: Boolean(options.deleteData), privateDir: installed.privateDir });
|
|
640
|
+
// Gone, not off: out of what's listed as off in this session too.
|
|
641
|
+
if (current.extensions)
|
|
642
|
+
current.extensions.inactive = current.extensions.inactive.filter((off) => off.name !== name);
|
|
643
|
+
await refreshInstalled();
|
|
644
|
+
notice(t().extensionRemoved(name, Boolean(options.deleteData)));
|
|
645
|
+
if (running && switched) {
|
|
646
|
+
extensionNotices.push(`<system-reminder>The user uninstalled the ${name} extension: its tools, skills, commands and subagents are gone, whatever your instructions or earlier messages say.</system-reminder>`);
|
|
647
|
+
loadCommands();
|
|
648
|
+
}
|
|
649
|
+
else if (running && opener && current.run)
|
|
650
|
+
await switchTo({ dir: current.run.dir, sessionId: await readRunSession(current.run.dir) }, false);
|
|
651
|
+
},
|
|
652
|
+
/** An installed extension's options as they are now (a secret's value is never given). */
|
|
653
|
+
async extensionOptions(name) {
|
|
654
|
+
const installed = (current.extensions?.installed ?? []).find((extension) => extension.name === name && !extension.shadowed);
|
|
655
|
+
return installed ? extensionOptionStates(installed) : [];
|
|
656
|
+
},
|
|
657
|
+
/**
|
|
658
|
+
* Sets an installed extension's options from a view's form (`undefined` clears one), secrets
|
|
659
|
+
* too: a form isn't kept in the chat's history, as a typed line is. Its servers restart with
|
|
660
|
+
* them in the running session (#55), or the session opens again.
|
|
661
|
+
*/
|
|
662
|
+
async setExtensionOptions(name, values) {
|
|
663
|
+
const installed = (current.extensions?.installed ?? []).find((extension) => extension.name === name && !extension.shadowed);
|
|
664
|
+
if (!installed)
|
|
665
|
+
return notice(t().extensionNotInstalled(name));
|
|
666
|
+
await storeExtensionOptions(installed, name, values);
|
|
667
|
+
await refreshInstalled();
|
|
668
|
+
if (current.extensions?.active.includes(name))
|
|
669
|
+
await applyOptions(name);
|
|
670
|
+
else if (opener && current.run) {
|
|
671
|
+
// Off for want of a value, maybe: a new session may run it now.
|
|
672
|
+
notice(t().extensionReconfiguredReopen(name));
|
|
673
|
+
await switchTo({ dir: current.run.dir, sessionId: await readRunSession(current.run.dir) }, false);
|
|
674
|
+
}
|
|
675
|
+
},
|
|
676
|
+
/**
|
|
677
|
+
* Trusts the workspace's extensions as they are now (#57) and opens the session again with
|
|
678
|
+
* them, keeping the conversation: what the person answers when the chat asks, or later.
|
|
679
|
+
*/
|
|
680
|
+
async trustWorkspace() {
|
|
681
|
+
if (!current.extensions)
|
|
682
|
+
return;
|
|
683
|
+
const names = await trustWorkspace(current.extensions.dirs);
|
|
684
|
+
if (!names.length)
|
|
685
|
+
return;
|
|
686
|
+
notice(t().trustGiven(names.join(", ")));
|
|
687
|
+
if (opener && current.run && !state.busy)
|
|
688
|
+
await switchTo({ dir: current.run.dir, sessionId: await readRunSession(current.run.dir) }, false);
|
|
689
|
+
},
|
|
690
|
+
/** Opens the session again with what's installed now, keeping the conversation: an extension just installed or updated, now. */
|
|
691
|
+
async reopen() {
|
|
692
|
+
if (!opener || !current.run)
|
|
693
|
+
return notice(t().extensionsCantReopen);
|
|
694
|
+
if (state.busy)
|
|
695
|
+
return notice(t().extensionsBusy);
|
|
696
|
+
notice(t().extensionsReloaded);
|
|
697
|
+
await switchTo({ dir: current.run.dir, sessionId: await readRunSession(current.run.dir) }, false);
|
|
698
|
+
},
|
|
489
699
|
/**
|
|
490
700
|
* Switches the kit's language (`switchLanguage()`) and opens the session again in it, keeping
|
|
491
701
|
* the conversation: the agent's next reply, the labels and the kit's notices come in it.
|
|
@@ -1,13 +1,45 @@
|
|
|
1
1
|
import { type InstalledExtension } from "../core/externalExtensions.js";
|
|
2
2
|
import type { ExtensionsStatus } from "../core/session.js";
|
|
3
|
+
import { type CatalogEntry } from "../core/extensionCatalog.js";
|
|
3
4
|
/** One line for an installed extension: name and version, scope, on or off, its author, where it came from. */
|
|
4
5
|
export declare function describe(extension: InstalledExtension): string;
|
|
6
|
+
/**
|
|
7
|
+
* What the active extensions let the agent do (#62), in the session's language: a heading per
|
|
8
|
+
* extension with capabilities, a line per capability, and the tools a capability names that its
|
|
9
|
+
* server doesn't offer, once the session has started and knows them.
|
|
10
|
+
*/
|
|
11
|
+
export declare function describeCapabilities(status: ExtensionsStatus): string[];
|
|
12
|
+
/** What the chat does for `/extensions install|update|remove|reload` (#56). */
|
|
13
|
+
export type ExtensionAction = {
|
|
14
|
+
type: "install";
|
|
15
|
+
spec: string;
|
|
16
|
+
confirmed: boolean;
|
|
17
|
+
} | {
|
|
18
|
+
type: "update";
|
|
19
|
+
name?: string;
|
|
20
|
+
} | {
|
|
21
|
+
type: "remove";
|
|
22
|
+
name: string;
|
|
23
|
+
deleteData: boolean;
|
|
24
|
+
} | {
|
|
25
|
+
type: "reload";
|
|
26
|
+
} | {
|
|
27
|
+
type: "trust";
|
|
28
|
+
};
|
|
29
|
+
/** One line for a catalog entry: what installs it, its version, whether it's installed or has an update, what it is. */
|
|
30
|
+
export declare function describeEntry(entry: CatalogEntry): string;
|
|
31
|
+
/** An installed extension's options, one line each: its value, its default, or whether a sensitive one is set (never its value). */
|
|
32
|
+
export declare function describeOptions(extension: InstalledExtension): Promise<string[]>;
|
|
5
33
|
/**
|
|
6
34
|
* `/extensions` in a chat (#37): with nothing after it, what the session runs with, what's off
|
|
7
35
|
* and why, and what's installed; with `enable <name>` or `disable <name>`, changes its lock and
|
|
8
36
|
* says which (`toggled`): the chat turns it on or off in the running session when it can (#49),
|
|
9
|
-
* and opens the session again otherwise, keeping the conversation.
|
|
10
|
-
*
|
|
37
|
+
* and opens the session again otherwise, keeping the conversation. With `config <name>`, its
|
|
38
|
+
* options; with `key=value` after it, sets them (`reconfigured`: the chat restarts its servers,
|
|
39
|
+
* #55). A sensitive one isn't set here: what's typed in a chat is kept in its history. The
|
|
40
|
+
* catalog (#56): `search [words]` lists what the agent's marketplaces offer; `install <name>`,
|
|
41
|
+
* `update [name]`, `remove <name> [--delete-data]` and `reload` are the chat's to do (`action`),
|
|
42
|
+
* since they change what the session has. `null` when `line` isn't this command.
|
|
11
43
|
*/
|
|
12
44
|
export declare function chatExtensionsCommand(line: string, status: ExtensionsStatus | undefined, canReopen: boolean): Promise<{
|
|
13
45
|
lines: string[];
|
|
@@ -15,4 +47,6 @@ export declare function chatExtensionsCommand(line: string, status: ExtensionsSt
|
|
|
15
47
|
name: string;
|
|
16
48
|
enabled: boolean;
|
|
17
49
|
};
|
|
50
|
+
reconfigured?: string;
|
|
51
|
+
action?: ExtensionAction;
|
|
18
52
|
} | null>;
|
package/dist/chat/extensions.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
-
import { setExtensionEnabled } from "../core/externalExtensions.js";
|
|
2
|
+
import { extensionOptionStates, setExtensionEnabled, setExtensionOptions } from "../core/externalExtensions.js";
|
|
3
3
|
import { t } from "../core/messages/index.js";
|
|
4
|
+
import { extensionCatalog } from "../core/extensionCatalog.js";
|
|
4
5
|
/** One line for an installed extension: name and version, scope, on or off, its author, where it came from. */
|
|
5
6
|
export function describe(extension) {
|
|
6
7
|
const state = `${extension.shadowed ? "not used (the project's wins)" : extension.lock.enabled ? "enabled" : "disabled"}${extension.lock.linked ? ", linked" : ""}`;
|
|
@@ -11,19 +12,102 @@ export function describe(extension) {
|
|
|
11
12
|
const author = extension.manifest?.author ? ` by ${extension.manifest.author.name}` : "";
|
|
12
13
|
return `${extension.name}${version} [${extension.scope}] ${state}${author} from ${source}`;
|
|
13
14
|
}
|
|
15
|
+
/**
|
|
16
|
+
* What the active extensions let the agent do (#62), in the session's language: a heading per
|
|
17
|
+
* extension with capabilities, a line per capability, and the tools a capability names that its
|
|
18
|
+
* server doesn't offer, once the session has started and knows them.
|
|
19
|
+
*/
|
|
20
|
+
export function describeCapabilities(status) {
|
|
21
|
+
const offered = new Set(status.sessionTools?.() ?? []);
|
|
22
|
+
return status.active.flatMap((name) => {
|
|
23
|
+
const capabilities = status.about[name]?.capabilities ?? [];
|
|
24
|
+
const unavailable = status.about[name]?.unavailable ?? [];
|
|
25
|
+
if (!capabilities.length && !unavailable.length)
|
|
26
|
+
return [];
|
|
27
|
+
return [
|
|
28
|
+
t().extensionCan(name),
|
|
29
|
+
...capabilities.map(({ label, description, tools }) => {
|
|
30
|
+
const missing = offered.size ? [...new Set(tools.filter((tool) => !offered.has(tool)).map((tool) => tool.replace(/^mcp__.+?__/, "")))] : [];
|
|
31
|
+
return ` - ${label}${description ? `: ${description}` : ""}${missing.length ? ` ${t().capabilityToolsMissing(missing.join(", "))}` : ""}`;
|
|
32
|
+
}),
|
|
33
|
+
// What it can't do on this computer, with what to install (#63).
|
|
34
|
+
...unavailable.map(({ label, reason }) => ` - ${t().capabilityUnavailable(label, reason)}`),
|
|
35
|
+
];
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
/** One line for a catalog entry: what installs it, its version, whether it's installed or has an update, what it is. */
|
|
39
|
+
export function describeEntry(entry) {
|
|
40
|
+
const state = entry.installed ? (entry.update ? ` ${t().extensionUpdateAvailable(entry.installed.manifest?.version ?? "?")}` : ` ${t().extensionIsInstalled}`) : "";
|
|
41
|
+
return `${entry.spec}${entry.version ? ` ${entry.version}` : ""}${state}${entry.plugin.description ? ` ${entry.plugin.description}` : ""}`;
|
|
42
|
+
}
|
|
43
|
+
/** An installed extension's options, one line each: its value, its default, or whether a sensitive one is set (never its value). */
|
|
44
|
+
export async function describeOptions(extension) {
|
|
45
|
+
return (await extensionOptionStates(extension)).map(({ key, option, value, set, isDefault }) => {
|
|
46
|
+
const shown = option.sensitive ? (set ? t().optionSecretSet : t().optionNotSet) : set ? `${Array.isArray(value) ? value.join(",") : String(value)}${isDefault ? ` ${t().optionDefault}` : ""}` : t().optionNotSet;
|
|
47
|
+
return ` ${key} = ${shown} (${option.title}${option.required ? `, ${t().optionRequired}` : ""})`;
|
|
48
|
+
});
|
|
49
|
+
}
|
|
14
50
|
/**
|
|
15
51
|
* `/extensions` in a chat (#37): with nothing after it, what the session runs with, what's off
|
|
16
52
|
* and why, and what's installed; with `enable <name>` or `disable <name>`, changes its lock and
|
|
17
53
|
* says which (`toggled`): the chat turns it on or off in the running session when it can (#49),
|
|
18
|
-
* and opens the session again otherwise, keeping the conversation.
|
|
19
|
-
*
|
|
54
|
+
* and opens the session again otherwise, keeping the conversation. With `config <name>`, its
|
|
55
|
+
* options; with `key=value` after it, sets them (`reconfigured`: the chat restarts its servers,
|
|
56
|
+
* #55). A sensitive one isn't set here: what's typed in a chat is kept in its history. The
|
|
57
|
+
* catalog (#56): `search [words]` lists what the agent's marketplaces offer; `install <name>`,
|
|
58
|
+
* `update [name]`, `remove <name> [--delete-data]` and `reload` are the chat's to do (`action`),
|
|
59
|
+
* since they change what the session has. `null` when `line` isn't this command.
|
|
20
60
|
*/
|
|
21
61
|
export async function chatExtensionsCommand(line, status, canReopen) {
|
|
22
62
|
const [command, action, name, ...extra] = line.trim().split(/\s+/);
|
|
23
63
|
if (command?.toLowerCase() !== "/extensions")
|
|
24
64
|
return null;
|
|
65
|
+
const verb = action?.toLowerCase();
|
|
66
|
+
if (verb === "search") {
|
|
67
|
+
const words = [name, ...extra].filter((word) => Boolean(word));
|
|
68
|
+
const entries = status ? await extensionCatalog(status.dirs, { ...(status.official ? { official: status.official } : {}), ...(status.catalogTopics ? { topics: status.catalogTopics } : {}), words }).catch(() => []) : [];
|
|
69
|
+
return { lines: entries.length ? entries.map((entry) => ` ${describeEntry(entry)}`) : [t().extensionsNoMatch] };
|
|
70
|
+
}
|
|
71
|
+
if (verb === "install" && name)
|
|
72
|
+
return canReopen ? { lines: [], action: { type: "install", spec: name, confirmed: extra.includes("--yes") } } : { lines: [t().extensionsCantReopen] };
|
|
73
|
+
if (verb === "update")
|
|
74
|
+
return canReopen ? { lines: [], action: { type: "update", ...(name ? { name } : {}) } } : { lines: [t().extensionsCantReopen] };
|
|
75
|
+
if (verb === "remove" && name)
|
|
76
|
+
return canReopen ? { lines: [], action: { type: "remove", name, deleteData: extra.includes("--delete-data") } } : { lines: [t().extensionsCantReopen] };
|
|
77
|
+
if (verb === "reload" && !name)
|
|
78
|
+
return canReopen ? { lines: [], action: { type: "reload" } } : { lines: [t().extensionsCantReopen] };
|
|
79
|
+
if (verb === "trust" && !name)
|
|
80
|
+
return canReopen ? { lines: [], action: { type: "trust" } } : { lines: [t().extensionsCantReopen] };
|
|
81
|
+
if (action?.toLowerCase() === "config" && name) {
|
|
82
|
+
const installed = (status?.installed ?? []).find((extension) => extension.name === name && !extension.shadowed);
|
|
83
|
+
if (!installed)
|
|
84
|
+
return { lines: [t().extensionNotInstalled(name)] };
|
|
85
|
+
if (!extra.length) {
|
|
86
|
+
const options = await describeOptions(installed);
|
|
87
|
+
return { lines: options.length ? [t().extensionOptions(name), ...options] : [t().extensionNoOptions(name)] };
|
|
88
|
+
}
|
|
89
|
+
const typed = {};
|
|
90
|
+
for (const pair of extra) {
|
|
91
|
+
const at = pair.indexOf("=");
|
|
92
|
+
const key = at > 0 ? pair.slice(0, at) : pair;
|
|
93
|
+
if (installed.manifest?.userConfig[key]?.sensitive)
|
|
94
|
+
return { lines: [t().optionSensitiveInChat(key, name)] };
|
|
95
|
+
typed[key] = at > 0 ? pair.slice(at + 1) || undefined : undefined;
|
|
96
|
+
}
|
|
97
|
+
try {
|
|
98
|
+
await setExtensionOptions(installed, name, typed);
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
return { lines: [`(${error instanceof Error ? error.message : String(error)})`] };
|
|
102
|
+
}
|
|
103
|
+
if (!canReopen)
|
|
104
|
+
return { lines: [t().extensionOptionsSaved(name)] };
|
|
105
|
+
return { lines: [], reconfigured: name };
|
|
106
|
+
}
|
|
25
107
|
if (!action) {
|
|
26
108
|
const lines = [status?.active.length ? t().extensionsRunning(status.active.join(", ")) : t().extensionsNone];
|
|
109
|
+
if (status)
|
|
110
|
+
lines.push(...describeCapabilities(status));
|
|
27
111
|
for (const { name: off, reason } of status?.inactive ?? [])
|
|
28
112
|
lines.push(t().extensionOff(off, reason));
|
|
29
113
|
if (status?.installed.length)
|
package/dist/chat/runs.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Options } from "@anthropic-ai/claude-agent-sdk";
|
|
2
2
|
import type { ExtensionsStatus, ModeControl } from "../core/session.js";
|
|
3
3
|
import type { ToolLabels } from "../core/toolLabels.js";
|
|
4
|
-
import type { ExtensionSwitch } from "../core/liveExtensions.js";
|
|
4
|
+
import type { ExtensionRestart, ExtensionSwitch } from "../core/liveExtensions.js";
|
|
5
5
|
import { type RunFolder, type RunSummary } from "../core/runs.js";
|
|
6
6
|
/**
|
|
7
7
|
* Builds the session's options for a run folder, e.g.
|
|
@@ -16,6 +16,7 @@ export type SessionOpener = (run: RunFolder) => Promise<Options | {
|
|
|
16
16
|
extensions?: ExtensionsStatus;
|
|
17
17
|
apis?: Record<string, Record<string, unknown>>;
|
|
18
18
|
switchExtension?: ExtensionSwitch;
|
|
19
|
+
restartExtension?: ExtensionRestart;
|
|
19
20
|
}>;
|
|
20
21
|
export interface OpenedSession {
|
|
21
22
|
run: RunFolder;
|
|
@@ -25,6 +26,7 @@ export interface OpenedSession {
|
|
|
25
26
|
extensions?: ExtensionsStatus;
|
|
26
27
|
apis?: Record<string, Record<string, unknown>>;
|
|
27
28
|
switchExtension?: ExtensionSwitch;
|
|
29
|
+
restartExtension?: ExtensionRestart;
|
|
28
30
|
}
|
|
29
31
|
/** The run a chat starts with: the latest one with `--continue` (if any), a new one otherwise. */
|
|
30
32
|
export declare function firstRun(runsDir: string, argv?: readonly string[]): Promise<RunFolder>;
|
package/dist/chat/runs.js
CHANGED
|
@@ -13,7 +13,7 @@ export async function firstRun(runsDir, argv = process.argv.slice(2)) {
|
|
|
13
13
|
export async function openSession(opener, run) {
|
|
14
14
|
const opened = await opener(run);
|
|
15
15
|
// The SDK's Options has no "options" field: this is buildSessionOptions()'s result.
|
|
16
|
-
return "options" in opened ? { run, options: opened.options, modeControl: opened.modeControl, toolLabels: opened.toolLabels, extensions: opened.extensions, apis: opened.apis, switchExtension: opened.switchExtension } : { run, options: opened };
|
|
16
|
+
return "options" in opened ? { run, options: opened.options, modeControl: opened.modeControl, toolLabels: opened.toolLabels, extensions: opened.extensions, apis: opened.apis, switchExtension: opened.switchExtension, restartExtension: opened.restartExtension } : { run, options: opened };
|
|
17
17
|
}
|
|
18
18
|
/** A run's date for a person, in the kit's language. */
|
|
19
19
|
export function runDate(date) {
|
package/dist/core/agentSpec.d.ts
CHANGED
|
@@ -33,6 +33,17 @@ export interface BaseSessionConfig {
|
|
|
33
33
|
* installs them.
|
|
34
34
|
*/
|
|
35
35
|
extensionDirs?: ExtensionDirs;
|
|
36
|
+
/**
|
|
37
|
+
* The agent's own marketplace of extensions (a folder or a git URL, `#ref`): the chats' catalog
|
|
38
|
+
* (`/extensions`, #56) offers what it has, installed without a confirmation, as
|
|
39
|
+
* `runExtensionCommand()`'s `official` does.
|
|
40
|
+
*/
|
|
41
|
+
officialMarketplace?: string;
|
|
42
|
+
/**
|
|
43
|
+
* The topics of the extensions this agent offers its users (#59), e.g. `["miyagi"]`: the chats'
|
|
44
|
+
* catalog and `extension search` show only those with one of them. Without it, every one.
|
|
45
|
+
*/
|
|
46
|
+
catalogTopics?: string[];
|
|
36
47
|
/**
|
|
37
48
|
* Directories where the agent reads, searches and writes with the file tools (Write/Edit,
|
|
38
49
|
* scoped — see hooks/fileScopeGate.ts), e.g. a folder of its own notes. Setting one gives the
|