@coralai/sps-cli 0.49.15 → 0.50.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 (175) hide show
  1. package/dist/commands/cardAdd.d.ts.map +1 -1
  2. package/dist/commands/cardAdd.js +47 -67
  3. package/dist/commands/cardAdd.js.map +1 -1
  4. package/dist/commands/cardMarkComplete.js +1 -1
  5. package/dist/commands/cardMarkComplete.js.map +1 -1
  6. package/dist/commands/cardMarkStarted.js +1 -1
  7. package/dist/commands/cardMarkStarted.js.map +1 -1
  8. package/dist/commands/consoleCommand.js +3 -3
  9. package/dist/commands/consoleCommand.js.map +1 -1
  10. package/dist/commands/hookCommand.d.ts +25 -0
  11. package/dist/commands/hookCommand.d.ts.map +1 -1
  12. package/dist/commands/hookCommand.js +2 -2
  13. package/dist/commands/hookCommand.js.map +1 -1
  14. package/dist/console/index.d.ts +15 -0
  15. package/dist/console/index.d.ts.map +1 -0
  16. package/dist/console/index.js +177 -0
  17. package/dist/console/index.js.map +1 -0
  18. package/dist/console/lib/lockFile.d.ts +17 -0
  19. package/dist/console/lib/lockFile.d.ts.map +1 -0
  20. package/dist/console/lib/lockFile.js +61 -0
  21. package/dist/console/lib/lockFile.js.map +1 -0
  22. package/dist/console/lib/portPicker.d.ts +3 -0
  23. package/dist/console/lib/portPicker.d.ts.map +1 -0
  24. package/dist/console/lib/portPicker.js +25 -0
  25. package/dist/console/lib/portPicker.js.map +1 -0
  26. package/dist/console/lib/resultToJson.d.ts +18 -0
  27. package/dist/console/lib/resultToJson.d.ts.map +1 -0
  28. package/dist/console/lib/resultToJson.js +19 -0
  29. package/dist/console/lib/resultToJson.js.map +1 -0
  30. package/dist/console/routes/cards.d.ts +26 -0
  31. package/dist/console/routes/cards.d.ts.map +1 -0
  32. package/dist/console/routes/cards.js +85 -0
  33. package/dist/console/routes/cards.js.map +1 -0
  34. package/dist/console/routes/chat.d.ts +36 -0
  35. package/dist/console/routes/chat.d.ts.map +1 -0
  36. package/dist/console/routes/chat.js +458 -0
  37. package/dist/console/routes/chat.js.map +1 -0
  38. package/dist/console/routes/logs.d.ts +17 -0
  39. package/dist/console/routes/logs.d.ts.map +1 -0
  40. package/dist/console/routes/logs.js +159 -0
  41. package/dist/console/routes/logs.js.map +1 -0
  42. package/dist/console/routes/pipeline.d.ts +10 -0
  43. package/dist/console/routes/pipeline.d.ts.map +1 -0
  44. package/dist/console/routes/pipeline.js +39 -0
  45. package/dist/console/routes/pipeline.js.map +1 -0
  46. package/dist/console/routes/projects.d.ts +15 -0
  47. package/dist/console/routes/projects.d.ts.map +1 -0
  48. package/dist/console/routes/projects.js +132 -0
  49. package/dist/console/routes/projects.js.map +1 -0
  50. package/dist/console/routes/skills.d.ts +4 -0
  51. package/dist/console/routes/skills.d.ts.map +1 -0
  52. package/dist/console/routes/skills.js +91 -0
  53. package/dist/console/routes/skills.js.map +1 -0
  54. package/dist/console/routes/system.d.ts +7 -0
  55. package/dist/console/routes/system.d.ts.map +1 -0
  56. package/dist/console/routes/system.js +288 -0
  57. package/dist/console/routes/system.js.map +1 -0
  58. package/dist/console/routes/workers.d.ts +5 -0
  59. package/dist/console/routes/workers.d.ts.map +1 -0
  60. package/dist/console/routes/workers.js +149 -0
  61. package/dist/console/routes/workers.js.map +1 -0
  62. package/dist/console/sse/eventBus.d.ts +25 -0
  63. package/dist/console/sse/eventBus.d.ts.map +1 -0
  64. package/dist/console/sse/eventBus.js +32 -0
  65. package/dist/console/sse/eventBus.js.map +1 -0
  66. package/dist/console/sse/projectStream.d.ts +13 -0
  67. package/dist/console/sse/projectStream.d.ts.map +1 -0
  68. package/dist/console/sse/projectStream.js +84 -0
  69. package/dist/console/sse/projectStream.js.map +1 -0
  70. package/dist/console-server/routes/projects.d.ts.map +1 -1
  71. package/dist/console-server/routes/projects.js +2 -1
  72. package/dist/console-server/routes/projects.js.map +1 -1
  73. package/dist/console-server/routes/system.js +1 -1
  74. package/dist/console-server/routes/system.js.map +1 -1
  75. package/dist/console-server/routes/workers.d.ts.map +1 -1
  76. package/dist/console-server/routes/workers.js +14 -5
  77. package/dist/console-server/routes/workers.js.map +1 -1
  78. package/dist/console-server/watchers/markerWatcher.d.ts.map +1 -1
  79. package/dist/console-server/watchers/markerWatcher.js +4 -2
  80. package/dist/console-server/watchers/markerWatcher.js.map +1 -1
  81. package/dist/infra/chokidarWatchers.d.ts +38 -0
  82. package/dist/infra/chokidarWatchers.d.ts.map +1 -0
  83. package/dist/infra/chokidarWatchers.js +213 -0
  84. package/dist/infra/chokidarWatchers.js.map +1 -0
  85. package/dist/infra/clock.d.ts +35 -0
  86. package/dist/infra/clock.d.ts.map +1 -0
  87. package/dist/infra/clock.js +45 -0
  88. package/dist/infra/clock.js.map +1 -0
  89. package/dist/infra/filesystem.d.ts +89 -0
  90. package/dist/infra/filesystem.d.ts.map +1 -0
  91. package/dist/infra/filesystem.js +247 -0
  92. package/dist/infra/filesystem.js.map +1 -0
  93. package/dist/infra/spawn.d.ts +67 -0
  94. package/dist/infra/spawn.d.ts.map +1 -0
  95. package/dist/infra/spawn.js +116 -0
  96. package/dist/infra/spawn.js.map +1 -0
  97. package/dist/infra/sseBus.d.ts +36 -0
  98. package/dist/infra/sseBus.d.ts.map +1 -0
  99. package/dist/infra/sseBus.js +72 -0
  100. package/dist/infra/sseBus.js.map +1 -0
  101. package/dist/interfaces/ACPClient.d.ts +1 -1
  102. package/dist/interfaces/ACPClient.d.ts.map +1 -1
  103. package/dist/main.js +1 -1
  104. package/dist/main.js.map +1 -1
  105. package/dist/providers/LocalACPClient.d.ts +1 -1
  106. package/dist/providers/LocalACPClient.d.ts.map +1 -1
  107. package/dist/services/CardService.d.ts +86 -0
  108. package/dist/services/CardService.d.ts.map +1 -0
  109. package/dist/services/CardService.js +313 -0
  110. package/dist/services/CardService.js.map +1 -0
  111. package/dist/services/ChatService.d.ts +62 -0
  112. package/dist/services/ChatService.d.ts.map +1 -0
  113. package/dist/services/ChatService.js +157 -0
  114. package/dist/services/ChatService.js.map +1 -0
  115. package/dist/services/LogService.d.ts +46 -0
  116. package/dist/services/LogService.d.ts.map +1 -0
  117. package/dist/services/LogService.js +185 -0
  118. package/dist/services/LogService.js.map +1 -0
  119. package/dist/services/PipelineService.d.ts +71 -0
  120. package/dist/services/PipelineService.d.ts.map +1 -0
  121. package/dist/services/PipelineService.js +349 -0
  122. package/dist/services/PipelineService.js.map +1 -0
  123. package/dist/services/ProjectService.d.ts +105 -0
  124. package/dist/services/ProjectService.d.ts.map +1 -0
  125. package/dist/services/ProjectService.js +346 -0
  126. package/dist/services/ProjectService.js.map +1 -0
  127. package/dist/services/SkillService.d.ts +38 -0
  128. package/dist/services/SkillService.d.ts.map +1 -0
  129. package/dist/services/SkillService.js +301 -0
  130. package/dist/services/SkillService.js.map +1 -0
  131. package/dist/services/WorkerService.d.ts +83 -0
  132. package/dist/services/WorkerService.d.ts.map +1 -0
  133. package/dist/services/WorkerService.js +262 -0
  134. package/dist/services/WorkerService.js.map +1 -0
  135. package/dist/services/container.d.ts +47 -0
  136. package/dist/services/container.d.ts.map +1 -0
  137. package/dist/services/container.js +77 -0
  138. package/dist/services/container.js.map +1 -0
  139. package/dist/services/defaults.d.ts +15 -0
  140. package/dist/services/defaults.d.ts.map +1 -0
  141. package/dist/services/defaults.js +11 -0
  142. package/dist/services/defaults.js.map +1 -0
  143. package/dist/services/executors.d.ts +38 -0
  144. package/dist/services/executors.d.ts.map +1 -0
  145. package/dist/services/executors.js +157 -0
  146. package/dist/services/executors.js.map +1 -0
  147. package/dist/services/ports.d.ts +17 -0
  148. package/dist/services/ports.d.ts.map +1 -0
  149. package/dist/services/ports.js +2 -0
  150. package/dist/services/ports.js.map +1 -0
  151. package/dist/shared/domainEvents.d.ts +118 -0
  152. package/dist/shared/domainEvents.d.ts.map +1 -0
  153. package/dist/shared/domainEvents.js +40 -0
  154. package/dist/shared/domainEvents.js.map +1 -0
  155. package/dist/shared/errors.d.ts +59 -0
  156. package/dist/shared/errors.d.ts.map +1 -0
  157. package/dist/shared/errors.js +79 -0
  158. package/dist/shared/errors.js.map +1 -0
  159. package/dist/shared/result.d.ts +51 -0
  160. package/dist/shared/result.d.ts.map +1 -0
  161. package/dist/shared/result.js +48 -0
  162. package/dist/shared/result.js.map +1 -0
  163. package/dist/shared/runtimePaths.d.ts +78 -0
  164. package/dist/shared/runtimePaths.d.ts.map +1 -0
  165. package/dist/shared/runtimePaths.js +179 -0
  166. package/dist/shared/runtimePaths.js.map +1 -0
  167. package/dist/shared/runtimeSchemas.d.ts +324 -0
  168. package/dist/shared/runtimeSchemas.d.ts.map +1 -0
  169. package/dist/shared/runtimeSchemas.js +124 -0
  170. package/dist/shared/runtimeSchemas.js.map +1 -0
  171. package/dist/shared/types.d.ts +12 -0
  172. package/dist/shared/types.d.ts.map +1 -0
  173. package/dist/shared/types.js +2 -0
  174. package/dist/shared/types.js.map +1 -0
  175. package/package.json +1 -1
