agentfootprint
Version:
The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.
99 lines • 4.29 kB
JavaScript
;
/**
* chartBinding — every stage of a runner's chart knows its runner, so a chart
* mounted into an executor the app built still serves its events under the
* policy the runner declares.
*
* Pattern: a decorator over a built chart's stage functions, applied ONCE when
* the runner builds its chart (`RunnerBase · initChart`).
* Role: the Map half of `src/redaction/`, for the one door that runs a
* runner's stages without the runner: `getSpec()` mounted with
* footprintjs's own `addSubFlowChart*` and executed by the app's own
* `FlowChartExecutor`. footprintjs hands a mounted chart's stages the
* PARENT executor's scopes, so no run of this library made them — and
* a typed event's payload is served at its source only through a
* scope a run made (`runRedaction.ts` · `emitServed`). Without this
* binding such a stage would emit raw, its runner's `redact` dropped
* in silence.
*
* Each stage function (and a pausable stage's `resumeFn`) is wrapped so that,
* as the stage starts, its scope is tied to the runner's declared policy WHEN
* no run tied it already (`adoptScopeOutsideRun`). In the runner's own runs —
* and in a composition that mounts it — every scope is already tied to the
* run, and the binding does nothing. A runner that declares no policy is
* byte-identical to before.
*
* What it does not reach: a mounted chart's STATE belongs to the app's
* executor (its commit log and snapshot follow that executor's own policy —
* footprintjs's law), and a stage built at run time (a dynamic `StageNode`
* return) is not part of the chart that was bound. Named in
* `src/redaction/README.md`.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.bindChartStages = void 0;
/**
* Bind every stage of `chart` — its nodes, its stage map, its mounted
* subflows (eager and lazy) — so each stage calls `adopt(scope)` before it
* runs. One twin per stage function, so a function the chart holds in two
* places (a node and the stage map) stays one function — footprintjs's mount
* guards compare them by identity.
*/
function bindChartStages(chart, adopt) {
const twins = new Map();
const twin = (fn) => {
const known = twins.get(fn);
if (known !== undefined)
return known;
const bound = function boundStage(scope, ...rest) {
adopt(scope);
return fn.call(this, scope, ...rest);
};
Object.defineProperty(bound, 'name', { value: fn.name, configurable: true });
twins.set(fn, bound);
twins.set(bound, bound);
return bound;
};
// Weak: a lazy subflow resolves a fresh definition at run time.
const seen = new WeakSet();
const visitNode = (value) => {
if (value === null || typeof value !== 'object' || seen.has(value))
return;
seen.add(value);
const node = value;
// A loop's back-edge stub is a reference to a node visited elsewhere.
if (node.isLoopRef === true)
return;
if (typeof node.fn === 'function')
node.fn = twin(node.fn);
if (typeof node.resumeFn === 'function')
node.resumeFn = twin(node.resumeFn);
visitNode(node.next);
for (const child of node.children ?? [])
visitNode(child);
if (node.subflowDef !== undefined)
visitChart(node.subflowDef);
if (typeof node.subflowResolver === 'function') {
const resolve = node.subflowResolver;
node.subflowResolver = () => {
const def = resolve();
visitChart(def);
return def;
};
}
};
const visitChart = (def) => {
visitNode(def.root);
const map = def.stageMap;
if (map instanceof Map) {
for (const [id, fn] of map) {
if (typeof fn === 'function')
map.set(id, twin(fn));
}
}
for (const sub of Object.values(def.subflows ?? {}))
visitNode(sub.root);
};
visitChart(chart);
}
exports.bindChartStages = bindChartStages;
//# sourceMappingURL=chartBinding.js.map