@ny4n/durable 0.0.0-stage → 1.0.1

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 (303) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +711 -2
  3. package/dist/documents.d.ts +66 -0
  4. package/dist/documents.d.ts.map +1 -0
  5. package/dist/documents.js +98 -0
  6. package/dist/documents.js.map +1 -0
  7. package/dist/entries.d.ts +31 -0
  8. package/dist/entries.d.ts.map +1 -0
  9. package/dist/entries.js +31 -0
  10. package/dist/entries.js.map +1 -0
  11. package/dist/env/decode.d.ts +16 -0
  12. package/dist/env/decode.d.ts.map +1 -0
  13. package/dist/env/decode.js +28 -0
  14. package/dist/env/decode.js.map +1 -0
  15. package/dist/env/index.d.ts +263 -0
  16. package/dist/env/index.d.ts.map +1 -0
  17. package/dist/env/index.js +49 -0
  18. package/dist/env/index.js.map +1 -0
  19. package/dist/env/line-scan.d.ts +13 -0
  20. package/dist/env/line-scan.d.ts.map +1 -0
  21. package/dist/env/line-scan.js +147 -0
  22. package/dist/env/line-scan.js.map +1 -0
  23. package/dist/env/node-watch.d.ts +29 -0
  24. package/dist/env/node-watch.d.ts.map +1 -0
  25. package/dist/env/node-watch.js +485 -0
  26. package/dist/env/node-watch.js.map +1 -0
  27. package/dist/env/node.d.ts +56 -0
  28. package/dist/env/node.d.ts.map +1 -0
  29. package/dist/env/node.js +1166 -0
  30. package/dist/env/node.js.map +1 -0
  31. package/dist/errors.d.ts +32 -0
  32. package/dist/errors.d.ts.map +1 -0
  33. package/dist/errors.js +53 -0
  34. package/dist/errors.js.map +1 -0
  35. package/dist/harness/agent.d.ts +33 -0
  36. package/dist/harness/agent.d.ts.map +1 -0
  37. package/dist/harness/agent.js +235 -0
  38. package/dist/harness/agent.js.map +1 -0
  39. package/dist/harness/assistant.d.ts +15 -0
  40. package/dist/harness/assistant.d.ts.map +1 -0
  41. package/dist/harness/assistant.js +23 -0
  42. package/dist/harness/assistant.js.map +1 -0
  43. package/dist/harness/compaction.d.ts +56 -0
  44. package/dist/harness/compaction.d.ts.map +1 -0
  45. package/dist/harness/compaction.js +384 -0
  46. package/dist/harness/compaction.js.map +1 -0
  47. package/dist/harness/context.d.ts +63 -0
  48. package/dist/harness/context.d.ts.map +1 -0
  49. package/dist/harness/context.js +221 -0
  50. package/dist/harness/context.js.map +1 -0
  51. package/dist/harness/define.d.ts +18 -0
  52. package/dist/harness/define.d.ts.map +1 -0
  53. package/dist/harness/define.js +25 -0
  54. package/dist/harness/define.js.map +1 -0
  55. package/dist/harness/events.d.ts +180 -0
  56. package/dist/harness/events.d.ts.map +1 -0
  57. package/dist/harness/events.js +370 -0
  58. package/dist/harness/events.js.map +1 -0
  59. package/dist/harness/generation.d.ts +54 -0
  60. package/dist/harness/generation.d.ts.map +1 -0
  61. package/dist/harness/generation.js +514 -0
  62. package/dist/harness/generation.js.map +1 -0
  63. package/dist/harness/harness.d.ts +17 -0
  64. package/dist/harness/harness.d.ts.map +1 -0
  65. package/dist/harness/harness.js +332 -0
  66. package/dist/harness/harness.js.map +1 -0
  67. package/dist/harness/inbox.d.ts +59 -0
  68. package/dist/harness/inbox.d.ts.map +1 -0
  69. package/dist/harness/inbox.js +97 -0
  70. package/dist/harness/inbox.js.map +1 -0
  71. package/dist/harness/json.d.ts +10 -0
  72. package/dist/harness/json.d.ts.map +1 -0
  73. package/dist/harness/json.js +33 -0
  74. package/dist/harness/json.js.map +1 -0
  75. package/dist/harness/live.d.ts +142 -0
  76. package/dist/harness/live.d.ts.map +1 -0
  77. package/dist/harness/live.js +149 -0
  78. package/dist/harness/live.js.map +1 -0
  79. package/dist/harness/output.d.ts +69 -0
  80. package/dist/harness/output.d.ts.map +1 -0
  81. package/dist/harness/output.js +318 -0
  82. package/dist/harness/output.js.map +1 -0
  83. package/dist/harness/prompt.d.ts +27 -0
  84. package/dist/harness/prompt.d.ts.map +1 -0
  85. package/dist/harness/prompt.js +131 -0
  86. package/dist/harness/prompt.js.map +1 -0
  87. package/dist/harness/provider.d.ts +14 -0
  88. package/dist/harness/provider.d.ts.map +1 -0
  89. package/dist/harness/provider.js +30 -0
  90. package/dist/harness/provider.js.map +1 -0
  91. package/dist/harness/registry.d.ts +6 -0
  92. package/dist/harness/registry.d.ts.map +1 -0
  93. package/dist/harness/registry.js +101 -0
  94. package/dist/harness/registry.js.map +1 -0
  95. package/dist/harness/run.d.ts +17 -0
  96. package/dist/harness/run.d.ts.map +1 -0
  97. package/dist/harness/run.js +26 -0
  98. package/dist/harness/run.js.map +1 -0
  99. package/dist/harness/scheduler.d.ts +106 -0
  100. package/dist/harness/scheduler.d.ts.map +1 -0
  101. package/dist/harness/scheduler.js +1743 -0
  102. package/dist/harness/scheduler.js.map +1 -0
  103. package/dist/harness/submissions.d.ts +29 -0
  104. package/dist/harness/submissions.d.ts.map +1 -0
  105. package/dist/harness/submissions.js +175 -0
  106. package/dist/harness/submissions.js.map +1 -0
  107. package/dist/harness/task-graph.d.ts +49 -0
  108. package/dist/harness/task-graph.d.ts.map +1 -0
  109. package/dist/harness/task-graph.js +168 -0
  110. package/dist/harness/task-graph.js.map +1 -0
  111. package/dist/harness/tool.d.ts +76 -0
  112. package/dist/harness/tool.d.ts.map +1 -0
  113. package/dist/harness/tool.js +736 -0
  114. package/dist/harness/tool.js.map +1 -0
  115. package/dist/harness/types.d.ts +713 -0
  116. package/dist/harness/types.d.ts.map +1 -0
  117. package/dist/harness/types.js +2 -0
  118. package/dist/harness/types.js.map +1 -0
  119. package/dist/harness/usage.d.ts +18 -0
  120. package/dist/harness/usage.d.ts.map +1 -0
  121. package/dist/harness/usage.js +57 -0
  122. package/dist/harness/usage.js.map +1 -0
  123. package/dist/harness/util.d.ts +19 -0
  124. package/dist/harness/util.d.ts.map +1 -0
  125. package/dist/harness/util.js +58 -0
  126. package/dist/harness/util.js.map +1 -0
  127. package/dist/harness/view.d.ts +47 -0
  128. package/dist/harness/view.d.ts.map +1 -0
  129. package/dist/harness/view.js +206 -0
  130. package/dist/harness/view.js.map +1 -0
  131. package/dist/ids.d.ts +6 -0
  132. package/dist/ids.d.ts.map +1 -0
  133. package/dist/ids.js +9 -0
  134. package/dist/ids.js.map +1 -0
  135. package/dist/images/cloudflare.d.ts +4 -0
  136. package/dist/images/cloudflare.d.ts.map +1 -0
  137. package/dist/images/cloudflare.js +9 -0
  138. package/dist/images/cloudflare.js.map +1 -0
  139. package/dist/images/exif.d.ts +3 -0
  140. package/dist/images/exif.d.ts.map +1 -0
  141. package/dist/images/exif.js +71 -0
  142. package/dist/images/exif.js.map +1 -0
  143. package/dist/images/index.d.ts +18 -0
  144. package/dist/images/index.d.ts.map +1 -0
  145. package/dist/images/index.js +125 -0
  146. package/dist/images/index.js.map +1 -0
  147. package/dist/images/node-worker.d.ts +17 -0
  148. package/dist/images/node-worker.d.ts.map +1 -0
  149. package/dist/images/node-worker.js +16 -0
  150. package/dist/images/node-worker.js.map +1 -0
  151. package/dist/images/node.d.ts +21 -0
  152. package/dist/images/node.d.ts.map +1 -0
  153. package/dist/images/node.js +121 -0
  154. package/dist/images/node.js.map +1 -0
  155. package/dist/index.d.ts +24 -0
  156. package/dist/index.d.ts.map +1 -0
  157. package/dist/index.js +20 -0
  158. package/dist/index.js.map +1 -0
  159. package/dist/session/forks.d.ts +10 -0
  160. package/dist/session/forks.d.ts.map +1 -0
  161. package/dist/session/forks.js +42 -0
  162. package/dist/session/forks.js.map +1 -0
  163. package/dist/session/observation.d.ts +42 -0
  164. package/dist/session/observation.d.ts.map +1 -0
  165. package/dist/session/observation.js +281 -0
  166. package/dist/session/observation.js.map +1 -0
  167. package/dist/session/session.d.ts +133 -0
  168. package/dist/session/session.d.ts.map +1 -0
  169. package/dist/session/session.js +811 -0
  170. package/dist/session/session.js.map +1 -0
  171. package/dist/session/transaction.d.ts +119 -0
  172. package/dist/session/transaction.d.ts.map +1 -0
  173. package/dist/session/transaction.js +795 -0
  174. package/dist/session/transaction.js.map +1 -0
  175. package/dist/storage/jsonl/index.d.ts +2 -0
  176. package/dist/storage/jsonl/index.d.ts.map +1 -0
  177. package/dist/storage/jsonl/index.js +2 -0
  178. package/dist/storage/jsonl/index.js.map +1 -0
  179. package/dist/storage/jsonl/node.d.ts +6 -0
  180. package/dist/storage/jsonl/node.d.ts.map +1 -0
  181. package/dist/storage/jsonl/node.js +8 -0
  182. package/dist/storage/jsonl/node.js.map +1 -0
  183. package/dist/storage/jsonl/storage.d.ts +143 -0
  184. package/dist/storage/jsonl/storage.d.ts.map +1 -0
  185. package/dist/storage/jsonl/storage.js +682 -0
  186. package/dist/storage/jsonl/storage.js.map +1 -0
  187. package/dist/storage/memory.d.ts +62 -0
  188. package/dist/storage/memory.d.ts.map +1 -0
  189. package/dist/storage/memory.js +733 -0
  190. package/dist/storage/memory.js.map +1 -0
  191. package/dist/storage/scan.d.ts +18 -0
  192. package/dist/storage/scan.d.ts.map +1 -0
  193. package/dist/storage/scan.js +29 -0
  194. package/dist/storage/scan.js.map +1 -0
  195. package/dist/storage/sqlite/cloudflare.d.ts +46 -0
  196. package/dist/storage/sqlite/cloudflare.d.ts.map +1 -0
  197. package/dist/storage/sqlite/cloudflare.js +106 -0
  198. package/dist/storage/sqlite/cloudflare.js.map +1 -0
  199. package/dist/storage/sqlite/database.d.ts +37 -0
  200. package/dist/storage/sqlite/database.d.ts.map +1 -0
  201. package/dist/storage/sqlite/database.js +2 -0
  202. package/dist/storage/sqlite/database.js.map +1 -0
  203. package/dist/storage/sqlite/index.d.ts +4 -0
  204. package/dist/storage/sqlite/index.d.ts.map +1 -0
  205. package/dist/storage/sqlite/index.js +3 -0
  206. package/dist/storage/sqlite/index.js.map +1 -0
  207. package/dist/storage/sqlite/migrations.d.ts +11 -0
  208. package/dist/storage/sqlite/migrations.d.ts.map +1 -0
  209. package/dist/storage/sqlite/migrations.js +111 -0
  210. package/dist/storage/sqlite/migrations.js.map +1 -0
  211. package/dist/storage/sqlite/node.d.ts +41 -0
  212. package/dist/storage/sqlite/node.d.ts.map +1 -0
  213. package/dist/storage/sqlite/node.js +179 -0
  214. package/dist/storage/sqlite/node.js.map +1 -0
  215. package/dist/storage/sqlite/storage.d.ts +65 -0
  216. package/dist/storage/sqlite/storage.d.ts.map +1 -0
  217. package/dist/storage/sqlite/storage.js +670 -0
  218. package/dist/storage/sqlite/storage.js.map +1 -0
  219. package/dist/tasks.d.ts +6 -0
  220. package/dist/tasks.d.ts.map +1 -0
  221. package/dist/tasks.js +5 -0
  222. package/dist/tasks.js.map +1 -0
  223. package/dist/testing/assertions.d.ts +5 -0
  224. package/dist/testing/assertions.d.ts.map +1 -0
  225. package/dist/testing/assertions.js +25 -0
  226. package/dist/testing/assertions.js.map +1 -0
  227. package/dist/testing/env-conformance.d.ts +7 -0
  228. package/dist/testing/env-conformance.d.ts.map +1 -0
  229. package/dist/testing/env-conformance.js +431 -0
  230. package/dist/testing/env-conformance.js.map +1 -0
  231. package/dist/testing/index.d.ts +7 -0
  232. package/dist/testing/index.d.ts.map +1 -0
  233. package/dist/testing/index.js +6 -0
  234. package/dist/testing/index.js.map +1 -0
  235. package/dist/testing/runner.d.ts +12 -0
  236. package/dist/testing/runner.d.ts.map +1 -0
  237. package/dist/testing/runner.js +23 -0
  238. package/dist/testing/runner.js.map +1 -0
  239. package/dist/testing/storage-benchmark.d.ts +41 -0
  240. package/dist/testing/storage-benchmark.d.ts.map +1 -0
  241. package/dist/testing/storage-benchmark.js +350 -0
  242. package/dist/testing/storage-benchmark.js.map +1 -0
  243. package/dist/testing/storage-conformance.d.ts +4 -0
  244. package/dist/testing/storage-conformance.d.ts.map +1 -0
  245. package/dist/testing/storage-conformance.js +1213 -0
  246. package/dist/testing/storage-conformance.js.map +1 -0
  247. package/dist/testing/types.d.ts +37 -0
  248. package/dist/testing/types.d.ts.map +1 -0
  249. package/dist/testing/types.js +2 -0
  250. package/dist/testing/types.js.map +1 -0
  251. package/dist/tools/bash.d.ts +55 -0
  252. package/dist/tools/bash.d.ts.map +1 -0
  253. package/dist/tools/bash.js +138 -0
  254. package/dist/tools/bash.js.map +1 -0
  255. package/dist/tools/edit-diff.d.ts +89 -0
  256. package/dist/tools/edit-diff.d.ts.map +1 -0
  257. package/dist/tools/edit-diff.js +386 -0
  258. package/dist/tools/edit-diff.js.map +1 -0
  259. package/dist/tools/edit.d.ts +18 -0
  260. package/dist/tools/edit.d.ts.map +1 -0
  261. package/dist/tools/edit.js +117 -0
  262. package/dist/tools/edit.js.map +1 -0
  263. package/dist/tools/env.d.ts +5 -0
  264. package/dist/tools/env.d.ts.map +1 -0
  265. package/dist/tools/env.js +7 -0
  266. package/dist/tools/env.js.map +1 -0
  267. package/dist/tools/file-mutation-queue.d.ts +9 -0
  268. package/dist/tools/file-mutation-queue.d.ts.map +1 -0
  269. package/dist/tools/file-mutation-queue.js +52 -0
  270. package/dist/tools/file-mutation-queue.js.map +1 -0
  271. package/dist/tools/image-processor.d.ts +47 -0
  272. package/dist/tools/image-processor.d.ts.map +1 -0
  273. package/dist/tools/image-processor.js +30 -0
  274. package/dist/tools/image-processor.js.map +1 -0
  275. package/dist/tools/image.d.ts +20 -0
  276. package/dist/tools/image.d.ts.map +1 -0
  277. package/dist/tools/image.js +178 -0
  278. package/dist/tools/image.js.map +1 -0
  279. package/dist/tools/index.d.ts +46 -0
  280. package/dist/tools/index.d.ts.map +1 -0
  281. package/dist/tools/index.js +23 -0
  282. package/dist/tools/index.js.map +1 -0
  283. package/dist/tools/path-utils.d.ts +5 -0
  284. package/dist/tools/path-utils.d.ts.map +1 -0
  285. package/dist/tools/path-utils.js +26 -0
  286. package/dist/tools/path-utils.js.map +1 -0
  287. package/dist/tools/read.d.ts +25 -0
  288. package/dist/tools/read.d.ts.map +1 -0
  289. package/dist/tools/read.js +235 -0
  290. package/dist/tools/read.js.map +1 -0
  291. package/dist/tools/write.d.ts +10 -0
  292. package/dist/tools/write.d.ts.map +1 -0
  293. package/dist/tools/write.js +31 -0
  294. package/dist/tools/write.js.map +1 -0
  295. package/dist/truncate.d.ts +64 -0
  296. package/dist/truncate.d.ts.map +1 -0
  297. package/dist/truncate.js +154 -0
  298. package/dist/truncate.js.map +1 -0
  299. package/dist/types.d.ts +932 -0
  300. package/dist/types.d.ts.map +1 -0
  301. package/dist/types.js +3 -0
  302. package/dist/types.js.map +1 -0
  303. package/package.json +140 -5
