@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
@@ -167,6 +167,8 @@ export interface SessionStorage {
167
167
  readSnapshotSync?(path: string): SessionStorageSnapshot;
168
168
  statSync(path: string): SessionStorageStat;
169
169
  listFilesSync(dir: string, pattern: string): string[];
170
+ /** List matching files with mtimes without issuing one JavaScript stat call per path. */
171
+ listFilesByMtime?(dir: string, pattern: string): Promise<Array<{ path: string; mtimeMs: number }>>;
170
172
  /**
171
173
  * Strict directory scan that never suppresses scan/root errors. Used by strict
172
174
  * authorization inventory; the forgiving {@link listFilesSync} stays display-only.
@@ -259,6 +261,19 @@ export interface VerifiedSessionDeleteTarget {
259
261
  detachedArtifactsPath?: string;
260
262
  /** Identity-bound quarantine path retained when transcript unlink deferred cleanup. */
261
263
  detachedTranscriptPath?: string;
264
+ /** Native-retained publisher successor observed during transcript cleanup. */
265
+ retainedTranscriptSuccessorPath?: string;
266
+ /** Native-retained exchange placeholder observed during transcript cleanup. */
267
+ retainedTranscriptPlaceholderPath?: string;
268
+ /** Native-retained transcript entry whose identity could not be verified. */
269
+ retainedTranscriptUnknownPath?: string;
270
+ /** Native-retained publisher successor observed during cleanup. */
271
+ retainedArtifactsSuccessorPath?: string;
272
+ /** Native-retained exchange placeholder observed during cleanup. */
273
+ retainedArtifactsPlaceholderPath?: string;
274
+ /** Native-retained entry whose identity could not be verified. */
275
+ retainedArtifactsUnknownPath?: string;
276
+
262
277
  /** Caller-published, no-replace quarantine pathname for the next artifact detach. */
263
278
  plannedArtifactsPath?: string;
264
279
  /** Caller-published, no-replace quarantine pathname for the next transcript detach. */
@@ -288,6 +303,9 @@ export type VerifiedSessionDeleteResult =
288
303
  artifactsTree: NativeDirectoryTreeSnapshot;
289
304
  /** Transcript identity (unchanged) for retry binding. */
290
305
  transcriptIdentity: SessionStorageFileIdentity;
306
+ retainedSuccessorPath?: string;
307
+ retainedPlaceholderPath?: string;
308
+ retainedUnknownPath?: string;
291
309
  }
292
310
  | {
293
311
  kind: "cleanup_pending";
@@ -297,6 +315,9 @@ export type VerifiedSessionDeleteResult =
297
315
  transcriptIdentity: SessionStorageFileIdentity;
298
316
  /** Optional identity-bound transcript quarantine path for restart cleanup. */
299
317
  detachedTranscriptPath?: string;
318
+ retainedSuccessorPath?: string;
319
+ retainedPlaceholderPath?: string;
320
+ retainedUnknownPath?: string;
300
321
  };
301
322
 
302
323
  /** Default OS-close dispatcher: a direct `fs.closeSync`. */
@@ -306,7 +327,22 @@ const defaultCloseAdapter: SessionStorageWriterCloseAdapter = {
306
327
  },
307
328
  };
308
329
 
