overture-mcp 0.1.7 → 0.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-BYH4GX7P.js → chunk-SNLEFIK4.js} +1011 -92
- package/dist/cli.js +1 -1
- package/dist/index.js +266 -19
- package/package.json +2 -2
- package/prompts/claude-code.md +118 -7
- package/prompts/cline.md +108 -8
- package/prompts/cursor.md +124 -7
- package/prompts/gh_copilot.md +118 -7
- package/prompts/overture-instructions.md +14 -3
- package/prompts/sixth.md +108 -8
- package/ui-dist/assets/index-BL8qQVed.js +1156 -0
- package/ui-dist/assets/index-BL8qQVed.js.map +1 -0
- package/ui-dist/assets/index-CYlFe3KX.js +1156 -0
- package/ui-dist/assets/index-CYlFe3KX.js.map +1 -0
- package/ui-dist/assets/index-CjnQ7nIb.js +1156 -0
- package/ui-dist/assets/index-CjnQ7nIb.js.map +1 -0
- package/ui-dist/assets/index-CnRRiWVT.js +1156 -0
- package/ui-dist/assets/index-CnRRiWVT.js.map +1 -0
- package/ui-dist/assets/index-DOLl8Y--.css +1 -0
- package/ui-dist/assets/index-DTPAMidB.css +1 -0
- package/ui-dist/assets/index-DY9vK3S8.js +1156 -0
- package/ui-dist/assets/index-DY9vK3S8.js.map +1 -0
- package/ui-dist/assets/index-EbBCQtJD.css +1 -0
- package/ui-dist/assets/index-cq95_haL.js +1129 -0
- package/ui-dist/assets/index-cq95_haL.js.map +1 -0
- package/ui-dist/assets/index-kZuH1WSG.js +1156 -0
- package/ui-dist/assets/index-kZuH1WSG.js.map +1 -0
- package/ui-dist/index.html +2 -2
|
@@ -209,6 +209,289 @@ var historyStorage = new HistoryStorage();
|
|
|
209
209
|
// src/websocket/ws-server.ts
|
|
210
210
|
import { WebSocketServer, WebSocket } from "ws";
|
|
211
211
|
|
|
212
|
+
// src/storage/project-storage.ts
|
|
213
|
+
import fs2 from "fs/promises";
|
|
214
|
+
import path2 from "path";
|
|
215
|
+
var MAX_PROJECT_HISTORY_ENTRIES = 50;
|
|
216
|
+
var ProjectStorage = class {
|
|
217
|
+
workspacePath;
|
|
218
|
+
filePath;
|
|
219
|
+
projectId;
|
|
220
|
+
cache = null;
|
|
221
|
+
writeDebounceTimer = null;
|
|
222
|
+
writePromise = null;
|
|
223
|
+
writePermissionDenied = false;
|
|
224
|
+
constructor(workspacePath, projectId) {
|
|
225
|
+
this.workspacePath = workspacePath;
|
|
226
|
+
this.projectId = projectId;
|
|
227
|
+
this.filePath = path2.join(workspacePath, ".overture.json");
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Get the storage file path
|
|
231
|
+
*/
|
|
232
|
+
getFilePath() {
|
|
233
|
+
return this.filePath;
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Check if write permission was denied (should fall back to global storage)
|
|
237
|
+
*/
|
|
238
|
+
isWritePermissionDenied() {
|
|
239
|
+
return this.writePermissionDenied;
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Check if project storage file exists
|
|
243
|
+
*/
|
|
244
|
+
async exists() {
|
|
245
|
+
try {
|
|
246
|
+
await fs2.access(this.filePath);
|
|
247
|
+
return true;
|
|
248
|
+
} catch {
|
|
249
|
+
return false;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Load project configuration from disk (with caching)
|
|
254
|
+
*/
|
|
255
|
+
async load() {
|
|
256
|
+
if (this.cache) return this.cache;
|
|
257
|
+
try {
|
|
258
|
+
const data = await fs2.readFile(this.filePath, "utf-8");
|
|
259
|
+
const parsed = JSON.parse(data);
|
|
260
|
+
if (!parsed || typeof parsed.version !== "number") {
|
|
261
|
+
console.error("[Overture] Invalid project config format, creating new one");
|
|
262
|
+
this.cache = this.createEmptyConfig();
|
|
263
|
+
return this.cache;
|
|
264
|
+
}
|
|
265
|
+
this.cache = parsed;
|
|
266
|
+
console.error(`[Overture] Loaded project config: ${this.cache.history.length} history entries`);
|
|
267
|
+
return this.cache;
|
|
268
|
+
} catch (error) {
|
|
269
|
+
if (error.code === "ENOENT") {
|
|
270
|
+
console.error("[Overture] Project config does not exist, creating new one");
|
|
271
|
+
this.cache = this.createEmptyConfig();
|
|
272
|
+
return this.cache;
|
|
273
|
+
}
|
|
274
|
+
if (error.code === "EACCES") {
|
|
275
|
+
console.error("[Overture] Permission denied reading project config, will use global storage");
|
|
276
|
+
this.writePermissionDenied = true;
|
|
277
|
+
this.cache = this.createEmptyConfig();
|
|
278
|
+
return this.cache;
|
|
279
|
+
}
|
|
280
|
+
console.error("[Overture] Error loading project config:", error.message);
|
|
281
|
+
this.cache = this.createEmptyConfig();
|
|
282
|
+
return this.cache;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Create an empty project configuration
|
|
287
|
+
*/
|
|
288
|
+
createEmptyConfig() {
|
|
289
|
+
return {
|
|
290
|
+
version: 1,
|
|
291
|
+
projectId: this.projectId,
|
|
292
|
+
history: []
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* Save project configuration to disk (debounced)
|
|
297
|
+
*/
|
|
298
|
+
async save() {
|
|
299
|
+
if (this.writePermissionDenied) {
|
|
300
|
+
console.error("[Overture] Write permission denied, skipping project storage save");
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
if (this.writeDebounceTimer) {
|
|
304
|
+
clearTimeout(this.writeDebounceTimer);
|
|
305
|
+
}
|
|
306
|
+
if (this.writePromise) {
|
|
307
|
+
return this.writePromise;
|
|
308
|
+
}
|
|
309
|
+
this.writePromise = new Promise((resolve, reject) => {
|
|
310
|
+
this.writeDebounceTimer = setTimeout(async () => {
|
|
311
|
+
try {
|
|
312
|
+
if (!this.cache) {
|
|
313
|
+
resolve();
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
await fs2.writeFile(this.filePath, JSON.stringify(this.cache, null, 2));
|
|
317
|
+
console.error("[Overture] Project config saved to", this.filePath);
|
|
318
|
+
resolve();
|
|
319
|
+
} catch (error) {
|
|
320
|
+
if (error.code === "EACCES") {
|
|
321
|
+
console.error("[Overture] Permission denied writing project config, will use global storage");
|
|
322
|
+
this.writePermissionDenied = true;
|
|
323
|
+
resolve();
|
|
324
|
+
} else {
|
|
325
|
+
console.error("[Overture] Failed to save project config:", error);
|
|
326
|
+
reject(error);
|
|
327
|
+
}
|
|
328
|
+
} finally {
|
|
329
|
+
this.writePromise = null;
|
|
330
|
+
}
|
|
331
|
+
}, 1e3);
|
|
332
|
+
});
|
|
333
|
+
return this.writePromise;
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Force immediate save (bypass debounce)
|
|
337
|
+
*/
|
|
338
|
+
async saveNow() {
|
|
339
|
+
if (this.writePermissionDenied) {
|
|
340
|
+
console.error("[Overture] Write permission denied, skipping project storage save");
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
if (this.writeDebounceTimer) {
|
|
344
|
+
clearTimeout(this.writeDebounceTimer);
|
|
345
|
+
this.writeDebounceTimer = null;
|
|
346
|
+
}
|
|
347
|
+
if (!this.cache) return;
|
|
348
|
+
try {
|
|
349
|
+
await fs2.writeFile(this.filePath, JSON.stringify(this.cache, null, 2));
|
|
350
|
+
console.error("[Overture] Project config saved (immediate) to", this.filePath);
|
|
351
|
+
} catch (error) {
|
|
352
|
+
if (error.code === "EACCES") {
|
|
353
|
+
console.error("[Overture] Permission denied writing project config");
|
|
354
|
+
this.writePermissionDenied = true;
|
|
355
|
+
} else {
|
|
356
|
+
console.error("[Overture] Failed to save project config:", error);
|
|
357
|
+
throw error;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* Add or update a plan in history
|
|
363
|
+
*/
|
|
364
|
+
async addPlanToHistory(plan) {
|
|
365
|
+
const config = await this.load();
|
|
366
|
+
const existingIndex = config.history.findIndex((p) => p.plan.id === plan.plan.id);
|
|
367
|
+
if (existingIndex >= 0) {
|
|
368
|
+
config.history[existingIndex] = plan;
|
|
369
|
+
} else {
|
|
370
|
+
config.history.unshift(plan);
|
|
371
|
+
}
|
|
372
|
+
if (config.history.length > MAX_PROJECT_HISTORY_ENTRIES) {
|
|
373
|
+
const removed = config.history.splice(MAX_PROJECT_HISTORY_ENTRIES);
|
|
374
|
+
console.error(`[Overture] Pruned ${removed.length} old project history entries`);
|
|
375
|
+
}
|
|
376
|
+
await this.save();
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* Get all history entries for this project
|
|
380
|
+
*/
|
|
381
|
+
async getHistory() {
|
|
382
|
+
const config = await this.load();
|
|
383
|
+
return config.history;
|
|
384
|
+
}
|
|
385
|
+
/**
|
|
386
|
+
* Get history entries as lightweight HistoryEntry objects
|
|
387
|
+
*/
|
|
388
|
+
async getHistoryEntries() {
|
|
389
|
+
const config = await this.load();
|
|
390
|
+
return config.history.map((persisted) => ({
|
|
391
|
+
id: persisted.plan.id,
|
|
392
|
+
projectId: persisted.plan.projectId,
|
|
393
|
+
workspacePath: persisted.plan.workspacePath,
|
|
394
|
+
projectName: path2.basename(persisted.plan.workspacePath),
|
|
395
|
+
title: persisted.plan.title,
|
|
396
|
+
agent: persisted.plan.agent,
|
|
397
|
+
status: persisted.plan.status,
|
|
398
|
+
createdAt: persisted.plan.createdAt,
|
|
399
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
400
|
+
// We don't store updatedAt per plan, so use now
|
|
401
|
+
nodeCount: persisted.nodes.length,
|
|
402
|
+
completedNodeCount: persisted.nodes.filter((n) => n.status === "completed").length
|
|
403
|
+
}));
|
|
404
|
+
}
|
|
405
|
+
/**
|
|
406
|
+
* Get a specific plan by ID
|
|
407
|
+
*/
|
|
408
|
+
async getPlan(planId) {
|
|
409
|
+
const config = await this.load();
|
|
410
|
+
return config.history.find((p) => p.plan.id === planId) || null;
|
|
411
|
+
}
|
|
412
|
+
/**
|
|
413
|
+
* Delete a plan from history
|
|
414
|
+
*/
|
|
415
|
+
async deletePlan(planId) {
|
|
416
|
+
const config = await this.load();
|
|
417
|
+
config.history = config.history.filter((p) => p.plan.id !== planId);
|
|
418
|
+
await this.save();
|
|
419
|
+
}
|
|
420
|
+
/**
|
|
421
|
+
* Get project settings
|
|
422
|
+
*/
|
|
423
|
+
async getSettings() {
|
|
424
|
+
const config = await this.load();
|
|
425
|
+
return config.settings;
|
|
426
|
+
}
|
|
427
|
+
/**
|
|
428
|
+
* Update project settings
|
|
429
|
+
*/
|
|
430
|
+
async updateSettings(settings) {
|
|
431
|
+
const config = await this.load();
|
|
432
|
+
config.settings = {
|
|
433
|
+
...config.settings,
|
|
434
|
+
...settings
|
|
435
|
+
};
|
|
436
|
+
await this.save();
|
|
437
|
+
}
|
|
438
|
+
/**
|
|
439
|
+
* Clear all history
|
|
440
|
+
*/
|
|
441
|
+
async clearHistory() {
|
|
442
|
+
const config = await this.load();
|
|
443
|
+
config.history = [];
|
|
444
|
+
await this.saveNow();
|
|
445
|
+
}
|
|
446
|
+
/**
|
|
447
|
+
* Invalidate cache (force reload from disk on next access)
|
|
448
|
+
*/
|
|
449
|
+
invalidateCache() {
|
|
450
|
+
this.cache = null;
|
|
451
|
+
}
|
|
452
|
+
};
|
|
453
|
+
var ProjectStorageRegistry = class {
|
|
454
|
+
storages = /* @__PURE__ */ new Map();
|
|
455
|
+
/**
|
|
456
|
+
* Get or create a ProjectStorage instance for a workspace path
|
|
457
|
+
*/
|
|
458
|
+
getStorage(workspacePath, projectId) {
|
|
459
|
+
const key = workspacePath;
|
|
460
|
+
let storage = this.storages.get(key);
|
|
461
|
+
if (!storage) {
|
|
462
|
+
storage = new ProjectStorage(workspacePath, projectId);
|
|
463
|
+
this.storages.set(key, storage);
|
|
464
|
+
console.error(`[Overture] Created project storage for: ${workspacePath}`);
|
|
465
|
+
}
|
|
466
|
+
return storage;
|
|
467
|
+
}
|
|
468
|
+
/**
|
|
469
|
+
* Check if a project has local storage
|
|
470
|
+
*/
|
|
471
|
+
hasStorage(workspacePath) {
|
|
472
|
+
return this.storages.has(workspacePath);
|
|
473
|
+
}
|
|
474
|
+
/**
|
|
475
|
+
* Remove storage instance from registry
|
|
476
|
+
*/
|
|
477
|
+
removeStorage(workspacePath) {
|
|
478
|
+
this.storages.delete(workspacePath);
|
|
479
|
+
}
|
|
480
|
+
/**
|
|
481
|
+
* Get all active storage instances
|
|
482
|
+
*/
|
|
483
|
+
getAllStorages() {
|
|
484
|
+
return Array.from(this.storages.values());
|
|
485
|
+
}
|
|
486
|
+
/**
|
|
487
|
+
* Clear all storage instances
|
|
488
|
+
*/
|
|
489
|
+
clear() {
|
|
490
|
+
this.storages.clear();
|
|
491
|
+
}
|
|
492
|
+
};
|
|
493
|
+
var projectStorageRegistry = new ProjectStorageRegistry();
|
|
494
|
+
|
|
212
495
|
// src/utils/plan-diff.ts
|
|
213
496
|
function calculatePlanDiff(oldPlan, newPlan) {
|
|
214
497
|
const oldNodeMap = new Map(oldPlan.nodes.map((n) => [n.id, n]));
|
|
@@ -272,7 +555,7 @@ function nodesAreEqual(a, b) {
|
|
|
272
555
|
}
|
|
273
556
|
|
|
274
557
|
// src/store/plan-store.ts
|
|
275
|
-
import
|
|
558
|
+
import path3 from "path";
|
|
276
559
|
var DEFAULT_PROJECT_ID = "default";
|
|
277
560
|
var MultiProjectPlanStore = class {
|
|
278
561
|
projects = /* @__PURE__ */ new Map();
|
|
@@ -292,6 +575,21 @@ var MultiProjectPlanStore = class {
|
|
|
292
575
|
constructor() {
|
|
293
576
|
this.startAutoSave();
|
|
294
577
|
}
|
|
578
|
+
/**
|
|
579
|
+
* Get project storage for a project, or null if it should use global storage
|
|
580
|
+
* Uses project-local .overture.json if workspace path is available
|
|
581
|
+
*/
|
|
582
|
+
getProjectStorage(projectId) {
|
|
583
|
+
const state = this.projects.get(projectId);
|
|
584
|
+
if (!state || !state.workspacePath || state.workspacePath === process.cwd()) {
|
|
585
|
+
return null;
|
|
586
|
+
}
|
|
587
|
+
const storage = projectStorageRegistry.getStorage(state.workspacePath, projectId);
|
|
588
|
+
if (storage.isWritePermissionDenied()) {
|
|
589
|
+
return null;
|
|
590
|
+
}
|
|
591
|
+
return storage;
|
|
592
|
+
}
|
|
295
593
|
/**
|
|
296
594
|
* Start auto-save interval to persist all active projects every 3 seconds
|
|
297
595
|
*/
|
|
@@ -311,9 +609,16 @@ var MultiProjectPlanStore = class {
|
|
|
311
609
|
selectedBranches: state.selectedBranches,
|
|
312
610
|
nodeConfigs: state.nodeConfigs
|
|
313
611
|
};
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
612
|
+
const projectStorage = this.getProjectStorage(projectId);
|
|
613
|
+
if (projectStorage) {
|
|
614
|
+
await projectStorage.addPlanToHistory(persisted);
|
|
615
|
+
await projectStorage.saveNow();
|
|
616
|
+
console.error(`[Overture] Auto-saved project ${projectId} to project storage (plan: ${state.plan.id})`);
|
|
617
|
+
} else {
|
|
618
|
+
await historyStorage.savePlan(persisted);
|
|
619
|
+
await historyStorage.saveNow();
|
|
620
|
+
console.error(`[Overture] Auto-saved project ${projectId} to global storage (plan: ${state.plan.id})`);
|
|
621
|
+
}
|
|
317
622
|
} catch (error) {
|
|
318
623
|
console.error(`[Overture] Auto-save failed for project ${projectId}:`, error);
|
|
319
624
|
}
|
|
@@ -354,7 +659,7 @@ var MultiProjectPlanStore = class {
|
|
|
354
659
|
contexts.push({
|
|
355
660
|
projectId,
|
|
356
661
|
workspacePath: state.workspacePath,
|
|
357
|
-
projectName:
|
|
662
|
+
projectName: path3.basename(state.workspacePath),
|
|
358
663
|
agentType: state.plan?.agent || "unknown"
|
|
359
664
|
});
|
|
360
665
|
}
|
|
@@ -464,6 +769,19 @@ var MultiProjectPlanStore = class {
|
|
|
464
769
|
this.persistToHistory(projectId);
|
|
465
770
|
}
|
|
466
771
|
}
|
|
772
|
+
updatePlanSettings(projectId, planId, settings) {
|
|
773
|
+
const state = this.projects.get(projectId);
|
|
774
|
+
if (!state?.plan || state.plan.id !== planId) return false;
|
|
775
|
+
if (settings.model !== void 0) {
|
|
776
|
+
state.plan.model = settings.model || void 0;
|
|
777
|
+
}
|
|
778
|
+
if (settings.provider !== void 0) {
|
|
779
|
+
state.plan.provider = settings.provider || void 0;
|
|
780
|
+
}
|
|
781
|
+
this.persistToHistory(projectId);
|
|
782
|
+
console.error(`[Overture] Plan settings updated for ${planId}: model=${settings.model}, provider=${settings.provider}`);
|
|
783
|
+
return true;
|
|
784
|
+
}
|
|
467
785
|
updateNodeStatus(projectId, nodeId, status, output, structuredOutput) {
|
|
468
786
|
const state = this.projects.get(projectId);
|
|
469
787
|
if (!state) return;
|
|
@@ -479,6 +797,17 @@ var MultiProjectPlanStore = class {
|
|
|
479
797
|
this.persistToHistory(projectId);
|
|
480
798
|
}
|
|
481
799
|
}
|
|
800
|
+
updateNodeDescription(projectId, nodeId, description) {
|
|
801
|
+
const state = this.projects.get(projectId);
|
|
802
|
+
if (!state) return false;
|
|
803
|
+
const node = state.nodes.find((n) => n.id === nodeId);
|
|
804
|
+
if (node) {
|
|
805
|
+
node.description = description;
|
|
806
|
+
this.persistToHistory(projectId);
|
|
807
|
+
return true;
|
|
808
|
+
}
|
|
809
|
+
return false;
|
|
810
|
+
}
|
|
482
811
|
// === Approval ===
|
|
483
812
|
async setApproval(projectId, fieldValues, selectedBranches, nodeConfigs = {}) {
|
|
484
813
|
console.error(`[Overture] setApproval called for project: ${projectId}`);
|
|
@@ -679,6 +1008,45 @@ var MultiProjectPlanStore = class {
|
|
|
679
1008
|
}
|
|
680
1009
|
return { removedEdgeIds, reconnectionEdges };
|
|
681
1010
|
}
|
|
1011
|
+
/**
|
|
1012
|
+
* Insert nodes BEFORE a reference node.
|
|
1013
|
+
* Used when inserting before the first node in a plan.
|
|
1014
|
+
*/
|
|
1015
|
+
insertNodesBefore(projectId, beforeNodeId, newNodes, newEdges) {
|
|
1016
|
+
const state = this.projects.get(projectId);
|
|
1017
|
+
if (!state) return { removedEdgeIds: [], allEdges: [] };
|
|
1018
|
+
const incomingEdges = state.edges.filter((e) => e.to === beforeNodeId);
|
|
1019
|
+
const removedEdgeIds = incomingEdges.map((e) => e.id);
|
|
1020
|
+
state.edges = state.edges.filter((e) => e.to !== beforeNodeId);
|
|
1021
|
+
state.nodes.push(...newNodes);
|
|
1022
|
+
state.edges.push(...newEdges);
|
|
1023
|
+
const newNodeIds = new Set(newNodes.map((n) => n.id));
|
|
1024
|
+
const entryNodeIds = newNodes.filter((n) => !newEdges.some((e) => e.to === n.id && newNodeIds.has(e.from))).map((n) => n.id);
|
|
1025
|
+
const exitNodeIds = newNodes.filter((n) => !newEdges.some((e) => e.from === n.id && newNodeIds.has(e.to))).map((n) => n.id);
|
|
1026
|
+
const allNewEdges = [...newEdges];
|
|
1027
|
+
let edgeCounter = Date.now();
|
|
1028
|
+
for (const incomingEdge of incomingEdges) {
|
|
1029
|
+
for (const entryNodeId of entryNodeIds) {
|
|
1030
|
+
const reconnectEdge = {
|
|
1031
|
+
id: `e_inserted_${edgeCounter++}`,
|
|
1032
|
+
from: incomingEdge.from,
|
|
1033
|
+
to: entryNodeId
|
|
1034
|
+
};
|
|
1035
|
+
state.edges.push(reconnectEdge);
|
|
1036
|
+
allNewEdges.push(reconnectEdge);
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
for (const exitNodeId of exitNodeIds) {
|
|
1040
|
+
const exitEdge = {
|
|
1041
|
+
id: `e_inserted_${edgeCounter++}`,
|
|
1042
|
+
from: exitNodeId,
|
|
1043
|
+
to: beforeNodeId
|
|
1044
|
+
};
|
|
1045
|
+
state.edges.push(exitEdge);
|
|
1046
|
+
allNewEdges.push(exitEdge);
|
|
1047
|
+
}
|
|
1048
|
+
return { removedEdgeIds, allEdges: allNewEdges };
|
|
1049
|
+
}
|
|
682
1050
|
removeNode(projectId, nodeId) {
|
|
683
1051
|
const state = this.projects.get(projectId);
|
|
684
1052
|
if (!state) return { newEdges: [], removedEdgeIds: [] };
|
|
@@ -767,7 +1135,8 @@ var MultiProjectPlanStore = class {
|
|
|
767
1135
|
// === History/Persistence ===
|
|
768
1136
|
/**
|
|
769
1137
|
* Persist current project state to history
|
|
770
|
-
* Uses
|
|
1138
|
+
* Uses project-local storage (.overture.json) if workspace path is available,
|
|
1139
|
+
* otherwise falls back to global storage (~/.overture/history.json)
|
|
771
1140
|
*/
|
|
772
1141
|
async persistToHistory(projectId) {
|
|
773
1142
|
const state = this.projects.get(projectId);
|
|
@@ -781,9 +1150,16 @@ var MultiProjectPlanStore = class {
|
|
|
781
1150
|
selectedBranches: state.selectedBranches,
|
|
782
1151
|
nodeConfigs: state.nodeConfigs
|
|
783
1152
|
};
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
1153
|
+
const projectStorage = this.getProjectStorage(projectId);
|
|
1154
|
+
if (projectStorage) {
|
|
1155
|
+
await projectStorage.addPlanToHistory(persisted);
|
|
1156
|
+
await projectStorage.saveNow();
|
|
1157
|
+
console.error(`[Overture] Persisted to project storage: ${state.plan.id} (${state.nodes.length} nodes)`);
|
|
1158
|
+
} else {
|
|
1159
|
+
await historyStorage.savePlan(persisted);
|
|
1160
|
+
await historyStorage.saveNow();
|
|
1161
|
+
console.error(`[Overture] Persisted to global storage: ${state.plan.id} (${state.nodes.length} nodes)`);
|
|
1162
|
+
}
|
|
787
1163
|
} catch (error) {
|
|
788
1164
|
console.error("[Overture] Failed to persist plan to history:", error);
|
|
789
1165
|
}
|
|
@@ -808,20 +1184,62 @@ var MultiProjectPlanStore = class {
|
|
|
808
1184
|
selectedBranches: state.selectedBranches,
|
|
809
1185
|
nodeConfigs: state.nodeConfigs
|
|
810
1186
|
};
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
1187
|
+
const projectStorage = this.getProjectStorage(projectId);
|
|
1188
|
+
if (projectStorage) {
|
|
1189
|
+
await projectStorage.addPlanToHistory(persisted);
|
|
1190
|
+
await projectStorage.saveNow();
|
|
1191
|
+
console.error(`[Overture] Plan ${state.plan.id} force-persisted to project storage`);
|
|
1192
|
+
return { success: true, planId: state.plan.id, storageType: "project" };
|
|
1193
|
+
} else {
|
|
1194
|
+
await historyStorage.savePlan(persisted);
|
|
1195
|
+
await historyStorage.saveNow();
|
|
1196
|
+
console.error(`[Overture] Plan ${state.plan.id} force-persisted to global storage`);
|
|
1197
|
+
return { success: true, planId: state.plan.id, storageType: "global" };
|
|
1198
|
+
}
|
|
815
1199
|
} catch (error) {
|
|
816
1200
|
console.error("[Overture] Failed to force persist plan:", error);
|
|
817
1201
|
return { success: false };
|
|
818
1202
|
}
|
|
819
1203
|
}
|
|
1204
|
+
/**
|
|
1205
|
+
* Load a plan from a PersistedPlan object directly into the store
|
|
1206
|
+
* Used when loading from project storage
|
|
1207
|
+
*/
|
|
1208
|
+
async loadFromPersistedPlan(persisted) {
|
|
1209
|
+
const state = {
|
|
1210
|
+
projectId: persisted.plan.projectId,
|
|
1211
|
+
workspacePath: persisted.plan.workspacePath,
|
|
1212
|
+
plan: persisted.plan,
|
|
1213
|
+
nodes: persisted.nodes,
|
|
1214
|
+
edges: persisted.edges,
|
|
1215
|
+
fieldValues: persisted.fieldValues,
|
|
1216
|
+
selectedBranches: persisted.selectedBranches,
|
|
1217
|
+
nodeConfigs: persisted.nodeConfigs
|
|
1218
|
+
};
|
|
1219
|
+
this.projects.set(state.projectId, state);
|
|
1220
|
+
console.error(`[Overture] Loaded plan from PersistedPlan: ${persisted.plan.id}`);
|
|
1221
|
+
return state;
|
|
1222
|
+
}
|
|
820
1223
|
/**
|
|
821
1224
|
* Load a plan from history into a project
|
|
1225
|
+
* Checks both project-local storage and global storage
|
|
822
1226
|
*/
|
|
823
|
-
async loadFromHistory(planId) {
|
|
824
|
-
|
|
1227
|
+
async loadFromHistory(planId, workspacePath) {
|
|
1228
|
+
let persisted = null;
|
|
1229
|
+
if (workspacePath) {
|
|
1230
|
+
const tempProjectId = "temp_lookup";
|
|
1231
|
+
const projectStorage = projectStorageRegistry.getStorage(workspacePath, tempProjectId);
|
|
1232
|
+
persisted = await projectStorage.getPlan(planId);
|
|
1233
|
+
if (persisted) {
|
|
1234
|
+
console.error(`[Overture] Loaded plan ${planId} from project storage`);
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
1237
|
+
if (!persisted) {
|
|
1238
|
+
persisted = await historyStorage.getPlan(planId);
|
|
1239
|
+
if (persisted) {
|
|
1240
|
+
console.error(`[Overture] Loaded plan ${planId} from global storage`);
|
|
1241
|
+
}
|
|
1242
|
+
}
|
|
825
1243
|
if (!persisted) return null;
|
|
826
1244
|
const state = {
|
|
827
1245
|
projectId: persisted.plan.projectId,
|
|
@@ -839,19 +1257,33 @@ var MultiProjectPlanStore = class {
|
|
|
839
1257
|
/**
|
|
840
1258
|
* Restore a project from history by projectId
|
|
841
1259
|
* Finds the most recent plan for this project and loads it
|
|
1260
|
+
* Checks both project-local storage and global storage
|
|
842
1261
|
*/
|
|
843
|
-
async restoreProjectFromHistory(projectId) {
|
|
1262
|
+
async restoreProjectFromHistory(projectId, workspacePath) {
|
|
844
1263
|
try {
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
1264
|
+
let entries = [];
|
|
1265
|
+
let persisted = null;
|
|
1266
|
+
if (workspacePath) {
|
|
1267
|
+
const projectStorage = projectStorageRegistry.getStorage(workspacePath, projectId);
|
|
1268
|
+
entries = await projectStorage.getHistoryEntries();
|
|
1269
|
+
if (entries.length > 0) {
|
|
1270
|
+
const mostRecent = entries[0];
|
|
1271
|
+
console.error(`[Overture] Found project storage entry: ${mostRecent.title} (${mostRecent.id})`);
|
|
1272
|
+
persisted = await projectStorage.getPlan(mostRecent.id);
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
if (!persisted) {
|
|
1276
|
+
entries = await historyStorage.getEntriesByProject(projectId);
|
|
1277
|
+
if (entries.length === 0) {
|
|
1278
|
+
console.error(`[Overture] No history entries found for project ${projectId}`);
|
|
1279
|
+
return false;
|
|
1280
|
+
}
|
|
1281
|
+
const mostRecent = entries[0];
|
|
1282
|
+
console.error(`[Overture] Found global storage entry: ${mostRecent.title} (${mostRecent.id})`);
|
|
1283
|
+
persisted = await historyStorage.getPlan(mostRecent.id);
|
|
849
1284
|
}
|
|
850
|
-
const mostRecent = entries[0];
|
|
851
|
-
console.error(`[Overture] Found history entry: ${mostRecent.title} (${mostRecent.id})`);
|
|
852
|
-
const persisted = await historyStorage.getPlan(mostRecent.id);
|
|
853
1285
|
if (!persisted) {
|
|
854
|
-
console.error(`[Overture] Could not load plan data for ${
|
|
1286
|
+
console.error(`[Overture] Could not load plan data for project ${projectId}`);
|
|
855
1287
|
return false;
|
|
856
1288
|
}
|
|
857
1289
|
const state = {
|
|
@@ -877,6 +1309,89 @@ var MultiProjectPlanStore = class {
|
|
|
877
1309
|
return false;
|
|
878
1310
|
}
|
|
879
1311
|
}
|
|
1312
|
+
/**
|
|
1313
|
+
* Get all history entries for a project
|
|
1314
|
+
* Combines entries from project-local storage and global storage
|
|
1315
|
+
*/
|
|
1316
|
+
async getProjectHistory(projectId, workspacePath) {
|
|
1317
|
+
const allEntries = [];
|
|
1318
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
1319
|
+
if (workspacePath) {
|
|
1320
|
+
try {
|
|
1321
|
+
const projectStorage = projectStorageRegistry.getStorage(workspacePath, projectId);
|
|
1322
|
+
const projectEntries = await projectStorage.getHistoryEntries();
|
|
1323
|
+
for (const entry of projectEntries) {
|
|
1324
|
+
if (!seenIds.has(entry.id)) {
|
|
1325
|
+
seenIds.add(entry.id);
|
|
1326
|
+
allEntries.push(entry);
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
console.error(`[Overture] Found ${projectEntries.length} entries in project storage`);
|
|
1330
|
+
} catch (error) {
|
|
1331
|
+
console.error("[Overture] Failed to get project storage history:", error);
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1334
|
+
try {
|
|
1335
|
+
const globalEntries = await historyStorage.getEntriesByProject(projectId);
|
|
1336
|
+
for (const entry of globalEntries) {
|
|
1337
|
+
if (!seenIds.has(entry.id)) {
|
|
1338
|
+
seenIds.add(entry.id);
|
|
1339
|
+
allEntries.push(entry);
|
|
1340
|
+
}
|
|
1341
|
+
}
|
|
1342
|
+
console.error(`[Overture] Found ${globalEntries.length} entries in global storage`);
|
|
1343
|
+
} catch (error) {
|
|
1344
|
+
console.error("[Overture] Failed to get global storage history:", error);
|
|
1345
|
+
}
|
|
1346
|
+
allEntries.sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime());
|
|
1347
|
+
return allEntries;
|
|
1348
|
+
}
|
|
1349
|
+
/**
|
|
1350
|
+
* Get all history entries grouped by project
|
|
1351
|
+
* Combines entries from all project storages and global storage
|
|
1352
|
+
*/
|
|
1353
|
+
async getAllHistoryGroupedByProject() {
|
|
1354
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
1355
|
+
for (const storage of projectStorageRegistry.getAllStorages()) {
|
|
1356
|
+
try {
|
|
1357
|
+
const entries = await storage.getHistoryEntries();
|
|
1358
|
+
for (const entry of entries) {
|
|
1359
|
+
if (!grouped.has(entry.projectId)) {
|
|
1360
|
+
grouped.set(entry.projectId, {
|
|
1361
|
+
projectName: entry.projectName,
|
|
1362
|
+
workspacePath: entry.workspacePath,
|
|
1363
|
+
entries: []
|
|
1364
|
+
});
|
|
1365
|
+
}
|
|
1366
|
+
grouped.get(entry.projectId).entries.push(entry);
|
|
1367
|
+
}
|
|
1368
|
+
} catch (error) {
|
|
1369
|
+
console.error("[Overture] Failed to get project storage history:", error);
|
|
1370
|
+
}
|
|
1371
|
+
}
|
|
1372
|
+
try {
|
|
1373
|
+
const globalEntries = await historyStorage.getAllEntries();
|
|
1374
|
+
for (const entry of globalEntries) {
|
|
1375
|
+
if (!grouped.has(entry.projectId)) {
|
|
1376
|
+
grouped.set(entry.projectId, {
|
|
1377
|
+
projectName: entry.projectName,
|
|
1378
|
+
workspacePath: entry.workspacePath,
|
|
1379
|
+
entries: []
|
|
1380
|
+
});
|
|
1381
|
+
}
|
|
1382
|
+
const group = grouped.get(entry.projectId);
|
|
1383
|
+
if (!group.entries.some((e) => e.id === entry.id)) {
|
|
1384
|
+
group.entries.push(entry);
|
|
1385
|
+
}
|
|
1386
|
+
}
|
|
1387
|
+
} catch (error) {
|
|
1388
|
+
console.error("[Overture] Failed to get global storage history:", error);
|
|
1389
|
+
}
|
|
1390
|
+
for (const group of grouped.values()) {
|
|
1391
|
+
group.entries.sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime());
|
|
1392
|
+
}
|
|
1393
|
+
return grouped;
|
|
1394
|
+
}
|
|
880
1395
|
// === Resume Info ===
|
|
881
1396
|
/**
|
|
882
1397
|
* Generate resume info for a paused/failed plan
|
|
@@ -1062,6 +1577,49 @@ var LegacyPlanStore = class {
|
|
|
1062
1577
|
};
|
|
1063
1578
|
var planStore = new LegacyPlanStore();
|
|
1064
1579
|
|
|
1580
|
+
// src/store/settings-store.ts
|
|
1581
|
+
var DEFAULT_SETTINGS = {
|
|
1582
|
+
minNodesPerPlan: 1
|
|
1583
|
+
};
|
|
1584
|
+
var MIN_NODES_MIN = 1;
|
|
1585
|
+
var MIN_NODES_MAX = 20;
|
|
1586
|
+
var SettingsStore = class {
|
|
1587
|
+
settings = { ...DEFAULT_SETTINGS };
|
|
1588
|
+
/**
|
|
1589
|
+
* Get current settings
|
|
1590
|
+
*/
|
|
1591
|
+
getSettings() {
|
|
1592
|
+
return { ...this.settings };
|
|
1593
|
+
}
|
|
1594
|
+
/**
|
|
1595
|
+
* Get minimum nodes per plan setting
|
|
1596
|
+
*/
|
|
1597
|
+
getMinNodesPerPlan() {
|
|
1598
|
+
return this.settings.minNodesPerPlan;
|
|
1599
|
+
}
|
|
1600
|
+
/**
|
|
1601
|
+
* Update settings from UI
|
|
1602
|
+
*/
|
|
1603
|
+
updateSettings(newSettings) {
|
|
1604
|
+
if (newSettings.minNodesPerPlan !== void 0) {
|
|
1605
|
+
const value = Math.min(
|
|
1606
|
+
Math.max(newSettings.minNodesPerPlan, MIN_NODES_MIN),
|
|
1607
|
+
MIN_NODES_MAX
|
|
1608
|
+
);
|
|
1609
|
+
this.settings.minNodesPerPlan = value;
|
|
1610
|
+
console.error(`[Overture] Settings updated: minNodesPerPlan = ${value}`);
|
|
1611
|
+
}
|
|
1612
|
+
}
|
|
1613
|
+
/**
|
|
1614
|
+
* Reset settings to defaults
|
|
1615
|
+
*/
|
|
1616
|
+
reset() {
|
|
1617
|
+
this.settings = { ...DEFAULT_SETTINGS };
|
|
1618
|
+
console.error("[Overture] Settings reset to defaults");
|
|
1619
|
+
}
|
|
1620
|
+
};
|
|
1621
|
+
var settingsStore = new SettingsStore();
|
|
1622
|
+
|
|
1065
1623
|
// src/websocket/ws-server.ts
|
|
1066
1624
|
var WebSocketManager = class {
|
|
1067
1625
|
wss = null;
|
|
@@ -1215,7 +1773,25 @@ var WebSocketManager = class {
|
|
|
1215
1773
|
case "get_history": {
|
|
1216
1774
|
console.error("[Overture] History requested");
|
|
1217
1775
|
let entries;
|
|
1218
|
-
if (message.projectId) {
|
|
1776
|
+
if (message.workspacePath && message.projectId) {
|
|
1777
|
+
const projectStorage = projectStorageRegistry.getStorage(message.workspacePath, message.projectId);
|
|
1778
|
+
if (projectStorage.isWritePermissionDenied()) {
|
|
1779
|
+
console.error("[Overture] Project storage permission denied, using global storage");
|
|
1780
|
+
entries = await historyStorage.getEntriesByProject(message.projectId);
|
|
1781
|
+
} else {
|
|
1782
|
+
const projectEntries = await projectStorage.getHistoryEntries();
|
|
1783
|
+
const globalEntries = await historyStorage.getEntriesByProject(message.projectId);
|
|
1784
|
+
const entryMap = /* @__PURE__ */ new Map();
|
|
1785
|
+
for (const entry of globalEntries) {
|
|
1786
|
+
entryMap.set(entry.id, entry);
|
|
1787
|
+
}
|
|
1788
|
+
for (const entry of projectEntries) {
|
|
1789
|
+
entryMap.set(entry.id, entry);
|
|
1790
|
+
}
|
|
1791
|
+
entries = Array.from(entryMap.values());
|
|
1792
|
+
entries.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
|
|
1793
|
+
}
|
|
1794
|
+
} else if (message.projectId) {
|
|
1219
1795
|
entries = await historyStorage.getEntriesByProject(message.projectId);
|
|
1220
1796
|
} else {
|
|
1221
1797
|
entries = await historyStorage.getAllEntries();
|
|
@@ -1225,7 +1801,19 @@ var WebSocketManager = class {
|
|
|
1225
1801
|
}
|
|
1226
1802
|
case "load_plan": {
|
|
1227
1803
|
console.error(`[Overture] Loading plan from history: ${message.planId}`);
|
|
1228
|
-
|
|
1804
|
+
let state = null;
|
|
1805
|
+
if (message.workspacePath && message.projectId) {
|
|
1806
|
+
const projectStorage = projectStorageRegistry.getStorage(message.workspacePath, message.projectId);
|
|
1807
|
+
if (!projectStorage.isWritePermissionDenied()) {
|
|
1808
|
+
const persistedPlan = await projectStorage.getPlan(message.planId);
|
|
1809
|
+
if (persistedPlan) {
|
|
1810
|
+
state = await multiProjectPlanStore.loadFromPersistedPlan(persistedPlan);
|
|
1811
|
+
}
|
|
1812
|
+
}
|
|
1813
|
+
}
|
|
1814
|
+
if (!state) {
|
|
1815
|
+
state = await multiProjectPlanStore.loadFromHistory(message.planId);
|
|
1816
|
+
}
|
|
1229
1817
|
if (state?.plan) {
|
|
1230
1818
|
const client = this.clients.get(ws);
|
|
1231
1819
|
if (client) {
|
|
@@ -1341,16 +1929,30 @@ var WebSocketManager = class {
|
|
|
1341
1929
|
this.broadcastToProject(projectId, { type: "plan_resumed", projectId });
|
|
1342
1930
|
break;
|
|
1343
1931
|
case "insert_nodes": {
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1932
|
+
if (message.afterNodeId) {
|
|
1933
|
+
console.error(`[Overture] Inserting ${message.nodes.length} node(s) after ${message.afterNodeId} (project: ${projectId})`);
|
|
1934
|
+
const insertResult = multiProjectPlanStore.insertNodes(projectId, message.afterNodeId, message.nodes, message.edges);
|
|
1935
|
+
const allEdges = [...message.edges, ...insertResult.reconnectionEdges];
|
|
1936
|
+
this.broadcastToProject(projectId, {
|
|
1937
|
+
type: "nodes_inserted",
|
|
1938
|
+
nodes: message.nodes,
|
|
1939
|
+
edges: allEdges,
|
|
1940
|
+
removedEdgeIds: insertResult.removedEdgeIds,
|
|
1941
|
+
projectId
|
|
1942
|
+
});
|
|
1943
|
+
} else if (message.beforeNodeId) {
|
|
1944
|
+
console.error(`[Overture] Inserting ${message.nodes.length} node(s) before ${message.beforeNodeId} (project: ${projectId})`);
|
|
1945
|
+
const insertResult = multiProjectPlanStore.insertNodesBefore(projectId, message.beforeNodeId, message.nodes, message.edges);
|
|
1946
|
+
this.broadcastToProject(projectId, {
|
|
1947
|
+
type: "nodes_inserted",
|
|
1948
|
+
nodes: message.nodes,
|
|
1949
|
+
edges: insertResult.allEdges,
|
|
1950
|
+
removedEdgeIds: insertResult.removedEdgeIds,
|
|
1951
|
+
projectId
|
|
1952
|
+
});
|
|
1953
|
+
} else {
|
|
1954
|
+
console.error(`[Overture] insert_nodes called without afterNodeId or beforeNodeId`);
|
|
1955
|
+
}
|
|
1354
1956
|
break;
|
|
1355
1957
|
}
|
|
1356
1958
|
case "remove_node": {
|
|
@@ -1388,6 +1990,48 @@ var WebSocketManager = class {
|
|
|
1388
1990
|
});
|
|
1389
1991
|
break;
|
|
1390
1992
|
}
|
|
1993
|
+
case "update_node_description": {
|
|
1994
|
+
const effectiveProjectId = message.projectId || projectId;
|
|
1995
|
+
console.error(`[Overture] Updating node description: ${message.nodeId} (project: ${effectiveProjectId})`);
|
|
1996
|
+
const success = multiProjectPlanStore.updateNodeDescription(
|
|
1997
|
+
effectiveProjectId,
|
|
1998
|
+
message.nodeId,
|
|
1999
|
+
message.description
|
|
2000
|
+
);
|
|
2001
|
+
if (success) {
|
|
2002
|
+
this.broadcastToProject(effectiveProjectId, {
|
|
2003
|
+
type: "node_description_updated",
|
|
2004
|
+
nodeId: message.nodeId,
|
|
2005
|
+
description: message.description,
|
|
2006
|
+
projectId: effectiveProjectId
|
|
2007
|
+
});
|
|
2008
|
+
}
|
|
2009
|
+
break;
|
|
2010
|
+
}
|
|
2011
|
+
case "sync_settings": {
|
|
2012
|
+
console.error("[Overture] Received settings sync:", message.settings);
|
|
2013
|
+
settingsStore.updateSettings(message.settings);
|
|
2014
|
+
break;
|
|
2015
|
+
}
|
|
2016
|
+
case "update_plan_settings": {
|
|
2017
|
+
const effectiveProjectId = message.projectId || projectId;
|
|
2018
|
+
console.error(`[Overture] Updating plan settings for plan: ${message.planId} (project: ${effectiveProjectId})`);
|
|
2019
|
+
const success = multiProjectPlanStore.updatePlanSettings(
|
|
2020
|
+
effectiveProjectId,
|
|
2021
|
+
message.planId,
|
|
2022
|
+
{ model: message.model, provider: message.provider }
|
|
2023
|
+
);
|
|
2024
|
+
if (success) {
|
|
2025
|
+
this.broadcastToProject(effectiveProjectId, {
|
|
2026
|
+
type: "plan_settings_updated",
|
|
2027
|
+
planId: message.planId,
|
|
2028
|
+
model: message.model,
|
|
2029
|
+
provider: message.provider,
|
|
2030
|
+
projectId: effectiveProjectId
|
|
2031
|
+
});
|
|
2032
|
+
}
|
|
2033
|
+
break;
|
|
2034
|
+
}
|
|
1391
2035
|
}
|
|
1392
2036
|
}
|
|
1393
2037
|
/**
|
|
@@ -1564,8 +2208,8 @@ var wsManager = new WebSocketManager();
|
|
|
1564
2208
|
|
|
1565
2209
|
// src/tools/handlers.ts
|
|
1566
2210
|
import { createHash } from "crypto";
|
|
1567
|
-
import
|
|
1568
|
-
import
|
|
2211
|
+
import path4 from "path";
|
|
2212
|
+
import fs3 from "fs/promises";
|
|
1569
2213
|
import { fileURLToPath } from "url";
|
|
1570
2214
|
|
|
1571
2215
|
// src/parser/xml-parser.ts
|
|
@@ -1609,7 +2253,9 @@ var StreamingXMLParser = class {
|
|
|
1609
2253
|
title: tag.attributes.title || "Untitled Plan",
|
|
1610
2254
|
agent: tag.attributes.agent || "unknown",
|
|
1611
2255
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1612
|
-
status: "streaming"
|
|
2256
|
+
status: "streaming",
|
|
2257
|
+
model: tag.attributes.model,
|
|
2258
|
+
provider: tag.attributes.provider
|
|
1613
2259
|
};
|
|
1614
2260
|
this.callback({ type: "plan", plan: this.state.plan });
|
|
1615
2261
|
break;
|
|
@@ -1778,6 +2424,38 @@ var StreamingXMLParser = class {
|
|
|
1778
2424
|
|
|
1779
2425
|
// src/parser/output-parser.ts
|
|
1780
2426
|
import sax2 from "sax";
|
|
2427
|
+
function normalizeDiffContent(content) {
|
|
2428
|
+
if (!content) return "";
|
|
2429
|
+
let normalized = content.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
2430
|
+
let lines = normalized.split("\n");
|
|
2431
|
+
while (lines.length > 0 && lines[0].trim() === "") {
|
|
2432
|
+
lines.shift();
|
|
2433
|
+
}
|
|
2434
|
+
while (lines.length > 0 && lines[lines.length - 1].trim() === "") {
|
|
2435
|
+
lines.pop();
|
|
2436
|
+
}
|
|
2437
|
+
if (lines.length === 0) return "";
|
|
2438
|
+
const nonDiffLines = lines.filter(
|
|
2439
|
+
(line) => line.trim() !== "" && !/^[+\-@]/.test(line.trimStart())
|
|
2440
|
+
);
|
|
2441
|
+
let commonIndent = Infinity;
|
|
2442
|
+
for (const line of nonDiffLines) {
|
|
2443
|
+
const match = line.match(/^(\s*)/);
|
|
2444
|
+
if (match) {
|
|
2445
|
+
commonIndent = Math.min(commonIndent, match[1].length);
|
|
2446
|
+
}
|
|
2447
|
+
}
|
|
2448
|
+
if (commonIndent > 0 && commonIndent !== Infinity && commonIndent <= 8) {
|
|
2449
|
+
lines = lines.map((line) => {
|
|
2450
|
+
if (line.trim() === "") return "";
|
|
2451
|
+
if (line.length >= commonIndent) {
|
|
2452
|
+
return line.slice(commonIndent);
|
|
2453
|
+
}
|
|
2454
|
+
return line;
|
|
2455
|
+
});
|
|
2456
|
+
}
|
|
2457
|
+
return lines.join("\n");
|
|
2458
|
+
}
|
|
1781
2459
|
function parseStructuredOutput(output) {
|
|
1782
2460
|
const startTag = "<execution_output>";
|
|
1783
2461
|
const endTag = "</execution_output>";
|
|
@@ -1905,7 +2583,12 @@ function parseStructuredOutput(output) {
|
|
|
1905
2583
|
break;
|
|
1906
2584
|
case "diff":
|
|
1907
2585
|
if (state.currentFile) {
|
|
1908
|
-
state.currentFile.diff =
|
|
2586
|
+
state.currentFile.diff = normalizeDiffContent(state.textBuffer);
|
|
2587
|
+
}
|
|
2588
|
+
break;
|
|
2589
|
+
case "content":
|
|
2590
|
+
if (state.currentFileCreated) {
|
|
2591
|
+
state.currentFileCreated.content = normalizeDiffContent(state.textBuffer);
|
|
1909
2592
|
}
|
|
1910
2593
|
break;
|
|
1911
2594
|
case "config":
|
|
@@ -2348,7 +3031,7 @@ function handleSubmitPlan(planXml, workspacePath, agentType) {
|
|
|
2348
3031
|
const projectContext = {
|
|
2349
3032
|
projectId,
|
|
2350
3033
|
workspacePath: effectivePath,
|
|
2351
|
-
projectName:
|
|
3034
|
+
projectName: path4.basename(effectivePath),
|
|
2352
3035
|
agentType: plan.agent
|
|
2353
3036
|
};
|
|
2354
3037
|
multiProjectPlanStore.initializeProject(projectContext);
|
|
@@ -2404,6 +3087,16 @@ function handleSubmitPlan(planXml, workspacePath, agentType) {
|
|
|
2404
3087
|
console.error("[Overture] Plan parsing result. Nodes:", nodes.length, "Edges:", edges.length);
|
|
2405
3088
|
console.error("[Overture] Project stored with ID:", projectId);
|
|
2406
3089
|
console.error("[Overture] All projects after submit:", Array.from(multiProjectPlanStore.getAllProjects().map((p) => p.projectId)));
|
|
3090
|
+
const minNodesRequired = settingsStore.getMinNodesPerPlan();
|
|
3091
|
+
if (nodes.length > 0 && nodes.length < minNodesRequired) {
|
|
3092
|
+
console.error(`[Overture] Plan rejected: ${nodes.length} nodes < minimum ${minNodesRequired}`);
|
|
3093
|
+
multiProjectPlanStore.clearProjectPlan(projectId);
|
|
3094
|
+
return {
|
|
3095
|
+
success: false,
|
|
3096
|
+
message: `Plan rejected: Only ${nodes.length} node(s) provided, but minimum ${minNodesRequired} node(s) required. Please create a more detailed plan.`,
|
|
3097
|
+
projectId
|
|
3098
|
+
};
|
|
3099
|
+
}
|
|
2407
3100
|
if (nodes.length > 0) {
|
|
2408
3101
|
return {
|
|
2409
3102
|
success: true,
|
|
@@ -2419,8 +3112,9 @@ function handleSubmitPlan(planXml, workspacePath, agentType) {
|
|
|
2419
3112
|
projectId
|
|
2420
3113
|
};
|
|
2421
3114
|
}
|
|
2422
|
-
async function handleGetApproval(projectId) {
|
|
3115
|
+
async function handleGetApproval(projectId, workspacePath) {
|
|
2423
3116
|
const effectiveProjectId = projectId || currentProjectId;
|
|
3117
|
+
const effectiveWorkspacePath = workspacePath;
|
|
2424
3118
|
console.error(`[Overture] get_approval called for project: ${effectiveProjectId}`);
|
|
2425
3119
|
console.error(`[Overture] Provided projectId: ${projectId}, currentProjectId: ${currentProjectId}`);
|
|
2426
3120
|
console.error(`[Overture] All projects in store:`, multiProjectPlanStore.getAllProjects().map((p) => p.projectId));
|
|
@@ -2454,23 +3148,26 @@ async function handleGetApproval(projectId) {
|
|
|
2454
3148
|
status: "approved",
|
|
2455
3149
|
firstNode: firstNodeInfo,
|
|
2456
3150
|
message: "Plan approved by user. Execute firstNode, then call update_node_status to get the next node.",
|
|
2457
|
-
projectId: effectiveProjectId
|
|
3151
|
+
projectId: effectiveProjectId,
|
|
3152
|
+
workspacePath: effectiveWorkspacePath
|
|
2458
3153
|
};
|
|
2459
3154
|
}
|
|
2460
3155
|
if (result === "cancelled") {
|
|
2461
3156
|
return {
|
|
2462
3157
|
status: "cancelled",
|
|
2463
3158
|
message: "Plan cancelled by user",
|
|
2464
|
-
projectId: effectiveProjectId
|
|
3159
|
+
projectId: effectiveProjectId,
|
|
3160
|
+
workspacePath: effectiveWorkspacePath
|
|
2465
3161
|
};
|
|
2466
3162
|
}
|
|
2467
3163
|
return {
|
|
2468
3164
|
status: "pending",
|
|
2469
3165
|
message: "Waiting for user approval. Call get_approval again to continue waiting.",
|
|
2470
|
-
projectId: effectiveProjectId
|
|
3166
|
+
projectId: effectiveProjectId,
|
|
3167
|
+
workspacePath: effectiveWorkspacePath
|
|
2471
3168
|
};
|
|
2472
3169
|
}
|
|
2473
|
-
async function handleCheckPause(wait = false, projectId) {
|
|
3170
|
+
async function handleCheckPause(wait = false, projectId, workspacePath) {
|
|
2474
3171
|
const effectiveProjectId = projectId || currentProjectId;
|
|
2475
3172
|
const isPaused = multiProjectPlanStore.getIsPaused(effectiveProjectId);
|
|
2476
3173
|
if (!isPaused) {
|
|
@@ -2478,7 +3175,8 @@ async function handleCheckPause(wait = false, projectId) {
|
|
|
2478
3175
|
isPaused: false,
|
|
2479
3176
|
wasResumed: false,
|
|
2480
3177
|
message: "Execution is not paused",
|
|
2481
|
-
projectId: effectiveProjectId
|
|
3178
|
+
projectId: effectiveProjectId,
|
|
3179
|
+
workspacePath
|
|
2482
3180
|
};
|
|
2483
3181
|
}
|
|
2484
3182
|
if (!wait) {
|
|
@@ -2486,7 +3184,8 @@ async function handleCheckPause(wait = false, projectId) {
|
|
|
2486
3184
|
isPaused: true,
|
|
2487
3185
|
wasResumed: false,
|
|
2488
3186
|
message: "Execution is paused. Call with wait=true to block until resumed.",
|
|
2489
|
-
projectId: effectiveProjectId
|
|
3187
|
+
projectId: effectiveProjectId,
|
|
3188
|
+
workspacePath
|
|
2490
3189
|
};
|
|
2491
3190
|
}
|
|
2492
3191
|
await multiProjectPlanStore.waitIfPaused(effectiveProjectId);
|
|
@@ -2494,10 +3193,11 @@ async function handleCheckPause(wait = false, projectId) {
|
|
|
2494
3193
|
isPaused: false,
|
|
2495
3194
|
wasResumed: true,
|
|
2496
3195
|
message: "Execution was paused and has now been resumed",
|
|
2497
|
-
projectId: effectiveProjectId
|
|
3196
|
+
projectId: effectiveProjectId,
|
|
3197
|
+
workspacePath
|
|
2498
3198
|
};
|
|
2499
3199
|
}
|
|
2500
|
-
function handleUpdateNodeStatus(nodeId, status, output, projectId) {
|
|
3200
|
+
function handleUpdateNodeStatus(nodeId, status, output, projectId, workspacePath) {
|
|
2501
3201
|
const effectiveProjectId = projectId || currentProjectId;
|
|
2502
3202
|
const plan = multiProjectPlanStore.getPlan(effectiveProjectId);
|
|
2503
3203
|
const provider = plan?.agent || "unknown";
|
|
@@ -2506,7 +3206,7 @@ function handleUpdateNodeStatus(nodeId, status, output, projectId) {
|
|
|
2506
3206
|
const nodeConfigs = multiProjectPlanStore.getNodeConfigs(effectiveProjectId);
|
|
2507
3207
|
const node = nodes.find((n) => n.id === nodeId);
|
|
2508
3208
|
if (!node) {
|
|
2509
|
-
return { success: false, message: `Node ${nodeId} not found`, projectId: effectiveProjectId };
|
|
3209
|
+
return { success: false, message: `Node ${nodeId} not found`, projectId: effectiveProjectId, workspacePath };
|
|
2510
3210
|
}
|
|
2511
3211
|
if (plan && (plan.status === "ready" || plan.status === "streaming")) {
|
|
2512
3212
|
console.error(`[Overture] Auto-approving plan - agent called update_node_status before get_approval (manual approval detected)`);
|
|
@@ -2542,7 +3242,8 @@ function handleUpdateNodeStatus(nodeId, status, output, projectId) {
|
|
|
2542
3242
|
message: `Node ${nodeId} status updated to ${status}. Execute this node now.`,
|
|
2543
3243
|
currentNode: currentNodeInfo,
|
|
2544
3244
|
isPaused,
|
|
2545
|
-
projectId: effectiveProjectId
|
|
3245
|
+
projectId: effectiveProjectId,
|
|
3246
|
+
workspacePath
|
|
2546
3247
|
};
|
|
2547
3248
|
}
|
|
2548
3249
|
if (status === "completed") {
|
|
@@ -2553,7 +3254,8 @@ function handleUpdateNodeStatus(nodeId, status, output, projectId) {
|
|
|
2553
3254
|
message: `Node ${nodeId} status updated to ${status}`,
|
|
2554
3255
|
nextNode: nextNodeInfo,
|
|
2555
3256
|
isPaused,
|
|
2556
|
-
projectId: effectiveProjectId
|
|
3257
|
+
projectId: effectiveProjectId,
|
|
3258
|
+
workspacePath
|
|
2557
3259
|
};
|
|
2558
3260
|
} else {
|
|
2559
3261
|
return {
|
|
@@ -2561,7 +3263,8 @@ function handleUpdateNodeStatus(nodeId, status, output, projectId) {
|
|
|
2561
3263
|
message: `Node ${nodeId} status updated to ${status}. This was the last node.`,
|
|
2562
3264
|
isLastNode: true,
|
|
2563
3265
|
isPaused,
|
|
2564
|
-
projectId: effectiveProjectId
|
|
3266
|
+
projectId: effectiveProjectId,
|
|
3267
|
+
workspacePath
|
|
2565
3268
|
};
|
|
2566
3269
|
}
|
|
2567
3270
|
}
|
|
@@ -2569,7 +3272,8 @@ function handleUpdateNodeStatus(nodeId, status, output, projectId) {
|
|
|
2569
3272
|
success: true,
|
|
2570
3273
|
message: `Node ${nodeId} status updated to ${status}`,
|
|
2571
3274
|
isPaused,
|
|
2572
|
-
projectId: effectiveProjectId
|
|
3275
|
+
projectId: effectiveProjectId,
|
|
3276
|
+
workspacePath
|
|
2573
3277
|
};
|
|
2574
3278
|
}
|
|
2575
3279
|
function findNextNode(projectId, currentNodeId, nodes, edges, provider) {
|
|
@@ -2646,26 +3350,27 @@ function findNextNode(projectId, currentNodeId, nodes, edges, provider) {
|
|
|
2646
3350
|
}
|
|
2647
3351
|
return null;
|
|
2648
3352
|
}
|
|
2649
|
-
function handlePlanCompleted(projectId) {
|
|
3353
|
+
function handlePlanCompleted(projectId, workspacePath) {
|
|
2650
3354
|
const effectiveProjectId = projectId || currentProjectId;
|
|
2651
3355
|
multiProjectPlanStore.updatePlanStatus(effectiveProjectId, "completed");
|
|
2652
3356
|
wsManager.broadcastToProject(effectiveProjectId, { type: "plan_completed", projectId: effectiveProjectId });
|
|
2653
|
-
return { success: true, message: "Plan completed", projectId: effectiveProjectId };
|
|
3357
|
+
return { success: true, message: "Plan completed", projectId: effectiveProjectId, workspacePath };
|
|
2654
3358
|
}
|
|
2655
|
-
function handlePlanFailed(error, projectId) {
|
|
3359
|
+
function handlePlanFailed(error, projectId, workspacePath) {
|
|
2656
3360
|
const effectiveProjectId = projectId || currentProjectId;
|
|
2657
3361
|
multiProjectPlanStore.updatePlanStatus(effectiveProjectId, "failed");
|
|
2658
3362
|
wsManager.broadcastToProject(effectiveProjectId, { type: "plan_failed", error, projectId: effectiveProjectId });
|
|
2659
|
-
return { success: true, message: "Plan failed", projectId: effectiveProjectId };
|
|
3363
|
+
return { success: true, message: "Plan failed", projectId: effectiveProjectId, workspacePath };
|
|
2660
3364
|
}
|
|
2661
|
-
async function handleCheckRerun(timeoutMs = 5e3, projectId) {
|
|
3365
|
+
async function handleCheckRerun(timeoutMs = 5e3, projectId, workspacePath) {
|
|
2662
3366
|
const effectiveProjectId = projectId || currentProjectId;
|
|
2663
3367
|
const rerunRequest = await multiProjectPlanStore.waitForRerun(effectiveProjectId, timeoutMs);
|
|
2664
3368
|
if (!rerunRequest) {
|
|
2665
3369
|
return {
|
|
2666
3370
|
hasRerun: false,
|
|
2667
3371
|
message: "No rerun request pending",
|
|
2668
|
-
projectId: effectiveProjectId
|
|
3372
|
+
projectId: effectiveProjectId,
|
|
3373
|
+
workspacePath
|
|
2669
3374
|
};
|
|
2670
3375
|
}
|
|
2671
3376
|
const resetNodeIds = multiProjectPlanStore.resetNodesForRerun(effectiveProjectId, rerunRequest.nodeId, rerunRequest.mode);
|
|
@@ -2699,27 +3404,30 @@ async function handleCheckRerun(timeoutMs = 5e3, projectId) {
|
|
|
2699
3404
|
mode: rerunRequest.mode,
|
|
2700
3405
|
nodeInfo,
|
|
2701
3406
|
message: `Rerun requested from node ${rerunRequest.nodeId} (${rerunRequest.mode})`,
|
|
2702
|
-
projectId: effectiveProjectId
|
|
3407
|
+
projectId: effectiveProjectId,
|
|
3408
|
+
workspacePath
|
|
2703
3409
|
};
|
|
2704
3410
|
}
|
|
2705
|
-
function handleGetResumeInfo(projectId) {
|
|
3411
|
+
function handleGetResumeInfo(projectId, workspacePath) {
|
|
2706
3412
|
const effectiveProjectId = projectId || currentProjectId;
|
|
2707
3413
|
const resumeInfo = multiProjectPlanStore.getResumeInfo(effectiveProjectId);
|
|
2708
3414
|
if (!resumeInfo) {
|
|
2709
3415
|
return {
|
|
2710
3416
|
success: false,
|
|
2711
3417
|
message: `No active plan found for project: ${effectiveProjectId}`,
|
|
2712
|
-
projectId: effectiveProjectId
|
|
3418
|
+
projectId: effectiveProjectId,
|
|
3419
|
+
workspacePath
|
|
2713
3420
|
};
|
|
2714
3421
|
}
|
|
2715
3422
|
return {
|
|
2716
3423
|
success: true,
|
|
2717
3424
|
resumeInfo,
|
|
2718
3425
|
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}`,
|
|
2719
|
-
projectId: effectiveProjectId
|
|
3426
|
+
projectId: effectiveProjectId,
|
|
3427
|
+
workspacePath
|
|
2720
3428
|
};
|
|
2721
3429
|
}
|
|
2722
|
-
function handleRequestPlanUpdate(operations, projectId) {
|
|
3430
|
+
function handleRequestPlanUpdate(operations, projectId, workspacePath) {
|
|
2723
3431
|
const effectiveProjectId = projectId || currentProjectId;
|
|
2724
3432
|
const currentPlan = multiProjectPlanStore.getPlan(effectiveProjectId);
|
|
2725
3433
|
if (!currentPlan) {
|
|
@@ -2727,7 +3435,8 @@ function handleRequestPlanUpdate(operations, projectId) {
|
|
|
2727
3435
|
success: false,
|
|
2728
3436
|
message: `No active plan found for project: ${effectiveProjectId}. Submit a new plan instead.`,
|
|
2729
3437
|
results: [],
|
|
2730
|
-
projectId: effectiveProjectId
|
|
3438
|
+
projectId: effectiveProjectId,
|
|
3439
|
+
workspacePath
|
|
2731
3440
|
};
|
|
2732
3441
|
}
|
|
2733
3442
|
multiProjectPlanStore.storePreviousPlanState(effectiveProjectId);
|
|
@@ -2780,7 +3489,8 @@ function handleRequestPlanUpdate(operations, projectId) {
|
|
|
2780
3489
|
success: failCount === 0,
|
|
2781
3490
|
message: `Applied ${successCount}/${operations.length} operations. ${failCount > 0 ? "Some operations failed." : "All operations succeeded."} Call get_approval to confirm changes with user.`,
|
|
2782
3491
|
results,
|
|
2783
|
-
projectId: effectiveProjectId
|
|
3492
|
+
projectId: effectiveProjectId,
|
|
3493
|
+
workspacePath
|
|
2784
3494
|
};
|
|
2785
3495
|
}
|
|
2786
3496
|
function applyInsertOperation(projectId, referenceNodeId, position, nodeData) {
|
|
@@ -2907,7 +3617,7 @@ function applyReplaceOperation(projectId, nodeId, newNodeData) {
|
|
|
2907
3617
|
console.error(`[Overture] Node ${nodeId} replaced with new content`);
|
|
2908
3618
|
return { success: true, message: `Node "${oldNode.title}" replaced with "${updatedNode.title}"` };
|
|
2909
3619
|
}
|
|
2910
|
-
function handleCreateNewPlan(projectId) {
|
|
3620
|
+
function handleCreateNewPlan(projectId, workspacePath) {
|
|
2911
3621
|
const effectiveProjectId = projectId || currentProjectId;
|
|
2912
3622
|
wsManager.broadcastToProject(effectiveProjectId, {
|
|
2913
3623
|
type: "new_plan_created",
|
|
@@ -2919,7 +3629,8 @@ function handleCreateNewPlan(projectId) {
|
|
|
2919
3629
|
return {
|
|
2920
3630
|
success: true,
|
|
2921
3631
|
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.`,
|
|
2922
|
-
projectId: effectiveProjectId
|
|
3632
|
+
projectId: effectiveProjectId,
|
|
3633
|
+
workspacePath
|
|
2923
3634
|
};
|
|
2924
3635
|
}
|
|
2925
3636
|
async function handleGetUsageInstructions(agentType) {
|
|
@@ -2950,25 +3661,25 @@ async function handleGetUsageInstructions(agentType) {
|
|
|
2950
3661
|
};
|
|
2951
3662
|
}
|
|
2952
3663
|
const __filename2 = fileURLToPath(import.meta.url);
|
|
2953
|
-
const __dirname2 =
|
|
3664
|
+
const __dirname2 = path4.dirname(__filename2);
|
|
2954
3665
|
const possiblePaths = [
|
|
2955
|
-
|
|
3666
|
+
path4.resolve(__dirname2, "../../prompts"),
|
|
2956
3667
|
// npm installed (dist/tools -> prompts)
|
|
2957
|
-
|
|
3668
|
+
path4.resolve(__dirname2, "../prompts"),
|
|
2958
3669
|
// Alternative npm location
|
|
2959
|
-
|
|
3670
|
+
path4.resolve(__dirname2, "../../../../prompts"),
|
|
2960
3671
|
// Development (monorepo root)
|
|
2961
|
-
|
|
3672
|
+
path4.resolve(__dirname2, "../../../prompts"),
|
|
2962
3673
|
// Alternative dev location
|
|
2963
|
-
|
|
3674
|
+
path4.resolve(process.cwd(), "prompts")
|
|
2964
3675
|
// Relative to cwd
|
|
2965
3676
|
];
|
|
2966
3677
|
let promptFile = null;
|
|
2967
3678
|
let instructions = null;
|
|
2968
3679
|
for (const promptsDir of possiblePaths) {
|
|
2969
|
-
const candidatePath =
|
|
3680
|
+
const candidatePath = path4.join(promptsDir, `${mappedType}.md`);
|
|
2970
3681
|
try {
|
|
2971
|
-
instructions = await
|
|
3682
|
+
instructions = await fs3.readFile(candidatePath, "utf-8");
|
|
2972
3683
|
promptFile = candidatePath;
|
|
2973
3684
|
console.error(`[Overture] Found instructions at: ${candidatePath}`);
|
|
2974
3685
|
break;
|
|
@@ -2987,7 +3698,7 @@ async function handleGetUsageInstructions(agentType) {
|
|
|
2987
3698
|
};
|
|
2988
3699
|
}
|
|
2989
3700
|
console.error(`[Overture] Failed to find instructions for ${mappedType}. Searched paths:`);
|
|
2990
|
-
possiblePaths.forEach((p) => console.error(` - ${
|
|
3701
|
+
possiblePaths.forEach((p) => console.error(` - ${path4.join(p, `${mappedType}.md`)}`));
|
|
2991
3702
|
return {
|
|
2992
3703
|
success: false,
|
|
2993
3704
|
agentType: mappedType,
|
|
@@ -2995,34 +3706,210 @@ async function handleGetUsageInstructions(agentType) {
|
|
|
2995
3706
|
availableAgents
|
|
2996
3707
|
};
|
|
2997
3708
|
}
|
|
3709
|
+
function handleGetNodeInfo(nodeId, projectId, workspacePath) {
|
|
3710
|
+
const effectiveProjectId = projectId || currentProjectId;
|
|
3711
|
+
const nodes = multiProjectPlanStore.getNodes(effectiveProjectId);
|
|
3712
|
+
const nodeConfigs = multiProjectPlanStore.getNodeConfigs(effectiveProjectId);
|
|
3713
|
+
const selectedBranches = multiProjectPlanStore.getSelectedBranches(effectiveProjectId);
|
|
3714
|
+
const node = nodes.find((n) => n.id === nodeId);
|
|
3715
|
+
if (!node) {
|
|
3716
|
+
return {
|
|
3717
|
+
success: false,
|
|
3718
|
+
error: `Node ${nodeId} not found in project ${effectiveProjectId}`,
|
|
3719
|
+
projectId: effectiveProjectId,
|
|
3720
|
+
workspacePath
|
|
3721
|
+
};
|
|
3722
|
+
}
|
|
3723
|
+
const config = nodeConfigs[nodeId] || { fieldValues: {}, attachments: [] };
|
|
3724
|
+
let selectedBranchId;
|
|
3725
|
+
if (node.isBranchPoint) {
|
|
3726
|
+
selectedBranchId = selectedBranches[nodeId];
|
|
3727
|
+
}
|
|
3728
|
+
return {
|
|
3729
|
+
success: true,
|
|
3730
|
+
node: {
|
|
3731
|
+
id: node.id,
|
|
3732
|
+
title: node.title,
|
|
3733
|
+
type: node.type,
|
|
3734
|
+
status: node.status,
|
|
3735
|
+
description: node.description,
|
|
3736
|
+
complexity: node.complexity,
|
|
3737
|
+
expectedOutput: node.expectedOutput,
|
|
3738
|
+
risks: node.risks,
|
|
3739
|
+
fieldValues: config.fieldValues || {},
|
|
3740
|
+
attachments: config.attachments || [],
|
|
3741
|
+
mcpServers: config.mcpServers || [],
|
|
3742
|
+
metaInstructions: config.metaInstructions,
|
|
3743
|
+
isBranchPoint: node.isBranchPoint,
|
|
3744
|
+
branchTargetIds: node.branchTargetIds,
|
|
3745
|
+
selectedBranchId,
|
|
3746
|
+
branchSourceId: node.branchSourceId,
|
|
3747
|
+
output: node.output
|
|
3748
|
+
},
|
|
3749
|
+
projectId: effectiveProjectId,
|
|
3750
|
+
workspacePath
|
|
3751
|
+
};
|
|
3752
|
+
}
|
|
3753
|
+
function handleUpdateNodesDetail(updates, projectId, workspacePath) {
|
|
3754
|
+
const effectiveProjectId = projectId || currentProjectId;
|
|
3755
|
+
const state = multiProjectPlanStore.getState(effectiveProjectId);
|
|
3756
|
+
if (!state) {
|
|
3757
|
+
return {
|
|
3758
|
+
success: false,
|
|
3759
|
+
updatedCount: 0,
|
|
3760
|
+
errors: [`No active plan found for project: ${effectiveProjectId}`],
|
|
3761
|
+
projectId: effectiveProjectId,
|
|
3762
|
+
workspacePath
|
|
3763
|
+
};
|
|
3764
|
+
}
|
|
3765
|
+
const errors = [];
|
|
3766
|
+
let updatedCount = 0;
|
|
3767
|
+
const appliedUpdates = [];
|
|
3768
|
+
for (const update of updates) {
|
|
3769
|
+
const nodeIndex = state.nodes.findIndex((n) => n.id === update.node_id);
|
|
3770
|
+
if (nodeIndex < 0) {
|
|
3771
|
+
errors.push(`Node ${update.node_id} not found`);
|
|
3772
|
+
continue;
|
|
3773
|
+
}
|
|
3774
|
+
const node = state.nodes[nodeIndex];
|
|
3775
|
+
const nodeUpdates = {};
|
|
3776
|
+
if (update.title !== void 0) {
|
|
3777
|
+
node.title = update.title;
|
|
3778
|
+
nodeUpdates.title = update.title;
|
|
3779
|
+
}
|
|
3780
|
+
if (update.description !== void 0) {
|
|
3781
|
+
node.description = update.description;
|
|
3782
|
+
nodeUpdates.description = update.description;
|
|
3783
|
+
}
|
|
3784
|
+
if (update.complexity !== void 0) {
|
|
3785
|
+
node.complexity = update.complexity;
|
|
3786
|
+
nodeUpdates.complexity = update.complexity;
|
|
3787
|
+
}
|
|
3788
|
+
if (update.expectedOutput !== void 0) {
|
|
3789
|
+
node.expectedOutput = update.expectedOutput;
|
|
3790
|
+
nodeUpdates.expectedOutput = update.expectedOutput;
|
|
3791
|
+
}
|
|
3792
|
+
if (update.risks !== void 0) {
|
|
3793
|
+
node.risks = update.risks;
|
|
3794
|
+
nodeUpdates.risks = update.risks;
|
|
3795
|
+
}
|
|
3796
|
+
if (Object.keys(nodeUpdates).length > 0) {
|
|
3797
|
+
appliedUpdates.push({ nodeId: update.node_id, updates: nodeUpdates });
|
|
3798
|
+
updatedCount++;
|
|
3799
|
+
}
|
|
3800
|
+
}
|
|
3801
|
+
if (appliedUpdates.length > 0) {
|
|
3802
|
+
wsManager.broadcastToProject(effectiveProjectId, {
|
|
3803
|
+
type: "nodes_detail_updated",
|
|
3804
|
+
updates: appliedUpdates,
|
|
3805
|
+
projectId: effectiveProjectId
|
|
3806
|
+
});
|
|
3807
|
+
console.error(`[Overture] Updated details for ${updatedCount} node(s) in project ${effectiveProjectId}`);
|
|
3808
|
+
}
|
|
3809
|
+
return {
|
|
3810
|
+
success: errors.length === 0,
|
|
3811
|
+
updatedCount,
|
|
3812
|
+
errors: errors.length > 0 ? errors : void 0,
|
|
3813
|
+
projectId: effectiveProjectId,
|
|
3814
|
+
workspacePath
|
|
3815
|
+
};
|
|
3816
|
+
}
|
|
3817
|
+
function handleUpdateNodeDetail(nodeId, updates, projectId, workspacePath) {
|
|
3818
|
+
const effectiveProjectId = projectId || currentProjectId;
|
|
3819
|
+
const state = multiProjectPlanStore.getState(effectiveProjectId);
|
|
3820
|
+
if (!state) {
|
|
3821
|
+
return {
|
|
3822
|
+
success: false,
|
|
3823
|
+
message: `No active plan found for project: ${effectiveProjectId}`,
|
|
3824
|
+
projectId: effectiveProjectId,
|
|
3825
|
+
workspacePath
|
|
3826
|
+
};
|
|
3827
|
+
}
|
|
3828
|
+
const nodeIndex = state.nodes.findIndex((n) => n.id === nodeId);
|
|
3829
|
+
if (nodeIndex < 0) {
|
|
3830
|
+
return {
|
|
3831
|
+
success: false,
|
|
3832
|
+
message: `Node ${nodeId} not found in project ${effectiveProjectId}`,
|
|
3833
|
+
projectId: effectiveProjectId,
|
|
3834
|
+
workspacePath
|
|
3835
|
+
};
|
|
3836
|
+
}
|
|
3837
|
+
const node = state.nodes[nodeIndex];
|
|
3838
|
+
const nodeUpdates = {};
|
|
3839
|
+
if (updates.title !== void 0) {
|
|
3840
|
+
node.title = updates.title;
|
|
3841
|
+
nodeUpdates.title = updates.title;
|
|
3842
|
+
}
|
|
3843
|
+
if (updates.description !== void 0) {
|
|
3844
|
+
node.description = updates.description;
|
|
3845
|
+
nodeUpdates.description = updates.description;
|
|
3846
|
+
}
|
|
3847
|
+
if (updates.complexity !== void 0) {
|
|
3848
|
+
node.complexity = updates.complexity;
|
|
3849
|
+
nodeUpdates.complexity = updates.complexity;
|
|
3850
|
+
}
|
|
3851
|
+
if (updates.expectedOutput !== void 0) {
|
|
3852
|
+
node.expectedOutput = updates.expectedOutput;
|
|
3853
|
+
nodeUpdates.expectedOutput = updates.expectedOutput;
|
|
3854
|
+
}
|
|
3855
|
+
if (updates.risks !== void 0) {
|
|
3856
|
+
node.risks = updates.risks;
|
|
3857
|
+
nodeUpdates.risks = updates.risks;
|
|
3858
|
+
}
|
|
3859
|
+
if (Object.keys(nodeUpdates).length > 0) {
|
|
3860
|
+
wsManager.broadcastToProject(effectiveProjectId, {
|
|
3861
|
+
type: "node_detail_updated",
|
|
3862
|
+
nodeId,
|
|
3863
|
+
updates: nodeUpdates,
|
|
3864
|
+
projectId: effectiveProjectId
|
|
3865
|
+
});
|
|
3866
|
+
console.error(`[Overture] Updated details for node ${nodeId} in project ${effectiveProjectId}`);
|
|
3867
|
+
}
|
|
3868
|
+
return {
|
|
3869
|
+
success: true,
|
|
3870
|
+
message: `Node ${nodeId} updated successfully`,
|
|
3871
|
+
node: {
|
|
3872
|
+
id: node.id,
|
|
3873
|
+
title: node.title,
|
|
3874
|
+
type: node.type,
|
|
3875
|
+
status: node.status,
|
|
3876
|
+
description: node.description,
|
|
3877
|
+
complexity: node.complexity,
|
|
3878
|
+
expectedOutput: node.expectedOutput,
|
|
3879
|
+
risks: node.risks
|
|
3880
|
+
},
|
|
3881
|
+
projectId: effectiveProjectId,
|
|
3882
|
+
workspacePath
|
|
3883
|
+
};
|
|
3884
|
+
}
|
|
2998
3885
|
|
|
2999
3886
|
// src/http/server.ts
|
|
3000
3887
|
import express from "express";
|
|
3001
|
-
import
|
|
3888
|
+
import path5 from "path";
|
|
3002
3889
|
import { createServer } from "http";
|
|
3003
|
-
import
|
|
3890
|
+
import fs4 from "fs";
|
|
3004
3891
|
import fsp from "fs/promises";
|
|
3005
3892
|
import os2 from "os";
|
|
3006
3893
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
3007
3894
|
var __filename = fileURLToPath2(import.meta.url);
|
|
3008
|
-
var __dirname =
|
|
3895
|
+
var __dirname = path5.dirname(__filename);
|
|
3009
3896
|
function startHttpServer(port) {
|
|
3010
3897
|
const app = express();
|
|
3011
3898
|
app.use(express.json({ limit: "25mb" }));
|
|
3012
3899
|
const possiblePaths = [
|
|
3013
|
-
|
|
3900
|
+
path5.resolve(__dirname, "../ui-dist"),
|
|
3014
3901
|
// packages/mcp-server/dist/../ui-dist
|
|
3015
|
-
|
|
3902
|
+
path5.resolve(__dirname, "../../ui-dist"),
|
|
3016
3903
|
// packages/mcp-server/ui-dist
|
|
3017
|
-
|
|
3904
|
+
path5.resolve(__dirname, "../../../ui/dist"),
|
|
3018
3905
|
// packages/ui/dist
|
|
3019
|
-
|
|
3906
|
+
path5.resolve(process.cwd(), "ui-dist"),
|
|
3020
3907
|
// fallback to cwd
|
|
3021
|
-
|
|
3908
|
+
path5.resolve(process.cwd(), "packages/mcp-server/ui-dist")
|
|
3022
3909
|
];
|
|
3023
3910
|
let staticPath = possiblePaths[0];
|
|
3024
3911
|
for (const p of possiblePaths) {
|
|
3025
|
-
if (
|
|
3912
|
+
if (fs4.existsSync(path5.join(p, "index.html"))) {
|
|
3026
3913
|
staticPath = p;
|
|
3027
3914
|
break;
|
|
3028
3915
|
}
|
|
@@ -3049,17 +3936,46 @@ function startHttpServer(port) {
|
|
|
3049
3936
|
res.status(500).json({ error: "Failed to fetch MCP marketplace" });
|
|
3050
3937
|
}
|
|
3051
3938
|
});
|
|
3939
|
+
app.post("/api/read-file", async (req, res) => {
|
|
3940
|
+
try {
|
|
3941
|
+
const { filePath } = req.body;
|
|
3942
|
+
if (!filePath) {
|
|
3943
|
+
return res.status(400).json({ error: "filePath is required" });
|
|
3944
|
+
}
|
|
3945
|
+
const normalizedPath = path5.normalize(filePath);
|
|
3946
|
+
if (normalizedPath.includes("..") && !path5.isAbsolute(normalizedPath)) {
|
|
3947
|
+
return res.status(400).json({ error: "Invalid file path" });
|
|
3948
|
+
}
|
|
3949
|
+
try {
|
|
3950
|
+
await fsp.access(normalizedPath, fs4.constants.R_OK);
|
|
3951
|
+
} catch {
|
|
3952
|
+
return res.status(404).json({ error: "File not found or not readable" });
|
|
3953
|
+
}
|
|
3954
|
+
const content = await fsp.readFile(normalizedPath, "utf-8");
|
|
3955
|
+
const stats = await fsp.stat(normalizedPath);
|
|
3956
|
+
const lineCount = content.split("\n").length;
|
|
3957
|
+
res.json({
|
|
3958
|
+
content,
|
|
3959
|
+
lineCount,
|
|
3960
|
+
size: stats.size,
|
|
3961
|
+
lastModified: stats.mtime.toISOString()
|
|
3962
|
+
});
|
|
3963
|
+
} catch (error) {
|
|
3964
|
+
console.error("[Overture] Failed to read file:", error);
|
|
3965
|
+
res.status(500).json({ error: "Failed to read file" });
|
|
3966
|
+
}
|
|
3967
|
+
});
|
|
3052
3968
|
app.post("/api/attachments/save", async (req, res) => {
|
|
3053
3969
|
try {
|
|
3054
3970
|
const { fileName, contentBase64 } = req.body;
|
|
3055
3971
|
if (!fileName || !contentBase64) {
|
|
3056
3972
|
return res.status(400).json({ error: "fileName and contentBase64 are required" });
|
|
3057
3973
|
}
|
|
3058
|
-
const safeFileName =
|
|
3059
|
-
const attachmentDir =
|
|
3974
|
+
const safeFileName = path5.basename(fileName).replace(/[^\w.-]/g, "_");
|
|
3975
|
+
const attachmentDir = path5.join(os2.homedir(), ".overture", "attachments");
|
|
3060
3976
|
await fsp.mkdir(attachmentDir, { recursive: true });
|
|
3061
3977
|
const timestamp = Date.now();
|
|
3062
|
-
const absolutePath =
|
|
3978
|
+
const absolutePath = path5.join(attachmentDir, `${timestamp}_${safeFileName}`);
|
|
3063
3979
|
const fileBuffer = Buffer.from(contentBase64, "base64");
|
|
3064
3980
|
await fsp.writeFile(absolutePath, fileBuffer);
|
|
3065
3981
|
res.json({
|
|
@@ -3073,7 +3989,7 @@ function startHttpServer(port) {
|
|
|
3073
3989
|
});
|
|
3074
3990
|
app.use(express.static(staticPath));
|
|
3075
3991
|
app.get("*", (_req, res) => {
|
|
3076
|
-
res.sendFile(
|
|
3992
|
+
res.sendFile(path5.join(staticPath, "index.html"));
|
|
3077
3993
|
});
|
|
3078
3994
|
const server = createServer(app);
|
|
3079
3995
|
server.on("error", (err) => {
|
|
@@ -3102,5 +4018,8 @@ export {
|
|
|
3102
4018
|
handleRequestPlanUpdate,
|
|
3103
4019
|
handleCreateNewPlan,
|
|
3104
4020
|
handleGetUsageInstructions,
|
|
4021
|
+
handleGetNodeInfo,
|
|
4022
|
+
handleUpdateNodesDetail,
|
|
4023
|
+
handleUpdateNodeDetail,
|
|
3105
4024
|
startHttpServer
|
|
3106
4025
|
};
|