@gajae-code/coding-agent 0.13.2 → 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 (610) hide show
  1. package/CHANGELOG.md +215 -8
  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 +10 -1
  35. package/dist/types/config/models-config-schema.d.ts +2 -0
  36. package/dist/types/config/settings-schema.d.ts +73 -9
  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 +2 -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/dynamic-theme-text.d.ts +9 -0
  87. package/dist/types/modes/components/gajae-pet-widget.d.ts +6 -28
  88. package/dist/types/modes/components/irc-sidebar.d.ts +1 -0
  89. package/dist/types/modes/components/iterm-pet-transport.d.ts +122 -0
  90. package/dist/types/modes/components/oauth-selector.d.ts +10 -4
  91. package/dist/types/modes/components/pet-capability.d.ts +7 -21
  92. package/dist/types/modes/components/settings-selector.d.ts +5 -0
  93. package/dist/types/modes/components/status-line/gh.d.ts +6 -2
  94. package/dist/types/modes/controllers/extension-ui-controller.d.ts +1 -1
  95. package/dist/types/modes/controllers/selector-controller.d.ts +7 -8
  96. package/dist/types/modes/controllers/stt-controller.d.ts +1 -1
  97. package/dist/types/modes/interactive-mode.d.ts +1 -0
  98. package/dist/types/modes/theme/defaults/index.d.ts +118 -0
  99. package/dist/types/modes/types.d.ts +1 -0
  100. package/dist/types/modes/utils/injected-user-submission.d.ts +10 -0
  101. package/dist/types/runtime-mcp/config.d.ts +9 -0
  102. package/dist/types/runtime-mcp/manager.d.ts +26 -1
  103. package/dist/types/runtime-mcp/oauth-discovery.d.ts +4 -0
  104. package/dist/types/runtime-mcp/oauth-flow.d.ts +24 -1
  105. package/dist/types/runtime-mcp/pool-key.d.ts +2 -0
  106. package/dist/types/runtime-mcp/protocol.d.ts +278 -0
  107. package/dist/types/runtime-mcp/scope-config.d.ts +29 -0
  108. package/dist/types/runtime-mcp/tool-bridge.d.ts +15 -13
  109. package/dist/types/runtime-mcp/transports/http.d.ts +9 -0
  110. package/dist/types/runtime-mcp/types.d.ts +102 -10
  111. package/dist/types/sdk/acp/adapter.d.ts +26 -5
  112. package/dist/types/sdk/broker/broker.d.ts +3 -55
  113. package/dist/types/sdk/broker/lifecycle.d.ts +18 -12
  114. package/dist/types/sdk/broker/session-index.d.ts +14 -5
  115. package/dist/types/sdk/broker/startup-failure.d.ts +16 -1
  116. package/dist/types/sdk/bus/chat-daemon-control.d.ts +51 -1
  117. package/dist/types/sdk/bus/chat-daemon-runtime.d.ts +4 -41
  118. package/dist/types/sdk/bus/config.d.ts +11 -0
  119. package/dist/types/sdk/bus/conversation-store.d.ts +0 -1
  120. package/dist/types/sdk/bus/daemon-paths.d.ts +6 -0
  121. package/dist/types/sdk/bus/discord-conversation.d.ts +2 -0
  122. package/dist/types/sdk/bus/discord-daemon.d.ts +17 -12
  123. package/dist/types/sdk/bus/existing-thread-readiness.d.ts +4 -34
  124. package/dist/types/sdk/bus/inbound-reaction-ordering.d.ts +61 -0
  125. package/dist/types/sdk/bus/index.d.ts +85 -99
  126. package/dist/types/sdk/bus/kind-aware-reconciliation.d.ts +29 -7
  127. package/dist/types/sdk/bus/managed-daemon.d.ts +2 -32
  128. package/dist/types/sdk/bus/notification-service.d.ts +52 -0
  129. package/dist/types/sdk/bus/operator-runtime.d.ts +1 -1
  130. package/dist/types/sdk/bus/prompt-reconciliation.d.ts +16 -5
  131. package/dist/types/sdk/bus/reconciliation-store.d.ts +113 -5
  132. package/dist/types/sdk/bus/session-control.d.ts +25 -1
  133. package/dist/types/sdk/bus/slack-conversation.d.ts +4 -0
  134. package/dist/types/sdk/bus/slack-daemon.d.ts +9 -17
  135. package/dist/types/sdk/bus/slack-live-provider.d.ts +3 -1
  136. package/dist/types/sdk/bus/slack-provider.d.ts +4 -0
  137. package/dist/types/sdk/bus/slack-thread-binding.d.ts +6 -40
  138. package/dist/types/sdk/bus/telegram-adoption-intent.d.ts +30 -104
  139. package/dist/types/sdk/bus/telegram-daemon-cli.d.ts +10 -0
  140. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +262 -19
  141. package/dist/types/sdk/bus/telegram-daemon-orphan-reap.d.ts +125 -0
  142. package/dist/types/sdk/bus/telegram-daemon-owner-registry.d.ts +22 -0
  143. package/dist/types/sdk/bus/telegram-daemon.d.ts +119 -310
  144. package/dist/types/sdk/bus/telegram-reference.d.ts +1 -47
  145. package/dist/types/sdk/bus/threaded-render.d.ts +1 -0
  146. package/dist/types/sdk/bus/topic-registry.d.ts +33 -17
  147. package/dist/types/sdk/cli/rows.d.ts +2 -11
  148. package/dist/types/sdk/cli/session-cli.d.ts +15 -25
  149. package/dist/types/sdk/client/client.d.ts +97 -1
  150. package/dist/types/sdk/host/control/dispatch.d.ts +0 -7
  151. package/dist/types/sdk/host/control/index.d.ts +1 -1
  152. package/dist/types/sdk/host/control/operations.d.ts +18 -5
  153. package/dist/types/sdk/host/control/runtime-gate.d.ts +19 -0
  154. package/dist/types/sdk/host/host.d.ts +15 -1
  155. package/dist/types/sdk/host/query/handlers.d.ts +5 -0
  156. package/dist/types/sdk/host/reverse-leases.d.ts +1 -1
  157. package/dist/types/sdk/host/session-runtime.d.ts +127 -7
  158. package/dist/types/sdk/host/types.d.ts +1 -1
  159. package/dist/types/sdk/host/websocket-transport.d.ts +1 -1
  160. package/dist/types/sdk/index.d.ts +2 -1
  161. package/dist/types/sdk/lifecycle/broker-client.d.ts +8 -0
  162. package/dist/types/sdk/lifecycle/client.d.ts +59 -0
  163. package/dist/types/sdk/lifecycle/index.d.ts +3 -0
  164. package/dist/types/sdk/lifecycle/service.d.ts +232 -0
  165. package/dist/types/sdk/mcp/server.d.ts +9 -6
  166. package/dist/types/sdk/prompt-deadline-lease.d.ts +43 -0
  167. package/dist/types/sdk/prompt-deadline-manager.d.ts +28 -0
  168. package/dist/types/sdk/prompt-status.d.ts +10 -3
  169. package/dist/types/sdk/protocol/operation-registry.d.ts +5 -0
  170. package/dist/types/sdk/receipt-state.d.ts +17 -0
  171. package/dist/types/sdk/reconciliation-extensions.d.ts +2 -0
  172. package/dist/types/sdk/router/index.d.ts +1 -0
  173. package/dist/types/sdk/router/session-router.d.ts +152 -0
  174. package/dist/types/sdk/session-list.d.ts +22 -0
  175. package/dist/types/sdk/session-reconnect.d.ts +43 -1
  176. package/dist/types/sdk/session.d.ts +34 -1
  177. package/dist/types/sdk/transport/serve-cli.d.ts +1 -4
  178. package/dist/types/session/account-inventory.d.ts +86 -0
  179. package/dist/types/session/agent-session.d.ts +145 -6
  180. package/dist/types/session/agent-storage.d.ts +10 -0
  181. package/dist/types/session/coordinator-tool-label.d.ts +28 -0
  182. package/dist/types/session/internal/managed-session-storage.d.ts +23 -4
  183. package/dist/types/session/messages.d.ts +1 -1
  184. package/dist/types/session/secure-token-file.d.ts +3 -0
  185. package/dist/types/session/session-manager.d.ts +47 -1
  186. package/dist/types/session/startup-auth-config.d.ts +44 -0
  187. package/dist/types/session/terminal-abort.d.ts +378 -0
  188. package/dist/types/session/yield-queue.d.ts +7 -0
  189. package/dist/types/session-import/claude.d.ts +26 -0
  190. package/dist/types/session-import/codex.d.ts +88 -0
  191. package/dist/types/session-import/command.d.ts +35 -0
  192. package/dist/types/session-import/detect.d.ts +22 -0
  193. package/dist/types/session-import/index.d.ts +7 -0
  194. package/dist/types/session-import/provider-codex.d.ts +22 -0
  195. package/dist/types/session-import/provider-service.d.ts +38 -0
  196. package/dist/types/session-import/redact.d.ts +23 -0
  197. package/dist/types/session-import/service.d.ts +47 -0
  198. package/dist/types/session-import/types.d.ts +120 -0
  199. package/dist/types/setup/paseo/check.d.ts +16 -0
  200. package/dist/types/setup/paseo/install-saga.d.ts +115 -0
  201. package/dist/types/setup/paseo/json-publisher.d.ts +76 -0
  202. package/dist/types/setup/paseo/orchestration-preferences.d.ts +24 -0
  203. package/dist/types/setup/paseo/paseo-ownership.d.ts +87 -0
  204. package/dist/types/setup/paseo/paseo-setup.d.ts +29 -0
  205. package/dist/types/setup/paseo/provider-config.d.ts +35 -0
  206. package/dist/types/setup/paseo/remove.d.ts +14 -0
  207. package/dist/types/setup/paseo/result-types.d.ts +59 -0
  208. package/dist/types/setup/paseo/setup-deps.d.ts +73 -0
  209. package/dist/types/setup/paseo/skills-bridge.d.ts +44 -0
  210. package/dist/types/skill-state/active-state.d.ts +5 -1
  211. package/dist/types/slash-commands/acp-builtins.d.ts +5 -1
  212. package/dist/types/slash-commands/builtin-registry.d.ts +2 -0
  213. package/dist/types/slash-commands/helpers/credential-switch.d.ts +12 -0
  214. package/dist/types/slash-commands/helpers/usage-report.d.ts +5 -6
  215. package/dist/types/slash-commands/types.d.ts +7 -1
  216. package/dist/types/task/executor.d.ts +10 -0
  217. package/dist/types/task/index.d.ts +3 -1
  218. package/dist/types/tools/ask-answer-registry.d.ts +6 -3
  219. package/dist/types/tools/bash-sleep-advisory.d.ts +14 -0
  220. package/dist/types/tools/bash.d.ts +2 -6
  221. package/dist/types/tools/browser/attach.d.ts +11 -0
  222. package/dist/types/tools/browser/launch.d.ts +20 -0
  223. package/dist/types/tools/browser/profile-discovery.d.ts +6 -0
  224. package/dist/types/tools/browser/render.d.ts +1 -0
  225. package/dist/types/tools/browser.d.ts +3 -1
  226. package/dist/types/tools/image-gen.d.ts +10 -2
  227. package/dist/types/tools/index.d.ts +8 -0
  228. package/dist/types/tools/monitor.d.ts +1 -1
  229. package/dist/types/tools/skill-discovery.d.ts +8 -1
  230. package/dist/types/tools/subagent.d.ts +2 -0
  231. package/dist/types/tools/ultragoal-ask-guard.d.ts +2 -2
  232. package/dist/types/utils/herdr-pane.d.ts +76 -0
  233. package/package.json +19 -9
  234. package/scripts/build-binary.ts +2 -1
  235. package/scripts/build-sdk-package-smoke.ts +2 -13
  236. package/scripts/capture-sticky-viewport-showcase.ts +0 -1
  237. package/scripts/compile-args.ts +2 -0
  238. package/scripts/generate-sdk-adapter-parity-manifest.ts +31 -16
  239. package/scripts/generate-sdk-operation-inventory.ts +28 -0
  240. package/scripts/generate-telegram-baseline-manifest.ts +1 -0
  241. package/scripts/generate-tool-catalog.ts +6 -4
  242. package/scripts/verify-gjc-sdk-canonicalization.ts +5 -39
  243. package/src/async/job-manager.ts +352 -17
  244. package/src/async/support.ts +25 -0
  245. package/src/autoresearch/dashboard.ts +28 -13
  246. package/src/capability/index.ts +3 -1
  247. package/src/capability/mcp.ts +6 -0
  248. package/src/capability/types.ts +6 -0
  249. package/src/cli/accounts-cli.ts +395 -0
  250. package/src/cli/args.ts +23 -0
  251. package/src/cli/auth-broker-cli.ts +136 -66
  252. package/src/cli/auth-gateway-cli.ts +94 -70
  253. package/src/cli/config-cli.ts +27 -2
  254. package/src/cli/crash-cli.ts +78 -0
  255. package/src/cli/customize-doctor.ts +1605 -0
  256. package/src/cli/daemon-cli.ts +12 -3
  257. package/src/cli/fast-help.ts +1 -0
  258. package/src/cli/mcp-cli.ts +128 -60
  259. package/src/cli/notify-cli.ts +8 -14
  260. package/src/cli/root-flags.ts +8 -0
  261. package/src/cli/setup-cli.ts +145 -0
  262. package/src/cli/skills-cli.ts +38 -2
  263. package/src/cli/update-cli.ts +167 -12
  264. package/src/cli.ts +4 -0
  265. package/src/commands/accounts.ts +71 -0
  266. package/src/commands/crash.ts +35 -0
  267. package/src/commands/customize.ts +84 -0
  268. package/src/commands/daemon.ts +2 -2
  269. package/src/commands/deep-interview.ts +2 -0
  270. package/src/commands/launch.ts +1 -0
  271. package/src/commands/mcp.ts +12 -7
  272. package/src/commands/ralplan.ts +2 -0
  273. package/src/commands/sdk.ts +65 -32
  274. package/src/commands/setup.ts +5 -0
  275. package/src/commands/skills.ts +14 -4
  276. package/src/commands/ultragoal.ts +10 -1
  277. package/src/commands/update.ts +26 -15
  278. package/src/config/atomic-yaml-patch-worker.ts +57 -0
  279. package/src/config/atomic-yaml-patch.ts +769 -29
  280. package/src/config/config-schema-version.ts +10 -0
  281. package/src/config/file-lock.ts +70 -1
  282. package/src/config/mcp-schema.json +52 -14
  283. package/src/config/model-profile-activation.ts +23 -2
  284. package/src/config/model-profiles.ts +88 -21
  285. package/src/config/model-registry.ts +147 -21
  286. package/src/config/models-config-schema.ts +1 -1
  287. package/src/config/settings-schema.ts +77 -7
  288. package/src/config/settings.ts +4038 -199
  289. package/src/config/skill-settings-defaults.ts +41 -3
  290. package/src/config/task-delegation.ts +87 -0
  291. package/src/config/update-channel.ts +23 -0
  292. package/src/coordinator-mcp/server.ts +377 -308
  293. package/src/crash/index-store.ts +723 -0
  294. package/src/crash/nudge.ts +89 -0
  295. package/src/crash/record-loader.ts +149 -0
  296. package/src/crash/report.ts +551 -0
  297. package/src/crash/sanitize.ts +129 -0
  298. package/src/cursor.ts +36 -10
  299. package/src/customization/import.ts +946 -0
  300. package/src/customization/index.ts +4 -0
  301. package/src/customization/inventory.ts +335 -0
  302. package/src/customization/mutations.ts +141 -0
  303. package/src/customization/types.ts +223 -0
  304. package/src/daemon/control-types.ts +8 -1
  305. package/src/daemon/operator-contract.ts +6 -6
  306. package/src/defaults/gjc/extensions/grok-cli-vendor/src/models/catalog.ts +31 -6
  307. package/src/defaults/gjc/skills/deep-interview/SKILL.md +11 -22
  308. package/src/defaults/gjc/skills/ralplan/SKILL.md +36 -23
  309. package/src/defaults/gjc/skills/ultragoal/SKILL.md +16 -0
  310. package/src/discovery/claude-hooks.ts +38 -0
  311. package/src/discovery/claude.ts +142 -209
  312. package/src/discovery/codex-hooks.ts +38 -0
  313. package/src/discovery/codex.ts +132 -198
  314. package/src/discovery/helpers.ts +8 -1
  315. package/src/discovery/index.ts +2 -0
  316. package/src/discovery/mcp-compat.ts +234 -0
  317. package/src/discovery/mcp-json.ts +6 -0
  318. package/src/edit/renderer.ts +40 -0
  319. package/src/edit/streaming.ts +4 -2
  320. package/src/exec/bash-executor.ts +24 -20
  321. package/src/export/html/index.ts +51 -2
  322. package/src/extensibility/extensions/loader.ts +9 -1
  323. package/src/extensibility/extensions/runner.ts +17 -4
  324. package/src/extensibility/extensions/types.ts +31 -6
  325. package/src/extensibility/gjc-plugins/constrained-hooks.ts +33 -0
  326. package/src/extensibility/gjc-plugins/installer.ts +9 -5
  327. package/src/extensibility/gjc-plugins/observability.ts +8 -2
  328. package/src/extensibility/gjc-plugins/registry.ts +13 -2
  329. package/src/extensibility/gjc-plugins/runtime-adapters.ts +3 -1
  330. package/src/extensibility/gjc-plugins/schema.ts +222 -39
  331. package/src/extensibility/hooks/loader.ts +154 -10
  332. package/src/extensibility/runtime-skill-discovery.ts +257 -60
  333. package/src/extensibility/skill-management.ts +368 -0
  334. package/src/extensibility/skills.ts +52 -16
  335. package/src/gjc-runtime/deep-interview-runtime.ts +45 -61
  336. package/src/gjc-runtime/launch-worktree.ts +56 -2
  337. package/src/gjc-runtime/linux-proc.ts +7 -1
  338. package/src/gjc-runtime/ralplan-runtime.ts +100 -164
  339. package/src/gjc-runtime/session-state-lock.ts +992 -0
  340. package/src/gjc-runtime/session-state-sidecar.ts +644 -142
  341. package/src/gjc-runtime/state-writer.ts +39 -14
  342. package/src/gjc-runtime/tmux-owner-isolation.ts +5 -4
  343. package/src/gjc-runtime/ultragoal-guard.ts +11 -4
  344. package/src/gjc-runtime/ultragoal-runtime.ts +44 -41
  345. package/src/gjc-runtime/workflow-settings.ts +596 -0
  346. package/src/goals/tools/goal-tool.ts +6 -1
  347. package/src/hooks/events.ts +291 -0
  348. package/src/hooks/native-skill-hook.ts +4 -0
  349. package/src/hooks/normalize.ts +369 -0
  350. package/src/hooks/skill-state.ts +165 -46
  351. package/src/internal-urls/docs-index.generated.ts +41 -32
  352. package/src/main.ts +36 -5
  353. package/src/migrate/mcp-mapper.ts +25 -2
  354. package/src/modes/acp/abort-scope.ts +29 -0
  355. package/src/modes/acp/acp-agent.ts +423 -98
  356. package/src/modes/components/assistant-message.ts +6 -1
  357. package/src/modes/components/chrome.ts +4 -3
  358. package/src/modes/components/customization/customization-dashboard.ts +443 -0
  359. package/src/modes/components/customization/import-wizard.ts +372 -0
  360. package/src/modes/components/customization/index.ts +1 -0
  361. package/src/modes/components/dynamic-theme-text.ts +22 -0
  362. package/src/modes/components/gajae-pet-widget.ts +607 -193
  363. package/src/modes/components/irc-sidebar.ts +11 -0
  364. package/src/modes/components/iterm-pet-transport.ts +585 -0
  365. package/src/modes/components/model-selector.ts +1 -6
  366. package/src/modes/components/oauth-selector.ts +277 -86
  367. package/src/modes/components/pet-capability.ts +35 -31
  368. package/src/modes/components/settings-selector.ts +29 -5
  369. package/src/modes/components/status-line/gh.ts +45 -1
  370. package/src/modes/components/status-line/segments.ts +19 -4
  371. package/src/modes/components/tool-status-header.ts +13 -3
  372. package/src/modes/controllers/command-controller.ts +20 -8
  373. package/src/modes/controllers/extension-ui-controller.ts +56 -257
  374. package/src/modes/controllers/input-controller.ts +10 -0
  375. package/src/modes/controllers/runtime-mcp-command-controller.ts +16 -3
  376. package/src/modes/controllers/selector-controller.ts +238 -77
  377. package/src/modes/controllers/stt-controller.ts +3 -3
  378. package/src/modes/interactive-mode.ts +88 -16
  379. package/src/modes/print-mode.ts +35 -6
  380. package/src/modes/runtime-init.ts +21 -68
  381. package/src/modes/theme/defaults/index.ts +2 -0
  382. package/src/modes/theme/defaults/ouroboros.json +118 -0
  383. package/src/modes/types.ts +1 -0
  384. package/src/modes/utils/injected-user-submission.ts +25 -0
  385. package/src/modes/utils/terminal-bell.ts +15 -1
  386. package/src/prompts/agent-fragments/ralplan-persistence.md +1 -1
  387. package/src/prompts/agent-fragments/restricted-bash.md +1 -1
  388. package/src/prompts/agents/critic.md +1 -1
  389. package/src/prompts/system/rlm-research.md +1 -1
  390. package/src/prompts/system/system-prompt.md +0 -2
  391. package/src/prompts/tools/bash.md +7 -1
  392. package/src/prompts/tools/browser.md +1 -1
  393. package/src/prompts/tools/cron.md +6 -6
  394. package/src/prompts/tools/read.md +0 -2
  395. package/src/prompts/tools/skill-discovery.md +4 -2
  396. package/src/runtime-mcp/client.ts +427 -23
  397. package/src/runtime-mcp/config.ts +37 -5
  398. package/src/runtime-mcp/manager-pool.test.ts +56 -0
  399. package/src/runtime-mcp/manager.ts +52 -2
  400. package/src/runtime-mcp/oauth-discovery.ts +13 -0
  401. package/src/runtime-mcp/oauth-flow.ts +46 -1
  402. package/src/runtime-mcp/pool-key.ts +5 -1
  403. package/src/runtime-mcp/pool.test.ts +9 -0
  404. package/src/runtime-mcp/protocol.test.ts +353 -0
  405. package/src/runtime-mcp/protocol.ts +624 -0
  406. package/src/runtime-mcp/scope-config.ts +78 -0
  407. package/src/runtime-mcp/tool-bridge.ts +44 -10
  408. package/src/runtime-mcp/transports/http.ts +84 -13
  409. package/src/runtime-mcp/types.ts +123 -12
  410. package/src/sdk/acp/adapter.ts +328 -97
  411. package/src/sdk/broker/broker.ts +160 -741
  412. package/src/sdk/broker/ensure.ts +9 -1
  413. package/src/sdk/broker/lifecycle.ts +510 -116
  414. package/src/sdk/broker/operation-receipt-ledger.ts +11 -2
  415. package/src/sdk/broker/process-incarnation.ts +19 -0
  416. package/src/sdk/broker/session-index.ts +516 -117
  417. package/src/sdk/broker/startup-failure.ts +28 -5
  418. package/src/sdk/broker/transport.ts +1 -13
  419. package/src/sdk/bus/chat-daemon-cli.ts +4 -2
  420. package/src/sdk/bus/chat-daemon-control.ts +67 -6
  421. package/src/sdk/bus/chat-daemon-runtime.ts +183 -859
  422. package/src/sdk/bus/config.ts +13 -0
  423. package/src/sdk/bus/conversation-store.ts +59 -171
  424. package/src/sdk/bus/daemon-paths.ts +20 -0
  425. package/src/sdk/bus/discord-conversation.ts +2 -0
  426. package/src/sdk/bus/discord-daemon.ts +524 -126
  427. package/src/sdk/bus/discord-live-provider.ts +206 -79
  428. package/src/sdk/bus/existing-thread-readiness.ts +4 -97
  429. package/src/sdk/bus/inbound-reaction-ordering.ts +122 -0
  430. package/src/sdk/bus/index.ts +2480 -456
  431. package/src/sdk/bus/kind-aware-reconciliation.ts +204 -34
  432. package/src/sdk/bus/lifecycle-commands.ts +2 -2
  433. package/src/sdk/bus/managed-daemon.ts +2 -145
  434. package/src/sdk/bus/notification-service.ts +196 -0
  435. package/src/sdk/bus/operator-runtime.ts +1 -1
  436. package/src/sdk/bus/prompt-reconciliation.ts +24 -15
  437. package/src/sdk/bus/reconciliation-store.ts +553 -24
  438. package/src/sdk/bus/session-control.ts +49 -3
  439. package/src/sdk/bus/slack-conversation.ts +4 -0
  440. package/src/sdk/bus/slack-daemon.ts +733 -134
  441. package/src/sdk/bus/slack-live-provider.ts +65 -15
  442. package/src/sdk/bus/slack-provider.ts +4 -0
  443. package/src/sdk/bus/slack-thread-binding.ts +9 -104
  444. package/src/sdk/bus/telegram-adoption-intent.ts +299 -209
  445. package/src/sdk/bus/telegram-daemon-cli.ts +194 -7
  446. package/src/sdk/bus/telegram-daemon-contract.ts +265 -19
  447. package/src/sdk/bus/telegram-daemon-control.ts +3 -13
  448. package/src/sdk/bus/telegram-daemon-orphan-reap.ts +498 -0
  449. package/src/sdk/bus/telegram-daemon-owner-registry.ts +176 -0
  450. package/src/sdk/bus/telegram-daemon.ts +2552 -2280
  451. package/src/sdk/bus/telegram-reference.ts +1 -226
  452. package/src/sdk/bus/telegram-setup.ts +2 -2
  453. package/src/sdk/bus/threaded-render.ts +1 -0
  454. package/src/sdk/bus/topic-registry.ts +279 -146
  455. package/src/sdk/cli/rows.ts +2 -35
  456. package/src/sdk/cli/session-cli.ts +776 -700
  457. package/src/sdk/client/client.ts +892 -1
  458. package/src/sdk/host/control/dispatch.ts +93 -58
  459. package/src/sdk/host/control/index.ts +1 -1
  460. package/src/sdk/host/control/operations.ts +18 -10
  461. package/src/sdk/host/control/runtime-gate.ts +40 -0
  462. package/src/sdk/host/host.ts +63 -12
  463. package/src/sdk/host/query/handlers.ts +66 -7
  464. package/src/sdk/host/reverse-leases.ts +2 -2
  465. package/src/sdk/host/sdk-surface-parity.test.ts +1 -1
  466. package/src/sdk/host/session-runtime.test.ts +2787 -34
  467. package/src/sdk/host/session-runtime.ts +1489 -101
  468. package/src/sdk/host/surface-policy.ts +12 -8
  469. package/src/sdk/host/types.ts +1 -1
  470. package/src/sdk/host/websocket-transport.lifecycle.test.ts +31 -59
  471. package/src/sdk/host/websocket-transport.ts +85 -50
  472. package/src/sdk/index.ts +2 -1
  473. package/src/sdk/lifecycle/broker-client.ts +53 -0
  474. package/src/sdk/lifecycle/client.ts +234 -0
  475. package/src/sdk/lifecycle/index.ts +3 -0
  476. package/src/sdk/{bus/recent-activity.ts → lifecycle/recent-sessions.ts} +5 -2
  477. package/src/sdk/lifecycle/service.ts +720 -0
  478. package/src/sdk/mcp/server.ts +226 -188
  479. package/src/sdk/prompt-deadline-lease.ts +55 -0
  480. package/src/sdk/prompt-deadline-manager.ts +148 -0
  481. package/src/sdk/prompt-status.ts +11 -3
  482. package/src/sdk/protocol/operation-inventory.generated.json +272 -129
  483. package/src/sdk/protocol/operation-registry.ts +71 -8
  484. package/src/sdk/receipt-state.ts +43 -0
  485. package/src/sdk/reconciliation-extensions.ts +14 -0
  486. package/src/sdk/router/index.ts +1 -0
  487. package/src/sdk/router/session-router.ts +1658 -0
  488. package/src/sdk/session-list.ts +93 -0
  489. package/src/sdk/session-reconnect.ts +45 -1
  490. package/src/sdk/session.ts +716 -120
  491. package/src/sdk/transport/serve-cli.ts +18 -34
  492. package/src/session/account-inventory.ts +603 -0
  493. package/src/session/agent-session.ts +2873 -559
  494. package/src/session/agent-storage.ts +35 -0
  495. package/src/session/blob-store.ts +32 -1
  496. package/src/session/coordinator-tool-label.ts +40 -0
  497. package/src/session/internal/managed-session-scope.ts +4 -0
  498. package/src/session/internal/managed-session-storage.ts +199 -31
  499. package/src/session/messages.ts +1 -1
  500. package/src/session/secure-token-file.ts +80 -0
  501. package/src/session/session-manager.ts +757 -57
  502. package/src/session/session-storage.ts +25 -16
  503. package/src/session/startup-auth-config.ts +297 -0
  504. package/src/session/terminal-abort.ts +1253 -0
  505. package/src/session/yield-queue.ts +57 -15
  506. package/src/session-import/claude.ts +334 -0
  507. package/src/session-import/codex.ts +1033 -0
  508. package/src/session-import/command.ts +77 -0
  509. package/src/session-import/detect.ts +133 -0
  510. package/src/session-import/index.ts +7 -0
  511. package/src/session-import/provider-codex.ts +327 -0
  512. package/src/session-import/provider-service.ts +457 -0
  513. package/src/session-import/redact.ts +128 -0
  514. package/src/session-import/service.ts +1264 -0
  515. package/src/session-import/types.ts +148 -0
  516. package/src/setup/paseo/check.ts +213 -0
  517. package/src/setup/paseo/install-saga.ts +293 -0
  518. package/src/setup/paseo/json-publisher.ts +263 -0
  519. package/src/setup/paseo/orchestration-preferences.ts +86 -0
  520. package/src/setup/paseo/paseo-ownership.ts +224 -0
  521. package/src/setup/paseo/paseo-setup.ts +253 -0
  522. package/src/setup/paseo/provider-config.ts +139 -0
  523. package/src/setup/paseo/remove.ts +183 -0
  524. package/src/setup/paseo/result-types.ts +79 -0
  525. package/src/setup/paseo/setup-deps.ts +175 -0
  526. package/src/setup/paseo/skills-bridge.ts +260 -0
  527. package/src/skill-state/active-state.ts +21 -11
  528. package/src/slash-commands/acp-builtins.ts +9 -1
  529. package/src/slash-commands/builtin-registry.ts +117 -19
  530. package/src/slash-commands/helpers/credential-switch.ts +143 -0
  531. package/src/slash-commands/helpers/mcp.ts +9 -3
  532. package/src/slash-commands/helpers/usage-report.ts +61 -56
  533. package/src/slash-commands/types.ts +7 -2
  534. package/src/task/executor.ts +30 -11
  535. package/src/task/index.ts +66 -7
  536. package/src/task/render.ts +23 -9
  537. package/src/tools/ask-answer-registry.ts +23 -7
  538. package/src/tools/ask.ts +8 -4
  539. package/src/tools/bash-sleep-advisory.ts +115 -0
  540. package/src/tools/bash.ts +98 -18
  541. package/src/tools/browser/attach.ts +71 -3
  542. package/src/tools/browser/launch.ts +86 -13
  543. package/src/tools/browser/profile-discovery.ts +57 -7
  544. package/src/tools/browser/render.ts +8 -1
  545. package/src/tools/browser.ts +130 -21
  546. package/src/tools/computer.enforcement.test.ts +1 -1
  547. package/src/tools/computer.ts +3 -2
  548. package/src/tools/cron.ts +12 -1
  549. package/src/tools/image-gen.ts +349 -142
  550. package/src/tools/index.ts +8 -0
  551. package/src/tools/job.ts +33 -6
  552. package/src/tools/monitor.ts +41 -2
  553. package/src/tools/skill-discovery.ts +21 -29
  554. package/src/tools/subagent.ts +142 -24
  555. package/src/tools/todo-contract.ts +55 -3
  556. package/src/tools/todo-write.ts +4 -3
  557. package/src/tools/tool-catalog.generated.ts +6 -6
  558. package/src/tools/ultragoal-ask-guard.ts +10 -3
  559. package/src/utils/herdr-pane.ts +345 -0
  560. package/src/utils/title-generator.ts +2 -0
  561. package/src/web/scrapers/index.ts +6 -4
  562. package/dist/types/cli/malloc-env-guard.test.d.ts +0 -1
  563. package/dist/types/defaults/gjc-defaults.test.d.ts +0 -1
  564. package/dist/types/memory-backend/lazy-loading.test.d.ts +0 -1
  565. package/dist/types/runtime/boot-ordering.test.d.ts +0 -1
  566. package/dist/types/runtime/lazy-service.test.d.ts +0 -1
  567. package/dist/types/runtime/network-prewarm-service.test.d.ts +0 -1
  568. package/dist/types/runtime/workspace-tree-service.test.d.ts +0 -1
  569. package/dist/types/runtime-mcp/config.test.d.ts +0 -1
  570. package/dist/types/runtime-mcp/http.test.d.ts +0 -1
  571. package/dist/types/runtime-mcp/manager-pool.test.d.ts +0 -1
  572. package/dist/types/runtime-mcp/pool-key.test.d.ts +0 -1
  573. package/dist/types/runtime-mcp/pool.test.d.ts +0 -1
  574. package/dist/types/sdk/bus/lifecycle-control-runtime.d.ts +0 -117
  575. package/dist/types/sdk/bus/lifecycle-orchestrator.d.ts +0 -141
  576. package/dist/types/sdk/bus/telegram-cli.d.ts +0 -31
  577. package/dist/types/sdk/elevation/allowlist.d.ts +0 -20
  578. package/dist/types/sdk/elevation/audit.d.ts +0 -93
  579. package/dist/types/sdk/elevation/capability.d.ts +0 -3
  580. package/dist/types/sdk/elevation/digest.d.ts +0 -72
  581. package/dist/types/sdk/elevation/dispatch-receipt.d.ts +0 -85
  582. package/dist/types/sdk/elevation/grant-ledger.d.ts +0 -163
  583. package/dist/types/sdk/elevation/owner.d.ts +0 -48
  584. package/dist/types/sdk/host/import-graph.test.d.ts +0 -1
  585. package/dist/types/sdk/host/sdk-surface-parity.test.d.ts +0 -1
  586. package/dist/types/sdk/host/session-runtime.test.d.ts +0 -1
  587. package/dist/types/sdk/host/websocket-transport.lifecycle.test.d.ts +0 -1
  588. package/dist/types/session/auth-broker-config.d.ts +0 -13
  589. package/dist/types/session/heap-eviction-artifacts.test.d.ts +0 -1
  590. package/dist/types/skills/skills.test.d.ts +0 -1
  591. package/dist/types/slash-commands/notify-command.test.d.ts +0 -1
  592. package/dist/types/task/token-log.test.d.ts +0 -1
  593. package/dist/types/tools/computer.enforcement.test.d.ts +0 -1
  594. package/dist/types/tools/descriptors.test.d.ts +0 -1
  595. package/scripts/g005-daemon-smoke.ts +0 -180
  596. package/scripts/g011-daemon-path-smoke.ts +0 -153
  597. package/src/sdk/bus/lifecycle-control-runtime.ts +0 -1733
  598. package/src/sdk/bus/lifecycle-orchestrator.ts +0 -489
  599. package/src/sdk/bus/telegram-cli.ts +0 -212
  600. package/src/sdk/elevation/allowlist.ts +0 -54
  601. package/src/sdk/elevation/audit.ts +0 -208
  602. package/src/sdk/elevation/capability.ts +0 -41
  603. package/src/sdk/elevation/digest.ts +0 -129
  604. package/src/sdk/elevation/dispatch-receipt.ts +0 -184
  605. package/src/sdk/elevation/grant-ledger.ts +0 -1142
  606. package/src/sdk/elevation/owner.ts +0 -109
  607. package/src/session/auth-broker-config.ts +0 -109
  608. /package/dist/types/{ai-core-import-gate.test.d.ts → discovery/claude-hooks.d.ts} +0 -0
  609. /package/dist/types/{cli/fixture-report.test.d.ts → discovery/codex-hooks.d.ts} +0 -0
  610. /package/dist/types/sdk/{bus/recent-activity.d.ts → lifecycle/recent-sessions.d.ts} +0 -0
