UNPKG

agentfootprint

Version:

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

1,026 lines • 58.2 kB
"use strict";
/**
 * arguments/serve — what the inputs layer says, and to whom.
 *
 * Pattern: Lens. Pure composers, one per served surface: the ruled tool's
 *          SCHEMA (a rebuilt copy per request, never the registry reference),
 *          the past-tense NOTE on a result whose call ran on a filled value
 *          (a declared default, or the person's answer to the batch ask), the
 *          REFUSALS a call reads when it does not run, and the "Assumed"
 *          block under an existing `.limitsTravelWithTheAnswer()`.
 * Role:    core/ layer leaf of the inputs layer (honesty layer 2). Every
 *          sentence here is registered in `test/modelFacingSurfaces.test.ts`:
 *          it says what the model may do and what the record keeps, and
 *          promises no outcome a later path can break. Loaded through
 *          `import()` by every static site that serves one of them — the
 *          tools slot and seed (the schema), ToolCalls through
 *          `dispatch.ts` (the note, the refusal), the final branch's armed
 *          variant (the block) — and only under the arm: the optional-family
 *          law of docs-next's site budget, so a plain agent's graph never
 *          carries this module.
 * Emits:   N/A.
 *
 * ## The value in a sentence goes through the tool's own view
 *
 * A served schema and a served tool message are both part of the record. When
 * the tool's argument view (`core/toolShownArgs.ts` · `shownArgsOf`) hides a
 * ruled argument, no sentence here prints its value — it says the value is
 * hidden by the tool's view instead.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.assumedLinesFor = exports.assumedBlockOf = exports.assumedBlock = exports.ASSUMED_BLOCK_HEADING = exports.keptAnswersNote = exports.timeRefusal = exports.secondPauseRefusal = exports.unansweredRefusal = exports.unmountedRulesRefusal = exports.unreadableRulesRefusal = exports.ANSWERED_SOURCE_CLAUSE = exports.filledNote = exports.timeLine = exports.TIME_LINE_SOURCE = exports.clockSentence = exports.UNKNOWN_ZONE_CLAUSE = exports.timeLimitsSentence = exports.timeLimitsLine = exports.timeWindowsLine = exports.rulesOnWire = exports.withArgumentRules = exports.ASK_SOURCES_SENTENCE = exports.ASK_SENTENCE = exports.assumeSentence = exports.hidesArgument = exports.printedValue = void 0;
const toolShownArgs_js_1 = require("../../toolShownArgs.js");
const answer_js_1 = require("../../../semantics/coverage/answer.js");
Object.defineProperty(exports, "ASSUMED_BLOCK_HEADING", { enumerable: true, get: function () { return answer_js_1.ASSUMED_BLOCK_HEADING; } });
const timeLimits_js_1 = require("../../../semantics/coverage/timeLimits.js");
const rewrites_js_1 = require("../middleware/rewrites.js");
const declare_js_1 = require("../../../tools/declare.js");
const rows_js_1 = require("./rows.js");
const saidByPerson_js_1 = require("../../../lib/saidByPerson.js");
const convert_js_1 = require("../../../time/convert.js");
const windows_js_1 = require("../../../time/windows.js");
const present_js_1 = require("../../../time/present.js");
const isPlainObject = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
/** A declared value as a sentence prints it: a string quoted, a number or boolean bare. */
function printedValue(value) {
    return typeof value === 'string' ? JSON.stringify(value) : String(value);
}
exports.printedValue = printedValue;
/**
 * Whether the tool's own argument view hides `argument` — asked with a probe
 * of the declared value, so the answer is the view's, never a guess.
 */
function hidesArgument(tool, argument, value) {
    const probe = { [argument]: value };
    const shown = (0, toolShownArgs_js_1.shownArgsOf)(tool, probe);
    return shown !== probe && shown[argument] !== value;
}
exports.hidesArgument = hidesArgument;
/**
 * The sentence a ruled `assume` property carries in the served schema: what the
 * library does when the call leaves the argument out, and what the record
 * keeps. It promises nothing a later path can break — not that the call runs
 * (permission may deny it), only what fills a value that is left out.
 */
function assumeSentence(value, hidden) {
    return hidden
        ? "If left out, the tool's rule fills a declared value (hidden by the tool's view), recorded as assumed."
        : `If left out, the tool's rule fills ${printedValue(value)}, recorded as assumed.`;
}
exports.assumeSentence = assumeSentence;
// LENS · tool-description · persistent-history
// reads: the tool's own `ask` rule — nothing about the call, the model or the person
// law: says what the model may do and what the record keeps; promises no outcome (the call may still
// be denied by permission, refused, or asked about again).
/**
 * The sentence a ruled `ask` property carries in the served schema: the rule
 * asks the person for the value, so the model may leave it out — and should,
 * unless the person gave it. No value is printed: an `ask` rule has no default.
 */
exports.ASK_SENTENCE = "The tool's rule asks the person for this value; leave it out unless the person gave it.";
// LENS · tool-description · persistent-history
// reads: the tool's own `ask` rule and the declared-sources arm — nothing about the call, the model or
//        the person
// law: says what the model may do and where it declares it; promises no outcome (the call may still be
// denied by permission, refused, or asked about again).
/**
 * The sentence a ruled `ask` property carries under DECLARED SOURCES
 * (`.findings({ argumentSources: true })`, `.inputsLayer({ argumentSources:
 * true })`) in place of `ASK_SENTENCE`: the same rule, and — at the place the
 * model decides whether to send the value — where it says the person gave it.
 * Under the arm a present value with no traced source is asked of the person
 * (`resolve.ts`), so "leave it out unless the person gave it" alone told the
 * model that SENDING the value was the whole of its statement: in the paid
 * step-5 run (`bench/inputs/runs/haiku45-step5`) it left the period out on 32
 * of 32 calls where the person gave none — and a period the person HAD given
 * was traced to their words, unasked, on 7 of 88 calls.
 */
exports.ASK_SOURCES_SENTENCE = "The tool's rule asks the person for this value; leave it out unless the person gave it, and " +
    'then quote their words for it in `_findings.from`.';
