skillscript-runtime 0.8.0 → 0.9.6

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.
Files changed (90) hide show
  1. package/dist/approval.d.ts +99 -0
  2. package/dist/approval.d.ts.map +1 -0
  3. package/dist/approval.js +224 -0
  4. package/dist/approval.js.map +1 -0
  5. package/dist/bootstrap.d.ts +21 -1
  6. package/dist/bootstrap.d.ts.map +1 -1
  7. package/dist/bootstrap.js +19 -5
  8. package/dist/bootstrap.js.map +1 -1
  9. package/dist/compile.d.ts.map +1 -1
  10. package/dist/compile.js +9 -1
  11. package/dist/compile.js.map +1 -1
  12. package/dist/composition.d.ts +29 -0
  13. package/dist/composition.d.ts.map +1 -1
  14. package/dist/composition.js +62 -1
  15. package/dist/composition.js.map +1 -1
  16. package/dist/connectors/agent-noop.d.ts +12 -7
  17. package/dist/connectors/agent-noop.d.ts.map +1 -1
  18. package/dist/connectors/agent-noop.js +31 -12
  19. package/dist/connectors/agent-noop.js.map +1 -1
  20. package/dist/connectors/agent.d.ts +150 -28
  21. package/dist/connectors/agent.d.ts.map +1 -1
  22. package/dist/connectors/agent.js +13 -10
  23. package/dist/connectors/agent.js.map +1 -1
  24. package/dist/connectors/local-model-mcp.d.ts +8 -0
  25. package/dist/connectors/local-model-mcp.d.ts.map +1 -1
  26. package/dist/connectors/local-model-mcp.js +10 -0
  27. package/dist/connectors/local-model-mcp.js.map +1 -1
  28. package/dist/connectors/memory-store-mcp.d.ts +9 -0
  29. package/dist/connectors/memory-store-mcp.d.ts.map +1 -1
  30. package/dist/connectors/memory-store-mcp.js +11 -0
  31. package/dist/connectors/memory-store-mcp.js.map +1 -1
  32. package/dist/connectors/registry.d.ts +15 -1
  33. package/dist/connectors/registry.d.ts.map +1 -1
  34. package/dist/connectors/registry.js +23 -1
  35. package/dist/connectors/registry.js.map +1 -1
  36. package/dist/connectors/skill-store.d.ts.map +1 -1
  37. package/dist/connectors/skill-store.js +43 -13
  38. package/dist/connectors/skill-store.js.map +1 -1
  39. package/dist/connectors/types.d.ts +12 -0
  40. package/dist/connectors/types.d.ts.map +1 -1
  41. package/dist/connectors/types.js.map +1 -1
  42. package/dist/dashboard/spa/app.js +37 -9
  43. package/dist/dispatch-validate.d.ts +64 -0
  44. package/dist/dispatch-validate.d.ts.map +1 -0
  45. package/dist/dispatch-validate.js +81 -0
  46. package/dist/dispatch-validate.js.map +1 -0
  47. package/dist/errors.d.ts +11 -0
  48. package/dist/errors.d.ts.map +1 -1
  49. package/dist/errors.js +16 -0
  50. package/dist/errors.js.map +1 -1
  51. package/dist/help-content.d.ts.map +1 -1
  52. package/dist/help-content.js +92 -12
  53. package/dist/help-content.js.map +1 -1
  54. package/dist/index.d.ts +3 -0
  55. package/dist/index.d.ts.map +1 -1
  56. package/dist/index.js +3 -0
  57. package/dist/index.js.map +1 -1
  58. package/dist/lint.d.ts +11 -0
  59. package/dist/lint.d.ts.map +1 -1
  60. package/dist/lint.js +560 -104
  61. package/dist/lint.js.map +1 -1
  62. package/dist/mcp-server.d.ts.map +1 -1
  63. package/dist/mcp-server.js +53 -11
  64. package/dist/mcp-server.js.map +1 -1
  65. package/dist/parser.d.ts +27 -6
  66. package/dist/parser.d.ts.map +1 -1
  67. package/dist/parser.js +115 -11
  68. package/dist/parser.js.map +1 -1
  69. package/dist/runtime.d.ts +51 -2
  70. package/dist/runtime.d.ts.map +1 -1
  71. package/dist/runtime.js +126 -35
  72. package/dist/runtime.js.map +1 -1
  73. package/dist/scheduler.d.ts +20 -1
  74. package/dist/scheduler.d.ts.map +1 -1
  75. package/dist/scheduler.js +40 -3
  76. package/dist/scheduler.js.map +1 -1
  77. package/dist/testing/conformance.d.ts.map +1 -1
  78. package/dist/testing/conformance.js +16 -6
  79. package/dist/testing/conformance.js.map +1 -1
  80. package/examples/classify-support-ticket.skill.md +1 -1
  81. package/examples/cut-release-tag.skill.md +1 -1
  82. package/examples/doc-qa-with-citations.skill.md +1 -1
  83. package/examples/feedback-sentiment-scan.skill.md +1 -1
  84. package/examples/hello.skill.provenance.json +1 -1
  85. package/examples/morning-brief.skill.md +1 -1
  86. package/examples/queue-length-monitor.skill.md +1 -1
  87. package/examples/service-health-watch.skill.md +1 -1
  88. package/examples/youtrack-morning-sweep.skill.md +1 -1
  89. package/package.json +1 -1
  90. package/scaffold/examples/hello.skill.md +3 -2
