@gajae-code/coding-agent 0.13.3 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (599) hide show
  1. package/CHANGELOG.md +184 -2
  2. package/README.md +2 -0
  3. package/bin/gajaessi.js +4 -0
  4. package/dist/types/async/job-manager.d.ts +55 -2
  5. package/dist/types/async/support.d.ts +14 -0
  6. package/dist/types/capability/mcp.d.ts +6 -0
  7. package/dist/types/capability/types.d.ts +6 -0
  8. package/dist/types/cli/accounts-cli.d.ts +19 -0
  9. package/dist/types/cli/args.d.ts +3 -0
  10. package/dist/types/cli/config-cli.d.ts +6 -0
  11. package/dist/types/cli/crash-cli.d.ts +10 -0
  12. package/dist/types/cli/customize-doctor.d.ts +145 -0
  13. package/dist/types/cli/daemon-cli.d.ts +3 -1
  14. package/dist/types/cli/mcp-cli.d.ts +11 -0
  15. package/dist/types/cli/notify-cli.d.ts +1 -1
  16. package/dist/types/cli/root-flags.d.ts +12 -0
  17. package/dist/types/cli/setup-cli.d.ts +3 -1
  18. package/dist/types/cli/skills-cli.d.ts +2 -1
  19. package/dist/types/cli/update-cli.d.ts +19 -1
  20. package/dist/types/cli.d.ts +6 -0
  21. package/dist/types/commands/accounts.d.ts +40 -0
  22. package/dist/types/commands/crash.d.ts +24 -0
  23. package/dist/types/commands/customize.d.ts +22 -0
  24. package/dist/types/commands/daemon.d.ts +1 -1
  25. package/dist/types/commands/launch.d.ts +6 -0
  26. package/dist/types/commands/setup.d.ts +6 -0
  27. package/dist/types/commands/skills.d.ts +6 -1
  28. package/dist/types/commands/update.d.ts +7 -0
  29. package/dist/types/config/atomic-yaml-patch-worker.d.ts +25 -0
  30. package/dist/types/config/atomic-yaml-patch.d.ts +62 -2
  31. package/dist/types/config/config-schema-version.d.ts +10 -0
  32. package/dist/types/config/file-lock.d.ts +18 -0
  33. package/dist/types/config/model-profile-activation.d.ts +3 -1
  34. package/dist/types/config/model-registry.d.ts +9 -1
  35. package/dist/types/config/models-config-schema.d.ts +2 -0
  36. package/dist/types/config/settings-schema.d.ts +70 -6
  37. package/dist/types/config/settings.d.ts +63 -0
  38. package/dist/types/config/skill-settings-defaults.d.ts +26 -0
  39. package/dist/types/config/task-delegation.d.ts +39 -0
  40. package/dist/types/config/update-channel.d.ts +10 -0
  41. package/dist/types/coordinator-mcp/server.d.ts +5 -1
  42. package/dist/types/crash/index-store.d.ts +87 -0
  43. package/dist/types/crash/nudge.d.ts +50 -0
  44. package/dist/types/crash/record-loader.d.ts +17 -0
  45. package/dist/types/crash/report.d.ts +134 -0
  46. package/dist/types/crash/sanitize.d.ts +25 -0
  47. package/dist/types/cursor.d.ts +13 -2
  48. package/dist/types/customization/import.d.ts +37 -0
  49. package/dist/types/customization/index.d.ts +4 -0
  50. package/dist/types/customization/inventory.d.ts +25 -0
  51. package/dist/types/customization/mutations.d.ts +32 -0
  52. package/dist/types/customization/types.d.ts +115 -0
  53. package/dist/types/daemon/control-types.d.ts +8 -1
  54. package/dist/types/daemon/operator-contract.d.ts +4 -4
  55. package/dist/types/discovery/claude.d.ts +39 -1
  56. package/dist/types/discovery/codex.d.ts +39 -1
  57. package/dist/types/discovery/index.d.ts +2 -0
  58. package/dist/types/discovery/mcp-compat.d.ts +30 -0
  59. package/dist/types/edit/renderer.d.ts +23 -0
  60. package/dist/types/extensibility/extensions/runner.d.ts +3 -0
  61. package/dist/types/extensibility/extensions/types.d.ts +21 -4
  62. package/dist/types/extensibility/gjc-plugins/observability.d.ts +6 -1
  63. package/dist/types/extensibility/gjc-plugins/registry.d.ts +8 -1
  64. package/dist/types/extensibility/hooks/loader.d.ts +19 -5
  65. package/dist/types/extensibility/runtime-skill-discovery.d.ts +22 -1
  66. package/dist/types/extensibility/skill-management.d.ts +107 -0
  67. package/dist/types/gjc-runtime/deep-interview-runtime.d.ts +3 -1
  68. package/dist/types/gjc-runtime/launch-worktree.d.ts +8 -0
  69. package/dist/types/gjc-runtime/linux-proc.d.ts +3 -0
  70. package/dist/types/gjc-runtime/ralplan-runtime.d.ts +10 -6
  71. package/dist/types/gjc-runtime/session-state-lock.d.ts +104 -0
  72. package/dist/types/gjc-runtime/session-state-sidecar.d.ts +120 -2
  73. package/dist/types/gjc-runtime/state-writer.d.ts +10 -1
  74. package/dist/types/gjc-runtime/ultragoal-guard.d.ts +4 -2
  75. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +8 -6
  76. package/dist/types/gjc-runtime/workflow-settings.d.ts +109 -0
  77. package/dist/types/hooks/events.d.ts +60 -0
  78. package/dist/types/hooks/normalize.d.ts +83 -0
  79. package/dist/types/hooks/skill-state.d.ts +9 -2
  80. package/dist/types/main.d.ts +1 -1
  81. package/dist/types/modes/acp/abort-scope.d.ts +13 -0
  82. package/dist/types/modes/acp/acp-agent.d.ts +7 -4
  83. package/dist/types/modes/components/customization/customization-dashboard.d.ts +44 -0
  84. package/dist/types/modes/components/customization/import-wizard.d.ts +21 -0
  85. package/dist/types/modes/components/customization/index.d.ts +1 -0
  86. package/dist/types/modes/components/gajae-pet-widget.d.ts +8 -30
  87. package/dist/types/modes/components/irc-sidebar.d.ts +1 -0
  88. package/dist/types/modes/components/iterm-pet-transport.d.ts +122 -0
  89. package/dist/types/modes/components/oauth-selector.d.ts +10 -4
  90. package/dist/types/modes/components/pet-capability.d.ts +11 -25
  91. package/dist/types/modes/components/status-line/gh.d.ts +6 -2
  92. package/dist/types/modes/controllers/extension-ui-controller.d.ts +1 -1
  93. package/dist/types/modes/controllers/selector-controller.d.ts +7 -8
  94. package/dist/types/modes/controllers/stt-controller.d.ts +1 -1
  95. package/dist/types/modes/interactive-mode.d.ts +1 -0
  96. package/dist/types/modes/types.d.ts +1 -0
  97. package/dist/types/modes/utils/injected-user-submission.d.ts +10 -0
  98. package/dist/types/runtime-mcp/config.d.ts +9 -0
  99. package/dist/types/runtime-mcp/manager.d.ts +26 -1
  100. package/dist/types/runtime-mcp/oauth-discovery.d.ts +4 -0
  101. package/dist/types/runtime-mcp/oauth-flow.d.ts +24 -1
  102. package/dist/types/runtime-mcp/pool-key.d.ts +2 -0
  103. package/dist/types/runtime-mcp/protocol.d.ts +278 -0
  104. package/dist/types/runtime-mcp/scope-config.d.ts +29 -0
  105. package/dist/types/runtime-mcp/tool-bridge.d.ts +15 -13
  106. package/dist/types/runtime-mcp/transports/http.d.ts +9 -0
  107. package/dist/types/runtime-mcp/types.d.ts +102 -10
  108. package/dist/types/sdk/acp/adapter.d.ts +26 -5
  109. package/dist/types/sdk/broker/broker.d.ts +3 -55
  110. package/dist/types/sdk/broker/lifecycle.d.ts +18 -12
  111. package/dist/types/sdk/broker/session-index.d.ts +14 -5
  112. package/dist/types/sdk/broker/startup-failure.d.ts +16 -1
  113. package/dist/types/sdk/bus/chat-daemon-control.d.ts +51 -1
  114. package/dist/types/sdk/bus/chat-daemon-runtime.d.ts +4 -41
  115. package/dist/types/sdk/bus/config.d.ts +11 -0
  116. package/dist/types/sdk/bus/conversation-store.d.ts +0 -1
  117. package/dist/types/sdk/bus/daemon-paths.d.ts +6 -0
  118. package/dist/types/sdk/bus/discord-conversation.d.ts +2 -0
  119. package/dist/types/sdk/bus/discord-daemon.d.ts +17 -12
  120. package/dist/types/sdk/bus/existing-thread-readiness.d.ts +4 -34
  121. package/dist/types/sdk/bus/inbound-reaction-ordering.d.ts +61 -0
  122. package/dist/types/sdk/bus/index.d.ts +85 -99
  123. package/dist/types/sdk/bus/kind-aware-reconciliation.d.ts +29 -7
  124. package/dist/types/sdk/bus/managed-daemon.d.ts +2 -32
  125. package/dist/types/sdk/bus/notification-service.d.ts +52 -0
  126. package/dist/types/sdk/bus/operator-runtime.d.ts +1 -1
  127. package/dist/types/sdk/bus/prompt-reconciliation.d.ts +16 -5
  128. package/dist/types/sdk/bus/reconciliation-store.d.ts +113 -5
  129. package/dist/types/sdk/bus/session-control.d.ts +25 -1
  130. package/dist/types/sdk/bus/slack-conversation.d.ts +4 -0
  131. package/dist/types/sdk/bus/slack-daemon.d.ts +9 -17
  132. package/dist/types/sdk/bus/slack-live-provider.d.ts +3 -1
  133. package/dist/types/sdk/bus/slack-provider.d.ts +4 -0
  134. package/dist/types/sdk/bus/slack-thread-binding.d.ts +6 -40
  135. package/dist/types/sdk/bus/telegram-adoption-intent.d.ts +30 -104
  136. package/dist/types/sdk/bus/telegram-daemon-cli.d.ts +10 -0
  137. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +262 -19
  138. package/dist/types/sdk/bus/telegram-daemon-orphan-reap.d.ts +125 -0
  139. package/dist/types/sdk/bus/telegram-daemon-owner-registry.d.ts +22 -0
  140. package/dist/types/sdk/bus/telegram-daemon.d.ts +119 -310
  141. package/dist/types/sdk/bus/telegram-reference.d.ts +1 -47
  142. package/dist/types/sdk/bus/threaded-render.d.ts +1 -0
  143. package/dist/types/sdk/bus/topic-registry.d.ts +33 -17
  144. package/dist/types/sdk/cli/rows.d.ts +2 -11
  145. package/dist/types/sdk/cli/session-cli.d.ts +15 -25
  146. package/dist/types/sdk/client/client.d.ts +97 -1
  147. package/dist/types/sdk/host/control/dispatch.d.ts +0 -7
  148. package/dist/types/sdk/host/control/index.d.ts +1 -1
  149. package/dist/types/sdk/host/control/operations.d.ts +18 -5
  150. package/dist/types/sdk/host/control/runtime-gate.d.ts +19 -0
  151. package/dist/types/sdk/host/host.d.ts +15 -1
  152. package/dist/types/sdk/host/query/handlers.d.ts +5 -0
  153. package/dist/types/sdk/host/reverse-leases.d.ts +1 -1
  154. package/dist/types/sdk/host/session-runtime.d.ts +127 -7
  155. package/dist/types/sdk/host/types.d.ts +1 -1
  156. package/dist/types/sdk/host/websocket-transport.d.ts +1 -1
  157. package/dist/types/sdk/index.d.ts +2 -1
  158. package/dist/types/sdk/lifecycle/broker-client.d.ts +8 -0
  159. package/dist/types/sdk/lifecycle/client.d.ts +59 -0
  160. package/dist/types/sdk/lifecycle/index.d.ts +3 -0
  161. package/dist/types/sdk/lifecycle/service.d.ts +232 -0
  162. package/dist/types/sdk/mcp/server.d.ts +9 -6
  163. package/dist/types/sdk/prompt-deadline-lease.d.ts +43 -0
  164. package/dist/types/sdk/prompt-deadline-manager.d.ts +28 -0
  165. package/dist/types/sdk/prompt-status.d.ts +10 -3
  166. package/dist/types/sdk/protocol/operation-registry.d.ts +5 -0
  167. package/dist/types/sdk/receipt-state.d.ts +17 -0
  168. package/dist/types/sdk/reconciliation-extensions.d.ts +2 -0
  169. package/dist/types/sdk/router/index.d.ts +1 -0
  170. package/dist/types/sdk/router/session-router.d.ts +152 -0
  171. package/dist/types/sdk/session-list.d.ts +22 -0
  172. package/dist/types/sdk/session-reconnect.d.ts +43 -1
  173. package/dist/types/sdk/session.d.ts +34 -1
  174. package/dist/types/sdk/transport/serve-cli.d.ts +1 -4
  175. package/dist/types/session/account-inventory.d.ts +86 -0
  176. package/dist/types/session/agent-session.d.ts +145 -6
  177. package/dist/types/session/agent-storage.d.ts +10 -0
  178. package/dist/types/session/coordinator-tool-label.d.ts +28 -0
  179. package/dist/types/session/internal/managed-session-storage.d.ts +23 -4
  180. package/dist/types/session/messages.d.ts +1 -1
  181. package/dist/types/session/secure-token-file.d.ts +3 -0
  182. package/dist/types/session/session-manager.d.ts +40 -1
  183. package/dist/types/session/startup-auth-config.d.ts +44 -0
  184. package/dist/types/session/terminal-abort.d.ts +378 -0
  185. package/dist/types/session/yield-queue.d.ts +7 -0
  186. package/dist/types/session-import/claude.d.ts +26 -0
  187. package/dist/types/session-import/codex.d.ts +88 -0
  188. package/dist/types/session-import/command.d.ts +35 -0
  189. package/dist/types/session-import/detect.d.ts +22 -0
  190. package/dist/types/session-import/index.d.ts +7 -0
  191. package/dist/types/session-import/provider-codex.d.ts +22 -0
  192. package/dist/types/session-import/provider-service.d.ts +38 -0
  193. package/dist/types/session-import/redact.d.ts +23 -0
  194. package/dist/types/session-import/service.d.ts +47 -0
  195. package/dist/types/session-import/types.d.ts +120 -0
  196. package/dist/types/setup/paseo/check.d.ts +16 -0
  197. package/dist/types/setup/paseo/install-saga.d.ts +115 -0
  198. package/dist/types/setup/paseo/json-publisher.d.ts +76 -0
  199. package/dist/types/setup/paseo/orchestration-preferences.d.ts +24 -0
  200. package/dist/types/setup/paseo/paseo-ownership.d.ts +87 -0
  201. package/dist/types/setup/paseo/paseo-setup.d.ts +29 -0
  202. package/dist/types/setup/paseo/provider-config.d.ts +35 -0
  203. package/dist/types/setup/paseo/remove.d.ts +14 -0
  204. package/dist/types/setup/paseo/result-types.d.ts +59 -0
  205. package/dist/types/setup/paseo/setup-deps.d.ts +73 -0
  206. package/dist/types/setup/paseo/skills-bridge.d.ts +44 -0
  207. package/dist/types/skill-state/active-state.d.ts +5 -1
  208. package/dist/types/slash-commands/acp-builtins.d.ts +5 -1
  209. package/dist/types/slash-commands/builtin-registry.d.ts +2 -0
  210. package/dist/types/slash-commands/helpers/credential-switch.d.ts +12 -0
  211. package/dist/types/slash-commands/helpers/usage-report.d.ts +5 -6
  212. package/dist/types/slash-commands/types.d.ts +7 -1
  213. package/dist/types/task/executor.d.ts +10 -0
  214. package/dist/types/task/index.d.ts +3 -1
  215. package/dist/types/tools/ask-answer-registry.d.ts +6 -3
  216. package/dist/types/tools/bash-sleep-advisory.d.ts +14 -0
  217. package/dist/types/tools/bash.d.ts +2 -6
  218. package/dist/types/tools/browser/attach.d.ts +11 -0
  219. package/dist/types/tools/browser/launch.d.ts +20 -0
  220. package/dist/types/tools/browser/profile-discovery.d.ts +6 -0
  221. package/dist/types/tools/browser/render.d.ts +1 -0
  222. package/dist/types/tools/browser.d.ts +3 -1
  223. package/dist/types/tools/image-gen.d.ts +9 -2
  224. package/dist/types/tools/index.d.ts +8 -0
  225. package/dist/types/tools/monitor.d.ts +1 -1
  226. package/dist/types/tools/skill-discovery.d.ts +8 -1
  227. package/dist/types/tools/subagent.d.ts +2 -0
  228. package/dist/types/tools/ultragoal-ask-guard.d.ts +2 -2
  229. package/dist/types/utils/herdr-pane.d.ts +76 -0
  230. package/package.json +19 -9
  231. package/scripts/build-binary.ts +2 -1
  232. package/scripts/build-sdk-package-smoke.ts +2 -13
  233. package/scripts/capture-sticky-viewport-showcase.ts +0 -1
  234. package/scripts/compile-args.ts +2 -0
  235. package/scripts/generate-sdk-adapter-parity-manifest.ts +31 -16
  236. package/scripts/generate-sdk-operation-inventory.ts +28 -0
  237. package/scripts/generate-telegram-baseline-manifest.ts +1 -0
  238. package/scripts/generate-tool-catalog.ts +6 -4
  239. package/scripts/verify-gjc-sdk-canonicalization.ts +5 -39
  240. package/src/async/job-manager.ts +352 -17
  241. package/src/async/support.ts +25 -0
  242. package/src/autoresearch/dashboard.ts +28 -13
  243. package/src/capability/index.ts +3 -1
  244. package/src/capability/mcp.ts +6 -0
  245. package/src/capability/types.ts +6 -0
  246. package/src/cli/accounts-cli.ts +395 -0
  247. package/src/cli/args.ts +23 -0
  248. package/src/cli/auth-broker-cli.ts +136 -66
  249. package/src/cli/auth-gateway-cli.ts +94 -70
  250. package/src/cli/config-cli.ts +27 -2
  251. package/src/cli/crash-cli.ts +78 -0
  252. package/src/cli/customize-doctor.ts +1605 -0
  253. package/src/cli/daemon-cli.ts +12 -3
  254. package/src/cli/fast-help.ts +1 -0
  255. package/src/cli/mcp-cli.ts +128 -60
  256. package/src/cli/notify-cli.ts +8 -14
  257. package/src/cli/root-flags.ts +8 -0
  258. package/src/cli/setup-cli.ts +145 -0
  259. package/src/cli/skills-cli.ts +38 -2
  260. package/src/cli/update-cli.ts +167 -12
  261. package/src/cli.ts +4 -0
  262. package/src/commands/accounts.ts +71 -0
  263. package/src/commands/crash.ts +35 -0
  264. package/src/commands/customize.ts +84 -0
  265. package/src/commands/daemon.ts +2 -2
  266. package/src/commands/deep-interview.ts +2 -0
  267. package/src/commands/launch.ts +1 -0
  268. package/src/commands/mcp.ts +12 -7
  269. package/src/commands/ralplan.ts +2 -0
  270. package/src/commands/sdk.ts +65 -32
  271. package/src/commands/setup.ts +5 -0
  272. package/src/commands/skills.ts +14 -4
  273. package/src/commands/ultragoal.ts +10 -1
  274. package/src/commands/update.ts +26 -15
  275. package/src/config/atomic-yaml-patch-worker.ts +57 -0
  276. package/src/config/atomic-yaml-patch.ts +769 -29
  277. package/src/config/config-schema-version.ts +10 -0
  278. package/src/config/file-lock.ts +70 -1
  279. package/src/config/mcp-schema.json +52 -14
  280. package/src/config/model-profile-activation.ts +20 -1
  281. package/src/config/model-profiles.ts +29 -19
  282. package/src/config/model-registry.ts +132 -21
  283. package/src/config/models-config-schema.ts +1 -1
  284. package/src/config/settings-schema.ts +76 -6
  285. package/src/config/settings.ts +4038 -199
  286. package/src/config/skill-settings-defaults.ts +41 -3
  287. package/src/config/task-delegation.ts +87 -0
  288. package/src/config/update-channel.ts +23 -0
  289. package/src/coordinator-mcp/server.ts +377 -308
  290. package/src/crash/index-store.ts +723 -0
  291. package/src/crash/nudge.ts +89 -0
  292. package/src/crash/record-loader.ts +149 -0
  293. package/src/crash/report.ts +551 -0
  294. package/src/crash/sanitize.ts +129 -0
  295. package/src/cursor.ts +36 -10
  296. package/src/customization/import.ts +946 -0
  297. package/src/customization/index.ts +4 -0
  298. package/src/customization/inventory.ts +335 -0
  299. package/src/customization/mutations.ts +141 -0
  300. package/src/customization/types.ts +223 -0
  301. package/src/daemon/control-types.ts +8 -1
  302. package/src/daemon/operator-contract.ts +6 -6
  303. package/src/defaults/gjc/skills/deep-interview/SKILL.md +11 -22
  304. package/src/defaults/gjc/skills/ralplan/SKILL.md +36 -23
  305. package/src/defaults/gjc/skills/ultragoal/SKILL.md +16 -0
  306. package/src/discovery/claude-hooks.ts +38 -0
  307. package/src/discovery/claude.ts +142 -209
  308. package/src/discovery/codex-hooks.ts +38 -0
  309. package/src/discovery/codex.ts +132 -198
  310. package/src/discovery/helpers.ts +8 -1
  311. package/src/discovery/index.ts +2 -0
  312. package/src/discovery/mcp-compat.ts +234 -0
  313. package/src/discovery/mcp-json.ts +6 -0
  314. package/src/edit/renderer.ts +40 -0
  315. package/src/edit/streaming.ts +4 -2
  316. package/src/exec/bash-executor.ts +24 -20
  317. package/src/export/html/index.ts +51 -2
  318. package/src/extensibility/extensions/loader.ts +9 -1
  319. package/src/extensibility/extensions/runner.ts +17 -4
  320. package/src/extensibility/extensions/types.ts +31 -6
  321. package/src/extensibility/gjc-plugins/constrained-hooks.ts +33 -0
  322. package/src/extensibility/gjc-plugins/installer.ts +9 -5
  323. package/src/extensibility/gjc-plugins/observability.ts +8 -2
  324. package/src/extensibility/gjc-plugins/registry.ts +13 -2
  325. package/src/extensibility/gjc-plugins/runtime-adapters.ts +3 -1
  326. package/src/extensibility/gjc-plugins/schema.ts +222 -39
  327. package/src/extensibility/hooks/loader.ts +154 -10
  328. package/src/extensibility/runtime-skill-discovery.ts +257 -60
  329. package/src/extensibility/skill-management.ts +368 -0
  330. package/src/extensibility/skills.ts +52 -16
  331. package/src/gjc-runtime/deep-interview-runtime.ts +45 -61
  332. package/src/gjc-runtime/launch-worktree.ts +56 -2
  333. package/src/gjc-runtime/linux-proc.ts +7 -1
  334. package/src/gjc-runtime/ralplan-runtime.ts +100 -164
  335. package/src/gjc-runtime/session-state-lock.ts +992 -0
  336. package/src/gjc-runtime/session-state-sidecar.ts +644 -142
  337. package/src/gjc-runtime/state-writer.ts +39 -14
  338. package/src/gjc-runtime/tmux-owner-isolation.ts +5 -4
  339. package/src/gjc-runtime/ultragoal-guard.ts +11 -4
  340. package/src/gjc-runtime/ultragoal-runtime.ts +44 -41
  341. package/src/gjc-runtime/workflow-settings.ts +596 -0
  342. package/src/goals/tools/goal-tool.ts +6 -1
  343. package/src/hooks/events.ts +291 -0
  344. package/src/hooks/native-skill-hook.ts +4 -0
  345. package/src/hooks/normalize.ts +369 -0
  346. package/src/hooks/skill-state.ts +165 -46
  347. package/src/internal-urls/docs-index.generated.ts +40 -32
  348. package/src/main.ts +32 -5
  349. package/src/migrate/mcp-mapper.ts +25 -2
  350. package/src/modes/acp/abort-scope.ts +29 -0
  351. package/src/modes/acp/acp-agent.ts +423 -98
  352. package/src/modes/components/assistant-message.ts +6 -1
  353. package/src/modes/components/customization/customization-dashboard.ts +443 -0
  354. package/src/modes/components/customization/import-wizard.ts +372 -0
  355. package/src/modes/components/customization/index.ts +1 -0
  356. package/src/modes/components/gajae-pet-widget.ts +539 -213
  357. package/src/modes/components/irc-sidebar.ts +11 -0
  358. package/src/modes/components/iterm-pet-transport.ts +585 -0
  359. package/src/modes/components/oauth-selector.ts +277 -86
  360. package/src/modes/components/pet-capability.ts +39 -40
  361. package/src/modes/components/settings-selector.ts +9 -0
  362. package/src/modes/components/status-line/gh.ts +45 -1
  363. package/src/modes/components/status-line/segments.ts +16 -1
  364. package/src/modes/components/tool-status-header.ts +13 -3
  365. package/src/modes/controllers/command-controller.ts +20 -8
  366. package/src/modes/controllers/extension-ui-controller.ts +56 -257
  367. package/src/modes/controllers/input-controller.ts +10 -0
  368. package/src/modes/controllers/runtime-mcp-command-controller.ts +16 -3
  369. package/src/modes/controllers/selector-controller.ts +177 -72
  370. package/src/modes/controllers/stt-controller.ts +3 -3
  371. package/src/modes/interactive-mode.ts +88 -16
  372. package/src/modes/print-mode.ts +35 -6
  373. package/src/modes/runtime-init.ts +21 -68
  374. package/src/modes/types.ts +1 -0
  375. package/src/modes/utils/injected-user-submission.ts +25 -0
  376. package/src/modes/utils/terminal-bell.ts +15 -1
  377. package/src/prompts/agent-fragments/ralplan-persistence.md +1 -1
  378. package/src/prompts/agent-fragments/restricted-bash.md +1 -1
  379. package/src/prompts/agents/critic.md +1 -1
  380. package/src/prompts/system/rlm-research.md +1 -1
  381. package/src/prompts/system/system-prompt.md +0 -2
  382. package/src/prompts/tools/bash.md +7 -1
  383. package/src/prompts/tools/browser.md +1 -1
  384. package/src/prompts/tools/cron.md +6 -6
  385. package/src/prompts/tools/read.md +0 -2
  386. package/src/prompts/tools/skill-discovery.md +4 -2
  387. package/src/runtime-mcp/client.ts +427 -23
  388. package/src/runtime-mcp/config.ts +37 -5
  389. package/src/runtime-mcp/manager-pool.test.ts +56 -0
  390. package/src/runtime-mcp/manager.ts +52 -2
  391. package/src/runtime-mcp/oauth-discovery.ts +13 -0
  392. package/src/runtime-mcp/oauth-flow.ts +46 -1
  393. package/src/runtime-mcp/pool-key.ts +5 -1
  394. package/src/runtime-mcp/pool.test.ts +9 -0
  395. package/src/runtime-mcp/protocol.test.ts +353 -0
  396. package/src/runtime-mcp/protocol.ts +624 -0
  397. package/src/runtime-mcp/scope-config.ts +78 -0
  398. package/src/runtime-mcp/tool-bridge.ts +44 -10
  399. package/src/runtime-mcp/transports/http.ts +84 -13
  400. package/src/runtime-mcp/types.ts +123 -12
  401. package/src/sdk/acp/adapter.ts +328 -97
  402. package/src/sdk/broker/broker.ts +160 -741
  403. package/src/sdk/broker/ensure.ts +9 -1
  404. package/src/sdk/broker/lifecycle.ts +510 -116
  405. package/src/sdk/broker/operation-receipt-ledger.ts +11 -2
  406. package/src/sdk/broker/process-incarnation.ts +19 -0
  407. package/src/sdk/broker/session-index.ts +516 -117
  408. package/src/sdk/broker/startup-failure.ts +28 -5
  409. package/src/sdk/broker/transport.ts +1 -13
  410. package/src/sdk/bus/chat-daemon-cli.ts +4 -2
  411. package/src/sdk/bus/chat-daemon-control.ts +67 -6
  412. package/src/sdk/bus/chat-daemon-runtime.ts +183 -859
  413. package/src/sdk/bus/config.ts +13 -0
  414. package/src/sdk/bus/conversation-store.ts +59 -171
  415. package/src/sdk/bus/daemon-paths.ts +20 -0
  416. package/src/sdk/bus/discord-conversation.ts +2 -0
  417. package/src/sdk/bus/discord-daemon.ts +524 -126
  418. package/src/sdk/bus/discord-live-provider.ts +206 -79
  419. package/src/sdk/bus/existing-thread-readiness.ts +4 -97
  420. package/src/sdk/bus/inbound-reaction-ordering.ts +122 -0
  421. package/src/sdk/bus/index.ts +2480 -456
  422. package/src/sdk/bus/kind-aware-reconciliation.ts +204 -34
  423. package/src/sdk/bus/lifecycle-commands.ts +2 -2
  424. package/src/sdk/bus/managed-daemon.ts +2 -145
  425. package/src/sdk/bus/notification-service.ts +196 -0
  426. package/src/sdk/bus/operator-runtime.ts +1 -1
  427. package/src/sdk/bus/prompt-reconciliation.ts +24 -15
  428. package/src/sdk/bus/reconciliation-store.ts +553 -24
  429. package/src/sdk/bus/session-control.ts +49 -3
  430. package/src/sdk/bus/slack-conversation.ts +4 -0
  431. package/src/sdk/bus/slack-daemon.ts +733 -134
  432. package/src/sdk/bus/slack-live-provider.ts +65 -15
  433. package/src/sdk/bus/slack-provider.ts +4 -0
  434. package/src/sdk/bus/slack-thread-binding.ts +9 -104
  435. package/src/sdk/bus/telegram-adoption-intent.ts +299 -209
  436. package/src/sdk/bus/telegram-daemon-cli.ts +194 -7
  437. package/src/sdk/bus/telegram-daemon-contract.ts +265 -19
  438. package/src/sdk/bus/telegram-daemon-control.ts +3 -13
  439. package/src/sdk/bus/telegram-daemon-orphan-reap.ts +498 -0
  440. package/src/sdk/bus/telegram-daemon-owner-registry.ts +176 -0
  441. package/src/sdk/bus/telegram-daemon.ts +2552 -2280
  442. package/src/sdk/bus/telegram-reference.ts +1 -226
  443. package/src/sdk/bus/telegram-setup.ts +2 -2
  444. package/src/sdk/bus/threaded-render.ts +1 -0
  445. package/src/sdk/bus/topic-registry.ts +279 -146
  446. package/src/sdk/cli/rows.ts +2 -35
  447. package/src/sdk/cli/session-cli.ts +776 -700
  448. package/src/sdk/client/client.ts +892 -1
  449. package/src/sdk/host/control/dispatch.ts +93 -58
  450. package/src/sdk/host/control/index.ts +1 -1
  451. package/src/sdk/host/control/operations.ts +18 -10
  452. package/src/sdk/host/control/runtime-gate.ts +40 -0
  453. package/src/sdk/host/host.ts +63 -12
  454. package/src/sdk/host/query/handlers.ts +66 -7
  455. package/src/sdk/host/reverse-leases.ts +2 -2
  456. package/src/sdk/host/sdk-surface-parity.test.ts +1 -1
  457. package/src/sdk/host/session-runtime.test.ts +2787 -34
  458. package/src/sdk/host/session-runtime.ts +1489 -101
  459. package/src/sdk/host/surface-policy.ts +12 -8
  460. package/src/sdk/host/types.ts +1 -1
  461. package/src/sdk/host/websocket-transport.lifecycle.test.ts +31 -59
  462. package/src/sdk/host/websocket-transport.ts +85 -50
  463. package/src/sdk/index.ts +2 -1
  464. package/src/sdk/lifecycle/broker-client.ts +53 -0
  465. package/src/sdk/lifecycle/client.ts +234 -0
  466. package/src/sdk/lifecycle/index.ts +3 -0
  467. package/src/sdk/{bus/recent-activity.ts → lifecycle/recent-sessions.ts} +5 -2
  468. package/src/sdk/lifecycle/service.ts +720 -0
  469. package/src/sdk/mcp/server.ts +226 -188
  470. package/src/sdk/prompt-deadline-lease.ts +55 -0
  471. package/src/sdk/prompt-deadline-manager.ts +148 -0
  472. package/src/sdk/prompt-status.ts +11 -3
  473. package/src/sdk/protocol/operation-inventory.generated.json +272 -129
  474. package/src/sdk/protocol/operation-registry.ts +71 -8
  475. package/src/sdk/receipt-state.ts +43 -0
  476. package/src/sdk/reconciliation-extensions.ts +14 -0
  477. package/src/sdk/router/index.ts +1 -0
  478. package/src/sdk/router/session-router.ts +1658 -0
  479. package/src/sdk/session-list.ts +93 -0
  480. package/src/sdk/session-reconnect.ts +45 -1
  481. package/src/sdk/session.ts +716 -120
  482. package/src/sdk/transport/serve-cli.ts +18 -34
  483. package/src/session/account-inventory.ts +603 -0
  484. package/src/session/agent-session.ts +2857 -582
  485. package/src/session/agent-storage.ts +35 -0
  486. package/src/session/blob-store.ts +32 -1
  487. package/src/session/coordinator-tool-label.ts +40 -0
  488. package/src/session/internal/managed-session-scope.ts +4 -0
  489. package/src/session/internal/managed-session-storage.ts +173 -12
  490. package/src/session/messages.ts +1 -1
  491. package/src/session/secure-token-file.ts +80 -0
  492. package/src/session/session-manager.ts +730 -58
  493. package/src/session/session-storage.ts +25 -16
  494. package/src/session/startup-auth-config.ts +297 -0
  495. package/src/session/terminal-abort.ts +1253 -0
  496. package/src/session/yield-queue.ts +57 -15
  497. package/src/session-import/claude.ts +334 -0
  498. package/src/session-import/codex.ts +1033 -0
  499. package/src/session-import/command.ts +77 -0
  500. package/src/session-import/detect.ts +133 -0
  501. package/src/session-import/index.ts +7 -0
  502. package/src/session-import/provider-codex.ts +327 -0
  503. package/src/session-import/provider-service.ts +457 -0
  504. package/src/session-import/redact.ts +128 -0
  505. package/src/session-import/service.ts +1264 -0
  506. package/src/session-import/types.ts +148 -0
  507. package/src/setup/paseo/check.ts +213 -0
  508. package/src/setup/paseo/install-saga.ts +293 -0
  509. package/src/setup/paseo/json-publisher.ts +263 -0
  510. package/src/setup/paseo/orchestration-preferences.ts +86 -0
  511. package/src/setup/paseo/paseo-ownership.ts +224 -0
  512. package/src/setup/paseo/paseo-setup.ts +253 -0
  513. package/src/setup/paseo/provider-config.ts +139 -0
  514. package/src/setup/paseo/remove.ts +183 -0
  515. package/src/setup/paseo/result-types.ts +79 -0
  516. package/src/setup/paseo/setup-deps.ts +175 -0
  517. package/src/setup/paseo/skills-bridge.ts +260 -0
  518. package/src/skill-state/active-state.ts +21 -11
  519. package/src/slash-commands/acp-builtins.ts +9 -1
  520. package/src/slash-commands/builtin-registry.ts +98 -16
  521. package/src/slash-commands/helpers/credential-switch.ts +143 -0
  522. package/src/slash-commands/helpers/mcp.ts +9 -3
  523. package/src/slash-commands/helpers/usage-report.ts +61 -56
  524. package/src/slash-commands/types.ts +7 -2
  525. package/src/task/executor.ts +30 -11
  526. package/src/task/index.ts +66 -7
  527. package/src/task/render.ts +23 -9
  528. package/src/tools/ask-answer-registry.ts +23 -7
  529. package/src/tools/ask.ts +8 -4
  530. package/src/tools/bash-sleep-advisory.ts +115 -0
  531. package/src/tools/bash.ts +98 -18
  532. package/src/tools/browser/attach.ts +71 -3
  533. package/src/tools/browser/launch.ts +86 -13
  534. package/src/tools/browser/profile-discovery.ts +57 -7
  535. package/src/tools/browser/render.ts +8 -1
  536. package/src/tools/browser.ts +130 -21
  537. package/src/tools/computer.enforcement.test.ts +1 -1
  538. package/src/tools/computer.ts +3 -2
  539. package/src/tools/cron.ts +12 -1
  540. package/src/tools/image-gen.ts +27 -16
  541. package/src/tools/index.ts +8 -0
  542. package/src/tools/job.ts +33 -6
  543. package/src/tools/monitor.ts +41 -2
  544. package/src/tools/skill-discovery.ts +21 -29
  545. package/src/tools/subagent.ts +142 -24
  546. package/src/tools/tool-catalog.generated.ts +6 -6
  547. package/src/tools/ultragoal-ask-guard.ts +10 -3
  548. package/src/utils/herdr-pane.ts +345 -0
  549. package/src/utils/title-generator.ts +2 -0
  550. package/src/web/scrapers/index.ts +6 -4
  551. package/dist/types/cli/malloc-env-guard.test.d.ts +0 -1
  552. package/dist/types/defaults/gjc-defaults.test.d.ts +0 -1
  553. package/dist/types/memory-backend/lazy-loading.test.d.ts +0 -1
  554. package/dist/types/runtime/boot-ordering.test.d.ts +0 -1
  555. package/dist/types/runtime/lazy-service.test.d.ts +0 -1
  556. package/dist/types/runtime/network-prewarm-service.test.d.ts +0 -1
  557. package/dist/types/runtime/workspace-tree-service.test.d.ts +0 -1
  558. package/dist/types/runtime-mcp/config.test.d.ts +0 -1
  559. package/dist/types/runtime-mcp/http.test.d.ts +0 -1
  560. package/dist/types/runtime-mcp/manager-pool.test.d.ts +0 -1
  561. package/dist/types/runtime-mcp/pool-key.test.d.ts +0 -1
  562. package/dist/types/runtime-mcp/pool.test.d.ts +0 -1
  563. package/dist/types/sdk/bus/lifecycle-control-runtime.d.ts +0 -117
  564. package/dist/types/sdk/bus/lifecycle-orchestrator.d.ts +0 -141
  565. package/dist/types/sdk/bus/telegram-cli.d.ts +0 -31
  566. package/dist/types/sdk/elevation/allowlist.d.ts +0 -20
  567. package/dist/types/sdk/elevation/audit.d.ts +0 -93
  568. package/dist/types/sdk/elevation/capability.d.ts +0 -3
  569. package/dist/types/sdk/elevation/digest.d.ts +0 -72
  570. package/dist/types/sdk/elevation/dispatch-receipt.d.ts +0 -85
  571. package/dist/types/sdk/elevation/grant-ledger.d.ts +0 -163
  572. package/dist/types/sdk/elevation/owner.d.ts +0 -48
  573. package/dist/types/sdk/host/import-graph.test.d.ts +0 -1
  574. package/dist/types/sdk/host/sdk-surface-parity.test.d.ts +0 -1
  575. package/dist/types/sdk/host/session-runtime.test.d.ts +0 -1
  576. package/dist/types/sdk/host/websocket-transport.lifecycle.test.d.ts +0 -1
  577. package/dist/types/session/auth-broker-config.d.ts +0 -13
  578. package/dist/types/session/heap-eviction-artifacts.test.d.ts +0 -1
  579. package/dist/types/skills/skills.test.d.ts +0 -1
  580. package/dist/types/slash-commands/notify-command.test.d.ts +0 -1
  581. package/dist/types/task/token-log.test.d.ts +0 -1
  582. package/dist/types/tools/computer.enforcement.test.d.ts +0 -1
  583. package/dist/types/tools/descriptors.test.d.ts +0 -1
  584. package/scripts/g005-daemon-smoke.ts +0 -180
  585. package/scripts/g011-daemon-path-smoke.ts +0 -153
  586. package/src/sdk/bus/lifecycle-control-runtime.ts +0 -1733
  587. package/src/sdk/bus/lifecycle-orchestrator.ts +0 -489
  588. package/src/sdk/bus/telegram-cli.ts +0 -212
  589. package/src/sdk/elevation/allowlist.ts +0 -54
  590. package/src/sdk/elevation/audit.ts +0 -208
  591. package/src/sdk/elevation/capability.ts +0 -41
  592. package/src/sdk/elevation/digest.ts +0 -129
  593. package/src/sdk/elevation/dispatch-receipt.ts +0 -184
  594. package/src/sdk/elevation/grant-ledger.ts +0 -1142
  595. package/src/sdk/elevation/owner.ts +0 -109
  596. package/src/session/auth-broker-config.ts +0 -109
  597. /package/dist/types/{ai-core-import-gate.test.d.ts → discovery/claude-hooks.d.ts} +0 -0
  598. /package/dist/types/{cli/fixture-report.test.d.ts → discovery/codex-hooks.d.ts} +0 -0
  599. /package/dist/types/sdk/{bus/recent-activity.d.ts → lifecycle/recent-sessions.d.ts} +0 -0
