saeeol 1.2.9 → 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 (539) 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/helper.tsx +1 -0
  111. package/src/cli/cmd/tui/context/app/theme.tsx +1 -0
  112. package/src/cli/cmd/tui/util/revert-diff.ts +1 -1
  113. package/src/overlay/cli/cmd/roll-call-call.ts +1 -1
  114. package/src/overlay/cost-tracker/format.ts +1 -1
  115. package/src/overlay/cost-tracker/index.ts +4 -4
  116. package/src/overlay/cost-tracker/state.ts +2 -2
  117. package/src/overlay/cost-tracker/types.ts +2 -2
  118. package/src/overlay/memory/age.ts +1 -1
  119. package/src/overlay/memory/index.ts +4 -4
  120. package/src/overlay/memory/paths.ts +2 -2
  121. package/src/overlay/memory/scan.ts +1 -1
  122. package/src/overlay/memory/types.ts +2 -2
  123. package/src/overlay/tool/bash-security.ts +3 -3
  124. package/src/overlay/util/url.ts +1 -1
  125. package/src/plugin/codex-auth.ts +1 -1
  126. package/src/provider/model-cache.ts +2 -2
  127. package/src/provider/provider-resolve.ts +3 -3
  128. package/src/provider/transform-message.ts +1 -1
  129. package/src/server/routes/game.ts +284 -0
  130. package/src/server/server.ts +2 -0
  131. package/src/session/core/compaction/compaction-helpers.ts +1 -1
  132. package/src/session/core/compaction/compaction.ts +1 -1
  133. package/src/session/core/session-events.ts +50 -8
  134. package/src/session/core/session.ts +2 -0
  135. package/src/sessions/ingest-queue.ts +2 -2
  136. package/src/sessions/remote-ws.ts +1 -1
  137. package/src/tool/workflow/question.ts +1 -1
  138. package/src/util/abort.ts +1 -1
  139. package/src/util/bom.ts +2 -2
  140. package/src/util/color.ts +1 -1
  141. package/src/util/data-url.ts +1 -1
  142. package/src/util/defer.ts +1 -1
  143. package/src/util/error.ts +2 -2
  144. package/src/util/filesystem.ts +2 -2
  145. package/src/util/format.ts +1 -1
  146. package/src/util/iife.ts +1 -1
  147. package/src/util/local-context.ts +1 -1
  148. package/src/util/locale.ts +2 -2
  149. package/src/util/lock.ts +1 -1
  150. package/src/util/network.ts +1 -1
  151. package/src/util/signal.ts +1 -1
  152. package/src/util/token.ts +1 -1
  153. package/src/util/wildcard.ts +1 -1
  154. package/sst-env.d.ts +10 -0
  155. package/test/AGENTS.md +133 -0
  156. package/test/account/repo.test.ts +352 -0
  157. package/test/account/service.test.ts +456 -0
  158. package/test/acp/agent-interface.test.ts +51 -0
  159. package/test/acp/event-subscription.test.ts +725 -0
  160. package/test/agent/agent.test.ts +890 -0
  161. package/test/auth/auth.test.ts +86 -0
  162. package/test/bun/registry.test.ts +75 -0
  163. package/test/bus/bus-effect.test.ts +161 -0
  164. package/test/bus/bus-integration.test.ts +87 -0
  165. package/test/bus/bus.test.ts +219 -0
  166. package/test/cli/account.test.ts +26 -0
  167. package/test/cli/auto-mode.test.ts +75 -0
  168. package/test/cli/bin-saeeol.test.ts +8 -0
  169. package/test/cli/cmd/tui/prompt-part.test.ts +47 -0
  170. package/test/cli/cmd/tui/prompt-traits.test.ts +38 -0
  171. package/test/cli/cmd/tui/sync.test.tsx +159 -0
  172. package/test/cli/error.test.ts +18 -0
  173. package/test/cli/github-action.test.ts +198 -0
  174. package/test/cli/github-remote.test.ts +85 -0
  175. package/test/cli/import.test.ts +97 -0
  176. package/test/cli/install-artifact.test.ts +72 -0
  177. package/test/cli/plugin-auth-picker.test.ts +120 -0
  178. package/test/cli/pr.test.ts +59 -0
  179. package/test/cli/tui/editor-context-zed.test.ts +356 -0
  180. package/test/cli/tui/editor-context.test.tsx +228 -0
  181. package/test/cli/tui/keybind-plugin.test.ts +90 -0
  182. package/test/cli/tui/markdown.test.ts +161 -0
  183. package/test/cli/tui/plugin-add.test.ts +111 -0
  184. package/test/cli/tui/plugin-install.test.ts +87 -0
  185. package/test/cli/tui/plugin-lifecycle.test.ts +224 -0
  186. package/test/cli/tui/plugin-loader-entrypoint.test.ts +484 -0
  187. package/test/cli/tui/plugin-loader-pure.test.ts +71 -0
  188. package/test/cli/tui/plugin-loader.test.ts +816 -0
  189. package/test/cli/tui/plugin-toggle.test.ts +157 -0
  190. package/test/cli/tui/revert-diff.test.ts +35 -0
  191. package/test/cli/tui/slot-replace.test.tsx +47 -0
  192. package/test/cli/tui/theme-store.test.ts +54 -0
  193. package/test/cli/tui/thread.test.ts +28 -0
  194. package/test/cli/tui/transcript.test.ts +426 -0
  195. package/test/cli/tui/usage.test.ts +60 -0
  196. package/test/cli/tui/use-event.test.tsx +175 -0
  197. package/test/config/agent-color.test.ts +67 -0
  198. package/test/config/config.test.ts +2544 -0
  199. package/test/config/fixtures/empty-frontmatter.md +4 -0
  200. package/test/config/fixtures/frontmatter.md +28 -0
  201. package/test/config/fixtures/markdown-header.md +11 -0
  202. package/test/config/fixtures/no-frontmatter.md +1 -0
  203. package/test/config/fixtures/weird-model-id.md +13 -0
  204. package/test/config/lsp.test.ts +87 -0
  205. package/test/config/markdown.test.ts +228 -0
  206. package/test/config/plugin.test.ts +0 -0
  207. package/test/config/tui.test.ts +624 -0
  208. package/test/control-plane/adapters.test.ts +71 -0
  209. package/test/control-plane/workspace.test.ts +1526 -0
  210. package/test/effect/app-runtime-logger.test.ts +98 -0
  211. package/test/effect/config-service.test.ts +65 -0
  212. package/test/effect/instance-state.test.ts +394 -0
  213. package/test/effect/run-service.test.ts +89 -0
  214. package/test/effect/runner.test.ts +523 -0
  215. package/test/fake/provider.ts +82 -0
  216. package/test/file/fsmonitor.test.ts +68 -0
  217. package/test/file/ignore.test.ts +10 -0
  218. package/test/file/index.test.ts +954 -0
  219. package/test/file/path-traversal.test.ts +205 -0
  220. package/test/file/ripgrep.test.ts +226 -0
  221. package/test/file/watcher.test.ts +249 -0
  222. package/test/filesystem/filesystem.test.ts +319 -0
  223. package/test/fixture/db.ts +11 -0
  224. package/test/fixture/fixture.test.ts +26 -0
  225. package/test/fixture/fixture.ts +175 -0
  226. package/test/fixture/flock-worker.ts +72 -0
  227. package/test/fixture/log-init-worker.ts +62 -0
  228. package/test/fixture/lsp/fake-lsp-server.js +249 -0
  229. package/test/fixture/plug-worker.ts +93 -0
  230. package/test/fixture/plugin-meta-worker.ts +19 -0
  231. package/test/fixture/skills/agents-sdk/SKILL.md +152 -0
  232. package/test/fixture/skills/cloudflare/SKILL.md +211 -0
  233. package/test/fixture/skills/index.json +6 -0
  234. package/test/fixture/tui-plugin.ts +323 -0
  235. package/test/fixture/tui-runtime.ts +31 -0
  236. package/test/format/format.test.ts +272 -0
  237. package/test/git/git.test.ts +128 -0
  238. package/test/ide/ide.test.ts +82 -0
  239. package/test/installation/installation.test.ts +168 -0
  240. package/test/keybind.test.ts +421 -0
  241. package/test/lib/effect.ts +53 -0
  242. package/test/lib/filesystem.ts +10 -0
  243. package/test/lib/llm-server.ts +778 -0
  244. package/test/lib/websocket.ts +46 -0
  245. package/test/lsp/client.test.ts +482 -0
  246. package/test/lsp/index.test.ts +160 -0
  247. package/test/lsp/launch.test.ts +22 -0
  248. package/test/lsp/lifecycle.test.ts +184 -0
  249. package/test/ltm/ltm.test.ts +230 -0
  250. package/test/mcp/headers.test.ts +178 -0
  251. package/test/mcp/lifecycle.test.ts +787 -0
  252. package/test/mcp/oauth-auto-connect.test.ts +311 -0
  253. package/test/mcp/oauth-browser.test.ts +276 -0
  254. package/test/mcp/oauth-callback.test.ts +34 -0
  255. package/test/memory/abort-leak-webfetch.ts +49 -0
  256. package/test/memory/abort-leak.test.ts +128 -0
  257. package/test/patch/patch.test.ts +348 -0
  258. package/test/permission/arity.test.ts +33 -0
  259. package/test/permission/next.test.ts +1227 -0
  260. package/test/permission/next.toConfig.test.ts +110 -0
  261. package/test/permission-task.test.ts +326 -0
  262. package/test/plugin/auth-override.test.ts +79 -0
  263. package/test/plugin/cloudflare.test.ts +68 -0
  264. package/test/plugin/codex.test.ts +123 -0
  265. package/test/plugin/github-copilot-models.test.ts +261 -0
  266. package/test/plugin/install-concurrency.test.ts +140 -0
  267. package/test/plugin/install.test.ts +570 -0
  268. package/test/plugin/loader-shared.test.ts +1169 -0
  269. package/test/plugin/meta.test.ts +137 -0
  270. package/test/plugin/plugin-contract.test.ts +291 -0
  271. package/test/plugin/shared.test.ts +88 -0
  272. package/test/plugin/trigger.test.ts +102 -0
  273. package/test/plugin/workspace-adapter.test.ts +109 -0
  274. package/test/preload.ts +77 -0
  275. package/test/project/instance.test.ts +276 -0
  276. package/test/project/migrate-global.test.ts +152 -0
  277. package/test/project/project.test.ts +600 -0
  278. package/test/project/vcs.test.ts +286 -0
  279. package/test/project/worktree-remove.test.ts +126 -0
  280. package/test/project/worktree.test.ts +223 -0
  281. package/test/provider/amazon-bedrock.test.ts +462 -0
  282. package/test/provider/copilot/convert-to-copilot-messages.test.ts +523 -0
  283. package/test/provider/copilot/copilot-chat-model.test.ts +592 -0
  284. package/test/provider/gitlab-duo.test.ts +413 -0
  285. package/test/provider/local.test.ts +208 -0
  286. package/test/provider/models.test.ts +261 -0
  287. package/test/provider/provider-category.test.ts +190 -0
  288. package/test/provider/provider.test.ts +2758 -0
  289. package/test/provider/transform.test.ts +3681 -0
  290. package/test/pty/pty-output-isolation.test.ts +147 -0
  291. package/test/pty/pty-session.test.ts +102 -0
  292. package/test/pty/pty-shell.test.ts +104 -0
  293. package/test/question/question.test.ts +490 -0
  294. package/test/saeeol/agent-global-config-dirs.test.ts +24 -0
  295. package/test/saeeol/agent-manager-tool.test.ts +71 -0
  296. package/test/saeeol/agent-permission-overrides.test.ts +75 -0
  297. package/test/saeeol/agent-skill-permissions.test.ts +37 -0
  298. package/test/saeeol/ask-agent-permissions.test.ts +303 -0
  299. package/test/saeeol/bash-hierarchy.test.ts +64 -0
  300. package/test/saeeol/bash-permission-metadata.test.ts +66 -0
  301. package/test/saeeol/bash-security-extended.test.ts +243 -0
  302. package/test/saeeol/bedrock-claude-empty-content.test.ts +138 -0
  303. package/test/saeeol/boxes-integration.test.ts +415 -0
  304. package/test/saeeol/builtin-skills.test.ts +75 -0
  305. package/test/saeeol/cleanup.ts +28 -0
  306. package/test/saeeol/cli/dev-setup.test.ts +74 -0
  307. package/test/saeeol/cli/roll-call.test.ts +161 -0
  308. package/test/saeeol/cli-run-auto-helper.test.ts +58 -0
  309. package/test/saeeol/codex-auth-refresh.test.ts +124 -0
  310. package/test/saeeol/commit-message/generate.test.ts +188 -0
  311. package/test/saeeol/commit-message/git-context.test.ts +303 -0
  312. package/test/saeeol/commit-message-windows.test.ts +38 -0
  313. package/test/saeeol/compaction-payload-recovery.test.ts +406 -0
  314. package/test/saeeol/compaction-preservation-audit.test.ts +122 -0
  315. package/test/saeeol/compaction-skip-guard.test.ts +224 -0
  316. package/test/saeeol/compaction-smart-select.test.ts +100 -0
  317. package/test/saeeol/config/config.test.ts +166 -0
  318. package/test/saeeol/config/indexing-default-plugin.test.ts +82 -0
  319. package/test/saeeol/config/opentelemetry-default.test.ts +29 -0
  320. package/test/saeeol/config-gitignore.test.ts +70 -0
  321. package/test/saeeol/config-injector.test.ts +305 -0
  322. package/test/saeeol/config-resilience.test.ts +234 -0
  323. package/test/saeeol/config-validation.test.ts +183 -0
  324. package/test/saeeol/cost-propagation.test.ts +94 -0
  325. package/test/saeeol/cost-tracker-extended.test.ts +141 -0
  326. package/test/saeeol/cost-tracker.test.ts +64 -0
  327. package/test/saeeol/custom-provider-delete.test.ts +149 -0
  328. package/test/saeeol/diff-full.test.ts +226 -0
  329. package/test/saeeol/edit-permission-filediff.test.ts +223 -0
  330. package/test/saeeol/encoding.test.ts +364 -0
  331. package/test/saeeol/enhance-prompt.test.ts +61 -0
  332. package/test/saeeol/ensure-plan-dir.test.ts +32 -0
  333. package/test/saeeol/errors.test.ts +144 -0
  334. package/test/saeeol/external-directory-boundary.test.ts +96 -0
  335. package/test/saeeol/gateway-headers.test.ts +88 -0
  336. package/test/saeeol/help.test.ts +191 -0
  337. package/test/saeeol/ignore-migrator.test.ts +308 -0
  338. package/test/saeeol/indexing-auth.test.ts +45 -0
  339. package/test/saeeol/indexing-feature.test.ts +44 -0
  340. package/test/saeeol/indexing-label.test.ts +70 -0
  341. package/test/saeeol/indexing-startup.test.ts +381 -0
  342. package/test/saeeol/indexing-worktree.test.ts +73 -0
  343. package/test/saeeol/instruction.test.ts +136 -0
  344. package/test/saeeol/lancedb-runtime.test.ts +116 -0
  345. package/test/saeeol/loader-auth.test.ts +168 -0
  346. package/test/saeeol/local-model.test.ts +621 -0
  347. package/test/saeeol/logo.test.ts +31 -0
  348. package/test/saeeol/lsp-typescript-lightweight.test.ts +89 -0
  349. package/test/saeeol/mcp-branding.test.ts +33 -0
  350. package/test/saeeol/mcp-docker-rm.test.ts +32 -0
  351. package/test/saeeol/mcp-migrator.test.ts +736 -0
  352. package/test/saeeol/mcp-oauth-callback.test.ts +33 -0
  353. package/test/saeeol/memory-io.test.ts +198 -0
  354. package/test/saeeol/memory-paths.test.ts +87 -0
  355. package/test/saeeol/memory-security.test.ts +166 -0
  356. package/test/saeeol/model-cache-org.test.ts +164 -0
  357. package/test/saeeol/model-info-panel-utils.test.ts +52 -0
  358. package/test/saeeol/model-info-panel.types.test.ts +7 -0
  359. package/test/saeeol/models-401-fallback.test.ts +52 -0
  360. package/test/saeeol/modes-migrator.test.ts +320 -0
  361. package/test/saeeol/nvidia-headers.test.ts +74 -0
  362. package/test/saeeol/patch-jsonc.test.ts +73 -0
  363. package/test/saeeol/patch.test.ts +172 -0
  364. package/test/saeeol/paths.test.ts +265 -0
  365. package/test/saeeol/permission/config-paths.test.ts +174 -0
  366. package/test/saeeol/permission/env-read.test.ts +149 -0
  367. package/test/saeeol/permission/external-directory-allow.test.ts +327 -0
  368. package/test/saeeol/permission/next.always-rules.test.ts +882 -0
  369. package/test/saeeol/permission/next.reply-http.test.ts +205 -0
  370. package/test/saeeol/permission/next.reply-routing.test.ts +184 -0
  371. package/test/saeeol/plan-exit-detection.test.ts +494 -0
  372. package/test/saeeol/plan-followup.test.ts +1376 -0
  373. package/test/saeeol/project-config-update.test.ts +120 -0
  374. package/test/saeeol/project-id.test.ts +455 -0
  375. package/test/saeeol/provider-cost.test.ts +171 -0
  376. package/test/saeeol/provider-list-failed-state.test.ts +100 -0
  377. package/test/saeeol/question-dismiss-all.test.ts +174 -0
  378. package/test/saeeol/read-directory.test.ts +116 -0
  379. package/test/saeeol/rules-migrator.test.ts +257 -0
  380. package/test/saeeol/run-auto.test.ts +176 -0
  381. package/test/saeeol/run-network.test.ts +224 -0
  382. package/test/saeeol/semantic-search.test.ts +186 -0
  383. package/test/saeeol/server/permission-allow-everything.test.ts +125 -0
  384. package/test/saeeol/session/instruction-substitution.test.ts +72 -0
  385. package/test/saeeol/session/platform-attribution.test.ts +118 -0
  386. package/test/saeeol/session/session.test.ts +105 -0
  387. package/test/saeeol/session-compaction-cap.test.ts +399 -0
  388. package/test/saeeol/session-compaction-chunks.test.ts +501 -0
  389. package/test/saeeol/session-compaction-safety.test.ts +481 -0
  390. package/test/saeeol/session-fork-remap.test.ts +251 -0
  391. package/test/saeeol/session-import-service.test.ts +114 -0
  392. package/test/saeeol/session-list.test.ts +47 -0
  393. package/test/saeeol/session-message-metadata.test.ts +128 -0
  394. package/test/saeeol/session-overflow.test.ts +78 -0
  395. package/test/saeeol/session-processor-empty-tool-calls.test.ts +571 -0
  396. package/test/saeeol/session-processor-network-offline.test.ts +204 -0
  397. package/test/saeeol/session-processor-retry-limit.test.ts +238 -0
  398. package/test/saeeol/session-processor-review-telemetry.test.ts +82 -0
  399. package/test/saeeol/session-prompt-compaction-safety.test.ts +517 -0
  400. package/test/saeeol/session-prompt-queue.test.ts +815 -0
  401. package/test/saeeol/sessions/inflight-cache.test.ts +157 -0
  402. package/test/saeeol/sessions/ingest-queue.test.ts +402 -0
  403. package/test/saeeol/sessions/remote-protocol.test.ts +258 -0
  404. package/test/saeeol/sessions/remote-sender.test.ts +1036 -0
  405. package/test/saeeol/sessions/remote-ws.test.ts +367 -0
  406. package/test/saeeol/sessions/sessions-enable-remote.test.disable +181 -0
  407. package/test/saeeol/slot-prop-reactivity.test.ts +142 -0
  408. package/test/saeeol/snapshot-cache.test.ts +84 -0
  409. package/test/saeeol/snapshot-freeze-repro.test.ts +100 -0
  410. package/test/saeeol/snapshot-track-timeout.test.ts +519 -0
  411. package/test/saeeol/stats-subagent-cost.test.ts +123 -0
  412. package/test/saeeol/suggestion/auto-dismiss.test.ts +65 -0
  413. package/test/saeeol/suggestion/suggestion.test.ts +145 -0
  414. package/test/saeeol/suggestion/tool.test.ts +298 -0
  415. package/test/saeeol/summary-file-diff.test.ts +28 -0
  416. package/test/saeeol/system-prompt.test.ts +142 -0
  417. package/test/saeeol/task-nesting.test.ts +193 -0
  418. package/test/saeeol/telemetry/feedback.test.ts +8 -0
  419. package/test/saeeol/todo-view.test.ts +57 -0
  420. package/test/saeeol/tool-encoding.test.ts +455 -0
  421. package/test/saeeol/tool-registry-indexing-import-failure.test.ts +49 -0
  422. package/test/saeeol/tool-registry-indexing.test.ts +236 -0
  423. package/test/saeeol/tool-registry-semantic-import-failure.test.ts +55 -0
  424. package/test/saeeol/tool-task-model.test.ts +352 -0
  425. package/test/saeeol/transform-opus-4.7.test.ts +89 -0
  426. package/test/saeeol/tui-diff.test.ts +91 -0
  427. package/test/saeeol/tui-sync.test.ts +80 -0
  428. package/test/saeeol/util/url.test.ts +141 -0
  429. package/test/saeeol/workflows-migrator.test.ts +261 -0
  430. package/test/saeeol/worktree-diff-summary.test.ts +64 -0
  431. package/test/saeeol/worktree-diff.test.ts +223 -0
  432. package/test/saeeol/worktree-remove-lock.test.ts +82 -0
  433. package/test/server/AGENTS.md +15 -0
  434. package/test/server/contract.test.ts +357 -0
  435. package/test/server/experimental-session-list.test.ts +157 -0
  436. package/test/server/global-session-list.test.ts +155 -0
  437. package/test/server/httpapi-authorization.test.ts +103 -0
  438. package/test/server/httpapi-bridge.test.ts +440 -0
  439. package/test/server/httpapi-config.test.ts +67 -0
  440. package/test/server/httpapi-cors.test.ts +89 -0
  441. package/test/server/httpapi-event.test.ts +57 -0
  442. package/test/server/httpapi-experimental.test.ts +219 -0
  443. package/test/server/httpapi-file.test.ts +79 -0
  444. package/test/server/httpapi-instance-context.test.ts +237 -0
  445. package/test/server/httpapi-instance.legacy.test.ts +140 -0
  446. package/test/server/httpapi-instance.test.ts +83 -0
  447. package/test/server/httpapi-json-parity.test.ts +263 -0
  448. package/test/server/httpapi-mcp-oauth.test.ts +76 -0
  449. package/test/server/httpapi-mcp.test.ts +189 -0
  450. package/test/server/httpapi-provider.test.ts +153 -0
  451. package/test/server/httpapi-pty-websocket.test.ts +16 -0
  452. package/test/server/httpapi-pty.test.ts +175 -0
  453. package/test/server/httpapi-raw-route-auth.test.ts +89 -0
  454. package/test/server/httpapi-sdk.test.ts +681 -0
  455. package/test/server/httpapi-session.test.ts +464 -0
  456. package/test/server/httpapi-sync.test.ts +130 -0
  457. package/test/server/httpapi-tui.test.ts +121 -0
  458. package/test/server/httpapi-workspace-routing.test.ts +471 -0
  459. package/test/server/httpapi-workspace.test.ts +427 -0
  460. package/test/server/lib/conformance.ts +88 -0
  461. package/test/server/lib/stateful.ts +112 -0
  462. package/test/server/project-init-git.test.ts +113 -0
  463. package/test/server/proxy-util.test.ts +113 -0
  464. package/test/server/session-actions.test.ts +49 -0
  465. package/test/server/session-list.test.ts +238 -0
  466. package/test/server/session-messages.test.ts +167 -0
  467. package/test/server/session-select.test.ts +100 -0
  468. package/test/server/trace-attributes.test.ts +76 -0
  469. package/test/server/workspace-proxy.test.ts +165 -0
  470. package/test/server/workspace-routing.test.ts +85 -0
  471. package/test/session/compaction.test.ts +2420 -0
  472. package/test/session/instruction.test.ts +247 -0
  473. package/test/session/llm.test.ts +1273 -0
  474. package/test/session/message-v2.test.ts +1291 -0
  475. package/test/session/messages-pagination.test.ts +1173 -0
  476. package/test/session/network.test.ts +249 -0
  477. package/test/session/processor-effect.test.ts +847 -0
  478. package/test/session/prompt.test.ts +2131 -0
  479. package/test/session/retry.test.ts +340 -0
  480. package/test/session/revert-compact.test.ts +639 -0
  481. package/test/session/schema-decoding.test.ts +311 -0
  482. package/test/session/session-entry-stepper.test.ts +917 -0
  483. package/test/session/session-schema.test.ts +76 -0
  484. package/test/session/snapshot-tool-race.test.ts +257 -0
  485. package/test/session/structured-output-integration.test.ts +265 -0
  486. package/test/session/structured-output.test.ts +381 -0
  487. package/test/session/system.test.ts +73 -0
  488. package/test/share/share-next.test.ts +333 -0
  489. package/test/shell/shell.test.ts +99 -0
  490. package/test/skill/discovery.test.ts +116 -0
  491. package/test/skill/skill.test.ts +393 -0
  492. package/test/snapshot/snapshot.test.ts +1531 -0
  493. package/test/storage/db.test.ts +23 -0
  494. package/test/storage/json-migration.test.ts +832 -0
  495. package/test/storage/storage.test.ts +293 -0
  496. package/test/suggestion/suggestion.test.ts +1 -0
  497. package/test/sync/index.test.ts +256 -0
  498. package/test/tool/__snapshots__/parameters.test.ts.snap +500 -0
  499. package/test/tool/__snapshots__/tool.test.ts.snap +9 -0
  500. package/test/tool/apply_patch.test.ts +614 -0
  501. package/test/tool/bash.test.ts +1225 -0
  502. package/test/tool/diagnostics-filter.test.ts +55 -0
  503. package/test/tool/edit.test.ts +754 -0
  504. package/test/tool/external-directory.test.ts +169 -0
  505. package/test/tool/fixtures/large-image.png +0 -0
  506. package/test/tool/fixtures/models-api.json +65179 -0
  507. package/test/tool/glob.test.ts +107 -0
  508. package/test/tool/grep.test.ts +114 -0
  509. package/test/tool/lsp.test.ts +187 -0
  510. package/test/tool/parameters.test.ts +243 -0
  511. package/test/tool/question.test.ts +129 -0
  512. package/test/tool/read.test.ts +500 -0
  513. package/test/tool/recall.test.ts +151 -0
  514. package/test/tool/registry.test.ts +203 -0
  515. package/test/tool/skill.test.ts +135 -0
  516. package/test/tool/suggest.test.ts +1 -0
  517. package/test/tool/task.test.ts +612 -0
  518. package/test/tool/tool-define.test.ts +99 -0
  519. package/test/tool/truncation.test.ts +260 -0
  520. package/test/tool/webfetch.test.ts +103 -0
  521. package/test/tool/write.test.ts +291 -0
  522. package/test/util/data-url.test.ts +14 -0
  523. package/test/util/effect-zod.test.ts +754 -0
  524. package/test/util/error.test.ts +38 -0
  525. package/test/util/filesystem.test.ts +656 -0
  526. package/test/util/format.test.ts +59 -0
  527. package/test/util/glob.test.ts +164 -0
  528. package/test/util/iife.test.ts +36 -0
  529. package/test/util/lazy.test.ts +50 -0
  530. package/test/util/lock.test.ts +72 -0
  531. package/test/util/log.test.ts +86 -0
  532. package/test/util/module.test.ts +59 -0
  533. package/test/util/process.test.ts +128 -0
  534. package/test/util/timeout.test.ts +21 -0
  535. package/test/util/which.test.ts +100 -0
  536. package/test/util/wildcard.test.ts +90 -0
  537. package/test/workspace/workspace-restore.test.ts +296 -0
  538. package/src/provider/models-snapshot.d.ts +0 -2
  539. package/src/provider/models-snapshot.js +0 -3