@@ -1,22 +1,28 @@
1
1
  import {
2
2
  type AnimationRegistration,
3
3
  buildGajaePixelFrames,
4
+ type CellRect,
4
5
  type Component,
5
6
  type Container,
6
- type GajaePixelFrameName,
7
+ type GajaeGifFrame,
8
+ type GajaeGifTimeline,
7
9
  type GajaePixelFrames,
8
10
  getCellDimensions,
9
- PARA_PARA_STEPS,
11
+ getGajaePetGifCached,
10
12
  PET_SKINS,
13
+ type PetBurst,
14
+ type PetFrameName,
11
15
  type PetMode,
12
16
  type PetSkinId,
13
17
  petBurstDurationMs,
14
18
  petBurstFrame,
19
+ type RasterLeaseToken,
15
20
  registerAnimationCallback,
16
21
  type TUI,
22
+ wrapITerm2RecordForTmux,
17
23
  } from "@gajae-code/tui";
18
24
  import type { CustomEditor } from "./custom-editor";
19
- import { getPetPixelProtocol } from "./pet-capability";
25
+ import { getItermPetUnavailableReason, getPetPixelProtocol, getVerifiedItermPetAvailability } from "./pet-capability";
20
26
 
21
27
  /** Re-exported from the tui skin registry so widget-relative imports stay valid. */
