agentfootprint
Version:
The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.
745 lines • 38.9 kB
JavaScript
"use strict";
/**
* arguments/resolve — the inputs layer's checks and its one table, as pure
* functions of what the layer is handed.
*
* Pattern: Walker. Four pure steps, one per stage of the `sf-inputs` subflow
* (`arguments/subflow.ts` wires them to scope): DECLARE (which calls
* carry rules, and which ruled values are missing), VERIFY (where each
* ruled value came from), RECORD (the rows, in the tool's own view),
* RESOLVE (the fills and refusals ToolCalls applies). The same inputs
* always give the same outputs; nothing here reads a clock, a model or
* scope.
* Role: core/ layer, the inputs layer (honesty layer 2). Loaded through
* `import()` when the layer is armed — never on a plain agent's graph.
* Imports nothing from `findings/` (the one-way law).
* Emits: N/A.
*
* ## The table (declared sources unarmed)
*
* | The value | `assume` rule | `ask` rule |
* |-----------------------------------------|---------------------------------|-----------------------------------|
* | missing | `default` — fill the default | `asked: 'missing'` — ask, once per batch |
* | missing, an answer KEPT for it (`kept.ts`) | — | `answered` — fill the person's kept answer |
* | present, equal to the declared default | `default` — run (model's value) | — (an `ask` rule has no default) |
* | present, any other value | `model` — run, flagged | `model` — run, flagged (adopted Q2) |
*
* ## Under declared sources (`.inputsLayer({ argumentSources: true })`, `.findings({ argumentSources: true })`)
*
* A PRESENT value is checked against the source the model declared for it
* (`checks.ts` · `checkSource`) — and so is every free argument a `from`
* entry names, filed with no rule. A missing value is resolved exactly as
* above: it has no source to check.
*
* | The present value | `assume` rule | `ask` rule | no rule |
* |-----------------------------------------------------------------------|----------------------|-----------------------------------|---------|
* | traced — `said` (quote or phrase), `answered`, `result`, `app` | run | run | run, a row |
* | the declared default, not the person's (V1) | run → `default` | — | — |
* | untraced — nothing declared, `assumed`, a hint, a failed claim, a reading | run, flagged | `asked: 'unverified'` — ask | run, a row |
*
* A reading (`said` + `reading`: the person's words were found, the value is
* not in them) ASKS under an `ask` rule — otherwise any exact fragment of the
* person's message would carry any value past the rule — and the ask's field
* shows the person their own words (`quoted`) unless a tool in reach may hide
* arguments (`quotesMayShow`). The model's value never rides the ask.
* "Equal" is the evidence module's same-value rule (`tools/declare.ts` ·
* `sameArgumentValue`). A present value equal to the default is filed as
* `default`, never as the model's own choice: a model that copies a default
* from a description chose nothing (the row's `proposed` tells it from a fill).
* A present value on an `ask` argument is not asked in this version: the model
* cannot yet say where a value came from (declared sources), so every present
* value is unverified, and asking would fire on nearly every call — it runs,
* flagged `model`. A missing one is asked: RESOLVE names it on the call's
* entry (`ask`), and ToolCalls raises the ONE typed ask for the whole batch
* before anything in it runs (`arguments/ask.ts`). A missing one the person
* already answered for a call the batch that asked could not finish — its
* answer KEPT (`kept.ts`: that batch had no second pause to give) — is filled
* with the kept answer instead of asked again, and filed `answered`. A call
* whose tool's rules cannot be read at dispatch is refused, never run unruled
* and never repaired.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.resolutionsOf = exports.rowsOf = exports.verifyPlan = exports.callWindowRowsOf = exports.timeDecisionsOf = exports.declareBatch = void 0;
const bind_js_1 = require("../../../time/bind.js");
const convert_js_1 = require("../../../time/convert.js");
const rowsBuild_js_1 = require("../../../time/rowsBuild.js");
const toolShownArgs_js_1 = require("../../toolShownArgs.js");
const toolArgsValidation_js_1 = require("../../../tools/toolArgsValidation.js");
const checks_js_1 = require("./checks.js");
const declare_js_1 = require("../../../tools/declare.js");
const kept_js_1 = require("./kept.js");
const rows_js_1 = require("./rows.js");
const serve_js_1 = require("./serve.js");
// ─── DECLARE ────────────────────────────────────────────────────────────
/** The free arguments (no rule) a call's `from` entries name, in declared order. */
function freeNamed(sources, toolCallId, ruled) {
const from = sources?.declared.get(toolCallId)?.from ?? [];
const ruledNames = new Set(ruled.map((r) => r.argument));
return from.filter((e) => !ruledNames.has(e.argument)).map((e) => e.argument);
}
/**
* Every call of the batch whose tool carries rules — read through `rulesOf`,
* the dispatch re-read of the SAME assert `defineTool` runs — with each ruled
* argument marked missing or present. A call whose rules cannot be read is
* planned as refused. Calls to tools without rules are not planned — unless,
* under declared sources (`sources`), a `from` entry names one of their
* arguments: that argument is planned FREE, to be checked and filed.
*/
function declareBatch(calls, toolOf, sources) {
const plan = [];
for (const call of calls) {
// Missing values in a damaged stream are not a request to fill or ask.
// ToolCalls files the ordinary enforced refusal for this exact attempt.
if (call.argumentsFailure !== undefined)
continue;
const rules = (0, declare_js_1.rulesOf)(toolOf(call.name));
if (rules === undefined) {
const free = freeNamed(sources, call.id, []);
if (free.length > 0)
plan.push({ toolCallId: call.id, toolName: call.name, ruled: [], free });
continue;
}
if ((0, declare_js_1.isRefused)(rules)) {
plan.push({ toolCallId: call.id, toolName: call.name, refused: rules.refused, ruled: [] });
continue;
}
const free = freeNamed(sources, call.id, rules.ruled);
plan.push({
toolCallId: call.id,
toolName: call.name,
ruled: rules.ruled.map((r) => ({
argument: r.argument,
rule: r.rule,
...(r.period === true && { period: true }),
missing: (0, declare_js_1.isMissing)(call.args, r.argument),
})),
...(free.length > 0 && { free }),
});
}
return plan;
}
exports.declareBatch = declareBatch;
// ─── VERIFY ─────────────────────────────────────────────────────────────
const callById = (calls) => new Map(calls.map((c) => [c.id, c]));
/** The tool's rules, when they can be read — `undefined` otherwise. */
function readableRules(toolOf, toolName) {
const rules = (0, declare_js_1.rulesOf)(toolOf(toolName));
return rules === undefined || (0, declare_js_1.isRefused)(rules) ? undefined : rules;
}
/** The declared default of one ruled argument, read off the implementation that will run. */
function declaredDefault(toolOf, toolName, argument) {
return readableRules(toolOf, toolName)?.ruled.find((r) => r.argument === argument)?.assume;
}
/**
* The person's answer KEPT for one `ask`-ruled (tool, argument) this turn
* (`kept.ts`), when it still fits the property's own schema on the
* implementation that will run — re-checked by the same validator the answer
* passed when it was bound; one that no longer fits is not used, and the
* value is asked for as usual.
*/
function keptValue(toolOf, kept, toolName, argument) {
const answer = (0, kept_js_1.keptAnswerFor)(kept, toolName, argument);
if (answer === undefined)
return undefined;
const properties = toolOf(toolName)?.schema.inputSchema?.properties;
const property = typeof properties === 'object' && properties !== null
? properties[argument]
: undefined;
const schema = typeof property === 'object' && property !== null
? property
: undefined;
return (0, toolArgsValidation_js_1.validatePropertyValue)(answer.value, schema).ok ? answer.value : undefined;
}
/** A free-text `ask` field — a string with no declared choices: the person typed a name. */
function isFreeAsk(toolOf, toolName, argument) {
const rule = readableRules(toolOf, toolName)?.ruled.find((r) => r.argument === argument);
return rule?.type === 'string' && rule.ask !== undefined && rule.ask.choices === undefined;
}
/** The model's `from` entry for one argument of one call, if it declared one. */
function claimFor(sources, toolCallId, argument) {
return sources?.declared.get(toolCallId)?.from.find((e) => e.argument === argument);
}
/**
* The declared-sources verdict on one present value (`checks.ts` ·
* `checkSource`) — `undefined` when the arm is off for this call (no corpus,
* or the call declares nothing: its tool owns the reserved argument).
*/
function sourceCheckOf(sources, toolOf, call, argument, rule) {
if (sources?.corpus === undefined || !sources.declared.has(call.id))
return undefined;
const tool = toolOf(call.name);
const spelling = rule?.period === true ? readableRules(toolOf, call.name)?.period?.spelling : undefined;
const claim = claimFor(sources, call.id, argument);
return (0, checks_js_1.checkSource)({
toolName: call.name,
argument,
value: call.args[argument],
...(rule !== undefined && { rule }),
...(spelling !== undefined && { spelling }),
...(tool?.argumentsFrom !== undefined && { argumentsFrom: tool.argumentsFrom }),
...(claim !== undefined && { claim }),
}, sources.corpus);
}
/** A present value's place under the arm: run on the verdict, or — an untraced `ask` value — ask. */
function placeChecked(base, check) {
if (base.rule === 'ask' && !(0, checks_js_1.isTraced)(check))
return { ...base, asked: 'unverified', check };
return { ...base, source: check.source, check };
}
/**
* Whether ANY quote of this batch may be SHOWN — on its row and on the ask. A
* quote is free text the model wrote, token-equal to the person's words: it
* may hold ANY value the person gave, in any spelling (a user name and a
* password in one sentence; a PIN typed "1 2 3 4" and passed as `1234`), and
* no tool's view covers it — a tool hides its OWN arguments. So a quote is
* shown only on an agent where no tool in reach can hide arguments
* (`SourcesArm.argumentViews`, decided ONCE at build over every party
* `toolOf` can resolve — the registry, each tool asked, and any ToolProvider,
* whatever it lists). Decided before the first quote is filed, so no ordering
* can leak one: a quote filed BEFORE the call that carries the hidden value,
* in an earlier iteration or turn, or before a provider first lists the tool
* that hides it, is hidden too. The checks still read every quote in memory;
* only what the record SHOWS is decided here.
*/
function quotesMayShow(sources) {
return sources?.corpus !== undefined && sources.argumentViews !== true;
}
/**
* The quotes the model declared as the person's (`source: 'user'`) for a
* call's period arguments — `undefined` when declared sources are off for the
* call (the arm is off, or the call declares nothing). Read from the entries
* alone, so every stage (with or without the corpora) decides alike.
*/
function periodQuotesOf(sources, toolCallId, periodArguments) {
if (sources === undefined || !sources.declared.has(toolCallId))
return undefined;
const from = sources.declared.get(toolCallId)?.from ?? [];
return from
.filter((e) => e.source === 'user' && e.quote !== undefined && periodArguments.has(e.argument))
.map((e) => e.quote);
}
/**
* Under `.time()`: per planned call whose tool's period declares forms, which
* window the call carries (`time/bind.ts` · `callWindowOf`) — a pure
* function of the call, the tool's forms and facts, the turn's windows and the
* model's declared quotes. Empty when the arm is off. Every stage asks it
* afresh (the same inputs, the same answer), so no decision is staged twice.
*/
function timeDecisionsOf(plan, calls, toolOf, time, sources) {
const decisions = new Map();
if (time === undefined)
return decisions;
const byId = callById(calls);
for (const planned of plan) {
if (planned.refused !== undefined)
continue;
const call = byId.get(planned.toolCallId);
const rules = readableRules(toolOf, planned.toolName);
const forms = (0, declare_js_1.periodFormsOf)(rules?.period);
if (call === undefined || rules === undefined || forms.length === 0)
continue;
const quotes = periodQuotesOf(sources, call.id, (0, declare_js_1.periodArgumentsOf)(rules.period));
decisions.set(call.id, (0, bind_js_1.callWindowOf)({
args: call.args,
forms,
isMissing: (argument) => (0, declare_js_1.isMissing)(call.args, argument),
...(quotes !== undefined && { quotes }),
facts: (0, declare_js_1.periodFactsOf)(rules.period),
}, time.turn, {
now: time.now,
zone: time.zone,
...(time.appZone !== undefined && { appZone: time.appZone }),
granularityMs: (0, convert_js_1.granularityMsOf)((0, declare_js_1.periodFactsOf)(rules.period)),
}));
}
return decisions;
}
exports.timeDecisionsOf = timeDecisionsOf;
/** The `call-window` rows for this batch's decisions, in batch order (`time/rows.ts`). */
function callWindowRowsOf(decisions, calls, stamp) {
return calls.flatMap((c) => {
const decision = decisions.get(c.id);
return decision === undefined
? []
: [(0, rowsBuild_js_1.callWindowRow)({ toolCallId: c.id, toolName: c.name }, decision, stamp)];
});
}
exports.callWindowRowsOf = callWindowRowsOf;
/** The arguments a decision FILLS — the chosen form's, by name — or none. */
function filledArguments(decision) {
return decision?.how === 'filled'
? new Set(Object.keys(decision.conversion.values))
: new Set();
}
/** The arguments of every form of a decided call — a filled call leaves the others' alone. */
function formArgumentsOf(toolOf, toolName) {
const forms = (0, declare_js_1.periodFormsOf)(readableRules(toolOf, toolName)?.period);
return new Set(forms.flatMap((f) => (0, convert_js_1.formArguments)(f).map((a) => a.argument)));
}
/**
* The arguments of every form OTHER than the one a present window was read
* back from (`bound`, `model-chosen`, `model`) that the read form does not
* also name — an alternative the call did not take. Empty for any other
* decision; a ruled argument outside every form is never in it.
*/
function untakenFormArgumentsOf(toolOf, toolName, decision) {
if (decision === undefined ||
(decision.how !== 'bound' && decision.how !== 'model-chosen' && decision.how !== 'model')) {
return new Set();
}
return (0, declare_js_1.untakenBesides)((0, declare_js_1.periodFormsOf)(readableRules(toolOf, toolName)?.period), decision.form);
}
/**
* A window is owed ONCE, with no window decision to read (`.time()` off, or nothing filled):
* the form the call SENT its window in (`tools/declare.ts` · `sentFormOf`) — or, the period left out
* entirely, the form the rules owe (`owedFormOf`: kept answers, else the ASSUMED form, else
* the first form's own asks) — is the window; every other form's arguments are untaken. Before
* this, each form's arguments were ruled one by one: an assumed look-back beside asked bounds
* was filled AND asked, a pause for two windows the tool refuses.
*/
function windowUntakenOf(toolOf, toolName, call, kept) {
const rules = readableRules(toolOf, toolName);
const forms = (0, declare_js_1.periodFormsOf)(rules?.period);
if (rules === undefined || forms.length < 2)
return new Set();
const sent = (0, declare_js_1.sentFormOf)(forms, call.args);
const taken = sent ?? (0, declare_js_1.owedFormOf)(forms, rules, (a) => keptValue(toolOf, kept, toolName, a) !== undefined);
return (0, declare_js_1.untakenBesides)(forms, taken);
}
/** The argument-row source a window fills with: the person's answer, a reading of their words, or the app's control. */
const sourceOfWindow = (window) => window === 'control' ? 'app' : window === 'answered' ? 'answered' : 'said';
/**
* A present period value's check under a time decision: BOUND to the person's
* window by the model's quote → the person's (`matched: 'mention'`: `answered`
* for a window they settled in the time ask; `said` + `reading` for a `model`
* reader's window); a window that
* DIFFERS from the person's runs as sent (the v1 law, § 7.3) — never asked.
* The binding raises the row ONLY when the quote itself checked out — found in
* the person's words (`source: 'said'`, nothing `failed`). A quote the check
* refused (made up, or another runner's words) is a claim that failed: the
* row keeps its own verdict and the call runs as sent, like a value binding.
*/
function checkUnderWindow(check, decision) {
if (decision?.how === 'bound' &&
decision.by === 'quote' &&
check.failed === undefined &&
check.source === 'said') {
const { failed: _f, coincides: _c, reading: _r, ...kept } = check;
void _f;
void _c;
void _r;
return {
check: {
...kept,
// A window the person settled in the time ask is their ANSWER, not their chat words.
source: decision.window.source === 'answered' ? 'answered' : 'said',
matched: 'mention',
...(decision.window.source === 'derived-from-reading' && { reading: true }),
},
runs: true,
};
}
// Bound by value (the sent window IS the person's, but no declared quote names it) and a window
// that differs both run as sent; neither is raised to the person's words.
return { check, runs: decision?.how === 'model-chosen' || decision?.how === 'bound' };
}
/**
* The facts a refusal's reason names: the sent form's own (its `maxRange` in place) when the
* refused window was sent in one form, else the tool's (`maxRange` the widest any form reads).
*/
function refusalFactsOf(period, form) {
const facts = (0, declare_js_1.periodFactsOf)(period);
const forms = (0, declare_js_1.periodFormsOf)(period);
const sentIn = form === undefined ? undefined : forms[form];
return sentIn !== undefined ? (0, convert_js_1.formFacts)(sentIn, facts) : (0, convert_js_1.toolFacts)(forms, facts);
}
/**
* Where each ruled value of each planned call came from, by the table above.
* Unarmed (no `sources`), a present value either IS the declared default (an
* `assume` rule) or is the model's own; a missing value on an `ask` rule is
* placed `asked: 'missing'` — or, when this turn KEPT the person's answer for
* that (tool, argument) (`kept`), filled with it and placed `answered`. Under
* declared sources, a PRESENT value — and every free argument a `from` entry
* names — is checked (`checks.ts` · `checkSource`), and an untraced value on
* an `ask` rule is asked about (`asked: 'unverified'`).
*/
function verifyPlan(plan, calls, toolOf, kept, sources, time) {
const byId = callById(calls);
const decisions = timeDecisionsOf(plan, calls, toolOf, time, sources);
const checked = [];
// Quotes are shown on this batch's rows and ask only when no tool in reach can hide
// arguments — and never beside a call whose name nothing answers (no view to ask).
const quotesShown = quotesMayShow(sources);
const quoteOf = (call, argument) => quotesShown &&
toolOf(call.name) !== undefined &&
claimFor(sources, call.id, argument)?.quote !== undefined
? { quoteShown: true }
: {};
for (const planned of plan) {
if (planned.refused !== undefined)
continue;
const call = byId.get(planned.toolCallId);
if (call === undefined)
continue;
const rules = readableRules(toolOf, planned.toolName);
const decision = decisions.get(planned.toolCallId);
// Refused before dispatch on its window (step T5b): the call will not run, so none of its
// arguments is filled, asked or filed — its `call-window` row is the record.
if (decision?.how === 'refused')
continue;
const filled = filledArguments(decision);
const alternatives = filled.size > 0 ? formArgumentsOf(toolOf, planned.toolName) : filled;
// A window the model SENT in one form: another form's missing arguments were an alternative
// it did not take — never filled or asked (step T5b: a look-back sent to a tool that also
// takes bounds must run, so the clock at dispatch can record it).
// The person named a time the library could only PROPOSE (the owner's decision "Always
// confirm"): a period left out is asked as that mention's window even where the tool's
// rule assumes one — its default would silently stand in for what the person said.
const proposed = decision?.how === 'not-filled' &&
decision.why === 'open-reading' &&
(time?.turn.open?.length ?? 0) > 0
? formArgumentsOf(toolOf, planned.toolName)
: undefined;
// Otherwise the window is owed once: the untaken forms' arguments are neither asked nor filed.
const decided = untakenFormArgumentsOf(toolOf, planned.toolName, decision);
const untaken = decided.size > 0 || proposed !== undefined || filled.size > 0
? decided
: windowUntakenOf(toolOf, planned.toolName, call, kept);
for (const p of planned.ruled) {
const base = {
toolCallId: planned.toolCallId,
toolName: planned.toolName,
argument: p.argument,
rule: p.rule,
...(p.period === true && { period: true }),
};
// Under `.time()`: the turn's one window fills the chosen form's arguments; another
// form's arguments were an alternative the call no longer needs — never filled or asked.
if (decision?.how === 'filled' && filled.has(p.argument)) {
const from = decision.window.source;
checked.push({ ...base, source: sourceOfWindow(from), filled: true, window: from });
continue;
}
if (alternatives.has(p.argument))
continue;
if (p.missing && untaken.has(p.argument))
continue;
const rule = rules?.ruled.find((r) => r.argument === p.argument);
const check = p.missing ? undefined : sourceCheckOf(sources, toolOf, call, p.argument, rule);
if (check !== undefined) {
if (p.period === true && decision !== undefined) {
const under = checkUnderWindow(check, decision);
checked.push(under.runs || (0, checks_js_1.isTraced)(under.check)
? {
...base,
...quoteOf(call, p.argument),
source: under.check.source,
check: under.check,
}
: placeChecked({ ...base, ...quoteOf(call, p.argument) }, under.check));
continue;
}
checked.push(placeChecked({ ...base, ...quoteOf(call, p.argument) }, check));
continue;
}
if (p.rule === 'ask') {
if (!p.missing) {
checked.push({ ...base, source: 'model' });
}
else if (keptValue(toolOf, kept, planned.toolName, p.argument) !== undefined) {
checked.push({ ...base, source: 'answered', filled: true });
}
else {
checked.push({ ...base, asked: 'missing' });
}
continue;
}
if (p.missing && proposed?.has(p.argument) === true) {
checked.push({ ...base, asked: 'missing' });
continue;
}
const assumed = declaredDefault(toolOf, planned.toolName, p.argument);
if (assumed === undefined)
continue;
if (p.missing) {
checked.push({ ...base, source: 'default', filled: true });
}
else if ((0, declare_js_1.sameArgumentValue)(call.args[p.argument], assumed)) {
checked.push({ ...base, source: 'default' });
}
else {
checked.push({ ...base, source: 'model' });
}
}
for (const argument of planned.free ?? []) {
const check = sourceCheckOf(sources, toolOf, call, argument, undefined);
if (check === undefined)
continue;
checked.push({
toolCallId: planned.toolCallId,
toolName: planned.toolName,
argument,
source: check.source,
check,
...quoteOf(call, argument),
});
}
}
return checked;
}
exports.verifyPlan = verifyPlan;
// ─── RECORD ─────────────────────────────────────────────────────────────
/** The rule a checked argument carries — defined on every path but a free argument's. */
function ruleOf(c) {
return c.rule ?? 'assume';
}
/**
* The row for a value the declared-sources check judged: the verdict, the
* value (or, on an ask, the model's proposal) — each in the tool's OWN
* argument view — and the quote, which reads `'REDACTED'` unless no tool in
* reach can hide arguments (`quoteShown`, `quotesMayShow`).
*/
function sourcedRow(c, call, toolOf, sources, stamp) {
const tool = toolOf(c.toolName);
// A name nothing answers has no view to ask — shown as hidden, never raw.
const shown = tool === undefined ? rows_js_1.HIDDEN_VALUE : (0, toolShownArgs_js_1.shownArgsOf)(tool, call.args)[c.argument];
const quoteShown = c.quoteShown === true;
const quote = claimFor(sources, c.toolCallId, c.argument)?.quote;
const { check } = c;
const asked = c.asked === 'unverified';
return (0, rows_js_1.sourcedRowOf)({
toolCallId: c.toolCallId,
toolName: c.toolName,
argument: c.argument,
...(c.rule !== undefined && { rule: c.rule }),
...(c.period === true && { period: true }),
...(asked ? { asked: 'unverified' } : { source: check.source }),
...(!asked && { shownValue: shown }),
// The model's own value: on an ask (it will not run) and on a default it sent (V1).
...((asked || check.source === 'default') && { shownProposed: shown }),
claimed: check.claimed,
...(check.matched !== undefined && { matched: check.matched }),
...(quote !== undefined && { shownQuoteText: quoteShown ? quote : rows_js_1.HIDDEN_VALUE }),
...(check.reading === true && { reading: true }),
...(check.earlier === true && { earlier: true }),
...(check.result !== undefined && { result: check.result }),
...(check.setAside !== undefined && { setAside: check.setAside }),
...(check.argumentsFrom !== undefined && { argumentsFrom: check.argumentsFrom }),
...(check.appSource !== undefined && { appSource: check.appSource }),
...(check.coincides !== undefined && { coincides: check.coincides }),
...(check.failed !== undefined && { failed: check.failed }),
}, stamp);
}
/** One checked argument's row, on the unarmed paths (and a missing value's under the arm). */
function plainRow(c, call, toolOf, stamp, kept, decision) {
const tool = toolOf(c.toolName);
const who = { ...c, rule: ruleOf(c) };
if (c.asked !== undefined)
return (0, rows_js_1.askedRowOf)(who, stamp);
if (c.filled === true && c.window !== undefined) {
const value = windowValue(decision, c.argument);
if (value === undefined)
return undefined;
const shown = (0, toolShownArgs_js_1.shownArgsOf)(tool, { ...call.args, [c.argument]: value });
return (0, rows_js_1.windowRowOf)({
toolCallId: c.toolCallId,
toolName: c.toolName,
argument: c.argument,
rule: ruleOf(c),
...(c.period === true && { period: true }),
// A name nothing answers has no view to ask — shown as hidden, never raw.
shownValue: tool === undefined ? rows_js_1.HIDDEN_VALUE : shown[c.argument],
window: c.window,
}, stamp);
}
if (c.filled === true && c.source === 'answered') {
const answer = keptValue(toolOf, kept, c.toolName, c.argument);
if (answer === undefined)
return undefined;
const shown = (0, toolShownArgs_js_1.shownArgsOf)(tool, { ...call.args, [c.argument]: answer });
return (0, rows_js_1.answeredRowOf)({
toolCallId: c.toolCallId,
toolName: c.toolName,
argument: c.argument,
rule: 'ask',
...(c.period === true && { period: true }),
// A name nothing answers has no view to ask — shown as hidden, never raw.
shownValue: tool === undefined ? rows_js_1.HIDDEN_VALUE : shown[c.argument],
...(isFreeAsk(toolOf, c.toolName, c.argument) && { free: true }),
}, stamp);
}
if (c.filled === true) {
const assumed = declaredDefault(toolOf, c.toolName, c.argument);
if (assumed === undefined)
return undefined;
const shown = (0, toolShownArgs_js_1.shownArgsOf)(tool, { ...call.args, [c.argument]: assumed });
return (0, rows_js_1.argumentRowOf)({ ...who, source: 'default', shownValue: shown[c.argument] }, stamp);
}
const shown = (0, toolShownArgs_js_1.shownArgsOf)(tool, call.args)[c.argument];
return (0, rows_js_1.argumentRowOf)({
...who,
// A present value: the declared default the model sent itself, or the model's own
// (`answered` is only ever a FILL, handled above).
source: c.source === 'default' ? 'default' : 'model',
shownValue: shown,
...(c.source === 'default' && { shownProposed: shown }),
}, stamp);
}
/** The value a window fill writes into `argument`, raw — `undefined` when the decision fills none. */
function windowValue(decision, argument) {
return decision?.how === 'filled' ? decision.conversion.values[argument] : undefined;
}
/**
* `row` with the call's dropped `from` entries counted — the call's FIRST row
* carries the count when no basis row does (`sources.ts` · `CallSources`),
* written before `failed`, in `ArgumentRow`'s own field order.
*/
function withMalformed(row, malformed) {
const { failed, ...rest } = row;
return { ...rest, malformed, ...(failed !== undefined && { failed }) };
}
/**
* One row per checked argument, its value in the tool's OWN argument view
* (`shownArgsOf`) — a fill is shown as the call will run with it, a model's
* value as the model sent it; an argument to ASK has no value yet, so its row
* carries none (under declared sources, the model's proposal rides as
* `proposed`); a kept answer files `answered` (`free` for a free-text field),
* exactly as the answer did when the person gave it. Under declared sources a
* present value's row carries the check's verdict (`claimed`, `matched`,
* `quote`, `failed`, …), and the call's first row carries the count of its
* dropped `from` entries when no basis row does. The raw value never reaches
* a row.
*/
function rowsOf(checked, calls, toolOf, stamp, kept, sources, decisions) {
const byId = callById(calls);
const rows = [];
const counted = new Set();
for (const c of checked) {
const call = byId.get(c.toolCallId);
if (call === undefined)
continue;
const row = c.check !== undefined
? sourcedRow({ ...c, check: c.check }, call, toolOf, sources, stamp)
: plainRow(c, call, toolOf, stamp, kept, decisions?.get(c.toolCallId));
if (row === undefined)
continue;
const malformed = sources?.declared.get(c.toolCallId)?.malformed;
if (malformed !== undefined && malformed > 0 && !counted.has(c.toolCallId)) {
counted.add(c.toolCallId);
rows.push(withMalformed(row, malformed));
continue;
}
rows.push(row);
}
return rows;
}
exports.rowsOf = rowsOf;
// ─── RESOLVE ────────────────────────────────────────────────────────────
/**
* The person's own words a READING was made of, for the ask's field — the
* quote the model declared, found in the person's messages (`said` +
* `reading`), and only where the row may show it (`quoteShown`: the ask is on
* the record too).
*/
function quotedFor(c, sources) {
if (c.asked !== 'unverified' || c.check?.source !== 'said' || c.check.reading !== true) {
return undefined;
}
if (c.quoteShown !== true)
return undefined;
return claimFor(sources, c.toolCallId, c.argument)?.quote;
}
/**
* What ToolCalls applies: per call, the declared defaults and the kept answers
* to fill and the `ask` arguments to ask the person for (with, under declared
* sources, the person's words a reading was made of), or — for a call whose
* rules could not be read — the sentence it reads instead of running. Nothing
* for a call that runs as the model sent it.
*/
function resolutionsOf(plan, checked, toolOf, iteration, kept, sources, decisions) {
const resolutions = [];
for (const planned of plan) {
if (planned.refused !== undefined) {
resolutions.push({
toolCallId: planned.toolCallId,
iteration,
refused: (0, serve_js_1.unreadableRulesRefusal)(planned.toolName, planned.refused),
});
continue;
}
const decision = decisions?.get(planned.toolCallId);
if (decision?.how === 'refused') {
resolutions.push({
toolCallId: planned.toolCallId,
iteration,
refused: (0, serve_js_1.timeRefusal)(planned.toolName, decision.refused, refusalFactsOf(readableRules(toolOf, planned.toolName)?.period, decision.form), decision.argument),
});
continue;
}
const fills = [];
const ask = [];
const quoted = [];
for (const c of checked) {
if (c.toolCallId !== planned.toolCallId)
continue;
if (c.asked !== undefined) {
ask.push(c.argument);
const quote = quotedFor(c, sources);
if (quote !== undefined)
quoted.push({ argument: c.argument, quote });
continue;
}
if (c.filled !== true)
continue;
if (c.window !== undefined)
continue; // the window's fills are added per call, below
if (c.source === 'answered') {
const answer = keptValue(toolOf, kept, c.toolName, c.argument);
if (answer !== undefined) {
fills.push({ argument: c.argument, value: answer, source: 'answered' });
}
continue;
}
const value = declaredDefault(toolOf, c.toolName, c.argument);
if (value !== undefined)
fills.push({ argument: c.argument, value, source: 'default' });
}
// Under `.time()`: every argument the turn's one window fills — the ruled ones filed above,
// and an `object` form's (a ruled argument is a flat value, so an object carries no rule).
if (decision?.how === 'filled') {
const wider = 'sent' in decision.conversion
? decision.trimmedByTool === true
? 'tool-trims'
: 'reads-more'
: undefined;
for (const [argument, value] of Object.entries(decision.conversion.values)) {
fills.push({
argument,
value: value,
source: 'window',
from: decision.window.source,
...(wider !== undefined && { wider }),
});
}
}
// The lazy word-driven ask: a period left out while the turn's one mention is open is asked
// as that mention's window — only where the tool's own rule asks for a period argument.
const forms = formArgumentsOf(toolOf, planned.toolName);
const window = decision?.how === 'not-filled' &&
decision.why === 'open-reading' &&
ask.some((a) => forms.has(a));
if (fills.length > 0 || ask.length > 0) {
resolutions.push({
toolCallId: planned.toolCallId,
iteration,
...(fills.length > 0 && { fills }),
...(ask.length > 0 && { ask }),
...(quoted.length > 0 && { quoted }),
...(window && { window: true }),
});
}
}
return resolutions;
}
exports.resolutionsOf = resolutionsOf;
//# sourceMappingURL=resolve.js.map