@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
@@ -1,3 +1,4 @@
1
+ import * as crypto from "node:crypto";
1
2
  import {
2
3
  type Component,
3
4
  padding,
@@ -21,6 +22,46 @@ const IRC_SIDEBAR_MAX_RENDER_SOURCE_UTF8_BYTES = 64 * 1_024;
21
22
  const IRC_SIDEBAR_OLDER_MESSAGES_ELISION = "… older IRC messages elided …";
22
23
  const IRC_SIDEBAR_MESSAGE_ELISION = " … message elided …";
23
24
 
25
+ export const __ircSidebarPerfCounters = {
26
+ enabled: false,
27
+ projectionMemoHits: 0,
28
+ projectionMemoMisses: 0,
29
+ recordsVisited: 0,
30
+ sourceBytes: 0,
31
+ rows: 0,
32
+ styledCacheHits: 0,
33
+ styledCacheMisses: 0,
34
+ wrapCalls: 0,
35
+ snapshot() {
36
+ return {
37
+ projectionMemoHits: this.projectionMemoHits,
38
+ projectionMemoMisses: this.projectionMemoMisses,
39
+ recordsVisited: this.recordsVisited,
40
+ sourceBytes: this.sourceBytes,
41
+ rows: this.rows,
42
+ styledCacheHits: this.styledCacheHits,
43
+ styledCacheMisses: this.styledCacheMisses,
44
+ wrapCalls: this.wrapCalls,
45
+ };
46
+ },
47
+ enable(): void {
48
+ this.enabled = true;
49
+ },
50
+ disable(): void {
51
+ this.enabled = false;
52
+ },
53
+ reset(): void {
54
+ this.projectionMemoHits = 0;
55
+ this.projectionMemoMisses = 0;
56
+ this.recordsVisited = 0;
57
+ this.sourceBytes = 0;
58
+ this.rows = 0;
59
+ this.styledCacheHits = 0;
60
+ this.styledCacheMisses = 0;
61
+ this.wrapCalls = 0;
62
+ },
63
+ };
64
+
24
65
  /** Computes transcript/sidebar widths while preserving at least half the terminal for the transcript. */
25
66
  export function computeIrcSplitWidths(width: number): {
26
67
  leftWidth: number;
@@ -34,12 +75,7 @@ export function computeIrcSplitWidths(width: number): {
34
75
  0,
35
76
  Math.min(preferredSidebar, normalizedWidth - IRC_SEPARATOR_WIDTH - transcriptFloor),
36
77
  );
37
-
38
- // The sidebar yields entirely rather than rendering below its readable minimum.
39
- if (sidebarWidth < IRC_SIDEBAR_MIN_WIDTH) {
40
- return { leftWidth: normalizedWidth, separatorWidth: 0, rightWidth: 0 };
41
- }
42
-
78
+ if (sidebarWidth < IRC_SIDEBAR_MIN_WIDTH) return { leftWidth: normalizedWidth, separatorWidth: 0, rightWidth: 0 };
43
79
  return {
44
80
  leftWidth: normalizedWidth - IRC_SEPARATOR_WIDTH - sidebarWidth,
45
81
  separatorWidth: IRC_SEPARATOR_WIDTH,
@@ -47,52 +83,99 @@ export function computeIrcSplitWidths(width: number): {
47
83
  };
48
84
  }
49
85
 
50
- function styleSender(componentTheme: IrcSidebarTheme, sender: string): string {
51
- return componentTheme.fg("accent", componentTheme.bold(sender));
86
+ type SemanticLine = Readonly<{
87
+ kind: "header" | "body" | "elision" | "blank";
88
+ text: string;
89
+ sender?: string;
90
+ recipient?: string;
91
+ time?: string;
92
+ }>;
93
+ export type IrcSidebarSemanticProjection = Readonly<{ token: string; lines: readonly SemanticLine[] }>;
94
+ const projectionMemo = new WeakMap<
95
+ IrcObservationLedger,
96
+ { epoch: number; width: number; projection: IrcSidebarSemanticProjection }
97
+ >();
98
+
99
+ function semanticProjectionToken(lines: readonly SemanticLine[]): string {
100
+ const hash = crypto.createHash("sha256");
101
+ const length = Buffer.allocUnsafe(4);
102
+ const writeField = (value: string | undefined): void => {
103
+ length.writeUInt32BE(value === undefined ? 0xffffffff : Buffer.byteLength(value, "utf8"));
104
+ hash.update(length);
105
+ if (value !== undefined) hash.update(value, "utf8");
106
+ };
107
+
108
+ hash.update("gjc:irc:sidebar-semantic-projection:v1\0");
109
+ length.writeUInt32BE(lines.length);
110
+ hash.update(length);
111
+ for (const line of lines) {
112
+ writeField(line.kind);
113
+ writeField(line.text);
114
+ writeField(line.sender);
115
+ writeField(line.recipient);
116
+ writeField(line.time);
117
+ }
118
+ return hash.digest("hex");
52
119
  }
53
120
 
54
- function renderSidebarRecord(
121
+ function projectRecord(
55
122
  record: IrcObservationRecord,
56
123
  width: number,
57
- componentTheme: IrcSidebarTheme,
58
124
  maxRows: number,
59
125
  maxSourceUtf8Bytes: number,
60
- ): { lines: string[]; sourceUtf8Bytes: number; truncated: boolean } {
126
+ ): { lines: SemanticLine[]; sourceUtf8Bytes: number; truncated: boolean } {
61
127
  const bodyWidth = Math.max(1, width - 2);
62
128
  const sourceLimit = Math.min(maxSourceUtf8Bytes, Math.max(0, maxRows - 1) * bodyWidth);
63
129
  const clipped = projectIrcText(record.text, sourceLimit);
64
130
  const block = formatIrcMessageBlock({ ...record, text: clipped.text });
65
- const sender = styleSender(componentTheme, block.sender);
66
- const time = componentTheme.fg("dim", block.time);
67
- const lines = [truncateToWidth(`${sender} → ${block.recipient} · ${time}`, width)];
131
+ const lines: SemanticLine[] = [
132
+ {
133
+ kind: "header",
134
+ text: `${block.sender} → ${block.recipient} · ${block.time}`,
135
+ sender: block.sender,
136
+ recipient: block.recipient,
137
+ time: block.time,
138
+ },
139
+ ];
68
140
  let truncated = clipped.truncated;
69
-
70
141
  body: for (const bodyLine of block.bodyLines) {
142
+ if (__ircSidebarPerfCounters.enabled) __ircSidebarPerfCounters.wrapCalls++;
71
143
  for (const wrappedLine of wrapTextWithAnsi(bodyLine, bodyWidth)) {
72
144
  if (lines.length >= maxRows) {
73
145
  truncated = true;
74
146
  break body;
75
147
  }
76
- lines.push(truncateToWidth(` ${wrappedLine}`, width));
148
+ lines.push({ kind: "body", text: ` ${wrappedLine}` });
77
149
  }
78
150
  }
79
151
  if (truncated && maxRows > 1) {
80
- const marker = componentTheme.fg("dim", IRC_SIDEBAR_MESSAGE_ELISION);
81
- if (lines.length >= maxRows) lines[maxRows - 1] = truncateToWidth(marker, width);
82
- else lines.push(truncateToWidth(marker, width));
152
+ const marker: SemanticLine = { kind: "elision", text: IRC_SIDEBAR_MESSAGE_ELISION };
153
+ if (lines.length >= maxRows) lines[maxRows - 1] = marker;
154
+ else lines.push(marker);
83
155
  }
84
156
  return { lines, sourceUtf8Bytes: clipped.utf8Bytes, truncated };
85
157
  }
86
158
 
87
- function renderSidebarRecords(ledger: IrcObservationLedger, width: number, componentTheme: IrcSidebarTheme): string[] {
88
- if (width <= 0) return [];
89
-
90
- const records = ledger.getSidebarRecords();
91
- const newestFirstBlocks: string[][] = [];
159
+ /** The authoritative bounded, display-neutral IRC sidebar projection. */
160
+ export function getIrcSidebarSemanticProjection(
161
+ ledger: IrcObservationLedger,
162
+ width: number,
163
+ ): IrcSidebarSemanticProjection {
164
+ if (width <= 0) return { token: "", lines: [] };
165
+ const memo = projectionMemo.get(ledger);
166
+ const cachedProjection = memo?.epoch === ledger.mutationEpoch && memo.width === width ? memo.projection : undefined;
167
+ if (cachedProjection) {
168
+ if (__ircSidebarPerfCounters.enabled) __ircSidebarPerfCounters.projectionMemoHits++;
169
+ return cachedProjection;
170
+ }
171
+ if (__ircSidebarPerfCounters.enabled) __ircSidebarPerfCounters.projectionMemoMisses++;
172
+ const newestFirstBlocks: SemanticLine[][] = [];
92
173
  let renderedRows = 0;
93
174
  let renderedSourceUtf8Bytes = 0;
94
175
  let omittedOlderRecords = false;
176
+ const records = ledger.getSidebarRecords();
95
177
  for (let index = records.length - 1; index >= 0; index--) {
178
+ if (__ircSidebarPerfCounters.enabled) __ircSidebarPerfCounters.recordsVisited++;
96
179
  const separatorRows = newestFirstBlocks.length > 0 ? 1 : 0;
97
180
  const availableRows = IRC_SIDEBAR_MAX_RENDER_ROWS - renderedRows - separatorRows - 2;
98
181
  const availableSourceUtf8Bytes = IRC_SIDEBAR_MAX_RENDER_SOURCE_UTF8_BYTES - renderedSourceUtf8Bytes;
@@ -101,32 +184,32 @@ function renderSidebarRecords(ledger: IrcObservationLedger, width: number, compo
101
184
  omittedOlderRecords = true;
102
185
  break;
103
186
  }
104
-
105
- const rendered = renderSidebarRecord(
106
- records[index],
107
- width,
108
- componentTheme,
109
- availableRows,
110
- availableSourceUtf8Bytes,
111
- );
112
- newestFirstBlocks.push(rendered.lines);
113
- renderedRows += separatorRows + rendered.lines.length;
114
- renderedSourceUtf8Bytes += rendered.sourceUtf8Bytes;
115
- if (rendered.truncated) {
187
+ const projected = projectRecord(records[index], width, availableRows, availableSourceUtf8Bytes);
188
+ newestFirstBlocks.push(projected.lines);
189
+ renderedRows += separatorRows + projected.lines.length;
190
+ renderedSourceUtf8Bytes += projected.sourceUtf8Bytes;
191
+ if (__ircSidebarPerfCounters.enabled) __ircSidebarPerfCounters.sourceBytes += projected.sourceUtf8Bytes;
192
+ if (projected.truncated) {
116
193
  omittedOlderRecords = index > 0;
117
194
  break;
118
195
  }
119
196
  }
120
-
121
- const lines: string[] = [];
122
- if (omittedOlderRecords) {
123
- lines.push(truncateToWidth(componentTheme.fg("dim", IRC_SIDEBAR_OLDER_MESSAGES_ELISION), width));
124
- }
197
+ const lines: SemanticLine[] = omittedOlderRecords
198
+ ? [{ kind: "elision", text: IRC_SIDEBAR_OLDER_MESSAGES_ELISION }]
199
+ : [];
125
200
  for (const block of newestFirstBlocks.reverse()) {
126
- if (lines.length > 0) lines.push("");
201
+ if (lines.length > 0) lines.push({ kind: "blank", text: "" });
127
202
  lines.push(...block);
128
203
  }
129
- return lines.slice(0, IRC_SIDEBAR_MAX_RENDER_ROWS);
204
+ const bounded = lines.slice(0, IRC_SIDEBAR_MAX_RENDER_ROWS);
205
+ if (__ircSidebarPerfCounters.enabled) __ircSidebarPerfCounters.rows += bounded.length;
206
+ const projection = { token: semanticProjectionToken(bounded), lines: bounded };
207
+ projectionMemo.set(ledger, { epoch: ledger.mutationEpoch, width, projection });
208
+ return projection;
209
+ }
210
+
211
+ export function getIrcSidebarSemanticToken(ledger: IrcObservationLedger, width: number): string {
212
+ return getIrcSidebarSemanticProjection(ledger, width).token;
130
213
  }
131
214
 
132
215
  export interface IrcSidebarTheme {
@@ -134,64 +217,96 @@ export interface IrcSidebarTheme {
134
217
  bold(text: string): string;
135
218
  readonly boxSharp: { readonly vertical: string };
136
219
  }
137
-
138
220
  export type IrcSidebarThemeSource = IrcSidebarTheme | (() => IrcSidebarTheme);
139
221
 
140
222
  /** Read-only IRC history alongside the active transcript. */
141
223
  export class IrcSplitViewComponent implements ViewportAnchorProvider {
142
224
  #visible = false;
225
+ #themeGeneration = 0;
226
+ #styledMemo:
227
+ | { token: string; width: number; themeGeneration: number; theme: IrcSidebarTheme; lines: string[] }
228
+ | undefined;
143
229
 
144
230
  constructor(
145
231
  private readonly leftPane: Component,
146
232
  private readonly ledger: IrcObservationLedger,
147
233
  private readonly componentTheme: IrcSidebarThemeSource,
148
234
  ) {}
149
-
150
235
  get visible(): boolean {
151
236
  return this.#visible;
152
237
  }
153
-
154
238
  effectiveSidebarVisible(width = process.stdout.columns ?? 0): boolean {
155
239
  return this.#visible && computeIrcSplitWidths(width).rightWidth > 0;
156
240
  }
157
-
158
241
  setVisible(visible: boolean): void {
159
- if (this.#visible === visible) return;
160
- this.#visible = visible;
161
- this.invalidate();
242
+ if (this.#visible !== visible) {
243
+ this.#visible = visible;
244
+ this.invalidate();
245
+ }
246
+ }
247
+ invalidateTheme(): void {
248
+ this.#themeGeneration++;
249
+ this.#styledMemo = undefined;
250
+ }
251
+ resetSource(): void {
252
+ projectionMemo.delete(this.ledger);
253
+ this.#styledMemo = undefined;
162
254
  }
163
-
164
255
  render(width: number): string[] {
165
256
  return this.renderWithViewportAnchors(width).lines;
166
257
  }
167
-
258
+ #renderSidebar(width: number, componentTheme: IrcSidebarTheme): string[] {
259
+ const projection = getIrcSidebarSemanticProjection(this.ledger, width);
260
+ const cached = this.#styledMemo;
261
+ if (
262
+ cached?.token === projection.token &&
263
+ cached.width === width &&
264
+ cached.themeGeneration === this.#themeGeneration &&
265
+ cached.theme === componentTheme
266
+ ) {
267
+ if (__ircSidebarPerfCounters.enabled) __ircSidebarPerfCounters.styledCacheHits++;
268
+ return cached.lines;
269
+ }
270
+ if (__ircSidebarPerfCounters.enabled) __ircSidebarPerfCounters.styledCacheMisses++;
271
+ const lines = projection.lines.map(line => {
272
+ if (line.kind === "header")
273
+ return truncateToWidth(
274
+ `${componentTheme.fg("accent", componentTheme.bold(line.sender!))} → ${line.recipient} · ${componentTheme.fg("dim", line.time!)}`,
275
+ width,
276
+ );
277
+ return truncateToWidth(line.kind === "elision" ? componentTheme.fg("dim", line.text) : line.text, width);
278
+ });
279
+ this.#styledMemo = {
280
+ token: projection.token,
281
+ width,
282
+ themeGeneration: this.#themeGeneration,
283
+ theme: componentTheme,
284
+ lines,
285
+ };
286
+ return lines;
287
+ }
168
288
  renderWithViewportAnchors(width: number): ViewportAnchorRender {
169
289
  if (!this.#visible) return renderComponentWithViewportAnchors(this.leftPane, width);
170
-
171
290
  const componentTheme = typeof this.componentTheme === "function" ? this.componentTheme() : this.componentTheme;
172
291
  const { leftWidth, separatorWidth, rightWidth } = computeIrcSplitWidths(width);
173
292
  if (rightWidth === 0) return renderComponentWithViewportAnchors(this.leftPane, width);
174
-
175
- const separatorText = componentTheme.fg("dim", ` ${componentTheme.boxSharp.vertical} `);
176
- const separator = separatorWidth > 0 ? separatorText : "";
293
+ const separator = separatorWidth > 0 ? componentTheme.fg("dim", ` ${componentTheme.boxSharp.vertical} `) : "";
177
294
  const leftRender = withTerminalGraphicsFallback(
178
295
  () => renderComponentWithViewportAnchors(this.leftPane, leftWidth),
179
296
  { allowCursorNeutralImages: true },
180
297
  );
181
- const rightLines = renderSidebarRecords(this.ledger, rightWidth, componentTheme);
298
+ const rightLines = this.#renderSidebar(rightWidth, componentTheme);
182
299
  const lineCount = Math.max(leftRender.lines.length, rightLines.length);
183
300
  const lines: string[] = [];
184
301
  const anchors: ViewportAnchorRender["anchors"] = [];
185
-
186
302
  const leftOffset = lineCount - leftRender.lines.length;
187
303
  const rightOffset = lineCount - rightLines.length;
188
304
  for (let index = 0; index < lineCount; index++) {
189
305
  const leftIndex = index - leftOffset;
190
306
  const leftRaw = leftRender.lines[leftIndex] ?? "";
191
307
  const right = truncateToWidth(rightLines[index - rightOffset] ?? "", rightWidth);
192
- if (TERMINAL.isImageLine(leftRaw)) {
193
- lines.push(leftRaw + padding(leftWidth) + separator + right);
194
- } else {
308
+ if (TERMINAL.isImageLine(leftRaw)) lines.push(leftRaw + padding(leftWidth) + separator + right);
309
+ else {
195
310
  const left = truncateToWidth(leftRaw, leftWidth);
196
311
  lines.push(left + padding(Math.max(0, leftWidth - visibleWidth(left))) + separator + right);
197
312
  }
@@ -199,7 +314,6 @@ export class IrcSplitViewComponent implements ViewportAnchorProvider {
199
314
  }
200
315
  return { lines, anchors };
201
316
  }
202
-
203
317
  invalidate(): void {
204
318
  this.leftPane.invalidate?.();
205
319
  }
@@ -31,6 +31,8 @@ import {
31
31
  } from "../../config/model-resolver";
32
32
  import { type ModelSelectorValue, normalizeModelSelectorValue, selectorHead } from "../../config/model-selector-value";
33
33
  import type { ModelProfileConfig } from "../../config/models-config-schema";
34
+ import { getProviderAuthHealth } from "../../config/provider-auth-health";
35
+ import { compareRankedProviders, type ProviderAuthState } from "../../config/provider-ranking";
34
36
  import type { Settings } from "../../config/settings";
35
37
  import { type ThemeColor, theme } from "../../modes/theme/theme";
36
38
  import { formatModelOnboardingInlineHint } from "../../setup/model-onboarding-guidance";
@@ -542,10 +544,22 @@ export class ModelSelectorComponent extends Container {
542
544
  this.#applyTabFilter();
543
545
  }
544
546
 
547
+ #resolveProviderAuthState(providerId: string): ProviderAuthState {
548
+ const health = getProviderAuthHealth(this.#modelRegistry.authStorage, providerId);
549
+ if (health) return health;
550
+ return this.#modelRegistry.hasConfiguredProviderAuth(providerId) ? "configured" : "none";
551
+ }
552
+
545
553
  #sortModels(models: ModelItem[]): void {
546
- // Sort: default-tagged model first, then MRU, then alphabetical
554
+ // Sort: default-tagged model first, then MRU, then provider ranking
547
555
  const mruOrder = this.#settings.getStorage()?.getModelUsageOrder() ?? [];
548
556
  const mruIndex = new Map(mruOrder.map((key, i) => [key, i]));
557
+ const providerAuthStateById = new Map<string, ProviderAuthState>();
558
+ for (const item of models) {
559
+ if (!providerAuthStateById.has(item.provider)) {
560
+ providerAuthStateById.set(item.provider, this.#resolveProviderAuthState(item.provider));
561
+ }
562
+ }
549
563
 
550
564
  const modelRank = (item: ModelItem) => computeModelRank(item.model, this.#roles);
551
565
 
@@ -566,7 +580,18 @@ export class ModelSelectorComponent extends Container {
566
580
  if (aMru !== bMru) return aMru - bMru;
567
581
 
568
582
  // By provider, then recency within provider
569
- const providerCmp = a.provider.localeCompare(b.provider);
583
+ const providerCmp = compareRankedProviders(
584
+ {
585
+ id: a.provider,
586
+ label: a.provider,
587
+ authState: providerAuthStateById.get(a.provider) ?? "none",
588
+ },
589
+ {
590
+ id: b.provider,
591
+ label: b.provider,
592
+ authState: providerAuthStateById.get(b.provider) ?? "none",
593
+ },
594
+ );
570
595
  if (providerCmp !== 0) return providerCmp;
571
596
 
572
597
  // Priority field (lower = better, e.g. OpenAI code backend priority values)
@@ -608,6 +633,12 @@ export class ModelSelectorComponent extends Container {
608
633
  #sortCanonicalModels(models: CanonicalModelItem[]): void {
609
634
  const mruOrder = this.#settings.getStorage()?.getModelUsageOrder() ?? [];
610
635
  const mruIndex = new Map(mruOrder.map((key, i) => [key, i]));
636
+ const providerAuthStateById = new Map<string, ProviderAuthState>();
637
+ for (const item of models) {
638
+ if (!providerAuthStateById.has(item.model.provider)) {
639
+ providerAuthStateById.set(item.model.provider, this.#resolveProviderAuthState(item.model.provider));
640
+ }
641
+ }
611
642
 
612
643
  const modelRank = (item: CanonicalModelItem) => computeModelRank(item.model, this.#roles);
613
644
 
@@ -620,7 +651,18 @@ export class ModelSelectorComponent extends Container {
620
651
  const bMru = mruIndex.get(`${b.model.provider}/${b.model.id}`) ?? Number.MAX_SAFE_INTEGER;
621
652
  if (aMru !== bMru) return aMru - bMru;
622
653
 
623
- const providerCmp = a.model.provider.localeCompare(b.model.provider);
654
+ const providerCmp = compareRankedProviders(
655
+ {
656
+ id: a.model.provider,
657
+ label: a.model.provider,
658
+ authState: providerAuthStateById.get(a.model.provider) ?? "none",
659
+ },
660
+ {
661
+ id: b.model.provider,
662
+ label: b.model.provider,
663
+ authState: providerAuthStateById.get(b.model.provider) ?? "none",
664
+ },
665
+ );
624
666
  if (providerCmp !== 0) return providerCmp;
625
667
 
626
668
  return a.id.localeCompare(b.id);
@@ -735,8 +777,23 @@ export class ModelSelectorComponent extends Container {
735
777
  for (const provider of this.#modelRegistry.getDiscoverableProviders()) {
736
778
  providerSet.add(provider);
737
779
  }
780
+ const providerAuthStateById = new Map<string, ProviderAuthState>();
781
+ for (const provider of providerSet) {
782
+ providerAuthStateById.set(provider, this.#resolveProviderAuthState(provider));
783
+ }
738
784
  const sortedProviderIds = Array.from(providerSet).sort((left, right) =>
739
- formatProviderTabLabel(left).localeCompare(formatProviderTabLabel(right)),
785
+ compareRankedProviders(
786
+ {
787
+ id: left,
788
+ label: formatProviderTabLabel(left),
789
+ authState: providerAuthStateById.get(left) ?? "none",
790
+ },
791
+ {
792
+ id: right,
793
+ label: formatProviderTabLabel(right),
794
+ authState: providerAuthStateById.get(right) ?? "none",
795
+ },
796
+ ),
740
797
  );
741
798
  this.#providers = [...STATIC_PROVIDER_TABS, ...sortedProviderIds.map(createProviderTab)];
742
799
  const activeIndex = this.#providers.findIndex(tab => tab.id === activeTabId);
@@ -32,6 +32,7 @@ export interface NotificationsEditorPreferences {
32
32
  richDraftEnabled: boolean;
33
33
  toolActivityEnabled: boolean;
34
34
  streamingEnabled: boolean;
35
+ sound: "all" | "important" | "none";
35
36
  }
36
37
 
37
38
  /** Secret-safe snapshot used to render the Notifications tab. */
@@ -160,7 +161,8 @@ type HomeActionId =
160
161
  | "reconnect"
161
162
  | "remove"
162
163
  | "preferences"
163
- | "tool-activity";
164
+ | "tool-activity"
165
+ | "sound";
164
166
 
165
167
  interface Action {
166
168
  id: HomeActionId | "telegram" | "external" | "save" | "save-inactive" | "cancel" | "confirm";
@@ -197,6 +199,7 @@ function emptyState(): NotificationsEditorState {
197
199
  richDraftEnabled: false,
198
200
  toolActivityEnabled: false,
199
201
  streamingEnabled: true,
202
+ sound: "all",
200
203
  },
201
204
  };
202
205
  }
@@ -942,6 +945,9 @@ export class NotificationsSettingsEditorComponent implements Component, Focusabl
942
945
  case "test":
943
946
  draft.streamingEnabled = !draft.streamingEnabled;
944
947
  return;
948
+ case "sound":
949
+ draft.sound = draft.sound === "all" ? "important" : draft.sound === "important" ? "none" : "all";
950
+ return;
945
951
  default:
946
952
  return;
947
953
  }
@@ -1092,6 +1098,11 @@ export class NotificationsSettingsEditorComponent implements Component, Focusabl
1092
1098
  label: `Telegram streaming: ${draft.streamingEnabled ? "on" : "off"}`,
1093
1099
  description: "Toggle the unsaved Telegram streaming preference.",
1094
1100
  },
1101
+ {
1102
+ id: "sound",
1103
+ label: `Telegram notification sound: ${draft.sound}`,
1104
+ description: "Cycle between all, important, and none notification sounds.",
1105
+ },
1095
1106
  { id: "save", label: "Save preferences", description: "Atomically persist this preference draft." },
1096
1107
  { id: "cancel", label: "Cancel and discard draft", description: "Leave saved preferences unchanged." },
1097
1108
  ];