function withSentence(property, sentence) {
    const description = property.description;
    const text = typeof description === 'string' && description.trim() !== ''
        ? `${description.trimEnd()} ${sentence}`
        : sentence;
    return { ...property, description: text };
}
// FOLD · the one owner of how a ruled tool's schema is SERVED
// consumers read this and never re-derive it: core/slots/buildToolsSlot.ts · commitWire (the one
// decoration site, through `rulesOnWire`) and core/agent/stages/seed.ts (its static twin), each
// loading this module through `import()` under the arm; servedView rebuilds from the committed
// list, so the decoration is a pure function of (schema, tool).
/**
 * The served copy of a ruled tool's schema: every argument an `assume` or an
 * `ask` rule governs leaves `required` (the library fills it, or asks the
 * person for it, when the call leaves it out) and its property's description
 * gains the rule's sentence (an `ask` rule's names `_findings.from` under the
 * sources arm, `options.sources`). A rebuilt copy — the registry schema, which
 * `validateToolArgs` judges and `mcpServe` serves, is never edited. The SAME
 * reference back for a tool with no rules, rules that cannot be read (the
 * dispatch re-read refuses its calls) or no tool at all (a chart with no
 * claimant record) — so an agent whose tools declare nothing serves the bytes
 * it always did.
 *
 * @example
 * ```ts
 * withArgumentRules(searchLogs.schema, searchLogs).inputSchema.required; // ['service']
 * ```
 */
function withArgumentRules(schema, tool, options) {
    const rules = (0, declare_js_1.rulesOf)(tool);
    if (rules === undefined || (0, declare_js_1.isRefused)(rules))
        return schema;
    const served = rules.ruled.filter((r) => (r.rule === 'assume' && r.assume !== undefined) || (r.rule === 'ask' && r.ask !== undefined));
    if (served.length === 0)
        return schema;
    const input = schema.inputSchema;
    const properties = isPlainObject(input.properties) ? { ...input.properties } : {};
    for (const r of served) {
        const property = properties[r.argument];
        if (!isPlainObject(property))
            continue;
        if (r.rule === 'ask') {
            const sentence = options?.sources === true ? exports.ASK_SOURCES_SENTENCE : exports.ASK_SENTENCE;
            properties[r.argument] = withSentence(property, sentence);
            continue;
        }
        const value = r.assume;
        properties[r.argument] = withSentence(property, assumeSentence(value, hidesArgument(tool, r.argument, value)));
    }
    const ruledNames = new Set(served.map((r) => r.argument));
    const required = Array.isArray(input.required)
        ? input.required.filter((name) => !(typeof name === 'string' && ruledNames.has(name)))
        : undefined;
    const { required: _required, ...rest } = input;
    void _required;
    return {
        ...schema,
        inputSchema: {
            ...rest,
            properties,
            ...(required !== undefined && required.length > 0 && { required }),
        },
    };
}
exports.withArgumentRules = withArgumentRules;
/**
 * The served list with each ruled tool's schema decorated (`withArgumentRules`,
 * from the rules of the implementation that WINS each name), or the SAME list
 * when no served schema changed — so an armed agent whose wire carries no
 * ruled tool commits the bytes it always did. The tools slot's one decoration
 * site (`core/slots/buildToolsSlot.ts` · `commitWire`) calls it under the arm.
 * `optionsOf` gives each schema its own `ServeOptions`: under declared sources
 * one wire can hold a tool that carries `_findings.from` beside a provider's
 * tool whose author owns `_findings`, which does not.
 */
function rulesOnWire(served, winningTools, optionsOf) {
    let changed = false;
    const decorated = served.map((schema) => {
        const next = withArgumentRules(schema, winningTools.get(schema.name), optionsOf?.(schema));
        if (next !== schema)
            changed = true;
        return next;
    });
    return changed ? decorated : served;
}
exports.rulesOnWire = rulesOnWire;
/** A converted value as the line prints it — an object form's as JSON. */
function printedArgument(value) {
    return typeof value === 'object' && value !== null
        ? JSON.stringify(value)
        : printedValue(value);
}
/** The served tools (in served order) whose winning implementation declares period forms. */
function periodToolsOf(served, winningTools) {
    const out = [];
    for (const schema of served) {
        const tool = winningTools.get(schema.name);
        const rules = (0, declare_js_1.rulesOf)(tool);
        if (tool === undefined || rules === undefined || (0, declare_js_1.isRefused)(rules))
            continue;
        const forms = (0, declare_js_1.periodFormsOf)(rules.period);
        if (forms.length === 0)
            continue;
        out.push({ name: schema.name, tool, forms, facts: (0, declare_js_1.periodFactsOf)(rules.period) });
    }
    return out;
}
/** Whose one window of the person's words is — the line's source clause. */
function whoseWindow(w) {
    if (w.source === 'answered') {
        return w.answer === 'edited'
            ? 'the window the person gave when asked what their words meant'
            : 'the window the person confirmed when asked what their words meant';
    }
    return "a reading of the person's words they have not confirmed, not their words";
}
/**
 * A window for a person, in its zone, to the minute with its end AS SAID — the last minute inside
 * the half-open range (`time/present.ts` · `presentRange`); the ISO interval if it cannot be.
 */
function presentedWindow(w) {
    try {
        return (0, present_js_1.presentRange)(w.range, { zone: w.zone }, 'minute');
    }
    catch {
        return `${w.range.from}/${w.range.to}`;
    }
}
/** One tool's values for one settled window — exact, else the wider read the fill would use, said so. */
function toolValues(pt, w, windows) {
    const ctx = {
        now: windows.now,
        zone: w.zone,
        ...(windows.appZone !== undefined && { appZone: windows.appZone }),
        granularityMs: (0, convert_js_1.granularityMsOf)(pt.facts),
    };
    // The one answer (`convertForTool`) over the one input (`windowToConvert`) the fill and the time
    // ask's answer also use: the values named here are the values the call is handed, and a window
    // the tool would refuse is named for no tool.
    const read = (0, convert_js_1.convertForTool)((0, windows_js_1.windowToConvert)(w), pt.forms, pt.facts, ctx);
    if ('refused' in read)
        return undefined;
    const conversion = read.conversion;
    const values = Object.entries(conversion.values).map(([argument, value]) => hidesArgument(pt.tool, argument, value)
        ? `${argument} (hidden by the tool's view)`
        : `${argument} ${printedArgument(value)}`);
    const wider = 'sent' in conversion ? ' (a wider read than the words named)' : '';
    return `${pt.name} ${values.join(', ')}${wider}`;
}
/** The settled half: each window, whose it is, and each tool's values for it. */
function settledSentence(tools, windows) {
    const clauses = [];
    for (const w of windows.windows) {
        if (w.quote === undefined)
            continue;
        const values = tools.flatMap((pt) => toolValues(pt, w, windows) ?? []);
        if (values.length === 0)
            continue;
        clauses.push(`“${w.quote}” is ${presentedWindow(w)}, ${whoseWindow(w)} — ${values.join('; ')}`);
    }
    if (clauses.length === 0)
        return undefined;
    const these = clauses.length === 1 ? 'that window' : 'those windows';
    return (`The person's time words, as the library holds them: ${clauses.join('. ')}. ` +
        `A call may pass these values as written; an answer built on them states ${these}.`);
}
/**
 * The control half: the window the person set in the app's time control (the run's `time.window`,
 * `source: 'control'`, TQ26) — the person's own, like an answer — in their zone, with each served
 * period tool's values for it, the same conversion the fill uses. So an app never writes its own
 * prompt text for a window it set from a UI. The window is a fact about the person's TURN, not
 * about a tool (G14): with no served period tool that can read it, the sentence still names it —
 * without values, and without a permission to pass any.
 */
