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,093 lines • 55.9 kB
"use strict";
/**
 * arguments/ask — the ONE typed ask per batch for the values `ask` rules need:
 * its fields, its declaration, the binding of the person's answer, the re-ask.
 *
 * Pattern: Walker, pure. Every function here is a function of what it is
 *          handed — the batch's calls, the rules of the implementation that
 *          will run (`ToolOf`), the ask's own state — and nothing reads a
 *          clock, a model or scope. The stage glue (`stages/argumentAsk.ts`)
 *          reads and writes scope and raises the pause.
 * Role:    core/ layer leaf of the inputs layer (honesty layer 2). Imports
 *          nothing from `findings/` (the folder's one-way law).
 * Emits:   N/A.
 *
 * ## One ask for the whole batch, before anything in it runs
 *
 * The layer's Resolve stage names, per call, the `ask` arguments it left out
 * (`ArgumentResolution.ask`). They become ONE typed ask (`tools/inputRequest.ts`,
 * the door `requestInput` uses): one field per distinct (tool, argument), bound
 * to every call of the batch that needs it. Two PERIOD arguments of different
 * tools share one field when their spellings convert (`lookback` ↔
 * `signed-lookback`) and their choices are the same periods — asking twice for
 * one period would ask the person what they already answered. An `iso-range`
 * never merges.
 *
 * - **Field ids are positional** (`f1`, `f2`, …) per declaration — never a
 *   joined `tool.argument` string, which two pairs could spell alike (the
 *   injective-key law). The (tool, argument, calls) behind each id rides the
 *   declaration's `context` under the library's reserved key.
 * - **A value the model READ into the person's words** (declared sources: the
 *   quote was found in the person's messages, the value is not in it) is asked
 *   too, and its field's `context` entry carries those words as `quoted` —
 *   the person's own words, never the model's value, and never while a tool
 *   in reach can hide arguments (`resolve.ts` · `quotesMayShow`).
 * - **A fixed library question.** Each author's question is its field's
 *   `description`; joined questions could break the 4096-character bound at
 *   pause time although each passed it at definition.
 * - **Nothing the model proposed rides the ask** — no `supplied`, no value, no
 *   choice the model wrote.
 * - **More than 32 fields** (the typed ask's limit) go in rounds of 32.
 *
 * ## The answer, re-checked, and a bounded re-ask
 *
 * The typed ask checks type and enum only, so an integer asked as a number can
 * come back `2.5`, and a string can break its `pattern`. Each answer is judged
 * against the PROPERTY's own schema of every argument it binds to, read off the
 * implementation that will run, through the one validator's honest subset
 * (`toolArgsValidation.ts` · `validatePropertyValue`: type, integer, enum,
 * `pattern`, `minLength`, `maxLength` — numeric `minimum` / `maximum` are NOT
 * judged, there or anywhere). One that fails is not bound: its rows say
 * `asked: 'invalid-answer'` and the field is asked again with a second fixed
 * question — at most `MAX_ASK_ROUNDS` times per field; after that, the calls
 * that needed it are refused.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.settledCalls = exports.WINDOW_ZONE_EXPECTATION = exports.WINDOW_FORM_EXPECTATION = exports.bindAnswer = exports.factExpectation = exports.checkAnswer = exports.memberValue = exports.readAskAnswer = exports.isArgumentAsk = exports.withWaiting = exports.argumentAskDeclaration = exports.judgeAskContextHook = exports.nextAskRound = exports.initialAskState = exports.planAskFields = exports.periodsShareField = exports.convertSpelling = exports.ARGUMENT_REASK_QUESTION = exports.ARGUMENT_ASK_QUESTION = exports.ARGUMENT_ASK_ID = exports.ASK_CONTEXT_CHARS = exports.MAX_ASK_FIELDS = exports.MAX_ASK_ROUNDS = exports.ASK_CONTEXT_KEY = exports.ARGUMENT_ASK_KIND = void 0;
const inputRequest_js_1 = require("../../../tools/inputRequest.js");
const convert_js_1 = require("../../../time/convert.js");
const windows_js_1 = require("../../../time/windows.js");
const rowsBuild_js_1 = require("../../../time/rowsBuild.js");
const instant_js_1 = require("../../../time/instant.js");
const range_js_1 = require("../../../time/range.js");
const resolve_js_1 = require("../../../time/resolve.js");
const readingAsk_js_1 = require("../../../time/readingAsk.js");
const rows_js_1 = require("../../../time/rows.js");
const zone_js_1 = require("../../../time/zone.js");
const toolShownArgs_js_1 = require("../../toolShownArgs.js");
const toolArgsValidation_js_1 = require("../../../tools/toolArgsValidation.js");
const declare_js_1 = require("../../../tools/declare.js");
Object.defineProperty(exports, "convertSpelling", { enumerable: true, get: function () { return declare_js_1.convertSpelling; } });
const askMarker_js_1 = require("./askMarker.js");
Object.defineProperty(exports, "ARGUMENT_ASK_KIND", { enumerable: true, get: function () { return askMarker_js_1.ARGUMENT_ASK_KIND; } });
Object.defineProperty(exports, "ASK_CONTEXT_KEY", { enumerable: true, get: function () { return askMarker_js_1.ASK_CONTEXT_KEY; } });
const declare_js_2 = require("../../../tools/declare.js");
const rows_js_2 = require("./rows.js");
// ─── The bounds and the fixed words ─────────────────────────────────────
/** How many times one field may be put to the person before its calls are refused. */
exports.MAX_ASK_ROUNDS = 3;
/** Fields per declaration — the typed ask's own limit (`tools/inputRequest.ts`). */
exports.MAX_ASK_FIELDS = 32;
/** The declaration's `context` bound — the typed ask's own. */
exports.ASK_CONTEXT_CHARS = 16384;
/** The declaration id — the author's reusable id; each round is stamped with its own `requestId`. */
exports.ARGUMENT_ASK_ID = 'agentfootprint.arguments';
// LENS · person-facing · the typed ask's question
// reads: nothing — one fixed sentence; each field's description is its author's question
// law: says what is needed and why the fields exist; promises nothing a later path can break.
/** The question of a first ask. Each field's `description` is its author's question. */
exports.ARGUMENT_ASK_QUESTION = 'Before the next step can run, a few values are needed. Each field says what it is for.';
// LENS · person-facing · the typed ask's question
// reads: nothing — one fixed sentence
// law: says what happened to the earlier answer, in the past tense, and what is asked now.
/** The question of a re-ask, after an answer that did not fit the tool's own schema. */
exports.ARGUMENT_REASK_QUESTION = 'One answer did not fit what the tool accepts. Please answer that field again.';
/**
 * Why one member tool cannot read a window the ask would put into it — the
 * one owner's answer (`time/convert.ts` · `convertForTool`: a fact it
 * breaks, `multi-day`, or `no-form-holds`) — or `undefined` when it can. A
 * tool whose rules cannot be read has nothing to say here.
 */
