zidane 1.8.0 → 2.0.1
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-CWQ5XOw6.d.ts → agent-D-ZFMbSd.d.ts} +371 -21
- package/dist/{chunk-FFFDQHMA.js → chunk-7JTBBZ2U.js} +21 -0
- package/dist/chunk-BCXXXJ3G.js +779 -0
- package/dist/{chunk-WQBKOZVI.js → chunk-FRNFVKWW.js} +50 -12
- package/dist/{chunk-EC7IRWVS.js → chunk-LN4LLLHA.js} +101 -7
- package/dist/chunk-LVC7NQUZ.js +22 -0
- package/dist/chunk-MYWDHD7C.js +14 -0
- package/dist/{chunk-3RJOWJOJ.js → chunk-OVQ4N64O.js} +1 -1
- package/dist/{chunk-4N5ADW7A.js → chunk-PASFWG7S.js} +343 -32
- package/dist/{chunk-TBC6MSVK.js → chunk-PJUUYBKF.js} +53 -4
- package/dist/{chunk-2IB4XBQE.js → chunk-VG2E6YK3.js} +0 -97
- package/dist/harnesses.d.ts +1 -2
- package/dist/harnesses.js +5 -5
- package/dist/index.d.ts +5 -6
- package/dist/index.js +42 -12
- package/dist/mcp.d.ts +1 -2
- package/dist/mcp.js +3 -1
- package/dist/providers.d.ts +1 -2
- package/dist/providers.js +3 -3
- package/dist/session/sqlite.d.ts +16 -0
- package/dist/session/sqlite.js +98 -0
- package/dist/session.d.ts +1 -2
- package/dist/session.js +3 -5
- package/dist/skills-use-C4KFVla0.d.ts +66 -0
- package/dist/skills.d.ts +215 -30
- package/dist/skills.js +21 -2
- package/dist/{spawn-EEv1Johs.d.ts → spawn-RoqpjYLZ.d.ts} +1 -1
- package/dist/tools.d.ts +3 -4
- package/dist/tools.js +10 -4
- package/dist/types.d.ts +2 -3
- package/dist/types.js +8 -2
- package/package.json +12 -3
- package/dist/chunk-4C6Y56CC.js +0 -390
- package/dist/chunk-CFLC2I7D.js +0 -8
- package/dist/glob-j9gbk6xm.d.ts +0 -5
- package/dist/types-CDI8Kmve.d.ts +0 -64
|
@@ -1,20 +1,23 @@
|
|
|
1
1
|
import {
|
|
2
2
|
buildCatalog,
|
|
3
|
+
createSkillActivationState,
|
|
4
|
+
installAllowedToolsGate,
|
|
3
5
|
interpolateShellCommands,
|
|
4
6
|
mergeSkillsConfig,
|
|
5
|
-
resolveSkills
|
|
6
|
-
|
|
7
|
+
resolveSkills,
|
|
8
|
+
validateResourcePath
|
|
9
|
+
} from "./chunk-BCXXXJ3G.js";
|
|
7
10
|
import {
|
|
8
11
|
createProcessContext
|
|
9
12
|
} from "./chunk-SZA4FKW5.js";
|
|
10
13
|
import {
|
|
11
14
|
connectMcpServers
|
|
12
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-PJUUYBKF.js";
|
|
13
16
|
import {
|
|
14
17
|
AgentAbortedError,
|
|
15
18
|
AgentProviderError,
|
|
16
19
|
toTypedError
|
|
17
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-7JTBBZ2U.js";
|
|
18
21
|
|
|
19
22
|
// src/tools/glob.ts
|
|
20
23
|
var DEFAULT_LIMIT = 1e3;
|
|
@@ -157,6 +160,227 @@ ${result.stderr}`.trim();
|
|
|
157
160
|
}
|
|
158
161
|
};
|
|
159
162
|
|
|
163
|
+
// src/tools/skills-read.ts
|
|
164
|
+
var SNIFF_BYTES = 8192;
|
|
165
|
+
function looksBinary(text) {
|
|
166
|
+
const len = Math.min(text.length, SNIFF_BYTES);
|
|
167
|
+
for (let i = 0; i < len; i++) {
|
|
168
|
+
if (text.charCodeAt(i) === 0)
|
|
169
|
+
return true;
|
|
170
|
+
}
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
173
|
+
function createSkillsReadTool(options) {
|
|
174
|
+
const byName = new Map(options.catalog.map((s) => [s.name, s]));
|
|
175
|
+
return {
|
|
176
|
+
spec: {
|
|
177
|
+
name: "skills_read",
|
|
178
|
+
description: `Read a bundled resource file from an active skill. The skill must have been activated via skills_use first. Path is relative to the skill's directory (e.g. "references/REFERENCE.md", "assets/template.txt").`,
|
|
179
|
+
inputSchema: {
|
|
180
|
+
type: "object",
|
|
181
|
+
properties: {
|
|
182
|
+
name: {
|
|
183
|
+
type: "string",
|
|
184
|
+
enum: options.catalog.map((s) => s.name),
|
|
185
|
+
description: "The name of the active skill."
|
|
186
|
+
},
|
|
187
|
+
path: {
|
|
188
|
+
type: "string",
|
|
189
|
+
description: "Path to the resource, relative to the skill root. Cannot escape the skill directory."
|
|
190
|
+
}
|
|
191
|
+
},
|
|
192
|
+
required: ["name", "path"],
|
|
193
|
+
additionalProperties: false
|
|
194
|
+
}
|
|
195
|
+
},
|
|
196
|
+
async execute(input, ctx) {
|
|
197
|
+
const skillName = input.name;
|
|
198
|
+
const relPath = input.path;
|
|
199
|
+
const skill = byName.get(skillName);
|
|
200
|
+
if (!skill)
|
|
201
|
+
return `Error: unknown skill "${skillName}".`;
|
|
202
|
+
if (!options.state.isActive(skillName))
|
|
203
|
+
return `Error: skill "${skillName}" is not active. Call skills_use with name: "${skillName}" first.`;
|
|
204
|
+
if (!skill.baseDir) {
|
|
205
|
+
return `Error: skill "${skillName}" has no base directory (likely an inline skill without bundled resources); cannot read files.`;
|
|
206
|
+
}
|
|
207
|
+
const validated = validateResourcePath(relPath, skill.baseDir);
|
|
208
|
+
if (!validated.valid)
|
|
209
|
+
return `Error: ${validated.error}`;
|
|
210
|
+
let content;
|
|
211
|
+
try {
|
|
212
|
+
content = await ctx.execution.readFile(ctx.handle, validated.absolutePath);
|
|
213
|
+
} catch (err) {
|
|
214
|
+
return `Error reading "${relPath}" in skill "${skillName}": ${err.message}`;
|
|
215
|
+
}
|
|
216
|
+
if (looksBinary(content)) {
|
|
217
|
+
return JSON.stringify({
|
|
218
|
+
kind: "binary-unsupported",
|
|
219
|
+
path: validated.absolutePath,
|
|
220
|
+
note: "This file appears to be binary. The skills_read tool returns text only; binary files are not delivered through the execution context's text-based readFile API."
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
return content;
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// src/tools/skills-run-script.ts
|
|
229
|
+
var SINGLE_QUOTE_RE = /'/g;
|
|
230
|
+
var ABS_WINDOWS_RE = /^[a-z]:[\\/]/i;
|
|
231
|
+
var COLLAPSE_SLASHES_RE = /\/+/g;
|
|
232
|
+
function quoteShellArg(arg) {
|
|
233
|
+
return `'${arg.replace(SINGLE_QUOTE_RE, `'\\''`)}'`;
|
|
234
|
+
}
|
|
235
|
+
function createSkillsRunScriptTool(options) {
|
|
236
|
+
const byName = new Map(options.catalog.map((s) => [s.name, s]));
|
|
237
|
+
const timeoutMs = options.scriptTimeoutMs ?? 6e4;
|
|
238
|
+
return {
|
|
239
|
+
spec: {
|
|
240
|
+
name: "skills_run_script",
|
|
241
|
+
description: "Execute a script bundled with an active skill (from its scripts/ directory). The skill must have been activated via skills_use first. Returns stdout, stderr, and the exit code. Honors the script's shebang.",
|
|
242
|
+
inputSchema: {
|
|
243
|
+
type: "object",
|
|
244
|
+
properties: {
|
|
245
|
+
name: {
|
|
246
|
+
type: "string",
|
|
247
|
+
enum: options.catalog.map((s) => s.name),
|
|
248
|
+
description: "The name of the active skill."
|
|
249
|
+
},
|
|
250
|
+
script: {
|
|
251
|
+
type: "string",
|
|
252
|
+
description: `Path to the script relative to the skill's scripts/ directory (e.g. "extract.py", "merge.sh").`
|
|
253
|
+
},
|
|
254
|
+
args: {
|
|
255
|
+
type: "array",
|
|
256
|
+
items: { type: "string" },
|
|
257
|
+
description: "Optional argv array passed to the script."
|
|
258
|
+
}
|
|
259
|
+
},
|
|
260
|
+
required: ["name", "script"],
|
|
261
|
+
additionalProperties: false
|
|
262
|
+
}
|
|
263
|
+
},
|
|
264
|
+
async execute(input, ctx) {
|
|
265
|
+
const skillName = input.name;
|
|
266
|
+
const scriptRel = input.script;
|
|
267
|
+
const args = input.args ?? [];
|
|
268
|
+
const skill = byName.get(skillName);
|
|
269
|
+
if (!skill)
|
|
270
|
+
return `Error: unknown skill "${skillName}".`;
|
|
271
|
+
if (!options.state.isActive(skillName))
|
|
272
|
+
return `Error: skill "${skillName}" is not active. Call skills_use with name: "${skillName}" first.`;
|
|
273
|
+
if (!skill.baseDir)
|
|
274
|
+
return `Error: skill "${skillName}" has no base directory (likely an inline skill); cannot run scripts.`;
|
|
275
|
+
if (scriptRel.startsWith("/") || ABS_WINDOWS_RE.test(scriptRel))
|
|
276
|
+
return `Error: Absolute paths are not allowed ("${scriptRel}").`;
|
|
277
|
+
const joinedPath = `scripts/${scriptRel}`.replace(COLLAPSE_SLASHES_RE, "/");
|
|
278
|
+
const validated = validateResourcePath(joinedPath, skill.baseDir);
|
|
279
|
+
if (!validated.valid)
|
|
280
|
+
return `Error: ${validated.error}`;
|
|
281
|
+
const cmd = [validated.absolutePath, ...args].map(quoteShellArg).join(" ");
|
|
282
|
+
try {
|
|
283
|
+
const result = await ctx.execution.exec(ctx.handle, cmd, {
|
|
284
|
+
timeout: Math.max(1, Math.round(timeoutMs / 1e3))
|
|
285
|
+
});
|
|
286
|
+
return JSON.stringify({
|
|
287
|
+
exitCode: result.exitCode,
|
|
288
|
+
stdout: result.stdout,
|
|
289
|
+
stderr: result.stderr
|
|
290
|
+
});
|
|
291
|
+
} catch (err) {
|
|
292
|
+
return `Error running script "${scriptRel}" for skill "${skillName}": ${err.message}`;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// src/tools/skills-use.ts
|
|
299
|
+
var MAX_RESOURCE_LIST = 50;
|
|
300
|
+
var AMP_RE = /&/g;
|
|
301
|
+
var LT_RE = /</g;
|
|
302
|
+
var GT_RE = />/g;
|
|
303
|
+
var QUOT_RE = /"/g;
|
|
304
|
+
function escapeXml(str) {
|
|
305
|
+
return str.replace(AMP_RE, "&").replace(LT_RE, "<").replace(GT_RE, ">").replace(QUOT_RE, """);
|
|
306
|
+
}
|
|
307
|
+
function buildSkillContentWrapper(skill, body) {
|
|
308
|
+
const parts = [];
|
|
309
|
+
parts.push(`<skill_content name="${escapeXml(skill.name)}" spec_version="0.1">`);
|
|
310
|
+
parts.push(body);
|
|
311
|
+
if (skill.baseDir) {
|
|
312
|
+
parts.push("");
|
|
313
|
+
parts.push(`Skill directory: ${skill.baseDir}`);
|
|
314
|
+
parts.push("Relative paths resolve against this directory.");
|
|
315
|
+
}
|
|
316
|
+
if (skill.resources?.length) {
|
|
317
|
+
parts.push("");
|
|
318
|
+
parts.push("<skill_resources>");
|
|
319
|
+
const shown = skill.resources.slice(0, MAX_RESOURCE_LIST);
|
|
320
|
+
for (const res of shown) {
|
|
321
|
+
parts.push(` <file type="${res.type}">${escapeXml(res.path)}</file>`);
|
|
322
|
+
}
|
|
323
|
+
if (skill.resources.length > MAX_RESOURCE_LIST) {
|
|
324
|
+
parts.push(` <!-- \u2026(${skill.resources.length - MAX_RESOURCE_LIST} more) -->`);
|
|
325
|
+
}
|
|
326
|
+
parts.push("</skill_resources>");
|
|
327
|
+
}
|
|
328
|
+
if (skill.compatibility) {
|
|
329
|
+
parts.push("");
|
|
330
|
+
parts.push(`Compatibility: ${skill.compatibility}`);
|
|
331
|
+
}
|
|
332
|
+
if (skill.allowedTools?.length) {
|
|
333
|
+
parts.push(`Allowed tools: ${skill.allowedTools.join(" ")}`);
|
|
334
|
+
}
|
|
335
|
+
parts.push("</skill_content>");
|
|
336
|
+
return parts.join("\n");
|
|
337
|
+
}
|
|
338
|
+
function createSkillsUseTool(options) {
|
|
339
|
+
const byName = new Map(options.catalog.map((s) => [s.name, s]));
|
|
340
|
+
const interpolatedBodyCache = /* @__PURE__ */ new Map();
|
|
341
|
+
return {
|
|
342
|
+
spec: {
|
|
343
|
+
name: "skills_use",
|
|
344
|
+
description: "Activate a specialized skill and load its full instructions. Call this when a task matches a skill's description from the catalog. After calling, follow the returned instructions; use skills_read to load referenced files and skills_run_script to execute bundled scripts.",
|
|
345
|
+
inputSchema: {
|
|
346
|
+
type: "object",
|
|
347
|
+
properties: {
|
|
348
|
+
name: {
|
|
349
|
+
type: "string",
|
|
350
|
+
enum: options.catalog.map((s) => s.name),
|
|
351
|
+
description: "The name of the skill to activate (must be in the available skills catalog)."
|
|
352
|
+
}
|
|
353
|
+
},
|
|
354
|
+
required: ["name"],
|
|
355
|
+
additionalProperties: false
|
|
356
|
+
}
|
|
357
|
+
},
|
|
358
|
+
async execute(input, ctx) {
|
|
359
|
+
const skillName = input.name;
|
|
360
|
+
const skill = byName.get(skillName);
|
|
361
|
+
if (!skill) {
|
|
362
|
+
const available = [...byName.keys()].join(", ") || "<none>";
|
|
363
|
+
return `Error: unknown skill "${skillName}". Available skills: ${available}.`;
|
|
364
|
+
}
|
|
365
|
+
const wasActive = options.state.isActive(skillName);
|
|
366
|
+
if (!wasActive) {
|
|
367
|
+
const outcome = options.state.activate(skill, "model");
|
|
368
|
+
if (outcome === "cap-reached") {
|
|
369
|
+
const activeNames = options.state.active().map((a) => a.skill.name).join(", ");
|
|
370
|
+
return `Error: cannot activate "${skillName}" \u2014 the maxActive skill cap has been reached. Currently active: ${activeNames}. Deactivate an existing skill first.`;
|
|
371
|
+
}
|
|
372
|
+
await options.hooks.callHook("skills:activate", { skill, via: "model" });
|
|
373
|
+
}
|
|
374
|
+
let body = interpolatedBodyCache.get(skillName);
|
|
375
|
+
if (body === void 0) {
|
|
376
|
+
body = skill.instructions.includes("!`") ? await interpolateShellCommands(skill.instructions, ctx.execution, ctx.handle) : skill.instructions;
|
|
377
|
+
interpolatedBodyCache.set(skillName, body);
|
|
378
|
+
}
|
|
379
|
+
return buildSkillContentWrapper(skill, body);
|
|
380
|
+
}
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
|
|
160
384
|
// src/agent.ts
|
|
161
385
|
import { createHooks } from "hookable";
|
|
162
386
|
|
|
@@ -241,9 +465,25 @@ function validateToolArgs(input, schema) {
|
|
|
241
465
|
}
|
|
242
466
|
|
|
243
467
|
// src/loop.ts
|
|
468
|
+
var IMAGE_OMITTED_MARKER = "[image omitted \u2014 model does not support vision]";
|
|
244
469
|
function turnsToMessages(turns) {
|
|
245
470
|
return turns.filter((t) => t.role !== "system").map((t) => ({ role: t.role, content: t.content }));
|
|
246
471
|
}
|
|
472
|
+
function sanitizeStoredToolResults(provider, messages) {
|
|
473
|
+
if (provider.meta.capabilities?.vision !== false)
|
|
474
|
+
return messages;
|
|
475
|
+
return messages.map((msg) => {
|
|
476
|
+
let changed = false;
|
|
477
|
+
const newContent = msg.content.map((block) => {
|
|
478
|
+
if (block.type !== "tool_result" || typeof block.output === "string")
|
|
479
|
+
return block;
|
|
480
|
+
changed = true;
|
|
481
|
+
const flattened = block.output.map((b) => b.type === "image" ? IMAGE_OMITTED_MARKER : b.text).join("\n");
|
|
482
|
+
return { ...block, output: flattened };
|
|
483
|
+
});
|
|
484
|
+
return changed ? { ...msg, content: newContent } : msg;
|
|
485
|
+
});
|
|
486
|
+
}
|
|
247
487
|
async function runLoop(ctx) {
|
|
248
488
|
let totalIn = 0;
|
|
249
489
|
let totalOut = 0;
|
|
@@ -338,11 +578,12 @@ async function executeTurn(ctx, turn) {
|
|
|
338
578
|
const turnId = await ctx.generateTurnId();
|
|
339
579
|
const canonicalMessages = turnsToMessages(ctx.turns);
|
|
340
580
|
const wireMessages = rewriteMessagesToWire(canonicalMessages, ctx.aliasMaps);
|
|
581
|
+
const sanitizedMessages = sanitizeStoredToolResults(ctx.provider, wireMessages);
|
|
341
582
|
const streamOptions = {
|
|
342
583
|
model: ctx.model,
|
|
343
584
|
system: ctx.system,
|
|
344
585
|
tools: ctx.formattedTools,
|
|
345
|
-
messages:
|
|
586
|
+
messages: sanitizedMessages,
|
|
346
587
|
maxTokens: ctx.maxTokens ?? 16384,
|
|
347
588
|
thinking: ctx.thinking,
|
|
348
589
|
thinkingBudget: ctx.thinkingBudget,
|
|
@@ -474,6 +715,13 @@ async function executeTurn(ctx, turn) {
|
|
|
474
715
|
});
|
|
475
716
|
return { ended: false, turnId, usage: result.usage };
|
|
476
717
|
}
|
|
718
|
+
function stripImagesForNonVision(provider, output) {
|
|
719
|
+
if (typeof output === "string")
|
|
720
|
+
return output;
|
|
721
|
+
if (provider.meta.capabilities?.vision !== false)
|
|
722
|
+
return output;
|
|
723
|
+
return output.map((b) => b.type === "image" ? IMAGE_OMITTED_MARKER : b.text).join("\n");
|
|
724
|
+
}
|
|
477
725
|
async function executeSingleTool(ctx, call, turnId) {
|
|
478
726
|
const toolDef = ctx.tools[call.name];
|
|
479
727
|
const callId = call.id;
|
|
@@ -553,6 +801,7 @@ async function executeSingleTool(ctx, call, turnId) {
|
|
|
553
801
|
await ctx.hooks.callHook("tool:transform", transformCtx);
|
|
554
802
|
output = transformCtx.result;
|
|
555
803
|
isError = transformCtx.isError;
|
|
804
|
+
output = stripImagesForNonVision(ctx.provider, output);
|
|
556
805
|
await ctx.hooks.callHook("tool:after", {
|
|
557
806
|
turnId,
|
|
558
807
|
callId,
|
|
@@ -701,6 +950,9 @@ function createAgent({ harness: harnessOption, provider, behavior: agentBehavior
|
|
|
701
950
|
const skillsDisabled = skillsEnabledValue === false || Array.isArray(skillsEnabledValue) && skillsEnabledValue.length === 0;
|
|
702
951
|
let resolvedSkills = null;
|
|
703
952
|
let skillsCatalog = null;
|
|
953
|
+
const skillActivationState = createSkillActivationState({
|
|
954
|
+
maxActive: mergedSkillsConfig?.maxActive
|
|
955
|
+
});
|
|
704
956
|
async function run(options) {
|
|
705
957
|
if (running) {
|
|
706
958
|
throw new Error("Agent is already running. Use steer() or followUp() to queue messages, or waitForIdle().");
|
|
@@ -763,34 +1015,32 @@ function createAgent({ harness: harnessOption, provider, behavior: agentBehavior
|
|
|
763
1015
|
if (!skillsDisabled && mergedSkillsConfig && !resolvedSkills) {
|
|
764
1016
|
resolvedSkills = await resolveSkills(mergedSkillsConfig);
|
|
765
1017
|
await hooks.callHook("skills:resolve", { skills: resolvedSkills });
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
executionContext,
|
|
772
|
-
executionHandle
|
|
773
|
-
);
|
|
774
|
-
}
|
|
775
|
-
}
|
|
776
|
-
}
|
|
777
|
-
const readToolName = mergedSkillsConfig.readToolName ?? "read_file";
|
|
778
|
-
const catalogCtx = { catalog: buildCatalog(resolvedSkills, readToolName), skills: resolvedSkills };
|
|
1018
|
+
const skillsToolRegistered = mergedSkillsConfig?.tool !== false && resolvedSkills.length > 0;
|
|
1019
|
+
const catalogCtx = {
|
|
1020
|
+
catalog: buildCatalog(resolvedSkills, { skillsToolRegistered }),
|
|
1021
|
+
skills: resolvedSkills
|
|
1022
|
+
};
|
|
779
1023
|
await hooks.callHook("skills:catalog", catalogCtx);
|
|
780
1024
|
skillsCatalog = catalogCtx.catalog;
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
if (
|
|
789
|
-
|
|
790
|
-
const
|
|
791
|
-
if (
|
|
792
|
-
|
|
793
|
-
|
|
1025
|
+
}
|
|
1026
|
+
if (resolvedSkills && session && session.turns.length > 0 && skillActivationState.active().length === 0) {
|
|
1027
|
+
const skillsByName = new Map(resolvedSkills.map((s) => [s.name, s]));
|
|
1028
|
+
for (const turn of session.turns) {
|
|
1029
|
+
if (turn.role !== "assistant")
|
|
1030
|
+
continue;
|
|
1031
|
+
for (const block of turn.content) {
|
|
1032
|
+
if (block.type !== "tool_call" || block.name !== "skills_use")
|
|
1033
|
+
continue;
|
|
1034
|
+
const skillName = block.input?.name;
|
|
1035
|
+
if (!skillName)
|
|
1036
|
+
continue;
|
|
1037
|
+
const skill = skillsByName.get(skillName);
|
|
1038
|
+
if (!skill)
|
|
1039
|
+
continue;
|
|
1040
|
+
if (skillActivationState.activate(skill, "resume") === "ok") {
|
|
1041
|
+
await hooks.callHook("skills:activate", { skill, via: "resume" });
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
794
1044
|
}
|
|
795
1045
|
}
|
|
796
1046
|
const thinking = options.thinking ?? "off";
|
|
@@ -803,8 +1053,27 @@ function createAgent({ harness: harnessOption, provider, behavior: agentBehavior
|
|
|
803
1053
|
${skillsCatalog}`;
|
|
804
1054
|
}
|
|
805
1055
|
const baseTools = options.tools !== void 0 ? options.tools : mcpConnection ? { ...harness.tools, ...mcpConnection.tools } : harness.tools;
|
|
1056
|
+
const shouldInjectSkillTools = options.tools === void 0 && !!resolvedSkills && resolvedSkills.length > 0 && mergedSkillsConfig?.tool !== false;
|
|
1057
|
+
const mergedWithSkills = shouldInjectSkillTools ? {
|
|
1058
|
+
// Auto-injected first so harness + MCP tools can override by name.
|
|
1059
|
+
skills_use: createSkillsUseTool({
|
|
1060
|
+
catalog: resolvedSkills,
|
|
1061
|
+
state: skillActivationState,
|
|
1062
|
+
hooks
|
|
1063
|
+
}),
|
|
1064
|
+
skills_read: createSkillsReadTool({
|
|
1065
|
+
catalog: resolvedSkills,
|
|
1066
|
+
state: skillActivationState
|
|
1067
|
+
}),
|
|
1068
|
+
skills_run_script: createSkillsRunScriptTool({
|
|
1069
|
+
catalog: resolvedSkills,
|
|
1070
|
+
state: skillActivationState,
|
|
1071
|
+
scriptTimeoutMs: mergedSkillsConfig?.scriptTimeoutMs
|
|
1072
|
+
}),
|
|
1073
|
+
...baseTools
|
|
1074
|
+
} : baseTools;
|
|
806
1075
|
const tools = {};
|
|
807
|
-
for (const tool of Object.values(
|
|
1076
|
+
for (const tool of Object.values(mergedWithSkills)) {
|
|
808
1077
|
tools[tool.spec.name] = tool;
|
|
809
1078
|
}
|
|
810
1079
|
const aliasMaps = buildAliasMaps(harness.toolAliases, Object.keys(tools));
|
|
@@ -856,6 +1125,10 @@ ${skillsCatalog}`;
|
|
|
856
1125
|
await hooks.callHook("session:turns", { sessionId: session.id, turns: remaining, count: turns.length });
|
|
857
1126
|
}
|
|
858
1127
|
}
|
|
1128
|
+
async function deactivateAllSkills() {
|
|
1129
|
+
for (const record of skillActivationState.clear())
|
|
1130
|
+
await hooks.callHook("skills:deactivate", { skill: record.skill, reason: "run-end" });
|
|
1131
|
+
}
|
|
859
1132
|
async function finalizeSession(status) {
|
|
860
1133
|
if (!session)
|
|
861
1134
|
return;
|
|
@@ -865,6 +1138,7 @@ ${skillsCatalog}`;
|
|
|
865
1138
|
await session.updateStatus(status === "aborted" ? "idle" : status);
|
|
866
1139
|
await hooks.callHook("session:end", { sessionId: session.id, runId, status, turnRange: [runTurnStart, turns.length - 1] });
|
|
867
1140
|
}
|
|
1141
|
+
const uninstallAllowedToolsGate = installAllowedToolsGate(hooks, skillActivationState);
|
|
868
1142
|
const runStartMs = Date.now();
|
|
869
1143
|
try {
|
|
870
1144
|
const stats = await runLoop({
|
|
@@ -929,6 +1203,8 @@ ${skillsCatalog}`;
|
|
|
929
1203
|
await finalizeSession("error");
|
|
930
1204
|
throw err;
|
|
931
1205
|
} finally {
|
|
1206
|
+
await deactivateAllSkills();
|
|
1207
|
+
uninstallAllowedToolsGate();
|
|
932
1208
|
unregisterSpawnHook();
|
|
933
1209
|
unregisterSessionSync?.();
|
|
934
1210
|
for (const unregister of perRunUnregisters)
|
|
@@ -958,6 +1234,33 @@ ${skillsCatalog}`;
|
|
|
958
1234
|
conversationTurns = [];
|
|
959
1235
|
steeringQueue.length = 0;
|
|
960
1236
|
followUpQueue.length = 0;
|
|
1237
|
+
for (const record of skillActivationState.clear())
|
|
1238
|
+
void hooks.callHook("skills:deactivate", { skill: record.skill, reason: "reset" });
|
|
1239
|
+
}
|
|
1240
|
+
async function activateSkill(name) {
|
|
1241
|
+
if (!resolvedSkills) {
|
|
1242
|
+
throw new Error(
|
|
1243
|
+
`Cannot activate skill "${name}" \u2014 skills have not been resolved yet. Call activateSkill after the first \`run()\`, or pass a skills config that resolves at agent-creation time.`
|
|
1244
|
+
);
|
|
1245
|
+
}
|
|
1246
|
+
const skill = resolvedSkills.find((s) => s.name === name);
|
|
1247
|
+
if (!skill) {
|
|
1248
|
+
const available = resolvedSkills.map((s) => s.name).join(", ") || "<none>";
|
|
1249
|
+
throw new Error(`Unknown skill "${name}". Available skills: ${available}.`);
|
|
1250
|
+
}
|
|
1251
|
+
const outcome = skillActivationState.activate(skill, "explicit");
|
|
1252
|
+
if (outcome === "cap-reached") {
|
|
1253
|
+
throw new Error(
|
|
1254
|
+
`Cannot activate skill "${name}" \u2014 the maxActive cap of ${mergedSkillsConfig?.maxActive} has been reached.`
|
|
1255
|
+
);
|
|
1256
|
+
}
|
|
1257
|
+
if (outcome === "ok")
|
|
1258
|
+
await hooks.callHook("skills:activate", { skill, via: "explicit" });
|
|
1259
|
+
}
|
|
1260
|
+
async function deactivateSkill(name) {
|
|
1261
|
+
const removed = skillActivationState.deactivate(name);
|
|
1262
|
+
if (removed)
|
|
1263
|
+
await hooks.callHook("skills:deactivate", { skill: removed.skill, reason: "explicit" });
|
|
961
1264
|
}
|
|
962
1265
|
if (session) {
|
|
963
1266
|
const originalSave = session.save.bind(session);
|
|
@@ -990,6 +1293,8 @@ ${skillsCatalog}`;
|
|
|
990
1293
|
waitForIdle,
|
|
991
1294
|
reset,
|
|
992
1295
|
destroy,
|
|
1296
|
+
activateSkill,
|
|
1297
|
+
deactivateSkill,
|
|
993
1298
|
get isRunning() {
|
|
994
1299
|
return running;
|
|
995
1300
|
},
|
|
@@ -1005,6 +1310,9 @@ ${skillsCatalog}`;
|
|
|
1005
1310
|
get session() {
|
|
1006
1311
|
return session ?? null;
|
|
1007
1312
|
},
|
|
1313
|
+
get activeSkills() {
|
|
1314
|
+
return skillActivationState.active();
|
|
1315
|
+
},
|
|
1008
1316
|
meta: provider.meta
|
|
1009
1317
|
};
|
|
1010
1318
|
}
|
|
@@ -1134,6 +1442,9 @@ var writeFile = {
|
|
|
1134
1442
|
|
|
1135
1443
|
export {
|
|
1136
1444
|
validateToolArgs,
|
|
1445
|
+
createSkillsReadTool,
|
|
1446
|
+
createSkillsRunScriptTool,
|
|
1447
|
+
createSkillsUseTool,
|
|
1137
1448
|
createAgent,
|
|
1138
1449
|
glob,
|
|
1139
1450
|
createInteractionTool,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// src/mcp/index.ts
|
|
2
2
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
3
3
|
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
|
|
4
|
-
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
4
|
+
import { getDefaultEnvironment, StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
5
5
|
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
6
6
|
function inferTransport(raw) {
|
|
7
7
|
if (raw.transport === "stdio" || raw.transport === "sse" || raw.transport === "streamable-http")
|
|
@@ -28,6 +28,8 @@ function normalizeOne(name, raw) {
|
|
|
28
28
|
config.args = raw.args;
|
|
29
29
|
if (raw.env)
|
|
30
30
|
config.env = raw.env;
|
|
31
|
+
if (raw.strictEnv === true)
|
|
32
|
+
config.strictEnv = true;
|
|
31
33
|
if (url)
|
|
32
34
|
config.url = url;
|
|
33
35
|
if (raw.headers)
|
|
@@ -72,14 +74,60 @@ function resultToString(content) {
|
|
|
72
74
|
return JSON.stringify(block);
|
|
73
75
|
}).join("\n");
|
|
74
76
|
}
|
|
77
|
+
function normalizeMcpBlocks(content) {
|
|
78
|
+
if (!Array.isArray(content))
|
|
79
|
+
return null;
|
|
80
|
+
const out = [];
|
|
81
|
+
for (const raw of content) {
|
|
82
|
+
if (!raw || typeof raw !== "object")
|
|
83
|
+
continue;
|
|
84
|
+
const block = raw;
|
|
85
|
+
if (block.type === "text" && typeof block.text === "string") {
|
|
86
|
+
out.push({ type: "text", text: block.text });
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
if (block.type === "image" && typeof block.data === "string") {
|
|
90
|
+
const mediaType = typeof block.mimeType === "string" ? block.mimeType : typeof block.mediaType === "string" ? block.mediaType : "image/png";
|
|
91
|
+
out.push({ type: "image", mediaType, data: block.data });
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
if (block.type === "resource" && block.resource && typeof block.resource === "object") {
|
|
95
|
+
const res = block.resource;
|
|
96
|
+
if (typeof res.text === "string") {
|
|
97
|
+
out.push({ type: "text", text: res.text });
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
if (typeof res.blob === "string" && typeof res.mimeType === "string" && res.mimeType.startsWith("image/")) {
|
|
101
|
+
out.push({ type: "image", mediaType: res.mimeType, data: res.blob });
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
out.push({ type: "text", text: JSON.stringify(block) });
|
|
106
|
+
}
|
|
107
|
+
return out;
|
|
108
|
+
}
|
|
109
|
+
function packMcpResult(content) {
|
|
110
|
+
const normalized = normalizeMcpBlocks(content);
|
|
111
|
+
if (!normalized || normalized.length === 0)
|
|
112
|
+
return "";
|
|
113
|
+
const parts = [];
|
|
114
|
+
for (const block of normalized) {
|
|
115
|
+
if (block.type !== "text")
|
|
116
|
+
return normalized;
|
|
117
|
+
parts.push(block.text);
|
|
118
|
+
}
|
|
119
|
+
return parts.join("\n");
|
|
120
|
+
}
|
|
75
121
|
function createTransport(config) {
|
|
76
122
|
switch (config.transport) {
|
|
77
|
-
case "stdio":
|
|
123
|
+
case "stdio": {
|
|
124
|
+
const mergedEnv = config.env && !config.strictEnv ? { ...getDefaultEnvironment(), ...config.env } : config.env;
|
|
78
125
|
return new StdioClientTransport({
|
|
79
126
|
command: config.command,
|
|
80
127
|
args: config.args,
|
|
81
|
-
env:
|
|
128
|
+
env: mergedEnv
|
|
82
129
|
});
|
|
130
|
+
}
|
|
83
131
|
case "sse":
|
|
84
132
|
return new SSEClientTransport(new URL(config.url), {
|
|
85
133
|
requestInit: config.headers ? { headers: config.headers } : void 0
|
|
@@ -147,7 +195,7 @@ async function connectMcpServers(configs, _clientFactory, hooks) {
|
|
|
147
195
|
timer = setTimeout(() => reject(new Error(`MCP tool "${tool.name}" on server "${config.name}" timed out after ${timeout}ms`)), timeout);
|
|
148
196
|
})
|
|
149
197
|
]).finally(() => clearTimeout(timer));
|
|
150
|
-
let output =
|
|
198
|
+
let output = packMcpResult(result.content);
|
|
151
199
|
const transformCtx = {
|
|
152
200
|
turnId,
|
|
153
201
|
callId,
|
|
@@ -223,5 +271,6 @@ async function connectMcpServers(configs, _clientFactory, hooks) {
|
|
|
223
271
|
export {
|
|
224
272
|
normalizeMcpServers,
|
|
225
273
|
resultToString,
|
|
274
|
+
normalizeMcpBlocks,
|
|
226
275
|
connectMcpServers
|
|
227
276
|
};
|