@yaklabs-ai/plugin-sdk 0.8.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.
@@ -0,0 +1,424 @@
1
+ // packages/plugin-sdk/dist/jev.js
2
+ function createFallbackOnlyWithJev() {
3
+ return async (_use, fallback, options = {}) => {
4
+ if (typeof fallback !== "function") {
5
+ throw new TypeError("withJev requires a `fallback` function as its second argument.");
6
+ }
7
+ const { signal, deadlineAt } = options;
8
+ if (deadlineAt !== void 0 && !Number.isFinite(deadlineAt)) {
9
+ throw new TypeError("withJev's `deadlineAt` must be a finite epoch-ms timestamp.");
10
+ }
11
+ const assertWithinBoundaries = () => {
12
+ if (signal?.aborted === true) {
13
+ throw signal.reason ?? new DOMException("Aborted", "AbortError");
14
+ }
15
+ if (deadlineAt !== void 0 && Date.now() >= deadlineAt) {
16
+ throw new Error("withJev's deadline has passed.");
17
+ }
18
+ };
19
+ assertWithinBoundaries();
20
+ let timer;
21
+ let onAbort;
22
+ const stopped = new Promise((_resolve, reject) => {
23
+ if (signal !== void 0) {
24
+ onAbort = () => reject(signal.reason ?? new DOMException("Aborted", "AbortError"));
25
+ signal.addEventListener("abort", onAbort, { once: true });
26
+ }
27
+ if (deadlineAt !== void 0) {
28
+ const checkDeadline = () => {
29
+ const remaining = deadlineAt - Date.now();
30
+ if (remaining <= 0) {
31
+ reject(new Error("withJev's deadline has passed."));
32
+ } else {
33
+ timer = setTimeout(checkDeadline, Math.min(remaining, 2147483647));
34
+ }
35
+ };
36
+ checkDeadline();
37
+ }
38
+ });
39
+ try {
40
+ const result = await Promise.race([
41
+ Promise.resolve().then(() => {
42
+ assertWithinBoundaries();
43
+ return fallback();
44
+ }),
45
+ stopped
46
+ ]);
47
+ assertWithinBoundaries();
48
+ return result;
49
+ } catch (error) {
50
+ assertWithinBoundaries();
51
+ throw error;
52
+ } finally {
53
+ if (timer !== void 0)
54
+ clearTimeout(timer);
55
+ if (signal !== void 0 && onAbort !== void 0) {
56
+ signal.removeEventListener("abort", onAbort);
57
+ }
58
+ }
59
+ };
60
+ }
61
+
62
+ // packages/plugin-sdk/dist/signals.js
63
+ import { z } from "zod";
64
+ function createFallbackOnlySignals() {
65
+ const noStore = { status: "unavailable", reason: "no-store" };
66
+ return {
67
+ observe: (input) => Promise.resolve(invalidObservation(input) ?? {
68
+ status: "unavailable",
69
+ reason: "no-jev-lane",
70
+ detector: { ...input.detector, rubricDigest: "" },
71
+ source: input.source,
72
+ stored: false,
73
+ deduplicated: false,
74
+ inputDigest: "",
75
+ recordedAtMs: Date.now()
76
+ }),
77
+ listDetectors: () => Promise.resolve(noStore),
78
+ list: () => Promise.resolve(noStore)
79
+ };
80
+ }
81
+ var SIGNAL_TIME_MAX_MS = 864e13;
82
+ var MAX_CHOICE_LABELS = 64;
83
+ var MAX_NAME = 64;
84
+ var MAX_POINTER = 256;
85
+ var MAX_QUESTIONS = 64;
86
+ var DETECTOR_ID = /^[a-z0-9]+(?:-[a-z0-9]+)*$/u;
87
+ var VERSION = /^[A-Za-z0-9]+(?:[._-][A-Za-z0-9]+)*$/u;
88
+ var QUESTION_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/u;
89
+ var POINTER = /^[^\s\p{C}/?#]+$/u;
90
+ var LABEL = /^[^\s,@\p{C}]+(?: [^\s,@\p{C}]+)*$/u;
91
+ var RUN_ID = /^\P{Cc}+$/u;
92
+ var POINTER_RULE = `1 to ${MAX_POINTER} characters with no whitespace, \`/\`, \`?\`, \`#\`, or control or invisible character`;
93
+ var ObserveShape = z.looseObject({
94
+ detector: z.looseObject({ id: z.string(), version: z.string() }),
95
+ source: z.looseObject({
96
+ sessionId: z.string(),
97
+ entryId: z.string(),
98
+ openerEntryId: z.string().optional(),
99
+ runId: z.string().optional(),
100
+ occurredAtMs: z.unknown().optional()
101
+ }),
102
+ questions: z.record(z.string(), z.unknown())
103
+ });
104
+ var ChoiceQuestion = z.looseObject({
105
+ type: z.literal("choice"),
106
+ criteria: z.record(z.string(), z.unknown())
107
+ });
108
+ var fits = (value, grammar) => value.length <= MAX_NAME && grammar.test(value);
109
+ var quoted = (value) => JSON.stringify(value.length > 40 ? `${value.slice(0, 40)}\u2026` : value).replace(/(?! )[\s\p{C}]/gu, (char) => `\\u${char.codePointAt(0).toString(16).padStart(4, "0")}`);
110
+ function grammarProblem(input) {
111
+ const shape = ObserveShape.safeParse(input);
112
+ if (!shape.success) {
113
+ const field = shape.error.issues[0]?.path.join(".") ?? "";
114
+ return field === "" ? "`observe` takes an object with `detector`, `source`, `state`, and `questions`." : `\`${field}\` is missing or has the wrong type.`;
115
+ }
116
+ const { detector, source, questions } = shape.data;
117
+ if (!fits(detector.id, DETECTOR_ID)) {
118
+ return "`detector.id` must be a lowercase slug of at most 64 characters: a-z and 0-9 in words joined by single hyphens.";
119
+ }
120
+ if (!fits(detector.version, VERSION)) {
121
+ return "`detector.version` must be at most 64 characters: letters and digits in parts joined by single `.`, `-`, or `_`.";
122
+ }
123
+ for (const [field, grammar, rule] of [
124
+ ["sessionId", POINTER, POINTER_RULE],
125
+ ["entryId", POINTER, POINTER_RULE],
126
+ ["openerEntryId", POINTER, POINTER_RULE],
127
+ [
128
+ "runId",
129
+ RUN_ID,
130
+ `1 to ${MAX_POINTER} characters with no control character`
131
+ ]
132
+ ]) {
133
+ const id = source[field];
134
+ if (id !== void 0 && !(id.length <= MAX_POINTER && grammar.test(id))) {
135
+ return `\`source.${field}\` must be ${rule}.`;
136
+ }
137
+ }
138
+ const occurredAtMs = source.occurredAtMs;
139
+ if (occurredAtMs !== void 0 && !(typeof occurredAtMs === "number" && Number.isSafeInteger(occurredAtMs) && occurredAtMs >= 0 && occurredAtMs <= SIGNAL_TIME_MAX_MS)) {
140
+ return `\`source.occurredAtMs\` must be whole epoch milliseconds from 0 to ${SIGNAL_TIME_MAX_MS}.`;
141
+ }
142
+ const names = Object.keys(questions);
143
+ if (names.length === 0 || names.length > MAX_QUESTIONS) {
144
+ return `\`questions\` must ask 1 to ${MAX_QUESTIONS} questions.`;
145
+ }
146
+ for (const name of names) {
147
+ if (!fits(name, QUESTION_NAME)) {
148
+ return `The question name ${quoted(name)} must be an identifier of at most 64 characters: letters, digits, and \`_\`, not starting with a digit.`;
149
+ }
150
+ const choice = ChoiceQuestion.safeParse(questions[name]);
151
+ if (!choice.success)
152
+ continue;
153
+ const labels = Object.keys(choice.data.criteria);
154
+ if (labels.length > MAX_CHOICE_LABELS) {
155
+ return `The choice question \`${name}\` offers ${labels.length} labels; at most ${MAX_CHOICE_LABELS}.`;
156
+ }
157
+ const bad = labels.find((label) => !fits(label, LABEL));
158
+ if (bad !== void 0) {
159
+ return `The choice question \`${name}\` offers the label ${quoted(bad)}, which a \`where\` filter cannot name: a label is 1 to 64 characters with no \`,\`, \`@\`, or control or invisible character, and no whitespace but single spaces between words.`;
160
+ }
161
+ }
162
+ return void 0;
163
+ }
164
+ var EchoText = z.string().catch("");
165
+ var Echo = z.object({
166
+ detector: z.object({ id: EchoText, version: EchoText }).catch({ id: "", version: "" }),
167
+ source: z.object({ sessionId: EchoText, entryId: EchoText }).catch({ sessionId: "", entryId: "" })
168
+ }).catch({
169
+ detector: { id: "", version: "" },
170
+ source: { sessionId: "", entryId: "" }
171
+ });
172
+ function invalidObservation(input) {
173
+ const reason = grammarProblem(input);
174
+ if (reason === void 0)
175
+ return void 0;
176
+ const { detector, source } = Echo.parse(input);
177
+ return {
178
+ status: "invalid",
179
+ reason,
180
+ detector: { ...detector, rubricDigest: "" },
181
+ source,
182
+ stored: false,
183
+ deduplicated: false,
184
+ inputDigest: "",
185
+ recordedAtMs: Date.now()
186
+ };
187
+ }
188
+
189
+ // packages/herd-uri/dist/errors.js
190
+ var UriError = class extends Error {
191
+ };
192
+ var CONFLICT_MESSAGES = {
193
+ changed: "This resource changed since this session last read it. Read it again before modifying it.",
194
+ unwitnessed: "This resource was not read in this session. Read it before modifying it.",
195
+ unknown: "This resource changed or was not read in this session. Read it again before modifying it."
196
+ };
197
+ var UriConflictError = class extends UriError {
198
+ _tag = "UriConflictError";
199
+ reason;
200
+ constructor(reason) {
201
+ super(CONFLICT_MESSAGES[reason ?? "unknown"]);
202
+ this.name = "UriConflictError";
203
+ this.reason = reason;
204
+ }
205
+ };
206
+ var UriValidationError = class extends UriError {
207
+ _tag = "UriValidationError";
208
+ constructor(message) {
209
+ super(message);
210
+ this.name = "UriValidationError";
211
+ }
212
+ };
213
+ var UriProviderError = class extends UriError {
214
+ _tag = "UriProviderError";
215
+ moduleId;
216
+ operation;
217
+ uri;
218
+ status;
219
+ providerCode;
220
+ retryable;
221
+ /** Provider-directed delay before retrying, in milliseconds. */
222
+ retryAfterMs;
223
+ constructor(options) {
224
+ super(options.message);
225
+ this.name = "UriProviderError";
226
+ this.moduleId = options.moduleId;
227
+ this.operation = options.operation;
228
+ this.uri = options.uri;
229
+ if (options.status !== void 0)
230
+ this.status = options.status;
231
+ if (options.providerCode !== void 0)
232
+ this.providerCode = options.providerCode;
233
+ if (options.retryable !== void 0)
234
+ this.retryable = options.retryable;
235
+ if (options.retryAfterMs !== void 0) {
236
+ if (!Number.isInteger(options.retryAfterMs) || options.retryAfterMs < 0) {
237
+ throw new TypeError("retryAfterMs must be a nonnegative integer.");
238
+ }
239
+ this.retryAfterMs = options.retryAfterMs;
240
+ }
241
+ }
242
+ };
243
+ var UriNotFoundError = class extends UriError {
244
+ uri;
245
+ hint;
246
+ _tag = "UriNotFoundError";
247
+ constructor(uri, hint) {
248
+ super(hint !== void 0 ? `No resource found at '${uri}'. ${hint}` : `No resource found at '${uri}'.`);
249
+ this.uri = uri;
250
+ this.hint = hint;
251
+ this.name = "UriNotFoundError";
252
+ }
253
+ };
254
+ var UriUnsupportedOperationError = class extends UriError {
255
+ operation;
256
+ moduleId;
257
+ uri;
258
+ _tag = "UriUnsupportedOperationError";
259
+ constructor(operation, moduleId, uri, message) {
260
+ super(message ?? `URI module '${moduleId}' does not support '${operation}' for '${uri}'.`);
261
+ this.operation = operation;
262
+ this.moduleId = moduleId;
263
+ this.uri = uri;
264
+ this.name = "UriUnsupportedOperationError";
265
+ }
266
+ };
267
+
268
+ // packages/herd-uri/dist/operations.js
269
+ import { z as z2 } from "zod";
270
+ var URI_OPERATIONS = [
271
+ "help",
272
+ "read",
273
+ "write",
274
+ "edit",
275
+ "list",
276
+ "search",
277
+ "delete"
278
+ ];
279
+ var UriOperationSchema = z2.enum(URI_OPERATIONS);
280
+
281
+ // packages/herd-uri/dist/help-schema.js
282
+ import { z as z3 } from "zod";
283
+ var UriContractJsonSchema = z3.record(z3.string(), z3.unknown());
284
+ var UriParameterSchema = z3.strictObject({
285
+ name: z3.string().min(1),
286
+ in: z3.enum(["uri", "query", "selector"]),
287
+ required: z3.boolean(),
288
+ description: z3.string().optional(),
289
+ schema: UriContractJsonSchema,
290
+ examples: z3.array(z3.unknown()).optional()
291
+ });
292
+ var UriPayloadContractSchema = z3.strictObject({
293
+ name: z3.string().min(1),
294
+ mediaType: z3.string().optional(),
295
+ description: z3.string().optional(),
296
+ schema: UriContractJsonSchema.optional(),
297
+ semanticConstraints: z3.array(z3.string().min(1)).min(1).optional(),
298
+ examples: z3.array(z3.unknown()).optional()
299
+ });
300
+
301
+ // packages/herd-uri/dist/text-edit.js
302
+ import { z as z4 } from "zod";
303
+ var findReplaceArm = z4.strictObject({
304
+ find: z4.string().min(1),
305
+ replace: z4.string(),
306
+ all: z4.boolean().optional()
307
+ });
308
+ var oldNewEntry = z4.strictObject({
309
+ oldText: z4.string().min(1),
310
+ newText: z4.string()
311
+ });
312
+ var uriTextEditPayloadArms = z4.union([
313
+ findReplaceArm,
314
+ z4.strictObject({ edits: z4.array(oldNewEntry).min(1) }),
315
+ oldNewEntry,
316
+ z4.array(oldNewEntry).min(1)
317
+ ]);
318
+
319
+ // packages/herd-uri/dist/schema/to-json-schema.js
320
+ import { z as z5 } from "zod";
321
+ function toJsonSchema(schema) {
322
+ if (schema instanceof z5.ZodType) {
323
+ const { $schema: _dialect, ...value } = z5.toJSONSchema(schema, {
324
+ // `io: "input"` describes what a caller may SEND, which is what a parameter and an
325
+ // `accepts` payload advertise: a field with a Zod default is optional on the wire even
326
+ // though the parsed value always carries it. Strictness still survives where the validator
327
+ // really enforces it — `z.strictObject` emits `additionalProperties: false`, while a plain
328
+ // `z.object` correctly does not, because it TOLERATES unknown keys and silently strips
329
+ // them. A `returns` payload derived from a Zod schema is described in the same input terms;
330
+ // no module does that today, and a faithful output projection would need its own seam.
331
+ io: "input",
332
+ // Inline every reused sub-schema rather than emitting `$ref`s into a `$defs` block. A
333
+ // contract schema is a FRAGMENT: it gets embedded in a payload contract, and callers
334
+ // legitimately lift one arm of a union out of it. A `$ref` only resolves relative to the
335
+ // root that carries `$defs`, so the moment the fragment moves, the reference dangles.
336
+ // Self-contained is worth the repetition at these sizes.
337
+ reused: "inline",
338
+ // Recursion (`z.lazy`) has no self-contained form: zod's default is to emit a
339
+ // `$defs` block with `$ref` pointers, which dangle the moment the fragment is
340
+ // embedded. Throw instead, so a contract that needs recursion has to disclose a
341
+ // non-recursive shape (see skill-module's metadata disclosure) rather than ship a
342
+ // reference nothing can resolve.
343
+ cycles: "throw"
344
+ });
345
+ return value;
346
+ }
347
+ return structuredClone(schema);
348
+ }
349
+
350
+ // packages/herd-uri/dist/schema/helpers.js
351
+ function payload(name, schema, options = {}) {
352
+ return {
353
+ name,
354
+ ...options.mediaType === void 0 ? {} : { mediaType: options.mediaType },
355
+ ...options.description === void 0 ? {} : { description: options.description },
356
+ schema: toJsonSchema(schema),
357
+ ...options.semanticConstraints === void 0 ? {} : { semanticConstraints: options.semanticConstraints },
358
+ ...options.examples === void 0 ? {} : { examples: options.examples }
359
+ };
360
+ }
361
+
362
+ // packages/plugin-sdk/dist/kay-app/transcript-text.js
363
+ function transcriptMessageTexts(entries) {
364
+ return entries.flatMap((entry) => {
365
+ if (entry.type !== "message")
366
+ return [];
367
+ const role = entry.message.role;
368
+ if (role !== "user" && role !== "assistant")
369
+ return [];
370
+ const text = transcriptMessageText(entry.message);
371
+ return text === "" ? [] : [{ role, text }];
372
+ });
373
+ }
374
+ function transcriptMessageText(message) {
375
+ if (typeof message.content === "string")
376
+ return message.content.trim();
377
+ if (!Array.isArray(message.content))
378
+ return "";
379
+ return message.content.flatMap((part) => {
380
+ if (typeof part !== "object" || part === null)
381
+ return [];
382
+ const candidate = part;
383
+ return candidate.type === "text" && typeof candidate.text === "string" ? [candidate.text] : [];
384
+ }).join("").trim();
385
+ }
386
+ function transcriptRoleLabel(role) {
387
+ return role === "user" ? "User" : "Assistant";
388
+ }
389
+
390
+ // packages/plugin-protocol/dist/app-data.js
391
+ var KAY_APP_DATA_SNAPSHOT_MAX_BYTES = 16 * 1024 * 1024;
392
+ var KAY_APP_DATA_CHANGE_MAX_BYTES = 256 * 1024;
393
+ var KAY_APP_DATA_READ_REQUEST_MAX_BYTES = 64 * 1024;
394
+ var KAY_APP_DATA_READ_RESULT_MAX_BYTES = 16 * 1024 * 1024;
395
+
396
+ // packages/plugin-sdk/dist/kay-app/index.js
397
+ function isRetryable(result) {
398
+ return result.status === "not-ready";
399
+ }
400
+ var KAY_APP_AUTOMATION_CONDITION_KINDS = [
401
+ "time",
402
+ "uri-change",
403
+ "occurrence",
404
+ "delayed-occurrence"
405
+ ];
406
+ export {
407
+ KAY_APP_AUTOMATION_CONDITION_KINDS,
408
+ KAY_APP_DATA_CHANGE_MAX_BYTES,
409
+ MAX_CHOICE_LABELS,
410
+ SIGNAL_TIME_MAX_MS,
411
+ UriConflictError,
412
+ UriNotFoundError,
413
+ UriProviderError,
414
+ UriUnsupportedOperationError,
415
+ UriValidationError,
416
+ createFallbackOnlySignals,
417
+ createFallbackOnlyWithJev,
418
+ invalidObservation,
419
+ isRetryable,
420
+ payload,
421
+ transcriptMessageText,
422
+ transcriptMessageTexts,
423
+ transcriptRoleLabel
424
+ };
@@ -0,0 +1,127 @@
1
+ import { z } from 'zod';
2
+
3
+ export declare const PHASE_SHIFT_CUSTOM_TYPE = "ext:kay.phase-shift:observation";
4
+
5
+ /**
6
+ * The custom-entry type a SURFACE reads phase shifts from: the derived
7
+ * {@link PhaseShiftTimelineRecord} stream, one record per accepted check.
8
+ *
9
+ * Never written to a session log. The detector records
10
+ * {@link PHASE_SHIFT_CUSTOM_TYPE} observations, which stay daemon bookkeeping
11
+ * because a surface cannot validate them: custom entries reach a surface
12
+ * without their entry ids, a tool call carries no entry id at all, and the
13
+ * observation's source ids routinely name tool-result entries. The daemon
14
+ * therefore folds the branch itself and hands a surface only what it can place.
15
+ */
16
+ export declare const PHASE_SHIFT_TIMELINE_CUSTOM_TYPE = "ext:kay.phase-shift:timeline";
17
+
18
+ declare type PhaseShiftCost = z.infer<typeof phaseShiftCostSchema>;
19
+
20
+ /**
21
+ * What one check cost: the detector's OWN model call, priced by the same
22
+ * accounting as any other direct completion.
23
+ *
24
+ * It lives on the observation because nothing else can carry it. A detector
25
+ * call is a direct completion, not a turn, so it writes no `herd.cost_fact`
26
+ * and never reaches the session's usage ledger; the daemon's telemetry knows
27
+ * it, but a surface reading the session log does not. Recording it here is how
28
+ * the feature accounts for itself.
29
+ *
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.
32
+ */
33
+ declare const phaseShiftCostSchema: z.ZodObject<{
34
+ amountUsd: z.ZodNumber;
35
+ amountBasis: z.ZodEnum<{
36
+ unknown: "unknown";
37
+ measured: "measured";
38
+ "list-estimate": "list-estimate";
39
+ }>;
40
+ inputTokens: z.ZodNumber;
41
+ outputTokens: z.ZodNumber;
42
+ attempts: z.ZodNumber;
43
+ }, z.core.$strip>;
44
+
45
+ /** One accepted transition, placed in time. */
46
+ export declare interface PhaseShiftTimelineChange {
47
+ readonly startEntryId: string;
48
+ /** When the new work began — the onset entry's own timestamp, not the judgment's. */
49
+ readonly startedAt: string;
50
+ readonly relation: "outcome" | "phase";
51
+ readonly reason: string;
52
+ readonly purposeAfter: string;
53
+ }
54
+
55
+ /**
56
+ * A session's phase-shift timeline, read from its FLAT entry log.
57
+ *
58
+ * The one entry point for a consumer holding a whole log rather than a branch:
59
+ * it walks the tree to the active branch itself (a `leaf` marker moves the
60
+ * chain, so the last entry is not the branch tip after a rewind), folds the
61
+ * branch through {@link acceptedPhaseShiftObservations}, and resolves a
62
+ * version-1 record's source timestamps from the entries it referenced. Those
63
+ * references are branch-validated by the fold, so the resolution is total.
64
+ */
65
+ export declare function phaseShiftTimelineFromEntries(entries: readonly TranscriptSourceEntry[]): readonly PhaseShiftTimelineRecord[];
66
+
67
+ /**
68
+ * One accepted detector check, as a surface reads it.
69
+ *
70
+ * A record is evidence about the span it names and nothing else: time outside
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
73
+ * found no transition.
74
+ */
75
+ export declare interface PhaseShiftTimelineRecord {
76
+ readonly version: 1;
77
+ readonly detectorVersion: string;
78
+ /** The model that judged, as resolved after any provider recovery. */
79
+ readonly modelReference: string;
80
+ /** When the detector RECOGNIZED this, which can trail the onsets it names. */
81
+ readonly observedAt: string;
82
+ readonly windowStartEntryId: string;
83
+ readonly windowStartAt: string;
84
+ readonly throughEntryId: string;
85
+ readonly throughAt: string;
86
+ /** The purpose standing after this check. */
87
+ readonly purpose: string;
88
+ /** Accepted transitions in onset order; empty means none was found. */
89
+ readonly changes: readonly PhaseShiftTimelineChange[];
90
+ /**
91
+ * What running this check cost. Absent means the cost was not recorded (every
92
+ * record written before the detector accounted for itself, and every version-1
93
+ * record), which is never the same as a check that cost nothing.
94
+ */
95
+ readonly cost?: PhaseShiftCost;
96
+ }
97
+
98
+ /** The fields the customType selector needs off a session entry. */
99
+ declare interface TranscriptEntry {
100
+ readonly id: string;
101
+ readonly type: string;
102
+ readonly customType?: string;
103
+ /** Opaque. Never parsed or validated here — a feature's own schema owns that. */
104
+ readonly data?: unknown;
105
+ }
106
+
107
+ /**
108
+ * A tree entry plus the ISO timestamp the timeline projections read alongside
109
+ * the walk result (`turnSegments`, `phaseShiftTimelineFromEntries`).
110
+ */
111
+ declare interface TranscriptSourceEntry extends TranscriptTreeEntry {
112
+ readonly timestamp: string;
113
+ }
114
+
115
+ /**
116
+ * A session entry as {@link branchEntryIds} reads it: the fields above plus the
117
+ * tree links the walk follows to find the active branch. No timestamp: the walk
118
+ * never reads one, and a live v2 entry carries epoch milliseconds where a raw
119
+ * v3 log carries an ISO string, so requiring either shape here would force one
120
+ * class of caller to convert entries it only wants walked.
121
+ */
122
+ declare interface TranscriptTreeEntry extends TranscriptEntry {
123
+ readonly parentId?: string | null;
124
+ readonly targetId?: string | null;
125
+ }
126
+
127
+ export { }