@sayknow-cli/coding-agent 0.4.6 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (428) hide show
  1. package/CHANGELOG.md +112 -0
  2. package/dist/types/capability/index.d.ts +10 -20
  3. package/dist/types/capability/types.d.ts +3 -0
  4. package/dist/types/cli/args.d.ts +0 -2
  5. package/dist/types/cli/read-cli.d.ts +1 -0
  6. package/dist/types/cli.d.ts +9 -1
  7. package/dist/types/commands/daemon.d.ts +2 -2
  8. package/dist/types/commands/deep-interview.d.ts +9 -0
  9. package/dist/types/commands/harness.d.ts +22 -0
  10. package/dist/types/commands/read.d.ts +6 -0
  11. package/dist/types/commands/sdk.d.ts +20 -2
  12. package/dist/types/config/keybindings.d.ts +34 -4
  13. package/dist/types/config/model-profile-contract.d.ts +40 -0
  14. package/dist/types/config/model-profiles.d.ts +2 -3
  15. package/dist/types/config/model-registry.d.ts +5 -0
  16. package/dist/types/config/provider-auth-health.d.ts +14 -0
  17. package/dist/types/config/provider-ranking.d.ts +59 -0
  18. package/dist/types/config/settings-schema.d.ts +156 -10
  19. package/dist/types/config/settings.d.ts +3 -0
  20. package/dist/types/edit/modes/patch.d.ts +13 -0
  21. package/dist/types/edit/path-mutation-lock.d.ts +15 -0
  22. package/dist/types/eval/py/executor.d.ts +1 -1
  23. package/dist/types/eval/py/runner-artifact.d.ts +8 -0
  24. package/dist/types/eval/py/tool-bridge.d.ts +1 -2
  25. package/dist/types/extensibility/extensions/types.d.ts +43 -2
  26. package/dist/types/extensibility/shared-events.d.ts +6 -0
  27. package/dist/types/internal-urls/local-root-gc.d.ts +2 -0
  28. package/dist/types/main.d.ts +33 -2
  29. package/dist/types/memories/index.d.ts +2 -0
  30. package/dist/types/modes/acp/acp-agent.d.ts +22 -9
  31. package/dist/types/modes/components/assistant-message.d.ts +3 -1
  32. package/dist/types/modes/components/btw-panel.d.ts +1 -0
  33. package/dist/types/modes/components/irc-sidebar.d.ts +41 -0
  34. package/dist/types/modes/components/queue-pane.d.ts +8 -0
  35. package/dist/types/modes/components/queued-message-selector.d.ts +6 -0
  36. package/dist/types/modes/components/read-tool-group.d.ts +4 -4
  37. package/dist/types/modes/components/runtime-mcp-add-wizard.d.ts +2 -1
  38. package/dist/types/modes/components/session-observer-overlay.d.ts +4 -0
  39. package/dist/types/modes/components/settings-selector.d.ts +2 -0
  40. package/dist/types/modes/components/tool-execution.d.ts +8 -0
  41. package/dist/types/modes/components/tool-status-header.d.ts +4 -2
  42. package/dist/types/modes/components/welcome.d.ts +5 -0
  43. package/dist/types/modes/controllers/btw-controller.d.ts +5 -0
  44. package/dist/types/modes/controllers/command-controller.d.ts +2 -0
  45. package/dist/types/modes/controllers/event-controller.d.ts +3 -0
  46. package/dist/types/modes/interactive-mode.d.ts +13 -2
  47. package/dist/types/modes/irc-observation-ledger.d.ts +17 -1
  48. package/dist/types/modes/prompt-action-autocomplete.d.ts +6 -1
  49. package/dist/types/modes/prompt-suggestion-controller.d.ts +35 -0
  50. package/dist/types/modes/shared/agent-wire/workflow-gate-broker.d.ts +2 -2
  51. package/dist/types/modes/types.d.ts +6 -0
  52. package/dist/types/modes/utils/hotkeys-markdown.d.ts +2 -1
  53. package/dist/types/modes/utils/ui-helpers.d.ts +16 -0
  54. package/dist/types/runtime/memory-domain.d.ts +9 -0
  55. package/dist/types/runtime/memory-guard-contract.d.ts +71 -0
  56. package/dist/types/runtime/memory-guard.d.ts +39 -0
  57. package/dist/types/runtime/memory-limit.d.ts +11 -0
  58. package/dist/types/runtime-mcp/content-limits.d.ts +7 -0
  59. package/dist/types/runtime-mcp/discoverable-tool-metadata.d.ts +1 -1
  60. package/dist/types/runtime-mcp/manager.d.ts +9 -1
  61. package/dist/types/runtime-mcp/oauth-flow.d.ts +0 -5
  62. package/dist/types/runtime-mcp/plugin-network-boundary.d.ts +14 -0
  63. package/dist/types/runtime-mcp/redaction.d.ts +2 -0
  64. package/dist/types/runtime-mcp/smithery-auth.d.ts +5 -0
  65. package/dist/types/sdk/acp/final-text.d.ts +19 -0
  66. package/dist/types/sdk/acp/mcp.d.ts +23 -0
  67. package/dist/types/sdk/broker/broker.d.ts +2 -0
  68. package/dist/types/sdk/broker/lifecycle.d.ts +13 -0
  69. package/dist/types/sdk/bus/chat-daemon-control.d.ts +9 -1
  70. package/dist/types/sdk/bus/config-commands.d.ts +2 -2
  71. package/dist/types/sdk/bus/config.d.ts +2 -0
  72. package/dist/types/sdk/bus/control-drain-lease.d.ts +52 -0
  73. package/dist/types/sdk/bus/conversation-store.d.ts +1 -0
  74. package/dist/types/sdk/bus/daemon-paths.d.ts +1 -0
  75. package/dist/types/sdk/bus/index.d.ts +52 -4
  76. package/dist/types/sdk/bus/kind-aware-reconciliation.d.ts +37 -0
  77. package/dist/types/sdk/bus/lifecycle-commands.d.ts +5 -4
  78. package/dist/types/sdk/bus/notification-orchestration.d.ts +1 -0
  79. package/dist/types/sdk/bus/notification-service.d.ts +34 -0
  80. package/dist/types/sdk/bus/operator-runtime.d.ts +1 -0
  81. package/dist/types/sdk/bus/prompt-reconciliation.d.ts +106 -0
  82. package/dist/types/sdk/bus/rate-limit-pool.d.ts +2 -0
  83. package/dist/types/sdk/bus/reconciliation-store.d.ts +65 -0
  84. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +33 -9
  85. package/dist/types/sdk/bus/telegram-daemon-control.d.ts +5 -1
  86. package/dist/types/sdk/bus/telegram-daemon.d.ts +286 -12
  87. package/dist/types/sdk/bus/telegram-reference.d.ts +2 -0
  88. package/dist/types/sdk/bus/topic-registry.d.ts +88 -2
  89. package/dist/types/sdk/client/discovery.d.ts +2 -0
  90. package/dist/types/sdk/client/liveness.d.ts +7 -0
  91. package/dist/types/sdk/host/control/operations.d.ts +3 -2
  92. package/dist/types/sdk/host/host.d.ts +3 -1
  93. package/dist/types/sdk/host/query/handlers.d.ts +14 -0
  94. package/dist/types/sdk/index.d.ts +2 -0
  95. package/dist/types/sdk/lifecycle-session.d.ts +10 -1
  96. package/dist/types/sdk/prompt-status.d.ts +88 -0
  97. package/dist/types/sdk/session-directory.d.ts +1 -1
  98. package/dist/types/sdk/session.d.ts +7 -0
  99. package/dist/types/sdk/startup-capability.d.ts +9 -0
  100. package/dist/types/sdk/transport/auth-preface.d.ts +9 -0
  101. package/dist/types/sdk/transport/index.d.ts +12 -0
  102. package/dist/types/sdk/transport/relay.d.ts +43 -0
  103. package/dist/types/sdk/transport/serve-cli.d.ts +4 -0
  104. package/dist/types/sdk/transport/socket.d.ts +7 -0
  105. package/dist/types/sdk/transport/stdio.d.ts +3 -0
  106. package/dist/types/session/agent-session.d.ts +83 -24
  107. package/dist/types/session/btw-contract.d.ts +16 -0
  108. package/dist/types/session/fallback-chain-controller.d.ts +18 -2
  109. package/dist/types/session/internal/managed-session-scope.d.ts +70 -10
  110. package/dist/types/session/internal/managed-session-storage.d.ts +34 -12
  111. package/dist/types/session/internal/native-publish-outcome.d.ts +48 -0
  112. package/dist/types/session/memory-guard-checkpoint-participant.d.ts +69 -0
  113. package/dist/types/session/session-manager.d.ts +89 -18
  114. package/dist/types/session/session-storage.d.ts +31 -0
  115. package/dist/types/session/streaming-output.d.ts +60 -5
  116. package/dist/types/setup/credential-import.d.ts +5 -1
  117. package/dist/types/setup/provider-onboarding.d.ts +2 -0
  118. package/dist/types/skc-runtime/deep-interview-runtime.d.ts +1 -1
  119. package/dist/types/skc-runtime/deep-interview-stage.d.ts +37 -0
  120. package/dist/types/skc-runtime/gc-runtime.d.ts +1 -1
  121. package/dist/types/skc-runtime/linux-proc.d.ts +22 -14
  122. package/dist/types/skc-runtime/memory-guard-owner-claims.d.ts +47 -0
  123. package/dist/types/skc-runtime/ralplan-runtime.d.ts +112 -1
  124. package/dist/types/skc-runtime/repository-binding.d.ts +63 -0
  125. package/dist/types/skc-runtime/state-runtime.d.ts +3 -1
  126. package/dist/types/skc-runtime/state-writer.d.ts +5 -1
  127. package/dist/types/skc-runtime/team-launch.d.ts +1 -1
  128. package/dist/types/skc-runtime/team-runtime.d.ts +9 -2
  129. package/dist/types/skc-runtime/team-store.d.ts +12 -0
  130. package/dist/types/skc-runtime/team-worker-memory-guard.d.ts +92 -0
  131. package/dist/types/skc-runtime/tmux-owner-isolation.d.ts +7 -0
  132. package/dist/types/skc-runtime/ultragoal-guard.d.ts +6 -5
  133. package/dist/types/skc-runtime/ultragoal-receipt-freshness.d.ts +28 -0
  134. package/dist/types/skc-runtime/ultragoal-runtime.d.ts +43 -5
  135. package/dist/types/skill-state/workflow-hud.d.ts +3 -0
  136. package/dist/types/ssh/utils.d.ts +1 -0
  137. package/dist/types/task/discovery.d.ts +3 -1
  138. package/dist/types/task/executor.d.ts +6 -0
  139. package/dist/types/task/index.d.ts +3 -1
  140. package/dist/types/task/provider-retry-status.d.ts +13 -0
  141. package/dist/types/task/receipt.d.ts +2 -0
  142. package/dist/types/task/types.d.ts +133 -0
  143. package/dist/types/task/ultragoal-redteam-activation.d.ts +33 -0
  144. package/dist/types/tools/browser/launch.d.ts +7 -0
  145. package/dist/types/tools/fetch.d.ts +22 -9
  146. package/dist/types/tools/image-gen.d.ts +63 -1
  147. package/dist/types/tools/index.d.ts +7 -1
  148. package/dist/types/tools/output-meta.d.ts +21 -1
  149. package/dist/types/tools/read-internals.d.ts +12 -0
  150. package/dist/types/tools/read.d.ts +45 -1
  151. package/dist/types/tools/resource-gc.d.ts +23 -0
  152. package/dist/types/tools/skill-discovery.d.ts +7 -0
  153. package/dist/types/tools/sqlite-reader.d.ts +7 -0
  154. package/dist/types/tools/tool-result.d.ts +7 -1
  155. package/dist/types/utils/prompt-suggestion.d.ts +33 -0
  156. package/dist/types/utils/shell-snapshot.d.ts +12 -0
  157. package/dist/types/web/insane/bridge.d.ts +1 -0
  158. package/dist/types/web/insane/url-guard.d.ts +17 -0
  159. package/package.json +8 -12
  160. package/scripts/benchmark-sticky-viewport-pr1.ts +239 -0
  161. package/scripts/capture-platform-shortcut-labels-showcase.ts +342 -0
  162. package/scripts/capture-sticky-viewport-showcase.ts +292 -0
  163. package/scripts/compile-args.ts +0 -1
  164. package/scripts/dogfood-repository-binding.ts +209 -0
  165. package/scripts/generate-docs-index.ts +4 -1
  166. package/scripts/generate-sdk-operation-inventory.ts +9 -0
  167. package/scripts/run-test-manifest.ts +6 -4
  168. package/scripts/verify-skc-sdk-canonicalization.ts +122 -18
  169. package/scripts/verify-sticky-viewport-showcase.ts +611 -0
  170. package/src/capability/index.ts +58 -42
  171. package/src/capability/types.ts +3 -0
  172. package/src/cli/args.ts +16 -100
  173. package/src/cli/fast-help.ts +1 -1
  174. package/src/cli/mcp-cli.ts +4 -21
  175. package/src/cli/notify-cli.ts +6 -38
  176. package/src/cli/read-cli.ts +2 -1
  177. package/src/cli/setup-cli.ts +10 -4
  178. package/src/cli.ts +133 -11
  179. package/src/commands/daemon.ts +22 -5
  180. package/src/commands/deep-interview.ts +26 -1
  181. package/src/commands/harness.ts +35 -12
  182. package/src/commands/read.ts +11 -2
  183. package/src/commands/sdk.ts +134 -17
  184. package/src/commands/team.ts +1 -1
  185. package/src/commit/agentic/index.ts +3 -3
  186. package/src/commit/map-reduce/index.ts +2 -2
  187. package/src/config/keybindings.ts +158 -25
  188. package/src/config/model-profile-contract.ts +174 -0
  189. package/src/config/model-profiles.ts +51 -10
  190. package/src/config/model-registry.ts +24 -4
  191. package/src/config/model-resolver.ts +2 -2
  192. package/src/config/provider-auth-health.ts +42 -0
  193. package/src/config/provider-ranking.ts +119 -0
  194. package/src/config/settings-schema.ts +157 -10
  195. package/src/config/settings.ts +254 -108
  196. package/src/coordinator-mcp/model-preset.ts +21 -74
  197. package/src/defaults/skc/skills/deep-interview/SKILL.md +147 -24
  198. package/src/defaults/skc/skills/ralplan/SKILL.md +91 -34
  199. package/src/defaults/skc/skills/team/SKILL.md +1 -1
  200. package/src/defaults/skc/skills/ultragoal/SKILL.md +68 -17
  201. package/src/discovery/ssh.ts +12 -1
  202. package/src/edit/index.ts +3 -6
  203. package/src/edit/modes/patch.ts +38 -1
  204. package/src/edit/modes/replace.ts +42 -0
  205. package/src/edit/path-mutation-lock.ts +112 -0
  206. package/src/eval/js/tool-bridge.ts +1 -1
  207. package/src/eval/py/executor.ts +37 -13
  208. package/src/eval/py/kernel.ts +1 -21
  209. package/src/eval/py/prelude.py +2 -2
  210. package/src/eval/py/runner-artifact.ts +84 -0
  211. package/src/eval/py/tool-bridge.ts +34 -18
  212. package/src/extensibility/extensions/runner.ts +4 -0
  213. package/src/extensibility/extensions/types.ts +42 -4
  214. package/src/extensibility/plugins/marketplace/fetcher.ts +191 -7
  215. package/src/extensibility/shared-events.ts +5 -0
  216. package/src/extensibility/skc-plugins/runtime-adapters.ts +2 -1
  217. package/src/hooks/native-skill-hook.ts +17 -4
  218. package/src/internal-urls/docs-index.generated.ts +17 -16
  219. package/src/internal-urls/local-protocol.ts +87 -25
  220. package/src/internal-urls/local-root-gc.ts +127 -0
  221. package/src/internal-urls/mcp-protocol.ts +73 -11
  222. package/src/main.ts +120 -42
  223. package/src/memories/index.ts +10 -0
  224. package/src/modes/DESIGN.md +52 -0
  225. package/src/modes/acp/acp-agent.ts +647 -83
  226. package/src/modes/acp/acp-event-mapper.ts +106 -12
  227. package/src/modes/acp/acp-mode.ts +39 -3
  228. package/src/modes/action-registry.ts +1 -1
  229. package/src/modes/components/assistant-message.ts +123 -22
  230. package/src/modes/components/btw-panel.ts +82 -16
  231. package/src/modes/components/irc-sidebar.ts +176 -62
  232. package/src/modes/components/model-selector.ts +61 -4
  233. package/src/modes/components/notifications-settings-editor.ts +1 -1
  234. package/src/modes/components/oauth-selector.ts +42 -12
  235. package/src/modes/components/plan-preview-overlay.ts +1 -1
  236. package/src/modes/components/queue-pane.ts +55 -9
  237. package/src/modes/components/queued-message-selector.ts +53 -10
  238. package/src/modes/components/read-tool-group.ts +82 -14
  239. package/src/modes/components/runtime-mcp-add-wizard.ts +5 -0
  240. package/src/modes/components/sayknow-pet-widget.ts +27 -8
  241. package/src/modes/components/session-observer-overlay.ts +187 -32
  242. package/src/modes/components/settings-selector.ts +60 -18
  243. package/src/modes/components/tool-execution.ts +138 -27
  244. package/src/modes/components/tool-status-header.ts +26 -8
  245. package/src/modes/components/welcome.ts +50 -23
  246. package/src/modes/controllers/btw-controller.ts +119 -36
  247. package/src/modes/controllers/command-controller.ts +101 -54
  248. package/src/modes/controllers/event-controller.ts +119 -27
  249. package/src/modes/controllers/extension-ui-controller.ts +23 -12
  250. package/src/modes/controllers/input-controller.ts +100 -8
  251. package/src/modes/controllers/runtime-mcp-command-controller.ts +29 -6
  252. package/src/modes/controllers/selector-controller.ts +4 -1
  253. package/src/modes/interactive-mode.ts +201 -60
  254. package/src/modes/irc-observation-ledger.ts +71 -15
  255. package/src/modes/prompt-action-autocomplete.ts +32 -13
  256. package/src/modes/prompt-suggestion-controller.ts +96 -0
  257. package/src/modes/runtime-init.ts +20 -2
  258. package/src/modes/shared/agent-wire/command-dispatch.ts +3 -3
  259. package/src/modes/shared/agent-wire/workflow-gate-broker.ts +2 -2
  260. package/src/modes/types.ts +6 -0
  261. package/src/modes/utils/hotkeys-markdown.ts +64 -36
  262. package/src/modes/utils/ui-helpers.ts +99 -4
  263. package/src/prompts/agent-fragments/restricted-bash.md +1 -1
  264. package/src/prompts/agents/architect.md +17 -3
  265. package/src/prompts/agents/critic.md +17 -2
  266. package/src/prompts/agents/executor.md +1 -1
  267. package/src/prompts/agents/planner.md +8 -1
  268. package/src/prompts/system/btw-user.md +3 -8
  269. package/src/prompts/system/prompt-suggestion-system.md +32 -0
  270. package/src/prompts/system/system-prompt.md +6 -3
  271. package/src/prompts/tools/cron.md +3 -1
  272. package/src/prompts/tools/read.md +4 -2
  273. package/src/prompts/tools/skill-discovery.md +1 -0
  274. package/src/runtime/memory-domain.ts +66 -0
  275. package/src/runtime/memory-guard-contract.ts +73 -0
  276. package/src/runtime/memory-guard.ts +292 -0
  277. package/src/runtime/memory-limit.ts +50 -0
  278. package/src/runtime-mcp/client.ts +41 -55
  279. package/src/runtime-mcp/content-limits.ts +71 -0
  280. package/src/runtime-mcp/discoverable-tool-metadata.ts +1 -1
  281. package/src/runtime-mcp/json-rpc.ts +28 -11
  282. package/src/runtime-mcp/manager.ts +77 -59
  283. package/src/runtime-mcp/oauth-flow.ts +0 -72
  284. package/src/runtime-mcp/plugin-network-boundary.ts +101 -0
  285. package/src/runtime-mcp/redaction.ts +30 -0
  286. package/src/runtime-mcp/smithery-auth.ts +17 -2
  287. package/src/runtime-mcp/smithery-connect.ts +11 -1
  288. package/src/runtime-mcp/transports/http.ts +63 -22
  289. package/src/sdk/acp/adapter.ts +8 -1
  290. package/src/sdk/acp/final-text.ts +40 -0
  291. package/src/sdk/acp/mcp.ts +26 -0
  292. package/src/sdk/broker/broker.ts +34 -20
  293. package/src/sdk/broker/identity.ts +22 -12
  294. package/src/sdk/broker/lifecycle.ts +293 -43
  295. package/src/sdk/broker/session-index.ts +11 -1
  296. package/src/sdk/bus/chat-daemon-cli.ts +2 -2
  297. package/src/sdk/bus/chat-daemon-control.ts +22 -5
  298. package/src/sdk/bus/config-commands.ts +2 -2
  299. package/src/sdk/bus/config.ts +10 -2
  300. package/src/sdk/bus/control-drain-lease.ts +104 -0
  301. package/src/sdk/bus/conversation-store.ts +40 -6
  302. package/src/sdk/bus/daemon-paths.ts +2 -0
  303. package/src/sdk/bus/index.ts +793 -121
  304. package/src/sdk/bus/kind-aware-reconciliation.ts +242 -0
  305. package/src/sdk/bus/lifecycle-commands.ts +63 -10
  306. package/src/sdk/bus/lifecycle-control-runtime.ts +11 -2
  307. package/src/sdk/bus/lifecycle-orchestrator.ts +32 -1
  308. package/src/sdk/bus/notification-orchestration.ts +12 -0
  309. package/src/sdk/bus/notification-service.ts +87 -25
  310. package/src/sdk/bus/operator-runtime.ts +31 -5
  311. package/src/sdk/bus/prompt-reconciliation.ts +240 -0
  312. package/src/sdk/bus/rate-limit-pool.ts +8 -0
  313. package/src/sdk/bus/recent-activity.ts +90 -26
  314. package/src/sdk/bus/reconciliation-store.ts +224 -0
  315. package/src/sdk/bus/telegram-cli.ts +6 -13
  316. package/src/sdk/bus/telegram-daemon-cli.ts +8 -3
  317. package/src/sdk/bus/telegram-daemon-contract.ts +34 -9
  318. package/src/sdk/bus/telegram-daemon-control.ts +161 -40
  319. package/src/sdk/bus/telegram-daemon.ts +3649 -593
  320. package/src/sdk/bus/telegram-reference.ts +20 -2
  321. package/src/sdk/bus/topic-registry.ts +451 -12
  322. package/src/sdk/cli/session-cli.ts +15 -2
  323. package/src/sdk/client/discovery.ts +19 -3
  324. package/src/sdk/client/liveness.ts +49 -0
  325. package/src/sdk/host/control/dispatch.ts +8 -2
  326. package/src/sdk/host/control/operations.ts +3 -2
  327. package/src/sdk/host/host.ts +27 -9
  328. package/src/sdk/host/query/handlers.ts +125 -5
  329. package/src/sdk/index.ts +12 -0
  330. package/src/sdk/lifecycle-session.ts +19 -3
  331. package/src/sdk/mcp/server.ts +46 -18
  332. package/src/sdk/prompt-status.ts +97 -0
  333. package/src/sdk/protocol/operation-inventory.generated.json +145 -0
  334. package/src/sdk/protocol/operation-registry.ts +24 -4
  335. package/src/sdk/session-directory.ts +5 -1
  336. package/src/sdk/session.ts +69 -8
  337. package/src/sdk/startup-capability.ts +15 -1
  338. package/src/sdk/transport/auth-preface.ts +82 -0
  339. package/src/sdk/transport/index.ts +18 -0
  340. package/src/sdk/transport/relay.ts +252 -0
  341. package/src/sdk/transport/serve-cli.ts +105 -0
  342. package/src/sdk/transport/socket.ts +197 -0
  343. package/src/sdk/transport/stdio.ts +17 -0
  344. package/src/session/agent-session.ts +1112 -314
  345. package/src/session/blob-store.ts +25 -7
  346. package/src/session/btw-contract.ts +59 -0
  347. package/src/session/fallback-chain-controller.ts +47 -8
  348. package/src/session/internal/managed-session-scope.ts +1426 -259
  349. package/src/session/internal/managed-session-storage.ts +414 -238
  350. package/src/session/internal/native-publish-outcome.ts +271 -0
  351. package/src/session/memory-guard-checkpoint-participant.ts +131 -0
  352. package/src/session/session-manager.ts +2079 -469
  353. package/src/session/session-storage.ts +151 -11
  354. package/src/session/streaming-output.ts +262 -43
  355. package/src/setup/credential-import.ts +28 -3
  356. package/src/setup/provider-onboarding.ts +49 -5
  357. package/src/setup/provider-presets.json +2 -1
  358. package/src/skc-runtime/deep-interview-runtime.ts +303 -36
  359. package/src/skc-runtime/deep-interview-stage.ts +1016 -0
  360. package/src/skc-runtime/deep-interview-state.ts +8 -5
  361. package/src/skc-runtime/gc-render.ts +1 -0
  362. package/src/skc-runtime/gc-runtime.ts +11 -1
  363. package/src/skc-runtime/linux-proc.ts +70 -36
  364. package/src/skc-runtime/managed-owner-supervisor.ts +83 -4
  365. package/src/skc-runtime/memory-guard-owner-claims.ts +389 -0
  366. package/src/skc-runtime/ralplan-runtime.ts +1057 -75
  367. package/src/skc-runtime/repository-binding.ts +267 -0
  368. package/src/skc-runtime/session-state-sidecar.ts +25 -9
  369. package/src/skc-runtime/state-runtime.ts +50 -12
  370. package/src/skc-runtime/state-writer.ts +68 -56
  371. package/src/skc-runtime/team-launch.ts +34 -5
  372. package/src/skc-runtime/team-runtime.ts +41 -1
  373. package/src/skc-runtime/team-store.ts +32 -0
  374. package/src/skc-runtime/team-worker-memory-guard.ts +363 -0
  375. package/src/skc-runtime/team-workers.ts +1 -0
  376. package/src/skc-runtime/tmux-common.ts +13 -2
  377. package/src/skc-runtime/tmux-owner-isolation.ts +20 -1
  378. package/src/skc-runtime/tmux-sessions.ts +45 -6
  379. package/src/skc-runtime/ultragoal-guard.ts +93 -15
  380. package/src/skc-runtime/ultragoal-receipt-freshness.ts +123 -0
  381. package/src/skc-runtime/ultragoal-runtime.ts +469 -38
  382. package/src/skc-runtime/workflow-manifest.generated.json +195 -5
  383. package/src/skc-runtime/workflow-manifest.ts +67 -3
  384. package/src/skill-state/workflow-hud.ts +25 -2
  385. package/src/skill-state/workflow-mutation-guard.ts +104 -12
  386. package/src/slash-commands/builtin-registry.ts +1 -1
  387. package/src/ssh/utils.ts +14 -0
  388. package/src/task/discovery.ts +8 -2
  389. package/src/task/executor.ts +165 -47
  390. package/src/task/index.ts +123 -15
  391. package/src/task/provider-retry-status.ts +88 -0
  392. package/src/task/receipt.ts +3 -0
  393. package/src/task/render.ts +45 -26
  394. package/src/task/skc-command.ts +2 -2
  395. package/src/task/types.ts +55 -10
  396. package/src/task/ultragoal-redteam-activation.ts +75 -0
  397. package/src/tools/ask.ts +1 -1
  398. package/src/tools/ast-edit.ts +2 -2
  399. package/src/tools/bash-allowed-prefixes.ts +45 -1
  400. package/src/tools/bash-pty-selection.ts +2 -2
  401. package/src/tools/browser/launch.ts +41 -6
  402. package/src/tools/browser/screenshot-format.ts +6 -1
  403. package/src/tools/cron.ts +1 -1
  404. package/src/tools/fetch.ts +127 -154
  405. package/src/tools/image-gen.ts +476 -23
  406. package/src/tools/index.ts +7 -1
  407. package/src/tools/output-meta.ts +176 -9
  408. package/src/tools/path-utils.ts +5 -1
  409. package/src/tools/read-internals.ts +44 -0
  410. package/src/tools/read.ts +1544 -310
  411. package/src/tools/resource-gc.ts +665 -125
  412. package/src/tools/skill-discovery.ts +39 -3
  413. package/src/tools/sqlite-reader.ts +63 -5
  414. package/src/tools/subagent-render.ts +74 -16
  415. package/src/tools/subagent.ts +2 -0
  416. package/src/tools/tool-result.ts +14 -1
  417. package/src/utils/edit-mode.ts +2 -2
  418. package/src/utils/pasted-image-path.ts +15 -6
  419. package/src/utils/prompt-suggestion.ts +265 -0
  420. package/src/utils/shell-snapshot.ts +173 -63
  421. package/src/utils/sixel.ts +3 -3
  422. package/src/web/insane/bridge.ts +5 -1
  423. package/src/web/insane/url-guard.ts +140 -27
  424. package/src/web/scrapers/docs-rs.ts +30 -5
  425. package/src/web/scrapers/types.ts +19 -49
  426. package/src/web/scrapers/utils.ts +12 -26
  427. package/src/web/search/providers/codex.ts +1 -1
  428. package/src/workflow/workflow-intent-diff.ts +4 -1