function controlSentence(tools, windows) {
    const w = windows.control;
    if (w === undefined)
        return undefined;
    const values = tools.flatMap((pt) => toolValues(pt, w, windows) ?? []);
    // Under an unknown zone (G15) the window is spelled in UTC — the zone the presented window
    // already names — and the line says why, once.
    const unknown = windows.zoneUnknown === true ? ` (${exports.UNKNOWN_ZONE_CLAUSE})` : '';
    const named = `The window the person set in the app's time control is ${presentedWindow(w)}${unknown}`;
    if (values.length === 0)
        return `${named}. An answer built on it states that window.`;
    return (`${named} — ${values.join('; ')}. A call may pass these values as written; an answer built ` +
        'on them states that window.');
}
/**
 * The pending half: the quotes no one has confirmed. Before any call ran on a window of the
 * model's own, the one move that asks the person — the call with the period left out — named as
 * the next step, with the two moves that do not (a question in the reply, a written window).
 * After one did, the limit the answer states instead, as a CONCLUSION about the results in hand:
 * whose reading their window is and what the answer names — never a story of the call that ran
 * and never a negation of a confirmation. Measured twice: "the person has not confirmed it" drew
 * "You're right… I apologize" on 37/37 answers (T6b round 1); "the call that ran carried a window
 * written into the call, not one the person confirmed", under the library's opening, still drew it
 * on 7/10 (`bench/time/runs/haiku45-t6b-r2`, stopped) — a past-tense account of the call reads as
 * the person's complaint, whoever the opening says is talking.
 */
function pendingSentence(allTools, windows) {
    // A tool that refused a quote cannot confirm it: the quote is pending only for the tools that
    // did not, and one every served period tool refused is the refused half's alone.
    const pending = (windows.pending ?? []).filter((q) => allTools.some((t) => !refusedFor(windows, q, t.name)));
    if (pending.length === 0)
        return undefined;
    const tools = allTools.filter((t) => pending.some((q) => !refusedFor(windows, q, t.name)));
    const quotes = pending.map((q) => `“${q}”`).join(', ');
    if (windows.ranUnconfirmed === true) {
        return (`The results for ${quotes} cover the window written into the call — the assistant's own ` +
            `reading of those words. So the answer gives those results and names that window as the ` +
            `assistant's reading of ${quotes}.`);
    }
    const leftOut = tools.flatMap((pt) => {
        const args = [...new Set(pt.forms.flatMap((f) => (0, convert_js_1.formArguments)(f).map((a) => a.argument)))];
        return args.length === 0 ? [] : [{ name: pt.name, args: args.join(', ') }];
    });
    if (leftOut.length === 0)
        return undefined;
    const unread = (windows.pendingUnread ?? []).filter((q) => pending.includes(q));
    if (unread.length > 0) {
        return unreadSentence(pending, windows.pendingZones ?? [], unread, leftOut.map((c) => `${c.name} with ${c.args} left out`).join(', or '));
    }
    const calls = leftOut.map((c) => `${c.name} is called with ${c.args} left out`);
    return (`The window for ${quotes} is not settled yet: ${formClause(pending, windows.pendingZones)} ` +
        `${calls.join(', or ')} (or the call is refused with the reason). So the next step is that ` +
        'call — not a question about the time in the reply, and not a window written into the call, ' +
        'which would run unconfirmed.');
}
/**
 * What the library's own form does for the pending quotes, up to "opens when": it shows its
 * reading to confirm — or, for a quote it holds no reading of (`ReaderWindows.pendingZones`: a
 * zone it cannot resolve), it asks which time zone the words name and shows no reading. Words it
 * could not read at all (`ReaderWindows.pendingUnread`) are {@link unreadSentence}'s.
 */
function formClause(pending, zones = []) {
    const zoned = pending.filter((q) => zones.includes(q));
    const read = pending.filter((q) => !zones.includes(q));
    if (zoned.length === 0) {
        return ("the person confirms it in the library's own form, which shows its reading of those " +
            'words with the zone and opens when');
    }
    const zq = zoned.map((q) => `“${q}”`).join(', ');
    if (read.length === 0) {
        return ('the library holds no reading of those words until it knows which time zone they name, ' +
            'so its own form asks the person for that zone, and it opens when');
    }
    const rq = read.map((q) => `“${q}”`).join(', ');
    return (`the person confirms ${rq} in the library's own form, which shows its reading of those ` +
        `words with the zone, and names the time zone of ${zq}, which the library holds no reading ` +
        'of until it knows it; the form opens when');
}
/**
 * The pending half when some of the person's words were not read at all
 * (`ReaderWindows.pendingUnread`): the library's CONCLUSION and the ONE next step, in that order —
 * it could not read those words, so the next step is the call with the period left out, which
 * opens its own form (asking which time they meant, nothing filled in; beside it, a reading to
 * confirm and a zone to name, each quote with what the form does for it) — then the two moves
 * that are not it, said as what not to do.
 *
 * WHY THIS SHAPE. The line before it opened "The window for … is not settled yet: the library
 * could not read those words, so its own form asks the person which time they meant … So the next
 * step is that call — not a question about the time in the reply". Haiku 4.5 answered it with a
 * question in prose and no call on 15/15 `yesterday-morning` runs of the T6b bench
 * (`bench/time/runs/haiku45-t6b-v2`), and again in the 2026-10-01 demo video ("which clients had
 * slow operations yesterday morning"): "could not read" read as permission to ask, and the next
 * step came last. So the step comes first, as the conclusion of what the library could not read,
 * and the reply's question is named as the move NOT to make (`bench/time/RULE-unread.md`).
 */