function unreadableBy(toolName, range, zone, toolOf, time) {
    const rules = (0, declare_js_1.rulesOf)(toolOf(toolName));
    if (rules === undefined || (0, declare_js_1.isRefused)(rules))
        return undefined;
    const facts = (0, declare_js_1.periodFactsOf)(rules.period);
    const at = zone ?? time.zone;
    if (at === undefined) {
        // No form chosen yet: the tool's facts, `maxRange` the widest any form reads (`toolFacts`).
        const judged = (0, convert_js_1.toolFacts)((0, declare_js_1.periodFormsOf)(rules.period), facts);
        const problem = (0, convert_js_1.periodFactProblem)(range, judged, time.now);
        return problem === undefined ? undefined : { refused: problem, facts: judged };
    }
    const read = (0, convert_js_1.convertForTool)({ range }, (0, declare_js_1.periodFormsOf)(rules.period), facts, {
        now: time.now,
        zone: at,
        ...(time.appZone !== undefined && { appZone: time.appZone }),
        granularityMs: (0, convert_js_1.granularityMsOf)(facts),
    });
    return 'refused' in read
        ? { refused: read.refused, facts: (0, convert_js_1.toolFacts)((0, declare_js_1.periodFormsOf)(rules.period), facts) }
        : undefined;
}
/**
 * The indexes of the choices EVERY member tool can read (§ 6.3: "some
 * readings inside the tool's `direction` → the ones inside as choices" — and,
 * by the same law, a reading no form of the tool can hold is not offered) —
 * `undefined` when none is left (several tools that disagree: every choice
 * is offered, and the answer is judged against each tool).
 */
function allowedChoices(choices, zones, members, toolOf, time) {
    const names = [...new Set(members.map((m) => m.toolName))];
    const kept = choices.flatMap((value, i) => {
        const range = (0, range_js_1.parseRange)(value);
        if (range === undefined)
            return [];
        const zone = zones?.[i];
        return names.every((name) => unreadableBy(name, range, zone, toolOf, time) === undefined)
            ? [i]
            : [];
    });
    return kept.length > 0 ? kept : undefined;
}
/** A window field's choices narrowed to `kept` (its labels and zones with them). */
function narrowed(field, kept) {
    const window = field.window;
    if (kept === undefined || field.choices === undefined || window === undefined)
        return field;
    const pick = (list) => list === undefined ? undefined : kept.map((i) => list[i]);
    const labels = pick(window.labels);
    const zones = pick(window.zones);
    return {
        ...field,
        choices: pick(field.choices),
        window: {
            ...window,
            ...(labels !== undefined && { labels }),
            ...(zones !== undefined && { zones }),
        },
    };
}
/** The arguments of every period form a tool declares — what a window fills. */
function formArgumentsOfTool(toolOf, toolName) {
    const rules = (0, declare_js_1.rulesOf)(toolOf(toolName));
    if (rules === undefined || (0, declare_js_1.isRefused)(rules))
        return new Set();
    return new Set((0, declare_js_1.periodFormsOf)(rules.period).flatMap((f) => (0, convert_js_1.formArguments)(f).map((a) => a.argument)));
}
const RULED_ASK_TYPES = {
    string: 'string',
    number: 'number',
    integer: 'number',
    boolean: 'boolean',
};
/** One ruled argument's `ask` rule, read off the implementation that will answer the name. */
function askRuleOf(toolOf, toolName, argument) {
    const rules = (0, declare_js_1.rulesOf)(toolOf(toolName));
    if (rules === undefined || (0, declare_js_1.isRefused)(rules))
        return undefined;
    const rule = rules.ruled.find((r) => r.argument === argument);
    if (rule === undefined || rule.ask === undefined)
        return undefined;
    const spelling = rule.period === true ? rules.period?.spelling : undefined;
    return { rule, ...(spelling !== undefined && { spelling }) };
}
const CONVERTIBLE = ['lookback', 'signed-lookback'];
/**
 * Whether the period field `b` may share `a`'s field: both declare a spelling
 * the library converts, both declare choices, and the choices are the SAME
 * periods under the conversion — so every choice the person can pick is a
 * valid choice of each argument, whichever member's spelling it is read in.
 */
function periodsShareField(a, b) {
    if (a.spelling === undefined || b.spelling === undefined)
        return false;
    if (!CONVERTIBLE.includes(a.spelling) || !CONVERTIBLE.includes(b.spelling))
        return false;
    if (a.choices === undefined || b.choices === undefined)
        return false;
    if (a.choices.length !== b.choices.length)
        return false;
    const bSet = new Set(b.choices);
    return a.choices.every((choice) => {
        const converted = (0, declare_js_1.convertSpelling)(choice, a.spelling, b.spelling);
        return converted !== undefined && bSet.has(converted);
    });
}
exports.periodsShareField = periodsShareField;
function pendingFieldOf(toolName, argument, read, toolCallId) {
    const ask = read.rule.ask;
    return {
        field: {
            type: RULED_ASK_TYPES[read.rule.type],
            question: ask.question,
            ...(ask.choices !== undefined && { choices: ask.choices }),
            ...(read.spelling !== undefined && { spelling: read.spelling }),
        },
        member: {
            toolName,
            argument,
            ...(read.rule.period === true && { period: true }),
            ...(read.spelling !== undefined && { spelling: read.spelling }),
        },
        ids: [toolCallId],
    };
}
/** Each field merged into an earlier one it shares a period with (`periodsShareField`). */
function mergeSharedPeriods(fields) {
    const merged = [];
    for (const field of fields) {
        const host = merged.findIndex((f) => periodsShareField(f, field));
        if (host < 0)
            merged.push(field);
        else
            merged[host] = { ...merged[host], members: [...merged[host].members, ...field.members] };
    }
    return merged;
}
/**
 * The batch ask's fields: one per distinct (tool, argument) the calls left out,
 * in first-seen batch order, each bound to every call that needs it; then a
 * period field merged into an earlier one it shares (`periodsShareField`). An
 * argument whose rule cannot be read here is left out — the dispatch re-read
 * refuses its call.
 */
