overture-mcp 0.1.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/README.md +154 -0
- package/dist/chunk-LFRCKLZZ.js +2688 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +56 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +608 -0
- package/package.json +71 -0
- package/prompts/claude-code.md +832 -0
- package/prompts/cline.md +648 -0
- package/prompts/cursor.md +656 -0
- package/prompts/overture-instructions.md +376 -0
- package/prompts/sixth.md +640 -0
- package/ui-dist/assets/index-BLZ2dlmA.css +1 -0
- package/ui-dist/assets/index-DwGNwSSY.js +408 -0
- package/ui-dist/assets/index-DwGNwSSY.js.map +1 -0
- package/ui-dist/favicon.svg +8 -0
- package/ui-dist/index.html +18 -0
|
@@ -0,0 +1,2688 @@
|
|
|
1
|
+
// src/storage/history-storage.ts
|
|
2
|
+
import fs from "fs/promises";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import os from "os";
|
|
5
|
+
var HISTORY_DIR = path.join(os.homedir(), ".overture");
|
|
6
|
+
var HISTORY_FILE = path.join(HISTORY_DIR, "history.json");
|
|
7
|
+
var MAX_HISTORY_ENTRIES = 100;
|
|
8
|
+
var HistoryStorage = class {
|
|
9
|
+
cache = null;
|
|
10
|
+
writeDebounceTimer = null;
|
|
11
|
+
writePromise = null;
|
|
12
|
+
/**
|
|
13
|
+
* Ensure the history directory exists
|
|
14
|
+
*/
|
|
15
|
+
async ensureDirectory() {
|
|
16
|
+
try {
|
|
17
|
+
await fs.mkdir(HISTORY_DIR, { recursive: true });
|
|
18
|
+
} catch (error) {
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Load history from disk (with caching)
|
|
23
|
+
*/
|
|
24
|
+
async load() {
|
|
25
|
+
if (this.cache) return this.cache;
|
|
26
|
+
try {
|
|
27
|
+
await this.ensureDirectory();
|
|
28
|
+
const data = await fs.readFile(HISTORY_FILE, "utf-8");
|
|
29
|
+
const parsed = JSON.parse(data);
|
|
30
|
+
if (!parsed || typeof parsed.version !== "number") {
|
|
31
|
+
console.error("[Overture] Invalid history file format, but preserving file");
|
|
32
|
+
this.cache = {
|
|
33
|
+
version: 1,
|
|
34
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
35
|
+
entries: [],
|
|
36
|
+
plans: {}
|
|
37
|
+
};
|
|
38
|
+
return this.cache;
|
|
39
|
+
}
|
|
40
|
+
this.cache = parsed;
|
|
41
|
+
console.error(`[Overture] Loaded history: ${this.cache.entries.length} entries`);
|
|
42
|
+
return this.cache;
|
|
43
|
+
} catch (error) {
|
|
44
|
+
if (error.code === "ENOENT") {
|
|
45
|
+
console.error("[Overture] History file does not exist, creating new one");
|
|
46
|
+
this.cache = {
|
|
47
|
+
version: 1,
|
|
48
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
49
|
+
entries: [],
|
|
50
|
+
plans: {}
|
|
51
|
+
};
|
|
52
|
+
await this.ensureDirectory();
|
|
53
|
+
await fs.writeFile(HISTORY_FILE, JSON.stringify(this.cache, null, 2));
|
|
54
|
+
console.error("[Overture] Created new history file at", HISTORY_FILE);
|
|
55
|
+
return this.cache;
|
|
56
|
+
}
|
|
57
|
+
console.error("[Overture] Error loading history file:", error.message);
|
|
58
|
+
console.error("[Overture] NOT overwriting existing file - using empty cache");
|
|
59
|
+
this.cache = {
|
|
60
|
+
version: 1,
|
|
61
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
62
|
+
entries: [],
|
|
63
|
+
plans: {}
|
|
64
|
+
};
|
|
65
|
+
return this.cache;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Initialize history storage (call this on server start)
|
|
70
|
+
*/
|
|
71
|
+
async initialize() {
|
|
72
|
+
const history = await this.load();
|
|
73
|
+
console.error("[Overture] History storage initialized");
|
|
74
|
+
console.error("[Overture] History file:", HISTORY_FILE);
|
|
75
|
+
console.error("[Overture] Entries loaded:", history.entries.length);
|
|
76
|
+
if (history.entries.length > 0) {
|
|
77
|
+
console.error("[Overture] Most recent plan:", history.entries[0].title);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Save history to disk (debounced to avoid excessive writes)
|
|
82
|
+
*/
|
|
83
|
+
async save() {
|
|
84
|
+
if (this.writeDebounceTimer) {
|
|
85
|
+
clearTimeout(this.writeDebounceTimer);
|
|
86
|
+
}
|
|
87
|
+
if (this.writePromise) {
|
|
88
|
+
return this.writePromise;
|
|
89
|
+
}
|
|
90
|
+
this.writePromise = new Promise((resolve, reject) => {
|
|
91
|
+
this.writeDebounceTimer = setTimeout(async () => {
|
|
92
|
+
try {
|
|
93
|
+
if (!this.cache) {
|
|
94
|
+
resolve();
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
this.cache.lastUpdated = (/* @__PURE__ */ new Date()).toISOString();
|
|
98
|
+
await this.ensureDirectory();
|
|
99
|
+
await fs.writeFile(HISTORY_FILE, JSON.stringify(this.cache, null, 2));
|
|
100
|
+
console.error("[Overture] History saved to", HISTORY_FILE);
|
|
101
|
+
resolve();
|
|
102
|
+
} catch (error) {
|
|
103
|
+
console.error("[Overture] Failed to save history:", error);
|
|
104
|
+
reject(error);
|
|
105
|
+
} finally {
|
|
106
|
+
this.writePromise = null;
|
|
107
|
+
}
|
|
108
|
+
}, 1e3);
|
|
109
|
+
});
|
|
110
|
+
return this.writePromise;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Force immediate save (bypass debounce)
|
|
114
|
+
*/
|
|
115
|
+
async saveNow() {
|
|
116
|
+
if (this.writeDebounceTimer) {
|
|
117
|
+
clearTimeout(this.writeDebounceTimer);
|
|
118
|
+
this.writeDebounceTimer = null;
|
|
119
|
+
}
|
|
120
|
+
if (!this.cache) return;
|
|
121
|
+
this.cache.lastUpdated = (/* @__PURE__ */ new Date()).toISOString();
|
|
122
|
+
await this.ensureDirectory();
|
|
123
|
+
await fs.writeFile(HISTORY_FILE, JSON.stringify(this.cache, null, 2));
|
|
124
|
+
console.error("[Overture] History saved (immediate) to", HISTORY_FILE);
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Save or update a plan in history
|
|
128
|
+
*/
|
|
129
|
+
async savePlan(plan) {
|
|
130
|
+
const history = await this.load();
|
|
131
|
+
const entry = {
|
|
132
|
+
id: plan.plan.id,
|
|
133
|
+
projectId: plan.plan.projectId,
|
|
134
|
+
workspacePath: plan.plan.workspacePath,
|
|
135
|
+
projectName: path.basename(plan.plan.workspacePath),
|
|
136
|
+
title: plan.plan.title,
|
|
137
|
+
agent: plan.plan.agent,
|
|
138
|
+
status: plan.plan.status,
|
|
139
|
+
createdAt: plan.plan.createdAt,
|
|
140
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
141
|
+
nodeCount: plan.nodes.length,
|
|
142
|
+
completedNodeCount: plan.nodes.filter((n) => n.status === "completed").length
|
|
143
|
+
};
|
|
144
|
+
const existingIndex = history.entries.findIndex((e) => e.id === plan.plan.id);
|
|
145
|
+
if (existingIndex >= 0) {
|
|
146
|
+
history.entries[existingIndex] = entry;
|
|
147
|
+
} else {
|
|
148
|
+
history.entries.unshift(entry);
|
|
149
|
+
}
|
|
150
|
+
history.plans[plan.plan.id] = plan;
|
|
151
|
+
if (history.entries.length > MAX_HISTORY_ENTRIES) {
|
|
152
|
+
const removed = history.entries.splice(MAX_HISTORY_ENTRIES);
|
|
153
|
+
removed.forEach((e) => delete history.plans[e.id]);
|
|
154
|
+
console.error(`[Overture] Pruned ${removed.length} old history entries`);
|
|
155
|
+
}
|
|
156
|
+
await this.save();
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Get a full plan by ID
|
|
160
|
+
*/
|
|
161
|
+
async getPlan(planId) {
|
|
162
|
+
const history = await this.load();
|
|
163
|
+
return history.plans[planId] || null;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Get history entries filtered by project
|
|
167
|
+
*/
|
|
168
|
+
async getEntriesByProject(projectId) {
|
|
169
|
+
const history = await this.load();
|
|
170
|
+
return history.entries.filter((e) => e.projectId === projectId);
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Get all history entries
|
|
174
|
+
*/
|
|
175
|
+
async getAllEntries() {
|
|
176
|
+
const history = await this.load();
|
|
177
|
+
return history.entries;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Delete a plan from history
|
|
181
|
+
*/
|
|
182
|
+
async deletePlan(planId) {
|
|
183
|
+
const history = await this.load();
|
|
184
|
+
history.entries = history.entries.filter((e) => e.id !== planId);
|
|
185
|
+
delete history.plans[planId];
|
|
186
|
+
await this.save();
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Clear all history
|
|
190
|
+
*/
|
|
191
|
+
async clearAll() {
|
|
192
|
+
this.cache = {
|
|
193
|
+
version: 1,
|
|
194
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
195
|
+
entries: [],
|
|
196
|
+
plans: {}
|
|
197
|
+
};
|
|
198
|
+
await this.saveNow();
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Get the history file path (for debugging)
|
|
202
|
+
*/
|
|
203
|
+
getHistoryPath() {
|
|
204
|
+
return HISTORY_FILE;
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
var historyStorage = new HistoryStorage();
|
|
208
|
+
|
|
209
|
+
// src/websocket/ws-server.ts
|
|
210
|
+
import { WebSocketServer, WebSocket } from "ws";
|
|
211
|
+
|
|
212
|
+
// src/utils/plan-diff.ts
|
|
213
|
+
function calculatePlanDiff(oldPlan, newPlan) {
|
|
214
|
+
const oldNodeMap = new Map(oldPlan.nodes.map((n) => [n.id, n]));
|
|
215
|
+
const newNodeMap = new Map(newPlan.nodes.map((n) => [n.id, n]));
|
|
216
|
+
const addedNodes = [];
|
|
217
|
+
const removedNodes = [];
|
|
218
|
+
const modifiedNodes = [];
|
|
219
|
+
for (const [nodeId, newNode] of newNodeMap) {
|
|
220
|
+
const oldNode = oldNodeMap.get(nodeId);
|
|
221
|
+
if (!oldNode) {
|
|
222
|
+
addedNodes.push(newNode);
|
|
223
|
+
} else if (!nodesAreEqual(oldNode, newNode)) {
|
|
224
|
+
modifiedNodes.push({ before: oldNode, after: newNode });
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
for (const [nodeId, oldNode] of oldNodeMap) {
|
|
228
|
+
if (!newNodeMap.has(nodeId)) {
|
|
229
|
+
removedNodes.push(oldNode);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
const oldEdgeSet = new Set(oldPlan.edges.map((e) => `${e.from}->${e.to}`));
|
|
233
|
+
const newEdgeSet = new Set(newPlan.edges.map((e) => `${e.from}->${e.to}`));
|
|
234
|
+
const addedEdges = newPlan.edges.filter((e) => !oldEdgeSet.has(`${e.from}->${e.to}`));
|
|
235
|
+
const removedEdges = oldPlan.edges.filter((e) => !newEdgeSet.has(`${e.from}->${e.to}`));
|
|
236
|
+
return {
|
|
237
|
+
addedNodes,
|
|
238
|
+
removedNodes,
|
|
239
|
+
modifiedNodes,
|
|
240
|
+
addedEdges,
|
|
241
|
+
removedEdges
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
function nodesAreEqual(a, b) {
|
|
245
|
+
if (a.title !== b.title) return false;
|
|
246
|
+
if (a.description !== b.description) return false;
|
|
247
|
+
if (a.type !== b.type) return false;
|
|
248
|
+
if (a.complexity !== b.complexity) return false;
|
|
249
|
+
if (a.expectedOutput !== b.expectedOutput) return false;
|
|
250
|
+
if (a.risks !== b.risks) return false;
|
|
251
|
+
if (a.dynamicFields.length !== b.dynamicFields.length) return false;
|
|
252
|
+
for (let i = 0; i < a.dynamicFields.length; i++) {
|
|
253
|
+
const fieldA = a.dynamicFields[i];
|
|
254
|
+
const fieldB = b.dynamicFields.find((f) => f.id === fieldA.id);
|
|
255
|
+
if (!fieldB) return false;
|
|
256
|
+
if (fieldA.name !== fieldB.name) return false;
|
|
257
|
+
if (fieldA.type !== fieldB.type) return false;
|
|
258
|
+
if (fieldA.title !== fieldB.title) return false;
|
|
259
|
+
if (fieldA.required !== fieldB.required) return false;
|
|
260
|
+
}
|
|
261
|
+
if (a.branches?.length !== b.branches?.length) return false;
|
|
262
|
+
if (a.branches && b.branches) {
|
|
263
|
+
for (let i = 0; i < a.branches.length; i++) {
|
|
264
|
+
const branchA = a.branches[i];
|
|
265
|
+
const branchB = b.branches.find((br) => br.id === branchA.id);
|
|
266
|
+
if (!branchB) return false;
|
|
267
|
+
if (branchA.label !== branchB.label) return false;
|
|
268
|
+
if (branchA.description !== branchB.description) return false;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
return true;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// src/store/plan-store.ts
|
|
275
|
+
import path2 from "path";
|
|
276
|
+
var DEFAULT_PROJECT_ID = "default";
|
|
277
|
+
var MultiProjectPlanStore = class {
|
|
278
|
+
projects = /* @__PURE__ */ new Map();
|
|
279
|
+
// Per-project control state
|
|
280
|
+
approvalResolvers = /* @__PURE__ */ new Map();
|
|
281
|
+
approvalPromises = /* @__PURE__ */ new Map();
|
|
282
|
+
pendingRerunRequests = /* @__PURE__ */ new Map();
|
|
283
|
+
rerunResolvers = /* @__PURE__ */ new Map();
|
|
284
|
+
rerunPromises = /* @__PURE__ */ new Map();
|
|
285
|
+
pauseStates = /* @__PURE__ */ new Map();
|
|
286
|
+
pauseResolvers = /* @__PURE__ */ new Map();
|
|
287
|
+
pausePromises = /* @__PURE__ */ new Map();
|
|
288
|
+
// Store previous plan state for diff calculation
|
|
289
|
+
previousPlanStates = /* @__PURE__ */ new Map();
|
|
290
|
+
// Auto-save interval
|
|
291
|
+
autoSaveInterval = null;
|
|
292
|
+
constructor() {
|
|
293
|
+
this.startAutoSave();
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* Start auto-save interval to persist all active projects every 3 seconds
|
|
297
|
+
*/
|
|
298
|
+
startAutoSave() {
|
|
299
|
+
console.error("[Overture] Starting auto-save interval (every 3 seconds)");
|
|
300
|
+
this.autoSaveInterval = setInterval(async () => {
|
|
301
|
+
const activeProjects = Array.from(this.projects.entries()).filter(([, state]) => state.plan);
|
|
302
|
+
if (activeProjects.length > 0) {
|
|
303
|
+
console.error(`[Overture] Auto-saving ${activeProjects.length} active project(s)...`);
|
|
304
|
+
for (const [projectId, state] of activeProjects) {
|
|
305
|
+
try {
|
|
306
|
+
const persisted = {
|
|
307
|
+
plan: state.plan,
|
|
308
|
+
nodes: state.nodes,
|
|
309
|
+
edges: state.edges,
|
|
310
|
+
fieldValues: state.fieldValues,
|
|
311
|
+
selectedBranches: state.selectedBranches,
|
|
312
|
+
nodeConfigs: state.nodeConfigs
|
|
313
|
+
};
|
|
314
|
+
await historyStorage.savePlan(persisted);
|
|
315
|
+
await historyStorage.saveNow();
|
|
316
|
+
console.error(`[Overture] Auto-saved project ${projectId} (plan: ${state.plan.id})`);
|
|
317
|
+
} catch (error) {
|
|
318
|
+
console.error(`[Overture] Auto-save failed for project ${projectId}:`, error);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}, 3e3);
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* Get or initialize project state
|
|
326
|
+
*/
|
|
327
|
+
getProjectState(projectId) {
|
|
328
|
+
return this.projects.get(projectId) || null;
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* Initialize a new project
|
|
332
|
+
*/
|
|
333
|
+
initializeProject(context) {
|
|
334
|
+
if (!this.projects.has(context.projectId)) {
|
|
335
|
+
this.projects.set(context.projectId, {
|
|
336
|
+
projectId: context.projectId,
|
|
337
|
+
workspacePath: context.workspacePath,
|
|
338
|
+
plan: null,
|
|
339
|
+
nodes: [],
|
|
340
|
+
edges: [],
|
|
341
|
+
fieldValues: {},
|
|
342
|
+
selectedBranches: {},
|
|
343
|
+
nodeConfigs: {}
|
|
344
|
+
});
|
|
345
|
+
console.error(`[Overture] Initialized project: ${context.projectName} (${context.projectId})`);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* Get all active projects
|
|
350
|
+
*/
|
|
351
|
+
getAllProjects() {
|
|
352
|
+
const contexts = [];
|
|
353
|
+
for (const [projectId, state] of this.projects) {
|
|
354
|
+
contexts.push({
|
|
355
|
+
projectId,
|
|
356
|
+
workspacePath: state.workspacePath,
|
|
357
|
+
projectName: path2.basename(state.workspacePath),
|
|
358
|
+
agentType: state.plan?.agent || "unknown"
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
return contexts;
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* Get all active plans across all projects
|
|
365
|
+
*/
|
|
366
|
+
getAllActivePlans() {
|
|
367
|
+
const result = [];
|
|
368
|
+
for (const [projectId, state] of this.projects) {
|
|
369
|
+
if (state.plan) {
|
|
370
|
+
result.push({
|
|
371
|
+
projectId,
|
|
372
|
+
plan: state.plan,
|
|
373
|
+
nodes: state.nodes
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
return result;
|
|
378
|
+
}
|
|
379
|
+
// === Plan Management ===
|
|
380
|
+
/**
|
|
381
|
+
* Start a new plan for a project
|
|
382
|
+
*/
|
|
383
|
+
startPlan(projectId, plan) {
|
|
384
|
+
let state = this.projects.get(projectId);
|
|
385
|
+
if (!state) {
|
|
386
|
+
this.initializeProject({
|
|
387
|
+
projectId,
|
|
388
|
+
workspacePath: process.cwd(),
|
|
389
|
+
projectName: "default",
|
|
390
|
+
agentType: plan.agent
|
|
391
|
+
});
|
|
392
|
+
state = this.projects.get(projectId);
|
|
393
|
+
}
|
|
394
|
+
const planWithProject = {
|
|
395
|
+
...plan,
|
|
396
|
+
projectId,
|
|
397
|
+
workspacePath: state.workspacePath
|
|
398
|
+
};
|
|
399
|
+
state.plan = planWithProject;
|
|
400
|
+
state.nodes = [];
|
|
401
|
+
state.edges = [];
|
|
402
|
+
state.fieldValues = {};
|
|
403
|
+
state.selectedBranches = {};
|
|
404
|
+
state.nodeConfigs = {};
|
|
405
|
+
console.error(`[Overture] Plan stored for project: ${projectId}, planId: ${planWithProject.id}`);
|
|
406
|
+
console.error(`[Overture] All projects after startPlan:`, Array.from(this.projects.keys()));
|
|
407
|
+
console.error(`[Overture] Creating approval promise for project: ${projectId}`);
|
|
408
|
+
this.approvalPromises.set(projectId, new Promise((resolve) => {
|
|
409
|
+
this.approvalResolvers.set(projectId, resolve);
|
|
410
|
+
}));
|
|
411
|
+
this.pauseStates.set(projectId, false);
|
|
412
|
+
console.error(`[Overture] Plan started. Projects with promises:`, Array.from(this.approvalPromises.keys()));
|
|
413
|
+
this.persistToHistory(projectId);
|
|
414
|
+
return planWithProject;
|
|
415
|
+
}
|
|
416
|
+
getPlan(projectId) {
|
|
417
|
+
return this.projects.get(projectId)?.plan ?? null;
|
|
418
|
+
}
|
|
419
|
+
getNodes(projectId) {
|
|
420
|
+
return this.projects.get(projectId)?.nodes ?? [];
|
|
421
|
+
}
|
|
422
|
+
getEdges(projectId) {
|
|
423
|
+
return this.projects.get(projectId)?.edges ?? [];
|
|
424
|
+
}
|
|
425
|
+
getState(projectId) {
|
|
426
|
+
const state = this.projects.get(projectId);
|
|
427
|
+
if (!state) return null;
|
|
428
|
+
return {
|
|
429
|
+
plan: state.plan,
|
|
430
|
+
nodes: state.nodes,
|
|
431
|
+
edges: state.edges,
|
|
432
|
+
fieldValues: state.fieldValues,
|
|
433
|
+
selectedBranches: state.selectedBranches,
|
|
434
|
+
nodeConfigs: state.nodeConfigs
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
getFieldValues(projectId) {
|
|
438
|
+
return this.projects.get(projectId)?.fieldValues ?? {};
|
|
439
|
+
}
|
|
440
|
+
getSelectedBranches(projectId) {
|
|
441
|
+
return this.projects.get(projectId)?.selectedBranches ?? {};
|
|
442
|
+
}
|
|
443
|
+
getNodeConfigs(projectId) {
|
|
444
|
+
return this.projects.get(projectId)?.nodeConfigs ?? {};
|
|
445
|
+
}
|
|
446
|
+
addNode(projectId, node) {
|
|
447
|
+
const state = this.projects.get(projectId);
|
|
448
|
+
if (state) {
|
|
449
|
+
state.nodes.push(node);
|
|
450
|
+
this.persistToHistory(projectId);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
addEdge(projectId, edge) {
|
|
454
|
+
const state = this.projects.get(projectId);
|
|
455
|
+
if (state) {
|
|
456
|
+
state.edges.push(edge);
|
|
457
|
+
this.persistToHistory(projectId);
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
updatePlanStatus(projectId, status) {
|
|
461
|
+
const state = this.projects.get(projectId);
|
|
462
|
+
if (state?.plan) {
|
|
463
|
+
state.plan.status = status;
|
|
464
|
+
this.persistToHistory(projectId);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
updateNodeStatus(projectId, nodeId, status, output) {
|
|
468
|
+
const state = this.projects.get(projectId);
|
|
469
|
+
if (!state) return;
|
|
470
|
+
const node = state.nodes.find((n) => n.id === nodeId);
|
|
471
|
+
if (node) {
|
|
472
|
+
node.status = status;
|
|
473
|
+
if (output) {
|
|
474
|
+
node.output = output;
|
|
475
|
+
}
|
|
476
|
+
this.persistToHistory(projectId);
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
// === Approval ===
|
|
480
|
+
async setApproval(projectId, fieldValues, selectedBranches, nodeConfigs = {}) {
|
|
481
|
+
console.error(`[Overture] setApproval called for project: ${projectId}`);
|
|
482
|
+
console.error(`[Overture] Available projects:`, Array.from(this.projects.keys()));
|
|
483
|
+
console.error(`[Overture] Available resolvers:`, Array.from(this.approvalResolvers.keys()));
|
|
484
|
+
let state = this.projects.get(projectId);
|
|
485
|
+
if (!state) {
|
|
486
|
+
console.error(`[Overture] No state found for project ${projectId}, attempting to restore from history...`);
|
|
487
|
+
const restored = await this.restoreProjectFromHistory(projectId);
|
|
488
|
+
if (restored) {
|
|
489
|
+
state = this.projects.get(projectId);
|
|
490
|
+
console.error(`[Overture] Successfully restored project from history`);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
if (!state) {
|
|
494
|
+
console.error(`[Overture] ERROR: No state found for project ${projectId} and could not restore from history`);
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
497
|
+
state.fieldValues = fieldValues;
|
|
498
|
+
state.selectedBranches = selectedBranches;
|
|
499
|
+
state.nodeConfigs = nodeConfigs;
|
|
500
|
+
if (state.plan) {
|
|
501
|
+
state.plan.status = "approved";
|
|
502
|
+
console.error(`[Overture] Plan status set to 'approved'`);
|
|
503
|
+
}
|
|
504
|
+
const resolver = this.approvalResolvers.get(projectId);
|
|
505
|
+
if (resolver) {
|
|
506
|
+
console.error(`[Overture] Resolving approval promise for project: ${projectId}`);
|
|
507
|
+
resolver(true);
|
|
508
|
+
this.approvalResolvers.delete(projectId);
|
|
509
|
+
} else {
|
|
510
|
+
console.error(`[Overture] WARNING: No resolver found for project ${projectId}`);
|
|
511
|
+
}
|
|
512
|
+
this.persistToHistory(projectId);
|
|
513
|
+
}
|
|
514
|
+
cancelApproval(projectId) {
|
|
515
|
+
const resolver = this.approvalResolvers.get(projectId);
|
|
516
|
+
if (resolver) {
|
|
517
|
+
resolver(false);
|
|
518
|
+
this.approvalResolvers.delete(projectId);
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
async waitForApproval(projectId, timeoutMs = 6e4) {
|
|
522
|
+
console.error(`[Overture] waitForApproval called for project: ${projectId}`);
|
|
523
|
+
console.error(`[Overture] Available promises:`, Array.from(this.approvalPromises.keys()));
|
|
524
|
+
let promise = this.approvalPromises.get(projectId);
|
|
525
|
+
if (!promise) {
|
|
526
|
+
console.error(`[Overture] No approval promise found, attempting to restore project from history...`);
|
|
527
|
+
const restored = await this.restoreProjectFromHistory(projectId);
|
|
528
|
+
if (restored) {
|
|
529
|
+
promise = this.approvalPromises.get(projectId);
|
|
530
|
+
console.error(`[Overture] Project restored, approval promise created`);
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
if (!promise) {
|
|
534
|
+
const state = this.projects.get(projectId);
|
|
535
|
+
if (state?.plan) {
|
|
536
|
+
console.error(`[Overture] Project exists but no promise, creating one...`);
|
|
537
|
+
promise = new Promise((resolve) => {
|
|
538
|
+
this.approvalResolvers.set(projectId, resolve);
|
|
539
|
+
});
|
|
540
|
+
this.approvalPromises.set(projectId, promise);
|
|
541
|
+
} else {
|
|
542
|
+
console.error(`[Overture] ERROR: No project found for ${projectId}, cannot create approval promise`);
|
|
543
|
+
return "cancelled";
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
console.error(`[Overture] Waiting for approval (timeout: ${timeoutMs}ms)...`);
|
|
547
|
+
const timeoutPromise = new Promise((resolve) => {
|
|
548
|
+
setTimeout(() => resolve("pending"), timeoutMs);
|
|
549
|
+
});
|
|
550
|
+
const result = await Promise.race([
|
|
551
|
+
promise.then((approved) => approved ? "approved" : "cancelled"),
|
|
552
|
+
timeoutPromise
|
|
553
|
+
]);
|
|
554
|
+
console.error(`[Overture] waitForApproval result: ${result}`);
|
|
555
|
+
return result;
|
|
556
|
+
}
|
|
557
|
+
// === Rerun ===
|
|
558
|
+
setRerunRequest(projectId, nodeId, mode) {
|
|
559
|
+
const request = { nodeId, mode, timestamp: Date.now() };
|
|
560
|
+
this.pendingRerunRequests.set(projectId, request);
|
|
561
|
+
const resolver = this.rerunResolvers.get(projectId);
|
|
562
|
+
if (resolver) {
|
|
563
|
+
resolver(request);
|
|
564
|
+
this.rerunResolvers.delete(projectId);
|
|
565
|
+
this.rerunPromises.delete(projectId);
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
getPendingRerun(projectId) {
|
|
569
|
+
return this.pendingRerunRequests.get(projectId) || null;
|
|
570
|
+
}
|
|
571
|
+
clearPendingRerun(projectId) {
|
|
572
|
+
this.pendingRerunRequests.delete(projectId);
|
|
573
|
+
}
|
|
574
|
+
async waitForRerun(projectId, timeoutMs = 6e4) {
|
|
575
|
+
const pending = this.pendingRerunRequests.get(projectId);
|
|
576
|
+
if (pending) {
|
|
577
|
+
this.pendingRerunRequests.delete(projectId);
|
|
578
|
+
return pending;
|
|
579
|
+
}
|
|
580
|
+
const promise = new Promise((resolve) => {
|
|
581
|
+
this.rerunResolvers.set(projectId, resolve);
|
|
582
|
+
});
|
|
583
|
+
this.rerunPromises.set(projectId, promise);
|
|
584
|
+
const timeoutPromise = new Promise((resolve) => {
|
|
585
|
+
setTimeout(() => resolve(null), timeoutMs);
|
|
586
|
+
});
|
|
587
|
+
const result = await Promise.race([promise, timeoutPromise]);
|
|
588
|
+
if (result) {
|
|
589
|
+
this.pendingRerunRequests.delete(projectId);
|
|
590
|
+
}
|
|
591
|
+
return result;
|
|
592
|
+
}
|
|
593
|
+
resetNodesForRerun(projectId, startNodeId, mode) {
|
|
594
|
+
const state = this.projects.get(projectId);
|
|
595
|
+
if (!state) return [];
|
|
596
|
+
const nodeIds = [];
|
|
597
|
+
if (mode === "single") {
|
|
598
|
+
const node = state.nodes.find((n) => n.id === startNodeId);
|
|
599
|
+
if (node) {
|
|
600
|
+
node.status = "pending";
|
|
601
|
+
node.output = void 0;
|
|
602
|
+
nodeIds.push(node.id);
|
|
603
|
+
}
|
|
604
|
+
} else {
|
|
605
|
+
const startIndex = state.nodes.findIndex((n) => n.id === startNodeId);
|
|
606
|
+
if (startIndex !== -1) {
|
|
607
|
+
for (let i = startIndex; i < state.nodes.length; i++) {
|
|
608
|
+
const node = state.nodes[i];
|
|
609
|
+
node.status = "pending";
|
|
610
|
+
node.output = void 0;
|
|
611
|
+
nodeIds.push(node.id);
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
return nodeIds;
|
|
616
|
+
}
|
|
617
|
+
// === Pause/Resume ===
|
|
618
|
+
pause(projectId) {
|
|
619
|
+
this.pauseStates.set(projectId, true);
|
|
620
|
+
const state = this.projects.get(projectId);
|
|
621
|
+
if (state?.plan) {
|
|
622
|
+
state.plan.status = "paused";
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
resume(projectId) {
|
|
626
|
+
this.pauseStates.set(projectId, false);
|
|
627
|
+
const state = this.projects.get(projectId);
|
|
628
|
+
if (state?.plan) {
|
|
629
|
+
state.plan.status = "executing";
|
|
630
|
+
}
|
|
631
|
+
const resolver = this.pauseResolvers.get(projectId);
|
|
632
|
+
if (resolver) {
|
|
633
|
+
resolver();
|
|
634
|
+
this.pauseResolvers.delete(projectId);
|
|
635
|
+
this.pausePromises.delete(projectId);
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
getIsPaused(projectId) {
|
|
639
|
+
return this.pauseStates.get(projectId) || false;
|
|
640
|
+
}
|
|
641
|
+
async waitIfPaused(projectId) {
|
|
642
|
+
if (!this.pauseStates.get(projectId)) {
|
|
643
|
+
return false;
|
|
644
|
+
}
|
|
645
|
+
const promise = new Promise((resolve) => {
|
|
646
|
+
this.pauseResolvers.set(projectId, resolve);
|
|
647
|
+
});
|
|
648
|
+
this.pausePromises.set(projectId, promise);
|
|
649
|
+
await promise;
|
|
650
|
+
return true;
|
|
651
|
+
}
|
|
652
|
+
// === Node Operations ===
|
|
653
|
+
insertNodes(projectId, afterNodeId, newNodes, newEdges) {
|
|
654
|
+
const state = this.projects.get(projectId);
|
|
655
|
+
if (!state) return { removedEdgeIds: [], reconnectionEdges: [] };
|
|
656
|
+
const edgesToRemove = state.edges.filter((e) => e.from === afterNodeId);
|
|
657
|
+
const removedEdgeIds = edgesToRemove.map((e) => e.id);
|
|
658
|
+
const targetNodeIds = edgesToRemove.map((e) => e.to);
|
|
659
|
+
state.edges = state.edges.filter((e) => e.from !== afterNodeId);
|
|
660
|
+
state.nodes.push(...newNodes);
|
|
661
|
+
state.edges.push(...newEdges);
|
|
662
|
+
const newNodeIds = new Set(newNodes.map((n) => n.id));
|
|
663
|
+
const exitNodeIds = newNodes.filter((n) => !newEdges.some((e) => e.from === n.id && newNodeIds.has(e.to))).map((n) => n.id);
|
|
664
|
+
const reconnectionEdges = [];
|
|
665
|
+
let edgeCounter = Date.now();
|
|
666
|
+
for (const exitNodeId of exitNodeIds) {
|
|
667
|
+
for (const targetNodeId of targetNodeIds) {
|
|
668
|
+
const reconnectEdge = {
|
|
669
|
+
id: `e_inserted_${edgeCounter++}`,
|
|
670
|
+
from: exitNodeId,
|
|
671
|
+
to: targetNodeId
|
|
672
|
+
};
|
|
673
|
+
state.edges.push(reconnectEdge);
|
|
674
|
+
reconnectionEdges.push(reconnectEdge);
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
return { removedEdgeIds, reconnectionEdges };
|
|
678
|
+
}
|
|
679
|
+
removeNode(projectId, nodeId) {
|
|
680
|
+
const state = this.projects.get(projectId);
|
|
681
|
+
if (!state) return { newEdges: [], removedEdgeIds: [] };
|
|
682
|
+
const incomingEdges = state.edges.filter((e) => e.to === nodeId);
|
|
683
|
+
const outgoingEdges = state.edges.filter((e) => e.from === nodeId);
|
|
684
|
+
const removedEdgeIds = [
|
|
685
|
+
...incomingEdges.map((e) => e.id),
|
|
686
|
+
...outgoingEdges.map((e) => e.id)
|
|
687
|
+
];
|
|
688
|
+
const newEdges = [];
|
|
689
|
+
let edgeCounter = Date.now();
|
|
690
|
+
for (const incoming of incomingEdges) {
|
|
691
|
+
for (const outgoing of outgoingEdges) {
|
|
692
|
+
newEdges.push({
|
|
693
|
+
id: `e_bridge_${edgeCounter++}`,
|
|
694
|
+
from: incoming.from,
|
|
695
|
+
to: outgoing.to
|
|
696
|
+
});
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
state.nodes = state.nodes.filter((n) => n.id !== nodeId);
|
|
700
|
+
state.edges = [
|
|
701
|
+
...state.edges.filter((e) => e.to !== nodeId && e.from !== nodeId),
|
|
702
|
+
...newEdges
|
|
703
|
+
];
|
|
704
|
+
return { newEdges, removedEdgeIds };
|
|
705
|
+
}
|
|
706
|
+
// === Plan Update Support ===
|
|
707
|
+
/**
|
|
708
|
+
* Store the current plan state before an update for diff calculation
|
|
709
|
+
*/
|
|
710
|
+
storePreviousPlanState(projectId) {
|
|
711
|
+
const state = this.projects.get(projectId);
|
|
712
|
+
if (!state) return;
|
|
713
|
+
this.previousPlanStates.set(projectId, {
|
|
714
|
+
nodes: JSON.parse(JSON.stringify(state.nodes)),
|
|
715
|
+
edges: JSON.parse(JSON.stringify(state.edges))
|
|
716
|
+
});
|
|
717
|
+
console.error(`[Overture] Stored previous plan state for project ${projectId} (${state.nodes.length} nodes, ${state.edges.length} edges)`);
|
|
718
|
+
}
|
|
719
|
+
/**
|
|
720
|
+
* Get the previous plan state for diff calculation
|
|
721
|
+
*/
|
|
722
|
+
getPreviousPlanState(projectId) {
|
|
723
|
+
return this.previousPlanStates.get(projectId) || null;
|
|
724
|
+
}
|
|
725
|
+
/**
|
|
726
|
+
* Clear the previous plan state after diff has been calculated
|
|
727
|
+
*/
|
|
728
|
+
clearPreviousPlanState(projectId) {
|
|
729
|
+
this.previousPlanStates.delete(projectId);
|
|
730
|
+
}
|
|
731
|
+
/**
|
|
732
|
+
* Calculate diff between previous and current plan states
|
|
733
|
+
*/
|
|
734
|
+
calculateDiff(projectId) {
|
|
735
|
+
const previousState = this.previousPlanStates.get(projectId);
|
|
736
|
+
const currentState = this.projects.get(projectId);
|
|
737
|
+
if (!previousState || !currentState) {
|
|
738
|
+
return null;
|
|
739
|
+
}
|
|
740
|
+
return calculatePlanDiff(previousState, {
|
|
741
|
+
nodes: currentState.nodes,
|
|
742
|
+
edges: currentState.edges
|
|
743
|
+
});
|
|
744
|
+
}
|
|
745
|
+
/**
|
|
746
|
+
* Clear the current plan for a project to prepare for a new unrelated plan
|
|
747
|
+
*/
|
|
748
|
+
clearProjectPlan(projectId) {
|
|
749
|
+
const state = this.projects.get(projectId);
|
|
750
|
+
if (!state) return;
|
|
751
|
+
state.plan = null;
|
|
752
|
+
state.nodes = [];
|
|
753
|
+
state.edges = [];
|
|
754
|
+
state.fieldValues = {};
|
|
755
|
+
state.selectedBranches = {};
|
|
756
|
+
state.nodeConfigs = {};
|
|
757
|
+
this.approvalResolvers.delete(projectId);
|
|
758
|
+
this.approvalPromises.delete(projectId);
|
|
759
|
+
this.pendingRerunRequests.delete(projectId);
|
|
760
|
+
this.pauseStates.delete(projectId);
|
|
761
|
+
this.previousPlanStates.delete(projectId);
|
|
762
|
+
console.error(`[Overture] Cleared plan for project ${projectId}`);
|
|
763
|
+
}
|
|
764
|
+
// === History/Persistence ===
|
|
765
|
+
/**
|
|
766
|
+
* Persist current project state to history
|
|
767
|
+
* Uses immediate write to ensure data is not lost
|
|
768
|
+
*/
|
|
769
|
+
async persistToHistory(projectId) {
|
|
770
|
+
const state = this.projects.get(projectId);
|
|
771
|
+
if (!state?.plan) return;
|
|
772
|
+
try {
|
|
773
|
+
const persisted = {
|
|
774
|
+
plan: state.plan,
|
|
775
|
+
nodes: state.nodes,
|
|
776
|
+
edges: state.edges,
|
|
777
|
+
fieldValues: state.fieldValues,
|
|
778
|
+
selectedBranches: state.selectedBranches,
|
|
779
|
+
nodeConfigs: state.nodeConfigs
|
|
780
|
+
};
|
|
781
|
+
await historyStorage.savePlan(persisted);
|
|
782
|
+
await historyStorage.saveNow();
|
|
783
|
+
console.error(`[Overture] Persisted to history: ${state.plan.id} (${state.nodes.length} nodes)`);
|
|
784
|
+
} catch (error) {
|
|
785
|
+
console.error("[Overture] Failed to persist plan to history:", error);
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
/**
|
|
789
|
+
* Force immediate persist to history (for UI-triggered saves)
|
|
790
|
+
*/
|
|
791
|
+
async forcePersist(projectId) {
|
|
792
|
+
console.error(`[Overture] forcePersist called for project: ${projectId}`);
|
|
793
|
+
console.error(`[Overture] Available projects:`, Array.from(this.projects.keys()));
|
|
794
|
+
const state = this.projects.get(projectId);
|
|
795
|
+
if (!state?.plan) {
|
|
796
|
+
console.error(`[Overture] No plan found for project ${projectId}. State exists: ${!!state}, Plan exists: ${!!state?.plan}`);
|
|
797
|
+
return { success: false };
|
|
798
|
+
}
|
|
799
|
+
try {
|
|
800
|
+
const persisted = {
|
|
801
|
+
plan: state.plan,
|
|
802
|
+
nodes: state.nodes,
|
|
803
|
+
edges: state.edges,
|
|
804
|
+
fieldValues: state.fieldValues,
|
|
805
|
+
selectedBranches: state.selectedBranches,
|
|
806
|
+
nodeConfigs: state.nodeConfigs
|
|
807
|
+
};
|
|
808
|
+
await historyStorage.savePlan(persisted);
|
|
809
|
+
await historyStorage.saveNow();
|
|
810
|
+
console.error(`[Overture] Plan ${state.plan.id} force-persisted to history`);
|
|
811
|
+
return { success: true, planId: state.plan.id };
|
|
812
|
+
} catch (error) {
|
|
813
|
+
console.error("[Overture] Failed to force persist plan:", error);
|
|
814
|
+
return { success: false };
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
/**
|
|
818
|
+
* Load a plan from history into a project
|
|
819
|
+
*/
|
|
820
|
+
async loadFromHistory(planId) {
|
|
821
|
+
const persisted = await historyStorage.getPlan(planId);
|
|
822
|
+
if (!persisted) return null;
|
|
823
|
+
const state = {
|
|
824
|
+
projectId: persisted.plan.projectId,
|
|
825
|
+
workspacePath: persisted.plan.workspacePath,
|
|
826
|
+
plan: persisted.plan,
|
|
827
|
+
nodes: persisted.nodes,
|
|
828
|
+
edges: persisted.edges,
|
|
829
|
+
fieldValues: persisted.fieldValues,
|
|
830
|
+
selectedBranches: persisted.selectedBranches,
|
|
831
|
+
nodeConfigs: persisted.nodeConfigs
|
|
832
|
+
};
|
|
833
|
+
this.projects.set(state.projectId, state);
|
|
834
|
+
return state;
|
|
835
|
+
}
|
|
836
|
+
/**
|
|
837
|
+
* Restore a project from history by projectId
|
|
838
|
+
* Finds the most recent plan for this project and loads it
|
|
839
|
+
*/
|
|
840
|
+
async restoreProjectFromHistory(projectId) {
|
|
841
|
+
try {
|
|
842
|
+
const entries = await historyStorage.getEntriesByProject(projectId);
|
|
843
|
+
if (entries.length === 0) {
|
|
844
|
+
console.error(`[Overture] No history entries found for project ${projectId}`);
|
|
845
|
+
return false;
|
|
846
|
+
}
|
|
847
|
+
const mostRecent = entries[0];
|
|
848
|
+
console.error(`[Overture] Found history entry: ${mostRecent.title} (${mostRecent.id})`);
|
|
849
|
+
const persisted = await historyStorage.getPlan(mostRecent.id);
|
|
850
|
+
if (!persisted) {
|
|
851
|
+
console.error(`[Overture] Could not load plan data for ${mostRecent.id}`);
|
|
852
|
+
return false;
|
|
853
|
+
}
|
|
854
|
+
const state = {
|
|
855
|
+
projectId: persisted.plan.projectId,
|
|
856
|
+
workspacePath: persisted.plan.workspacePath,
|
|
857
|
+
plan: persisted.plan,
|
|
858
|
+
nodes: persisted.nodes,
|
|
859
|
+
edges: persisted.edges,
|
|
860
|
+
fieldValues: persisted.fieldValues,
|
|
861
|
+
selectedBranches: persisted.selectedBranches,
|
|
862
|
+
nodeConfigs: persisted.nodeConfigs
|
|
863
|
+
};
|
|
864
|
+
this.projects.set(state.projectId, state);
|
|
865
|
+
console.error(`[Overture] Creating approval promise for restored project: ${projectId}`);
|
|
866
|
+
this.approvalPromises.set(projectId, new Promise((resolve) => {
|
|
867
|
+
this.approvalResolvers.set(projectId, resolve);
|
|
868
|
+
}));
|
|
869
|
+
this.pauseStates.set(projectId, false);
|
|
870
|
+
console.error(`[Overture] Project ${projectId} restored from history with ${state.nodes.length} nodes`);
|
|
871
|
+
return true;
|
|
872
|
+
} catch (error) {
|
|
873
|
+
console.error(`[Overture] Failed to restore project from history:`, error);
|
|
874
|
+
return false;
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
// === Resume Info ===
|
|
878
|
+
/**
|
|
879
|
+
* Generate resume info for a paused/failed plan
|
|
880
|
+
*/
|
|
881
|
+
getResumeInfo(projectId) {
|
|
882
|
+
const state = this.projects.get(projectId);
|
|
883
|
+
if (!state?.plan) return null;
|
|
884
|
+
const plan = state.plan;
|
|
885
|
+
let currentNode = null;
|
|
886
|
+
for (const node of state.nodes) {
|
|
887
|
+
if (node.status === "active" || node.status === "failed") {
|
|
888
|
+
currentNode = node;
|
|
889
|
+
break;
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
if (!currentNode) {
|
|
893
|
+
const completedNodes2 = state.nodes.filter((n) => n.status === "completed");
|
|
894
|
+
if (completedNodes2.length > 0) {
|
|
895
|
+
currentNode = completedNodes2[completedNodes2.length - 1];
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
const completedNodes = state.nodes.filter((n) => n.status === "completed").map((n) => ({
|
|
899
|
+
id: n.id,
|
|
900
|
+
title: n.title,
|
|
901
|
+
output: n.output
|
|
902
|
+
}));
|
|
903
|
+
const pendingNodes = state.nodes.filter((n) => n.status === "pending").map((n) => ({
|
|
904
|
+
id: n.id,
|
|
905
|
+
title: n.title,
|
|
906
|
+
description: n.description
|
|
907
|
+
}));
|
|
908
|
+
const failedNodes = state.nodes.filter((n) => n.status === "failed").map((n) => ({
|
|
909
|
+
id: n.id,
|
|
910
|
+
title: n.title,
|
|
911
|
+
output: n.output
|
|
912
|
+
}));
|
|
913
|
+
const resumeInfo = {
|
|
914
|
+
planId: plan.id,
|
|
915
|
+
planTitle: plan.title,
|
|
916
|
+
agent: plan.agent,
|
|
917
|
+
status: plan.status,
|
|
918
|
+
projectId: state.projectId,
|
|
919
|
+
workspacePath: state.workspacePath,
|
|
920
|
+
currentNodeId: currentNode?.id || null,
|
|
921
|
+
currentNodeTitle: currentNode?.title || null,
|
|
922
|
+
currentNodeStatus: currentNode?.status || null,
|
|
923
|
+
completedNodes,
|
|
924
|
+
pendingNodes,
|
|
925
|
+
failedNodes,
|
|
926
|
+
fieldValues: state.fieldValues,
|
|
927
|
+
selectedBranches: state.selectedBranches,
|
|
928
|
+
nodeConfigs: state.nodeConfigs,
|
|
929
|
+
createdAt: plan.createdAt,
|
|
930
|
+
pausedAt: plan.status === "paused" ? (/* @__PURE__ */ new Date()).toISOString() : void 0
|
|
931
|
+
};
|
|
932
|
+
return resumeInfo;
|
|
933
|
+
}
|
|
934
|
+
// === Cleanup ===
|
|
935
|
+
clear(projectId) {
|
|
936
|
+
this.projects.delete(projectId);
|
|
937
|
+
this.approvalResolvers.delete(projectId);
|
|
938
|
+
this.approvalPromises.delete(projectId);
|
|
939
|
+
this.pendingRerunRequests.delete(projectId);
|
|
940
|
+
this.rerunResolvers.delete(projectId);
|
|
941
|
+
this.rerunPromises.delete(projectId);
|
|
942
|
+
this.pauseStates.delete(projectId);
|
|
943
|
+
this.pauseResolvers.delete(projectId);
|
|
944
|
+
this.pausePromises.delete(projectId);
|
|
945
|
+
this.previousPlanStates.delete(projectId);
|
|
946
|
+
}
|
|
947
|
+
clearAll() {
|
|
948
|
+
this.projects.clear();
|
|
949
|
+
this.approvalResolvers.clear();
|
|
950
|
+
this.approvalPromises.clear();
|
|
951
|
+
this.pendingRerunRequests.clear();
|
|
952
|
+
this.rerunResolvers.clear();
|
|
953
|
+
this.rerunPromises.clear();
|
|
954
|
+
this.pauseStates.clear();
|
|
955
|
+
this.pauseResolvers.clear();
|
|
956
|
+
this.pausePromises.clear();
|
|
957
|
+
this.previousPlanStates.clear();
|
|
958
|
+
}
|
|
959
|
+
};
|
|
960
|
+
var multiProjectPlanStore = new MultiProjectPlanStore();
|
|
961
|
+
var LegacyPlanStore = class {
|
|
962
|
+
get projectId() {
|
|
963
|
+
return DEFAULT_PROJECT_ID;
|
|
964
|
+
}
|
|
965
|
+
getPlan() {
|
|
966
|
+
return multiProjectPlanStore.getPlan(this.projectId);
|
|
967
|
+
}
|
|
968
|
+
getNodes() {
|
|
969
|
+
return multiProjectPlanStore.getNodes(this.projectId);
|
|
970
|
+
}
|
|
971
|
+
getEdges() {
|
|
972
|
+
return multiProjectPlanStore.getEdges(this.projectId);
|
|
973
|
+
}
|
|
974
|
+
getState() {
|
|
975
|
+
return multiProjectPlanStore.getState(this.projectId) || {
|
|
976
|
+
plan: null,
|
|
977
|
+
nodes: [],
|
|
978
|
+
edges: [],
|
|
979
|
+
fieldValues: {},
|
|
980
|
+
selectedBranches: {},
|
|
981
|
+
nodeConfigs: {}
|
|
982
|
+
};
|
|
983
|
+
}
|
|
984
|
+
getFieldValues() {
|
|
985
|
+
return multiProjectPlanStore.getFieldValues(this.projectId);
|
|
986
|
+
}
|
|
987
|
+
getSelectedBranches() {
|
|
988
|
+
return multiProjectPlanStore.getSelectedBranches(this.projectId);
|
|
989
|
+
}
|
|
990
|
+
getNodeConfigs() {
|
|
991
|
+
return multiProjectPlanStore.getNodeConfigs(this.projectId);
|
|
992
|
+
}
|
|
993
|
+
startPlan(plan) {
|
|
994
|
+
multiProjectPlanStore.initializeProject({
|
|
995
|
+
projectId: this.projectId,
|
|
996
|
+
workspacePath: process.cwd(),
|
|
997
|
+
projectName: "default",
|
|
998
|
+
agentType: plan.agent
|
|
999
|
+
});
|
|
1000
|
+
multiProjectPlanStore.startPlan(this.projectId, plan);
|
|
1001
|
+
}
|
|
1002
|
+
addNode(node) {
|
|
1003
|
+
multiProjectPlanStore.addNode(this.projectId, node);
|
|
1004
|
+
}
|
|
1005
|
+
addEdge(edge) {
|
|
1006
|
+
multiProjectPlanStore.addEdge(this.projectId, edge);
|
|
1007
|
+
}
|
|
1008
|
+
updatePlanStatus(status) {
|
|
1009
|
+
multiProjectPlanStore.updatePlanStatus(this.projectId, status);
|
|
1010
|
+
}
|
|
1011
|
+
updateNodeStatus(nodeId, status, output) {
|
|
1012
|
+
multiProjectPlanStore.updateNodeStatus(this.projectId, nodeId, status, output);
|
|
1013
|
+
}
|
|
1014
|
+
setApproval(fieldValues, selectedBranches, nodeConfigs = {}) {
|
|
1015
|
+
multiProjectPlanStore.setApproval(this.projectId, fieldValues, selectedBranches, nodeConfigs);
|
|
1016
|
+
}
|
|
1017
|
+
cancelApproval() {
|
|
1018
|
+
multiProjectPlanStore.cancelApproval(this.projectId);
|
|
1019
|
+
}
|
|
1020
|
+
async waitForApproval(timeoutMs = 6e4) {
|
|
1021
|
+
return multiProjectPlanStore.waitForApproval(this.projectId, timeoutMs);
|
|
1022
|
+
}
|
|
1023
|
+
setRerunRequest(nodeId, mode) {
|
|
1024
|
+
multiProjectPlanStore.setRerunRequest(this.projectId, nodeId, mode);
|
|
1025
|
+
}
|
|
1026
|
+
getPendingRerun() {
|
|
1027
|
+
return multiProjectPlanStore.getPendingRerun(this.projectId);
|
|
1028
|
+
}
|
|
1029
|
+
clearPendingRerun() {
|
|
1030
|
+
multiProjectPlanStore.clearPendingRerun(this.projectId);
|
|
1031
|
+
}
|
|
1032
|
+
async waitForRerun(timeoutMs = 6e4) {
|
|
1033
|
+
return multiProjectPlanStore.waitForRerun(this.projectId, timeoutMs);
|
|
1034
|
+
}
|
|
1035
|
+
resetNodesForRerun(startNodeId, mode) {
|
|
1036
|
+
return multiProjectPlanStore.resetNodesForRerun(this.projectId, startNodeId, mode);
|
|
1037
|
+
}
|
|
1038
|
+
pause() {
|
|
1039
|
+
multiProjectPlanStore.pause(this.projectId);
|
|
1040
|
+
}
|
|
1041
|
+
resume() {
|
|
1042
|
+
multiProjectPlanStore.resume(this.projectId);
|
|
1043
|
+
}
|
|
1044
|
+
getIsPaused() {
|
|
1045
|
+
return multiProjectPlanStore.getIsPaused(this.projectId);
|
|
1046
|
+
}
|
|
1047
|
+
async waitIfPaused() {
|
|
1048
|
+
return multiProjectPlanStore.waitIfPaused(this.projectId);
|
|
1049
|
+
}
|
|
1050
|
+
insertNodes(afterNodeId, newNodes, newEdges) {
|
|
1051
|
+
return multiProjectPlanStore.insertNodes(this.projectId, afterNodeId, newNodes, newEdges);
|
|
1052
|
+
}
|
|
1053
|
+
removeNode(nodeId) {
|
|
1054
|
+
return multiProjectPlanStore.removeNode(this.projectId, nodeId);
|
|
1055
|
+
}
|
|
1056
|
+
clear() {
|
|
1057
|
+
multiProjectPlanStore.clear(this.projectId);
|
|
1058
|
+
}
|
|
1059
|
+
};
|
|
1060
|
+
var planStore = new LegacyPlanStore();
|
|
1061
|
+
|
|
1062
|
+
// src/websocket/ws-server.ts
|
|
1063
|
+
var WebSocketManager = class {
|
|
1064
|
+
wss = null;
|
|
1065
|
+
clients = /* @__PURE__ */ new Map();
|
|
1066
|
+
relayClient = null;
|
|
1067
|
+
port = 3030;
|
|
1068
|
+
start(port) {
|
|
1069
|
+
this.port = port;
|
|
1070
|
+
try {
|
|
1071
|
+
this.wss = new WebSocketServer({ port });
|
|
1072
|
+
} catch (err) {
|
|
1073
|
+
console.error(`[Overture] WebSocket server failed to start on port ${port}, will try relay mode`);
|
|
1074
|
+
this.connectAsRelay(port);
|
|
1075
|
+
return;
|
|
1076
|
+
}
|
|
1077
|
+
this.wss.on("error", (err) => {
|
|
1078
|
+
if (err.code === "EADDRINUSE") {
|
|
1079
|
+
console.error(`[Overture] WebSocket port ${port} already in use - connecting as relay client`);
|
|
1080
|
+
this.wss = null;
|
|
1081
|
+
this.connectAsRelay(port);
|
|
1082
|
+
} else {
|
|
1083
|
+
console.error(`[Overture] WebSocket server error:`, err);
|
|
1084
|
+
}
|
|
1085
|
+
});
|
|
1086
|
+
console.error(`[Overture] WebSocket server listening on ws://localhost:${port}`);
|
|
1087
|
+
this.wss.on("connection", (ws) => {
|
|
1088
|
+
console.error("[Overture] Client connected");
|
|
1089
|
+
this.clients.set(ws, {
|
|
1090
|
+
ws,
|
|
1091
|
+
projectId: null,
|
|
1092
|
+
projectName: null,
|
|
1093
|
+
workspacePath: null
|
|
1094
|
+
});
|
|
1095
|
+
this.send(ws, { type: "connected" });
|
|
1096
|
+
const projects = multiProjectPlanStore.getAllProjects();
|
|
1097
|
+
if (projects.length > 0) {
|
|
1098
|
+
this.send(ws, { type: "projects_list", projects });
|
|
1099
|
+
}
|
|
1100
|
+
const plan = planStore.getPlan();
|
|
1101
|
+
if (plan) {
|
|
1102
|
+
this.send(ws, { type: "plan_started", plan });
|
|
1103
|
+
for (const node of planStore.getNodes()) {
|
|
1104
|
+
this.send(ws, { type: "node_added", node });
|
|
1105
|
+
}
|
|
1106
|
+
for (const edge of planStore.getEdges()) {
|
|
1107
|
+
this.send(ws, { type: "edge_added", edge });
|
|
1108
|
+
}
|
|
1109
|
+
if (plan.status === "ready") {
|
|
1110
|
+
this.send(ws, { type: "plan_ready" });
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
ws.on("message", (data) => {
|
|
1114
|
+
try {
|
|
1115
|
+
const message = JSON.parse(data.toString());
|
|
1116
|
+
if (message.type === "relay" && message.payload) {
|
|
1117
|
+
const payload = message.payload;
|
|
1118
|
+
console.error("[Overture] Relaying message:", payload.type);
|
|
1119
|
+
try {
|
|
1120
|
+
this.syncStateFromRelay(payload);
|
|
1121
|
+
} catch (err) {
|
|
1122
|
+
console.error("[Overture] Error syncing state from relay:", err);
|
|
1123
|
+
}
|
|
1124
|
+
const relayData = JSON.stringify(payload);
|
|
1125
|
+
for (const [clientWs] of this.clients) {
|
|
1126
|
+
if (clientWs !== ws && clientWs.readyState === WebSocket.OPEN) {
|
|
1127
|
+
clientWs.send(relayData);
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
return;
|
|
1131
|
+
}
|
|
1132
|
+
this.handleClientMessage(ws, message);
|
|
1133
|
+
} catch (error) {
|
|
1134
|
+
console.error("[Overture] Failed to parse client message:", error);
|
|
1135
|
+
}
|
|
1136
|
+
});
|
|
1137
|
+
ws.on("close", () => {
|
|
1138
|
+
console.error("[Overture] Client disconnected");
|
|
1139
|
+
this.clients.delete(ws);
|
|
1140
|
+
});
|
|
1141
|
+
ws.on("error", (error) => {
|
|
1142
|
+
console.error("[Overture] WebSocket error:", error);
|
|
1143
|
+
this.clients.delete(ws);
|
|
1144
|
+
});
|
|
1145
|
+
});
|
|
1146
|
+
}
|
|
1147
|
+
async handleClientMessage(ws, message) {
|
|
1148
|
+
const projectId = "projectId" in message && message.projectId ? message.projectId : "default";
|
|
1149
|
+
switch (message.type) {
|
|
1150
|
+
case "register_project": {
|
|
1151
|
+
const { projectContext } = message;
|
|
1152
|
+
console.error(`[Overture] Project registered: ${projectContext.projectName} (${projectContext.projectId})`);
|
|
1153
|
+
multiProjectPlanStore.initializeProject(projectContext);
|
|
1154
|
+
const client = this.clients.get(ws);
|
|
1155
|
+
if (client) {
|
|
1156
|
+
client.projectId = projectContext.projectId;
|
|
1157
|
+
client.projectName = projectContext.projectName;
|
|
1158
|
+
client.workspacePath = projectContext.workspacePath;
|
|
1159
|
+
}
|
|
1160
|
+
this.broadcastAll({
|
|
1161
|
+
type: "project_registered",
|
|
1162
|
+
projectId: projectContext.projectId,
|
|
1163
|
+
projectName: projectContext.projectName,
|
|
1164
|
+
workspacePath: projectContext.workspacePath
|
|
1165
|
+
});
|
|
1166
|
+
const projects = multiProjectPlanStore.getAllProjects();
|
|
1167
|
+
this.broadcastAll({ type: "projects_list", projects });
|
|
1168
|
+
break;
|
|
1169
|
+
}
|
|
1170
|
+
case "subscribe_project": {
|
|
1171
|
+
console.error(`[Overture] Client subscribed to project: ${message.projectId}`);
|
|
1172
|
+
const client = this.clients.get(ws);
|
|
1173
|
+
if (client) {
|
|
1174
|
+
client.projectId = message.projectId;
|
|
1175
|
+
const state = multiProjectPlanStore.getState(message.projectId);
|
|
1176
|
+
if (state?.plan) {
|
|
1177
|
+
this.send(ws, { type: "plan_started", plan: state.plan, projectId: message.projectId });
|
|
1178
|
+
for (const node of state.nodes) {
|
|
1179
|
+
this.send(ws, { type: "node_added", node, projectId: message.projectId });
|
|
1180
|
+
}
|
|
1181
|
+
for (const edge of state.edges) {
|
|
1182
|
+
this.send(ws, { type: "edge_added", edge, projectId: message.projectId });
|
|
1183
|
+
}
|
|
1184
|
+
if (state.plan.status === "ready") {
|
|
1185
|
+
this.send(ws, { type: "plan_ready", projectId: message.projectId });
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
break;
|
|
1190
|
+
}
|
|
1191
|
+
case "unsubscribe_project": {
|
|
1192
|
+
console.error(`[Overture] Client unsubscribed from project: ${message.projectId}`);
|
|
1193
|
+
const client = this.clients.get(ws);
|
|
1194
|
+
if (client && client.projectId === message.projectId) {
|
|
1195
|
+
client.projectId = null;
|
|
1196
|
+
}
|
|
1197
|
+
break;
|
|
1198
|
+
}
|
|
1199
|
+
case "get_history": {
|
|
1200
|
+
console.error("[Overture] History requested");
|
|
1201
|
+
let entries;
|
|
1202
|
+
if (message.projectId) {
|
|
1203
|
+
entries = await historyStorage.getEntriesByProject(message.projectId);
|
|
1204
|
+
} else {
|
|
1205
|
+
entries = await historyStorage.getAllEntries();
|
|
1206
|
+
}
|
|
1207
|
+
this.send(ws, { type: "history_entries", entries });
|
|
1208
|
+
break;
|
|
1209
|
+
}
|
|
1210
|
+
case "load_plan": {
|
|
1211
|
+
console.error(`[Overture] Loading plan from history: ${message.planId}`);
|
|
1212
|
+
const state = await multiProjectPlanStore.loadFromHistory(message.planId);
|
|
1213
|
+
if (state?.plan) {
|
|
1214
|
+
const client = this.clients.get(ws);
|
|
1215
|
+
if (client) {
|
|
1216
|
+
client.projectId = state.projectId;
|
|
1217
|
+
}
|
|
1218
|
+
this.send(ws, {
|
|
1219
|
+
type: "plan_loaded",
|
|
1220
|
+
plan: {
|
|
1221
|
+
plan: {
|
|
1222
|
+
...state.plan,
|
|
1223
|
+
projectId: state.projectId,
|
|
1224
|
+
workspacePath: state.workspacePath || ""
|
|
1225
|
+
},
|
|
1226
|
+
nodes: state.nodes,
|
|
1227
|
+
edges: state.edges,
|
|
1228
|
+
fieldValues: state.fieldValues,
|
|
1229
|
+
selectedBranches: state.selectedBranches,
|
|
1230
|
+
nodeConfigs: state.nodeConfigs
|
|
1231
|
+
},
|
|
1232
|
+
projectId: state.projectId
|
|
1233
|
+
});
|
|
1234
|
+
} else {
|
|
1235
|
+
this.send(ws, { type: "error", message: `Plan not found: ${message.planId}` });
|
|
1236
|
+
}
|
|
1237
|
+
break;
|
|
1238
|
+
}
|
|
1239
|
+
case "get_resume_info": {
|
|
1240
|
+
console.error(`[Overture] Resume info requested for project: ${message.projectId || projectId}`);
|
|
1241
|
+
const effectiveProjectId = message.projectId || projectId;
|
|
1242
|
+
if (message.planId) {
|
|
1243
|
+
const state = await multiProjectPlanStore.loadFromHistory(message.planId);
|
|
1244
|
+
if (state) {
|
|
1245
|
+
const resumeInfo = multiProjectPlanStore.getResumeInfo(state.projectId);
|
|
1246
|
+
if (resumeInfo) {
|
|
1247
|
+
this.send(ws, { type: "resume_plan_info", resumeInfo });
|
|
1248
|
+
} else {
|
|
1249
|
+
this.send(ws, { type: "error", message: `No resume info available for plan: ${message.planId}` });
|
|
1250
|
+
}
|
|
1251
|
+
} else {
|
|
1252
|
+
this.send(ws, { type: "error", message: `Plan not found: ${message.planId}` });
|
|
1253
|
+
}
|
|
1254
|
+
} else {
|
|
1255
|
+
const resumeInfo = multiProjectPlanStore.getResumeInfo(effectiveProjectId);
|
|
1256
|
+
if (resumeInfo) {
|
|
1257
|
+
this.send(ws, { type: "resume_plan_info", resumeInfo });
|
|
1258
|
+
} else {
|
|
1259
|
+
this.send(ws, { type: "error", message: `No active plan for project: ${effectiveProjectId}` });
|
|
1260
|
+
}
|
|
1261
|
+
}
|
|
1262
|
+
break;
|
|
1263
|
+
}
|
|
1264
|
+
case "save_plan": {
|
|
1265
|
+
const effectiveProjectId = message.projectId || projectId;
|
|
1266
|
+
console.error(`[Overture] Save plan requested for project: ${effectiveProjectId}`);
|
|
1267
|
+
console.error(`[Overture] Message projectId: ${message.projectId}, Default projectId: ${projectId}`);
|
|
1268
|
+
const result = await multiProjectPlanStore.forcePersist(effectiveProjectId);
|
|
1269
|
+
if (result.success && result.planId) {
|
|
1270
|
+
this.send(ws, { type: "plan_saved", projectId: effectiveProjectId, planId: result.planId });
|
|
1271
|
+
} else {
|
|
1272
|
+
this.send(ws, { type: "error", message: `No active plan to save for project: ${effectiveProjectId}` });
|
|
1273
|
+
}
|
|
1274
|
+
break;
|
|
1275
|
+
}
|
|
1276
|
+
case "approve_plan": {
|
|
1277
|
+
const effectiveProjectId = message.projectId || projectId;
|
|
1278
|
+
console.error(`[Overture] Plan approved by user (project: ${effectiveProjectId})`);
|
|
1279
|
+
console.error(`[Overture] Message projectId: ${message.projectId}, Default projectId: ${projectId}`);
|
|
1280
|
+
console.error(`[Overture] Field values:`, Object.keys(message.fieldValues || {}).length);
|
|
1281
|
+
console.error(`[Overture] Node configs:`, Object.keys(message.nodeConfigs || {}).length);
|
|
1282
|
+
const existingProject = multiProjectPlanStore.getProjectState(effectiveProjectId);
|
|
1283
|
+
console.error(`[Overture] Project exists in store: ${!!existingProject}`);
|
|
1284
|
+
if (existingProject) {
|
|
1285
|
+
console.error(`[Overture] Project plan status: ${existingProject.plan?.status}`);
|
|
1286
|
+
console.error(`[Overture] Project nodes: ${existingProject.nodes.length}`);
|
|
1287
|
+
}
|
|
1288
|
+
await multiProjectPlanStore.setApproval(
|
|
1289
|
+
effectiveProjectId,
|
|
1290
|
+
message.fieldValues,
|
|
1291
|
+
message.selectedBranches,
|
|
1292
|
+
message.nodeConfigs || {}
|
|
1293
|
+
);
|
|
1294
|
+
this.broadcastAll({
|
|
1295
|
+
type: "approval_granted",
|
|
1296
|
+
projectId: effectiveProjectId,
|
|
1297
|
+
fieldValues: message.fieldValues,
|
|
1298
|
+
selectedBranches: message.selectedBranches,
|
|
1299
|
+
nodeConfigs: message.nodeConfigs || {}
|
|
1300
|
+
});
|
|
1301
|
+
break;
|
|
1302
|
+
}
|
|
1303
|
+
case "cancel_plan":
|
|
1304
|
+
console.error(`[Overture] Plan cancelled by user (project: ${projectId})`);
|
|
1305
|
+
multiProjectPlanStore.cancelApproval(projectId);
|
|
1306
|
+
break;
|
|
1307
|
+
case "rerun_request":
|
|
1308
|
+
console.error(`[Overture] Rerun requested: node=${message.nodeId}, mode=${message.mode} (project: ${projectId})`);
|
|
1309
|
+
multiProjectPlanStore.setRerunRequest(projectId, message.nodeId, message.mode);
|
|
1310
|
+
break;
|
|
1311
|
+
case "pause_execution":
|
|
1312
|
+
console.error(`[Overture] Execution paused by user (project: ${projectId})`);
|
|
1313
|
+
multiProjectPlanStore.pause(projectId);
|
|
1314
|
+
this.broadcastToProject(projectId, { type: "plan_paused", projectId });
|
|
1315
|
+
break;
|
|
1316
|
+
case "resume_execution":
|
|
1317
|
+
console.error(`[Overture] Execution resumed by user (project: ${projectId})`);
|
|
1318
|
+
multiProjectPlanStore.resume(projectId);
|
|
1319
|
+
this.broadcastToProject(projectId, { type: "plan_resumed", projectId });
|
|
1320
|
+
break;
|
|
1321
|
+
case "insert_nodes": {
|
|
1322
|
+
console.error(`[Overture] Inserting ${message.nodes.length} node(s) after ${message.afterNodeId} (project: ${projectId})`);
|
|
1323
|
+
const insertResult = multiProjectPlanStore.insertNodes(projectId, message.afterNodeId, message.nodes, message.edges);
|
|
1324
|
+
const allEdges = [...message.edges, ...insertResult.reconnectionEdges];
|
|
1325
|
+
this.broadcastToProject(projectId, {
|
|
1326
|
+
type: "nodes_inserted",
|
|
1327
|
+
nodes: message.nodes,
|
|
1328
|
+
edges: allEdges,
|
|
1329
|
+
removedEdgeIds: insertResult.removedEdgeIds,
|
|
1330
|
+
projectId
|
|
1331
|
+
});
|
|
1332
|
+
break;
|
|
1333
|
+
}
|
|
1334
|
+
case "remove_node": {
|
|
1335
|
+
console.error(`[Overture] Removing node ${message.nodeId} (project: ${projectId})`);
|
|
1336
|
+
const removeResult = multiProjectPlanStore.removeNode(projectId, message.nodeId);
|
|
1337
|
+
this.broadcastToProject(projectId, {
|
|
1338
|
+
type: "node_removed",
|
|
1339
|
+
nodeId: message.nodeId,
|
|
1340
|
+
newEdges: removeResult.newEdges,
|
|
1341
|
+
removedEdgeIds: removeResult.removedEdgeIds,
|
|
1342
|
+
projectId
|
|
1343
|
+
});
|
|
1344
|
+
break;
|
|
1345
|
+
}
|
|
1346
|
+
case "request_plan_update": {
|
|
1347
|
+
const effectiveProjectId = message.projectId || projectId;
|
|
1348
|
+
console.error(`[Overture] Plan update requested for project: ${effectiveProjectId}`);
|
|
1349
|
+
const currentState = multiProjectPlanStore.getState(effectiveProjectId);
|
|
1350
|
+
if (currentState?.plan) {
|
|
1351
|
+
multiProjectPlanStore.storePreviousPlanState(effectiveProjectId);
|
|
1352
|
+
console.error(`[Overture] Stored previous plan state, waiting for updated plan`);
|
|
1353
|
+
} else {
|
|
1354
|
+
this.send(ws, { type: "error", message: `No active plan for project: ${effectiveProjectId}` });
|
|
1355
|
+
}
|
|
1356
|
+
break;
|
|
1357
|
+
}
|
|
1358
|
+
case "create_new_plan": {
|
|
1359
|
+
const effectiveProjectId = message.projectId || projectId;
|
|
1360
|
+
console.error(`[Overture] New plan requested for project: ${effectiveProjectId}`);
|
|
1361
|
+
this.broadcastToProject(effectiveProjectId, {
|
|
1362
|
+
type: "new_plan_created",
|
|
1363
|
+
planId: "",
|
|
1364
|
+
// Will be set when the new plan arrives
|
|
1365
|
+
projectId: effectiveProjectId
|
|
1366
|
+
});
|
|
1367
|
+
break;
|
|
1368
|
+
}
|
|
1369
|
+
}
|
|
1370
|
+
}
|
|
1371
|
+
/**
|
|
1372
|
+
* Sync local state when receiving relay messages from another MCP instance.
|
|
1373
|
+
* This ensures the main server has the project state needed to handle approve_plan.
|
|
1374
|
+
*/
|
|
1375
|
+
syncStateFromRelay(payload) {
|
|
1376
|
+
const projectId = "projectId" in payload && payload.projectId ? payload.projectId : "default";
|
|
1377
|
+
switch (payload.type) {
|
|
1378
|
+
case "project_registered": {
|
|
1379
|
+
const msg = payload;
|
|
1380
|
+
console.error(`[Overture] Syncing project_registered: ${msg.projectId}`);
|
|
1381
|
+
multiProjectPlanStore.initializeProject({
|
|
1382
|
+
projectId: msg.projectId,
|
|
1383
|
+
projectName: msg.projectName,
|
|
1384
|
+
workspacePath: msg.workspacePath,
|
|
1385
|
+
agentType: "unknown"
|
|
1386
|
+
});
|
|
1387
|
+
break;
|
|
1388
|
+
}
|
|
1389
|
+
case "plan_started": {
|
|
1390
|
+
const msg = payload;
|
|
1391
|
+
console.error(`[Overture] Syncing plan_started: ${msg.plan?.id} for project ${projectId}`);
|
|
1392
|
+
if (msg.plan) {
|
|
1393
|
+
multiProjectPlanStore.startPlan(projectId, msg.plan);
|
|
1394
|
+
}
|
|
1395
|
+
break;
|
|
1396
|
+
}
|
|
1397
|
+
case "node_added": {
|
|
1398
|
+
const msg = payload;
|
|
1399
|
+
console.error(`[Overture] Syncing node_added: ${msg.node?.id} for project ${projectId}`);
|
|
1400
|
+
if (msg.node) {
|
|
1401
|
+
multiProjectPlanStore.addNode(projectId, msg.node);
|
|
1402
|
+
}
|
|
1403
|
+
break;
|
|
1404
|
+
}
|
|
1405
|
+
case "edge_added": {
|
|
1406
|
+
const msg = payload;
|
|
1407
|
+
if (msg.edge) {
|
|
1408
|
+
multiProjectPlanStore.addEdge(projectId, msg.edge);
|
|
1409
|
+
}
|
|
1410
|
+
break;
|
|
1411
|
+
}
|
|
1412
|
+
case "plan_ready": {
|
|
1413
|
+
console.error(`[Overture] Syncing plan_ready for project ${projectId}`);
|
|
1414
|
+
multiProjectPlanStore.updatePlanStatus(projectId, "ready");
|
|
1415
|
+
break;
|
|
1416
|
+
}
|
|
1417
|
+
case "node_status_updated": {
|
|
1418
|
+
const msg = payload;
|
|
1419
|
+
multiProjectPlanStore.updateNodeStatus(projectId, msg.nodeId, msg.status, msg.output);
|
|
1420
|
+
break;
|
|
1421
|
+
}
|
|
1422
|
+
// Other message types don't need state sync
|
|
1423
|
+
default:
|
|
1424
|
+
break;
|
|
1425
|
+
}
|
|
1426
|
+
}
|
|
1427
|
+
connectAsRelay(port) {
|
|
1428
|
+
try {
|
|
1429
|
+
this.relayClient = new WebSocket(`ws://localhost:${port}`);
|
|
1430
|
+
this.relayClient.on("open", () => {
|
|
1431
|
+
console.error("[Overture] Connected as relay client to existing server");
|
|
1432
|
+
});
|
|
1433
|
+
this.relayClient.on("message", (data) => {
|
|
1434
|
+
try {
|
|
1435
|
+
const message = JSON.parse(data.toString());
|
|
1436
|
+
console.error("[Overture] Relay client received:", message.type);
|
|
1437
|
+
if (message.type === "approval_granted") {
|
|
1438
|
+
console.error("[Overture] Relay client: Resolving approval for project:", message.projectId);
|
|
1439
|
+
multiProjectPlanStore.setApproval(
|
|
1440
|
+
message.projectId,
|
|
1441
|
+
message.fieldValues || {},
|
|
1442
|
+
message.selectedBranches || {},
|
|
1443
|
+
message.nodeConfigs || {}
|
|
1444
|
+
);
|
|
1445
|
+
}
|
|
1446
|
+
} catch (err) {
|
|
1447
|
+
console.error("[Overture] Relay client: Failed to parse message:", err);
|
|
1448
|
+
}
|
|
1449
|
+
});
|
|
1450
|
+
this.relayClient.on("error", (err) => {
|
|
1451
|
+
console.error("[Overture] Relay client error:", err.message);
|
|
1452
|
+
this.relayClient = null;
|
|
1453
|
+
});
|
|
1454
|
+
this.relayClient.on("close", () => {
|
|
1455
|
+
console.error("[Overture] Relay client disconnected");
|
|
1456
|
+
this.relayClient = null;
|
|
1457
|
+
});
|
|
1458
|
+
} catch (err) {
|
|
1459
|
+
console.error("[Overture] Failed to connect as relay:", err);
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
/**
|
|
1463
|
+
* Broadcast message to all clients subscribed to a specific project
|
|
1464
|
+
*/
|
|
1465
|
+
broadcastToProject(projectId, message) {
|
|
1466
|
+
const data = JSON.stringify(message);
|
|
1467
|
+
if (this.relayClient && this.relayClient.readyState === WebSocket.OPEN) {
|
|
1468
|
+
this.relayClient.send(JSON.stringify({ type: "relay", payload: message }));
|
|
1469
|
+
return;
|
|
1470
|
+
}
|
|
1471
|
+
for (const [clientWs, client] of this.clients) {
|
|
1472
|
+
if (clientWs.readyState === WebSocket.OPEN) {
|
|
1473
|
+
if (client.projectId === projectId || client.projectId === null) {
|
|
1474
|
+
clientWs.send(data);
|
|
1475
|
+
}
|
|
1476
|
+
}
|
|
1477
|
+
}
|
|
1478
|
+
}
|
|
1479
|
+
/**
|
|
1480
|
+
* Broadcast message to ALL connected clients (for global events)
|
|
1481
|
+
*/
|
|
1482
|
+
broadcastAll(message) {
|
|
1483
|
+
const data = JSON.stringify(message);
|
|
1484
|
+
if (this.relayClient && this.relayClient.readyState === WebSocket.OPEN) {
|
|
1485
|
+
this.relayClient.send(JSON.stringify({ type: "relay", payload: message }));
|
|
1486
|
+
return;
|
|
1487
|
+
}
|
|
1488
|
+
for (const [clientWs] of this.clients) {
|
|
1489
|
+
if (clientWs.readyState === WebSocket.OPEN) {
|
|
1490
|
+
clientWs.send(data);
|
|
1491
|
+
}
|
|
1492
|
+
}
|
|
1493
|
+
}
|
|
1494
|
+
/**
|
|
1495
|
+
* Legacy broadcast - broadcasts to all clients (backwards compatibility)
|
|
1496
|
+
*/
|
|
1497
|
+
broadcast(message) {
|
|
1498
|
+
const projectId = "projectId" in message && message.projectId ? message.projectId : null;
|
|
1499
|
+
if (projectId) {
|
|
1500
|
+
this.broadcastToProject(projectId, message);
|
|
1501
|
+
} else {
|
|
1502
|
+
this.broadcastAll(message);
|
|
1503
|
+
}
|
|
1504
|
+
}
|
|
1505
|
+
send(ws, message) {
|
|
1506
|
+
if (ws.readyState === WebSocket.OPEN) {
|
|
1507
|
+
ws.send(JSON.stringify(message));
|
|
1508
|
+
}
|
|
1509
|
+
}
|
|
1510
|
+
/**
|
|
1511
|
+
* Get all active projects from connected clients
|
|
1512
|
+
*/
|
|
1513
|
+
getActiveProjects() {
|
|
1514
|
+
return multiProjectPlanStore.getAllProjects();
|
|
1515
|
+
}
|
|
1516
|
+
/**
|
|
1517
|
+
* Get clients subscribed to a specific project
|
|
1518
|
+
*/
|
|
1519
|
+
getProjectClients(projectId) {
|
|
1520
|
+
let count = 0;
|
|
1521
|
+
for (const client of this.clients.values()) {
|
|
1522
|
+
if (client.projectId === projectId) {
|
|
1523
|
+
count++;
|
|
1524
|
+
}
|
|
1525
|
+
}
|
|
1526
|
+
return count;
|
|
1527
|
+
}
|
|
1528
|
+
stop() {
|
|
1529
|
+
if (this.wss) {
|
|
1530
|
+
for (const [clientWs] of this.clients) {
|
|
1531
|
+
clientWs.close();
|
|
1532
|
+
}
|
|
1533
|
+
this.wss.close();
|
|
1534
|
+
this.wss = null;
|
|
1535
|
+
}
|
|
1536
|
+
}
|
|
1537
|
+
getClientCount() {
|
|
1538
|
+
return this.clients.size;
|
|
1539
|
+
}
|
|
1540
|
+
};
|
|
1541
|
+
var wsManager = new WebSocketManager();
|
|
1542
|
+
|
|
1543
|
+
// src/tools/handlers.ts
|
|
1544
|
+
import { createHash } from "crypto";
|
|
1545
|
+
import path3 from "path";
|
|
1546
|
+
import fs2 from "fs/promises";
|
|
1547
|
+
import { fileURLToPath } from "url";
|
|
1548
|
+
|
|
1549
|
+
// src/parser/xml-parser.ts
|
|
1550
|
+
import sax from "sax";
|
|
1551
|
+
var StreamingXMLParser = class {
|
|
1552
|
+
parser;
|
|
1553
|
+
state;
|
|
1554
|
+
callback;
|
|
1555
|
+
nodeCounter = 0;
|
|
1556
|
+
edgeCounter = 0;
|
|
1557
|
+
fieldCounter = 0;
|
|
1558
|
+
branchCounter = 0;
|
|
1559
|
+
constructor(callback) {
|
|
1560
|
+
this.callback = callback;
|
|
1561
|
+
this.parser = sax.parser(true, { trim: true });
|
|
1562
|
+
this.state = this.createInitialState();
|
|
1563
|
+
this.setupParser();
|
|
1564
|
+
}
|
|
1565
|
+
createInitialState() {
|
|
1566
|
+
return {
|
|
1567
|
+
currentNode: null,
|
|
1568
|
+
currentBranch: null,
|
|
1569
|
+
currentField: null,
|
|
1570
|
+
currentEdge: null,
|
|
1571
|
+
currentElement: null,
|
|
1572
|
+
textBuffer: "",
|
|
1573
|
+
plan: {}
|
|
1574
|
+
};
|
|
1575
|
+
}
|
|
1576
|
+
setupParser() {
|
|
1577
|
+
this.parser.onerror = (error) => {
|
|
1578
|
+
this.callback({ type: "error", error });
|
|
1579
|
+
};
|
|
1580
|
+
this.parser.onopentag = (tag) => {
|
|
1581
|
+
this.state.textBuffer = "";
|
|
1582
|
+
this.state.currentElement = tag.name;
|
|
1583
|
+
switch (tag.name) {
|
|
1584
|
+
case "plan":
|
|
1585
|
+
this.state.plan = {
|
|
1586
|
+
id: tag.attributes.id || `plan_${Date.now()}`,
|
|
1587
|
+
title: tag.attributes.title || "Untitled Plan",
|
|
1588
|
+
agent: tag.attributes.agent || "unknown",
|
|
1589
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1590
|
+
status: "streaming"
|
|
1591
|
+
};
|
|
1592
|
+
this.callback({ type: "plan", plan: this.state.plan });
|
|
1593
|
+
break;
|
|
1594
|
+
case "node":
|
|
1595
|
+
this.state.currentNode = {
|
|
1596
|
+
id: tag.attributes.id || `n${++this.nodeCounter}`,
|
|
1597
|
+
type: tag.attributes.type || "task",
|
|
1598
|
+
status: "pending",
|
|
1599
|
+
title: "",
|
|
1600
|
+
description: "",
|
|
1601
|
+
dynamicFields: [],
|
|
1602
|
+
branches: [],
|
|
1603
|
+
branchParent: tag.attributes.branch_parent,
|
|
1604
|
+
branchId: tag.attributes.branch_id
|
|
1605
|
+
};
|
|
1606
|
+
break;
|
|
1607
|
+
case "branch":
|
|
1608
|
+
this.state.currentBranch = {
|
|
1609
|
+
id: tag.attributes.id || `b${++this.branchCounter}`,
|
|
1610
|
+
label: tag.attributes.label || "",
|
|
1611
|
+
description: ""
|
|
1612
|
+
};
|
|
1613
|
+
break;
|
|
1614
|
+
case "dynamic_field":
|
|
1615
|
+
this.state.currentField = {
|
|
1616
|
+
id: tag.attributes.id || `f${++this.fieldCounter}`,
|
|
1617
|
+
name: tag.attributes.name || "",
|
|
1618
|
+
type: tag.attributes.type || "string",
|
|
1619
|
+
required: tag.attributes.required === "true",
|
|
1620
|
+
title: tag.attributes.title || "",
|
|
1621
|
+
description: tag.attributes.description || "",
|
|
1622
|
+
value: tag.attributes.value,
|
|
1623
|
+
options: tag.attributes.options,
|
|
1624
|
+
setupInstructions: tag.attributes.setup_instructions
|
|
1625
|
+
};
|
|
1626
|
+
break;
|
|
1627
|
+
case "edge":
|
|
1628
|
+
this.state.currentEdge = {
|
|
1629
|
+
id: tag.attributes.id || `e${++this.edgeCounter}`,
|
|
1630
|
+
from: tag.attributes.from,
|
|
1631
|
+
to: tag.attributes.to
|
|
1632
|
+
};
|
|
1633
|
+
break;
|
|
1634
|
+
}
|
|
1635
|
+
};
|
|
1636
|
+
this.parser.onclosetag = (tagName) => {
|
|
1637
|
+
const text = this.state.textBuffer.trim();
|
|
1638
|
+
switch (tagName) {
|
|
1639
|
+
case "plan":
|
|
1640
|
+
this.callback({ type: "complete" });
|
|
1641
|
+
break;
|
|
1642
|
+
case "node":
|
|
1643
|
+
if (this.state.currentNode) {
|
|
1644
|
+
this.callback({
|
|
1645
|
+
type: "node",
|
|
1646
|
+
node: this.state.currentNode
|
|
1647
|
+
});
|
|
1648
|
+
this.state.currentNode = null;
|
|
1649
|
+
}
|
|
1650
|
+
break;
|
|
1651
|
+
case "branch":
|
|
1652
|
+
if (this.state.currentBranch && this.state.currentNode) {
|
|
1653
|
+
if (!this.state.currentNode.branches) {
|
|
1654
|
+
this.state.currentNode.branches = [];
|
|
1655
|
+
}
|
|
1656
|
+
this.state.currentNode.branches.push(this.state.currentBranch);
|
|
1657
|
+
this.state.currentBranch = null;
|
|
1658
|
+
}
|
|
1659
|
+
break;
|
|
1660
|
+
case "dynamic_field":
|
|
1661
|
+
if (this.state.currentField && this.state.currentNode) {
|
|
1662
|
+
if (!this.state.currentNode.dynamicFields) {
|
|
1663
|
+
this.state.currentNode.dynamicFields = [];
|
|
1664
|
+
}
|
|
1665
|
+
this.state.currentNode.dynamicFields.push(
|
|
1666
|
+
this.state.currentField
|
|
1667
|
+
);
|
|
1668
|
+
this.state.currentField = null;
|
|
1669
|
+
}
|
|
1670
|
+
break;
|
|
1671
|
+
case "edge":
|
|
1672
|
+
if (this.state.currentEdge) {
|
|
1673
|
+
this.callback({
|
|
1674
|
+
type: "edge",
|
|
1675
|
+
edge: this.state.currentEdge
|
|
1676
|
+
});
|
|
1677
|
+
this.state.currentEdge = null;
|
|
1678
|
+
}
|
|
1679
|
+
break;
|
|
1680
|
+
// Node child elements
|
|
1681
|
+
case "title":
|
|
1682
|
+
if (this.state.currentNode) {
|
|
1683
|
+
this.state.currentNode.title = text;
|
|
1684
|
+
}
|
|
1685
|
+
break;
|
|
1686
|
+
case "description":
|
|
1687
|
+
if (this.state.currentBranch) {
|
|
1688
|
+
this.state.currentBranch.description = text;
|
|
1689
|
+
} else if (this.state.currentNode) {
|
|
1690
|
+
this.state.currentNode.description = text;
|
|
1691
|
+
}
|
|
1692
|
+
break;
|
|
1693
|
+
case "complexity":
|
|
1694
|
+
if (this.state.currentNode) {
|
|
1695
|
+
this.state.currentNode.complexity = text;
|
|
1696
|
+
}
|
|
1697
|
+
break;
|
|
1698
|
+
case "expected_output":
|
|
1699
|
+
if (this.state.currentNode) {
|
|
1700
|
+
this.state.currentNode.expectedOutput = text;
|
|
1701
|
+
}
|
|
1702
|
+
break;
|
|
1703
|
+
case "risks":
|
|
1704
|
+
if (this.state.currentNode) {
|
|
1705
|
+
this.state.currentNode.risks = text;
|
|
1706
|
+
}
|
|
1707
|
+
break;
|
|
1708
|
+
// Branch child elements
|
|
1709
|
+
case "label":
|
|
1710
|
+
if (this.state.currentBranch) {
|
|
1711
|
+
this.state.currentBranch.label = text;
|
|
1712
|
+
}
|
|
1713
|
+
break;
|
|
1714
|
+
case "pros":
|
|
1715
|
+
if (this.state.currentBranch) {
|
|
1716
|
+
this.state.currentBranch.pros = text;
|
|
1717
|
+
}
|
|
1718
|
+
break;
|
|
1719
|
+
case "cons":
|
|
1720
|
+
if (this.state.currentBranch) {
|
|
1721
|
+
this.state.currentBranch.cons = text;
|
|
1722
|
+
}
|
|
1723
|
+
break;
|
|
1724
|
+
}
|
|
1725
|
+
this.state.currentElement = null;
|
|
1726
|
+
this.state.textBuffer = "";
|
|
1727
|
+
};
|
|
1728
|
+
this.parser.ontext = (text) => {
|
|
1729
|
+
this.state.textBuffer += text;
|
|
1730
|
+
};
|
|
1731
|
+
this.parser.oncdata = (cdata) => {
|
|
1732
|
+
this.state.textBuffer += cdata;
|
|
1733
|
+
};
|
|
1734
|
+
}
|
|
1735
|
+
write(chunk) {
|
|
1736
|
+
this.parser.write(chunk);
|
|
1737
|
+
}
|
|
1738
|
+
close() {
|
|
1739
|
+
this.parser.close();
|
|
1740
|
+
}
|
|
1741
|
+
reset() {
|
|
1742
|
+
this.parser = sax.parser(true, { trim: true });
|
|
1743
|
+
this.state = this.createInitialState();
|
|
1744
|
+
this.nodeCounter = 0;
|
|
1745
|
+
this.edgeCounter = 0;
|
|
1746
|
+
this.fieldCounter = 0;
|
|
1747
|
+
this.branchCounter = 0;
|
|
1748
|
+
this.setupParser();
|
|
1749
|
+
}
|
|
1750
|
+
};
|
|
1751
|
+
|
|
1752
|
+
// src/tools/handlers.ts
|
|
1753
|
+
var currentParsers = /* @__PURE__ */ new Map();
|
|
1754
|
+
var currentProjectId = "default";
|
|
1755
|
+
function generateProjectId(workspacePath) {
|
|
1756
|
+
return createHash("sha256").update(workspacePath).digest("hex").substring(0, 12);
|
|
1757
|
+
}
|
|
1758
|
+
function getProviderMcpSetupInstructions(provider, serverName) {
|
|
1759
|
+
const normalizedProvider = provider.toLowerCase();
|
|
1760
|
+
const configServerName = serverName.includes("/") ? serverName : serverName.toLowerCase().replace(/\s+/g, "-");
|
|
1761
|
+
switch (normalizedProvider) {
|
|
1762
|
+
case "cline":
|
|
1763
|
+
return `
|
|
1764
|
+
### Cline MCP Setup Instructions
|
|
1765
|
+
|
|
1766
|
+
**Configuration File Locations:**
|
|
1767
|
+
- macOS: ~/Library/Application Support/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json
|
|
1768
|
+
- Windows: %APPDATA%\\Code\\User\\globalStorage\\saoudrizwan.claude-dev\\settings\\cline_mcp_settings.json
|
|
1769
|
+
- Linux: ~/.config/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json
|
|
1770
|
+
|
|
1771
|
+
**Steps:**
|
|
1772
|
+
1. Open the MCP settings file at the location above
|
|
1773
|
+
2. Add the server configuration to the "mcpServers" object
|
|
1774
|
+
3. Save the file
|
|
1775
|
+
|
|
1776
|
+
**Example Configuration:**
|
|
1777
|
+
\`\`\`json
|
|
1778
|
+
{
|
|
1779
|
+
"mcpServers": {
|
|
1780
|
+
"${configServerName}": {
|
|
1781
|
+
"command": "uvx",
|
|
1782
|
+
"args": ["mcp-server-name"],
|
|
1783
|
+
"disabled": false
|
|
1784
|
+
}
|
|
1785
|
+
}
|
|
1786
|
+
}
|
|
1787
|
+
\`\`\`
|
|
1788
|
+
|
|
1789
|
+
**Important:** Read the existing file first - DO NOT overwrite other servers!
|
|
1790
|
+
`;
|
|
1791
|
+
case "sixth":
|
|
1792
|
+
case "sixth-ai":
|
|
1793
|
+
return `
|
|
1794
|
+
### Sixth AI MCP Setup Instructions
|
|
1795
|
+
|
|
1796
|
+
**Configuration File Locations:**
|
|
1797
|
+
- macOS: ~/Library/Application Support/Code/User/globalStorage/sixth.sixth-ai/settings/sixth-mcp-settings.json
|
|
1798
|
+
- Windows: %APPDATA%\\Code\\User\\globalStorage\\sixth.sixth-ai\\settings\\sixth-mcp-settings.json
|
|
1799
|
+
- Linux: ~/.config/Code/User/globalStorage/sixth.sixth-ai/settings/sixth-mcp-settings.json
|
|
1800
|
+
|
|
1801
|
+
**Steps:**
|
|
1802
|
+
1. Open the MCP settings file at the location above
|
|
1803
|
+
2. Add the server configuration to the "mcpServers" object
|
|
1804
|
+
3. Save the file
|
|
1805
|
+
|
|
1806
|
+
**Example Configuration:**
|
|
1807
|
+
\`\`\`json
|
|
1808
|
+
{
|
|
1809
|
+
"mcpServers": {
|
|
1810
|
+
"${configServerName}": {
|
|
1811
|
+
"command": "uvx",
|
|
1812
|
+
"args": ["mcp-server-name"],
|
|
1813
|
+
"disabled": false
|
|
1814
|
+
}
|
|
1815
|
+
}
|
|
1816
|
+
}
|
|
1817
|
+
\`\`\`
|
|
1818
|
+
|
|
1819
|
+
**Important:** Read the existing file first - DO NOT overwrite other servers!
|
|
1820
|
+
`;
|
|
1821
|
+
case "cursor":
|
|
1822
|
+
return `
|
|
1823
|
+
### Cursor MCP Setup Instructions
|
|
1824
|
+
|
|
1825
|
+
**Configuration File Locations:**
|
|
1826
|
+
- Project-level: .cursor/mcp.json (in project root)
|
|
1827
|
+
- Global: ~/.cursor/mcp.json (user home directory)
|
|
1828
|
+
|
|
1829
|
+
**Steps:**
|
|
1830
|
+
1. Create or open the mcp.json file at one of the locations above
|
|
1831
|
+
2. Add the server configuration
|
|
1832
|
+
3. Save the file
|
|
1833
|
+
4. Restart Cursor or reload the window
|
|
1834
|
+
|
|
1835
|
+
**Example Configuration:**
|
|
1836
|
+
\`\`\`json
|
|
1837
|
+
{
|
|
1838
|
+
"mcpServers": {
|
|
1839
|
+
"${configServerName}": {
|
|
1840
|
+
"command": "uvx",
|
|
1841
|
+
"args": ["mcp-server-name"]
|
|
1842
|
+
}
|
|
1843
|
+
}
|
|
1844
|
+
}
|
|
1845
|
+
\`\`\`
|
|
1846
|
+
|
|
1847
|
+
**Tip:** Use project-level config for project-specific tools, global for tools you want everywhere.
|
|
1848
|
+
`;
|
|
1849
|
+
case "claude-code":
|
|
1850
|
+
case "claude":
|
|
1851
|
+
return `
|
|
1852
|
+
### Claude Code MCP Setup Instructions
|
|
1853
|
+
|
|
1854
|
+
**Option 1: Using CLI (Recommended)**
|
|
1855
|
+
\`\`\`bash
|
|
1856
|
+
claude mcp add ${configServerName} --scope user
|
|
1857
|
+
\`\`\`
|
|
1858
|
+
|
|
1859
|
+
**Option 2: Direct Configuration**
|
|
1860
|
+
- User scope: ~/.claude.json
|
|
1861
|
+
- Project scope: .mcp.json (in project root)
|
|
1862
|
+
|
|
1863
|
+
**Steps for manual setup:**
|
|
1864
|
+
1. Open ~/.claude.json (create if it doesn't exist)
|
|
1865
|
+
2. Add the server configuration
|
|
1866
|
+
3. Save the file
|
|
1867
|
+
|
|
1868
|
+
**Example Configuration:**
|
|
1869
|
+
\`\`\`json
|
|
1870
|
+
{
|
|
1871
|
+
"mcpServers": {
|
|
1872
|
+
"${configServerName}": {
|
|
1873
|
+
"type": "stdio",
|
|
1874
|
+
"command": "uvx",
|
|
1875
|
+
"args": ["mcp-server-name"]
|
|
1876
|
+
}
|
|
1877
|
+
}
|
|
1878
|
+
}
|
|
1879
|
+
\`\`\`
|
|
1880
|
+
|
|
1881
|
+
**Verify Installation:**
|
|
1882
|
+
\`\`\`bash
|
|
1883
|
+
claude mcp list
|
|
1884
|
+
claude mcp get ${configServerName}
|
|
1885
|
+
\`\`\`
|
|
1886
|
+
`;
|
|
1887
|
+
default:
|
|
1888
|
+
return `
|
|
1889
|
+
### MCP Setup Instructions
|
|
1890
|
+
|
|
1891
|
+
Please configure the MCP server according to your AI assistant's documentation.
|
|
1892
|
+
The server name to use: ${configServerName}
|
|
1893
|
+
|
|
1894
|
+
**Common Configuration Format:**
|
|
1895
|
+
\`\`\`json
|
|
1896
|
+
{
|
|
1897
|
+
"mcpServers": {
|
|
1898
|
+
"${configServerName}": {
|
|
1899
|
+
"command": "uvx",
|
|
1900
|
+
"args": ["mcp-server-name"]
|
|
1901
|
+
}
|
|
1902
|
+
}
|
|
1903
|
+
}
|
|
1904
|
+
\`\`\`
|
|
1905
|
+
`;
|
|
1906
|
+
}
|
|
1907
|
+
}
|
|
1908
|
+
function formatSingleMcpServer(mcpServer, provider) {
|
|
1909
|
+
const usageDesc = mcpServer.usageDescription || "";
|
|
1910
|
+
const providerSetupInstructions = getProviderMcpSetupInstructions(provider, mcpServer.mcpId || mcpServer.name);
|
|
1911
|
+
const formattedInstructions = `
|
|
1912
|
+
=== MCP SERVER INTEGRATION ===
|
|
1913
|
+
Server: ${mcpServer.name} by ${mcpServer.author}
|
|
1914
|
+
Description: ${mcpServer.description}
|
|
1915
|
+
GitHub: ${mcpServer.githubUrl}
|
|
1916
|
+
|
|
1917
|
+
${usageDesc ? `=== USER'S INTENDED USAGE ===
|
|
1918
|
+
${usageDesc}
|
|
1919
|
+
` : ""}
|
|
1920
|
+
=== CRITICAL INSTRUCTIONS ===
|
|
1921
|
+
**YOU MUST** use this MCP server exactly as described by the user above.
|
|
1922
|
+
${usageDesc ? `The user has specified their intent - follow it precisely.` : ""}
|
|
1923
|
+
|
|
1924
|
+
**IF THE MCP SERVER IS NOT AVAILABLE OR NOT CONFIGURED:**
|
|
1925
|
+
You **MUST** install and configure it NOW. DO NOT skip to the next node.
|
|
1926
|
+
DO NOT proceed with the task until the MCP server is working.
|
|
1927
|
+
|
|
1928
|
+
${providerSetupInstructions}
|
|
1929
|
+
|
|
1930
|
+
${mcpServer.requiresApiKey ? `**NOTE:** This MCP server requires an API key. Ensure the API key is configured before use.
|
|
1931
|
+
` : ""}
|
|
1932
|
+
=== README / Installation Guide ===
|
|
1933
|
+
${mcpServer.readmeContent ? mcpServer.readmeContent.substring(0, 3e3) : "Refer to the GitHub repository for setup instructions: " + mcpServer.githubUrl}
|
|
1934
|
+
${"=".repeat(50)}
|
|
1935
|
+
`.trim();
|
|
1936
|
+
return {
|
|
1937
|
+
...mcpServer,
|
|
1938
|
+
formattedInstructions
|
|
1939
|
+
};
|
|
1940
|
+
}
|
|
1941
|
+
function formatMcpServersWithInstructions(mcpServers, provider) {
|
|
1942
|
+
if (!mcpServers || mcpServers.length === 0) return void 0;
|
|
1943
|
+
const agentProvider = provider || "unknown";
|
|
1944
|
+
return mcpServers.map((mcpServer) => formatSingleMcpServer(mcpServer, agentProvider));
|
|
1945
|
+
}
|
|
1946
|
+
function handleStreamPlanChunk(xmlChunk, workspacePath, agentType) {
|
|
1947
|
+
const effectivePath = workspacePath || process.cwd();
|
|
1948
|
+
const projectId = workspacePath ? generateProjectId(effectivePath) : currentProjectId;
|
|
1949
|
+
currentProjectId = projectId;
|
|
1950
|
+
if (!currentParsers.has(projectId)) {
|
|
1951
|
+
const parser = new StreamingXMLParser((event) => {
|
|
1952
|
+
switch (event.type) {
|
|
1953
|
+
case "plan":
|
|
1954
|
+
const plan = {
|
|
1955
|
+
id: event.plan.id || `plan_${Date.now()}`,
|
|
1956
|
+
title: event.plan.title || "Untitled Plan",
|
|
1957
|
+
agent: event.plan.agent || agentType || "unknown",
|
|
1958
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1959
|
+
status: "streaming"
|
|
1960
|
+
};
|
|
1961
|
+
multiProjectPlanStore.initializeProject({
|
|
1962
|
+
projectId,
|
|
1963
|
+
workspacePath: effectivePath,
|
|
1964
|
+
projectName: path3.basename(effectivePath),
|
|
1965
|
+
agentType: plan.agent
|
|
1966
|
+
});
|
|
1967
|
+
multiProjectPlanStore.startPlan(projectId, plan);
|
|
1968
|
+
wsManager.broadcastToProject(projectId, { type: "plan_started", plan, projectId });
|
|
1969
|
+
break;
|
|
1970
|
+
case "node":
|
|
1971
|
+
multiProjectPlanStore.addNode(projectId, event.node);
|
|
1972
|
+
wsManager.broadcastToProject(projectId, { type: "node_added", node: event.node, projectId });
|
|
1973
|
+
break;
|
|
1974
|
+
case "edge":
|
|
1975
|
+
multiProjectPlanStore.addEdge(projectId, event.edge);
|
|
1976
|
+
wsManager.broadcastToProject(projectId, { type: "edge_added", edge: event.edge, projectId });
|
|
1977
|
+
break;
|
|
1978
|
+
case "complete":
|
|
1979
|
+
multiProjectPlanStore.updatePlanStatus(projectId, "ready");
|
|
1980
|
+
wsManager.broadcastToProject(projectId, { type: "plan_ready", projectId });
|
|
1981
|
+
currentParsers.delete(projectId);
|
|
1982
|
+
break;
|
|
1983
|
+
case "error":
|
|
1984
|
+
console.error("[Overture] XML parse error:", event.error);
|
|
1985
|
+
wsManager.broadcastToProject(projectId, { type: "error", message: event.error.message });
|
|
1986
|
+
break;
|
|
1987
|
+
}
|
|
1988
|
+
});
|
|
1989
|
+
currentParsers.set(projectId, parser);
|
|
1990
|
+
}
|
|
1991
|
+
try {
|
|
1992
|
+
const parser = currentParsers.get(projectId);
|
|
1993
|
+
parser.write(xmlChunk);
|
|
1994
|
+
return {
|
|
1995
|
+
success: true,
|
|
1996
|
+
message: "Chunk processed",
|
|
1997
|
+
projectId,
|
|
1998
|
+
expected_project_id: projectId
|
|
1999
|
+
};
|
|
2000
|
+
} catch (error) {
|
|
2001
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
2002
|
+
return { success: false, message, projectId };
|
|
2003
|
+
}
|
|
2004
|
+
}
|
|
2005
|
+
function handleSubmitPlan(planXml, workspacePath, agentType) {
|
|
2006
|
+
const effectivePath = workspacePath || process.cwd();
|
|
2007
|
+
const projectId = workspacePath ? generateProjectId(effectivePath) : "default";
|
|
2008
|
+
currentProjectId = projectId;
|
|
2009
|
+
currentParsers.delete(projectId);
|
|
2010
|
+
console.error("[Overture] submit_plan called, XML length:", planXml.length);
|
|
2011
|
+
console.error("[Overture] Project:", projectId, "Path:", effectivePath);
|
|
2012
|
+
console.error("[Overture] Connected clients:", wsManager.getClientCount());
|
|
2013
|
+
const parser = new StreamingXMLParser((event) => {
|
|
2014
|
+
switch (event.type) {
|
|
2015
|
+
case "plan":
|
|
2016
|
+
const plan = {
|
|
2017
|
+
id: event.plan.id || `plan_${Date.now()}`,
|
|
2018
|
+
title: event.plan.title || "Untitled Plan",
|
|
2019
|
+
agent: event.plan.agent || agentType || "unknown",
|
|
2020
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2021
|
+
status: "streaming"
|
|
2022
|
+
};
|
|
2023
|
+
const projectContext = {
|
|
2024
|
+
projectId,
|
|
2025
|
+
workspacePath: effectivePath,
|
|
2026
|
+
projectName: path3.basename(effectivePath),
|
|
2027
|
+
agentType: plan.agent
|
|
2028
|
+
};
|
|
2029
|
+
multiProjectPlanStore.initializeProject(projectContext);
|
|
2030
|
+
multiProjectPlanStore.startPlan(projectId, plan);
|
|
2031
|
+
console.error("[Overture] Broadcasting plan_started:", plan.title, "to project:", projectId);
|
|
2032
|
+
wsManager.broadcastToProject(projectId, { type: "plan_started", plan, projectId });
|
|
2033
|
+
wsManager.broadcastAll({
|
|
2034
|
+
type: "project_registered",
|
|
2035
|
+
projectId,
|
|
2036
|
+
projectName: projectContext.projectName,
|
|
2037
|
+
workspacePath: projectContext.workspacePath
|
|
2038
|
+
});
|
|
2039
|
+
break;
|
|
2040
|
+
case "node":
|
|
2041
|
+
console.error("[Overture] Broadcasting node_added:", event.node.id);
|
|
2042
|
+
multiProjectPlanStore.addNode(projectId, event.node);
|
|
2043
|
+
wsManager.broadcastToProject(projectId, { type: "node_added", node: event.node, projectId });
|
|
2044
|
+
break;
|
|
2045
|
+
case "edge":
|
|
2046
|
+
console.error("[Overture] Broadcasting edge_added:", event.edge.id);
|
|
2047
|
+
multiProjectPlanStore.addEdge(projectId, event.edge);
|
|
2048
|
+
wsManager.broadcastToProject(projectId, { type: "edge_added", edge: event.edge, projectId });
|
|
2049
|
+
break;
|
|
2050
|
+
case "complete":
|
|
2051
|
+
console.error("[Overture] Broadcasting plan_ready");
|
|
2052
|
+
multiProjectPlanStore.updatePlanStatus(projectId, "ready");
|
|
2053
|
+
wsManager.broadcastToProject(projectId, { type: "plan_ready", projectId });
|
|
2054
|
+
break;
|
|
2055
|
+
case "error":
|
|
2056
|
+
console.error("[Overture] XML parse error:", event.error);
|
|
2057
|
+
wsManager.broadcastToProject(projectId, { type: "error", message: event.error.message });
|
|
2058
|
+
break;
|
|
2059
|
+
}
|
|
2060
|
+
});
|
|
2061
|
+
try {
|
|
2062
|
+
parser.write(planXml);
|
|
2063
|
+
parser.close();
|
|
2064
|
+
const nodes = multiProjectPlanStore.getNodes(projectId);
|
|
2065
|
+
const edges = multiProjectPlanStore.getEdges(projectId);
|
|
2066
|
+
console.error("[Overture] Plan parsing complete. Nodes:", nodes.length, "Edges:", edges.length);
|
|
2067
|
+
console.error("[Overture] Project stored with ID:", projectId);
|
|
2068
|
+
console.error("[Overture] All projects after submit:", Array.from(multiProjectPlanStore.getAllProjects().map((p) => p.projectId)));
|
|
2069
|
+
return {
|
|
2070
|
+
success: true,
|
|
2071
|
+
message: `Plan submitted successfully. IMPORTANT: Use project_id "${projectId}" in ALL subsequent calls (get_approval, update_node_status, etc.)`,
|
|
2072
|
+
projectId,
|
|
2073
|
+
// Explicit field to make it clear what ID to use
|
|
2074
|
+
expected_project_id: projectId
|
|
2075
|
+
};
|
|
2076
|
+
} catch (error) {
|
|
2077
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
2078
|
+
console.error("[Overture] Plan parsing failed:", message);
|
|
2079
|
+
return { success: false, message, projectId };
|
|
2080
|
+
}
|
|
2081
|
+
}
|
|
2082
|
+
async function handleGetApproval(projectId) {
|
|
2083
|
+
const effectiveProjectId = projectId || currentProjectId;
|
|
2084
|
+
console.error(`[Overture] get_approval called for project: ${effectiveProjectId}`);
|
|
2085
|
+
console.error(`[Overture] Provided projectId: ${projectId}, currentProjectId: ${currentProjectId}`);
|
|
2086
|
+
console.error(`[Overture] All projects in store:`, multiProjectPlanStore.getAllProjects().map((p) => p.projectId));
|
|
2087
|
+
console.error(`[Overture] Current plan status:`, multiProjectPlanStore.getPlan(effectiveProjectId)?.status);
|
|
2088
|
+
const result = await multiProjectPlanStore.waitForApproval(effectiveProjectId, 6e4);
|
|
2089
|
+
console.error(`[Overture] waitForApproval returned: ${result}`);
|
|
2090
|
+
if (result === "approved") {
|
|
2091
|
+
multiProjectPlanStore.updatePlanStatus(effectiveProjectId, "executing");
|
|
2092
|
+
const plan = multiProjectPlanStore.getPlan(effectiveProjectId);
|
|
2093
|
+
const provider = plan?.agent || "unknown";
|
|
2094
|
+
const nodes = multiProjectPlanStore.getNodes(effectiveProjectId);
|
|
2095
|
+
const edges = multiProjectPlanStore.getEdges(effectiveProjectId);
|
|
2096
|
+
const nodeConfigs = multiProjectPlanStore.getNodeConfigs(effectiveProjectId);
|
|
2097
|
+
const nodesWithIncomingEdges = new Set(edges.map((e) => e.to));
|
|
2098
|
+
const firstNode = nodes.find((n) => !nodesWithIncomingEdges.has(n.id));
|
|
2099
|
+
let firstNodeInfo;
|
|
2100
|
+
if (firstNode) {
|
|
2101
|
+
const config = nodeConfigs[firstNode.id] || { fieldValues: {}, attachments: [] };
|
|
2102
|
+
firstNodeInfo = {
|
|
2103
|
+
id: firstNode.id,
|
|
2104
|
+
title: firstNode.title,
|
|
2105
|
+
type: firstNode.type,
|
|
2106
|
+
description: firstNode.description,
|
|
2107
|
+
fieldValues: config.fieldValues || {},
|
|
2108
|
+
attachments: config.attachments || [],
|
|
2109
|
+
metaInstructions: config.metaInstructions,
|
|
2110
|
+
mcpServers: formatMcpServersWithInstructions(config.mcpServers, provider)
|
|
2111
|
+
};
|
|
2112
|
+
}
|
|
2113
|
+
return {
|
|
2114
|
+
status: "approved",
|
|
2115
|
+
firstNode: firstNodeInfo,
|
|
2116
|
+
message: "Plan approved by user. Execute firstNode, then call update_node_status to get the next node.",
|
|
2117
|
+
projectId: effectiveProjectId
|
|
2118
|
+
};
|
|
2119
|
+
}
|
|
2120
|
+
if (result === "cancelled") {
|
|
2121
|
+
return {
|
|
2122
|
+
status: "cancelled",
|
|
2123
|
+
message: "Plan cancelled by user",
|
|
2124
|
+
projectId: effectiveProjectId
|
|
2125
|
+
};
|
|
2126
|
+
}
|
|
2127
|
+
return {
|
|
2128
|
+
status: "pending",
|
|
2129
|
+
message: "Waiting for user approval. Call get_approval again to continue waiting.",
|
|
2130
|
+
projectId: effectiveProjectId
|
|
2131
|
+
};
|
|
2132
|
+
}
|
|
2133
|
+
async function handleCheckPause(wait = false, projectId) {
|
|
2134
|
+
const effectiveProjectId = projectId || currentProjectId;
|
|
2135
|
+
const isPaused = multiProjectPlanStore.getIsPaused(effectiveProjectId);
|
|
2136
|
+
if (!isPaused) {
|
|
2137
|
+
return {
|
|
2138
|
+
isPaused: false,
|
|
2139
|
+
wasResumed: false,
|
|
2140
|
+
message: "Execution is not paused",
|
|
2141
|
+
projectId: effectiveProjectId
|
|
2142
|
+
};
|
|
2143
|
+
}
|
|
2144
|
+
if (!wait) {
|
|
2145
|
+
return {
|
|
2146
|
+
isPaused: true,
|
|
2147
|
+
wasResumed: false,
|
|
2148
|
+
message: "Execution is paused. Call with wait=true to block until resumed.",
|
|
2149
|
+
projectId: effectiveProjectId
|
|
2150
|
+
};
|
|
2151
|
+
}
|
|
2152
|
+
await multiProjectPlanStore.waitIfPaused(effectiveProjectId);
|
|
2153
|
+
return {
|
|
2154
|
+
isPaused: false,
|
|
2155
|
+
wasResumed: true,
|
|
2156
|
+
message: "Execution was paused and has now been resumed",
|
|
2157
|
+
projectId: effectiveProjectId
|
|
2158
|
+
};
|
|
2159
|
+
}
|
|
2160
|
+
function handleUpdateNodeStatus(nodeId, status, output, projectId) {
|
|
2161
|
+
const effectiveProjectId = projectId || currentProjectId;
|
|
2162
|
+
const plan = multiProjectPlanStore.getPlan(effectiveProjectId);
|
|
2163
|
+
const provider = plan?.agent || "unknown";
|
|
2164
|
+
const nodes = multiProjectPlanStore.getNodes(effectiveProjectId);
|
|
2165
|
+
const edges = multiProjectPlanStore.getEdges(effectiveProjectId);
|
|
2166
|
+
const nodeConfigs = multiProjectPlanStore.getNodeConfigs(effectiveProjectId);
|
|
2167
|
+
const node = nodes.find((n) => n.id === nodeId);
|
|
2168
|
+
if (!node) {
|
|
2169
|
+
return { success: false, message: `Node ${nodeId} not found`, projectId: effectiveProjectId };
|
|
2170
|
+
}
|
|
2171
|
+
multiProjectPlanStore.updateNodeStatus(effectiveProjectId, nodeId, status, output);
|
|
2172
|
+
wsManager.broadcastToProject(effectiveProjectId, { type: "node_status_updated", nodeId, status, output, projectId: effectiveProjectId });
|
|
2173
|
+
const isPaused = multiProjectPlanStore.getIsPaused(effectiveProjectId);
|
|
2174
|
+
if (status === "active") {
|
|
2175
|
+
const config = nodeConfigs[nodeId] || { fieldValues: {}, attachments: [] };
|
|
2176
|
+
const currentNodeInfo = {
|
|
2177
|
+
id: node.id,
|
|
2178
|
+
title: node.title,
|
|
2179
|
+
type: node.type,
|
|
2180
|
+
description: node.description,
|
|
2181
|
+
fieldValues: config.fieldValues || {},
|
|
2182
|
+
attachments: config.attachments || [],
|
|
2183
|
+
metaInstructions: config.metaInstructions,
|
|
2184
|
+
mcpServers: formatMcpServersWithInstructions(config.mcpServers, provider)
|
|
2185
|
+
};
|
|
2186
|
+
return {
|
|
2187
|
+
success: true,
|
|
2188
|
+
message: `Node ${nodeId} status updated to ${status}. Execute this node now.`,
|
|
2189
|
+
currentNode: currentNodeInfo,
|
|
2190
|
+
isPaused,
|
|
2191
|
+
projectId: effectiveProjectId
|
|
2192
|
+
};
|
|
2193
|
+
}
|
|
2194
|
+
if (status === "completed") {
|
|
2195
|
+
const nextNodeInfo = findNextNode(effectiveProjectId, nodeId, nodes, edges, provider);
|
|
2196
|
+
if (nextNodeInfo) {
|
|
2197
|
+
return {
|
|
2198
|
+
success: true,
|
|
2199
|
+
message: `Node ${nodeId} status updated to ${status}`,
|
|
2200
|
+
nextNode: nextNodeInfo,
|
|
2201
|
+
isPaused,
|
|
2202
|
+
projectId: effectiveProjectId
|
|
2203
|
+
};
|
|
2204
|
+
} else {
|
|
2205
|
+
return {
|
|
2206
|
+
success: true,
|
|
2207
|
+
message: `Node ${nodeId} status updated to ${status}. This was the last node.`,
|
|
2208
|
+
isLastNode: true,
|
|
2209
|
+
isPaused,
|
|
2210
|
+
projectId: effectiveProjectId
|
|
2211
|
+
};
|
|
2212
|
+
}
|
|
2213
|
+
}
|
|
2214
|
+
return {
|
|
2215
|
+
success: true,
|
|
2216
|
+
message: `Node ${nodeId} status updated to ${status}`,
|
|
2217
|
+
isPaused,
|
|
2218
|
+
projectId: effectiveProjectId
|
|
2219
|
+
};
|
|
2220
|
+
}
|
|
2221
|
+
function findNextNode(projectId, currentNodeId, nodes, edges, provider) {
|
|
2222
|
+
const selectedBranches = multiProjectPlanStore.getSelectedBranches(projectId);
|
|
2223
|
+
const nodeConfigs = multiProjectPlanStore.getNodeConfigs(projectId);
|
|
2224
|
+
const outgoingEdges = edges.filter((e) => e.from === currentNodeId);
|
|
2225
|
+
if (outgoingEdges.length === 0) {
|
|
2226
|
+
return null;
|
|
2227
|
+
}
|
|
2228
|
+
for (const edge of outgoingEdges) {
|
|
2229
|
+
const nextNode = nodes.find((n) => n.id === edge.to);
|
|
2230
|
+
if (!nextNode) continue;
|
|
2231
|
+
if (nextNode.branchParent && nextNode.branchId) {
|
|
2232
|
+
const selectedBranch = selectedBranches[nextNode.branchParent];
|
|
2233
|
+
if (selectedBranch && selectedBranch !== nextNode.branchId) {
|
|
2234
|
+
continue;
|
|
2235
|
+
}
|
|
2236
|
+
}
|
|
2237
|
+
const config = nodeConfigs[nextNode.id] || { fieldValues: {}, attachments: [] };
|
|
2238
|
+
return {
|
|
2239
|
+
id: nextNode.id,
|
|
2240
|
+
title: nextNode.title,
|
|
2241
|
+
type: nextNode.type,
|
|
2242
|
+
description: nextNode.description,
|
|
2243
|
+
fieldValues: config.fieldValues || {},
|
|
2244
|
+
attachments: config.attachments || [],
|
|
2245
|
+
metaInstructions: config.metaInstructions,
|
|
2246
|
+
mcpServers: formatMcpServersWithInstructions(config.mcpServers, provider)
|
|
2247
|
+
};
|
|
2248
|
+
}
|
|
2249
|
+
for (const edge of outgoingEdges) {
|
|
2250
|
+
const skippedNode = nodes.find((n) => n.id === edge.to);
|
|
2251
|
+
if (skippedNode) {
|
|
2252
|
+
const nextAfterSkipped = findNextNode(projectId, skippedNode.id, nodes, edges, provider);
|
|
2253
|
+
if (nextAfterSkipped) {
|
|
2254
|
+
return nextAfterSkipped;
|
|
2255
|
+
}
|
|
2256
|
+
}
|
|
2257
|
+
}
|
|
2258
|
+
return null;
|
|
2259
|
+
}
|
|
2260
|
+
function handlePlanCompleted(projectId) {
|
|
2261
|
+
const effectiveProjectId = projectId || currentProjectId;
|
|
2262
|
+
multiProjectPlanStore.updatePlanStatus(effectiveProjectId, "completed");
|
|
2263
|
+
wsManager.broadcastToProject(effectiveProjectId, { type: "plan_completed", projectId: effectiveProjectId });
|
|
2264
|
+
return { success: true, message: "Plan completed", projectId: effectiveProjectId };
|
|
2265
|
+
}
|
|
2266
|
+
function handlePlanFailed(error, projectId) {
|
|
2267
|
+
const effectiveProjectId = projectId || currentProjectId;
|
|
2268
|
+
multiProjectPlanStore.updatePlanStatus(effectiveProjectId, "failed");
|
|
2269
|
+
wsManager.broadcastToProject(effectiveProjectId, { type: "plan_failed", error, projectId: effectiveProjectId });
|
|
2270
|
+
return { success: true, message: "Plan failed", projectId: effectiveProjectId };
|
|
2271
|
+
}
|
|
2272
|
+
async function handleCheckRerun(timeoutMs = 5e3, projectId) {
|
|
2273
|
+
const effectiveProjectId = projectId || currentProjectId;
|
|
2274
|
+
const rerunRequest = await multiProjectPlanStore.waitForRerun(effectiveProjectId, timeoutMs);
|
|
2275
|
+
if (!rerunRequest) {
|
|
2276
|
+
return {
|
|
2277
|
+
hasRerun: false,
|
|
2278
|
+
message: "No rerun request pending",
|
|
2279
|
+
projectId: effectiveProjectId
|
|
2280
|
+
};
|
|
2281
|
+
}
|
|
2282
|
+
const resetNodeIds = multiProjectPlanStore.resetNodesForRerun(effectiveProjectId, rerunRequest.nodeId, rerunRequest.mode);
|
|
2283
|
+
for (const nodeId of resetNodeIds) {
|
|
2284
|
+
wsManager.broadcastToProject(effectiveProjectId, { type: "node_status_updated", nodeId, status: "pending", projectId: effectiveProjectId });
|
|
2285
|
+
}
|
|
2286
|
+
multiProjectPlanStore.updatePlanStatus(effectiveProjectId, "executing");
|
|
2287
|
+
const plan = multiProjectPlanStore.getPlan(effectiveProjectId);
|
|
2288
|
+
const provider = plan?.agent || "unknown";
|
|
2289
|
+
wsManager.broadcastToProject(effectiveProjectId, { type: "plan_started", plan, projectId: effectiveProjectId });
|
|
2290
|
+
const nodes = multiProjectPlanStore.getNodes(effectiveProjectId);
|
|
2291
|
+
const nodeConfigs = multiProjectPlanStore.getNodeConfigs(effectiveProjectId);
|
|
2292
|
+
const startNode = nodes.find((n) => n.id === rerunRequest.nodeId);
|
|
2293
|
+
let nodeInfo;
|
|
2294
|
+
if (startNode) {
|
|
2295
|
+
const config = nodeConfigs[startNode.id] || { fieldValues: {}, attachments: [] };
|
|
2296
|
+
nodeInfo = {
|
|
2297
|
+
id: startNode.id,
|
|
2298
|
+
title: startNode.title,
|
|
2299
|
+
type: startNode.type,
|
|
2300
|
+
description: startNode.description,
|
|
2301
|
+
fieldValues: config.fieldValues || {},
|
|
2302
|
+
attachments: config.attachments || [],
|
|
2303
|
+
metaInstructions: config.metaInstructions,
|
|
2304
|
+
mcpServers: formatMcpServersWithInstructions(config.mcpServers, provider)
|
|
2305
|
+
};
|
|
2306
|
+
}
|
|
2307
|
+
return {
|
|
2308
|
+
hasRerun: true,
|
|
2309
|
+
nodeId: rerunRequest.nodeId,
|
|
2310
|
+
mode: rerunRequest.mode,
|
|
2311
|
+
nodeInfo,
|
|
2312
|
+
message: `Rerun requested from node ${rerunRequest.nodeId} (${rerunRequest.mode})`,
|
|
2313
|
+
projectId: effectiveProjectId
|
|
2314
|
+
};
|
|
2315
|
+
}
|
|
2316
|
+
function handleGetResumeInfo(projectId) {
|
|
2317
|
+
const effectiveProjectId = projectId || currentProjectId;
|
|
2318
|
+
const resumeInfo = multiProjectPlanStore.getResumeInfo(effectiveProjectId);
|
|
2319
|
+
if (!resumeInfo) {
|
|
2320
|
+
return {
|
|
2321
|
+
success: false,
|
|
2322
|
+
message: `No active plan found for project: ${effectiveProjectId}`,
|
|
2323
|
+
projectId: effectiveProjectId
|
|
2324
|
+
};
|
|
2325
|
+
}
|
|
2326
|
+
return {
|
|
2327
|
+
success: true,
|
|
2328
|
+
resumeInfo,
|
|
2329
|
+
message: `Resume info retrieved. Plan is at status '${resumeInfo.status}'. ${resumeInfo.currentNodeId ? `Current node: ${resumeInfo.currentNodeTitle} (${resumeInfo.currentNodeStatus})` : "No current node."} Completed: ${resumeInfo.completedNodes.length}, Pending: ${resumeInfo.pendingNodes.length}, Failed: ${resumeInfo.failedNodes.length}`,
|
|
2330
|
+
projectId: effectiveProjectId
|
|
2331
|
+
};
|
|
2332
|
+
}
|
|
2333
|
+
function handleRequestPlanUpdate(operations, projectId) {
|
|
2334
|
+
const effectiveProjectId = projectId || currentProjectId;
|
|
2335
|
+
const currentPlan = multiProjectPlanStore.getPlan(effectiveProjectId);
|
|
2336
|
+
if (!currentPlan) {
|
|
2337
|
+
return {
|
|
2338
|
+
success: false,
|
|
2339
|
+
message: `No active plan found for project: ${effectiveProjectId}. Submit a new plan instead.`,
|
|
2340
|
+
results: [],
|
|
2341
|
+
projectId: effectiveProjectId
|
|
2342
|
+
};
|
|
2343
|
+
}
|
|
2344
|
+
multiProjectPlanStore.storePreviousPlanState(effectiveProjectId);
|
|
2345
|
+
console.error(`[Overture] Processing ${operations.length} plan update operations for project: ${effectiveProjectId}`);
|
|
2346
|
+
const results = [];
|
|
2347
|
+
for (const operation of operations) {
|
|
2348
|
+
try {
|
|
2349
|
+
switch (operation.op) {
|
|
2350
|
+
case "insert_after":
|
|
2351
|
+
case "insert_before": {
|
|
2352
|
+
const position = operation.op === "insert_after" ? "after" : "before";
|
|
2353
|
+
const result = applyInsertOperation(
|
|
2354
|
+
effectiveProjectId,
|
|
2355
|
+
operation.reference_node_id,
|
|
2356
|
+
position,
|
|
2357
|
+
operation.node
|
|
2358
|
+
);
|
|
2359
|
+
results.push({ op: operation.op, ...result });
|
|
2360
|
+
break;
|
|
2361
|
+
}
|
|
2362
|
+
case "delete": {
|
|
2363
|
+
const result = applyDeleteOperation(effectiveProjectId, operation.node_id);
|
|
2364
|
+
results.push({ op: "delete", ...result });
|
|
2365
|
+
break;
|
|
2366
|
+
}
|
|
2367
|
+
case "replace": {
|
|
2368
|
+
const result = applyReplaceOperation(effectiveProjectId, operation.node_id, operation.node);
|
|
2369
|
+
results.push({ op: "replace", ...result });
|
|
2370
|
+
break;
|
|
2371
|
+
}
|
|
2372
|
+
default:
|
|
2373
|
+
results.push({ op: "unknown", success: false, message: "Unknown operation type" });
|
|
2374
|
+
}
|
|
2375
|
+
} catch (error) {
|
|
2376
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
2377
|
+
results.push({ op: operation.op || "unknown", success: false, message });
|
|
2378
|
+
}
|
|
2379
|
+
}
|
|
2380
|
+
const successCount = results.filter((r) => r.success).length;
|
|
2381
|
+
const failCount = results.length - successCount;
|
|
2382
|
+
wsManager.broadcastToProject(effectiveProjectId, {
|
|
2383
|
+
type: "plan_updated_incrementally",
|
|
2384
|
+
operationCount: operations.length,
|
|
2385
|
+
successCount,
|
|
2386
|
+
failCount,
|
|
2387
|
+
projectId: effectiveProjectId
|
|
2388
|
+
});
|
|
2389
|
+
console.error(`[Overture] Plan update complete: ${successCount} succeeded, ${failCount} failed`);
|
|
2390
|
+
return {
|
|
2391
|
+
success: failCount === 0,
|
|
2392
|
+
message: `Applied ${successCount}/${operations.length} operations. ${failCount > 0 ? "Some operations failed." : "All operations succeeded."} Call get_approval to confirm changes with user.`,
|
|
2393
|
+
results,
|
|
2394
|
+
projectId: effectiveProjectId
|
|
2395
|
+
};
|
|
2396
|
+
}
|
|
2397
|
+
function applyInsertOperation(projectId, referenceNodeId, position, nodeData) {
|
|
2398
|
+
const nodes = multiProjectPlanStore.getNodes(projectId);
|
|
2399
|
+
const edges = multiProjectPlanStore.getEdges(projectId);
|
|
2400
|
+
const refNode = nodes.find((n) => n.id === referenceNodeId);
|
|
2401
|
+
if (!refNode) {
|
|
2402
|
+
return { success: false, message: `Reference node ${referenceNodeId} not found` };
|
|
2403
|
+
}
|
|
2404
|
+
const newNode = {
|
|
2405
|
+
id: nodeData.id,
|
|
2406
|
+
type: nodeData.type,
|
|
2407
|
+
title: nodeData.title,
|
|
2408
|
+
description: nodeData.description,
|
|
2409
|
+
complexity: nodeData.complexity,
|
|
2410
|
+
expectedOutput: nodeData.expectedOutput,
|
|
2411
|
+
risks: nodeData.risks,
|
|
2412
|
+
status: "pending",
|
|
2413
|
+
dynamicFields: [],
|
|
2414
|
+
attachments: []
|
|
2415
|
+
};
|
|
2416
|
+
if (position === "after") {
|
|
2417
|
+
const edgeToNewNode = { id: `e_${referenceNodeId}_${nodeData.id}`, from: referenceNodeId, to: nodeData.id };
|
|
2418
|
+
const result = multiProjectPlanStore.insertNodes(
|
|
2419
|
+
projectId,
|
|
2420
|
+
referenceNodeId,
|
|
2421
|
+
[newNode],
|
|
2422
|
+
[edgeToNewNode]
|
|
2423
|
+
);
|
|
2424
|
+
const allNewEdges = [edgeToNewNode, ...result.reconnectionEdges];
|
|
2425
|
+
wsManager.broadcastToProject(projectId, {
|
|
2426
|
+
type: "nodes_inserted",
|
|
2427
|
+
nodes: [newNode],
|
|
2428
|
+
edges: allNewEdges,
|
|
2429
|
+
removedEdgeIds: result.removedEdgeIds,
|
|
2430
|
+
projectId
|
|
2431
|
+
});
|
|
2432
|
+
} else {
|
|
2433
|
+
const incomingEdges = edges.filter((e) => e.to === referenceNodeId);
|
|
2434
|
+
if (incomingEdges.length === 0) {
|
|
2435
|
+
multiProjectPlanStore.addNode(projectId, newNode);
|
|
2436
|
+
const newEdge = { id: `e_${nodeData.id}_${referenceNodeId}`, from: nodeData.id, to: referenceNodeId };
|
|
2437
|
+
multiProjectPlanStore.addEdge(projectId, newEdge);
|
|
2438
|
+
wsManager.broadcastToProject(projectId, { type: "node_added", node: newNode, projectId });
|
|
2439
|
+
wsManager.broadcastToProject(projectId, { type: "edge_added", edge: newEdge, projectId });
|
|
2440
|
+
} else {
|
|
2441
|
+
const firstIncoming = incomingEdges[0];
|
|
2442
|
+
multiProjectPlanStore.addNode(projectId, newNode);
|
|
2443
|
+
wsManager.broadcastToProject(projectId, { type: "node_added", node: newNode, projectId });
|
|
2444
|
+
for (const edge of incomingEdges) {
|
|
2445
|
+
const state = multiProjectPlanStore.getState(projectId);
|
|
2446
|
+
if (state) {
|
|
2447
|
+
const edgeIndex = state.edges.findIndex((e) => e.id === edge.id);
|
|
2448
|
+
if (edgeIndex >= 0) {
|
|
2449
|
+
state.edges.splice(edgeIndex, 1);
|
|
2450
|
+
}
|
|
2451
|
+
}
|
|
2452
|
+
}
|
|
2453
|
+
const edgeToNew = { id: `e_${firstIncoming.from}_${nodeData.id}`, from: firstIncoming.from, to: nodeData.id };
|
|
2454
|
+
const edgeToRef = { id: `e_${nodeData.id}_${referenceNodeId}`, from: nodeData.id, to: referenceNodeId };
|
|
2455
|
+
multiProjectPlanStore.addEdge(projectId, edgeToNew);
|
|
2456
|
+
multiProjectPlanStore.addEdge(projectId, edgeToRef);
|
|
2457
|
+
wsManager.broadcastToProject(projectId, { type: "edge_added", edge: edgeToNew, projectId });
|
|
2458
|
+
wsManager.broadcastToProject(projectId, { type: "edge_added", edge: edgeToRef, projectId });
|
|
2459
|
+
}
|
|
2460
|
+
}
|
|
2461
|
+
console.error(`[Overture] Node ${nodeData.id} inserted ${position} ${referenceNodeId}`);
|
|
2462
|
+
return { success: true, message: `Node "${nodeData.title}" inserted ${position} node ${referenceNodeId}` };
|
|
2463
|
+
}
|
|
2464
|
+
function applyDeleteOperation(projectId, nodeId) {
|
|
2465
|
+
const nodes = multiProjectPlanStore.getNodes(projectId);
|
|
2466
|
+
const node = nodes.find((n) => n.id === nodeId);
|
|
2467
|
+
if (!node) {
|
|
2468
|
+
return { success: false, message: `Node ${nodeId} not found` };
|
|
2469
|
+
}
|
|
2470
|
+
const result = multiProjectPlanStore.removeNode(projectId, nodeId);
|
|
2471
|
+
wsManager.broadcastToProject(projectId, {
|
|
2472
|
+
type: "node_removed",
|
|
2473
|
+
nodeId,
|
|
2474
|
+
newEdges: result.newEdges,
|
|
2475
|
+
removedEdgeIds: result.removedEdgeIds,
|
|
2476
|
+
projectId
|
|
2477
|
+
});
|
|
2478
|
+
console.error(`[Overture] Node ${nodeId} deleted from plan`);
|
|
2479
|
+
return { success: true, message: `Node "${node.title}" deleted from plan` };
|
|
2480
|
+
}
|
|
2481
|
+
function applyReplaceOperation(projectId, nodeId, newNodeData) {
|
|
2482
|
+
const state = multiProjectPlanStore.getState(projectId);
|
|
2483
|
+
if (!state) {
|
|
2484
|
+
return { success: false, message: `No plan found for project ${projectId}` };
|
|
2485
|
+
}
|
|
2486
|
+
const nodeIndex = state.nodes.findIndex((n) => n.id === nodeId);
|
|
2487
|
+
if (nodeIndex < 0) {
|
|
2488
|
+
return { success: false, message: `Node ${nodeId} not found` };
|
|
2489
|
+
}
|
|
2490
|
+
const oldNode = state.nodes[nodeIndex];
|
|
2491
|
+
const updatedNode = {
|
|
2492
|
+
...oldNode,
|
|
2493
|
+
id: newNodeData.id || oldNode.id,
|
|
2494
|
+
type: newNodeData.type || oldNode.type,
|
|
2495
|
+
title: newNodeData.title,
|
|
2496
|
+
description: newNodeData.description,
|
|
2497
|
+
complexity: newNodeData.complexity || oldNode.complexity,
|
|
2498
|
+
expectedOutput: newNodeData.expectedOutput || oldNode.expectedOutput,
|
|
2499
|
+
risks: newNodeData.risks || oldNode.risks
|
|
2500
|
+
};
|
|
2501
|
+
state.nodes[nodeIndex] = updatedNode;
|
|
2502
|
+
if (newNodeData.id && newNodeData.id !== oldNode.id) {
|
|
2503
|
+
for (const edge of state.edges) {
|
|
2504
|
+
if (edge.from === oldNode.id) {
|
|
2505
|
+
edge.from = newNodeData.id;
|
|
2506
|
+
}
|
|
2507
|
+
if (edge.to === oldNode.id) {
|
|
2508
|
+
edge.to = newNodeData.id;
|
|
2509
|
+
}
|
|
2510
|
+
}
|
|
2511
|
+
}
|
|
2512
|
+
wsManager.broadcastToProject(projectId, {
|
|
2513
|
+
type: "node_replaced",
|
|
2514
|
+
oldNodeId: nodeId,
|
|
2515
|
+
node: updatedNode,
|
|
2516
|
+
projectId
|
|
2517
|
+
});
|
|
2518
|
+
console.error(`[Overture] Node ${nodeId} replaced with new content`);
|
|
2519
|
+
return { success: true, message: `Node "${oldNode.title}" replaced with "${updatedNode.title}"` };
|
|
2520
|
+
}
|
|
2521
|
+
function handleCreateNewPlan(projectId) {
|
|
2522
|
+
const effectiveProjectId = projectId || currentProjectId;
|
|
2523
|
+
wsManager.broadcastToProject(effectiveProjectId, {
|
|
2524
|
+
type: "new_plan_created",
|
|
2525
|
+
planId: "",
|
|
2526
|
+
projectId: effectiveProjectId
|
|
2527
|
+
});
|
|
2528
|
+
console.error(`[Overture] New plan requested for project: ${effectiveProjectId}`);
|
|
2529
|
+
console.error(`[Overture] Existing plans preserved. New plan will be added alongside them.`);
|
|
2530
|
+
return {
|
|
2531
|
+
success: true,
|
|
2532
|
+
message: `Ready to receive new plan. Submit the new plan using submit_plan or stream_plan_chunk, then call get_approval to wait for user approval. Note: Existing plans will be preserved on the canvas.`,
|
|
2533
|
+
projectId: effectiveProjectId
|
|
2534
|
+
};
|
|
2535
|
+
}
|
|
2536
|
+
async function handleGetUsageInstructions(agentType) {
|
|
2537
|
+
const availableAgents = ["claude-code", "cline", "cursor", "sixth"];
|
|
2538
|
+
const normalizedType = agentType.toLowerCase().trim();
|
|
2539
|
+
const agentMap = {
|
|
2540
|
+
"claude-code": "claude-code",
|
|
2541
|
+
"claude": "claude-code",
|
|
2542
|
+
"claudecode": "claude-code",
|
|
2543
|
+
"cline": "cline",
|
|
2544
|
+
"cursor": "cursor",
|
|
2545
|
+
"sixth": "sixth",
|
|
2546
|
+
"6th": "sixth"
|
|
2547
|
+
};
|
|
2548
|
+
const mappedType = agentMap[normalizedType];
|
|
2549
|
+
if (!mappedType) {
|
|
2550
|
+
return {
|
|
2551
|
+
success: false,
|
|
2552
|
+
agentType: normalizedType,
|
|
2553
|
+
message: `Unknown agent type "${agentType}". Available agents: ${availableAgents.join(", ")}`,
|
|
2554
|
+
availableAgents
|
|
2555
|
+
};
|
|
2556
|
+
}
|
|
2557
|
+
const __filename2 = fileURLToPath(import.meta.url);
|
|
2558
|
+
const __dirname2 = path3.dirname(__filename2);
|
|
2559
|
+
const possiblePaths = [
|
|
2560
|
+
path3.resolve(__dirname2, "../../prompts"),
|
|
2561
|
+
// npm installed (dist/tools -> prompts)
|
|
2562
|
+
path3.resolve(__dirname2, "../prompts"),
|
|
2563
|
+
// Alternative npm location
|
|
2564
|
+
path3.resolve(__dirname2, "../../../../prompts"),
|
|
2565
|
+
// Development (monorepo root)
|
|
2566
|
+
path3.resolve(__dirname2, "../../../prompts"),
|
|
2567
|
+
// Alternative dev location
|
|
2568
|
+
path3.resolve(process.cwd(), "prompts")
|
|
2569
|
+
// Relative to cwd
|
|
2570
|
+
];
|
|
2571
|
+
let promptFile = null;
|
|
2572
|
+
let instructions = null;
|
|
2573
|
+
for (const promptsDir of possiblePaths) {
|
|
2574
|
+
const candidatePath = path3.join(promptsDir, `${mappedType}.md`);
|
|
2575
|
+
try {
|
|
2576
|
+
instructions = await fs2.readFile(candidatePath, "utf-8");
|
|
2577
|
+
promptFile = candidatePath;
|
|
2578
|
+
console.error(`[Overture] Found instructions at: ${candidatePath}`);
|
|
2579
|
+
break;
|
|
2580
|
+
} catch {
|
|
2581
|
+
continue;
|
|
2582
|
+
}
|
|
2583
|
+
}
|
|
2584
|
+
if (instructions && promptFile) {
|
|
2585
|
+
console.error(`[Overture] Loaded instructions for ${mappedType} (${instructions.length} chars)`);
|
|
2586
|
+
return {
|
|
2587
|
+
success: true,
|
|
2588
|
+
agentType: mappedType,
|
|
2589
|
+
instructions,
|
|
2590
|
+
message: `Instructions loaded for ${mappedType}. Follow these instructions to use Overture MCP effectively.`,
|
|
2591
|
+
availableAgents
|
|
2592
|
+
};
|
|
2593
|
+
}
|
|
2594
|
+
console.error(`[Overture] Failed to find instructions for ${mappedType}. Searched paths:`);
|
|
2595
|
+
possiblePaths.forEach((p) => console.error(` - ${path3.join(p, `${mappedType}.md`)}`));
|
|
2596
|
+
return {
|
|
2597
|
+
success: false,
|
|
2598
|
+
agentType: mappedType,
|
|
2599
|
+
message: `Failed to load instructions for ${mappedType}. Instructions file not found.`,
|
|
2600
|
+
availableAgents
|
|
2601
|
+
};
|
|
2602
|
+
}
|
|
2603
|
+
|
|
2604
|
+
// src/http/server.ts
|
|
2605
|
+
import express from "express";
|
|
2606
|
+
import path4 from "path";
|
|
2607
|
+
import { createServer } from "http";
|
|
2608
|
+
import fs3 from "fs";
|
|
2609
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
2610
|
+
var __filename = fileURLToPath2(import.meta.url);
|
|
2611
|
+
var __dirname = path4.dirname(__filename);
|
|
2612
|
+
function startHttpServer(port) {
|
|
2613
|
+
const app = express();
|
|
2614
|
+
app.use(express.json());
|
|
2615
|
+
const possiblePaths = [
|
|
2616
|
+
path4.resolve(__dirname, "../ui-dist"),
|
|
2617
|
+
// packages/mcp-server/dist/../ui-dist
|
|
2618
|
+
path4.resolve(__dirname, "../../ui-dist"),
|
|
2619
|
+
// packages/mcp-server/ui-dist
|
|
2620
|
+
path4.resolve(__dirname, "../../../ui/dist"),
|
|
2621
|
+
// packages/ui/dist
|
|
2622
|
+
path4.resolve(process.cwd(), "ui-dist"),
|
|
2623
|
+
// fallback to cwd
|
|
2624
|
+
path4.resolve(process.cwd(), "packages/mcp-server/ui-dist")
|
|
2625
|
+
];
|
|
2626
|
+
let staticPath = possiblePaths[0];
|
|
2627
|
+
for (const p of possiblePaths) {
|
|
2628
|
+
if (fs3.existsSync(path4.join(p, "index.html"))) {
|
|
2629
|
+
staticPath = p;
|
|
2630
|
+
break;
|
|
2631
|
+
}
|
|
2632
|
+
}
|
|
2633
|
+
console.error(`[Overture] Serving UI from: ${staticPath}`);
|
|
2634
|
+
app.post("/api/test-plan", (req, res) => {
|
|
2635
|
+
const { plan_xml } = req.body;
|
|
2636
|
+
if (!plan_xml) {
|
|
2637
|
+
return res.status(400).json({ error: "plan_xml is required" });
|
|
2638
|
+
}
|
|
2639
|
+
const result = handleSubmitPlan(plan_xml);
|
|
2640
|
+
res.json(result);
|
|
2641
|
+
});
|
|
2642
|
+
app.get("/api/mcp-marketplace", async (req, res) => {
|
|
2643
|
+
try {
|
|
2644
|
+
const response = await fetch("https://api.cline.bot/v1/mcp/marketplace");
|
|
2645
|
+
if (!response.ok) {
|
|
2646
|
+
return res.status(response.status).json({ error: "Failed to fetch MCP marketplace" });
|
|
2647
|
+
}
|
|
2648
|
+
const data = await response.json();
|
|
2649
|
+
res.json(data);
|
|
2650
|
+
} catch (error) {
|
|
2651
|
+
console.error("[Overture] Failed to fetch MCP marketplace:", error);
|
|
2652
|
+
res.status(500).json({ error: "Failed to fetch MCP marketplace" });
|
|
2653
|
+
}
|
|
2654
|
+
});
|
|
2655
|
+
app.use(express.static(staticPath));
|
|
2656
|
+
app.get("*", (req, res) => {
|
|
2657
|
+
res.sendFile(path4.join(staticPath, "index.html"));
|
|
2658
|
+
});
|
|
2659
|
+
const server = createServer(app);
|
|
2660
|
+
server.on("error", (err) => {
|
|
2661
|
+
if (err.code === "EADDRINUSE") {
|
|
2662
|
+
console.error(`[Overture] Port ${port} already in use - another instance may be running`);
|
|
2663
|
+
} else {
|
|
2664
|
+
console.error(`[Overture] HTTP server error:`, err);
|
|
2665
|
+
}
|
|
2666
|
+
});
|
|
2667
|
+
server.listen(port, () => {
|
|
2668
|
+
console.error(`[Overture] UI server listening on http://localhost:${port}`);
|
|
2669
|
+
});
|
|
2670
|
+
}
|
|
2671
|
+
|
|
2672
|
+
export {
|
|
2673
|
+
historyStorage,
|
|
2674
|
+
wsManager,
|
|
2675
|
+
handleStreamPlanChunk,
|
|
2676
|
+
handleSubmitPlan,
|
|
2677
|
+
handleGetApproval,
|
|
2678
|
+
handleCheckPause,
|
|
2679
|
+
handleUpdateNodeStatus,
|
|
2680
|
+
handlePlanCompleted,
|
|
2681
|
+
handlePlanFailed,
|
|
2682
|
+
handleCheckRerun,
|
|
2683
|
+
handleGetResumeInfo,
|
|
2684
|
+
handleRequestPlanUpdate,
|
|
2685
|
+
handleCreateNewPlan,
|
|
2686
|
+
handleGetUsageInstructions,
|
|
2687
|
+
startHttpServer
|
|
2688
|
+
};
|