function unreadSentence(pending, zones, unread, leftOut) {
    const listed = (qs) => qs.map((q) => `“${q}”`).join(', ');
    const zoned = pending.filter((q) => zones.includes(q) && !unread.includes(q));
    const read = pending.filter((q) => !zones.includes(q) && !unread.includes(q));
    const alone = zoned.length === 0 && read.length === 0;
    const does = [
        alone
            ? 'asks the person which time they meant, with nothing filled in'
            : `asks the person which time ${listed(unread)} meant, with nothing filled in`,
        ...(read.length > 0
            ? [`shows its reading of ${listed(read)} with the zone for the person to confirm`]
            : []),
        ...(zoned.length > 0
            ? [
                `asks the time zone of ${listed(zoned)}, which the library holds no reading of until it knows it`,
            ]
            : []),
    ];
    const doing = does.length === 1
        ? does[0]
        : `${does.slice(0, -1).join(', ')}, and ${does[does.length - 1]}`;
    return (`The library could not read ${listed(unread)}, so the next step is to call ${leftOut}: ` +
        `that call opens the library's own form, which ${doing} (or the call is refused with the ` +
        'reason). Do not ask about the time in the reply — the form asks it — and do not write a ' +
        'window into the call, which would run unconfirmed.');
}
// LENS · late-line · request-ephemeral
// reads: the person's windows a SERVED period tool refused this turn before dispatch (`windows.ts` ·
//        `readerWindowsOf`, `refused`: the quote, the tool and the reason code) and that tool's
//        declared facts
// law: the library's CONCLUSION about a call that did not run: past tense for the refusal, the
//      reason in the tool's declared facts only (the one text `timeRefusal` also serves — never the
//      window's value), then what an answer states; it names no next move for that window.
/**
 * The refused half of the served time line: each window of the person's a
 * served period tool refused before dispatch — the call's reason in the same
 * words its result read ({@link timeRefusal}), then the conclusion an answer
 * states. A refused window is not a pending one to ask about again: the T6b
 * bench's future case asked the same refused call five times and ran out of
 * budget while the pending half kept naming that call as the next step.
 * `undefined` when nothing was refused.
 */
function refusedSentence(tools, windows) {
    const byName = new Map(tools.map((t) => [t.name, t]));
    const refused = (windows.refused ?? []).filter((r) => byName.has(r.toolName));
    if (refused.length === 0)
        return undefined;
    const clauses = refused.map((r) => {
        // The person's window was refused before a form was chosen: the tool's facts (`toolFacts`).
        const pt = byName.get(r.toolName);
        const facts = (0, convert_js_1.toolFacts)(pt.forms, pt.facts);
        return `${r.toolName} was not run for “${r.quote}”: ${refusalReason(r.refused, facts, r.argument)}.`;
    });
    const names = [...new Set(refused.map((r) => r.toolName))];
    const conclusion = refused.length === 1
        ? `So the answer tells the person that ${names[0]} could not read that time, and claims ` +
            `nothing about it from ${names[0]}.`
        : 'So the answer tells the person which of those times each tool could not read, and ' +
            'claims nothing about them from that tool.';
    return `${clauses.join(' ')} ${conclusion}`;
}
/** Whether `tool` refused the person's window for `quote` this turn. */
const refusedFor = (windows, quote, tool) => (windows.refused ?? []).some((r) => r.quote === quote && r.toolName === tool);
// LENS · late-line · request-ephemeral
// reads: the turn's settled windows of the person's words (each mention's quote, its one window and
//        WHOSE it is — the person's answer in the time ask, or a model reader's reading), the
//        quotes still pending (a rule reading the person has not answered — `bind.ts` ·
//        `pendingQuotesOf`), the turn's clock, and the SERVED tools' declared period forms and
//        facts — converted by the one owner (`time/convert.ts`)
// law: the library's CONCLUSION, never raw facts: a settled window is named with its source (a
//      reading is never called the person's words) and the values each tool takes, as a
//      permission ("may pass"); a pending quote names no window and no reading (a proposal is not
//      a fact the model may pass) — only that it is not confirmed and the one move that asks the
//      person. Whether a call is filled, bound or refused is decided at dispatch and recorded there.
/**
 * The ONE served time line (TQ13; step T6b's serving placement): one
 * request-only `user` line appended LAST to the request, at the decision
 * point — composed at the tools slot's one decoration site
 * (`core/slots/buildToolsSlot.ts` · `commitWire`) from the tools it really
 * serves, carried to `callLLM` on `timeLine`, and rebuilt by
 * `time-travel/servedView.ts` from the same committed key. Four parts:
 *
 * - SETTLED — each window the person confirmed or gave in the time ask (or a
 *   `model` reader's reading), in the person's zone, WHOSE it is, and each
 *   served period tool's values for it — the exact conversion, else the wider
 *   one the fill would use (said so); a value the tool's view hides is named
 *   hidden. So the model never re-derives a window from words, and the answer
 *   states the window it was built on.
 * - CONTROL — the window the person set in the app's time control, in
 *   their zone, with each served period tool's values for it; with none
 *   (no period tool served, or none can read it) the window alone (G14).
 * - REFUSED — a window of the person's a served period tool refused before
 *   dispatch this turn: the refusal's reason in the result's own words, then
 *   what an answer states. That quote is no longer pending for that tool.
 * - PENDING — a proposal the person has not answered is no window yet, and a
 *   call that writes its own window runs as sent, unconfirmed (§ 7.3). The
 *   line names the next step — the call with the period arguments left out,
 *   so the library's own form confirms the window with the person — and the
 *   two moves that are not it (a question about the time in the reply, a
 *   written window). Once a call of the turn already ran on a written window
 *   (`ranUnconfirmed`), it names the limit an answer states instead.
 *
 * `undefined` when no part has anything to say — a turn with no time words
 * and no control window serves no line at all; with no served period tool
 * only the control half can speak. The halves carry no opening: {@link timeLine} composes the served line and
 * opens it, once, with {@link TIME_LINE_SOURCE}.
 *
 * @example
 * ```ts
 * timeWindowsLine(served, winningTools, { now, windows: [], pending: ['yesterday'] });
 * // 'The window for “yesterday” is not settled yet: the person confirms it in the library's own
 * //  form, which shows its reading of those words with the zone and opens when client_activity
 * //  is called with start_time, end_time left out (or the call is refused with the reason). So
 * //  the next step is that call — not a question about the time in the reply, and not a window
 * //  written into the call, which would run unconfirmed.'
 * ```
 */
function timeWindowsLine(served, winningTools, windows) {
    const tools = periodToolsOf(served, winningTools);
    // The control window is the person's turn's, served whether or not a period tool is (G14); the
    // other halves speak about period tools only.
    if (tools.length === 0)
        return controlSentence(tools, windows);
    const halves = [
        settledSentence(tools, windows),
        controlSentence(tools, windows),
        refusedSentence(tools, windows),
        pendingSentence(tools, windows),
    ].filter((h) => h !== undefined);
    return halves.length === 0 ? undefined : halves.join(' ');
}
exports.timeWindowsLine = timeWindowsLine;
/**
 * The served time limits from the Tools mount's `timeLimits` FACTS
 * (`semantics/coverage/timeLimitFacts.ts` · `TimeLimitFacts`): rendered for the MODEL
 * (`semantics/coverage/timeLimits.ts` · `renderTimeLimits` — the one composer the
 * limits block asks too), then {@link timeLimitsSentence}. The facts cross
 * the mount unrendered so the renderer loads here, under the arm, and never
 * on the graph a plain agent loads.
 *
 * @example
 * ```ts
 * timeLimitsLine(undefined); // undefined — the turn's reads match what was asked
 * ```
 */