@@ -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("skc: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
  }
@@ -32,6 +32,8 @@ import {
32
32
  } from "../../config/model-resolver";
33
33
  import { type ModelSelectorValue, normalizeModelSelectorValue, selectorHead } from "../../config/model-selector-value";
34
34
  import type { ModelProfileConfig } from "../../config/models-config-schema";
35
+ import { getProviderAuthHealth } from "../../config/provider-auth-health";
36
+ import { compareRankedProviders, type ProviderAuthState } from "../../config/provider-ranking";
35
37
  import type { Settings } from "../../config/settings";
36
38
  import { t } from "../../i18n";
37
39
  import { type ThemeColor, theme } from "../../modes/theme/theme";
@@ -521,10 +523,22 @@ export class ModelSelectorComponent extends Container {
521
523
  this.#applyTabFilter();
522
524
  }
523
525
 
526
+ #resolveProviderAuthState(providerId: string): ProviderAuthState {
527
+ const health = getProviderAuthHealth(this.#modelRegistry.authStorage, providerId);
528
+ if (health) return health;
529
+ return this.#modelRegistry.hasConfiguredProviderAuth(providerId) ? "configured" : "none";
530
+ }
531
+
524
532
  #sortModels(models: ModelItem[]): void {
525
- // Sort: default-tagged model first, then MRU, then alphabetical
533
+ // Sort: default-tagged model first, then MRU, then provider ranking
526
534
  const mruOrder = this.#settings.getStorage()?.getModelUsageOrder() ?? [];
