@myagentroam/node 0.13.13 → 0.13.15
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/dist/connector.js +17 -3
- package/dist/marctl/package.json +1 -1
- package/dist/marctl/src/config.mjs +1 -7
- package/dist/marctl/src/i18n/en-US.mjs +1 -1
- package/dist/marctl/src/i18n/zh-CN.mjs +1 -1
- package/dist/remote-desktop/macos-remote-desktop-runtime.js +127 -33
- package/dist/remote-desktop/macvnc-managed-process.js +7 -1
- package/dist/service/node-request-service.js +1 -1
- package/dist/service/node-upgrade-reporter.js +1 -5
- package/package.json +7 -6
package/dist/connector.js
CHANGED
|
@@ -13,7 +13,7 @@ import { NodeModelUsageService } from './service/model-usage-service.js';
|
|
|
13
13
|
import { dirname, resolve } from 'node:path';
|
|
14
14
|
import { homedir } from 'node:os';
|
|
15
15
|
import WebSocket from 'ws';
|
|
16
|
-
import { createEnvelope, directNodeCommandSchema, remoteDesktopDescriptionSchema } from '@myagentroam/protocol';
|
|
16
|
+
import { createEnvelope, directNodeCommandSchema, remoteDesktopDescriptionSchema, remoteDesktopDescribeInputSchema } from '@myagentroam/protocol';
|
|
17
17
|
import { loadNodeConfig, computerUseRuntimeExecutable, directTransferUdpPortRange, nodeDataDirectory, nodeDatabasePath } from './config.js';
|
|
18
18
|
import { NodeMetrics, nodeLog } from './operational.js';
|
|
19
19
|
import { assertLocalSqlitePath } from './storage.js';
|
|
@@ -1550,16 +1550,30 @@ export class NodeConnector {
|
|
|
1550
1550
|
service: this.agentNetworkService,
|
|
1551
1551
|
observations: this.agentNetworkObservations
|
|
1552
1552
|
}));
|
|
1553
|
-
this.operationRouter.register('desktop.describe', async () => {
|
|
1553
|
+
this.operationRouter.register('desktop.describe', async (data) => {
|
|
1554
|
+
if (!isPlainRecord(data))
|
|
1555
|
+
throw new RemoteDesktopError('DESKTOP_TOPOLOGY_UNAVAILABLE');
|
|
1556
|
+
const { __workspaceWatchSessionHash: _sessionHash, __requestUserId: _userId, __usageGrant: _usageGrant, __marctlCaller: _caller, ...input } = data;
|
|
1557
|
+
const parsed = remoteDesktopDescribeInputSchema.safeParse(input);
|
|
1558
|
+
if (!parsed.success)
|
|
1559
|
+
throw new RemoteDesktopError('DESKTOP_TOPOLOGY_UNAVAILABLE');
|
|
1560
|
+
const selection = parsed.data;
|
|
1554
1561
|
const runtime = this.desktopRuntime;
|
|
1555
1562
|
if (!runtime)
|
|
1556
1563
|
throw new RemoteDesktopError('DESKTOP_UNAVAILABLE');
|
|
1557
1564
|
if (runtime instanceof WindowsRemoteDesktopRuntime ||
|
|
1558
1565
|
runtime instanceof MacosRemoteDesktopRuntime)
|
|
1559
1566
|
await runtime.refresh(AbortSignal.timeout(10_000));
|
|
1560
|
-
const description = remoteDesktopDescriptionSchema.safeParse(runtime
|
|
1567
|
+
const description = remoteDesktopDescriptionSchema.safeParse(runtime instanceof MacosRemoteDesktopRuntime
|
|
1568
|
+
? runtime.describe(selection)
|
|
1569
|
+
: runtime.describe());
|
|
1561
1570
|
if (!description.success)
|
|
1562
1571
|
throw new RemoteDesktopError('DESKTOP_TOPOLOGY_UNAVAILABLE');
|
|
1572
|
+
if ((selection.desktopGeneration !== undefined &&
|
|
1573
|
+
selection.desktopGeneration !== description.data.desktopGeneration) ||
|
|
1574
|
+
(selection.displayId !== undefined &&
|
|
1575
|
+
!description.data.displays.some((display) => display.displayId === selection.displayId)))
|
|
1576
|
+
throw new RemoteDesktopError('DESKTOP_TOPOLOGY_UNAVAILABLE');
|
|
1563
1577
|
return description.data;
|
|
1564
1578
|
});
|
|
1565
1579
|
this.operationRouter.registerAll(this.promptCompletion.operations());
|
package/dist/marctl/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"type":"module","version":"0.13.
|
|
1
|
+
{"type":"module","version":"0.13.15"}
|
|
@@ -27,13 +27,7 @@ export function validateServerUrl(value, translate = createTranslator()) {
|
|
|
27
27
|
throw new MarctlError(translate('errors.serverUrlInvalid'));
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
url.protocol === 'http:' &&
|
|
32
|
-
(url.hostname === 'localhost' ||
|
|
33
|
-
url.hostname === '127.0.0.1' ||
|
|
34
|
-
url.hostname === '::1' ||
|
|
35
|
-
url.hostname === '[::1]');
|
|
36
|
-
if (url.protocol !== 'https:' && !isLoopbackHttp)
|
|
30
|
+
if (url.protocol !== 'https:' && url.protocol !== 'http:')
|
|
37
31
|
throw new MarctlError(translate('errors.serverUrlTransport'));
|
|
38
32
|
if (url.username || url.password || url.search || url.hash || url.pathname !== '/')
|
|
39
33
|
throw new MarctlError(translate('errors.serverUrlOrigin'));
|
|
@@ -234,7 +234,7 @@ No configuration, authorization, or Server connection is required.`
|
|
|
234
234
|
userContextUnavailable: 'This Node session has no valid marctl user authorization.',
|
|
235
235
|
sessionContextRequired: 'No current Session is available; specify a Session ID explicitly.',
|
|
236
236
|
serverUrlInvalid: 'MAR_CTL_SERVER_URL is invalid.',
|
|
237
|
-
serverUrlTransport: 'MAR_CTL_SERVER_URL must use
|
|
237
|
+
serverUrlTransport: 'MAR_CTL_SERVER_URL must use HTTP or HTTPS.',
|
|
238
238
|
serverUrlOrigin: 'MAR_CTL_SERVER_URL must be an origin without credentials or query data.',
|
|
239
239
|
fetchUnavailable: 'Fetch is unavailable in this Node.js runtime.',
|
|
240
240
|
serverInvalidNodes: 'The server returned an invalid node list.',
|
|
@@ -220,7 +220,7 @@ ${configurationHelp}`,
|
|
|
220
220
|
userContextUnavailable: '此 Node 会话没有有效的 marctl 用户授权。',
|
|
221
221
|
sessionContextRequired: '当前上下文没有会话,请显式指定会话 ID。',
|
|
222
222
|
serverUrlInvalid: 'MAR_CTL_SERVER_URL 无效。',
|
|
223
|
-
serverUrlTransport: 'MAR_CTL_SERVER_URL 必须使用
|
|
223
|
+
serverUrlTransport: 'MAR_CTL_SERVER_URL 必须使用 HTTP 或 HTTPS。',
|
|
224
224
|
serverUrlOrigin: 'MAR_CTL_SERVER_URL 必须是不含凭据或查询参数的 Origin。',
|
|
225
225
|
fetchUnavailable: '当前 Node.js 运行时没有 Fetch。',
|
|
226
226
|
serverInvalidNodes: 'Server 返回的节点列表无效。',
|
|
@@ -55,67 +55,161 @@ async function queryScreens(binary, signal) {
|
|
|
55
55
|
throw new RemoteDesktopError('DESKTOP_TOPOLOGY_UNAVAILABLE');
|
|
56
56
|
return parsed.displays;
|
|
57
57
|
}
|
|
58
|
-
/** Owns
|
|
58
|
+
/** Owns per-display capture identities and backend pools; selecting a display never changes another page. */
|
|
59
59
|
export class MacosRemoteDesktopRuntime {
|
|
60
60
|
options;
|
|
61
|
-
#
|
|
62
|
-
#
|
|
63
|
-
#
|
|
61
|
+
#pools = new Set();
|
|
62
|
+
#entries = new Map();
|
|
63
|
+
#screens = [];
|
|
64
64
|
#signature = '';
|
|
65
65
|
#revision = 0;
|
|
66
66
|
#disposed = false;
|
|
67
|
+
#topologyAvailable = false;
|
|
68
|
+
#refreshTail = Promise.resolve();
|
|
67
69
|
constructor(options) {
|
|
68
70
|
this.options = options;
|
|
69
|
-
|
|
71
|
+
}
|
|
72
|
+
#createPool(displayId) {
|
|
73
|
+
const pool = new TigerVncBackendPool(async (signal, onUnexpectedExit) => {
|
|
74
|
+
const signature = this.#signature;
|
|
75
|
+
const screens = await this.#probeTopology(signal);
|
|
76
|
+
if (JSON.stringify(screens) !== this.#signature)
|
|
77
|
+
this.#invalidateTopology();
|
|
78
|
+
const displayIndex = screens.findIndex((screen) => screen.id === displayId);
|
|
79
|
+
if (this.#disposed ||
|
|
80
|
+
!this.#topologyAvailable ||
|
|
81
|
+
displayIndex < 0 ||
|
|
82
|
+
this.#signature !== signature)
|
|
83
|
+
throw new RemoteDesktopError('DESKTOP_TOPOLOGY_UNAVAILABLE');
|
|
84
|
+
const backend = await MacVncManagedProcess.start({ binaryPath: this.options.binaryPath, displayIndex, onUnexpectedExit }, signal);
|
|
85
|
+
// The pool owns cleanup before the post-launch check can fail. A failed stop
|
|
86
|
+
// must retain the backend/reference so its attachment can retry cleanup.
|
|
87
|
+
return {
|
|
88
|
+
stop: () => backend.stop(),
|
|
89
|
+
connect: async (maxPixels, connectSignal) => {
|
|
90
|
+
const current = await this.#probeTopology(connectSignal);
|
|
91
|
+
if (JSON.stringify(current) !== this.#signature)
|
|
92
|
+
this.#invalidateTopology();
|
|
93
|
+
if (this.#disposed || !this.#topologyAvailable || this.#signature !== signature)
|
|
94
|
+
throw new RemoteDesktopError('DESKTOP_TOPOLOGY_UNAVAILABLE');
|
|
95
|
+
return backend.connect(maxPixels, connectSignal);
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
}, () => this.options.fence('DESKTOP_BACKEND_CLOSED'));
|
|
99
|
+
this.#pools.add(pool);
|
|
100
|
+
return pool;
|
|
70
101
|
}
|
|
71
102
|
static async start(options, signal) {
|
|
72
103
|
const runtime = new MacosRemoteDesktopRuntime(options);
|
|
73
104
|
await runtime.refresh(signal);
|
|
74
105
|
return runtime;
|
|
75
106
|
}
|
|
76
|
-
|
|
77
|
-
const
|
|
107
|
+
refresh(signal) {
|
|
108
|
+
const result = this.#refreshTail.then(() => this.#refresh(signal));
|
|
109
|
+
this.#refreshTail = result.catch(() => { });
|
|
110
|
+
return result;
|
|
111
|
+
}
|
|
112
|
+
#invalidateTopology() {
|
|
113
|
+
const wasAvailable = this.#topologyAvailable;
|
|
114
|
+
this.#topologyAvailable = false;
|
|
115
|
+
if (wasAvailable)
|
|
116
|
+
this.options.fence('DESKTOP_TOPOLOGY_UNAVAILABLE');
|
|
117
|
+
}
|
|
118
|
+
async #probeTopology(signal) {
|
|
119
|
+
if (this.#disposed)
|
|
120
|
+
throw new RemoteDesktopError('DESKTOP_BACKEND_CLOSED');
|
|
121
|
+
try {
|
|
122
|
+
return await queryScreens(this.options.topologyBinary, signal);
|
|
123
|
+
}
|
|
124
|
+
catch (error) {
|
|
125
|
+
// Closing one page aborts its probe; it is not evidence of desktop loss.
|
|
126
|
+
if (!signal.aborted)
|
|
127
|
+
this.#invalidateTopology();
|
|
128
|
+
throw error;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
async #refresh(signal) {
|
|
132
|
+
// Keep native enumeration order: macVNC's -display takes an index, not a display ID.
|
|
133
|
+
const screens = await this.#probeTopology(signal);
|
|
78
134
|
const signature = JSON.stringify(screens);
|
|
79
135
|
if (this.#disposed)
|
|
80
136
|
throw new RemoteDesktopError('DESKTOP_BACKEND_CLOSED');
|
|
81
|
-
if (signature === this.#signature)
|
|
137
|
+
if (signature === this.#signature && this.#topologyAvailable)
|
|
82
138
|
return;
|
|
83
|
-
|
|
84
|
-
|
|
139
|
+
// Revoke old captures before validating new geometry. A failed probe/layout
|
|
140
|
+
// must not leave the previous grants current or revive them after recovery.
|
|
141
|
+
this.#invalidateTopology();
|
|
142
|
+
const selectableDisplays = screens.map((screen) => ({
|
|
143
|
+
displayId: `display_${screen.id}`,
|
|
144
|
+
name: `Display ${screen.id}`,
|
|
145
|
+
primary: screen.primary
|
|
146
|
+
}));
|
|
147
|
+
const layouts = screens.map((screen) => {
|
|
148
|
+
const displayId = `display_${screen.id}`;
|
|
149
|
+
const layout = remoteDesktopDescriptionSchema.parse({
|
|
150
|
+
...macosCombinedDesktopLayout([{ ...screen, primary: true }], this.#entries.get(displayId)?.layout.desktopGeneration ?? randomUUID(), this.#revision + 1),
|
|
151
|
+
selectableDisplays
|
|
152
|
+
});
|
|
153
|
+
if (layout.framebuffer.width * layout.framebuffer.height > this.options.maxPixels)
|
|
154
|
+
throw new RemoteDesktopError('DESKTOP_TOPOLOGY_UNAVAILABLE');
|
|
155
|
+
return { screen, displayId, layout };
|
|
156
|
+
});
|
|
157
|
+
if (!layouts.length)
|
|
85
158
|
throw new RemoteDesktopError('DESKTOP_TOPOLOGY_UNAVAILABLE');
|
|
86
|
-
|
|
87
|
-
|
|
159
|
+
this.#entries = new Map(layouts.map(({ screen, displayId, layout }) => [
|
|
160
|
+
displayId,
|
|
161
|
+
{ layout, pool: this.#entries.get(displayId)?.pool ?? this.#createPool(screen.id) }
|
|
162
|
+
]));
|
|
163
|
+
this.#screens = screens;
|
|
88
164
|
this.#signature = signature;
|
|
89
165
|
this.#revision++;
|
|
90
|
-
this.#
|
|
166
|
+
this.#topologyAvailable = true;
|
|
91
167
|
}
|
|
92
|
-
describe() {
|
|
93
|
-
|
|
168
|
+
describe(selection = {}) {
|
|
169
|
+
const entry = selection.desktopGeneration !== undefined
|
|
170
|
+
? [...this.#entries.values()].find((candidate) => candidate.layout.desktopGeneration === selection.desktopGeneration)
|
|
171
|
+
: this.#entries.get(selection.displayId ?? `display_${this.#screens.find((screen) => screen.primary)?.id}`);
|
|
172
|
+
if (this.#disposed)
|
|
94
173
|
throw new RemoteDesktopError('DESKTOP_BACKEND_CLOSED');
|
|
95
|
-
|
|
174
|
+
if (!this.#topologyAvailable)
|
|
175
|
+
throw new RemoteDesktopError('DESKTOP_TOPOLOGY_UNAVAILABLE');
|
|
176
|
+
if (!entry)
|
|
177
|
+
throw new RemoteDesktopError('DESKTOP_TOPOLOGY_UNAVAILABLE');
|
|
178
|
+
if (!entry.pool.isAvailable())
|
|
179
|
+
throw new RemoteDesktopError('DESKTOP_BACKEND_CLOSED');
|
|
180
|
+
return structuredClone(entry.layout);
|
|
96
181
|
}
|
|
97
182
|
isCurrent = (command) => !this.#disposed &&
|
|
98
|
-
this.#
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
createAttachment = (command, frontend, callbacks) =>
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
183
|
+
this.#topologyAvailable &&
|
|
184
|
+
[...this.#entries.values()].some((entry) => entry.pool.isAvailable() &&
|
|
185
|
+
entry.layout.desktopGeneration === command.desktopGeneration &&
|
|
186
|
+
entry.layout.topologyRevision === command.topologyRevision);
|
|
187
|
+
createAttachment = (command, frontend, callbacks) => {
|
|
188
|
+
const entry = [...this.#entries.values()].find((candidate) => candidate.layout.desktopGeneration === command.desktopGeneration);
|
|
189
|
+
if (!entry || !this.isCurrent(command))
|
|
190
|
+
throw new RemoteDesktopError('DESKTOP_TOPOLOGY_UNAVAILABLE');
|
|
191
|
+
return new PooledDesktopAttachment(entry.pool, frontend, (backend) => new RemoteDesktopAttachment({
|
|
192
|
+
attachmentId: command.attachmentId,
|
|
193
|
+
frontend,
|
|
194
|
+
backend,
|
|
195
|
+
mode: command.mode,
|
|
196
|
+
maxPixels: this.options.maxPixels,
|
|
197
|
+
isCurrentTopology: (width, height) => this.isCurrent(command) &&
|
|
198
|
+
entry.layout.framebuffer.width === width &&
|
|
199
|
+
entry.layout.framebuffer.height === height,
|
|
200
|
+
onAttached: callbacks.attached,
|
|
201
|
+
onFailure: callbacks.failed
|
|
202
|
+
}));
|
|
203
|
+
};
|
|
114
204
|
async dispose() {
|
|
115
205
|
if (!this.#disposed) {
|
|
116
206
|
this.#disposed = true;
|
|
117
207
|
this.options.fence('DESKTOP_SESSION_UNAVAILABLE');
|
|
118
208
|
}
|
|
119
|
-
await this.#pool.dispose();
|
|
209
|
+
const results = await Promise.allSettled([...this.#pools].map((pool) => pool.dispose()));
|
|
210
|
+
for (const result of results)
|
|
211
|
+
if (result.status === 'rejected')
|
|
212
|
+
throw result.reason;
|
|
213
|
+
this.#pools.clear();
|
|
120
214
|
}
|
|
121
215
|
}
|
|
@@ -47,6 +47,11 @@ export class MacVncManagedProcess {
|
|
|
47
47
|
static async start(options, signal) {
|
|
48
48
|
if (process.platform !== 'darwin' || !isAbsolute(options.binaryPath))
|
|
49
49
|
throw new RemoteDesktopError('DESKTOP_BACKEND_CONFIG_INVALID');
|
|
50
|
+
if (options.displayIndex !== undefined &&
|
|
51
|
+
(!Number.isInteger(options.displayIndex) ||
|
|
52
|
+
options.displayIndex < 0 ||
|
|
53
|
+
options.displayIndex >= 64))
|
|
54
|
+
throw new RemoteDesktopError('DESKTOP_BACKEND_CONFIG_INVALID');
|
|
50
55
|
const deadline = AbortSignal.any([signal, AbortSignal.timeout(10_000)]);
|
|
51
56
|
if (deadline.aborted)
|
|
52
57
|
throw new RemoteDesktopError('DESKTOP_SETUP_CANCELLED');
|
|
@@ -79,7 +84,8 @@ export class MacVncManagedProcess {
|
|
|
79
84
|
'127.0.0.1',
|
|
80
85
|
'-rfbauth',
|
|
81
86
|
config,
|
|
82
|
-
'-alwaysshared'
|
|
87
|
+
'-alwaysshared',
|
|
88
|
+
...(options.displayIndex === undefined ? [] : ['-display', String(options.displayIndex)])
|
|
83
89
|
], {
|
|
84
90
|
cwd: dirname(options.binaryPath),
|
|
85
91
|
windowsHide: true,
|
|
@@ -47,7 +47,7 @@ export class NodeRequestService {
|
|
|
47
47
|
await this.options.authorizeMarctlOperation(marctlCaller, payload.operation, payload.data, authorization.userId);
|
|
48
48
|
}
|
|
49
49
|
const usageGrant = payload.userAuthorization.usageGrant;
|
|
50
|
-
const data = payload.data !== null && typeof payload.data === 'object'
|
|
50
|
+
const data = payload.data !== null && typeof payload.data === 'object' && !Array.isArray(payload.data)
|
|
51
51
|
? (() => {
|
|
52
52
|
const { __marctlCaller: _untrustedCaller, __usageGrant: _untrustedGrant, __marctlExecutionResources: _untrustedExecutionResources, __requestAbortSignal: _untrustedSignal, ...input } = payload.data;
|
|
53
53
|
return {
|
|
@@ -18,11 +18,7 @@ export class NodeUpgradeReporter {
|
|
|
18
18
|
endpoint.protocol = 'http:';
|
|
19
19
|
if (endpoint.protocol === 'wss:')
|
|
20
20
|
endpoint.protocol = 'https:';
|
|
21
|
-
if (endpoint.username ||
|
|
22
|
-
endpoint.password ||
|
|
23
|
-
(endpoint.protocol !== 'https:' &&
|
|
24
|
-
!(endpoint.protocol === 'http:' &&
|
|
25
|
-
['localhost', '127.0.0.1', '[::1]'].includes(endpoint.hostname))))
|
|
21
|
+
if (endpoint.username || endpoint.password || !['http:', 'https:'].includes(endpoint.protocol))
|
|
26
22
|
throw new Error('NODE_UPGRADE_REPORT_ORIGIN_INVALID');
|
|
27
23
|
endpoint.pathname = `/api/nodes/${encodeURIComponent(nodeId)}/upgrade/report`;
|
|
28
24
|
endpoint.search = '';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@myagentroam/node",
|
|
3
|
-
"version": "0.13.
|
|
3
|
+
"version": "0.13.15",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/myagentroam/mar.git",
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"dependencies": {
|
|
38
38
|
"@anthropic-ai/claude-agent-sdk": "0.3.233",
|
|
39
39
|
"@modelcontextprotocol/sdk": "1.32.0",
|
|
40
|
-
"@myagentroam/binaries": "0.10.
|
|
40
|
+
"@myagentroam/binaries": "0.10.4",
|
|
41
41
|
"@opencode-ai/sdk": "1.18.20",
|
|
42
42
|
"minimatch": "^10.2.6",
|
|
43
43
|
"node-datachannel": "0.33.4",
|
|
@@ -49,13 +49,13 @@
|
|
|
49
49
|
"turndown-plugin-gfm": "^1.0.2",
|
|
50
50
|
"ws": "^8.21.3",
|
|
51
51
|
"zod": "4.4.3",
|
|
52
|
-
"@myagentroam/
|
|
53
|
-
"@myagentroam/
|
|
52
|
+
"@myagentroam/agent": "0.13.15",
|
|
53
|
+
"@myagentroam/protocol": "0.13.15"
|
|
54
54
|
},
|
|
55
55
|
"devDependencies": {
|
|
56
56
|
"@types/turndown": "^5.0.6",
|
|
57
57
|
"@types/ws": "^8.18.1",
|
|
58
|
-
"@myagentroam/marctl": "0.13.
|
|
58
|
+
"@myagentroam/marctl": "0.13.15",
|
|
59
59
|
"@myagentroam/code-navigation": "0.11.1"
|
|
60
60
|
},
|
|
61
61
|
"exports": {
|
|
@@ -76,6 +76,7 @@
|
|
|
76
76
|
"test:unit": "vitest run --config vitest.config.mts && node --test test/unit/*.test.mjs",
|
|
77
77
|
"test:e2e:real": "vitest run --config vitest.e2e.config.mts",
|
|
78
78
|
"test:e2e:computer-use:windows": "node --test test/e2e/computer-use-windows.test.mjs",
|
|
79
|
-
"test:e2e:remote-desktop:windows": "node --test test/e2e/remote-desktop-windows.test.mjs"
|
|
79
|
+
"test:e2e:remote-desktop:windows": "node --test test/e2e/remote-desktop-windows.test.mjs",
|
|
80
|
+
"test:e2e:remote-desktop:macos": "node --test test/e2e/remote-desktop-macos.test.mjs"
|
|
80
81
|
}
|
|
81
82
|
}
|