@jingyi0605/codingns 0.3.6 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (369) hide show
  1. package/README.md +3 -0
  2. package/bin/codingns.mjs +913 -1
  3. package/dist/public/assets/AdaptiveButlerPage-B153lk5H.css +1 -0
  4. package/dist/public/assets/AdaptiveButlerPage-R-XZw7pd.js +3 -0
  5. package/dist/public/assets/App-DUAg5urj.css +1 -0
  6. package/dist/public/assets/App-DkvE5EyM.js +30 -0
  7. package/dist/public/assets/BootstrapPage-Vu5oEJ8z.js +1 -0
  8. package/dist/public/assets/ConversationPage-Cjpg6g0J.js +2 -0
  9. package/dist/public/assets/DesktopDetachPreviewPage-BgeEqbc5.js +1 -0
  10. package/dist/public/assets/DesktopWindowPage-1WelvxdH.js +2 -0
  11. package/dist/public/assets/FileContextPanel-D_ghXJuW.js +1 -0
  12. package/dist/public/assets/GitSidebar-D9f9Jxwr.js +6 -0
  13. package/dist/public/assets/MobileCreateSessionSheet-DLq5qPkx.js +1 -0
  14. package/dist/public/assets/MobileSheet-DLg-gX1t.js +1 -0
  15. package/dist/public/assets/MobileTopHeaderFrame-DArgZI7L.js +1 -0
  16. package/dist/public/assets/MobileWorkspaceSwitcherHeader-0ywJKfBQ.js +1 -0
  17. package/dist/public/assets/ServerSettingsModal-izoYMx9U.js +1 -0
  18. package/dist/public/assets/SessionIndexPage-C5aG8FIv.js +1 -0
  19. package/dist/public/assets/SettingsPage-HJIC-P-4.js +1 -0
  20. package/dist/public/assets/TerminalManagerPanel-DpyUTo9k.js +1 -0
  21. package/dist/public/assets/{TerminalPage-D00S4KM6.js → TerminalPage-CtKXIU0h.js} +19 -19
  22. package/dist/public/assets/TerminalRuntimeFallbackModal-CRhOQOsT.js +1 -0
  23. package/dist/public/assets/ToolFilesPage-DcYPsS-e.js +1 -0
  24. package/dist/public/assets/ToolGitPage-CsPl89ty.js +1 -0
  25. package/dist/public/assets/ToolProcessesPage-D0dvR8xK.js +1 -0
  26. package/dist/public/assets/ToolsHomePage-4fP-KRiv.js +1 -0
  27. package/dist/public/assets/WorkbenchLandingPage-kvlfyxRo.js +1 -0
  28. package/dist/public/assets/WorkbenchLayout-ByFw4eeu.js +3 -0
  29. package/dist/public/assets/WorkbenchModal-Ctob14VR.js +1 -0
  30. package/dist/public/assets/WorkbenchShellRoute-BUITtdAg.css +1 -0
  31. package/dist/public/assets/WorkbenchShellRoute-Kw7JEZI3.js +1 -0
  32. package/dist/public/assets/WorkspaceDebugDetailPage-Com5kEXJ.js +1 -0
  33. package/dist/public/assets/WorkspaceDetailPage-D0Lrx4Uz.js +1 -0
  34. package/dist/public/assets/WorkspaceHomePage-wR8d3aP9.js +1 -0
  35. package/dist/public/assets/butler-records-events-DgWCG364.js +1 -0
  36. package/dist/public/assets/default-session-permission-mode-CcGwR4Kk.js +1 -0
  37. package/dist/public/assets/event-DvH9tcej.js +1 -0
  38. package/dist/public/assets/file-tree-icon-UFVoVzhM.js +31 -0
  39. package/dist/public/assets/index-Byp9hJ0c.js +42 -0
  40. package/dist/public/assets/index-_52jxu4a.css +1 -0
  41. package/dist/public/assets/preferences-service-KIYeE2gk.js +1 -0
  42. package/dist/public/assets/session-runtime-machine-0KNSSPp5.js +17 -0
  43. package/dist/public/assets/styles-BWPBZvze.css +1 -0
  44. package/dist/public/assets/styles-CSUx5LGe.js +1 -0
  45. package/dist/public/assets/terminal-runtime-meta-AWXJpN4r.js +1 -0
  46. package/dist/public/assets/useRegisteredDebugTemplates-DBDRdptr.js +1 -0
  47. package/dist/public/assets/window-BWqRixxq.js +1 -0
  48. package/dist/public/index.html +2 -2
  49. package/dist/server/middlewares/auth-guard.d.ts +4 -0
  50. package/dist/server/middlewares/auth-guard.js +42 -4
  51. package/dist/server/middlewares/auth-guard.js.map +1 -1
  52. package/dist/server/modules/assistant-capability/assistant-capability-controller.d.ts +234 -0
  53. package/dist/server/modules/assistant-capability/assistant-capability-controller.js +365 -0
  54. package/dist/server/modules/assistant-capability/assistant-capability-controller.js.map +1 -1
  55. package/dist/server/modules/assistant-capability/assistant-capability-service.d.ts +262 -2
  56. package/dist/server/modules/assistant-capability/assistant-capability-service.js +737 -3
  57. package/dist/server/modules/assistant-capability/assistant-capability-service.js.map +1 -1
  58. package/dist/server/modules/auth/auth-controller.d.ts +11 -1
  59. package/dist/server/modules/auth/auth-controller.js +61 -2
  60. package/dist/server/modules/auth/auth-controller.js.map +1 -1
  61. package/dist/server/modules/auth/auth-device-display-name.d.ts +10 -0
  62. package/dist/server/modules/auth/auth-device-display-name.js +190 -0
  63. package/dist/server/modules/auth/auth-device-display-name.js.map +1 -0
  64. package/dist/server/modules/auth/auth-service.d.ts +80 -5
  65. package/dist/server/modules/auth/auth-service.js +333 -23
  66. package/dist/server/modules/auth/auth-service.js.map +1 -1
  67. package/dist/server/modules/butler/assistant-automation-service.d.ts +112 -0
  68. package/dist/server/modules/butler/assistant-automation-service.js +832 -0
  69. package/dist/server/modules/butler/assistant-automation-service.js.map +1 -0
  70. package/dist/server/modules/butler/assistant-automation-trigger.d.ts +94 -0
  71. package/dist/server/modules/butler/assistant-automation-trigger.js +400 -0
  72. package/dist/server/modules/butler/assistant-automation-trigger.js.map +1 -0
  73. package/dist/server/modules/butler/assistant-sandbox-cleanup-scheduler.d.ts +32 -0
  74. package/dist/server/modules/butler/assistant-sandbox-cleanup-scheduler.js +93 -0
  75. package/dist/server/modules/butler/assistant-sandbox-cleanup-scheduler.js.map +1 -0
  76. package/dist/server/modules/butler/assistant-sandbox-service.d.ts +69 -0
  77. package/dist/server/modules/butler/assistant-sandbox-service.js +399 -0
  78. package/dist/server/modules/butler/assistant-sandbox-service.js.map +1 -0
  79. package/dist/server/modules/butler/butler-action-context-service.d.ts +4 -1
  80. package/dist/server/modules/butler/butler-action-context-service.js +8 -2
  81. package/dist/server/modules/butler/butler-action-context-service.js.map +1 -1
  82. package/dist/server/modules/butler/butler-auth-service.js +7 -2
  83. package/dist/server/modules/butler/butler-auth-service.js.map +1 -1
  84. package/dist/server/modules/butler/butler-control-session-service.d.ts +11 -1
  85. package/dist/server/modules/butler/butler-control-session-service.js +173 -40
  86. package/dist/server/modules/butler/butler-control-session-service.js.map +1 -1
  87. package/dist/server/modules/butler/butler-control-timer-scheduler.d.ts +32 -0
  88. package/dist/server/modules/butler/butler-control-timer-scheduler.js +93 -0
  89. package/dist/server/modules/butler/butler-control-timer-scheduler.js.map +1 -0
  90. package/dist/server/modules/butler/butler-control-timer-service.d.ts +42 -0
  91. package/dist/server/modules/butler/butler-control-timer-service.js +132 -0
  92. package/dist/server/modules/butler/butler-control-timer-service.js.map +1 -0
  93. package/dist/server/modules/butler/butler-controller.d.ts +42 -2
  94. package/dist/server/modules/butler/butler-controller.js +79 -12
  95. package/dist/server/modules/butler/butler-controller.js.map +1 -1
  96. package/dist/server/modules/butler/butler-follow-up-evaluation-instruction-adapter.d.ts +2 -1
  97. package/dist/server/modules/butler/butler-follow-up-evaluation-instruction-adapter.js +27 -25
  98. package/dist/server/modules/butler/butler-follow-up-evaluation-instruction-adapter.js.map +1 -1
  99. package/dist/server/modules/butler/butler-follow-up-service.d.ts +41 -5
  100. package/dist/server/modules/butler/butler-follow-up-service.js +568 -371
  101. package/dist/server/modules/butler/butler-follow-up-service.js.map +1 -1
  102. package/dist/server/modules/butler/butler-inbox-analysis-service.d.ts +4 -1
  103. package/dist/server/modules/butler/butler-inbox-analysis-service.js +18 -4
  104. package/dist/server/modules/butler/butler-inbox-analysis-service.js.map +1 -1
  105. package/dist/server/modules/butler/butler-inbox-service.js +1 -0
  106. package/dist/server/modules/butler/butler-inbox-service.js.map +1 -1
  107. package/dist/server/modules/butler/butler-profile-service.js +2 -5
  108. package/dist/server/modules/butler/butler-profile-service.js.map +1 -1
  109. package/dist/server/modules/butler/butler-project-service.d.ts +3 -1
  110. package/dist/server/modules/butler/butler-project-service.js +7 -1
  111. package/dist/server/modules/butler/butler-project-service.js.map +1 -1
  112. package/dist/server/modules/butler/butler-session-service.d.ts +5 -1
  113. package/dist/server/modules/butler/butler-session-service.js +26 -1
  114. package/dist/server/modules/butler/butler-session-service.js.map +1 -1
  115. package/dist/server/modules/butler/butler-session-summary-service.js +2 -1
  116. package/dist/server/modules/butler/butler-session-summary-service.js.map +1 -1
  117. package/dist/server/modules/butler/butler-workspace-context.d.ts +4 -1
  118. package/dist/server/modules/butler/butler-workspace-context.js +204 -58
  119. package/dist/server/modules/butler/butler-workspace-context.js.map +1 -1
  120. package/dist/server/modules/butler/patrol-execution-service.js +2 -1
  121. package/dist/server/modules/butler/patrol-execution-service.js.map +1 -1
  122. package/dist/server/modules/butler/provider-adapter-registry.d.ts +3 -0
  123. package/dist/server/modules/butler/provider-adapter-registry.js +18 -1
  124. package/dist/server/modules/butler/provider-adapter-registry.js.map +1 -1
  125. package/dist/server/modules/butler/verification-run-service.d.ts +9 -2
  126. package/dist/server/modules/butler/verification-run-service.js +188 -34
  127. package/dist/server/modules/butler/verification-run-service.js.map +1 -1
  128. package/dist/server/modules/debug-target/debug-target-controller.js +1 -1
  129. package/dist/server/modules/debug-target/debug-target-controller.js.map +1 -1
  130. package/dist/server/modules/debug-target/debug-target-service.d.ts +7 -2
  131. package/dist/server/modules/debug-target/debug-target-service.js +563 -100
  132. package/dist/server/modules/debug-target/debug-target-service.js.map +1 -1
  133. package/dist/server/modules/git/git-command-helper-client.d.ts +1 -0
  134. package/dist/server/modules/git/git-command-helper-client.js +19 -26
  135. package/dist/server/modules/git/git-command-helper-client.js.map +1 -1
  136. package/dist/server/modules/git/git-command-runner.js +19 -1
  137. package/dist/server/modules/git/git-command-runner.js.map +1 -1
  138. package/dist/server/modules/preferences/profile-service.d.ts +3 -1
  139. package/dist/server/modules/preferences/profile-service.js +74 -3
  140. package/dist/server/modules/preferences/profile-service.js.map +1 -1
  141. package/dist/server/modules/provider/provider-controller.d.ts +1 -1
  142. package/dist/server/modules/provider/provider-controller.js.map +1 -1
  143. package/dist/server/modules/provider/provider-discovery-helper-client.d.ts +5 -3
  144. package/dist/server/modules/provider/provider-discovery-helper-client.js +129 -43
  145. package/dist/server/modules/provider/provider-discovery-helper-client.js.map +1 -1
  146. package/dist/server/modules/provider/provider-discovery-helper-process.js +44 -0
  147. package/dist/server/modules/provider/provider-discovery-helper-process.js.map +1 -1
  148. package/dist/server/modules/provider/provider-discovery-runtime.js +83 -3
  149. package/dist/server/modules/provider/provider-discovery-runtime.js.map +1 -1
  150. package/dist/server/modules/relay-tunnel/crypto/relay-tunnel-identity-service.d.ts +10 -0
  151. package/dist/server/modules/relay-tunnel/crypto/relay-tunnel-identity-service.js +48 -0
  152. package/dist/server/modules/relay-tunnel/crypto/relay-tunnel-identity-service.js.map +1 -0
  153. package/dist/server/modules/relay-tunnel/crypto/relay-tunnel-packets.d.ts +48 -0
  154. package/dist/server/modules/relay-tunnel/crypto/relay-tunnel-packets.js +11 -0
  155. package/dist/server/modules/relay-tunnel/crypto/relay-tunnel-packets.js.map +1 -0
  156. package/dist/server/modules/relay-tunnel/crypto/relay-tunnel-protocol.d.ts +74 -0
  157. package/dist/server/modules/relay-tunnel/crypto/relay-tunnel-protocol.js +302 -0
  158. package/dist/server/modules/relay-tunnel/crypto/relay-tunnel-protocol.js.map +1 -0
  159. package/dist/server/modules/relay-tunnel/relay-tunnel-controller.d.ts +33 -0
  160. package/dist/server/modules/relay-tunnel/relay-tunnel-controller.js +57 -0
  161. package/dist/server/modules/relay-tunnel/relay-tunnel-controller.js.map +1 -0
  162. package/dist/server/modules/relay-tunnel/relay-tunnel-edge-proof.d.ts +9 -0
  163. package/dist/server/modules/relay-tunnel/relay-tunnel-edge-proof.js +25 -0
  164. package/dist/server/modules/relay-tunnel/relay-tunnel-edge-proof.js.map +1 -0
  165. package/dist/server/modules/relay-tunnel/relay-tunnel-gateway-service.d.ts +18 -0
  166. package/dist/server/modules/relay-tunnel/relay-tunnel-gateway-service.js +230 -0
  167. package/dist/server/modules/relay-tunnel/relay-tunnel-gateway-service.js.map +1 -0
  168. package/dist/server/modules/relay-tunnel/relay-tunnel-runtime-adapter.d.ts +41 -0
  169. package/dist/server/modules/relay-tunnel/relay-tunnel-runtime-adapter.js +443 -0
  170. package/dist/server/modules/relay-tunnel/relay-tunnel-runtime-adapter.js.map +1 -0
  171. package/dist/server/modules/relay-tunnel/relay-tunnel-service.d.ts +111 -0
  172. package/dist/server/modules/relay-tunnel/relay-tunnel-service.js +771 -0
  173. package/dist/server/modules/relay-tunnel/relay-tunnel-service.js.map +1 -0
  174. package/dist/server/modules/sessions/claude-runtime-helper-client.js +23 -1
  175. package/dist/server/modules/sessions/claude-runtime-helper-client.js.map +1 -1
  176. package/dist/server/modules/sessions/codex-app-server-helper-client.d.ts +2 -1
  177. package/dist/server/modules/sessions/codex-app-server-helper-client.js +78 -0
  178. package/dist/server/modules/sessions/codex-app-server-helper-client.js.map +1 -1
  179. package/dist/server/modules/sessions/codex-app-server-helper-process.js +84 -2
  180. package/dist/server/modules/sessions/codex-app-server-helper-process.js.map +1 -1
  181. package/dist/server/modules/sessions/provider-session-delete-cli.d.ts +15 -0
  182. package/dist/server/modules/sessions/provider-session-delete-cli.js +148 -0
  183. package/dist/server/modules/sessions/provider-session-delete-cli.js.map +1 -0
  184. package/dist/server/modules/sessions/session-controller.d.ts +4 -1
  185. package/dist/server/modules/sessions/session-controller.js +4 -0
  186. package/dist/server/modules/sessions/session-controller.js.map +1 -1
  187. package/dist/server/modules/sessions/session-history-service.d.ts +24 -1
  188. package/dist/server/modules/sessions/session-history-service.js +401 -42
  189. package/dist/server/modules/sessions/session-history-service.js.map +1 -1
  190. package/dist/server/modules/sessions/session-live-runtime-router-service.d.ts +25 -0
  191. package/dist/server/modules/sessions/session-live-runtime-router-service.js +42 -0
  192. package/dist/server/modules/sessions/session-live-runtime-router-service.js.map +1 -0
  193. package/dist/server/modules/sessions/session-live-runtime-service.d.ts +6 -0
  194. package/dist/server/modules/sessions/session-live-runtime-service.js +130 -28
  195. package/dist/server/modules/sessions/session-live-runtime-service.js.map +1 -1
  196. package/dist/server/modules/sessions/session-message-attachment-service.d.ts +1 -0
  197. package/dist/server/modules/sessions/session-message-attachment-service.js +22 -0
  198. package/dist/server/modules/sessions/session-message-attachment-service.js.map +1 -1
  199. package/dist/server/modules/sessions/session-message-origin-utils.d.ts +12 -0
  200. package/dist/server/modules/sessions/session-message-origin-utils.js +45 -0
  201. package/dist/server/modules/sessions/session-message-origin-utils.js.map +1 -0
  202. package/dist/server/modules/sessions/session-permission-request-service.d.ts +1 -0
  203. package/dist/server/modules/sessions/session-permission-request-service.js +367 -5
  204. package/dist/server/modules/sessions/session-permission-request-service.js.map +1 -1
  205. package/dist/server/modules/sessions/session-provider-error-mapper.js +32 -0
  206. package/dist/server/modules/sessions/session-provider-error-mapper.js.map +1 -1
  207. package/dist/server/modules/sessions/session-provider-usage-guard-service.d.ts +37 -0
  208. package/dist/server/modules/sessions/session-provider-usage-guard-service.js +179 -0
  209. package/dist/server/modules/sessions/session-provider-usage-guard-service.js.map +1 -0
  210. package/dist/server/modules/sessions/session-provider-usage-limit.d.ts +17 -0
  211. package/dist/server/modules/sessions/session-provider-usage-limit.js +465 -0
  212. package/dist/server/modules/sessions/session-provider-usage-limit.js.map +1 -0
  213. package/dist/server/modules/skills/assistant-runtime-skill-catalog.d.ts +8 -0
  214. package/dist/server/modules/skills/assistant-runtime-skill-catalog.js +26 -0
  215. package/dist/server/modules/skills/assistant-runtime-skill-catalog.js.map +1 -0
  216. package/dist/server/modules/skills/assistant-runtime-skill-cleanup.d.ts +9 -0
  217. package/dist/server/modules/skills/assistant-runtime-skill-cleanup.js +55 -0
  218. package/dist/server/modules/skills/assistant-runtime-skill-cleanup.js.map +1 -0
  219. package/dist/server/modules/skills/builtin-skill-service.js +1 -6
  220. package/dist/server/modules/skills/builtin-skill-service.js.map +1 -1
  221. package/dist/server/modules/skills/builtin-skills/codingns-assistant/SKILL.md +19 -12
  222. package/dist/server/modules/skills/builtin-skills/codingns-assistant/references/cli-workflow.md +9 -3
  223. package/dist/server/modules/skills/skill-controller.d.ts +2 -2
  224. package/dist/server/modules/skills/skill-controller.js +9 -1
  225. package/dist/server/modules/skills/skill-controller.js.map +1 -1
  226. package/dist/server/modules/skills/skill-manager-service.d.ts +26 -1
  227. package/dist/server/modules/skills/skill-manager-service.js +346 -90
  228. package/dist/server/modules/skills/skill-manager-service.js.map +1 -1
  229. package/dist/server/modules/skills/skill-name-policy.d.ts +2 -0
  230. package/dist/server/modules/skills/skill-name-policy.js +10 -0
  231. package/dist/server/modules/skills/skill-name-policy.js.map +1 -0
  232. package/dist/server/modules/tailscale/tailscale-service.d.ts +2 -0
  233. package/dist/server/modules/tailscale/tailscale-service.js +21 -8
  234. package/dist/server/modules/tailscale/tailscale-service.js.map +1 -1
  235. package/dist/server/modules/tasks/task-helper-client.d.ts +5 -2
  236. package/dist/server/modules/tasks/task-helper-client.js +118 -38
  237. package/dist/server/modules/tasks/task-helper-client.js.map +1 -1
  238. package/dist/server/modules/tasks/task-helper-process.js +94 -3
  239. package/dist/server/modules/tasks/task-helper-process.js.map +1 -1
  240. package/dist/server/modules/tasks/task-types.d.ts +6 -0
  241. package/dist/server/modules/tasks/task-types.js +7 -1
  242. package/dist/server/modules/tasks/task-types.js.map +1 -1
  243. package/dist/server/modules/terminal/command-template-service.d.ts +9 -0
  244. package/dist/server/modules/terminal/command-template-service.js +87 -5
  245. package/dist/server/modules/terminal/command-template-service.js.map +1 -1
  246. package/dist/server/modules/terminal/template-reverse-proxy-service.js +71 -3
  247. package/dist/server/modules/terminal/template-reverse-proxy-service.js.map +1 -1
  248. package/dist/server/modules/terminal/terminal-controller.d.ts +3 -0
  249. package/dist/server/modules/terminal/terminal-controller.js +41 -0
  250. package/dist/server/modules/terminal/terminal-controller.js.map +1 -1
  251. package/dist/server/modules/workbench/workbench-service.d.ts +3 -0
  252. package/dist/server/modules/workbench/workbench-service.js +4 -3
  253. package/dist/server/modules/workbench/workbench-service.js.map +1 -1
  254. package/dist/server/modules/workbench/workspace-file-watcher.d.ts +14 -6
  255. package/dist/server/modules/workbench/workspace-file-watcher.js +267 -57
  256. package/dist/server/modules/workbench/workspace-file-watcher.js.map +1 -1
  257. package/dist/server/modules/workbench/workspace-panel-snapshot-service.d.ts +2 -0
  258. package/dist/server/modules/workbench/workspace-panel-snapshot-service.js +32 -3
  259. package/dist/server/modules/workbench/workspace-panel-snapshot-service.js.map +1 -1
  260. package/dist/server/modules/worktree/worktree-manager.d.ts +9 -1
  261. package/dist/server/modules/worktree/worktree-manager.js +9 -1
  262. package/dist/server/modules/worktree/worktree-manager.js.map +1 -1
  263. package/dist/server/routes/assistant.js +49 -0
  264. package/dist/server/routes/assistant.js.map +1 -1
  265. package/dist/server/routes/auth.js +4 -0
  266. package/dist/server/routes/auth.js.map +1 -1
  267. package/dist/server/routes/butler.js +5 -0
  268. package/dist/server/routes/butler.js.map +1 -1
  269. package/dist/server/routes/sessions.js +1 -0
  270. package/dist/server/routes/sessions.js.map +1 -1
  271. package/dist/server/routes/system.d.ts +2 -1
  272. package/dist/server/routes/system.js +13 -1
  273. package/dist/server/routes/system.js.map +1 -1
  274. package/dist/server/server/create-server.d.ts +18 -0
  275. package/dist/server/server/create-server.js +113 -20
  276. package/dist/server/server/create-server.js.map +1 -1
  277. package/dist/server/shared/utils/tokens.d.ts +3 -1
  278. package/dist/server/shared/utils/tokens.js +9 -2
  279. package/dist/server/shared/utils/tokens.js.map +1 -1
  280. package/dist/server/storage/repositories/assistant-automation-run-repository.d.ts +12 -0
  281. package/dist/server/storage/repositories/assistant-automation-run-repository.js +139 -0
  282. package/dist/server/storage/repositories/assistant-automation-run-repository.js.map +1 -0
  283. package/dist/server/storage/repositories/assistant-automation-task-repository.d.ts +17 -0
  284. package/dist/server/storage/repositories/assistant-automation-task-repository.js +179 -0
  285. package/dist/server/storage/repositories/assistant-automation-task-repository.js.map +1 -0
  286. package/dist/server/storage/repositories/assistant-sandbox-workspace-repository.d.ts +18 -0
  287. package/dist/server/storage/repositories/assistant-sandbox-workspace-repository.js +191 -0
  288. package/dist/server/storage/repositories/assistant-sandbox-workspace-repository.js.map +1 -0
  289. package/dist/server/storage/repositories/auth-device-repository.d.ts +22 -0
  290. package/dist/server/storage/repositories/auth-device-repository.js +97 -0
  291. package/dist/server/storage/repositories/auth-device-repository.js.map +1 -0
  292. package/dist/server/storage/repositories/auth-device-session-repository.d.ts +17 -0
  293. package/dist/server/storage/repositories/auth-device-session-repository.js +82 -0
  294. package/dist/server/storage/repositories/auth-device-session-repository.js.map +1 -0
  295. package/dist/server/storage/repositories/auth-login-event-repository.d.ts +9 -0
  296. package/dist/server/storage/repositories/auth-login-event-repository.js +53 -0
  297. package/dist/server/storage/repositories/auth-login-event-repository.js.map +1 -0
  298. package/dist/server/storage/repositories/auth-token-repository.d.ts +4 -0
  299. package/dist/server/storage/repositories/auth-token-repository.js +58 -5
  300. package/dist/server/storage/repositories/auth-token-repository.js.map +1 -1
  301. package/dist/server/storage/repositories/butler-control-session-repository.js +27 -3
  302. package/dist/server/storage/repositories/butler-control-session-repository.js.map +1 -1
  303. package/dist/server/storage/repositories/butler-control-timer-repository.d.ts +15 -0
  304. package/dist/server/storage/repositories/butler-control-timer-repository.js +157 -0
  305. package/dist/server/storage/repositories/butler-control-timer-repository.js.map +1 -0
  306. package/dist/server/storage/repositories/butler-follow-up-task-repository.js +21 -3
  307. package/dist/server/storage/repositories/butler-follow-up-task-repository.js.map +1 -1
  308. package/dist/server/storage/repositories/instance-relay-tunnel-identity-repository.d.ts +8 -0
  309. package/dist/server/storage/repositories/instance-relay-tunnel-identity-repository.js +52 -0
  310. package/dist/server/storage/repositories/instance-relay-tunnel-identity-repository.js.map +1 -0
  311. package/dist/server/storage/repositories/instance-relay-tunnel-repository.d.ts +10 -0
  312. package/dist/server/storage/repositories/instance-relay-tunnel-repository.js +153 -0
  313. package/dist/server/storage/repositories/instance-relay-tunnel-repository.js.map +1 -0
  314. package/dist/server/storage/repositories/instance-tailscale-repository.js +6 -3
  315. package/dist/server/storage/repositories/instance-tailscale-repository.js.map +1 -1
  316. package/dist/server/storage/repositories/managed-skill-repository.d.ts +2 -1
  317. package/dist/server/storage/repositories/managed-skill-repository.js +14 -4
  318. package/dist/server/storage/repositories/managed-skill-repository.js.map +1 -1
  319. package/dist/server/storage/repositories/session-message-attachment-repository.d.ts +2 -0
  320. package/dist/server/storage/repositories/session-message-attachment-repository.js +24 -0
  321. package/dist/server/storage/repositories/session-message-attachment-repository.js.map +1 -1
  322. package/dist/server/storage/repositories/user-preference-profile-repository.js +6 -3
  323. package/dist/server/storage/repositories/user-preference-profile-repository.js.map +1 -1
  324. package/dist/server/storage/sqlite/client.js +534 -2
  325. package/dist/server/storage/sqlite/client.js.map +1 -1
  326. package/dist/server/storage/sqlite/schema.sql +228 -4
  327. package/dist/server/types/domain.d.ts +170 -2
  328. package/dist/server/ws/workbench-ws-hub.d.ts +14 -8
  329. package/dist/server/ws/workbench-ws-hub.js +369 -209
  330. package/dist/server/ws/workbench-ws-hub.js.map +1 -1
  331. package/dist/server/ws/ws-auth-guard.js +1 -4
  332. package/dist/server/ws/ws-auth-guard.js.map +1 -1
  333. package/dist/server/ws/ws-server.d.ts +1 -1
  334. package/dist/server/ws/ws-server.js.map +1 -1
  335. package/node_modules/@codingns/session-sync-core/dist/codex-resume-history.d.ts +1 -0
  336. package/node_modules/@codingns/session-sync-core/dist/codex-resume-history.js +80 -0
  337. package/node_modules/@codingns/session-sync-core/dist/codex-resume-history.js.map +1 -0
  338. package/node_modules/@codingns/session-sync-core/dist/providers/claude-code.d.ts +5 -1
  339. package/node_modules/@codingns/session-sync-core/dist/providers/claude-code.js +122 -4
  340. package/node_modules/@codingns/session-sync-core/dist/providers/claude-code.js.map +1 -1
  341. package/node_modules/@codingns/session-sync-core/dist/providers/codex.d.ts +17 -1
  342. package/node_modules/@codingns/session-sync-core/dist/providers/codex.js +437 -51
  343. package/node_modules/@codingns/session-sync-core/dist/providers/codex.js.map +1 -1
  344. package/node_modules/@codingns/session-sync-core/dist/providers/gemini.d.ts +7 -1
  345. package/node_modules/@codingns/session-sync-core/dist/providers/gemini.js +240 -27
  346. package/node_modules/@codingns/session-sync-core/dist/providers/gemini.js.map +1 -1
  347. package/node_modules/@codingns/session-sync-core/dist/providers/kimi.d.ts +5 -1
  348. package/node_modules/@codingns/session-sync-core/dist/providers/kimi.js +108 -2
  349. package/node_modules/@codingns/session-sync-core/dist/providers/kimi.js.map +1 -1
  350. package/node_modules/@codingns/session-sync-core/dist/providers/opencode.d.ts +3 -0
  351. package/node_modules/@codingns/session-sync-core/dist/providers/opencode.js +101 -8
  352. package/node_modules/@codingns/session-sync-core/dist/providers/opencode.js.map +1 -1
  353. package/node_modules/@codingns/session-sync-core/dist/providers/utils.d.ts +1 -0
  354. package/node_modules/@codingns/session-sync-core/dist/providers/utils.js +4 -1
  355. package/node_modules/@codingns/session-sync-core/dist/providers/utils.js.map +1 -1
  356. package/node_modules/@codingns/session-sync-core/dist/runtime/claude-runtime.js +44 -0
  357. package/node_modules/@codingns/session-sync-core/dist/runtime/claude-runtime.js.map +1 -1
  358. package/node_modules/@codingns/session-sync-core/dist/runtime/codex-runtime.d.ts +5 -1
  359. package/node_modules/@codingns/session-sync-core/dist/runtime/codex-runtime.js +153 -60
  360. package/node_modules/@codingns/session-sync-core/dist/runtime/codex-runtime.js.map +1 -1
  361. package/node_modules/@codingns/session-sync-core/dist/runtime/types.d.ts +1 -0
  362. package/node_modules/@codingns/session-sync-core/dist/services.d.ts +1 -0
  363. package/node_modules/@codingns/session-sync-core/dist/services.js +24 -8
  364. package/node_modules/@codingns/session-sync-core/dist/services.js.map +1 -1
  365. package/node_modules/@codingns/session-sync-core/dist/types.d.ts +6 -0
  366. package/package.json +1 -1
  367. package/scripts/postinstall.mjs +0 -33
  368. package/dist/public/assets/index-BlOinYqR.js +0 -122
  369. package/dist/public/assets/index-Dg_7g6lA.css +0 -1
