@clinebot/core 0.0.33 → 0.0.34
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/auth/client.d.ts +19 -0
- package/dist/auth/client.d.ts.map +1 -1
- package/dist/auth/cline.d.ts.map +1 -1
- package/dist/auth/oca.d.ts.map +1 -1
- package/dist/auth/server.d.ts +32 -0
- package/dist/auth/server.d.ts.map +1 -1
- package/dist/auth/types.d.ts +29 -0
- package/dist/auth/types.d.ts.map +1 -1
- package/dist/extensions/index.d.ts +2 -1
- package/dist/extensions/index.d.ts.map +1 -1
- package/dist/extensions/plugin/plugin-config-loader.d.ts +2 -1
- package/dist/extensions/plugin/plugin-config-loader.d.ts.map +1 -1
- package/dist/extensions/plugin/plugin-load-report.d.ts +19 -0
- package/dist/extensions/plugin/plugin-load-report.d.ts.map +1 -0
- package/dist/extensions/plugin/plugin-loader.d.ts +6 -0
- package/dist/extensions/plugin/plugin-loader.d.ts.map +1 -1
- package/dist/extensions/plugin/plugin-sandbox.d.ts +2 -1
- package/dist/extensions/plugin/plugin-sandbox.d.ts.map +1 -1
- package/dist/extensions/plugin-sandbox-bootstrap.js +148 -148
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +207 -207
- package/dist/runtime/runtime-builder.d.ts +1 -1
- package/dist/runtime/runtime-builder.d.ts.map +1 -1
- package/dist/runtime/subprocess-sandbox.d.ts +2 -0
- package/dist/runtime/subprocess-sandbox.d.ts.map +1 -1
- package/dist/runtime/tool-approval.d.ts.map +1 -1
- package/dist/session/default-session-manager.d.ts.map +1 -1
- package/dist/session/persistence-service.d.ts.map +1 -1
- package/dist/session/session-artifacts.d.ts +2 -0
- package/dist/session/session-artifacts.d.ts.map +1 -1
- package/dist/session/session-config-builder.d.ts.map +1 -1
- package/dist/team/team-tools.d.ts.map +1 -1
- package/dist/types/config.d.ts +1 -0
- package/dist/types/config.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/auth/client.test.ts +29 -0
- package/src/auth/client.ts +21 -0
- package/src/auth/cline.ts +2 -0
- package/src/auth/oca.ts +2 -0
- package/src/auth/server.test.ts +287 -0
- package/src/auth/server.ts +50 -1
- package/src/auth/types.ts +29 -0
- package/src/extensions/index.ts +6 -0
- package/src/extensions/plugin/plugin-config-loader.test.ts +37 -0
- package/src/extensions/plugin/plugin-config-loader.ts +18 -10
- package/src/extensions/plugin/plugin-load-report.ts +20 -0
- package/src/extensions/plugin/plugin-loader.test.ts +45 -0
- package/src/extensions/plugin/plugin-loader.ts +57 -3
- package/src/extensions/plugin/plugin-sandbox-bootstrap.ts +158 -86
- package/src/extensions/plugin/plugin-sandbox.test.ts +70 -0
- package/src/extensions/plugin/plugin-sandbox.ts +17 -6
- package/src/index.ts +11 -0
- package/src/runtime/hook-file-hooks.test.ts +42 -7
- package/src/runtime/runtime-builder.test.ts +98 -0
- package/src/runtime/runtime-builder.ts +112 -65
- package/src/runtime/subprocess-sandbox.ts +26 -23
- package/src/runtime/tool-approval.ts +13 -15
- package/src/session/default-session-manager.ts +1 -3
- package/src/session/persistence-service.test.ts +38 -0
- package/src/session/persistence-service.ts +16 -1
- package/src/session/session-artifacts.ts +16 -0
- package/src/session/session-config-builder.ts +46 -0
- package/src/team/team-tools.test.ts +104 -0
- package/src/team/team-tools.ts +35 -16
- package/src/types/config.ts +1 -0
- package/dist/runtime/team-runtime-registry.d.ts +0 -13
- package/dist/runtime/team-runtime-registry.d.ts.map +0 -1
- package/src/runtime/team-runtime-registry.ts +0 -43
|
@@ -4,6 +4,7 @@ import { dirname, join } from "node:path";
|
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
import type { AgentConfig, HookStage, Tool } from "@clinebot/shared";
|
|
6
6
|
import { SubprocessSandbox } from "../../runtime/subprocess-sandbox";
|
|
7
|
+
import type { PluginLoadDiagnostics } from "./plugin-load-report";
|
|
7
8
|
|
|
8
9
|
export interface PluginSandboxOptions {
|
|
9
10
|
pluginPaths: string[];
|
|
@@ -31,6 +32,7 @@ type SandboxedContributionDescriptor = {
|
|
|
31
32
|
|
|
32
33
|
type SandboxedPluginDescriptor = {
|
|
33
34
|
pluginId: string;
|
|
35
|
+
pluginPath: string;
|
|
34
36
|
name: string;
|
|
35
37
|
manifest: AgentExtension["manifest"];
|
|
36
38
|
contributions: {
|
|
@@ -43,6 +45,10 @@ type SandboxedPluginDescriptor = {
|
|
|
43
45
|
};
|
|
44
46
|
};
|
|
45
47
|
|
|
48
|
+
type SandboxedInitializeResult = {
|
|
49
|
+
plugins: SandboxedPluginDescriptor[];
|
|
50
|
+
} & PluginLoadDiagnostics;
|
|
51
|
+
|
|
46
52
|
function isUnknownPluginIdError(error: unknown): boolean {
|
|
47
53
|
const message = error instanceof Error ? error.message : String(error);
|
|
48
54
|
return message.includes("Unknown sandbox plugin id:");
|
|
@@ -153,10 +159,12 @@ function withTimeoutFallback(
|
|
|
153
159
|
|
|
154
160
|
export async function loadSandboxedPlugins(
|
|
155
161
|
options: PluginSandboxOptions,
|
|
156
|
-
): Promise<
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
162
|
+
): Promise<
|
|
163
|
+
{
|
|
164
|
+
extensions: AgentConfig["extensions"];
|
|
165
|
+
shutdown: () => Promise<void>;
|
|
166
|
+
} & PluginLoadDiagnostics
|
|
167
|
+
> {
|
|
160
168
|
const sandbox = new SubprocessSandbox({
|
|
161
169
|
name: "plugin-sandbox",
|
|
162
170
|
...("file" in BOOTSTRAP
|
|
@@ -187,9 +195,9 @@ export async function loadSandboxedPlugins(
|
|
|
187
195
|
return reinitPromise;
|
|
188
196
|
};
|
|
189
197
|
|
|
190
|
-
let
|
|
198
|
+
let initialized: SandboxedInitializeResult;
|
|
191
199
|
try {
|
|
192
|
-
|
|
200
|
+
initialized = await sandbox.call<SandboxedInitializeResult>(
|
|
193
201
|
"initialize",
|
|
194
202
|
initArgs,
|
|
195
203
|
{ timeoutMs: importTimeoutMs },
|
|
@@ -200,6 +208,7 @@ export async function loadSandboxedPlugins(
|
|
|
200
208
|
});
|
|
201
209
|
throw error;
|
|
202
210
|
}
|
|
211
|
+
const descriptors = initialized.plugins;
|
|
203
212
|
|
|
204
213
|
const extensions: NonNullable<AgentConfig["extensions"]> = descriptors.map(
|
|
205
214
|
(descriptor) => {
|
|
@@ -239,9 +248,11 @@ export async function loadSandboxedPlugins(
|
|
|
239
248
|
|
|
240
249
|
return {
|
|
241
250
|
extensions,
|
|
251
|
+
failures: initialized.failures,
|
|
242
252
|
shutdown: async () => {
|
|
243
253
|
await sandbox.shutdown();
|
|
244
254
|
},
|
|
255
|
+
warnings: initialized.warnings,
|
|
245
256
|
};
|
|
246
257
|
}
|
|
247
258
|
|
package/src/index.ts
CHANGED
|
@@ -128,6 +128,13 @@ export {
|
|
|
128
128
|
OCI_HEADER_OPC_REQUEST_ID,
|
|
129
129
|
refreshOcaToken,
|
|
130
130
|
} from "./auth/oca";
|
|
131
|
+
export type {
|
|
132
|
+
LocalOAuthServer,
|
|
133
|
+
LocalOAuthServerOptions,
|
|
134
|
+
OAuthCallbackPayload,
|
|
135
|
+
OAuthServerCloseInfo,
|
|
136
|
+
OAuthServerListeningInfo,
|
|
137
|
+
} from "./auth/server";
|
|
131
138
|
export { startLocalOAuthServer } from "./auth/server";
|
|
132
139
|
export type {
|
|
133
140
|
OAuthCredentials,
|
|
@@ -163,12 +170,16 @@ export {
|
|
|
163
170
|
} from "./chat/chat-schema";
|
|
164
171
|
export type {
|
|
165
172
|
LoadAgentPluginFromPathOptions,
|
|
173
|
+
PluginInitializationFailure,
|
|
174
|
+
PluginInitializationWarning,
|
|
175
|
+
PluginLoadDiagnostics,
|
|
166
176
|
ResolveAgentPluginPathsOptions,
|
|
167
177
|
} from "./extensions";
|
|
168
178
|
export {
|
|
169
179
|
discoverPluginModulePaths,
|
|
170
180
|
loadAgentPluginFromPath,
|
|
171
181
|
loadAgentPluginsFromPaths,
|
|
182
|
+
loadAgentPluginsFromPathsWithDiagnostics,
|
|
172
183
|
resolveAgentPluginPaths,
|
|
173
184
|
resolveAndLoadAgentPlugins,
|
|
174
185
|
resolvePluginConfigSearchPaths,
|
|
@@ -50,7 +50,12 @@ describe("createHookConfigFileHooks", () => {
|
|
|
50
50
|
});
|
|
51
51
|
expect(hooks).toBeUndefined();
|
|
52
52
|
} finally {
|
|
53
|
-
await rm(workspace, {
|
|
53
|
+
await rm(workspace, {
|
|
54
|
+
recursive: true,
|
|
55
|
+
force: true,
|
|
56
|
+
maxRetries: 3,
|
|
57
|
+
retryDelay: 250,
|
|
58
|
+
});
|
|
54
59
|
}
|
|
55
60
|
});
|
|
56
61
|
|
|
@@ -78,7 +83,12 @@ describe("createHookConfigFileHooks", () => {
|
|
|
78
83
|
});
|
|
79
84
|
expect(control).toMatchObject({ cancel: true, context: "legacy-ok" });
|
|
80
85
|
} finally {
|
|
81
|
-
await rm(workspace, {
|
|
86
|
+
await rm(workspace, {
|
|
87
|
+
recursive: true,
|
|
88
|
+
force: true,
|
|
89
|
+
maxRetries: 3,
|
|
90
|
+
retryDelay: 250,
|
|
91
|
+
});
|
|
82
92
|
}
|
|
83
93
|
});
|
|
84
94
|
|
|
@@ -106,7 +116,12 @@ describe("createHookConfigFileHooks", () => {
|
|
|
106
116
|
});
|
|
107
117
|
expect(control).toMatchObject({ cancel: false, context: "shebang-ok" });
|
|
108
118
|
} finally {
|
|
109
|
-
await rm(workspace, {
|
|
119
|
+
await rm(workspace, {
|
|
120
|
+
recursive: true,
|
|
121
|
+
force: true,
|
|
122
|
+
maxRetries: 3,
|
|
123
|
+
retryDelay: 250,
|
|
124
|
+
});
|
|
110
125
|
}
|
|
111
126
|
});
|
|
112
127
|
|
|
@@ -137,7 +152,12 @@ describe("createHookConfigFileHooks", () => {
|
|
|
137
152
|
context: "needs-review",
|
|
138
153
|
});
|
|
139
154
|
} finally {
|
|
140
|
-
await rm(workspace, {
|
|
155
|
+
await rm(workspace, {
|
|
156
|
+
recursive: true,
|
|
157
|
+
force: true,
|
|
158
|
+
maxRetries: 3,
|
|
159
|
+
retryDelay: 250,
|
|
160
|
+
});
|
|
141
161
|
}
|
|
142
162
|
});
|
|
143
163
|
|
|
@@ -168,7 +188,12 @@ describe("createHookConfigFileHooks", () => {
|
|
|
168
188
|
context: "python-ok",
|
|
169
189
|
});
|
|
170
190
|
} finally {
|
|
171
|
-
await rm(workspace, {
|
|
191
|
+
await rm(workspace, {
|
|
192
|
+
recursive: true,
|
|
193
|
+
force: true,
|
|
194
|
+
maxRetries: 3,
|
|
195
|
+
retryDelay: 250,
|
|
196
|
+
});
|
|
172
197
|
}
|
|
173
198
|
});
|
|
174
199
|
|
|
@@ -201,7 +226,12 @@ describe("createHookConfigFileHooks", () => {
|
|
|
201
226
|
context: "powershell-ok",
|
|
202
227
|
});
|
|
203
228
|
} finally {
|
|
204
|
-
await rm(workspace, {
|
|
229
|
+
await rm(workspace, {
|
|
230
|
+
recursive: true,
|
|
231
|
+
force: true,
|
|
232
|
+
maxRetries: 3,
|
|
233
|
+
retryDelay: 250,
|
|
234
|
+
});
|
|
205
235
|
}
|
|
206
236
|
},
|
|
207
237
|
);
|
|
@@ -231,7 +261,12 @@ describe("createHookConfigFileHooks", () => {
|
|
|
231
261
|
expect(payload.hookName).toBe("agent_error");
|
|
232
262
|
expect(payload.error?.message).toBe("401 unauthorized");
|
|
233
263
|
} finally {
|
|
234
|
-
await rm(workspace, {
|
|
264
|
+
await rm(workspace, {
|
|
265
|
+
recursive: true,
|
|
266
|
+
force: true,
|
|
267
|
+
maxRetries: 3,
|
|
268
|
+
retryDelay: 250,
|
|
269
|
+
});
|
|
235
270
|
}
|
|
236
271
|
});
|
|
237
272
|
|
|
@@ -186,6 +186,22 @@ describe("DefaultRuntimeBuilder", () => {
|
|
|
186
186
|
expect(runtime.tools).toEqual([]);
|
|
187
187
|
});
|
|
188
188
|
|
|
189
|
+
it("omits tools disabled by policy from the advertised runtime tool list", async () => {
|
|
190
|
+
const runtime = await new DefaultRuntimeBuilder().build({
|
|
191
|
+
config: makeBaseConfig({
|
|
192
|
+
toolPolicies: {
|
|
193
|
+
run_commands: { enabled: false },
|
|
194
|
+
read_files: { enabled: false },
|
|
195
|
+
},
|
|
196
|
+
}),
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
const names = runtime.tools.map((tool) => tool.name);
|
|
200
|
+
expect(names).not.toContain("run_commands");
|
|
201
|
+
expect(names).not.toContain("read_files");
|
|
202
|
+
expect(names).toContain("search_codebase");
|
|
203
|
+
});
|
|
204
|
+
|
|
189
205
|
it("adds spawn tool when enabled", async () => {
|
|
190
206
|
const runtime = await new DefaultRuntimeBuilder().build({
|
|
191
207
|
config: makeBaseConfig({
|
|
@@ -284,6 +300,88 @@ process.stdin.on("data", (chunk) => {
|
|
|
284
300
|
}
|
|
285
301
|
});
|
|
286
302
|
|
|
303
|
+
it("skips MCP settings tools when disableMcpSettingsTools is true", async () => {
|
|
304
|
+
const tempRoot = mkdtempSync(
|
|
305
|
+
join(tmpdir(), "runtime-builder-mcp-disabled-"),
|
|
306
|
+
);
|
|
307
|
+
const serverPath = join(tempRoot, "mock-mcp-server.js");
|
|
308
|
+
const settingsPath = join(tempRoot, "cline_mcp_settings.json");
|
|
309
|
+
const previousSettingsPath = process.env.CLINE_MCP_SETTINGS_PATH;
|
|
310
|
+
|
|
311
|
+
writeFileSync(
|
|
312
|
+
serverPath,
|
|
313
|
+
`let buffer = "";
|
|
314
|
+
function write(payload) {
|
|
315
|
+
const body = JSON.stringify(payload);
|
|
316
|
+
process.stdout.write("Content-Length: " + Buffer.byteLength(body, "utf8") + "\\r\\n\\r\\n" + body);
|
|
317
|
+
}
|
|
318
|
+
function handle(message) {
|
|
319
|
+
if (message.method === "initialize") {
|
|
320
|
+
write({ jsonrpc: "2.0", id: message.id, result: { protocolVersion: "2024-11-05", capabilities: { tools: {} }, serverInfo: { name: "mock", version: "1.0.0" } } });
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
if (message.method === "tools/list") {
|
|
324
|
+
write({ jsonrpc: "2.0", id: message.id, result: { tools: [{ name: "echo", description: "Echo tool", inputSchema: { type: "object", properties: { value: { type: "string" } }, required: [] } }] } });
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
if (message.method === "tools/call") {
|
|
328
|
+
write({ jsonrpc: "2.0", id: message.id, result: { echoed: message.params?.arguments?.value ?? null } });
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
process.stdin.on("data", (chunk) => {
|
|
332
|
+
buffer += chunk.toString("utf8");
|
|
333
|
+
while (true) {
|
|
334
|
+
const separator = buffer.indexOf("\\r\\n\\r\\n");
|
|
335
|
+
if (separator < 0) break;
|
|
336
|
+
const header = buffer.slice(0, separator);
|
|
337
|
+
const match = header.match(/Content-Length:\\s*(\\d+)/i);
|
|
338
|
+
if (!match) throw new Error("missing content length");
|
|
339
|
+
const length = Number(match[1]);
|
|
340
|
+
const start = separator + 4;
|
|
341
|
+
const end = start + length;
|
|
342
|
+
if (buffer.length < end) break;
|
|
343
|
+
const body = buffer.slice(start, end);
|
|
344
|
+
buffer = buffer.slice(end);
|
|
345
|
+
const message = JSON.parse(body);
|
|
346
|
+
if (message.method === "notifications/initialized") continue;
|
|
347
|
+
handle(message);
|
|
348
|
+
}
|
|
349
|
+
});`,
|
|
350
|
+
"utf8",
|
|
351
|
+
);
|
|
352
|
+
writeFileSync(
|
|
353
|
+
settingsPath,
|
|
354
|
+
JSON.stringify(
|
|
355
|
+
{
|
|
356
|
+
mcpServers: {
|
|
357
|
+
mock: {
|
|
358
|
+
command: process.execPath,
|
|
359
|
+
args: [serverPath],
|
|
360
|
+
},
|
|
361
|
+
},
|
|
362
|
+
},
|
|
363
|
+
null,
|
|
364
|
+
2,
|
|
365
|
+
),
|
|
366
|
+
"utf8",
|
|
367
|
+
);
|
|
368
|
+
|
|
369
|
+
process.env.CLINE_MCP_SETTINGS_PATH = settingsPath;
|
|
370
|
+
try {
|
|
371
|
+
const runtime = await new DefaultRuntimeBuilder().build({
|
|
372
|
+
config: makeBaseConfig({
|
|
373
|
+
disableMcpSettingsTools: true,
|
|
374
|
+
}),
|
|
375
|
+
});
|
|
376
|
+
expect(runtime.tools.map((tool) => tool.name)).not.toContain(
|
|
377
|
+
"mock__echo",
|
|
378
|
+
);
|
|
379
|
+
await runtime.shutdown("test");
|
|
380
|
+
} finally {
|
|
381
|
+
process.env.CLINE_MCP_SETTINGS_PATH = previousSettingsPath;
|
|
382
|
+
}
|
|
383
|
+
});
|
|
384
|
+
|
|
287
385
|
it("skips broken MCP servers without crashing", async () => {
|
|
288
386
|
const tempRoot = mkdtempSync(join(tmpdir(), "runtime-builder-mcp-bad-"));
|
|
289
387
|
const serverPath = join(tempRoot, "malformed-mcp-server.js");
|
|
@@ -39,7 +39,6 @@ import type {
|
|
|
39
39
|
RuntimeBuilderInput,
|
|
40
40
|
BuiltRuntime as RuntimeEnvironment,
|
|
41
41
|
} from "./session-runtime";
|
|
42
|
-
import { TeamRuntimeRegistry } from "./team-runtime-registry";
|
|
43
42
|
|
|
44
43
|
type SkillsExecutorMetadataItem = {
|
|
45
44
|
id: string;
|
|
@@ -52,6 +51,29 @@ type SkillsExecutorWithMetadata = SkillsExecutor & {
|
|
|
52
51
|
configuredSkills?: SkillsExecutorMetadataItem[];
|
|
53
52
|
};
|
|
54
53
|
|
|
54
|
+
function isToolEnabledByPolicies(
|
|
55
|
+
toolName: string,
|
|
56
|
+
toolPolicies: CoreSessionConfig["toolPolicies"],
|
|
57
|
+
): boolean {
|
|
58
|
+
const globalPolicy = toolPolicies?.["*"] ?? {};
|
|
59
|
+
const toolPolicy = toolPolicies?.[toolName] ?? {};
|
|
60
|
+
return (
|
|
61
|
+
{
|
|
62
|
+
...globalPolicy,
|
|
63
|
+
...toolPolicy,
|
|
64
|
+
}.enabled !== false
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function filterToolsByPolicies(
|
|
69
|
+
tools: Tool[],
|
|
70
|
+
toolPolicies: CoreSessionConfig["toolPolicies"],
|
|
71
|
+
): Tool[] {
|
|
72
|
+
return tools.filter((tool) =>
|
|
73
|
+
isToolEnabledByPolicies(tool.name, toolPolicies),
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
55
77
|
export function createTeamName(): string {
|
|
56
78
|
return `team-${nanoid(5)}`;
|
|
57
79
|
}
|
|
@@ -63,6 +85,7 @@ function createBuiltinToolsList(
|
|
|
63
85
|
yolo: boolean | undefined,
|
|
64
86
|
modelId: string,
|
|
65
87
|
toolRoutingRules: ToolRoutingRule[] | undefined,
|
|
88
|
+
toolPolicies: CoreSessionConfig["toolPolicies"],
|
|
66
89
|
skillsExecutor?: SkillsExecutorWithMetadata,
|
|
67
90
|
executorOverrides?: Partial<ToolExecutors>,
|
|
68
91
|
): Tool[] {
|
|
@@ -74,20 +97,23 @@ function createBuiltinToolsList(
|
|
|
74
97
|
toolRoutingRules ?? DEFAULT_MODEL_TOOL_ROUTING_RULES,
|
|
75
98
|
);
|
|
76
99
|
|
|
77
|
-
return
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
100
|
+
return filterToolsByPolicies(
|
|
101
|
+
createBuiltinTools({
|
|
102
|
+
cwd,
|
|
103
|
+
...preset,
|
|
104
|
+
enableSkills: !!skillsExecutor,
|
|
105
|
+
...toolRoutingConfig,
|
|
106
|
+
executors: {
|
|
107
|
+
...(skillsExecutor
|
|
108
|
+
? {
|
|
109
|
+
skills: skillsExecutor,
|
|
110
|
+
}
|
|
111
|
+
: {}),
|
|
112
|
+
...(executorOverrides ?? {}),
|
|
113
|
+
},
|
|
114
|
+
}),
|
|
115
|
+
toolPolicies,
|
|
116
|
+
);
|
|
91
117
|
}
|
|
92
118
|
|
|
93
119
|
const SKILL_FILE_NAME = "SKILL.md";
|
|
@@ -96,7 +122,7 @@ function listAvailableSkillNames(
|
|
|
96
122
|
watcher: UserInstructionConfigWatcher,
|
|
97
123
|
allowedSkillNames?: ReadonlyArray<string>,
|
|
98
124
|
): string[] {
|
|
99
|
-
return
|
|
125
|
+
return getConfiguredSkills(watcher, allowedSkillNames)
|
|
100
126
|
.filter((skill) => !skill.disabled)
|
|
101
127
|
.map((skill) => skill.name.trim())
|
|
102
128
|
.filter((name) => name.length > 0)
|
|
@@ -143,10 +169,14 @@ function isSkillAllowed(
|
|
|
143
169
|
);
|
|
144
170
|
}
|
|
145
171
|
|
|
146
|
-
|
|
172
|
+
type ConfiguredSkill = SkillsExecutorMetadataItem & {
|
|
173
|
+
skill: SkillConfig;
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
function getConfiguredSkills(
|
|
147
177
|
watcher: UserInstructionConfigWatcher,
|
|
148
178
|
allowedSkillNames?: ReadonlyArray<string>,
|
|
149
|
-
):
|
|
179
|
+
): ConfiguredSkill[] {
|
|
150
180
|
const allowedSkills = toAllowedSkillSet(allowedSkillNames);
|
|
151
181
|
const snapshot = watcher.getSnapshot("skill");
|
|
152
182
|
return [...snapshot.entries()]
|
|
@@ -157,6 +187,7 @@ function listConfiguredSkills(
|
|
|
157
187
|
name: skill.name.trim(),
|
|
158
188
|
description: skill.description?.trim(),
|
|
159
189
|
disabled: skill.disabled === true,
|
|
190
|
+
skill,
|
|
160
191
|
};
|
|
161
192
|
})
|
|
162
193
|
.filter((skill) => isSkillAllowed(skill.id, skill.name, allowedSkills));
|
|
@@ -254,28 +285,22 @@ function resolveSkillRecord(
|
|
|
254
285
|
requestedSkill: string,
|
|
255
286
|
allowedSkillNames?: ReadonlyArray<string>,
|
|
256
287
|
): { id: string; skill: SkillConfig } | { error: string } {
|
|
257
|
-
const allowedSkills = toAllowedSkillSet(allowedSkillNames);
|
|
258
288
|
const normalized = requestedSkill.trim().replace(/^\/+/, "").toLowerCase();
|
|
259
289
|
if (!normalized) {
|
|
260
290
|
return { error: "Missing skill name." };
|
|
261
291
|
}
|
|
262
292
|
|
|
263
|
-
const
|
|
264
|
-
const
|
|
265
|
-
const skill = record.item as SkillConfig;
|
|
266
|
-
return isSkillAllowed(id, skill.name, allowedSkills);
|
|
267
|
-
});
|
|
268
|
-
const scopedSnapshot = new Map(scopedEntries);
|
|
269
|
-
const exact = scopedSnapshot.get(normalized);
|
|
293
|
+
const configuredSkills = getConfiguredSkills(watcher, allowedSkillNames);
|
|
294
|
+
const exact = configuredSkills.find((entry) => entry.id === normalized);
|
|
270
295
|
if (exact) {
|
|
271
|
-
const skill = exact
|
|
296
|
+
const { skill } = exact;
|
|
272
297
|
if (skill.disabled === true) {
|
|
273
298
|
return {
|
|
274
299
|
error: `Skill "${skill.name}" is configured but disabled.`,
|
|
275
300
|
};
|
|
276
301
|
}
|
|
277
302
|
return {
|
|
278
|
-
id:
|
|
303
|
+
id: exact.id,
|
|
279
304
|
skill,
|
|
280
305
|
};
|
|
281
306
|
}
|
|
@@ -284,30 +309,38 @@ function resolveSkillRecord(
|
|
|
284
309
|
? (normalized.split(":").at(-1) ?? normalized)
|
|
285
310
|
: normalized;
|
|
286
311
|
|
|
287
|
-
const suffixMatches =
|
|
312
|
+
const suffixMatches = configuredSkills.filter(({ id }) => {
|
|
288
313
|
if (id === bareName) {
|
|
289
314
|
return true;
|
|
290
315
|
}
|
|
291
316
|
return id.endsWith(`:${bareName}`);
|
|
292
317
|
});
|
|
293
318
|
|
|
319
|
+
const enabledSuffixMatches = suffixMatches.filter(
|
|
320
|
+
({ skill }) => skill.disabled !== true,
|
|
321
|
+
);
|
|
322
|
+
|
|
323
|
+
if (enabledSuffixMatches.length === 1) {
|
|
324
|
+
const { id, skill } = enabledSuffixMatches[0];
|
|
325
|
+
return { id, skill };
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
if (enabledSuffixMatches.length > 1) {
|
|
329
|
+
return {
|
|
330
|
+
error: `Skill "${requestedSkill}" is ambiguous. Use one of: ${enabledSuffixMatches.map(({ id }) => id).join(", ")}`,
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
|
|
294
334
|
if (suffixMatches.length === 1) {
|
|
295
|
-
const
|
|
296
|
-
const skill = record.item as SkillConfig;
|
|
297
|
-
if (skill.disabled === true) {
|
|
298
|
-
return {
|
|
299
|
-
error: `Skill "${skill.name}" is configured but disabled.`,
|
|
300
|
-
};
|
|
301
|
-
}
|
|
335
|
+
const { skill } = suffixMatches[0];
|
|
302
336
|
return {
|
|
303
|
-
|
|
304
|
-
skill,
|
|
337
|
+
error: `Skill "${skill.name}" is configured but disabled.`,
|
|
305
338
|
};
|
|
306
339
|
}
|
|
307
340
|
|
|
308
341
|
if (suffixMatches.length > 1) {
|
|
309
342
|
return {
|
|
310
|
-
error: `Skill "${requestedSkill}" is ambiguous
|
|
343
|
+
error: `Skill "${requestedSkill}" is ambiguous, and all matches are disabled: ${suffixMatches.map(({ id }) => id).join(", ")}`,
|
|
311
344
|
};
|
|
312
345
|
}
|
|
313
346
|
|
|
@@ -354,7 +387,10 @@ function createSkillsExecutor(
|
|
|
354
387
|
}
|
|
355
388
|
};
|
|
356
389
|
Object.defineProperty(executor, "configuredSkills", {
|
|
357
|
-
get: () =>
|
|
390
|
+
get: () =>
|
|
391
|
+
getConfiguredSkills(watcher, allowedSkillNames).map(
|
|
392
|
+
({ skill: _skill, ...metadata }) => metadata,
|
|
393
|
+
),
|
|
358
394
|
enumerable: true,
|
|
359
395
|
configurable: false,
|
|
360
396
|
});
|
|
@@ -386,6 +422,7 @@ function normalizeConfig(
|
|
|
386
422
|
| "enableTools"
|
|
387
423
|
| "enableSpawnAgent"
|
|
388
424
|
| "enableAgentTeams"
|
|
425
|
+
| "disableMcpSettingsTools"
|
|
389
426
|
| "yolo"
|
|
390
427
|
| "missionLogIntervalSteps"
|
|
391
428
|
| "missionLogIntervalMs"
|
|
@@ -407,6 +444,7 @@ function normalizeConfig(
|
|
|
407
444
|
config.enableSpawnAgent ?? preset.enableSpawnAgent ?? true,
|
|
408
445
|
enableAgentTeams:
|
|
409
446
|
config.enableAgentTeams ?? preset.enableAgentTeams ?? true,
|
|
447
|
+
disableMcpSettingsTools: config.disableMcpSettingsTools === true,
|
|
410
448
|
yolo: config.yolo === true,
|
|
411
449
|
missionLogIntervalSteps:
|
|
412
450
|
typeof config.missionLogIntervalSteps === "number" &&
|
|
@@ -422,7 +460,15 @@ function normalizeConfig(
|
|
|
422
460
|
}
|
|
423
461
|
|
|
424
462
|
export class DefaultRuntimeBuilder implements RuntimeBuilder {
|
|
425
|
-
private readonly
|
|
463
|
+
private readonly teamRuntimeEntries = new Map<
|
|
464
|
+
string,
|
|
465
|
+
{
|
|
466
|
+
runtime?: AgentTeamsRuntime;
|
|
467
|
+
delegatedAgentConfigProvider: ReturnType<
|
|
468
|
+
typeof createDelegatedAgentConfigProvider
|
|
469
|
+
>;
|
|
470
|
+
}
|
|
471
|
+
>();
|
|
426
472
|
|
|
427
473
|
async build(input: RuntimeBuilderInput): Promise<RuntimeEnvironment> {
|
|
428
474
|
const {
|
|
@@ -440,6 +486,7 @@ export class DefaultRuntimeBuilder implements RuntimeBuilder {
|
|
|
440
486
|
const normalized = normalizeConfig(config);
|
|
441
487
|
const tools: Tool[] = [];
|
|
442
488
|
const effectiveTeamName = config.teamName?.trim() || createTeamName();
|
|
489
|
+
const hasLocalSkills = hasSkillsFiles(config.cwd);
|
|
443
490
|
let teamToolsRegistered = false;
|
|
444
491
|
const watcherProvided = Boolean(sharedUserInstructionWatcher);
|
|
445
492
|
let userInstructionWatcher = sharedUserInstructionWatcher;
|
|
@@ -447,11 +494,7 @@ export class DefaultRuntimeBuilder implements RuntimeBuilder {
|
|
|
447
494
|
let skillsExecutor: SkillsExecutorWithMetadata | undefined;
|
|
448
495
|
let mcpShutdown: (() => Promise<void>) | undefined;
|
|
449
496
|
|
|
450
|
-
if (
|
|
451
|
-
!userInstructionWatcher &&
|
|
452
|
-
normalized.enableTools &&
|
|
453
|
-
hasSkillsFiles(config.cwd)
|
|
454
|
-
) {
|
|
497
|
+
if (!userInstructionWatcher && normalized.enableTools && hasLocalSkills) {
|
|
455
498
|
userInstructionWatcher = createUserInstructionConfigWatcher({
|
|
456
499
|
skills: { workspacePath: config.cwd },
|
|
457
500
|
rules: { workspacePath: config.cwd },
|
|
@@ -464,8 +507,8 @@ export class DefaultRuntimeBuilder implements RuntimeBuilder {
|
|
|
464
507
|
normalized.enableTools &&
|
|
465
508
|
userInstructionWatcher &&
|
|
466
509
|
(watcherProvided ||
|
|
467
|
-
|
|
468
|
-
|
|
510
|
+
hasLocalSkills ||
|
|
511
|
+
getConfiguredSkills(userInstructionWatcher, config.skills).length > 0)
|
|
469
512
|
) {
|
|
470
513
|
skillsExecutor = createSkillsExecutor(
|
|
471
514
|
userInstructionWatcher,
|
|
@@ -483,13 +526,16 @@ export class DefaultRuntimeBuilder implements RuntimeBuilder {
|
|
|
483
526
|
normalized.yolo,
|
|
484
527
|
config.modelId,
|
|
485
528
|
config.toolRoutingRules,
|
|
529
|
+
config.toolPolicies,
|
|
486
530
|
skillsExecutor,
|
|
487
531
|
defaultToolExecutors,
|
|
488
532
|
),
|
|
489
533
|
);
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
534
|
+
if (!normalized.disableMcpSettingsTools) {
|
|
535
|
+
const mcpRuntime = await loadConfiguredMcpTools(config.logger);
|
|
536
|
+
tools.push(...mcpRuntime.tools);
|
|
537
|
+
mcpShutdown = mcpRuntime.shutdown;
|
|
538
|
+
}
|
|
493
539
|
}
|
|
494
540
|
|
|
495
541
|
let teamRuntime: AgentTeamsRuntime | undefined;
|
|
@@ -526,21 +572,21 @@ export class DefaultRuntimeBuilder implements RuntimeBuilder {
|
|
|
526
572
|
telemetry: input.telemetry ?? config.telemetry,
|
|
527
573
|
workspaceMetadata: config.workspaceMetadata,
|
|
528
574
|
});
|
|
529
|
-
this.
|
|
530
|
-
|
|
531
|
-
|
|
575
|
+
if (!this.teamRuntimeEntries.has(registryKey)) {
|
|
576
|
+
this.teamRuntimeEntries.set(registryKey, {
|
|
577
|
+
delegatedAgentConfigProvider,
|
|
578
|
+
});
|
|
579
|
+
}
|
|
532
580
|
|
|
533
581
|
const ensureTeamRuntime = (): AgentTeamsRuntime | undefined => {
|
|
534
582
|
if (!normalized.enableAgentTeams) {
|
|
535
583
|
return undefined;
|
|
536
584
|
}
|
|
537
585
|
|
|
538
|
-
const registryEntry = this.
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
}),
|
|
543
|
-
);
|
|
586
|
+
const registryEntry = this.teamRuntimeEntries.get(registryKey) ?? {
|
|
587
|
+
delegatedAgentConfigProvider,
|
|
588
|
+
};
|
|
589
|
+
this.teamRuntimeEntries.set(registryKey, registryEntry);
|
|
544
590
|
teamRuntime = registryEntry.runtime;
|
|
545
591
|
|
|
546
592
|
if (!teamRuntime) {
|
|
@@ -592,7 +638,7 @@ export class DefaultRuntimeBuilder implements RuntimeBuilder {
|
|
|
592
638
|
const factory = input.teamToolsFactory ?? bootstrapAgentTeams;
|
|
593
639
|
const teamBootstrap = factory({
|
|
594
640
|
runtime: teamRuntime,
|
|
595
|
-
leadAgentId: "lead",
|
|
641
|
+
leadAgentId: config.sessionId || "lead",
|
|
596
642
|
restoredFromPersistence: Boolean(restoredTeamState),
|
|
597
643
|
restoredTeammates: restoredTeammateSpecs,
|
|
598
644
|
includeLeadSpawnTool: true,
|
|
@@ -610,6 +656,7 @@ export class DefaultRuntimeBuilder implements RuntimeBuilder {
|
|
|
610
656
|
normalized.yolo,
|
|
611
657
|
config.modelId,
|
|
612
658
|
config.toolRoutingRules,
|
|
659
|
+
config.toolPolicies,
|
|
613
660
|
skillsExecutor,
|
|
614
661
|
defaultToolExecutors,
|
|
615
662
|
)
|
|
@@ -643,7 +690,7 @@ export class DefaultRuntimeBuilder implements RuntimeBuilder {
|
|
|
643
690
|
|
|
644
691
|
const completionGuard = normalized.enableAgentTeams
|
|
645
692
|
? () => {
|
|
646
|
-
const rt = this.
|
|
693
|
+
const rt = this.teamRuntimeEntries.get(registryKey)?.runtime;
|
|
647
694
|
if (!rt) return undefined;
|
|
648
695
|
const tasks = rt.listTasks();
|
|
649
696
|
const hasInProgress = tasks.some(
|
|
@@ -675,13 +722,13 @@ export class DefaultRuntimeBuilder implements RuntimeBuilder {
|
|
|
675
722
|
: undefined;
|
|
676
723
|
|
|
677
724
|
return {
|
|
678
|
-
tools,
|
|
725
|
+
tools: filterToolsByPolicies(tools, config.toolPolicies),
|
|
679
726
|
logger: logger ?? config.logger,
|
|
680
727
|
telemetry: telemetry ?? config.telemetry,
|
|
681
728
|
teamRuntime,
|
|
682
729
|
teamRestoredFromPersistence: Boolean(restoredTeamState),
|
|
683
730
|
delegatedAgentConfigProvider:
|
|
684
|
-
this.
|
|
731
|
+
this.teamRuntimeEntries.get(registryKey)
|
|
685
732
|
?.delegatedAgentConfigProvider ?? delegatedAgentConfigProvider,
|
|
686
733
|
completionGuard,
|
|
687
734
|
registerLeadAgent: (agent) => {
|
|
@@ -692,7 +739,7 @@ export class DefaultRuntimeBuilder implements RuntimeBuilder {
|
|
|
692
739
|
},
|
|
693
740
|
shutdown: async (reason: string) => {
|
|
694
741
|
shutdownTeamRuntime(teamRuntime, reason);
|
|
695
|
-
this.
|
|
742
|
+
this.teamRuntimeEntries.delete(registryKey);
|
|
696
743
|
await mcpShutdown?.();
|
|
697
744
|
if (!watcherProvided) {
|
|
698
745
|
userInstructionWatcher?.stop();
|