skillscript-runtime 0.16.3 → 0.16.5
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/connectors/config.d.ts.map +1 -1
- package/dist/connectors/config.js +14 -0
- package/dist/connectors/config.js.map +1 -1
- package/dist/connectors/http-mcp.d.ts +63 -0
- package/dist/connectors/http-mcp.d.ts.map +1 -0
- package/dist/connectors/http-mcp.js +243 -0
- package/dist/connectors/http-mcp.js.map +1 -0
- package/dist/connectors/index.d.ts +2 -0
- package/dist/connectors/index.d.ts.map +1 -1
- package/dist/connectors/index.js +3 -0
- package/dist/connectors/index.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/lint.d.ts +16 -0
- package/dist/lint.d.ts.map +1 -1
- package/dist/lint.js +244 -0
- package/dist/lint.js.map +1 -1
- package/dist/testing/conformance.d.ts +55 -0
- package/dist/testing/conformance.d.ts.map +1 -1
- package/dist/testing/conformance.js +150 -0
- package/dist/testing/conformance.js.map +1 -1
- package/dist/testing/index.d.ts +2 -2
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/index.js +6 -1
- package/dist/testing/index.js.map +1 -1
- package/docs/adopter-playbook.md +21 -4
- package/docs/language-reference.md +159 -39
- package/examples/connectors/McpConnectorTemplate/README.md +7 -6
- package/examples/onboarding-scaffold/connectors.json +13 -1
- package/examples/skillscripts/hello-world.skill.provenance.json +1 -1
- package/examples/skillscripts/morning-brief.skill.md +2 -2
- package/package.json +1 -1
package/dist/lint.js
CHANGED
|
@@ -4,6 +4,9 @@ import { KNOWN_FILTERS } from "./filters.js";
|
|
|
4
4
|
// ─── lint() entry point ────────────────────────────────────────────────────
|
|
5
5
|
export async function lint(source, options) {
|
|
6
6
|
const parsed = parse(source);
|
|
7
|
+
const localModelInfo = options?.localModelAliases !== undefined
|
|
8
|
+
? { aliases: options.localModelAliases, modelsAvailable: options.localModelsAvailable ?? [] }
|
|
9
|
+
: await collectLocalModelInfoFromRegistry(options?.registry);
|
|
7
10
|
const ctx = {
|
|
8
11
|
parsed,
|
|
9
12
|
source,
|
|
@@ -16,6 +19,8 @@ export async function lint(source, options) {
|
|
|
16
19
|
connectorConfigErrors: options?.connectorConfigErrors ?? [],
|
|
17
20
|
mcpConnectorAllowedTools: options?.mcpConnectorAllowedTools ?? collectMcpConnectorAllowedToolsFromRegistry(options?.registry),
|
|
18
21
|
agentConnectorNames: options?.agentConnectorNames ?? collectAgentConnectorNamesFromRegistry(options?.registry),
|
|
22
|
+
localModelAliases: localModelInfo?.aliases,
|
|
23
|
+
localModelsAvailable: localModelInfo?.modelsAvailable,
|
|
19
24
|
mcpConnectorStaticTools: collectMcpConnectorStaticToolsFromRegistry(options?.registry),
|
|
20
25
|
};
|
|
21
26
|
const findings = [];
|
|
@@ -55,6 +60,8 @@ export function lintSync(source, options) {
|
|
|
55
60
|
connectorConfigErrors: options?.connectorConfigErrors ?? [],
|
|
56
61
|
mcpConnectorAllowedTools: options?.mcpConnectorAllowedTools ?? collectMcpConnectorAllowedToolsFromRegistry(options?.registry),
|
|
57
62
|
agentConnectorNames: options?.agentConnectorNames ?? collectAgentConnectorNamesFromRegistry(options?.registry),
|
|
63
|
+
localModelAliases: options?.localModelAliases ?? collectLocalModelAliasesFromRegistry(options?.registry),
|
|
64
|
+
localModelsAvailable: options?.localModelsAvailable,
|
|
58
65
|
mcpConnectorStaticTools: collectMcpConnectorStaticToolsFromRegistry(options?.registry),
|
|
59
66
|
};
|
|
60
67
|
const findings = [];
|
|
@@ -468,6 +475,203 @@ const UNKNOWN_CONNECTOR = {
|
|
|
468
475
|
return findings;
|
|
469
476
|
},
|
|
470
477
|
};
|
|
478
|
+
// v0.16.4 — `$ llm prompt="..." model="X"` where X matches neither any
|
|
479
|
+
// registered LocalModel alias name NOR any `models_available` entry from
|
|
480
|
+
// any registered LocalModel's `manifest()` payload. Tier-2 warning.
|
|
481
|
+
//
|
|
482
|
+
// Closes the documented-but-unenforced surface from v0.16.2 (the `model=`
|
|
483
|
+
// kwarg shipped working at runtime but a typo like `model="qwen2.5"` —
|
|
484
|
+
// intending the upstream Ollama tag `qwen2.5:7b` or the alias `qwen` —
|
|
485
|
+
// would silently fall through to the default model). With v0.16.3
|
|
486
|
+
// putting `manifest()` in `runtime_capabilities`, the lint now has
|
|
487
|
+
// substrate-aware source of truth: both alias names AND each registered
|
|
488
|
+
// LocalModel's underlying model surface participate in the typo-catch.
|
|
489
|
+
//
|
|
490
|
+
// Silent unless context carries the data — `lint()` without a `registry`
|
|
491
|
+
// or explicit `localModelAliases` cannot validate. Variable-substituted
|
|
492
|
+
// values (`model="${TAG}"`) are skipped — the literal isn't a model
|
|
493
|
+
// identifier at compile time.
|
|
494
|
+
const UNKNOWN_LLM_MODEL = {
|
|
495
|
+
id: "unknown-llm-model",
|
|
496
|
+
severity: "warning",
|
|
497
|
+
description: "A `$ llm` op carries `model=\"X\"` where X is neither a registered LocalModel alias nor a model in any registered LocalModel's `models_available` manifest field.",
|
|
498
|
+
remediation: "Check the model name against the runtime's registered LocalModels — `runtime_capabilities()` lists every alias plus each instance's `manifest.models_available`. Either fix the typo, register the model under a runtime alias (e.g., `registry.registerLocalModel(\"<alias>\", <instance>)`), or wrap the value in a substitution if it's resolved at runtime.",
|
|
499
|
+
check: (ctx) => {
|
|
500
|
+
if (ctx.localModelAliases === undefined)
|
|
501
|
+
return [];
|
|
502
|
+
const known = new Set(ctx.localModelAliases);
|
|
503
|
+
if (ctx.localModelsAvailable !== undefined) {
|
|
504
|
+
for (const m of ctx.localModelsAvailable)
|
|
505
|
+
known.add(m);
|
|
506
|
+
}
|
|
507
|
+
const findings = [];
|
|
508
|
+
const reported = new Set();
|
|
509
|
+
for (const [targetName, target] of ctx.parsed.targets) {
|
|
510
|
+
walkOps(target.ops, (op) => {
|
|
511
|
+
if (op.kind !== "$")
|
|
512
|
+
return;
|
|
513
|
+
// Bare-form `$ llm ...` puts `llm` as the first token of op.body
|
|
514
|
+
// with op.mcpConnector === undefined. Named-form `$ llm.run ...`
|
|
515
|
+
// would set op.mcpConnector === "llm" — match either.
|
|
516
|
+
const tokens = tokenizeKeywordArgs(op.body);
|
|
517
|
+
const head = tokens[0];
|
|
518
|
+
const isLlmDispatch = op.mcpConnector === "llm" || (head === "llm" && op.mcpConnector === undefined);
|
|
519
|
+
if (!isLlmDispatch)
|
|
520
|
+
return;
|
|
521
|
+
for (const tok of tokens) {
|
|
522
|
+
const eq = tok.indexOf("=");
|
|
523
|
+
if (eq === -1)
|
|
524
|
+
continue;
|
|
525
|
+
const key = tok.slice(0, eq).trim();
|
|
526
|
+
if (key !== "model")
|
|
527
|
+
continue;
|
|
528
|
+
let value = tok.slice(eq + 1).trim();
|
|
529
|
+
// Strip outer quotes if present.
|
|
530
|
+
if ((value.startsWith('"') && value.endsWith('"')) ||
|
|
531
|
+
(value.startsWith("'") && value.endsWith("'"))) {
|
|
532
|
+
value = value.slice(1, -1);
|
|
533
|
+
}
|
|
534
|
+
if (value === "")
|
|
535
|
+
continue;
|
|
536
|
+
// Substitutions — `${TAG}` / `$(TAG)` — are runtime-resolved.
|
|
537
|
+
if (value.includes("$(") || value.includes("${"))
|
|
538
|
+
continue;
|
|
539
|
+
if (known.has(value))
|
|
540
|
+
continue;
|
|
541
|
+
const dedupKey = `${targetName}:${value}`;
|
|
542
|
+
if (reported.has(dedupKey))
|
|
543
|
+
continue;
|
|
544
|
+
reported.add(dedupKey);
|
|
545
|
+
const knownDescr = known.size === 0
|
|
546
|
+
? "(no LocalModels registered)"
|
|
547
|
+
: [...known].sort().join(", ");
|
|
548
|
+
findings.push({
|
|
549
|
+
rule: "unknown-llm-model",
|
|
550
|
+
severity: "warning",
|
|
551
|
+
message: `\`$ llm model="${value}"\` in target '${targetName}': '${value}' matches no registered LocalModel alias and no \`models_available\` entry from any registered LocalModel. Known: ${knownDescr}.`,
|
|
552
|
+
block: targetName,
|
|
553
|
+
extras: { referenced_model: value },
|
|
554
|
+
});
|
|
555
|
+
}
|
|
556
|
+
});
|
|
557
|
+
}
|
|
558
|
+
return findings;
|
|
559
|
+
},
|
|
560
|
+
};
|
|
561
|
+
// v0.16.5 — `$ llm` op with a kwarg outside the canonical closed set.
|
|
562
|
+
// Catches authors leaking provider-API kwargs (e.g., `temperature=0.7`)
|
|
563
|
+
// which the LocalModelMcpConnector bridge silently drops — LocalModel.run()
|
|
564
|
+
// only consumes `{maxTokens, model}`. Tier-2 warning.
|
|
565
|
+
//
|
|
566
|
+
// Sibling to `unknown-llm-model` (v0.16.4) — same shape, different axis.
|
|
567
|
+
// The `model=` axis validates a value against registered substrate state;
|
|
568
|
+
// this rule validates a kwarg KEY against the documented closed surface.
|
|
569
|
+
//
|
|
570
|
+
// Per memory `9254a648`. Closed set lives in `LLM_KWARG_SURFACE`.
|
|
571
|
+
const LLM_KWARG_SURFACE = new Set([
|
|
572
|
+
"prompt",
|
|
573
|
+
"maxTokens",
|
|
574
|
+
"model",
|
|
575
|
+
"timeout",
|
|
576
|
+
"approved",
|
|
577
|
+
"fallback",
|
|
578
|
+
]);
|
|
579
|
+
const UNKNOWN_LLM_ARG = {
|
|
580
|
+
id: "unknown-llm-arg",
|
|
581
|
+
severity: "warning",
|
|
582
|
+
description: "A `$ llm` op carries a kwarg outside the canonical surface (`prompt`/`maxTokens`/`model`/`timeout`/`approved`/`fallback`). Provider-API kwargs (e.g., `temperature=`) silently dropped by the bridge.",
|
|
583
|
+
remediation: "Drop the kwarg, or substitute the canonical equivalent. The closed surface is documented under `$ llm` in the language reference. If your substrate accepts additional kwargs, configure them at the LocalModel adapter layer (e.g., construct an OpenAILocalModel with `defaultTemperature: 0.7`) — kwargs from the skill body must match the substrate-neutral contract.",
|
|
584
|
+
check: (ctx) => {
|
|
585
|
+
const findings = [];
|
|
586
|
+
const reported = new Set();
|
|
587
|
+
for (const [targetName, target] of ctx.parsed.targets) {
|
|
588
|
+
walkOps(target.ops, (op) => {
|
|
589
|
+
if (op.kind !== "$")
|
|
590
|
+
return;
|
|
591
|
+
const tokens = tokenizeKeywordArgs(op.body);
|
|
592
|
+
const head = tokens[0];
|
|
593
|
+
const isLlmDispatch = op.mcpConnector === "llm" || (head === "llm" && op.mcpConnector === undefined);
|
|
594
|
+
if (!isLlmDispatch)
|
|
595
|
+
return;
|
|
596
|
+
for (const tok of tokens) {
|
|
597
|
+
const eq = tok.indexOf("=");
|
|
598
|
+
if (eq === -1)
|
|
599
|
+
continue;
|
|
600
|
+
const key = tok.slice(0, eq).trim();
|
|
601
|
+
if (LLM_KWARG_SURFACE.has(key))
|
|
602
|
+
continue;
|
|
603
|
+
const dedupKey = `${targetName}:${key}`;
|
|
604
|
+
if (reported.has(dedupKey))
|
|
605
|
+
continue;
|
|
606
|
+
reported.add(dedupKey);
|
|
607
|
+
findings.push({
|
|
608
|
+
rule: "unknown-llm-arg",
|
|
609
|
+
severity: "warning",
|
|
610
|
+
message: `\`$ llm\` in target '${targetName}' carries unknown kwarg '${key}'. Canonical surface: ${[...LLM_KWARG_SURFACE].sort().join(", ")}.`,
|
|
611
|
+
block: targetName,
|
|
612
|
+
extras: { kwarg: key },
|
|
613
|
+
});
|
|
614
|
+
}
|
|
615
|
+
});
|
|
616
|
+
}
|
|
617
|
+
return findings;
|
|
618
|
+
},
|
|
619
|
+
};
|
|
620
|
+
// v0.16.5 — `$ data_read` op with a kwarg outside the canonical closed
|
|
621
|
+
// set. Replaces the deleted `unknown-retrieval-arg` rule which targeted
|
|
622
|
+
// the v0.7.0-removed `>` symbol op surface. Same shape as `unknown-llm-arg`.
|
|
623
|
+
// Per memory `9254a648`.
|
|
624
|
+
const DATA_READ_KWARG_SURFACE = new Set([
|
|
625
|
+
"mode",
|
|
626
|
+
"query",
|
|
627
|
+
"limit",
|
|
628
|
+
"connector",
|
|
629
|
+
"fallback",
|
|
630
|
+
"domain_tags",
|
|
631
|
+
"filters",
|
|
632
|
+
"min_confidence",
|
|
633
|
+
]);
|
|
634
|
+
const UNKNOWN_DATA_READ_ARG = {
|
|
635
|
+
id: "unknown-data-read-arg",
|
|
636
|
+
severity: "warning",
|
|
637
|
+
description: "A `$ data_read` op carries a kwarg outside the canonical surface (`mode`/`query`/`limit`/`connector`/`fallback`/`domain_tags`/`filters`/`min_confidence`).",
|
|
638
|
+
remediation: "Drop the kwarg, or substitute the canonical equivalent. The closed surface is documented under `$ data_read` in the language reference. Substrate-specific filters belong inside the `filters={...}` object literal, not as top-level kwargs.",
|
|
639
|
+
check: (ctx) => {
|
|
640
|
+
const findings = [];
|
|
641
|
+
const reported = new Set();
|
|
642
|
+
for (const [targetName, target] of ctx.parsed.targets) {
|
|
643
|
+
walkOps(target.ops, (op) => {
|
|
644
|
+
if (op.kind !== "$")
|
|
645
|
+
return;
|
|
646
|
+
const tokens = tokenizeKeywordArgs(op.body);
|
|
647
|
+
const head = tokens[0];
|
|
648
|
+
const isDataReadDispatch = op.mcpConnector === "data_read" || (head === "data_read" && op.mcpConnector === undefined);
|
|
649
|
+
if (!isDataReadDispatch)
|
|
650
|
+
return;
|
|
651
|
+
for (const tok of tokens) {
|
|
652
|
+
const eq = tok.indexOf("=");
|
|
653
|
+
if (eq === -1)
|
|
654
|
+
continue;
|
|
655
|
+
const key = tok.slice(0, eq).trim();
|
|
656
|
+
if (DATA_READ_KWARG_SURFACE.has(key))
|
|
657
|
+
continue;
|
|
658
|
+
const dedupKey = `${targetName}:${key}`;
|
|
659
|
+
if (reported.has(dedupKey))
|
|
660
|
+
continue;
|
|
661
|
+
reported.add(dedupKey);
|
|
662
|
+
findings.push({
|
|
663
|
+
rule: "unknown-data-read-arg",
|
|
664
|
+
severity: "warning",
|
|
665
|
+
message: `\`$ data_read\` in target '${targetName}' carries unknown kwarg '${key}'. Canonical surface: ${[...DATA_READ_KWARG_SURFACE].sort().join(", ")}.`,
|
|
666
|
+
block: targetName,
|
|
667
|
+
extras: { kwarg: key },
|
|
668
|
+
});
|
|
669
|
+
}
|
|
670
|
+
});
|
|
671
|
+
}
|
|
672
|
+
return findings;
|
|
673
|
+
},
|
|
674
|
+
};
|
|
471
675
|
// v0.4.1 — `$ name.tool` where `name` is configured with an
|
|
472
676
|
// `allowed_tools` list that doesn't include `tool`. Tier-1 lint error
|
|
473
677
|
// at compile time. Closes the "minion-safe by default" framing from
|
|
@@ -2362,6 +2566,9 @@ const RULES = [
|
|
|
2362
2566
|
TRANSCRIPT_FOOTGUN,
|
|
2363
2567
|
SET_JSON_LITERAL_ADVISORY,
|
|
2364
2568
|
SKILL_NAME_COLLISION,
|
|
2569
|
+
UNKNOWN_LLM_MODEL,
|
|
2570
|
+
UNKNOWN_LLM_ARG,
|
|
2571
|
+
UNKNOWN_DATA_READ_ARG,
|
|
2365
2572
|
// v0.9.2 — promoted from tier-3 info to tier-1 error (P0.9 in c9c667d2)
|
|
2366
2573
|
NO_DEFAULT_TARGET,
|
|
2367
2574
|
COLON_KWARG_SYNTAX,
|
|
@@ -2533,6 +2740,43 @@ function collectAgentConnectorNamesFromRegistry(registry) {
|
|
|
2533
2740
|
// registry.ts) — empty array means "no real AgentConnector wired."
|
|
2534
2741
|
return registry.listAgentConnectors().map((e) => e.name);
|
|
2535
2742
|
}
|
|
2743
|
+
function collectLocalModelAliasesFromRegistry(registry) {
|
|
2744
|
+
if (registry === undefined)
|
|
2745
|
+
return undefined;
|
|
2746
|
+
return registry.listLocalModels().map((e) => e.name);
|
|
2747
|
+
}
|
|
2748
|
+
// v0.16.4 — async sibling to collectLocalModelAliasesFromRegistry: probes each
|
|
2749
|
+
// registered LocalModel's `manifest()` to harvest `models_available`. Used by
|
|
2750
|
+
// the async `lint()` entry point so `unknown-llm-model` can validate against
|
|
2751
|
+
// both registry aliases AND the underlying substrate's model surface (sharpened
|
|
2752
|
+
// by Perry's `bfd776a9` audit insight — with manifest in capabilities, the lint
|
|
2753
|
+
// has substrate-aware source of truth, not just alias names).
|
|
2754
|
+
//
|
|
2755
|
+
// Per-instance try/catch — a throwing `manifest()` (e.g., substrate unreachable
|
|
2756
|
+
// at lint time) silently degrades to alias-only validation rather than failing
|
|
2757
|
+
// the lint. Adopters whose LocalModel `manifest()` omits `models_available`
|
|
2758
|
+
// (the contract permits absence) also gracefully fall through.
|
|
2759
|
+
async function collectLocalModelInfoFromRegistry(registry) {
|
|
2760
|
+
if (registry === undefined)
|
|
2761
|
+
return undefined;
|
|
2762
|
+
const entries = registry.listLocalModels();
|
|
2763
|
+
const aliases = entries.map((e) => e.name);
|
|
2764
|
+
const available = new Set();
|
|
2765
|
+
for (const entry of entries) {
|
|
2766
|
+
try {
|
|
2767
|
+
const m = await entry.instance.manifest();
|
|
2768
|
+
const inner = m.manifest;
|
|
2769
|
+
if (Array.isArray(inner.models_available)) {
|
|
2770
|
+
for (const tag of inner.models_available)
|
|
2771
|
+
available.add(tag);
|
|
2772
|
+
}
|
|
2773
|
+
}
|
|
2774
|
+
catch {
|
|
2775
|
+
// Probe failure — degrade to alias-only for this instance.
|
|
2776
|
+
}
|
|
2777
|
+
}
|
|
2778
|
+
return { aliases, modelsAvailable: [...available] };
|
|
2779
|
+
}
|
|
2536
2780
|
function collectMcpConnectorAllowedToolsFromRegistry(registry) {
|
|
2537
2781
|
const out = new Map();
|
|
2538
2782
|
if (registry === undefined)
|