@logictan/dsh-desktop-agent 0.1.2 → 0.1.4
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/README.md +8 -2
- package/cordis.patch.yml +8 -2
- package/lib/driver.js +177 -0
- package/lib/index.js +17 -7
- package/package.json +18 -5
package/README.md
CHANGED
|
@@ -29,8 +29,14 @@ describing a picture it never received — and answering confidently about it.
|
|
|
29
29
|
|
|
30
30
|
## Requirements
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
None beyond installing this plugin. It publishes the `cua_driver_native__*` tools
|
|
33
|
+
itself, from the Cua Driver native SDK it depends on, and dispatches to them.
|
|
34
|
+
|
|
35
|
+
A profile that still mounts the old
|
|
36
|
+
`@deepseek-ai/dsh-experimental-computer-use-cua-driver-native` provider must drop
|
|
37
|
+
that row and its `@deepseek-ai/dsh-computer-use` dependency first: both would
|
|
38
|
+
publish the same tool names, and the second registration is refused with
|
|
39
|
+
`tool "cua_driver_native__click" is already registered`.
|
|
34
40
|
|
|
35
41
|
## Settings
|
|
36
42
|
|
package/cordis.patch.yml
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
# dsh-desktop-agent bundle patch layer.
|
|
2
2
|
#
|
|
3
3
|
# One bare plugin row. The package ships two halves — the host half
|
|
4
|
-
# (exports ".") registers the `desktop_agent` model tool
|
|
5
|
-
# `
|
|
4
|
+
# (exports ".") registers the `desktop_agent` model tool, publishes the
|
|
5
|
+
# `cua_driver_native__*` driver tools from `src/driver.js`, and exports the
|
|
6
|
+
# `Config` schema the Plugins page derives its form from; the browser half
|
|
6
7
|
# (exports "./client") registers the Plugins-page configuration card.
|
|
7
8
|
#
|
|
8
9
|
# The row must stay a BARE package name: the web plugin table locates each
|
|
@@ -13,6 +14,11 @@
|
|
|
13
14
|
# Row id `desktop-agent` equals the host half's `export const name` in
|
|
14
15
|
# src/index.js. It is checked by hand against every other row id in this
|
|
15
16
|
# repository; `aggregate.mjs --check` does not validate id uniqueness.
|
|
17
|
+
#
|
|
18
|
+
# This row replaces the two hand-written rows a profile used to need
|
|
19
|
+
# (`computer-use` + `computer-use-cua-driver-native`): the driver runtime now
|
|
20
|
+
# lives in this package, so mounting it twice would collide at tool
|
|
21
|
+
# registration.
|
|
16
22
|
- insert:
|
|
17
23
|
- id: desktop-agent
|
|
18
24
|
name: '@logictan/dsh-desktop-agent'
|
package/lib/driver.js
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Cua Driver native runtime — the driver half.
|
|
3
|
+
*
|
|
4
|
+
* Absorbed from `@deepseek-ai/dsh-experimental-computer-use-cua-driver-native`,
|
|
5
|
+
* which the profile used to mount as its own loader row. The desktop agent drives
|
|
6
|
+
* the desktop exclusively by dispatching to the driver's `cua_driver_native__*`
|
|
7
|
+
* tools through the tool registry, so those tools have to be published by
|
|
8
|
+
* whoever owns the native handle; keeping that ownership in this package makes
|
|
9
|
+
* the plugin self-contained instead of leaving it inert without a second row.
|
|
10
|
+
*
|
|
11
|
+
* What the absorption deliberately drops: the `computerUse` registration slot.
|
|
12
|
+
* That slot existed to keep two computer-use providers from claiming the same
|
|
13
|
+
* desktop, and the plugin is now the only publisher of these tools — a profile
|
|
14
|
+
* that still mounts the old provider collides at `tools.register` with
|
|
15
|
+
* `tool "cua_driver_native__click" is already registered`, which is the honest
|
|
16
|
+
* signal that the old row must go.
|
|
17
|
+
*
|
|
18
|
+
* @module @logictan/dsh-desktop-agent/driver
|
|
19
|
+
*/
|
|
20
|
+
import { createMcpToolDefinition } from '@deepseek-ai/dsh-mcp-client';
|
|
21
|
+
|
|
22
|
+
/** Every published tool name carries this prefix; the loop's dispatch names depend on it. */
|
|
23
|
+
export const TOOL_PREFIX = 'cua_driver_native__';
|
|
24
|
+
|
|
25
|
+
/** DeepSeek's function-name alphabet and maximum length are protocol constants. */
|
|
26
|
+
const TOOL_NAME = /^[A-Za-z0-9_-]{1,64}$/u;
|
|
27
|
+
|
|
28
|
+
/** The prompt section this runtime owns. */
|
|
29
|
+
const SECTION_NAME = 'computer-use:cua-driver-native';
|
|
30
|
+
|
|
31
|
+
const GUIDANCE = `Cua Driver native computer-use tools operate the host desktop. Discover the exact app and window, then get a fresh window snapshot before acting. Use element_token from that snapshot, or coordinates from its screenshot. A new snapshot of that window invalidates its earlier element tokens. Select either target or the legacy pid/window_id fields; do not combine them.
|
|
32
|
+
|
|
33
|
+
Prefer background delivery. A refusal does not authorize a foreground retry. Verify the requested outcome from fresh state after an action; a delivered click alone does not prove the outcome. After cancellation, inspect current state before retrying because completed input is not rolled back. Other sessions and applications may change the same desktop.
|
|
34
|
+
|
|
35
|
+
On macOS, cursor-overlay operations may return facility_unavailable even when screenshots and input work.`;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Load the native SDK module.
|
|
39
|
+
*
|
|
40
|
+
* The one external boundary this plugin cannot validate away: the driver ships
|
|
41
|
+
* as per-platform native binaries, so the module is imported at the point of use
|
|
42
|
+
* rather than at module load, and a missing binary fails the mount with the
|
|
43
|
+
* resolver's own message instead of a module-scope import error.
|
|
44
|
+
*
|
|
45
|
+
* @returns the SDK module namespace.
|
|
46
|
+
*/
|
|
47
|
+
function loadNativeSdk() {
|
|
48
|
+
return import('@trycua/cua-driver');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Read the tool catalog out of the driver's JSON.
|
|
53
|
+
*
|
|
54
|
+
* The SDK marshals this from a native process, so it is a real untrusted
|
|
55
|
+
* boundary: a shape change must be reported here, not carried into tool
|
|
56
|
+
* registration where it would surface as an unexplained schema error.
|
|
57
|
+
*
|
|
58
|
+
* @param value - the parsed catalog payload.
|
|
59
|
+
* @returns the catalog entries.
|
|
60
|
+
* @throws {Error} when the payload is not a catalog.
|
|
61
|
+
*/
|
|
62
|
+
function readCatalog(value) {
|
|
63
|
+
const tools = value?.tools;
|
|
64
|
+
if (!Array.isArray(tools)) throw new Error('Cua Driver returned no tool catalog.');
|
|
65
|
+
return tools;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Mount the native runtime on `ctx`.
|
|
70
|
+
*
|
|
71
|
+
* The effect is registered before the first `await` so that a failure anywhere
|
|
72
|
+
* after the native handle exists still releases it, and so that unloading the
|
|
73
|
+
* plugin mid-startup aborts the in-flight discovery call instead of waiting for
|
|
74
|
+
* it to finish.
|
|
75
|
+
*
|
|
76
|
+
* @param ctx - the plugin context, providing `tools` and optionally `systemPrompt`.
|
|
77
|
+
* @param loadSdk - the SDK loader; overridden by tests, which cannot import the native binaries.
|
|
78
|
+
* @returns the effect disposer that tears the runtime down.
|
|
79
|
+
* @throws {Error} when the SDK cannot be loaded, the catalog is unusable, or a registration is refused.
|
|
80
|
+
*/
|
|
81
|
+
export async function mountDriver(ctx, loadSdk = loadNativeSdk) {
|
|
82
|
+
const lifetime = new AbortController();
|
|
83
|
+
const pending = new Set();
|
|
84
|
+
let driver;
|
|
85
|
+
|
|
86
|
+
const dispose = ctx.effect(
|
|
87
|
+
() => async () => {
|
|
88
|
+
lifetime.abort();
|
|
89
|
+
// Shutting the native handle down under an in-flight call would release
|
|
90
|
+
// memory the call still reads, so settlement comes first.
|
|
91
|
+
await Promise.allSettled(pending);
|
|
92
|
+
if (driver !== undefined) {
|
|
93
|
+
await driver.shutdown();
|
|
94
|
+
driver.uniffiDestroy();
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
'desktop-agent: cua driver runtime',
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
try {
|
|
101
|
+
const { CuaDriver } = await loadSdk();
|
|
102
|
+
lifetime.signal.throwIfAborted();
|
|
103
|
+
// The generated constructor returns its class with an owned binding handle,
|
|
104
|
+
// but declares only CuaDriverLike, which omits uniffiDestroy().
|
|
105
|
+
driver = CuaDriver.create(undefined);
|
|
106
|
+
await publishTools(ctx, driver, lifetime, pending);
|
|
107
|
+
} catch (error) {
|
|
108
|
+
await dispose();
|
|
109
|
+
throw error;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return dispose;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Publish one model tool per catalog entry and the usage guidance section.
|
|
117
|
+
*
|
|
118
|
+
* @param ctx - the plugin context.
|
|
119
|
+
* @param driver - the live native handle.
|
|
120
|
+
* @param lifetime - the runtime's abort source.
|
|
121
|
+
* @param pending - the in-flight native calls the teardown waits for.
|
|
122
|
+
*/
|
|
123
|
+
async function publishTools(ctx, driver, lifetime, pending) {
|
|
124
|
+
const tools = ctx.get('tools');
|
|
125
|
+
const catalog = readCatalog(JSON.parse(await driver.listToolsJson({ signal: lifetime.signal })));
|
|
126
|
+
lifetime.signal.throwIfAborted();
|
|
127
|
+
|
|
128
|
+
const names = new Set();
|
|
129
|
+
for (const tool of catalog) {
|
|
130
|
+
if (typeof tool?.name !== 'string' || tool.name === '') {
|
|
131
|
+
throw new Error('Cua Driver listed a tool without a name.');
|
|
132
|
+
}
|
|
133
|
+
const publicName = TOOL_PREFIX + tool.name;
|
|
134
|
+
if (!TOOL_NAME.test(publicName)) {
|
|
135
|
+
throw new Error(`Cua Driver tool "${tool.name}" exceeds the supported function-name format`);
|
|
136
|
+
}
|
|
137
|
+
if (names.has(publicName)) throw new Error(`Cua Driver listed tool "${tool.name}" more than once`);
|
|
138
|
+
names.add(publicName);
|
|
139
|
+
|
|
140
|
+
tools.register(createMcpToolDefinition(ctx, {
|
|
141
|
+
name: publicName,
|
|
142
|
+
rawName: tool.name,
|
|
143
|
+
description: tool.description ?? '',
|
|
144
|
+
inputSchema: tool.inputSchema,
|
|
145
|
+
outputSchema: tool.outputSchema,
|
|
146
|
+
async call(args, execution) {
|
|
147
|
+
// Two independent cancellations: the caller giving up, and the plugin
|
|
148
|
+
// being unloaded. The native call must observe both.
|
|
149
|
+
const combined = AbortSignal.any([execution.signal, lifetime.signal]);
|
|
150
|
+
combined.throwIfAborted();
|
|
151
|
+
const operation = driver.callTool(tool.name, JSON.stringify(args), { signal: combined });
|
|
152
|
+
pending.add(operation);
|
|
153
|
+
try {
|
|
154
|
+
const result = await operation;
|
|
155
|
+
combined.throwIfAborted();
|
|
156
|
+
return JSON.parse(result.rawJson);
|
|
157
|
+
} finally {
|
|
158
|
+
pending.delete(operation);
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
}));
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const systemPrompt = ctx.get('systemPrompt');
|
|
165
|
+
if (systemPrompt === null || systemPrompt === undefined) {
|
|
166
|
+
ctx.logger?.warn?.('desktop-agent: the systemPrompt service is unavailable; the Cua Driver guidance was not registered.');
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
ctx.effect(
|
|
170
|
+
() => systemPrompt.section({
|
|
171
|
+
name: SECTION_NAME,
|
|
172
|
+
order: systemPrompt.getSectionOrder('TOOL_COMPUTER_USE'),
|
|
173
|
+
text: GUIDANCE,
|
|
174
|
+
}),
|
|
175
|
+
'desktop-agent: cua driver guidance',
|
|
176
|
+
);
|
|
177
|
+
}
|
package/lib/index.js
CHANGED
|
@@ -6,10 +6,11 @@
|
|
|
6
6
|
* card reads its model list from.
|
|
7
7
|
*
|
|
8
8
|
* The plugin owns no execution engine of its own. Every action is dispatched to
|
|
9
|
-
* the `cua_driver_native__*` tools
|
|
10
|
-
* registry's own `execute` pipeline
|
|
11
|
-
* the same guards, timeouts, and cancellation as one the model made
|
|
12
|
-
* the driver's own coordinate and delivery contracts are the only
|
|
9
|
+
* the `cua_driver_native__*` tools this package publishes — see `./driver.js` —
|
|
10
|
+
* through the tool registry's own `execute` pipeline, so a call this plugin makes
|
|
11
|
+
* is subject to the same guards, timeouts, and cancellation as one the model made
|
|
12
|
+
* directly, and the driver's own coordinate and delivery contracts are the only
|
|
13
|
+
* ones in play.
|
|
13
14
|
*
|
|
14
15
|
* Configuration arrives as `apply`'s second argument. As of the 0.1.7-alpha.1
|
|
15
16
|
* settings redesign the plugin's own `Config` schema is the only config surface —
|
|
@@ -42,6 +43,7 @@ import { defineTool } from '@deepseek-ai/dsh-tools';
|
|
|
42
43
|
import { ToolCallId } from '@deepseek-ai/dsh-llm';
|
|
43
44
|
|
|
44
45
|
import { ENTRY_ID, resolveVisionRoute } from './config.js';
|
|
46
|
+
import { mountDriver } from './driver.js';
|
|
45
47
|
import { run } from './loop.js';
|
|
46
48
|
import { structured } from './observe.js';
|
|
47
49
|
import { makeRoutes } from './routes.js';
|
|
@@ -61,12 +63,16 @@ export const name = ENTRY_ID;
|
|
|
61
63
|
export const inject = [];
|
|
62
64
|
|
|
63
65
|
/**
|
|
64
|
-
* Register the `desktop_agent` tool
|
|
66
|
+
* Register the `desktop_agent` tool, the vision-catalog route, and the Cua
|
|
67
|
+
* Driver runtime the tool dispatches to.
|
|
65
68
|
*
|
|
66
69
|
* @param ctx - the plugin context.
|
|
67
70
|
* @param config - one live reference per {@link Config} field.
|
|
71
|
+
* @param loadSdk - the native SDK loader, forwarded to {@link mountDriver}; tests
|
|
72
|
+
* pass one because the driver ships native binaries that are not installed in
|
|
73
|
+
* this workspace, and activation must not depend on a binary the test cannot load.
|
|
68
74
|
*/
|
|
69
|
-
export function apply(ctx, config) {
|
|
75
|
+
export async function apply(ctx, config, loadSdk) {
|
|
70
76
|
const tools = ctx.get('tools');
|
|
71
77
|
if (tools === null || tools === undefined || typeof tools !== 'object') {
|
|
72
78
|
ctx.logger?.warn?.('desktop_agent: the tools service is unavailable; the tool was not registered.');
|
|
@@ -146,7 +152,7 @@ export function apply(ctx, config) {
|
|
|
146
152
|
'Resolves the target application, then runs an observation/decision loop: each step captures the window, ' +
|
|
147
153
|
'asks the configured model what to do, and performs the click, key, or text entry it chose. ' +
|
|
148
154
|
'Works on ordinary applications and on games that expose no usable accessibility tree, because it reads the ' +
|
|
149
|
-
'picture rather than the element table.
|
|
155
|
+
'picture rather than the element table. ' +
|
|
150
156
|
'Note that a covered window cannot be driven, and a game that filters injected input needs foreground ' +
|
|
151
157
|
'delivery — the plugin settings control that. This tool waits for the run to finish and returns a structured ' +
|
|
152
158
|
'trace of every step.',
|
|
@@ -215,6 +221,10 @@ export function apply(ctx, config) {
|
|
|
215
221
|
'desktop-agent: vision-model catalog route',
|
|
216
222
|
);
|
|
217
223
|
});
|
|
224
|
+
|
|
225
|
+
// Last, so that a driver that cannot start costs this plugin nothing else: a
|
|
226
|
+
// rejected mount rolls back the tool and route registrations above with it.
|
|
227
|
+
await mountDriver(ctx, loadSdk);
|
|
218
228
|
}
|
|
219
229
|
|
|
220
230
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@logictan/dsh-desktop-agent",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "DSH desktop agent: a native-vision decision loop that drives the host desktop through the Cua Driver tools. DSH 桌面 Agent 子插件。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -54,8 +54,10 @@
|
|
|
54
54
|
},
|
|
55
55
|
"peerDependencies": {
|
|
56
56
|
"@deepseek-ai/cordis": "^4.0.2",
|
|
57
|
-
"@deepseek-ai/dsh-llm": "^0.1.6-alpha.2",
|
|
58
|
-
"@deepseek-ai/dsh-
|
|
57
|
+
"@deepseek-ai/dsh-llm": "^0.1.5-alpha.1 || ^0.1.6-alpha.1 || ^0.1.7-alpha.1 || ^0.1.7-rc.1 || ^0.2.0-rc.1 || ^0.2.0-rc.2",
|
|
58
|
+
"@deepseek-ai/dsh-system-prompt": "^0.1.5-alpha.1 || ^0.1.6-alpha.1 || ^0.1.7-alpha.1 || ^0.1.7-rc.1 || ^0.2.0-rc.1 || ^0.2.0-rc.2",
|
|
59
|
+
"@deepseek-ai/dsh-mcp-client": "^0.1.5-alpha.1 || ^0.1.6-alpha.1 || ^0.1.7-alpha.1 || ^0.1.7-rc.1 || ^0.2.0-rc.1 || ^0.2.0-rc.2",
|
|
60
|
+
"@deepseek-ai/dsh-tools": "^0.1.5-alpha.1 || ^0.1.6-alpha.1 || ^0.1.7-alpha.1 || ^0.1.7-rc.1 || ^0.2.0-rc.1 || ^0.2.0-rc.2",
|
|
59
61
|
"@deepseek-ai/schemastery": "^3.18.3"
|
|
60
62
|
},
|
|
61
63
|
"peerDependenciesMeta": {
|
|
@@ -65,6 +67,12 @@
|
|
|
65
67
|
"@deepseek-ai/dsh-llm": {
|
|
66
68
|
"optional": true
|
|
67
69
|
},
|
|
70
|
+
"@deepseek-ai/dsh-mcp-client": {
|
|
71
|
+
"optional": true
|
|
72
|
+
},
|
|
73
|
+
"@deepseek-ai/dsh-system-prompt": {
|
|
74
|
+
"optional": true
|
|
75
|
+
},
|
|
68
76
|
"@deepseek-ai/dsh-tools": {
|
|
69
77
|
"optional": true
|
|
70
78
|
},
|
|
@@ -72,10 +80,15 @@
|
|
|
72
80
|
"optional": true
|
|
73
81
|
}
|
|
74
82
|
},
|
|
83
|
+
"dependencies": {
|
|
84
|
+
"@trycua/cua-driver": "0.28.0"
|
|
85
|
+
},
|
|
75
86
|
"devDependencies": {
|
|
76
87
|
"@deepseek-ai/cordis": "^4.0.2",
|
|
77
|
-
"@deepseek-ai/dsh-llm": "^0.
|
|
78
|
-
"@deepseek-ai/dsh-
|
|
88
|
+
"@deepseek-ai/dsh-llm": "^0.2.0-rc.2",
|
|
89
|
+
"@deepseek-ai/dsh-mcp-client": "^0.2.0-rc.2",
|
|
90
|
+
"@deepseek-ai/dsh-system-prompt": "^0.2.0-rc.2",
|
|
91
|
+
"@deepseek-ai/dsh-tools": "^0.2.0-rc.2",
|
|
79
92
|
"@deepseek-ai/schemastery": "^3.18.3"
|
|
80
93
|
}
|
|
81
94
|
}
|