function planAskFields(asks, calls, toolOf, windowPlan) {
    const nameOf = new Map(calls.map((c) => [c.id, c.name]));
    // Keyed by tool, then argument — a map of maps, never a joined key.
    const byTool = new Map();
    const order = [];
    // The lazy word-driven ask: a call's PERIOD arguments join the one window field.
    const windowMembers = new Map();
    for (const entry of asks) {
        const toolName = nameOf.get(entry.toolCallId);
        if (toolName === undefined)
            continue;
        const forTool = byTool.get(toolName) ?? new Map();
        byTool.set(toolName, forTool);
        const periodArguments = windowPlan !== undefined && entry.window === true
            ? formArgumentsOfTool(toolOf, toolName)
            : new Set();
        for (const argument of entry.ask) {
            if (periodArguments.has(argument)) {
                const forWindow = windowMembers.get(toolName) ?? new Map();
                windowMembers.set(toolName, forWindow);
                const ids = forWindow.get(argument) ?? [];
                if (!ids.includes(entry.toolCallId))
                    ids.push(entry.toolCallId);
                forWindow.set(argument, ids);
                continue;
            }
            const quote = entry.quoted?.find((q) => q.argument === argument)?.quote;
            const seen = forTool.get(argument);
            if (seen !== undefined) {
                if (!seen.ids.includes(entry.toolCallId))
                    seen.ids.push(entry.toolCallId);
                if (seen.quoted === undefined && quote !== undefined)
                    seen.quoted = quote;
                continue;
            }
            const read = askRuleOf(toolOf, toolName, argument);
            if (read === undefined)
                continue;
            const pending = pendingFieldOf(toolName, argument, read, entry.toolCallId);
            if (quote !== undefined)
                pending.quoted = quote;
            forTool.set(argument, pending);
            order.push(pending);
        }
    }
    const fields = mergeSharedPeriods(order.map((p) => ({
        ...p.field,
        members: [
            {
                ...p.member,
                toolCallIds: [...p.ids],
                ...(p.quoted !== undefined && { quoted: p.quoted }),
            },
        ],
    })));
    if (windowPlan === undefined || windowMembers.size === 0)
        return fields;
    const window = windowFieldOf(windowPlan, windowMembers);
    const kept = window.choices === undefined
        ? undefined
        : allowedChoices(window.choices, windowPlan.zones, window.members, toolOf, windowPlan);
    return [narrowed(window, kept), ...fields];
}
exports.planAskFields = planAskFields;
/** The ONE window field: the open mention's question, bound to every call's period arguments. */
function windowFieldOf(plan, members) {
    return {
        type: 'string',
        question: plan.question,
        ...(plan.choices !== undefined && { choices: plan.choices }),
        members: [...members].flatMap(([toolName, byArgument]) => [...byArgument].map(([argument, ids]) => ({
            toolName,
            argument,
            period: true,
            toolCallIds: [...ids],
        }))),
        window: {
            format: plan.format,
            ...(plan.labels !== undefined && { labels: plan.labels }),
            ...(plan.zones !== undefined && { zones: plan.zones }),
            mention: plan.mention,
        },
    };
}
// ─── The rounds ─────────────────────────────────────────────────────────
/** A fresh state for a batch's fields: nothing asked yet. */
function initialAskState(iteration, fields) {
    return { iteration, fields, progress: fields.map(() => ({ rounds: 0 })) };
}
exports.initialAskState = initialAskState;
/**
 * The next declaration's fields: every field to ask AGAIN (an answer that did
 * not fit) first, on their own, under the re-ask question; else the next
 * never-asked fields, up to `MAX_ASK_FIELDS`. `undefined` when every field has
 * settled — bound or exhausted.
 */
function nextAskRound(state) {
    const again = state.progress.flatMap((p, i) => (p.invalid === true ? [i] : []));
    if (again.length > 0)
        return { fieldIndexes: again.slice(0, exports.MAX_ASK_FIELDS), reask: true };
    const fresh = state.progress.flatMap((p, i) => (p.rounds === 0 || p.next === true ? [i] : []));
    if (fresh.length > 0)
        return { fieldIndexes: fresh.slice(0, exports.MAX_ASK_FIELDS), reask: false };
    return undefined;
}
exports.nextAskRound = nextAskRound;
const isPlainObject = (value) => typeof value === 'object' &&
    value !== null &&
    !Array.isArray(value) &&
    (Object.getPrototypeOf(value) === Object.prototype || Object.getPrototypeOf(value) === null);
/** JSON data all the way down — finite numbers, no functions, no cycles (depth-bounded). */
function isJsonValue(value, depth = 0) {
    if (depth > 32)
        return false;
    if (value === null || typeof value === 'string' || typeof value === 'boolean')
        return true;
    if (typeof value === 'number')
        return Number.isFinite(value);
    if (Array.isArray(value))
        return value.every((v) => isJsonValue(v, depth + 1));
    return isPlainObject(value) && Object.values(value).every((v) => isJsonValue(v, depth + 1));
}
/**
 * The host's own context for the ask (`AgentOptions.argumentAskContext`),
 * judged: a plain object that does not use the library's reserved key. Refused
 * — a `TypeError` naming the hook — rather than repaired or dropped.
 */
function judgeAskContextHook(value) {
    if (!isPlainObject(value) || !isJsonValue(value)) {
        throw new TypeError('argumentAskContext: must return a plain JSON object — its keys are spread into the ' +
            "library's ask `context` beside the reserved key.");
    }
    if (Object.prototype.hasOwnProperty.call(value, askMarker_js_1.ASK_CONTEXT_KEY)) {
        throw new TypeError(`argumentAskContext: returned the reserved key '${askMarker_js_1.ASK_CONTEXT_KEY}' — it is the library's ` +
            'marker for its own ask, and a host value there would be read as the library’s. ' +
            'Put the host state under a key of its own.');
    }
    return value;
}
exports.judgeAskContextHook = judgeAskContextHook;
function contextFieldOf(id, field, callLimit) {
    const [first, ...rest] = field.members;
    const calls = first.toolCallIds.slice(0, callLimit);
    const more = first.toolCallIds.length - calls.length;
    const quoted = field.members.find((m) => m.quoted !== undefined)?.quoted;
    return {
        id,
        tool: first.toolName,
        argument: first.argument,
        calls,
        ...(more > 0 && { moreCalls: more }),
        ...(quoted !== undefined && { quoted: (0, rows_js_2.shownQuote)(quoted) }),
        ...(rest.length > 0 && {
            sharedWith: rest.map((m) => ({
                tool: m.toolName,
                argument: m.argument,
                calls: m.toolCallIds.slice(0, callLimit),
            })),
        }),
    };
}
function contextOf(entries, host) {
    return { ...(host ?? {}), [askMarker_js_1.ASK_CONTEXT_KEY]: { ask: askMarker_js_1.ARGUMENT_ASK_KIND, fields: entries } };
}
const fits = (context) => JSON.stringify(context).length <= exports.ASK_CONTEXT_CHARS;
/**
 * The largest prefix of the round's fields whose reserved `context` fits the
 * bound (the rest go in the next round); when one field alone still does not
 * fit, it lists fewer of its calls and counts the rest (`moreCalls`).
 */
