@winspan/claude-forge 8.26.3 → 8.28.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.
- package/dist/capability/index.d.ts +9 -0
- package/dist/capability/index.d.ts.map +1 -0
- package/dist/capability/index.js +9 -0
- package/dist/capability/index.js.map +1 -0
- package/dist/capability/methodologies/bmad.yaml +69 -0
- package/dist/capability/methodologies/harness-engineering.yaml +69 -0
- package/dist/capability/methodology-planner.d.ts +33 -0
- package/dist/capability/methodology-planner.d.ts.map +1 -0
- package/dist/capability/methodology-planner.js +178 -0
- package/dist/capability/methodology-planner.js.map +1 -0
- package/dist/capability/methodology-registry.d.ts +32 -0
- package/dist/capability/methodology-registry.d.ts.map +1 -0
- package/dist/capability/methodology-registry.js +97 -0
- package/dist/capability/methodology-registry.js.map +1 -0
- package/dist/capability/types.d.ts +68 -0
- package/dist/capability/types.d.ts.map +1 -0
- package/dist/capability/types.js +7 -0
- package/dist/capability/types.js.map +1 -0
- package/dist/core/storage/schema.sql +40 -0
- package/dist/core/storage/sqlite.d.ts +25 -0
- package/dist/core/storage/sqlite.d.ts.map +1 -1
- package/dist/core/storage/sqlite.js +87 -0
- package/dist/core/storage/sqlite.js.map +1 -1
- package/dist/daemon/handlers/methodology-formatter.d.ts +9 -0
- package/dist/daemon/handlers/methodology-formatter.d.ts.map +1 -0
- package/dist/daemon/handlers/methodology-formatter.js +73 -0
- package/dist/daemon/handlers/methodology-formatter.js.map +1 -0
- package/dist/daemon/handlers/post-tool-use.d.ts +9 -1
- package/dist/daemon/handlers/post-tool-use.d.ts.map +1 -1
- package/dist/daemon/handlers/post-tool-use.js +94 -2
- package/dist/daemon/handlers/post-tool-use.js.map +1 -1
- package/dist/daemon/handlers/pre-tool-use.d.ts +5 -0
- package/dist/daemon/handlers/pre-tool-use.d.ts.map +1 -1
- package/dist/daemon/handlers/pre-tool-use.js +88 -0
- package/dist/daemon/handlers/pre-tool-use.js.map +1 -1
- package/dist/daemon/handlers/user-prompt.d.ts +5 -1
- package/dist/daemon/handlers/user-prompt.d.ts.map +1 -1
- package/dist/daemon/handlers/user-prompt.js +106 -16
- package/dist/daemon/handlers/user-prompt.js.map +1 -1
- package/dist/daemon/index.d.ts.map +1 -1
- package/dist/daemon/index.js +8 -2
- package/dist/daemon/index.js.map +1 -1
- package/dist/daemon/routing-state.d.ts +49 -0
- package/dist/daemon/routing-state.d.ts.map +1 -0
- package/dist/daemon/routing-state.js +189 -0
- package/dist/daemon/routing-state.js.map +1 -0
- package/dist/engine/conventions/routing.yaml +5 -0
- package/dist/web/server.d.ts.map +1 -1
- package/dist/web/server.js +122 -1
- package/dist/web/server.js.map +1 -1
- package/dist/web/static/assets/index-CtylfoaN.css +1 -0
- package/dist/web/static/assets/index-DnaQt27h.js +388 -0
- package/dist/web/static/assets/index-DnaQt27h.js.map +1 -0
- package/dist/web/static/index.html +12 -2971
- package/package.json +5 -2
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RoutingState — in-memory session routing state for PreToolUse enforcement
|
|
3
|
+
*
|
|
4
|
+
* Tracks which sessions have active routing decisions, so PreToolUse can
|
|
5
|
+
* enforce "must use Agent tool" when Claude tries to bypass the routing.
|
|
6
|
+
*/
|
|
7
|
+
import { logger } from '../core/utils/logger.js';
|
|
8
|
+
// Auto-cleanup stale routing state after 30 minutes
|
|
9
|
+
const ROUTING_STATE_TTL_MS = 30 * 60 * 1000;
|
|
10
|
+
/**
|
|
11
|
+
* Global routing state manager (singleton pattern)
|
|
12
|
+
*/
|
|
13
|
+
class RoutingStateManager {
|
|
14
|
+
state = new Map();
|
|
15
|
+
/**
|
|
16
|
+
* Record that a session has been routed to a specific agent
|
|
17
|
+
*/
|
|
18
|
+
setRouting(sessionId, decision) {
|
|
19
|
+
try {
|
|
20
|
+
// Parameter validation
|
|
21
|
+
if (!sessionId || typeof sessionId !== 'string') {
|
|
22
|
+
logger.error(`[RoutingState] setRouting: invalid sessionId: ${JSON.stringify(sessionId)}`);
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
if (!decision || typeof decision !== 'object') {
|
|
26
|
+
logger.error(`[RoutingState] setRouting: invalid decision object for session ${sessionId}`);
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
if (!decision.agentName || typeof decision.agentName !== 'string') {
|
|
30
|
+
logger.error(`[RoutingState] setRouting: invalid agentName for session ${sessionId}: ${JSON.stringify(decision)}`);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
if (!decision.routeRequestId || typeof decision.routeRequestId !== 'string') {
|
|
34
|
+
logger.error(`[RoutingState] setRouting: invalid routeRequestId for session ${sessionId}: ${JSON.stringify(decision)}`);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
if (typeof decision.timestamp !== 'number' || decision.timestamp <= 0) {
|
|
38
|
+
logger.error(`[RoutingState] setRouting: invalid timestamp for session ${sessionId}: ${JSON.stringify(decision)}`);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
this.state.set(sessionId, decision);
|
|
42
|
+
logger.debug(`[RoutingState] Set routing for session ${sessionId} → ${decision.agentName}`);
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
logger.error(`[RoutingState] setRouting: unexpected error for session ${sessionId}: ${error instanceof Error ? error.message : String(error)}`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Get the active routing decision for a session (if any)
|
|
50
|
+
* Returns null if no routing exists or if routing has expired
|
|
51
|
+
*/
|
|
52
|
+
getRouting(sessionId) {
|
|
53
|
+
try {
|
|
54
|
+
// Parameter validation
|
|
55
|
+
if (!sessionId || typeof sessionId !== 'string') {
|
|
56
|
+
logger.error(`[RoutingState] getRouting: invalid sessionId: ${JSON.stringify(sessionId)}`);
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
const decision = this.state.get(sessionId);
|
|
60
|
+
if (!decision)
|
|
61
|
+
return null;
|
|
62
|
+
// Check if routing has expired
|
|
63
|
+
const age = Date.now() - decision.timestamp;
|
|
64
|
+
if (age > ROUTING_STATE_TTL_MS) {
|
|
65
|
+
logger.debug(`[RoutingState] Routing expired for session ${sessionId} (age: ${Math.round(age / 1000)}s)`);
|
|
66
|
+
this.state.delete(sessionId);
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
return decision;
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
logger.error(`[RoutingState] getRouting: unexpected error for session ${sessionId}: ${error instanceof Error ? error.message : String(error)}`);
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Clear routing state for a session (called after agent is invoked or session ends)
|
|
78
|
+
*/
|
|
79
|
+
clearRouting(sessionId) {
|
|
80
|
+
try {
|
|
81
|
+
// Parameter validation
|
|
82
|
+
if (!sessionId || typeof sessionId !== 'string') {
|
|
83
|
+
logger.error(`[RoutingState] clearRouting: invalid sessionId: ${JSON.stringify(sessionId)}`);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
const decision = this.state.get(sessionId);
|
|
87
|
+
if (decision) {
|
|
88
|
+
logger.debug(`[RoutingState] Cleared routing for session ${sessionId} (was: ${decision.agentName})`);
|
|
89
|
+
}
|
|
90
|
+
this.state.delete(sessionId);
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
logger.error(`[RoutingState] clearRouting: unexpected error for session ${sessionId}: ${error instanceof Error ? error.message : String(error)}`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Check if a session has an active routing decision
|
|
98
|
+
*/
|
|
99
|
+
hasRouting(sessionId) {
|
|
100
|
+
try {
|
|
101
|
+
// Parameter validation
|
|
102
|
+
if (!sessionId || typeof sessionId !== 'string') {
|
|
103
|
+
logger.error(`[RoutingState] hasRouting: invalid sessionId: ${JSON.stringify(sessionId)}`);
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
return this.getRouting(sessionId) !== null;
|
|
107
|
+
}
|
|
108
|
+
catch (error) {
|
|
109
|
+
logger.error(`[RoutingState] hasRouting: unexpected error for session ${sessionId}: ${error instanceof Error ? error.message : String(error)}`);
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Get statistics about current routing state (for debugging/monitoring)
|
|
115
|
+
*/
|
|
116
|
+
getStats() {
|
|
117
|
+
try {
|
|
118
|
+
if (this.state.size === 0) {
|
|
119
|
+
return { totalSessions: 0, oldestAge: null };
|
|
120
|
+
}
|
|
121
|
+
const now = Date.now();
|
|
122
|
+
let oldestAge = 0;
|
|
123
|
+
for (const decision of this.state.values()) {
|
|
124
|
+
// Validate decision object
|
|
125
|
+
if (!decision || typeof decision.timestamp !== 'number') {
|
|
126
|
+
logger.warn(`[RoutingState] getStats: invalid decision object in state: ${JSON.stringify(decision)}`);
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
const age = now - decision.timestamp;
|
|
130
|
+
if (age > oldestAge)
|
|
131
|
+
oldestAge = age;
|
|
132
|
+
}
|
|
133
|
+
return {
|
|
134
|
+
totalSessions: this.state.size,
|
|
135
|
+
oldestAge: Math.round(oldestAge / 1000), // seconds
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
catch (error) {
|
|
139
|
+
logger.error(`[RoutingState] getStats: unexpected error: ${error instanceof Error ? error.message : String(error)}`);
|
|
140
|
+
return { totalSessions: 0, oldestAge: null };
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Cleanup expired routing states (called periodically by daemon)
|
|
145
|
+
*/
|
|
146
|
+
cleanup() {
|
|
147
|
+
try {
|
|
148
|
+
const now = Date.now();
|
|
149
|
+
let removed = 0;
|
|
150
|
+
for (const [sessionId, decision] of this.state.entries()) {
|
|
151
|
+
try {
|
|
152
|
+
// Validate decision object
|
|
153
|
+
if (!decision || typeof decision.timestamp !== 'number') {
|
|
154
|
+
logger.warn(`[RoutingState] cleanup: invalid decision object for session ${sessionId}, removing: ${JSON.stringify(decision)}`);
|
|
155
|
+
this.state.delete(sessionId);
|
|
156
|
+
removed++;
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
const age = now - decision.timestamp;
|
|
160
|
+
if (age > ROUTING_STATE_TTL_MS) {
|
|
161
|
+
this.state.delete(sessionId);
|
|
162
|
+
removed++;
|
|
163
|
+
logger.debug(`[RoutingState] Cleaned up expired routing for session ${sessionId}`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
catch (error) {
|
|
167
|
+
logger.error(`[RoutingState] cleanup: error processing session ${sessionId}, removing: ${error instanceof Error ? error.message : String(error)}`);
|
|
168
|
+
this.state.delete(sessionId);
|
|
169
|
+
removed++;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
if (removed > 0) {
|
|
173
|
+
logger.info(`[RoutingState] Cleanup: removed ${removed} expired routing state(s)`);
|
|
174
|
+
}
|
|
175
|
+
return removed;
|
|
176
|
+
}
|
|
177
|
+
catch (error) {
|
|
178
|
+
logger.error(`[RoutingState] cleanup: unexpected error: ${error instanceof Error ? error.message : String(error)}`);
|
|
179
|
+
return 0;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
// Singleton instance
|
|
184
|
+
export const routingState = new RoutingStateManager();
|
|
185
|
+
// Auto-cleanup every 10 minutes
|
|
186
|
+
setInterval(() => {
|
|
187
|
+
routingState.cleanup();
|
|
188
|
+
}, 10 * 60 * 1000).unref();
|
|
189
|
+
//# sourceMappingURL=routing-state.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"routing-state.js","sourceRoot":"","sources":["../../src/daemon/routing-state.ts"],"names":[],"mappings":"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"}
|
|
@@ -137,8 +137,13 @@ rules:
|
|
|
137
137
|
- when:
|
|
138
138
|
taskType: compare_solutions
|
|
139
139
|
action: { type: route_to_agent, name: researcher }
|
|
140
|
+
# Only route "explain" when the topic is complex enough to warrant a
|
|
141
|
+
# researcher dive. Plain clarification questions ("what does X mean",
|
|
142
|
+
# "does this work?") should stay with the main Claude — routing them out
|
|
143
|
+
# just produces disobey events since the main session will answer inline.
|
|
140
144
|
- when:
|
|
141
145
|
taskType: explain
|
|
146
|
+
complexity: complex
|
|
142
147
|
action: { type: route_to_agent, name: researcher }
|
|
143
148
|
|
|
144
149
|
# -- Documentation ---------------------------------------------------------
|
package/dist/web/server.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/web/server.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAQH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAC/D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAC3D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAC7D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAS3D,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,aAAa,CAAC;IACvB,UAAU,EAAE,UAAU,CAAC;IACvB,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,MAAM,CAAC,EAAE,aAAa,CAAC;CACxB;AAED,qBAAa,SAAS;IAMR,OAAO,CAAC,OAAO;IAL3B,OAAO,CAAC,GAAG,CAAsB;IACjC,OAAO,CAAC,MAAM,CAA0D;IACxE,OAAO,CAAC,MAAM,CAAC,CAAgB;IAC/B,OAAO,CAAC,MAAM,CAAC,CAAc;gBAET,OAAO,EAAE,gBAAgB;IAQ7C,OAAO,CAAC,WAAW;
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/web/server.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAQH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAC/D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAC3D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAC7D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAS3D,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,aAAa,CAAC;IACvB,UAAU,EAAE,UAAU,CAAC;IACvB,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,MAAM,CAAC,EAAE,aAAa,CAAC;CACxB;AAED,qBAAa,SAAS;IAMR,OAAO,CAAC,OAAO;IAL3B,OAAO,CAAC,GAAG,CAAsB;IACjC,OAAO,CAAC,MAAM,CAA0D;IACxE,OAAO,CAAC,MAAM,CAAC,CAAgB;IAC/B,OAAO,CAAC,MAAM,CAAC,CAAc;gBAET,OAAO,EAAE,gBAAgB;IAQ7C,OAAO,CAAC,WAAW;IA+6Eb,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAgBtB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;CAO5B"}
|
package/dist/web/server.js
CHANGED
|
@@ -59,8 +59,23 @@ export class WebServer {
|
|
|
59
59
|
];
|
|
60
60
|
const staticDir = candidates.find(dir => fs.existsSync(dir));
|
|
61
61
|
if (staticDir) {
|
|
62
|
-
|
|
62
|
+
// Static files with content-hash in filename: long cache
|
|
63
|
+
// index.html: no cache (always fetch latest)
|
|
64
|
+
this.app.use(express.static(staticDir, {
|
|
65
|
+
setHeaders: (res, filePath) => {
|
|
66
|
+
if (filePath.endsWith('index.html')) {
|
|
67
|
+
res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate');
|
|
68
|
+
res.setHeader('Pragma', 'no-cache');
|
|
69
|
+
res.setHeader('Expires', '0');
|
|
70
|
+
}
|
|
71
|
+
else if (filePath.includes('/assets/')) {
|
|
72
|
+
// Vite assets are content-hashed, safe to cache forever
|
|
73
|
+
res.setHeader('Cache-Control', 'public, max-age=31536000, immutable');
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
}));
|
|
63
77
|
this.app.get('/', (_req, res) => {
|
|
78
|
+
res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate');
|
|
64
79
|
res.sendFile(path.join(staticDir, 'index.html'));
|
|
65
80
|
});
|
|
66
81
|
logger.info(`[Web] Serving static files from ${staticDir}`);
|
|
@@ -638,6 +653,99 @@ export class WebServer {
|
|
|
638
653
|
const rows = storage.queryRoutingEvents(filter);
|
|
639
654
|
res.json(rows);
|
|
640
655
|
});
|
|
656
|
+
// Methodology Executions: list all executions
|
|
657
|
+
this.app.get('/api/methodology-executions', (req, res) => {
|
|
658
|
+
const limit = parseInt(req.query.limit) || 50;
|
|
659
|
+
const sessionId = req.query.session;
|
|
660
|
+
const db = storage.getDatabase();
|
|
661
|
+
const query = sessionId
|
|
662
|
+
? 'SELECT * FROM methodology_executions WHERE session_id = ? ORDER BY started_at DESC LIMIT ?'
|
|
663
|
+
: 'SELECT * FROM methodology_executions ORDER BY started_at DESC LIMIT ?';
|
|
664
|
+
const params = sessionId ? [sessionId, limit] : [limit];
|
|
665
|
+
const executions = db.prepare(query).all(...params);
|
|
666
|
+
res.json(executions);
|
|
667
|
+
});
|
|
668
|
+
// Methodology Execution Detail: get single execution with phases
|
|
669
|
+
this.app.get('/api/methodology-executions/:id', (req, res) => {
|
|
670
|
+
const executionId = parseInt(req.params.id);
|
|
671
|
+
const db = storage.getDatabase();
|
|
672
|
+
const execution = db.prepare('SELECT * FROM methodology_executions WHERE id = ?').get(executionId);
|
|
673
|
+
if (!execution) {
|
|
674
|
+
res.status(404).json({ error: 'Execution not found' });
|
|
675
|
+
return;
|
|
676
|
+
}
|
|
677
|
+
const phases = db.prepare(`
|
|
678
|
+
SELECT * FROM phase_executions
|
|
679
|
+
WHERE methodology_execution_id = ?
|
|
680
|
+
ORDER BY phase_index ASC
|
|
681
|
+
`).all(executionId);
|
|
682
|
+
res.json({
|
|
683
|
+
...execution,
|
|
684
|
+
plan: JSON.parse(execution.plan_json),
|
|
685
|
+
phases,
|
|
686
|
+
});
|
|
687
|
+
});
|
|
688
|
+
// Methodology Execution: cancel a running execution
|
|
689
|
+
this.app.post('/api/methodology-executions/:id/cancel', (req, res) => {
|
|
690
|
+
const executionId = parseInt(req.params.id);
|
|
691
|
+
const db = storage.getDatabase();
|
|
692
|
+
const execution = db.prepare('SELECT * FROM methodology_executions WHERE id = ?').get(executionId);
|
|
693
|
+
if (!execution) {
|
|
694
|
+
res.status(404).json({ error: 'Execution not found' });
|
|
695
|
+
return;
|
|
696
|
+
}
|
|
697
|
+
if (execution.status !== 'running') {
|
|
698
|
+
res.status(400).json({ error: `Cannot cancel execution in ${execution.status} state` });
|
|
699
|
+
return;
|
|
700
|
+
}
|
|
701
|
+
db.prepare(`
|
|
702
|
+
UPDATE methodology_executions
|
|
703
|
+
SET status = 'cancelled', completed_at = ?
|
|
704
|
+
WHERE id = ?
|
|
705
|
+
`).run(Date.now(), executionId);
|
|
706
|
+
// Also mark any running phase as cancelled
|
|
707
|
+
db.prepare(`
|
|
708
|
+
UPDATE phase_executions
|
|
709
|
+
SET status = 'cancelled', completed_at = ?
|
|
710
|
+
WHERE methodology_execution_id = ? AND status = 'running'
|
|
711
|
+
`).run(Date.now(), executionId);
|
|
712
|
+
res.json({ success: true, message: 'Execution cancelled' });
|
|
713
|
+
});
|
|
714
|
+
// Methodology Execution: delete an execution
|
|
715
|
+
this.app.delete('/api/methodology-executions/:id', (req, res) => {
|
|
716
|
+
const executionId = parseInt(req.params.id);
|
|
717
|
+
const db = storage.getDatabase();
|
|
718
|
+
db.prepare('DELETE FROM phase_executions WHERE methodology_execution_id = ?').run(executionId);
|
|
719
|
+
const result = db.prepare('DELETE FROM methodology_executions WHERE id = ?').run(executionId);
|
|
720
|
+
if (result.changes === 0) {
|
|
721
|
+
res.status(404).json({ error: 'Execution not found' });
|
|
722
|
+
return;
|
|
723
|
+
}
|
|
724
|
+
res.json({ success: true });
|
|
725
|
+
});
|
|
726
|
+
// Methodology: list all available methodologies
|
|
727
|
+
this.app.get('/api/methodologies', (_req, res) => {
|
|
728
|
+
try {
|
|
729
|
+
const candidates = [
|
|
730
|
+
path.join(__dirname, '../capability/methodologies'),
|
|
731
|
+
path.join(__dirname, '../../src/capability/methodologies'),
|
|
732
|
+
];
|
|
733
|
+
const dir = candidates.find(d => fs.existsSync(d));
|
|
734
|
+
if (!dir) {
|
|
735
|
+
res.json([]);
|
|
736
|
+
return;
|
|
737
|
+
}
|
|
738
|
+
const files = fs.readdirSync(dir).filter(f => f.endsWith('.yaml'));
|
|
739
|
+
const methodologies = files.map(f => {
|
|
740
|
+
const content = fs.readFileSync(path.join(dir, f), 'utf-8');
|
|
741
|
+
return yaml.load(content);
|
|
742
|
+
});
|
|
743
|
+
res.json(methodologies);
|
|
744
|
+
}
|
|
745
|
+
catch (err) {
|
|
746
|
+
res.status(500).json({ error: err.message });
|
|
747
|
+
}
|
|
748
|
+
});
|
|
641
749
|
// Refusal clustering (Plan A: SQL GROUP BY by taskType × agent)
|
|
642
750
|
this.app.get('/api/routing/refusals', (req, res) => {
|
|
643
751
|
const windowHours = parseInt(req.query.window || '168');
|
|
@@ -2104,6 +2212,19 @@ Return ONLY a JSON object with this exact structure (no markdown, no explanation
|
|
|
2104
2212
|
storage.removeListener('decision', onDecision);
|
|
2105
2213
|
});
|
|
2106
2214
|
});
|
|
2215
|
+
// SPA fallback: serve index.html for any non-API route
|
|
2216
|
+
// (must be the LAST route to not interfere with API routes)
|
|
2217
|
+
const spaCandidates = [
|
|
2218
|
+
path.join(__dirname, 'static'),
|
|
2219
|
+
path.join(__dirname, '../../src/web/static'),
|
|
2220
|
+
];
|
|
2221
|
+
const spaStaticDir = spaCandidates.find(dir => fs.existsSync(dir));
|
|
2222
|
+
if (spaStaticDir) {
|
|
2223
|
+
this.app.get(/^(?!\/api).*$/, (_req, res) => {
|
|
2224
|
+
res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate');
|
|
2225
|
+
res.sendFile(path.join(spaStaticDir, 'index.html'));
|
|
2226
|
+
});
|
|
2227
|
+
}
|
|
2107
2228
|
}
|
|
2108
2229
|
async start() {
|
|
2109
2230
|
return new Promise((resolve, reject) => {
|