@winspan/claude-forge 8.34.0 → 8.36.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 (186) hide show
  1. package/README.md +10 -30
  2. package/dist/capability/index.d.ts +6 -5
  3. package/dist/capability/index.d.ts.map +1 -1
  4. package/dist/capability/index.js +6 -5
  5. package/dist/capability/index.js.map +1 -1
  6. package/dist/capability/types.d.ts +5 -74
  7. package/dist/capability/types.d.ts.map +1 -1
  8. package/dist/capability/types.js +4 -1
  9. package/dist/capability/types.js.map +1 -1
  10. package/dist/core/ai/provider.d.ts +23 -1
  11. package/dist/core/ai/provider.d.ts.map +1 -1
  12. package/dist/core/ai/provider.js +67 -1
  13. package/dist/core/ai/provider.js.map +1 -1
  14. package/dist/core/ai/types.d.ts +28 -0
  15. package/dist/core/ai/types.d.ts.map +1 -1
  16. package/dist/core/storage/rows.d.ts +0 -36
  17. package/dist/core/storage/rows.d.ts.map +1 -1
  18. package/dist/core/storage/schema.sql +1 -45
  19. package/dist/core/storage/sqlite.d.ts +2 -79
  20. package/dist/core/storage/sqlite.d.ts.map +1 -1
  21. package/dist/core/storage/sqlite.js +3 -362
  22. package/dist/core/storage/sqlite.js.map +1 -1
  23. package/dist/core/utils/token-tracker.d.ts +0 -1
  24. package/dist/core/utils/token-tracker.d.ts.map +1 -1
  25. package/dist/core/utils/token-tracker.js +0 -1
  26. package/dist/core/utils/token-tracker.js.map +1 -1
  27. package/dist/daemon/handlers/post-tool-use.d.ts +1 -9
  28. package/dist/daemon/handlers/post-tool-use.d.ts.map +1 -1
  29. package/dist/daemon/handlers/post-tool-use.js +2 -99
  30. package/dist/daemon/handlers/post-tool-use.js.map +1 -1
  31. package/dist/daemon/handlers/stop.d.ts +1 -15
  32. package/dist/daemon/handlers/stop.d.ts.map +1 -1
  33. package/dist/daemon/handlers/stop.js +1 -95
  34. package/dist/daemon/handlers/stop.js.map +1 -1
  35. package/dist/daemon/handlers/user-prompt.d.ts +1 -5
  36. package/dist/daemon/handlers/user-prompt.d.ts.map +1 -1
  37. package/dist/daemon/handlers/user-prompt.js +29 -99
  38. package/dist/daemon/handlers/user-prompt.js.map +1 -1
  39. package/dist/daemon/index.d.ts.map +1 -1
  40. package/dist/daemon/index.js +9 -56
  41. package/dist/daemon/index.js.map +1 -1
  42. package/dist/engine/agent-router.d.ts +37 -0
  43. package/dist/engine/agent-router.d.ts.map +1 -1
  44. package/dist/engine/agent-router.js +58 -0
  45. package/dist/engine/agent-router.js.map +1 -1
  46. package/dist/engine/conventions/routing.yaml +31 -2
  47. package/dist/intelligence/classifier.d.ts +63 -43
  48. package/dist/intelligence/classifier.d.ts.map +1 -1
  49. package/dist/intelligence/classifier.js +256 -191
  50. package/dist/intelligence/classifier.js.map +1 -1
  51. package/dist/intelligence/context-gatherer.d.ts +101 -0
  52. package/dist/intelligence/context-gatherer.d.ts.map +1 -0
  53. package/dist/intelligence/context-gatherer.js +417 -0
  54. package/dist/intelligence/context-gatherer.js.map +1 -0
  55. package/dist/intelligence/cot-classifier.d.ts +95 -0
  56. package/dist/intelligence/cot-classifier.d.ts.map +1 -0
  57. package/dist/intelligence/cot-classifier.js +391 -0
  58. package/dist/intelligence/cot-classifier.js.map +1 -0
  59. package/dist/intelligence/execution-doc-builder.d.ts +90 -0
  60. package/dist/intelligence/execution-doc-builder.d.ts.map +1 -1
  61. package/dist/intelligence/execution-doc-builder.js +459 -42
  62. package/dist/intelligence/execution-doc-builder.js.map +1 -1
  63. package/dist/intelligence/intent-types.d.ts +13 -0
  64. package/dist/intelligence/intent-types.d.ts.map +1 -0
  65. package/dist/intelligence/intent-types.js +19 -0
  66. package/dist/intelligence/intent-types.js.map +1 -0
  67. package/dist/intelligence/multimodal-parser.d.ts +105 -0
  68. package/dist/intelligence/multimodal-parser.d.ts.map +1 -0
  69. package/dist/intelligence/multimodal-parser.js +425 -0
  70. package/dist/intelligence/multimodal-parser.js.map +1 -0
  71. package/dist/skills/official-skills.d.ts.map +1 -1
  72. package/dist/skills/official-skills.js +218 -20
  73. package/dist/skills/official-skills.js.map +1 -1
  74. package/dist/web/routes/status.d.ts.map +1 -1
  75. package/dist/web/routes/status.js +8 -10
  76. package/dist/web/routes/status.js.map +1 -1
  77. package/dist/web/routes/token-usage.d.ts +1 -1
  78. package/dist/web/routes/token-usage.d.ts.map +1 -1
  79. package/dist/web/routes/token-usage.js +1 -16
  80. package/dist/web/routes/token-usage.js.map +1 -1
  81. package/dist/web/routes/types.d.ts +0 -6
  82. package/dist/web/routes/types.d.ts.map +1 -1
  83. package/dist/web/routes/types.js.map +1 -1
  84. package/dist/web/server.d.ts +0 -6
  85. package/dist/web/server.d.ts.map +1 -1
  86. package/dist/web/server.js +0 -5
  87. package/dist/web/server.js.map +1 -1
  88. package/dist/web/static/assets/{AIConfig-DiUFET_Q.js → AIConfig-D4VglzCl.js} +2 -2
  89. package/dist/web/static/assets/{AIConfig-DiUFET_Q.js.map → AIConfig-D4VglzCl.js.map} +1 -1
  90. package/dist/web/static/assets/{Agents-bNNGbQnL.js → Agents-ne5lXc7V.js} +2 -2
  91. package/dist/web/static/assets/{Agents-bNNGbQnL.js.map → Agents-ne5lXc7V.js.map} +1 -1
  92. package/dist/web/static/assets/Dashboard-D4j0Zmek.js +2 -0
  93. package/dist/web/static/assets/Dashboard-D4j0Zmek.js.map +1 -0
  94. package/dist/web/static/assets/{Drawer-DOUcx6m1.js → Drawer-Lo5ihVP-.js} +2 -2
  95. package/dist/web/static/assets/{Drawer-DOUcx6m1.js.map → Drawer-Lo5ihVP-.js.map} +1 -1
  96. package/dist/web/static/assets/{Events-DQHP6Uaq.js → Events-DBJ1B7OW.js} +2 -2
  97. package/dist/web/static/assets/{Events-DQHP6Uaq.js.map → Events-DBJ1B7OW.js.map} +1 -1
  98. package/dist/web/static/assets/{ExecutionTrace-Co8ARdg-.js → ExecutionTrace-Du9XADc1.js} +2 -2
  99. package/dist/web/static/assets/{ExecutionTrace-Co8ARdg-.js.map → ExecutionTrace-Du9XADc1.js.map} +1 -1
  100. package/dist/web/static/assets/{Routing-BW3eGD-8.js → Routing-BNQ09OlH.js} +2 -2
  101. package/dist/web/static/assets/{Routing-BW3eGD-8.js.map → Routing-BNQ09OlH.js.map} +1 -1
  102. package/dist/web/static/assets/{SessionDetail-Cbd7Jwox.js → SessionDetail-BPrPyMNa.js} +2 -2
  103. package/dist/web/static/assets/{SessionDetail-Cbd7Jwox.js.map → SessionDetail-BPrPyMNa.js.map} +1 -1
  104. package/dist/web/static/assets/{Sessions-ZQSCgXyy.js → Sessions-o3EXsXz9.js} +2 -2
  105. package/dist/web/static/assets/{Sessions-ZQSCgXyy.js.map → Sessions-o3EXsXz9.js.map} +1 -1
  106. package/dist/web/static/assets/{Skills-C5-5zOSH.js → Skills-Czt5mkyc.js} +2 -2
  107. package/dist/web/static/assets/{Skills-C5-5zOSH.js.map → Skills-Czt5mkyc.js.map} +1 -1
  108. package/dist/web/static/assets/{export-CbQTOt71.js → export-C0mlC4AT.js} +2 -2
  109. package/dist/web/static/assets/{export-CbQTOt71.js.map → export-C0mlC4AT.js.map} +1 -1
  110. package/dist/web/static/assets/index-B1J7nBu0.js +3 -0
  111. package/dist/web/static/assets/index-B1J7nBu0.js.map +1 -0
  112. package/dist/web/static/assets/index-BVqk4bSO.css +1 -0
  113. package/dist/web/static/assets/{lucide-BanPULT1.js → lucide-Bu44HVAM.js} +33 -73
  114. package/dist/web/static/assets/lucide-Bu44HVAM.js.map +1 -0
  115. package/dist/web/static/index.html +3 -3
  116. package/package.json +2 -2
  117. package/dist/capability/execution-manager.d.ts +0 -96
  118. package/dist/capability/execution-manager.d.ts.map +0 -1
  119. package/dist/capability/execution-manager.js +0 -260
  120. package/dist/capability/execution-manager.js.map +0 -1
  121. package/dist/capability/executor/background-executor.d.ts +0 -58
  122. package/dist/capability/executor/background-executor.d.ts.map +0 -1
  123. package/dist/capability/executor/background-executor.js +0 -322
  124. package/dist/capability/executor/background-executor.js.map +0 -1
  125. package/dist/capability/executor/foreground-executor.d.ts +0 -26
  126. package/dist/capability/executor/foreground-executor.d.ts.map +0 -1
  127. package/dist/capability/executor/foreground-executor.js +0 -82
  128. package/dist/capability/executor/foreground-executor.js.map +0 -1
  129. package/dist/capability/executor/orchestrator.d.ts +0 -38
  130. package/dist/capability/executor/orchestrator.d.ts.map +0 -1
  131. package/dist/capability/executor/orchestrator.js +0 -158
  132. package/dist/capability/executor/orchestrator.js.map +0 -1
  133. package/dist/capability/executor/stream-parser.d.ts +0 -73
  134. package/dist/capability/executor/stream-parser.d.ts.map +0 -1
  135. package/dist/capability/executor/stream-parser.js +0 -56
  136. package/dist/capability/executor/stream-parser.js.map +0 -1
  137. package/dist/capability/executor/types.d.ts +0 -44
  138. package/dist/capability/executor/types.d.ts.map +0 -1
  139. package/dist/capability/executor/types.js +0 -9
  140. package/dist/capability/executor/types.js.map +0 -1
  141. package/dist/capability/executor/worker-auth-probe.d.ts +0 -30
  142. package/dist/capability/executor/worker-auth-probe.d.ts.map +0 -1
  143. package/dist/capability/executor/worker-auth-probe.js +0 -108
  144. package/dist/capability/executor/worker-auth-probe.js.map +0 -1
  145. package/dist/capability/methodologies/bmad.yaml +0 -81
  146. package/dist/capability/methodologies/code-quality-audit.yaml +0 -26
  147. package/dist/capability/methodologies/harness-engineering.yaml +0 -75
  148. package/dist/capability/methodologies/test-coverage-scan.yaml +0 -26
  149. package/dist/capability/methodology-planner.d.ts +0 -49
  150. package/dist/capability/methodology-planner.d.ts.map +0 -1
  151. package/dist/capability/methodology-planner.js +0 -306
  152. package/dist/capability/methodology-planner.js.map +0 -1
  153. package/dist/capability/methodology-registry.d.ts +0 -32
  154. package/dist/capability/methodology-registry.d.ts.map +0 -1
  155. package/dist/capability/methodology-registry.js +0 -113
  156. package/dist/capability/methodology-registry.js.map +0 -1
  157. package/dist/daemon/handlers/methodology-formatter.d.ts +0 -16
  158. package/dist/daemon/handlers/methodology-formatter.d.ts.map +0 -1
  159. package/dist/daemon/handlers/methodology-formatter.js +0 -119
  160. package/dist/daemon/handlers/methodology-formatter.js.map +0 -1
  161. package/dist/daemon/idle-detector.d.ts +0 -35
  162. package/dist/daemon/idle-detector.d.ts.map +0 -1
  163. package/dist/daemon/idle-detector.js +0 -56
  164. package/dist/daemon/idle-detector.js.map +0 -1
  165. package/dist/daemon/idle-trigger.d.ts +0 -53
  166. package/dist/daemon/idle-trigger.d.ts.map +0 -1
  167. package/dist/daemon/idle-trigger.js +0 -153
  168. package/dist/daemon/idle-trigger.js.map +0 -1
  169. package/dist/daemon/methodology-pending-queue.d.ts +0 -33
  170. package/dist/daemon/methodology-pending-queue.d.ts.map +0 -1
  171. package/dist/daemon/methodology-pending-queue.js +0 -120
  172. package/dist/daemon/methodology-pending-queue.js.map +0 -1
  173. package/dist/web/routes/methodology.d.ts +0 -12
  174. package/dist/web/routes/methodology.d.ts.map +0 -1
  175. package/dist/web/routes/methodology.js +0 -228
  176. package/dist/web/routes/methodology.js.map +0 -1
  177. package/dist/web/static/assets/Dashboard-Ciyyw6ph.js +0 -2
  178. package/dist/web/static/assets/Dashboard-Ciyyw6ph.js.map +0 -1
  179. package/dist/web/static/assets/Methodologies-CXNrDXwG.js +0 -5
  180. package/dist/web/static/assets/Methodologies-CXNrDXwG.js.map +0 -1
  181. package/dist/web/static/assets/MethodologyDetail-rV3W1utf.js +0 -2
  182. package/dist/web/static/assets/MethodologyDetail-rV3W1utf.js.map +0 -1
  183. package/dist/web/static/assets/index-DJK5beK6.js +0 -3
  184. package/dist/web/static/assets/index-DJK5beK6.js.map +0 -1
  185. package/dist/web/static/assets/index-phpuytMI.css +0 -1
  186. package/dist/web/static/assets/lucide-BanPULT1.js.map +0 -1