@@ -0,0 +1,301 @@
1
+ /**
2
+ * @module services/SkillService
3
+ * @description Skill 注册 + link/unlink/freeze service
4
+ *
5
+ * @layer services
6
+ *
7
+ * 包装 core/skillStore 的能力,让 Delivery 层不再直接调 Domain。
8
+ * Phase 3 会用 SkillRegistry port 替代,当前先内部 wrap。
9
+ */
10
+ import { existsSync, readdirSync, readFileSync } from 'node:fs';
11
+ import { dirname, resolve } from 'node:path';
12
+ import { fileURLToPath } from 'node:url';
13
+ const __dirname = dirname(fileURLToPath(import.meta.url));
14
+ import { domainError } from '../shared/errors.js';
15
+ import { err, ok } from '../shared/result.js';
16
+ import { projectConfFile, projectsDir, userSkillsDir } from '../shared/runtimePaths.js';
17
+ export class SkillService {
18
+ deps;
19
+ constructor(deps) {
20
+ this.deps = deps;
21
+ }
22
+ /** 列出 user-level skills。可选带 project 参数以得到 stateInProject。 */
23
+ async list(project) {
24
+ const { listUserSkills, inspectProjectSkill } = await import('../core/skillStore.js');
25
+ const users = listUserSkills();
26
+ const enriched = users.map((u) => {
27
+ const fm = readFrontmatter(resolve(u.userPath, 'SKILL.md'));
28
+ const base = {
29
+ name: u.name,
30
+ category: classifyCategory(u.name),
31
+ description: fm.description,
32
+ origin: fm.origin,
33
+ linkedProjects: collectLinkedProjects(u.name).map((p) => p.project),
34
+ };
35
+ if (project) {
36
+ const repo = findProjectRepoDir(project);
37
+ const state = repo
38
+ ? inspectProjectSkill(repo, u.name)?.state ?? 'absent'
39
+ : 'absent';
40
+ return { ...base, stateInProject: state };
41
+ }
42
+ return base;
43
+ });
44
+ return ok(enriched);
45
+ }
46
+ async get(name) {
47
+ if (!isValidSkillName(name)) {
48
+ return err(domainError('validation', 'INVALID_SKILL_NAME', 'skill 名非法'));
49
+ }
50
+ const { listUserSkills } = await import('../core/skillStore.js');
51
+ const found = listUserSkills().find((u) => u.name === name);
52
+ if (!found) {
53
+ return err(domainError('not-found', 'SKILL_NOT_FOUND', `skill ${name} 不存在`));
54
+ }
55
+ const fm = readFrontmatter(resolve(found.userPath, 'SKILL.md'));
56
+ const body = readFileOrEmpty(resolve(found.userPath, 'SKILL.md'));
57
+ const refsDir = resolve(found.userPath, 'references');
58
+ const references = existsSync(refsDir)
59
+ ? readdirSync(refsDir)
60
+ .filter((f) => f.endsWith('.md'))
61
+ .map((f) => {
62
+ const full = resolve(refsDir, f);
63
+ return { name: f, lines: readFileOrEmpty(full).split('\n').length };
64
+ })
65
+ : [];
66
+ return ok({
67
+ name,
68
+ category: classifyCategory(name),
69
+ description: fm.description,
70
+ origin: fm.origin,
71
+ body,
72
+ references,
73
+ linkedProjects: collectLinkedProjects(name).map((p) => p.project),
74
+ });
75
+ }
76
+ async sync() {
77
+ const skillStore = await import('../core/skillStore.js');
78
+ // syncBundledSkillsToUser 需要 bundled skills 目录参数。defaults 让它自己查找内置。
79
+ // Phase 3 重构 bundled 目录定位时再传显式参数。
80
+ const bundled = findBundledSkillsDir();
81
+ if (!bundled) {
82
+ // 没有 bundled skills 就是成功 noop
83
+ return ok(undefined);
84
+ }
85
+ try {
86
+ skillStore.syncBundledSkillsToUser(bundled);
87
+ }
88
+ catch (cause) {
89
+ return err(domainError('internal', 'SKILL_SYNC_FAIL', 'skill 同步失败', { cause }));
90
+ }
91
+ return ok(undefined);
92
+ }
93
+ async link(skill, project) {
94
+ if (!isValidSkillName(skill))
95
+ return err(invalidSkill());
96
+ if (!isValidProject(project))
97
+ return err(invalidProject());
98
+ const repo = findProjectRepoDir(project);
99
+ if (!repo)
100
+ return err(projectNotFound(project));
101
+ const { addSkillToProject } = await import('../core/skillStore.js');
102
+ const result = addSkillToProject(repo, skill);
103
+ if (result === 'skipped-absent') {
104
+ return err(domainError('not-found', 'SKILL_NOT_FOUND', `skill ${skill} 不在 user registry`));
105
+ }
106
+ // 已在工程中(symlink 或 frozen copy)→ 幂等 ok,不再 emit
107
+ if (result === 'skipped-linked' || result === 'skipped-frozen') {
108
+ return ok(undefined);
109
+ }
110
+ this.deps.events.emit({
111
+ type: 'skill.linked',
112
+ project,
113
+ skill,
114
+ ts: Date.now(),
115
+ });
116
+ return ok(undefined);
117
+ }
118
+ async unlink(skill, project) {
119
+ if (!isValidSkillName(skill))
120
+ return err(invalidSkill());
121
+ if (!isValidProject(project))
122
+ return err(invalidProject());
123
+ const repo = findProjectRepoDir(project);
124
+ if (!repo)
125
+ return err(projectNotFound(project));
126
+ const { removeSkillFromProject } = await import('../core/skillStore.js');
127
+ const removed = removeSkillFromProject(repo, skill);
128
+ if (!removed)
129
+ return ok(undefined); // idempotent
130
+ this.deps.events.emit({
131
+ type: 'skill.unlinked',
132
+ project,
133
+ skill,
134
+ ts: Date.now(),
135
+ });
136
+ return ok(undefined);
137
+ }
138
+ async freeze(skill, project) {
139
+ if (!isValidSkillName(skill))
140
+ return err(invalidSkill());
141
+ if (!isValidProject(project))
142
+ return err(invalidProject());
143
+ const repo = findProjectRepoDir(project);
144
+ if (!repo)
145
+ return err(projectNotFound(project));
146
+ const { freezeSkillInProject } = await import('../core/skillStore.js');
147
+ if (!freezeSkillInProject(repo, skill)) {
148
+ return err(domainError('conflict', 'FREEZE_FAIL', '冻结失败 —— 可能未 link'));
149
+ }
150
+ return ok(undefined);
151
+ }
152
+ async unfreeze(skill, project) {
153
+ if (!isValidSkillName(skill))
154
+ return err(invalidSkill());
155
+ if (!isValidProject(project))
156
+ return err(invalidProject());
157
+ const repo = findProjectRepoDir(project);
158
+ if (!repo)
159
+ return err(projectNotFound(project));
160
+ const { unfreezeSkillInProject } = await import('../core/skillStore.js');
161
+ if (!unfreezeSkillInProject(repo, skill)) {
162
+ return err(domainError('conflict', 'UNFREEZE_FAIL', '解冻失败 —— 可能未冻结'));
163
+ }
164
+ return ok(undefined);
165
+ }
166
+ }
167
+ // ─── Helpers ──────────────────────────────────────────────────────
168
+ function classifyCategory(name) {
169
+ const LANGUAGES = new Set(['python', 'typescript', 'golang', 'rust', 'kotlin', 'swift', 'java']);
170
+ const ENDS = new Set(['frontend', 'backend', 'mobile', 'database', 'devops']);
171
+ const PERSONAS = new Set([
172
+ 'backend-architect',
173
+ 'frontend-developer',
174
+ 'code-reviewer',
175
+ 'database-optimizer',
176
+ 'devops-automator',
177
+ 'security-engineer',
178
+ 'qa-tester',
179
+ ]);
180
+ const WORKFLOWS = new Set([
181
+ 'coding-standards',
182
+ 'tdd-workflow',
183
+ 'git-workflow',
184
+ 'architecture-decision-records',
185
+ 'debugging-workflow',
186
+ ]);
187
+ if (LANGUAGES.has(name))
188
+ return 'language';
189
+ if (ENDS.has(name))
190
+ return 'end';
191
+ if (PERSONAS.has(name))
192
+ return 'persona';
193
+ if (WORKFLOWS.has(name))
194
+ return 'workflow';
195
+ return 'other';
196
+ }
197
+ function readFrontmatter(path) {
198
+ if (!existsSync(path))
199
+ return { description: '', origin: '' };
200
+ try {
201
+ const raw = readFileSync(path, 'utf-8');
202
+ if (!raw.startsWith('---'))
203
+ return { description: '', origin: '' };
204
+ const end = raw.indexOf('\n---', 3);
205
+ if (end === -1)
206
+ return { description: '', origin: '' };
207
+ const yaml = raw.slice(3, end);
208
+ const desc = yaml.match(/^description:\s*(.+)$/m)?.[1] ?? '';
209
+ const origin = yaml.match(/^origin:\s*(.+)$/m)?.[1] ?? '';
210
+ return {
211
+ description: desc.trim().replace(/^["']|["']$/g, ''),
212
+ origin: origin.trim().replace(/^["']|["']$/g, ''),
213
+ };
214
+ }
215
+ catch {
216
+ return { description: '', origin: '' };
217
+ }
218
+ }
219
+ function readFileOrEmpty(path) {
220
+ try {
221
+ return readFileSync(path, 'utf-8');
222
+ }
223
+ catch {
224
+ return '';
225
+ }
226
+ }
227
+ function collectLinkedProjects(skillName) {
228
+ const root = projectsDir();
229
+ if (!existsSync(root))
230
+ return [];
231
+ const out = [];
232
+ const projects = readdirSync(root, { withFileTypes: true })
233
+ .filter((e) => e.isDirectory())
234
+ .map((e) => e.name);
235
+ for (const name of projects) {
236
+ const repo = findProjectRepoDir(name);
237
+ if (!repo || !existsSync(repo))
238
+ continue;
239
+ // Avoid importing skillStore here —— dynamic import in methods that need it
240
+ // 此 helper 仅供 list/get 内部预计算,用脏读直接 fs 判定:
241
+ const linked = resolve(repo, '.claude', 'skills', skillName);
242
+ if (existsSync(linked)) {
243
+ const frozen = existsSync(resolve(linked, '.frozen'));
244
+ out.push({ project: name, state: frozen ? 'frozen' : 'linked' });
245
+ }
246
+ }
247
+ return out;
248
+ }
249
+ function findProjectRepoDir(project) {
250
+ const confPath = projectConfFile(project);
251
+ if (!existsSync(confPath))
252
+ return null;
253
+ try {
254
+ const content = readFileSync(confPath, 'utf-8');
255
+ const match = content.match(/^(?:export\s+)?(?:PROJECT_DIR|REPO_DIR)=["']?([^"'\n]+)["']?/m);
256
+ return match ? match[1] ?? null : null;
257
+ }
258
+ catch {
259
+ return null;
260
+ }
261
+ }
262
+ function isValidProject(project) {
263
+ return typeof project === 'string' && /^[a-zA-Z0-9_-]+$/.test(project);
264
+ }
265
+ function isValidSkillName(name) {
266
+ return typeof name === 'string' && /^[a-zA-Z0-9_-]+$/.test(name);
267
+ }
268
+ function invalidSkill() {
269
+ return domainError('validation', 'INVALID_SKILL_NAME', 'skill 名非法');
270
+ }
271
+ function invalidProject() {
272
+ return domainError('validation', 'INVALID_PROJECT_NAME', '项目名非法');
273
+ }
274
+ function projectNotFound(name) {
275
+ return domainError('not-found', 'PROJECT_NOT_FOUND', `项目 ${name} 不存在`);
276
+ }
277
+ /**
278
+ * 定位仓库里的 `skills/` bundled 目录(project-template 模板同级)。
279
+ * 和 console-server/routes/skills.ts::findBundledSkillsDir 逻辑一致 ——
280
+ * Phase 3 迁移 Delivery 时再合并到 shared helper。
281
+ */
282
+ function findBundledSkillsDir() {
283
+ const candidates = [
284
+ resolve(process.cwd(), 'skills'),
285
+ // 从 src/services/SkillService.ts 往上数:services → src → workflow-cli → skills
286
+ resolve(__dirname, '..', '..', 'skills'),
287
+ ];
288
+ for (const c of candidates) {
289
+ try {
290
+ if (existsSync(c))
291
+ return c;
292
+ }
293
+ catch {
294
+ /* ignore */
295
+ }
296
+ }
297
+ return null;
298
+ }
299
+ // void —— 让 TypeScript 不要 warn userSkillsDir 没直接用
300
+ void userSkillsDir;
301
+ //# sourceMappingURL=SkillService.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SkillService.js","sourceRoot":"","sources":["../../src/services/SkillService.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,83 @@
1
+ /**
2
+ * @module services/WorkerService
3
+ * @description Worker 查询 + 控制 service
4
+ *
5
+ * @layer services
6
+ *
7
+ * 职责:
8
+ * - 读 runtime/worker-worker-<N>-current.json marker → 结构化 WorkerInfo
9
+ * - 列项目 workers / 跨项目聚合
10
+ * - launch / kill —— Phase 2 暂不负责真 spawn,返 pending port(Phase 3 填)
11
+ *
12
+ * 不负责:
13
+ * - marker 写入(worker-manager 的事)
14
+ * - 日志 tail(LogService)
15
+ */
16
+ import type { Clock } from '../infra/clock.js';
17
+ import type { FileSystem } from '../infra/filesystem.js';
18
+ import type { DomainEventBus } from '../shared/domainEvents.js';
19
+ import { type DomainError } from '../shared/errors.js';
20
+ import { type Result } from '../shared/result.js';
21
+ export type WorkerState = 'idle' | 'starting' | 'running' | 'stuck' | 'crashed';
22
+ export interface WorkerInfo {
23
+ readonly slot: number;
24
+ readonly pid: number | null;
25
+ readonly state: WorkerState;
26
+ readonly card: {
27
+ seq: number;
28
+ title: string;
29
+ } | null;
30
+ readonly stage: string | null;
31
+ readonly startedAt: string | null;
32
+ readonly runtimeMs: number | null;
33
+ readonly markerUpdatedAt: string | null;
34
+ }
35
+ export interface AggregateResult {
36
+ readonly alerts: Array<WorkerInfo & {
37
+ project: string;
38
+ }>;
39
+ readonly active: Array<WorkerInfo & {
40
+ project: string;
41
+ }>;
42
+ readonly capacity: Array<{
43
+ project: string;
44
+ total: number;
45
+ running: number;
46
+ starting: number;
47
+ stuck: number;
48
+ crashed: number;
49
+ idle: number;
50
+ }>;
51
+ }
52
+ /** Service 让调用方注入 launch/kill 执行器(Phase 3 由 CLI 命令绑定) */
53
+ export interface WorkerExecutor {
54
+ launch(project: string, seq: number): Promise<void>;
55
+ kill(project: string, slot: number): Promise<void>;
56
+ }
57
+ export interface WorkerServiceDeps {
58
+ readonly fs: FileSystem;
59
+ readonly clock: Clock;
60
+ readonly events: DomainEventBus;
61
+ /** 卡片 title 反查器(CardService 实例或类似),Phase 3 注入。不注入则 title 用 `#<seq>`。 */
62
+ readonly cardTitleLookup?: (project: string, seq: number) => Promise<string | null>;
63
+ readonly executor?: WorkerExecutor;
64
+ }
65
+ export declare class WorkerService {
66
+ private readonly deps;
67
+ constructor(deps: WorkerServiceDeps);
68
+ /** 列单项目的 worker slots(按 slot 数字升序)。 */
69
+ listByProject(project: string): Promise<Result<WorkerInfo[], DomainError>>;
70
+ /** 单 slot detail。 */
71
+ getBySlot(project: string, slot: number): Promise<Result<WorkerInfo, DomainError>>;
72
+ /** 跨项目聚合 —— Workers 页用。 */
73
+ aggregate(): Promise<Result<AggregateResult, DomainError>>;
74
+ /** launch 转发到注入的 executor。未注入返 internal。 */
75
+ launch(project: string, seq: number): Promise<Result<void>>;
76
+ kill(project: string, slot: number): Promise<Result<void>>;
77
+ /** 扫 runtime/ 下 marker 文件,匹配双前缀 + 单前缀,返 slot 数字 + 实际路径 */
78
+ private listMarkerSlots;
79
+ /** 优先双前缀,再试老单前缀 */
80
+ private resolveMarkerPath;
81
+ private buildInfo;
82
+ }
83
+ //# sourceMappingURL=WorkerService.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WorkerService.d.ts","sourceRoot":"","sources":["../../src/services/WorkerService.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,262 @@
1
+ /**
2
+ * @module services/WorkerService
3
+ * @description Worker 查询 + 控制 service
4
+ *
5
+ * @layer services
6
+ *
7
+ * 职责:
8
+ * - 读 runtime/worker-worker-<N>-current.json marker → 结构化 WorkerInfo
9
+ * - 列项目 workers / 跨项目聚合
10
+ * - launch / kill —— Phase 2 暂不负责真 spawn,返 pending port(Phase 3 填)
11
+ *
12
+ * 不负责:
13
+ * - marker 写入(worker-manager 的事)
14
+ * - 日志 tail(LogService)
15
+ */
16
+ import { resolve } from 'node:path';
17
+ import { domainError } from '../shared/errors.js';
18
+ import { err, ok } from '../shared/result.js';
19
+ import { projectDir, projectsDir, runtimeDir, slotFromMarkerFilename, WorkerMarkerFilenameRe, workerMarkerFile, } from '../shared/runtimePaths.js';
20
+ import { WorkerMarkerSchema } from '../shared/runtimeSchemas.js';
21
+ const STUCK_THRESHOLD_MS = 5 * 60 * 1000;
22
+ const ACK_TIMEOUT_MS = 60 * 1000;
23
+ export class WorkerService {
24
+ deps;
25
+ constructor(deps) {
26
+ this.deps = deps;
27
+ }
28
+ /** 列单项目的 worker slots(按 slot 数字升序)。 */
29
+ async listByProject(project) {
30
+ if (!isValidProject(project))
31
+ return err(invalidProject());
32
+ const dir = runtimeDir(project);
33
+ if (!this.deps.fs.exists(dir))
34
+ return ok([]);
35
+ const slots = this.listMarkerSlots(project);
36
+ const infos = [];
37
+ for (const { slot, file } of slots) {
38
+ infos.push(await this.buildInfo(project, slot, file));
39
+ }
40
+ return ok(infos.sort((a, b) => a.slot - b.slot));
41
+ }
42
+ /** 单 slot detail。 */
43
+ async getBySlot(project, slot) {
44
+ if (!isValidProject(project))
45
+ return err(invalidProject());
46
+ if (!Number.isInteger(slot) || slot <= 0) {
47
+ return err(domainError('validation', 'INVALID_SLOT', 'slot 必须是正整数'));
48
+ }
49
+ const candidates = this.resolveMarkerPath(project, slot);
50
+ if (!candidates) {
51
+ return err(domainError('not-found', 'WORKER_MARKER_NOT_FOUND', `worker-${slot} marker 不存在`));
52
+ }
53
+ return ok(await this.buildInfo(project, slot, candidates));
54
+ }
55
+ /** 跨项目聚合 —— Workers 页用。 */
56
+ async aggregate() {
57
+ const root = projectsDir();
58
+ if (!this.deps.fs.exists(root)) {
59
+ return ok({ alerts: [], active: [], capacity: [] });
60
+ }
61
+ let projects;
62
+ try {
63
+ projects = this.deps.fs.readDir(root).filter((e) => e.isDirectory).map((e) => e.name);
64
+ }
65
+ catch (cause) {
66
+ return err(domainError('internal', 'PROJECTS_READ_FAIL', '项目目录读取失败', { cause }));
67
+ }
68
+ projects.sort();
69
+ const result = { alerts: [], active: [], capacity: [] };
70
+ for (const project of projects) {
71
+ const slots = this.listMarkerSlots(project);
72
+ const stats = {
73
+ project,
74
+ total: slots.length,
75
+ running: 0,
76
+ starting: 0,
77
+ stuck: 0,
78
+ crashed: 0,
79
+ idle: 0,
80
+ };
81
+ for (const { slot, file } of slots) {
82
+ const info = await this.buildInfo(project, slot, file);
83
+ stats[info.state]++;
84
+ if (info.state === 'stuck' || info.state === 'crashed') {
85
+ result.alerts.push({ ...info, project });
86
+ }
87
+ else if (info.state === 'running' || info.state === 'starting') {
88
+ result.active.push({ ...info, project });
89
+ }
90
+ }
91
+ result.capacity.push(stats);
92
+ }
93
+ result.alerts.sort((a, b) => {
94
+ if (a.state !== b.state)
95
+ return a.state === 'stuck' ? -1 : 1;
96
+ return (b.runtimeMs ?? 0) - (a.runtimeMs ?? 0);
97
+ });
98
+ result.active.sort((a, b) => (b.runtimeMs ?? 0) - (a.runtimeMs ?? 0));
99
+ return ok(result);
100
+ }
101
+ /** launch 转发到注入的 executor。未注入返 internal。 */
102
+ async launch(project, seq) {
103
+ if (!isValidProject(project))
104
+ return err(invalidProject());
105
+ if (!Number.isInteger(seq) || seq <= 0) {
106
+ return err(domainError('validation', 'INVALID_SEQ', 'seq 必须是正整数'));
107
+ }
108
+ if (!this.deps.executor) {
109
+ return err(domainError('internal', 'WORKER_EXECUTOR_MISSING', '需要注入 WorkerExecutor(Phase 3 task)'));
110
+ }
111
+ if (!this.deps.fs.exists(projectDir(project))) {
112
+ return err(domainError('not-found', 'PROJECT_NOT_FOUND', `项目 ${project} 不存在`));
113
+ }
114
+ try {
115
+ await this.deps.executor.launch(project, seq);
116
+ }
117
+ catch (cause) {
118
+ return err(domainError('external', 'WORKER_LAUNCH_FAIL', 'worker launch 失败', {
119
+ cause,
120
+ details: { message: cause instanceof Error ? cause.message : String(cause) },
121
+ }));
122
+ }
123
+ return ok(undefined);
124
+ }
125
+ async kill(project, slot) {
126
+ if (!isValidProject(project))
127
+ return err(invalidProject());
128
+ if (!Number.isInteger(slot) || slot <= 0) {
129
+ return err(domainError('validation', 'INVALID_SLOT', 'slot 必须是正整数'));
130
+ }
131
+ if (!this.deps.executor) {
132
+ return err(domainError('internal', 'WORKER_EXECUTOR_MISSING', '需要注入 WorkerExecutor'));
133
+ }
134
+ try {
135
+ await this.deps.executor.kill(project, slot);
136
+ }
137
+ catch (cause) {
138
+ return err(domainError('external', 'WORKER_KILL_FAIL', 'worker kill 失败', {
139
+ cause,
140
+ details: { message: cause instanceof Error ? cause.message : String(cause) },
141
+ }));
142
+ }
143
+ return ok(undefined);
144
+ }
145
+ // ─── 内部 ─────────────────────────────────────────────────────────
146
+ /** 扫 runtime/ 下 marker 文件,匹配双前缀 + 单前缀,返 slot 数字 + 实际路径 */
147
+ listMarkerSlots(project) {
148
+ const dir = runtimeDir(project);
149
+ if (!this.deps.fs.exists(dir))
150
+ return [];
151
+ try {
152
+ return this.deps.fs
153
+ .readDir(dir)
154
+ .filter((e) => e.isFile && WorkerMarkerFilenameRe.test(e.name))
155
+ .map((e) => {
156
+ const slot = slotFromMarkerFilename(e.name);
157
+ return { slot: slot ?? 0, file: resolve(dir, e.name) };
158
+ })
159
+ .filter((x) => x.slot > 0);
160
+ }
161
+ catch {
162
+ return [];
163
+ }
164
+ }
165
+ /** 优先双前缀,再试老单前缀 */
166
+ resolveMarkerPath(project, slot) {
167
+ const doublePrefix = workerMarkerFile(project, `worker-${slot}`);
168
+ if (this.deps.fs.exists(doublePrefix))
169
+ return doublePrefix;
170
+ // 老兼容:worker-<N>-current.json(单前缀)
171
+ const single = resolve(runtimeDir(project), `worker-${slot}-current.json`);
172
+ if (this.deps.fs.exists(single))
173
+ return single;
174
+ return null;
175
+ }
176
+ async buildInfo(project, slot, markerPath) {
177
+ const now = this.deps.clock.now();
178
+ let pid = null;
179
+ let card = null;
180
+ let stage = null;
181
+ let startedAt = null;
182
+ let markerUpdatedAt = null;
183
+ try {
184
+ const raw = this.deps.fs.readFile(markerPath);
185
+ const parsed = WorkerMarkerSchema.safeParse(JSON.parse(raw));
186
+ if (parsed.success) {
187
+ const d = parsed.data;
188
+ pid = d.pid ?? null;
189
+ stage = d.stage;
190
+ startedAt = d.dispatchedAt;
191
+ const seq = parseSeqFromCardId(d.cardId);
192
+ if (seq !== null) {
193
+ let title = `#${seq}`;
194
+ if (this.deps.cardTitleLookup) {
195
+ try {
196
+ const t = await this.deps.cardTitleLookup(project, seq);
197
+ if (t)
198
+ title = t;
199
+ }
200
+ catch {
201
+ /* lookup 失败用 fallback */
202
+ }
203
+ }
204
+ card = { seq, title };
205
+ }
206
+ }
207
+ }
208
+ catch {
209
+ /* marker 读不了就按最小信息返 */
210
+ }
211
+ const stat = this.deps.fs.stat(markerPath);
212
+ if (stat)
213
+ markerUpdatedAt = new Date(stat.mtimeMs).toISOString();
214
+ const alive = isPidAlive(pid);
215
+ const fresh = markerUpdatedAt ? now - new Date(markerUpdatedAt).getTime() < STUCK_THRESHOLD_MS : false;
216
+ const ageMs = markerUpdatedAt ? now - new Date(markerUpdatedAt).getTime() : null;
217
+ let state;
218
+ if (!alive) {
219
+ state = card !== null ? 'crashed' : 'idle';
220
+ }
221
+ else if (card === null) {
222
+ state = 'idle';
223
+ }
224
+ else {
225
+ const starting = ageMs !== null && ageMs <= ACK_TIMEOUT_MS;
226
+ if (starting)
227
+ state = 'starting';
228
+ else if (!fresh)
229
+ state = 'stuck';
230
+ else
231
+ state = 'running';
232
+ }
233
+ const runtimeMs = startedAt ? now - new Date(startedAt).getTime() : null;
234
+ return { slot, pid, state, card, stage, startedAt, runtimeMs, markerUpdatedAt };
235
+ }
236
+ }
237
+ // ─── Helpers ──────────────────────────────────────────────────────
238
+ function parseSeqFromCardId(cardId) {
239
+ const m = cardId.match(/^(?:md-)?(\d+)$/);
240
+ if (!m)
241
+ return null;
242
+ const n = Number.parseInt(m[1] ?? '', 10);
243
+ return Number.isFinite(n) && n > 0 ? n : null;
244
+ }
245
+ function isPidAlive(pid) {
246
+ if (!pid)
247
+ return false;
248
+ try {
249
+ process.kill(pid, 0);
250
+ return true;
251
+ }
252
+ catch {
253
+ return false;
254
+ }
255
+ }
256
+ function isValidProject(project) {
257
+ return typeof project === 'string' && /^[a-zA-Z0-9_-]+$/.test(project);
258
+ }
259
+ function invalidProject() {
260
+ return domainError('validation', 'INVALID_PROJECT_NAME', '项目名非法');
261
+ }
262
+ //# sourceMappingURL=WorkerService.js.map