@astralyn/pi 1.4.6 → 1.4.7

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 (242) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/dist/bundle/chunks/{chunk-KDO6L76C.js → chunk-FODCTZXW.js} +4 -4
  3. package/dist/bundle/chunks/{chunk-EYKC6ESU.js → chunk-FYKNSJG6.js} +1 -1
  4. package/dist/bundle/chunks/{chunk-AXFWZIHH.js → chunk-MG5XDTXV.js} +1 -1
  5. package/dist/bundle/chunks/{chunk-ALHAFUHP.js → chunk-PU2IWWXV.js} +1 -1
  6. package/dist/bundle/chunks/{chunk-5M3YJ54Z.js → chunk-RO2VH6UZ.js} +1 -1
  7. package/dist/bundle/chunks/{chunk-RBTJBQWX.js → chunk-YJTSOLP2.js} +73 -74
  8. package/dist/bundle/chunks/{cli-FH77R7JR.js → cli-MEL7SGJD.js} +1 -1
  9. package/dist/bundle/chunks/{execute-5ZQUS52M.js → execute-4CWRNHPO.js} +1 -1
  10. package/dist/bundle/chunks/{runtime-R5CMHJ5P.js → runtime-R3X67XMR.js} +1 -1
  11. package/dist/bundle/chunks/{virtual-modules-KCQU7LOC.js → virtual-modules-PKEOKGYI.js} +1 -1
  12. package/dist/bundle/cli.js +1 -1
  13. package/dist/bundle/index.js +1 -1
  14. package/dist/bundle/rpc-entry.js +1 -1
  15. package/dist/core/agent-session.d.ts +0 -3
  16. package/dist/core/agent-session.d.ts.map +1 -1
  17. package/dist/core/agent-session.js +0 -4
  18. package/dist/core/agent-session.js.map +1 -1
  19. package/dist/core/extensions/index.d.ts +1 -1
  20. package/dist/core/extensions/index.d.ts.map +1 -1
  21. package/dist/core/extensions/index.js.map +1 -1
  22. package/dist/core/package-manager.d.ts +7 -1
  23. package/dist/core/package-manager.d.ts.map +1 -1
  24. package/dist/core/package-manager.js +72 -1
  25. package/dist/core/package-manager.js.map +1 -1
  26. package/dist/core/resource-loader.d.ts +1 -0
  27. package/dist/core/resource-loader.d.ts.map +1 -1
  28. package/dist/core/resource-loader.js +23 -7
  29. package/dist/core/resource-loader.js.map +1 -1
  30. package/dist/core/settings-manager.d.ts +4 -2
  31. package/dist/core/settings-manager.d.ts.map +1 -1
  32. package/dist/core/settings-manager.js +11 -0
  33. package/dist/core/settings-manager.js.map +1 -1
  34. package/dist/core/settings-schema.d.ts +7 -0
  35. package/dist/core/settings-schema.d.ts.map +1 -1
  36. package/dist/core/settings-schema.js +6 -0
  37. package/dist/core/settings-schema.js.map +1 -1
  38. package/dist/core/skills.d.ts +10 -0
  39. package/dist/core/skills.d.ts.map +1 -1
  40. package/dist/core/skills.js +23 -0
  41. package/dist/core/skills.js.map +1 -1
  42. package/dist/core/tasks/delivery.d.ts +11 -4
  43. package/dist/core/tasks/delivery.d.ts.map +1 -1
  44. package/dist/core/tasks/delivery.js +9 -9
  45. package/dist/core/tasks/delivery.js.map +1 -1
  46. package/dist/core/tasks/execution.d.ts +10 -3
  47. package/dist/core/tasks/execution.d.ts.map +1 -1
  48. package/dist/core/tasks/execution.js +72 -52
  49. package/dist/core/tasks/execution.js.map +1 -1
  50. package/dist/core/tasks/format.d.ts +0 -2
  51. package/dist/core/tasks/format.d.ts.map +1 -1
  52. package/dist/core/tasks/format.js +1 -16
  53. package/dist/core/tasks/format.js.map +1 -1
  54. package/dist/core/tasks/limits.d.ts +12 -1
  55. package/dist/core/tasks/limits.d.ts.map +1 -1
  56. package/dist/core/tasks/limits.js +20 -1
  57. package/dist/core/tasks/limits.js.map +1 -1
  58. package/dist/core/tasks/output.d.ts +6 -12
  59. package/dist/core/tasks/output.d.ts.map +1 -1
  60. package/dist/core/tasks/output.js +35 -32
  61. package/dist/core/tasks/output.js.map +1 -1
  62. package/dist/core/tasks/record.d.ts +45 -0
  63. package/dist/core/tasks/record.d.ts.map +1 -0
  64. package/dist/core/tasks/record.js +80 -0
  65. package/dist/core/tasks/record.js.map +1 -0
  66. package/dist/core/tasks/runtime.d.ts +14 -14
  67. package/dist/core/tasks/runtime.d.ts.map +1 -1
  68. package/dist/core/tasks/runtime.js +111 -93
  69. package/dist/core/tasks/runtime.js.map +1 -1
  70. package/dist/core/tasks/session.d.ts +0 -1
  71. package/dist/core/tasks/session.d.ts.map +1 -1
  72. package/dist/core/tasks/session.js +7 -8
  73. package/dist/core/tasks/session.js.map +1 -1
  74. package/dist/core/tasks/store.d.ts +18 -25
  75. package/dist/core/tasks/store.d.ts.map +1 -1
  76. package/dist/core/tasks/store.js +42 -40
  77. package/dist/core/tasks/store.js.map +1 -1
  78. package/dist/core/tasks/types.d.ts +96 -17
  79. package/dist/core/tasks/types.d.ts.map +1 -1
  80. package/dist/core/tasks/types.js.map +1 -1
  81. package/dist/core/tools/shell-execution.d.ts +8 -7
  82. package/dist/core/tools/shell-execution.d.ts.map +1 -1
  83. package/dist/core/tools/shell-execution.js +18 -26
  84. package/dist/core/tools/shell-execution.js.map +1 -1
  85. package/dist/core/tools/shell-tool.d.ts.map +1 -1
  86. package/dist/core/tools/shell-tool.js +31 -29
  87. package/dist/core/tools/shell-tool.js.map +1 -1
  88. package/dist/core/usage-totals.d.ts +3 -0
  89. package/dist/core/usage-totals.d.ts.map +1 -1
  90. package/dist/core/usage-totals.js +15 -0
  91. package/dist/core/usage-totals.js.map +1 -1
  92. package/dist/extensions/agent/progress.d.ts.map +1 -1
  93. package/dist/extensions/agent/progress.js +1 -11
  94. package/dist/extensions/agent/progress.js.map +1 -1
  95. package/dist/extensions/agent/tool.d.ts.map +1 -1
  96. package/dist/extensions/agent/tool.js +9 -5
  97. package/dist/extensions/agent/tool.js.map +1 -1
  98. package/dist/extensions/agent/worker.d.ts.map +1 -1
  99. package/dist/extensions/agent/worker.js +3 -5
  100. package/dist/extensions/agent/worker.js.map +1 -1
  101. package/dist/extensions/btw/agent.d.ts +1 -1
  102. package/dist/extensions/btw/agent.d.ts.map +1 -1
  103. package/dist/extensions/btw/agent.js +3 -20
  104. package/dist/extensions/btw/agent.js.map +1 -1
  105. package/dist/extensions/cached-compaction/index.d.ts.map +1 -1
  106. package/dist/extensions/cached-compaction/index.js +3 -22
  107. package/dist/extensions/cached-compaction/index.js.map +1 -1
  108. package/dist/extensions/cached-compaction/render.d.ts +1 -0
  109. package/dist/extensions/cached-compaction/render.d.ts.map +1 -1
  110. package/dist/extensions/cached-compaction/render.js +42 -14
  111. package/dist/extensions/cached-compaction/render.js.map +1 -1
  112. package/dist/extensions/deepwiki/client.d.ts.map +1 -1
  113. package/dist/extensions/deepwiki/client.js +6 -78
  114. package/dist/extensions/deepwiki/client.js.map +1 -1
  115. package/dist/extensions/deepwiki/transport.d.ts +3 -0
  116. package/dist/extensions/deepwiki/transport.d.ts.map +1 -0
  117. package/dist/extensions/deepwiki/transport.js +96 -0
  118. package/dist/extensions/deepwiki/transport.js.map +1 -0
  119. package/dist/extensions/provider/ui/builtin-data.d.ts +3 -3
  120. package/dist/extensions/provider/ui/builtin-data.d.ts.map +1 -1
  121. package/dist/extensions/provider/ui/builtin-data.js.map +1 -1
  122. package/dist/extensions/provider/ui/compat.d.ts +3 -3
  123. package/dist/extensions/provider/ui/compat.d.ts.map +1 -1
  124. package/dist/extensions/provider/ui/compat.js.map +1 -1
  125. package/dist/extensions/provider/ui/controls.d.ts +2 -2
  126. package/dist/extensions/provider/ui/controls.d.ts.map +1 -1
  127. package/dist/extensions/provider/ui/controls.js.map +1 -1
  128. package/dist/extensions/provider/ui/dialogs.d.ts +3 -3
  129. package/dist/extensions/provider/ui/dialogs.d.ts.map +1 -1
  130. package/dist/extensions/provider/ui/dialogs.js.map +1 -1
  131. package/dist/extensions/provider/ui/dictionary.d.ts +2 -2
  132. package/dist/extensions/provider/ui/dictionary.d.ts.map +1 -1
  133. package/dist/extensions/provider/ui/dictionary.js.map +1 -1
  134. package/dist/extensions/provider/ui/editor.d.ts.map +1 -1
  135. package/dist/extensions/provider/ui/editor.js +1 -3
  136. package/dist/extensions/provider/ui/editor.js.map +1 -1
  137. package/dist/extensions/provider/ui/fetch-models.d.ts +2 -2
  138. package/dist/extensions/provider/ui/fetch-models.d.ts.map +1 -1
  139. package/dist/extensions/provider/ui/fetch-models.js.map +1 -1
  140. package/dist/extensions/provider/ui/model-fields.d.ts +2 -2
  141. package/dist/extensions/provider/ui/model-fields.d.ts.map +1 -1
  142. package/dist/extensions/provider/ui/model-fields.js.map +1 -1
  143. package/dist/extensions/provider/ui/model-options.d.ts +6 -6
  144. package/dist/extensions/provider/ui/model-options.d.ts.map +1 -1
  145. package/dist/extensions/provider/ui/model-options.js.map +1 -1
  146. package/dist/extensions/provider/ui/pane.d.ts +21 -12
  147. package/dist/extensions/provider/ui/pane.d.ts.map +1 -1
  148. package/dist/extensions/provider/ui/pane.js.map +1 -1
  149. package/dist/extensions/provider/ui/provider-fields.d.ts +3 -3
  150. package/dist/extensions/provider/ui/provider-fields.d.ts.map +1 -1
  151. package/dist/extensions/provider/ui/provider-fields.js.map +1 -1
  152. package/dist/extensions/provider/ui/thinking-map.d.ts +2 -2
  153. package/dist/extensions/provider/ui/thinking-map.d.ts.map +1 -1
  154. package/dist/extensions/provider/ui/thinking-map.js.map +1 -1
  155. package/dist/extensions/run-summary/format.js +1 -1
  156. package/dist/extensions/run-summary/format.js.map +1 -1
  157. package/dist/extensions/statusline/usage.d.ts.map +1 -1
  158. package/dist/extensions/statusline/usage.js +2 -6
  159. package/dist/extensions/statusline/usage.js.map +1 -1
  160. package/dist/extensions/tasks/tool/actions.d.ts +1 -1
  161. package/dist/extensions/tasks/tool/actions.d.ts.map +1 -1
  162. package/dist/extensions/tasks/tool/actions.js +49 -106
  163. package/dist/extensions/tasks/tool/actions.js.map +1 -1
  164. package/dist/extensions/tasks/tool/constants.d.ts +1 -6
  165. package/dist/extensions/tasks/tool/constants.d.ts.map +1 -1
  166. package/dist/extensions/tasks/tool/constants.js +1 -6
  167. package/dist/extensions/tasks/tool/constants.js.map +1 -1
  168. package/dist/extensions/tasks/tool/schema.d.ts +0 -3
  169. package/dist/extensions/tasks/tool/schema.d.ts.map +1 -1
  170. package/dist/extensions/tasks/tool/schema.js +4 -13
  171. package/dist/extensions/tasks/tool/schema.js.map +1 -1
  172. package/dist/extensions/tasks/tool.d.ts.map +1 -1
  173. package/dist/extensions/tasks/tool.js +2 -2
  174. package/dist/extensions/tasks/tool.js.map +1 -1
  175. package/dist/extensions/tasks/ui/completion-render.d.ts.map +1 -1
  176. package/dist/extensions/tasks/ui/completion-render.js +31 -33
  177. package/dist/extensions/tasks/ui/completion-render.js.map +1 -1
  178. package/dist/extensions/tasks/ui/layout.d.ts +7 -7
  179. package/dist/extensions/tasks/ui/layout.d.ts.map +1 -1
  180. package/dist/extensions/tasks/ui/layout.js +17 -19
  181. package/dist/extensions/tasks/ui/layout.js.map +1 -1
  182. package/dist/extensions/tasks/ui/list.d.ts.map +1 -1
  183. package/dist/extensions/tasks/ui/list.js +2 -3
  184. package/dist/extensions/tasks/ui/list.js.map +1 -1
  185. package/dist/extensions/tasks/ui/manager.d.ts +16 -7
  186. package/dist/extensions/tasks/ui/manager.d.ts.map +1 -1
  187. package/dist/extensions/tasks/ui/manager.js +163 -131
  188. package/dist/extensions/tasks/ui/manager.js.map +1 -1
  189. package/dist/extensions/tasks/ui/model.d.ts +25 -0
  190. package/dist/extensions/tasks/ui/model.d.ts.map +1 -1
  191. package/dist/extensions/tasks/ui/model.js +4 -0
  192. package/dist/extensions/tasks/ui/model.js.map +1 -1
  193. package/dist/extensions/tasks/ui/render.d.ts +1 -1
  194. package/dist/extensions/tasks/ui/render.d.ts.map +1 -1
  195. package/dist/extensions/tasks/ui/render.js +11 -7
  196. package/dist/extensions/tasks/ui/render.js.map +1 -1
  197. package/dist/extensions/tasks/ui/status.d.ts +15 -0
  198. package/dist/extensions/tasks/ui/status.d.ts.map +1 -0
  199. package/dist/extensions/tasks/ui/status.js +16 -0
  200. package/dist/extensions/tasks/ui/status.js.map +1 -0
  201. package/dist/index.d.ts +1 -1
  202. package/dist/index.d.ts.map +1 -1
  203. package/dist/index.js.map +1 -1
  204. package/dist/modes/interactive/components/config-selector.d.ts +14 -1
  205. package/dist/modes/interactive/components/config-selector.d.ts.map +1 -1
  206. package/dist/modes/interactive/components/config-selector.js +258 -7
  207. package/dist/modes/interactive/components/config-selector.js.map +1 -1
  208. package/dist/modes/interactive/components/status-marker.d.ts +2 -1
  209. package/dist/modes/interactive/components/status-marker.d.ts.map +1 -1
  210. package/dist/modes/interactive/components/status-marker.js +0 -1
  211. package/dist/modes/interactive/components/status-marker.js.map +1 -1
  212. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  213. package/dist/modes/interactive/interactive-mode.js +7 -1
  214. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  215. package/dist/utils/duration.d.ts +3 -0
  216. package/dist/utils/duration.d.ts.map +1 -0
  217. package/dist/utils/duration.js +17 -0
  218. package/dist/utils/duration.js.map +1 -0
  219. package/dist/utils/number.d.ts +3 -0
  220. package/dist/utils/number.d.ts.map +1 -0
  221. package/dist/utils/number.js +5 -0
  222. package/dist/utils/number.js.map +1 -0
  223. package/docs/bundled/extensions/agent.md +3 -3
  224. package/docs/bundled/extensions/cached-compaction.md +13 -4
  225. package/docs/bundled/extensions/deepwiki.md +3 -1
  226. package/docs/extensions.md +5 -5
  227. package/docs/packages.md +5 -3
  228. package/docs/sdk.md +1 -1
  229. package/docs/session-format.md +1 -1
  230. package/docs/settings.md +11 -0
  231. package/docs/skills.md +23 -0
  232. package/npm-shrinkwrap.json +2 -2
  233. package/package.json +1 -1
  234. package/schemas/settings.schema.json +36 -0
  235. package/dist/extensions/deepwiki/http.d.ts +0 -13
  236. package/dist/extensions/deepwiki/http.d.ts.map +0 -1
  237. package/dist/extensions/deepwiki/http.js +0 -88
  238. package/dist/extensions/deepwiki/http.js.map +0 -1
  239. package/dist/extensions/tasks/ui/task-view.d.ts +0 -8
  240. package/dist/extensions/tasks/ui/task-view.d.ts.map +0 -1
  241. package/dist/extensions/tasks/ui/task-view.js +0 -13
  242. package/dist/extensions/tasks/ui/task-view.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"output.js","sourceRoot":"","sources":["../../../src/core/tasks/output.ts"],"names":[],"mappings":"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","sourcesContent":["import { open } from \"node:fs/promises\";\nimport type { AgentToolResult } from \"@earendil-works/pi-agent-core\";\nimport type { TaskFact, TaskInput, TaskText } from \"./types.ts\";\n\nexport const TASK_RESULT_BYTES = 48 * 1024;\nexport const TASK_DETAILS_BYTES = 120 * 1024;\nexport const TASK_READ_BYTES = 8 * 1024;\nexport const TASK_INPUT_BYTES = 24 * 1024;\n/** Task title budget, independent of notification wording and rendering. */\nexport const TASK_TITLE_BYTES = 1024;\nexport const TASK_LABEL_BYTES = 256;\nexport const TASK_FACTS = 16;\nexport const TASK_FACT_BYTES = 1024;\nexport const TASK_ERROR_BYTES = 4096;\nexport const TASK_PATH_BYTES = 8192;\n\nexport function finiteLimit(value: number | undefined, fallback: number, ceiling: number): number {\n\treturn value === undefined || !Number.isFinite(value) ? fallback : Math.max(0, Math.min(ceiling, Math.floor(value)));\n}\n\n/** Both ends are codepoint aligned, including a head read ending inside a character. */\nfunction alignedSlice(buffer: Buffer, start: number, end: number): Buffer {\n\twhile (start < end && (buffer[start]! & 0xc0) === 0x80) start++;\n\tif (end < buffer.length) {\n\t\twhile (end > start && (buffer[end]! & 0xc0) === 0x80) end--;\n\t}\n\t// A concurrently written file can itself end in an incomplete UTF-8 sequence.\n\tlet lead = end - 1;\n\twhile (lead >= start && (buffer[lead]! & 0xc0) === 0x80) lead--;\n\tif (lead >= start) {\n\t\tconst byte = buffer[lead]!;\n\t\tconst width = byte >= 0xf0 ? 4 : byte >= 0xe0 ? 3 : byte >= 0xc0 ? 2 : 1;\n\t\tif (end - lead < width) end = lead;\n\t}\n\treturn buffer.subarray(start, end);\n}\n\nexport function boundText(text: string, bytes = TASK_RESULT_BYTES): string {\n\t// Avoid allocating a buffer proportional to an unbounded source.\n\tconst limit = finiteLimit(bytes, TASK_RESULT_BYTES, TASK_RESULT_BYTES);\n\tconst prefix = text.slice(0, limit + 1);\n\tconst buffer = Buffer.from(prefix);\n\treturn alignedSlice(buffer, 0, Math.min(limit, buffer.length)).toString(\"utf8\");\n}\n\nexport function boundTaskText(text: string, bytes = TASK_RESULT_BYTES): TaskText {\n\tconst bounded = boundText(text, bytes);\n\treturn { text: bounded, truncated: bounded !== text };\n}\n\nexport function boundInput(input: TaskInput): TaskInput {\n\tconst text = boundTaskText(input.text, TASK_INPUT_BYTES);\n\treturn {\n\t\tlabel: boundText(input.label, TASK_LABEL_BYTES),\n\t\ttext: text.text,\n\t\ttruncated: input.truncated || text.truncated,\n\t\t...(input.language ? { language: boundText(input.language, 64) } : {}),\n\t};\n}\n\nexport function boundFacts(facts: readonly TaskFact[]): TaskFact[] {\n\treturn facts\n\t\t.slice(0, TASK_FACTS)\n\t\t.map(([label, value]): TaskFact => [boundText(label, TASK_LABEL_BYTES), boundText(value, TASK_FACT_BYTES)]);\n}\n\n/** The end of a bounded text, codepoint aligned: a command's outcome is at its end. */\nexport function boundTail(text: string, bytes: number): { text: string; truncated: boolean } {\n\tconst buffer = Buffer.from(text);\n\tif (buffer.length <= bytes) return { text, truncated: false };\n\treturn { text: alignedSlice(buffer, buffer.length - bytes, buffer.length).toString(\"utf8\"), truncated: true };\n}\n\n/** The bounded model-readable text of a result; images and private details never enter task state. */\nexport function resultText(result: AgentToolResult<unknown>): TaskText {\n\tlet remaining = TASK_RESULT_BYTES;\n\tconst parts: string[] = [];\n\tlet truncated = false;\n\tfor (const block of result.content) {\n\t\tif (remaining <= 0) {\n\t\t\ttruncated = true;\n\t\t\tbreak;\n\t\t}\n\t\tconst source = block.type === \"text\" ? block.text : \"[Image omitted from task output]\";\n\t\tconst text = boundText(source, remaining);\n\t\tif (block.type !== \"text\" || text !== source) truncated = true;\n\t\tparts.push(text);\n\t\tremaining -= Math.max(1, Buffer.byteLength(text) + 1);\n\t}\n\treturn { text: boundText(parts.join(\"\\n\")), truncated };\n}\n\nexport interface OutputSlice {\n\ttext: string;\n\ttotalBytes: number;\n\ttruncated: boolean;\n\tfromByte: number;\n}\n\n/** Positioned, bounded reads only. The executor owns the path and its cleanup. */\nexport async function readOutputSlice(\n\tpath: string,\n\toptions: { mode?: \"head\" | \"tail\"; bytes?: number; sinceBytes?: number } = {},\n): Promise<OutputSlice> {\n\tconst bytes = finiteLimit(options.bytes, TASK_READ_BYTES, TASK_RESULT_BYTES);\n\tconst file = await open(path, \"r\");\n\ttry {\n\t\tconst { size } = await file.stat();\n\t\tconst since = finiteLimit(options.sinceBytes, 0, Number.MAX_SAFE_INTEGER);\n\t\tconst floor = since > size ? 0 : since;\n\t\tconst start =\n\t\t\toptions.sinceBytes !== undefined\n\t\t\t\t? Math.max(floor, size - bytes)\n\t\t\t\t: options.mode === \"head\"\n\t\t\t\t\t? 0\n\t\t\t\t\t: Math.max(0, size - bytes);\n\t\tconst length = Math.min(bytes, size - start);\n\t\t// One extra byte lets alignment distinguish a complete last codepoint.\n\t\tconst buffer = Buffer.alloc(Math.min(length + 1, size - start));\n\t\tconst { bytesRead } = await file.read(buffer, 0, buffer.length, start);\n\t\tconst slice = alignedSlice(buffer.subarray(0, bytesRead), 0, Math.min(length, bytesRead));\n\t\treturn {\n\t\t\ttext: slice.toString(\"utf8\"),\n\t\t\ttotalBytes: size,\n\t\t\ttruncated: options.sinceBytes !== undefined ? start > floor || slice.length < length : slice.length < size,\n\t\t\tfromByte: start + slice.byteOffset - buffer.byteOffset,\n\t\t};\n\t} finally {\n\t\tawait file.close();\n\t}\n}\n\nexport function sliceText(\n\ttext: string,\n\toptions: { mode?: \"head\" | \"tail\"; bytes?: number; sinceBytes?: number } = {},\n): OutputSlice {\n\tconst buffer = Buffer.from(text);\n\tconst bytes = finiteLimit(options.bytes, TASK_READ_BYTES, TASK_RESULT_BYTES);\n\tconst since = finiteLimit(options.sinceBytes, 0, Number.MAX_SAFE_INTEGER);\n\tconst floor = since > buffer.length ? 0 : since;\n\tconst start =\n\t\toptions.sinceBytes !== undefined\n\t\t\t? Math.max(floor, buffer.length - bytes)\n\t\t\t: options.mode === \"head\"\n\t\t\t\t? 0\n\t\t\t\t: Math.max(0, buffer.length - bytes);\n\tconst slice = alignedSlice(buffer, start, Math.min(buffer.length, start + bytes));\n\treturn {\n\t\ttext: slice.toString(\"utf8\"),\n\t\ttotalBytes: buffer.length,\n\t\ttruncated: slice.length < buffer.length - floor,\n\t\tfromByte: slice.byteOffset - buffer.byteOffset,\n\t};\n}\n"]}