@@ -0,0 +1,318 @@
1
+ import { utf8ByteLength } from "../truncate.js";
2
+ const NEWLINE = 0x0a;
3
+ const INVALID_OUTPUT = /[\x00-\x08\x0b-\x1f\ufff9-\ufffb]/g;
4
+ const encoder = new TextEncoder();
5
+ /** Slices decode exactly: a U+FEFF at a slice's start is text, not a byte-order mark. */
6
+ const decoder = new TextDecoder("utf-8", { ignoreBOM: true });
7
+ /** Remove control characters that break display and transcripts; tabs and newlines stay. */
8
+ export function sanitizeOutput(text) {
9
+ return text.replace(INVALID_OUTPUT, "");
10
+ }
11
+ /**
12
+ * Bound `text` to whole lines within the limits: the first lines for `head`, the last lines for `tail`. The result is an
13
+ * exact slice, trailing newline included. A single line longer than `maxBytes` is cut at the byte limit on a character
14
+ * boundary.
15
+ */
16
+ export function boundOutput(text, limits) {
17
+ const bytes = encoder.encode(text);
18
+ const [from, to] = limits.retain === "head" ? headRange(bytes, limits) : tailRange(bytes, limits);
19
+ const kept = bytes.subarray(from, to);
20
+ return {
21
+ text: kept.length === bytes.length ? text : decoder.decode(kept),
22
+ bytes: kept.length,
23
+ droppedBytes: bytes.length - kept.length,
24
+ droppedLines: lineCount(bytes) - lineCount(kept),
25
+ };
26
+ }
27
+ function headRange(bytes, limits) {
28
+ if (limits.maxLines === 0 || limits.maxBytes === 0)
29
+ return [0, 0];
30
+ let end = bytes.length;
31
+ let lines = 0;
32
+ for (let index = bytes.indexOf(NEWLINE); index !== -1; index = bytes.indexOf(NEWLINE, index + 1)) {
33
+ if (++lines === limits.maxLines) {
34
+ end = index + 1;
35
+ break;
36
+ }
37
+ }
38
+ if (end > limits.maxBytes) {
39
+ const newline = bytes.lastIndexOf(NEWLINE, limits.maxBytes - 1);
40
+ end = newline === -1 ? characterEnd(bytes, limits.maxBytes) : newline + 1;
41
+ }
42
+ return [0, end];
43
+ }
44
+ function tailRange(bytes, limits) {
45
+ if (limits.maxLines === 0 || limits.maxBytes === 0)
46
+ return [bytes.length, bytes.length];
47
+ // A trailing newline ends the last line rather than starting another.
48
+ const last = bytes[bytes.length - 1] === NEWLINE ? bytes.length - 2 : bytes.length - 1;
49
+ let start = 0;
50
+ let lines = 1;
51
+ for (let index = last < 0 ? -1 : bytes.lastIndexOf(NEWLINE, last); index !== -1;) {
52
+ if (lines === limits.maxLines) {
53
+ start = index + 1;
54
+ break;
55
+ }
56
+ lines++;
57
+ index = index === 0 ? -1 : bytes.lastIndexOf(NEWLINE, index - 1);
58
+ }
59
+ if (bytes.length - start > limits.maxBytes) {
60
+ const from = bytes.length - limits.maxBytes;
61
+ const newline = bytes.indexOf(NEWLINE, from - 1);
62
+ // The first line starting inside the byte window, or a cut of the last line when it alone is too long.
63
+ start = newline !== -1 && newline + 1 < bytes.length ? newline + 1 : characterStart(bytes, from);
64
+ }
65
+ return [start, bytes.length];
66
+ }
67
+ /** The last character boundary at or before `index`. */
68
+ export function characterEnd(bytes, index) {
69
+ let end = index;
70
+ while (end > 0 && ((bytes[end] ?? 0) & 0xc0) === 0x80)
71
+ end--;
72
+ return end;
73
+ }
74
+ /** The first character boundary at or after `index`. */
75
+ function characterStart(bytes, index) {
76
+ let start = index;
77
+ while (start < bytes.length && ((bytes[start] ?? 0) & 0xc0) === 0x80)
78
+ start++;
79
+ return start;
80
+ }
81
+ function lineCount(bytes) {
82
+ if (bytes.length === 0)
83
+ return 0;
84
+ let newlines = 0;
85
+ for (let index = bytes.indexOf(NEWLINE); index !== -1; index = bytes.indexOf(NEWLINE, index + 1))
86
+ newlines++;
87
+ return newlines + (bytes[bytes.length - 1] === NEWLINE ? 0 : 1);
88
+ }
89
+ /**
90
+ * Bounded running output of one tool call. Accepting a chunk costs time proportional to the chunk: head retention stops
91
+ * storing once the window is full, and tail retention drops stored text the window no longer needs when it snapshots.
92
+ * Counts of the whole stream are kept so the dropped totals stay exact.
93
+ */
94
+ export class OutputBuffer {
95
+ #limits;
96
+ /**
97
+ * Decoder of byte chunks. A string chunk or a skip ends an incomplete character of earlier bytes (it becomes U+FFFD);
98
+ * the next byte chunk then starts a new character. As in `StreamDecoder`, only a byte-order mark at the very start of
99
+ * the output is dropped, never a U+FEFF later in it.
100
+ */
101
+ #decoder = new TextDecoder("utf-8", { ignoreBOM: true });
102
+ #started = false;
103
+ /** Stored chunks: for head the start of the stream, for tail a suffix that still contains the next window. */
104
+ #chunks = [];
105
+ #storedBytes = 0;
106
+ #storedNewlines = 0;
107
+ #full = false;
108
+ #totalBytes = 0;
109
+ #totalNewlines = 0;
110
+ #endsWithNewline = true;
111
+ constructor(limits) {
112
+ this.#limits = limits;
113
+ }
114
+ /** Bytes currently held; bounded by the limits plus one chunk. */
115
+ get storedBytes() {
116
+ return this.#storedBytes;
117
+ }
118
+ /**
119
+ * Accept a chunk; returns whether anything was accepted. `skipped` is output omitted right before the chunk, which
120
+ * must be more than the tail window by at least one byte or line (`ShellOutputInfo.skipped`); only tail retention
121
+ * accepts it.
122
+ */
123
+ push(chunk, skipped) {
124
+ // Bytes of an incomplete character from an earlier byte chunk come first.
125
+ const pending = typeof chunk === "string" || skipped !== undefined ? this.#decoder.decode() : "";
126
+ let text = typeof chunk === "string" ? chunk : this.#decoder.decode(chunk, { stream: true });
127
+ const first = !this.#started && pending === "" && skipped === undefined;
128
+ if (pending !== "" || text !== "" || skipped !== undefined)
129
+ this.#started = true;
130
+ if (first && typeof chunk !== "string" && text.startsWith("\ufeff"))
131
+ text = text.slice(1);
132
+ if (skipped === undefined)
133
+ return this.#accept(pending + text);
134
+ if (this.#limits.retain !== "tail")
135
+ throw new Error("Skipped output requires tail retention");
136
+ this.#accept(pending);
137
+ this.#skip(skipped);
138
+ this.#accept(text);
139
+ return true;
140
+ }
141
+ /** Count omitted output; nothing stored before it can be in the window once the text after it arrives. */
142
+ #skip(skipped) {
143
+ if (skipped.bytes === 0)
144
+ return;
145
+ this.#totalBytes += skipped.bytes;
146
+ this.#totalNewlines += skipped.newlines;
147
+ this.#endsWithNewline = skipped.endsWithNewline;
148
+ this.#chunks = [];
149
+ this.#storedBytes = 0;
150
+ this.#storedNewlines = 0;
151
+ }
152
+ /** Flush an incomplete trailing character as a replacement character; call when the stream ends. */
153
+ end() {
154
+ this.#accept(this.#decoder.decode());
155
+ }
156
+ #accept(text) {
157
+ if (text.length === 0)
158
+ return false;
159
+ const bytes = utf8ByteLength(text);
160
+ const newlines = countNewlines(text);
161
+ this.#totalBytes += bytes;
162
+ this.#totalNewlines += newlines;
163
+ this.#endsWithNewline = text.endsWith("\n");
164
+ if (this.#full)
165
+ return true;
166
+ this.#chunks.push({ text, bytes, newlines });
167
+ this.#storedBytes += bytes;
168
+ this.#storedNewlines += newlines;
169
+ if (this.#limits.retain === "head") {
170
+ // Nothing past a full window is ever needed.
171
+ this.#full = this.#storedBytes > this.#limits.maxBytes || this.#storedNewlines >= this.#limits.maxLines;
172
+ return true;
173
+ }
174
+ // Drop leading chunks while the rest still holds more than a window: more than `maxBytes` bytes or `maxLines`
175
+ // newlines, plus one, so the window's line start can still be found. Each chunk is dropped once.
176
+ while (this.#chunks.length > 1) {
177
+ const first = this.#chunks[0];
178
+ const bytesAfter = this.#storedBytes - first.bytes;
179
+ const newlinesAfter = this.#storedNewlines - first.newlines;
180
+ if (bytesAfter <= this.#limits.maxBytes + 1 && newlinesAfter <= this.#limits.maxLines + 1)
181
+ break;
182
+ this.#chunks.shift();
183
+ this.#storedBytes = bytesAfter;
184
+ this.#storedNewlines = newlinesAfter;
185
+ }
186
+ return true;
187
+ }
188
+ /** Retained, sanitized output and what the limits dropped from the whole stream. */
189
+ snapshot() {
190
+ const stored = this.#chunks.length === 1 ? this.#chunks[0].text : this.#chunks.map((chunk) => chunk.text).join("");
191
+ const kept = boundOutput(stored, this.#limits);
192
+ const storedLines = lines(this.#storedNewlines, stored === "" || stored.endsWith("\n"));
193
+ const keptLines = storedLines - kept.droppedLines;
194
+ // Tail windows never reach back before this one, but finding a later window's first line needs what precedes it:
195
+ // keep the shortest suffix longer than the window by a byte or a line, as `#accept` does.
196
+ if (this.#limits.retain === "tail" || this.#chunks.length > 1) {
197
+ const text = this.#limits.retain === "tail" ? tailMargin(stored, this.#limits) : stored;
198
+ const bytes = this.#limits.retain === "tail" ? utf8ByteLength(text) : this.#storedBytes;
199
+ this.#chunks = text === "" ? [] : [{ text, bytes, newlines: countNewlines(text) }];
200
+ this.#storedBytes = bytes;
201
+ this.#storedNewlines = this.#chunks[0]?.newlines ?? 0;
202
+ }
203
+ return {
204
+ text: sanitizeOutput(kept.text),
205
+ droppedBytes: this.#totalBytes - kept.bytes,
206
+ droppedLines: lines(this.#totalNewlines, this.#endsWithNewline) - keptLines,
207
+ };
208
+ }
209
+ }
210
+ /**
211
+ * The shortest suffix of `text` with more than `maxBytes` bytes or more than `maxLines` newlines, or all of it. The
212
+ * tail window of any text that ends with this suffix, followed by anything, is the same as of `text` followed by it.
213
+ */
214
+ function tailMargin(text, limits) {
215
+ const bytes = encoder.encode(text);
216
+ const byteStart = bytes.length > limits.maxBytes ? characterEnd(bytes, bytes.length - limits.maxBytes - 1) : 0;
217
+ let lineStart = 0;
218
+ let newlines = 0;
219
+ for (let index = bytes.lastIndexOf(NEWLINE); index !== -1; index = bytes.lastIndexOf(NEWLINE, index - 1)) {
220
+ if (++newlines > limits.maxLines) {
221
+ lineStart = index;
222
+ break;
223
+ }
224
+ if (index === 0)
225
+ break;
226
+ }
227
+ const start = Math.max(byteStart, lineStart);
228
+ return start === 0 ? text : decoder.decode(bytes.subarray(start));
229
+ }
230
+ /** Lines of text with `newlines` newlines; a final unterminated line counts. */
231
+ function lines(newlines, terminated) {
232
+ return newlines + (terminated ? 0 : 1);
233
+ }
234
+ function countNewlines(text) {
235
+ let count = 0;
236
+ for (let index = text.indexOf("\n"); index !== -1; index = text.indexOf("\n", index + 1))
237
+ count++;
238
+ return count;
239
+ }
240
+ /** Each progress commit also buys a pause proportional to what it wrote. */
241
+ export const PROGRESS_BYTES_PER_SECOND = 100 * 1024;
242
+ /**
243
+ * Adaptive progress commits: the first change after an idle period commits at once; each commit then delays the next
244
+ * by at least `minIntervalMs` and by its written size at 100 KiB/s. At most one commit is in flight; changes made
245
+ * meanwhile coalesce into the next one.
246
+ */
247
+ export class Progress {
248
+ #write;
249
+ #onError;
250
+ #minIntervalMs;
251
+ #waiters = [];
252
+ #timer;
253
+ #inFlight;
254
+ #nextAt = 0;
255
+ #dirty = false;
256
+ #stopped = false;
257
+ constructor(write, onError, minIntervalMs) {
258
+ this.#write = write;
259
+ this.#onError = onError;
260
+ this.#minIntervalMs = minIntervalMs;
261
+ }
262
+ /** Schedule a commit. */
263
+ mark() {
264
+ this.#dirty = true;
265
+ this.#schedule();
266
+ }
267
+ /** Schedule a commit; the promise settles with the commit that includes this change. */
268
+ markAndWait() {
269
+ const waiter = Promise.withResolvers();
270
+ this.#waiters.push(waiter);
271
+ this.mark();
272
+ return waiter.promise;
273
+ }
274
+ /** Stop committing and wait for the commit in flight; returns the waiters the final commit must settle. */
275
+ async stop() {
276
+ this.#stopped = true;
277
+ clearTimeout(this.#timer);
278
+ this.#timer = undefined;
279
+ await this.#inFlight;
280
+ return this.#waiters.splice(0);
281
+ }
282
+ #schedule() {
283
+ if (this.#stopped || this.#timer !== undefined || this.#inFlight !== undefined)
284
+ return;
285
+ const wait = this.#nextAt - Date.now();
286
+ if (wait <= 0)
287
+ this.#flush();
288
+ else
289
+ this.#timer = setTimeout(() => {
290
+ this.#timer = undefined;
291
+ this.#flush();
292
+ }, wait);
293
+ }
294
+ #flush() {
295
+ if (this.#stopped || !this.#dirty)
296
+ return;
297
+ this.#dirty = false;
298
+ const waiters = this.#waiters.splice(0);
299
+ const started = Date.now();
300
+ this.#inFlight = this.#write()
301
+ .then((bytes) => {
302
+ this.#nextAt = started + Math.max(this.#minIntervalMs, (bytes * 1000) / PROGRESS_BYTES_PER_SECOND);
303
+ for (const waiter of waiters)
304
+ waiter.resolve();
305
+ }, (error) => {
306
+ this.#nextAt = started + this.#minIntervalMs;
307
+ for (const waiter of waiters)
308
+ waiter.reject(error);
309
+ this.#onError(error);
310
+ })
311
+ .finally(() => {
312
+ this.#inFlight = undefined;
313
+ if (this.#dirty)
314
+ this.#schedule();
315
+ });
316
+ }
317
+ }
318
+ //# sourceMappingURL=output.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"output.js","sourceRoot":"","sources":["../../src/harness/output.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ShellOutputSkip } from \"../env/index.ts\";\nimport { utf8ByteLength } from \"../truncate.ts\";\n\n/** Retention limits of one tool's output. */\nexport type OutputLimits = { readonly maxBytes: number; readonly maxLines: number; readonly retain: \"head\" | \"tail\" };\n\n/** Retained output and what the limits dropped. */\nexport type BoundedOutput = { readonly text: string; readonly droppedBytes: number; readonly droppedLines: number };\n\n/** An exact slice of the input within the limits, and what it left out. */\nexport type OutputSlice = {\n\treadonly text: string;\n\treadonly bytes: number;\n\treadonly droppedBytes: number;\n\treadonly droppedLines: number;\n};\n\nconst NEWLINE = 0x0a;\nconst INVALID_OUTPUT = /[\\x00-\\x08\\x0b-\\x1f\\ufff9-\\ufffb]/g;\nconst encoder = new TextEncoder();\n/** Slices decode exactly: a U+FEFF at a slice's start is text, not a byte-order mark. */\nconst decoder = new TextDecoder(\"utf-8\", { ignoreBOM: true });\n\n/** Remove control characters that break display and transcripts; tabs and newlines stay. */\nexport function sanitizeOutput(text: string): string {\n\treturn text.replace(INVALID_OUTPUT, \"\");\n}\n\n/**\n * Bound `text` to whole lines within the limits: the first lines for `head`, the last lines for `tail`. The result is an\n * exact slice, trailing newline included. A single line longer than `maxBytes` is cut at the byte limit on a character\n * boundary.\n */\nexport function boundOutput(text: string, limits: OutputLimits): OutputSlice {\n\tconst bytes = encoder.encode(text);\n\tconst [from, to] = limits.retain === \"head\" ? headRange(bytes, limits) : tailRange(bytes, limits);\n\tconst kept = bytes.subarray(from, to);\n\treturn {\n\t\ttext: kept.length === bytes.length ? text : decoder.decode(kept),\n\t\tbytes: kept.length,\n\t\tdroppedBytes: bytes.length - kept.length,\n\t\tdroppedLines: lineCount(bytes) - lineCount(kept),\n\t};\n}\n\nfunction headRange(bytes: Uint8Array, limits: OutputLimits): [number, number] {\n\tif (limits.maxLines === 0 || limits.maxBytes === 0) return [0, 0];\n\tlet end = bytes.length;\n\tlet lines = 0;\n\tfor (let index = bytes.indexOf(NEWLINE); index !== -1; index = bytes.indexOf(NEWLINE, index + 1)) {\n\t\tif (++lines === limits.maxLines) {\n\t\t\tend = index + 1;\n\t\t\tbreak;\n\t\t}\n\t}\n\tif (end > limits.maxBytes) {\n\t\tconst newline = bytes.lastIndexOf(NEWLINE, limits.maxBytes - 1);\n\t\tend = newline === -1 ? characterEnd(bytes, limits.maxBytes) : newline + 1;\n\t}\n\treturn [0, end];\n}\n\nfunction tailRange(bytes: Uint8Array, limits: OutputLimits): [number, number] {\n\tif (limits.maxLines === 0 || limits.maxBytes === 0) return [bytes.length, bytes.length];\n\t// A trailing newline ends the last line rather than starting another.\n\tconst last = bytes[bytes.length - 1] === NEWLINE ? bytes.length - 2 : bytes.length - 1;\n\tlet start = 0;\n\tlet lines = 1;\n\tfor (let index = last < 0 ? -1 : bytes.lastIndexOf(NEWLINE, last); index !== -1; ) {\n\t\tif (lines === limits.maxLines) {\n\t\t\tstart = index + 1;\n\t\t\tbreak;\n\t\t}\n\t\tlines++;\n\t\tindex = index === 0 ? -1 : bytes.lastIndexOf(NEWLINE, index - 1);\n\t}\n\tif (bytes.length - start > limits.maxBytes) {\n\t\tconst from = bytes.length - limits.maxBytes;\n\t\tconst newline = bytes.indexOf(NEWLINE, from - 1);\n\t\t// The first line starting inside the byte window, or a cut of the last line when it alone is too long.\n\t\tstart = newline !== -1 && newline + 1 < bytes.length ? newline + 1 : characterStart(bytes, from);\n\t}\n\treturn [start, bytes.length];\n}\n\n/** The last character boundary at or before `index`. */\nexport function characterEnd(bytes: Uint8Array, index: number): number {\n\tlet end = index;\n\twhile (end > 0 && ((bytes[end] ?? 0) & 0xc0) === 0x80) end--;\n\treturn end;\n}\n\n/** The first character boundary at or after `index`. */\nfunction characterStart(bytes: Uint8Array, index: number): number {\n\tlet start = index;\n\twhile (start < bytes.length && ((bytes[start] ?? 0) & 0xc0) === 0x80) start++;\n\treturn start;\n}\n\nfunction lineCount(bytes: Uint8Array): number {\n\tif (bytes.length === 0) return 0;\n\tlet newlines = 0;\n\tfor (let index = bytes.indexOf(NEWLINE); index !== -1; index = bytes.indexOf(NEWLINE, index + 1)) newlines++;\n\treturn newlines + (bytes[bytes.length - 1] === NEWLINE ? 0 : 1);\n}\n\n/**\n * Bounded running output of one tool call. Accepting a chunk costs time proportional to the chunk: head retention stops\n * storing once the window is full, and tail retention drops stored text the window no longer needs when it snapshots.\n * Counts of the whole stream are kept so the dropped totals stay exact.\n */\nexport class OutputBuffer {\n\treadonly #limits: OutputLimits;\n\t/**\n\t * Decoder of byte chunks. A string chunk or a skip ends an incomplete character of earlier bytes (it becomes U+FFFD);\n\t * the next byte chunk then starts a new character. As in `StreamDecoder`, only a byte-order mark at the very start of\n\t * the output is dropped, never a U+FEFF later in it.\n\t */\n\treadonly #decoder = new TextDecoder(\"utf-8\", { ignoreBOM: true });\n\t#started = false;\n\t/** Stored chunks: for head the start of the stream, for tail a suffix that still contains the next window. */\n\t#chunks: { readonly text: string; readonly bytes: number; readonly newlines: number }[] = [];\n\t#storedBytes = 0;\n\t#storedNewlines = 0;\n\t#full = false;\n\t#totalBytes = 0;\n\t#totalNewlines = 0;\n\t#endsWithNewline = true;\n\n\tconstructor(limits: OutputLimits) {\n\t\tthis.#limits = limits;\n\t}\n\n\t/** Bytes currently held; bounded by the limits plus one chunk. */\n\tget storedBytes(): number {\n\t\treturn this.#storedBytes;\n\t}\n\n\t/**\n\t * Accept a chunk; returns whether anything was accepted. `skipped` is output omitted right before the chunk, which\n\t * must be more than the tail window by at least one byte or line (`ShellOutputInfo.skipped`); only tail retention\n\t * accepts it.\n\t */\n\tpush(chunk: string | Uint8Array, skipped?: ShellOutputSkip): boolean {\n\t\t// Bytes of an incomplete character from an earlier byte chunk come first.\n\t\tconst pending = typeof chunk === \"string\" || skipped !== undefined ? this.#decoder.decode() : \"\";\n\t\tlet text = typeof chunk === \"string\" ? chunk : this.#decoder.decode(chunk, { stream: true });\n\t\tconst first = !this.#started && pending === \"\" && skipped === undefined;\n\t\tif (pending !== \"\" || text !== \"\" || skipped !== undefined) this.#started = true;\n\t\tif (first && typeof chunk !== \"string\" && text.startsWith(\"\\ufeff\")) text = text.slice(1);\n\t\tif (skipped === undefined) return this.#accept(pending + text);\n\t\tif (this.#limits.retain !== \"tail\") throw new Error(\"Skipped output requires tail retention\");\n\t\tthis.#accept(pending);\n\t\tthis.#skip(skipped);\n\t\tthis.#accept(text);\n\t\treturn true;\n\t}\n\n\t/** Count omitted output; nothing stored before it can be in the window once the text after it arrives. */\n\t#skip(skipped: ShellOutputSkip): void {\n\t\tif (skipped.bytes === 0) return;\n\t\tthis.#totalBytes += skipped.bytes;\n\t\tthis.#totalNewlines += skipped.newlines;\n\t\tthis.#endsWithNewline = skipped.endsWithNewline;\n\t\tthis.#chunks = [];\n\t\tthis.#storedBytes = 0;\n\t\tthis.#storedNewlines = 0;\n\t}\n\n\t/** Flush an incomplete trailing character as a replacement character; call when the stream ends. */\n\tend(): void {\n\t\tthis.#accept(this.#decoder.decode());\n\t}\n\n\t#accept(text: string): boolean {\n\t\tif (text.length === 0) return false;\n\t\tconst bytes = utf8ByteLength(text);\n\t\tconst newlines = countNewlines(text);\n\t\tthis.#totalBytes += bytes;\n\t\tthis.#totalNewlines += newlines;\n\t\tthis.#endsWithNewline = text.endsWith(\"\\n\");\n\t\tif (this.#full) return true;\n\t\tthis.#chunks.push({ text, bytes, newlines });\n\t\tthis.#storedBytes += bytes;\n\t\tthis.#storedNewlines += newlines;\n\t\tif (this.#limits.retain === \"head\") {\n\t\t\t// Nothing past a full window is ever needed.\n\t\t\tthis.#full = this.#storedBytes > this.#limits.maxBytes || this.#storedNewlines >= this.#limits.maxLines;\n\t\t\treturn true;\n\t\t}\n\t\t// Drop leading chunks while the rest still holds more than a window: more than `maxBytes` bytes or `maxLines`\n\t\t// newlines, plus one, so the window's line start can still be found. Each chunk is dropped once.\n\t\twhile (this.#chunks.length > 1) {\n\t\t\tconst first = this.#chunks[0]!;\n\t\t\tconst bytesAfter = this.#storedBytes - first.bytes;\n\t\t\tconst newlinesAfter = this.#storedNewlines - first.newlines;\n\t\t\tif (bytesAfter <= this.#limits.maxBytes + 1 && newlinesAfter <= this.#limits.maxLines + 1) break;\n\t\t\tthis.#chunks.shift();\n\t\t\tthis.#storedBytes = bytesAfter;\n\t\t\tthis.#storedNewlines = newlinesAfter;\n\t\t}\n\t\treturn true;\n\t}\n\n\t/** Retained, sanitized output and what the limits dropped from the whole stream. */\n\tsnapshot(): BoundedOutput {\n\t\tconst stored =\n\t\t\tthis.#chunks.length === 1 ? this.#chunks[0]!.text : this.#chunks.map((chunk) => chunk.text).join(\"\");\n\t\tconst kept = boundOutput(stored, this.#limits);\n\t\tconst storedLines = lines(this.#storedNewlines, stored === \"\" || stored.endsWith(\"\\n\"));\n\t\tconst keptLines = storedLines - kept.droppedLines;\n\t\t// Tail windows never reach back before this one, but finding a later window's first line needs what precedes it:\n\t\t// keep the shortest suffix longer than the window by a byte or a line, as `#accept` does.\n\t\tif (this.#limits.retain === \"tail\" || this.#chunks.length > 1) {\n\t\t\tconst text = this.#limits.retain === \"tail\" ? tailMargin(stored, this.#limits) : stored;\n\t\t\tconst bytes = this.#limits.retain === \"tail\" ? utf8ByteLength(text) : this.#storedBytes;\n\t\t\tthis.#chunks = text === \"\" ? [] : [{ text, bytes, newlines: countNewlines(text) }];\n\t\t\tthis.#storedBytes = bytes;\n\t\t\tthis.#storedNewlines = this.#chunks[0]?.newlines ?? 0;\n\t\t}\n\t\treturn {\n\t\t\ttext: sanitizeOutput(kept.text),\n\t\t\tdroppedBytes: this.#totalBytes - kept.bytes,\n\t\t\tdroppedLines: lines(this.#totalNewlines, this.#endsWithNewline) - keptLines,\n\t\t};\n\t}\n}\n\n/**\n * The shortest suffix of `text` with more than `maxBytes` bytes or more than `maxLines` newlines, or all of it. The\n * tail window of any text that ends with this suffix, followed by anything, is the same as of `text` followed by it.\n */\nfunction tailMargin(text: string, limits: OutputLimits): string {\n\tconst bytes = encoder.encode(text);\n\tconst byteStart = bytes.length > limits.maxBytes ? characterEnd(bytes, bytes.length - limits.maxBytes - 1) : 0;\n\tlet lineStart = 0;\n\tlet newlines = 0;\n\tfor (let index = bytes.lastIndexOf(NEWLINE); index !== -1; index = bytes.lastIndexOf(NEWLINE, index - 1)) {\n\t\tif (++newlines > limits.maxLines) {\n\t\t\tlineStart = index;\n\t\t\tbreak;\n\t\t}\n\t\tif (index === 0) break;\n\t}\n\tconst start = Math.max(byteStart, lineStart);\n\treturn start === 0 ? text : decoder.decode(bytes.subarray(start));\n}\n\n/** Lines of text with `newlines` newlines; a final unterminated line counts. */\nfunction lines(newlines: number, terminated: boolean): number {\n\treturn newlines + (terminated ? 0 : 1);\n}\n\nfunction countNewlines(text: string): number {\n\tlet count = 0;\n\tfor (let index = text.indexOf(\"\\n\"); index !== -1; index = text.indexOf(\"\\n\", index + 1)) count++;\n\treturn count;\n}\n\n/** Each progress commit also buys a pause proportional to what it wrote. */\nexport const PROGRESS_BYTES_PER_SECOND = 100 * 1024;\n\n/**\n * Adaptive progress commits: the first change after an idle period commits at once; each commit then delays the next\n * by at least `minIntervalMs` and by its written size at 100 KiB/s. At most one commit is in flight; changes made\n * meanwhile coalesce into the next one.\n */\nexport class Progress {\n\treadonly #write: () => Promise<number>;\n\treadonly #onError: (error: unknown) => void;\n\treadonly #minIntervalMs: number;\n\t#waiters: PromiseWithResolvers<void>[] = [];\n\t#timer: ReturnType<typeof setTimeout> | undefined;\n\t#inFlight: Promise<void> | undefined;\n\t#nextAt = 0;\n\t#dirty = false;\n\t#stopped = false;\n\n\tconstructor(write: () => Promise<number>, onError: (error: unknown) => void, minIntervalMs: number) {\n\t\tthis.#write = write;\n\t\tthis.#onError = onError;\n\t\tthis.#minIntervalMs = minIntervalMs;\n\t}\n\n\t/** Schedule a commit. */\n\tmark(): void {\n\t\tthis.#dirty = true;\n\t\tthis.#schedule();\n\t}\n\n\t/** Schedule a commit; the promise settles with the commit that includes this change. */\n\tmarkAndWait(): Promise<void> {\n\t\tconst waiter = Promise.withResolvers<void>();\n\t\tthis.#waiters.push(waiter);\n\t\tthis.mark();\n\t\treturn waiter.promise;\n\t}\n\n\t/** Stop committing and wait for the commit in flight; returns the waiters the final commit must settle. */\n\tasync stop(): Promise<PromiseWithResolvers<void>[]> {\n\t\tthis.#stopped = true;\n\t\tclearTimeout(this.#timer);\n\t\tthis.#timer = undefined;\n\t\tawait this.#inFlight;\n\t\treturn this.#waiters.splice(0);\n\t}\n\n\t#schedule(): void {\n\t\tif (this.#stopped || this.#timer !== undefined || this.#inFlight !== undefined) return;\n\t\tconst wait = this.#nextAt - Date.now();\n\t\tif (wait <= 0) this.#flush();\n\t\telse\n\t\t\tthis.#timer = setTimeout(() => {\n\t\t\t\tthis.#timer = undefined;\n\t\t\t\tthis.#flush();\n\t\t\t}, wait);\n\t}\n\n\t#flush(): void {\n\t\tif (this.#stopped || !this.#dirty) return;\n\t\tthis.#dirty = false;\n\t\tconst waiters = this.#waiters.splice(0);\n\t\tconst started = Date.now();\n\t\tthis.#inFlight = this.#write()\n\t\t\t.then(\n\t\t\t\t(bytes) => {\n\t\t\t\t\tthis.#nextAt = started + Math.max(this.#minIntervalMs, (bytes * 1000) / PROGRESS_BYTES_PER_SECOND);\n\t\t\t\t\tfor (const waiter of waiters) waiter.resolve();\n\t\t\t\t},\n\t\t\t\t(error: unknown) => {\n\t\t\t\t\tthis.#nextAt = started + this.#minIntervalMs;\n\t\t\t\t\tfor (const waiter of waiters) waiter.reject(error);\n\t\t\t\t\tthis.#onError(error);\n\t\t\t\t},\n\t\t\t)\n\t\t\t.finally(() => {\n\t\t\t\tthis.#inFlight = undefined;\n\t\t\t\tif (this.#dirty) this.#schedule();\n\t\t\t});\n\t}\n}\n"]}
@@ -0,0 +1,27 @@
1
+ import type { Message, Tool } from "@ny4n/ai/contracts";
2
+ import type { Context } from "@ny4n/chord";
3
+ import type { TypedEntryDraft } from "../types.ts";
4
+ import type { ContextView, PromptInput, PromptSection, ToolRegistration } from "./types.ts";
5
+ /** Sections in effect after replaying system messages in order: set in place, `null` deletes, re-adding appends. */
6
+ export declare function replaySections(messages: readonly Message[]): Map<string, string>;
7
+ /**
8
+ * Render the agent's sections in order. `undefined` omits a section; tagged text is wrapped as `<key>\n...\n</key>`. A
9
+ * section that throws keeps its shown text, if any, and is reported; errors after `context` is aborted propagate.
10
+ */
11
+ export declare function renderSections<Tool extends ToolRegistration>(sections: readonly PromptSection<Tool>[], input: PromptInput<Tool>, shown: ReadonlyMap<string, string>, report: (error: unknown) => void, context: Context): Promise<Map<string, string>>;
12
+ type SystemDraft = TypedEntryDraft<never>;
13
+ /**
14
+ * Plan the `nyan.system` entries that make the replayed sections and tools of `view` equal `desired` and `tools` in values
15
+ * and order.
16
+ *
17
+ * - A head marker with no later `nyan.system` entry in context: one complete baseline that omits every retained earlier
18
+ * `nyan.system` entry, written even when it restates the replayed values.
19
+ * - Otherwise, when a minimal section patch would leave a different order: remove every shown section, then re-add
20
+ * every desired section in order.
21
+ * - Otherwise the minimal patch of changed values and `null` removals, or nothing.
22
+ *
23
+ * Tool changes ride on the last planned entry, or on one entry of their own.
24
+ */
25
+ export declare function planSystemEntries(view: ContextView, desired: ReadonlyMap<string, string>, tools: readonly Tool[], timestamp: number): SystemDraft[];
26
+ export {};
27
+ //# sourceMappingURL=prompt.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompt.d.ts","sourceRoot":"","sources":["../../src/harness/prompt.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAiB,IAAI,EAAiB,MAAM,oBAAoB,CAAC;AAEtF,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAE3C,OAAO,KAAK,EAAe,eAAe,EAAE,MAAM,aAAa,CAAC;AAChE,OAAO,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAE5F,oHAAoH;AACpH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,SAAS,OAAO,EAAE,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAUhF;AAED;;;GAGG;AACH,wBAAsB,cAAc,CAAC,IAAI,SAAS,gBAAgB,EACjE,QAAQ,EAAE,SAAS,aAAa,CAAC,IAAI,CAAC,EAAE,EACxC,KAAK,EAAE,WAAW,CAAC,IAAI,CAAC,EACxB,KAAK,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,EAClC,MAAM,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,EAChC,OAAO,EAAE,OAAO,GACd,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAiB9B;AAED,KAAK,WAAW,GAAG,eAAe,CAAC,KAAK,CAAC,CAAC;AAI1C;;;;;;;;;;;GAWG;AACH,wBAAgB,iBAAiB,CAChC,IAAI,EAAE,WAAW,EACjB,OAAO,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,EACpC,KAAK,EAAE,SAAS,IAAI,EAAE,EACtB,SAAS,EAAE,MAAM,GACf,WAAW,EAAE,CAsBf"}
@@ -0,0 +1,131 @@
1
+ import { declarationsEqual, getCurrentTools, toToolDeclaration } from "@ny4n/ai/utils/transcript";
2
+ import { SystemEntry } from "../entries.js";
3
+ /** Sections in effect after replaying system messages in order: set in place, `null` deletes, re-adding appends. */
4
+ export function replaySections(messages) {
5
+ const shown = new Map();
6
+ for (const message of messages) {
7
+ if (message.role !== "system" || message.sections === undefined)
8
+ continue;
9
+ for (const [key, value] of Object.entries(message.sections)) {
10
+ if (value === null)
11
+ shown.delete(key);
12
+ else
13
+ shown.set(key, value);
14
+ }
15
+ }
16
+ return shown;
17
+ }
18
+ /**
19
+ * Render the agent's sections in order. `undefined` omits a section; tagged text is wrapped as `<key>\n...\n</key>`. A
20
+ * section that throws keeps its shown text, if any, and is reported; errors after `context` is aborted propagate.
21
+ */
22
+ export async function renderSections(sections, input, shown, report, context) {
23
+ const desired = new Map();
24
+ for (const section of sections) {
25
+ let text;
26
+ try {
27
+ text = await section.render(input, context);
28
+ }
29
+ catch (error) {
30
+ if (context.abortSignal?.aborted)
31
+ throw error;
32
+ report(error);
33
+ const kept = shown.get(section.key);
34
+ if (kept !== undefined)
35
+ desired.set(section.key, kept);
36
+ continue;
37
+ }
38
+ if (text === undefined)
39
+ continue;
40
+ desired.set(section.key, section.tag === false ? text : `<${section.key}>\n${text}\n</${section.key}>`);
41
+ }
42
+ return desired;
43
+ }
44
+ /**
45
+ * Plan the `nyan.system` entries that make the replayed sections and tools of `view` equal `desired` and `tools` in values
46
+ * and order.
47
+ *
48
+ * - A head marker with no later `nyan.system` entry in context: one complete baseline that omits every retained earlier
49
+ * `nyan.system` entry, written even when it restates the replayed values.
50
+ * - Otherwise, when a minimal section patch would leave a different order: remove every shown section, then re-add
51
+ * every desired section in order.
52
+ * - Otherwise the minimal patch of changed values and `null` removals, or nothing.
53
+ *
54
+ * Tool changes ride on the last planned entry, or on one entry of their own.
55
+ */
56
+ export function planSystemEntries(view, desired, tools, timestamp) {
57
+ const head = view.head;
58
+ if (head !== undefined && !view.entries.some((entry) => SystemEntry.is(entry) && entry.id > head.id)) {
59
+ const edits = view.entries
60
+ .filter((entry) => SystemEntry.is(entry))
61
+ .map((entry) => ({ target: entry.id, action: "omit" }));
62
+ const baseline = systemEntry(Object.fromEntries(desired), { toolsRemoved: [], toolsAdded: tools.map(toToolDeclaration) }, timestamp);
63
+ return [edits.length === 0 ? baseline : { ...baseline, edits }];
64
+ }
65
+ const sections = planSections(replaySections(view.messages), desired);
66
+ const changes = planTools(getCurrentTools(view.messages), tools);
67
+ if (changes.toolsRemoved.length === 0 && changes.toolsAdded.length === 0) {
68
+ return sections.map((patch) => systemEntry(patch, undefined, timestamp));
69
+ }
70
+ if (sections.length === 0)
71
+ return [systemEntry(undefined, changes, timestamp)];
72
+ return sections.map((patch, index) => systemEntry(patch, index === sections.length - 1 ? changes : undefined, timestamp));
73
+ }
74
+ /**
75
+ * Tool changes from `offered` to `desired`. A changed declaration is removed and re-added. Replay keeps retained tools
76
+ * in place and appends additions; when that would not yield the desired order, every offered tool is removed and
77
+ * every desired tool re-added in order.
78
+ */
79
+ function planTools(offered, desired) {
80
+ const wanted = new Map(desired.map((tool) => [tool.name, tool]));
81
+ const kept = offered.filter((tool) => {
82
+ const next = wanted.get(tool.name);
83
+ return next !== undefined && declarationsEqual(tool, next);
84
+ });
85
+ const keptNames = new Set(kept.map((tool) => tool.name));
86
+ const added = desired.filter((tool) => !keptNames.has(tool.name));
87
+ const replayed = [...kept, ...added];
88
+ if (replayed.some((tool, index) => tool.name !== desired[index].name)) {
89
+ return {
90
+ toolsRemoved: offered.map((tool) => ({ name: tool.name })),
91
+ toolsAdded: desired.map(toToolDeclaration),
92
+ };
93
+ }
94
+ return {
95
+ toolsRemoved: offered.filter((tool) => !keptNames.has(tool.name)).map((tool) => ({ name: tool.name })),
96
+ toolsAdded: added.map(toToolDeclaration),
97
+ };
98
+ }
99
+ /** Section patches: none, the minimal patch, or a remove-all/re-add-all pair when the order would differ. */
100
+ function planSections(shown, desired) {
101
+ const patchedOrder = [
102
+ ...[...shown.keys()].filter((key) => desired.has(key)),
103
+ ...[...desired.keys()].filter((key) => !shown.has(key)),
104
+ ];
105
+ const desiredOrder = [...desired.keys()];
106
+ if (patchedOrder.some((key, index) => key !== desiredOrder[index])) {
107
+ return [Object.fromEntries([...shown.keys()].map((key) => [key, null])), Object.fromEntries(desired)];
108
+ }
109
+ const patch = {};
110
+ for (const [key, value] of shown) {
111
+ const next = desired.get(key);
112
+ if (next !== value)
113
+ patch[key] = next ?? null;
114
+ }
115
+ for (const [key, value] of desired)
116
+ if (!shown.has(key))
117
+ patch[key] = value;
118
+ return Object.keys(patch).length === 0 ? [] : [patch];
119
+ }
120
+ function systemEntry(sections, tools, timestamp) {
121
+ const message = {
122
+ role: "system",
123
+ content: "",
124
+ ...(sections === undefined ? {} : { sections }),
125
+ ...(tools === undefined || tools.toolsRemoved.length === 0 ? {} : { toolsRemoved: tools.toolsRemoved }),
126
+ ...(tools === undefined || tools.toolsAdded.length === 0 ? {} : { toolsAdded: tools.toolsAdded }),
127
+ timestamp,
128
+ };
129
+ return { model: [message] };
130
+ }
131
+ //# sourceMappingURL=prompt.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompt.js","sourceRoot":"","sources":["../../src/harness/prompt.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Message, SystemMessage, Tool, ToolReference } from \"@ny4n/ai/contracts\";\nimport { declarationsEqual, getCurrentTools, toToolDeclaration } from \"@ny4n/ai/utils/transcript\";\nimport type { Context } from \"@ny4n/chord\";\nimport { SystemEntry } from \"../entries.ts\";\nimport type { ContextEdit, TypedEntryDraft } from \"../types.ts\";\nimport type { ContextView, PromptInput, PromptSection, ToolRegistration } from \"./types.ts\";\n\n/** Sections in effect after replaying system messages in order: set in place, `null` deletes, re-adding appends. */\nexport function replaySections(messages: readonly Message[]): Map<string, string> {\n\tconst shown = new Map<string, string>();\n\tfor (const message of messages) {\n\t\tif (message.role !== \"system\" || message.sections === undefined) continue;\n\t\tfor (const [key, value] of Object.entries(message.sections)) {\n\t\t\tif (value === null) shown.delete(key);\n\t\t\telse shown.set(key, value);\n\t\t}\n\t}\n\treturn shown;\n}\n\n/**\n * Render the agent's sections in order. `undefined` omits a section; tagged text is wrapped as `<key>\\n...\\n</key>`. A\n * section that throws keeps its shown text, if any, and is reported; errors after `context` is aborted propagate.\n */\nexport async function renderSections<Tool extends ToolRegistration>(\n\tsections: readonly PromptSection<Tool>[],\n\tinput: PromptInput<Tool>,\n\tshown: ReadonlyMap<string, string>,\n\treport: (error: unknown) => void,\n\tcontext: Context,\n): Promise<Map<string, string>> {\n\tconst desired = new Map<string, string>();\n\tfor (const section of sections) {\n\t\tlet text: string | undefined;\n\t\ttry {\n\t\t\ttext = await section.render(input, context);\n\t\t} catch (error) {\n\t\t\tif (context.abortSignal?.aborted) throw error;\n\t\t\treport(error);\n\t\t\tconst kept = shown.get(section.key);\n\t\t\tif (kept !== undefined) desired.set(section.key, kept);\n\t\t\tcontinue;\n\t\t}\n\t\tif (text === undefined) continue;\n\t\tdesired.set(section.key, section.tag === false ? text : `<${section.key}>\\n${text}\\n</${section.key}>`);\n\t}\n\treturn desired;\n}\n\ntype SystemDraft = TypedEntryDraft<never>;\n\ntype ToolChanges = { readonly toolsRemoved: ToolReference[]; readonly toolsAdded: Tool[] };\n\n/**\n * Plan the `nyan.system` entries that make the replayed sections and tools of `view` equal `desired` and `tools` in values\n * and order.\n *\n * - A head marker with no later `nyan.system` entry in context: one complete baseline that omits every retained earlier\n * `nyan.system` entry, written even when it restates the replayed values.\n * - Otherwise, when a minimal section patch would leave a different order: remove every shown section, then re-add\n * every desired section in order.\n * - Otherwise the minimal patch of changed values and `null` removals, or nothing.\n *\n * Tool changes ride on the last planned entry, or on one entry of their own.\n */\nexport function planSystemEntries(\n\tview: ContextView,\n\tdesired: ReadonlyMap<string, string>,\n\ttools: readonly Tool[],\n\ttimestamp: number,\n): SystemDraft[] {\n\tconst head = view.head;\n\tif (head !== undefined && !view.entries.some((entry) => SystemEntry.is(entry) && entry.id > head.id)) {\n\t\tconst edits: ContextEdit[] = view.entries\n\t\t\t.filter((entry) => SystemEntry.is(entry))\n\t\t\t.map((entry) => ({ target: entry.id, action: \"omit\" }));\n\t\tconst baseline = systemEntry(\n\t\t\tObject.fromEntries(desired),\n\t\t\t{ toolsRemoved: [], toolsAdded: tools.map(toToolDeclaration) },\n\t\t\ttimestamp,\n\t\t);\n\t\treturn [edits.length === 0 ? baseline : { ...baseline, edits }];\n\t}\n\tconst sections = planSections(replaySections(view.messages), desired);\n\tconst changes = planTools(getCurrentTools(view.messages), tools);\n\tif (changes.toolsRemoved.length === 0 && changes.toolsAdded.length === 0) {\n\t\treturn sections.map((patch) => systemEntry(patch, undefined, timestamp));\n\t}\n\tif (sections.length === 0) return [systemEntry(undefined, changes, timestamp)];\n\treturn sections.map((patch, index) =>\n\t\tsystemEntry(patch, index === sections.length - 1 ? changes : undefined, timestamp),\n\t);\n}\n\n/**\n * Tool changes from `offered` to `desired`. A changed declaration is removed and re-added. Replay keeps retained tools\n * in place and appends additions; when that would not yield the desired order, every offered tool is removed and\n * every desired tool re-added in order.\n */\nfunction planTools(offered: readonly Tool[], desired: readonly Tool[]): ToolChanges {\n\tconst wanted = new Map(desired.map((tool) => [tool.name, tool]));\n\tconst kept = offered.filter((tool) => {\n\t\tconst next = wanted.get(tool.name);\n\t\treturn next !== undefined && declarationsEqual(tool, next);\n\t});\n\tconst keptNames = new Set(kept.map((tool) => tool.name));\n\tconst added = desired.filter((tool) => !keptNames.has(tool.name));\n\tconst replayed = [...kept, ...added];\n\tif (replayed.some((tool, index) => tool.name !== desired[index]!.name)) {\n\t\treturn {\n\t\t\ttoolsRemoved: offered.map((tool) => ({ name: tool.name })),\n\t\t\ttoolsAdded: desired.map(toToolDeclaration),\n\t\t};\n\t}\n\treturn {\n\t\ttoolsRemoved: offered.filter((tool) => !keptNames.has(tool.name)).map((tool) => ({ name: tool.name })),\n\t\ttoolsAdded: added.map(toToolDeclaration),\n\t};\n}\n\n/** Section patches: none, the minimal patch, or a remove-all/re-add-all pair when the order would differ. */\nfunction planSections(\n\tshown: ReadonlyMap<string, string>,\n\tdesired: ReadonlyMap<string, string>,\n): Record<string, string | null>[] {\n\tconst patchedOrder = [\n\t\t...[...shown.keys()].filter((key) => desired.has(key)),\n\t\t...[...desired.keys()].filter((key) => !shown.has(key)),\n\t];\n\tconst desiredOrder = [...desired.keys()];\n\tif (patchedOrder.some((key, index) => key !== desiredOrder[index])) {\n\t\treturn [Object.fromEntries([...shown.keys()].map((key) => [key, null])), Object.fromEntries(desired)];\n\t}\n\n\tconst patch: Record<string, string | null> = {};\n\tfor (const [key, value] of shown) {\n\t\tconst next = desired.get(key);\n\t\tif (next !== value) patch[key] = next ?? null;\n\t}\n\tfor (const [key, value] of desired) if (!shown.has(key)) patch[key] = value;\n\treturn Object.keys(patch).length === 0 ? [] : [patch];\n}\n\nfunction systemEntry(\n\tsections: Record<string, string | null> | undefined,\n\ttools: ToolChanges | undefined,\n\ttimestamp: number,\n): SystemDraft {\n\tconst message: SystemMessage = {\n\t\trole: \"system\",\n\t\tcontent: \"\",\n\t\t...(sections === undefined ? {} : { sections }),\n\t\t...(tools === undefined || tools.toolsRemoved.length === 0 ? {} : { toolsRemoved: tools.toolsRemoved }),\n\t\t...(tools === undefined || tools.toolsAdded.length === 0 ? {} : { toolsAdded: tools.toolsAdded }),\n\t\ttimestamp,\n\t};\n\treturn { model: [message] };\n}\n"]}
@@ -0,0 +1,14 @@
1
+ import type { Context } from "@ny4n/chord";
2
+ import type { TaskRuntime } from "../types.ts";
3
+ /** Stable provider-facing identity of one conversation. */
4
+ export type ProviderState = {
5
+ sessionId: string;
6
+ };
7
+ /** Built-in provider state; every fork starts with a fresh identity instead of copying its parent. */
8
+ export declare const ProviderDoc: import("../types.ts").ConversationDocToken<ProviderState>;
9
+ /**
10
+ * Return the persisted identity without writing in the normal path. A legacy conversation without `nyan.provider` gets
11
+ * one migration commit whose `tx.doc()` runs `initial()` before the provider request starts.
12
+ */
13
+ export declare function ensureProviderSessionId<I, S, R, H extends object>(runtime: TaskRuntime<I, S, R, H>, context: Context): Promise<string>;
14
+ //# sourceMappingURL=provider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../../src/harness/provider.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAE3C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE/C,2DAA2D;AAC3D,MAAM,MAAM,aAAa,GAAG;IAC3B,SAAS,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,sGAAsG;AACtG,eAAO,MAAM,WAAW,2DAQtB,CAAC;AAEH;;;GAGG;AACH,wBAAsB,uBAAuB,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,SAAS,MAAM,EACtE,OAAO,EAAE,WAAW,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAChC,OAAO,EAAE,OAAO,GACd,OAAO,CAAC,MAAM,CAAC,CAUjB"}
@@ -0,0 +1,30 @@
1
+ import { uuidv7 } from "@ny4n/ai/utils/uuid";
2
+ import { defineDoc } from "../documents.js";
3
+ /** Built-in provider state; every fork starts with a fresh identity instead of copying its parent. */
4
+ export const ProviderDoc = defineDoc({
5
+ kind: "nyan.provider",
6
+ version: 1,
7
+ scope: "conversation",
8
+ history: "latest",
9
+ fork: "initial",
10
+ initial: () => ({ sessionId: uuidv7() }),
11
+ checkpointWhen: () => true,
12
+ });
13
+ /**
14
+ * Return the persisted identity without writing in the normal path. A legacy conversation without `nyan.provider` gets
15
+ * one migration commit whose `tx.doc()` runs `initial()` before the provider request starts.
16
+ */
17
+ export async function ensureProviderSessionId(runtime, context) {
18
+ const existing = await runtime.snapshot(ProviderDoc, runtime.conversationId, context);
19
+ if (existing !== undefined)
20
+ return existing.sessionId;
21
+ let created;
22
+ await runtime.commit(async (tx) => {
23
+ created = (await tx.doc(ProviderDoc, runtime.conversationId)).sessionId;
24
+ return undefined;
25
+ }, context);
26
+ if (created === undefined)
27
+ throw new Error(`Conversation ${runtime.conversationId} has no provider session ID`);
28
+ return created;
29
+ }
30
+ //# sourceMappingURL=provider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider.js","sourceRoot":"","sources":["../../src/harness/provider.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAE7C,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAQ5C,sGAAsG;AACtG,MAAM,CAAC,MAAM,WAAW,GAAG,SAAS,CAAgB;IACnD,IAAI,EAAE,eAAe;IACrB,OAAO,EAAE,CAAC;IACV,KAAK,EAAE,cAAc;IACrB,OAAO,EAAE,QAAQ;IACjB,IAAI,EAAE,SAAS;IACf,OAAO,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,SAAS,EAAE,MAAM,EAAE,EAAE,CAAC;IACxC,cAAc,EAAE,GAAG,EAAE,CAAC,IAAI;CAC1B,CAAC,CAAC;AAEH;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAC5C,OAAgC,EAChC,OAAgB;IAEhB,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC,cAAc,EAAE,OAAO,CAAC,CAAC;IACtF,IAAI,QAAQ,KAAK,SAAS;QAAE,OAAO,QAAQ,CAAC,SAAS,CAAC;IACtD,IAAI,OAA2B,CAAC;IAChC,MAAM,OAAO,CAAC,MAAM,CAAC,KAAK,EAAE,EAAE,EAAE,EAAE;QACjC,OAAO,GAAG,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,WAAW,EAAE,OAAO,CAAC,cAAc,CAAC,CAAC,CAAC,SAAS,CAAC;QACxE,OAAO,SAAS,CAAC;IAClB,CAAC,EAAE,OAAO,CAAC,CAAC;IACZ,IAAI,OAAO,KAAK,SAAS;QAAE,MAAM,IAAI,KAAK,CAAC,gBAAgB,OAAO,CAAC,cAAc,6BAA6B,CAAC,CAAC;IAChH,OAAO,OAAO,CAAC;AAChB,CAAC","sourcesContent":["import { uuidv7 } from \"@ny4n/ai/utils/uuid\";\nimport type { Context } from \"@ny4n/chord\";\nimport { defineDoc } from \"../documents.ts\";\nimport type { TaskRuntime } from \"../types.ts\";\n\n/** Stable provider-facing identity of one conversation. */\nexport type ProviderState = {\n\tsessionId: string;\n};\n\n/** Built-in provider state; every fork starts with a fresh identity instead of copying its parent. */\nexport const ProviderDoc = defineDoc<ProviderState>({\n\tkind: \"nyan.provider\",\n\tversion: 1,\n\tscope: \"conversation\",\n\thistory: \"latest\",\n\tfork: \"initial\",\n\tinitial: () => ({ sessionId: uuidv7() }),\n\tcheckpointWhen: () => true,\n});\n\n/**\n * Return the persisted identity without writing in the normal path. A legacy conversation without `nyan.provider` gets\n * one migration commit whose `tx.doc()` runs `initial()` before the provider request starts.\n */\nexport async function ensureProviderSessionId<I, S, R, H extends object>(\n\truntime: TaskRuntime<I, S, R, H>,\n\tcontext: Context,\n): Promise<string> {\n\tconst existing = await runtime.snapshot(ProviderDoc, runtime.conversationId, context);\n\tif (existing !== undefined) return existing.sessionId;\n\tlet created: string | undefined;\n\tawait runtime.commit(async (tx) => {\n\t\tcreated = (await tx.doc(ProviderDoc, runtime.conversationId)).sessionId;\n\t\treturn undefined;\n\t}, context);\n\tif (created === undefined) throw new Error(`Conversation ${runtime.conversationId} has no provider session ID`);\n\treturn created;\n}\n"]}
@@ -0,0 +1,6 @@
1
+ import type { AnyTask, Registry, ToolRegistration } from "./types.ts";
2
+ /** Built-in task definitions every registry holds; they are not an extension and cannot be removed or replaced. */
3
+ export declare const BUILTIN_TASKS: readonly AnyTask[];
4
+ /** Create an application-owned registry holding only the built-in tasks. */
5
+ export declare function createRegistry<Tool extends ToolRegistration = ToolRegistration>(): Registry<Tool>;
6
+ //# sourceMappingURL=registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/harness/registry.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,OAAO,EAA4B,QAAQ,EAAoB,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAIlH,mHAAmH;AACnH,eAAO,MAAM,aAAa,EAAE,SAAS,OAAO,EAA+C,CAAC;AAqG5F,4EAA4E;AAC5E,wBAAgB,cAAc,CAAC,IAAI,SAAS,gBAAgB,GAAG,gBAAgB,KAAK,QAAQ,CAAC,IAAI,CAAC,CAEjG"}