function fittedRound(state, wanted, host) {
    const contextFor = (list, limit) => contextOf(list.map((i, k) => contextFieldOf(`f${k + 1}`, state.fields[i], limit)), host);
    let indexes = [...wanted];
    while (indexes.length > 1 && !fits(contextFor(indexes, Number.POSITIVE_INFINITY))) {
        indexes = indexes.slice(0, -1);
    }
    let limit = Number.POSITIVE_INFINITY;
    if (!fits(contextFor(indexes, limit))) {
        limit = Math.max(...indexes.map((i) => state.fields[i].members[0].toolCallIds.length));
        while (limit > 1 && !fits(contextFor(indexes, limit)))
            limit -= 1;
    }
    return { indexes, context: contextFor(indexes, limit) };
}
/**
 * The typed ask for one round: the fixed question, one positional field per
 * field of the round, and the reserved `context` listing the (tool, argument,
 * calls) behind each id — within 16384 characters, so a round that would not
 * fit carries fewer fields (the rest go in the next round) and a single field
 * bound to more calls than fit lists the first ones and counts the rest
 * (`moreCalls`). Passes `validateInputDeclaration`.
 */
function argumentAskDeclaration(state, round, host) {
    if (host !== undefined && !fits(contextOf([], host))) {
        throw new TypeError(`argumentAskContext: the returned object alone exceeds the ask's ${exports.ASK_CONTEXT_CHARS}-` +
            'character context bound — keep the host state small (an id to look up, not the state).');
    }
    const { indexes, context } = fittedRound(state, round.fieldIndexes, host);
    if (host !== undefined && !fits(context)) {
        // One field listing one call still does not fit beside the host's object:
        // the host's state leaves no room for the library's own entry.
        throw new TypeError(`argumentAskContext: the returned object leaves no room for the library's own entry ` +
            `within the ask's ${exports.ASK_CONTEXT_CHARS}-character context bound — keep the host state ` +
            'small (an id to look up, not the state).');
    }
    const fields = indexes.map((i, k) => {
        const field = state.fields[i];
        return {
            id: `f${k + 1}`,
            type: field.type,
            required: true,
            description: field.question,
            ...(field.choices !== undefined && { enum: [...field.choices] }),
            // A window field is a TIME field: the door judges its answer (`time/ask.ts`).
            ...(field.window !== undefined && { format: field.window.format }),
            ...(field.window?.labels !== undefined && { labels: [...field.window.labels] }),
        };
    });
    return {
        declaration: {
            id: exports.ARGUMENT_ASK_ID,
            question: round.reask ? exports.ARGUMENT_REASK_QUESTION : exports.ARGUMENT_ASK_QUESTION,
            fields,
            context,
        },
        fieldIndexes: indexes,
    };
}
exports.argumentAskDeclaration = argumentAskDeclaration;
/** `state` with the round's fields counted as asked and the question out. */
function withWaiting(state, waiting) {
    const asked = new Set(waiting.fieldIndexes);
    return {
        ...state,
        progress: state.progress.map((p, i) => (asked.has(i) ? { rounds: p.rounds + 1 } : p)),
        waiting,
    };
}
exports.withWaiting = withWaiting;
/** Whether a stored library ask is the one this state is waiting on (the reserved marker). */
function isArgumentAsk(awaiting) {
    return (0, askMarker_js_1.isArgumentAskContext)(awaiting?.context);
}
exports.isArgumentAsk = isArgumentAsk;
// ─── The answer ─────────────────────────────────────────────────────────
/**
 * The person's answer to the ask now out, as field values by id. Accepts what
 * `Agent.resume` hands the run (`{ status: 'input_received', requestId, values }`,
 * already checked at its door) and a raw `InputResponse` (`{ requestId, values }`)
 * from a runner that resumes the executor directly — both judged by the SAME
 * validator (`tools/inputRequest.ts` · `applyInputResponse`): the request id, the
 * declared fields, their types and choices. Throws `InputRequestError` on a
 * reply to another request or a malformed one, and on a PARTIAL reply (only
 * `Agent.resume`'s door keeps a partial answer, without running anything); a
 * cancellation throws the door's `TypeError`.
 */
function readAskAnswer(waiting, input) {
    const reply = typeof input === 'object' &&
        input !== null &&
        input.status === 'input_received'
        ? {
            requestId: input.requestId,
            values: input.values,
        }
        : input;
    const answered = (0, inputRequest_js_1.applyInputResponse)(waiting.awaiting, reply);
    if ('cancel' in answered) {
        throw new TypeError('[input request] Cancel a hosted request through its host, or abandonPause() before starting another run.');
    }
    if (answered.missing.length > 0) {
        throw new inputRequest_js_1.InputRequestError(`the library's ask needs every field answered in one reply (missing: ${answered.missing.join(', ')}); ` + 'Agent.resume keeps a partial answer at its door without running anything');
    }
    return answered.supplied;
}
exports.readAskAnswer = readAskAnswer;
/** The property schema a member's argument is judged against, off the implementation that will run. */
function propertyOf(toolOf, member) {
    const properties = toolOf(member.toolName)?.schema.inputSchema?.properties;
    const property = typeof properties === 'object' && properties !== null
        ? properties[member.argument]
        : undefined;
    return typeof property === 'object' && property !== null
        ? property
        : undefined;
}
/** The answer in a member's own spelling — itself, unless the field is a shared period. */
function memberValue(field, member, answer) {
    if (field.spelling === undefined || member.spelling === undefined)
        return answer;
    return (0, declare_js_1.convertSpelling)(answer, field.spelling, member.spelling);
}
exports.memberValue = memberValue;
/**
 * Whether `answer` fits EVERY member's property schema (in the member's own
 * spelling) — the re-check the typed ask cannot do: an integer asked as a
 * number, a `pattern`, a string length (never a numeric bound — the one
 * validator ignores `minimum` / `maximum`). The rule is re-read through the
 * same resolver.
 * `expected` names what a misfit failed — the schema's expectation, never the
 * answer.
 */
