@jingyi0605/codingns 0.3.5 → 0.4.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 (255) hide show
  1. package/README.md +16 -0
  2. package/bin/codingns.mjs +1369 -10
  3. package/dist/public/assets/{TerminalPage-CgrfstRm.js → TerminalPage-6jHZV9Mh.js} +17 -17
  4. package/dist/public/assets/index-CSVhg7I8.js +123 -0
  5. package/dist/public/assets/index-Ce1VX19m.css +1 -0
  6. package/dist/public/index.html +2 -2
  7. package/dist/server/config/opencode-base-url-resolver.d.ts +7 -0
  8. package/dist/server/config/opencode-base-url-resolver.js +48 -11
  9. package/dist/server/config/opencode-base-url-resolver.js.map +1 -1
  10. package/dist/server/config/opencode-system-probe-helper-client.d.ts +4 -0
  11. package/dist/server/config/opencode-system-probe-helper-client.js +29 -0
  12. package/dist/server/config/opencode-system-probe-helper-client.js.map +1 -1
  13. package/dist/server/config/opencode-system-probe-helper-process.d.ts +12 -0
  14. package/dist/server/config/opencode-system-probe-helper-process.js +34 -7
  15. package/dist/server/config/opencode-system-probe-helper-process.js.map +1 -1
  16. package/dist/server/modules/assistant-capability/assistant-capability-controller.d.ts +317 -0
  17. package/dist/server/modules/assistant-capability/assistant-capability-controller.js +549 -1
  18. package/dist/server/modules/assistant-capability/assistant-capability-controller.js.map +1 -1
  19. package/dist/server/modules/assistant-capability/assistant-capability-service.d.ts +330 -2
  20. package/dist/server/modules/assistant-capability/assistant-capability-service.js +958 -3
  21. package/dist/server/modules/assistant-capability/assistant-capability-service.js.map +1 -1
  22. package/dist/server/modules/butler/assistant-automation-service.d.ts +110 -0
  23. package/dist/server/modules/butler/assistant-automation-service.js +786 -0
  24. package/dist/server/modules/butler/assistant-automation-service.js.map +1 -0
  25. package/dist/server/modules/butler/assistant-automation-trigger.d.ts +94 -0
  26. package/dist/server/modules/butler/assistant-automation-trigger.js +400 -0
  27. package/dist/server/modules/butler/assistant-automation-trigger.js.map +1 -0
  28. package/dist/server/modules/butler/assistant-sandbox-service.d.ts +55 -0
  29. package/dist/server/modules/butler/assistant-sandbox-service.js +266 -0
  30. package/dist/server/modules/butler/assistant-sandbox-service.js.map +1 -0
  31. package/dist/server/modules/butler/butler-action-context-service.d.ts +4 -1
  32. package/dist/server/modules/butler/butler-action-context-service.js +8 -2
  33. package/dist/server/modules/butler/butler-action-context-service.js.map +1 -1
  34. package/dist/server/modules/butler/butler-control-session-service.d.ts +8 -1
  35. package/dist/server/modules/butler/butler-control-session-service.js +154 -40
  36. package/dist/server/modules/butler/butler-control-session-service.js.map +1 -1
  37. package/dist/server/modules/butler/butler-control-timer-scheduler.d.ts +32 -0
  38. package/dist/server/modules/butler/butler-control-timer-scheduler.js +93 -0
  39. package/dist/server/modules/butler/butler-control-timer-scheduler.js.map +1 -0
  40. package/dist/server/modules/butler/butler-control-timer-service.d.ts +42 -0
  41. package/dist/server/modules/butler/butler-control-timer-service.js +132 -0
  42. package/dist/server/modules/butler/butler-control-timer-service.js.map +1 -0
  43. package/dist/server/modules/butler/butler-controller.d.ts +42 -2
  44. package/dist/server/modules/butler/butler-controller.js +79 -12
  45. package/dist/server/modules/butler/butler-controller.js.map +1 -1
  46. package/dist/server/modules/butler/butler-follow-up-service.d.ts +9 -1
  47. package/dist/server/modules/butler/butler-follow-up-service.js +273 -181
  48. package/dist/server/modules/butler/butler-follow-up-service.js.map +1 -1
  49. package/dist/server/modules/butler/butler-inbox-analysis-service.d.ts +4 -1
  50. package/dist/server/modules/butler/butler-inbox-analysis-service.js +18 -4
  51. package/dist/server/modules/butler/butler-inbox-analysis-service.js.map +1 -1
  52. package/dist/server/modules/butler/butler-inbox-instruction-adapter.js +7 -6
  53. package/dist/server/modules/butler/butler-inbox-instruction-adapter.js.map +1 -1
  54. package/dist/server/modules/butler/butler-profile-service.js +2 -5
  55. package/dist/server/modules/butler/butler-profile-service.js.map +1 -1
  56. package/dist/server/modules/butler/butler-project-service.d.ts +3 -1
  57. package/dist/server/modules/butler/butler-project-service.js +7 -1
  58. package/dist/server/modules/butler/butler-project-service.js.map +1 -1
  59. package/dist/server/modules/butler/butler-session-service.d.ts +3 -1
  60. package/dist/server/modules/butler/butler-session-service.js +12 -1
  61. package/dist/server/modules/butler/butler-session-service.js.map +1 -1
  62. package/dist/server/modules/butler/butler-session-summary-service.js +2 -1
  63. package/dist/server/modules/butler/butler-session-summary-service.js.map +1 -1
  64. package/dist/server/modules/butler/butler-workspace-context.d.ts +3 -0
  65. package/dist/server/modules/butler/butler-workspace-context.js +182 -51
  66. package/dist/server/modules/butler/butler-workspace-context.js.map +1 -1
  67. package/dist/server/modules/butler/patrol-execution-service.js +2 -1
  68. package/dist/server/modules/butler/patrol-execution-service.js.map +1 -1
  69. package/dist/server/modules/butler/provider-adapter-registry.d.ts +3 -0
  70. package/dist/server/modules/butler/provider-adapter-registry.js +18 -1
  71. package/dist/server/modules/butler/provider-adapter-registry.js.map +1 -1
  72. package/dist/server/modules/butler/verification-run-service.d.ts +9 -2
  73. package/dist/server/modules/butler/verification-run-service.js +188 -34
  74. package/dist/server/modules/butler/verification-run-service.js.map +1 -1
  75. package/dist/server/modules/debug-target/debug-target-controller.d.ts +13 -0
  76. package/dist/server/modules/debug-target/debug-target-controller.js +77 -2
  77. package/dist/server/modules/debug-target/debug-target-controller.js.map +1 -1
  78. package/dist/server/modules/debug-target/debug-target-service.d.ts +17 -3
  79. package/dist/server/modules/debug-target/debug-target-service.js +696 -98
  80. package/dist/server/modules/debug-target/debug-target-service.js.map +1 -1
  81. package/dist/server/modules/git/git-command-helper-client.d.ts +3 -0
  82. package/dist/server/modules/git/git-command-helper-client.js +71 -29
  83. package/dist/server/modules/git/git-command-helper-client.js.map +1 -1
  84. package/dist/server/modules/git/git-command-helper-process.js +62 -9
  85. package/dist/server/modules/git/git-command-helper-process.js.map +1 -1
  86. package/dist/server/modules/git/git-command-runner.d.ts +1 -0
  87. package/dist/server/modules/git/git-command-runner.js +44 -1
  88. package/dist/server/modules/git/git-command-runner.js.map +1 -1
  89. package/dist/server/modules/git/git-controller.js +8 -7
  90. package/dist/server/modules/git/git-controller.js.map +1 -1
  91. package/dist/server/modules/git/git-read-service.d.ts +7 -7
  92. package/dist/server/modules/git/git-read-service.js +41 -24
  93. package/dist/server/modules/git/git-read-service.js.map +1 -1
  94. package/dist/server/modules/model-switch/cc-switch-adapter.js +6 -2
  95. package/dist/server/modules/model-switch/cc-switch-adapter.js.map +1 -1
  96. package/dist/server/modules/preferences/profile-service.d.ts +3 -1
  97. package/dist/server/modules/preferences/profile-service.js +74 -3
  98. package/dist/server/modules/preferences/profile-service.js.map +1 -1
  99. package/dist/server/modules/provider/codex-model-options.js +2 -3
  100. package/dist/server/modules/provider/codex-model-options.js.map +1 -1
  101. package/dist/server/modules/provider/opencode-model-options.js +2 -3
  102. package/dist/server/modules/provider/opencode-model-options.js.map +1 -1
  103. package/dist/server/modules/provider/provider-discovery-helper-client.d.ts +14 -7
  104. package/dist/server/modules/provider/provider-discovery-helper-client.js +208 -46
  105. package/dist/server/modules/provider/provider-discovery-helper-client.js.map +1 -1
  106. package/dist/server/modules/provider/provider-discovery-helper-process.js +96 -47
  107. package/dist/server/modules/provider/provider-discovery-helper-process.js.map +1 -1
  108. package/dist/server/modules/provider/provider-discovery-runtime.d.ts +4 -0
  109. package/dist/server/modules/provider/provider-discovery-runtime.js +145 -0
  110. package/dist/server/modules/provider/provider-discovery-runtime.js.map +1 -0
  111. package/dist/server/modules/sessions/claude-runtime-helper-client.js +23 -1
  112. package/dist/server/modules/sessions/claude-runtime-helper-client.js.map +1 -1
  113. package/dist/server/modules/sessions/session-history-service.d.ts +12 -3
  114. package/dist/server/modules/sessions/session-history-service.js +465 -67
  115. package/dist/server/modules/sessions/session-history-service.js.map +1 -1
  116. package/dist/server/modules/sessions/session-live-runtime-service.d.ts +8 -0
  117. package/dist/server/modules/sessions/session-live-runtime-service.js +164 -34
  118. package/dist/server/modules/sessions/session-live-runtime-service.js.map +1 -1
  119. package/dist/server/modules/sessions/session-message-origin-utils.d.ts +12 -0
  120. package/dist/server/modules/sessions/session-message-origin-utils.js +45 -0
  121. package/dist/server/modules/sessions/session-message-origin-utils.js.map +1 -0
  122. package/dist/server/modules/sessions/session-permission-request-service.js +167 -0
  123. package/dist/server/modules/sessions/session-permission-request-service.js.map +1 -1
  124. package/dist/server/modules/skills/builtin-skill-service.d.ts +12 -0
  125. package/dist/server/modules/skills/builtin-skill-service.js +49 -0
  126. package/dist/server/modules/skills/builtin-skill-service.js.map +1 -0
  127. package/dist/server/modules/skills/builtin-skills/codingns-assistant/SKILL.md +82 -0
  128. package/dist/server/modules/skills/builtin-skills/codingns-assistant/agents/openai.yaml +4 -0
  129. package/dist/server/modules/skills/builtin-skills/codingns-assistant/references/cli-workflow.md +136 -0
  130. package/dist/server/modules/skills/skill-manager-service.d.ts +7 -0
  131. package/dist/server/modules/skills/skill-manager-service.js +98 -0
  132. package/dist/server/modules/skills/skill-manager-service.js.map +1 -1
  133. package/dist/server/modules/tailscale/tailscale-helper-client.d.ts +1 -0
  134. package/dist/server/modules/tailscale/tailscale-helper-client.js +12 -0
  135. package/dist/server/modules/tailscale/tailscale-helper-client.js.map +1 -1
  136. package/dist/server/modules/tasks/task-helper-client.d.ts +10 -2
  137. package/dist/server/modules/tasks/task-helper-client.js +152 -27
  138. package/dist/server/modules/tasks/task-helper-client.js.map +1 -1
  139. package/dist/server/modules/tasks/task-helper-process-handlers.d.ts +10 -3
  140. package/dist/server/modules/tasks/task-helper-process-handlers.js +7 -5
  141. package/dist/server/modules/tasks/task-helper-process-handlers.js.map +1 -1
  142. package/dist/server/modules/tasks/task-helper-process.js +104 -3
  143. package/dist/server/modules/tasks/task-helper-process.js.map +1 -1
  144. package/dist/server/modules/tasks/task-lane-executors.js +2 -2
  145. package/dist/server/modules/tasks/task-lane-executors.js.map +1 -1
  146. package/dist/server/modules/tasks/task-types.d.ts +4 -0
  147. package/dist/server/modules/tasks/task-types.js +5 -1
  148. package/dist/server/modules/tasks/task-types.js.map +1 -1
  149. package/dist/server/modules/terminal/command-template-service.d.ts +11 -2
  150. package/dist/server/modules/terminal/command-template-service.js +91 -9
  151. package/dist/server/modules/terminal/command-template-service.js.map +1 -1
  152. package/dist/server/modules/terminal/runtime/terminal-log-writer-client.js +1 -1
  153. package/dist/server/modules/terminal/runtime/terminal-log-writer-client.js.map +1 -1
  154. package/dist/server/modules/terminal/runtime/terminal-log-writer-process.js +160 -11
  155. package/dist/server/modules/terminal/runtime/terminal-log-writer-process.js.map +1 -1
  156. package/dist/server/modules/terminal/template-port-runtime.d.ts +1 -1
  157. package/dist/server/modules/terminal/template-port-runtime.js +87 -37
  158. package/dist/server/modules/terminal/template-port-runtime.js.map +1 -1
  159. package/dist/server/modules/terminal/terminal-controller.d.ts +3 -0
  160. package/dist/server/modules/terminal/terminal-controller.js +41 -0
  161. package/dist/server/modules/terminal/terminal-controller.js.map +1 -1
  162. package/dist/server/modules/terminal/terminal-service.d.ts +4 -0
  163. package/dist/server/modules/terminal/terminal-service.js +35 -1
  164. package/dist/server/modules/terminal/terminal-service.js.map +1 -1
  165. package/dist/server/modules/workbench/workbench-service.d.ts +3 -0
  166. package/dist/server/modules/workbench/workbench-service.js +7 -6
  167. package/dist/server/modules/workbench/workbench-service.js.map +1 -1
  168. package/dist/server/modules/workbench/workspace-file-watcher.d.ts +14 -6
  169. package/dist/server/modules/workbench/workspace-file-watcher.js +267 -57
  170. package/dist/server/modules/workbench/workspace-file-watcher.js.map +1 -1
  171. package/dist/server/modules/workbench/workspace-panel-snapshot-service.d.ts +3 -0
  172. package/dist/server/modules/workbench/workspace-panel-snapshot-service.js +149 -41
  173. package/dist/server/modules/workbench/workspace-panel-snapshot-service.js.map +1 -1
  174. package/dist/server/modules/workspace/workspace-code-composition.d.ts +1 -0
  175. package/dist/server/modules/workspace/workspace-code-composition.js +183 -1
  176. package/dist/server/modules/workspace/workspace-code-composition.js.map +1 -1
  177. package/dist/server/modules/workspace/workspace-service.js +54 -17
  178. package/dist/server/modules/workspace/workspace-service.js.map +1 -1
  179. package/dist/server/modules/worktree/worktree-cleanup-service.d.ts +1 -1
  180. package/dist/server/modules/worktree/worktree-cleanup-service.js +22 -17
  181. package/dist/server/modules/worktree/worktree-cleanup-service.js.map +1 -1
  182. package/dist/server/modules/worktree/worktree-controller.js +6 -5
  183. package/dist/server/modules/worktree/worktree-controller.js.map +1 -1
  184. package/dist/server/modules/worktree/worktree-manager.d.ts +10 -2
  185. package/dist/server/modules/worktree/worktree-manager.js +35 -20
  186. package/dist/server/modules/worktree/worktree-manager.js.map +1 -1
  187. package/dist/server/modules/worktree/worktree-merge-service.d.ts +2 -2
  188. package/dist/server/modules/worktree/worktree-merge-service.js +34 -27
  189. package/dist/server/modules/worktree/worktree-merge-service.js.map +1 -1
  190. package/dist/server/modules/worktree/worktree-sync-service.d.ts +1 -1
  191. package/dist/server/modules/worktree/worktree-sync-service.js +5 -3
  192. package/dist/server/modules/worktree/worktree-sync-service.js.map +1 -1
  193. package/dist/server/routes/assistant.js +43 -0
  194. package/dist/server/routes/assistant.js.map +1 -1
  195. package/dist/server/routes/butler.js +5 -0
  196. package/dist/server/routes/butler.js.map +1 -1
  197. package/dist/server/server/create-server.d.ts +8 -0
  198. package/dist/server/server/create-server.js +51 -13
  199. package/dist/server/server/create-server.js.map +1 -1
  200. package/dist/server/shared/http/request-abort.d.ts +2 -0
  201. package/dist/server/shared/http/request-abort.js +38 -0
  202. package/dist/server/shared/http/request-abort.js.map +1 -0
  203. package/dist/server/storage/repositories/assistant-automation-run-repository.d.ts +12 -0
  204. package/dist/server/storage/repositories/assistant-automation-run-repository.js +139 -0
  205. package/dist/server/storage/repositories/assistant-automation-run-repository.js.map +1 -0
  206. package/dist/server/storage/repositories/assistant-automation-task-repository.d.ts +15 -0
  207. package/dist/server/storage/repositories/assistant-automation-task-repository.js +173 -0
  208. package/dist/server/storage/repositories/assistant-automation-task-repository.js.map +1 -0
  209. package/dist/server/storage/repositories/assistant-sandbox-workspace-repository.d.ts +17 -0
  210. package/dist/server/storage/repositories/assistant-sandbox-workspace-repository.js +164 -0
  211. package/dist/server/storage/repositories/assistant-sandbox-workspace-repository.js.map +1 -0
  212. package/dist/server/storage/repositories/butler-control-session-repository.js +27 -3
  213. package/dist/server/storage/repositories/butler-control-session-repository.js.map +1 -1
  214. package/dist/server/storage/repositories/butler-control-timer-repository.d.ts +15 -0
  215. package/dist/server/storage/repositories/butler-control-timer-repository.js +157 -0
  216. package/dist/server/storage/repositories/butler-control-timer-repository.js.map +1 -0
  217. package/dist/server/storage/repositories/user-preference-profile-repository.js +6 -3
  218. package/dist/server/storage/repositories/user-preference-profile-repository.js.map +1 -1
  219. package/dist/server/storage/sqlite/client.js +239 -2
  220. package/dist/server/storage/sqlite/client.js.map +1 -1
  221. package/dist/server/storage/sqlite/schema.sql +107 -1
  222. package/dist/server/types/domain.d.ts +89 -2
  223. package/dist/server/ws/workbench-ws-hub.d.ts +14 -7
  224. package/dist/server/ws/workbench-ws-hub.js +316 -158
  225. package/dist/server/ws/workbench-ws-hub.js.map +1 -1
  226. package/node_modules/@codingns/session-sync-core/dist/providers/claude-code.d.ts +4 -1
  227. package/node_modules/@codingns/session-sync-core/dist/providers/claude-code.js +111 -3
  228. package/node_modules/@codingns/session-sync-core/dist/providers/claude-code.js.map +1 -1
  229. package/node_modules/@codingns/session-sync-core/dist/providers/codex.d.ts +6 -1
  230. package/node_modules/@codingns/session-sync-core/dist/providers/codex.js +306 -31
  231. package/node_modules/@codingns/session-sync-core/dist/providers/codex.js.map +1 -1
  232. package/node_modules/@codingns/session-sync-core/dist/providers/gemini.d.ts +5 -1
  233. package/node_modules/@codingns/session-sync-core/dist/providers/gemini.js +187 -26
  234. package/node_modules/@codingns/session-sync-core/dist/providers/gemini.js.map +1 -1
  235. package/node_modules/@codingns/session-sync-core/dist/providers/kimi.d.ts +4 -1
  236. package/node_modules/@codingns/session-sync-core/dist/providers/kimi.js +98 -1
  237. package/node_modules/@codingns/session-sync-core/dist/providers/kimi.js.map +1 -1
  238. package/node_modules/@codingns/session-sync-core/dist/providers/opencode.d.ts +2 -0
  239. package/node_modules/@codingns/session-sync-core/dist/providers/opencode.js +71 -8
  240. package/node_modules/@codingns/session-sync-core/dist/providers/opencode.js.map +1 -1
  241. package/node_modules/@codingns/session-sync-core/dist/providers/utils.d.ts +1 -0
  242. package/node_modules/@codingns/session-sync-core/dist/providers/utils.js +4 -1
  243. package/node_modules/@codingns/session-sync-core/dist/providers/utils.js.map +1 -1
  244. package/node_modules/@codingns/session-sync-core/dist/runtime/claude-runtime.js +44 -0
  245. package/node_modules/@codingns/session-sync-core/dist/runtime/claude-runtime.js.map +1 -1
  246. package/node_modules/@codingns/session-sync-core/dist/runtime/codex-runtime.js +9 -3
  247. package/node_modules/@codingns/session-sync-core/dist/runtime/codex-runtime.js.map +1 -1
  248. package/node_modules/@codingns/session-sync-core/dist/runtime/types.d.ts +1 -0
  249. package/node_modules/@codingns/session-sync-core/dist/services.js +17 -8
  250. package/node_modules/@codingns/session-sync-core/dist/services.js.map +1 -1
  251. package/node_modules/@codingns/session-sync-core/dist/types.d.ts +4 -0
  252. package/package.json +1 -1
  253. package/scripts/postinstall.mjs +33 -0
  254. package/dist/public/assets/index-Cek6u0b9.css +0 -1
  255. package/dist/public/assets/index-THHY79si.js +0 -122