309
- type NativeExactUnlinkResult = { ok: true; detachedPath?: string } | { ok: false; code: string; detachedPath?: string };
330
+ type NativeExactUnlinkResult =
331
+ | {
332
+ ok: true;
333
+ detachedPath?: string;
334
+ retainedSuccessorPath?: string;
335
+ retainedPlaceholderPath?: string;
336
+ retainedUnknownPath?: string;
337
+ }
338
+ | {
339
+ ok: false;
340
+ code: string;
341
+ detachedPath?: string;
342
+ retainedSuccessorPath?: string;
343
+ retainedPlaceholderPath?: string;
344
+ retainedUnknownPath?: string;
345
+ };
310
346
  type NativeExactUnlink = (
311
347
  path: string,
312
348
  identity: {
@@ -671,6 +707,21 @@ export class FileSessionStorage implements SessionStorage {
671
707
  return [];
672
708
  }
673
709
  }
710
+ async listFilesByMtime(dir: string, pattern: string): Promise<Array<{ path: string; mtimeMs: number }>> {
711
+ const result = await native.glob({
712
+ path: dir,
713
+ pattern,
714
+ fileType: native.FileType.File,
715
+ recursive: false,
716
+ hidden: false,
717
+ gitignore: false,
718
+ sortByMtime: true,
719
+ });
720
+ return result.matches.map(match => ({
721
+ path: path.join(dir, match.path),
722
+ mtimeMs: match.mtime ?? 0,
723
+ }));
724
+ }
674
725
 
675
726
  listFilesStrictSync(dir: string, pattern: string): string[] {
676
727
  // Strict: never suppress scan/root errors. Authorization inventory depends on
@@ -766,7 +817,13 @@ export class FileSessionStorage implements SessionStorage {
766
817
  expectedArtifactsIdentity,
767
818
  expectedArtifactsTree,
768
819
  detachedArtifactsPath,
820
+ retainedArtifactsSuccessorPath,
821
+ retainedArtifactsPlaceholderPath,
822
+ retainedArtifactsUnknownPath,
769
823
  detachedTranscriptPath,
824
+ retainedTranscriptSuccessorPath,
825
+ retainedTranscriptPlaceholderPath,
826
+ retainedTranscriptUnknownPath,
770
827
  plannedArtifactsPath,
771
828
  plannedTranscriptPath,
772
829
  artifactsRemoved,
@@ -847,7 +904,8 @@ export class FileSessionStorage implements SessionStorage {
847
904
  if (
848
905
  !expectedArtifactsIdentity ||
849
906
  path.dirname(detachedArtifactsPath) !== path.dirname(transcriptPath) ||
850
- !path.basename(detachedArtifactsPath).startsWith(".skc-delete-")
907
+ (!path.basename(detachedArtifactsPath).startsWith(".skc-delete-") &&
908
+ !detachedArtifactsPath.endsWith(".removing"))
851
909
  ) {
852
910
  throw new SessionDeleteVerificationError("artifacts", "Detached artifact cleanup evidence is invalid");
853
911
  }
@@ -882,17 +940,20 @@ export class FileSessionStorage implements SessionStorage {
882
940
  artifactsIdentity: expectedArtifactsIdentity,
883
941
  detachedArtifactsPath: retainedRoot,
884
942
  artifactsTree: expectedArtifactsTree,
943
+ ...((removal.retainedSuccessorPath ?? retainedArtifactsSuccessorPath)
944
+ ? { retainedSuccessorPath: removal.retainedSuccessorPath ?? retainedArtifactsSuccessorPath }
945
+ : {}),
946
+ ...((removal.retainedPlaceholderPath ?? retainedArtifactsPlaceholderPath)
947
+ ? { retainedPlaceholderPath: removal.retainedPlaceholderPath ?? retainedArtifactsPlaceholderPath }
948
+ : {}),
949
+ ...((removal.retainedUnknownPath ?? retainedArtifactsUnknownPath)
950
+ ? { retainedUnknownPath: removal.retainedUnknownPath ?? retainedArtifactsUnknownPath }
951
+ : {}),
885
952
  transcriptIdentity,
886
953
  };
887
954
  }
888
955
  }
889
956
 
890
- if (artifactsRemoved && (detachedArtifactsPath || expectedArtifactsIdentity)) {
891
- throw new SessionDeleteVerificationError(
892
- "artifacts",
893
- "Artifact phase receipt conflicts with pending artifact cleanup",
894
- );
895
- }
896
957
  const artifactsDir = transcriptPath.slice(0, -6);
897
958
  const artifactsIdentity = this.#optionalDirectoryIdentity(artifactsDir);
898
959
  if (artifactsRemoved && artifactsIdentity) {
@@ -947,12 +1008,48 @@ export class FileSessionStorage implements SessionStorage {
947
1008
  directory: true,
948
1009
  quarantineName: path.basename(plannedArtifactsPath),
949
1010
  });
950
- if (!detach.ok || !detach.detachedPath) {
1011
+ if (!detach.detachedPath) {
951
1012
  throw new SessionDeleteVerificationError(
952
1013
  "artifacts",
953
1014
  `Exact artifact detach rejected: ${detach.ok ? "missing_path" : detach.code}`,
954
1015
  );
955
1016
  }
1017
+ if (!detach.ok && process.platform !== "win32") {
1018
+ let descriptor: number | undefined;
1019
+ try {
1020
+ descriptor = fs.openSync(
1021
+ path.dirname(transcriptPath),
1022
+ fs.constants.O_RDONLY | fs.constants.O_DIRECTORY | fs.constants.O_NOFOLLOW,
1023
+ );
1024
+ const durableParent = fs.fstatSync(descriptor, { bigint: true });
1025
+ if (
1026
+ !durableParent.isDirectory() ||
1027
+ durableParent.dev !== parentIdentity.dev ||
1028
+ durableParent.ino !== parentIdentity.ino
1029
+ )
1030
+ throw new Error("parent_changed");
1031
+ fs.fsyncSync(descriptor);
1032
+ } catch (error) {
1033
+ throw new SessionDeleteVerificationError("artifacts", "durability_failed", { cause: toError(error) });
1034
+ } finally {
1035
+ if (descriptor !== undefined) fs.closeSync(descriptor);
1036
+ }
1037
+ }
1038
+ if (!detach.ok) {
1039
+ return {
1040
+ kind: "cleanup_pending",
1041
+ phase: "artifacts",
1042
+ error: new SessionDeleteVerificationError("artifacts", `Exact artifact detach retained: ${detach.code}`),
1043
+ artifactsIdentity,
1044
+ detachedArtifactsPath: detach.detachedPath,
1045
+ artifactsTree,
1046
+ ...(detach.retainedSuccessorPath ? { retainedSuccessorPath: detach.retainedSuccessorPath } : {}),
1047
+ ...(detach.retainedPlaceholderPath ? { retainedPlaceholderPath: detach.retainedPlaceholderPath } : {}),
1048
+ ...(detach.retainedUnknownPath ? { retainedUnknownPath: detach.retainedUnknownPath } : {}),
1049
+
1050
+ transcriptIdentity,
1051
+ };
1052
+ }
956
1053
  const removal = removeDirectoryTreeExact(detach.detachedPath, artifactsTree);
957
1054
  if (!removal.ok) {
958
1055
  const retainedRoot = removal.detachedPath ?? detach.detachedPath;
@@ -971,6 +1068,15 @@ export class FileSessionStorage implements SessionStorage {
971
1068
  artifactsIdentity,
972
1069
  detachedArtifactsPath: retainedRoot,
973
1070
  artifactsTree,
1071
+ ...((removal.retainedSuccessorPath ?? retainedArtifactsSuccessorPath)
1072
+ ? { retainedSuccessorPath: removal.retainedSuccessorPath ?? retainedArtifactsSuccessorPath }
1073
+ : {}),
1074
+ ...((removal.retainedPlaceholderPath ?? retainedArtifactsPlaceholderPath)
1075
+ ? { retainedPlaceholderPath: removal.retainedPlaceholderPath ?? retainedArtifactsPlaceholderPath }
1076
+ : {}),
1077
+ ...((removal.retainedUnknownPath ?? retainedArtifactsUnknownPath)
1078
+ ? { retainedUnknownPath: removal.retainedUnknownPath ?? retainedArtifactsUnknownPath }
1079
+ : {}),
974
1080
  transcriptIdentity,
975
1081
  };
976
1082
  }
@@ -1010,13 +1116,27 @@ export class FileSessionStorage implements SessionStorage {
1010
1116
  });
1011
1117
  if (!deletion.ok) {
1012
1118
  const error = exactUnlinkFailure(deletion);
1013
- if (error.kind === "identity" || error.kind === "symlink") throw error;
1119
+ const retainedAuthority =
1120
+ deletion.detachedPath ||
1121
+ deletion.retainedSuccessorPath ||
1122
+ deletion.retainedPlaceholderPath ||
1123
+ deletion.retainedUnknownPath;
1124
+ if ((error.kind === "identity" || error.kind === "symlink") && !retainedAuthority) throw error;
1014
1125
  return {
1015
1126
  kind: "cleanup_pending",
1016
1127
  phase: "transcript",
1017
1128
  error,
1018
1129
  transcriptIdentity,
1019
1130
  detachedTranscriptPath: deletion.detachedPath ?? detachedTranscriptPath,
1131
+ ...((deletion.retainedSuccessorPath ?? retainedTranscriptSuccessorPath)
1132
+ ? { retainedSuccessorPath: deletion.retainedSuccessorPath ?? retainedTranscriptSuccessorPath }
1133
+ : {}),
1134
+ ...((deletion.retainedPlaceholderPath ?? retainedTranscriptPlaceholderPath)
1135
+ ? { retainedPlaceholderPath: deletion.retainedPlaceholderPath ?? retainedTranscriptPlaceholderPath }
1136
+ : {}),
1137
+ ...((deletion.retainedUnknownPath ?? retainedTranscriptUnknownPath)
1138
+ ? { retainedUnknownPath: deletion.retainedUnknownPath ?? retainedTranscriptUnknownPath }
1139
+ : {}),
1020
1140
  };
1021
1141
  }
1022
1142
  return { kind: "deleted" };
@@ -1058,13 +1178,27 @@ export class FileSessionStorage implements SessionStorage {
1058
1178
  });
1059
1179
  if (!deletion.ok) {
1060
1180
  const error = exactUnlinkFailure(deletion);
1061
- if (error.kind === "identity" || error.kind === "symlink") throw error;
1181
+ const retainedAuthority =
1182
+ deletion.detachedPath ||
1183
+ deletion.retainedSuccessorPath ||
1184
+ deletion.retainedPlaceholderPath ||
1185
+ deletion.retainedUnknownPath;
1186
+ if ((error.kind === "identity" || error.kind === "symlink") && !retainedAuthority) throw error;
1062
1187
  return {
1063
1188
  kind: "cleanup_pending",
1064
1189
  phase: "transcript",
1065
1190
  error,
1066
1191
  transcriptIdentity,
1067
1192
  detachedTranscriptPath: deletion.detachedPath,
1193
+ ...((deletion.retainedSuccessorPath ?? retainedTranscriptSuccessorPath)
1194
+ ? { retainedSuccessorPath: deletion.retainedSuccessorPath ?? retainedTranscriptSuccessorPath }
1195
+ : {}),
1196
+ ...((deletion.retainedPlaceholderPath ?? retainedTranscriptPlaceholderPath)
1197
+ ? { retainedPlaceholderPath: deletion.retainedPlaceholderPath ?? retainedTranscriptPlaceholderPath }
1198
+ : {}),
1199
+ ...((deletion.retainedUnknownPath ?? retainedTranscriptUnknownPath)
1200
+ ? { retainedUnknownPath: deletion.retainedUnknownPath ?? retainedTranscriptUnknownPath }
1201
+ : {}),
1068
1202
  };
1069
1203
  }
1070
1204
  return { kind: "deleted" };
@@ -1353,6 +1487,12 @@ export class MemorySessionStorage implements SessionStorage {
1353
1487
  }
1354
1488
  return files;
1355
1489
  }