function timeLimitsLine(facts) {
    return timeLimitsSentence((0, timeLimits_js_1.renderTimeLimits)(facts, 'model'));
}
exports.timeLimitsLine = timeLimitsLine;
// LENS · late-line · request-ephemeral
// reads: the turn's `period` rows whose result checks hold and its `source-clock` rows, composed by
//        the ONE owner of the limits lines (`semantics/coverage/timeLimits.ts` · `timeLimitLinesOf`,
//        audience `model`) — the same lines the limits block prints for the person after the answer
// law: the library's CONCLUSION about what each call READ against what it ASKED (step T8), both
//      ranges in the person's zone, and what an answer built on it states; it names what the record
//      holds for calls that already ran and promises nothing a later call can break. A source that
//      holds none or part of the asked time is named with ITS span in the person's zone (`held`):
//      the zone conversion is the library's, never the model's.
/**
 * The time limits an answer states (time step T8's serving placement): the
 * result checks that hold this turn — a read narrower, wider or shifted from
 * what was asked, a window older than the source keeps, sources on different
 * clocks — served to the MODEL as the library's conclusion, LATE, at the
 * decision point, in the ONE served time line (TQ13) after the windows'
 * halves. The step-7b bench showed why: raw facts on a result are not
 * compared by the model, a conclusion served at the decision point is.
 *
 * `undefined` when nothing holds — a turn whose reads match what was asked
 * serves no sentence at all. It carries no opening: {@link timeLine} opens
 * the served line once with {@link TIME_LINE_SOURCE}.
 *
 * @example
 * ```ts
 * timeLimitsSentence({ period: ['client_activity read less than was asked — asked: …; read: …'], clocks: [] });
 * // 'The time the tools read is not the time asked about — client_activity read less than was
 * //  asked — asked: …; read: …. So the answer to the person states the time each result read and
 * //  claims nothing about time no result read.'
 * ```
 */
function timeLimitsSentence(lines) {
    if (lines === undefined)
        return undefined;
    const parts = [];
    if (lines.period.length > 0) {
        parts.push(`the time the tools read is not the time asked about — ${lines.period.join('; ')}. So ` +
            `the answer to the person states the time each result read and claims nothing about ` +
            `time no result read.`);
    }
    // A source that holds none or only part of the asked time (take 3): the spans in the person's
    // zone, so the answer names what the source holds without converting a UTC instant itself.
    if (lines.held !== undefined && lines.held.length > 0) {
        parts.push(`what the sources hold is not all of the time asked about — ${lines.held.join('; ')}. So ` +
            `the answer to the person states what each source holds and claims nothing about the ` +
            `time it does not hold.`);
    }
    if (lines.clocks.length > 0) {
        parts.push(`Clocks: ${lines.clocks.join('; ')}.`);
    }
    if (parts.length === 0)
        return undefined;
    const capital = (part) => part.charAt(0).toUpperCase() + part.slice(1);
    return parts.map(capital).join(' ');
}
exports.timeLimitsSentence = timeLimitsSentence;
/** What a line says when the run's clock zone is unknown (G15) — the zone is never guessed. */
exports.UNKNOWN_ZONE_CLAUSE = "the person's time zone is not known";
// LENS · late-line · request-ephemeral
// reads: the turn's `clock` row — its `now` and zone (`zoneSource: 'unknown'` → the UTC spelling)
// law: the library's clock as a CONCLUSION, one short sentence, to the minute, the weekday named
//      (a model re-derives a weekday wrong), the zone always named — and named as not known when
//      it is, never guessed (G15).
/**
 * The run clock, for the model (G16): the turn's `now` to the minute, its
 * weekday and date in the person's zone, the zone named — so the model never
 * answers "today" or "this morning" from its training date. Under `.time()`
 * it is the first part of the ONE served time line on every request; never
 * without it. `undefined` when there is no clock (or it cannot be spelled).
 *
 * @example
 * ```ts
 * clockSentence({ now: '2026-10-09T15:40:00Z', zone: 'America/Los_Angeles' });
 * // "This turn's time: Friday 2026-10-09 08:40 America/Los_Angeles (UTC-07:00)."
 * clockSentence({ now: '2026-10-09T15:40:00Z', zone: 'UTC', zoneUnknown: true });
 * // "This turn's time: Friday 2026-10-09 15:40 UTC (the person's time zone is not known)."
 * ```
 */
function clockSentence(clock) {
    if (clock === undefined)
        return undefined;
    const ms = Date.parse(clock.now);
    if (!Number.isFinite(ms))
        return undefined;
    try {
        const minute = new Date(Math.floor(ms / 60_000) * 60_000).toISOString();
        const at = (0, present_js_1.presentInstant)(minute, { zone: clock.zone });
        const weekday = new Intl.DateTimeFormat('en-US', {
            weekday: 'long',
            timeZone: clock.zone,
        }).format(ms);
        const unknown = clock.zoneUnknown === true ? ` (${exports.UNKNOWN_ZONE_CLAUSE})` : '';
        return `This turn's time: ${weekday} ${at}${unknown}.`;
    }
    catch {
        return undefined;
    }
}
exports.clockSentence = clockSentence;
/**
 * The ONE served time line's opening: WHO says it — the library, not the
 * person. The line is a request-only `user` message, so an unmarked one reads
 * as the person talking. Measured twice: the T8 bench's first paid round
 * (stopped at 41 runs) — 15 of 15 answers to the limits sentence opened
 * "You're right" / "I apologize", and naming the library alone (round 1,
 * stopped at 46) still drew "Thank you for the clarification"; the T6b paid
 * run, whose windows halves carried no opening — 37 of 37 answers after the
 * unconfirmed-call sentence opened "You're right… I apologize". So it also
 * says the note is no correction, and how an answer uses it. It says nothing
 * about WHEN to answer: the pending half names a call as the next step.
 * Applied once, by {@link timeLine}, whichever halves the line holds.
 *
 * The bytes are the authorship registry's (`lib/saidByPerson.ts` ·
 * `LIBRARY_NOTE_OPENING`, one of `LIBRARY_AUTHORED_PREFIXES`), so the line
 * that is served LAST on every request under `.time()` is never read as the
 * person's words by `isSaidByPerson` — the last `role: 'user'` message of a
 * request is the library's, and "this turn" is not (G17).
 */
