@sweny-ai/core 0.1.51 → 0.1.53
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/__tests__/publish.test.js +146 -0
- package/dist/__tests__/schema.test.js +156 -1
- package/dist/__tests__/skills.test.js +135 -4
- package/dist/__tests__/templates.test.d.ts +1 -0
- package/dist/__tests__/templates.test.js +49 -0
- package/dist/__tests__/version-consistency.test.js +7 -7
- package/dist/cli/config-file.d.ts +1 -1
- package/dist/cli/config-file.js +1 -1
- package/dist/cli/credentials.d.ts +1 -1
- package/dist/cli/credentials.js +1 -1
- package/dist/cli/credentials.test.js +1 -1
- package/dist/cli/e2e.d.ts +5 -1
- package/dist/cli/e2e.js +7 -3
- package/dist/cli/e2e.test.js +46 -1
- package/dist/cli/main.js +21 -7
- package/dist/cli/new.d.ts +101 -0
- package/dist/cli/new.js +670 -0
- package/dist/cli/new.test.d.ts +1 -0
- package/dist/cli/{init.test.js → new.test.js} +216 -78
- package/dist/cli/publish.d.ts +34 -0
- package/dist/cli/publish.js +368 -0
- package/dist/cli/templates.d.ts +13 -0
- package/dist/cli/templates.js +181 -0
- package/dist/executor.js +63 -20
- package/dist/executor.test.js +233 -0
- package/dist/index.d.ts +5 -5
- package/dist/index.js +3 -3
- package/dist/mcp.d.ts +2 -0
- package/dist/mcp.js +8 -0
- package/dist/mcp.test.js +67 -0
- package/dist/schema.d.ts +428 -18
- package/dist/schema.js +71 -4
- package/dist/skills/custom-loader.d.ts +12 -12
- package/dist/skills/custom-loader.js +105 -50
- package/dist/types.d.ts +18 -1
- package/dist/workflows/browser.js +3 -3
- package/package.json +10 -2
- package/dist/cli/init.d.ts +0 -53
- package/dist/cli/init.js +0 -521
- /package/dist/{cli/init.test.d.ts → __tests__/publish.test.d.ts} +0 -0
package/dist/executor.js
CHANGED
|
@@ -21,7 +21,11 @@ import { consoleLogger } from "./types.js";
|
|
|
21
21
|
* - `trace`: full ordered execution trace including loops and routing decisions
|
|
22
22
|
*/
|
|
23
23
|
export async function execute(workflow, input, options) {
|
|
24
|
-
const {
|
|
24
|
+
const { claude, observer } = options;
|
|
25
|
+
// Merge inline workflow skills into the skill map so they resolve at runtime.
|
|
26
|
+
// Inline skills (instruction/mcp only) become Skill objects with empty tools/config.
|
|
27
|
+
// The caller's skill map takes precedence — inline skills only fill gaps.
|
|
28
|
+
const skills = mergeInlineSkills(options.skills, workflow.skills);
|
|
25
29
|
const config = resolveConfig(skills, options.config);
|
|
26
30
|
const logger = options.logger ?? consoleLogger;
|
|
27
31
|
const results = new Map();
|
|
@@ -41,11 +45,13 @@ export async function execute(workflow, input, options) {
|
|
|
41
45
|
logger.info(`→ ${node.name}`, { node: currentId });
|
|
42
46
|
// Gather tools from the node's skills
|
|
43
47
|
const tools = resolveTools(node.skills, skills);
|
|
48
|
+
const skillInstructions = resolveSkillInstructions(node.skills, skills);
|
|
44
49
|
// Runtime guard: if this node declares skills but none resolved, the node
|
|
45
50
|
// cannot do its job (e.g. "create a Linear issue" with no linear skill).
|
|
46
51
|
// The startup validate() warns about this possibility, but only throw when
|
|
47
52
|
// the node is actually reached — unreachable nodes with missing skills are fine.
|
|
48
|
-
|
|
53
|
+
// Instruction-only skills (no tools) are valid — they inject context into the prompt.
|
|
54
|
+
if (node.skills.length > 0 && tools.length === 0 && skillInstructions.length === 0) {
|
|
49
55
|
throw new Error(`Node "${currentId}" requires skills [${node.skills.join(", ")}] but none are configured. ` +
|
|
50
56
|
`Set the required environment variables and try again.`);
|
|
51
57
|
}
|
|
@@ -66,7 +72,7 @@ export async function execute(workflow, input, options) {
|
|
|
66
72
|
},
|
|
67
73
|
}));
|
|
68
74
|
// Prepend rules and context to instruction if provided
|
|
69
|
-
const instruction = buildNodeInstruction(node.instruction, input);
|
|
75
|
+
const instruction = buildNodeInstruction(node.instruction, input, skillInstructions);
|
|
70
76
|
// Run Claude on this node
|
|
71
77
|
const result = await claude.run({
|
|
72
78
|
instruction,
|
|
@@ -116,30 +122,35 @@ export async function execute(workflow, input, options) {
|
|
|
116
122
|
* Rules get "You MUST follow" framing; context gets "Background" framing.
|
|
117
123
|
* Falls back to legacy `additionalContext` if rules/context aren't set.
|
|
118
124
|
*/
|
|
119
|
-
function buildNodeInstruction(baseInstruction, input) {
|
|
125
|
+
function buildNodeInstruction(baseInstruction, input, skillInstructions) {
|
|
120
126
|
const inp = input;
|
|
121
|
-
if (!inp)
|
|
122
|
-
return baseInstruction;
|
|
123
127
|
const sections = [];
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
128
|
+
if (inp) {
|
|
129
|
+
const rules = typeof inp.rules === "string" && inp.rules ? inp.rules : "";
|
|
130
|
+
const context = typeof inp.context === "string" && inp.context ? inp.context : "";
|
|
131
|
+
if (rules) {
|
|
132
|
+
sections.push(`## Rules — You MUST Follow These\n\n${rules}`);
|
|
133
|
+
}
|
|
134
|
+
if (context) {
|
|
135
|
+
sections.push(`## Background Context\n\n${context}`);
|
|
136
|
+
}
|
|
137
|
+
// Legacy fallback
|
|
138
|
+
if (sections.length === 0) {
|
|
139
|
+
const legacy = typeof inp.additionalContext === "string" ? inp.additionalContext : "";
|
|
140
|
+
if (legacy) {
|
|
141
|
+
sections.push(`## Additional Context & Rules\n\n${legacy}`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
132
144
|
}
|
|
133
|
-
//
|
|
134
|
-
if (
|
|
135
|
-
const
|
|
136
|
-
|
|
137
|
-
sections.push(`## Additional Context & Rules\n\n${legacy}`);
|
|
145
|
+
// Skill instructions — injected between context and base instruction
|
|
146
|
+
if (skillInstructions && skillInstructions.length > 0) {
|
|
147
|
+
for (const { name, instruction } of skillInstructions) {
|
|
148
|
+
sections.push(`## Skill: ${name}\n\n${instruction}`);
|
|
138
149
|
}
|
|
139
150
|
}
|
|
140
151
|
if (sections.length === 0)
|
|
141
152
|
return baseInstruction;
|
|
142
|
-
return `${sections.join("\n\n")}\n\n---\n\n${baseInstruction}`;
|
|
153
|
+
return `${sections.join("\n\n---\n\n")}\n\n---\n\n${baseInstruction}`;
|
|
143
154
|
}
|
|
144
155
|
/**
|
|
145
156
|
* Call observer without letting exceptions crash the workflow.
|
|
@@ -157,12 +168,44 @@ function safeObserve(observer, event, logger) {
|
|
|
157
168
|
(logger ?? consoleLogger).warn(`Observer error (non-fatal): ${err.message}`);
|
|
158
169
|
}
|
|
159
170
|
}
|
|
171
|
+
/**
|
|
172
|
+
* Merge inline workflow skill definitions into the skill map.
|
|
173
|
+
* Inline skills (from workflow.skills) become Skill objects with empty tools/config.
|
|
174
|
+
* The caller's skill map takes precedence — inline skills only fill gaps.
|
|
175
|
+
*/
|
|
176
|
+
function mergeInlineSkills(skills, inlineSkills) {
|
|
177
|
+
if (!inlineSkills || Object.keys(inlineSkills).length === 0)
|
|
178
|
+
return skills;
|
|
179
|
+
const merged = new Map(skills);
|
|
180
|
+
for (const [id, def] of Object.entries(inlineSkills)) {
|
|
181
|
+
if (merged.has(id))
|
|
182
|
+
continue; // caller-provided skill takes precedence
|
|
183
|
+
merged.set(id, {
|
|
184
|
+
id,
|
|
185
|
+
name: def.name ?? id,
|
|
186
|
+
description: def.description ?? `Inline skill: ${id}`,
|
|
187
|
+
category: "general",
|
|
188
|
+
config: {},
|
|
189
|
+
tools: [],
|
|
190
|
+
instruction: def.instruction,
|
|
191
|
+
mcp: def.mcp,
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
return merged;
|
|
195
|
+
}
|
|
160
196
|
function resolveTools(skillIds, skills) {
|
|
161
197
|
return skillIds
|
|
162
198
|
.map((id) => skills.get(id))
|
|
163
199
|
.filter((s) => s != null)
|
|
164
200
|
.flatMap((s) => s.tools);
|
|
165
201
|
}
|
|
202
|
+
/** Collect instruction strings from skills that have them, in array order. */
|
|
203
|
+
function resolveSkillInstructions(skillIds, skills) {
|
|
204
|
+
return skillIds
|
|
205
|
+
.map((id) => skills.get(id))
|
|
206
|
+
.filter((s) => s != null && s.instruction != null)
|
|
207
|
+
.map((s) => ({ name: s.name, instruction: s.instruction }));
|
|
208
|
+
}
|
|
166
209
|
/**
|
|
167
210
|
* Resolve config values: check explicit overrides first, then env vars.
|
|
168
211
|
* Throws if a required field is missing.
|
package/dist/executor.test.js
CHANGED
|
@@ -313,3 +313,236 @@ describe("file skill", () => {
|
|
|
313
313
|
expect(existsSync(path.join(outputDir, "deep/nested/file.json"))).toBe(true);
|
|
314
314
|
});
|
|
315
315
|
});
|
|
316
|
+
// ─── Skill instruction injection tests ──────────────────────────
|
|
317
|
+
describe("skill instruction injection", () => {
|
|
318
|
+
let outputDir;
|
|
319
|
+
beforeEach(() => {
|
|
320
|
+
outputDir = freshDir("skill-inject");
|
|
321
|
+
});
|
|
322
|
+
it("injects skill instructions into the prompt", async () => {
|
|
323
|
+
const instructionSkill = {
|
|
324
|
+
id: "code-standards",
|
|
325
|
+
name: "Code Standards",
|
|
326
|
+
description: "Team conventions",
|
|
327
|
+
category: "general",
|
|
328
|
+
config: {},
|
|
329
|
+
tools: [],
|
|
330
|
+
instruction: "Always use camelCase for variable names.",
|
|
331
|
+
};
|
|
332
|
+
const workflow = {
|
|
333
|
+
id: "test-inject",
|
|
334
|
+
name: "Inject Test",
|
|
335
|
+
description: "",
|
|
336
|
+
entry: "step",
|
|
337
|
+
nodes: {
|
|
338
|
+
step: {
|
|
339
|
+
name: "Do Work",
|
|
340
|
+
instruction: "Write some code.",
|
|
341
|
+
skills: ["code-standards"],
|
|
342
|
+
},
|
|
343
|
+
},
|
|
344
|
+
edges: [],
|
|
345
|
+
};
|
|
346
|
+
let capturedInstruction = "";
|
|
347
|
+
const claude = {
|
|
348
|
+
async run(opts) {
|
|
349
|
+
capturedInstruction = opts.instruction;
|
|
350
|
+
return { status: "success", data: {}, toolCalls: [] };
|
|
351
|
+
},
|
|
352
|
+
async evaluate() {
|
|
353
|
+
return "";
|
|
354
|
+
},
|
|
355
|
+
};
|
|
356
|
+
await execute(workflow, {}, {
|
|
357
|
+
skills: createSkillMap([instructionSkill]),
|
|
358
|
+
claude,
|
|
359
|
+
config: {},
|
|
360
|
+
});
|
|
361
|
+
expect(capturedInstruction).toContain("## Skill: Code Standards");
|
|
362
|
+
expect(capturedInstruction).toContain("Always use camelCase for variable names.");
|
|
363
|
+
expect(capturedInstruction).toContain("Write some code.");
|
|
364
|
+
});
|
|
365
|
+
it("injects multiple skill instructions in array order", async () => {
|
|
366
|
+
const skillA = {
|
|
367
|
+
id: "skill-a",
|
|
368
|
+
name: "Skill A",
|
|
369
|
+
description: "First",
|
|
370
|
+
category: "general",
|
|
371
|
+
config: {},
|
|
372
|
+
tools: [],
|
|
373
|
+
instruction: "AAA instruction",
|
|
374
|
+
};
|
|
375
|
+
const skillB = {
|
|
376
|
+
id: "skill-b",
|
|
377
|
+
name: "Skill B",
|
|
378
|
+
description: "Second",
|
|
379
|
+
category: "general",
|
|
380
|
+
config: {},
|
|
381
|
+
tools: [],
|
|
382
|
+
instruction: "BBB instruction",
|
|
383
|
+
};
|
|
384
|
+
const workflow = {
|
|
385
|
+
id: "test-multi",
|
|
386
|
+
name: "Multi Inject",
|
|
387
|
+
description: "",
|
|
388
|
+
entry: "step",
|
|
389
|
+
nodes: {
|
|
390
|
+
step: {
|
|
391
|
+
name: "Work",
|
|
392
|
+
instruction: "Do the work.",
|
|
393
|
+
skills: ["skill-a", "skill-b"],
|
|
394
|
+
},
|
|
395
|
+
},
|
|
396
|
+
edges: [],
|
|
397
|
+
};
|
|
398
|
+
let capturedInstruction = "";
|
|
399
|
+
const claude = {
|
|
400
|
+
async run(opts) {
|
|
401
|
+
capturedInstruction = opts.instruction;
|
|
402
|
+
return { status: "success", data: {}, toolCalls: [] };
|
|
403
|
+
},
|
|
404
|
+
async evaluate() {
|
|
405
|
+
return "";
|
|
406
|
+
},
|
|
407
|
+
};
|
|
408
|
+
await execute(workflow, {}, {
|
|
409
|
+
skills: createSkillMap([skillA, skillB]),
|
|
410
|
+
claude,
|
|
411
|
+
config: {},
|
|
412
|
+
});
|
|
413
|
+
const posA = capturedInstruction.indexOf("AAA instruction");
|
|
414
|
+
const posB = capturedInstruction.indexOf("BBB instruction");
|
|
415
|
+
const posBase = capturedInstruction.indexOf("Do the work.");
|
|
416
|
+
expect(posA).toBeLessThan(posB);
|
|
417
|
+
expect(posB).toBeLessThan(posBase);
|
|
418
|
+
});
|
|
419
|
+
it("allows instruction-only nodes (no tools, no error)", async () => {
|
|
420
|
+
const instructionSkill = {
|
|
421
|
+
id: "rubric",
|
|
422
|
+
name: "Evaluation Rubric",
|
|
423
|
+
description: "Scoring criteria",
|
|
424
|
+
category: "general",
|
|
425
|
+
config: {},
|
|
426
|
+
tools: [],
|
|
427
|
+
instruction: "Score quality 1-5.",
|
|
428
|
+
};
|
|
429
|
+
const workflow = {
|
|
430
|
+
id: "test-notools",
|
|
431
|
+
name: "No Tools",
|
|
432
|
+
description: "",
|
|
433
|
+
entry: "judge",
|
|
434
|
+
nodes: {
|
|
435
|
+
judge: {
|
|
436
|
+
name: "Judge",
|
|
437
|
+
instruction: "Evaluate the output.",
|
|
438
|
+
skills: ["rubric"],
|
|
439
|
+
},
|
|
440
|
+
},
|
|
441
|
+
edges: [],
|
|
442
|
+
};
|
|
443
|
+
const claude = {
|
|
444
|
+
async run() {
|
|
445
|
+
return { status: "success", data: { score: 4 }, toolCalls: [] };
|
|
446
|
+
},
|
|
447
|
+
async evaluate() {
|
|
448
|
+
return "";
|
|
449
|
+
},
|
|
450
|
+
};
|
|
451
|
+
// Should NOT throw "none are configured"
|
|
452
|
+
const { results } = await execute(workflow, {}, {
|
|
453
|
+
skills: createSkillMap([instructionSkill]),
|
|
454
|
+
claude,
|
|
455
|
+
config: {},
|
|
456
|
+
});
|
|
457
|
+
expect(results.get("judge")?.status).toBe("success");
|
|
458
|
+
});
|
|
459
|
+
it("wires inline workflow.skills into the executor automatically", async () => {
|
|
460
|
+
const workflow = {
|
|
461
|
+
id: "test-inline",
|
|
462
|
+
name: "Inline Skills",
|
|
463
|
+
description: "",
|
|
464
|
+
entry: "review",
|
|
465
|
+
nodes: {
|
|
466
|
+
review: {
|
|
467
|
+
name: "Review",
|
|
468
|
+
instruction: "Review the code.",
|
|
469
|
+
skills: ["my-rubric"],
|
|
470
|
+
},
|
|
471
|
+
},
|
|
472
|
+
edges: [],
|
|
473
|
+
skills: {
|
|
474
|
+
"my-rubric": {
|
|
475
|
+
name: "My Rubric",
|
|
476
|
+
instruction: "Check for security issues first, then correctness.",
|
|
477
|
+
},
|
|
478
|
+
},
|
|
479
|
+
};
|
|
480
|
+
let capturedInstruction = "";
|
|
481
|
+
const claude = {
|
|
482
|
+
async run(opts) {
|
|
483
|
+
capturedInstruction = opts.instruction;
|
|
484
|
+
return { status: "success", data: {}, toolCalls: [] };
|
|
485
|
+
},
|
|
486
|
+
async evaluate() {
|
|
487
|
+
return "";
|
|
488
|
+
},
|
|
489
|
+
};
|
|
490
|
+
// No "my-rubric" in the skill map — executor must pull it from workflow.skills
|
|
491
|
+
const { results } = await execute(workflow, {}, {
|
|
492
|
+
skills: createSkillMap([]),
|
|
493
|
+
claude,
|
|
494
|
+
config: {},
|
|
495
|
+
});
|
|
496
|
+
expect(results.get("review")?.status).toBe("success");
|
|
497
|
+
expect(capturedInstruction).toContain("## Skill: My Rubric");
|
|
498
|
+
expect(capturedInstruction).toContain("Check for security issues first");
|
|
499
|
+
});
|
|
500
|
+
it("caller-provided skills take precedence over inline workflow.skills", async () => {
|
|
501
|
+
const callerSkill = {
|
|
502
|
+
id: "shared",
|
|
503
|
+
name: "Caller Version",
|
|
504
|
+
description: "From caller",
|
|
505
|
+
category: "general",
|
|
506
|
+
config: {},
|
|
507
|
+
tools: [],
|
|
508
|
+
instruction: "Caller instruction wins.",
|
|
509
|
+
};
|
|
510
|
+
const workflow = {
|
|
511
|
+
id: "test-precedence",
|
|
512
|
+
name: "Precedence",
|
|
513
|
+
description: "",
|
|
514
|
+
entry: "step",
|
|
515
|
+
nodes: {
|
|
516
|
+
step: {
|
|
517
|
+
name: "Step",
|
|
518
|
+
instruction: "Do work.",
|
|
519
|
+
skills: ["shared"],
|
|
520
|
+
},
|
|
521
|
+
},
|
|
522
|
+
edges: [],
|
|
523
|
+
skills: {
|
|
524
|
+
shared: {
|
|
525
|
+
name: "Inline Version",
|
|
526
|
+
instruction: "Inline instruction should NOT appear.",
|
|
527
|
+
},
|
|
528
|
+
},
|
|
529
|
+
};
|
|
530
|
+
let capturedInstruction = "";
|
|
531
|
+
const claude = {
|
|
532
|
+
async run(opts) {
|
|
533
|
+
capturedInstruction = opts.instruction;
|
|
534
|
+
return { status: "success", data: {}, toolCalls: [] };
|
|
535
|
+
},
|
|
536
|
+
async evaluate() {
|
|
537
|
+
return "";
|
|
538
|
+
},
|
|
539
|
+
};
|
|
540
|
+
await execute(workflow, {}, {
|
|
541
|
+
skills: createSkillMap([callerSkill]),
|
|
542
|
+
claude,
|
|
543
|
+
config: {},
|
|
544
|
+
});
|
|
545
|
+
expect(capturedInstruction).toContain("Caller instruction wins.");
|
|
546
|
+
expect(capturedInstruction).not.toContain("Inline instruction should NOT appear.");
|
|
547
|
+
});
|
|
548
|
+
});
|
package/dist/index.d.ts
CHANGED
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
* })
|
|
22
22
|
* ```
|
|
23
23
|
*/
|
|
24
|
-
export type { Skill, SkillCategory, Tool, ToolContext, ConfigField, JSONSchema, Workflow, Node, Edge, NodeResult, ToolCall, ExecutionEvent, Observer, Claude, Logger, TraceStep, TraceEdge, ExecutionTrace, ExecutionResult, } from "./types.js";
|
|
24
|
+
export type { Skill, SkillCategory, SkillDefinition, Tool, ToolContext, ConfigField, JSONSchema, Workflow, Node, Edge, NodeResult, ToolCall, ExecutionEvent, Observer, Claude, Logger, TraceStep, TraceEdge, ExecutionTrace, ExecutionResult, } from "./types.js";
|
|
25
25
|
export { consoleLogger } from "./types.js";
|
|
26
26
|
export { execute } from "./executor.js";
|
|
27
27
|
export type { ExecuteOptions } from "./executor.js";
|
|
@@ -29,11 +29,11 @@ export { ClaudeClient } from "./claude.js";
|
|
|
29
29
|
export type { ClaudeClientOptions } from "./claude.js";
|
|
30
30
|
export { github, linear, slack, sentry, datadog, notification, supabase, builtinSkills, createSkillMap, allSkills, isSkillConfigured, configuredBuiltinSkills, validateWorkflowSkills, } from "./skills/index.js";
|
|
31
31
|
export type { SkillValidationResult } from "./skills/index.js";
|
|
32
|
-
export { loadCustomSkills, configuredSkills } from "./skills/custom-loader.js";
|
|
33
|
-
export { workflowZ, nodeZ, edgeZ, skillZ, parseWorkflow, validateWorkflow, workflowJsonSchema } from "./schema.js";
|
|
32
|
+
export { loadCustomSkills, discoverSkills, configuredSkills } from "./skills/custom-loader.js";
|
|
33
|
+
export { workflowZ, nodeZ, edgeZ, skillZ, mcpServerConfigZ, skillDefinitionZ, parseWorkflow, validateWorkflow, workflowJsonSchema, } from "./schema.js";
|
|
34
34
|
export type { WorkflowError } from "./schema.js";
|
|
35
|
-
export { buildAutoMcpServers, buildProviderContext } from "./mcp.js";
|
|
36
|
-
export type { ProviderContextOptions } from "./mcp.js";
|
|
35
|
+
export { buildAutoMcpServers, buildSkillMcpServers, buildProviderContext } from "./mcp.js";
|
|
36
|
+
export type { ProviderContextOptions, SkillMcpOptions } from "./mcp.js";
|
|
37
37
|
export type { McpServerConfig, McpAutoConfig } from "./types.js";
|
|
38
38
|
export { buildWorkflow, refineWorkflow } from "./workflow-builder.js";
|
|
39
39
|
export type { BuildWorkflowOptions } from "./workflow-builder.js";
|
package/dist/index.js
CHANGED
|
@@ -29,11 +29,11 @@ export { ClaudeClient } from "./claude.js";
|
|
|
29
29
|
// Skills
|
|
30
30
|
export { github, linear, slack, sentry, datadog, notification, supabase, builtinSkills, createSkillMap, allSkills, isSkillConfigured, configuredBuiltinSkills, validateWorkflowSkills, } from "./skills/index.js";
|
|
31
31
|
// Node-only skill discovery (filesystem-based)
|
|
32
|
-
export { loadCustomSkills, configuredSkills } from "./skills/custom-loader.js";
|
|
32
|
+
export { loadCustomSkills, discoverSkills, configuredSkills } from "./skills/custom-loader.js";
|
|
33
33
|
// Schema & validation
|
|
34
|
-
export { workflowZ, nodeZ, edgeZ, skillZ, parseWorkflow, validateWorkflow, workflowJsonSchema } from "./schema.js";
|
|
34
|
+
export { workflowZ, nodeZ, edgeZ, skillZ, mcpServerConfigZ, skillDefinitionZ, parseWorkflow, validateWorkflow, workflowJsonSchema, } from "./schema.js";
|
|
35
35
|
// MCP auto-injection
|
|
36
|
-
export { buildAutoMcpServers, buildProviderContext } from "./mcp.js";
|
|
36
|
+
export { buildAutoMcpServers, buildSkillMcpServers, buildProviderContext } from "./mcp.js";
|
|
37
37
|
// Workflow builder
|
|
38
38
|
export { buildWorkflow, refineWorkflow } from "./workflow-builder.js";
|
|
39
39
|
// Templates
|
package/dist/mcp.d.ts
CHANGED
|
@@ -6,6 +6,8 @@ export interface SkillMcpOptions {
|
|
|
6
6
|
credentials: Record<string, string>;
|
|
7
7
|
/** User-supplied MCP servers — always win on key conflict. */
|
|
8
8
|
userMcpServers?: Record<string, McpServerConfig>;
|
|
9
|
+
/** MCP servers declared by custom skills. Only wired when the skill is referenced. */
|
|
10
|
+
skillMcpServers?: Record<string, McpServerConfig>;
|
|
9
11
|
}
|
|
10
12
|
/**
|
|
11
13
|
* Build MCP server configs for the skills a workflow references.
|
package/dist/mcp.js
CHANGED
|
@@ -155,6 +155,14 @@ export function buildSkillMcpServers(opts) {
|
|
|
155
155
|
env: { ASANA_ACCESS_TOKEN: creds.ASANA_ACCESS_TOKEN },
|
|
156
156
|
};
|
|
157
157
|
}
|
|
158
|
+
// Skill-declared MCP servers — only include for referenced skills.
|
|
159
|
+
if (opts.skillMcpServers) {
|
|
160
|
+
for (const [skillId, mcpConfig] of Object.entries(opts.skillMcpServers)) {
|
|
161
|
+
if (refs.has(skillId) && !auto[skillId]) {
|
|
162
|
+
auto[skillId] = mcpConfig;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
158
166
|
// User-supplied servers always win on key conflict.
|
|
159
167
|
return { ...auto, ...(opts.userMcpServers ?? {}) };
|
|
160
168
|
}
|
package/dist/mcp.test.js
CHANGED
|
@@ -665,4 +665,71 @@ describe("buildSkillMcpServers", () => {
|
|
|
665
665
|
});
|
|
666
666
|
expect(result["betterstack"]?.headers?.Authorization).toBe("Bearer primary");
|
|
667
667
|
});
|
|
668
|
+
// ── Skill-declared MCP servers ──────────────────────────────────────
|
|
669
|
+
it("includes skill-declared MCP servers", () => {
|
|
670
|
+
const result = buildSkillMcpServers({
|
|
671
|
+
referencedSkills: new Set(["our-crm"]),
|
|
672
|
+
credentials: {},
|
|
673
|
+
skillMcpServers: {
|
|
674
|
+
"our-crm": {
|
|
675
|
+
type: "stdio",
|
|
676
|
+
command: "npx",
|
|
677
|
+
args: ["-y", "@company/crm-server"],
|
|
678
|
+
env: { API_KEY: "test-key" },
|
|
679
|
+
},
|
|
680
|
+
},
|
|
681
|
+
});
|
|
682
|
+
expect(result["our-crm"]).toBeDefined();
|
|
683
|
+
expect(result["our-crm"].command).toBe("npx");
|
|
684
|
+
});
|
|
685
|
+
it("skill-declared MCP is only included when skill is referenced", () => {
|
|
686
|
+
const result = buildSkillMcpServers({
|
|
687
|
+
referencedSkills: new Set(["github"]),
|
|
688
|
+
credentials: { GITHUB_TOKEN: "ghp_test" },
|
|
689
|
+
skillMcpServers: {
|
|
690
|
+
"our-crm": {
|
|
691
|
+
type: "stdio",
|
|
692
|
+
command: "npx",
|
|
693
|
+
args: ["-y", "@company/crm-server"],
|
|
694
|
+
},
|
|
695
|
+
},
|
|
696
|
+
});
|
|
697
|
+
expect(result["github"]).toBeDefined();
|
|
698
|
+
expect(result["our-crm"]).toBeUndefined();
|
|
699
|
+
});
|
|
700
|
+
it("user-supplied servers win over skill-declared", () => {
|
|
701
|
+
const result = buildSkillMcpServers({
|
|
702
|
+
referencedSkills: new Set(["our-crm"]),
|
|
703
|
+
credentials: {},
|
|
704
|
+
skillMcpServers: {
|
|
705
|
+
"our-crm": {
|
|
706
|
+
type: "stdio",
|
|
707
|
+
command: "npx",
|
|
708
|
+
args: ["-y", "@company/crm-server"],
|
|
709
|
+
},
|
|
710
|
+
},
|
|
711
|
+
userMcpServers: {
|
|
712
|
+
"our-crm": {
|
|
713
|
+
type: "http",
|
|
714
|
+
url: "https://override.example.com",
|
|
715
|
+
},
|
|
716
|
+
},
|
|
717
|
+
});
|
|
718
|
+
expect(result["our-crm"].url).toBe("https://override.example.com");
|
|
719
|
+
});
|
|
720
|
+
it("skill-declared and auto-wired MCPs coexist", () => {
|
|
721
|
+
const result = buildSkillMcpServers({
|
|
722
|
+
referencedSkills: new Set(["github", "our-crm"]),
|
|
723
|
+
credentials: { GITHUB_TOKEN: "ghp_test" },
|
|
724
|
+
skillMcpServers: {
|
|
725
|
+
"our-crm": {
|
|
726
|
+
type: "stdio",
|
|
727
|
+
command: "npx",
|
|
728
|
+
args: ["-y", "@company/crm-server"],
|
|
729
|
+
},
|
|
730
|
+
},
|
|
731
|
+
});
|
|
732
|
+
expect(result["github"]).toBeDefined();
|
|
733
|
+
expect(result["our-crm"]).toBeDefined();
|
|
734
|
+
});
|
|
668
735
|
});
|