22
28
  export type { PetMode, PetSkinId };
@@ -37,6 +43,8 @@ const KITTY_DROP_FRACTION = 0.45;
37
43
  const petKittyDropPx = (cellHeightPx: number): number =>
38
44
  Math.min(Math.max(0, cellHeightPx - 1), Math.floor(cellHeightPx * KITTY_DROP_FRACTION));
39
45
  const PET_RAISE_ROWS = 1;
46
+ const PET_ART_ROWS = 2;
47
+ const ITERM_CANVAS_ROWS = PET_ART_ROWS + 1;
40
48
  const allocatedPetKittyImageIds = new Set<number>();
41
49
 
42
50
  function allocatePetKittyImageId(): number {
@@ -59,41 +67,86 @@ function sameFootprint(left: SixelFootprint, right: SixelFootprint): boolean {
59
67
  return left.x === right.x && left.y === right.y && left.columns === right.columns && left.rows === right.rows;
60
68
  }
61
69
 
70
+ type PetOverlayEmission = {
71
+ payload: string;
72
+ onWritten?: () => void;
73
+ };
74
+
62
75
  /**
63
76
  * Which widget currently owns each TUI's single shared post-render emitter
64
77
  * slot. A stale or repeated dispose (or off-switch) of a predecessor widget
65
78
  * must never clear a successor's overlay authority.
66
79
  */
67
80
  const petOverlayEmitterOwners = new WeakMap<TUI, GajaePetWidget>();
81
+ const petOverlayOwnershipEpochs = new WeakMap<TUI, number>();
68
82
 
69
- /** Working animation: the shared para-para beats looped end to end. */
70
- const WORK_LOOP_TOTAL = PARA_PARA_STEPS.reduce((sum, [, ms]) => sum + ms, 0);
71
- /** Random gap between automatic claw flexes (fires while idle AND working). */
83
+ /** Random gap between automatic claw flexes while work is active. */
72
84
  const AUTO_FLEX_MIN_GAP_MS = 12_000;
73
85
  const AUTO_FLEX_MAX_GAP_MS = 40_000;
74
- // Deterministic idle loop: gaze around with a rare visor flicker.
75
- const IDLE_LOOP: Array<[GajaePixelFrameName, number]> = [
76
- ["base", 1100],
77
- ["gazeL", 350],
78
- ["base", 500],
79
- ["gazeR", 350],
80
- ["base", 800],
81
- ["flicker", 150],
82
- ];
83
- const IDLE_LOOP_TOTAL = IDLE_LOOP.reduce((sum, [, ms]) => sum + ms, 0);
86
+ function animationFrameAt(
87
+ steps: ReadonlyArray<readonly [PetFrameName, number]>,
88
+ now: number,
89
+ fallback: PetFrameName,
90
+ ): PetFrameName {
91
+ const total = steps.reduce((sum, [, ms]) => sum + ms, 0);
92
+ if (total <= 0) return fallback;
93
+ let elapsed = now % total;
94
+ for (const [frame, ms] of steps) {
95
+ if (elapsed < ms) return frame;
96
+ elapsed -= ms;
97
+ }
98
+ return fallback;
99
+ }
84
100
 
85
- /**
86
- * Selector preview: fire the first burst this soon after a skin is previewed, so the
87
- * pet shows one idle eye-roll (base -> gazeL -> base -> gazeR) and then its signature
88
- * flex/cry. Live use keeps the random AUTO_FLEX gap; only preview forces this demo.
89
- */
101
+ function animationFrameAtElapsed(
102
+ steps: ReadonlyArray<readonly [PetFrameName, number]>,
103
+ elapsed: number,
104
+ fallback: PetFrameName,
105
+ ): PetFrameName {
106
+ for (const [frame, ms] of steps) {
107
+ if (elapsed < ms) return frame;
108
+ elapsed -= ms;
109
+ }
110
+ return fallback;
111
+ }
112
+
113
+ function animationDuration(steps: ReadonlyArray<readonly [PetFrameName, number]>): number {
114
+ return steps.reduce((sum, [, ms]) => sum + ms, 0);
115
+ }
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
+
138
+ interface WorkTransition {
139
+ startedAt: number;
140
+ steps: ReadonlyArray<readonly [PetFrameName, number]>;
141
+ }
142
+
143
+ const OUROBOROS_WORK_BURST_MIN_GAP_MS = 14_000;
144
+ const OUROBOROS_WORK_BURST_MAX_GAP_MS = 30_000;
145
+
146
+ /** Selector preview: fire the first burst this soon after a skin is previewed. */
90
147
  const PREVIEW_INTRO_MS = 2300;
91
148
 
92
- /**
93
- * Wraps the composer editor, reserving a right-side area beside it where the
94
- * real-pixel pet is drawn. The editor just renders narrower; the pet pixels
95
- * are emitted separately as an absolute-positioned overlay.
96
- */
149
+ /** Reserve a right-side area beside the composer where the pixel pet is drawn. */
97
150
  export class PetFramedEditor implements Component {
98
151
  #editor: CustomEditor;
99
152
  #reserve = 0;
@@ -115,27 +168,12 @@ export class PetFramedEditor implements Component {
115
168
  }
116
169
 
117
170
  render(width: number): string[] {
118
- if (!this.canFit(width)) {
119
- return this.#editor.render(width);
120
- }
171
+ if (!this.canFit(width)) return this.#editor.render(width);
121
172
  return this.#editor.render(width - this.#reserve);
122
173
  }
123
174
  }
124
175
 
125
- /**
126
- * The gajae pet: a 16x16 real-pixel sprite living in a reserved area beside
127
- * the composer. It is nearest-neighbor scaled to the two terminal rows occupied
128
- * by an empty one-line composer and lifted one row so its feet meet the input
129
- * box's bottom edge.
130
- *
131
- * Rendering has two paths that share one payload builder:
132
- * - a post-render emitter re-draws the sprite after every TUI write (line
133
- * renders clear the pet cells, so the overlay must be re-applied), and
134
- * - frame advances write the payload directly to the terminal, because the
135
- * TUI skips writes entirely when no component line changed.
136
- *
137
- * Requires a sixel- or kitty-graphics terminal (`pixelProtocol()`).
138
- */
176
+ /** The gajae pet pixel sprite and protocol-specific rendering lifecycle. */
139
177
  export class GajaePetWidget {
140
178
  #ui: TUI;
141
179
  #editor: CustomEditor;
@@ -146,29 +184,37 @@ export class GajaePetWidget {
146
184
  #getComposerBottomOffset: () => number;
147
185
  #mode: PetMode = "off";
148
186
  #pixel: GajaePixelFrames | undefined;
149
- #frame: GajaePixelFrameName = "base";
187
+ #frame: PetFrameName = "base";
150
188
  #animation: AnimationRegistration | undefined;
151
189
  #flexUntil = 0;
190
+ #activeBurst: PetBurst | undefined;
152
191
  #nextAutoFlexAt = 0;
192
+ #working = false;
193
+ #workTransition: WorkTransition | undefined;
194
+ #nextWorkBurstAt = 0;
195
+ #workBurstIndex = 0;
196
+ #flexSource: "preview" | "working" | undefined;
197
+ #previewFlexAt = 0;
153
198
  #autoFlexGapMs: [number, number] | null;
154
- #forcedProtocol: "sixel" | "kitty" | undefined;
155
- /** Cell metrics the current frames were built for; a change triggers a rebuild. */
199
+ #forcedProtocol: "sixel" | "kitty" | "iterm" | undefined;
156
200
  #builtCellW = 0;
157
201
  #builtCellH = 0;
158
202
  #kittyImageId: number | undefined;
159
- /** True while a kitty placement may exist on screen; cleared only after the delete escape is delivered. */
160
203
  #kittyCleanupPending = false;
161
- /** Last emitted Sixel raster position; retained until an erase is actually delivered. */
204
+ #kittyCleanupGeneration = 0;
162
205
  #lastSixelFootprint: SixelFootprint | undefined;
163
- /** Terminal state: a disposed widget never touches the TUI or shared slots again. */
164
206
  #disposed = false;
165
- /**
166
- * True while the previous overlay frame carried the cleanup payload. The
167
- * TUI writes the frame after the emitter returns, so delivery is
168
- * acknowledged only on the next emitter pass — and only while the terminal
169
- * stayed available, since a failed render write drops availability.
170
- */
171
- #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>;
172
218
 
173
219
  constructor(options: {
174
220
  ui: TUI;
@@ -176,10 +222,9 @@ export class GajaePetWidget {
176
222
  editorContainer: Container;
177
223
  floorContainer: Container;
178
224
  isWorking: () => boolean;
179
- /** Rows rendered below the composer box (pet floor + hook widgets). */
180
225
  getComposerBottomOffset: () => number;
226
+ syncManagedItermCursor: (row: number, column: number) => Promise<boolean>;
181
227
  forcePixelProtocol?: "sixel" | "kitty";
182
- /** Random [min, max] ms between auto-flexes; null disables. */
183
228
  autoFlexGapMs?: [number, number] | null;
184
229
  }) {
185
230
  this.#ui = options.ui;
@@ -189,13 +234,13 @@ export class GajaePetWidget {
189
234
  this.#framedEditor = new PetFramedEditor(options.editor);
190
235
  this.#isWorking = options.isWorking;
191
236
  this.#getComposerBottomOffset = options.getComposerBottomOffset;
237
+ this.#syncManagedItermCursor = options.syncManagedItermCursor;
192
238
  this.#forcedProtocol = options.forcePixelProtocol;
193
239
  this.#autoFlexGapMs =
194
240
  options.autoFlexGapMs === undefined ? [AUTO_FLEX_MIN_GAP_MS, AUTO_FLEX_MAX_GAP_MS] : options.autoFlexGapMs;
195
241
  }
196
242
 
197
- /** Protocol available for the real-pixel pet, if any. */
198
- static pixelProtocol(): "sixel" | "kitty" | null {
243
+ static pixelProtocol(): "sixel" | "kitty" | "iterm" | null {
199
244
  return getPetPixelProtocol();
200
245
  }
201
246
 
@@ -211,15 +256,21 @@ export class GajaePetWidget {
211
256
  this.#applyMode(mode, true);
212
257
  }
213
258
 
214
- /** Live preview during a selector: change the sprite without re-mounting the
215
- * composer editor (that would tear down the open overlay). After a short idle
216
- * eye-roll it fires the signature burst once (RedGajae flex, BlueGajae para-para
217
- * then sob) so the selector demos the animation instead of waiting the random gap. */
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);
267
+ }
268
+
218
269
  previewMode(mode: PetMode): void {
270
+ if (this.#disposed) return;
219
271
  this.#applyMode(mode, false);
220
- if (mode !== "off" && this.#autoFlexGapMs) {
221
- this.#nextAutoFlexAt = performance.now() + PREVIEW_INTRO_MS;
222
- }
272
+ if (!this.#isActiveOwner() || mode === "off" || !this.#autoFlexGapMs) return;
273
+ this.#previewFlexAt = performance.now() + PREVIEW_INTRO_MS;
223
274
  }
224
275
 
225
276
  commitPreviewMode(mode: PetMode): void {
@@ -228,15 +279,25 @@ export class GajaePetWidget {
228
279
 
229
280
  #applyMode(mode: PetMode, mountComposer: boolean): void {
230
281
  if (this.#disposed || mode === this.#mode) return;
231
-
232
282
  if (mode === "off") {
233
- 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);
234
292
  this.#mode = "off";
235
293
  this.#animation?.unregister();
236
294
  this.#animation = undefined;
237
295
  this.#releaseOverlayEmitter();
238
296
  this.#floorContainer.clear();
239
297
  this.#pixel = undefined;
298
+ this.#activeBurst = undefined;
299
+ this.#workTransition = undefined;
300
+ this.#nextWorkBurstAt = 0;
240
301
  this.#framedEditor.setReserve(0);
241
302
  if (mountComposer) this.#mountEditor(false);
242
303
  this.#ui.requestRender(true);
@@ -244,33 +305,77 @@ export class GajaePetWidget {
244
305
  }
245
306
 
246
307
  const protocol = this.#forcedProtocol ?? GajaePetWidget.pixelProtocol();
247
- if (!protocol) return;
248
- 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
+ }
249
322
  this.#mode = mode;
250
- this.#frame = "base";
323
+ this.#frame = PET_SKINS[mode].baseFrame;
251
324
  this.#flexUntil = 0;
325
+ this.#activeBurst = undefined;
326
+ this.#flexSource = undefined;
327
+ this.#working = this.#isWorking();
328
+ this.#workTransition = undefined;
329
+ this.#nextWorkBurstAt = 0;
330
+ this.#workBurstIndex = 0;
331
+ this.#previewFlexAt = 0;
252
332
  this.#nextAutoFlexAt = 0;
253
333
  this.#buildPixel(protocol);
254
334
  if (mountComposer) this.#mountEditor(true);
255
- // The pet overlays the composer's bottom rows; no floor row is reserved, so
256
- // the composer stays pinned to the terminal bottom.
257
335
  this.#floorContainer.clear();
258
- this.#ui.setPostRenderEmitter(() => this.#overlayPayload());
336
+ this.#ui.setPostRenderEmitter(() => this.#overlayEmission());
337
+ this.#ownedOverlayEpoch = ownershipEpoch + 1;
338
+ petOverlayOwnershipEpochs.set(this.#ui, this.#ownedOverlayEpoch);
259
339
  petOverlayEmitterOwners.set(this.#ui, this);
260
340
  this.#animation ??= registerAnimationCallback(now => this.#tick(now), 80);
261
341
  this.#ui.requestRender(true);
262
342
  }
263
343
 
264
- /** (Re)build the encoded frames for the current terminal cell metrics. */
265
- #buildPixel(protocol: "sixel" | "kitty"): 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 {
266
363
  const cell = getCellDimensions();
267
364
  this.#builtCellW = cell.widthPx;
268
365
  this.#builtCellH = cell.heightPx;
269
366
  const skin: PetSkinId = this.#mode === "off" ? "red" : this.#mode;
270
367
  if (protocol === "kitty") {
271
368
  this.#kittyImageId ??= allocatePetKittyImageId();
369
+ this.#kittyCleanupGeneration++;
272
370
  this.#kittyCleanupPending = true;
273
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;
274
379
  this.#pixel = buildGajaePixelFrames({
275
380
  protocol,
276
381
  skin,
@@ -286,37 +391,50 @@ export class GajaePetWidget {
286
391
 
287
392
  dispose(): void {
288
393
  if (this.#disposed) return;
394
+ const canMutateSharedUi = this.#canMutateSharedUi();
395
+ this.#disposeNeedsLifecycle = canMutateSharedUi;
289
396
  this.#disposed = true;
290
- const kittyImageId = this.#kittyImageId;
291
- const cleanupPayload = this.#imageCleanupPayload();
292
- try {
293
- if (cleanupPayload) {
294
- this.#ui.queueTerminalCleanup(
295
- `\x1b[?2026h\x1b7${cleanupPayload}\x1b8\x1b[?2026l`,
296
- kittyImageId === undefined ? undefined : () => allocatedPetKittyImageIds.delete(kittyImageId),
297
- );
298
- } else if (kittyImageId !== undefined) {
299
- allocatedPetKittyImageIds.delete(kittyImageId);
300
- }
301
- this.#consumeCleanupAuthority();
302
- this.#kittyImageId = undefined;
303
- } finally {
304
- this.#animation?.unregister();
305
- this.#animation = undefined;
306
- this.#releaseOverlayEmitter();
307
- this.#mode = "off";
308
- this.#pixel = 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) {
309
415
  this.#floorContainer.clear();
310
416
  this.#framedEditor.setReserve(0);
311
- // Restore the plain composer only while our framed wrapper is still
312
- // mounted; a successor widget may already own the editor container.
313
- if (this.#editorContainer.children.includes(this.#framedEditor)) {
314
- this.#mountEditor(false);
315
- }
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);
316
434
  }
435
+ await this.#disposePromise;
317
436
  }
318
437
 
319
- /** Clear the shared post-render slot only while this widget still owns it. */
320
438
  #releaseOverlayEmitter(): void {
321
439
  if (petOverlayEmitterOwners.get(this.#ui) === this) {
322
440
  this.#ui.setPostRenderEmitter(undefined);
@@ -329,36 +447,286 @@ export class GajaePetWidget {
329
447
  this.#editorContainer.addChild(framed ? this.#framedEditor : this.#editor);
330
448
  }
331
449
 
332
- /** Re-mount the composer editor (framed when a skin is active) after an overlay. */
333
450
  remountComposer(): void {
334
- 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");
335
457
  }
336
458
 
337
- #pickFrame(now: number): GajaePixelFrameName {
459
+ #syncWorkingState(now: number): PetSkinId | undefined {
460
+ if (this.#mode === "off") return undefined;
461
+ const skin = PET_SKINS[this.#mode];
462
+ const working = this.#isWorking();
463
+ if (working !== this.#working) {
464
+ this.#working = working;
465
+ this.#flexUntil = 0;
466
+ this.#activeBurst = undefined;
467
+ this.#nextWorkBurstAt = 0;
468
+ const steps = working ? skin.workEnter : skin.workExit;
469
+ this.#workTransition = steps?.length ? { startedAt: now, steps } : undefined;
470
+ }
471
+ return this.#mode;
472
+ }
473
+
474
+ #pickFrame(now: number): PetFrameName {
338
475
  const mode = this.#mode;
339
476
  if (mode === "off") return "base";
340
- // Random idle burst → the skin's own animation, driven by its burst descriptor
341
- // (RedGajae holds a flex; BlueGajae dances the para-para then sobs).
477
+ const skin = PET_SKINS[mode];
478
+ this.#syncWorkingState(now);
479
+ if (this.#workTransition) {
480
+ const elapsed = now - this.#workTransition.startedAt;
481
+ const duration = animationDuration(this.#workTransition.steps);
482
+ if (elapsed < duration) return animationFrameAtElapsed(this.#workTransition.steps, elapsed, skin.baseFrame);
483
+ this.#workTransition = undefined;
484
+ }
342
485
  if (now < this.#flexUntil) {
343
- const burst = PET_SKINS[mode].burst;
486
+ const burst = this.#activeBurst ?? skin.burst;
344
487
  const elapsed = now - (this.#flexUntil - petBurstDurationMs(burst));
345
488
  return petBurstFrame(burst, elapsed, now);
346
489
  }
347
- // Working → loop the shared para-para beats.
348
- if (this.#isWorking()) {
349
- let d = now % WORK_LOOP_TOTAL;
350
- for (const [frame, ms] of PARA_PARA_STEPS) {
351
- if (d < ms) return frame;
352
- d -= ms;
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" });
353
532
  }
354
- return "base";
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;
355
546
  }
356
- let t = now % IDLE_LOOP_TOTAL;
357
- for (const [frame, ms] of IDLE_LOOP) {
358
- if (t < ms) return frame;
359
- t -= ms;
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;
360
722
  }
361
- return "base";
723
+ }
724
+
725
+ #scheduleWorkBurst(now: number): void {
726
+ this.#nextWorkBurstAt =
727
+ now +
728
+ OUROBOROS_WORK_BURST_MIN_GAP_MS +
729
+ Math.random() * (OUROBOROS_WORK_BURST_MAX_GAP_MS - OUROBOROS_WORK_BURST_MIN_GAP_MS);
362
730
  }
363
731
 
364
732
  #scheduleAutoFlex(now: number): void {
@@ -368,9 +736,52 @@ export class GajaePetWidget {
368
736
  }
369
737
 
370
738
  #tick(now: number): void {
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;
748
+ }
749
+ }
750
+ if (now >= this.#flexUntil) {
751
+ this.#flexUntil = 0;
752
+ this.#flexSource = undefined;
753
+ if (this.#previewFlexAt !== 0 && now >= this.#previewFlexAt && skin) {
754
+ this.#activeBurst = skin.burst;
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
+ }
767
+ }
768
+ }
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) {
772
+ const burst = skin.workBursts[this.#workBurstIndex % skin.workBursts.length];
773
+ this.#workBurstIndex++;
774
+ this.#activeBurst = burst;
775
+ this.#flexUntil = now + petBurstDurationMs(burst);
776
+ this.#flexSource = "working";
777
+ this.#scheduleWorkBurst(this.#flexUntil);
778
+ }
779
+ }
780
+ if (this.#itermProtocol) {
781
+ this.#tickIterm(now);
782
+ return;
783
+ }
371
784
  if (this.#mode === "off" || !this.#pixel) return;
372
- // A font/zoom change resizes the terminal cells; rebuild the frames so the
373
- // kitty image and its sub-cell drop match the new cell metrics.
374
785
  const cell = getCellDimensions();
375
786
  if (cell.widthPx !== this.#builtCellW || cell.heightPx !== this.#builtCellH) {
376
787
  const protocol = this.#forcedProtocol ?? GajaePetWidget.pixelProtocol();
@@ -380,25 +791,37 @@ export class GajaePetWidget {
380
791
  this.#ui.requestRender(true);
381
792
  }
382
793
  }
383
- // Random show-off, both while idle and while working. Each skin's burst runs for
384
- // its own length (RedGajae a brief flex; BlueGajae a para-para cycle plus sob).
385
- if (this.#autoFlexGapMs && now >= this.#flexUntil) {
386
- if (this.#nextAutoFlexAt === 0) {
387
- this.#scheduleAutoFlex(now);
388
- } else if (now >= this.#nextAutoFlexAt) {
389
- const burstMs = petBurstDurationMs(PET_SKINS[this.#mode].burst);
390
- this.#flexUntil = now + burstMs;
391
- this.#scheduleAutoFlex(now + burstMs);
392
- }
393
- }
394
794
  const frame = this.#pickFrame(now);
395
795
  if (frame === this.#frame) return;
396
796
  this.#frame = frame;
397
- // Write directly: a frame swap changes no component line, so the TUI
398
- // would skip the render write (and with it the post-render emitter).
399
- const payload = this.#overlayPayload(true) ?? "";
400
- if (payload && this.#ui.terminalAvailable) {
401
- 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
+ });
402
825
  }
403
826
  }
404
827
 
@@ -408,9 +831,6 @@ export class GajaePetWidget {
408
831
  const columns = this.#ui.terminal.columns;
409
832
  if (!this.#framedEditor.canFit(columns)) return null;
410
833
  const rows = this.#ui.terminal.rows;
411
- // The sprite is lifted one safety row above the scrolling edge, then dropped
412
- // back onto the composer's bottom border per protocol (sixel via transparent
413
- // top padding, kitty via a sub-cell Y offset baked into the frames).
414
834
  const composerBottom = rows - this.#getComposerBottomOffset();
415
835
  const y = composerBottom - pixel.rows - PET_RAISE_ROWS;
416
836
  const x = columns - pixel.columns - PET_SIDE_MARGIN;
@@ -420,21 +840,16 @@ export class GajaePetWidget {
420
840
 
421
841
  #clearSixelFootprint(footprint: SixelFootprint): string {
422
842
  let out = "\x1b[0m";
423
- for (let row = 0; row < footprint.rows; row++) {
843
+ for (let row = 0; row < footprint.rows; row++)
424
844
  out += `\x1b[${footprint.y + row + 1};${footprint.x + 1}H\x1b[${footprint.columns}X`;
425
- }
426
845
  return out;
427
846
  }
428
847
 
429
- /** Pending on-screen image cleanup. Pure: authority is consumed separately, on delivery. */
430
848
  #imageCleanupPayload(): string {
431
849
  let out = "";
432
- if (this.#kittyCleanupPending && this.#kittyImageId !== undefined) {
850
+ if (this.#kittyCleanupPending && this.#kittyImageId !== undefined)
433
851
  out += `\x1b_Ga=d,d=I,i=${this.#kittyImageId},q=2\x1b\\`;
434
- }
435
- if (this.#lastSixelFootprint) {
436
- out += this.#clearSixelFootprint(this.#lastSixelFootprint);
437
- }
852
+ if (this.#lastSixelFootprint) out += this.#clearSixelFootprint(this.#lastSixelFootprint);
438
853
  return out;
439
854
  }
440
855
 
@@ -443,67 +858,66 @@ export class GajaePetWidget {
443
858
  this.#lastSixelFootprint = undefined;
444
859
  }
445
860
 
446
- /**
447
- * Best-effort direct erase of the on-screen pet image. Cleanup authority is
448
- * consumed only after the write is actually delivered: an unavailable
449
- * terminal or a throwing write keeps the erase pending so a later mode
450
- * switch or dispose can retry it.
451
- */
452
- #writeImageCleanup(): void {
453
- if (!this.#ui.terminalAvailable) return;
454
- const payload = this.#imageCleanupPayload();
455
- if (!payload) return;
456
- try {
457
- this.#ui.terminal.write(`\x1b[?2026h\x1b7${payload}\x1b8\x1b[?2026l`);
458
- } catch {
459
- // Keep the footprint/placement authority; the terminal write layer
460
- // reports availability separately and callers retry on the next
461
- // lifecycle transition.
462
- 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);
463
868
  }
464
- 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
+ });
465
887
  }
466
888
 
467
- /** Draw escape payload at the pet's absolute position. */
468
- #overlayPayload(clearPet = false): string | null {
889
+ #overlayEmission(clearPet = false): PetOverlayEmission | null {
890
+ if (!this.#isActiveOwner()) return null;
469
891
  const pixel = this.#pixel;
470
892
  if (!pixel) return null;
471
893
  const pos = this.#petPosition();
472
894
  if (!pos) {
473
- // Deferred delivery acknowledgement: the TUI writes the frame after
474
- // this emitter returns, and that write can fail. Consume the cleanup
475
- // authority only once a later pass observes the terminal survived
476
- // the frame that carried the payload; otherwise retain it so a later
477
- // lifecycle cleanup retries the erase/delete.
478
- if (this.#frameCleanupAwaitingAck && this.#ui.terminalAvailable) {
479
- this.#consumeCleanupAuthority();
480
- }
481
- this.#frameCleanupAwaitingAck = false;
482
- if (!this.#ui.terminalAvailable) return null;
483
895
  const cleanup = this.#imageCleanupPayload();
484
896
  if (!cleanup) return null;
485
- this.#frameCleanupAwaitingAck = true;
486
- return cleanup;
897
+ return {
898
+ payload: cleanup,
899
+ onWritten: () => {
900
+ if (this.#isActiveOwner()) this.#consumeCleanupAuthority();
901
+ },
902
+ };
487
903
  }
488
- // A full frame supersedes any cleanup-only frame still awaiting ack.
489
- this.#frameCleanupAwaitingAck = false;
490
904
  const { x, y } = pos;
491
905
  let out = "";
492
-
906
+ let onWritten: (() => void) | undefined;
493
907
  if (pixel.protocol === "sixel") {
494
908
  const footprint = { x, y, columns: pixel.columns, rows: pixel.rasterRows };
495
- if (this.#lastSixelFootprint && !sameFootprint(this.#lastSixelFootprint, footprint)) {
496
- out += this.#clearSixelFootprint(this.#lastSixelFootprint);
497
- }
909
+ const previous = this.#lastSixelFootprint;
910
+ if (previous && !sameFootprint(previous, footprint)) out += this.#clearSixelFootprint(previous);
498
911
  if (clearPet) out += this.#clearSixelFootprint(footprint);
499
- this.#lastSixelFootprint = footprint;
912
+ onWritten = () => {
913
+ if (this.#isActiveOwner()) this.#lastSixelFootprint = footprint;
914
+ };
500
915
  } else {
501
- // A kitty frame emitted below (re)places the image, so cleanup is
502
- // pending again even if a narrow-terminal pass consumed it earlier.
503
- this.#kittyCleanupPending = true;
916
+ onWritten = () => {
917
+ if (this.#isActiveOwner()) this.#kittyCleanupPending = true;
918
+ };
504
919
  }
505
-
506
920
  out += `\x1b[${y + 1};${x + 1}H${pixel.frames[this.#frame]}`;
507
- return out;
921
+ return { payload: out, onWritten };
508
922
  }
509
923
  }