@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.
Files changed (620) hide show
  1. package/README.md +202 -0
  2. package/dist/backend/asset-events.d.ts +31 -0
  3. package/dist/backend/asset-events.js +149 -0
  4. package/dist/backend/assets.d.ts +31 -0
  5. package/dist/backend/assets.js +210 -0
  6. package/dist/backend/auth.d.ts +18 -0
  7. package/dist/backend/auth.js +59 -0
  8. package/dist/backend/config.d.ts +14 -0
  9. package/dist/backend/config.js +41 -0
  10. package/dist/backend/conversations.d.ts +21 -0
  11. package/dist/backend/conversations.js +141 -0
  12. package/dist/backend/crypto.d.ts +9 -0
  13. package/dist/backend/crypto.js +68 -0
  14. package/dist/backend/engine.d.ts +13 -0
  15. package/dist/backend/engine.js +78 -0
  16. package/dist/backend/http.d.ts +25 -0
  17. package/dist/backend/http.js +112 -0
  18. package/dist/backend/index.d.ts +1 -0
  19. package/dist/backend/index.js +24 -0
  20. package/dist/backend/routes.d.ts +14 -0
  21. package/dist/backend/routes.js +169 -0
  22. package/dist/backend/sdk.d.ts +12 -0
  23. package/dist/backend/sdk.js +11 -0
  24. package/dist/backend/server.d.ts +12 -0
  25. package/dist/backend/server.js +91 -0
  26. package/dist/backend/temp-image-store.d.ts +46 -0
  27. package/dist/backend/temp-image-store.js +197 -0
  28. package/dist/backend/types.d.ts +103 -0
  29. package/dist/backend/types.js +29 -0
  30. package/dist/backend/users.d.ts +13 -0
  31. package/dist/backend/users.js +76 -0
  32. package/dist/backend/ws-handler.d.ts +14 -0
  33. package/dist/backend/ws-handler.js +72 -0
  34. package/dist/backend/ws.d.ts +19 -0
  35. package/dist/backend/ws.js +146 -0
  36. package/dist/core/agent.d.ts +56 -0
  37. package/dist/core/agent.js +1783 -0
  38. package/dist/core/background.d.ts +45 -0
  39. package/dist/core/background.js +140 -0
  40. package/dist/core/bootstrap.d.ts +45 -0
  41. package/dist/core/bootstrap.js +294 -0
  42. package/dist/core/cli-args.d.ts +41 -0
  43. package/dist/core/cli-args.js +282 -0
  44. package/dist/core/collapsible-output.d.ts +38 -0
  45. package/dist/core/collapsible-output.js +143 -0
  46. package/dist/core/compaction-checkpoint.d.ts +9 -0
  47. package/dist/core/compaction-checkpoint.js +58 -0
  48. package/dist/core/compaction.d.ts +42 -0
  49. package/dist/core/compaction.js +270 -0
  50. package/dist/core/config.d.ts +49 -0
  51. package/dist/core/config.js +417 -0
  52. package/dist/core/context-ledger.d.ts +99 -0
  53. package/dist/core/context-ledger.js +222 -0
  54. package/dist/core/context-policy.d.ts +17 -0
  55. package/dist/core/context-policy.js +81 -0
  56. package/dist/core/continuation-review.d.ts +23 -0
  57. package/dist/core/continuation-review.js +64 -0
  58. package/dist/core/crash-report.d.ts +3 -0
  59. package/dist/core/crash-report.js +27 -0
  60. package/dist/core/credentials.d.ts +11 -0
  61. package/dist/core/credentials.js +183 -0
  62. package/dist/core/doctor.d.ts +24 -0
  63. package/dist/core/doctor.js +125 -0
  64. package/dist/core/effort.d.ts +5 -0
  65. package/dist/core/effort.js +42 -0
  66. package/dist/core/env-paths.d.ts +3 -0
  67. package/dist/core/env-paths.js +38 -0
  68. package/dist/core/execution-guidance.d.ts +9 -0
  69. package/dist/core/execution-guidance.js +73 -0
  70. package/dist/core/execution-target.d.ts +41 -0
  71. package/dist/core/execution-target.js +69 -0
  72. package/dist/core/file-coordination.d.ts +66 -0
  73. package/dist/core/file-coordination.js +176 -0
  74. package/dist/core/file-lease-store.d.ts +14 -0
  75. package/dist/core/file-lease-store.js +89 -0
  76. package/dist/core/image-limits.d.ts +26 -0
  77. package/dist/core/image-limits.js +205 -0
  78. package/dist/core/interruptible-completion.d.ts +3 -0
  79. package/dist/core/interruptible-completion.js +42 -0
  80. package/dist/core/kingdoms-mode-state.d.ts +24 -0
  81. package/dist/core/kingdoms-mode-state.js +139 -0
  82. package/dist/core/licenza.d.ts +38 -0
  83. package/dist/core/licenza.js +125 -0
  84. package/dist/core/model-limits.d.ts +2 -0
  85. package/dist/core/model-limits.js +36 -0
  86. package/dist/core/output-lifecycle.d.ts +44 -0
  87. package/dist/core/output-lifecycle.js +190 -0
  88. package/dist/core/package-update.d.ts +15 -0
  89. package/dist/core/package-update.js +121 -0
  90. package/dist/core/permission-queue.d.ts +2 -0
  91. package/dist/core/permission-queue.js +20 -0
  92. package/dist/core/prompt-attachment.d.ts +21 -0
  93. package/dist/core/prompt-attachment.js +123 -0
  94. package/dist/core/request-plan.d.ts +16 -0
  95. package/dist/core/request-plan.js +36 -0
  96. package/dist/core/run-timing.d.ts +17 -0
  97. package/dist/core/run-timing.js +123 -0
  98. package/dist/core/self-update.d.ts +17 -0
  99. package/dist/core/self-update.js +54 -0
  100. package/dist/core/session-refs.d.ts +27 -0
  101. package/dist/core/session-refs.js +126 -0
  102. package/dist/core/session-storage.d.ts +4 -0
  103. package/dist/core/session-storage.js +48 -0
  104. package/dist/core/session-title.d.ts +1 -0
  105. package/dist/core/session-title.js +21 -0
  106. package/dist/core/session-usage.d.ts +17 -0
  107. package/dist/core/session-usage.js +74 -0
  108. package/dist/core/session.d.ts +58 -0
  109. package/dist/core/session.js +296 -0
  110. package/dist/core/settings.d.ts +17 -0
  111. package/dist/core/settings.js +107 -0
  112. package/dist/core/storage-retention.d.ts +21 -0
  113. package/dist/core/storage-retention.js +227 -0
  114. package/dist/core/text-sanitize.d.ts +1 -0
  115. package/dist/core/text-sanitize.js +35 -0
  116. package/dist/core/tool-artifacts.d.ts +6 -0
  117. package/dist/core/tool-artifacts.js +51 -0
  118. package/dist/core/tool-compress.d.ts +8 -0
  119. package/dist/core/tool-compress.js +111 -0
  120. package/dist/core/tool-executor.d.ts +16 -0
  121. package/dist/core/tool-executor.js +97 -0
  122. package/dist/core/tool-result-index.d.ts +52 -0
  123. package/dist/core/tool-result-index.js +327 -0
  124. package/dist/core/tool-result-store.d.ts +38 -0
  125. package/dist/core/tool-result-store.js +190 -0
  126. package/dist/core/tool-scheduler.d.ts +16 -0
  127. package/dist/core/tool-scheduler.js +35 -0
  128. package/dist/core/trust.d.ts +21 -0
  129. package/dist/core/trust.js +95 -0
  130. package/dist/core/types.d.ts +402 -0
  131. package/dist/core/types.js +1 -0
  132. package/dist/core/update-check.d.ts +26 -0
  133. package/dist/core/update-check.js +89 -0
  134. package/dist/core/update-process.d.ts +10 -0
  135. package/dist/core/update-process.js +31 -0
  136. package/dist/core/user-inbox.d.ts +23 -0
  137. package/dist/core/user-inbox.js +56 -0
  138. package/dist/core/user-input.d.ts +10 -0
  139. package/dist/core/user-input.js +35 -0
  140. package/dist/core/version.d.ts +1 -0
  141. package/dist/core/version.js +10 -0
  142. package/dist/core/vibe-modes.d.ts +38 -0
  143. package/dist/core/vibe-modes.js +383 -0
  144. package/dist/core/workspace-roots.d.ts +13 -0
  145. package/dist/core/workspace-roots.js +91 -0
  146. package/dist/core/workspace-sessions.d.ts +51 -0
  147. package/dist/core/workspace-sessions.js +243 -0
  148. package/dist/cowork/client.d.ts +29 -0
  149. package/dist/cowork/client.js +49 -0
  150. package/dist/cowork/engine-bridge.d.ts +22 -0
  151. package/dist/cowork/engine-bridge.js +98 -0
  152. package/dist/cowork/index.d.ts +11 -0
  153. package/dist/cowork/index.js +16 -0
  154. package/dist/cowork/ipc.d.ts +97 -0
  155. package/dist/cowork/ipc.js +96 -0
  156. package/dist/cowork/manager.d.ts +66 -0
  157. package/dist/cowork/manager.js +325 -0
  158. package/dist/cowork/store.d.ts +23 -0
  159. package/dist/cowork/store.js +182 -0
  160. package/dist/cowork/sync.d.ts +20 -0
  161. package/dist/cowork/sync.js +164 -0
  162. package/dist/cowork/types.d.ts +78 -0
  163. package/dist/cowork/types.js +21 -0
  164. package/dist/extensions/ext-tools.d.ts +3 -0
  165. package/dist/extensions/ext-tools.js +73 -0
  166. package/dist/extensions/loader.d.ts +91 -0
  167. package/dist/extensions/loader.js +474 -0
  168. package/dist/extensions/settings.d.ts +20 -0
  169. package/dist/extensions/settings.js +86 -0
  170. package/dist/extensions/types.d.ts +219 -0
  171. package/dist/extensions/types.js +1 -0
  172. package/dist/horizon/async-queue.d.ts +9 -0
  173. package/dist/horizon/async-queue.js +46 -0
  174. package/dist/horizon/engine.d.ts +85 -0
  175. package/dist/horizon/engine.js +603 -0
  176. package/dist/horizon/index.d.ts +4 -0
  177. package/dist/horizon/index.js +2 -0
  178. package/dist/horizon/main-team.d.ts +163 -0
  179. package/dist/horizon/main-team.js +774 -0
  180. package/dist/horizon/types.d.ts +233 -0
  181. package/dist/horizon/types.js +1 -0
  182. package/dist/index.d.ts +2 -0
  183. package/dist/index.js +233 -0
  184. package/dist/mcp/protocol.d.ts +81 -0
  185. package/dist/mcp/protocol.js +122 -0
  186. package/dist/package.json +1 -0
  187. package/dist/packages/packages.d.ts +12 -0
  188. package/dist/packages/packages.js +323 -0
  189. package/dist/prompts/templates.d.ts +13 -0
  190. package/dist/prompts/templates.js +139 -0
  191. package/dist/providers/anthropic-oauth.d.ts +13 -0
  192. package/dist/providers/anthropic-oauth.js +124 -0
  193. package/dist/providers/anthropic.d.ts +27 -0
  194. package/dist/providers/anthropic.js +526 -0
  195. package/dist/providers/catalog.d.ts +15 -0
  196. package/dist/providers/catalog.js +166 -0
  197. package/dist/providers/effort.d.ts +12 -0
  198. package/dist/providers/effort.js +102 -0
  199. package/dist/providers/model-catalog.d.ts +43 -0
  200. package/dist/providers/model-catalog.js +70 -0
  201. package/dist/providers/model-effort.d.ts +5 -0
  202. package/dist/providers/model-effort.js +43 -0
  203. package/dist/providers/model-registry.d.ts +39 -0
  204. package/dist/providers/model-registry.js +142 -0
  205. package/dist/providers/models-dev.d.ts +9 -0
  206. package/dist/providers/models-dev.js +97 -0
  207. package/dist/providers/oauth-callback-server.d.ts +28 -0
  208. package/dist/providers/oauth-callback-server.js +184 -0
  209. package/dist/providers/oauth-orchestrator.d.ts +58 -0
  210. package/dist/providers/oauth-orchestrator.js +159 -0
  211. package/dist/providers/oauth-store.d.ts +12 -0
  212. package/dist/providers/oauth-store.js +139 -0
  213. package/dist/providers/open-browser.d.ts +20 -0
  214. package/dist/providers/open-browser.js +92 -0
  215. package/dist/providers/openai-compat.d.ts +14 -0
  216. package/dist/providers/openai-compat.js +523 -0
  217. package/dist/providers/openai-oauth.d.ts +15 -0
  218. package/dist/providers/openai-oauth.js +382 -0
  219. package/dist/providers/openrouter-auth.d.ts +73 -0
  220. package/dist/providers/openrouter-auth.js +321 -0
  221. package/dist/providers/openrouter-manual-fallback.d.ts +25 -0
  222. package/dist/providers/openrouter-manual-fallback.js +161 -0
  223. package/dist/providers/openrouter.d.ts +15 -0
  224. package/dist/providers/openrouter.js +107 -0
  225. package/dist/providers/plan-limits.d.ts +6 -0
  226. package/dist/providers/plan-limits.js +175 -0
  227. package/dist/providers/provider-reset.d.ts +4 -0
  228. package/dist/providers/provider-reset.js +83 -0
  229. package/dist/providers/quota-limits.d.ts +4 -0
  230. package/dist/providers/quota-limits.js +33 -0
  231. package/dist/providers/registry.d.ts +33 -0
  232. package/dist/providers/registry.js +213 -0
  233. package/dist/providers/retry.d.ts +37 -0
  234. package/dist/providers/retry.js +347 -0
  235. package/dist/rpc/server.d.ts +23 -0
  236. package/dist/rpc/server.js +222 -0
  237. package/dist/sdk.d.ts +67 -0
  238. package/dist/sdk.js +64 -0
  239. package/dist/skills/skills.d.ts +17 -0
  240. package/dist/skills/skills.js +155 -0
  241. package/dist/text-sanitize.js +28 -0
  242. package/dist/tools/ask-user.d.ts +2 -0
  243. package/dist/tools/ask-user.js +60 -0
  244. package/dist/tools/builtin.d.ts +48 -0
  245. package/dist/tools/builtin.js +2494 -0
  246. package/dist/tools/capabilities.d.ts +5 -0
  247. package/dist/tools/capabilities.js +55 -0
  248. package/dist/tools/computer-capture.d.ts +2 -0
  249. package/dist/tools/computer-capture.js +91 -0
  250. package/dist/tools/computer-posix.d.ts +5 -0
  251. package/dist/tools/computer-posix.js +136 -0
  252. package/dist/tools/computer-use.d.ts +14 -0
  253. package/dist/tools/computer-use.js +108 -0
  254. package/dist/tools/discovery.d.ts +12 -0
  255. package/dist/tools/discovery.js +205 -0
  256. package/dist/tools/execute.d.ts +2 -0
  257. package/dist/tools/execute.js +73 -0
  258. package/dist/tools/normalize-input.d.ts +17 -0
  259. package/dist/tools/normalize-input.js +174 -0
  260. package/dist/tools/output-artifact.d.ts +3 -0
  261. package/dist/tools/output-artifact.js +80 -0
  262. package/dist/tools/pagination.d.ts +15 -0
  263. package/dist/tools/pagination.js +93 -0
  264. package/dist/tools/plan-status.d.ts +2 -0
  265. package/dist/tools/plan-status.js +109 -0
  266. package/dist/tools/program-input.d.ts +3 -0
  267. package/dist/tools/program-input.js +19 -0
  268. package/dist/tools/program.d.ts +3 -0
  269. package/dist/tools/program.js +567 -0
  270. package/dist/tools/protocol-catalog.d.ts +12 -0
  271. package/dist/tools/protocol-catalog.js +46 -0
  272. package/dist/tools/ramification-tools.d.ts +12 -0
  273. package/dist/tools/ramification-tools.js +528 -0
  274. package/dist/tools/regex-compat.d.ts +9 -0
  275. package/dist/tools/regex-compat.js +96 -0
  276. package/dist/tools/registry.d.ts +51 -0
  277. package/dist/tools/registry.js +185 -0
  278. package/dist/tools/resolve.d.ts +2 -0
  279. package/dist/tools/resolve.js +107 -0
  280. package/dist/tools/shell-env.d.ts +2 -0
  281. package/dist/tools/shell-env.js +7 -0
  282. package/dist/tools/team-input.d.ts +3 -0
  283. package/dist/tools/team-input.js +129 -0
  284. package/dist/tools/team-tools.d.ts +7 -0
  285. package/dist/tools/team-tools.js +348 -0
  286. package/dist/tools/workspace-sessions.d.ts +2 -0
  287. package/dist/tools/workspace-sessions.js +35 -0
  288. package/dist/tui/account-entry.d.ts +1 -0
  289. package/dist/tui/account-entry.js +93 -0
  290. package/dist/tui/attachment-highlight.d.ts +1 -0
  291. package/dist/tui/attachment-highlight.js +24 -0
  292. package/dist/tui/attachment-refs.d.ts +2 -0
  293. package/dist/tui/attachment-refs.js +3 -0
  294. package/dist/tui/bottom-input.d.ts +219 -0
  295. package/dist/tui/bottom-input.js +1958 -0
  296. package/dist/tui/branding.d.ts +107 -0
  297. package/dist/tui/branding.js +750 -0
  298. package/dist/tui/clipboard-image.d.ts +10 -0
  299. package/dist/tui/clipboard-image.js +163 -0
  300. package/dist/tui/clipboard-text.d.ts +9 -0
  301. package/dist/tui/clipboard-text.js +51 -0
  302. package/dist/tui/clipboard-windows.d.ts +8 -0
  303. package/dist/tui/clipboard-windows.js +84 -0
  304. package/dist/tui/collapsible-command.d.ts +15 -0
  305. package/dist/tui/collapsible-command.js +48 -0
  306. package/dist/tui/command-feedback.d.ts +10 -0
  307. package/dist/tui/command-feedback.js +46 -0
  308. package/dist/tui/command-output-registry.d.ts +28 -0
  309. package/dist/tui/command-output-registry.js +103 -0
  310. package/dist/tui/command-transcript.d.ts +12 -0
  311. package/dist/tui/command-transcript.js +90 -0
  312. package/dist/tui/commands.d.ts +20 -0
  313. package/dist/tui/commands.js +131 -0
  314. package/dist/tui/context-bar.d.ts +4 -0
  315. package/dist/tui/context-bar.js +74 -0
  316. package/dist/tui/context-meter.d.ts +25 -0
  317. package/dist/tui/context-meter.js +81 -0
  318. package/dist/tui/cost-binding.d.ts +8 -0
  319. package/dist/tui/cost-binding.js +43 -0
  320. package/dist/tui/cost-tracker.d.ts +49 -0
  321. package/dist/tui/cost-tracker.js +122 -0
  322. package/dist/tui/diff.d.ts +9 -0
  323. package/dist/tui/diff.js +119 -0
  324. package/dist/tui/i18n-catalog.d.ts +5 -0
  325. package/dist/tui/i18n-catalog.js +184 -0
  326. package/dist/tui/i18n.d.ts +6 -0
  327. package/dist/tui/i18n.js +10 -0
  328. package/dist/tui/image-attachments.d.ts +46 -0
  329. package/dist/tui/image-attachments.js +167 -0
  330. package/dist/tui/images.d.ts +12 -0
  331. package/dist/tui/images.js +87 -0
  332. package/dist/tui/intro.d.ts +13 -0
  333. package/dist/tui/intro.js +109 -0
  334. package/dist/tui/language-preference.d.ts +6 -0
  335. package/dist/tui/language-preference.js +24 -0
  336. package/dist/tui/live-output.d.ts +2 -0
  337. package/dist/tui/live-output.js +18 -0
  338. package/dist/tui/locales.d.ts +19 -0
  339. package/dist/tui/locales.js +22 -0
  340. package/dist/tui/login-menu.d.ts +17 -0
  341. package/dist/tui/login-menu.js +58 -0
  342. package/dist/tui/logo.d.ts +8 -0
  343. package/dist/tui/logo.js +305 -0
  344. package/dist/tui/markdown-inline.d.ts +3 -0
  345. package/dist/tui/markdown-inline.js +99 -0
  346. package/dist/tui/markdown-table.d.ts +4 -0
  347. package/dist/tui/markdown-table.js +63 -0
  348. package/dist/tui/markdown.d.ts +11 -0
  349. package/dist/tui/markdown.js +257 -0
  350. package/dist/tui/mcp-transcript.d.ts +7 -0
  351. package/dist/tui/mcp-transcript.js +40 -0
  352. package/dist/tui/model-details.d.ts +2 -0
  353. package/dist/tui/model-details.js +21 -0
  354. package/dist/tui/model-selection.d.ts +7 -0
  355. package/dist/tui/model-selection.js +41 -0
  356. package/dist/tui/native-scrollback.d.ts +27 -0
  357. package/dist/tui/native-scrollback.js +134 -0
  358. package/dist/tui/onboarding.d.ts +72 -0
  359. package/dist/tui/onboarding.js +207 -0
  360. package/dist/tui/paste-collapse.d.ts +39 -0
  361. package/dist/tui/paste-collapse.js +117 -0
  362. package/dist/tui/perm-mode.d.ts +8 -0
  363. package/dist/tui/perm-mode.js +41 -0
  364. package/dist/tui/prompt-queue-store.d.ts +26 -0
  365. package/dist/tui/prompt-queue-store.js +87 -0
  366. package/dist/tui/prompt-queue.d.ts +47 -0
  367. package/dist/tui/prompt-queue.js +187 -0
  368. package/dist/tui/provider-menu.d.ts +33 -0
  369. package/dist/tui/provider-menu.js +175 -0
  370. package/dist/tui/question.d.ts +24 -0
  371. package/dist/tui/question.js +32 -0
  372. package/dist/tui/queue-screen.d.ts +25 -0
  373. package/dist/tui/queue-screen.js +152 -0
  374. package/dist/tui/queue-team.d.ts +3 -0
  375. package/dist/tui/queue-team.js +28 -0
  376. package/dist/tui/queued-questions.d.ts +20 -0
  377. package/dist/tui/queued-questions.js +163 -0
  378. package/dist/tui/quick-help.d.ts +1 -0
  379. package/dist/tui/quick-help.js +53 -0
  380. package/dist/tui/repl.d.ts +78 -0
  381. package/dist/tui/repl.js +4130 -0
  382. package/dist/tui/restore-queued-input.d.ts +3 -0
  383. package/dist/tui/restore-queued-input.js +20 -0
  384. package/dist/tui/review-detail.d.ts +6 -0
  385. package/dist/tui/review-detail.js +56 -0
  386. package/dist/tui/review-files.d.ts +3 -0
  387. package/dist/tui/review-files.js +22 -0
  388. package/dist/tui/review-render.d.ts +22 -0
  389. package/dist/tui/review-render.js +98 -0
  390. package/dist/tui/review-screen.d.ts +38 -0
  391. package/dist/tui/review-screen.js +358 -0
  392. package/dist/tui/review-store.d.ts +60 -0
  393. package/dist/tui/review-store.js +270 -0
  394. package/dist/tui/review-team.d.ts +2 -0
  395. package/dist/tui/review-team.js +27 -0
  396. package/dist/tui/selector-mouse.d.ts +26 -0
  397. package/dist/tui/selector-mouse.js +65 -0
  398. package/dist/tui/selector.d.ts +51 -0
  399. package/dist/tui/selector.js +299 -0
  400. package/dist/tui/session-replay.d.ts +10 -0
  401. package/dist/tui/session-replay.js +78 -0
  402. package/dist/tui/session-usage.d.ts +22 -0
  403. package/dist/tui/session-usage.js +55 -0
  404. package/dist/tui/skill-transcript.d.ts +5 -0
  405. package/dist/tui/skill-transcript.js +31 -0
  406. package/dist/tui/startup.d.ts +5 -0
  407. package/dist/tui/startup.js +61 -0
  408. package/dist/tui/status-line-loader.d.ts +8 -0
  409. package/dist/tui/status-line-loader.js +33 -0
  410. package/dist/tui/status-line.d.ts +38 -0
  411. package/dist/tui/status-line.js +213 -0
  412. package/dist/tui/system-language.d.ts +8 -0
  413. package/dist/tui/system-language.js +41 -0
  414. package/dist/tui/task-labels.d.ts +2 -0
  415. package/dist/tui/task-labels.js +7 -0
  416. package/dist/tui/term-bg.d.ts +4 -0
  417. package/dist/tui/term-bg.js +67 -0
  418. package/dist/tui/terminal-screen.d.ts +12 -0
  419. package/dist/tui/terminal-screen.js +1 -0
  420. package/dist/tui/terminal-text.d.ts +7 -0
  421. package/dist/tui/terminal-text.js +123 -0
  422. package/dist/tui/theme.d.ts +25 -0
  423. package/dist/tui/theme.js +383 -0
  424. package/dist/tui/tool-style.d.ts +7 -0
  425. package/dist/tui/tool-style.js +119 -0
  426. package/dist/tui/update-prompt.d.ts +14 -0
  427. package/dist/tui/update-prompt.js +104 -0
  428. package/dist/tui/usage-footer.d.ts +9 -0
  429. package/dist/tui/usage-footer.js +123 -0
  430. package/dist/tui/vascend-login.d.ts +68 -0
  431. package/dist/tui/vascend-login.js +355 -0
  432. package/dist/tui/web-login.d.ts +28 -0
  433. package/dist/tui/web-login.js +115 -0
  434. package/dist/vascend/activity-status.d.ts +12 -0
  435. package/dist/vascend/activity-status.js +68 -0
  436. package/dist/vascend/architecture-map.d.ts +73 -0
  437. package/dist/vascend/architecture-map.js +424 -0
  438. package/dist/vascend/attention-layout.d.ts +19 -0
  439. package/dist/vascend/attention-layout.js +141 -0
  440. package/dist/vascend/budget-allocator.d.ts +31 -0
  441. package/dist/vascend/budget-allocator.js +79 -0
  442. package/dist/vascend/candidate-collector.d.ts +20 -0
  443. package/dist/vascend/candidate-collector.js +220 -0
  444. package/dist/vascend/code-events.d.ts +24 -0
  445. package/dist/vascend/code-events.js +304 -0
  446. package/dist/vascend/code-graph-memory.d.ts +38 -0
  447. package/dist/vascend/code-graph-memory.js +334 -0
  448. package/dist/vascend/code-graph-runtime.d.ts +19 -0
  449. package/dist/vascend/code-graph-runtime.js +190 -0
  450. package/dist/vascend/code-graph-types.d.ts +58 -0
  451. package/dist/vascend/code-graph-types.js +50 -0
  452. package/dist/vascend/code-parser.d.ts +4 -0
  453. package/dist/vascend/code-parser.js +257 -0
  454. package/dist/vascend/competitor-knowledge.d.ts +53 -0
  455. package/dist/vascend/competitor-knowledge.js +262 -0
  456. package/dist/vascend/completeness.d.ts +16 -0
  457. package/dist/vascend/completeness.js +90 -0
  458. package/dist/vascend/context-autotuner.d.ts +45 -0
  459. package/dist/vascend/context-autotuner.js +231 -0
  460. package/dist/vascend/context-candidate.d.ts +45 -0
  461. package/dist/vascend/context-candidate.js +150 -0
  462. package/dist/vascend/context-capsule.d.ts +51 -0
  463. package/dist/vascend/context-capsule.js +133 -0
  464. package/dist/vascend/context-fabric.d.ts +108 -0
  465. package/dist/vascend/context-fabric.js +480 -0
  466. package/dist/vascend/context-feedback-loop.d.ts +56 -0
  467. package/dist/vascend/context-feedback-loop.js +204 -0
  468. package/dist/vascend/context-learning-identity.d.ts +4 -0
  469. package/dist/vascend/context-learning-identity.js +8 -0
  470. package/dist/vascend/context-links.d.ts +14 -0
  471. package/dist/vascend/context-links.js +83 -0
  472. package/dist/vascend/context-lint.d.ts +22 -0
  473. package/dist/vascend/context-lint.js +91 -0
  474. package/dist/vascend/context-market-selection.d.ts +17 -0
  475. package/dist/vascend/context-market-selection.js +50 -0
  476. package/dist/vascend/context-metrics.d.ts +25 -0
  477. package/dist/vascend/context-metrics.js +57 -0
  478. package/dist/vascend/context-pack-render.d.ts +16 -0
  479. package/dist/vascend/context-pack-render.js +52 -0
  480. package/dist/vascend/context-pareto.d.ts +33 -0
  481. package/dist/vascend/context-pareto.js +165 -0
  482. package/dist/vascend/context-pattern-explorer.d.ts +41 -0
  483. package/dist/vascend/context-pattern-explorer.js +276 -0
  484. package/dist/vascend/context-regret.d.ts +32 -0
  485. package/dist/vascend/context-regret.js +93 -0
  486. package/dist/vascend/context-retriever.d.ts +22 -0
  487. package/dist/vascend/context-retriever.js +243 -0
  488. package/dist/vascend/context-scope-index.d.ts +5 -0
  489. package/dist/vascend/context-scope-index.js +96 -0
  490. package/dist/vascend/context-scorer.d.ts +35 -0
  491. package/dist/vascend/context-scorer.js +162 -0
  492. package/dist/vascend/context-search-index.d.ts +12 -0
  493. package/dist/vascend/context-search-index.js +25 -0
  494. package/dist/vascend/context-snapshot.d.ts +22 -0
  495. package/dist/vascend/context-snapshot.js +135 -0
  496. package/dist/vascend/context-window-compiler.d.ts +42 -0
  497. package/dist/vascend/context-window-compiler.js +237 -0
  498. package/dist/vascend/contract-memory.d.ts +61 -0
  499. package/dist/vascend/contract-memory.js +435 -0
  500. package/dist/vascend/council.d.ts +58 -0
  501. package/dist/vascend/council.js +337 -0
  502. package/dist/vascend/decision-promotion.d.ts +48 -0
  503. package/dist/vascend/decision-promotion.js +410 -0
  504. package/dist/vascend/delta-context.d.ts +12 -0
  505. package/dist/vascend/delta-context.js +36 -0
  506. package/dist/vascend/dev-context-compiler.d.ts +27 -0
  507. package/dist/vascend/dev-context-compiler.js +133 -0
  508. package/dist/vascend/dev-context-layout.d.ts +30 -0
  509. package/dist/vascend/dev-context-layout.js +262 -0
  510. package/dist/vascend/evidence-slots.d.ts +14 -0
  511. package/dist/vascend/evidence-slots.js +55 -0
  512. package/dist/vascend/expansion-levels.d.ts +15 -0
  513. package/dist/vascend/expansion-levels.js +178 -0
  514. package/dist/vascend/file-lock.d.ts +7 -0
  515. package/dist/vascend/file-lock.js +95 -0
  516. package/dist/vascend/granular-kingdom.d.ts +25 -0
  517. package/dist/vascend/granular-kingdom.js +259 -0
  518. package/dist/vascend/granular-launcher.d.ts +48 -0
  519. package/dist/vascend/granular-launcher.js +202 -0
  520. package/dist/vascend/kingdom-bus.d.ts +25 -0
  521. package/dist/vascend/kingdom-bus.js +54 -0
  522. package/dist/vascend/kingdom-channel.d.ts +22 -0
  523. package/dist/vascend/kingdom-channel.js +180 -0
  524. package/dist/vascend/kingdom-kpi.d.ts +48 -0
  525. package/dist/vascend/kingdom-kpi.js +172 -0
  526. package/dist/vascend/kingdom-live.d.ts +28 -0
  527. package/dist/vascend/kingdom-live.js +61 -0
  528. package/dist/vascend/kingdom-run.d.ts +18 -0
  529. package/dist/vascend/kingdom-run.js +506 -0
  530. package/dist/vascend/kingdom-scribe.d.ts +20 -0
  531. package/dist/vascend/kingdom-scribe.js +52 -0
  532. package/dist/vascend/kingdom.d.ts +79 -0
  533. package/dist/vascend/kingdom.js +870 -0
  534. package/dist/vascend/lexical-index.d.ts +10 -0
  535. package/dist/vascend/lexical-index.js +99 -0
  536. package/dist/vascend/lexical-search.d.ts +29 -0
  537. package/dist/vascend/lexical-search.js +262 -0
  538. package/dist/vascend/long-term-context.d.ts +51 -0
  539. package/dist/vascend/long-term-context.js +194 -0
  540. package/dist/vascend/mandate.d.ts +33 -0
  541. package/dist/vascend/mandate.js +107 -0
  542. package/dist/vascend/memory-packet.d.ts +13 -0
  543. package/dist/vascend/memory-packet.js +155 -0
  544. package/dist/vascend/memory-status.d.ts +22 -0
  545. package/dist/vascend/memory-status.js +73 -0
  546. package/dist/vascend/memory-store.d.ts +62 -0
  547. package/dist/vascend/memory-store.js +273 -0
  548. package/dist/vascend/objective-revision.d.ts +69 -0
  549. package/dist/vascend/objective-revision.js +319 -0
  550. package/dist/vascend/patch-plan-store.d.ts +33 -0
  551. package/dist/vascend/patch-plan-store.js +240 -0
  552. package/dist/vascend/patch-plan.d.ts +97 -0
  553. package/dist/vascend/patch-plan.js +345 -0
  554. package/dist/vascend/plan-progress.d.ts +11 -0
  555. package/dist/vascend/plan-progress.js +68 -0
  556. package/dist/vascend/plan-store.d.ts +64 -0
  557. package/dist/vascend/plan-store.js +460 -0
  558. package/dist/vascend/proof-chains.d.ts +19 -0
  559. package/dist/vascend/proof-chains.js +120 -0
  560. package/dist/vascend/ramification-store.d.ts +25 -0
  561. package/dist/vascend/ramification-store.js +94 -0
  562. package/dist/vascend/ramification.d.ts +193 -0
  563. package/dist/vascend/ramification.js +583 -0
  564. package/dist/vascend/recall-planner.d.ts +17 -0
  565. package/dist/vascend/recall-planner.js +109 -0
  566. package/dist/vascend/requirements.d.ts +31 -0
  567. package/dist/vascend/requirements.js +151 -0
  568. package/dist/vascend/reviewer.d.ts +9 -0
  569. package/dist/vascend/reviewer.js +93 -0
  570. package/dist/vascend/room-graph.d.ts +29 -0
  571. package/dist/vascend/room-graph.js +242 -0
  572. package/dist/vascend/room-memory.d.ts +57 -0
  573. package/dist/vascend/room-memory.js +631 -0
  574. package/dist/vascend/runtime.d.ts +84 -0
  575. package/dist/vascend/runtime.js +286 -0
  576. package/dist/vascend/self-improve.d.ts +104 -0
  577. package/dist/vascend/self-improve.js +396 -0
  578. package/dist/vascend/session-search.d.ts +52 -0
  579. package/dist/vascend/session-search.js +349 -0
  580. package/dist/vascend/supersession-graph.d.ts +14 -0
  581. package/dist/vascend/supersession-graph.js +70 -0
  582. package/dist/vascend/team-areas.d.ts +9 -0
  583. package/dist/vascend/team-areas.js +28 -0
  584. package/dist/vascend/team-handoff.d.ts +24 -0
  585. package/dist/vascend/team-handoff.js +37 -0
  586. package/dist/vascend/team-scheduler.d.ts +13 -0
  587. package/dist/vascend/team-scheduler.js +74 -0
  588. package/dist/vascend/team-session.d.ts +42 -0
  589. package/dist/vascend/team-session.js +142 -0
  590. package/dist/vascend/test-failure-memory.d.ts +76 -0
  591. package/dist/vascend/test-failure-memory.js +354 -0
  592. package/dist/vascend/token-market.d.ts +42 -0
  593. package/dist/vascend/token-market.js +144 -0
  594. package/dist/vascend/tuner-baseline-store.d.ts +52 -0
  595. package/dist/vascend/tuner-baseline-store.js +446 -0
  596. package/dist/vascend/user-profile.d.ts +44 -0
  597. package/dist/vascend/user-profile.js +131 -0
  598. package/dist/vascend/vision.d.ts +94 -0
  599. package/dist/vascend/vision.js +375 -0
  600. package/dist/vascend/work-agent.d.ts +12 -0
  601. package/dist/vascend/work-agent.js +83 -0
  602. package/dist/vascend/work-queue.d.ts +62 -0
  603. package/dist/vascend/work-queue.js +255 -0
  604. package/dist/vascend/work-runner.d.ts +64 -0
  605. package/dist/vascend/work-runner.js +137 -0
  606. package/dist/vascend/worktree.d.ts +38 -0
  607. package/dist/vascend/worktree.js +206 -0
  608. package/dist/vendor/computer-use/README.md +7 -0
  609. package/dist/vendor/computer-use/computer-desktop.js +211 -0
  610. package/dist/vendor/computer-use/computer-observe.js +315 -0
  611. package/dist/vendor/computer-use/computer-scripts.js +1230 -0
  612. package/dist/vendor/computer-use/computer-tools.js +2136 -0
  613. package/dist/vendor/computer-use/package.json +1 -0
  614. package/dist/vendor/computer-use/rac/atomic-input.js +91 -0
  615. package/dist/vendor/computer-use/rac/grammatica.js +101 -0
  616. package/dist/vendor/computer-use/rac/parser.js +185 -0
  617. package/dist/vendor/computer-use/rac/protocol.js +136 -0
  618. package/install.ps1 +30 -0
  619. package/install.sh +50 -0
  620. 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
+ };