@@ -1,260 +0,0 @@
1
- /**
2
- * ExecutionManager — daemon 进程内单例,统筹所有 methodology_executions 的后台执行。
3
- *
4
- * 职责:
5
- * 1. 提供 `start({session_id, plan, mode})` 入口:为前台/后台两种模式分别使用合适的 executor
6
- * 2. 跟踪活跃的 orchestrator,让 cancel() 能真正 kill 子进程
7
- * 3. 在 daemon 启动时扫一遍 listRunningBackgroundExecutions() —— 对应 worker
8
- * 已不在了(孤儿 execution),标记为 failed("crashed")
9
- *
10
- * 不负责:路由决策 / planner / UI。
11
- */
12
- import { ForegroundExecutor } from './executor/foreground-executor.js';
13
- import { BackgroundExecutor } from './executor/background-executor.js';
14
- import { Orchestrator } from './executor/orchestrator.js';
15
- import { probeWorkerAuth, WorkerAuthError } from './executor/worker-auth-probe.js';
16
- import { logger } from '../core/utils/logger.js';
17
- export class ExecutionManager {
18
- storage;
19
- registry;
20
- foregroundExecutor;
21
- backgroundExecutor;
22
- activeOrchestrators = new Map();
23
- listeners = new Set();
24
- reconcileTimer = null;
25
- staleThresholdMs;
26
- constructor(storage, registry, options = {}) {
27
- this.storage = storage;
28
- this.registry = registry;
29
- this.foregroundExecutor = new ForegroundExecutor(storage, registry);
30
- this.backgroundExecutor = new BackgroundExecutor(storage, registry, options.background);
31
- this.staleThresholdMs = options.staleThresholdMs ?? 30 * 60 * 1000;
32
- // Relay executor events to external subscribers (SSE, metrics, ...)
33
- const relay = (ev) => {
34
- for (const l of this.listeners) {
35
- try {
36
- l(ev);
37
- }
38
- catch (err) {
39
- logger.debug(`[ExecutionManager] relay failed: ${err}`);
40
- }
41
- }
42
- };
43
- this.foregroundExecutor.subscribe(relay);
44
- this.backgroundExecutor.subscribe(relay);
45
- }
46
- /**
47
- * Create the execution row + kick off orchestrator.
48
- * Returns the newly created execution id.
49
- */
50
- start(input) {
51
- // 后台模式:前置认证探测,给调用方明确错误而不是等 worker 起来后才报错
52
- if (input.mode === 'background') {
53
- const probe = probeWorkerAuth();
54
- if (!probe.available) {
55
- throw new WorkerAuthError(probe);
56
- }
57
- }
58
- const executionId = this.storage.createMethodologyExecution({
59
- session_id: input.session_id,
60
- methodology_id: input.methodology_id,
61
- plan_json: JSON.stringify(input.plan),
62
- mode: input.mode,
63
- trigger_type: input.trigger_type ?? 'manual',
64
- });
65
- // Seed the first phase_executions row so listings show immediately
66
- if (input.plan.phases[0]) {
67
- const p0 = input.plan.phases[0];
68
- this.storage.createPhaseExecution({
69
- methodology_execution_id: executionId,
70
- phase_id: p0.id,
71
- phase_index: 0,
72
- agent_name: p0.agent,
73
- prompt: p0.prompt,
74
- });
75
- }
76
- const executor = input.mode === 'background' ? this.backgroundExecutor : this.foregroundExecutor;
77
- const orch = new Orchestrator(this.storage, executor);
78
- this.activeOrchestrators.set(executionId, orch);
79
- // Fire-and-forget; errors are logged but don't crash the daemon.
80
- orch.run(executionId)
81
- .catch(err => {
82
- logger.warn(`[ExecutionManager] orchestrator for ${executionId} threw: ${err}`);
83
- try {
84
- this.storage.updateMethodologyExecution(executionId, {
85
- status: 'failed',
86
- completed_at: Date.now(),
87
- worker_pid: null,
88
- });
89
- }
90
- catch { /* swallow */ }
91
- })
92
- .finally(() => {
93
- this.activeOrchestrators.delete(executionId);
94
- });
95
- return executionId;
96
- }
97
- async cancel(execution_id) {
98
- const exec = this.storage.getMethodologyExecution(execution_id);
99
- if (!exec)
100
- throw new Error(`execution ${execution_id} not found`);
101
- const executor = exec.mode === 'background'
102
- ? this.backgroundExecutor
103
- : this.foregroundExecutor;
104
- await executor.cancel(execution_id);
105
- }
106
- /**
107
- * Subscribe to all executor events. Returns an unsubscribe function.
108
- */
109
- subscribe(listener) {
110
- this.listeners.add(listener);
111
- return () => { this.listeners.delete(listener); };
112
- }
113
- /**
114
- * At daemon startup: find background executions whose worker_pid is no longer
115
- * alive. Mark them failed so the UI shows a definitive state.
116
- */
117
- reconcileOrphansOnStartup() {
118
- const rows = this.storage.listRunningBackgroundExecutions();
119
- for (const row of rows) {
120
- const pid = row.worker_pid;
121
- const alive = pid ? this.isPidAlive(pid) : false;
122
- if (!alive) {
123
- logger.warn(`[ExecutionManager] execution=${row.id} has stale worker_pid=${pid}; marking failed`);
124
- this.storage.updateMethodologyExecution(row.id, {
125
- status: 'failed',
126
- completed_at: Date.now(),
127
- worker_pid: null,
128
- });
129
- }
130
- }
131
- }
132
- /**
133
- * Cross-session residue sweep: find `mode='foreground' AND status='running'`
134
- * executions whose `started_at` is older than `maxAgeMs`, and mark them
135
- * `cancelled`. Foreground executions are driven by Claude Code sessions — if
136
- * the originating session ended without the Stop hook firing (crash, kill,
137
- * detach, pre-guard legacy data), the row can stay `running` forever and
138
- * blocks the session-level exclusivity check at UserPromptSubmit.
139
- *
140
- * Complements:
141
- * - `reconcileOrphansOnStartup()` handles background workers (pid-based).
142
- * - `sweepStaleMethodologies()` is the softer no-progress sweep (30min → `stale`).
143
- *
144
- * @param maxAgeMs absolute age since started_at. Default 4h.
145
- * @returns list of execution ids that were cancelled.
146
- */
147
- cancelStaleForegroundExecutions(maxAgeMs = 4 * 60 * 60 * 1000) {
148
- const now = Date.now();
149
- const rows = this.storage.listRunningForegroundExecutions();
150
- const cancelled = [];
151
- for (const row of rows) {
152
- const startedAt = row.started_at ?? 0;
153
- const age = now - startedAt;
154
- if (age < maxAgeMs)
155
- continue;
156
- try {
157
- this.storage.updateMethodologyExecution(row.id, {
158
- status: 'cancelled',
159
- completed_at: now,
160
- });
161
- cancelled.push(row.id);
162
- logger.warn(`[ExecutionManager] execution=${row.id} session=${row.session_id} ` +
163
- `foreground residue age=${Math.round(age / 60000)}min > ${Math.round(maxAgeMs / 60000)}min — marked cancelled`);
164
- }
165
- catch (err) {
166
- logger.warn(`[ExecutionManager] cross-session cancel failed for execution=${row.id}: ${err}`);
167
- }
168
- }
169
- return cancelled;
170
- }
171
- isPidAlive(pid) {
172
- try {
173
- process.kill(pid, 0);
174
- return true;
175
- }
176
- catch {
177
- return false;
178
- }
179
- }
180
- /**
181
- * Sweep methodology_executions that are still `running` but haven't made any
182
- * progress for longer than `staleThresholdMs`. Mark them `stale` so the UI
183
- * (and downstream schedulers) stop treating them as active.
184
- *
185
- * 判定依据:`now - (last_progress_at || started_at) > staleThresholdMs`
186
- * 非 running 的 execution 一律忽略。
187
- *
188
- * 返回被标记为 stale 的 execution id 列表,便于测试 / 观测。
189
- */
190
- sweepStaleMethodologies(now = Date.now()) {
191
- const rows = this.storage.getDatabase().prepare(`SELECT id, status, started_at, last_progress_at
192
- FROM methodology_executions
193
- WHERE status = 'running'`).all();
194
- const marked = [];
195
- for (const row of rows) {
196
- const reference = row.last_progress_at ?? row.started_at;
197
- if (reference == null)
198
- continue;
199
- if (now - reference <= this.staleThresholdMs)
200
- continue;
201
- try {
202
- this.storage.updateMethodologyExecution(row.id, {
203
- status: 'stale',
204
- completed_at: now,
205
- });
206
- marked.push(row.id);
207
- logger.warn(`[ExecutionManager] execution=${row.id} marked stale (no progress for ${Math.round((now - reference) / 60_000)}min)`);
208
- }
209
- catch (err) {
210
- logger.warn(`[ExecutionManager] failed to mark execution=${row.id} stale: ${err}`);
211
- }
212
- }
213
- return marked;
214
- }
215
- /**
216
- * Start a periodic reconcile loop — picks up workers killed while daemon
217
- * was running (e.g. user kill -9'd the pid). Safe to call once at startup.
218
- * Returns a stop function.
219
- *
220
- * @param intervalMs reconcile interval in milliseconds
221
- * @param idleTriggerCallback optional callback to trigger idle methodologies
222
- */
223
- startReconcileLoop(intervalMs = 60_000, idleTriggerCallback) {
224
- if (this.reconcileTimer)
225
- clearInterval(this.reconcileTimer);
226
- this.reconcileTimer = setInterval(() => {
227
- try {
228
- this.reconcileOrphansOnStartup();
229
- }
230
- catch (err) {
231
- logger.debug(`[ExecutionManager] periodic reconcile failed: ${err}`);
232
- }
233
- try {
234
- this.sweepStaleMethodologies();
235
- }
236
- catch (err) {
237
- logger.debug(`[ExecutionManager] periodic stale sweep failed: ${err}`);
238
- }
239
- // Trigger idle methodologies if callback provided
240
- if (idleTriggerCallback) {
241
- idleTriggerCallback().catch(err => {
242
- logger.debug(`[ExecutionManager] idle trigger callback failed: ${err}`);
243
- });
244
- }
245
- }, intervalMs);
246
- // Don't keep the event loop alive just for this.
247
- this.reconcileTimer.unref?.();
248
- return () => {
249
- if (this.reconcileTimer)
250
- clearInterval(this.reconcileTimer);
251
- this.reconcileTimer = null;
252
- };
253
- }
254
- stopReconcileLoop() {
255
- if (this.reconcileTimer)
256
- clearInterval(this.reconcileTimer);
257
- this.reconcileTimer = null;
258
- }
259
- }
260
- //# sourceMappingURL=execution-manager.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"execution-manager.js","sourceRoot":"","sources":["../../src/capability/execution-manager.ts"],"names":[],"mappings":"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"}
@@ -1,58 +0,0 @@
1
- /**
2
- * BackgroundExecutor —— spawn `claude -p` 子进程独立执行一个 phase。
3
- *
4
- * 与 ForegroundExecutor 的差异:
5
- * - 真正调用 CLI 并等待结束,所以 `executePhase` 返回的是最终 status
6
- * ('completed' | 'failed'), 不是 'injected'
7
- * - 不会改 pending queue —— 后台执行路径跟 Claude 主会话完全隔离
8
- * - 进度通过 subscribe() 的 ExecutorEvent 对外暴露(SSE 可订阅)
9
- */
10
- import type { MethodologyExecution, PlannedPhase } from '../types.js';
11
- import type { SQLiteStorage } from '../../core/storage/sqlite.js';
12
- import type { MethodologyRegistry } from '../methodology-registry.js';
13
- import type { ExecutorEventListener, PhaseExecutionResult, PhaseExecutor } from './types.js';
14
- export interface BackgroundExecutorOptions {
15
- /** claude CLI 可执行文件路径(默认走 PATH) */
16
- claudeBin?: string;
17
- /** 子进程 cwd;默认为当前项目目录 */
18
- cwd?: string;
19
- /** 项目根目录,作为 --add-dir 的参数;默认走 cwd */
20
- projectRoot?: string;
21
- /** 每个 phase 的最大预算美元(默认 2) */
22
- maxBudgetUsdPerPhase?: number;
23
- /** 权限模式(默认 bypassPermissions) */
24
- permissionMode?: 'acceptEdits' | 'bypassPermissions' | 'default' | 'dontAsk' | 'plan';
25
- /** 默认禁用的工具(逗号分隔) */
26
- disallowedTools?: string;
27
- /** phase 级 wall-clock 超时(ms),默认 30 分钟 */
28
- phaseTimeoutMs?: number;
29
- /** stream-json 日志根目录 */
30
- logDir?: string;
31
- /**
32
- * worker 专用 settings.json 路径。若未提供,会懒生成到
33
- * `$HOME/.claude-forge/worker-settings.json`,内容为 `{"hooks": {}}`。
34
- * 目的:让 worker 子进程跳过 daemon hooks,避免递归触发。
35
- */
36
- settingsPath?: string;
37
- }
38
- export declare class BackgroundExecutor implements PhaseExecutor {
39
- private storage;
40
- private registry;
41
- readonly mode: "background";
42
- private listeners;
43
- private running;
44
- private cancelRequested;
45
- private readonly opts;
46
- constructor(storage: SQLiteStorage, registry: MethodologyRegistry, options?: BackgroundExecutorOptions);
47
- executePhase(execution: MethodologyExecution, phase: PlannedPhase, phaseIndex: number): Promise<PhaseExecutionResult>;
48
- cancel(execution_id: number): Promise<void>;
49
- subscribe(listener: ExecutorEventListener): () => void;
50
- private emit;
51
- /**
52
- * @internal test-only: 构建 spawn 参数。生产代码请走 `executePhase`。
53
- */
54
- buildArgs(): string[];
55
- /** @internal test-only */
56
- getSettingsPath(): string;
57
- }
58
- //# sourceMappingURL=background-executor.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"background-executor.d.ts","sourceRoot":"","sources":["../../../src/capability/executor/background-executor.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAMH,OAAO,KAAK,EAAE,oBAAoB,EAAE,YAAY,EAAiB,MAAM,aAAa,CAAC;AACrF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAClE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AACtE,OAAO,KAAK,EAEV,qBAAqB,EACrB,oBAAoB,EACpB,aAAa,EACd,MAAM,YAAY,CAAC;AAWpB,MAAM,WAAW,yBAAyB;IACxC,mCAAmC;IACnC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,wBAAwB;IACxB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,qCAAqC;IACrC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,6BAA6B;IAC7B,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,iCAAiC;IACjC,cAAc,CAAC,EAAE,aAAa,GAAG,mBAAmB,GAAG,SAAS,GAAG,SAAS,GAAG,MAAM,CAAC;IACtF,oBAAoB;IACpB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,yCAAyC;IACzC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,wBAAwB;IACxB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAcD,qBAAa,kBAAmB,YAAW,aAAa;IAQpD,OAAO,CAAC,OAAO;IACf,OAAO,CAAC,QAAQ;IARlB,QAAQ,CAAC,IAAI,EAAG,YAAY,CAAU;IACtC,OAAO,CAAC,SAAS,CAAoC;IACrD,OAAO,CAAC,OAAO,CAAqD;IACpE,OAAO,CAAC,eAAe,CAAqB;IAC5C,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAsF;gBAGjG,OAAO,EAAE,aAAa,EACtB,QAAQ,EAAE,mBAAmB,EACrC,OAAO,GAAE,yBAA8B;IAiBnC,YAAY,CAChB,SAAS,EAAE,oBAAoB,EAC/B,KAAK,EAAE,YAAY,EACnB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,oBAAoB,CAAC;IAoO1B,MAAM,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAuBjD,SAAS,CAAC,QAAQ,EAAE,qBAAqB,GAAG,MAAM,IAAI;IAKtD,OAAO,CAAC,IAAI;IAMZ;;OAEG;IACH,SAAS,IAAI,MAAM,EAAE;IAoBrB,0BAA0B;IAC1B,eAAe,IAAI,MAAM;CAG1B"}
@@ -1,322 +0,0 @@
1
- /**
2
- * BackgroundExecutor —— spawn `claude -p` 子进程独立执行一个 phase。
3
- *
4
- * 与 ForegroundExecutor 的差异:
5
- * - 真正调用 CLI 并等待结束,所以 `executePhase` 返回的是最终 status
6
- * ('completed' | 'failed'), 不是 'injected'
7
- * - 不会改 pending queue —— 后台执行路径跟 Claude 主会话完全隔离
8
- * - 进度通过 subscribe() 的 ExecutorEvent 对外暴露(SSE 可订阅)
9
- */
10
- import { spawn } from 'node:child_process';
11
- import { mkdirSync, existsSync, createWriteStream, writeFileSync } from 'node:fs';
12
- import { homedir } from 'node:os';
13
- import path from 'node:path';
14
- import { formatMethodologyPhaseDirectiveForWorker } from '../../daemon/handlers/methodology-formatter.js';
15
- import { parseStreamLine, extractResultText, extractToolUses, isTerminal, } from './stream-parser.js';
16
- import { logger } from '../../core/utils/logger.js';
17
- const DEFAULT_LOG_DIR = path.join(homedir(), '.claude-forge', 'logs');
18
- const DEFAULT_WORKER_SETTINGS_PATH = path.join(homedir(), '.claude-forge', 'worker-settings.json');
19
- /**
20
- * 确保 worker settings 文件存在。已存在则复用(不覆写),不存在则以 `{"hooks": {}}` 创建。
21
- */
22
- function ensureWorkerSettingsFile(target) {
23
- if (existsSync(target))
24
- return;
25
- mkdirSync(path.dirname(target), { recursive: true });
26
- writeFileSync(target, JSON.stringify({ hooks: {} }, null, 2), 'utf-8');
27
- }
28
- export class BackgroundExecutor {
29
- storage;
30
- registry;
31
- mode = 'background';
32
- listeners = new Set();
33
- running = new Map();
34
- cancelRequested = new Set();
35
- opts;
36
- constructor(storage, registry, options = {}) {
37
- this.storage = storage;
38
- this.registry = registry;
39
- const settingsPath = options.settingsPath ?? DEFAULT_WORKER_SETTINGS_PATH;
40
- ensureWorkerSettingsFile(settingsPath);
41
- this.opts = {
42
- claudeBin: options.claudeBin ?? 'claude',
43
- cwd: options.cwd ?? process.cwd(),
44
- projectRoot: options.projectRoot,
45
- maxBudgetUsdPerPhase: options.maxBudgetUsdPerPhase ?? 2,
46
- permissionMode: options.permissionMode ?? 'bypassPermissions',
47
- disallowedTools: options.disallowedTools ?? 'WebFetch,WebSearch',
48
- phaseTimeoutMs: options.phaseTimeoutMs ?? 30 * 60 * 1000,
49
- logDir: options.logDir ?? DEFAULT_LOG_DIR,
50
- settingsPath,
51
- };
52
- }
53
- async executePhase(execution, phase, phaseIndex) {
54
- const methodology = this.registry.get(execution.methodology_id);
55
- if (!methodology) {
56
- return {
57
- status: 'failed',
58
- phase_index: phaseIndex,
59
- phase_id: phase.id,
60
- error: `methodology ${execution.methodology_id} not found`,
61
- };
62
- }
63
- const plan = JSON.parse(execution.plan_json);
64
- const directive = formatMethodologyPhaseDirectiveForWorker(methodology, plan, phase, phaseIndex, execution.id);
65
- // Find or create the phase_executions row for this phase
66
- const existing = this.storage.getPhaseExecutions(execution.id).find(p => p.phase_index === phaseIndex);
67
- const phaseExecId = existing?.id ?? this.storage.createPhaseExecution({
68
- methodology_execution_id: execution.id,
69
- phase_id: phase.id,
70
- phase_index: phaseIndex,
71
- agent_name: phase.agent,
72
- prompt: phase.prompt,
73
- });
74
- if (!existsSync(this.opts.logDir))
75
- mkdirSync(this.opts.logDir, { recursive: true });
76
- const logPath = path.join(this.opts.logDir, `execution-${execution.id}-phase-${phaseIndex}.jsonl`);
77
- this.storage.updatePhaseExecution(phaseExecId, { stream_log_path: logPath });
78
- this.emit({
79
- type: 'phase-start',
80
- execution_id: execution.id,
81
- phase_index: phaseIndex,
82
- phase_id: phase.id,
83
- timestamp: Date.now(),
84
- payload: { mode: 'background', agent: phase.agent, logPath },
85
- });
86
- const startedAt = Date.now();
87
- const args = this.buildArgs();
88
- logger.info(`[BackgroundExecutor] spawn claude -p for execution=${execution.id} phase=${phaseIndex} (${phase.id})`);
89
- let stdoutBuf = '';
90
- let finalText = null;
91
- let capturedSessionId = null;
92
- let capturedErrorMessage = null;
93
- let exitReason = 'completed';
94
- const child = spawn(this.opts.claudeBin, args, {
95
- cwd: this.opts.cwd,
96
- env: { ...process.env, CLAUDE_FORGE_WORKER: '1' },
97
- stdio: ['pipe', 'pipe', 'pipe'],
98
- });
99
- this.running.set(execution.id, child);
100
- this.storage.updateMethodologyExecution(execution.id, {
101
- worker_pid: child.pid ?? null,
102
- last_progress_at: Date.now(),
103
- });
104
- const logStream = createWriteStream(logPath, { flags: 'a' });
105
- const handleStreamEvent = (event) => {
106
- // Capture session id from init event
107
- if (event.type === 'system' && 'session_id' in event && typeof event.session_id === 'string') {
108
- capturedSessionId = event.session_id;
109
- this.storage.updateMethodologyExecution(execution.id, {
110
- worker_session_id: event.session_id,
111
- last_progress_at: Date.now(),
112
- });
113
- }
114
- // Tool uses — emit as phase-log for observability
115
- const tools = extractToolUses(event);
116
- for (const t of tools) {
117
- this.emit({
118
- type: 'phase-log',
119
- execution_id: execution.id,
120
- phase_index: phaseIndex,
121
- phase_id: phase.id,
122
- timestamp: Date.now(),
123
- payload: { kind: 'tool_use', tool: t.name, input: t.input },
124
- });
125
- }
126
- // Result event — terminal
127
- if (event.type === 'result') {
128
- const txt = extractResultText(event);
129
- if (txt)
130
- finalText = txt;
131
- if ('is_error' in event && event.is_error) {
132
- exitReason = 'failed';
133
- capturedErrorMessage = typeof event.result === 'string' ? event.result : 'claude -p reported error';
134
- }
135
- }
136
- };
137
- const waitForExit = new Promise((resolve) => {
138
- let timer = setTimeout(() => {
139
- logger.warn(`[BackgroundExecutor] phase ${phaseIndex} timed out`);
140
- exitReason = 'timeout';
141
- capturedErrorMessage = `phase exceeded ${this.opts.phaseTimeoutMs}ms wall-clock timeout`;
142
- child.kill('SIGTERM');
143
- }, this.opts.phaseTimeoutMs);
144
- child.stdout.on('data', (chunk) => {
145
- const text = chunk.toString('utf-8');
146
- stdoutBuf += text;
147
- logStream.write(text);
148
- let newlineIdx;
149
- while ((newlineIdx = stdoutBuf.indexOf('\n')) >= 0) {
150
- const line = stdoutBuf.slice(0, newlineIdx);
151
- stdoutBuf = stdoutBuf.slice(newlineIdx + 1);
152
- const parsed = parseStreamLine(line);
153
- if (parsed.event) {
154
- handleStreamEvent(parsed.event);
155
- if (isTerminal(parsed.event)) {
156
- // result event — child should exit on its own shortly
157
- }
158
- }
159
- }
160
- });
161
- child.stderr.on('data', (chunk) => {
162
- const text = chunk.toString('utf-8');
163
- logStream.write(`[stderr] ${text}`);
164
- logger.debug(`[BackgroundExecutor][stderr] ${text.slice(0, 200)}`);
165
- });
166
- child.on('error', (err) => {
167
- logger.warn(`[BackgroundExecutor] spawn error: ${err.message}`);
168
- exitReason = 'failed';
169
- capturedErrorMessage = `spawn failed: ${err.message}`;
170
- if (timer) {
171
- clearTimeout(timer);
172
- timer = null;
173
- }
174
- resolve();
175
- });
176
- child.on('exit', (code, signal) => {
177
- if (timer) {
178
- clearTimeout(timer);
179
- timer = null;
180
- }
181
- logger.info(`[BackgroundExecutor] phase ${phaseIndex} child exit code=${code} signal=${signal}`);
182
- if (exitReason === 'timeout') {
183
- // already set
184
- }
185
- else if (this.cancelRequested.has(execution.id) ||
186
- signal === 'SIGKILL' ||
187
- signal === 'SIGTERM' ||
188
- // macOS 下 SIGTERM 杀掉的 child,Node 会报 code=143 (128+SIGTERM) / signal=null,
189
- // SIGKILL 对应 code=137。无论 signal 字段形状如何,都认定为 cancelled。
190
- code === 143 ||
191
- code === 137) {
192
- // manual cancel takes priority over natural completion semantics
193
- if (exitReason === 'completed')
194
- exitReason = 'cancelled';
195
- }
196
- else if (code !== 0 && exitReason === 'completed') {
197
- exitReason = 'failed';
198
- capturedErrorMessage = capturedErrorMessage ?? `claude -p exited with code ${code}`;
199
- }
200
- resolve();
201
- });
202
- });
203
- child.stdin.end(directive);
204
- await waitForExit;
205
- this.running.delete(execution.id);
206
- this.cancelRequested.delete(execution.id);
207
- logStream.end();
208
- const durationMs = Date.now() - startedAt;
209
- // exitReason 的三路映射:
210
- // completed -> 'completed'
211
- // cancelled -> 'cancelled'(需要 schema 支持,见 runMigrations)
212
- // failed/timeout -> 'failed'
213
- const phaseStatus = exitReason === 'completed'
214
- ? 'completed'
215
- : exitReason === 'cancelled'
216
- ? 'cancelled'
217
- : 'failed';
218
- this.storage.updatePhaseExecution(phaseExecId, {
219
- status: phaseStatus,
220
- completed_at: Date.now(),
221
- output_text: finalText ?? null,
222
- error_message: capturedErrorMessage,
223
- });
224
- this.emit({
225
- type: 'phase-finish',
226
- execution_id: execution.id,
227
- phase_index: phaseIndex,
228
- phase_id: phase.id,
229
- timestamp: Date.now(),
230
- payload: { status: phaseStatus, duration_ms: durationMs, session_id: capturedSessionId },
231
- });
232
- if (exitReason === 'completed') {
233
- return {
234
- status: 'completed',
235
- phase_index: phaseIndex,
236
- phase_id: phase.id,
237
- output_text: finalText ?? undefined,
238
- duration_ms: durationMs,
239
- };
240
- }
241
- if (exitReason === 'cancelled') {
242
- return {
243
- status: 'cancelled',
244
- phase_index: phaseIndex,
245
- phase_id: phase.id,
246
- error: capturedErrorMessage ?? 'cancelled',
247
- duration_ms: durationMs,
248
- };
249
- }
250
- return {
251
- status: 'failed',
252
- phase_index: phaseIndex,
253
- phase_id: phase.id,
254
- error: capturedErrorMessage ?? 'unknown failure',
255
- duration_ms: durationMs,
256
- };
257
- }
258
- async cancel(execution_id) {
259
- this.cancelRequested.add(execution_id);
260
- const child = this.running.get(execution_id);
261
- if (child) {
262
- logger.info(`[BackgroundExecutor] kill worker pid=${child.pid} for execution=${execution_id}`);
263
- child.kill('SIGTERM');
264
- setTimeout(() => {
265
- if (!child.killed)
266
- child.kill('SIGKILL');
267
- }, 5000);
268
- }
269
- this.storage.updateMethodologyExecution(execution_id, {
270
- status: 'cancelled',
271
- completed_at: Date.now(),
272
- worker_pid: null,
273
- });
274
- this.emit({
275
- type: 'done',
276
- execution_id,
277
- timestamp: Date.now(),
278
- payload: { reason: 'cancelled' },
279
- });
280
- }
281
- subscribe(listener) {
282
- this.listeners.add(listener);
283
- return () => { this.listeners.delete(listener); };
284
- }
285
- emit(event) {
286
- for (const l of this.listeners) {
287
- try {
288
- l(event);
289
- }
290
- catch (err) {
291
- logger.debug(`[BackgroundExecutor] listener failed: ${err}`);
292
- }
293
- }
294
- }
295
- /**
296
- * @internal test-only: 构建 spawn 参数。生产代码请走 `executePhase`。
297
- */
298
- buildArgs() {
299
- const args = [
300
- '-p',
301
- '--output-format', 'stream-json',
302
- '--verbose', // required for stream-json with -p
303
- '--include-hook-events',
304
- '--permission-mode', this.opts.permissionMode,
305
- '--max-budget-usd', String(this.opts.maxBudgetUsdPerPhase),
306
- '--no-session-persistence',
307
- '--settings', this.opts.settingsPath, // worker 用空 hooks settings,避免再触发 daemon hook
308
- ];
309
- if (this.opts.disallowedTools) {
310
- args.push('--disallowed-tools', this.opts.disallowedTools);
311
- }
312
- if (this.opts.projectRoot) {
313
- args.push('--add-dir', this.opts.projectRoot);
314
- }
315
- return args;
316
- }
317
- /** @internal test-only */
318
- getSettingsPath() {
319
- return this.opts.settingsPath;
320
- }
321
- }
322
- //# sourceMappingURL=background-executor.js.map