@gajae-code/coding-agent 0.11.11 → 0.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (344) hide show
  1. package/CHANGELOG.md +131 -4
  2. package/dist/types/async/job-manager.d.ts +17 -3
  3. package/dist/types/cli/read-cli.d.ts +1 -0
  4. package/dist/types/commands/deep-interview.d.ts +9 -0
  5. package/dist/types/commands/harness.d.ts +22 -0
  6. package/dist/types/commands/read.d.ts +6 -0
  7. package/dist/types/config/model-registry.d.ts +4 -0
  8. package/dist/types/config/provider-auth-health.d.ts +14 -0
  9. package/dist/types/config/provider-ranking.d.ts +59 -0
  10. package/dist/types/config/settings-schema.d.ts +83 -1
  11. package/dist/types/config/settings.d.ts +2 -0
  12. package/dist/types/exa/mcp-client.d.ts +10 -1
  13. package/dist/types/exec/bash-executor.d.ts +45 -12
  14. package/dist/types/extensibility/extensions/types.d.ts +38 -4
  15. package/dist/types/extensibility/gjc-plugins/index.d.ts +12 -2
  16. package/dist/types/extensibility/gjc-plugins/installer.d.ts +63 -7
  17. package/dist/types/extensibility/gjc-plugins/lifecycle-reconciliation.d.ts +55 -0
  18. package/dist/types/extensibility/gjc-plugins/lifecycle.d.ts +64 -0
  19. package/dist/types/extensibility/gjc-plugins/runtime-quarantine.d.ts +67 -0
  20. package/dist/types/extensibility/gjc-plugins/session-validation.d.ts +3 -1
  21. package/dist/types/extensibility/gjc-plugins/types.d.ts +124 -0
  22. package/dist/types/extensibility/shared-events.d.ts +6 -0
  23. package/dist/types/gjc-runtime/deep-interview-ambiguity.d.ts +5 -12
  24. package/dist/types/gjc-runtime/deep-interview-recorder.d.ts +17 -32
  25. package/dist/types/gjc-runtime/deep-interview-stage.d.ts +37 -0
  26. package/dist/types/gjc-runtime/deep-interview-state.d.ts +4 -135
  27. package/dist/types/gjc-runtime/gc-runtime.d.ts +1 -1
  28. package/dist/types/gjc-runtime/launch-tmux.d.ts +1 -0
  29. package/dist/types/gjc-runtime/memory-guard-owner-claims.d.ts +47 -0
  30. package/dist/types/gjc-runtime/ralplan-runtime.d.ts +70 -1
  31. package/dist/types/gjc-runtime/state-writer.d.ts +5 -46
  32. package/dist/types/gjc-runtime/team-launch.d.ts +1 -1
  33. package/dist/types/gjc-runtime/team-runtime.d.ts +27 -1
  34. package/dist/types/gjc-runtime/team-store.d.ts +12 -0
  35. package/dist/types/gjc-runtime/team-worker-heartbeat.d.ts +43 -0
  36. package/dist/types/gjc-runtime/team-worker-memory-guard.d.ts +92 -0
  37. package/dist/types/gjc-runtime/tmux-owner-isolation.d.ts +17 -0
  38. package/dist/types/gjc-runtime/ultragoal-receipt-freshness.d.ts +8 -0
  39. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +31 -80
  40. package/dist/types/gjc-runtime/workflow-manifest.d.ts +3 -5
  41. package/dist/types/internal-urls/local-root-gc.d.ts +2 -0
  42. package/dist/types/main.d.ts +1 -1
  43. package/dist/types/memories/index.d.ts +2 -0
  44. package/dist/types/modes/acp/acp-agent.d.ts +10 -19
  45. package/dist/types/modes/components/assistant-message.d.ts +3 -1
  46. package/dist/types/modes/components/gjc-bundle-settings.d.ts +34 -0
  47. package/dist/types/modes/components/irc-sidebar.d.ts +41 -0
  48. package/dist/types/modes/components/notifications-settings-editor.d.ts +1 -0
  49. package/dist/types/modes/components/read-tool-group.d.ts +4 -4
  50. package/dist/types/modes/components/session-observer-overlay.d.ts +22 -0
  51. package/dist/types/modes/components/settings-selector.d.ts +11 -0
  52. package/dist/types/modes/components/status-line/gh.d.ts +5 -0
  53. package/dist/types/modes/components/tool-execution.d.ts +8 -0
  54. package/dist/types/modes/components/transcript-viewer-overlay.d.ts +18 -1
  55. package/dist/types/modes/controllers/event-controller.d.ts +3 -0
  56. package/dist/types/modes/interactive-mode.d.ts +2 -0
  57. package/dist/types/modes/irc-observation-ledger.d.ts +17 -1
  58. package/dist/types/modes/types.d.ts +2 -0
  59. package/dist/types/modes/utils/ui-helpers.d.ts +16 -0
  60. package/dist/types/reminders/star-reminder.d.ts +3 -13
  61. package/dist/types/runtime-mcp/manager.d.ts +1 -0
  62. package/dist/types/runtime-mcp/smithery-auth.d.ts +5 -0
  63. package/dist/types/sdk/acp/adapter.d.ts +3 -1
  64. package/dist/types/sdk/broker/lifecycle.d.ts +5 -0
  65. package/dist/types/sdk/broker/transport.d.ts +2 -0
  66. package/dist/types/sdk/bus/attachment-registry.d.ts +1 -0
  67. package/dist/types/sdk/bus/chat-daemon-control.d.ts +4 -1
  68. package/dist/types/sdk/bus/config.d.ts +6 -2
  69. package/dist/types/sdk/bus/control-drain-lease.d.ts +52 -0
  70. package/dist/types/sdk/bus/index.d.ts +44 -1
  71. package/dist/types/sdk/bus/kind-aware-reconciliation.d.ts +44 -0
  72. package/dist/types/sdk/bus/lifecycle-control-runtime.d.ts +4 -0
  73. package/dist/types/sdk/bus/prompt-reconciliation.d.ts +2 -1
  74. package/dist/types/sdk/bus/reconciliation-store.d.ts +69 -0
  75. package/dist/types/sdk/bus/rich-draft.d.ts +1 -0
  76. package/dist/types/sdk/bus/rich-render.d.ts +2 -0
  77. package/dist/types/sdk/bus/telegram-adoption-intent.d.ts +165 -0
  78. package/dist/types/sdk/bus/telegram-cli.d.ts +12 -0
  79. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +11 -4
  80. package/dist/types/sdk/bus/telegram-daemon.d.ts +25 -1
  81. package/dist/types/sdk/bus/telegram-reference.d.ts +11 -1
  82. package/dist/types/sdk/bus/threaded-render.d.ts +3 -2
  83. package/dist/types/sdk/bus/topic-registry.d.ts +11 -1
  84. package/dist/types/sdk/host/control/operations.d.ts +1 -1
  85. package/dist/types/sdk/host/host.d.ts +2 -0
  86. package/dist/types/sdk/host/query/handlers.d.ts +5 -0
  87. package/dist/types/sdk/host/query/revision-store.d.ts +2 -0
  88. package/dist/types/sdk/host/reverse-leases.d.ts +1 -1
  89. package/dist/types/sdk/prompt-status.d.ts +23 -3
  90. package/dist/types/sdk/session.d.ts +6 -0
  91. package/dist/types/session/agent-session.d.ts +82 -12
  92. package/dist/types/session/artifacts.d.ts +8 -0
  93. package/dist/types/session/blob-store.d.ts +57 -4
  94. package/dist/types/session/internal/managed-session-storage.d.ts +8 -1
  95. package/dist/types/session/memory-guard-checkpoint-participant.d.ts +69 -0
  96. package/dist/types/session/session-manager.d.ts +48 -19
  97. package/dist/types/session/session-storage.d.ts +4 -0
  98. package/dist/types/session/streaming-output.d.ts +96 -12
  99. package/dist/types/setup/credential-import.d.ts +12 -0
  100. package/dist/types/skill-state/active-state.d.ts +12 -4
  101. package/dist/types/skill-state/workflow-hud.d.ts +9 -0
  102. package/dist/types/task/executor.d.ts +3 -2
  103. package/dist/types/task/index.d.ts +3 -1
  104. package/dist/types/task/provider-retry-status.d.ts +8 -0
  105. package/dist/types/task/receipt.d.ts +7 -1
  106. package/dist/types/task/types.d.ts +23 -0
  107. package/dist/types/tools/ask-answer-registry.d.ts +2 -2
  108. package/dist/types/tools/bash-interactive.d.ts +4 -1
  109. package/dist/types/tools/bash.d.ts +3 -1
  110. package/dist/types/tools/browser/launch.d.ts +7 -0
  111. package/dist/types/tools/fetch.d.ts +17 -1
  112. package/dist/types/tools/image-gen.d.ts +4 -0
  113. package/dist/types/tools/index.d.ts +4 -0
  114. package/dist/types/tools/output-meta.d.ts +44 -3
  115. package/dist/types/tools/path-utils.d.ts +1 -0
  116. package/dist/types/tools/read-internals.d.ts +12 -0
  117. package/dist/types/tools/read.d.ts +45 -7
  118. package/dist/types/tools/resource-gc.d.ts +4 -0
  119. package/dist/types/tools/review.d.ts +5 -7
  120. package/dist/types/tools/subagent.d.ts +2 -0
  121. package/dist/types/tools/tool-result.d.ts +7 -1
  122. package/dist/types/utils/gh.d.ts +13 -0
  123. package/dist/types/utils/image-resize.d.ts +1 -0
  124. package/dist/types/web/search/providers/kimi.d.ts +2 -0
  125. package/dist/types/web/search/providers/searxng.d.ts +7 -0
  126. package/dist/types/web/search/providers/xai.d.ts +2 -0
  127. package/package.json +11 -13
  128. package/scripts/acp-conformance-agent.ts +218 -0
  129. package/scripts/benchmark-sticky-viewport-pr1.ts +239 -0
  130. package/scripts/capture-gjc-bundle-settings.ts +306 -0
  131. package/scripts/capture-sticky-viewport-showcase.ts +292 -0
  132. package/scripts/generate-sdk-operation-inventory.ts +5 -0
  133. package/scripts/resident-memory-bench.ts +1341 -0
  134. package/scripts/run-acp-conformance.ts +285 -0
  135. package/scripts/verify-gjc-sdk-canonicalization.ts +122 -18
  136. package/scripts/verify-sticky-viewport-showcase.ts +611 -0
  137. package/src/async/job-manager.ts +78 -20
  138. package/src/capability/index.ts +8 -0
  139. package/src/cli/fast-help.ts +1 -1
  140. package/src/cli/plugin-cli.ts +176 -18
  141. package/src/cli/read-cli.ts +2 -1
  142. package/src/cli.ts +1 -0
  143. package/src/commands/deep-interview.ts +29 -9
  144. package/src/commands/harness.ts +35 -12
  145. package/src/commands/read.ts +11 -2
  146. package/src/commands/team.ts +20 -2
  147. package/src/config/file-lock.ts +11 -2
  148. package/src/config/model-registry.ts +21 -2
  149. package/src/config/provider-auth-health.ts +42 -0
  150. package/src/config/provider-ranking.ts +119 -0
  151. package/src/config/settings-schema.ts +74 -1
  152. package/src/config/settings.ts +246 -108
  153. package/src/coordinator-mcp/question-gate-codec.ts +1 -1
  154. package/src/coordinator-mcp/server.ts +5 -12
  155. package/src/defaults/gjc/skills/deep-interview/SKILL.md +230 -82
  156. package/src/defaults/gjc/skills/ralplan/SKILL.md +77 -41
  157. package/src/defaults/gjc/skills/team/SKILL.md +3 -1
  158. package/src/defaults/gjc/skills/ultragoal/SKILL.md +62 -39
  159. package/src/defaults/gjc/skills/ultragoal/validation-batch-contracts.md +12 -0
  160. package/src/defaults/gjc-defaults.ts +4 -4
  161. package/src/eval/py/executor.ts +7 -2
  162. package/src/exa/mcp-client.ts +12 -3
  163. package/src/exec/bash-executor.ts +144 -27
  164. package/src/extensibility/extensions/runner.ts +15 -0
  165. package/src/extensibility/extensions/types.ts +46 -5
  166. package/src/extensibility/gjc-plugins/constrained-hooks.ts +8 -2
  167. package/src/extensibility/gjc-plugins/index.ts +19 -2
  168. package/src/extensibility/gjc-plugins/installer.ts +189 -50
  169. package/src/extensibility/gjc-plugins/lifecycle-reconciliation.ts +199 -0
  170. package/src/extensibility/gjc-plugins/lifecycle.ts +626 -0
  171. package/src/extensibility/gjc-plugins/observability.ts +3 -3
  172. package/src/extensibility/gjc-plugins/prompt-appendix.ts +5 -1
  173. package/src/extensibility/gjc-plugins/runtime-adapters.ts +12 -2
  174. package/src/extensibility/gjc-plugins/runtime-quarantine.ts +121 -0
  175. package/src/extensibility/gjc-plugins/schema.ts +84 -12
  176. package/src/extensibility/gjc-plugins/session-validation.ts +9 -3
  177. package/src/extensibility/gjc-plugins/types.ts +141 -0
  178. package/src/extensibility/gjc-plugins/validation.ts +9 -0
  179. package/src/extensibility/shared-events.ts +5 -0
  180. package/src/gjc-runtime/deep-interview-ambiguity.ts +47 -82
  181. package/src/gjc-runtime/deep-interview-recorder.ts +227 -310
  182. package/src/gjc-runtime/deep-interview-runtime.ts +109 -270
  183. package/src/gjc-runtime/deep-interview-stage.ts +1016 -0
  184. package/src/gjc-runtime/deep-interview-state.ts +12 -943
  185. package/src/gjc-runtime/gc-render.ts +1 -0
  186. package/src/gjc-runtime/gc-runtime.ts +11 -1
  187. package/src/gjc-runtime/launch-tmux.ts +25 -6
  188. package/src/gjc-runtime/managed-owner-admission.ts +1 -1
  189. package/src/gjc-runtime/memory-guard-owner-claims.ts +389 -0
  190. package/src/gjc-runtime/ralplan-runtime.ts +1149 -98
  191. package/src/gjc-runtime/state-runtime.ts +2 -1
  192. package/src/gjc-runtime/state-writer.ts +70 -222
  193. package/src/gjc-runtime/team-launch.ts +42 -5
  194. package/src/gjc-runtime/team-runtime.ts +1085 -40
  195. package/src/gjc-runtime/team-store.ts +32 -0
  196. package/src/gjc-runtime/team-worker-heartbeat.ts +149 -0
  197. package/src/gjc-runtime/team-worker-memory-guard.ts +363 -0
  198. package/src/gjc-runtime/team-workers.ts +1 -0
  199. package/src/gjc-runtime/tmux-owner-isolation.ts +28 -0
  200. package/src/gjc-runtime/tmux-sessions.ts +58 -13
  201. package/src/gjc-runtime/ultragoal-receipt-freshness.ts +20 -0
  202. package/src/gjc-runtime/ultragoal-runtime.ts +414 -618
  203. package/src/gjc-runtime/workflow-manifest.generated.json +105 -449
  204. package/src/gjc-runtime/workflow-manifest.ts +38 -247
  205. package/src/hooks/native-skill-hook.ts +17 -4
  206. package/src/hooks/skill-state.ts +1 -23
  207. package/src/internal-urls/docs-index.generated.ts +21 -22
  208. package/src/internal-urls/local-root-gc.ts +127 -0
  209. package/src/main.ts +9 -5
  210. package/src/memories/index.ts +10 -0
  211. package/src/modes/DESIGN.md +98 -0
  212. package/src/modes/acp/acp-agent.ts +339 -191
  213. package/src/modes/acp/acp-event-mapper.ts +14 -7
  214. package/src/modes/acp/acp-mode.ts +39 -3
  215. package/src/modes/components/assistant-message.ts +123 -22
  216. package/src/modes/components/gjc-bundle-settings.ts +508 -0
  217. package/src/modes/components/irc-sidebar.ts +176 -62
  218. package/src/modes/components/model-selector.ts +61 -4
  219. package/src/modes/components/notifications-settings-editor.ts +12 -1
  220. package/src/modes/components/oauth-selector.ts +96 -28
  221. package/src/modes/components/read-tool-group.ts +82 -14
  222. package/src/modes/components/session-observer-overlay.ts +457 -83
  223. package/src/modes/components/settings-selector.ts +116 -23
  224. package/src/modes/components/status-line/gh.ts +16 -0
  225. package/src/modes/components/tool-execution.ts +138 -27
  226. package/src/modes/components/tool-status-header.ts +3 -14
  227. package/src/modes/components/transcript-viewer-overlay.ts +99 -24
  228. package/src/modes/controllers/command-controller.ts +4 -0
  229. package/src/modes/controllers/event-controller.ts +109 -27
  230. package/src/modes/controllers/extension-ui-controller.ts +26 -2
  231. package/src/modes/controllers/input-controller.ts +18 -3
  232. package/src/modes/controllers/selector-controller.ts +127 -36
  233. package/src/modes/interactive-mode.ts +62 -3
  234. package/src/modes/irc-observation-ledger.ts +71 -15
  235. package/src/modes/runtime-init.ts +22 -2
  236. package/src/modes/types.ts +2 -0
  237. package/src/modes/utils/ui-helpers.ts +94 -1
  238. package/src/prompts/agent-fragments/ralplan-persistence.md +4 -3
  239. package/src/prompts/agents/architect.md +9 -1
  240. package/src/prompts/agents/critic.md +9 -0
  241. package/src/prompts/system/eager-todo.md +8 -4
  242. package/src/prompts/system/system-prompt.md +11 -5
  243. package/src/prompts/tools/bash.md +5 -5
  244. package/src/prompts/tools/read.md +4 -2
  245. package/src/prompts/tools/search-tool-bm25.md +5 -0
  246. package/src/prompts/tools/task.md +1 -1
  247. package/src/reminders/star-reminder.ts +4 -42
  248. package/src/runtime-mcp/manager.ts +19 -4
  249. package/src/runtime-mcp/smithery-auth.ts +17 -2
  250. package/src/runtime-mcp/smithery-connect.ts +11 -1
  251. package/src/sdk/acp/adapter.ts +22 -6
  252. package/src/sdk/broker/lifecycle.ts +25 -6
  253. package/src/sdk/broker/transport.ts +35 -3
  254. package/src/sdk/bus/attachment-registry.ts +5 -1
  255. package/src/sdk/bus/chat-daemon-cli.ts +4 -39
  256. package/src/sdk/bus/chat-daemon-control.ts +6 -3
  257. package/src/sdk/bus/config.ts +18 -2
  258. package/src/sdk/bus/control-drain-lease.ts +104 -0
  259. package/src/sdk/bus/index.ts +984 -146
  260. package/src/sdk/bus/kind-aware-reconciliation.ts +317 -0
  261. package/src/sdk/bus/lifecycle-commands.ts +4 -1
  262. package/src/sdk/bus/lifecycle-control-runtime.ts +403 -88
  263. package/src/sdk/bus/prompt-reconciliation.ts +2 -1
  264. package/src/sdk/bus/reconciliation-store.ts +303 -0
  265. package/src/sdk/bus/rich-draft.ts +1 -1
  266. package/src/sdk/bus/rich-render.ts +2 -2
  267. package/src/sdk/bus/telegram-adoption-intent.ts +574 -0
  268. package/src/sdk/bus/telegram-cli.ts +33 -8
  269. package/src/sdk/bus/telegram-daemon-cli.ts +1 -0
  270. package/src/sdk/bus/telegram-daemon-contract.ts +11 -4
  271. package/src/sdk/bus/telegram-daemon.ts +916 -23
  272. package/src/sdk/bus/telegram-reference.ts +40 -3
  273. package/src/sdk/bus/threaded-render.ts +48 -3
  274. package/src/sdk/bus/topic-registry.ts +20 -0
  275. package/src/sdk/host/control/dispatch.ts +5 -1
  276. package/src/sdk/host/control/operations.ts +1 -1
  277. package/src/sdk/host/host.ts +23 -1
  278. package/src/sdk/host/query/handlers.ts +55 -0
  279. package/src/sdk/host/query/revision-store.ts +36 -2
  280. package/src/sdk/host/reverse-leases.ts +44 -7
  281. package/src/sdk/mcp/server.ts +46 -18
  282. package/src/sdk/prompt-status.ts +16 -3
  283. package/src/sdk/protocol/operation-inventory.generated.json +51 -0
  284. package/src/sdk/protocol/operation-registry.ts +7 -2
  285. package/src/sdk/session.ts +101 -10
  286. package/src/session/agent-session.ts +1286 -220
  287. package/src/session/artifacts.ts +22 -0
  288. package/src/session/auth-broker-config.ts +10 -3
  289. package/src/session/blob-store.ts +915 -24
  290. package/src/session/internal/managed-session-scope.ts +44 -6
  291. package/src/session/internal/managed-session-storage.ts +48 -6
  292. package/src/session/memory-guard-checkpoint-participant.ts +131 -0
  293. package/src/session/session-manager.ts +2364 -438
  294. package/src/session/session-storage.ts +9 -1
  295. package/src/session/streaming-output.ts +500 -111
  296. package/src/setup/credential-import.ts +75 -13
  297. package/src/setup/provider-onboarding.ts +19 -4
  298. package/src/skill-state/active-state.ts +32 -10
  299. package/src/skill-state/workflow-hud.ts +42 -2
  300. package/src/skill-state/workflow-mutation-guard.ts +560 -8
  301. package/src/task/executor.ts +122 -39
  302. package/src/task/index.ts +23 -14
  303. package/src/task/provider-retry-status.ts +16 -0
  304. package/src/task/receipt.ts +23 -7
  305. package/src/task/render.ts +6 -2
  306. package/src/task/types.ts +108 -0
  307. package/src/tools/ask-answer-registry.ts +12 -6
  308. package/src/tools/ask.ts +169 -118
  309. package/src/tools/ast-edit.ts +1 -0
  310. package/src/tools/ast-grep.ts +1 -0
  311. package/src/tools/bash-interactive.ts +8 -3
  312. package/src/tools/bash.ts +503 -60
  313. package/src/tools/browser/launch.ts +41 -6
  314. package/src/tools/fetch.ts +114 -23
  315. package/src/tools/find.ts +1 -0
  316. package/src/tools/image-gen.ts +29 -5
  317. package/src/tools/index.ts +4 -0
  318. package/src/tools/output-meta.ts +281 -20
  319. package/src/tools/path-utils.ts +8 -3
  320. package/src/tools/read-internals.ts +44 -0
  321. package/src/tools/read.ts +1560 -310
  322. package/src/tools/resource-gc.ts +182 -18
  323. package/src/tools/review.ts +5 -7
  324. package/src/tools/search.ts +1 -0
  325. package/src/tools/subagent-render.ts +6 -2
  326. package/src/tools/subagent.ts +6 -0
  327. package/src/tools/telegram-send.ts +6 -3
  328. package/src/tools/tool-result.ts +14 -1
  329. package/src/utils/gh.ts +39 -0
  330. package/src/utils/image-resize.ts +3 -0
  331. package/src/web/search/providers/anthropic.ts +4 -4
  332. package/src/web/search/providers/codex.ts +1 -1
  333. package/src/web/search/providers/kimi.ts +15 -5
  334. package/src/web/search/providers/searxng.ts +47 -4
  335. package/src/web/search/providers/xai.ts +7 -2
  336. package/dist/types/gjc-runtime/deep-interview-draft.d.ts +0 -8
  337. package/dist/types/gjc-runtime/deep-interview-payload.d.ts +0 -41
  338. package/dist/types/gjc-runtime/deep-interview-repair.d.ts +0 -17
  339. package/dist/types/gjc-runtime/ultragoal-pipeline.d.ts +0 -23
  340. package/src/defaults/gjc/skills/ultragoal/pipeline-validation-contracts.md +0 -30
  341. package/src/gjc-runtime/deep-interview-draft.ts +0 -843
  342. package/src/gjc-runtime/deep-interview-payload.ts +0 -262
  343. package/src/gjc-runtime/deep-interview-repair.ts +0 -1674
  344. package/src/gjc-runtime/ultragoal-pipeline.ts +0 -306
