narraeon 0.1.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.
Files changed (60) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +164 -0
  3. package/README.zh-CN.md +164 -0
  4. package/dist/node/cli/NarraeonCli.js +163 -0
  5. package/dist/node/cli/main.js +38 -0
  6. package/dist/node/protocol/appPreferences.js +5 -0
  7. package/dist/node/protocol/contentTree.js +14 -0
  8. package/dist/node/protocol/modelConnections.js +110 -0
  9. package/dist/node/protocol/playerViews.js +1 -0
  10. package/dist/node/protocol/v1.js +278 -0
  11. package/dist/node/runtime/V1Runtime.js +625 -0
  12. package/dist/node/runtime/artifact/FileNativeArtifactStore.js +1914 -0
  13. package/dist/node/runtime/config/AppPreferencesStore.js +92 -0
  14. package/dist/node/runtime/config/appPaths.js +19 -0
  15. package/dist/node/runtime/content/ContentLimits.js +1 -0
  16. package/dist/node/runtime/content/ContentTreeFile.js +1 -0
  17. package/dist/node/runtime/content/ContentWorkspace.js +601 -0
  18. package/dist/node/runtime/content/CurrentTreeContentLibrary.js +721 -0
  19. package/dist/node/runtime/content/FileNativeContentTree.js +512 -0
  20. package/dist/node/runtime/extension/FrontendExtensionBundle.js +229 -0
  21. package/dist/node/runtime/extension/PlayerViewPanelProjector.js +121 -0
  22. package/dist/node/runtime/model/AiFailureLog.js +332 -0
  23. package/dist/node/runtime/model/FileNativeModelAdapters.js +1246 -0
  24. package/dist/node/runtime/model/ModelConnectionStore.js +378 -0
  25. package/dist/node/runtime/model/ModelHost.js +89 -0
  26. package/dist/node/runtime/model/ModelHostStream.js +367 -0
  27. package/dist/node/runtime/model/ProviderExchangeState.js +1 -0
  28. package/dist/node/runtime/model/ProviderStream.js +90 -0
  29. package/dist/node/runtime/model/SettingAuthorStream.js +118 -0
  30. package/dist/node/runtime/play/FileNativeContinuityCorrection.js +304 -0
  31. package/dist/node/runtime/play/FileNativePlayPresetStore.js +1856 -0
  32. package/dist/node/runtime/play/PlayCallChain.js +1280 -0
  33. package/dist/node/runtime/play/PlayDocumentTools.js +1121 -0
  34. package/dist/node/runtime/play/PlayFollowupRequests.js +183 -0
  35. package/dist/node/runtime/play/PlayPresetWorkbench.js +185 -0
  36. package/dist/node/runtime/prompt/FileNativePromptCompiler.js +1425 -0
  37. package/dist/node/runtime/prompt/FileNativeToolRegistry.js +192 -0
  38. package/dist/node/runtime/prompt/MaterialSelection.js +95 -0
  39. package/dist/node/runtime/setting/DocumentCandidateSettingImprovement.js +1726 -0
  40. package/dist/node/runtime/world/FileNativeWorldStore.js +2028 -0
  41. package/dist/node/runtime/world/PlayerViewRenderer.js +322 -0
  42. package/dist/node/runtime/world/WorldDocumentStore.js +2808 -0
  43. package/dist/node/runtime/world/WorldOperationCoordinator.js +490 -0
  44. package/dist/node/runtime/world/WorldStateRevision.js +75 -0
  45. package/dist/node/server/createRuntime.js +6 -0
  46. package/dist/node/server/createServer.js +218 -0
  47. package/dist/node/server/main.js +25 -0
  48. package/dist/node/server/startWebServer.js +28 -0
  49. package/dist/node/shared/default-play-prompts.js +85 -0
  50. package/dist/node/shared/default-preset-host.js +538 -0
  51. package/dist/node/shared/default-setting-improvement-prompt.js +77 -0
  52. package/dist/node/shared/first-party-action-choices.js +164 -0
  53. package/dist/node/shared/first-party-generic-panels.js +105 -0
  54. package/dist/node/shared/first-party-play-preset-templates.js +41 -0
  55. package/dist/node/shared/first-party-player-view.js +179 -0
  56. package/dist/node/shared/play-preset-prompt-roles.js +4 -0
  57. package/dist/web/assets/index-CRGTZ9Ak.css +1 -0
  58. package/dist/web/assets/index-DDx1itzT.js +1192 -0
  59. package/dist/web/index.html +14 -0
  60. package/package.json +86 -0
