@visual-z/xeno 0.8.4 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (350) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/README.md +31 -746
  3. package/bin/xeno.exe +26 -0
  4. package/install.cjs +88 -0
  5. package/package.json +18 -48
  6. package/docs/app-server.md +0 -115
  7. package/docs/foundations.md +0 -88
  8. package/docs/install.md +0 -244
  9. package/docs/providers.md +0 -172
  10. package/docs/research/foundation-clients-2026-10-09.md +0 -27
  11. package/docs/research/foundation-editing-2026-10-09.md +0 -56
  12. package/docs/research/foundation-network-2026-10-09.md +0 -37
  13. package/docs/research/foundation-workflow-2026-10-09.md +0 -37
  14. package/src/apps/acp-server.ts +0 -41
  15. package/src/apps/agent-profiles.ts +0 -19
  16. package/src/apps/app-server.ts +0 -30
  17. package/src/apps/approve.ts +0 -49
  18. package/src/apps/cli.ts +0 -99
  19. package/src/apps/commands.ts +0 -319
  20. package/src/apps/completions.ts +0 -87
  21. package/src/apps/control-transport.ts +0 -90
  22. package/src/apps/export-cli.ts +0 -24
  23. package/src/apps/goal-flow.ts +0 -70
  24. package/src/apps/image-draft.ts +0 -46
  25. package/src/apps/input.ts +0 -312
  26. package/src/apps/model-catalog.ts +0 -69
  27. package/src/apps/preferences.ts +0 -20
  28. package/src/apps/project-trust.ts +0 -9
  29. package/src/apps/provider-connect.ts +0 -346
  30. package/src/apps/question-ui.ts +0 -27
  31. package/src/apps/render.ts +0 -181
  32. package/src/apps/resources.ts +0 -66
  33. package/src/apps/review.ts +0 -109
  34. package/src/apps/session-hub.ts +0 -96
  35. package/src/apps/session-lifecycle.ts +0 -64
  36. package/src/apps/shell.ts +0 -2071
  37. package/src/apps/startup-input.ts +0 -135
  38. package/src/apps/startup.ts +0 -1677
  39. package/src/apps/ui/app.ts +0 -1818
  40. package/src/apps/ui/control-model.ts +0 -43
  41. package/src/apps/ui/extension-host.ts +0 -194
  42. package/src/apps/ui/image-cache.ts +0 -121
  43. package/src/apps/ui/palette.ts +0 -88
  44. package/src/apps/ui/project.ts +0 -127
  45. package/src/apps/ui/standalone.ts +0 -148
  46. package/src/apps/ui/status-model.ts +0 -68
  47. package/src/apps/ui/surface.ts +0 -91
  48. package/src/apps/ui/theme.ts +0 -86
  49. package/src/apps/update-check.ts +0 -112
  50. package/src/apps/workflow.ts +0 -58
  51. package/src/core/acceptance.ts +0 -139
  52. package/src/core/action-defaults.ts +0 -58
  53. package/src/core/activity-status.ts +0 -52
  54. package/src/core/agent-profile.ts +0 -130
  55. package/src/core/attachment-input.ts +0 -17
  56. package/src/core/blind-checks.ts +0 -72
  57. package/src/core/boundary.ts +0 -174
  58. package/src/core/branch-receipt.ts +0 -26
  59. package/src/core/catalog-merge.ts +0 -19
  60. package/src/core/checkpoints.ts +0 -85
  61. package/src/core/churn.ts +0 -61
  62. package/src/core/command-artifacts.ts +0 -94
  63. package/src/core/command-contract.ts +0 -151
  64. package/src/core/completion-action.ts +0 -20
  65. package/src/core/completion-review.ts +0 -98
  66. package/src/core/context-detail.ts +0 -37
  67. package/src/core/context-policy.ts +0 -133
  68. package/src/core/context-window.ts +0 -54
  69. package/src/core/continuation.ts +0 -130
  70. package/src/core/control-protocol.ts +0 -69
  71. package/src/core/control-view.ts +0 -39
  72. package/src/core/cycle.ts +0 -17
  73. package/src/core/data-query.ts +0 -201
  74. package/src/core/deadline.ts +0 -40
  75. package/src/core/derive.ts +0 -516
  76. package/src/core/draft-images.ts +0 -56
  77. package/src/core/edit-plan.ts +0 -200
  78. package/src/core/environment.ts +0 -181
  79. package/src/core/event-validation.ts +0 -220
  80. package/src/core/events.ts +0 -670
  81. package/src/core/evidence-ladder.ts +0 -62
  82. package/src/core/evidence-links.ts +0 -68
  83. package/src/core/evidence.ts +0 -240
  84. package/src/core/execution.ts +0 -18
  85. package/src/core/exhaustive.ts +0 -18
  86. package/src/core/export.ts +0 -179
  87. package/src/core/extension-flags.ts +0 -14
  88. package/src/core/external-effects.ts +0 -488
  89. package/src/core/failure-recovery.ts +0 -173
  90. package/src/core/file-precondition.ts +0 -78
  91. package/src/core/file-references.ts +0 -35
  92. package/src/core/frontmatter.ts +0 -213
  93. package/src/core/git-status.ts +0 -29
  94. package/src/core/goal-view.ts +0 -64
  95. package/src/core/goal.ts +0 -200
  96. package/src/core/handoff.ts +0 -479
  97. package/src/core/history-query.ts +0 -139
  98. package/src/core/history.ts +0 -237
  99. package/src/core/image-limits.ts +0 -72
  100. package/src/core/images.ts +0 -46
  101. package/src/core/integration-health.ts +0 -34
  102. package/src/core/json-event.ts +0 -131
  103. package/src/core/json-pointer.ts +0 -46
  104. package/src/core/keymap.ts +0 -153
  105. package/src/core/lsp-framing.ts +0 -34
  106. package/src/core/mcp-config.ts +0 -68
  107. package/src/core/model-picker.ts +0 -64
  108. package/src/core/model-preferences.ts +0 -28
  109. package/src/core/model-replay.ts +0 -22
  110. package/src/core/model-scope.ts +0 -53
  111. package/src/core/note-guidance.ts +0 -34
  112. package/src/core/oauth-fields.ts +0 -44
  113. package/src/core/observation-view.ts +0 -152
  114. package/src/core/outcome.ts +0 -44
  115. package/src/core/overflow.ts +0 -59
  116. package/src/core/packages.ts +0 -47
  117. package/src/core/patch.ts +0 -130
  118. package/src/core/path-suggestions.ts +0 -59
  119. package/src/core/permissions.ts +0 -410
  120. package/src/core/process-receipt.ts +0 -105
  121. package/src/core/process-state.ts +0 -37
  122. package/src/core/program-status.ts +0 -40
  123. package/src/core/progress.ts +0 -150
  124. package/src/core/project.ts +0 -431
  125. package/src/core/prompt-policy.ts +0 -46
  126. package/src/core/prompts.ts +0 -111
  127. package/src/core/question.ts +0 -94
  128. package/src/core/queue.ts +0 -78
  129. package/src/core/read-bounds.ts +0 -24
  130. package/src/core/read-observation.ts +0 -106
  131. package/src/core/recovery-sources.ts +0 -74
  132. package/src/core/regex-dialect.ts +0 -25
  133. package/src/core/request-budget.ts +0 -23
  134. package/src/core/request-progress.ts +0 -87
  135. package/src/core/request-state.ts +0 -273
  136. package/src/core/resource-selection.ts +0 -36
  137. package/src/core/response-recovery.ts +0 -59
  138. package/src/core/resume.ts +0 -59
  139. package/src/core/review.ts +0 -68
  140. package/src/core/ripgrep-release.ts +0 -64
  141. package/src/core/runtime-target.ts +0 -94
  142. package/src/core/scheduling.ts +0 -8
  143. package/src/core/search-observation.ts +0 -97
  144. package/src/core/secrets.ts +0 -188
  145. package/src/core/session-archive.ts +0 -4
  146. package/src/core/session-browser.ts +0 -82
  147. package/src/core/session-entry.ts +0 -61
  148. package/src/core/session-exit.ts +0 -42
  149. package/src/core/session-format.ts +0 -169
  150. package/src/core/session-position.ts +0 -26
  151. package/src/core/session-tree.ts +0 -147
  152. package/src/core/settings.ts +0 -192
  153. package/src/core/skills.ts +0 -165
  154. package/src/core/sql-query.ts +0 -211
  155. package/src/core/stall.ts +0 -128
  156. package/src/core/stop-report.ts +0 -68
  157. package/src/core/text-normalize.ts +0 -81
  158. package/src/core/theme.ts +0 -166
  159. package/src/core/thinking.ts +0 -26
  160. package/src/core/tokens.ts +0 -151
  161. package/src/core/tool-contract.ts +0 -153
  162. package/src/core/tool-discovery.ts +0 -87
  163. package/src/core/tool-failure.ts +0 -81
  164. package/src/core/tool-input.ts +0 -44
  165. package/src/core/transcript.ts +0 -108
  166. package/src/core/tui-mode.ts +0 -29
  167. package/src/core/update-check.ts +0 -157
  168. package/src/core/version.ts +0 -55
  169. package/src/core/web-content.ts +0 -80
  170. package/src/core/work-state.ts +0 -154
  171. package/src/core/workflow-mode.ts +0 -34
  172. package/src/core/working-set.ts +0 -119
  173. package/src/core/yaml.ts +0 -282
  174. package/src/runtime/acp-agent.ts +0 -289
  175. package/src/runtime/agent-profiles.ts +0 -21
  176. package/src/runtime/auth.ts +0 -367
  177. package/src/runtime/blind-check.ts +0 -36
  178. package/src/runtime/branch-jobs.ts +0 -36
  179. package/src/runtime/catalog.ts +0 -285
  180. package/src/runtime/clipboard.ts +0 -125
  181. package/src/runtime/command-artifacts.ts +0 -64
  182. package/src/runtime/connection-reuse.ts +0 -40
  183. package/src/runtime/control-client.ts +0 -149
  184. package/src/runtime/control-connection.ts +0 -139
  185. package/src/runtime/counter-check.ts +0 -74
  186. package/src/runtime/environment-tools.ts +0 -60
  187. package/src/runtime/evidence-notes.ts +0 -35
  188. package/src/runtime/exec-sessions.ts +0 -456
  189. package/src/runtime/execution-control.ts +0 -40
  190. package/src/runtime/extension-loader.ts +0 -37
  191. package/src/runtime/extension-ui.ts +0 -47
  192. package/src/runtime/extensions.ts +0 -370
  193. package/src/runtime/formatter-tool.ts +0 -38
  194. package/src/runtime/formatter.ts +0 -50
  195. package/src/runtime/foundation.ts +0 -116
  196. package/src/runtime/history-cache.ts +0 -57
  197. package/src/runtime/history-tools.ts +0 -168
  198. package/src/runtime/images.ts +0 -80
  199. package/src/runtime/integrations.ts +0 -216
  200. package/src/runtime/loop.ts +0 -3108
  201. package/src/runtime/lsp-client.ts +0 -201
  202. package/src/runtime/mcp-client.ts +0 -264
  203. package/src/runtime/mcp-oauth.ts +0 -168
  204. package/src/runtime/mcp-schema.ts +0 -51
  205. package/src/runtime/model-config.ts +0 -232
  206. package/src/runtime/model-data.LICENSE +0 -27
  207. package/src/runtime/model-data.ts +0 -21
  208. package/src/runtime/model-discovery.ts +0 -208
  209. package/src/runtime/model-replay.ts +0 -10
  210. package/src/runtime/model-scope.ts +0 -20
  211. package/src/runtime/model-sources.ts +0 -151
  212. package/src/runtime/model.ts +0 -686
  213. package/src/runtime/models-fetch.ts +0 -57
  214. package/src/runtime/oauth-device.ts +0 -86
  215. package/src/runtime/oauth.ts +0 -853
  216. package/src/runtime/open-image.ts +0 -42
  217. package/src/runtime/openai-responses.ts +0 -304
  218. package/src/runtime/openai.ts +0 -697
  219. package/src/runtime/output-schema.ts +0 -26
  220. package/src/runtime/package-manager.ts +0 -125
  221. package/src/runtime/patch-tool.ts +0 -38
  222. package/src/runtime/permission-resources.ts +0 -18
  223. package/src/runtime/pi-provider.ts +0 -423
  224. package/src/runtime/program-status.ts +0 -38
  225. package/src/runtime/progress.ts +0 -26
  226. package/src/runtime/provider-access.ts +0 -21
  227. package/src/runtime/provider-connect.ts +0 -109
  228. package/src/runtime/provider-presets.ts +0 -98
  229. package/src/runtime/provider-protocols.ts +0 -29
  230. package/src/runtime/provider-request.ts +0 -87
  231. package/src/runtime/provider-tool-policy.ts +0 -33
  232. package/src/runtime/provider.ts +0 -580
  233. package/src/runtime/question-tool.ts +0 -62
  234. package/src/runtime/retry.ts +0 -447
  235. package/src/runtime/review-tool.ts +0 -48
  236. package/src/runtime/rich-text.ts +0 -36
  237. package/src/runtime/runtime-target.ts +0 -149
  238. package/src/runtime/secrets.ts +0 -79
  239. package/src/runtime/self-update.ts +0 -74
  240. package/src/runtime/session-control.ts +0 -265
  241. package/src/runtime/session-hub.ts +0 -85
  242. package/src/runtime/stream-idle.ts +0 -80
  243. package/src/runtime/tool-broker.ts +0 -137
  244. package/src/runtime/tool-discovery.ts +0 -58
  245. package/src/runtime/tool-queue.ts +0 -77
  246. package/src/runtime/tools.ts +0 -1820
  247. package/src/runtime/turn-effects.ts +0 -31
  248. package/src/runtime/update-check.ts +0 -48
  249. package/src/runtime/usage.ts +0 -253
  250. package/src/runtime/version.ts +0 -12
  251. package/src/runtime/web-http.ts +0 -56
  252. package/src/runtime/web-tools.ts +0 -88
  253. package/src/runtime/working-notes.ts +0 -60
  254. package/src/runtime/workspace-lifecycle.ts +0 -141
  255. package/src/store/acceptance.ts +0 -15
  256. package/src/store/agent-profile-files.ts +0 -13
  257. package/src/store/atomic.ts +0 -31
  258. package/src/store/auth-store.ts +0 -50
  259. package/src/store/blind-checks.ts +0 -32
  260. package/src/store/blobs.ts +0 -143
  261. package/src/store/checkpoint-history.ts +0 -32
  262. package/src/store/command-artifacts.ts +0 -96
  263. package/src/store/command.ts +0 -111
  264. package/src/store/config.ts +0 -69
  265. package/src/store/context-files.ts +0 -436
  266. package/src/store/file-checkpoints.ts +0 -108
  267. package/src/store/file-index.ts +0 -174
  268. package/src/store/foundation-config.ts +0 -24
  269. package/src/store/git-checkpoints.ts +0 -67
  270. package/src/store/git-status.ts +0 -15
  271. package/src/store/history-search.ts +0 -19
  272. package/src/store/log.ts +0 -725
  273. package/src/store/lsp-document.ts +0 -30
  274. package/src/store/mcp-credentials.ts +0 -85
  275. package/src/store/models.ts +0 -60
  276. package/src/store/observations.ts +0 -224
  277. package/src/store/packages.ts +0 -98
  278. package/src/store/patch.ts +0 -92
  279. package/src/store/permission-paths.ts +0 -34
  280. package/src/store/process-receipts.ts +0 -34
  281. package/src/store/query-source.ts +0 -49
  282. package/src/store/recovery.ts +0 -44
  283. package/src/store/replay.ts +0 -76
  284. package/src/store/resource-index.ts +0 -170
  285. package/src/store/resources.ts +0 -285
  286. package/src/store/restore.ts +0 -110
  287. package/src/store/review.ts +0 -109
  288. package/src/store/runtime-target.ts +0 -64
  289. package/src/store/search.ts +0 -728
  290. package/src/store/secrets.ts +0 -44
  291. package/src/store/serial.ts +0 -13
  292. package/src/store/session-archive.ts +0 -165
  293. package/src/store/session-browser.ts +0 -68
  294. package/src/store/session-position.ts +0 -27
  295. package/src/store/sessions.ts +0 -525
  296. package/src/store/source-cancellation.ts +0 -6
  297. package/src/store/tool-binaries.ts +0 -90
  298. package/src/store/trust.ts +0 -82
  299. package/src/store/update-cache.ts +0 -36
  300. package/src/store/user-settings.ts +0 -30
  301. package/src/store/workspace-clone.ts +0 -42
  302. package/src/store/workspace-observation.ts +0 -113
  303. package/src/store/workspace.ts +0 -115
  304. package/src/store/worktree.ts +0 -278
  305. package/src/tui/ansi-spans.ts +0 -126
  306. package/src/tui/diff.ts +0 -160
  307. package/src/tui/exhaustive.ts +0 -14
  308. package/src/tui/fuzzy.ts +0 -75
  309. package/src/tui/style.ts +0 -208
  310. package/src/tui/ui/atomic-draft.ts +0 -121
  311. package/src/tui/ui/branch-panel.ts +0 -47
  312. package/src/tui/ui/canvas.ts +0 -96
  313. package/src/tui/ui/colors.ts +0 -70
  314. package/src/tui/ui/composer.ts +0 -199
  315. package/src/tui/ui/control-panel.ts +0 -25
  316. package/src/tui/ui/control-sidebar.ts +0 -49
  317. package/src/tui/ui/dialog.ts +0 -398
  318. package/src/tui/ui/draft.ts +0 -23
  319. package/src/tui/ui/duration.ts +0 -16
  320. package/src/tui/ui/edge.ts +0 -186
  321. package/src/tui/ui/image-size.ts +0 -44
  322. package/src/tui/ui/ink.ts +0 -210
  323. package/src/tui/ui/items.ts +0 -42
  324. package/src/tui/ui/keys.ts +0 -40
  325. package/src/tui/ui/layout.ts +0 -74
  326. package/src/tui/ui/list-model.ts +0 -86
  327. package/src/tui/ui/list-view.ts +0 -172
  328. package/src/tui/ui/mascot.ts +0 -67
  329. package/src/tui/ui/messages.ts +0 -518
  330. package/src/tui/ui/motion.ts +0 -28
  331. package/src/tui/ui/paths.ts +0 -10
  332. package/src/tui/ui/pictures.ts +0 -108
  333. package/src/tui/ui/prompt-history.ts +0 -60
  334. package/src/tui/ui/screen.ts +0 -579
  335. package/src/tui/ui/status.ts +0 -403
  336. package/src/tui/ui/styled.ts +0 -85
  337. package/src/tui/ui/tool-card.ts +0 -150
  338. package/src/tui/width.ts +0 -567
  339. package/src/vendor/pi-rich/LICENSE.upstream +0 -21
  340. package/src/vendor/pi-rich/UPSTREAM.json +0 -17
  341. package/src/vendor/pi-rich/clipboard-command.ts +0 -44
  342. package/src/vendor/pi-rich/exif-orientation.ts +0 -182
  343. package/src/vendor/pi-rich/html.ts +0 -51
  344. package/src/vendor/pi-rich/image-convert.ts +0 -49
  345. package/src/vendor/pi-rich/image-resize-core.ts +0 -164
  346. package/src/vendor/pi-rich/image-resize-worker.ts +0 -42
  347. package/src/vendor/pi-rich/image-resize.ts +0 -123
  348. package/src/vendor/pi-rich/mime.ts +0 -116
  349. package/src/vendor/pi-rich/photon.ts +0 -139
  350. package/src/vendor/pi-rich/syntax-highlight.ts +0 -214
