@orkestrel/agent 0.0.36 → 0.0.37

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -8806,6 +8806,11 @@ var PLACED_CATEGORIES = Object.freeze(["rule", "correction"]);
8806
8806
  var LEDGER_RULES_KEY = "rules";
8807
8807
  /** Prefixes the key of an owner's record, which the owner's id follows. */
8808
8808
  var LEDGER_OWNER_PREFIX = "owner:";
8809
+ /** Labels recalled conversation text by its origin. */
8810
+ var LEDGER_RECALL_LABELS = Object.freeze({
8811
+ request: "Earlier request:",
8812
+ reply: "Earlier reply:"
8813
+ });
8809
8814
  /**
8810
8815
  * Supplies the wording of every question the ledger asks its judge.
8811
8816
  *
@@ -8953,6 +8958,108 @@ function isLedgerError(value) {
8953
8958
  }
8954
8959
  // src/core/ledgers/helpers.ts
8955
8960
  /**
8961
+ * Digests held messages, lookup call context, stored copies, and sentence receipts using canonical text.
8962
+ * This non-cryptographic checksum detects host edits and makes no security claim.
8963
+ * @param exchanges - The stored exchange entries
8964
+ * @param messages - The conversation holding their messages
8965
+ * @returns The hexadecimal checksum, or undefined when a required message is absent or canonical encoding fails
8966
+ * @example
8967
+ * ```ts
8968
+ * digestLedgerWindow([], []) // a deterministic hexadecimal checksum
8969
+ * ```
8970
+ */
8971
+ function digestLedgerWindow(exchanges, messages) {
8972
+ const byId = new Map(messages.map((message) => [message.id, message]));
8973
+ const held = [...new Set(exchanges.flatMap((entry) => [
8974
+ entry.request,
8975
+ entry.reply,
8976
+ ...entry.lines.map((line) => line.source)
8977
+ ]))].map((id) => byId.get(id));
8978
+ const callers = messages.filter((message) => message.role === "tool" || message.calls !== void 0);
8979
+ const encoded = (0, _orkestrel_contract.attempt)(() => {
8980
+ const values = [...held, ...callers].map((message) => (0, _orkestrel_contract.canonicalStringify)(message));
8981
+ return values.some((value) => value === void 0) ? void 0 : (0, _orkestrel_contract.canonicalStringify)([values, exchanges.map((entry) => [entry.copy ?? null, entry.lines])]);
8982
+ });
8983
+ if (!encoded.success || encoded.value === void 0) return void 0;
8984
+ const hash = [...encoded.value].reduce((prior, character) => {
8985
+ const code = character.codePointAt(0) ?? 0;
8986
+ return {
8987
+ first: Math.imul(prior.first ^ code, 16777619) >>> 0,
8988
+ second: Math.imul(prior.second ^ code, 16777619) >>> 0
8989
+ };
8990
+ }, {
8991
+ first: 2166136261,
8992
+ second: 3335557771
8993
+ });
8994
+ return hash.first.toString(16).padStart(8, "0") + hash.second.toString(16).padStart(8, "0");
8995
+ }
8996
+ /**
8997
+ * Collects stored messages eligible to open the recall offer.
8998
+ * @remarks
8999
+ * Notes and findings without a result do not open the offer. User statements, including quiet
9000
+ * statements available through recall's word route, can open it; this helper does not filter results.
9001
+ * @param input - The messages, findings, and classification
9002
+ * @param requests - The marked request ids
9003
+ * @param notes - The annotation ids to exclude
9004
+ * @returns User statements, lookup findings with results, marked requests, and nonblank call-free replies
9005
+ * @example
9006
+ * ```ts
9007
+ * collectRecallMessages(input, new Set(), new Set()) // eligible stored messages
9008
+ * ```
9009
+ */
9010
+ function collectRecallMessages(input, requests, notes) {
9011
+ const findings = new Set(input.findings.filter((finding) => finding.result !== void 0).map((finding) => finding.id));
9012
+ return input.messages.filter((message) => !notes.has(message.id) && (requests.has(message.id) || message.role === "assistant" && !hasCalls(message) && message.content.trim() !== "" || message.role === "user" && !hasCalls(message) || message.role === "tool" && findings.has(message.id)));
9013
+ }
9014
+ /**
9015
+ * Checks whether an eligible earlier message is absent from the prompt or incompletely covered.
9016
+ * A successful finding is held when a finding with the same canonical identity and text is fully received.
9017
+ * @param input - The messages, findings, and classification
9018
+ * @param request - The current request id; only messages before it count
9019
+ * @param requests - The marked request ids
9020
+ * @param notes - The annotation ids to exclude
9021
+ * @param messages - The seed tail and held exchange messages; lookup stubs retain their rendered text
9022
+ * @param lines - The receipts held by the window and the request copy together
9023
+ * @returns True if an eligible earlier message is absent or only partly covered; false otherwise
9024
+ * @example
9025
+ * ```ts
9026
+ * hasHiddenRecall(input, request.id, requests, notes, tail, lines) // whether to offer recall
9027
+ * ```
9028
+ */
9029
+ function hasHiddenRecall(input, request, requests, notes, messages, lines) {
9030
+ const start = input.messages.findIndex((message) => message.id === request);
9031
+ const before = new Set(input.messages.slice(0, Math.max(0, start)).map((message) => message.id));
9032
+ const present = new Map(messages.map((message) => [message.id, message]));
9033
+ const receipts = Map.groupBy(lines, (line) => line.source);
9034
+ return collectRecallMessages(input, requests, notes).some((message) => {
9035
+ if (!before.has(message.id)) return false;
9036
+ if (requests.has(message.id) || message.role === "assistant") return !present.has(message.id);
9037
+ if (present.get(message.id)?.content === message.content) return false;
9038
+ const finding = input.findings.find((one) => one.id === message.id);
9039
+ return !(finding === void 0 ? [message.id] : input.findings.filter((one) => before.has(one.id) && matchesLookup(finding, one)).map((one) => one.id)).some((id) => {
9040
+ if (present.get(id)?.content === message.content) return true;
9041
+ const held = new Set((receipts.get(id) ?? []).map((line) => line.sentence));
9042
+ return splitSentences(message.content).every((_, at) => held.has(at));
9043
+ });
9044
+ });
9045
+ }
9046
+ /**
9047
+ * Checks whether two successful findings carry the same text for the same canonical lookup.
9048
+ * @param left - The finding to compare
9049
+ * @param right - The candidate copy
9050
+ * @returns True for the same finding or equal canonical identities and text; unkeyable findings match only themselves
9051
+ * @example
9052
+ * ```ts
9053
+ * matchesLookup(finding, finding) // true when it has a result
9054
+ * ```
9055
+ */
9056
+ function matchesLookup(left, right) {
9057
+ if (left.result === void 0 || right.result === void 0 || left.text !== right.text) return false;
9058
+ if (left.id === right.id) return true;
9059
+ const identity = identifyLookup(left.name, left.arguments);
9060
+ return identity !== void 0 && identity === identifyLookup(right.name, right.arguments);
9061
+ }
9062
+ /**
8956
9063
  * Collects the live message ids placed in records or left without a record.
8957
9064
  * @param projection - The projected records and unplaced ids
8958
9065
  * @returns The distinct live message ids
@@ -9508,17 +9615,17 @@ function matchEntities(index, text, partial) {
9508
9615
  * Projects the owner records and the rules record from a conversation's messages.
9509
9616
  *
9510
9617
  * @remarks
9511
- * Each line is a sentence of a live message, verbatim except that a sentence that opens with a pronoun opens with its party and a colon. A message is live when it is a user message,
9512
- * or a tool message whose finding is the last finding of its call, and the input neither excludes it nor
9513
- * files it quiet or replaced. A finding with an undefined `result` still replaces the earlier
9514
- * finding of the same call, so the earlier result leaves every record.
9618
+ * Each line is a sentence of a live message, verbatim except that a sentence that opens with a
9619
+ * pronoun opens with its party and a colon. {@link collectLive} retains user messages and the
9620
+ * earliest finding in each call identity's last run of identical text, subject to exclusions,
9621
+ * quiet classification, and supersessions. An undefined result still replaces the earlier finding.
9515
9622
  *
9516
9623
  * A live message joins the owner records its entities name, directly or through a linked lookup
9517
9624
  * argument. A message that names no owner joins where its earlier side of an amendment pair joins,
9518
9625
  * or on the rules record when it is filed as a rule or a correction, and is an orphan otherwise.
9519
9626
  *
9520
- * Every live sentence is retained. Accepted direct edges are listed in `amendments`. An amending message
9521
- * that is itself superseded retains its accepted edge without restoring its text.
9627
+ * Every live sentence is retained. Accepted edges are listed in `amendments`. An amender that is
9628
+ * superseded points to its newest live successor, or retains its endpoint when none exists.
9522
9629
  *
9523
9630
  * A sentence that opens with a pronoun takes the party named in the sentence before it. The reading
9524
9631
  * trusts capitals, so a capitalized word that is no person, such as a carrier named mid-sentence,
@@ -9728,10 +9835,12 @@ function splitTopic(topic) {
9728
9835
  * @remarks
9729
9836
  * The cut keeps at least one entry. When it leaves entries out, its last line reads
9730
9837
  * `N older items not shown; name a narrower topic to narrow the recall`, which
9731
- * {@link matchesCutLine} recognizes.
9838
+ * {@link matchesCutLine} recognizes. Pass `more` when the entries aren't in age order, so the line
9839
+ * doesn't claim an omitted entry is older than a kept one.
9732
9840
  *
9733
9841
  * @param entries - The entries in the order they are kept
9734
9842
  * @param room - The estimate units the joined entries can take
9843
+ * @param kind - What the cut line calls an omitted entry. Default: `older`
9735
9844
  * @returns The kept entries followed by the cut line when any were left out, joined by newlines
9736
9845
  *
9737
9846
  * @example
@@ -9739,7 +9848,7 @@ function splitTopic(topic) {
9739
9848
  * cutListing(['first entry', 'second entry'], 1) // 'first entry\n1 older item not shown; name a narrower topic to narrow the recall'
9740
9849
  * ```
9741
9850
  */
