berry-agent 0.1.0-alpha.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 (401) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +96 -0
  3. package/dist/.api-emit.stamp +1 -0
  4. package/dist/.build-meta.json +6 -0
  5. package/dist/agent/events.js +1 -0
  6. package/dist/agent/index.js +2 -0
  7. package/dist/agent/loop.js +139 -0
  8. package/dist/agent/queue.js +119 -0
  9. package/dist/agent/stream.js +62 -0
  10. package/dist/agent/tools-batch.js +221 -0
  11. package/dist/agent/types.js +8 -0
  12. package/dist/api/berry-agent-llm.d.ts +18 -0
  13. package/dist/api/berry-agent.d.ts +9 -0
  14. package/dist/api/surface.json +1632 -0
  15. package/dist/api/tsconfig.paths.json +12 -0
  16. package/dist/api/typebox-compile.d.ts +2 -0
  17. package/dist/api/typebox-value.d.ts +2 -0
  18. package/dist/api/typebox.d.ts +2 -0
  19. package/dist/browser/cdp.js +256 -0
  20. package/dist/browser/codes.js +31 -0
  21. package/dist/browser/discover.js +107 -0
  22. package/dist/browser/engine.js +169 -0
  23. package/dist/browser/index.js +17 -0
  24. package/dist/browser/install.js +342 -0
  25. package/dist/browser/page.js +321 -0
  26. package/dist/browser/service.js +158 -0
  27. package/dist/browser/tools.js +198 -0
  28. package/dist/browser/types.js +81 -0
  29. package/dist/channels/ask-queue.js +82 -0
  30. package/dist/channels/commands.js +129 -0
  31. package/dist/channels/engine/cell.js +143 -0
  32. package/dist/channels/engine/diff.js +143 -0
  33. package/dist/channels/engine/engine.js +416 -0
  34. package/dist/channels/engine/index.js +8 -0
  35. package/dist/channels/engine/input-keys.js +86 -0
  36. package/dist/channels/engine/input.js +596 -0
  37. package/dist/channels/engine/memory-io.js +71 -0
  38. package/dist/channels/engine/process-io.js +38 -0
  39. package/dist/channels/engine/types.js +17 -0
  40. package/dist/channels/engine/width.js +122 -0
  41. package/dist/channels/index.js +15 -0
  42. package/dist/channels/registry.js +56 -0
  43. package/dist/channels/sdk/admit.js +29 -0
  44. package/dist/channels/sdk/backend.js +102 -0
  45. package/dist/channels/sdk/cursor.js +56 -0
  46. package/dist/channels/sdk/index.js +20 -0
  47. package/dist/channels/sdk/jsonl.js +208 -0
  48. package/dist/channels/sdk/protocol.js +24 -0
  49. package/dist/channels/sdk/schema.js +91 -0
  50. package/dist/channels/sdk/wire-core.js +446 -0
  51. package/dist/channels/service.js +169 -0
  52. package/dist/channels/tui/autocomplete/autocomplete.js +42 -0
  53. package/dist/channels/tui/autocomplete/file-mentions.js +106 -0
  54. package/dist/channels/tui/autocomplete/popup.js +148 -0
  55. package/dist/channels/tui/autocomplete/provider.js +7 -0
  56. package/dist/channels/tui/autocomplete/token.js +68 -0
  57. package/dist/channels/tui/backend/ansi-rows.js +211 -0
  58. package/dist/channels/tui/backend/main-screen.js +207 -0
  59. package/dist/channels/tui/backend/osc.js +91 -0
  60. package/dist/channels/tui/backend/transcript.js +230 -0
  61. package/dist/channels/tui/backend/tui-backend.js +915 -0
  62. package/dist/channels/tui/editor/editor-model.js +543 -0
  63. package/dist/channels/tui/editor/editor-view.js +140 -0
  64. package/dist/channels/tui/editor/editor.js +275 -0
  65. package/dist/channels/tui/editor/undo-stack.js +35 -0
  66. package/dist/channels/tui/editor/visual-lines.js +126 -0
  67. package/dist/channels/tui/editor/word-nav.js +106 -0
  68. package/dist/channels/tui/history/history-viewer.js +403 -0
  69. package/dist/channels/tui/index.js +3 -0
  70. package/dist/channels/tui/layout.js +167 -0
  71. package/dist/channels/tui/markdown/blocks.js +111 -0
  72. package/dist/channels/tui/markdown/inline.js +75 -0
  73. package/dist/channels/tui/markdown/markdown.js +182 -0
  74. package/dist/channels/tui/memory/memory-viewer.js +540 -0
  75. package/dist/channels/tui/overlay/alt-screen.js +105 -0
  76. package/dist/channels/tui/overlay/overlay.js +57 -0
  77. package/dist/channels/tui/overlay/select-confirm.js +156 -0
  78. package/dist/channels/tui/panels/todo-panel.js +54 -0
  79. package/dist/channels/tui/panels/tool-progress-panel.js +93 -0
  80. package/dist/channels/tui/scroll/scroll-view.js +254 -0
  81. package/dist/channels/tui/status/status-line.js +76 -0
  82. package/dist/channels/tui/text.js +38 -0
  83. package/dist/channels/tui/theme.js +41 -0
  84. package/dist/channels/types.js +11 -0
  85. package/dist/channels/ui-core.js +209 -0
  86. package/dist/checkpoint/capture.js +45 -0
  87. package/dist/checkpoint/codes.js +33 -0
  88. package/dist/checkpoint/command.js +82 -0
  89. package/dist/checkpoint/gate.js +48 -0
  90. package/dist/checkpoint/index.js +20 -0
  91. package/dist/checkpoint/restore.js +172 -0
  92. package/dist/checkpoint/store.js +247 -0
  93. package/dist/checkpoint/types.js +4 -0
  94. package/dist/checkpoint/walk.js +146 -0
  95. package/dist/compaction/ccr-tools.js +112 -0
  96. package/dist/compaction/ccr.js +84 -0
  97. package/dist/compaction/codes.js +21 -0
  98. package/dist/compaction/index.js +18 -0
  99. package/dist/compaction/policy.js +200 -0
  100. package/dist/compaction/service.js +422 -0
  101. package/dist/compaction/slots.js +121 -0
  102. package/dist/compaction/types.js +10 -0
  103. package/dist/context/codes.js +40 -0
  104. package/dist/context/events.js +160 -0
  105. package/dist/context/index.js +13 -0
  106. package/dist/context/logger.js +136 -0
  107. package/dist/context/scope.js +182 -0
  108. package/dist/context/workspace.js +104 -0
  109. package/dist/contracts/agent-events.d.ts +73 -0
  110. package/dist/contracts/agent-events.js +16 -0
  111. package/dist/contracts/api.d.ts +249 -0
  112. package/dist/contracts/api.js +299 -0
  113. package/dist/contracts/approval.d.ts +37 -0
  114. package/dist/contracts/approval.js +9 -0
  115. package/dist/contracts/env-ref.d.ts +41 -0
  116. package/dist/contracts/env-ref.js +32 -0
  117. package/dist/contracts/errors.d.ts +56 -0
  118. package/dist/contracts/errors.js +266 -0
  119. package/dist/contracts/events.d.ts +70 -0
  120. package/dist/contracts/events.js +329 -0
  121. package/dist/contracts/index.d.ts +32 -0
  122. package/dist/contracts/index.js +26 -0
  123. package/dist/contracts/llm.d.ts +275 -0
  124. package/dist/contracts/llm.js +12 -0
  125. package/dist/contracts/messages.d.ts +63 -0
  126. package/dist/contracts/messages.js +65 -0
  127. package/dist/contracts/redact.d.ts +46 -0
  128. package/dist/contracts/redact.js +223 -0
  129. package/dist/contracts/tools.d.ts +197 -0
  130. package/dist/contracts/tools.js +33 -0
  131. package/dist/contracts/types.d.ts +302 -0
  132. package/dist/contracts/types.js +50 -0
  133. package/dist/contracts/ui.d.ts +122 -0
  134. package/dist/contracts/ui.js +15 -0
  135. package/dist/conversation/agent-service.js +61 -0
  136. package/dist/conversation/approval-wiring.js +82 -0
  137. package/dist/conversation/backoff.js +38 -0
  138. package/dist/conversation/codes.js +47 -0
  139. package/dist/conversation/control-tools.js +102 -0
  140. package/dist/conversation/control.js +210 -0
  141. package/dist/conversation/driver.js +922 -0
  142. package/dist/conversation/index.js +12 -0
  143. package/dist/conversation/model-visible.js +169 -0
  144. package/dist/conversation/open-tools.js +126 -0
  145. package/dist/conversation/reseed.js +132 -0
  146. package/dist/conversation/sessions.js +215 -0
  147. package/dist/conversation/todo.js +203 -0
  148. package/dist/conversation/types.js +35 -0
  149. package/dist/conversation/wiring.js +204 -0
  150. package/dist/credentials/codes.js +59 -0
  151. package/dist/credentials/commands.js +186 -0
  152. package/dist/credentials/env-ref.js +34 -0
  153. package/dist/credentials/index.js +28 -0
  154. package/dist/credentials/migration.js +18 -0
  155. package/dist/credentials/oauth.js +266 -0
  156. package/dist/credentials/refresh.js +152 -0
  157. package/dist/credentials/secrets.js +118 -0
  158. package/dist/credentials/types.js +30 -0
  159. package/dist/exec/bash.js +279 -0
  160. package/dist/exec/codes.js +37 -0
  161. package/dist/exec/env.js +99 -0
  162. package/dist/exec/environment.js +39 -0
  163. package/dist/exec/git-guard.js +623 -0
  164. package/dist/exec/index.js +24 -0
  165. package/dist/exec/registry.js +120 -0
  166. package/dist/exec/spawn.js +281 -0
  167. package/dist/exec/tail.js +77 -0
  168. package/dist/exec/types.js +5 -0
  169. package/dist/goal/codes.js +33 -0
  170. package/dist/goal/command.js +175 -0
  171. package/dist/goal/fold.js +121 -0
  172. package/dist/goal/gates.js +122 -0
  173. package/dist/goal/index.js +22 -0
  174. package/dist/goal/migration.js +48 -0
  175. package/dist/goal/service.js +501 -0
  176. package/dist/goal/todo-tool.js +174 -0
  177. package/dist/goal/types.js +1 -0
  178. package/dist/goal/update-tool.js +46 -0
  179. package/dist/host/approval-cmd.js +276 -0
  180. package/dist/host/assembly.js +1146 -0
  181. package/dist/host/boot-failures.js +104 -0
  182. package/dist/host/budget-advisory.js +74 -0
  183. package/dist/host/budget-broadcast.js +50 -0
  184. package/dist/host/builtins.js +54 -0
  185. package/dist/host/cli.js +544 -0
  186. package/dist/host/codes.js +120 -0
  187. package/dist/host/config-schema.js +195 -0
  188. package/dist/host/conversation-stack.js +654 -0
  189. package/dist/host/core-plugins.js +1685 -0
  190. package/dist/host/credentials-cmd.js +42 -0
  191. package/dist/host/disclosure.js +30 -0
  192. package/dist/host/dispatch.js +105 -0
  193. package/dist/host/doors-cmd.js +154 -0
  194. package/dist/host/dump-config.js +44 -0
  195. package/dist/host/hook-dispatch-guard.js +27 -0
  196. package/dist/host/import-gate.js +153 -0
  197. package/dist/host/index.js +54 -0
  198. package/dist/host/issue-session.js +241 -0
  199. package/dist/host/loader.js +372 -0
  200. package/dist/host/main.js +161 -0
  201. package/dist/host/manifest.js +381 -0
  202. package/dist/host/mcp-entry.js +53 -0
  203. package/dist/host/plugin-boot.js +952 -0
  204. package/dist/host/plugin-context.js +672 -0
  205. package/dist/host/plugin-install.js +480 -0
  206. package/dist/host/plugin-reload.js +124 -0
  207. package/dist/host/plugin-store.js +462 -0
  208. package/dist/host/plugin-tools.js +359 -0
  209. package/dist/host/plugin-uninstall.js +257 -0
  210. package/dist/host/plugins-cmd.js +302 -0
  211. package/dist/host/plugins-command.js +130 -0
  212. package/dist/host/plugins-config.js +195 -0
  213. package/dist/host/prompt-sections.js +133 -0
  214. package/dist/host/run-entry.js +544 -0
  215. package/dist/host/runtime.js +209 -0
  216. package/dist/host/scheduler-tick.js +294 -0
  217. package/dist/host/serve-daemon.js +501 -0
  218. package/dist/host/serve-entry.js +279 -0
  219. package/dist/host/session-anchor.js +41 -0
  220. package/dist/host/sessions-cmd.js +219 -0
  221. package/dist/host/sessions-face.js +47 -0
  222. package/dist/host/settings-store.js +112 -0
  223. package/dist/host/signals.js +69 -0
  224. package/dist/host/single-instance.js +105 -0
  225. package/dist/host/subagent-factory.js +291 -0
  226. package/dist/host/testkit/harness.js +283 -0
  227. package/dist/host/testkit/index.js +14 -0
  228. package/dist/host/testkit/matrix.js +302 -0
  229. package/dist/host/tool-policy-store.js +177 -0
  230. package/dist/host/triggers.js +212 -0
  231. package/dist/host/tui-entry.js +216 -0
  232. package/dist/host/webui-bridge.js +165 -0
  233. package/dist/issue/codes.js +38 -0
  234. package/dist/issue/filter.js +146 -0
  235. package/dist/issue/github.js +116 -0
  236. package/dist/issue/index.js +17 -0
  237. package/dist/issue/mount.js +70 -0
  238. package/dist/issue/poll.js +58 -0
  239. package/dist/issue/service.js +341 -0
  240. package/dist/issue/tools.js +70 -0
  241. package/dist/issue/types.js +34 -0
  242. package/dist/issue/webhook.js +126 -0
  243. package/dist/llm/codes.js +77 -0
  244. package/dist/llm/complete.js +196 -0
  245. package/dist/llm/events.js +15 -0
  246. package/dist/llm/index.js +24 -0
  247. package/dist/llm/inflight.js +65 -0
  248. package/dist/llm/model-id.js +58 -0
  249. package/dist/llm/provider-face.d.ts +32 -0
  250. package/dist/llm/provider-face.js +34 -0
  251. package/dist/llm/recovery.js +146 -0
  252. package/dist/llm/runtime.js +46 -0
  253. package/dist/llm/stream-fn.js +147 -0
  254. package/dist/lsp/codes.js +31 -0
  255. package/dist/lsp/connection.js +147 -0
  256. package/dist/lsp/frame.js +110 -0
  257. package/dist/lsp/index.js +16 -0
  258. package/dist/lsp/inject.js +58 -0
  259. package/dist/lsp/instance.js +264 -0
  260. package/dist/lsp/service.js +522 -0
  261. package/dist/lsp/tools.js +53 -0
  262. package/dist/lsp/types.js +201 -0
  263. package/dist/mcp/bridge.js +222 -0
  264. package/dist/mcp/codes.js +21 -0
  265. package/dist/mcp/index.js +19 -0
  266. package/dist/mcp/jsonrpc.js +230 -0
  267. package/dist/mcp/service.js +125 -0
  268. package/dist/mcp/tools.js +145 -0
  269. package/dist/mcp/types.js +108 -0
  270. package/dist/memory/cite.js +131 -0
  271. package/dist/memory/codes.js +50 -0
  272. package/dist/memory/command.js +71 -0
  273. package/dist/memory/consolidate.js +219 -0
  274. package/dist/memory/cycle.js +109 -0
  275. package/dist/memory/dao.js +878 -0
  276. package/dist/memory/diff.js +238 -0
  277. package/dist/memory/extract.js +181 -0
  278. package/dist/memory/fts.js +58 -0
  279. package/dist/memory/index.js +42 -0
  280. package/dist/memory/inject.js +354 -0
  281. package/dist/memory/merge.js +178 -0
  282. package/dist/memory/migration.js +132 -0
  283. package/dist/memory/pollution.js +59 -0
  284. package/dist/memory/port.js +385 -0
  285. package/dist/memory/review.js +201 -0
  286. package/dist/memory/scan.js +89 -0
  287. package/dist/memory/tools.js +508 -0
  288. package/dist/memory/types.js +140 -0
  289. package/dist/obs/codes.js +38 -0
  290. package/dist/obs/db.js +115 -0
  291. package/dist/obs/index.js +20 -0
  292. package/dist/obs/rollup.js +61 -0
  293. package/dist/obs/service.js +363 -0
  294. package/dist/obs/session-tools.js +282 -0
  295. package/dist/obs/session-view.js +225 -0
  296. package/dist/obs/tool.js +97 -0
  297. package/dist/obs/types.js +1 -0
  298. package/dist/persist/audit.js +63 -0
  299. package/dist/persist/aux.js +39 -0
  300. package/dist/persist/codes.js +35 -0
  301. package/dist/persist/index.js +30 -0
  302. package/dist/persist/load-history.js +50 -0
  303. package/dist/persist/migrations.js +37 -0
  304. package/dist/persist/paths.js +108 -0
  305. package/dist/persist/persistence.js +229 -0
  306. package/dist/persist/schema.js +99 -0
  307. package/dist/persist/secret-box.js +95 -0
  308. package/dist/persist/store.js +706 -0
  309. package/dist/persist/write-behind.js +238 -0
  310. package/dist/safety/approval.js +171 -0
  311. package/dist/safety/bwrap.js +123 -0
  312. package/dist/safety/codes.js +57 -0
  313. package/dist/safety/danger.js +505 -0
  314. package/dist/safety/gate.js +226 -0
  315. package/dist/safety/index.js +24 -0
  316. package/dist/safety/presets.js +73 -0
  317. package/dist/safety/roots.js +139 -0
  318. package/dist/safety/sandbox.js +214 -0
  319. package/dist/safety/seatbelt.js +88 -0
  320. package/dist/safety/sensitive.js +34 -0
  321. package/dist/safety/tool-policy.js +203 -0
  322. package/dist/safety/types.js +13 -0
  323. package/dist/scheduler/codes.js +54 -0
  324. package/dist/scheduler/cron-backend.js +135 -0
  325. package/dist/scheduler/engine.js +280 -0
  326. package/dist/scheduler/gates.js +60 -0
  327. package/dist/scheduler/index.js +25 -0
  328. package/dist/scheduler/migration.js +22 -0
  329. package/dist/scheduler/runner.js +171 -0
  330. package/dist/scheduler/schedule.js +193 -0
  331. package/dist/scheduler/service.js +283 -0
  332. package/dist/scheduler/tick.js +139 -0
  333. package/dist/scheduler/types.js +1 -0
  334. package/dist/sdk/http.js +865 -0
  335. package/dist/sdk/index.js +22 -0
  336. package/dist/sdk/mcp.js +289 -0
  337. package/dist/sdk/plugin-route-registry.js +126 -0
  338. package/dist/sdk/plugin-routes.js +149 -0
  339. package/dist/sdk/security.js +116 -0
  340. package/dist/sdk/types.js +32 -0
  341. package/dist/session/budget.js +159 -0
  342. package/dist/session/codes.js +40 -0
  343. package/dist/session/derive.js +180 -0
  344. package/dist/session/event-data.js +1 -0
  345. package/dist/session/fork.js +72 -0
  346. package/dist/session/import-gates.js +148 -0
  347. package/dist/session/index.js +18 -0
  348. package/dist/session/recover.js +103 -0
  349. package/dist/session/session.js +279 -0
  350. package/dist/session/snapshot.js +107 -0
  351. package/dist/skills/agents.js +242 -0
  352. package/dist/skills/codes.js +51 -0
  353. package/dist/skills/discovery.js +266 -0
  354. package/dist/skills/frontmatter.js +215 -0
  355. package/dist/skills/index.js +28 -0
  356. package/dist/skills/load.js +118 -0
  357. package/dist/skills/manage.js +201 -0
  358. package/dist/skills/registry.js +138 -0
  359. package/dist/skills/render.js +124 -0
  360. package/dist/skills/sections.js +171 -0
  361. package/dist/skills/types.js +21 -0
  362. package/dist/subagent/codes.js +50 -0
  363. package/dist/subagent/declarative.js +55 -0
  364. package/dist/subagent/index.js +19 -0
  365. package/dist/subagent/notify.js +51 -0
  366. package/dist/subagent/provide.js +7 -0
  367. package/dist/subagent/registry.js +149 -0
  368. package/dist/subagent/service.js +338 -0
  369. package/dist/subagent/surface.js +44 -0
  370. package/dist/subagent/tool.js +153 -0
  371. package/dist/subagent/types.js +25 -0
  372. package/dist/tools/apply-patch.js +164 -0
  373. package/dist/tools/codes.js +109 -0
  374. package/dist/tools/fs.js +501 -0
  375. package/dist/tools/index.js +17 -0
  376. package/dist/tools/observed.js +99 -0
  377. package/dist/tools/pipeline.js +176 -0
  378. package/dist/tools/protected-read.js +54 -0
  379. package/dist/tools/registry.js +250 -0
  380. package/dist/tools/search.js +451 -0
  381. package/dist/tools/worktree.js +327 -0
  382. package/dist/web/codes.js +37 -0
  383. package/dist/web/gate.js +43 -0
  384. package/dist/web/hygiene.js +195 -0
  385. package/dist/web/index.js +16 -0
  386. package/dist/web/service.js +207 -0
  387. package/dist/web/ssrf-guard.js +44 -0
  388. package/dist/web/tool.js +76 -0
  389. package/dist/web/types.js +10 -0
  390. package/dist/webui/assets/index-B-t2O8gh.js +22 -0
  391. package/dist/webui/assets/index-DOlgSZ9Q.css +2 -0
  392. package/dist/webui/index.html +14 -0
  393. package/dist/webui/index.js +14 -0
  394. package/dist/webui/server.js +549 -0
  395. package/dist/webui/types.js +38 -0
  396. package/examples/README.md +26 -0
  397. package/examples/minimal-code-plugin/entry.js +43 -0
  398. package/examples/minimal-code-plugin/package.json +12 -0
  399. package/examples/pure-skill-pack/package.json +14 -0
  400. package/examples/pure-skill-pack/skills/markdown-table/SKILL.md +32 -0
  401. package/package.json +86 -0
