@vascend/talos 0.1.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/README.md +202 -0
- package/dist/backend/asset-events.d.ts +31 -0
- package/dist/backend/asset-events.js +149 -0
- package/dist/backend/assets.d.ts +31 -0
- package/dist/backend/assets.js +210 -0
- package/dist/backend/auth.d.ts +18 -0
- package/dist/backend/auth.js +59 -0
- package/dist/backend/config.d.ts +14 -0
- package/dist/backend/config.js +41 -0
- package/dist/backend/conversations.d.ts +21 -0
- package/dist/backend/conversations.js +141 -0
- package/dist/backend/crypto.d.ts +9 -0
- package/dist/backend/crypto.js +68 -0
- package/dist/backend/engine.d.ts +13 -0
- package/dist/backend/engine.js +78 -0
- package/dist/backend/http.d.ts +25 -0
- package/dist/backend/http.js +112 -0
- package/dist/backend/index.d.ts +1 -0
- package/dist/backend/index.js +24 -0
- package/dist/backend/routes.d.ts +14 -0
- package/dist/backend/routes.js +169 -0
- package/dist/backend/sdk.d.ts +12 -0
- package/dist/backend/sdk.js +11 -0
- package/dist/backend/server.d.ts +12 -0
- package/dist/backend/server.js +91 -0
- package/dist/backend/temp-image-store.d.ts +46 -0
- package/dist/backend/temp-image-store.js +197 -0
- package/dist/backend/types.d.ts +103 -0
- package/dist/backend/types.js +29 -0
- package/dist/backend/users.d.ts +13 -0
- package/dist/backend/users.js +76 -0
- package/dist/backend/ws-handler.d.ts +14 -0
- package/dist/backend/ws-handler.js +72 -0
- package/dist/backend/ws.d.ts +19 -0
- package/dist/backend/ws.js +146 -0
- package/dist/core/agent.d.ts +56 -0
- package/dist/core/agent.js +1783 -0
- package/dist/core/background.d.ts +45 -0
- package/dist/core/background.js +140 -0
- package/dist/core/bootstrap.d.ts +45 -0
- package/dist/core/bootstrap.js +294 -0
- package/dist/core/cli-args.d.ts +41 -0
- package/dist/core/cli-args.js +282 -0
- package/dist/core/collapsible-output.d.ts +38 -0
- package/dist/core/collapsible-output.js +143 -0
- package/dist/core/compaction-checkpoint.d.ts +9 -0
- package/dist/core/compaction-checkpoint.js +58 -0
- package/dist/core/compaction.d.ts +42 -0
- package/dist/core/compaction.js +270 -0
- package/dist/core/config.d.ts +49 -0
- package/dist/core/config.js +417 -0
- package/dist/core/context-ledger.d.ts +99 -0
- package/dist/core/context-ledger.js +222 -0
- package/dist/core/context-policy.d.ts +17 -0
- package/dist/core/context-policy.js +81 -0
- package/dist/core/continuation-review.d.ts +23 -0
- package/dist/core/continuation-review.js +64 -0
- package/dist/core/crash-report.d.ts +3 -0
- package/dist/core/crash-report.js +27 -0
- package/dist/core/credentials.d.ts +11 -0
- package/dist/core/credentials.js +183 -0
- package/dist/core/doctor.d.ts +24 -0
- package/dist/core/doctor.js +125 -0
- package/dist/core/effort.d.ts +5 -0
- package/dist/core/effort.js +42 -0
- package/dist/core/env-paths.d.ts +3 -0
- package/dist/core/env-paths.js +38 -0
- package/dist/core/execution-guidance.d.ts +9 -0
- package/dist/core/execution-guidance.js +73 -0
- package/dist/core/execution-target.d.ts +41 -0
- package/dist/core/execution-target.js +69 -0
- package/dist/core/file-coordination.d.ts +66 -0
- package/dist/core/file-coordination.js +176 -0
- package/dist/core/file-lease-store.d.ts +14 -0
- package/dist/core/file-lease-store.js +89 -0
- package/dist/core/image-limits.d.ts +26 -0
- package/dist/core/image-limits.js +205 -0
- package/dist/core/interruptible-completion.d.ts +3 -0
- package/dist/core/interruptible-completion.js +42 -0
- package/dist/core/kingdoms-mode-state.d.ts +24 -0
- package/dist/core/kingdoms-mode-state.js +139 -0
- package/dist/core/licenza.d.ts +38 -0
- package/dist/core/licenza.js +125 -0
- package/dist/core/model-limits.d.ts +2 -0
- package/dist/core/model-limits.js +36 -0
- package/dist/core/output-lifecycle.d.ts +44 -0
- package/dist/core/output-lifecycle.js +190 -0
- package/dist/core/package-update.d.ts +15 -0
- package/dist/core/package-update.js +121 -0
- package/dist/core/permission-queue.d.ts +2 -0
- package/dist/core/permission-queue.js +20 -0
- package/dist/core/prompt-attachment.d.ts +21 -0
- package/dist/core/prompt-attachment.js +123 -0
- package/dist/core/request-plan.d.ts +16 -0
- package/dist/core/request-plan.js +36 -0
- package/dist/core/run-timing.d.ts +17 -0
- package/dist/core/run-timing.js +123 -0
- package/dist/core/self-update.d.ts +17 -0
- package/dist/core/self-update.js +54 -0
- package/dist/core/session-refs.d.ts +27 -0
- package/dist/core/session-refs.js +126 -0
- package/dist/core/session-storage.d.ts +4 -0
- package/dist/core/session-storage.js +48 -0
- package/dist/core/session-title.d.ts +1 -0
- package/dist/core/session-title.js +21 -0
- package/dist/core/session-usage.d.ts +17 -0
- package/dist/core/session-usage.js +74 -0
- package/dist/core/session.d.ts +58 -0
- package/dist/core/session.js +296 -0
- package/dist/core/settings.d.ts +17 -0
- package/dist/core/settings.js +107 -0
- package/dist/core/storage-retention.d.ts +21 -0
- package/dist/core/storage-retention.js +227 -0
- package/dist/core/text-sanitize.d.ts +1 -0
- package/dist/core/text-sanitize.js +35 -0
- package/dist/core/tool-artifacts.d.ts +6 -0
- package/dist/core/tool-artifacts.js +51 -0
- package/dist/core/tool-compress.d.ts +8 -0
- package/dist/core/tool-compress.js +111 -0
- package/dist/core/tool-executor.d.ts +16 -0
- package/dist/core/tool-executor.js +97 -0
- package/dist/core/tool-result-index.d.ts +52 -0
- package/dist/core/tool-result-index.js +327 -0
- package/dist/core/tool-result-store.d.ts +38 -0
- package/dist/core/tool-result-store.js +190 -0
- package/dist/core/tool-scheduler.d.ts +16 -0
- package/dist/core/tool-scheduler.js +35 -0
- package/dist/core/trust.d.ts +21 -0
- package/dist/core/trust.js +95 -0
- package/dist/core/types.d.ts +402 -0
- package/dist/core/types.js +1 -0
- package/dist/core/update-check.d.ts +26 -0
- package/dist/core/update-check.js +89 -0
- package/dist/core/update-process.d.ts +10 -0
- package/dist/core/update-process.js +31 -0
- package/dist/core/user-inbox.d.ts +23 -0
- package/dist/core/user-inbox.js +56 -0
- package/dist/core/user-input.d.ts +10 -0
- package/dist/core/user-input.js +35 -0
- package/dist/core/version.d.ts +1 -0
- package/dist/core/version.js +10 -0
- package/dist/core/vibe-modes.d.ts +38 -0
- package/dist/core/vibe-modes.js +383 -0
- package/dist/core/workspace-roots.d.ts +13 -0
- package/dist/core/workspace-roots.js +91 -0
- package/dist/core/workspace-sessions.d.ts +51 -0
- package/dist/core/workspace-sessions.js +243 -0
- package/dist/cowork/client.d.ts +29 -0
- package/dist/cowork/client.js +49 -0
- package/dist/cowork/engine-bridge.d.ts +22 -0
- package/dist/cowork/engine-bridge.js +98 -0
- package/dist/cowork/index.d.ts +11 -0
- package/dist/cowork/index.js +16 -0
- package/dist/cowork/ipc.d.ts +97 -0
- package/dist/cowork/ipc.js +96 -0
- package/dist/cowork/manager.d.ts +66 -0
- package/dist/cowork/manager.js +325 -0
- package/dist/cowork/store.d.ts +23 -0
- package/dist/cowork/store.js +182 -0
- package/dist/cowork/sync.d.ts +20 -0
- package/dist/cowork/sync.js +164 -0
- package/dist/cowork/types.d.ts +78 -0
- package/dist/cowork/types.js +21 -0
- package/dist/extensions/ext-tools.d.ts +3 -0
- package/dist/extensions/ext-tools.js +73 -0
- package/dist/extensions/loader.d.ts +91 -0
- package/dist/extensions/loader.js +474 -0
- package/dist/extensions/settings.d.ts +20 -0
- package/dist/extensions/settings.js +86 -0
- package/dist/extensions/types.d.ts +219 -0
- package/dist/extensions/types.js +1 -0
- package/dist/horizon/async-queue.d.ts +9 -0
- package/dist/horizon/async-queue.js +46 -0
- package/dist/horizon/engine.d.ts +85 -0
- package/dist/horizon/engine.js +603 -0
- package/dist/horizon/index.d.ts +4 -0
- package/dist/horizon/index.js +2 -0
- package/dist/horizon/main-team.d.ts +163 -0
- package/dist/horizon/main-team.js +774 -0
- package/dist/horizon/types.d.ts +233 -0
- package/dist/horizon/types.js +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +233 -0
- package/dist/mcp/protocol.d.ts +81 -0
- package/dist/mcp/protocol.js +122 -0
- package/dist/package.json +1 -0
- package/dist/packages/packages.d.ts +12 -0
- package/dist/packages/packages.js +323 -0
- package/dist/prompts/templates.d.ts +13 -0
- package/dist/prompts/templates.js +139 -0
- package/dist/providers/anthropic-oauth.d.ts +13 -0
- package/dist/providers/anthropic-oauth.js +124 -0
- package/dist/providers/anthropic.d.ts +27 -0
- package/dist/providers/anthropic.js +526 -0
- package/dist/providers/catalog.d.ts +15 -0
- package/dist/providers/catalog.js +166 -0
- package/dist/providers/effort.d.ts +12 -0
- package/dist/providers/effort.js +102 -0
- package/dist/providers/model-catalog.d.ts +43 -0
- package/dist/providers/model-catalog.js +70 -0
- package/dist/providers/model-effort.d.ts +5 -0
- package/dist/providers/model-effort.js +43 -0
- package/dist/providers/model-registry.d.ts +39 -0
- package/dist/providers/model-registry.js +142 -0
- package/dist/providers/models-dev.d.ts +9 -0
- package/dist/providers/models-dev.js +97 -0
- package/dist/providers/oauth-callback-server.d.ts +28 -0
- package/dist/providers/oauth-callback-server.js +184 -0
- package/dist/providers/oauth-orchestrator.d.ts +58 -0
- package/dist/providers/oauth-orchestrator.js +159 -0
- package/dist/providers/oauth-store.d.ts +12 -0
- package/dist/providers/oauth-store.js +139 -0
- package/dist/providers/open-browser.d.ts +20 -0
- package/dist/providers/open-browser.js +92 -0
- package/dist/providers/openai-compat.d.ts +14 -0
- package/dist/providers/openai-compat.js +523 -0
- package/dist/providers/openai-oauth.d.ts +15 -0
- package/dist/providers/openai-oauth.js +382 -0
- package/dist/providers/openrouter-auth.d.ts +73 -0
- package/dist/providers/openrouter-auth.js +321 -0
- package/dist/providers/openrouter-manual-fallback.d.ts +25 -0
- package/dist/providers/openrouter-manual-fallback.js +161 -0
- package/dist/providers/openrouter.d.ts +15 -0
- package/dist/providers/openrouter.js +107 -0
- package/dist/providers/plan-limits.d.ts +6 -0
- package/dist/providers/plan-limits.js +175 -0
- package/dist/providers/provider-reset.d.ts +4 -0
- package/dist/providers/provider-reset.js +83 -0
- package/dist/providers/quota-limits.d.ts +4 -0
- package/dist/providers/quota-limits.js +33 -0
- package/dist/providers/registry.d.ts +33 -0
- package/dist/providers/registry.js +213 -0
- package/dist/providers/retry.d.ts +37 -0
- package/dist/providers/retry.js +347 -0
- package/dist/rpc/server.d.ts +23 -0
- package/dist/rpc/server.js +222 -0
- package/dist/sdk.d.ts +67 -0
- package/dist/sdk.js +64 -0
- package/dist/skills/skills.d.ts +17 -0
- package/dist/skills/skills.js +155 -0
- package/dist/text-sanitize.js +28 -0
- package/dist/tools/ask-user.d.ts +2 -0
- package/dist/tools/ask-user.js +60 -0
- package/dist/tools/builtin.d.ts +48 -0
- package/dist/tools/builtin.js +2494 -0
- package/dist/tools/capabilities.d.ts +5 -0
- package/dist/tools/capabilities.js +55 -0
- package/dist/tools/computer-capture.d.ts +2 -0
- package/dist/tools/computer-capture.js +91 -0
- package/dist/tools/computer-posix.d.ts +5 -0
- package/dist/tools/computer-posix.js +136 -0
- package/dist/tools/computer-use.d.ts +14 -0
- package/dist/tools/computer-use.js +108 -0
- package/dist/tools/discovery.d.ts +12 -0
- package/dist/tools/discovery.js +205 -0
- package/dist/tools/execute.d.ts +2 -0
- package/dist/tools/execute.js +73 -0
- package/dist/tools/normalize-input.d.ts +17 -0
- package/dist/tools/normalize-input.js +174 -0
- package/dist/tools/output-artifact.d.ts +3 -0
- package/dist/tools/output-artifact.js +80 -0
- package/dist/tools/pagination.d.ts +15 -0
- package/dist/tools/pagination.js +93 -0
- package/dist/tools/plan-status.d.ts +2 -0
- package/dist/tools/plan-status.js +109 -0
- package/dist/tools/program-input.d.ts +3 -0
- package/dist/tools/program-input.js +19 -0
- package/dist/tools/program.d.ts +3 -0
- package/dist/tools/program.js +567 -0
- package/dist/tools/protocol-catalog.d.ts +12 -0
- package/dist/tools/protocol-catalog.js +46 -0
- package/dist/tools/ramification-tools.d.ts +12 -0
- package/dist/tools/ramification-tools.js +528 -0
- package/dist/tools/regex-compat.d.ts +9 -0
- package/dist/tools/regex-compat.js +96 -0
- package/dist/tools/registry.d.ts +51 -0
- package/dist/tools/registry.js +185 -0
- package/dist/tools/resolve.d.ts +2 -0
- package/dist/tools/resolve.js +107 -0
- package/dist/tools/shell-env.d.ts +2 -0
- package/dist/tools/shell-env.js +7 -0
- package/dist/tools/team-input.d.ts +3 -0
- package/dist/tools/team-input.js +129 -0
- package/dist/tools/team-tools.d.ts +7 -0
- package/dist/tools/team-tools.js +348 -0
- package/dist/tools/workspace-sessions.d.ts +2 -0
- package/dist/tools/workspace-sessions.js +35 -0
- package/dist/tui/account-entry.d.ts +1 -0
- package/dist/tui/account-entry.js +93 -0
- package/dist/tui/attachment-highlight.d.ts +1 -0
- package/dist/tui/attachment-highlight.js +24 -0
- package/dist/tui/attachment-refs.d.ts +2 -0
- package/dist/tui/attachment-refs.js +3 -0
- package/dist/tui/bottom-input.d.ts +219 -0
- package/dist/tui/bottom-input.js +1958 -0
- package/dist/tui/branding.d.ts +107 -0
- package/dist/tui/branding.js +750 -0
- package/dist/tui/clipboard-image.d.ts +10 -0
- package/dist/tui/clipboard-image.js +163 -0
- package/dist/tui/clipboard-text.d.ts +9 -0
- package/dist/tui/clipboard-text.js +51 -0
- package/dist/tui/clipboard-windows.d.ts +8 -0
- package/dist/tui/clipboard-windows.js +84 -0
- package/dist/tui/collapsible-command.d.ts +15 -0
- package/dist/tui/collapsible-command.js +48 -0
- package/dist/tui/command-feedback.d.ts +10 -0
- package/dist/tui/command-feedback.js +46 -0
- package/dist/tui/command-output-registry.d.ts +28 -0
- package/dist/tui/command-output-registry.js +103 -0
- package/dist/tui/command-transcript.d.ts +12 -0
- package/dist/tui/command-transcript.js +90 -0
- package/dist/tui/commands.d.ts +20 -0
- package/dist/tui/commands.js +131 -0
- package/dist/tui/context-bar.d.ts +4 -0
- package/dist/tui/context-bar.js +74 -0
- package/dist/tui/context-meter.d.ts +25 -0
- package/dist/tui/context-meter.js +81 -0
- package/dist/tui/cost-binding.d.ts +8 -0
- package/dist/tui/cost-binding.js +43 -0
- package/dist/tui/cost-tracker.d.ts +49 -0
- package/dist/tui/cost-tracker.js +122 -0
- package/dist/tui/diff.d.ts +9 -0
- package/dist/tui/diff.js +119 -0
- package/dist/tui/i18n-catalog.d.ts +5 -0
- package/dist/tui/i18n-catalog.js +184 -0
- package/dist/tui/i18n.d.ts +6 -0
- package/dist/tui/i18n.js +10 -0
- package/dist/tui/image-attachments.d.ts +46 -0
- package/dist/tui/image-attachments.js +167 -0
- package/dist/tui/images.d.ts +12 -0
- package/dist/tui/images.js +87 -0
- package/dist/tui/intro.d.ts +13 -0
- package/dist/tui/intro.js +109 -0
- package/dist/tui/language-preference.d.ts +6 -0
- package/dist/tui/language-preference.js +24 -0
- package/dist/tui/live-output.d.ts +2 -0
- package/dist/tui/live-output.js +18 -0
- package/dist/tui/locales.d.ts +19 -0
- package/dist/tui/locales.js +22 -0
- package/dist/tui/login-menu.d.ts +17 -0
- package/dist/tui/login-menu.js +58 -0
- package/dist/tui/logo.d.ts +8 -0
- package/dist/tui/logo.js +305 -0
- package/dist/tui/markdown-inline.d.ts +3 -0
- package/dist/tui/markdown-inline.js +99 -0
- package/dist/tui/markdown-table.d.ts +4 -0
- package/dist/tui/markdown-table.js +63 -0
- package/dist/tui/markdown.d.ts +11 -0
- package/dist/tui/markdown.js +257 -0
- package/dist/tui/mcp-transcript.d.ts +7 -0
- package/dist/tui/mcp-transcript.js +40 -0
- package/dist/tui/model-details.d.ts +2 -0
- package/dist/tui/model-details.js +21 -0
- package/dist/tui/model-selection.d.ts +7 -0
- package/dist/tui/model-selection.js +41 -0
- package/dist/tui/native-scrollback.d.ts +27 -0
- package/dist/tui/native-scrollback.js +134 -0
- package/dist/tui/onboarding.d.ts +72 -0
- package/dist/tui/onboarding.js +207 -0
- package/dist/tui/paste-collapse.d.ts +39 -0
- package/dist/tui/paste-collapse.js +117 -0
- package/dist/tui/perm-mode.d.ts +8 -0
- package/dist/tui/perm-mode.js +41 -0
- package/dist/tui/prompt-queue-store.d.ts +26 -0
- package/dist/tui/prompt-queue-store.js +87 -0
- package/dist/tui/prompt-queue.d.ts +47 -0
- package/dist/tui/prompt-queue.js +187 -0
- package/dist/tui/provider-menu.d.ts +33 -0
- package/dist/tui/provider-menu.js +175 -0
- package/dist/tui/question.d.ts +24 -0
- package/dist/tui/question.js +32 -0
- package/dist/tui/queue-screen.d.ts +25 -0
- package/dist/tui/queue-screen.js +152 -0
- package/dist/tui/queue-team.d.ts +3 -0
- package/dist/tui/queue-team.js +28 -0
- package/dist/tui/queued-questions.d.ts +20 -0
- package/dist/tui/queued-questions.js +163 -0
- package/dist/tui/quick-help.d.ts +1 -0
- package/dist/tui/quick-help.js +53 -0
- package/dist/tui/repl.d.ts +78 -0
- package/dist/tui/repl.js +4130 -0
- package/dist/tui/restore-queued-input.d.ts +3 -0
- package/dist/tui/restore-queued-input.js +20 -0
- package/dist/tui/review-detail.d.ts +6 -0
- package/dist/tui/review-detail.js +56 -0
- package/dist/tui/review-files.d.ts +3 -0
- package/dist/tui/review-files.js +22 -0
- package/dist/tui/review-render.d.ts +22 -0
- package/dist/tui/review-render.js +98 -0
- package/dist/tui/review-screen.d.ts +38 -0
- package/dist/tui/review-screen.js +358 -0
- package/dist/tui/review-store.d.ts +60 -0
- package/dist/tui/review-store.js +270 -0
- package/dist/tui/review-team.d.ts +2 -0
- package/dist/tui/review-team.js +27 -0
- package/dist/tui/selector-mouse.d.ts +26 -0
- package/dist/tui/selector-mouse.js +65 -0
- package/dist/tui/selector.d.ts +51 -0
- package/dist/tui/selector.js +299 -0
- package/dist/tui/session-replay.d.ts +10 -0
- package/dist/tui/session-replay.js +78 -0
- package/dist/tui/session-usage.d.ts +22 -0
- package/dist/tui/session-usage.js +55 -0
- package/dist/tui/skill-transcript.d.ts +5 -0
- package/dist/tui/skill-transcript.js +31 -0
- package/dist/tui/startup.d.ts +5 -0
- package/dist/tui/startup.js +61 -0
- package/dist/tui/status-line-loader.d.ts +8 -0
- package/dist/tui/status-line-loader.js +33 -0
- package/dist/tui/status-line.d.ts +38 -0
- package/dist/tui/status-line.js +213 -0
- package/dist/tui/system-language.d.ts +8 -0
- package/dist/tui/system-language.js +41 -0
- package/dist/tui/task-labels.d.ts +2 -0
- package/dist/tui/task-labels.js +7 -0
- package/dist/tui/term-bg.d.ts +4 -0
- package/dist/tui/term-bg.js +67 -0
- package/dist/tui/terminal-screen.d.ts +12 -0
- package/dist/tui/terminal-screen.js +1 -0
- package/dist/tui/terminal-text.d.ts +7 -0
- package/dist/tui/terminal-text.js +123 -0
- package/dist/tui/theme.d.ts +25 -0
- package/dist/tui/theme.js +383 -0
- package/dist/tui/tool-style.d.ts +7 -0
- package/dist/tui/tool-style.js +119 -0
- package/dist/tui/update-prompt.d.ts +14 -0
- package/dist/tui/update-prompt.js +104 -0
- package/dist/tui/usage-footer.d.ts +9 -0
- package/dist/tui/usage-footer.js +123 -0
- package/dist/tui/vascend-login.d.ts +68 -0
- package/dist/tui/vascend-login.js +355 -0
- package/dist/tui/web-login.d.ts +28 -0
- package/dist/tui/web-login.js +115 -0
- package/dist/vascend/activity-status.d.ts +12 -0
- package/dist/vascend/activity-status.js +68 -0
- package/dist/vascend/architecture-map.d.ts +73 -0
- package/dist/vascend/architecture-map.js +424 -0
- package/dist/vascend/attention-layout.d.ts +19 -0
- package/dist/vascend/attention-layout.js +141 -0
- package/dist/vascend/budget-allocator.d.ts +31 -0
- package/dist/vascend/budget-allocator.js +79 -0
- package/dist/vascend/candidate-collector.d.ts +20 -0
- package/dist/vascend/candidate-collector.js +220 -0
- package/dist/vascend/code-events.d.ts +24 -0
- package/dist/vascend/code-events.js +304 -0
- package/dist/vascend/code-graph-memory.d.ts +38 -0
- package/dist/vascend/code-graph-memory.js +334 -0
- package/dist/vascend/code-graph-runtime.d.ts +19 -0
- package/dist/vascend/code-graph-runtime.js +190 -0
- package/dist/vascend/code-graph-types.d.ts +58 -0
- package/dist/vascend/code-graph-types.js +50 -0
- package/dist/vascend/code-parser.d.ts +4 -0
- package/dist/vascend/code-parser.js +257 -0
- package/dist/vascend/competitor-knowledge.d.ts +53 -0
- package/dist/vascend/competitor-knowledge.js +262 -0
- package/dist/vascend/completeness.d.ts +16 -0
- package/dist/vascend/completeness.js +90 -0
- package/dist/vascend/context-autotuner.d.ts +45 -0
- package/dist/vascend/context-autotuner.js +231 -0
- package/dist/vascend/context-candidate.d.ts +45 -0
- package/dist/vascend/context-candidate.js +150 -0
- package/dist/vascend/context-capsule.d.ts +51 -0
- package/dist/vascend/context-capsule.js +133 -0
- package/dist/vascend/context-fabric.d.ts +108 -0
- package/dist/vascend/context-fabric.js +480 -0
- package/dist/vascend/context-feedback-loop.d.ts +56 -0
- package/dist/vascend/context-feedback-loop.js +204 -0
- package/dist/vascend/context-learning-identity.d.ts +4 -0
- package/dist/vascend/context-learning-identity.js +8 -0
- package/dist/vascend/context-links.d.ts +14 -0
- package/dist/vascend/context-links.js +83 -0
- package/dist/vascend/context-lint.d.ts +22 -0
- package/dist/vascend/context-lint.js +91 -0
- package/dist/vascend/context-market-selection.d.ts +17 -0
- package/dist/vascend/context-market-selection.js +50 -0
- package/dist/vascend/context-metrics.d.ts +25 -0
- package/dist/vascend/context-metrics.js +57 -0
- package/dist/vascend/context-pack-render.d.ts +16 -0
- package/dist/vascend/context-pack-render.js +52 -0
- package/dist/vascend/context-pareto.d.ts +33 -0
- package/dist/vascend/context-pareto.js +165 -0
- package/dist/vascend/context-pattern-explorer.d.ts +41 -0
- package/dist/vascend/context-pattern-explorer.js +276 -0
- package/dist/vascend/context-regret.d.ts +32 -0
- package/dist/vascend/context-regret.js +93 -0
- package/dist/vascend/context-retriever.d.ts +22 -0
- package/dist/vascend/context-retriever.js +243 -0
- package/dist/vascend/context-scope-index.d.ts +5 -0
- package/dist/vascend/context-scope-index.js +96 -0
- package/dist/vascend/context-scorer.d.ts +35 -0
- package/dist/vascend/context-scorer.js +162 -0
- package/dist/vascend/context-search-index.d.ts +12 -0
- package/dist/vascend/context-search-index.js +25 -0
- package/dist/vascend/context-snapshot.d.ts +22 -0
- package/dist/vascend/context-snapshot.js +135 -0
- package/dist/vascend/context-window-compiler.d.ts +42 -0
- package/dist/vascend/context-window-compiler.js +237 -0
- package/dist/vascend/contract-memory.d.ts +61 -0
- package/dist/vascend/contract-memory.js +435 -0
- package/dist/vascend/council.d.ts +58 -0
- package/dist/vascend/council.js +337 -0
- package/dist/vascend/decision-promotion.d.ts +48 -0
- package/dist/vascend/decision-promotion.js +410 -0
- package/dist/vascend/delta-context.d.ts +12 -0
- package/dist/vascend/delta-context.js +36 -0
- package/dist/vascend/dev-context-compiler.d.ts +27 -0
- package/dist/vascend/dev-context-compiler.js +133 -0
- package/dist/vascend/dev-context-layout.d.ts +30 -0
- package/dist/vascend/dev-context-layout.js +262 -0
- package/dist/vascend/evidence-slots.d.ts +14 -0
- package/dist/vascend/evidence-slots.js +55 -0
- package/dist/vascend/expansion-levels.d.ts +15 -0
- package/dist/vascend/expansion-levels.js +178 -0
- package/dist/vascend/file-lock.d.ts +7 -0
- package/dist/vascend/file-lock.js +95 -0
- package/dist/vascend/granular-kingdom.d.ts +25 -0
- package/dist/vascend/granular-kingdom.js +259 -0
- package/dist/vascend/granular-launcher.d.ts +48 -0
- package/dist/vascend/granular-launcher.js +202 -0
- package/dist/vascend/kingdom-bus.d.ts +25 -0
- package/dist/vascend/kingdom-bus.js +54 -0
- package/dist/vascend/kingdom-channel.d.ts +22 -0
- package/dist/vascend/kingdom-channel.js +180 -0
- package/dist/vascend/kingdom-kpi.d.ts +48 -0
- package/dist/vascend/kingdom-kpi.js +172 -0
- package/dist/vascend/kingdom-live.d.ts +28 -0
- package/dist/vascend/kingdom-live.js +61 -0
- package/dist/vascend/kingdom-run.d.ts +18 -0
- package/dist/vascend/kingdom-run.js +506 -0
- package/dist/vascend/kingdom-scribe.d.ts +20 -0
- package/dist/vascend/kingdom-scribe.js +52 -0
- package/dist/vascend/kingdom.d.ts +79 -0
- package/dist/vascend/kingdom.js +870 -0
- package/dist/vascend/lexical-index.d.ts +10 -0
- package/dist/vascend/lexical-index.js +99 -0
- package/dist/vascend/lexical-search.d.ts +29 -0
- package/dist/vascend/lexical-search.js +262 -0
- package/dist/vascend/long-term-context.d.ts +51 -0
- package/dist/vascend/long-term-context.js +194 -0
- package/dist/vascend/mandate.d.ts +33 -0
- package/dist/vascend/mandate.js +107 -0
- package/dist/vascend/memory-packet.d.ts +13 -0
- package/dist/vascend/memory-packet.js +155 -0
- package/dist/vascend/memory-status.d.ts +22 -0
- package/dist/vascend/memory-status.js +73 -0
- package/dist/vascend/memory-store.d.ts +62 -0
- package/dist/vascend/memory-store.js +273 -0
- package/dist/vascend/objective-revision.d.ts +69 -0
- package/dist/vascend/objective-revision.js +319 -0
- package/dist/vascend/patch-plan-store.d.ts +33 -0
- package/dist/vascend/patch-plan-store.js +240 -0
- package/dist/vascend/patch-plan.d.ts +97 -0
- package/dist/vascend/patch-plan.js +345 -0
- package/dist/vascend/plan-progress.d.ts +11 -0
- package/dist/vascend/plan-progress.js +68 -0
- package/dist/vascend/plan-store.d.ts +64 -0
- package/dist/vascend/plan-store.js +460 -0
- package/dist/vascend/proof-chains.d.ts +19 -0
- package/dist/vascend/proof-chains.js +120 -0
- package/dist/vascend/ramification-store.d.ts +25 -0
- package/dist/vascend/ramification-store.js +94 -0
- package/dist/vascend/ramification.d.ts +193 -0
- package/dist/vascend/ramification.js +583 -0
- package/dist/vascend/recall-planner.d.ts +17 -0
- package/dist/vascend/recall-planner.js +109 -0
- package/dist/vascend/requirements.d.ts +31 -0
- package/dist/vascend/requirements.js +151 -0
- package/dist/vascend/reviewer.d.ts +9 -0
- package/dist/vascend/reviewer.js +93 -0
- package/dist/vascend/room-graph.d.ts +29 -0
- package/dist/vascend/room-graph.js +242 -0
- package/dist/vascend/room-memory.d.ts +57 -0
- package/dist/vascend/room-memory.js +631 -0
- package/dist/vascend/runtime.d.ts +84 -0
- package/dist/vascend/runtime.js +286 -0
- package/dist/vascend/self-improve.d.ts +104 -0
- package/dist/vascend/self-improve.js +396 -0
- package/dist/vascend/session-search.d.ts +52 -0
- package/dist/vascend/session-search.js +349 -0
- package/dist/vascend/supersession-graph.d.ts +14 -0
- package/dist/vascend/supersession-graph.js +70 -0
- package/dist/vascend/team-areas.d.ts +9 -0
- package/dist/vascend/team-areas.js +28 -0
- package/dist/vascend/team-handoff.d.ts +24 -0
- package/dist/vascend/team-handoff.js +37 -0
- package/dist/vascend/team-scheduler.d.ts +13 -0
- package/dist/vascend/team-scheduler.js +74 -0
- package/dist/vascend/team-session.d.ts +42 -0
- package/dist/vascend/team-session.js +142 -0
- package/dist/vascend/test-failure-memory.d.ts +76 -0
- package/dist/vascend/test-failure-memory.js +354 -0
- package/dist/vascend/token-market.d.ts +42 -0
- package/dist/vascend/token-market.js +144 -0
- package/dist/vascend/tuner-baseline-store.d.ts +52 -0
- package/dist/vascend/tuner-baseline-store.js +446 -0
- package/dist/vascend/user-profile.d.ts +44 -0
- package/dist/vascend/user-profile.js +131 -0
- package/dist/vascend/vision.d.ts +94 -0
- package/dist/vascend/vision.js +375 -0
- package/dist/vascend/work-agent.d.ts +12 -0
- package/dist/vascend/work-agent.js +83 -0
- package/dist/vascend/work-queue.d.ts +62 -0
- package/dist/vascend/work-queue.js +255 -0
- package/dist/vascend/work-runner.d.ts +64 -0
- package/dist/vascend/work-runner.js +137 -0
- package/dist/vascend/worktree.d.ts +38 -0
- package/dist/vascend/worktree.js +206 -0
- package/dist/vendor/computer-use/README.md +7 -0
- package/dist/vendor/computer-use/computer-desktop.js +211 -0
- package/dist/vendor/computer-use/computer-observe.js +315 -0
- package/dist/vendor/computer-use/computer-scripts.js +1230 -0
- package/dist/vendor/computer-use/computer-tools.js +2136 -0
- package/dist/vendor/computer-use/package.json +1 -0
- package/dist/vendor/computer-use/rac/atomic-input.js +91 -0
- package/dist/vendor/computer-use/rac/grammatica.js +101 -0
- package/dist/vendor/computer-use/rac/parser.js +185 -0
- package/dist/vendor/computer-use/rac/protocol.js +136 -0
- package/install.ps1 +30 -0
- package/install.sh +50 -0
- package/package.json +56 -0
|
@@ -0,0 +1,1230 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Layer NATIVO di Computer Use: gli script PowerShell e i loro runner. Vive
|
|
4
|
+
// separato dalle definizioni dei tool perche' qui non c'e' nessuna politica,
|
|
5
|
+
// solo l'accesso a user32 e a UI Automation. Gli script sono fissi e ricevono
|
|
6
|
+
// soltanto numeri validati o un payload su stdin: nessun input dell'utente
|
|
7
|
+
// viene mai interpolato come codice.
|
|
8
|
+
|
|
9
|
+
const { spawn } = require('child_process');
|
|
10
|
+
const fs = require('fs/promises');
|
|
11
|
+
const os = require('os');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
|
|
14
|
+
const MAX_TEXT = 12000;
|
|
15
|
+
// Oltre questa soglia la digitazione carattere per carattere non arriva in
|
|
16
|
+
// tempo: il ritmo umano su 2000 caratteri supererebbe il timeout. Sopra la
|
|
17
|
+
// soglia si incolla dagli appunti (e li si ripristina).
|
|
18
|
+
const PASTE_THRESHOLD = 400;
|
|
19
|
+
|
|
20
|
+
const VK = Object.freeze({
|
|
21
|
+
BACKSPACE: 0x08, TAB: 0x09, CLEAR: 0x0C, ENTER: 0x0D, RETURN: 0x0D,
|
|
22
|
+
SHIFT: 0x10, CTRL: 0x11, CONTROL: 0x11, ALT: 0x12, MENU: 0x12,
|
|
23
|
+
PAUSE: 0x13, CAPSLOCK: 0x14, ESC: 0x1B, ESCAPE: 0x1B, SPACE: 0x20,
|
|
24
|
+
PAGEUP: 0x21, PGUP: 0x21, PAGEDOWN: 0x22, PGDN: 0x22,
|
|
25
|
+
END: 0x23, HOME: 0x24, LEFT: 0x25, UP: 0x26, RIGHT: 0x27, DOWN: 0x28,
|
|
26
|
+
PRINTSCREEN: 0x2C, INSERT: 0x2D, DELETE: 0x2E, DEL: 0x2E,
|
|
27
|
+
META: 0x5B, WIN: 0x5B, WINDOWS: 0x5B, SUPER: 0x5B, LWIN: 0x5B, RWIN: 0x5C, APPS: 0x5D,
|
|
28
|
+
NUMPAD0: 0x60, NUMPAD1: 0x61, NUMPAD2: 0x62, NUMPAD3: 0x63, NUMPAD4: 0x64,
|
|
29
|
+
NUMPAD5: 0x65, NUMPAD6: 0x66, NUMPAD7: 0x67, NUMPAD8: 0x68, NUMPAD9: 0x69,
|
|
30
|
+
MULTIPLY: 0x6A, ADD: 0x6B, SUBTRACT: 0x6D, DECIMAL: 0x6E, DIVIDE: 0x6F,
|
|
31
|
+
F1: 0x70, F2: 0x71, F3: 0x72, F4: 0x73, F5: 0x74, F6: 0x75,
|
|
32
|
+
F7: 0x76, F8: 0x77, F9: 0x78, F10: 0x79, F11: 0x7A, F12: 0x7B,
|
|
33
|
+
NUMLOCK: 0x90, SCROLLLOCK: 0x91,
|
|
34
|
+
// Tasti multimediali: agiscono sull'app in primo piano o sul player attivo
|
|
35
|
+
// senza bisogno di collegare una finestra.
|
|
36
|
+
MUTE: 0xAD, VOLUMEDOWN: 0xAE, VOLUMEUP: 0xAF,
|
|
37
|
+
MEDIANEXT: 0xB0, MEDIAPREV: 0xB1, MEDIASTOP: 0xB2, MEDIAPLAY: 0xB3,
|
|
38
|
+
SEMICOLON: 0xBA, EQUAL: 0xBB, COMMA: 0xBC, MINUS: 0xBD, PERIOD: 0xBE,
|
|
39
|
+
SLASH: 0xBF, BACKTICK: 0xC0, LBRACKET: 0xDB, BACKSLASH: 0xDC,
|
|
40
|
+
RBRACKET: 0xDD, QUOTE: 0xDE,
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
// I tasti estesi vanno inviati con KEYEVENTF_EXTENDEDKEY: senza il flag alcune
|
|
44
|
+
// applicazioni (Electron e i controlli WPF in particolare) ignorano frecce,
|
|
45
|
+
// PagUp/PagGiu, Canc e i tasti multimediali.
|
|
46
|
+
const EXTENDED_VK = new Set([
|
|
47
|
+
0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x2C, 0x2D, 0x2E,
|
|
48
|
+
0x5B, 0x5C, 0x5D, 0x90, 0xAD, 0xAE, 0xAF, 0xB0, 0xB1, 0xB2, 0xB3, 0x6F,
|
|
49
|
+
]);
|
|
50
|
+
|
|
51
|
+
const ALIAS = Object.freeze({ PLUS: 'ADD', SPACEBAR: 'SPACE', ESCAPE_KEY: 'ESC', CMD: 'WIN' });
|
|
52
|
+
|
|
53
|
+
// Una scorciatoia sola: ['CTRL','L'] -> [0x11, 0x4c].
|
|
54
|
+
function keyCodes(value) {
|
|
55
|
+
const raw = Array.isArray(value) ? value : String(value || '').split(/[+\s]+/);
|
|
56
|
+
const out = [];
|
|
57
|
+
for (const item of raw.slice(0, 8)) {
|
|
58
|
+
const key = String(item || '').trim().toUpperCase().replace(/[\s_-]+/g, '');
|
|
59
|
+
if (!key) continue;
|
|
60
|
+
let code = VK[ALIAS[key] || key];
|
|
61
|
+
if (code == null && /^[A-Z0-9]$/.test(key)) code = key.charCodeAt(0);
|
|
62
|
+
if (code == null) return [];
|
|
63
|
+
if (!out.includes(code)) out.push(code);
|
|
64
|
+
}
|
|
65
|
+
return out;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Sequenza di scorciatoie: ['CTRL+A', 'DELETE'] oppure [['CTRL','A'], 'DELETE'].
|
|
69
|
+
// Serve a chiudere in una sola chiamata cio' che altrimenti diventa una catena
|
|
70
|
+
// di tool call identiche ("seleziona tutto", "cancella", "conferma").
|
|
71
|
+
function keyChords(value) {
|
|
72
|
+
const list = Array.isArray(value) ? value : [value];
|
|
73
|
+
const flatChord = list.every((item) => typeof item === 'string' && !/[+\s]/.test(String(item).trim()))
|
|
74
|
+
&& list.length > 1 && keyCodes(list).length === list.length;
|
|
75
|
+
const chords = flatChord ? [list] : list;
|
|
76
|
+
const out = [];
|
|
77
|
+
for (const chord of chords.slice(0, 12)) {
|
|
78
|
+
const codes = keyCodes(chord);
|
|
79
|
+
if (!codes.length) return [];
|
|
80
|
+
out.push(codes);
|
|
81
|
+
}
|
|
82
|
+
return out;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function keyFlags(code, up) {
|
|
86
|
+
return (EXTENDED_VK.has(code) ? 0x0001 : 0) | (up ? 0x0002 : 0);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function chordScript(codes) {
|
|
90
|
+
const press = codes.map((code) => `[VascendComputerInput]::keybd_event(${code},0,${keyFlags(code, false)},[UIntPtr]::Zero)`);
|
|
91
|
+
const release = codes.slice().reverse().map((code) => `[VascendComputerInput]::keybd_event(${code},0,${keyFlags(code, true)},[UIntPtr]::Zero)`);
|
|
92
|
+
return press.concat(release).join('\n');
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const USER32_HEAD = String.raw`
|
|
96
|
+
Add-Type @'
|
|
97
|
+
using System;
|
|
98
|
+
using System.Runtime.InteropServices;
|
|
99
|
+
public static class VascendComputerInput {
|
|
100
|
+
[DllImport("user32.dll")] public static extern bool SetCursorPos(int X, int Y);
|
|
101
|
+
[DllImport("user32.dll")] public static extern void mouse_event(uint flags, uint dx, uint dy, int data, UIntPtr extra);
|
|
102
|
+
[DllImport("user32.dll")] public static extern void keybd_event(byte vk, byte scan, uint flags, UIntPtr extra);
|
|
103
|
+
[DllImport("user32.dll")] static extern IntPtr GetForegroundWindow();
|
|
104
|
+
[DllImport("user32.dll")] static extern uint GetWindowThreadProcessId(IntPtr h, out uint pid);
|
|
105
|
+
[DllImport("kernel32.dll")] static extern uint GetCurrentThreadId();
|
|
106
|
+
[DllImport("user32.dll")] static extern bool AttachThreadInput(uint from, uint to, bool attach);
|
|
107
|
+
[DllImport("user32.dll")] static extern bool IsIconic(IntPtr h);
|
|
108
|
+
[DllImport("user32.dll")] static extern bool ShowWindowAsync(IntPtr h, int command);
|
|
109
|
+
[DllImport("user32.dll")] static extern bool BringWindowToTop(IntPtr h);
|
|
110
|
+
[DllImport("user32.dll")] static extern bool SetForegroundWindow(IntPtr h);
|
|
111
|
+
[DllImport("user32.dll")] static extern IntPtr SetActiveWindow(IntPtr h);
|
|
112
|
+
[DllImport("user32.dll")] static extern IntPtr GetAncestor(IntPtr h, uint flags);
|
|
113
|
+
[DllImport("user32.dll")] static extern bool SetWindowPos(IntPtr h, IntPtr after, int x, int y, int cx, int cy, uint flags);
|
|
114
|
+
static bool IsForeground(IntPtr h) {
|
|
115
|
+
if (h == IntPtr.Zero) return false;
|
|
116
|
+
return GetAncestor(GetForegroundWindow(), 2) == GetAncestor(h, 2);
|
|
117
|
+
}
|
|
118
|
+
public static bool ActivateWindow(IntPtr h) {
|
|
119
|
+
if (h == IntPtr.Zero) return true;
|
|
120
|
+
if (IsIconic(h)) ShowWindowAsync(h, 9);
|
|
121
|
+
else ShowWindowAsync(h, 5);
|
|
122
|
+
for (int attempt = 0; attempt < 4; attempt++) {
|
|
123
|
+
IntPtr foreground = GetForegroundWindow(); uint ignored;
|
|
124
|
+
uint foregroundThread = foreground == IntPtr.Zero ? 0 : GetWindowThreadProcessId(foreground, out ignored);
|
|
125
|
+
uint targetThread = GetWindowThreadProcessId(h, out ignored);
|
|
126
|
+
uint currentThread = GetCurrentThreadId();
|
|
127
|
+
bool attachedForeground = foregroundThread != 0 && foregroundThread != currentThread
|
|
128
|
+
&& AttachThreadInput(currentThread, foregroundThread, true);
|
|
129
|
+
bool attachedTarget = targetThread != 0 && targetThread != currentThread
|
|
130
|
+
&& targetThread != foregroundThread && AttachThreadInput(currentThread, targetThread, true);
|
|
131
|
+
try {
|
|
132
|
+
SetWindowPos(h, IntPtr.Zero, 0, 0, 0, 0, 0x0001 | 0x0002 | 0x0040);
|
|
133
|
+
BringWindowToTop(h); SetActiveWindow(h); SetForegroundWindow(h);
|
|
134
|
+
} finally {
|
|
135
|
+
if (attachedTarget) AttachThreadInput(currentThread, targetThread, false);
|
|
136
|
+
if (attachedForeground) AttachThreadInput(currentThread, foregroundThread, false);
|
|
137
|
+
}
|
|
138
|
+
System.Threading.Thread.Sleep(35 + attempt * 20);
|
|
139
|
+
if (IsForeground(h)) return true;
|
|
140
|
+
}
|
|
141
|
+
return IsForeground(h);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
'@
|
|
145
|
+
`;
|
|
146
|
+
|
|
147
|
+
// Digitazione Unicode reale (SendInput con scan code): funziona anche dove
|
|
148
|
+
// SetValue di UI Automation non genera eventi, per esempio nelle superfici web
|
|
149
|
+
// dentro Electron.
|
|
150
|
+
const UNICODE_TYPER = String.raw`
|
|
151
|
+
Add-Type @'
|
|
152
|
+
using System;
|
|
153
|
+
using System.Runtime.InteropServices;
|
|
154
|
+
public static class VascendUnicodeInput {
|
|
155
|
+
[StructLayout(LayoutKind.Sequential)] public struct INPUT { public uint type; public InputUnion U; }
|
|
156
|
+
[StructLayout(LayoutKind.Explicit)] public struct InputUnion {
|
|
157
|
+
[FieldOffset(0)] public KEYBDINPUT ki; [FieldOffset(0)] public MOUSEINPUT mi; [FieldOffset(0)] public HARDWAREINPUT hi;
|
|
158
|
+
}
|
|
159
|
+
[StructLayout(LayoutKind.Sequential)] public struct KEYBDINPUT {
|
|
160
|
+
public ushort wVk; public ushort wScan; public uint dwFlags; public uint time; public UIntPtr dwExtraInfo;
|
|
161
|
+
}
|
|
162
|
+
[StructLayout(LayoutKind.Sequential)] public struct MOUSEINPUT {
|
|
163
|
+
public int dx; public int dy; public uint mouseData; public uint dwFlags; public uint time; public UIntPtr dwExtraInfo;
|
|
164
|
+
}
|
|
165
|
+
[StructLayout(LayoutKind.Sequential)] public struct HARDWAREINPUT {
|
|
166
|
+
public uint uMsg; public ushort wParamL; public ushort wParamH;
|
|
167
|
+
}
|
|
168
|
+
[DllImport("user32.dll", SetLastError=true)] static extern uint SendInput(uint count, INPUT[] inputs, int size);
|
|
169
|
+
static INPUT Key(char ch, uint flags) {
|
|
170
|
+
INPUT input = new INPUT(); input.type = 1; input.U.ki.wScan = ch; input.U.ki.dwFlags = flags; return input;
|
|
171
|
+
}
|
|
172
|
+
static readonly Random Random = new Random();
|
|
173
|
+
public static void TypeText(string value) {
|
|
174
|
+
int length = value == null ? 0 : value.Length;
|
|
175
|
+
foreach (char ch in value ?? "") {
|
|
176
|
+
INPUT[] pair = new INPUT[] { Key(ch, 0x0004), Key(ch, 0x0004 | 0x0002) };
|
|
177
|
+
SendInput(2, pair, Marshal.SizeOf(typeof(INPUT)));
|
|
178
|
+
// Ritmo umano senza rendere interminabili i testi lunghi: piccole
|
|
179
|
+
// variazioni per carattere e una pausa naturale sulla punteggiatura.
|
|
180
|
+
int delay = length > 400 ? Random.Next(1, 4) : length > 120 ? Random.Next(3, 9) : Random.Next(8, 22);
|
|
181
|
+
if (ch == '.' || ch == ',' || ch == ';' || ch == ':' || ch == '!' || ch == '?') delay += Random.Next(10, 30);
|
|
182
|
+
else if (ch == ' ' || ch == '\n') delay += Random.Next(2, 8);
|
|
183
|
+
System.Threading.Thread.Sleep(delay);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
'@
|
|
188
|
+
`;
|
|
189
|
+
|
|
190
|
+
const TYPE_SCRIPT = UNICODE_TYPER + String.raw`
|
|
191
|
+
$text = [Console]::In.ReadToEnd()
|
|
192
|
+
[VascendUnicodeInput]::TypeText($text)
|
|
193
|
+
`;
|
|
194
|
+
|
|
195
|
+
// Incolla dagli appunti e li riporta come erano: un testo lungo arriva in un
|
|
196
|
+
// colpo solo invece di superare il timeout carattere per carattere.
|
|
197
|
+
const PASTE_BODY = String.raw`
|
|
198
|
+
function Invoke-VascendPaste([string] $value) {
|
|
199
|
+
$previous = $null
|
|
200
|
+
try { $previous = Get-Clipboard -Raw -ErrorAction SilentlyContinue } catch {}
|
|
201
|
+
try { Set-Clipboard -Value $value } catch { return $false }
|
|
202
|
+
Start-Sleep -Milliseconds 45
|
|
203
|
+
[VascendComputerInput]::keybd_event(0x11,0,0,[UIntPtr]::Zero)
|
|
204
|
+
[VascendComputerInput]::keybd_event(0x56,0,0,[UIntPtr]::Zero)
|
|
205
|
+
[VascendComputerInput]::keybd_event(0x56,0,2,[UIntPtr]::Zero)
|
|
206
|
+
[VascendComputerInput]::keybd_event(0x11,0,2,[UIntPtr]::Zero)
|
|
207
|
+
Start-Sleep -Milliseconds 140
|
|
208
|
+
try { if ($null -ne $previous -and $previous -ne '') { Set-Clipboard -Value $previous } else { Set-Clipboard -Value ' ' } } catch {}
|
|
209
|
+
return $true
|
|
210
|
+
}
|
|
211
|
+
`;
|
|
212
|
+
|
|
213
|
+
const PASTE_SCRIPT = USER32_HEAD + PASTE_BODY + String.raw`
|
|
214
|
+
$text = [Console]::In.ReadToEnd()
|
|
215
|
+
if (-not (Invoke-VascendPaste $text)) { [Console]::Error.Write('Appunti non disponibili: il testo non e stato incollato.'); exit 4 }
|
|
216
|
+
`;
|
|
217
|
+
|
|
218
|
+
function focusGuard(windowId) {
|
|
219
|
+
const id = /^\d+$/.test(String(windowId || '')) ? String(windowId) : '';
|
|
220
|
+
return id ? `\nif (-not [VascendComputerInput]::ActivateWindow([IntPtr]([Int64]${id}))) { [Console]::Error.Write('Non riesco a riportare la finestra collegata in primo piano. Nessun input e stato inviato.'); exit 3 }\n` : '\n';
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const DESKTOP_INFO_SCRIPT = String.raw`
|
|
224
|
+
Add-Type @'
|
|
225
|
+
using System;
|
|
226
|
+
using System.Runtime.InteropServices;
|
|
227
|
+
using System.Text;
|
|
228
|
+
public static class VascendDesktopInfo {
|
|
229
|
+
[StructLayout(LayoutKind.Sequential)] public struct RECT { public int Left, Top, Right, Bottom; }
|
|
230
|
+
[StructLayout(LayoutKind.Sequential)] public struct POINT { public int X, Y; }
|
|
231
|
+
[DllImport("user32.dll")] static extern IntPtr GetForegroundWindow();
|
|
232
|
+
[DllImport("user32.dll", CharSet=CharSet.Unicode)] static extern int GetWindowText(IntPtr h, StringBuilder s, int n);
|
|
233
|
+
[DllImport("user32.dll")] static extern bool GetWindowRect(IntPtr h, out RECT r);
|
|
234
|
+
[DllImport("user32.dll")] static extern bool GetCursorPos(out POINT p);
|
|
235
|
+
static string Esc(string value) {
|
|
236
|
+
var text = new StringBuilder();
|
|
237
|
+
foreach (char ch in value ?? "") {
|
|
238
|
+
if (ch == '\\') text.Append("\\\\");
|
|
239
|
+
else if (ch == '"') text.Append("\\\"");
|
|
240
|
+
else if (ch < 32 || ch == 127) text.Append(' ');
|
|
241
|
+
else text.Append(ch);
|
|
242
|
+
}
|
|
243
|
+
return text.ToString();
|
|
244
|
+
}
|
|
245
|
+
public static string Json() {
|
|
246
|
+
IntPtr h = GetForegroundWindow(); var title = new StringBuilder(1024); GetWindowText(h, title, title.Capacity);
|
|
247
|
+
RECT r; GetWindowRect(h, out r); POINT p; GetCursorPos(out p);
|
|
248
|
+
string safe = Esc(title.ToString());
|
|
249
|
+
return "{\"activeWindow\":\"" + safe + "\",\"windowBounds\":{\"x\":" + r.Left + ",\"y\":" + r.Top + ",\"width\":" + (r.Right-r.Left) + ",\"height\":" + (r.Bottom-r.Top) + "},\"cursor\":{\"x\":" + p.X + ",\"y\":" + p.Y + "}}";
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
'@
|
|
253
|
+
[Console]::Out.Write([VascendDesktopInfo]::Json())
|
|
254
|
+
`;
|
|
255
|
+
|
|
256
|
+
// Elenca le finestre top-level della sessione, incluse quelle minimizzate o
|
|
257
|
+
// temporaneamente nascoste nel tray. Filtrare con IsWindowVisible faceva
|
|
258
|
+
// sparire proprio Claude Desktop quando era in background: il processo veniva
|
|
259
|
+
// trovato, ma non esisteva alcun target collegabile. Non leggiamo memoria,
|
|
260
|
+
// command line o contenuti privati dei processi.
|
|
261
|
+
const WINDOWS_SCRIPT = String.raw`
|
|
262
|
+
Add-Type @'
|
|
263
|
+
using System;
|
|
264
|
+
using System.Collections.Generic;
|
|
265
|
+
using System.Runtime.InteropServices;
|
|
266
|
+
using System.Text;
|
|
267
|
+
public static class VascendTopLevelWindows {
|
|
268
|
+
public delegate bool EnumWindowsProc(IntPtr h, IntPtr p);
|
|
269
|
+
[StructLayout(LayoutKind.Sequential)] public struct RECT { public int Left, Top, Right, Bottom; }
|
|
270
|
+
[DllImport("user32.dll")] static extern bool EnumWindows(EnumWindowsProc cb, IntPtr p);
|
|
271
|
+
[DllImport("user32.dll")] static extern bool IsWindowVisible(IntPtr h);
|
|
272
|
+
[DllImport("user32.dll")] static extern bool IsIconic(IntPtr h);
|
|
273
|
+
[DllImport("user32.dll", CharSet=CharSet.Unicode)] static extern int GetWindowText(IntPtr h, StringBuilder s, int n);
|
|
274
|
+
[DllImport("user32.dll")] static extern bool GetWindowRect(IntPtr h, out RECT r);
|
|
275
|
+
[DllImport("user32.dll")] static extern uint GetWindowThreadProcessId(IntPtr h, out uint pid);
|
|
276
|
+
// Un titolo di finestra puo contenere caratteri di controllo (Spotify usa BEL
|
|
277
|
+
// fra brano e artista): non escaparli produceva un JSON illeggibile e la
|
|
278
|
+
// lista finestre tornava VUOTA per colpa di una sola app.
|
|
279
|
+
static string Esc(string value) {
|
|
280
|
+
var text = new StringBuilder();
|
|
281
|
+
foreach (char ch in value ?? "") {
|
|
282
|
+
if (ch == '\\') text.Append("\\\\");
|
|
283
|
+
else if (ch == '"') text.Append("\\\"");
|
|
284
|
+
else if (ch < 32 || ch == 127) text.Append(' ');
|
|
285
|
+
else text.Append(ch);
|
|
286
|
+
}
|
|
287
|
+
return text.ToString();
|
|
288
|
+
}
|
|
289
|
+
public static string Json() {
|
|
290
|
+
var rows = new List<string>();
|
|
291
|
+
EnumWindows((h, p) => {
|
|
292
|
+
var title = new StringBuilder(1024); GetWindowText(h, title, title.Capacity);
|
|
293
|
+
if (title.Length == 0) return true;
|
|
294
|
+
RECT r; if (!GetWindowRect(h, out r) || r.Right-r.Left < 20 || r.Bottom-r.Top < 20) return true;
|
|
295
|
+
uint pid; GetWindowThreadProcessId(h, out pid);
|
|
296
|
+
string process = "";
|
|
297
|
+
try { process = System.Diagnostics.Process.GetProcessById((int)pid).ProcessName; } catch {}
|
|
298
|
+
Func<string,string> esc = Esc;
|
|
299
|
+
rows.Add("{\"windowId\":\"" + h.ToInt64().ToString() + "\",\"processId\":" + pid
|
|
300
|
+
+ ",\"process\":\"" + esc(process) + "\",\"title\":\"" + esc(title.ToString())
|
|
301
|
+
+ "\",\"visible\":" + (IsWindowVisible(h) ? "true" : "false")
|
|
302
|
+
+ ",\"minimized\":" + (IsIconic(h) ? "true" : "false")
|
|
303
|
+
+ ",\"bounds\":{\"x\":" + r.Left + ",\"y\":" + r.Top + ",\"width\":" + (r.Right-r.Left)
|
|
304
|
+
+ ",\"height\":" + (r.Bottom-r.Top) + "}}" );
|
|
305
|
+
return rows.Count < 240;
|
|
306
|
+
}, IntPtr.Zero);
|
|
307
|
+
return "[" + string.Join(",", rows) + "]";
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
'@
|
|
311
|
+
[Console]::Out.Write([VascendTopLevelWindows]::Json())
|
|
312
|
+
`;
|
|
313
|
+
|
|
314
|
+
// EnumWindows descrive soltanto le superfici top-level. Per scegliere
|
|
315
|
+
// un'applicazione in modo affidabile serve anche l'elenco dei processi della
|
|
316
|
+
// sessione utente corrente: Electron, Claude Desktop incluso, puo mantenere il
|
|
317
|
+
// processo vivo mentre la sua finestra e minimizzata o temporaneamente nel
|
|
318
|
+
// tray. Non leggiamo command line o memoria. Il percorso dell'eseguibile viene
|
|
319
|
+
// usato soltanto dall'host locale per ricavare l'icona Windows e non viene mai
|
|
320
|
+
// consegnato al modello o al renderer.
|
|
321
|
+
const PROCESSES_SCRIPT = String.raw`
|
|
322
|
+
Add-Type @'
|
|
323
|
+
using System;
|
|
324
|
+
using System.Runtime.InteropServices;
|
|
325
|
+
public static class VascendProcessWindowState {
|
|
326
|
+
[StructLayout(LayoutKind.Sequential)] public struct RECT { public int Left, Top, Right, Bottom; }
|
|
327
|
+
[DllImport("user32.dll")] static extern bool GetWindowRect(IntPtr h, out RECT r);
|
|
328
|
+
[DllImport("user32.dll")] public static extern bool IsWindowVisible(IntPtr h);
|
|
329
|
+
[DllImport("user32.dll")] public static extern bool IsIconic(IntPtr h);
|
|
330
|
+
public static int[] Bounds(IntPtr h) {
|
|
331
|
+
RECT r; if (h == IntPtr.Zero || !GetWindowRect(h, out r)) return new int[0];
|
|
332
|
+
return new [] { r.Left, r.Top, r.Right-r.Left, r.Bottom-r.Top };
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
'@
|
|
336
|
+
$session = [System.Diagnostics.Process]::GetCurrentProcess().SessionId
|
|
337
|
+
$items = New-Object System.Collections.Generic.List[object]
|
|
338
|
+
foreach ($process in [System.Diagnostics.Process]::GetProcesses()) {
|
|
339
|
+
try {
|
|
340
|
+
if ($process.SessionId -ne $session) { continue }
|
|
341
|
+
$title = [string]$process.MainWindowTitle
|
|
342
|
+
$handle = [Int64]$process.MainWindowHandle
|
|
343
|
+
$executablePath = ''
|
|
344
|
+
try { $executablePath = [string]$process.MainModule.FileName } catch {}
|
|
345
|
+
$rect = [VascendProcessWindowState]::Bounds([IntPtr]$handle)
|
|
346
|
+
$bounds = $null
|
|
347
|
+
if ($rect.Length -eq 4 -and $rect[2] -gt 0 -and $rect[3] -gt 0) {
|
|
348
|
+
$bounds = [ordered]@{ x = $rect[0]; y = $rect[1]; width = $rect[2]; height = $rect[3] }
|
|
349
|
+
}
|
|
350
|
+
$items.Add([ordered]@{
|
|
351
|
+
processId = [int]$process.Id
|
|
352
|
+
process = [string]$process.ProcessName
|
|
353
|
+
executablePath = $executablePath
|
|
354
|
+
title = ([regex]::Replace($title, '[\p{Cc}\p{Cf}]', ' ')).Substring(0, [Math]::Min(300, $title.Length))
|
|
355
|
+
windowId = $(if ($handle -gt 0) { [string]$handle } else { '' })
|
|
356
|
+
hasWindow = [bool]($handle -gt 0 -or -not [string]::IsNullOrWhiteSpace($title))
|
|
357
|
+
visible = [bool]($handle -gt 0 -and [VascendProcessWindowState]::IsWindowVisible([IntPtr]$handle))
|
|
358
|
+
minimized = [bool]($handle -gt 0 -and [VascendProcessWindowState]::IsIconic([IntPtr]$handle))
|
|
359
|
+
bounds = $bounds
|
|
360
|
+
responding = [bool]$process.Responding
|
|
361
|
+
})
|
|
362
|
+
} catch {}
|
|
363
|
+
}
|
|
364
|
+
$sorted = @($items | Sort-Object @{Expression='hasWindow';Descending=$true}, @{Expression='process';Descending=$false}, @{Expression='processId';Descending=$false})
|
|
365
|
+
[Console]::Out.Write((ConvertTo-Json -InputObject $sorted -Depth 3 -Compress))
|
|
366
|
+
`;
|
|
367
|
+
|
|
368
|
+
// Frammento condiviso: cammina il tree UIA di una finestra e produce righe
|
|
369
|
+
// semantiche. Lo usano sia la lettura (computer_inspect) sia la risoluzione di
|
|
370
|
+
// un target prima di agire.
|
|
371
|
+
const UIA_WALK_BODY = String.raw`
|
|
372
|
+
Add-Type -AssemblyName UIAutomationClient
|
|
373
|
+
Add-Type -AssemblyName UIAutomationTypes
|
|
374
|
+
# Le applicazioni di terze parti mettono caratteri di CONTROLLO dentro i nomi
|
|
375
|
+
# accessibili: Spotify usa BEL (U+0007) come separatore in "Brano BEL Artista".
|
|
376
|
+
# ConvertTo-Json di PowerShell 5.1 non li escapa, il JSON diventa illeggibile e
|
|
377
|
+
# l'intera lettura si perdeva restituendo "nessun controllo". Gli emoji (coppie
|
|
378
|
+
# di surrogati) devono invece sopravvivere: si toglie solo Cc/Cf.
|
|
379
|
+
function Get-VascendText([string] $value, [int] $max) {
|
|
380
|
+
if ([string]::IsNullOrEmpty($value)) { return '' }
|
|
381
|
+
$clean = [regex]::Replace($value, '[\p{Cc}\p{Cf}]', ' ')
|
|
382
|
+
if ($clean.Length -gt $max) { $clean = $clean.Substring(0, $max) }
|
|
383
|
+
return $clean.Trim()
|
|
384
|
+
}
|
|
385
|
+
function Get-VascendRoot([string] $handle) {
|
|
386
|
+
$root = $null
|
|
387
|
+
if ($handle -match '^\d+$') {
|
|
388
|
+
try { $root = [System.Windows.Automation.AutomationElement]::FromHandle([IntPtr]([Int64]$handle)) } catch {}
|
|
389
|
+
}
|
|
390
|
+
if ($null -eq $root) {
|
|
391
|
+
$focused = [System.Windows.Automation.AutomationElement]::FocusedElement
|
|
392
|
+
if ($null -ne $focused) {
|
|
393
|
+
$walker = [System.Windows.Automation.TreeWalker]::ControlViewWalker
|
|
394
|
+
$cursor = $focused
|
|
395
|
+
for ($i = 0; $i -lt 24 -and $null -ne $cursor; $i++) {
|
|
396
|
+
if ($cursor.Current.ControlType.ProgrammaticName -eq 'ControlType.Window') { $root = $cursor; break }
|
|
397
|
+
$cursor = $walker.GetParent($cursor)
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
if ($null -eq $root) { $root = [System.Windows.Automation.AutomationElement]::RootElement }
|
|
402
|
+
return $root
|
|
403
|
+
}
|
|
404
|
+
function Get-VascendNodes($root, [int] $max) {
|
|
405
|
+
$queue = New-Object 'System.Collections.Generic.Queue[System.Windows.Automation.AutomationElement]'
|
|
406
|
+
$walker = [System.Windows.Automation.TreeWalker]::RawViewWalker
|
|
407
|
+
$walkChildren = $false
|
|
408
|
+
try {
|
|
409
|
+
$nodes = $root.FindAll([System.Windows.Automation.TreeScope]::Descendants, [System.Windows.Automation.Condition]::TrueCondition)
|
|
410
|
+
foreach ($candidate in $nodes) { $queue.Enqueue($candidate) }
|
|
411
|
+
} catch {
|
|
412
|
+
$walkChildren = $true
|
|
413
|
+
try {
|
|
414
|
+
$child = $walker.GetFirstChild($root)
|
|
415
|
+
while ($null -ne $child) { $queue.Enqueue($child); $child = $walker.GetNextSibling($child) }
|
|
416
|
+
} catch {}
|
|
417
|
+
}
|
|
418
|
+
$found = New-Object System.Collections.Generic.List[object]
|
|
419
|
+
while ($queue.Count -gt 0) {
|
|
420
|
+
$node = $queue.Dequeue()
|
|
421
|
+
if ($found.Count -ge $max) { break }
|
|
422
|
+
$found.Add($node)
|
|
423
|
+
if ($walkChildren) {
|
|
424
|
+
try {
|
|
425
|
+
$child = $walker.GetFirstChild($node)
|
|
426
|
+
while ($null -ne $child) { $queue.Enqueue($child); $child = $walker.GetNextSibling($child) }
|
|
427
|
+
} catch {}
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
return $found
|
|
431
|
+
}
|
|
432
|
+
# Enumerazione RISTRETTA: il filtro per ruolo lo applica il provider, non noi.
|
|
433
|
+
# Chiedere TrueCondition significa farsi consegnare anche seicento Pane/Group/
|
|
434
|
+
# Text per poi buttarli — ed e' meta' del costo di una scansione. Si usa quando
|
|
435
|
+
# il chiamante vuole solo i controlli agibili; per la lettura del testo o per
|
|
436
|
+
# l'ispezione completa resta il cammino su tutto l'albero.
|
|
437
|
+
function Get-VascendInteractive($root, [int] $max) {
|
|
438
|
+
$names = @('Button','Edit','CheckBox','RadioButton','ComboBox','ListItem','MenuItem','TabItem',
|
|
439
|
+
'TreeItem','Hyperlink','Slider','Spinner','SplitButton','DataItem','Document','List','Tree','Tab','Menu')
|
|
440
|
+
$conditions = New-Object System.Collections.Generic.List[System.Windows.Automation.Condition]
|
|
441
|
+
foreach ($name in $names) {
|
|
442
|
+
try {
|
|
443
|
+
$type = [System.Windows.Automation.ControlType]::$name
|
|
444
|
+
if ($null -ne $type) {
|
|
445
|
+
$conditions.Add((New-Object System.Windows.Automation.PropertyCondition(
|
|
446
|
+
[System.Windows.Automation.AutomationElement]::ControlTypeProperty, $type)))
|
|
447
|
+
}
|
|
448
|
+
} catch {}
|
|
449
|
+
}
|
|
450
|
+
if ($conditions.Count -lt 2) { return Get-VascendNodes $root $max }
|
|
451
|
+
try {
|
|
452
|
+
$condition = New-Object System.Windows.Automation.OrCondition($conditions.ToArray())
|
|
453
|
+
$found = $root.FindAll([System.Windows.Automation.TreeScope]::Descendants, $condition)
|
|
454
|
+
$out = New-Object System.Collections.Generic.List[object]
|
|
455
|
+
foreach ($node in $found) {
|
|
456
|
+
if ($out.Count -ge $max) { break }
|
|
457
|
+
$out.Add($node)
|
|
458
|
+
}
|
|
459
|
+
return $out
|
|
460
|
+
} catch { return Get-VascendNodes $root $max }
|
|
461
|
+
}
|
|
462
|
+
function Get-VascendRow($node, [bool] $allowOffscreen) {
|
|
463
|
+
$c = $node.Current
|
|
464
|
+
$r = $c.BoundingRectangle
|
|
465
|
+
# Un controllo fuori vista non e' leggibile, ma DEVE restare risolvibile:
|
|
466
|
+
# ScrollIntoView esiste proprio per riportarlo sullo schermo prima di agire.
|
|
467
|
+
if (-not $allowOffscreen -and ($c.IsOffscreen -or $r.Width -lt 2 -or $r.Height -lt 2)) { return $null }
|
|
468
|
+
$name = [string]$c.Name
|
|
469
|
+
$automationId = [string]$c.AutomationId
|
|
470
|
+
$type = ([string]$c.ControlType.ProgrammaticName).Replace('ControlType.','')
|
|
471
|
+
# Regola volutamente permissiva: nelle app Electron molti controlli reali si
|
|
472
|
+
# presentano come Custom o Group con un nome. Il filtro per intento (query,
|
|
473
|
+
# types, interactive_only) vive nel layer di osservazione, non qui.
|
|
474
|
+
$interactive = $type -in @('Edit','Button','CheckBox','RadioButton','ComboBox','ListItem','MenuItem','TabItem','TreeItem','Hyperlink','Slider','Document','Spinner','SplitButton')
|
|
475
|
+
if ([string]::IsNullOrWhiteSpace($name) -and [string]::IsNullOrWhiteSpace($automationId) -and -not $interactive) { return $null }
|
|
476
|
+
$isPassword = [bool]$c.IsPassword
|
|
477
|
+
$value = ''
|
|
478
|
+
$toggle = ''
|
|
479
|
+
$selected = $null
|
|
480
|
+
$expanded = ''
|
|
481
|
+
# Ogni TryGetCurrentPattern e' una chiamata COM cross-process: cinque per nodo,
|
|
482
|
+
# su duecento nodi, erano i tre secondi della scansione. Le etichette di testo
|
|
483
|
+
# non hanno pattern da interrogare: si sondano solo i controlli che possono
|
|
484
|
+
# averne (ruolo interattivo, oppure un automationId, come le superfici web).
|
|
485
|
+
$probe = $interactive -or $automationId -ne ''
|
|
486
|
+
if ($isPassword) { $value = '[PASSWORD REDACTED]' }
|
|
487
|
+
elseif ($probe) {
|
|
488
|
+
try { $pattern = $null; if ($node.TryGetCurrentPattern([System.Windows.Automation.ValuePattern]::Pattern, [ref]$pattern)) { $value = [string]$pattern.Current.Value } } catch {}
|
|
489
|
+
if ([string]::IsNullOrEmpty($value)) {
|
|
490
|
+
try { $pattern = $null; if ($node.TryGetCurrentPattern([System.Windows.Automation.RangeValuePattern]::Pattern, [ref]$pattern)) { $value = [string]$pattern.Current.Value } } catch {}
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
$actions = New-Object System.Collections.Generic.List[string]
|
|
494
|
+
if ($probe) {
|
|
495
|
+
try { $pattern = $null; if ($node.TryGetCurrentPattern([System.Windows.Automation.TogglePattern]::Pattern, [ref]$pattern)) { $toggle = [string]$pattern.Current.ToggleState } } catch {}
|
|
496
|
+
try { $pattern = $null; if ($node.TryGetCurrentPattern([System.Windows.Automation.SelectionItemPattern]::Pattern, [ref]$pattern)) { $selected = [bool]$pattern.Current.IsSelected } } catch {}
|
|
497
|
+
try { $pattern = $null; if ($node.TryGetCurrentPattern([System.Windows.Automation.ExpandCollapsePattern]::Pattern, [ref]$pattern)) { $expanded = [string]$pattern.Current.ExpandCollapseState } } catch {}
|
|
498
|
+
try { $pattern = $null; if ($node.TryGetCurrentPattern([System.Windows.Automation.InvokePattern]::Pattern, [ref]$pattern)) { $actions.Add('invoke') } } catch {}
|
|
499
|
+
}
|
|
500
|
+
if ($toggle -ne '') { $actions.Add('toggle') }
|
|
501
|
+
if ($null -ne $selected) { $actions.Add('select') }
|
|
502
|
+
if ($expanded -ne '') { $actions.Add('expand') }
|
|
503
|
+
if (-not $isPassword) {
|
|
504
|
+
try { $pattern = $null; if ($node.TryGetCurrentPattern([System.Windows.Automation.ValuePattern]::Pattern, [ref]$pattern)) { if (-not $pattern.Current.IsReadOnly) { $actions.Add('setValue') } } } catch {}
|
|
505
|
+
}
|
|
506
|
+
return [ordered]@{
|
|
507
|
+
name = Get-VascendText $name 180; automationId = Get-VascendText $automationId 120;
|
|
508
|
+
type = $type; className = Get-VascendText ([string]$c.ClassName) 100;
|
|
509
|
+
processId = [int]$c.ProcessId; enabled = [bool]$c.IsEnabled; focusable = [bool]$c.IsKeyboardFocusable;
|
|
510
|
+
isPassword = $isPassword; value = Get-VascendText $value 500; toggle = $toggle; selected = $selected; expanded = $expanded;
|
|
511
|
+
actions = @($actions.ToArray() | Select-Object -Unique)
|
|
512
|
+
bounds = [ordered]@{ x=[int]$r.X; y=[int]$r.Y; width=[int]$r.Width; height=[int]$r.Height }
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
`;
|
|
516
|
+
|
|
517
|
+
// Lo script riceve soltanto un windowId numerico via stdin e interroga il tree
|
|
518
|
+
// UIA della finestra. Questo fornisce controlli semantici (Button, Edit, Tab...)
|
|
519
|
+
// molto piu affidabili di un click stimato da un'immagine.
|
|
520
|
+
const UI_ELEMENTS_SCRIPT = UIA_WALK_BODY + String.raw`
|
|
521
|
+
$raw = [Console]::In.ReadToEnd().Trim()
|
|
522
|
+
# '+interactive': il chiamante vuole solo i controlli agibili, quindi il filtro
|
|
523
|
+
# per ruolo lo fa il provider. Senza il suffisso si cammina tutto l'albero.
|
|
524
|
+
$narrow = $raw.EndsWith('+interactive')
|
|
525
|
+
if ($narrow) { $raw = $raw.Substring(0, $raw.Length - 12) }
|
|
526
|
+
$root = Get-VascendRoot $raw
|
|
527
|
+
$items = New-Object System.Collections.Generic.List[object]
|
|
528
|
+
$nodes = if ($narrow) { Get-VascendInteractive $root 900 } else { Get-VascendNodes $root 900 }
|
|
529
|
+
foreach ($node in $nodes) {
|
|
530
|
+
if ($items.Count -ge 320) { break }
|
|
531
|
+
try { $row = Get-VascendRow $node $false; if ($null -ne $row) { $items.Add($row) } } catch {}
|
|
532
|
+
}
|
|
533
|
+
$array = [object[]]$items.ToArray()
|
|
534
|
+
[Console]::Out.Write((ConvertTo-Json -InputObject $array -Depth 5 -Compress))
|
|
535
|
+
`;
|
|
536
|
+
|
|
537
|
+
// Agisce su un controllo tramite i pattern nativi di UI Automation: Invoke,
|
|
538
|
+
// Toggle, Select, ExpandCollapse, SetValue, SetFocus. E' molto piu affidabile di
|
|
539
|
+
// un click a coordinate (funziona anche se il controllo va portato in vista, non
|
|
540
|
+
// sposta il puntatore dell'utente e non sbaglia bersaglio se la finestra si e
|
|
541
|
+
// mossa). Il controllo viene RIRISOLTO qui, nel tree vivo: se non esiste piu,
|
|
542
|
+
// nessun input viene inviato.
|
|
543
|
+
// Corpo dell azione: risoluzione del target piu il ramo per ogni azione. I
|
|
544
|
+
// frammenti C# NON si caricano tutti sempre — ogni Add-Type e una compilazione
|
|
545
|
+
// a runtime (circa 250 ms a testa) e il typer serve solo a chi digita.
|
|
546
|
+
const UIA_ACTION_BODY = String.raw`
|
|
547
|
+
$payload = [Console]::In.ReadToEnd() | ConvertFrom-Json
|
|
548
|
+
$handle = [string]$payload.windowId
|
|
549
|
+
$selector = $payload.selector
|
|
550
|
+
$action = [string]$payload.action
|
|
551
|
+
# locate e' una LETTURA: non ruba il focus a quello che l'utente sta facendo.
|
|
552
|
+
# Tutte le altre azioni consegnano input, quindi la finestra va davvero davanti.
|
|
553
|
+
if ($handle -match '^\d+$' -and $action -ne 'locate') {
|
|
554
|
+
if (-not [VascendComputerInput]::ActivateWindow([IntPtr]([Int64]$handle))) {
|
|
555
|
+
[Console]::Out.Write('{"ok":false,"code":"FOREGROUND_LOST","error":"Non riesco a riportare la finestra collegata in primo piano."}')
|
|
556
|
+
exit 0
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
$root = Get-VascendRoot $handle
|
|
560
|
+
$best = $null
|
|
561
|
+
$bestRow = $null
|
|
562
|
+
$bestScore = -1
|
|
563
|
+
$wantId = [string]$selector.automationId
|
|
564
|
+
$wantName = [string]$selector.name
|
|
565
|
+
$wantType = [string]$selector.type
|
|
566
|
+
$wantClass = [string]$selector.className
|
|
567
|
+
$wantX = [double]$selector.centerX
|
|
568
|
+
$wantY = [double]$selector.centerY
|
|
569
|
+
|
|
570
|
+
# VIA RAPIDA. Camminare tutto l'albero per trovare UN elemento costava sei
|
|
571
|
+
# secondi per azione: 1572 nodi, e per ognuno cinque interrogazioni COM
|
|
572
|
+
# cross-process sui pattern. FindFirst con una PropertyCondition e' una query
|
|
573
|
+
# indicizzata dentro il provider: stesso risultato, un ordine di grandezza in
|
|
574
|
+
# meno. Il cammino completo resta come ripiego, per quando l'identita' non basta
|
|
575
|
+
# (nessun automationId, nessun nome: solo la posizione).
|
|
576
|
+
function Find-VascendFast($root, [string] $id, [string] $name) {
|
|
577
|
+
$conditions = New-Object System.Collections.Generic.List[System.Windows.Automation.Condition]
|
|
578
|
+
if ($id -ne '') {
|
|
579
|
+
$conditions.Add((New-Object System.Windows.Automation.PropertyCondition(
|
|
580
|
+
[System.Windows.Automation.AutomationElement]::AutomationIdProperty, $id)))
|
|
581
|
+
}
|
|
582
|
+
if ($name -ne '') {
|
|
583
|
+
$conditions.Add((New-Object System.Windows.Automation.PropertyCondition(
|
|
584
|
+
[System.Windows.Automation.AutomationElement]::NameProperty, $name)))
|
|
585
|
+
}
|
|
586
|
+
if ($conditions.Count -eq 0) { return $null }
|
|
587
|
+
try {
|
|
588
|
+
$condition = if ($conditions.Count -eq 1) { $conditions[0] } else {
|
|
589
|
+
New-Object System.Windows.Automation.AndCondition($conditions.ToArray())
|
|
590
|
+
}
|
|
591
|
+
return $root.FindFirst([System.Windows.Automation.TreeScope]::Descendants, $condition)
|
|
592
|
+
} catch { return $null }
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
$fast = Find-VascendFast $root $wantId $wantName
|
|
596
|
+
if ($null -ne $fast) {
|
|
597
|
+
try {
|
|
598
|
+
$row = Get-VascendRow $fast $true
|
|
599
|
+
if ($null -ne $row) {
|
|
600
|
+
# Stessa soglia del cammino lento: identita' piu' tipo/classe.
|
|
601
|
+
$score = 0
|
|
602
|
+
if ($wantId -ne '' -and $row.automationId -eq $wantId) { $score += 8 }
|
|
603
|
+
if ($wantName -ne '' -and $row.name -eq $wantName) { $score += 5 }
|
|
604
|
+
if ($wantType -ne '' -and $row.type -eq $wantType) { $score += 3 }
|
|
605
|
+
if ($wantClass -ne '' -and $row.className -eq $wantClass) { $score += 2 }
|
|
606
|
+
if ($score -ge 5) { $best = $fast; $bestRow = $row; $bestScore = $score }
|
|
607
|
+
}
|
|
608
|
+
} catch {}
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
if ($null -eq $best) {
|
|
612
|
+
foreach ($node in Get-VascendNodes $root 900) {
|
|
613
|
+
try {
|
|
614
|
+
$row = Get-VascendRow $node $true
|
|
615
|
+
if ($null -eq $row) { continue }
|
|
616
|
+
if ($wantId -ne '' -and $row.automationId -ne $wantId) { continue }
|
|
617
|
+
if ($wantId -eq '' -and $wantName -ne '' -and $row.name -ne $wantName) { continue }
|
|
618
|
+
$score = 0
|
|
619
|
+
if ($wantId -ne '' -and $row.automationId -eq $wantId) { $score += 8 }
|
|
620
|
+
if ($wantName -ne '' -and $row.name -eq $wantName) { $score += 5 }
|
|
621
|
+
if ($wantType -ne '' -and $row.type -eq $wantType) { $score += 3 }
|
|
622
|
+
if ($wantClass -ne '' -and $row.className -eq $wantClass) { $score += 2 }
|
|
623
|
+
if ($wantId -eq '' -and $wantName -eq '') {
|
|
624
|
+
$dx = [Math]::Abs(($row.bounds.x + $row.bounds.width / 2) - $wantX)
|
|
625
|
+
$dy = [Math]::Abs(($row.bounds.y + $row.bounds.height / 2) - $wantY)
|
|
626
|
+
if ($dx + $dy -le 48) { $score += 4 }
|
|
627
|
+
}
|
|
628
|
+
if ($score -gt $bestScore) { $bestScore = $score; $best = $node; $bestRow = $row }
|
|
629
|
+
} catch {}
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
if ($null -eq $best -or $bestScore -lt 5) {
|
|
633
|
+
[Console]::Out.Write('{"ok":false,"code":"TARGET_GONE","error":"Il controllo non esiste piu nel tree corrente."}')
|
|
634
|
+
exit 0
|
|
635
|
+
}
|
|
636
|
+
try { $pattern = $null; if ($best.TryGetCurrentPattern([System.Windows.Automation.ScrollItemPattern]::Pattern, [ref]$pattern)) { $pattern.ScrollIntoView() } } catch {}
|
|
637
|
+
Start-Sleep -Milliseconds 25
|
|
638
|
+
try { $bestRow = Get-VascendRow $best $true } catch {}
|
|
639
|
+
$result = [ordered]@{ ok = $false; method = ''; code = ''; error = ''; fallback = '' }
|
|
640
|
+
if ($null -ne $bestRow) {
|
|
641
|
+
$result.name = $bestRow.name; $result.type = $bestRow.type; $result.enabled = $bestRow.enabled
|
|
642
|
+
$result.isPassword = $bestRow.isPassword; $result.bounds = $bestRow.bounds; $result.value = $bestRow.value
|
|
643
|
+
}
|
|
644
|
+
function Set-VascendFocus($node) {
|
|
645
|
+
try { $node.SetFocus(); Start-Sleep -Milliseconds 60; return $true } catch { return $false }
|
|
646
|
+
}
|
|
647
|
+
if ($null -ne $bestRow -and -not $bestRow.enabled -and $action -ne 'locate') {
|
|
648
|
+
$result.code = 'DISABLED'; $result.error = 'Il controllo esiste ma e disabilitato.'
|
|
649
|
+
} elseif ($action -eq 'locate') {
|
|
650
|
+
$result.ok = $true; $result.method = 'Locate'
|
|
651
|
+
} elseif ($action -eq 'invoke') {
|
|
652
|
+
$done = $false
|
|
653
|
+
try { $pattern = $null; if ($best.TryGetCurrentPattern([System.Windows.Automation.InvokePattern]::Pattern, [ref]$pattern)) { $pattern.Invoke(); $result.method = 'Invoke'; $done = $true } } catch { $result.error = [string]$_.Exception.Message }
|
|
654
|
+
if (-not $done) {
|
|
655
|
+
try { $pattern = $null; if ($best.TryGetCurrentPattern([System.Windows.Automation.TogglePattern]::Pattern, [ref]$pattern)) { $pattern.Toggle(); $result.method = 'Toggle'; $done = $true } } catch {}
|
|
656
|
+
}
|
|
657
|
+
if (-not $done) {
|
|
658
|
+
try { $pattern = $null; if ($best.TryGetCurrentPattern([System.Windows.Automation.SelectionItemPattern]::Pattern, [ref]$pattern)) { $pattern.Select(); $result.method = 'Select'; $done = $true } } catch {}
|
|
659
|
+
}
|
|
660
|
+
if (-not $done) {
|
|
661
|
+
try {
|
|
662
|
+
$pattern = $null
|
|
663
|
+
if ($best.TryGetCurrentPattern([System.Windows.Automation.ExpandCollapsePattern]::Pattern, [ref]$pattern)) {
|
|
664
|
+
if ([string]$pattern.Current.ExpandCollapseState -eq 'Expanded') { $pattern.Collapse(); $result.method = 'Collapse' }
|
|
665
|
+
else { $pattern.Expand(); $result.method = 'Expand' }
|
|
666
|
+
$done = $true
|
|
667
|
+
}
|
|
668
|
+
} catch {}
|
|
669
|
+
}
|
|
670
|
+
$result.ok = $done
|
|
671
|
+
if (-not $done) { $result.code = 'NO_PATTERN'; $result.fallback = 'click'; $result.error = 'Il controllo non espone un pattern nativo.' }
|
|
672
|
+
} elseif ($action -eq 'toggle' -or $action -eq 'select' -or $action -eq 'expand' -or $action -eq 'collapse') {
|
|
673
|
+
try {
|
|
674
|
+
$pattern = $null
|
|
675
|
+
if ($action -eq 'toggle' -and $best.TryGetCurrentPattern([System.Windows.Automation.TogglePattern]::Pattern, [ref]$pattern)) { $pattern.Toggle(); $result.ok = $true; $result.method = 'Toggle' }
|
|
676
|
+
elseif ($action -eq 'select' -and $best.TryGetCurrentPattern([System.Windows.Automation.SelectionItemPattern]::Pattern, [ref]$pattern)) { $pattern.Select(); $result.ok = $true; $result.method = 'Select' }
|
|
677
|
+
elseif ($best.TryGetCurrentPattern([System.Windows.Automation.ExpandCollapsePattern]::Pattern, [ref]$pattern)) {
|
|
678
|
+
if ($action -eq 'expand') { $pattern.Expand(); $result.method = 'Expand' } else { $pattern.Collapse(); $result.method = 'Collapse' }
|
|
679
|
+
$result.ok = $true
|
|
680
|
+
}
|
|
681
|
+
} catch { $result.error = [string]$_.Exception.Message }
|
|
682
|
+
if (-not $result.ok -and $result.error -eq '') { $result.code = 'NO_PATTERN'; $result.fallback = 'click'; $result.error = 'Pattern non disponibile su questo controllo.' }
|
|
683
|
+
} elseif ($action -eq 'focus') {
|
|
684
|
+
$result.ok = Set-VascendFocus $best
|
|
685
|
+
if ($result.ok) { $result.method = 'SetFocus' } else { $result.code = 'NO_FOCUS'; $result.fallback = 'click'; $result.error = 'Il controllo non accetta il focus da UI Automation.' }
|
|
686
|
+
} elseif ($action -eq 'setValue' -or $action -eq 'type' -or $action -eq 'paste') {
|
|
687
|
+
if ($null -ne $bestRow -and $bestRow.isPassword) {
|
|
688
|
+
$result.code = 'PASSWORD'; $result.error = 'Computer Use non scrive in campi password.'
|
|
689
|
+
} else {
|
|
690
|
+
$focused = Set-VascendFocus $best
|
|
691
|
+
if (-not $focused) { $result.code = 'NO_FOCUS'; $result.fallback = 'click'; $result.error = 'Il controllo non accetta il focus da UI Automation.' }
|
|
692
|
+
else {
|
|
693
|
+
$text = [string]$payload.value
|
|
694
|
+
if ($payload.clear -eq $true) {
|
|
695
|
+
[VascendComputerInput]::keybd_event(0x11,0,0,[UIntPtr]::Zero)
|
|
696
|
+
[VascendComputerInput]::keybd_event(0x41,0,0,[UIntPtr]::Zero)
|
|
697
|
+
[VascendComputerInput]::keybd_event(0x41,0,2,[UIntPtr]::Zero)
|
|
698
|
+
[VascendComputerInput]::keybd_event(0x11,0,2,[UIntPtr]::Zero)
|
|
699
|
+
Start-Sleep -Milliseconds 40
|
|
700
|
+
}
|
|
701
|
+
if ($action -eq 'setValue') {
|
|
702
|
+
try {
|
|
703
|
+
$pattern = $null
|
|
704
|
+
if ($best.TryGetCurrentPattern([System.Windows.Automation.ValuePattern]::Pattern, [ref]$pattern) -and -not $pattern.Current.IsReadOnly) {
|
|
705
|
+
$pattern.SetValue($text); $result.ok = $true; $result.method = 'SetValue'
|
|
706
|
+
} else { $result.code = 'NO_PATTERN'; $result.fallback = 'type'; $result.error = 'Il campo non espone ValuePattern scrivibile.' }
|
|
707
|
+
} catch { $result.code = 'NO_PATTERN'; $result.fallback = 'type'; $result.error = [string]$_.Exception.Message }
|
|
708
|
+
} elseif ($action -eq 'paste') {
|
|
709
|
+
if (Invoke-VascendPaste $text) { $result.ok = $true; $result.method = 'Paste' }
|
|
710
|
+
else { $result.code = 'NO_CLIPBOARD'; $result.fallback = 'type'; $result.error = 'Appunti non disponibili.' }
|
|
711
|
+
} else {
|
|
712
|
+
[VascendUnicodeInput]::TypeText($text)
|
|
713
|
+
$result.ok = $true; $result.method = 'Type'
|
|
714
|
+
}
|
|
715
|
+
if ($result.ok -and $payload.submit -eq $true) {
|
|
716
|
+
Start-Sleep -Milliseconds 90
|
|
717
|
+
[VascendComputerInput]::keybd_event(13,0,0,[UIntPtr]::Zero)
|
|
718
|
+
[VascendComputerInput]::keybd_event(13,0,2,[UIntPtr]::Zero)
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
} else {
|
|
723
|
+
$result.code = 'UNSUPPORTED'; $result.error = 'Azione UI Automation sconosciuta.'
|
|
724
|
+
}
|
|
725
|
+
[Console]::Out.Write((ConvertTo-Json -InputObject $result -Depth 4 -Compress))
|
|
726
|
+
`;
|
|
727
|
+
|
|
728
|
+
// Script per una singola azione: solo i frammenti che servono davvero. Sotto la
|
|
729
|
+
// soglia degli 8000 caratteri evita anche il giro dal file temporaneo.
|
|
730
|
+
function uiaActionScript(action) {
|
|
731
|
+
const needsTyper = action === 'type';
|
|
732
|
+
const needsPaste = action === 'paste';
|
|
733
|
+
return UIA_WALK_BODY + USER32_HEAD
|
|
734
|
+
+ (needsTyper ? UNICODE_TYPER : '')
|
|
735
|
+
+ (needsPaste ? PASTE_BODY : '')
|
|
736
|
+
+ UIA_ACTION_BODY;
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
// Variante completa: la usano i test statici e gli adapter che vogliono un solo
|
|
740
|
+
// programma per tutte le azioni.
|
|
741
|
+
const UIA_ACTION_SCRIPT = UIA_WALK_BODY + USER32_HEAD + UNICODE_TYPER + PASTE_BODY + UIA_ACTION_BODY;
|
|
742
|
+
|
|
743
|
+
// Avvia una applicazione. Il percorso arriva via stdin come DATO (mai come
|
|
744
|
+
// codice) e viene passato a Start-Process come argomento. Nessuna riga di
|
|
745
|
+
// comando viene composta per interpolazione.
|
|
746
|
+
const LAUNCH_SCRIPT = String.raw`
|
|
747
|
+
$payload = [Console]::In.ReadToEnd() | ConvertFrom-Json
|
|
748
|
+
$target = [string]$payload.target
|
|
749
|
+
if ([string]::IsNullOrWhiteSpace($target)) { [Console]::Out.Write('{"ok":false,"error":"Percorso applicazione mancante."}'); exit 0 }
|
|
750
|
+
$processId = 0
|
|
751
|
+
try {
|
|
752
|
+
if ($target.StartsWith('shell:', [System.StringComparison]::OrdinalIgnoreCase)) {
|
|
753
|
+
# Le applicazioni del Microsoft Store non hanno un eseguibile da avviare:
|
|
754
|
+
# si aprono dalla loro voce in AppsFolder, tramite la shell di Windows.
|
|
755
|
+
Start-Process -FilePath 'explorer.exe' -ArgumentList $target -ErrorAction Stop
|
|
756
|
+
} else {
|
|
757
|
+
$started = Start-Process -FilePath $target -PassThru -ErrorAction Stop
|
|
758
|
+
if ($null -ne $started) { $processId = [int]$started.Id }
|
|
759
|
+
}
|
|
760
|
+
} catch {
|
|
761
|
+
[Console]::Out.Write((ConvertTo-Json -InputObject ([ordered]@{ ok = $false; error = [string]$_.Exception.Message }) -Compress))
|
|
762
|
+
exit 0
|
|
763
|
+
}
|
|
764
|
+
[Console]::Out.Write((ConvertTo-Json -InputObject ([ordered]@{ ok = $true; processId = $processId }) -Compress))
|
|
765
|
+
`;
|
|
766
|
+
|
|
767
|
+
// Navigazione semantica di Esplora file. Il percorso arriva via stdin e il
|
|
768
|
+
// risultato e' positivo soltanto quando Shell.Application espone esattamente
|
|
769
|
+
// quella cartella: inviare Ctrl+L/Invio non basta a dichiarare il successo.
|
|
770
|
+
const OPEN_FOLDER_SCRIPT = String.raw`
|
|
771
|
+
$payload = [Console]::In.ReadToEnd() | ConvertFrom-Json
|
|
772
|
+
$requested = [System.IO.Path]::GetFullPath([string]$payload.path)
|
|
773
|
+
$expected = $requested.TrimEnd([char[]]'\')
|
|
774
|
+
if (-not (Test-Path -LiteralPath $requested -PathType Container)) {
|
|
775
|
+
[Console]::Out.Write((ConvertTo-Json -InputObject ([ordered]@{ ok = $false; code = 'NOT_FOUND'; error = 'La cartella non esiste.' }) -Compress))
|
|
776
|
+
exit 0
|
|
777
|
+
}
|
|
778
|
+
$shell = New-Object -ComObject Shell.Application
|
|
779
|
+
$requestedWindow = [string]$payload.windowId
|
|
780
|
+
$navigated = $false
|
|
781
|
+
if (-not [string]::IsNullOrWhiteSpace($requestedWindow)) {
|
|
782
|
+
foreach ($window in @($shell.Windows())) {
|
|
783
|
+
try {
|
|
784
|
+
if ([string][long]$window.HWND -eq $requestedWindow) {
|
|
785
|
+
$window.Navigate($requested)
|
|
786
|
+
$navigated = $true
|
|
787
|
+
break
|
|
788
|
+
}
|
|
789
|
+
} catch {}
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
if (-not $navigated) {
|
|
793
|
+
try { Start-Process -FilePath 'explorer.exe' -ArgumentList @($requested) -ErrorAction Stop } catch {
|
|
794
|
+
[Console]::Out.Write((ConvertTo-Json -InputObject ([ordered]@{ ok = $false; code = 'OPEN_FAILED'; error = [string]$_.Exception.Message }) -Compress))
|
|
795
|
+
exit 0
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
$lastPath = ''
|
|
799
|
+
for ($attempt = 0; $attempt -lt 40; $attempt++) {
|
|
800
|
+
Start-Sleep -Milliseconds 150
|
|
801
|
+
foreach ($window in @($shell.Windows())) {
|
|
802
|
+
try {
|
|
803
|
+
$current = [System.IO.Path]::GetFullPath([string]$window.Document.Folder.Self.Path).TrimEnd([char[]]'\')
|
|
804
|
+
$lastPath = $current
|
|
805
|
+
if ($current.Equals($expected, [System.StringComparison]::OrdinalIgnoreCase)) {
|
|
806
|
+
[Console]::Out.Write((ConvertTo-Json -InputObject ([ordered]@{
|
|
807
|
+
ok = $true
|
|
808
|
+
verified = $true
|
|
809
|
+
path = $current
|
|
810
|
+
windowId = [string][long]$window.HWND
|
|
811
|
+
title = [string]$window.LocationName
|
|
812
|
+
}) -Compress))
|
|
813
|
+
exit 0
|
|
814
|
+
}
|
|
815
|
+
} catch {}
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
[Console]::Out.Write((ConvertTo-Json -InputObject ([ordered]@{
|
|
819
|
+
ok = $false
|
|
820
|
+
code = 'PATH_NOT_CONFIRMED'
|
|
821
|
+
error = 'Esplora file non ha confermato la cartella richiesta.'
|
|
822
|
+
currentPath = $lastPath
|
|
823
|
+
}) -Compress))
|
|
824
|
+
`;
|
|
825
|
+
|
|
826
|
+
// Windows limita la riga di comando a 32767 caratteri e -EncodedCommand paga
|
|
827
|
+
// UTF-16 piu base64: gli script UI Automation, che portano con se' il walk del
|
|
828
|
+
// tree, la sforano (visto come spawn ENAMETOOLONG). Oltre la soglia il
|
|
829
|
+
// programma passa da un file temporaneo in una cartella dedicata; lo stdin
|
|
830
|
+
// resta il canale del PAYLOAD, quindi nessun input dell'utente diventa codice.
|
|
831
|
+
const MAX_ENCODED_SCRIPT = 8000;
|
|
832
|
+
|
|
833
|
+
async function writeTempScript(text) {
|
|
834
|
+
const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'vascend-computer-'));
|
|
835
|
+
const file = path.join(dir, 'step.ps1');
|
|
836
|
+
// Il BOM evita che PowerShell 5.1 legga il file come ANSI e rovini gli accenti.
|
|
837
|
+
await fs.writeFile(file, '' + text, 'utf8');
|
|
838
|
+
return { file, cleanup: () => fs.rm(dir, { recursive: true, force: true }).catch(() => {}) };
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
function spawnPowerShell(args, options = {}, cleanup = null) {
|
|
842
|
+
return new Promise((resolve) => {
|
|
843
|
+
const child = spawn('powershell.exe', args, {
|
|
844
|
+
windowsHide: true,
|
|
845
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
846
|
+
});
|
|
847
|
+
let stdout = '';
|
|
848
|
+
let stderr = '';
|
|
849
|
+
let settled = false;
|
|
850
|
+
const finish = (result) => {
|
|
851
|
+
if (settled) return;
|
|
852
|
+
settled = true;
|
|
853
|
+
clearTimeout(timer);
|
|
854
|
+
if (typeof cleanup === 'function') cleanup();
|
|
855
|
+
resolve(result);
|
|
856
|
+
};
|
|
857
|
+
const timer = setTimeout(() => {
|
|
858
|
+
try { child.kill(); } catch (_) { /* gia chiuso */ }
|
|
859
|
+
finish({ ok: false, error: 'Computer Use: timeout del controllo Windows.' });
|
|
860
|
+
}, Math.max(1000, Number(options.timeoutMs) || 15000));
|
|
861
|
+
const maxOutput = Math.max(24000, Math.min(256000, Number(options.maxOutput) || 24000));
|
|
862
|
+
child.stdout.on('data', (chunk) => { stdout += String(chunk); if (stdout.length > maxOutput) stdout = stdout.slice(0, maxOutput); });
|
|
863
|
+
child.stderr.on('data', (chunk) => { stderr += String(chunk); if (stderr.length > 2000) stderr = stderr.slice(0, 2000); });
|
|
864
|
+
child.on('error', (error) => finish({ ok: false, error: String(error && error.message || error) }));
|
|
865
|
+
child.on('close', (code) => finish(Number(code) === 0
|
|
866
|
+
? { ok: true, output: stdout.trim() }
|
|
867
|
+
: { ok: false, error: stderr.trim() || 'PowerShell terminato con codice ' + code }));
|
|
868
|
+
if (options.input != null) child.stdin.write(String(options.input));
|
|
869
|
+
child.stdin.end();
|
|
870
|
+
});
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
// PowerShell scrive su stdout con la code page OEM della console: il bullet
|
|
874
|
+
// "•" dei titoli Chromium (Spotify usa "Brano • Artista") diventava il byte
|
|
875
|
+
// 0x07, cioe' BEL, e il JSON arrivava a Node con un carattere di controllo
|
|
876
|
+
// dentro una stringa -> JSON.parse falliva -> la lettura dei controlli
|
|
877
|
+
// sembrava VUOTA. Le stesse code page rovinavano accenti e trattini lunghi nei
|
|
878
|
+
// titoli delle finestre. Si dichiara UTF-8 prima di ogni script.
|
|
879
|
+
const OUTPUT_ENCODING_PREFIX = '[Console]::OutputEncoding = [System.Text.Encoding]::UTF8\n';
|
|
880
|
+
|
|
881
|
+
async function runPowerShell(script, options = {}) {
|
|
882
|
+
const text = OUTPUT_ENCODING_PREFIX + String(script || '');
|
|
883
|
+
const base = ['-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass'];
|
|
884
|
+
if (text.length > MAX_ENCODED_SCRIPT) {
|
|
885
|
+
let temp = null;
|
|
886
|
+
try { temp = await writeTempScript(text); } catch (error) {
|
|
887
|
+
return { ok: false, error: 'Computer Use: non riesco a preparare il controllo Windows. ' + String(error && error.message || error) };
|
|
888
|
+
}
|
|
889
|
+
return spawnPowerShell([...base, '-File', temp.file], options, temp.cleanup);
|
|
890
|
+
}
|
|
891
|
+
// -EncodedCommand separa il programma dallo stdin: computer_type puo cosi'
|
|
892
|
+
// consegnare testo arbitrario a Console.In senza trasformarlo in script.
|
|
893
|
+
const encoded = Buffer.from(text, 'utf16le').toString('base64');
|
|
894
|
+
return spawnPowerShell([...base, '-EncodedCommand', encoded], options);
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
// Ultima rete di sicurezza lato JS: se un'app emette comunque caratteri che
|
|
898
|
+
// PowerShell non ha escapato (o un emoji troncato a meta'), si ripara il testo
|
|
899
|
+
// invece di perdere l'intera lettura.
|
|
900
|
+
function repairJsonText(text) {
|
|
901
|
+
return String(text || '')
|
|
902
|
+
.replace(/[\u0000-\u001f]/g, ' ')
|
|
903
|
+
.replace(/[\uD800-\uDBFF](?![\uDC00-\uDFFF])/g, '')
|
|
904
|
+
.replace(/(^|[^\uD800-\uDBFF])([\uDC00-\uDFFF])/g, '$1');
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
// Un JSON illeggibile NON e' un risultato vuoto. Confonderli rendeva ciechi:
|
|
908
|
+
// una lettura fallita diceva "nessun controllo" e il modello concludeva che
|
|
909
|
+
// l'applicazione non fosse pilotabile.
|
|
910
|
+
function readJsonOutput(result) {
|
|
911
|
+
if (!result || result.ok !== true) {
|
|
912
|
+
return { ok: false, code: 'NATIVE_ERROR', error: String(result && result.error || 'Controllo Windows non riuscito.') };
|
|
913
|
+
}
|
|
914
|
+
const text = String(result.output || '').trim();
|
|
915
|
+
if (!text) return { ok: true, value: null, empty: true };
|
|
916
|
+
try { return { ok: true, value: JSON.parse(text) }; } catch (_) { /* si tenta la riparazione */ }
|
|
917
|
+
try { return { ok: true, value: JSON.parse(repairJsonText(text)), repaired: true }; } catch (error) {
|
|
918
|
+
return {
|
|
919
|
+
ok: false,
|
|
920
|
+
code: 'UNREADABLE',
|
|
921
|
+
error: 'La risposta del controllo Windows non e interpretabile: ' + String(error && error.message || '').slice(0, 140),
|
|
922
|
+
};
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
function parseJsonOutput(result, fallback) {
|
|
927
|
+
const read = readJsonOutput(result);
|
|
928
|
+
if (!read.ok || read.value == null) return fallback;
|
|
929
|
+
if (Array.isArray(fallback)) return Array.isArray(read.value) ? read.value : [read.value];
|
|
930
|
+
return typeof read.value === 'object' ? read.value : fallback;
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
function integer(value, min, max, fallback = 0) {
|
|
934
|
+
const number = Math.trunc(Number(value));
|
|
935
|
+
return Number.isFinite(number) ? Math.max(min, Math.min(max, number)) : fallback;
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
function typingTimeout(text) {
|
|
939
|
+
return Math.min(60000, Math.max(20000, 12000 + String(text || '').length * 55));
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
async function defaultDesktopInfo() {
|
|
943
|
+
return parseJsonOutput(await runPowerShell(DESKTOP_INFO_SCRIPT, { timeoutMs: 5000 }), {});
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
async function defaultWindows() {
|
|
947
|
+
return parseJsonOutput(await runPowerShell(WINDOWS_SCRIPT, { timeoutMs: 7000, maxOutput: 128000 }), []);
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
async function defaultProcesses() {
|
|
951
|
+
// Una workstation di sviluppo puo avere facilmente centinaia di processi.
|
|
952
|
+
// Il JSON non deve venire troncato prima che computer_processes applichi la
|
|
953
|
+
// query richiesta dall'agente (es. "spotify").
|
|
954
|
+
return parseJsonOutput(await runPowerShell(PROCESSES_SCRIPT, { timeoutMs: 7000, maxOutput: 256000 }), []);
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
// Ritorna un ESITO, non solo righe: chi legge deve poter distinguere "finestra
|
|
958
|
+
// senza controlli" da "lettura non riuscita". Confonderli e' esattamente cio'
|
|
959
|
+
// che rendeva Computer Use cieco su Spotify.
|
|
960
|
+
async function defaultUiElements(target = {}) {
|
|
961
|
+
const windowId = /^\d+$/.test(String(target.windowId || '')) ? String(target.windowId) : '';
|
|
962
|
+
const narrow = target.interactiveOnly === true ? '+interactive' : '';
|
|
963
|
+
const read = readJsonOutput(await runPowerShell(UI_ELEMENTS_SCRIPT, {
|
|
964
|
+
input: windowId + narrow,
|
|
965
|
+
timeoutMs: 20000,
|
|
966
|
+
maxOutput: 400000,
|
|
967
|
+
}));
|
|
968
|
+
if (!read.ok) return { ok: false, code: read.code, error: read.error, rows: [] };
|
|
969
|
+
const rows = Array.isArray(read.value) ? read.value : (read.value ? [read.value] : []);
|
|
970
|
+
return { ok: true, rows, ...(read.repaired ? { repaired: true } : {}) };
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
// Azione semantica su un controllo: il target viene ririsolto nel tree vivo
|
|
974
|
+
// dentro lo stesso processo che agisce, quindi non esiste una finestra temporale
|
|
975
|
+
// in cui la UI cambia tra risoluzione e input.
|
|
976
|
+
async function defaultUiAction(request = {}) {
|
|
977
|
+
const payload = {
|
|
978
|
+
windowId: /^\d+$/.test(String(request.windowId || '')) ? String(request.windowId) : '',
|
|
979
|
+
action: String(request.action || 'locate'),
|
|
980
|
+
selector: {
|
|
981
|
+
automationId: String(request.selector && request.selector.automationId || ''),
|
|
982
|
+
name: String(request.selector && request.selector.name || ''),
|
|
983
|
+
type: String(request.selector && request.selector.type || ''),
|
|
984
|
+
className: String(request.selector && request.selector.className || ''),
|
|
985
|
+
centerX: Number(request.selector && request.selector.centerX) || 0,
|
|
986
|
+
centerY: Number(request.selector && request.selector.centerY) || 0,
|
|
987
|
+
},
|
|
988
|
+
value: String(request.value || '').slice(0, MAX_TEXT),
|
|
989
|
+
clear: request.clear === true,
|
|
990
|
+
submit: request.submit === true,
|
|
991
|
+
};
|
|
992
|
+
const result = await runPowerShell(uiaActionScript(payload.action), {
|
|
993
|
+
input: JSON.stringify(payload),
|
|
994
|
+
timeoutMs: payload.action === 'type' ? typingTimeout(payload.value) : 20000,
|
|
995
|
+
maxOutput: 32000,
|
|
996
|
+
});
|
|
997
|
+
if (!result.ok) return { ok: false, code: 'NATIVE_ERROR', error: String(result.error || 'Azione UI Automation non riuscita.') };
|
|
998
|
+
return parseJsonOutput(result, { ok: false, code: 'NATIVE_ERROR', error: 'Risposta UI Automation illeggibile.' });
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
async function defaultLaunch(request = {}) {
|
|
1002
|
+
const target = String(request.target || '').slice(0, 1024);
|
|
1003
|
+
if (!target) return { ok: false, error: 'Percorso applicazione mancante.' };
|
|
1004
|
+
const result = await runPowerShell(LAUNCH_SCRIPT, {
|
|
1005
|
+
input: JSON.stringify({ target }),
|
|
1006
|
+
timeoutMs: 15000,
|
|
1007
|
+
maxOutput: 8000,
|
|
1008
|
+
});
|
|
1009
|
+
if (!result.ok) return { ok: false, error: String(result.error || 'Avvio non riuscito.') };
|
|
1010
|
+
return parseJsonOutput(result, { ok: false, error: 'Risposta di avvio illeggibile.' });
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
async function defaultOpenPath(request = {}) {
|
|
1014
|
+
const requested = String(request.path || '').trim();
|
|
1015
|
+
if (!requested || requested.includes('\0') || !path.win32.isAbsolute(requested)) {
|
|
1016
|
+
return { ok: false, code: 'BAD_PATH', error: 'Serve un percorso assoluto di una cartella.' };
|
|
1017
|
+
}
|
|
1018
|
+
const folderPath = path.win32.normalize(requested);
|
|
1019
|
+
try {
|
|
1020
|
+
const stat = await fs.stat(folderPath);
|
|
1021
|
+
if (!stat.isDirectory()) return { ok: false, code: 'NOT_DIRECTORY', error: 'Il percorso non e una cartella.' };
|
|
1022
|
+
} catch (_) {
|
|
1023
|
+
return { ok: false, code: 'NOT_FOUND', error: 'La cartella non esiste.' };
|
|
1024
|
+
}
|
|
1025
|
+
const result = await runPowerShell(OPEN_FOLDER_SCRIPT, {
|
|
1026
|
+
input: JSON.stringify({
|
|
1027
|
+
path: folderPath,
|
|
1028
|
+
windowId: /^\d+$/.test(String(request.windowId || '')) ? String(request.windowId) : '',
|
|
1029
|
+
}),
|
|
1030
|
+
timeoutMs: 12000,
|
|
1031
|
+
maxOutput: 16000,
|
|
1032
|
+
});
|
|
1033
|
+
if (!result.ok) return { ok: false, code: 'NATIVE_ERROR', error: String(result.error || 'Navigazione non riuscita.') };
|
|
1034
|
+
return parseJsonOutput(result, { ok: false, code: 'NATIVE_ERROR', error: 'Risposta di navigazione illeggibile.' });
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
async function defaultAction(action, input) {
|
|
1038
|
+
if (action === 'click') {
|
|
1039
|
+
const x = integer(input.x, -32768, 32767);
|
|
1040
|
+
const y = integer(input.y, -32768, 32767);
|
|
1041
|
+
const button = String(input.button || 'left').toLowerCase();
|
|
1042
|
+
const count = integer(input.clicks, 1, 3, 1);
|
|
1043
|
+
const down = button === 'right' ? 0x0008 : button === 'middle' ? 0x0020 : 0x0002;
|
|
1044
|
+
const up = button === 'right' ? 0x0010 : button === 'middle' ? 0x0040 : 0x0004;
|
|
1045
|
+
const script = USER32_HEAD + focusGuard(input.windowId) + `[VascendComputerInput]::SetCursorPos(${x}, ${y}) | Out-Null\n`
|
|
1046
|
+
+ Array.from({ length: count }, () => `[VascendComputerInput]::mouse_event(${down},0,0,0,[UIntPtr]::Zero)\n[VascendComputerInput]::mouse_event(${up},0,0,0,[UIntPtr]::Zero)\nStart-Sleep -Milliseconds 45`).join('\n');
|
|
1047
|
+
return runPowerShell(script);
|
|
1048
|
+
}
|
|
1049
|
+
if (action === 'hover') {
|
|
1050
|
+
const x = integer(input.x, -32768, 32767);
|
|
1051
|
+
const y = integer(input.y, -32768, 32767);
|
|
1052
|
+
return runPowerShell(USER32_HEAD + focusGuard(input.windowId)
|
|
1053
|
+
+ `[VascendComputerInput]::SetCursorPos(${x}, ${y}) | Out-Null\nStart-Sleep -Milliseconds 220`);
|
|
1054
|
+
}
|
|
1055
|
+
if (action === 'drag') {
|
|
1056
|
+
const x = integer(input.x, -32768, 32767);
|
|
1057
|
+
const y = integer(input.y, -32768, 32767);
|
|
1058
|
+
const toX = integer(input.toX, -32768, 32767);
|
|
1059
|
+
const toY = integer(input.toY, -32768, 32767);
|
|
1060
|
+
// Il trascinamento viene interpolato: molti controlli ignorano un salto
|
|
1061
|
+
// secco dal punto di partenza a quello d'arrivo perche' non vedono mai il
|
|
1062
|
+
// movimento del mouse.
|
|
1063
|
+
const steps = 14;
|
|
1064
|
+
const path = Array.from({ length: steps }, (_, index) => {
|
|
1065
|
+
const progress = (index + 1) / steps;
|
|
1066
|
+
return `[VascendComputerInput]::SetCursorPos(${Math.round(x + (toX - x) * progress)}, ${Math.round(y + (toY - y) * progress)}) | Out-Null\nStart-Sleep -Milliseconds 18`;
|
|
1067
|
+
}).join('\n');
|
|
1068
|
+
const script = USER32_HEAD + focusGuard(input.windowId)
|
|
1069
|
+
+ `[VascendComputerInput]::SetCursorPos(${x}, ${y}) | Out-Null\nStart-Sleep -Milliseconds 60\n`
|
|
1070
|
+
+ '[VascendComputerInput]::mouse_event(2,0,0,0,[UIntPtr]::Zero)\nStart-Sleep -Milliseconds 90\n'
|
|
1071
|
+
+ path + '\nStart-Sleep -Milliseconds 90\n'
|
|
1072
|
+
+ '[VascendComputerInput]::mouse_event(4,0,0,0,[UIntPtr]::Zero)';
|
|
1073
|
+
return runPowerShell(script, { timeoutMs: 20000 });
|
|
1074
|
+
}
|
|
1075
|
+
if (action === 'type') {
|
|
1076
|
+
const text = String(input.text || '').slice(0, MAX_TEXT);
|
|
1077
|
+
if (!text) return { ok: false, error: 'Testo mancante.' };
|
|
1078
|
+
const submit = input.submit === true
|
|
1079
|
+
? '\n[VascendComputerInput]::keybd_event(13,0,0,[UIntPtr]::Zero)\n[VascendComputerInput]::keybd_event(13,0,2,[UIntPtr]::Zero)\n' : '';
|
|
1080
|
+
// Un testo lungo non puo passare per la digitazione a ritmo umano: si
|
|
1081
|
+
// incolla dagli appunti (poi ripristinati) e resta dentro il timeout.
|
|
1082
|
+
if (text.length > PASTE_THRESHOLD || input.paste === true) {
|
|
1083
|
+
return runPowerShell(USER32_HEAD + focusGuard(input.windowId) + PASTE_BODY
|
|
1084
|
+
+ '$text = [Console]::In.ReadToEnd()\n'
|
|
1085
|
+
+ 'if (-not (Invoke-VascendPaste $text)) { [Console]::Error.Write(\'Appunti non disponibili: il testo non e stato incollato.\'); exit 4 }\n'
|
|
1086
|
+
+ submit, { input: text, timeoutMs: 25000 });
|
|
1087
|
+
}
|
|
1088
|
+
return runPowerShell(USER32_HEAD + focusGuard(input.windowId) + TYPE_SCRIPT + submit, {
|
|
1089
|
+
input: text,
|
|
1090
|
+
timeoutMs: typingTimeout(text),
|
|
1091
|
+
});
|
|
1092
|
+
}
|
|
1093
|
+
if (action === 'key') {
|
|
1094
|
+
const chords = keyChords(input.keys || input.key);
|
|
1095
|
+
if (!chords.length) return { ok: false, error: 'Scorciatoia non valida.' };
|
|
1096
|
+
const repeat = integer(input.repeat, 1, 40, 1);
|
|
1097
|
+
const body = Array.from({ length: repeat }, () => chords.map((codes) => chordScript(codes)).join('\nStart-Sleep -Milliseconds 45\n'))
|
|
1098
|
+
.join('\nStart-Sleep -Milliseconds 45\n');
|
|
1099
|
+
return runPowerShell(USER32_HEAD + focusGuard(input.windowId) + body, {
|
|
1100
|
+
timeoutMs: Math.min(60000, 15000 + chords.length * repeat * 200),
|
|
1101
|
+
});
|
|
1102
|
+
}
|
|
1103
|
+
if (action === 'scroll') {
|
|
1104
|
+
const x = integer(input.x, -32768, 32767);
|
|
1105
|
+
const y = integer(input.y, -32768, 32767);
|
|
1106
|
+
const amount = integer(input.amount, -12000, 12000, -600);
|
|
1107
|
+
// 0x0800 = WHEEL verticale, 0x01000 = WHEEL orizzontale.
|
|
1108
|
+
const wheel = String(input.axis || '').toLowerCase() === 'horizontal' ? 0x01000 : 0x0800;
|
|
1109
|
+
const script = USER32_HEAD + focusGuard(input.windowId) + `[VascendComputerInput]::SetCursorPos(${x}, ${y}) | Out-Null\n[VascendComputerInput]::mouse_event(${wheel},0,0,${amount},[UIntPtr]::Zero)`;
|
|
1110
|
+
return runPowerShell(script);
|
|
1111
|
+
}
|
|
1112
|
+
if (action === 'fill') {
|
|
1113
|
+
const x = integer(input.x, -32768, 32767);
|
|
1114
|
+
const y = integer(input.y, -32768, 32767);
|
|
1115
|
+
const text = String(input.text || '').slice(0, MAX_TEXT);
|
|
1116
|
+
if (!text) return { ok: false, error: 'Testo mancante.' };
|
|
1117
|
+
const submit = input.submit === true
|
|
1118
|
+
? '\n[VascendComputerInput]::keybd_event(13,0,0,[UIntPtr]::Zero)\n[VascendComputerInput]::keybd_event(13,0,2,[UIntPtr]::Zero)\n' : '';
|
|
1119
|
+
const clear = input.clear === true
|
|
1120
|
+
? '[VascendComputerInput]::keybd_event(0x11,0,0,[UIntPtr]::Zero)\n[VascendComputerInput]::keybd_event(0x41,0,0,[UIntPtr]::Zero)\n[VascendComputerInput]::keybd_event(0x41,0,2,[UIntPtr]::Zero)\n[VascendComputerInput]::keybd_event(0x11,0,2,[UIntPtr]::Zero)\nStart-Sleep -Milliseconds 40\n'
|
|
1121
|
+
: '';
|
|
1122
|
+
const head = USER32_HEAD + focusGuard(input.windowId)
|
|
1123
|
+
+ `[VascendComputerInput]::SetCursorPos(${x}, ${y}) | Out-Null\n`
|
|
1124
|
+
+ '[VascendComputerInput]::mouse_event(2,0,0,0,[UIntPtr]::Zero)\n'
|
|
1125
|
+
+ '[VascendComputerInput]::mouse_event(4,0,0,0,[UIntPtr]::Zero)\n'
|
|
1126
|
+
+ 'Start-Sleep -Milliseconds 90\n' + clear;
|
|
1127
|
+
if (text.length > PASTE_THRESHOLD || input.paste === true) {
|
|
1128
|
+
return runPowerShell(head + PASTE_BODY
|
|
1129
|
+
+ '$text = [Console]::In.ReadToEnd()\n'
|
|
1130
|
+
+ 'if (-not (Invoke-VascendPaste $text)) { [Console]::Error.Write(\'Appunti non disponibili: il testo non e stato incollato.\'); exit 4 }\n'
|
|
1131
|
+
+ submit, { input: text, timeoutMs: 25000 });
|
|
1132
|
+
}
|
|
1133
|
+
return runPowerShell(head + TYPE_SCRIPT + submit, { input: text, timeoutMs: typingTimeout(text) });
|
|
1134
|
+
}
|
|
1135
|
+
if (action === 'focusWindow') {
|
|
1136
|
+
const windowId = String(input.windowId || '');
|
|
1137
|
+
if (!/^\d+$/.test(windowId)) return { ok: false, error: 'Finestra collegata non valida.' };
|
|
1138
|
+
return runPowerShell(USER32_HEAD + focusGuard(windowId));
|
|
1139
|
+
}
|
|
1140
|
+
return { ok: false, error: 'Azione Computer Use sconosciuta.' };
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
// Applicazioni installate. E' l'elenco che rende possibile "apri Spotify"
|
|
1144
|
+
// quando l'app non e' in esecuzione, senza comporre comandi arbitrari: si apre
|
|
1145
|
+
// una voce che esiste gia' nel menu Start.
|
|
1146
|
+
//
|
|
1147
|
+
// Servono DUE fonti: le scorciatoie .lnk sul filesystem e le voci di
|
|
1148
|
+
// AppsFolder. Claude Desktop, Spotify e Blocco note su Windows 11 sono
|
|
1149
|
+
// pacchetti del Microsoft Store e non hanno alcun .lnk: cercandoli solo fra i
|
|
1150
|
+
// file non si trovavano proprio le app che l'utente nomina piu' spesso.
|
|
1151
|
+
const START_APPS_SCRIPT = String.raw`
|
|
1152
|
+
$apps = @()
|
|
1153
|
+
try { $apps = @(Get-StartApps | Select-Object Name, AppID) } catch {}
|
|
1154
|
+
[Console]::Out.Write((ConvertTo-Json -InputObject $apps -Depth 3 -Compress))
|
|
1155
|
+
`;
|
|
1156
|
+
|
|
1157
|
+
async function startAppEntries() {
|
|
1158
|
+
const rows = parseJsonOutput(await runPowerShell(START_APPS_SCRIPT, { timeoutMs: 12000, maxOutput: 128000 }), []);
|
|
1159
|
+
return rows
|
|
1160
|
+
.map((row) => ({
|
|
1161
|
+
name: String(row && row.Name || row && row.name || '').slice(0, 160),
|
|
1162
|
+
path: 'shell:AppsFolder\\' + String(row && row.AppID || row && row.appId || ''),
|
|
1163
|
+
}))
|
|
1164
|
+
.filter((row) => row.name && !row.path.endsWith('\\'));
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
async function collectShortcuts(dir, depth, out) {
|
|
1168
|
+
if (depth < 0 || out.length >= 900) return;
|
|
1169
|
+
let entries = [];
|
|
1170
|
+
try { entries = await fs.readdir(dir, { withFileTypes: true }); } catch (_) { return; }
|
|
1171
|
+
for (const entry of entries) {
|
|
1172
|
+
if (out.length >= 900) return;
|
|
1173
|
+
const full = path.join(dir, entry.name);
|
|
1174
|
+
if (entry.isDirectory()) {
|
|
1175
|
+
await collectShortcuts(full, depth - 1, out);
|
|
1176
|
+
continue;
|
|
1177
|
+
}
|
|
1178
|
+
if (!/\.(lnk|url)$/i.test(entry.name)) continue;
|
|
1179
|
+
out.push({ name: entry.name.replace(/\.(lnk|url)$/i, ''), path: full });
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
async function defaultStartMenuApps() {
|
|
1184
|
+
const roots = [
|
|
1185
|
+
path.join(process.env.ProgramData || 'C:\\ProgramData', 'Microsoft', 'Windows', 'Start Menu', 'Programs'),
|
|
1186
|
+
process.env.APPDATA ? path.join(process.env.APPDATA, 'Microsoft', 'Windows', 'Start Menu', 'Programs') : '',
|
|
1187
|
+
].filter(Boolean);
|
|
1188
|
+
const shortcuts = [];
|
|
1189
|
+
const [store] = await Promise.all([
|
|
1190
|
+
startAppEntries().catch(() => []),
|
|
1191
|
+
(async () => { for (const root of roots) await collectShortcuts(root, 3, shortcuts); })(),
|
|
1192
|
+
]);
|
|
1193
|
+
// AppsFolder e' la fonte piu' vicina a quello che l'utente vede nel menu
|
|
1194
|
+
// Start: vince sui .lnk a parita' di nome.
|
|
1195
|
+
const byName = new Map();
|
|
1196
|
+
for (const entry of [...shortcuts, ...store]) {
|
|
1197
|
+
byName.set(entry.name.toLocaleLowerCase(), entry);
|
|
1198
|
+
}
|
|
1199
|
+
return [...byName.values()].slice(0, 900);
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
module.exports = {
|
|
1203
|
+
VK,
|
|
1204
|
+
EXTENDED_VK,
|
|
1205
|
+
MAX_TEXT,
|
|
1206
|
+
PASTE_THRESHOLD,
|
|
1207
|
+
keyCodes,
|
|
1208
|
+
keyChords,
|
|
1209
|
+
keyFlags,
|
|
1210
|
+
chordScript,
|
|
1211
|
+
runPowerShell,
|
|
1212
|
+
repairJsonText,
|
|
1213
|
+
readJsonOutput,
|
|
1214
|
+
focusGuard,
|
|
1215
|
+
typingTimeout,
|
|
1216
|
+
UIA_ACTION_SCRIPT,
|
|
1217
|
+
uiaActionScript,
|
|
1218
|
+
UI_ELEMENTS_SCRIPT,
|
|
1219
|
+
PASTE_SCRIPT,
|
|
1220
|
+
TYPE_SCRIPT,
|
|
1221
|
+
defaultAction,
|
|
1222
|
+
defaultDesktopInfo,
|
|
1223
|
+
defaultWindows,
|
|
1224
|
+
defaultProcesses,
|
|
1225
|
+
defaultUiElements,
|
|
1226
|
+
defaultUiAction,
|
|
1227
|
+
defaultLaunch,
|
|
1228
|
+
defaultOpenPath,
|
|
1229
|
+
defaultStartMenuApps,
|
|
1230
|
+
};
|