exports.TIME_LINE_SOURCE = saidByPerson_js_1.LIBRARY_NOTE_OPENING;
// LENS · late-line · request-ephemeral
// reads: the composed halves of the turn's ONE served time line — the person's windows
//        (`timeWindowsLine`) and the time limits (`timeLimitsLine`) — and nothing else
// law: one line, one opening: the source is said ONCE, first, whichever halves are present; no half
//      carries its own, so the opening never repeats inside the line.
/**
 * The ONE served time line (TQ13): its parts in order — the windows' halves,
 * then the limits — opened ONCE with {@link TIME_LINE_SOURCE}. The tools
 * slot's one decoration site (`core/slots/buildToolsSlot.ts` · `commitWire`)
 * calls it, `callLLM` appends the result LAST to the request, and
 * `time-travel/servedView.ts` rebuilds it byte for byte from the same
 * committed `timeLine` key. `undefined` when no part has anything to say — a
 * turn with nothing to say serves no line (and without `.time()` nothing here
 * runs at all).
 *
 * @example
 * ```ts
 * timeLine([undefined, timeLimitsLine(facts)]);
 * // '[A note from the library that runs the tools — …] The time the tools read is not …'
 * timeLine([undefined, undefined]); // undefined
 * ```
 */
function timeLine(parts) {
    const present = parts.filter((p) => p !== undefined && p.length > 0);
    return present.length === 0 ? undefined : [exports.TIME_LINE_SOURCE, ...present].join(' ');
}
exports.timeLine = timeLine;
// LENS · tool-result · persistent-history
// reads: the call's own fill (`argumentResolutions`, the layer's entry for this toolCallId), kept only
//        where the call RAN with it (`dispatch.ts` · `fillsThatRan` — a middleware may rewrite one)
// law: may omit, never deny; every clause anchored to the call it was composed on — past tense,
// naming the call this result answers, so a later call of the turn re-reading it reads a true sentence.
/**
 * The past-tense note ToolCalls appends to the result of a call that RAN on a
 * value the library filled — one bracket per filled argument the call really
 * ran with (a clause whose fill a before-tool middleware rewrote is left
 * out), after the tool's own bytes. The message then carries `toolChars`, and
 * every reader of a result's content as the TOOL's words reads through that
 * cut (`lib/toolBytes.ts` · `toolBytesOf`): the evidence index, the answer's
 * standing and the answer account's in-view results, the unsupported-argument
 * seam's grounded corpus and the empty-lookup seam's producer corpus. The
 * value is kept in the note because the model needs it to reason about what
 * ran ("no errors in 2h"); it is never evidence for that value, and it never
 * hides a reading of the tool's own bytes.
 *
 * @example
 * ```ts
 * filledNote('search_logs', [{ argument: 'window', value: '2h', hidden: false }]);
 * // '\n\n[window was not in the search_logs call this result answers; the call ran with "2h",
 * //  the value the tool's rule assumes — recorded as assumed, not as the person's.]'
 * ```
 */
function filledNote(toolName, fills, options) {
    return fills
        .map((f) => f.source === 'answered'
        ? answeredClause(toolName, f, options?.sources === true)
        : f.source === 'window'
            ? windowClause(toolName, f)
            : assumedClause(toolName, f))
        .join('');
}
exports.filledNote = filledNote;
/** A filled value as a note prints it — an object form's value as JSON. */
function printedFill(value) {
    return typeof value === 'object' ? JSON.stringify(value) : printedValue(value);
}
// LENS · tool-result · persistent-history
// reads: the call's window fill (`argumentResolutions`: the turn's ONE window of the person's, converted
//        into the tool's form — `time/bind.ts`), kept only where the call RAN with it, and whose window
//        it was (the person's words, a model reader's unconfirmed reading of them, a UI control)
// law: may omit, never deny; past tense, naming the call this result answers; a reading of the person's
//      words is never called their words.
/** The clause for a value the turn's one window of the person's filled (the time layer, step T5a). */
function windowClause(toolName, f) {
    const whose = f.from === 'control'
        ? 'from the window the person set in the app — recorded as set in the app'
        : f.from === 'answered'
            ? 'from the window the person chose when asked what their words meant — recorded as ' +
                "the person's answer"
            : f.from === 'derived-from-reading'
                ? "from a reading of the person's words they have not confirmed — recorded as a reading, " +
                    "not as the person's"
                : "from the window the person's own words gave — recorded as the person's";
    const wider = f.wider === 'reads-more'
        ? "; the tool's form could not hold that window exactly, so the value reads a wider one — " +
            'recorded as wider than asked'
        : f.wider === 'tool-trims'
            ? "; the tool's form could not hold that window exactly, so the value reads a wider one, " +
                'and the tool declares that it drops the rows outside the asked window'
            : '';
    return f.hidden
        ? `\n\n[${f.argument} was not in the ${toolName} call this result answers; the call ran with ` +
            `a value ${whose} (the value is hidden by the tool's view)${wider}.]`
        : `\n\n[${f.argument} was not in the ${toolName} call this result answers; the call ran with ` +
            `${printedFill(f.value)}, ${whose}${wider}.]`;
}
function assumedClause(toolName, f) {
    return f.hidden
        ? `\n\n[${f.argument} was not in the ${toolName} call this result answers; the call ran ` +
            "with the value the tool's rule assumes (the value is hidden by the tool's view) — " +
            "recorded as assumed, not as the person's.]"
        : `\n\n[${f.argument} was not in the ${toolName} call this result answers; the call ran ` +
            `with ${printedFill(f.value)}, the value the tool's rule assumes — recorded as ` +
            "assumed, not as the person's.]";
}
/**
 * The clause an answered note gains under declared sources: a later call may
 * name the person's answer as its source — `turn`, checked against the
 * ledger's `answered` rows (`checks.ts` · `checkTurn`). A permission, never a
 * promise: the claim is still checked. In the paid step-5 run the model sent
 * an answered period again on the next turn with no `from` entry (and so was
 * asked again) or left it out (and so was asked again): nothing it was served
 * said an earlier ANSWER was a source it could name.
 */