1
+ {"version":3,"file":"output.js","sourceRoot":"","sources":["../../../src/core/tasks/output.ts"],"names":[],"mappings":"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","sourcesContent":["import { open } from \"node:fs/promises\";\nimport type { AgentToolResult } from \"@earendil-works/pi-agent-core\";\nimport { finiteLimit } from \"../../utils/number.ts\";\nimport type { TaskFact, TaskInput, TaskReadOptions, TaskText } from \"./types.ts\";\n\nexport const TASK_RESULT_BYTES = 48 * 1024;\nexport const TASK_DETAILS_BYTES = 120 * 1024;\nexport const TASK_READ_BYTES = 8 * 1024;\nexport const TASK_INPUT_BYTES = 24 * 1024;\n/** Task title budget, independent of notification wording and rendering. */\nexport const TASK_TITLE_BYTES = 1024;\nexport const TASK_LABEL_BYTES = 256;\nexport const TASK_FACTS = 16;\nexport const TASK_FACT_BYTES = 1024;\nexport const TASK_ERROR_BYTES = 4096;\nexport const TASK_PATH_BYTES = 8192;\n\n/** A bounded, printable message for any thrown value. */\nexport function errorText(error: unknown, fallback = \"Task execution failed (unprintable error)\"): string {\n\ttry {\n\t\treturn boundText(error instanceof Error ? error.message : String(error), TASK_ERROR_BYTES);\n\t} catch {\n\t\treturn fallback;\n\t}\n}\n\nexport function boundReadBytes(bytes: number | undefined): number {\n\treturn finiteLimit(bytes, TASK_READ_BYTES, TASK_RESULT_BYTES);\n}\n\n/** Both ends are codepoint aligned, including a head read ending inside a character. */\nfunction alignedSlice(buffer: Buffer, start: number, end: number): Buffer {\n\twhile (start < end && (buffer[start]! & 0xc0) === 0x80) start++;\n\tif (end < buffer.length) {\n\t\twhile (end > start && (buffer[end]! & 0xc0) === 0x80) end--;\n\t}\n\t// A concurrently written file can itself end in an incomplete UTF-8 sequence.\n\tlet lead = end - 1;\n\twhile (lead >= start && (buffer[lead]! & 0xc0) === 0x80) lead--;\n\tif (lead >= start) {\n\t\tconst byte = buffer[lead]!;\n\t\tconst width = byte >= 0xf0 ? 4 : byte >= 0xe0 ? 3 : byte >= 0xc0 ? 2 : 1;\n\t\tif (end - lead < width) end = lead;\n\t}\n\treturn buffer.subarray(start, end);\n}\n\nexport function boundText(text: string, bytes = TASK_RESULT_BYTES): string {\n\t// Avoid allocating a buffer proportional to an unbounded source.\n\tconst limit = finiteLimit(bytes, TASK_RESULT_BYTES, TASK_RESULT_BYTES);\n\tconst prefix = text.slice(0, limit + 1);\n\tconst buffer = Buffer.from(prefix);\n\treturn alignedSlice(buffer, 0, Math.min(limit, buffer.length)).toString(\"utf8\");\n}\n\nexport function boundTaskText(text: string, bytes = TASK_RESULT_BYTES): TaskText {\n\tconst bounded = boundText(text, bytes);\n\treturn { text: bounded, truncated: bounded !== text };\n}\n\nexport function boundInput(input: TaskInput): TaskInput {\n\tconst text = boundTaskText(input.text, TASK_INPUT_BYTES);\n\treturn {\n\t\tlabel: boundText(input.label, TASK_LABEL_BYTES),\n\t\ttext: text.text,\n\t\ttruncated: input.truncated || text.truncated,\n\t\t...(input.language ? { language: boundText(input.language, 64) } : {}),\n\t};\n}\n\nexport function boundFacts(facts: readonly TaskFact[]): TaskFact[] {\n\treturn facts\n\t\t.slice(0, TASK_FACTS)\n\t\t.map(([label, value]): TaskFact => [boundText(label, TASK_LABEL_BYTES), boundText(value, TASK_FACT_BYTES)]);\n}\n\n/** The end of a bounded text, codepoint aligned: a command's outcome is at its end. */\nexport function boundTail(text: string, bytes: number): { text: string; truncated: boolean } {\n\tconst buffer = Buffer.from(text);\n\tif (buffer.length <= bytes) return { text, truncated: false };\n\treturn { text: alignedSlice(buffer, buffer.length - bytes, buffer.length).toString(\"utf8\"), truncated: true };\n}\n\n/** The bounded model-readable text of a result; images and private details never enter task state. */\nexport function resultText(result: AgentToolResult<unknown>): TaskText {\n\tlet remaining = TASK_RESULT_BYTES;\n\tconst parts: string[] = [];\n\tlet truncated = false;\n\tfor (const block of result.content) {\n\t\tif (remaining <= 0) {\n\t\t\ttruncated = true;\n\t\t\tbreak;\n\t\t}\n\t\tconst source = block.type === \"text\" ? block.text : \"[Image omitted from task output]\";\n\t\tconst text = boundText(source, remaining);\n\t\tif (block.type !== \"text\" || text !== source) truncated = true;\n\t\tparts.push(text);\n\t\tremaining -= Math.max(1, Buffer.byteLength(text) + 1);\n\t}\n\treturn { text: boundText(parts.join(\"\\n\")), truncated };\n}\n\nexport interface OutputSlice {\n\ttext: string;\n\ttotalBytes: number;\n\ttruncated: boolean;\n\tfromByte: number;\n}\n\n/**\n * The byte window a read covers in an output of `size` bytes. `floor` is the first byte the\n * caller has not seen yet; anything before it never counts as truncated.\n */\nfunction readWindow(size: number, options: TaskReadOptions): { start: number; length: number; floor: number } {\n\tconst bytes = boundReadBytes(options.bytes);\n\tconst since = finiteLimit(options.sinceBytes, 0, Number.MAX_SAFE_INTEGER);\n\tconst floor = since > size ? 0 : since;\n\tconst start =\n\t\toptions.sinceBytes !== undefined\n\t\t\t? Math.max(floor, size - bytes)\n\t\t\t: options.mode === \"head\"\n\t\t\t\t? 0\n\t\t\t\t: Math.max(0, size - bytes);\n\treturn { start, length: Math.min(bytes, size - start), floor };\n}\n\nfunction outputSlice(size: number, floor: number, slice: Buffer, fromByte: number): OutputSlice {\n\treturn { text: slice.toString(\"utf8\"), totalBytes: size, truncated: slice.length < size - floor, fromByte };\n}\n\n/** Positioned, bounded reads only. The executor owns the path and its cleanup. */\nexport async function readOutputSlice(path: string, options: TaskReadOptions = {}): Promise<OutputSlice> {\n\tconst file = await open(path, \"r\");\n\ttry {\n\t\tconst { size } = await file.stat();\n\t\tconst { start, length, floor } = readWindow(size, options);\n\t\t// One extra byte lets alignment distinguish a complete last codepoint.\n\t\tconst buffer = Buffer.alloc(Math.min(length + 1, size - start));\n\t\tconst { bytesRead } = await file.read(buffer, 0, buffer.length, start);\n\t\tconst slice = alignedSlice(buffer.subarray(0, bytesRead), 0, Math.min(length, bytesRead));\n\t\treturn outputSlice(size, floor, slice, start + slice.byteOffset - buffer.byteOffset);\n\t} finally {\n\t\tawait file.close();\n\t}\n}\n\nexport function sliceText(text: string, options: TaskReadOptions = {}): OutputSlice {\n\tconst buffer = Buffer.from(text);\n\tconst { start, length, floor } = readWindow(buffer.length, options);\n\tconst slice = alignedSlice(buffer, start, start + length);\n\treturn outputSlice(buffer.length, floor, slice, slice.byteOffset - buffer.byteOffset);\n}\n"]}
@@ -0,0 +1,45 @@
1
+ import type { TaskSnapshot } from "./types.ts";
2
+ /**
3
+ * none: running, or answered inline by its tool call; pending: a settled handoff
4
+ * awaiting automatic delivery; claimed: handed to the host; delivered: acknowledged.
5
+ */
6
+ export type TaskDeliveryState = "none" | "pending" | "claimed" | "delivered";
7
+ /**
8
+ * The runtime's state for one task: its working snapshot, retention counters and delivery
9
+ * state. The working snapshot changes only through `update()`, which also drops the frozen
10
+ * copy that readers share, so a mutation can never leave a stale snapshot behind.
11
+ */
12
+ export declare class TaskRecord {
13
+ /** Read-only view of the working copy; change it with update(). */
14
+ readonly task: Readonly<TaskSnapshot>;
15
+ settled: boolean;
16
+ /** Whether the launch anchor is on the selected branch. */
17
+ visible: boolean;
18
+ pins: number;
19
+ deliveryHolds: number;
20
+ cleanup?: () => void | Promise<void>;
21
+ readError?: string;
22
+ readonly waiters: Set<() => void>;
23
+ private readonly working;
24
+ private frozen?;
25
+ private state;
26
+ constructor(task: TaskSnapshot);
27
+ /** A terminal record rebuilt from persisted history: nothing is left to run or deliver. */
28
+ static restored(task: TaskSnapshot, readError: string | undefined): TaskRecord;
29
+ update(mutate: (task: TaskSnapshot) => void): void;
30
+ /** Readers share one frozen copy, so polling and status rendering never clone unchanged output. */
31
+ snapshot(): TaskSnapshot;
32
+ get delivery(): TaskDeliveryState;
33
+ /** A settled handoff the host has not yet been told about. */
34
+ queueDelivery(): void;
35
+ /** The result was answered inline, or the host acknowledged its delivery. */
36
+ acknowledge(): void;
37
+ /** Only explicit result-delivery holds and active waits delay an automatic completion. */
38
+ get deliverable(): boolean;
39
+ claim(): boolean;
40
+ /** Return a claimed record to pending after a failed delivery. */
41
+ unclaim(): boolean;
42
+ /** Delivered, unpinned and unwatched: bounded history that retention may evict. */
43
+ get inHistory(): boolean;
44
+ }
45
+ //# sourceMappingURL=record.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"record.d.ts","sourceRoot":"","sources":["../../../src/core/tasks/record.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE/C;;;GAGG;AACH,MAAM,MAAM,iBAAiB,GAAG,MAAM,GAAG,SAAS,GAAG,SAAS,GAAG,WAAW,CAAC;AAU7E;;;;GAIG;AACH,qBAAa,UAAU;IACtB,mEAAmE;IACnE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC,YAAY,CAAC,CAAC;IACtC,OAAO,UAAS;IAChB,2DAA2D;IAC3D,OAAO,UAAQ;IACf,IAAI,SAAK;IACT,aAAa,SAAK;IAClB,OAAO,CAAC,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACrC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,OAAO,YAAiB,IAAI,EAAI;IACzC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAe;IACvC,OAAO,CAAC,MAAM,CAAC,CAAe;IAC9B,OAAO,CAAC,KAAK,CAA6B;gBAE9B,IAAI,EAAE,YAAY;IAK9B,2FAA2F;IAC3F,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,GAAG,SAAS,GAAG,UAAU;IAQ9E,MAAM,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,YAAY,KAAK,IAAI,GAAG,IAAI;IAQlD,mGAAmG;IACnG,QAAQ,IAAI,YAAY;IAKxB,IAAI,QAAQ,IAAI,iBAAiB,CAEhC;IAED,8DAA8D;IAC9D,aAAa,IAAI,IAAI;IAIrB,6EAA6E;IAC7E,WAAW,IAAI,IAAI;IAInB,0FAA0F;IAC1F,IAAI,WAAW,IAAI,OAAO,CAEzB;IAED,KAAK,IAAI,OAAO;IAMhB,kEAAkE;IAClE,OAAO,IAAI,OAAO;IAMlB,mFAAmF;IACnF,IAAI,SAAS,IAAI,OAAO,CAEvB;CACD"}
@@ -0,0 +1,80 @@
1
+ function freeze(value) {
2
+ if (value !== null && typeof value === "object" && !Object.isFrozen(value)) {
3
+ for (const child of Object.values(value))
4
+ freeze(child);
5
+ Object.freeze(value);
6
+ }
7
+ return value;
8
+ }
9
+ /**
10
+ * The runtime's state for one task: its working snapshot, retention counters and delivery
11
+ * state. The working snapshot changes only through `update()`, which also drops the frozen
12
+ * copy that readers share, so a mutation can never leave a stale snapshot behind.
13
+ */
14
+ export class TaskRecord {
15
+ constructor(task) {
16
+ this.settled = false;
17
+ /** Whether the launch anchor is on the selected branch. */
18
+ this.visible = true;
19
+ this.pins = 0;
20
+ this.deliveryHolds = 0;
21
+ this.waiters = new Set();
22
+ this.state = "none";
23
+ this.working = task;
24
+ this.task = task;
25
+ }
26
+ /** A terminal record rebuilt from persisted history: nothing is left to run or deliver. */
27
+ static restored(task, readError) {
28
+ const record = new TaskRecord(task);
29
+ record.settled = true;
30
+ record.state = "delivered";
31
+ record.readError = readError;
32
+ return record;
33
+ }
34
+ update(mutate) {
35
+ try {
36
+ mutate(this.working);
37
+ }
38
+ finally {
39
+ this.frozen = undefined;
40
+ }
41
+ }
42
+ /** Readers share one frozen copy, so polling and status rendering never clone unchanged output. */
43
+ snapshot() {
44
+ this.frozen ??= freeze(structuredClone(this.working));
45
+ return this.frozen;
46
+ }
47
+ get delivery() {
48
+ return this.state;
49
+ }
50
+ /** A settled handoff the host has not yet been told about. */
51
+ queueDelivery() {
52
+ this.state = "pending";
53
+ }
54
+ /** The result was answered inline, or the host acknowledged its delivery. */
55
+ acknowledge() {
56
+ this.state = "delivered";
57
+ }
58
+ /** Only explicit result-delivery holds and active waits delay an automatic completion. */
59
+ get deliverable() {
60
+ return this.visible && this.state === "pending" && this.waiters.size === 0 && this.deliveryHolds === 0;
61
+ }
62
+ claim() {
63
+ if (!this.deliverable)
64
+ return false;
65
+ this.state = "claimed";
66
+ return true;
67
+ }
68
+ /** Return a claimed record to pending after a failed delivery. */
69
+ unclaim() {
70
+ if (this.state !== "claimed")
71
+ return false;
72
+ this.state = "pending";
73
+ return true;
74
+ }
75
+ /** Delivered, unpinned and unwatched: bounded history that retention may evict. */
76
+ get inHistory() {
77
+ return this.settled && this.state === "delivered" && !this.pins && !this.waiters.size;
78
+ }
79
+ }
80
+ //# sourceMappingURL=record.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"record.js","sourceRoot":"","sources":["../../../src/core/tasks/record.ts"],"names":[],"mappings":"AAQA,SAAS,MAAM,CAAI,KAAQ;IAC1B,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QAC5E,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC;YAAE,MAAM,CAAC,KAAK,CAAC,CAAC;QACxD,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACtB,CAAC;IACD,OAAO,KAAK,CAAC;AACd,CAAC;AAED;;;;GAIG;AACH,MAAM,OAAO,UAAU;IAetB,YAAY,IAAkB;QAZ9B,YAAO,GAAG,KAAK,CAAC;QAChB,2DAA2D;QAC3D,YAAO,GAAG,IAAI,CAAC;QACf,SAAI,GAAG,CAAC,CAAC;QACT,kBAAa,GAAG,CAAC,CAAC;QAGT,YAAO,GAAG,IAAI,GAAG,EAAc,CAAC;QAGjC,UAAK,GAAsB,MAAM,CAAC;QAGzC,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;QACpB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IAClB,CAAC;IAED,2FAA2F;IAC3F,MAAM,CAAC,QAAQ,CAAC,IAAkB,EAAE,SAA6B;QAChE,MAAM,MAAM,GAAG,IAAI,UAAU,CAAC,IAAI,CAAC,CAAC;QACpC,MAAM,CAAC,OAAO,GAAG,IAAI,CAAC;QACtB,MAAM,CAAC,KAAK,GAAG,WAAW,CAAC;QAC3B,MAAM,CAAC,SAAS,GAAG,SAAS,CAAC;QAC7B,OAAO,MAAM,CAAC;IACf,CAAC;IAED,MAAM,CAAC,MAAoC;QAC1C,IAAI,CAAC;YACJ,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACtB,CAAC;gBAAS,CAAC;YACV,IAAI,CAAC,MAAM,GAAG,SAAS,CAAC;QACzB,CAAC;IACF,CAAC;IAED,mGAAmG;IACnG,QAAQ;QACP,IAAI,CAAC,MAAM,KAAK,MAAM,CAAC,eAAe,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC;QACtD,OAAO,IAAI,CAAC,MAAM,CAAC;IACpB,CAAC;IAED,IAAI,QAAQ;QACX,OAAO,IAAI,CAAC,KAAK,CAAC;IACnB,CAAC;IAED,8DAA8D;IAC9D,aAAa;QACZ,IAAI,CAAC,KAAK,GAAG,SAAS,CAAC;IACxB,CAAC;IAED,6EAA6E;IAC7E,WAAW;QACV,IAAI,CAAC,KAAK,GAAG,WAAW,CAAC;IAC1B,CAAC;IAED,0FAA0F;IAC1F,IAAI,WAAW;QACd,OAAO,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,KAAK,KAAK,SAAS,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,KAAK,CAAC,IAAI,IAAI,CAAC,aAAa,KAAK,CAAC,CAAC;IACxG,CAAC;IAED,KAAK;QACJ,IAAI,CAAC,IAAI,CAAC,WAAW;YAAE,OAAO,KAAK,CAAC;QACpC,IAAI,CAAC,KAAK,GAAG,SAAS,CAAC;QACvB,OAAO,IAAI,CAAC;IACb,CAAC;IAED,kEAAkE;IAClE,OAAO;QACN,IAAI,IAAI,CAAC,KAAK,KAAK,SAAS;YAAE,OAAO,KAAK,CAAC;QAC3C,IAAI,CAAC,KAAK,GAAG,SAAS,CAAC;QACvB,OAAO,IAAI,CAAC;IACb,CAAC;IAED,mFAAmF;IACnF,IAAI,SAAS;QACZ,OAAO,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,KAAK,KAAK,WAAW,IAAI,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC;IACvF,CAAC;CACD","sourcesContent":["import type { TaskSnapshot } from \"./types.ts\";\n\n/**\n * none: running, or answered inline by its tool call; pending: a settled handoff\n * awaiting automatic delivery; claimed: handed to the host; delivered: acknowledged.\n */\nexport type TaskDeliveryState = \"none\" | \"pending\" | \"claimed\" | \"delivered\";\n\nfunction freeze<T>(value: T): T {\n\tif (value !== null && typeof value === \"object\" && !Object.isFrozen(value)) {\n\t\tfor (const child of Object.values(value)) freeze(child);\n\t\tObject.freeze(value);\n\t}\n\treturn value;\n}\n\n/**\n * The runtime's state for one task: its working snapshot, retention counters and delivery\n * state. The working snapshot changes only through `update()`, which also drops the frozen\n * copy that readers share, so a mutation can never leave a stale snapshot behind.\n */\nexport class TaskRecord {\n\t/** Read-only view of the working copy; change it with update(). */\n\treadonly task: Readonly<TaskSnapshot>;\n\tsettled = false;\n\t/** Whether the launch anchor is on the selected branch. */\n\tvisible = true;\n\tpins = 0;\n\tdeliveryHolds = 0;\n\tcleanup?: () => void | Promise<void>;\n\treadError?: string;\n\treadonly waiters = new Set<() => void>();\n\tprivate readonly working: TaskSnapshot;\n\tprivate frozen?: TaskSnapshot;\n\tprivate state: TaskDeliveryState = \"none\";\n\n\tconstructor(task: TaskSnapshot) {\n\t\tthis.working = task;\n\t\tthis.task = task;\n\t}\n\n\t/** A terminal record rebuilt from persisted history: nothing is left to run or deliver. */\n\tstatic restored(task: TaskSnapshot, readError: string | undefined): TaskRecord {\n\t\tconst record = new TaskRecord(task);\n\t\trecord.settled = true;\n\t\trecord.state = \"delivered\";\n\t\trecord.readError = readError;\n\t\treturn record;\n\t}\n\n\tupdate(mutate: (task: TaskSnapshot) => void): void {\n\t\ttry {\n\t\t\tmutate(this.working);\n\t\t} finally {\n\t\t\tthis.frozen = undefined;\n\t\t}\n\t}\n\n\t/** Readers share one frozen copy, so polling and status rendering never clone unchanged output. */\n\tsnapshot(): TaskSnapshot {\n\t\tthis.frozen ??= freeze(structuredClone(this.working));\n\t\treturn this.frozen;\n\t}\n\n\tget delivery(): TaskDeliveryState {\n\t\treturn this.state;\n\t}\n\n\t/** A settled handoff the host has not yet been told about. */\n\tqueueDelivery(): void {\n\t\tthis.state = \"pending\";\n\t}\n\n\t/** The result was answered inline, or the host acknowledged its delivery. */\n\tacknowledge(): void {\n\t\tthis.state = \"delivered\";\n\t}\n\n\t/** Only explicit result-delivery holds and active waits delay an automatic completion. */\n\tget deliverable(): boolean {\n\t\treturn this.visible && this.state === \"pending\" && this.waiters.size === 0 && this.deliveryHolds === 0;\n\t}\n\n\tclaim(): boolean {\n\t\tif (!this.deliverable) return false;\n\t\tthis.state = \"claimed\";\n\t\treturn true;\n\t}\n\n\t/** Return a claimed record to pending after a failed delivery. */\n\tunclaim(): boolean {\n\t\tif (this.state !== \"claimed\") return false;\n\t\tthis.state = \"pending\";\n\t\treturn true;\n\t}\n\n\t/** Delivered, unpinned and unwatched: bounded history that retention may evict. */\n\tget inHistory(): boolean {\n\t\treturn this.settled && this.state === \"delivered\" && !this.pins && !this.waiters.size;\n\t}\n}\n"]}
@@ -1,7 +1,7 @@
1
1
  import type { AgentToolResult } from "@earendil-works/pi-agent-core";
