pi-tuck 1.0.1 → 1.0.4

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.
Files changed (50) hide show
  1. package/CHANGELOG.md +65 -3
  2. package/README.md +138 -57
  3. package/dist/index.d.ts +2 -1
  4. package/dist/index.js +7 -4
  5. package/dist/src/budget.d.ts +17 -4
  6. package/dist/src/budget.js +33 -10
  7. package/dist/src/build-id.d.ts +1 -1
  8. package/dist/src/build-id.js +1 -1
  9. package/dist/src/compaction.d.ts +7 -0
  10. package/dist/src/compaction.js +32 -16
  11. package/dist/src/diagnostics.d.ts +2 -1
  12. package/dist/src/omissions.d.ts +1 -1
  13. package/dist/src/omissions.js +8 -8
  14. package/dist/src/tuck/core/brief.d.ts +13 -43
  15. package/dist/src/tuck/core/brief.js +41 -255
  16. package/dist/src/tuck/core/filter-noise.js +2 -1
  17. package/dist/src/tuck/core/format.d.ts +2 -6
  18. package/dist/src/tuck/core/format.js +4 -10
  19. package/dist/src/tuck/core/normalize.js +22 -50
  20. package/dist/src/tuck/core/shrink.d.ts +33 -0
  21. package/dist/src/tuck/core/shrink.js +218 -0
  22. package/dist/src/tuck/core/summarize.d.ts +15 -7
  23. package/dist/src/tuck/core/summarize.js +17 -22
  24. package/dist/src/tuck/core/transcript.d.ts +24 -13
  25. package/dist/src/tuck/core/transcript.js +251 -83
  26. package/dist/src/tuck/types.d.ts +4 -21
  27. package/dist/src/tuck.d.ts +8 -10
  28. package/dist/src/tuck.js +23 -25
  29. package/index.ts +8 -5
  30. package/package.json +1 -1
  31. package/src/budget.ts +29 -9
  32. package/src/build-id.ts +1 -1
  33. package/src/compaction.ts +33 -14
  34. package/src/diagnostics.ts +1 -1
  35. package/src/omissions.ts +8 -8
  36. package/src/tuck/core/brief.ts +48 -265
  37. package/src/tuck/core/filter-noise.ts +2 -1
  38. package/src/tuck/core/format.ts +4 -11
  39. package/src/tuck/core/normalize.ts +21 -50
  40. package/src/tuck/core/shrink.ts +217 -0
  41. package/src/tuck/core/summarize.ts +26 -24
  42. package/src/tuck/core/transcript.ts +238 -82
  43. package/src/tuck/types.ts +9 -20
  44. package/src/tuck.ts +30 -32
  45. package/dist/src/tuck/core/brief-meter.d.ts +0 -42
  46. package/dist/src/tuck/core/brief-meter.js +0 -273
  47. package/dist/src/tuck/core/rank.d.ts +0 -42
  48. package/dist/src/tuck/core/rank.js +0 -292
  49. package/src/tuck/core/brief-meter.ts +0 -235
  50. package/src/tuck/core/rank.ts +0 -331
@@ -5,7 +5,7 @@ import { exportSource } from "./source-export.js";
5
5
  import { isDeepStrictEqual } from "node:util";
6
6
  import { assertNativeToolRounds } from "./protocol.js";
7
7
  import { projectedMessages } from "./omissions.js";
8
- import { bandOf, chooseCut, conversationTokens, decideCut, gateCut, incomingEventTokens, incomingTokens, measurableConversationTokens, planningReserve, projectedConversationTokens, requestTokens, systemPart } from "./budget.js";
8
+ import { DEFAULT_CUT_AT, bandOf, chooseCut, conversationTokens, decideCut, gateCut, incomingEventTokens, incomingTokens, measurableConversationTokens, planningReserve, projectedConversationTokens, requestTokens, systemPart, validCutAt } from "./budget.js";
9
9
  import { isContextOverflow } from "@earendil-works/pi-ai";
10
10
  import { convertToLlm, SessionManager, SettingsManager } from "@earendil-works/pi-coding-agent";
11
11
  import { Type } from "typebox";
@@ -36,8 +36,18 @@ const RecallInput = Type.Union([
36
36
  ]);
