winter-super-cli 2026.6.24 → 2026.6.27
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/CHANGELOG.md +28 -5
- package/README.md +85 -0
- package/package.json +5 -1
- package/resources/local/gsap-skills/.claude-plugin/marketplace.json +20 -0
- package/resources/local/gsap-skills/.claude-plugin/plugin.json +6 -0
- package/resources/local/gsap-skills/.cursor-plugin/marketplace.json +13 -0
- package/resources/local/gsap-skills/.cursor-plugin/plugin.json +22 -0
- package/resources/local/gsap-skills/.github/copilot-instructions.md +17 -0
- package/resources/local/gsap-skills/.github/instructions/react.instructions.md +15 -0
- package/resources/local/gsap-skills/.github/instructions/scrolltrigger.instructions.md +18 -0
- package/resources/local/gsap-skills/AGENTS.md +27 -0
- package/resources/local/gsap-skills/CLAUDE.md +1 -0
- package/resources/local/gsap-skills/GEMINI.md +1 -0
- package/resources/local/gsap-skills/LICENSE +21 -0
- package/resources/local/gsap-skills/README.md +163 -0
- package/resources/local/gsap-skills/assets/gsap-green.svg +7 -0
- package/resources/local/gsap-skills/assets/gsap-icon-inverted.svg +15 -0
- package/resources/local/gsap-skills/assets/gsap-icon-square.svg +1 -0
- package/resources/local/gsap-skills/assets/gsap-white.svg +7 -0
- package/resources/local/gsap-skills/examples/README.md +29 -0
- package/resources/local/gsap-skills/examples/nuxt/app/app.vue +3 -0
- package/resources/local/gsap-skills/examples/nuxt/app/composables/useGSAP.ts +91 -0
- package/resources/local/gsap-skills/examples/nuxt/app/pages/index.vue +55 -0
- package/resources/local/gsap-skills/examples/nuxt/nuxt.config.ts +4 -0
- package/resources/local/gsap-skills/examples/nuxt/package.json +18 -0
- package/resources/local/gsap-skills/examples/react/App.jsx +46 -0
- package/resources/local/gsap-skills/examples/react/index.html +12 -0
- package/resources/local/gsap-skills/examples/react/main.jsx +9 -0
- package/resources/local/gsap-skills/examples/react/package.json +21 -0
- package/resources/local/gsap-skills/examples/react/vite.config.js +7 -0
- package/resources/local/gsap-skills/examples/vanilla/index.html +33 -0
- package/resources/local/gsap-skills/examples/vanilla/main.js +36 -0
- package/resources/local/gsap-skills/examples/vue/app.vue +47 -0
- package/resources/local/gsap-skills/examples/vue/index.html +15 -0
- package/resources/local/gsap-skills/examples/vue/main.js +9 -0
- package/resources/local/gsap-skills/examples/vue/package.json +19 -0
- package/resources/local/gsap-skills/examples/vue/vite.config.js +7 -0
- package/resources/local/gsap-skills/skills/gsap-core/SKILL.md +254 -0
- package/resources/local/gsap-skills/skills/gsap-frameworks/SKILL.md +266 -0
- package/resources/local/gsap-skills/skills/gsap-performance/SKILL.md +79 -0
- package/resources/local/gsap-skills/skills/gsap-plugins/SKILL.md +433 -0
- package/resources/local/gsap-skills/skills/gsap-react/SKILL.md +136 -0
- package/resources/local/gsap-skills/skills/gsap-scrolltrigger/SKILL.md +296 -0
- package/resources/local/gsap-skills/skills/gsap-timeline/SKILL.md +107 -0
- package/resources/local/gsap-skills/skills/gsap-utils/SKILL.md +284 -0
- package/resources/local/gsap-skills/skills/llms.txt +39 -0
- package/resources/local/hermes-agent-core/AGENTS.md +1132 -0
- package/resources/local/hermes-agent-core/LICENSE +21 -0
- package/resources/local/hermes-agent-core/README.md +215 -0
- package/resources/local/hermes-agent-core/docs/2026-05-07-s6-overlay-dynamic-subagent-gateways.md +434 -0
- package/resources/local/hermes-agent-core/hermes-already-has-routines.md +160 -0
- package/resources/local/hermes-agent-core/skills/autonomous-ai-agents/DESCRIPTION.md +3 -0
- package/resources/local/hermes-agent-core/skills/autonomous-ai-agents/claude-code/SKILL.md +745 -0
- package/resources/local/hermes-agent-core/skills/autonomous-ai-agents/codex/SKILL.md +130 -0
- package/resources/local/hermes-agent-core/skills/autonomous-ai-agents/hermes-agent/SKILL.md +1021 -0
- package/resources/local/hermes-agent-core/skills/autonomous-ai-agents/kanban-codex-lane/SKILL.md +277 -0
- package/resources/local/hermes-agent-core/skills/autonomous-ai-agents/kanban-codex-lane/templates/pmb-codex-lane-prompt.md +57 -0
- package/resources/local/hermes-agent-core/skills/autonomous-ai-agents/opencode/SKILL.md +219 -0
- package/resources/local/hermes-agent-core/skills/github/DESCRIPTION.md +3 -0
- package/resources/local/hermes-agent-core/skills/github/codebase-inspection/SKILL.md +116 -0
- package/resources/local/hermes-agent-core/skills/github/github-auth/SKILL.md +247 -0
- package/resources/local/hermes-agent-core/skills/github/github-auth/scripts/gh-env.sh +66 -0
- package/resources/local/hermes-agent-core/skills/github/github-code-review/SKILL.md +481 -0
- package/resources/local/hermes-agent-core/skills/github/github-code-review/references/review-output-template.md +74 -0
- package/resources/local/hermes-agent-core/skills/github/github-issues/SKILL.md +370 -0
- package/resources/local/hermes-agent-core/skills/github/github-issues/templates/bug-report.md +35 -0
- package/resources/local/hermes-agent-core/skills/github/github-issues/templates/feature-request.md +31 -0
- package/resources/local/hermes-agent-core/skills/github/github-pr-workflow/SKILL.md +367 -0
- package/resources/local/hermes-agent-core/skills/github/github-pr-workflow/references/ci-troubleshooting.md +183 -0
- package/resources/local/hermes-agent-core/skills/github/github-pr-workflow/references/conventional-commits.md +71 -0
- package/resources/local/hermes-agent-core/skills/github/github-pr-workflow/templates/pr-body-bugfix.md +35 -0
- package/resources/local/hermes-agent-core/skills/github/github-pr-workflow/templates/pr-body-feature.md +33 -0
- package/resources/local/hermes-agent-core/skills/github/github-repo-management/SKILL.md +516 -0
- package/resources/local/hermes-agent-core/skills/github/github-repo-management/references/github-api-cheatsheet.md +161 -0
- package/resources/local/hermes-agent-core/skills/mcp/DESCRIPTION.md +3 -0
- package/resources/local/hermes-agent-core/skills/mcp/native-mcp/SKILL.md +357 -0
- package/resources/local/hermes-agent-core/skills/software-development/debugging-hermes-tui-commands/SKILL.md +152 -0
- package/resources/local/hermes-agent-core/skills/software-development/hermes-agent-skill-authoring/SKILL.md +165 -0
- package/resources/local/hermes-agent-core/skills/software-development/hermes-s6-container-supervision/SKILL.md +176 -0
- package/resources/local/hermes-agent-core/skills/software-development/node-inspect-debugger/SKILL.md +319 -0
- package/resources/local/hermes-agent-core/skills/software-development/plan/SKILL.md +58 -0
- package/resources/local/hermes-agent-core/skills/software-development/python-debugpy/SKILL.md +375 -0
- package/resources/local/hermes-agent-core/skills/software-development/requesting-code-review/SKILL.md +280 -0
- package/resources/local/hermes-agent-core/skills/software-development/spike/SKILL.md +197 -0
- package/resources/local/hermes-agent-core/skills/software-development/subagent-driven-development/SKILL.md +352 -0
- package/resources/local/hermes-agent-core/skills/software-development/subagent-driven-development/references/context-budget-discipline.md +53 -0
- package/resources/local/hermes-agent-core/skills/software-development/subagent-driven-development/references/gates-taxonomy.md +93 -0
- package/resources/local/hermes-agent-core/skills/software-development/systematic-debugging/SKILL.md +367 -0
- package/resources/local/hermes-agent-core/skills/software-development/test-driven-development/SKILL.md +343 -0
- package/resources/local/hermes-agent-core/skills/software-development/writing-plans/SKILL.md +297 -0
- package/resources/local/manifest.json +12 -0
- package/rule.md +2 -0
- package/scripts/audit-pack.js +5 -0
- package/scripts/smoke-browser.js +53 -0
- package/scripts/smoke-package.js +38 -4
- package/skill.md +36 -4
- package/skills/gsap.md +26 -0
- package/skills/hermes-agent.md +17 -0
- package/src/agent/agent-definitions.js +4 -4
- package/src/agent/runtime.js +179 -5
- package/src/agent/subagent-child.js +44 -0
- package/src/ai/capability-scorecard.js +193 -14
- package/src/ai/hermes-core.js +77 -0
- package/src/ai/model-capabilities.js +42 -2
- package/src/ai/prompts/system-prompt.js +18 -2
- package/src/ai/small-model-amplifier.js +35 -7
- package/src/ai/workflow-selector.js +22 -1
- package/src/cli/commands.js +46 -2
- package/src/cli/config.js +45 -6
- package/src/cli/context-loader.js +253 -9
- package/src/cli/conversation-format.js +5 -0
- package/src/cli/input-controller.js +79 -10
- package/src/cli/prompt-builder.js +47 -8
- package/src/cli/repl-commands.js +115 -0
- package/src/cli/repl.js +343 -85
- package/src/cli/slash-commands.js +4 -2
- package/src/cli/tui.js +133 -37
- package/src/mcp/client.js +54 -11
- package/src/mcp/presets.js +114 -0
- package/src/tools/agent.js +316 -25
- package/src/tools/executor.js +412 -12
- package/src/tools/permission.js +20 -17
- package/winter.d.ts +112 -10
package/src/cli/repl.js
CHANGED
|
@@ -22,6 +22,8 @@ import { SessionManager } from '../session/manager.js';
|
|
|
22
22
|
import { AIProviderManager } from '../ai/providers.js';
|
|
23
23
|
import { ConfigLoader } from './config.js';
|
|
24
24
|
import { PermissionManager } from '../tools/permission.js';
|
|
25
|
+
import { MCPClient } from '../mcp/client.js';
|
|
26
|
+
import { getMcpPreset, upsertMcpServer } from '../mcp/presets.js';
|
|
25
27
|
import { compressConversation } from '../context/compress.js';
|
|
26
28
|
import { getToolUsageSummary } from '../tools/analytics.js';
|
|
27
29
|
import { SweAgent } from '../agent/swe-agent.js';
|
|
@@ -425,6 +427,7 @@ export class WinterREPL {
|
|
|
425
427
|
const shouldDrop = (entry) => {
|
|
426
428
|
const text = typeof entry === 'string' ? entry : entry?.text || '';
|
|
427
429
|
return text.startsWith('[Required local resources]')
|
|
430
|
+
|| text.startsWith('[Auto-loaded resource application profile]')
|
|
428
431
|
|| text.startsWith('[Auto-applied skills]')
|
|
429
432
|
|| text.startsWith('[Project rule file')
|
|
430
433
|
|| text.startsWith('[Startup local resource index]')
|
|
@@ -440,7 +443,11 @@ export class WinterREPL {
|
|
|
440
443
|
`awesome-design-md: ${resourcePaths.designs}`,
|
|
441
444
|
`karpathy-tools: ${resourcePaths.karpathy}`,
|
|
442
445
|
`page-agent: ${resourcePaths.pageAgent}`,
|
|
446
|
+
`hermes-agent-core: ${resourcePaths.hermesAgentCore}`,
|
|
447
|
+
`gsap-skills: ${resourcePaths.gsapSkills}`,
|
|
443
448
|
`ecc: ${resourcePaths.ecc}`,
|
|
449
|
+
`codex: ${resourcePaths.codex.root}`,
|
|
450
|
+
`claude: ${resourcePaths.claude.root}`,
|
|
444
451
|
];
|
|
445
452
|
await this.session.replaceMemory(
|
|
446
453
|
'[Startup local resource index]',
|
|
@@ -669,8 +676,8 @@ export class WinterREPL {
|
|
|
669
676
|
}
|
|
670
677
|
}
|
|
671
678
|
|
|
672
|
-
await this.bootstrapProjectCapabilities();
|
|
673
679
|
await this.compactStartupMemories({ projectInstructionFiles, autoCreateDocs });
|
|
680
|
+
await this.bootstrapProjectCapabilities();
|
|
674
681
|
|
|
675
682
|
// Codebase Index is intentionally lazy/on-demand. Top-tier coding agents do
|
|
676
683
|
// not make first-run chat pay for a repo-wide scan, and this also prevents
|
|
@@ -716,6 +723,7 @@ export class WinterREPL {
|
|
|
716
723
|
this._pasteTimer = null;
|
|
717
724
|
this._isPasteChunk = false;
|
|
718
725
|
this._pasteChunkTimer = null;
|
|
726
|
+
this._pasteCounter = 0;
|
|
719
727
|
const PASTE_DELAY = 80;
|
|
720
728
|
|
|
721
729
|
process.stdin.on('data', (chunk) => {
|
|
@@ -734,7 +742,7 @@ export class WinterREPL {
|
|
|
734
742
|
}
|
|
735
743
|
});
|
|
736
744
|
|
|
737
|
-
const flushPasteBuffer = () => {
|
|
745
|
+
const flushPasteBuffer = async () => {
|
|
738
746
|
this._pasteTimer = null;
|
|
739
747
|
if (this._pasteBuffer.length === 0) return;
|
|
740
748
|
|
|
@@ -770,6 +778,19 @@ export class WinterREPL {
|
|
|
770
778
|
this._multilineBuffer.push(...this._pasteBuffer);
|
|
771
779
|
this._pasteBuffer = [];
|
|
772
780
|
|
|
781
|
+
if (this.shouldAutoPersistBufferedPaste(this._multilineBuffer, { isPasteChunk: this._isPasteChunk })) {
|
|
782
|
+
const pastedText = this._multilineBuffer.join('\n').trimEnd();
|
|
783
|
+
if (this.shouldPersistPastedText(pastedText)) {
|
|
784
|
+
const paste = await this.persistPastedText(pastedText);
|
|
785
|
+
this._multilineBuffer = [];
|
|
786
|
+
this._isPasteChunk = false;
|
|
787
|
+
const reference = this.formatPastedTextReference(paste);
|
|
788
|
+
console.log(`${colors.cyan}│ ${colors.dim}${reference}${colors.reset}`);
|
|
789
|
+
this.submitInputQueue(reference);
|
|
790
|
+
return;
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
|
|
773
794
|
// If they pressed Enter on an empty line, submit the multiline buffer!
|
|
774
795
|
if (isJustEmptyEnter && this._multilineBuffer.length > 1) {
|
|
775
796
|
// Remove the trailing empty line
|
|
@@ -786,6 +807,14 @@ export class WinterREPL {
|
|
|
786
807
|
return;
|
|
787
808
|
}
|
|
788
809
|
|
|
810
|
+
if (this.shouldPersistPastedText(combined)) {
|
|
811
|
+
const paste = await this.persistPastedText(combined);
|
|
812
|
+
const reference = this.formatPastedTextReference(paste);
|
|
813
|
+
console.log(`${colors.cyan}│ ${colors.dim}${reference}${colors.reset}`);
|
|
814
|
+
this.submitInputQueue(reference);
|
|
815
|
+
return;
|
|
816
|
+
}
|
|
817
|
+
|
|
789
818
|
this.submitInputQueue(combined);
|
|
790
819
|
return;
|
|
791
820
|
}
|
|
@@ -814,7 +843,14 @@ export class WinterREPL {
|
|
|
814
843
|
this.rl.on('line', (line) => {
|
|
815
844
|
this._pasteBuffer.push(line);
|
|
816
845
|
if (this._pasteTimer) clearTimeout(this._pasteTimer);
|
|
817
|
-
this._pasteTimer = setTimeout(
|
|
846
|
+
this._pasteTimer = setTimeout(() => {
|
|
847
|
+
void flushPasteBuffer().catch((error) => {
|
|
848
|
+
this._pasteBuffer = [];
|
|
849
|
+
this._multilineBuffer = [];
|
|
850
|
+
console.log(`\n${colors.red}? Paste error: ${error.message}${colors.reset}\n`);
|
|
851
|
+
if (this.running && !this.readlineClosed) this.showInputPrompt();
|
|
852
|
+
});
|
|
853
|
+
}, PASTE_DELAY);
|
|
818
854
|
});
|
|
819
855
|
|
|
820
856
|
this.rl.on('close', async () => {
|
|
@@ -861,6 +897,55 @@ export class WinterREPL {
|
|
|
861
897
|
return this.inputController.handleDirectClipboardPaste();
|
|
862
898
|
}
|
|
863
899
|
|
|
900
|
+
getPasteStorageDir() {
|
|
901
|
+
return path.join(this.config?.winterDir || path.join(homedir(), '.winter'), 'pastes');
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
normalizePastedText(text = '') {
|
|
905
|
+
return String(text || '')
|
|
906
|
+
.replace(/\x1b\[200~/g, '')
|
|
907
|
+
.replace(/\x1b\[201~/g, '')
|
|
908
|
+
.replace(/\r\n/g, '\n')
|
|
909
|
+
.replace(/\r/g, '\n');
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
getPastedTextLineCount(text = '') {
|
|
913
|
+
const value = this.normalizePastedText(text);
|
|
914
|
+
if (!value) return 0;
|
|
915
|
+
return value.split(/\r\n|\r|\n/).length;
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
shouldPersistPastedText(text = '', { minLines = 8, minChars = 4000 } = {}) {
|
|
919
|
+
const value = this.normalizePastedText(text);
|
|
920
|
+
return this.getPastedTextLineCount(value) >= minLines || value.length >= minChars;
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
shouldAutoPersistBufferedPaste(buffer = [], { isPasteChunk = false } = {}) {
|
|
924
|
+
if (!Array.isArray(buffer) || buffer.length === 0) return false;
|
|
925
|
+
const manualMultiline = buffer[0] === '';
|
|
926
|
+
const text = this.normalizePastedText(buffer.join('\n')).trimEnd();
|
|
927
|
+
return Boolean(text) && this.shouldPersistPastedText(text) && (isPasteChunk || !manualMultiline);
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
async persistPastedText(text = '') {
|
|
931
|
+
const content = this.normalizePastedText(text);
|
|
932
|
+
const dir = this.getPasteStorageDir();
|
|
933
|
+
await fs.mkdir(dir, { recursive: true });
|
|
934
|
+
this._pasteCounter = Number(this._pasteCounter || 0) + 1;
|
|
935
|
+
const filePath = path.join(dir, `paste_${this._pasteCounter}_${Date.now()}.txt`);
|
|
936
|
+
await fs.writeFile(filePath, content, 'utf8');
|
|
937
|
+
return {
|
|
938
|
+
index: this._pasteCounter,
|
|
939
|
+
path: filePath,
|
|
940
|
+
lines: this.getPastedTextLineCount(content),
|
|
941
|
+
chars: content.length,
|
|
942
|
+
};
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
formatPastedTextReference(paste = {}) {
|
|
946
|
+
return `[Pasted text #${paste.index || 1}: ${paste.lines || 0} lines -> ${paste.path}]`;
|
|
947
|
+
}
|
|
948
|
+
|
|
864
949
|
buildInputPanel() {
|
|
865
950
|
return this.inputController.buildInputPanel();
|
|
866
951
|
}
|
|
@@ -1123,6 +1208,19 @@ export class WinterREPL {
|
|
|
1123
1208
|
return;
|
|
1124
1209
|
}
|
|
1125
1210
|
|
|
1211
|
+
if (!input.startsWith('/')) {
|
|
1212
|
+
const browserShortcut = this.resolveBrowserShortcut(input);
|
|
1213
|
+
if (browserShortcut) {
|
|
1214
|
+
await this.handleOpenBrowserIntent(input, browserShortcut);
|
|
1215
|
+
return;
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
if (!input.startsWith('/') && this.isOpenBrowserIntent(input)) {
|
|
1220
|
+
await this.handleOpenBrowserIntent(input);
|
|
1221
|
+
return;
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1126
1224
|
// Parse @-symbols for non-command input
|
|
1127
1225
|
if (!input.startsWith('/')) {
|
|
1128
1226
|
const canUseHeavyContext = await this.shouldUseHeavyProjectContext();
|
|
@@ -1196,6 +1294,84 @@ export class WinterREPL {
|
|
|
1196
1294
|
}
|
|
1197
1295
|
}
|
|
1198
1296
|
|
|
1297
|
+
isOpenBrowserIntent(input = '') {
|
|
1298
|
+
const raw = String(input || '').trim();
|
|
1299
|
+
if (!raw) return false;
|
|
1300
|
+
const text = `${raw.toLowerCase()}\n${this.normalizeIntentText(raw).toLowerCase()}`;
|
|
1301
|
+
return /\b(mo|open|launch|start)\b.*\b(chrome|browser|trinh duyet|google chrome)\b/i.test(text)
|
|
1302
|
+
|| /\b(chrome|browser|trinh duyet|google chrome)\b.*\b(mo|open|launch|start)\b/i.test(text);
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1305
|
+
resolveBrowserShortcut(input = '') {
|
|
1306
|
+
const raw = String(input || '').trim();
|
|
1307
|
+
if (!raw) return null;
|
|
1308
|
+
const normalized = this.normalizeIntentText(raw).toLowerCase();
|
|
1309
|
+
const text = `${raw.toLowerCase()}\n${normalized}`;
|
|
1310
|
+
|
|
1311
|
+
const url = this.extractUrlFromText(raw);
|
|
1312
|
+
if (url && /\b(mo|open|launch|start|browse)\b/i.test(normalized)) {
|
|
1313
|
+
return { url, label: url };
|
|
1314
|
+
}
|
|
1315
|
+
|
|
1316
|
+
const knownSites = [
|
|
1317
|
+
{ pattern: /\b(youtube music|yt music|music youtube)\b/i, url: 'https://music.youtube.com', label: 'YouTube Music' },
|
|
1318
|
+
{ pattern: /\b(youtube|you tube)\b/i, url: 'https://www.youtube.com', label: 'YouTube' },
|
|
1319
|
+
{ pattern: /\b(spotify)\b/i, url: 'https://open.spotify.com', label: 'Spotify' },
|
|
1320
|
+
{ pattern: /\b(google)\b/i, url: 'https://www.google.com', label: 'Google' },
|
|
1321
|
+
];
|
|
1322
|
+
|
|
1323
|
+
if (/\b(mo|open|launch|start)\b/i.test(normalized)) {
|
|
1324
|
+
const site = knownSites.find(item => item.pattern.test(text));
|
|
1325
|
+
if (site) return site;
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1328
|
+
if (/\b(tim|search|kiem)\b/i.test(normalized) && /\b(chrome|google|browser|trinh duyet)\b/i.test(normalized)) {
|
|
1329
|
+
const query = this.extractBrowserSearchQuery(raw);
|
|
1330
|
+
if (query) {
|
|
1331
|
+
return {
|
|
1332
|
+
url: `https://www.google.com/search?${new URLSearchParams({ q: query }).toString()}`,
|
|
1333
|
+
label: `Google search: ${query}`,
|
|
1334
|
+
};
|
|
1335
|
+
}
|
|
1336
|
+
}
|
|
1337
|
+
|
|
1338
|
+
return null;
|
|
1339
|
+
}
|
|
1340
|
+
|
|
1341
|
+
extractBrowserSearchQuery(input = '') {
|
|
1342
|
+
let query = String(input || '').trim();
|
|
1343
|
+
query = query.replace(/^\s*(tìm|tim|search|kiếm|kiem)\s+/i, '');
|
|
1344
|
+
query = query.replace(/\s+(trên|tren|on)\s+(chrome|google|browser|trình duyệt|trinh duyet)\b.*$/i, '');
|
|
1345
|
+
query = query.replace(/\s+(đi|di)\s*$/i, '');
|
|
1346
|
+
query = query.trim();
|
|
1347
|
+
return query || null;
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1350
|
+
extractUrlFromText(input = '') {
|
|
1351
|
+
const match = String(input || '').match(/https?:\/\/[^\s]+/i);
|
|
1352
|
+
return match ? match[0].replace(/[),.;]+$/g, '') : null;
|
|
1353
|
+
}
|
|
1354
|
+
|
|
1355
|
+
async handleOpenBrowserIntent(input = '', options = {}) {
|
|
1356
|
+
const url = options.url || this.extractUrlFromText(input) || 'about:blank';
|
|
1357
|
+
const result = await this.tools.execute('OpenBrowser', { browser: 'chrome', url }, { cwd: this.projectPath });
|
|
1358
|
+
await this.session?.addToHistory?.({ role: 'user', content: input });
|
|
1359
|
+
|
|
1360
|
+
if (result?.success === false) {
|
|
1361
|
+
const message = `Không mở được Chrome: ${result.error || 'unknown error'}`;
|
|
1362
|
+
console.log(`${colors.red}${message}${colors.reset}`);
|
|
1363
|
+
if (result.recovery) console.log(`${colors.dim}${result.recovery}${colors.reset}`);
|
|
1364
|
+
await this.session?.addToHistory?.({ role: 'assistant', content: message });
|
|
1365
|
+
return result;
|
|
1366
|
+
}
|
|
1367
|
+
|
|
1368
|
+
const label = options.label || url;
|
|
1369
|
+
const message = `Đã mở Chrome${url && url !== 'about:blank' ? `: ${label}` : '.'}`;
|
|
1370
|
+
console.log(`${colors.green}${message}${colors.reset}`);
|
|
1371
|
+
await this.session?.addToHistory?.({ role: 'assistant', content: message });
|
|
1372
|
+
return result;
|
|
1373
|
+
}
|
|
1374
|
+
|
|
1199
1375
|
showSmartTip(input = '') {
|
|
1200
1376
|
const text = input.toLowerCase();
|
|
1201
1377
|
let tip = null;
|
|
@@ -1395,7 +1571,7 @@ export class WinterREPL {
|
|
|
1395
1571
|
|
|
1396
1572
|
CRITICAL DEBUG/AGENT RULES:
|
|
1397
1573
|
1. Inspect the project before changing anything. Read the failing file, related caller, config, and logs.
|
|
1398
|
-
2. Reproduce or locate the first hard failure. For frontend/runtime UI issues, use
|
|
1574
|
+
2. Reproduce or locate the first hard failure. For frontend/runtime UI issues, use chrome-devtools MCP in visible Chrome when a URL/dev server is available; use BrowserDebug only as a headless fallback.
|
|
1399
1575
|
3. Patch the smallest root cause with Write/Edit.
|
|
1400
1576
|
4. Run the closest verification command(s): ${verifyCommands.join(' && ')}.
|
|
1401
1577
|
5. If verification fails, read the new error, patch again, and run verification again.
|
|
@@ -1651,14 +1827,14 @@ ${colors.reset}
|
|
|
1651
1827
|
case 'review':
|
|
1652
1828
|
return byName(['Read', 'Grep', 'Glob', 'Bash', 'WebFetch']);
|
|
1653
1829
|
case 'debug':
|
|
1654
|
-
return byName(['Read', 'Write', 'Edit', 'Bash', 'Grep', 'Glob', 'BrowserDebug', 'WebFetch', 'Parallel']);
|
|
1830
|
+
return byName(['Read', 'Write', 'Edit', 'Bash', 'Grep', 'Glob', 'OpenBrowser', 'VisibleBrowser', 'BrowserDebug', 'WebFetch', 'MCP', 'Parallel']);
|
|
1655
1831
|
case 'research':
|
|
1656
1832
|
return byName(['Read', 'Grep', 'Glob', 'WebFetch', 'WebSearch', 'Parallel']);
|
|
1657
1833
|
case 'design':
|
|
1658
1834
|
case 'ui':
|
|
1659
|
-
return byName(['Read', 'Write', 'Edit', 'Bash', 'Grep', 'Glob', 'BrowserDebug', 'WebFetch']);
|
|
1835
|
+
return byName(['Read', 'Write', 'Edit', 'Bash', 'Grep', 'Glob', 'OpenBrowser', 'VisibleBrowser', 'BrowserDebug', 'WebFetch', 'MCP']);
|
|
1660
1836
|
default:
|
|
1661
|
-
return byName(['Read', 'Write', 'Edit', 'Bash', 'Glob', 'Grep', 'BrowserDebug', 'WebFetch', 'WebSearch', 'Parallel', 'Agent']);
|
|
1837
|
+
return byName(['Read', 'Write', 'Edit', 'Bash', 'Glob', 'Grep', 'OpenBrowser', 'VisibleBrowser', 'BrowserDebug', 'WebFetch', 'WebSearch', 'MCP', 'Parallel', 'Agent', 'DelegateTask', 'ParallelAgent']);
|
|
1662
1838
|
}
|
|
1663
1839
|
}
|
|
1664
1840
|
|
|
@@ -1783,6 +1959,8 @@ ${colors.reset}
|
|
|
1783
1959
|
const pureQuestionPattern = /^(what|why|how|when|where|is|are|can|could|should|would|explain|describe|tell me|compare|giải thích|mô tả|so sánh|tai sao|vi sao|la gi|co nen|co phai|tại sao|vì sao|là gì|có nên|có phải|nhu the nao|như thế nào|khi nào)\b/i;
|
|
1784
1960
|
|
|
1785
1961
|
if (pureQuestionPattern.test(text) && !actionPattern.test(text)) return false;
|
|
1962
|
+
|
|
1963
|
+
if (this.isBrowserInteractionRequest(rawText)) return true;
|
|
1786
1964
|
|
|
1787
1965
|
// Even without explicit target, some verbs are strong enough on their own
|
|
1788
1966
|
const strongActionAlone = /\b(fix|debug|deploy|build|test|commit|install|run|refactor|sửa|chạy|cài|triển khai|xây dựng)\b/i;
|
|
@@ -1812,6 +1990,15 @@ ${colors.reset}
|
|
|
1812
1990
|
return true;
|
|
1813
1991
|
}
|
|
1814
1992
|
|
|
1993
|
+
isBrowserInteractionRequest(text = '') {
|
|
1994
|
+
const raw = String(text || '');
|
|
1995
|
+
const normalized = this.normalizeIntentText(raw);
|
|
1996
|
+
const combined = `${raw}\n${normalized}`;
|
|
1997
|
+
const action = /\b(click|fill|submit|press|select|navigate|bam|dien|chon|nhan|vao|bấm|điền|chọn|nhấn|vào)\b/i;
|
|
1998
|
+
const target = /\b(url|http|https|site|website|web|page|form|button|link|chrome|browser|trang|nut|nút|dang ky|dang nhap|khach hang|khách hàng|đăng ký|đăng nhập)\b/i;
|
|
1999
|
+
return action.test(combined) && target.test(combined);
|
|
2000
|
+
}
|
|
2001
|
+
|
|
1815
2002
|
detectFakeCompletion(content = '') {
|
|
1816
2003
|
const text = String(content || '').toLowerCase();
|
|
1817
2004
|
if (!text.trim()) return false;
|
|
@@ -1820,6 +2007,9 @@ ${colors.reset}
|
|
|
1820
2007
|
const fakeCompletionClaims = /(?:đã (?:sửa|tạo|viết|xóa|cập nhật|thêm|chỉnh|xong|hoàn thành|fix|update|edit|write|create|delete|remove|modify|change|apply|deploy|push)|i(?:'ve| have) (?:fixed|created|written|updated|added|modified|changed|edited|applied|deployed|deleted|removed|patched|implemented|refactored)|done!|xong rồi|hoàn thành|đã hoàn tất|hoàn tất|the (?:fix|change|update|edit|modification) (?:has been|is) (?:applied|done|completed|made)|here(?:'s| is) the (?:fix|update|change|solution|implementation|code)|file (?:has been|was) (?:updated|created|modified|written|changed)|changes? (?:have been|has been|were) (?:made|applied|saved)|successfully (?:updated|created|modified|fixed|applied|changed|written))/i;
|
|
1821
2008
|
if (fakeCompletionClaims.test(text)) return true;
|
|
1822
2009
|
|
|
2010
|
+
const fakeBrowserClaims = /(?:đã|da|i(?:'ve| have))\s+(?:bấm|bam|click(?:ed)?|mở|mo|open(?:ed)?|điền|dien|fill(?:ed)?|chọn|chon|select(?:ed)?|submit(?:ted)?|vào|vao|navigate(?:d)?)/i;
|
|
2011
|
+
if (fakeBrowserClaims.test(text)) return true;
|
|
2012
|
+
|
|
1823
2013
|
// Detect code blocks that pretend to show "changes" without tool use
|
|
1824
2014
|
const codeBlockWithFilePath = /```[\s\S]*?(?:[\/\\][\w.-]+\.(?:js|ts|py|css|html|json|md|jsx|tsx|vue|go|rs|java|c|cpp|rb|sh))[\s\S]*?```/i;
|
|
1825
2015
|
const claimsFileChange = /(?:here(?:'s| is)|below|sau đây|dưới đây|như sau|updated|modified|changed|new|fixed)/i;
|
|
@@ -1830,6 +2020,19 @@ ${colors.reset}
|
|
|
1830
2020
|
|
|
1831
2021
|
buildToolEvidenceCorrection(messages = []) {
|
|
1832
2022
|
const request = this.getLatestUserText(messages);
|
|
2023
|
+
if (this.isBrowserInteractionRequest(request)) {
|
|
2024
|
+
return [
|
|
2025
|
+
'RUNTIME ENFORCEMENT: Your previous response was BLOCKED because you claimed a browser/web action without real browser tool evidence.',
|
|
2026
|
+
'',
|
|
2027
|
+
'For browser interaction tasks you MUST use tools, not prose:',
|
|
2028
|
+
'1. If chrome-devtools MCP is configured, call MCP {"server":"chrome-devtools","tool":"list"} first if needed.',
|
|
2029
|
+
'2. Use chrome-devtools MCP tools such as new_page/navigate_page, take_snapshot, click, fill/fill_form, evaluate_script, list_network_requests, and take_screenshot in visible Chrome.',
|
|
2030
|
+
'3. Use WebFetch only for static text extraction. WebFetch cannot click buttons, fill forms, or preserve page state. Use VisibleBrowser when MCP is unavailable and the user needs visible browser control. BrowserDebug is headless and should be fallback only.',
|
|
2031
|
+
'4. Do not say "đã bấm", "đã điền", "đã mở", or "đã kiểm tra" until a browser/MCP tool result proves it.',
|
|
2032
|
+
'',
|
|
2033
|
+
`Original user request: ${request}`,
|
|
2034
|
+
].join('\n');
|
|
2035
|
+
}
|
|
1833
2036
|
return [
|
|
1834
2037
|
'⚠️ RUNTIME ENFORCEMENT: Your previous response was BLOCKED because you did not use any tool.',
|
|
1835
2038
|
'',
|
|
@@ -1842,7 +2045,7 @@ ${colors.reset}
|
|
|
1842
2045
|
'DO NOT say "I have updated/created/fixed" without a tool call proving it.',
|
|
1843
2046
|
'DO NOT describe what you would do. Actually DO IT with tool calls.',
|
|
1844
2047
|
'',
|
|
1845
|
-
'Available tools: Read, Write, Edit, Bash, Glob, Grep, BrowserDebug, WebFetch, WebSearch.',
|
|
2048
|
+
'Available tools: Read, Write, Edit, Bash, Glob, Grep, OpenBrowser, VisibleBrowser, BrowserDebug, WebFetch, WebSearch, MCP.',
|
|
1846
2049
|
'',
|
|
1847
2050
|
'If native tool calls are not supported, output exactly one fallback tool call:',
|
|
1848
2051
|
'<invoke name="Read"><parameter name="path">README.md</parameter></invoke>',
|
|
@@ -1864,6 +2067,10 @@ ${colors.reset}
|
|
|
1864
2067
|
|
|
1865
2068
|
const hints = [];
|
|
1866
2069
|
|
|
2070
|
+
if (/\b(click|fill|submit|press|select|navigate|bam|dien|chon|nhan|vao|bấm|điền|chọn|nhấn|vào)\b/i.test(text) && /\b(web|website|page|url|http|chrome|browser|form|button|link|trang|nut|nút|dang ky|dang nhap|khach hang|đăng ký|đăng nhập|khách hàng)\b/i.test(text)) {
|
|
2071
|
+
hints.push('TOOL HINT: This is a live browser interaction. Do NOT use WebFetch alone. Prefer visible Chrome via MCP {"server":"chrome-devtools","tool":"list"} then chrome-devtools tools new_page/navigate_page, take_snapshot, click, fill/fill_form, evaluate_script, list_network_requests, and take_screenshot. If MCP is unavailable, call VisibleBrowser for real visible Puppeteer control. Only use BrowserDebug as headless fallback. Only claim click/fill/navigation after MCP, VisibleBrowser, or BrowserDebug evidence.');
|
|
2072
|
+
}
|
|
2073
|
+
|
|
1867
2074
|
// Detect file reading requests
|
|
1868
2075
|
const hasPath = /[A-Za-z]:[\\/][\w.\\/\\-]+/i.test(text) || /(?:^|\s)[.~]?\/[\w.\/-]+/i.test(text);
|
|
1869
2076
|
const readVerbs = /\b(đọc|doc|read|xem|view|mở|open|show|hiện|hiển thị|cat|type)\b/i;
|
|
@@ -1902,7 +2109,11 @@ ${colors.reset}
|
|
|
1902
2109
|
|
|
1903
2110
|
// Detect URL/web requests
|
|
1904
2111
|
if (/\b(https?:\/\/[^\s]+|url|website|trang web|web page)\b/i.test(text)) {
|
|
1905
|
-
hints.push('TOOL HINT: To fetch
|
|
2112
|
+
hints.push('TOOL HINT: To fetch static URL text, call WebFetch. For live visible browser debugging/control, prefer MCP server chrome-devtools with new_page/navigate_page, take_snapshot, click, fill/fill_form, evaluate_script, list_console_messages, list_network_requests, or performance trace tools; use BrowserDebug only as headless fallback.');
|
|
2113
|
+
}
|
|
2114
|
+
|
|
2115
|
+
if (/\b(chrome|devtools|browser debug|debug browser|screenshot|console|network|lcp|performance|perf|web vitals|localhost|127\.0\.0\.1)\b/i.test(text)) {
|
|
2116
|
+
hints.push('TOOL HINT: For "open Chrome" / "mở chrome", call OpenBrowser {"browser":"chrome","url":"about:blank"}. Do NOT call Bash/Get-Command/Start-Process. If configured, use MCP {"server":"chrome-devtools","tool":"list"} for page state, console, network, screenshots, and performance.');
|
|
1906
2117
|
}
|
|
1907
2118
|
|
|
1908
2119
|
if (hints.length === 0) return null;
|
|
@@ -1980,6 +2191,9 @@ ${colors.reset}
|
|
|
1980
2191
|
if ((options?.requireToolEvidence && this.responseNeedsToolEvidence(assistantMsg.content)) || isFakeCompletion) {
|
|
1981
2192
|
return { assistantMsg, toolCalls, finalContent: '', finishReason: 'tool_evidence_required' };
|
|
1982
2193
|
}
|
|
2194
|
+
if (options?.deferFinalContent) {
|
|
2195
|
+
return { assistantMsg, toolCalls, finalContent: assistantMsg.content, finishReason };
|
|
2196
|
+
}
|
|
1983
2197
|
this.printAssistantAnswer(assistantMsg.content, startedAt, totalUsage);
|
|
1984
2198
|
return { assistantMsg, toolCalls, finalContent: assistantMsg.content, finishReason };
|
|
1985
2199
|
}
|
|
@@ -1994,7 +2208,7 @@ ${colors.reset}
|
|
|
1994
2208
|
const toolCallParts = [];
|
|
1995
2209
|
let finishReason = null;
|
|
1996
2210
|
let printed = false;
|
|
1997
|
-
const bufferToolModeContent =
|
|
2211
|
+
const bufferToolModeContent = false;
|
|
1998
2212
|
|
|
1999
2213
|
for await (const chunk of this.ai.streamRequest(messages, options)) {
|
|
2000
2214
|
if (chunk.usage) this.addUsage(totalUsage, chunk.usage);
|
|
@@ -2111,6 +2325,14 @@ ${colors.reset}
|
|
|
2111
2325
|
finishReason: 'tool_evidence_required',
|
|
2112
2326
|
};
|
|
2113
2327
|
}
|
|
2328
|
+
if (options?.deferFinalContent) {
|
|
2329
|
+
return {
|
|
2330
|
+
assistantMsg: { content: visibleContent },
|
|
2331
|
+
toolCalls,
|
|
2332
|
+
finalContent: visibleContent,
|
|
2333
|
+
finishReason,
|
|
2334
|
+
};
|
|
2335
|
+
}
|
|
2114
2336
|
this.printAssistantAnswer(visibleContent, startedAt, totalUsage);
|
|
2115
2337
|
return {
|
|
2116
2338
|
assistantMsg: { content: visibleContent },
|
|
@@ -2147,7 +2369,7 @@ ${colors.reset}
|
|
|
2147
2369
|
'/provider', '/model', '/models', '/providers',
|
|
2148
2370
|
'/theme:toggle', '/tui',
|
|
2149
2371
|
'/auto', '/debug', '/doctor', '/context', '/scorecard', '/swe',
|
|
2150
|
-
'/read', '/write', '/glob', '/grep', '/bash',
|
|
2372
|
+
'/read', '/write', '/glob', '/grep', '/bash', '/browser', '/paste',
|
|
2151
2373
|
'/codex', '/claude', '/karpathy', '/agents',
|
|
2152
2374
|
'/resources', '/designs', '/skills',
|
|
2153
2375
|
'/ecc',
|
|
@@ -2244,6 +2466,9 @@ ${colors.reset}
|
|
|
2244
2466
|
|
|
2245
2467
|
async requestFinalAnswer(messages, toolSummaries, startedAt, totalUsage) {
|
|
2246
2468
|
const executionProfile = this.selectExecutionProfile(messages, { enableTools: false });
|
|
2469
|
+
const latestUserText = this.getLatestUserText(messages);
|
|
2470
|
+
const browserInteraction = this.isBrowserInteractionRequest(latestUserText);
|
|
2471
|
+
const hasBrowserEvidence = toolSummaries.some(summary => /^(MCP|VisibleBrowser|BrowserDebug|OpenBrowser):/i.test(summary));
|
|
2247
2472
|
const finalMessages = [
|
|
2248
2473
|
...messages,
|
|
2249
2474
|
{
|
|
@@ -2256,6 +2481,7 @@ ${colors.reset}
|
|
|
2256
2481
|
'Start with the actual outcome, then mention only the most relevant files/commands. Avoid broad generic advice.',
|
|
2257
2482
|
'If a tool failed, explain the concrete failure briefly and answer with the available evidence.',
|
|
2258
2483
|
'Do not repeat the plan. Do not re-summarize unrelated project context. Do not claim memory/tool state that is not visible in the transcript.',
|
|
2484
|
+
browserInteraction && !hasBrowserEvidence ? 'Important: The user asked for browser interaction, but no MCP/VisibleBrowser/BrowserDebug/OpenBrowser result is available. You must say the browser action was NOT performed; do not claim you clicked, filled, navigated, or inspected pages.' : '',
|
|
2259
2485
|
toolSummaries.length ? `Tool summary:\n${toolSummaries.join('\n')}` : '',
|
|
2260
2486
|
].filter(Boolean).join('\n'),
|
|
2261
2487
|
},
|
|
@@ -2268,7 +2494,7 @@ ${colors.reset}
|
|
|
2268
2494
|
}
|
|
2269
2495
|
|
|
2270
2496
|
if (typeof this.ai.streamRequest === 'function') {
|
|
2271
|
-
return await this.streamFinalAnswer(finalMessages, startedAt, totalUsage, executionProfile);
|
|
2497
|
+
return await this.streamFinalAnswer(finalMessages, startedAt, totalUsage, executionProfile, { browserInteraction, hasBrowserEvidence });
|
|
2272
2498
|
}
|
|
2273
2499
|
|
|
2274
2500
|
const response = await this.ai.sendRequest(finalMessages, {
|
|
@@ -2278,7 +2504,10 @@ ${colors.reset}
|
|
|
2278
2504
|
signal: this.currentAbortController?.signal,
|
|
2279
2505
|
});
|
|
2280
2506
|
this.addUsage(totalUsage, response.usage);
|
|
2281
|
-
|
|
2507
|
+
let content = response.choices?.[0]?.message?.content || '';
|
|
2508
|
+
if (browserInteraction && !hasBrowserEvidence && this.detectFakeCompletion(content)) {
|
|
2509
|
+
content = 'Chưa thực hiện được thao tác trên trình duyệt: lượt này không có bằng chứng từ MCP/VisibleBrowser/BrowserDebug/OpenBrowser, nên Winter chặn câu trả lời để tránh báo sai. Hãy bật chrome-devtools MCP hoặc dùng VisibleBrowser để Winter gọi đúng browser tool.';
|
|
2510
|
+
}
|
|
2282
2511
|
|
|
2283
2512
|
if (this.spinner) this.spinner.stop();
|
|
2284
2513
|
|
|
@@ -2295,7 +2524,7 @@ ${colors.reset}
|
|
|
2295
2524
|
}
|
|
2296
2525
|
}
|
|
2297
2526
|
|
|
2298
|
-
async streamFinalAnswer(messages, startedAt, totalUsage, executionProfile = null) {
|
|
2527
|
+
async streamFinalAnswer(messages, startedAt, totalUsage, executionProfile = null, validation = {}) {
|
|
2299
2528
|
let content = '';
|
|
2300
2529
|
const profile = executionProfile || this.selectExecutionProfile(messages, { enableTools: false });
|
|
2301
2530
|
|
|
@@ -2321,6 +2550,10 @@ ${colors.reset}
|
|
|
2321
2550
|
|
|
2322
2551
|
if (this.spinner) this.spinner.stop();
|
|
2323
2552
|
|
|
2553
|
+
if (validation.browserInteraction && !validation.hasBrowserEvidence && this.detectFakeCompletion(content)) {
|
|
2554
|
+
content = 'Chưa thực hiện được thao tác trên trình duyệt: lượt này không có bằng chứng từ MCP/VisibleBrowser/BrowserDebug/OpenBrowser, nên Winter chặn câu trả lời để tránh báo sai. Hãy bật chrome-devtools MCP hoặc dùng VisibleBrowser để Winter gọi đúng browser tool.';
|
|
2555
|
+
}
|
|
2556
|
+
|
|
2324
2557
|
if (content) {
|
|
2325
2558
|
this.printAssistantAnswer(content, startedAt, totalUsage);
|
|
2326
2559
|
return content;
|
|
@@ -2341,6 +2574,9 @@ ${colors.reset}
|
|
|
2341
2574
|
});
|
|
2342
2575
|
this.addUsage(totalUsage, response.usage);
|
|
2343
2576
|
content = response.choices?.[0]?.message?.content || '';
|
|
2577
|
+
if (validation.browserInteraction && !validation.hasBrowserEvidence && this.detectFakeCompletion(content)) {
|
|
2578
|
+
content = 'Chưa thực hiện được thao tác trên trình duyệt: lượt này không có bằng chứng từ MCP/VisibleBrowser/BrowserDebug/OpenBrowser, nên Winter chặn câu trả lời để tránh báo sai. Hãy bật chrome-devtools MCP hoặc dùng VisibleBrowser để Winter gọi đúng browser tool.';
|
|
2579
|
+
}
|
|
2344
2580
|
if (content) {
|
|
2345
2581
|
this.printAssistantAnswer(content, startedAt, totalUsage);
|
|
2346
2582
|
}
|
|
@@ -2747,7 +2983,7 @@ ${colors.reset}
|
|
|
2747
2983
|
messages.push({ role: 'system', content: toolHint });
|
|
2748
2984
|
}
|
|
2749
2985
|
|
|
2750
|
-
const { finalContent, usedMutatingTools } = await this.runConversation(messages, 'Thinking', tools);
|
|
2986
|
+
const { finalContent, usedMutatingTools, autoVerified } = await this.runConversation(messages, 'Thinking', tools);
|
|
2751
2987
|
|
|
2752
2988
|
const allToolCalls = [];
|
|
2753
2989
|
for (const msg of messages) {
|
|
@@ -2772,7 +3008,7 @@ ${colors.reset}
|
|
|
2772
3008
|
});
|
|
2773
3009
|
|
|
2774
3010
|
// Tự động verify: nếu AI đã dùng tools (sửa code), chạy test/build.
|
|
2775
|
-
if (finalContent && this.shouldAutoVerifyAfterTools(message, usedMutatingTools)) {
|
|
3011
|
+
if (!autoVerified && finalContent && this.shouldAutoVerifyAfterTools(message, usedMutatingTools)) {
|
|
2776
3012
|
const sessionContext = this.session?.getContext?.() || {};
|
|
2777
3013
|
const profile = String(sessionContext.workflowProfile || 'general');
|
|
2778
3014
|
const amplifier = sessionContext.smallModelAmplifier || {};
|
|
@@ -2823,7 +3059,7 @@ ${colors.reset}
|
|
|
2823
3059
|
].filter(Boolean).join('\n');
|
|
2824
3060
|
|
|
2825
3061
|
const amplifier = buildSmallModelAmplification({
|
|
2826
|
-
modelTier: this.
|
|
3062
|
+
modelTier: this.getActiveModelTier(),
|
|
2827
3063
|
workflowProfile: workflow.profile,
|
|
2828
3064
|
depth: workflow.depth,
|
|
2829
3065
|
});
|
|
@@ -2973,35 +3209,10 @@ Do NOT stop until all errors are resolved.`;
|
|
|
2973
3209
|
}
|
|
2974
3210
|
|
|
2975
3211
|
async runAgent(role, task) {
|
|
2976
|
-
const
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
];
|
|
2981
|
-
|
|
2982
|
-
const promptHistory = this.getCompressedPromptHistory({
|
|
2983
|
-
limit: 40,
|
|
2984
|
-
keepRecent: 16,
|
|
2985
|
-
maxTotalChars: 16000,
|
|
2986
|
-
});
|
|
2987
|
-
if (promptHistory.summary) {
|
|
2988
|
-
messages.push({ role: 'system', content: `Compressed prior conversation:\n${promptHistory.summary}` });
|
|
2989
|
-
}
|
|
2990
|
-
for (const entry of promptHistory.entries) {
|
|
2991
|
-
messages.push({ role: entry.role, content: entry.content });
|
|
2992
|
-
}
|
|
2993
|
-
|
|
2994
|
-
messages.push({ role: 'user', content: `Task: ${task}` });
|
|
2995
|
-
|
|
2996
|
-
const agentTools = this.getAgentToolsForDefinition(agentDefinition);
|
|
2997
|
-
const { finalContent, usedMutatingTools } = await this.runConversation(messages, `Subagent [${agentDefinition.id}]`, agentTools);
|
|
2998
|
-
|
|
2999
|
-
await this.session.addToHistory({ role: 'user', content: `[subagent:${agentDefinition.id}] ${task}` });
|
|
3000
|
-
await this.session.addToHistory({ role: 'assistant', content: finalContent });
|
|
3001
|
-
|
|
3002
|
-
if (finalContent && this.shouldAutoVerifyAfterTools(task, usedMutatingTools)) {
|
|
3003
|
-
await this.verifyAndHeal(messages, agentTools, 2);
|
|
3004
|
-
}
|
|
3212
|
+
const result = await this.tools.agentTool.run(task, { role: role || 'general' });
|
|
3213
|
+
await this.session.addToHistory({ role: 'user', content: `[subagent:${result.role || role || 'general'}] ${task}` });
|
|
3214
|
+
await this.session.addToHistory({ role: 'assistant', content: result.finalContent || result.summary || result.error || '' });
|
|
3215
|
+
return result;
|
|
3005
3216
|
}
|
|
3006
3217
|
|
|
3007
3218
|
async listAgentDefinitions() {
|
|
@@ -3278,6 +3489,14 @@ Light mode enabled for safety. Heavy codebase, graph, and git context are skippe
|
|
|
3278
3489
|
await this.session.replaceMemory('[Required local resources]', requiredLocalResources, 'resource');
|
|
3279
3490
|
}
|
|
3280
3491
|
|
|
3492
|
+
const projectInstructionFiles = await this.readProjectInstructionFiles();
|
|
3493
|
+
const resourceApplicationProfile = await this.contextLoader.getResourceApplicationProfile({ projectInstructionFiles });
|
|
3494
|
+
if (resourceApplicationProfile) {
|
|
3495
|
+
await this.session.updateContext('resourceApplicationProfile', resourceApplicationProfile);
|
|
3496
|
+
await this.session.replaceMemory('[Auto-loaded resource application profile]', resourceApplicationProfile, 'resource');
|
|
3497
|
+
this.startupNotice('resource profile loaded');
|
|
3498
|
+
}
|
|
3499
|
+
|
|
3281
3500
|
const skillSnapshot = await this.inferStartupSkills();
|
|
3282
3501
|
await this.session.updateContext('availableSkillCatalog', skillSnapshot.availableSkills);
|
|
3283
3502
|
await this.session.updateContext('activeSkills', skillSnapshot.activeSkills);
|
|
@@ -3289,43 +3508,7 @@ Light mode enabled for safety. Heavy codebase, graph, and git context are skippe
|
|
|
3289
3508
|
}
|
|
3290
3509
|
|
|
3291
3510
|
async inferStartupSkills() {
|
|
3292
|
-
|
|
3293
|
-
const signals = await this.getProjectSignals();
|
|
3294
|
-
const normalizedSignals = new Set(signals.map(value => value.toLowerCase()));
|
|
3295
|
-
|
|
3296
|
-
const hasAny = (...items) => items.some(item => normalizedSignals.has(item));
|
|
3297
|
-
const activeSkills = new Set([
|
|
3298
|
-
'coding',
|
|
3299
|
-
'debug',
|
|
3300
|
-
'refactor',
|
|
3301
|
-
'test',
|
|
3302
|
-
]);
|
|
3303
|
-
|
|
3304
|
-
if (hasAny('react', 'next', 'nextjs', 'tsx', 'jsx', 'vue', 'svelte', 'vite')) {
|
|
3305
|
-
['vercel-react-best-practices', 'web-design-guidelines', 'frontend-design', 'design'].forEach(skill => activeSkills.add(skill));
|
|
3306
|
-
}
|
|
3307
|
-
|
|
3308
|
-
if (hasAny('design', 'ui', 'ux', 'css', 'tailwind', 'styled-components', 'scss', 'style', 'component')) {
|
|
3309
|
-
['web-design-guidelines', 'frontend-design', 'design'].forEach(skill => activeSkills.add(skill));
|
|
3310
|
-
}
|
|
3311
|
-
|
|
3312
|
-
if (hasAny('claude', 'agent', 'mcp', 'plugin', 'skill', 'automation', 'workflow')) {
|
|
3313
|
-
['skill-creator', 'claude-automation-recommender', 'claude-md-improver', 'agent-development', 'hook-development', 'command-development', 'plugin-dev'].forEach(skill => activeSkills.add(skill));
|
|
3314
|
-
}
|
|
3315
|
-
|
|
3316
|
-
if (hasAny('docs', 'markdown', 'md', 'readme', 'documentation')) {
|
|
3317
|
-
['claude-md-improver', 'docs', 'writing-rules'].forEach(skill => activeSkills.add(skill));
|
|
3318
|
-
}
|
|
3319
|
-
|
|
3320
|
-
if (hasAny('figma', 'design-md', 'brand', 'brand-guidelines', 'style-guide')) {
|
|
3321
|
-
['vibefigma', 'web-design-guidelines'].forEach(skill => activeSkills.add(skill));
|
|
3322
|
-
}
|
|
3323
|
-
|
|
3324
|
-
const filtered = [...activeSkills].filter(skill => catalog.has(skill));
|
|
3325
|
-
return {
|
|
3326
|
-
availableSkills: [...catalog],
|
|
3327
|
-
activeSkills: filtered,
|
|
3328
|
-
};
|
|
3511
|
+
return this.contextLoader.inferStartupSkills();
|
|
3329
3512
|
}
|
|
3330
3513
|
|
|
3331
3514
|
async getStartupSkillCatalog() {
|
|
@@ -3378,6 +3561,8 @@ Light mode enabled for safety. Heavy codebase, graph, and git context are skippe
|
|
|
3378
3561
|
const [action, ...rest] = args;
|
|
3379
3562
|
const config = await this.config.load();
|
|
3380
3563
|
config.mcp = config.mcp || { servers: [] };
|
|
3564
|
+
config.permissions = config.permissions || { allowlist: {} };
|
|
3565
|
+
config.permissions.allowlist = config.permissions.allowlist || { tools: [], commands: [], mcpServers: [] };
|
|
3381
3566
|
|
|
3382
3567
|
switch (action) {
|
|
3383
3568
|
case undefined:
|
|
@@ -3410,10 +3595,33 @@ Light mode enabled for safety. Heavy codebase, graph, and git context are skippe
|
|
|
3410
3595
|
}
|
|
3411
3596
|
config.mcp.servers = (config.mcp.servers || []).filter(server => server.name !== name);
|
|
3412
3597
|
config.mcp.servers.push({ name, command, args: parsedArgs, enabled: true });
|
|
3598
|
+
config.permissions.allowlist.mcpServers = [...new Set([...(config.permissions.allowlist.mcpServers || []), name])];
|
|
3413
3599
|
await this.config.save(config);
|
|
3414
3600
|
console.log(`${colors.green}✓ Added MCP server: ${name}${colors.reset}`);
|
|
3415
3601
|
break;
|
|
3416
3602
|
}
|
|
3603
|
+
case 'preset':
|
|
3604
|
+
case 'install': {
|
|
3605
|
+
const [presetName, ...presetOptions] = rest;
|
|
3606
|
+
if (!presetName) {
|
|
3607
|
+
console.log(`${colors.yellow}Usage: /mcp preset <chrome-devtools> [--isolated] [--headless] [--browser-url <url>]${colors.reset}`);
|
|
3608
|
+
break;
|
|
3609
|
+
}
|
|
3610
|
+
try {
|
|
3611
|
+
const server = getMcpPreset(presetName, presetOptions);
|
|
3612
|
+
upsertMcpServer(config, server);
|
|
3613
|
+
await this.config.save(config);
|
|
3614
|
+
console.log(`${colors.green}OK Installed MCP preset: ${server.name}${colors.reset}`);
|
|
3615
|
+
console.log(` ${colors.dim}${server.command} ${server.args.join(' ')}${colors.reset}`);
|
|
3616
|
+
if (!server.args.includes('--headless')) {
|
|
3617
|
+
console.log(` ${colors.dim}Visible Chrome mode: enabled. Use --headless only for background browser runs.${colors.reset}`);
|
|
3618
|
+
}
|
|
3619
|
+
console.log(` ${colors.dim}Inspect tools with: /mcp tools ${server.name}${colors.reset}`);
|
|
3620
|
+
} catch (error) {
|
|
3621
|
+
console.log(`${colors.red}${error.message}${colors.reset}`);
|
|
3622
|
+
}
|
|
3623
|
+
break;
|
|
3624
|
+
}
|
|
3417
3625
|
case 'remove': {
|
|
3418
3626
|
const name = rest[0];
|
|
3419
3627
|
if (!name) {
|
|
@@ -3421,6 +3629,7 @@ Light mode enabled for safety. Heavy codebase, graph, and git context are skippe
|
|
|
3421
3629
|
break;
|
|
3422
3630
|
}
|
|
3423
3631
|
config.mcp.servers = (config.mcp.servers || []).filter(server => server.name !== name);
|
|
3632
|
+
config.permissions.allowlist.mcpServers = (config.permissions.allowlist.mcpServers || []).filter(server => server !== name);
|
|
3424
3633
|
await this.config.save(config);
|
|
3425
3634
|
console.log(`${colors.green}✓ Removed MCP server: ${name}${colors.reset}`);
|
|
3426
3635
|
break;
|
|
@@ -3435,8 +3644,37 @@ Light mode enabled for safety. Heavy codebase, graph, and git context are skippe
|
|
|
3435
3644
|
console.log(`${colors.green}✓ MCP server allowed: ${name}${colors.reset}`);
|
|
3436
3645
|
break;
|
|
3437
3646
|
}
|
|
3647
|
+
case 'tools': {
|
|
3648
|
+
const name = rest[0];
|
|
3649
|
+
if (!name) {
|
|
3650
|
+
console.log(`${colors.yellow}Usage: /mcp tools <name>${colors.reset}`);
|
|
3651
|
+
break;
|
|
3652
|
+
}
|
|
3653
|
+
const server = (config.mcp.servers || []).find(item => item.name === name && item.enabled !== false);
|
|
3654
|
+
if (!server) {
|
|
3655
|
+
console.log(`${colors.red}MCP server not configured or disabled: ${name}${colors.reset}`);
|
|
3656
|
+
break;
|
|
3657
|
+
}
|
|
3658
|
+
const client = new MCPClient(server);
|
|
3659
|
+
try {
|
|
3660
|
+
const tools = await client.listTools();
|
|
3661
|
+
console.log(`${colors.cyan}MCP Tools: ${name}${colors.reset}`);
|
|
3662
|
+
if (!tools.length) {
|
|
3663
|
+
console.log(` ${colors.dim}No tools reported.${colors.reset}`);
|
|
3664
|
+
}
|
|
3665
|
+
tools.forEach(tool => {
|
|
3666
|
+
const description = tool.description ? ` - ${tool.description}` : '';
|
|
3667
|
+
console.log(` ${colors.green}${tool.name}${colors.reset}${description}`);
|
|
3668
|
+
});
|
|
3669
|
+
} catch (error) {
|
|
3670
|
+
console.log(`${colors.red}Failed to list MCP tools: ${error.message}${colors.reset}`);
|
|
3671
|
+
} finally {
|
|
3672
|
+
await client.close();
|
|
3673
|
+
}
|
|
3674
|
+
break;
|
|
3675
|
+
}
|
|
3438
3676
|
default:
|
|
3439
|
-
console.log(`${colors.yellow}Usage: /mcp <list|add|remove|allow>${colors.reset}`);
|
|
3677
|
+
console.log(`${colors.yellow}Usage: /mcp <list|add|preset|install|remove|allow|tools>${colors.reset}`);
|
|
3440
3678
|
}
|
|
3441
3679
|
}
|
|
3442
3680
|
|
|
@@ -3450,6 +3688,9 @@ Light mode enabled for safety. Heavy codebase, graph, and git context are skippe
|
|
|
3450
3688
|
case undefined:
|
|
3451
3689
|
case 'list':
|
|
3452
3690
|
console.log(`${colors.cyan}Permission Allowlist:${colors.reset}`);
|
|
3691
|
+
console.log(` Full access: ${config.sandbox?.enabled === false ? 'on' : 'off'}`);
|
|
3692
|
+
console.log(` Sandbox enabled: ${config.sandbox?.enabled !== false}`);
|
|
3693
|
+
console.log(` Restrict workspace: ${config.sandbox?.restrictToWorkspace !== false}`);
|
|
3453
3694
|
console.log(` Tools: ${(config.permissions.allowlist.tools || []).join(', ') || 'none'}`);
|
|
3454
3695
|
console.log(` Commands: ${(config.permissions.allowlist.commands || []).join(', ') || 'none'}`);
|
|
3455
3696
|
console.log(` MCP Servers: ${(config.permissions.allowlist.mcpServers || []).join(', ') || 'none'}`);
|
|
@@ -3476,8 +3717,25 @@ Light mode enabled for safety. Heavy codebase, graph, and git context are skippe
|
|
|
3476
3717
|
console.log(`${colors.green}✓ Updated prompt policy${colors.reset}`);
|
|
3477
3718
|
break;
|
|
3478
3719
|
}
|
|
3720
|
+
case 'full': {
|
|
3721
|
+
const value = String(rest[0] || 'on').toLowerCase();
|
|
3722
|
+
const enabled = !(value === 'off' || value === 'false' || value === '0' || value === 'no');
|
|
3723
|
+
if (typeof this.config.setFullAccess === 'function') {
|
|
3724
|
+
await this.config.setFullAccess(enabled);
|
|
3725
|
+
} else {
|
|
3726
|
+
config.sandbox = {
|
|
3727
|
+
...(config.sandbox || {}),
|
|
3728
|
+
enabled: !enabled,
|
|
3729
|
+
restrictToWorkspace: !enabled,
|
|
3730
|
+
};
|
|
3731
|
+
config.permissions.promptByDefault = !enabled;
|
|
3732
|
+
await this.config.save(config);
|
|
3733
|
+
}
|
|
3734
|
+
console.log(`${colors.green}Full access ${enabled ? 'enabled' : 'disabled'}${colors.reset}`);
|
|
3735
|
+
break;
|
|
3736
|
+
}
|
|
3479
3737
|
default:
|
|
3480
|
-
console.log(`${colors.yellow}Usage: /permissions <list|allow|prompt>${colors.reset}`);
|
|
3738
|
+
console.log(`${colors.yellow}Usage: /permissions <list|allow|prompt|full>${colors.reset}`);
|
|
3481
3739
|
}
|
|
3482
3740
|
}
|
|
3483
3741
|
|