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.
Files changed (123) hide show
  1. package/CHANGELOG.md +28 -5
  2. package/README.md +85 -0
  3. package/package.json +5 -1
  4. package/resources/local/gsap-skills/.claude-plugin/marketplace.json +20 -0
  5. package/resources/local/gsap-skills/.claude-plugin/plugin.json +6 -0
  6. package/resources/local/gsap-skills/.cursor-plugin/marketplace.json +13 -0
  7. package/resources/local/gsap-skills/.cursor-plugin/plugin.json +22 -0
  8. package/resources/local/gsap-skills/.github/copilot-instructions.md +17 -0
  9. package/resources/local/gsap-skills/.github/instructions/react.instructions.md +15 -0
  10. package/resources/local/gsap-skills/.github/instructions/scrolltrigger.instructions.md +18 -0
  11. package/resources/local/gsap-skills/AGENTS.md +27 -0
  12. package/resources/local/gsap-skills/CLAUDE.md +1 -0
  13. package/resources/local/gsap-skills/GEMINI.md +1 -0
  14. package/resources/local/gsap-skills/LICENSE +21 -0
  15. package/resources/local/gsap-skills/README.md +163 -0
  16. package/resources/local/gsap-skills/assets/gsap-green.svg +7 -0
  17. package/resources/local/gsap-skills/assets/gsap-icon-inverted.svg +15 -0
  18. package/resources/local/gsap-skills/assets/gsap-icon-square.svg +1 -0
  19. package/resources/local/gsap-skills/assets/gsap-white.svg +7 -0
  20. package/resources/local/gsap-skills/examples/README.md +29 -0
  21. package/resources/local/gsap-skills/examples/nuxt/app/app.vue +3 -0
  22. package/resources/local/gsap-skills/examples/nuxt/app/composables/useGSAP.ts +91 -0
  23. package/resources/local/gsap-skills/examples/nuxt/app/pages/index.vue +55 -0
  24. package/resources/local/gsap-skills/examples/nuxt/nuxt.config.ts +4 -0
  25. package/resources/local/gsap-skills/examples/nuxt/package.json +18 -0
  26. package/resources/local/gsap-skills/examples/react/App.jsx +46 -0
  27. package/resources/local/gsap-skills/examples/react/index.html +12 -0
  28. package/resources/local/gsap-skills/examples/react/main.jsx +9 -0
  29. package/resources/local/gsap-skills/examples/react/package.json +21 -0
  30. package/resources/local/gsap-skills/examples/react/vite.config.js +7 -0
  31. package/resources/local/gsap-skills/examples/vanilla/index.html +33 -0
  32. package/resources/local/gsap-skills/examples/vanilla/main.js +36 -0
  33. package/resources/local/gsap-skills/examples/vue/app.vue +47 -0
  34. package/resources/local/gsap-skills/examples/vue/index.html +15 -0
  35. package/resources/local/gsap-skills/examples/vue/main.js +9 -0
  36. package/resources/local/gsap-skills/examples/vue/package.json +19 -0
  37. package/resources/local/gsap-skills/examples/vue/vite.config.js +7 -0
  38. package/resources/local/gsap-skills/skills/gsap-core/SKILL.md +254 -0
  39. package/resources/local/gsap-skills/skills/gsap-frameworks/SKILL.md +266 -0
  40. package/resources/local/gsap-skills/skills/gsap-performance/SKILL.md +79 -0
  41. package/resources/local/gsap-skills/skills/gsap-plugins/SKILL.md +433 -0
  42. package/resources/local/gsap-skills/skills/gsap-react/SKILL.md +136 -0
  43. package/resources/local/gsap-skills/skills/gsap-scrolltrigger/SKILL.md +296 -0
  44. package/resources/local/gsap-skills/skills/gsap-timeline/SKILL.md +107 -0
  45. package/resources/local/gsap-skills/skills/gsap-utils/SKILL.md +284 -0
  46. package/resources/local/gsap-skills/skills/llms.txt +39 -0
  47. package/resources/local/hermes-agent-core/AGENTS.md +1132 -0
  48. package/resources/local/hermes-agent-core/LICENSE +21 -0
  49. package/resources/local/hermes-agent-core/README.md +215 -0
  50. package/resources/local/hermes-agent-core/docs/2026-05-07-s6-overlay-dynamic-subagent-gateways.md +434 -0
  51. package/resources/local/hermes-agent-core/hermes-already-has-routines.md +160 -0
  52. package/resources/local/hermes-agent-core/skills/autonomous-ai-agents/DESCRIPTION.md +3 -0
  53. package/resources/local/hermes-agent-core/skills/autonomous-ai-agents/claude-code/SKILL.md +745 -0
  54. package/resources/local/hermes-agent-core/skills/autonomous-ai-agents/codex/SKILL.md +130 -0
  55. package/resources/local/hermes-agent-core/skills/autonomous-ai-agents/hermes-agent/SKILL.md +1021 -0
  56. package/resources/local/hermes-agent-core/skills/autonomous-ai-agents/kanban-codex-lane/SKILL.md +277 -0
  57. package/resources/local/hermes-agent-core/skills/autonomous-ai-agents/kanban-codex-lane/templates/pmb-codex-lane-prompt.md +57 -0
  58. package/resources/local/hermes-agent-core/skills/autonomous-ai-agents/opencode/SKILL.md +219 -0
  59. package/resources/local/hermes-agent-core/skills/github/DESCRIPTION.md +3 -0
  60. package/resources/local/hermes-agent-core/skills/github/codebase-inspection/SKILL.md +116 -0
  61. package/resources/local/hermes-agent-core/skills/github/github-auth/SKILL.md +247 -0
  62. package/resources/local/hermes-agent-core/skills/github/github-auth/scripts/gh-env.sh +66 -0
  63. package/resources/local/hermes-agent-core/skills/github/github-code-review/SKILL.md +481 -0
  64. package/resources/local/hermes-agent-core/skills/github/github-code-review/references/review-output-template.md +74 -0
  65. package/resources/local/hermes-agent-core/skills/github/github-issues/SKILL.md +370 -0
  66. package/resources/local/hermes-agent-core/skills/github/github-issues/templates/bug-report.md +35 -0
  67. package/resources/local/hermes-agent-core/skills/github/github-issues/templates/feature-request.md +31 -0
  68. package/resources/local/hermes-agent-core/skills/github/github-pr-workflow/SKILL.md +367 -0
  69. package/resources/local/hermes-agent-core/skills/github/github-pr-workflow/references/ci-troubleshooting.md +183 -0
  70. package/resources/local/hermes-agent-core/skills/github/github-pr-workflow/references/conventional-commits.md +71 -0
  71. package/resources/local/hermes-agent-core/skills/github/github-pr-workflow/templates/pr-body-bugfix.md +35 -0
  72. package/resources/local/hermes-agent-core/skills/github/github-pr-workflow/templates/pr-body-feature.md +33 -0
  73. package/resources/local/hermes-agent-core/skills/github/github-repo-management/SKILL.md +516 -0
  74. package/resources/local/hermes-agent-core/skills/github/github-repo-management/references/github-api-cheatsheet.md +161 -0
  75. package/resources/local/hermes-agent-core/skills/mcp/DESCRIPTION.md +3 -0
  76. package/resources/local/hermes-agent-core/skills/mcp/native-mcp/SKILL.md +357 -0
  77. package/resources/local/hermes-agent-core/skills/software-development/debugging-hermes-tui-commands/SKILL.md +152 -0
  78. package/resources/local/hermes-agent-core/skills/software-development/hermes-agent-skill-authoring/SKILL.md +165 -0
  79. package/resources/local/hermes-agent-core/skills/software-development/hermes-s6-container-supervision/SKILL.md +176 -0
  80. package/resources/local/hermes-agent-core/skills/software-development/node-inspect-debugger/SKILL.md +319 -0
  81. package/resources/local/hermes-agent-core/skills/software-development/plan/SKILL.md +58 -0
  82. package/resources/local/hermes-agent-core/skills/software-development/python-debugpy/SKILL.md +375 -0
  83. package/resources/local/hermes-agent-core/skills/software-development/requesting-code-review/SKILL.md +280 -0
  84. package/resources/local/hermes-agent-core/skills/software-development/spike/SKILL.md +197 -0
  85. package/resources/local/hermes-agent-core/skills/software-development/subagent-driven-development/SKILL.md +352 -0
  86. package/resources/local/hermes-agent-core/skills/software-development/subagent-driven-development/references/context-budget-discipline.md +53 -0
  87. package/resources/local/hermes-agent-core/skills/software-development/subagent-driven-development/references/gates-taxonomy.md +93 -0
  88. package/resources/local/hermes-agent-core/skills/software-development/systematic-debugging/SKILL.md +367 -0
  89. package/resources/local/hermes-agent-core/skills/software-development/test-driven-development/SKILL.md +343 -0
  90. package/resources/local/hermes-agent-core/skills/software-development/writing-plans/SKILL.md +297 -0
  91. package/resources/local/manifest.json +12 -0
  92. package/rule.md +2 -0
  93. package/scripts/audit-pack.js +5 -0
  94. package/scripts/smoke-browser.js +53 -0
  95. package/scripts/smoke-package.js +38 -4
  96. package/skill.md +36 -4
  97. package/skills/gsap.md +26 -0
  98. package/skills/hermes-agent.md +17 -0
  99. package/src/agent/agent-definitions.js +4 -4
  100. package/src/agent/runtime.js +179 -5
  101. package/src/agent/subagent-child.js +44 -0
  102. package/src/ai/capability-scorecard.js +193 -14
  103. package/src/ai/hermes-core.js +77 -0
  104. package/src/ai/model-capabilities.js +42 -2
  105. package/src/ai/prompts/system-prompt.js +18 -2
  106. package/src/ai/small-model-amplifier.js +35 -7
  107. package/src/ai/workflow-selector.js +22 -1
  108. package/src/cli/commands.js +46 -2
  109. package/src/cli/config.js +45 -6
  110. package/src/cli/context-loader.js +253 -9
  111. package/src/cli/conversation-format.js +5 -0
  112. package/src/cli/input-controller.js +79 -10
  113. package/src/cli/prompt-builder.js +47 -8
  114. package/src/cli/repl-commands.js +115 -0
  115. package/src/cli/repl.js +343 -85
  116. package/src/cli/slash-commands.js +4 -2
  117. package/src/cli/tui.js +133 -37
  118. package/src/mcp/client.js +54 -11
  119. package/src/mcp/presets.js +114 -0
  120. package/src/tools/agent.js +316 -25
  121. package/src/tools/executor.js +412 -12
  122. package/src/tools/permission.js +20 -17
  123. package/winter.d.ts +112 -10
