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.
@@ -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
+ };