skillscript-runtime 0.22.1 → 0.23.0
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/CHANGELOG.md +13 -0
- package/dist/connectors/http-mcp.d.ts +9 -1
- package/dist/connectors/http-mcp.d.ts.map +1 -1
- package/dist/connectors/http-mcp.js +26 -5
- package/dist/connectors/http-mcp.js.map +1 -1
- package/dist/connectors/mcp-remote.d.ts +10 -1
- package/dist/connectors/mcp-remote.d.ts.map +1 -1
- package/dist/connectors/mcp-remote.js +23 -3
- package/dist/connectors/mcp-remote.js.map +1 -1
- package/dist/connectors/types.d.ts +21 -0
- package/dist/connectors/types.d.ts.map +1 -1
- package/dist/connectors/types.js.map +1 -1
- package/dist/lint.d.ts +17 -1
- package/dist/lint.d.ts.map +1 -1
- package/dist/lint.js +167 -0
- package/dist/lint.js.map +1 -1
- package/dist/mcp-server.d.ts +7 -0
- package/dist/mcp-server.d.ts.map +1 -1
- package/dist/mcp-server.js +86 -2
- package/dist/mcp-server.js.map +1 -1
- package/dist/observed-shape.d.ts +36 -0
- package/dist/observed-shape.d.ts.map +1 -0
- package/dist/observed-shape.js +67 -0
- package/dist/observed-shape.js.map +1 -0
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +29 -0
- package/dist/runtime.js.map +1 -1
- package/dist/skill-surface.d.ts +12 -0
- package/dist/skill-surface.d.ts.map +1 -1
- package/dist/skill-surface.js +22 -0
- package/dist/skill-surface.js.map +1 -1
- package/dist/trace.d.ts +28 -0
- package/dist/trace.d.ts.map +1 -1
- package/dist/trace.js +56 -3
- package/dist/trace.js.map +1 -1
- package/docs/adopter-playbook.md +10 -0
- package/examples/skillscripts/hello-world.skill.provenance.json +1 -1
- package/package.json +1 -1
package/dist/lint.js
CHANGED
|
@@ -24,6 +24,7 @@ export async function lint(source, options) {
|
|
|
24
24
|
bareArrayReturnTools: options?.bareArrayReturnTools ?? [],
|
|
25
25
|
shellAllowlist: options?.shellAllowlist,
|
|
26
26
|
mcpConnectorStaticTools: collectMcpConnectorStaticToolsFromRegistry(options?.registry),
|
|
27
|
+
mcpConnectorToolSchemas: options?.mcpConnectorToolSchemas ?? (await collectMcpConnectorToolSchemasFromRegistry(options?.registry)),
|
|
27
28
|
};
|
|
28
29
|
const findings = [];
|
|
29
30
|
for (const rule of RULES) {
|
|
@@ -67,6 +68,9 @@ export function lintSync(source, options) {
|
|
|
67
68
|
bareArrayReturnTools: options?.bareArrayReturnTools ?? [],
|
|
68
69
|
shellAllowlist: options?.shellAllowlist,
|
|
69
70
|
mcpConnectorStaticTools: collectMcpConnectorStaticToolsFromRegistry(options?.registry),
|
|
71
|
+
// lintSync can't warm schemas (describeTools is async); honor an explicit
|
|
72
|
+
// injection (tests) but otherwise leave empty → connector-arg rules degrade.
|
|
73
|
+
mcpConnectorToolSchemas: options?.mcpConnectorToolSchemas ?? new Map(),
|
|
70
74
|
};
|
|
71
75
|
const findings = [];
|
|
72
76
|
for (const rule of RULES) {
|
|
@@ -1037,6 +1041,124 @@ const UNVERIFIED_QUALIFIED_TOOL = {
|
|
|
1037
1041
|
return findings;
|
|
1038
1042
|
},
|
|
1039
1043
|
};
|
|
1044
|
+
// v0.23.0 — connector-aware input lint. For a qualified `$ ref.tool arg=...`
|
|
1045
|
+
// op where the connector's warmed inputSchema is available (ctx.
|
|
1046
|
+
// mcpConnectorToolSchemas), validate the arg NAMES against the schema. Catches
|
|
1047
|
+
// the typo class (`$ ddg.search querry=...`) statically — today it compiles
|
|
1048
|
+
// clean and fails only at runtime because MCP connector args are unvalidated.
|
|
1049
|
+
//
|
|
1050
|
+
// SCOPE (phase 1): qualified `$ connector.tool` form only (bare `$ tool`
|
|
1051
|
+
// resolves its owning connector at dispatch and is deferred). Reads only the
|
|
1052
|
+
// pre-warmed cache — no I/O here; the async lint() entry point warms it.
|
|
1053
|
+
//
|
|
1054
|
+
// GRACEFUL DEGRADE: no cache entry / no inputSchema / open schema
|
|
1055
|
+
// (additionalProperties: true) → skip. Schema-less or unreachable connectors
|
|
1056
|
+
// behave exactly as before (no false positives).
|
|
1057
|
+
function connectorArgLintInfo(ctx, op) {
|
|
1058
|
+
if (op.kind !== "$" || op.mcpConnector === undefined)
|
|
1059
|
+
return null;
|
|
1060
|
+
const byTool = ctx.mcpConnectorToolSchemas.get(op.mcpConnector);
|
|
1061
|
+
if (byTool === undefined)
|
|
1062
|
+
return null;
|
|
1063
|
+
const m = /^([A-Za-z_][\w:-]*)\s*([\s\S]*)$/.exec(op.body);
|
|
1064
|
+
if (m === null)
|
|
1065
|
+
return null;
|
|
1066
|
+
const toolName = m[1];
|
|
1067
|
+
const descriptor = byTool.get(toolName);
|
|
1068
|
+
if (descriptor === undefined || descriptor.inputSchema === undefined)
|
|
1069
|
+
return null;
|
|
1070
|
+
const argNames = [];
|
|
1071
|
+
for (const tok of tokenizeKeywordArgs(m[2] ?? "")) {
|
|
1072
|
+
const eq = tok.indexOf("=");
|
|
1073
|
+
if (eq === -1)
|
|
1074
|
+
continue;
|
|
1075
|
+
const k = tok.slice(0, eq).trim();
|
|
1076
|
+
// `timeout` is a runtime-reserved per-op kwarg, popped before the connector
|
|
1077
|
+
// sees args — never part of the tool's own schema.
|
|
1078
|
+
if (k !== "" && k !== "timeout")
|
|
1079
|
+
argNames.push(k);
|
|
1080
|
+
}
|
|
1081
|
+
return { connector: op.mcpConnector, toolName, argNames, inputSchema: descriptor.inputSchema };
|
|
1082
|
+
}
|
|
1083
|
+
const UNKNOWN_CONNECTOR_ARG = {
|
|
1084
|
+
id: "unknown-connector-arg",
|
|
1085
|
+
severity: "warning",
|
|
1086
|
+
description: "A qualified `$ ref.tool arg=...` op passes an argument name the connector tool's inputSchema doesn't declare.",
|
|
1087
|
+
remediation: "Use an argument the tool declares (see `runtime_capabilities` for the connector tool's schema), or remove the unrecognized one. If the tool genuinely accepts it, the upstream inputSchema may be incomplete — verify before relying on it.",
|
|
1088
|
+
check: (ctx) => {
|
|
1089
|
+
if (ctx.mcpConnectorToolSchemas.size === 0)
|
|
1090
|
+
return [];
|
|
1091
|
+
const findings = [];
|
|
1092
|
+
const reported = new Set();
|
|
1093
|
+
for (const [targetName, target] of ctx.parsed.targets) {
|
|
1094
|
+
walkOps(target.ops, (op) => {
|
|
1095
|
+
const info = connectorArgLintInfo(ctx, op);
|
|
1096
|
+
if (info === null)
|
|
1097
|
+
return;
|
|
1098
|
+
const props = info.inputSchema.properties;
|
|
1099
|
+
if (props === null || typeof props !== "object")
|
|
1100
|
+
return; // can't enumerate the valid set
|
|
1101
|
+
if (info.inputSchema.additionalProperties === true)
|
|
1102
|
+
return; // open schema
|
|
1103
|
+
const valid = new Set(Object.keys(props));
|
|
1104
|
+
for (const arg of info.argNames) {
|
|
1105
|
+
if (valid.has(arg))
|
|
1106
|
+
continue;
|
|
1107
|
+
const key = `${targetName}:${info.connector}:${info.toolName}:${arg}`;
|
|
1108
|
+
if (reported.has(key))
|
|
1109
|
+
continue;
|
|
1110
|
+
reported.add(key);
|
|
1111
|
+
findings.push({
|
|
1112
|
+
rule: "unknown-connector-arg",
|
|
1113
|
+
severity: "warning",
|
|
1114
|
+
message: `\`$ ${info.connector}.${info.toolName}\` in target '${targetName}' passes arg '${arg}', which isn't in the tool's input schema. Declared args: ${[...valid].join(", ") || "(none)"}.`,
|
|
1115
|
+
block: targetName,
|
|
1116
|
+
extras: { connector: info.connector, tool: info.toolName, arg, declared_args: [...valid] },
|
|
1117
|
+
});
|
|
1118
|
+
}
|
|
1119
|
+
});
|
|
1120
|
+
}
|
|
1121
|
+
return findings;
|
|
1122
|
+
},
|
|
1123
|
+
};
|
|
1124
|
+
const MISSING_REQUIRED_CONNECTOR_ARG = {
|
|
1125
|
+
id: "missing-required-connector-arg",
|
|
1126
|
+
severity: "warning",
|
|
1127
|
+
description: "A qualified `$ ref.tool` op omits an argument the connector tool's inputSchema marks required.",
|
|
1128
|
+
remediation: "Supply the required argument(s). See `runtime_capabilities` for the tool's schema (its `required` list).",
|
|
1129
|
+
check: (ctx) => {
|
|
1130
|
+
if (ctx.mcpConnectorToolSchemas.size === 0)
|
|
1131
|
+
return [];
|
|
1132
|
+
const findings = [];
|
|
1133
|
+
const reported = new Set();
|
|
1134
|
+
for (const [targetName, target] of ctx.parsed.targets) {
|
|
1135
|
+
walkOps(target.ops, (op) => {
|
|
1136
|
+
const info = connectorArgLintInfo(ctx, op);
|
|
1137
|
+
if (info === null)
|
|
1138
|
+
return;
|
|
1139
|
+
const requiredRaw = info.inputSchema.required;
|
|
1140
|
+
if (!Array.isArray(requiredRaw))
|
|
1141
|
+
return;
|
|
1142
|
+
const provided = new Set(info.argNames);
|
|
1143
|
+
const missing = requiredRaw.filter((r) => typeof r === "string" && !provided.has(r));
|
|
1144
|
+
if (missing.length === 0)
|
|
1145
|
+
return;
|
|
1146
|
+
const key = `${targetName}:${info.connector}:${info.toolName}:${missing.join(",")}`;
|
|
1147
|
+
if (reported.has(key))
|
|
1148
|
+
return;
|
|
1149
|
+
reported.add(key);
|
|
1150
|
+
findings.push({
|
|
1151
|
+
rule: "missing-required-connector-arg",
|
|
1152
|
+
severity: "warning",
|
|
1153
|
+
message: `\`$ ${info.connector}.${info.toolName}\` in target '${targetName}' is missing required arg(s): ${missing.join(", ")}.`,
|
|
1154
|
+
block: targetName,
|
|
1155
|
+
extras: { connector: info.connector, tool: info.toolName, missing },
|
|
1156
|
+
});
|
|
1157
|
+
});
|
|
1158
|
+
}
|
|
1159
|
+
return findings;
|
|
1160
|
+
},
|
|
1161
|
+
};
|
|
1040
1162
|
// v0.5.0 item 5 — bare `$ TOOL` op (no connector prefix) when no
|
|
1041
1163
|
// `primary` connector is wired. Runtime now throws ConnectorNotFoundError
|
|
1042
1164
|
// instead of silent-stub (was: emitted "Would call tool X" + bound null,
|
|
@@ -3271,6 +3393,8 @@ const RULES = [
|
|
|
3271
3393
|
DISALLOWED_TOOL,
|
|
3272
3394
|
UNKNOWN_TOOL_ON_CONNECTOR,
|
|
3273
3395
|
UNVERIFIED_QUALIFIED_TOOL,
|
|
3396
|
+
UNKNOWN_CONNECTOR_ARG,
|
|
3397
|
+
MISSING_REQUIRED_CONNECTOR_ARG,
|
|
3274
3398
|
UNINITIALIZED_APPEND,
|
|
3275
3399
|
FOREACH_LOCAL_ACCUMULATOR_TARGET,
|
|
3276
3400
|
APPEND_TO_NON_LIST,
|
|
@@ -3544,6 +3668,49 @@ function collectMcpConnectorStaticToolsFromRegistry(registry) {
|
|
|
3544
3668
|
}
|
|
3545
3669
|
return out;
|
|
3546
3670
|
}
|
|
3671
|
+
/**
|
|
3672
|
+
* v0.23.0 — warm + collect per-connector tool schemas for connector-aware
|
|
3673
|
+
* input lint. For each wired MCP connector exposing `describeTools()`, calls it
|
|
3674
|
+
* (which ensure-warms a read-only `tools/list` when cold — protocol
|
|
3675
|
+
* introspection, NOT a tool dispatch, so no effect boundary is crossed) under a
|
|
3676
|
+
* short timeout. Best-effort + graceful: a connector with no `describeTools()`,
|
|
3677
|
+
* an unreachable upstream, or a timeout simply contributes no schema, and the
|
|
3678
|
+
* arg rules stay silent for it (no false positives). Async — only reachable via
|
|
3679
|
+
* the async `lint()` entry point.
|
|
3680
|
+
*/
|
|
3681
|
+
const CONNECTOR_SCHEMA_WARM_TIMEOUT_MS = 2000;
|
|
3682
|
+
async function collectMcpConnectorToolSchemasFromRegistry(registry) {
|
|
3683
|
+
const out = new Map();
|
|
3684
|
+
if (registry === undefined)
|
|
3685
|
+
return out;
|
|
3686
|
+
await Promise.all(registry.listMcpConnectors().map(async (e) => {
|
|
3687
|
+
const instance = e.instance;
|
|
3688
|
+
if (typeof instance.describeTools !== "function")
|
|
3689
|
+
return;
|
|
3690
|
+
try {
|
|
3691
|
+
const descriptors = await Promise.race([
|
|
3692
|
+
instance.describeTools(),
|
|
3693
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error("describeTools timeout")), CONNECTOR_SCHEMA_WARM_TIMEOUT_MS)),
|
|
3694
|
+
]);
|
|
3695
|
+
// Respect the operator's per-connector `allowed_tools` gate: don't
|
|
3696
|
+
// surface (or validate against) tools the operator gated off — a
|
|
3697
|
+
// disallowed `$ conn.tool` is the `disallowed-tool` rule's job, not
|
|
3698
|
+
// ours. undefined allowlist = allow-all.
|
|
3699
|
+
const allowed = e.allowedTools;
|
|
3700
|
+
const byTool = new Map();
|
|
3701
|
+
for (const d of descriptors) {
|
|
3702
|
+
if (allowed !== undefined && !allowed.includes(d.name))
|
|
3703
|
+
continue;
|
|
3704
|
+
byTool.set(d.name, d);
|
|
3705
|
+
}
|
|
3706
|
+
out.set(e.name, byTool);
|
|
3707
|
+
}
|
|
3708
|
+
catch {
|
|
3709
|
+
// Unreachable / timeout / no schema — degrade arg-agnostic for this connector.
|
|
3710
|
+
}
|
|
3711
|
+
}));
|
|
3712
|
+
return out;
|
|
3713
|
+
}
|
|
3547
3714
|
function buildFeatureSet(classes) {
|
|
3548
3715
|
const provided = new Set();
|
|
3549
3716
|
for (const Ctor of classes) {
|