@@ -1,10 +1,14 @@
1
1
  import {
2
2
  type AnimationRegistration,
3
3
  buildGajaePixelFrames,
4
+ type CellRect,
4
5
  type Component,
5
6
  type Container,
7
+ type GajaeGifFrame,
8
+ type GajaeGifTimeline,
6
9
  type GajaePixelFrames,
7
10
  getCellDimensions,
11
+ getGajaePetGifCached,
8
12
  PET_SKINS,
9
13
  type PetBurst,
10
14
  type PetFrameName,
@@ -12,12 +16,13 @@ import {
12
16
  type PetSkinId,
13
17
  petBurstDurationMs,
14
18
  petBurstFrame,
19
+ type RasterLeaseToken,
15
20
  registerAnimationCallback,
16
- resolvePetMode,
17
21
  type TUI,
22
+ wrapITerm2RecordForTmux,
18
23
  } from "@gajae-code/tui";
19
24
  import type { CustomEditor } from "./custom-editor";
20
- import { getPetPixelProtocol } from "./pet-capability";
25
+ import { getItermPetUnavailableReason, getPetPixelProtocol, getVerifiedItermPetAvailability } from "./pet-capability";
21
26
 
22
27
  /** Re-exported from the tui skin registry so widget-relative imports stay valid. */
23
28
  export type { PetMode, PetSkinId };
@@ -38,6 +43,8 @@ const KITTY_DROP_FRACTION = 0.45;
38
43
  const petKittyDropPx = (cellHeightPx: number): number =>
39
44
  Math.min(Math.max(0, cellHeightPx - 1), Math.floor(cellHeightPx * KITTY_DROP_FRACTION));
40
45
  const PET_RAISE_ROWS = 1;
46
+ const PET_ART_ROWS = 2;
47
+ const ITERM_CANVAS_ROWS = PET_ART_ROWS + 1;
41
48
  const allocatedPetKittyImageIds = new Set<number>();
42
49
 
43
50
  function allocatePetKittyImageId(): number {
@@ -60,17 +67,22 @@ function sameFootprint(left: SixelFootprint, right: SixelFootprint): boolean {
60
67
  return left.x === right.x && left.y === right.y && left.columns === right.columns && left.rows === right.rows;
61
68
  }
62
69
 
70
+ type PetOverlayEmission = {
71
+ payload: string;
72
+ onWritten?: () => void;
73
+ };
74
+
63
75
  /**
64
76
  * Which widget currently owns each TUI's single shared post-render emitter
65
77
  * slot. A stale or repeated dispose (or off-switch) of a predecessor widget
66
78
  * must never clear a successor's overlay authority.
67
79
  */
68
80
  const petOverlayEmitterOwners = new WeakMap<TUI, GajaePetWidget>();
81
+ const petOverlayOwnershipEpochs = new WeakMap<TUI, number>();
69
82
 
70
- /** Random gap between automatic claw flexes (fires while idle AND working). */
83
+ /** Random gap between automatic claw flexes while work is active. */
71
84
  const AUTO_FLEX_MIN_GAP_MS = 12_000;
72
85
  const AUTO_FLEX_MAX_GAP_MS = 40_000;
73
-
74
86
  function animationFrameAt(
75
87
  steps: ReadonlyArray<readonly [PetFrameName, number]>,
76
88
  now: number,
@@ -102,6 +114,27 @@ function animationDuration(steps: ReadonlyArray<readonly [PetFrameName, number]>
102
114
  return steps.reduce((sum, [, ms]) => sum + ms, 0);
103
115
  }
104
116
 
117
+ function gifTimeline(steps: ReadonlyArray<readonly [PetFrameName, number]>): GajaeGifFrame[] {
118
+ return steps.map(([name, delayMs]) => ({ name, delayMs }));
119
+ }
120
+
121
+ function burstGifTimeline(burst: PetBurst): GajaeGifTimeline {
122
+ const frames = gifTimeline(burst.intro);
123
+ const tail = burst.tail;
124
+ if (!tail || tail.frames.length === 0) return frames;
125
+ for (let elapsed = 0; elapsed < tail.ms; elapsed += tail.stepMs) {
126
+ frames.push({
127
+ name: tail.frames[Math.floor(elapsed / tail.stepMs) % tail.frames.length]!,
128
+ delayMs: Math.min(tail.stepMs, tail.ms - elapsed),
129
+ });
130
+ }
131
+ return frames;
132
+ }
133
+
134
+ function timelineSignature(frames: GajaeGifTimeline): string {
135
+ return frames.map(({ name, delayMs }) => `${name}:${delayMs}`).join(",");
136
+ }
137
+
105
138
  interface WorkTransition {
106
139
  startedAt: number;
107
140
  steps: ReadonlyArray<readonly [PetFrameName, number]>;
@@ -110,18 +143,10 @@ interface WorkTransition {
110
143
  const OUROBOROS_WORK_BURST_MIN_GAP_MS = 14_000;
111
144
  const OUROBOROS_WORK_BURST_MAX_GAP_MS = 30_000;
112
145
 
113
- /**
114
- * Selector preview: fire the first burst this soon after a skin is previewed, so the
115
- * pet shows one idle eye-roll (base -> gazeL -> base -> gazeR) and then its signature
116
- * flex/cry. Live use keeps the random AUTO_FLEX gap; only preview forces this demo.
117
- */
146
+ /** Selector preview: fire the first burst this soon after a skin is previewed. */
118
147
  const PREVIEW_INTRO_MS = 2300;
119
148
 
120
- /**
121
- * Wraps the composer editor, reserving a right-side area beside it where the
122
- * real-pixel pet is drawn. The editor just renders narrower; the pet pixels
123
- * are emitted separately as an absolute-positioned overlay.
124
- */
149
+ /** Reserve a right-side area beside the composer where the pixel pet is drawn. */
125
150
  export class PetFramedEditor implements Component {
126
151
  #editor: CustomEditor;
127
152
  #reserve = 0;
@@ -143,27 +168,12 @@ export class PetFramedEditor implements Component {
143
168
  }
144
169
 
145
170
  render(width: number): string[] {
146
- if (!this.canFit(width)) {
147
- return this.#editor.render(width);
148
- }
171
+ if (!this.canFit(width)) return this.#editor.render(width);
149
172
  return this.#editor.render(width - this.#reserve);
150
173
  }
151
174
  }
152
175
 
153
- /**
154
- * The gajae pet: a real-pixel sprite living in a reserved area beside the
155
- * composer. It is nearest-neighbor scaled to the two terminal rows occupied
156
- * by an empty one-line composer and lifted one row so its feet meet the input
157
- * box's bottom edge.
158
- *
159
- * Rendering has two paths that share one payload builder:
160
- * - a post-render emitter re-draws the sprite after every TUI write (line
161
- * renders clear the pet cells, so the overlay must be re-applied), and
162
- * - frame advances write the payload directly to the terminal, because the
163
- * TUI skips writes entirely when no component line changed.
164
- *
165
- * Requires a sixel- or kitty-graphics terminal (`pixelProtocol()`).
166
- */
176
+ /** The gajae pet pixel sprite and protocol-specific rendering lifecycle. */
167
177
  export class GajaePetWidget {
168
178
  #ui: TUI;
169
179
  #editor: CustomEditor;
@@ -183,25 +193,28 @@ export class GajaePetWidget {
183
193
  #workTransition: WorkTransition | undefined;
184
194
  #nextWorkBurstAt = 0;
185
195
  #workBurstIndex = 0;
196
+ #flexSource: "preview" | "working" | undefined;
197
+ #previewFlexAt = 0;
186
198
  #autoFlexGapMs: [number, number] | null;
187
- #forcedProtocol: "sixel" | "kitty" | "iterm2" | undefined;
188
- /** Cell metrics the current frames were built for; a change triggers a rebuild. */
199
+ #forcedProtocol: "sixel" | "kitty" | "iterm" | undefined;
189
200
  #builtCellW = 0;
190
201
  #builtCellH = 0;
191
202
  #kittyImageId: number | undefined;
192
- /** True while a kitty placement may exist on screen; cleared only after the delete escape is delivered. */
193
203
  #kittyCleanupPending = false;
194
- /** Last emitted Sixel raster position; retained until an erase is actually delivered. */
204
+ #kittyCleanupGeneration = 0;
195
205
  #lastSixelFootprint: SixelFootprint | undefined;
196
- /** Terminal state: a disposed widget never touches the TUI or shared slots again. */
197
206
  #disposed = false;
198
- /**
199
- * True while the previous overlay frame carried the cleanup payload. The
200
- * TUI writes the frame after the emitter returns, so delivery is
201
- * acknowledged only on the next emitter pass — and only while the terminal
202
- * stayed available, since a failed render write drops availability.
203
- */
204
- #frameCleanupAwaitingAck = false;
207
+ #ownedOverlayEpoch = 0;
208
+ #itermLease: RasterLeaseToken | undefined;
209
+ #disposePromise: Promise<void> | undefined;
210
+ #disposeRasterBarrier: Promise<void> = Promise.resolve();
211
+ #disposeNeedsLifecycle = false;
212
+ #itermProtocol = false;
213
+ #itermLastSemantic = "";
214
+ #itermOwner = `gajae-pet-${Math.random().toString(36).slice(2)}`;
215
+ #itermGeneration = 0;
216
+ #itermSubmitPending = false;
217
+ #syncManagedItermCursor: (row: number, column: number) => Promise<boolean>;
205
218
 
206
219
  constructor(options: {
207
220
  ui: TUI;
@@ -209,10 +222,9 @@ export class GajaePetWidget {
209
222
  editorContainer: Container;
210
223
  floorContainer: Container;
211
224
  isWorking: () => boolean;
212
- /** Rows rendered below the composer box (pet floor + hook widgets). */
213
225
  getComposerBottomOffset: () => number;
214
- forcePixelProtocol?: "sixel" | "kitty" | "iterm2";
215
- /** Random [min, max] ms between auto-flexes; null disables. */
226
+ syncManagedItermCursor: (row: number, column: number) => Promise<boolean>;
227
+ forcePixelProtocol?: "sixel" | "kitty";
216
228
  autoFlexGapMs?: [number, number] | null;
217
229
  }) {
218
230
  this.#ui = options.ui;
@@ -222,13 +234,13 @@ export class GajaePetWidget {
222
234
  this.#framedEditor = new PetFramedEditor(options.editor);
223
235
  this.#isWorking = options.isWorking;
224
236
  this.#getComposerBottomOffset = options.getComposerBottomOffset;
237
+ this.#syncManagedItermCursor = options.syncManagedItermCursor;
225
238
  this.#forcedProtocol = options.forcePixelProtocol;
226
239
  this.#autoFlexGapMs =
227
240
  options.autoFlexGapMs === undefined ? [AUTO_FLEX_MIN_GAP_MS, AUTO_FLEX_MAX_GAP_MS] : options.autoFlexGapMs;
228
241
  }
229
242
 
230
- /** Protocol available for the real-pixel pet, if any. */
231
- static pixelProtocol(): "sixel" | "kitty" | "iterm2" | null {
243
+ static pixelProtocol(): "sixel" | "kitty" | "iterm" | null {
232
244
  return getPetPixelProtocol();
233
245
  }
234
246
 
@@ -240,19 +252,25 @@ export class GajaePetWidget {
240
252
  return performance.now() < this.#flexUntil;
241
253
  }
242
254
 
243
- setMode(mode: string): void {
244
- this.#applyMode(resolvePetMode(mode), true);
255
+ setMode(mode: PetMode): void {
256
+ this.#applyMode(mode, true);
257
+ }
258
+
259
+ async suspendItermCapability(): Promise<void> {
260
+ if (!this.#isActiveOwner()) return;
261
+ this.#itermGeneration++;
262
+ const lease = this.#itermLease;
263
+ this.#itermLease = undefined;
264
+ this.#itermLastSemantic = "";
265
+ if (lease) await this.#ui.invalidateRasterLease({ token: lease, cause: "capability-loss" });
266
+ this.#ui.requestRender(true);
245
267
  }
246
268
 
247
- /** Live preview during a selector: change the sprite without re-mounting the
248
- * composer editor (that would tear down the open overlay). After a short idle
249
- * eye-roll it fires the signature burst once (RedGajae flex, BlueGajae para-para
250
- * then sob) so the selector demos the animation instead of waiting the random gap. */
251
269
  previewMode(mode: PetMode): void {
270
+ if (this.#disposed) return;
252
271
  this.#applyMode(mode, false);
253
- if (mode !== "off" && this.#autoFlexGapMs) {
254
- this.#nextAutoFlexAt = performance.now() + PREVIEW_INTRO_MS;
255
- }
272
+ if (!this.#isActiveOwner() || mode === "off" || !this.#autoFlexGapMs) return;
273
+ this.#previewFlexAt = performance.now() + PREVIEW_INTRO_MS;
256
274
  }
257
275
 
258
276
  commitPreviewMode(mode: PetMode): void {
@@ -261,18 +279,25 @@ export class GajaePetWidget {
261
279
 
262
280
  #applyMode(mode: PetMode, mountComposer: boolean): void {
263
281
  if (this.#disposed || mode === this.#mode) return;
264
-
265
282
  if (mode === "off") {
266
- this.#writeImageCleanup();
283
+ if (!this.#canMutateSharedUi()) return;
284
+ this.#itermGeneration++;
285
+ if (this.#itermLease) {
286
+ void this.#ui.invalidateRasterLease({ token: this.#itermLease, cause: "mode-off" });
287
+ this.#itermLease = undefined;
288
+ }
289
+ this.#itermLastSemantic = "";
290
+ this.#itermProtocol = false;
291
+ this.#queueImageCleanup(true);
267
292
  this.#mode = "off";
268
293
  this.#animation?.unregister();
269
294
  this.#animation = undefined;
270
295
  this.#releaseOverlayEmitter();
271
296
  this.#floorContainer.clear();
272
297
  this.#pixel = undefined;
298
+ this.#activeBurst = undefined;
273
299
  this.#workTransition = undefined;
274
300
  this.#nextWorkBurstAt = 0;
275
- this.#activeBurst = undefined;
276
301
  this.#framedEditor.setReserve(0);
277
302
  if (mountComposer) this.#mountEditor(false);
278
303
  this.#ui.requestRender(true);
@@ -280,94 +305,136 @@ export class GajaePetWidget {
280
305
  }
281
306
 
282
307
  const protocol = this.#forcedProtocol ?? GajaePetWidget.pixelProtocol();
283
- if (!protocol) return;
284
- if (this.#mode !== "off") this.#writeImageCleanup();
308
+ const ownershipEpoch = petOverlayOwnershipEpochs.get(this.#ui) ?? 0;
309
+ if (!protocol || (this.#ownedOverlayEpoch !== 0 && this.#ownedOverlayEpoch < ownershipEpoch)) return;
310
+ const predecessor = petOverlayEmitterOwners.get(this.#ui);
311
+ if (predecessor && predecessor !== this) predecessor.#retireForSuccessor();
312
+ this.#itermGeneration++;
313
+ if (this.#itermLease) {
314
+ void this.#ui.invalidateRasterLease({ token: this.#itermLease, cause: "explicit" });
315
+ this.#itermLease = undefined;
316
+ }
317
+ this.#itermLastSemantic = "";
318
+ if (this.#mode !== "off") {
319
+ const releasesKittyImage = this.#pixel?.protocol === "kitty" && protocol !== "kitty";
320
+ this.#queueImageCleanup(releasesKittyImage);
321
+ }
285
322
  this.#mode = mode;
286
323
  this.#frame = PET_SKINS[mode].baseFrame;
287
324
  this.#flexUntil = 0;
288
325
  this.#activeBurst = undefined;
289
- this.#nextAutoFlexAt = 0;
326
+ this.#flexSource = undefined;
290
327
  this.#working = this.#isWorking();
291
328
  this.#workTransition = undefined;
292
329
  this.#nextWorkBurstAt = 0;
293
330
  this.#workBurstIndex = 0;
331
+ this.#previewFlexAt = 0;
332
+ this.#nextAutoFlexAt = 0;
294
333
  this.#buildPixel(protocol);
295
334
  if (mountComposer) this.#mountEditor(true);
296
- // The pet overlays the composer's bottom rows; no floor row is reserved, so
297
- // the composer stays pinned to the terminal bottom.
298
335
  this.#floorContainer.clear();
299
- this.#ui.setPostRenderEmitter(() => this.#overlayPayload());
336
+ this.#ui.setPostRenderEmitter(() => this.#overlayEmission());
337
+ this.#ownedOverlayEpoch = ownershipEpoch + 1;
338
+ petOverlayOwnershipEpochs.set(this.#ui, this.#ownedOverlayEpoch);
300
339
  petOverlayEmitterOwners.set(this.#ui, this);
301
340
  this.#animation ??= registerAnimationCallback(now => this.#tick(now), 80);
302
341
  this.#ui.requestRender(true);
303
342
  }
304
343
 
305
- /** (Re)build the encoded frames for the current terminal cell metrics. */
306
- #buildPixel(protocol: "sixel" | "kitty" | "iterm2"): void {
344
+ #isActiveOwner(): boolean {
345
+ return !this.#disposed && petOverlayEmitterOwners.get(this.#ui) === this;
346
+ }
347
+
348
+ #canMutateSharedUi(): boolean {
349
+ const owner = petOverlayEmitterOwners.get(this.#ui);
350
+ return (
351
+ owner === this ||
352
+ (owner === undefined &&
353
+ this.#ownedOverlayEpoch !== 0 &&
354
+ this.#ownedOverlayEpoch === (petOverlayOwnershipEpochs.get(this.#ui) ?? 0))
355
+ );
356
+ }
357
+
358
+ #retireForSuccessor(): void {
359
+ this.dispose();
360
+ }
361
+
362
+ #buildPixel(protocol: "sixel" | "kitty" | "iterm"): void {
307
363
  const cell = getCellDimensions();
308
364
  this.#builtCellW = cell.widthPx;
309
365
  this.#builtCellH = cell.heightPx;
310
366
  const skin: PetSkinId = this.#mode === "off" ? "red" : this.#mode;
311
367
  if (protocol === "kitty") {
312
368
  this.#kittyImageId ??= allocatePetKittyImageId();
369
+ this.#kittyCleanupGeneration++;
313
370
  this.#kittyCleanupPending = true;
314
371
  }
372
+ if (protocol === "iterm") {
373
+ this.#itermProtocol = true;
374
+ this.#pixel = undefined;
375
+ this.#framedEditor.setReserve(Math.max(1, Math.ceil((2 * cell.heightPx) / cell.widthPx)) + PET_SIDE_MARGIN);
376
+ return;
377
+ }
378
+ this.#itermProtocol = false;
315
379
  this.#pixel = buildGajaePixelFrames({
316
380
  protocol,
317
381
  skin,
318
382
  cellWidthPx: cell.widthPx,
319
383
  cellHeightPx: cell.heightPx,
320
- // Encode iTerm2 at the same two-row pixel size as Kitty/Sixel. The
321
- // iTerm2 protocol receives explicit pixel dimensions below, so it must
322
- // not be reduced to a fractional cell canvas or it becomes undersized.
323
384
  targetRows: 2,
324
385
  sixelTopPaddingPx: protocol === "sixel" ? PET_SIXEL_DROP_PX : 0,
325
386
  kittyCellYOffsetPx: protocol === "kitty" ? petKittyDropPx(cell.heightPx) : 0,
326
387
  kittyImageId: protocol === "kitty" ? this.#kittyImageId : undefined,
327
- // iTerm2 receives an explicit 1:1 pixel size; keep its raster inside the
328
- // same two-row footprint reserved for Kitty and Sixel.
329
- iterm2TopPaddingPx: 0,
330
- iterm2BottomPaddingPx: 0,
331
388
  });
332
389
  this.#framedEditor.setReserve(this.#pixel.columns + PET_SIDE_MARGIN);
333
390
  }
334
391
 
335
392
  dispose(): void {
336
393
  if (this.#disposed) return;
394
+ const canMutateSharedUi = this.#canMutateSharedUi();
395
+ this.#disposeNeedsLifecycle = canMutateSharedUi;
337
396
  this.#disposed = true;
338
- const kittyImageId = this.#kittyImageId;
339
- const cleanupPayload = this.#imageCleanupPayload();
340
- try {
341
- if (cleanupPayload) {
342
- this.#ui.queueTerminalCleanup(
343
- `\x1b[?2026h\x1b7${cleanupPayload}\x1b8\x1b[?2026l`,
344
- kittyImageId === undefined ? undefined : () => allocatedPetKittyImageIds.delete(kittyImageId),
345
- );
346
- } else if (kittyImageId !== undefined) {
347
- allocatedPetKittyImageIds.delete(kittyImageId);
348
- }
349
- this.#consumeCleanupAuthority();
350
- this.#kittyImageId = undefined;
351
- } finally {
352
- this.#animation?.unregister();
353
- this.#animation = undefined;
354
- this.#releaseOverlayEmitter();
355
- this.#mode = "off";
356
- this.#pixel = undefined;
357
- this.#workTransition = undefined;
358
- this.#nextWorkBurstAt = 0;
359
- this.#activeBurst = undefined;
397
+ this.#itermGeneration++;
398
+ const lease = this.#itermLease;
399
+ this.#itermLease = undefined;
400
+ if (lease)
401
+ this.#disposeRasterBarrier = this.#ui
402
+ .invalidateRasterLease({ token: lease, cause: "dispose" })
403
+ .then(() => undefined);
404
+ const cleanupBarrier = canMutateSharedUi ? this.#queueImageCleanup(true) : this.#queueImageCleanup(true, false);
405
+ this.#disposeRasterBarrier = Promise.all([this.#disposeRasterBarrier, cleanupBarrier]).then(() => undefined);
406
+ this.#animation?.unregister();
407
+ this.#animation = undefined;
408
+ this.#releaseOverlayEmitter();
409
+ this.#mode = "off";
410
+ this.#pixel = undefined;
411
+ this.#activeBurst = undefined;
412
+ this.#workTransition = undefined;
413
+ this.#nextWorkBurstAt = 0;
414
+ if (canMutateSharedUi) {
360
415
  this.#floorContainer.clear();
361
416
  this.#framedEditor.setReserve(0);
362
- // Restore the plain composer only while our framed wrapper is still
363
- // mounted; a successor widget may already own the editor container.
364
- if (this.#editorContainer.children.includes(this.#framedEditor)) {
365
- this.#mountEditor(false);
366
- }
417
+ if (this.#editorContainer.children.includes(this.#framedEditor)) this.#mountEditor(false);
418
+ }
419
+ }
420
+
421
+ async disposeAsync(): Promise<void> {
422
+ if (!this.#disposePromise) {
423
+ this.dispose();
424
+ if (!this.#disposeNeedsLifecycle) return;
425
+ this.#disposePromise = this.#disposeRasterBarrier
426
+ .then(() =>
427
+ this.#ui.notifyTerminalLifecycle({
428
+ kind: "explicit-cleanup",
429
+ source: "interactive-mode",
430
+ terminalGeneration: this.#ui.terminalGeneration,
431
+ }),
432
+ )
433
+ .then(() => undefined);
367
434
  }
435
+ await this.#disposePromise;
368
436
  }
369
437
 
370
- /** Clear the shared post-render slot only while this widget still owns it. */
371
438
  #releaseOverlayEmitter(): void {
372
439
  if (petOverlayEmitterOwners.get(this.#ui) === this) {
373
440
  this.#ui.setPostRenderEmitter(undefined);
@@ -380,15 +447,18 @@ export class GajaePetWidget {
380
447
  this.#editorContainer.addChild(framed ? this.#framedEditor : this.#editor);
381
448
  }
382
449
 
383
- /** Re-mount the composer editor (framed when a skin is active) after an overlay. */
384
450
  remountComposer(): void {
385
- this.#mountEditor(this.#mode !== "off");
451
+ // The composer container is shared with overlays (palette, selectors). A
452
+ // registered overlay owner or this widget may remount it; when no owner is
453
+ // registered the pet was never mounted, and remounting the plain editor is
454
+ // the same no-overlay restore the host would perform itself.
455
+ const owner = petOverlayEmitterOwners.get(this.#ui);
456
+ if (owner === undefined || owner === this) this.#mountEditor(this.#mode !== "off");
386
457
  }
387
458
 
388
- #pickFrame(now: number): PetFrameName {
389
- const mode = this.#mode;
390
- if (mode === "off") return "base";
391
- const skin = PET_SKINS[mode];
459
+ #syncWorkingState(now: number): PetSkinId | undefined {
460
+ if (this.#mode === "off") return undefined;
461
+ const skin = PET_SKINS[this.#mode];
392
462
  const working = this.#isWorking();
393
463
  if (working !== this.#working) {
394
464
  this.#working = working;
@@ -398,26 +468,258 @@ export class GajaePetWidget {
398
468
  const steps = working ? skin.workEnter : skin.workExit;
399
469
  this.#workTransition = steps?.length ? { startedAt: now, steps } : undefined;
400
470
  }
471
+ return this.#mode;
472
+ }
473
+
474
+ #pickFrame(now: number): PetFrameName {
475
+ const mode = this.#mode;
476
+ if (mode === "off") return "base";
477
+ const skin = PET_SKINS[mode];
478
+ this.#syncWorkingState(now);
401
479
  if (this.#workTransition) {
402
480
  const elapsed = now - this.#workTransition.startedAt;
403
481
  const duration = animationDuration(this.#workTransition.steps);
404
- if (elapsed < duration) {
405
- return animationFrameAtElapsed(this.#workTransition.steps, elapsed, skin.baseFrame);
406
- }
482
+ if (elapsed < duration) return animationFrameAtElapsed(this.#workTransition.steps, elapsed, skin.baseFrame);
407
483
  this.#workTransition = undefined;
408
484
  }
409
- // Random idle burst → the skin's own animation, driven by its burst descriptor
410
- // (RedGajae holds a flex; BlueGajae dances the para-para then sobs).
411
485
  if (now < this.#flexUntil) {
412
486
  const burst = this.#activeBurst ?? skin.burst;
413
487
  const elapsed = now - (this.#flexUntil - petBurstDurationMs(burst));
414
488
  return petBurstFrame(burst, elapsed, now);
415
489
  }
416
- // Working → loop the selected skin's activity animation.
417
- if (this.#isWorking()) {
418
- return animationFrameAt(skin.work, now, skin.baseFrame);
490
+ return this.#isWorking()
491
+ ? animationFrameAt(skin.work, now, skin.baseFrame)
492
+ : animationFrameAt(skin.idle, now, skin.baseFrame);
493
+ }
494
+
495
+ #itermTimeline(now: number, working: boolean, flexing: boolean): GajaeGifTimeline {
496
+ if (this.#mode === "off") return gifTimeline([]);
497
+ this.#syncWorkingState(now);
498
+ const skin = PET_SKINS[this.#mode];
499
+ if (flexing) return burstGifTimeline(this.#activeBurst ?? skin.burst);
500
+ if (this.#workTransition) {
501
+ const elapsed = now - this.#workTransition.startedAt;
502
+ if (elapsed < animationDuration(this.#workTransition.steps)) return gifTimeline(this.#workTransition.steps);
503
+ this.#workTransition = undefined;
504
+ }
505
+ return gifTimeline(working ? skin.work : skin.idle);
506
+ }
507
+
508
+ #tickIterm(now: number): void {
509
+ if (!this.#ui.isRunning || !this.#isActiveOwner() || this.#ui.manualViewportActive) return;
510
+ const cell = getCellDimensions();
511
+ const pixelColumns = Math.max(1, Math.ceil((PET_ART_ROWS * cell.heightPx) / cell.widthPx));
512
+ const pixelRows = ITERM_CANVAS_ROWS;
513
+ let metricsChanged = false;
514
+ if (cell.widthPx !== this.#builtCellW || cell.heightPx !== this.#builtCellH) {
515
+ metricsChanged = true;
516
+ this.#itermGeneration++;
517
+ const lease = this.#itermLease;
518
+ this.#itermLease = undefined;
519
+ if (lease) void this.#ui.invalidateRasterLease({ token: lease, cause: "resize" });
520
+ this.#itermLastSemantic = "";
521
+ this.#builtCellW = cell.widthPx;
522
+ this.#builtCellH = cell.heightPx;
523
+ this.#framedEditor.setReserve(pixelColumns + PET_SIDE_MARGIN);
524
+ this.#ui.requestRender(true);
525
+ }
526
+ if (!this.#framedEditor.canFit(this.#ui.terminal.columns)) {
527
+ if (!metricsChanged) {
528
+ this.#itermGeneration++;
529
+ const lease = this.#itermLease;
530
+ this.#itermLease = undefined;
531
+ if (lease) void this.#ui.invalidateRasterLease({ token: lease, cause: "resize" });
532
+ }
533
+ this.#itermLastSemantic = "";
534
+ return;
535
+ }
536
+ const terminalRows = this.#ui.terminal.rows;
537
+ if (terminalRows < ITERM_CANVAS_ROWS + PET_RAISE_ROWS) {
538
+ if (!metricsChanged) {
539
+ this.#itermGeneration++;
540
+ const lease = this.#itermLease;
541
+ this.#itermLease = undefined;
542
+ if (lease) void this.#ui.invalidateRasterLease({ token: lease, cause: "resize" });
543
+ }
544
+ this.#itermLastSemantic = "";
545
+ return;
546
+ }
547
+ const composerBottom = terminalRows - this.#getComposerBottomOffset();
548
+ const desiredRow = composerBottom - pixelRows;
549
+ const maxSafeRow =
550
+ terminalRows === ITERM_CANVAS_ROWS + PET_RAISE_ROWS
551
+ ? terminalRows - pixelRows - PET_RAISE_ROWS
552
+ : terminalRows - pixelRows;
553
+ const rect: CellRect = {
554
+ column: Math.max(0, this.#ui.terminal.columns - pixelColumns - PET_SIDE_MARGIN),
555
+ row: Math.max(0, Math.min(desiredRow, maxSafeRow)),
556
+ width: pixelColumns,
557
+ height: pixelRows,
558
+ };
559
+ const availability = getVerifiedItermPetAvailability();
560
+ if (!availability?.available || getItermPetUnavailableReason() || !this.#ui.terminalAvailable) return;
561
+ this.#syncWorkingState(now);
562
+ const working = this.#isWorking();
563
+ const flexing = this.#flexUntil > now;
564
+ const frames = this.#itermTimeline(now, working, flexing);
565
+ const semantic = `${this.#mode}:${availability.mode}:${availability.epoch}:${working}:${flexing}:${timelineSignature(frames)}:${rect.column},${rect.row}:${cell.widthPx},${cell.heightPx}:${this.#ui.terminal.columns},${this.#ui.terminal.rows}`;
566
+ if (this.#itermSubmitPending || (semantic === this.#itermLastSemantic && this.#itermLease)) return;
567
+ this.#itermLastSemantic = semantic;
568
+ this.#itermSubmitPending = true;
569
+ const generation = this.#itermGeneration;
570
+ void this.#submitIterm(
571
+ rect,
572
+ generation,
573
+ availability.epoch,
574
+ availability.mode,
575
+ semantic,
576
+ working,
577
+ flexing,
578
+ frames,
579
+ {
580
+ columns: this.#ui.terminal.columns,
581
+ rows: terminalRows,
582
+ cellWidthPx: cell.widthPx,
583
+ cellHeightPx: cell.heightPx,
584
+ },
585
+ this.#getComposerBottomOffset(),
586
+ ).finally(() => {
587
+ this.#itermSubmitPending = false;
588
+ if (!this.#itermLease) this.#itermLastSemantic = "";
589
+ });
590
+ }
591
+
592
+ async #submitIterm(
593
+ rect: CellRect,
594
+ generation: number,
595
+ epoch: number,
596
+ mode: "direct" | "managed",
597
+ semantic: string,
598
+ working: boolean,
599
+ flexing: boolean,
600
+ frames: GajaeGifTimeline,
601
+ geometry: Readonly<{ columns: number; rows: number; cellWidthPx: number; cellHeightPx: number }>,
602
+ composerBottomOffset: number,
603
+ ): Promise<void> {
604
+ const current = () => {
605
+ const availability = getVerifiedItermPetAvailability();
606
+ const flexingNow = this.#flexUntil > performance.now();
607
+ const terminal = this.#ui.terminal;
608
+ const cell = getCellDimensions();
609
+ const liveComposerBottomOffset = this.#getComposerBottomOffset();
610
+ const liveComposerBottom = terminal.rows - liveComposerBottomOffset;
611
+ const liveMaxSafeRow =
612
+ terminal.rows === ITERM_CANVAS_ROWS + PET_RAISE_ROWS
613
+ ? terminal.rows - rect.height - PET_RAISE_ROWS
614
+ : terminal.rows - rect.height;
615
+ const expectedColumn = Math.max(0, terminal.columns - rect.width - PET_SIDE_MARGIN);
616
+ const expectedRow = Math.max(0, Math.min(liveComposerBottom - rect.height, liveMaxSafeRow));
617
+ return (
618
+ this.#ui.isRunning &&
619
+ this.#isActiveOwner() &&
620
+ generation === this.#itermGeneration &&
621
+ availability?.available === true &&
622
+ availability.epoch === epoch &&
623
+ availability.mode === mode &&
624
+ !this.#ui.manualViewportActive &&
625
+ this.#isWorking() === working &&
626
+ flexingNow === flexing &&
627
+ timelineSignature(this.#itermTimeline(performance.now(), this.#isWorking(), flexingNow)) ===
628
+ timelineSignature(frames) &&
629
+ this.#framedEditor.canFit(terminal.columns) &&
630
+ terminal.columns === geometry.columns &&
631
+ terminal.rows === geometry.rows &&
632
+ cell.widthPx === geometry.cellWidthPx &&
633
+ cell.heightPx === geometry.cellHeightPx &&
634
+ liveComposerBottomOffset === composerBottomOffset &&
635
+ rect.column === expectedColumn &&
636
+ rect.row === expectedRow &&
637
+ rect.column + rect.width <= terminal.columns &&
638
+ rect.row + rect.height <= terminal.rows
639
+ );
640
+ };
641
+ let token = this.#itermLease;
642
+ if (
643
+ token &&
644
+ (token.rect.column !== rect.column ||
645
+ token.rect.row !== rect.row ||
646
+ token.rect.width !== rect.width ||
647
+ token.rect.height !== rect.height)
648
+ ) {
649
+ await this.#ui.invalidateRasterLease({ token, cause: "resize" });
650
+ if (this.#itermLease === token) this.#itermLease = undefined;
651
+ token = undefined;
652
+ }
653
+ if (!current()) return;
654
+ if (!token) {
655
+ const acquired = await this.#ui.acquireRasterLease({
656
+ ownerId: this.#itermOwner,
657
+ rect,
658
+ erase: {
659
+ type: "raster-erase",
660
+ bytes: new TextEncoder().encode(
661
+ `\x1b[0m${Array.from({ length: rect.height }, (_, row) => `\x1b[${rect.row + row + 1};${rect.column + 1}H\x1b[${rect.width}X`).join("")}`,
662
+ ),
663
+ },
664
+ onInvalidated: notice => {
665
+ if (this.#itermLease === notice.token) {
666
+ this.#itermLease = undefined;
667
+ this.#itermLastSemantic = "";
668
+ }
669
+ },
670
+ });
671
+ if (!current() || acquired.status !== "acquired") {
672
+ if (acquired.status === "acquired")
673
+ await this.#ui.invalidateRasterLease({
674
+ token: acquired.token,
675
+ cause: this.#ui.manualViewportActive ? "manual-viewport" : "capability-loss",
676
+ });
677
+ return;
678
+ }
679
+ token = acquired.token;
680
+ this.#itermLease = token;
681
+ }
682
+ this.#itermLastSemantic = semantic;
683
+ const cell = getCellDimensions();
684
+ const gif = getGajaePetGifCached({
685
+ skin: this.#mode === "off" ? "red" : this.#mode,
686
+ timeline: frames,
687
+ targetRows: PET_ART_ROWS,
688
+ rectangle: { width: rect.width * cell.widthPx, height: rect.height * cell.heightPx },
689
+ contentInset: { topPx: Math.floor(cell.heightPx / 2), bottomPx: Math.ceil(cell.heightPx / 2) },
690
+ displaySize: { width: rect.width, height: rect.height },
691
+ });
692
+ const cursorPosition = `\x1b[${rect.row + 1};${rect.column + 1}H`;
693
+ const cursorRestore =
694
+ mode === "managed" ? `${wrapITerm2RecordForTmux("\x1b8\x1b[?2026l")}\x1b8` : "\x1b8\x1b[?2026l";
695
+ const encodedRecords = (mode === "managed" ? gif.tmuxDcs : gif.multipart).map(record =>
696
+ new TextEncoder().encode(record),
697
+ );
698
+ const submit = await this.#ui.submitTerminalOutput({
699
+ token,
700
+ operation: {
701
+ type: "raster-multipart-batch",
702
+ prefix: new TextEncoder().encode(
703
+ mode === "managed"
704
+ ? `${wrapITerm2RecordForTmux("\x1b[?2026h\x1b7\x1b[?25l")}\x1b7${cursorPosition}`
705
+ : `\x1b[?2026h\x1b7\x1b[?25l${cursorPosition}`,
706
+ ),
707
+ afterPrefix:
708
+ mode === "managed"
709
+ ? async () => (current() ? await this.#syncManagedItermCursor(rect.row, rect.column) : false)
710
+ : undefined,
711
+ replayPrefix: mode === "managed" ? new TextEncoder().encode(cursorPosition) : undefined,
712
+ records: encodedRecords,
713
+ suffix: new TextEncoder().encode(cursorRestore),
714
+ abortSuffix: mode === "managed" ? new TextEncoder().encode(cursorRestore) : undefined,
715
+ restoreCursorVisibility: true,
716
+ shouldWrite: current,
717
+ },
718
+ });
719
+ if (!current() || submit.status !== "written") {
720
+ await this.#ui.invalidateRasterLease({ token, cause: "capability-loss" });
721
+ if (this.#itermLease === token) this.#itermLease = undefined;
419
722
  }
420
- return animationFrameAt(skin.idle, now, skin.baseFrame);
421
723
  }
422
724
 
423
725
  #scheduleWorkBurst(now: number): void {
@@ -434,51 +736,92 @@ export class GajaePetWidget {
434
736
  }
435
737
 
436
738
  #tick(now: number): void {
437
- if (this.#mode === "off" || !this.#pixel) return;
438
- // A font/zoom change resizes the terminal cells; rebuild the frames so the
439
- // kitty image and its sub-cell drop match the new cell metrics.
440
- const cell = getCellDimensions();
441
- if (cell.widthPx !== this.#builtCellW || cell.heightPx !== this.#builtCellH) {
442
- const protocol = this.#forcedProtocol ?? GajaePetWidget.pixelProtocol();
443
- if (protocol) {
444
- this.#buildPixel(protocol);
445
- this.#mountEditor(true);
446
- this.#ui.requestRender(true);
739
+ if (!this.#isActiveOwner()) return;
740
+ const skin = this.#mode === "off" ? undefined : PET_SKINS[this.#mode];
741
+ this.#syncWorkingState(now);
742
+ const working = this.#isWorking();
743
+ if (!working) {
744
+ this.#nextAutoFlexAt = 0;
745
+ if (this.#flexSource === "working") {
746
+ this.#flexUntil = 0;
747
+ this.#flexSource = undefined;
447
748
  }
448
749
  }
449
- // Random show-off, both while idle and while working. Each skin's burst runs for
450
- // its own length (RedGajae a brief flex; BlueGajae a para-para cycle plus sob).
451
- const skin = PET_SKINS[this.#mode];
452
- const usesWorkBursts = this.#isWorking() && Boolean(skin.workBursts?.length);
453
- if (this.#autoFlexGapMs && !usesWorkBursts && now >= this.#flexUntil) {
454
- if (this.#nextAutoFlexAt === 0) {
455
- this.#scheduleAutoFlex(now);
456
- } else if (now >= this.#nextAutoFlexAt) {
457
- const burstMs = petBurstDurationMs(PET_SKINS[this.#mode].burst);
750
+ if (now >= this.#flexUntil) {
751
+ this.#flexUntil = 0;
752
+ this.#flexSource = undefined;
753
+ if (this.#previewFlexAt !== 0 && now >= this.#previewFlexAt && skin) {
458
754
  this.#activeBurst = skin.burst;
459
- this.#flexUntil = now + burstMs;
460
- this.#scheduleAutoFlex(now + burstMs);
755
+ this.#flexUntil = now + petBurstDurationMs(skin.burst);
756
+ this.#flexSource = "preview";
757
+ this.#previewFlexAt = 0;
758
+ } else if (this.#autoFlexGapMs && working && skin && !skin.workBursts?.length) {
759
+ if (this.#nextAutoFlexAt === 0) this.#scheduleAutoFlex(now);
760
+ else if (now >= this.#nextAutoFlexAt) {
761
+ this.#activeBurst = skin.burst;
762
+ const burstMs = petBurstDurationMs(skin.burst);
763
+ this.#flexUntil = now + burstMs;
764
+ this.#flexSource = "working";
765
+ this.#scheduleAutoFlex(now + burstMs);
766
+ }
461
767
  }
462
768
  }
463
- if (this.#isWorking() && !this.#workTransition && now >= this.#flexUntil && skin.workBursts?.length) {
464
- if (this.#nextWorkBurstAt === 0) {
465
- this.#scheduleWorkBurst(now);
466
- } else if (now >= this.#nextWorkBurstAt) {
769
+ if (working && skin?.workBursts?.length && !this.#workTransition && now >= this.#flexUntil) {
770
+ if (this.#nextWorkBurstAt === 0) this.#scheduleWorkBurst(now);
771
+ else if (now >= this.#nextWorkBurstAt) {
467
772
  const burst = skin.workBursts[this.#workBurstIndex % skin.workBursts.length];
468
773
  this.#workBurstIndex++;
469
774
  this.#activeBurst = burst;
470
775
  this.#flexUntil = now + petBurstDurationMs(burst);
776
+ this.#flexSource = "working";
471
777
  this.#scheduleWorkBurst(this.#flexUntil);
472
778
  }
473
779
  }
780
+ if (this.#itermProtocol) {
781
+ this.#tickIterm(now);
782
+ return;
783
+ }
784
+ if (this.#mode === "off" || !this.#pixel) return;
785
+ const cell = getCellDimensions();
786
+ if (cell.widthPx !== this.#builtCellW || cell.heightPx !== this.#builtCellH) {
787
+ const protocol = this.#forcedProtocol ?? GajaePetWidget.pixelProtocol();
788
+ if (protocol) {
789
+ this.#buildPixel(protocol);
790
+ this.#mountEditor(true);
791
+ this.#ui.requestRender(true);
792
+ }
793
+ }
474
794
  const frame = this.#pickFrame(now);
475
795
  if (frame === this.#frame) return;
476
796
  this.#frame = frame;
477
- // Write directly: a frame swap changes no component line, so the TUI
478
- // would skip the render write (and with it the post-render emitter).
479
- const payload = this.#overlayPayload(true) ?? "";
480
- if (payload && this.#ui.terminalAvailable) {
481
- this.#ui.terminal.write(`\x1b[?2026h\x1b7${payload}\x1b8\x1b[?2026l`);
797
+ const pixel = this.#pixel;
798
+ const mode = this.#mode;
799
+ const position = this.#petPosition();
800
+ const terminalColumns = this.#ui.terminal.columns;
801
+ const terminalRows = this.#ui.terminal.rows;
802
+ const queuedCell = getCellDimensions();
803
+ const emission = this.#overlayEmission(true);
804
+ if (emission && pixel && this.#ui.terminalAvailable) {
805
+ void this.#ui.queueTerminalOutput(`\x1b[?2026h\x1b7${emission.payload}\x1b8\x1b[?2026l`, {
806
+ shouldWrite: () => {
807
+ const currentPosition = this.#petPosition();
808
+ const currentCell = getCellDimensions();
809
+ return (
810
+ this.#isActiveOwner() &&
811
+ this.#mode === mode &&
812
+ this.#pixel === pixel &&
813
+ this.#frame === frame &&
814
+ this.#ui.terminal.columns === terminalColumns &&
815
+ this.#ui.terminal.rows === terminalRows &&
816
+ currentCell.widthPx === queuedCell.widthPx &&
817
+ currentCell.heightPx === queuedCell.heightPx &&
818
+ (position === null
819
+ ? currentPosition === null
820
+ : currentPosition?.x === position.x && currentPosition.y === position.y)
821
+ );
822
+ },
823
+ onWritten: emission.onWritten,
824
+ });
482
825
  }
483
826
  }
484
827
 
@@ -488,19 +831,8 @@ export class GajaePetWidget {
488
831
  const columns = this.#ui.terminal.columns;
489
832
  if (!this.#framedEditor.canFit(columns)) return null;
490
833
  const rows = this.#ui.terminal.rows;
491
- // The sprite is lifted one safety row above the scrolling edge, then dropped
492
- // back onto the composer's bottom border per protocol (sixel via transparent
493
- // top padding, kitty via a sub-cell Y offset baked into the frames).
494
834
  const composerBottom = rows - this.#getComposerBottomOffset();
495
- // iTerm2 anchors an inline image at the current cursor row. Keep the full
496
- // sprite inside the composer rows and leave the HUD boundary below it.
497
- // Keep the full-size iTerm2 sprite aligned with the composer like the
498
- // cursor-neutral Kitty/Sixel overlays; only the protocol encoding differs.
499
- // Nudge the iTerm2 image one terminal row upward while preserving its
500
- // explicit 36px size and aspect ratio. Kitty/Sixel keep their established
501
- // safety lift.
502
- const safetyLift = PET_RAISE_ROWS;
503
- const y = composerBottom - pixel.rows - safetyLift;
835
+ const y = composerBottom - pixel.rows - PET_RAISE_ROWS;
504
836
  const x = columns - pixel.columns - PET_SIDE_MARGIN;
505
837
  if (y < 0 || x < 0) return null;
506
838
  return { x, y };
@@ -508,21 +840,16 @@ export class GajaePetWidget {
508
840
 
509
841
  #clearSixelFootprint(footprint: SixelFootprint): string {
510
842
  let out = "\x1b[0m";
511
- for (let row = 0; row < footprint.rows; row++) {
843
+ for (let row = 0; row < footprint.rows; row++)
512
844
  out += `\x1b[${footprint.y + row + 1};${footprint.x + 1}H\x1b[${footprint.columns}X`;
513
- }
514
845
  return out;
515
846
  }
516
847
 
517
- /** Pending on-screen image cleanup. Pure: authority is consumed separately, on delivery. */
518
848
  #imageCleanupPayload(): string {
519
849
  let out = "";
520
- if (this.#kittyCleanupPending && this.#kittyImageId !== undefined) {
850
+ if (this.#kittyCleanupPending && this.#kittyImageId !== undefined)
521
851
  out += `\x1b_Ga=d,d=I,i=${this.#kittyImageId},q=2\x1b\\`;
522
- }
523
- if (this.#lastSixelFootprint) {
524
- out += this.#clearSixelFootprint(this.#lastSixelFootprint);
525
- }
852
+ if (this.#lastSixelFootprint) out += this.#clearSixelFootprint(this.#lastSixelFootprint);
526
853
  return out;
527
854
  }
528
855
 
@@ -531,67 +858,66 @@ export class GajaePetWidget {
531
858
  this.#lastSixelFootprint = undefined;
532
859
  }
533
860
 
534
- /**
535
- * Best-effort direct erase of the on-screen pet image. Cleanup authority is
536
- * consumed only after the write is actually delivered: an unavailable
537
- * terminal or a throwing write keeps the erase pending so a later mode
538
- * switch or dispose can retry it.
539
- */
540
- #writeImageCleanup(): void {
541
- if (!this.#ui.terminalAvailable) return;
542
- const payload = this.#imageCleanupPayload();
543
- if (!payload) return;
544
- try {
545
- this.#ui.terminal.write(`\x1b[?2026h\x1b7${payload}\x1b8\x1b[?2026l`);
546
- } catch {
547
- // Keep the footprint/placement authority; the terminal write layer
548
- // reports availability separately and callers retry on the next
549
- // lifecycle transition.
550
- return;
861
+ #queueImageCleanup(releaseKittyImage = false, includeSixel = true): Promise<void> {
862
+ const sixelFootprint = includeSixel ? this.#lastSixelFootprint : undefined;
863
+ const kittyImageId = this.#kittyCleanupPending ? this.#kittyImageId : undefined;
864
+ const deliveredKittyImageId = releaseKittyImage && kittyImageId === undefined ? this.#kittyImageId : undefined;
865
+ if (deliveredKittyImageId !== undefined) {
866
+ this.#kittyImageId = undefined;
867
+ allocatedPetKittyImageIds.delete(deliveredKittyImageId);
551
868
  }
552
- this.#consumeCleanupAuthority();
869
+ let payload = "";
870
+ if (kittyImageId !== undefined) payload += `\x1b_Ga=d,d=I,i=${kittyImageId},q=2\x1b\\`;
871
+ if (sixelFootprint) payload += this.#clearSixelFootprint(sixelFootprint);
872
+ if (!payload) return Promise.resolve();
873
+ const kittyCleanupGeneration = kittyImageId === undefined ? undefined : ++this.#kittyCleanupGeneration;
874
+ if (releaseKittyImage && kittyImageId !== undefined && this.#kittyImageId === kittyImageId)
875
+ this.#kittyImageId = undefined;
876
+ return this.#ui.queueTerminalCleanup(`\x1b[?2026h\x1b7${payload}\x1b8\x1b[?2026l`, () => {
877
+ if (sixelFootprint && this.#lastSixelFootprint && sameFootprint(this.#lastSixelFootprint, sixelFootprint))
878
+ this.#lastSixelFootprint = undefined;
879
+ if (
880
+ kittyImageId !== undefined &&
881
+ kittyCleanupGeneration === this.#kittyCleanupGeneration &&
882
+ (this.#kittyImageId === kittyImageId || this.#kittyImageId === undefined)
883
+ )
884
+ this.#kittyCleanupPending = false;
885
+ if (kittyImageId !== undefined && releaseKittyImage) allocatedPetKittyImageIds.delete(kittyImageId);
886
+ });
553
887
  }
554
888
 
555
- /** Draw escape payload at the pet's absolute position. */
556
- #overlayPayload(clearPet = false): string | null {
889
+ #overlayEmission(clearPet = false): PetOverlayEmission | null {
890
+ if (!this.#isActiveOwner()) return null;
557
891
  const pixel = this.#pixel;
558
892
  if (!pixel) return null;
559
893
  const pos = this.#petPosition();
560
894
  if (!pos) {
561
- // Deferred delivery acknowledgement: the TUI writes the frame after
562
- // this emitter returns, and that write can fail. Consume the cleanup
563
- // authority only once a later pass observes the terminal survived
564
- // the frame that carried the payload; otherwise retain it so a later
565
- // lifecycle cleanup retries the erase/delete.
566
- if (this.#frameCleanupAwaitingAck && this.#ui.terminalAvailable) {
567
- this.#consumeCleanupAuthority();
568
- }
569
- this.#frameCleanupAwaitingAck = false;
570
- if (!this.#ui.terminalAvailable) return null;
571
895
  const cleanup = this.#imageCleanupPayload();
572
896
  if (!cleanup) return null;
573
- this.#frameCleanupAwaitingAck = true;
574
- return cleanup;
897
+ return {
898
+ payload: cleanup,
899
+ onWritten: () => {
900
+ if (this.#isActiveOwner()) this.#consumeCleanupAuthority();
901
+ },
902
+ };
575
903
  }
576
- // A full frame supersedes any cleanup-only frame still awaiting ack.
577
- this.#frameCleanupAwaitingAck = false;
578
904
  const { x, y } = pos;
579
905
  let out = "";
580
-
581
- if (pixel.protocol === "sixel" || pixel.protocol === "iterm2") {
906
+ let onWritten: (() => void) | undefined;
907
+ if (pixel.protocol === "sixel") {
582
908
  const footprint = { x, y, columns: pixel.columns, rows: pixel.rasterRows };
583
- if (this.#lastSixelFootprint && !sameFootprint(this.#lastSixelFootprint, footprint)) {
584
- out += this.#clearSixelFootprint(this.#lastSixelFootprint);
585
- }
909
+ const previous = this.#lastSixelFootprint;
910
+ if (previous && !sameFootprint(previous, footprint)) out += this.#clearSixelFootprint(previous);
586
911
  if (clearPet) out += this.#clearSixelFootprint(footprint);
587
- this.#lastSixelFootprint = footprint;
912
+ onWritten = () => {
913
+ if (this.#isActiveOwner()) this.#lastSixelFootprint = footprint;
914
+ };
588
915
  } else {
589
- // A kitty frame emitted below (re)places the image, so cleanup is
590
- // pending again even if a narrow-terminal pass consumed it earlier.
591
- this.#kittyCleanupPending = true;
916
+ onWritten = () => {
917
+ if (this.#isActiveOwner()) this.#kittyCleanupPending = true;
918
+ };
592
919
  }
593
-
594
920
  out += `\x1b[${y + 1};${x + 1}H${pixel.frames[this.#frame]}`;
595
- return out;
921
+ return { payload: out, onWritten };
596
922
  }
597
923
  }