@@ -54,6 +54,34 @@ export type TranscriptViewerOverlayOptions = {
54
54
 
55
55
  type RenderedEntry = { lineStart: number; lineCount: number };
56
56
 
57
+ export const __transcriptViewerPerfCounters = {
58
+ enabled: false,
59
+ layoutCacheHits: 0,
60
+ layoutCacheMisses: 0,
61
+ refreshRuns: 0,
62
+ rebuildRuns: 0,
63
+ snapshot() {
64
+ return {
65
+ layoutCacheHits: this.layoutCacheHits,
66
+ layoutCacheMisses: this.layoutCacheMisses,
67
+ refreshRuns: this.refreshRuns,
68
+ rebuildRuns: this.rebuildRuns,
69
+ };
70
+ },
71
+ enable(): void {
72
+ this.enabled = true;
73
+ },
74
+ disable(): void {
75
+ this.enabled = false;
76
+ },
77
+ reset(): void {
78
+ this.layoutCacheHits = 0;
79
+ this.layoutCacheMisses = 0;
80
+ this.refreshRuns = 0;
81
+ this.rebuildRuns = 0;
82
+ },
83
+ };
84
+
57
85
  /** Message-agnostic, modal transcript browser over resolved projection entries. */
58
86
  export class TranscriptViewerOverlay extends Container {
59
87
  #options: TranscriptViewerOverlayOptions;
@@ -73,6 +101,7 @@ export class TranscriptViewerOverlay extends Container {
73
101
  #followTailPending = false;
74
102
  #skipFollowTailOnce = false;
75
103
  #followTailActive = false;
104
+ #layoutCache = new Map<string, Map<string, string[]>>();
76
105
 
77
106
  constructor(options: TranscriptViewerOverlayOptions) {
78
107
  super();
@@ -87,7 +116,11 @@ export class TranscriptViewerOverlay extends Container {
87
116
  get isFullscreen(): boolean {
88
117
  return this.#fullscreen;
89
118
  }
90
- refresh(identityMap?: ReadonlyMap<string, string>): void {
119
+ refresh(identityMap?: ReadonlyMap<string, string>, preserveLayoutCache = false): void {
120
+ if (__transcriptViewerPerfCounters.enabled) __transcriptViewerPerfCounters.refreshRuns++;
121
+ // Incremental sources explicitly invalidate same-id payload patches before requesting a
122
+ // preserving refresh. Other source changes conservatively drop all layout variants.
123
+ if (!preserveLayoutCache) this.#layoutCache.clear();
91
124
  const previous = this.selectedEntryId;
92
125
  const previousPosition = this.#selected;
93
126
  const reconciledPrevious = previous ? (identityMap?.get(previous) ?? previous) : undefined;
@@ -109,9 +142,15 @@ export class TranscriptViewerOverlay extends Container {
109
142
  this.#initialized = true;
110
143
  this.#rebuild();
111
144
  }
145
+ invalidateLayoutEntries(entryIds: readonly string[]): void {
146
+ for (const id of entryIds) this.#layoutCache.delete(id);
147
+ }
112
148
  override render(width: number): string[] {
113
- this.#width = Math.max(1, width);
114
- this.refresh();
149
+ const nextWidth = Math.max(1, width);
150
+ const widthChanged = nextWidth !== this.#width;
151
+ this.#width = nextWidth;
152
+ if (widthChanged) this.#layoutCache.clear();
153
+ if (widthChanged || getMarkdownTheme() !== this.#mdTheme) this.#rebuild();
115
154
  const defaultHeader = this.#fullscreen
116
155
  ? [theme.fg("accent", "Transcript block")]
117
156
  : [theme.fg("accent", sanitizeText(this.#options.title ?? "Transcript"))];
@@ -179,6 +218,7 @@ export class TranscriptViewerOverlay extends Container {
179
218
  if (this.#fullscreen) {
180
219
  this.#fullscreen = false;
181
220
  this.#scrollOffset = 0;
221
+ this.#rebuild();
182
222
  this.#requestRender();
183
223
  } else this.#options.onClose();
184
224
  return;
@@ -208,6 +248,7 @@ export class TranscriptViewerOverlay extends Container {
208
248
  this.#followTailActive = false;
209
249
  this.#followTailPending = false;
210
250
  this.#selected = 0;
251
+ this.#rebuild();
211
252
  this.#scrollOffset = 0;
212
253
  this.#requestRender();
213
254
  return;
@@ -216,6 +257,7 @@ export class TranscriptViewerOverlay extends Container {
216
257
  this.#followTailActive = true;
217
258
  this.#followTailPending = true;
218
259
  this.#selected = Math.max(0, count - 1);
260
+ this.#rebuild();
219
261
  this.#scrollOffset = this.#lines.length;
220
262
  this.#requestRender();
221
263
  return;
@@ -228,6 +270,7 @@ export class TranscriptViewerOverlay extends Container {
228
270
  this.#followTailActive = false;
229
271
  this.#fullscreen = true;
230
272
  this.#scrollOffset = 0;
273
+ this.#rebuild();
231
274
  this.#requestRender();
232
275
  return;
233
276
  }
@@ -243,6 +286,7 @@ export class TranscriptViewerOverlay extends Container {
243
286
  const entry = this.#entries[this.#selected];
244
287
  if (entry?.rawViewable !== false) {
245
288
  this.#raw.has(entry.id) ? this.#raw.delete(entry.id) : this.#raw.add(entry.id);
289
+ this.#rebuild();
246
290
  this.#requestRender();
247
291
  }
248
292
  }
@@ -284,6 +328,7 @@ export class TranscriptViewerOverlay extends Container {
284
328
  this.#followTailActive = false;
285
329
  this.#followTailPending = false;
286
330
  this.#selected = Math.max(0, Math.min(this.#selected + direction * 5, this.#entries.length - 1));
331
+ this.#rebuild();
287
332
  this.#scrollOffset += direction * PAGE_SIZE;
288
333
  this.#requestRender();
289
334
  }
@@ -327,32 +372,58 @@ export class TranscriptViewerOverlay extends Container {
327
372
  this.#options.requestRender?.();
328
373
  }
329
374
  #rebuild(): void {
375
+ if (__transcriptViewerPerfCounters.enabled) __transcriptViewerPerfCounters.rebuildRuns++;
376
+ // Theme is tracked by reference outside the variant key; a reference change clears
377
+ // the whole cache so plain and markdown layouts pick up new markdown colors.
378
+ const currentMdTheme = getMarkdownTheme();
379
+ if (currentMdTheme !== this.#mdTheme) {
380
+ this.#mdTheme = currentMdTheme;
381
+ this.#layoutCache.clear();
382
+ }
330
383
  const lines: string[] = [];
331
384
  this.#renderedEntries = [];
332
385
  const display = this.#fullscreen ? this.#entries.slice(this.#selected, this.#selected + 1) : this.#entries;
333
386
  const contentWidth = Math.max(1, this.#width - INDENT.length - 1);
334
387
  for (const entry of display) {
335
388
  const start = lines.length;
336
- const selected = entry.id === this.selectedEntryId;
337
- const expanded = this.#fullscreen || this.#expanded.has(entry.id);
338
- const raw = this.#raw.has(entry.id);
339
- lines.push("");
340
- lines.push(
341
- `${selected ? theme.fg("accent", "▶") : " "} ${theme.fg("muted", `[${sanitizeText(entry.label ?? entry.kind)}]`)}`,
342
- );
343
- if (
344
- !raw &&
345
- expanded &&
346
- selected &&
347
- entry.kind === "tool" &&
348
- entry.richRenderEligible === true &&
349
- entry.renderDescriptor
350
- ) {
351
- for (const line of renderToolDisplayLines(entry.renderDescriptor, contentWidth, theme))
352
- lines.push(`${INDENT}${line}`);
353
- this.#renderedEntries.push({ lineStart: start, lineCount: lines.length - start });
354
- continue;
389
+ const entryLines = this.#renderEntryLines(entry, contentWidth);
390
+ lines.push(...entryLines);
391
+ // Cumulative positions are recomputed from cached line lengths on every rebuild
392
+ // so PR2c can later separate virtual coordinates from materialized lines.
393
+ this.#renderedEntries.push({ lineStart: start, lineCount: entryLines.length });
394
+ }
395
+ this.#lines = lines.length ? lines : [theme.fg("dim", "No transcript entries yet.")];
396
+ }
397
+ #renderEntryLines(entry: TranscriptViewerEntry, contentWidth: number): string[] {
398
+ const selected = entry.id === this.selectedEntryId;
399
+ const expanded = this.#fullscreen || this.#expanded.has(entry.id);
400
+ const raw = this.#raw.has(entry.id);
401
+ const variantKey = `${expanded ? 1 : 0}|${raw ? 1 : 0}|${selected ? 1 : 0}|${contentWidth}`;
402
+ let variants = this.#layoutCache.get(entry.id);
403
+ if (variants) {
404
+ const cached = variants.get(variantKey);
405
+ if (cached) {
406
+ if (__transcriptViewerPerfCounters.enabled) __transcriptViewerPerfCounters.layoutCacheHits++;
407
+ return cached;
355
408
  }
409
+ }
410
+ if (__transcriptViewerPerfCounters.enabled) __transcriptViewerPerfCounters.layoutCacheMisses++;
411
+ const lines: string[] = [];
412
+ lines.push("");
413
+ lines.push(
414
+ `${selected ? theme.fg("accent", "▶") : " "} ${theme.fg("muted", `[${sanitizeText(entry.label ?? entry.kind)}]`)}`,
415
+ );
416
+ if (
417
+ !raw &&
418
+ expanded &&
419
+ selected &&
420
+ entry.kind === "tool" &&
421
+ entry.richRenderEligible === true &&
422
+ entry.renderDescriptor
423
+ ) {
424
+ for (const line of renderToolDisplayLines(entry.renderDescriptor, contentWidth, theme))
425
+ lines.push(`${INDENT}${line}`);
426
+ } else {
356
427
  const text = sanitizeText(
357
428
  (raw
358
429
  ? entry.payload.text
@@ -375,9 +446,13 @@ export class TranscriptViewerOverlay extends Container {
375
446
  for (const line of preview) lines.push(`${INDENT}${truncateToWidth(line, contentWidth)}`);
376
447
  if (text.split("\n").length > PREVIEW_LINES) lines.push(`${INDENT}${theme.fg("dim", "... more (Space)")}`);
377
448
  }
378
- this.#renderedEntries.push({ lineStart: start, lineCount: lines.length - start });
379
449
  }
380
- this.#lines = lines.length ? lines : [theme.fg("dim", "No transcript entries yet.")];
450
+ if (!variants) {
451
+ variants = new Map();
452
+ this.#layoutCache.set(entry.id, variants);
453
+ }
454
+ variants.set(variantKey, lines);
455
+ return lines;
381
456
  }
382
457
  #markdown(text: string, width: number): string[] {
383
458
  return new Markdown(text, 0, 0, this.#mdTheme).render(width).map(line => `${INDENT}${line.trimEnd()}`);
@@ -1175,6 +1175,10 @@ export class CommandController {
1175
1175
  }
1176
1176
  this.ctx.showError(`Bash command failed: ${error instanceof Error ? error.message : "Unknown error"}`);
1177
1177
  }
1178
+ const bashComponent = this.ctx.bashComponent;
1179
+ if (isDeferred && bashComponent && this.ctx.pendingBashComponents.includes(bashComponent)) {
1180
+ this.ctx.pendingMessagesContainer.detachChild(bashComponent);
1181
+ }
1178
1182
 
1179
1183
  this.ctx.bashComponent = undefined;
1180
1184
  this.ctx.ui.requestRender();
@@ -12,7 +12,7 @@ import {
12
12
  readArgsTargetInternalUrl,
13
13
  } from "../../modes/components/read-tool-group";
14
14
  import { TodoReminderComponent } from "../../modes/components/todo-reminder";
15
- import { ToolExecutionComponent } from "../../modes/components/tool-execution";
15
+ import { ToolExecutionComponent, type ToolExecutionHandle } from "../../modes/components/tool-execution";
16
16
  import { TtsrNotificationComponent } from "../../modes/components/ttsr-notification";
17
17
  import { getSymbolTheme, theme } from "../../modes/theme/theme";
18
18
  import type { InteractiveModeContext, TodoPhase } from "../../modes/types";
@@ -23,6 +23,7 @@ import type { AgentSessionEvent } from "../../session/agent-session";
23
23
  import { type CustomMessage, isSilentAbort, readPendingDisplayTag } from "../../session/messages";
24
24
  import { transferSessionMessageIdentity } from "../../session/session-manager";
25
25
  import type { ResolveToolDetails } from "../../tools/resolve";
26
+ import { computeIrcSplitWidths, getIrcSidebarSemanticToken } from "../components/irc-sidebar";
26
27
  import type { IrcObservationRecord } from "../irc-observation-ledger";
27
28
  import { interruptHint } from "../shared";
28
29
  import { buildAbortDisplayMessage } from "../utils/abort-message";
@@ -36,7 +37,14 @@ type AgentSessionEventKind = AgentSessionEvent["type"];
36
37
 
37
38
  /** Test-only performance counters for advisory baseline tests. */
38
39
  export const __eventControllerPerfCounters = {
40
+ enabled: false,
39
41
  messageUpdateContentVisits: 0,
42
+ enable(): void {
43
+ this.enabled = true;
44
+ },
45
+ disable(): void {
46
+ this.enabled = false;
47
+ },
40
48
  reset(): void {
41
49
  this.messageUpdateContentVisits = 0;
42
50
  },
@@ -118,6 +126,9 @@ export class EventController {
118
126
  #toolIntentCache = new Map<string, { args: unknown; intent: string | undefined }>();
119
127
  #thinkingContentIndices = new Set<number>();
120
128
  #handlers: AgentSessionEventHandlers;
129
+ #visibleTranscriptChanged = false;
130
+ #handlingEvent = false;
131
+ #eventQueue: Promise<void> = Promise.resolve();
121
132
 
122
133
  constructor(private ctx: InteractiveModeContext) {
123
134
  this.#handlers = {
@@ -162,6 +173,26 @@ export class EventController {
162
173
  this.clearIrcExpiryTimers();
163
174
  }
164
175
 
176
+ #recordVisibleTranscriptMutation(): void {
177
+ this.#visibleTranscriptChanged = true;
178
+ }
179
+
180
+ #observeVisibleTranscriptMutation(): void {
181
+ if (this.#handlingEvent) {
182
+ this.#recordVisibleTranscriptMutation();
183
+ return;
184
+ }
185
+ this.ctx.recordVisibleTranscriptMutation?.();
186
+ }
187
+
188
+ #consumeToolVisibleChange(handle: ToolExecutionHandle): void {
189
+ // Structural third-party renderers may not implement the optional exact-consume
190
+ // capability; preserve revision correctness conservatively until they opt in.
191
+ if (typeof handle.consumeVisibleTranscriptChange !== "function" || handle.consumeVisibleTranscriptChange()) {
192
+ this.#recordVisibleTranscriptMutation();
193
+ }
194
+ }
195
+
165
196
  #resetReadGroup(): void {
166
197
  this.#lastReadGroup = undefined;
167
198
  }
@@ -228,17 +259,26 @@ export class EventController {
228
259
  });
229
260
  }
230
261
 
231
- async handleEvent(event: AgentSessionEvent): Promise<void> {
232
- if (!this.ctx.isInitialized) {
233
- await this.ctx.init();
234
- }
235
-
236
- this.ctx.statusLine.invalidate();
237
- this.ctx.updateEditorTopBorder();
262
+ handleEvent(event: AgentSessionEvent): Promise<void> {
263
+ const dispatch = this.#eventQueue.then(() => this.#dispatchEvent(event));
264
+ this.#eventQueue = dispatch.catch(() => {});
265
+ return dispatch;
266
+ }
238
267
 
239
- const run = this.#handlers[event.type] as (e: AgentSessionEvent) => Promise<void>;
240
- await run(event);
241
- if (this.ctx.isTranscriptViewerOpen?.()) this.ctx.refreshTranscriptViewer?.();
268
+ async #dispatchEvent(event: AgentSessionEvent): Promise<void> {
269
+ if (!this.ctx.isInitialized) await this.ctx.init();
270
+ this.#visibleTranscriptChanged = false;
271
+ this.#handlingEvent = true;
272
+ try {
273
+ this.ctx.statusLine.invalidate();
274
+ this.ctx.updateEditorTopBorder();
275
+ const run = this.#handlers[event.type] as (e: AgentSessionEvent) => Promise<void>;
276
+ await run(event);
277
+ if (this.#visibleTranscriptChanged) this.ctx.recordVisibleTranscriptMutation?.();
278
+ if (this.ctx.isTranscriptViewerOpen?.()) this.ctx.refreshTranscriptViewer?.();
279
+ } finally {
280
+ this.#handlingEvent = false;
281
+ }
242
282
  }
243
283
 
244
284
  async #handleAgentStart(_event: Extract<AgentSessionEvent, { type: "agent_start" }>): Promise<void> {
@@ -268,8 +308,12 @@ export class EventController {
268
308
  const parsed = parseIrcMessage(message);
269
309
  if (!parsed) return;
270
310
  const arrival = this.ctx.captureIrcArrivalSnapshot();
311
+ const rightWidth = arrival.panelVisible
312
+ ? computeIrcSplitWidths(this.ctx.ui.terminal?.columns ?? 0).rightWidth
313
+ : 0;
314
+ const beforeToken = rightWidth > 0 ? getIrcSidebarSemanticToken(this.ctx.ircLedger, rightWidth) : "";
271
315
  const record = this.ctx.ircLedger.observe(parsed, arrival.panelVisible);
272
- this.#cleanupEvictedIrcObservations();
316
+ const evictedInlineRemoved = this.#cleanupIrcObservationIds(this.ctx.ircLedger.drainEvictedObservationIds());
273
317
  if (!record) return;
274
318
  const signature = `irc:${record.observationId}`;
275
319
  if (this.#renderedCustomMessages.has(signature)) return;
@@ -279,9 +323,13 @@ export class EventController {
279
323
  this.#renderedIrcComponents.set(record.observationId, components);
280
324
  this.#scheduleIrcExpiry(record, components);
281
325
  this.ctx.ui.requestRender();
326
+ const afterToken = rightWidth > 0 ? getIrcSidebarSemanticToken(this.ctx.ircLedger, rightWidth) : "";
327
+ if (components.length > 0 || evictedInlineRemoved || (rightWidth > 0 && beforeToken !== afterToken)) {
328
+ this.#recordVisibleTranscriptMutation();
329
+ }
282
330
  }
283
331
 
284
- #cleanupIrcObservationIds(observationIds: readonly string[]): void {
332
+ #cleanupIrcObservationIds(observationIds: readonly string[]): boolean {
285
333
  const removedComponents = new Set<Component>();
286
334
  for (const observationId of observationIds) {
287
335
  const timer = this.#ircExpiryTimers.get(observationId);
@@ -294,23 +342,26 @@ export class EventController {
294
342
  for (const component of this.ctx.removeRenderedIrcInlineComponents(observationId) ?? []) {
295
343
  removedComponents.add(component);
296
344
  }
297
-
298
345
  this.#renderedCustomMessages.delete(`irc:${observationId}`);
299
346
  }
300
347
  for (const component of removedComponents) this.ctx.chatContainer.removeChild(component);
348
+ return removedComponents.size > 0;
301
349
  }
302
350
 
303
351
  #cleanupEvictedIrcObservations(): void {
304
- this.#cleanupIrcObservationIds(this.ctx.ircLedger.drainEvictedObservationIds());
352
+ if (this.#cleanupIrcObservationIds(this.ctx.ircLedger.drainEvictedObservationIds())) {
353
+ this.#recordVisibleTranscriptMutation();
354
+ }
305
355
  }
306
356
 
307
- #cleanupExpiredIrcInlineComponents(observationId: string): void {
357
+ #cleanupExpiredIrcInlineComponents(observationId: string): boolean {
308
358
  const removedComponents = new Set<Component>(this.#renderedIrcComponents.get(observationId));
309
359
  this.#renderedIrcComponents.delete(observationId);
310
360
  for (const component of this.ctx.removeRenderedIrcInlineComponents(observationId) ?? []) {
311
361
  removedComponents.add(component);
312
362
  }
313
363
  for (const component of removedComponents) this.ctx.chatContainer.removeChild(component);
364
+ return removedComponents.size > 0;
314
365
  }
315
366
 
316
367
  async #handleMessageStart(event: Extract<AgentSessionEvent, { type: "message_start" }>): Promise<void> {
@@ -326,6 +377,7 @@ export class EventController {
326
377
  this.#renderedCustomMessages.add(signature);
327
378
  this.#resetReadGroup();
328
379
  this.ctx.addMessageToChat(event.message);
380
+ if (event.message.display) this.#recordVisibleTranscriptMutation();
329
381
  // Tag-keyed pending-bar refresh: when AgentSession.#handleAgentEvent
330
382
  // spliced this dequeued custom message out of #steeringMessages /
331
383
  // #followUpMessages (it ran before this emit), the array state is
@@ -373,10 +425,12 @@ export class EventController {
373
425
  this.ctx.updatePendingMessagesDisplay();
374
426
  }
375
427
  this.ctx.ui.requestRender();
428
+ if (!wasOptimistic) this.#recordVisibleTranscriptMutation();
376
429
  } else if (event.message.role === "fileMention") {
377
430
  this.#resetReadGroup();
378
431
  this.ctx.addMessageToChat(event.message);
379
432
  this.ctx.ui.requestRender();
433
+ this.#recordVisibleTranscriptMutation();
380
434
  } else if (event.message.role === "assistant") {
381
435
  this.#lastThinkingCount = 0;
382
436
  this.#resetReadGroup();
@@ -387,6 +441,7 @@ export class EventController {
387
441
  this.ctx.hideThinkingBlock,
388
442
  () => this.ctx.ui.requestRender(),
389
443
  this.ctx.getAssistantViewportAnchorId?.(event.message),
444
+ () => this.#observeVisibleTranscriptMutation(),
390
445
  );
391
446
  this.ctx.streamingMessage = event.message;
392
447
  addChatChild(this.ctx, this.ctx.streamingComponent);
@@ -429,15 +484,10 @@ export class EventController {
429
484
  this.#cleanupEvictedIrcObservations();
430
485
  const projectedObservationIds = new Set(inlineProjection.map(record => record.observationId));
431
486
  for (const [observationId, components] of componentsByObservationId) {
432
- const record = this.ctx.ircLedger.getRecord(observationId);
433
- if (
434
- !projectedObservationIds.has(observationId) ||
435
- (record?.mode === "ephemeral" && record.expiresAt! - now <= 0)
436
- ) {
487
+ if (!projectedObservationIds.has(observationId)) {
437
488
  for (const component of components) {
438
489
  this.ctx.chatContainer.removeChild(component);
439
490
  }
440
- this.#renderedIrcComponents.delete(observationId);
441
491
  componentsByObservationId.delete(observationId);
442
492
  }
443
493
  }
@@ -468,12 +518,12 @@ export class EventController {
468
518
  }
469
519
  const remainingMs = record.expiresAt! - Date.now();
470
520
  if (remainingMs <= 0) {
471
- this.#cleanupExpiredIrcInlineComponents(record.observationId);
521
+ if (this.#cleanupExpiredIrcInlineComponents(record.observationId)) this.#observeVisibleTranscriptMutation();
472
522
  return false;
473
523
  }
474
524
  const timer = setTimeout(() => {
475
525
  this.#ircExpiryTimers.delete(record.observationId);
476
- this.#cleanupExpiredIrcInlineComponents(record.observationId);
526
+ if (this.#cleanupExpiredIrcInlineComponents(record.observationId)) this.#observeVisibleTranscriptMutation();
477
527
  this.ctx.ui.requestRender();
478
528
  }, remainingMs);
479
529
  timer.unref?.();
@@ -496,6 +546,7 @@ export class EventController {
496
546
  this.#resetReadGroup();
497
547
  this.ctx.addMessageToChat(event.message);
498
548
  this.ctx.ui.requestRender();
549
+ this.#recordVisibleTranscriptMutation();
499
550
  }
500
551
 
501
552
  async #handleModelFallbackSwitched(
@@ -549,19 +600,24 @@ export class EventController {
549
600
  }
550
601
 
551
602
  for (const content of contentsToProcess) {
552
- __eventControllerPerfCounters.messageUpdateContentVisits += 1;
603
+ if (__eventControllerPerfCounters.enabled) __eventControllerPerfCounters.messageUpdateContentVisits += 1;
553
604
  if (content.type !== "toolCall") continue;
554
605
  if (content.name === "read") {
555
606
  if (!readArgsHaveTarget(content.arguments)) continue;
556
607
  if (!readArgsTargetInternalUrl(content.arguments)) {
557
608
  this.#trackReadToolCall(content.id, content.arguments);
558
609
  const component = this.ctx.pendingTools.get(content.id);
559
- if (component) component.updateArgs(content.arguments, content.id);
560
- else {
610
+ if (component) {
611
+ component.updateArgs(content.arguments, content.id);
612
+ this.#consumeToolVisibleChange(component);
613
+ } else {
561
614
  const group = this.#getReadGroup();
562
615
  group.updateArgs(content.arguments, content.id);
563
616
  this.ctx.pendingTools.set(content.id, group);
617
+ this.#recordVisibleTranscriptMutation();
618
+ group.consumeVisibleTranscriptChange();
564
619
  }
620
+
565
621
  continue;
566
622
  }
567
623
  }
@@ -582,6 +638,7 @@ export class EventController {
582
638
  editFuzzyThreshold: settings.get("edit.fuzzyThreshold"),
583
639
  editAllowFuzzy: settings.get("edit.fuzzyMatch"),
584
640
  hashlineAutoDropPureInsertDuplicates: settings.get("edit.hashlineAutoDropPureInsertDuplicates"),
641
+ onVisibleTranscriptMutation: () => this.#observeVisibleTranscriptMutation(),
585
642
  },
586
643
  tool,
587
644
  this.ctx.ui,
@@ -591,8 +648,10 @@ export class EventController {
591
648
  component.setExpanded(this.ctx.toolOutputExpanded);
592
649
  this.ctx.pendingTools.set(content.id, component);
593
650
  addChatChild(this.ctx, component);
651
+ this.#recordVisibleTranscriptMutation();
594
652
  } else {
595
653
  this.ctx.pendingTools.get(content.id)?.updateArgs(renderArgs, content.id);
654
+ this.#consumeToolVisibleChange(this.ctx.pendingTools.get(content.id)!);
596
655
  }
597
656
 
598
657
  const args = content.arguments;
@@ -659,6 +718,7 @@ export class EventController {
659
718
  if (this.ctx.streamingMessage.stopReason !== "aborted" && this.ctx.streamingMessage.stopReason !== "error") {
660
719
  for (const [toolCallId, component] of this.ctx.pendingTools.entries()) {
661
720
  component.setArgsComplete(toolCallId);
721
+ this.#consumeToolVisibleChange(component);
662
722
  }
663
723
  }
664
724
  this.#lastAssistantComponent = this.ctx.streamingComponent;
@@ -685,6 +745,8 @@ export class EventController {
685
745
  this.ctx.pendingTools.set(event.toolCallId, group);
686
746
  }
687
747
  this.ctx.ui.requestRender();
748
+ this.#consumeToolVisibleChange(this.ctx.pendingTools.get(event.toolCallId)!);
749
+
688
750
  return;
689
751
  }
690
752
 
@@ -698,6 +760,7 @@ export class EventController {
698
760
  editFuzzyThreshold: settings.get("edit.fuzzyThreshold"),
699
761
  editAllowFuzzy: settings.get("edit.fuzzyMatch"),
700
762
  hashlineAutoDropPureInsertDuplicates: settings.get("edit.hashlineAutoDropPureInsertDuplicates"),
763
+ onVisibleTranscriptMutation: () => this.#observeVisibleTranscriptMutation(),
701
764
  },
702
765
  tool,
703
766
  this.ctx.ui,
@@ -707,6 +770,8 @@ export class EventController {
707
770
  component.setExpanded(this.ctx.toolOutputExpanded);
708
771
  this.ctx.pendingTools.set(event.toolCallId, component);
709
772
  addChatChild(this.ctx, component);
773
+ this.#recordVisibleTranscriptMutation();
774
+
710
775
  this.ctx.ui.requestRender();
711
776
  }
712
777
  }
@@ -723,6 +788,8 @@ export class EventController {
723
788
  !isFinalAsyncState,
724
789
  event.toolCallId,
725
790
  );
791
+ this.#consumeToolVisibleChange(component);
792
+
726
793
  if (isFinalAsyncState) {
727
794
  this.ctx.pendingTools.delete(event.toolCallId);
728
795
  this.#backgroundToolCallIds.delete(event.toolCallId);
@@ -737,6 +804,8 @@ export class EventController {
737
804
  const component = this.ctx.pendingTools.get(event.toolCallId);
738
805
  if (component) {
739
806
  component.updateResult({ ...event.result, isError: event.isError }, false, event.toolCallId);
807
+ this.#consumeToolVisibleChange(component);
808
+
740
809
  this.ctx.pendingTools.delete(event.toolCallId);
741
810
  }
742
811
  const asyncState = (event.result.details as { async?: { state?: string } } | undefined)?.async?.state;
@@ -761,6 +830,8 @@ export class EventController {
761
830
  const asyncState = (event.result.details as { async?: { state?: string } } | undefined)?.async?.state;
762
831
  const isBackgroundRunning = asyncState === "running";
763
832
  component.updateResult({ ...event.result, isError: event.isError }, isBackgroundRunning, event.toolCallId);
833
+ this.#consumeToolVisibleChange(component);
834
+
764
835
  if (isBackgroundRunning) {
765
836
  this.#backgroundToolCallIds.add(event.toolCallId);
766
837
  } else {
@@ -776,6 +847,8 @@ export class EventController {
776
847
  const asyncState = (event.result.details as { async?: { state?: string } } | undefined)?.async?.state;
777
848
  const isBackgroundRunning = asyncState === "running";
778
849
  component.updateResult({ ...event.result, isError: event.isError }, isBackgroundRunning, event.toolCallId);
850
+ this.#consumeToolVisibleChange(component);
851
+
779
852
  if (isBackgroundRunning) {
780
853
  this.#backgroundToolCallIds.add(event.toolCallId);
781
854
  } else {
@@ -893,6 +966,12 @@ export class EventController {
893
966
  } else if (event.willRetry && !isHandoffAction) {
894
967
  this.ctx.showStatus("Context overflow maintenance completed");
895
968
  }
969
+ } else if (
970
+ event.skipped &&
971
+ event.errorMessage?.startsWith("Context overflow recovery skipped: nothing eligible to compact.") &&
972
+ !isHandoffAction
973
+ ) {
974
+ this.ctx.showStatus(event.errorMessage);
896
975
  } else if (event.errorMessage) {
897
976
  this.ctx.showWarning(event.errorMessage);
898
977
  } else if (isHandoffAction) {
@@ -999,12 +1078,15 @@ export class EventController {
999
1078
  const component = new TtsrNotificationComponent(event.rules);
1000
1079
  component.setExpanded(this.ctx.toolOutputExpanded);
1001
1080
  addChatChild(this.ctx, component);
1081
+ this.#recordVisibleTranscriptMutation();
1082
+
1002
1083
  this.ctx.ui.requestRender();
1003
1084
  }
1004
1085
 
1005
1086
  async #handleTodoReminder(event: Extract<AgentSessionEvent, { type: "todo_reminder" }>): Promise<void> {
1006
1087
  const component = new TodoReminderComponent(event.todos, event.attempt, event.maxAttempts);
1007
1088
  addChatChild(this.ctx, component);
1089
+ this.#recordVisibleTranscriptMutation();
1008
1090
  this.ctx.ui.requestRender();
1009
1091
  }
1010
1092
 
@@ -281,7 +281,24 @@ export class ExtensionUiController {
281
281
  const disabled = [...(session.settings.get("disabledExtensions") ?? [])];
282
282
  const on = input.on === true;
283
283
  const next = on ? disabled.filter(value => value !== id) : [...new Set([...disabled, id])];
284
- session.settings.set("disabledExtensions", next);
284
+ if (!session.settings.canWriteDurableConfig()) {
285
+ throw Object.assign(
286
+ new Error(
287
+ "Cannot change settings while config.yml has invalid YAML syntax. Repair config.yml and reload settings.",
288
+ ),
289
+ { code: "invalid_request" },
290
+ );
291
+ }
292
+ try {
293
+ session.settings.set("disabledExtensions", next);
294
+ } catch (error) {
295
+ if (!session.settings.canWriteDurableConfig()) {
296
+ throw Object.assign(new Error(error instanceof Error ? error.message : String(error)), {
297
+ code: "invalid_request",
298
+ });
299
+ }
300
+ throw error;
301
+ }
285
302
  return { changed: true, enabled: on };
286
303
  }
287
304
  case "session.delete":
@@ -399,7 +416,9 @@ export class ExtensionUiController {
399
416
  const contextActions: ExtensionContextActions = {
400
417
  getModel: () => this.ctx.session.model,
401
418
  isIdle: () => !this.ctx.session.isStreaming,
419
+ getActivePromptHandle: () => this.ctx.session.activePromptHandle,
402
420
  abort: () => this.ctx.session.abort(),
421
+ abortPromptAndWait: (handle, options) => this.ctx.session.abortPromptAndWait(handle, options),
403
422
  hasPendingMessages: () => this.ctx.session.queuedMessageCount > 0,
404
423
  getPendingMessageCounts: () => this.ctx.session.pendingMessageCounts,
405
424
  getTranscript: () => this.ctx.session.getTranscript(),
@@ -442,7 +461,7 @@ export class ExtensionUiController {
442
461
  }
443
462
  return false;
444
463
  },
445
- invokeSkill: (name, args) => this.ctx.session.invokeSkill(name, args),
464
+ invokeSkill: (name, args, options) => this.ctx.session.invokeSkill(name, args, options),
446
465
  setPlanMode: on => this.ctx.session.setSdkPlanMode(on),
447
466
  operateGoal: (op, objective) => this.ctx.session.operateGoal(op, objective),
448
467
  getSkillState: () =>
@@ -451,6 +470,7 @@ export class ExtensionUiController {
451
470
  getBranchCandidates: () => this.ctx.sessionManager.getTree(),
452
471
  getExtensions: () => this.ctx.session.extensionRunner?.getExtensionPaths() ?? [],
453
472
  setSdkPermissionProvider: provider => this.ctx.session.setSdkPermissionProvider(provider),
473
+ setSdkClientBridge: bridge => this.ctx.session.setClientBridge(bridge),
454
474
  sdkControl: this.#sdkControl,
455
475
  };
456
476
  const commandActions: ExtensionCommandContextActions = {
@@ -698,7 +718,9 @@ export class ExtensionUiController {
698
718
  const contextActions: ExtensionContextActions = {
699
719
  getModel: () => this.ctx.session.model,
700
720
  isIdle: () => !this.ctx.session.isStreaming,
721
+ getActivePromptHandle: () => this.ctx.session.activePromptHandle,
701
722
  abort: () => this.ctx.session.abort(),
723
+ abortPromptAndWait: (handle, options) => this.ctx.session.abortPromptAndWait(handle, options),
702
724
  hasPendingMessages: () => this.ctx.session.queuedMessageCount > 0,
703
725
  getPendingMessageCounts: () => this.ctx.session.pendingMessageCounts,
704
726
  getTranscript: () => this.ctx.session.getTranscript(),
@@ -747,6 +769,7 @@ export class ExtensionUiController {
747
769
  getBranchCandidates: () => this.ctx.sessionManager.getTree(),
748
770
  getExtensions: () => this.ctx.session.extensionRunner?.getExtensionPaths() ?? [],
749
771
  setSdkPermissionProvider: provider => this.ctx.session.setSdkPermissionProvider(provider),
772
+ setSdkClientBridge: bridge => this.ctx.session.setClientBridge(bridge),
750
773
  sdkControl: this.#sdkControl,
751
774
  };
752
775
  const commandActions: ExtensionCommandContextActions = {
@@ -918,6 +941,7 @@ export class ExtensionUiController {
918
941
  sessionManager: createReadonlySessionManager(this.ctx.session.sessionManager),
919
942
  modelRegistry: this.ctx.session.modelRegistry,
920
943
  model: this.ctx.session.model,
944
+ getActivePromptHandle: () => this.ctx.session.activePromptHandle,
921
945
  isIdle: () => !this.ctx.session.isStreaming,
922
946
  hasPendingMessages: () => this.ctx.session.queuedMessageCount > 0,
923
947
  getPendingMessageCounts: () => this.ctx.session.pendingMessageCounts,