@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
@@ -129,6 +129,21 @@ function stabilizeStreamingPreviews(previews: PerFileDiffPreview[]): PerFileDiff
129
129
  return changed ? next : previews;
130
130
  }
131
131
 
132
+ function previewsAreEqual(a: PerFileDiffPreview[] | undefined, b: PerFileDiffPreview[]): boolean {
133
+ return (
134
+ a?.length === b.length &&
135
+ a.every((preview, index) => {
136
+ const next = b[index];
137
+ return (
138
+ preview.path === next?.path &&
139
+ preview.diff === next.diff &&
140
+ preview.error === next.error &&
141
+ preview.firstChangedLine === next.firstChangedLine
142
+ );
143
+ })
144
+ );
145
+ }
146
+
132
147
  function isEditLikeToolName(toolName: string): boolean {
133
148
  return toolName === "edit" || toolName === "apply_patch";
134
149
  }
@@ -144,6 +159,8 @@ export interface ToolExecutionOptions {
144
159
  editFuzzyThreshold?: number;
145
160
  editAllowFuzzy?: boolean;
146
161
  hashlineAutoDropPureInsertDuplicates?: boolean;
162
+ /** Internal observer for visible asynchronous renderer mutations. */
163
+ onVisibleTranscriptMutation?: () => void;
147
164
  }
148
165
 
