onbuzz 4.9.13 → 4.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/node_modules/glob/README.md +31 -5
- package/node_modules/glob/dist/commonjs/glob.d.ts +8 -0
- package/node_modules/glob/dist/commonjs/glob.d.ts.map +1 -1
- package/node_modules/glob/dist/commonjs/glob.js +2 -1
- package/node_modules/glob/dist/commonjs/glob.js.map +1 -1
- package/node_modules/glob/dist/commonjs/index.min.js +3 -3
- package/node_modules/glob/dist/commonjs/index.min.js.map +4 -4
- package/node_modules/glob/dist/commonjs/pattern.d.ts +3 -0
- package/node_modules/glob/dist/commonjs/pattern.d.ts.map +1 -1
- package/node_modules/glob/dist/commonjs/pattern.js +4 -0
- package/node_modules/glob/dist/commonjs/pattern.js.map +1 -1
- package/node_modules/glob/dist/esm/glob.d.ts +8 -0
- package/node_modules/glob/dist/esm/glob.d.ts.map +1 -1
- package/node_modules/glob/dist/esm/glob.js +2 -1
- package/node_modules/glob/dist/esm/glob.js.map +1 -1
- package/node_modules/glob/dist/esm/index.min.js +3 -3
- package/node_modules/glob/dist/esm/index.min.js.map +4 -4
- package/node_modules/glob/dist/esm/pattern.d.ts +3 -0
- package/node_modules/glob/dist/esm/pattern.d.ts.map +1 -1
- package/node_modules/glob/dist/esm/pattern.js +4 -0
- package/node_modules/glob/dist/esm/pattern.js.map +1 -1
- package/node_modules/{@isaacs → glob/node_modules}/balanced-match/README.md +7 -10
- package/node_modules/{@isaacs → glob/node_modules}/balanced-match/package.json +7 -18
- package/node_modules/{@isaacs → glob/node_modules}/brace-expansion/README.md +3 -6
- package/node_modules/{@isaacs → glob/node_modules}/brace-expansion/dist/commonjs/index.js +6 -4
- package/node_modules/glob/node_modules/brace-expansion/dist/commonjs/index.js.map +1 -0
- package/node_modules/{@isaacs → glob/node_modules}/brace-expansion/dist/esm/index.js +6 -4
- package/node_modules/glob/node_modules/brace-expansion/dist/esm/index.js.map +1 -0
- package/node_modules/{@isaacs → glob/node_modules}/brace-expansion/package.json +11 -7
- package/node_modules/glob/node_modules/minimatch/README.md +76 -1
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/assert-valid-pattern.d.ts +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/assert-valid-pattern.d.ts.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/assert-valid-pattern.js.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/ast.d.ts +4 -2
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/ast.d.ts.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/ast.js +309 -55
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/ast.js.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/brace-expressions.d.ts.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/brace-expressions.js +2 -4
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/brace-expressions.js.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/escape.d.ts +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/escape.d.ts.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/escape.js +4 -4
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/escape.js.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/index.d.ts +81 -1
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/index.d.ts.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/index.js +232 -134
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/index.js.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/unescape.d.ts +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/unescape.d.ts.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/unescape.js +8 -8
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/unescape.js.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/esm/assert-valid-pattern.d.ts +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/esm/assert-valid-pattern.d.ts.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/esm/assert-valid-pattern.js.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/esm/ast.d.ts +4 -2
- package/node_modules/glob/node_modules/minimatch/dist/esm/ast.d.ts.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/esm/ast.js +309 -55
- package/node_modules/glob/node_modules/minimatch/dist/esm/ast.js.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/esm/brace-expressions.d.ts.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/esm/brace-expressions.js +2 -4
- package/node_modules/glob/node_modules/minimatch/dist/esm/brace-expressions.js.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/esm/escape.d.ts +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/esm/escape.d.ts.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/esm/escape.js +4 -4
- package/node_modules/glob/node_modules/minimatch/dist/esm/escape.js.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/esm/index.d.ts +81 -1
- package/node_modules/glob/node_modules/minimatch/dist/esm/index.d.ts.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/esm/index.js +232 -134
- package/node_modules/glob/node_modules/minimatch/dist/esm/index.js.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/esm/unescape.d.ts +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/esm/unescape.d.ts.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/esm/unescape.js +8 -8
- package/node_modules/glob/node_modules/minimatch/dist/esm/unescape.js.map +1 -1
- package/node_modules/glob/node_modules/minimatch/package.json +17 -11
- package/node_modules/glob/package.json +10 -13
- package/node_modules/minipass/LICENSE.md +55 -0
- package/node_modules/minipass/dist/commonjs/index.d.ts +12 -16
- package/node_modules/minipass/dist/commonjs/index.d.ts.map +1 -1
- package/node_modules/minipass/dist/commonjs/index.js +13 -3
- package/node_modules/minipass/dist/commonjs/index.js.map +1 -1
- package/node_modules/minipass/dist/esm/index.d.ts +12 -16
- package/node_modules/minipass/dist/esm/index.d.ts.map +1 -1
- package/node_modules/minipass/dist/esm/index.js +3 -1
- package/node_modules/minipass/dist/esm/index.js.map +1 -1
- package/node_modules/minipass/package.json +9 -14
- package/node_modules/path-scurry/node_modules/lru-cache/README.md +96 -10
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/diagnostics-channel-browser.d.ts.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/diagnostics-channel-browser.js.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/diagnostics-channel.d.ts +5 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/diagnostics-channel.js +7 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/index.d.ts +1400 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/index.d.ts.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/index.js +1726 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/index.js.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/index.min.js +2 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/index.min.js.map +7 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/perf.d.ts +12 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/perf.d.ts.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/perf.js +10 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/perf.js.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/diagnostics-channel-cjs.cjs.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/diagnostics-channel-cjs.d.cts.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/diagnostics-channel.d.ts +5 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/diagnostics-channel.js +7 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/index.d.ts +109 -32
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/index.d.ts.map +1 -1
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/index.js +334 -197
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/index.js.map +1 -1
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/index.min.js +1 -1
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/index.min.js.map +4 -4
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/diagnostics-channel-node.d.ts.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/diagnostics-channel-node.js.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/diagnostics-channel.d.ts +5 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/diagnostics-channel.js +9 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/index.d.ts +1400 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/index.d.ts.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/index.js +1726 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/index.js.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/index.min.js +2 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/index.min.js.map +7 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/perf.d.ts +12 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/perf.d.ts.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/perf.js +10 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/perf.js.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/perf.d.ts +12 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/perf.d.ts.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/perf.js +10 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/perf.js.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/diagnostics-channel-browser.d.ts.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/diagnostics-channel-browser.js.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/diagnostics-channel.d.ts +5 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/diagnostics-channel.js +4 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/index.d.ts +1400 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/index.d.ts.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/index.js +1722 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/index.js.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/index.min.js +2 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/index.min.js.map +7 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/perf.d.ts +12 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/perf.d.ts.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/perf.js +7 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/perf.js.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/diagnostics-channel-esm.d.mts.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/diagnostics-channel-esm.mjs.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/diagnostics-channel.d.ts +5 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/diagnostics-channel.js +19 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/index.d.ts +109 -32
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/index.d.ts.map +1 -1
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/index.js +333 -196
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/index.js.map +1 -1
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/index.min.js +1 -1
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/index.min.js.map +4 -4
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/diagnostics-channel-node.d.ts.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/diagnostics-channel-node.js.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/diagnostics-channel.d.ts +5 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/diagnostics-channel.js +6 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/index.d.ts +1400 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/index.d.ts.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/index.js +1722 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/index.js.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/index.min.js +2 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/index.min.js.map +7 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/perf.d.ts +12 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/perf.d.ts.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/perf.js +7 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/perf.js.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/perf.d.ts +12 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/perf.d.ts.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/perf.js +7 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/perf.js.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/package.json +71 -18
- package/node_modules/path-scurry/package.json +8 -24
- package/package.json +1 -1
- package/scripts/debug-balance-probe.mjs +35 -35
- package/scripts/push-image.sh +43 -43
- package/scripts/setup-acr.sh +65 -65
- package/scripts/verify-optional-deps.js +96 -1
- package/src/__tests__/composioCliFlags.test.js +239 -239
- package/src/analyzers/CSSAnalyzer.js +298 -297
- package/src/analyzers/ConfigValidator.js +691 -690
- package/src/analyzers/ESLintAnalyzer.js +320 -320
- package/src/analyzers/JavaScriptAnalyzer.js +260 -261
- package/src/analyzers/PrettierFormatter.js +246 -247
- package/src/analyzers/PythonAnalyzer.js +283 -283
- package/src/analyzers/SecurityAnalyzer.js +729 -729
- package/src/analyzers/SparrowAnalyzer.js +341 -341
- package/src/analyzers/TypeScriptAnalyzer.js +247 -247
- package/src/analyzers/__tests__/CSSAnalyzer.test.js +41 -41
- package/src/analyzers/__tests__/ConfigValidator.test.js +362 -362
- package/src/analyzers/__tests__/JavaScriptAnalyzer.test.js +40 -40
- package/src/analyzers/__tests__/PythonAnalyzer.test.js +205 -208
- package/src/analyzers/__tests__/SecurityAnalyzer.test.js +303 -303
- package/src/analyzers/__tests__/TypeScriptAnalyzer.test.js +187 -187
- package/src/analyzers/codeCloneDetector/analyzer.js +344 -344
- package/src/analyzers/codeCloneDetector/detector.js +250 -250
- package/src/analyzers/codeCloneDetector/index.js +194 -192
- package/src/analyzers/codeCloneDetector/parser.js +199 -199
- package/src/core/__tests__/agentPool.test.js +866 -866
- package/src/core/__tests__/agentPoolAutoResume.test.js +209 -209
- package/src/core/__tests__/agentPoolWakeOnMessage.test.js +315 -315
- package/src/core/__tests__/agentScheduler.emptyResponseChatStall.test.js +213 -213
- package/src/core/__tests__/agentScheduler.errorCategorisation.test.js +246 -246
- package/src/core/__tests__/agentScheduler.firstChunkTimeout.test.js +138 -138
- package/src/core/__tests__/agentScheduler.modeTransitions.test.js +233 -233
- package/src/core/__tests__/agentScheduler.nativePromptPick.test.js +319 -319
- package/src/core/__tests__/agentScheduler.taskLifecycleInstruction.test.js +78 -78
- package/src/core/__tests__/agentScheduler.visualizer.test.js +258 -258
- package/src/core/__tests__/flowCheckpointStore.test.js +140 -140
- package/src/core/__tests__/flowEndToEnd.test.js +565 -565
- package/src/core/__tests__/flowFieldMapping.test.js +188 -189
- package/src/core/__tests__/flowLintClientMirror.test.js +96 -98
- package/src/core/__tests__/flowSavePayload.test.js +170 -169
- package/src/core/__tests__/flowTemplates.test.js +311 -311
- package/src/core/__tests__/flowVersionStore.test.js +123 -123
- package/src/core/__tests__/messageProcessor.test.js +669 -669
- package/src/core/__tests__/stateManager.test.js +0 -1
- package/src/core/agentPool.js +2474 -2475
- package/src/core/agentScheduler.js +1 -4
- package/src/core/contextManager.js +708 -708
- package/src/core/flowExecutor.js +1510 -1510
- package/src/core/flowFieldMapping.js +136 -138
- package/src/core/messageProcessor.js +953 -954
- package/src/core/orchestrator.js +593 -595
- package/src/core/stateManager.js +1765 -1752
- package/src/index.js +1221 -1221
- package/src/interfaces/__tests__/archivedAgentDelete.test.js +207 -207
- package/src/interfaces/__tests__/bulkAgentRoute.test.js +361 -361
- package/src/interfaces/__tests__/imageServing.test.js +228 -228
- package/src/interfaces/__tests__/remoteSessionAuth.test.js +308 -308
- package/src/interfaces/__tests__/videoJobsRoutes.test.js +178 -179
- package/src/interfaces/__tests__/webServer.marketplace.test.js +629 -629
- package/src/interfaces/schedulerRoutes.js +50 -50
- package/src/interfaces/terminal/__tests__/smoke/connection.test.js +341 -350
- package/src/interfaces/terminal/__tests__/smoke/enhancements.test.js +156 -156
- package/src/interfaces/terminal/__tests__/smoke/imports.test.js +325 -330
- package/src/interfaces/terminal/__tests__/smoke/tools.test.js +385 -388
- package/src/interfaces/terminal/api/session.js +265 -266
- package/src/interfaces/terminal/api/websocket.js +496 -497
- package/src/interfaces/terminal/components/AgentCreator.js +691 -705
- package/src/interfaces/terminal/components/AgentEditor.js +676 -678
- package/src/interfaces/terminal/components/AgentSwitcher.js +331 -330
- package/src/interfaces/terminal/components/ErrorPanel.js +263 -264
- package/src/interfaces/terminal/components/Header.js +28 -28
- package/src/interfaces/terminal/components/Layout.js +598 -603
- package/src/interfaces/terminal/components/MessageList.js +280 -281
- package/src/interfaces/terminal/components/SettingsPanel.js +410 -415
- package/src/interfaces/terminal/components/StatusBar.js +2 -0
- package/src/interfaces/terminal/index.js +168 -168
- package/src/interfaces/terminal/state/useAgentControl.js +496 -496
- package/src/interfaces/terminal/state/useAgents.js +537 -537
- package/src/interfaces/terminal/state/useMessages.js +629 -630
- package/src/interfaces/terminal/state/useTools.js +554 -554
- package/src/interfaces/terminal/utils/debugLogger.js +44 -44
- package/src/interfaces/terminal/utils/settingsStorage.js +232 -232
- package/src/interfaces/webServer.js +7578 -7579
- package/src/interfaces/webServer.js.bak +7046 -7046
- package/src/modules/fileExplorer/__tests__/zipDownload.test.js +237 -237
- package/src/modules/fileExplorer/controller.js +470 -469
- package/src/modules/fileExplorer/routes.js +285 -286
- package/src/modules/widget/__tests__/isDisabled.test.js +41 -41
- package/src/modules/widget/__tests__/routes.test.js +677 -678
- package/src/modules/widget/__tests__/runtime.test.js +401 -401
- package/src/modules/widget/__tests__/versioning.test.js +309 -309
- package/src/modules/widget/__tests__/webComponentRuntime.test.js +565 -565
- package/src/modules/widget/__tests__/widgetTool.test.js +316 -316
- package/src/modules/widget/routes.js +435 -435
- package/src/modules/widget/runtime/bundle.js +640 -640
- package/src/modules/widget/runtime/webComponentBundle.js +470 -470
- package/src/modules/widget/schema.js +182 -181
- package/src/modules/widget/widgetTool.js +1389 -1389
- package/src/services/__tests__/agentActivityService.test.js +401 -402
- package/src/services/__tests__/benchmarkService.test.js +184 -184
- package/src/services/__tests__/contextInjectionService.test.js +246 -246
- package/src/services/__tests__/conversationQuery.test.js +721 -723
- package/src/services/__tests__/credentialVault.test.js +469 -469
- package/src/services/__tests__/discordService.integration.test.js +638 -639
- package/src/services/__tests__/flowContextService.test.js +590 -590
- package/src/services/__tests__/memoryService.test.js +1 -1
- package/src/services/__tests__/messageSource.test.js +380 -380
- package/src/services/__tests__/modelRouterNaming.test.js +111 -111
- package/src/services/__tests__/projectDetector.test.js +34 -34
- package/src/services/__tests__/promptService.test.js +242 -242
- package/src/services/__tests__/telegramService.test.js +941 -941
- package/src/services/__tests__/tokenCountingService.test.js +48 -48
- package/src/services/agentActivityService.js +419 -420
- package/src/services/aiService.js +2997 -3001
- package/src/services/apiKeyManager.js +359 -359
- package/src/services/benchmarkService.js +196 -196
- package/src/services/codebaseKnowledgeService.js +2 -2
- package/src/services/composioService.js +738 -738
- package/src/services/conversationCompactionService.js +1258 -1257
- package/src/services/credentialVault.js +685 -685
- package/src/services/discordService.js +792 -793
- package/src/services/embeddings/__tests__/azureCustomProvider.test.js +232 -232
- package/src/services/embeddings/__tests__/embeddingService.test.js +417 -417
- package/src/services/embeddings/__tests__/localProvider.test.js +263 -263
- package/src/services/embeddings/autoRecall.js +218 -219
- package/src/services/embeddings/indexers/__tests__/agentIndexer.test.js +232 -232
- package/src/services/embeddings/indexers/__tests__/memoryIndexer.test.js +418 -418
- package/src/services/embeddings/indexers/__tests__/reminisceIndexer.test.js +356 -357
- package/src/services/embeddings/indexers/__tests__/skillsIndexer.test.js +145 -145
- package/src/services/embeddings/indexers/__tests__/taskIndexer.test.js +146 -146
- package/src/services/embeddings/indexers/composioIndexer.js +279 -279
- package/src/services/embeddings/providerInterface.js +206 -206
- package/src/services/embeddings/providers/localProvider.js +11 -7
- package/src/services/embeddings/providers/openaiProvider.js +101 -101
- package/src/services/embeddings/vectorStore/inMemoryJsonStore.js +356 -356
- package/src/services/errorHandler.js +809 -809
- package/src/services/flowContextService.js +586 -586
- package/src/services/grounding/MockAdapter.js +125 -125
- package/src/services/modelRouterService.js +26 -31
- package/src/services/modelsService.js +322 -322
- package/src/services/ollamaService.js +452 -452
- package/src/services/projectDetector.js +403 -404
- package/src/services/promptService.js +418 -418
- package/src/services/qualityInspector.js +795 -795
- package/src/services/scheduleService.js +726 -726
- package/src/services/serviceRegistry.js +386 -386
- package/src/services/telegrafBot.js +174 -174
- package/src/services/telegramService.js +1972 -1972
- package/src/services/visualEditorBridge.js +1033 -1033
- package/src/services/visualEditorServer.js +1769 -1774
- package/src/services/whatsappService.js +667 -668
- package/src/tools/__tests__/agentCommunicationTool.findAgent.test.js +226 -226
- package/src/tools/__tests__/agentCommunicationTool.test.js +3 -3
- package/src/tools/__tests__/agentDelayTool.test.js +342 -342
- package/src/tools/__tests__/baseTool.test.js +3 -3
- package/src/tools/__tests__/codeMapTool.test.js +915 -915
- package/src/tools/__tests__/fileContentReplaceTool.test.js +309 -309
- package/src/tools/__tests__/fileTreeTool.test.js +274 -274
- package/src/tools/__tests__/filesystemTool.test.js +815 -815
- package/src/tools/__tests__/foundryWebSearchTool.test.js +252 -252
- package/src/tools/__tests__/imageTool.validator.test.js +194 -194
- package/src/tools/__tests__/jobDoneTool.test.js +580 -581
- package/src/tools/__tests__/memoryTool.forgetStale.test.js +272 -272
- package/src/tools/__tests__/memoryTool.reminisce.test.js +2 -2
- package/src/tools/__tests__/memoryTool.reminisceSemanticSearch.test.js +301 -301
- package/src/tools/__tests__/memoryTool.semanticSearch.test.js +405 -405
- package/src/tools/__tests__/memoryTool.teamPool.test.js +293 -293
- package/src/tools/__tests__/memoryTool.test.js +1 -1
- package/src/tools/__tests__/seekTool.test.js +282 -282
- package/src/tools/__tests__/skillsTool.search.test.js +164 -164
- package/src/tools/__tests__/skillsTool.test.js +226 -226
- package/src/tools/__tests__/staticAnalysisTool.test.js +509 -509
- package/src/tools/__tests__/taskManagerTool.discipline.test.js +137 -137
- package/src/tools/__tests__/taskManagerTool.search.test.js +143 -143
- package/src/tools/__tests__/taskManagerTool.test.js +866 -866
- package/src/tools/__tests__/terminalTool.test.js +448 -448
- package/src/tools/__tests__/toolShapeForgiveness.test.js +259 -260
- package/src/tools/__tests__/userPromptTool.test.js +297 -297
- package/src/tools/__tests__/videoTool.jobs.test.js +147 -147
- package/src/tools/__tests__/webTool.e2e.test.js +609 -603
- package/src/tools/__tests__/webTool.unit.test.js +195 -195
- package/src/tools/__tests__/webTool.visionModel.test.js +75 -75
- package/src/tools/agentCommunicationTool.js +8 -10
- package/src/tools/agentDelayTool.js +496 -497
- package/src/tools/asyncToolManager.js +602 -603
- package/src/tools/baseTool.js +12 -11
- package/src/tools/cloneDetectionTool.js +576 -581
- package/src/tools/codeMapTool.js +0 -6
- package/src/tools/composioTool.js +617 -617
- package/src/tools/dependencyResolverTool.js +1211 -1212
- package/src/tools/desktop/DesktopTool.js +629 -638
- package/src/tools/desktop/__tests__/DesktopTool.e2e.test.js +306 -306
- package/src/tools/desktop/__tests__/DesktopTool.test.js +507 -507
- package/src/tools/desktop/__tests__/osController.test.js +364 -364
- package/src/tools/desktop/osController.js +491 -491
- package/src/tools/docxTool.js +623 -623
- package/src/tools/excelTool.js +636 -636
- package/src/tools/fileContentReplaceTool.js +5 -7
- package/src/tools/fileSystemTool.js +12 -19
- package/src/tools/fileTreeTool.js +840 -840
- package/src/tools/foundryWebSearchTool.js +273 -273
- package/src/tools/helpTool.js +198 -198
- package/src/tools/imageTool.js +1397 -1397
- package/src/tools/importAnalyzerTool.js +1056 -1056
- package/src/tools/jobDoneTool.js +495 -495
- package/src/tools/memoryTool.js +1 -1
- package/src/tools/office/pres/__tests__/presSystem.test.js +365 -365
- package/src/tools/office/pres/archetypes/agenda.js +61 -61
- package/src/tools/office/pres/archetypes/bentoGrid.js +218 -219
- package/src/tools/office/pres/archetypes/bigStat.js +140 -142
- package/src/tools/office/pres/archetypes/closing.js +70 -70
- package/src/tools/office/pres/archetypes/hero.js +70 -70
- package/src/tools/office/pres/archetypes/productHero.js +93 -94
- package/src/tools/office/pres/archetypes/table.js +73 -74
- package/src/tools/office/pres/backgrounds/orb.js +66 -66
- package/src/tools/office/pres/components.js +422 -423
- package/src/tools/officeTool.js +441 -441
- package/src/tools/pdfTool.js +625 -627
- package/src/tools/platformControlTool.js +1081 -1081
- package/src/tools/seekTool.js +917 -918
- package/src/tools/skillsTool.js +1 -1
- package/src/tools/staticAnalysisTool.js +2143 -2146
- package/src/tools/taskManagerTool.js +3324 -3324
- package/src/tools/terminalTool.js +2615 -2618
- package/src/tools/videoTool.js +1303 -1303
- package/src/tools/visionTool.js +508 -508
- package/src/tools/visualEditorTool.js +1289 -1290
- package/src/tools/webTool.js +3368 -3368
- package/src/tools/whatsappTool.js +464 -464
- package/src/types/__tests__/agent.test.js +499 -499
- package/src/types/__tests__/contextReference.test.js +606 -606
- package/src/types/__tests__/conversation.test.js +555 -555
- package/src/types/__tests__/toolCommand.test.js +584 -584
- package/src/types/contextReference.js +974 -971
- package/src/types/conversation.js +729 -729
- package/src/types/toolCommand.js +746 -746
- package/src/utilities/__tests__/attachmentValidator.test.js +80 -80
- package/src/utilities/__tests__/auditReport.test.js +328 -328
- package/src/utilities/__tests__/directoryAccessManager.test.js +388 -388
- package/src/utilities/__tests__/jsonRepair.test.js +103 -104
- package/src/utilities/__tests__/modeTransitionReasons.test.js +105 -105
- package/src/utilities/__tests__/platformUtils.test.js +80 -87
- package/src/utilities/__tests__/structuredFileValidator.test.js +261 -263
- package/src/utilities/__tests__/toolConstants.test.js +92 -94
- package/src/utilities/__tests__/useIsTouchDevice.detect.test.js +114 -114
- package/src/utilities/__tests__/webUiUtilSync.test.js +117 -117
- package/src/utilities/attachmentValidator.js +284 -288
- package/src/utilities/authCache.js.backup-1779570472481 +121 -121
- package/src/utilities/browserStealth.js +631 -630
- package/src/utilities/configManager.js +616 -617
- package/src/utilities/directoryAccessManager.js +564 -565
- package/src/utilities/fileProcessor.js +308 -307
- package/src/utilities/humanBehavior.js +454 -453
- package/src/utilities/logger.js +479 -479
- package/src/utilities/structuredFileValidator.js +696 -699
- package/src/utilities/tagParser.js +5 -10
- package/src/utilities/userDataDir.js +308 -308
- package/node_modules/@isaacs/brace-expansion/dist/commonjs/index.js.map +0 -1
- package/node_modules/@isaacs/brace-expansion/dist/esm/index.js.map +0 -1
- package/node_modules/minipass/LICENSE +0 -15
- /package/node_modules/{@isaacs → glob/node_modules}/balanced-match/LICENSE.md +0 -0
- /package/node_modules/{@isaacs → glob/node_modules}/balanced-match/dist/commonjs/index.d.ts +0 -0
- /package/node_modules/{@isaacs → glob/node_modules}/balanced-match/dist/commonjs/index.d.ts.map +0 -0
- /package/node_modules/{@isaacs → glob/node_modules}/balanced-match/dist/commonjs/index.js +0 -0
- /package/node_modules/{@isaacs → glob/node_modules}/balanced-match/dist/commonjs/index.js.map +0 -0
- /package/node_modules/{@isaacs → glob/node_modules}/balanced-match/dist/commonjs/package.json +0 -0
- /package/node_modules/{@isaacs → glob/node_modules}/balanced-match/dist/esm/index.d.ts +0 -0
- /package/node_modules/{@isaacs → glob/node_modules}/balanced-match/dist/esm/index.d.ts.map +0 -0
- /package/node_modules/{@isaacs → glob/node_modules}/balanced-match/dist/esm/index.js +0 -0
- /package/node_modules/{@isaacs → glob/node_modules}/balanced-match/dist/esm/index.js.map +0 -0
- /package/node_modules/{@isaacs → glob/node_modules}/balanced-match/dist/esm/package.json +0 -0
- /package/node_modules/{@isaacs → glob/node_modules}/brace-expansion/LICENSE +0 -0
- /package/node_modules/{@isaacs → glob/node_modules}/brace-expansion/dist/commonjs/index.d.ts +0 -0
- /package/node_modules/{@isaacs → glob/node_modules}/brace-expansion/dist/commonjs/index.d.ts.map +0 -0
- /package/node_modules/{@isaacs → glob/node_modules}/brace-expansion/dist/commonjs/package.json +0 -0
- /package/node_modules/{@isaacs → glob/node_modules}/brace-expansion/dist/esm/index.d.ts +0 -0
- /package/node_modules/{@isaacs → glob/node_modules}/brace-expansion/dist/esm/index.d.ts.map +0 -0
- /package/node_modules/{@isaacs → glob/node_modules}/brace-expansion/dist/esm/package.json +0 -0
|
@@ -1,491 +1,491 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* OS-level desktop primitives — screenshot, mouse, keyboard, windows.
|
|
3
|
-
*
|
|
4
|
-
* Wraps `@nut-tree-fork/nut-js` (cross-platform input + screen capture).
|
|
5
|
-
* Two design choices worth knowing about:
|
|
6
|
-
*
|
|
7
|
-
* 1. LAZY NATIVE LOADING. `nut.js` is an OPTIONAL dependency (~30MB
|
|
8
|
-
* native binary, needs build tools per OS). It's loaded on first
|
|
9
|
-
* use via dynamic `import()`. Headless server CLI installs that
|
|
10
|
-
* never call DesktopTool never pay the cost. Build failures on
|
|
11
|
-
* Wayland / minimal containers don't break the rest of Loxia —
|
|
12
|
-
* the controller surfaces a clear `code: 'NATIVE_UNAVAILABLE'`
|
|
13
|
-
* error and the agent falls back gracefully.
|
|
14
|
-
*
|
|
15
|
-
* 2. CAPABILITY-AWARE. Every method consults osCapabilities and
|
|
16
|
-
* refuses operations the OS won't support (e.g. mouseInput on
|
|
17
|
-
* Wayland). Capabilities are probed once at boot.
|
|
18
|
-
*
|
|
19
|
-
* Coordinates are CSS-style pixels, top-left origin. The controller
|
|
20
|
-
* normalises across DPI / Retina via nut.js's built-in scaling. The
|
|
21
|
-
* caller (DesktopTool / model) never sees raw device pixels.
|
|
22
|
-
*/
|
|
23
|
-
|
|
24
|
-
import { detectOSCapabilities
|
|
25
|
-
import { makeError } from '../../services/grounding/GroundingModel.js';
|
|
26
|
-
|
|
27
|
-
// ─── stable error codes ─────────────────────────────────────────────
|
|
28
|
-
|
|
29
|
-
export const OSError = Object.freeze({
|
|
30
|
-
/** nut.js failed to load (missing dep, build error, etc.). */
|
|
31
|
-
NATIVE_UNAVAILABLE: 'OS_NATIVE_UNAVAILABLE',
|
|
32
|
-
/** The current OS / display server doesn't support this op. */
|
|
33
|
-
UNSUPPORTED: 'OS_UNSUPPORTED',
|
|
34
|
-
/** Coords / args are outside the screen or otherwise invalid. */
|
|
35
|
-
INVALID_INPUT: 'OS_INVALID_INPUT',
|
|
36
|
-
/** Underlying op (nut.js call) threw. */
|
|
37
|
-
OP_FAILED: 'OS_OP_FAILED',
|
|
38
|
-
/** Permission missing (macOS Accessibility / Screen Recording). */
|
|
39
|
-
PERMISSION_DENIED: 'OS_PERMISSION_DENIED',
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
// ─── tunable defaults ───────────────────────────────────────────────
|
|
43
|
-
|
|
44
|
-
const Defaults = Object.freeze({
|
|
45
|
-
/** Per-keystroke delay when typing — keeps under most app input caps. */
|
|
46
|
-
TYPE_DELAY_MS: 20,
|
|
47
|
-
/** Inter-click delay for double-clicks. */
|
|
48
|
-
DOUBLE_CLICK_GAP_MS: 80,
|
|
49
|
-
/** Drag step duration; quick enough to feel snappy. */
|
|
50
|
-
DRAG_DURATION_MS: 200,
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
/** Mouse button names mapped to nut.js Button enum keys. */
|
|
54
|
-
const MouseButton = Object.freeze({
|
|
55
|
-
LEFT: 'LEFT',
|
|
56
|
-
RIGHT: 'RIGHT',
|
|
57
|
-
MIDDLE: 'MIDDLE',
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Cross-platform key-name aliases → nut.js Key enum names. Maps the
|
|
62
|
-
* names agents naturally try (Meta, Command, Cmd, Windows, Win, Super,
|
|
63
|
-
* Ctrl, Esc, Return, etc.) onto the actual nut.js names. Lookups in
|
|
64
|
-
* keyPress check this map BEFORE the strict Key[name] lookup, so
|
|
65
|
-
* "Meta" works on Windows (→ LeftWin), "Command" works on macOS
|
|
66
|
-
* (→ LeftCmd), and friends-of-friends spellings resolve too.
|
|
67
|
-
*
|
|
68
|
-
* Keep this list curated — adding every conceivable spelling makes
|
|
69
|
-
* debugging "why did this map" harder. Only add aliases an agent
|
|
70
|
-
* actually emits in practice.
|
|
71
|
-
*/
|
|
72
|
-
const KEY_ALIASES = Object.freeze({
|
|
73
|
-
// OS / Modifier keys
|
|
74
|
-
Meta: 'LeftWin', meta: 'LeftWin',
|
|
75
|
-
Command: 'LeftCmd', command: 'LeftCmd',
|
|
76
|
-
Cmd: 'LeftCmd', cmd: 'LeftCmd',
|
|
77
|
-
Windows: 'LeftWin', windows: 'LeftWin',
|
|
78
|
-
Win: 'LeftWin', win: 'LeftWin',
|
|
79
|
-
Super: 'LeftSuper', super: 'LeftSuper',
|
|
80
|
-
Ctrl: 'LeftControl', ctrl: 'LeftControl', Control: 'LeftControl', control: 'LeftControl',
|
|
81
|
-
Alt: 'LeftAlt', alt: 'LeftAlt',
|
|
82
|
-
Shift: 'LeftShift', shift: 'LeftShift',
|
|
83
|
-
// Common punctuation / whitespace aliases
|
|
84
|
-
Esc: 'Escape', esc: 'Escape',
|
|
85
|
-
Return: 'Enter', return: 'Enter',
|
|
86
|
-
Del: 'Delete', del: 'Delete',
|
|
87
|
-
Ins: 'Insert', ins: 'Insert',
|
|
88
|
-
PgUp: 'PageUp', pgup: 'PageUp',
|
|
89
|
-
PgDown: 'PageDown', pgdown: 'PageDown',
|
|
90
|
-
Space: 'Space', space: 'Space',
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Construct + return a controller. Caller passes a capabilities
|
|
95
|
-
* snapshot (or null → we'll detect). Lazy-import of nut.js happens
|
|
96
|
-
* inside; constructor is sync.
|
|
97
|
-
*
|
|
98
|
-
* @param {Object} [opts]
|
|
99
|
-
* @param {import('./osCapabilities.js').OSCapabilities} [opts.capabilities]
|
|
100
|
-
* @param {Object} [opts.nutImpl] - injected for tests; defaults to lazy import
|
|
101
|
-
* @returns {OSController}
|
|
102
|
-
*/
|
|
103
|
-
export function createOSController(opts = {}) {
|
|
104
|
-
return new OSController(opts);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
export class OSController {
|
|
108
|
-
constructor({ capabilities = null, nutImpl = null } = {}) {
|
|
109
|
-
this._caps = capabilities;
|
|
110
|
-
this._nutImpl = nutImpl;
|
|
111
|
-
this._loadPromise = null;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// ─── lifecycle ────────────────────────────────────────────────────
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Resolves capabilities + lazy-loads nut.js. Idempotent — safe to
|
|
118
|
-
* call before each op without re-incurring the load cost.
|
|
119
|
-
*/
|
|
120
|
-
async _ensureReady() {
|
|
121
|
-
if (!this._caps) this._caps = await detectOSCapabilities();
|
|
122
|
-
if (this._nutImpl) return;
|
|
123
|
-
if (!this._loadPromise) this._loadPromise = this._loadNative();
|
|
124
|
-
await this._loadPromise;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
async _loadNative() {
|
|
128
|
-
try {
|
|
129
|
-
// Dynamic import keeps this an OPTIONAL dependency. Wrapped in a
|
|
130
|
-
// try so a missing-or-broken nut.js doesn't crash the whole CLI.
|
|
131
|
-
this._nutImpl = await import('@nut-tree-fork/nut-js');
|
|
132
|
-
} catch (err) {
|
|
133
|
-
throw makeError(OSError.NATIVE_UNAVAILABLE,
|
|
134
|
-
'DesktopTool requires "@nut-tree-fork/nut-js" — install it (it is in optionalDependencies) ' +
|
|
135
|
-
'or check the platform build logs. Original error: ' + err.message);
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
/**
|
|
140
|
-
* Returns a frozen snapshot of OS capabilities + load status.
|
|
141
|
-
* Useful for the admin UI's "OS capability status" panel.
|
|
142
|
-
*/
|
|
143
|
-
async describeCapabilities() {
|
|
144
|
-
await this._ensureReady().catch(() => {/* still report caps */});
|
|
145
|
-
return {
|
|
146
|
-
...this._caps,
|
|
147
|
-
nativeLoaded: this._nutImpl != null,
|
|
148
|
-
};
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// ─── primitives ───────────────────────────────────────────────────
|
|
152
|
-
|
|
153
|
-
/**
|
|
154
|
-
* Capture the screen (or a region/monitor) as PNG bytes.
|
|
155
|
-
* @param {Object} [opts]
|
|
156
|
-
* @param {number} [opts.monitor] - 0-indexed monitor; default 0
|
|
157
|
-
* @param {{x:number,y:number,width:number,height:number}} [opts.region]
|
|
158
|
-
* @returns {Promise<Buffer>}
|
|
159
|
-
*/
|
|
160
|
-
async screenshot(opts = {}) {
|
|
161
|
-
await this._ensureReady();
|
|
162
|
-
this._requireCapability('screenshot');
|
|
163
|
-
try {
|
|
164
|
-
const { screen, Region } = this._nutImpl;
|
|
165
|
-
if (opts.region) {
|
|
166
|
-
validateRegion(opts.region);
|
|
167
|
-
const r = new Region(opts.region.x, opts.region.y, opts.region.width, opts.region.height);
|
|
168
|
-
return await screen.grabRegion(r).then(toPngBuffer);
|
|
169
|
-
}
|
|
170
|
-
return await screen.grab().then(toPngBuffer);
|
|
171
|
-
} catch (err) {
|
|
172
|
-
throw wrapOpError('screenshot', err);
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
/** Returns the primary screen size in CSS pixels + DPI ratio. */
|
|
177
|
-
async screenSize() {
|
|
178
|
-
await this._ensureReady();
|
|
179
|
-
try {
|
|
180
|
-
const { screen } = this._nutImpl;
|
|
181
|
-
const width = await screen.width();
|
|
182
|
-
const height = await screen.height();
|
|
183
|
-
return { width, height, dpr: 1 }; // nut.js normalizes to CSS px
|
|
184
|
-
} catch (err) {
|
|
185
|
-
throw wrapOpError('screenSize', err);
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
/**
|
|
190
|
-
* Move the mouse cursor to (x, y).
|
|
191
|
-
* @param {number} x
|
|
192
|
-
* @param {number} y
|
|
193
|
-
*/
|
|
194
|
-
async mouseMove(x, y) {
|
|
195
|
-
await this._ensureReady();
|
|
196
|
-
this._requireCapability('mouseInput');
|
|
197
|
-
validateCoord(x, y);
|
|
198
|
-
try {
|
|
199
|
-
const { mouse, Point } = this._nutImpl;
|
|
200
|
-
await mouse.move([new Point(x, y)]);
|
|
201
|
-
} catch (err) {
|
|
202
|
-
throw wrapOpError('mouseMove', err);
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
/**
|
|
207
|
-
* Click at (x, y). Move-then-click; single op so DesktopTool's
|
|
208
|
-
* mutex only needs to lock once per logical click.
|
|
209
|
-
*
|
|
210
|
-
* @param {number} x
|
|
211
|
-
* @param {number} y
|
|
212
|
-
* @param {Object} [opts]
|
|
213
|
-
* @param {'left'|'right'|'middle'} [opts.button='left']
|
|
214
|
-
* @param {1|2} [opts.count=1]
|
|
215
|
-
*/
|
|
216
|
-
async mouseClick(x, y, opts = {}) {
|
|
217
|
-
await this._ensureReady();
|
|
218
|
-
this._requireCapability('mouseInput');
|
|
219
|
-
validateCoord(x, y);
|
|
220
|
-
const button = (opts.button || 'left').toUpperCase();
|
|
221
|
-
if (!MouseButton[button]) {
|
|
222
|
-
throw makeError(OSError.INVALID_INPUT, `unknown button "${opts.button}"`);
|
|
223
|
-
}
|
|
224
|
-
const count = opts.count === 2 ? 2 : 1;
|
|
225
|
-
try {
|
|
226
|
-
const { mouse, Point, Button } = this._nutImpl;
|
|
227
|
-
await mouse.move([new Point(x, y)]);
|
|
228
|
-
const btn = Button[button];
|
|
229
|
-
if (count === 2) {
|
|
230
|
-
await mouse.doubleClick(btn);
|
|
231
|
-
} else {
|
|
232
|
-
await mouse.click(btn);
|
|
233
|
-
}
|
|
234
|
-
} catch (err) {
|
|
235
|
-
throw wrapOpError('mouseClick', err);
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
/**
|
|
240
|
-
* Scroll at (x, y).
|
|
241
|
-
* @param {number} x
|
|
242
|
-
* @param {number} y
|
|
243
|
-
* @param {number} dy - positive = scroll down
|
|
244
|
-
*/
|
|
245
|
-
async mouseScroll(x, y, dy) {
|
|
246
|
-
await this._ensureReady();
|
|
247
|
-
this._requireCapability('mouseInput');
|
|
248
|
-
validateCoord(x, y);
|
|
249
|
-
if (!Number.isFinite(dy)) throw makeError(OSError.INVALID_INPUT, 'dy must be a number');
|
|
250
|
-
try {
|
|
251
|
-
const { mouse, Point } = this._nutImpl;
|
|
252
|
-
await mouse.move([new Point(x, y)]);
|
|
253
|
-
if (dy > 0) await mouse.scrollDown(Math.abs(dy));
|
|
254
|
-
else if (dy < 0) await mouse.scrollUp(Math.abs(dy));
|
|
255
|
-
} catch (err) {
|
|
256
|
-
throw wrapOpError('mouseScroll', err);
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
/**
|
|
261
|
-
* Drag from one point to another.
|
|
262
|
-
* @param {{x:number,y:number}} from
|
|
263
|
-
* @param {{x:number,y:number}} to
|
|
264
|
-
* @param {Object} [opts]
|
|
265
|
-
* @param {'left'|'right'|'middle'} [opts.button='left']
|
|
266
|
-
*/
|
|
267
|
-
async mouseDrag(from, to, opts = {}) {
|
|
268
|
-
await this._ensureReady();
|
|
269
|
-
this._requireCapability('mouseInput');
|
|
270
|
-
validateCoord(from.x, from.y);
|
|
271
|
-
validateCoord(to.x, to.y);
|
|
272
|
-
const button = (opts.button || 'left').toUpperCase();
|
|
273
|
-
if (!MouseButton[button]) {
|
|
274
|
-
throw makeError(OSError.INVALID_INPUT, `unknown button "${opts.button}"`);
|
|
275
|
-
}
|
|
276
|
-
try {
|
|
277
|
-
const { mouse, Point, Button } = this._nutImpl;
|
|
278
|
-
await mouse.move([new Point(from.x, from.y)]);
|
|
279
|
-
await mouse.pressButton(Button[button]);
|
|
280
|
-
await mouse.move([new Point(to.x, to.y)]);
|
|
281
|
-
await mouse.releaseButton(Button[button]);
|
|
282
|
-
} catch (err) {
|
|
283
|
-
throw wrapOpError('mouseDrag', err);
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
/**
|
|
288
|
-
* Type a literal string into the focused app.
|
|
289
|
-
* @param {string} text
|
|
290
|
-
* @param {Object} [opts]
|
|
291
|
-
* @param {number} [opts.delayMs=Defaults.TYPE_DELAY_MS]
|
|
292
|
-
*/
|
|
293
|
-
async typeText(text, opts = {}) {
|
|
294
|
-
await this._ensureReady();
|
|
295
|
-
this._requireCapability('keyboardInput');
|
|
296
|
-
if (typeof text !== 'string' || text.length === 0) {
|
|
297
|
-
throw makeError(OSError.INVALID_INPUT, 'text must be a non-empty string');
|
|
298
|
-
}
|
|
299
|
-
const delayMs = Number.isFinite(opts.delayMs) ? opts.delayMs : Defaults.TYPE_DELAY_MS;
|
|
300
|
-
try {
|
|
301
|
-
const { keyboard } = this._nutImpl;
|
|
302
|
-
keyboard.config.autoDelayMs = delayMs;
|
|
303
|
-
await keyboard.type(text);
|
|
304
|
-
} catch (err) {
|
|
305
|
-
throw wrapOpError('typeText', err);
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
/**
|
|
310
|
-
* Send a chord (e.g. Ctrl+S, Alt+Tab). Keys are nut.js-style names
|
|
311
|
-
* (Control, Shift, S, F5, Enter, etc.).
|
|
312
|
-
* @param {string[]} keys
|
|
313
|
-
*/
|
|
314
|
-
async keyPress(keys) {
|
|
315
|
-
await this._ensureReady();
|
|
316
|
-
this._requireCapability('keyboardInput');
|
|
317
|
-
if (!Array.isArray(keys) || keys.length === 0) {
|
|
318
|
-
throw makeError(OSError.INVALID_INPUT, 'keys must be a non-empty array');
|
|
319
|
-
}
|
|
320
|
-
try {
|
|
321
|
-
const { keyboard, Key } = this._nutImpl;
|
|
322
|
-
const resolved = keys.map(k => {
|
|
323
|
-
// 1. Cross-platform aliases — agents naturally try "Meta",
|
|
324
|
-
// "Command", "Cmd", "Super", "Windows", "Win". Map those to
|
|
325
|
-
// nut.js's actual key names (LeftWin / LeftCmd) before
|
|
326
|
-
// looking up. Without this, a perfectly reasonable
|
|
327
|
-
// "press Windows key" call dies with OS_INVALID_INPUT.
|
|
328
|
-
const normalized = KEY_ALIASES[k] ?? KEY_ALIASES[k.toLowerCase()] ?? k;
|
|
329
|
-
const upper = normalized.length === 1 ? normalized.toUpperCase() : normalized;
|
|
330
|
-
if (Key[upper] === undefined) {
|
|
331
|
-
// Best-effort hint: include 4 nearby key names from the enum
|
|
332
|
-
// so the agent can self-correct on the next attempt.
|
|
333
|
-
const nearby = Object.keys(Key)
|
|
334
|
-
.filter(n => n.toLowerCase().includes(upper.toLowerCase()) || upper.toLowerCase().includes(n.toLowerCase()))
|
|
335
|
-
.slice(0, 4);
|
|
336
|
-
const hint = nearby.length ? ` (did you mean: ${nearby.join(', ')}?)` : '';
|
|
337
|
-
throw makeError(OSError.INVALID_INPUT, `unknown key "${k}"${hint}`);
|
|
338
|
-
}
|
|
339
|
-
return Key[upper];
|
|
340
|
-
});
|
|
341
|
-
// pressKey takes the chord as varargs; nut.js holds modifiers down,
|
|
342
|
-
// presses the last key, releases all.
|
|
343
|
-
await keyboard.pressKey(...resolved);
|
|
344
|
-
await keyboard.releaseKey(...resolved);
|
|
345
|
-
} catch (err) {
|
|
346
|
-
throw wrapOpError('keyPress', err);
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
// ─── window management (best-effort, OS-dependent) ────────────────
|
|
351
|
-
|
|
352
|
-
/**
|
|
353
|
-
* List currently-open windows.
|
|
354
|
-
* @returns {Promise<Array<{title:string, pid?:number, bounds?:object}>>}
|
|
355
|
-
*/
|
|
356
|
-
async listWindows() {
|
|
357
|
-
await this._ensureReady();
|
|
358
|
-
this._requireCapability('windowEnumeration');
|
|
359
|
-
try {
|
|
360
|
-
const { getWindows } = this._nutImpl;
|
|
361
|
-
const winRefs = await getWindows();
|
|
362
|
-
return Promise.all(winRefs.map(async w => ({
|
|
363
|
-
title: await w.getTitle(),
|
|
364
|
-
// bounds + pid are best-effort; nut.js varies by OS.
|
|
365
|
-
bounds: await safeCall(() => w.getRegion()),
|
|
366
|
-
})));
|
|
367
|
-
} catch (err) {
|
|
368
|
-
throw wrapOpError('listWindows', err);
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
/**
|
|
373
|
-
* Focus a window matched by title regex or substring.
|
|
374
|
-
* @param {Object} criteria
|
|
375
|
-
* @param {RegExp|string} [criteria.titleMatch]
|
|
376
|
-
*/
|
|
377
|
-
async focusWindow(criteria) {
|
|
378
|
-
await this._ensureReady();
|
|
379
|
-
this._requireCapability('windowFocus');
|
|
380
|
-
if (!criteria || !criteria.titleMatch) {
|
|
381
|
-
throw makeError(OSError.INVALID_INPUT, 'criteria.titleMatch is required');
|
|
382
|
-
}
|
|
383
|
-
try {
|
|
384
|
-
const { getWindows } = this._nutImpl;
|
|
385
|
-
const wins = await getWindows();
|
|
386
|
-
for (const w of wins) {
|
|
387
|
-
const title = await w.getTitle();
|
|
388
|
-
if (matchesCriteria(title, criteria.titleMatch)) {
|
|
389
|
-
await w.focus();
|
|
390
|
-
return { focused: true, title };
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
return { focused: false, title: null };
|
|
394
|
-
} catch (err) {
|
|
395
|
-
throw wrapOpError('focusWindow', err);
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
// ─── internal ─────────────────────────────────────────────────────
|
|
400
|
-
|
|
401
|
-
/**
|
|
402
|
-
* Refuse the op if the OS doesn't support it (e.g. Wayland mouseInput).
|
|
403
|
-
* Provides the operator-facing remediation string from osCapabilities.
|
|
404
|
-
*/
|
|
405
|
-
_requireCapability(name) {
|
|
406
|
-
const ok = this._caps?.[name];
|
|
407
|
-
if (!ok) {
|
|
408
|
-
throw makeError(OSError.UNSUPPORTED,
|
|
409
|
-
`${name} not available on this OS/session. ` +
|
|
410
|
-
(this._caps?.degradedReason || 'Run on a supported configuration.'));
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
// ─── helpers (module-private) ───────────────────────────────────────
|
|
416
|
-
|
|
417
|
-
function validateCoord(x, y) {
|
|
418
|
-
if (!Number.isFinite(x) || !Number.isFinite(y) || x < 0 || y < 0) {
|
|
419
|
-
throw makeError(OSError.INVALID_INPUT,
|
|
420
|
-
`coordinates must be non-negative finite numbers; got (${x}, ${y})`);
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
function validateRegion(r) {
|
|
425
|
-
if (!r || !Number.isFinite(r.x) || !Number.isFinite(r.y) ||
|
|
426
|
-
!Number.isFinite(r.width) || !Number.isFinite(r.height) ||
|
|
427
|
-
r.x < 0 || r.y < 0 || r.width < 1 || r.height < 1) {
|
|
428
|
-
throw makeError(OSError.INVALID_INPUT, `invalid region ${JSON.stringify(r)}`);
|
|
429
|
-
}
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
function matchesCriteria(title, m) {
|
|
433
|
-
if (m instanceof RegExp) return m.test(title);
|
|
434
|
-
if (typeof m === 'string') return title.toLowerCase().includes(m.toLowerCase());
|
|
435
|
-
return false;
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
async function safeCall(fn) {
|
|
439
|
-
try { return await fn(); }
|
|
440
|
-
catch { return null; }
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
async function toPngBuffer(nutImage) {
|
|
444
|
-
// Forward-compat fast paths if a future nut.js ships a PNG encoder.
|
|
445
|
-
if (typeof nutImage.toPng === 'function') return nutImage.toPng();
|
|
446
|
-
if (typeof nutImage.toBuffer === 'function') return nutImage.toBuffer();
|
|
447
|
-
|
|
448
|
-
// Current shape (nut-tree-fork@4.x): Image exposes raw pixel data in
|
|
449
|
-
// `nutImage.data` with `channels` (3 or 4), `colorMode` (0=BGR,1=RGB
|
|
450
|
-
// per nut.js convention), and dimensions. We encode to PNG with sharp
|
|
451
|
-
// which is already a dep (bench harness uses it).
|
|
452
|
-
if (nutImage.data && nutImage.width && nutImage.height) {
|
|
453
|
-
const sharp = (await import('sharp')).default;
|
|
454
|
-
const { width, height } = nutImage;
|
|
455
|
-
const channels = nutImage.channels || 4;
|
|
456
|
-
// nut.js's pixel buffer is BGR(A) on Windows by default; sharp wants
|
|
457
|
-
// RGB(A). Swap red/blue if colorMode signals BGR. Channel-4 with
|
|
458
|
-
// alpha is preserved.
|
|
459
|
-
const buf = Buffer.from(nutImage.data);
|
|
460
|
-
const isBgr = nutImage.colorMode === 0;
|
|
461
|
-
if (isBgr) swapRedBlueInPlace(buf, channels);
|
|
462
|
-
return sharp(buf, { raw: { width, height, channels } }).png().toBuffer();
|
|
463
|
-
}
|
|
464
|
-
throw makeError(OSError.OP_FAILED, 'nut.js Image has no recognisable pixel buffer');
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
function swapRedBlueInPlace(buf, channels) {
|
|
468
|
-
for (let i = 0; i < buf.length; i += channels) {
|
|
469
|
-
const r = buf[i];
|
|
470
|
-
buf[i] = buf[i + 2];
|
|
471
|
-
buf[i + 2] = r;
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
function wrapOpError(opName, err) {
|
|
476
|
-
// Detect permission-denied flavours from OS error messages.
|
|
477
|
-
const msg = (err.message || String(err)).toLowerCase();
|
|
478
|
-
if (msg.includes('not authorized') || msg.includes('permission')) {
|
|
479
|
-
return makeError(OSError.PERMISSION_DENIED,
|
|
480
|
-
`${opName} failed: ${err.message}. Check OS permission grants.`);
|
|
481
|
-
}
|
|
482
|
-
// Already-typed errors (validation, unsupported, native-missing) pass through;
|
|
483
|
-
// we only wrap raw native errors.
|
|
484
|
-
if (err.code === OSError.NATIVE_UNAVAILABLE ||
|
|
485
|
-
err.code === OSError.UNSUPPORTED ||
|
|
486
|
-
err.code === OSError.INVALID_INPUT ||
|
|
487
|
-
err.code === OSError.PERMISSION_DENIED) {
|
|
488
|
-
return err;
|
|
489
|
-
}
|
|
490
|
-
return makeError(OSError.OP_FAILED, `${opName} failed: ${err.message}`, { cause: err });
|
|
491
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* OS-level desktop primitives — screenshot, mouse, keyboard, windows.
|
|
3
|
+
*
|
|
4
|
+
* Wraps `@nut-tree-fork/nut-js` (cross-platform input + screen capture).
|
|
5
|
+
* Two design choices worth knowing about:
|
|
6
|
+
*
|
|
7
|
+
* 1. LAZY NATIVE LOADING. `nut.js` is an OPTIONAL dependency (~30MB
|
|
8
|
+
* native binary, needs build tools per OS). It's loaded on first
|
|
9
|
+
* use via dynamic `import()`. Headless server CLI installs that
|
|
10
|
+
* never call DesktopTool never pay the cost. Build failures on
|
|
11
|
+
* Wayland / minimal containers don't break the rest of Loxia —
|
|
12
|
+
* the controller surfaces a clear `code: 'NATIVE_UNAVAILABLE'`
|
|
13
|
+
* error and the agent falls back gracefully.
|
|
14
|
+
*
|
|
15
|
+
* 2. CAPABILITY-AWARE. Every method consults osCapabilities and
|
|
16
|
+
* refuses operations the OS won't support (e.g. mouseInput on
|
|
17
|
+
* Wayland). Capabilities are probed once at boot.
|
|
18
|
+
*
|
|
19
|
+
* Coordinates are CSS-style pixels, top-left origin. The controller
|
|
20
|
+
* normalises across DPI / Retina via nut.js's built-in scaling. The
|
|
21
|
+
* caller (DesktopTool / model) never sees raw device pixels.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import { detectOSCapabilities } from './osCapabilities.js';
|
|
25
|
+
import { makeError } from '../../services/grounding/GroundingModel.js';
|
|
26
|
+
|
|
27
|
+
// ─── stable error codes ─────────────────────────────────────────────
|
|
28
|
+
|
|
29
|
+
export const OSError = Object.freeze({
|
|
30
|
+
/** nut.js failed to load (missing dep, build error, etc.). */
|
|
31
|
+
NATIVE_UNAVAILABLE: 'OS_NATIVE_UNAVAILABLE',
|
|
32
|
+
/** The current OS / display server doesn't support this op. */
|
|
33
|
+
UNSUPPORTED: 'OS_UNSUPPORTED',
|
|
34
|
+
/** Coords / args are outside the screen or otherwise invalid. */
|
|
35
|
+
INVALID_INPUT: 'OS_INVALID_INPUT',
|
|
36
|
+
/** Underlying op (nut.js call) threw. */
|
|
37
|
+
OP_FAILED: 'OS_OP_FAILED',
|
|
38
|
+
/** Permission missing (macOS Accessibility / Screen Recording). */
|
|
39
|
+
PERMISSION_DENIED: 'OS_PERMISSION_DENIED',
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// ─── tunable defaults ───────────────────────────────────────────────
|
|
43
|
+
|
|
44
|
+
const Defaults = Object.freeze({
|
|
45
|
+
/** Per-keystroke delay when typing — keeps under most app input caps. */
|
|
46
|
+
TYPE_DELAY_MS: 20,
|
|
47
|
+
/** Inter-click delay for double-clicks. */
|
|
48
|
+
DOUBLE_CLICK_GAP_MS: 80,
|
|
49
|
+
/** Drag step duration; quick enough to feel snappy. */
|
|
50
|
+
DRAG_DURATION_MS: 200,
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
/** Mouse button names mapped to nut.js Button enum keys. */
|
|
54
|
+
const MouseButton = Object.freeze({
|
|
55
|
+
LEFT: 'LEFT',
|
|
56
|
+
RIGHT: 'RIGHT',
|
|
57
|
+
MIDDLE: 'MIDDLE',
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Cross-platform key-name aliases → nut.js Key enum names. Maps the
|
|
62
|
+
* names agents naturally try (Meta, Command, Cmd, Windows, Win, Super,
|
|
63
|
+
* Ctrl, Esc, Return, etc.) onto the actual nut.js names. Lookups in
|
|
64
|
+
* keyPress check this map BEFORE the strict Key[name] lookup, so
|
|
65
|
+
* "Meta" works on Windows (→ LeftWin), "Command" works on macOS
|
|
66
|
+
* (→ LeftCmd), and friends-of-friends spellings resolve too.
|
|
67
|
+
*
|
|
68
|
+
* Keep this list curated — adding every conceivable spelling makes
|
|
69
|
+
* debugging "why did this map" harder. Only add aliases an agent
|
|
70
|
+
* actually emits in practice.
|
|
71
|
+
*/
|
|
72
|
+
const KEY_ALIASES = Object.freeze({
|
|
73
|
+
// OS / Modifier keys
|
|
74
|
+
Meta: 'LeftWin', meta: 'LeftWin',
|
|
75
|
+
Command: 'LeftCmd', command: 'LeftCmd',
|
|
76
|
+
Cmd: 'LeftCmd', cmd: 'LeftCmd',
|
|
77
|
+
Windows: 'LeftWin', windows: 'LeftWin',
|
|
78
|
+
Win: 'LeftWin', win: 'LeftWin',
|
|
79
|
+
Super: 'LeftSuper', super: 'LeftSuper',
|
|
80
|
+
Ctrl: 'LeftControl', ctrl: 'LeftControl', Control: 'LeftControl', control: 'LeftControl',
|
|
81
|
+
Alt: 'LeftAlt', alt: 'LeftAlt',
|
|
82
|
+
Shift: 'LeftShift', shift: 'LeftShift',
|
|
83
|
+
// Common punctuation / whitespace aliases
|
|
84
|
+
Esc: 'Escape', esc: 'Escape',
|
|
85
|
+
Return: 'Enter', return: 'Enter',
|
|
86
|
+
Del: 'Delete', del: 'Delete',
|
|
87
|
+
Ins: 'Insert', ins: 'Insert',
|
|
88
|
+
PgUp: 'PageUp', pgup: 'PageUp',
|
|
89
|
+
PgDown: 'PageDown', pgdown: 'PageDown',
|
|
90
|
+
Space: 'Space', space: 'Space',
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Construct + return a controller. Caller passes a capabilities
|
|
95
|
+
* snapshot (or null → we'll detect). Lazy-import of nut.js happens
|
|
96
|
+
* inside; constructor is sync.
|
|
97
|
+
*
|
|
98
|
+
* @param {Object} [opts]
|
|
99
|
+
* @param {import('./osCapabilities.js').OSCapabilities} [opts.capabilities]
|
|
100
|
+
* @param {Object} [opts.nutImpl] - injected for tests; defaults to lazy import
|
|
101
|
+
* @returns {OSController}
|
|
102
|
+
*/
|
|
103
|
+
export function createOSController(opts = {}) {
|
|
104
|
+
return new OSController(opts);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export class OSController {
|
|
108
|
+
constructor({ capabilities = null, nutImpl = null } = {}) {
|
|
109
|
+
this._caps = capabilities;
|
|
110
|
+
this._nutImpl = nutImpl;
|
|
111
|
+
this._loadPromise = null;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// ─── lifecycle ────────────────────────────────────────────────────
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Resolves capabilities + lazy-loads nut.js. Idempotent — safe to
|
|
118
|
+
* call before each op without re-incurring the load cost.
|
|
119
|
+
*/
|
|
120
|
+
async _ensureReady() {
|
|
121
|
+
if (!this._caps) this._caps = await detectOSCapabilities();
|
|
122
|
+
if (this._nutImpl) return;
|
|
123
|
+
if (!this._loadPromise) this._loadPromise = this._loadNative();
|
|
124
|
+
await this._loadPromise;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async _loadNative() {
|
|
128
|
+
try {
|
|
129
|
+
// Dynamic import keeps this an OPTIONAL dependency. Wrapped in a
|
|
130
|
+
// try so a missing-or-broken nut.js doesn't crash the whole CLI.
|
|
131
|
+
this._nutImpl = await import('@nut-tree-fork/nut-js');
|
|
132
|
+
} catch (err) {
|
|
133
|
+
throw makeError(OSError.NATIVE_UNAVAILABLE,
|
|
134
|
+
'DesktopTool requires "@nut-tree-fork/nut-js" — install it (it is in optionalDependencies) ' +
|
|
135
|
+
'or check the platform build logs. Original error: ' + err.message);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Returns a frozen snapshot of OS capabilities + load status.
|
|
141
|
+
* Useful for the admin UI's "OS capability status" panel.
|
|
142
|
+
*/
|
|
143
|
+
async describeCapabilities() {
|
|
144
|
+
await this._ensureReady().catch(() => {/* still report caps */});
|
|
145
|
+
return {
|
|
146
|
+
...this._caps,
|
|
147
|
+
nativeLoaded: this._nutImpl != null,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// ─── primitives ───────────────────────────────────────────────────
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Capture the screen (or a region/monitor) as PNG bytes.
|
|
155
|
+
* @param {Object} [opts]
|
|
156
|
+
* @param {number} [opts.monitor] - 0-indexed monitor; default 0
|
|
157
|
+
* @param {{x:number,y:number,width:number,height:number}} [opts.region]
|
|
158
|
+
* @returns {Promise<Buffer>}
|
|
159
|
+
*/
|
|
160
|
+
async screenshot(opts = {}) {
|
|
161
|
+
await this._ensureReady();
|
|
162
|
+
this._requireCapability('screenshot');
|
|
163
|
+
try {
|
|
164
|
+
const { screen, Region } = this._nutImpl;
|
|
165
|
+
if (opts.region) {
|
|
166
|
+
validateRegion(opts.region);
|
|
167
|
+
const r = new Region(opts.region.x, opts.region.y, opts.region.width, opts.region.height);
|
|
168
|
+
return await screen.grabRegion(r).then(toPngBuffer);
|
|
169
|
+
}
|
|
170
|
+
return await screen.grab().then(toPngBuffer);
|
|
171
|
+
} catch (err) {
|
|
172
|
+
throw wrapOpError('screenshot', err);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** Returns the primary screen size in CSS pixels + DPI ratio. */
|
|
177
|
+
async screenSize() {
|
|
178
|
+
await this._ensureReady();
|
|
179
|
+
try {
|
|
180
|
+
const { screen } = this._nutImpl;
|
|
181
|
+
const width = await screen.width();
|
|
182
|
+
const height = await screen.height();
|
|
183
|
+
return { width, height, dpr: 1 }; // nut.js normalizes to CSS px
|
|
184
|
+
} catch (err) {
|
|
185
|
+
throw wrapOpError('screenSize', err);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Move the mouse cursor to (x, y).
|
|
191
|
+
* @param {number} x
|
|
192
|
+
* @param {number} y
|
|
193
|
+
*/
|
|
194
|
+
async mouseMove(x, y) {
|
|
195
|
+
await this._ensureReady();
|
|
196
|
+
this._requireCapability('mouseInput');
|
|
197
|
+
validateCoord(x, y);
|
|
198
|
+
try {
|
|
199
|
+
const { mouse, Point } = this._nutImpl;
|
|
200
|
+
await mouse.move([new Point(x, y)]);
|
|
201
|
+
} catch (err) {
|
|
202
|
+
throw wrapOpError('mouseMove', err);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Click at (x, y). Move-then-click; single op so DesktopTool's
|
|
208
|
+
* mutex only needs to lock once per logical click.
|
|
209
|
+
*
|
|
210
|
+
* @param {number} x
|
|
211
|
+
* @param {number} y
|
|
212
|
+
* @param {Object} [opts]
|
|
213
|
+
* @param {'left'|'right'|'middle'} [opts.button='left']
|
|
214
|
+
* @param {1|2} [opts.count=1]
|
|
215
|
+
*/
|
|
216
|
+
async mouseClick(x, y, opts = {}) {
|
|
217
|
+
await this._ensureReady();
|
|
218
|
+
this._requireCapability('mouseInput');
|
|
219
|
+
validateCoord(x, y);
|
|
220
|
+
const button = (opts.button || 'left').toUpperCase();
|
|
221
|
+
if (!MouseButton[button]) {
|
|
222
|
+
throw makeError(OSError.INVALID_INPUT, `unknown button "${opts.button}"`);
|
|
223
|
+
}
|
|
224
|
+
const count = opts.count === 2 ? 2 : 1;
|
|
225
|
+
try {
|
|
226
|
+
const { mouse, Point, Button } = this._nutImpl;
|
|
227
|
+
await mouse.move([new Point(x, y)]);
|
|
228
|
+
const btn = Button[button];
|
|
229
|
+
if (count === 2) {
|
|
230
|
+
await mouse.doubleClick(btn);
|
|
231
|
+
} else {
|
|
232
|
+
await mouse.click(btn);
|
|
233
|
+
}
|
|
234
|
+
} catch (err) {
|
|
235
|
+
throw wrapOpError('mouseClick', err);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Scroll at (x, y).
|
|
241
|
+
* @param {number} x
|
|
242
|
+
* @param {number} y
|
|
243
|
+
* @param {number} dy - positive = scroll down
|
|
244
|
+
*/
|
|
245
|
+
async mouseScroll(x, y, dy) {
|
|
246
|
+
await this._ensureReady();
|
|
247
|
+
this._requireCapability('mouseInput');
|
|
248
|
+
validateCoord(x, y);
|
|
249
|
+
if (!Number.isFinite(dy)) throw makeError(OSError.INVALID_INPUT, 'dy must be a number');
|
|
250
|
+
try {
|
|
251
|
+
const { mouse, Point } = this._nutImpl;
|
|
252
|
+
await mouse.move([new Point(x, y)]);
|
|
253
|
+
if (dy > 0) await mouse.scrollDown(Math.abs(dy));
|
|
254
|
+
else if (dy < 0) await mouse.scrollUp(Math.abs(dy));
|
|
255
|
+
} catch (err) {
|
|
256
|
+
throw wrapOpError('mouseScroll', err);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Drag from one point to another.
|
|
262
|
+
* @param {{x:number,y:number}} from
|
|
263
|
+
* @param {{x:number,y:number}} to
|
|
264
|
+
* @param {Object} [opts]
|
|
265
|
+
* @param {'left'|'right'|'middle'} [opts.button='left']
|
|
266
|
+
*/
|
|
267
|
+
async mouseDrag(from, to, opts = {}) {
|
|
268
|
+
await this._ensureReady();
|
|
269
|
+
this._requireCapability('mouseInput');
|
|
270
|
+
validateCoord(from.x, from.y);
|
|
271
|
+
validateCoord(to.x, to.y);
|
|
272
|
+
const button = (opts.button || 'left').toUpperCase();
|
|
273
|
+
if (!MouseButton[button]) {
|
|
274
|
+
throw makeError(OSError.INVALID_INPUT, `unknown button "${opts.button}"`);
|
|
275
|
+
}
|
|
276
|
+
try {
|
|
277
|
+
const { mouse, Point, Button } = this._nutImpl;
|
|
278
|
+
await mouse.move([new Point(from.x, from.y)]);
|
|
279
|
+
await mouse.pressButton(Button[button]);
|
|
280
|
+
await mouse.move([new Point(to.x, to.y)]);
|
|
281
|
+
await mouse.releaseButton(Button[button]);
|
|
282
|
+
} catch (err) {
|
|
283
|
+
throw wrapOpError('mouseDrag', err);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Type a literal string into the focused app.
|
|
289
|
+
* @param {string} text
|
|
290
|
+
* @param {Object} [opts]
|
|
291
|
+
* @param {number} [opts.delayMs=Defaults.TYPE_DELAY_MS]
|
|
292
|
+
*/
|
|
293
|
+
async typeText(text, opts = {}) {
|
|
294
|
+
await this._ensureReady();
|
|
295
|
+
this._requireCapability('keyboardInput');
|
|
296
|
+
if (typeof text !== 'string' || text.length === 0) {
|
|
297
|
+
throw makeError(OSError.INVALID_INPUT, 'text must be a non-empty string');
|
|
298
|
+
}
|
|
299
|
+
const delayMs = Number.isFinite(opts.delayMs) ? opts.delayMs : Defaults.TYPE_DELAY_MS;
|
|
300
|
+
try {
|
|
301
|
+
const { keyboard } = this._nutImpl;
|
|
302
|
+
keyboard.config.autoDelayMs = delayMs;
|
|
303
|
+
await keyboard.type(text);
|
|
304
|
+
} catch (err) {
|
|
305
|
+
throw wrapOpError('typeText', err);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Send a chord (e.g. Ctrl+S, Alt+Tab). Keys are nut.js-style names
|
|
311
|
+
* (Control, Shift, S, F5, Enter, etc.).
|
|
312
|
+
* @param {string[]} keys
|
|
313
|
+
*/
|
|
314
|
+
async keyPress(keys) {
|
|
315
|
+
await this._ensureReady();
|
|
316
|
+
this._requireCapability('keyboardInput');
|
|
317
|
+
if (!Array.isArray(keys) || keys.length === 0) {
|
|
318
|
+
throw makeError(OSError.INVALID_INPUT, 'keys must be a non-empty array');
|
|
319
|
+
}
|
|
320
|
+
try {
|
|
321
|
+
const { keyboard, Key } = this._nutImpl;
|
|
322
|
+
const resolved = keys.map(k => {
|
|
323
|
+
// 1. Cross-platform aliases — agents naturally try "Meta",
|
|
324
|
+
// "Command", "Cmd", "Super", "Windows", "Win". Map those to
|
|
325
|
+
// nut.js's actual key names (LeftWin / LeftCmd) before
|
|
326
|
+
// looking up. Without this, a perfectly reasonable
|
|
327
|
+
// "press Windows key" call dies with OS_INVALID_INPUT.
|
|
328
|
+
const normalized = KEY_ALIASES[k] ?? KEY_ALIASES[k.toLowerCase()] ?? k;
|
|
329
|
+
const upper = normalized.length === 1 ? normalized.toUpperCase() : normalized;
|
|
330
|
+
if (Key[upper] === undefined) {
|
|
331
|
+
// Best-effort hint: include 4 nearby key names from the enum
|
|
332
|
+
// so the agent can self-correct on the next attempt.
|
|
333
|
+
const nearby = Object.keys(Key)
|
|
334
|
+
.filter(n => n.toLowerCase().includes(upper.toLowerCase()) || upper.toLowerCase().includes(n.toLowerCase()))
|
|
335
|
+
.slice(0, 4);
|
|
336
|
+
const hint = nearby.length ? ` (did you mean: ${nearby.join(', ')}?)` : '';
|
|
337
|
+
throw makeError(OSError.INVALID_INPUT, `unknown key "${k}"${hint}`);
|
|
338
|
+
}
|
|
339
|
+
return Key[upper];
|
|
340
|
+
});
|
|
341
|
+
// pressKey takes the chord as varargs; nut.js holds modifiers down,
|
|
342
|
+
// presses the last key, releases all.
|
|
343
|
+
await keyboard.pressKey(...resolved);
|
|
344
|
+
await keyboard.releaseKey(...resolved);
|
|
345
|
+
} catch (err) {
|
|
346
|
+
throw wrapOpError('keyPress', err);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// ─── window management (best-effort, OS-dependent) ────────────────
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* List currently-open windows.
|
|
354
|
+
* @returns {Promise<Array<{title:string, pid?:number, bounds?:object}>>}
|
|
355
|
+
*/
|
|
356
|
+
async listWindows() {
|
|
357
|
+
await this._ensureReady();
|
|
358
|
+
this._requireCapability('windowEnumeration');
|
|
359
|
+
try {
|
|
360
|
+
const { getWindows } = this._nutImpl;
|
|
361
|
+
const winRefs = await getWindows();
|
|
362
|
+
return Promise.all(winRefs.map(async w => ({
|
|
363
|
+
title: await w.getTitle(),
|
|
364
|
+
// bounds + pid are best-effort; nut.js varies by OS.
|
|
365
|
+
bounds: await safeCall(() => w.getRegion()),
|
|
366
|
+
})));
|
|
367
|
+
} catch (err) {
|
|
368
|
+
throw wrapOpError('listWindows', err);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Focus a window matched by title regex or substring.
|
|
374
|
+
* @param {Object} criteria
|
|
375
|
+
* @param {RegExp|string} [criteria.titleMatch]
|
|
376
|
+
*/
|
|
377
|
+
async focusWindow(criteria) {
|
|
378
|
+
await this._ensureReady();
|
|
379
|
+
this._requireCapability('windowFocus');
|
|
380
|
+
if (!criteria || !criteria.titleMatch) {
|
|
381
|
+
throw makeError(OSError.INVALID_INPUT, 'criteria.titleMatch is required');
|
|
382
|
+
}
|
|
383
|
+
try {
|
|
384
|
+
const { getWindows } = this._nutImpl;
|
|
385
|
+
const wins = await getWindows();
|
|
386
|
+
for (const w of wins) {
|
|
387
|
+
const title = await w.getTitle();
|
|
388
|
+
if (matchesCriteria(title, criteria.titleMatch)) {
|
|
389
|
+
await w.focus();
|
|
390
|
+
return { focused: true, title };
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
return { focused: false, title: null };
|
|
394
|
+
} catch (err) {
|
|
395
|
+
throw wrapOpError('focusWindow', err);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// ─── internal ─────────────────────────────────────────────────────
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* Refuse the op if the OS doesn't support it (e.g. Wayland mouseInput).
|
|
403
|
+
* Provides the operator-facing remediation string from osCapabilities.
|
|
404
|
+
*/
|
|
405
|
+
_requireCapability(name) {
|
|
406
|
+
const ok = this._caps?.[name];
|
|
407
|
+
if (!ok) {
|
|
408
|
+
throw makeError(OSError.UNSUPPORTED,
|
|
409
|
+
`${name} not available on this OS/session. ` +
|
|
410
|
+
(this._caps?.degradedReason || 'Run on a supported configuration.'));
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// ─── helpers (module-private) ───────────────────────────────────────
|
|
416
|
+
|
|
417
|
+
function validateCoord(x, y) {
|
|
418
|
+
if (!Number.isFinite(x) || !Number.isFinite(y) || x < 0 || y < 0) {
|
|
419
|
+
throw makeError(OSError.INVALID_INPUT,
|
|
420
|
+
`coordinates must be non-negative finite numbers; got (${x}, ${y})`);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
function validateRegion(r) {
|
|
425
|
+
if (!r || !Number.isFinite(r.x) || !Number.isFinite(r.y) ||
|
|
426
|
+
!Number.isFinite(r.width) || !Number.isFinite(r.height) ||
|
|
427
|
+
r.x < 0 || r.y < 0 || r.width < 1 || r.height < 1) {
|
|
428
|
+
throw makeError(OSError.INVALID_INPUT, `invalid region ${JSON.stringify(r)}`);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
function matchesCriteria(title, m) {
|
|
433
|
+
if (m instanceof RegExp) return m.test(title);
|
|
434
|
+
if (typeof m === 'string') return title.toLowerCase().includes(m.toLowerCase());
|
|
435
|
+
return false;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
async function safeCall(fn) {
|
|
439
|
+
try { return await fn(); }
|
|
440
|
+
catch { return null; }
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
async function toPngBuffer(nutImage) {
|
|
444
|
+
// Forward-compat fast paths if a future nut.js ships a PNG encoder.
|
|
445
|
+
if (typeof nutImage.toPng === 'function') return nutImage.toPng();
|
|
446
|
+
if (typeof nutImage.toBuffer === 'function') return nutImage.toBuffer();
|
|
447
|
+
|
|
448
|
+
// Current shape (nut-tree-fork@4.x): Image exposes raw pixel data in
|
|
449
|
+
// `nutImage.data` with `channels` (3 or 4), `colorMode` (0=BGR,1=RGB
|
|
450
|
+
// per nut.js convention), and dimensions. We encode to PNG with sharp
|
|
451
|
+
// which is already a dep (bench harness uses it).
|
|
452
|
+
if (nutImage.data && nutImage.width && nutImage.height) {
|
|
453
|
+
const sharp = (await import('sharp')).default;
|
|
454
|
+
const { width, height } = nutImage;
|
|
455
|
+
const channels = nutImage.channels || 4;
|
|
456
|
+
// nut.js's pixel buffer is BGR(A) on Windows by default; sharp wants
|
|
457
|
+
// RGB(A). Swap red/blue if colorMode signals BGR. Channel-4 with
|
|
458
|
+
// alpha is preserved.
|
|
459
|
+
const buf = Buffer.from(nutImage.data);
|
|
460
|
+
const isBgr = nutImage.colorMode === 0;
|
|
461
|
+
if (isBgr) swapRedBlueInPlace(buf, channels);
|
|
462
|
+
return sharp(buf, { raw: { width, height, channels } }).png().toBuffer();
|
|
463
|
+
}
|
|
464
|
+
throw makeError(OSError.OP_FAILED, 'nut.js Image has no recognisable pixel buffer');
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
function swapRedBlueInPlace(buf, channels) {
|
|
468
|
+
for (let i = 0; i < buf.length; i += channels) {
|
|
469
|
+
const r = buf[i];
|
|
470
|
+
buf[i] = buf[i + 2];
|
|
471
|
+
buf[i + 2] = r;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
function wrapOpError(opName, err) {
|
|
476
|
+
// Detect permission-denied flavours from OS error messages.
|
|
477
|
+
const msg = (err.message || String(err)).toLowerCase();
|
|
478
|
+
if (msg.includes('not authorized') || msg.includes('permission')) {
|
|
479
|
+
return makeError(OSError.PERMISSION_DENIED,
|
|
480
|
+
`${opName} failed: ${err.message}. Check OS permission grants.`);
|
|
481
|
+
}
|
|
482
|
+
// Already-typed errors (validation, unsupported, native-missing) pass through;
|
|
483
|
+
// we only wrap raw native errors.
|
|
484
|
+
if (err.code === OSError.NATIVE_UNAVAILABLE ||
|
|
485
|
+
err.code === OSError.UNSUPPORTED ||
|
|
486
|
+
err.code === OSError.INVALID_INPUT ||
|
|
487
|
+
err.code === OSError.PERMISSION_DENIED) {
|
|
488
|
+
return err;
|
|
489
|
+
}
|
|
490
|
+
return makeError(OSError.OP_FAILED, `${opName} failed: ${err.message}`, { cause: err });
|
|
491
|
+
}
|