37
37
  const SearchInput = Type.Object({ query, priorResults: Type.Optional(Type.Array(Type.Object({ entryId, sourceSha: Type.String({ minLength: 64, maxLength: 64 }), start: Type.Integer({ minimum: 0 }), end: Type.Integer({ minimum: 0 }) }, { additionalProperties: false }), { maxItems: 20 })) }, { additionalProperties: false });
38
38
  const reply = (value) => ({ content: [{ type: "text", text: JSON.stringify(value) }], details: {} });
39
- class CapacityError extends Error {
39
+ /** The compactor's verdict that this attempt cannot be made: the cut it needs cannot be made, or the request cannot fit. It is a decision about the
40
+ * state as it was, so it ends with the attempt (`forgetRefusal`) and the next attempt decides afresh. Any other failure is a fault in the compactor
41
+ * or its host, and stays until the session restarts. */
42
+ class Refusal extends Error {
40
43
  }
44
+ /** A refusal for want of room, which the user can remedy: it carries advice. */
45
+ class CapacityError extends Refusal {
46
+ }
47
+ /** What a capacity refusal tells the operator. When the provider has counted far more input than pi-tuck's estimate, the refusal most
48
+ * likely comes from that counting (an upstream behind a router can spend much more context on tool schemas), so the notice says so. */
49
+ export const refusalNotice = (message, factor) => message + "; shorten the input, use a model with a larger window, or lower Pi's compaction.reserveTokens, then retry." +
50
+ (factor >= 1.5 ? ` This provider has counted up to ${factor.toFixed(1)}x the input pi-tuck estimates; through a router such as OpenRouter, a different upstream may count less.` : "");
41
51
  /** The most a seed or an overflow raise may set the calibration factor to. A paired reply's own ratio is not bounded by it. */
42
52
  export const CALIBRATION_CEILING = 1.5;
43
53
  /** How many recent paired replies (and overflow bounds, and the seed while it is among them) the calibration factor is the maximum of. */
@@ -81,6 +91,9 @@ export function seedWindow(branch, key) {
81
91
  return initialWindow(key);
82
92
  }
83
93
  export async function installCompaction(pi, options = {}) {
94
+ const cutAt = options.cutAt ?? DEFAULT_CUT_AT;
95
+ if (!validCutAt(cutAt))
96
+ throw new Error("Invalid cut point: cutAt must be a number above 0 and at most 1");
84
97
  const semantic = options.semanticRecall ?? createSemanticRecall();
85
98
  let semanticTransition = Promise.resolve();
86
99
  let semanticRequest = 0;
@@ -101,7 +114,7 @@ export async function installCompaction(pi, options = {}) {
101
114
  }
102
115
  });