149
166
  export interface ToolExecutionHandle {
@@ -168,6 +185,11 @@ export interface ToolExecutionHandle {
168
185
  * back to {@link setExpanded}.
169
186
  */
170
187
  setManuallyExpanded?(expanded: boolean): void;
188
+ /**
189
+ * Internal capability for dispatchers that need exact synchronous visible-output
190
+ * detection. Optional so existing structural handles remain source compatible.
191
+ */
192
+ consumeVisibleTranscriptChange?(): boolean;
171
193
  }
172
194
 
173
195
  /**
@@ -213,8 +235,11 @@ export class ToolExecutionComponent extends Container {
213
235
  #argsIdentityVersion = 0;
214
236
  #lastArgsReference: any;
215
237
  #shareArgsWithRenderer = false;
216
- // Cached converted images for Kitty protocol (which requires PNG), keyed by index
217
- #convertedImages: Map<number, { data: string; mimeType: string }> = new Map();
238
+ // Cached converted images for Kitty protocol (which requires PNG), keyed by index and source.
239
+ #convertedImages: Map<number, { data: string; mimeType: string; source: string }> = new Map();
240
+ #imageConversionGenerations = new Map<number, number>();
241
+ #imageConversionsInFlight = new Map<number, string>();
242
+
218
243
  // Spinner animation for partial task results
219
244
  #spinnerFrame?: number;
220
245
  #spinnerAnimation?: AnimationRegistration;
@@ -229,6 +254,10 @@ export class ToolExecutionComponent extends Container {
229
254
  expanded: false,
230
255
  isPartial: true,
231
256
  };
257
+ #onVisibleTranscriptMutation?: () => void;
258
+ #visibleTranscriptChanged = false;
259
+ #visibleProjection = "";
260
+ #disposed = false;
232
261
 
233
262
  constructor(
234
263
  toolName: string,
@@ -246,6 +275,8 @@ export class ToolExecutionComponent extends Container {
246
275
  this.#editFuzzyThreshold = options.editFuzzyThreshold;
247
276
  this.#editAllowFuzzy = options.editAllowFuzzy;
248
277
  this.#hashlineAutoDropPureInsertDuplicates = options.hashlineAutoDropPureInsertDuplicates;
278
+ this.#onVisibleTranscriptMutation = options.onVisibleTranscriptMutation;
279
+
249
280
  this.#tool = tool;
250
281
  this.#ui = ui;
251
282
  this.#cwd = cwd;
@@ -273,18 +304,23 @@ export class ToolExecutionComponent extends Container {
273
304
  this.#editMode = resolveEditModeForTool(toolName, tool);
274
305
 
275
306
  this.#updateDisplay();
307
+ this.#visibleProjection = this.#captureLogicalVisibleProjection();
308
+
276
309
  void this.#runPreviewDiff();
277
310
  }
278
311
 
279
312
  updateArgs(args: any, _toolCallId?: string): void {
313
+ const argsChanged = !Bun.deepEquals(this.#args, args);
314
+ if (!argsChanged && !this.#editMode) return;
280
315
  if (args !== this.#lastArgsReference) {
281
316
  this.#lastArgsReference = args;
282
317
  this.#argsIdentityVersion += 1;
283
318
  }
284
- this.#args = this.#shareArgsWithRenderer ? args : cloneToolArgs(args);
319
+ this.#args = argsChanged ? (this.#shareArgsWithRenderer ? args : cloneToolArgs(args)) : this.#args;
285
320
  this.#updateSpinnerAnimation();
286
321
  void this.#runPreviewDiff();
287
322
  this.#updateDisplay();
323
+ this.#markVisibleMutationIfChanged();
288
324
  }
289
325
 
290
326
  /**
@@ -295,6 +331,8 @@ export class ToolExecutionComponent extends Container {
295
331
  this.#argsComplete = true;
296
332
  this.#updateSpinnerAnimation();
297
333
  void this.#runPreviewDiff();
334
+ this.#updateDisplay();
335
+ this.#markVisibleMutationIfChanged();
298
336
  }
299
337
 
300
338
  async #runPreviewDiff(): Promise<void> {
@@ -338,14 +376,17 @@ export class ToolExecutionComponent extends Container {
338
376
  hashlineAutoDropPureInsertDuplicates: this.#hashlineAutoDropPureInsertDuplicates,
339
377
  isStreaming,
340
378
  });
341
- if (controller.signal.aborted) return;
379
+ if (this.#disposed || controller.signal.aborted) return;
342
380
  if (previews) {
343
- this.#editDiffPreview = isStreaming ? stabilizeStreamingPreviews(previews) : previews;
381
+ const nextPreview = isStreaming ? stabilizeStreamingPreviews(previews) : previews;
382
+ const changed = !previewsAreEqual(this.#editDiffPreview, nextPreview);
383
+ this.#editDiffPreview = nextPreview;
344
384
  this.#updateDisplay();
345
385
  this.#ui.requestRender();
386
+ if (changed) this.#markVisibleMutationIfChanged(true);
346
387
  }
347
388
  } catch (err) {
348
- if (controller.signal.aborted) return;
389
+ if (this.#disposed || controller.signal.aborted) return;
349
390
  logger.warn("Edit preview diff failed", { tool: this.#toolName, error: String(err) });
350
391
  }
351
392
  }
@@ -359,8 +400,10 @@ export class ToolExecutionComponent extends Container {
359
400
  isPartial = false,
360
401
  _toolCallId?: string,
361
402
  ): void {
403
+ if (this.#isPartial === isPartial && Bun.deepEquals(this.#result, result)) return;
362
404
  this.#textOutputCache = undefined;
363
405
  this.#result = result;
406
+ this.#invalidateStaleKittyConversions();
364
407
  this.#isPartial = isPartial;
365
408
  // When tool is complete, ensure args are marked complete so spinner stops
366
409
  if (!isPartial) {
@@ -370,6 +413,7 @@ export class ToolExecutionComponent extends Container {
370
413
  this.#updateDisplay();
371
414
  // Convert non-PNG images to PNG for Kitty protocol (async)
372
415
  this.#maybeConvertImagesForKitty();
416
+ this.#markVisibleMutationIfChanged();
373
417
  }
374
418
 
375
419
  /**
@@ -383,40 +427,101 @@ export class ToolExecutionComponent extends Container {
383
427
  return [...contentImages, ...detailImages];
384
428
  }
385
429
 
386
- /**
387
- * Convert non-PNG images to PNG for Kitty graphics protocol.
388
- * Kitty requires PNG format (f=100), so JPEG/GIF/WebP won't display.
389
- */
390
- #maybeConvertImagesForKitty(): void {
391
- // Only needed for Kitty protocol
392
- if (TERMINAL.imageProtocol !== ImageProtocol.Kitty) return;
393
- if (!this.#result) return;
394
-
395
- const imageBlocks = this.#getAllImageBlocks();
430
+ #imageSource(image: { data?: string; mimeType?: string }): string | undefined {
431
+ return image.data && image.mimeType ? `${image.mimeType}\u0000${image.data}` : undefined;
432
+ }
396
433
 
397
- for (let i = 0; i < imageBlocks.length; i++) {
398
- const img = imageBlocks[i];
399
- if (!img.data || !img.mimeType) continue;
400
- // Skip if already PNG or already converted
401
- if (img.mimeType === "image/png") continue;
402
- if (this.#convertedImages.has(i)) continue;
434
+ #invalidateStaleKittyConversions(): void {
435
+ const images = this.#getAllImageBlocks();
436
+ const indices = new Set<number>();
437
+ for (let index = 0; index < images.length; index++) {
438
+ const source = this.#imageSource(images[index]);
439
+ if (!source) continue;
440
+ indices.add(index);
441
+ if (this.#convertedImages.get(index)?.source !== source) this.#convertedImages.delete(index);
442
+ if (this.#imageConversionsInFlight.get(index) !== source) {
443
+ this.#imageConversionsInFlight.delete(index);
444
+ this.#imageConversionGenerations.set(index, (this.#imageConversionGenerations.get(index) ?? 0) + 1);
445
+ }
446
+ }
447
+ for (const index of this.#convertedImages.keys()) {
448
+ if (!indices.has(index)) this.#convertedImages.delete(index);
449
+ }
450
+ for (const index of this.#imageConversionsInFlight.keys()) {
451
+ if (!indices.has(index)) {
452
+ this.#imageConversionsInFlight.delete(index);
453
+ this.#imageConversionGenerations.set(index, (this.#imageConversionGenerations.get(index) ?? 0) + 1);
454
+ }
455
+ }
456
+ }
403
457
 
404
- // Convert async - catch errors from processing
405
- const index = i;
406
- new Bun.Image(Buffer.from(img.data, "base64"))
458
+ /** Convert non-PNG images to PNG for Kitty graphics protocol. */
459
+ #maybeConvertImagesForKitty(): void {
460
+ if (TERMINAL.imageProtocol !== ImageProtocol.Kitty) return;
461
+ for (const [index, image] of this.#getAllImageBlocks().entries()) {
462
+ const source = this.#imageSource(image);
463
+ if (!source || image.mimeType === "image/png") continue;
464
+ if (
465
+ this.#convertedImages.get(index)?.source === source ||
466
+ this.#imageConversionsInFlight.get(index) === source
467
+ )
468
+ continue;
469
+ const generation = this.#imageConversionGenerations.get(index) ?? 0;
470
+ this.#imageConversionsInFlight.set(index, source);
471
+ new Bun.Image(Buffer.from(image.data!, "base64"))
407
472
  .png()
408
473
  .toBase64()
409
474
  .then(data => {
410
- this.#convertedImages.set(index, { data, mimeType: "image/png" });
475
+ if (
476
+ this.#disposed ||
477
+ this.#imageConversionGenerations.get(index) !== generation ||
478
+ this.#imageConversionsInFlight.get(index) !== source ||
479
+ this.#imageSource(this.#getAllImageBlocks()[index] ?? {}) !== source
480
+ )
481
+ return;
482
+ this.#imageConversionsInFlight.delete(index);
483
+ this.#convertedImages.set(index, { data, mimeType: "image/png", source });
411
484
  this.#updateDisplay();
412
485
  this.#ui.requestRender();
486
+ this.#markVisibleMutationIfChanged(true);
413
487
  })
414
488
  .catch(() => {
415
- // Ignore conversion failures - display will use original image format
489
+ if (
490
+ this.#disposed ||
491
+ this.#imageConversionGenerations.get(index) !== generation ||
492
+ this.#imageConversionsInFlight.get(index) !== source
493
+ )
494
+ return;
495
+ this.#imageConversionsInFlight.delete(index);
416
496
  });
417
497
  }
418
498
  }
419
499
 
500
+ #captureLogicalVisibleProjection(): string {
501
+ return this.render(10_000).join("\n");
502
+ }
503
+
504
+ #markVisibleMutationIfChanged(notify = false): void {
505
+ const projection = this.#captureLogicalVisibleProjection();
506
+ if (projection === this.#visibleProjection) return;
507
+ this.#visibleProjection = projection;
508
+ this.#markVisibleMutation(notify);
509
+ }
510
+
511
+ #markVisibleMutation(notify = false): void {
512
+ if (notify) {
513
+ this.#onVisibleTranscriptMutation?.();
514
+ return;
515
+ }
516
+ this.#visibleTranscriptChanged = true;
517
+ }
518
+
519
+ consumeVisibleTranscriptChange(): boolean {
520
+ const changed = this.#visibleTranscriptChanged;
521
+ this.#visibleTranscriptChanged = false;
522
+ return changed;
523
+ }
524
+
420
525
  /**
421
526
  * Start or stop spinner animation based on whether this is a partial task result.
422
527
  */