exports.ANSWERED_SOURCE_CLAUSE = "; a later call may cite that answer in `_findings.from` with source 'turn'";
// LENS · tool-result · persistent-history
// reads: the call's answered fill (the batch ask's answer bound to this toolCallId), kept only where the
//        call RAN with it (`dispatch.ts` · `fillsThatRan`), the value the call had carried, if any, and
//        whether declared sources are armed (the `turn` clause)
// law: may omit, never deny; past tense, naming the call this result answers; the `turn` clause is a
// permission ("may cite"), never an outcome — the claim is checked like any other.
function answeredClause(toolName, f, sources) {
    const before = f.carried === undefined
        ? 'the call had left it out'
        : f.hidden
            ? 'the call had carried a value of its own'
            : `the call had carried ${printedValue(f.carried)}`;
    // A hidden answer is on the record as 'REDACTED' — a `turn` claim for it cannot be checked
    // (`checks.ts` · `checkTurn`: `uncheckable`), so its clause offers none.
    return f.hidden
        ? `\n\n[${f.argument} in the ${toolName} call this result answers was chosen by the person ` +
            `when asked (the value is hidden by the tool's view; ${before}).]`
        : `\n\n[${f.argument} = ${printedFill(f.value)} in the ${toolName} call this result answers ` +
            `was chosen by the person when asked (${before})${sources ? exports.ANSWERED_SOURCE_CLAUSE : ''}.]`;
}
// ─── The refusals ───────────────────────────────────────────────────────
// LENS · tool-result · persistent-history
// reads: the dispatch re-read of the tool's rules (`tools/declare.ts` · `rulesOf`) — the assert's own sentence
// law: may omit, never deny; every clause anchored to the call it was composed on.
/** The result a call reads when its tool's rules could not be read at dispatch. */
function unreadableRulesRefusal(toolName, reason) {
    const why = reason.replace(/^defineTool\('[^']*'\):\s*/, '');
    return `${toolName} was not run on that call: its argument rules could not be read (${why}).`;
}
exports.unreadableRulesRefusal = unreadableRulesRefusal;
// LENS · tool-result · persistent-history
// reads: nothing but the tool's declaration — the agent was built without the inputs layer
// law: may omit, never deny; every clause anchored to the call it was composed on.
/**
 * The result a call reads when its tool declares argument rules and the agent
 * was built without the inputs layer (a ToolProvider served the tool, and the
 * build could not see it) — refused rather than run unruled, because
 * configured-and-inert looks exactly like configured-and-working.
 */
function unmountedRulesRefusal(toolName) {
    return `${toolName} was not run on that call: it declares argument rules this agent was not built to apply.`;
}
exports.unmountedRulesRefusal = unmountedRulesRefusal;
// LENS · tool-result · persistent-history
// reads: the batch ask's settled state — the arguments whose answers never fit, and the rule they failed
// law: may omit, never deny; every clause anchored to the call it was composed on.
/**
 * The result a call reads when the person was asked for a ruled argument
 * `MAX_ASK_ROUNDS` times and no answer fitted what the tool accepts — the call
 * does not run. `expected` is the property schema's own expectation (never the
 * person's answer).
 */
function unansweredRefusal(toolName, unanswered) {
    const names = unanswered.map((u) => u.argument).join(', ');
    const rules = unanswered.flatMap((u) => u.expected !== undefined ? [`${u.argument}: ${u.expected}`] : []);
    return (`${toolName} was not run on that call: the person's answers for ${names} did not fit what ` +
        `the tool accepts${rules.length > 0 ? ` (${rules.join('; ')})` : ''}.`);
}
exports.unansweredRefusal = unansweredRefusal;
// LENS · tool-result · persistent-history
// reads: nothing but the fact that this batch already paused once, for the library's own ask
// law: may omit, never deny; past tense, anchored to the call; names no destination.
/**
 * The result a call reads when it needed a person — its check-in consent gate
 * tripped, or the tool itself asked to pause — in a batch that had ALREADY
 * paused once, to ask the person for argument values: a resumed batch has no
 * second pause to give (at most one human question per resume). The call did
 * not finish; nothing about it is decided for the person.
 */
function secondPauseRefusal(toolName, why) {
    const need = why === 'check-in'
        ? 'its check-in consent gate needed a person’s approval for those arguments'
        : 'the tool asked to pause for a person';
    return (`${toolName} was not run to completion on that call: ${need}, and this batch had already ` +
        'paused once — to ask the person for argument values — so there was no second pause to ' +
        'ask with.');
}
exports.secondPauseRefusal = secondPauseRefusal;
// LENS · tool-result · persistent-history
// reads: the call's `call-window` decision (`time/bind.ts` · `callWindowOf`: the window it asked
//        for against the tool's DECLARED facts — `direction`, `retention`, `maxRange`, a `day`-only form,
//        a sent wall time the zone skips, a person's window no declared form can read) and those facts'
//        own spellings
// law: may omit, never deny; past tense, anchored to the call; prints no window value (it may be the
//      person's) — only the tool's declared facts and an argument name; says what the model may do and
//      promises no outcome.
/**
 * The result a call reads when it was refused BEFORE DISPATCH on its window
 * (the time layer, step T5b — time design § 7.2): the window breaks one of the
 * tool's declared facts, spans days for a tool that reads one day per call, a
 * sent wall time is one the zone's clocks skip, or no form the tool declares
 * can read the person's window, exactly or wider (`no-form-holds` — the tool's
 * own default never stands in for it). Splitting a window into several calls
 * is the model's choice, never the library's.
 *
 * @example
 * ```ts
 * timeRefusal('search_logs', 'time-future', { direction: 'past' });
 * // 'search_logs was not run on that call: the window it asked for had not happened yet, and the
 * //  tool declares that its source holds only the past.'
 * ```
 */
function timeRefusal(toolName, refusal, facts, argument) {
    return `${toolName} was not run on that call: ${refusalReason(refusal, facts, argument)}.`;
}
exports.timeRefusal = timeRefusal;
/**
 * Why a window was refused before dispatch, in the tool's declared facts and
 * an argument name only — never the window's value. ONE text per code: the
 * refused call's result ({@link timeRefusal}) and the served line's
 * conclusion (`refusedSentence`) both say it.
 */
function refusalReason(refusal, facts, argument) {
    switch (refusal) {
        case 'time-future':
            return ('the window it asked for had not happened yet, and the tool declares that its source ' +
                'holds only the past');
        case 'time-past':
            return ('the window it asked for had already ended, and the tool declares that its source ' +
                'holds only the future');
        case 'beyond-retention':
            return ('the window it asked for was wholly older than the oldest data the tool declares its ' +
                `source keeps (${facts.retention ?? 'its retention'})`);
        case 'over-max-range':
            return ('the window it asked for was wider than the tool declares it reads at once ' +
                `(maxRange ${facts.maxRange ?? 'undeclared'}); narrower windows, one call each, may be ` +
                'proposed instead');
        case 'multi-day':
            return ('the window it asked for spanned more than one calendar day, and the tool reads one day ' +
                'per call; one call per day may be proposed instead');
        case 'dst-gap':
            return (`the wall time sent for ${argument ?? 'its period'} does not exist in the tool's zone — ` +
                'the clocks skip it at a daylight-saving change');
        case 'no-form-holds':
            return ('no period form the tool declares can read the window it asked for, exactly or by ' +
                'reading a wider one' +
                (facts.maxRange !== undefined
                    ? `, within the most the tool declares it reads at once (maxRange ${facts.maxRange})`
                    : ''));
    }
}
// LENS · tool-result · persistent-history
// reads: the tool's name and the NAMES of the arguments whose answers the refused call carried
//        (`kept.ts` keeps their values)
// law: past tense, anchored to the call it was composed on, so a later call of the turn re-reading it
// (after the kept answer was used) still reads a true sentence; says what the model may do and promises
// no outcome (a call proposed again may still be denied, refused, paused or asked); prints no value.
/**
 * The clause a refusal by the one-question law gains when the refused call
 * carried the person's answers: they were KEPT for the tool's next call that
 * leaves those arguments out (`kept.ts`), so the model may propose the call
 * again without them. Empty when the call carried none.
 *
 * @example
 * ```ts
 * keptAnswersNote('purge_logs', ['window']);
 * // " The person's answer for window was kept for the next purge_logs call that leaves it out,
 * //   so the call may be proposed again without window."
 * ```
 */
function keptAnswersNote(toolName, argumentNames) {
    if (argumentNames.length === 0)
        return '';
    if (argumentNames.length === 1) {
        const [name] = argumentNames;
        return (` The person's answer for ${name} was kept for the next ${toolName} call that leaves ` +
            `it out, so the call may be proposed again without ${name}.`);
    }
    const names = `${argumentNames.slice(0, -1).join(', ')} and ${argumentNames.at(-1)}`;
    return (` The person's answers for ${names} were kept for the next ${toolName} call that leaves ` +
        'them out, so the call may be proposed again without them.');
}
exports.keptAnswersNote = keptAnswersNote;
/**
 * The "Assumed" block: one line per distinct (tool, argument, value) of this
 * turn's `default` rows, in the order the rows were filed. Composed by the
 * framework from the rows — the model does not write it, so it cannot drop
 * it. Empty string when there is nothing assumed.
 */
function assumedBlock(lines) {
    const seen = new Set();
    const printed = [];
    for (const line of lines) {
        const key = JSON.stringify([line.toolName, line.argument, line.hidden ? '' : line.value]);
        if (seen.has(key))
            continue;
        seen.add(key);
        printed.push(line.hidden
            ? `- ${line.argument} (its value is hidden by the tool's view) (${line.toolName})`
            : `- ${line.argument} = ${JSON.stringify(line.value)} (${line.toolName})`);
    }
    return printed.length === 0 ? '' : `${answer_js_1.ASSUMED_BLOCK_HEADING}\n${printed.join('\n')}`;
}
exports.assumedBlock = assumedBlock;
/**
 * The "Assumed" block for THIS turn, read off the record: every `default`
 * argument row of `turn`, in the order it was filed, less each row a
 * before-tool rewrite superseded (its call ran with the rewrite's value, not
 * the one the row names). `readDecisions` hands over `middlewareDecisions`; it
 * is asked only when a row exists (a run with nothing assumed never reads the
 * key), and is absent on an agent with no before-tool chain, whose record can
 * hold no tool rewrite. `''` when nothing is assumed. The final branch's armed
 * variant (`stages/prepareFinal.ts` · `prepareFinalWithLimitsAndAssumedStage`)
 * calls it through `import()`.
 *
 * @example
 * ```ts
 * assumedBlockOf(state.findingsLedger ?? [], state.turnNumber, undefined);
 * // "Assumed (a tool's rule, not your words):\n- window = \"2h\" (search_logs)"
 * ```
 */
function assumedBlockOf(ledger, turn, readDecisions) {
    return assumedBlock(assumedLinesFor(ledger, turn, readDecisions));
}
exports.assumedBlockOf = assumedBlockOf;
// FOLD · the one reading of "what was assumed this turn"
// consumers read this and never re-derive it: the prose "Assumed" block (`assumedBlockOf`, above),
// a TYPED answer's limits as data (`stages/answerCoverage.ts` · `withAnswerCoverage` — the
// `assumed` list on `answerCoverage`) and the answer layer's standing line
// (`assessment/stage.ts` · `assessAnswerStage`). Same rows, same view, same order — so the block,
// the data and the line cannot disagree about which values were assumed.
/**
 * This turn's assumed values, as the "Assumed" block's lines: every `default`
 * argument row of `turn`, in the order it was filed, less each row a
 * before-tool rewrite superseded (its call ran with the rewrite's value, not
 * the one the row names). `readDecisions` hands over `middlewareDecisions`; it
 * is asked only when a row exists, and is absent on an agent with no
 * before-tool chain, whose record can hold no tool rewrite. `[]` when nothing
 * is assumed. The block prints each distinct (tool, argument, value) once.
 *
 * @example
 * ```ts
 * assumedLinesFor(state.findingsLedger ?? [], state.turnNumber, undefined);
 * // [{ toolName: 'search_logs', argument: 'window', value: '2h', hidden: false }]
 * ```
 */
function assumedLinesFor(ledger, turn, readDecisions) {
    const rows = defaultRowsOf(ledger, turn);
    const rewrites = readDecisions !== undefined && rows.length > 0
        ? (0, rewrites_js_1.argumentRewritesOf)(readDecisions())
        : undefined;
    return assumedLinesOf(rows, rewrites);
}
exports.assumedLinesFor = assumedLinesFor;
const isArgumentRow = (row) => row.kind === 'argument';
/** This turn's `default` argument rows, in the order they were filed. */
function defaultRowsOf(ledger, turn) {
    const rows = [];
    for (const row of ledger) {
        if (isArgumentRow(row) && row.turn === turn && row.source === 'default')
            rows.push(row);
    }
    return rows;
}
/**
 * The rows as the "Assumed" block names them — every row a before-tool
 * rewrite superseded left out: its call ran with the rewrite's value, not
 * the one the row names (`middleware/rewrites.ts` · `argumentRewritesOf`).
 */
function assumedLinesOf(rows, rewrites) {
    const lines = [];
    for (const row of rows) {
        if (rewrites?.get(row.toolCallId)?.has(row.argument) === true)
            continue;
        const value = row.value ?? '';
        lines.push({
            toolName: row.toolName,
            argument: row.argument,
            value,
            hidden: value === rows_js_1.HIDDEN_VALUE,
        });
    }
    return lines;
}
//# sourceMappingURL=serve.js.map