103
116
  const captureDraft = (draft, ctx, reserve, reason, tokensBefore, retainCurrentInput, operatorManual, previousSummary, budget) => recorder.measureInline(() => {
104
- const attached = attachReceipt(draft, reason, reserve, ctx.model?.contextWindow ?? 0, { responseReserveTokens: budget?.response, hostInputTokens: budget?.host, mandatoryInputTokens: budget?.mandatory, target: budget?.target, triggerMarginTokens: budget?.margin, calibrationFactor: factor, calibrationWindow: [...ratios], calibrationIdentity: identity, activeInputCeilingTokens: lastCeiling });
117
+ const attached = attachReceipt(draft, reason, reserve, ctx.model?.contextWindow ?? 0, { responseReserveTokens: budget?.response, hostInputTokens: budget?.host, mandatoryInputTokens: budget?.mandatory, target: budget?.target, triggerMarginTokens: budget?.margin, ...(cutAt === 1 ? {} : { cutAt }), calibrationFactor: factor, calibrationWindow: [...ratios], calibrationIdentity: identity, activeInputCeilingTokens: lastCeiling });
105
118
  proposalId = receiptId(attached);
106
119
  if (recorder.enabled) {
107
120
  try {
@@ -204,7 +217,7 @@ export async function installCompaction(pi, options = {}) {
204
217
  // The cut planner's raw reserve for the target limit, on the same factor as every other consumer.
205
218
  const planReserve = (target, room, mandatory, ctx) => {
206
219
  identityFor(ctx);
207
- return planningReserve(ctx.model?.contextWindow ?? 0, target === "band" ? bandOf(room) : room, factor, mandatory);
220
+ return planningReserve(ctx.model?.contextWindow ?? 0, target === "band" ? bandOf(room, cutAt) : room, factor, mandatory);
208
221
  };
209
222
  pi.on("message_end", (event, ctx) => {
210
223
  if (event.message.role !== "assistant")
@@ -246,7 +259,7 @@ export async function installCompaction(pi, options = {}) {
246
259
  learn(ratio);
247
260
  });
248
261
  pi.on("agent_start", () => { admittedRun = true; pending = undefined; phase = "idle"; });
249
- pi.on("agent_settled", () => { admittedRun = false; pending = undefined; phase = "idle"; });
262
+ pi.on("agent_settled", () => { admittedRun = false; pending = undefined; phase = "idle"; forgetRefusal(); });
250
263
  const active = () => options.active?.() ?? true;
251
264
  const settingsFor = () => options.settings ?? SettingsManager.inMemory(pi.getSettings());
252
265
  // The standalone default reserves the model's catalogue maximum output: a transport may omit the output cap, and then only that
@@ -268,10 +281,10 @@ export async function installCompaction(pi, options = {}) {
268
281
  throw new Error("Invalid active-input token ceiling");
269
282
  return Math.min((ctx.model?.contextWindow ?? 0) - response, ceiling ?? Infinity);
270
283
  };
271
- // The exact room, and the band a cut is triggered at (budget.ts); every throw and every fault uses the exact room.
284
+ // The exact room, and the band a cut is triggered at (budget.ts, from this session's cutAt); every throw and every fault uses the exact room.
272
285
  const limits = (ctx) => {
273
286
  const { response } = requestReserve(ctx), room = inputRoom(response, ctx);
274
- return { response, room, band: bandOf(room) };
287
+ return { response, room, band: bandOf(room, cutAt) };
275
288
  };
276
289
  const fits = (tokens, limit, ctx) => corrected(tokens, ctx) < limit;
277
290
  // The saved conversation and a cut's quantities are counted by the one function the guard counts with (strict, as the context hook is).
@@ -304,13 +317,17 @@ export async function installCompaction(pi, options = {}) {
304
317
  finally {
305
318
  try {
306
319
  if (notify)
307
- ctx.ui.notify(failure.message + "; shorten the input, use a model with a larger window, or lower Pi's compaction.reserveTokens, then retry.", "error");
320
+ ctx.ui.notify(refusalNotice(failure.message, factor), "error");
308
321
  }
309
322
  finally {
310
323
  ctx.abort();
311
324
  }
312
325
  }
313
326
  };
327
+ // The one place a refusal is dropped: when its attempt has settled, when a prompt arrives, and when the operator asks for a cut. A run that no
328
+ // input started (a custom message) has only the first of these.
329
+ const forgetRefusal = () => { if (failure instanceof Refusal)
330
+ failure = undefined; };
314
331
  const access = () => {
315
332
  if (failure)
316
333
  throw failure;
@@ -326,8 +343,7 @@ export async function installCompaction(pi, options = {}) {
326
343
  pi.on("input", async (event, ctx) => {
327
344
  if (!ctx.isIdle() || phase === "starting")
328
345
  return; // a steering or follow-up message joins the running request
329
- if (failure instanceof CapacityError)
330
- failure = undefined;
346
+ forgetRefusal();
331
347
  phase = "decided";
332
348
  try {
333
349
  await preflight(ctx, incomingTokens(event.text, event.images), ctx.getSystemPrompt(), pi.getActiveTools());
@@ -401,8 +417,8 @@ export async function installCompaction(pi, options = {}) {
401
417
  // Pi also labels the awaited idle incoming preflight "manual".
402
418
  const incoming = pendingCut;
403
419
  const operatorManual = event.reason === "manual" && !(incoming && !admittedRun);
404
- if (operatorManual && !admittedRun && failure instanceof CapacityError)
405
- failure = undefined;
420
+ if (operatorManual && !admittedRun)
421
+ forgetRefusal();
406
422
  access();
407
423
  if (event.customInstructions?.trim()) {
408
424
  recorder.stop("no-cut-custom-instructions");
@@ -453,7 +469,7 @@ export async function installCompaction(pi, options = {}) {
453
469
  recorder.stop("no-cut");
454
470
  ctx.ui.notify(decision.message, "warning");
455
471
  if (!operatorManual)
456
- fail(decision.capacity ? new CapacityError(decision.message) : new Error(decision.message), ctx);
472
+ fail(new (decision.capacity ? CapacityError : Refusal)(decision.message), ctx);
457
473
  return { cancel: true };
458
474
  }
459
475
  catch (error) {
@@ -513,7 +529,7 @@ export async function installCompaction(pi, options = {}) {
513
529
  if (cut.opportunistic && !failure && !cut.proposed)
514
530
  resolve();
515
531
  else
516
- reject(error.message === "Compaction cancelled" && failure instanceof CapacityError ? failure : error);
532
+ reject(error.message === "Compaction cancelled" && failure instanceof Refusal ? failure : error);
517
533
  } }));
518
534
  }
519
535
  finally {
@@ -523,7 +539,7 @@ export async function installCompaction(pi, options = {}) {
523
539
  // The cut awaited: take the snapshot again. An opportunistic cut never throws; the request is not over the room, and the guard decides.
524
540
  const next = snapshot(), after = next.before + next.mandatory, afterFits = fits(after, limits(ctx).room, ctx);
525
541
  if (!cut.opportunistic && (after >= total || !afterFits))
526
- throw new (afterFits ? Error : CapacityError)("Incoming Pi preflight compaction did not reduce context within the estimated request budget");
542
+ throw new (afterFits ? Refusal : CapacityError)("Incoming Pi preflight compaction did not reduce context within the estimated request budget");
527
543
  };
528
544
  pi.on("before_agent_start", async (event, ctx) => {
529
545
  const prompted = phase === "decided";
@@ -580,7 +596,7 @@ export async function installCompaction(pi, options = {}) {
580
596
  },
581
597
  });
582
598
  if (decision.kind === "fault")
583
- throw new (decision.capacity ? CapacityError : Error)(decision.message);
599
+ throw new (decision.capacity ? CapacityError : Refusal)(decision.message);
584
600
  if (decision.kind !== "cut")
585
601
  return;
586
602
  const compaction = captureDraft(decision.plan.compaction, ctx, decision.plan.reserve, "tool-loop", before, true, false, previousSummary, { response, host, mandatory, target: decision.target, margin: room - band });
@@ -1,6 +1,6 @@
1
1
  import { type RecallPolicy } from './recall.js';
2
2
  import type { SessionEntry } from '@earendil-works/pi-coding-agent';
3
- export declare const DIAGNOSTIC_BUILD = "2a0ebe00760f2226d888fbca503dbde44d32623cb644186a6dd2eef59b65fbe5";
3
+ export declare const DIAGNOSTIC_BUILD = "fb0ab667f5bfcd22fd5cc6edb89cd859426fd7c28bb58c2bc7b9fbec6fcdd3da";
4
4
  export interface Receipt {
5
5
  id: string;
6
6
  build: string;
@@ -15,6 +15,7 @@ export interface Receipt {
15
15
  mandatoryInputTokens?: number;
16
16
  target?: 'band' | 'room';
17
17
  triggerMarginTokens?: number;
18
+ cutAt?: number;
18
19
  calibrationFactor?: number;
19
20
  calibrationWindow?: number[];
20
21
  calibrationIdentity?: string;
@@ -2,7 +2,7 @@ import type { ExtensionContext, SessionEntry } from '@earendil-works/pi-coding-a
2
2
  import { type RecallPolicy } from './recall.js';
3
3
  /** Native branch edits supply exclusion; original global indices supply provenance. */
4
4
  export declare function omittedSourceIndices(entries: readonly SessionEntry[], branch: readonly SessionEntry[]): ReadonlySet<number>;
5
- /** Preserve surviving record bytes/order. No text matching or semantic inference. */
5
+ /** Preserve surviving record bytes/order, and the summary's format (a v1 summary stays v1). No text matching or semantic inference. */
6
6
  export declare function filterOmittedSummary(summary: string, omitted: ReadonlySet<number>): string;
7
7
  /** Custom quotes carry native entry identity; no text matching or legacy fallback. */
8
8
  export declare function filterCustomQuotes(summary: string, entries: readonly SessionEntry[], branch: readonly SessionEntry[], policy?: RecallPolicy): string;
@@ -1,5 +1,5 @@
1
1
  import { buildGlobalIndexById } from './tuck/core/global-indices.js';
2
- import { TRANSCRIPT_HEADER, parseRecords, splitSummary } from './tuck/core/transcript.js';
2
+ import { parseRecords, splitSummary, transcriptHeader } from './tuck/core/transcript.js';
3
3
  import { createRecall } from './recall.js';
4
4
  /** Native branch edits supply exclusion; original global indices supply provenance. */
5
5
  export function omittedSourceIndices(entries, branch) {
@@ -13,24 +13,24 @@ export function omittedSourceIndices(entries, branch) {
13
13
  }
14
14
  return new Set([...edits].flatMap(([id, omitted]) => omitted && indices.has(id) ? [indices.get(id)] : []));
15
15
  }
16
- /** Preserve surviving record bytes/order. No text matching or semantic inference. */
16
+ /** Preserve surviving record bytes/order, and the summary's format (a v1 summary stays v1). No text matching or semantic inference. */
17
17
  export function filterOmittedSummary(summary, omitted) {
18
18
  if (!omitted.size)
19
19
  return summary;
20
20
  const split = splitSummary(summary);
21
21
  if (!split)
22
22
  throw new Error('Cannot apply native source omission to an unsupported compaction summary');
23
- const { body, closing } = split;
24
- const records = parseRecords(body), lines = body ? body.split('\n') : [];
23
+ const { body, closing, version } = split;
24
+ const records = parseRecords(body, version), lines = body ? body.split('\n') : [];
25
25
  const retained = records.flatMap((record, i) => (record.sourceIndex !== undefined && omitted.has(record.sourceIndex)) || record.sourceIndices?.some(index => omitted.has(index)) ? [] : [lines[i]]);
26
- return TRANSCRIPT_HEADER + '\n' + retained.join('\n') + closing;
26
+ return transcriptHeader(version) + '\n' + retained.join('\n') + closing;
27
27
  }
28
28
  /** Custom quotes carry native entry identity; no text matching or legacy fallback. */
29
29
  export function filterCustomQuotes(summary, entries, branch, policy = {}) {
30
30
  const split = splitSummary(summary);
31
31
  if (!split)
32
32
  throw new Error('Cannot resolve custom sources in an unsupported compaction summary');
33
- const { body, closing } = split;
33
+ const { body, closing, version } = split;
34
34
  const omitted = new Set(branch.flatMap(entry => entry.type === 'context_edit' && entry.replacement === null ? [entry.targetId] : []));
35
35
  const eligible = createRecall(policy).isHistorySource, indices = buildGlobalIndexById(entries);
36
36
  const globalCounts = new Map(), branchSources = new Map();
@@ -42,7 +42,7 @@ export function filterCustomQuotes(summary, entries, branch, policy = {}) {
42
42
  branchSources.set(entry.id, found);
43
43
  }
44
44
  const lines = body ? body.split('\n') : [];
45
- const records = parseRecords(body);
45
+ const records = parseRecords(body, version);
46
46
  const retained = records.flatMap((record, i) => {
47
47
  if (record.role !== 'custom')
48
48
  return [lines[i]];
@@ -56,7 +56,7 @@ export function filterCustomQuotes(summary, entries, branch, policy = {}) {
56
56
  return [];
57
57
  return [lines[i]];
58
58
  });
59
- return TRANSCRIPT_HEADER + '\n' + retained.join('\n') + closing;
59
+ return transcriptHeader(version) + '\n' + retained.join('\n') + closing;
60
60
  }
61
61
  /** The saved conversation as this compactor delivers it: native omissions and ineligible custom quotes removed from summaries. */
62
62
  export function projectedMessages(messages, ctx, policy) {
@@ -1,49 +1,19 @@
1
1
  import type { NormalizedBlock } from "../types.js";
2
- import type { SourceTime } from "./source-time.js";
3
2
  import { type TranscriptRecord } from "./transcript.js";
4
- export declare const isSegmentClosingAssistant: (blocks: NormalizedBlock[], index: number) => boolean;
5
- export declare const HEREDOC_OPEN_RE: RegExp;
6
- /**
7
- * If `lines[i]` opens a heredoc whose terminator actually appears on a later
8
- * line, return that terminator's line index; otherwise -1. Callers use -1 to
9
- * leave following lines intact instead of treating a stray `<<` (a shift op, a
10
- * quoted string, or a truncated body) as a heredoc and skipping real commands.
11
- */
12
- export declare const heredocCloseIndex: (lines: string[], i: number) => number;
13
- export type CallRecord = Extract<TranscriptRecord, {
14
- kind: "tool_call";
3
+ /** The user, assistant and notice blocks of a dialogue; tool blocks are never rendered. */
4
+ export type DialogueBlock = Extract<NormalizedBlock, {
5
+ kind: "user" | "assistant" | "notice";
15
6
  }>;
16
- /** One record is independently decodable; source text cannot create structure. */
17
- export type ExcerptCache = WeakMap<NormalizedBlock, Map<string, TranscriptRecord[]>>;
18
- /** The assistant texts that close a segment: no further text before the next user turn or the end of the chronology. */
19
- export declare const segmentClosing: (chronology: NormalizedBlock[]) => Set<NormalizedBlock>;
7
+ export declare const isDialogue: (block: NormalizedBlock) => block is DialogueBlock;
20
8
  /** A user or assistant text block the omission notices count when it is unselected: non-empty, and not rendered in the mandatory base. */
21
9
  export declare const isOmittable: (block: NormalizedBlock, mandatorySources?: ReadonlySet<number>) => boolean;
22
- /** `sources` are the distinct locators of the omitted blocks in ascending order; at most the first 9 decide the notice. */
10
+ /** `sources` are the distinct locators of the omitted blocks in ascending order; at most the first 9 decide the notice. They are written
11
+ * as "#N" on the record's line. */
23
12
  export declare const omissionRecord: (role: "user" | "assistant", count: number, sources: number[], unlocated: boolean) => TranscriptRecord;
24
- export declare const planBrief: (blocks: NormalizedBlock[], full: Set<NormalizedBlock>, chronology: NormalizedBlock[], mandatorySources?: ReadonlySet<number>, includeOmissions?: boolean) => void;
25
- /** The original call that a selected complete receipt quotes; that call's own overview is then redundant. */
26
- export declare const receiptKey: (b: NormalizedBlock) => string | undefined;
27
- /** The receipt that would make this call redundant, when the call and its single receipt are fully identified. */
28
- export declare const coveredCallKey: (b: NormalizedBlock) => string | undefined;
29
- /** What one block renders in this brief, set by the plan and by the selected array around it. */
30
- export interface BlockView {
31
- full: boolean;
32
- closing: boolean;
33
- mandatory?: ReadonlySet<number>;
34
- bounded: boolean;
35
- covered: boolean;
36
- }
37
- /** The records of one block alone: before identical calls merge and before source times are attached. */
38
- export declare const blockRecords: (b: NormalizedBlock, view: BlockView, cache?: ExcerptCache) => TranscriptRecord[];
39
- /** Whether `record` is the same call as the `previous` record, shown a second time. */
40
- export declare const repeatsCall: (previous: TranscriptRecord | undefined, record: TranscriptRecord) => previous is CallRecord & {
41
- sourceIndex: number;
42
- };
43
- /** The earlier record of a repeated call, now also locating the later one. */
44
- export declare const absorbCall: (previous: TranscriptRecord, record: TranscriptRecord) => TranscriptRecord;
45
- /** A source time for each locator the record names; only briefs of times-carrying blocks have them. */
46
- export declare const noticeTimes: (blocks: NormalizedBlock[]) => Map<string, SourceTime>;
47
- export declare const decorateRecord: (record: TranscriptRecord, times: NormalizedBlock["sourceTimes"], entryTimes: ReadonlyMap<string, SourceTime>) => TranscriptRecord;
48
- export declare const buildBriefRecords: (blocks: NormalizedBlock[], cache?: ExcerptCache) => TranscriptRecord[];
49
- export declare const compileBrief: (blocks: NormalizedBlock[], cache?: ExcerptCache) => string;
13
+ /** The word budgets of a clipped excerpt: the passages kept from the start of the text, and from its end. */
14
+ export type Clip = readonly [headWords: number, tailWords: number];
15
+ /** The records of one dialogue block alone, before source times are attached: its whole original text, or, with a `clip`, the excerpts
16
+ * that keep their sanitized-block spans around an omission. A mandatory source renders nothing: the adapter quotes it in its base. */
17
+ export declare const blockRecords: (b: DialogueBlock, mandatory: ReadonlySet<number> | undefined, clip?: Clip) => TranscriptRecord[];
18
+ /** A source time for each locator a user record names; only briefs of times-carrying blocks have them. */
19
+ export declare const decorateRecord: (record: TranscriptRecord, times: NormalizedBlock["sourceTimes"]) => TranscriptRecord;