@@ -0,0 +1,53 @@
1
+ /**
2
+ * diff-apply.ts — Parse and apply unified diffs
3
+ * Inspired by Continue.dev edit pipeline (Apache-2.0)
4
+ * Deps: none
5
+ */
6
+
7
+ export interface DiffHunk {
8
+ oldStart: number; oldCount: number
9
+ newStart: number; newCount: number
10
+ lines: string[]
11
+ }
12
+
13
+ export function parseUnifiedDiff(diff: string): DiffHunk[] {
14
+ const hunks: DiffHunk[] = []
15
+ let current: DiffHunk | null = null
16
+ for (const line of diff.split("\n")) {
17
+ const m = line.match(/^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/)
18
+ if (m) {
19
+ if (current) hunks.push(current)
20
+ current = {
21
+ oldStart: parseInt(m[1]), oldCount: parseInt(m[2] ?? "1"),
22
+ newStart: parseInt(m[3]), newCount: parseInt(m[4] ?? "1"),
23
+ lines: [],
24
+ }
25
+ } else if (current && (line.startsWith("+") || line.startsWith("-") || line.startsWith(" ") || line === "")) {
26
+ current.lines.push(line)
27
+ }
28
+ }
29
+ if (current) hunks.push(current)
30
+ return hunks
31
+ }
32
+
33
+ export function applyDiff(source: string, hunks: DiffHunk[]): string {
34
+ const lines = source.split("\n")
35
+ let offset = 0
36
+ for (const h of hunks) {
37
+ const start = h.oldStart - 1 + offset
38
+ const oldLines: string[] = []
39
+ const newLines: string[] = []
40
+ for (const l of h.lines) {
41
+ if (l.startsWith("-")) oldLines.push(l.slice(1))
42
+ else if (l.startsWith("+")) newLines.push(l.slice(1))
43
+ else { oldLines.push(l.slice(1)); newLines.push(l.slice(1)) }
44
+ }
45
+ lines.splice(start, oldLines.length, ...newLines)
46
+ offset += newLines.length - oldLines.length
47
+ }
48
+ return lines.join("\n")
49
+ }
50
+
51
+ export function applyUnifiedDiff(source: string, diff: string): string {
52
+ return applyDiff(source, parseUnifiedDiff(diff))
53
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * disposable.ts — Wrap cleanup fn as Disposable + AsyncDisposable
3
+ * Zero deps.
4
+ *
5
+ * const d = disposable(() => console.log("cleaned"))
6
+ * using d = disposable(() => cleanup())
7
+ */
8
+ export function disposable(fn: () => void | Promise<void>): AsyncDisposable & Disposable {
9
+ return {
10
+ [Symbol.dispose]() { void fn() },
11
+ [Symbol.asyncDispose]() { return Promise.resolve(fn()) },
12
+ }
13
+ }
@@ -0,0 +1,34 @@
1
+ /**
2
+ * err.ts — Universal error message extraction
3
+ * Zero deps.
4
+ *
5
+ * msg(new Error("fail")) → "fail"
6
+ * msg({ message: "bad" }) → "bad"
7
+ */
8
+ export function isObj(v: unknown): v is Record<string, unknown> {
9
+ return !!v && typeof v === "object" && !Array.isArray(v)
10
+ }
11
+
12
+ export function msg(e: unknown): string {
13
+ if (e instanceof Error) {
14
+ if (e.message) return e.message
15
+ if (e.name) return e.name
16
+ }
17
+ if (isObj(e)) {
18
+ if (typeof e.message === "string" && e.message) return e.message
19
+ if (isObj(e.data) && typeof e.data.message === "string" && e.data.message) return e.data.message
20
+ }
21
+ const s = String(e)
22
+ if (s && s !== "[object Object]") return s
23
+ const f = fmt(e)
24
+ if (f && f !== "{}") return f
25
+ return "unknown error"
26
+ }
27
+
28
+ export function fmt(e: unknown): string {
29
+ if (e instanceof Error) return e.stack ?? `${e.name}: ${e.message}`
30
+ if (typeof e === "object" && e !== null) {
31
+ try { return JSON.stringify(e, null, 2) } catch { return "Unexpected error (unserializable)" }
32
+ }
33
+ return String(e)
34
+ }
@@ -0,0 +1,47 @@
1
+ /**
2
+ * human.ts — Human-readable formatting: duration, numbers, dates, truncate, plural
3
+ * Zero deps.
4
+ *
5
+ * duration(150000) → "2m 30s"
6
+ * compact(1500) → "1.5K"
7
+ * truncate("hello world", 8) → "hello w…"
8
+ */
9
+ export function title(s: string) { return s.replace(/\b\w/g, c => c.toUpperCase()) }
10
+
11
+ export function time(ms: number) { return new Date(ms).toLocaleTimeString(undefined, { timeStyle: "short" }) }
12
+
13
+ export function datetime(ms: number) { return `${time(ms)} · ${new Date(ms).toLocaleDateString()}` }
14
+
15
+ export function compact(n: number): string {
16
+ if (n >= 1_000_000) return (n / 1_000_000).toFixed(1) + "M"
17
+ if (n >= 1000) return (n / 1000).toFixed(1) + "K"
18
+ return n.toString()
19
+ }
20
+
21
+ export function duration(ms: number): string {
22
+ if (ms < 1000) return `${ms}ms`
23
+ if (ms < 60_000) return `${(ms / 1000).toFixed(1)}s`
24
+ if (ms < 3_600_000) return `${Math.floor(ms / 60_000)}m ${Math.floor((ms % 60_000) / 1000)}s`
25
+ if (ms < 86_400_000) return `${Math.floor(ms / 3_600_000)}h ${Math.floor((ms % 3_600_000) / 60_000)}m`
26
+ return `${Math.floor(ms / 3_600_000)}h`
27
+ }
28
+
29
+ export function truncate(s: string, len: number): string { return s.length <= len ? s : s.slice(0, len - 1) + "…" }
30
+
31
+ export function truncateMid(s: string, max = 35): string {
32
+ if (s.length <= max) return s
33
+ const pre = Math.ceil((max - 1) / 2)
34
+ const suf = Math.floor((max - 1) / 2)
35
+ return s.slice(0, pre) + "…" + s.slice(-suf)
36
+ }
37
+
38
+ export function plural(n: number, one: string, many: string): string { return (n === 1 ? one : many).replace("{}", String(n)) }
39
+
40
+ export function secsDuration(s: number): string {
41
+ if (s <= 0) return ""
42
+ if (s < 60) return `${s}s`
43
+ if (s < 3600) { const m = Math.floor(s / 60); const r = s % 60; return r > 0 ? `${m}m ${r}s` : `${m}m` }
44
+ if (s < 86400) { const h = Math.floor(s / 3600); const r = Math.floor((s % 3600) / 60); return r > 0 ? `${h}h ${r}m` : `${h}h` }
45
+ if (s < 604800) { const d = Math.floor(s / 86400); return d === 1 ? "~1 day" : `~${d} days` }
46
+ const w = Math.floor(s / 604800); return w === 1 ? "~1 week" : `~${w} weeks`
47
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * iife.ts — Immediately-invoked function expression
3
+ * Zero deps.
4
+ *
5
+ * const x = iife(() => compute())
6
+ */
7
+ export function iife<T>(fn: () => T) {
8
+ return fn()
9
+ }
@@ -0,0 +1,8 @@
1
+ export function latch() {
2
+ let resolve: () => void
3
+ const p = new Promise<void>((r) => { resolve = r })
4
+ return {
5
+ trip() { resolve() },
6
+ wait() { return p },
7
+ }
8
+ }
@@ -0,0 +1,198 @@
1
+ /**
2
+ * memory.ts — File-based persistent memory with typed taxonomy
3
+ * Deps: Node built-ins (os, path, fs/promises)
4
+ * Ported from Claude Code memdir/
5
+ */
6
+
7
+ import { homedir } from "os"
8
+ import { join, normalize, sep, basename } from "path"
9
+ import { mkdir, readFile, writeFile, readdir, stat } from "fs/promises"
10
+
11
+ // ── Types ──────────────────────────────────────────
12
+
13
+ export const MEMORY_TYPES = ["user", "feedback", "project", "reference"] as const
14
+ export type MemoryType = (typeof MEMORY_TYPES)[number]
15
+
16
+ export function parseMemoryType(raw: unknown): MemoryType | undefined {
17
+ if (typeof raw !== "string") return undefined
18
+ return MEMORY_TYPES.find(t => t === raw)
19
+ }
20
+
21
+ export interface MemoryHeader {
22
+ filename: string
23
+ filePath: string
24
+ mtimeMs: number
25
+ description: string | null
26
+ type: MemoryType | undefined
27
+ }
28
+
29
+ // ── Age ────────────────────────────────────────────
30
+
31
+ export function memoryAgeDays(mtimeMs: number): number {
32
+ return Math.max(0, Math.floor((Date.now() - mtimeMs) / 86_400_000))
33
+ }
34
+
35
+ export function memoryAge(mtimeMs: number): string {
36
+ const d = memoryAgeDays(mtimeMs)
37
+ if (d === 0) return "today"
38
+ if (d === 1) return "yesterday"
39
+ return `${d} days ago`
40
+ }
41
+
42
+ export function memoryFreshnessText(mtimeMs: number): string {
43
+ const d = memoryAgeDays(mtimeMs)
44
+ if (d <= 1) return ""
45
+ return `This memory is ${d} days old. Memories are point-in-time observations, not live state — verify against current code before asserting as fact.`
46
+ }
47
+
48
+ // ── Paths ──────────────────────────────────────────
49
+
50
+ export const MAX_ENTRYPOINT_LINES = 200
51
+ export const MAX_ENTRYPOINT_BYTES = 25_000
52
+
53
+ export function getMemoryBaseDir(): string {
54
+ return process.env.KILO_MEMORY_DIR ?? join(homedir(), ".config", "saeeol")
55
+ }
56
+
57
+ export function getMemoryDir(projectRoot: string): string {
58
+ if (process.env.KILO_MEMORY_DIR) return process.env.KILO_MEMORY_DIR
59
+ return join(getMemoryBaseDir(), "projects", sanitize(projectRoot), "memory") + sep
60
+ }
61
+
62
+ export function getMemoryEntrypoint(projectRoot: string): string {
63
+ return join(getMemoryDir(projectRoot), "MEMORY.md")
64
+ }
65
+
66
+ export function isMemoryPath(absolutePath: string, projectRoot: string): boolean {
67
+ return normalize(absolutePath).startsWith(getMemoryDir(projectRoot))
68
+ }
69
+
70
+ function sanitize(p: string): string {
71
+ return p.replace(/[<>:"|?*]/g, "_").replace(/[/\\]+/g, "_")
72
+ }
73
+
74
+ // ── Scan ───────────────────────────────────────────
75
+
76
+ const MAX_MEMORY_FILES = 200
77
+ const FM_MAX_LINES = 30
78
+
79
+ export async function scanMemoryFiles(dir: string): Promise<MemoryHeader[]> {
80
+ try {
81
+ const entries = await readdir(dir, { recursive: true })
82
+ const mds = entries.filter(
83
+ (f): f is string => typeof f === "string" && f.endsWith(".md") && basename(f) !== "MEMORY.md",
84
+ )
85
+ if (mds.length === 0) return []
86
+ const results = await Promise.allSettled(mds.map(async (rel): Promise<MemoryHeader> => {
87
+ const fp = join(dir, rel)
88
+ const s = await stat(fp)
89
+ const { description, type } = await parseFrontmatter(fp)
90
+ return { filename: rel, filePath: fp, mtimeMs: s.mtimeMs, description, type }
91
+ }))
92
+ return results
93
+ .filter((r): r is PromiseFulfilledResult<MemoryHeader> => r.status === "fulfilled")
94
+ .map(r => r.value)
95
+ .sort((a, b) => b.mtimeMs - a.mtimeMs)
96
+ .slice(0, MAX_MEMORY_FILES)
97
+ } catch { return [] }
98
+ }
99
+
100
+ export function formatMemoryManifest(memories: MemoryHeader[]): string {
101
+ return memories.map(m => {
102
+ const tag = m.type ? `[${m.type}] ` : ""
103
+ const ts = new Date(m.mtimeMs).toISOString()
104
+ return m.description ? `- ${tag}${m.filename} (${ts}): ${m.description}` : `- ${tag}${m.filename} (${ts})`
105
+ }).join("\n")
106
+ }
107
+
108
+ async function parseFrontmatter(
109
+ filePath: string,
110
+ ): Promise<{ description: string | null; type: MemoryType | undefined }> {
111
+ try {
112
+ const raw = await readFile(filePath, "utf-8")
113
+ const lines = raw.split("\n").slice(0, FM_MAX_LINES)
114
+ const acc = lines.reduce(
115
+ (s, line) => {
116
+ if (s.done) return s
117
+ if (line.trim() === "---") { if (s.inFm) return { ...s, done: true }; return { ...s, inFm: true } }
118
+ if (!s.inFm) return s
119
+ const desc = line.match(/^description:\s*(.+)$/i)?.[1]
120
+ const typ = line.match(/^type:\s*(.+)$/i)?.[1]
121
+ return { ...s, desc: desc ?? s.desc, typeRaw: typ ?? s.typeRaw }
122
+ },
123
+ { inFm: false, done: false, desc: null as string | null, typeRaw: undefined as string | undefined },
124
+ )
125
+ return { description: acc.desc, type: parseMemoryType(acc.typeRaw) }
126
+ } catch { return { description: null, type: undefined } }
127
+ }
128
+
129
+ // ── Entrypoint I/O ─────────────────────────────────
130
+
131
+ export function truncateEntrypoint(raw: string): { content: string; wasTruncated: boolean } {
132
+ const trimmed = raw.trim()
133
+ const lines = trimmed.split("\n")
134
+ const wasLine = lines.length > MAX_ENTRYPOINT_LINES
135
+ const wasByte = trimmed.length > MAX_ENTRYPOINT_BYTES
136
+ if (!wasLine && !wasByte) return { content: trimmed, wasTruncated: false }
137
+ const afterLine = wasLine ? lines.slice(0, MAX_ENTRYPOINT_LINES).join("\n") : trimmed
138
+ const final = afterLine.length > MAX_ENTRYPOINT_BYTES
139
+ ? (() => { const cut = afterLine.lastIndexOf("\n", MAX_ENTRYPOINT_BYTES); return afterLine.slice(0, cut > 0 ? cut : MAX_ENTRYPOINT_BYTES) })()
140
+ : afterLine
141
+ return { content: final + "\n\n> WARNING: MEMORY.md was truncated. Keep entries concise.", wasTruncated: true }
142
+ }
143
+
144
+ export async function readEntrypoint(projectRoot: string): Promise<string> {
145
+ try { return await readFile(getMemoryEntrypoint(projectRoot), "utf-8") }
146
+ catch { return "" }
147
+ }
148
+
149
+ export async function writeEntrypoint(projectRoot: string, content: string): Promise<void> {
150
+ const { content: final } = truncateEntrypoint(content)
151
+ await writeFile(getMemoryEntrypoint(projectRoot), final, "utf-8")
152
+ }
153
+
154
+ export async function ensureDir(projectRoot: string): Promise<string> {
155
+ const dir = getMemoryDir(projectRoot)
156
+ try { await mkdir(dir, { recursive: true }) } catch { /* exists */ }
157
+ return dir
158
+ }
159
+
160
+ // ── Prompt builder ─────────────────────────────────
161
+
162
+ const TYPES_SECTION = [
163
+ "## Types of memory", "",
164
+ "There are several discrete types of memory you can store:", "",
165
+ "<types>",
166
+ '<type><name>user</name><description>User preferences, role, and knowledge.</description><when_to_save>When you learn about the user\'s role, preferences, or knowledge.</when_to_save></type>',
167
+ '<type><name>feedback</name><description>Guidance about how to approach work — both corrections and confirmed approaches.</description><when_to_save>When the user corrects or confirms your approach.</when_to_save></type>',
168
+ '<type><name>project</name><description>Ongoing work, goals, bugs, or incidents not derivable from code.</description><when_to_save>When you learn who is doing what, why, or by when.</when_to_save></type>',
169
+ '<type><name>reference</name><description>Pointers to external systems and resources.</description><when_to_save>When you learn about external resources and their purpose.</when_to_save></type>',
170
+ "</types>", "",
171
+ ]
172
+
173
+ export async function buildPrompt(projectRoot: string): Promise<string> {
174
+ const dir = getMemoryDir(projectRoot)
175
+ const ep = await readEntrypoint(projectRoot)
176
+ const lines = [
177
+ "# auto memory", "",
178
+ `You have a persistent memory system at \`${dir}\`. Write to it directly.`,
179
+ "", "Build up this memory so future conversations have context about the user, their preferences, and project decisions.",
180
+ "", ...TYPES_SECTION,
181
+ "## What NOT to save in memory", "",
182
+ "- Code patterns, architecture, file paths — derivable from current project state.",
183
+ "- Git history — `git log` / `git blame` are authoritative.",
184
+ "- Debugging solutions — the fix is in the code.",
185
+ "- Ephemeral task details: in-progress work, temporary state.", "",
186
+ "## When to access memories",
187
+ "- When memories seem relevant to the user's request.",
188
+ "- When the user explicitly asks you to recall something.",
189
+ "- Memory records can become stale. Verify against current state before asserting as fact.", "",
190
+ ]
191
+ const epContent = ep.trim() ? truncateEntrypoint(ep).content : "Your MEMORY.md is currently empty."
192
+ lines.push("## MEMORY.md", "", epContent)
193
+ return lines.join("\n")
194
+ }
195
+
196
+ export async function scan(projectRoot: string) {
197
+ return scanMemoryFiles(getMemoryDir(projectRoot))
198
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * net.ts — Network status checks
3
+ * Zero deps.
4
+ *
5
+ * online() → true/false
6
+ * proxied() → true/false
7
+ */
8
+ export function online(): boolean {
9
+ const n = globalThis.navigator
10
+ if (!n || typeof n.onLine !== "boolean") return true
11
+ return n.onLine
12
+ }
13
+
14
+ export function proxied(): boolean {
15
+ return !!(process.env.HTTP_PROXY || process.env.HTTPS_PROXY || process.env.http_proxy || process.env.https_proxy)
16
+ }
@@ -0,0 +1,4 @@
1
+ export function plural(n: number, one: string, many: string): string {
2
+ const tpl = n === 1 ? one : many
3
+ return tpl.replace("{}", String(n))
4
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * puny.ts — Normalize IDN/Unicode URLs to punycode ASCII (anti-homograph)
3
+ * Zero deps. Uses URL built-in.
4
+ *
5
+ * norm("https://аpitest.com/path") → "https://xn--pitest-2nf.com/path"
6
+ */
7
+ export function norm(text: string): string {
8
+ return text.replace(/https?:\/\/\S+/g, (match) => {
9
+ const stripped = match.replace(/[.,!?;:)"'\]>]+$/, "")
10
+ const tail = match.slice(stripped.length)
11
+ try {
12
+ const u = new URL(stripped)
13
+ const after = stripped.indexOf("//") + 2
14
+ const slash = stripped.indexOf("/", after)
15
+ const raw = slash === -1 ? stripped.slice(after) : stripped.slice(after, slash)
16
+ const host = raw.includes(":") ? raw.slice(0, raw.indexOf(":")) : raw
17
+ if (host === u.hostname) return match
18
+ return stripped.replace(host, u.hostname) + tail
19
+ } catch { return match }
20
+ })
21
+ }
@@ -0,0 +1,49 @@
1
+ import { expDelay } from "./schedule"
2
+
3
+ const TRANSIENT = [
4
+ "load failed",
5
+ "network connection was lost",
6
+ "network request failed",
7
+ "failed to fetch",
8
+ "econnreset",
9
+ "econnrefused",
10
+ "etimedout",
11
+ "socket hang up",
12
+ ]
13
+
14
+ function isTransient(err: unknown): boolean {
15
+ if (!err) return false
16
+ const msg = String(err instanceof Error ? err.message : err).toLowerCase()
17
+ return TRANSIENT.some((t) => msg.includes(t))
18
+ }
19
+
20
+ export interface RetryOpts {
21
+ attempts?: number
22
+ delay?: number
23
+ factor?: number
24
+ maxDelay?: number
25
+ retryIf?: (err: unknown) => boolean
26
+ }
27
+
28
+ export async function retry<T>(
29
+ fn: () => Promise<T>,
30
+ opts: RetryOpts = {},
31
+ ): Promise<T> {
32
+ const {
33
+ attempts = 3,
34
+ delay = 500,
35
+ factor = 2,
36
+ maxDelay = 10_000,
37
+ retryIf = isTransient,
38
+ } = opts
39
+ let last: unknown
40
+ for (let i = 0; i < attempts; i++) {
41
+ try { return await fn() } catch (err) {
42
+ last = err
43
+ if (i === attempts - 1 || !retryIf(err)) throw err
44
+ const wait = Math.min(expDelay(delay, i, factor), maxDelay)
45
+ await new Promise((r) => setTimeout(r, wait))
46
+ }
47
+ }
48
+ throw last
49
+ }
@@ -0,0 +1,41 @@
1
+ /**
2
+ * rwlock.ts — Readers-writer lock map (writer priority, no starvation)
3
+ * Zero deps.
4
+ *
5
+ * using r = await rwlock.read("key")
6
+ * using w = await rwlock.write("key")
7
+ */
8
+ const map = new Map<string, { r: number; w: boolean; wr: (() => void)[]; ww: (() => void)[] }>()
9
+
10
+ function get(k: string) {
11
+ if (!map.has(k)) map.set(k, { r: 0, w: false, wr: [], ww: [] })
12
+ return map.get(k)!
13
+ }
14
+
15
+ function drain(k: string) {
16
+ const l = map.get(k)
17
+ if (!l || l.w || l.r > 0) return
18
+ if (l.ww.length > 0) { l.ww.shift()!(); return }
19
+ while (l.wr.length > 0) l.wr.shift()!()
20
+ if (l.r === 0 && !l.w && l.wr.length === 0 && l.ww.length === 0) map.delete(k)
21
+ }
22
+
23
+ function mkRelease(k: string, mode: "r" | "w"): Disposable {
24
+ return { [Symbol.dispose]: () => { const l = map.get(k); if (!l) return; if (mode === "w") l.w = false; else l.r--; drain(k) } }
25
+ }
26
+
27
+ export async function read(k: string): Promise<Disposable> {
28
+ const l = get(k)
29
+ return new Promise<Disposable>((res) => {
30
+ if (!l.w && l.ww.length === 0) { l.r++; res(mkRelease(k, "r")) }
31
+ else l.wr.push(() => { l.r++; res(mkRelease(k, "r")) })
32
+ })
33
+ }
34
+
35
+ export async function write(k: string): Promise<Disposable> {
36
+ const l = get(k)
37
+ return new Promise<Disposable>((res) => {
38
+ if (!l.w && l.r === 0) { l.w = true; res(mkRelease(k, "w")) }
39
+ else l.ww.push(() => { l.w = true; res(mkRelease(k, "w")) })
40
+ })
41
+ }
@@ -0,0 +1,71 @@
1
+ /**
2
+ * schedule.ts — Retry schedule patterns (exponential/fibonacci)
3
+ * Inspired by Effect-TS Schedule (MIT)
4
+ * Deps: none
5
+ */
6
+
7
+ // ── Pure functions (stateless, safe for concurrent use) ──
8
+
9
+ export function expDelay(baseMs: number, attempt: number, factor = 2): number {
10
+ return baseMs * Math.pow(factor, attempt)
11
+ }
12
+
13
+ export function fibDelay(oneMs: number, attempt: number): number {
14
+ if (attempt <= 0) return 0
15
+ let a = 0, b = 1
16
+ for (let i = 1; i < attempt; i++) { [a, b] = [b, a + b] }
17
+ return b * oneMs
18
+ }
19
+
20
+ // ── Stateful iterators (for retryWith / single-owner retry loops) ──
21
+
22
+ export interface ScheduleStep {
23
+ delay: number
24
+ attempt: number
25
+ done: boolean
26
+ }
27
+
28
+ export interface ScheduleBuilder {
29
+ next(): ScheduleStep
30
+ reset(): void
31
+ }
32
+
33
+ export function exponential(baseMs: number, factor = 2, maxRetries = Infinity): ScheduleBuilder {
34
+ let attempt = 0
35
+ return {
36
+ next: () => {
37
+ if (attempt >= maxRetries) return { delay: 0, attempt, done: true }
38
+ const delay = expDelay(baseMs, attempt, factor)
39
+ return { delay, attempt: attempt++, done: false }
40
+ },
41
+ reset: () => { attempt = 0 },
42
+ }
43
+ }
44
+
45
+ export function fibonacci(oneMs: number, maxRetries = Infinity): ScheduleBuilder {
46
+ let a = 0, b = 1, attempt = 0
47
+ return {
48
+ next: () => {
49
+ if (attempt >= maxRetries) return { delay: 0, attempt, done: true }
50
+ const delay = a * oneMs; [a, b] = [b, a + b]
51
+ return { delay, attempt: attempt++, done: false }
52
+ },
53
+ reset: () => { a = 0; b = 1; attempt = 0 },
54
+ }
55
+ }
56
+
57
+ export function capDelay(maxMs: number, s: ScheduleBuilder): ScheduleBuilder {
58
+ return {
59
+ next: () => { const r = s.next(); return { ...r, delay: Math.min(r.delay, maxMs) } },
60
+ reset: () => { s.reset() },
61
+ }
62
+ }
63
+
64
+ export async function retryWith<T>(fn: (attempt: number) => Promise<T>, s: ScheduleBuilder): Promise<T> {
65
+ for (;;) {
66
+ const step = s.next()
67
+ if (step.done) throw new Error(`Retry exhausted after ${step.attempt} attempts`)
68
+ if (step.attempt > 0) await new Promise(r => setTimeout(r, step.delay))
69
+ try { const v = await fn(step.attempt); s.reset(); return v } catch { continue }
70
+ }
71
+ }
@@ -0,0 +1,21 @@
1
+ import { AsyncLocalStorage } from "async_hooks"
2
+
3
+ export class NotFound extends Error {
4
+ constructor(public override readonly name: string) {
5
+ super(`no context for ${name}`)
6
+ }
7
+ }
8
+
9
+ export function scope<T>(label: string) {
10
+ const store = new AsyncLocalStorage<T>()
11
+ return {
12
+ use(): T {
13
+ const v = store.getStore()
14
+ if (!v) throw new NotFound(label)
15
+ return v
16
+ },
17
+ provide<R>(val: T, fn: () => R): R {
18
+ return store.run(val, fn)
19
+ },
20
+ }
21
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * tokens.ts — Rough token estimator
3
+ * Zero deps.
4
+ *
5
+ * count("hello world") → 3
6
+ */
7
+ export function count(text: string): number {
8
+ return Math.max(0, Math.round((text || "").length / 4))
9
+ }
@@ -0,0 +1,63 @@
1
+ /**
2
+ * ttl-cache.ts — Generic TTL cache with Map/WeakMap storage
3
+ * Ported from gemini-cli CacheService (Apache-2.0)
4
+ * Deps: none
5
+ */
6
+ export interface CacheOpts {
7
+ defaultTtl?: number
8
+ storage?: "map" | "weakmap"
9
+ }
10
+
11
+ interface Entry<V> { value: V; ts: number; ttl?: number }
12
+
13
+ export class TTLCache<K extends object | string | undefined, V> {
14
+ private store: Map<K, Entry<V>> | WeakMap<WeakKey, Entry<V>>
15
+ private defaultTtl?: number
16
+
17
+ constructor(opts: CacheOpts = {}) {
18
+ this.store = opts.storage === "weakmap"
19
+ ? new WeakMap<WeakKey, Entry<V>>()
20
+ : new Map<K, Entry<V>>()
21
+ this.defaultTtl = opts.defaultTtl
22
+ }
23
+
24
+ get(key: K): V | undefined {
25
+ const e = (this.store as any).get(key) as Entry<V> | undefined
26
+ if (!e) return undefined
27
+ const ttl = e.ttl ?? this.defaultTtl
28
+ if (ttl !== undefined && Date.now() - e.ts > ttl) {
29
+ this.delete(key)
30
+ return undefined
31
+ }
32
+ return e.value
33
+ }
34
+
35
+ set(key: K, value: V, ttl?: number): void {
36
+ (this.store as any).set(key, { value, ts: Date.now(), ttl })
37
+ }
38
+
39
+ getOrCreate(key: K, creator: () => V, ttl?: number): V {
40
+ const v = this.get(key)
41
+ if (v !== undefined) return v
42
+ const created = creator()
43
+ this.set(key, created, ttl)
44
+ return created
45
+ }
46
+
47
+ delete(key: K): void { (this.store as any).delete(key) }
48
+
49
+ clear(): void {
50
+ if (this.store instanceof Map) this.store.clear()
51
+ else throw new Error("clear() not supported on WeakMap")
52
+ }
53
+ }
54
+
55
+ export function createCache<K extends string | undefined, V>(
56
+ opts: CacheOpts & { storage: "map" },
57
+ ): TTLCache<K, V>
58
+ export function createCache<K extends object, V>(opts?: CacheOpts): TTLCache<K, V>
59
+ export function createCache<K extends object | string | undefined, V>(
60
+ opts: CacheOpts = {},
61
+ ): TTLCache<K, V> {
62
+ return new TTLCache<K, V>(opts)
63
+ }