skillscript-runtime 0.15.7 → 0.16.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/README.md +23 -24
- package/dist/compile.js +25 -71
- package/dist/compile.js.map +1 -1
- package/dist/connectors/mcp-remote.d.ts.map +1 -1
- package/dist/connectors/mcp-remote.js +7 -1
- package/dist/connectors/mcp-remote.js.map +1 -1
- package/dist/errors.d.ts +13 -7
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +20 -13
- package/dist/errors.js.map +1 -1
- package/dist/help-content.d.ts.map +1 -1
- package/dist/help-content.js +78 -103
- package/dist/help-content.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/lint.d.ts.map +1 -1
- package/dist/lint.js +167 -228
- package/dist/lint.js.map +1 -1
- package/dist/mutation-gate.d.ts +7 -13
- package/dist/mutation-gate.d.ts.map +1 -1
- package/dist/mutation-gate.js +6 -11
- package/dist/mutation-gate.js.map +1 -1
- package/dist/parser.d.ts +25 -74
- package/dist/parser.d.ts.map +1 -1
- package/dist/parser.js +80 -272
- package/dist/parser.js.map +1 -1
- package/dist/runtime.d.ts +1 -10
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +76 -188
- package/dist/runtime.js.map +1 -1
- package/dist/safe-path.d.ts +20 -0
- package/dist/safe-path.d.ts.map +1 -0
- package/dist/safe-path.js +49 -0
- package/dist/safe-path.js.map +1 -0
- package/dist/skill-manager.js +1 -1
- package/dist/skill-manager.js.map +1 -1
- package/dist/trace.d.ts.map +1 -1
- package/dist/trace.js +26 -9
- package/dist/trace.js.map +1 -1
- package/docs/language-reference.md +3 -24
- package/examples/README.md +1 -2
- package/examples/connectors/McpConnectorTemplate/McpConnectorTemplate.ts +4 -5
- package/examples/connectors/SkillStoreTemplate/SkillStoreTemplate.ts +9 -9
- package/examples/custom-bootstrap.example.ts +4 -4
- package/examples/onboarding-scaffold/bootstrap.ts +7 -7
- package/examples/onboarding-scaffold/file-data-store.ts +3 -5
- package/examples/onboarding-scaffold/openai-local-model.ts +7 -8
- package/examples/onboarding-scaffold/tmux-shell-agent-connector.ts +4 -4
- package/examples/skillscripts/hello-world.skill.provenance.json +1 -1
- package/package.json +1 -1
- package/examples/skillscripts/cut-release-tag.skill.md +0 -40
package/dist/parser.js
CHANGED
|
@@ -2,22 +2,21 @@
|
|
|
2
2
|
// no resolution against external state. Semantic analysis (variable resolution,
|
|
3
3
|
// data-skill inlining, topo-sort) lives in compile.ts.
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* Runtime-intrinsic function-call names. Closed set of ops the language
|
|
6
|
+
* implements directly (no MCP dispatch). Function-call grammar:
|
|
7
7
|
* `verb(kwarg=value, ...) [-> BINDING]`.
|
|
8
8
|
*
|
|
9
9
|
* Anything else with function-call shape is rejected by parser with a
|
|
10
10
|
* remediation pointing at `$ tool args -> R` for MCP dispatch.
|
|
11
11
|
*/
|
|
12
12
|
export const RUNTIME_INTRINSIC_FN_NAMES = [
|
|
13
|
-
"emit", //
|
|
14
|
-
"
|
|
15
|
-
"
|
|
16
|
-
"
|
|
17
|
-
"shell", // → @ (local subprocess)
|
|
13
|
+
"emit", // output to skill consumer
|
|
14
|
+
"inline", // compile-time skill composition
|
|
15
|
+
"execute_skill", // runtime skill invocation (dispatches via $ execute_skill)
|
|
16
|
+
"shell", // local subprocess
|
|
18
17
|
"file_read", // read file contents at runtime
|
|
19
18
|
"file_write", // write file contents at runtime
|
|
20
|
-
"notify", //
|
|
19
|
+
"notify", // mid-skill synchronous agent alert via AgentConnector(s)
|
|
21
20
|
];
|
|
22
21
|
/**
|
|
23
22
|
* Case-insensitive accept, canonical-form return. The `allowed` list defines
|
|
@@ -37,8 +36,6 @@ function normalizeEnumValue(raw, allowed) {
|
|
|
37
36
|
const REQUIRES_LINE = /^(user-var|system-var):([A-Za-z0-9_-]+)\s*(?:→|->)\s*([A-Za-z_][\w-]*)\s*(?:\(\s*fallback\s*:\s*(.+?)\s*\)\s*)?$/;
|
|
38
37
|
/** Capability token: `connector_type.feature_flag`. Matches one space-separated token of a capability `# Requires:` line. */
|
|
39
38
|
const CAPABILITY_TOKEN = /^[a-z_][a-z0-9_]*\.[a-z_][a-z0-9_]*$/;
|
|
40
|
-
/** `&` op: `& skill-name [arg=value ...] [-> VARNAME]`. Skill names follow the same charset as filesystem-safe identifiers (alphanumeric, hyphen, underscore). */
|
|
41
|
-
const AMPERSAND_OP_REGEX = /^&\s+([A-Za-z0-9][\w-]*)\s*(.*?)(?:\s*->\s*([A-Za-z_]\w*))?(?:\s+\(fallback\s*:\s*(.+?)\))?\s*$/s;
|
|
42
39
|
// v0.7.2 — `(.*)` widened to `([\s\S]*)` so multi-line triple-quote
|
|
43
40
|
// (`"""..."""`) values fold into a single $set value capture. Without the
|
|
44
41
|
// dotall-equivalent, the `.` excludes newlines and the regex stops at the
|
|
@@ -75,24 +72,9 @@ function extractApprovedKwarg(body) {
|
|
|
75
72
|
return undefined;
|
|
76
73
|
return m[1] ?? m[2] ?? "";
|
|
77
74
|
}
|
|
78
|
-
//
|
|
79
|
-
//
|
|
75
|
+
// P0.5 detect missing-space dispatch: `$<word> args` where `<word>` isn't
|
|
76
|
+
// `set`/`append` (the only legitimate no-space `$`-prefix verbs).
|
|
80
77
|
const NO_SPACE_DISPATCH_RE = /^\$(?!set\b|append\b)\w+\s/;
|
|
81
|
-
/**
|
|
82
|
-
* `>` and `~` ops accept optional trailing `(fallback: <value>)` per
|
|
83
|
-
* language reference §9 (Error Handling, Layer 3). Fires when the op
|
|
84
|
-
* throws or returns empty — runtime binds the fallback value to the
|
|
85
|
-
* output var and continues without surfacing the error.
|
|
86
|
-
*
|
|
87
|
-
* Value is permissive (matching `# Requires:` cascade convention): bare
|
|
88
|
-
* identifiers (`ip-based`), quoted strings (`"weather unavailable"`),
|
|
89
|
-
* array literals (`[]`, `[a, b]`), and arbitrary text between the colon
|
|
90
|
-
* and the closing paren are all accepted. Parser stores the raw form;
|
|
91
|
-
* runtime applies `coerceLiteralValue` for `>` (binds array on `[...]`)
|
|
92
|
-
* and the raw string for `~` (model response shape).
|
|
93
|
-
*/
|
|
94
|
-
const RETRIEVAL_OP_REGEX = /^>\s+(.+?)\s+->\s+([A-Za-z_]\w*)(?:\s+\(fallback\s*:\s*(.+?)\))?\s*$/s;
|
|
95
|
-
const LOCAL_MODEL_OP_REGEX = /^~\s+(.+?)\s+->\s+([A-Za-z_]\w*)(?:\s+\(fallback\s*:\s*(.+?)\))?\s*$/s;
|
|
96
78
|
const MCP_CONNECTOR_PREFIX = /^([a-z_][a-z0-9_-]*)\.(?=[A-Za-z_])([\s\S]*)$/;
|
|
97
79
|
// Narrow v1 condition grammar.
|
|
98
80
|
// v0.3.4: filter chain support — each `(REF)(|filter)?` became `(REF)(|filter)*`
|
|
@@ -122,26 +104,44 @@ const COND_EQ_REF = new RegExp(`^\\s*${REF_PATTERN}\\s*(?:==|!=)\\s*${REF_PATTER
|
|
|
122
104
|
const COND_CMP = new RegExp(`^\\s*${REF_PATTERN}\\s*(?:<=|>=|<|>)\\s*"[^"]*"\\s*$`);
|
|
123
105
|
const COND_CMP_REF = new RegExp(`^\\s*${REF_PATTERN}\\s*(?:<=|>=|<|>)\\s*${REF_PATTERN}\\s*$`);
|
|
124
106
|
const COND_IN = new RegExp(`^\\s*${REF_PATTERN}\\s+(?:not\\s+)?in\\s+${REF_PATTERN_NO_FILTER}\\s*$`);
|
|
107
|
+
// Parser resource caps. The "parser never throws on bad input" contract
|
|
108
|
+
// requires bounded recursion + length-capped regex application — without
|
|
109
|
+
// these guards, adversarial input crashes the host (stack overflow via
|
|
110
|
+
// `a and b and c and ...` deeply-chained AND; CPU exhaustion via
|
|
111
|
+
// REF_PATTERN's nested `*` quantifiers backtracking on near-valid input).
|
|
112
|
+
// Length cap is upstream of regex application (smaller risk than regex
|
|
113
|
+
// refactor, which is deferred). Depth cap is downstream of the length
|
|
114
|
+
// check; both must hold.
|
|
115
|
+
const MAX_CONDITION_LENGTH = 4096;
|
|
116
|
+
const MAX_CONDITION_DEPTH = 64;
|
|
125
117
|
function validateCondition(cond) {
|
|
126
|
-
|
|
118
|
+
const trimmed = cond.trim();
|
|
119
|
+
if (trimmed.length > MAX_CONDITION_LENGTH)
|
|
120
|
+
return false;
|
|
121
|
+
return validateCompoundCondition(trimmed, 0);
|
|
127
122
|
}
|
|
128
|
-
//
|
|
123
|
+
// Recursive structural decomposition matching runtime evalCondition.
|
|
129
124
|
// Order: strip parens → split on outermost OR → AND → not prefix → simple shape.
|
|
130
|
-
|
|
125
|
+
// `depth` is incremented per recursive call and capped at MAX_CONDITION_DEPTH —
|
|
126
|
+
// adversarial input like `a and b and c and ... (1000 ANDs)` would otherwise
|
|
127
|
+
// blow the JS stack.
|
|
128
|
+
function validateCompoundCondition(cond, depth) {
|
|
129
|
+
if (depth > MAX_CONDITION_DEPTH)
|
|
130
|
+
return false;
|
|
131
131
|
const stripped = stripOuterCondParens(cond);
|
|
132
132
|
const orIdx = findOuterCondToken(stripped, "or");
|
|
133
133
|
if (orIdx >= 0) {
|
|
134
|
-
return validateCompoundCondition(stripped.slice(0, orIdx).trim())
|
|
135
|
-
&& validateCompoundCondition(stripped.slice(orIdx + 4).trim());
|
|
134
|
+
return validateCompoundCondition(stripped.slice(0, orIdx).trim(), depth + 1)
|
|
135
|
+
&& validateCompoundCondition(stripped.slice(orIdx + 4).trim(), depth + 1);
|
|
136
136
|
}
|
|
137
137
|
const andIdx = findOuterCondToken(stripped, "and");
|
|
138
138
|
if (andIdx >= 0) {
|
|
139
|
-
return validateCompoundCondition(stripped.slice(0, andIdx).trim())
|
|
140
|
-
&& validateCompoundCondition(stripped.slice(andIdx + 5).trim());
|
|
139
|
+
return validateCompoundCondition(stripped.slice(0, andIdx).trim(), depth + 1)
|
|
140
|
+
&& validateCompoundCondition(stripped.slice(andIdx + 5).trim(), depth + 1);
|
|
141
141
|
}
|
|
142
142
|
const lead = stripped.trimStart();
|
|
143
143
|
if (lead.startsWith("not "))
|
|
144
|
-
return validateCompoundCondition(lead.slice(4));
|
|
144
|
+
return validateCompoundCondition(lead.slice(4), depth + 1);
|
|
145
145
|
return COND_TRUTHY.test(stripped) || COND_EQ.test(stripped) || COND_EQ_REF.test(stripped) ||
|
|
146
146
|
COND_CMP.test(stripped) || COND_CMP_REF.test(stripped) || COND_IN.test(stripped);
|
|
147
147
|
}
|
|
@@ -686,109 +686,6 @@ function splitMcpConnectorPrefix(body) {
|
|
|
686
686
|
return { connector: undefined, rest: body };
|
|
687
687
|
return { connector: m[1], rest: m[2] };
|
|
688
688
|
}
|
|
689
|
-
function parseRetrievalArgs(argsStr, targetName) {
|
|
690
|
-
const errors = [];
|
|
691
|
-
const map = {};
|
|
692
|
-
const tokens = tokenizeKeywordArgs(argsStr);
|
|
693
|
-
for (const tok of tokens) {
|
|
694
|
-
const eq = tok.indexOf("=");
|
|
695
|
-
if (eq === -1) {
|
|
696
|
-
errors.push(`Malformed \`>\` arg '${tok}' in target '${targetName}' — expected key=value`);
|
|
697
|
-
continue;
|
|
698
|
-
}
|
|
699
|
-
const key = tok.slice(0, eq).trim();
|
|
700
|
-
const rawValue = tok.slice(eq + 1);
|
|
701
|
-
map[key] = processSetValue(rawValue);
|
|
702
|
-
}
|
|
703
|
-
for (const required of ["mode", "query", "limit"]) {
|
|
704
|
-
if (!(required in map) || map[required] === "") {
|
|
705
|
-
errors.push(`\`>\` op in target '${targetName}' missing required param '${required}'`);
|
|
706
|
-
}
|
|
707
|
-
}
|
|
708
|
-
// Defer integer validation when the value contains a `$(VAR)` ref — runtime
|
|
709
|
-
// substitutes + parses after the ref resolves. Literal numerics still
|
|
710
|
-
// validate at parse time.
|
|
711
|
-
let limit = 0;
|
|
712
|
-
const rawLimit = map["limit"] ?? "";
|
|
713
|
-
if (/\$[(\{]/.test(rawLimit)) {
|
|
714
|
-
limit = rawLimit;
|
|
715
|
-
}
|
|
716
|
-
else {
|
|
717
|
-
const n = parseInt(rawLimit, 10);
|
|
718
|
-
if (!Number.isFinite(n) || n <= 0) {
|
|
719
|
-
errors.push(`\`>\` op in target '${targetName}': 'limit' must be a positive integer or a \`$(VAR)\` ref (got '${rawLimit}')`);
|
|
720
|
-
}
|
|
721
|
-
else {
|
|
722
|
-
limit = n;
|
|
723
|
-
}
|
|
724
|
-
}
|
|
725
|
-
const extra = {};
|
|
726
|
-
for (const [k, v] of Object.entries(map)) {
|
|
727
|
-
if (k === "mode" || k === "query" || k === "limit" || k === "connector")
|
|
728
|
-
continue;
|
|
729
|
-
extra[k] = v;
|
|
730
|
-
}
|
|
731
|
-
return {
|
|
732
|
-
params: {
|
|
733
|
-
mode: map["mode"] ?? "",
|
|
734
|
-
query: map["query"] ?? "",
|
|
735
|
-
limit,
|
|
736
|
-
connector: map["connector"] ?? "primary",
|
|
737
|
-
extra,
|
|
738
|
-
},
|
|
739
|
-
errors,
|
|
740
|
-
};
|
|
741
|
-
}
|
|
742
|
-
function parseLocalModelArgs(argsStr, targetName) {
|
|
743
|
-
const errors = [];
|
|
744
|
-
const map = {};
|
|
745
|
-
const tokens = tokenizeKeywordArgs(argsStr);
|
|
746
|
-
for (const tok of tokens) {
|
|
747
|
-
const eq = tok.indexOf("=");
|
|
748
|
-
if (eq === -1) {
|
|
749
|
-
errors.push(`Malformed \`~\` arg '${tok}' in target '${targetName}' — expected key=value`);
|
|
750
|
-
continue;
|
|
751
|
-
}
|
|
752
|
-
const key = tok.slice(0, eq).trim();
|
|
753
|
-
const rawValue = tok.slice(eq + 1);
|
|
754
|
-
map[key] = processSetValue(rawValue);
|
|
755
|
-
}
|
|
756
|
-
const recognized = new Set(["prompt", "model", "maxTokens", "timeoutSeconds"]);
|
|
757
|
-
for (const key of Object.keys(map)) {
|
|
758
|
-
if (!recognized.has(key)) {
|
|
759
|
-
errors.push(`\`~\` op in target '${targetName}': unrecognized param '${key}' — strict grammar allows prompt/model/maxTokens/timeoutSeconds only. Interpolate context into the prompt string via $(...) instead.`);
|
|
760
|
-
}
|
|
761
|
-
}
|
|
762
|
-
if (!("prompt" in map) || map["prompt"] === "") {
|
|
763
|
-
errors.push(`\`~\` op in target '${targetName}' missing required param 'prompt'`);
|
|
764
|
-
}
|
|
765
|
-
// Defer integer validation when the value contains a `$(VAR)` ref.
|
|
766
|
-
function deferInt(key) {
|
|
767
|
-
if (!(key in map))
|
|
768
|
-
return undefined;
|
|
769
|
-
const raw = map[key];
|
|
770
|
-
if (/\$[(\{]/.test(raw))
|
|
771
|
-
return raw;
|
|
772
|
-
const n = parseInt(raw, 10);
|
|
773
|
-
if (!Number.isFinite(n) || n <= 0) {
|
|
774
|
-
errors.push(`\`~\` op in target '${targetName}': '${key}' must be a positive integer or a \`$(VAR)\` ref (got '${raw}')`);
|
|
775
|
-
return undefined;
|
|
776
|
-
}
|
|
777
|
-
return n;
|
|
778
|
-
}
|
|
779
|
-
const maxTokens = deferInt("maxTokens");
|
|
780
|
-
const timeoutSeconds = deferInt("timeoutSeconds");
|
|
781
|
-
const params = {
|
|
782
|
-
prompt: map["prompt"] ?? "",
|
|
783
|
-
};
|
|
784
|
-
if ("model" in map && map["model"] !== "")
|
|
785
|
-
params.model = map["model"];
|
|
786
|
-
if (maxTokens !== undefined)
|
|
787
|
-
params.maxTokens = maxTokens;
|
|
788
|
-
if (timeoutSeconds !== undefined)
|
|
789
|
-
params.timeoutSeconds = timeoutSeconds;
|
|
790
|
-
return { params, errors };
|
|
791
|
-
}
|
|
792
689
|
function popToDepth(stack, targetDepth) {
|
|
793
690
|
while (stack.length > 0 && stack[stack.length - 1].depth > targetDepth) {
|
|
794
691
|
stack.pop();
|
|
@@ -1349,82 +1246,18 @@ export function parse(source) {
|
|
|
1349
1246
|
continue;
|
|
1350
1247
|
}
|
|
1351
1248
|
if (stripped0.startsWith("> ")) {
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
result.parseErrors.push(`Malformed \`>\` op in target '${currentTarget.name}' — expected \`> key=value ... -> VARNAME [(fallback: "value")]\``);
|
|
1355
|
-
continue;
|
|
1356
|
-
}
|
|
1357
|
-
const [, argsStr, outputVar, fallback] = match;
|
|
1358
|
-
const parsed = parseRetrievalArgs(argsStr, currentTarget.name);
|
|
1359
|
-
if (parsed.errors.length > 0) {
|
|
1360
|
-
for (const e of parsed.errors)
|
|
1361
|
-
result.parseErrors.push(e);
|
|
1362
|
-
continue;
|
|
1363
|
-
}
|
|
1364
|
-
if (fallback !== undefined)
|
|
1365
|
-
parsed.params.fallback = processSetValue(fallback);
|
|
1366
|
-
opBucket.push({
|
|
1367
|
-
kind: ">",
|
|
1368
|
-
body: stripped0,
|
|
1369
|
-
outputVar: outputVar,
|
|
1370
|
-
retrievalParams: parsed.params,
|
|
1371
|
-
});
|
|
1249
|
+
result.parseErrors.push(`Legacy \`>\` retrieval op in target '${currentTarget.name}' is no longer supported. ` +
|
|
1250
|
+
`Use \`$ data_read mode="fts|semantic|rerank" query="..." limit=N -> R\` (or qualify the connector: \`$ <connector>.data_read ...\`).`);
|
|
1372
1251
|
continue;
|
|
1373
1252
|
}
|
|
1374
1253
|
if (stripped0.startsWith("~ ")) {
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
result.parseErrors.push(`Malformed \`~\` op in target '${currentTarget.name}' — expected \`~ key=value ... -> VARNAME [(fallback: "value")]\``);
|
|
1378
|
-
continue;
|
|
1379
|
-
}
|
|
1380
|
-
const [, argsStr, outputVar, fallback] = match;
|
|
1381
|
-
const parsed = parseLocalModelArgs(argsStr, currentTarget.name);
|
|
1382
|
-
if (parsed.errors.length > 0) {
|
|
1383
|
-
for (const e of parsed.errors)
|
|
1384
|
-
result.parseErrors.push(e);
|
|
1385
|
-
continue;
|
|
1386
|
-
}
|
|
1387
|
-
if (fallback !== undefined)
|
|
1388
|
-
parsed.params.fallback = processSetValue(fallback);
|
|
1389
|
-
opBucket.push({
|
|
1390
|
-
kind: "~",
|
|
1391
|
-
body: stripped0,
|
|
1392
|
-
outputVar: outputVar,
|
|
1393
|
-
localModelParams: parsed.params,
|
|
1394
|
-
});
|
|
1254
|
+
result.parseErrors.push(`Legacy \`~\` LocalModel op in target '${currentTarget.name}' is no longer supported. ` +
|
|
1255
|
+
`Use \`$ llm prompt="..." [maxTokens=N] [model="..."] -> R\` (op-level \`timeout=N\` kwarg and trailing \`(fallback: "value")\` are honored).`);
|
|
1395
1256
|
continue;
|
|
1396
1257
|
}
|
|
1397
1258
|
if (stripped0.startsWith("& ")) {
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
result.parseErrors.push(`Malformed \`&\` op in target '${currentTarget.name}' — expected \`& skill-name [key=value ...] [-> VARNAME]\``);
|
|
1401
|
-
continue;
|
|
1402
|
-
}
|
|
1403
|
-
const [, skillName, argsStr, outputVar, ampFallback] = match;
|
|
1404
|
-
const args = {};
|
|
1405
|
-
const tokens = tokenizeKeywordArgs(argsStr ?? "");
|
|
1406
|
-
let argError = false;
|
|
1407
|
-
for (const tok of tokens) {
|
|
1408
|
-
const eq = tok.indexOf("=");
|
|
1409
|
-
if (eq === -1) {
|
|
1410
|
-
result.parseErrors.push(`Malformed \`&\` arg '${tok}' in target '${currentTarget.name}' — expected key=value`);
|
|
1411
|
-
argError = true;
|
|
1412
|
-
continue;
|
|
1413
|
-
}
|
|
1414
|
-
args[tok.slice(0, eq).trim()] = processSetValue(tok.slice(eq + 1));
|
|
1415
|
-
}
|
|
1416
|
-
if (argError)
|
|
1417
|
-
continue;
|
|
1418
|
-
const ampOp = {
|
|
1419
|
-
kind: "&",
|
|
1420
|
-
body: stripped0,
|
|
1421
|
-
ampParams: { skillName: skillName, args },
|
|
1422
|
-
};
|
|
1423
|
-
if (outputVar !== undefined)
|
|
1424
|
-
ampOp.outputVar = outputVar;
|
|
1425
|
-
if (ampFallback !== undefined)
|
|
1426
|
-
ampOp.fallback = processSetValue(ampFallback);
|
|
1427
|
-
opBucket.push(ampOp);
|
|
1259
|
+
result.parseErrors.push(`Legacy \`&\` inline op in target '${currentTarget.name}' is no longer supported. ` +
|
|
1260
|
+
`Use \`inline(skill="...")\` for compile-time data-skill inlines, or \`execute_skill(name="...", ...) -> R\` for runtime composition.`);
|
|
1428
1261
|
continue;
|
|
1429
1262
|
}
|
|
1430
1263
|
if (stripped0.startsWith("foreach ")) {
|
|
@@ -1515,34 +1348,21 @@ export function parse(source) {
|
|
|
1515
1348
|
}
|
|
1516
1349
|
const text = kwArgs["text"] ?? "";
|
|
1517
1350
|
opBucket.push({
|
|
1518
|
-
kind: "
|
|
1351
|
+
kind: "emit",
|
|
1519
1352
|
body: text,
|
|
1520
1353
|
...(approved !== undefined ? { approved } : {}),
|
|
1521
|
-
sourceForm: "function-call",
|
|
1522
|
-
});
|
|
1523
|
-
continue;
|
|
1524
|
-
}
|
|
1525
|
-
if (fnName === "ask") {
|
|
1526
|
-
const prompt = kwArgs["prompt"] ?? "";
|
|
1527
|
-
opBucket.push({
|
|
1528
|
-
kind: "??",
|
|
1529
|
-
body: prompt,
|
|
1530
|
-
...(outputVar !== undefined ? { outputVar } : {}),
|
|
1531
|
-
...(approved !== undefined ? { approved } : {}),
|
|
1532
|
-
sourceForm: "function-call",
|
|
1533
1354
|
});
|
|
1534
1355
|
continue;
|
|
1535
1356
|
}
|
|
1536
1357
|
if (fnName === "inline") {
|
|
1537
1358
|
const skill = kwArgs["skill"] ?? "";
|
|
1538
1359
|
opBucket.push({
|
|
1539
|
-
kind: "
|
|
1360
|
+
kind: "inline",
|
|
1540
1361
|
body: stripped0,
|
|
1541
1362
|
ampParams: { skillName: skill, args: {} },
|
|
1542
1363
|
...(outputVar !== undefined ? { outputVar } : {}),
|
|
1543
1364
|
...(fallback !== undefined ? { fallback } : {}),
|
|
1544
1365
|
...(approved !== undefined ? { approved } : {}),
|
|
1545
|
-
sourceForm: "function-call",
|
|
1546
1366
|
});
|
|
1547
1367
|
continue;
|
|
1548
1368
|
}
|
|
@@ -1568,7 +1388,6 @@ export function parse(source) {
|
|
|
1568
1388
|
...(outputVar !== undefined ? { outputVar } : {}),
|
|
1569
1389
|
...(fallback !== undefined ? { fallback } : {}),
|
|
1570
1390
|
...(approved !== undefined ? { approved } : {}),
|
|
1571
|
-
sourceForm: "function-call",
|
|
1572
1391
|
});
|
|
1573
1392
|
continue;
|
|
1574
1393
|
}
|
|
@@ -1576,13 +1395,12 @@ export function parse(source) {
|
|
|
1576
1395
|
const command = kwArgs["command"] ?? "";
|
|
1577
1396
|
const unsafe = kwArgs["unsafe"] === "true";
|
|
1578
1397
|
opBucket.push({
|
|
1579
|
-
kind: "
|
|
1398
|
+
kind: "shell",
|
|
1580
1399
|
body: command,
|
|
1581
1400
|
...(unsafe ? { policy: "unsafe" } : {}),
|
|
1582
1401
|
...(outputVar !== undefined ? { outputVar } : {}),
|
|
1583
1402
|
...(fallback !== undefined ? { fallback } : {}),
|
|
1584
1403
|
...(approved !== undefined ? { approved } : {}),
|
|
1585
|
-
sourceForm: "function-call",
|
|
1586
1404
|
});
|
|
1587
1405
|
continue;
|
|
1588
1406
|
}
|
|
@@ -1594,7 +1412,6 @@ export function parse(source) {
|
|
|
1594
1412
|
fileParams: { path },
|
|
1595
1413
|
...(outputVar !== undefined ? { outputVar } : {}),
|
|
1596
1414
|
...(fallback !== undefined ? { fallback } : {}),
|
|
1597
|
-
sourceForm: "function-call",
|
|
1598
1415
|
});
|
|
1599
1416
|
continue;
|
|
1600
1417
|
}
|
|
@@ -1607,7 +1424,6 @@ export function parse(source) {
|
|
|
1607
1424
|
fileParams: { path, content },
|
|
1608
1425
|
...(outputVar !== undefined ? { outputVar } : {}),
|
|
1609
1426
|
...(approved !== undefined ? { approved } : {}),
|
|
1610
|
-
sourceForm: "function-call",
|
|
1611
1427
|
});
|
|
1612
1428
|
continue;
|
|
1613
1429
|
}
|
|
@@ -1655,7 +1471,6 @@ export function parse(source) {
|
|
|
1655
1471
|
...(outputVar !== undefined ? { outputVar } : {}),
|
|
1656
1472
|
...(fallback !== undefined ? { fallback } : {}),
|
|
1657
1473
|
...(approved !== undefined ? { approved } : {}),
|
|
1658
|
-
sourceForm: "function-call",
|
|
1659
1474
|
});
|
|
1660
1475
|
continue;
|
|
1661
1476
|
}
|
|
@@ -1670,19 +1485,11 @@ export function parse(source) {
|
|
|
1670
1485
|
let kind = null;
|
|
1671
1486
|
let body = "";
|
|
1672
1487
|
let mcpConnectorForOp = undefined;
|
|
1673
|
-
|
|
1674
|
-
let atOutputVar = undefined;
|
|
1675
|
-
let atFallback = undefined;
|
|
1676
|
-
// Check `??` before `?`, `$set` before `$`.
|
|
1488
|
+
// Check `??`/`$set` before bare `?`/`$`.
|
|
1677
1489
|
if (stripped.startsWith("?? ") || stripped === "??") {
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
opBucket.push({ kind: "??", body: m[1].trim(), outputVar: m[2] });
|
|
1682
|
-
}
|
|
1683
|
-
else {
|
|
1684
|
-
opBucket.push({ kind: "??", body: tail });
|
|
1685
|
-
}
|
|
1490
|
+
result.parseErrors.push(`Legacy \`??\` ask op in target '${currentTarget.name}' is no longer supported. ` +
|
|
1491
|
+
`\`ask\` was removed in v0.16.0 — it conflated user-surfacing (which the runtime can't guarantee a channel for) with mutation-gating (already covered by \`approved="reason"\` per-op kwarg + \`# Autonomous: true\` skill flag). ` +
|
|
1492
|
+
`For input, use \`emit(text="...")\` and have the caller handle the round-trip. For mutation authorization, use \`approved=\` or \`# Autonomous:\`.`);
|
|
1686
1493
|
continue;
|
|
1687
1494
|
}
|
|
1688
1495
|
else if (stripped.startsWith("$set ") || stripped === "$set") {
|
|
@@ -1696,6 +1503,30 @@ export function parse(source) {
|
|
|
1696
1503
|
setValue: processSetValue(rawValue),
|
|
1697
1504
|
});
|
|
1698
1505
|
}
|
|
1506
|
+
else {
|
|
1507
|
+
// Malformed `$set` no longer silently drops. Diagnose the
|
|
1508
|
+
// specific shape so the author sees the cause, not a downstream
|
|
1509
|
+
// `undeclared-var` blaming the symptom. Sibling to v0.9.2 P0.8
|
|
1510
|
+
// (`$append VAR =`) + qwen-test P0.5 (`$<word>`) treatments.
|
|
1511
|
+
const tail = stripped.slice(4).trim();
|
|
1512
|
+
let detail;
|
|
1513
|
+
if (tail === "") {
|
|
1514
|
+
detail = "missing variable name and `=` assignment. Canonical: `$set VAR = value`.";
|
|
1515
|
+
}
|
|
1516
|
+
else if (!tail.includes("=")) {
|
|
1517
|
+
detail = `'${tail.slice(0, 40)}' has no \`=\` assignment. Canonical: \`$set VAR = value\` (the \`=\` is required).`;
|
|
1518
|
+
}
|
|
1519
|
+
else if (/^\d/.test(tail)) {
|
|
1520
|
+
detail = `'${tail.slice(0, 40)}' starts with a digit. Variable names must start with a letter or underscore.`;
|
|
1521
|
+
}
|
|
1522
|
+
else if (/^[A-Za-z_]\w*\.[A-Za-z_]/.test(tail)) {
|
|
1523
|
+
detail = `'${tail.slice(0, 40)}' uses dotted target. \`$set\` binds top-level variables only; dotted writes aren't supported. Bind a parent var first via \`$set PARENT = {...}\` then mutate via \`$ json_parse\` + structured ops.`;
|
|
1524
|
+
}
|
|
1525
|
+
else {
|
|
1526
|
+
detail = `'${tail.slice(0, 40)}' doesn't match the \`VAR = value\` shape. Variable names: \`[A-Za-z_]\\w*\`. Canonical: \`$set VAR = value\`.`;
|
|
1527
|
+
}
|
|
1528
|
+
result.parseErrors.push(`Malformed \`$set\` op in target '${currentTarget.name}': ${detail}`);
|
|
1529
|
+
}
|
|
1699
1530
|
continue;
|
|
1700
1531
|
}
|
|
1701
1532
|
else if (stripped.startsWith("$append ") || stripped === "$append") {
|
|
@@ -1771,45 +1602,22 @@ export function parse(source) {
|
|
|
1771
1602
|
body = stripped.slice(2).trim();
|
|
1772
1603
|
}
|
|
1773
1604
|
else if (stripped.startsWith("@ ") || stripped === "@") {
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
// v0.2.4 Bug F: the trailing `(fallback: ...)` clause is now supported
|
|
1778
|
-
// for parity with $/~/> ops — cold authors reach for op-level fallback
|
|
1779
|
-
// as a defensive-coding posture and previously hit silent
|
|
1780
|
-
// outputVar-not-bound failures.
|
|
1781
|
-
const outMatch = /^(.+?)\s+->\s+([A-Za-z_]\w*)(?:\s+\(fallback\s*:\s*(.+?)\))?\s*$/.exec(tail);
|
|
1782
|
-
if (outMatch !== null) {
|
|
1783
|
-
atOutputVar = outMatch[2];
|
|
1784
|
-
if (outMatch[3] !== undefined)
|
|
1785
|
-
atFallback = processSetValue(outMatch[3]);
|
|
1786
|
-
tail = outMatch[1].trim();
|
|
1787
|
-
}
|
|
1788
|
-
// `@ unsafe <command>` — `unsafe` as literal first token signals
|
|
1789
|
-
// opt-in full-shell exec (vs default structured-spawn sandbox).
|
|
1790
|
-
const unsafeMatch = /^unsafe(?:\s+(.*))?$/.exec(tail);
|
|
1791
|
-
if (unsafeMatch !== null) {
|
|
1792
|
-
atPolicy = "unsafe";
|
|
1793
|
-
body = (unsafeMatch[1] ?? "").trim();
|
|
1794
|
-
}
|
|
1795
|
-
else {
|
|
1796
|
-
body = tail;
|
|
1797
|
-
}
|
|
1605
|
+
result.parseErrors.push(`Legacy \`@\` shell op in target '${currentTarget.name}' is no longer supported. ` +
|
|
1606
|
+
`Use \`shell(command="...") [-> R] [(fallback: "...")]\` (add \`unsafe=true\` kwarg for full-shell exec; runtime opt-in still required).`);
|
|
1607
|
+
continue;
|
|
1798
1608
|
}
|
|
1799
1609
|
else if (stripped.startsWith("! ") || stripped === "!") {
|
|
1800
|
-
|
|
1801
|
-
|
|
1610
|
+
result.parseErrors.push(`Legacy \`!\` emit op in target '${currentTarget.name}' is no longer supported. ` +
|
|
1611
|
+
`Use \`emit(text="...")\`.`);
|
|
1612
|
+
continue;
|
|
1802
1613
|
}
|
|
1803
1614
|
if (kind !== null) {
|
|
1804
|
-
//
|
|
1615
|
+
// N1: extract `approved=` for the no-binding `$` op path.
|
|
1805
1616
|
const approvedKwarg = kind === "$" ? extractApprovedKwarg(body) : undefined;
|
|
1806
1617
|
opBucket.push({
|
|
1807
1618
|
kind,
|
|
1808
1619
|
body,
|
|
1809
1620
|
...(mcpConnectorForOp !== undefined ? { mcpConnector: mcpConnectorForOp } : {}),
|
|
1810
|
-
...(atPolicy !== undefined ? { policy: atPolicy } : {}),
|
|
1811
|
-
...(atOutputVar !== undefined ? { outputVar: atOutputVar } : {}),
|
|
1812
|
-
...(atFallback !== undefined ? { fallback: atFallback } : {}),
|
|
1813
1621
|
...(approvedKwarg !== undefined ? { approved: approvedKwarg } : {}),
|
|
1814
1622
|
});
|
|
1815
1623
|
continue;
|
|
@@ -1826,7 +1634,7 @@ export function parse(source) {
|
|
|
1826
1634
|
result.parseErrors.push(`Unknown block-introducer '${keyword}:' in target '${currentTarget.name}'. ` +
|
|
1827
1635
|
`Skillscript recognizes \`if COND:\`, \`elif COND:\`, \`else:\`, and \`foreach IT in $(LIST):\` ` +
|
|
1828
1636
|
`at body scope (target-level \`else:\` is the error handler). ` +
|
|
1829
|
-
`Composition is via
|
|
1637
|
+
`Composition is via \`inline(skill="...")\` (data-skill inline) or \`execute_skill(name="...")\` (in-skill invocation), not block syntax.`);
|
|
1830
1638
|
scopeStack.push({
|
|
1831
1639
|
kind: "unknown-block",
|
|
1832
1640
|
target: currentTarget,
|