@sabaiway/agent-workflow-kit 1.23.0 → 1.25.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.
@@ -19,7 +19,7 @@
19
19
  import { readFileSync, lstatSync } from 'node:fs';
20
20
  import { homedir } from 'node:os';
21
21
  import { pathToFileURL } from 'node:url';
22
- import { detectBackends, wrapperCmdFor } from './detect-backends.mjs';
22
+ import { detectBackends, wrapperCmdFor, wrapperContractFor } from './detect-backends.mjs';
23
23
  import { ACTIVITIES, resolveActivityRecipe, planRecipe } from './recipes.mjs';
24
24
  import { resolveEngineDir, readEngineFragment, PROCEDURES_FRAGMENT_REL } from './engine-source.mjs';
25
25
  // The config schema/read core lives in orchestration-config.mjs (the single config contract). procedures
@@ -131,10 +131,16 @@ const resolveAllSlots = ({ activity, config, detection, overrides }) =>
131
131
  // The concrete wrapper set this slot's EFFECTIVE recipe dispatches (empty for solo). Reuse
132
132
  // planRecipe's drift-guarded dispatch for WHICH backends, then resolve each (backend, role) to its
133
133
  // manifest wrapper cmd via the bridge registry — no wrapper name is hand-composed here.
134
- const backends = planRecipe(resolved.recipe, detection).dispatch
135
- .map((d) => wrapperCmdFor(d.backend, d.role))
136
- .filter(Boolean);
137
- return { slot, ...resolved, backends };
134
+ const { dispatch } = planRecipe(resolved.recipe, detection);
135
+ const backends = dispatch.map((d) => wrapperCmdFor(d.backend, d.role)).filter(Boolean);
136
+ // The full DRIVING CONTRACT per dispatched (backend, role) — resolved HERE, on the raw dispatch
137
+ // pairs, BEFORE they are flattened to wrapper names (the name array cannot reconstruct the role).
138
+ // Every slot with a non-empty dispatch gets contracts — including execute=delegated; the contract
139
+ // is NEVER gated by REVIEW_RECIPES (that set gates only the review-loop economics block).
140
+ const contracts = dispatch
141
+ .map((d) => ({ backend: d.backend, role: d.role, cmd: wrapperCmdFor(d.backend, d.role), contract: wrapperContractFor(d.backend, d.role) }))
142
+ .filter((c) => c.cmd && c.contract);
143
+ return { slot, ...resolved, backends, contracts };
138
144
  });
139
145
 
140
146
  // An unsatisfiable EXPLICIT override is the only "warning" (loud, flagged for the agent to relay). A
@@ -182,6 +188,28 @@ const reviewLoopAdvice = (slots) =>
182
188
  ]
183
189
  : [];
184
190
 
191
+ // The verbatim per-backend DRIVING CONTRACT block (M-contract): the exact invocation descriptor(s),
192
+ // the closed flag set, the grounding note, the round-2/continue delta, and the guarded passthrough
193
+ // tiers — every descriptor printed VERBATIM from the registry mirror of the bridge manifest
194
+ // roles[role].contract (drift-guarded), never re-derived or re-worded here. Rendered for EVERY
195
+ // dispatched backend of EVERY slot (review AND execute=delegated); each wrapper's --help prints the
196
+ // same contract, so the agent never needs to open the wrapper source.
197
+ const contractLines = ({ cmd, contract }) => {
198
+ const lines = [` ${cmd} — driving contract (as bundled with this kit; copy-paste — \`${cmd} --help\` prints the same):`];
199
+ for (const inv of contract.invocations) lines.push(` ${inv}`);
200
+ for (const f of contract.flags ?? []) lines.push(` ${f}`);
201
+ if (contract.grounding) lines.push(` grounding: ${contract.grounding}`);
202
+ const cont = contract.continue ?? [];
203
+ if (cont.length) {
204
+ lines.push(' round-2 delta (resume — never re-send the reviewed artifact):');
205
+ for (const c of cont) lines.push(` ${c}`);
206
+ }
207
+ if (contract.passthrough) {
208
+ lines.push(` passthrough after '--' is ${contract.passthrough.policy}: blocked always: ${contract.passthrough.blocked.join(' ')}; relaxed only under CODEX_PROBE=1: ${contract.passthrough.probeRelaxed.join(' ')}`);
209
+ }
210
+ return lines;
211
+ };
212
+
185
213
  const formatHuman = ({ activity, section, slots, warnings }) => {
186
214
  const lines = [
187
215
  section,
@@ -192,6 +220,7 @@ const formatHuman = ({ activity, section, slots, warnings }) => {
192
220
  const arrow = s.degradedFrom ? ` (requested ${s.degradedFrom} → degraded)` : '';
193
221
  lines.push(` ${s.slot}: ${s.recipe} — ${SOURCE_LABEL[s.source]}${arrow}${backendSetLabel(s.backends)}`);
194
222
  if (s.reason) lines.push(` ↳ ${s.reason}`);
223
+ for (const c of s.contracts ?? []) lines.push(...contractLines(c));
195
224
  }
196
225
  const advice = reviewLoopAdvice(slots);
197
226
  if (advice.length) lines.push('', ...advice);
@@ -206,7 +235,9 @@ const buildJson = ({ activity, section, slots, configSource, warnings }) => ({
206
235
  activity,
207
236
  section,
208
237
  slots: Object.fromEntries(
209
- slots.map((s) => [s.slot, { recipe: s.recipe, source: s.source, degradedFrom: s.degradedFrom, reason: s.reason, backends: s.backends }]),
238
+ // `backends: string[]` is the STABLE pre-existing shape (wrapper names) never repurposed.
239
+ // `contracts` is the ADDITIVE per-dispatch driving-contract field (empty for solo).
240
+ slots.map((s) => [s.slot, { recipe: s.recipe, source: s.source, degradedFrom: s.degradedFrom, reason: s.reason, backends: s.backends, contracts: s.contracts }]),
210
241
  ),
211
242
  reviewLoop: reviewLoopAdvice(slots),
212
243
  configSource,