@yaklabs-ai/plugin-sdk 0.15.3 → 0.27.0

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.
package/dist/kay-app.js CHANGED
@@ -163,6 +163,94 @@ function invalidObservation(input) {
163
163
  };
164
164
  }
165
165
 
166
+ // packages/herd-transcript/dist/record-kinds.js
167
+ var STORED_TRANSCRIPT_MESSAGE_KINDS = [
168
+ "user",
169
+ "assistant",
170
+ "tool_result",
171
+ "custom_message:*",
172
+ "compaction",
173
+ "branch_summary"
174
+ ];
175
+ var NAMED_KINDS = /* @__PURE__ */ new Set([
176
+ "user",
177
+ "assistant",
178
+ "tool_result",
179
+ "compaction",
180
+ "branch_summary",
181
+ "other",
182
+ "unreadable"
183
+ ]);
184
+ var ROLE_KINDS = {
185
+ user: "user",
186
+ assistant: "assistant",
187
+ toolResult: "tool_result"
188
+ };
189
+ function storedTranscriptKindOf(record) {
190
+ if (!("entry" in record))
191
+ return "unreadable";
192
+ const entry = record.entry;
193
+ switch (entry.type) {
194
+ case "message": {
195
+ const message = entry.message;
196
+ if (message.role === "custom") {
197
+ return `custom_message:${typeof message.customType === "string" ? message.customType : ""}`;
198
+ }
199
+ return (typeof message.role === "string" ? ROLE_KINDS[message.role] : void 0) ?? "other";
200
+ }
201
+ case "custom":
202
+ return `custom:${entry.customType}`;
203
+ case "compaction":
204
+ return "compaction";
205
+ case "branch_summary":
206
+ return "branch_summary";
207
+ default:
208
+ return "other";
209
+ }
210
+ }
211
+ function parseStoredTranscriptKindFilter(tokens) {
212
+ const exact = /* @__PURE__ */ new Set();
213
+ const families = /* @__PURE__ */ new Set();
214
+ let all = false;
215
+ for (const raw of tokens) {
216
+ const token = raw.trim();
217
+ if (token === "all") {
218
+ all = true;
219
+ } else if (token === "messages") {
220
+ for (const kind of STORED_TRANSCRIPT_MESSAGE_KINDS)
221
+ addToken(kind);
222
+ } else if (!addToken(token)) {
223
+ return { ok: false, invalid: raw };
224
+ }
225
+ }
226
+ return {
227
+ ok: true,
228
+ filter: {
229
+ includes: (kind) => all || exact.has(kind) || families.has(kind.slice(0, kind.indexOf(":") + 1))
230
+ }
231
+ };
232
+ function addToken(token) {
233
+ if (NAMED_KINDS.has(token)) {
234
+ exact.add(token);
235
+ return true;
236
+ }
237
+ const family = /^(custom|custom_message):(.+)$/.exec(token);
238
+ if (family === null)
239
+ return false;
240
+ if (family[2] === "*")
241
+ families.add(`${family[1]}:`);
242
+ else
243
+ exact.add(token);
244
+ return true;
245
+ }
246
+ }
247
+ var STORED_TRANSCRIPT_MESSAGE_FILTER = (() => {
248
+ const parsed = parseStoredTranscriptKindFilter(STORED_TRANSCRIPT_MESSAGE_KINDS);
249
+ if (!parsed.ok)
250
+ throw new Error(`invalid default kinds: ${parsed.invalid}`);
251
+ return parsed.filter;
252
+ })();
253
+
166
254
  // packages/plugin-sdk/dist/kay-app/index.js
167
255
  import { pathToFileURL } from "node:url";
168
256
 
@@ -190,6 +278,15 @@ var UriValidationError = class extends UriError {
190
278
  this.name = "UriValidationError";
191
279
  }
192
280
  };