function checkAnswer(field, answer, toolOf, time) {
    for (const member of field.members) {
        const value = memberValue(field, member, answer);
        if (value === undefined) {
            return { fits: false, expected: `a value spelled '${field.spelling ?? 'as declared'}'` };
        }
        const verdict = (0, toolArgsValidation_js_1.validatePropertyValue)(value, propertyOf(toolOf, member));
        if (!verdict.ok) {
            const expected = verdict.issues[0]?.expected;
            return { fits: false, ...(expected !== undefined && { expected }) };
        }
        const fact = time === undefined ? undefined : factBroken(toolOf, member, value, time);
        if (fact !== undefined)
            return { fits: false, expected: factExpectation(fact.problem, fact.facts) };
    }
    return { fits: true };
}
exports.checkAnswer = checkAnswer;
/**
 * The tool's declared fact an answer for its period argument breaks (the time
 * layer, step T5a — `time/convert.ts` · `periodFactProblem`): the answer,
 * read back through the single-argument form that names the argument, against
 * `direction`, `retention` and `maxRange` at the turn's clock. `undefined` for
 * a tool that declares none of the three, an argument no single-argument form
 * names (one bound of two), or an answer no form reads back.
 */
function factBroken(toolOf, member, value, time) {
    if (member.period !== true)
        return undefined;
    const rules = (0, declare_js_1.rulesOf)(toolOf(member.toolName));
    if (rules === undefined || (0, declare_js_1.isRefused)(rules))
        return undefined;
    const declared = (0, declare_js_1.periodFactsOf)(rules.period);
    const forms = (0, declare_js_1.periodFormsOf)(rules.period);
    if (!declaresFacts(declared, forms))
        return undefined;
    for (const form of forms) {
        if (form.kind === 'bounds' || form.kind === 'object' || form.argument !== member.argument)
            continue;
        const range = (0, convert_js_1.readBack)({ [member.argument]: value }, form, time);
        if (range === undefined)
            continue;
        // Read back through THIS form, so held to this form's own `maxRange` (`formFacts`).
        const facts = (0, convert_js_1.formFacts)(form, declared);
        const problem = (0, convert_js_1.periodFactProblem)(range, facts, time.now);
        return problem === undefined ? undefined : { problem, facts };
    }
    return undefined;
}
/** Whether a tool declares any fact a range can break — a period fact, or a form's own `maxRange`. */
function declaresFacts(facts, forms) {
    return (facts.direction !== undefined ||
        facts.retention !== undefined ||
        facts.maxRange !== undefined ||
        forms.some((f) => f.maxRange !== undefined));
}
/**
 * The period fields whose answers break a tool's fact only TOGETHER — one
 * bound of two (`start_time`, `end_time`) is no window, so {@link checkAnswer}
 * cannot judge it alone (`factBroken` reads single-argument forms only). Per
 * call, the answers this pass carries and the ones an earlier round bound are
 * written over the call's own arguments; every `bounds` / `object` form whose
 * arguments are then all present is read back (`convert.ts` · `readBack`) and
 * judged against `direction`, `retention` and `maxRange` at the turn's clock
 * (`periodFactProblem`). A pair that breaks one sends each of its fields
 * asked in this pass back to be asked again, naming the fact; a pair whose
 * bounds are all present yet name no window (the end given before the start)
 * goes back the same way, naming the window form — the same
 * bounded re-ask a single argument gets, so a future start and end the
 * person gave never reach a tool whose source holds only the past, with or
 * without the reader.
 */
function pairsBroken(state, values, byId, toolOf, time) {
    const broken = new Map();
    const waiting = state.waiting;
    if (time === undefined || waiting === undefined)
        return broken;
    const now = new Map(waiting.fieldIndexes.map((index, k) => [index, values[`f${k + 1}`]]));
    // Per call: its tool, and each period argument's answer with the field it came from.
    const perCall = new Map();
    state.fields.forEach((field, index) => {
        if (field.window !== undefined)
            return;
        const answer = now.has(index) ? now.get(index) : state.progress[index]?.answer;
        if (answer === undefined)
            return;
        for (const member of field.members) {
            if (member.period !== true)
                continue;
            const value = memberValue(field, member, answer);
            if (value === undefined)
                continue;
            for (const id of member.toolCallIds) {
                const entry = perCall.get(id) ?? { toolName: member.toolName, answers: new Map() };
                entry.answers.set(member.argument, { value, field: index });
                perCall.set(id, entry);
            }
        }
    });
    for (const [id, { toolName, answers }] of perCall) {
        const call = byId.get(id);
        const rules = (0, declare_js_1.rulesOf)(toolOf(toolName));
        if (call === undefined || rules === undefined || (0, declare_js_1.isRefused)(rules))
            continue;
        const declared = (0, declare_js_1.periodFactsOf)(rules.period);
        if (!declaresFacts(declared, (0, declare_js_1.periodFormsOf)(rules.period)))
            continue;
        const args = { ...call.args };
        for (const [argument, { value }] of answers)
            args[argument] = value;
        for (const form of (0, declare_js_1.periodFormsOf)(rules.period)) {
            if (form.kind !== 'bounds' && form.kind !== 'object')
                continue;
            // A zone argument the rule assumes is not in the call yet; `readBack` answers without one.
            const names = (0, convert_js_1.formArguments)(form).map((a) => a.argument);
            const bounds = (0, convert_js_1.formArguments)(form).filter((a) => a.role !== 'zone');
            const asked = names.filter((a) => answers.has(a));
            if (asked.length === 0 || bounds.some((a) => args[a.argument] === undefined))
                continue;
            const range = (0, convert_js_1.readBack)(args, form, time);
            // Every bound is present: no range means the pair names no window (a start not before its
            // end, a misspelled bound) — never "no problem", or it reaches the tool unjudged. Only a
            // zone still to come leaves the pair for the zone's own ask.
            const facts = (0, convert_js_1.formFacts)(form, declared);
            const expectation = range !== undefined
                ? pairExpectation((0, convert_js_1.periodFactProblem)(range, facts, time.now), facts)
                : (0, convert_js_1.readBackLacksZone)(args, form, time)
                    ? undefined
                    : exports.WINDOW_FORM_EXPECTATION;
            if (expectation === undefined)
                continue;
            for (const a of asked) {
                const field = answers.get(a).field;
                if (now.has(field) && !broken.has(field))
                    broken.set(field, expectation);
            }
        }
    }
    return broken;
}
/** A pair's fact problem in the refusal's words — `undefined` when the pair breaks none. */
function pairExpectation(problem, facts) {
    return problem === undefined ? undefined : factExpectation(problem, facts);
}
// LENS · tool-result · persistent-history (through `serve.ts` · `unansweredRefusal`)
// reads: the tool's declared fact the person's answers kept breaking, and its declared value
// law: names the rule, never the answer; a fact about the source, in the present tense of the source.
/**
 * What an answer for a period argument must be, in the words the refusal of
 * an exhausted ask names it (`serve.ts` · `unansweredRefusal`'s `expected`) —
 * the tool's own fact, never the person's answer.
 */