package/dist/runtime.d.ts CHANGED
@@ -62,8 +62,11 @@ export interface ExecuteContext {
62
62
  /** Persistence backend for trace records. Wires alongside `trace`. */
63
63
  traceStore?: TraceStore;
64
64
  /**
65
- * Trigger context for trace identity + sampling. Scheduler passes the
66
- * fired trigger's metadata; direct callers can synthesize.
65
+ * Trigger context for trace identity + sampling + DeliveryMeta.origin.
66
+ * Scheduler passes the fired trigger's metadata; direct callers can
67
+ * synthesize. v0.9.6 — `source` values are constrained to the Q8 enum
68
+ * (cron / session / webhook / agent / cli / dashboard / inline) when
69
+ * the value flows into DeliveryMeta.origin.trigger_kind.
67
70
  */
68
71
  triggerCtx?: {
69
72
  source: string;
@@ -71,6 +74,23 @@ export interface ExecuteContext {
71
74
  fired_at_ms: number;
72
75
  trigger_id?: string;
73
76
  };
77
+ /**
78
+ * v0.9.6 — root entry-point skill name for composition chains. When
79
+ * skill A inlines B via `&`, A's execution sets this; B's nested
80
+ * execute() inherits it; B's deliver()-time meta gets
81
+ * `entry_skill_name = A`. Undefined for top-level execution (the
82
+ * emitter IS the entry skill).
83
+ */
84
+ entrySkillName?: string;
85
+ /**
86
+ * v0.9.6 — current skill's name + frontmatter eventType, stashed by
87
+ * `execute()` at the top of the per-skill run so deep-stack op
88
+ * handlers (notify(), etc.) can build DeliveryMeta without threading
89
+ * `parsed` through every call hop. Internal-use; do not set from
90
+ * outside execute().
91
+ */
92
+ _currentSkillName?: string;
93
+ _currentSkillEventType?: string | null;
74
94
  /** Skill identity for trace records. Optional — falls back to parsed.name + version inference. */
75
95
  skillVersion?: string;
76
96
  /**
@@ -100,11 +120,29 @@ export interface ExecutionError {
100
120
  remediation?: string;
101
121
  innerCause?: string;
102
122
  }
123
+ /**
124
+ * v0.9.2 — fallback-fire record per P1.4. When an op's `(fallback: ...)`
125
+ * trailer absorbs a dispatch failure, the runtime appends one record so
126
+ * callers can distinguish "real success" from "fallback substituted."
127
+ * Previously the caller saw `errors: []` either way and couldn't tell.
128
+ */
129
+ export interface FallbackRecord {
130
+ target: string;
131
+ opKind: string;
132
+ value: unknown;
133
+ reason: string;
134
+ }
103
135
  export interface ExecuteResult {
104
136
  finalVars: Record<string, unknown>;
105
137
  emissions: string[];
106
138
  outputs: Record<string, unknown>;
107
139
  errors: ExecutionError[];
140
+ /**
141
+ * v0.9.2 — fallback events. Populated when an op's `(fallback: ...)`
142
+ * trailer caught a dispatch failure. Empty array when no fallbacks
143
+ * fired. Inspect `length > 0` to detect partial-success runs.
144
+ */
145
+ fallbacks: FallbackRecord[];
108
146
  targetOrder: string[];
109
147
  /**
110
148
  * Delivery receipts from `AgentConnector.deliver` calls fired after the
@@ -121,6 +159,17 @@ export interface AgentDeliveryReceiptRecord {
121
159
  agent_id: string;
122
160
  output_kind: "agent" | "template";
123
161
  receipt: DeliveryReceipt;
162
+ /**
163
+ * v0.9.2 — true when no real AgentConnector was wired and the
164
+ * NoOpAgentConnector fallback "handled" the dispatch (i.e. accepted
165
+ * the payload without delivering it anywhere). Cold authors writing
166
+ * `# Output: agent: oncall` skills against a runtime without an
167
+ * AgentConnector get a clear signal that the delivery didn't happen.
168
+ * Per P1.1 finding in `dec3ca8a`.
169
+ */
170
+ delivery_skipped?: boolean;
171
+ /** Human-readable reason when `delivery_skipped: true`. */
172
+ reason?: string;
124
173
  }
125
174
  /**
126
175
  * Execute a parsed skill against the live variable state. Walks targets in
@@ -1 +1 @@
1
- {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"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"}
package/dist/runtime.js CHANGED
@@ -1,11 +1,52 @@
1
+ import { randomUUID } from "node:crypto";
1
2
  import { tokenizeKeywordArgs } from "./parser.js";
2
3
  import { applyFilter, parseFilterChain } from "./filters.js";
3
4
  import { dispatchExecuteSkillIntercept } from "./composition.js";
5
+ import { validateQualifiedDispatch } from "./dispatch-validate.js";
4
6
  import { spawn } from "node:child_process";
5
7
  import { readFile as fsReadFile, writeFile as fsWriteFile, mkdir as fsMkdir } from "node:fs/promises";
6
8
  import { dirname as pathDirname } from "node:path";
7
9
  import { OpError, ConnectorNotFoundError, OpTimeoutError, InteractiveOpInAutonomousModeError, UnsafeShellDisabledError, UnresolvedVariableError, TypeMismatchError, MissingSkillReferenceError, } from "./errors.js";
8
10
  import { TraceBuilder, shouldTraceFire } from "./trace.js";
11
+ /**
12
+ * v0.9.6 audit Q8 — build the `DeliveryMeta` envelope for an `AgentConnector.deliver()`
13
+ * call. Runtime is the sole producer of meta; adopters consume but never construct.
14
+ *
15
+ * - `dispatch_id` is one UUID per call to this helper. Lifecycle hook callers and
16
+ * notify() callers each call it once per emit; multi-connector broadcast (one
17
+ * notify(), N wired connectors) shares the same id across all N deliver() calls
18
+ * because we hold the same `meta` reference across the loop.
19
+ * - `sent_at` is the runtime emit-clock (NOT the substrate's delivered_at).
20
+ * - `origin.trigger_kind` maps from `ctx.triggerCtx.source` per Q8 enum.
21
+ * `inline` is the fallback when no triggerCtx is set.
22
+ * - `origin.entry_skill_name` propagates from `ctx.entrySkillName` (set when
23
+ * composition runs an inner skill).
24
+ * - `origin.caller_agent_id` propagates from `ctx.agentId` when the chain was
25
+ * started by an agent (cron/cli/dashboard/inline triggers leave it undefined).
26
+ * - `event_type` precedence: opOverrides.event_type ?? parsed.eventType ?? undefined.
27
+ * `notify(event_type=...)` kwarg wins; `# Event-type:` frontmatter is the fallback.
28
+ * - `correlation_id` only set when explicitly passed via opOverrides (notify()
29
+ * kwarg). Lifecycle hooks always emit with correlation_id undefined per Q8.
30
+ */
31
+ function buildLifecycleMeta(skillName, frontmatterEventType, ctx, opOverrides) {
32
+ const triggerKind = (ctx.triggerCtx?.source ?? "inline");
33
+ const eventType = opOverrides?.event_type ?? frontmatterEventType ?? undefined;
34
+ const name = skillName ?? "(unnamed)";
35
+ return {
36
+ dispatch_id: randomUUID(),
37
+ sent_at: Date.now(),
38
+ origin: {
39
+ skill_name: name,
40
+ ...(ctx.entrySkillName !== undefined && ctx.entrySkillName !== name
41
+ ? { entry_skill_name: ctx.entrySkillName }
42
+ : {}),
43
+ trigger_kind: triggerKind,
44
+ ...(ctx.agentId !== undefined ? { caller_agent_id: ctx.agentId } : {}),
45
+ },
46
+ ...(eventType !== undefined ? { event_type: eventType } : {}),
47
+ ...(opOverrides?.correlation_id !== undefined ? { correlation_id: opOverrides.correlation_id } : {}),
48
+ };
49
+ }
9
50
  /**
10
51
  * Execute a parsed skill against the live variable state. Walks targets in
11
52
  * the provided order. Each target's ops run sequentially; on failure the
@@ -28,7 +69,9 @@ export async function execute(parsed, initialVars, order, ctx) {
28
69
  vars.set("NOW", new Date(nowMs).toISOString());
29
70
  vars.set("USER", ctx.agentId ?? "unknown");
30
71
  vars.set("SESSION_CONTEXT", "");
31
- vars.set("TRIGGER_TYPE", "manual");
72
+ // v0.9.6 — "manual" enum value dropped per audit Q12; "inline" is the
73
+ // fallback for runtime-direct callers without explicit trigger context.
74
+ vars.set("TRIGGER_TYPE", "inline");
32
75
  vars.set("TRIGGER_PAYLOAD", "");
33
76
  vars.set("EVENT.fired_at", nowMs);
34
77
  vars.set("EVENT.fired_at_unix", nowSec);
@@ -44,14 +87,21 @@ export async function execute(parsed, initialVars, order, ctx) {
44
87
  }
45
88
  const emissions = [];
46
89
  const errors = [];
90
+ const fallbacks = [];
47
91
  let lastBoundVar = null;
48
92
  const absoluteTimeoutMs = ctx.absoluteTimeoutMs ?? DEFAULT_RUNTIME_ABSOLUTE_TIMEOUT_MS;
49
93
  // Trace recording (per ERD §8). Build when shouldTraceFire returns true;
50
94
  // skip entirely when off (the NFR-11 floor — errors still surface via
51
95
  // `result.errors[]`).
52
- const triggerCtx = ctx.triggerCtx ?? { source: "manual", name: "", fired_at_ms: nowMs };
96
+ // v0.9.6 "manual" enum value dropped per audit Q12; "inline" is the
97
+ // fallback for callers without explicit trigger context.
98
+ const triggerCtx = ctx.triggerCtx ?? { source: "inline", name: "", fired_at_ms: nowMs };
53
99
  const triggerId = triggerCtx.trigger_id ?? `${triggerCtx.source}:${triggerCtx.name}`;
54
100
  const skillName = parsed.name ?? "(anonymous)";
101
+ // v0.9.6 — stash skill identity onto ctx so deep-stack op handlers
102
+ // (notify()) can read for DeliveryMeta without threading `parsed`
103
+ // through every call hop. Internal convention; do not set from outside.
104
+ ctx = { ...ctx, _currentSkillName: skillName, _currentSkillEventType: parsed.eventType };
55
105
  const traceBuilder = shouldTraceFire(ctx.trace, triggerId, skillName)
56
106
  ? new TraceBuilder(skillName, ctx.skillVersion ?? "unknown", triggerCtx, { agent_id: ctx.agentId })
57
107
  : null;
@@ -62,7 +112,7 @@ export async function execute(parsed, initialVars, order, ctx) {
62
112
  let targetLastBound = null;
63
113
  let targetLastValue = undefined;
64
114
  try {
65
- const r = await execOps(target.ops, vars, emissions, ctx, targetName, parsed.timeout, absoluteTimeoutMs, traceBuilder);
115
+ const r = await execOps(target.ops, vars, emissions, fallbacks, ctx, targetName, parsed.timeout, absoluteTimeoutMs, traceBuilder);
66
116
  targetLastBound = r.lastBoundVar;
67
117
  targetLastValue = r.lastBoundVar !== null ? vars.get(r.lastBoundVar) : r.lastValue;
68
118
  }
@@ -70,7 +120,7 @@ export async function execute(parsed, initialVars, order, ctx) {
70
120
  errors.push(buildExecutionError(err, targetName));
71
121
  if (target.elseBlock !== undefined) {
72
122
  try {
73
- const r = await execOps(target.elseBlock, vars, emissions, ctx, targetName, parsed.timeout, absoluteTimeoutMs, traceBuilder);
123
+ const r = await execOps(target.elseBlock, vars, emissions, fallbacks, ctx, targetName, parsed.timeout, absoluteTimeoutMs, traceBuilder);
74
124
  targetLastBound = r.lastBoundVar;
75
125
  targetLastValue = r.lastBoundVar !== null ? vars.get(r.lastBoundVar) : r.lastValue;
76
126
  }
@@ -153,28 +203,31 @@ export async function execute(parsed, initialVars, order, ctx) {
153
203
  const key = `${decl.kind}:${decl.target}`;
154
204
  const body = String(outputs[key] ?? emissions.join("\n"));
155
205
  const agent = ctx.registry.getAgentConnector();
156
- // Common provenance + augmenting-context fields populated alongside
157
- // every delivery (v0.2.6). source_skill identifies the authoring
158
- // skill; triggered_by lets the receiver disambiguate cron vs manual
159
- // vs session-boundary fires; delivery_context + templates surface the
160
- // optional `# Delivery-context:` and `# Templates:` frontmatter.
161
- const common = {
162
- ...(parsed.name !== null ? { source_skill: parsed.name } : {}),
163
- ...(ctx.triggerCtx !== undefined ? {
164
- triggered_by: {
165
- source: ctx.triggerCtx.source,
166
- name: ctx.triggerCtx.name,
167
- fired_at_ms: ctx.triggerCtx.fired_at_ms,
168
- },
169
- } : {}),
170
- ...(parsed.deliveryContext !== null ? { delivery_context: parsed.deliveryContext } : {}),
171
- ...(parsed.templates.length > 0 ? { templates: parsed.templates } : {}),
172
- };
206
+ // v0.9.6 audit Q8 build DeliveryMeta per the locked v1.0 shape.
207
+ // Runtime auto-fills dispatch_id, sent_at, origin; threads optional
208
+ // event_type from frontmatter (`# Event-type:` fallback per Q9).
209
+ // Lifecycle hook deliveries carry no correlation_id (Q8 design
210
+ // boundary that's notify()-kwarg territory).
211
+ const meta = buildLifecycleMeta(parsed.name, parsed.eventType, ctx);
212
+ // v0.9.2 P1.1 flag the no-op dispatch case. When no real
213
+ // AgentConnector is wired, the NoOp fallback accepts the deliver
214
+ // call without doing anything; cold authors get a clear signal.
215
+ const hasRealConnector = ctx.registry.hasAgentConnector();
173
216
  try {
174
217
  const receipt = decl.kind === "agent"
175
- ? await agent.deliver(decl.target, { kind: "augment", content: body, ...common })
176
- : await agent.deliver(decl.target, { kind: "template", prompt: body, ...common });
177
- agentDeliveryReceipts.push({ agent_id: decl.target, output_kind: decl.kind, receipt });
218
+ ? await agent.deliver(decl.target, { kind: "augment", content: body, meta })
219
+ : await agent.deliver(decl.target, { kind: "template", prompt: body, meta });
220
+ const record = { agent_id: decl.target, output_kind: decl.kind, receipt };
221
+ if (!hasRealConnector) {
222
+ record.delivery_skipped = true;
223
+ record.reason = `No AgentConnector wired for runtime; NoOpAgentConnector accepted but didn't deliver. Wire an AgentConnector via registry.registerAgentConnector('primary', <YourImpl>) to enable real delivery.`;
224
+ }
225
+ else if (receipt.delivery_skipped === true) {
226
+ // v0.9.6 Q7 — connector signaled accept-but-not-pushed (offline,
227
+ // rate-limit drop, etc.). Honor the contract-level signal.
228
+ record.delivery_skipped = true;
229
+ }
230
+ agentDeliveryReceipts.push(record);
178
231
  }
179
232
  catch (err) {
180
233
  // Delivery failure is non-fatal — record alongside other errors so
@@ -203,15 +256,16 @@ export async function execute(parsed, initialVars, order, ctx) {
203
256
  emissions,
204
257
  outputs,
205
258
  errors,
259
+ fallbacks,
206
260
  targetOrder: order,
207
261
  agentDeliveryReceipts,
208
262
  };
209
263
  }
210
- async function execOps(ops, vars, emissions, ctx, targetName, skillTimeoutSec, absoluteTimeoutMs, traceBuilder) {
264
+ async function execOps(ops, vars, emissions, fallbacks, ctx, targetName, skillTimeoutSec, absoluteTimeoutMs, traceBuilder) {
211
265
  let lastBoundVar = null;
212
266
  let lastValue = undefined;
213
267
  for (const op of ops) {
214
- const r = await execOp(op, vars, emissions, ctx, targetName, skillTimeoutSec, absoluteTimeoutMs, traceBuilder);
268
+ const r = await execOp(op, vars, emissions, fallbacks, ctx, targetName, skillTimeoutSec, absoluteTimeoutMs, traceBuilder);
215
269
  if (r.lastBoundVar !== null) {
216
270
  lastBoundVar = r.lastBoundVar;
217
271
  lastValue = r.lastValue;
@@ -222,11 +276,11 @@ async function execOps(ops, vars, emissions, ctx, targetName, skillTimeoutSec, a
222
276
  }
223
277
  return { lastBoundVar, lastValue };
224
278
  }
225
- async function execOp(op, vars, emissions, ctx, targetName, skillTimeoutSec, absoluteTimeoutMs, traceBuilder) {
279
+ async function execOp(op, vars, emissions, fallbacks, ctx, targetName, skillTimeoutSec, absoluteTimeoutMs, traceBuilder) {
226
280
  const startMs = traceBuilder !== null ? Date.now() : 0;
227
281
  let errored = false;
228
282
  try {
229
- return await execOpInner(op, vars, emissions, ctx, targetName, skillTimeoutSec, absoluteTimeoutMs, traceBuilder);
283
+ return await execOpInner(op, vars, emissions, fallbacks, ctx, targetName, skillTimeoutSec, absoluteTimeoutMs, traceBuilder);
230
284
  }
231
285
  catch (err) {
232
286
  errored = true;
@@ -261,7 +315,7 @@ function extractOpConnector(op) {
261
315
  default: return undefined;
262
316
  }
263
317
  }
264
- async function execOpInner(op, vars, emissions, ctx, targetName, skillTimeoutSec, absoluteTimeoutMs, traceBuilder) {
318
+ async function execOpInner(op, vars, emissions, fallbacks, ctx, targetName, skillTimeoutSec, absoluteTimeoutMs, traceBuilder) {
265
319
  switch (op.kind) {
266
320
  case "$set": {
267
321
  // v0.5.0 item 3 — `$set X = "...$(REF)..."` now resolves $(REF) at
@@ -429,6 +483,14 @@ async function execOpInner(op, vars, emissions, ctx, targetName, skillTimeoutSec
429
483
  vars.set(flatKey, fallbackValue);
430
484
  if (op.outputVar !== undefined)
431
485
  vars.set(op.outputVar, fallbackValue);
486
+ // v0.9.2 — P1.4 record fallback firing so callers can distinguish
487
+ // "read succeeded" from "fallback substituted."
488
+ fallbacks.push({
489
+ target: targetName,
490
+ opKind: "file_read",
491
+ value: fallbackValue,
492
+ reason: `file_read failed: ${err.message}`,
493
+ });
432
494
  return { lastBoundVar: op.outputVar ?? flatKey, lastValue: fallbackValue };
433
495
  }
434
496
  throw makeOpError("file_read", `\`file_read(path="${path}")\` in target '${targetName}' failed: ${err instanceof Error ? err.message : String(err)}`);
@@ -456,6 +518,9 @@ async function execOpInner(op, vars, emissions, ctx, targetName, skillTimeoutSec
456
518
  catch (err) {
457
519
  throw makeOpError("file_write", `\`file_write(path="${path}")\` in target '${targetName}' failed: ${err instanceof Error ? err.message : String(err)}`);
458
520
  }
521
+ // v0.9.2 — P2.5 transcript line on successful write so cold authors
522
+ // can confirm side effects landed without reading the file back.
523
+ emissions.push(`[file_write] wrote ${Buffer.byteLength(content, "utf8")} bytes to ${path}`);
459
524
  return { lastBoundVar: null, lastValue: undefined };
460
525
  }
461
526
  case "notify": {
@@ -486,6 +551,15 @@ async function execOpInner(op, vars, emissions, ctx, targetName, skillTimeoutSec
486
551
  // connector failures are recorded in the ACK but don't propagate.
487
552
  const allConnectors = ctx.registry.listAgentConnectors();
488
553
  const dispatched = [];
554
+ // v0.9.6 audit Q8 — ONE notify() op invocation = ONE dispatch_id across
555
+ // every wired connector that gets the deliver() call. Same payload + meta
556
+ // for the whole fanout; receivers dedupe substrate retries by dispatch_id.
557
+ // event_type / correlation_id come from the notify() kwargs (per-emit
558
+ // override of any frontmatter fallback).
559
+ const notifyMeta = buildLifecycleMeta(ctx._currentSkillName ?? null, ctx._currentSkillEventType ?? null, ctx, {
560
+ event_type: op.notifyParams?.event_type,
561
+ correlation_id: op.notifyParams?.correlation_id,
562
+ });
489
563
  for (const entry of allConnectors) {
490
564
  if (restrictConnectors !== undefined && !restrictConnectors.includes(entry.name))
491
565
  continue;
@@ -504,6 +578,7 @@ async function execOpInner(op, vars, emissions, ctx, targetName, skillTimeoutSec
504
578
  await entry.instance.deliver(agent, {
505
579
  kind: "augment",
506
580
  content: message,
581
+ meta: notifyMeta,
507
582
  });
508
583
  dispatched.push({ connector: entry.name, ok: true });
509
584
  }
@@ -606,10 +681,19 @@ async function execOpInner(op, vars, emissions, ctx, targetName, skillTimeoutSec
606
681
  // an explicit connector name is set (op.mcpConnector !== undefined)
607
682
  // — the implicit "primary" path is for embedder-wired connectors
608
683
  // that don't go through connectors.json.
684
+ //
685
+ // v0.9.1 — extended to call the shared `validateQualifiedDispatch`
686
+ // helper so runtime and lint use the SAME source of truth. Catches
687
+ // unknown-tool-on-connector at runtime when lint was skipped.
609
688
  if (op.mcpConnector !== undefined && ctx.registry.hasMcpConnector(connectorName)) {
610
- const allowed = ctx.registry.getMcpConnectorAllowedTools(connectorName);
611
- if (allowed !== undefined && !allowed.includes(toolName)) {
612
- throw makeOpError("$", `\`$ ${connectorName}.${toolName}\` is not in the allowlist for connector '${connectorName}'. ${allowed.length === 0 ? "Allowlist is empty (no tools permitted)." : `Allowed: ${allowed.join(", ")}.`} (Defense-in-depth: lint should have caught this earlier.)`);
689
+ const diagnostics = validateQualifiedDispatch({
690
+ toolName,
691
+ qualifiedConnector: connectorName,
692
+ registry: ctx.registry,
693
+ });
694
+ const blocking = diagnostics.find((d) => d.severity === "error");
695
+ if (blocking !== undefined) {
696
+ throw makeOpError("$", `${blocking.message} (Defense-in-depth: lint should have caught this earlier.)`);
613
697
  }
614
698
  }
615
699
  let rawResult;
@@ -644,6 +728,13 @@ async function execOpInner(op, vars, emissions, ctx, targetName, skillTimeoutSec
644
728
  vars.set(flatKey, dollarFallback);
645
729
  if (op.outputVar !== undefined)
646
730
  vars.set(op.outputVar, dollarFallback);
731
+ // v0.9.2 — P1.4 record the fallback substitution
732
+ fallbacks.push({
733
+ target: targetName,
734
+ opKind: "$",
735
+ value: dollarFallback,
736
+ reason: `$ ${connectorLabel}${toolName} failed: ${err.message}`,
737
+ });
647
738
  return { lastBoundVar: op.outputVar ?? flatKey, lastValue: dollarFallback };
648
739
  }
649
740
  throw err;
@@ -781,7 +872,7 @@ async function execOpInner(op, vars, emissions, ctx, targetName, skillTimeoutSec
781
872
  let last = { lastBoundVar: null, lastValue: undefined };
782
873
  for (const item of listVal) {
783
874
  vars.set(iterName, item);
784
- last = await execOps(op.foreachBody, vars, emissions, ctx, targetName, skillTimeoutSec, absoluteTimeoutMs, traceBuilder);
875
+ last = await execOps(op.foreachBody, vars, emissions, fallbacks, ctx, targetName, skillTimeoutSec, absoluteTimeoutMs, traceBuilder);
785
876
  }
786
877
  for (const k of Array.from(vars.keys())) {
787
878
  if (!before.has(k))
@@ -792,11 +883,11 @@ async function execOpInner(op, vars, emissions, ctx, targetName, skillTimeoutSec
792
883
  case "if": {
793
884
  for (const branch of op.ifBranches) {
794
885
  if (evalCondition(branch.cond, vars)) {
795
- return execOps(branch.body, vars, emissions, ctx, targetName, skillTimeoutSec, absoluteTimeoutMs, traceBuilder);
886
+ return execOps(branch.body, vars, emissions, fallbacks, ctx, targetName, skillTimeoutSec, absoluteTimeoutMs, traceBuilder);
796
887
  }
797
888
  }
798
889
  if (op.ifElseBody !== undefined) {
799
- return execOps(op.ifElseBody, vars, emissions, ctx, targetName, skillTimeoutSec, absoluteTimeoutMs, traceBuilder);
890
+ return execOps(op.ifElseBody, vars, emissions, fallbacks, ctx, targetName, skillTimeoutSec, absoluteTimeoutMs, traceBuilder);
800
891
  }
801
892
  return { lastBoundVar: null, lastValue: undefined };
802
893
  }