@@ -0,0 +1,1121 @@
1
+ import { createHash, createHmac, randomBytes, timingSafeEqual, } from "node:crypto";
2
+ import { stringify } from "yaml";
3
+ import { PromptCompilationError, } from "../prompt/FileNativePromptCompiler.js";
4
+ import {} from "../world/WorldDocumentStore.js";
5
+ import { acceptWorldStateRevision, beginWorldStateRevision, fileNativeStateChanges, worldDocumentRevisionFailureMessage, } from "../world/WorldStateRevision.js";
6
+ class PlayDocumentToolFailure extends Error {
7
+ failureKind;
8
+ constructor(message, failureKind = "protocol") {
9
+ super(message);
10
+ this.name = "PlayDocumentToolFailure";
11
+ this.failureKind = failureKind;
12
+ }
13
+ }
14
+ /**
15
+ * One operation-bound document context for the play call chain. Product flows
16
+ * submit tool calls here; document parsing, identity, lookup, locator semantics
17
+ * and revision mechanics remain behind WorldDocumentStore.open/query/revise.
18
+ */
19
+ export class FileNativePlayDocuments {
20
+ #candidate;
21
+ #reads;
22
+ constructor(files) {
23
+ this.#candidate = openPlayCandidate({ ...files });
24
+ this.#reads = {
25
+ snapshotId: this.#candidate.snapshot.id,
26
+ documents: new Map(),
27
+ pendingReads: new Map(),
28
+ };
29
+ }
30
+ get snapshot() {
31
+ return this.#candidate.snapshot;
32
+ }
33
+ bindBootstrap(bootstrap) {
34
+ this.#reads = bootstrapAuthorizations(bootstrap, this.#candidate.snapshot);
35
+ }
36
+ authorizationCheckpoint() {
37
+ return {
38
+ schemaVersion: 1,
39
+ kind: "play_document_authorizations",
40
+ stateFingerprint: fingerprintStateFiles(this.#candidate.files),
41
+ documents: [...this.#reads.documents.entries()]
42
+ .sort(([left], [right]) => left.localeCompare(right))
43
+ .map(([shortRef, locators]) => ({
44
+ shortRef,
45
+ locators: locators === null
46
+ ? null
47
+ : locators
48
+ .filter((locator, index, all) => all.findIndex((candidate) => sameLocator(candidate, locator)) === index)
49
+ .map((locator) => structuredClone(locator)),
50
+ })),
51
+ };
52
+ }
53
+ restoreAuthorizationCheckpoint(checkpoint) {
54
+ if (!isPlayDocumentAuthorizationCheckpoint(checkpoint))
55
+ throw new TypeError("The play-document authorization checkpoint has an invalid format");
56
+ if (checkpoint.stateFingerprint !==
57
+ fingerprintStateFiles(this.#candidate.files))
58
+ throw new TypeError("The play-document authorization checkpoint does not match the current state");
59
+ const documents = new Map();
60
+ for (const { shortRef, locators } of checkpoint.documents) {
61
+ if (documentDescriptorByRef(this.#candidate.snapshot, shortRef) === null)
62
+ throw new TypeError(`The play-document authorization checkpoint refers to missing @${shortRef}`);
63
+ const restoredLocators = locators?.map((locator) => {
64
+ const selected = this.#candidate.snapshot.query({
65
+ kind: "select_node",
66
+ document: { shortRef },
67
+ locator,
68
+ });
69
+ if (selected.kind !== "select_node" || !selected.ok)
70
+ throw new TypeError(`The play-document authorization checkpoint refers to a missing node in @${shortRef}`);
71
+ return structuredClone(selected.node.locator);
72
+ });
73
+ documents.set(shortRef, restoredLocators ?? null);
74
+ }
75
+ this.#reads = {
76
+ snapshotId: this.#candidate.snapshot.id,
77
+ documents,
78
+ // Provider cursors are deliberately snapshot-local. A partial read has
79
+ // granted no write authority yet, so it restarts after cold recovery.
80
+ pendingReads: new Map(),
81
+ };
82
+ }
83
+ contextToolBudgetRequest(call, history) {
84
+ const preview = previewContextToolResult(this.#candidate.snapshot, history, call);
85
+ if (preview === null)
86
+ return null;
87
+ const retryParameter = call.name === "context_read" ? "max_bytes" : "limit";
88
+ return {
89
+ requestedResultBytes: Buffer.byteLength(preview.markdown, "utf8"),
90
+ previewMarkdown: preview.markdown,
91
+ retryParameter,
92
+ retryMinimum: call.name === "context_read" ? 4 : 1,
93
+ retryMaximum: contextToolRetryMaximum(call),
94
+ restartWithoutCursor: record(call.arguments) && typeof call.arguments.cursor === "string",
95
+ };
96
+ }
97
+ contextToolRetryPreview(call, history, retryValue) {
98
+ const argumentsWithoutCursor = record(call.arguments)
99
+ ? Object.fromEntries(Object.entries(call.arguments).filter(([key]) => key !== "cursor"))
100
+ : {};
101
+ const retryCall = {
102
+ ...structuredClone(call),
103
+ id: `${call.id}-budget-retry`,
104
+ arguments: {
105
+ ...argumentsWithoutCursor,
106
+ [call.name === "context_read" ? "maxBytes" : "limit"]: retryValue,
107
+ },
108
+ };
109
+ const preview = previewContextToolResult(this.#candidate.snapshot, history, retryCall);
110
+ if (preview === null)
111
+ throw new TypeError("context tool retry preview requires a read tool");
112
+ return { call: retryCall, markdown: preview.markdown };
113
+ }
114
+ execute(call, history) {
115
+ if (call.name === "context_search")
116
+ return executeContextSearch(this.#candidate.snapshot, history, call.arguments);
117
+ if (call.name === "context_list")
118
+ return executeContextList(this.#candidate.snapshot, history, call.arguments);
119
+ if (call.name === "context_read") {
120
+ const result = executeContextRead(this.#candidate.snapshot, history, call.arguments);
121
+ authorizeToolRead(this.#reads, this.#candidate.snapshot, result);
122
+ return result;
123
+ }
124
+ if (call.name === "world_patch")
125
+ return applyPlayCandidatePatch(this.#candidate, this.#reads, call.arguments);
126
+ if (call.name === "world_create")
127
+ return applyPlayCandidateCreate(this.#candidate, this.#reads, call.arguments);
128
+ return toolFailure(`The current document context does not accept ${call.name}`);
129
+ }
130
+ stateChanges() {
131
+ return playCandidateStateChanges(this.#candidate);
132
+ }
133
+ /**
134
+ * The current revision has been accepted by Authority. Keep using the same
135
+ * snapshot and read authorizations, but make subsequent writes relative to
136
+ * this new committed baseline instead of replaying the old diff.
137
+ */
138
+ acceptCommittedState() {
139
+ this.#candidate.changes.clear();
140
+ this.#candidate.suppliedBytes = 0;
141
+ }
142
+ }
143
+ function bootstrapAuthorizations(bootstrap, snapshot) {
144
+ const result = {
145
+ snapshotId: snapshot.id,
146
+ documents: new Map(),
147
+ pendingReads: new Map(),
148
+ };
149
+ for (const { status, complete, readAuthorization } of bootstrap.coverage) {
150
+ if (status !== "resolved" || !complete || readAuthorization === undefined)
151
+ continue;
152
+ if (readAuthorization.locator === null) {
153
+ const resolved = snapshot.query({
154
+ kind: "read_document",
155
+ document: { shortRef: readAuthorization.shortRef },
156
+ maxBytes: 4,
157
+ });
158
+ if (resolved.kind === "read_document" && resolved.ok)
159
+ authorizeRead(result, snapshot.id, resolved.document.shortRef, null);
160
+ }
161
+ else {
162
+ const selected = snapshot.query({
163
+ kind: "select_node",
164
+ document: { shortRef: readAuthorization.shortRef },
165
+ locator: readAuthorization.locator,
166
+ });
167
+ if (selected.kind === "select_node" && selected.ok)
168
+ authorizeRead(result, snapshot.id, selected.document.shortRef, selected.node.locator);
169
+ }
170
+ }
171
+ return result;
172
+ }
173
+ function authorizeRead(authorizations, snapshotId, ref, locator) {
174
+ if (authorizations.snapshotId !== snapshotId)
175
+ return;
176
+ if (locator === null)
177
+ authorizations.documents.set(ref, null);
178
+ else if (authorizations.documents.get(ref) !== null)
179
+ authorizations.documents.set(ref, [
180
+ ...(authorizations.documents.get(ref) ?? []),
181
+ locator,
182
+ ]);
183
+ }
184
+ function authorizeToolRead(authorizations, snapshot, result) {
185
+ const authorization = result.readAuthorization;
186
+ if (authorization?.snapshotId !== snapshot.id ||
187
+ authorizations.snapshotId !== snapshot.id)
188
+ return;
189
+ const { page } = authorization;
190
+ let progress = authorizations.pendingReads.get(authorization.readKey);
191
+ if (page.start === 0) {
192
+ progress = {
193
+ shortRef: authorization.shortRef,
194
+ locator: authorization.locator,
195
+ nextOffset: page.end,
196
+ totalBytes: page.total,
197
+ };
198
+ authorizations.pendingReads.set(authorization.readKey, progress);
199
+ }
200
+ else {
201
+ if (progress === undefined)
202
+ return;
203
+ if (progress.shortRef !== authorization.shortRef ||
204
+ !sameLocator(progress.locator, authorization.locator) ||
205
+ progress.nextOffset !== page.start ||
206
+ progress.totalBytes !== page.total)
207
+ return;
208
+ progress.nextOffset = page.end;
209
+ }
210
+ if (progress.nextOffset !== progress.totalBytes)
211
+ return;
212
+ authorizeRead(authorizations, snapshot.id, progress.shortRef, progress.locator);
213
+ authorizations.pendingReads.delete(authorization.readKey);
214
+ }
215
+ function sameLocator(left, right) {
216
+ if (left === null || right === null)
217
+ return left === right;
218
+ const leftPath = "yaml" in left ? left.yaml : left.markdown;
219
+ const rightPath = "yaml" in right ? right.yaml : right.markdown;
220
+ return ("yaml" in left === "yaml" in right &&
221
+ leftPath.length === rightPath.length &&
222
+ leftPath.every((segment, index) => segment === rightPath[index]));
223
+ }
224
+ function parseYamlHandleSegment(segment) {
225
+ return /^(?:0|[1-9][0-9]*)$/u.test(segment) ? Number(segment) : segment;
226
+ }
227
+ function openPlayCandidate(files) {
228
+ return {
229
+ ...beginWorldStateRevision(files),
230
+ suppliedBytes: 0,
231
+ };
232
+ }
233
+ function documentDescriptorByRef(snapshot, ref) {
234
+ const result = snapshot.query({
235
+ kind: "read_document",
236
+ document: { shortRef: ref },
237
+ maxBytes: 4,
238
+ });
239
+ return result.kind === "read_document" && result.ok ? result.document : null;
240
+ }
241
+ function applyPlayCandidatePatch(candidate, reads, args) {
242
+ if (!record(args) ||
243
+ typeof args.target !== "string" ||
244
+ !Array.isArray(args.edits) ||
245
+ args.edits.length < 1 ||
246
+ args.edits.length > 32)
247
+ return toolFailure("world_patch requires a target and 1 to 32 edits.");
248
+ const ref = args.target.replace(/^@/u, "");
249
+ const target = documentDescriptorByRef(candidate.snapshot, ref);
250
+ if (target === null)
251
+ return toolFailure(`Target @${ref} does not exist.`);
252
+ const retainedAuthorization = reads.documents.get(ref);
253
+ if (reads.snapshotId !== candidate.snapshot.id ||
254
+ retainedAuthorization === undefined ||
255
+ !editsAreAuthorized(retainedAuthorization, args.edits))
256
+ return toolFailure(`Read @${ref} exactly, or receive the complete document in bootstrap, before writing it.`);
257
+ try {
258
+ const suppliedBytes = Buffer.byteLength(JSON.stringify(args.edits), "utf8");
259
+ if (suppliedBytes > 64 * 1024)
260
+ throw new PlayDocumentToolFailure("A single patch body exceeds 64 KiB");
261
+ if (candidate.suppliedBytes + suppliedBytes > 256 * 1024)
262
+ throw new PlayDocumentToolFailure("New body content for this model operation exceeds 256 KiB in total");
263
+ assertToolReferenceHandles(args.edits);
264
+ const revised = candidate.snapshot.revise({
265
+ commands: [
266
+ {
267
+ kind: "patch",
268
+ document: { documentId: target.documentId },
269
+ edits: args.edits,
270
+ },
271
+ ],
272
+ });
273
+ if (!revised.ok || revised.snapshotStatus !== "usable")
274
+ throw new PlayDocumentToolFailure(worldDocumentRevisionFailureMessage(revised.diagnostics), "candidate");
275
+ const changed = revised.changes.length > 0;
276
+ const receipt = formatToolRevisionReceipt(target.shortRef, revised.changes);
277
+ const previousSnapshot = candidate.snapshot;
278
+ acceptWorldStateRevision(candidate, revised);
279
+ candidate.suppliedBytes += suppliedBytes;
280
+ carryWriteAuthorizations(previousSnapshot, candidate, reads, revised.changes);
281
+ if (retainedAuthorization === null)
282
+ authorizeRead(reads, candidate.snapshot.id, ref, null);
283
+ else
284
+ for (const locator of retainedAuthorization)
285
+ authorizeRead(reads, candidate.snapshot.id, ref, locator);
286
+ return {
287
+ ok: true,
288
+ markdown: receipt,
289
+ candidateWrite: { shortRef: target.shortRef, changed },
290
+ };
291
+ }
292
+ catch (error) {
293
+ if (error instanceof PlayDocumentToolFailure)
294
+ return toolFailure(error.message, error.failureKind);
295
+ throw error;
296
+ }
297
+ }
298
+ function editsAreAuthorized(authorization, edits) {
299
+ if (authorization === null)
300
+ return true;
301
+ if (authorization === undefined)
302
+ return false;
303
+ return edits.every((edit) => {
304
+ if (!record(edit) || edit.op === "set_metadata" || !record(edit.locator))
305
+ return false;
306
+ const requested = Array.isArray(edit.locator.yaml)
307
+ ? { codec: "yaml", path: edit.locator.yaml }
308
+ : Array.isArray(edit.locator.markdown)
309
+ ? { codec: "markdown", path: edit.locator.markdown }
310
+ : null;
311
+ return (requested !== null &&
312
+ authorization.some((allowed) => {
313
+ const path = "yaml" in allowed ? allowed.yaml : allowed.markdown;
314
+ return (requested.codec in allowed &&
315
+ path.every((segment, index) => requested.path[index] === segment));
316
+ }));
317
+ });
318
+ }
319
+ function formatToolRevisionReceipt(shortRef, changes) {
320
+ if (changes.length > 1)
321
+ throw new Error("A world_patch revision must affect only its target document");
322
+ return changes.length === 1
323
+ ? `@${shortRef} pending write`
324
+ : `@${shortRef} unchanged`;
325
+ }
326
+ function nextAvailableStatePath(candidate, parentDirectory, refHint, codec) {
327
+ const directory = parentDirectory === "" ? "" : `${parentDirectory}/`;
328
+ const extension = codec === "yaml" ? "yaml" : "md";
329
+ for (let suffix = 1;; suffix += 1) {
330
+ const suffixText = suffix === 1 ? "" : `-${suffix}`;
331
+ const fileName = `${refHint.slice(0, 32 - suffixText.length)}${suffixText}`;
332
+ const path = `state/${directory}${fileName}.${extension}`;
333
+ if (candidate.files[path] === undefined)
334
+ return path;
335
+ }
336
+ }
337
+ function assertToolReferenceHandles(value, seen = new WeakSet()) {
338
+ if (value === null || typeof value !== "object")
339
+ return;
340
+ if (seen.has(value))
341
+ throw new PlayDocumentToolFailure("Tool arguments must not contain circular references");
342
+ seen.add(value);
343
+ if (Array.isArray(value)) {
344
+ for (const item of value)
345
+ assertToolReferenceHandles(item, seen);
346
+ seen.delete(value);
347
+ return;
348
+ }
349
+ if (!record(value))
350
+ throw new PlayDocumentToolFailure("Tool arguments may contain only plain objects");
351
+ if (Object.hasOwn(value, "$ref") &&
352
+ (Object.keys(value).length !== 1 ||
353
+ typeof value.$ref !== "string" ||
354
+ !value.$ref.startsWith("@")))
355
+ throw new PlayDocumentToolFailure("$ref must use an @short-ref returned by the Runtime, and a reference object cannot contain other fields");
356
+ for (const item of Object.values(value))
357
+ assertToolReferenceHandles(item, seen);
358
+ seen.delete(value);
359
+ }
360
+ function applyPlayCandidateCreate(candidate, authorizations, args) {
361
+ try {
362
+ const parentDirectory = record(args) && typeof args.parent === "string"
363
+ ? parseStateDirectoryHandle(args.parent)
364
+ : null;
365
+ if (!record(args) ||
366
+ typeof args.parent !== "string" ||
367
+ parentDirectory === null ||
368
+ (args.codec !== "yaml" && args.codec !== "markdown") ||
369
+ typeof args.refHint !== "string" ||
370
+ !/^[a-z][a-z0-9-]{1,31}$/u.test(args.refHint) ||
371
+ typeof args.title !== "string" ||
372
+ typeof args.summary !== "string" ||
373
+ !Array.isArray(args.aliases) ||
374
+ !args.aliases.every((alias) => typeof alias === "string") ||
375
+ typeof args.body !== "string")
376
+ throw new PlayDocumentToolFailure("world_create arguments are invalid");
377
+ const suppliedBytes = Buffer.byteLength(args.body, "utf8");
378
+ if (suppliedBytes > 64 * 1024)
379
+ throw new PlayDocumentToolFailure("The world_create body exceeds 64 KiB");
380
+ if (candidate.suppliedBytes + suppliedBytes > 256 * 1024)
381
+ throw new PlayDocumentToolFailure("New body content for this model operation exceeds 256 KiB in total");
382
+ const path = nextAvailableStatePath(candidate, parentDirectory, args.refHint, args.codec);
383
+ const command = args.codec === "yaml"
384
+ ? {
385
+ kind: "create",
386
+ temporaryName: "created",
387
+ logicalPath: path,
388
+ codec: "yaml",
389
+ refHint: args.refHint,
390
+ title: args.title,
391
+ summary: args.summary,
392
+ aliases: args.aliases,
393
+ body: args.body,
394
+ }
395
+ : {
396
+ kind: "create",
397
+ temporaryName: "created",
398
+ logicalPath: path,
399
+ codec: "markdown",
400
+ refHint: args.refHint,
401
+ title: args.title,
402
+ summary: args.summary,
403
+ aliases: args.aliases,
404
+ body: args.body,
405
+ };
406
+ if (command.codec === "yaml")
407
+ assertToolReferenceHandles(command.body);
408
+ const revised = candidate.snapshot.revise({
409
+ commands: [command],
410
+ });
411
+ if (!revised.ok || revised.snapshotStatus !== "usable")
412
+ throw new PlayDocumentToolFailure(worldDocumentRevisionFailureMessage(revised.diagnostics), "candidate");
413
+ const created = revised.changes.find(({ before }) => before === null);
414
+ if (created === undefined)
415
+ throw new Error("The world_create revision did not return a newly created document change");
416
+ const previousSnapshot = candidate.snapshot;
417
+ acceptWorldStateRevision(candidate, revised);
418
+ carryWriteAuthorizations(previousSnapshot, candidate, authorizations, revised.changes);
419
+ authorizeRead(authorizations, candidate.snapshot.id, created.shortRef, null);
420
+ candidate.suppliedBytes += suppliedBytes;
421
+ return {
422
+ ok: true,
423
+ markdown: `@${created.shortRef} pending write`,
424
+ candidateWrite: { shortRef: created.shortRef, changed: true },
425
+ };
426
+ }
427
+ catch (error) {
428
+ if (error instanceof PlayDocumentToolFailure)
429
+ return toolFailure(error.message, error.failureKind);
430
+ throw error;
431
+ }
432
+ }
433
+ /**
434
+ * Move read authorizations onto the revised snapshot. Only the documents this
435
+ * revision actually rewrote go stale — everything else is byte-identical in the
436
+ * new snapshot, so a model that read several documents and then patches one of
437
+ * them keeps the right to write the others in the same model response.
438
+ */
439
+ function carryWriteAuthorizations(previousSnapshot, candidate, authorizations, changes) {
440
+ const stale = new Set();
441
+ for (const change of changes) {
442
+ stale.add(change.shortRef);
443
+ const previous = documentById(previousSnapshot, change.documentId);
444
+ if (previous !== null)
445
+ stale.add(previous.shortRef);
446
+ }
447
+ authorizations.snapshotId = candidate.snapshot.id;
448
+ for (const ref of stale)
449
+ authorizations.documents.delete(ref);
450
+ for (const [readKey, progress] of authorizations.pendingReads)
451
+ if (stale.has(progress.shortRef))
452
+ authorizations.pendingReads.delete(readKey);
453
+ }
454
+ function playCandidateStateChanges(candidate) {
455
+ return fileNativeStateChanges(candidate);
456
+ }
457
+ function documentById(snapshot, id) {
458
+ const result = snapshot.query({
459
+ kind: "read_document",
460
+ document: { documentId: id },
461
+ maxBytes: 4,
462
+ });
463
+ return result.kind === "read_document" && result.ok ? result.document : null;
464
+ }
465
+ function toolFailure(message, failureKind = "protocol") {
466
+ return {
467
+ ok: false,
468
+ markdown: `# Runtime tool rejected\n\n${message}`,
469
+ failureKind,
470
+ };
471
+ }
472
+ function previewContextToolResult(snapshot, history, call) {
473
+ if (call.name === "context_list")
474
+ return executeContextList(snapshot, history, call.arguments);
475
+ if (call.name === "context_search")
476
+ return executeContextSearch(snapshot, history, call.arguments);
477
+ if (call.name === "context_read")
478
+ return executeContextRead(snapshot, history, call.arguments);
479
+ return null;
480
+ }
481
+ function contextToolRetryMaximum(call) {
482
+ if (!record(call.arguments))
483
+ return call.name === "context_read" ? 8_192 : 1;
484
+ if (call.name === "context_read")
485
+ return Number.isInteger(call.arguments.maxBytes) &&
486
+ Number(call.arguments.maxBytes) >= 4 &&
487
+ Number(call.arguments.maxBytes) <= 8_192
488
+ ? Number(call.arguments.maxBytes)
489
+ : 8_192;
490
+ const defaultLimit = call.name === "context_search" ? 10 : 20;
491
+ const maximumLimit = call.name === "context_search" ? 50 : 100;
492
+ return Number.isInteger(call.arguments.limit) &&
493
+ Number(call.arguments.limit) >= 1 &&
494
+ Number(call.arguments.limit) <= maximumLimit
495
+ ? Number(call.arguments.limit)
496
+ : defaultLimit;
497
+ }
498
+ function executeContextSearch(snapshot, history, args) {
499
+ if (!record(args) ||
500
+ !hasOnlyToolKeys(args, [
501
+ "source",
502
+ "query",
503
+ "caseSensitive",
504
+ "within",
505
+ "limit",
506
+ "cursor",
507
+ ]) ||
508
+ (args.source !== "state" && args.source !== "history") ||
509
+ typeof args.query !== "string" ||
510
+ args.query.length < 1 ||
511
+ args.query.length > 256 ||
512
+ (args.caseSensitive !== undefined &&
513
+ typeof args.caseSensitive !== "boolean") ||
514
+ (args.within !== undefined && typeof args.within !== "string") ||
515
+ (args.limit !== undefined && typeof args.limit !== "number") ||
516
+ !validOptionalCursor(args.cursor))
517
+ return {
518
+ ok: false,
519
+ markdown: "# Runtime argument error\n\ncontext_search accepts only a literal query and strict source, within, caseSensitive, limit, and cursor arguments; semantic filters are not accepted.",
520
+ };
521
+ const caseSensitive = args.caseSensitive === true;
522
+ const query = args.query;
523
+ const limit = typeof args.limit === "number" ? args.limit : 10;
524
+ if (!Number.isInteger(limit) || limit < 1 || limit > 50)
525
+ return {
526
+ ok: false,
527
+ markdown: "# Runtime argument error\n\nlimit must be between 1 and 50.",
528
+ };
529
+ if (args.source === "state")
530
+ return searchState(snapshot, {
531
+ query,
532
+ caseSensitive,
533
+ ...(args.within === undefined ? {} : { within: args.within }),
534
+ limit,
535
+ ...(typeof args.cursor === "string" || args.cursor === null
536
+ ? { cursor: args.cursor }
537
+ : {}),
538
+ });
539
+ const source = history.map(({ path, contents }) => [
540
+ historyRef(path),
541
+ contents,
542
+ [`history-commit-${path.split("-")[0] ?? ""}`],
543
+ ]);
544
+ const normalizedQuery = normalizeSearch(query, caseSensitive);
545
+ const within = typeof args.within === "string" ? args.within.replace(/^@/u, "") : null;
546
+ const allHits = source.filter(([ref, text, parentScopes]) => (within === null || ref === within || parentScopes.includes(within)) &&
547
+ normalizeSearch(text, caseSensitive).includes(normalizedQuery));
548
+ const scope = JSON.stringify({
549
+ kind: "search",
550
+ source: "history",
551
+ query,
552
+ caseSensitive,
553
+ within: args.within ?? null,
554
+ limit,
555
+ });
556
+ const offset = parseCursor(args.cursor, scope);
557
+ if (offset === null)
558
+ return {
559
+ ok: false,
560
+ markdown: "# Runtime argument error\n\nThe cursor does not match the search criteria or endpoint.",
561
+ };
562
+ const hits = allHits.slice(offset, offset + limit);
563
+ const complete = offset + hits.length >= allHits.length;
564
+ const renderedHits = hits
565
+ .map(([ref, text]) => `- @${ref}\n Exact-match excerpt:\n${quoteMarkdown(snippet(text, query), " ")}`)
566
+ .join("\n");
567
+ return {
568
+ ok: true,
569
+ markdown: `# Literal search\n\nScope: history${args.within === undefined ? "" : ` · ${args.within}`}\nNormalization: ${caseSensitive ? "original text" : "NFKC + case folding"}\nTotal matches: ${allHits.length}\n${renderedHits || "Zero literal matches do not prove that the fact is absent from the world."}\n\n---\nThis page: ${offset}..${offset + hits.length} / ${allHits.length} matches\nComplete: ${complete ? "yes" : "no"}${complete ? "" : `\nNext-page cursor: ${cursorFor(scope, offset + hits.length)}`}`,
570
+ };
571
+ }
572
+ function searchState(snapshot, input) {
573
+ const within = parseStateSearchScope(input.within);
574
+ if (within === invalidStateHandle)
575
+ return {
576
+ ok: false,
577
+ markdown: "# Runtime argument error\n\nA state search scope must be an @dir-* or document @short-ref returned by the Runtime.",
578
+ };
579
+ const result = snapshot.query({
580
+ kind: "literal_search",
581
+ query: input.query,
582
+ caseSensitive: input.caseSensitive,
583
+ ...(within === undefined ? {} : { within }),
584
+ limit: input.limit,
585
+ ...(input.cursor === undefined ? {} : { cursor: input.cursor }),
586
+ });
587
+ if (result.kind === "error")
588
+ return renderStateQueryFailure(result);
589
+ if (result.kind !== "literal_search")
590
+ return unexpectedStateQueryResult();
591
+ const renderedHits = result.matches
592
+ .map(({ document, referenceProjection, range }) => `- @${document.shortRef} · ${document.title} · line ${range.start.line}, column ${range.start.column}\n Exact match: ${JSON.stringify(referenceProjection.text)}\n Exact-match excerpt (mechanical references appear as @short-refs):\n${quoteMarkdown(referenceProjection.excerpt, " ")}`)
593
+ .join("\n");
594
+ const scope = input.within === undefined ? "state" : `state · ${input.within}`;
595
+ return {
596
+ ok: true,
597
+ markdown: `# Literal search\n\nScope: ${scope}\nNormalization: ${input.caseSensitive ? "original text" : "NFKC + case folding"}\nCoverage: ${result.coverage.status === "complete" ? "complete" : `partial (${result.coverage.excludedDocuments} damaged documents excluded)`}\nTotal matches: ${result.page.total}\n${renderedHits || "Zero literal matches do not prove that the fact is absent from the world."}\n\n---\nThis page: ${result.page.start}..${result.page.end} / ${result.page.total} matches\nComplete: ${result.page.complete ? "yes" : "no"}${result.page.nextCursor === null ? "" : `\nNext-page cursor: ${result.page.nextCursor}`}`,
598
+ };
599
+ }
600
+ const invalidStateHandle = Symbol("invalid-state-handle");
601
+ function parseStateSearchScope(handle) {
602
+ if (handle === undefined)
603
+ return undefined;
604
+ const directory = parseStateDirectoryHandle(handle);
605
+ if (directory !== null)
606
+ return { directory };
607
+ const document = /^@([a-z][a-z0-9-]*)$/u.exec(handle);
608
+ return document === null
609
+ ? invalidStateHandle
610
+ : { document: { shortRef: document[1] } };
611
+ }
612
+ function stateDirectoryHandle(directory) {
613
+ if (directory === "")
614
+ return "@dir-/";
615
+ const encoded = directory.split("/").map(encodeURIComponent).join("/");
616
+ return `@dir-/${encoded}`;
617
+ }
618
+ function parseStateDirectoryHandle(handle) {
619
+ if (handle === "@dir-/")
620
+ return "";
621
+ if (!handle.startsWith("@dir-/") || handle.length === "@dir-/".length)
622
+ return null;
623
+ try {
624
+ const directory = handle
625
+ .slice("@dir-/".length)
626
+ .split("/")
627
+ .map(decodeURIComponent)
628
+ .join("/");
629
+ if (directory
630
+ .split("/")
631
+ .some((segment) => segment === "" ||
632
+ segment === "." ||
633
+ segment === ".." ||
634
+ segment.includes("\\")) ||
635
+ stateDirectoryHandle(directory) !== handle)
636
+ return null;
637
+ return directory;
638
+ }
639
+ catch {
640
+ return null;
641
+ }
642
+ }
643
+ function executeContextList(snapshot, history, args) {
644
+ if (!record(args) || (args.source !== "state" && args.source !== "history"))
645
+ return {
646
+ ok: false,
647
+ markdown: '# Runtime argument error\n\nUse {source:"state", parent:"@dir-/"} for state directories and {source:"history", order:"newest_first"} for history.',
648
+ };
649
+ const allowedKeys = ["source", "parent", "order", "cursor", "limit"];
650
+ const invalidStateShape = args.source === "state" &&
651
+ (typeof args.parent !== "string" ||
652
+ parseStateDirectoryHandle(args.parent) === null ||
653
+ args.order !== undefined);
654
+ const invalidHistoryShape = args.source === "history" &&
655
+ (args.parent !== undefined ||
656
+ (args.order !== "newest_first" && args.order !== "oldest_first"));
657
+ if (!hasOnlyToolKeys(args, allowedKeys) ||
658
+ invalidStateShape ||
659
+ invalidHistoryShape ||
660
+ (args.limit !== undefined && typeof args.limit !== "number") ||
661
+ !validOptionalCursor(args.cursor))
662
+ return {
663
+ ok: false,
664
+ markdown: "# Runtime argument error\n\nstate must use an @dir-* parent returned by the Runtime and omit order; history must use newest_first or oldest_first order and omit parent.",
665
+ };
666
+ const limit = typeof args.limit === "number" ? args.limit : 20;
667
+ if (!Number.isInteger(limit) || limit < 1 || limit > 100)
668
+ return {
669
+ ok: false,
670
+ markdown: "# Runtime argument error\n\nlimit must be between 1 and 100.",
671
+ };
672
+ if (args.source === "state") {
673
+ const parent = args.parent;
674
+ const directory = parseStateDirectoryHandle(parent);
675
+ if (directory === null)
676
+ return unexpectedStateQueryResult();
677
+ const result = snapshot.query({
678
+ kind: "catalog",
679
+ directory,
680
+ limit,
681
+ ...(typeof args.cursor === "string" || args.cursor === null
682
+ ? { cursor: args.cursor }
683
+ : {}),
684
+ });
685
+ if (result.kind === "error")
686
+ return renderStateQueryFailure(result);
687
+ if (result.kind !== "catalog")
688
+ return unexpectedStateQueryResult();
689
+ const entries = result.entries.map((entry) => {
690
+ if (entry.kind === "directory") {
691
+ const relative = entry.logicalPath.slice(`${snapshot.logicalRoot}/`.length);
692
+ return `- Directory ${stateDirectoryHandle(relative)}`;
693
+ }
694
+ if (entry.document === undefined)
695
+ return "- Damaged document (currently not addressable)";
696
+ return `- Document @${entry.document.shortRef} · ${entry.document.title} · ${entry.document.codec.toUpperCase()} · ${entry.document.summary}${entry.status === "damaged" ? " · needs repair" : ""}`;
697
+ });
698
+ return {
699
+ ok: true,
700
+ markdown: `# Directory listing\n\nScope: state · ${parent}\nCoverage: ${result.coverage.status === "complete" ? "complete" : "partial"}\n${entries.join("\n") || "(empty)"}\n\n---\nThis page: ${result.page.start}..${result.page.end} / ${result.page.total} items\nComplete: ${result.page.complete ? "yes" : "no"}${result.page.nextCursor === null ? "" : `\nNext-page cursor: ${result.page.nextCursor}`}`,
701
+ };
702
+ }
703
+ const entries = [...history]
704
+ .sort((a, b) => (args.order ?? "newest_first") === "oldest_first"
705
+ ? a.path.localeCompare(b.path)
706
+ : b.path.localeCompare(a.path))
707
+ .map(({ path, contents }) => `- @${historyRef(path)}, ${Buffer.byteLength(contents, "utf8")} bytes`);
708
+ const scope = JSON.stringify({
709
+ kind: "list",
710
+ source: "history",
711
+ order: args.order,
712
+ limit,
713
+ });
714
+ const offset = parseCursor(args.cursor, scope);
715
+ if (offset === null)
716
+ return {
717
+ ok: false,
718
+ markdown: "# Runtime argument error\n\nThe cursor does not match the listing criteria or endpoint.",
719
+ };
720
+ const page = entries.slice(offset, offset + limit);
721
+ const complete = offset + page.length >= entries.length;
722
+ return {
723
+ ok: true,
724
+ markdown: `# Directory listing\n\nScope: history · ${String(args.order)}\n${page.join("\n") || "(empty)"}\n\n---\nThis page: ${offset}..${offset + page.length} / ${entries.length} items\nComplete: ${complete ? "yes" : "no"}${complete ? "" : `\nNext-page cursor: ${cursorFor(scope, offset + page.length)}`}`,
725
+ };
726
+ }
727
+ function executeContextRead(snapshot, history, args) {
728
+ if (!record(args) ||
729
+ !hasOnlyToolKeys(args, ["ref", "cursor", "maxBytes"]) ||
730
+ typeof args.ref !== "string" ||
731
+ (args.maxBytes !== undefined && typeof args.maxBytes !== "number") ||
732
+ !validOptionalCursor(args.cursor))
733
+ return {
734
+ ok: false,
735
+ markdown: "# Runtime argument error\n\nA stable ref returned by list or search is required.",
736
+ };
737
+ const maxBytes = typeof args.maxBytes === "number" ? args.maxBytes : 8192;
738
+ if (!Number.isInteger(maxBytes) || maxBytes < 4 || maxBytes > 8192)
739
+ return {
740
+ ok: false,
741
+ markdown: "# Runtime argument error\n\nmaxBytes must be between 4 and 8192.",
742
+ };
743
+ const historyRefValue = args.ref.replace(/^@/u, "");
744
+ const historyEntry = history.find(({ path }) => historyRef(path) === historyRefValue);
745
+ if (historyEntry !== undefined)
746
+ return pagedRead(historyRefValue, historyEntry.contents, maxBytes, args.cursor);
747
+ const handle = parseStateReadHandle(args.ref);
748
+ if (handle === null)
749
+ return {
750
+ ok: false,
751
+ markdown: "# Runtime argument error\n\nAn exact state read requires a document @short-ref or one of its #/logical nodes.",
752
+ };
753
+ const resolved = snapshot.query({
754
+ kind: "read_document",
755
+ document: { shortRef: handle.shortRef },
756
+ maxBytes: 4,
757
+ });
758
+ if (resolved.kind === "error")
759
+ return renderStateQueryFailure(resolved);
760
+ if (resolved.kind !== "read_document")
761
+ return unexpectedStateQueryResult();
762
+ if (handle.path === null)
763
+ return resolved.codec === "markdown"
764
+ ? readMarkdownDocument(snapshot, resolved.document, maxBytes, args.cursor)
765
+ : readProjectedNode(snapshot, resolved.document, { yaml: [] }, args.ref, maxBytes, args.cursor, true);
766
+ const locator = resolved.codec === "yaml"
767
+ ? { yaml: handle.path.map(parseYamlHandleSegment) }
768
+ : { markdown: handle.path };
769
+ return readProjectedNode(snapshot, resolved.document, locator, args.ref, maxBytes, args.cursor, false);
770
+ }
771
+ function readMarkdownDocument(snapshot, document, maxBytes, cursor) {
772
+ const result = snapshot.query({
773
+ kind: "read_document",
774
+ document: { shortRef: document.shortRef },
775
+ maxBytes,
776
+ ...(typeof cursor === "string" || cursor === null ? { cursor } : {}),
777
+ });
778
+ if (result.kind === "error")
779
+ return renderStateQueryFailure(result);
780
+ if (result.kind !== "read_document")
781
+ return unexpectedStateQueryResult();
782
+ return {
783
+ ok: true,
784
+ markdown: `# Exact read @${document.shortRef}\n\n${renderDocumentMetadata(document)}\n[Writable body starts; locators are relative to this point]\n${result.body.trimEnd()}\n[Writable body ${result.page.complete ? "ends" : "continues"}]\n\n---\nScope: state · @${document.shortRef}\nThis page: ${result.page.start}..${result.page.end} / ${result.page.total} bytes\nComplete: ${result.page.complete ? "yes" : "no"}${result.page.nextCursor === null ? "" : `\nNext-page cursor: ${result.page.nextCursor}`}`,
785
+ readAuthorization: {
786
+ snapshotId: snapshot.id,
787
+ readKey: readAuthorizationKey(document.shortRef, null, maxBytes),
788
+ shortRef: document.shortRef,
789
+ locator: null,
790
+ page: {
791
+ start: result.page.start,
792
+ end: result.page.end,
793
+ total: result.page.total,
794
+ },
795
+ },
796
+ };
797
+ }
798
+ function readProjectedNode(snapshot, document, locator, requestedHandle, maxBytes, cursor, wholeDocument) {
799
+ const result = snapshot.query({
800
+ kind: "select_node",
801
+ document: { shortRef: document.shortRef },
802
+ locator,
803
+ });
804
+ if (result.kind === "error")
805
+ return renderStateQueryFailure(result);
806
+ if (result.kind !== "select_node")
807
+ return unexpectedStateQueryResult();
808
+ const body = result.node.codec === "yaml"
809
+ ? stringify(toolWorldDocumentValue(result.node.value), {
810
+ indent: 2,
811
+ lineWidth: 0,
812
+ }).trimEnd()
813
+ : result.node.markdown.trimEnd();
814
+ const text = wholeDocument
815
+ ? `${renderDocumentMetadata(document)}\n[Writable body starts; locators are relative to this point]\n${body}\n[Writable body ends]\n`
816
+ : `# ${document.title} · ${renderLocator(result.node.locator)} [${requestedHandle}]\n\n${body}\n`;
817
+ return pagedStateProjection(snapshot, requestedHandle, text, maxBytes, cursor, document.shortRef, wholeDocument ? null : result.node.locator);
818
+ }
819
+ function renderDocumentMetadata(document) {
820
+ const metadata = stringify({
821
+ title: document.title,
822
+ summary: document.summary,
823
+ aliases: document.aliases,
824
+ }, { indent: 2, lineWidth: 0 }).trimEnd();
825
+ return `[Document metadata: not body content; use set_metadata to replace the entire group]\n${metadata}`;
826
+ }
827
+ function toolWorldDocumentValue(value) {
828
+ if (Array.isArray(value))
829
+ return value.map(toolWorldDocumentValue);
830
+ if (!record(value))
831
+ return value;
832
+ if (typeof value.$ref === "string" &&
833
+ record(value.target) &&
834
+ typeof value.target.shortRef === "string")
835
+ return { $ref: `@${value.target.shortRef}` };
836
+ return Object.fromEntries(Object.entries(value).map(([key, child]) => [
837
+ key,
838
+ toolWorldDocumentValue(child),
839
+ ]));
840
+ }
841
+ function pagedStateProjection(snapshot, handle, text, maxBytes, cursor, shortRef, locator) {
842
+ const scope = JSON.stringify({
843
+ kind: "state_projected_read",
844
+ snapshotId: snapshot.id,
845
+ handle,
846
+ maxBytes,
847
+ });
848
+ const offset = parseStateProjectionCursor(snapshot, cursor, scope);
849
+ const bytes = Buffer.from(text, "utf8");
850
+ if (offset === null || offset > bytes.length)
851
+ return {
852
+ ok: false,
853
+ markdown: "# Runtime argument error\n\nThe cursor does not match the current snapshot or complete query criteria.",
854
+ };
855
+ const end = safeUtf8ReadEnd(bytes, offset, maxBytes);
856
+ const complete = end === bytes.length;
857
+ const page = bytes.subarray(offset, end).toString("utf8");
858
+ return {
859
+ ok: true,
860
+ markdown: `# Exact read ${handle}\n\n${page}\n\n---\nScope: state · ${handle}\nThis page: ${offset}..${end} / ${bytes.length} bytes\nComplete: ${complete ? "yes" : "no"}${complete ? "" : `\nNext-page cursor: ${stateProjectionCursorFor(snapshot, scope, end)}`}`,
861
+ readAuthorization: {
862
+ snapshotId: snapshot.id,
863
+ readKey: readAuthorizationKey(shortRef, locator, maxBytes),
864
+ shortRef,
865
+ locator,
866
+ page: { start: offset, end, total: bytes.length },
867
+ },
868
+ };
869
+ }
870
+ function readAuthorizationKey(shortRef, locator, maxBytes) {
871
+ return JSON.stringify({ shortRef, locator, maxBytes });
872
+ }
873
+ const stateProjectionCursorSecrets = new WeakMap();
874
+ function stateProjectionCursorFor(snapshot, scope, offset) {
875
+ const payload = Buffer.from(JSON.stringify({ offset }), "utf8").toString("base64url");
876
+ const signature = createHmac("sha256", stateProjectionCursorSecret(snapshot))
877
+ .update(snapshot.id)
878
+ .update("\0")
879
+ .update(scope)
880
+ .update("\0")
881
+ .update(payload)
882
+ .digest("base64url");
883
+ return `${payload}.${signature}`;
884
+ }
885
+ function parseStateProjectionCursor(snapshot, cursor, scope) {
886
+ if (cursor === undefined || cursor === null)
887
+ return 0;
888
+ if (typeof cursor !== "string")
889
+ return null;
890
+ const parts = cursor.split(".");
891
+ if (parts.length !== 2)
892
+ return null;
893
+ const [payload, suppliedSignature] = parts;
894
+ if (payload === undefined || suppliedSignature === undefined)
895
+ return null;
896
+ try {
897
+ const supplied = Buffer.from(suppliedSignature, "base64url");
898
+ if (supplied.toString("base64url") !== suppliedSignature)
899
+ return null;
900
+ const expected = createHmac("sha256", stateProjectionCursorSecret(snapshot))
901
+ .update(snapshot.id)
902
+ .update("\0")
903
+ .update(scope)
904
+ .update("\0")
905
+ .update(payload)
906
+ .digest();
907
+ if (supplied.length !== expected.length ||
908
+ !timingSafeEqual(supplied, expected))
909
+ return null;
910
+ const decoded = Buffer.from(payload, "base64url");
911
+ if (decoded.toString("base64url") !== payload)
912
+ return null;
913
+ const value = JSON.parse(decoded.toString("utf8"));
914
+ return record(value) &&
915
+ Object.keys(value).length === 1 &&
916
+ Number.isSafeInteger(value.offset) &&
917
+ Number(value.offset) >= 0
918
+ ? Number(value.offset)
919
+ : null;
920
+ }
921
+ catch {
922
+ return null;
923
+ }
924
+ }
925
+ function stateProjectionCursorSecret(snapshot) {
926
+ const existing = stateProjectionCursorSecrets.get(snapshot);
927
+ if (existing !== undefined)
928
+ return existing;
929
+ const created = randomBytes(32);
930
+ stateProjectionCursorSecrets.set(snapshot, created);
931
+ return created;
932
+ }
933
+ function parseStateReadHandle(value) {
934
+ const match = /^@([a-z][a-z0-9-]*)(?:#\/(.+))?$/u.exec(value);
935
+ if (match === null)
936
+ return null;
937
+ try {
938
+ return {
939
+ shortRef: match[1],
940
+ path: match[2] === undefined
941
+ ? null
942
+ : match[2].split("/").map(decodeURIComponent),
943
+ };
944
+ }
945
+ catch {
946
+ return null;
947
+ }
948
+ }
949
+ function renderLocator(locator) {
950
+ return "yaml" in locator
951
+ ? `yaml:${locator.yaml.join("/")}`
952
+ : `markdown:${locator.markdown.join("/")}`;
953
+ }
954
+ function renderStateQueryFailure(result) {
955
+ return {
956
+ ok: false,
957
+ markdown: `# Runtime argument error\n\n${result.diagnostics.map(({ code, message }) => `${code}: ${message}`).join("\n")}`,
958
+ };
959
+ }
960
+ function unexpectedStateQueryResult() {
961
+ return {
962
+ ok: false,
963
+ markdown: "# Runtime tool rejected\n\nThe world-document query returned an incompatible result type.",
964
+ };
965
+ }
966
+ function quoteMarkdown(value, indent = "") {
967
+ return value
968
+ .split("\n")
969
+ .map((line) => `${indent}> ${line}`)
970
+ .join("\n");
971
+ }
972
+ function hasOnlyToolKeys(value, allowed) {
973
+ const keys = new Set(allowed);
974
+ return Object.keys(value).every((key) => keys.has(key));
975
+ }
976
+ function validOptionalCursor(value) {
977
+ return value === undefined || value === null || typeof value === "string";
978
+ }
979
+ function normalizeSearch(value, caseSensitive) {
980
+ const normalized = value.normalize("NFKC");
981
+ return caseSensitive ? normalized : normalized.toLocaleLowerCase("und");
982
+ }
983
+ function snippet(text, query) {
984
+ const position = text
985
+ .normalize("NFKC")
986
+ .toLocaleLowerCase("und")
987
+ .indexOf(query.normalize("NFKC").toLocaleLowerCase("und"));
988
+ const start = Math.max(0, position - 120);
989
+ return text.slice(start, start + 240).replace(/\s+/gu, " ");
990
+ }
991
+ function historyRef(path) {
992
+ return `history-message-${path.replace(/\.md$/u, "")}`;
993
+ }
994
+ function pagedRead(ref, text, maxBytes, cursor) {
995
+ const scope = JSON.stringify({
996
+ kind: "read",
997
+ ref,
998
+ sha256: createHash("sha256").update(text).digest("hex"),
999
+ maxBytes,
1000
+ });
1001
+ const offset = parseCursor(cursor, scope);
1002
+ if (offset === null)
1003
+ return {
1004
+ ok: false,
1005
+ markdown: "# Runtime argument error\n\nThe cursor does not match the current read.",
1006
+ };
1007
+ const bytes = Buffer.from(text, "utf8");
1008
+ const end = safeUtf8ReadEnd(bytes, offset, maxBytes);
1009
+ const page = bytes.subarray(offset, end).toString("utf8");
1010
+ return {
1011
+ ok: true,
1012
+ markdown: `# Exact read @${ref}\n\n${page}\n\n---\nSource: @${ref}\nThis page: ${offset}..${end} bytes\nComplete: ${end === bytes.length ? "yes" : "no"}\n${end === bytes.length ? "" : `Next-page cursor: ${cursorFor(scope, end)}`}`,
1013
+ };
1014
+ }
1015
+ function safeUtf8ReadEnd(bytes, offset, maxBytes) {
1016
+ let end = Math.min(bytes.length, offset + maxBytes);
1017
+ while (end > offset && (bytes[end] ?? 0) >= 0x80 && (bytes[end] ?? 0) < 0xc0)
1018
+ end -= 1;
1019
+ return end;
1020
+ }
1021
+ function cursorFor(scope, offset) {
1022
+ return Buffer.from(JSON.stringify({
1023
+ scope: createHash("sha256").update(scope).digest("hex"),
1024
+ offset,
1025
+ }), "utf8").toString("base64url");
1026
+ }
1027
+ function parseCursor(cursor, scope) {
1028
+ if (cursor === undefined || cursor === null)
1029
+ return 0;
1030
+ if (typeof cursor !== "string")
1031
+ return null;
1032
+ try {
1033
+ const value = JSON.parse(Buffer.from(cursor, "base64url").toString("utf8"));
1034
+ if (!record(value) ||
1035
+ value.scope !== createHash("sha256").update(scope).digest("hex") ||
1036
+ !Number.isSafeInteger(value.offset) ||
1037
+ Number(value.offset) < 0)
1038
+ return null;
1039
+ return Number(value.offset);
1040
+ }
1041
+ catch {
1042
+ return null;
1043
+ }
1044
+ }
1045
+ export function parseNarrative(args) {
1046
+ if (!record(args) ||
1047
+ typeof args.text !== "string" ||
1048
+ args.text.length < 1 ||
1049
+ args.text.length > 24_000)
1050
+ throw new PromptCompilationError("narrative_invalid", "Narrative text must contain 1 to 24,000 Unicode characters");
1051
+ return args.text;
1052
+ }
1053
+ export function fingerprintControl(files) {
1054
+ const hash = createHash("sha256");
1055
+ for (const [path, contents] of Object.entries(files)
1056
+ .filter(([path]) => path.startsWith("control/"))
1057
+ .sort(([a], [b]) => a.localeCompare(b)))
1058
+ hash.update(path).update("\0").update(contents).update("\0");
1059
+ return `sha256:${hash.digest("hex")}`;
1060
+ }
1061
+ export function isPlayDocumentAuthorizationCheckpoint(value) {
1062
+ if (!record(value) ||
1063
+ !hasExactKeys(value, [
1064
+ "schemaVersion",
1065
+ "kind",
1066
+ "stateFingerprint",
1067
+ "documents",
1068
+ ]) ||
1069
+ value.schemaVersion !== 1 ||
1070
+ value.kind !== "play_document_authorizations" ||
1071
+ typeof value.stateFingerprint !== "string" ||
1072
+ !/^sha256:[0-9a-f]{64}$/u.test(value.stateFingerprint) ||
1073
+ !Array.isArray(value.documents) ||
1074
+ value.documents.length > 100_000)
1075
+ return false;
1076
+ const refs = new Set();
1077
+ for (const document of value.documents) {
1078
+ if (!record(document) ||
1079
+ !hasExactKeys(document, ["shortRef", "locators"]) ||
1080
+ typeof document.shortRef !== "string" ||
1081
+ !/^[a-z][a-z0-9-]*$/u.test(document.shortRef) ||
1082
+ refs.has(document.shortRef) ||
1083
+ (document.locators !== null &&
1084
+ (!Array.isArray(document.locators) ||
1085
+ document.locators.length > 100_000 ||
1086
+ !document.locators.every(isPersistedAuthorizedLocator))))
1087
+ return false;
1088
+ refs.add(document.shortRef);
1089
+ }
1090
+ return true;
1091
+ }
1092
+ function fingerprintStateFiles(files) {
1093
+ const hash = createHash("sha256");
1094
+ for (const [path, contents] of Object.entries(files)
1095
+ .filter(([path]) => path.startsWith("state/"))
1096
+ .sort(([left], [right]) => left.localeCompare(right)))
1097
+ hash.update(path).update("\0").update(contents).update("\0");
1098
+ return `sha256:${hash.digest("hex")}`;
1099
+ }
1100
+ function isPersistedAuthorizedLocator(value) {
1101
+ if (!record(value))
1102
+ return false;
1103
+ if (hasExactKeys(value, ["yaml"]))
1104
+ return (Array.isArray(value.yaml) &&
1105
+ value.yaml.length <= 64 &&
1106
+ value.yaml.every((segment) => typeof segment === "string" ||
1107
+ (Number.isSafeInteger(segment) && Number(segment) >= 0)));
1108
+ if (hasExactKeys(value, ["markdown"]))
1109
+ return (Array.isArray(value.markdown) &&
1110
+ value.markdown.length <= 64 &&
1111
+ value.markdown.every((segment) => typeof segment === "string"));
1112
+ return false;
1113
+ }
1114
+ function hasExactKeys(value, expected) {
1115
+ const keys = Object.keys(value);
1116
+ return (keys.length === expected.length &&
1117
+ expected.every((key) => Object.hasOwn(value, key)));
1118
+ }
1119
+ function record(value) {
1120
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1121
+ }