saeeol 1.3.0 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (537) hide show
  1. package/.turbo/turbo-typecheck.log +1 -0
  2. package/AGENTS.md +72 -0
  3. package/BUN_SHELL_MIGRATION_PLAN.md +136 -0
  4. package/Dockerfile +18 -0
  5. package/assets/saeeol.ico +0 -0
  6. package/bin/saeeol.cjs +0 -0
  7. package/database.db +0 -0
  8. package/drizzle.config.ts +10 -0
  9. package/git +0 -0
  10. package/migration/20260127222353_familiar_lady_ursula/migration.sql +90 -0
  11. package/migration/20260127222353_familiar_lady_ursula/snapshot.json +796 -0
  12. package/migration/20260211171708_add_project_commands/migration.sql +1 -0
  13. package/migration/20260211171708_add_project_commands/snapshot.json +806 -0
  14. package/migration/20260213144116_wakeful_the_professor/migration.sql +11 -0
  15. package/migration/20260213144116_wakeful_the_professor/snapshot.json +897 -0
  16. package/migration/20260225215848_workspace/migration.sql +7 -0
  17. package/migration/20260225215848_workspace/snapshot.json +959 -0
  18. package/migration/20260227213759_add_session_workspace_id/migration.sql +2 -0
  19. package/migration/20260227213759_add_session_workspace_id/snapshot.json +983 -0
  20. package/migration/20260228203230_blue_harpoon/migration.sql +17 -0
  21. package/migration/20260228203230_blue_harpoon/snapshot.json +1102 -0
  22. package/migration/20260303231226_add_workspace_fields/migration.sql +5 -0
  23. package/migration/20260303231226_add_workspace_fields/snapshot.json +1013 -0
  24. package/migration/20260309230000_move_org_to_state/migration.sql +3 -0
  25. package/migration/20260309230000_move_org_to_state/snapshot.json +1156 -0
  26. package/migration/20260312043431_session_message_cursor/migration.sql +4 -0
  27. package/migration/20260312043431_session_message_cursor/snapshot.json +1168 -0
  28. package/migration/20260323234822_events/migration.sql +13 -0
  29. package/migration/20260323234822_events/snapshot.json +1271 -0
  30. package/migration/20260410174513_workspace-name/migration.sql +16 -0
  31. package/migration/20260410174513_workspace-name/snapshot.json +1271 -0
  32. package/migration/20260413175956_chief_energizer/migration.sql +13 -0
  33. package/migration/20260413175956_chief_energizer/snapshot.json +1399 -0
  34. package/migration/20260423070820_add_icon_url_override/migration.sql +2 -0
  35. package/migration/20260423070820_add_icon_url_override/snapshot.json +1409 -0
  36. package/migration/20260428004200_add_session_path/migration.sql +1 -0
  37. package/migration/20260428004200_add_session_path/snapshot.json +1419 -0
  38. package/npm/bin/saeeol +42 -0
  39. package/npm/package.json +39 -0
  40. package/npm/postinstall.js +162 -0
  41. package/package.json +201 -207
  42. package/parsers-config.ts +289 -0
  43. package/script/build.ts +393 -0
  44. package/script/check-migrations.ts +16 -0
  45. package/script/fix-node-pty.ts +34 -0
  46. package/script/generate.ts +23 -0
  47. package/script/postinstall.mjs +189 -0
  48. package/script/publish.ts +200 -0
  49. package/script/run-workspace-server +106 -0
  50. package/script/schema.ts +63 -0
  51. package/script/test-runner.ts +420 -0
  52. package/script/time.ts +6 -0
  53. package/script/trace-imports.ts +153 -0
  54. package/script/upgrade-opentui.ts +64 -0
  55. package/scripts/diff-sdk-types.sh +52 -0
  56. package/specs/effect/facades.md +221 -0
  57. package/specs/effect/http-api.md +401 -0
  58. package/specs/effect/instance-context.md +309 -0
  59. package/specs/effect/loose-ends.md +34 -0
  60. package/specs/effect/migration.md +299 -0
  61. package/specs/effect/routes.md +64 -0
  62. package/specs/effect/schema.md +399 -0
  63. package/specs/effect/server-package.md +668 -0
  64. package/specs/effect/tools.md +90 -0
  65. package/specs/tui-plugins.md +433 -0
  66. package/specs/v2/api.ts +67 -0
  67. package/specs/v2/keymappings.md +10 -0
  68. package/specs/v2/message-shape.md +136 -0
  69. package/src/acp/agent-message.ts +1 -1
  70. package/src/acp/agent-utils.ts +1 -1
  71. package/src/boxes/ansi.ts +17 -0
  72. package/src/boxes/atomic-write.ts +35 -0
  73. package/src/boxes/b64.ts +58 -0
  74. package/src/boxes/bash-security.ts +129 -0
  75. package/src/boxes/bom.ts +18 -0
  76. package/src/boxes/cancel.ts +16 -0
  77. package/src/boxes/chop.ts +12 -0
  78. package/src/boxes/clamp.ts +3 -0
  79. package/src/boxes/compact.ts +9 -0
  80. package/src/boxes/cost-tracker.ts +116 -0
  81. package/src/boxes/dataurl.ts +29 -0
  82. package/src/boxes/delay.ts +27 -0
  83. package/src/boxes/diff-apply.ts +53 -0
  84. package/src/boxes/disposable.ts +13 -0
  85. package/src/boxes/err.ts +34 -0
  86. package/src/boxes/human.ts +47 -0
  87. package/src/boxes/iife.ts +9 -0
  88. package/src/boxes/latch.ts +8 -0
  89. package/src/boxes/memory.ts +198 -0
  90. package/src/boxes/net.ts +16 -0
  91. package/src/boxes/plural.ts +4 -0
  92. package/src/boxes/puny.ts +21 -0
  93. package/src/boxes/retry.ts +49 -0
  94. package/src/boxes/rwlock.ts +41 -0
  95. package/src/boxes/schedule.ts +71 -0
  96. package/src/boxes/scope.ts +21 -0
  97. package/src/boxes/tokens.ts +9 -0
  98. package/src/boxes/ttl-cache.ts +63 -0
  99. package/src/boxes/typed-event.ts +51 -0
  100. package/src/boxes/uid.ts +50 -0
  101. package/src/boxes/wave6.test.ts +296 -0
  102. package/src/boxes/wildcard.ts +58 -0
  103. package/src/bus/global.ts +1 -1
  104. package/src/cli/cmd/github-run-api.ts +2 -2
  105. package/src/cli/cmd/run-events.ts +2 -2
  106. package/src/cli/cmd/tui/component/logo.tsx +1 -1
  107. package/src/cli/cmd/tui/component/prompt/use-prompt-memos.ts +2 -2
  108. package/src/cli/cmd/tui/context/app/editor-zed.ts +1 -1
  109. package/src/cli/cmd/tui/context/app/editor.ts +1 -1
  110. package/src/cli/cmd/tui/context/app/theme.tsx +1 -1
  111. package/src/cli/cmd/tui/util/revert-diff.ts +1 -1
  112. package/src/overlay/cli/cmd/roll-call-call.ts +1 -1
  113. package/src/overlay/cost-tracker/format.ts +1 -1
  114. package/src/overlay/cost-tracker/index.ts +4 -4
  115. package/src/overlay/cost-tracker/state.ts +2 -2
  116. package/src/overlay/cost-tracker/types.ts +2 -2
  117. package/src/overlay/memory/age.ts +1 -1
  118. package/src/overlay/memory/index.ts +4 -4
  119. package/src/overlay/memory/paths.ts +2 -2
  120. package/src/overlay/memory/scan.ts +1 -1
  121. package/src/overlay/memory/types.ts +2 -2
  122. package/src/overlay/tool/bash-security.ts +3 -3
  123. package/src/overlay/util/url.ts +1 -1
  124. package/src/plugin/codex-auth.ts +1 -1
  125. package/src/provider/model-cache.ts +2 -2
  126. package/src/provider/provider-resolve.ts +3 -3
  127. package/src/provider/transform-message.ts +1 -1
  128. package/src/server/routes/game.ts +284 -0
  129. package/src/server/server.ts +2 -0
  130. package/src/session/core/compaction/compaction-helpers.ts +1 -1
  131. package/src/session/core/compaction/compaction.ts +1 -1
  132. package/src/session/core/session.ts +2 -0
  133. package/src/sessions/ingest-queue.ts +2 -2
  134. package/src/sessions/remote-ws.ts +1 -1
  135. package/src/tool/workflow/question.ts +1 -1
  136. package/src/util/abort.ts +1 -1
  137. package/src/util/bom.ts +2 -2
  138. package/src/util/color.ts +1 -1
  139. package/src/util/data-url.ts +1 -1
  140. package/src/util/defer.ts +1 -1
  141. package/src/util/error.ts +2 -2
  142. package/src/util/filesystem.ts +2 -2
  143. package/src/util/format.ts +1 -1
  144. package/src/util/iife.ts +1 -1
  145. package/src/util/local-context.ts +1 -1
  146. package/src/util/locale.ts +2 -2
  147. package/src/util/lock.ts +1 -1
  148. package/src/util/network.ts +1 -1
  149. package/src/util/signal.ts +1 -1
  150. package/src/util/token.ts +1 -1
  151. package/src/util/wildcard.ts +1 -1
  152. package/sst-env.d.ts +10 -0
  153. package/test/AGENTS.md +133 -0
  154. package/test/account/repo.test.ts +352 -0
  155. package/test/account/service.test.ts +456 -0
  156. package/test/acp/agent-interface.test.ts +51 -0
  157. package/test/acp/event-subscription.test.ts +725 -0
  158. package/test/agent/agent.test.ts +890 -0
  159. package/test/auth/auth.test.ts +86 -0
  160. package/test/bun/registry.test.ts +75 -0
  161. package/test/bus/bus-effect.test.ts +161 -0
  162. package/test/bus/bus-integration.test.ts +87 -0
  163. package/test/bus/bus.test.ts +219 -0
  164. package/test/cli/account.test.ts +26 -0
  165. package/test/cli/auto-mode.test.ts +75 -0
  166. package/test/cli/bin-saeeol.test.ts +8 -0
  167. package/test/cli/cmd/tui/prompt-part.test.ts +47 -0
  168. package/test/cli/cmd/tui/prompt-traits.test.ts +38 -0
  169. package/test/cli/cmd/tui/sync.test.tsx +159 -0
  170. package/test/cli/error.test.ts +18 -0
  171. package/test/cli/github-action.test.ts +198 -0
  172. package/test/cli/github-remote.test.ts +85 -0
  173. package/test/cli/import.test.ts +97 -0
  174. package/test/cli/install-artifact.test.ts +72 -0
  175. package/test/cli/plugin-auth-picker.test.ts +120 -0
  176. package/test/cli/pr.test.ts +59 -0
  177. package/test/cli/tui/editor-context-zed.test.ts +356 -0
  178. package/test/cli/tui/editor-context.test.tsx +228 -0
  179. package/test/cli/tui/keybind-plugin.test.ts +90 -0
  180. package/test/cli/tui/markdown.test.ts +161 -0
  181. package/test/cli/tui/plugin-add.test.ts +111 -0
  182. package/test/cli/tui/plugin-install.test.ts +87 -0
  183. package/test/cli/tui/plugin-lifecycle.test.ts +224 -0
  184. package/test/cli/tui/plugin-loader-entrypoint.test.ts +484 -0
  185. package/test/cli/tui/plugin-loader-pure.test.ts +71 -0
  186. package/test/cli/tui/plugin-loader.test.ts +816 -0
  187. package/test/cli/tui/plugin-toggle.test.ts +157 -0
  188. package/test/cli/tui/revert-diff.test.ts +35 -0
  189. package/test/cli/tui/slot-replace.test.tsx +47 -0
  190. package/test/cli/tui/theme-store.test.ts +54 -0
  191. package/test/cli/tui/thread.test.ts +28 -0
  192. package/test/cli/tui/transcript.test.ts +426 -0
  193. package/test/cli/tui/usage.test.ts +60 -0
  194. package/test/cli/tui/use-event.test.tsx +175 -0
  195. package/test/config/agent-color.test.ts +67 -0
  196. package/test/config/config.test.ts +2544 -0
  197. package/test/config/fixtures/empty-frontmatter.md +4 -0
  198. package/test/config/fixtures/frontmatter.md +28 -0
  199. package/test/config/fixtures/markdown-header.md +11 -0
  200. package/test/config/fixtures/no-frontmatter.md +1 -0
  201. package/test/config/fixtures/weird-model-id.md +13 -0
  202. package/test/config/lsp.test.ts +87 -0
  203. package/test/config/markdown.test.ts +228 -0
  204. package/test/config/plugin.test.ts +0 -0
  205. package/test/config/tui.test.ts +624 -0
  206. package/test/control-plane/adapters.test.ts +71 -0
  207. package/test/control-plane/workspace.test.ts +1526 -0
  208. package/test/effect/app-runtime-logger.test.ts +98 -0
  209. package/test/effect/config-service.test.ts +65 -0
  210. package/test/effect/instance-state.test.ts +394 -0
  211. package/test/effect/run-service.test.ts +89 -0
  212. package/test/effect/runner.test.ts +523 -0
  213. package/test/fake/provider.ts +82 -0
  214. package/test/file/fsmonitor.test.ts +68 -0
  215. package/test/file/ignore.test.ts +10 -0
  216. package/test/file/index.test.ts +954 -0
  217. package/test/file/path-traversal.test.ts +205 -0
  218. package/test/file/ripgrep.test.ts +226 -0
  219. package/test/file/watcher.test.ts +249 -0
  220. package/test/filesystem/filesystem.test.ts +319 -0
  221. package/test/fixture/db.ts +11 -0
  222. package/test/fixture/fixture.test.ts +26 -0
  223. package/test/fixture/fixture.ts +175 -0
  224. package/test/fixture/flock-worker.ts +72 -0
  225. package/test/fixture/log-init-worker.ts +62 -0
  226. package/test/fixture/lsp/fake-lsp-server.js +249 -0
  227. package/test/fixture/plug-worker.ts +93 -0
  228. package/test/fixture/plugin-meta-worker.ts +19 -0
  229. package/test/fixture/skills/agents-sdk/SKILL.md +152 -0
  230. package/test/fixture/skills/cloudflare/SKILL.md +211 -0
  231. package/test/fixture/skills/index.json +6 -0
  232. package/test/fixture/tui-plugin.ts +323 -0
  233. package/test/fixture/tui-runtime.ts +31 -0
  234. package/test/format/format.test.ts +272 -0
  235. package/test/git/git.test.ts +128 -0
  236. package/test/ide/ide.test.ts +82 -0
  237. package/test/installation/installation.test.ts +168 -0
  238. package/test/keybind.test.ts +421 -0
  239. package/test/lib/effect.ts +53 -0
  240. package/test/lib/filesystem.ts +10 -0
  241. package/test/lib/llm-server.ts +778 -0
  242. package/test/lib/websocket.ts +46 -0
  243. package/test/lsp/client.test.ts +482 -0
  244. package/test/lsp/index.test.ts +160 -0
  245. package/test/lsp/launch.test.ts +22 -0
  246. package/test/lsp/lifecycle.test.ts +184 -0
  247. package/test/ltm/ltm.test.ts +230 -0
  248. package/test/mcp/headers.test.ts +178 -0
  249. package/test/mcp/lifecycle.test.ts +787 -0
  250. package/test/mcp/oauth-auto-connect.test.ts +311 -0
  251. package/test/mcp/oauth-browser.test.ts +276 -0
  252. package/test/mcp/oauth-callback.test.ts +34 -0
  253. package/test/memory/abort-leak-webfetch.ts +49 -0
  254. package/test/memory/abort-leak.test.ts +128 -0
  255. package/test/patch/patch.test.ts +348 -0
  256. package/test/permission/arity.test.ts +33 -0
  257. package/test/permission/next.test.ts +1227 -0
  258. package/test/permission/next.toConfig.test.ts +110 -0
  259. package/test/permission-task.test.ts +326 -0
  260. package/test/plugin/auth-override.test.ts +79 -0
  261. package/test/plugin/cloudflare.test.ts +68 -0
  262. package/test/plugin/codex.test.ts +123 -0
  263. package/test/plugin/github-copilot-models.test.ts +261 -0
  264. package/test/plugin/install-concurrency.test.ts +140 -0
  265. package/test/plugin/install.test.ts +570 -0
  266. package/test/plugin/loader-shared.test.ts +1169 -0
  267. package/test/plugin/meta.test.ts +137 -0
  268. package/test/plugin/plugin-contract.test.ts +291 -0
  269. package/test/plugin/shared.test.ts +88 -0
  270. package/test/plugin/trigger.test.ts +102 -0
  271. package/test/plugin/workspace-adapter.test.ts +109 -0
  272. package/test/preload.ts +77 -0
  273. package/test/project/instance.test.ts +276 -0
  274. package/test/project/migrate-global.test.ts +152 -0
  275. package/test/project/project.test.ts +600 -0
  276. package/test/project/vcs.test.ts +286 -0
  277. package/test/project/worktree-remove.test.ts +126 -0
  278. package/test/project/worktree.test.ts +223 -0
  279. package/test/provider/amazon-bedrock.test.ts +462 -0
  280. package/test/provider/copilot/convert-to-copilot-messages.test.ts +523 -0
  281. package/test/provider/copilot/copilot-chat-model.test.ts +592 -0
  282. package/test/provider/gitlab-duo.test.ts +413 -0
  283. package/test/provider/local.test.ts +208 -0
  284. package/test/provider/models.test.ts +261 -0
  285. package/test/provider/provider-category.test.ts +190 -0
  286. package/test/provider/provider.test.ts +2758 -0
  287. package/test/provider/transform.test.ts +3681 -0
  288. package/test/pty/pty-output-isolation.test.ts +147 -0
  289. package/test/pty/pty-session.test.ts +102 -0
  290. package/test/pty/pty-shell.test.ts +104 -0
  291. package/test/question/question.test.ts +490 -0
  292. package/test/saeeol/agent-global-config-dirs.test.ts +24 -0
  293. package/test/saeeol/agent-manager-tool.test.ts +71 -0
  294. package/test/saeeol/agent-permission-overrides.test.ts +75 -0
  295. package/test/saeeol/agent-skill-permissions.test.ts +37 -0
  296. package/test/saeeol/ask-agent-permissions.test.ts +303 -0
  297. package/test/saeeol/bash-hierarchy.test.ts +64 -0
  298. package/test/saeeol/bash-permission-metadata.test.ts +66 -0
  299. package/test/saeeol/bash-security-extended.test.ts +243 -0
  300. package/test/saeeol/bedrock-claude-empty-content.test.ts +138 -0
  301. package/test/saeeol/boxes-integration.test.ts +415 -0
  302. package/test/saeeol/builtin-skills.test.ts +75 -0
  303. package/test/saeeol/cleanup.ts +28 -0
  304. package/test/saeeol/cli/dev-setup.test.ts +74 -0
  305. package/test/saeeol/cli/roll-call.test.ts +161 -0
  306. package/test/saeeol/cli-run-auto-helper.test.ts +58 -0
  307. package/test/saeeol/codex-auth-refresh.test.ts +124 -0
  308. package/test/saeeol/commit-message/generate.test.ts +188 -0
  309. package/test/saeeol/commit-message/git-context.test.ts +303 -0
  310. package/test/saeeol/commit-message-windows.test.ts +38 -0
  311. package/test/saeeol/compaction-payload-recovery.test.ts +406 -0
  312. package/test/saeeol/compaction-preservation-audit.test.ts +122 -0
  313. package/test/saeeol/compaction-skip-guard.test.ts +224 -0
  314. package/test/saeeol/compaction-smart-select.test.ts +100 -0
  315. package/test/saeeol/config/config.test.ts +166 -0
  316. package/test/saeeol/config/indexing-default-plugin.test.ts +82 -0
  317. package/test/saeeol/config/opentelemetry-default.test.ts +29 -0
  318. package/test/saeeol/config-gitignore.test.ts +70 -0
  319. package/test/saeeol/config-injector.test.ts +305 -0
  320. package/test/saeeol/config-resilience.test.ts +234 -0
  321. package/test/saeeol/config-validation.test.ts +183 -0
  322. package/test/saeeol/cost-propagation.test.ts +94 -0
  323. package/test/saeeol/cost-tracker-extended.test.ts +141 -0
  324. package/test/saeeol/cost-tracker.test.ts +64 -0
  325. package/test/saeeol/custom-provider-delete.test.ts +149 -0
  326. package/test/saeeol/diff-full.test.ts +226 -0
  327. package/test/saeeol/edit-permission-filediff.test.ts +223 -0
  328. package/test/saeeol/encoding.test.ts +364 -0
  329. package/test/saeeol/enhance-prompt.test.ts +61 -0
  330. package/test/saeeol/ensure-plan-dir.test.ts +32 -0
  331. package/test/saeeol/errors.test.ts +144 -0
  332. package/test/saeeol/external-directory-boundary.test.ts +96 -0
  333. package/test/saeeol/gateway-headers.test.ts +88 -0
  334. package/test/saeeol/help.test.ts +191 -0
  335. package/test/saeeol/ignore-migrator.test.ts +308 -0
  336. package/test/saeeol/indexing-auth.test.ts +45 -0
  337. package/test/saeeol/indexing-feature.test.ts +44 -0
  338. package/test/saeeol/indexing-label.test.ts +70 -0
  339. package/test/saeeol/indexing-startup.test.ts +381 -0
  340. package/test/saeeol/indexing-worktree.test.ts +73 -0
  341. package/test/saeeol/instruction.test.ts +136 -0
  342. package/test/saeeol/lancedb-runtime.test.ts +116 -0
  343. package/test/saeeol/loader-auth.test.ts +168 -0
  344. package/test/saeeol/local-model.test.ts +621 -0
  345. package/test/saeeol/logo.test.ts +31 -0
  346. package/test/saeeol/lsp-typescript-lightweight.test.ts +89 -0
  347. package/test/saeeol/mcp-branding.test.ts +33 -0
  348. package/test/saeeol/mcp-docker-rm.test.ts +32 -0
  349. package/test/saeeol/mcp-migrator.test.ts +736 -0
  350. package/test/saeeol/mcp-oauth-callback.test.ts +33 -0
  351. package/test/saeeol/memory-io.test.ts +198 -0
  352. package/test/saeeol/memory-paths.test.ts +87 -0
  353. package/test/saeeol/memory-security.test.ts +166 -0
  354. package/test/saeeol/model-cache-org.test.ts +164 -0
  355. package/test/saeeol/model-info-panel-utils.test.ts +52 -0
  356. package/test/saeeol/model-info-panel.types.test.ts +7 -0
  357. package/test/saeeol/models-401-fallback.test.ts +52 -0
  358. package/test/saeeol/modes-migrator.test.ts +320 -0
  359. package/test/saeeol/nvidia-headers.test.ts +74 -0
  360. package/test/saeeol/patch-jsonc.test.ts +73 -0
  361. package/test/saeeol/patch.test.ts +172 -0
  362. package/test/saeeol/paths.test.ts +265 -0
  363. package/test/saeeol/permission/config-paths.test.ts +174 -0
  364. package/test/saeeol/permission/env-read.test.ts +149 -0
  365. package/test/saeeol/permission/external-directory-allow.test.ts +327 -0
  366. package/test/saeeol/permission/next.always-rules.test.ts +882 -0
  367. package/test/saeeol/permission/next.reply-http.test.ts +205 -0
  368. package/test/saeeol/permission/next.reply-routing.test.ts +184 -0
  369. package/test/saeeol/plan-exit-detection.test.ts +494 -0
  370. package/test/saeeol/plan-followup.test.ts +1376 -0
  371. package/test/saeeol/project-config-update.test.ts +120 -0
  372. package/test/saeeol/project-id.test.ts +455 -0
  373. package/test/saeeol/provider-cost.test.ts +171 -0
  374. package/test/saeeol/provider-list-failed-state.test.ts +100 -0
  375. package/test/saeeol/question-dismiss-all.test.ts +174 -0
  376. package/test/saeeol/read-directory.test.ts +116 -0
  377. package/test/saeeol/rules-migrator.test.ts +257 -0
  378. package/test/saeeol/run-auto.test.ts +176 -0
  379. package/test/saeeol/run-network.test.ts +224 -0
  380. package/test/saeeol/semantic-search.test.ts +186 -0
  381. package/test/saeeol/server/permission-allow-everything.test.ts +125 -0
  382. package/test/saeeol/session/instruction-substitution.test.ts +72 -0
  383. package/test/saeeol/session/platform-attribution.test.ts +118 -0
  384. package/test/saeeol/session/session.test.ts +105 -0
  385. package/test/saeeol/session-compaction-cap.test.ts +399 -0
  386. package/test/saeeol/session-compaction-chunks.test.ts +501 -0
  387. package/test/saeeol/session-compaction-safety.test.ts +481 -0
  388. package/test/saeeol/session-fork-remap.test.ts +251 -0
  389. package/test/saeeol/session-import-service.test.ts +114 -0
  390. package/test/saeeol/session-list.test.ts +47 -0
  391. package/test/saeeol/session-message-metadata.test.ts +128 -0
  392. package/test/saeeol/session-overflow.test.ts +78 -0
  393. package/test/saeeol/session-processor-empty-tool-calls.test.ts +571 -0
  394. package/test/saeeol/session-processor-network-offline.test.ts +204 -0
  395. package/test/saeeol/session-processor-retry-limit.test.ts +238 -0
  396. package/test/saeeol/session-processor-review-telemetry.test.ts +82 -0
  397. package/test/saeeol/session-prompt-compaction-safety.test.ts +517 -0
  398. package/test/saeeol/session-prompt-queue.test.ts +815 -0
  399. package/test/saeeol/sessions/inflight-cache.test.ts +157 -0
  400. package/test/saeeol/sessions/ingest-queue.test.ts +402 -0
  401. package/test/saeeol/sessions/remote-protocol.test.ts +258 -0
  402. package/test/saeeol/sessions/remote-sender.test.ts +1036 -0
  403. package/test/saeeol/sessions/remote-ws.test.ts +367 -0
  404. package/test/saeeol/sessions/sessions-enable-remote.test.disable +181 -0
  405. package/test/saeeol/slot-prop-reactivity.test.ts +142 -0
  406. package/test/saeeol/snapshot-cache.test.ts +84 -0
  407. package/test/saeeol/snapshot-freeze-repro.test.ts +100 -0
  408. package/test/saeeol/snapshot-track-timeout.test.ts +519 -0
  409. package/test/saeeol/stats-subagent-cost.test.ts +123 -0
  410. package/test/saeeol/suggestion/auto-dismiss.test.ts +65 -0
  411. package/test/saeeol/suggestion/suggestion.test.ts +145 -0
  412. package/test/saeeol/suggestion/tool.test.ts +298 -0
  413. package/test/saeeol/summary-file-diff.test.ts +28 -0
  414. package/test/saeeol/system-prompt.test.ts +142 -0
  415. package/test/saeeol/task-nesting.test.ts +193 -0
  416. package/test/saeeol/telemetry/feedback.test.ts +8 -0
  417. package/test/saeeol/todo-view.test.ts +57 -0
  418. package/test/saeeol/tool-encoding.test.ts +455 -0
  419. package/test/saeeol/tool-registry-indexing-import-failure.test.ts +49 -0
  420. package/test/saeeol/tool-registry-indexing.test.ts +236 -0
  421. package/test/saeeol/tool-registry-semantic-import-failure.test.ts +55 -0
  422. package/test/saeeol/tool-task-model.test.ts +352 -0
  423. package/test/saeeol/transform-opus-4.7.test.ts +89 -0
  424. package/test/saeeol/tui-diff.test.ts +91 -0
  425. package/test/saeeol/tui-sync.test.ts +80 -0
  426. package/test/saeeol/util/url.test.ts +141 -0
  427. package/test/saeeol/workflows-migrator.test.ts +261 -0
  428. package/test/saeeol/worktree-diff-summary.test.ts +64 -0
  429. package/test/saeeol/worktree-diff.test.ts +223 -0
  430. package/test/saeeol/worktree-remove-lock.test.ts +82 -0
  431. package/test/server/AGENTS.md +15 -0
  432. package/test/server/contract.test.ts +357 -0
  433. package/test/server/experimental-session-list.test.ts +157 -0
  434. package/test/server/global-session-list.test.ts +155 -0
  435. package/test/server/httpapi-authorization.test.ts +103 -0
  436. package/test/server/httpapi-bridge.test.ts +440 -0
  437. package/test/server/httpapi-config.test.ts +67 -0
  438. package/test/server/httpapi-cors.test.ts +89 -0
  439. package/test/server/httpapi-event.test.ts +57 -0
  440. package/test/server/httpapi-experimental.test.ts +219 -0
  441. package/test/server/httpapi-file.test.ts +79 -0
  442. package/test/server/httpapi-instance-context.test.ts +237 -0
  443. package/test/server/httpapi-instance.legacy.test.ts +140 -0
  444. package/test/server/httpapi-instance.test.ts +83 -0
  445. package/test/server/httpapi-json-parity.test.ts +263 -0
  446. package/test/server/httpapi-mcp-oauth.test.ts +76 -0
  447. package/test/server/httpapi-mcp.test.ts +189 -0
  448. package/test/server/httpapi-provider.test.ts +153 -0
  449. package/test/server/httpapi-pty-websocket.test.ts +16 -0
  450. package/test/server/httpapi-pty.test.ts +175 -0
  451. package/test/server/httpapi-raw-route-auth.test.ts +89 -0
  452. package/test/server/httpapi-sdk.test.ts +681 -0
  453. package/test/server/httpapi-session.test.ts +464 -0
  454. package/test/server/httpapi-sync.test.ts +130 -0
  455. package/test/server/httpapi-tui.test.ts +121 -0
  456. package/test/server/httpapi-workspace-routing.test.ts +471 -0
  457. package/test/server/httpapi-workspace.test.ts +427 -0
  458. package/test/server/lib/conformance.ts +88 -0
  459. package/test/server/lib/stateful.ts +112 -0
  460. package/test/server/project-init-git.test.ts +113 -0
  461. package/test/server/proxy-util.test.ts +113 -0
  462. package/test/server/session-actions.test.ts +49 -0
  463. package/test/server/session-list.test.ts +238 -0
  464. package/test/server/session-messages.test.ts +167 -0
  465. package/test/server/session-select.test.ts +100 -0
  466. package/test/server/trace-attributes.test.ts +76 -0
  467. package/test/server/workspace-proxy.test.ts +165 -0
  468. package/test/server/workspace-routing.test.ts +85 -0
  469. package/test/session/compaction.test.ts +2420 -0
  470. package/test/session/instruction.test.ts +247 -0
  471. package/test/session/llm.test.ts +1273 -0
  472. package/test/session/message-v2.test.ts +1291 -0
  473. package/test/session/messages-pagination.test.ts +1173 -0
  474. package/test/session/network.test.ts +249 -0
  475. package/test/session/processor-effect.test.ts +847 -0
  476. package/test/session/prompt.test.ts +2131 -0
  477. package/test/session/retry.test.ts +340 -0
  478. package/test/session/revert-compact.test.ts +639 -0
  479. package/test/session/schema-decoding.test.ts +311 -0
  480. package/test/session/session-entry-stepper.test.ts +917 -0
  481. package/test/session/session-schema.test.ts +76 -0
  482. package/test/session/snapshot-tool-race.test.ts +257 -0
  483. package/test/session/structured-output-integration.test.ts +265 -0
  484. package/test/session/structured-output.test.ts +381 -0
  485. package/test/session/system.test.ts +73 -0
  486. package/test/share/share-next.test.ts +333 -0
  487. package/test/shell/shell.test.ts +99 -0
  488. package/test/skill/discovery.test.ts +116 -0
  489. package/test/skill/skill.test.ts +393 -0
  490. package/test/snapshot/snapshot.test.ts +1531 -0
  491. package/test/storage/db.test.ts +23 -0
  492. package/test/storage/json-migration.test.ts +832 -0
  493. package/test/storage/storage.test.ts +293 -0
  494. package/test/suggestion/suggestion.test.ts +1 -0
  495. package/test/sync/index.test.ts +256 -0
  496. package/test/tool/__snapshots__/parameters.test.ts.snap +500 -0
  497. package/test/tool/__snapshots__/tool.test.ts.snap +9 -0
  498. package/test/tool/apply_patch.test.ts +614 -0
  499. package/test/tool/bash.test.ts +1225 -0
  500. package/test/tool/diagnostics-filter.test.ts +55 -0
  501. package/test/tool/edit.test.ts +754 -0
  502. package/test/tool/external-directory.test.ts +169 -0
  503. package/test/tool/fixtures/large-image.png +0 -0
  504. package/test/tool/fixtures/models-api.json +65179 -0
  505. package/test/tool/glob.test.ts +107 -0
  506. package/test/tool/grep.test.ts +114 -0
  507. package/test/tool/lsp.test.ts +187 -0
  508. package/test/tool/parameters.test.ts +243 -0
  509. package/test/tool/question.test.ts +129 -0
  510. package/test/tool/read.test.ts +500 -0
  511. package/test/tool/recall.test.ts +151 -0
  512. package/test/tool/registry.test.ts +203 -0
  513. package/test/tool/skill.test.ts +135 -0
  514. package/test/tool/suggest.test.ts +1 -0
  515. package/test/tool/task.test.ts +612 -0
  516. package/test/tool/tool-define.test.ts +99 -0
  517. package/test/tool/truncation.test.ts +260 -0
  518. package/test/tool/webfetch.test.ts +103 -0
  519. package/test/tool/write.test.ts +291 -0
  520. package/test/util/data-url.test.ts +14 -0
  521. package/test/util/effect-zod.test.ts +754 -0
  522. package/test/util/error.test.ts +38 -0
  523. package/test/util/filesystem.test.ts +656 -0
  524. package/test/util/format.test.ts +59 -0
  525. package/test/util/glob.test.ts +164 -0
  526. package/test/util/iife.test.ts +36 -0
  527. package/test/util/lazy.test.ts +50 -0
  528. package/test/util/lock.test.ts +72 -0
  529. package/test/util/log.test.ts +86 -0
  530. package/test/util/module.test.ts +59 -0
  531. package/test/util/process.test.ts +128 -0
  532. package/test/util/timeout.test.ts +21 -0
  533. package/test/util/which.test.ts +100 -0
  534. package/test/util/wildcard.test.ts +90 -0
  535. package/test/workspace/workspace-restore.test.ts +296 -0
  536. package/src/provider/models-snapshot.d.ts +0 -2
  537. package/src/provider/models-snapshot.js +0 -3
