@sayknow-cli/coding-agent 0.4.7 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (428) hide show
  1. package/CHANGELOG.md +122 -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 +20 -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
@@ -197,21 +197,30 @@ export function mapAgentSessionEventToAcpSessionUpdates(
197
197
  return [toSessionNotification(sessionId, update)];
198
198
  }
199
199
  case "tool_execution_end": {
200
- const resultContent = [...extractDiffToolCallContent(event.result), ...extractToolCallContent(event.result)];
201
- const content = mergeToolUpdateContent(
202
- buildToolStartContent(event.toolName, getToolExecutionEndArgs(event, options)),
203
- resultContent,
204
- );
200
+ const args = getToolExecutionEndArgs(event, options);
201
+ const resultContent = [
202
+ ...extractDiffToolCallContent(event.result, options.cwd),
203
+ ...extractToolCallContent(event.result),
204
+ ];
205
+ const content = mergeToolUpdateContent(buildToolStartContent(event.toolName, args), resultContent);
205
206
  const update: SessionUpdate = {
206
207
  sessionUpdate: "tool_call_update",
207
208
  toolCallId: event.toolCallId,
208
209
  status: event.isError ? "failed" : "completed",
209
210
  rawOutput: event.result,
210
211
  };
212
+ if (event.isError) {
213
+ // Failure is carried by `status`; `kind` stays the tool's real category so
214
+ // clients keep the icon/treatment established by the initial `tool_call`.
215
+ update.title = `Failed: ${buildToolTitle(event.toolName, args, undefined)}`;
216
+ }
211
217
  if (content.length > 0) {
212
218
  update.content = content;
213
219
  }
214
- const locations = extractToolLocationsFromResult(event.result, options.cwd);
220
+ const locations = mergeToolLocations(
221
+ extractToolLocations(args, options.cwd),
222
+ extractToolLocationsFromResult(event.result, options.cwd),
223
+ );
215
224
  if (locations.length > 0) {
216
225
  update.locations = locations;
217
226
  }
@@ -295,14 +304,83 @@ export function mapAgentSessionEventToAcpSessionUpdates(
295
304
  return [toSessionNotification(sessionId, { sessionUpdate: "session_info_update", _meta: meta })];
296
305
  }
297
306
  case "auto_retry_start":
307
+ return [
308
+ toSessionNotification(sessionId, {
309
+ sessionUpdate: "session_info_update",
310
+ _meta: {
311
+ skcPhase: "retrying",
312
+ skcRetryState: "waiting",
313
+ skcRetryAttempt: event.attempt,
314
+ skcRetryMaxAttempts: event.maxAttempts,
315
+ skcRetryDelayMs: event.delayMs,
316
+ skcRetryErrorMessage: event.errorMessage,
317
+ skcRetryUnbounded: event.unbounded ?? false,
318
+ running: true,
319
+ skcRunning: true,
320
+ },
321
+ }),
322
+ ];
298
323
  case "auto_retry_end":
324
+ return [
325
+ toSessionNotification(sessionId, {
326
+ sessionUpdate: "session_info_update",
327
+ _meta: {
328
+ skcPhase: event.success ? "responding" : "retry_failed",
329
+ skcRetryState: event.success ? "succeeded" : "failed",
330
+ skcRetryAttempt: event.attempt,
331
+ ...(event.finalError ? { skcRetryFinalError: event.finalError } : {}),
332
+ running: true,
333
+ skcRunning: true,
334
+ },
335
+ }),
336
+ ];
299
337
  case "ttsr_triggered":
338
+ return [
339
+ toSessionNotification(sessionId, {
340
+ sessionUpdate: "session_info_update",
341
+ _meta: {
342
+ skcTtsrTriggered: true,
343
+ skcTtsrRuleCount: event.rules.length,
344
+ },
345
+ }),
346
+ ];
300
347
  case "irc_message":
301
348
  case "subagent_steer_message":
349
+ return [];
302
350
  case "notice":
351
+ return [
352
+ toSessionNotification(sessionId, {
353
+ sessionUpdate: "agent_thought_chunk",
354
+ content: {
355
+ type: "text",
356
+ text: `[${event.level}${event.source ? `:${event.source}` : ""}] ${event.message}\n`,
357
+ },
358
+ }),
359
+ ];
303
360
  case "thinking_level_changed":
361
+ return [
362
+ toSessionNotification(sessionId, {
363
+ sessionUpdate: "session_info_update",
364
+ _meta: { skcThinkingLevel: event.thinkingLevel ?? "off" },
365
+ }),
366
+ ];
304
367
  case "goal_updated":
305
- return [];
368
+ return [
369
+ toSessionNotification(sessionId, {
370
+ sessionUpdate: "session_info_update",
371
+ _meta: {
372
+ skcGoalActive: event.goal !== null,
373
+ ...(event.goal
374
+ ? {
375
+ skcGoalId: event.goal.id,
376
+ skcGoalStatus: event.goal.status,
377
+ skcGoalObjective: event.goal.objective,
378
+ }
379
+ : {}),
380
+ ...(event.state ? { skcGoalModeState: event.state } : {}),
381
+ },
382
+ }),
383
+ ];
306
384
  default:
307
385
  return assertNeverAcp(event);
308
386
  }