1490
+ listFilesByMtime(dir: string, pattern: string): Promise<Array<{ path: string; mtimeMs: number }>> {
1491
+ const files = this.listFilesSync(dir, pattern)
1492
+ .map(path => ({ path, mtimeMs: this.statSync(path).mtimeMs }))
1493
+ .sort((a, b) => b.mtimeMs - a.mtimeMs);
1494
+ return Promise.resolve(files);
1495
+ }
1356
1496
  listFilesStrictSync(dir: string, pattern: string): string[] {
1357
1497
  // In-memory scan never suppresses; identical to the display scan.
1358
1498
  return this.listFilesSync(dir, pattern);
@@ -114,8 +114,12 @@ export interface TruncationResult {
114
114
  elidedLines?: number;
115
115
  lastLinePartial?: boolean;
116
116
  firstLineExceedsLimit?: boolean;
117
+ lastLineExceedsLimit?: boolean;
117
118
  }
118
119
 
120
+ /** Direction vocabulary used by callers that select which end of content to retain. */
121
+ export type TruncationDirection = "head" | "last" | "both";
122
+
119
123
  export interface TruncationOptions {
120
124
  /** Maximum number of lines (default: 3000) */
121
125
  maxLines?: number;
@@ -131,6 +135,8 @@ export interface TruncationOptions {
131
135
  * window receives `maxLines - maxHeadLines`. Default `floor(maxLines/2)`.
132
136
  */
133
137
  maxHeadLines?: number;
138
+ /** Direction to dispatch through truncateContent (head, last, or both). */
139
+ direction?: TruncationDirection;
134
140
  }
135
141
 
136
142
  /** Result from byte-level truncation helpers. */
@@ -452,6 +458,7 @@ export function truncateTail(content: string, options: TruncationOptions = {}):
452
458
  outputBytes: tail.bytes,
453
459
  lastLinePartial: true,
454
460
  firstLineExceedsLimit: false,
461
+ lastLineExceedsLimit: true,
455
462
  };
456
463
  }