@@ -430,6 +535,7 @@ export class ToolExecutionComponent extends Container {
430
535
  const needsSpinner = isStreamingArgs || isPartialTask;
431
536
  if (needsSpinner && !this.#spinnerAnimation) {
432
537
  this.#spinnerAnimation = registerAnimationCallback(() => {
538
+ if (this.#disposed) return;
433
539
  const frameCount = theme.spinnerFrames.length;
434
540
  if (frameCount === 0) return;
435
541
  this.#spinnerFrame = ((this.#spinnerFrame ?? -1) + 1) % frameCount;
@@ -456,6 +562,11 @@ export class ToolExecutionComponent extends Container {
456
562
  }
457
563
 
458
564
  override dispose(): void {
565
+ if (this.#disposed) return;
566
+ this.#disposed = true;
567
+ this.#imageConversionGenerations.clear();
568
+ this.#imageConversionsInFlight.clear();
569
+ this.#convertedImages.clear();
459
570
  this.stopAnimation();
460
571
  super.dispose();
461
572
  }
@@ -3,8 +3,12 @@ import * as fs from "node:fs";
3
3
  import { type Component, truncateToWidth, visibleWidth } from "@sayknow-cli/tui";
4
4
  import { formatCount, getProjectDir } from "@sayknow-cli/utils";
5
5
  import { $ } from "bun";
6
- import type { AppKeybinding, KeybindingsManager } from "../../config/keybindings";
7
- import { KEYBINDINGS } from "../../config/keybindings";
6
+ import {
7
+ type AppKeybinding,
8
+ KEYBINDINGS,
9
+ type KeybindingsManager,
10
+ type KeyDisplayContext,
11
+ } from "../../config/keybindings";
8
12
  import { settings } from "../../config/settings";
9
13
  import type { StatusLinePreset, StatusLineSegmentId, StatusLineSeparatorStyle } from "../../config/settings-schema";
10
14
  import { theme } from "../../modes/theme/theme";
@@ -46,6 +50,7 @@ export interface StatusLineSettings {
46
50
  previewHighlightSegment?: StatusLineSegmentId;
47
51
  showHookStatus?: boolean;
48
52
  showSkillHud?: boolean;
53
+ showActionHints?: boolean;
49
54
  sessionAccent?: boolean;
50
55
  maxRows?: number;
51
56
  }
@@ -55,6 +60,7 @@ export interface StatusLineComponentOptions {
55
60
  actionRegistry?: ActionRegistry<void>;
56
61
  getKeybindings?: () => KeybindingsManager;
57
62
  focusDomain?: FocusDomain;
63
+ keyDisplayContext?: KeyDisplayContext;
58
64
  }
59
65
 
60
66
  export interface StatusLineActionHint {
@@ -64,16 +70,13 @@ export interface StatusLineActionHint {
64
70
 
65
71
  const ACTION_HINT_PRIORITY: readonly AppKeybinding[] = [
66
72
  "app.message.sendNow",
67
- "app.message.queue",
68
73
  "app.message.followUp",
74
+ "app.message.queue",
69
75
  "app.message.dequeue",
70
76
  "app.commandPalette.open",
71
77
  "app.plan.toggle",
72
78
  "app.mode.cycle",
73
- "app.thinking.cycle",
74
- "app.model.select",
75
79
  "app.model.cycleForward",
76
- "app.history.search",
77
80
  "app.session.togglePath",
78
81
  "app.session.toggleSort",
79
82
  "app.session.rename",
@@ -92,6 +95,7 @@ export function getAvailableActionHints(
92
95
  getKeybindings: (() => KeybindingsManager) | undefined,
93
96
  width: number,
94
97
  domain: FocusDomain = "composer",
98
+ keyDisplayContext?: KeyDisplayContext,
95
99
  ): StatusLineActionHint[] {
96
100
  if (!actionRegistry || !getKeybindings || width <= 0) return [];
97
101
  const keybindings = getKeybindings();
@@ -108,7 +112,9 @@ export function getAvailableActionHints(
108
112
  if (!(bindingId in KEYBINDINGS)) continue;
109
113
  const keys = keybindings.getKeys(bindingId);
110
114
  if (keys.length === 0) continue;
111
- const content = theme.fg("dim", keybindings.getDisplayString(bindingId)) + theme.fg("muted", ` ${action.title}`);
115
+ const content =
116
+ theme.fg("dim", keybindings.getDisplayString(bindingId, keyDisplayContext)) +
117
+ theme.fg("muted", ` ${action.title}`);
112
118
  const nextWidth = visibleWidth(content) + (selected.length === 0 ? 0 : 3);
113
119
  if (used + nextWidth > width) break;
114
120
  selected.push({ id: action.id, content });
@@ -160,6 +166,7 @@ export class StatusLineComponent implements Component {
160
166
  #actionRegistry: ActionRegistry<void> | undefined;
161
167
  #getKeybindings: (() => KeybindingsManager) | undefined;
162
168
  #focusDomain: FocusDomain;
169
+ #keyDisplayContext: KeyDisplayContext | undefined;
163
170
 
164
171
  #resolvedSettingsCache:
165
172
  | (Required<Pick<StatusLineSettings, "leftSegments" | "rightSegments" | "separator" | "segmentOptions">> &
@@ -199,6 +206,7 @@ export class StatusLineComponent implements Component {
199
206
  separator: settings.get("statusLine.separator"),
200
207
  showHookStatus: settings.get("statusLine.showHookStatus"),
201
208
  showSkillHud: settings.get("statusLine.showSkillHud"),
209
+ showActionHints: settings.get("statusLine.showActionHints"),
202
210
  segmentOptions: settings.getGroup("statusLine").segmentOptions,
203
211
  sessionAccent: settings.get("statusLine.sessionAccent"),
204
212
  maxRows: settings.get("statusLine.maxRows"),
@@ -207,6 +215,7 @@ export class StatusLineComponent implements Component {
207
215
  this.#actionRegistry = options.actionRegistry;
208
216
  this.#getKeybindings = options.getKeybindings;
209
217
  this.#focusDomain = options.focusDomain ?? "composer";
218
+ this.#keyDisplayContext = options.keyDisplayContext;
210
219
  }
211
220
 
212
221
  updateSettings(settings: StatusLineSettings): void {
@@ -791,7 +800,16 @@ export class StatusLineComponent implements Component {
791
800
  }
792
801
 
793
802
  const right: string[] = [];
794
- const actionHints = getAvailableActionHints(this.#actionRegistry, this.#getKeybindings, width, this.#focusDomain);
803
+ const actionHints =
804
+ effectiveSettings.showActionHints === false
805
+ ? []
806
+ : getAvailableActionHints(
807
+ this.#actionRegistry,
808
+ this.#getKeybindings,
809
+ width,
810
+ this.#focusDomain,
811
+ this.#keyDisplayContext,
812
+ );
795
813
  for (const segId of effectiveSettings.rightSegments) {
796
814
  const rendered = renderSegment(segId, ctx);
797
815
  if (rendered.visible && rendered.content) {
@@ -1,6 +1,7 @@
1
1
  import { type Component, padding, TERMINAL, truncateToWidth, visibleWidth } from "@sayknow-cli/tui";
2
2
  import { APP_NAME } from "@sayknow-cli/utils";
3
3
  import { formatBuildLabel } from "../../build-metadata";
4
+ import { formatKeyHint, type KeyDisplayContext } from "../../config/keybindings";
4
5
  import { t } from "../../i18n";
5
6
  import { type ThemeColor, theme } from "../../modes/theme/theme";
6
7
 
@@ -20,28 +21,31 @@ export interface WelcomeComponentOptions {
20
21
  getViewportRows?: () => number | undefined;
21
22
  getReservedBottomRows?: (termWidth: number) => number;
22
23
  changelogMarkdown?: string;
24
+ rightGutterWidth?: number;
23
25
  collapseChangelog?: boolean;
24
26
  buildLabel?: string;
27
+ keyDisplayContext?: KeyDisplayContext;
25
28
  }
26
29
 
27
30
  const WELCOME_STATIC_RIGHT_ROWS_EXCLUDING_DYNAMIC_SECTIONS = 15;
28
31
  const DEFAULT_WHATS_NEW_ROWS = 3;
29
32
  const MAX_WHATS_NEW_ROWS = 12;
30
- const NEWLINE_FLOW_KEY =
31
- process.platform === "win32" ? { key: "alt+enter", label: "newline" } : { key: "ctrl+j", label: "newline" };
32
-
33
- const FLOW_KEY_ITEMS: ReadonlyArray<{ key: string; label: string }> = [
34
- { key: "/", label: "commands" },
35
- { key: "#", label: "actions" },
36
- { key: "!", label: "shell" },
37
- { key: "$", label: "python" },
38
- { key: "?", label: "keymap" },
39
- { key: "ctrl+l", label: "model" },
40
- { key: "shift+tab", label: "reasoning" },
41
- { key: "tab", label: "complete" },
42
- NEWLINE_FLOW_KEY,
43
- { key: "ctrl+c", label: "clear" },
44
- ];
33
+
34
+ function flowKeyItems(context: KeyDisplayContext): ReadonlyArray<{ key: string; label: string }> {
35
+ const newlineKey = context.platform === "win32" ? "alt+enter" : "ctrl+j";
36
+ return [
37
+ { key: "/", label: "commands" },
38
+ { key: "#", label: "actions" },
39
+ { key: "!", label: "shell" },
40
+ { key: "$", label: "python" },
41
+ { key: "?", label: "keymap" },
42
+ { key: "ctrl+l", label: "model" },
43
+ { key: "shift+tab", label: "reasoning" },
44
+ { key: "tab", label: "complete" },
45
+ { key: newlineKey, label: "newline" },
46
+ { key: "ctrl+c", label: "clear" },
47
+ ];
48
+ }
45
49
 
46
50
  /**
47
51
  * Sayknow-CLI launch surface: a blue-gradient SAYKNOW wordmark, compact
@@ -109,7 +113,8 @@ export class WelcomeComponent implements Component {
109
113
  }
110
114
 
111
115
  render(termWidth: number): string[] {
112
- const boxWidth = Math.max(0, termWidth);
116
+ const rightGutterWidth = this.#rightGutterWidth(termWidth);
117
+ const boxWidth = Math.max(0, termWidth - rightGutterWidth);
113
118
  if (boxWidth < 4) {
114
119
  return [];
115
120
  }
@@ -264,7 +269,7 @@ export class WelcomeComponent implements Component {
264
269
  lines.push(tl + titleStyled + theme.fg("dim", hChar.repeat(afterTitle)) + tr);
265
270
  }
266
271
  if (outputRows === 1) {
267
- return lines;
272
+ return this.#withRightGutter(lines, rightGutterWidth);
268
273
  }
269
274
 
270
275
  if (showRightColumn) {
@@ -292,7 +297,7 @@ export class WelcomeComponent implements Component {
292
297
  lines.push(bl + h.repeat(leftCol) + br);
293
298
  }
294
299
 
295
- return lines;
300
+ return this.#withRightGutter(lines, rightGutterWidth);
296
301
  }
297
302
 
298
303
  /** Center text within a given width */
@@ -314,6 +319,19 @@ export class WelcomeComponent implements Component {
314
319
  }
315
320
  return str + padding(width - visLen);
316
321
  }
322
+ #rightGutterWidth(termWidth: number): number {
323
+ const configured = this.options.rightGutterWidth ?? 0;
324
+ if (!Number.isFinite(configured) || configured <= 0) return 0;
325
+ const gutterWidth = Math.floor(configured);
326
+ return Math.min(gutterWidth, Math.max(0, termWidth - 4));
327
+ }
328
+
329
+ #withRightGutter(lines: string[], rightGutterWidth: number): string[] {
330
+ if (rightGutterWidth <= 0) return lines;
331
+ const gutter = padding(rightGutterWidth);
332
+ return lines.map(line => line + gutter);
333
+ }
334
+
317
335
  #targetRows(termWidth: number): number | undefined {
318
336
  const viewportRows = this.options.getViewportRows?.();
319
337
  if (typeof viewportRows !== "number" || !Number.isFinite(viewportRows) || viewportRows <= 0) {
@@ -347,7 +365,8 @@ export class WelcomeComponent implements Component {
347
365
  }
348
366
 
349
367
  #flowKeyItemText(item: { key: string; label: string }): string {
350
- return `${theme.fg("dim", item.key)}${theme.fg("muted", ` ${this.#flowKeyLabel(item.label)}`)}`;
368
+ const context = this.options.keyDisplayContext ?? { platform: process.platform };
369
+ return `${theme.fg("dim", formatKeyHint(item.key, context))}${theme.fg("muted", ` ${this.#flowKeyLabel(item.label)}`)}`;
351
370
  }
352
371
 
353
372
  #flowKeyLabel(label: string): string {
@@ -376,7 +395,7 @@ export class WelcomeComponent implements Component {
376
395
  const separator = ` ${theme.fg("dim", "·")} `;
377
396
  const rows: string[] = [];
378
397
  let current = "";
379
- for (const item of FLOW_KEY_ITEMS) {
398
+ for (const item of flowKeyItems(this.options.keyDisplayContext ?? { platform: process.platform })) {
380
399
  const segment = this.#flowKeyItemText(item);
381
400
  const next = current ? `${current}${separator}${segment}` : segment;
382
401
  if (current && visibleWidth(next) > contentWidth) {
@@ -395,7 +414,7 @@ export class WelcomeComponent implements Component {
395
414
  const rowLimit = Math.max(1, Math.floor(maxRows));
396
415
  if (rows.length <= rowLimit) return rows;
397
416
  if (rowLimit === 1) {
398
- const firstItem = FLOW_KEY_ITEMS[0];
417
+ const firstItem = flowKeyItems(this.options.keyDisplayContext ?? { platform: process.platform })[0];
399
418
  const firstSegment = firstItem ? this.#flowKeyItemText(firstItem) : theme.fg("dim", "keys");
400
419
  return [this.#fitToWidth(` ${firstSegment} ${theme.fg("dim", "· … ")}${theme.bold("/help")}`, width)];
401
420
  }
@@ -679,8 +698,16 @@ function gradientLogo(lines: readonly string[], phase = 0, shine?: ShineConfig):
679
698
 
680
699
  /** Total length of the intro animation. */
681
700
  const INTRO_MS = 3000;
682
- /** Render cadence during the intro (~30fps). */
683
- const INTRO_TICK_MS = 33;
701
+ /** Resolve the intro cadence without making tests mutate global process state. */
702
+ export function resolveWelcomeIntroTickMs(
703
+ platform: NodeJS.Platform = process.platform,
704
+ tmux = process.env.TMUX,
705
+ ): number {
706
+ return platform === "win32" && tmux ? 100 : 33;
707
+ }
708
+
709
+ /** Render at 30fps directly, but cap native Windows multiplexers at 10fps to avoid ConPTY output backpressure. */
710
+ const INTRO_TICK_MS = resolveWelcomeIntroTickMs();
684
711
  /** Number of full gradient rotations the sweep performs before settling. */
685
712
  const INTRO_SWEEPS = 2.5;
686
713
  /** Number of times the shine highlight crosses the diagonal across the intro. */