2
- import { type TaskExecution, type TaskRead, type TaskRuntimeOptions, type TaskSnapshot, type TasksContext, type TaskToolOutcome } from "./types.ts";
2
+ import { type TaskDeliveryLedger, type TaskExecution, type TaskRead, type TaskReadOptions, type TaskRuntimeOptions, type TaskSnapshot, type TasksContext, type TaskToolOutcome, type TaskWaitAnyOptions, type TaskWaitAnyResult, type TaskWaitOptions, type TaskWaitResult } from "./types.ts";
3
3
  /** Session-local supervision. Executors own their processes, items and output files. */
4
- export declare class TaskRuntime implements TasksContext {
4
+ export declare class TaskRuntime implements TasksContext, TaskDeliveryLedger {
5
5
  private readonly store;
6
6
  private readonly executions;
7
7
  private readonly scheduler;
@@ -13,7 +13,6 @@ export declare class TaskRuntime implements TasksContext {
13
13
  private readonly options;
14
14
  private configuredEnabled;
15
15
  private _closed;
16
- private pauses;
17
16
  constructor(options?: TaskRuntimeOptions);
18
17
  get closed(): boolean;
19
18
  get session(): TaskRuntimeOptions["session"];
@@ -28,6 +27,7 @@ export declare class TaskRuntime implements TasksContext {
28
27
  get enabled(): boolean;
29
28
  setEnabled(enabled: boolean): void;
30
29
  /** Foreground work is bounded by its caller; only background work, queued or running, holds a slot. */
30
+ private activeBackground;
31
31
  private backgroundActive;
32
32
  private limitError;
33
33
  execute<T>(execution: TaskExecution<T>): Promise<TaskToolOutcome<T>>;
@@ -38,17 +38,18 @@ export declare class TaskRuntime implements TasksContext {
38
38
  list(): TaskSnapshot[];
39
39
  private lookup;
40
40
  get(id: string): TaskSnapshot;
41
- read(id: string, options?: {
42
- mode?: "head" | "tail";
43
- bytes?: number;
44
- sinceBytes?: number;
45
- }): Promise<TaskRead>;
41
+ read(id: string, options?: TaskReadOptions): Promise<TaskRead>;
46
42
  private resultText;
47
43
  /** One event-driven waiter can observe a whole waitAny cohort without per-task timers. */
48
44
  private waitForRecords;
49
- /** Observe completion without consuming delivery; the host acknowledges only persisted tool results. */
50
- wait(id: string, timeoutMs?: number, signal?: AbortSignal, interruptSignal?: AbortSignal): Promise<TaskSnapshot>;
51
- waitAny(timeoutMs?: number, signal?: AbortSignal, interruptSignal?: AbortSignal): Promise<TaskSnapshot | undefined>;
45
+ /**
46
+ * One observation: hold delivery for the observed records, wait, read, then register the
47
+ * receipt with the host, and only then release. Nothing the caller does between those steps
48
+ * can leak a settled result into an automatic notification or lose it.
49
+ */
50
+ private observe;
51
+ wait(id: string, options?: TaskWaitOptions): Promise<TaskWaitResult>;
52
+ waitAny(options?: TaskWaitAnyOptions): Promise<TaskWaitAnyResult>;
52
53
  kill(id: string): boolean;
53
54
  private cancel;
54
55
  subscribe(listener: () => void): () => void;
@@ -56,11 +57,10 @@ export declare class TaskRuntime implements TasksContext {
56
57
  * Retention protects snapshots and logs only. Delivery has its own explicit hold.
57
58
  */
58
59
  retain(id: string): () => void;
60
+ private pin;
59
61
  holdDelivery(id: string): () => void;
60
- pause(): () => void;
62
+ private holdRecord;
61
63
  pendingNotifications(): TaskSnapshot[];
62
- /** Only explicit result-delivery holds delay an automatic completion. */
63
- private candidate;
64
64
  claimNotification(id: string): boolean;
65
65
  markDelivered(id: string): void;
66
66
  releaseNotification(id: string): void;
@@ -1 +1 @@
1
- {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../../../src/core/tasks/runtime.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAerE,OAAO,EAGN,KAAK,aAAa,EAElB,KAAK,QAAQ,EACb,KAAK,kBAAkB,EACvB,KAAK,YAAY,EACjB,KAAK,YAAY,EACjB,KAAK,eAAe,EACpB,MAAM,YAAY,CAAC;AAEpB,wFAAwF;AACxF,qBAAa,WAAY,YAAW,YAAY;IAC/C,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAY;IAClC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAyC;IACpE,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAuB;IACjD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAyB;IACnD,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAgB;IACrC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IACvC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAqB;IAC7C,OAAO,CAAC,iBAAiB,CAAU;IACnC,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,MAAM,CAAK;gBAEP,OAAO,GAAE,kBAAuB;IAwB5C,IAAI,MAAM,IAAI,OAAO,CAEpB;IAED,IAAI,OAAO,IAAI,kBAAkB,CAAC,SAAS,CAAC,CAE3C;IAED;;;;;;OAMG;IACH,cAAc,CACb,OAAO,EAAE,SAAS,OAAO,EAAE,EAC3B,WAAW,CAAC,EAAE,WAAW,CAAC,MAAM,EAAE,eAAe,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,CAAC,GACpE,IAAI;IAIP,IAAI,OAAO,IAAI,OAAO,CAErB;IAED,UAAU,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI;IAKlC,uGAAuG;IACvG,OAAO,CAAC,gBAAgB;IAOxB,OAAO,CAAC,UAAU;IAMZ,OAAO,CAAC,CAAC,EAAE,SAAS,EAAE,aAAa,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;IAqD1E,OAAO,CAAC,UAAU;IAIlB,gBAAgB,IAAI,MAAM;IAe1B,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO;IAK9B,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO;IAW3B,IAAI,IAAI,YAAY,EAAE;IAItB,OAAO,CAAC,MAAM;IAYd,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,YAAY;IAIvB,IAAI,CACT,EAAE,EAAE,MAAM,EACV,OAAO,GAAE;QAAE,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAO,GAC3E,OAAO,CAAC,QAAQ,CAAC;IAsBpB,OAAO,CAAC,UAAU;IAIlB,0FAA0F;IAC1F,OAAO,CAAC,cAAc;IAkCtB,wGAAwG;IAClG,IAAI,CACT,EAAE,EAAE,MAAM,EACV,SAAS,SAAwB,EACjC,MAAM,CAAC,EAAE,WAAW,EACpB,eAAe,CAAC,EAAE,WAAW,GAC3B,OAAO,CAAC,YAAY,CAAC;IAQlB,OAAO,CACZ,SAAS,SAAwB,EACjC,MAAM,CAAC,EAAE,WAAW,EACpB,eAAe,CAAC,EAAE,WAAW,GAC3B,OAAO,CAAC,YAAY,GAAG,SAAS,CAAC;IAapC,IAAI,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO;IAIzB,OAAO,CAAC,MAAM;IAId,SAAS,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI;IAQ3C;;OAEG;IACH,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,IAAI;IAa9B,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,IAAI;IAapC,KAAK,IAAI,MAAM,IAAI;IAWnB,oBAAoB,IAAI,YAAY,EAAE;IAKtC,yEAAyE;IACzE,OAAO,CAAC,SAAS;IAIjB,iBAAiB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO;IAQtC,aAAa,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI;IAO/B,mBAAmB,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI;IAQrC,KAAK,IAAI,IAAI;IAYP,QAAQ,CAAC,OAAO,SAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAK7C,mFAAmF;IACnF,mBAAmB,CAAC,SAAS,EAAE,WAAW,CAAC,MAAM,CAAC,GAAG,YAAY,EAAE;IAMnE;;;OAGG;IACG,mBAAmB,CAAC,SAAS,EAAE,WAAW,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;YAU1D,KAAK;IAgBnB,OAAO,CAAC,IAAI;CAWZ"}
1
+ {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../../../src/core/tasks/runtime.ts"],"names":[],"mappings":"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"}
@@ -1,9 +1,11 @@
1
1
  import { randomUUID } from "node:crypto";
2
- import { Execution, errorText } from "./execution.js";
3
- import { TASKS_WAIT_DEFAULT_MS, TASKS_WAIT_MAX_MS } from "./limits.js";
4
- import { boundFacts, boundInput, boundText, finiteLimit, readOutputSlice, sliceText, TASK_LABEL_BYTES, TASK_TITLE_BYTES, } from "./output.js";
2
+ import { finiteLimit } from "../../utils/number.js";
3
+ import { Execution } from "./execution.js";
4
+ import { boundWaitMs, TASKS_WAIT_DEFAULT_MS } from "./limits.js";
5
+ import { boundFacts, boundInput, boundText, errorText, readOutputSlice, sliceText, TASK_LABEL_BYTES, TASK_TITLE_BYTES, } from "./output.js";
6
+ import { TaskRecord } from "./record.js";
5
7
  import { TaskScheduler } from "./scheduler.js";
6
- import { snapshotOf, TaskStore } from "./store.js";
8
+ import { TaskStore } from "./store.js";
7
9
  import { TASK_BACKGROUND_REJECTION, TASK_KIND_PATTERN, TaskLookupError, } from "./types.js";
8
10
  /** Session-local supervision. Executors own their processes, items and output files. */
9
11
  export class TaskRuntime {
@@ -12,7 +14,6 @@ export class TaskRuntime {
12
14
  this.scheduler = new TaskScheduler();
13
15
  this.listeners = new Set();
14
16
  this._closed = false;
15
- this.pauses = 0;
16
17
  this.options = options;
17
18
  this.configuredEnabled = options.enabled ?? false;
18
19
  this.maxBackground = Math.max(1, finiteLimit(options.maxBackground, 8, 128));
@@ -59,12 +60,11 @@ export class TaskRuntime {
59
60
  this.emit();
60
61
  }
61
62
  /** Foreground work is bounded by its caller; only background work, queued or running, holds a slot. */
63
+ activeBackground() {
64
+ return this.store.unsettled().filter((record) => record.task.mode === "background");
65
+ }
62
66
  backgroundActive() {
63
- let count = 0;
64
- for (const record of this.store.records.values())
65
- if (!record.settled && record.task.mode === "background")
66
- count++;
67
- return count;
67
+ return this.activeBackground().length;
68
68
  }
69
69
  limitError() {
70
70
  return new Error(`Background task limit reached (${this.maxBackground}); stop or wait for a background task, or run in the foreground.`);
@@ -80,7 +80,7 @@ export class TaskRuntime {
80
80
  throw new Error("Background execution is not available in this host");
81
81
  if (this.backgroundActive() >= this.maxBackground)
82
82
  throw this.limitError();
83
- if (this.store.records.size + this.store.cleanups.size >= this.maxRetained) {
83
+ if (this.store.retainedCount() >= this.maxRetained) {
84
84
  throw new Error("Task history retention limit reached; deliver pending notifications or release pinned or claimed records");
85
85
  }
86
86
  }
@@ -108,17 +108,9 @@ export class TaskRuntime {
108
108
  status: "queued",
109
109
  startedAt: Date.now(),
110
110
  };
111
- const record = {
112
- task,
113
- settled: false,
114
- visible: true,
115
- delivery: "none",
116
- pins: 0,
117
- deliveryHolds: 0,
118
- waiters: new Set(),
119
- };
111
+ const record = new TaskRecord(task);
120
112
  const active = new Execution(this.host, record, execution);
121
- this.store.records.set(task.id, record);
113
+ this.store.add(record);
122
114
  this.executions.set(task.id, active);
123
115
  this.scheduler.schedule(active);
124
116
  // Queued background work returns at once; started work hands off after its own accept().
@@ -133,15 +125,15 @@ export class TaskRuntime {
133
125
  detachForeground() {
134
126
  if (!this.enabled)
135
127
  return 0;
136
- const eligible = [...this.store.records.values()]
137
- .filter((record) => this.detachable(record))
128
+ const eligible = [...this.executions.values()]
129
+ .filter((execution) => execution.detachable)
138
130
  .sort((left, right) => left.task.startedAt - right.task.startedAt);
139
131
  if (!eligible.length)
140
132
  return 0;
141
133
  const free = this.maxBackground - this.backgroundActive();
142
134
  if (free <= 0)
143
135
  throw this.limitError();
144
- const executions = eligible.slice(0, free).map((record) => this.executions.get(record.task.id));
136
+ const executions = eligible.slice(0, free);
145
137
  for (const execution of executions)
146
138
  execution.requestDetach();
147
139
  for (const execution of executions)
@@ -166,26 +158,26 @@ export class TaskRuntime {
166
158
  return true;
167
159
  }
168
160
  list() {
169
- return [...this.store.records.values()].filter((record) => record.visible).map(snapshotOf);
161
+ return this.store.visible().map((record) => record.snapshot());
170
162
  }
171
163
  lookup(id) {
172
- const exact = this.store.records.get(id);
164
+ const exact = this.store.get(id);
173
165
  if (exact)
174
166
  return exact;
175
- const matches = [...this.store.records.values()].filter(({ task }) => task.id.startsWith(id) || task.id.slice(task.kind.length + 1).startsWith(id));
167
+ const matches = this.store.match(id);
176
168
  if (!id || matches.length !== 1) {
177
- throw new TaskLookupError(matches.length > 1 ? "ambiguous" : "unknown", matches.map(snapshotOf));
169
+ throw new TaskLookupError(matches.length > 1 ? "ambiguous" : "unknown", matches.map((record) => record.snapshot()));
178
170
  }
179
171
  return matches[0];
180
172
  }
181
173
  get(id) {
182
- return snapshotOf(this.lookup(id));
174
+ return this.lookup(id).snapshot();
183
175
  }
184
176
  async read(id, options = {}) {
185
177
  const record = this.lookup(id);
186
178
  const release = this.retain(record.task.id);
187
179
  try {
188
- const task = snapshotOf(record);
180
+ const task = record.snapshot();
189
181
  if (task.outputPath) {
190
182
  try {
191
183
  return { task, ...(await readOutputSlice(task.outputPath, options)) };
@@ -238,34 +230,76 @@ export class TaskRuntime {
238
230
  record.waiters.add(wake);
239
231
  signal?.addEventListener("abort", abort, { once: true });
240
232
  interruptSignal?.addEventListener("abort", interrupt, { once: true });
241
- timer = setTimeout(wake, finiteLimit(timeoutMs, TASKS_WAIT_DEFAULT_MS, TASKS_WAIT_MAX_MS));
233
+ timer = setTimeout(wake, boundWaitMs(timeoutMs));
242
234
  if (this.closed || interruptSignal?.aborted)
243
235
  wake();
244
236
  if (signal?.aborted)
245
237
  abort();
246
238
  });
247
239
  }
248
- /** Observe completion without consuming delivery; the host acknowledges only persisted tool results. */
249
- async wait(id, timeoutMs = TASKS_WAIT_DEFAULT_MS, signal, interruptSignal) {
250
- if (signal?.aborted)
251
- throw signal.reason ?? new Error("Wait aborted");
240
+ /**
241
+ * One observation: hold delivery for the observed records, wait, read, then register the
242
+ * receipt with the host, and only then release. Nothing the caller does between those steps
243
+ * can leak a settled result into an automatic notification or lose it.
244
+ */
245
+ async observe(records, options,
246
+ /** Record to read when none settled; a cohort has none. */
247
+ fallback) {
248
+ const { signal } = options;
249
+ const releases = records.map((record) => this.holdRecord(record));
250
+ try {
251
+ const start = Date.now();
252
+ const interrupt = this.options.waits?.interruptSignal;
253
+ await this.waitForRecords(records, options.timeoutMs ?? TASKS_WAIT_DEFAULT_MS, signal, interrupt);
254
+ const waitedMs = Date.now() - start;
255
+ const settled = records
256
+ .filter((record) => record.settled)
257
+ .sort((left, right) => (left.task.endedAt ?? 0) - (right.task.endedAt ?? 0))[0];
258
+ const outcome = settled
259
+ ? "settled"
260
+ : this.closed
261
+ ? "closed"
262
+ : interrupt?.aborted
263
+ ? "interrupted"
264
+ : "timed_out";
265
+ const running = records
266
+ .filter((record) => record.visible && !record.settled)
267
+ .map((record) => record.snapshot());
268
+ const observed = settled ?? fallback;
269
+ const read = options.read && observed && outcome !== "interrupted"
270
+ ? await this.read(observed.task.id, options.read)
271
+ : undefined;
272
+ signal?.throwIfAborted();
273
+ if (settled && settled.task.mode === "background" && options.toolCallId)
274
+ this.options.waits?.bind(options.toolCallId, settled.task.id);
275
+ return { outcome, waitedMs, settled, running, read };
276
+ }
277
+ finally {
278
+ for (const release of releases)
279
+ release();
280
+ }
281
+ }
282
+ async wait(id, options = {}) {
283
+ if (options.signal?.aborted)
284
+ throw options.signal.reason ?? new Error("Wait aborted");
252
285
  const record = this.lookup(id);
253
- if (record.settled)
254
- return snapshotOf(record);
255
- await this.waitForRecords([record], timeoutMs, signal, interruptSignal);
256
- return snapshotOf(record);
257
- }
258
- async waitAny(timeoutMs = TASKS_WAIT_DEFAULT_MS, signal, interruptSignal) {
259
- if (signal?.aborted)
260
- throw signal.reason ?? new Error("Wait aborted");
261
- const records = [...this.store.records.values()].filter((record) => record.visible && record.task.mode === "background" && !record.settled);
262
- if (records.length === 0 || interruptSignal?.aborted)
263
- return undefined;
264
- await this.waitForRecords(records, timeoutMs, signal, interruptSignal);
265
- const settled = records
266
- .filter((record) => record.settled)
267
- .sort((left, right) => (left.task.endedAt ?? 0) - (right.task.endedAt ?? 0))[0];
268
- return settled ? snapshotOf(settled) : undefined;
286
+ const { outcome, waitedMs, read } = await this.observe([record], options, record);
287
+ return { outcome, waitedMs, task: record.snapshot(), ...(read ? { read } : {}) };
288
+ }
289
+ async waitAny(options = {}) {
290
+ if (options.signal?.aborted)
291
+ throw options.signal.reason ?? new Error("Wait aborted");
292
+ const records = this.activeBackground().filter((record) => record.visible);
293
+ if (records.length === 0)
294
+ return { outcome: "no_tasks", waitedMs: 0, running: [] };
295
+ const { outcome, waitedMs, settled, running, read } = await this.observe(records, options);
296
+ return {
297
+ outcome,
298
+ waitedMs,
299
+ running,
300
+ ...(settled ? { task: settled.snapshot() } : {}),
301
+ ...(read ? { read } : {}),
302
+ };
269
303
  }
270
304
  kill(id) {
271
305
  return this.cancel(this.lookup(id));
@@ -285,7 +319,9 @@ export class TaskRuntime {
285
319
  * Retention protects snapshots and logs only. Delivery has its own explicit hold.
286
320
  */
287
321
  retain(id) {
288
- const record = this.lookup(id);
322
+ return this.pin(this.lookup(id));
323
+ }
324
+ pin(record) {
289
325
  record.pins++;
290
326
  let released = false;
291
327
  return () => {
@@ -294,13 +330,16 @@ export class TaskRuntime {
294
330
  released = true;
295
331
  record.pins--;
296
332
  this.store.trim();
297
- if (!this.closed && this.pauses === 0 && this.candidate(record))
333
+ // Listeners decide whether delivery may proceed; TaskDelivery owns pausing.
334
+ if (!this.closed && record.deliverable)
298
335
  this.emit();
299
336
  };
300
337
  }
301
338
  holdDelivery(id) {
302
- const record = this.lookup(id);
303
- const releaseRetention = this.retain(id);
339
+ return this.holdRecord(this.lookup(id));
340
+ }
341
+ holdRecord(record) {
342
+ const releasePin = this.pin(record);
304
343
  record.deliveryHolds++;
305
344
  let released = false;
306
345
  return () => {
@@ -308,50 +347,27 @@ export class TaskRuntime {
308
347
  return;
309
348
  released = true;
310
349
  record.deliveryHolds--;
311
- releaseRetention();
312
- };
313
- }
314
- pause() {
315
- this.pauses++;
316
- let resumed = false;
317
- return () => {
318
- if (resumed)
319
- return;
320
- resumed = true;
321
- this.pauses--;
322
- this.emit();
350
+ releasePin();
323
351
  };
324
352
  }
325
353
  pendingNotifications() {
326
- if (this.closed || this.pauses > 0)
354
+ if (this.closed)
327
355
  return [];
328
- return [...this.store.records.values()].filter((record) => this.candidate(record)).map(snapshotOf);
329
- }
330
- /** Only explicit result-delivery holds delay an automatic completion. */
331
- candidate(record) {
332
- return record.visible && record.delivery === "pending" && record.waiters.size === 0 && record.deliveryHolds === 0;
356
+ return this.store.deliverable().map((record) => record.snapshot());
333
357
  }
334
358
  claimNotification(id) {
335
- if (this.closed || this.pauses > 0)
336
- return false;
337
- const record = this.store.records.get(id);
338
- if (!record || !this.candidate(record))
339
- return false;
340
- record.delivery = "claimed";
341
- return true;
359
+ return !this.closed && (this.store.get(id)?.claim() ?? false);
342
360
  }
343
361
  markDelivered(id) {
344
- const record = this.store.records.get(id);
362
+ const record = this.store.get(id);
345
363
  if (!record)
346
364
  return;
347
- record.delivery = "delivered";
365
+ record.acknowledge();
348
366
  this.store.trim();
349
367
  }
350
368
  releaseNotification(id) {
351
- const record = this.store.records.get(id);
352
- if (!record || record.delivery !== "claimed")
369
+ if (!this.store.get(id)?.unclaim())
353
370
  return;
354
- record.delivery = "pending";
355
371
  this.store.trim();
356
372
  this.emit();
357
373
  }
@@ -360,7 +376,7 @@ export class TaskRuntime {
360
376
  return;
361
377
  this._closed = true;
362
378
  this.listeners.clear();
363
- for (const record of this.store.records.values()) {
379
+ for (const record of this.store.all()) {
364
380
  this.executions.get(record.task.id)?.releaseParent();
365
381
  this.cancel(record);
366
382
  for (const waiter of [...record.waiters])
@@ -370,22 +386,24 @@ export class TaskRuntime {
370
386
  }
371
387
  async shutdown(graceMs = 2000) {
372
388
  this.close();
373
- await this.drain([...this.store.records.values()], graceMs);
389
+ await this.drain(this.store.all(), graceMs);
374
390
  }
375
391
  /** Running tasks whose launch anchor is outside the branch made of `ancestors`. */
376
392
  activeOutsideBranch(ancestors) {
377
- return [...this.store.records.values()]
378
- .filter((record) => !record.settled && record.task.anchorId !== null && !ancestors.has(record.task.anchorId))
379
- .map(snapshotOf);
393
+ return this.store
394
+ .unsettled()
395
+ .filter((record) => record.task.anchorId !== null && !ancestors.has(record.task.anchorId))
396
+ .map((record) => record.snapshot());
380
397
  }
381
398
  /**
382
399
  * Hidden results stay pending: returning to their branch delivers them again.
383
400
  * Restored history is already delivered.
384
401
  */
385
402
  async cancelOutsideBranch(ancestors) {
386
- for (const record of this.store.records.values())
403
+ const records = this.store.all();
404
+ for (const record of records)
387
405
  record.visible = record.task.anchorId === null || ancestors.has(record.task.anchorId);
388
- const outside = [...this.store.records.values()].filter((record) => !record.visible);
406
+ const outside = records.filter((record) => !record.visible);
389
407
  for (const record of outside)
390
408
  this.cancel(record);
391
409
  this.store.trim();
@@ -396,7 +414,7 @@ export class TaskRuntime {
396
414
  let timer;
397
415
  try {
398
416
  await Promise.race([
399
- Promise.all(records.map((record) => this.executions.get(record.task.id)?.done)).then(() => Promise.all(this.store.cleanups)),
417
+ Promise.all(records.map((record) => this.executions.get(record.task.id)?.done)).then(() => this.store.cleanupsDone()),
400
418
  new Promise((resolve) => {
401
419
  timer = setTimeout(resolve, finiteLimit(graceMs, 2000, 60_000));
402
420
  }),