@@ -1,237 +0,0 @@
1
- /** D20: bounded carried state, with deterministic pages rebuilt from the log. */
2
- import { emitHandoff, type Handoff, type UserTurnRecord } from "./handoff.ts";
3
- import { estimateTokens } from "./tokens.ts";
4
- import { recoveryWorkingSet, recoveryWorkingSetV3 } from "./working-set.ts";
5
- import { recoveryInstructions } from "./prompt-policy.ts";
6
- import type { RecoveryNodes } from "./recovery-sources.ts";
7
-
8
- // A carried handoff must leave room for another segment's work. This budget is
9
- // the existing 5k work floor, not a new segment threshold or an accuracy claim.
10
- export const DEFAULT_HANDOFF_TOKENS = 5_000;
11
- export const HISTORY_SECTIONS = [
12
- "user_turns", "state.completed", "state.unverified_claims", "state.in_flight",
13
- "state.blocked", "decisions", "pitfalls", "file_anchors", "verification",
14
- "branches", "open_questions", "progress_evidence",
15
- "state.work_items",
16
- "evidence_links",
17
- "acceptance.criteria",
18
- "acceptance.targets",
19
- "loaded_skills",
20
- "processes",
21
- ] as const;
22
- export type HistorySection = (typeof HISTORY_SECTIONS)[number];
23
-
24
- export function historyItems(h: Handoff, section: HistorySection): readonly unknown[] {
25
- switch (section) {
26
- case "state.completed": return h.state.completed;
27
- case "state.unverified_claims": return h.state.unverified_claims;
28
- case "state.in_flight": return h.state.in_flight;
29
- case "state.blocked": return h.state.blocked;
30
- case "state.work_items": return h.state.work_items ?? [];
31
- case "evidence_links": return h.evidence_links ?? [];
32
- case "acceptance.criteria": return h.acceptance?.criteria ?? [];
33
- case "acceptance.targets": return h.acceptance?.targets ?? [];
34
- case "loaded_skills": return h.loaded_skills ?? [];
35
- case "processes": return h.processes ?? [];
36
- case "progress_evidence": return h.progress_evidence ?? [];
37
- default: return h[section];
38
- }
39
- }
40
-
41
- function suffixes(h: Handoff, offsets: Record<HistorySection, number>): Handoff {
42
- return {
43
- ...h,
44
- user_turns: h.user_turns.slice(offsets.user_turns),
45
- state: {
46
- ...h.state,
47
- completed: h.state.completed.slice(offsets["state.completed"]),
48
- unverified_claims: h.state.unverified_claims.slice(offsets["state.unverified_claims"]),
49
- in_flight: h.state.in_flight.slice(offsets["state.in_flight"]),
50
- blocked: h.state.blocked.slice(offsets["state.blocked"]),
51
- ...(h.state.work_items !== undefined ? { work_items: h.state.work_items.slice(offsets["state.work_items"] ?? 0) } : {}),
52
- },
53
- decisions: h.decisions.slice(offsets.decisions),
54
- pitfalls: h.pitfalls.slice(offsets.pitfalls),
55
- file_anchors: h.file_anchors.slice(offsets.file_anchors),
56
- verification: h.verification.slice(offsets.verification),
57
- branches: h.branches.slice(offsets.branches),
58
- open_questions: h.open_questions.slice(offsets.open_questions),
59
- progress_evidence: (h.progress_evidence ?? []).slice(offsets.progress_evidence),
60
- ...(h.evidence_links !== undefined ? { evidence_links: h.evidence_links.slice(offsets.evidence_links ?? 0) } : {}),
61
- ...(h.acceptance ? { acceptance: { ...h.acceptance, criteria: h.acceptance.criteria.slice(offsets["acceptance.criteria"] ?? 0),
62
- ...(h.acceptance.targets ? { targets: h.acceptance.targets.slice(offsets["acceptance.targets"] ?? 0) } : {}) } } : {}),
63
- ...(h.loaded_skills ? { loaded_skills: h.loaded_skills.slice(offsets.loaded_skills ?? 0) } : {}),
64
- ...(h.processes ? { processes: h.processes.slice(offsets.processes ?? 0) } : {}),
65
- };
66
- }
67
-
68
- /** Sections a handoff actually has; an absent new section must not change legacy projections or frozen replay. */
69
- function pageableSections(full: Handoff): HistorySection[] {
70
- return HISTORY_SECTIONS.filter(section => (section !== "state.work_items" || full.state.work_items !== undefined) &&
71
- (section !== "evidence_links" || full.evidence_links !== undefined) &&
72
- (section !== "acceptance.criteria" || full.acceptance !== undefined) &&
73
- (section !== "acceptance.targets" || full.acceptance?.targets !== undefined) &&
74
- (section !== "loaded_skills" || full.loaded_skills !== undefined) &&
75
- (section !== "processes" || full.processes !== undefined));
76
- }
77
-
78
- /** The v2 shape carrying `items.length - offset` newest items of every pageable section. */
79
- function carriedAt(full: Handoff, sections: readonly HistorySection[], offsets: Record<HistorySection, number>): Handoff {
80
- return {
81
- ...suffixes(full, offsets),
82
- handoff_version: 2,
83
- history: {
84
- source: full.predecessor!,
85
- sections: Object.fromEntries(sections.map((s) => [s, {
86
- total: historyItems(full, s).length, offset: offsets[s],
87
- }])),
88
- },
89
- };
90
- }
91
-
92
- const handoffSize = (h: Handoff): number => estimateTokens.count([], emitHandoff(h));
93
-
94
- /**
95
- * `priority` sections grow to completion, newest first, before any other
96
- * section gets an item; the rest then share what is left round-robin. The
97
- * default (none) is the original D20 order, which recorded cuts replay.
98
- */
99
- export function boundHandoff(full: Handoff, budget = DEFAULT_HANDOFF_TOKENS, priority: readonly HistorySection[] = []): Handoff {
100
- if (!Number.isFinite(budget) || budget <= 0) throw new RangeError("handoff budget must be positive");
101
- const size = handoffSize;
102
- if (size(full) <= budget) return full;
103
- if (full.predecessor === null) throw new Error("history requires an immutable source");
104
- const sections = pageableSections(full);
105
- const offsets = Object.fromEntries(sections.map((s) => [s, historyItems(full, s).length])) as Record<HistorySection, number>;
106
- const build = (): Handoff => carriedAt(full, sections, offsets);
107
- let carried = build();
108
- // The objective stays verbatim (D2). An oversized objective is an explicit
109
- // input problem; repeatedly cutting it would create an infinite cut loop.
110
- if (size(carried) > budget) throw new RangeError("handoff scalar fields exceed budget; shorten the objective or frozen input");
111
- const grow = (section: HistorySection): boolean => {
112
- if (offsets[section] === 0) return false;
113
- offsets[section]--;
114
- const candidate = build();
115
- if (size(candidate) <= budget) { carried = candidate; return true; }
116
- offsets[section]++;
117
- return false;
118
- };
119
- for (const section of priority) if (sections.includes(section)) while (grow(section)) { /* newest first until full or out of room */ }
120
- const active = new Set<HistorySection>(sections.filter(section => !priority.includes(section)));
121
- // Round-robin suffix growth prevents one long section starving every other
122
- // category. Each retained range is contiguous and independently pageable.
123
- while (active.size > 0) {
124
- for (const section of active) if (!grow(section)) active.delete(section);
125
- }
126
- return carried;
127
- }
128
-
129
- export function historyPage(full: Handoff, section: HistorySection, offset: number, limit: number): {
130
- total: number; offset: number; next: number | null; items: readonly unknown[];
131
- } {
132
- if (!Number.isSafeInteger(offset) || offset < 0 || !Number.isSafeInteger(limit) || limit < 1) {
133
- throw new RangeError("history offset and limit must be nonnegative/positive integers");
134
- }
135
- const items = historyItems(full, section);
136
- const end = Math.min(items.length, offset + limit);
137
- return { total: items.length, offset, next: end < items.length ? end : null, items: items.slice(offset, end) };
138
- }
139
-
140
- /** R9 uses the same budget; originals are supplied by the ancestry owner. */
141
- export function boundRecoveryHandoff(full: Handoff, nodes: RecoveryNodes, budget = DEFAULT_HANDOFF_TOKENS, tools?: readonly string[]): Handoff {
142
- const legacy = boundHandoff(full, budget);
143
- const view = recoveryWorkingSet(full, nodes, budget * .4, tools);
144
- if (!view) return legacy;
145
- let carried: Handoff;
146
- try { carried = boundHandoff({ ...full, working_set: view }, budget); }
147
- catch (error) { if (error instanceof RangeError) return legacy; throw error; }
148
- // Whether the user turns survived bounding is known only now. The inline
149
- // wording is shorter, so swapping it in cannot push the handoff over budget.
150
- const inline = tools?.includes("history") && carried.user_turns_truncated === undefined && (carried.history?.sections["user_turns"]?.offset ?? 0) === 0;
151
- return inline && carried.working_set ? { ...carried, working_set: { ...carried.working_set, recovery: recoveryInstructions(tools!, true) } } : carried;
152
- }
153
-
154
- /**
155
- * D8: join carried turns to their original user events to name attached images
156
- * by digest. Turns and events are both the active ancestry in log order; any
157
- * disagreement leaves the turns undecorated rather than guessing a pairing.
158
- */
159
- export function userTurnImages(turns: readonly UserTurnRecord[], nodes: RecoveryNodes): readonly UserTurnRecord[] {
160
- const users = nodes.flatMap(part => part.events.flatMap(event => event.t === "user" ? [event] : []));
161
- if (users.length !== turns.length || users.some((event, i) => event.at !== turns[i]!.at || event.text !== turns[i]!.text)) return turns;
162
- return turns.map((turn, i) => {
163
- const event = users[i]!;
164
- return event.images?.length ? { ...turn, event: event.id,
165
- images: event.images.map(({ sha256, name, width, height }) => ({ sha256, name, width, height })) } : turn;
166
- });
167
- }
168
-
169
- /**
170
- * v3 carries one copy of each work item's prose. `state.in_flight` and
171
- * `state.blocked` are compatible projections of identified work (O.2), so a
172
- * leading run of items the carried `state.work_items` or working set already
173
- * hold moves into the existing page range, which `validateHandoff` accepts for
174
- * continuation; D20 arrays stay contiguous suffixes. A working-set entry whose
175
- * prose the carried `state.work_items` holds keeps only its id and status, since
176
- * `state.work_items` is the canonical identified work. Every change only
177
- * removes bytes, so the budget still holds.
178
- */
179
- function oneCopyOfWork(h: Handoff): Handoff {
180
- const items = new Map((h.state.work_items ?? []).map(item => [item.id, item]));
181
- const viewed = new Map((h.working_set?.work ?? []).map(entry => [entry.id, entry]));
182
- const history = h.history && { ...h.history, sections: { ...h.history.sections } };
183
- type Prose = { status: string; what?: string; next_action?: string; reason?: string };
184
- const holders = (id: string | undefined): Prose[] => {
185
- const found: Prose[] = [], item = id === undefined ? undefined : items.get(id), entry = id === undefined ? undefined : viewed.get(id);
186
- if (item) found.push(item);
187
- if (entry) found.push(entry);
188
- return found;
189
- };
190
- const page = <T extends { work_id?: string }>(section: "state.in_flight" | "state.blocked", list: readonly T[], same: (item: T, holder: Prose) => boolean): readonly T[] => {
191
- const range = history?.sections[section];
192
- let skip = 0;
193
- while (range && skip < list.length && holders(list[skip]!.work_id).some(holder => same(list[skip]!, holder))) skip++;
194
- if (skip === 0) return list;
195
- history!.sections[section] = { total: range!.total, offset: range!.offset + skip };
196
- return list.slice(skip);
197
- };
198
- const in_flight = page("state.in_flight", h.state.in_flight, (item, holder) => holder.status === "active" && holder.what === item.what && holder.next_action === item.next_action);
199
- const blocked = page("state.blocked", h.state.blocked, (item, holder) => holder.status === "blocked" && holder.what === item.what && holder.reason === item.reason);
200
- const view = h.working_set && { ...h.working_set, work: h.working_set.work.map(entry => {
201
- const item = items.get(entry.id);
202
- if (!item || item.what !== entry.what || item.next_action !== entry.next_action || item.reason !== entry.reason) return entry;
203
- const { what: _what, next_action: _next, reason: _reason, ...rest } = entry;
204
- return rest;
205
- }) };
206
- return { ...h, ...(view ? { working_set: view } : {}), ...(history ? { history } : {}), state: { ...h.state, in_flight, blocked } };
207
- }
208
-
209
- const USERS_FIRST: readonly HistorySection[] = ["user_turns"];
210
-
211
- /**
212
- * Working set v3. User turns are the smallest, highest-value carried content
213
- * (Codex compaction likewise keeps user messages newest first, verbatim, under
214
- * its own cap), so they are reserved before the working set and before every
215
- * other page; only turns that alone exceed the budget are cut, newest kept.
216
- * While every turn is inline, user references would only point at text already
217
- * present, so the view leaves them out. The view keeps v2's 40% allocation
218
- * assumption, measured inside the handoff it is carried in.
219
- */
220
- export function boundRecoveryHandoffV3(full: Handoff, nodes: RecoveryNodes, budget = DEFAULT_HANDOFF_TOKENS, tools?: readonly string[]): Handoff {
221
- if (!Number.isFinite(budget) || budget <= 0) throw new RangeError("handoff budget must be positive");
222
- const base: Handoff = { ...full, user_turns: userTurnImages(full.user_turns, nodes) };
223
- const plain = (): Handoff => oneCopyOfWork(boundHandoff(base, budget, USERS_FIRST));
224
- if (base.predecessor === null) return plain();
225
- const sections = pageableSections(base);
226
- const empty = Object.fromEntries(sections.map((s) => [s, historyItems(base, s).length])) as Record<HistorySection, number>;
227
- const withUsers = carriedAt(base, sections, { ...empty, user_turns: 0 });
228
- const usersInline = base.user_turns_truncated === undefined && handoffSize(withUsers) <= budget;
229
- const skeleton = usersInline ? withUsers : carriedAt(base, sections, empty);
230
- const cap = Math.min(budget, handoffSize(skeleton) + budget * .4);
231
- const recovery = recoveryInstructions(tools ?? ["history"], usersInline, base.user_turns.some(turn => turn.images !== undefined));
232
- const view = recoveryWorkingSetV3(base, nodes, { users: !usersInline, recovery,
233
- fits: candidate => handoffSize({ ...skeleton, working_set: candidate }) <= cap });
234
- if (!view) return plain();
235
- try { return oneCopyOfWork(boundHandoff({ ...base, working_set: view }, budget, USERS_FIRST)); }
236
- catch (error) { if (error instanceof RangeError) return plain(); throw error; }
237
- }
@@ -1,72 +0,0 @@
1
- /** Admission, model detail and preview memory are separate budgets (D8). */
2
- export const MAX_IMAGE_SOURCE_BYTES = 64 * 1024 * 1024;
3
- /** Matches the native decoder's area cap; inspect headers before WASM allocates. */
4
- export const MAX_IMAGE_SOURCE_PIXELS = 25_000_000;
5
- /** More readable screenshots than 2000px, with at most 32 MiB RGBA per preview. */
6
- export const MAX_MODEL_IMAGE_SIDE = 4096;
7
- export const MAX_MODEL_IMAGE_PIXELS = 8 * 1024 * 1024;
8
- export interface ImageDimensions { readonly width: number; readonly height: number }
9
-
10
- /** Header-only probe. Decoders still validate the complete image afterward. */
11
- export function imageDimensions(bytes: Uint8Array, mime: string): ImageDimensions | undefined {
12
- const v = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
13
- const result = (width: number, height: number): ImageDimensions | undefined =>
14
- Number.isSafeInteger(width) && Number.isSafeInteger(height) && width > 0 && height > 0 ? { width, height } : undefined;
15
- if (mime === "image/png" && bytes.length >= 24) return result(v.getUint32(16), v.getUint32(20));
16
- if (mime === "image/gif" && bytes.length >= 10) return result(v.getUint16(6, true), v.getUint16(8, true));
17
- if (mime === "image/bmp" && bytes.length >= 26) {
18
- const header = v.getUint32(14, true);
19
- if (header === 12) return result(v.getUint16(18, true), v.getUint16(20, true));
20
- if (header >= 40) return result(v.getInt32(18, true), Math.abs(v.getInt32(22, true)));
21
- return undefined;
22
- }
23
- if (mime === "image/jpeg") {
24
- let offset = 2;
25
- while (offset + 3 < bytes.length) {
26
- if (bytes[offset++] !== 255) return undefined;
27
- while (bytes[offset] === 255) offset++;
28
- const marker = bytes[offset++];
29
- if (marker === undefined || marker === 0xda || marker === 0xd9) return undefined;
30
- if (marker === 0x01 || (marker >= 0xd0 && marker <= 0xd8)) continue;
31
- if (offset + 2 > bytes.length) return undefined;
32
- const size = v.getUint16(offset);
33
- if (size < 2 || offset + size > bytes.length) return undefined;
34
- if (marker >= 0xc0 && marker <= 0xcf && ![0xc4, 0xc8, 0xcc].includes(marker)) {
35
- return size >= 8 ? result(v.getUint16(offset + 5), v.getUint16(offset + 3)) : undefined;
36
- }
37
- offset += size;
38
- }
39
- }
40
- if (mime === "image/webp") {
41
- for (let offset = 12; offset + 8 <= bytes.length;) {
42
- const size = v.getUint32(offset + 4, true), start = offset + 8;
43
- if (start + size > bytes.length) return undefined;
44
- const kind = String.fromCharCode(...bytes.subarray(offset, offset + 4));
45
- const u24 = (at: number) => v.getUint8(at) | v.getUint8(at + 1) << 8 | v.getUint8(at + 2) << 16;
46
- if (kind === "VP8X" && size >= 10) return result(u24(start + 4) + 1, u24(start + 7) + 1);
47
- if (kind === "VP8L" && size >= 5 && bytes[start] === 0x2f) {
48
- const packed = v.getUint32(start + 1, true);
49
- return result((packed & 0x3fff) + 1, ((packed >>> 14) & 0x3fff) + 1);
50
- }
51
- if (kind === "VP8 " && size >= 10 && bytes[start + 3] === 0x9d && bytes[start + 4] === 1 && bytes[start + 5] === 0x2a)
52
- return result(v.getUint16(start + 6, true) & 0x3fff, v.getUint16(start + 8, true) & 0x3fff);
53
- offset = start + size + size % 2;
54
- }
55
- }
56
- return undefined;
57
- }
58
-
59
- /** Floors keep the area bound exact; small inputs are never upscaled. */
60
- export function modelImageDimensions(image: ImageDimensions): ImageDimensions {
61
- const scale = Math.min(1, MAX_MODEL_IMAGE_SIDE / image.width, MAX_MODEL_IMAGE_SIDE / image.height,
62
- Math.sqrt(MAX_MODEL_IMAGE_PIXELS / image.width / image.height));
63
- return { width: Math.max(1, Math.floor(image.width * scale)), height: Math.max(1, Math.floor(image.height * scale)) };
64
- }
65
-
66
- /** Pi rotates EXIF before resizing and rounds the short side; symmetric bounds preserve both orientations. */
67
- export function modelImageSide(image: ImageDimensions): number {
68
- const long = Math.max(image.width, image.height), short = Math.min(image.width, image.height);
69
- let side = Math.max(...Object.values(modelImageDimensions(image)));
70
- while (side * Math.max(1, Math.round(short * side / long)) > MAX_MODEL_IMAGE_PIXELS) side--;
71
- return side;
72
- }
@@ -1,46 +0,0 @@
1
- /** Immutable attachment metadata; D8 stores bytes once and projects references without I/O. */
2
- export interface ImageRef {
3
- readonly sha256: string;
4
- readonly mimeType: "image/png" | "image/jpeg" | "image/gif" | "image/webp";
5
- readonly width: number;
6
- readonly height: number;
7
- readonly bytes: number;
8
- readonly name: string;
9
- /** Submission-time policy; enabling images later cannot rewrite an earlier request prefix. */
10
- readonly blocked?: true;
11
- readonly original?: { readonly sha256: string; readonly width: number; readonly height: number };
12
- }
13
- export interface ImageContent { readonly type: "image"; readonly image: ImageRef; readonly data?: string }
14
- export type UserInput = string | { readonly text: string; readonly images?: readonly ImageRef[] };
15
- export function validImageRef(value: unknown): value is ImageRef {
16
- if (!value || typeof value !== "object") return false;
17
- const v = value as Record<string, unknown>;
18
- const digest = (s: unknown) => typeof s === "string" && /^[a-f0-9]{64}$/.test(s);
19
- const positive = (n: unknown) => typeof n === "number" && Number.isSafeInteger(n) && n > 0;
20
- if (v["blocked"] !== undefined && v["blocked"] !== true) return false;
21
- if (!digest(v["sha256"]) || !positive(v["width"]) || !positive(v["height"]) || !positive(v["bytes"]) ||
22
- typeof v["name"] !== "string" || !["image/png", "image/jpeg", "image/gif", "image/webp"].includes(String(v["mimeType"]))) return false;
23
- if (v["original"] === undefined) return true;
24
- if (!v["original"] || typeof v["original"] !== "object") return false;
25
- const o = v["original"] as Record<string, unknown>;
26
- return digest(o["sha256"]) && positive(o["width"]) && positive(o["height"]);
27
- }
28
- export function submissionImages(images: readonly ImageRef[], blocked: boolean): ImageRef[] {
29
- return images.map(image => {
30
- if (!validImageRef(image)) throw new Error("Invalid image reference");
31
- const { sha256, mimeType, width, height, bytes, name, original } = image;
32
- return { sha256, mimeType, width, height, bytes, name, ...(original ? { original } : {}), ...(blocked ? { blocked: true as const } : {}) };
33
- });
34
- }
35
- export function userInputData(input: UserInput, blocked?: boolean): { text: string; images?: readonly ImageRef[] } {
36
- return typeof input === "string" ? { text: input } : { text: input.text,
37
- ...(input.images?.length ? { images: blocked === undefined ? input.images : submissionImages(input.images, blocked) } : {}) };
38
- }
39
- export function imageDescription(image: ImageRef): string {
40
- const original = image.original;
41
- return `[Image: ${image.name}, ${image.width}x${image.height}${original ? `; original ${original.width}x${original.height}, coordinate scale ${(original.width / image.width).toFixed(2)}` : ""}]`;
42
- }
43
- /** Assumption, not provider billing: 32px patches plus framing; anchors replace this after the response. */
44
- export function estimateImageTokens(image: Pick<ImageRef, "width" | "height">): number {
45
- return 256 + Math.ceil(image.width / 32) * Math.ceil(image.height / 32) * 2;
46
- }
@@ -1,34 +0,0 @@
1
- /** Q.13: observed connectivity is a cache, never authorization or replay evidence. */
2
- export type IntegrationFailureCode = "offline" | "rate_limited" | "auth_required" | "protocol_error" | "timeout" | "unknown";
3
- export type IntegrationTime = { at: number; tick: number };
4
- export type IntegrationHealth = {
5
- state: "not_checked" | "ready" | "backoff" | "auth_required" | "protocol_error";
6
- observed: IntegrationTime | null;
7
- failures: number;
8
- retryAt: number | null;
9
- error: IntegrationFailureCode | null;
10
- };
11
- // Assumed bounds, not measured optima: avoid tight failure loops and unbounded server delays.
12
- export const INTEGRATION_BACKOFF_MS = 1_000;
13
- export const MAX_INTEGRATION_BACKOFF_MS = 60_000;
14
- export const INTEGRATION_HEALTH_TTL_MS = 30_000;
15
-
16
- export function initialIntegrationHealth(): IntegrationHealth {
17
- return { state: "not_checked", observed: null, failures: 0, retryAt: null, error: null };
18
- }
19
- export function readyIntegrationHealth(now: IntegrationTime): IntegrationHealth {
20
- return { ...initialIntegrationHealth(), state: "ready", observed: { ...now } };
21
- }
22
- export function failedIntegrationHealth(previous: IntegrationHealth, error: IntegrationFailureCode, now: IntegrationTime, retryAfterMs?: number): IntegrationHealth {
23
- const failures = Math.min(16, previous.failures + 1);
24
- const delay = Math.min(MAX_INTEGRATION_BACKOFF_MS, Math.max(INTEGRATION_BACKOFF_MS * 2 ** (failures - 1),
25
- Number.isFinite(retryAfterMs) ? Math.max(0, retryAfterMs!) : 0));
26
- const state = error === "auth_required" || error === "protocol_error" ? error : "backoff";
27
- return { state, observed: { ...now }, failures, retryAt: state === "backoff" ? now.tick + delay : null, error };
28
- }
29
- export function integrationHealthView(health: IntegrationHealth, now: IntegrationTime) {
30
- const age = health.observed === null ? null : Math.max(0, now.tick - health.observed.tick);
31
- return { state: health.state, observed_at: health.observed?.at ?? null, age_ms: age,
32
- stale: age === null || age >= INTEGRATION_HEALTH_TTL_MS, error: health.error,
33
- failures: health.failures, retry_after_ms: health.retryAt === null ? null : Math.max(0, health.retryAt - now.tick) };
34
- }
@@ -1,131 +0,0 @@
1
- /**
2
- * LoopEvent → one JSON object for `--mode json`.
3
- *
4
- * Declared here, not imported from runtime, so core stays below the loop
5
- * (D13: core does not import a layer above it). The kind union is kept in
6
- * lockstep with LoopEvent; a new kind makes the call site in apps fail to
7
- * type-check until this switch grows a case.
8
- *
9
- * Deltas return null. A pipe cannot retract a half-turn, and a retry would
10
- * then emit the same prefix twice — the same reason `-p` waits for the
11
- * complete block. The caller still sees `assistant_text` / `thinking` once
12
- * the turn has a final block.
13
- */
14
-
15
- import { assertNever } from "./exhaustive.ts";
16
- import type { ExecutionOutcome } from "./outcome.ts";
17
-
18
- export type JsonLoopKind =
19
- | "turn_start"
20
- | "assistant_text"
21
- | "thinking"
22
- | "text_delta"
23
- | "thinking_delta"
24
- | "discard"
25
- | "progress"
26
- | "tool_call"
27
- | "tool_result"
28
- | "usage"
29
- | "cut"
30
- | "stop_and_ask"
31
- | "branch_start"
32
- | "branch_done"
33
- | "done";
34
-
35
- /**
36
- * Structural match for runtime LoopEvent. Copied rather than imported so
37
- * this module cannot grow an upward dependency.
38
- */
39
- export interface JsonLoopEvent {
40
- kind: JsonLoopKind;
41
- text?: string;
42
- tool?: { name: string; input: unknown };
43
- ok?: boolean;
44
- usage?: {
45
- input_tokens: number;
46
- output_tokens: number;
47
- cache_creation_input_tokens: number;
48
- cache_read_input_tokens: number;
49
- node: number;
50
- ms: number;
51
- };
52
- prefix?: string;
53
- node?: number;
54
- reason?: string;
55
- at?: number;
56
- outcome?: ExecutionOutcome;
57
- }
58
-
59
- /**
60
- * `null` means an internal event that must not go on the wire.
61
- * `at` is supplied by the caller (ISO from the session clock) so this
62
- * function stays free of Date.
63
- */
64
- export function toJsonEvent(e: JsonLoopEvent, at: string): Record<string, unknown> | null {
65
- switch (e.kind) {
66
- case "text_delta":
67
- case "thinking_delta":
68
- case "discard":
69
- case "progress":
70
- return null;
71
- case "assistant_text":
72
- return { type: "assistant_text", text: e.text ?? "", at };
73
- case "thinking":
74
- return { type: "thinking", text: e.text ?? "", at };
75
- case "tool_call":
76
- return { type: "tool_call", tool: e.tool ?? { name: "", input: {} }, at };
77
- case "tool_result":
78
- return { type: "tool_result", text: e.text ?? "", ok: e.ok !== false,
79
- ...(e.outcome ? { outcome: e.outcome } : {}), at };
80
- case "usage":
81
- return {
82
- type: "usage",
83
- ...(e.usage !== undefined
84
- ? {
85
- usage: {
86
- input_tokens: e.usage.input_tokens,
87
- output_tokens: e.usage.output_tokens,
88
- cache_creation_input_tokens: e.usage.cache_creation_input_tokens,
89
- cache_read_input_tokens: e.usage.cache_read_input_tokens,
90
- node: e.usage.node,
91
- ms: e.usage.ms,
92
- },
93
- }
94
- : {}),
95
- ...(e.prefix !== undefined ? { prefix: e.prefix } : {}),
96
- ok: e.ok !== false,
97
- at,
98
- };
99
- case "turn_start":
100
- return { type: "turn_start", ...(e.node !== undefined ? { node: e.node } : {}), at };
101
- case "cut":
102
- return {
103
- type: "cut",
104
- ...(e.node !== undefined ? { node: e.node } : {}),
105
- ...(e.reason !== undefined ? { reason: e.reason } : {}),
106
- at,
107
- };
108
- case "stop_and_ask":
109
- return { type: "stop_and_ask", ...(e.reason !== undefined ? { reason: e.reason } : {}), at };
110
- case "done":
111
- return { type: "done", ...(e.reason !== undefined ? { reason: e.reason } : {}),
112
- ...(e.outcome ? { outcome: e.outcome } : {}), at };
113
- case "branch_start":
114
- return {
115
- type: "branch_start",
116
- ...(e.node !== undefined ? { node: e.node } : {}),
117
- ...(e.reason !== undefined ? { reason: e.reason } : {}),
118
- at,
119
- };
120
- case "branch_done":
121
- return {
122
- type: "branch_done",
123
- ...(e.outcome ? { outcome: e.outcome } : {}),
124
- ...(e.node !== undefined ? { node: e.node } : {}),
125
- ...(e.text !== undefined ? { text: e.text } : {}),
126
- at,
127
- };
128
- default:
129
- return assertNever(e.kind, "toJsonEvent");
130
- }
131
- }
@@ -1,46 +0,0 @@
1
- /** Parsed-JSON traversal and recoverable diagnostics (research N.5, RFC 6901). */
2
- // Assumed diagnostic budget, not a data limit: show only complete short keys.
3
- // Twelve forty-unit keys keep even JSON-escaped control characters below 4 KiB.
4
- const HINT_KEYS = 12;
5
- const HINT_KEY_UNITS = 40;
6
-
7
- export function validateJsonPointer(pointer: string): void {
8
- if ((pointer !== "" && !pointer.startsWith("/")) || /~(?:[^01]|$)/.test(pointer)) {
9
- throw new Error('invalid JSON pointer: use "" for the root, / between keys, ~0 for ~ and ~1 for /');
10
- }
11
- }
12
-
13
- function arrayIndex(key: string, length: number): boolean {
14
- const index = Number(key);
15
- return /^(0|[1-9][0-9]*)$/.test(key) && Number.isSafeInteger(index) && index < length;
16
- }
17
-
18
- function missingPointer(parent: unknown, segment: number): Error {
19
- const array = Array.isArray(parent);
20
- const kind = parent === null ? "null" : array ? "array" : typeof parent;
21
- const keys = parent !== null && typeof parent === "object"
22
- ? Object.keys(parent).filter((key) => !array || arrayIndex(key, parent.length)) : [];
23
- const shown = keys.filter((key) => key.length <= HINT_KEY_UNITS).sort().slice(0, HINT_KEYS);
24
- // Segment ordinal identifies the valid prefix without echoing unbounded input
25
- // paths or source values. Omitted names remain readable through that prefix.
26
- return new Error(`JSON pointer does not exist at segment ${segment}; parent type: ${kind}. ` +
27
- `Available keys (${shown.length} of ${keys.length}): ${JSON.stringify(shown)}; ` +
28
- `${keys.length - shown.length} omitted (long names or diagnostic budget). ` +
29
- `Inspect the pointer prefix before segment ${segment}; use pointer="" for the document root. ` +
30
- "The pointer / selects an empty key, not the root.");
31
- }
32
-
33
- export function resolveJsonPointer(document: unknown, pointer: string): unknown {
34
- validateJsonPointer(pointer);
35
- if (pointer === "") return document;
36
- let value = document;
37
- for (const [index, raw] of pointer.slice(1).split("/").entries()) {
38
- const key = raw.replaceAll("~1", "/").replaceAll("~0", "~");
39
- if (value === null || typeof value !== "object" ||
40
- (Array.isArray(value) && !arrayIndex(key, value.length)) || !Object.hasOwn(value, key)) {
41
- throw missingPointer(value, index + 1);
42
- }
43
- value = (value as Record<string, unknown>)[key];
44
- }
45
- return value;
46
- }