@jingyi0605/codingns 0.3.6 → 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 (185) hide show
  1. package/README.md +3 -0
  2. package/bin/codingns.mjs +489 -1
  3. package/dist/public/assets/{TerminalPage-D00S4KM6.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/modules/assistant-capability/assistant-capability-controller.d.ts +173 -0
  8. package/dist/server/modules/assistant-capability/assistant-capability-controller.js +307 -0
  9. package/dist/server/modules/assistant-capability/assistant-capability-controller.js.map +1 -1
  10. package/dist/server/modules/assistant-capability/assistant-capability-service.d.ts +199 -2
  11. package/dist/server/modules/assistant-capability/assistant-capability-service.js +565 -3
  12. package/dist/server/modules/assistant-capability/assistant-capability-service.js.map +1 -1
  13. package/dist/server/modules/butler/assistant-automation-service.d.ts +110 -0
  14. package/dist/server/modules/butler/assistant-automation-service.js +786 -0
  15. package/dist/server/modules/butler/assistant-automation-service.js.map +1 -0
  16. package/dist/server/modules/butler/assistant-automation-trigger.d.ts +94 -0
  17. package/dist/server/modules/butler/assistant-automation-trigger.js +400 -0
  18. package/dist/server/modules/butler/assistant-automation-trigger.js.map +1 -0
  19. package/dist/server/modules/butler/assistant-sandbox-service.d.ts +55 -0
  20. package/dist/server/modules/butler/assistant-sandbox-service.js +266 -0
  21. package/dist/server/modules/butler/assistant-sandbox-service.js.map +1 -0
  22. package/dist/server/modules/butler/butler-action-context-service.d.ts +4 -1
  23. package/dist/server/modules/butler/butler-action-context-service.js +8 -2
  24. package/dist/server/modules/butler/butler-action-context-service.js.map +1 -1
  25. package/dist/server/modules/butler/butler-control-session-service.d.ts +8 -1
  26. package/dist/server/modules/butler/butler-control-session-service.js +154 -40
  27. package/dist/server/modules/butler/butler-control-session-service.js.map +1 -1
  28. package/dist/server/modules/butler/butler-control-timer-scheduler.d.ts +32 -0
  29. package/dist/server/modules/butler/butler-control-timer-scheduler.js +93 -0
  30. package/dist/server/modules/butler/butler-control-timer-scheduler.js.map +1 -0
  31. package/dist/server/modules/butler/butler-control-timer-service.d.ts +42 -0
  32. package/dist/server/modules/butler/butler-control-timer-service.js +132 -0
  33. package/dist/server/modules/butler/butler-control-timer-service.js.map +1 -0
  34. package/dist/server/modules/butler/butler-controller.d.ts +42 -2
  35. package/dist/server/modules/butler/butler-controller.js +79 -12
  36. package/dist/server/modules/butler/butler-controller.js.map +1 -1
  37. package/dist/server/modules/butler/butler-follow-up-service.d.ts +9 -1
  38. package/dist/server/modules/butler/butler-follow-up-service.js +273 -181
  39. package/dist/server/modules/butler/butler-follow-up-service.js.map +1 -1
  40. package/dist/server/modules/butler/butler-inbox-analysis-service.d.ts +4 -1
  41. package/dist/server/modules/butler/butler-inbox-analysis-service.js +18 -4
  42. package/dist/server/modules/butler/butler-inbox-analysis-service.js.map +1 -1
  43. package/dist/server/modules/butler/butler-profile-service.js +2 -5
  44. package/dist/server/modules/butler/butler-profile-service.js.map +1 -1
  45. package/dist/server/modules/butler/butler-project-service.d.ts +3 -1
  46. package/dist/server/modules/butler/butler-project-service.js +7 -1
  47. package/dist/server/modules/butler/butler-project-service.js.map +1 -1
  48. package/dist/server/modules/butler/butler-session-service.d.ts +3 -1
  49. package/dist/server/modules/butler/butler-session-service.js +12 -1
  50. package/dist/server/modules/butler/butler-session-service.js.map +1 -1
  51. package/dist/server/modules/butler/butler-session-summary-service.js +2 -1
  52. package/dist/server/modules/butler/butler-session-summary-service.js.map +1 -1
  53. package/dist/server/modules/butler/butler-workspace-context.d.ts +3 -0
  54. package/dist/server/modules/butler/butler-workspace-context.js +164 -44
  55. package/dist/server/modules/butler/butler-workspace-context.js.map +1 -1
  56. package/dist/server/modules/butler/patrol-execution-service.js +2 -1
  57. package/dist/server/modules/butler/patrol-execution-service.js.map +1 -1
  58. package/dist/server/modules/butler/provider-adapter-registry.d.ts +3 -0
  59. package/dist/server/modules/butler/provider-adapter-registry.js +18 -1
  60. package/dist/server/modules/butler/provider-adapter-registry.js.map +1 -1
  61. package/dist/server/modules/butler/verification-run-service.d.ts +9 -2
  62. package/dist/server/modules/butler/verification-run-service.js +188 -34
  63. package/dist/server/modules/butler/verification-run-service.js.map +1 -1
  64. package/dist/server/modules/debug-target/debug-target-controller.js +1 -1
  65. package/dist/server/modules/debug-target/debug-target-controller.js.map +1 -1
  66. package/dist/server/modules/debug-target/debug-target-service.d.ts +7 -2
  67. package/dist/server/modules/debug-target/debug-target-service.js +563 -100
  68. package/dist/server/modules/debug-target/debug-target-service.js.map +1 -1
  69. package/dist/server/modules/git/git-command-helper-client.d.ts +1 -0
  70. package/dist/server/modules/git/git-command-helper-client.js +19 -26
  71. package/dist/server/modules/git/git-command-helper-client.js.map +1 -1
  72. package/dist/server/modules/git/git-command-runner.js +19 -1
  73. package/dist/server/modules/git/git-command-runner.js.map +1 -1
  74. package/dist/server/modules/preferences/profile-service.d.ts +3 -1
  75. package/dist/server/modules/preferences/profile-service.js +74 -3
  76. package/dist/server/modules/preferences/profile-service.js.map +1 -1
  77. package/dist/server/modules/provider/provider-discovery-helper-client.d.ts +5 -3
  78. package/dist/server/modules/provider/provider-discovery-helper-client.js +129 -43
  79. package/dist/server/modules/provider/provider-discovery-helper-client.js.map +1 -1
  80. package/dist/server/modules/provider/provider-discovery-helper-process.js +44 -0
  81. package/dist/server/modules/provider/provider-discovery-helper-process.js.map +1 -1
  82. package/dist/server/modules/provider/provider-discovery-runtime.js +83 -3
  83. package/dist/server/modules/provider/provider-discovery-runtime.js.map +1 -1
  84. package/dist/server/modules/sessions/claude-runtime-helper-client.js +23 -1
  85. package/dist/server/modules/sessions/claude-runtime-helper-client.js.map +1 -1
  86. package/dist/server/modules/sessions/session-history-service.d.ts +7 -1
  87. package/dist/server/modules/sessions/session-history-service.js +251 -41
  88. package/dist/server/modules/sessions/session-history-service.js.map +1 -1
  89. package/dist/server/modules/sessions/session-live-runtime-service.d.ts +6 -0
  90. package/dist/server/modules/sessions/session-live-runtime-service.js +97 -11
  91. package/dist/server/modules/sessions/session-live-runtime-service.js.map +1 -1
  92. package/dist/server/modules/sessions/session-message-origin-utils.d.ts +12 -0
  93. package/dist/server/modules/sessions/session-message-origin-utils.js +45 -0
  94. package/dist/server/modules/sessions/session-message-origin-utils.js.map +1 -0
  95. package/dist/server/modules/sessions/session-permission-request-service.js +167 -0
  96. package/dist/server/modules/sessions/session-permission-request-service.js.map +1 -1
  97. package/dist/server/modules/skills/builtin-skill-service.js +1 -1
  98. package/dist/server/modules/skills/builtin-skill-service.js.map +1 -1
  99. package/dist/server/modules/skills/builtin-skills/codingns-assistant/SKILL.md +19 -12
  100. package/dist/server/modules/skills/builtin-skills/codingns-assistant/references/cli-workflow.md +9 -3
  101. package/dist/server/modules/tasks/task-helper-client.d.ts +5 -2
  102. package/dist/server/modules/tasks/task-helper-client.js +118 -38
  103. package/dist/server/modules/tasks/task-helper-client.js.map +1 -1
  104. package/dist/server/modules/tasks/task-helper-process.js +94 -3
  105. package/dist/server/modules/tasks/task-helper-process.js.map +1 -1
  106. package/dist/server/modules/tasks/task-types.d.ts +3 -0
  107. package/dist/server/modules/tasks/task-types.js +4 -1
  108. package/dist/server/modules/tasks/task-types.js.map +1 -1
  109. package/dist/server/modules/terminal/command-template-service.d.ts +9 -0
  110. package/dist/server/modules/terminal/command-template-service.js +87 -5
  111. package/dist/server/modules/terminal/command-template-service.js.map +1 -1
  112. package/dist/server/modules/terminal/terminal-controller.d.ts +3 -0
  113. package/dist/server/modules/terminal/terminal-controller.js +41 -0
  114. package/dist/server/modules/terminal/terminal-controller.js.map +1 -1
  115. package/dist/server/modules/workbench/workbench-service.d.ts +3 -0
  116. package/dist/server/modules/workbench/workbench-service.js +4 -3
  117. package/dist/server/modules/workbench/workbench-service.js.map +1 -1
  118. package/dist/server/modules/workbench/workspace-file-watcher.d.ts +14 -6
  119. package/dist/server/modules/workbench/workspace-file-watcher.js +267 -57
  120. package/dist/server/modules/workbench/workspace-file-watcher.js.map +1 -1
  121. package/dist/server/modules/workbench/workspace-panel-snapshot-service.d.ts +2 -0
  122. package/dist/server/modules/workbench/workspace-panel-snapshot-service.js +32 -3
  123. package/dist/server/modules/workbench/workspace-panel-snapshot-service.js.map +1 -1
  124. package/dist/server/modules/worktree/worktree-manager.d.ts +9 -1
  125. package/dist/server/modules/worktree/worktree-manager.js +9 -1
  126. package/dist/server/modules/worktree/worktree-manager.js.map +1 -1
  127. package/dist/server/routes/assistant.js +19 -0
  128. package/dist/server/routes/assistant.js.map +1 -1
  129. package/dist/server/routes/butler.js +5 -0
  130. package/dist/server/routes/butler.js.map +1 -1
  131. package/dist/server/server/create-server.d.ts +8 -0
  132. package/dist/server/server/create-server.js +36 -13
  133. package/dist/server/server/create-server.js.map +1 -1
  134. package/dist/server/storage/repositories/assistant-automation-run-repository.d.ts +12 -0
  135. package/dist/server/storage/repositories/assistant-automation-run-repository.js +139 -0
  136. package/dist/server/storage/repositories/assistant-automation-run-repository.js.map +1 -0
  137. package/dist/server/storage/repositories/assistant-automation-task-repository.d.ts +15 -0
  138. package/dist/server/storage/repositories/assistant-automation-task-repository.js +173 -0
  139. package/dist/server/storage/repositories/assistant-automation-task-repository.js.map +1 -0
  140. package/dist/server/storage/repositories/assistant-sandbox-workspace-repository.d.ts +17 -0
  141. package/dist/server/storage/repositories/assistant-sandbox-workspace-repository.js +164 -0
  142. package/dist/server/storage/repositories/assistant-sandbox-workspace-repository.js.map +1 -0
  143. package/dist/server/storage/repositories/butler-control-session-repository.js +27 -3
  144. package/dist/server/storage/repositories/butler-control-session-repository.js.map +1 -1
  145. package/dist/server/storage/repositories/butler-control-timer-repository.d.ts +15 -0
  146. package/dist/server/storage/repositories/butler-control-timer-repository.js +157 -0
  147. package/dist/server/storage/repositories/butler-control-timer-repository.js.map +1 -0
  148. package/dist/server/storage/repositories/user-preference-profile-repository.js +6 -3
  149. package/dist/server/storage/repositories/user-preference-profile-repository.js.map +1 -1
  150. package/dist/server/storage/sqlite/client.js +239 -2
  151. package/dist/server/storage/sqlite/client.js.map +1 -1
  152. package/dist/server/storage/sqlite/schema.sql +107 -1
  153. package/dist/server/types/domain.d.ts +89 -2
  154. package/dist/server/ws/workbench-ws-hub.d.ts +14 -8
  155. package/dist/server/ws/workbench-ws-hub.js +299 -163
  156. package/dist/server/ws/workbench-ws-hub.js.map +1 -1
  157. package/node_modules/@codingns/session-sync-core/dist/providers/claude-code.d.ts +4 -1
  158. package/node_modules/@codingns/session-sync-core/dist/providers/claude-code.js +111 -3
  159. package/node_modules/@codingns/session-sync-core/dist/providers/claude-code.js.map +1 -1
  160. package/node_modules/@codingns/session-sync-core/dist/providers/codex.d.ts +6 -1
  161. package/node_modules/@codingns/session-sync-core/dist/providers/codex.js +306 -31
  162. package/node_modules/@codingns/session-sync-core/dist/providers/codex.js.map +1 -1
  163. package/node_modules/@codingns/session-sync-core/dist/providers/gemini.d.ts +5 -1
  164. package/node_modules/@codingns/session-sync-core/dist/providers/gemini.js +187 -26
  165. package/node_modules/@codingns/session-sync-core/dist/providers/gemini.js.map +1 -1
  166. package/node_modules/@codingns/session-sync-core/dist/providers/kimi.d.ts +4 -1
  167. package/node_modules/@codingns/session-sync-core/dist/providers/kimi.js +98 -1
  168. package/node_modules/@codingns/session-sync-core/dist/providers/kimi.js.map +1 -1
  169. package/node_modules/@codingns/session-sync-core/dist/providers/opencode.d.ts +2 -0
  170. package/node_modules/@codingns/session-sync-core/dist/providers/opencode.js +71 -8
  171. package/node_modules/@codingns/session-sync-core/dist/providers/opencode.js.map +1 -1
  172. package/node_modules/@codingns/session-sync-core/dist/providers/utils.d.ts +1 -0
  173. package/node_modules/@codingns/session-sync-core/dist/providers/utils.js +4 -1
  174. package/node_modules/@codingns/session-sync-core/dist/providers/utils.js.map +1 -1
  175. package/node_modules/@codingns/session-sync-core/dist/runtime/claude-runtime.js +44 -0
  176. package/node_modules/@codingns/session-sync-core/dist/runtime/claude-runtime.js.map +1 -1
  177. package/node_modules/@codingns/session-sync-core/dist/runtime/codex-runtime.js +9 -3
  178. package/node_modules/@codingns/session-sync-core/dist/runtime/codex-runtime.js.map +1 -1
  179. package/node_modules/@codingns/session-sync-core/dist/runtime/types.d.ts +1 -0
  180. package/node_modules/@codingns/session-sync-core/dist/services.js +17 -8
  181. package/node_modules/@codingns/session-sync-core/dist/services.js.map +1 -1
  182. package/node_modules/@codingns/session-sync-core/dist/types.d.ts +4 -0
  183. package/package.json +1 -1
  184. package/dist/public/assets/index-BlOinYqR.js +0 -122
  185. package/dist/public/assets/index-Dg_7g6lA.css +0 -1
@@ -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