function factExpectation(problem, facts) {
    switch (problem) {
        case 'time-future':
            return 'a window that has already happened (the source holds only the past)';
        case 'time-past':
            return 'a window still to come (the source holds only the future)';
        case 'beyond-retention':
            return `a window inside what the source keeps (${facts.retention ?? 'its retention'})`;
        case 'over-max-range':
            return `a window no wider than ${facts.maxRange ?? 'the source reads at once'}`;
    }
}
exports.factExpectation = factExpectation;
/**
 * Bind the person's answer to the ask now out: each field whose value fits
 * every member's schema is bound (`answered` rows, one per (call, argument),
 * the value in the tool's own argument view, `free` for a free-text string
 * field); each that does not is marked to ask again (`invalid-answer` rows),
 * or — asked `MAX_ASK_ROUNDS` times already — exhausted. The question is no
 * longer out afterwards.
 */
function bindAnswer(state, values, calls, toolOf, stamp, time) {
    const waiting = state.waiting;
    if (waiting === undefined)
        return { state, rows: [] };
    const byId = new Map(calls.map((c) => [c.id, c]));
    const progress = [...state.progress];
    const rows = [];
    const pairs = pairsBroken(state, values, byId, toolOf, time);
    const fields = [...state.fields];
    waiting.fieldIndexes.forEach((index, k) => {
        const field = state.fields[index];
        const answer = values[`f${k + 1}`];
        const was = progress[index];
        if (field.window !== undefined && answer !== undefined) {
            const bound = bindWindowAnswer(field, answer, byId, toolOf, time);
            if ('followUp' in bound) {
                fields[index] = bound.followUp;
                progress[index] = { rounds: was.rounds, next: true };
                return;
            }
            if ('fills' in bound) {
                progress[index] = { rounds: was.rounds, answer, fills: bound.fills };
                rows.push(...windowAnsweredRows(field, bound.fills, byId, toolOf, stamp));
                // The person's window for the mention — the only door a window of words becomes theirs.
                rows.push((0, rows_js_1.timeAnswerRow)({ mention: field.window.mention, ...bound.answered }, stamp));
                // Each filled call's window, as it will run: the person's answer, exactly or WIDER (the
                // covering look-back's `sent` and `extra`) — the call's latest `call-window` row, so the
                // clock at dispatch, `ctx.time` and the result checks read the window the call carries.
                for (const [toolCallId, { toolName, decision }] of Object.entries(bound.filled)) {
                    rows.push((0, rowsBuild_js_1.callWindowRow)({ toolCallId, toolName }, decision, stamp));
                }
                return;
            }
            if ('outside' in bound) {
                progress[index] = { rounds: was.rounds, exhausted: true, expected: bound.outside };
                rows.push(...memberCalls(field).map((who) => (0, rows_js_2.askedRowOf)(who, stamp, 'invalid-answer')));
                return;
            }
            progress[index] =
                was.rounds >= exports.MAX_ASK_ROUNDS
                    ? { rounds: was.rounds, exhausted: true, expected: bound.expected }
                    : { rounds: was.rounds, invalid: true, expected: bound.expected };
            rows.push(...memberCalls(field).map((who) => (0, rows_js_2.askedRowOf)(who, stamp, 'invalid-answer')));
            return;
        }
        const alone = answer === undefined ? { fits: false } : checkAnswer(field, answer, toolOf, time);
        // An answer that fits alone can still break a fact with the other bound of its window.
        const pair = pairs.get(index);
        const check = alone.fits && pair !== undefined ? { fits: false, expected: pair } : alone;
        if (answer !== undefined && check.fits) {
            progress[index] = { rounds: was.rounds, answer };
            rows.push(...answeredRows(field, answer, byId, toolOf, stamp));
            return;
        }
        const expected = 'expected' in check && check.expected !== undefined ? { expected: check.expected } : {};
        progress[index] =
            was.rounds >= exports.MAX_ASK_ROUNDS
                ? { rounds: was.rounds, exhausted: true, ...expected }
                : { rounds: was.rounds, invalid: true, ...expected };
        rows.push(...memberCalls(field).map((who) => (0, rows_js_2.askedRowOf)(who, stamp, 'invalid-answer')));
    });
    const { waiting: _settled, ...rest } = state;
    void _settled;
    return { state: { ...rest, fields, progress }, rows };
}
exports.bindAnswer = bindAnswer;
// LENS · tool-result · persistent-history (through `serve.ts` · `unansweredRefusal`)
// reads: nothing — fixed expectations for a window answer a tool's period forms cannot take
// law: names the rule, never the answer.
/** What a window answer must be when no period form of a tool holds it. */
exports.WINDOW_FORM_EXPECTATION = "a window one of the tool's declared period forms can hold";
/** What a zone answer must be when the person's words name no time in it. */
exports.WINDOW_ZONE_EXPECTATION = 'a time zone in which the time the person wrote exists';
/**
 * Bind one answer to a window field: a `zone` answer re-reads the mention in
 * that zone and asks the follow-up question with the readings as choices —
 * each still a PROPOSAL to confirm (the owner's decision "Always confirm");
 * a `time-range` answer (a choice, or free entry the door already judged) is
 * converted into every member call's forms — exactly, else wider (the fill's
 * own rule) — after each tool's declared facts. A choice the ask offered is
 * `confirmed` (the person's click on the pre-filled reading); any other window
 * is `edited` — both are the person's answer.
 */
