@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
@@ -2044,9 +2044,24 @@ export class InputController {
2044
2044
  }
2045
2045
 
2046
2046
  toggleThinkingBlockVisibility(): void {
2047
- this.ctx.hideThinkingBlock = !this.ctx.hideThinkingBlock;
2048
- settings.set("hideThinkingBlock", this.ctx.hideThinkingBlock);
2049
- this.ctx.session.agent.hideThinkingSummary = this.ctx.hideThinkingBlock;
2047
+ if (!settings.canWriteDurableConfig()) {
2048
+ this.ctx.showError(
2049
+ "Cannot change settings while config.yml has invalid YAML syntax. Repair config.yml and reload settings.",
2050
+ );
2051
+ return;
2052
+ }
2053
+ const hideThinkingBlock = !this.ctx.hideThinkingBlock;
2054
+ try {
2055
+ settings.set("hideThinkingBlock", hideThinkingBlock);
2056
+ } catch (error) {
2057
+ if (!settings.canWriteDurableConfig()) {
2058
+ this.ctx.showError(error instanceof Error ? error.message : String(error));
2059
+ return;
2060
+ }
2061
+ throw error;
2062
+ }
2063
+ this.ctx.hideThinkingBlock = hideThinkingBlock;
2064
+ this.ctx.session.setThinkingVisibility(hideThinkingBlock ? "hidden" : "visible");
2050
2065
 
2051
2066
  // Rebuild chat from session messages
2052
2067
  // Detach the live streaming component before the disposing clear() so the
@@ -30,6 +30,7 @@ import {
30
30
  getPluginsCacheDir,
31
31
  MarketplaceManager,
32
32
  } from "../../extensibility/plugins/marketplace";
33
+ import { INTERACTIVE_SELECTOR_RESUME_ORIGIN } from "../../extensibility/shared-events";
33
34
  import {
34
35
  getAvailableThemes,
35
36
  getCurrentThemeName,
@@ -129,6 +130,7 @@ import { CustomProviderWizardComponent, type CustomProviderWizardSubmit } from "
129
130
  import { ExtensionDashboard } from "../components/extensions";
130
131
  import type { PetMode } from "../components/gajae-pet-widget";
131
132
  import { HistorySearchComponent } from "../components/history-search";
133
+ import { HookSelectorComponent } from "../components/hook-selector";
132
134
  import { JobsOverlayComponent } from "../components/jobs-overlay";
133
135
  import { ModelSelectorComponent } from "../components/model-selector";
134
136
  import type {
@@ -165,6 +167,7 @@ import type { JobsObserver } from "../jobs-observer";
165
167
  import type { SessionObserverRegistry } from "../session-observer-registry";
166
168
  import type { TasksAggregator } from "../tasks-aggregator";
167
169
  import type { TranscriptItemRegistry } from "../transcript-item-registry";
170
+ import { acquireResumeProgressLease } from "../utils/ui-helpers";
168
171
 
169
172
  const CALLBACK_SERVER_PROVIDERS = new Set<string>([
170
173
  "anthropic",
@@ -349,6 +352,7 @@ export function createNotificationsEditorOperations(
349
352
  richDraftEnabled: config.richDraft.enabled,
350
353
  toolActivityEnabled: config.toolActivity.enabled,
351
354
  streamingEnabled: config.streaming.enabled,
355
+ sound: config.sound,
352
356
  },
353
357
  };
354
358
  },
@@ -722,6 +726,7 @@ export function createNotificationsEditorOperations(
722
726
  { path: "notifications.redact", op: "set", value: preferences.redact },
723
727
  { path: "notifications.verbosity", op: "set", value: preferences.verbosity },
724
728
  { path: "notifications.sessionScope", op: "set", value: preferences.sessionScope },
729
+ { path: "notifications.telegram.sound", op: "set", value: preferences.sound },
725
730
  { path: "notifications.telegram.rich.enabled", op: "set", value: preferences.richEnabled },
726
731
  { path: "notifications.telegram.richDraft.enabled", op: "set", value: preferences.richDraftEnabled },
727
732
  { path: "notifications.telegram.streaming.enabled", op: "set", value: preferences.streamingEnabled },
@@ -1151,6 +1156,12 @@ export class SelectorController {
1151
1156
  );
1152
1157
  if (scope === undefined) return;
1153
1158
  const persistDefault = scope.trim().toLowerCase() === "default";
1159
+ if (persistDefault && !this.ctx.settings.canWriteDurableConfig()) {
1160
+ this.ctx.showError(
1161
+ "Cannot change settings while config.yml has invalid YAML syntax. Repair config.yml and reload settings.",
1162
+ );
1163
+ return;
1164
+ }
1154
1165
 
1155
1166
  const imageProvider = normalized as
1156
1167
  | "auto"
@@ -1224,13 +1235,18 @@ export class SelectorController {
1224
1235
  const selector = new ThinkingSelectorComponent(
1225
1236
  this.ctx.session.thinkingLevel,
1226
1237
  availableLevels,
1227
- selection => {
1228
- done();
1229
-
1238
+ async selection => {
1230
1239
  const { level, persistDefault } = selection;
1231
1240
  const configuredDefault = this.ctx.settings.get("defaultThinkingLevel");
1232
1241
  const levelToApply = level === ThinkingLevel.Inherit ? configuredDefault : level;
1233
- this.ctx.session.setThinkingLevel(levelToApply, persistDefault);
1242
+ try {
1243
+ await this.ctx.session.setThinkingLevelForControl(level, persistDefault);
1244
+ } catch (error) {
1245
+ this.ctx.showError(error instanceof Error ? error.message : String(error));
1246
+ return;
1247
+ }
1248
+ done();
1249
+
1234
1250
  const effectiveLevel = this.ctx.session.thinkingLevel ?? ThinkingLevel.Off;
1235
1251
  const requestedLabel =
1236
1252
  level === ThinkingLevel.Inherit ? `${level} (configured default: ${configuredDefault})` : level;
@@ -1267,9 +1283,12 @@ export class SelectorController {
1267
1283
  availableThemes,
1268
1284
  availableModelProfiles: [...this.ctx.session.modelRegistry.getModelProfiles().keys()],
1269
1285
  cwd: getProjectDir(),
1286
+ gjcRuntimeSnapshot: this.ctx.session.gjcRuntimeSnapshot,
1287
+ gjcActivationGeneration: this.ctx.session.gjcActivationGeneration,
1270
1288
  },
1271
1289
  {
1272
1290
  onChange: (id, value) => this.handleSettingChange(id, value),
1291
+ onError: message => this.ctx.showError(message),
1273
1292
  onThemePreview: themeName => {
1274
1293
  return previewTheme(themeName).then(result => {
1275
1294
  if (!result.success && result.error && !isThemePreviewSuperseded(result)) {
@@ -1289,6 +1308,7 @@ export class SelectorController {
1289
1308
  onPetPreview: mode => {
1290
1309
  this.ctx.previewPetMode(mode as PetMode);
1291
1310
  },
1311
+ onPetCommit: mode => this.ctx.commitPetPreviewMode(mode as PetMode),
1292
1312
  onStatusLinePreview: previewSettings => {
1293
1313
  // Update status line with preview settings
1294
1314
  this.ctx.statusLine.updateSettings({
@@ -1332,24 +1352,40 @@ export class SelectorController {
1332
1352
  getAvailableThemes().then(availableThemes => {
1333
1353
  const initialTheme = getCurrentThemeName() ?? "red-claw";
1334
1354
  this.showSelector(done => {
1355
+ const restoreAndClose = () => {
1356
+ void restoreThemePreview(initialTheme).then(result => {
1357
+ if (!result.success && result.error) {
1358
+ this.ctx.showError(`Failed to restore theme preview: ${result.error}`);
1359
+ }
1360
+ this.#refreshThemeUi();
1361
+ });
1362
+ done();
1363
+ };
1335
1364
  const selector = new ThemeSelectorComponent(
1336
1365
  initialTheme,
1337
1366
  availableThemes,
1338
1367
  themeName => {
1339
- const settingPath = getDetectedThemeSettingsPath();
1340
- settings.set(settingPath, themeName);
1341
- this.#refreshThemeUi();
1342
- done();
1343
- },
1344
- () => {
1345
- void restoreThemePreview(initialTheme).then(result => {
1346
- if (!result.success && result.error) {
1347
- this.ctx.showError(`Failed to restore theme preview: ${result.error}`);
1368
+ if (!settings.canWriteDurableConfig()) {
1369
+ this.ctx.showError(
1370
+ "Cannot change settings while config.yml has invalid YAML syntax. Repair config.yml and reload settings.",
1371
+ );
1372
+ restoreAndClose();
1373
+ return;
1374
+ }
1375
+ try {
1376
+ settings.set(getDetectedThemeSettingsPath(), themeName);
1377
+ } catch (error) {
1378
+ if (!settings.canWriteDurableConfig()) {
1379
+ this.ctx.showError(error instanceof Error ? error.message : String(error));
1380
+ restoreAndClose();
1381
+ return;
1348
1382
  }
1349
- this.#refreshThemeUi();
1350
- });
1383
+ throw error;
1384
+ }
1385
+ this.#refreshThemeUi();
1351
1386
  done();
1352
1387
  },
1388
+ restoreAndClose,
1353
1389
  themeName => {
1354
1390
  void previewTheme(themeName).then(result => {
1355
1391
  if (!result.success && result.error) {
@@ -1373,8 +1409,9 @@ export class SelectorController {
1373
1409
  const selector = new PetSelectorComponent(
1374
1410
  initial,
1375
1411
  mode => {
1376
- this.ctx.setPetMode(mode);
1377
- done();
1412
+ if (this.ctx.setPetMode(mode)) {
1413
+ done();
1414
+ }
1378
1415
  },
1379
1416
  () => {
1380
1417
  this.ctx.previewPetMode(initial);
@@ -2262,28 +2299,82 @@ export class SelectorController {
2262
2299
  async handleResumeSession(sessionPath: string): Promise<void> {
2263
2300
  const previousSessionId = this.ctx.sessionManager.getSessionId();
2264
2301
  this.#clearTransientSessionUi();
2265
- const migrationPolicy =
2266
- this.ctx.settings?.get("session.directoryMigration") === "disabled" ? "disabled" : "copy-retain";
2267
- let writableSessionPath = sessionPath;
2268
- if (this.ctx.sessionManager.isManagedDestination()) {
2269
- const inspection = await SessionManager.inspectSessionTailReadOnly(sessionPath);
2270
- if (inspection.kind === "error") throw new Error(`Could not inspect selected session: ${inspection.reason}`);
2271
- writableSessionPath = await this.ctx.sessionManager.prepareManagedCandidateForStrictAdoption(
2272
- sessionPath,
2273
- migrationPolicy,
2274
- inspection.identity,
2302
+ const progressLease = acquireResumeProgressLease(this.ctx);
2303
+ try {
2304
+ await progressLease.committed;
2305
+ const migrationPolicy =
2306
+ this.ctx.settings?.get("session.directoryMigration") === "disabled" ? "disabled" : "copy-retain";
2307
+ let writableSessionPath = sessionPath;
2308
+ if (this.ctx.sessionManager.isManagedDestination()) {
2309
+ const inspection = await SessionManager.inspectSessionTailReadOnly(sessionPath);
2310
+ if (inspection.kind === "error")
2311
+ throw new Error(`Could not inspect selected session: ${inspection.reason}`);
2312
+ writableSessionPath = await this.ctx.sessionManager.prepareManagedCandidateForStrictAdoption(
2313
+ sessionPath,
2314
+ migrationPolicy,
2315
+ inspection.identity,
2316
+ );
2317
+ }
2318
+ // Switch session via AgentSession (emits hook and tool session events)
2319
+ if (
2320
+ !(await this.ctx.session.switchSession(writableSessionPath, {
2321
+ transition: { origin: INTERACTIVE_SELECTOR_RESUME_ORIGIN },
2322
+ }))
2323
+ )
2324
+ return;
2325
+ const switchingToDifferentSession = previousSessionId !== this.ctx.sessionManager.getSessionId();
2326
+ if (switchingToDifferentSession) this.ctx.resetIrcSidebarSession();
2327
+ this.#refreshSessionTerminalTitle();
2328
+ this.ctx.updateEditorBorderColor();
2329
+
2330
+ this.ctx.rebuildInitialMessages(
2331
+ switchingToDifferentSession ? "replace-identity" : "reconcile-same-transcript",
2275
2332
  );
2333
+ await this.ctx.reloadTodos();
2334
+ this.ctx.showStatus("Resumed session");
2335
+ this.#maybePromptResumeModelChoice();
2336
+ } finally {
2337
+ progressLease.clear();
2276
2338
  }
2277
- // Switch session via AgentSession (emits hook and tool session events)
2278
- if (!(await this.ctx.session.switchSession(writableSessionPath))) return;
2279
- const switchingToDifferentSession = previousSessionId !== this.ctx.sessionManager.getSessionId();
2280
- if (switchingToDifferentSession) this.ctx.resetIrcSidebarSession();
2281
- this.#refreshSessionTerminalTitle();
2282
- this.ctx.updateEditorBorderColor();
2339
+ }
2283
2340
 
2284
- this.ctx.rebuildInitialMessages(switchingToDifferentSession ? "replace-identity" : "reconcile-same-transcript");
2285
- await this.ctx.reloadTodos();
2286
- this.ctx.showStatus("Resumed session");
2341
+ /**
2342
+ * When `session.resumeModelBehavior` is "ask", offer a one-shot choice after
2343
+ * resuming: keep the model the session was last using, or switch to whatever
2344
+ * `modelRoles.default` currently resolves to. No-op if the setting is unset,
2345
+ * the two models already match, or either model can't be resolved.
2346
+ */
2347
+ #maybePromptResumeModelChoice(): void {
2348
+ if (this.ctx.settings?.get("session.resumeModelBehavior") !== "ask") return;
2349
+ const sessionModel = this.ctx.session.model;
2350
+ const currentDefault = this.ctx.session.resolveConfiguredDefaultModel?.();
2351
+ if (!sessionModel || !currentDefault) return;
2352
+ if (sessionModel.provider === currentDefault.provider && sessionModel.id === currentDefault.id) return;
2353
+
2354
+ this.showSelector(done => {
2355
+ const selector = new HookSelectorComponent(
2356
+ `This session last used ${sessionModel.provider}/${sessionModel.id}.\n` +
2357
+ `Current default model is ${currentDefault.provider}/${currentDefault.id}.`,
2358
+ [`Keep ${sessionModel.id}`, `Use ${currentDefault.id}`],
2359
+ async (option: string) => {
2360
+ done();
2361
+ if (option === `Use ${currentDefault.id}`) {
2362
+ try {
2363
+ await this.ctx.session.setModel(currentDefault);
2364
+ this.ctx.showStatus(`Switched to ${currentDefault.provider}/${currentDefault.id}`);
2365
+ } catch (err) {
2366
+ this.ctx.showError(err instanceof Error ? err.message : String(err));
2367
+ }
2368
+ }
2369
+ this.ctx.ui.requestRender();
2370
+ },
2371
+ () => {
2372
+ done();
2373
+ this.ctx.ui.requestRender();
2374
+ },
2375
+ );
2376
+ return { component: selector, focus: selector };
2377
+ });
2287
2378
  }
2288
2379
 
2289
2380
  async handleSessionDeleteCommand(): Promise<void> {
@@ -61,7 +61,7 @@ import { GajaePetWidget, type PetMode } from "./components/gajae-pet-widget";
61
61
  import type { HookEditorComponent } from "./components/hook-editor";
62
62
  import type { HookInputComponent } from "./components/hook-input";
63
63
  import type { HookSelectorComponent } from "./components/hook-selector";
64
- import { IrcSplitViewComponent } from "./components/irc-sidebar";
64
+ import { computeIrcSplitWidths, getIrcSidebarSemanticToken, IrcSplitViewComponent } from "./components/irc-sidebar";
65
65
  import {
66
66
  getPetUnavailableWarning,
67
67
  isPetAvailable,
@@ -416,6 +416,9 @@ export class InteractiveMode implements InteractiveModeContext {
416
416
  #sttController: SttModeController | undefined;
417
417
  #resizeHandler?: () => void;
418
418
  #observerRegistry: SessionObserverRegistry;
419
+ #viewportOutputRevision = 0n;
420
+ #viewportOutputIdentity: string;
421
+
419
422
  #transcriptRegistry = new TranscriptItemRegistry();
420
423
 
421
424
  /** Direct controller capabilities for consumers that coordinate mode transitions. */
@@ -484,6 +487,8 @@ export class InteractiveMode implements InteractiveModeContext {
484
487
  this.mcpManager = mcpManager;
485
488
  this.#eventBus = eventBus;
486
489
  this.#keyDisplayContext = keyDisplayContext;
490
+ this.#viewportOutputIdentity = `session:${this.sessionManager.getSessionId()}`;
491
+
487
492
  this.keybindings.setDisplayContext(this.#keyDisplayContext);
488
493
  const thisMode = this;
489
494
  this.#goalModeController = new GoalModeController({
@@ -738,6 +743,11 @@ export class InteractiveMode implements InteractiveModeContext {
738
743
  this.ui.addChild(this.hookWidgetContainerBelow);
739
744
  this.ui.setBottomPinnedComponent(this.statusLine);
740
745
  this.ui.setFocus(this.editor);
746
+ this.ui.setViewportOutputSource({
747
+ identity: this.#viewportOutputIdentity,
748
+ revision: this.#viewportOutputRevision,
749
+ });
750
+
741
751
  this.petWidget?.dispose();
742
752
  this.petWidget = this.#createPetWidget(this.editor);
743
753
  const configuredPetMode = settings.get("pet.mode");
@@ -770,6 +780,12 @@ export class InteractiveMode implements InteractiveModeContext {
770
780
 
771
781
  this.#inputController.setupKeyHandlers();
772
782
  this.#inputController.setupEditorSubmitHandler();
783
+ this.editor.onViewportPageScroll = direction => {
784
+ this.ui.scrollViewportPages(direction);
785
+ };
786
+ this.editor.onViewportFollowLive = () => {
787
+ this.ui.followLiveViewport();
788
+ };
773
789
 
774
790
  // Wire observer registry to EventBus
775
791
  if (this.#eventBus) {
@@ -891,6 +907,7 @@ export class InteractiveMode implements InteractiveModeContext {
891
907
  // Set up theme file watcher
892
908
  onThemeChange(() => {
893
909
  clearRenderCache();
910
+ this.#ircSplitView.invalidateTheme();
894
911
  configureDefaultComposerChrome(this.editor);
895
912
  this.editor.setPlaceholder(this.#getComposerPlaceholder());
896
913
  this.ui.invalidate();
@@ -1132,6 +1149,7 @@ export class InteractiveMode implements InteractiveModeContext {
1132
1149
  this.statusLine,
1133
1150
  this.hookWidgetContainerAbove,
1134
1151
  this.editorContainer,
1152
+ this.petFloorContainer,
1135
1153
  this.hookWidgetContainerBelow,
1136
1154
  ].reduce((rows, component) => rows + component.render(width).length, 0);
1137
1155
 
@@ -1199,8 +1217,22 @@ export class InteractiveMode implements InteractiveModeContext {
1199
1217
  this.ui.requestRender();
1200
1218
  return false;
1201
1219
  }
1220
+ if (!settings.canWriteDurableConfig()) {
1221
+ this.showError(
1222
+ "Cannot change settings while config.yml has invalid YAML syntax. Repair config.yml and reload settings.",
1223
+ );
1224
+ return false;
1225
+ }
1226
+ try {
1227
+ settings.set("pet.mode", mode);
1228
+ } catch (error) {
1229
+ if (!settings.canWriteDurableConfig()) {
1230
+ this.showError(error instanceof Error ? error.message : String(error));
1231
+ return false;
1232
+ }
1233
+ throw error;
1234
+ }
1202
1235
  apply(mode);
1203
- settings.set("pet.mode", mode);
1204
1236
  this.ui.requestRender();
1205
1237
  return true;
1206
1238
  }
@@ -1499,6 +1531,12 @@ export class InteractiveMode implements InteractiveModeContext {
1499
1531
 
1500
1532
  this.#inputController.setupKeyHandlers();
1501
1533
  this.#inputController.setupEditorSubmitHandler();
1534
+ this.editor.onViewportPageScroll = direction => {
1535
+ this.ui.scrollViewportPages(direction);
1536
+ };
1537
+ this.editor.onViewportFollowLive = () => {
1538
+ this.ui.followLiveViewport();
1539
+ };
1502
1540
 
1503
1541
  void this.refreshSlashCommandState().catch(error => {
1504
1542
  logger.warn("Failed to refresh slash command state for custom editor", { error: String(error) });
@@ -1649,6 +1687,21 @@ export class InteractiveMode implements InteractiveModeContext {
1649
1687
  return this.#uiHelpers.isKnownSlashCommand(text);
1650
1688
  }
1651
1689
 
1690
+ /** Advances the sticky-viewport source only for semantic transcript output. */
1691
+ recordVisibleTranscriptMutation(): void {
1692
+ const identity = `session:${this.sessionManager.getSessionId()}`;
1693
+ if (identity !== this.#viewportOutputIdentity) {
1694
+ this.#viewportOutputIdentity = identity;
1695
+ this.#viewportOutputRevision = 0n;
1696
+ } else {
1697
+ this.#viewportOutputRevision += 1n;
1698
+ }
1699
+ this.ui.setViewportOutputSource({
1700
+ identity: this.#viewportOutputIdentity,
1701
+ revision: this.#viewportOutputRevision,
1702
+ });
1703
+ }
1704
+
1652
1705
  addMessageToChat(message: AgentMessage, options?: { populateHistory?: boolean }): Component[] {
1653
1706
  return this.#uiHelpers.addMessageToChat(message, options);
1654
1707
  }
@@ -2176,12 +2229,18 @@ export class InteractiveMode implements InteractiveModeContext {
2176
2229
  }
2177
2230
 
2178
2231
  resetIrcSidebarSession(): void {
2179
- this.ircLedger.reset();
2232
+ const sidebarVisible = this.#ircSplitView.effectiveSidebarVisible(this.ui.terminal.columns);
2233
+ const rightWidth = computeIrcSplitWidths(this.ui.terminal.columns).rightWidth;
2234
+ const beforeToken = sidebarVisible ? getIrcSidebarSemanticToken(this.ircLedger, rightWidth) : "";
2235
+ this.ircLedger.reset({ retireCurrentSessionIdentities: true });
2236
+ this.#ircSplitView.resetSource();
2237
+ const afterToken = sidebarVisible ? getIrcSidebarSemanticToken(this.ircLedger, rightWidth) : "";
2180
2238
  this.#eventController.resetIrcObservations();
2181
2239
  this.#ircSidebarRequestedVisible = false;
2182
2240
  this.#ircSplitView.setVisible(false);
2183
2241
  this.#uiHelpers.resetIrcSidebarHint();
2184
2242
  this.#syncIrcSidebarAvailabilityFromSettings();
2243
+ if (sidebarVisible && beforeToken !== afterToken) this.recordVisibleTranscriptMutation();
2185
2244
  }
2186
2245
 
2187
2246
  #invalidateIrcSidebarRender(): void {
@@ -4,9 +4,26 @@ import type { ParsedIrcMessage } from "./utils/irc-message";
4
4
 
5
5
  type InlineMode = "persistent" | "ephemeral";
6
6
 
7
- const MAX_RECORDS = 10_000;
8
- const MAX_RETAINED_UTF8_BYTES = 16 * 1024 * 1024;
9
- const MAX_SEEN_IDENTITIES = 100_000;
7
+ export const IRC_OBSERVATION_LEDGER_MAX_RECORDS = 10_000;
8
+ export const IRC_OBSERVATION_LEDGER_MAX_RETAINED_UTF8_BYTES = 16 * 1024 * 1024;
9
+ export const IRC_OBSERVATION_LEDGER_MAX_SEEN_IDENTITIES = 100_000;
10
+
11
+ export const __ircLedgerPerfCounters = {
12
+ enabled: false,
13
+ epochAdvances: 0,
14
+ snapshot() {
15
+ return { epochAdvances: this.epochAdvances };
16
+ },
17
+ enable(): void {
18
+ this.enabled = true;
19
+ },
20
+ disable(): void {
21
+ this.enabled = false;
22
+ },
23
+ reset(): void {
24
+ this.epochAdvances = 0;
25
+ },
26
+ };
10
27
 
11
28
  export type IrcObservationRecord = Readonly<
12
29
  ParsedIrcMessage & {
@@ -22,7 +39,7 @@ function measureRetainedUtf8Bytes(message: ParsedIrcMessage): number {
22
39
  let bytes = 4;
23
40
  for (const value of [message.observationId, message.from, message.to, message.text, message.kind]) {
24
41
  bytes += Buffer.byteLength(value, "utf8");
25
- if (bytes > MAX_RETAINED_UTF8_BYTES) return bytes;
42
+ if (bytes > IRC_OBSERVATION_LEDGER_MAX_RETAINED_UTF8_BYTES) return bytes;
26
43
  }
27
44
  return bytes;
28
45
  }
@@ -43,12 +60,28 @@ export class IrcObservationLedger {
43
60
  #nextSequence = 0;
44
61
  #seenObservationIdentities = new Set<string>();
45
62
  #identityCapacityExhausted = false;
63
+ #retiredSessionIdentities = new Set<string>();
46
64
  #evictedObservationIds = new Set<string>();
65
+ #mutationEpoch = 0;
66
+
67
+ get mutationEpoch(): number {
68
+ return this.#mutationEpoch;
69
+ }
70
+
71
+ #advanceMutationEpoch(): void {
72
+ this.#mutationEpoch++;
73
+ if (__ircLedgerPerfCounters.enabled) __ircLedgerPerfCounters.epochAdvances++;
74
+ }
47
75
 
48
76
  #rememberObservationIdentity(observationId: string): boolean {
49
77
  const identity = tombstoneIdentity(observationId);
50
- if (this.#seenObservationIdentities.has(identity) || this.#identityCapacityExhausted) return false;
51
- if (this.#seenObservationIdentities.size >= MAX_SEEN_IDENTITIES) {
78
+ if (
79
+ this.#seenObservationIdentities.has(identity) ||
80
+ this.#retiredSessionIdentities.has(identity) ||
81
+ this.#identityCapacityExhausted
82
+ )
83
+ return false;
84
+ if (this.#seenObservationIdentities.size >= IRC_OBSERVATION_LEDGER_MAX_SEEN_IDENTITIES) {
52
85
  this.#identityCapacityExhausted = true;
53
86
  return false;
54
87
  }
@@ -56,6 +89,18 @@ export class IrcObservationLedger {
56
89
  return true;
57
90
  }
58
91
 
92
+ #retireCurrentSessionIdentities(): void {
93
+ for (const identity of this.#seenObservationIdentities) {
94
+ this.#retiredSessionIdentities.delete(identity);
95
+ this.#retiredSessionIdentities.add(identity);
96
+ while (this.#retiredSessionIdentities.size > IRC_OBSERVATION_LEDGER_MAX_SEEN_IDENTITIES) {
97
+ const oldest = this.#retiredSessionIdentities.values().next().value;
98
+ if (oldest === undefined) break;
99
+ this.#retiredSessionIdentities.delete(oldest);
100
+ }
101
+ }
102
+ }
103
+
59
104
  #evict(observationId: string): void {
60
105
  const record = this.#records.get(observationId);
61
106
  if (!record) return;
@@ -65,7 +110,10 @@ export class IrcObservationLedger {
65
110
  }
66
111
 
67
112
  #enforceBounds(): void {
68
- while (this.#records.size > MAX_RECORDS || this.#retainedUtf8Bytes > MAX_RETAINED_UTF8_BYTES) {
113
+ while (
114
+ this.#records.size > IRC_OBSERVATION_LEDGER_MAX_RECORDS ||
115
+ this.#retainedUtf8Bytes > IRC_OBSERVATION_LEDGER_MAX_RETAINED_UTF8_BYTES
116
+ ) {
69
117
  const oldestObservationId = this.#records.keys().next().value;
70
118
  if (oldestObservationId === undefined) return;
71
119
  this.#evict(oldestObservationId);
@@ -76,10 +124,9 @@ export class IrcObservationLedger {
76
124
  observe(message: ParsedIrcMessage, panelVisibleAtObservation: boolean): IrcObservationRecord | undefined {
77
125
  const existing = this.#records.get(message.observationId);
78
126
  if (existing) return existing;
79
- if (!this.#rememberObservationIdentity(message.observationId)) return undefined;
80
-
81
127
  const estimatedBytes = measureRetainedUtf8Bytes(message);
82
- if (estimatedBytes > MAX_RETAINED_UTF8_BYTES) return undefined;
128
+ if (estimatedBytes > IRC_OBSERVATION_LEDGER_MAX_RETAINED_UTF8_BYTES) return undefined;
129
+ if (!this.#rememberObservationIdentity(message.observationId)) return undefined;
83
130
 
84
131
  const observedAt = Date.now();
85
132
  const mode: InlineMode = panelVisibleAtObservation ? "ephemeral" : "persistent";
@@ -94,6 +141,7 @@ export class IrcObservationLedger {
94
141
  this.#records.set(record.observationId, record);
95
142
  this.#retainedUtf8Bytes += estimatedBytes;
96
143
  this.#enforceBounds();
144
+ this.#advanceMutationEpoch();
97
145
  return this.#records.get(record.observationId);
98
146
  }
99
147
 
@@ -116,12 +164,20 @@ export class IrcObservationLedger {
116
164
  return observationIds;
117
165
  }
118
166
 
119
- reset(): void {
120
- for (const observationId of this.#records.keys()) {
121
- this.#evictedObservationIds.add(observationId);
167
+ reset(options?: { retireCurrentSessionIdentities?: boolean }): void {
168
+ const retainedRecordsChanged = this.#records.size > 0;
169
+ if (options?.retireCurrentSessionIdentities) this.#retireCurrentSessionIdentities();
170
+ else this.#retiredSessionIdentities.clear();
171
+ if (retainedRecordsChanged) {
172
+ for (const observationId of this.#records.keys()) {
173
+ this.#evictedObservationIds.add(observationId);
174
+ }
175
+ this.#records.clear();
176
+ this.#retainedUtf8Bytes = 0;
122
177
  }
123
- this.#records.clear();
124
- this.#retainedUtf8Bytes = 0;
125
178
  this.#nextSequence = 0;
179
+ this.#seenObservationIdentities.clear();
180
+ this.#identityCapacityExhausted = false;
181
+ if (retainedRecordsChanged) this.#advanceMutationEpoch();
126
182
  }
127
183
  }
@@ -94,7 +94,9 @@ export async function initializeExtensions(session: AgentSession, options: Initi
94
94
  {
95
95
  getModel: () => session.model,
96
96
  isIdle: () => !session.isStreaming,
97
+ getActivePromptHandle: () => session.activePromptHandle,
97
98
  abort: () => session.abort(),
99
+ abortPromptAndWait: (handle, abortOptions) => session.abortPromptAndWait(handle, abortOptions),
98
100
  hasPendingMessages: () => session.queuedMessageCount > 0,
99
101
  getPendingMessageCounts: () => session.pendingMessageCounts,
100
102
  getTranscript: () => session.getTranscript(),
@@ -132,7 +134,7 @@ export async function initializeExtensions(session: AgentSession, options: Initi
132
134
  }
133
135
  return false;
134
136
  },
135
- invokeSkill: (name, args) => session.invokeSkill(name, args),
137
+ invokeSkill: (name, args, options) => session.invokeSkill(name, args, options),
136
138
  setPlanMode: on => session.setSdkPlanMode(on),
137
139
  operateGoal: (op, objective) => session.operateGoal(op, objective),
138
140
  getSkillState: () => session.skills.map(skill => ({ name: skill.name, description: skill.description })),
@@ -168,6 +170,7 @@ export async function initializeExtensions(session: AgentSession, options: Initi
168
170
  },
169
171
  getJobs: () => session.getAsyncJobSnapshot(),
170
172
  setSdkPermissionProvider: provider => session.setSdkPermissionProvider(provider),
173
+ setSdkClientBridge: bridge => session.setClientBridge(bridge),
171
174
  sdkControl: async (operation, input) => {
172
175
  switch (operation) {
173
176
  case "model.set": {
@@ -380,7 +383,24 @@ export async function initializeExtensions(session: AgentSession, options: Initi
380
383
  const disabled = [...(session.settings.get("disabledExtensions") ?? [])];
381
384
  const on = input.on === true;
382
385
  const next = on ? disabled.filter(value => value !== id) : [...new Set([...disabled, id])];
383
- session.settings.set("disabledExtensions", next);
386
+ if (!session.settings.canWriteDurableConfig()) {
387
+ throw Object.assign(
388
+ new Error(
389
+ "Cannot change settings while config.yml has invalid YAML syntax. Repair config.yml and reload settings.",
390
+ ),
391
+ { code: "invalid_request" },
392
+ );
393
+ }
394
+ try {
395
+ session.settings.set("disabledExtensions", next);
396
+ } catch (error) {
397
+ if (!session.settings.canWriteDurableConfig()) {
398
+ throw Object.assign(new Error(error instanceof Error ? error.message : String(error)), {
399
+ code: "invalid_request",
400
+ });
401
+ }
402
+ throw error;
403
+ }
384
404
  return { changed: true, enabled: on };
385
405
  }
386
406
  case "session.delete":
@@ -248,6 +248,8 @@ export interface InteractiveModeContext {
248
248
  getAssistantViewportAnchorId?(message: AssistantMessage): string;
249
249
  findLastAssistantMessage(): AssistantMessage | undefined;
250
250
  extractAssistantText(message: AssistantMessage): string;
251
+ /** Records one semantic visible-transcript mutation for the sticky viewport. */
252
+ recordVisibleTranscriptMutation?(): void;
251
253
  updateEditorTopBorder(): void;
252
254
  updateEditorBorderColor(): void;
253
255
  rebuildChatFromMessages(policy: TranscriptRebuildPolicy): void;