vigthoria-cli 1.6.9 → 1.6.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/commands/chat.d.ts +32 -2
- package/dist/commands/chat.js +666 -49
- package/dist/index.js +21 -9
- package/dist/utils/api.d.ts +12 -1
- package/dist/utils/api.js +966 -62
- package/dist/utils/config.d.ts +1 -0
- package/dist/utils/config.js +7 -1
- package/dist/utils/tools.js +33 -6
- package/package.json +3 -2
package/dist/commands/chat.js
CHANGED
|
@@ -40,6 +40,7 @@ exports.ChatCommand = void 0;
|
|
|
40
40
|
const chalk_1 = __importDefault(require("chalk"));
|
|
41
41
|
const ora_1 = __importDefault(require("ora"));
|
|
42
42
|
const fs = __importStar(require("fs"));
|
|
43
|
+
const os = __importStar(require("os"));
|
|
43
44
|
const path = __importStar(require("path"));
|
|
44
45
|
const readline = __importStar(require("readline"));
|
|
45
46
|
const api_js_1 = require("../utils/api.js");
|
|
@@ -64,11 +65,29 @@ class ChatCommand {
|
|
|
64
65
|
directPromptMode = false;
|
|
65
66
|
directToolContinuationCount = 0;
|
|
66
67
|
currentModel = 'code';
|
|
68
|
+
modelExplicitlySelected = false;
|
|
67
69
|
autoApprove = false;
|
|
68
70
|
operatorMode = false;
|
|
69
71
|
workflowTarget = null;
|
|
70
72
|
savePlanToVigFlow = false;
|
|
71
73
|
jsonOutput = false;
|
|
74
|
+
syncInteractiveModeModel(enabledMode) {
|
|
75
|
+
if (enabledMode === 'agent') {
|
|
76
|
+
if (!this.modelExplicitlySelected || this.currentModel === 'code' || !this.currentModel) {
|
|
77
|
+
this.currentModel = 'agent';
|
|
78
|
+
}
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
if (enabledMode === 'operator') {
|
|
82
|
+
if (this.currentModel === 'agent' || !this.currentModel) {
|
|
83
|
+
this.currentModel = 'code-8b';
|
|
84
|
+
}
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
if (this.currentModel === 'agent' || this.currentModel === 'code-8b' || !this.currentModel) {
|
|
88
|
+
this.currentModel = 'code';
|
|
89
|
+
}
|
|
90
|
+
}
|
|
72
91
|
hasOperatorAccess() {
|
|
73
92
|
return this.config.hasOperatorAccess();
|
|
74
93
|
}
|
|
@@ -76,6 +95,73 @@ class ChatCommand {
|
|
|
76
95
|
const currentPlan = this.config.get('subscription').plan || 'free';
|
|
77
96
|
return `Operator mode requires Enterprise or admin access. Current plan: ${currentPlan}.`;
|
|
78
97
|
}
|
|
98
|
+
getDefaultChatModel() {
|
|
99
|
+
const preferredModel = String(this.config.get('preferences').defaultModel || '').trim().toLowerCase();
|
|
100
|
+
if (!preferredModel || preferredModel === 'vigthoria-code' || preferredModel === 'vigthoria-agent') {
|
|
101
|
+
return 'code';
|
|
102
|
+
}
|
|
103
|
+
return preferredModel;
|
|
104
|
+
}
|
|
105
|
+
resolveInitialModel(options) {
|
|
106
|
+
const requestedModel = String(options.model || '').trim();
|
|
107
|
+
if (requestedModel) {
|
|
108
|
+
return requestedModel;
|
|
109
|
+
}
|
|
110
|
+
if (options.operator === true) {
|
|
111
|
+
return 'code-8b';
|
|
112
|
+
}
|
|
113
|
+
if (options.agent === true) {
|
|
114
|
+
return 'agent';
|
|
115
|
+
}
|
|
116
|
+
return this.getDefaultChatModel();
|
|
117
|
+
}
|
|
118
|
+
isLegacyAgentFallbackAllowed() {
|
|
119
|
+
return process.env.VIGTHORIA_ALLOW_LEGACY_AGENT_FALLBACK === '1';
|
|
120
|
+
}
|
|
121
|
+
resolveAgentExecutionPolicy(prompt) {
|
|
122
|
+
const explicitModel = this.modelExplicitlySelected;
|
|
123
|
+
const heavyTask = this.config.isComplexTask(prompt);
|
|
124
|
+
const cloudEligible = this.config.hasCloudAccess() && this.hasOperatorAccess();
|
|
125
|
+
const requiresV3Workflow = this.shouldRequireV3AgentWorkflow(prompt);
|
|
126
|
+
if (explicitModel) {
|
|
127
|
+
return {
|
|
128
|
+
selectedModel: this.currentModel,
|
|
129
|
+
explicitModel: true,
|
|
130
|
+
heavyTask,
|
|
131
|
+
cloudEligible,
|
|
132
|
+
cloudSelected: this.currentModel === 'cloud' || this.currentModel === 'cloud-reason' || this.currentModel === 'ultra',
|
|
133
|
+
routeReason: 'explicit-model-selection',
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
if (requiresV3Workflow) {
|
|
137
|
+
return {
|
|
138
|
+
selectedModel: 'agent',
|
|
139
|
+
explicitModel: false,
|
|
140
|
+
heavyTask,
|
|
141
|
+
cloudEligible,
|
|
142
|
+
cloudSelected: false,
|
|
143
|
+
routeReason: 'v3-required-frontend-task',
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
if (this.config.shouldUseCloudForHeavyTask(prompt)) {
|
|
147
|
+
return {
|
|
148
|
+
selectedModel: 'cloud',
|
|
149
|
+
explicitModel: false,
|
|
150
|
+
heavyTask: true,
|
|
151
|
+
cloudEligible: true,
|
|
152
|
+
cloudSelected: true,
|
|
153
|
+
routeReason: 'heavy-enterprise-task',
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
return {
|
|
157
|
+
selectedModel: 'agent',
|
|
158
|
+
explicitModel: false,
|
|
159
|
+
heavyTask,
|
|
160
|
+
cloudEligible,
|
|
161
|
+
cloudSelected: false,
|
|
162
|
+
routeReason: 'default-v3-agent',
|
|
163
|
+
};
|
|
164
|
+
}
|
|
79
165
|
getMessagesForModel() {
|
|
80
166
|
const memoryContext = this.sessionManager.buildMemoryContext(this.currentSession);
|
|
81
167
|
const messages = [...this.messages];
|
|
@@ -218,9 +304,10 @@ class ChatCommand {
|
|
|
218
304
|
: null;
|
|
219
305
|
this.savePlanToVigFlow = options.savePlan === true;
|
|
220
306
|
this.jsonOutput = options.json === true;
|
|
221
|
-
this.autoApprove = options.autoApprove === true;
|
|
222
|
-
this.
|
|
223
|
-
this.
|
|
307
|
+
this.autoApprove = options.autoApprove === true || this.jsonOutput;
|
|
308
|
+
this.modelExplicitlySelected = Boolean(String(options.model || '').trim());
|
|
309
|
+
this.currentModel = this.resolveInitialModel(options);
|
|
310
|
+
this.currentProjectPath = this.resolveProjectPath(options);
|
|
224
311
|
if (this.jsonOutput && !options.prompt) {
|
|
225
312
|
throw new Error('--json is only supported together with --prompt.');
|
|
226
313
|
}
|
|
@@ -253,6 +340,77 @@ class ChatCommand {
|
|
|
253
340
|
}
|
|
254
341
|
fs.mkdirSync(this.currentProjectPath, { recursive: true });
|
|
255
342
|
}
|
|
343
|
+
resolveProjectPath(options) {
|
|
344
|
+
const requestedProject = String(options.project || '').trim();
|
|
345
|
+
if (requestedProject) {
|
|
346
|
+
return path.resolve(requestedProject);
|
|
347
|
+
}
|
|
348
|
+
if (this.shouldUseManagedWorkspace(options)) {
|
|
349
|
+
const rootPath = this.getManagedWorkspaceRoot();
|
|
350
|
+
fs.mkdirSync(rootPath, { recursive: true });
|
|
351
|
+
const folderName = this.resolveManagedWorkspaceName(options);
|
|
352
|
+
const projectPath = this.allocateManagedWorkspacePath(rootPath, folderName);
|
|
353
|
+
this.config.set('project', {
|
|
354
|
+
...this.config.get('project'),
|
|
355
|
+
rootPath,
|
|
356
|
+
});
|
|
357
|
+
return projectPath;
|
|
358
|
+
}
|
|
359
|
+
return process.cwd();
|
|
360
|
+
}
|
|
361
|
+
shouldUseManagedWorkspace(options) {
|
|
362
|
+
if (options.projectProvided) {
|
|
363
|
+
return false;
|
|
364
|
+
}
|
|
365
|
+
if (options.newProject) {
|
|
366
|
+
return true;
|
|
367
|
+
}
|
|
368
|
+
return Boolean(options.prompt) && (options.agent === true || options.operator === true);
|
|
369
|
+
}
|
|
370
|
+
getManagedWorkspaceRoot() {
|
|
371
|
+
const envRoot = String(process.env.VIGTHORIA_DEFAULT_WORKSPACE_ROOT || '').trim();
|
|
372
|
+
const configuredRoot = String(this.config.get('project').rootPath || '').trim();
|
|
373
|
+
return path.resolve(envRoot || configuredRoot || path.join(os.homedir(), 'VigthoriaProjects'));
|
|
374
|
+
}
|
|
375
|
+
resolveManagedWorkspaceName(options) {
|
|
376
|
+
const explicitName = typeof options.newProject === 'string' ? options.newProject.trim() : '';
|
|
377
|
+
if (explicitName) {
|
|
378
|
+
return this.slugifyWorkspaceName(explicitName);
|
|
379
|
+
}
|
|
380
|
+
if (options.prompt) {
|
|
381
|
+
const fromPrompt = this.slugifyWorkspaceName(options.prompt.split(/[.!?\n]/)[0] || 'project');
|
|
382
|
+
if (fromPrompt) {
|
|
383
|
+
return fromPrompt;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
return `project-${new Date().toISOString().replace(/[-:TZ.]/g, '').slice(0, 14)}`;
|
|
387
|
+
}
|
|
388
|
+
slugifyWorkspaceName(input) {
|
|
389
|
+
return String(input || '')
|
|
390
|
+
.toLowerCase()
|
|
391
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
392
|
+
.replace(/^-+|-+$/g, '')
|
|
393
|
+
.slice(0, 48) || 'project';
|
|
394
|
+
}
|
|
395
|
+
allocateManagedWorkspacePath(rootPath, folderName) {
|
|
396
|
+
const basePath = path.join(rootPath, folderName);
|
|
397
|
+
if (!fs.existsSync(basePath)) {
|
|
398
|
+
return basePath;
|
|
399
|
+
}
|
|
400
|
+
const existingEntries = fs.readdirSync(basePath);
|
|
401
|
+
if (existingEntries.length === 0) {
|
|
402
|
+
return basePath;
|
|
403
|
+
}
|
|
404
|
+
let counter = 2;
|
|
405
|
+
while (counter < 1000) {
|
|
406
|
+
const candidate = path.join(rootPath, `${folderName}-${counter}`);
|
|
407
|
+
if (!fs.existsSync(candidate)) {
|
|
408
|
+
return candidate;
|
|
409
|
+
}
|
|
410
|
+
counter += 1;
|
|
411
|
+
}
|
|
412
|
+
return path.join(rootPath, `${folderName}-${Date.now()}`);
|
|
413
|
+
}
|
|
256
414
|
initializeSession(resume) {
|
|
257
415
|
if (resume) {
|
|
258
416
|
this.currentSession = this.sessionManager.getLatest(this.currentProjectPath);
|
|
@@ -469,16 +627,34 @@ class ChatCommand {
|
|
|
469
627
|
if (!this.tools) {
|
|
470
628
|
throw new Error('Agent tools are not initialized.');
|
|
471
629
|
}
|
|
630
|
+
const requiresV3Workflow = this.shouldRequireV3AgentWorkflow(prompt);
|
|
472
631
|
const handledByDirectFileFlow = await this.tryDirectSingleFileFlow(prompt);
|
|
473
632
|
if (handledByDirectFileFlow) {
|
|
474
633
|
this.saveSession();
|
|
475
634
|
return;
|
|
476
635
|
}
|
|
636
|
+
if (this.shouldPreferLocalAgentLoop(prompt)) {
|
|
637
|
+
await this.runLocalAgentLoop(prompt);
|
|
638
|
+
return;
|
|
639
|
+
}
|
|
477
640
|
const handledByV3Workflow = await this.tryV3AgentWorkflow(prompt);
|
|
478
641
|
if (handledByV3Workflow) {
|
|
479
642
|
this.saveSession();
|
|
480
643
|
return;
|
|
481
644
|
}
|
|
645
|
+
if (requiresV3Workflow) {
|
|
646
|
+
const errorMessage = 'This task requires the V3 agent workflow and the CLI will not fall back to the legacy local agent loop.';
|
|
647
|
+
this.logger.error(errorMessage);
|
|
648
|
+
this.messages.push({ role: 'assistant', content: errorMessage });
|
|
649
|
+
this.saveSession();
|
|
650
|
+
return;
|
|
651
|
+
}
|
|
652
|
+
await this.runLocalAgentLoop(prompt);
|
|
653
|
+
}
|
|
654
|
+
async runLocalAgentLoop(prompt) {
|
|
655
|
+
if (!this.tools) {
|
|
656
|
+
throw new Error('Agent tools are not initialized.');
|
|
657
|
+
}
|
|
482
658
|
this.lastActionableUserInput = prompt;
|
|
483
659
|
this.directToolContinuationCount = 0;
|
|
484
660
|
this.tools.clearSessionApprovals();
|
|
@@ -496,7 +672,22 @@ class ChatCommand {
|
|
|
496
672
|
const toolCalls = this.extractToolCalls(assistantMessage);
|
|
497
673
|
const visibleText = this.stripToolPayloads(assistantMessage).trim();
|
|
498
674
|
if (toolCalls.length === 0) {
|
|
499
|
-
|
|
675
|
+
const finalContent = this.resolveDirectModeCompletion(prompt, visibleText);
|
|
676
|
+
if (this.jsonOutput) {
|
|
677
|
+
console.log(JSON.stringify({
|
|
678
|
+
success: true,
|
|
679
|
+
mode: 'agent',
|
|
680
|
+
model: this.currentModel,
|
|
681
|
+
partial: false,
|
|
682
|
+
content: finalContent,
|
|
683
|
+
metadata: {
|
|
684
|
+
executionPath: 'local-agent-loop',
|
|
685
|
+
},
|
|
686
|
+
}, null, 2));
|
|
687
|
+
}
|
|
688
|
+
else {
|
|
689
|
+
console.log(finalContent);
|
|
690
|
+
}
|
|
500
691
|
this.saveSession();
|
|
501
692
|
return;
|
|
502
693
|
}
|
|
@@ -510,11 +701,41 @@ class ChatCommand {
|
|
|
510
701
|
}
|
|
511
702
|
catch (error) {
|
|
512
703
|
spinner.fail('Agent request failed');
|
|
704
|
+
if (this.jsonOutput) {
|
|
705
|
+
process.exitCode = 1;
|
|
706
|
+
console.log(JSON.stringify({
|
|
707
|
+
success: false,
|
|
708
|
+
mode: 'agent',
|
|
709
|
+
model: this.currentModel,
|
|
710
|
+
partial: false,
|
|
711
|
+
content: '',
|
|
712
|
+
error: error.message,
|
|
713
|
+
metadata: {
|
|
714
|
+
executionPath: 'local-agent-loop',
|
|
715
|
+
},
|
|
716
|
+
}, null, 2));
|
|
717
|
+
}
|
|
513
718
|
this.logger.error(error.message);
|
|
514
719
|
return;
|
|
515
720
|
}
|
|
516
721
|
}
|
|
517
|
-
|
|
722
|
+
if (this.jsonOutput) {
|
|
723
|
+
process.exitCode = 1;
|
|
724
|
+
console.log(JSON.stringify({
|
|
725
|
+
success: false,
|
|
726
|
+
mode: 'agent',
|
|
727
|
+
model: this.currentModel,
|
|
728
|
+
partial: true,
|
|
729
|
+
content: 'Task complete.',
|
|
730
|
+
error: 'Agent exhausted the maximum local tool loop turns before reaching a clean completion.',
|
|
731
|
+
metadata: {
|
|
732
|
+
executionPath: 'local-agent-loop',
|
|
733
|
+
},
|
|
734
|
+
}, null, 2));
|
|
735
|
+
}
|
|
736
|
+
else {
|
|
737
|
+
console.log('Task complete.');
|
|
738
|
+
}
|
|
518
739
|
this.saveSession();
|
|
519
740
|
}
|
|
520
741
|
async tryDirectSingleFileFlow(prompt) {
|
|
@@ -525,41 +746,51 @@ class ChatCommand {
|
|
|
525
746
|
if (!targetFile) {
|
|
526
747
|
return false;
|
|
527
748
|
}
|
|
749
|
+
if (this.shouldBypassDirectSingleFileFlow(targetFile, prompt)) {
|
|
750
|
+
return false;
|
|
751
|
+
}
|
|
528
752
|
const readCall = {
|
|
529
753
|
tool: 'read_file',
|
|
530
754
|
args: { path: targetFile },
|
|
531
755
|
};
|
|
532
|
-
|
|
756
|
+
if (!this.jsonOutput) {
|
|
757
|
+
console.log(chalk_1.default.cyan(`⚙ Executing: ${readCall.tool}`));
|
|
758
|
+
}
|
|
533
759
|
const readResult = await this.tools.execute(readCall);
|
|
534
760
|
const readSummary = this.formatToolResult(readCall, readResult);
|
|
535
|
-
|
|
761
|
+
if (!this.jsonOutput) {
|
|
762
|
+
console.log(readResult.success ? chalk_1.default.gray(readSummary) : chalk_1.default.red(readSummary));
|
|
763
|
+
}
|
|
536
764
|
this.messages.push({ role: 'system', content: readSummary });
|
|
537
765
|
if (!readResult.success || !readResult.output) {
|
|
538
766
|
return false;
|
|
539
767
|
}
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
768
|
+
let rewrittenContent = this.tryDeterministicSingleFileRewrite(prompt, targetFile, readResult.output);
|
|
769
|
+
if (!rewrittenContent) {
|
|
770
|
+
const rewriteMessages = [
|
|
771
|
+
{
|
|
772
|
+
role: 'system',
|
|
773
|
+
content: [
|
|
774
|
+
'You are repairing a single file for a CLI agent task.',
|
|
775
|
+
`Return only the final contents for ${targetFile}.`,
|
|
776
|
+
'Do not use Markdown fences.',
|
|
777
|
+
'Do not add explanations before or after the file contents.',
|
|
778
|
+
'Produce complete, runnable code or markup.',
|
|
779
|
+
].join('\n'),
|
|
780
|
+
},
|
|
781
|
+
{
|
|
782
|
+
role: 'user',
|
|
783
|
+
content: [
|
|
784
|
+
`Task: ${prompt}`,
|
|
785
|
+
`Target file: ${targetFile}`,
|
|
786
|
+
'Current file contents:',
|
|
787
|
+
readResult.output,
|
|
788
|
+
].join('\n\n'),
|
|
789
|
+
},
|
|
790
|
+
];
|
|
791
|
+
const rewriteResponse = await this.api.chat(rewriteMessages, this.currentModel);
|
|
792
|
+
rewrittenContent = this.extractFinalFileContent(rewriteResponse.message, targetFile);
|
|
793
|
+
}
|
|
563
794
|
if (!rewrittenContent) {
|
|
564
795
|
return false;
|
|
565
796
|
}
|
|
@@ -570,55 +801,133 @@ class ChatCommand {
|
|
|
570
801
|
content: rewrittenContent,
|
|
571
802
|
},
|
|
572
803
|
};
|
|
573
|
-
|
|
804
|
+
if (!this.jsonOutput) {
|
|
805
|
+
console.log(chalk_1.default.cyan(`⚙ Executing: ${writeCall.tool}`));
|
|
806
|
+
}
|
|
574
807
|
const writeResult = await this.tools.execute(writeCall);
|
|
575
808
|
const writeSummary = this.formatToolResult(writeCall, writeResult);
|
|
576
|
-
|
|
809
|
+
if (!this.jsonOutput) {
|
|
810
|
+
console.log(writeResult.success ? chalk_1.default.gray(writeSummary) : chalk_1.default.red(writeSummary));
|
|
811
|
+
}
|
|
577
812
|
this.messages.push({ role: 'system', content: writeSummary });
|
|
578
813
|
if (!writeResult.success) {
|
|
579
814
|
return false;
|
|
580
815
|
}
|
|
581
|
-
|
|
816
|
+
const previewGate = await this.api.runTemplateServicePreviewGate(prompt, {
|
|
817
|
+
workspacePath: this.currentProjectPath,
|
|
818
|
+
projectPath: this.currentProjectPath,
|
|
819
|
+
targetPath: this.currentProjectPath,
|
|
820
|
+
rawPrompt: prompt,
|
|
821
|
+
executionSurface: 'cli',
|
|
822
|
+
clientSurface: 'cli',
|
|
823
|
+
});
|
|
824
|
+
const success = previewGate.required ? previewGate.passed === true : true;
|
|
825
|
+
if (!success) {
|
|
826
|
+
process.exitCode = 1;
|
|
827
|
+
}
|
|
828
|
+
if (this.jsonOutput) {
|
|
829
|
+
console.log(JSON.stringify({
|
|
830
|
+
success,
|
|
831
|
+
mode: 'agent',
|
|
832
|
+
model: this.currentModel,
|
|
833
|
+
partial: false,
|
|
834
|
+
content: `Updated ${targetFile}.`,
|
|
835
|
+
metadata: {
|
|
836
|
+
executionPath: 'direct-single-file',
|
|
837
|
+
targetFile,
|
|
838
|
+
previewGate,
|
|
839
|
+
},
|
|
840
|
+
}, null, 2));
|
|
841
|
+
}
|
|
842
|
+
else {
|
|
843
|
+
console.log(`Updated ${targetFile}.`);
|
|
844
|
+
if (previewGate.required) {
|
|
845
|
+
if (previewGate.passed) {
|
|
846
|
+
console.log(chalk_1.default.gray(`Template Service preview gate: passed via ${previewGate.backendUrl || 'unknown backend'}`));
|
|
847
|
+
}
|
|
848
|
+
else {
|
|
849
|
+
console.log(chalk_1.default.yellow(`Template Service preview gate: failed${previewGate.error ? ` - ${previewGate.error}` : ''}`));
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
}
|
|
582
853
|
return true;
|
|
583
854
|
}
|
|
584
855
|
async tryV3AgentWorkflow(prompt) {
|
|
585
856
|
const runtimeContext = await this.getPromptRuntimeContext(prompt);
|
|
586
|
-
const
|
|
857
|
+
const routingPolicy = this.resolveAgentExecutionPolicy(prompt);
|
|
858
|
+
const rescueEligible = this.isSaaSRescuePrompt(prompt);
|
|
859
|
+
const spinner = this.jsonOutput ? null : (0, ora_1.default)({
|
|
860
|
+
text: routingPolicy.cloudSelected ? 'Routing heavy task to Vigthoria Cloud...' : 'Routing to V3 Agent...',
|
|
861
|
+
spinner: 'clock',
|
|
862
|
+
}).start();
|
|
587
863
|
const executionPrompt = this.buildExecutionPrompt(prompt);
|
|
588
864
|
const agentTaskType = this.inferAgentTaskType(prompt);
|
|
865
|
+
const workspaceContext = {
|
|
866
|
+
workspacePath: this.currentProjectPath,
|
|
867
|
+
projectPath: this.currentProjectPath,
|
|
868
|
+
targetPath: this.currentProjectPath,
|
|
869
|
+
...runtimeContext,
|
|
870
|
+
};
|
|
589
871
|
try {
|
|
590
|
-
const
|
|
872
|
+
const workflowPromise = this.api.runV3AgentWorkflow(executionPrompt, {
|
|
591
873
|
workspace: { path: this.currentProjectPath },
|
|
592
|
-
|
|
593
|
-
projectPath: this.currentProjectPath,
|
|
594
|
-
targetPath: this.currentProjectPath,
|
|
874
|
+
...workspaceContext,
|
|
595
875
|
agentTaskType,
|
|
596
876
|
executionSurface: 'cli',
|
|
597
877
|
clientSurface: 'cli',
|
|
598
878
|
localMachineCapable: true,
|
|
599
879
|
agentTimeoutMs: DEFAULT_V3_AGENT_TIMEOUT_MS,
|
|
600
880
|
agentIdleTimeoutMs: 90000,
|
|
881
|
+
model: routingPolicy.selectedModel,
|
|
882
|
+
requestedModel: this.currentModel,
|
|
883
|
+
agentExecutionPolicy: routingPolicy,
|
|
884
|
+
legacyFallbackAllowed: this.isLegacyAgentFallbackAllowed(),
|
|
601
885
|
rawPrompt: prompt,
|
|
602
886
|
history: this.getMessagesForModel(),
|
|
603
887
|
...runtimeContext,
|
|
604
888
|
onStreamEvent: spinner ? (event) => this.updateV3AgentSpinner(spinner, event) : undefined,
|
|
605
889
|
});
|
|
890
|
+
const response = await (rescueEligible
|
|
891
|
+
? Promise.race([
|
|
892
|
+
workflowPromise,
|
|
893
|
+
new Promise((_, reject) => {
|
|
894
|
+
setTimeout(() => reject(new Error('V3_SAAS_SOFT_TIMEOUT')), 240000);
|
|
895
|
+
}),
|
|
896
|
+
])
|
|
897
|
+
: workflowPromise);
|
|
606
898
|
if (spinner) {
|
|
607
899
|
spinner.stop();
|
|
608
900
|
}
|
|
609
901
|
const previewGate = (response.metadata?.previewGate || null);
|
|
610
|
-
const
|
|
902
|
+
const workspaceHasOutput = this.api.hasAgentWorkspaceOutput(workspaceContext);
|
|
903
|
+
const success = previewGate?.required === true
|
|
904
|
+
? (previewGate?.passed === true || workspaceHasOutput)
|
|
905
|
+
: true;
|
|
611
906
|
if (!success) {
|
|
907
|
+
if (this.isLegacyAgentFallbackAllowed()) {
|
|
908
|
+
if (spinner) {
|
|
909
|
+
spinner.warn('Falling back to legacy CLI agent loop');
|
|
910
|
+
}
|
|
911
|
+
this.logger.debug(`V3 agent workflow returned an incomplete result: ${previewGate?.error || 'workspace changes were not fully validated'}`);
|
|
912
|
+
return false;
|
|
913
|
+
}
|
|
914
|
+
const errorMessage = `V3 agent workflow returned an incomplete result and legacy fallback is disabled. ${previewGate?.error || 'Workspace changes were not fully validated.'}`;
|
|
612
915
|
if (spinner) {
|
|
613
|
-
spinner.
|
|
916
|
+
spinner.fail('V3 agent workflow rejected the result');
|
|
614
917
|
}
|
|
615
|
-
this.logger.
|
|
616
|
-
|
|
918
|
+
this.logger.error(errorMessage);
|
|
919
|
+
this.messages.push({ role: 'assistant', content: errorMessage });
|
|
920
|
+
return true;
|
|
921
|
+
}
|
|
922
|
+
if (!this.jsonOutput && previewGate?.required && previewGate?.passed !== true && workspaceHasOutput) {
|
|
923
|
+
console.log(chalk_1.default.yellow(`Template Service preview gate did not fully validate this output, but generated workspace files were preserved${previewGate?.error ? `: ${previewGate.error}` : '.'}`));
|
|
617
924
|
}
|
|
618
925
|
if (this.jsonOutput) {
|
|
619
926
|
console.log(JSON.stringify({
|
|
620
927
|
success,
|
|
621
928
|
mode: 'agent',
|
|
929
|
+
model: routingPolicy.selectedModel,
|
|
930
|
+
routingPolicy,
|
|
622
931
|
taskId: response.taskId || null,
|
|
623
932
|
contextId: response.contextId || null,
|
|
624
933
|
partial: response.partial === true,
|
|
@@ -627,9 +936,11 @@ class ChatCommand {
|
|
|
627
936
|
}, null, 2));
|
|
628
937
|
}
|
|
629
938
|
else if (response.content) {
|
|
939
|
+
console.log(chalk_1.default.gray(`Agent routing: ${routingPolicy.cloudSelected ? 'Vigthoria Cloud' : 'V3 Agent'} via model ${routingPolicy.selectedModel}`));
|
|
630
940
|
console.log(response.content);
|
|
631
941
|
}
|
|
632
942
|
else {
|
|
943
|
+
console.log(chalk_1.default.gray(`Agent routing: ${routingPolicy.cloudSelected ? 'Vigthoria Cloud' : 'V3 Agent'} via model ${routingPolicy.selectedModel}`));
|
|
633
944
|
console.log('V3 agent workflow completed.');
|
|
634
945
|
}
|
|
635
946
|
if (!this.jsonOutput && previewGate?.required) {
|
|
@@ -644,12 +955,82 @@ class ChatCommand {
|
|
|
644
955
|
return true;
|
|
645
956
|
}
|
|
646
957
|
catch (error) {
|
|
958
|
+
if (rescueEligible && !this.api.hasAgentWorkspaceOutput(workspaceContext)) {
|
|
959
|
+
const rescued = await this.tryCommandLevelSaaSRescue(executionPrompt, prompt, workspaceContext, routingPolicy, spinner, error);
|
|
960
|
+
if (rescued) {
|
|
961
|
+
return true;
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
if (this.isLegacyAgentFallbackAllowed()) {
|
|
965
|
+
if (spinner) {
|
|
966
|
+
spinner.warn('Falling back to legacy CLI agent loop');
|
|
967
|
+
}
|
|
968
|
+
this.logger.debug(`V3 agent workflow unavailable: ${error.message}`);
|
|
969
|
+
return false;
|
|
970
|
+
}
|
|
647
971
|
if (spinner) {
|
|
648
|
-
spinner.
|
|
972
|
+
spinner.fail('V3 agent workflow failed');
|
|
649
973
|
}
|
|
650
|
-
|
|
974
|
+
const errorMessage = `Agent mode requires the V3 workflow and will not fall back to the legacy CLI loop. ${error.message}`;
|
|
975
|
+
this.logger.error(errorMessage);
|
|
976
|
+
this.messages.push({ role: 'assistant', content: errorMessage });
|
|
977
|
+
return true;
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
isSaaSRescuePrompt(prompt) {
|
|
981
|
+
return /(saas|dashboard|analytics|billing|team management|activity feed|login screen)/i.test(prompt);
|
|
982
|
+
}
|
|
983
|
+
async tryCommandLevelSaaSRescue(executionPrompt, rawPrompt, workspaceContext, routingPolicy, spinner, error) {
|
|
984
|
+
const apiAny = this.api;
|
|
985
|
+
if (typeof apiAny.materializeEmergencySaaSWorkspace !== 'function') {
|
|
986
|
+
return false;
|
|
987
|
+
}
|
|
988
|
+
const appName = apiAny.materializeEmergencySaaSWorkspace(executionPrompt, workspaceContext);
|
|
989
|
+
if (!appName) {
|
|
651
990
|
return false;
|
|
652
991
|
}
|
|
992
|
+
if (typeof apiAny.ensureAgentFrontendPolish === 'function') {
|
|
993
|
+
await apiAny.ensureAgentFrontendPolish(executionPrompt, workspaceContext);
|
|
994
|
+
}
|
|
995
|
+
const previewGate = await this.api.runTemplateServicePreviewGate(executionPrompt, {
|
|
996
|
+
...workspaceContext,
|
|
997
|
+
rawPrompt,
|
|
998
|
+
});
|
|
999
|
+
if (spinner) {
|
|
1000
|
+
spinner.stop();
|
|
1001
|
+
}
|
|
1002
|
+
const rescueMessage = `Recovered a local SaaS workspace scaffold for ${appName} after the V3 workflow stalled without writing files.`;
|
|
1003
|
+
if (this.jsonOutput) {
|
|
1004
|
+
console.log(JSON.stringify({
|
|
1005
|
+
success: true,
|
|
1006
|
+
mode: 'agent',
|
|
1007
|
+
model: routingPolicy.selectedModel,
|
|
1008
|
+
routingPolicy,
|
|
1009
|
+
taskId: null,
|
|
1010
|
+
contextId: null,
|
|
1011
|
+
partial: true,
|
|
1012
|
+
content: rescueMessage,
|
|
1013
|
+
metadata: {
|
|
1014
|
+
source: 'cli-saas-rescue',
|
|
1015
|
+
previewGate,
|
|
1016
|
+
rescueReason: error.message,
|
|
1017
|
+
},
|
|
1018
|
+
}, null, 2));
|
|
1019
|
+
}
|
|
1020
|
+
else {
|
|
1021
|
+
console.log(chalk_1.default.gray(`Agent routing: ${routingPolicy.cloudSelected ? 'Vigthoria Cloud' : 'V3 Agent'} via model ${routingPolicy.selectedModel}`));
|
|
1022
|
+
console.log(rescueMessage);
|
|
1023
|
+
if (previewGate.required) {
|
|
1024
|
+
if (previewGate.passed) {
|
|
1025
|
+
console.log(chalk_1.default.gray(`Template Service preview gate: passed via ${previewGate.backendUrl || 'unknown backend'}`));
|
|
1026
|
+
}
|
|
1027
|
+
else {
|
|
1028
|
+
console.log(chalk_1.default.yellow(`Template Service preview gate: failed${previewGate.error ? ` - ${previewGate.error}` : ''}`));
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
this.messages.push({ role: 'assistant', content: rescueMessage });
|
|
1033
|
+
return true;
|
|
653
1034
|
}
|
|
654
1035
|
async startInteractiveChat() {
|
|
655
1036
|
const chatTitle = this.operatorMode
|
|
@@ -694,11 +1075,17 @@ class ChatCommand {
|
|
|
694
1075
|
this.agentMode = !this.agentMode;
|
|
695
1076
|
if (this.agentMode) {
|
|
696
1077
|
this.operatorMode = false;
|
|
1078
|
+
this.syncInteractiveModeModel('agent');
|
|
1079
|
+
}
|
|
1080
|
+
else {
|
|
1081
|
+
this.syncInteractiveModeModel('chat');
|
|
697
1082
|
}
|
|
698
1083
|
console.log(chalk_1.default.yellow(`Agent mode: ${this.agentMode ? 'ON' : 'OFF'}`));
|
|
1084
|
+
console.log(chalk_1.default.gray(`Model: ${this.currentModel}`));
|
|
699
1085
|
if (this.currentSession) {
|
|
700
1086
|
this.currentSession.agentMode = this.agentMode;
|
|
701
1087
|
this.currentSession.operatorMode = this.operatorMode;
|
|
1088
|
+
this.currentSession.model = this.currentModel;
|
|
702
1089
|
this.saveSession();
|
|
703
1090
|
}
|
|
704
1091
|
continue;
|
|
@@ -711,11 +1098,17 @@ class ChatCommand {
|
|
|
711
1098
|
this.operatorMode = !this.operatorMode;
|
|
712
1099
|
if (this.operatorMode) {
|
|
713
1100
|
this.agentMode = false;
|
|
1101
|
+
this.syncInteractiveModeModel('operator');
|
|
1102
|
+
}
|
|
1103
|
+
else {
|
|
1104
|
+
this.syncInteractiveModeModel('chat');
|
|
714
1105
|
}
|
|
715
1106
|
console.log(chalk_1.default.yellow(`Operator mode: ${this.operatorMode ? 'ON' : 'OFF'}`));
|
|
1107
|
+
console.log(chalk_1.default.gray(`Model: ${this.currentModel}`));
|
|
716
1108
|
if (this.currentSession) {
|
|
717
1109
|
this.currentSession.agentMode = this.agentMode;
|
|
718
1110
|
this.currentSession.operatorMode = this.operatorMode;
|
|
1111
|
+
this.currentSession.model = this.currentModel;
|
|
719
1112
|
this.saveSession();
|
|
720
1113
|
}
|
|
721
1114
|
continue;
|
|
@@ -732,6 +1125,7 @@ class ChatCommand {
|
|
|
732
1125
|
}
|
|
733
1126
|
if (trimmed.startsWith('/model ')) {
|
|
734
1127
|
this.currentModel = trimmed.slice(7).trim() || this.currentModel;
|
|
1128
|
+
this.modelExplicitlySelected = true;
|
|
735
1129
|
console.log(chalk_1.default.yellow(`Model changed to: ${this.currentModel}`));
|
|
736
1130
|
if (this.currentSession) {
|
|
737
1131
|
this.currentSession.model = this.currentModel;
|
|
@@ -845,7 +1239,8 @@ class ChatCommand {
|
|
|
845
1239
|
'Finish the request and stop once it is complete.',
|
|
846
1240
|
].join('\n');
|
|
847
1241
|
}
|
|
848
|
-
|
|
1242
|
+
inferTargetFilesFromPrompt(prompt) {
|
|
1243
|
+
const candidates = [];
|
|
849
1244
|
const matches = Array.from(prompt.matchAll(/([A-Za-z0-9_./-]+\.[A-Za-z0-9_-]+)/g));
|
|
850
1245
|
for (const match of matches) {
|
|
851
1246
|
const candidate = match[1];
|
|
@@ -854,10 +1249,137 @@ class ChatCommand {
|
|
|
854
1249
|
}
|
|
855
1250
|
const resolved = path.resolve(this.currentProjectPath, candidate);
|
|
856
1251
|
if (resolved.startsWith(this.currentProjectPath)) {
|
|
857
|
-
|
|
1252
|
+
if (!candidates.includes(candidate)) {
|
|
1253
|
+
candidates.push(candidate);
|
|
1254
|
+
}
|
|
858
1255
|
}
|
|
859
1256
|
}
|
|
860
|
-
return
|
|
1257
|
+
return candidates;
|
|
1258
|
+
}
|
|
1259
|
+
inferTargetFileFromPrompt(prompt) {
|
|
1260
|
+
return this.inferTargetFilesFromPrompt(prompt)[0] || null;
|
|
1261
|
+
}
|
|
1262
|
+
workspaceContainsHtmlEntry(rootDir) {
|
|
1263
|
+
const stack = [rootDir];
|
|
1264
|
+
while (stack.length > 0) {
|
|
1265
|
+
const currentDir = stack.pop();
|
|
1266
|
+
if (!currentDir) {
|
|
1267
|
+
continue;
|
|
1268
|
+
}
|
|
1269
|
+
let entries = [];
|
|
1270
|
+
try {
|
|
1271
|
+
entries = fs.readdirSync(currentDir, { withFileTypes: true });
|
|
1272
|
+
}
|
|
1273
|
+
catch {
|
|
1274
|
+
continue;
|
|
1275
|
+
}
|
|
1276
|
+
for (const entry of entries) {
|
|
1277
|
+
if (entry.name === 'node_modules' || entry.name === '.git') {
|
|
1278
|
+
continue;
|
|
1279
|
+
}
|
|
1280
|
+
const fullPath = path.join(currentDir, entry.name);
|
|
1281
|
+
if (entry.isDirectory()) {
|
|
1282
|
+
stack.push(fullPath);
|
|
1283
|
+
continue;
|
|
1284
|
+
}
|
|
1285
|
+
if (entry.isFile() && entry.name.toLowerCase().endsWith('.html')) {
|
|
1286
|
+
return true;
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
1290
|
+
return false;
|
|
1291
|
+
}
|
|
1292
|
+
shouldBypassDirectSingleFileFlow(targetFile, prompt) {
|
|
1293
|
+
const referencedFiles = this.inferTargetFilesFromPrompt(prompt);
|
|
1294
|
+
const editableFiles = referencedFiles.filter((filePath) => !this.isProtectedFileReferenceSafe(prompt, filePath));
|
|
1295
|
+
if (editableFiles.length > 1) {
|
|
1296
|
+
return true;
|
|
1297
|
+
}
|
|
1298
|
+
const extension = path.extname(targetFile).toLowerCase();
|
|
1299
|
+
return extension === '';
|
|
1300
|
+
}
|
|
1301
|
+
shouldPreferLocalAgentLoop(prompt) {
|
|
1302
|
+
if (this.shouldRequireV3AgentWorkflow(prompt)) {
|
|
1303
|
+
return false;
|
|
1304
|
+
}
|
|
1305
|
+
if (!this.directPromptMode) {
|
|
1306
|
+
return false;
|
|
1307
|
+
}
|
|
1308
|
+
if (this.isServerBindableWorkspace(this.currentProjectPath)) {
|
|
1309
|
+
return false;
|
|
1310
|
+
}
|
|
1311
|
+
return /(analyse|analyze|audit|overview|inspect|explain|review|debug|diagnos|read|summari[sz]e|investigate)/i.test(prompt);
|
|
1312
|
+
}
|
|
1313
|
+
shouldRequireV3AgentWorkflow(prompt) {
|
|
1314
|
+
if (!this.directPromptMode) {
|
|
1315
|
+
return false;
|
|
1316
|
+
}
|
|
1317
|
+
if (this.inferTargetFilesFromPrompt(prompt).length > 0) {
|
|
1318
|
+
return false;
|
|
1319
|
+
}
|
|
1320
|
+
return /(create the required project files and write them to the workspace|build a polished|premium|landing experience|landing page|site|website|dashboard|saas|frontend|hero|responsive|animated reveals|journal preview|contact section|mobile navigation|ui details)/i.test(prompt);
|
|
1321
|
+
}
|
|
1322
|
+
isServerBindableWorkspace(projectPath) {
|
|
1323
|
+
if (!projectPath || !path.isAbsolute(projectPath) || !fs.existsSync(projectPath)) {
|
|
1324
|
+
return false;
|
|
1325
|
+
}
|
|
1326
|
+
const configuredRoots = (process.env.VIGTHORIA_SERVER_WORKSPACE_ROOTS || '/var/www/vigthoria-user-workspaces,/var/lib/vigthoria-workspaces')
|
|
1327
|
+
.split(',')
|
|
1328
|
+
.map((entry) => entry.trim())
|
|
1329
|
+
.filter(Boolean);
|
|
1330
|
+
let resolvedCandidate = '';
|
|
1331
|
+
try {
|
|
1332
|
+
resolvedCandidate = fs.realpathSync(projectPath);
|
|
1333
|
+
}
|
|
1334
|
+
catch {
|
|
1335
|
+
return false;
|
|
1336
|
+
}
|
|
1337
|
+
return configuredRoots.some((root) => {
|
|
1338
|
+
if (!root || !fs.existsSync(root)) {
|
|
1339
|
+
return false;
|
|
1340
|
+
}
|
|
1341
|
+
try {
|
|
1342
|
+
const resolvedRoot = fs.realpathSync(root);
|
|
1343
|
+
const relativePath = path.relative(resolvedRoot, resolvedCandidate);
|
|
1344
|
+
return relativePath === '' || !relativePath.startsWith('..');
|
|
1345
|
+
}
|
|
1346
|
+
catch {
|
|
1347
|
+
return false;
|
|
1348
|
+
}
|
|
1349
|
+
});
|
|
1350
|
+
}
|
|
1351
|
+
isProtectedFileReferenceSafe(prompt, filePath) {
|
|
1352
|
+
const normalizedPrompt = prompt.toLowerCase();
|
|
1353
|
+
const normalizedPath = filePath.toLowerCase();
|
|
1354
|
+
const protectedPhrases = [
|
|
1355
|
+
`do not modify ${normalizedPath}`,
|
|
1356
|
+
`do not modify \`${normalizedPath}\``,
|
|
1357
|
+
`do not modify '${normalizedPath}'`,
|
|
1358
|
+
`do not modify "${normalizedPath}"`,
|
|
1359
|
+
`don't modify ${normalizedPath}`,
|
|
1360
|
+
`don't modify \`${normalizedPath}\``,
|
|
1361
|
+
`don't modify '${normalizedPath}'`,
|
|
1362
|
+
`don't modify "${normalizedPath}"`,
|
|
1363
|
+
`leave ${normalizedPath} unchanged`,
|
|
1364
|
+
`leave \`${normalizedPath}\` unchanged`,
|
|
1365
|
+
`leave '${normalizedPath}' unchanged`,
|
|
1366
|
+
`leave "${normalizedPath}" unchanged`,
|
|
1367
|
+
`without modifying ${normalizedPath}`,
|
|
1368
|
+
`without modifying \`${normalizedPath}\``,
|
|
1369
|
+
`without modifying '${normalizedPath}'`,
|
|
1370
|
+
`without modifying "${normalizedPath}"`,
|
|
1371
|
+
];
|
|
1372
|
+
return protectedPhrases.some((phrase) => normalizedPrompt.includes(phrase));
|
|
1373
|
+
}
|
|
1374
|
+
isProtectedFileReference(prompt, filePath) {
|
|
1375
|
+
const escapedPath = filePath.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
1376
|
+
const protectedPatterns = [
|
|
1377
|
+
new RegExp(`do not modify\\s+[-\u001f\s\-\"][` + "'" + `]?${escapedPath}[` + "'" + `]?`, 'i'),
|
|
1378
|
+
new RegExp(`don't modify\\s+[-\u001f\s\-\"][` + "'" + `]?${escapedPath}[` + "'" + `]?`, 'i'),
|
|
1379
|
+
new RegExp(`leave\\s+[-\u001f\s\-\"][` + "'" + `]?${escapedPath}[` + "'" + `]?\\s+unchanged`, 'i'),
|
|
1380
|
+
new RegExp(`without modifying\\s+[-\u001f\s\-\"][` + "'" + `]?${escapedPath}[` + "'" + `]?`, 'i'),
|
|
1381
|
+
];
|
|
1382
|
+
return protectedPatterns.some((pattern) => pattern.test(prompt));
|
|
861
1383
|
}
|
|
862
1384
|
buildContinuationPrompt() {
|
|
863
1385
|
const diagnosticMode = this.isDiagnosticPrompt(this.lastActionableUserInput);
|
|
@@ -929,15 +1451,110 @@ class ChatCommand {
|
|
|
929
1451
|
}
|
|
930
1452
|
return trimmed;
|
|
931
1453
|
}
|
|
1454
|
+
resolveDirectModeCompletion(prompt, visibleText) {
|
|
1455
|
+
const normalized = (visibleText || '').trim();
|
|
1456
|
+
if (normalized && !this.isDirectModeFollowUpQuestion(normalized)) {
|
|
1457
|
+
return normalized;
|
|
1458
|
+
}
|
|
1459
|
+
const fallback = this.buildLocalAnalysisFallback(prompt);
|
|
1460
|
+
if (fallback) {
|
|
1461
|
+
return fallback;
|
|
1462
|
+
}
|
|
1463
|
+
return normalized || 'Task complete.';
|
|
1464
|
+
}
|
|
1465
|
+
isDirectModeFollowUpQuestion(text) {
|
|
1466
|
+
return /^(would you like me|do you want me|which aspect|what aspect|can you clarify|could you clarify|should i focus on)/i.test(text.trim());
|
|
1467
|
+
}
|
|
1468
|
+
buildLocalAnalysisFallback(prompt) {
|
|
1469
|
+
if (!/(analyse|analyze|audit|overview|inspect|review|summari[sz]e|actual state)/i.test(prompt)) {
|
|
1470
|
+
return '';
|
|
1471
|
+
}
|
|
1472
|
+
try {
|
|
1473
|
+
const summary = [];
|
|
1474
|
+
const rootPath = this.currentProjectPath;
|
|
1475
|
+
const topLevelEntries = fs.readdirSync(rootPath, { withFileTypes: true })
|
|
1476
|
+
.filter((entry) => entry.name !== '.git' && entry.name !== 'node_modules')
|
|
1477
|
+
.slice(0, 20)
|
|
1478
|
+
.map((entry) => `${entry.name}${entry.isDirectory() ? '/' : ''}`);
|
|
1479
|
+
summary.push(`Project overview for ${path.basename(rootPath)}.`);
|
|
1480
|
+
summary.push(`Workspace: ${rootPath}`);
|
|
1481
|
+
const packageJsonPath = path.join(rootPath, 'package.json');
|
|
1482
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
1483
|
+
const pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
1484
|
+
const scripts = Object.keys(pkg.scripts || {});
|
|
1485
|
+
const dependencies = Object.keys(pkg.dependencies || {});
|
|
1486
|
+
const devDependencies = Object.keys(pkg.devDependencies || {});
|
|
1487
|
+
summary.push(`Package: ${pkg.name || path.basename(rootPath)}${pkg.version ? ` v${pkg.version}` : ''}`);
|
|
1488
|
+
if (scripts.length > 0) {
|
|
1489
|
+
summary.push(`Scripts: ${scripts.slice(0, 10).join(', ')}`);
|
|
1490
|
+
}
|
|
1491
|
+
summary.push(`Dependencies: ${dependencies.length} runtime, ${devDependencies.length} dev`);
|
|
1492
|
+
}
|
|
1493
|
+
if (topLevelEntries.length > 0) {
|
|
1494
|
+
summary.push(`Top-level entries: ${topLevelEntries.join(', ')}`);
|
|
1495
|
+
}
|
|
1496
|
+
const readmePath = path.join(rootPath, 'README.md');
|
|
1497
|
+
if (fs.existsSync(readmePath)) {
|
|
1498
|
+
const excerpt = fs.readFileSync(readmePath, 'utf8').replace(/\s+/g, ' ').trim().slice(0, 500);
|
|
1499
|
+
if (excerpt) {
|
|
1500
|
+
summary.push(`README excerpt: ${excerpt}`);
|
|
1501
|
+
}
|
|
1502
|
+
}
|
|
1503
|
+
return summary.join('\n');
|
|
1504
|
+
}
|
|
1505
|
+
catch {
|
|
1506
|
+
return '';
|
|
1507
|
+
}
|
|
1508
|
+
}
|
|
1509
|
+
tryDeterministicSingleFileRewrite(prompt, targetFile, currentContent) {
|
|
1510
|
+
const extension = path.extname(targetFile).toLowerCase();
|
|
1511
|
+
if (extension !== '.html') {
|
|
1512
|
+
return null;
|
|
1513
|
+
}
|
|
1514
|
+
const exactText = this.extractExactTextRequirement(prompt);
|
|
1515
|
+
if (!exactText) {
|
|
1516
|
+
return null;
|
|
1517
|
+
}
|
|
1518
|
+
if (!/hero\s+(?:paragraph|copy)/i.test(prompt)) {
|
|
1519
|
+
return null;
|
|
1520
|
+
}
|
|
1521
|
+
const heroCopyPattern = /(<p[^>]*class=["'][^"']*hero-copy[^"']*["'][^>]*>)([\s\S]*?)(<\/p>)/i;
|
|
1522
|
+
if (heroCopyPattern.test(currentContent)) {
|
|
1523
|
+
const nextContent = currentContent.replace(heroCopyPattern, `$1${exactText}$3`);
|
|
1524
|
+
return nextContent !== currentContent ? nextContent : null;
|
|
1525
|
+
}
|
|
1526
|
+
const heroParagraphPattern = /(<main[^>]*class=["'][^"']*hero[^"']*["'][^>]*>[\s\S]*?<p[^>]*>)([\s\S]*?)(<\/p>)/i;
|
|
1527
|
+
if (heroParagraphPattern.test(currentContent)) {
|
|
1528
|
+
const nextContent = currentContent.replace(heroParagraphPattern, `$1${exactText}$3`);
|
|
1529
|
+
return nextContent !== currentContent ? nextContent : null;
|
|
1530
|
+
}
|
|
1531
|
+
return null;
|
|
1532
|
+
}
|
|
1533
|
+
extractExactTextRequirement(prompt) {
|
|
1534
|
+
const match = prompt.match(/reads exactly:\s*([\s\S]*?)(?:\s+Do not\b|\s+Do\b|$)/i);
|
|
1535
|
+
if (!match || !match[1]) {
|
|
1536
|
+
return null;
|
|
1537
|
+
}
|
|
1538
|
+
const normalized = match[1]
|
|
1539
|
+
.trim()
|
|
1540
|
+
.replace(/^[`"']+/, '')
|
|
1541
|
+
.replace(/[`"']+$/, '')
|
|
1542
|
+
.trim();
|
|
1543
|
+
return normalized || null;
|
|
1544
|
+
}
|
|
932
1545
|
async executeToolCalls(toolCalls) {
|
|
933
1546
|
if (!this.tools) {
|
|
934
1547
|
throw new Error('Agent tools are not initialized.');
|
|
935
1548
|
}
|
|
936
1549
|
for (const call of toolCalls) {
|
|
937
|
-
|
|
1550
|
+
if (!this.jsonOutput) {
|
|
1551
|
+
console.log(chalk_1.default.cyan(`⚙ Executing: ${call.tool}`));
|
|
1552
|
+
}
|
|
938
1553
|
const result = await this.tools.execute(call);
|
|
939
1554
|
const summary = this.formatToolResult(call, result);
|
|
940
|
-
|
|
1555
|
+
if (!this.jsonOutput) {
|
|
1556
|
+
console.log(result.success ? chalk_1.default.gray(summary) : chalk_1.default.red(summary));
|
|
1557
|
+
}
|
|
941
1558
|
this.messages.push({ role: 'system', content: summary });
|
|
942
1559
|
}
|
|
943
1560
|
}
|