@monoes/monomindcli 1.14.7 → 1.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/agents/reengineer-squad/boss.md +113 -0
- package/.claude/agents/reengineer-squad/critic-architect.md +132 -0
- package/.claude/agents/reengineer-squad/git-manager.md +145 -0
- package/.claude/agents/reengineer-squad/idea-generator.md +95 -0
- package/.claude/agents/reengineer-squad/implementer.md +112 -0
- package/.claude/agents/reengineer-squad/integration-planner.md +112 -0
- package/.claude/agents/reengineer-squad/source-analyst.md +103 -0
- package/.claude/agents/reengineer-squad/target-analyst.md +118 -0
- package/.claude/agents/reengineer-squad/tester.md +105 -0
- package/.claude/commands/mastermind/master.md +35 -14
- package/.claude/helpers/handlers/capture-handler.cjs +155 -18
- package/.claude/helpers/monolean-activate.cjs +20 -0
- package/.claude/helpers/monolean-config.cjs +76 -0
- package/.claude/helpers/monolean-instructions.cjs +109 -0
- package/.claude/helpers/monolean-propagate.cjs +9 -0
- package/.claude/helpers/monolean-tracker.cjs +18 -0
- package/.claude/helpers/skill-registry.json +2 -2
- package/.claude/skills/agent-browser-testing/SKILL.md +301 -18
- package/.claude/skills/mastermind/runorg.md +69 -23
- package/.claude/skills/monodesign/SKILL.md +32 -1
- package/.claude/skills/monodesign/adapt.md +53 -0
- package/.claude/skills/monodesign/agents/monodesign-asset-producer.md +100 -0
- package/.claude/skills/monodesign/animate.md +65 -0
- package/.claude/skills/monodesign/audit.md +89 -0
- package/.claude/skills/monodesign/bolder.md +50 -0
- package/.claude/skills/monodesign/clarify.md +64 -0
- package/.claude/skills/monodesign/colorize.md +68 -0
- package/.claude/skills/monodesign/craft.md +51 -0
- package/.claude/skills/monodesign/critique.md +66 -0
- package/.claude/skills/monodesign/delight.md +47 -0
- package/.claude/skills/monodesign/distill.md +56 -0
- package/.claude/skills/monodesign/document.md +80 -0
- package/.claude/skills/monodesign/extract.md +74 -0
- package/.claude/skills/monodesign/harden.md +65 -0
- package/.claude/skills/monodesign/live.md +59 -0
- package/.claude/skills/monodesign/onboard.md +50 -0
- package/.claude/skills/monodesign/optimize.md +64 -0
- package/.claude/skills/monodesign/overdrive.md +56 -0
- package/.claude/skills/monodesign/polish.md +68 -0
- package/.claude/skills/monodesign/quieter.md +57 -0
- package/.claude/skills/monodesign/reference/antipatterns-catalog.md +248 -76
- package/.claude/skills/monodesign/reference/codex.md +107 -0
- package/.claude/skills/monodesign/reference/craft.md +3 -0
- package/.claude/skills/monodesign/reference/hooks.md +99 -0
- package/.claude/skills/monodesign/reference/image-prompts.md +12 -0
- package/.claude/skills/monodesign/shape.md +71 -0
- package/.claude/skills/monodesign/teach.md +69 -0
- package/.claude/skills/monodesign/typeset.md +59 -0
- package/.claude/skills/monolean/SKILL.md +118 -0
- package/.claude/skills/monolean-audit/SKILL.md +41 -0
- package/.claude/skills/monolean-debt/SKILL.md +46 -0
- package/.claude/skills/monolean-help/SKILL.md +60 -0
- package/.claude/skills/monolean-review/SKILL.md +57 -0
- package/bin/cli.js +3 -1
- package/dist/dashboard/server.js +137 -0
- package/dist/src/__tests__/browse-adapters.test.d.ts +2 -0
- package/dist/src/__tests__/browse-adapters.test.d.ts.map +1 -0
- package/dist/src/__tests__/browse-adapters.test.js +51 -0
- package/dist/src/__tests__/browse-adapters.test.js.map +1 -0
- package/dist/src/__tests__/browse-analyzer.test.d.ts +2 -0
- package/dist/src/__tests__/browse-analyzer.test.d.ts.map +1 -0
- package/dist/src/__tests__/browse-analyzer.test.js +68 -0
- package/dist/src/__tests__/browse-analyzer.test.js.map +1 -0
- package/dist/src/__tests__/browse-builtin-handlers.test.d.ts +2 -0
- package/dist/src/__tests__/browse-builtin-handlers.test.d.ts.map +1 -0
- package/dist/src/__tests__/browse-builtin-handlers.test.js +139 -0
- package/dist/src/__tests__/browse-builtin-handlers.test.js.map +1 -0
- package/dist/src/__tests__/browse-cdp.test.d.ts +2 -0
- package/dist/src/__tests__/browse-cdp.test.d.ts.map +1 -0
- package/dist/src/__tests__/browse-cdp.test.js +169 -0
- package/dist/src/__tests__/browse-cdp.test.js.map +1 -0
- package/dist/src/__tests__/browse-dashboard.test.d.ts +2 -0
- package/dist/src/__tests__/browse-dashboard.test.d.ts.map +1 -0
- package/dist/src/__tests__/browse-dashboard.test.js +179 -0
- package/dist/src/__tests__/browse-dashboard.test.js.map +1 -0
- package/dist/src/__tests__/browse-engine.test.d.ts +2 -0
- package/dist/src/__tests__/browse-engine.test.d.ts.map +1 -0
- package/dist/src/__tests__/browse-engine.test.js +122 -0
- package/dist/src/__tests__/browse-engine.test.js.map +1 -0
- package/dist/src/__tests__/browse-expression.test.d.ts +2 -0
- package/dist/src/__tests__/browse-expression.test.d.ts.map +1 -0
- package/dist/src/__tests__/browse-expression.test.js +54 -0
- package/dist/src/__tests__/browse-expression.test.js.map +1 -0
- package/dist/src/__tests__/browse-store.test.d.ts +2 -0
- package/dist/src/__tests__/browse-store.test.d.ts.map +1 -0
- package/dist/src/__tests__/browse-store.test.js +99 -0
- package/dist/src/__tests__/browse-store.test.js.map +1 -0
- package/dist/src/__tests__/browse-workflow-types.test.d.ts +2 -0
- package/dist/src/__tests__/browse-workflow-types.test.d.ts.map +1 -0
- package/dist/src/__tests__/browse-workflow-types.test.js +33 -0
- package/dist/src/__tests__/browse-workflow-types.test.js.map +1 -0
- package/dist/src/browser/action-builder/analyzer.d.ts +11 -0
- package/dist/src/browser/action-builder/analyzer.d.ts.map +1 -0
- package/dist/src/browser/action-builder/analyzer.js +71 -0
- package/dist/src/browser/action-builder/analyzer.js.map +1 -0
- package/dist/src/browser/action-builder/types.d.ts +47 -0
- package/dist/src/browser/action-builder/types.d.ts.map +1 -0
- package/dist/src/browser/action-builder/types.js +2 -0
- package/dist/src/browser/action-builder/types.js.map +1 -0
- package/dist/src/browser/adapters/gemini.d.ts +3 -0
- package/dist/src/browser/adapters/gemini.d.ts.map +1 -0
- package/dist/src/browser/adapters/gemini.js +16 -0
- package/dist/src/browser/adapters/gemini.js.map +1 -0
- package/dist/src/browser/adapters/google.d.ts +3 -0
- package/dist/src/browser/adapters/google.d.ts.map +1 -0
- package/dist/src/browser/adapters/google.js +17 -0
- package/dist/src/browser/adapters/google.js.map +1 -0
- package/dist/src/browser/adapters/index.d.ts +19 -0
- package/dist/src/browser/adapters/index.d.ts.map +1 -0
- package/dist/src/browser/adapters/index.js +23 -0
- package/dist/src/browser/adapters/index.js.map +1 -0
- package/dist/src/browser/adapters/instagram.d.ts +3 -0
- package/dist/src/browser/adapters/instagram.d.ts.map +1 -0
- package/dist/src/browser/adapters/instagram.js +17 -0
- package/dist/src/browser/adapters/instagram.js.map +1 -0
- package/dist/src/browser/adapters/linkedin.d.ts +3 -0
- package/dist/src/browser/adapters/linkedin.d.ts.map +1 -0
- package/dist/src/browser/adapters/linkedin.js +19 -0
- package/dist/src/browser/adapters/linkedin.js.map +1 -0
- package/dist/src/browser/adapters/microsoft.d.ts +3 -0
- package/dist/src/browser/adapters/microsoft.d.ts.map +1 -0
- package/dist/src/browser/adapters/microsoft.js +16 -0
- package/dist/src/browser/adapters/microsoft.js.map +1 -0
- package/dist/src/browser/adapters/x.d.ts +3 -0
- package/dist/src/browser/adapters/x.d.ts.map +1 -0
- package/dist/src/browser/adapters/x.js +19 -0
- package/dist/src/browser/adapters/x.js.map +1 -0
- package/dist/src/browser/dashboard/api-types.d.ts +50 -0
- package/dist/src/browser/dashboard/api-types.d.ts.map +1 -0
- package/dist/src/browser/dashboard/api-types.js +14 -0
- package/dist/src/browser/dashboard/api-types.js.map +1 -0
- package/dist/src/browser/dashboard/server.d.ts +9 -0
- package/dist/src/browser/dashboard/server.d.ts.map +1 -0
- package/dist/src/browser/dashboard/server.js +62 -0
- package/dist/src/browser/dashboard/server.js.map +1 -0
- package/dist/src/browser/dashboard/ui.html +1811 -0
- package/dist/src/browser/workflow/builtin-handlers.d.ts +3 -0
- package/dist/src/browser/workflow/builtin-handlers.d.ts.map +1 -0
- package/dist/src/browser/workflow/builtin-handlers.js +343 -0
- package/dist/src/browser/workflow/builtin-handlers.js.map +1 -0
- package/dist/src/browser/workflow/engine.d.ts +15 -0
- package/dist/src/browser/workflow/engine.d.ts.map +1 -0
- package/dist/src/browser/workflow/engine.js +127 -0
- package/dist/src/browser/workflow/engine.js.map +1 -0
- package/dist/src/browser/workflow/expression.d.ts +4 -0
- package/dist/src/browser/workflow/expression.d.ts.map +1 -0
- package/dist/src/browser/workflow/expression.js +64 -0
- package/dist/src/browser/workflow/expression.js.map +1 -0
- package/dist/src/browser/workflow/store.d.ts +24 -0
- package/dist/src/browser/workflow/store.d.ts.map +1 -0
- package/dist/src/browser/workflow/store.js +145 -0
- package/dist/src/browser/workflow/store.js.map +1 -0
- package/dist/src/browser/workflow/types.d.ts +48 -0
- package/dist/src/browser/workflow/types.d.ts.map +1 -0
- package/dist/src/browser/workflow/types.js +2 -0
- package/dist/src/browser/workflow/types.js.map +1 -0
- package/dist/src/commands/browse-action.d.ts +4 -0
- package/dist/src/commands/browse-action.d.ts.map +1 -0
- package/dist/src/commands/browse-action.js +151 -0
- package/dist/src/commands/browse-action.js.map +1 -0
- package/dist/src/commands/browse-platform.d.ts +4 -0
- package/dist/src/commands/browse-platform.d.ts.map +1 -0
- package/dist/src/commands/browse-platform.js +117 -0
- package/dist/src/commands/browse-platform.js.map +1 -0
- package/dist/src/commands/browse-workflow.d.ts +4 -0
- package/dist/src/commands/browse-workflow.d.ts.map +1 -0
- package/dist/src/commands/browse-workflow.js +153 -0
- package/dist/src/commands/browse-workflow.js.map +1 -0
- package/dist/src/commands/browse.d.ts +10 -6
- package/dist/src/commands/browse.d.ts.map +1 -1
- package/dist/src/commands/browse.js +11 -2154
- package/dist/src/commands/browse.js.map +1 -1
- package/dist/src/commands/design-detect.d.ts +21 -0
- package/dist/src/commands/design-detect.d.ts.map +1 -0
- package/dist/src/commands/design-detect.js +127 -0
- package/dist/src/commands/design-detect.js.map +1 -0
- package/dist/src/commands/design-palette.d.ts +22 -0
- package/dist/src/commands/design-palette.d.ts.map +1 -0
- package/dist/src/commands/design-palette.js +539 -0
- package/dist/src/commands/design-palette.js.map +1 -0
- package/dist/src/commands/hooks-core-commands.d.ts +10 -0
- package/dist/src/commands/hooks-core-commands.d.ts.map +1 -0
- package/dist/src/commands/hooks-core-commands.js +377 -0
- package/dist/src/commands/hooks-core-commands.js.map +1 -0
- package/dist/src/commands/hooks-coverage-commands.d.ts +12 -0
- package/dist/src/commands/hooks-coverage-commands.d.ts.map +1 -0
- package/dist/src/commands/hooks-coverage-commands.js +1217 -0
- package/dist/src/commands/hooks-coverage-commands.js.map +1 -0
- package/dist/src/commands/hooks-coverage-utils.d.ts +42 -0
- package/dist/src/commands/hooks-coverage-utils.d.ts.map +1 -0
- package/dist/src/commands/hooks-coverage-utils.js +220 -0
- package/dist/src/commands/hooks-coverage-utils.js.map +1 -0
- package/dist/src/commands/hooks-extended-commands.d.ts +14 -0
- package/dist/src/commands/hooks-extended-commands.d.ts.map +1 -0
- package/dist/src/commands/hooks-extended-commands.js +579 -0
- package/dist/src/commands/hooks-extended-commands.js.map +1 -0
- package/dist/src/commands/hooks-formatting.d.ts +13 -0
- package/dist/src/commands/hooks-formatting.d.ts.map +1 -0
- package/dist/src/commands/hooks-formatting.js +42 -0
- package/dist/src/commands/hooks-formatting.js.map +1 -0
- package/dist/src/commands/hooks-routing-commands.d.ts +15 -0
- package/dist/src/commands/hooks-routing-commands.d.ts.map +1 -0
- package/dist/src/commands/hooks-routing-commands.js +723 -0
- package/dist/src/commands/hooks-routing-commands.js.map +1 -0
- package/dist/src/commands/hooks-workers.d.ts +9 -0
- package/dist/src/commands/hooks-workers.d.ts.map +1 -0
- package/dist/src/commands/hooks-workers.js +782 -0
- package/dist/src/commands/hooks-workers.js.map +1 -0
- package/dist/src/commands/hooks.d.ts +8 -0
- package/dist/src/commands/hooks.d.ts.map +1 -1
- package/dist/src/commands/hooks.js +179 -4103
- package/dist/src/commands/hooks.js.map +1 -1
- package/dist/src/commands/index.d.ts +1 -0
- package/dist/src/commands/index.d.ts.map +1 -1
- package/dist/src/commands/index.js +6 -0
- package/dist/src/commands/index.js.map +1 -1
- package/dist/src/commands/org.d.ts.map +1 -1
- package/dist/src/commands/org.js +14 -15
- package/dist/src/commands/org.js.map +1 -1
- package/dist/src/commands/tokens.d.ts.map +1 -1
- package/dist/src/commands/tokens.js +77 -1
- package/dist/src/commands/tokens.js.map +1 -1
- package/dist/src/init/executor.d.ts.map +1 -1
- package/dist/src/init/executor.js +18 -8
- package/dist/src/init/executor.js.map +1 -1
- package/dist/src/init/settings-generator.d.ts.map +1 -1
- package/dist/src/init/settings-generator.js +39 -5
- package/dist/src/init/settings-generator.js.map +1 -1
- package/dist/src/init/statusline-generator.d.ts.map +1 -1
- package/dist/src/init/statusline-generator.js +25 -5
- package/dist/src/init/statusline-generator.js.map +1 -1
- package/dist/src/mcp-tools/browser-tools.d.ts +3 -5
- package/dist/src/mcp-tools/browser-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/browser-tools.js +619 -326
- package/dist/src/mcp-tools/browser-tools.js.map +1 -1
- package/dist/src/mcp-tools/hooks-embedding.d.ts +161 -0
- package/dist/src/mcp-tools/hooks-embedding.d.ts.map +1 -0
- package/dist/src/mcp-tools/hooks-embedding.js +506 -0
- package/dist/src/mcp-tools/hooks-embedding.js.map +1 -0
- package/dist/src/mcp-tools/hooks-intelligence.d.ts +26 -0
- package/dist/src/mcp-tools/hooks-intelligence.d.ts.map +1 -0
- package/dist/src/mcp-tools/hooks-intelligence.js +1328 -0
- package/dist/src/mcp-tools/hooks-intelligence.js.map +1 -0
- package/dist/src/mcp-tools/hooks-routing.d.ts +27 -0
- package/dist/src/mcp-tools/hooks-routing.d.ts.map +1 -0
- package/dist/src/mcp-tools/hooks-routing.js +1591 -0
- package/dist/src/mcp-tools/hooks-routing.js.map +1 -0
- package/dist/src/mcp-tools/hooks-tools.d.ts +3 -38
- package/dist/src/mcp-tools/hooks-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/hooks-tools.js +5 -3393
- package/dist/src/mcp-tools/hooks-tools.js.map +1 -1
- package/dist/src/mcp-tools/monograph-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/monograph-tools.js +24 -14
- package/dist/src/mcp-tools/monograph-tools.js.map +1 -1
- package/dist/src/mcp-tools/workflow-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/workflow-tools.js +54 -1
- package/dist/src/mcp-tools/workflow-tools.js.map +1 -1
- package/dist/src/memory/embedding-operations.d.ts +58 -0
- package/dist/src/memory/embedding-operations.d.ts.map +1 -0
- package/dist/src/memory/embedding-operations.js +299 -0
- package/dist/src/memory/embedding-operations.js.map +1 -0
- package/dist/src/memory/ewc-consolidation.d.ts.map +1 -1
- package/dist/src/memory/ewc-consolidation.js +37 -3
- package/dist/src/memory/ewc-consolidation.js.map +1 -1
- package/dist/src/memory/hnsw-operations.d.ts +130 -0
- package/dist/src/memory/hnsw-operations.d.ts.map +1 -0
- package/dist/src/memory/hnsw-operations.js +400 -0
- package/dist/src/memory/hnsw-operations.js.map +1 -0
- package/dist/src/memory/intelligence.d.ts.map +1 -1
- package/dist/src/memory/intelligence.js +42 -23
- package/dist/src/memory/intelligence.js.map +1 -1
- package/dist/src/memory/memory-bridge.d.ts.map +1 -1
- package/dist/src/memory/memory-bridge.js +52 -8
- package/dist/src/memory/memory-bridge.js.map +1 -1
- package/dist/src/memory/memory-crud.d.ts +67 -0
- package/dist/src/memory/memory-crud.d.ts.map +1 -0
- package/dist/src/memory/memory-crud.js +415 -0
- package/dist/src/memory/memory-crud.js.map +1 -0
- package/dist/src/memory/memory-initializer.d.ts +9 -322
- package/dist/src/memory/memory-initializer.d.ts.map +1 -1
- package/dist/src/memory/memory-initializer.js +17 -1794
- package/dist/src/memory/memory-initializer.js.map +1 -1
- package/dist/src/memory/memory-migrations.d.ts +30 -0
- package/dist/src/memory/memory-migrations.d.ts.map +1 -0
- package/dist/src/memory/memory-migrations.js +134 -0
- package/dist/src/memory/memory-migrations.js.map +1 -0
- package/dist/src/memory/memory-read.d.ts +78 -0
- package/dist/src/memory/memory-read.d.ts.map +1 -0
- package/dist/src/memory/memory-read.js +331 -0
- package/dist/src/memory/memory-read.js.map +1 -0
- package/dist/src/memory/memory-schema.d.ts +13 -0
- package/dist/src/memory/memory-schema.d.ts.map +1 -0
- package/dist/src/memory/memory-schema.js +167 -0
- package/dist/src/memory/memory-schema.js.map +1 -0
- package/dist/src/memory/sona-optimizer.d.ts.map +1 -1
- package/dist/src/memory/sona-optimizer.js +37 -4
- package/dist/src/memory/sona-optimizer.js.map +1 -1
- package/dist/src/monovector/route-outcomes.d.ts.map +1 -1
- package/dist/src/monovector/route-outcomes.js +16 -6
- package/dist/src/monovector/route-outcomes.js.map +1 -1
- package/dist/src/pricing/model-pricing.d.ts +41 -0
- package/dist/src/pricing/model-pricing.d.ts.map +1 -0
- package/dist/src/pricing/model-pricing.js +61 -0
- package/dist/src/pricing/model-pricing.js.map +1 -0
- package/dist/src/ui/.monomind/capture/active-run.json +1 -0
- package/dist/src/ui/.monomind/orgs/system-trial-qa/runs/real-events-1782290897.convs.jsonl +3 -0
- package/dist/src/ui/.monomind/orgs/system-trial-qa/runs/real-events-1782290897.jsonl +11 -0
- package/dist/src/ui/.monomind/orgs/system-trial-qa/runs/rigid-qa-restart-1782288201.jsonl +540 -0
- package/dist/src/ui/.monomind/orgs/system-trial-qa-threads.jsonl +3 -0
- package/dist/src/ui/.monomind/orgs/test-event-fix/runs/rigid-qa-restart-1782288201.jsonl +2 -0
- package/dist/src/ui/MODULARIZATION_PLAN.md +79 -0
- package/dist/src/ui/collector.mjs +23 -13
- package/dist/src/ui/dashboard.html +1652 -13
- package/dist/src/ui/data/known-projects.json +1 -0
- package/dist/src/ui/data/mastermind-events.jsonl +553 -0
- package/dist/src/ui/data/sessions/_index.json +1 -0
- package/dist/src/ui/data/sessions/final-sess-001.jsonl +542 -0
- package/dist/src/ui/data/unknown-events.jsonl +1 -0
- package/dist/src/ui/orgs.html +154 -10
- package/dist/src/ui/server.mjs +1131 -168
- package/dist/src/ui/sse-manager.mjs +119 -0
- package/dist/src/update/checker.js +1 -1
- package/dist/src/update/checker.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/workflow/builtin-handlers.js +321 -0
- package/dist/workflow/engine.js +253 -0
- package/dist/workflow/expression.js +98 -0
- package/dist/workflow/types.js +2 -0
- package/package.json +8 -5
|
@@ -1,53 +1,85 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Browser MCP Tools
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Uses @monoes/monobrowse CDP client directly — no external binary required.
|
|
5
|
+
* Sessions are keyed by session ID; each maps to a persistent CDP connection
|
|
6
|
+
* on the configured port (default: MONOBROWSE_CDP_PORT env var or 9222).
|
|
6
7
|
*/
|
|
7
8
|
const MAX_BROWSER_SESSIONS = 100;
|
|
8
9
|
const SESSION_TTL_MS = 30 * 60 * 1000; // 30 minutes
|
|
9
|
-
// Session registry for multi-session support
|
|
10
|
+
// Session registry for multi-session support (sessionId → connection)
|
|
10
11
|
const browserSessions = new Map();
|
|
12
|
+
const connectionCache = new Map();
|
|
11
13
|
function pruneExpiredSessions() {
|
|
12
14
|
const cutoff = Date.now() - SESSION_TTL_MS;
|
|
13
15
|
for (const [id, info] of browserSessions) {
|
|
14
16
|
if (new Date(info.lastActivity).getTime() < cutoff) {
|
|
15
17
|
browserSessions.delete(id);
|
|
18
|
+
const conn = connectionCache.get(id);
|
|
19
|
+
if (conn) {
|
|
20
|
+
try {
|
|
21
|
+
conn.client.close();
|
|
22
|
+
}
|
|
23
|
+
catch { /* ignore */ }
|
|
24
|
+
}
|
|
25
|
+
connectionCache.delete(id);
|
|
16
26
|
}
|
|
17
27
|
}
|
|
18
28
|
}
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
29
|
+
function touchSession(sessionId) {
|
|
30
|
+
const info = browserSessions.get(sessionId);
|
|
31
|
+
if (info)
|
|
32
|
+
info.lastActivity = new Date().toISOString();
|
|
33
|
+
}
|
|
34
|
+
async function getConnection(sessionId) {
|
|
35
|
+
if (connectionCache.has(sessionId)) {
|
|
36
|
+
const conn = connectionCache.get(sessionId);
|
|
37
|
+
try {
|
|
38
|
+
// Liveness check — evict stale CDP connections
|
|
39
|
+
await conn.client.send('Runtime.evaluate', { expression: '1', returnByValue: true }, conn.cdpSessionId);
|
|
40
|
+
return conn;
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
connectionCache.delete(sessionId);
|
|
44
|
+
}
|
|
30
45
|
}
|
|
31
|
-
|
|
32
|
-
|
|
46
|
+
const port = Number(process.env['MONOBROWSE_CDP_PORT'] ?? 9222);
|
|
47
|
+
const { connectToTarget } = await import('@monoes/monobrowse');
|
|
48
|
+
const { client, sessionId: cdpSessionId } = await connectToTarget(port);
|
|
49
|
+
const conn = { client, cdpSessionId, refs: new Map() };
|
|
50
|
+
connectionCache.set(sessionId, conn);
|
|
51
|
+
return conn;
|
|
52
|
+
}
|
|
53
|
+
function releaseConnection(sessionId) {
|
|
54
|
+
const conn = connectionCache.get(sessionId);
|
|
55
|
+
if (conn) {
|
|
56
|
+
try {
|
|
57
|
+
conn.client.close();
|
|
58
|
+
}
|
|
59
|
+
catch { /* ignore */ }
|
|
33
60
|
}
|
|
34
|
-
|
|
61
|
+
connectionCache.delete(sessionId);
|
|
62
|
+
browserSessions.delete(sessionId);
|
|
63
|
+
}
|
|
64
|
+
function ok(data = {}) {
|
|
65
|
+
return { content: [{ type: 'text', text: JSON.stringify({ success: true, ...data }, null, 2) }] };
|
|
66
|
+
}
|
|
67
|
+
function fail(message) {
|
|
68
|
+
return {
|
|
69
|
+
content: [{ type: 'text', text: JSON.stringify({ success: false, error: message }) }],
|
|
70
|
+
isError: true,
|
|
71
|
+
};
|
|
35
72
|
}
|
|
36
73
|
/** Validate a session ID against a strict allowlist. */
|
|
37
74
|
function validateSessionId(value) {
|
|
38
|
-
if (value === undefined || value === null || value === '')
|
|
75
|
+
if (value === undefined || value === null || value === '')
|
|
39
76
|
return 'default';
|
|
40
|
-
}
|
|
41
77
|
if (typeof value !== 'string' || !/^[A-Za-z0-9_-]{1,64}$/.test(value)) {
|
|
42
78
|
throw new Error('session: must match ^[A-Za-z0-9_-]{1,64}$');
|
|
43
79
|
}
|
|
44
80
|
return value;
|
|
45
81
|
}
|
|
46
|
-
/**
|
|
47
|
-
* Validate a URL against a scheme allowlist. Without this, `file://`,
|
|
48
|
-
* `data:`, `javascript:`, `chrome://` schemes give SSRF / local-file read /
|
|
49
|
-
* scheme-abuse on the underlying browser.
|
|
50
|
-
*/
|
|
82
|
+
/** Validate a URL against a scheme allowlist. */
|
|
51
83
|
const ALLOWED_URL_SCHEMES = new Set(['http:', 'https:', 'about:']);
|
|
52
84
|
function validateUrl(value) {
|
|
53
85
|
if (typeof value !== 'string')
|
|
@@ -67,111 +99,87 @@ function validateUrl(value) {
|
|
|
67
99
|
return value;
|
|
68
100
|
}
|
|
69
101
|
/**
|
|
70
|
-
* Validate a screenshot path. Resolved
|
|
71
|
-
* `<projectRoot>/.monomind/screenshots
|
|
72
|
-
* so a malicious LLM action cannot clobber e.g. `~/.ssh/authorized_keys` or
|
|
73
|
-
* `.git/hooks/pre-commit`.
|
|
102
|
+
* Validate a screenshot path. Resolved path must be within
|
|
103
|
+
* `<projectRoot>/.monomind/screenshots` and must not already exist.
|
|
74
104
|
*/
|
|
75
105
|
async function validateScreenshotPath(value) {
|
|
76
|
-
if (typeof value !== 'string' || value.length === 0)
|
|
106
|
+
if (typeof value !== 'string' || value.length === 0)
|
|
77
107
|
throw new Error('path: must be a non-empty string');
|
|
78
|
-
|
|
79
|
-
if (value.startsWith('-')) {
|
|
108
|
+
if (value.startsWith('-'))
|
|
80
109
|
throw new Error('path: must not start with "-"');
|
|
81
|
-
}
|
|
82
110
|
const path = await import('node:path');
|
|
83
111
|
const fs = await import('node:fs');
|
|
84
|
-
const
|
|
85
|
-
const root = path.resolve(projectRoot, '.monomind', 'screenshots');
|
|
112
|
+
const root = path.resolve(process.cwd(), '.monomind', 'screenshots');
|
|
86
113
|
await fs.promises.mkdir(root, { recursive: true });
|
|
87
114
|
const resolved = path.resolve(value);
|
|
88
115
|
if (!resolved.startsWith(root + path.sep) && resolved !== root) {
|
|
89
116
|
throw new Error(`path: must be within ${root}`);
|
|
90
117
|
}
|
|
91
|
-
if (fs.existsSync(resolved))
|
|
118
|
+
if (fs.existsSync(resolved))
|
|
92
119
|
throw new Error(`path: refuses to overwrite existing file at ${resolved}`);
|
|
93
|
-
}
|
|
94
120
|
return resolved;
|
|
95
121
|
}
|
|
96
|
-
/**
|
|
122
|
+
/** Reject strings starting with '-' (flag-injection defense). */
|
|
123
|
+
function rejectFlagLike(value, field) {
|
|
124
|
+
if (typeof value !== 'string')
|
|
125
|
+
throw new Error(`${field}: must be a string`);
|
|
126
|
+
if (value.startsWith('-'))
|
|
127
|
+
throw new Error(`${field}: must not start with '-' (flag-injection defense)`);
|
|
128
|
+
return value;
|
|
129
|
+
}
|
|
130
|
+
/** Cap on browser_eval scripts. */
|
|
97
131
|
const MAX_BROWSER_EVAL_BYTES = 16 * 1024;
|
|
98
132
|
/**
|
|
99
|
-
*
|
|
133
|
+
* Resolve an element target using monobrowse finders.
|
|
134
|
+
* target: CSS selector string (e.g. "#id", ".class", "button")
|
|
135
|
+
* locator: "selector" (default) | "role" | "text" | "label" | "placeholder"
|
|
100
136
|
*/
|
|
101
|
-
async function
|
|
102
|
-
const {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
});
|
|
122
|
-
let data;
|
|
123
|
-
try {
|
|
124
|
-
data = JSON.parse(result);
|
|
125
|
-
}
|
|
126
|
-
catch {
|
|
127
|
-
data = result.trim();
|
|
128
|
-
}
|
|
129
|
-
// Update session activity
|
|
130
|
-
const sessionInfo = browserSessions.get(session);
|
|
131
|
-
if (sessionInfo) {
|
|
132
|
-
sessionInfo.lastActivity = new Date().toISOString();
|
|
133
|
-
}
|
|
134
|
-
return {
|
|
135
|
-
content: [{
|
|
136
|
-
type: 'text',
|
|
137
|
-
text: JSON.stringify(data, null, 2),
|
|
138
|
-
}],
|
|
139
|
-
};
|
|
140
|
-
}
|
|
141
|
-
catch (error) {
|
|
142
|
-
return {
|
|
143
|
-
content: [{
|
|
144
|
-
type: 'text',
|
|
145
|
-
text: JSON.stringify({
|
|
146
|
-
success: false,
|
|
147
|
-
error: error instanceof Error ? error.message : String(error),
|
|
148
|
-
}),
|
|
149
|
-
}],
|
|
150
|
-
isError: true,
|
|
151
|
-
};
|
|
137
|
+
async function findElement(conn, target, locator = 'selector') {
|
|
138
|
+
const { findBySelector, findByRole, findByText, findByLabel, findByPlaceholder, } = await import('@monoes/monobrowse');
|
|
139
|
+
let ref = null;
|
|
140
|
+
switch (locator) {
|
|
141
|
+
case 'selector':
|
|
142
|
+
ref = await findBySelector(conn.client, conn.cdpSessionId, conn.refs, target);
|
|
143
|
+
break;
|
|
144
|
+
case 'role':
|
|
145
|
+
ref = await findByRole(conn.client, conn.cdpSessionId, conn.refs, target);
|
|
146
|
+
break;
|
|
147
|
+
case 'text':
|
|
148
|
+
ref = await findByText(conn.client, conn.cdpSessionId, conn.refs, target);
|
|
149
|
+
break;
|
|
150
|
+
case 'label':
|
|
151
|
+
ref = await findByLabel(conn.client, conn.cdpSessionId, conn.refs, target);
|
|
152
|
+
break;
|
|
153
|
+
case 'placeholder':
|
|
154
|
+
ref = await findByPlaceholder(conn.client, conn.cdpSessionId, conn.refs, target);
|
|
155
|
+
break;
|
|
156
|
+
default: throw new Error(`Unknown locator "${locator}". Use: selector|role|text|label|placeholder`);
|
|
152
157
|
}
|
|
158
|
+
if (!ref)
|
|
159
|
+
throw new Error(`Element not found: ${locator}="${target}"`);
|
|
160
|
+
return ref;
|
|
153
161
|
}
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
162
|
+
// ---------------------------------------------------------------------------
|
|
163
|
+
// Exported tool list
|
|
164
|
+
// ---------------------------------------------------------------------------
|
|
157
165
|
export const browserTools = [
|
|
158
166
|
// ==========================================================================
|
|
159
167
|
// Navigation Tools
|
|
160
168
|
// ==========================================================================
|
|
161
169
|
{
|
|
162
170
|
name: 'browser_open',
|
|
163
|
-
description: 'Navigate browser to a URL',
|
|
171
|
+
description: 'Navigate browser to a URL via Chrome CDP (port set by MONOBROWSE_CDP_PORT, default 9222). Chrome must already be running with --remote-debugging-port.',
|
|
164
172
|
category: 'browser',
|
|
165
173
|
tags: ['navigation', 'web'],
|
|
166
174
|
inputSchema: {
|
|
167
175
|
type: 'object',
|
|
168
176
|
properties: {
|
|
169
|
-
url: { type: 'string', description: 'URL to navigate to' },
|
|
177
|
+
url: { type: 'string', description: 'URL to navigate to (http/https/about only)' },
|
|
170
178
|
session: { type: 'string', description: 'Session ID (default: "default")' },
|
|
171
179
|
waitUntil: {
|
|
172
180
|
type: 'string',
|
|
173
181
|
enum: ['load', 'domcontentloaded', 'networkidle'],
|
|
174
|
-
description: 'Wait condition',
|
|
182
|
+
description: 'Wait condition after navigation',
|
|
175
183
|
},
|
|
176
184
|
},
|
|
177
185
|
required: ['url'],
|
|
@@ -185,25 +193,15 @@ export const browserTools = [
|
|
|
185
193
|
sessionId = validateSessionId(raw.session);
|
|
186
194
|
}
|
|
187
195
|
catch (e) {
|
|
188
|
-
return
|
|
189
|
-
content: [{ type: 'text', text: JSON.stringify({ success: false, error: e.message }) }],
|
|
190
|
-
isError: true,
|
|
191
|
-
};
|
|
196
|
+
return fail(e.message);
|
|
192
197
|
}
|
|
193
|
-
const args = ['open', url];
|
|
194
|
-
if (raw.waitUntil && typeof raw.waitUntil === 'string'
|
|
195
|
-
&& ['load', 'domcontentloaded', 'networkidle'].includes(raw.waitUntil)) {
|
|
196
|
-
args.push('--wait-until', raw.waitUntil);
|
|
197
|
-
}
|
|
198
|
-
// Always prune expired sessions, not just on create — otherwise
|
|
199
|
-
// sessions that re-use an existing key never trigger eviction.
|
|
200
198
|
pruneExpiredSessions();
|
|
201
199
|
if (!browserSessions.has(sessionId)) {
|
|
202
200
|
if (browserSessions.size >= MAX_BROWSER_SESSIONS) {
|
|
203
201
|
const oldest = [...browserSessions.entries()]
|
|
204
202
|
.sort((a, b) => a[1].lastActivity.localeCompare(b[1].lastActivity))[0];
|
|
205
203
|
if (oldest)
|
|
206
|
-
|
|
204
|
+
releaseConnection(oldest[0]);
|
|
207
205
|
}
|
|
208
206
|
browserSessions.set(sessionId, {
|
|
209
207
|
sessionId,
|
|
@@ -211,7 +209,22 @@ export const browserTools = [
|
|
|
211
209
|
lastActivity: new Date().toISOString(),
|
|
212
210
|
});
|
|
213
211
|
}
|
|
214
|
-
|
|
212
|
+
try {
|
|
213
|
+
const { openUrl, waitForLoad, getCurrentUrl, getCurrentTitle } = await import('@monoes/monobrowse');
|
|
214
|
+
const conn = await getConnection(sessionId);
|
|
215
|
+
await openUrl(conn.client, conn.cdpSessionId, url);
|
|
216
|
+
const condition = raw.waitUntil ?? 'load';
|
|
217
|
+
await waitForLoad(conn.client, conn.cdpSessionId, condition, 30000);
|
|
218
|
+
touchSession(sessionId);
|
|
219
|
+
return ok({
|
|
220
|
+
url: await getCurrentUrl(conn.client, conn.cdpSessionId),
|
|
221
|
+
title: await getCurrentTitle(conn.client, conn.cdpSessionId),
|
|
222
|
+
session: sessionId,
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
catch (e) {
|
|
226
|
+
return fail(e.message);
|
|
227
|
+
}
|
|
215
228
|
},
|
|
216
229
|
},
|
|
217
230
|
{
|
|
@@ -221,13 +234,26 @@ export const browserTools = [
|
|
|
221
234
|
tags: ['navigation'],
|
|
222
235
|
inputSchema: {
|
|
223
236
|
type: 'object',
|
|
224
|
-
properties: {
|
|
225
|
-
session: { type: 'string', description: 'Session ID' },
|
|
226
|
-
},
|
|
237
|
+
properties: { session: { type: 'string', description: 'Session ID' } },
|
|
227
238
|
},
|
|
228
239
|
handler: async (input) => {
|
|
229
240
|
const { session } = input;
|
|
230
|
-
|
|
241
|
+
let sessionId;
|
|
242
|
+
try {
|
|
243
|
+
sessionId = validateSessionId(session);
|
|
244
|
+
}
|
|
245
|
+
catch (e) {
|
|
246
|
+
return fail(e.message);
|
|
247
|
+
}
|
|
248
|
+
try {
|
|
249
|
+
const conn = await getConnection(sessionId);
|
|
250
|
+
await conn.client.send('Page.goBack', {}, conn.cdpSessionId);
|
|
251
|
+
touchSession(sessionId);
|
|
252
|
+
return ok();
|
|
253
|
+
}
|
|
254
|
+
catch (e) {
|
|
255
|
+
return fail(e.message);
|
|
256
|
+
}
|
|
231
257
|
},
|
|
232
258
|
},
|
|
233
259
|
{
|
|
@@ -237,13 +263,26 @@ export const browserTools = [
|
|
|
237
263
|
tags: ['navigation'],
|
|
238
264
|
inputSchema: {
|
|
239
265
|
type: 'object',
|
|
240
|
-
properties: {
|
|
241
|
-
session: { type: 'string', description: 'Session ID' },
|
|
242
|
-
},
|
|
266
|
+
properties: { session: { type: 'string', description: 'Session ID' } },
|
|
243
267
|
},
|
|
244
268
|
handler: async (input) => {
|
|
245
269
|
const { session } = input;
|
|
246
|
-
|
|
270
|
+
let sessionId;
|
|
271
|
+
try {
|
|
272
|
+
sessionId = validateSessionId(session);
|
|
273
|
+
}
|
|
274
|
+
catch (e) {
|
|
275
|
+
return fail(e.message);
|
|
276
|
+
}
|
|
277
|
+
try {
|
|
278
|
+
const conn = await getConnection(sessionId);
|
|
279
|
+
await conn.client.send('Page.goForward', {}, conn.cdpSessionId);
|
|
280
|
+
touchSession(sessionId);
|
|
281
|
+
return ok();
|
|
282
|
+
}
|
|
283
|
+
catch (e) {
|
|
284
|
+
return fail(e.message);
|
|
285
|
+
}
|
|
247
286
|
},
|
|
248
287
|
},
|
|
249
288
|
{
|
|
@@ -253,31 +292,48 @@ export const browserTools = [
|
|
|
253
292
|
tags: ['navigation'],
|
|
254
293
|
inputSchema: {
|
|
255
294
|
type: 'object',
|
|
256
|
-
properties: {
|
|
257
|
-
session: { type: 'string', description: 'Session ID' },
|
|
258
|
-
},
|
|
295
|
+
properties: { session: { type: 'string', description: 'Session ID' } },
|
|
259
296
|
},
|
|
260
297
|
handler: async (input) => {
|
|
261
298
|
const { session } = input;
|
|
262
|
-
|
|
299
|
+
let sessionId;
|
|
300
|
+
try {
|
|
301
|
+
sessionId = validateSessionId(session);
|
|
302
|
+
}
|
|
303
|
+
catch (e) {
|
|
304
|
+
return fail(e.message);
|
|
305
|
+
}
|
|
306
|
+
try {
|
|
307
|
+
const conn = await getConnection(sessionId);
|
|
308
|
+
await conn.client.send('Page.reload', {}, conn.cdpSessionId);
|
|
309
|
+
touchSession(sessionId);
|
|
310
|
+
return ok();
|
|
311
|
+
}
|
|
312
|
+
catch (e) {
|
|
313
|
+
return fail(e.message);
|
|
314
|
+
}
|
|
263
315
|
},
|
|
264
316
|
},
|
|
265
317
|
{
|
|
266
318
|
name: 'browser_close',
|
|
267
|
-
description: 'Close the browser session',
|
|
319
|
+
description: 'Close the browser session (releases CDP connection)',
|
|
268
320
|
category: 'browser',
|
|
269
321
|
tags: ['navigation'],
|
|
270
322
|
inputSchema: {
|
|
271
323
|
type: 'object',
|
|
272
|
-
properties: {
|
|
273
|
-
session: { type: 'string', description: 'Session ID' },
|
|
274
|
-
},
|
|
324
|
+
properties: { session: { type: 'string', description: 'Session ID' } },
|
|
275
325
|
},
|
|
276
326
|
handler: async (input) => {
|
|
277
327
|
const { session } = input;
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
328
|
+
let sessionId;
|
|
329
|
+
try {
|
|
330
|
+
sessionId = validateSessionId(session);
|
|
331
|
+
}
|
|
332
|
+
catch (e) {
|
|
333
|
+
return fail(e.message);
|
|
334
|
+
}
|
|
335
|
+
releaseConnection(sessionId);
|
|
336
|
+
return ok({ session: sessionId });
|
|
281
337
|
},
|
|
282
338
|
},
|
|
283
339
|
// ==========================================================================
|
|
@@ -285,87 +341,87 @@ export const browserTools = [
|
|
|
285
341
|
// ==========================================================================
|
|
286
342
|
{
|
|
287
343
|
name: 'browser_snapshot',
|
|
288
|
-
description: 'Get
|
|
344
|
+
description: 'Get accessibility tree snapshot of the current page. Use element roles, names, and text from the output to target elements in subsequent interaction tools.',
|
|
289
345
|
category: 'browser',
|
|
290
346
|
tags: ['snapshot', 'ai'],
|
|
291
347
|
inputSchema: {
|
|
292
348
|
type: 'object',
|
|
293
349
|
properties: {
|
|
294
350
|
session: { type: 'string', description: 'Session ID' },
|
|
295
|
-
interactive: { type: 'boolean', description: 'Only interactive elements
|
|
296
|
-
compact: { type: 'boolean', description: 'Remove empty structural elements
|
|
297
|
-
depth: { type: 'number', description: 'Limit tree depth
|
|
298
|
-
selector: { type: 'string', description: 'Scope to CSS selector
|
|
351
|
+
interactive: { type: 'boolean', description: 'Only include interactive elements' },
|
|
352
|
+
compact: { type: 'boolean', description: 'Remove empty structural elements' },
|
|
353
|
+
depth: { type: 'number', description: 'Limit tree depth' },
|
|
354
|
+
selector: { type: 'string', description: 'Scope snapshot to CSS selector' },
|
|
299
355
|
},
|
|
300
356
|
},
|
|
301
357
|
handler: async (input) => {
|
|
302
358
|
const raw = input;
|
|
303
|
-
let
|
|
359
|
+
let sessionId;
|
|
304
360
|
try {
|
|
305
|
-
|
|
361
|
+
sessionId = validateSessionId(raw.session);
|
|
306
362
|
}
|
|
307
363
|
catch (e) {
|
|
308
|
-
return
|
|
309
|
-
content: [{ type: 'text', text: JSON.stringify({ success: false, error: e.message }) }],
|
|
310
|
-
isError: true,
|
|
311
|
-
};
|
|
364
|
+
return fail(e.message);
|
|
312
365
|
}
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
366
|
+
try {
|
|
367
|
+
const { captureSnapshot } = await import('@monoes/monobrowse');
|
|
368
|
+
const conn = await getConnection(sessionId);
|
|
369
|
+
let safeSel;
|
|
370
|
+
if (raw.selector !== undefined)
|
|
371
|
+
safeSel = rejectFlagLike(raw.selector, 'selector');
|
|
372
|
+
const result = await captureSnapshot(conn.client, conn.cdpSessionId, {
|
|
373
|
+
selector: safeSel,
|
|
374
|
+
interactiveOnly: raw.interactive ?? false,
|
|
375
|
+
compact: raw.compact ?? true,
|
|
376
|
+
maxDepth: raw.depth,
|
|
377
|
+
});
|
|
378
|
+
touchSession(sessionId);
|
|
379
|
+
return ok({ snapshot: result });
|
|
380
|
+
}
|
|
381
|
+
catch (e) {
|
|
382
|
+
return fail(e.message);
|
|
330
383
|
}
|
|
331
|
-
return execBrowserCommand(args, safeSession);
|
|
332
384
|
},
|
|
333
385
|
},
|
|
334
386
|
{
|
|
335
387
|
name: 'browser_screenshot',
|
|
336
|
-
description: 'Capture screenshot of the page',
|
|
388
|
+
description: 'Capture a screenshot of the current page',
|
|
337
389
|
category: 'browser',
|
|
338
390
|
tags: ['snapshot', 'screenshot'],
|
|
339
391
|
inputSchema: {
|
|
340
392
|
type: 'object',
|
|
341
393
|
properties: {
|
|
342
394
|
session: { type: 'string', description: 'Session ID' },
|
|
343
|
-
path: { type: 'string', description: 'Save path (returns
|
|
344
|
-
fullPage: { type: 'boolean', description: 'Capture full page' },
|
|
395
|
+
path: { type: 'string', description: 'Save path within .monomind/screenshots/ (returns dataUrl if omitted)' },
|
|
396
|
+
fullPage: { type: 'boolean', description: 'Capture full scrollable page' },
|
|
345
397
|
},
|
|
346
398
|
},
|
|
347
399
|
handler: async (input) => {
|
|
348
400
|
const raw = input;
|
|
349
|
-
let
|
|
401
|
+
let sessionId;
|
|
350
402
|
let safePath;
|
|
351
403
|
try {
|
|
352
|
-
|
|
353
|
-
if (raw.path !== undefined)
|
|
404
|
+
sessionId = validateSessionId(raw.session);
|
|
405
|
+
if (raw.path !== undefined)
|
|
354
406
|
safePath = await validateScreenshotPath(raw.path);
|
|
355
|
-
}
|
|
356
407
|
}
|
|
357
408
|
catch (e) {
|
|
358
|
-
return
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
};
|
|
409
|
+
return fail(e.message);
|
|
410
|
+
}
|
|
411
|
+
try {
|
|
412
|
+
const { captureScreenshot } = await import('@monoes/monobrowse');
|
|
413
|
+
const conn = await getConnection(sessionId);
|
|
414
|
+
const { path: savedPath, dataUrl } = await captureScreenshot(conn.client, conn.cdpSessionId, {
|
|
415
|
+
path: safePath,
|
|
416
|
+
fullPage: raw.fullPage === true,
|
|
417
|
+
format: 'png',
|
|
418
|
+
});
|
|
419
|
+
touchSession(sessionId);
|
|
420
|
+
return ok(safePath ? { path: savedPath } : { dataUrl });
|
|
421
|
+
}
|
|
422
|
+
catch (e) {
|
|
423
|
+
return fail(e.message);
|
|
362
424
|
}
|
|
363
|
-
const args = ['screenshot'];
|
|
364
|
-
if (safePath)
|
|
365
|
-
args.push(safePath);
|
|
366
|
-
if (raw.fullPage === true)
|
|
367
|
-
args.push('--full');
|
|
368
|
-
return execBrowserCommand(args, safeSession);
|
|
369
425
|
},
|
|
370
426
|
},
|
|
371
427
|
// ==========================================================================
|
|
@@ -373,149 +429,308 @@ export const browserTools = [
|
|
|
373
429
|
// ==========================================================================
|
|
374
430
|
{
|
|
375
431
|
name: 'browser_click',
|
|
376
|
-
description: 'Click an element
|
|
432
|
+
description: 'Click an element. Use locator="selector" (CSS selector), "role", "text", "label", or "placeholder".',
|
|
377
433
|
category: 'browser',
|
|
378
434
|
tags: ['interaction'],
|
|
379
435
|
inputSchema: {
|
|
380
436
|
type: 'object',
|
|
381
437
|
properties: {
|
|
382
|
-
target: { type: 'string', description: '
|
|
438
|
+
target: { type: 'string', description: 'CSS selector, role name, visible text, label text, or placeholder text' },
|
|
439
|
+
locator: { type: 'string', enum: ['selector', 'role', 'text', 'label', 'placeholder'], description: 'How to find the element (default: selector)' },
|
|
383
440
|
session: { type: 'string', description: 'Session ID' },
|
|
384
|
-
button: { type: 'string', enum: ['left', 'right', 'middle'], description: 'Mouse button' },
|
|
385
|
-
count: { type: 'number', description: 'Click count (2 for double-click)' },
|
|
386
441
|
},
|
|
387
442
|
required: ['target'],
|
|
388
443
|
},
|
|
389
444
|
handler: async (input) => {
|
|
390
|
-
const
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
445
|
+
const raw = input;
|
|
446
|
+
let sessionId;
|
|
447
|
+
let target;
|
|
448
|
+
try {
|
|
449
|
+
sessionId = validateSessionId(raw.session);
|
|
450
|
+
target = rejectFlagLike(raw.target, 'target');
|
|
451
|
+
}
|
|
452
|
+
catch (e) {
|
|
453
|
+
return fail(e.message);
|
|
454
|
+
}
|
|
455
|
+
const locator = typeof raw.locator === 'string' ? raw.locator : 'selector';
|
|
456
|
+
try {
|
|
457
|
+
const { clickElement } = await import('@monoes/monobrowse');
|
|
458
|
+
const conn = await getConnection(sessionId);
|
|
459
|
+
const ref = await findElement(conn, target, locator);
|
|
460
|
+
await clickElement(conn.client, conn.cdpSessionId, ref);
|
|
461
|
+
touchSession(sessionId);
|
|
462
|
+
return ok();
|
|
463
|
+
}
|
|
464
|
+
catch (e) {
|
|
465
|
+
return fail(e.message);
|
|
466
|
+
}
|
|
397
467
|
},
|
|
398
468
|
},
|
|
399
469
|
{
|
|
400
470
|
name: 'browser_fill',
|
|
401
|
-
description: 'Clear and fill an input element',
|
|
471
|
+
description: 'Clear and fill an input element with a value',
|
|
402
472
|
category: 'browser',
|
|
403
473
|
tags: ['interaction', 'form'],
|
|
404
474
|
inputSchema: {
|
|
405
475
|
type: 'object',
|
|
406
476
|
properties: {
|
|
407
|
-
target: { type: 'string', description: '
|
|
477
|
+
target: { type: 'string', description: 'CSS selector, role, text, label, or placeholder' },
|
|
408
478
|
value: { type: 'string', description: 'Value to fill' },
|
|
479
|
+
locator: { type: 'string', enum: ['selector', 'role', 'text', 'label', 'placeholder'], description: 'How to find the element (default: selector)' },
|
|
409
480
|
session: { type: 'string', description: 'Session ID' },
|
|
410
481
|
},
|
|
411
482
|
required: ['target', 'value'],
|
|
412
483
|
},
|
|
413
484
|
handler: async (input) => {
|
|
414
|
-
const
|
|
415
|
-
|
|
485
|
+
const raw = input;
|
|
486
|
+
let sessionId;
|
|
487
|
+
let target;
|
|
488
|
+
try {
|
|
489
|
+
sessionId = validateSessionId(raw.session);
|
|
490
|
+
target = rejectFlagLike(raw.target, 'target');
|
|
491
|
+
}
|
|
492
|
+
catch (e) {
|
|
493
|
+
return fail(e.message);
|
|
494
|
+
}
|
|
495
|
+
if (typeof raw.value !== 'string')
|
|
496
|
+
return fail('value: must be a string');
|
|
497
|
+
const locator = typeof raw.locator === 'string' ? raw.locator : 'selector';
|
|
498
|
+
try {
|
|
499
|
+
const { fillElement } = await import('@monoes/monobrowse');
|
|
500
|
+
const conn = await getConnection(sessionId);
|
|
501
|
+
const ref = await findElement(conn, target, locator);
|
|
502
|
+
await fillElement(conn.client, conn.cdpSessionId, ref, raw.value);
|
|
503
|
+
touchSession(sessionId);
|
|
504
|
+
return ok();
|
|
505
|
+
}
|
|
506
|
+
catch (e) {
|
|
507
|
+
return fail(e.message);
|
|
508
|
+
}
|
|
416
509
|
},
|
|
417
510
|
},
|
|
418
511
|
{
|
|
419
512
|
name: 'browser_type',
|
|
420
|
-
description: 'Type text
|
|
513
|
+
description: 'Type text character-by-character (useful for autocomplete, live-search, etc.)',
|
|
421
514
|
category: 'browser',
|
|
422
515
|
tags: ['interaction', 'form'],
|
|
423
516
|
inputSchema: {
|
|
424
517
|
type: 'object',
|
|
425
518
|
properties: {
|
|
426
|
-
target: { type: 'string', description: '
|
|
519
|
+
target: { type: 'string', description: 'CSS selector, role, text, label, or placeholder' },
|
|
427
520
|
text: { type: 'string', description: 'Text to type' },
|
|
521
|
+
locator: { type: 'string', enum: ['selector', 'role', 'text', 'label', 'placeholder'], description: 'How to find the element (default: selector)' },
|
|
428
522
|
session: { type: 'string', description: 'Session ID' },
|
|
429
|
-
delay: { type: 'number', description: 'Delay between keystrokes (ms)' },
|
|
430
523
|
},
|
|
431
524
|
required: ['target', 'text'],
|
|
432
525
|
},
|
|
433
526
|
handler: async (input) => {
|
|
434
|
-
const
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
527
|
+
const raw = input;
|
|
528
|
+
let sessionId;
|
|
529
|
+
let target;
|
|
530
|
+
try {
|
|
531
|
+
sessionId = validateSessionId(raw.session);
|
|
532
|
+
target = rejectFlagLike(raw.target, 'target');
|
|
533
|
+
}
|
|
534
|
+
catch (e) {
|
|
535
|
+
return fail(e.message);
|
|
536
|
+
}
|
|
537
|
+
if (typeof raw.text !== 'string')
|
|
538
|
+
return fail('text: must be a string');
|
|
539
|
+
const locator = typeof raw.locator === 'string' ? raw.locator : 'selector';
|
|
540
|
+
try {
|
|
541
|
+
const { typeText, fillElement } = await import('@monoes/monobrowse');
|
|
542
|
+
const conn = await getConnection(sessionId);
|
|
543
|
+
const ref = await findElement(conn, target, locator);
|
|
544
|
+
// Focus by clicking (filling with empty string focuses without clearing for type)
|
|
545
|
+
await fillElement(conn.client, conn.cdpSessionId, ref, '');
|
|
546
|
+
await typeText(conn.client, conn.cdpSessionId, raw.text);
|
|
547
|
+
touchSession(sessionId);
|
|
548
|
+
return ok();
|
|
549
|
+
}
|
|
550
|
+
catch (e) {
|
|
551
|
+
return fail(e.message);
|
|
552
|
+
}
|
|
439
553
|
},
|
|
440
554
|
},
|
|
441
555
|
{
|
|
442
556
|
name: 'browser_press',
|
|
443
|
-
description: 'Press a keyboard key',
|
|
557
|
+
description: 'Press a keyboard key or combo (e.g. "Enter", "Tab", "Escape", "Ctrl+A", "Shift+Tab")',
|
|
444
558
|
category: 'browser',
|
|
445
559
|
tags: ['interaction'],
|
|
446
560
|
inputSchema: {
|
|
447
561
|
type: 'object',
|
|
448
562
|
properties: {
|
|
449
|
-
key: { type: 'string', description: 'Key
|
|
563
|
+
key: { type: 'string', description: 'Key name or combo (Enter, Tab, Escape, Ctrl+A, Shift+Tab, etc.)' },
|
|
450
564
|
session: { type: 'string', description: 'Session ID' },
|
|
451
565
|
},
|
|
452
566
|
required: ['key'],
|
|
453
567
|
},
|
|
454
568
|
handler: async (input) => {
|
|
455
|
-
const
|
|
456
|
-
|
|
569
|
+
const raw = input;
|
|
570
|
+
let sessionId;
|
|
571
|
+
let key;
|
|
572
|
+
try {
|
|
573
|
+
sessionId = validateSessionId(raw.session);
|
|
574
|
+
key = rejectFlagLike(raw.key, 'key');
|
|
575
|
+
}
|
|
576
|
+
catch (e) {
|
|
577
|
+
return fail(e.message);
|
|
578
|
+
}
|
|
579
|
+
try {
|
|
580
|
+
const { pressKey, pressKeyCombo } = await import('@monoes/monobrowse');
|
|
581
|
+
const conn = await getConnection(sessionId);
|
|
582
|
+
if (key.includes('+')) {
|
|
583
|
+
const parts = key.split('+').map(k => k.trim());
|
|
584
|
+
const mainKey = parts[parts.length - 1];
|
|
585
|
+
let bits = 0;
|
|
586
|
+
for (const m of parts.slice(0, -1)) {
|
|
587
|
+
switch (m.toLowerCase()) {
|
|
588
|
+
case 'alt':
|
|
589
|
+
bits |= 1;
|
|
590
|
+
break;
|
|
591
|
+
case 'ctrl':
|
|
592
|
+
case 'control':
|
|
593
|
+
bits |= 2;
|
|
594
|
+
break;
|
|
595
|
+
case 'meta':
|
|
596
|
+
case 'cmd':
|
|
597
|
+
bits |= 4;
|
|
598
|
+
break;
|
|
599
|
+
case 'shift':
|
|
600
|
+
bits |= 8;
|
|
601
|
+
break;
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
await pressKeyCombo(conn.client, conn.cdpSessionId, mainKey, bits);
|
|
605
|
+
}
|
|
606
|
+
else {
|
|
607
|
+
await pressKey(conn.client, conn.cdpSessionId, key);
|
|
608
|
+
}
|
|
609
|
+
touchSession(sessionId);
|
|
610
|
+
return ok();
|
|
611
|
+
}
|
|
612
|
+
catch (e) {
|
|
613
|
+
return fail(e.message);
|
|
614
|
+
}
|
|
457
615
|
},
|
|
458
616
|
},
|
|
459
617
|
{
|
|
460
618
|
name: 'browser_hover',
|
|
461
|
-
description: 'Hover over an element',
|
|
619
|
+
description: 'Hover the mouse over an element',
|
|
462
620
|
category: 'browser',
|
|
463
621
|
tags: ['interaction'],
|
|
464
622
|
inputSchema: {
|
|
465
623
|
type: 'object',
|
|
466
624
|
properties: {
|
|
467
|
-
target: { type: 'string', description: '
|
|
625
|
+
target: { type: 'string', description: 'CSS selector, role, text, label, or placeholder' },
|
|
626
|
+
locator: { type: 'string', enum: ['selector', 'role', 'text', 'label', 'placeholder'], description: 'How to find the element (default: selector)' },
|
|
468
627
|
session: { type: 'string', description: 'Session ID' },
|
|
469
628
|
},
|
|
470
629
|
required: ['target'],
|
|
471
630
|
},
|
|
472
631
|
handler: async (input) => {
|
|
473
|
-
const
|
|
474
|
-
|
|
632
|
+
const raw = input;
|
|
633
|
+
let sessionId;
|
|
634
|
+
let target;
|
|
635
|
+
try {
|
|
636
|
+
sessionId = validateSessionId(raw.session);
|
|
637
|
+
target = rejectFlagLike(raw.target, 'target');
|
|
638
|
+
}
|
|
639
|
+
catch (e) {
|
|
640
|
+
return fail(e.message);
|
|
641
|
+
}
|
|
642
|
+
const locator = typeof raw.locator === 'string' ? raw.locator : 'selector';
|
|
643
|
+
try {
|
|
644
|
+
const { hoverElement } = await import('@monoes/monobrowse');
|
|
645
|
+
const conn = await getConnection(sessionId);
|
|
646
|
+
const ref = await findElement(conn, target, locator);
|
|
647
|
+
await hoverElement(conn.client, conn.cdpSessionId, ref);
|
|
648
|
+
touchSession(sessionId);
|
|
649
|
+
return ok();
|
|
650
|
+
}
|
|
651
|
+
catch (e) {
|
|
652
|
+
return fail(e.message);
|
|
653
|
+
}
|
|
475
654
|
},
|
|
476
655
|
},
|
|
477
656
|
{
|
|
478
657
|
name: 'browser_select',
|
|
479
|
-
description: 'Select an option from a dropdown',
|
|
658
|
+
description: 'Select an option from a <select> dropdown by value or label',
|
|
480
659
|
category: 'browser',
|
|
481
660
|
tags: ['interaction', 'form'],
|
|
482
661
|
inputSchema: {
|
|
483
662
|
type: 'object',
|
|
484
663
|
properties: {
|
|
485
|
-
target: { type: 'string', description: '
|
|
486
|
-
value: { type: 'string', description: 'Option value to select' },
|
|
664
|
+
target: { type: 'string', description: 'CSS selector, role, text, label, or placeholder for the <select>' },
|
|
665
|
+
value: { type: 'string', description: 'Option value or visible text to select' },
|
|
666
|
+
locator: { type: 'string', enum: ['selector', 'role', 'text', 'label', 'placeholder'], description: 'How to find the element (default: selector)' },
|
|
487
667
|
session: { type: 'string', description: 'Session ID' },
|
|
488
668
|
},
|
|
489
669
|
required: ['target', 'value'],
|
|
490
670
|
},
|
|
491
671
|
handler: async (input) => {
|
|
492
|
-
const
|
|
493
|
-
|
|
672
|
+
const raw = input;
|
|
673
|
+
let sessionId;
|
|
674
|
+
let target;
|
|
675
|
+
try {
|
|
676
|
+
sessionId = validateSessionId(raw.session);
|
|
677
|
+
target = rejectFlagLike(raw.target, 'target');
|
|
678
|
+
}
|
|
679
|
+
catch (e) {
|
|
680
|
+
return fail(e.message);
|
|
681
|
+
}
|
|
682
|
+
if (typeof raw.value !== 'string')
|
|
683
|
+
return fail('value: must be a string');
|
|
684
|
+
const locator = typeof raw.locator === 'string' ? raw.locator : 'selector';
|
|
685
|
+
try {
|
|
686
|
+
const { selectOption } = await import('@monoes/monobrowse');
|
|
687
|
+
const conn = await getConnection(sessionId);
|
|
688
|
+
const ref = await findElement(conn, target, locator);
|
|
689
|
+
await selectOption(conn.client, conn.cdpSessionId, ref, raw.value);
|
|
690
|
+
touchSession(sessionId);
|
|
691
|
+
return ok();
|
|
692
|
+
}
|
|
693
|
+
catch (e) {
|
|
694
|
+
return fail(e.message);
|
|
695
|
+
}
|
|
494
696
|
},
|
|
495
697
|
},
|
|
496
698
|
{
|
|
497
699
|
name: 'browser_check',
|
|
498
|
-
description: 'Check a checkbox',
|
|
700
|
+
description: 'Check a checkbox or radio button',
|
|
499
701
|
category: 'browser',
|
|
500
702
|
tags: ['interaction', 'form'],
|
|
501
703
|
inputSchema: {
|
|
502
704
|
type: 'object',
|
|
503
705
|
properties: {
|
|
504
|
-
target: { type: 'string', description: '
|
|
706
|
+
target: { type: 'string', description: 'CSS selector, role, text, label, or placeholder' },
|
|
707
|
+
locator: { type: 'string', enum: ['selector', 'role', 'text', 'label', 'placeholder'], description: 'How to find the element (default: selector)' },
|
|
505
708
|
session: { type: 'string', description: 'Session ID' },
|
|
506
709
|
},
|
|
507
710
|
required: ['target'],
|
|
508
711
|
},
|
|
509
712
|
handler: async (input) => {
|
|
510
|
-
const
|
|
713
|
+
const raw = input;
|
|
714
|
+
let sessionId;
|
|
715
|
+
let target;
|
|
511
716
|
try {
|
|
512
|
-
|
|
717
|
+
sessionId = validateSessionId(raw.session);
|
|
718
|
+
target = rejectFlagLike(raw.target, 'target');
|
|
513
719
|
}
|
|
514
720
|
catch (e) {
|
|
515
|
-
return
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
721
|
+
return fail(e.message);
|
|
722
|
+
}
|
|
723
|
+
const locator = typeof raw.locator === 'string' ? raw.locator : 'selector';
|
|
724
|
+
try {
|
|
725
|
+
const { checkElement } = await import('@monoes/monobrowse');
|
|
726
|
+
const conn = await getConnection(sessionId);
|
|
727
|
+
const ref = await findElement(conn, target, locator);
|
|
728
|
+
await checkElement(conn.client, conn.cdpSessionId, ref, true);
|
|
729
|
+
touchSession(sessionId);
|
|
730
|
+
return ok();
|
|
731
|
+
}
|
|
732
|
+
catch (e) {
|
|
733
|
+
return fail(e.message);
|
|
519
734
|
}
|
|
520
735
|
},
|
|
521
736
|
},
|
|
@@ -527,44 +742,79 @@ export const browserTools = [
|
|
|
527
742
|
inputSchema: {
|
|
528
743
|
type: 'object',
|
|
529
744
|
properties: {
|
|
530
|
-
target: { type: 'string', description: '
|
|
745
|
+
target: { type: 'string', description: 'CSS selector, role, text, label, or placeholder' },
|
|
746
|
+
locator: { type: 'string', enum: ['selector', 'role', 'text', 'label', 'placeholder'], description: 'How to find the element (default: selector)' },
|
|
531
747
|
session: { type: 'string', description: 'Session ID' },
|
|
532
748
|
},
|
|
533
749
|
required: ['target'],
|
|
534
750
|
},
|
|
535
751
|
handler: async (input) => {
|
|
536
|
-
const
|
|
752
|
+
const raw = input;
|
|
753
|
+
let sessionId;
|
|
754
|
+
let target;
|
|
755
|
+
try {
|
|
756
|
+
sessionId = validateSessionId(raw.session);
|
|
757
|
+
target = rejectFlagLike(raw.target, 'target');
|
|
758
|
+
}
|
|
759
|
+
catch (e) {
|
|
760
|
+
return fail(e.message);
|
|
761
|
+
}
|
|
762
|
+
const locator = typeof raw.locator === 'string' ? raw.locator : 'selector';
|
|
537
763
|
try {
|
|
538
|
-
|
|
764
|
+
const { checkElement } = await import('@monoes/monobrowse');
|
|
765
|
+
const conn = await getConnection(sessionId);
|
|
766
|
+
const ref = await findElement(conn, target, locator);
|
|
767
|
+
await checkElement(conn.client, conn.cdpSessionId, ref, false);
|
|
768
|
+
touchSession(sessionId);
|
|
769
|
+
return ok();
|
|
539
770
|
}
|
|
540
771
|
catch (e) {
|
|
541
|
-
return
|
|
542
|
-
content: [{ type: 'text', text: JSON.stringify({ success: false, error: e.message }) }],
|
|
543
|
-
isError: true,
|
|
544
|
-
};
|
|
772
|
+
return fail(e.message);
|
|
545
773
|
}
|
|
546
774
|
},
|
|
547
775
|
},
|
|
548
776
|
{
|
|
549
777
|
name: 'browser_scroll',
|
|
550
|
-
description: 'Scroll the page',
|
|
778
|
+
description: 'Scroll the page or a specific element',
|
|
551
779
|
category: 'browser',
|
|
552
780
|
tags: ['interaction'],
|
|
553
781
|
inputSchema: {
|
|
554
782
|
type: 'object',
|
|
555
783
|
properties: {
|
|
556
784
|
direction: { type: 'string', enum: ['up', 'down', 'left', 'right'], description: 'Scroll direction' },
|
|
557
|
-
amount: { type: 'number', description: 'Scroll amount in pixels' },
|
|
785
|
+
amount: { type: 'number', description: 'Scroll amount in pixels (default 300)' },
|
|
786
|
+
target: { type: 'string', description: 'Optional CSS selector to scroll a specific element' },
|
|
558
787
|
session: { type: 'string', description: 'Session ID' },
|
|
559
788
|
},
|
|
560
789
|
required: ['direction'],
|
|
561
790
|
},
|
|
562
791
|
handler: async (input) => {
|
|
563
|
-
const
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
792
|
+
const raw = input;
|
|
793
|
+
let sessionId;
|
|
794
|
+
try {
|
|
795
|
+
sessionId = validateSessionId(raw.session);
|
|
796
|
+
}
|
|
797
|
+
catch (e) {
|
|
798
|
+
return fail(e.message);
|
|
799
|
+
}
|
|
800
|
+
const direction = raw.direction ?? 'down';
|
|
801
|
+
const amount = raw.amount ?? 300;
|
|
802
|
+
try {
|
|
803
|
+
const { scrollElement } = await import('@monoes/monobrowse');
|
|
804
|
+
const conn = await getConnection(sessionId);
|
|
805
|
+
if (raw.target !== undefined) {
|
|
806
|
+
const ref = await findElement(conn, rejectFlagLike(raw.target, 'target'), 'selector');
|
|
807
|
+
await scrollElement(conn.client, conn.cdpSessionId, direction, amount, ref);
|
|
808
|
+
}
|
|
809
|
+
else {
|
|
810
|
+
await scrollElement(conn.client, conn.cdpSessionId, direction, amount);
|
|
811
|
+
}
|
|
812
|
+
touchSession(sessionId);
|
|
813
|
+
return ok();
|
|
814
|
+
}
|
|
815
|
+
catch (e) {
|
|
816
|
+
return fail(e.message);
|
|
817
|
+
}
|
|
568
818
|
},
|
|
569
819
|
},
|
|
570
820
|
// ==========================================================================
|
|
@@ -572,27 +822,39 @@ export const browserTools = [
|
|
|
572
822
|
// ==========================================================================
|
|
573
823
|
{
|
|
574
824
|
name: 'browser_get-text',
|
|
575
|
-
description: 'Get text
|
|
825
|
+
description: 'Get inner text of an element',
|
|
576
826
|
category: 'browser',
|
|
577
827
|
tags: ['info'],
|
|
578
828
|
inputSchema: {
|
|
579
829
|
type: 'object',
|
|
580
830
|
properties: {
|
|
581
|
-
target: { type: 'string', description: '
|
|
831
|
+
target: { type: 'string', description: 'CSS selector, role, text, label, or placeholder' },
|
|
832
|
+
locator: { type: 'string', enum: ['selector', 'role', 'text', 'label', 'placeholder'], description: 'How to find the element (default: selector)' },
|
|
582
833
|
session: { type: 'string', description: 'Session ID' },
|
|
583
834
|
},
|
|
584
835
|
required: ['target'],
|
|
585
836
|
},
|
|
586
837
|
handler: async (input) => {
|
|
587
|
-
const
|
|
838
|
+
const raw = input;
|
|
839
|
+
let sessionId;
|
|
840
|
+
let target;
|
|
588
841
|
try {
|
|
589
|
-
|
|
842
|
+
sessionId = validateSessionId(raw.session);
|
|
843
|
+
target = rejectFlagLike(raw.target, 'target');
|
|
590
844
|
}
|
|
591
845
|
catch (e) {
|
|
592
|
-
return
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
846
|
+
return fail(e.message);
|
|
847
|
+
}
|
|
848
|
+
const locator = typeof raw.locator === 'string' ? raw.locator : 'selector';
|
|
849
|
+
try {
|
|
850
|
+
const conn = await getConnection(sessionId);
|
|
851
|
+
const ref = await findElement(conn, target, locator);
|
|
852
|
+
const res = await conn.client.send('Runtime.callFunctionOn', { objectId: ref.objectId, functionDeclaration: 'function(){return this.innerText??this.textContent??""}', returnByValue: true }, conn.cdpSessionId);
|
|
853
|
+
touchSession(sessionId);
|
|
854
|
+
return ok({ text: res.result.value });
|
|
855
|
+
}
|
|
856
|
+
catch (e) {
|
|
857
|
+
return fail(e.message);
|
|
596
858
|
}
|
|
597
859
|
},
|
|
598
860
|
},
|
|
@@ -604,54 +866,94 @@ export const browserTools = [
|
|
|
604
866
|
inputSchema: {
|
|
605
867
|
type: 'object',
|
|
606
868
|
properties: {
|
|
607
|
-
target: { type: 'string', description: '
|
|
869
|
+
target: { type: 'string', description: 'CSS selector, role, text, label, or placeholder' },
|
|
870
|
+
locator: { type: 'string', enum: ['selector', 'role', 'text', 'label', 'placeholder'], description: 'How to find the element (default: selector)' },
|
|
608
871
|
session: { type: 'string', description: 'Session ID' },
|
|
609
872
|
},
|
|
610
873
|
required: ['target'],
|
|
611
874
|
},
|
|
612
875
|
handler: async (input) => {
|
|
613
|
-
const
|
|
876
|
+
const raw = input;
|
|
877
|
+
let sessionId;
|
|
878
|
+
let target;
|
|
614
879
|
try {
|
|
615
|
-
|
|
880
|
+
sessionId = validateSessionId(raw.session);
|
|
881
|
+
target = rejectFlagLike(raw.target, 'target');
|
|
616
882
|
}
|
|
617
883
|
catch (e) {
|
|
618
|
-
return
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
884
|
+
return fail(e.message);
|
|
885
|
+
}
|
|
886
|
+
const locator = typeof raw.locator === 'string' ? raw.locator : 'selector';
|
|
887
|
+
try {
|
|
888
|
+
const conn = await getConnection(sessionId);
|
|
889
|
+
const ref = await findElement(conn, target, locator);
|
|
890
|
+
const res = await conn.client.send('Runtime.callFunctionOn', { objectId: ref.objectId, functionDeclaration: 'function(){return this.value??""}', returnByValue: true }, conn.cdpSessionId);
|
|
891
|
+
touchSession(sessionId);
|
|
892
|
+
return ok({ value: res.result.value });
|
|
893
|
+
}
|
|
894
|
+
catch (e) {
|
|
895
|
+
return fail(e.message);
|
|
622
896
|
}
|
|
623
897
|
},
|
|
624
898
|
},
|
|
625
899
|
{
|
|
626
900
|
name: 'browser_get-title',
|
|
627
|
-
description: 'Get the page title',
|
|
901
|
+
description: 'Get the current page title',
|
|
628
902
|
category: 'browser',
|
|
629
903
|
tags: ['info'],
|
|
630
904
|
inputSchema: {
|
|
631
905
|
type: 'object',
|
|
632
|
-
properties: {
|
|
633
|
-
session: { type: 'string', description: 'Session ID' },
|
|
634
|
-
},
|
|
906
|
+
properties: { session: { type: 'string', description: 'Session ID' } },
|
|
635
907
|
},
|
|
636
908
|
handler: async (input) => {
|
|
637
909
|
const { session } = input;
|
|
638
|
-
|
|
910
|
+
let sessionId;
|
|
911
|
+
try {
|
|
912
|
+
sessionId = validateSessionId(session);
|
|
913
|
+
}
|
|
914
|
+
catch (e) {
|
|
915
|
+
return fail(e.message);
|
|
916
|
+
}
|
|
917
|
+
try {
|
|
918
|
+
const { getCurrentTitle } = await import('@monoes/monobrowse');
|
|
919
|
+
const conn = await getConnection(sessionId);
|
|
920
|
+
const title = await getCurrentTitle(conn.client, conn.cdpSessionId);
|
|
921
|
+
touchSession(sessionId);
|
|
922
|
+
return ok({ title });
|
|
923
|
+
}
|
|
924
|
+
catch (e) {
|
|
925
|
+
return fail(e.message);
|
|
926
|
+
}
|
|
639
927
|
},
|
|
640
928
|
},
|
|
641
929
|
{
|
|
642
930
|
name: 'browser_get-url',
|
|
643
|
-
description: 'Get the current URL',
|
|
931
|
+
description: 'Get the current page URL',
|
|
644
932
|
category: 'browser',
|
|
645
933
|
tags: ['info'],
|
|
646
934
|
inputSchema: {
|
|
647
935
|
type: 'object',
|
|
648
|
-
properties: {
|
|
649
|
-
session: { type: 'string', description: 'Session ID' },
|
|
650
|
-
},
|
|
936
|
+
properties: { session: { type: 'string', description: 'Session ID' } },
|
|
651
937
|
},
|
|
652
938
|
handler: async (input) => {
|
|
653
939
|
const { session } = input;
|
|
654
|
-
|
|
940
|
+
let sessionId;
|
|
941
|
+
try {
|
|
942
|
+
sessionId = validateSessionId(session);
|
|
943
|
+
}
|
|
944
|
+
catch (e) {
|
|
945
|
+
return fail(e.message);
|
|
946
|
+
}
|
|
947
|
+
try {
|
|
948
|
+
const { getCurrentUrl } = await import('@monoes/monobrowse');
|
|
949
|
+
const conn = await getConnection(sessionId);
|
|
950
|
+
const url = await getCurrentUrl(conn.client, conn.cdpSessionId);
|
|
951
|
+
touchSession(sessionId);
|
|
952
|
+
return ok({ url });
|
|
953
|
+
}
|
|
954
|
+
catch (e) {
|
|
955
|
+
return fail(e.message);
|
|
956
|
+
}
|
|
655
957
|
},
|
|
656
958
|
},
|
|
657
959
|
// ==========================================================================
|
|
@@ -659,42 +961,55 @@ export const browserTools = [
|
|
|
659
961
|
// ==========================================================================
|
|
660
962
|
{
|
|
661
963
|
name: 'browser_wait',
|
|
662
|
-
description: 'Wait for a
|
|
964
|
+
description: 'Wait for a CSS selector to appear, a URL pattern, page load, or a fixed duration',
|
|
663
965
|
category: 'browser',
|
|
664
966
|
tags: ['wait'],
|
|
665
967
|
inputSchema: {
|
|
666
968
|
type: 'object',
|
|
667
969
|
properties: {
|
|
668
970
|
selector: { type: 'string', description: 'CSS selector to wait for' },
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
971
|
+
url: { type: 'string', description: 'URL substring to wait for in current URL' },
|
|
972
|
+
load: { type: 'boolean', description: 'Wait for page load event' },
|
|
973
|
+
duration: { type: 'number', description: 'Wait a fixed number of milliseconds (max 60000)' },
|
|
974
|
+
timeout: { type: 'number', description: 'Timeout in ms for selector/url/load conditions (default 30000)' },
|
|
672
975
|
session: { type: 'string', description: 'Session ID' },
|
|
673
976
|
},
|
|
674
977
|
},
|
|
675
978
|
handler: async (input) => {
|
|
676
979
|
const raw = input;
|
|
677
|
-
|
|
980
|
+
let sessionId;
|
|
678
981
|
try {
|
|
679
|
-
|
|
680
|
-
args.push(rejectFlagLike(raw.selector, 'selector'));
|
|
681
|
-
if (raw.text !== undefined)
|
|
682
|
-
args.push('--text', rejectFlagLike(raw.text, 'text'));
|
|
683
|
-
if (raw.url !== undefined)
|
|
684
|
-
args.push('--url', validateUrl(raw.url));
|
|
982
|
+
sessionId = validateSessionId(raw.session);
|
|
685
983
|
}
|
|
686
984
|
catch (e) {
|
|
687
|
-
return
|
|
688
|
-
content: [{ type: 'text', text: JSON.stringify({ success: false, error: e.message }) }],
|
|
689
|
-
isError: true,
|
|
690
|
-
};
|
|
985
|
+
return fail(e.message);
|
|
691
986
|
}
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
const
|
|
695
|
-
|
|
987
|
+
const timeout = Math.min(Math.max(Number(raw.timeout ?? 30000), 0), 60000);
|
|
988
|
+
try {
|
|
989
|
+
const { waitFor, waitForLoad } = await import('@monoes/monobrowse');
|
|
990
|
+
const conn = await getConnection(sessionId);
|
|
991
|
+
if (raw.duration !== undefined) {
|
|
992
|
+
const ms = Math.min(Math.max(Number(raw.duration), 0), 60000);
|
|
993
|
+
await new Promise(r => setTimeout(r, ms));
|
|
994
|
+
}
|
|
995
|
+
else if (raw.selector !== undefined) {
|
|
996
|
+
await waitFor(conn.client, conn.cdpSessionId, { selector: rejectFlagLike(raw.selector, 'selector'), timeout });
|
|
997
|
+
}
|
|
998
|
+
else if (raw.url !== undefined) {
|
|
999
|
+
await waitFor(conn.client, conn.cdpSessionId, { url: validateUrl(raw.url), timeout });
|
|
1000
|
+
}
|
|
1001
|
+
else if (raw.load) {
|
|
1002
|
+
await waitForLoad(conn.client, conn.cdpSessionId, 'load', timeout);
|
|
1003
|
+
}
|
|
1004
|
+
else {
|
|
1005
|
+
return fail('browser_wait: provide selector, url, load:true, or duration');
|
|
1006
|
+
}
|
|
1007
|
+
touchSession(sessionId);
|
|
1008
|
+
return ok();
|
|
1009
|
+
}
|
|
1010
|
+
catch (e) {
|
|
1011
|
+
return fail(e.message);
|
|
696
1012
|
}
|
|
697
|
-
return execBrowserCommand(args, raw.session);
|
|
698
1013
|
},
|
|
699
1014
|
},
|
|
700
1015
|
// ==========================================================================
|
|
@@ -702,63 +1017,52 @@ export const browserTools = [
|
|
|
702
1017
|
// ==========================================================================
|
|
703
1018
|
{
|
|
704
1019
|
name: 'browser_eval',
|
|
705
|
-
description: 'Execute JavaScript in page context',
|
|
1020
|
+
description: 'Execute JavaScript in page context. Requires MONOMIND_ALLOW_BROWSER_EVAL=1 env var.',
|
|
706
1021
|
category: 'browser',
|
|
707
1022
|
tags: ['eval', 'js'],
|
|
708
1023
|
inputSchema: {
|
|
709
1024
|
type: 'object',
|
|
710
1025
|
properties: {
|
|
711
|
-
script: { type: 'string', description: 'JavaScript
|
|
1026
|
+
script: { type: 'string', description: 'JavaScript expression to evaluate' },
|
|
712
1027
|
session: { type: 'string', description: 'Session ID' },
|
|
713
1028
|
},
|
|
714
1029
|
required: ['script'],
|
|
715
1030
|
},
|
|
716
1031
|
handler: async (input) => {
|
|
717
|
-
// SECURITY: browser_eval runs arbitrary JS
|
|
718
|
-
//
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
// any logged-in site reachable in the active browser session.
|
|
722
|
-
if (process.env.MONOMIND_ALLOW_BROWSER_EVAL !== '1') {
|
|
723
|
-
return {
|
|
724
|
-
content: [{ type: 'text', text: JSON.stringify({
|
|
725
|
-
success: false,
|
|
726
|
-
error: 'browser_eval is disabled by default. Set MONOMIND_ALLOW_BROWSER_EVAL=1 to enable.',
|
|
727
|
-
}) }],
|
|
728
|
-
isError: true,
|
|
729
|
-
};
|
|
1032
|
+
// SECURITY: browser_eval runs arbitrary JS with the browser's session cookies.
|
|
1033
|
+
// Require explicit operator opt-in via env var to prevent SSRF / credential theft.
|
|
1034
|
+
if (process.env['MONOMIND_ALLOW_BROWSER_EVAL'] !== '1') {
|
|
1035
|
+
return fail('browser_eval is disabled by default. Set MONOMIND_ALLOW_BROWSER_EVAL=1 to enable.');
|
|
730
1036
|
}
|
|
731
1037
|
const raw = input;
|
|
732
|
-
if (typeof raw.script !== 'string')
|
|
733
|
-
return
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
}
|
|
738
|
-
if (raw.script.length > MAX_BROWSER_EVAL_BYTES) {
|
|
739
|
-
return {
|
|
740
|
-
content: [{ type: 'text', text: JSON.stringify({ success: false, error: `script: too long (max ${MAX_BROWSER_EVAL_BYTES})` }) }],
|
|
741
|
-
isError: true,
|
|
742
|
-
};
|
|
743
|
-
}
|
|
744
|
-
let safeSession;
|
|
1038
|
+
if (typeof raw.script !== 'string')
|
|
1039
|
+
return fail('script: must be a string');
|
|
1040
|
+
if (raw.script.length > MAX_BROWSER_EVAL_BYTES)
|
|
1041
|
+
return fail(`script: too long (max ${MAX_BROWSER_EVAL_BYTES})`);
|
|
1042
|
+
let sessionId;
|
|
745
1043
|
try {
|
|
746
|
-
|
|
1044
|
+
sessionId = validateSessionId(raw.session);
|
|
747
1045
|
}
|
|
748
1046
|
catch (e) {
|
|
749
|
-
return
|
|
750
|
-
content: [{ type: 'text', text: JSON.stringify({ success: false, error: e.message }) }],
|
|
751
|
-
isError: true,
|
|
752
|
-
};
|
|
1047
|
+
return fail(e.message);
|
|
753
1048
|
}
|
|
754
|
-
// Audit log every eval call
|
|
1049
|
+
// Audit log every eval call
|
|
755
1050
|
try {
|
|
756
1051
|
const crypto = await import('node:crypto');
|
|
757
1052
|
const hash = crypto.createHash('sha256').update(raw.script).digest('hex').slice(0, 16);
|
|
758
|
-
console.error(`[${new Date().toISOString()}] AUDIT browser_eval session=${
|
|
1053
|
+
console.error(`[${new Date().toISOString()}] AUDIT browser_eval session=${sessionId} script_sha256_16=${hash}`);
|
|
759
1054
|
}
|
|
760
1055
|
catch { /* best-effort */ }
|
|
761
|
-
|
|
1056
|
+
try {
|
|
1057
|
+
const { evaluateJs } = await import('@monoes/monobrowse');
|
|
1058
|
+
const conn = await getConnection(sessionId);
|
|
1059
|
+
const result = await evaluateJs(conn.client, conn.cdpSessionId, raw.script);
|
|
1060
|
+
touchSession(sessionId);
|
|
1061
|
+
return ok({ result });
|
|
1062
|
+
}
|
|
1063
|
+
catch (e) {
|
|
1064
|
+
return fail(e.message);
|
|
1065
|
+
}
|
|
762
1066
|
},
|
|
763
1067
|
},
|
|
764
1068
|
// ==========================================================================
|
|
@@ -769,22 +1073,11 @@ export const browserTools = [
|
|
|
769
1073
|
description: 'List active browser sessions',
|
|
770
1074
|
category: 'browser',
|
|
771
1075
|
tags: ['session'],
|
|
772
|
-
inputSchema: {
|
|
773
|
-
type: 'object',
|
|
774
|
-
properties: {},
|
|
775
|
-
},
|
|
1076
|
+
inputSchema: { type: 'object', properties: {} },
|
|
776
1077
|
handler: async () => {
|
|
1078
|
+
pruneExpiredSessions();
|
|
777
1079
|
const sessions = Array.from(browserSessions.values());
|
|
778
|
-
return {
|
|
779
|
-
content: [{
|
|
780
|
-
type: 'text',
|
|
781
|
-
text: JSON.stringify({
|
|
782
|
-
success: true,
|
|
783
|
-
sessions,
|
|
784
|
-
count: sessions.length,
|
|
785
|
-
}, null, 2),
|
|
786
|
-
}],
|
|
787
|
-
};
|
|
1080
|
+
return ok({ sessions, count: sessions.length });
|
|
788
1081
|
},
|
|
789
1082
|
},
|
|
790
1083
|
];
|