@@ -5,7 +5,7 @@
5
5
 
6
6
  import { promises as fs } from 'fs';
7
7
  import path from 'path';
8
- import { exec, execFile } from 'child_process';
8
+ import { exec, execFile, spawn } from 'child_process';
9
9
  import { promisify } from 'util';
10
10
  import { diffLines } from 'diff';
11
11
  import { withRetry } from './retry.js';
@@ -30,7 +30,14 @@ export class ToolExecutor {
30
30
  constructor(repl) {
31
31
  this.repl = repl;
32
32
  this.projectPath = repl?.projectPath || process.cwd();
33
- this.allowedCommands = ['git', 'npm', 'node', 'python', 'code', 'pnpm', 'yarn', 'bun', 'pip', 'cargo', 'rustc', 'echo', 'printf', 'cat', 'ls', 'dir', 'type', 'copy', 'mkdir', 'get-childitem', 'set-content', 'get-content', 'test-path', 'get-date'];
33
+ this.allowedCommands = [
34
+ 'git', 'npm', 'npx', 'node', 'python', 'code', 'pnpm', 'yarn', 'bun', 'pip', 'cargo', 'rustc',
35
+ 'echo', 'printf', 'cat', 'ls', 'dir', 'type', 'copy', 'mkdir', 'get-childitem', 'set-content',
36
+ 'get-content', 'test-path', 'get-date',
37
+ 'ping', 'test-connection', 'curl', 'wget', 'iwr', 'irm', 'invoke-webrequest', 'invoke-restmethod',
38
+ 'nslookup', 'resolve-dnsname', 'tracert', 'traceroute', 'pathping', 'dig', 'ipconfig', 'ifconfig',
39
+ 'ip', 'netstat', 'speedtest', 'speedtest-cli', 'measure-command',
40
+ ];
34
41
  this.blockedPatterns = [
35
42
  { pattern: /\brm\s+-[^\n;|&]*r[^\n;|&]*f\b|\brm\s+-[^\n;|&]*f[^\n;|&]*r\b/i, label: 'rm -rf' },
36
43
  { pattern: /\bremove-item\b[^\n;|&]*(?:-recurse\b[^\n;|&]*-force\b|-force\b[^\n;|&]*-recurse\b)/i, label: 'Remove-Item -Recurse -Force' },
@@ -207,11 +214,11 @@ export class ToolExecutor {
207
214
  {
208
215
  type: 'function',
209
216
  name: 'MCP',
210
- description: 'Call a configured MCP server tool by name. Use for external integrations and IDE-like tools. Discover available MCP tools via the MCP tool with server name and tool=list. Also, tools from MCP servers are exposed with mcp__<server>__<tool> naming for direct IDE integration (e.g. mcp__vscode__open_file).',
217
+ description: 'Call a configured MCP server tool by name. Use for external integrations and IDE-like tools. Discover available MCP tools via the MCP tool with server name and tool=list. For live Chrome debugging, use server chrome-devtools with tools such as new_page, navigate_page, take_snapshot, take_screenshot, evaluate_script, list_console_messages, list_network_requests, and performance trace tools. Also, tools from MCP servers are exposed with mcp__<server>__<tool> naming for direct IDE integration (e.g. mcp__vscode__open_file).',
211
218
  parameters: {
212
219
  type: 'object',
213
220
  properties: {
214
- server: { type: 'string', description: 'Configured MCP server name (e.g. vscode)' },
221
+ server: { type: 'string', description: 'Configured MCP server name (e.g. vscode, chrome-devtools)' },
215
222
  tool: { type: 'string', description: 'MCP tool name, or set to "list" to discover all tools from a server' },
216
223
  arguments: { type: 'object', description: 'Tool arguments' },
217
224
  },
@@ -334,18 +341,68 @@ export class ToolExecutor {
334
341
  {
335
342
  type: 'function',
336
343
  name: 'Agent',
337
- description: 'Spawn a subagent to execute a complex task with planning, execution, and verification workflow.',
344
+ description: 'Spawn and await a real isolated subagent conversation with scoped tools, timeout, execution, verification, and result passing.',
338
345
  parameters: {
339
346
  type: 'object',
340
347
  properties: {
341
348
  task: { type: 'string', description: 'Task description for the agent' },
349
+ role: { type: 'string', description: 'Agent role/profile, e.g. general, debug, review, design, research, swe' },
350
+ context: { type: 'string', description: 'Parent context to pass into the subagent' },
351
+ tools: { type: 'array', items: { type: 'string' }, description: 'Optional restricted tool allowlist for this subagent' },
352
+ process_isolation: { type: 'boolean', description: 'Run subagent in a child process. Defaults true in live REPL.' },
342
353
  max_steps: { type: 'number', description: 'Maximum execution steps (default: 10, max: 25)' },
354
+ timeout_ms: { type: 'number', description: 'Timeout for the subagent run' },
343
355
  provider: { type: 'string', description: 'AI provider to use for this agent' },
344
356
  cwd: { type: 'string', description: 'Working directory' },
345
357
  },
346
358
  required: ['task']
347
359
  }
348
360
  },
361
+ {
362
+ type: 'function',
363
+ name: 'DelegateTask',
364
+ description: 'Delegate a task to a real subagent and wait for summary, changed files, tool evidence, usage, and errors. Alias of Agent with clearer delegation semantics.',
365
+ parameters: {
366
+ type: 'object',
367
+ properties: {
368
+ goal: { type: 'string', description: 'Goal or task for the delegated subagent' },
369
+ task: { type: 'string', description: 'Task description for the delegated subagent' },
370
+ role: { type: 'string', description: 'Agent role/profile' },
371
+ context: { type: 'string', description: 'Parent context to pass into the subagent' },
372
+ tools: { type: 'array', items: { type: 'string' }, description: 'Restricted tool allowlist' },
373
+ process_isolation: { type: 'boolean', description: 'Run subagent in a child process. Defaults true in live REPL.' },
374
+ max_steps: { type: 'number', description: 'Maximum execution steps' },
375
+ timeout_ms: { type: 'number', description: 'Timeout for the delegated run' },
376
+ provider: { type: 'string', description: 'AI provider to use for this subagent' },
377
+ cwd: { type: 'string', description: 'Working directory' },
378
+ }
379
+ }
380
+ },
381
+ {
382
+ type: 'function',
383
+ name: 'ParallelAgent',
384
+ description: 'Run multiple real delegated subagents concurrently and aggregate their summaries, changed files, tool evidence, and failures.',
385
+ parameters: {
386
+ type: 'object',
387
+ properties: {
388
+ tasks: {
389
+ type: 'array',
390
+ items: {
391
+ type: 'object',
392
+ properties: {
393
+ goal: { type: 'string' },
394
+ task: { type: 'string' },
395
+ role: { type: 'string' },
396
+ tools: { type: 'array', items: { type: 'string' } },
397
+ }
398
+ }
399
+ },
400
+ concurrency: { type: 'number', description: 'Maximum subagents to run at once, capped at 6' },
401
+ timeout_ms: { type: 'number', description: 'Timeout per subagent' },
402
+ },
403
+ required: ['tasks']
404
+ }
405
+ },
349
406
  {
350
407
  type: 'function',
351
408
  name: 'InsertText',
@@ -388,6 +445,37 @@ export class ToolExecutor {
388
445
  required: ['url']
389
446
  }
390
447
  },
448
+ {
449
+ type: 'function',
450
+ name: 'VisibleBrowser',
451
+ description: 'Launch and control a real visible Chrome/Chromium browser with Puppeteer. Use when the user asks Winter to actually click, type, navigate, inspect, or screenshot a page and chrome-devtools MCP is unavailable. Actions: open, navigate, click, type, evaluate, screenshot, snapshot.',
452
+ parameters: {
453
+ type: 'object',
454
+ properties: {
455
+ url: { type: 'string', description: 'URL to open or navigate to. Defaults to about:blank.' },
456
+ action: { type: 'string', description: 'open, navigate, click, type, evaluate, screenshot, or snapshot' },
457
+ selector: { type: 'string', description: 'CSS selector for click/type/snapshot targeting' },
458
+ text: { type: 'string', description: 'Text to type for action=type' },
459
+ script: { type: 'string', description: 'JavaScript expression/function body for action=evaluate' },
460
+ wait_for: { type: 'string', description: 'Optional CSS selector to wait for after navigation/action' },
461
+ screenshot_path: { type: 'string', description: 'Optional output PNG path for action=screenshot' },
462
+ keep_open: { type: 'boolean', description: 'Leave the browser open after action. Default false.' },
463
+ browser: { type: 'string', description: 'chrome or chromium. Defaults to chrome.' }
464
+ }
465
+ }
466
+ },
467
+ {
468
+ type: 'function',
469
+ name: 'OpenBrowser',
470
+ description: 'Open Chrome or the default browser visibly for the user. Use this for requests like "mở chrome", "open Chrome", or "open this URL in browser". Do not use Bash/Start-Process for this.',
471
+ parameters: {
472
+ type: 'object',
473
+ properties: {
474
+ url: { type: 'string', description: 'URL to open. Defaults to about:blank.' },
475
+ browser: { type: 'string', description: 'chrome or default. Defaults to chrome.' },
476
+ }
477
+ }
478
+ },
391
479
  {
392
480
  type: 'function',
393
481
  name: 'WebFetch',
@@ -532,6 +620,10 @@ export class ToolExecutor {
532
620
  return await this.parallelExecute(input.tools ?? input.calls ?? [], { cwd });
533
621
  case 'BrowserDebug':
534
622
  return await this.browserDebug(input.url ?? input.uri, input.action);
623
+ case 'VisibleBrowser':
624
+ return await this.visibleBrowser(input, cwd);
625
+ case 'OpenBrowser':
626
+ return await this.openBrowser(input.url ?? input.uri ?? input.href, input.browser);
535
627
  case 'WebFetch':
536
628
  return await this.webFetch(input.url ?? input.uri ?? input.href, input.prompt ?? input.query ?? input.extract);
537
629
  case 'WebSearch':
@@ -549,7 +641,35 @@ export class ToolExecutor {
549
641
  case 'AskUserQuestion':
550
642
  return await this.interactiveTool.askQuestion(input.questions ?? input.question);
551
643
  case 'Agent':
552
- return await this.agentTool.run(input.task, { maxSteps: input.max_steps ?? input.maxSteps, provider: input.provider, cwd: input.cwd });
644
+ return await this.agentTool.run(input.task, {
645
+ role: input.role ?? input.agent,
646
+ context: input.context,
647
+ tools: input.tools,
648
+ processIsolation: input.process_isolation ?? input.processIsolation,
649
+ maxSteps: input.max_steps ?? input.maxSteps,
650
+ timeoutMs: input.timeout_ms ?? input.timeoutMs,
651
+ provider: input.provider,
652
+ cwd: input.cwd,
653
+ });
654
+ case 'DelegateTask':
655
+ return await this.agentTool.run(input.task ?? input.goal, {
656
+ role: input.role ?? input.agent,
657
+ context: input.context,
658
+ tools: input.tools,
659
+ processIsolation: input.process_isolation ?? input.processIsolation,
660
+ maxSteps: input.max_steps ?? input.maxSteps,
661
+ timeoutMs: input.timeout_ms ?? input.timeoutMs,
662
+ provider: input.provider,
663
+ cwd: input.cwd,
664
+ });
665
+ case 'ParallelAgent':
666
+ return await this.agentTool.runParallel(input.tasks, {
667
+ concurrency: input.concurrency,
668
+ timeoutMs: input.timeout_ms ?? input.timeoutMs,
669
+ processIsolation: input.process_isolation ?? input.processIsolation,
670
+ provider: input.provider,
671
+ cwd: input.cwd,
672
+ });
553
673
  case 'InsertText':
554
674
  return await this.insertTextTool.insert(this.resolveInputPath(input.file_path ?? input.path ?? input.file, cwd), input.insert_text ?? input.text ?? input.content, input);
555
675
  case 'StrReplaceAll':
@@ -572,7 +692,7 @@ export class ToolExecutor {
572
692
  return {
573
693
  success: false,
574
694
  error: `Unknown tool: ${toolName}`,
575
- availableTools: ['Read', 'Write', 'Edit', 'Bash', 'Glob', 'Grep', 'TaskCreate', 'TaskUpdate', 'TaskList', 'MCP', 'Parallel', 'BrowserDebug', 'WebFetch', 'WebSearch', 'WebArchive', 'HtmlEffectiveness', 'NotebookRead', 'NotebookEdit', 'TodoWrite', 'TodoList', 'ScheduleWakeup', 'AskUserQuestion', 'Agent', 'InsertText', 'StrReplaceAll'],
695
+ availableTools: ['Read', 'Write', 'Edit', 'Bash', 'Glob', 'Grep', 'TaskCreate', 'TaskUpdate', 'TaskList', 'MCP', 'Parallel', 'OpenBrowser', 'VisibleBrowser', 'BrowserDebug', 'WebFetch', 'WebSearch', 'WebArchive', 'HtmlEffectiveness', 'NotebookRead', 'NotebookEdit', 'TodoWrite', 'TodoList', 'ScheduleWakeup', 'AskUserQuestion', 'Agent', 'DelegateTask', 'ParallelAgent', 'InsertText', 'StrReplaceAll'],
576
696
  recovery: 'Call one of the available tools. For file writes use Write with { "file_path": "...", "content": "..." }. For shell commands use Bash with { "command": "..." }.',
577
697
  };
578
698
  }
@@ -716,15 +836,36 @@ export class ToolExecutor {
716
836
  return { success: true, coerced: true, args: { notebook_path: notebookPath, cell_id: cellId, new_source: newSource } };
717
837
  }
718
838
 
719
- if (toolName === 'WebFetch' || toolName === 'WebArchive' || toolName === 'BrowserDebug') {
839
+ if (toolName === 'WebFetch' || toolName === 'WebArchive' || toolName === 'BrowserDebug' || toolName === 'VisibleBrowser') {
720
840
  const url = pick('url', 'uri', 'href');
721
- if (!url) {
841
+ if (!url && toolName !== 'VisibleBrowser') {
722
842
  return { success: false, error: 'url is required', recovery: `Example: ${toolName} {"url":"https://example.com"}` };
723
843
  }
724
844
  const next = { ...args, url };
725
845
  return { success: true, coerced: true, args: next };
726
846
  }
727
847
 
848
+ if (toolName === 'OpenBrowser') {
849
+ const url = pick('url', 'uri', 'href') || 'about:blank';
850
+ const browser = pick('browser', 'app') || 'chrome';
851
+ return { success: true, coerced: true, args: { ...args, url, browser } };
852
+ }
853
+
854
+ if (toolName === 'Agent' || toolName === 'DelegateTask') {
855
+ const task = pick('task', 'goal', 'prompt', 'description');
856
+ if (!task) {
857
+ return { success: false, error: 'task is required', recovery: `Example: ${toolName} {"task":"Inspect the failing test and report the first blocker"}` };
858
+ }
859
+ return { success: true, coerced: true, args: { ...args, task } };
860
+ }
861
+
862
+ if (toolName === 'ParallelAgent') {
863
+ if (!Array.isArray(args.tasks) || args.tasks.length === 0) {
864
+ return { success: false, error: 'tasks array is required', recovery: 'Example: ParallelAgent {"tasks":[{"goal":"Review auth"},{"goal":"Inspect tests"}]}' };
865
+ }
866
+ return { success: true, coerced: true, args };
867
+ }
868
+
728
869
  if (toolName === 'WebSearch') {
729
870
  const query = pick('query', 'q', 'search', 'search_query', 'searchQuery');
730
871
  if (!query) {
@@ -752,9 +893,20 @@ export class ToolExecutor {
752
893
  const baseCommand = this.getBaseCommand(text);
753
894
  if (!baseCommand) return { success: true };
754
895
 
896
+ if (/^(?:get-command|start-process|start|open|xdg-open)$/i.test(baseCommand) && /\b(chrome|browser|google chrome)\b/i.test(text)) {
897
+ return {
898
+ success: false,
899
+ error: `Use OpenBrowser instead of shell command for browser launch: ${baseCommand}`,
900
+ recovery: 'Call OpenBrowser {"browser":"chrome","url":"about:blank"} for "mở chrome", or OpenBrowser {"browser":"chrome","url":"https://example.com"} for a specific URL.',
901
+ };
902
+ }
903
+
755
904
  const cfg = await this.getRuntimeConfig();
756
905
  const permissionCommands = cfg.permissions?.allowlist?.commands || [];
757
906
  const sandbox = cfg.sandbox || {};
907
+ if (sandbox.enabled === false) {
908
+ return { success: true };
909
+ }
758
910
  const sandboxCommands = sandbox.enabled === false ? [] : (sandbox.allowedCommands || this.allowedCommands);
759
911
  const allowed = new Set([
760
912
  ...this.allowedCommands,
@@ -814,14 +966,19 @@ export class ToolExecutor {
814
966
  const summary = {
815
967
  success: result.success !== false,
816
968
  };
817
- for (const key of ['error', 'path', 'count', 'exitCode', 'server', 'tool']) {
969
+ for (const key of ['error', 'path', 'count', 'exitCode', 'server', 'tool', 'url', 'title', 'action', 'visible', 'controlled', 'keptOpen', 'agentId', 'role', 'status', 'summary', 'processIsolated', 'childPid']) {
818
970
  if (result[key] !== undefined) summary[key] = result[key];
819
971
  }
820
972
  if (typeof result.stdout === 'string') summary.stdout = result.stdout.slice(0, 1000);
821
973
  if (typeof result.stderr === 'string') summary.stderr = result.stderr.slice(0, 1000);
822
974
  if (typeof result.content === 'string') summary.content = result.content.slice(0, 1000);
975
+ if (typeof result.domSnippet === 'string') summary.domSnippet = result.domSnippet.slice(0, 1000);
976
+ if (Array.isArray(result.consoleErrors)) summary.consoleErrors = result.consoleErrors.slice(-10);
977
+ if (Array.isArray(result.networkErrors)) summary.networkErrors = result.networkErrors.slice(-10);
823
978
  if (Array.isArray(result.files)) summary.files = result.files.slice(0, 20);
824
979
  if (Array.isArray(result.matches)) summary.matches = result.matches.slice(0, 20);
980
+ if (Array.isArray(result.changedFiles)) summary.changedFiles = result.changedFiles.slice(0, 20);
981
+ if (Array.isArray(result.toolSummaries)) summary.toolSummaries = result.toolSummaries.slice(0, 20);
825
982
  return summary;
826
983
  }
827
984
 
@@ -879,10 +1036,32 @@ export class ToolExecutor {
879
1036
 
880
1037
  const retryPolicy = await this.getRetryPolicy();
881
1038
  try {
882
- const result = await withRetry(() => client.callTool(toolName, argumentsObject), retryPolicy);
1039
+ const result = await withRetry(() => client.callTool(toolName, argumentsObject), {
1040
+ ...retryPolicy,
1041
+ retryable: error => error?.code !== 'MCP_REQUEST_TIMEOUT',
1042
+ });
883
1043
  return { success: true, server: serverName, tool: toolName, result };
884
1044
  } catch (error) {
885
- return { success: false, error: error.message, server: serverName, tool: toolName, recovery: 'Inspect the MCP server error, verify the tool name and arguments, then retry with corrected arguments.' };
1045
+ if (error?.code === 'MCP_REQUEST_TIMEOUT') {
1046
+ await client.close();
1047
+ this.mcpClients.delete(serverName);
1048
+ return {
1049
+ success: false,
1050
+ error: error.message,
1051
+ code: error.code,
1052
+ server: serverName,
1053
+ tool: toolName,
1054
+ timeoutMs: error.timeoutMs,
1055
+ recovery: `MCP server "${serverName}" did not answer "${toolName}" before the timeout. Do not blindly retry the same call. First run /mcp tools ${serverName} to confirm the server responds, narrow the tool arguments, or increase this server's requestTimeoutMs if the tool is expected to be slow.`,
1056
+ };
1057
+ }
1058
+ return {
1059
+ success: false,
1060
+ error: error.message,
1061
+ server: serverName,
1062
+ tool: toolName,
1063
+ recovery: `MCP tool "${toolName}" failed on server "${serverName}". Check the server stderr/logs and compare the arguments with /mcp tools ${serverName}; retry only after correcting the failing input.`,
1064
+ };
886
1065
  }
887
1066
  }
888
1067
 
@@ -974,6 +1153,20 @@ export class ToolExecutor {
974
1153
  searchweb: 'WebSearch',
975
1154
  internetsearch: 'WebSearch',
976
1155
  googlesearch: 'WebSearch',
1156
+ openbrowser: 'OpenBrowser',
1157
+ open_browser: 'OpenBrowser',
1158
+ browseropen: 'OpenBrowser',
1159
+ openchrome: 'OpenBrowser',
1160
+ launchchrome: 'OpenBrowser',
1161
+ visiblebrowser: 'VisibleBrowser',
1162
+ visible_browser: 'VisibleBrowser',
1163
+ browsercontrol: 'VisibleBrowser',
1164
+ browser_control: 'VisibleBrowser',
1165
+ browseraction: 'VisibleBrowser',
1166
+ chromecontrol: 'VisibleBrowser',
1167
+ chrome_control: 'VisibleBrowser',
1168
+ clickbrowser: 'VisibleBrowser',
1169
+ browserclick: 'VisibleBrowser',
977
1170
  browserdebug: 'BrowserDebug',
978
1171
  browser: 'BrowserDebug',
979
1172
  browserinspect: 'BrowserDebug',
@@ -1016,6 +1209,13 @@ export class ToolExecutor {
1016
1209
  agent: 'Agent',
1017
1210
  subagent: 'Agent',
1018
1211
  agentrun: 'Agent',
1212
+ delegatetask: 'DelegateTask',
1213
+ delegate: 'DelegateTask',
1214
+ taskdelegate: 'DelegateTask',
1215
+ spawnagent: 'DelegateTask',
1216
+ parallelagent: 'ParallelAgent',
1217
+ multiagent: 'ParallelAgent',
1218
+ parallelagents: 'ParallelAgent',
1019
1219
  };
1020
1220
  return aliases[normalized] || raw;
1021
1221
  }
@@ -1045,6 +1245,7 @@ export class ToolExecutor {
1045
1245
  case 'WebFetch':
1046
1246
  case 'WebArchive':
1047
1247
  case 'BrowserDebug':
1248
+ case 'VisibleBrowser':
1048
1249
  return { url: value };
1049
1250
  case 'TaskCreate':
1050
1251
  case 'TodoWrite':
@@ -1052,6 +1253,7 @@ export class ToolExecutor {
1052
1253
  case 'ScheduleWakeup':
1053
1254
  return { prompt: value };
1054
1255
  case 'Agent':
1256
+ case 'DelegateTask':
1055
1257
  return { task: value };
1056
1258
  default:
1057
1259
  return { input: value };
@@ -2076,6 +2278,204 @@ export class ToolExecutor {
2076
2278
  }
2077
2279
  }
2078
2280
 
2281
+ async visibleBrowser(input = {}, cwd = this.projectPath) {
2282
+ const url = String(input.url ?? input.uri ?? input.href ?? 'about:blank');
2283
+ const action = String(input.action || (url ? 'open' : 'snapshot')).toLowerCase();
2284
+ const selector = input.selector || input.css || input.target;
2285
+ const text = input.text ?? input.value ?? input.input;
2286
+ const script = input.script ?? input.js ?? input.code;
2287
+ const waitFor = input.wait_for ?? input.waitFor;
2288
+ const keepOpen = input.keep_open === true || input.keepOpen === true;
2289
+ const browserName = String(input.browser || 'chrome').toLowerCase();
2290
+ const screenshotPath = input.screenshot_path ?? input.screenshotPath;
2291
+
2292
+ let puppeteer;
2293
+ try {
2294
+ puppeteer = (await import('puppeteer')).default;
2295
+ } catch {
2296
+ return {
2297
+ success: false,
2298
+ error: 'Thư viện puppeteer chưa được cài đặt.',
2299
+ recovery: 'Run Bash {"command":"npm install puppeteer --no-save"} or configure chrome-devtools MCP, then retry VisibleBrowser.',
2300
+ };
2301
+ }
2302
+
2303
+ let browser;
2304
+ try {
2305
+ const launchOptions = {
2306
+ headless: false,
2307
+ defaultViewport: null,
2308
+ args: ['--start-maximized'],
2309
+ };
2310
+ if (browserName === 'chrome') {
2311
+ launchOptions.channel = 'chrome';
2312
+ }
2313
+
2314
+ browser = await puppeteer.launch(launchOptions).catch(async error => {
2315
+ if (launchOptions.channel) {
2316
+ const fallbackOptions = { ...launchOptions };
2317
+ delete fallbackOptions.channel;
2318
+ return await puppeteer.launch(fallbackOptions);
2319
+ }
2320
+ throw error;
2321
+ });
2322
+
2323
+ const page = await browser.newPage();
2324
+ const consoleLogs = [];
2325
+ const networkErrors = [];
2326
+ page.on('console', msg => {
2327
+ if (['error', 'warning'].includes(msg.type())) {
2328
+ consoleLogs.push(`[${msg.type()}] ${msg.text()}`);
2329
+ }
2330
+ });
2331
+ page.on('requestfailed', request => {
2332
+ networkErrors.push(`${request.method()} ${request.url()} - ${request.failure()?.errorText}`);
2333
+ });
2334
+
2335
+ if (url && url !== 'about:blank') {
2336
+ await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 30000 });
2337
+ } else {
2338
+ await page.goto('about:blank', { waitUntil: 'domcontentloaded', timeout: 30000 });
2339
+ }
2340
+
2341
+ let actionResult = null;
2342
+ if (waitFor) {
2343
+ await page.waitForSelector(waitFor, { timeout: 15000 });
2344
+ }
2345
+
2346
+ if (action === 'navigate' || action === 'open') {
2347
+ actionResult = { navigated: page.url() };
2348
+ } else if (action === 'click') {
2349
+ if (!selector) throw new Error('selector is required for VisibleBrowser action=click');
2350
+ await page.waitForSelector(selector, { timeout: 15000 });
2351
+ await page.click(selector);
2352
+ actionResult = { clicked: selector };
2353
+ } else if (action === 'type' || action === 'fill') {
2354
+ if (!selector) throw new Error('selector is required for VisibleBrowser action=type');
2355
+ await page.waitForSelector(selector, { timeout: 15000 });
2356
+ await page.click(selector, { clickCount: 3 });
2357
+ await page.type(selector, String(text ?? ''), { delay: 10 });
2358
+ actionResult = { typed: selector, text: String(text ?? '') };
2359
+ } else if (action === 'evaluate' || action === 'eval') {
2360
+ if (!script) throw new Error('script is required for VisibleBrowser action=evaluate');
2361
+ actionResult = await page.evaluate(source => (0, eval)(source), String(script));
2362
+ } else if (action === 'screenshot') {
2363
+ const outputPath = screenshotPath
2364
+ ? this.resolveInputPath(screenshotPath, cwd)
2365
+ : path.join(cwd, '.winter', `visible-browser-${Date.now()}.png`);
2366
+ await fs.mkdir(path.dirname(outputPath), { recursive: true });
2367
+ await page.screenshot({ path: outputPath, fullPage: true });
2368
+ actionResult = { screenshotPath: outputPath };
2369
+ } else if (action === 'snapshot') {
2370
+ actionResult = selector
2371
+ ? await page.$eval(selector, el => el.outerHTML.slice(0, 3000))
2372
+ : await page.evaluate(() => document.body?.innerHTML?.slice(0, 3000) || '');
2373
+ } else {
2374
+ throw new Error(`Unsupported VisibleBrowser action: ${action}`);
2375
+ }
2376
+
2377
+ const title = await page.title().catch(() => '');
2378
+ const currentUrl = page.url();
2379
+ const domSnippet = await page.evaluate(() => document.body?.innerText?.slice(0, 1600) || '').catch(() => '');
2380
+
2381
+ if (!keepOpen) {
2382
+ await browser.close();
2383
+ }
2384
+
2385
+ return {
2386
+ success: true,
2387
+ visible: true,
2388
+ controlled: true,
2389
+ browser: browserName,
2390
+ action,
2391
+ url: currentUrl,
2392
+ title,
2393
+ actionResult,
2394
+ consoleErrors: consoleLogs.slice(-10),
2395
+ networkErrors: networkErrors.slice(-10),
2396
+ domSnippet,
2397
+ keptOpen: keepOpen,
2398
+ };
2399
+ } catch (error) {
2400
+ if (browser && !keepOpen) {
2401
+ await browser.close().catch(() => {});
2402
+ }
2403
+ return {
2404
+ success: false,
2405
+ error: error.message,
2406
+ url,
2407
+ action,
2408
+ recovery: 'Use a valid URL and CSS selector. If Chrome launch fails, install Chrome or retry with browser:"chromium"; for persistent live control use chrome-devtools MCP.',
2409
+ };
2410
+ }
2411
+ }
2412
+
2413
+ buildBrowserLaunchCommand(url = 'about:blank', browser = 'chrome', platform = process.platform) {
2414
+ const targetUrl = String(url || 'about:blank');
2415
+ const targetBrowser = String(browser || 'chrome').toLowerCase();
2416
+
2417
+ if (platform === 'win32') {
2418
+ if (targetBrowser === 'default') {
2419
+ return { command: 'cmd', args: ['/c', 'start', '', targetUrl] };
2420
+ }
2421
+ return { command: 'cmd', args: ['/c', 'start', '', 'chrome', targetUrl] };
2422
+ }
2423
+
2424
+ if (platform === 'darwin') {
2425
+ if (targetBrowser === 'default') {
2426
+ return { command: 'open', args: [targetUrl] };
2427
+ }
2428
+ return { command: 'open', args: ['-a', 'Google Chrome', targetUrl] };
2429
+ }
2430
+
2431
+ if (targetBrowser === 'default') {
2432
+ return { command: 'xdg-open', args: [targetUrl] };
2433
+ }
2434
+ return { command: 'google-chrome', args: [targetUrl] };
2435
+ }
2436
+
2437
+ async openBrowser(url = 'about:blank', browser = 'chrome') {
2438
+ const targetUrl = String(url || 'about:blank');
2439
+ const targetBrowser = String(browser || 'chrome').toLowerCase();
2440
+ const launch = this.buildBrowserLaunchCommand(targetUrl, targetBrowser);
2441
+
2442
+ return await new Promise(resolve => {
2443
+ let child;
2444
+ try {
2445
+ child = spawn(launch.command, launch.args, {
2446
+ detached: true,
2447
+ stdio: 'ignore',
2448
+ windowsHide: false,
2449
+ });
2450
+ } catch (error) {
2451
+ resolve({
2452
+ success: false,
2453
+ error: error.message,
2454
+ recovery: 'Install Chrome or retry with OpenBrowser {"browser":"default","url":"about:blank"}.',
2455
+ });
2456
+ return;
2457
+ }
2458
+
2459
+ child.once('error', error => {
2460
+ resolve({
2461
+ success: false,
2462
+ error: error.message,
2463
+ recovery: 'Install Chrome or retry with OpenBrowser {"browser":"default","url":"about:blank"}.',
2464
+ });
2465
+ });
2466
+ child.once('spawn', () => {
2467
+ child.unref();
2468
+ resolve({
2469
+ success: true,
2470
+ browser: targetBrowser,
2471
+ url: targetUrl,
2472
+ command: launch.command,
2473
+ args: launch.args,
2474
+ });
2475
+ });
2476
+ });
2477
+ }
2478
+
2079
2479
  async htmlEffectivenessCompile(input, cwd) {
2080
2480
  const inputPath = this.resolveInputPath(input.input_path ?? input.inputPath ?? input.input, cwd);
2081
2481
  const outputPath = this.resolveInputPath(input.output_path ?? input.outputPath ?? input.output, cwd);
@@ -13,18 +13,20 @@ export class PermissionManager {
13
13
  const permissions = cfg.permissions || {};
14
14
  const allowlist = permissions.allowlist || {};
15
15
 
16
- return {
17
- promptByDefault: permissions.promptByDefault !== false,
18
- allowedTools: new Set([...(allowlist.tools || []), ...DEFAULT_ALLOWED]),
19
- allowedCommands: new Set(allowlist.commands || []),
20
- allowedServers: new Set(allowlist.mcpServers || []),
21
- };
22
- }
23
-
24
- async isAllowedTool(toolName) {
25
- const policy = await this.getPolicy();
26
- return policy.allowedTools.has(toolName);
27
- }
16
+ return {
17
+ promptByDefault: permissions.promptByDefault !== false,
18
+ fullAccess: cfg.sandbox?.enabled === false,
19
+ allowedTools: new Set([...(allowlist.tools || []), ...DEFAULT_ALLOWED]),
20
+ allowedCommands: new Set(allowlist.commands || []),
21
+ allowedServers: new Set(allowlist.mcpServers || []),
22
+ };
23
+ }
24
+
25
+ async isAllowedTool(toolName) {
26
+ const policy = await this.getPolicy();
27
+ if (policy.fullAccess) return true;
28
+ return policy.allowedTools.has(toolName);
29
+ }
28
30
 
29
31
  async shouldPromptForTool(toolName) {
30
32
  const policy = await this.getPolicy();
@@ -37,11 +39,12 @@ export class PermissionManager {
37
39
  return await this.shouldPromptForTool(toolName);
38
40
  }
39
41
 
40
- async isMcpServerAllowed(serverName) {
41
- if (!serverName) return false;
42
- const policy = await this.getPolicy();
43
- return policy.allowedServers.has(serverName);
44
- }
42
+ async isMcpServerAllowed(serverName) {
43
+ if (!serverName) return false;
44
+ const policy = await this.getPolicy();
45
+ if (policy.fullAccess) return true;
46
+ return policy.allowedServers.has(serverName);
47
+ }
45
48
 
46
49
  async allowTool(toolName) {
47
50
  if (!this.config?.load || !this.config?.save) return;