@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
package/src/task/index.ts CHANGED
@@ -17,7 +17,7 @@ import * as os from "node:os";
17
17
  import path from "node:path";
18
18
  import type { AgentTool, AgentToolResult, AgentToolUpdateCallback } from "@sayknow-cli/agent-core";
19
19
  import type { Model, Usage } from "@sayknow-cli/ai";
20
- import { $env, prompt, Snowflake } from "@sayknow-cli/utils";
20
+ import { $pickenv, prompt, Snowflake } from "@sayknow-cli/utils";
21
21
  import type { ToolSession } from "..";
22
22
  import { AsyncJobManager, OwnerSubagentShutdownError, type ResumeRunner } from "../async";
23
23
  import { resolveAgentModelPatterns } from "../config/model-resolver";
@@ -44,6 +44,15 @@ import {
44
44
  // Import review tools for side effects (registers subagent tool handlers)
45
45
  import "../tools/review";
46
46
  import { initializeLocalRoot, type LocalProtocolOptions, resolveLocalUrlToPath } from "../internal-urls";
47
+ import {
48
+ assertExecutionRootMatchesRepositoryBinding,
49
+ assertPathUnderRepositoryBinding,
50
+ captureRepositoryBinding,
51
+ publicRepositoryBinding,
52
+ type RepositoryBinding,
53
+ RepositoryBindingError,
54
+ resolveTaskRepositoryBinding,
55
+ } from "../skc-runtime/repository-binding";
47
56
  import { generateCommitMessage } from "../utils/commit-message-generator";
48
57
  import * as git from "../utils/git";
49
58
  import { discoverAgents, filterVisibleAgents, getAgent } from "./discovery";
@@ -128,6 +137,43 @@ function addUsageTotals(target: Usage, usage: Partial<Usage>): void {
128
137
  target.cost.total += cost.total;
129
138
  }
130
139
 
140
+ /**
141
+ * Stamp/resolve repository authority for every task before agent discovery or spawn.
142
+ * Omitted bindings inherit the session worktree; declared ones must match it.
143
+ */
144
+ async function resolveTaskItemsWithRepositoryBindings(
145
+ cwd: string,
146
+ tasks: readonly TaskItem[],
147
+ ): Promise<{ tasks: TaskItem[]; error?: string }> {
148
+ const resolved: TaskItem[] = [];
149
+ for (const task of tasks) {
150
+ try {
151
+ const binding = await resolveTaskRepositoryBinding(cwd, task.repositoryBinding);
152
+ if (binding.relativeSubdir) {
153
+ assertPathUnderRepositoryBinding(binding, ".");
154
+ }
155
+ resolved.push({
156
+ ...task,
157
+ repositoryBinding: binding,
158
+ });
159
+ } catch (error) {
160
+ const id = task.id?.trim() ? task.id : "(missing-id)";
161
+ if (error instanceof RepositoryBindingError) {
162
+ return { tasks: [], error: `Task "${id}" repository binding rejected: ${error.message}` };
163
+ }
164
+ return {
165
+ tasks: [],
166
+ error: `Task "${id}" repository binding rejected: ${error instanceof Error ? error.message : String(error)}`,
167
+ };
168
+ }
169
+ }
170
+ return { tasks: resolved };
171
+ }
172
+
173
+ function repositoryBindingFromTask(task: TaskItem): RepositoryBinding | undefined {
174
+ return task.repositoryBinding ? publicRepositoryBinding(task.repositoryBinding as RepositoryBinding) : undefined;
175
+ }
176
+
131
177
  function validateTaskIdsForScheduling(tasks: readonly TaskItem[]): string | undefined {
132
178
  const invalid: string[] = [];
133
179
  for (let i = 0; i < tasks.length; i++) {
@@ -399,12 +445,16 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
399
445
  this.session.getSessionSpawns() ?? "*",
400
446
  );
401
447
  }
448
+ readonly #sessionRepositoryBinding: RepositoryBinding;
449
+
402
450
  private constructor(
403
451
  private readonly session: ToolSession,
404
452
  discoveredAgents: AgentDefinition[],
453
+ sessionRepositoryBinding: RepositoryBinding,
405
454
  ) {
406
- this.#blockedAgent = $env.PI_BLOCKED_AGENT;
455
+ this.#blockedAgent = $pickenv("SKC_BLOCKED_AGENT", "PI_BLOCKED_AGENT");
407
456
  this.#discoveredAgents = discoveredAgents;
457
+ this.#sessionRepositoryBinding = sessionRepositoryBinding;
408
458
  }
409
459
 
410
460
  #getTaskSimpleMode(): TaskSimpleMode {
@@ -412,11 +462,16 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
412
462
  }