@@ -0,0 +1,416 @@
1
+ /**
2
+ * 引擎编排件(07 篇引擎节件 5——帧管线 + 生命周期状态机 + 交出面)。
3
+ *
4
+ * - **帧管线**:requestRender 单入口——请求合并(帧定时器在飞则不另排,多次
5
+ * 请求一帧收)+ FPS 帧率帽(帧间隔下限:距上帧不足一间隔则排到帽点)+
6
+ * 按需渲染(无请求零写出)。帧体:back 清屏 → 根渲染 → renderFrameDiff
7
+ * 增量(光标去重内建 diff 件——内容与光标**双零才整帧零写出**)→ DECSET
8
+ * 2026 同步输出包裹 → io.write。双缓冲换位(back 升格屏上真相——复用
9
+ * 内存零分配,resize 是唯一重分配点)。
10
+ * - **生命周期四态闸门**(idle / running / suspended / disposed):挂起终退后
11
+ * 残听、迟到 resize、迟到渲染请求一律静默吞——竞态不靠时序约定靠闸门。
12
+ * - **双形态模式串**(单源常量、严格对称反序):inline 主屏形态缺省——驻
13
+ * primary screen 不进 1049 备屏(正文 append-only 交终端原生 scrollback),
14
+ * **零鼠标位**(鼠标签听即劫持滚轮与原生选区——07 屏模型主屏注);alt-screen
15
+ * 副屏形态——1049 备屏进出(/history /memory 回看器)+ 鼠标准入(DECSET
16
+ * 1002 按钮事件跟踪 + 1006 SGR 编码——2026-09-11 鼠标解码批;出屏对称反
17
+ * 序先关 1006 再关 1002)。
18
+ * - **挂起 / 复起交出面**:挂起三件套(出屏模式串 → 卸输入监听 → 丢在途
19
+ * 转义 → 停流 → raw 复原先验态);复起六步(进屏模式串 → raw 重设 →
20
+ * 监听重装 → **显式放流**(已被显式 pause 的流再挂监听不会自动回 flowing,
21
+ * 缺放流则永久失聪)→ 几何核对(挂起期 resize 被闸门整批丢弃,复起重查
22
+ * 真值不信缓存)→ 全量重绘)。drainInput 终退排空(临时吞处理器 + 放流 +
23
+ * 闲窗轮询——总帽 1000ms / 闲窗 50ms;dispose 不自动排空、终退编舞由装配
24
+ * 侧显式调用)。
25
+ * - **终端态复原钩子**:仅真 ProcessTerminalIO 武装(注入 io 零污染)——持屏
26
+ * 窗内 fatal exit 不经 dispose 时 process 'exit' 兜底写 LEAVE_MODES + raw
27
+ * 交还。
28
+ * - **输入接线**:decoder.feed → 事件排空上抛;lone-ESC 判定窗定时器由引擎
29
+ * 装 / 卸;kitty 探测应答上抛 keyboardProtocol 事件。
30
+ */
31
+ import { CellGrid } from './cell.js';
32
+ import { renderFrameDiff } from './diff.js';
33
+ import { InputDecoder } from './input.js';
34
+ import { ProcessTerminalIO } from './process-io.js';
35
+ /** 缺省帧率帽(帧间隔下限 = 1000/60——批 10f-3 性能回归锁建锁校准:v1 定值 60 维持〔perf-lock.test.ts 四指标锁内过〕,实机校准后收紧留批 12 host 装配) */
36
+ const DEFAULT_FPS_CAP = 60;
37
+ /** 缺省 lone-ESC 判定窗(ms——承 berry 实证值升为行为预算) */
38
+ const DEFAULT_ESCAPE_WINDOW_MS = 30;
39
+ /** drainInput 缺省窗(总帽 / 闲窗——承 berry 实证值升为行为预算) */
40
+ const DRAIN_MAX_MS = 1000;
41
+ const DRAIN_IDLE_MS = 50;
42
+ /** 进屏模式串公共尾(两形态共用):光标藏 + 粘贴开 + kitty 推送 + 探测 */
43
+ const ENTER_COMMON = '\x1b[?25l' + // 光标藏(帧尾按声明落位再显)
44
+ '\x1b[?2004h' + // bracketed paste 开(粘贴严格处理)
45
+ '\x1b[>1u' + // kitty 键盘协议推栈:disambiguate 最小位(Esc/alt/ctrl 无歧义化)
46
+ '\x1b[?u' + // kitty 探测:查询当前增强位
47
+ '\x1b[c'; // DA1 探测哨兵(应答先到无 kitty 应答 = legacy)
48
+ /** 出屏模式串公共头(两形态共用):与进屏公共尾严格对称反序 */
49
+ const LEAVE_COMMON = '\x1b[<u' + // kitty 弹栈(恢复宿主栈态——kitty 规范)
50
+ '\x1b[?2004l' + // 粘贴关
51
+ '\x1b[?25h'; // 光标显
52
+ /**
53
+ * 副屏鼠标准入串(2026-09-11 鼠标解码批——07 件 5 鼠标位条款):1002 按钮事
54
+ * 件跟踪(拖选需按住 motion)+ 1006 SGR 编码(`CSI <` 报文形),追加于 1049
55
+ * 与公共尾之后(inline 主屏形态零鼠标位——标签听即劫持滚轮与原生选区)。
56
+ */
57
+ const ENTER_MOUSE = '\x1b[?1002h\x1b[?1006h';
58
+ /** 副屏鼠标关停串(与准入严格对称反序——先关 1006 再关 1002;两关都写幂等无害) */
59
+ const LEAVE_MOUSE = '\x1b[?1006l\x1b[?1002l';
60
+ /** 形态 → 进屏模式串(1049 备屏进出 + 鼠标准入仅在 alt-screen 形态) */
61
+ const ENTER_MODES = {
62
+ inline: ENTER_COMMON,
63
+ 'alt-screen': '\x1b[?1049h' + ENTER_COMMON + ENTER_MOUSE, // 备屏进 + 鼠标准入
64
+ };
65
+ /** 形态 → 出屏模式串(与进屏严格对称反序——单源常量) */
66
+ const LEAVE_MODES = {
67
+ inline: LEAVE_COMMON,
68
+ 'alt-screen': LEAVE_MOUSE + LEAVE_COMMON + '\x1b[?1049l', // 鼠标关停 + 备屏出(回主屏)
69
+ };
70
+ /** 极简类型化 emitter(订阅返回卸载函数——装拆对称) */
71
+ class Emitter {
72
+ map = new Map();
73
+ on(event, cb) {
74
+ let set = this.map.get(event);
75
+ if (!set) {
76
+ set = new Set();
77
+ this.map.set(event, set);
78
+ }
79
+ set.add(cb);
80
+ return () => set.delete(cb);
81
+ }
82
+ emit(event, payload) {
83
+ const set = this.map.get(event);
84
+ if (!set)
85
+ return;
86
+ for (const cb of set)
87
+ cb(payload);
88
+ }
89
+ }
90
+ /** TUI 引擎(构造后 start 进屏;dispose 终退) */
91
+ export class Engine {
92
+ io;
93
+ screen;
94
+ /** 帧率帽(帧间隔下限 = 1000/fpsCap ms) */
95
+ minFrameMs;
96
+ /** 时钟 / 调度注入(测试假钟注入口) */
97
+ now;
98
+ scheduleFn;
99
+ cancelFn;
100
+ /** 渲染树根(shell 经 setRoot 装载) */
101
+ root = null;
102
+ /** 双缓冲:front = 屏上真相(差分基线)/ back = 本帧渲染目标 */
103
+ front;
104
+ back;
105
+ /** 全量帧旗标(首帧 / 复起重进 / resize——front 基线失真时置位) */
106
+ forceFull = true;
107
+ /** 帧定时器句柄(null = 无在飞帧——请求合并的合并位) */
108
+ frameHandle = null;
109
+ /** 上帧时刻(FPS 帽锚——调度时刻滚;-Infinity = 首帧立即出) */
110
+ lastFrameAt = Number.NEGATIVE_INFINITY;
111
+ /** 生命周期态:idle 未启 / running 持屏 / suspended 挂起 / disposed 终退 */
112
+ state = 'idle';
113
+ /** start 时的 raw 先验态(suspend/dispose 复原依据) */
114
+ priorRaw = false;
115
+ /** 输入解码器(kitty/legacy 双轨 + IME 组字态) */
116
+ decoder;
117
+ /** lone-ESC 判定窗(构造时留档——定时器装排用) */
118
+ decoderEscapeWindowMs;
119
+ /** lone-ESC 判定窗定时器句柄 */
120
+ escapeHandle = null;
121
+ /** 输入监听退订函数(null = 未装) */
122
+ unsubInput = null;
123
+ /** resize 监听退订函数(挂起期保留监听、dispose 才卸) */
124
+ unsubResize = null;
125
+ /** 引擎事件面 */
126
+ events = new Emitter();
127
+ /** kitty 探测落定值(应答到达前缺省 legacy) */
128
+ protocol = 'legacy';
129
+ /**
130
+ * 硬退复原钩子解除器(null = 未武装):持屏窗内 fatal exit(1)/二次
131
+ * SIGINT exit(130) 不经 dispose——用户被留在死模式态(备屏 / 光标藏 /
132
+ * 粘贴开 / kitty 推栈)。生命周期 = 持屏窗〔start/resume → suspend/dispose〕。
133
+ */
134
+ disarmExitRestore = null;
135
+ constructor(opts = {}) {
136
+ this.io = opts.io ?? new ProcessTerminalIO();
137
+ this.screen = opts.screen ?? 'inline';
138
+ this.minFrameMs = 1000 / (opts.fpsCap ?? DEFAULT_FPS_CAP);
139
+ this.now = opts.now ?? Date.now;
140
+ this.scheduleFn = opts.schedule ?? ((fn, ms) => setTimeout(fn, ms));
141
+ this.cancelFn = opts.cancelSchedule ?? ((h) => clearTimeout(h));
142
+ const { columns, rows } = this.io.size();
143
+ this.front = new CellGrid(columns, rows);
144
+ this.back = new CellGrid(columns, rows);
145
+ this.decoder = new InputDecoder({
146
+ now: this.now,
147
+ escapeWindowMs: opts.escapeWindowMs ?? DEFAULT_ESCAPE_WINDOW_MS,
148
+ onProtocol: (p) => {
149
+ this.protocol = p;
150
+ this.events.emit('keyboardProtocol', p);
151
+ },
152
+ onMouseLegacy: opts.onMouseLegacy,
153
+ });
154
+ this.decoderEscapeWindowMs = opts.escapeWindowMs ?? DEFAULT_ESCAPE_WINDOW_MS;
155
+ }
156
+ /** 当前网格宽(列——io 直查真值不缓存) */
157
+ get columns() {
158
+ return this.io.size().columns;
159
+ }
160
+ /** 当前网格高(行) */
161
+ get rows() {
162
+ return this.io.size().rows;
163
+ }
164
+ /** 挂起中旗标(换防面查询) */
165
+ get suspended() {
166
+ return this.state === 'suspended';
167
+ }
168
+ /** 生命周期态(idle/running/suspended/disposed) */
169
+ get lifecycle() {
170
+ return this.state;
171
+ }
172
+ /** kitty 探测落定值(应答前缺省 legacy) */
173
+ get keyboardProtocol() {
174
+ return this.protocol;
175
+ }
176
+ /** 事件订阅(返回卸载函数) */
177
+ on(event, cb) {
178
+ return this.events.on(event, cb);
179
+ }
180
+ /**
181
+ * 进屏启动:模式串写出 + raw 设定 + 输入 / resize 监听装上 + 全量首帧。
182
+ * 幂等保护:仅 idle 可启(挂起复位走 resume)。
183
+ */
184
+ start(root) {
185
+ if (this.state !== 'idle')
186
+ return;
187
+ this.priorRaw = this.io.isRaw();
188
+ this.root = root;
189
+ this.io.write(ENTER_MODES[this.screen]);
190
+ this.armExitRestore();
191
+ this.io.setRawMode(true);
192
+ this.unsubInput = this.io.onInput(this.handleInput);
193
+ this.unsubResize = this.io.onResize(this.handleResize);
194
+ // 显式放流:已被显式 pause 的流(如副屏 Engine 复用主屏挂起后共享的 io)
195
+ // 再挂监听不会自动回 flowing——须放流才有数据事件;首启场景对已
196
+ // flowing 流为 no-op(Node 流语义),与 resume 六步的放流位对齐
197
+ this.io.resume();
198
+ this.state = 'running';
199
+ this.forceFull = true;
200
+ this.requestRender();
201
+ }
202
+ /** 换渲染树根(同帧请求合并——下帧生效) */
203
+ setRoot(root) {
204
+ this.root = root;
205
+ this.requestRender();
206
+ }
207
+ /**
208
+ * 渲染请求(单入口语义):挂起 / 终退态静默短路(闸门);帧定时器在飞则
209
+ * 不另排(请求合并——多次请求一帧收)。
210
+ */
211
+ requestRender() {
212
+ if (this.state === 'suspended' || this.state === 'disposed')
213
+ return; // 闸门短路
214
+ if (this.frameHandle === null) {
215
+ // FPS 帽:距上帧不足一个帧间隔 → 排到帽点;否则下一拍即出
216
+ const due = Math.max(0, this.lastFrameAt + this.minFrameMs - this.now());
217
+ this.frameHandle = this.scheduleFn(() => {
218
+ this.frameHandle = null;
219
+ this.renderNow();
220
+ }, due);
221
+ }
222
+ }
223
+ /** 立即帧(测试与强制刷新面——绕过调度直出) */
224
+ renderNow() {
225
+ if (this.state !== 'running' || !this.root)
226
+ return;
227
+ this.lastFrameAt = this.now();
228
+ // back 清屏 → 根渲染(全屏区域)→ 差分。forceFull 时清差分基线——front
229
+ // 全空基线使 renderFrameDiff 自然全量(光标声明随 clear 归零、去重基线同步重置)
230
+ this.back.clear();
231
+ if (this.forceFull) {
232
+ this.front.clear();
233
+ this.forceFull = false;
234
+ }
235
+ const region = { row: 0, col: 0, width: this.back.columns, height: this.back.rows };
236
+ this.root.render(this.back, region);
237
+ const content = renderFrameDiff(this.front, this.back);
238
+ if (content.length === 0)
239
+ return; // 内容与光标双零才整帧零写出
240
+ // DECSET 2026 同步输出包裹:内容 + 光标同帧原子呈现(不支持的主流外终端
241
+ // 撕裂自担——赌主流不探测)
242
+ this.io.write(`\x1b[?2026h${content}\x1b[?2026l`);
243
+ // 双缓冲换位:back 升格屏上真相(新 back 为旧 front——复用内存零分配)
244
+ const t = this.front;
245
+ this.front = this.back;
246
+ this.back = t;
247
+ }
248
+ /**
249
+ * 挂起(换防交出方——三件套):出屏模式串 → 卸输入监听 → 丢在途转义 →
250
+ * 停流 → raw 复原先验态。挂起后 requestRender 静默短路(闸门);resize
251
+ * 监听保留(无副作用——复起几何核对兜住挂起期变更)。
252
+ */
253
+ suspend() {
254
+ if (this.state !== 'running')
255
+ return;
256
+ this.io.write(LEAVE_MODES[this.screen]);
257
+ this.unsubInput?.();
258
+ this.unsubInput = null;
259
+ this.decoder.discardPending(); // 在途转义 / 粘贴 / 预编辑一窗全丢(粘贴态转吸收)
260
+ this.io.pause();
261
+ this.io.setRawMode(this.priorRaw);
262
+ this.cancelFrame();
263
+ this.state = 'suspended';
264
+ this.disarmExitRestore?.(); // 出屏解除硬退复原钩子(持屏窗闭合)
265
+ }
266
+ /**
267
+ * 复位(换防接收方——六步):进屏模式串清屏进 → raw 重设 → 监听重装 →
268
+ * **显式放流**(已被显式 pause 的流再挂监听不会自动回 flowing——缺放流则
269
+ * 复位后引擎永久失聪)→ 几何核对(挂起期 resize 事件被闸门整批丢弃——
270
+ * 复起重查当前真值,失配即 handleResize 同款弃旧换新)→ 全量首帧重绘
271
+ * (front 基线在挂起期已失真——对方栈写过屏)。
272
+ */
273
+ resume() {
274
+ if (this.state !== 'suspended')
275
+ return;
276
+ this.io.write(ENTER_MODES[this.screen]);
277
+ this.armExitRestore(); // 复进屏重武装(arm 幂等——先解除旧钩子再挂)
278
+ this.io.setRawMode(true);
279
+ this.unsubInput = this.io.onInput(this.handleInput);
280
+ this.io.resume();
281
+ this.state = 'running';
282
+ // 几何核对:失配即走 handleResize 同款重分配(此时 state 已 running——
283
+ // 新 CellBuffer×2 + 2J 清屏锤 + resize 事件 + forceFull,尾部自带
284
+ // requestRender 直接收口)。修前不核对:按旧尺寸全量重绘——终端已缩则
285
+ // 差分写出含越界定位 / 旧宽折行,已扩则桌面不扩张
286
+ const { columns, rows } = this.io.size();
287
+ if (columns !== this.front.columns || rows !== this.front.rows) {
288
+ this.handleResize();
289
+ return;
290
+ }
291
+ this.forceFull = true;
292
+ this.requestRender();
293
+ }
294
+ /**
295
+ * 排空 stdin 缓冲(终退回 shell 前——交出面公开方法位):临时吞处理器 +
296
+ * 放流 → 闲窗(idleMs 无新数据)或总帽(maxMs)到点收手。返回后输入处理
297
+ * 器保持卸载、stdin 暂停——干净交回 shell。dispose 不自动排空——终退编舞
298
+ * (排空 → 出屏 → dispose 序)由装配侧显式调用。
299
+ */
300
+ async drainInput(maxMs = DRAIN_MAX_MS, idleMs = DRAIN_IDLE_MS) {
301
+ const wasRunning = this.state === 'running';
302
+ if (wasRunning) {
303
+ this.unsubInput?.(); // 运行态调用:先卸引擎处理器
304
+ this.unsubInput = null;
305
+ }
306
+ let lastData = this.now();
307
+ const swallow = () => {
308
+ lastData = this.now();
309
+ };
310
+ const unsubSwallow = this.io.onInput(swallow);
311
+ this.io.resume(); // 放流:缓冲数据事件化(被吞处理器吃掉)
312
+ const deadline = this.now() + maxMs;
313
+ while (this.now() < deadline && this.now() - lastData < idleMs) {
314
+ await new Promise((res) => this.scheduleFn(res, idleMs));
315
+ }
316
+ unsubSwallow();
317
+ this.io.pause();
318
+ if (wasRunning) {
319
+ // 运行态调用(罕见):恢复引擎处理器与流态
320
+ this.unsubInput = this.io.onInput(this.handleInput);
321
+ this.io.resume();
322
+ }
323
+ }
324
+ /** 终退:出屏 + 一切复原(dispose 后引擎不可复用) */
325
+ dispose() {
326
+ if (this.state === 'disposed')
327
+ return;
328
+ if (this.state === 'running') {
329
+ this.io.write(LEAVE_MODES[this.screen]);
330
+ this.unsubInput?.();
331
+ this.unsubInput = null;
332
+ this.io.pause();
333
+ this.io.setRawMode(this.priorRaw);
334
+ }
335
+ this.unsubResize?.();
336
+ this.unsubResize = null;
337
+ this.cancelFrame();
338
+ this.cancelEscapeTimer();
339
+ this.disarmExitRestore?.(); // 出屏解除硬退复原钩子(挂起态已解除亦幂等)
340
+ this.state = 'disposed';
341
+ }
342
+ /**
343
+ * 武装硬退复原钩子(仅真 ProcessTerminalIO——注入 io 零污染):进屏模式串
344
+ * 是写字节开启的设备态(备屏 / 光标 / 粘贴 / kitty 推栈),fatal exit 不经
345
+ * dispose——钩子在 process 'exit' 兜底写 LEAVE_MODES(与出屏严格对称反序
346
+ * ——单源常量)+ raw 交还。
347
+ */
348
+ armExitRestore() {
349
+ if (!(this.io instanceof ProcessTerminalIO))
350
+ return; // 注入 io(测试)零污染
351
+ this.disarmExitRestore?.();
352
+ const restore = () => {
353
+ try {
354
+ this.io.write(LEAVE_MODES[this.screen]); // 出屏对称反序单源
355
+ this.io.setRawMode(false); // raw 交还(进程出场钩亦会做——幂等兜底)
356
+ }
357
+ catch {
358
+ // 复位尽力而为——退出路径不允许二次异常
359
+ }
360
+ };
361
+ process.on('exit', restore);
362
+ this.disarmExitRestore = () => {
363
+ process.removeListener('exit', restore);
364
+ this.disarmExitRestore = null;
365
+ };
366
+ }
367
+ /** 输入处理器(decoder 喂入 + 事件排空上抛 + lone-ESC 判定窗定时器装 / 卸) */
368
+ handleInput = (chunk) => {
369
+ if (this.state !== 'running')
370
+ return; // 挂起 / 终退后残听防御(闸门)
371
+ this.decoder.feed(chunk);
372
+ if (this.decoder.hasPendingEscape && this.escapeHandle === null) {
373
+ this.escapeHandle = this.scheduleFn(() => {
374
+ this.escapeHandle = null;
375
+ this.decoder.settle();
376
+ this.flushDecoderEvents();
377
+ }, this.decoderEscapeWindowMs);
378
+ }
379
+ this.flushDecoderEvents();
380
+ };
381
+ /** 排空 decoder 事件队列并上抛 emitter */
382
+ flushDecoderEvents() {
383
+ for (const ev of this.decoder.take()) {
384
+ this.events.emit('input', ev);
385
+ }
386
+ }
387
+ /** resize 处理器:缓冲弃旧换新 + 清屏锤 + 全量重绘 + 事件上抛 */
388
+ handleResize = () => {
389
+ if (this.state !== 'running')
390
+ return; // 迟到 resize 静默吞(闸门)
391
+ const { columns, rows } = this.io.size();
392
+ if (columns === this.front.columns && rows === this.front.rows)
393
+ return;
394
+ this.front = new CellGrid(columns, rows); // 弃旧换新(不做原地搬运——宽字符 reflow 必失真)
395
+ this.back = new CellGrid(columns, rows);
396
+ this.forceFull = true;
397
+ // 清屏锤:resize 后终端可能 reflow 残留——2J 全清 + 全量重绘兜底
398
+ this.io.write('\x1b[2J');
399
+ this.events.emit('resize', { columns, rows });
400
+ this.requestRender();
401
+ };
402
+ /** 取消在飞帧定时器 */
403
+ cancelFrame() {
404
+ if (this.frameHandle !== null) {
405
+ this.cancelFn(this.frameHandle);
406
+ this.frameHandle = null;
407
+ }
408
+ }
409
+ /** 取消 lone-ESC 判定窗定时器 */
410
+ cancelEscapeTimer() {
411
+ if (this.escapeHandle !== null) {
412
+ this.cancelFn(this.escapeHandle);
413
+ this.escapeHandle = null;
414
+ }
415
+ }
416
+ }
@@ -0,0 +1,8 @@
1
+ export { ansiColor } from './types.js';
2
+ export { MemoryTerminalIO } from './memory-io.js';
3
+ export { graphemeWidth, splitGraphemes, truncateToWidth, wrapText } from './width.js';
4
+ export { CellGrid, cellEquals, EMPTY_STYLE, styleEquals } from './cell.js';
5
+ export { renderFrameDiff } from './diff.js';
6
+ export { InputDecoder } from './input.js';
7
+ export { Engine } from './engine.js';
8
+ export { ProcessTerminalIO } from './process-io.js';
@@ -0,0 +1,86 @@
1
+ /** 无修饰基线(mods 解码基准——所有修饰还原都以拷贝此基线起步) */
2
+ export const NO_MODS = {
3
+ ctrl: false,
4
+ alt: false,
5
+ shift: false,
6
+ meta: false,
7
+ };
8
+ /** CSI ~ 数字键名表(legacy 功能键编码:home/delete/F1-F12…) */
9
+ export const TILDE_KEYS = {
10
+ 1: 'home',
11
+ 2: 'insert',
12
+ 3: 'delete',
13
+ 5: 'pageup',
14
+ 6: 'pagedown',
15
+ 7: 'home',
16
+ 8: 'end',
17
+ 11: 'f1',
18
+ 12: 'f2',
19
+ 13: 'f3',
20
+ 14: 'f4',
21
+ 15: 'f5',
22
+ 17: 'f6',
23
+ 18: 'f7',
24
+ 19: 'f8',
25
+ 20: 'f9',
26
+ 21: 'f10',
27
+ 23: 'f11',
28
+ 24: 'f12',
29
+ 29: 'menu',
30
+ };
31
+ /** kitty CSI u 私用区功能键名表(kitty 规范 Functional key definitions——
32
+ * 箭头 / 翻页 / 首尾 / 增删 57414-57426 区段) */
33
+ export const KITTY_PUA_KEYS = {
34
+ 57417: 'left',
35
+ 57418: 'right',
36
+ 57419: 'up',
37
+ 57420: 'down',
38
+ 57421: 'pageup',
39
+ 57422: 'pagedown',
40
+ 57423: 'home',
41
+ 57424: 'end',
42
+ 57425: 'insert',
43
+ 57426: 'delete',
44
+ };
45
+ /** CSI/SS3 字母终点键名表(A 上 / B 下 / C 右 / D 左 / H Home / F End / P-S F1-F4) */
46
+ export const LETTER_KEYS = {
47
+ A: 'up',
48
+ B: 'down',
49
+ C: 'right',
50
+ D: 'left',
51
+ E: 'begin',
52
+ H: 'home',
53
+ F: 'end',
54
+ P: 'f1',
55
+ Q: 'f2',
56
+ R: 'f3',
57
+ S: 'f4',
58
+ };
59
+ /**
60
+ * kitty 修饰参数(值 = 1+位域)→ 修饰键四元还原。
61
+ * 位域:shift=1 / alt=2 / ctrl=4 / super8+hyper16+meta32 合并折叠进 meta
62
+ * (本引擎修饰面只有四键——合并位不细分)。
63
+ */
64
+ export function decodeMods(param) {
65
+ const v = Number(param ?? '1') - 1;
66
+ if (!Number.isFinite(v) || v <= 0)
67
+ return { ...NO_MODS };
68
+ return {
69
+ shift: (v & 0b1) !== 0,
70
+ alt: (v & 0b10) !== 0,
71
+ ctrl: (v & 0b100) !== 0,
72
+ meta: (v & 0b111000) !== 0,
73
+ };
74
+ }
75
+ /**
76
+ * kitty 事件型数字(冒号后缀域)→ 键相三值。
77
+ * kitty 编码:1 = press(缺省)/ 2 = repeat / 3 = release;非法值按 press
78
+ * 收口(宁保守不丢键)。
79
+ */
80
+ export function decodePhase(evTypeNum) {
81
+ if (evTypeNum === 2)
82
+ return 'repeat';
83
+ if (evTypeNum === 3)
84
+ return 'release';
85
+ return 'press';
86
+ }