function bindWindowAnswer(field, answer, byId, toolOf, time) {
    const window = field.window;
    if (time === undefined)
        return { expected: exports.WINDOW_FORM_EXPECTATION };
    if (window.format === 'zone') {
        const reading = time.reading;
        if (reading === undefined || typeof answer !== 'string' || time.zone === undefined) {
            return { expected: exports.WINDOW_ZONE_EXPECTATION };
        }
        const row = reading.row;
        // The re-read stays a PROPOSAL: the zone was the person's, the window is still words. Under
        // an unknown clock zone (G15) the answer is the person's own zone: the words are read in it.
        const resolution = (0, resolve_js_1.resolveMention)((0, resolve_js_1.withZoneAnswered)(row.parses ?? [], answer, reading.policy), { now: time.now, zone: time.zoneUnknown === true && (0, zone_js_1.isZoneName)(answer) ? answer : time.zone }, row.reader, true, reading.policy);
        const choice = (0, resolve_js_1.chooseReading)(resolution, reading.policy, row.reader.kind, undefined, true);
        if (choice.by === 'open') {
            const ask = (0, readingAsk_js_1.timeAskOf)({ ...row, candidates: resolution.candidates, choice }, reading.messages);
            if (ask === undefined || ask.field.format !== 'time-range') {
                return { expected: exports.WINDOW_ZONE_EXPECTATION };
            }
            const zones = (ask.field.enum ?? []).map((value) => resolution.candidates.find((c) => (0, range_js_1.spellRange)(c.range) === value)?.zone ?? answer);
            const followUp = {
                ...field,
                question: ask.question,
                ...(ask.field.enum !== undefined && { choices: ask.field.enum }),
                window: {
                    format: 'time-range',
                    ...(ask.field.labels !== undefined && { labels: ask.field.labels }),
                    zones,
                    mention: window.mention,
                },
            };
            const choices = (ask.field.enum ?? []);
            const kept = allowedChoices(choices, zones, field.members, toolOf, time);
            if (kept === undefined && field.members.length > 0) {
                // A member tool can read no reading: nothing is asked (§ 6.3) — the reason.
                const outside = readingsOutside(choices, zones, field.members, toolOf, time);
                if (outside !== undefined)
                    return { outside };
            }
            return { followUp: narrowed(followUp, kept) };
        }
        return { expected: exports.WINDOW_ZONE_EXPECTATION };
    }
    const range = typeof answer === 'string' ? (0, range_js_1.parseRange)(answer) : undefined;
    if (range === undefined)
        return { expected: exports.WINDOW_FORM_EXPECTATION };
    const at = field.choices?.indexOf(answer) ?? -1;
    const zone = at >= 0 ? window.zones?.[at] ?? time.zone : editedZone(range, window.zones, time.zone);
    if (zone === undefined)
        return { expected: exports.WINDOW_FORM_EXPECTATION };
    const bound = windowFills(field, range, zone, byId, toolOf, time);
    if (!('fills' in bound))
        return bound;
    return {
        fills: bound.fills,
        filled: bound.filled,
        answered: { range, zone, how: at >= 0 ? 'confirmed' : 'edited' },
    };
}
/**
 * The zone a window the person TYPED is recorded in: the first of the
 * offered readings' zones — then the app's — whose offsets at both ends are
 * the ones typed; else the fixed-offset zone the typed offsets spell
 * (`Etc/GMT-1` for `+01:00`, `zone.ts` · `fixedOffsetZone`); else the app's.
 * A London day typed with `+01:00` under an app in Los Angeles is recorded
 * in Europe/London (the zone the mention named), never in the app's zone.
 */
function editedZone(range, offered, appZone) {
    const from = (0, instant_js_1.instantOf)(range.from, 'lenient');
    const to = (0, instant_js_1.instantOf)(range.to, 'lenient');
    if (from === undefined || to === undefined)
        return appZone;
    const shows = (zone) => (0, zone_js_1.offsetAt)(zone, from.ms) === from.offsetMinutes && (0, zone_js_1.offsetAt)(zone, to.ms) === to.offsetMinutes;
    const zones = [...(offered ?? []), ...(appZone !== undefined ? [appZone] : [])];
    const match = zones.find(shows);
    if (match !== undefined)
        return match;
    const fixed = from.offsetMinutes === to.offsetMinutes ? (0, zone_js_1.fixedOffsetZone)(from.offsetMinutes) : undefined;
    return fixed ?? appZone;
}
/** Why the first reading cannot be read, when ONE member tool can read no reading at all — the refusal's words. */
function readingsOutside(choices, zones, members, toolOf, time) {
    for (const name of new Set(members.map((m) => m.toolName))) {
        const reasons = choices.map((value, i) => {
            const range = (0, range_js_1.parseRange)(value);
            return range === undefined ? undefined : unreadableBy(name, range, zones[i], toolOf, time);
        });
        const first = reasons[0];
        if (first !== undefined && reasons.every((r) => r !== undefined)) {
            return refusalExpectation(first.refused, first.facts);
        }
    }
    return undefined;
}
/** What an answer must be, in the refusal's words, when a tool cannot read it: its fact, else a form that holds it. */
function refusalExpectation(refused, facts) {
    return refused === 'time-future' ||
        refused === 'time-past' ||
        refused === 'beyond-retention' ||
        refused === 'over-max-range'
        ? factExpectation(refused, facts)
        : exports.WINDOW_FORM_EXPECTATION;
}
/**
 * The window written into every member call's forms — the one owner's answer
 * per tool (`time/convert.ts` · `convertForTool`: exactly, else wider,
 * after the tool's facts) — or what it failed. Each fill also carries the
 * decision its `call-window` row records (`filled`, the person's ANSWERED
 * window, with the wider read's `sent` and `extra`).
 */
