UNPKG

agentfootprint

Version:

The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.

113 lines • 5.59 kB
"use strict"; /** * eventMeta — build EventMeta from a footprintjs TraversalContext. * * Pattern: Adapter (GoF) — translates footprintjs's per-stage context into * agentfootprint's per-event metadata shape. * Role: Used by every core recorder to attach meta to emitted events. * Emits: N/A (helper only). */ Object.defineProperty(exports, "__esModule", { value: true }); exports.parseSubflowPath = exports.eventBelongsToRun = exports.CONSUMER_SCOPE_RUN_ID = exports.buildEventMeta = void 0; const foottrace_1 = require("foottrace"); /** * Build an EventMeta from a stage origin + run-level context. * * Accepts footprintjs's TraversalContext (FlowRecorder events), RecorderContext * (WriteEvent / CommitEvent / etc.), or a bare StageOrigin. When the origin * has no runtimeStageId (rare — manual emit during tests), the meta degrades * gracefully to 'unknown#0'. */ function buildEventMeta(origin, run) { const stageOrigin = origin; const now = stageOrigin?.timestamp ?? Date.now(); const runtimeStageId = origin?.runtimeStageId ?? 'unknown#0'; // Normalize subflowPath across the 3 shapes footprintjs uses: // - undefined (RecorderContext: derive from runtimeStageId) // - /-separated string (TraversalContext: parse) // - readonly string[] (EmitEvent: pass through) const raw = origin?.subflowPath; const subflowPath = Array.isArray(raw) ? raw : typeof raw === 'string' ? parseSubflowPath(raw) : parseSubflowPath((0, foottrace_1.parseRuntimeStageId)(runtimeStageId).subflowPath); return { wallClockMs: now, runOffsetMs: now - run.runStartMs, ...(stageOrigin?.sourcePosition !== undefined && { sourcePosition: stageOrigin.sourcePosition, }), runtimeStageId, subflowPath, compositionPath: run.compositionPath, runId: run.runId, ...(run.traceId !== undefined && { traceId: run.traceId }), ...(run.correlationId !== undefined && { correlationId: run.correlationId }), ...(run.turnIndex !== undefined && { turnIndex: run.turnIndex }), ...(run.iterIndex !== undefined && { iterIndex: run.iterIndex }), // Present only when the run really is session-bound. An absent key and an // empty string are different facts, and only one of them is honest. ...(run.sessionId !== undefined && { sessionId: run.sessionId }), // The actor half of the audit wire (9.11.0). Same rule, one layer up: the // runner only ever puts an EXPLICIT identity on the run context, so there // is nothing to filter here — an absent key means nobody named one. ...(run.principal !== undefined && { principal: run.principal }), ...(run.tenant !== undefined && { tenant: run.tenant }), }; } exports.buildEventMeta = buildEventMeta; /** * The run id an event carries when it was produced OUTSIDE any run — a * consumer's own `agent.emit(...)`, the hosting door's redemption facts. * One spelling, owned here beside the builder of every other run id. */ exports.CONSUMER_SCOPE_RUN_ID = 'consumer-scope'; /** * Does an event belong to `run`? — the ONE answer to "which run is this event * for?", asked by every collector that keeps a single run's events: the run's * own recording (the `recordings` dial) and the self-explain evidence. * * Those collectors subscribe to the whole dispatcher and keep what arrives * while their run is in flight. On an instance that serves more than one * session — `standingAgent({ agent })`, where every session shares one agent — * the dispatcher also carries facts produced for SOMEBODY ELSE at that moment: * a screen redeeming its own chart, a host filing for its own turn, a tool * upload from an earlier run landing late. Kept by timing alone, those facts * entered the wrong person's recording, which that person could then redeem. * * Two clauses, in order: * 1. **An event that names a run belongs to that run, and no other.** A run's * own events carry its id through {@link buildEventMeta}; a fact emitted * through an artifact binding carries the run the BINDING was created for * (captured at bind time, never read from whichever run is live when the * fact lands). * 2. **An event of no run** ({@link CONSUMER_SCOPE_RUN_ID}) **belongs to runs * of the session it was produced for** — the hosting door stamps what it * produces (`RunnerBase.emitAttributed`) — **or, naming no session, to * whatever run is in flight**, exactly as before: a consumer's own * `agent.emit(...)` from inside a tool. */ function eventBelongsToRun(meta, run) { const namedRun = meta?.runId; if (namedRun !== undefined && namedRun !== exports.CONSUMER_SCOPE_RUN_ID) return namedRun === run.runId; const producedFor = meta?.sessionId; return producedFor === undefined || producedFor === run.sessionId; } exports.eventBelongsToRun = eventBelongsToRun; /** * Parse footprintjs's `/`-separated subflow path into a readonly array. * * The source of truth for runtimeStageId parsing lives in footprintjs at * `foottrace::parseRuntimeStageId`. We only need the path-split * convenience here; the `/` separator is stable across footprintjs * versions (covered by their `parseRuntimeStageId` tests). */ function parseSubflowPath(raw) { if (!raw) return []; return raw.split('/').filter((s) => s.length > 0); } exports.parseSubflowPath = parseSubflowPath; //# sourceMappingURL=eventMeta.js.map