@@ -1,11 +1,9 @@
1
- import fs from "node:fs";
2
- import os from "node:os";
3
- import path from "node:path";
4
1
  import { AppError } from "../../shared/errors/app-error.js";
5
2
  import { createId } from "../../shared/utils/id.js";
6
3
  import { nowIso } from "../../shared/utils/time.js";
7
- import { ensureButlerWorkspaceIsolation } from "./butler-profile-service.js";
8
4
  import { resolveButlerCodexBackgroundModel } from "./butler-codex-model-policy.js";
5
+ import { normalizeProviderUsageLimit } from "../sessions/session-provider-usage-limit.js";
6
+ import { resolveProviderUsageLimitBlockedUntil, resolveProviderUsageLimitFromError, SessionProviderUsageLimitGuardService } from "../sessions/session-provider-usage-guard-service.js";
9
7
  const DEFAULT_CHECK_INTERVAL_SECONDS = 300;
10
8
  const MIN_CHECK_INTERVAL_SECONDS = 60;
11
9
  const MAX_CHECK_INTERVAL_SECONDS = 3600;
@@ -15,6 +13,8 @@ const MAX_MAX_AUTO_CONTINUE_COUNT = 20;
15
13
  const FOLLOW_UP_EVALUATOR_DIRNAME = ".butler-follow-up-evaluator";