9742
- function cutListing(entries, room) {
9851
+ function cutListing(entries, room, kind = "older") {
9743
9852
  const stop = [...entries.keys()].findIndex((at) => at > 0 && estimateMessages([{
9744
9853
  id: "recall",
9745
9854
  role: "tool",
@@ -9747,7 +9856,7 @@ function cutListing(entries, room) {
9747
9856
  }]) > room);
9748
9857
  const kept = entries.slice(0, stop === -1 ? void 0 : stop);
9749
9858
  const left = entries.length - kept.length;
9750
- return [...kept, ...left > 0 ? [`${left} older item${left === 1 ? "" : "s"} not shown; name a narrower topic to narrow the recall`] : []].join("\n");
9859
+ return [...kept, ...left > 0 ? [`${left} ${kind} item${left === 1 ? "" : "s"} not shown; name a narrower topic to narrow the recall`] : []].join("\n");
9751
9860
  }
9752
9861
  /**
9753
9862
  * Checks whether a line is the cut line {@link cutListing} writes.
@@ -9761,7 +9870,7 @@ function cutListing(entries, room) {
9761
9870
  * ```
9762
9871
  */
9763
9872
  function matchesCutLine(line) {
9764
- return /^\d+ older items? not shown; /.test(line);
9873
+ return /^\d+ (?:older|more) items? not shown; /.test(line);
9765
9874
  }
9766
9875
  /**
9767
9876
  * Renders the tail stub of a lookup result from an earlier request.
@@ -9822,11 +9931,12 @@ function fitSlope(groups) {
9822
9931
  * Collects the ids of the live messages in conversation order.
9823
9932
  *
9824
9933
  * @remarks
9825
- * A message is live when it is a user message, or a tool message whose finding is the last finding of its
9826
- * call, and the input neither excludes it nor files it quiet or replaced. A finding replaces any
9827
- * earlier finding of the same call, an empty one included, so an empty lookup leaves the earlier
9828
- * result out of every record. A finding with no canonical identity stays live as its own source
9829
- * and participates in no replacement.
9934
+ * A message is live when it is a user message or the first unsuperseded finding in its canonical
9935
+ * call identity's last run of identical text, and the input neither excludes it nor files it quiet
9936
+ * or superseded. When the whole run is superseded, its first finding reaches that final filter.
9937
+ * Different text or an undefined result replaces the earlier run, so an empty lookup leaves the
9938
+ * earlier result out of every record. A thrown lookup leaves the earlier result live. A finding
9939
+ * with no canonical identity participates in no replacement.
9830
9940
  *
9831
9941
  * @param input - The messages, findings, and classification to read
9832
9942
  * @returns The live message ids
@@ -9842,7 +9952,14 @@ function collectLive(input) {
9842
9952
  const position = new Map(input.messages.map((message, at) => [message.id, at]));
9843
9953
  const findings = input.findings.filter((finding) => position.has(finding.id));
9844
9954
  const identities = new Map(findings.map((finding) => [finding.id, identifyLookup(finding.name, finding.arguments)]));
9845
- const current = new Set(findings.filter((finding) => finding.result !== void 0 && !findings.some((later) => identities.get(finding.id) !== void 0 && (position.get(later.id) ?? -1) > (position.get(finding.id) ?? -1) && identities.get(later.id) === identities.get(finding.id))).map((finding) => finding.id));
9955
+ const current = new Set([...Map.groupBy(findings, (finding) => identities.get(finding.id) ?? finding).values()].flatMap((group) => {
9956
+ const ordered = group.toSorted((left, right) => (position.get(left.id) ?? -1) - (position.get(right.id) ?? -1));
9957
+ const last = ordered.at(-1);
9958
+ if (last?.result === void 0) return [];
9959
+ const boundary = ordered.findLastIndex((finding) => finding.result === void 0 || finding.text !== last.text);
9960
+ const first = ordered.slice(boundary + 1).find((finding) => (classification.supersessions.get(finding.id) ?? []).length === 0) ?? ordered[boundary + 1];
9961
+ return first === void 0 ? [] : [first.id];
9962
+ }));
9846
9963
  return [...new Map(input.messages.map((message) => [message.id, message])).values()].filter((message) => message.role === "user" || message.role === "tool" && current.has(message.id)).map((message) => message.id).filter((id) => !excluded.has(id) && !classification.quiet.has(id) && (classification.supersessions.get(id) ?? []).length === 0);
9847
9964
  }
9848
9965
  /**
@@ -9897,17 +10014,18 @@ function placeMember(input, links, amended, id, seen, settled) {
9897
10014
  return keys;
9898
10015
  }
9899
10016
  /**
9900
- * Collects accepted direct amendments of live messages.
10017
+ * Collects accepted amendments of live messages, following superseded amenders to live successors.
9901
10018
  *
9902
10019
  * @remarks
9903
10020
  * An accepted edge relates an earlier live message to an amender without claiming which sentences
9904
- * changed. An amender takes effect while live and retains its edge after another message supersedes
9905
- * it, without restoring the superseded text. An excluded or quiet user or tool message has no effect.
10021
+ * changed. A superseded amender points to its newest reachable live successor in conversation order,
10022
+ * with cycles guarded and duplicate endpoints removed. With no live successor, its endpoint stays.
10023
+ * This never restores superseded text. An excluded or quiet user or tool message has no effect.
9906
10024
  *
9907
10025
  * @param input - The exclusions and classification to read
9908
10026
  * @param byId - The messages by id
9909
10027
  * @param live - The live message ids from {@link collectLive}
9910
- * @returns The distinct accepted direct edges in conversation order
10028
+ * @returns The distinct accepted edges in conversation order
9911
10029
  *
9912
10030
  * @example
9913
10031
  * ```ts
@@ -9921,7 +10039,10 @@ function collectAmendments(input, byId, live) {
9921
10039
  const ordered = [...new Set(input.messages.map((message) => message.id))];
9922
10040
  const positions = new Map(ordered.map((id, index) => [id, index]));
9923
10041
  const effective = /* @__PURE__ */ new Set([...live, ...[...byId.values()].filter((message) => message.role !== "assistant" && !excluded.has(message.id) && !classification.quiet.has(message.id) && (classification.supersessions.get(message.id) ?? []).length > 0).map((message) => message.id)]);
9924
- return ordered.filter((id) => living.has(id)).flatMap((earlier) => [...new Set(classification.amendments.get(earlier) ?? [])].filter((later) => effective.has(later) && positions.has(later)).sort((a, b) => (positions.get(a) ?? 0) - (positions.get(b) ?? 0)).map((later) => ({
10042
+ return ordered.filter((id) => living.has(id)).flatMap((earlier) => [...new Set((classification.amendments.get(earlier) ?? []).filter((later) => effective.has(later) && positions.has(later)).map((later) => {
10043
+ const successors = new Set(scanAmendments(classification.supersessions, later, () => true));
10044
+ return ordered.findLast((id) => living.has(id) && successors.has(id)) ?? later;
10045
+ }))].sort((a, b) => (positions.get(a) ?? 0) - (positions.get(b) ?? 0)).map((later) => ({
9925
10046
  earlier,
9926
10047
  later
9927
10048
  })));
@@ -9967,6 +10088,83 @@ function buildLines(input, byId, id, holders, system) {
9967
10088
  }];
9968
10089
  });
9969
10090
  }
10091
+ /**
10092
+ * Encodes a sentence's record placement without comparing rendered text.
10093
+ * @param line - The record key, title, source, and zero-based sentence index
10094
+ * @returns The serialized placement identity
10095
+ * @example
10096
+ * ```ts
10097
+ * buildLedgerLineKey({ key: 'rules', title: 'Rules', source: 'one', sentence: 0 })
10098
+ * ```
10099
+ */
10100
+ function buildLedgerLineKey(line) {
10101
+ return JSON.stringify([
10102
+ line.key,
10103
+ line.title,
10104
+ line.source,
10105
+ line.sentence
10106
+ ]);
10107
+ }
10108
+ /**
10109
+ * Collects the record's sentence receipts not already held by a window.
10110
+ * @param record - The selected record with its original sentence identities
10111
+ * @param held - Placement identities already rendered in the window
10112
+ * @returns The remaining sentences with their record placement
10113
+ * @example
10114
+ * ```ts
10115
+ * collectLedgerLines({ key: 'rules', title: 'Rules', members: [], lines: [] }, new Set()) // []
10116
+ * ```
10117
+ */
10118
+ function collectLedgerLines(record, held) {
10119
+ return record.lines.map((line) => ({
10120
+ ...line,
10121
+ key: record.key,
10122
+ title: record.title
10123
+ })).filter((line) => !held.has(buildLedgerLineKey(line)));
10124
+ }
10125
+ /**
10126
+ * Selects each marked request's unambiguous final call-free reply.
10127
+ * @param messages - Stored conversation messages in order
10128
+ * @param requests - Marked request ids delimiting reply intervals
10129
+ * @param notes - Ledger notes that separate answer passes
10130
+ * @returns Eligible final replies keyed by request id
10131
+ * @example
10132
+ * ```ts
10133
+ * collectLedgerReplies([], new Set(), new Set()).size // 0
10134
+ * ```
10135
+ */
10136
+ function collectLedgerReplies(messages, requests, notes) {
10137
+ const positions = messages.flatMap((message, at) => requests.has(message.id) ? [at] : []);
10138
+ return new Map(positions.flatMap((position, index) => {
10139
+ const request = messages[position];
10140
+ const turns = messages.slice(position + 1, positions[index + 1]);
10141
+ const reply = turns.findLast((message) => message.role === "assistant" && !hasCalls(message));
10142
+ const answers = turns.flatMap((message, at) => message.role === "assistant" && !hasCalls(message) && message.content.trim() !== "" ? [at] : []);
10143
+ const certain = answers.every((at, offset) => offset === 0 || turns.slice((answers[offset - 1] ?? -1) + 1, at).some((message) => notes.has(message.id)));
10144
+ return request !== void 0 && certain && reply !== void 0 && reply.content.trim() !== "" ? [[request.id, reply]] : [];
10145
+ }));
10146
+ }
10147
+ /**
10148
+ * Captures source bytes and direct amendment edges for a held window.
10149
+ * @param messages - Current stored messages
10150
+ * @param sources - Requests, replies, and record sources the window holds
10151
+ * @param amendments - Accepted direct amendment edges from the current projection
10152
+ * @returns Source and edge identities for stale detection
10153
+ * @example
10154
+ * ```ts
10155
+ * collectLedgerEvidence([], new Set(), []).messages.size // 0
10156
+ * ```
10157
+ */
10158
+ function collectLedgerEvidence(messages, sources, amendments) {
10159
+ return {
10160
+ messages: new Set(messages.filter((message) => sources.has(message.id)).map((message) => JSON.stringify(["message", message]))),
10161
+ amendments: new Map(amendments.filter((edge) => sources.has(edge.earlier) || sources.has(edge.later)).map((edge) => [JSON.stringify([
10162
+ "edge",
10163
+ edge.earlier,
10164
+ edge.later
10165
+ ]), { ...edge }]))
10166
+ };
10167
+ }
9970
10168
  // src/core/ledgers/Classifier.ts
9971
10169
  /**
9972
10170
  * Files messages through the conversation's judgment manager and reads their categories and corrections.
@@ -10011,7 +10209,7 @@ var Classifier = class {
10011
10209
  /**
10012
10210
  * Reads the category a message is filed under.
10013
10211
  * @param id - The message id
10014
- * @returns The assigned category, the recorded category at the cutoff, or `undefined`
10212
+ * @returns The assigned category, the recorded category at the cutoff, or `undefined`; a quiet winner suppressed by the user literal floor remains undecided
10015
10213
  */
10016
10214
  category(id) {
10017
10215
  const message = this.#options.conversation.message(id);
@@ -10020,25 +10218,29 @@ var Classifier = class {
10020
10218
  if (assigned !== void 0) return assigned;
10021
10219
  const answer = this.#readCategories(id);
10022
10220
  const cutoff = this.#options.thresholds.category;
10023
- return LEDGER_CATEGORIES.flatMap((category) => {
10221
+ const winner = LEDGER_CATEGORIES.flatMap((category) => {
10024
10222
  const probability = answer?.probabilities[category];
10025
10223
  return probability !== void 0 && probability >= cutoff ? [{
10026
10224
  category,
10027
10225
  probability
10028
10226
  }] : [];
10029
10227
  }).reduce((best, candidate) => best === void 0 || candidate.probability > best.probability ? candidate : best, void 0)?.category;
10228
+ return winner !== void 0 && QUIET_CATEGORIES.includes(winner) && !this.quiet(id) ? void 0 : winner;
10030
10229
  }
10031
10230
  /**
10032
10231
  * Reports whether a message files under a quiet category.
10033
10232
  * @param id - The message id
10034
- * @returns True if the assigned or recorded category is quiet; false otherwise.
10233
+ * @returns True if the assigned category is quiet or the recorded quiet weight reaches the cutoff, except for a user message carrying an id or number; false otherwise
10035
10234
  */
10036
10235
  quiet(id) {
10037
10236
  const message = this.#options.conversation.message(id);
10038
10237
  if (message === void 0) return false;
10039
10238
  const assigned = this.#options.assign(message);
10040
10239
  if (assigned !== void 0) return QUIET_CATEGORIES.includes(assigned);
10041
- return this.#weigh(id, QUIET_CATEGORIES) >= this.#options.thresholds.category;
10240
+ if (this.#weigh(id, QUIET_CATEGORIES) < this.#options.thresholds.category) return false;
10241
+ if (message.role !== "user") return true;
10242
+ const literals = extractLiterals(message.content);
10243
+ return literals.ids.size + literals.numbers.size === 0;
10042
10244
  }
10043
10245
  /**
10044
10246
  * Reports whether a message files under a decisive category.
@@ -10077,7 +10279,7 @@ var Classifier = class {
10077
10279
  if (head !== "amends" && head !== "supersedes" || !(0, _orkestrel_contract.isString)(earlier) || !(0, _orkestrel_contract.isString)(later)) return [];
10078
10280
  const before = this.#options.conversation.message(earlier);
10079
10281
  const after = this.#options.conversation.message(later);
10080
- if (before === void 0 || after === void 0 || (this.#readNoul(this.#buildPair(head, earlier, later)) ?? 0) < this.#options.thresholds[head]) return [];
10282
+ if (before === void 0 || after === void 0 || (this.#readNoul(this.#buildPair(head, earlier, later)) ?? 0) < this.#options.thresholds[head] || this.quiet(later)) return [];
10081
10283
  return head === "amends" && !this.#sharesLiteral(extractLiterals(before.content), extractLiterals(after.content)) ? [] : [{
10082
10284
  head,
10083
10285
  earlier,
@@ -10329,8 +10531,9 @@ var Classifier = class {
10329
10531
  * has left.
10330
10532
  *
10331
10533
  * @remarks
10332
- * `observe` rescales from the first call of each finished request with the overhead cost taken out,
10333
- * and keeps that request's calls for the marginal rate and the longest final completion for the reply
10534
+ * `observe` rescales from the first call only when `spared` is absent, zero, or at least `overhead`,
10535
+ * with `max(0, overhead - spared)` taken out. A partially spared call keeps the existing scale.
10536
+ * It keeps that request's calls for the marginal rate and the longest final completion for the reply
10334
10537
  * reserve after subtracting its thinking. `overhead` never changes after construction.
10335
10538
  * {@link GaugeOptions} defines the measured use, reply reserve, and recall-room formulas.
10336
10539
  * {@link LedgerOptions} defines the ledger's plan budget and recall close rule.
@@ -10388,7 +10591,7 @@ var Gauge = class {
10388
10591
  }
10389
10592
  remainder(calls) {
10390
10593
  const call = calls.at(-1);
10391
- const used = (0, _orkestrel_contract.isFiniteNumber)(call?.prompt) ? call.prompt + (call.completion ?? 0) - (this.#replay === "none" ? call.thinking ?? 0 : 0) : this.#overhead + this.#scale * (call?.estimate ?? 0);
10594
+ const used = (0, _orkestrel_contract.isFiniteNumber)(call?.prompt) ? call.prompt + (call.completion ?? 0) - (this.#replay === "none" ? call.thinking ?? 0 : 0) : Math.max(0, this.#overhead - (call?.spared ?? 0)) + this.#scale * (call?.estimate ?? 0);
10392
10595
  return Math.max(0, this.#capacity - used);
10393
10596
  }
10394
10597
  reserve(calls, longest) {
@@ -10408,8 +10611,8 @@ var Gauge = class {
10408
10611
  }
10409
10612
  observe(calls, reply) {
10410
10613
  const first = calls[0];
10411
- if (first !== void 0 && (0, _orkestrel_contract.isFiniteNumber)(first.prompt) && first.estimate > 0) {
10412
- const priced = first.prompt - this.#overhead;
10614
+ if (first !== void 0 && (0, _orkestrel_contract.isFiniteNumber)(first.prompt) && first.estimate > 0 && ((first.spared ?? 0) === 0 || (first.spared ?? 0) >= this.#overhead)) {
10615
+ const priced = first.prompt - Math.max(0, this.#overhead - (first.spared ?? 0));
10413
10616
  if (priced > 0) this.#scale = priced / first.estimate;
10414
10617
  }
10415
10618
  this.#history = [...this.#history, [...calls]];
@@ -10586,6 +10789,9 @@ var LedgerRequest = class {
10586
10789
  #faults = /* @__PURE__ */ new Set();
10587
10790
  #message;
10588
10791
  #plan;
10792
+ #scope;
10793
+ #draft;
10794
+ #sent;
10589
10795
  #passes = [];
10590
10796
  #attempts = [];
10591
10797
  #admitted = /* @__PURE__ */ new Set();
@@ -10620,6 +10826,16 @@ var LedgerRequest = class {
10620
10826
  get plan() {
10621
10827
  return this.#plan;
10622
10828
  }
10829
+ get scope() {
10830
+ return this.#scope;
10831
+ }
10832
+ apply(scope) {
10833
+ this.#scope = scope;
10834
+ }
10835
+ /** Returns the copy and receipts adopted by the last provider turn. */
10836
+ get sent() {
10837
+ return this.#sent;
10838
+ }
10623
10839
  /**
10624
10840
  * Returns the selection the current pass's run adopted last.
10625
10841
  *
@@ -10675,6 +10891,8 @@ var LedgerRequest = class {
10675
10891
  this.#passes.at(-1)?.adopt(selection, boundary);
10676
10892
  }
10677
10893
  observe(call, position) {
10894
+ const draft = this.#draft;
10895
+ this.#sent = draft !== void 0 && this.selection?.messages.some((message) => message.id === draft.copy.id && message.content === draft.copy.content) ? draft : void 0;
10678
10896
  this.#passes.at(-1)?.observe(call, position);
10679
10897
  }
10680
10898
  spend(usage) {
@@ -10695,8 +10913,9 @@ var LedgerRequest = class {
10695
10913
  faulted(error) {
10696
10914
  return this.#faults.has(error);
10697
10915
  }
10698
- file(attempts, plan) {
10916
+ file(attempts, plan, draft) {
10699
10917
  this.#attempts = [...this.#attempts, ...attempts];
10918
+ this.#draft = draft;
10700
10919
  if (plan !== void 0) this.#plan = plan;
10701
10920
  }
10702
10921
  admit(name, args) {
@@ -10828,6 +11047,29 @@ var LedgerScan = class {
10828
11047
  }
10829
11048
  };
10830
11049
  // src/core/ledgers/shapers.ts
11050
+ /** Describes the minimal serialized evidence of a nonempty exchange window. */
11051
+ var ledgerWindowShape = (0, _orkestrel_contract.objectShape)({
11052
+ digest: (0, _orkestrel_contract.stringShape)(),
11053
+ exchanges: (0, _orkestrel_contract.arrayShape)((0, _orkestrel_contract.objectShape)({
11054
+ request: (0, _orkestrel_contract.stringShape)(),
11055
+ copy: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.stringShape)()),
11056
+ reply: (0, _orkestrel_contract.stringShape)(),
11057
+ lines: (0, _orkestrel_contract.arrayShape)((0, _orkestrel_contract.objectShape)({
11058
+ key: (0, _orkestrel_contract.stringShape)(),
11059
+ title: (0, _orkestrel_contract.stringShape)(),
11060
+ source: (0, _orkestrel_contract.stringShape)(),
11061
+ sentence: (0, _orkestrel_contract.numberShape)({
11062
+ integer: true,
11063
+ min: 0,
11064
+ max: Number.MAX_SAFE_INTEGER
11065
+ })
11066
+ }))
11067
+ }), { min: 1 }),
11068
+ amendments: (0, _orkestrel_contract.arrayShape)((0, _orkestrel_contract.objectShape)({
11069
+ earlier: (0, _orkestrel_contract.stringShape)(),
11070
+ later: (0, _orkestrel_contract.stringShape)()
11071
+ }))
11072
+ });
10831
11073
  /**
10832
11074
  * Describes a ledger snapshot exactly: an unknown key refuses, so a key a later version adds never reads
10833
11075
  * as absent.
@@ -10845,13 +11087,36 @@ var ledgerSnapshotShape = (0, _orkestrel_contract.objectShape)({
10845
11087
  gauge: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.objectShape)({
10846
11088
  scale: (0, _orkestrel_contract.numberShape)(),
10847
11089
  overhead: (0, _orkestrel_contract.numberShape)()
10848
- }))
11090
+ })),
11091
+ window: (0, _orkestrel_contract.optionalShape)(ledgerWindowShape)
10849
11092
  });
10850
11093
  // src/core/ledgers/contracts.ts
10851
- /** Reads the faults of a refused ledger snapshot. */
11094
+ /** Reads structural faults of a serialized exchange window. */
11095
+ var ledgerWindowContract = (0, _orkestrel_contract.createContract)(ledgerWindowShape);
11096
+ /** Reads structural faults of a ledger snapshot, including its optional held exchange window. */
10852
11097
  var ledgerSnapshotContract = (0, _orkestrel_contract.createContract)(ledgerSnapshotShape);
10853
11098
  // src/core/ledgers/validators.ts
10854
11099
  /**
11100
+ * Checks whether a value has the exact shape of a nonempty exchange window.
11101
+ * @param value - The untrusted window
11102
+ * @returns True if the window and its members are well shaped; false otherwise
11103
+ * @example
11104
+ * ```ts
11105
+ * isLedgerWindowSnapshot({ digest: 'checksum', exchanges: [{ request: 'request', reply: 'reply', lines: [] }], amendments: [] }) // true
11106
+ * ```
11107
+ */
11108
+ function isLedgerWindowSnapshot(value) {
11109
+ return (0, _orkestrel_contract.holds)(() => {
11110
+ if (!(0, _orkestrel_contract.isRecord)(value) || Object.keys(value).length !== 3 || !(0, _orkestrel_contract.isString)(value.digest)) return false;
11111
+ const { exchanges, amendments } = value;
11112
+ if (!(0, _orkestrel_contract.arrayOf)(_orkestrel_contract.isRecord)(exchanges) || exchanges.length === 0 || !(0, _orkestrel_contract.arrayOf)(_orkestrel_contract.isRecord)(amendments)) return false;
11113
+ return exchanges.every((entry) => {
11114
+ const { request, reply, copy, lines } = entry;
11115
+ return Object.keys(entry).length === 3 + Number(copy !== void 0) && (0, _orkestrel_contract.isString)(request) && (0, _orkestrel_contract.isString)(reply) && (copy === void 0 || (0, _orkestrel_contract.isString)(copy)) && (0, _orkestrel_contract.arrayOf)(_orkestrel_contract.isRecord)(lines) && lines.every((line) => Object.keys(line).length === 4 && (0, _orkestrel_contract.isString)(line.key) && (0, _orkestrel_contract.isString)(line.title) && (0, _orkestrel_contract.isString)(line.source) && isNonNegativeSafeInteger(line.sentence));
11116
+ }) && amendments.every((edge) => Object.keys(edge).length === 2 && (0, _orkestrel_contract.isString)(edge.earlier) && (0, _orkestrel_contract.isString)(edge.later));
11117
+ });
11118
+ }
11119
+ /**
10855
11120
  * Checks whether a value is finite and lies in the interval (0, 1].
10856
11121
  * @param value - The value to inspect
10857
11122
  * @returns True if the value is a valid ledger fraction; false otherwise
@@ -10902,6 +11167,8 @@ function isLedgerQuestion(value) {
10902
11167
  * guard is structural: it admits a conversation holding a stored `system` role
10903
11168
  * or a repeated id, which `requireConversationSnapshot` and the `Conversation` constructor then
10904
11169
  * refuse as the stored-data check a ledger's hydrate runs.
11170
+ * A present `window` must be nonempty and exactly shaped. This guard does not audit its references
11171
+ * or sentence ranges. Hydration discards the window when its digest disagrees or cannot be computed.
10905
11172
  *
10906
11173
  * @param value - The value to inspect, such as a parsed checkpoint
10907
11174
  * @returns True if the value has the shape of a ledger snapshot whose marked ids name messages of its own conversation; false otherwise
@@ -10915,8 +11182,8 @@ function isLedgerQuestion(value) {
10915
11182
  function isLedgerSnapshot(value) {
10916
11183
  return (0, _orkestrel_contract.holds)(() => {
10917
11184
  if (!(0, _orkestrel_contract.isRecord)(value)) return false;
10918
- const { conversation, requests, notes, failures, gauge } = value;
10919
- if (Object.keys(value).length !== (gauge === void 0 ? 4 : 5)) return false;
11185
+ const { conversation, requests, notes, failures, gauge, window } = value;
11186
+ if (Object.keys(value).length !== 4 + Number(gauge !== void 0) + Number(window !== void 0)) return false;
10920
11187
  if (!isConversationSnapshot(conversation)) return false;
10921
11188
  const isIds = (0, _orkestrel_contract.arrayOf)(_orkestrel_contract.isString);
10922
11189
  if (!isIds(requests) || !isIds(notes) || !isIds(failures)) return false;
@@ -10926,7 +11193,7 @@ function isLedgerSnapshot(value) {
10926
11193
  if (!(0, _orkestrel_contract.isFiniteNumber)(scale) || scale <= 0 || !(0, _orkestrel_contract.isFiniteNumber)(overhead) || overhead < 0) return false;
10927
11194
  }
10928
11195
  const roles = new Map(conversation.messages.map((message) => [message.id, message.role]));
10929
- return requests.every((id) => roles.get(id) === "user") && notes.every((id) => roles.get(id) === "user") && failures.every((id) => roles.get(id) === "tool");
11196
+ return requests.every((id) => roles.get(id) === "user") && notes.every((id) => roles.get(id) === "user") && failures.every((id) => roles.get(id) === "tool") && (window === void 0 || isLedgerWindowSnapshot(window));
10930
11197
  });
10931
11198
  }
10932
11199
  // src/core/ledgers/Ledger.ts
@@ -10956,6 +11223,8 @@ var Ledger = class {
10956
11223
  #scans = /* @__PURE__ */ new Set();
10957
11224
  #calibrations = /* @__PURE__ */ new Set();
10958
11225
  #gauge;
11226
+ #window = [];
11227
+ #evidence = collectLedgerEvidence([], /* @__PURE__ */ new Set(), []);
10959
11228
  /**
10960
11229
  * Composes the conversation, classifier, tools, and agent.
10961
11230
  * @param provider - The provider that serves the conversation
@@ -10989,7 +11258,8 @@ var Ledger = class {
10989
11258
  if (!isLedgerQuestion(options.questions)) throw new LedgerError("QUESTION", "questions must hold a choice category question whose criteria describe every category, a non-empty topic string, and noul amends and supersedes questions; pass the LEDGER_QUESTIONS constant for the measured wording");
10990
11259
  const snapshot = options.snapshot;
10991
11260
  if (snapshot !== void 0 && !isLedgerSnapshot(snapshot)) {
10992
- const faults = ledgerSnapshotContract.audit(snapshot);
11261
+ const candidate = options.snapshot;
11262
+ const faults = ledgerSnapshotContract.audit(candidate);
10993
11263
  throw new LedgerError("SNAPSHOT", `snapshot must be well formed and mark only messages of its conversation; pass what snapshot() returned${faults.length === 0 ? "" : `\n${(0, _orkestrel_contract.describeFaults)(faults, ledgerSnapshotContract.schema)}`}`, { context: { faults } });
10994
11264
  }
10995
11265
  this.#provider = provider;
@@ -11037,7 +11307,7 @@ var Ledger = class {
11037
11307
  }));
11038
11308
  tools.add((0, _orkestrel_tool.createTool)({
11039
11309
  name: "recall",
11040
- description: `Recall what the full conversation record holds on a topic: an owner name, an id, or one of the topics ${renderTopicNames(options.topics)}. Returns source lines, newest first.`,
11310
+ description: `Find something said earlier that isn't in the messages you can see, by an owner name, an id, or one of the topics ${renderTopicNames(options.topics)}. Returns statements and lookup findings first, then earlier requests and replies labeled by origin; newest first within each group.`,
11041
11311
  contract: (0, _orkestrel_contract.objectShape)({ topic: (0, _orkestrel_contract.stringShape)({
11042
11312
  min: 1,
11043
11313
  description: "An owner name, an id, or a topic"
@@ -11121,8 +11391,8 @@ var Ledger = class {
11121
11391
  }
11122
11392
  }
11123
11393
  /**
11124
- * Serializes the conversation, the marked ids, and the gauge into a snapshot a later ledger hydrates from.
11125
- * @returns The snapshot, each id list in conversation order and `gauge` present after the ledger holds one
11394
+ * Serializes the conversation, marked ids, gauge, and completed exchange window for a later ledger.
11395
+ * @returns The snapshot, each id list in conversation order, with a gauge and window only when held
11126
11396
  * @throws {AgentError} Thrown when a request or calibration is active (code `'CONCURRENCY'`)
11127
11397
  */
11128
11398
  snapshot() {
@@ -11131,12 +11401,31 @@ var Ledger = class {
11131
11401
  const conversation = this.#conversation.snapshot();
11132
11402
  const ids = conversation.messages.map((message) => message.id);
11133
11403
  const gauge = this.gauge;
11404
+ const window = {
11405
+ exchanges: this.#window.map((entry) => ({
11406
+ request: entry.request.id,
11407
+ ...entry.request.content === this.#conversation.message(entry.request.id)?.content ? {} : { copy: entry.request.content },
11408
+ reply: entry.reply.id,
11409
+ lines: entry.lines.map(({ key, title, source, sentence }) => ({
11410
+ key,
11411
+ title,
11412
+ source,
11413
+ sentence
11414
+ }))
11415
+ })),
11416
+ amendments: [...this.#evidence.amendments.values()].map((edge) => ({ ...edge }))
11417
+ };
11418
+ const digest = window.exchanges.length === 0 ? void 0 : digestLedgerWindow(window.exchanges, conversation.messages);
11134
11419
  return {
11135
11420
  conversation,
11136
11421
  requests: ids.filter((id) => this.#requests.has(id)),
11137
11422
  notes: ids.filter((id) => this.#annotations.has(id)),
11138
11423
  failures: ids.filter((id) => this.#results.get(id)?.success === false),
11139
- ...gauge === void 0 ? {} : { gauge }
11424
+ ...gauge === void 0 ? {} : { gauge },
11425
+ ...window.exchanges.length === 0 || digest === void 0 ? {} : { window: {
11426
+ ...window,
11427
+ digest
11428
+ } }
11140
11429
  };
11141
11430
  }
11142
11431
  /**
@@ -11154,6 +11443,7 @@ var Ledger = class {
11154
11443
  if (this.#busy()) throw new AgentError("CONCURRENCY", "a ledger request is already active");
11155
11444
  const caller = (0, _orkestrel_abort.createAbort)({ ...signal === void 0 ? {} : { signal } }).signal;
11156
11445
  const request = new LedgerRequest(content, this.#options.recall?.limit);
11446
+ const scope = this.#agent.context.scope;
11157
11447
  this.#live.add(request);
11158
11448
  try {
11159
11449
  if (this.#gauge === void 0) await this.#measureGauge(caller);
@@ -11179,7 +11469,8 @@ var Ledger = class {
11179
11469
  content: LEDGER_NOTES.cue
11180
11470
  }).id);
11181
11471
  const previous = this.#agent.context.scope;
11182
- this.#agent.context.apply(createScope({
11472
+ const answer = createScope({
11473
+ ...previous,
11183
11474
  name: "answer",
11184
11475
  tools: [],
11185
11476
  select: async (conversation, message, runSignal) => {
@@ -11190,11 +11481,12 @@ var Ledger = class {
11190
11481
  messages: selection.messages.filter((one) => one.role !== "tool" && !hasCalls(one))
11191
11482
  };
11192
11483
  }
11193
- }));
11484
+ });
11485
+ this.#agent.context.apply(answer);
11194
11486
  try {
11195
11487
  passes.push(await this.#generatePass(request, caller, false));
11196
11488
  } finally {
11197
- this.#agent.context.apply(previous);
11489
+ if (this.#agent.context.scope === answer) this.#agent.context.apply(previous);
11198
11490
  }
11199
11491
  }
11200
11492
  const last = passes.at(-1) ?? first;
@@ -11213,7 +11505,9 @@ var Ledger = class {
11213
11505
  ...last.stop === void 0 ? {} : { stop: last.stop }
11214
11506
  };
11215
11507
  } finally {
11508
+ if (request.scope !== void 0 && this.#agent.context.scope === request.scope) this.#agent.context.apply(scope);
11216
11509
  this.#flush();
11510
+ this.#commitExchange(request);
11217
11511
  this.#live.delete(request);
11218
11512
  }
11219
11513
  }
@@ -11276,6 +11570,37 @@ var Ledger = class {
11276
11570
  for (const id of snapshot.requests) this.#requests.add(id);
11277
11571
  for (const id of snapshot.notes) this.#annotations.add(id);
11278
11572
  const messages = this.#conversation.messages();
11573
+ if (snapshot.window !== void 0 && digestLedgerWindow(snapshot.window.exchanges, messages) === snapshot.window.digest) {
11574
+ const window = snapshot.window.exchanges.map((entry) => {
11575
+ const request = this.#conversation.message(entry.request);
11576
+ const reply = this.#conversation.message(entry.reply);
11577
+ if (request === void 0 || reply === void 0) throw new LedgerError("SNAPSHOT", "window pair must belong to its conversation");
11578
+ const lines = entry.lines.map((line) => {
11579
+ const source = this.#conversation.message(line.source);
11580
+ if (source === void 0) throw new LedgerError("SNAPSHOT", "window source must belong to its conversation");
11581
+ return {
11582
+ ...line,
11583
+ text: splitSentences(source.content)[line.sentence] ?? "",
11584
+ topics: [],
11585
+ role: source.role
11586
+ };
11587
+ });
11588
+ return {
11589
+ request: {
11590
+ ...request,
11591
+ content: entry.copy ?? request.content
11592
+ },
11593
+ reply,
11594
+ lines
11595
+ };
11596
+ });
11597
+ this.#window = window;
11598
+ this.#evidence = collectLedgerEvidence(messages, new Set(this.#window.flatMap((entry) => [
11599
+ entry.request.id,
11600
+ entry.reply.id,
11601
+ ...entry.lines.map((line) => line.source)
11602
+ ])), snapshot.window.amendments);
11603
+ }
11279
11604
  const groups = collectToolGroups(messages);
11280
11605
  for (const message of messages) {
11281
11606
  if (!snapshot.failures.includes(message.id)) continue;
@@ -11406,20 +11731,26 @@ var Ledger = class {
11406
11731
  const { attempts, failure, ...filed } = classified;
11407
11732
  const planned = (0, _orkestrel_contract.attempt)(() => {
11408
11733
  if (filed.fault !== void 0) throw filed.fault;
11734
+ const { draft, ...plan } = this.#plan(selected, this.#rescan());
11409
11735
  return {
11410
- ...this.#plan(selected, this.#rescan()),
11411
- ...filed,
11412
- ...failure === void 0 ? {} : { fault: failure }
11736
+ draft,
11737
+ selection: {
11738
+ ...plan,
11739
+ ...filed,
11740
+ ...failure === void 0 ? {} : { fault: failure }
11741
+ }
11413
11742
  };
11414
11743
  });
11415
- request.file(attempts, planned.success ? planned.value : void 0);
11744
+ request.file(attempts, planned.success ? planned.value.selection : void 0, planned.success ? planned.value.draft : void 0);
11416
11745
  if (!planned.success) return this.#refuse(planned.error, filed, this.#conversation);
11746
+ const selection = planned.value.selection;
11417
11747
  return this.#annotations.has(message.id) ? {
11418
- ...planned.value,
11419
- messages: [...planned.value.messages, ...this.#collectAfter(selected)]
11420
- } : planned.value;
11748
+ ...selection,
11749
+ messages: [...selection.messages, ...this.#collectAfter(selected)]
11750
+ } : selection;
11421
11751
  }
11422
11752
  #refuse(error, filed, conversation) {
11753
+ if (conversation === this.#conversation && !(isLedgerError(error) && error.code === "REQUEST")) this.#current()?.file([]);
11423
11754
  return {
11424
11755
  ...filed,
11425
11756
  messages: conversation.view(),
@@ -11446,7 +11777,11 @@ var Ledger = class {
11446
11777
  topics: [...near]
11447
11778
  });
11448
11779
  const total = Math.max(0, (this.#options.capacity - this.#predict) * this.#share.prompt - (this.#gauge?.overhead ?? 0)) / (1 + LEDGER_SCALE_DRIFT);
11449
- const tail = this.#selectTail(request, total * this.#share.tail, input, scan);
11780
+ const built = this.#agent.context.build({
11781
+ messages: [request],
11782
+ judgments: []
11783
+ });
11784
+ const tail = this.#selectTail(request, total, input, scan);
11450
11785
  const tailIds = new Set(tail.map((message) => message.id));
11451
11786
  const scoped = records.some((record) => record.key !== LEDGER_RULES_KEY);
11452
11787
  const held = /* @__PURE__ */ new Set([...projection.records.filter((record) => scoped || record.key === "rules").flatMap((record) => record.members), ...records.length > 0 ? projection.records.flatMap((record) => record.members.filter((id) => this.#conversation.message(id)?.role === "user" && this.#classifier.category(id) === "rule" && this.#classifier.decisive(id))) : []]);
@@ -11494,25 +11829,121 @@ var Ledger = class {
11494
11829
  source: unit.source,
11495
11830
  record: this.#buildUnitRecord(unit.source, unit.category, input, projection, held)
11496
11831
  }));
11497
- const trimmed = this.#trim(steps, records, mapped, new Set(ordered.map((unit) => unit.source)), request, tail, total, projection.amendments, scan);
11498
- const shown = /* @__PURE__ */ new Set([...trimmed.included, ...records.flatMap((record) => record.members.filter((source) => {
11499
- const full = record.lines.filter((line) => line.source === source);
11500
- const visible = trimmed.records.find((one) => one.key === record.key)?.lines.filter((line) => line.source === source) ?? [];
11501
- return full.length > 0 && full.length === visible.length;
11502
- }))]);
11503
- return { messages: [...tail.slice(0, -1).map((message) => message.role === "tool" ? this.#renderTailStub(message, scan, shown) : message), buildRequestMessage(request, trimmed.rendered)] };
11504
- }
11505
- #trim(steps, records, mapped, included, request, tail, total, amendments, scan) {
11506
- const built = this.#agent.context.build({
11507
- messages: [request],
11508
- judgments: []
11832
+ const trimmed = this.#trim(steps, records, mapped, new Set(ordered.map((unit) => unit.source)), built, tail, total, projection.amendments, scan);
11833
+ const replies = collectLedgerReplies(scan.messages, this.#requests, this.#annotations);
11834
+ const sources = new Set(this.#window.flatMap((entry) => [
11835
+ entry.request.id,
11836
+ entry.reply.id,
11837
+ ...entry.lines.map((line) => line.source)
11838
+ ]));
11839
+ const evidence = collectLedgerEvidence(scan.messages, sources, projection.amendments);
11840
+ const prefix = this.#window.flatMap((entry) => entry.lines);
11841
+ const prior = buildLedgerRenderContext(scan.messages, prefix, projection.amendments);
11842
+ const combined = buildLedgerRenderContext(scan.messages, [...prefix, ...trimmed.rendered.lines], projection.amendments);
11843
+ const stale = evidence.messages.size !== this.#evidence.messages.size || [...evidence.messages].some((receipt) => !this.#evidence.messages.has(receipt)) || evidence.amendments.size !== this.#evidence.amendments.size || [...evidence.amendments.keys()].some((receipt) => !this.#evidence.amendments.has(receipt)) || this.#window.some((entry) => replies.get(entry.request.id)?.id !== entry.reply.id || entry.lines.some((line) => !live.has(line.source))) || projection.amendments.some((edge) => prior.first.has(edge.earlier) && prior.complete.has(edge.later) !== combined.complete.has(edge.later) || prior.first.has(edge.later) && combined.complete.has(edge.later) && !prior.first.has(edge.earlier) && combined.first.has(edge.earlier));
11844
+ const last = stale ? this.#window.at(-1) : void 0;
11845
+ const retained = last === void 0 || replies.get(last.request.id)?.id !== last.reply.id ? void 0 : this.#conversation.message(last.request.id);
11846
+ if (stale) this.#window = [];
11847
+ const [previous, reply] = this.#window.length === 0 ? this.#selectExchange(request, input) : [];
11848
+ const window = this.#window.length > 0 ? this.#window : previous !== void 0 && reply !== void 0 ? [{
11849
+ request: previous,
11850
+ reply,
11851
+ lines: []
11852
+ }] : retained !== void 0 && last !== void 0 ? [{
11853
+ request: retained,
11854
+ reply: last.reply,
11855
+ lines: []
11856
+ }] : [];
11857
+ if (this.#window.length === 0) {
11858
+ const derived = collectLedgerEvidence(scan.messages, new Set(window.flatMap((entry) => [entry.request.id, entry.reply.id])), projection.amendments);
11859
+ this.#window = window;
11860
+ this.#evidence = derived;
11861
+ }
11862
+ const carried = window.flatMap((entry) => entry.lines);
11863
+ const delta = this.#render(trimmed.records, mapped, trimmed.included, projection.amendments, scan, new Set(carried.map(buildLedgerLineKey)), carried);
11864
+ const newest = window.at(-1);
11865
+ const plain = newest === void 0 ? void 0 : this.#conversation.message(newest.request.id);
11866
+ const fallback = newest === void 0 || plain === void 0 ? [] : [{
11867
+ request: plain,
11868
+ reply: newest.reply,
11869
+ lines: []
11870
+ }];
11871
+ const empty = {
11872
+ window: [],
11873
+ rendered: trimmed.rendered
11874
+ };
11875
+ const rungs = [
11876
+ {
11877
+ window,
11878
+ rendered: delta
11879
+ },
11880
+ {
11881
+ window: fallback,
11882
+ rendered: trimmed.rendered
11883
+ },
11884
+ empty
11885
+ ];
11886
+ const probes = rungs.map((rung) => [...built.slice(0, -1), ...stripThinking([
11887
+ ...tail.slice(0, -1),
11888
+ ...rung.window.flatMap((entry) => [entry.request, entry.reply]),
11889
+ buildRequestMessage(built.at(-1) ?? request, rung.rendered.text)
11890
+ ], this.#replay)]);
11891
+ const names = this.#agent.context.scope?.tools;
11892
+ const definitions = this.#agent.context.tools.definitions();
11893
+ const offered = rungs.map((rung) => hasHiddenRecall(input, request.id, this.#requests, this.#annotations, [...tail.slice(0, -1), ...rung.window.flatMap((entry) => [entry.request, entry.reply])], [...rung.window.flatMap((entry) => entry.lines), ...rung.rendered.lines]));
11894
+ const longest = this.#findLongest();
11895
+ const closed = probes.map((messages, at) => {
11896
+ const tools = definitions.filter((tool) => (names === void 0 || names.includes(tool.name)) && (tool.name !== "recall" || offered[at]));
11897
+ const calls = [{
11898
+ estimate: estimateMessages(messages),
11899
+ tools: tools.length,
11900
+ spared: this.#spared(tools)
11901
+ }];
11902
+ return offered[at] === true && this.#gauge !== void 0 && this.#gauge.remainder(calls) - this.#predict < 2 * this.#gauge.reserve(calls, longest);
11509
11903
  });
11904
+ const prices = probes.map((messages) => this.#price(messages));
11905
+ const admitted = rungs.find((_rung, at) => (prices[at] ?? Infinity) <= total && (closed[2] === true || closed[at] === false)) ?? empty;
11906
+ if (offered[rungs.indexOf(admitted)] === false && (names === void 0 || names.includes("recall"))) {
11907
+ const tools = definitions.filter((tool) => tool.name !== "recall").map((tool) => tool.name);
11908
+ const narrowed = this.#agent.context.scope?.narrow({ tools }) ?? createScope({
11909
+ name: "ledger",
11910
+ tools
11911
+ });
11912
+ this.#current()?.apply(narrowed);
11913
+ this.#agent.context.apply(narrowed);
11914
+ }
11915
+ const copy = buildRequestMessage(request, admitted.rendered.text);
11916
+ const kept = admitted.window.flatMap((entry) => entry.lines);
11917
+ const origins = /* @__PURE__ */ new Set([
11918
+ request.id,
11919
+ ...admitted.window.flatMap((entry) => [entry.request.id, entry.reply.id]),
11920
+ ...kept.map((line) => line.source),
11921
+ ...admitted.rendered.lines.map((line) => line.source)
11922
+ ]);
11923
+ const draft = {
11924
+ copy,
11925
+ lines: admitted.rendered.lines,
11926
+ evidence: collectLedgerEvidence(scan.messages, origins, projection.amendments),
11927
+ window: admitted.window
11928
+ };
11929
+ const shown = buildLedgerRenderContext(scan.messages, [...kept, ...admitted.rendered.lines], []).complete;
11930
+ return {
11931
+ draft,
11932
+ messages: [
11933
+ ...tail.slice(0, -1).map((message) => message.role === "tool" ? this.#renderTailStub(message, scan, shown) : message),
11934
+ ...admitted.window.flatMap((entry) => [entry.request, entry.reply]),
11935
+ copy
11936
+ ]
11937
+ };
11938
+ }
11939
+ #trim(steps, records, mapped, included, built, tail, total, amendments, scan) {
11510
11940
  const head = built.slice(0, -1);
11511
- const priced = built.at(-1) ?? request;
11941
+ const priced = built.at(-1);
11942
+ if (priced === void 0) throw new Error("a built plan holds its request");
11512
11943
  const found = findLedgerCut(0, steps.length, (count) => {
11513
11944
  const cut = buildLedgerCut(steps.slice(0, count), records, included);
11514
11945
  const rendered = this.#render(cut.records, mapped, cut.included, amendments, scan);
11515
- const copy = buildRequestMessage(priced, rendered);
11946
+ const copy = buildRequestMessage(priced, rendered.text);
11516
11947
  return this.#price([...head, ...stripThinking([...tail.slice(0, -1), copy], this.#replay)]) <= total ? {
11517
11948
  ...cut,
11518
11949
  rendered
@@ -11537,36 +11968,48 @@ var Ledger = class {
11537
11968
  const live = collectProjectionIds(projection);
11538
11969
  return scanAmendments(input.classification.amendments, source, (id) => id === source || !held.has(id) && live.has(id)).flatMap((id) => this.#projectLines(input, id));
11539
11970
  }
11540
- #render(records, mapped, included, amendments, scan) {
11541
- const entries = mapped.filter((unit) => included.has(unit.source)).flatMap(({ record: unit }) => [...Map.groupBy(unit.lines, (line) => line.source)].map(([source, lines]) => ({
11971
+ #render(records, mapped, included, amendments, scan, held = /* @__PURE__ */ new Set(), prefix = []) {
11972
+ const population = records.map((record) => ({
11973
+ ...record,
11974
+ lines: collectLedgerLines(record, held)
11975
+ }));
11976
+ const entries = mapped.filter((unit) => included.has(unit.source)).flatMap(({ record: unit }) => [...Map.groupBy(collectLedgerLines(unit, /* @__PURE__ */ new Set()), (line) => line.source)].map(([source, lines]) => ({
11542
11977
  unit,
11543
11978
  source,
11544
11979
  lines
11545
11980
  })));
11546
11981
  const first = new Map(entries.toReversed().map((entry) => [entry.source, entry]));
11547
- const unique = entries.filter((entry) => first.get(entry.source) === entry);
11548
- const record = records.find((candidate) => candidate.key === "rules" && candidate.lines.length > 0);
11549
- const ownerRecords = records.filter((candidate) => candidate.key !== "rules" && candidate.lines.length > 0);
11550
- const context = buildLedgerRenderContext(this.#conversation.messages(), [
11982
+ const unique = entries.filter((entry) => first.get(entry.source) === entry).map((entry) => ({
11983
+ ...entry,
11984
+ lines: entry.lines.filter((line) => !prefix.some((prior) => prior.source === line.source && prior.sentence === line.sentence))
11985
+ })).filter((entry) => entry.lines.length > 0);
11986
+ const record = population.find((candidate) => candidate.key === "rules" && candidate.lines.length > 0);
11987
+ const ownerRecords = population.filter((candidate) => candidate.key !== "rules" && candidate.lines.length > 0);
11988
+ const lines = [
11551
11989
  ...ownerRecords.flatMap((candidate) => candidate.lines),
11552
11990
  ...unique.filter((entry) => entry.unit.key !== LEDGER_RULES_KEY).flatMap((entry) => entry.lines),
11553
11991
  ...record?.lines ?? [],
11554
11992
  ...unique.filter((entry) => entry.unit.key === LEDGER_RULES_KEY).flatMap((entry) => entry.lines)
11555
- ], amendments);
11993
+ ];
11994
+ const context = buildLedgerRenderContext(scan.messages, [...prefix, ...lines], amendments);
11556
11995
  const owners = ownerRecords.map((owner) => renderLedgerPinned(owner, context)).join("\n\n");
11557
- const rendered = unique.map(({ unit, source, lines }) => {
11996
+ const rendered = unique.map(({ unit, source, lines: retained }) => {
11558
11997
  const sentences = splitSentences(this.#conversation.message(source)?.content ?? "");
11559
11998
  return {
11560
11999
  rules: unit.key === LEDGER_RULES_KEY,
11561
- texts: unit.key === "rules" && source === unit.members[0] ? lines.map((line) => this.#renderSource(source, sentences, [line], context, scan)) : [this.#renderSource(source, sentences, lines, context, scan)]
12000
+ texts: unit.key === "rules" && source === unit.members[0] ? retained.map((line) => this.#renderSource(source, sentences, [line], context, scan)) : [this.#renderSource(source, sentences, retained, context, scan)]
11562
12001
  };
11563
12002
  });
11564
12003
  const pinned = rendered.filter((one) => !one.rules).flatMap((one) => one.texts);
11565
12004
  const rules = rendered.filter((one) => one.rules).flatMap((one) => one.texts);
11566
12005
  const body = [owners, pinned.join("\n")].filter((text) => text !== "").join("\n\n");
11567
- return [body === "" ? "" : `## Records\n${body}`, record === void 0 ? rules.length === 0 ? "" : ["## Rules", ...rules].join("\n") : [renderLedgerRecord(record, context), ...rules].join("\n")].filter((text) => text !== "").join("\n\n");
12006
+ return {
12007
+ text: [body === "" ? "" : `## Records\n${body}`, record === void 0 ? rules.length === 0 ? "" : ["## Rules", ...rules].join("\n") : [renderLedgerRecord(record, context), ...rules].join("\n")].filter((part) => part !== "").join("\n\n"),
12008
+ lines
12009
+ };
11568
12010
  }
11569
- #selectTail(request, cap, input, scan) {
12011
+ #selectTail(request, total, input, scan) {
12012
+ const cap = total * this.#share.tail;
11570
12013
  const first = input.messages.findIndex((message) => this.#requests.has(message.id));
11571
12014
  const seed = input.messages.slice(0, first < 0 ? input.messages.findIndex((message) => message.id === request.id) : first);
11572
12015
  const groups = collectToolGroups(seed).flatMap((group) => {
@@ -11597,6 +12040,29 @@ var Ledger = class {
11597
12040
  const stop = exchanges.findIndex((_exchange, index) => this.#price(stripThinking([...exchanges.slice(0, index + 1).toReversed().flat(), request], this.#replay)) > cap);
11598
12041
  return [...exchanges.slice(0, stop === -1 ? exchanges.length : stop).toReversed().flat(), request];
11599
12042
  }
12043
+ #selectExchange(request, input) {
12044
+ const before = input.messages.slice(0, input.messages.findIndex((message) => message.id === request.id));
12045
+ const previous = before.findLast((message) => this.#requests.has(message.id));
12046
+ const reply = previous === void 0 ? void 0 : collectLedgerReplies(before, this.#requests, this.#annotations).get(previous.id);
12047
+ return previous !== void 0 && reply !== void 0 ? [previous, reply] : [];
12048
+ }
12049
+ #commitExchange(request) {
12050
+ const draft = request.sent;
12051
+ const message = request.message;
12052
+ if (message === void 0) return;
12053
+ const reply = collectLedgerReplies(this.#conversation.messages(), this.#requests, this.#annotations).get(message.id);
12054
+ if (reply === void 0) return;
12055
+ this.#window = [...draft?.window ?? this.#window, {
12056
+ request: draft?.copy ?? message,
12057
+ reply,
12058
+ lines: draft?.lines ?? []
12059
+ }];
12060
+ const evidence = draft?.evidence ?? this.#evidence;
12061
+ this.#evidence = {
12062
+ messages: /* @__PURE__ */ new Set([...evidence.messages, ...collectLedgerEvidence([message, reply], /* @__PURE__ */ new Set([message.id, reply.id]), []).messages]),
12063
+ amendments: evidence.amendments
12064
+ };
12065
+ }
11600
12066
  #renderTailStub(message, scan, shown) {
11601
12067
  const group = scan.group(message.id);
11602
12068
  const call = group === void 0 ? void 0 : findLedgerCall(group, message);
@@ -11605,7 +12071,7 @@ var Ledger = class {
11605
12071
  const visible = renderStub(call?.name ?? "tool", call?.arguments ?? {}, "shown");
11606
12072
  return {
11607
12073
  ...message,
11608
- content: renderStub(call?.name ?? "tool", call?.arguments ?? {}, this.#results.get(message.id)?.success === false ? "failed" : finding?.result === void 0 ? "empty" : shown === void 0 ? hidden.length > visible.length ? "hidden" : "shown" : shown.has(message.id) ? "shown" : "hidden")
12074
+ content: renderStub(call?.name ?? "tool", call?.arguments ?? {}, this.#results.get(message.id)?.success === false ? "failed" : finding?.result === void 0 ? "empty" : shown === void 0 ? hidden.length > visible.length ? "hidden" : "shown" : scan.findings.some((other) => shown.has(other.id) && matchesLookup(finding, other)) ? "shown" : "hidden")
11609
12075
  };
11610
12076
  }
11611
12077
  #observeTurn() {
@@ -11616,11 +12082,19 @@ var Ledger = class {
11616
12082
  const additions = this.#conversation.messages().slice(boundary);
11617
12083
  const messages = this.#agent.context.build(request.selection);
11618
12084
  const names = this.#agent.context.scope?.tools;
12085
+ const tools = this.#agent.context.tools.definitions().filter((tool) => names === void 0 || names.includes(tool.name));
11619
12086
  request.observe({
11620
12087
  estimate: estimateMessages(stripThinking([...messages, ...additions], this.#replay)),
11621
- tools: this.#agent.context.tools.definitions().filter((tool) => names === void 0 || names.includes(tool.name)).length
12088
+ tools: tools.length,
12089
+ spared: this.#spared(tools)
11622
12090
  }, this.#conversation.messages().length);
11623
12091
  }
12092
+ #spared(tools) {
12093
+ const overhead = this.#gauge?.overhead ?? 0;
12094
+ if (tools.length === 0) return overhead;
12095
+ const missing = this.#agent.context.tools.definitions().filter((tool) => !tools.some((one) => one.name === tool.name));
12096
+ return missing.length === 0 ? 0 : Math.min(overhead, estimateTokens(JSON.stringify(missing)) * (this.#gauge?.scale ?? 1));
12097
+ }
11624
12098
  #findLongest() {
11625
12099
  return this.#conversation.messages().filter((message) => message.role === "assistant").reduce((longest, message) => message.content.length > longest.length ? message.content : longest, "");
11626
12100
  }
@@ -11673,11 +12147,12 @@ var Ledger = class {
11673
12147
  const source = listable.get(id);
11674
12148
  if (source === void 0) return [];
11675
12149
  const lookup = scan.finding(id);
11676
- const text = lookup === void 0 ? source.content : `${renderLookupCall(lookup.name, lookup.arguments)}: ${source.content}`;
12150
+ const content = source.content.replace(/(^|[\n\v\f\r\u0085\u2028\u2029])(?=Earlier (?:request|reply):)/g, "$1 ");
12151
+ const text = lookup === void 0 ? content : `${renderLookupCall(lookup.name, lookup.arguments)}: ${content}`;
11677
12152
  return [{
11678
12153
  id,
11679
12154
  text,
11680
- stored: lookup === void 0 ? [] : [[splitResultLines(text)[0] ?? "", splitResultLines(source.content)[0] ?? ""]]
12155
+ stored: lookup === void 0 ? [] : [[splitResultLines(text)[0] ?? "", splitResultLines(content)[0] ?? ""]]
11681
12156
  }];
11682
12157
  });
11683
12158
  return {
@@ -11690,7 +12165,14 @@ var Ledger = class {
11690
12165
  listed: [],
11691
12166
  recalled: /* @__PURE__ */ new Map()
11692
12167
  });
11693
- const result = gathered.listed.length === 0 ? `nothing on "${topic}"; recall ${guidance}` : cutListing(gathered.listed, room);
12168
+ const earlier = input.messages.slice(0, Math.max(0, start)).reverse().flatMap((message) => {
12169
+ if (gathered.done.has(message.id) || this.#annotations.has(message.id)) return [];
12170
+ const label = this.#requests.has(message.id) ? LEDGER_RECALL_LABELS.request : message.role === "assistant" && !hasCalls(message) && message.content.trim() !== "" ? LEDGER_RECALL_LABELS.reply : void 0;
12171
+ if (label === void 0 || !searches.some((search) => search.words.length > 0 && search.words.every((word) => message.content.toLowerCase().includes(word)))) return [];
12172
+ return [`${label} ${message.content.replace(/[\n\v\f\r\u0085\u2028\u2029]+/g, "\n ")}`];
12173
+ });
12174
+ const listed = [...gathered.listed, ...earlier];
12175
+ const result = listed.length === 0 ? `nothing earlier on "${topic}"; answer from the messages you can see` : cutListing(listed, room, earlier.length > 0 ? "more" : "older");
11694
12176
  request?.list(result, gathered.recalled);
11695
12177
  return result;
11696
12178
  } finally {
@@ -11703,7 +12185,7 @@ var Ledger = class {
11703
12185
  const result = this.#results.get(message.id);
11704
12186
  if (message.role !== "tool" || result?.success !== true) return [];
11705
12187
  const recalled = result.name === "recall";
11706
- if (recalled && /^nothing on /.test(message.content)) return [];
12188
+ if (recalled && /^nothing earlier on /.test(message.content)) return [];
11707
12189
  const stored = recalled ? request.lines(message.content) : void 0;
11708
12190
  return splitResultLines(message.content).filter((line) => !recalled || !matchesCutLine(line)).map((line) => stored?.get(line) ?? line);
11709
12191
  }))];
@@ -11768,6 +12250,7 @@ exports.LEDGER_CATEGORIES = LEDGER_CATEGORIES;
11768
12250
  exports.LEDGER_NOTES = LEDGER_NOTES;
11769
12251
  exports.LEDGER_OWNER_PREFIX = LEDGER_OWNER_PREFIX;
11770
12252
  exports.LEDGER_QUESTIONS = LEDGER_QUESTIONS;
12253
+ exports.LEDGER_RECALL_LABELS = LEDGER_RECALL_LABELS;
11771
12254
  exports.LEDGER_RULES_KEY = LEDGER_RULES_KEY;
11772
12255
  exports.LEDGER_SCALE_DRIFT = LEDGER_SCALE_DRIFT;
11773
12256
  exports.LEDGER_SLICE = LEDGER_SLICE;
@@ -11826,6 +12309,7 @@ exports.buildJudgeResult = buildJudgeResult;
11826
12309
  exports.buildJudgments = buildJudgments;
11827
12310
  exports.buildLedgerCut = buildLedgerCut;
11828
12311
  exports.buildLedgerIndex = buildLedgerIndex;
12312
+ exports.buildLedgerLineKey = buildLedgerLineKey;
11829
12313
  exports.buildLedgerRenderContext = buildLedgerRenderContext;
11830
12314
  exports.buildLines = buildLines;
11831
12315
  exports.buildProviderHeaders = buildProviderHeaders;
@@ -11840,9 +12324,13 @@ exports.chargeUsage = chargeUsage;
11840
12324
  exports.collectAmendments = collectAmendments;
11841
12325
  exports.collectExchanges = collectExchanges;
11842
12326
  exports.collectImageData = collectImageData;
12327
+ exports.collectLedgerEvidence = collectLedgerEvidence;
12328
+ exports.collectLedgerLines = collectLedgerLines;
12329
+ exports.collectLedgerReplies = collectLedgerReplies;
11843
12330
  exports.collectLive = collectLive;
11844
12331
  exports.collectNames = collectNames;
11845
12332
  exports.collectProjectionIds = collectProjectionIds;
12333
+ exports.collectRecallMessages = collectRecallMessages;
11846
12334
  exports.collectRegistry = collectRegistry;
11847
12335
  exports.collectToolGroups = collectToolGroups;
11848
12336
  exports.computeThinking = computeThinking;
@@ -11879,6 +12367,7 @@ exports.denyCall = denyCall;
11879
12367
  exports.describeQuestionRefusal = describeQuestionRefusal;
11880
12368
  exports.describeRelayRefusal = describeRelayRefusal;
11881
12369
  exports.describeSnapshotRefusal = describeSnapshotRefusal;
12370
+ exports.digestLedgerWindow = digestLedgerWindow;
11882
12371
  exports.estimateMessages = estimateMessages;
11883
12372
  exports.estimateRequest = estimateRequest;
11884
12373
  exports.estimateTokens = estimateTokens;
@@ -11894,6 +12383,7 @@ exports.fitSlope = fitSlope;
11894
12383
  exports.freezeJSON = freezeJSON;
11895
12384
  exports.handleAgentQueueJob = handleAgentQueueJob;
11896
12385
  exports.hasCalls = hasCalls;
12386
+ exports.hasHiddenRecall = hasHiddenRecall;
11897
12387
  exports.identifyLookup = identifyLookup;
11898
12388
  exports.inferStop = inferStop;
11899
12389
  exports.intersectKeys = intersectKeys;
@@ -11915,6 +12405,7 @@ exports.isJudgment = isJudgment;
11915
12405
  exports.isLedgerError = isLedgerError;
11916
12406
  exports.isLedgerQuestion = isLedgerQuestion;
11917
12407
  exports.isLedgerSnapshot = isLedgerSnapshot;
12408
+ exports.isLedgerWindowSnapshot = isLedgerWindowSnapshot;
11918
12409
  exports.isMemo = isMemo;
11919
12410
  exports.isMemoOrigin = isMemoOrigin;
11920
12411
  exports.isMemoSnapshot = isMemoSnapshot;
@@ -11938,11 +12429,14 @@ exports.judgmentContract = judgmentContract;
11938
12429
  exports.judgmentShape = judgmentShape;
11939
12430
  exports.ledgerSnapshotContract = ledgerSnapshotContract;
11940
12431
  exports.ledgerSnapshotShape = ledgerSnapshotShape;
12432
+ exports.ledgerWindowContract = ledgerWindowContract;
12433
+ exports.ledgerWindowShape = ledgerWindowShape;
11941
12434
  exports.linkOwners = linkOwners;
11942
12435
  exports.matchEntities = matchEntities;
11943
12436
  exports.matchesCutLine = matchesCutLine;
11944
12437
  exports.matchesJSON = matchesJSON;
11945
12438
  exports.matchesJudgment = matchesJudgment;
12439
+ exports.matchesLookup = matchesLookup;
11946
12440
  exports.measureAnswer = measureAnswer;
11947
12441
  exports.measureMessage = measureMessage;
11948
12442
  exports.measureRequest = measureRequest;