agentfootprint
Version:
The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.
100 lines • 4.54 kB
JavaScript
;
/**
* StatusRecorder — Claude Code-style live status line for Agent runs.
*
* Pattern: Facade over EventDispatcher's wildcard subscription.
* Role: Tier 3 observability — the low-level helper behind
* `attachStatus(dispatcher, { onStatus })` (exported from
* `agentfootprint/observe`). For the high-level, uniform path use
* `agent.enable.liveStatus({ strategy: chatBubbleLiveStatus({ onLine }) })`.
* One callback receives a human-readable status string at every
* meaningful moment.
* Emits: Does NOT emit; READS core events via the dispatcher and calls
* `onStatus`.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.attachStatus = void 0;
const statusTemplates_js_1 = require("./status/statusTemplates.js");
const RELEVANT = new Set([
'agentfootprint.agent.turn_start',
'agentfootprint.agent.turn_end',
'agentfootprint.agent.iteration_start',
'agentfootprint.agent.route_decided',
'agentfootprint.stream.tool_start',
'agentfootprint.stream.tool_progress',
'agentfootprint.stream.tool_end',
]);
/**
* Attach a thinking-status subscription to the event dispatcher.
* Returns an Unsubscribe — call to detach.
*
* Holds one small piece of state: how many mid-call reports each in-flight
* tool call has filed (9.54.0), so the generic progress line can say "3 so
* far" instead of repeating one indistinguishable sentence. The tally is
* per-`toolCallId` — parallel calls count separately — and is dropped when
* that call ends, so nothing accumulates across a long-lived run.
*/
function attachStatus(dispatcher, options) {
const progressCounts = new Map();
const format = options.format ?? ((e) => defaultFormatter(e, progressCounts));
return dispatcher.on('*', (event) => {
if (!RELEVANT.has(event.type))
return;
if (event.type === 'agentfootprint.stream.tool_progress') {
const id = event.payload.toolCallId;
progressCounts.set(id, (progressCounts.get(id) ?? 0) + 1);
}
const status = format(event);
if (event.type === 'agentfootprint.stream.tool_end') {
progressCounts.delete(event.payload.toolCallId);
}
if (status !== null)
options.onStatus(status);
});
}
exports.attachStatus = attachStatus;
/**
* Default renderer. Humanizes each supported event into a short status line.
*
* `tool_progress` keeps the same display contract the projection keeps
* (`progressMessageOf`): a top-level string `message` is shown verbatim,
* length-capped; anything else gets the tool's name, the fact that it
* reported, and the count. The author's payload is never dumped into prose.
*/
function defaultFormatter(event, progressCounts) {
switch (event.type) {
case 'agentfootprint.agent.turn_start':
return 'Thinking...';
case 'agentfootprint.agent.iteration_start':
return `Iteration ${event.payload.iterIndex}`;
// A bracket carrying `notDispatched` (9.113.0) is a call the paused batch
// never dispatched: "Calling …" and "… failed" would both be false, and a
// live status line reports what the agent is doing, so it says nothing.
case 'agentfootprint.stream.tool_start':
if (event.payload.notDispatched !== undefined)
return null;
return `Calling ${event.payload.toolName}(…)`;
case 'agentfootprint.stream.tool_progress': {
const message = (0, statusTemplates_js_1.progressMessageOf)(event.payload.payload);
if (message !== null)
return message;
const n = progressCounts?.get(event.payload.toolCallId) ?? 1;
return `${event.payload.toolName} reported progress (${String(n)} so far)`;
}
case 'agentfootprint.stream.tool_end':
if (event.payload.notDispatched !== undefined)
return null;
return event.payload.error
? `Tool ${event.payload.toolCallId} failed`
: `Got result from ${event.payload.toolCallId}`;
case 'agentfootprint.agent.route_decided':
return event.payload.chosen === 'final'
? 'Composing answer...'
: 'Continuing with tool calls...';
case 'agentfootprint.agent.turn_end':
return 'Done';
default:
return null;
}
}
//# sourceMappingURL=StatusRecorder.js.map