457
464
  break;
@@ -474,6 +481,7 @@ export function truncateTail(content: string, options: TruncationOptions = {}):
474
481
  outputBytes: tail.bytes,
475
482
  lastLinePartial: true,
476
483
  firstLineExceedsLimit: false,
484
+ lastLineExceedsLimit: true,
477
485
  };
478
486
  }
479
487
  break;
@@ -516,14 +524,129 @@ export function formatMiddleElisionMarker(elidedLines: number, elidedBytes: numb
516
524
  }
517
525
 
518
526
  /**
519
- * Truncate content keeping a head window and a tail window, eliding the middle.
527
+ * A retained source segment returned by {@link truncateMiddleWindows}.
528
+ * Line coordinates are 1-indexed and inclusive.
529
+ */
530
+ export type ReadSegment =
531
+ | {
532
+ kind: "lines";
533
+ content: string;
534
+ lines: number;
535
+ bytes: number;
536
+ origin: { startLine: number; endLine: number };
537
+ lastLinePartial: false;
538
+ }
539
+ | {
540
+ kind: "partial-line";
541
+ content: string;
542
+ lines: 1;
543
+ bytes: number;
544
+ origin: { startLine: number; endLine: number };
545
+ sourceLineBytes: number;
546
+ lastLinePartial: true;
547
+ };
548
+
549
+ /** The actual windows retained by middle truncation. */
550
+ export interface ReadWindow {
551
+ kind: "full" | "head-only" | "tail-only" | "middle";
552
+ head?: ReadSegment;
553
+ tail?: ReadSegment;
554
+ // With normalized head/tail line budgets, an overlap cannot occur: a
555
+ // source that can fit both windows returns `full`, otherwise their kept
556
+ // line counts sum to less than the source. Retain this defensive value for
557
+ // custom/future budgets and wire compatibility.
558
+ overlap: "disjoint" | "adjacent" | "overlapping";
559
+ elidedLines: number;
560
+ elidedBytes: number;
561
+ totalLines: number;
562
+ totalBytes: number;
563
+ /** @deprecated Non-enumerable compatibility reason for older consumers. */
564
+ truncatedBy?: "lines" | "bytes" | "middle";
565
+ }
566
+
567
+ function outputLineCount(result: TruncationResult): number {
568
+ return result.outputLines ?? (result.content.length === 0 ? 0 : countNewlines(result.content) + 1);
569
+ }
570
+
571
+ function outputByteCount(result: TruncationResult): number {
572
+ return result.outputBytes ?? Buffer.byteLength(result.content, "utf-8");
573
+ }
574
+
575
+ function lastSourceLineBytes(content: string): number {
576
+ const lineStart = content.lastIndexOf(NL) + 1;
577
+ return Buffer.byteLength(content.slice(lineStart), "utf-8");
578
+ }
579
+
580
+ function makeReadSegment(
581
+ result: TruncationResult,
582
+ startLine: number,
583
+ totalLines: number,
584
+ sourceContent: string = result.content,
585
+ ): ReadSegment | undefined {
586
+ const lines = outputLineCount(result);
587
+ if (lines <= 0) return undefined;
588
+ const bytes = outputByteCount(result);
589
+ const endLine = Math.min(totalLines, startLine + lines - 1);
590
+ if (result.lastLinePartial) {
591
+ const partialLine = endLine;
592
+ return {
593
+ kind: "partial-line",
594
+ content: result.content,
595
+ lines: 1,
596
+ bytes,
597
+ origin: { startLine: partialLine, endLine: partialLine },
598
+ sourceLineBytes: lastSourceLineBytes(sourceContent),
599
+ lastLinePartial: true,
600
+ };
601
+ }
602
+ return {
603
+ kind: "lines",
604
+ content: result.content,
605
+ lines,
606
+ bytes,
607
+ origin: { startLine, endLine },
608
+ lastLinePartial: false,
609
+ };
610
+ }
611
+
612
+ function withMiddleWindowReason(windows: ReadWindow, truncatedBy: "lines" | "bytes" | "middle"): ReadWindow {
613
+ Object.defineProperty(windows, "truncatedBy", { value: truncatedBy, enumerable: false });
614
+ return windows;
615
+ }
616
+
617
+ function fullReadWindow(
618
+ content: string,
619
+ totalLines: number,
620
+ totalBytes: number,
621
+ overlap: ReadWindow["overlap"] = "disjoint",
622
+ ): ReadWindow {
623
+ return {
624
+ kind: "full",
625
+ head: {
626
+ kind: "lines",
627
+ content,
628
+ lines: totalLines,
629
+ bytes: totalBytes,
630
+ origin: { startLine: 1, endLine: totalLines },
631
+ lastLinePartial: false,
632
+ },
633
+ overlap,
634
+ elidedLines: 0,
635
+ elidedBytes: 0,
636
+ totalLines,
637
+ totalBytes,
638
+ };
639
+ }
640
+
641
+ /**
642
+ * Truncate content while exposing the exact retained head/tail windows.
520
643
  *
521
- * The combined output is `<head>\n<marker>\n<tail>` when truncation is needed.
522
- * `maxHeadBytes` defaults to `floor(maxBytes / 2)`; the tail receives the
523
- * remainder. Falls back to `truncateTail` / `truncateHead` if either side's
524
- * budget is empty or the content already fits.
644
+ * The budget split and fallback ordering intentionally mirror truncateMiddle.
645
+ * The overlap classification is performed before resolving an overlap to a
646
+ * full window, so callers can distinguish disjoint, adjacent and overlapping
647
+ * candidate windows even when no marker is needed.
525
648
  */
