@oharness/engine 0.11.0 → 0.12.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/dist/agent/skills.d.ts +16 -3
- package/dist/agent/skills.js +36 -2
- package/dist/agent/skills.js.map +1 -1
- package/dist/agent/subagent.d.ts +15 -2
- package/dist/agent/subagent.js +70 -16
- package/dist/agent/subagent.js.map +1 -1
- package/dist/config.d.ts +9 -0
- package/dist/config.js +2 -0
- package/dist/config.js.map +1 -1
- package/dist/foreign.d.ts +81 -0
- package/dist/foreign.js +198 -0
- package/dist/foreign.js.map +1 -0
- package/dist/harness.d.ts +27 -0
- package/dist/harness.js +128 -5
- package/dist/harness.js.map +1 -1
- package/dist/mcp/registry.d.ts +2 -0
- package/dist/mcp/registry.js +1 -0
- package/dist/mcp/registry.js.map +1 -1
- package/dist/mcp/registry.json +394 -1
- package/dist/paths.d.ts +19 -0
- package/dist/paths.js +38 -0
- package/dist/paths.js.map +1 -1
- package/dist/plugin-market.d.ts +57 -0
- package/dist/plugin-market.js +229 -0
- package/dist/plugin-market.js.map +1 -0
- package/dist/plugin-market.json +1246 -0
- package/dist/plugins.d.ts +49 -2
- package/dist/plugins.js +252 -10
- package/dist/plugins.js.map +1 -1
- package/dist/util/toml.d.ts +22 -0
- package/dist/util/toml.js +305 -0
- package/dist/util/toml.js.map +1 -0
- package/package.json +2 -2
package/dist/foreign.js
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { agentsHome, claudeHome, claudeStateFile, codexHome } from "./paths.js";
|
|
4
|
+
import { claudeServer } from "./plugins.js";
|
|
5
|
+
import { parseJson } from "./util/json.js";
|
|
6
|
+
import { parseToml } from "./util/toml.js";
|
|
7
|
+
/**
|
|
8
|
+
* Skill directories, split by level so each can sit beneath this harness's
|
|
9
|
+
* own directory at the same level: a skill in `~/.oharness/skills` overrides
|
|
10
|
+
* one of the same name in `~/.claude/skills`, and a project's own beats both.
|
|
11
|
+
*/
|
|
12
|
+
export function foreignSkillRoots(cwd, config = {}) {
|
|
13
|
+
const user = [];
|
|
14
|
+
const project = [];
|
|
15
|
+
if (config.codex !== false) {
|
|
16
|
+
user.push({ dir: path.join(agentsHome(), "skills"), source: "codex" });
|
|
17
|
+
user.push({ dir: path.join(codexHome(), "skills"), source: "codex" });
|
|
18
|
+
project.push({ dir: path.join(cwd, ".agents", "skills"), source: "codex" });
|
|
19
|
+
}
|
|
20
|
+
if (config.claudeCode !== false) {
|
|
21
|
+
user.push({ dir: path.join(claudeHome(), "skills"), source: "claude" });
|
|
22
|
+
project.push({ dir: path.join(cwd, ".claude", "skills"), source: "claude" });
|
|
23
|
+
}
|
|
24
|
+
return { user, project };
|
|
25
|
+
}
|
|
26
|
+
/** Agent directories, on the same terms. Codex has no agent files to read. */
|
|
27
|
+
export function foreignAgentRoots(cwd, config = {}) {
|
|
28
|
+
if (config.claudeCode === false)
|
|
29
|
+
return { user: [], project: [] };
|
|
30
|
+
return {
|
|
31
|
+
user: [{ dir: path.join(claudeHome(), "agents"), source: "claude" }],
|
|
32
|
+
project: [{ dir: path.join(cwd, ".claude", "agents"), source: "claude" }],
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Every server Claude Code and Codex declare for this project, in that order,
|
|
37
|
+
* then those inside their plugins. A plugin from either is loaded for its
|
|
38
|
+
* skills and agents, but its servers are never started on their own — see
|
|
39
|
+
* `pluginContributions` — so they are offered here like any other.
|
|
40
|
+
*/
|
|
41
|
+
export async function foreignMcpServers(cwd, config = {}, plugins = []) {
|
|
42
|
+
return [
|
|
43
|
+
...(config.claudeCode === false ? [] : await claudeMcpServers(cwd)),
|
|
44
|
+
...(config.codex === false ? [] : await codexMcpServers()),
|
|
45
|
+
...pluginServers(plugins.filter((plugin) => plugin.source === "claude" ? config.claudeCode !== false : plugin.source === "codex" && config.codex !== false)),
|
|
46
|
+
];
|
|
47
|
+
}
|
|
48
|
+
function pluginServers(plugins) {
|
|
49
|
+
return plugins.flatMap((plugin) => Object.entries(plugin.mcpServers ?? {}).map(([name, config]) => ({
|
|
50
|
+
name,
|
|
51
|
+
source: plugin.source,
|
|
52
|
+
scope: "plugin",
|
|
53
|
+
plugin: plugin.name,
|
|
54
|
+
...(plugin.title ? { pluginTitle: plugin.title } : {}),
|
|
55
|
+
file: plugin.directory,
|
|
56
|
+
config,
|
|
57
|
+
...secretFlag(config),
|
|
58
|
+
})));
|
|
59
|
+
}
|
|
60
|
+
/** Find one by the key the connector list sends back. */
|
|
61
|
+
export async function findForeignServer(cwd, key, config = {}, plugins = []) {
|
|
62
|
+
return (await foreignMcpServers(cwd, config, plugins)).find((one) => one.source === key.source && one.scope === key.scope && one.name === key.name &&
|
|
63
|
+
(one.plugin ?? "") === (key.plugin ?? ""));
|
|
64
|
+
}
|
|
65
|
+
async function claudeMcpServers(cwd) {
|
|
66
|
+
const out = [];
|
|
67
|
+
const state = claudeStateFile();
|
|
68
|
+
const raw = await readJson(state);
|
|
69
|
+
if (isRecord(raw)) {
|
|
70
|
+
out.push(...fromClaude(raw.mcpServers, "user", state));
|
|
71
|
+
const projects = isRecord(raw.projects) ? raw.projects : {};
|
|
72
|
+
const here = projects[path.resolve(cwd)];
|
|
73
|
+
if (isRecord(here))
|
|
74
|
+
out.push(...fromClaude(here.mcpServers, "local", state));
|
|
75
|
+
}
|
|
76
|
+
const shared = path.join(cwd, ".mcp.json");
|
|
77
|
+
const checkedIn = await readJson(shared);
|
|
78
|
+
if (isRecord(checkedIn))
|
|
79
|
+
out.push(...fromClaude(checkedIn.mcpServers, "project", shared));
|
|
80
|
+
return out;
|
|
81
|
+
}
|
|
82
|
+
function fromClaude(servers, scope, file) {
|
|
83
|
+
if (!isRecord(servers))
|
|
84
|
+
return [];
|
|
85
|
+
return Object.entries(servers).map(([name, entry]) => {
|
|
86
|
+
const translated = claudeServer(entry);
|
|
87
|
+
return "config" in translated
|
|
88
|
+
? { name, source: "claude", scope, file, config: translated.config, ...secretFlag(translated.config) }
|
|
89
|
+
: { name, source: "claude", scope, file, reason: translated.reason };
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
async function codexMcpServers() {
|
|
93
|
+
const file = path.join(codexHome(), "config.toml");
|
|
94
|
+
let table;
|
|
95
|
+
try {
|
|
96
|
+
table = parseToml(await readFile(file, "utf8"));
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
// Missing is the common case; a file Codex itself can read but this
|
|
100
|
+
// cannot is rarer, and either way there is nothing here to offer.
|
|
101
|
+
return [];
|
|
102
|
+
}
|
|
103
|
+
const servers = table["mcp_servers"];
|
|
104
|
+
if (!isTable(servers))
|
|
105
|
+
return [];
|
|
106
|
+
const out = [];
|
|
107
|
+
for (const [name, entry] of Object.entries(servers)) {
|
|
108
|
+
if (!isTable(entry))
|
|
109
|
+
continue;
|
|
110
|
+
// Switched off in Codex is a decision this list should not undo.
|
|
111
|
+
if (entry["enabled"] === false)
|
|
112
|
+
continue;
|
|
113
|
+
const config = codexServer(entry);
|
|
114
|
+
out.push(config
|
|
115
|
+
? { name, source: "codex", scope: "user", file, config, ...secretFlag(config) }
|
|
116
|
+
: { name, source: "codex", scope: "user", file, reason: "malformed" });
|
|
117
|
+
}
|
|
118
|
+
return out;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* One `[mcp_servers.<name>]` table in this harness's shape.
|
|
122
|
+
*
|
|
123
|
+
* Codex names environment variables rather than writing values in a few
|
|
124
|
+
* places — `bearer_token_env_var`, `env_http_headers` — and those become
|
|
125
|
+
* `${env:NAME}` references, so the token stays in the environment here too.
|
|
126
|
+
*/
|
|
127
|
+
function codexServer(entry) {
|
|
128
|
+
const timeout = typeof entry["tool_timeout_sec"] === "number" ? { timeoutMs: Math.round(entry["tool_timeout_sec"] * 1000) } : {};
|
|
129
|
+
const allowed = strings(entry["enabled_tools"]);
|
|
130
|
+
const tools = allowed ? { allowedTools: allowed } : {};
|
|
131
|
+
if (typeof entry["command"] === "string") {
|
|
132
|
+
const args = strings(entry["args"]);
|
|
133
|
+
const env = stringTable(entry["env"]);
|
|
134
|
+
return {
|
|
135
|
+
type: "stdio",
|
|
136
|
+
command: entry["command"],
|
|
137
|
+
...(args ? { args } : {}),
|
|
138
|
+
...(env ? { env } : {}),
|
|
139
|
+
...(typeof entry["cwd"] === "string" ? { cwd: entry["cwd"] } : {}),
|
|
140
|
+
...tools,
|
|
141
|
+
...timeout,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
if (typeof entry["url"] === "string") {
|
|
145
|
+
const headers = { ...stringTable(entry["http_headers"]) };
|
|
146
|
+
for (const [header, variable] of Object.entries(stringTable(entry["env_http_headers"]) ?? {})) {
|
|
147
|
+
headers[header] = `\${env:${variable}}`;
|
|
148
|
+
}
|
|
149
|
+
if (typeof entry["bearer_token_env_var"] === "string") {
|
|
150
|
+
headers["Authorization"] = `Bearer \${env:${entry["bearer_token_env_var"]}}`;
|
|
151
|
+
}
|
|
152
|
+
return {
|
|
153
|
+
type: "http",
|
|
154
|
+
url: entry["url"],
|
|
155
|
+
...(Object.keys(headers).length ? { headers } : {}),
|
|
156
|
+
...tools,
|
|
157
|
+
...timeout,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
return undefined;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Whether a credential is written out rather than referenced: any header, or
|
|
164
|
+
* an environment variable whose name says it is one. Only those: they are
|
|
165
|
+
* where credentials go, and `NODE_ENV=production` is not worth a warning.
|
|
166
|
+
*/
|
|
167
|
+
function secretFlag(config) {
|
|
168
|
+
const entries = Object.entries((config.type === "http" ? config.headers : config.env) ?? {});
|
|
169
|
+
const credential = (name) => config.type === "http" || /key|token|secret|pass|auth|credential/i.test(name);
|
|
170
|
+
const literal = (value) => value !== "" && !/^(Bearer )?\$\{(env|auth):[^}]+\}$/.test(value);
|
|
171
|
+
return entries.some(([name, value]) => credential(name) && literal(value)) ? { inlineSecret: true } : {};
|
|
172
|
+
}
|
|
173
|
+
// ---------------------------------------------------------------------------
|
|
174
|
+
async function readJson(file) {
|
|
175
|
+
try {
|
|
176
|
+
const parsed = parseJson(await readFile(file, "utf8"));
|
|
177
|
+
return parsed.ok ? parsed.value : undefined;
|
|
178
|
+
}
|
|
179
|
+
catch {
|
|
180
|
+
return undefined;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
function strings(value) {
|
|
184
|
+
return Array.isArray(value) ? value.filter((item) => typeof item === "string") : undefined;
|
|
185
|
+
}
|
|
186
|
+
function stringTable(value) {
|
|
187
|
+
if (!isTable(value))
|
|
188
|
+
return undefined;
|
|
189
|
+
const out = Object.fromEntries(Object.entries(value).filter(([, v]) => typeof v === "string"));
|
|
190
|
+
return Object.keys(out).length ? out : undefined;
|
|
191
|
+
}
|
|
192
|
+
function isTable(value) {
|
|
193
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
194
|
+
}
|
|
195
|
+
function isRecord(value) {
|
|
196
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
197
|
+
}
|
|
198
|
+
//# sourceMappingURL=foreign.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"foreign.js","sourceRoot":"","sources":["../src/foreign.ts"],"names":[],"mappings":"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"}
|
package/dist/harness.d.ts
CHANGED
|
@@ -441,6 +441,33 @@ export declare class Harness {
|
|
|
441
441
|
* which breaks the one loop that would let the agent improve itself.
|
|
442
442
|
*/
|
|
443
443
|
reloadSkills(): Promise<number>;
|
|
444
|
+
/**
|
|
445
|
+
* Switch one skill off or back on, now and for later launches.
|
|
446
|
+
*
|
|
447
|
+
* Off rather than deleted: most skills in the list are files another tool
|
|
448
|
+
* put there — Claude Code's, Codex's, a plugin's — and removing someone
|
|
449
|
+
* else's file to hide it from this harness would take it from that tool
|
|
450
|
+
* too.
|
|
451
|
+
*/
|
|
452
|
+
setSkillEnabled(name: string, on: boolean): Promise<void>;
|
|
453
|
+
/**
|
|
454
|
+
* Every markdown agent this harness reads, with the dialect each was
|
|
455
|
+
* written in: a Claude Code plugin's names tools and models the way
|
|
456
|
+
* Claude Code does, and is translated on the way in.
|
|
457
|
+
*/
|
|
458
|
+
private markdownAgents;
|
|
459
|
+
/**
|
|
460
|
+
* Take in a plugin that arrived while the harness was running.
|
|
461
|
+
*
|
|
462
|
+
* What `create` does for every plugin at launch, for one: its skills and
|
|
463
|
+
* agents join the registries, its servers connect. Without this an install
|
|
464
|
+
* from the menu would sit inert until a restart, which reads as a broken
|
|
465
|
+
* install. A server name the config already has is left to the config, the
|
|
466
|
+
* same precedence a launch gives it.
|
|
467
|
+
*/
|
|
468
|
+
adoptPlugin(directory: string): Promise<PluginDefinition | undefined>;
|
|
469
|
+
/** Undo `adoptPlugin` for a plugin whose directory is gone. */
|
|
470
|
+
dropPlugin(directory: string): Promise<void>;
|
|
444
471
|
/** Providers declared by the catalog, for `/models` and login hints. */
|
|
445
472
|
listCatalogProviders(): CatalogProvider[];
|
|
446
473
|
listModels(configuredOnly?: boolean): ModelSpec[];
|
package/dist/harness.js
CHANGED
|
@@ -6,6 +6,7 @@ import { HookRegistry } from "./agent/hooks.js";
|
|
|
6
6
|
import { Session } from "./agent/session.js";
|
|
7
7
|
import { SessionStore } from "./agent/store.js";
|
|
8
8
|
import { BUILTIN_SUBAGENTS, SubagentRegistry, loadMarkdownSubagents } from "./agent/subagent.js";
|
|
9
|
+
import { foreignAgentRoots, foreignSkillRoots } from "./foreign.js";
|
|
9
10
|
import { claudeAgent } from "./acp/claude.js";
|
|
10
11
|
import { SkillRegistry } from "./agent/skills.js";
|
|
11
12
|
import { SettingsStore } from "./agent/settings.js";
|
|
@@ -205,7 +206,14 @@ export class Harness {
|
|
|
205
206
|
...(config.skillDirs ?? []),
|
|
206
207
|
...(config.agentDirs ?? []),
|
|
207
208
|
],
|
|
208
|
-
|
|
209
|
+
// Claude Code's and Codex's skill and agent directories are read, so
|
|
210
|
+
// the agent can open a skill's scripts/, but not written: those belong
|
|
211
|
+
// to the user's other tools, not to anything this harness installed.
|
|
212
|
+
readableRoots: [
|
|
213
|
+
...(config.readableRoots ?? []),
|
|
214
|
+
...Object.values(foreignSkillRoots(this.cwd, config.imports)).flat().map((root) => root.dir),
|
|
215
|
+
...Object.values(foreignAgentRoots(this.cwd, config.imports)).flat().map((root) => root.dir),
|
|
216
|
+
],
|
|
209
217
|
});
|
|
210
218
|
this.mcp = new McpManager(this.logger, this.auth.store, (id) => this.auth.secret(id));
|
|
211
219
|
this.store = new SessionStore(config.sessionDir);
|
|
@@ -241,7 +249,7 @@ export class Harness {
|
|
|
241
249
|
: (await ensureProjectManifest(cwd)).id;
|
|
242
250
|
opts = { ...opts, cwd };
|
|
243
251
|
const baseConfig = opts.config ?? (await loadConfig(cwd));
|
|
244
|
-
const plugins = await discoverPlugins(cwd, baseConfig
|
|
252
|
+
const plugins = await discoverPlugins(cwd, pluginOptions(baseConfig));
|
|
245
253
|
// Explicit user/project config is applied after plugins, so it can always
|
|
246
254
|
// tighten policy or replace a plugin's MCP server declaration.
|
|
247
255
|
const config = mergeConfig(pluginContributions(plugins), baseConfig);
|
|
@@ -269,7 +277,8 @@ export class Harness {
|
|
|
269
277
|
})
|
|
270
278
|
.catch((err) => harness.logger.warn("session pruning failed", { error: String(err) }));
|
|
271
279
|
}
|
|
272
|
-
|
|
280
|
+
harness.skills.setDisabled(config.disabledSkills ?? []);
|
|
281
|
+
await harness.skills.discover(cwd, config.skillDirs ?? [], config.imports ?? {});
|
|
273
282
|
// Claude Agent, when the user turned it on and this machine has their
|
|
274
283
|
// own Claude Code to hand work to. Before the markdown ones, so a
|
|
275
284
|
// project can redefine it.
|
|
@@ -277,7 +286,7 @@ export class Harness {
|
|
|
277
286
|
await harness.registerClaudeAgent();
|
|
278
287
|
// Markdown definitions layer over the JSON ones, so a repo can drop in an
|
|
279
288
|
// agent file without editing config.
|
|
280
|
-
for (const definition of await
|
|
289
|
+
for (const definition of await harness.markdownAgents()) {
|
|
281
290
|
harness.subagents.register(definition);
|
|
282
291
|
}
|
|
283
292
|
if (config.mcpServers && Object.keys(config.mcpServers).length > 0) {
|
|
@@ -939,9 +948,109 @@ export class Harness {
|
|
|
939
948
|
* which breaks the one loop that would let the agent improve itself.
|
|
940
949
|
*/
|
|
941
950
|
async reloadSkills() {
|
|
942
|
-
await this.skills.discover(this.cwd, this.config.skillDirs ?? []);
|
|
951
|
+
await this.skills.discover(this.cwd, this.config.skillDirs ?? [], this.config.imports ?? {});
|
|
943
952
|
return this.skills.size;
|
|
944
953
|
}
|
|
954
|
+
/**
|
|
955
|
+
* Switch one skill off or back on, now and for later launches.
|
|
956
|
+
*
|
|
957
|
+
* Off rather than deleted: most skills in the list are files another tool
|
|
958
|
+
* put there — Claude Code's, Codex's, a plugin's — and removing someone
|
|
959
|
+
* else's file to hide it from this harness would take it from that tool
|
|
960
|
+
* too.
|
|
961
|
+
*/
|
|
962
|
+
async setSkillEnabled(name, on) {
|
|
963
|
+
await updateUserConfig((current) => {
|
|
964
|
+
const off = new Set(current.disabledSkills ?? []);
|
|
965
|
+
if (on)
|
|
966
|
+
off.delete(name);
|
|
967
|
+
else
|
|
968
|
+
off.add(name);
|
|
969
|
+
return { ...current, disabledSkills: [...off].sort() };
|
|
970
|
+
});
|
|
971
|
+
const off = new Set(this.config.disabledSkills ?? []);
|
|
972
|
+
if (on)
|
|
973
|
+
off.delete(name);
|
|
974
|
+
else
|
|
975
|
+
off.add(name);
|
|
976
|
+
this.config.disabledSkills = [...off];
|
|
977
|
+
this.skills.setDisabled(off);
|
|
978
|
+
}
|
|
979
|
+
/**
|
|
980
|
+
* Every markdown agent this harness reads, with the dialect each was
|
|
981
|
+
* written in: a Claude Code plugin's names tools and models the way
|
|
982
|
+
* Claude Code does, and is translated on the way in.
|
|
983
|
+
*/
|
|
984
|
+
async markdownAgents() {
|
|
985
|
+
const claude = new Set(this.plugins.filter((plugin) => plugin.claudeFormat).map((plugin) => plugin.agentDir));
|
|
986
|
+
const roots = (this.config.agentDirs ?? []).map((dir) => claude.has(dir) ? { dir, dialect: "claude" } : { dir });
|
|
987
|
+
return loadMarkdownSubagents(this.cwd, roots, this.config.imports ?? {});
|
|
988
|
+
}
|
|
989
|
+
/**
|
|
990
|
+
* Take in a plugin that arrived while the harness was running.
|
|
991
|
+
*
|
|
992
|
+
* What `create` does for every plugin at launch, for one: its skills and
|
|
993
|
+
* agents join the registries, its servers connect. Without this an install
|
|
994
|
+
* from the menu would sit inert until a restart, which reads as a broken
|
|
995
|
+
* install. A server name the config already has is left to the config, the
|
|
996
|
+
* same precedence a launch gives it.
|
|
997
|
+
*/
|
|
998
|
+
async adoptPlugin(directory) {
|
|
999
|
+
const plugin = (await discoverPlugins(this.cwd, pluginOptions(this.config))).find((one) => one.directory === directory);
|
|
1000
|
+
if (!plugin)
|
|
1001
|
+
return undefined;
|
|
1002
|
+
const known = this.plugins.findIndex((one) => one.name === plugin.name);
|
|
1003
|
+
if (known >= 0)
|
|
1004
|
+
this.plugins.splice(known, 1, plugin);
|
|
1005
|
+
else
|
|
1006
|
+
this.plugins.push(plugin);
|
|
1007
|
+
const contributed = pluginContributions([plugin]);
|
|
1008
|
+
this.config.skillDirs = [...new Set([...(this.config.skillDirs ?? []), ...contributed.skillDirs])];
|
|
1009
|
+
this.config.agentDirs = [...new Set([...(this.config.agentDirs ?? []), ...contributed.agentDirs])];
|
|
1010
|
+
await this.reloadSkills();
|
|
1011
|
+
for (const definition of contributed.subagents)
|
|
1012
|
+
this.subagents.register(definition);
|
|
1013
|
+
for (const definition of await this.markdownAgents()) {
|
|
1014
|
+
this.subagents.register(definition);
|
|
1015
|
+
}
|
|
1016
|
+
for (const [name, server] of Object.entries(contributed.mcpServers)) {
|
|
1017
|
+
if (this.config.mcpServers?.[name] || this.mcp.configFor(name))
|
|
1018
|
+
continue;
|
|
1019
|
+
this.config.mcpServers = { ...(this.config.mcpServers ?? {}), [name]: server };
|
|
1020
|
+
// One server that will not start is not a failed install; its row says
|
|
1021
|
+
// why, as any connector's does.
|
|
1022
|
+
await this.addMcpServer(name, server).catch((err) => this.logger.warn("plugin server unavailable", { server: name, error: String(err) }));
|
|
1023
|
+
}
|
|
1024
|
+
return plugin;
|
|
1025
|
+
}
|
|
1026
|
+
/** Undo `adoptPlugin` for a plugin whose directory is gone. */
|
|
1027
|
+
async dropPlugin(directory) {
|
|
1028
|
+
const index = this.plugins.findIndex((one) => one.directory === directory);
|
|
1029
|
+
if (index < 0)
|
|
1030
|
+
return;
|
|
1031
|
+
const [plugin] = this.plugins.splice(index, 1);
|
|
1032
|
+
const contributed = pluginContributions([plugin]);
|
|
1033
|
+
const before = await this.markdownAgents();
|
|
1034
|
+
this.config.skillDirs = (this.config.skillDirs ?? []).filter((dir) => !contributed.skillDirs.includes(dir));
|
|
1035
|
+
this.config.agentDirs = (this.config.agentDirs ?? []).filter((dir) => !contributed.agentDirs.includes(dir));
|
|
1036
|
+
await this.reloadSkills();
|
|
1037
|
+
const after = new Set((await this.markdownAgents()).map((one) => one.name));
|
|
1038
|
+
for (const definition of [...before, ...contributed.subagents]) {
|
|
1039
|
+
if (after.has(definition.name))
|
|
1040
|
+
continue;
|
|
1041
|
+
const registered = this.subagents.get(definition.name);
|
|
1042
|
+
if (registered)
|
|
1043
|
+
this.subagents.unregister(registered);
|
|
1044
|
+
}
|
|
1045
|
+
for (const name of Object.keys(contributed.mcpServers)) {
|
|
1046
|
+
const mine = this.config.mcpServers?.[name] === contributed.mcpServers[name];
|
|
1047
|
+
if (!mine)
|
|
1048
|
+
continue;
|
|
1049
|
+
const { [name]: _gone, ...rest } = this.config.mcpServers ?? {};
|
|
1050
|
+
this.config.mcpServers = rest;
|
|
1051
|
+
await this.removeMcpServer(name);
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
945
1054
|
/** Providers declared by the catalog, for `/models` and login hints. */
|
|
946
1055
|
listCatalogProviders() {
|
|
947
1056
|
return this.providers.listCatalogProviders();
|
|
@@ -1361,4 +1470,18 @@ export class Harness {
|
|
|
1361
1470
|
return first.id;
|
|
1362
1471
|
}
|
|
1363
1472
|
}
|
|
1473
|
+
/**
|
|
1474
|
+
* Plugin discovery, with `imports` deciding for the two other tools' caches
|
|
1475
|
+
* where `plugins` does not: switching Codex off is switching off its skills,
|
|
1476
|
+
* servers and plugins together.
|
|
1477
|
+
*/
|
|
1478
|
+
function pluginOptions(config) {
|
|
1479
|
+
const claudeCode = config.plugins?.claudeCode ?? config.imports?.claudeCode;
|
|
1480
|
+
const codex = config.plugins?.codex ?? config.imports?.codex;
|
|
1481
|
+
return {
|
|
1482
|
+
...config.plugins,
|
|
1483
|
+
...(claudeCode !== undefined ? { claudeCode } : {}),
|
|
1484
|
+
...(codex !== undefined ? { codex } : {}),
|
|
1485
|
+
};
|
|
1486
|
+
}
|
|
1364
1487
|
//# sourceMappingURL=harness.js.map
|