@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,544 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Conductor Execution Planner
|
|
3
|
+
*
|
|
4
|
+
* Plans deterministic execution ordering for multi-agent operations.
|
|
5
|
+
* Uses topological sorting to respect dependencies.
|
|
6
|
+
*
|
|
7
|
+
* Codename: Conductor
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
"use strict";
|
|
11
|
+
|
|
12
|
+
import path from "path";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @typedef {Object} ExecutionStep
|
|
16
|
+
* @property {number} order - Execution order (0 = first)
|
|
17
|
+
* @property {string} proposalId - Proposal to execute
|
|
18
|
+
* @property {string} agentId - Agent that owns the proposal
|
|
19
|
+
* @property {string[]} dependsOn - Proposal IDs this depends on
|
|
20
|
+
* @property {string[]} blockedBy - Proposal IDs blocking this
|
|
21
|
+
* @property {boolean} canExecuteNow - Can execute immediately
|
|
22
|
+
* @property {string} status - pending, executing, completed, failed
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @typedef {Object} ExecutionPlan
|
|
27
|
+
* @property {string} planId - Unique plan ID
|
|
28
|
+
* @property {ExecutionStep[]} steps - Ordered execution steps
|
|
29
|
+
* @property {boolean} canExecute - Can the plan be executed
|
|
30
|
+
* @property {Object[]} conflicts - Unresolved conflicts blocking execution
|
|
31
|
+
* @property {Date} createdAt - When plan was created
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Tier priority mapping (higher = more priority)
|
|
36
|
+
*/
|
|
37
|
+
const TIER_PRIORITY = {
|
|
38
|
+
ENTERPRISE: 4,
|
|
39
|
+
PRO: 3,
|
|
40
|
+
STARTER: 2,
|
|
41
|
+
FREE: 1,
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Execution Planner class
|
|
46
|
+
*/
|
|
47
|
+
class ExecutionPlanner {
|
|
48
|
+
constructor(conflictResolver = null) {
|
|
49
|
+
this.conflictResolver = conflictResolver;
|
|
50
|
+
this.plans = new Map(); // planId -> ExecutionPlan
|
|
51
|
+
this.executionHistory = []; // For auditing
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Set the conflict resolver
|
|
56
|
+
* @param {Object} resolver - Conflict resolver instance
|
|
57
|
+
*/
|
|
58
|
+
setConflictResolver(resolver) {
|
|
59
|
+
this.conflictResolver = resolver;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Create an execution plan from proposals
|
|
64
|
+
* @param {Object[]} proposals - Proposals to plan
|
|
65
|
+
* @param {Object} options - Planning options
|
|
66
|
+
* @returns {ExecutionPlan} Execution plan
|
|
67
|
+
*/
|
|
68
|
+
createPlan(proposals, options = {}) {
|
|
69
|
+
const planId = `plan_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
70
|
+
|
|
71
|
+
// Build dependency graph
|
|
72
|
+
const graph = this.buildDependencyGraph(proposals);
|
|
73
|
+
|
|
74
|
+
// Check for cycles
|
|
75
|
+
const cycles = this.detectCycles(graph);
|
|
76
|
+
if (cycles.length > 0) {
|
|
77
|
+
return {
|
|
78
|
+
planId,
|
|
79
|
+
steps: [],
|
|
80
|
+
canExecute: false,
|
|
81
|
+
conflicts: cycles.map(c => ({
|
|
82
|
+
type: "circular_dependency",
|
|
83
|
+
proposals: c,
|
|
84
|
+
description: `Circular dependency detected: ${c.join(" -> ")}`,
|
|
85
|
+
})),
|
|
86
|
+
createdAt: new Date(),
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Topological sort
|
|
91
|
+
const sortedOrder = this.topologicalSort(graph);
|
|
92
|
+
|
|
93
|
+
if (!sortedOrder) {
|
|
94
|
+
return {
|
|
95
|
+
planId,
|
|
96
|
+
steps: [],
|
|
97
|
+
canExecute: false,
|
|
98
|
+
conflicts: [{ type: "sort_failed", description: "Could not determine execution order" }],
|
|
99
|
+
createdAt: new Date(),
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Apply tier-based priority within independent groups
|
|
104
|
+
const prioritizedOrder = this.applyTierPriority(sortedOrder, proposals);
|
|
105
|
+
|
|
106
|
+
// Build execution steps
|
|
107
|
+
const steps = this.buildExecutionSteps(prioritizedOrder, proposals, graph);
|
|
108
|
+
|
|
109
|
+
// Check for blocking conflicts
|
|
110
|
+
const blockingConflicts = this.getBlockingConflicts(proposals);
|
|
111
|
+
|
|
112
|
+
const plan = {
|
|
113
|
+
planId,
|
|
114
|
+
steps,
|
|
115
|
+
canExecute: blockingConflicts.length === 0,
|
|
116
|
+
conflicts: blockingConflicts,
|
|
117
|
+
createdAt: new Date(),
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
this.plans.set(planId, plan);
|
|
121
|
+
|
|
122
|
+
return plan;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Build a dependency graph from proposals
|
|
127
|
+
* @param {Object[]} proposals - Proposals to analyze
|
|
128
|
+
* @returns {Map} Dependency graph (proposalId -> Set<dependsOn>)
|
|
129
|
+
*/
|
|
130
|
+
buildDependencyGraph(proposals) {
|
|
131
|
+
const graph = new Map();
|
|
132
|
+
|
|
133
|
+
// Initialize all nodes
|
|
134
|
+
for (const proposal of proposals) {
|
|
135
|
+
graph.set(proposal.proposalId, new Set());
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Build dependencies based on file overlaps and assumptions
|
|
139
|
+
for (const proposalA of proposals) {
|
|
140
|
+
const filesA = this.extractAffectedFiles(proposalA);
|
|
141
|
+
const assumptionsA = proposalA.assumptions || [];
|
|
142
|
+
|
|
143
|
+
for (const proposalB of proposals) {
|
|
144
|
+
if (proposalA.proposalId === proposalB.proposalId) continue;
|
|
145
|
+
|
|
146
|
+
const operationsB = proposalB.operations || [];
|
|
147
|
+
|
|
148
|
+
// Check if A's assumptions depend on B's operations
|
|
149
|
+
for (const assumption of assumptionsA) {
|
|
150
|
+
for (const operation of operationsB) {
|
|
151
|
+
if (this.assumptionDependsOnOperation(assumption, operation)) {
|
|
152
|
+
// A depends on B (B must execute first)
|
|
153
|
+
graph.get(proposalA.proposalId).add(proposalB.proposalId);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Check for file-based dependencies
|
|
159
|
+
const filesB = this.extractAffectedFiles(proposalB);
|
|
160
|
+
for (const fileA of filesA) {
|
|
161
|
+
for (const fileB of filesB) {
|
|
162
|
+
const dependency = this.checkFileDependency(fileA, fileB, proposalA, proposalB);
|
|
163
|
+
if (dependency) {
|
|
164
|
+
graph.get(dependency.dependent).add(dependency.dependsOn);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return graph;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Check if an assumption depends on an operation
|
|
176
|
+
* @param {Object} assumption - Assumption to check
|
|
177
|
+
* @param {Object} operation - Operation to check against
|
|
178
|
+
* @returns {boolean} Has dependency
|
|
179
|
+
*/
|
|
180
|
+
assumptionDependsOnOperation(assumption, operation) {
|
|
181
|
+
const assumptionTarget = this.normalizePath(assumption.target || assumption.path || "");
|
|
182
|
+
const operationTarget = this.normalizePath(operation.path || operation.file || "");
|
|
183
|
+
|
|
184
|
+
if (!assumptionTarget || !operationTarget) return false;
|
|
185
|
+
|
|
186
|
+
// If assumption is about a file that the operation creates/modifies
|
|
187
|
+
if (assumptionTarget === operationTarget) {
|
|
188
|
+
// file_exists depends on create
|
|
189
|
+
if (assumption.type === "file_exists" && operation.type === "create") {
|
|
190
|
+
return true;
|
|
191
|
+
}
|
|
192
|
+
// file_contains depends on modify that adds content
|
|
193
|
+
if (assumption.type === "file_contains" && operation.type === "modify") {
|
|
194
|
+
return true;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return false;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Check for file-based dependency between operations
|
|
203
|
+
* @param {Object} fileA - First file operation
|
|
204
|
+
* @param {Object} fileB - Second file operation
|
|
205
|
+
* @param {Object} proposalA - First proposal
|
|
206
|
+
* @param {Object} proposalB - Second proposal
|
|
207
|
+
* @returns {Object|null} Dependency info or null
|
|
208
|
+
*/
|
|
209
|
+
checkFileDependency(fileA, fileB, proposalA, proposalB) {
|
|
210
|
+
const pathA = this.normalizePath(fileA.path);
|
|
211
|
+
const pathB = this.normalizePath(fileB.path);
|
|
212
|
+
|
|
213
|
+
if (pathA !== pathB) return null;
|
|
214
|
+
|
|
215
|
+
// Create before modify/read
|
|
216
|
+
if (fileB.operation === "create" && fileA.operation === "modify") {
|
|
217
|
+
return { dependent: proposalA.proposalId, dependsOn: proposalB.proposalId };
|
|
218
|
+
}
|
|
219
|
+
if (fileA.operation === "create" && fileB.operation === "modify") {
|
|
220
|
+
return { dependent: proposalB.proposalId, dependsOn: proposalA.proposalId };
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return null;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Extract affected files from a proposal
|
|
228
|
+
* @param {Object} proposal - Proposal to analyze
|
|
229
|
+
* @returns {Object[]} Affected files
|
|
230
|
+
*/
|
|
231
|
+
extractAffectedFiles(proposal) {
|
|
232
|
+
const files = [];
|
|
233
|
+
for (const op of proposal.operations || []) {
|
|
234
|
+
if (op.path || op.file || op.filePath) {
|
|
235
|
+
files.push({
|
|
236
|
+
path: op.path || op.file || op.filePath,
|
|
237
|
+
operation: op.type || op.operation || "modify",
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
return files;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Detect cycles in the dependency graph
|
|
246
|
+
* @param {Map} graph - Dependency graph
|
|
247
|
+
* @returns {string[][]} Detected cycles
|
|
248
|
+
*/
|
|
249
|
+
detectCycles(graph) {
|
|
250
|
+
const cycles = [];
|
|
251
|
+
const visited = new Set();
|
|
252
|
+
const inStack = new Set();
|
|
253
|
+
|
|
254
|
+
const dfs = (node, path) => {
|
|
255
|
+
if (inStack.has(node)) {
|
|
256
|
+
const cycleStart = path.indexOf(node);
|
|
257
|
+
cycles.push(path.slice(cycleStart));
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if (visited.has(node)) return;
|
|
262
|
+
|
|
263
|
+
visited.add(node);
|
|
264
|
+
inStack.add(node);
|
|
265
|
+
path.push(node);
|
|
266
|
+
|
|
267
|
+
const deps = graph.get(node) || new Set();
|
|
268
|
+
for (const dep of deps) {
|
|
269
|
+
dfs(dep, [...path]);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
inStack.delete(node);
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
for (const node of graph.keys()) {
|
|
276
|
+
if (!visited.has(node)) {
|
|
277
|
+
dfs(node, []);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
return cycles;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Perform topological sort
|
|
286
|
+
* @param {Map} graph - Dependency graph
|
|
287
|
+
* @returns {string[]|null} Sorted order or null if not possible
|
|
288
|
+
*/
|
|
289
|
+
topologicalSort(graph) {
|
|
290
|
+
const inDegree = new Map();
|
|
291
|
+
const sorted = [];
|
|
292
|
+
const queue = [];
|
|
293
|
+
|
|
294
|
+
// Initialize in-degrees
|
|
295
|
+
for (const node of graph.keys()) {
|
|
296
|
+
inDegree.set(node, 0);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Calculate in-degrees
|
|
300
|
+
for (const [, deps] of graph) {
|
|
301
|
+
for (const dep of deps) {
|
|
302
|
+
inDegree.set(dep, (inDegree.get(dep) || 0) + 1);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// Find nodes with no dependencies
|
|
307
|
+
for (const [node, degree] of inDegree) {
|
|
308
|
+
if (degree === 0) {
|
|
309
|
+
queue.push(node);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// Process queue
|
|
314
|
+
while (queue.length > 0) {
|
|
315
|
+
const node = queue.shift();
|
|
316
|
+
sorted.push(node);
|
|
317
|
+
|
|
318
|
+
const deps = graph.get(node) || new Set();
|
|
319
|
+
for (const dep of deps) {
|
|
320
|
+
inDegree.set(dep, inDegree.get(dep) - 1);
|
|
321
|
+
if (inDegree.get(dep) === 0) {
|
|
322
|
+
queue.push(dep);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Check if all nodes were processed
|
|
328
|
+
if (sorted.length !== graph.size) {
|
|
329
|
+
return null; // Cycle detected
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// Reverse to get correct order (dependencies first)
|
|
333
|
+
return sorted.reverse();
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Apply tier-based priority to execution order
|
|
338
|
+
* @param {string[]} sortedOrder - Topologically sorted order
|
|
339
|
+
* @param {Object[]} proposals - Proposals
|
|
340
|
+
* @returns {string[]} Priority-adjusted order
|
|
341
|
+
*/
|
|
342
|
+
applyTierPriority(sortedOrder, proposals) {
|
|
343
|
+
const proposalMap = new Map(proposals.map(p => [p.proposalId, p]));
|
|
344
|
+
|
|
345
|
+
// Group by dependency level
|
|
346
|
+
const levels = this.groupByDependencyLevel(sortedOrder, proposalMap);
|
|
347
|
+
|
|
348
|
+
// Sort within each level by tier priority
|
|
349
|
+
const prioritized = [];
|
|
350
|
+
for (const level of levels) {
|
|
351
|
+
level.sort((a, b) => {
|
|
352
|
+
const proposalA = proposalMap.get(a);
|
|
353
|
+
const proposalB = proposalMap.get(b);
|
|
354
|
+
const priorityA = TIER_PRIORITY[proposalA?.tier] || 1;
|
|
355
|
+
const priorityB = TIER_PRIORITY[proposalB?.tier] || 1;
|
|
356
|
+
return priorityB - priorityA; // Higher priority first
|
|
357
|
+
});
|
|
358
|
+
prioritized.push(...level);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
return prioritized;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Group proposals by dependency level
|
|
366
|
+
* @param {string[]} sorted - Sorted order
|
|
367
|
+
* @param {Map} proposalMap - Proposal map
|
|
368
|
+
* @returns {string[][]} Grouped levels
|
|
369
|
+
*/
|
|
370
|
+
groupByDependencyLevel(sorted, proposalMap) {
|
|
371
|
+
// Simple grouping - items that can execute in parallel at each step
|
|
372
|
+
// For now, return as individual levels
|
|
373
|
+
return sorted.map(id => [id]);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Build execution steps from sorted order
|
|
378
|
+
* @param {string[]} order - Execution order
|
|
379
|
+
* @param {Object[]} proposals - Proposals
|
|
380
|
+
* @param {Map} graph - Dependency graph
|
|
381
|
+
* @returns {ExecutionStep[]} Execution steps
|
|
382
|
+
*/
|
|
383
|
+
buildExecutionSteps(order, proposals, graph) {
|
|
384
|
+
const proposalMap = new Map(proposals.map(p => [p.proposalId, p]));
|
|
385
|
+
const steps = [];
|
|
386
|
+
const completed = new Set();
|
|
387
|
+
|
|
388
|
+
for (let i = 0; i < order.length; i++) {
|
|
389
|
+
const proposalId = order[i];
|
|
390
|
+
const proposal = proposalMap.get(proposalId);
|
|
391
|
+
|
|
392
|
+
if (!proposal) continue;
|
|
393
|
+
|
|
394
|
+
const dependencies = Array.from(graph.get(proposalId) || []);
|
|
395
|
+
const blockedBy = dependencies.filter(dep => !completed.has(dep));
|
|
396
|
+
|
|
397
|
+
steps.push({
|
|
398
|
+
order: i,
|
|
399
|
+
proposalId,
|
|
400
|
+
agentId: proposal.agentId,
|
|
401
|
+
sessionId: proposal.sessionId,
|
|
402
|
+
tier: proposal.tier,
|
|
403
|
+
intent: proposal.intent,
|
|
404
|
+
dependsOn: dependencies,
|
|
405
|
+
blockedBy,
|
|
406
|
+
canExecuteNow: blockedBy.length === 0,
|
|
407
|
+
status: "pending",
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
// For planning purposes, mark as "completed"
|
|
411
|
+
completed.add(proposalId);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
return steps;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* Get blocking conflicts from the conflict resolver
|
|
419
|
+
* @param {Object[]} proposals - Proposals to check
|
|
420
|
+
* @returns {Object[]} Blocking conflicts
|
|
421
|
+
*/
|
|
422
|
+
getBlockingConflicts(proposals) {
|
|
423
|
+
if (!this.conflictResolver) return [];
|
|
424
|
+
|
|
425
|
+
const blocking = [];
|
|
426
|
+
|
|
427
|
+
for (const proposal of proposals) {
|
|
428
|
+
const result = this.conflictResolver.canProceed(proposal.proposalId);
|
|
429
|
+
if (!result.canProceed) {
|
|
430
|
+
blocking.push(...result.mustResolve);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
return blocking;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/**
|
|
438
|
+
* Update step status
|
|
439
|
+
* @param {string} planId - Plan ID
|
|
440
|
+
* @param {string} proposalId - Proposal ID
|
|
441
|
+
* @param {string} status - New status
|
|
442
|
+
*/
|
|
443
|
+
updateStepStatus(planId, proposalId, status) {
|
|
444
|
+
const plan = this.plans.get(planId);
|
|
445
|
+
if (!plan) return;
|
|
446
|
+
|
|
447
|
+
const step = plan.steps.find(s => s.proposalId === proposalId);
|
|
448
|
+
if (step) {
|
|
449
|
+
step.status = status;
|
|
450
|
+
|
|
451
|
+
// Update canExecuteNow for dependent steps
|
|
452
|
+
if (status === "completed") {
|
|
453
|
+
for (const s of plan.steps) {
|
|
454
|
+
s.blockedBy = s.blockedBy.filter(b => b !== proposalId);
|
|
455
|
+
s.canExecuteNow = s.blockedBy.length === 0 && s.status === "pending";
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// Record in history
|
|
460
|
+
this.executionHistory.push({
|
|
461
|
+
planId,
|
|
462
|
+
proposalId,
|
|
463
|
+
status,
|
|
464
|
+
timestamp: new Date(),
|
|
465
|
+
});
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
/**
|
|
470
|
+
* Get next executable steps from a plan
|
|
471
|
+
* @param {string} planId - Plan ID
|
|
472
|
+
* @returns {ExecutionStep[]} Executable steps
|
|
473
|
+
*/
|
|
474
|
+
getNextExecutableSteps(planId) {
|
|
475
|
+
const plan = this.plans.get(planId);
|
|
476
|
+
if (!plan) return [];
|
|
477
|
+
|
|
478
|
+
return plan.steps.filter(s => s.canExecuteNow && s.status === "pending");
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
/**
|
|
482
|
+
* Check if plan is complete
|
|
483
|
+
* @param {string} planId - Plan ID
|
|
484
|
+
* @returns {boolean} Is complete
|
|
485
|
+
*/
|
|
486
|
+
isPlanComplete(planId) {
|
|
487
|
+
const plan = this.plans.get(planId);
|
|
488
|
+
if (!plan) return false;
|
|
489
|
+
|
|
490
|
+
return plan.steps.every(s => s.status === "completed" || s.status === "failed");
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
/**
|
|
494
|
+
* Get plan summary
|
|
495
|
+
* @param {string} planId - Plan ID
|
|
496
|
+
* @returns {Object} Plan summary
|
|
497
|
+
*/
|
|
498
|
+
getPlanSummary(planId) {
|
|
499
|
+
const plan = this.plans.get(planId);
|
|
500
|
+
if (!plan) return null;
|
|
501
|
+
|
|
502
|
+
const pending = plan.steps.filter(s => s.status === "pending").length;
|
|
503
|
+
const executing = plan.steps.filter(s => s.status === "executing").length;
|
|
504
|
+
const completed = plan.steps.filter(s => s.status === "completed").length;
|
|
505
|
+
const failed = plan.steps.filter(s => s.status === "failed").length;
|
|
506
|
+
|
|
507
|
+
return {
|
|
508
|
+
planId,
|
|
509
|
+
totalSteps: plan.steps.length,
|
|
510
|
+
pending,
|
|
511
|
+
executing,
|
|
512
|
+
completed,
|
|
513
|
+
failed,
|
|
514
|
+
canExecute: plan.canExecute,
|
|
515
|
+
isComplete: pending === 0 && executing === 0,
|
|
516
|
+
conflicts: plan.conflicts.length,
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
/**
|
|
521
|
+
* Normalize a file path
|
|
522
|
+
* @param {string} filePath - Path to normalize
|
|
523
|
+
* @returns {string} Normalized path
|
|
524
|
+
*/
|
|
525
|
+
normalizePath(filePath) {
|
|
526
|
+
if (!filePath) return "";
|
|
527
|
+
return path.resolve(filePath).replace(/\\/g, "/").toLowerCase();
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
/**
|
|
532
|
+
* Create a new execution planner
|
|
533
|
+
* @param {Object} conflictResolver - Optional conflict resolver
|
|
534
|
+
* @returns {ExecutionPlanner} New planner
|
|
535
|
+
*/
|
|
536
|
+
function createExecutionPlanner(conflictResolver = null) {
|
|
537
|
+
return new ExecutionPlanner(conflictResolver);
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
export {
|
|
541
|
+
ExecutionPlanner,
|
|
542
|
+
createExecutionPlanner,
|
|
543
|
+
TIER_PRIORITY,
|
|
544
|
+
};
|