@@ -0,0 +1,786 @@
1
+ import { AppError } from "../../shared/errors/app-error.js";
2
+ import { createId } from "../../shared/utils/id.js";
3
+ import { nowIso } from "../../shared/utils/time.js";
4
+ import { createTaskManager } from "../tasks/task-manager.js";
5
+ import { HOST_TASK_TYPES } from "../tasks/task-types.js";
6
+ import { buildConditionTriggerConfig, computeNextRunAt, createTriggerConfig, parseActionConfig, parseConditionState, parseTriggerConfig } from "./assistant-automation-trigger.js";
7
+ const DEFAULT_DUE_TASK_LIMIT = 20;
8
+ const DEFAULT_RECENT_RUN_LIMIT = 30;
9
+ export class AssistantAutomationService {
10
+ butlerProfileService;
11
+ butlerControlSessionService;
12
+ taskRepository;
13
+ runRepository;
14
+ taskManager;
15
+ gitCommandRunner;
16
+ sessionLiveRuntimeService;
17
+ gitWorkingDirectory;
18
+ constructor(butlerProfileService, butlerControlSessionService, taskRepository, runRepository, taskManager = createTaskManager(), dependencies = {}) {
19
+ this.butlerProfileService = butlerProfileService;
20
+ this.butlerControlSessionService = butlerControlSessionService;
21
+ this.taskRepository = taskRepository;
22
+ this.runRepository = runRepository;
23
+ this.taskManager = taskManager;
24
+ this.gitCommandRunner = dependencies.gitCommandRunner ?? null;
25
+ this.sessionLiveRuntimeService = dependencies.sessionLiveRuntimeService ?? null;
26
+ this.gitWorkingDirectory = dependencies.gitWorkingDirectory ?? process.cwd();
27
+ this.registerBackgroundTasks();
28
+ }
29
+ listTasks(filters) {
30
+ this.butlerProfileService.ensureInitialized();
31
+ return this.taskRepository
32
+ .list({
33
+ statuses: filters.statuses,
34
+ controlSessionId: filters.controlSessionId?.trim() || undefined,
35
+ limit: filters.limit
36
+ })
37
+ .map((record) => this.toTaskView(record, filters.userId));
38
+ }
39
+ getTask(taskId, userId) {
40
+ this.butlerProfileService.ensureInitialized();
41
+ const task = this.requireTask(taskId.trim());
42
+ return this.toTaskView(task, userId);
43
+ }
44
+ listRuns(taskId, _userId, limit) {
45
+ this.butlerProfileService.ensureInitialized();
46
+ this.requireTask(taskId);
47
+ return this.runRepository
48
+ .listByAutomation(taskId.trim(), limit)
49
+ .map((record) => this.toRunView(record));
50
+ }
51
+ listRecentRuns(filters) {
52
+ this.butlerProfileService.ensureInitialized();
53
+ const limit = filters.limit && filters.limit > 0 ? filters.limit : DEFAULT_RECENT_RUN_LIMIT;
54
+ const taskMap = new Map(this.taskRepository.list().map((task) => [task.id, task]));
55
+ return this.runRepository
56
+ .listRecent(limit * 3)
57
+ .filter((run) => {
58
+ const task = taskMap.get(run.automationId);
59
+ if (!task || task.userId !== filters.userId) {
60
+ return false;
61
+ }
62
+ if (filters.controlSessionId?.trim()) {
63
+ return task.controlSessionId === filters.controlSessionId.trim();
64
+ }
65
+ return true;
66
+ })
67
+ .slice(0, limit)
68
+ .map((record) => this.toRunView(record));
69
+ }
70
+ createTask(input) {
71
+ this.butlerProfileService.ensureInitialized();
72
+ const controlSession = input.controlSessionId?.trim()
73
+ ? this.butlerControlSessionService.getSession(input.controlSessionId, input.userId)
74
+ : this.butlerControlSessionService.getCurrentSession(input.userId);
75
+ if (!controlSession) {
76
+ throw new AppError({
77
+ statusCode: 409,
78
+ errorCode: "BUTLER_CONTROL_SESSION_NOT_FOUND",
79
+ detail: "当前没有可用的助手控制会话,无法创建自动化任务"
80
+ });
81
+ }
82
+ const timestamp = nowIso();
83
+ const trigger = createTriggerConfig(input.trigger, timestamp);
84
+ const task = this.taskRepository.create({
85
+ id: createId(),
86
+ userId: input.userId,
87
+ controlSessionId: controlSession.id,
88
+ projectId: normalizeNullableText(input.projectId),
89
+ title: normalizeNullableText(input.title),
90
+ triggerType: trigger.triggerType,
91
+ triggerConfigJson: trigger.triggerConfigJson,
92
+ actionType: "send_control_message",
93
+ actionConfigJson: JSON.stringify({
94
+ content: requireContent(input.action.content),
95
+ includeTriggerContext: input.action.includeTriggerContext === true,
96
+ targetSessionId: normalizeNullableText(input.action.targetSessionId)
97
+ }),
98
+ status: "active",
99
+ nextRunAt: trigger.nextRunAt,
100
+ lastRunAt: null,
101
+ lastRunSummary: null,
102
+ lastError: null,
103
+ createdAt: timestamp,
104
+ updatedAt: timestamp,
105
+ cancelledAt: null
106
+ });
107
+ return this.toTaskView(task, input.userId);
108
+ }
109
+ updateTask(input) {
110
+ this.butlerProfileService.ensureInitialized();
111
+ const current = this.requireTask(input.taskId);
112
+ if (current.status !== "active") {
113
+ throw new AppError({
114
+ statusCode: 409,
115
+ errorCode: "ASSISTANT_AUTOMATION_UPDATE_NOT_ALLOWED",
116
+ detail: "只有进行中的自动化可以修改配置"
117
+ });
118
+ }
119
+ const updatedAt = nowIso();
120
+ const currentTriggerConfig = parseTriggerConfig(current.triggerType, current.triggerConfigJson);
121
+ const currentActionConfig = parseActionConfig(current.actionConfigJson);
122
+ const trigger = this.buildUpdatedTrigger(current, currentTriggerConfig, input, updatedAt);
123
+ const actionConfig = {
124
+ content: typeof input.content === "string"
125
+ ? requireContent(input.content)
126
+ : currentActionConfig.content,
127
+ includeTriggerContext: typeof input.includeTriggerContext === "boolean"
128
+ ? input.includeTriggerContext
129
+ : currentActionConfig.includeTriggerContext,
130
+ targetSessionId: currentActionConfig.targetSessionId
131
+ };
132
+ const nextTitle = input.title !== undefined
133
+ ? normalizeNullableText(input.title)
134
+ : current.title;
135
+ const updated = this.taskRepository.update({
136
+ ...current,
137
+ title: nextTitle,
138
+ triggerConfigJson: trigger.triggerConfigJson,
139
+ nextRunAt: trigger.nextRunAt,
140
+ actionConfigJson: JSON.stringify(actionConfig),
141
+ updatedAt,
142
+ lastError: null
143
+ });
144
+ return this.toTaskView(updated, input.userId);
145
+ }
146
+ cancelTask(taskId, userId) {
147
+ this.butlerProfileService.ensureInitialized();
148
+ const current = this.requireTask(taskId);
149
+ const cancelledAt = nowIso();
150
+ const updated = this.taskRepository.update({
151
+ ...current,
152
+ status: "cancelled",
153
+ updatedAt: cancelledAt,
154
+ cancelledAt,
155
+ nextRunAt: null
156
+ });
157
+ return this.toTaskView(updated, userId);
158
+ }
159
+ skipCurrentWait(taskId, userId) {
160
+ this.butlerProfileService.ensureInitialized();
161
+ const current = this.requireTask(taskId);
162
+ if (current.status !== "active" || !current.nextRunAt) {
163
+ throw new AppError({
164
+ statusCode: 409,
165
+ errorCode: "ASSISTANT_AUTOMATION_WAIT_NOT_ACTIVE",
166
+ detail: "当前自动化没有可取消的等待"
167
+ });
168
+ }
169
+ if (current.triggerType === "once") {
170
+ throw new AppError({
171
+ statusCode: 409,
172
+ errorCode: "ASSISTANT_AUTOMATION_WAIT_SKIP_UNSUPPORTED",
173
+ detail: "单次自动化不支持只取消本次等待"
174
+ });
175
+ }
176
+ const referenceAt = nowIso();
177
+ const triggerConfig = parseTriggerConfig(current.triggerType, current.triggerConfigJson);
178
+ const nextReferenceAt = current.nextRunAt > referenceAt ? current.nextRunAt : referenceAt;
179
+ const nextRunAt = computeNextRunAt(triggerConfig, nextReferenceAt);
180
+ const updatedAt = referenceAt;
181
+ this.runRepository.create({
182
+ id: createId(),
183
+ automationId: current.id,
184
+ runSeq: this.runRepository.getLatestSeq(current.id) + 1,
185
+ triggerType: current.triggerType,
186
+ triggerSnapshotJson: current.triggerConfigJson,
187
+ actionType: current.actionType,
188
+ actionSnapshotJson: current.actionConfigJson,
189
+ status: "cancelled",
190
+ summary: "已手动取消本次等待,保留自动化并重新安排下一次运行。",
191
+ error: null,
192
+ scheduledAt: current.nextRunAt,
193
+ startedAt: updatedAt,
194
+ finishedAt: updatedAt,
195
+ createdAt: updatedAt
196
+ });
197
+ const updated = this.taskRepository.update({
198
+ ...current,
199
+ status: nextRunAt === null ? "completed" : "active",
200
+ nextRunAt,
201
+ lastError: null,
202
+ updatedAt
203
+ });
204
+ return this.toTaskView(updated, userId);
205
+ }
206
+ async runDueTasks(referenceAt) {
207
+ this.butlerProfileService.ensureInitialized();
208
+ return await this.taskManager.enqueue(HOST_TASK_TYPES.assistantAutomationTick, {
209
+ key: "global",
210
+ source: "assistant_automation.run_due_tasks",
211
+ input: {
212
+ referenceAt
213
+ }
214
+ }).promise;
215
+ }
216
+ registerBackgroundTasks() {
217
+ if (!this.taskManager.has(HOST_TASK_TYPES.assistantAutomationTick)) {
218
+ this.taskManager.register({
219
+ taskType: HOST_TASK_TYPES.assistantAutomationTick,
220
+ executionLane: "host_background",
221
+ timeoutMs: 10_000,
222
+ run: async ({ referenceAt }) => await this.runDueTasksDirect(referenceAt)
223
+ });
224
+ }
225
+ if (!this.taskManager.has(HOST_TASK_TYPES.assistantAutomationEvaluate)) {
226
+ this.taskManager.register({
227
+ taskType: HOST_TASK_TYPES.assistantAutomationEvaluate,
228
+ executionLane: "host_background",
229
+ timeoutMs: 20_000,
230
+ run: async ({ automationId, referenceAt }) => {
231
+ await this.evaluateTask(automationId, referenceAt);
232
+ }
233
+ });
234
+ }
235
+ }
236
+ async runDueTasksDirect(referenceAt) {
237
+ const activeTaskCount = this.taskRepository.list({
238
+ statuses: ["active"]
239
+ }).length;
240
+ const dueTasks = this.taskRepository.listDueActive(referenceAt, DEFAULT_DUE_TASK_LIMIT);
241
+ const handles = dueTasks.map((task) => this.taskManager.enqueue(HOST_TASK_TYPES.assistantAutomationEvaluate, {
242
+ key: task.id,
243
+ source: "assistant_automation.tick.evaluate",
244
+ input: {
245
+ automationId: task.id,
246
+ referenceAt
247
+ }
248
+ }));
249
+ await Promise.all(handles.map((handle) => handle.promise));
250
+ return {
251
+ activeTaskCount,
252
+ dueTaskCount: dueTasks.length,
253
+ processedTaskCount: dueTasks.length,
254
+ idle: dueTasks.length === 0
255
+ };
256
+ }
257
+ async evaluateTask(automationId, referenceAt) {
258
+ const task = this.reconcileTaskWithLatestRun(this.requireTask(automationId), referenceAt);
259
+ if (!task || task.status !== "active" || !task.nextRunAt || task.nextRunAt > referenceAt) {
260
+ return;
261
+ }
262
+ await this.processDueTask(task, referenceAt);
263
+ }
264
+ async processDueTask(task, referenceAt) {
265
+ const prepared = await this.prepareDueTask(task, referenceAt);
266
+ if (prepared.kind === "skip") {
267
+ this.taskRepository.update(prepared.task);
268
+ return;
269
+ }
270
+ const run = this.runRepository.create({
271
+ id: createId(),
272
+ automationId: task.id,
273
+ runSeq: this.runRepository.getLatestSeq(task.id) + 1,
274
+ triggerType: task.triggerType,
275
+ triggerSnapshotJson: prepared.triggerSnapshotJson,
276
+ actionType: task.actionType,
277
+ actionSnapshotJson: task.actionConfigJson,
278
+ status: "running",
279
+ summary: null,
280
+ error: null,
281
+ scheduledAt: prepared.scheduledAt,
282
+ startedAt: referenceAt,
283
+ finishedAt: null,
284
+ createdAt: referenceAt
285
+ });
286
+ try {
287
+ const result = await this.butlerControlSessionService.sendMessage(task.userId, {
288
+ controlSessionId: task.controlSessionId,
289
+ content: prepared.messageContent,
290
+ clientRequestId: buildAutomationClientRequestId(task.id, prepared.scheduledAt)
291
+ });
292
+ const summary = summarizeMessage(prepared.messageContent);
293
+ const finishedRun = this.runRepository.update({
294
+ ...run,
295
+ status: "succeeded",
296
+ summary,
297
+ error: null,
298
+ finishedAt: result.acceptedAt
299
+ });
300
+ this.taskRepository.update(prepared.finalizeSuccess(result.acceptedAt, finishedRun.summary ?? summary));
301
+ }
302
+ catch (error) {
303
+ const finishedAt = nowIso();
304
+ const errorMessage = error instanceof Error ? error.message : String(error);
305
+ this.runRepository.update({
306
+ ...run,
307
+ status: "failed",
308
+ summary: null,
309
+ error: errorMessage,
310
+ finishedAt
311
+ });
312
+ this.taskRepository.update(prepared.finalizeFailure(finishedAt, errorMessage));
313
+ }
314
+ }
315
+ async prepareDueTask(task, referenceAt) {
316
+ const actionConfig = parseActionConfig(task.actionConfigJson);
317
+ const triggerConfig = parseTriggerConfig(task.triggerType, task.triggerConfigJson);
318
+ const scheduledAt = task.nextRunAt ?? referenceAt;
319
+ if (triggerConfig.type === "condition") {
320
+ return await this.prepareConditionTask(task, triggerConfig, actionConfig, referenceAt, scheduledAt);
321
+ }
322
+ const messageContent = actionConfig.content;
323
+ const triggerSnapshotJson = JSON.stringify(triggerConfig);
324
+ return {
325
+ kind: "run",
326
+ task,
327
+ scheduledAt,
328
+ triggerSnapshotJson,
329
+ actionConfig,
330
+ messageContent,
331
+ finalizeSuccess: (finishedAt, summary) => this.finalizeSuccessfulTask(task, triggerConfig, finishedAt, summary),
332
+ finalizeFailure: (finishedAt, errorMessage) => ({
333
+ ...task,
334
+ status: "failed",
335
+ nextRunAt: null,
336
+ lastRunAt: finishedAt,
337
+ lastError: errorMessage,
338
+ updatedAt: finishedAt
339
+ })
340
+ };
341
+ }
342
+ async prepareConditionTask(task, triggerConfig, actionConfig, referenceAt, scheduledAt) {
343
+ const currentState = parseConditionState(triggerConfig);
344
+ if (shouldCompleteConditionWithoutCheck(triggerConfig, currentState, referenceAt)) {
345
+ return {
346
+ kind: "skip",
347
+ task: {
348
+ ...task,
349
+ status: "completed",
350
+ nextRunAt: null,
351
+ updatedAt: referenceAt,
352
+ lastError: null
353
+ }
354
+ };
355
+ }
356
+ try {
357
+ if (triggerConfig.conditionKind === "git.remote_tag_changed") {
358
+ return await this.prepareGitRemoteTagChangedTask(task, triggerConfig, currentState, actionConfig, referenceAt, scheduledAt);
359
+ }
360
+ return await this.prepareSessionRuntimeIdleTask(task, triggerConfig, currentState, actionConfig, referenceAt, scheduledAt);
361
+ }
362
+ catch (error) {
363
+ const errorMessage = error instanceof Error ? error.message : String(error);
364
+ const nextState = bumpConditionStateOnError(triggerConfig.conditionKind, currentState, referenceAt);
365
+ const nextTask = this.buildConditionSkippedTask(task, triggerConfig, nextState, referenceAt, errorMessage);
366
+ return {
367
+ kind: "skip",
368
+ task: nextTask
369
+ };
370
+ }
371
+ }
372
+ async prepareGitRemoteTagChangedTask(task, triggerConfig, state, actionConfig, referenceAt, scheduledAt) {
373
+ const latest = await this.readLatestRemoteTag(state.repositoryUrl);
374
+ const nextState = {
375
+ ...state,
376
+ latestTag: latest.tag,
377
+ latestRef: latest.ref,
378
+ checkCount: state.checkCount + 1,
379
+ lastCheckedAt: referenceAt
380
+ };
381
+ const baselineMissing = !state.latestTag && !state.latestRef;
382
+ const matched = !baselineMissing
383
+ && (state.latestTag !== latest.tag || state.latestRef !== latest.ref);
384
+ if (!matched) {
385
+ return {
386
+ kind: "skip",
387
+ task: this.buildConditionSkippedTask(task, triggerConfig, nextState, referenceAt, null)
388
+ };
389
+ }
390
+ const nextTriggerConfig = buildConditionTriggerConfig(triggerConfig, nextState);
391
+ const triggerContext = {
392
+ conditionKind: triggerConfig.conditionKind,
393
+ repositoryUrl: state.repositoryUrl,
394
+ previousTag: state.latestTag,
395
+ previousRef: state.latestRef,
396
+ currentTag: latest.tag,
397
+ currentRef: latest.ref,
398
+ checkedAt: referenceAt
399
+ };
400
+ const messageContent = mergeTriggerContext(actionConfig.content, actionConfig.includeTriggerContext, buildGitTagChangedContextMessage(triggerContext));
401
+ return {
402
+ kind: "run",
403
+ task,
404
+ scheduledAt,
405
+ triggerSnapshotJson: JSON.stringify({
406
+ ...nextTriggerConfig,
407
+ triggerContext
408
+ }),
409
+ actionConfig,
410
+ messageContent,
411
+ finalizeSuccess: (finishedAt, summary) => ({
412
+ ...task,
413
+ triggerConfigJson: JSON.stringify(nextTriggerConfig),
414
+ status: "completed",
415
+ nextRunAt: null,
416
+ lastRunAt: finishedAt,
417
+ lastRunSummary: summary,
418
+ lastError: null,
419
+ updatedAt: finishedAt
420
+ }),
421
+ finalizeFailure: (finishedAt, errorMessage) => ({
422
+ ...task,
423
+ triggerConfigJson: JSON.stringify(nextTriggerConfig),
424
+ status: "failed",
425
+ nextRunAt: null,
426
+ lastRunAt: finishedAt,
427
+ lastError: errorMessage,
428
+ updatedAt: finishedAt
429
+ })
430
+ };
431
+ }
432
+ async prepareSessionRuntimeIdleTask(task, triggerConfig, state, actionConfig, referenceAt, scheduledAt) {
433
+ if (!this.sessionLiveRuntimeService) {
434
+ throw new AppError({
435
+ statusCode: 500,
436
+ errorCode: "ASSISTANT_AUTOMATION_CONDITION_UNSUPPORTED",
437
+ detail: "当前环境未配置 session runtime 能力,无法检查 session.runtime_idle"
438
+ });
439
+ }
440
+ const runtime = await this.sessionLiveRuntimeService.getSessionRuntime(state.sessionId, task.userId);
441
+ const nextState = {
442
+ ...state,
443
+ lastObservedRunningState: normalizeNullableText(String(runtime.runningState)),
444
+ lastHasActiveRun: runtime.hasActiveRun,
445
+ checkCount: state.checkCount + 1,
446
+ lastCheckedAt: referenceAt
447
+ };
448
+ const baselineMissing = state.lastHasActiveRun === null && !state.lastObservedRunningState;
449
+ const matched = !baselineMissing
450
+ && state.lastHasActiveRun === true
451
+ && runtime.hasActiveRun === false;
452
+ if (!matched) {
453
+ return {
454
+ kind: "skip",
455
+ task: this.buildConditionSkippedTask(task, triggerConfig, nextState, referenceAt, null)
456
+ };
457
+ }
458
+ const nextTriggerConfig = buildConditionTriggerConfig(triggerConfig, nextState);
459
+ const triggerContext = {
460
+ conditionKind: triggerConfig.conditionKind,
461
+ sessionId: state.sessionId,
462
+ previousRunningState: state.lastObservedRunningState,
463
+ previousHasActiveRun: state.lastHasActiveRun,
464
+ currentRunningState: String(runtime.runningState),
465
+ currentHasActiveRun: runtime.hasActiveRun,
466
+ checkedAt: referenceAt
467
+ };
468
+ const messageContent = mergeTriggerContext(actionConfig.content, actionConfig.includeTriggerContext, buildSessionRuntimeIdleContextMessage(triggerContext));
469
+ return {
470
+ kind: "run",
471
+ task,
472
+ scheduledAt,
473
+ triggerSnapshotJson: JSON.stringify({
474
+ ...nextTriggerConfig,
475
+ triggerContext
476
+ }),
477
+ actionConfig,
478
+ messageContent,
479
+ finalizeSuccess: (finishedAt, summary) => ({
480
+ ...task,
481
+ triggerConfigJson: JSON.stringify(nextTriggerConfig),
482
+ status: "completed",
483
+ nextRunAt: null,
484
+ lastRunAt: finishedAt,
485
+ lastRunSummary: summary,
486
+ lastError: null,
487
+ updatedAt: finishedAt
488
+ }),
489
+ finalizeFailure: (finishedAt, errorMessage) => ({
490
+ ...task,
491
+ triggerConfigJson: JSON.stringify(nextTriggerConfig),
492
+ status: "failed",
493
+ nextRunAt: null,
494
+ lastRunAt: finishedAt,
495
+ lastError: errorMessage,
496
+ updatedAt: finishedAt
497
+ })
498
+ };
499
+ }
500
+ buildConditionSkippedTask(task, triggerConfig, nextState, referenceAt, lastError) {
501
+ const nextTriggerConfig = buildConditionTriggerConfig(triggerConfig, nextState);
502
+ const exhaustedByCount = triggerConfig.maxChecks !== null && nextState.checkCount >= triggerConfig.maxChecks;
503
+ const nextRunAt = exhaustedByCount ? null : computeNextRunAt(nextTriggerConfig, referenceAt);
504
+ const completed = nextRunAt === null;
505
+ return {
506
+ ...task,
507
+ triggerConfigJson: JSON.stringify(nextTriggerConfig),
508
+ status: completed ? "completed" : "active",
509
+ nextRunAt,
510
+ lastError,
511
+ updatedAt: referenceAt
512
+ };
513
+ }
514
+ finalizeSuccessfulTask(task, triggerConfig, finishedAt, summary) {
515
+ const nextRunAt = computeNextRunAt(triggerConfig, finishedAt);
516
+ const completed = triggerConfig.type === "once" || nextRunAt === null;
517
+ return {
518
+ ...task,
519
+ status: completed ? "completed" : "active",
520
+ nextRunAt,
521
+ lastRunAt: finishedAt,
522
+ lastRunSummary: summary,
523
+ lastError: null,
524
+ updatedAt: finishedAt
525
+ };
526
+ }
527
+ requireTask(taskId) {
528
+ const task = this.taskRepository.findById(taskId.trim());
529
+ if (!task) {
530
+ throw new AppError({
531
+ statusCode: 404,
532
+ errorCode: "ASSISTANT_AUTOMATION_NOT_FOUND",
533
+ detail: "未找到对应的助手自动化任务"
534
+ });
535
+ }
536
+ return task;
537
+ }
538
+ buildUpdatedTrigger(task, currentTriggerConfig, input, referenceAt) {
539
+ switch (currentTriggerConfig.type) {
540
+ case "once": {
541
+ const nextDueAt = input.dueAt !== undefined
542
+ ? requireIsoTimestamp(input.dueAt, "dueAt")
543
+ : currentTriggerConfig.dueAt;
544
+ return {
545
+ triggerConfigJson: JSON.stringify({
546
+ type: "once",
547
+ dueAt: nextDueAt
548
+ }),
549
+ nextRunAt: nextDueAt
550
+ };
551
+ }
552
+ case "interval": {
553
+ const nextTrigger = createTriggerConfig({
554
+ type: "interval",
555
+ seconds: input.everySeconds !== undefined ? input.everySeconds : currentTriggerConfig.seconds,
556
+ minutes: input.everyMinutes !== undefined ? input.everyMinutes : currentTriggerConfig.minutes,
557
+ hours: input.everyHours !== undefined ? input.everyHours : currentTriggerConfig.hours,
558
+ stopAt: input.stopAt !== undefined ? input.stopAt : currentTriggerConfig.stopAt
559
+ }, referenceAt);
560
+ return {
561
+ triggerConfigJson: nextTrigger.triggerConfigJson,
562
+ nextRunAt: nextTrigger.nextRunAt
563
+ };
564
+ }
565
+ case "cron": {
566
+ const nextTrigger = createTriggerConfig({
567
+ type: "cron",
568
+ minute: input.cronMinute !== undefined ? input.cronMinute : currentTriggerConfig.minute,
569
+ hour: input.cronHour !== undefined ? input.cronHour : currentTriggerConfig.hour,
570
+ daysOfWeek: input.cronDaysOfWeek !== undefined
571
+ ? input.cronDaysOfWeek
572
+ : currentTriggerConfig.daysOfWeek,
573
+ stopAt: input.stopAt !== undefined ? input.stopAt : currentTriggerConfig.stopAt
574
+ }, referenceAt);
575
+ return {
576
+ triggerConfigJson: nextTrigger.triggerConfigJson,
577
+ nextRunAt: nextTrigger.nextRunAt
578
+ };
579
+ }
580
+ case "condition": {
581
+ const nextTriggerConfig = {
582
+ ...currentTriggerConfig,
583
+ pollIntervalSeconds: input.pollIntervalSeconds !== undefined
584
+ ? input.pollIntervalSeconds || currentTriggerConfig.pollIntervalSeconds
585
+ : currentTriggerConfig.pollIntervalSeconds,
586
+ expiresAt: input.expiresAt !== undefined
587
+ ? input.expiresAt
588
+ : currentTriggerConfig.expiresAt,
589
+ maxChecks: input.maxChecks !== undefined
590
+ ? input.maxChecks
591
+ : currentTriggerConfig.maxChecks
592
+ };
593
+ return {
594
+ triggerConfigJson: JSON.stringify(nextTriggerConfig),
595
+ nextRunAt: computeNextRunAt(nextTriggerConfig, referenceAt)
596
+ };
597
+ }
598
+ default:
599
+ return assertNeverTriggerConfig(currentTriggerConfig);
600
+ }
601
+ }
602
+ reconcileTaskWithLatestRun(task, referenceAt) {
603
+ if (task.status !== "active" || !task.nextRunAt) {
604
+ return task;
605
+ }
606
+ const latestRun = this.runRepository.findLatestByAutomation(task.id);
607
+ if (!latestRun || latestRun.scheduledAt !== task.nextRunAt) {
608
+ return task;
609
+ }
610
+ const triggerConfig = parseTriggerConfig(task.triggerType, task.triggerConfigJson);
611
+ if (latestRun.status === "succeeded") {
612
+ const reconciled = this.finalizeSuccessfulTask(task, triggerConfig, latestRun.finishedAt ?? latestRun.startedAt ?? referenceAt, latestRun.summary ?? summarizeMessage(parseActionConfig(task.actionConfigJson).content));
613
+ this.taskRepository.update(reconciled);
614
+ return null;
615
+ }
616
+ if (latestRun.status === "failed") {
617
+ this.taskRepository.update({
618
+ ...task,
619
+ status: "failed",
620
+ nextRunAt: null,
621
+ lastRunAt: latestRun.finishedAt ?? latestRun.startedAt,
622
+ lastRunSummary: latestRun.summary,
623
+ lastError: latestRun.error ?? "ASSISTANT_AUTOMATION_RUN_FAILED",
624
+ updatedAt: latestRun.finishedAt ?? referenceAt
625
+ });
626
+ return null;
627
+ }
628
+ if (latestRun.status === "running") {
629
+ this.runRepository.update({
630
+ ...latestRun,
631
+ status: "failed",
632
+ error: latestRun.error ?? "ASSISTANT_AUTOMATION_RUN_INTERRUPTED",
633
+ finishedAt: referenceAt
634
+ });
635
+ }
636
+ return this.requireTask(task.id);
637
+ }
638
+ async readLatestRemoteTag(repositoryUrl) {
639
+ if (!this.gitCommandRunner) {
640
+ throw new AppError({
641
+ statusCode: 500,
642
+ errorCode: "ASSISTANT_AUTOMATION_CONDITION_UNSUPPORTED",
643
+ detail: "当前环境未配置 git 能力,无法检查 git.remote_tag_changed"
644
+ });
645
+ }
646
+ const result = await this.gitCommandRunner.run(this.gitWorkingDirectory, ["ls-remote", "--refs", "--tags", "--sort=-v:refname", repositoryUrl], {
647
+ timeoutMs: 15_000,
648
+ allowNonZeroExit: false,
649
+ operation: "assistant_automation_git_remote_tag_changed"
650
+ });
651
+ const firstLine = result.stdout
652
+ .split(/\r?\n/)
653
+ .map((line) => line.trim())
654
+ .find((line) => line.length > 0);
655
+ if (!firstLine) {
656
+ return {
657
+ tag: null,
658
+ ref: null
659
+ };
660
+ }
661
+ const [ref, fullName] = firstLine.split(/\s+/, 2);
662
+ return {
663
+ tag: fullName?.replace(/^refs\/tags\//, "") || null,
664
+ ref: ref?.trim() || null
665
+ };
666
+ }
667
+ toTaskView(record, userId) {
668
+ return {
669
+ ...record,
670
+ controlSession: this.butlerControlSessionService.getSession(record.controlSessionId, userId),
671
+ triggerConfig: parseTriggerConfig(record.triggerType, record.triggerConfigJson),
672
+ actionConfig: parseActionConfig(record.actionConfigJson)
673
+ };
674
+ }
675
+ toRunView(record) {
676
+ const triggerSnapshot = parseTriggerConfig(record.triggerType, record.triggerSnapshotJson);
677
+ const parsedSnapshot = tryParseJson(record.triggerSnapshotJson);
678
+ if (parsedSnapshot?.triggerContext && typeof parsedSnapshot.triggerContext === "object") {
679
+ triggerSnapshot.triggerContext = parsedSnapshot.triggerContext;
680
+ }
681
+ return {
682
+ ...record,
683
+ triggerSnapshot,
684
+ actionSnapshot: parseActionConfig(record.actionSnapshotJson)
685
+ };
686
+ }
687
+ }
688
+ function requireContent(value) {
689
+ const normalized = value.trim();
690
+ if (!normalized) {
691
+ throw new AppError({
692
+ statusCode: 400,
693
+ errorCode: "INVALID_INPUT",
694
+ detail: "创建助手自动化必须提供 content",
695
+ field: "content"
696
+ });
697
+ }
698
+ return normalized;
699
+ }
700
+ function requireIsoTimestamp(value, field) {
701
+ const normalized = normalizeNullableText(value);
702
+ if (!normalized) {
703
+ throw new AppError({
704
+ statusCode: 400,
705
+ errorCode: "INVALID_INPUT",
706
+ detail: `${field} 必须是合法的 ISO 时间`,
707
+ field
708
+ });
709
+ }
710
+ const timestamp = Date.parse(normalized);
711
+ if (Number.isNaN(timestamp)) {
712
+ throw new AppError({
713
+ statusCode: 400,
714
+ errorCode: "INVALID_INPUT",
715
+ detail: `${field} 必须是合法的 ISO 时间`,
716
+ field
717
+ });
718
+ }
719
+ return new Date(timestamp).toISOString();
720
+ }
721
+ function assertNeverTriggerConfig(value) {
722
+ throw new Error(`Unsupported trigger config: ${JSON.stringify(value)}`);
723
+ }
724
+ function normalizeNullableText(value) {
725
+ const normalized = value?.trim();
726
+ return normalized ? normalized : null;
727
+ }
728
+ function buildAutomationClientRequestId(taskId, referenceAt) {
729
+ return `assistant-automation:${taskId}:${Date.parse(referenceAt) || Date.now()}`;
730
+ }
731
+ function summarizeMessage(content) {
732
+ return content.length > 120 ? `${content.slice(0, 117)}...` : content;
733
+ }
734
+ function shouldCompleteConditionWithoutCheck(config, state, referenceAt) {
735
+ if (config.maxChecks !== null && state.checkCount >= config.maxChecks) {
736
+ return true;
737
+ }
738
+ return config.expiresAt !== null && referenceAt > config.expiresAt;
739
+ }
740
+ function bumpConditionStateOnError(conditionKind, state, referenceAt) {
741
+ if (conditionKind === "git.remote_tag_changed") {
742
+ return {
743
+ ...state,
744
+ checkCount: state.checkCount + 1,
745
+ lastCheckedAt: referenceAt
746
+ };
747
+ }
748
+ return {
749
+ ...state,
750
+ checkCount: state.checkCount + 1,
751
+ lastCheckedAt: referenceAt
752
+ };
753
+ }
754
+ function mergeTriggerContext(content, includeTriggerContext, triggerContext) {
755
+ return includeTriggerContext ? `${triggerContext}\n\n${content}` : content;
756
+ }
757
+ function buildGitTagChangedContextMessage(context) {
758
+ return [
759
+ "触发条件:远端仓库出现新 tag",
760
+ `仓库:${context.repositoryUrl}`,
761
+ `旧基线:${context.previousTag ?? "-"} @ ${context.previousRef ?? "-"}`,
762
+ `新状态:${context.currentTag ?? "-"} @ ${context.currentRef ?? "-"}`,
763
+ `检查时间:${context.checkedAt}`
764
+ ].join("\n");
765
+ }
766
+ function buildSessionRuntimeIdleContextMessage(context) {
767
+ return [
768
+ "触发条件:目标会话已进入空闲状态",
769
+ `会话:${context.sessionId}`,
770
+ `上一轮状态:${context.previousRunningState ?? "-"} / hasActiveRun=${String(context.previousHasActiveRun)}`,
771
+ `当前状态:${context.currentRunningState ?? "-"} / hasActiveRun=${String(context.currentHasActiveRun)}`,
772
+ `检查时间:${context.checkedAt}`
773
+ ].join("\n");
774
+ }
775
+ function tryParseJson(value) {
776
+ try {
777
+ const parsed = JSON.parse(value);
778
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed)
779
+ ? parsed
780
+ : null;
781
+ }
782
+ catch {
783
+ return null;
784
+ }
785
+ }
786
+ //# sourceMappingURL=assistant-automation-service.js.map