@jingyi0605/codingns 0.1.3 → 0.1.5

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 (224) hide show
  1. package/bin/codingns.mjs +47 -8
  2. package/dist/public/assets/{TerminalPage-Nq5sPc5c.js → TerminalPage-4p6EBqrR.js} +19 -19
  3. package/dist/public/assets/gemini-D4G1NbrE.png +0 -0
  4. package/dist/public/assets/index-CxeghocY.css +1 -0
  5. package/dist/public/assets/index-DXusStl0.js +108 -0
  6. package/dist/public/assets/kimi-BWNNSh7e.png +0 -0
  7. package/dist/public/index.html +2 -2
  8. package/dist/server/config/env.d.ts +7 -0
  9. package/dist/server/config/env.js +147 -1
  10. package/dist/server/config/env.js.map +1 -1
  11. package/dist/server/config/opencode-system-probe-helper-process.d.ts +24 -0
  12. package/dist/server/config/opencode-system-probe-helper-process.js +70 -5
  13. package/dist/server/config/opencode-system-probe-helper-process.js.map +1 -1
  14. package/dist/server/modules/auth/auth-service.d.ts +7 -1
  15. package/dist/server/modules/auth/auth-service.js +23 -3
  16. package/dist/server/modules/auth/auth-service.js.map +1 -1
  17. package/dist/server/modules/bootstrap/bootstrap-service.d.ts +3 -1
  18. package/dist/server/modules/bootstrap/bootstrap-service.js +7 -2
  19. package/dist/server/modules/bootstrap/bootstrap-service.js.map +1 -1
  20. package/dist/server/modules/butler/butler-action-context-service.d.ts +30 -0
  21. package/dist/server/modules/butler/butler-action-context-service.js +108 -0
  22. package/dist/server/modules/butler/butler-action-context-service.js.map +1 -0
  23. package/dist/server/modules/butler/butler-auth-service.d.ts +17 -0
  24. package/dist/server/modules/butler/butler-auth-service.js +91 -0
  25. package/dist/server/modules/butler/butler-auth-service.js.map +1 -0
  26. package/dist/server/modules/butler/butler-control-action-service.d.ts +65 -0
  27. package/dist/server/modules/butler/butler-control-action-service.js +296 -0
  28. package/dist/server/modules/butler/butler-control-action-service.js.map +1 -0
  29. package/dist/server/modules/butler/butler-control-session-service.d.ts +55 -0
  30. package/dist/server/modules/butler/butler-control-session-service.js +367 -0
  31. package/dist/server/modules/butler/butler-control-session-service.js.map +1 -0
  32. package/dist/server/modules/butler/butler-controller.d.ts +367 -0
  33. package/dist/server/modules/butler/butler-controller.js +475 -0
  34. package/dist/server/modules/butler/butler-controller.js.map +1 -0
  35. package/dist/server/modules/butler/butler-follow-up-evaluation-instruction-adapter.d.ts +34 -0
  36. package/dist/server/modules/butler/butler-follow-up-evaluation-instruction-adapter.js +77 -0
  37. package/dist/server/modules/butler/butler-follow-up-evaluation-instruction-adapter.js.map +1 -0
  38. package/dist/server/modules/butler/butler-follow-up-scheduler.d.ts +23 -0
  39. package/dist/server/modules/butler/butler-follow-up-scheduler.js +57 -0
  40. package/dist/server/modules/butler/butler-follow-up-scheduler.js.map +1 -0
  41. package/dist/server/modules/butler/butler-follow-up-service.d.ts +86 -0
  42. package/dist/server/modules/butler/butler-follow-up-service.js +948 -0
  43. package/dist/server/modules/butler/butler-follow-up-service.js.map +1 -0
  44. package/dist/server/modules/butler/butler-inbox-service.d.ts +35 -0
  45. package/dist/server/modules/butler/butler-inbox-service.js +136 -0
  46. package/dist/server/modules/butler/butler-inbox-service.js.map +1 -0
  47. package/dist/server/modules/butler/butler-notification-service.d.ts +12 -0
  48. package/dist/server/modules/butler/butler-notification-service.js +45 -0
  49. package/dist/server/modules/butler/butler-notification-service.js.map +1 -0
  50. package/dist/server/modules/butler/butler-profile-service.d.ts +26 -0
  51. package/dist/server/modules/butler/butler-profile-service.js +529 -0
  52. package/dist/server/modules/butler/butler-profile-service.js.map +1 -0
  53. package/dist/server/modules/butler/butler-project-service.d.ts +48 -0
  54. package/dist/server/modules/butler/butler-project-service.js +253 -0
  55. package/dist/server/modules/butler/butler-project-service.js.map +1 -0
  56. package/dist/server/modules/butler/butler-session-service.d.ts +79 -0
  57. package/dist/server/modules/butler/butler-session-service.js +503 -0
  58. package/dist/server/modules/butler/butler-session-service.js.map +1 -0
  59. package/dist/server/modules/butler/butler-session-summary-service.d.ts +55 -0
  60. package/dist/server/modules/butler/butler-session-summary-service.js +382 -0
  61. package/dist/server/modules/butler/butler-session-summary-service.js.map +1 -0
  62. package/dist/server/modules/butler/context-aggregator.d.ts +187 -0
  63. package/dist/server/modules/butler/context-aggregator.js +807 -0
  64. package/dist/server/modules/butler/context-aggregator.js.map +1 -0
  65. package/dist/server/modules/butler/instruction-adapter.d.ts +28 -0
  66. package/dist/server/modules/butler/instruction-adapter.js +101 -0
  67. package/dist/server/modules/butler/instruction-adapter.js.map +1 -0
  68. package/dist/server/modules/butler/patrol-execution-service.d.ts +47 -0
  69. package/dist/server/modules/butler/patrol-execution-service.js +347 -0
  70. package/dist/server/modules/butler/patrol-execution-service.js.map +1 -0
  71. package/dist/server/modules/butler/patrol-plan-service.d.ts +54 -0
  72. package/dist/server/modules/butler/patrol-plan-service.js +272 -0
  73. package/dist/server/modules/butler/patrol-plan-service.js.map +1 -0
  74. package/dist/server/modules/butler/patrol-run-service.d.ts +60 -0
  75. package/dist/server/modules/butler/patrol-run-service.js +185 -0
  76. package/dist/server/modules/butler/patrol-run-service.js.map +1 -0
  77. package/dist/server/modules/butler/patrol-scheduler.d.ts +36 -0
  78. package/dist/server/modules/butler/patrol-scheduler.js +99 -0
  79. package/dist/server/modules/butler/patrol-scheduler.js.map +1 -0
  80. package/dist/server/modules/butler/project-memory-service.d.ts +30 -0
  81. package/dist/server/modules/butler/project-memory-service.js +103 -0
  82. package/dist/server/modules/butler/project-memory-service.js.map +1 -0
  83. package/dist/server/modules/butler/provider-adapter-registry.d.ts +61 -0
  84. package/dist/server/modules/butler/provider-adapter-registry.js +430 -0
  85. package/dist/server/modules/butler/provider-adapter-registry.js.map +1 -0
  86. package/dist/server/modules/butler/session-summary-instruction-adapter.d.ts +28 -0
  87. package/dist/server/modules/butler/session-summary-instruction-adapter.js +79 -0
  88. package/dist/server/modules/butler/session-summary-instruction-adapter.js.map +1 -0
  89. package/dist/server/modules/butler/session-summary-scheduler.d.ts +23 -0
  90. package/dist/server/modules/butler/session-summary-scheduler.js +57 -0
  91. package/dist/server/modules/butler/session-summary-scheduler.js.map +1 -0
  92. package/dist/server/modules/butler/verification-run-service.d.ts +73 -0
  93. package/dist/server/modules/butler/verification-run-service.js +633 -0
  94. package/dist/server/modules/butler/verification-run-service.js.map +1 -0
  95. package/dist/server/modules/demo/demo-cleanup-service.d.ts +41 -0
  96. package/dist/server/modules/demo/demo-cleanup-service.js +111 -0
  97. package/dist/server/modules/demo/demo-cleanup-service.js.map +1 -0
  98. package/dist/server/modules/preferences/profile-service.js +8 -2
  99. package/dist/server/modules/preferences/profile-service.js.map +1 -1
  100. package/dist/server/modules/sessions/claude-runtime-helper-process.js +1 -1
  101. package/dist/server/modules/sessions/claude-runtime-helper-process.js.map +1 -1
  102. package/dist/server/modules/sessions/codex-app-server-helper-client.d.ts +5 -1
  103. package/dist/server/modules/sessions/codex-app-server-helper-client.js +10 -2
  104. package/dist/server/modules/sessions/codex-app-server-helper-client.js.map +1 -1
  105. package/dist/server/modules/sessions/session-controller.d.ts +3 -1
  106. package/dist/server/modules/sessions/session-controller.js +11 -2
  107. package/dist/server/modules/sessions/session-controller.js.map +1 -1
  108. package/dist/server/modules/sessions/session-history-service.d.ts +14 -1
  109. package/dist/server/modules/sessions/session-history-service.js +291 -30
  110. package/dist/server/modules/sessions/session-history-service.js.map +1 -1
  111. package/dist/server/modules/sessions/session-live-runtime-service.d.ts +25 -2
  112. package/dist/server/modules/sessions/session-live-runtime-service.js +526 -158
  113. package/dist/server/modules/sessions/session-live-runtime-service.js.map +1 -1
  114. package/dist/server/modules/sessions/session-provider-error-mapper.js +28 -0
  115. package/dist/server/modules/sessions/session-provider-error-mapper.js.map +1 -1
  116. package/dist/server/modules/terminal/terminal-service.js +2 -2
  117. package/dist/server/modules/terminal/terminal-service.js.map +1 -1
  118. package/dist/server/modules/workbench/workbench-service.d.ts +7 -1
  119. package/dist/server/modules/workbench/workbench-service.js +31 -7
  120. package/dist/server/modules/workbench/workbench-service.js.map +1 -1
  121. package/dist/server/routes/butler.d.ts +3 -0
  122. package/dist/server/routes/butler.js +54 -0
  123. package/dist/server/routes/butler.js.map +1 -0
  124. package/dist/server/routes/sessions.d.ts +1 -0
  125. package/dist/server/routes/sessions.js +12 -3
  126. package/dist/server/routes/sessions.js.map +1 -1
  127. package/dist/server/server/create-server.d.ts +61 -0
  128. package/dist/server/server/create-server.js +180 -10
  129. package/dist/server/server/create-server.js.map +1 -1
  130. package/dist/server/storage/repositories/butler-control-event-repository.d.ts +8 -0
  131. package/dist/server/storage/repositories/butler-control-event-repository.js +78 -0
  132. package/dist/server/storage/repositories/butler-control-event-repository.js.map +1 -0
  133. package/dist/server/storage/repositories/butler-control-session-repository.d.ts +11 -0
  134. package/dist/server/storage/repositories/butler-control-session-repository.js +86 -0
  135. package/dist/server/storage/repositories/butler-control-session-repository.js.map +1 -0
  136. package/dist/server/storage/repositories/butler-follow-up-task-repository.d.ts +16 -0
  137. package/dist/server/storage/repositories/butler-follow-up-task-repository.js +252 -0
  138. package/dist/server/storage/repositories/butler-follow-up-task-repository.js.map +1 -0
  139. package/dist/server/storage/repositories/butler-inbox-item-repository.d.ts +15 -0
  140. package/dist/server/storage/repositories/butler-inbox-item-repository.js +111 -0
  141. package/dist/server/storage/repositories/butler-inbox-item-repository.js.map +1 -0
  142. package/dist/server/storage/repositories/butler-notification-archive-repository.d.ts +9 -0
  143. package/dist/server/storage/repositories/butler-notification-archive-repository.js +48 -0
  144. package/dist/server/storage/repositories/butler-notification-archive-repository.js.map +1 -0
  145. package/dist/server/storage/repositories/butler-profile-repository.d.ts +9 -0
  146. package/dist/server/storage/repositories/butler-profile-repository.js +86 -0
  147. package/dist/server/storage/repositories/butler-profile-repository.js.map +1 -0
  148. package/dist/server/storage/repositories/butler-project-repository.d.ts +14 -0
  149. package/dist/server/storage/repositories/butler-project-repository.js +140 -0
  150. package/dist/server/storage/repositories/butler-project-repository.js.map +1 -0
  151. package/dist/server/storage/repositories/butler-session-repository.d.ts +11 -0
  152. package/dist/server/storage/repositories/butler-session-repository.js +106 -0
  153. package/dist/server/storage/repositories/butler-session-repository.js.map +1 -0
  154. package/dist/server/storage/repositories/butler-session-summary-state-repository.d.ts +8 -0
  155. package/dist/server/storage/repositories/butler-session-summary-state-repository.js +62 -0
  156. package/dist/server/storage/repositories/butler-session-summary-state-repository.js.map +1 -0
  157. package/dist/server/storage/repositories/patrol-plan-repository.d.ts +27 -0
  158. package/dist/server/storage/repositories/patrol-plan-repository.js +119 -0
  159. package/dist/server/storage/repositories/patrol-plan-repository.js.map +1 -0
  160. package/dist/server/storage/repositories/patrol-run-repository.d.ts +28 -0
  161. package/dist/server/storage/repositories/patrol-run-repository.js +121 -0
  162. package/dist/server/storage/repositories/patrol-run-repository.js.map +1 -0
  163. package/dist/server/storage/repositories/project-memory-repository.d.ts +15 -0
  164. package/dist/server/storage/repositories/project-memory-repository.js +150 -0
  165. package/dist/server/storage/repositories/project-memory-repository.js.map +1 -0
  166. package/dist/server/storage/repositories/session-checkpoint-repository.d.ts +9 -0
  167. package/dist/server/storage/repositories/session-checkpoint-repository.js +72 -0
  168. package/dist/server/storage/repositories/session-checkpoint-repository.js.map +1 -0
  169. package/dist/server/storage/repositories/session-message-origin-repository.d.ts +10 -0
  170. package/dist/server/storage/repositories/session-message-origin-repository.js +93 -0
  171. package/dist/server/storage/repositories/session-message-origin-repository.js.map +1 -0
  172. package/dist/server/storage/repositories/verification-run-repository.d.ts +29 -0
  173. package/dist/server/storage/repositories/verification-run-repository.js +125 -0
  174. package/dist/server/storage/repositories/verification-run-repository.js.map +1 -0
  175. package/dist/server/storage/sqlite/client.js +39 -0
  176. package/dist/server/storage/sqlite/client.js.map +1 -1
  177. package/dist/server/storage/sqlite/schema.sql +324 -0
  178. package/dist/server/types/domain.d.ts +261 -1
  179. package/dist/server/ws/ws-server.d.ts +2 -1
  180. package/dist/server/ws/ws-server.js +2 -1
  181. package/dist/server/ws/ws-server.js.map +1 -1
  182. package/node_modules/@codingns/session-sync-core/dist/index.d.ts +4 -0
  183. package/node_modules/@codingns/session-sync-core/dist/index.js +4 -0
  184. package/node_modules/@codingns/session-sync-core/dist/index.js.map +1 -1
  185. package/node_modules/@codingns/session-sync-core/dist/kimi-message-normalizer.d.ts +18 -0
  186. package/node_modules/@codingns/session-sync-core/dist/kimi-message-normalizer.js +659 -0
  187. package/node_modules/@codingns/session-sync-core/dist/kimi-message-normalizer.js.map +1 -0
  188. package/node_modules/@codingns/session-sync-core/dist/kimi-shared.d.ts +11 -0
  189. package/node_modules/@codingns/session-sync-core/dist/kimi-shared.js +72 -0
  190. package/node_modules/@codingns/session-sync-core/dist/kimi-shared.js.map +1 -0
  191. package/node_modules/@codingns/session-sync-core/dist/patch-builder.d.ts +8 -0
  192. package/node_modules/@codingns/session-sync-core/dist/patch-builder.js +89 -0
  193. package/node_modules/@codingns/session-sync-core/dist/patch-builder.js.map +1 -1
  194. package/node_modules/@codingns/session-sync-core/dist/providers/codex.js +4 -1
  195. package/node_modules/@codingns/session-sync-core/dist/providers/codex.js.map +1 -1
  196. package/node_modules/@codingns/session-sync-core/dist/providers/gemini.d.ts +41 -0
  197. package/node_modules/@codingns/session-sync-core/dist/providers/gemini.js +1086 -0
  198. package/node_modules/@codingns/session-sync-core/dist/providers/gemini.js.map +1 -0
  199. package/node_modules/@codingns/session-sync-core/dist/providers/kimi.d.ts +29 -0
  200. package/node_modules/@codingns/session-sync-core/dist/providers/kimi.js +578 -0
  201. package/node_modules/@codingns/session-sync-core/dist/providers/kimi.js.map +1 -0
  202. package/node_modules/@codingns/session-sync-core/dist/providers/opencode.js +2 -1
  203. package/node_modules/@codingns/session-sync-core/dist/providers/opencode.js.map +1 -1
  204. package/node_modules/@codingns/session-sync-core/dist/providers/utils.js +30 -2
  205. package/node_modules/@codingns/session-sync-core/dist/providers/utils.js.map +1 -1
  206. package/node_modules/@codingns/session-sync-core/dist/runtime/active-run-registry.d.ts +2 -0
  207. package/node_modules/@codingns/session-sync-core/dist/runtime/active-run-registry.js +43 -5
  208. package/node_modules/@codingns/session-sync-core/dist/runtime/active-run-registry.js.map +1 -1
  209. package/node_modules/@codingns/session-sync-core/dist/runtime/codex-runtime.d.ts +2 -0
  210. package/node_modules/@codingns/session-sync-core/dist/runtime/codex-runtime.js +320 -69
  211. package/node_modules/@codingns/session-sync-core/dist/runtime/codex-runtime.js.map +1 -1
  212. package/node_modules/@codingns/session-sync-core/dist/runtime/gemini-runtime.d.ts +21 -0
  213. package/node_modules/@codingns/session-sync-core/dist/runtime/gemini-runtime.js +537 -0
  214. package/node_modules/@codingns/session-sync-core/dist/runtime/gemini-runtime.js.map +1 -0
  215. package/node_modules/@codingns/session-sync-core/dist/runtime/kimi-runtime.d.ts +38 -0
  216. package/node_modules/@codingns/session-sync-core/dist/runtime/kimi-runtime.js +911 -0
  217. package/node_modules/@codingns/session-sync-core/dist/runtime/kimi-runtime.js.map +1 -0
  218. package/node_modules/@codingns/session-sync-core/dist/sqlite/node-sqlite.d.ts +6 -0
  219. package/node_modules/@codingns/session-sync-core/dist/sqlite/node-sqlite.js +9 -0
  220. package/node_modules/@codingns/session-sync-core/dist/sqlite/node-sqlite.js.map +1 -0
  221. package/node_modules/@codingns/session-sync-core/package.json +8 -0
  222. package/package.json +1 -1
  223. package/dist/public/assets/index-9hnprhO7.css +0 -1
  224. package/dist/public/assets/index-BTpmuKhG.js +0 -108