281
+ function requireWriteContent(payload2, uri) {
282
+ if (payload2 === void 0) {
283
+ throw new UriValidationError(`Writing ${uri} needs \`content\` (or a \`source\` to copy from): pass the payload this address takes, which its help names.`);
284
+ }
285
+ if (payload2 instanceof Uint8Array) {
286
+ throw new UriValidationError(`${uri} stores text only, so it cannot take raw bytes. Copy the file to an address that accepts bytes.`);
287
+ }
288
+ return payload2;
289
+ }
193
290
  var UriProviderError = class extends UriError {
194
291
  _tag = "UriProviderError";
195
292
  moduleId;
@@ -202,6 +299,8 @@ var UriProviderError = class extends UriError {
202
299
  retryAfterMs;
203
300
  /** See {@link UriProviderErrorOptions.effect}. */
204
301
  effect;
302
+ /** See {@link UriProviderErrorOptions.refusalMessage}. */
303
+ refusalMessage;
205
304
  constructor(options) {
206
305
  super(options.message);
207
306
  this.name = "UriProviderError";
@@ -222,6 +321,12 @@ var UriProviderError = class extends UriError {
222
321
  }
223
322
  if (options.effect !== void 0)
224
323
  this.effect = options.effect;
324
+ if (options.refusalMessage !== void 0) {
325
+ if (options.effect !== "none") {
326
+ throw new TypeError('refusalMessage requires effect: "none".');
327
+ }
328
+ this.refusalMessage = options.refusalMessage;
329
+ }
225
330
  }
226
331
  };
227
332
  var UriNotFoundError = class extends UriError {
@@ -400,7 +505,7 @@ function toJsonSchema(schema) {
400
505
  // `io: "input"` describes what a caller may SEND, which is what a parameter and an
401
506
  // `accepts` payload advertise: a field with a Zod default is optional on the wire even
402
507
  // though the parsed value always carries it. Strictness still survives where the validator
403
- // really enforces it — `z.strictObject` emits `additionalProperties: false`, while a plain
508
+ // really enforces it - `z.strictObject` emits `additionalProperties: false`, while a plain
404
509
  // `z.object` correctly does not, because it TOLERATES unknown keys and silently strips
405
510
  // them. A `returns` payload derived from a Zod schema is described in the same input terms;
406
511
  // no module does that today, and a faithful output projection would need its own seam.
@@ -440,6 +545,28 @@ function isRetryable(result) {
440
545
  return result.status === "not-ready";
441
546
  }
442
547
 
548
+ // packages/plugin-sdk/dist/kay-app/auth.js
549
+ var KayAppCredentialRejectedError = class extends Error {
550
+ name = "KayAppCredentialRejectedError";
551
+ };
552
+ async function withCredential(auth, request, use) {
553
+ const candidates = await auth.resolveCandidates(request);
554
+ let rejection;
555
+ for (const credential of candidates) {
556
+ try {
557
+ const result = await use(credential);
558
+ await auth.reportWorked(request, credential.schemeId).catch(() => {
559
+ });
560
+ return result;
561
+ } catch (error) {
562
+ if (!(error instanceof KayAppCredentialRejectedError))
563
+ throw error;
564
+ rejection = error;
565
+ }
566
+ }
567
+ throw rejection ?? new Error(`No credential is resolvable for '${request.serviceId}'.`);
568
+ }
569
+
443
570
  // packages/plugin-sdk/dist/kay-app/automations.js
444
571
  var KAY_APP_AUTOMATION_CONDITION_KINDS = [
445
572
  "time",
@@ -449,6 +576,46 @@ var KAY_APP_AUTOMATION_CONDITION_KINDS = [
449
576
  "delayed-occurrence"
450
577
  ];
451
578
 
579
+ // packages/plugin-sdk/dist/log.js
580
+ function createKayPluginLog(write, bound = {}) {
581
+ const at = (level) => (name, message, details) => {
582
+ try {
583
+ write({
584
+ level,
585
+ name,
586
+ message,
587
+ ...details?.error === void 0 ? {} : { error: details.error },
588
+ ...details?.sessionId === void 0 ? {} : { sessionId: details.sessionId },
589
+ attributes: { ...bound, ...details?.attributes }
590
+ });
591
+ } catch {
592
+ }
593
+ };
594
+ return {
595
+ debug: at("debug"),
596
+ info: at("info"),
597
+ warn: at("warn"),
598
+ error: at("error"),
599
+ child: (attributes) => createKayPluginLog(write, { ...bound, ...attributes })
600
+ };
601
+ }
602
+ function createConsoleKayPluginLog(pluginId) {
603
+ const prefix = pluginId === void 0 ? "" : `${pluginId}.`;
604
+ return createKayPluginLog((line) => {
605
+ const args = [`[${prefix}${line.name}] ${line.message}`];
606
+ if (Object.keys(line.attributes).length > 0)
607
+ args.push(line.attributes);
608
+ if (line.error !== void 0)
609
+ args.push(line.error);
610
+ console[line.level](...args);
611
+ });
612
+ }
613
+
614
+ // packages/plugin-sdk/dist/kay-app/model-selection.js
615
+ var KAY_APP_MODEL_SELECTION_START_BOUND_MS = 2e3;
616
+ var KAY_APP_MODEL_SELECTION_RESPONSE_BOUND_MS = 6e3;
617
+ var KAY_APP_MODEL_SELECTION_SEND_BOUND_MS = 4e3;
618
+
452
619
  // packages/plugin-sdk/dist/kay-app/poller.js
453
620
  function accountCursors(state) {
454
621
  if (state === void 0)
@@ -482,7 +649,7 @@ function plainText(value, max) {
482
649
  return text;
483
650
  }
484
651
  function definePoller(app, localId, options) {
485
- const { workflows, auth, events } = app;
652
+ const { workflows, auth, backgroundAuth, events } = app;
486
653
  if (!workflows)
487
654
  throw new Error("Poller requires workflows");
488
655
  if (!Number.isSafeInteger(options.every) || options.every <= 0) {
@@ -498,6 +665,8 @@ function definePoller(app, localId, options) {
498
665
  async run(ctx) {
499
666
  if (!auth)
500
667
  throw new Error("Poller requires account listing");
668
+ if (!backgroundAuth)
669
+ throw new Error("Poller requires background auth");
501
670
  const previous = accountCursors(ctx.state);
502
671
  const accounts = await ctx.runStep(async () => {
503
672
  const listed = await auth.listAccounts({
@@ -523,7 +692,12 @@ function definePoller(app, localId, options) {
523
692
  }
524
693
  let credential;
525
694
  try {
526
- credential = await options.resolveCredential(account, ctx.runId);
695
+ credential = await backgroundAuth.resolveCredential({
696
+ serviceId: options.serviceId,
697
+ schemeId: account.schemeId,
698
+ accountId: account.accountId,
699
+ jobId: ctx.runId
700
+ });
527
701
  } catch {
528
702
  throw new Error("Poller credential resolution failed");
529
703
  }
@@ -597,6 +771,15 @@ function definePoller(app, localId, options) {
597
771
  });
598
772
  }
599
773
 
774
+ // packages/plugin-sdk/dist/kay-app/sealing.js
775
+ var KAY_APP_SEALING_MAX_BYTES = 1048576;
776
+ function createUnavailableSealing(message) {
777
+ return {
778
+ seal: async () => ({ status: "unavailable", message }),
779
+ open: async () => ({ status: "unavailable", message })
780
+ };
781
+ }
782
+
600
783
  // packages/plugin-sdk/dist/kay-app/skills.js
601
784
  function createUnavailableSkills(message) {
602
785
  const unavailable = {
@@ -605,9 +788,38 @@ function createUnavailableSkills(message) {
605
788
  message
606
789
  };
607
790
  return {
791
+ propose: async () => {
792
+ throw new Error(message);
793
+ },
608
794
  list: async () => unavailable,
609
795
  read: async () => unavailable,
610
- history: async () => unavailable
796
+ history: async () => unavailable,
797
+ diff: async () => unavailable,
798
+ revision: async () => unavailable,
799
+ writeFile: async () => {
800
+ throw new Error(message);
801
+ },
802
+ install: async () => {
803
+ throw new Error(message);
804
+ },
805
+ delete: async () => {
806
+ throw new Error(message);
807
+ },
808
+ sourceContributors: async () => {
809
+ throw new Error(message);
810
+ },
811
+ setMarketplaceAdded: async () => {
812
+ throw new Error(message);
813
+ },
814
+ shareDestinations: async () => {
815
+ throw new Error(message);
816
+ },
817
+ prepareShare: async () => {
818
+ throw new Error(message);
819
+ },
820
+ confirmShare: async () => {
821
+ throw new Error(message);
822
+ }
611
823
  };
612
824
  }
613
825
 
@@ -649,7 +861,8 @@ function transcriptRoleLabel(role) {
649
861
  // packages/plugin-sdk/dist/kay-app/usage.js
650
862
  function createUnavailableUsage(message) {
651
863
  return {
652
- uriActivity: async () => ({ status: "unavailable", message })
864
+ uriActivity: async () => ({ status: "unavailable", message }),
865
+ skillInvocations: async () => ({ status: "unavailable", message })
653
866
  };
654
867
  }
655
868
 
@@ -677,15 +890,25 @@ export {
677
890
  KAY_APP_AUTOMATION_CONDITION_KINDS,
678
891
  KAY_APP_DATA_CHANGE_MAX_BYTES,
679
892
  KAY_APP_DATA_SNAPSHOT_MAX_BYTES,
893
+ KAY_APP_MODEL_SELECTION_RESPONSE_BOUND_MS,
894
+ KAY_APP_MODEL_SELECTION_SEND_BOUND_MS,
895
+ KAY_APP_MODEL_SELECTION_START_BOUND_MS,
896
+ KAY_APP_SEALING_MAX_BYTES,
897
+ KayAppCredentialRejectedError,
680
898
  KayAppWorkflowFailure,
681
899
  MAX_CHOICE_LABELS,
682
900
  SIGNAL_TIME_MAX_MS,
901
+ STORED_TRANSCRIPT_MESSAGE_FILTER,
902
+ STORED_TRANSCRIPT_MESSAGE_KINDS,
683
903
  UriConflictError,
684
904
  UriNotFoundError,
685
905
  UriProviderError,
686
906
  UriUnsupportedOperationError,
687
907
  UriValidationError,
908
+ createConsoleKayPluginLog,
688
909
  createFallbackOnlySignals,
910
+ createKayPluginLog,
911
+ createUnavailableSealing,
689
912
  createUnavailableSkills,
690
913
  createUnavailableTools,
691
914
  createUnavailableUsage,
@@ -694,9 +917,13 @@ export {
694
917
  invalidObservation,
695
918
  isRetryable,
696
919
  parseLineSelector,
920
+ parseStoredTranscriptKindFilter,
697
921
  pathToFileURL,
698
922
  payload,
923
+ requireWriteContent,
924
+ storedTranscriptKindOf,
699
925
  transcriptMessageText,
700
926
  transcriptMessageTexts,
701
- transcriptRoleLabel
927
+ transcriptRoleLabel,
928
+ withCredential
702
929
  };
@@ -28,7 +28,7 @@ declare type PhaseShiftCost = z.infer<typeof phaseShiftCostSchema>;
28
28
  * the feature accounts for itself.
29
29
  *
30
30
  * Absent on a record written before the detector accounted for itself, and on
31
- * a call no attempt priced — absence means "not recorded", never zero.
31
+ * a call no attempt priced - absence means "not recorded", never zero.
32
32
  */
33
33
  declare const phaseShiftCostSchema: z.ZodObject<{
34
34
  amountUsd: z.ZodNumber;
@@ -45,7 +45,7 @@ declare const phaseShiftCostSchema: z.ZodObject<{
45
45
  /** One accepted transition, placed in time. */
46
46
  export declare interface PhaseShiftTimelineChange {
47
47
  readonly startEntryId: string;
48
- /** When the new work began — the onset entry's own timestamp, not the judgment's. */
48
+ /** When the new work began - the onset entry's own timestamp, not the judgment's. */
49
49
  readonly startedAt: string;
50
50
  readonly relation: "outcome" | "phase";
51
51
  readonly reason: string;
@@ -69,7 +69,7 @@ export declare function phaseShiftTimelineFromEntries(entries: readonly Transcri
69
69
  *
70
70
  * A record is evidence about the span it names and nothing else: time outside
71
71
  * every record's `[windowStartAt, throughAt]` is unobserved, which is not the
72
- * same as unchanged. An empty `changes` is the opposite — a check that ran and
72
+ * same as unchanged. An empty `changes` is the opposite - a check that ran and
73
73
  * found no transition.
74
74
  */
75
75
  export declare interface PhaseShiftTimelineRecord {
@@ -100,7 +100,7 @@ declare interface TranscriptEntryLike {
100
100
  readonly id: string;
101
101
  readonly type: string;
102
102
  readonly customType?: string;
103
- /** Opaque. Never parsed or validated here — a feature's own schema owns that. */
103
+ /** Opaque. Never parsed or validated here - a feature's own schema owns that. */
104
104
  readonly data?: unknown;
105
105
  }
106
106
 
@@ -231,12 +231,25 @@ export declare interface SystemOneAvailability {
231
231
  readonly cause?: SystemOneFailureCause;
232
232
  }
233
233
 
234
+ /** Structurally the plugin SDK's `KayAppSessionEventCause`: only the fields the host reads. */
235
+ declare interface SystemOneCallCause {
236
+ readonly kind?: "session-event" | "backfill";
237
+ readonly handler: string;
238
+ readonly sourceSessionId: string;
239
+ }
240
+
234
241
  export declare interface SystemOneCallOptions {
235
242
  readonly signal?: AbortSignal;
236
243
  /** Bounds the whole call, retries included. */
237
244
  readonly timeoutMs?: number;
238
245
  /** What the call is for, carried to the host's usage accounting. Never sent to a provider. */
239
246
  readonly purpose?: string;
247
+ /**
248
+ * The session event a plugin's call answers, so the host charges the call to that session.
249
+ * The host checks the plugin is handling that session's event right now. Never sent to a
250
+ * provider.
251
+ */
252
+ readonly cause?: SystemOneCallCause;
240
253
  }
241
254
 
242
255
  export declare interface SystemOneClient {
package/dist/time.d.ts CHANGED
@@ -17,7 +17,7 @@ export declare function isValidZone(id: unknown): boolean;
17
17
  /**
18
18
  * `app.time` / `api.time`: host facts about time. Added to `kay.app` in SDK 0.12.1 and to
19
19
  * `kay.agent` with the calendar zone-of-record work: a plugin that calls it declares
20
- * `compatibility.sdk` `^0.12.1`, so an older Bonsai refuses a directory or repository plugin at
20
+ * `compatibility.sdk` `^0.19.0`, so an older Bonsai refuses a directory or repository plugin at
21
21
  * load instead of the call failing.
22
22
  */
23
23
  export declare interface KayAppTime {
@@ -27,9 +27,7 @@ export declare interface KayAppTime {
27
27
  * Every call asks the host again and returns a new object; nothing is cached. Call it where you
28
28
  * need the value (inside a tool, a handler or a workflow step), not inside `activate()`: a value
29
29
  * read there is as old as the activation, and the host can run a bundled plugin's `activate()`
30
- * against a recording api that has no `time`, so a read there throws. The exception is a
31
- * directory plugin that declares a workflow with a cron `timezone`: `workflows.define` is legal
32
- * only during `activate()`, so it reads the zone there, and the schedule then keeps that zone.
30
+ * against a recording api that has no `time`, so a read there throws.
33
31
  * Keep the result for as long as you use it, and not across a long wait (a sleep, a timer, a
34
32
  * queued run): the zone can change while you wait. On a daemon that runs on the user's machine,
35
33
  * a change of the process's `TZ` shows in the next call. A `TZ` that names a zone wins over the
@@ -152,6 +152,16 @@ export declare interface TranscriptReview {
152
152
  readonly start: number;
153
153
  readonly end: number;
154
154
  } | undefined;
155
+ /**
156
+ * The run whose own extent holds this position, the one starting latest; among runs that
157
+ * start at the same position, the first in run order. Undefined before the first run and at
158
+ * or after the end.
159
+ */
160
+ runAt(position: number): {
161
+ readonly runId: string;
162
+ readonly start: number;
163
+ readonly end: number;
164
+ } | undefined;
155
165
  /** The run input carrying this input id. */
156
166
  inputById(inputId: string): TranscriptReviewRunInput | undefined;
157
167
  attemptAt(position: number, block: number): ToolCallAttempt | undefined;
@@ -533,43 +533,98 @@ function createTranscriptReview(options = {}) {
533
533
  }
534
534
  starts.sort((left, right) => left.boundary - right.boundary);
535
535
  const runStarts = [...finalRuns.values()].map((run) => ({ runId: run.runId, boundary: run.boundary })).sort((left, right) => left.boundary - right.boundary);
536
+ const runEndAt = Array.from({ length: runStarts.length });
537
+ for (let at = runStarts.length - 1; at >= 0; at -= 1) {
538
+ const next = runStarts[at + 1];
539
+ runEndAt[at] = next === void 0 ? endPosition : next.boundary > runStarts[at].boundary ? next.boundary : runEndAt[at + 1];
540
+ }
541
+ const runStartIndex = /* @__PURE__ */ new Map();
542
+ runStarts.forEach((run, at) => runStartIndex.set(run.runId, at));
543
+ const lastProseBefore = [];
544
+ let lastProse;
545
+ for (const record of records) {
546
+ lastProseBefore.push(lastProse);
547
+ if (record.hasProse)
548
+ lastProse = record.position;
549
+ }
536
550
  const previousProse = (before) => {
537
- let index = byPosition.get(before);
538
- if (index === void 0)
551
+ const index = byPosition.get(before);
552
+ return index === void 0 ? void 0 : lastProseBefore[index];
553
+ };
554
+ const spanIndexAt = (position) => {
555
+ if (position >= endPosition)
539
556
  return void 0;
540
- for (index -= 1; index >= 0; index -= 1) {
541
- if (records[index].hasProse)
542
- return records[index].position;
557
+ let low = 0;
558
+ let high = starts.length - 1;
559
+ let found;
560
+ while (low <= high) {
561
+ const middle = low + high >> 1;
562
+ if (starts[middle].boundary <= position) {
563
+ found = middle;
564
+ low = middle + 1;
565
+ } else {
566
+ high = middle - 1;
567
+ }
543
568
  }
544
- return void 0;
569
+ return found;
570
+ };
571
+ const ownRunsOf = starts.map(() => ({
572
+ ids: [],
573
+ seen: /* @__PURE__ */ new Set()
574
+ }));
575
+ const addOwnRun = (span, runId) => {
576
+ const own = ownRunsOf[span];
577
+ if (own.seen.has(runId))
578
+ return;
579
+ own.seen.add(runId);
580
+ own.ids.push(runId);
581
+ };
582
+ for (const run of runStarts) {
583
+ const started = finalRuns.get(run.runId).startedPosition;
584
+ if (started === void 0)
585
+ continue;
586
+ const span = spanIndexAt(started);
587
+ if (span !== void 0)
588
+ addOwnRun(span, run.runId);
589
+ }
590
+ const joinedOf = starts.map(() => []);
591
+ for (const input of inputs) {
592
+ if (input.runId === void 0)
593
+ continue;
594
+ const span = spanIndexAt(input.position);
595
+ if (span === void 0)
596
+ continue;
597
+ addOwnRun(span, input.runId);
598
+ if (input.human && !input.opensRun)
599
+ joinedOf[span].push(input);
600
+ }
601
+ const firstInputAtOrAfter = (position) => {
602
+ let low = 0;
603
+ let high = inputs.length;
604
+ while (low < high) {
605
+ const middle = low + high >> 1;
606
+ if (inputs[middle].position < position)
607
+ low = middle + 1;
608
+ else
609
+ high = middle;
610
+ }
611
+ return low;
545
612
  };
546
613
  const spans = starts.map((start, index) => {
547
614
  const next = starts[index + 1];
548
615
  const end = next?.boundary ?? endPosition;
549
616
  const closingEnd = next?.opener !== void 0 ? Math.max(end, next.opener.position + 1) : end;
550
- const ownRuns = [];
617
+ const ownRuns = ownRunsOf[index].ids;
551
618
  const closingRuns = [];
552
- for (const run of runStarts) {
553
- const started = finalRuns.get(run.runId).startedPosition;
554
- if (started !== void 0 && started >= start.boundary && started < end) {
555
- if (!ownRuns.includes(run.runId))
556
- ownRuns.push(run.runId);
557
- }
558
- }
559
- const joined = [];
560
- for (const input of inputs) {
561
- if (input.runId === void 0)
562
- continue;
563
- if (input.position >= start.boundary && input.position < end) {
564
- if (!ownRuns.includes(input.runId))
565
- ownRuns.push(input.runId);
566
- if (input.human && !input.opensRun)
567
- joined.push(input);
568
- } else if (input.position >= end && input.position < closingEnd) {
569
- if (!closingRuns.includes(input.runId))
570
- closingRuns.push(input.runId);
619
+ const closingSeen = /* @__PURE__ */ new Set();
620
+ for (let at = firstInputAtOrAfter(end); at < inputs.length && inputs[at].position < closingEnd; at += 1) {
621
+ const runId = inputs[at].runId;
622
+ if (runId !== void 0 && !closingSeen.has(runId)) {
623
+ closingSeen.add(runId);
624
+ closingRuns.push(runId);
571
625
  }
572
626
  }
627
+ const joined = joinedOf[index];
573
628
  ownRuns.sort((left, right) => finalRuns.get(left).boundary - finalRuns.get(right).boundary);
574
629
  const close = next !== void 0 ? next.opener !== void 0 ? "next-opener" : "next-opener-unresolved" : cutAt !== void 0 ? "time-bound" : "end-of-store";
575
630
  const previousAnswer = previousProse(start.boundary);
@@ -626,12 +681,30 @@ function createTranscriptReview(options = {}) {
626
681
  },
627
682
  spanAt: (position) => position < endPosition ? lastAtOrBefore(spans, position) : void 0,
628
683
  runExtent: (runId) => {
629
- const run = finalRuns.get(runId);
630
- if (run === void 0)
684
+ const at = runStartIndex.get(runId);
685
+ return at === void 0 ? void 0 : { start: runStarts[at].boundary, end: runEndAt[at] };
686
+ },
687
+ runAt: (position) => {
688
+ let low = 0;
689
+ let high = runStarts.length - 1;
690
+ let found;
691
+ while (low <= high) {
692
+ const middle = low + high >> 1;
693
+ if (runStarts[middle].boundary <= position) {
694
+ found = middle;
695
+ low = middle + 1;
696
+ } else {
697
+ high = middle - 1;
698
+ }
699
+ }
700
+ if (found === void 0)
701
+ return void 0;
702
+ const start = runStarts[found].boundary;
703
+ while (found > 0 && runStarts[found - 1].boundary === start)
704
+ found -= 1;
705
+ if (position >= runEndAt[found])
631
706
  return void 0;
632
- const at = runStarts.findIndex((candidate) => candidate.runId === runId);
633
- const next = runStarts.slice(at + 1).find((candidate) => candidate.boundary > run.boundary);
634
- return { start: run.boundary, end: next?.boundary ?? endPosition };
707
+ return { runId: runStarts[found].runId, start, end: runEndAt[found] };
635
708
  },
636
709
  inputById: (inputId) => inputById.get(inputId),
637
710
  attemptAt: (position, block) => {
@@ -15,16 +15,16 @@ export declare const ATTACHMENTS_CUSTOM_TYPE = "attachments";
15
15
  export declare function branchEntryIds(entries: readonly TranscriptTreeEntryLike[]): ReadonlySet<string>;
16
16
 
17
17
  /**
18
- * Every `type: "custom"` entry of one `customType`, oldest→newest, with every
18
+ * Every `type: "custom"` entry of one `customType`, oldest->newest, with every
19
19
  * field ({@link TranscriptSourceEntryLike}'s id/timestamp/tree links plus the
20
- * entry's own `data`) preserved unchanged — `data` is returned exactly as it
20
+ * entry's own `data`) preserved unchanged - `data` is returned exactly as it
21
21
  * arrived, opaque and unvalidated, never coerced toward a schema this module
22
22
  * cannot know.
23
23
  *
24
24
  * `options.branch` is required, deliberately: `"active"` filters to
25
25
  * {@link branchEntryIds}; `"all"` returns every matching entry in the flat
26
26
  * log, rewind-abandoned or not. A silent default would let a caller reach
27
- * for whichever one it did not mean to — pick `"all"` only when the caller
27
+ * for whichever one it did not mean to - pick `"all"` only when the caller
28
28
  * itself needs to reason about abandoned entries (an audit, a recovery
29
29
  * tool); an ordinary reader states `"active"` explicitly.
30
30
  */
@@ -53,7 +53,7 @@ declare type ProducedResourceAction = "created" | "updated";
53
53
 
54
54
  /** One URI a tool call created or updated as a side effect. */
55
55
  declare interface ProducedResourceDeclaration {
56
- /** A `scheme://…` URI or an absolute local file path, compared as an exact string. */
56
+ /** A `scheme://...` URI or an absolute local file path, compared as an exact string. */
57
57
  readonly uri: string;
58
58
  readonly action: ProducedResourceAction;
59
59
  }
@@ -92,7 +92,7 @@ export declare interface TranscriptEntryLike {
92
92
  readonly id: string;
93
93
  readonly type: string;
94
94
  readonly customType?: string;
95
- /** Opaque. Never parsed or validated here — a feature's own schema owns that. */
95
+ /** Opaque. Never parsed or validated here - a feature's own schema owns that. */
96
96
  readonly data?: unknown;
97
97
  }
98
98
 
@@ -162,7 +162,9 @@ export declare interface TurnSegment {
162
162
  * nothing distinguishes a steer from a fresh request.
163
163
  *
164
164
  * `throughIndex` bounds the walk for a caller holding only a prefix; segments
165
- * are returned oldest first.
165
+ * are returned oldest first. A daemon reader that cannot hold the entry list
166
+ * looks the run up by run id instead (engine redesign 3.1,
167
+ * `docs/engine-redesign/decisions/2026-10-28-3.1-turn-definition.md`).
166
168
  */
167
169
  export declare function turnSegments(entries: readonly TurnBoundaryEntry[], throughIndex?: number): readonly TurnSegment[];
168
170