@ashx-j/lunr 0.2.25 → 0.2.26
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/CHANGELOG.md +16 -1
- package/dist/builtin-extensions/index.d.ts.map +1 -1
- package/dist/builtin-extensions/index.js +1 -0
- package/dist/builtin-extensions/index.js.map +1 -1
- package/dist/builtin-extensions/lunr-computer-use.d.ts +3 -0
- package/dist/builtin-extensions/lunr-computer-use.d.ts.map +1 -0
- package/dist/builtin-extensions/lunr-computer-use.js +179 -0
- package/dist/builtin-extensions/lunr-computer-use.js.map +1 -0
- package/dist/builtin-extensions/pi-subagents/src/extension/schemas.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/extension/schemas.js +1 -1
- package/dist/builtin-extensions/pi-subagents/src/extension/schemas.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/extension/tool-description.d.ts +4 -4
- package/dist/builtin-extensions/pi-subagents/src/extension/tool-description.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/extension/tool-description.js +8 -5
- package/dist/builtin-extensions/pi-subagents/src/extension/tool-description.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/subagent-runner.d.ts +1 -0
- package/dist/builtin-extensions/pi-subagents/src/runs/background/subagent-runner.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/subagent-runner.js +11 -2
- package/dist/builtin-extensions/pi-subagents/src/runs/background/subagent-runner.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/chain-execution.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/chain-execution.js +3 -3
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/chain-execution.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/execution.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/execution.js +15 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/execution.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/chain-outputs.d.ts +1 -0
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/chain-outputs.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/chain-outputs.js +5 -0
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/chain-outputs.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/child-tools.d.ts +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/child-tools.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/child-tools.js +2 -0
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/child-tools.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/tui/communication.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/tui/communication.js +1 -1
- package/dist/builtin-extensions/pi-subagents/src/tui/communication.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/tui/render.d.ts +1 -0
- package/dist/builtin-extensions/pi-subagents/src/tui/render.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/tui/render.js +5 -3
- package/dist/builtin-extensions/pi-subagents/src/tui/render.js.map +1 -1
- package/dist/core/computer-use/adapter.d.ts +28 -0
- package/dist/core/computer-use/adapter.d.ts.map +1 -0
- package/dist/core/computer-use/adapter.js +133 -0
- package/dist/core/computer-use/adapter.js.map +1 -0
- package/dist/core/computer-use/image.d.ts +27 -0
- package/dist/core/computer-use/image.d.ts.map +1 -0
- package/dist/core/computer-use/image.js +72 -0
- package/dist/core/computer-use/image.js.map +1 -0
- package/dist/core/computer-use/lease.d.ts +18 -0
- package/dist/core/computer-use/lease.d.ts.map +1 -0
- package/dist/core/computer-use/lease.js +129 -0
- package/dist/core/computer-use/lease.js.map +1 -0
- package/dist/core/computer-use/macos-runtime.d.ts +14 -0
- package/dist/core/computer-use/macos-runtime.d.ts.map +1 -0
- package/dist/core/computer-use/macos-runtime.js +148 -0
- package/dist/core/computer-use/macos-runtime.js.map +1 -0
- package/dist/core/computer-use/policy.d.ts +19 -0
- package/dist/core/computer-use/policy.d.ts.map +1 -0
- package/dist/core/computer-use/policy.js +67 -0
- package/dist/core/computer-use/policy.js.map +1 -0
- package/dist/core/computer-use/release.generated.d.ts +31 -0
- package/dist/core/computer-use/release.generated.d.ts.map +1 -0
- package/dist/core/computer-use/release.generated.js +36 -0
- package/dist/core/computer-use/release.generated.js.map +1 -0
- package/dist/core/computer-use/runtime.d.ts +13 -0
- package/dist/core/computer-use/runtime.d.ts.map +1 -0
- package/dist/core/computer-use/runtime.js +158 -0
- package/dist/core/computer-use/runtime.js.map +1 -0
- package/dist/core/computer-use/schemas.d.ts +100 -0
- package/dist/core/computer-use/schemas.d.ts.map +1 -0
- package/dist/core/computer-use/schemas.js +79 -0
- package/dist/core/computer-use/schemas.js.map +1 -0
- package/dist/core/computer-use/windows-desktop.d.ts +3 -0
- package/dist/core/computer-use/windows-desktop.d.ts.map +1 -0
- package/dist/core/computer-use/windows-desktop.js +48 -0
- package/dist/core/computer-use/windows-desktop.js.map +1 -0
- package/dist/core/computer-use/workflow.d.ts +38 -0
- package/dist/core/computer-use/workflow.d.ts.map +1 -0
- package/dist/core/computer-use/workflow.js +560 -0
- package/dist/core/computer-use/workflow.js.map +1 -0
- package/dist/core/extensions/loader.d.ts.map +1 -1
- package/dist/core/extensions/loader.js +4 -0
- package/dist/core/extensions/loader.js.map +1 -1
- package/dist/core/permissions.d.ts +1 -1
- package/dist/core/permissions.d.ts.map +1 -1
- package/dist/core/permissions.js +14 -3
- package/dist/core/permissions.js.map +1 -1
- package/dist/core/plan-mode.d.ts.map +1 -1
- package/dist/core/plan-mode.js +9 -0
- package/dist/core/plan-mode.js.map +1 -1
- package/dist/core/settings-manager.d.ts +6 -0
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +19 -0
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/subagent-event-transport.d.ts +3 -0
- package/dist/core/subagent-event-transport.d.ts.map +1 -0
- package/dist/core/subagent-event-transport.js +61 -0
- package/dist/core/subagent-event-transport.js.map +1 -0
- package/dist/gateway/adapters/discord.d.ts.map +1 -1
- package/dist/gateway/adapters/discord.js +2 -2
- package/dist/gateway/adapters/discord.js.map +1 -1
- package/dist/gateway/approval.d.ts.map +1 -1
- package/dist/gateway/approval.js +3 -3
- package/dist/gateway/approval.js.map +1 -1
- package/dist/gateway/authz.d.ts +4 -4
- package/dist/gateway/authz.d.ts.map +1 -1
- package/dist/gateway/authz.js +8 -12
- package/dist/gateway/authz.js.map +1 -1
- package/dist/gateway/commands.d.ts.map +1 -1
- package/dist/gateway/commands.js +2 -6
- package/dist/gateway/commands.js.map +1 -1
- package/dist/gateway/cron.d.ts.map +1 -1
- package/dist/gateway/cron.js +20 -6
- package/dist/gateway/cron.js.map +1 -1
- package/dist/gateway/index.d.ts.map +1 -1
- package/dist/gateway/index.js +3 -3
- package/dist/gateway/index.js.map +1 -1
- package/dist/gateway/mobile-commands.d.ts.map +1 -1
- package/dist/gateway/mobile-commands.js +8 -8
- package/dist/gateway/mobile-commands.js.map +1 -1
- package/dist/gateway/presenter.d.ts.map +1 -1
- package/dist/gateway/presenter.js +3 -3
- package/dist/gateway/presenter.js.map +1 -1
- package/dist/gateway/router.d.ts.map +1 -1
- package/dist/gateway/router.js +5 -7
- package/dist/gateway/router.js.map +1 -1
- package/dist/gateway/setup.d.ts.map +1 -1
- package/dist/gateway/setup.js +6 -4
- package/dist/gateway/setup.js.map +1 -1
- package/dist/modes/interactive/components/settings-selector.d.ts +4 -0
- package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/settings-selector.js +20 -0
- package/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +8 -0
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/print-mode.d.ts.map +1 -1
- package/dist/modes/print-mode.js +9 -1
- package/dist/modes/print-mode.js.map +1 -1
- package/dist/node-runtime/adapter-VFG7WNPX.js +17 -0
- package/dist/node-runtime/{agent-session-services-YXXFCP5J.js → agent-session-services-5YXS2PLF.js} +10 -8
- package/dist/node-runtime/{builtin-extensions-ZLAX7TUH.js → builtin-extensions-I2HIFEMY.js} +3 -2
- package/dist/node-runtime/{chunk-3XPLBUEM.js → chunk-2CFDTH5B.js} +7 -3
- package/dist/node-runtime/chunk-2IDTZAG5.js +1415 -0
- package/dist/node-runtime/{chunk-QTU6AKNJ.js → chunk-3MISGG4S.js} +11 -15
- package/dist/node-runtime/chunk-4SBQAI4T.js +2185 -0
- package/dist/node-runtime/{chunk-TORR5XBV.js → chunk-5B5QN3ZQ.js} +12 -12
- package/dist/node-runtime/chunk-5B6VKFEV.js +180 -0
- package/dist/node-runtime/{chunk-AD6VXAO7.js → chunk-5Q4A37RB.js} +15 -104
- package/dist/node-runtime/{chunk-4TLO4PO2.js → chunk-7IQGBKBJ.js} +7 -1401
- package/dist/node-runtime/{chunk-OCVIXVTZ.js → chunk-D5UCKPBQ.js} +12 -1
- package/dist/node-runtime/{chunk-7S3T6YEA.js → chunk-DVQSHCVP.js} +1 -1
- package/dist/node-runtime/{chunk-HFVZUPFJ.js → chunk-E75WFZ33.js} +2 -2
- package/dist/node-runtime/chunk-FY4A63RK.js +73 -0
- package/dist/node-runtime/{chunk-6XL67VVY.js → chunk-I74LN5JG.js} +15 -0
- package/dist/node-runtime/chunk-IKU6Y7DL.js +287 -0
- package/dist/node-runtime/{chunk-WF7PGWRZ.js → chunk-L24TPBS4.js} +5 -4
- package/dist/node-runtime/{chunk-VQFSUJJG.js → chunk-LJTIVM7I.js} +7 -7
- package/dist/node-runtime/{chunk-DSIKTUPW.js → chunk-LWJXTA6Z.js} +274 -2433
- package/dist/node-runtime/{chunk-E4D3IGP2.js → chunk-P7XBVXGT.js} +6 -7
- package/dist/node-runtime/{chunk-OVX6X377.js → chunk-PTFBPO3C.js} +1 -1
- package/dist/node-runtime/{chunk-RC7DMV45.js → chunk-RCR3XAQY.js} +34 -6
- package/dist/node-runtime/chunk-RGQAEOM5.js +81 -0
- package/dist/node-runtime/{chunk-YCUZEGJG.js → chunk-SN2KSMPQ.js} +6 -3
- package/dist/node-runtime/{chunk-NRKGMJYU.js → chunk-SPVDBUOS.js} +1 -1
- package/dist/node-runtime/{chunk-IJIFHRTB.js → chunk-T4DA4XE7.js} +19 -19
- package/dist/node-runtime/chunk-U5F4IC7R.js +53 -0
- package/dist/node-runtime/{chunk-KWIYUUZK.js → chunk-UVWCDQYF.js} +27 -18
- package/dist/node-runtime/{chunk-ZXSGTGJ6.js → chunk-WIF24I7G.js} +21 -5
- package/dist/node-runtime/{chunk-2HHLJWGD.js → chunk-X3PGBLQ7.js} +2 -2
- package/dist/node-runtime/chunk-XHIRAZ26.js +108 -0
- package/dist/node-runtime/{chunk-YDO2F2IA.js → chunk-YWOJIGFY.js} +1 -1
- package/dist/node-runtime/{command-6WDOYKS6.js → command-FSBU2EVZ.js} +3 -3
- package/dist/node-runtime/{commands-NZLSOHPE.js → commands-EMBQQVVT.js} +4 -2
- package/dist/node-runtime/{commands-QC6VUZJQ.js → commands-NAAL7KNT.js} +5 -4
- package/dist/node-runtime/{direct-tool-executor-77GW4F5P.js → direct-tool-executor-4D7UXYU4.js} +7 -4
- package/dist/node-runtime/{gateway-INAYBF4Q.js → gateway-DQM333YV.js} +18 -19
- package/dist/node-runtime/index.js +23 -19
- package/dist/node-runtime/{init-HS6NQXSK.js → init-P5AUV77I.js} +4 -2
- package/dist/node-runtime/{install-cli-KL4UDDAD.js → install-cli-NN4YAU44.js} +1 -1
- package/dist/node-runtime/{interactive-mode-Z5M7WYYZ.js → interactive-mode-YOHZVMKX.js} +11 -9
- package/dist/node-runtime/lunr-computer-use-Y23CCDIJ.js +158 -0
- package/dist/node-runtime/{lunr-cron-LWIZJM2N.js → lunr-cron-USHC4LXH.js} +6 -4
- package/dist/node-runtime/main.js +16 -14
- package/dist/node-runtime/{mcp-auth-flow-QIXHW6QN.js → mcp-auth-flow-QVLFVLEO.js} +2 -1
- package/dist/node-runtime/{mobile-commands-J7OJEKFO.js → mobile-commands-O5LCHL32.js} +4 -3
- package/dist/node-runtime/{package-manager-cli-VYZM5WHN.js → package-manager-cli-Q7BL6KZO.js} +7 -6
- package/dist/node-runtime/{pi-mcp-adapter-5UKBZP44.js → pi-mcp-adapter-3PHF4KZ4.js} +5 -5
- package/dist/node-runtime/{pi-subagents-DNY7EAIG.js → pi-subagents-7IBSSGEW.js} +24 -18
- package/dist/node-runtime/{print-mode-XSFM26EL.js → print-mode-XQ76CYMT.js} +2 -1
- package/dist/node-runtime/{proxy-modes-LGEUYD7R.js → proxy-modes-5FAPSZEZ.js} +7 -4
- package/dist/node-runtime/rpc-entry.js +16 -14
- package/dist/node-runtime/runtime-EFPPD2UW.js +21 -0
- package/dist/node-runtime/{runtime-bridges-VRDDR73P.js → runtime-bridges-7WVCLI76.js} +3 -2
- package/dist/node-runtime/{settings-manager-J4SPQPDJ.js → settings-manager-DJDM5AHM.js} +2 -1
- package/dist/node-runtime/{setup-CQ2NLVK5.js → setup-PFBWRNUI.js} +10 -9
- package/dist/node-runtime/{subagent-executor-KZZQGAKV.js → subagent-executor-LPZ4BHWE.js} +29 -11
- package/dist/node-runtime/{update-cli-GTFJC7XK.js → update-cli-HNMOTNGX.js} +2 -1
- package/dist/node-runtime/workflow-OHPBEFO4.js +637 -0
- package/dist/utils/image-resize-core.d.ts +2 -0
- package/dist/utils/image-resize-core.d.ts.map +1 -1
- package/dist/utils/image-resize-core.js +10 -0
- package/dist/utils/image-resize-core.js.map +1 -1
- package/docs/computer-use.md +362 -0
- package/docs/features.md +8 -6
- package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/gondolin/package-lock.json +2 -2
- package/examples/extensions/gondolin/package.json +1 -1
- package/examples/extensions/sandbox/package-lock.json +2 -2
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/install-lock/package-lock.json +3841 -0
- package/install-lock/package.json +18 -0
- package/native/computer-use/LICENSE.md +21 -0
- package/npm-shrinkwrap.json +3822 -0
- package/package.json +15 -9
- package/dist/node-runtime/{chunk-LQP3RB67.js → chunk-RAK2QA2B.js} +3 -3
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
2
|
+
export type DriverReply = Awaited<ReturnType<Client["callTool"]>>;
|
|
3
|
+
export declare class DriverCallError extends Error {
|
|
4
|
+
readonly phase: "adapter_preflight" | "native_request";
|
|
5
|
+
readonly dispatched: boolean;
|
|
6
|
+
constructor(phase: "adapter_preflight" | "native_request", dispatched: boolean);
|
|
7
|
+
}
|
|
8
|
+
export interface ComputerDriver {
|
|
9
|
+
call(name: string, args: Record<string, unknown>, signal?: AbortSignal): Promise<DriverReply>;
|
|
10
|
+
close(): Promise<void>;
|
|
11
|
+
setProcessObserver?(observer: (pid: number) => Promise<void>): void;
|
|
12
|
+
}
|
|
13
|
+
export declare class CuaAdapter implements ComputerDriver {
|
|
14
|
+
private client?;
|
|
15
|
+
private transport?;
|
|
16
|
+
private mac?;
|
|
17
|
+
private stateDirectory?;
|
|
18
|
+
private connecting?;
|
|
19
|
+
private closing?;
|
|
20
|
+
private readonly abort;
|
|
21
|
+
private processObserver?;
|
|
22
|
+
setProcessObserver(observer: (pid: number) => Promise<void>): void;
|
|
23
|
+
private connect;
|
|
24
|
+
call(name: string, args: Record<string, unknown>, signal?: AbortSignal): Promise<DriverReply>;
|
|
25
|
+
close(): Promise<void>;
|
|
26
|
+
private stop;
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=adapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../../../src/core/computer-use/adapter.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AAMnE,MAAM,MAAM,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;AAClE,qBAAa,eAAgB,SAAQ,KAAK;IACzC,QAAQ,CAAC,KAAK,EAAE,mBAAmB,GAAG,gBAAgB,CAAC;IACvD,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IAC7B,YAAY,KAAK,EAAE,mBAAmB,GAAG,gBAAgB,EAAE,UAAU,EAAE,OAAO,EAI7E;CACD;AACD,MAAM,WAAW,cAAc;IAC9B,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;IAC9F,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,kBAAkB,CAAC,CAAC,QAAQ,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CACpE;AAED,qBAAa,UAAW,YAAW,cAAc;IAChD,OAAO,CAAC,MAAM,CAAC,CAAS;IACxB,OAAO,CAAC,SAAS,CAAC,CAAuB;IACzC,OAAO,CAAC,GAAG,CAAC,CAAa;IACzB,OAAO,CAAC,cAAc,CAAC,CAAS;IAChC,OAAO,CAAC,UAAU,CAAC,CAAgB;IACnC,OAAO,CAAC,OAAO,CAAC,CAAgB;IAChC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAyB;IAC/C,OAAO,CAAC,eAAe,CAAC,CAAiC;IACzD,kBAAkB,CAAC,QAAQ,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAEjE;YAEa,OAAO;IA4Cf,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC,CAmBlG;IAED,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAIrB;YAEa,IAAI;CAwBlB","sourcesContent":["import { mkdir, mkdtemp, realpath, rm } from \"node:fs/promises\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { setTimeout as delay } from \"node:timers/promises\";\nimport { Client } from \"@modelcontextprotocol/sdk/client/index.js\";\nimport { StdioClientTransport } from \"@modelcontextprotocol/sdk/client/stdio.js\";\nimport { MacRuntime } from \"./macos-runtime.ts\";\nimport { CUA_VERSION, installRuntime, runtimeEnvironment } from \"./runtime.ts\";\nimport { assertInteractiveDesktop } from \"./windows-desktop.ts\";\n\nexport type DriverReply = Awaited<ReturnType<Client[\"callTool\"]>>;\nexport class DriverCallError extends Error {\n\treadonly phase: \"adapter_preflight\" | \"native_request\";\n\treadonly dispatched: boolean;\n\tconstructor(phase: \"adapter_preflight\" | \"native_request\", dispatched: boolean) {\n\t\tsuper(phase === \"adapter_preflight\" ? \"Computer adapter preflight failed.\" : \"Computer native request failed.\");\n\t\tthis.phase = phase;\n\t\tthis.dispatched = dispatched;\n\t}\n}\nexport interface ComputerDriver {\n\tcall(name: string, args: Record<string, unknown>, signal?: AbortSignal): Promise<DriverReply>;\n\tclose(): Promise<void>;\n\tsetProcessObserver?(observer: (pid: number) => Promise<void>): void;\n}\n\nexport class CuaAdapter implements ComputerDriver {\n\tprivate client?: Client;\n\tprivate transport?: StdioClientTransport;\n\tprivate mac?: MacRuntime;\n\tprivate stateDirectory?: string;\n\tprivate connecting?: Promise<void>;\n\tprivate closing?: Promise<void>;\n\tprivate readonly abort = new AbortController();\n\tprivate processObserver?: (pid: number) => Promise<void>;\n\tsetProcessObserver(observer: (pid: number) => Promise<void>): void {\n\t\tthis.processObserver = observer;\n\t}\n\n\tprivate async connect(signal: AbortSignal): Promise<void> {\n\t\tconst { command, app } = await installRuntime(signal);\n\t\tsignal.throwIfAborted();\n\t\tlet args = [\"mcp\", \"--direct\", \"--embedded\", \"--no-overlay\"];\n\t\tif (app) {\n\t\t\tthis.mac = new MacRuntime();\n\t\t\tconst socket = await this.mac.start(app, signal);\n\t\t\targs = [\"mcp\", \"--embedded\", \"--socket\", socket, \"--no-overlay\"];\n\t\t}\n\t\tsignal.throwIfAborted();\n\t\tconst state = await mkdtemp(join(await realpath(tmpdir()), \"lunr-cua-client-\"));\n\t\tthis.stateDirectory = state;\n\t\tconst appData = join(state, \"AppData\");\n\t\tawait mkdir(appData);\n\t\tsignal.throwIfAborted();\n\t\tthis.transport = new StdioClientTransport({\n\t\t\tcommand,\n\t\t\targs,\n\t\t\tstderr: \"pipe\",\n\t\t\tenv: {\n\t\t\t\t...runtimeEnvironment(),\n\t\t\t\tHOME: state,\n\t\t\t\tUSERPROFILE: state,\n\t\t\t\tAPPDATA: appData,\n\t\t\t\tLOCALAPPDATA: appData,\n\t\t\t\tXDG_CONFIG_HOME: appData,\n\t\t\t\tXDG_STATE_HOME: appData,\n\t\t\t},\n\t\t});\n\t\t// Drain diagnostics without retaining desktop or runtime data in session history.\n\t\tthis.transport.stderr?.on(\"data\", () => {});\n\t\tthis.client = new Client({ name: \"lunr-computer-use\", version: CUA_VERSION });\n\t\tawait this.client.connect(this.transport, { timeout: 15000, signal });\n\t\tsignal.throwIfAborted();\n\t\tif (this.client.getServerVersion()?.version !== CUA_VERSION)\n\t\t\tthrow new Error(\"Computer runtime version mismatch.\");\n\t\tconst pids = [this.transport.pid, ...(this.mac ? [this.mac.processId] : [])];\n\t\tfor (const pid of pids) {\n\t\t\tif (typeof pid !== \"number\" || !Number.isInteger(pid) || pid < 1)\n\t\t\t\tthrow new Error(\"Computer runtime process identity is missing. Input refused.\");\n\t\t\tawait this.processObserver?.(pid);\n\t\t}\n\t}\n\n\tasync call(name: string, args: Record<string, unknown>, signal?: AbortSignal): Promise<DriverReply> {\n\t\tconst combined = AbortSignal.any([this.abort.signal, ...(signal ? [signal] : [])]);\n\t\tcombined.throwIfAborted();\n\t\tlet dispatched = false;\n\t\ttry {\n\t\t\tawait assertInteractiveDesktop(combined);\n\t\t\tcombined.throwIfAborted();\n\t\t\tthis.connecting ??= this.connect(AbortSignal.any([combined, AbortSignal.timeout(60000)]));\n\t\t\tawait this.connecting;\n\t\t\tcombined.throwIfAborted();\n\t\t\tif (!this.client) throw new Error(\"Computer connection closed.\");\n\t\t\tdispatched = true;\n\t\t\treturn await this.client.callTool({ name, arguments: args }, undefined, { timeout: name === \"launch_app\" ? 45000 : 15000, signal: combined });\n\t\t} catch (error) {\n\t\t\tconst wasCancelled = combined.aborted;\n\t\t\ttry { await this.close(); } catch { /* The workflow retains the lease if shutdown is unconfirmed. */ }\n\t\t\tif (wasCancelled) throw error;\n\t\t\tthrow new DriverCallError(dispatched ? \"native_request\" : \"adapter_preflight\", dispatched);\n\t\t}\n\t}\n\n\tclose(): Promise<void> {\n\t\tthis.abort.abort();\n\t\tthis.closing ??= this.stop();\n\t\treturn this.closing;\n\t}\n\n\tprivate async stop(): Promise<void> {\n\t\tawait this.connecting?.catch(() => undefined);\n\t\tconst pid = this.transport?.pid;\n\t\tawait this.client?.close();\n\t\tawait this.transport?.close();\n\t\tif (pid) {\n\t\t\tconst deadline = Date.now() + 5000;\n\t\t\tfor (;;) {\n\t\t\t\ttry {\n\t\t\t\t\tprocess.kill(pid, 0);\n\t\t\t\t} catch (error) {\n\t\t\t\t\tif (error instanceof Error && \"code\" in error && error.code === \"ESRCH\") break;\n\t\t\t\t\tthrow error;\n\t\t\t\t}\n\t\t\t\tif (Date.now() >= deadline)\n\t\t\t\t\tthrow new Error(\"Owned computer process shutdown is unconfirmed. Desktop lease retained.\");\n\t\t\t\tawait delay(50);\n\t\t\t}\n\t\t}\n\t\tawait this.mac?.close();\n\t\tif (this.stateDirectory) await rm(this.stateDirectory, { recursive: true, force: true });\n\t\tthis.client = undefined;\n\t\tthis.transport = undefined;\n\t}\n}\n"]}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { mkdir, mkdtemp, realpath, rm } from "node:fs/promises";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { setTimeout as delay } from "node:timers/promises";
|
|
5
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
6
|
+
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
7
|
+
import { MacRuntime } from "./macos-runtime.js";
|
|
8
|
+
import { CUA_VERSION, installRuntime, runtimeEnvironment } from "./runtime.js";
|
|
9
|
+
import { assertInteractiveDesktop } from "./windows-desktop.js";
|
|
10
|
+
export class DriverCallError extends Error {
|
|
11
|
+
phase;
|
|
12
|
+
dispatched;
|
|
13
|
+
constructor(phase, dispatched) {
|
|
14
|
+
super(phase === "adapter_preflight" ? "Computer adapter preflight failed." : "Computer native request failed.");
|
|
15
|
+
this.phase = phase;
|
|
16
|
+
this.dispatched = dispatched;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
export class CuaAdapter {
|
|
20
|
+
client;
|
|
21
|
+
transport;
|
|
22
|
+
mac;
|
|
23
|
+
stateDirectory;
|
|
24
|
+
connecting;
|
|
25
|
+
closing;
|
|
26
|
+
abort = new AbortController();
|
|
27
|
+
processObserver;
|
|
28
|
+
setProcessObserver(observer) {
|
|
29
|
+
this.processObserver = observer;
|
|
30
|
+
}
|
|
31
|
+
async connect(signal) {
|
|
32
|
+
const { command, app } = await installRuntime(signal);
|
|
33
|
+
signal.throwIfAborted();
|
|
34
|
+
let args = ["mcp", "--direct", "--embedded", "--no-overlay"];
|
|
35
|
+
if (app) {
|
|
36
|
+
this.mac = new MacRuntime();
|
|
37
|
+
const socket = await this.mac.start(app, signal);
|
|
38
|
+
args = ["mcp", "--embedded", "--socket", socket, "--no-overlay"];
|
|
39
|
+
}
|
|
40
|
+
signal.throwIfAborted();
|
|
41
|
+
const state = await mkdtemp(join(await realpath(tmpdir()), "lunr-cua-client-"));
|
|
42
|
+
this.stateDirectory = state;
|
|
43
|
+
const appData = join(state, "AppData");
|
|
44
|
+
await mkdir(appData);
|
|
45
|
+
signal.throwIfAborted();
|
|
46
|
+
this.transport = new StdioClientTransport({
|
|
47
|
+
command,
|
|
48
|
+
args,
|
|
49
|
+
stderr: "pipe",
|
|
50
|
+
env: {
|
|
51
|
+
...runtimeEnvironment(),
|
|
52
|
+
HOME: state,
|
|
53
|
+
USERPROFILE: state,
|
|
54
|
+
APPDATA: appData,
|
|
55
|
+
LOCALAPPDATA: appData,
|
|
56
|
+
XDG_CONFIG_HOME: appData,
|
|
57
|
+
XDG_STATE_HOME: appData,
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
// Drain diagnostics without retaining desktop or runtime data in session history.
|
|
61
|
+
this.transport.stderr?.on("data", () => { });
|
|
62
|
+
this.client = new Client({ name: "lunr-computer-use", version: CUA_VERSION });
|
|
63
|
+
await this.client.connect(this.transport, { timeout: 15000, signal });
|
|
64
|
+
signal.throwIfAborted();
|
|
65
|
+
if (this.client.getServerVersion()?.version !== CUA_VERSION)
|
|
66
|
+
throw new Error("Computer runtime version mismatch.");
|
|
67
|
+
const pids = [this.transport.pid, ...(this.mac ? [this.mac.processId] : [])];
|
|
68
|
+
for (const pid of pids) {
|
|
69
|
+
if (typeof pid !== "number" || !Number.isInteger(pid) || pid < 1)
|
|
70
|
+
throw new Error("Computer runtime process identity is missing. Input refused.");
|
|
71
|
+
await this.processObserver?.(pid);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
async call(name, args, signal) {
|
|
75
|
+
const combined = AbortSignal.any([this.abort.signal, ...(signal ? [signal] : [])]);
|
|
76
|
+
combined.throwIfAborted();
|
|
77
|
+
let dispatched = false;
|
|
78
|
+
try {
|
|
79
|
+
await assertInteractiveDesktop(combined);
|
|
80
|
+
combined.throwIfAborted();
|
|
81
|
+
this.connecting ??= this.connect(AbortSignal.any([combined, AbortSignal.timeout(60000)]));
|
|
82
|
+
await this.connecting;
|
|
83
|
+
combined.throwIfAborted();
|
|
84
|
+
if (!this.client)
|
|
85
|
+
throw new Error("Computer connection closed.");
|
|
86
|
+
dispatched = true;
|
|
87
|
+
return await this.client.callTool({ name, arguments: args }, undefined, { timeout: name === "launch_app" ? 45000 : 15000, signal: combined });
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
const wasCancelled = combined.aborted;
|
|
91
|
+
try {
|
|
92
|
+
await this.close();
|
|
93
|
+
}
|
|
94
|
+
catch { /* The workflow retains the lease if shutdown is unconfirmed. */ }
|
|
95
|
+
if (wasCancelled)
|
|
96
|
+
throw error;
|
|
97
|
+
throw new DriverCallError(dispatched ? "native_request" : "adapter_preflight", dispatched);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
close() {
|
|
101
|
+
this.abort.abort();
|
|
102
|
+
this.closing ??= this.stop();
|
|
103
|
+
return this.closing;
|
|
104
|
+
}
|
|
105
|
+
async stop() {
|
|
106
|
+
await this.connecting?.catch(() => undefined);
|
|
107
|
+
const pid = this.transport?.pid;
|
|
108
|
+
await this.client?.close();
|
|
109
|
+
await this.transport?.close();
|
|
110
|
+
if (pid) {
|
|
111
|
+
const deadline = Date.now() + 5000;
|
|
112
|
+
for (;;) {
|
|
113
|
+
try {
|
|
114
|
+
process.kill(pid, 0);
|
|
115
|
+
}
|
|
116
|
+
catch (error) {
|
|
117
|
+
if (error instanceof Error && "code" in error && error.code === "ESRCH")
|
|
118
|
+
break;
|
|
119
|
+
throw error;
|
|
120
|
+
}
|
|
121
|
+
if (Date.now() >= deadline)
|
|
122
|
+
throw new Error("Owned computer process shutdown is unconfirmed. Desktop lease retained.");
|
|
123
|
+
await delay(50);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
await this.mac?.close();
|
|
127
|
+
if (this.stateDirectory)
|
|
128
|
+
await rm(this.stateDirectory, { recursive: true, force: true });
|
|
129
|
+
this.client = undefined;
|
|
130
|
+
this.transport = undefined;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
//# sourceMappingURL=adapter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adapter.js","sourceRoot":"","sources":["../../../src/core/computer-use/adapter.ts"],"names":[],"mappings":"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","sourcesContent":["import { mkdir, mkdtemp, realpath, rm } from \"node:fs/promises\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { setTimeout as delay } from \"node:timers/promises\";\nimport { Client } from \"@modelcontextprotocol/sdk/client/index.js\";\nimport { StdioClientTransport } from \"@modelcontextprotocol/sdk/client/stdio.js\";\nimport { MacRuntime } from \"./macos-runtime.ts\";\nimport { CUA_VERSION, installRuntime, runtimeEnvironment } from \"./runtime.ts\";\nimport { assertInteractiveDesktop } from \"./windows-desktop.ts\";\n\nexport type DriverReply = Awaited<ReturnType<Client[\"callTool\"]>>;\nexport class DriverCallError extends Error {\n\treadonly phase: \"adapter_preflight\" | \"native_request\";\n\treadonly dispatched: boolean;\n\tconstructor(phase: \"adapter_preflight\" | \"native_request\", dispatched: boolean) {\n\t\tsuper(phase === \"adapter_preflight\" ? \"Computer adapter preflight failed.\" : \"Computer native request failed.\");\n\t\tthis.phase = phase;\n\t\tthis.dispatched = dispatched;\n\t}\n}\nexport interface ComputerDriver {\n\tcall(name: string, args: Record<string, unknown>, signal?: AbortSignal): Promise<DriverReply>;\n\tclose(): Promise<void>;\n\tsetProcessObserver?(observer: (pid: number) => Promise<void>): void;\n}\n\nexport class CuaAdapter implements ComputerDriver {\n\tprivate client?: Client;\n\tprivate transport?: StdioClientTransport;\n\tprivate mac?: MacRuntime;\n\tprivate stateDirectory?: string;\n\tprivate connecting?: Promise<void>;\n\tprivate closing?: Promise<void>;\n\tprivate readonly abort = new AbortController();\n\tprivate processObserver?: (pid: number) => Promise<void>;\n\tsetProcessObserver(observer: (pid: number) => Promise<void>): void {\n\t\tthis.processObserver = observer;\n\t}\n\n\tprivate async connect(signal: AbortSignal): Promise<void> {\n\t\tconst { command, app } = await installRuntime(signal);\n\t\tsignal.throwIfAborted();\n\t\tlet args = [\"mcp\", \"--direct\", \"--embedded\", \"--no-overlay\"];\n\t\tif (app) {\n\t\t\tthis.mac = new MacRuntime();\n\t\t\tconst socket = await this.mac.start(app, signal);\n\t\t\targs = [\"mcp\", \"--embedded\", \"--socket\", socket, \"--no-overlay\"];\n\t\t}\n\t\tsignal.throwIfAborted();\n\t\tconst state = await mkdtemp(join(await realpath(tmpdir()), \"lunr-cua-client-\"));\n\t\tthis.stateDirectory = state;\n\t\tconst appData = join(state, \"AppData\");\n\t\tawait mkdir(appData);\n\t\tsignal.throwIfAborted();\n\t\tthis.transport = new StdioClientTransport({\n\t\t\tcommand,\n\t\t\targs,\n\t\t\tstderr: \"pipe\",\n\t\t\tenv: {\n\t\t\t\t...runtimeEnvironment(),\n\t\t\t\tHOME: state,\n\t\t\t\tUSERPROFILE: state,\n\t\t\t\tAPPDATA: appData,\n\t\t\t\tLOCALAPPDATA: appData,\n\t\t\t\tXDG_CONFIG_HOME: appData,\n\t\t\t\tXDG_STATE_HOME: appData,\n\t\t\t},\n\t\t});\n\t\t// Drain diagnostics without retaining desktop or runtime data in session history.\n\t\tthis.transport.stderr?.on(\"data\", () => {});\n\t\tthis.client = new Client({ name: \"lunr-computer-use\", version: CUA_VERSION });\n\t\tawait this.client.connect(this.transport, { timeout: 15000, signal });\n\t\tsignal.throwIfAborted();\n\t\tif (this.client.getServerVersion()?.version !== CUA_VERSION)\n\t\t\tthrow new Error(\"Computer runtime version mismatch.\");\n\t\tconst pids = [this.transport.pid, ...(this.mac ? [this.mac.processId] : [])];\n\t\tfor (const pid of pids) {\n\t\t\tif (typeof pid !== \"number\" || !Number.isInteger(pid) || pid < 1)\n\t\t\t\tthrow new Error(\"Computer runtime process identity is missing. Input refused.\");\n\t\t\tawait this.processObserver?.(pid);\n\t\t}\n\t}\n\n\tasync call(name: string, args: Record<string, unknown>, signal?: AbortSignal): Promise<DriverReply> {\n\t\tconst combined = AbortSignal.any([this.abort.signal, ...(signal ? [signal] : [])]);\n\t\tcombined.throwIfAborted();\n\t\tlet dispatched = false;\n\t\ttry {\n\t\t\tawait assertInteractiveDesktop(combined);\n\t\t\tcombined.throwIfAborted();\n\t\t\tthis.connecting ??= this.connect(AbortSignal.any([combined, AbortSignal.timeout(60000)]));\n\t\t\tawait this.connecting;\n\t\t\tcombined.throwIfAborted();\n\t\t\tif (!this.client) throw new Error(\"Computer connection closed.\");\n\t\t\tdispatched = true;\n\t\t\treturn await this.client.callTool({ name, arguments: args }, undefined, { timeout: name === \"launch_app\" ? 45000 : 15000, signal: combined });\n\t\t} catch (error) {\n\t\t\tconst wasCancelled = combined.aborted;\n\t\t\ttry { await this.close(); } catch { /* The workflow retains the lease if shutdown is unconfirmed. */ }\n\t\t\tif (wasCancelled) throw error;\n\t\t\tthrow new DriverCallError(dispatched ? \"native_request\" : \"adapter_preflight\", dispatched);\n\t\t}\n\t}\n\n\tclose(): Promise<void> {\n\t\tthis.abort.abort();\n\t\tthis.closing ??= this.stop();\n\t\treturn this.closing;\n\t}\n\n\tprivate async stop(): Promise<void> {\n\t\tawait this.connecting?.catch(() => undefined);\n\t\tconst pid = this.transport?.pid;\n\t\tawait this.client?.close();\n\t\tawait this.transport?.close();\n\t\tif (pid) {\n\t\t\tconst deadline = Date.now() + 5000;\n\t\t\tfor (;;) {\n\t\t\t\ttry {\n\t\t\t\t\tprocess.kill(pid, 0);\n\t\t\t\t} catch (error) {\n\t\t\t\t\tif (error instanceof Error && \"code\" in error && error.code === \"ESRCH\") break;\n\t\t\t\t\tthrow error;\n\t\t\t\t}\n\t\t\t\tif (Date.now() >= deadline)\n\t\t\t\t\tthrow new Error(\"Owned computer process shutdown is unconfirmed. Desktop lease retained.\");\n\t\t\t\tawait delay(50);\n\t\t\t}\n\t\t}\n\t\tawait this.mac?.close();\n\t\tif (this.stateDirectory) await rm(this.stateDirectory, { recursive: true, force: true });\n\t\tthis.client = undefined;\n\t\tthis.transport = undefined;\n\t}\n}\n"]}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { ImageContent } from "@ashx-j/lunr-ai";
|
|
2
|
+
export interface ImageRegion {
|
|
3
|
+
x: number;
|
|
4
|
+
y: number;
|
|
5
|
+
width: number;
|
|
6
|
+
height: number;
|
|
7
|
+
}
|
|
8
|
+
export declare function screenshotDimensions(image: ImageContent): {
|
|
9
|
+
width: number;
|
|
10
|
+
height: number;
|
|
11
|
+
};
|
|
12
|
+
export declare function prepareComputerImage(image: ImageContent, crop?: ImageRegion): Promise<{
|
|
13
|
+
fingerprint: string;
|
|
14
|
+
image: {
|
|
15
|
+
type: "image";
|
|
16
|
+
data: string;
|
|
17
|
+
mimeType: string;
|
|
18
|
+
};
|
|
19
|
+
width: number;
|
|
20
|
+
height: number;
|
|
21
|
+
sourceWidth: number;
|
|
22
|
+
sourceHeight: number;
|
|
23
|
+
region: ImageRegion;
|
|
24
|
+
scaleX: number;
|
|
25
|
+
scaleY: number;
|
|
26
|
+
}>;
|
|
27
|
+
//# sourceMappingURL=image.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"image.d.ts","sourceRoot":"","sources":["../../../src/core/computer-use/image.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAI1D,MAAM,WAAW,WAAW;IAC3B,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CACf;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,YAAY,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAW3F;AAED,wBAAsB,oBAAoB,CAAC,KAAK,EAAE,YAAY,EAAE,IAAI,CAAC,EAAE,WAAW;;;;;;;;;;;;;;GAoDjF","sourcesContent":["import { createHash } from \"node:crypto\";\nimport type { ImageContent } from \"@ashx-j/lunr-ai\";\nimport { resizeImage } from \"../../utils/image-resize.ts\";\nimport { loadPhoton } from \"../../utils/photon.ts\";\n\nexport interface ImageRegion {\n\tx: number;\n\ty: number;\n\twidth: number;\n\theight: number;\n}\n\nexport function screenshotDimensions(image: ImageContent): { width: number; height: number } {\n\tif (image.mimeType !== \"image/png\" || image.data.length > 64 * 1024 * 1024)\n\t\tthrow new Error(\"Expected a bounded PNG screenshot.\");\n\tconst bytes = Buffer.from(image.data, \"base64\");\n\tif (bytes.length < 24 || !bytes.subarray(0, 8).equals(Buffer.from([137, 80, 78, 71, 13, 10, 26, 10])))\n\t\tthrow new Error(\"Invalid screenshot PNG.\");\n\tconst width = bytes.readUInt32BE(16);\n\tconst height = bytes.readUInt32BE(20);\n\tif (!width || !height || width > 16384 || height > 16384 || width * height > 40_000_000)\n\t\tthrow new Error(\"Screenshot dimensions exceed the supported capture budget.\");\n\treturn { width, height };\n}\n\nexport async function prepareComputerImage(image: ImageContent, crop?: ImageRegion) {\n\tconst source = screenshotDimensions(image);\n\tconst region = crop ?? { x: 0, y: 0, ...source };\n\tif (\n\t\t![region.x, region.y, region.width, region.height].every(Number.isInteger) ||\n\t\tregion.x < 0 || region.y < 0 || region.width < 1 || region.height < 1 ||\n\t\tregion.x + region.width > source.width || region.y + region.height > source.height\n\t) throw new Error(\"Crop is outside the captured image.\");\n\tlet bytes: Uint8Array = Buffer.from(image.data, \"base64\");\n\tlet fingerprint: string | undefined;\n\tif (crop) {\n\t\tconst photon = await loadPhoton();\n\t\tif (!photon) throw new Error(\"Image processing is unavailable; crop refused.\");\n\t\tconst original = photon.PhotonImage.new_from_byteslice(bytes);\n\t\ttry {\n\t\t\tif (original.get_width() !== source.width || original.get_height() !== source.height)\n\t\t\t\tthrow new Error(\"Decoded screenshot dimensions changed.\");\n\t\t\tfingerprint = createHash(\"sha256\").update(`${source.width}x${source.height}:`).update(original.get_raw_pixels()).digest(\"hex\");\n\t\t\tconst cropped = photon.crop(original, region.x, region.y, region.x + region.width, region.y + region.height);\n\t\t\ttry {\n\t\t\t\tbytes = cropped.get_bytes();\n\t\t\t} finally {\n\t\t\t\tcropped.free();\n\t\t\t}\n\t\t} finally {\n\t\t\toriginal.free();\n\t\t}\n\t}\n\tconst ratio = Math.min(1, 1280 / Math.max(region.width, region.height), Math.sqrt(1_000_000 / (region.width * region.height)));\n\tconst resized = await resizeImage(bytes, \"image/png\", {\n\t\tmaxWidth: Math.max(1, Math.floor(region.width * ratio)),\n\t\tmaxHeight: Math.max(1, Math.floor(region.height * ratio)),\n\t\tmaxBytes: 1.5 * 1024 * 1024,\n\t\tincludePixelFingerprint: !crop,\n\t});\n\tif (!resized || resized.originalWidth !== region.width || resized.originalHeight !== region.height ||\n\t\tresized.width < 1 || resized.height < 1 || resized.width > 1280 || resized.height > 1280 ||\n\t\tresized.width * resized.height > 1_000_000)\n\t\tthrow new Error(\"Screenshot could not be decoded within the image budget.\");\n\tfingerprint ??= resized.fingerprint;\n\tif (!fingerprint) throw new Error(\"Screenshot pixel identity could not be verified.\");\n\treturn {\n\t\tfingerprint,\n\t\timage: { type: \"image\", data: resized.data, mimeType: resized.mimeType } satisfies ImageContent,\n\t\twidth: resized.width,\n\t\theight: resized.height,\n\t\tsourceWidth: source.width,\n\t\tsourceHeight: source.height,\n\t\tregion,\n\t\tscaleX: region.width / resized.width,\n\t\tscaleY: region.height / resized.height,\n\t};\n}\n"]}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { resizeImage } from "../../utils/image-resize.js";
|
|
3
|
+
import { loadPhoton } from "../../utils/photon.js";
|
|
4
|
+
export function screenshotDimensions(image) {
|
|
5
|
+
if (image.mimeType !== "image/png" || image.data.length > 64 * 1024 * 1024)
|
|
6
|
+
throw new Error("Expected a bounded PNG screenshot.");
|
|
7
|
+
const bytes = Buffer.from(image.data, "base64");
|
|
8
|
+
if (bytes.length < 24 || !bytes.subarray(0, 8).equals(Buffer.from([137, 80, 78, 71, 13, 10, 26, 10])))
|
|
9
|
+
throw new Error("Invalid screenshot PNG.");
|
|
10
|
+
const width = bytes.readUInt32BE(16);
|
|
11
|
+
const height = bytes.readUInt32BE(20);
|
|
12
|
+
if (!width || !height || width > 16384 || height > 16384 || width * height > 40_000_000)
|
|
13
|
+
throw new Error("Screenshot dimensions exceed the supported capture budget.");
|
|
14
|
+
return { width, height };
|
|
15
|
+
}
|
|
16
|
+
export async function prepareComputerImage(image, crop) {
|
|
17
|
+
const source = screenshotDimensions(image);
|
|
18
|
+
const region = crop ?? { x: 0, y: 0, ...source };
|
|
19
|
+
if (![region.x, region.y, region.width, region.height].every(Number.isInteger) ||
|
|
20
|
+
region.x < 0 || region.y < 0 || region.width < 1 || region.height < 1 ||
|
|
21
|
+
region.x + region.width > source.width || region.y + region.height > source.height)
|
|
22
|
+
throw new Error("Crop is outside the captured image.");
|
|
23
|
+
let bytes = Buffer.from(image.data, "base64");
|
|
24
|
+
let fingerprint;
|
|
25
|
+
if (crop) {
|
|
26
|
+
const photon = await loadPhoton();
|
|
27
|
+
if (!photon)
|
|
28
|
+
throw new Error("Image processing is unavailable; crop refused.");
|
|
29
|
+
const original = photon.PhotonImage.new_from_byteslice(bytes);
|
|
30
|
+
try {
|
|
31
|
+
if (original.get_width() !== source.width || original.get_height() !== source.height)
|
|
32
|
+
throw new Error("Decoded screenshot dimensions changed.");
|
|
33
|
+
fingerprint = createHash("sha256").update(`${source.width}x${source.height}:`).update(original.get_raw_pixels()).digest("hex");
|
|
34
|
+
const cropped = photon.crop(original, region.x, region.y, region.x + region.width, region.y + region.height);
|
|
35
|
+
try {
|
|
36
|
+
bytes = cropped.get_bytes();
|
|
37
|
+
}
|
|
38
|
+
finally {
|
|
39
|
+
cropped.free();
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
finally {
|
|
43
|
+
original.free();
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
const ratio = Math.min(1, 1280 / Math.max(region.width, region.height), Math.sqrt(1_000_000 / (region.width * region.height)));
|
|
47
|
+
const resized = await resizeImage(bytes, "image/png", {
|
|
48
|
+
maxWidth: Math.max(1, Math.floor(region.width * ratio)),
|
|
49
|
+
maxHeight: Math.max(1, Math.floor(region.height * ratio)),
|
|
50
|
+
maxBytes: 1.5 * 1024 * 1024,
|
|
51
|
+
includePixelFingerprint: !crop,
|
|
52
|
+
});
|
|
53
|
+
if (!resized || resized.originalWidth !== region.width || resized.originalHeight !== region.height ||
|
|
54
|
+
resized.width < 1 || resized.height < 1 || resized.width > 1280 || resized.height > 1280 ||
|
|
55
|
+
resized.width * resized.height > 1_000_000)
|
|
56
|
+
throw new Error("Screenshot could not be decoded within the image budget.");
|
|
57
|
+
fingerprint ??= resized.fingerprint;
|
|
58
|
+
if (!fingerprint)
|
|
59
|
+
throw new Error("Screenshot pixel identity could not be verified.");
|
|
60
|
+
return {
|
|
61
|
+
fingerprint,
|
|
62
|
+
image: { type: "image", data: resized.data, mimeType: resized.mimeType },
|
|
63
|
+
width: resized.width,
|
|
64
|
+
height: resized.height,
|
|
65
|
+
sourceWidth: source.width,
|
|
66
|
+
sourceHeight: source.height,
|
|
67
|
+
region,
|
|
68
|
+
scaleX: region.width / resized.width,
|
|
69
|
+
scaleY: region.height / resized.height,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=image.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"image.js","sourceRoot":"","sources":["../../../src/core/computer-use/image.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash } from \"node:crypto\";\nimport type { ImageContent } from \"@ashx-j/lunr-ai\";\nimport { resizeImage } from \"../../utils/image-resize.ts\";\nimport { loadPhoton } from \"../../utils/photon.ts\";\n\nexport interface ImageRegion {\n\tx: number;\n\ty: number;\n\twidth: number;\n\theight: number;\n}\n\nexport function screenshotDimensions(image: ImageContent): { width: number; height: number } {\n\tif (image.mimeType !== \"image/png\" || image.data.length > 64 * 1024 * 1024)\n\t\tthrow new Error(\"Expected a bounded PNG screenshot.\");\n\tconst bytes = Buffer.from(image.data, \"base64\");\n\tif (bytes.length < 24 || !bytes.subarray(0, 8).equals(Buffer.from([137, 80, 78, 71, 13, 10, 26, 10])))\n\t\tthrow new Error(\"Invalid screenshot PNG.\");\n\tconst width = bytes.readUInt32BE(16);\n\tconst height = bytes.readUInt32BE(20);\n\tif (!width || !height || width > 16384 || height > 16384 || width * height > 40_000_000)\n\t\tthrow new Error(\"Screenshot dimensions exceed the supported capture budget.\");\n\treturn { width, height };\n}\n\nexport async function prepareComputerImage(image: ImageContent, crop?: ImageRegion) {\n\tconst source = screenshotDimensions(image);\n\tconst region = crop ?? { x: 0, y: 0, ...source };\n\tif (\n\t\t![region.x, region.y, region.width, region.height].every(Number.isInteger) ||\n\t\tregion.x < 0 || region.y < 0 || region.width < 1 || region.height < 1 ||\n\t\tregion.x + region.width > source.width || region.y + region.height > source.height\n\t) throw new Error(\"Crop is outside the captured image.\");\n\tlet bytes: Uint8Array = Buffer.from(image.data, \"base64\");\n\tlet fingerprint: string | undefined;\n\tif (crop) {\n\t\tconst photon = await loadPhoton();\n\t\tif (!photon) throw new Error(\"Image processing is unavailable; crop refused.\");\n\t\tconst original = photon.PhotonImage.new_from_byteslice(bytes);\n\t\ttry {\n\t\t\tif (original.get_width() !== source.width || original.get_height() !== source.height)\n\t\t\t\tthrow new Error(\"Decoded screenshot dimensions changed.\");\n\t\t\tfingerprint = createHash(\"sha256\").update(`${source.width}x${source.height}:`).update(original.get_raw_pixels()).digest(\"hex\");\n\t\t\tconst cropped = photon.crop(original, region.x, region.y, region.x + region.width, region.y + region.height);\n\t\t\ttry {\n\t\t\t\tbytes = cropped.get_bytes();\n\t\t\t} finally {\n\t\t\t\tcropped.free();\n\t\t\t}\n\t\t} finally {\n\t\t\toriginal.free();\n\t\t}\n\t}\n\tconst ratio = Math.min(1, 1280 / Math.max(region.width, region.height), Math.sqrt(1_000_000 / (region.width * region.height)));\n\tconst resized = await resizeImage(bytes, \"image/png\", {\n\t\tmaxWidth: Math.max(1, Math.floor(region.width * ratio)),\n\t\tmaxHeight: Math.max(1, Math.floor(region.height * ratio)),\n\t\tmaxBytes: 1.5 * 1024 * 1024,\n\t\tincludePixelFingerprint: !crop,\n\t});\n\tif (!resized || resized.originalWidth !== region.width || resized.originalHeight !== region.height ||\n\t\tresized.width < 1 || resized.height < 1 || resized.width > 1280 || resized.height > 1280 ||\n\t\tresized.width * resized.height > 1_000_000)\n\t\tthrow new Error(\"Screenshot could not be decoded within the image budget.\");\n\tfingerprint ??= resized.fingerprint;\n\tif (!fingerprint) throw new Error(\"Screenshot pixel identity could not be verified.\");\n\treturn {\n\t\tfingerprint,\n\t\timage: { type: \"image\", data: resized.data, mimeType: resized.mimeType } satisfies ImageContent,\n\t\twidth: resized.width,\n\t\theight: resized.height,\n\t\tsourceWidth: source.width,\n\t\tsourceHeight: source.height,\n\t\tregion,\n\t\tscaleX: region.width / resized.width,\n\t\tscaleY: region.height / resized.height,\n\t};\n}\n"]}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export declare class DesktopLease {
|
|
2
|
+
private readonly identity;
|
|
3
|
+
private owned;
|
|
4
|
+
private tail;
|
|
5
|
+
private closed;
|
|
6
|
+
private readonly controller;
|
|
7
|
+
get signal(): AbortSignal;
|
|
8
|
+
private readonly directory;
|
|
9
|
+
constructor(directory?: string);
|
|
10
|
+
private owner;
|
|
11
|
+
private withOwnerLock;
|
|
12
|
+
private writeOwner;
|
|
13
|
+
private acquire;
|
|
14
|
+
trackProcess(pid: number): Promise<void>;
|
|
15
|
+
run<T>(operation: () => Promise<T>, signal?: AbortSignal): Promise<T>;
|
|
16
|
+
close(): Promise<void>;
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=lease.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lease.d.ts","sourceRoot":"","sources":["../../../src/core/computer-use/lease.ts"],"names":[],"mappings":"AAOA,qBAAa,YAAY;IACxB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAgB;IACzC,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,IAAI,CAAuC;IACnD,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAyB;IACpD,IAAI,MAAM,IAAI,WAAW,CAExB;IACD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,YAAY,SAAS,SAA+C,EAEnE;YAEa,KAAK;YAwBL,aAAa;YAab,UAAU;YAUV,OAAO;IAwBf,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAQ7C;IAED,GAAG,CAAC,CAAC,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,CAAC,CAAC,CAepE;IAEK,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAU3B;CACD","sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport { mkdir, readFile, rename, rm, writeFile } from \"node:fs/promises\";\nimport { userInfo } from \"node:os\";\nimport { join } from \"node:path\";\nimport lockfile from \"proper-lockfile\";\nimport { assertOwnedPath } from \"./runtime.ts\";\n\nexport class DesktopLease {\n\tprivate readonly identity = randomUUID();\n\tprivate owned = false;\n\tprivate tail: Promise<unknown> = Promise.resolve();\n\tprivate closed = false;\n\tprivate readonly controller = new AbortController();\n\tget signal(): AbortSignal {\n\t\treturn this.controller.signal;\n\t}\n\tprivate readonly directory: string;\n\tconstructor(directory = join(userInfo().homedir, \".lunr\", \"desktop\")) {\n\t\tthis.directory = directory;\n\t}\n\n\tprivate async owner(): Promise<{ pid: number; identity: string; processes: number[] } | undefined> {\n\t\ttry {\n\t\t\tconst value: unknown = JSON.parse(await readFile(join(this.directory, \"workflow-owner.json\"), \"utf8\"));\n\t\t\tif (\n\t\t\t\t!value ||\n\t\t\t\ttypeof value !== \"object\" ||\n\t\t\t\t!(\"pid\" in value) ||\n\t\t\t\ttypeof value.pid !== \"number\" ||\n\t\t\t\t!Number.isInteger(value.pid) ||\n\t\t\t\tvalue.pid < 1 ||\n\t\t\t\t!(\"identity\" in value) ||\n\t\t\t\ttypeof value.identity !== \"string\"\n\t\t\t)\n\t\t\t\tthrow new Error(\"Invalid desktop owner record; refusing takeover.\");\n\t\t\tconst processes = \"processes\" in value ? value.processes : [];\n\t\t\tif (!Array.isArray(processes) || !processes.every((pid) => Number.isInteger(pid) && pid > 0))\n\t\t\t\tthrow new Error(\"Invalid runtime owner record; refusing takeover.\");\n\t\t\treturn { pid: value.pid, identity: value.identity, processes };\n\t\t} catch (error) {\n\t\t\tif (error instanceof Error && \"code\" in error && error.code === \"ENOENT\") return undefined;\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\tprivate async withOwnerLock(operation: () => Promise<void>): Promise<void> {\n\t\tconst release = await lockfile.lock(this.directory, {\n\t\t\tlockfilePath: join(this.directory, \"workflow-acquire.lock\"),\n\t\t\tretries: 0,\n\t\t\tstale: 10000,\n\t\t});\n\t\ttry {\n\t\t\tawait operation();\n\t\t} finally {\n\t\t\tawait release();\n\t\t}\n\t}\n\n\tprivate async writeOwner(owner: { pid: number; identity: string; processes?: number[] }): Promise<void> {\n\t\tconst temporary = join(this.directory, `workflow-owner-${randomUUID()}.tmp`);\n\t\ttry {\n\t\t\tawait writeFile(temporary, JSON.stringify(owner), { mode: 0o600, flag: \"wx\" });\n\t\t\tawait rename(temporary, join(this.directory, \"workflow-owner.json\"));\n\t\t} finally {\n\t\t\tawait rm(temporary, { force: true });\n\t\t}\n\t}\n\n\tprivate async acquire(): Promise<void> {\n\t\tawait assertOwnedPath(this.directory);\n\t\tawait mkdir(this.directory, { recursive: true, mode: 0o700 });\n\t\tawait this.withOwnerLock(async () => {\n\t\t\tconst owner = await this.owner();\n\t\t\tif (owner) {\n\t\t\t\tconst alive = [owner.pid, ...owner.processes].some((pid) => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tprocess.kill(pid, 0);\n\t\t\t\t\t\treturn true;\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\treturn !(error instanceof Error && \"code\" in error && error.code === \"ESRCH\");\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t\tif (alive)\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t\"Desktop busy: another lunR workflow owns this user's desktop. Retry only after it ends.\",\n\t\t\t\t\t);\n\t\t\t}\n\t\t\tawait this.writeOwner({ pid: process.pid, identity: this.identity });\n\t\t\tthis.owned = true;\n\t\t});\n\t}\n\n\tasync trackProcess(pid: number): Promise<void> {\n\t\tawait this.withOwnerLock(async () => {\n\t\t\tconst owner = await this.owner();\n\t\t\tif (!owner || owner.identity !== this.identity || !Number.isInteger(pid) || pid < 1)\n\t\t\t\tthrow new Error(\"Cannot bind runtime to the desktop lease.\");\n\t\t\tif (!owner.processes.includes(pid)) owner.processes.push(pid);\n\t\t\tawait this.writeOwner(owner);\n\t\t});\n\t}\n\n\trun<T>(operation: () => Promise<T>, signal?: AbortSignal): Promise<T> {\n\t\tconst result = this.tail.then(async () => {\n\t\t\tif (this.closed) throw new Error(\"Desktop workflow ended. Start a new workflow and observe again.\");\n\t\t\tsignal?.throwIfAborted();\n\t\t\tif (!this.owned) await this.acquire();\n\t\t\tif ((await this.owner())?.identity !== this.identity) {\n\t\t\t\tthis.closed = true;\n\t\t\t\tthis.controller.abort(new Error(\"Desktop lease lost.\"));\n\t\t\t\tthrow new Error(\"Desktop lease lost. Input refused.\");\n\t\t\t}\n\t\t\tsignal?.throwIfAborted();\n\t\t\treturn operation();\n\t\t});\n\t\tthis.tail = result.catch(() => undefined);\n\t\treturn result;\n\t}\n\n\tasync close(): Promise<void> {\n\t\tthis.closed = true;\n\t\tthis.controller.abort();\n\t\tawait this.tail;\n\t\tif (this.owned) {\n\t\t\tawait this.withOwnerLock(async () => {\n\t\t\t\tif ((await this.owner())?.identity === this.identity) await rm(join(this.directory, \"workflow-owner.json\"));\n\t\t\t});\n\t\t\tthis.owned = false;\n\t\t}\n\t}\n}\n"]}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
3
|
+
import { userInfo } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import lockfile from "proper-lockfile";
|
|
6
|
+
import { assertOwnedPath } from "./runtime.js";
|
|
7
|
+
export class DesktopLease {
|
|
8
|
+
identity = randomUUID();
|
|
9
|
+
owned = false;
|
|
10
|
+
tail = Promise.resolve();
|
|
11
|
+
closed = false;
|
|
12
|
+
controller = new AbortController();
|
|
13
|
+
get signal() {
|
|
14
|
+
return this.controller.signal;
|
|
15
|
+
}
|
|
16
|
+
directory;
|
|
17
|
+
constructor(directory = join(userInfo().homedir, ".lunr", "desktop")) {
|
|
18
|
+
this.directory = directory;
|
|
19
|
+
}
|
|
20
|
+
async owner() {
|
|
21
|
+
try {
|
|
22
|
+
const value = JSON.parse(await readFile(join(this.directory, "workflow-owner.json"), "utf8"));
|
|
23
|
+
if (!value ||
|
|
24
|
+
typeof value !== "object" ||
|
|
25
|
+
!("pid" in value) ||
|
|
26
|
+
typeof value.pid !== "number" ||
|
|
27
|
+
!Number.isInteger(value.pid) ||
|
|
28
|
+
value.pid < 1 ||
|
|
29
|
+
!("identity" in value) ||
|
|
30
|
+
typeof value.identity !== "string")
|
|
31
|
+
throw new Error("Invalid desktop owner record; refusing takeover.");
|
|
32
|
+
const processes = "processes" in value ? value.processes : [];
|
|
33
|
+
if (!Array.isArray(processes) || !processes.every((pid) => Number.isInteger(pid) && pid > 0))
|
|
34
|
+
throw new Error("Invalid runtime owner record; refusing takeover.");
|
|
35
|
+
return { pid: value.pid, identity: value.identity, processes };
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT")
|
|
39
|
+
return undefined;
|
|
40
|
+
throw error;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
async withOwnerLock(operation) {
|
|
44
|
+
const release = await lockfile.lock(this.directory, {
|
|
45
|
+
lockfilePath: join(this.directory, "workflow-acquire.lock"),
|
|
46
|
+
retries: 0,
|
|
47
|
+
stale: 10000,
|
|
48
|
+
});
|
|
49
|
+
try {
|
|
50
|
+
await operation();
|
|
51
|
+
}
|
|
52
|
+
finally {
|
|
53
|
+
await release();
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
async writeOwner(owner) {
|
|
57
|
+
const temporary = join(this.directory, `workflow-owner-${randomUUID()}.tmp`);
|
|
58
|
+
try {
|
|
59
|
+
await writeFile(temporary, JSON.stringify(owner), { mode: 0o600, flag: "wx" });
|
|
60
|
+
await rename(temporary, join(this.directory, "workflow-owner.json"));
|
|
61
|
+
}
|
|
62
|
+
finally {
|
|
63
|
+
await rm(temporary, { force: true });
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
async acquire() {
|
|
67
|
+
await assertOwnedPath(this.directory);
|
|
68
|
+
await mkdir(this.directory, { recursive: true, mode: 0o700 });
|
|
69
|
+
await this.withOwnerLock(async () => {
|
|
70
|
+
const owner = await this.owner();
|
|
71
|
+
if (owner) {
|
|
72
|
+
const alive = [owner.pid, ...owner.processes].some((pid) => {
|
|
73
|
+
try {
|
|
74
|
+
process.kill(pid, 0);
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
return !(error instanceof Error && "code" in error && error.code === "ESRCH");
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
if (alive)
|
|
82
|
+
throw new Error("Desktop busy: another lunR workflow owns this user's desktop. Retry only after it ends.");
|
|
83
|
+
}
|
|
84
|
+
await this.writeOwner({ pid: process.pid, identity: this.identity });
|
|
85
|
+
this.owned = true;
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
async trackProcess(pid) {
|
|
89
|
+
await this.withOwnerLock(async () => {
|
|
90
|
+
const owner = await this.owner();
|
|
91
|
+
if (!owner || owner.identity !== this.identity || !Number.isInteger(pid) || pid < 1)
|
|
92
|
+
throw new Error("Cannot bind runtime to the desktop lease.");
|
|
93
|
+
if (!owner.processes.includes(pid))
|
|
94
|
+
owner.processes.push(pid);
|
|
95
|
+
await this.writeOwner(owner);
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
run(operation, signal) {
|
|
99
|
+
const result = this.tail.then(async () => {
|
|
100
|
+
if (this.closed)
|
|
101
|
+
throw new Error("Desktop workflow ended. Start a new workflow and observe again.");
|
|
102
|
+
signal?.throwIfAborted();
|
|
103
|
+
if (!this.owned)
|
|
104
|
+
await this.acquire();
|
|
105
|
+
if ((await this.owner())?.identity !== this.identity) {
|
|
106
|
+
this.closed = true;
|
|
107
|
+
this.controller.abort(new Error("Desktop lease lost."));
|
|
108
|
+
throw new Error("Desktop lease lost. Input refused.");
|
|
109
|
+
}
|
|
110
|
+
signal?.throwIfAborted();
|
|
111
|
+
return operation();
|
|
112
|
+
});
|
|
113
|
+
this.tail = result.catch(() => undefined);
|
|
114
|
+
return result;
|
|
115
|
+
}
|
|
116
|
+
async close() {
|
|
117
|
+
this.closed = true;
|
|
118
|
+
this.controller.abort();
|
|
119
|
+
await this.tail;
|
|
120
|
+
if (this.owned) {
|
|
121
|
+
await this.withOwnerLock(async () => {
|
|
122
|
+
if ((await this.owner())?.identity === this.identity)
|
|
123
|
+
await rm(join(this.directory, "workflow-owner.json"));
|
|
124
|
+
});
|
|
125
|
+
this.owned = false;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
//# sourceMappingURL=lease.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lease.js","sourceRoot":"","sources":["../../../src/core/computer-use/lease.ts"],"names":[],"mappings":"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","sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport { mkdir, readFile, rename, rm, writeFile } from \"node:fs/promises\";\nimport { userInfo } from \"node:os\";\nimport { join } from \"node:path\";\nimport lockfile from \"proper-lockfile\";\nimport { assertOwnedPath } from \"./runtime.ts\";\n\nexport class DesktopLease {\n\tprivate readonly identity = randomUUID();\n\tprivate owned = false;\n\tprivate tail: Promise<unknown> = Promise.resolve();\n\tprivate closed = false;\n\tprivate readonly controller = new AbortController();\n\tget signal(): AbortSignal {\n\t\treturn this.controller.signal;\n\t}\n\tprivate readonly directory: string;\n\tconstructor(directory = join(userInfo().homedir, \".lunr\", \"desktop\")) {\n\t\tthis.directory = directory;\n\t}\n\n\tprivate async owner(): Promise<{ pid: number; identity: string; processes: number[] } | undefined> {\n\t\ttry {\n\t\t\tconst value: unknown = JSON.parse(await readFile(join(this.directory, \"workflow-owner.json\"), \"utf8\"));\n\t\t\tif (\n\t\t\t\t!value ||\n\t\t\t\ttypeof value !== \"object\" ||\n\t\t\t\t!(\"pid\" in value) ||\n\t\t\t\ttypeof value.pid !== \"number\" ||\n\t\t\t\t!Number.isInteger(value.pid) ||\n\t\t\t\tvalue.pid < 1 ||\n\t\t\t\t!(\"identity\" in value) ||\n\t\t\t\ttypeof value.identity !== \"string\"\n\t\t\t)\n\t\t\t\tthrow new Error(\"Invalid desktop owner record; refusing takeover.\");\n\t\t\tconst processes = \"processes\" in value ? value.processes : [];\n\t\t\tif (!Array.isArray(processes) || !processes.every((pid) => Number.isInteger(pid) && pid > 0))\n\t\t\t\tthrow new Error(\"Invalid runtime owner record; refusing takeover.\");\n\t\t\treturn { pid: value.pid, identity: value.identity, processes };\n\t\t} catch (error) {\n\t\t\tif (error instanceof Error && \"code\" in error && error.code === \"ENOENT\") return undefined;\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\tprivate async withOwnerLock(operation: () => Promise<void>): Promise<void> {\n\t\tconst release = await lockfile.lock(this.directory, {\n\t\t\tlockfilePath: join(this.directory, \"workflow-acquire.lock\"),\n\t\t\tretries: 0,\n\t\t\tstale: 10000,\n\t\t});\n\t\ttry {\n\t\t\tawait operation();\n\t\t} finally {\n\t\t\tawait release();\n\t\t}\n\t}\n\n\tprivate async writeOwner(owner: { pid: number; identity: string; processes?: number[] }): Promise<void> {\n\t\tconst temporary = join(this.directory, `workflow-owner-${randomUUID()}.tmp`);\n\t\ttry {\n\t\t\tawait writeFile(temporary, JSON.stringify(owner), { mode: 0o600, flag: \"wx\" });\n\t\t\tawait rename(temporary, join(this.directory, \"workflow-owner.json\"));\n\t\t} finally {\n\t\t\tawait rm(temporary, { force: true });\n\t\t}\n\t}\n\n\tprivate async acquire(): Promise<void> {\n\t\tawait assertOwnedPath(this.directory);\n\t\tawait mkdir(this.directory, { recursive: true, mode: 0o700 });\n\t\tawait this.withOwnerLock(async () => {\n\t\t\tconst owner = await this.owner();\n\t\t\tif (owner) {\n\t\t\t\tconst alive = [owner.pid, ...owner.processes].some((pid) => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tprocess.kill(pid, 0);\n\t\t\t\t\t\treturn true;\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\treturn !(error instanceof Error && \"code\" in error && error.code === \"ESRCH\");\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t\tif (alive)\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t\"Desktop busy: another lunR workflow owns this user's desktop. Retry only after it ends.\",\n\t\t\t\t\t);\n\t\t\t}\n\t\t\tawait this.writeOwner({ pid: process.pid, identity: this.identity });\n\t\t\tthis.owned = true;\n\t\t});\n\t}\n\n\tasync trackProcess(pid: number): Promise<void> {\n\t\tawait this.withOwnerLock(async () => {\n\t\t\tconst owner = await this.owner();\n\t\t\tif (!owner || owner.identity !== this.identity || !Number.isInteger(pid) || pid < 1)\n\t\t\t\tthrow new Error(\"Cannot bind runtime to the desktop lease.\");\n\t\t\tif (!owner.processes.includes(pid)) owner.processes.push(pid);\n\t\t\tawait this.writeOwner(owner);\n\t\t});\n\t}\n\n\trun<T>(operation: () => Promise<T>, signal?: AbortSignal): Promise<T> {\n\t\tconst result = this.tail.then(async () => {\n\t\t\tif (this.closed) throw new Error(\"Desktop workflow ended. Start a new workflow and observe again.\");\n\t\t\tsignal?.throwIfAborted();\n\t\t\tif (!this.owned) await this.acquire();\n\t\t\tif ((await this.owner())?.identity !== this.identity) {\n\t\t\t\tthis.closed = true;\n\t\t\t\tthis.controller.abort(new Error(\"Desktop lease lost.\"));\n\t\t\t\tthrow new Error(\"Desktop lease lost. Input refused.\");\n\t\t\t}\n\t\t\tsignal?.throwIfAborted();\n\t\t\treturn operation();\n\t\t});\n\t\tthis.tail = result.catch(() => undefined);\n\t\treturn result;\n\t}\n\n\tasync close(): Promise<void> {\n\t\tthis.closed = true;\n\t\tthis.controller.abort();\n\t\tawait this.tail;\n\t\tif (this.owned) {\n\t\t\tawait this.withOwnerLock(async () => {\n\t\t\t\tif ((await this.owner())?.identity === this.identity) await rm(join(this.directory, \"workflow-owner.json\"));\n\t\t\t});\n\t\t\tthis.owned = false;\n\t\t}\n\t}\n}\n"]}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export declare function macLaunchArguments(app: string, socket: string, home: string, lifetime: string): string[];
|
|
2
|
+
export declare class MacRuntime {
|
|
3
|
+
private directory?;
|
|
4
|
+
private lifetime?;
|
|
5
|
+
private exited?;
|
|
6
|
+
private pid?;
|
|
7
|
+
get processId(): number | undefined;
|
|
8
|
+
private socket?;
|
|
9
|
+
private closing?;
|
|
10
|
+
start(app: string, signal: AbortSignal): Promise<string>;
|
|
11
|
+
close(): Promise<void>;
|
|
12
|
+
private stop;
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=macos-runtime.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"macos-runtime.d.ts","sourceRoot":"","sources":["../../../src/core/computer-use/macos-runtime.ts"],"names":[],"mappings":"AAYA,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE,CAwBxG;AAoCD,qBAAa,UAAU;IACtB,OAAO,CAAC,SAAS,CAAC,CAAS;IAC3B,OAAO,CAAC,QAAQ,CAAC,CAAa;IAC9B,OAAO,CAAC,MAAM,CAAC,CAAgB;IAC/B,OAAO,CAAC,GAAG,CAAC,CAAS;IACrB,IAAI,SAAS,IAAI,MAAM,GAAG,SAAS,CAElC;IACD,OAAO,CAAC,MAAM,CAAC,CAAS;IACxB,OAAO,CAAC,OAAO,CAAC,CAAgB;IAE1B,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC,CA8C7D;IAED,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAGrB;YAEa,IAAI;CAoBlB","sourcesContent":["import { execFile, spawn } from \"node:child_process\";\nimport { constants } from \"node:fs\";\nimport { type FileHandle, mkdtemp, open, realpath, rm } from \"node:fs/promises\";\nimport { connect } from \"node:net\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { setTimeout as delay } from \"node:timers/promises\";\nimport { promisify } from \"node:util\";\nimport { CUA_VERSION, runtimeEnvironment } from \"./runtime.ts\";\n\nconst exec = promisify(execFile);\n\nexport function macLaunchArguments(app: string, socket: string, home: string, lifetime: string): string[] {\n\tconst environment = {\n\t\tHOME: home,\n\t\t...Object.fromEntries(Object.entries(runtimeEnvironment()).filter(([name]) => name.startsWith(\"CUA_\"))),\n\t};\n\treturn [\n\t\t\"-n\",\n\t\t\"-g\",\n\t\t\"-W\",\n\t\t\"-a\",\n\t\tapp,\n\t\t\"--stdin\",\n\t\tlifetime,\n\t\t...Object.entries(environment).flatMap(([name, value]) => [\"--env\", `${name}=${value}`]),\n\t\t\"--args\",\n\t\t\"serve\",\n\t\t\"--embedded\",\n\t\t\"--parent-liveness-stdio\",\n\t\t\"--socket\",\n\t\tsocket,\n\t\t\"--permission-mode\",\n\t\t\"standard\",\n\t\t\"--no-overlay\",\n\t];\n}\n\nfunction request(socket: string, method: string, args?: Record<string, unknown>): Promise<Record<string, unknown>> {\n\treturn new Promise((resolve, reject) => {\n\t\tconst stream = connect(socket);\n\t\tlet buffer = \"\";\n\t\tstream.setTimeout(1000, () => stream.destroy(new Error(\"Private computer endpoint timed out.\")));\n\t\tstream.on(\"error\", reject);\n\t\tstream.on(\"connect\", () => stream.write(`${JSON.stringify({ method, args })}\\n`));\n\t\tstream.on(\"data\", (chunk) => {\n\t\t\tbuffer += chunk.toString();\n\t\t\tif (buffer.length > 65536) {\n\t\t\t\tstream.destroy(new Error(\"Invalid private endpoint metadata.\"));\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (!buffer.includes(\"\\n\")) return;\n\t\t\ttry {\n\t\t\t\tconst reply: unknown = JSON.parse(buffer.slice(0, buffer.indexOf(\"\\n\")));\n\t\t\t\tif (\n\t\t\t\t\t!reply ||\n\t\t\t\t\ttypeof reply !== \"object\" ||\n\t\t\t\t\t!(\"result\" in reply) ||\n\t\t\t\t\t!reply.result ||\n\t\t\t\t\ttypeof reply.result !== \"object\"\n\t\t\t\t)\n\t\t\t\t\tthrow new Error(\"Private endpoint refused metadata.\");\n\t\t\t\tresolve(Object.fromEntries(Object.entries(reply.result)));\n\t\t\t} catch (error) {\n\t\t\t\treject(error);\n\t\t\t}\n\t\t\tstream.destroy();\n\t\t});\n\t\tstream.on(\"end\", () => reject(new Error(\"Private computer endpoint closed.\")));\n\t});\n}\n\nexport class MacRuntime {\n\tprivate directory?: string;\n\tprivate lifetime?: FileHandle;\n\tprivate exited?: Promise<void>;\n\tprivate pid?: number;\n\tget processId(): number | undefined {\n\t\treturn this.pid;\n\t}\n\tprivate socket?: string;\n\tprivate closing?: Promise<void>;\n\n\tasync start(app: string, signal: AbortSignal): Promise<string> {\n\t\tconst directory = await mkdtemp(join(await realpath(tmpdir()), \"lunr-cua-\"));\n\t\tthis.directory = directory;\n\t\tconst socket = join(directory, \"driver.sock\");\n\t\tthis.socket = socket;\n\t\tconst lifetime = join(directory, \"lifetime\");\n\t\tawait exec(\"/usr/bin/mkfifo\", [\"-m\", \"600\", lifetime], { timeout: 5000, signal });\n\t\tthis.lifetime = await open(lifetime, constants.O_RDWR);\n\t\tsignal.throwIfAborted();\n\t\tconst launcher = spawn(\"/usr/bin/open\", macLaunchArguments(app, socket, directory, lifetime), {\n\t\t\tenv: runtimeEnvironment(),\n\t\t\tstdio: \"ignore\",\n\t\t});\n\t\tthis.exited = new Promise<void>((resolve, reject) => {\n\t\t\tlauncher.once(\"error\", reject);\n\t\t\tlauncher.once(\"exit\", (code) =>\n\t\t\t\tcode === 0 ? resolve() : reject(new Error(`LaunchServices exited ${code}.`)),\n\t\t\t);\n\t\t});\n\t\tvoid this.exited.catch(() => undefined);\n\t\tconst deadline = Date.now() + 15000;\n\t\twhile (Date.now() < deadline) {\n\t\t\tsignal.throwIfAborted();\n\t\t\ttry {\n\t\t\t\tconst metadata = await request(socket, \"metadata\");\n\t\t\t\tif (\n\t\t\t\t\tmetadata.driver_version !== CUA_VERSION ||\n\t\t\t\t\tmetadata.embedded !== true ||\n\t\t\t\t\ttypeof metadata.pid !== \"number\" ||\n\t\t\t\t\tmetadata.pid < 1\n\t\t\t\t)\n\t\t\t\t\tthrow new Error(\"Private runtime has incompatible metadata.\");\n\t\t\t\tthis.pid = metadata.pid;\n\t\t\t\treturn socket;\n\t\t\t} catch {\n\t\t\t\tawait Promise.race([\n\t\t\t\t\tdelay(100, undefined, { signal }),\n\t\t\t\t\tthis.exited.then(() => {\n\t\t\t\t\t\tthrow new Error(\"Computer app exited during startup.\");\n\t\t\t\t\t}),\n\t\t\t\t]);\n\t\t\t}\n\t\t}\n\t\tthrow new Error(\n\t\t\t\"Private macOS runtime startup timed out. Check CuaDriver Accessibility and Screen Recording grants.\",\n\t\t);\n\t}\n\n\tclose(): Promise<void> {\n\t\tthis.closing ??= this.stop();\n\t\treturn this.closing;\n\t}\n\n\tprivate async stop(): Promise<void> {\n\t\tawait this.lifetime?.close();\n\t\tthis.lifetime = undefined;\n\t\tif (this.socket && this.pid)\n\t\t\tawait request(this.socket, \"shutdown_if_pid\", { expected_pid: this.pid }).catch(() => undefined);\n\t\tif (this.exited) {\n\t\t\tconst timeout = new AbortController();\n\t\t\ttry {\n\t\t\t\tawait Promise.race([\n\t\t\t\t\tthis.exited,\n\t\t\t\t\tdelay(10000, undefined, { signal: timeout.signal }).then(() => {\n\t\t\t\t\t\tthrow new Error(\"Owned macOS app shutdown is unconfirmed. Desktop lease retained.\");\n\t\t\t\t\t}),\n\t\t\t\t]);\n\t\t\t} finally {\n\t\t\t\ttimeout.abort();\n\t\t\t}\n\t\t}\n\t\tif (this.directory) await rm(this.directory, { recursive: true, force: true });\n\t}\n}\n"]}
|