function windowFills(field, range, zone, byId, toolOf, time) {
    const fills = {};
    const filled = {};
    // The offered look-back, picked as offered, stays a look-back from now — the same window the
    // served line and a later call's fill convert (`windows.ts` · `windowToConvert`), so the value
    // this call is handed and the value the model is told are one spelling.
    const lookback = (0, windows_js_1.offeredLookback)(time.reading?.row, range);
    const answered = {
        source: 'answered',
        mention: field.window.mention,
        range,
        ...(lookback !== undefined && { lookback }),
        zone,
    };
    for (const member of field.members) {
        const rules = (0, declare_js_1.rulesOf)(toolOf(member.toolName));
        if (rules === undefined || (0, declare_js_1.isRefused)(rules))
            return { expected: exports.WINDOW_FORM_EXPECTATION };
        const forms = (0, declare_js_1.periodFormsOf)(rules.period);
        const facts = (0, declare_js_1.periodFactsOf)(rules.period);
        const read = (0, convert_js_1.convertForTool)((0, windows_js_1.windowToConvert)(answered), forms, facts, {
            now: time.now,
            zone,
            ...(time.appZone !== undefined && { appZone: time.appZone }),
            granularityMs: (0, convert_js_1.granularityMsOf)(facts),
        });
        // Refused before any form was chosen: the reason names the TOOL's facts (`toolFacts` — the
        // widest any form reads), as every other door that judges a window before a form does.
        if ('refused' in read) {
            return { expected: refusalExpectation(read.refused, (0, convert_js_1.toolFacts)(forms, facts)) };
        }
        const conversion = read.conversion;
        const wider = 'sent' in conversion
            ? facts.filtersToAsked === true
                ? 'tool-trims'
                : 'reads-more'
            : undefined;
        for (const id of member.toolCallIds) {
            if (fills[id] !== undefined || !byId.has(id))
                continue;
            for (const [argument, value] of Object.entries(conversion.values)) {
                if (typeof value === 'object')
                    continue;
                const verdict = (0, toolArgsValidation_js_1.validatePropertyValue)(value, propertyOf(toolOf, { ...member, argument }));
                if (!verdict.ok) {
                    const expected = verdict.issues[0]?.expected;
                    return { expected: expected ?? exports.WINDOW_FORM_EXPECTATION };
                }
            }
            fills[id] = { values: conversion.values, ...(wider !== undefined && { wider }) };
            filled[id] = {
                toolName: member.toolName,
                decision: {
                    how: 'filled',
                    window: answered,
                    conversion,
                    ...(wider === 'tool-trims' && { trimmedByTool: true }),
                    ...(read.partlyBeyondRetention === true && { partlyBeyondRetention: true }),
                },
            };
        }
    }
    return { fills, filled };
}
/** The `answered` rows of a bound window: one per (call, argument) the window filled, in the tool's own view. */
function windowAnsweredRows(field, fills, byId, toolOf, stamp) {
    const nameOf = new Map(field.members.flatMap((m) => m.toolCallIds.map((id) => [id, m.toolName])));
    return Object.entries(fills).flatMap(([toolCallId, { values }]) => {
        const call = byId.get(toolCallId);
        const toolName = nameOf.get(toolCallId);
        if (call === undefined || toolName === undefined)
            return [];
        const tool = toolOf(toolName);
        const shown = tool === undefined ? undefined : (0, toolShownArgs_js_1.shownArgsOf)(tool, { ...call.args, ...values });
        return Object.keys(values).map((argument) => (0, rows_js_2.answeredRowOf)({
            toolCallId,
            toolName,
            argument,
            rule: 'ask',
            period: true,
            shownValue: shown === undefined ? rows_js_2.HIDDEN_VALUE : shown[argument],
        }, stamp));
    });
}
/** Every (call, argument) a field binds to, as a row identity. */
function memberCalls(field) {
    return field.members.flatMap((m) => m.toolCallIds.map((toolCallId) => ({
        toolCallId,
        toolName: m.toolName,
        argument: m.argument,
        rule: 'ask',
        ...(m.period === true && { period: true }),
    })));
}
function answeredRows(field, answer, byId, toolOf, stamp) {
    const free = field.type === 'string' && field.choices === undefined;
    return field.members.flatMap((m) => {
        const value = memberValue(field, m, answer);
        const tool = toolOf(m.toolName);
        return m.toolCallIds.flatMap((toolCallId) => {
            const call = byId.get(toolCallId);
            if (call === undefined)
                return [];
            // The tool's own view decides what a row may show. A name nothing answers at this moment
            // (its call is refused at dispatch) has no view to ask, so its value is shown as hidden —
            // an omission, never a raw value a view might have hidden.
            const shownValue = tool === undefined
                ? rows_js_2.HIDDEN_VALUE
                : (0, toolShownArgs_js_1.shownArgsOf)(tool, { ...call.args, [m.argument]: value })[m.argument];
            // A value the call CARRIED and the answer replaced (declared sources: an untraced value is
            // asked about) — the model's own, in the same view. Absent when the call left it out.
            const carried = !(0, declare_js_2.isMissing)(call.args, m.argument);
            const shownProposed = !carried
                ? undefined
                : tool === undefined
                    ? rows_js_2.HIDDEN_VALUE
                    : (0, toolShownArgs_js_1.shownArgsOf)(tool, call.args)[m.argument];
            return [
                (0, rows_js_2.answeredRowOf)({
                    toolCallId,
                    toolName: m.toolName,
                    argument: m.argument,
                    rule: 'ask',
                    ...(m.period === true && { period: true }),
                    shownValue,
                    ...(shownProposed !== undefined && { shownProposed }),
                    ...(free && { free: true }),
                }, stamp),
            ];
        });
    });
}
/** Per call id, what the settled ask resolved for it (only calls the ask touched). */
function settledCalls(state) {
    const out = new Map();
    const entry = (id) => {
        const seen = out.get(id);
        if (seen !== undefined)
            return seen;
        const fresh = {
            fills: [],
            exhausted: [],
        };
        out.set(id, fresh);
        return fresh;
    };
    state.fields.forEach((field, i) => {
        const p = state.progress[i];
        if (field.window !== undefined && p.fills !== undefined) {
            for (const [id, fill] of Object.entries(p.fills)) {
                for (const [argument, value] of Object.entries(fill.values)) {
                    entry(id).fills.push({
                        argument,
                        value: value,
                        source: 'answered',
                        window: true,
                        ...(fill.wider !== undefined && { wider: fill.wider }),
                    });
                }
            }
            return;
        }
        for (const m of field.members) {
            for (const id of m.toolCallIds) {
                if (p.answer !== undefined) {
                    const value = memberValue(field, m, p.answer);
                    if (value !== undefined)
                        entry(id).fills.push({ argument: m.argument, value, source: 'answered' });
                }
                else if (p.exhausted === true) {
                    entry(id).exhausted.push({
                        argument: m.argument,
                        ...(p.expected !== undefined && { expected: p.expected }),
                    });
                }
            }
        }
    });
    return out;
}
exports.settledCalls = settledCalls;
//# sourceMappingURL=ask.js.map