@@ -0,0 +1,420 @@
1
+ //
2
+ // Custom test runner that executes each test file in its own isolated process.
3
+ // Prevents cross-contamination between test files by ensuring separate PIDs,
4
+ // temp directories, in-memory databases, and environment state.
5
+
6
+ import os from "os"
7
+ import path from "path"
8
+ import fs from "fs/promises"
9
+
10
+ const root = path.resolve(import.meta.dir, "..")
11
+ const argv = process.argv.slice(2)
12
+
13
+ // ---------------------------------------------------------------------------
14
+ // Help
15
+ // ---------------------------------------------------------------------------
16
+
17
+ if (argv.includes("--help") || argv.includes("-h")) {
18
+ console.log(
19
+ [
20
+ "",
21
+ "Usage: bun run script/test-runner.ts [options] [patterns...]",
22
+ "",
23
+ "Runs test files in isolated parallel processes to prevent cross-contamination.",
24
+ "",
25
+ "Options:",
26
+ " --ci Enable JUnit XML output to .artifacts/unit/junit.xml",
27
+ " --concurrency <N> Max parallel processes (default: min(4, CPU count))",
28
+ " --timeout <ms> Per-test timeout passed to bun test (default: 60000)",
29
+ " --file-timeout <ms> Per-file process timeout (default: 300000)",
30
+ " --retries <N> Extra attempts for failing files (default: 1)",
31
+ " --bail Stop on first failure",
32
+ " --verbose Show full output for every file",
33
+ " -h, --help Show this help",
34
+ "",
35
+ "Positional:",
36
+ " [patterns...] Filter test files by substring match",
37
+ "",
38
+ ].join("\n"),
39
+ )
40
+ process.exit(0)
41
+ }
42
+
43
+ // ---------------------------------------------------------------------------
44
+ // CLI parsing
45
+ // ---------------------------------------------------------------------------
46
+
47
+ function opt(name: string, fallback: number) {
48
+ const i = argv.indexOf(`--${name}`)
49
+ return i >= 0 && i + 1 < argv.length ? Number(argv[i + 1]) || fallback : fallback
50
+ }
51
+
52
+ const ci = argv.includes("--ci")
53
+ const bail = argv.includes("--bail")
54
+ const verbose = argv.includes("--verbose")
55
+ // Cap concurrency at 4 even on bigger runners: the bottleneck is shared
56
+ // resources (ports, global filesystem like ~/.local/share/saeeol), not CPU.
57
+ // Eight parallel processes was triggering port/FS races, not going faster.
58
+ const concurrency = opt("concurrency", Math.min(4, os.cpus().length))
59
+ const timeout = opt("timeout", 60000)
60
+ const deadline = opt("file-timeout", 300000)
61
+ const retries = opt("retries", 1)
62
+
63
+ const valued = new Set(["--concurrency", "--timeout", "--file-timeout", "--retries"])
64
+ const patterns = argv.filter((arg, i) => {
65
+ if (arg.startsWith("-")) return false
66
+ if (i > 0 && valued.has(argv[i - 1])) return false
67
+ return true
68
+ })
69
+
70
+ // ---------------------------------------------------------------------------
71
+ // Colors
72
+ // ---------------------------------------------------------------------------
73
+
74
+ const tty = !!process.stdout.isTTY
75
+ const green = (s: string) => (tty ? `\x1b[32m${s}\x1b[0m` : s)
76
+ const red = (s: string) => (tty ? `\x1b[31m${s}\x1b[0m` : s)
77
+ const yellow = (s: string) => (tty ? `\x1b[33m${s}\x1b[0m` : s)
78
+ const dim = (s: string) => (tty ? `\x1b[2m${s}\x1b[0m` : s)
79
+ const bold = (s: string) => (tty ? `\x1b[1m${s}\x1b[0m` : s)
80
+
81
+ // ---------------------------------------------------------------------------
82
+ // File discovery
83
+ // ---------------------------------------------------------------------------
84
+
85
+ const glob = new Bun.Glob("**/*.test.{ts,tsx}")
86
+ const all = (await Array.fromAsync(glob.scan({ cwd: path.join(root, "test") }))).sort()
87
+
88
+ export const skipped = new Set([
89
+ // Upstream browser OAuth integration tests bind the fixed callback port and
90
+ // race with other parallel OAuth tests in CI.
91
+ "mcp/oauth-browser.test.ts",
92
+ ])
93
+
94
+ const matched =
95
+ patterns.length > 0 ? all.filter((f) => patterns.some((p) => f.includes(p) || path.join("test", f).includes(p))) : all
96
+ const files = patterns.length > 0 ? matched : matched.filter((f) => !skipped.has(f))
97
+
98
+ if (files.length === 0) {
99
+ console.log("No test files found")
100
+ process.exit(0)
101
+ }
102
+
103
+ // ---------------------------------------------------------------------------
104
+ // Types
105
+ // ---------------------------------------------------------------------------
106
+
107
+ type Result = {
108
+ file: string
109
+ passed: boolean
110
+ code: number
111
+ stdout: string
112
+ stderr: string
113
+ duration: number
114
+ timedout: boolean
115
+ attempts: number
116
+ }
117
+
118
+ // ---------------------------------------------------------------------------
119
+ // Setup
120
+ // ---------------------------------------------------------------------------
121
+
122
+ const xmldir = ci ? path.join(os.tmpdir(), `saeeol-junit-${process.pid}`) : ""
123
+ if (ci) await fs.mkdir(xmldir, { recursive: true })
124
+
125
+ const counter = { done: 0 }
126
+ const pad = String(files.length).length
127
+
128
+ // ---------------------------------------------------------------------------
129
+ // Run a single test file
130
+ // ---------------------------------------------------------------------------
131
+
132
+ async function run(file: string): Promise<Result> {
133
+ const target = path.join("test", file)
134
+ const cmd = ["bun", "test", target, "--timeout", String(timeout)]
135
+
136
+ if (ci) {
137
+ const name = file.replace(/[/\\]/g, "_") + ".xml"
138
+ cmd.push("--reporter=junit", `--reporter-outfile=${path.join(xmldir, name)}`)
139
+ }
140
+
141
+ const start = performance.now()
142
+ const killed = { value: false }
143
+
144
+ const proc = Bun.spawn(cmd, {
145
+ cwd: root,
146
+ stdout: "pipe",
147
+ stderr: "pipe",
148
+ })
149
+
150
+ const timer = setTimeout(() => {
151
+ killed.value = true
152
+ proc.kill()
153
+ }, deadline)
154
+
155
+ const [stdout, stderr, code] = await Promise.all([
156
+ new Response(proc.stdout).text(),
157
+ new Response(proc.stderr).text(),
158
+ proc.exited,
159
+ ])
160
+
161
+ clearTimeout(timer)
162
+
163
+ return {
164
+ file,
165
+ passed: code === 0,
166
+ code,
167
+ stdout,
168
+ stderr,
169
+ duration: performance.now() - start,
170
+ timedout: killed.value,
171
+ attempts: 1,
172
+ }
173
+ }
174
+
175
+ // ---------------------------------------------------------------------------
176
+ // Report a single result
177
+ // ---------------------------------------------------------------------------
178
+
179
+ function report(result: Result) {
180
+ counter.done++
181
+ const idx = String(counter.done).padStart(pad)
182
+ const secs = (result.duration / 1000).toFixed(1)
183
+ const tries = result.attempts > 1 ? dim(` [attempt ${result.attempts}/${retries + 1}]`) : ""
184
+
185
+ if (result.timedout) {
186
+ console.log(
187
+ `[${idx}/${files.length}] ${red("TIME")} ${result.file} ${dim(`(${secs}s - exceeded ${deadline / 1000}s)`)}${tries}`,
188
+ )
189
+ return
190
+ }
191
+
192
+ if (!result.passed) {
193
+ console.log(`[${idx}/${files.length}] ${red("FAIL")} ${result.file} ${dim(`(${secs}s)`)}${tries}`)
194
+ if (verbose && result.stderr.trim()) console.log(result.stderr)
195
+ if (verbose && result.stdout.trim()) console.log(result.stdout)
196
+ return
197
+ }
198
+
199
+ if (result.attempts > 1) {
200
+ console.log(`[${idx}/${files.length}] ${yellow("FLAKY")} ${result.file} ${dim(`(${secs}s)`)}${tries}`)
201
+ if (verbose && result.stdout.trim()) console.log(dim(result.stdout))
202
+ return
203
+ }
204
+
205
+ console.log(`[${idx}/${files.length}] ${green("PASS")} ${result.file} ${dim(`(${secs}s)`)}`)
206
+ if (verbose && result.stdout.trim()) console.log(dim(result.stdout))
207
+ }
208
+
209
+ // ---------------------------------------------------------------------------
210
+ // Parallel execution
211
+ // ---------------------------------------------------------------------------
212
+
213
+ console.log(`\nRunning ${bold(String(files.length))} test files with concurrency ${bold(String(concurrency))}\n`)
214
+
215
+ const start = performance.now()
216
+ const results: Result[] = []
217
+ const queue = [...files]
218
+ const stopped = { value: false }
219
+
220
+ const workers = Array.from({ length: Math.min(concurrency, files.length) }, async () => {
221
+ while (queue.length > 0 && !stopped.value) {
222
+ const file = queue.shift()!
223
+ let result = await run(file)
224
+ // Retry failing files up to `retries` extra times. Bugs still fail on every
225
+ // attempt; contention-based flakes (port races, slow FS, slow spawn) recover.
226
+ // Preserve the last attempt's stdout/stderr/duration so a truly broken file
227
+ // still shows a useful diagnostic.
228
+ while (!result.passed && result.attempts <= retries && !stopped.value) {
229
+ const retry = await run(file)
230
+ retry.attempts = result.attempts + 1
231
+ result = retry
232
+ }
233
+ results.push(result)
234
+ report(result)
235
+ if (bail && !result.passed) stopped.value = true
236
+ }
237
+ })
238
+
239
+ await Promise.all(workers)
240
+
241
+ const elapsed = (performance.now() - start) / 1000
242
+
243
+ // ---------------------------------------------------------------------------
244
+ // Failure details
245
+ // ---------------------------------------------------------------------------
246
+
247
+ const failures = results.filter((r) => !r.passed).sort((a, b) => a.file.localeCompare(b.file))
248
+
249
+ if (failures.length > 0 && !verbose) {
250
+ console.log(`\n${bold(red("--- FAILURES ---"))}\n`)
251
+ for (const f of failures) {
252
+ const tag = f.timedout ? " (TIMED OUT)" : ""
253
+ console.log(`${bold(red(f.file))}${tag}:`)
254
+ const output = (f.stderr || f.stdout).trim()
255
+ if (output)
256
+ console.log(
257
+ output
258
+ .split("\n")
259
+ .map((l) => " " + l)
260
+ .join("\n"),
261
+ )
262
+ console.log()
263
+ }
264
+ }
265
+
266
+ // ---------------------------------------------------------------------------
267
+ // Summary
268
+ // ---------------------------------------------------------------------------
269
+
270
+ const passed = results.filter((r) => r.passed).length
271
+ const flaky = results.filter((r) => r.passed && r.attempts > 1)
272
+
273
+ console.log(
274
+ `\n${bold(String(results.length))} files | ` +
275
+ `${green(passed + " passed")} | ` +
276
+ `${failures.length > 0 ? red(failures.length + " failed") : failures.length + " failed"} | ` +
277
+ `${flaky.length > 0 ? yellow(flaky.length + " flaky") : flaky.length + " flaky"} | ` +
278
+ `${elapsed.toFixed(1)}s\n`,
279
+ )
280
+
281
+ if (flaky.length > 0) {
282
+ const sorted = flaky.slice().sort((a, b) => a.file.localeCompare(b.file))
283
+
284
+ console.log(`${bold(yellow("--- FLAKY (passed on retry) ---"))}\n`)
285
+ for (const r of sorted) {
286
+ console.log(` ${yellow(r.file)} ${dim(`(passed on attempt ${r.attempts}/${retries + 1})`)}`)
287
+ }
288
+ console.log()
289
+
290
+ // Surface flakies to the GitHub Actions UI so reviewers don't have to scan
291
+ // the raw log. Annotations show up on the PR; the step summary is visible at
292
+ // the bottom of the job page and in the workflow summary email.
293
+ if (process.env.GITHUB_ACTIONS === "true") {
294
+ for (const r of sorted) {
295
+ const repo = `packages/saeeol/test/${r.file}`
296
+ console.log(`::warning file=${repo},title=Flaky test file::passed on attempt ${r.attempts} of ${retries + 1}`)
297
+ }
298
+
299
+ const summary = process.env.GITHUB_STEP_SUMMARY
300
+ if (summary) {
301
+ const md = [
302
+ "### ⚠️ Flaky test files (passed on retry)",
303
+ "",
304
+ `${sorted.length} file${sorted.length === 1 ? "" : "s"} needed more than one attempt to pass.`,
305
+ "",
306
+ "| File | Attempts |",
307
+ "|---|---|",
308
+ ...sorted.map((r) => `| \`${r.file}\` | ${r.attempts}/${retries + 1} |`),
309
+ "",
310
+ ].join("\n")
311
+ await fs.appendFile(summary, md + "\n")
312
+ }
313
+ }
314
+ }
315
+
316
+ // ---------------------------------------------------------------------------
317
+ // JUnit XML merge (CI mode)
318
+ // ---------------------------------------------------------------------------
319
+
320
+ if (ci) {
321
+ await merge()
322
+ await fs.rm(xmldir, { recursive: true, force: true }).catch((err) => {
323
+ console.error("cleanup failed:", err)
324
+ })
325
+ }
326
+
327
+ process.exit(failures.length > 0 ? 1 : 0)
328
+
329
+ // ---------------------------------------------------------------------------
330
+ // Helpers
331
+ // ---------------------------------------------------------------------------
332
+
333
+ async function merge() {
334
+ const dir = path.join(root, ".artifacts", "unit")
335
+ await fs.mkdir(dir, { recursive: true })
336
+
337
+ const suites: string[] = []
338
+ const counts = { tests: 0, failures: 0, errors: 0 }
339
+
340
+ for (const file of files) {
341
+ const name = file.replace(/[/\\]/g, "_") + ".xml"
342
+ const fpath = path.join(xmldir, name)
343
+ const found = await Bun.file(fpath).exists()
344
+
345
+ if (found) {
346
+ const content = await Bun.file(fpath).text()
347
+ const extracted = extract(content)
348
+ if (extracted) {
349
+ suites.push(extracted)
350
+ // Counts come from the outer <testsuites ...> root attributes, not from
351
+ // regex-scanning the inner content, so nested <testsuite> blocks (bun
352
+ // emits one per `describe`) don't get double-counted.
353
+ const root = content.match(/<testsuites\b([^>]*)>/)
354
+ if (root) {
355
+ counts.tests += attr(root[1], "tests")
356
+ counts.failures += attr(root[1], "failures")
357
+ counts.errors += attr(root[1], "errors")
358
+ }
359
+ continue
360
+ }
361
+ }
362
+
363
+ // No valid XML produced - generate synthetic entry for failed files
364
+ const result = results.find((r) => r.file === file)
365
+ if (!result || result.passed) continue
366
+
367
+ const secs = (result.duration / 1000).toFixed(3)
368
+ const msg = result.timedout
369
+ ? `Test file timed out after ${deadline / 1000}s`
370
+ : `Test process exited with code ${result.code}`
371
+ const detail = esc((result.stderr || result.stdout || msg).slice(0, 10000))
372
+
373
+ suites.push(
374
+ ` <testsuite name="${esc(file)}" tests="1" failures="1" errors="0" time="${secs}">\n` +
375
+ ` <testcase name="${esc(file)}" classname="${esc(file)}" time="${secs}">\n` +
376
+ ` <failure message="${esc(msg)}">${detail}</failure>\n` +
377
+ ` </testcase>\n` +
378
+ ` </testsuite>`,
379
+ )
380
+ counts.tests++
381
+ counts.failures++
382
+ }
383
+
384
+ const body = [
385
+ '<?xml version="1.0" encoding="UTF-8"?>',
386
+ `<testsuites tests="${counts.tests}" failures="${counts.failures}" errors="${counts.errors}" time="${elapsed.toFixed(3)}">`,
387
+ ...suites,
388
+ "</testsuites>",
389
+ "",
390
+ ].join("\n")
391
+
392
+ await Bun.write(path.join(dir, "junit.xml"), body)
393
+ }
394
+
395
+ // Grab everything between the outer <testsuites ...> and </testsuites> of a
396
+ // per-file JUnit XML. Preserves nested <testsuite> blocks verbatim — the
397
+ // previous hand-rolled walker matched the first </testsuite> it found, which
398
+ // closed an inner suite and left the outer one dangling in the merged output.
399
+ function extract(content: string): string {
400
+ const open = content.match(/<testsuites\b[^>]*>/)
401
+ if (!open) return ""
402
+ const start = open.index! + open[0].length
403
+ const end = content.lastIndexOf("</testsuites>")
404
+ if (end === -1 || end <= start) return ""
405
+ return content.slice(start, end).trim()
406
+ }
407
+
408
+ function attr(attrs: string, name: string): number {
409
+ const m = attrs.match(new RegExp(`\\b${name}="(\\d+)"`))
410
+ return m ? Number(m[1]) : 0
411
+ }
412
+
413
+ function esc(s: string): string {
414
+ return s
415
+ .replace(/&/g, "&amp;")
416
+ .replace(/</g, "&lt;")
417
+ .replace(/>/g, "&gt;")
418
+ .replace(/"/g, "&quot;")
419
+ .replace(/'/g, "&apos;")
420
+ }
package/script/time.ts ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env bun
2
+
3
+ import path from "path"
4
+ const toDynamicallyImport = path.join(process.cwd(), process.argv[2])
5
+ await import(toDynamicallyImport)
6
+ console.log(performance.now())
@@ -0,0 +1,153 @@
1
+ #!/usr/bin/env bun
2
+ import * as path from "path"
3
+ import * as ts from "typescript"
4
+
5
+ const BASE_DIR = "/home/thdxr/dev/projects/SAEEOL/saeeol/packages/saeeol"
6
+
7
+ // Get entry file from command line arg or use default
8
+ const ENTRY_FILE = process.argv[2] || "src/cli/cmd/tui/plugin/index.ts"
9
+
10
+ const visited = new Set<string>()
11
+
12
+ function resolveImport(importPath: string, fromFile: string): string | null {
13
+ if (importPath.startsWith("@/")) {
14
+ return path.join(BASE_DIR, "src", importPath.slice(2))
15
+ }
16
+
17
+ if (importPath.startsWith("./") || importPath.startsWith("../")) {
18
+ const dir = path.dirname(fromFile)
19
+ return path.resolve(dir, importPath)
20
+ }
21
+
22
+ return null
23
+ }
24
+
25
+ function isInternalImport(importPath: string): boolean {
26
+ return importPath.startsWith("@/") || importPath.startsWith("./") || importPath.startsWith("../")
27
+ }
28
+
29
+ async function tryExtensions(filePath: string): Promise<string | null> {
30
+ const extensions = [".ts", ".tsx", ".js", ".jsx"]
31
+
32
+ try {
33
+ const file = Bun.file(filePath)
34
+ const stat = await file.stat()
35
+
36
+ if (stat?.isDirectory()) {
37
+ for (const ext of extensions) {
38
+ const indexPath = path.join(filePath, "index" + ext)
39
+ const indexFile = Bun.file(indexPath)
40
+ if (await indexFile.exists()) return indexPath
41
+ }
42
+ return null
43
+ }
44
+
45
+ // It's a file
46
+ return filePath
47
+ } catch {
48
+ // Path doesn't exist, try adding extensions
49
+ for (const ext of extensions) {
50
+ const withExt = filePath + ext
51
+ const extFile = Bun.file(withExt)
52
+ if (await extFile.exists()) return withExt
53
+ }
54
+ return null
55
+ }
56
+ }
57
+
58
+ function extractImports(sourceFile: ts.SourceFile): string[] {
59
+ const imports: string[] = []
60
+
61
+ function visit(node: ts.Node) {
62
+ // import x from "path" or import { x } from "path"
63
+ if (ts.isImportDeclaration(node)) {
64
+ // Skip type-only imports
65
+ if (node.importClause?.isTypeOnly) return
66
+
67
+ const moduleSpec = node.moduleSpecifier
68
+ if (ts.isStringLiteral(moduleSpec)) {
69
+ imports.push(moduleSpec.text)
70
+ }
71
+ }
72
+
73
+ // export { x } from "path"
74
+ if (ts.isExportDeclaration(node) && node.moduleSpecifier) {
75
+ if (ts.isStringLiteral(node.moduleSpecifier)) {
76
+ imports.push(node.moduleSpecifier.text)
77
+ }
78
+ }
79
+
80
+ // Dynamic import: import("path")
81
+ if (ts.isCallExpression(node) && node.expression.kind === ts.SyntaxKind.ImportKeyword) {
82
+ const arg = node.arguments[0]
83
+ if (arg && ts.isStringLiteral(arg)) {
84
+ imports.push(arg.text)
85
+ }
86
+ }
87
+
88
+ ts.forEachChild(node, visit)
89
+ }
90
+
91
+ visit(sourceFile)
92
+ return imports
93
+ }
94
+
95
+ async function traceFile(filePath: string, depth = 0): Promise<void> {
96
+ const normalizedPath = path.relative(BASE_DIR, filePath)
97
+
98
+ if (visited.has(filePath)) {
99
+ return
100
+ }
101
+
102
+ // Only trace TypeScript/JavaScript files
103
+ if (!filePath.match(/\.(ts|tsx|js|jsx)$/)) {
104
+ return
105
+ }
106
+
107
+ visited.add(filePath)
108
+ console.log("\t".repeat(depth) + normalizedPath)
109
+
110
+ let content: string
111
+ try {
112
+ content = await Bun.file(filePath).text()
113
+ } catch {
114
+ return
115
+ }
116
+
117
+ const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true)
118
+
119
+ const imports = extractImports(sourceFile)
120
+ const internalImports = imports.filter(isInternalImport)
121
+ const externalImports = imports.filter((imp) => !isInternalImport(imp))
122
+
123
+ // Print external imports
124
+ for (const imp of externalImports) {
125
+ console.log("\t".repeat(depth + 1) + `[ext] ${imp}`)
126
+ }
127
+
128
+ for (const imp of internalImports) {
129
+ const resolved = resolveImport(imp, filePath)
130
+ if (!resolved) continue
131
+
132
+ const actualPath = await tryExtensions(resolved)
133
+ if (!actualPath) continue
134
+
135
+ await traceFile(actualPath, depth + 1)
136
+ }
137
+ }
138
+
139
+ async function main() {
140
+ const entryPath = path.join(BASE_DIR, ENTRY_FILE)
141
+
142
+ // Check if file exists
143
+ const file = Bun.file(entryPath)
144
+ if (!(await file.exists())) {
145
+ console.error(`File not found: ${ENTRY_FILE}`)
146
+ console.error(`Resolved to: ${entryPath}`)
147
+ process.exit(1)
148
+ }
149
+
150
+ await traceFile(entryPath)
151
+ }
152
+
153
+ main().catch(console.error)
@@ -0,0 +1,64 @@
1
+ #!/usr/bin/env bun
2
+
3
+ import path from "node:path"
4
+
5
+ const raw = process.argv[2]
6
+ if (!raw) {
7
+ console.error("Usage: bun run script/upgrade-opentui.ts <version>")
8
+ process.exit(1)
9
+ }
10
+
11
+ const ver = raw.replace(/^v/, "")
12
+ const root = path.resolve(import.meta.dir, "../../..")
13
+ const skip = new Set([".git", ".saeeol", ".turbo", "dist", "node_modules"])
14
+ const keys = ["@opentui/core", "@opentui/solid"] as const
15
+
16
+ const files = (await Array.fromAsync(new Bun.Glob("**/package.json").scan({ cwd: root }))).filter(
17
+ (file) => !file.split("/").some((part) => skip.has(part)),
18
+ )
19
+
20
+ const set = (cur: string) => {
21
+ if (cur.startsWith(">=")) return `>=${ver}`
22
+ if (cur.startsWith("^")) return `^${ver}`
23
+ if (cur.startsWith("~")) return `~${ver}`
24
+ return ver
25
+ }
26
+
27
+ const edit = (obj: unknown) => {
28
+ if (!obj || typeof obj !== "object") return false
29
+ const map = obj as Record<string, unknown>
30
+ return keys
31
+ .map((key) => {
32
+ const cur = map[key]
33
+ if (typeof cur !== "string") return false
34
+ const next = set(cur)
35
+ if (next === cur) return false
36
+ map[key] = next
37
+ return true
38
+ })
39
+ .some(Boolean)
40
+ }
41
+
42
+ const out = (
43
+ await Promise.all(
44
+ files.map(async (rel) => {
45
+ const file = path.join(root, rel)
46
+ const txt = await Bun.file(file).text()
47
+ const json = JSON.parse(txt)
48
+ const hit = [json.dependencies, json.devDependencies, json.peerDependencies].map(edit).some(Boolean)
49
+ if (!hit) return null
50
+ await Bun.write(file, `${JSON.stringify(json, null, 2)}\n`)
51
+ return rel
52
+ }),
53
+ )
54
+ ).filter((item): item is string => item !== null)
55
+
56
+ if (out.length === 0) {
57
+ console.log("No opentui deps found")
58
+ process.exit(0)
59
+ }
60
+
61
+ console.log(`Updated opentui to ${ver} in:`)
62
+ for (const file of out) {
63
+ console.log(`- ${file}`)
64
+ }
@@ -0,0 +1,52 @@
1
+ #!/bin/bash
2
+ # Compare SDK types generated from Hono vs HttpApi specs.
3
+ # Sorts types alphabetically so only meaningful body differences show.
4
+ #
5
+ # Usage: ./scripts/diff-sdk-types.sh # full diff
6
+ # ./scripts/diff-sdk-types.sh --stat # summary only
7
+ set -euo pipefail
8
+
9
+ DIR="$(cd "$(dirname "$0")/.." && pwd)"
10
+ SDK="$(cd "$DIR/../sdk/js" && pwd)"
11
+
12
+ normalize() {
13
+ python3 -c "
14
+ import re, sys
15
+ content = open(sys.argv[1]).read()
16
+ blocks = re.split(r'(?=^export (?:type|function|const) )', content, flags=re.MULTILINE)
17
+ header, body = blocks[0], blocks[1:]
18
+ body.sort(key=lambda b: m.group(1) if (m := re.match(r'export \w+ (\w+)', b)) else '')
19
+ sys.stdout.write(header + ''.join(body))
20
+ " "$1"
21
+ }
22
+
23
+ echo "Generating Hono SDK..." >&2
24
+ (cd "$SDK" && bun run script/build.ts >/dev/null 2>&1)
25
+ normalize "$SDK/src/v2/gen/types.gen.ts" > /tmp/sdk-types-hono.ts
26
+ git -C "$SDK" checkout -- src/ 2>/dev/null
27
+
28
+ echo "Generating HttpApi SDK..." >&2
29
+ (cd "$SDK" && OPENCODE_SDK_OPENAPI=httpapi bun run script/build.ts >/dev/null 2>&1)
30
+ normalize "$SDK/src/v2/gen/types.gen.ts" > /tmp/sdk-types-httpapi.ts
31
+ git -C "$SDK" checkout -- src/ 2>/dev/null
32
+
33
+ echo "" >&2
34
+ if [[ "${1:-}" == "--stat" ]]; then
35
+ diff_output=$(diff /tmp/sdk-types-hono.ts /tmp/sdk-types-httpapi.ts || true)
36
+ honly=$(printf "%s\n" "$diff_output" | grep -c '^< export type' || true)
37
+ aonly=$(printf "%s\n" "$diff_output" | grep -c '^> export type' || true)
38
+ total=$(printf "%s\n" "$diff_output" | wc -l | tr -d ' ')
39
+ echo "Hono-only: $honly types HttpApi-only: $aonly types Diff lines: $total"
40
+ echo ""
41
+ if [[ $honly -gt 0 ]]; then
42
+ echo "=== Hono-only types ==="
43
+ printf "%s\n" "$diff_output" | grep '^< export type' | sed 's/< export type //' | sed 's/[ =].*//' | sed 's/^/ /'
44
+ echo ""
45
+ fi
46
+ if [[ $aonly -gt 0 ]]; then
47
+ echo "=== HttpApi-only types ==="
48
+ printf "%s\n" "$diff_output" | grep '^> export type' | sed 's/> export type //' | sed 's/[ =].*//' | sed 's/^/ /'
49
+ fi
50
+ else
51
+ diff /tmp/sdk-types-hono.ts /tmp/sdk-types-httpapi.ts || true
52
+ fi