413
463
 
414
464
  /**
415
- * Create a TaskTool instance with async agent discovery.
465
+ * Create a TaskTool instance.
466
+ * Repository authority is captured from session cwd *before* agent discovery so
467
+ * multi-repo workspaces fail closed prior to context/discovery (#2901).
416
468
  */
417
469
  static async create(session: ToolSession): Promise<TaskTool> {
470
+ const sessionRepositoryBinding = await captureRepositoryBinding(session.cwd, { displayPath: session.cwd });
471
+ // Authority check before discovery: session cwd must resolve to a stable binding.
472
+ await assertExecutionRootMatchesRepositoryBinding(session.cwd, sessionRepositoryBinding);
418
473
  const { agents } = await discoverAgents(session.cwd);
419
- return new TaskTool(session, agents);
474
+ return new TaskTool(session, agents, publicRepositoryBinding(sessionRepositoryBinding));
420
475
  }
421
476
 
422
477
  async execute(
@@ -432,13 +487,32 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
432
487
  return createTaskModeError(validationError);
433
488
  }
434
489
 
435
- const taskItems = params.tasks ?? [];
436
- const taskIdValidationError = validateTaskIdsForScheduling(taskItems);
490
+ // Re-verify session authority before using any discovered agents or scheduling work.
491
+ try {
492
+ await assertExecutionRootMatchesRepositoryBinding(this.session.cwd, this.#sessionRepositoryBinding);
493
+ } catch (error) {
494
+ const message =
495
+ error instanceof RepositoryBindingError
496
+ ? error.message
497
+ : error instanceof Error
498
+ ? error.message
499
+ : String(error);
500
+ return createTaskModeError(`Session repository binding rejected before task discovery: ${message}`);
501
+ }
502
+
503
+ const rawTaskItems = params.tasks ?? [];
504
+ const taskIdValidationError = validateTaskIdsForScheduling(rawTaskItems);
437
505
  if (taskIdValidationError) {
438
506
  return createTaskModeError(taskIdValidationError);
439
507
  }
508
+ const bindingResolution = await resolveTaskItemsWithRepositoryBindings(this.session.cwd, rawTaskItems);
509
+ if (bindingResolution.error) {
510
+ return createTaskModeError(bindingResolution.error);
511
+ }
512
+ const taskItems = bindingResolution.tasks;
513
+ const paramsWithBindings: TaskParams = { ...params, tasks: taskItems };
440
514
  if (taskItems.length === 0) {
441
- return this.#executeSync(_toolCallId, params, signal, onUpdate);
515
+ return this.#executeSync(_toolCallId, paramsWithBindings, signal, onUpdate);
442
516
  }
443
517
  const agent = getAgent(this.#discoveredAgents, params.agent);
444
518
  if (!agent) {
@@ -926,13 +1000,31 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
926
1000
  },
927
1001
  ): Promise<AgentToolResult<TaskToolDetails>> {
928
1002
  const startTime = Date.now();
1003
+ // Pre-discovery authority: session cwd must still match the capture from create().
1004
+ try {
1005
+ await assertExecutionRootMatchesRepositoryBinding(this.session.cwd, this.#sessionRepositoryBinding);
1006
+ } catch (error) {
1007
+ const message =
1008
+ error instanceof RepositoryBindingError
1009
+ ? error.message
1010
+ : error instanceof Error
1011
+ ? error.message
1012
+ : String(error);
1013
+ return createTaskModeError(`Session repository binding rejected before task discovery: ${message}`);
1014
+ }
1015
+ const bindingResolution = await resolveTaskItemsWithRepositoryBindings(this.session.cwd, params.tasks ?? []);
1016
+ if (bindingResolution.error) {
1017
+ return createTaskModeError(bindingResolution.error);
1018
+ }
1019
+ const boundParams: TaskParams = { ...params, tasks: bindingResolution.tasks };
1020
+
929
1021
  const { agents, projectAgentsDir } = await discoverAgents(this.session.cwd);
930
- const { agent: agentName, context, schema: outputSchema } = params;
1022
+ const { agent: agentName, context, schema: outputSchema } = boundParams;
931
1023
  const simpleMode = this.#getTaskSimpleMode();
932
1024
  const { contextEnabled, customSchemaEnabled } = getTaskSimpleModeCapabilities(simpleMode);
933
1025
  const sharedContext = contextEnabled ? context?.trim() : undefined;
934
1026
  const isolationMode = this.session.settings.get("task.isolation.mode");
935
- const isolationRequested = "isolated" in params ? params.isolated === true : false;
1027
+ const isolationRequested = "isolated" in boundParams ? boundParams.isolated === true : false;
936
1028
  const isIsolated = isolationMode !== "none" && isolationRequested;
937
1029
  const mergeMode = this.session.settings.get("task.isolation.merge");
938
1030
  const commitStyle = this.session.settings.get("task.isolation.commits");
@@ -940,7 +1032,7 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
940
1032
  const taskDepth = this.session.taskDepth ?? 0;
941
1033
  const subagentLspEnabled = (this.session.enableLsp ?? true) && this.session.settings.get("task.enableLsp");
942
1034
 
943
- if (isolationMode === "none" && "isolated" in params) {
1035
+ if (isolationMode === "none" && "isolated" in boundParams) {
944
1036
  return {
945
1037
  content: [
946
1038
  {
@@ -1000,7 +1092,7 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
1000
1092
  }
1001
1093
 
1002
1094
  const forkContextValidationError = validateForkContextRequests(
1003
- params.tasks ?? [],
1095
+ boundParams.tasks ?? [],
1004
1096
  agent,
1005
1097
  this.session.settings.get("task.forkContext.enabled"),
1006
1098
  );
@@ -1039,7 +1131,7 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
1039
1131
  : (effectiveAgent.output ?? this.session.outputSchema);
1040
1132
 
1041
1133
  // Handle empty or missing tasks
1042
- if (!params.tasks || params.tasks.length === 0) {
1134
+ if (!boundParams.tasks || boundParams.tasks.length === 0) {
1043
1135
  return {
1044
1136
  content: [
1045
1137
  {
@@ -1057,7 +1149,7 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
1057
1149
  };
1058
1150
  }
1059
1151
 
1060
- const tasks = params.tasks;
1152
+ const tasks = boundParams.tasks;
1061
1153
  const missingTaskIndexes: number[] = [];
1062
1154
  const idIndexes = new Map<string, number[]>();
1063
1155
 
@@ -1173,7 +1265,7 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
1173
1265
  const emitProgress = () => {
1174
1266
  const progress = Array.from(progressMap.values()).sort((a, b) => a.index - b.index);
1175
1267
  onUpdate?.({
1176
- content: [{ type: "text", text: `Running ${params.tasks.length} agents...` }],
1268
+ content: [{ type: "text", text: `Running ${tasks.length} agents...` }],
1177
1269
  details: {
1178
1270
  projectAgentsDir,
1179
1271
  results: [],
@@ -1342,12 +1434,19 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
1342
1434
  const taskSessionFile = managedPersistence
1343
1435
  ? null
1344
1436
  : (overrides?.sessionFile ?? executionOverrides?.sessionFiles?.get(task.id) ?? null);
1437
+ const taskRepositoryBinding = repositoryBindingFromTask(task) ?? this.#sessionRepositoryBinding;
1438
+ // Declared paths (relativeSubdir) must stay under the bound root before spawn.
1439
+ if (taskRepositoryBinding.relativeSubdir) {
1440
+ assertPathUnderRepositoryBinding(taskRepositoryBinding, ".");
1441
+ }
1345
1442
  if (!isIsolated) {
1443
+ await assertExecutionRootMatchesRepositoryBinding(this.session.cwd, taskRepositoryBinding);
1346
1444
  const result = await runSubprocess({
1347
1445
  cwd: this.session.cwd,
1348
1446
  agent: effectiveAgent,
1349
1447
  task: renderTaskAssignment(task.assignment, simpleMode),
1350
1448
  assignment: task.assignment.trim(),
1449
+ executionMode: task.executionMode,
1351
1450
  context: sharedContext,
1352
1451
  description: task.description,
1353
1452
  index,
@@ -1393,7 +1492,12 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
1393
1492
  parentTelemetry: this.session.getTelemetry?.(),
1394
1493
  forkContextSeed,
1395
1494
  });
1396
- return { ...result, ...(forkContext ? { forkContext } : {}), forkContextAdvisory };
1495
+ return {
1496
+ ...result,
1497
+ ...(forkContext ? { forkContext } : {}),
1498
+ forkContextAdvisory,
1499
+ repositoryBinding: publicRepositoryBinding(taskRepositoryBinding),
1500
+ };
1397
1501
  }
1398
1502
 
1399
1503
  const taskStart = Date.now();
@@ -1406,6 +1510,8 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
1406
1510
 
1407
1511
  isolationHandle = await ensureIsolation(repoRoot, task.id, preferredIsolationBackend);
1408
1512
  const isolationDir = isolationHandle.mergedDir;
1513
+ // Isolated worktrees must preserve the source repository identity (#2901).
1514
+ await assertExecutionRootMatchesRepositoryBinding(isolationDir, taskRepositoryBinding);
1409
1515
 
1410
1516
  const result = await runSubprocess({
1411
1517
  cwd: this.session.cwd,
@@ -1413,6 +1519,7 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
1413
1519
  agent: effectiveAgent,
1414
1520
  task: renderTaskAssignment(task.assignment, simpleMode),
1415
1521
  assignment: task.assignment.trim(),
1522
+ executionMode: task.executionMode,
1416
1523
  context: sharedContext,
1417
1524
  description: task.description,
1418
1525
  index,
@@ -1462,6 +1569,7 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
1462
1569
  ...result,
1463
1570
  ...(forkContext ? { forkContext } : {}),
1464
1571
  forkContextAdvisory,
1572
+ repositoryBinding: publicRepositoryBinding(taskRepositoryBinding),
1465
1573
  };
1466
1574
  if (mergeMode === "branch" && resultWithForkContext.exitCode === 0) {
1467
1575
  try {
@@ -0,0 +1,88 @@
1
+ import type { AgentProgress, TaskToolDetails } from "./types";
2
+
3
+ export type ProviderRetryKind = NonNullable<AgentProgress["retryState"]>["kind"];
4
+
5
+ const FIRST_EVENT_TIMEOUT_PATTERN = /stream timed out while waiting for the first event/i;
6
+ const IDLE_STREAM_STALL_PATTERN = /stream stalled while waiting for the next event/i;
7
+
8
+ export function classifyProviderRetry(errorMessage: string): ProviderRetryKind {
9
+ if (FIRST_EVENT_TIMEOUT_PATTERN.test(errorMessage)) return "first_event_timeout";
10
+ if (IDLE_STREAM_STALL_PATTERN.test(errorMessage)) return "idle_stream_stall";
11
+ return "provider_error";
12
+ }
13
+
14
+ export function providerNameFromModel(model: string | undefined): string | undefined {
15
+ const provider = model?.split("/", 1)[0]?.trim();
16
+ return provider || undefined;
17
+ }
18
+
19
+ export function providerRetryPhaseLabel(kind: ProviderRetryKind): string {
20
+ switch (kind) {
21
+ case "first_event_timeout":
22
+ return "first event timeout";
23
+ case "idle_stream_stall":
24
+ return "stream stalled";
25
+ case "provider_error":
26
+ return "provider error";
27
+ }
28
+ }
29
+
30
+ export function providerProgressAgeLabel(retryState: NonNullable<AgentProgress["retryState"]>, nowMs: number): string {
31
+ if (retryState.lastProviderProgressAtMs === undefined) return "no provider events yet";
32
+ const ageSeconds = Math.max(0, Math.floor((nowMs - retryState.lastProviderProgressAtMs) / 1000));
33
+ return `last provider progress ${ageSeconds}s ago`;
34
+ }
35
+
36
+ function isRecord(value: unknown): value is Record<string, unknown> {
37
+ return Boolean(value) && typeof value === "object";
38
+ }
39
+
40
+ function isTaskToolDetails(value: unknown): value is TaskToolDetails {
41
+ return (
42
+ isRecord(value) && Array.isArray(value.results) && (value.progress === undefined || Array.isArray(value.progress))
43
+ );
44
+ }
45
+
46
+ function isAgentProgress(value: unknown): value is AgentProgress {
47
+ return isRecord(value) && typeof value.status === "string";
48
+ }
49
+
50
+ export function hasActiveProviderRetryInTaskDetails(details: TaskToolDetails, seen = new WeakSet<object>()): boolean {
51
+ if (!isTaskToolDetails(details) || seen.has(details)) return false;
52
+ seen.add(details);
53
+ for (const progress of details.progress ?? []) {
54
+ if (isAgentProgress(progress) && hasActiveProviderRetryInProgress(progress, seen)) return true;
55
+ }
56
+ return false;
57
+ }
58
+
59
+ export function hasActiveProviderRetryInProgress(progress: AgentProgress, seen = new WeakSet<object>()): boolean {
60
+ if (!isAgentProgress(progress) || seen.has(progress)) return false;
61
+ seen.add(progress);
62
+ if (progress.status === "running" && progress.retryState) return true;
63
+
64
+ const nestedTaskData = isRecord(progress.extractedToolData) ? progress.extractedToolData.task : undefined;
65
+ if (Array.isArray(nestedTaskData)) {
66
+ for (const nestedDetails of nestedTaskData) {
67
+ if (hasActiveProviderRetryInTaskDetails(nestedDetails as TaskToolDetails, seen)) return true;
68
+ }
69
+ }
70
+ return hasActiveProviderRetryInTaskDetails(progress.inflightTaskDetails as TaskToolDetails, seen);
71
+ }
72
+
73
+ export interface ProviderDegradationGroup {
74
+ provider: string;
75
+ count: number;
76
+ }
77
+
78
+ export function collectProviderDegradationGroups(progress: readonly AgentProgress[]): ProviderDegradationGroup[] {
79
+ const counts = new Map<string, number>();
80
+ for (const item of progress) {
81
+ if (!isAgentProgress(item) || item.status !== "running" || !item.retryState) continue;
82
+ const provider = item.retryState.provider ?? "provider";
83
+ counts.set(provider, (counts.get(provider) ?? 0) + 1);
84
+ }
85
+ return Array.from(counts, ([provider, count]) => ({ provider, count }))
86
+ .filter(group => group.count > 1)
87
+ .sort((a, b) => b.count - a.count || a.provider.localeCompare(b.provider));
88
+ }
@@ -48,6 +48,8 @@ export interface TaskResultReceipt {
48
48
  extractedToolCounts?: Record<string, number>;
49
49
  forkContext?: SingleResult["forkContext"];
50
50
  forkContextAdvisory?: SingleResult["forkContextAdvisory"];
51
+ /** Resolved repository identity for this delegated lane (#2901). */
52
+ repositoryBinding?: SingleResult["repositoryBinding"];
51
53
  roi?: TaskRoi;
52
54
  }
53
55
 
@@ -262,6 +264,7 @@ export function buildTaskReceipt(raw: SingleResult): TaskResultReceipt {
262
264
  extractedToolCounts,
263
265
  forkContext: raw.forkContext,
264
266
  forkContextAdvisory: raw.forkContextAdvisory,
267
+ repositoryBinding: raw.repositoryBinding,
265
268
  roi: buildTaskRoi(raw),
266
269
  };
267
270
  }
@@ -28,6 +28,12 @@ import {
28
28
  type SubmitReviewDetails,
29
29
  } from "../tools/review";
30
30
  import { Ellipsis, Hasher, type RenderCache, renderStatusLine } from "../tui";
31
+ import {
32
+ collectProviderDegradationGroups,
33
+ hasActiveProviderRetryInTaskDetails,
34
+ providerProgressAgeLabel,
35
+ providerRetryPhaseLabel,
36
+ } from "./provider-retry-status";
31
37
  import type { TaskResultReceipt } from "./receipt";
32
38
  import { subprocessToolRegistry } from "./subprocess-tool-registry";
33
39
  import type { AgentProgress, TaskParams, TaskToolDetails } from "./types";
@@ -123,6 +129,15 @@ function formatModelSubstitutionWarning(warning: { requested: string; effective:
123
129
  return `Requested model substituted: ${warning.requested} -> ${warning.effective}`;
124
130
  }
125
131
 
132
+ function renderProviderDegradationNotices(progress: readonly AgentProgress[], theme: Theme): string[] {
133
+ return collectProviderDegradationGroups(progress).map(group =>
134
+ theme.fg(
135
+ "warning",
136
+ truncateToWidth(replaceTabs(`provider degraded: ${group.count} subagents retrying on ${group.provider}`), 100),
137
+ ),
138
+ );
139
+ }
140
+
126
141
  function formatJsonScalar(value: unknown, _theme: Theme): string {
127
142
  if (value === null) return "null";
128
143
  if (typeof value === "string") {
@@ -548,14 +563,11 @@ function renderAgentProgress(
548
563
  const titlePart = description ? `${theme.bold(displayId)}: ${description}` : displayId;
549
564
  let statusLine = `${prefix} ${theme.fg(iconColor, icon)} ${theme.fg("accent", titlePart)}`;
550
565
 
551
- // Show retry-blocked badge so the parent immediately sees that a child
552
- // is sleeping on a provider 429, not silently progressing. Wins over the
553
- // generic running spinner because "we're waiting on a quota window" is
554
- // the operationally meaningful state.
566
+ // A provider recovery loop is operationally distinct from normal agent work.
555
567
  if (progress.retryState && progress.status === "running") {
556
- statusLine += ` ${formatBadge("retrying", "warning", theme)}`;
568
+ statusLine += ` ${formatBadge("provider degraded", "warning", theme)}`;
557
569
  } else if (progress.retryFailure && (progress.status === "failed" || progress.status === "aborted")) {
558
- statusLine += ` ${formatBadge("rate-limited", "error", theme)}`;
570
+ statusLine += ` ${formatBadge("provider failed", "error", theme)}`;
559
571
  } else if (progress.status === "failed" || progress.status === "aborted") {
560
572
  const statusLabel = progress.status === "failed" ? "failed" : "aborted";
561
573
  statusLine += ` ${formatBadge(statusLabel, iconColor, theme)}`;
@@ -615,8 +627,10 @@ function renderAgentProgress(
615
627
  // keep spinning while a child sleeps on a 3-hour provider rate-limit.
616
628
  if (progress.retryState && progress.status === "running") {
617
629
  const attemptLabel = progress.retryState.unbounded
618
- ? `attempt ${progress.retryState.attempt}`
619
- : `${progress.retryState.attempt}/${progress.retryState.maxAttempts}`;
630
+ ? `attempt ${progress.retryState.attempt}, unbounded`
631
+ : `attempt ${progress.retryState.attempt} of ${progress.retryState.maxAttempts}, bounded`;
632
+ const phaseLabel = providerRetryPhaseLabel(progress.retryState.kind);
633
+ const progressAge = staticTime ? "" : ` · ${providerProgressAgeLabel(progress.retryState, Date.now())}`;
620
634
  // `staticTime` omits the wall-clock countdown so a cached await body stays a
621
635
  // pure function of its key (the producer already drops time-only churn).
622
636
  let waitLabel = "";
@@ -624,7 +638,7 @@ function renderAgentProgress(
624
638
  const remainingMs = Math.max(0, progress.retryState.startedAtMs + progress.retryState.delayMs - Date.now());
625
639
  waitLabel = remainingMs > 0 ? ` in ${formatDuration(remainingMs)}` : " now";
626
640
  }
627
- const summary = `retrying ${attemptLabel}${waitLabel}: ${truncateToWidth(replaceTabs(progress.retryState.errorMessage), 60)}`;
641
+ const summary = `${phaseLabel} · retrying ${attemptLabel}${waitLabel}${progressAge}: ${truncateToWidth(replaceTabs(progress.retryState.errorMessage), 60)}`;
628
642
  lines.push(`${continuePrefix}${theme.tree.hook} ${theme.fg("warning", summary)}`);
629
643
  } else if (progress.retryFailure && progress.status !== "running") {
630
644
  const summary = `auto-retry gave up after ${progress.retryFailure.attempt} attempt${
@@ -953,23 +967,28 @@ export function renderResult(
953
967
  .u32(spinnerFrame ?? 0)
954
968
  .u32(width)
955
969
  .digest();
956
- if (cached?.key === key) return cached.lines;
970
+ const hasDynamicRetry = hasActiveProviderRetryInTaskDetails(details);
971
+ if (!hasDynamicRetry && cached?.key === key) return cached.lines;
957
972
 
958
973
  const lines: string[] = [];
959
974
 
960
- const shouldRenderProgress =
961
- Boolean(details.progress && details.progress.length > 0) && (isPartial || details.results.length === 0);
975
+ const hasProgress = Boolean(details.progress && details.progress.length > 0);
976
+ const shouldRenderProgress = hasProgress && (isPartial || details.results.length === 0);
977
+ const hasResults = details.results.length > 0;
978
+ if (hasResults) {
979
+ details.results.forEach((res, i) => {
980
+ const isLast = !shouldRenderProgress && i === details.results.length - 1;
981
+ lines.push(...renderAgentResult(res, isLast, expanded, theme));
982
+ });
983
+ }
962
984
  if (shouldRenderProgress && details.progress) {
985
+ lines.push(...renderProviderDegradationNotices(details.progress, theme));
963
986
  details.progress.forEach((progress, i) => {
964
987
  const isLast = i === details.progress!.length - 1;
965
988
  lines.push(...renderAgentProgress(progress, isLast, expanded, theme, spinnerFrame));
966
989
  });
967
- } else if (details.results && details.results.length > 0) {
968
- details.results.forEach((res, i) => {
969
- const isLast = i === details.results.length - 1;
970
- lines.push(...renderAgentResult(res, isLast, expanded, theme));
971
- });
972
-
990
+ }
991
+ if (hasResults && !shouldRenderProgress) {
973
992
  const abortedCount = details.results.filter(r => r.status === "aborted").length;
974
993
  const mergeFailedCount = details.results.filter(r => r.status === "merge_failed").length;
975
994
  const successCount = details.results.filter(r => r.status === "completed").length;
@@ -997,7 +1016,7 @@ export function renderResult(
997
1016
  if (lines.length === 0) {
998
1017
  const text = fallbackText.trim() ? fallbackText : "No results";
999
1018
  const result = [theme.fg("dim", truncateToWidth(text, width))];
1000
- cached = { key, lines: result };
1019
+ if (!hasDynamicRetry) cached = { key, lines: result };
1001
1020
  return result;
1002
1021
  }
1003
1022
 
@@ -1019,9 +1038,9 @@ export function renderResult(
1019
1038
  }
1020
1039
 
1021
1040
  const indented = lines.map(line =>
1022
- line.length > 0 ? truncateToWidth(` ${line}`, width, Ellipsis.Omit) : "",
1041
+ line.length > 0 ? truncateToWidth(` ${replaceTabs(line)}`, width, Ellipsis.Omit) : "",
1023
1042
  );
1024
- cached = { key, lines: indented };
1043
+ if (!hasDynamicRetry) cached = { key, lines: indented };
1025
1044
  return indented;
1026
1045
  },
1027
1046
  invalidate() {
@@ -1065,16 +1084,16 @@ function renderNestedTaskTree(
1065
1084
  ): string[] {
1066
1085
  const lines: string[] = [];
1067
1086
  for (const details of detailsList) {
1068
- const hasResults = Boolean(details.results && details.results.length > 0);
1069
- if (hasResults) {
1087
+ const inflight = details.progress;
1088
+ const hasInflight = Boolean(inflight && inflight.length > 0);
1089
+ if (details.results && details.results.length > 0) {
1070
1090
  details.results.forEach((result, index) => {
1071
- const isLast = index === details.results.length - 1;
1091
+ const isLast = !hasInflight && index === details.results.length - 1;
1072
1092
  lines.push(...renderAgentResult(result, isLast, expanded, theme));
1073
1093
  });
1074
- continue;
1075
1094
  }
1076
- const inflight = details.progress;
1077
1095
  if (inflight && inflight.length > 0) {
1096
+ lines.push(...renderProviderDegradationNotices(inflight, theme));
1078
1097
  inflight.forEach((prog, index) => {
1079
1098
  const isLast = index === inflight.length - 1;
1080
1099
  lines.push(...renderAgentProgress(prog, isLast, expanded, theme, spinnerFrame, staticTime));
@@ -1,6 +1,6 @@
1
1
  import process from "node:process";
2
2
 
3
- import { $env } from "@sayknow-cli/utils";
3
+ import { $pickenv } from "@sayknow-cli/utils";
4
4
 
5
5
  interface SkcCommand {
6
6
  cmd: string;
@@ -12,7 +12,7 @@ const DEFAULT_CMD = process.platform === "win32" ? "skc.cmd" : "skc";
12
12
  const DEFAULT_SHELL = process.platform === "win32";
13
13
 
14
14
  export function resolveSkcCommand(): SkcCommand {
15
- const envCmd = $env.PI_SUBPROCESS_CMD;
15
+ const envCmd = $pickenv("SKC_SUBPROCESS_CMD", "PI_SUBPROCESS_CMD");
16
16
  if (envCmd?.trim()) {
17
17
  return { cmd: envCmd, args: [], shell: DEFAULT_SHELL };
18
18
  }
package/src/task/types.ts CHANGED
@@ -14,23 +14,28 @@ export type AgentSource = "bundled" | "user" | "project";
14
14
  export type ForkContextPolicy = "forbidden" | "allowed";
15
15
  export type ForkContextMode = "none" | "receipt" | "last-turn" | "bounded" | "full";
16
16
 
17
- const parseNumber = (value: string | undefined, defaultValue: number): number => {
18
- if (value) {
19
- try {
20
- const number = Number.parseInt(value, 10);
21
- if (!Number.isNaN(number) && number > 0) {
22
- return number;
23
- }
24
- } catch {}
17
+ const parsePositiveIntegerEnvironment = (keys: string[], defaultValue: number): number => {
18
+ for (const key of keys) {
19
+ const value = $env[key];
20
+ if (!value || value.trim().length === 0) continue;
21
+ if (!/^\d+$/.test(value)) return defaultValue;
22
+ const number = Number(value);
23
+ return Number.isSafeInteger(number) && number > 0 ? number : defaultValue;
25
24
  }
26
25
  return defaultValue;
27
26
  };
28
27
 
29
28
  /** Maximum output bytes per agent */
30
- export const MAX_OUTPUT_BYTES = parseNumber($env.PI_TASK_MAX_OUTPUT_BYTES, 500_000);
29
+ export const MAX_OUTPUT_BYTES = parsePositiveIntegerEnvironment(
30
+ ["SKC_TASK_MAX_OUTPUT_BYTES", "PI_TASK_MAX_OUTPUT_BYTES"],
31
+ 500_000,
32
+ );
31
33
 
32
34
  /** Maximum output lines per agent */
33
- export const MAX_OUTPUT_LINES = parseNumber($env.PI_TASK_MAX_OUTPUT_LINES, 5000);
35
+ export const MAX_OUTPUT_LINES = parsePositiveIntegerEnvironment(
36
+ ["SKC_TASK_MAX_OUTPUT_LINES", "PI_TASK_MAX_OUTPUT_LINES"],
37
+ 5000,
38
+ );
34
39
 
35
40
  /** EventBus channel for raw subagent events */
36
41
  export const TASK_SUBAGENT_EVENT_CHANNEL = "task:subagent:event";
@@ -74,17 +79,41 @@ const spawnPlanSchema = z
74
79
  })
75
80
  .describe("justification required before spawning more than four tasks");
76
81
 
82
+ const repositoryBindingSchema = z
83
+ .object({
84
+ schema: z.literal("skc.repository_binding.v1"),
85
+ worktreeRoot: z.string().min(1).describe("canonical git worktree root"),
86
+ commonDir: z.string().min(1).nullable().describe("git common dir, or null outside a git checkout"),
87
+ relativeSubdir: z.string().min(1).optional().describe("optional repo-relative subdirectory; not an absolute cwd"),
88
+ displayPath: z.string().min(1).optional().describe("human-facing path; never used for authority"),
89
+ head: z.string().min(1).optional(),
90
+ branch: z.string().min(1).optional(),
91
+ })
92
+ .strict()
93
+ .describe("authoritative repository identity for multi-repo fail-closed spawn");
94
+
77
95
  const createTaskItemSchema = (_contextEnabled: boolean) =>
78
96
  z.object({
79
97
  id: z.string().max(48).refine(isValidTaskId, TASK_ID_DESCRIPTION).describe("filesystem-safe task identifier"),
80
98
  description: z.string().describe("ui label, not seen by subagent"),
81
99
  assignment: z.string().describe(assignmentDescription),
100
+ executionMode: z
101
+ .enum(["default", "ultragoal-red-team"])
102
+ .optional()
103
+ .describe(
104
+ "typed executor mode: default keeps ordinary executor behavior; ultragoal-red-team injects the Ultragoal QA/red-team prompt fragment. Prefer this over free-form assignment text (#2698).",
105
+ ),
82
106
  inheritContext: z
83
107
  .enum(["none", "receipt", "last-turn", "bounded", "full"])
84
108
  .optional()
85
109
  .describe(
86
110
  "fork-context mode: none/omitted copies no parent context; receipt copies a minimal receipt-sized snapshot; last-turn copies only the latest exchange; bounded copies the bounded default snapshot; full copies a larger sanitized snapshot up to the configured/model token cap",
87
111
  ),
112
+ repositoryBinding: repositoryBindingSchema
113
+ .optional()
114
+ .describe(
115
+ "authoritative repository identity; omitted items are stamped from session cwd before discovery/spawn and still fail closed on sibling drift",
116
+ ),
88
117
  });
89
118
 
90
119
  /** Single task item for parallel execution (default shape with context enabled). */
@@ -267,6 +296,9 @@ export interface AgentProgress {
267
296
  attempt: number;
268
297
  maxAttempts: number;
269
298
  unbounded?: boolean;
299
+ kind: "first_event_timeout" | "idle_stream_stall" | "provider_error";
300
+ provider?: string;
301
+ lastProviderProgressAtMs?: number;
270
302
  delayMs: number;
271
303
  errorMessage: string;
272
304
  startedAtMs: number;
@@ -353,6 +385,19 @@ export interface SingleResult {
353
385
  * never changes the actual mode selection).
354
386
  */
355
387
  forkContextAdvisory?: { recommendedMode: ForkContextMode; reasons: string[] };
388
+ /**
389
+ * Resolved repository identity used for this task after pre-discovery stamping
390
+ * and fail-closed validation (#2901).
391
+ */
392
+ repositoryBinding?: {
393
+ schema: "skc.repository_binding.v1";
394
+ worktreeRoot: string;
395
+ commonDir: string | null;
396
+ relativeSubdir?: string;
397
+ displayPath?: string;
398
+ head?: string;
399
+ branch?: string;
400
+ };
356
401
  }
357
402
 
358
403
  /** True only for complete, factual five-bucket cost accounting. */