@vibecheckai/cli 3.2.6 → 3.3.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/bin/registry.js +192 -5
- package/bin/runners/lib/agent-firewall/change-packet/builder.js +280 -6
- package/bin/runners/lib/agent-firewall/critic/index.js +151 -0
- package/bin/runners/lib/agent-firewall/critic/judge.js +432 -0
- package/bin/runners/lib/agent-firewall/critic/prompts.js +305 -0
- package/bin/runners/lib/agent-firewall/lawbook/distributor.js +465 -0
- package/bin/runners/lib/agent-firewall/lawbook/evaluator.js +604 -0
- package/bin/runners/lib/agent-firewall/lawbook/index.js +304 -0
- package/bin/runners/lib/agent-firewall/lawbook/registry.js +514 -0
- package/bin/runners/lib/agent-firewall/lawbook/schema.js +420 -0
- package/bin/runners/lib/agent-firewall/logger.js +141 -0
- package/bin/runners/lib/agent-firewall/policy/loader.js +312 -4
- package/bin/runners/lib/agent-firewall/policy/rules/ghost-env.js +113 -1
- package/bin/runners/lib/agent-firewall/policy/rules/ghost-route.js +133 -6
- package/bin/runners/lib/agent-firewall/proposal/extractor.js +394 -0
- package/bin/runners/lib/agent-firewall/proposal/index.js +212 -0
- package/bin/runners/lib/agent-firewall/proposal/schema.js +251 -0
- package/bin/runners/lib/agent-firewall/proposal/validator.js +386 -0
- package/bin/runners/lib/agent-firewall/reality/index.js +332 -0
- package/bin/runners/lib/agent-firewall/reality/state.js +625 -0
- package/bin/runners/lib/agent-firewall/reality/watcher.js +322 -0
- package/bin/runners/lib/agent-firewall/risk/index.js +173 -0
- package/bin/runners/lib/agent-firewall/risk/scorer.js +328 -0
- package/bin/runners/lib/agent-firewall/risk/thresholds.js +321 -0
- package/bin/runners/lib/agent-firewall/risk/vectors.js +421 -0
- package/bin/runners/lib/agent-firewall/simulator/diff-simulator.js +472 -0
- package/bin/runners/lib/agent-firewall/simulator/import-resolver.js +346 -0
- package/bin/runners/lib/agent-firewall/simulator/index.js +181 -0
- package/bin/runners/lib/agent-firewall/simulator/route-validator.js +380 -0
- package/bin/runners/lib/agent-firewall/time-machine/incident-correlator.js +661 -0
- package/bin/runners/lib/agent-firewall/time-machine/index.js +267 -0
- package/bin/runners/lib/agent-firewall/time-machine/replay-engine.js +436 -0
- package/bin/runners/lib/agent-firewall/time-machine/state-reconstructor.js +490 -0
- package/bin/runners/lib/agent-firewall/time-machine/timeline-builder.js +530 -0
- package/bin/runners/lib/analyzers.js +81 -18
- package/bin/runners/lib/authority-badge.js +425 -0
- package/bin/runners/lib/cli-output.js +7 -1
- package/bin/runners/lib/error-handler.js +16 -9
- package/bin/runners/lib/exit-codes.js +275 -0
- package/bin/runners/lib/global-flags.js +37 -0
- package/bin/runners/lib/help-formatter.js +413 -0
- package/bin/runners/lib/logger.js +38 -0
- package/bin/runners/lib/unified-cli-output.js +604 -0
- package/bin/runners/lib/upsell.js +148 -0
- package/bin/runners/runApprove.js +1200 -0
- package/bin/runners/runAuth.js +324 -95
- package/bin/runners/runCheckpoint.js +39 -21
- package/bin/runners/runClassify.js +859 -0
- package/bin/runners/runContext.js +136 -24
- package/bin/runners/runDoctor.js +108 -68
- package/bin/runners/runFix.js +6 -5
- package/bin/runners/runGuard.js +212 -118
- package/bin/runners/runInit.js +3 -2
- package/bin/runners/runMcp.js +130 -52
- package/bin/runners/runPolish.js +43 -20
- package/bin/runners/runProve.js +1 -2
- package/bin/runners/runReport.js +3 -2
- package/bin/runners/runScan.js +63 -44
- package/bin/runners/runShip.js +3 -4
- package/bin/runners/runValidate.js +19 -2
- package/bin/runners/runWatch.js +104 -53
- package/bin/vibecheck.js +106 -19
- package/mcp-server/HARDENING_SUMMARY.md +299 -0
- package/mcp-server/agent-firewall-interceptor.js +367 -31
- package/mcp-server/authority-tools.js +569 -0
- package/mcp-server/conductor/conflict-resolver.js +588 -0
- package/mcp-server/conductor/execution-planner.js +544 -0
- package/mcp-server/conductor/index.js +377 -0
- package/mcp-server/conductor/lock-manager.js +615 -0
- package/mcp-server/conductor/request-queue.js +550 -0
- package/mcp-server/conductor/session-manager.js +500 -0
- package/mcp-server/conductor/tools.js +510 -0
- package/mcp-server/index.js +1149 -243
- package/mcp-server/lib/{api-client.js → api-client.cjs} +40 -4
- package/mcp-server/lib/logger.cjs +30 -0
- package/mcp-server/logger.js +173 -0
- package/mcp-server/package.json +2 -2
- package/mcp-server/premium-tools.js +2 -2
- package/mcp-server/tier-auth.js +245 -35
- package/mcp-server/truth-firewall-tools.js +145 -15
- package/mcp-server/vibecheck-tools.js +2 -2
- package/package.json +2 -3
- package/mcp-server/index.old.js +0 -4137
- package/mcp-server/package-lock.json +0 -165
|
@@ -0,0 +1,550 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Conductor Request Queue
|
|
3
|
+
*
|
|
4
|
+
* Priority queue for multi-agent request processing.
|
|
5
|
+
* Implements tier-based ordering with fair scheduling.
|
|
6
|
+
*
|
|
7
|
+
* Codename: Conductor
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
"use strict";
|
|
11
|
+
|
|
12
|
+
import fs from "fs";
|
|
13
|
+
import path from "path";
|
|
14
|
+
import crypto from "crypto";
|
|
15
|
+
import { conductorLogger as log, getErrorMessage } from "../logger.js";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Request priority based on tier
|
|
19
|
+
*/
|
|
20
|
+
const TIER_PRIORITY = {
|
|
21
|
+
ENTERPRISE: 100,
|
|
22
|
+
PRO: 75,
|
|
23
|
+
STARTER: 50,
|
|
24
|
+
FREE: 25,
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Request states
|
|
29
|
+
*/
|
|
30
|
+
const REQUEST_STATE = {
|
|
31
|
+
PENDING: "pending",
|
|
32
|
+
PROCESSING: "processing",
|
|
33
|
+
COMPLETED: "completed",
|
|
34
|
+
FAILED: "failed",
|
|
35
|
+
CANCELLED: "cancelled",
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* @typedef {Object} QueuedRequest
|
|
40
|
+
* @property {string} requestId - Unique request ID
|
|
41
|
+
* @property {string} proposalId - Associated proposal ID
|
|
42
|
+
* @property {string} sessionId - Session ID
|
|
43
|
+
* @property {string} agentId - Agent ID
|
|
44
|
+
* @property {string} tier - User tier
|
|
45
|
+
* @property {number} priority - Calculated priority
|
|
46
|
+
* @property {Date} queuedAt - When request was queued
|
|
47
|
+
* @property {Date} processedAt - When processing started
|
|
48
|
+
* @property {string} state - Current state
|
|
49
|
+
* @property {number} attempts - Number of processing attempts
|
|
50
|
+
* @property {Object} payload - Request payload
|
|
51
|
+
*/
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Request Queue class
|
|
55
|
+
*/
|
|
56
|
+
class RequestQueue {
|
|
57
|
+
constructor(options = {}) {
|
|
58
|
+
this.queue = []; // Sorted by priority
|
|
59
|
+
this.processing = new Map(); // requestId -> QueuedRequest
|
|
60
|
+
this.completed = new Map(); // requestId -> QueuedRequest (recent)
|
|
61
|
+
this.maxConcurrent = options.maxConcurrent || 5;
|
|
62
|
+
this.maxRetries = options.maxRetries || 3;
|
|
63
|
+
this.fairSchedulingWindow = options.fairSchedulingWindow || 10000; // 10 seconds
|
|
64
|
+
this.persistPath = options.persistPath || null;
|
|
65
|
+
|
|
66
|
+
// Track requests per tier for fair scheduling
|
|
67
|
+
this.tierRequestCounts = {
|
|
68
|
+
ENTERPRISE: { queued: 0, processed: 0 },
|
|
69
|
+
PRO: { queued: 0, processed: 0 },
|
|
70
|
+
STARTER: { queued: 0, processed: 0 },
|
|
71
|
+
FREE: { queued: 0, processed: 0 },
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
// Load persisted state
|
|
75
|
+
if (this.persistPath) {
|
|
76
|
+
this.loadState();
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Generate a unique request ID
|
|
82
|
+
* @returns {string} Request ID
|
|
83
|
+
*/
|
|
84
|
+
generateRequestId() {
|
|
85
|
+
return `req_${Date.now()}_${crypto.randomBytes(4).toString("hex")}`;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Calculate priority for a request
|
|
90
|
+
* @param {string} tier - User tier
|
|
91
|
+
* @param {Date} queuedAt - When queued
|
|
92
|
+
* @returns {number} Priority score
|
|
93
|
+
*/
|
|
94
|
+
calculatePriority(tier, queuedAt) {
|
|
95
|
+
const basePriority = TIER_PRIORITY[tier] || 25;
|
|
96
|
+
|
|
97
|
+
// Add time-based bonus (older requests get slight boost)
|
|
98
|
+
const ageSeconds = (Date.now() - queuedAt.getTime()) / 1000;
|
|
99
|
+
const ageBonus = Math.min(ageSeconds / 60, 10); // Max 10 points after 10 minutes
|
|
100
|
+
|
|
101
|
+
// Fair scheduling adjustment
|
|
102
|
+
const tierStats = this.tierRequestCounts[tier] || { queued: 0, processed: 0 };
|
|
103
|
+
const fairnessBonus = tierStats.queued > tierStats.processed ? 5 : 0;
|
|
104
|
+
|
|
105
|
+
return basePriority + ageBonus + fairnessBonus;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Enqueue a request
|
|
110
|
+
* @param {Object} params - Request parameters
|
|
111
|
+
* @returns {QueuedRequest} Queued request
|
|
112
|
+
*/
|
|
113
|
+
enqueue({
|
|
114
|
+
proposalId,
|
|
115
|
+
sessionId,
|
|
116
|
+
agentId,
|
|
117
|
+
tier = "FREE",
|
|
118
|
+
payload = {},
|
|
119
|
+
}) {
|
|
120
|
+
const requestId = this.generateRequestId();
|
|
121
|
+
const queuedAt = new Date();
|
|
122
|
+
|
|
123
|
+
const request = {
|
|
124
|
+
requestId,
|
|
125
|
+
proposalId,
|
|
126
|
+
sessionId,
|
|
127
|
+
agentId,
|
|
128
|
+
tier,
|
|
129
|
+
priority: this.calculatePriority(tier, queuedAt),
|
|
130
|
+
queuedAt,
|
|
131
|
+
processedAt: null,
|
|
132
|
+
completedAt: null,
|
|
133
|
+
state: REQUEST_STATE.PENDING,
|
|
134
|
+
attempts: 0,
|
|
135
|
+
payload,
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
// Insert in priority order
|
|
139
|
+
this.insertByPriority(request);
|
|
140
|
+
|
|
141
|
+
// Update tier counts
|
|
142
|
+
if (this.tierRequestCounts[tier]) {
|
|
143
|
+
this.tierRequestCounts[tier].queued++;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
this.saveState();
|
|
147
|
+
|
|
148
|
+
return request;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Insert a request in priority order
|
|
153
|
+
* @param {QueuedRequest} request - Request to insert
|
|
154
|
+
*/
|
|
155
|
+
insertByPriority(request) {
|
|
156
|
+
// Find insertion point
|
|
157
|
+
let insertIndex = this.queue.length;
|
|
158
|
+
|
|
159
|
+
for (let i = 0; i < this.queue.length; i++) {
|
|
160
|
+
if (request.priority > this.queue[i].priority) {
|
|
161
|
+
insertIndex = i;
|
|
162
|
+
break;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
this.queue.splice(insertIndex, 0, request);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Dequeue the next request for processing
|
|
171
|
+
* @returns {QueuedRequest|null} Next request or null
|
|
172
|
+
*/
|
|
173
|
+
dequeue() {
|
|
174
|
+
// Check if we can process more
|
|
175
|
+
if (this.processing.size >= this.maxConcurrent) {
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Find next request that isn't blocked
|
|
180
|
+
for (let i = 0; i < this.queue.length; i++) {
|
|
181
|
+
const request = this.queue[i];
|
|
182
|
+
|
|
183
|
+
// Update priority (in case it changed due to aging)
|
|
184
|
+
request.priority = this.calculatePriority(request.tier, new Date(request.queuedAt));
|
|
185
|
+
|
|
186
|
+
// For now, just take the first one
|
|
187
|
+
this.queue.splice(i, 1);
|
|
188
|
+
|
|
189
|
+
// Move to processing
|
|
190
|
+
request.state = REQUEST_STATE.PROCESSING;
|
|
191
|
+
request.processedAt = new Date();
|
|
192
|
+
request.attempts++;
|
|
193
|
+
|
|
194
|
+
this.processing.set(request.requestId, request);
|
|
195
|
+
|
|
196
|
+
this.saveState();
|
|
197
|
+
|
|
198
|
+
return request;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Mark a request as completed
|
|
206
|
+
* @param {string} requestId - Request ID
|
|
207
|
+
* @param {Object} result - Processing result
|
|
208
|
+
* @returns {boolean} Success
|
|
209
|
+
*/
|
|
210
|
+
complete(requestId, result = {}) {
|
|
211
|
+
const request = this.processing.get(requestId);
|
|
212
|
+
if (!request) return false;
|
|
213
|
+
|
|
214
|
+
request.state = REQUEST_STATE.COMPLETED;
|
|
215
|
+
request.completedAt = new Date();
|
|
216
|
+
request.result = result;
|
|
217
|
+
|
|
218
|
+
// Move from processing to completed
|
|
219
|
+
this.processing.delete(requestId);
|
|
220
|
+
this.completed.set(requestId, request);
|
|
221
|
+
|
|
222
|
+
// Update tier counts
|
|
223
|
+
if (this.tierRequestCounts[request.tier]) {
|
|
224
|
+
this.tierRequestCounts[request.tier].processed++;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Cleanup old completed requests
|
|
228
|
+
this.cleanupCompleted();
|
|
229
|
+
|
|
230
|
+
this.saveState();
|
|
231
|
+
|
|
232
|
+
return true;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Mark a request as failed
|
|
237
|
+
* @param {string} requestId - Request ID
|
|
238
|
+
* @param {Error|string} error - Error information
|
|
239
|
+
* @returns {boolean} Success or requeued
|
|
240
|
+
*/
|
|
241
|
+
fail(requestId, error) {
|
|
242
|
+
const request = this.processing.get(requestId);
|
|
243
|
+
if (!request) return false;
|
|
244
|
+
|
|
245
|
+
this.processing.delete(requestId);
|
|
246
|
+
|
|
247
|
+
// Check if we should retry
|
|
248
|
+
if (request.attempts < this.maxRetries) {
|
|
249
|
+
// Requeue with lower priority
|
|
250
|
+
request.state = REQUEST_STATE.PENDING;
|
|
251
|
+
request.priority = Math.max(1, request.priority - 10);
|
|
252
|
+
request.lastError = error?.message || String(error);
|
|
253
|
+
|
|
254
|
+
this.insertByPriority(request);
|
|
255
|
+
|
|
256
|
+
this.saveState();
|
|
257
|
+
return true; // Requeued
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Max retries exceeded
|
|
261
|
+
request.state = REQUEST_STATE.FAILED;
|
|
262
|
+
request.completedAt = new Date();
|
|
263
|
+
request.error = error?.message || String(error);
|
|
264
|
+
|
|
265
|
+
this.completed.set(requestId, request);
|
|
266
|
+
|
|
267
|
+
this.saveState();
|
|
268
|
+
|
|
269
|
+
return false;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Cancel a request
|
|
274
|
+
* @param {string} requestId - Request ID
|
|
275
|
+
* @returns {boolean} Success
|
|
276
|
+
*/
|
|
277
|
+
cancel(requestId) {
|
|
278
|
+
// Check queue
|
|
279
|
+
const queueIndex = this.queue.findIndex(r => r.requestId === requestId);
|
|
280
|
+
if (queueIndex !== -1) {
|
|
281
|
+
const request = this.queue.splice(queueIndex, 1)[0];
|
|
282
|
+
request.state = REQUEST_STATE.CANCELLED;
|
|
283
|
+
request.completedAt = new Date();
|
|
284
|
+
this.completed.set(requestId, request);
|
|
285
|
+
this.saveState();
|
|
286
|
+
return true;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// Check processing
|
|
290
|
+
const processing = this.processing.get(requestId);
|
|
291
|
+
if (processing) {
|
|
292
|
+
processing.state = REQUEST_STATE.CANCELLED;
|
|
293
|
+
processing.completedAt = new Date();
|
|
294
|
+
this.processing.delete(requestId);
|
|
295
|
+
this.completed.set(requestId, processing);
|
|
296
|
+
this.saveState();
|
|
297
|
+
return true;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
return false;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Cancel all requests for a session
|
|
305
|
+
* @param {string} sessionId - Session ID
|
|
306
|
+
* @returns {number} Number cancelled
|
|
307
|
+
*/
|
|
308
|
+
cancelSession(sessionId) {
|
|
309
|
+
let cancelled = 0;
|
|
310
|
+
|
|
311
|
+
// Cancel from queue
|
|
312
|
+
for (let i = this.queue.length - 1; i >= 0; i--) {
|
|
313
|
+
if (this.queue[i].sessionId === sessionId) {
|
|
314
|
+
this.cancel(this.queue[i].requestId);
|
|
315
|
+
cancelled++;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Cancel from processing
|
|
320
|
+
for (const [requestId, request] of this.processing) {
|
|
321
|
+
if (request.sessionId === sessionId) {
|
|
322
|
+
this.cancel(requestId);
|
|
323
|
+
cancelled++;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
return cancelled;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Get a request by ID
|
|
332
|
+
* @param {string} requestId - Request ID
|
|
333
|
+
* @returns {QueuedRequest|null} Request or null
|
|
334
|
+
*/
|
|
335
|
+
getRequest(requestId) {
|
|
336
|
+
// Check queue
|
|
337
|
+
const queued = this.queue.find(r => r.requestId === requestId);
|
|
338
|
+
if (queued) return queued;
|
|
339
|
+
|
|
340
|
+
// Check processing
|
|
341
|
+
const processing = this.processing.get(requestId);
|
|
342
|
+
if (processing) return processing;
|
|
343
|
+
|
|
344
|
+
// Check completed
|
|
345
|
+
const completed = this.completed.get(requestId);
|
|
346
|
+
if (completed) return completed;
|
|
347
|
+
|
|
348
|
+
return null;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Get queue statistics
|
|
353
|
+
* @returns {Object} Statistics
|
|
354
|
+
*/
|
|
355
|
+
getStatistics() {
|
|
356
|
+
const queueByTier = { FREE: 0, STARTER: 0, PRO: 0, ENTERPRISE: 0 };
|
|
357
|
+
const processingByTier = { FREE: 0, STARTER: 0, PRO: 0, ENTERPRISE: 0 };
|
|
358
|
+
|
|
359
|
+
for (const request of this.queue) {
|
|
360
|
+
queueByTier[request.tier] = (queueByTier[request.tier] || 0) + 1;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
for (const request of this.processing.values()) {
|
|
364
|
+
processingByTier[request.tier] = (processingByTier[request.tier] || 0) + 1;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
return {
|
|
368
|
+
queueLength: this.queue.length,
|
|
369
|
+
processing: this.processing.size,
|
|
370
|
+
completed: this.completed.size,
|
|
371
|
+
maxConcurrent: this.maxConcurrent,
|
|
372
|
+
byTier: {
|
|
373
|
+
queue: queueByTier,
|
|
374
|
+
processing: processingByTier,
|
|
375
|
+
counts: this.tierRequestCounts,
|
|
376
|
+
},
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* Get the current queue
|
|
382
|
+
* @param {Object} options - Filter options
|
|
383
|
+
* @returns {QueuedRequest[]} Queue items
|
|
384
|
+
*/
|
|
385
|
+
getQueue(options = {}) {
|
|
386
|
+
let result = [...this.queue];
|
|
387
|
+
|
|
388
|
+
if (options.tier) {
|
|
389
|
+
result = result.filter(r => r.tier === options.tier);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
if (options.sessionId) {
|
|
393
|
+
result = result.filter(r => r.sessionId === options.sessionId);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
if (options.limit) {
|
|
397
|
+
result = result.slice(0, options.limit);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
return result;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* Get currently processing requests
|
|
405
|
+
* @returns {QueuedRequest[]} Processing requests
|
|
406
|
+
*/
|
|
407
|
+
getProcessing() {
|
|
408
|
+
return Array.from(this.processing.values());
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* Cleanup old completed requests
|
|
413
|
+
* @param {number} maxAge - Max age in milliseconds (default 1 hour)
|
|
414
|
+
*/
|
|
415
|
+
cleanupCompleted(maxAge = 3600000) {
|
|
416
|
+
const cutoff = Date.now() - maxAge;
|
|
417
|
+
|
|
418
|
+
for (const [requestId, request] of this.completed) {
|
|
419
|
+
if (new Date(request.completedAt).getTime() < cutoff) {
|
|
420
|
+
this.completed.delete(requestId);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* Resort the queue (useful after bulk operations)
|
|
427
|
+
*/
|
|
428
|
+
resort() {
|
|
429
|
+
// Update all priorities
|
|
430
|
+
for (const request of this.queue) {
|
|
431
|
+
request.priority = this.calculatePriority(request.tier, new Date(request.queuedAt));
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// Sort by priority (descending)
|
|
435
|
+
this.queue.sort((a, b) => b.priority - a.priority);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
/**
|
|
439
|
+
* Save state to disk
|
|
440
|
+
*/
|
|
441
|
+
saveState() {
|
|
442
|
+
if (!this.persistPath) return;
|
|
443
|
+
|
|
444
|
+
try {
|
|
445
|
+
const dir = path.dirname(this.persistPath);
|
|
446
|
+
if (!fs.existsSync(dir)) {
|
|
447
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
const state = {
|
|
451
|
+
queue: this.queue,
|
|
452
|
+
processing: Array.from(this.processing.entries()),
|
|
453
|
+
tierRequestCounts: this.tierRequestCounts,
|
|
454
|
+
timestamp: new Date().toISOString(),
|
|
455
|
+
};
|
|
456
|
+
|
|
457
|
+
fs.writeFileSync(this.persistPath, JSON.stringify(state, null, 2));
|
|
458
|
+
} catch (error) {
|
|
459
|
+
log.warn(`Failed to save queue state: ${getErrorMessage(error)}`);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* Load state from disk
|
|
465
|
+
*/
|
|
466
|
+
loadState() {
|
|
467
|
+
if (!this.persistPath || !fs.existsSync(this.persistPath)) return;
|
|
468
|
+
|
|
469
|
+
try {
|
|
470
|
+
const content = fs.readFileSync(this.persistPath, "utf-8");
|
|
471
|
+
const state = JSON.parse(content);
|
|
472
|
+
|
|
473
|
+
// Restore queue
|
|
474
|
+
this.queue = (state.queue || []).map(r => ({
|
|
475
|
+
...r,
|
|
476
|
+
queuedAt: new Date(r.queuedAt),
|
|
477
|
+
processedAt: r.processedAt ? new Date(r.processedAt) : null,
|
|
478
|
+
}));
|
|
479
|
+
|
|
480
|
+
// Restore processing (mark as failed since we restarted)
|
|
481
|
+
for (const [requestId, request] of state.processing || []) {
|
|
482
|
+
request.queuedAt = new Date(request.queuedAt);
|
|
483
|
+
request.processedAt = new Date(request.processedAt);
|
|
484
|
+
|
|
485
|
+
// Requeue interrupted processing
|
|
486
|
+
request.state = REQUEST_STATE.PENDING;
|
|
487
|
+
request.priority = Math.max(1, request.priority - 5);
|
|
488
|
+
this.insertByPriority(request);
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
// Restore tier counts
|
|
492
|
+
this.tierRequestCounts = state.tierRequestCounts || this.tierRequestCounts;
|
|
493
|
+
|
|
494
|
+
log.info(`Restored queue with ${this.queue.length} pending requests`);
|
|
495
|
+
} catch (error) {
|
|
496
|
+
log.warn(`Failed to load queue state: ${getErrorMessage(error)}`);
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
/**
|
|
501
|
+
* Clear the queue
|
|
502
|
+
*/
|
|
503
|
+
clear() {
|
|
504
|
+
this.queue = [];
|
|
505
|
+
this.processing.clear();
|
|
506
|
+
this.completed.clear();
|
|
507
|
+
this.tierRequestCounts = {
|
|
508
|
+
ENTERPRISE: { queued: 0, processed: 0 },
|
|
509
|
+
PRO: { queued: 0, processed: 0 },
|
|
510
|
+
STARTER: { queued: 0, processed: 0 },
|
|
511
|
+
FREE: { queued: 0, processed: 0 },
|
|
512
|
+
};
|
|
513
|
+
this.saveState();
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
/**
|
|
518
|
+
* Create a new request queue
|
|
519
|
+
* @param {Object} options - Queue options
|
|
520
|
+
* @returns {RequestQueue} New queue
|
|
521
|
+
*/
|
|
522
|
+
function createRequestQueue(options = {}) {
|
|
523
|
+
return new RequestQueue(options);
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
// Default instance
|
|
527
|
+
let defaultQueue = null;
|
|
528
|
+
|
|
529
|
+
/**
|
|
530
|
+
* Get the default request queue
|
|
531
|
+
* @param {string} projectRoot - Project root for persist path
|
|
532
|
+
* @returns {RequestQueue} Default queue
|
|
533
|
+
*/
|
|
534
|
+
function getRequestQueue(projectRoot) {
|
|
535
|
+
if (!defaultQueue) {
|
|
536
|
+
const persistPath = projectRoot
|
|
537
|
+
? path.join(projectRoot, ".vibecheck", "conductor", "queue.json")
|
|
538
|
+
: null;
|
|
539
|
+
defaultQueue = createRequestQueue({ persistPath });
|
|
540
|
+
}
|
|
541
|
+
return defaultQueue;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
export {
|
|
545
|
+
RequestQueue,
|
|
546
|
+
createRequestQueue,
|
|
547
|
+
getRequestQueue,
|
|
548
|
+
TIER_PRIORITY,
|
|
549
|
+
REQUEST_STATE,
|
|
550
|
+
};
|