@ai-agent-forge/plugin-desktop-macos-observe 0.88.3
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/MANUAL.md +19 -0
- package/README.md +49 -0
- package/agent-forge.json +27 -0
- package/dist/capability.d.ts +13 -0
- package/dist/capability.d.ts.map +1 -0
- package/dist/capability.js +97 -0
- package/dist/capability.js.map +1 -0
- package/dist/entry.d.ts +13 -0
- package/dist/entry.d.ts.map +1 -0
- package/dist/entry.js +14 -0
- package/dist/entry.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/macos-ax-adapter.d.ts +57 -0
- package/dist/macos-ax-adapter.d.ts.map +1 -0
- package/dist/macos-ax-adapter.js +342 -0
- package/dist/macos-ax-adapter.js.map +1 -0
- package/dist/projection.d.ts +114 -0
- package/dist/projection.d.ts.map +1 -0
- package/dist/projection.js +226 -0
- package/dist/projection.js.map +1 -0
- package/dist/shared.d.ts +15 -0
- package/dist/shared.d.ts.map +1 -0
- package/dist/shared.js +30 -0
- package/dist/shared.js.map +1 -0
- package/dist/types.d.ts +223 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +21 -0
- package/dist/types.js.map +1 -0
- package/package.json +57 -0
- package/plugin.json +25 -0
package/MANUAL.md
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# desktop-macos-observe 能力手册(AI 用)
|
|
2
|
+
|
|
3
|
+
macOS 桌面观察:AX(Accessibility)枚举窗口与元素树——窗口标题/位置/大小、元素 role/value/坐标、bundle id 定位。只读,零输入。
|
|
4
|
+
|
|
5
|
+
## 工具速查
|
|
6
|
+
|
|
7
|
+
| 工具 | 用途 |
|
|
8
|
+
| --- | --- |
|
|
9
|
+
| desktop_probe | 探测驱动可用性(darwin + osascript + AX 授权) |
|
|
10
|
+
| desktop_session_open | 开观察会话(applicationId 可选 bundle id,如 com.apple.Safari) |
|
|
11
|
+
| desktop_observe / desktop_reobserve | 全量/增量枚举 |
|
|
12
|
+
| desktop_session_stop | 关会话 |
|
|
13
|
+
|
|
14
|
+
## 使用要点
|
|
15
|
+
|
|
16
|
+
1. **SSH/后台 launchd 会话下 Apple events 慢 100 倍**:默认超时已放宽到 60s,按名定位失败(-2700)时改 bundle id 定位。
|
|
17
|
+
2. **TCC 权限**:AX 操作的授权对象是 `/usr/bin/osascript` 本身,不是 sshd;未授权时 -25201/-25211,让用户在 系统设置→隐私与安全性→辅助功能 里加白 osascript。
|
|
18
|
+
3. **锁屏时窗口从 AX 树消失**(进程在、窗口 0)——不是驱动故障,请用户解锁。
|
|
19
|
+
4. Safari/Chrome 的页面内容经 AXWebArea 暴露,可元素级读取;配合 desktop-macos-input 的 CGEvent 真实点击形成浏览器桌面通道。
|
package/README.md
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# @agent-forge/plugin-desktop-macos-observe
|
|
2
|
+
|
|
3
|
+
Read-only macOS desktop observation capability plugin for [Agent Forge](https://github.com/agent-forge/agent-forge), structured as the macOS
|
|
4
|
+
counterpart of `plugins/desktop-windows-observe`. It registers five read-only tools — `desktop_probe`, `desktop_session_open`, `desktop_observe`,
|
|
5
|
+
`desktop_reobserve`, `desktop_session_stop` — backed by the macOS Accessibility tree through a JXA driver (`osascript -l JavaScript`). It never
|
|
6
|
+
registers an input or replay tool.
|
|
7
|
+
|
|
8
|
+
The plugin ships its own osascript/JXA adapter. On non-macOS platforms importing the package does not throw; every tool reports an explicit
|
|
9
|
+
`unsupported` result instead of probing or falling back to a different driver. A missing Accessibility (assistive access) grant is reported
|
|
10
|
+
fail-closed as the probe reason `accessibility-permission-required`; there is no degraded observation mode.
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
```sh
|
|
15
|
+
# from a local checkout
|
|
16
|
+
agent-forge plugin install ./plugins/desktop-macos-observe
|
|
17
|
+
|
|
18
|
+
# from npm
|
|
19
|
+
agent-forge plugin install @agent-forge/plugin-desktop-macos-observe
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
The package ships two manifests: `agent-forge.json` (ecosystem manifest for the deterministic plugin resolver, `platform.os: ["macos"]`) and
|
|
23
|
+
`plugin.json` (host-load manifest consumed by `loadCapabilityPluginFromManifest`, entry `./dist/entry.js`).
|
|
24
|
+
|
|
25
|
+
## Library surface
|
|
26
|
+
|
|
27
|
+
`import { DesktopMacosAdapter, MacosAccessibilityDriver } from "@agent-forge/plugin-desktop-macos-observe";`
|
|
28
|
+
|
|
29
|
+
- `DesktopMacosAdapter` — read-only Accessibility adapter facade.
|
|
30
|
+
- `MacosAccessibilityDriver(options?)` — JXA driver; supports `command`/`timeoutMs`/`spawnSync`/`platform` injection for brokers and tests.
|
|
31
|
+
- `createDesktopObserveCapabilityFactoryV1(runtime)` — registers the five observation tools on a `CapabilityAPI`.
|
|
32
|
+
- Default export of `dist/entry.js` — the precomposed factory used by external plugin loading.
|
|
33
|
+
|
|
34
|
+
The observation projection (window/control identity, budgets, truncation) is a deliberate inlined copy shared with
|
|
35
|
+
`@agent-forge/plugin-desktop-macos-input` so both packages stay self-contained.
|
|
36
|
+
|
|
37
|
+
## Real-host status
|
|
38
|
+
|
|
39
|
+
The driver and adapter are contract-tested with an injected fake `spawnSync` (canned JSON, no real desktop). Real darwin behavior — the JXA
|
|
40
|
+
enumeration script, Accessibility permission classification and observed window/control fidelity — is verified only by the explicitly gated
|
|
41
|
+
test (`AGENT_FORGE_MACOS_AX_REAL_TEST=1` on macOS) and has not been validated on a real macOS host as of this change.
|
|
42
|
+
|
|
43
|
+
## Development
|
|
44
|
+
|
|
45
|
+
```sh
|
|
46
|
+
npm install --ignore-scripts # repo root, creates the workspace symlink
|
|
47
|
+
npm run build # plugins/desktop-macos-observe (tsgo)
|
|
48
|
+
npm test # vitest; the entry fidelity test requires `npm run build` first
|
|
49
|
+
```
|
package/agent-forge.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"packageFormatVersion": 1,
|
|
3
|
+
"id": "agent-forge.plugin.desktop-macos-observe",
|
|
4
|
+
"pluginVersion": "0.88.3",
|
|
5
|
+
"apiVersion": "1",
|
|
6
|
+
"minHostVersion": "0.84.4",
|
|
7
|
+
"capabilityRefs": [
|
|
8
|
+
{
|
|
9
|
+
"id": "desktop-session",
|
|
10
|
+
"version": 1
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"id": "desktop-uia",
|
|
14
|
+
"version": 1
|
|
15
|
+
}
|
|
16
|
+
],
|
|
17
|
+
"platform": {
|
|
18
|
+
"os": [
|
|
19
|
+
"macos"
|
|
20
|
+
],
|
|
21
|
+
"runtime": [
|
|
22
|
+
{
|
|
23
|
+
"name": "node"
|
|
24
|
+
}
|
|
25
|
+
]
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read-only desktop observation capability factory, mirroring the Windows
|
|
3
|
+
* package's `capability.ts` (migrated from agent-forge
|
|
4
|
+
* `src/profile/runtime-capabilities-desktop.ts`, `desktopFactory`, D-075 S3
|
|
5
|
+
* extraction). Differences from the Windows copy:
|
|
6
|
+
* - The runtime type is the macOS read-only adapter from `types.ts`.
|
|
7
|
+
* - The probe projection reports `driver: "macos-ax"`.
|
|
8
|
+
* Tool names, parameters, budgets and result shapes are unchanged.
|
|
9
|
+
*/
|
|
10
|
+
import type { CapabilityPluginSyncFactory } from "@agent-forge/plugin-sdk";
|
|
11
|
+
import type { DesktopReadonlyAdapterV1 } from "./types.ts";
|
|
12
|
+
export declare function createDesktopObserveCapabilityFactoryV1(runtime: DesktopReadonlyAdapterV1): CapabilityPluginSyncFactory;
|
|
13
|
+
//# sourceMappingURL=capability.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"capability.d.ts","sourceRoot":"","sources":["../src/capability.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,yBAAyB,CAAC;AAa3E,OAAO,KAAK,EAAE,wBAAwB,EAA2B,MAAM,YAAY,CAAC;AAEpF,wBAAgB,uCAAuC,CACtD,OAAO,EAAE,wBAAwB,GAC/B,2BAA2B,CAsH7B","sourcesContent":["/**\n * Read-only desktop observation capability factory, mirroring the Windows\n * package's `capability.ts` (migrated from agent-forge\n * `src/profile/runtime-capabilities-desktop.ts`, `desktopFactory`, D-075 S3\n * extraction). Differences from the Windows copy:\n * - The runtime type is the macOS read-only adapter from `types.ts`.\n * - The probe projection reports `driver: \"macos-ax\"`.\n * Tool names, parameters, budgets and result shapes are unchanged.\n */\nimport type { CapabilityPluginSyncFactory } from \"@agent-forge/plugin-sdk\";\nimport { Type } from \"typebox\";\nimport {\n\tDESKTOP_MAX_STRING_CHARS_V1,\n\tdesktopCapabilities,\n\tdesktopClampedString,\n\tdesktopFailure,\n\tdesktopObservationResult,\n\tdesktopResultWithinBudget,\n\tdesktopSessionResult,\n\tdesktopStopResult,\n} from \"./projection.ts\";\nimport { nonEmptyString, signalFrom, toolInvocation } from \"./shared.ts\";\nimport type { DesktopReadonlyAdapterV1, DesktopSessionRequestV1 } from \"./types.ts\";\n\nexport function createDesktopObserveCapabilityFactoryV1(\n\truntime: DesktopReadonlyAdapterV1,\n): CapabilityPluginSyncFactory {\n\treturn (api) => {\n\t\tconst invoke = <T>(\n\t\t\toperationId: string,\n\t\t\taction: () => T,\n\t\t\tproject: (value: T) => unknown,\n\t\t\tsignal?: AbortSignal,\n\t\t) => {\n\t\t\tif (signal?.aborted)\n\t\t\t\treturn Promise.resolve(desktopFailure(operationId, \"cancelled\", \"Desktop operation was cancelled\"));\n\t\t\ttry {\n\t\t\t\tconst value = project(action());\n\t\t\t\treturn Promise.resolve(\n\t\t\t\t\tsignal?.aborted ? desktopFailure(operationId, \"cancelled\", \"Desktop operation was cancelled\") : value,\n\t\t\t\t);\n\t\t\t} catch (error) {\n\t\t\t\t// 透传原始诊断(dogfood 2026-09-25:无信息报错让一次观察失败的排障\n\t\t\t\t// 成本极高);code 仍是适配器/投影面失败。\n\t\t\t\tconst detail = error instanceof Error ? error.message : String(error);\n\t\t\t\treturn Promise.resolve(desktopFailure(operationId, \"desktop_adapter_failed\", detail));\n\t\t\t}\n\t\t};\n\t\tapi.registerTool({\n\t\t\tname: \"desktop_probe\",\n\t\t\tlabel: \"Desktop probe\",\n\t\t\tdescription: \"Probe whether the selected macOS Accessibility adapter is available\",\n\t\t\tparameters: Type.Object({}),\n\t\t\texecute: async (first: unknown, second?: unknown, third?: unknown) => {\n\t\t\t\tconst { operationId } = toolInvocation(first, second);\n\t\t\t\treturn invoke(\n\t\t\t\t\toperationId,\n\t\t\t\t\t() => runtime.probe(),\n\t\t\t\t\t(probe) => {\n\t\t\t\t\t\tconst capabilities = desktopCapabilities(probe.capabilities);\n\t\t\t\t\t\treturn desktopResultWithinBudget({\n\t\t\t\t\t\t\tversion: 1,\n\t\t\t\t\t\t\toperationId,\n\t\t\t\t\t\t\tstatus: probe.available ? (\"succeeded\" as const) : (\"unsupported\" as const),\n\t\t\t\t\t\t\tprobe: {\n\t\t\t\t\t\t\t\tavailable: probe.available,\n\t\t\t\t\t\t\t\tplatform: desktopClampedString(probe.platform, \"platform\"),\n\t\t\t\t\t\t\t\tdriver: \"macos-ax\" as const,\n\t\t\t\t\t\t\t\t...(capabilities === undefined ? {} : { capabilities }),\n\t\t\t\t\t\t\t\t...(probe.reason === undefined ? {} : { reason: desktopClampedString(probe.reason, \"reason\") }),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t});\n\t\t\t\t\t},\n\t\t\t\t\tsignalFrom(third),\n\t\t\t\t);\n\t\t\t},\n\t\t});\n\t\tapi.registerTool({\n\t\t\tname: \"desktop_session_open\",\n\t\t\tlabel: \"Open desktop session\",\n\t\t\tdescription: \"Open a foreground or VM-scoped read-only macOS desktop session\",\n\t\t\tparameters: Type.Object({\n\t\t\t\tisolation: Type.Union([Type.Literal(\"foreground\"), Type.Literal(\"vm\")]),\n\t\t\t\tapplicationId: Type.Optional(Type.String({ minLength: 1, maxLength: DESKTOP_MAX_STRING_CHARS_V1 })),\n\t\t\t}),\n\t\t\texecute: async (first: unknown, second?: unknown, third?: unknown) => {\n\t\t\t\tconst { input, operationId } = toolInvocation(first, second);\n\t\t\t\tif (input.isolation !== \"foreground\" && input.isolation !== \"vm\")\n\t\t\t\t\tthrow new Error(\"isolation must be foreground or vm\");\n\t\t\t\tconst request: DesktopSessionRequestV1 = {\n\t\t\t\t\tversion: 1,\n\t\t\t\t\tisolation: input.isolation,\n\t\t\t\t\t...(input.applicationId === undefined\n\t\t\t\t\t\t? {}\n\t\t\t\t\t\t: { applicationId: nonEmptyString(input.applicationId, \"applicationId\") }),\n\t\t\t\t};\n\t\t\t\treturn invoke(\n\t\t\t\t\toperationId,\n\t\t\t\t\t() => runtime.openSession(request),\n\t\t\t\t\t(value) => desktopSessionResult(value, operationId),\n\t\t\t\t\tsignalFrom(third),\n\t\t\t\t);\n\t\t\t},\n\t\t});\n\t\tfor (const [name, action] of [\n\t\t\t[\"desktop_observe\", \"observe\"],\n\t\t\t[\"desktop_reobserve\", \"reobserve\"],\n\t\t] as const) {\n\t\t\tapi.registerTool({\n\t\t\t\tname,\n\t\t\t\tlabel: name === \"desktop_observe\" ? \"Observe desktop\" : \"Re-observe desktop\",\n\t\t\t\tdescription: \"Return a bounded macOS Accessibility observation for an existing session\",\n\t\t\t\tparameters: Type.Object({\n\t\t\t\t\tsessionId: Type.String({ minLength: 1, maxLength: DESKTOP_MAX_STRING_CHARS_V1 }),\n\t\t\t\t}),\n\t\t\t\texecute: async (first: unknown, second?: unknown, third?: unknown) => {\n\t\t\t\t\tconst { input, operationId } = toolInvocation(first, second);\n\t\t\t\t\tconst sessionId = nonEmptyString(input.sessionId, \"sessionId\");\n\t\t\t\t\treturn invoke(\n\t\t\t\t\t\toperationId,\n\t\t\t\t\t\t() => runtime[action](sessionId),\n\t\t\t\t\t\t(value) => desktopObservationResult(value, operationId),\n\t\t\t\t\t\tsignalFrom(third),\n\t\t\t\t\t);\n\t\t\t\t},\n\t\t\t});\n\t\t}\n\t\tapi.registerTool({\n\t\t\tname: \"desktop_session_stop\",\n\t\t\tlabel: \"Stop desktop session\",\n\t\t\tdescription: \"Stop an existing read-only desktop session\",\n\t\t\tparameters: Type.Object({ sessionId: Type.String({ minLength: 1, maxLength: DESKTOP_MAX_STRING_CHARS_V1 }) }),\n\t\t\texecute: async (first: unknown, second?: unknown, third?: unknown) => {\n\t\t\t\tconst { input, operationId } = toolInvocation(first, second);\n\t\t\t\tconst sessionId = nonEmptyString(input.sessionId, \"sessionId\");\n\t\t\t\treturn invoke(\n\t\t\t\t\toperationId,\n\t\t\t\t\t() => runtime.stop(sessionId),\n\t\t\t\t\t(value) => desktopStopResult(value, operationId),\n\t\t\t\t\tsignalFrom(third),\n\t\t\t\t);\n\t\t\t},\n\t\t});\n\t};\n}\n"]}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { Type } from "typebox";
|
|
2
|
+
import { DESKTOP_MAX_STRING_CHARS_V1, desktopCapabilities, desktopClampedString, desktopFailure, desktopObservationResult, desktopResultWithinBudget, desktopSessionResult, desktopStopResult, } from "./projection.js";
|
|
3
|
+
import { nonEmptyString, signalFrom, toolInvocation } from "./shared.js";
|
|
4
|
+
export function createDesktopObserveCapabilityFactoryV1(runtime) {
|
|
5
|
+
return (api) => {
|
|
6
|
+
const invoke = (operationId, action, project, signal) => {
|
|
7
|
+
if (signal?.aborted)
|
|
8
|
+
return Promise.resolve(desktopFailure(operationId, "cancelled", "Desktop operation was cancelled"));
|
|
9
|
+
try {
|
|
10
|
+
const value = project(action());
|
|
11
|
+
return Promise.resolve(signal?.aborted ? desktopFailure(operationId, "cancelled", "Desktop operation was cancelled") : value);
|
|
12
|
+
}
|
|
13
|
+
catch (error) {
|
|
14
|
+
// 透传原始诊断(dogfood 2026-09-25:无信息报错让一次观察失败的排障
|
|
15
|
+
// 成本极高);code 仍是适配器/投影面失败。
|
|
16
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
17
|
+
return Promise.resolve(desktopFailure(operationId, "desktop_adapter_failed", detail));
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
api.registerTool({
|
|
21
|
+
name: "desktop_probe",
|
|
22
|
+
label: "Desktop probe",
|
|
23
|
+
description: "Probe whether the selected macOS Accessibility adapter is available",
|
|
24
|
+
parameters: Type.Object({}),
|
|
25
|
+
execute: async (first, second, third) => {
|
|
26
|
+
const { operationId } = toolInvocation(first, second);
|
|
27
|
+
return invoke(operationId, () => runtime.probe(), (probe) => {
|
|
28
|
+
const capabilities = desktopCapabilities(probe.capabilities);
|
|
29
|
+
return desktopResultWithinBudget({
|
|
30
|
+
version: 1,
|
|
31
|
+
operationId,
|
|
32
|
+
status: probe.available ? "succeeded" : "unsupported",
|
|
33
|
+
probe: {
|
|
34
|
+
available: probe.available,
|
|
35
|
+
platform: desktopClampedString(probe.platform, "platform"),
|
|
36
|
+
driver: "macos-ax",
|
|
37
|
+
...(capabilities === undefined ? {} : { capabilities }),
|
|
38
|
+
...(probe.reason === undefined ? {} : { reason: desktopClampedString(probe.reason, "reason") }),
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
}, signalFrom(third));
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
api.registerTool({
|
|
45
|
+
name: "desktop_session_open",
|
|
46
|
+
label: "Open desktop session",
|
|
47
|
+
description: "Open a foreground or VM-scoped read-only macOS desktop session",
|
|
48
|
+
parameters: Type.Object({
|
|
49
|
+
isolation: Type.Union([Type.Literal("foreground"), Type.Literal("vm")]),
|
|
50
|
+
applicationId: Type.Optional(Type.String({ minLength: 1, maxLength: DESKTOP_MAX_STRING_CHARS_V1 })),
|
|
51
|
+
}),
|
|
52
|
+
execute: async (first, second, third) => {
|
|
53
|
+
const { input, operationId } = toolInvocation(first, second);
|
|
54
|
+
if (input.isolation !== "foreground" && input.isolation !== "vm")
|
|
55
|
+
throw new Error("isolation must be foreground or vm");
|
|
56
|
+
const request = {
|
|
57
|
+
version: 1,
|
|
58
|
+
isolation: input.isolation,
|
|
59
|
+
...(input.applicationId === undefined
|
|
60
|
+
? {}
|
|
61
|
+
: { applicationId: nonEmptyString(input.applicationId, "applicationId") }),
|
|
62
|
+
};
|
|
63
|
+
return invoke(operationId, () => runtime.openSession(request), (value) => desktopSessionResult(value, operationId), signalFrom(third));
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
for (const [name, action] of [
|
|
67
|
+
["desktop_observe", "observe"],
|
|
68
|
+
["desktop_reobserve", "reobserve"],
|
|
69
|
+
]) {
|
|
70
|
+
api.registerTool({
|
|
71
|
+
name,
|
|
72
|
+
label: name === "desktop_observe" ? "Observe desktop" : "Re-observe desktop",
|
|
73
|
+
description: "Return a bounded macOS Accessibility observation for an existing session",
|
|
74
|
+
parameters: Type.Object({
|
|
75
|
+
sessionId: Type.String({ minLength: 1, maxLength: DESKTOP_MAX_STRING_CHARS_V1 }),
|
|
76
|
+
}),
|
|
77
|
+
execute: async (first, second, third) => {
|
|
78
|
+
const { input, operationId } = toolInvocation(first, second);
|
|
79
|
+
const sessionId = nonEmptyString(input.sessionId, "sessionId");
|
|
80
|
+
return invoke(operationId, () => runtime[action](sessionId), (value) => desktopObservationResult(value, operationId), signalFrom(third));
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
api.registerTool({
|
|
85
|
+
name: "desktop_session_stop",
|
|
86
|
+
label: "Stop desktop session",
|
|
87
|
+
description: "Stop an existing read-only desktop session",
|
|
88
|
+
parameters: Type.Object({ sessionId: Type.String({ minLength: 1, maxLength: DESKTOP_MAX_STRING_CHARS_V1 }) }),
|
|
89
|
+
execute: async (first, second, third) => {
|
|
90
|
+
const { input, operationId } = toolInvocation(first, second);
|
|
91
|
+
const sessionId = nonEmptyString(input.sessionId, "sessionId");
|
|
92
|
+
return invoke(operationId, () => runtime.stop(sessionId), (value) => desktopStopResult(value, operationId), signalFrom(third));
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=capability.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"capability.js","sourceRoot":"","sources":["../src/capability.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Read-only desktop observation capability factory, mirroring the Windows\n * package's `capability.ts` (migrated from agent-forge\n * `src/profile/runtime-capabilities-desktop.ts`, `desktopFactory`, D-075 S3\n * extraction). Differences from the Windows copy:\n * - The runtime type is the macOS read-only adapter from `types.ts`.\n * - The probe projection reports `driver: \"macos-ax\"`.\n * Tool names, parameters, budgets and result shapes are unchanged.\n */\nimport type { CapabilityPluginSyncFactory } from \"@agent-forge/plugin-sdk\";\nimport { Type } from \"typebox\";\nimport {\n\tDESKTOP_MAX_STRING_CHARS_V1,\n\tdesktopCapabilities,\n\tdesktopClampedString,\n\tdesktopFailure,\n\tdesktopObservationResult,\n\tdesktopResultWithinBudget,\n\tdesktopSessionResult,\n\tdesktopStopResult,\n} from \"./projection.ts\";\nimport { nonEmptyString, signalFrom, toolInvocation } from \"./shared.ts\";\nimport type { DesktopReadonlyAdapterV1, DesktopSessionRequestV1 } from \"./types.ts\";\n\nexport function createDesktopObserveCapabilityFactoryV1(\n\truntime: DesktopReadonlyAdapterV1,\n): CapabilityPluginSyncFactory {\n\treturn (api) => {\n\t\tconst invoke = <T>(\n\t\t\toperationId: string,\n\t\t\taction: () => T,\n\t\t\tproject: (value: T) => unknown,\n\t\t\tsignal?: AbortSignal,\n\t\t) => {\n\t\t\tif (signal?.aborted)\n\t\t\t\treturn Promise.resolve(desktopFailure(operationId, \"cancelled\", \"Desktop operation was cancelled\"));\n\t\t\ttry {\n\t\t\t\tconst value = project(action());\n\t\t\t\treturn Promise.resolve(\n\t\t\t\t\tsignal?.aborted ? desktopFailure(operationId, \"cancelled\", \"Desktop operation was cancelled\") : value,\n\t\t\t\t);\n\t\t\t} catch (error) {\n\t\t\t\t// 透传原始诊断(dogfood 2026-09-25:无信息报错让一次观察失败的排障\n\t\t\t\t// 成本极高);code 仍是适配器/投影面失败。\n\t\t\t\tconst detail = error instanceof Error ? error.message : String(error);\n\t\t\t\treturn Promise.resolve(desktopFailure(operationId, \"desktop_adapter_failed\", detail));\n\t\t\t}\n\t\t};\n\t\tapi.registerTool({\n\t\t\tname: \"desktop_probe\",\n\t\t\tlabel: \"Desktop probe\",\n\t\t\tdescription: \"Probe whether the selected macOS Accessibility adapter is available\",\n\t\t\tparameters: Type.Object({}),\n\t\t\texecute: async (first: unknown, second?: unknown, third?: unknown) => {\n\t\t\t\tconst { operationId } = toolInvocation(first, second);\n\t\t\t\treturn invoke(\n\t\t\t\t\toperationId,\n\t\t\t\t\t() => runtime.probe(),\n\t\t\t\t\t(probe) => {\n\t\t\t\t\t\tconst capabilities = desktopCapabilities(probe.capabilities);\n\t\t\t\t\t\treturn desktopResultWithinBudget({\n\t\t\t\t\t\t\tversion: 1,\n\t\t\t\t\t\t\toperationId,\n\t\t\t\t\t\t\tstatus: probe.available ? (\"succeeded\" as const) : (\"unsupported\" as const),\n\t\t\t\t\t\t\tprobe: {\n\t\t\t\t\t\t\t\tavailable: probe.available,\n\t\t\t\t\t\t\t\tplatform: desktopClampedString(probe.platform, \"platform\"),\n\t\t\t\t\t\t\t\tdriver: \"macos-ax\" as const,\n\t\t\t\t\t\t\t\t...(capabilities === undefined ? {} : { capabilities }),\n\t\t\t\t\t\t\t\t...(probe.reason === undefined ? {} : { reason: desktopClampedString(probe.reason, \"reason\") }),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t});\n\t\t\t\t\t},\n\t\t\t\t\tsignalFrom(third),\n\t\t\t\t);\n\t\t\t},\n\t\t});\n\t\tapi.registerTool({\n\t\t\tname: \"desktop_session_open\",\n\t\t\tlabel: \"Open desktop session\",\n\t\t\tdescription: \"Open a foreground or VM-scoped read-only macOS desktop session\",\n\t\t\tparameters: Type.Object({\n\t\t\t\tisolation: Type.Union([Type.Literal(\"foreground\"), Type.Literal(\"vm\")]),\n\t\t\t\tapplicationId: Type.Optional(Type.String({ minLength: 1, maxLength: DESKTOP_MAX_STRING_CHARS_V1 })),\n\t\t\t}),\n\t\t\texecute: async (first: unknown, second?: unknown, third?: unknown) => {\n\t\t\t\tconst { input, operationId } = toolInvocation(first, second);\n\t\t\t\tif (input.isolation !== \"foreground\" && input.isolation !== \"vm\")\n\t\t\t\t\tthrow new Error(\"isolation must be foreground or vm\");\n\t\t\t\tconst request: DesktopSessionRequestV1 = {\n\t\t\t\t\tversion: 1,\n\t\t\t\t\tisolation: input.isolation,\n\t\t\t\t\t...(input.applicationId === undefined\n\t\t\t\t\t\t? {}\n\t\t\t\t\t\t: { applicationId: nonEmptyString(input.applicationId, \"applicationId\") }),\n\t\t\t\t};\n\t\t\t\treturn invoke(\n\t\t\t\t\toperationId,\n\t\t\t\t\t() => runtime.openSession(request),\n\t\t\t\t\t(value) => desktopSessionResult(value, operationId),\n\t\t\t\t\tsignalFrom(third),\n\t\t\t\t);\n\t\t\t},\n\t\t});\n\t\tfor (const [name, action] of [\n\t\t\t[\"desktop_observe\", \"observe\"],\n\t\t\t[\"desktop_reobserve\", \"reobserve\"],\n\t\t] as const) {\n\t\t\tapi.registerTool({\n\t\t\t\tname,\n\t\t\t\tlabel: name === \"desktop_observe\" ? \"Observe desktop\" : \"Re-observe desktop\",\n\t\t\t\tdescription: \"Return a bounded macOS Accessibility observation for an existing session\",\n\t\t\t\tparameters: Type.Object({\n\t\t\t\t\tsessionId: Type.String({ minLength: 1, maxLength: DESKTOP_MAX_STRING_CHARS_V1 }),\n\t\t\t\t}),\n\t\t\t\texecute: async (first: unknown, second?: unknown, third?: unknown) => {\n\t\t\t\t\tconst { input, operationId } = toolInvocation(first, second);\n\t\t\t\t\tconst sessionId = nonEmptyString(input.sessionId, \"sessionId\");\n\t\t\t\t\treturn invoke(\n\t\t\t\t\t\toperationId,\n\t\t\t\t\t\t() => runtime[action](sessionId),\n\t\t\t\t\t\t(value) => desktopObservationResult(value, operationId),\n\t\t\t\t\t\tsignalFrom(third),\n\t\t\t\t\t);\n\t\t\t\t},\n\t\t\t});\n\t\t}\n\t\tapi.registerTool({\n\t\t\tname: \"desktop_session_stop\",\n\t\t\tlabel: \"Stop desktop session\",\n\t\t\tdescription: \"Stop an existing read-only desktop session\",\n\t\t\tparameters: Type.Object({ sessionId: Type.String({ minLength: 1, maxLength: DESKTOP_MAX_STRING_CHARS_V1 }) }),\n\t\t\texecute: async (first: unknown, second?: unknown, third?: unknown) => {\n\t\t\t\tconst { input, operationId } = toolInvocation(first, second);\n\t\t\t\tconst sessionId = nonEmptyString(input.sessionId, \"sessionId\");\n\t\t\t\treturn invoke(\n\t\t\t\t\toperationId,\n\t\t\t\t\t() => runtime.stop(sessionId),\n\t\t\t\t\t(value) => desktopStopResult(value, operationId),\n\t\t\t\t\tsignalFrom(third),\n\t\t\t\t);\n\t\t\t},\n\t\t});\n\t};\n}\n"]}
|
package/dist/entry.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default-export factory consumed by the host's `loadCapabilityPluginFromManifest`.
|
|
3
|
+
*
|
|
4
|
+
* The adapter is constructed exactly like the Windows package's default
|
|
5
|
+
* desktop wiring: one `DesktopMacosAdapter` over the default
|
|
6
|
+
* `MacosAccessibilityDriver` (osascript, 15s timeout, `process.platform`).
|
|
7
|
+
* Non-macOS platforms never throw at import or call time: the driver probe
|
|
8
|
+
* reports unavailable and every tool returns an explicit `unsupported` result
|
|
9
|
+
* (fail-closed by design, same contract as the Windows package).
|
|
10
|
+
*/
|
|
11
|
+
declare const _default: import("@agent-forge/plugin-sdk").CapabilityPluginSyncFactory;
|
|
12
|
+
export default _default;
|
|
13
|
+
//# sourceMappingURL=entry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entry.d.ts","sourceRoot":"","sources":["../src/entry.ts"],"names":[],"mappings":"AAGA;;;;;;;;;GASG;;AACH,wBAAkF","sourcesContent":["import { createDesktopObserveCapabilityFactoryV1 } from \"./capability.ts\";\nimport { DesktopMacosAdapter } from \"./macos-ax-adapter.ts\";\n\n/**\n * Default-export factory consumed by the host's `loadCapabilityPluginFromManifest`.\n *\n * The adapter is constructed exactly like the Windows package's default\n * desktop wiring: one `DesktopMacosAdapter` over the default\n * `MacosAccessibilityDriver` (osascript, 15s timeout, `process.platform`).\n * Non-macOS platforms never throw at import or call time: the driver probe\n * reports unavailable and every tool returns an explicit `unsupported` result\n * (fail-closed by design, same contract as the Windows package).\n */\nexport default createDesktopObserveCapabilityFactoryV1(new DesktopMacosAdapter());\n"]}
|
package/dist/entry.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { createDesktopObserveCapabilityFactoryV1 } from "./capability.js";
|
|
2
|
+
import { DesktopMacosAdapter } from "./macos-ax-adapter.js";
|
|
3
|
+
/**
|
|
4
|
+
* Default-export factory consumed by the host's `loadCapabilityPluginFromManifest`.
|
|
5
|
+
*
|
|
6
|
+
* The adapter is constructed exactly like the Windows package's default
|
|
7
|
+
* desktop wiring: one `DesktopMacosAdapter` over the default
|
|
8
|
+
* `MacosAccessibilityDriver` (osascript, 15s timeout, `process.platform`).
|
|
9
|
+
* Non-macOS platforms never throw at import or call time: the driver probe
|
|
10
|
+
* reports unavailable and every tool returns an explicit `unsupported` result
|
|
11
|
+
* (fail-closed by design, same contract as the Windows package).
|
|
12
|
+
*/
|
|
13
|
+
export default createDesktopObserveCapabilityFactoryV1(new DesktopMacosAdapter());
|
|
14
|
+
//# sourceMappingURL=entry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entry.js","sourceRoot":"","sources":["../src/entry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uCAAuC,EAAE,MAAM,iBAAiB,CAAC;AAC1E,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAE5D;;;;;;;;;GASG;AACH,eAAe,uCAAuC,CAAC,IAAI,mBAAmB,EAAE,CAAC,CAAC","sourcesContent":["import { createDesktopObserveCapabilityFactoryV1 } from \"./capability.ts\";\nimport { DesktopMacosAdapter } from \"./macos-ax-adapter.ts\";\n\n/**\n * Default-export factory consumed by the host's `loadCapabilityPluginFromManifest`.\n *\n * The adapter is constructed exactly like the Windows package's default\n * desktop wiring: one `DesktopMacosAdapter` over the default\n * `MacosAccessibilityDriver` (osascript, 15s timeout, `process.platform`).\n * Non-macOS platforms never throw at import or call time: the driver probe\n * reports unavailable and every tool returns an explicit `unsupported` result\n * (fail-closed by design, same contract as the Windows package).\n */\nexport default createDesktopObserveCapabilityFactoryV1(new DesktopMacosAdapter());\n"]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { createDesktopObserveCapabilityFactoryV1 } from "./capability.ts";
|
|
2
|
+
export type { MacosAccessibilityDriverOptions } from "./macos-ax-adapter.ts";
|
|
3
|
+
export { DesktopMacosAdapter, MacosAccessibilityDriver } from "./macos-ax-adapter.ts";
|
|
4
|
+
export { DESKTOP_MAX_POSTCONDITIONS_V1, DESKTOP_MAX_REPLAY_ACTIONS_V1, DESKTOP_MAX_STRING_CHARS_V1, DESKTOP_MAX_TYPE_TEXT_CHARS_V1, desktopBoundedString, desktopClampedString, desktopFailure, desktopRequiredString, desktopResultWithinBudget, projectDesktopObservation, } from "./projection.ts";
|
|
5
|
+
export type * from "./types.ts";
|
|
6
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uCAAuC,EAAE,MAAM,iBAAiB,CAAC;AAC1E,YAAY,EAAE,+BAA+B,EAAE,MAAM,uBAAuB,CAAC;AAC7E,OAAO,EAAE,mBAAmB,EAAE,wBAAwB,EAAE,MAAM,uBAAuB,CAAC;AACtF,OAAO,EACN,6BAA6B,EAC7B,6BAA6B,EAC7B,2BAA2B,EAC3B,8BAA8B,EAC9B,oBAAoB,EACpB,oBAAoB,EACpB,cAAc,EACd,qBAAqB,EACrB,yBAAyB,EACzB,yBAAyB,GACzB,MAAM,iBAAiB,CAAC;AACzB,mBAAmB,YAAY,CAAC","sourcesContent":["export { createDesktopObserveCapabilityFactoryV1 } from \"./capability.ts\";\nexport type { MacosAccessibilityDriverOptions } from \"./macos-ax-adapter.ts\";\nexport { DesktopMacosAdapter, MacosAccessibilityDriver } from \"./macos-ax-adapter.ts\";\nexport {\n\tDESKTOP_MAX_POSTCONDITIONS_V1,\n\tDESKTOP_MAX_REPLAY_ACTIONS_V1,\n\tDESKTOP_MAX_STRING_CHARS_V1,\n\tDESKTOP_MAX_TYPE_TEXT_CHARS_V1,\n\tdesktopBoundedString,\n\tdesktopClampedString,\n\tdesktopFailure,\n\tdesktopRequiredString,\n\tdesktopResultWithinBudget,\n\tprojectDesktopObservation,\n} from \"./projection.ts\";\nexport type * from \"./types.ts\";\n"]}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { createDesktopObserveCapabilityFactoryV1 } from "./capability.js";
|
|
2
|
+
export { DesktopMacosAdapter, MacosAccessibilityDriver } from "./macos-ax-adapter.js";
|
|
3
|
+
export { DESKTOP_MAX_POSTCONDITIONS_V1, DESKTOP_MAX_REPLAY_ACTIONS_V1, DESKTOP_MAX_STRING_CHARS_V1, DESKTOP_MAX_TYPE_TEXT_CHARS_V1, desktopBoundedString, desktopClampedString, desktopFailure, desktopRequiredString, desktopResultWithinBudget, projectDesktopObservation, } from "./projection.js";
|
|
4
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uCAAuC,EAAE,MAAM,iBAAiB,CAAC;AAE1E,OAAO,EAAE,mBAAmB,EAAE,wBAAwB,EAAE,MAAM,uBAAuB,CAAC;AACtF,OAAO,EACN,6BAA6B,EAC7B,6BAA6B,EAC7B,2BAA2B,EAC3B,8BAA8B,EAC9B,oBAAoB,EACpB,oBAAoB,EACpB,cAAc,EACd,qBAAqB,EACrB,yBAAyB,EACzB,yBAAyB,GACzB,MAAM,iBAAiB,CAAC","sourcesContent":["export { createDesktopObserveCapabilityFactoryV1 } from \"./capability.ts\";\nexport type { MacosAccessibilityDriverOptions } from \"./macos-ax-adapter.ts\";\nexport { DesktopMacosAdapter, MacosAccessibilityDriver } from \"./macos-ax-adapter.ts\";\nexport {\n\tDESKTOP_MAX_POSTCONDITIONS_V1,\n\tDESKTOP_MAX_REPLAY_ACTIONS_V1,\n\tDESKTOP_MAX_STRING_CHARS_V1,\n\tDESKTOP_MAX_TYPE_TEXT_CHARS_V1,\n\tdesktopBoundedString,\n\tdesktopClampedString,\n\tdesktopFailure,\n\tdesktopRequiredString,\n\tdesktopResultWithinBudget,\n\tprojectDesktopObservation,\n} from \"./projection.ts\";\nexport type * from \"./types.ts\";\n"]}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { DesktopCapabilityIdV1, DesktopMacosProbeV1, DesktopMacosReadonlyDriverV1, DesktopObservationV1, DesktopOperationResultV1, DesktopReadonlyAdapterV1, DesktopSessionRequestV1, DesktopSessionResultV1, DesktopSessionV1 } from "./types.ts";
|
|
2
|
+
type MacosSpawnResult = {
|
|
3
|
+
readonly error?: Error;
|
|
4
|
+
readonly status: number | null;
|
|
5
|
+
readonly stdout: string;
|
|
6
|
+
readonly stderr?: string;
|
|
7
|
+
};
|
|
8
|
+
type MacosSpawn = (command: string, args: readonly string[], options: {
|
|
9
|
+
readonly encoding: "utf8";
|
|
10
|
+
readonly timeout: number;
|
|
11
|
+
readonly windowsHide: boolean;
|
|
12
|
+
readonly env: NodeJS.ProcessEnv;
|
|
13
|
+
}) => MacosSpawnResult;
|
|
14
|
+
export interface MacosAccessibilityDriverOptions {
|
|
15
|
+
/** Override the osascript executable for a brokered host or test harness. */
|
|
16
|
+
readonly command?: string;
|
|
17
|
+
readonly timeoutMs?: number;
|
|
18
|
+
/** Injectable host boundary for deterministic tests and brokered deployments. */
|
|
19
|
+
readonly spawnSync?: MacosSpawn;
|
|
20
|
+
/** Override the platform reported to the driver; intended for host adapters/tests. */
|
|
21
|
+
readonly platform?: NodeJS.Platform;
|
|
22
|
+
}
|
|
23
|
+
export declare class MacosAccessibilityDriver implements DesktopMacosReadonlyDriverV1 {
|
|
24
|
+
readonly driverId = "macos-ax";
|
|
25
|
+
readonly driverVersion = "1.0.0";
|
|
26
|
+
private readonly command;
|
|
27
|
+
private readonly timeoutMs;
|
|
28
|
+
private readonly platform;
|
|
29
|
+
private readonly spawn;
|
|
30
|
+
private readonly sessions;
|
|
31
|
+
private nextSessionId;
|
|
32
|
+
private nextObservationId;
|
|
33
|
+
constructor(options?: MacosAccessibilityDriverOptions);
|
|
34
|
+
probe(): DesktopMacosProbeV1;
|
|
35
|
+
openSession(request: DesktopSessionRequestV1): DesktopSessionResultV1;
|
|
36
|
+
observe(sessionId: string): DesktopObservationV1 | DesktopOperationResultV1;
|
|
37
|
+
stop(sessionId: string): DesktopSessionV1 | DesktopOperationResultV1;
|
|
38
|
+
private run;
|
|
39
|
+
private toObservation;
|
|
40
|
+
private failed;
|
|
41
|
+
private cancelled;
|
|
42
|
+
}
|
|
43
|
+
/** Read-only adapter facade over the macOS Accessibility driver. */
|
|
44
|
+
export declare class DesktopMacosAdapter implements DesktopReadonlyAdapterV1 {
|
|
45
|
+
readonly adapterId = "desktop-macos-ax";
|
|
46
|
+
readonly adapterVersion = "1.0.0";
|
|
47
|
+
readonly capabilities: readonly DesktopCapabilityIdV1[];
|
|
48
|
+
private readonly driver;
|
|
49
|
+
constructor(driver?: DesktopMacosReadonlyDriverV1);
|
|
50
|
+
probe(): DesktopMacosProbeV1;
|
|
51
|
+
openSession(request: DesktopSessionRequestV1): DesktopSessionResultV1;
|
|
52
|
+
observe(sessionId: string): DesktopObservationV1 | DesktopOperationResultV1;
|
|
53
|
+
reobserve(sessionId: string): DesktopObservationV1 | DesktopOperationResultV1;
|
|
54
|
+
stop(sessionId: string): DesktopSessionV1 | DesktopOperationResultV1;
|
|
55
|
+
}
|
|
56
|
+
export {};
|
|
57
|
+
//# sourceMappingURL=macos-ax-adapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"macos-ax-adapter.d.ts","sourceRoot":"","sources":["../src/macos-ax-adapter.ts"],"names":[],"mappings":"AAuBA,OAAO,KAAK,EACX,qBAAqB,EAGrB,mBAAmB,EACnB,4BAA4B,EAC5B,oBAAoB,EACpB,wBAAwB,EACxB,wBAAwB,EACxB,uBAAuB,EACvB,sBAAsB,EACtB,gBAAgB,EAEhB,MAAM,YAAY,CAAC;AAgCpB,KAAK,gBAAgB,GAAG;IACvB,QAAQ,CAAC,KAAK,CAAC,EAAE,KAAK,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CACzB,CAAC;AAEF,KAAK,UAAU,GAAG,CACjB,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,OAAO,EAAE;IACR,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;IAC9B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC,UAAU,CAAC;CAChC,KACG,gBAAgB,CAAC;AAEtB,MAAM,WAAW,+BAA+B;IAC/C,6EAA6E;IAC7E,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,iFAAiF;IACjF,QAAQ,CAAC,SAAS,CAAC,EAAE,UAAU,CAAC;IAChC,sFAAsF;IACtF,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC;CACpC;AAyHD,qBAAa,wBAAyB,YAAW,4BAA4B;IAC5E,QAAQ,CAAC,QAAQ,cAAc;IAC/B,QAAQ,CAAC,aAAa,WAAW;IACjC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAkB;IAC3C,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAa;IACnC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAuC;IAChE,OAAO,CAAC,aAAa,CAAK;IAC1B,OAAO,CAAC,iBAAiB,CAAK;IAE9B,YAAY,OAAO,GAAE,+BAAoC,EAUxD;IAED,KAAK,IAAI,mBAAmB,CA0C3B;IAED,WAAW,CAAC,OAAO,EAAE,uBAAuB,GAAG,sBAAsB,CAoBpE;IAED,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG,oBAAoB,GAAG,wBAAwB,CAS1E;IAED,IAAI,CAAC,SAAS,EAAE,MAAM,GAAG,gBAAgB,GAAG,wBAAwB,CAMnE;IAED,OAAO,CAAC,GAAG;IAsBX,OAAO,CAAC,aAAa;IAgDrB,OAAO,CAAC,MAAM;IAGd,OAAO,CAAC,SAAS;CASjB;AAED,oEAAoE;AACpE,qBAAa,mBAAoB,YAAW,wBAAwB;IACnE,QAAQ,CAAC,SAAS,sBAAsB;IACxC,QAAQ,CAAC,cAAc,WAAW;IAClC,QAAQ,CAAC,YAAY,mCAAyB;IAC9C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA+B;IACtD,YAAY,MAAM,GAAE,4BAA6D,EAEhF;IACD,KAAK,IAAI,mBAAmB,CAE3B;IACD,WAAW,CAAC,OAAO,EAAE,uBAAuB,GAAG,sBAAsB,CAEpE;IACD,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG,oBAAoB,GAAG,wBAAwB,CAE1E;IACD,SAAS,CAAC,SAAS,EAAE,MAAM,GAAG,oBAAoB,GAAG,wBAAwB,CAE5E;IACD,IAAI,CAAC,SAAS,EAAE,MAAM,GAAG,gBAAgB,GAAG,wBAAwB,CAEnE;CACD","sourcesContent":["/**\n * macOS Accessibility read-only driver and adapter facade for the observe\n * package. Structurally mirrors the Windows package's `windows-uia-adapter.ts`\n * (inlined from agent-forge `src/plugins/desktop/windows-uia-adapter.ts`,\n * D-075 S3) with these deliberate differences:\n * - Driver boundary is `osascript -l JavaScript -e <script>` (JXA) instead of\n * PowerShell UIA; the query script walks the System Events accessibility\n * tree (`Application(\"com.apple.systemevents\")` processes/windows/entireContents) and\n * prints one JSON document to stdout.\n * - This observe copy carries the query script only: the class implements\n * `DesktopMacosReadonlyDriverV1` and the facade implements\n * `DesktopReadonlyAdapterV1` — there is deliberately no execute surface.\n * - Non-darwin platforms never throw at import or call time: probe reports\n * `{available: false, reason: \"macOS Accessibility requires darwin\"}` and\n * every tool returns an explicit `unsupported` result (same fail-closed\n * contract as the Windows package off win32).\n * - Missing Accessibility (assistive access) permission is reported\n * fail-closed as `accessibility-permission-required`; no degraded mode.\n * This file is a deliberate copy shared (not imported) with the input\n * package's adapter; the input copy carries the action script and execute.\n */\nimport { spawnSync as nodeSpawnSync } from \"node:child_process\";\nimport { detachedFreeze } from \"@agent-forge/plugin-sdk\";\nimport type {\n\tDesktopCapabilityIdV1,\n\tDesktopControlIdentityV1,\n\tDesktopErrorV1,\n\tDesktopMacosProbeV1,\n\tDesktopMacosReadonlyDriverV1,\n\tDesktopObservationV1,\n\tDesktopOperationResultV1,\n\tDesktopReadonlyAdapterV1,\n\tDesktopSessionRequestV1,\n\tDesktopSessionResultV1,\n\tDesktopSessionV1,\n\tDesktopWindowIdentityV1,\n} from \"./types.ts\";\n\ntype RawWindow = {\n\tid?: string;\n\tprocessId?: number;\n\tprocessName?: string;\n\tname?: string;\n\tx?: number;\n\ty?: number;\n\twidth?: number;\n\theight?: number;\n};\ntype RawControl = {\n\twindowId?: string;\n\tcontrolId?: string;\n\trole?: string;\n\tname?: string;\n\tenabled?: boolean;\n\tvalue?: string;\n\tx?: number;\n\ty?: number;\n\twidth?: number;\n\theight?: number;\n};\ntype RawSnapshot = {\n\tavailable?: boolean;\n\treason?: string;\n\twindows?: RawWindow[];\n\tcontrols?: RawControl[];\n\tfocusedControlId?: string;\n};\n\ntype MacosSpawnResult = {\n\treadonly error?: Error;\n\treadonly status: number | null;\n\treadonly stdout: string;\n\treadonly stderr?: string;\n};\n\ntype MacosSpawn = (\n\tcommand: string,\n\targs: readonly string[],\n\toptions: {\n\t\treadonly encoding: \"utf8\";\n\t\treadonly timeout: number;\n\t\treadonly windowsHide: boolean;\n\t\treadonly env: NodeJS.ProcessEnv;\n\t},\n) => MacosSpawnResult;\n\nexport interface MacosAccessibilityDriverOptions {\n\t/** Override the osascript executable for a brokered host or test harness. */\n\treadonly command?: string;\n\treadonly timeoutMs?: number;\n\t/** Injectable host boundary for deterministic tests and brokered deployments. */\n\treadonly spawnSync?: MacosSpawn;\n\t/** Override the platform reported to the driver; intended for host adapters/tests. */\n\treadonly platform?: NodeJS.Platform;\n}\n\nconst READONLY_CAPABILITIES: readonly DesktopCapabilityIdV1[] = [\"desktop-session\", \"desktop-uia\"];\n\nconst error = (code: string, message: string, causeCode?: string): DesktopErrorV1 => ({\n\tversion: 1,\n\tcode,\n\tmessage,\n\t...(causeCode === undefined ? {} : { causeCode }),\n});\n\nconst unsupported = (operationId: string, reason: string): DesktopOperationResultV1 => ({\n\tversion: 1,\n\toperationId,\n\tstatus: \"unsupported\",\n\tsummary: reason,\n\terror: error(\"platform_unavailable\", reason, \"platform-unavailable\"),\n});\n\nconst rectangle = (raw: { x?: number; y?: number; width?: number; height?: number }) => ({\n\tx: Number.isFinite(raw.x) ? Number(raw.x) : 0,\n\ty: Number.isFinite(raw.y) ? Number(raw.y) : 0,\n\twidth: Number.isFinite(raw.width) ? Number(raw.width) : 0,\n\theight: Number.isFinite(raw.height) ? Number(raw.height) : 0,\n});\n\nconst isPermissionReason = (value: string): boolean =>\n\t/assistive access|not authorized|accessibility-permission-required/i.test(value);\n\n/**\n * JXA observation script. Enumerates every non-background System Events\n * process's windows (caps: 64 windows, 256 controls total, mirroring the\n * projection budgets) with role/name/position/size/enabled/value per control,\n * plus the frontmost process's focused element matched by role and frame.\n * Permission or automation failures are reported in-band as\n * `{available:false, reason:...}` so the probe can classify them; the reason\n * literal `accessibility-permission-required` is reserved for missing\n * Accessibility (assistive access) grants.\n */\nconst QUERY_SCRIPT = `\nvar se = Application('com.apple.systemevents');\nfunction safe(fn) { try { return fn(); } catch (e) { return undefined; } }\nfunction rectOf(el) {\n var p = safe(function() { return el.position(); });\n var z = safe(function() { return el.size(); });\n if (p === undefined || z === undefined || p.length < 2 || z.length < 2) return {x: 0, y: 0, width: 0, height: 0};\n return {x: Math.round(p[0]), y: Math.round(p[1]), width: Math.round(z[0]), height: Math.round(z[1])};\n}\nfunction axReason(e) {\n var msg = String(e && e.message !== undefined ? e.message : e);\n var num = e && typeof e.errorNumber === 'number' ? e.errorNumber : 0;\n if (msg.indexOf('assistive access') !== -1 || msg.indexOf('not authorized') !== -1 ||\n num === -25211 || num === -1719 || num === -1743) return 'accessibility-permission-required';\n return msg;\n}\ntry {\n var out = {available: true, windows: [], controls: [], focusedControlId: null};\n var procs = se.processes();\n var focused = null;\n for (var fi = 0; fi < procs.length; fi++) {\n var fp = procs[fi];\n if (safe(function() { return fp.frontmost(); }) !== true) continue;\n var fe = safe(function() { return fp.attributes['AXFocusedUIElement'].value(); });\n if (fe !== undefined) focused = {role: safe(function() { return fe.role(); }), rect: rectOf(fe)};\n break;\n }\n var windowCount = 0;\n var controlCount = 0;\n for (var pi = 0; pi < procs.length && windowCount < 64; pi++) {\n var proc = procs[pi];\n if (safe(function() { return proc.backgroundOnly(); }) === true) continue;\n var wins = safe(function() { return proc.windows(); });\n if (wins === undefined || wins.length === 0) continue;\n var pid = safe(function() { return proc.unixId(); });\n var pname = safe(function() { return proc.name(); });\n if (typeof pname !== 'string' || pname.length === 0) pname = 'unknown';\n for (var wi = 0; wi < wins.length && windowCount < 64; wi++) {\n var win = wins[wi];\n var winRect = rectOf(win);\n var winId = 'window-' + pid + '-' + wi;\n windowCount++;\n out.windows.push({id: winId, processId: pid, processName: pname,\n name: safe(function() { return win.name(); }) || '', x: winRect.x, y: winRect.y,\n width: winRect.width, height: winRect.height});\n var contents = safe(function() { return win.entireContents(); });\n if (contents === undefined) continue;\n for (var ci = 0; ci < contents.length && controlCount < 256; ci++) {\n var el = contents[ci];\n var role = safe(function() { return el.role(); });\n if (typeof role !== 'string' || role.length === 0) continue;\n var r = rectOf(el);\n var controlId = winId + ':' + role + ':' + ci;\n var value = safe(function() { return el.value(); });\n out.controls.push({windowId: winId, controlId: controlId, role: role,\n name: safe(function() { return el.name(); }) || safe(function() { return el.title(); }) || '',\n enabled: safe(function() { return el.enabled(); }) !== false,\n value: typeof value === 'string' && value.length > 0 ? value : undefined,\n x: r.x, y: r.y, width: r.width, height: r.height});\n controlCount++;\n if (focused !== null && out.focusedControlId === null && role === focused.role &&\n r.x === focused.rect.x && r.y === focused.rect.y &&\n r.width === focused.rect.width && r.height === focused.rect.height) {\n out.focusedControlId = controlId;\n }\n }\n }\n }\n JSON.stringify(out);\n} catch (e) {\n JSON.stringify({available: false, reason: axReason(e)});\n}`;\n\nconst parseJson = (stdout: string): Record<string, unknown> | undefined => {\n\ttry {\n\t\tconst parsed: unknown = JSON.parse(stdout.trim());\n\t\treturn typeof parsed === \"object\" && parsed !== null ? (parsed as Record<string, unknown>) : undefined;\n\t} catch {\n\t\treturn undefined;\n\t}\n};\n\nexport class MacosAccessibilityDriver implements DesktopMacosReadonlyDriverV1 {\n\treadonly driverId = \"macos-ax\";\n\treadonly driverVersion = \"1.0.0\";\n\tprivate readonly command: string;\n\tprivate readonly timeoutMs: number;\n\tprivate readonly platform: NodeJS.Platform;\n\tprivate readonly spawn: MacosSpawn;\n\tprivate readonly sessions = new Map<string, DesktopSessionV1>();\n\tprivate nextSessionId = 1;\n\tprivate nextObservationId = 1;\n\n\tconstructor(options: MacosAccessibilityDriverOptions = {}) {\n\t\tthis.command = options.command ?? \"osascript\";\n\t\t// 60s: Apple events from an SSH (Background launchd) session run ~100x\n\t\t// slower than a GUI session — one full enumeration measured 38s there;\n\t\t// GUI sessions complete in seconds and stay unaffected by the raise.\n\t\tthis.timeoutMs = options.timeoutMs ?? 60_000;\n\t\tthis.platform = options.platform ?? process.platform;\n\t\tthis.spawn =\n\t\t\toptions.spawnSync ??\n\t\t\t((command, args, spawnOptions) => nodeSpawnSync(command, args, spawnOptions) as unknown as MacosSpawnResult);\n\t}\n\n\tprobe(): DesktopMacosProbeV1 {\n\t\tif (this.platform !== \"darwin\") {\n\t\t\treturn {\n\t\t\t\tavailable: false,\n\t\t\t\tplatform: this.platform,\n\t\t\t\tdriver: \"macos-ax\",\n\t\t\t\treason: \"macOS Accessibility requires darwin\",\n\t\t\t};\n\t\t}\n\t\tconst run = this.run(QUERY_SCRIPT);\n\t\tif (run === undefined)\n\t\t\treturn {\n\t\t\t\tavailable: false,\n\t\t\t\tplatform: this.platform,\n\t\t\t\tdriver: \"macos-ax\",\n\t\t\t\treason: \"macOS Accessibility requires darwin\",\n\t\t\t};\n\t\tif (run.failed)\n\t\t\treturn {\n\t\t\t\tavailable: false,\n\t\t\t\tplatform: this.platform,\n\t\t\t\tdriver: \"macos-ax\",\n\t\t\t\treason: isPermissionReason(run.stderr) ? \"accessibility-permission-required\" : \"osascript is unavailable\",\n\t\t\t};\n\t\tconst raw = parseJson(run.stdout);\n\t\tif (raw === undefined)\n\t\t\treturn {\n\t\t\t\tavailable: false,\n\t\t\t\tplatform: this.platform,\n\t\t\t\tdriver: \"macos-ax\",\n\t\t\t\treason: \"osascript is unavailable\",\n\t\t\t};\n\t\tif (raw.available === true)\n\t\t\treturn {\n\t\t\t\tavailable: true,\n\t\t\t\tplatform: this.platform,\n\t\t\t\tdriver: \"macos-ax\",\n\t\t\t\tcapabilities: READONLY_CAPABILITIES,\n\t\t\t};\n\t\tconst reason =\n\t\t\ttypeof raw.reason === \"string\" && raw.reason.trim() !== \"\" ? raw.reason : \"macOS Accessibility probe failed\";\n\t\treturn { available: false, platform: this.platform, driver: \"macos-ax\", reason };\n\t}\n\n\topenSession(request: DesktopSessionRequestV1): DesktopSessionResultV1 {\n\t\tconst probe = this.probe();\n\t\tif (!probe.available)\n\t\t\treturn {\n\t\t\t\tstatus: \"unsupported\",\n\t\t\t\terror: error(\n\t\t\t\t\t\"platform_unavailable\",\n\t\t\t\t\tprobe.reason ?? \"macOS Accessibility is unavailable\",\n\t\t\t\t\t\"platform-unavailable\",\n\t\t\t\t),\n\t\t\t};\n\t\tconst session = detachedFreeze({\n\t\t\tversion: 1 as const,\n\t\t\tsessionId: `desktop-macos-session-${this.nextSessionId++}`,\n\t\t\tisolation: request.isolation,\n\t\t\tstatus: \"ready\" as const,\n\t\t\t...(request.applicationId === undefined ? {} : { applicationId: request.applicationId }),\n\t\t});\n\t\tthis.sessions.set(session.sessionId, session);\n\t\treturn { status: \"succeeded\", session };\n\t}\n\n\tobserve(sessionId: string): DesktopObservationV1 | DesktopOperationResultV1 {\n\t\tconst session = this.sessions.get(sessionId);\n\t\tif (session === undefined) return this.failed(\"observe\", \"session_not_found\", \"Session does not exist\");\n\t\tif (session.status === \"stopped\") return this.cancelled(\"observe\", \"Session has been stopped\");\n\t\tconst run = this.run(QUERY_SCRIPT);\n\t\tconst raw = run === undefined || run.failed ? undefined : parseJson(run.stdout);\n\t\tif (raw === undefined || raw.available !== true)\n\t\t\treturn unsupported(\"observe\", \"macOS Accessibility observation failed\");\n\t\treturn this.toObservation(session, raw as unknown as RawSnapshot);\n\t}\n\n\tstop(sessionId: string): DesktopSessionV1 | DesktopOperationResultV1 {\n\t\tconst session = this.sessions.get(sessionId);\n\t\tif (session === undefined) return this.failed(\"stop\", \"session_not_found\", \"Session does not exist\");\n\t\tconst stopped = detachedFreeze({ ...session, status: \"stopped\" as const });\n\t\tthis.sessions.set(sessionId, stopped);\n\t\treturn stopped;\n\t}\n\n\tprivate run(\n\t\tscript: string,\n\t\tpayload?: unknown,\n\t): { readonly stdout: string; readonly stderr: string; readonly failed: boolean } | undefined {\n\t\tif (this.platform !== \"darwin\") return undefined;\n\t\tconst env: NodeJS.ProcessEnv = {};\n\t\tfor (const key of [\"PATH\", \"HOME\", \"TMPDIR\", \"USER\", \"LOGNAME\"] as const) {\n\t\t\tconst value = process.env[key];\n\t\t\tif (value !== undefined) env[key] = value;\n\t\t}\n\t\tif (payload !== undefined) env.AGENT_FORGE_MACOS_AX_INPUT = JSON.stringify(payload);\n\t\tconst result = this.spawn(this.command, [\"-l\", \"JavaScript\", \"-e\", script], {\n\t\t\tencoding: \"utf8\",\n\t\t\ttimeout: this.timeoutMs,\n\t\t\twindowsHide: true,\n\t\t\tenv,\n\t\t});\n\t\tif (result.error !== undefined || result.status !== 0)\n\t\t\treturn { stdout: result.stdout, stderr: result.stderr ?? \"\", failed: true };\n\t\treturn { stdout: result.stdout, stderr: result.stderr ?? \"\", failed: false };\n\t}\n\n\tprivate toObservation(session: DesktopSessionV1, raw: RawSnapshot): DesktopObservationV1 {\n\t\tconst observationId = `desktop-macos-observation-${this.nextObservationId++}`;\n\t\tconst windows: DesktopWindowIdentityV1[] = [];\n\t\tfor (const item of raw.windows ?? []) {\n\t\t\tif (typeof item.id !== \"string\" || item.id === \"\") continue;\n\t\t\twindows.push({\n\t\t\t\tversion: 1,\n\t\t\t\twindowId: item.id,\n\t\t\t\tappId: `process:${Number(item.processId ?? 0)}`,\n\t\t\t\ttitle: item.name ?? \"\",\n\t\t\t\tbounds: rectangle(item),\n\t\t\t});\n\t\t}\n\t\tconst visibleWindows =\n\t\t\tsession.applicationId === undefined\n\t\t\t\t? windows\n\t\t\t\t: windows.filter((window) => window.appId === session.applicationId);\n\t\tconst visibleWindowIds = new Set(visibleWindows.map((window) => window.windowId));\n\t\tconst controls: DesktopControlIdentityV1[] = [];\n\t\tfor (const item of raw.controls ?? []) {\n\t\t\tif (typeof item.windowId !== \"string\" || !visibleWindowIds.has(item.windowId)) continue;\n\t\t\tcontrols.push({\n\t\t\t\tversion: 1,\n\t\t\t\tcontrolId: item.controlId ?? \"\",\n\t\t\t\twindowId: item.windowId,\n\t\t\t\trole: item.role ?? \"\",\n\t\t\t\tname: item.name ?? \"\",\n\t\t\t\tbounds: rectangle(item),\n\t\t\t\tenabled: item.enabled !== false,\n\t\t\t\t...(typeof item.value === \"string\" && item.value.length > 0 ? { value: item.value } : {}),\n\t\t\t});\n\t\t}\n\t\tconst focusedControlId =\n\t\t\ttypeof raw.focusedControlId === \"string\" &&\n\t\t\tcontrols.some((control) => control.controlId === raw.focusedControlId)\n\t\t\t\t? raw.focusedControlId\n\t\t\t\t: undefined;\n\t\treturn detachedFreeze({\n\t\t\tversion: 1 as const,\n\t\t\tsessionId: session.sessionId,\n\t\t\tobservationId,\n\t\t\tsource: \"macos-ax\" as const,\n\t\t\twindows: visibleWindows,\n\t\t\tcontrols,\n\t\t\t...(focusedControlId !== undefined ? { focusedControlId } : {}),\n\t\t});\n\t}\n\n\tprivate failed(operationId: string, code: string, message: string, causeCode?: string): DesktopOperationResultV1 {\n\t\treturn { version: 1, operationId, status: \"failed\", summary: message, error: error(code, message, causeCode) };\n\t}\n\tprivate cancelled(operationId: string, message: string): DesktopOperationResultV1 {\n\t\treturn {\n\t\t\tversion: 1,\n\t\t\toperationId,\n\t\t\tstatus: \"cancelled\",\n\t\t\tsummary: message,\n\t\t\terror: error(\"cancelled\", message, \"stopped\"),\n\t\t};\n\t}\n}\n\n/** Read-only adapter facade over the macOS Accessibility driver. */\nexport class DesktopMacosAdapter implements DesktopReadonlyAdapterV1 {\n\treadonly adapterId = \"desktop-macos-ax\";\n\treadonly adapterVersion = \"1.0.0\";\n\treadonly capabilities = READONLY_CAPABILITIES;\n\tprivate readonly driver: DesktopMacosReadonlyDriverV1;\n\tconstructor(driver: DesktopMacosReadonlyDriverV1 = new MacosAccessibilityDriver()) {\n\t\tthis.driver = driver;\n\t}\n\tprobe(): DesktopMacosProbeV1 {\n\t\treturn this.driver.probe();\n\t}\n\topenSession(request: DesktopSessionRequestV1): DesktopSessionResultV1 {\n\t\treturn this.driver.openSession(request);\n\t}\n\tobserve(sessionId: string): DesktopObservationV1 | DesktopOperationResultV1 {\n\t\treturn this.driver.observe(sessionId);\n\t}\n\treobserve(sessionId: string): DesktopObservationV1 | DesktopOperationResultV1 {\n\t\treturn this.driver.observe(sessionId);\n\t}\n\tstop(sessionId: string): DesktopSessionV1 | DesktopOperationResultV1 {\n\t\treturn this.driver.stop(sessionId);\n\t}\n}\n"]}
|