@clinebot/core 0.0.32 → 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/context/agentic-compaction.d.ts.map +1 -1
- package/dist/extensions/context/basic-compaction.d.ts.map +1 -1
- package/dist/extensions/context/compaction-shared.d.ts +1 -1
- package/dist/extensions/context/compaction-shared.d.ts.map +1 -1
- package/dist/extensions/context/compaction.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 +227 -229
- 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-agent-events.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/dist/types/events.d.ts +4 -0
- package/dist/types/events.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/context/agentic-compaction.ts +22 -10
- package/src/extensions/context/basic-compaction.ts +43 -18
- package/src/extensions/context/compaction-shared.ts +1 -1
- package/src/extensions/context/compaction.test.ts +16 -10
- package/src/extensions/context/compaction.ts +35 -12
- 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/providers/local-provider-service.test.ts +4 -4
- 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-agent-events.ts +9 -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/src/types/events.ts +4 -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
|
@@ -84,6 +84,7 @@ interface ContributionDescriptor {
|
|
|
84
84
|
|
|
85
85
|
interface PluginDescriptor {
|
|
86
86
|
pluginId: string;
|
|
87
|
+
pluginPath: string;
|
|
87
88
|
name: string;
|
|
88
89
|
manifest: Record<string, unknown>;
|
|
89
90
|
contributions: {
|
|
@@ -96,6 +97,28 @@ interface PluginDescriptor {
|
|
|
96
97
|
};
|
|
97
98
|
}
|
|
98
99
|
|
|
100
|
+
interface PluginInitializationFailure {
|
|
101
|
+
pluginPath: string;
|
|
102
|
+
pluginName?: string;
|
|
103
|
+
phase: "load" | "setup";
|
|
104
|
+
message: string;
|
|
105
|
+
stack?: string;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
interface PluginInitializationWarning {
|
|
109
|
+
type: "duplicate_plugin_override";
|
|
110
|
+
pluginPath: string;
|
|
111
|
+
pluginName: string;
|
|
112
|
+
overriddenPluginPath: string;
|
|
113
|
+
message: string;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
interface InitializeResult {
|
|
117
|
+
plugins: PluginDescriptor[];
|
|
118
|
+
failures: PluginInitializationFailure[];
|
|
119
|
+
warnings: PluginInitializationWarning[];
|
|
120
|
+
}
|
|
121
|
+
|
|
99
122
|
interface PluginState {
|
|
100
123
|
plugin: PluginModule;
|
|
101
124
|
handlers: {
|
|
@@ -191,104 +214,153 @@ function getPlugin(pluginId: string): PluginState {
|
|
|
191
214
|
async function initialize(args: {
|
|
192
215
|
pluginPaths?: string[];
|
|
193
216
|
exportName?: string;
|
|
194
|
-
}): Promise<
|
|
217
|
+
}): Promise<InitializeResult> {
|
|
195
218
|
pluginState.clear();
|
|
196
219
|
pluginCounter = 0;
|
|
197
220
|
contributionCounters.clear();
|
|
198
221
|
|
|
199
222
|
const descriptors: PluginDescriptor[] = [];
|
|
223
|
+
const failures: PluginInitializationFailure[] = [];
|
|
224
|
+
const warnings: PluginInitializationWarning[] = [];
|
|
200
225
|
const exportName = args.exportName || "plugin";
|
|
226
|
+
const pluginIndexByName = new Map<string, number>();
|
|
201
227
|
|
|
202
228
|
for (const pluginPath of args.pluginPaths || []) {
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
moduleExports
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
id,
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
229
|
+
let plugin: PluginModule | undefined;
|
|
230
|
+
try {
|
|
231
|
+
const moduleExports = await importPluginModule(pluginPath);
|
|
232
|
+
plugin = (moduleExports.default ??
|
|
233
|
+
moduleExports[exportName]) as unknown as PluginModule;
|
|
234
|
+
assertValidPluginModule(plugin, pluginPath);
|
|
235
|
+
|
|
236
|
+
const pluginId = `plugin_${++pluginCounter}`;
|
|
237
|
+
const contributions: PluginDescriptor["contributions"] = {
|
|
238
|
+
tools: [],
|
|
239
|
+
commands: [],
|
|
240
|
+
shortcuts: [],
|
|
241
|
+
flags: [],
|
|
242
|
+
messageBuilders: [],
|
|
243
|
+
providers: [],
|
|
244
|
+
};
|
|
245
|
+
const handlers: PluginState["handlers"] = {
|
|
246
|
+
tools: new Map(),
|
|
247
|
+
commands: new Map(),
|
|
248
|
+
messageBuilders: new Map(),
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
const api: PluginApi = {
|
|
252
|
+
registerTool: (tool) => {
|
|
253
|
+
const id = makeId(pluginId, "tool");
|
|
254
|
+
handlers.tools.set(id, tool.execute);
|
|
255
|
+
contributions.tools.push({
|
|
256
|
+
id,
|
|
257
|
+
name: tool.name,
|
|
258
|
+
description: tool.description,
|
|
259
|
+
inputSchema: tool.inputSchema,
|
|
260
|
+
timeoutMs: tool.timeoutMs,
|
|
261
|
+
retryable: tool.retryable,
|
|
262
|
+
});
|
|
263
|
+
},
|
|
264
|
+
registerCommand: (command) => {
|
|
265
|
+
const id = makeId(pluginId, "command");
|
|
266
|
+
if (typeof command.handler === "function") {
|
|
267
|
+
handlers.commands.set(id, command.handler);
|
|
268
|
+
}
|
|
269
|
+
contributions.commands.push({
|
|
270
|
+
id,
|
|
271
|
+
name: command.name,
|
|
272
|
+
description: command.description,
|
|
273
|
+
});
|
|
274
|
+
},
|
|
275
|
+
registerShortcut: (shortcut) => {
|
|
276
|
+
contributions.shortcuts.push({
|
|
277
|
+
id: makeId(pluginId, "shortcut"),
|
|
278
|
+
name: shortcut.name,
|
|
279
|
+
value: shortcut.value,
|
|
280
|
+
description: shortcut.description,
|
|
281
|
+
});
|
|
282
|
+
},
|
|
283
|
+
registerFlag: (flag) => {
|
|
284
|
+
contributions.flags.push({
|
|
285
|
+
id: makeId(pluginId, "flag"),
|
|
286
|
+
name: flag.name,
|
|
287
|
+
description: flag.description,
|
|
288
|
+
defaultValue: flag.defaultValue,
|
|
289
|
+
});
|
|
290
|
+
},
|
|
291
|
+
registerMessageBuilder: (builder) => {
|
|
292
|
+
const id = makeId(pluginId, "builder");
|
|
293
|
+
handlers.messageBuilders.set(id, builder.build);
|
|
294
|
+
contributions.messageBuilders.push({ id, name: builder.name });
|
|
295
|
+
},
|
|
296
|
+
registerProvider: (provider) => {
|
|
297
|
+
contributions.providers.push({
|
|
298
|
+
id: makeId(pluginId, "provider"),
|
|
299
|
+
name: provider.name,
|
|
300
|
+
description: provider.description,
|
|
301
|
+
metadata: sanitizeObject(provider.metadata),
|
|
302
|
+
});
|
|
303
|
+
},
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
if (typeof plugin.setup === "function") {
|
|
307
|
+
try {
|
|
308
|
+
await plugin.setup(api);
|
|
309
|
+
} catch (error) {
|
|
310
|
+
failures.push({
|
|
311
|
+
pluginPath,
|
|
312
|
+
pluginName: plugin.name,
|
|
313
|
+
phase: "setup",
|
|
314
|
+
message: error instanceof Error ? error.message : String(error),
|
|
315
|
+
stack: error instanceof Error ? error.stack : undefined,
|
|
316
|
+
});
|
|
317
|
+
continue;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
const previousIndex = pluginIndexByName.get(plugin.name);
|
|
322
|
+
if (previousIndex !== undefined) {
|
|
323
|
+
const previous = descriptors[previousIndex];
|
|
324
|
+
if (!previous) {
|
|
325
|
+
pluginIndexByName.delete(plugin.name);
|
|
326
|
+
} else {
|
|
327
|
+
warnings.push({
|
|
328
|
+
type: "duplicate_plugin_override",
|
|
329
|
+
pluginName: plugin.name,
|
|
330
|
+
pluginPath,
|
|
331
|
+
overriddenPluginPath: previous.pluginPath,
|
|
332
|
+
message: `Plugin "${plugin.name}" from ${pluginPath} overrides ${previous.pluginPath}`,
|
|
333
|
+
});
|
|
334
|
+
pluginState.delete(previous.pluginId);
|
|
335
|
+
descriptors.splice(previousIndex, 1);
|
|
336
|
+
pluginIndexByName.clear();
|
|
337
|
+
for (const [index, descriptor] of descriptors.entries()) {
|
|
338
|
+
pluginIndexByName.set(descriptor.name, index);
|
|
339
|
+
}
|
|
240
340
|
}
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
defaultValue: flag.defaultValue,
|
|
261
|
-
});
|
|
262
|
-
},
|
|
263
|
-
registerMessageBuilder: (builder) => {
|
|
264
|
-
const id = makeId(pluginId, "builder");
|
|
265
|
-
handlers.messageBuilders.set(id, builder.build);
|
|
266
|
-
contributions.messageBuilders.push({ id, name: builder.name });
|
|
267
|
-
},
|
|
268
|
-
registerProvider: (provider) => {
|
|
269
|
-
contributions.providers.push({
|
|
270
|
-
id: makeId(pluginId, "provider"),
|
|
271
|
-
name: provider.name,
|
|
272
|
-
description: provider.description,
|
|
273
|
-
metadata: sanitizeObject(provider.metadata),
|
|
274
|
-
});
|
|
275
|
-
},
|
|
276
|
-
};
|
|
277
|
-
|
|
278
|
-
if (typeof plugin.setup === "function") {
|
|
279
|
-
await plugin.setup(api);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
pluginState.set(pluginId, { plugin, handlers });
|
|
344
|
+
pluginIndexByName.set(plugin.name, descriptors.length);
|
|
345
|
+
descriptors.push({
|
|
346
|
+
pluginId,
|
|
347
|
+
pluginPath,
|
|
348
|
+
name: plugin.name,
|
|
349
|
+
manifest: plugin.manifest,
|
|
350
|
+
contributions,
|
|
351
|
+
});
|
|
352
|
+
} catch (error) {
|
|
353
|
+
failures.push({
|
|
354
|
+
pluginPath,
|
|
355
|
+
pluginName: plugin?.name,
|
|
356
|
+
phase: "load",
|
|
357
|
+
message: error instanceof Error ? error.message : String(error),
|
|
358
|
+
stack: error instanceof Error ? error.stack : undefined,
|
|
359
|
+
});
|
|
280
360
|
}
|
|
281
|
-
|
|
282
|
-
pluginState.set(pluginId, { plugin, handlers });
|
|
283
|
-
descriptors.push({
|
|
284
|
-
pluginId,
|
|
285
|
-
name: plugin.name,
|
|
286
|
-
manifest: plugin.manifest,
|
|
287
|
-
contributions,
|
|
288
|
-
});
|
|
289
361
|
}
|
|
290
362
|
|
|
291
|
-
return descriptors;
|
|
363
|
+
return { plugins: descriptors, failures, warnings };
|
|
292
364
|
}
|
|
293
365
|
|
|
294
366
|
async function invokeHook(args: {
|
|
@@ -184,6 +184,31 @@ describe("plugin-sandbox", () => {
|
|
|
184
184
|
"utf8",
|
|
185
185
|
);
|
|
186
186
|
|
|
187
|
+
await writeFile(
|
|
188
|
+
join(dir, "plugin-broken-setup.mjs"),
|
|
189
|
+
[
|
|
190
|
+
"export default {",
|
|
191
|
+
" name: 'sandbox-broken-setup',",
|
|
192
|
+
" manifest: { capabilities: ['tools'] },",
|
|
193
|
+
" async setup() {",
|
|
194
|
+
" throw new Error('broken setup');",
|
|
195
|
+
" },",
|
|
196
|
+
"};",
|
|
197
|
+
].join("\n"),
|
|
198
|
+
"utf8",
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
await writeFile(
|
|
202
|
+
join(dir, "plugin-duplicate-a.mjs"),
|
|
203
|
+
"export default { name: 'sandbox-duplicate', manifest: { capabilities: ['tools'] } };",
|
|
204
|
+
"utf8",
|
|
205
|
+
);
|
|
206
|
+
await writeFile(
|
|
207
|
+
join(dir, "plugin-duplicate-b.mjs"),
|
|
208
|
+
"export default { name: 'sandbox-duplicate', manifest: { capabilities: ['commands'] } };",
|
|
209
|
+
"utf8",
|
|
210
|
+
);
|
|
211
|
+
|
|
187
212
|
sharedSandbox = await loadSandboxedPlugins({
|
|
188
213
|
pluginPaths: [
|
|
189
214
|
join(dir, "plugin.mjs"),
|
|
@@ -321,6 +346,51 @@ describe("plugin-sandbox", () => {
|
|
|
321
346
|
expect(result).toEqual({ echoed: "ok" });
|
|
322
347
|
});
|
|
323
348
|
|
|
349
|
+
it("continues loading remaining sandbox plugins when one setup fails", async () => {
|
|
350
|
+
const sandboxed = await loadSandboxedPlugins({
|
|
351
|
+
pluginPaths: [
|
|
352
|
+
join(dir, "plugin.mjs"),
|
|
353
|
+
join(dir, "plugin-broken-setup.mjs"),
|
|
354
|
+
join(dir, "plugin-events.mjs"),
|
|
355
|
+
],
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
try {
|
|
359
|
+
expect(sandboxed.extensions?.map((extension) => extension.name)).toEqual([
|
|
360
|
+
"sandbox-test",
|
|
361
|
+
"sandbox-events",
|
|
362
|
+
]);
|
|
363
|
+
expect(sandboxed.failures).toHaveLength(1);
|
|
364
|
+
expect(sandboxed.failures[0]?.pluginName).toBe("sandbox-broken-setup");
|
|
365
|
+
expect(sandboxed.failures[0]?.phase).toBe("setup");
|
|
366
|
+
} finally {
|
|
367
|
+
await sandboxed.shutdown();
|
|
368
|
+
}
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
it("keeps the later duplicate sandbox plugin and reports the override", async () => {
|
|
372
|
+
const sandboxed = await loadSandboxedPlugins({
|
|
373
|
+
pluginPaths: [
|
|
374
|
+
join(dir, "plugin-duplicate-a.mjs"),
|
|
375
|
+
join(dir, "plugin-duplicate-b.mjs"),
|
|
376
|
+
],
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
try {
|
|
380
|
+
expect(sandboxed.extensions).toHaveLength(1);
|
|
381
|
+
expect(sandboxed.extensions?.[0]?.name).toBe("sandbox-duplicate");
|
|
382
|
+
expect(sandboxed.extensions?.[0]?.manifest.capabilities).toEqual([
|
|
383
|
+
"commands",
|
|
384
|
+
]);
|
|
385
|
+
expect(sandboxed.warnings).toHaveLength(1);
|
|
386
|
+
expect(sandboxed.warnings[0]?.overriddenPluginPath).toBe(
|
|
387
|
+
join(dir, "plugin-duplicate-a.mjs"),
|
|
388
|
+
);
|
|
389
|
+
} finally {
|
|
390
|
+
await sandboxed.shutdown();
|
|
391
|
+
}
|
|
392
|
+
});
|
|
393
|
+
|
|
324
394
|
it("resolves plugin-local dependencies in the sandbox process", async () => {
|
|
325
395
|
expect(sharedExtensions.get("sandbox-local-dep")?.name).toBe(
|
|
326
396
|
"sandbox-local-dep",
|
|
@@ -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,
|
|
@@ -922,7 +922,7 @@ describe("listLocalProviders", () => {
|
|
|
922
922
|
).toBe(true);
|
|
923
923
|
});
|
|
924
924
|
|
|
925
|
-
it("uses the same built-in model list for cline as
|
|
925
|
+
it("uses the same built-in model list for cline as openrouter", async () => {
|
|
926
926
|
manager.saveProviderSettings(
|
|
927
927
|
{
|
|
928
928
|
provider: "cline",
|
|
@@ -935,12 +935,12 @@ describe("listLocalProviders", () => {
|
|
|
935
935
|
|
|
936
936
|
const { providers } = await listLocalProviders(manager);
|
|
937
937
|
const cline = providers.find((provider) => provider.id === "cline");
|
|
938
|
-
const
|
|
939
|
-
(provider) => provider.id === "
|
|
938
|
+
const openrouter = providers.find(
|
|
939
|
+
(provider) => provider.id === "openrouter",
|
|
940
940
|
);
|
|
941
941
|
|
|
942
942
|
expect(cline?.modelList?.length).toBeGreaterThan(0);
|
|
943
|
-
expect(cline?.modelList).toEqual(
|
|
943
|
+
expect(cline?.modelList).toEqual(openrouter?.modelList);
|
|
944
944
|
});
|
|
945
945
|
|
|
946
946
|
it("does not eagerly fetch LiteLLM private models while listing providers", async () => {
|
|
@@ -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");
|