526
- export function truncateMiddle(content: string, options: TruncationOptions = {}): TruncationResult {
649
+ export function truncateMiddleWindows(content: string, options: TruncationOptions = {}): ReadWindow {
527
650
  const maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;
528
651
  const maxLines = options.maxLines ?? DEFAULT_MAX_LINES;
529
652
  const headBytes = options.maxHeadBytes ?? Math.floor(maxBytes / 2);
@@ -533,57 +656,153 @@ export function truncateMiddle(content: string, options: TruncationOptions = {})
533
656
 
534
657
  const totalBytes = Buffer.byteLength(content, "utf-8");
535
658
  const totalLines = countNewlines(content) + 1;
659
+ if (totalBytes <= maxBytes && totalLines <= maxLines) return fullReadWindow(content, totalLines, totalBytes);
660
+
661
+ const oneSided = (kind: "head-only" | "tail-only", result: TruncationResult, startLine: number): ReadWindow => {
662
+ const lines = outputLineCount(result);
663
+ const bytes = outputByteCount(result);
664
+ const segment = makeReadSegment(result, startLine, totalLines, content);
665
+
666
+ const windows: ReadWindow = {
667
+ kind,
668
+ ...(kind === "head-only" ? (segment ? { head: segment } : {}) : segment ? { tail: segment } : {}),
669
+ overlap: "disjoint",
670
+ elidedLines: Math.max(0, totalLines - lines),
671
+ elidedBytes: Math.max(0, totalBytes - bytes),
672
+ totalLines,
673
+ totalBytes,
674
+ };
675
+ return withMiddleWindowReason(windows, result.truncatedBy === "lines" ? "lines" : "bytes");
676
+ };
536
677
 
537
- if (totalBytes <= maxBytes && totalLines <= maxLines) {
538
- return noTruncResult(content, totalLines, totalBytes);
539
- }
540
-
541
- // Degenerate budgets → fall back to one-sided truncation.
678
+ // Keep this order identical to truncateMiddle: degenerate budgets are
679
+ // resolved before either one-sided truncator is called.
542
680
  if (headBytes <= 0 || headLines <= 0) {
543
- return truncateTail(content, { maxBytes: tailBytes || maxBytes, maxLines: tailLines || maxLines });
681
+ const tail = truncateTail(content, { maxBytes: tailBytes || maxBytes, maxLines: tailLines || maxLines });
682
+ return oneSided("tail-only", tail, totalLines - outputLineCount(tail) + 1);
544
683
  }
545
684
  if (tailBytes <= 0 || tailLines <= 0) {
546
- return truncateHead(content, { maxBytes: headBytes, maxLines: headLines });
685
+ const head = truncateHead(content, { maxBytes: headBytes, maxLines: headLines });
686
+ return oneSided("head-only", head, 1);
547
687
  }
548
688
 
549
689
  const head = truncateHead(content, { maxBytes: headBytes, maxLines: headLines });
550
690
  const tail = truncateTail(content, { maxBytes: tailBytes, maxLines: tailLines });
551
-
552
- const headLinesKept = head.outputLines ?? 0;
553
- const tailLinesKept = tail.outputLines ?? 0;
554
- const headBytesKept = head.outputBytes ?? Buffer.byteLength(head.content, "utf-8");
555
- const tailBytesKept = tail.outputBytes ?? Buffer.byteLength(tail.content, "utf-8");
691
+ const headLinesKept = outputLineCount(head);
692
+ const tailLinesKept = outputLineCount(tail);
693
+ const headBytesKept = outputByteCount(head);
694
+ const tailBytesKept = outputByteCount(tail);
556
695
 
557
696
  // Head unusable (first line exceeds budget) → tail-only.
558
- if (headLinesKept === 0 || head.firstLineExceedsLimit) return tail;
697
+ if (headLinesKept === 0 || head.firstLineExceedsLimit) {
698
+ return oneSided("tail-only", tail, totalLines - tailLinesKept + 1);
699
+ }
559
700
  // Tail unusable → head-only.
560
- if (tailLinesKept === 0) return head;
561
- // Windows overlap → no meaningful elision; return content untruncated.
562
- if (headLinesKept + tailLinesKept >= totalLines) {
563
- return noTruncResult(content, totalLines, totalBytes);
701
+ if (tailLinesKept === 0) return oneSided("head-only", head, 1);
702
+
703
+ const headEnd = headLinesKept;
704
+ const tailStart = totalLines - tailLinesKept + 1;
705
+ const overlap: ReadWindow["overlap"] =
706
+ headEnd + 1 < tailStart ? "disjoint" : headEnd + 1 === tailStart ? "adjacent" : "overlapping";
707
+ const tailPartial = tail.lastLinePartial === true;
708
+
709
+ // Adjacent/overlapping complete windows cover the whole source. Return one
710
+ // full segment so renderers never insert a marker for an overlap.
711
+ if (headLinesKept + tailLinesKept >= totalLines && !tailPartial) {
712
+ return fullReadWindow(content, totalLines, totalBytes, overlap);
564
713
  }
565
714
 
566
- const elidedLines = totalLines - headLinesKept - tailLinesKept;
567
- // `totalBytes - headBytesKept - tailBytesKept` includes newline separators
568
- // between the kept windows and the elided region; close enough for a notice.
569
- const elidedBytes = Math.max(0, totalBytes - headBytesKept - tailBytesKept);
570
- const marker = formatMiddleElisionMarker(elidedLines, elidedBytes);
571
- const composed = `${head.content}\n${marker}\n${tail.content}`;
572
- const markerBytes = Buffer.byteLength(marker, "utf-8");
715
+ const headWindow = makeReadSegment(head, 1, totalLines, content);
716
+ const tailWindow = makeReadSegment(tail, tailStart, totalLines, content);
717
+ if (!headWindow || !tailWindow) return fullReadWindow(content, totalLines, totalBytes, overlap);
718
+
719
+ const partialSourceBytes = tailWindow.kind === "partial-line" ? tailWindow.sourceLineBytes : tailWindow.bytes;
720
+ const elidedLines = tailPartial
721
+ ? Math.max(0, tailStart - headEnd - 1)
722
+ : Math.max(0, totalLines - headLinesKept - tailLinesKept);
723
+ const elidedBytes = tailPartial
724
+ ? Math.max(0, totalBytes - headBytesKept - partialSourceBytes)
725
+ : Math.max(0, totalBytes - headBytesKept - tailBytesKept);
726
+ return withMiddleWindowReason(
727
+ {
728
+ kind: "middle",
729
+ head: headWindow,
730
+ tail: tailWindow,
731
+ overlap,
732
+ elidedLines,
733
+ elidedBytes,
734
+ totalLines,
735
+ totalBytes,
736
+ },
737
+ "middle",
738
+ );
739
+ }
573
740
 
574
- return {
575
- content: composed,
576
- truncated: true,
577
- truncatedBy: "middle",
578
- totalLines,
579
- totalBytes,
580
- outputLines: headLinesKept + tailLinesKept + 1,
581
- outputBytes: headBytesKept + tailBytesKept + markerBytes + 2,
582
- elidedLines,
583
- elidedBytes,
584
- lastLinePartial: tail.lastLinePartial,
585
- firstLineExceedsLimit: false,
586
- };
741
+ /**
742
+ * Truncate content keeping a head window and a tail window, eliding the middle.
743
+ * The composed return shape remains field-for-field compatible with the
744
+ * historical implementation; callers that need coordinates use
745
+ * truncateMiddleWindows directly.
746
+ */
747
+ export function truncateMiddle(content: string, options: TruncationOptions = {}): TruncationResult {
748
+ const maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;
749
+ const maxLines = options.maxLines ?? DEFAULT_MAX_LINES;
750
+ const headBytes = options.maxHeadBytes ?? Math.floor(maxBytes / 2);
751
+ const tailBytes = Math.max(0, maxBytes - headBytes);
752
+ const headLines = options.maxHeadLines ?? Math.max(1, Math.floor(maxLines / 2));
753
+ const tailLines = Math.max(0, maxLines - headLines);
754
+ const windows = truncateMiddleWindows(content, options);
755
+
756
+ switch (windows.kind) {
757
+ case "full":
758
+ return noTruncResult(content, windows.totalLines, windows.totalBytes);
759
+ case "tail-only":
760
+ return truncateTail(content, { maxBytes: tailBytes || maxBytes, maxLines: tailLines || maxLines });
761
+ case "head-only":
762
+ return truncateHead(content, { maxBytes: headBytes, maxLines: headLines });
763
+ case "middle": {
764
+ const head = windows.head;
765
+ const tail = windows.tail;
766
+ if (!head || !tail) return noTruncResult(content, windows.totalLines, windows.totalBytes);
767
+ if (head.lines + tail.lines >= windows.totalLines)
768
+ return noTruncResult(content, windows.totalLines, windows.totalBytes);
769
+ const legacyElidedLines = Math.max(0, windows.totalLines - head.lines - tail.lines);
770
+ const legacyElidedBytes =
771
+ tail.kind === "partial-line"
772
+ ? Math.max(0, windows.totalBytes - head.bytes - tail.bytes)
773
+ : windows.elidedBytes;
774
+ const marker = formatMiddleElisionMarker(legacyElidedLines, legacyElidedBytes);
775
+ const markerBytes = Buffer.byteLength(marker, "utf-8");
776
+ return {
777
+ content: `${head.content}\n${marker}\n${tail.content}`,
778
+ truncated: true,
779
+ truncatedBy: "middle",
780
+ totalLines: windows.totalLines,
781
+ totalBytes: windows.totalBytes,
782
+ outputLines: head.lines + tail.lines + 1,
783
+ outputBytes: head.bytes + tail.bytes + markerBytes + 2,
784
+ elidedLines: legacyElidedLines,
785
+ elidedBytes: legacyElidedBytes,
786
+ lastLinePartial: tail.lastLinePartial,
787
+ firstLineExceedsLimit: false,
788
+ ...(tail.lastLinePartial ? { lastLineExceedsLimit: true } : {}),
789
+ };
790
+ }
791
+ }
792
+ }
793
+
794
+ /**
795
+ * Dispatch truncation using the caller-facing direction vocabulary.
796
+ */
797
+ export function truncateContent(content: string, options: TruncationOptions = {}): TruncationResult {
798
+ switch (options.direction) {
799
+ case "last":
800
+ return truncateTail(content, options);
801
+ case "both":
802
+ return truncateMiddle(content, options);
803
+ default:
804
+ return truncateHead(content, options);
805
+ }
587
806
  }
588
807
 
589
808
  // =============================================================================