@@ -547,6 +625,19 @@ function mergeToolUpdateContent(startContent: ToolCallContent[], resultContent:
547
625
  return merged;
548
626
  }
549
627
 
628
+ function mergeToolLocations(...groups: ToolCallLocation[][]): ToolCallLocation[] {
629
+ const locations: ToolCallLocation[] = [];
630
+ const seen = new Set<string>();
631
+ for (const group of groups) {
632
+ for (const location of group) {
633
+ if (seen.has(location.path)) continue;
634
+ seen.add(location.path);
635
+ locations.push(location);
636
+ }
637
+ }
638
+ return locations;
639
+ }
640
+
550
641
  function isCommandToolName(toolName: string): boolean {
551
642
  return toolName === "bash" || toolName === "shell" || toolName === "exec";
552
643
  }
@@ -626,7 +717,7 @@ function extractToolLocationsFromResult(result: unknown, cwd?: string): ToolCall
626
717
  }
627
718
 
628
719
  /** Emit a `diff` ToolCallContent for each per-file edit result that carries oldText/newText. */
629
- function extractDiffToolCallContent(result: unknown): ToolCallContent[] {
720
+ function extractDiffToolCallContent(result: unknown, cwd?: string): ToolCallContent[] {
630
721
  if (typeof result !== "object" || result === null) return [];
631
722
  const details = (result as { details?: unknown }).details;
632
723
  if (typeof details !== "object" || details === null) return [];
@@ -634,13 +725,13 @@ function extractDiffToolCallContent(result: unknown): ToolCallContent[] {
634
725
  const perFile = (details as { perFileResults?: unknown }).perFileResults;
635
726
  const entries: unknown[] = Array.isArray(perFile) ? perFile : [details];
636
727
  for (const entry of entries) {
637
- const block = buildDiffContent(entry);
728
+ const block = buildDiffContent(entry, cwd);
638
729
  if (block) blocks.push(block);
639
730
  }
640
731
  return blocks;
641
732
  }
642
733
 
643
- function buildDiffContent(entry: unknown): ToolCallContent | undefined {
734
+ function buildDiffContent(entry: unknown, cwd?: string): ToolCallContent | undefined {
644
735
  if (typeof entry !== "object" || entry === null) return undefined;
645
736
  const candidate = entry as { path?: unknown; oldText?: unknown; newText?: unknown; isError?: unknown };
646
737
  if (candidate.isError === true) return undefined;
@@ -651,7 +742,8 @@ function buildDiffContent(entry: unknown): ToolCallContent | undefined {
651
742
  if (oldText === undefined && newText === undefined) return undefined;
652
743
  return {
653
744
  type: "diff",
654
- path,
745
+ // `Diff.path` is specified as an absolute path, same as ToolCallLocation.
746
+ path: toAcpLocationPath(path, cwd),
655
747
  oldText: oldText ?? null,
656
748
  newText: newText ?? "",
657
749
  };
@@ -772,7 +864,9 @@ function toToolCallContent(value: unknown): ToolCallContent | undefined {
772
864
  resourceLinkContent.mimeType = mimeType;
773
865
  }
774
866
  const size = extractNumberProperty<ResourceLinkLikeContent>(value, "size");
775
- if (size !== undefined) {
867
+ // `ResourceLink.size` is a byte count typed as int64; drop fractional, negative,
868
+ // or non-representable values rather than emitting a block clients must reject.
869
+ if (size !== undefined && Number.isSafeInteger(size) && size >= 0) {
776
870
  resourceLinkContent.size = size;
777
871
  }
778
872
  return {
@@ -1,6 +1,6 @@
1
1
  import * as stream from "node:stream";
2
- import { AgentSideConnection, ndJsonStream, type Stream } from "@agentclientprotocol/sdk";
3
- import { AcpAgent } from "./acp-agent";
2
+ import { type Agent, AgentSideConnection, ndJsonStream, type Stream } from "@agentclientprotocol/sdk";
3
+ import { AcpAgent, acpRequestFailure } from "./acp-agent";
4
4
  import type { AcpStartupOptions } from "./startup-options";
5
5
 
6
6
  export interface AcpModeOptions {
@@ -8,8 +8,44 @@ export interface AcpModeOptions {
8
8
  startupOptions?: AcpStartupOptions;
9
9
  }
10
10
 
11
+ /**
12
+ * Every agent method reports failures to the client, and the SDK only derives a
13
+ * JSON-RPC code from a `RequestError` — anything else collapses to an opaque
14
+ * `-32603 Internal error` with the reason buried in `data`. Translating once at the
15
+ * connection boundary keeps that contract in one place instead of asking each method
16
+ * to remember it.
17
+ */
18
+ function withAcpRequestFailures(agent: AcpAgent): Agent {
19
+ const wrapped = new Map<string | symbol, (...args: unknown[]) => unknown>();
20
+ return new Proxy(agent, {
21
+ get(target, property) {
22
+ // Read against the target, not the proxy: `signal`/`closed` are getters that
23
+ // touch private fields, and the proxy carries no private-field brand.
24
+ const value = Reflect.get(target, property, target);
25
+ if (typeof value !== "function") return value;
26
+ const cached = wrapped.get(property);
27
+ if (cached) return cached;
28
+ // Cache per property so repeated reads keep a stable method identity.
29
+ const wrapper = (...args: unknown[]) => {
30
+ try {
31
+ const result = (value as (...a: unknown[]) => unknown).apply(target, args);
32
+ return result instanceof Promise
33
+ ? result.catch((error: unknown) => {
34
+ throw acpRequestFailure(error);
35
+ })
36
+ : result;
37
+ } catch (error) {
38
+ throw acpRequestFailure(error);
39
+ }
40
+ };
41
+ wrapped.set(property, wrapper);
42
+ return wrapper;
43
+ },
44
+ }) as unknown as Agent;
45
+ }
46
+
11
47
  export function createAcpConnection(transport: Stream, options: AcpModeOptions = {}): AgentSideConnection {
12
- return new AgentSideConnection(conn => new AcpAgent(conn, options), transport);
48
+ return new AgentSideConnection(conn => withAcpRequestFailures(new AcpAgent(conn, options)), transport);
13
49
  }
14
50
 
15
51
  export async function runAcpMode(options: AcpModeOptions = {}): Promise<never> {
@@ -49,7 +49,7 @@ export const APP_ACTION_METADATA: readonly ActionMetadata[] = [
49
49
  action("app.clipboard.copyPrompt", "Copy prompt", "Clipboard", ["composer"]),
50
50
  action("app.session.new", "New session", "Session", ["composer"]),
51
51
  action("app.session.tree", "Session tree", "Session", ["composer"]),
52
- action("app.session.fork", "Fork session", "Session", ["composer"]),
52
+ action("app.session.fork", "Branch from message", "Session", ["composer"]),
53
53
  action("app.session.resume", "Resume session", "Session", ["composer"]),
54
54
  action("app.session.observe", "Observe sessions", "Session", ["composer"]),
55
55
  action("app.session.dashboard", "Show sessions dashboard", "Session", ["composer"]),
@@ -84,8 +84,10 @@ export class AssistantMessageComponent extends Container {
84
84
  #lastMessage?: AssistantMessage;
85
85
  #toolImagesByCallId = new Map<string, ImageContent[]>();
86
86
  #usageInfo?: Usage;
87
- #convertedKittyImages = new Map<string, ImageContent>();
88
- #kittyConversionsInFlight = new Set<string>();
87
+ #convertedKittyImages = new Map<string, ImageContent & { source: string }>();
88
+ #kittyConversionsInFlight = new Map<string, string>();
89
+ #toolImageGenerations = new Map<string, number>();
90
+
89
91
  #responseHeader = new Text(theme.bold(theme.fg("statusLineModel", "sayknow")), 1, 0);
90
92
  #contentBlocksCache = new WeakMap<object, { source: string; component: Component }>();
91
93
  #lastStreaming = false;
@@ -94,11 +96,17 @@ export class AssistantMessageComponent extends Container {
94
96
  #nextContentBlockKey = 0;
95
97
  #reusableChildren = new WeakSet<Component>();
96
98
  #viewportAnchorId?: string;
99
+ #visibleSignature = "";
100
+ #visibleSignatureInitialized = false;
101
+ #toolImagesRevision = 0;
102
+ #convertedToolImagesRevision = 0;
103
+ #disposed = false;
97
104
  constructor(
98
105
  message?: AssistantMessage,
99
106
  private hideThinkingBlock = false,
100
107
  private readonly onImageUpdate?: () => void,
101
108
  viewportAnchorId?: string,
109
+ private readonly onVisibleMutation?: () => void,
102
110
  ) {
103
111
  super();
104
112
  this.#viewportAnchorId = viewportAnchorId;
@@ -109,6 +117,56 @@ export class AssistantMessageComponent extends Container {
109
117
  if (message) {
110
118
  this.updateContent(message);
111
119
  }
120
+ this.#visibleSignature = this.#visibleContentSignature();
121
+ this.#visibleSignatureInitialized = true;
122
+ }
123
+
124
+ #visibleContentSignature(): string {
125
+ const message = this.#lastMessage;
126
+ const content: string[] = [];
127
+ let hasMarkdownContent = false;
128
+ for (const block of message?.content ?? []) {
129
+ if (block.type === "text" && block.text.trim()) {
130
+ content.push(`text:${block.text.trim()}`);
131
+ hasMarkdownContent = true;
132
+ } else if (block.type === "thinking" && block.thinking.trim()) {
133
+ content.push(
134
+ this.hideThinkingBlock
135
+ ? "thinking"
136
+ : `thinking:${elideRunawayThinkingRepetition(block.thinking.trim())}`,
137
+ );
138
+ if (!this.hideThinkingBlock) hasMarkdownContent = true;
139
+ }
140
+ }
141
+ if (hasMarkdownContent) {
142
+ content.unshift(this.#lastStreaming ? "streaming" : "final");
143
+ }
144
+ const hasToolCalls = message?.content.some(block => block.type === "toolCall") ?? false;
145
+ if (!hasToolCalls && message?.stopReason === "aborted" && !isSilentAbort(message.errorMessage)) {
146
+ content.push(`aborted:${message.errorMessage ?? ""}`);
147
+ } else if (!hasToolCalls && message?.stopReason === "error") {
148
+ content.push(`error:${message.errorMessage ?? ""}`);
149
+ } else if (
150
+ message?.errorMessage &&
151
+ !isSilentAbort(message.errorMessage) &&
152
+ message.stopReason !== "aborted" &&
153
+ message.stopReason !== "error"
154
+ ) {
155
+ content.push(`message-error:${message.errorMessage}`);
156
+ }
157
+ if (settings.get("display.showTokenUsage") && this.#usageInfo) {
158
+ const usage = this.#usageInfo;
159
+ content.push(`usage:${usage.input}:${usage.cacheWrite}:${usage.output}:${usage.cacheRead}`);
160
+ }
161
+ content.push(`images:${this.#toolImagesRevision}:${this.#convertedToolImagesRevision}`);
162
+ return content.join("\u0000");
163
+ }
164
+
165
+ #notifyVisibleMutation(): void {
166
+ const after = this.#visibleContentSignature();
167
+ if (after === this.#visibleSignature) return;
168
+ this.#visibleSignature = after;
169
+ if (this.#visibleSignatureInitialized) this.onVisibleMutation?.();
112
170
  }
113
171
 
114
172
  #contentAnchorSource(
@@ -126,22 +184,36 @@ export class AssistantMessageComponent extends Container {
126
184
  }
127
185
 
128
186
  setHideThinkingBlock(hide: boolean): void {
187
+ if (this.hideThinkingBlock === hide) return;
129
188
  this.hideThinkingBlock = hide;
189
+ this.#notifyVisibleMutation();
130
190
  }
131
191
 
132
192
  setToolResultImages(toolCallId: string, images: ImageContent[]): void {
133
193
  if (!toolCallId) return;
134
194
  const validImages = images.filter(img => img.type === "image" && img.data && img.mimeType);
195
+ const previousImages = this.#toolImagesByCallId.get(toolCallId);
196
+ if (
197
+ (previousImages?.length ?? 0) === validImages.length &&
198
+ (previousImages?.every(
199
+ (image, index) =>
200
+ image.type === validImages[index]?.type &&
201
+ image.data === validImages[index]?.data &&
202
+ image.mimeType === validImages[index]?.mimeType,
203
+ ) ??
204
+ true)
205
+ ) {
206
+ return;
207
+ }
135
208
  for (const key of Array.from(this.#convertedKittyImages.keys())) {
136
- if (key.startsWith(`${toolCallId}:`)) {
137
- this.#convertedKittyImages.delete(key);
138
- }
209
+ if (key.startsWith(`${toolCallId}:`)) this.#convertedKittyImages.delete(key);
139
210
  }
140
- for (const key of Array.from(this.#kittyConversionsInFlight)) {
141
- if (key.startsWith(`${toolCallId}:`)) {
142
- this.#kittyConversionsInFlight.delete(key);
143
- }
211
+ for (const key of Array.from(this.#kittyConversionsInFlight.keys())) {
212
+ if (key.startsWith(`${toolCallId}:`)) this.#kittyConversionsInFlight.delete(key);
144
213
  }
214
+ this.#toolImageGenerations.set(toolCallId, (this.#toolImageGenerations.get(toolCallId) ?? 0) + 1);
215
+
216
+ this.#toolImagesRevision += 1;
145
217
  if (validImages.length === 0) {
146
218
  this.#toolImagesByCallId.delete(toolCallId);
147
219
  } else {
@@ -153,30 +225,49 @@ export class AssistantMessageComponent extends Container {
153
225
  }
154
226
  }
155
227
 
228
+ #toolImageSource(image: ImageContent): string {
229
+ return `${image.mimeType}\u0000${image.data}`;
230
+ }
231
+
156
232
  #convertToolImagesForKitty(toolCallId: string, images: ImageContent[]): void {
157
233
  if (TERMINAL.imageProtocol !== ImageProtocol.Kitty) return;
158
- for (let index = 0; index < images.length; index++) {
159
- const image = images[index];
160
- if (!image || image.mimeType === "image/png") continue;
234
+ const generation = this.#toolImageGenerations.get(toolCallId) ?? 0;
235
+ for (const [index, image] of images.entries()) {
236
+ if (image.mimeType === "image/png") continue;
161
237
  const key = `${toolCallId}:${index}`;
162
- if (this.#convertedKittyImages.has(key) || this.#kittyConversionsInFlight.has(key)) continue;
163
- this.#kittyConversionsInFlight.add(key);
238
+ const source = this.#toolImageSource(image);
239
+ if (
240
+ this.#convertedKittyImages.get(key)?.source === source ||
241
+ this.#kittyConversionsInFlight.get(key) === source
242
+ )
243
+ continue;
244
+ this.#kittyConversionsInFlight.set(key, source);
164
245
  new Bun.Image(Buffer.from(image.data, "base64"))
165
246
  .png()
166
247
  .toBase64()
167
248
  .then(data => {
249
+ const currentImage = this.#toolImagesByCallId.get(toolCallId)?.[index];
250
+ if (
251
+ this.#disposed ||
252
+ this.#toolImageGenerations.get(toolCallId) !== generation ||
253
+ this.#kittyConversionsInFlight.get(key) !== source ||
254
+ !currentImage ||
255
+ this.#toolImageSource(currentImage) !== source
256
+ )
257
+ return;
168
258
  this.#kittyConversionsInFlight.delete(key);
169
- this.#convertedKittyImages.set(key, {
170
- type: "image",
171
- data,
172
- mimeType: "image/png",
173
- });
174
- if (this.#lastMessage) {
175
- this.updateContent(this.#lastMessage, { streaming: this.#lastStreaming });
176
- }
259
+ this.#convertedKittyImages.set(key, { type: "image", data, mimeType: "image/png", source });
260
+ this.#convertedToolImagesRevision += 1;
261
+ if (this.#lastMessage) this.updateContent(this.#lastMessage, { streaming: this.#lastStreaming });
177
262
  this.onImageUpdate?.();
178
263
  })
179
264
  .catch(() => {
265
+ if (
266
+ this.#disposed ||
267
+ this.#toolImageGenerations.get(toolCallId) !== generation ||
268
+ this.#kittyConversionsInFlight.get(key) !== source
269
+ )
270
+ return;
180
271
  this.#kittyConversionsInFlight.delete(key);
181
272
  });
182
273
  }
@@ -487,5 +578,15 @@ export class AssistantMessageComponent extends Container {
487
578
  }
488
579
 
489
580
  this.#reconcileChildren(descriptors);
581
+ this.#notifyVisibleMutation();
582
+ }
583
+
584
+ override dispose(): void {
585
+ if (this.#disposed) return;
586
+ this.#disposed = true;
587
+ this.#toolImageGenerations.clear();
588
+ this.#kittyConversionsInFlight.clear();
589
+ this.#convertedKittyImages.clear();
590
+ super.dispose();
490
591
  }
491
592
  }
@@ -1,4 +1,10 @@
1
1
  import { type Component, Container, Markdown, Spacer, Text, type TUI } from "@sayknow-cli/tui";
2
+ import {
3
+ BTW_MAX_ANSWER_UTF8_BYTES,
4
+ BTW_MAX_CONTEXT_TURNS,
5
+ sanitizeBtwError,
6
+ truncateUtf8,
7
+ } from "../../session/btw-contract";
2
8
  import { replaceTabs } from "../../tools/render-utils";
3
9
  import { getMarkdownTheme, theme } from "../theme/theme";
4
10
  import { DynamicBorder } from "./dynamic-border";
@@ -10,12 +16,21 @@ interface BtwPanelComponentOptions {
10
16
  tui: TUI;
11
17
  }
12
18
 
19
+ interface BtwTurn {
20
+ question: string;
21
+ answer: string;
22
+ state: BtwPanelState;
23
+ errorMessage?: string;
24
+ }
25
+
13
26
  export class BtwPanelComponent extends Container {
14
27
  #question: string;
15
28
  #tui: TUI;
16
29
  #state: BtwPanelState = "running";
17
30
  #answer = "";
18
31
  #errorMessage: string | undefined;
32
+ #completedTurns: BtwTurn[] = [];
33
+ #streamingContent = new Container();
19
34
  #closed = false;
20
35
 
21
36
  constructor(options: BtwPanelComponentOptions) {
@@ -25,16 +40,29 @@ export class BtwPanelComponent extends Container {
25
40
  this.#rebuild();
26
41
  }
27
42
 
43
+ beginTurn(question: string): void {
44
+ if (this.#closed) return;
45
+ if (this.#state === "complete") {
46
+ this.#completedTurns.push(this.#currentTurn());
47
+ if (this.#completedTurns.length > BTW_MAX_CONTEXT_TURNS) this.#completedTurns.shift();
48
+ }
49
+ this.#question = question;
50
+ this.#answer = "";
51
+ this.#errorMessage = undefined;
52
+ this.#state = "running";
53
+ this.#rebuild();
54
+ }
55
+
28
56
  appendText(delta: string): void {
29
57
  if (!delta || this.#closed) return;
30
- this.#answer += delta;
31
- this.#rebuild();
58
+ this.#answer = truncateUtf8(this.#answer + delta, BTW_MAX_ANSWER_UTF8_BYTES);
59
+ this.#updateStreamingContent();
32
60
  }
33
61
 
34
62
  setAnswer(text: string): void {
35
63
  if (this.#closed) return;
36
- this.#answer = text;
37
- this.#rebuild();
64
+ this.#answer = truncateUtf8(text, BTW_MAX_ANSWER_UTF8_BYTES);
65
+ this.#updateStreamingContent();
38
66
  }
39
67
 
40
68
  markComplete(): void {
@@ -54,21 +82,33 @@ export class BtwPanelComponent extends Container {
54
82
  markError(message: string): void {
55
83
  if (this.#closed) return;
56
84
  this.#state = "error";
57
- this.#errorMessage = message;
85
+ this.#answer = "";
86
+ this.#errorMessage = sanitizeBtwError(message);
58
87
  this.#rebuild();
59
88
  }
60
89
 
61
90
  close(): void {
62
91
  this.#closed = true;
92
+ this.#question = "";
93
+ this.#completedTurns = [];
94
+ this.#answer = "";
95
+ this.#errorMessage = undefined;
96
+ this.#streamingContent.clear();
97
+ this.clear();
63
98
  }
64
99
 
65
100
  #rebuild(): void {
66
101
  this.clear();
67
102
  this.addChild(new DynamicBorder(str => theme.fg("dim", str)));
68
103
  this.addChild(new Spacer(1));
104
+ for (const turn of this.#completedTurns) {
105
+ this.addChild(this.#turnComponent(turn));
106
+ this.addChild(new Spacer(1));
107
+ }
69
108
  this.addChild(new Text(theme.fg("accent", replaceTabs(this.#question)), 1, 0));
70
109
  this.addChild(new Spacer(1));
71
- this.addChild(this.#contentComponent());
110
+ this.#updateStreamingContent(false);
111
+ this.addChild(this.#streamingContent);
72
112
  this.addChild(new Spacer(1));
73
113
  this.addChild(new Text(this.#footerLine(), 1, 0));
74
114
  this.addChild(new Spacer(1));
@@ -76,29 +116,55 @@ export class BtwPanelComponent extends Container {
76
116
  this.#tui.requestRender();
77
117
  }
78
118
 
119
+ #updateStreamingContent(requestRender = true): void {
120
+ this.#streamingContent.clear();
121
+ this.#streamingContent.addChild(this.#contentComponent(this.#state, this.#answer, this.#errorMessage));
122
+ if (requestRender) this.#tui.requestRender();
123
+ }
124
+
125
+ #currentTurn(): BtwTurn {
126
+ return {
127
+ question: this.#question,
128
+ answer: this.#answer,
129
+ state: this.#state,
130
+ errorMessage: this.#errorMessage,
131
+ };
132
+ }
133
+
134
+ #turnComponent(turn: BtwTurn): Component {
135
+ const container = new Container();
136
+ container.addChild(new Text(theme.fg("accent", replaceTabs(turn.question)), 1, 0));
137
+ container.addChild(new Spacer(1));
138
+ container.addChild(this.#contentComponent(turn.state, turn.answer, turn.errorMessage));
139
+ return container;
140
+ }
141
+
79
142
  #footerLine(): string {
80
143
  switch (this.#state) {
81
144
  case "running":
82
- return theme.fg("muted", "Esc cancel /btw");
145
+ return theme.fg("muted", "Esc cancel /btw and return to main chat");
83
146
  case "complete":
84
- return theme.fg("muted", "Esc dismiss");
147
+ return theme.fg("muted", "Type a follow-up · Esc return to main chat");
85
148
  case "aborted":
86
- return theme.fg("warning", `${theme.status.warning} Cancelled · Esc dismiss`);
149
+ return theme.fg(
150
+ "warning",
151
+ `${theme.status.warning} Cancelled · Type a follow-up · Esc return to main chat`,
152
+ );
87
153
  case "error":
88
- return theme.fg("error", `${theme.status.error} Error · Esc dismiss`);
154
+ return theme.fg("error", `${theme.status.error} Error · Type a follow-up · Esc return to main chat`);
89
155
  }
90
156
  }
91
157
 
92
- #contentComponent(): Component {
93
- if (this.#state === "error") {
94
- return new Text(theme.fg("error", replaceTabs(this.#errorMessage ?? "Unknown error")), 1, 0);
158
+ #contentComponent(state: BtwPanelState, answer: string, errorMessage: string | undefined): Component {
159
+ if (state === "error") {
160
+ return new Text(theme.fg("error", replaceTabs(errorMessage ?? "Unknown error")), 1, 0);
95
161
  }
96
- const text = replaceTabs(this.#answer).trim();
162
+ const text = replaceTabs(answer).trim();
97
163
  if (!text) {
98
- const waiting =
99
- this.#state === "running" ? `${theme.status.pending} Waiting for response…` : "No text returned.";
164
+ const waiting = state === "running" ? `${theme.status.pending} Waiting for response…` : "No text returned.";
100
165
  return new Text(theme.fg("dim", waiting), 1, 0);
101
166
  }
167
+ if (state === "running") return new Text(text, 1, 0);
102
168
  return new Markdown(text, 1, 0, getMarkdownTheme());
103
169
  }
104
170
  }