@sweny-ai/core 0.1.78 → 0.1.80
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/executor.js +6 -1
- package/dist/schema.d.ts +5 -0
- package/dist/schema.js +1 -0
- package/dist/skills/github.js +17 -0
- package/dist/skills/index.d.ts +22 -1
- package/dist/skills/index.js +91 -0
- package/dist/skills/linear.js +20 -0
- package/dist/types.d.ts +19 -0
- package/dist/verify.d.ts +5 -4
- package/dist/verify.js +20 -10
- package/package.json +1 -1
package/dist/executor.js
CHANGED
|
@@ -17,6 +17,7 @@ import { resolveSources } from "./source-resolver.js";
|
|
|
17
17
|
import { evaluateVerify } from "./verify.js";
|
|
18
18
|
import { evaluateRequires } from "./requires.js";
|
|
19
19
|
import { buildRetryPreamble } from "./retry.js";
|
|
20
|
+
import { buildToolAliases } from "./skills/index.js";
|
|
20
21
|
/**
|
|
21
22
|
* Execute a workflow from entry to completion.
|
|
22
23
|
*
|
|
@@ -37,6 +38,10 @@ export async function execute(workflow, input, options) {
|
|
|
37
38
|
const nodeRunCounts = new Map(); // node → times executed
|
|
38
39
|
const trace = { steps: [], edges: [], sources: {} };
|
|
39
40
|
validate(workflow, skills);
|
|
41
|
+
// Build a verify-time alias table from the loaded skills. Each skill owns
|
|
42
|
+
// its own mapping between skill-tool names and equivalent MCP names. Core
|
|
43
|
+
// stays vendor-neutral — this call just unions what the skills declare.
|
|
44
|
+
const toolAliases = buildToolAliases(skills.values(), logger);
|
|
40
45
|
// ── Source resolution phase ─────────────────────────────────────
|
|
41
46
|
// Resolve all Source values (node instructions + rules/context at every
|
|
42
47
|
// level) into plain text before the main execution loop. This eagerly
|
|
@@ -159,7 +164,7 @@ export async function execute(workflow, input, options) {
|
|
|
159
164
|
// Retry only triggers on verify failure — bail on tool/API errors.
|
|
160
165
|
if (result.status !== "success")
|
|
161
166
|
break;
|
|
162
|
-
const verifyError = evaluateVerify(node.verify, result);
|
|
167
|
+
const verifyError = evaluateVerify(node.verify, result, toolAliases);
|
|
163
168
|
if (!verifyError)
|
|
164
169
|
break;
|
|
165
170
|
// Apply verify failure to the result.
|
package/dist/schema.d.ts
CHANGED
|
@@ -236,6 +236,7 @@ export declare const skillZ: z.ZodEffects<z.ZodObject<{
|
|
|
236
236
|
command?: string | undefined;
|
|
237
237
|
args?: string[] | undefined;
|
|
238
238
|
}>>;
|
|
239
|
+
mcpAliases: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodArray<z.ZodString, "many">>>;
|
|
239
240
|
}, "strip", z.ZodTypeAny, {
|
|
240
241
|
id: string;
|
|
241
242
|
name: string;
|
|
@@ -260,6 +261,7 @@ export declare const skillZ: z.ZodEffects<z.ZodObject<{
|
|
|
260
261
|
command?: string | undefined;
|
|
261
262
|
args?: string[] | undefined;
|
|
262
263
|
} | undefined;
|
|
264
|
+
mcpAliases?: Record<string, string[]> | undefined;
|
|
263
265
|
}, {
|
|
264
266
|
id: string;
|
|
265
267
|
name: string;
|
|
@@ -284,6 +286,7 @@ export declare const skillZ: z.ZodEffects<z.ZodObject<{
|
|
|
284
286
|
description: string;
|
|
285
287
|
input_schema: Record<string, unknown>;
|
|
286
288
|
}[] | undefined;
|
|
289
|
+
mcpAliases?: Record<string, string[]> | undefined;
|
|
287
290
|
}>, {
|
|
288
291
|
id: string;
|
|
289
292
|
name: string;
|
|
@@ -308,6 +311,7 @@ export declare const skillZ: z.ZodEffects<z.ZodObject<{
|
|
|
308
311
|
command?: string | undefined;
|
|
309
312
|
args?: string[] | undefined;
|
|
310
313
|
} | undefined;
|
|
314
|
+
mcpAliases?: Record<string, string[]> | undefined;
|
|
311
315
|
}, {
|
|
312
316
|
id: string;
|
|
313
317
|
name: string;
|
|
@@ -332,6 +336,7 @@ export declare const skillZ: z.ZodEffects<z.ZodObject<{
|
|
|
332
336
|
description: string;
|
|
333
337
|
input_schema: Record<string, unknown>;
|
|
334
338
|
}[] | undefined;
|
|
339
|
+
mcpAliases?: Record<string, string[]> | undefined;
|
|
335
340
|
}>;
|
|
336
341
|
/**
|
|
337
342
|
* NodeSources: either an array of Sources (additive — inherits cascade)
|
package/dist/schema.js
CHANGED
|
@@ -61,6 +61,7 @@ export const skillZ = z
|
|
|
61
61
|
tools: z.array(toolZ).default([]),
|
|
62
62
|
instruction: z.string().optional(),
|
|
63
63
|
mcp: mcpServerConfigZ.optional(),
|
|
64
|
+
mcpAliases: z.record(z.array(z.string().min(1)).min(1)).optional(),
|
|
64
65
|
})
|
|
65
66
|
.refine((s) => s.tools.length > 0 || s.instruction || s.mcp, {
|
|
66
67
|
message: "Skill must provide at least one of: tools, instruction, or mcp",
|
package/dist/skills/github.js
CHANGED
|
@@ -183,4 +183,21 @@ export const github = {
|
|
|
183
183
|
},
|
|
184
184
|
},
|
|
185
185
|
],
|
|
186
|
+
// Equivalent tool names on GitHub's official MCP server
|
|
187
|
+
// (github.com/github/github-mcp-server). Aliases declared for every
|
|
188
|
+
// GitHub MCP name that does NOT also exist on Linear's MCP. The
|
|
189
|
+
// executor's buildToolAliases already drops any name claimed by more
|
|
190
|
+
// than one loaded skill, so these declarations are safe even if a
|
|
191
|
+
// future provider starts using the same name — declare what this
|
|
192
|
+
// skill knows, let the runtime resolve conflicts.
|
|
193
|
+
//
|
|
194
|
+
// Names that currently exist on both Linear and GitHub MCPs and are
|
|
195
|
+
// therefore intentionally NOT aliased on either side: `get_issue`,
|
|
196
|
+
// `list_issues`.
|
|
197
|
+
mcpAliases: {
|
|
198
|
+
github_create_pr: ["create_pull_request"],
|
|
199
|
+
github_add_comment: ["add_issue_comment"],
|
|
200
|
+
github_create_issue: ["create_issue"],
|
|
201
|
+
github_search_issues: ["search_issues"],
|
|
202
|
+
},
|
|
186
203
|
};
|
package/dist/skills/index.d.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* Custom skill discovery (filesystem-based) lives in `./custom-loader.js`,
|
|
8
8
|
* which is Node-only and re-exported from `@sweny-ai/core` (the Node entry).
|
|
9
9
|
*/
|
|
10
|
-
import type { Skill, SkillCategory } from "../types.js";
|
|
10
|
+
import type { Logger, Skill, SkillCategory } from "../types.js";
|
|
11
11
|
import { github } from "./github.js";
|
|
12
12
|
import { linear } from "./linear.js";
|
|
13
13
|
import { slack } from "./slack.js";
|
|
@@ -86,3 +86,24 @@ export declare function validateWorkflowSkills(workflow: {
|
|
|
86
86
|
skills: string[];
|
|
87
87
|
}>;
|
|
88
88
|
}, available: Map<string, Skill>, inlineSkills?: Record<string, InlineSkillDef>): SkillValidationResult;
|
|
89
|
+
/**
|
|
90
|
+
* Build a symmetric tool-name alias table from the loaded skills.
|
|
91
|
+
*
|
|
92
|
+
* Each skill declares its own `mcpAliases` (canonical name → list of
|
|
93
|
+
* equivalent MCP tool names). This function:
|
|
94
|
+
*
|
|
95
|
+
* 1. Collects every alias pair declared by every loaded skill.
|
|
96
|
+
* 2. Flattens each pair into one equivalence group (symmetric closure).
|
|
97
|
+
* 3. Drops any MCP name claimed by more than one skill (ambiguous).
|
|
98
|
+
*
|
|
99
|
+
* Core stays vendor-neutral: nothing here knows Linear or GitHub by name.
|
|
100
|
+
* Skills own their own naming, and ambiguity is detected at map-build
|
|
101
|
+
* time so a call to one provider's `get_issue` can never spuriously
|
|
102
|
+
* satisfy another provider's `any_tool_called` verify rule.
|
|
103
|
+
*
|
|
104
|
+
* @returns a map from every participating name to the full set of names
|
|
105
|
+
* that count as equivalent to it (including itself). Names that do not
|
|
106
|
+
* participate in any alias are absent — callers should fall back to
|
|
107
|
+
* name-equality when a lookup returns `undefined`.
|
|
108
|
+
*/
|
|
109
|
+
export declare function buildToolAliases(skills: Iterable<Skill>, logger?: Logger): ReadonlyMap<string, ReadonlySet<string>>;
|
package/dist/skills/index.js
CHANGED
|
@@ -144,3 +144,94 @@ export function validateWorkflowSkills(workflow, available, inlineSkills) {
|
|
|
144
144
|
}
|
|
145
145
|
return { configured, missing, errors, warnings };
|
|
146
146
|
}
|
|
147
|
+
/**
|
|
148
|
+
* Build a symmetric tool-name alias table from the loaded skills.
|
|
149
|
+
*
|
|
150
|
+
* Each skill declares its own `mcpAliases` (canonical name → list of
|
|
151
|
+
* equivalent MCP tool names). This function:
|
|
152
|
+
*
|
|
153
|
+
* 1. Collects every alias pair declared by every loaded skill.
|
|
154
|
+
* 2. Flattens each pair into one equivalence group (symmetric closure).
|
|
155
|
+
* 3. Drops any MCP name claimed by more than one skill (ambiguous).
|
|
156
|
+
*
|
|
157
|
+
* Core stays vendor-neutral: nothing here knows Linear or GitHub by name.
|
|
158
|
+
* Skills own their own naming, and ambiguity is detected at map-build
|
|
159
|
+
* time so a call to one provider's `get_issue` can never spuriously
|
|
160
|
+
* satisfy another provider's `any_tool_called` verify rule.
|
|
161
|
+
*
|
|
162
|
+
* @returns a map from every participating name to the full set of names
|
|
163
|
+
* that count as equivalent to it (including itself). Names that do not
|
|
164
|
+
* participate in any alias are absent — callers should fall back to
|
|
165
|
+
* name-equality when a lookup returns `undefined`.
|
|
166
|
+
*/
|
|
167
|
+
export function buildToolAliases(skills, logger) {
|
|
168
|
+
// First pass: record which skill(s) claim each MCP alias name.
|
|
169
|
+
const mcpClaims = new Map(); // mcp name → skill ids that declared it
|
|
170
|
+
const pairs = []; // [canonical, mcpAlias]
|
|
171
|
+
for (const skill of skills) {
|
|
172
|
+
if (!skill.mcpAliases)
|
|
173
|
+
continue;
|
|
174
|
+
for (const [canonical, aliases] of Object.entries(skill.mcpAliases)) {
|
|
175
|
+
for (const alias of aliases) {
|
|
176
|
+
if (canonical === alias)
|
|
177
|
+
continue;
|
|
178
|
+
pairs.push([canonical, alias]);
|
|
179
|
+
const claimers = mcpClaims.get(alias);
|
|
180
|
+
if (claimers) {
|
|
181
|
+
if (!claimers.includes(skill.id))
|
|
182
|
+
claimers.push(skill.id);
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
mcpClaims.set(alias, [skill.id]);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
// Drop ambiguous MCP names (claimed by >1 skill) and log once.
|
|
191
|
+
const ambiguous = new Set();
|
|
192
|
+
for (const [name, claimers] of mcpClaims) {
|
|
193
|
+
if (claimers.length > 1) {
|
|
194
|
+
ambiguous.add(name);
|
|
195
|
+
logger?.warn(`Tool alias "${name}" is declared by multiple skills (${claimers.join(", ")}); dropping from verify alias table to avoid cross-provider false positives.`);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
// Second pass: union-find over the remaining pairs.
|
|
199
|
+
const parent = new Map();
|
|
200
|
+
const find = (x) => {
|
|
201
|
+
let cur = parent.get(x) ?? x;
|
|
202
|
+
while (cur !== (parent.get(cur) ?? cur))
|
|
203
|
+
cur = parent.get(cur) ?? cur;
|
|
204
|
+
parent.set(x, cur);
|
|
205
|
+
return cur;
|
|
206
|
+
};
|
|
207
|
+
const union = (a, b) => {
|
|
208
|
+
const ra = find(a);
|
|
209
|
+
const rb = find(b);
|
|
210
|
+
if (ra !== rb)
|
|
211
|
+
parent.set(ra, rb);
|
|
212
|
+
};
|
|
213
|
+
// `find` registers previously-unseen nodes via `parent.get(x) ?? x` and
|
|
214
|
+
// writes them back, so `union` handles first-contact pairs on its own.
|
|
215
|
+
for (const [canonical, alias] of pairs) {
|
|
216
|
+
if (ambiguous.has(alias))
|
|
217
|
+
continue;
|
|
218
|
+
union(canonical, alias);
|
|
219
|
+
}
|
|
220
|
+
// Build the result: each name → its full equivalence set.
|
|
221
|
+
const groups = new Map();
|
|
222
|
+
for (const name of parent.keys()) {
|
|
223
|
+
const root = find(name);
|
|
224
|
+
let group = groups.get(root);
|
|
225
|
+
if (!group) {
|
|
226
|
+
group = new Set();
|
|
227
|
+
groups.set(root, group);
|
|
228
|
+
}
|
|
229
|
+
group.add(name);
|
|
230
|
+
}
|
|
231
|
+
const result = new Map();
|
|
232
|
+
for (const group of groups.values()) {
|
|
233
|
+
for (const name of group)
|
|
234
|
+
result.set(name, group);
|
|
235
|
+
}
|
|
236
|
+
return result;
|
|
237
|
+
}
|
package/dist/skills/linear.js
CHANGED
|
@@ -144,4 +144,24 @@ export const linear = {
|
|
|
144
144
|
},
|
|
145
145
|
},
|
|
146
146
|
],
|
|
147
|
+
// Equivalent tool names on Linear's official remote MCP server (mcp.linear.app).
|
|
148
|
+
//
|
|
149
|
+
// Linear's MCP uses upsert naming: a single `save_issue` handles both
|
|
150
|
+
// create and update. The executor builds symmetric equivalence groups, so
|
|
151
|
+
// both `linear_create_issue` and `linear_update_issue` end up in one group
|
|
152
|
+
// with `save_issue`. A `save_issue` call satisfies verify rules for either
|
|
153
|
+
// canonical name — verify cannot distinguish create from update when the
|
|
154
|
+
// agent uses the remote MCP. That reflects the provider's reality, not a
|
|
155
|
+
// shortcoming of the alias layer.
|
|
156
|
+
//
|
|
157
|
+
// Names that also exist on GitHub's MCP server (`get_issue`, `list_issues`,
|
|
158
|
+
// `get_team`) are left out. `buildToolAliases` also drops any MCP name
|
|
159
|
+
// claimed by more than one loaded skill as a defensive backstop.
|
|
160
|
+
mcpAliases: {
|
|
161
|
+
linear_create_issue: ["save_issue"],
|
|
162
|
+
linear_update_issue: ["save_issue"],
|
|
163
|
+
linear_search_issues: ["list_issues"],
|
|
164
|
+
linear_add_comment: ["save_comment"],
|
|
165
|
+
linear_list_teams: ["list_teams"],
|
|
166
|
+
},
|
|
147
167
|
};
|
package/dist/types.d.ts
CHANGED
|
@@ -36,6 +36,25 @@ export interface Skill {
|
|
|
36
36
|
instruction?: string;
|
|
37
37
|
/** External MCP server definition wired for nodes referencing this skill. */
|
|
38
38
|
mcp?: McpServerConfig;
|
|
39
|
+
/**
|
|
40
|
+
* Tool-name aliases recognized by `verify`.
|
|
41
|
+
*
|
|
42
|
+
* When a workflow references `any_tool_called: [linear_create_issue]` and
|
|
43
|
+
* the agent instead calls the equivalent MCP tool exposed by this skill's
|
|
44
|
+
* MCP server (e.g. `save_issue` on Linear's remote MCP), the two names
|
|
45
|
+
* should count as equivalent. Each skill owns the mapping for its own
|
|
46
|
+
* domain — core stays vendor-neutral.
|
|
47
|
+
*
|
|
48
|
+
* Key: a canonical tool name (usually one of this skill's `tools[].name`,
|
|
49
|
+
* but any name the skill wants to equate is valid).
|
|
50
|
+
* Value: list of equivalent names, typically tool names exposed by this
|
|
51
|
+
* skill's external MCP server.
|
|
52
|
+
*
|
|
53
|
+
* Aliases are symmetric: a verify rule naming either side matches a call
|
|
54
|
+
* on either side. Omit names that are ambiguous across providers
|
|
55
|
+
* (e.g. `get_issue` is exposed by both Linear and GitHub MCP servers).
|
|
56
|
+
*/
|
|
57
|
+
mcpAliases?: Record<string, string[]>;
|
|
39
58
|
}
|
|
40
59
|
/**
|
|
41
60
|
* Per-node rules or context.
|
package/dist/verify.d.ts
CHANGED
|
@@ -9,9 +9,10 @@ export type Resolution = {
|
|
|
9
9
|
};
|
|
10
10
|
export declare function resolvePath(data: unknown, path: string): Resolution;
|
|
11
11
|
export declare function isErrorOutput(output: unknown): boolean;
|
|
12
|
-
export
|
|
13
|
-
export declare function
|
|
14
|
-
export declare function
|
|
12
|
+
export type ToolAliases = ReadonlyMap<string, ReadonlySet<string>>;
|
|
13
|
+
export declare function checkAnyToolCalled(required: string[], toolCalls: ToolCall[], aliases?: ToolAliases): string | null;
|
|
14
|
+
export declare function checkAllToolsCalled(required: string[], toolCalls: ToolCall[], aliases?: ToolAliases): string | null;
|
|
15
|
+
export declare function checkNoToolCalled(forbidden: string[], toolCalls: ToolCall[], aliases?: ToolAliases): string | null;
|
|
15
16
|
export declare function checkOutputRequired(paths: string[], data: unknown): string | null;
|
|
16
17
|
export declare function checkOutputMatches(matches: OutputMatch[], data: unknown): string | null;
|
|
17
18
|
/**
|
|
@@ -21,4 +22,4 @@ export declare function checkOutputMatches(matches: OutputMatch[], data: unknown
|
|
|
21
22
|
* Deterministic and side-effect free — the executor calls this synchronously
|
|
22
23
|
* after the LLM finishes.
|
|
23
24
|
*/
|
|
24
|
-
export declare function evaluateVerify(verify: NodeVerify | undefined, result: NodeResult): string | null;
|
|
25
|
+
export declare function evaluateVerify(verify: NodeVerify | undefined, result: NodeResult, aliases?: ToolAliases): string | null;
|
package/dist/verify.js
CHANGED
|
@@ -105,22 +105,32 @@ function succeededTools(toolCalls) {
|
|
|
105
105
|
function calledList(toolCalls) {
|
|
106
106
|
return toolCalls.map((c) => c.tool).join(", ") || "none";
|
|
107
107
|
}
|
|
108
|
-
|
|
108
|
+
function expandAliases(name, aliases) {
|
|
109
|
+
return aliases?.get(name) ?? new Set([name]);
|
|
110
|
+
}
|
|
111
|
+
function anyAliasIn(name, set, aliases) {
|
|
112
|
+
for (const alias of expandAliases(name, aliases)) {
|
|
113
|
+
if (set.has(alias))
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
export function checkAnyToolCalled(required, toolCalls, aliases) {
|
|
109
119
|
const succeeded = succeededTools(toolCalls);
|
|
110
|
-
if (required.some((t) =>
|
|
120
|
+
if (required.some((t) => anyAliasIn(t, succeeded, aliases)))
|
|
111
121
|
return null;
|
|
112
122
|
return `any_tool_called: required one of [${required.join(", ")}] to succeed, called: [${calledList(toolCalls)}]`;
|
|
113
123
|
}
|
|
114
|
-
export function checkAllToolsCalled(required, toolCalls) {
|
|
124
|
+
export function checkAllToolsCalled(required, toolCalls, aliases) {
|
|
115
125
|
const succeeded = succeededTools(toolCalls);
|
|
116
|
-
const missing = required.filter((t) => !
|
|
126
|
+
const missing = required.filter((t) => !anyAliasIn(t, succeeded, aliases));
|
|
117
127
|
if (missing.length === 0)
|
|
118
128
|
return null;
|
|
119
129
|
return `all_tools_called: missing successful calls to [${missing.join(", ")}], called: [${calledList(toolCalls)}]`;
|
|
120
130
|
}
|
|
121
|
-
export function checkNoToolCalled(forbidden, toolCalls) {
|
|
131
|
+
export function checkNoToolCalled(forbidden, toolCalls, aliases) {
|
|
122
132
|
const calledNames = new Set(toolCalls.map((c) => c.tool));
|
|
123
|
-
const violated = forbidden.filter((t) =>
|
|
133
|
+
const violated = forbidden.filter((t) => anyAliasIn(t, calledNames, aliases));
|
|
124
134
|
if (violated.length === 0)
|
|
125
135
|
return null;
|
|
126
136
|
return `no_tool_called: forbidden tools were invoked: [${violated.join(", ")}], called: [${calledList(toolCalls)}]`;
|
|
@@ -312,22 +322,22 @@ function describeOperator(c) {
|
|
|
312
322
|
* Deterministic and side-effect free — the executor calls this synchronously
|
|
313
323
|
* after the LLM finishes.
|
|
314
324
|
*/
|
|
315
|
-
export function evaluateVerify(verify, result) {
|
|
325
|
+
export function evaluateVerify(verify, result, aliases) {
|
|
316
326
|
if (!verify)
|
|
317
327
|
return null;
|
|
318
328
|
const failures = [];
|
|
319
329
|
if (verify.any_tool_called && verify.any_tool_called.length > 0) {
|
|
320
|
-
const e = checkAnyToolCalled(verify.any_tool_called, result.toolCalls);
|
|
330
|
+
const e = checkAnyToolCalled(verify.any_tool_called, result.toolCalls, aliases);
|
|
321
331
|
if (e)
|
|
322
332
|
failures.push(e);
|
|
323
333
|
}
|
|
324
334
|
if (verify.all_tools_called && verify.all_tools_called.length > 0) {
|
|
325
|
-
const e = checkAllToolsCalled(verify.all_tools_called, result.toolCalls);
|
|
335
|
+
const e = checkAllToolsCalled(verify.all_tools_called, result.toolCalls, aliases);
|
|
326
336
|
if (e)
|
|
327
337
|
failures.push(e);
|
|
328
338
|
}
|
|
329
339
|
if (verify.no_tool_called && verify.no_tool_called.length > 0) {
|
|
330
|
-
const e = checkNoToolCalled(verify.no_tool_called, result.toolCalls);
|
|
340
|
+
const e = checkNoToolCalled(verify.no_tool_called, result.toolCalls, aliases);
|
|
331
341
|
if (e)
|
|
332
342
|
failures.push(e);
|
|
333
343
|
}
|