@@ -0,0 +1,73 @@
1
+ import type { ButlerProjectRepository } from "../../storage/repositories/butler-project-repository.js";
2
+ import type { ButlerSessionRepository } from "../../storage/repositories/butler-session-repository.js";
3
+ import type { SessionCheckpointRepository } from "../../storage/repositories/session-checkpoint-repository.js";
4
+ import type { VerificationRunRepository } from "../../storage/repositories/verification-run-repository.js";
5
+ import type { VerificationRunStatus, VerificationType } from "../../types/domain.js";
6
+ interface CommandExecutionInput {
7
+ command: string;
8
+ args: string[];
9
+ cwd: string;
10
+ env?: NodeJS.ProcessEnv;
11
+ timeoutMs: number;
12
+ }
13
+ interface CommandExecutionResult {
14
+ exitCode: number;
15
+ stdout: string;
16
+ stderr: string;
17
+ }
18
+ interface HealthExecutionResult {
19
+ statusCode: number;
20
+ ok: boolean;
21
+ responseText: string;
22
+ }
23
+ interface VerificationRunServiceOptions {
24
+ now?: () => string;
25
+ runCommand?: (input: CommandExecutionInput) => Promise<CommandExecutionResult>;
26
+ runHealthCheck?: (url: string, timeoutMs: number) => Promise<HealthExecutionResult>;
27
+ }
28
+ export interface VerificationRunView {
29
+ id: string;
30
+ projectId: string;
31
+ butlerSessionId: string | null;
32
+ sourcePatrolRunId: string | null;
33
+ verificationType: VerificationType;
34
+ status: VerificationRunStatus;
35
+ targetRef: string | null;
36
+ spec: Record<string, unknown>;
37
+ artifactRefs: Array<Record<string, unknown>>;
38
+ result: Record<string, unknown>;
39
+ summary: string | null;
40
+ startedAt: string | null;
41
+ finishedAt: string | null;
42
+ createdAt: string;
43
+ }
44
+ export interface StartVerificationRunInput {
45
+ verificationType?: VerificationType;
46
+ targetRef?: string | null;
47
+ butlerSessionId?: string | null;
48
+ sourcePatrolRunId?: string | null;
49
+ spec?: Record<string, unknown>;
50
+ }
51
+ export declare class VerificationRunService {
52
+ private readonly butlerProjectRepository;
53
+ private readonly butlerSessionRepository;
54
+ private readonly sessionCheckpointRepository;
55
+ private readonly verificationRunRepository;
56
+ private readonly now;
57
+ private readonly runCommand;
58
+ private readonly runHealthCheck;
59
+ constructor(butlerProjectRepository: ButlerProjectRepository, butlerSessionRepository: ButlerSessionRepository, sessionCheckpointRepository: SessionCheckpointRepository, verificationRunRepository: VerificationRunRepository, options?: VerificationRunServiceOptions);
60
+ listRuns(projectId: string, filters?: {
61
+ status?: VerificationRunStatus;
62
+ verificationType?: VerificationType;
63
+ }): VerificationRunView[];
64
+ getRun(projectId: string, runId: string): VerificationRunView;
65
+ startRun(projectId: string, input: StartVerificationRunInput): Promise<VerificationRunView>;
66
+ private executeVerification;
67
+ private writeVerificationCheckpoint;
68
+ private getProjectOrThrow;
69
+ private getProjectSessionOrThrow;
70
+ private getRunRecordOrThrow;
71
+ private updateRunRecord;
72
+ }
73
+ export {};
@@ -0,0 +1,633 @@
1
+ import { spawn } from "node:child_process";
2
+ import path from "node:path";
3
+ import { AppError } from "../../shared/errors/app-error.js";
4
+ import { resolveCommandLaunch } from "../../shared/utils/command-launch.js";
5
+ import { createId } from "../../shared/utils/id.js";
6
+ import { nowIso } from "../../shared/utils/time.js";
7
+ const DEFAULT_COMMAND_TIMEOUT_MS = 60_000;
8
+ const DEFAULT_HEALTH_TIMEOUT_MS = 10_000;
9
+ const MAX_PREVIEW_LENGTH = 2_000;
10
+ export class VerificationRunService {
11
+ butlerProjectRepository;
12
+ butlerSessionRepository;
13
+ sessionCheckpointRepository;
14
+ verificationRunRepository;
15
+ now;
16
+ runCommand;
17
+ runHealthCheck;
18
+ constructor(butlerProjectRepository, butlerSessionRepository, sessionCheckpointRepository, verificationRunRepository, options = {}) {
19
+ this.butlerProjectRepository = butlerProjectRepository;
20
+ this.butlerSessionRepository = butlerSessionRepository;
21
+ this.sessionCheckpointRepository = sessionCheckpointRepository;
22
+ this.verificationRunRepository = verificationRunRepository;
23
+ this.now = options.now ?? nowIso;
24
+ this.runCommand = options.runCommand ?? runCommandExecution;
25
+ this.runHealthCheck = options.runHealthCheck ?? runHttpHealthCheck;
26
+ }
27
+ listRuns(projectId, filters) {
28
+ this.getProjectOrThrow(projectId);
29
+ return this.verificationRunRepository
30
+ .listByProject(projectId, filters)
31
+ .map(mapVerificationRunRecord);
32
+ }
33
+ getRun(projectId, runId) {
34
+ this.getProjectOrThrow(projectId);
35
+ const record = this.getRunRecordOrThrow(runId);
36
+ if (record.projectId !== projectId) {
37
+ throw new AppError({
38
+ statusCode: 404,
39
+ errorCode: "VERIFICATION_RUN_NOT_FOUND",
40
+ detail: "当前项目下不存在该验证记录"
41
+ });
42
+ }
43
+ return mapVerificationRunRecord(record);
44
+ }
45
+ async startRun(projectId, input) {
46
+ const project = this.getProjectOrThrow(projectId);
47
+ const verificationType = normalizeVerificationType(input.verificationType);
48
+ const targetRef = normalizeNullableText(input.targetRef) ?? null;
49
+ const spec = normalizeSpec(input.spec);
50
+ const plan = prepareVerificationPlan(project, verificationType, targetRef, spec);
51
+ const butlerSession = input.butlerSessionId
52
+ ? this.getProjectSessionOrThrow(project.id, input.butlerSessionId)
53
+ : null;
54
+ if (this.verificationRunRepository.listRunningByProject(project.id).length > 0) {
55
+ throw new AppError({
56
+ statusCode: 409,
57
+ errorCode: "VERIFICATION_RUN_CONFLICT",
58
+ detail: "当前项目已有进行中的验证任务"
59
+ });
60
+ }
61
+ const startedAt = this.now();
62
+ const record = this.verificationRunRepository.create({
63
+ id: createId(),
64
+ projectId: project.id,
65
+ butlerSessionId: butlerSession?.id ?? null,
66
+ sourcePatrolRunId: normalizeNullableText(input.sourcePatrolRunId) ?? null,
67
+ verificationType,
68
+ status: "running",
69
+ targetRef,
70
+ specJson: JSON.stringify(spec),
71
+ artifactRefsJson: "[]",
72
+ resultJson: "{}",
73
+ summary: null,
74
+ startedAt,
75
+ finishedAt: null,
76
+ createdAt: startedAt
77
+ });
78
+ try {
79
+ const outcome = await this.executeVerification(plan);
80
+ const finishedAt = this.now();
81
+ const updated = this.updateRunRecord({
82
+ ...record,
83
+ status: outcome.status,
84
+ summary: outcome.summary,
85
+ artifactRefsJson: JSON.stringify(outcome.artifactRefs),
86
+ resultJson: JSON.stringify(outcome.result),
87
+ finishedAt
88
+ });
89
+ this.butlerProjectRepository.update({
90
+ ...project,
91
+ lastVerificationAt: finishedAt,
92
+ updatedAt: finishedAt,
93
+ config: {
94
+ ...project.config,
95
+ lastVerificationType: verificationType,
96
+ lastVerificationStatus: outcome.status
97
+ }
98
+ });
99
+ if (butlerSession) {
100
+ this.writeVerificationCheckpoint(butlerSession, outcome, finishedAt);
101
+ }
102
+ return mapVerificationRunRecord(updated);
103
+ }
104
+ catch (error) {
105
+ const finishedAt = this.now();
106
+ const detail = error instanceof Error ? error.message : String(error);
107
+ const failed = this.updateRunRecord({
108
+ ...record,
109
+ status: "failed",
110
+ summary: detail,
111
+ artifactRefsJson: JSON.stringify([]),
112
+ resultJson: JSON.stringify({
113
+ error: detail
114
+ }),
115
+ finishedAt
116
+ });
117
+ this.butlerProjectRepository.update({
118
+ ...project,
119
+ lastVerificationAt: finishedAt,
120
+ updatedAt: finishedAt,
121
+ config: {
122
+ ...project.config,
123
+ lastVerificationType: verificationType,
124
+ lastVerificationStatus: "failed"
125
+ }
126
+ });
127
+ if (butlerSession) {
128
+ this.writeVerificationCheckpoint(butlerSession, {
129
+ status: "failed",
130
+ summary: detail,
131
+ artifactRefs: [],
132
+ result: {
133
+ error: detail
134
+ }
135
+ }, finishedAt);
136
+ }
137
+ return mapVerificationRunRecord(failed);
138
+ }
139
+ }
140
+ async executeVerification(plan) {
141
+ if (plan.kind === "command") {
142
+ const result = await this.runCommand({
143
+ command: plan.command,
144
+ args: plan.args,
145
+ cwd: plan.cwd,
146
+ env: plan.env,
147
+ timeoutMs: plan.timeoutMs
148
+ });
149
+ const passed = result.exitCode === plan.expectedExitCode;
150
+ const summary = passed
151
+ ? `${plan.summaryLabel}通过:命令以退出码 ${result.exitCode} 结束`
152
+ : `${plan.summaryLabel}失败:命令退出码为 ${result.exitCode},期望为 ${plan.expectedExitCode}`;
153
+ return {
154
+ status: passed ? "passed" : "failed",
155
+ summary,
156
+ artifactRefs: buildCommandArtifactRefs(result.stdout, result.stderr),
157
+ result: {
158
+ command: plan.command,
159
+ args: plan.args,
160
+ cwd: plan.cwd,
161
+ exitCode: result.exitCode,
162
+ expectedExitCode: plan.expectedExitCode,
163
+ stdoutPreview: truncateText(result.stdout),
164
+ stderrPreview: truncateText(result.stderr)
165
+ }
166
+ };
167
+ }
168
+ const result = await this.runHealthCheck(plan.url, plan.timeoutMs);
169
+ const passed = plan.expectedStatus === null ? result.ok : result.statusCode === plan.expectedStatus;
170
+ const summary = passed
171
+ ? `健康检查通过:${plan.url} 返回 ${result.statusCode}`
172
+ : `健康检查失败:${plan.url} 返回 ${result.statusCode}`;
173
+ return {
174
+ status: passed ? "passed" : "failed",
175
+ summary,
176
+ artifactRefs: [
177
+ {
178
+ kind: "http",
179
+ url: plan.url,
180
+ statusCode: result.statusCode,
181
+ ok: result.ok
182
+ }
183
+ ],
184
+ result: {
185
+ url: plan.url,
186
+ statusCode: result.statusCode,
187
+ ok: result.ok,
188
+ expectedStatus: plan.expectedStatus,
189
+ responsePreview: truncateText(result.responseText)
190
+ }
191
+ };
192
+ }
193
+ writeVerificationCheckpoint(butlerSession, outcome, capturedAt) {
194
+ const progressState = outcome.status === "failed" ? "blocked" : outcome.status === "skipped" ? "unknown" : "done";
195
+ this.sessionCheckpointRepository.create({
196
+ id: createId(),
197
+ butlerSessionId: butlerSession.id,
198
+ checkpointSeq: this.sessionCheckpointRepository.getLatestSeq(butlerSession.id) + 1,
199
+ sourceKind: "verification",
200
+ progressState,
201
+ summary: outcome.summary,
202
+ riskFlags: outcome.status === "failed" ? [outcome.summary] : [],
203
+ nextActions: outcome.status === "failed"
204
+ ? ["检查验证结果并修复失败原因"]
205
+ : ["记录验证结论并继续推进后续任务"],
206
+ capturedAt
207
+ });
208
+ this.butlerSessionRepository.update({
209
+ ...butlerSession,
210
+ status: resolveNextSessionStatus(butlerSession.status, outcome.status),
211
+ lastSummary: outcome.summary,
212
+ lastCheckpointAt: capturedAt,
213
+ updatedAt: capturedAt
214
+ });
215
+ }
216
+ getProjectOrThrow(projectId) {
217
+ const project = this.butlerProjectRepository.findById(projectId);
218
+ if (!project) {
219
+ throw new AppError({
220
+ statusCode: 404,
221
+ errorCode: "BUTLER_PROJECT_NOT_FOUND",
222
+ detail: "代码助手项目不存在"
223
+ });
224
+ }
225
+ return project;
226
+ }
227
+ getProjectSessionOrThrow(projectId, butlerSessionId) {
228
+ const session = this.butlerSessionRepository.findById(butlerSessionId);
229
+ if (!session || session.projectId !== projectId) {
230
+ throw new AppError({
231
+ statusCode: 404,
232
+ errorCode: "BUTLER_SESSION_NOT_FOUND",
233
+ detail: "当前项目下不存在该会话",
234
+ field: "butlerSessionId"
235
+ });
236
+ }
237
+ return session;
238
+ }
239
+ getRunRecordOrThrow(runId) {
240
+ const record = this.verificationRunRepository.findById(runId);
241
+ if (!record) {
242
+ throw new AppError({
243
+ statusCode: 404,
244
+ errorCode: "VERIFICATION_RUN_NOT_FOUND",
245
+ detail: "验证记录不存在"
246
+ });
247
+ }
248
+ return record;
249
+ }
250
+ updateRunRecord(record) {
251
+ const updated = this.verificationRunRepository.update(record);
252
+ if (!updated) {
253
+ throw new AppError({
254
+ statusCode: 500,
255
+ errorCode: "VERIFICATION_RUN_UPDATE_FAILED",
256
+ detail: "验证记录更新失败"
257
+ });
258
+ }
259
+ return updated;
260
+ }
261
+ }
262
+ function mapVerificationRunRecord(record) {
263
+ return {
264
+ id: record.id,
265
+ projectId: record.projectId,
266
+ butlerSessionId: record.butlerSessionId,
267
+ sourcePatrolRunId: record.sourcePatrolRunId,
268
+ verificationType: normalizeVerificationType(record.verificationType),
269
+ status: normalizeVerificationStatus(record.status),
270
+ targetRef: record.targetRef,
271
+ spec: parseJsonObject(record.specJson),
272
+ artifactRefs: parseJsonArray(record.artifactRefsJson),
273
+ result: parseJsonObject(record.resultJson),
274
+ summary: record.summary,
275
+ startedAt: record.startedAt,
276
+ finishedAt: record.finishedAt,
277
+ createdAt: record.createdAt
278
+ };
279
+ }
280
+ function normalizeVerificationType(value) {
281
+ switch (value) {
282
+ case "test":
283
+ case "health":
284
+ case "browser":
285
+ case "visual":
286
+ case "metric":
287
+ return value;
288
+ default:
289
+ throw new AppError({
290
+ statusCode: 400,
291
+ errorCode: "INVALID_INPUT",
292
+ detail: "verificationType 不支持",
293
+ field: "verificationType"
294
+ });
295
+ }
296
+ }
297
+ function normalizeVerificationStatus(value) {
298
+ switch (value) {
299
+ case "queued":
300
+ case "running":
301
+ case "passed":
302
+ case "failed":
303
+ case "skipped":
304
+ return value;
305
+ default:
306
+ return "failed";
307
+ }
308
+ }
309
+ function normalizeSpec(spec) {
310
+ if (!spec) {
311
+ return {};
312
+ }
313
+ return { ...spec };
314
+ }
315
+ function prepareVerificationPlan(project, verificationType, targetRef, spec) {
316
+ if (verificationType === "test") {
317
+ return prepareCommandPlan(project, spec, "测试验证");
318
+ }
319
+ if (verificationType === "health") {
320
+ if (typeof spec.command === "string") {
321
+ return prepareCommandPlan(project, spec, "健康检查");
322
+ }
323
+ return {
324
+ kind: "health-url",
325
+ url: requireUrlTarget(targetRef),
326
+ timeoutMs: readTimeoutMs(spec.timeoutMs, DEFAULT_HEALTH_TIMEOUT_MS),
327
+ expectedStatus: readOptionalStatusCode(spec.expectedStatus)
328
+ };
329
+ }
330
+ throw new AppError({
331
+ statusCode: 400,
332
+ errorCode: "VERIFICATION_TYPE_UNSUPPORTED",
333
+ detail: `当前阶段暂不支持 ${verificationType} 验证`
334
+ });
335
+ }
336
+ function prepareCommandPlan(project, spec, summaryLabel) {
337
+ return {
338
+ kind: "command",
339
+ summaryLabel,
340
+ command: requireSpecString(spec, "command", "spec.command 不能为空"),
341
+ args: readStringArray(spec.args, "spec.args"),
342
+ cwd: resolveVerificationCwd(project.repoRoot, spec.cwd, "spec.cwd"),
343
+ env: readEnvironment(spec.env),
344
+ timeoutMs: readTimeoutMs(spec.timeoutMs, DEFAULT_COMMAND_TIMEOUT_MS),
345
+ expectedExitCode: readExpectedExitCode(spec.expectedExitCode)
346
+ };
347
+ }
348
+ function requireSpecString(spec, field, detail) {
349
+ const value = normalizeNullableText(spec[field]);
350
+ if (!value) {
351
+ throw new AppError({
352
+ statusCode: 400,
353
+ errorCode: "INVALID_INPUT",
354
+ detail,
355
+ field
356
+ });
357
+ }
358
+ return value;
359
+ }
360
+ function readStringArray(value, field) {
361
+ if (value === undefined) {
362
+ return [];
363
+ }
364
+ if (!Array.isArray(value) || value.some((item) => typeof item !== "string")) {
365
+ throw new AppError({
366
+ statusCode: 400,
367
+ errorCode: "INVALID_INPUT",
368
+ detail: `${field} 必须是字符串数组`,
369
+ field
370
+ });
371
+ }
372
+ return value.map((item) => item.trim());
373
+ }
374
+ function readEnvironment(value) {
375
+ if (value === undefined) {
376
+ return undefined;
377
+ }
378
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
379
+ throw new AppError({
380
+ statusCode: 400,
381
+ errorCode: "INVALID_INPUT",
382
+ detail: "spec.env 必须是键值对象",
383
+ field: "spec.env"
384
+ });
385
+ }
386
+ const env = {};
387
+ for (const [key, envValue] of Object.entries(value)) {
388
+ if (typeof envValue !== "string") {
389
+ throw new AppError({
390
+ statusCode: 400,
391
+ errorCode: "INVALID_INPUT",
392
+ detail: "spec.env 的值必须是字符串",
393
+ field: "spec.env"
394
+ });
395
+ }
396
+ env[key] = envValue;
397
+ }
398
+ return env;
399
+ }
400
+ function readTimeoutMs(value, fallback) {
401
+ if (value === undefined || value === null) {
402
+ return fallback;
403
+ }
404
+ const numericValue = Number(value);
405
+ if (!Number.isFinite(numericValue) || numericValue <= 0) {
406
+ throw new AppError({
407
+ statusCode: 400,
408
+ errorCode: "INVALID_INPUT",
409
+ detail: "timeoutMs 必须是正整数",
410
+ field: "spec.timeoutMs"
411
+ });
412
+ }
413
+ return Math.trunc(numericValue);
414
+ }
415
+ function readExpectedExitCode(value) {
416
+ if (value === undefined || value === null) {
417
+ return 0;
418
+ }
419
+ const numericValue = Number(value);
420
+ if (!Number.isInteger(numericValue)) {
421
+ throw new AppError({
422
+ statusCode: 400,
423
+ errorCode: "INVALID_INPUT",
424
+ detail: "expectedExitCode 必须是整数",
425
+ field: "spec.expectedExitCode"
426
+ });
427
+ }
428
+ return numericValue;
429
+ }
430
+ function readOptionalStatusCode(value) {
431
+ if (value === undefined || value === null) {
432
+ return null;
433
+ }
434
+ const numericValue = Number(value);
435
+ if (!Number.isInteger(numericValue) || numericValue < 100 || numericValue > 599) {
436
+ throw new AppError({
437
+ statusCode: 400,
438
+ errorCode: "INVALID_INPUT",
439
+ detail: "expectedStatus 必须是合法的 HTTP 状态码",
440
+ field: "spec.expectedStatus"
441
+ });
442
+ }
443
+ return numericValue;
444
+ }
445
+ function requireUrlTarget(targetRef) {
446
+ if (!targetRef) {
447
+ throw new AppError({
448
+ statusCode: 400,
449
+ errorCode: "INVALID_INPUT",
450
+ detail: "health 验证缺少 targetRef",
451
+ field: "targetRef"
452
+ });
453
+ }
454
+ try {
455
+ const url = new URL(targetRef);
456
+ if (url.protocol !== "http:" && url.protocol !== "https:") {
457
+ throw new Error("unsupported");
458
+ }
459
+ return url.toString();
460
+ }
461
+ catch {
462
+ throw new AppError({
463
+ statusCode: 400,
464
+ errorCode: "INVALID_INPUT",
465
+ detail: "targetRef 必须是合法的 HTTP/HTTPS 地址",
466
+ field: "targetRef"
467
+ });
468
+ }
469
+ }
470
+ function resolveVerificationCwd(repoRoot, value, field) {
471
+ if (value === undefined || value === null || value === "") {
472
+ return repoRoot;
473
+ }
474
+ if (typeof value !== "string") {
475
+ throw new AppError({
476
+ statusCode: 400,
477
+ errorCode: "INVALID_INPUT",
478
+ detail: `${field} 必须是字符串`,
479
+ field
480
+ });
481
+ }
482
+ const trimmed = value.trim();
483
+ const resolved = path.isAbsolute(trimmed) ? path.resolve(trimmed) : path.resolve(repoRoot, trimmed);
484
+ const relative = path.relative(repoRoot, resolved);
485
+ const isInsideRepo = relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
486
+ if (!isInsideRepo) {
487
+ throw new AppError({
488
+ statusCode: 400,
489
+ errorCode: "INVALID_INPUT",
490
+ detail: "验证执行目录必须位于项目仓库内",
491
+ field
492
+ });
493
+ }
494
+ return resolved;
495
+ }
496
+ function normalizeNullableText(value) {
497
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
498
+ }
499
+ function parseJsonObject(raw) {
500
+ try {
501
+ const parsed = JSON.parse(raw);
502
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed)
503
+ ? parsed
504
+ : {};
505
+ }
506
+ catch {
507
+ return {};
508
+ }
509
+ }
510
+ function parseJsonArray(raw) {
511
+ try {
512
+ const parsed = JSON.parse(raw);
513
+ return Array.isArray(parsed)
514
+ ? parsed.filter((item) => !!item && typeof item === "object")
515
+ : [];
516
+ }
517
+ catch {
518
+ return [];
519
+ }
520
+ }
521
+ function buildCommandArtifactRefs(stdout, stderr) {
522
+ const refs = [];
523
+ if (stdout.trim().length > 0) {
524
+ refs.push({
525
+ kind: "stdout",
526
+ preview: truncateText(stdout)
527
+ });
528
+ }
529
+ if (stderr.trim().length > 0) {
530
+ refs.push({
531
+ kind: "stderr",
532
+ preview: truncateText(stderr)
533
+ });
534
+ }
535
+ return refs;
536
+ }
537
+ function truncateText(value) {
538
+ if (value.length <= MAX_PREVIEW_LENGTH) {
539
+ return value;
540
+ }
541
+ return `${value.slice(0, MAX_PREVIEW_LENGTH)}...`;
542
+ }
543
+ function resolveNextSessionStatus(currentStatus, verificationStatus) {
544
+ if (verificationStatus === "failed") {
545
+ return currentStatus === "closed" ? "closed" : "blocked";
546
+ }
547
+ if (currentStatus === "running" || currentStatus === "closed") {
548
+ return currentStatus;
549
+ }
550
+ if (currentStatus === "failed" || currentStatus === "blocked") {
551
+ return currentStatus;
552
+ }
553
+ return "idle";
554
+ }
555
+ async function runCommandExecution(input) {
556
+ const launch = resolveCommandLaunch(input.command, input.args);
557
+ return await new Promise((resolve, reject) => {
558
+ let stdout = "";
559
+ let stderr = "";
560
+ let completed = false;
561
+ const child = spawn(launch.command, launch.args, {
562
+ cwd: input.cwd,
563
+ env: {
564
+ ...process.env,
565
+ ...(input.env ?? {})
566
+ },
567
+ shell: launch.shell,
568
+ stdio: ["ignore", "pipe", "pipe"]
569
+ });
570
+ const finish = (callback) => {
571
+ if (completed) {
572
+ return;
573
+ }
574
+ completed = true;
575
+ clearTimeout(timer);
576
+ callback();
577
+ };
578
+ const timer = setTimeout(() => {
579
+ child.kill("SIGTERM");
580
+ finish(() => {
581
+ reject(new Error(`VERIFICATION_COMMAND_TIMEOUT:${input.command}`));
582
+ });
583
+ }, input.timeoutMs);
584
+ timer.unref?.();
585
+ child.stdout.on("data", (chunk) => {
586
+ stdout += String(chunk);
587
+ });
588
+ child.stderr.on("data", (chunk) => {
589
+ stderr += String(chunk);
590
+ });
591
+ child.on("error", (error) => {
592
+ finish(() => {
593
+ reject(new Error(`VERIFICATION_COMMAND_FAILED:${error.message}`));
594
+ });
595
+ });
596
+ child.on("close", (exitCode) => {
597
+ finish(() => {
598
+ resolve({
599
+ exitCode: exitCode ?? 1,
600
+ stdout,
601
+ stderr
602
+ });
603
+ });
604
+ });
605
+ });
606
+ }
607
+ async function runHttpHealthCheck(url, timeoutMs) {
608
+ const controller = new AbortController();
609
+ const timer = setTimeout(() => {
610
+ controller.abort();
611
+ }, timeoutMs);
612
+ timer.unref?.();
613
+ try {
614
+ const response = await fetch(url, {
615
+ method: "GET",
616
+ signal: controller.signal
617
+ });
618
+ const responseText = await response.text();
619
+ return {
620
+ statusCode: response.status,
621
+ ok: response.ok,
622
+ responseText
623
+ };
624
+ }
625
+ catch (error) {
626
+ const detail = error instanceof Error ? error.message : String(error);
627
+ throw new Error(`VERIFICATION_HEALTHCHECK_FAILED:${detail}`);
628
+ }
629
+ finally {
630
+ clearTimeout(timer);
631
+ }
632
+ }
633
+ //# sourceMappingURL=verification-run-service.js.map