16
14
  const RECENT_HISTORY_LIMIT = 40;
17
15
  const FOLLOW_UP_PERMISSION_CHECK_INTERVAL_MS = 10_000;
16
+ const FOLLOW_UP_ASSISTANT_WAIT_TIMEOUT_MS = 20 * 60_000;
17
+ const FOLLOW_UP_ASSISTANT_WAIT_POLL_INTERVAL_MS = 2_000;
18
18
  const FOLLOW_UP_AUTO_APPROVE_ACTION_PREFERENCE = [
19
19
  "acceptForSession",
20
20
  "allow_session",
@@ -23,6 +23,12 @@ const FOLLOW_UP_AUTO_APPROVE_ACTION_PREFERENCE = [
23
23
  "once",
24
24
  "allow"
25
25
  ];
26
+ class FollowUpTaskCancelledError extends Error {
27
+ constructor() {
28
+ super("FOLLOW_UP_TASK_CANCELLED");
29
+ this.name = "FollowUpTaskCancelledError";
30
+ }
31
+ }
26
32
  export class ButlerFollowUpService {
27
33
  butlerProfileService;
28
34
  butlerProjectService;
@@ -37,8 +43,10 @@ export class ButlerFollowUpService {
37
43
  followUpCodexHomeDir;
38
44
  sourceCodexHomeDir;
39
45
  sessionMessageOriginRepository;
46
+ providerUsageLimitGuardService;
40
47
  permissionRequestSweepAtByTaskId = new Map();
41
- constructor(butlerProfileService, butlerProjectService, butlerSessionService, butlerFollowUpTaskRepository, sessionHistoryService, sessionIndexRepository, sessionLiveRuntimeService, workspaceService, providerAdapterRegistry, instructionAdapter, followUpCodexHomeDir = null, sourceCodexHomeDir = null, sessionMessageOriginRepository = null) {
48
+ activeExecutionStateByTaskId = new Map();
49
+ constructor(butlerProfileService, butlerProjectService, butlerSessionService, butlerFollowUpTaskRepository, sessionHistoryService, sessionIndexRepository, sessionLiveRuntimeService, workspaceService, providerAdapterRegistry, instructionAdapter, followUpCodexHomeDir = null, sourceCodexHomeDir = null, sessionMessageOriginRepository = null, providerUsageLimitGuardService = new SessionProviderUsageLimitGuardService(sessionHistoryService)) {
42
50
  this.butlerProfileService = butlerProfileService;
43
51
  this.butlerProjectService = butlerProjectService;
44
52
  this.butlerSessionService = butlerSessionService;
@@ -52,6 +60,7 @@ export class ButlerFollowUpService {
52
60
  this.followUpCodexHomeDir = followUpCodexHomeDir;
53
61
  this.sourceCodexHomeDir = sourceCodexHomeDir;
54
62
  this.sessionMessageOriginRepository = sessionMessageOriginRepository;
63
+ this.providerUsageLimitGuardService = providerUsageLimitGuardService;
55
64
  }
56
65
  listTasks(filters = {}) {
57
66
  this.butlerProfileService.ensureInitialized();
@@ -78,9 +87,41 @@ export class ButlerFollowUpService {
78
87
  const index = this.sessionIndexRepository.findIndexRecordBySessionId(task.sessionId);
79
88
  return mapTaskView(task, project.workspaceId, project.name, index?.title ?? null);
80
89
  }
90
+ toTaskView(task) {
91
+ const project = this.butlerProjectService.getById(task.projectId);
92
+ const index = this.sessionIndexRepository.findIndexRecordBySessionId(task.sessionId);
93
+ return mapTaskView(task, project.workspaceId, project.name, index?.title ?? null);
94
+ }
95
+ requireTaskForAssistantUpdate(taskId, userId) {
96
+ this.butlerProfileService.ensureInitialized();
97
+ const task = this.butlerFollowUpTaskRepository.findById(taskId);
98
+ if (!task) {
99
+ throw new AppError({
100
+ statusCode: 404,
101
+ errorCode: "BUTLER_FOLLOW_UP_TASK_NOT_FOUND",
102
+ detail: "未找到对应的跟进任务"
103
+ });
104
+ }
105
+ if (task.createdByUserId !== userId) {
106
+ throw new AppError({
107
+ statusCode: 403,
108
+ errorCode: "BUTLER_FOLLOW_UP_TASK_FORBIDDEN",
109
+ detail: "你没有权限更新这个跟进任务"
110
+ });
111
+ }
112
+ if (task.status !== "active") {
113
+ throw new AppError({
114
+ statusCode: 409,
115
+ errorCode: "BUTLER_FOLLOW_UP_TASK_NOT_ACTIVE",
116
+ detail: "当前跟进任务已经不处于可回写状态"
117
+ });
118
+ }
119
+ return task;
120
+ }
81
121
  async createTask(input, userId) {
82
122
  this.butlerProfileService.ensureInitialized();
83
123
  const project = this.butlerProjectService.getById(input.projectId);
124
+ const providerId = normalizeFollowUpProviderId(input.providerId);
84
125
  const objective = normalizeObjective(input.objective);
85
126
  const completionCriteria = normalizeCompletionCriteria(input.completionCriteria, objective);
86
127
  const maxAutoContinueCount = normalizeMaxAutoContinueCount(input.maxAutoContinueCount);
@@ -95,15 +136,38 @@ export class ButlerFollowUpService {
95
136
  });
96
137
  }
97
138
  const session = this.sessionHistoryService.getSession(snapshot.sessionId, userId);
139
+ const inspection = await this.inspectSession(snapshot.sessionId, userId);
140
+ const assistantSession = await this.butlerSessionService.startSession(project.id, {
141
+ providerId,
142
+ role: "adhoc",
143
+ ownershipMode: "managed",
144
+ content: buildFollowUpBootstrapPrompt({
145
+ project,
146
+ sourceButlerSessionId: input.butlerSessionId,
147
+ sourceSessionId: snapshot.sessionId,
148
+ sourceSessionTitle: inspection.sessionTitle,
149
+ objective,
150
+ completionCriteria,
151
+ maxAutoContinueCount,
152
+ latestAssistantText: inspection.latestAssistantText,
153
+ transcriptLines: inspection.transcriptLines
154
+ }),
155
+ model: resolveFollowUpModel(providerId, this.sourceCodexHomeDir),
156
+ reasoningLevel: "low",
157
+ permissionMode: "default"
158
+ }, userId);
98
159
  const timestamp = nowIso();
99
160
  const initialSummary = snapshot.runningState === "starting" || snapshot.runningState === "running"
100
- ? `已开始跟进,先等待当前运行结束,再由后台评估助手决定下一步。默认最多自动推进 ${maxAutoContinueCount} 轮。`
101
- : `已开始跟进,准备由后台评估助手检查当前进展。默认最多自动推进 ${maxAutoContinueCount} 轮。`;
161
+ ? `已创建跟进助手会话,先等待当前运行结束,再由该会话决定是否继续推进。默认最多自动推进 ${maxAutoContinueCount} 轮。`
162
+ : `已创建跟进助手会话,准备由该会话检查当前进展并决定是否继续推进。默认最多自动推进 ${maxAutoContinueCount} 轮。`;
102
163
  const task = this.butlerFollowUpTaskRepository.create({
103
164
  id: createId(),
104
165
  projectId: project.id,
105
166
  butlerSessionId: input.butlerSessionId,
106
167
  sessionId: snapshot.sessionId,
168
+ providerId,
169
+ assistantButlerSessionId: assistantSession.id,
170
+ assistantSessionId: assistantSession.sessionId,
107
171
  createdByUserId: userId,
108
172
  objective,
109
173
  completionCriteria,
@@ -129,7 +193,144 @@ export class ButlerFollowUpService {
129
193
  const processed = await this.processTask(task.id);
130
194
  return mapTaskView(processed, project.workspaceId, project.name, session.title ?? null);
131
195
  }
132
- cancelTask(taskId, userId) {
196
+ async continueTask(taskId, input, userId) {
197
+ const summary = requireNonEmptyFollowUpText(input.summary, "summary", "继续推进必须提供 summary");
198
+ const continuePrompt = requireNonEmptyFollowUpText(input.continuePrompt, "continuePrompt", "继续推进必须提供 continuePrompt");
199
+ const task = this.requireTaskForAssistantUpdate(taskId, userId);
200
+ if (hasReachedAutoContinueLimit(task)) {
201
+ throw new AppError({
202
+ statusCode: 409,
203
+ errorCode: "BUTLER_FOLLOW_UP_TASK_LIMIT_REACHED",
204
+ detail: "当前跟进任务已经达到自动推进上限,不能继续自动推进"
205
+ });
206
+ }
207
+ this.butlerSessionService.captureSessionSnapshot(task.projectId, task.butlerSessionId, task.createdByUserId, { sourceKind: "manual" });
208
+ const inspection = await this.inspectTask(task);
209
+ const timestamp = nowIso();
210
+ const runningState = normalizeRunningState(inspection.runningState);
211
+ const nextAutoContinueCount = task.autoContinueCount + 1;
212
+ const updated = this.persistWithRound({
213
+ ...task,
214
+ status: "active",
215
+ lastCheckedAt: timestamp,
216
+ lastObservedRunningState: runningState,
217
+ lastObservedMessageAt: inspection.messageAt,
218
+ lastObservedMessageCount: inspection.messageCount,
219
+ waitingReason: null,
220
+ nextCheckAt: shiftSeconds(timestamp, task.checkIntervalSeconds),
221
+ lastAutomationSummary: summary,
222
+ lastAutomationAt: timestamp,
223
+ autoContinueCount: nextAutoContinueCount,
224
+ updatedAt: timestamp,
225
+ completedAt: null
226
+ }, {
227
+ kind: "continue",
228
+ status: "active",
229
+ summary,
230
+ waitingReason: null,
231
+ continuePrompt,
232
+ observedRunningState: runningState,
233
+ autoContinueCount: nextAutoContinueCount,
234
+ createdAt: timestamp
235
+ });
236
+ return this.toTaskView(updated);
237
+ }
238
+ async markTaskWaitingUser(taskId, input, userId) {
239
+ const summary = requireNonEmptyFollowUpText(input.summary, "summary", "等待用户必须提供 summary");
240
+ const waitingReason = requireNonEmptyFollowUpText(input.waitingReason, "waitingReason", "等待用户必须提供 waitingReason");
241
+ const task = this.requireTaskForAssistantUpdate(taskId, userId);
242
+ const inspection = await this.inspectTask(task);
243
+ const timestamp = nowIso();
244
+ const runningState = normalizeRunningState(inspection.runningState);
245
+ const updated = this.persistWithRound({
246
+ ...task,
247
+ status: "waiting_user",
248
+ lastCheckedAt: timestamp,
249
+ lastObservedRunningState: runningState,
250
+ lastObservedMessageAt: inspection.messageAt,
251
+ lastObservedMessageCount: inspection.messageCount,
252
+ waitingReason,
253
+ nextCheckAt: null,
254
+ lastAutomationSummary: summary,
255
+ lastAutomationAt: timestamp,
256
+ updatedAt: timestamp,
257
+ completedAt: null
258
+ }, {
259
+ kind: "waiting_user",
260
+ status: "waiting_user",
261
+ summary,
262
+ waitingReason,
263
+ continuePrompt: null,
264
+ observedRunningState: runningState,
265
+ autoContinueCount: task.autoContinueCount,
266
+ createdAt: timestamp
267
+ });
268
+ return this.toTaskView(updated);
269
+ }
270
+ async completeTask(taskId, input, userId) {
271
+ const summary = requireNonEmptyFollowUpText(input.summary, "summary", "完成任务必须提供 summary");
272
+ const task = this.requireTaskForAssistantUpdate(taskId, userId);
273
+ const inspection = await this.inspectTask(task);
274
+ const timestamp = nowIso();
275
+ const runningState = normalizeRunningState(inspection.runningState);
276
+ const updated = this.persistWithRound({
277
+ ...task,
278
+ status: "completed",
279
+ lastCheckedAt: timestamp,
280
+ lastObservedRunningState: runningState,
281
+ lastObservedMessageAt: inspection.messageAt,
282
+ lastObservedMessageCount: inspection.messageCount,
283
+ waitingReason: null,
284
+ nextCheckAt: null,
285
+ lastAutomationSummary: summary,
286
+ lastAutomationAt: timestamp,
287
+ updatedAt: timestamp,
288
+ completedAt: timestamp
289
+ }, {
290
+ kind: "completed",
291
+ status: "completed",
292
+ summary,
293
+ waitingReason: null,
294
+ continuePrompt: null,
295
+ observedRunningState: runningState,
296
+ autoContinueCount: task.autoContinueCount,
297
+ createdAt: timestamp
298
+ });
299
+ return this.toTaskView(updated);
300
+ }
301
+ async failTask(taskId, input, userId) {
302
+ const summary = requireNonEmptyFollowUpText(input.summary, "summary", "标记失败必须提供 summary");
303
+ const reason = normalizeNullableText(input.reason) ?? summary;
304
+ const task = this.requireTaskForAssistantUpdate(taskId, userId);
305
+ const inspection = await this.inspectTask(task);
306
+ const timestamp = nowIso();
307
+ const runningState = normalizeRunningState(inspection.runningState);
308
+ const updated = this.persistWithRound({
309
+ ...task,
310
+ status: "failed",
311
+ lastCheckedAt: timestamp,
312
+ lastObservedRunningState: runningState,
313
+ lastObservedMessageAt: inspection.messageAt,
314
+ lastObservedMessageCount: inspection.messageCount,
315
+ waitingReason: reason,
316
+ nextCheckAt: null,
317
+ lastAutomationSummary: summary,
318
+ lastAutomationAt: timestamp,
319
+ updatedAt: timestamp,
320
+ completedAt: null
321
+ }, {
322
+ kind: "failed",
323
+ status: "failed",
324
+ summary,
325
+ waitingReason: reason,
326
+ continuePrompt: null,
327
+ observedRunningState: runningState,
328
+ autoContinueCount: task.autoContinueCount,
329
+ createdAt: timestamp
330
+ });
331
+ return this.toTaskView(updated);
332
+ }
333
+ async cancelTask(taskId, userId) {
133
334
  this.butlerProfileService.ensureInitialized();
134
335
  const task = this.butlerFollowUpTaskRepository.findById(taskId);
135
336
  if (!task) {
@@ -153,6 +354,7 @@ export class ButlerFollowUpService {
153
354
  detail: "当前跟进任务已经结束,不能再次停止"
154
355
  });
155
356
  }
357
+ const execution = this.markTaskExecutionCancelled(task.id);
156
358
  const timestamp = nowIso();
157
359
  const updated = this.persistWithRound({
158
360
  ...task,
@@ -173,6 +375,7 @@ export class ButlerFollowUpService {
173
375
  autoContinueCount: task.autoContinueCount,
174
376
  createdAt: timestamp
175
377
  });
378
+ await this.stopActiveTaskAutomation(task, execution);
176
379
  const project = this.butlerProjectService.getById(updated.projectId);
177
380
  const index = this.sessionIndexRepository.findIndexRecordBySessionId(updated.sessionId);
178
381
  return mapTaskView(updated, project.workspaceId, project.name, index?.title ?? null);
@@ -225,209 +428,136 @@ export class ButlerFollowUpService {
225
428
  if (task.status !== "active") {
226
429
  return task;
227
430
  }
228
- const profile = this.butlerProfileService.ensureInitialized();
229
- const project = this.butlerProjectService.getById(task.projectId);
230
- const inspection = await this.inspectTask(task);
231
- const runningState = normalizeRunningState(inspection.runningState);
232
- const baseUpdate = {
233
- ...task,
234
- lastCheckedAt: referenceAt,
235
- lastObservedRunningState: runningState,
236
- lastObservedMessageAt: inspection.messageAt,
237
- lastObservedMessageCount: inspection.messageCount,
238
- updatedAt: referenceAt
239
- };
240
- if (runningState === "starting" || runningState === "running") {
241
- return this.persist({
242
- ...baseUpdate,
243
- status: "active",
244
- waitingReason: null,
245
- nextCheckAt: shiftSeconds(referenceAt, task.checkIntervalSeconds),
246
- lastAutomationSummary: hasReachedAutoContinueLimit(task)
247
- ? `会话仍在运行,但已达到预设的自动跟进轮数上限(${task.autoContinueCount}/${task.maxAutoContinueCount}),本轮结束后将停止自动续接。`
248
- : "会话仍在运行,助手继续观察当前进度。"
249
- });
250
- }
251
- if (hasReachedAutoContinueLimit(task)) {
252
- const waitingReason = `已达到预设的自动跟进轮数上限(${task.autoContinueCount}/${task.maxAutoContinueCount}),如需继续,请手动重新发起跟进。`;
253
- const summary = `自动跟进已按预设上限停止。结束条件:${task.completionCriteria}`;
254
- return this.persistWithRound({
255
- ...baseUpdate,
256
- status: "waiting_user",
257
- waitingReason,
258
- nextCheckAt: null,
259
- completedAt: null,
260
- lastAutomationAt: referenceAt,
261
- lastAutomationSummary: summary
262
- }, {
263
- kind: "limit_reached",
264
- status: "waiting_user",
265
- summary,
266
- waitingReason,
267
- continuePrompt: null,
268
- observedRunningState: runningState,
269
- autoContinueCount: task.autoContinueCount,
270
- createdAt: referenceAt
271
- });
272
- }
431
+ const execution = this.beginTaskExecution(task.id);
273
432
  try {
274
- const evaluation = await this.evaluateTask(profile, project, task, inspection, runningState);
275
- switch (evaluation.decision) {
276
- case "completed":
277
- return this.persistWithRound({
433
+ const project = this.butlerProjectService.getById(task.projectId);
434
+ const inspection = await this.inspectTask(task);
435
+ this.ensureTaskExecutionActive(task.id, execution);
436
+ const runningState = normalizeRunningState(inspection.runningState);
437
+ const baseUpdate = {
438
+ ...task,
439
+ lastCheckedAt: referenceAt,
440
+ lastObservedRunningState: runningState,
441
+ lastObservedMessageAt: inspection.messageAt,
442
+ lastObservedMessageCount: inspection.messageCount,
443
+ updatedAt: referenceAt
444
+ };
445
+ if (runningState === "starting" || runningState === "running") {
446
+ return this.persistIfExecutionActive(task.id, execution, {
447
+ ...baseUpdate,
448
+ status: "active",
449
+ waitingReason: null,
450
+ nextCheckAt: shiftSeconds(referenceAt, task.checkIntervalSeconds),
451
+ lastAutomationSummary: hasReachedAutoContinueLimit(task)
452
+ ? `会话仍在运行,但已达到预设的自动跟进轮数上限(${task.autoContinueCount}/${task.maxAutoContinueCount}),本轮结束后将停止自动续接。`
453
+ : "会话仍在运行,助手继续观察当前进度。"
454
+ });
455
+ }
456
+ if (hasReachedAutoContinueLimit(task)) {
457
+ const waitingReason = `已达到预设的自动跟进轮数上限(${task.autoContinueCount}/${task.maxAutoContinueCount}),如需继续,请手动重新发起跟进。`;
458
+ const summary = `自动跟进已按预设上限停止。结束条件:${task.completionCriteria}`;
459
+ return this.persistWithRoundIfExecutionActive(task.id, execution, {
460
+ ...baseUpdate,
461
+ status: "waiting_user",
462
+ waitingReason,
463
+ nextCheckAt: null,
464
+ completedAt: null,
465
+ lastAutomationAt: referenceAt,
466
+ lastAutomationSummary: summary
467
+ }, {
468
+ kind: "limit_reached",
469
+ status: "waiting_user",
470
+ summary,
471
+ waitingReason,
472
+ continuePrompt: null,
473
+ observedRunningState: runningState,
474
+ autoContinueCount: task.autoContinueCount,
475
+ createdAt: referenceAt
476
+ });
477
+ }
478
+ const usageLimitBlock = await this.providerUsageLimitGuardService.resolveBlockingInspection([
479
+ {
480
+ sessionId: task.sessionId,
481
+ userId: task.createdByUserId,
482
+ sourceLabel: "跟进目标会话"
483
+ },
484
+ {
485
+ sessionId: task.assistantSessionId,
486
+ userId: task.createdByUserId,
487
+ sourceLabel: "跟进助手会话"
488
+ }
489
+ ], referenceAt);
490
+ if (usageLimitBlock) {
491
+ const nextCheckAt = usageLimitBlock.blockedUntil;
492
+ return this.persistIfExecutionActive(task.id, execution, {
493
+ ...baseUpdate,
494
+ status: "active",
495
+ waitingReason: null,
496
+ nextCheckAt,
497
+ completedAt: null,
498
+ lastAutomationAt: referenceAt,
499
+ lastAutomationSummary: buildFollowUpUsageLimitSummary(usageLimitBlock.inspection.providerUsageLimit, `${usageLimitBlock.inspection.sourceLabel ?? "当前会话"}被 provider 套餐限额暂时挡住。`)
500
+ });
501
+ }
502
+ try {
503
+ const progressBeforeDispatch = snapshotTaskProgress(task);
504
+ await this.requestAssistantEvaluation(project, task, inspection, runningState, execution);
505
+ this.ensureTaskExecutionActive(task.id, execution);
506
+ return this.requireAssistantDecisionPersisted(task.id, progressBeforeDispatch);
507
+ }
508
+ catch (error) {
509
+ if (error instanceof FollowUpTaskCancelledError) {
510
+ return this.butlerFollowUpTaskRepository.findById(task.id) ?? task;
511
+ }
512
+ const providerUsageLimit = resolveProviderUsageLimitFromError(error, task.providerId, referenceAt);
513
+ if (providerUsageLimit) {
514
+ return this.persistIfExecutionActive(task.id, execution, {
278
515
  ...baseUpdate,
279
- status: "completed",
280
- waitingReason: null,
281
- nextCheckAt: null,
282
- completedAt: referenceAt,
283
- lastAutomationAt: referenceAt,
284
- lastAutomationSummary: evaluation.summary
285
- }, {
286
- kind: "completed",
287
- status: "completed",
288
- summary: evaluation.summary,
516
+ status: "active",
289
517
  waitingReason: null,
290
- continuePrompt: null,
291
- observedRunningState: runningState,
292
- autoContinueCount: task.autoContinueCount,
293
- createdAt: referenceAt
294
- });
295
- case "waiting_user":
296
- return this.persistWithRound({
297
- ...baseUpdate,
298
- status: "waiting_user",
299
- waitingReason: evaluation.waitingReason ?? evaluation.summary,
300
- nextCheckAt: null,
301
- completedAt: null,
302
- lastAutomationAt: referenceAt,
303
- lastAutomationSummary: evaluation.summary
304
- }, {
305
- kind: "waiting_user",
306
- status: "waiting_user",
307
- summary: evaluation.summary,
308
- waitingReason: evaluation.waitingReason ?? evaluation.summary,
309
- continuePrompt: null,
310
- observedRunningState: runningState,
311
- autoContinueCount: task.autoContinueCount,
312
- createdAt: referenceAt
313
- });
314
- case "failed":
315
- return this.persistWithRound({
316
- ...baseUpdate,
317
- status: "failed",
318
- waitingReason: evaluation.waitingReason ?? evaluation.summary,
319
- nextCheckAt: null,
518
+ nextCheckAt: resolveProviderUsageLimitNextCheckAt(providerUsageLimit, referenceAt, task.checkIntervalSeconds),
320
519
  completedAt: null,
321
520
  lastAutomationAt: referenceAt,
322
- lastAutomationSummary: evaluation.summary
323
- }, {
324
- kind: "failed",
325
- status: "failed",
326
- summary: evaluation.summary,
327
- waitingReason: evaluation.waitingReason ?? evaluation.summary,
328
- continuePrompt: null,
329
- observedRunningState: runningState,
330
- autoContinueCount: task.autoContinueCount,
331
- createdAt: referenceAt
521
+ lastAutomationSummary: buildFollowUpUsageLimitSummary(providerUsageLimit, "跟进助手会话当前被 provider 额度限制暂时挡住。")
332
522
  });
333
- case "continue":
334
- if (!evaluation.continuePrompt) {
335
- return this.persistWithRound({
336
- ...baseUpdate,
337
- status: "failed",
338
- waitingReason: "后台评估助手没有返回可继续推进的指令。",
339
- nextCheckAt: null,
340
- completedAt: null,
341
- lastAutomationAt: referenceAt,
342
- lastAutomationSummary: evaluation.summary
343
- }, {
344
- kind: "failed",
345
- status: "failed",
346
- summary: evaluation.summary,
347
- waitingReason: "后台评估助手没有返回可继续推进的指令。",
348
- continuePrompt: null,
349
- observedRunningState: runningState,
350
- autoContinueCount: task.autoContinueCount,
351
- createdAt: referenceAt
352
- });
353
- }
354
- const sendResult = await this.sendContinuePrompt(task, evaluation.continuePrompt, referenceAt);
355
- this.butlerSessionService.captureSessionSnapshot(task.projectId, task.butlerSessionId, task.createdByUserId, { sourceKind: "manual" });
356
- const nextAutoContinueCount = task.autoContinueCount + 1;
357
- const nextSummary = sendResult.delivery === "queued"
358
- ? buildQueuedFollowUpSummary(evaluation.summary, sendResult.queueItem)
359
- : evaluation.summary;
360
- return this.persistWithRound({
523
+ }
524
+ if (isDeferredFollowUpSendError(error)) {
525
+ return this.persistIfExecutionActive(task.id, execution, {
361
526
  ...baseUpdate,
362
527
  status: "active",
363
528
  waitingReason: null,
364
529
  nextCheckAt: shiftSeconds(referenceAt, task.checkIntervalSeconds),
365
- lastAutomationAt: referenceAt,
366
- autoContinueCount: nextAutoContinueCount,
367
- lastAutomationSummary: nextSummary
368
- }, {
369
- kind: sendResult.delivery === "queued" ? "queued" : "continue",
370
- status: "active",
371
- summary: nextSummary,
372
- waitingReason: null,
373
- continuePrompt: evaluation.continuePrompt,
374
- observedRunningState: runningState,
375
- autoContinueCount: nextAutoContinueCount,
376
- createdAt: referenceAt
377
- });
378
- default:
379
- return this.persistWithRound({
380
- ...baseUpdate,
381
- status: "failed",
382
- waitingReason: "后台评估助手返回了不支持的决策。",
383
- nextCheckAt: null,
384
530
  completedAt: null,
385
531
  lastAutomationAt: referenceAt,
386
- lastAutomationSummary: "后台评估助手返回了不支持的决策。"
387
- }, {
388
- kind: "failed",
389
- status: "failed",
390
- summary: "后台评估助手返回了不支持的决策。",
391
- waitingReason: "后台评估助手返回了不支持的决策。",
392
- continuePrompt: null,
393
- observedRunningState: runningState,
394
- autoContinueCount: task.autoContinueCount,
395
- createdAt: referenceAt
532
+ lastAutomationSummary: "跟进助手会话当前仍在运行,本轮继续等待下一次检查。"
396
533
  });
397
- }
398
- }
399
- catch (error) {
400
- if (isDeferredFollowUpSendError(error)) {
401
- return this.persist({
534
+ }
535
+ const detail = error instanceof Error ? error.message : String(error);
536
+ const summary = `跟进助手执行失败:${detail}`;
537
+ return this.persistWithRoundIfExecutionActive(task.id, execution, {
402
538
  ...baseUpdate,
403
- status: "active",
404
- waitingReason: null,
405
- nextCheckAt: shiftSeconds(referenceAt, task.checkIntervalSeconds),
539
+ status: "failed",
540
+ waitingReason: detail,
541
+ nextCheckAt: null,
406
542
  completedAt: null,
407
543
  lastAutomationAt: referenceAt,
408
- lastAutomationSummary: "当前会话又进入运行态,本轮不插话,等待下一次检查。"
544
+ lastAutomationSummary: summary
545
+ }, {
546
+ kind: "failed",
547
+ status: "failed",
548
+ summary,
549
+ waitingReason: detail,
550
+ continuePrompt: null,
551
+ observedRunningState: runningState,
552
+ autoContinueCount: task.autoContinueCount,
553
+ createdAt: referenceAt
409
554
  });
410
555
  }
411
- const detail = error instanceof Error ? error.message : String(error);
412
- const summary = `后台评估助手执行失败:${detail}`;
413
- return this.persistWithRound({
414
- ...baseUpdate,
415
- status: "failed",
416
- waitingReason: detail,
417
- nextCheckAt: null,
418
- completedAt: null,
419
- lastAutomationAt: referenceAt,
420
- lastAutomationSummary: summary
421
- }, {
422
- kind: "failed",
423
- status: "failed",
424
- summary,
425
- waitingReason: detail,
426
- continuePrompt: null,
427
- observedRunningState: runningState,
428
- autoContinueCount: task.autoContinueCount,
429
- createdAt: referenceAt
430
- });
556
+ }
557
+ finally {
558
+ if (this.activeExecutionStateByTaskId.get(task.id) === execution) {
559
+ this.activeExecutionStateByTaskId.delete(task.id);
560
+ }
431
561
  }
432
562
  }
433
563
  persist(task) {
@@ -440,6 +570,16 @@ export class ButlerFollowUpService {
440
570
  }
441
571
  return this.butlerFollowUpTaskRepository.update(normalizedTask) ?? normalizedTask;
442
572
  }
573
+ requireAssistantDecisionPersisted(taskId, before) {
574
+ const updated = this.butlerFollowUpTaskRepository.findById(taskId);
575
+ if (!updated) {
576
+ throw new Error("跟进任务在回写结果前已丢失");
577
+ }
578
+ if (!hasTaskProgressAdvanced(updated, before)) {
579
+ throw new Error("跟进助手没有通过 follow-ups 命令回写本轮结果");
580
+ }
581
+ return updated;
582
+ }
443
583
  persistWithRound(task, round) {
444
584
  const normalizedRounds = normalizeFollowUpRounds(task.rounds);
445
585
  return this.persist({
@@ -447,6 +587,18 @@ export class ButlerFollowUpService {
447
587
  rounds: [...normalizedRounds, createFollowUpRound(normalizedRounds, round)]
448
588
  });
449
589
  }
590
+ persistIfExecutionActive(taskId, execution, task) {
591
+ if (!this.isTaskExecutionActive(taskId, execution)) {
592
+ return this.butlerFollowUpTaskRepository.findById(taskId) ?? task;
593
+ }
594
+ return this.persist(task);
595
+ }
596
+ persistWithRoundIfExecutionActive(taskId, execution, task, round) {
597
+ if (!this.isTaskExecutionActive(taskId, execution)) {
598
+ return this.butlerFollowUpTaskRepository.findById(taskId) ?? task;
599
+ }
600
+ return this.persistWithRound(task, round);
601
+ }
450
602
  async autoApprovePendingPermissionRequestsIfDue(task, referenceAt) {
451
603
  const parsedReferenceAt = Date.parse(referenceAt);
452
604
  const referenceAtMs = Number.isFinite(parsedReferenceAt) ? parsedReferenceAt : Date.now();
@@ -497,7 +649,7 @@ export class ButlerFollowUpService {
497
649
  && normalizeNullableIso(session.lastMessageAt) === normalizeNullableIso(task.lastObservedMessageAt)
498
650
  && session.messageCount === task.lastObservedMessageCount);
499
651
  }
500
- async sendContinuePrompt(task, continuePrompt, referenceAt) {
652
+ async sendContinuePrompt(task, providerId, continuePrompt, referenceAt) {
501
653
  const clientRequestId = buildFollowUpClientRequestId(task.id, referenceAt);
502
654
  try {
503
655
  const result = await this.sessionLiveRuntimeService.sendLiveMessage({
@@ -518,6 +670,13 @@ export class ButlerFollowUpService {
518
670
  };
519
671
  }
520
672
  catch (error) {
673
+ const providerUsageLimit = resolveProviderUsageLimitFromError(error, providerId, referenceAt);
674
+ if (providerUsageLimit) {
675
+ return {
676
+ delivery: "cooldown",
677
+ providerUsageLimit
678
+ };
679
+ }
521
680
  if (!isDeferredFollowUpSendError(error)) {
522
681
  throw error;
523
682
  }
@@ -553,32 +712,36 @@ export class ButlerFollowUpService {
553
712
  });
554
713
  }
555
714
  async inspectTask(task) {
556
- const session = this.sessionHistoryService.getSession(task.sessionId, task.createdByUserId);
557
- const runtime = await this.sessionLiveRuntimeService.getSessionRuntime(task.sessionId, task.createdByUserId);
558
- const envelope = await this.sessionHistoryService.readRecentHistoryEnvelope(task.sessionId, RECENT_HISTORY_LIMIT);
715
+ return this.inspectSession(task.sessionId, task.createdByUserId);
716
+ }
717
+ async inspectSession(sessionId, userId) {
718
+ const session = this.sessionHistoryService.getSession(sessionId, userId);
719
+ const runtime = await this.sessionLiveRuntimeService.getSessionRuntime(sessionId, userId);
720
+ const envelope = await this.sessionHistoryService.readRecentHistoryEnvelope(sessionId, RECENT_HISTORY_LIMIT);
721
+ const latestAssistantText = resolveLatestAssistantText(envelope);
559
722
  const sortedMessages = (envelope?.messages ?? [])
560
723
  .slice()
561
724
  .sort((left, right) => left.sequence - right.sequence);
725
+ const providerUsageLimit = resolveInspectionProviderUsageLimit(session.provider, session.lastErrorDetail, latestAssistantText, session.lastMessageAt);
562
726
  return {
727
+ providerId: session.provider,
563
728
  runningState: normalizeRunningState(runtime.runningState),
564
729
  messageAt: session.lastMessageAt,
565
730
  messageCount: session.messageCount,
566
731
  sessionTitle: session.title ?? null,
567
- latestAssistantText: resolveLatestAssistantText(envelope),
732
+ providerUsageLimit,
733
+ latestAssistantText,
568
734
  transcriptLines: sortedMessages.map((message) => renderHistoryLine(message.sequence, message.role, message.kind ?? "text", message.timestamp, message.content))
569
735
  };
570
736
  }
571
- async evaluateTask(profile, project, task, inspection, runningState) {
572
- const evaluatorWorkspacePath = path.join(profile.workspacePath, FOLLOW_UP_EVALUATOR_DIRNAME);
573
- ensureButlerWorkspaceIsolation(evaluatorWorkspacePath);
574
- this.writeEvaluationInstructionFiles(evaluatorWorkspacePath, profile.providerId);
575
- this.syncCodexInstructionConfig(profile.providerId, evaluatorWorkspacePath);
576
- const workspace = this.workspaceService.importWorkspace(evaluatorWorkspacePath, "代码助手");
737
+ async requestAssistantEvaluation(project, task, inspection, runningState, execution) {
577
738
  const instruction = this.instructionAdapter.buildInstruction({
578
- providerId: profile.providerId,
739
+ taskId: task.id,
740
+ providerId: task.providerId,
579
741
  project,
580
742
  sessionId: task.sessionId,
581
743
  butlerSessionId: task.butlerSessionId,
744
+ assistantSessionId: task.assistantSessionId,
582
745
  sessionTitle: inspection.sessionTitle,
583
746
  objective: task.objective,
584
747
  completionCriteria: task.completionCriteria,
@@ -591,56 +754,82 @@ export class ButlerFollowUpService {
591
754
  latestAssistantText: inspection.latestAssistantText,
592
755
  transcriptLines: inspection.transcriptLines
593
756
  });
594
- const adapter = this.providerAdapterRegistry.get(profile.providerId);
595
- const launch = await adapter.startPatrolSession({
596
- workspaceId: workspace.id,
597
- userId: task.createdByUserId,
598
- providerId: profile.providerId,
599
- prompt: instruction.prompt,
600
- model: resolveFollowUpModel(profile.providerId, this.sourceCodexHomeDir),
601
- reasoningLevel: "low",
602
- permissionMode: "default"
603
- });
604
- await adapter.waitForSessionTerminal(launch.sessionId);
605
- const result = await adapter.readPatrolResult(launch.sessionId);
606
- return parseEvaluationResult(result);
607
- }
608
- writeEvaluationInstructionFiles(workspacePath, providerId) {
609
- const content = [
610
- "# 代码助手后台跟进评估规则",
611
- "",
612
- "你不是普通项目会话,也不是面向用户的聊天助手。",
613
- "你的身份是后台跟进评估器,只负责判断某个开发会话现在该继续推进、等用户决定、还是已经完成。",
614
- "如果目标或上下文里提到了 spec,完成标准只能按 spec 明确要求的必做项判断。",
615
- "“建议下一步”“最佳实践”“可以顺手优化”这类内容默认都不是必做项,不能据此继续扩范围。",
616
- "如果没有 spec,就先从目标和最近消息里归纳一句当前核心任务,后续只能围绕这个核心任务判断,不准无限扩展。",
617
- "除非目标本身要求,否则不要把重构、补测试、补体验优化之类建议项升级成必须开发的工作。",
618
- "禁止照搬最后一句回复做草率判断,必须结合用户目标、当前运行态和最近消息一起判断。",
619
- "如果能继续推进,就直接给出下一条要发给开发会话的中文指令,不要空谈。",
620
- "如果确实需要用户决定,要把缺口说清楚,但不要替用户做不存在的决定。",
621
- "输出语言必须是中文,先给结论,再给结构化 JSON。"
622
- ].join("\n");
623
- writeFileIfChanged(path.join(workspacePath, "AGENTS.md"), `${content}\n`);
624
- if (providerId === "claude-code") {
625
- writeFileIfChanged(path.join(workspacePath, "CLAUDE.md"), `${content}\n`);
626
- }
627
- }
628
- syncCodexInstructionConfig(providerId, workspacePath) {
629
- if (providerId !== "codex" || !this.followUpCodexHomeDir?.trim()) {
757
+ execution.assistantSessionId = task.assistantSessionId;
758
+ try {
759
+ await this.waitForAssistantSessionTerminal(task.assistantSessionId, task.createdByUserId);
760
+ this.ensureTaskExecutionActive(task.id, execution);
761
+ await this.sessionLiveRuntimeService.sendLiveMessage({
762
+ sessionId: task.assistantSessionId,
763
+ userId: task.createdByUserId,
764
+ content: instruction.prompt,
765
+ clientRequestId: null,
766
+ runtimeOptions: {
767
+ model: resolveFollowUpModel(task.providerId, this.sourceCodexHomeDir),
768
+ reasoningLevel: "low",
769
+ permissionMode: "default",
770
+ attachments: []
771
+ }
772
+ });
773
+ await this.waitForAssistantSessionTerminal(task.assistantSessionId, task.createdByUserId);
774
+ this.ensureTaskExecutionActive(task.id, execution);
775
+ }
776
+ finally {
777
+ if (execution.assistantSessionId === task.assistantSessionId) {
778
+ execution.assistantSessionId = null;
779
+ }
780
+ }
781
+ }
782
+ beginTaskExecution(taskId) {
783
+ const execution = {
784
+ cancelled: false,
785
+ assistantSessionId: null
786
+ };
787
+ this.activeExecutionStateByTaskId.set(taskId, execution);
788
+ return execution;
789
+ }
790
+ markTaskExecutionCancelled(taskId) {
791
+ const execution = this.activeExecutionStateByTaskId.get(taskId) ?? null;
792
+ if (execution) {
793
+ execution.cancelled = true;
794
+ }
795
+ return execution;
796
+ }
797
+ ensureTaskExecutionActive(taskId, execution) {
798
+ if (!this.isTaskExecutionActive(taskId, execution)) {
799
+ throw new FollowUpTaskCancelledError();
800
+ }
801
+ }
802
+ isTaskExecutionActive(taskId, execution) {
803
+ const current = this.activeExecutionStateByTaskId.get(taskId);
804
+ return Boolean(current && current === execution && !execution.cancelled);
805
+ }
806
+ async stopActiveTaskAutomation(task, execution) {
807
+ if (!execution?.assistantSessionId) {
630
808
  return;
631
809
  }
632
- const targetHomeDir = path.resolve(this.followUpCodexHomeDir);
633
- const sourceHomeDir = resolveSourceCodexHomeDir(this.sourceCodexHomeDir, targetHomeDir);
634
- const sourceConfigPath = path.join(sourceHomeDir, "config.toml");
635
- const sourceConfigContent = sourceHomeDir !== targetHomeDir && fs.existsSync(sourceConfigPath) && fs.statSync(sourceConfigPath).isFile()
636
- ? fs.readFileSync(sourceConfigPath, "utf8")
637
- : "";
638
- const instructionFilePath = path.join(workspacePath, "AGENTS.md");
639
- fs.mkdirSync(targetHomeDir, { recursive: true });
640
- removeFileIfExists(path.join(targetHomeDir, "AGENTS.md"));
641
- removeFileIfExists(path.join(targetHomeDir, "AGENTS.override.md"));
642
- syncOptionalFile(path.join(sourceHomeDir, "auth.json"), path.join(targetHomeDir, "auth.json"));
643
- writeFileIfChanged(path.join(targetHomeDir, "config.toml"), `${composeCodexConfigContent(sourceConfigContent, instructionFilePath)}\n`);
810
+ try {
811
+ await this.sessionLiveRuntimeService.interruptSession(execution.assistantSessionId, task.createdByUserId);
812
+ }
813
+ catch (error) {
814
+ console.warn("[butler-follow-up] interrupt assistant follow-up session failed", {
815
+ sessionId: execution.assistantSessionId,
816
+ error: error instanceof Error ? error.message : String(error)
817
+ });
818
+ }
819
+ finally {
820
+ execution.assistantSessionId = null;
821
+ }
822
+ }
823
+ async waitForAssistantSessionTerminal(sessionId, userId) {
824
+ const startedAt = Date.now();
825
+ while (Date.now() - startedAt < FOLLOW_UP_ASSISTANT_WAIT_TIMEOUT_MS) {
826
+ const runtime = await this.sessionLiveRuntimeService.getSessionRuntime(sessionId, userId);
827
+ if (isAssistantTerminalRuntimeState(runtime.runningState)) {
828
+ return;
829
+ }
830
+ await delay(FOLLOW_UP_ASSISTANT_WAIT_POLL_INTERVAL_MS);
831
+ }
832
+ throw new Error(`BUTLER_FOLLOW_UP_ASSISTANT_WAIT_TIMEOUT:${sessionId}`);
644
833
  }
645
834
  }
646
835
  function mapTaskView(task, workspaceId, projectName, sessionTitle) {
@@ -652,6 +841,9 @@ function mapTaskView(task, workspaceId, projectName, sessionTitle) {
652
841
  workspaceId,
653
842
  butlerSessionId: task.butlerSessionId,
654
843
  sessionId: task.sessionId,
844
+ providerId: task.providerId,
845
+ assistantButlerSessionId: task.assistantButlerSessionId,
846
+ assistantSessionId: task.assistantSessionId,
655
847
  sessionTitle,
656
848
  objective: task.objective,
657
849
  completionCriteria: task.completionCriteria,
@@ -729,6 +921,24 @@ function normalizeObjective(value) {
729
921
  }
730
922
  return normalized;
731
923
  }
924
+ function normalizeFollowUpProviderId(value) {
925
+ switch (value) {
926
+ case undefined:
927
+ case null:
928
+ case "":
929
+ return "codex";
930
+ case "codex":
931
+ case "claude-code":
932
+ return value;
933
+ default:
934
+ throw new AppError({
935
+ statusCode: 400,
936
+ errorCode: "INVALID_INPUT",
937
+ detail: "会话跟进只允许选择 Codex 或 Claude Code",
938
+ field: "providerId"
939
+ });
940
+ }
941
+ }
732
942
  function normalizeCompletionCriteria(value, objective) {
733
943
  const normalized = value?.trim();
734
944
  return normalized && normalized.length > 0
@@ -754,6 +964,16 @@ function buildFollowUpClientRequestId(taskId, referenceAt) {
754
964
  function buildQueuedFollowUpSummary(summary, queueItem) {
755
965
  return `${summary} 已转入消息队列,等待当前会话空闲后自动补发(队列项 ${queueItem.orderIndex})。`;
756
966
  }
967
+ function buildFollowUpUsageLimitSummary(providerUsageLimit, prefix) {
968
+ return `${prefix} ${providerUsageLimit.summary}`;
969
+ }
970
+ function resolveProviderUsageLimitNextCheckAt(providerUsageLimit, referenceAt, fallbackSeconds) {
971
+ const blockedUntil = resolveProviderUsageLimitBlockedUntil(providerUsageLimit, referenceAt);
972
+ if (blockedUntil && Date.parse(blockedUntil) > Date.parse(referenceAt)) {
973
+ return blockedUntil;
974
+ }
975
+ return shiftSeconds(referenceAt, fallbackSeconds);
976
+ }
757
977
  function isDeferredFollowUpSendError(error) {
758
978
  if (error instanceof AppError) {
759
979
  return (error.errorCode === "ACTIVE_RUN_EXISTS"
@@ -775,6 +995,14 @@ function isDeferredFollowUpSendError(error) {
775
995
  || error.message === "SERVER_TIMEOUT"
776
996
  || error.message.includes("当前会话正在运行"));
777
997
  }
998
+ function isAssistantTerminalRuntimeState(state) {
999
+ return state === "idle" || state === "completed" || state === "failed" || state === "interrupted";
1000
+ }
1001
+ function delay(timeoutMs) {
1002
+ return new Promise((resolve) => {
1003
+ setTimeout(resolve, timeoutMs);
1004
+ });
1005
+ }
778
1006
  function isSyntheticMessageId(messageId) {
779
1007
  return typeof messageId === "string" && messageId.startsWith("synthetic-");
780
1008
  }
@@ -802,6 +1030,24 @@ function normalizeNullableIso(value) {
802
1030
  const normalized = value?.trim();
803
1031
  return normalized && normalized.length > 0 ? normalized : null;
804
1032
  }
1033
+ function resolveInspectionProviderUsageLimit(providerId, lastErrorDetail, latestAssistantText, referenceAt) {
1034
+ const normalizedReferenceAt = normalizeNullableIso(referenceAt) ?? undefined;
1035
+ const fromErrorDetail = normalizeProviderUsageLimit({
1036
+ providerId,
1037
+ text: lastErrorDetail,
1038
+ referenceAt: normalizedReferenceAt,
1039
+ source: "error_detail"
1040
+ });
1041
+ if (fromErrorDetail) {
1042
+ return fromErrorDetail;
1043
+ }
1044
+ return normalizeProviderUsageLimit({
1045
+ providerId,
1046
+ text: latestAssistantText,
1047
+ referenceAt: normalizedReferenceAt,
1048
+ source: "message"
1049
+ });
1050
+ }
805
1051
  function resolveLatestAssistantText(envelope) {
806
1052
  if (!envelope || envelope.messages.length === 0) {
807
1053
  return null;
@@ -823,140 +1069,91 @@ function truncateText(value, maxLength) {
823
1069
  }
824
1070
  return `${value.slice(0, Math.max(0, maxLength - 1))}…`;
825
1071
  }
1072
+ function buildFollowUpBootstrapPrompt(input) {
1073
+ const transcript = input.transcriptLines.length > 0
1074
+ ? input.transcriptLines.slice(-12).join("\n")
1075
+ : "- 暂时没有可用消息,请先按会话现状建立上下文。";
1076
+ return [
1077
+ "你现在是这条开发会话的专用跟进助手,会长期复用当前助手会话推进,不再切回隐藏评估器。",
1078
+ "你的职责只有三件事:",
1079
+ "1. 用 codingns assistant CLI 复核目标项目和目标会话的最新状态。",
1080
+ "2. 判断当前是否真的还需要继续跟进,还是应该等待用户决定,或者已经可以结束。",
1081
+ "3. 用 codingns assistant sessions send 和 codingns assistant follow-ups.* 自己完成发消息与任务回写,不要等后台代发或猜结果。",
1082
+ "",
1083
+ "硬约束:",
1084
+ "- 不要直接改当前仓库代码,这条会话只负责跟进判断和向目标开发会话发消息。",
1085
+ "- 如果决定继续,必须显式使用 `codingns assistant sessions send` 把中文跟进消息发到目标开发会话。",
1086
+ "- 每一轮正式结论都必须用 `codingns assistant follow-ups continue|waiting-user|complete|fail` 之一回写到跟进任务。",
1087
+ "- 如果信息不足或需要用户决策,要明确说明缺口,不要假装已经发消息。",
1088
+ "- 跟进边界只围绕当前目标和结束条件,不准顺手扩范围。",
1089
+ "",
1090
+ `项目名称:${input.project.name}`,
1091
+ `项目路径:${input.project.repoRoot}`,
1092
+ `目标 Butler 会话 ID:${input.sourceButlerSessionId}`,
1093
+ `目标真实会话 ID:${input.sourceSessionId}`,
1094
+ `目标会话标题:${input.sourceSessionTitle ?? "未命名会话"}`,
1095
+ `跟进目标:${input.objective}`,
1096
+ `结束条件:${input.completionCriteria}`,
1097
+ `最多自动推进轮数:${input.maxAutoContinueCount}`,
1098
+ `最近一条助手消息:${input.latestAssistantText?.trim() || "无"}`,
1099
+ "",
1100
+ "最近消息摘录:",
1101
+ transcript,
1102
+ "",
1103
+ "这条消息只用来建立上下文。请先整理当前理解,后续我会继续给你发送正式的跟进检查请求。"
1104
+ ].join("\n");
1105
+ }
826
1106
  function resolveFollowUpModel(providerId, sourceCodexHomeDir) {
827
1107
  if (providerId !== "codex") {
828
1108
  return "haiku";
829
1109
  }
830
1110
  return resolveButlerCodexBackgroundModel("gpt-5.1-codex-mini", sourceCodexHomeDir);
831
1111
  }
832
- function parseEvaluationResult(result) {
833
- const rawJson = result.structured.rawJson ?? extractJsonFromText(result.latestAssistantMessage);
834
- if (!rawJson) {
835
- throw new Error("后台评估助手没有返回结构化 JSON");
836
- }
837
- let parsed;
838
- try {
839
- parsed = JSON.parse(rawJson);
840
- }
841
- catch (error) {
842
- throw new Error(`后台评估助手返回的 JSON 无法解析:${error instanceof Error ? error.message : String(error)}`);
843
- }
844
- const decision = normalizeDecision(parsed.decision);
845
- if (!decision) {
846
- throw new Error("后台评估助手返回的 decision 不合法");
847
- }
848
- const summary = normalizeNonEmptyString(parsed.summary) ?? result.structured.summary ?? "后台评估助手未提供摘要";
849
- const waitingReason = normalizeNullableString(parsed.waitingReason);
850
- const continuePrompt = normalizeNullableString(parsed.continuePrompt);
851
- const riskLevel = normalizeRiskLevel(parsed.riskLevel);
1112
+ function snapshotTaskProgress(task) {
852
1113
  return {
853
- decision,
854
- summary,
855
- waitingReason,
856
- continuePrompt,
857
- riskLevel
1114
+ roundCount: normalizeFollowUpRounds(task.rounds).length,
1115
+ updatedAt: task.updatedAt,
1116
+ lastAutomationAt: task.lastAutomationAt,
1117
+ autoContinueCount: task.autoContinueCount
858
1118
  };
859
1119
  }
860
- function normalizeDecision(value) {
861
- switch (value) {
862
- case "continue":
863
- case "waiting_user":
864
- case "completed":
865
- case "failed":
866
- return value;
867
- default:
868
- return null;
869
- }
1120
+ function hasTaskProgressAdvanced(task, before) {
1121
+ const roundCount = normalizeFollowUpRounds(task.rounds).length;
1122
+ return (roundCount > before.roundCount
1123
+ || task.updatedAt !== before.updatedAt
1124
+ || task.lastAutomationAt !== before.lastAutomationAt
1125
+ || task.autoContinueCount !== before.autoContinueCount);
870
1126
  }
871
- function normalizeRiskLevel(value) {
872
- switch (value) {
873
- case "low":
874
- case "medium":
875
- case "high":
876
- return value;
877
- default:
878
- return null;
879
- }
880
- }
881
- function normalizeNonEmptyString(value) {
1127
+ function requireNonEmptyFollowUpText(value, field, detail) {
882
1128
  if (typeof value !== "string") {
883
- return null;
1129
+ throw new AppError({
1130
+ statusCode: 400,
1131
+ errorCode: "BUTLER_FOLLOW_UP_TASK_INVALID_INPUT",
1132
+ detail
1133
+ });
884
1134
  }
885
1135
  const normalized = value.trim();
886
- return normalized || null;
887
- }
888
- function normalizeNullableString(value) {
889
- if (value === null || value === undefined) {
890
- return null;
891
- }
892
- return normalizeNonEmptyString(value);
893
- }
894
- function extractJsonFromText(value) {
895
- if (!value) {
896
- return null;
897
- }
898
- const matched = value.match(/```json\s*([\s\S]*?)```/i);
899
- const raw = matched?.[1]?.trim();
900
- return raw || null;
901
- }
902
- function resolveSourceCodexHomeDir(sourceCodexHomeDir, targetHomeDir) {
903
- const configuredSource = sourceCodexHomeDir?.trim();
904
- if (configuredSource) {
905
- const resolvedConfiguredSource = path.resolve(configuredSource);
906
- if (resolvedConfiguredSource !== targetHomeDir) {
907
- return resolvedConfiguredSource;
908
- }
909
- }
910
- const fallbackHomeDir = path.resolve(path.join(os.homedir(), ".codex"));
911
- if (fallbackHomeDir !== targetHomeDir) {
912
- return fallbackHomeDir;
913
- }
914
- return targetHomeDir;
915
- }
916
- function composeCodexConfigContent(sourceConfigContent, instructionFilePath) {
917
- const normalizedSource = sourceConfigContent
918
- .split(/\r?\n/)
919
- .filter((line) => {
920
- const trimmed = line.trim();
921
- return trimmed.length > 0 && !trimmed.startsWith("model_instructions_file");
922
- })
923
- .join("\n")
924
- .trim();
925
- return [
926
- "# 代码助手跟进评估专用 Codex 配置(系统自动生成)",
927
- normalizedSource,
928
- `model_instructions_file = ${toTomlString(path.resolve(instructionFilePath))}`
929
- ]
930
- .filter((part) => part.trim().length > 0)
931
- .join("\n\n");
932
- }
933
- function toTomlString(value) {
934
- return `"${value.replaceAll("\\", "\\\\").replaceAll("\"", "\\\"")}"`;
935
- }
936
- function writeFileIfChanged(filePath, content) {
937
- fs.mkdirSync(path.dirname(filePath), { recursive: true });
938
- if (fs.existsSync(filePath) && fs.readFileSync(filePath, "utf8") === content) {
939
- return;
940
- }
941
- fs.writeFileSync(filePath, content, "utf8");
942
- }
943
- function removeFileIfExists(filePath) {
944
- if (!fs.existsSync(filePath)) {
945
- return;
1136
+ if (!normalized) {
1137
+ throw new AppError({
1138
+ statusCode: 400,
1139
+ errorCode: "BUTLER_FOLLOW_UP_TASK_INVALID_INPUT",
1140
+ detail
1141
+ });
946
1142
  }
947
- if (fs.statSync(filePath).isFile()) {
948
- fs.rmSync(filePath, { force: true });
1143
+ if (normalized.length > 4000) {
1144
+ throw new AppError({
1145
+ statusCode: 400,
1146
+ errorCode: "BUTLER_FOLLOW_UP_TASK_INVALID_INPUT",
1147
+ detail: `${field} 长度不能超过 4000 个字符`
1148
+ });
949
1149
  }
1150
+ return normalized;
950
1151
  }
951
- function syncOptionalFile(sourcePath, targetPath) {
952
- if (!fs.existsSync(sourcePath) || !fs.statSync(sourcePath).isFile()) {
953
- removeFileIfExists(targetPath);
954
- return;
955
- }
956
- fs.mkdirSync(path.dirname(targetPath), { recursive: true });
957
- if (fs.existsSync(targetPath) && fs.readFileSync(targetPath).equals(fs.readFileSync(sourcePath))) {
958
- return;
1152
+ function normalizeNullableText(value) {
1153
+ if (typeof value !== "string") {
1154
+ return null;
959
1155
  }
960
- fs.copyFileSync(sourcePath, targetPath);
1156
+ const normalized = value.trim();
1157
+ return normalized || null;
961
1158
  }
962
1159
  //# sourceMappingURL=butler-follow-up-service.js.map