527
535
  const mruIndex = new Map(mruOrder.map((key, i) => [key, i]));
536
+ const providerAuthStateById = new Map<string, ProviderAuthState>();
537
+ for (const item of models) {
538
+ if (!providerAuthStateById.has(item.provider)) {
539
+ providerAuthStateById.set(item.provider, this.#resolveProviderAuthState(item.provider));
540
+ }
541
+ }
528
542
 
529
543
  const modelRank = (item: ModelItem) => computeModelRank(item.model, this.#roles);
530
544
 
@@ -545,7 +559,18 @@ export class ModelSelectorComponent extends Container {
545
559
  if (aMru !== bMru) return aMru - bMru;
546
560
 
547
561
  // By provider, then recency within provider
548
- const providerCmp = a.provider.localeCompare(b.provider);
562
+ const providerCmp = compareRankedProviders(
563
+ {
564
+ id: a.provider,
565
+ label: a.provider,
566
+ authState: providerAuthStateById.get(a.provider) ?? "none",
567
+ },
568
+ {
569
+ id: b.provider,
570
+ label: b.provider,
571
+ authState: providerAuthStateById.get(b.provider) ?? "none",
572
+ },
573
+ );
549
574
  if (providerCmp !== 0) return providerCmp;
550
575
 
551
576
  // Priority field (lower = better, e.g. OpenAI code backend priority values)
@@ -587,6 +612,12 @@ export class ModelSelectorComponent extends Container {
587
612
  #sortCanonicalModels(models: CanonicalModelItem[]): void {
588
613
  const mruOrder = this.#settings.getStorage()?.getModelUsageOrder() ?? [];
589
614
  const mruIndex = new Map(mruOrder.map((key, i) => [key, i]));
615
+ const providerAuthStateById = new Map<string, ProviderAuthState>();
616
+ for (const item of models) {
617
+ if (!providerAuthStateById.has(item.model.provider)) {
618
+ providerAuthStateById.set(item.model.provider, this.#resolveProviderAuthState(item.model.provider));
619
+ }
620
+ }
590
621
 
591
622
  const modelRank = (item: CanonicalModelItem) => computeModelRank(item.model, this.#roles);
592
623
 
@@ -599,7 +630,18 @@ export class ModelSelectorComponent extends Container {
599
630
  const bMru = mruIndex.get(`${b.model.provider}/${b.model.id}`) ?? Number.MAX_SAFE_INTEGER;
600
631
  if (aMru !== bMru) return aMru - bMru;
601
632
 
602
- const providerCmp = a.model.provider.localeCompare(b.model.provider);
633
+ const providerCmp = compareRankedProviders(
634
+ {
635
+ id: a.model.provider,
636
+ label: a.model.provider,
637
+ authState: providerAuthStateById.get(a.model.provider) ?? "none",
638
+ },
639
+ {
640
+ id: b.model.provider,
641
+ label: b.model.provider,
642
+ authState: providerAuthStateById.get(b.model.provider) ?? "none",
643
+ },
644
+ );
603
645
  if (providerCmp !== 0) return providerCmp;
604
646
 
605
647
  return a.id.localeCompare(b.id);
@@ -714,8 +756,23 @@ export class ModelSelectorComponent extends Container {
714
756
  for (const provider of this.#modelRegistry.getDiscoverableProviders()) {
715
757
  providerSet.add(provider);
716
758
  }
759
+ const providerAuthStateById = new Map<string, ProviderAuthState>();
760
+ for (const provider of providerSet) {
761
+ providerAuthStateById.set(provider, this.#resolveProviderAuthState(provider));
762
+ }
717
763
  const sortedProviderIds = Array.from(providerSet).sort((left, right) =>
718
- formatProviderTabLabel(left).localeCompare(formatProviderTabLabel(right)),
764
+ compareRankedProviders(
765
+ {
766
+ id: left,
767
+ label: formatProviderTabLabel(left),
768
+ authState: providerAuthStateById.get(left) ?? "none",
769
+ },
770
+ {
771
+ id: right,
772
+ label: formatProviderTabLabel(right),
773
+ authState: providerAuthStateById.get(right) ?? "none",
774
+ },
775
+ ),
719
776
  );
720
777
  this.#providers = [...STATIC_PROVIDER_TABS, ...sortedProviderIds.map(createProviderTab)];
721
778
  const activeIndex = this.#providers.findIndex(tab => tab.id === activeTabId);
@@ -195,7 +195,7 @@ function emptyState(): NotificationsEditorState {
195
195
  sessionScope: "all",
196
196
  richEnabled: true,
197
197
  richDraftEnabled: false,
198
- toolActivityEnabled: true,
198
+ toolActivityEnabled: false,
199
199
  streamingEnabled: true,
200
200
  },
201
201
  };
@@ -1,6 +1,8 @@
1
1
  import { getOAuthProviders } from "@sayknow-cli/ai/utils/oauth";
2
2
  import type { OAuthProviderInfo } from "@sayknow-cli/ai/utils/oauth/types";
3
3
  import { Container, matchesKey, Spacer, TruncatedText } from "@sayknow-cli/tui";
4
+ import { recordProviderAuthHealth } from "../../config/provider-auth-health";
5
+ import { compareRankedProviders, type ProviderAuthState } from "../../config/provider-ranking";
4
6
  import { theme } from "../../modes/theme/theme";
5
7
  import { matchesSelectCancel } from "../../modes/utils/keybinding-matchers";
6
8
  import type { AuthStorage } from "../../session/auth-storage";
@@ -14,6 +16,7 @@ const OAUTH_SELECTOR_MAX_VISIBLE = 10;
14
16
  export class OAuthSelectorComponent extends Container {
15
17
  #listContainer: Container;
16
18
  #allProviders: OAuthProviderInfo[] = [];
19
+ #sortedProviders: OAuthProviderInfo[] = [];
17
20
  #selectedIndex: number = 0;
18
21
  #mode: "login" | "logout";
19
22
  #authStorage: AuthStorage;
@@ -91,7 +94,7 @@ export class OAuthSelectorComponent extends Container {
91
94
  }
92
95
  this.#authState.set(provider.id, "checking");
93
96
  pending += 1;
94
- void this.#validateProvider(provider.id, generation);
97
+ void this.#validateProvider(provider.id, generation, this.#authStorage.getGeneration());
95
98
  }
96
99
 
97
100
  if (pending > 0) {
@@ -101,7 +104,7 @@ export class OAuthSelectorComponent extends Container {
101
104
  }
102
105
  }
103
106
 
104
- async #validateProvider(providerId: string, generation: number): Promise<void> {
107
+ async #validateProvider(providerId: string, generation: number, authGeneration: number): Promise<void> {
105
108
  if (!this.#validateAuthCallback) return;
106
109
  let isValid = false;
107
110
  try {
@@ -112,6 +115,11 @@ export class OAuthSelectorComponent extends Container {
112
115
 
113
116
  if (generation !== this.#validationGeneration) return;
114
117
  this.#authState.set(providerId, isValid ? "valid" : "invalid");
118
+ // Only record the ordering hint when the credentials validated are still the
119
+ // current ones; a result from a superseded generation must not describe them.
120
+ if (authGeneration === this.#authStorage.getGeneration()) {
121
+ recordProviderAuthHealth(this.#authStorage, providerId, isValid ? "valid" : "invalid");
122
+ }
115
123
  if (![...this.#authState.values()].includes("checking")) {
116
124
  this.#stopSpinner();
117
125
  }
@@ -138,6 +146,10 @@ export class OAuthSelectorComponent extends Container {
138
146
  }
139
147
  }
140
148
 
149
+ #getProviderAuthState(providerId: string): ProviderAuthState {
150
+ return this.#authState.get(providerId) ?? "none";
151
+ }
152
+
141
153
  #getStatusIndicator(providerId: string): string {
142
154
  const state = this.#authState.get(providerId);
143
155
  if (state === "checking") {
@@ -154,9 +166,25 @@ export class OAuthSelectorComponent extends Container {
154
166
  return this.#authStorage.hasAuth(providerId) ? theme.fg("success", ` ${theme.status.success} logged in`) : "";
155
167
  }
156
168
  #updateList(): void {
169
+ const selectedProviderId = this.#sortedProviders[this.#selectedIndex]?.id;
170
+ const rankedProviders = this.#allProviders.map(provider => ({
171
+ provider,
172
+ id: provider.id,
173
+ label: provider.name,
174
+ authState: this.#getProviderAuthState(provider.id),
175
+ }));
176
+ rankedProviders.sort(compareRankedProviders);
177
+ this.#sortedProviders = rankedProviders.map(({ provider }) => provider);
178
+ if (selectedProviderId !== undefined) {
179
+ const selectedIndex = this.#sortedProviders.findIndex(provider => provider.id === selectedProviderId);
180
+ if (selectedIndex >= 0) this.#selectedIndex = selectedIndex;
181
+ }
182
+ if (this.#selectedIndex >= this.#sortedProviders.length) {
183
+ this.#selectedIndex = Math.max(0, this.#sortedProviders.length - 1);
184
+ }
157
185
  this.#listContainer.clear();
158
186
 
159
- const total = this.#allProviders.length;
187
+ const total = this.#sortedProviders.length;
160
188
  const maxVisible = OAUTH_SELECTOR_MAX_VISIBLE;
161
189
  const startIndex =
162
190
  total <= maxVisible
@@ -165,7 +193,7 @@ export class OAuthSelectorComponent extends Container {
165
193
  const endIndex = Math.min(startIndex + maxVisible, total);
166
194
 
167
195
  for (let i = startIndex; i < endIndex; i++) {
168
- const provider = this.#allProviders[i];
196
+ const provider = this.#sortedProviders[i];
169
197
  if (!provider) continue;
170
198
  const isSelected = i === this.#selectedIndex;
171
199
  const isAvailable = provider.available;
@@ -218,23 +246,25 @@ export class OAuthSelectorComponent extends Container {
218
246
  handleInput(keyData: string): void {
219
247
  // Up arrow
220
248
  if (matchesKey(keyData, "up")) {
221
- if (this.#allProviders.length > 0) {
222
- this.#selectedIndex = this.#selectedIndex === 0 ? this.#allProviders.length - 1 : this.#selectedIndex - 1;
249
+ if (this.#sortedProviders.length > 0) {
250
+ this.#selectedIndex =
251
+ this.#selectedIndex === 0 ? this.#sortedProviders.length - 1 : this.#selectedIndex - 1;
223
252
  }
224
253
  this.#statusMessage = undefined;
225
254
  this.#updateList();
226
255
  }
227
256
  // Down arrow
228
257
  else if (matchesKey(keyData, "down")) {
229
- if (this.#allProviders.length > 0) {
230
- this.#selectedIndex = this.#selectedIndex === this.#allProviders.length - 1 ? 0 : this.#selectedIndex + 1;
258
+ if (this.#sortedProviders.length > 0) {
259
+ this.#selectedIndex =
260
+ this.#selectedIndex === this.#sortedProviders.length - 1 ? 0 : this.#selectedIndex + 1;
231
261
  }
232
262
  this.#statusMessage = undefined;
233
263
  this.#updateList();
234
264
  }
235
265
  // Page up - jump up by one visible page
236
266
  else if (matchesKey(keyData, "pageUp")) {
237
- if (this.#allProviders.length > 0) {
267
+ if (this.#sortedProviders.length > 0) {
238
268
  this.#selectedIndex = Math.max(0, this.#selectedIndex - OAUTH_SELECTOR_MAX_VISIBLE);
239
269
  }
240
270
  this.#statusMessage = undefined;
@@ -242,9 +272,9 @@ export class OAuthSelectorComponent extends Container {
242
272
  }
243
273
  // Page down - jump down by one visible page
244
274
  else if (matchesKey(keyData, "pageDown")) {
245
- if (this.#allProviders.length > 0) {
275
+ if (this.#sortedProviders.length > 0) {
246
276
  this.#selectedIndex = Math.min(
247
- this.#allProviders.length - 1,
277
+ this.#sortedProviders.length - 1,
248
278
  this.#selectedIndex + OAUTH_SELECTOR_MAX_VISIBLE,
249
279
  );
250
280
  }
@@ -253,7 +283,7 @@ export class OAuthSelectorComponent extends Container {
253
283
  }
254
284
  // Enter
255
285
  else if (matchesKey(keyData, "enter") || matchesKey(keyData, "return") || keyData === "\n") {
256
- const selectedProvider = this.#allProviders[this.#selectedIndex];
286
+ const selectedProvider = this.#sortedProviders[this.#selectedIndex];
257
287
  if (selectedProvider?.available) {
258
288
  this.#statusMessage = undefined;
259
289
  this.stopValidation();
@@ -122,7 +122,7 @@ export class PlanPreviewOverlay extends Container {
122
122
  ? `${this.#inputKind === "notes" ? "Notes" : `Comment for source line ${this.#sourceLine}`} (Enter save, Esc cancel): ${this.#input.getValue()}`
123
123
  : this.#focus === "sourceSelect"
124
124
  ? `Source line ${this.#sourceLine}/${this.lines.length || 1} j/k:select c:comment Esc:done`
125
- : `${this.#scroll + 1}-${Math.min(this.#scroll + height, body.length)}/${body.length || 1} s:source line n:notes ${this.options.externalEditorKey ? `${this.options.externalEditorKey.toLowerCase()}:edit ` : ""}Tab:actions PgUp/PgDn:page`;
125
+ : `${this.#scroll + 1}-${Math.min(this.#scroll + height, body.length)}/${body.length || 1} s:source line n:notes ${this.options.externalEditorKey ? `${this.options.externalEditorKey}:edit ` : ""}Tab:actions PgUp/PgDn:page`;
126
126
 
127
127
  const rendered = [
128
128
  new DynamicBorder().render(width)[0] ?? "",