dsh-plugin-lcu 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +316 -0
- package/cordis.patch.yml +23 -0
- package/dev-types/dsh.d.ts +292 -0
- package/docs/README.zh.md +291 -0
- package/lib/approval.js +152 -0
- package/lib/approval.js.map +1 -0
- package/lib/connection.js +384 -0
- package/lib/connection.js.map +1 -0
- package/lib/diag.js +52 -0
- package/lib/diag.js.map +1 -0
- package/lib/host-guard.js +137 -0
- package/lib/host-guard.js.map +1 -0
- package/lib/index.js +318 -0
- package/lib/index.js.map +1 -0
- package/lib/tool.js +262 -0
- package/lib/tool.js.map +1 -0
- package/lib/types/approval.d.ts +71 -0
- package/lib/types/approval.d.ts.map +1 -0
- package/lib/types/connection.d.ts +178 -0
- package/lib/types/connection.d.ts.map +1 -0
- package/lib/types/diag.d.ts +17 -0
- package/lib/types/diag.d.ts.map +1 -0
- package/lib/types/host-guard.d.ts +46 -0
- package/lib/types/host-guard.d.ts.map +1 -0
- package/lib/types/index.d.ts +49 -0
- package/lib/types/index.d.ts.map +1 -0
- package/lib/types/tool.d.ts +90 -0
- package/lib/types/tool.d.ts.map +1 -0
- package/package.json +79 -0
- package/scripts/codex-baseline.json +10 -0
- package/scripts/gen-presets.mjs +266 -0
- package/scripts/probe-lcu.mjs +154 -0
- package/scripts/update-codex.mjs +525 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Kanner
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
# dsh-plugin-lcu
|
|
2
|
+
|
|
3
|
+
English | [中文](docs/README.zh.md)
|
|
4
|
+
|
|
5
|
+
Drive the desktop and Chrome from [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) by
|
|
6
|
+
plugging in [LCU](https://github.com/amontlabs/lcu) — *Codex computer use, decoupled from the app*.
|
|
7
|
+
|
|
8
|
+
LCU exposes the computer-use runtime that ships inside the ChatGPT desktop app as an MCP server.
|
|
9
|
+
This plugin makes that runtime a first-class DSH capability: an Agent in an enabled mode gets a `js`
|
|
10
|
+
tool that can read and operate real application windows and real Chrome tabs. **No Codex
|
|
11
|
+
authentication is involved** — the runtime comes from your local ChatGPT installation, and LCU never
|
|
12
|
+
downloads, installs, authenticates, or rewrites it.
|
|
13
|
+
|
|
14
|
+
## Table of Contents
|
|
15
|
+
|
|
16
|
+
- [What you get](#what-you-get)
|
|
17
|
+
- [Requirements](#requirements)
|
|
18
|
+
- [Install](#install)
|
|
19
|
+
- [Use](#use)
|
|
20
|
+
- [Configuration](#configuration)
|
|
21
|
+
- [Approvals and the security model](#approvals-and-the-security-model)
|
|
22
|
+
- [Understand the implementation](#understand-the-implementation)
|
|
23
|
+
- [Troubleshooting](#troubleshooting)
|
|
24
|
+
- [Companion tools](#companion-tools)
|
|
25
|
+
- [Known limitations and deferred work](#known-limitations-and-deferred-work)
|
|
26
|
+
- [Development](#development)
|
|
27
|
+
- [License](#license)
|
|
28
|
+
|
|
29
|
+
## What you get
|
|
30
|
+
|
|
31
|
+
Two model-facing tools, exactly as LCU defines them — this plugin does not invent a schema:
|
|
32
|
+
|
|
33
|
+
| Tool | What it does |
|
|
34
|
+
|---|---|
|
|
35
|
+
| `js` | Run one JavaScript program against the `cua` desktop/browser API. The first call returns the API documentation, and app or tab selection returns the initial UI state. |
|
|
36
|
+
| `js_reset` | Discard the persistent JavaScript session and start a fresh runtime. |
|
|
37
|
+
|
|
38
|
+
Two host-only tools stay reachable by the plugin and are **never** shown to the model:
|
|
39
|
+
`turn_ended` (per-turn cleanup) and `js_add_node_module_dir`.
|
|
40
|
+
|
|
41
|
+
Screenshots arrive as durable images through DSH's attachment store, so a model route that declares
|
|
42
|
+
image input can actually look at the screen.
|
|
43
|
+
|
|
44
|
+
## Requirements
|
|
45
|
+
|
|
46
|
+
| | |
|
|
47
|
+
|---|---|
|
|
48
|
+
| OS | macOS on Apple Silicon. LCU supports Linux too, but this plugin is developed and verified on macOS. |
|
|
49
|
+
| ChatGPT desktop app | Installed and signed by OpenAI. It supplies the runtime and the instructions. |
|
|
50
|
+
| Python | 3.12 or newer, on `PATH` or in `/opt/homebrew/bin`, `/usr/local/bin`, `/usr/bin`. |
|
|
51
|
+
| LCU | Installed separately — see below. |
|
|
52
|
+
| DSH | A profile you can install a bundle into. |
|
|
53
|
+
|
|
54
|
+
The plugin targets **macOS**; the `host-guard` uses `ps`/`plutil` and the lifecycle uses LCU's macOS
|
|
55
|
+
path. Linux would need those two pieces revisited.
|
|
56
|
+
|
|
57
|
+
## Install
|
|
58
|
+
|
|
59
|
+
### 1. Install LCU
|
|
60
|
+
|
|
61
|
+
Download the release archive for your platform, verify its checksum, and run its installer. Do **not**
|
|
62
|
+
register another harness: this plugin is your harness.
|
|
63
|
+
|
|
64
|
+
```sh
|
|
65
|
+
TAG=v0.9.6
|
|
66
|
+
TARGET=darwin-arm64
|
|
67
|
+
curl -fLO "https://github.com/amontlabs/lcu/releases/download/$TAG/lcu-${TAG#v}-$TARGET.tar.gz"
|
|
68
|
+
curl -fLO "https://github.com/amontlabs/lcu/releases/download/$TAG/lcu-${TAG#v}-$TARGET.tar.gz.sha256"
|
|
69
|
+
shasum -a 256 -c "lcu-${TAG#v}-$TARGET.tar.gz.sha256" # must print OK
|
|
70
|
+
tar -xzf "lcu-${TAG#v}-$TARGET.tar.gz" && cd "lcu-${TAG#v}-$TARGET"
|
|
71
|
+
./scripts/install.sh --runtime-only --yes
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Then confirm the runtime loads:
|
|
75
|
+
|
|
76
|
+
```sh
|
|
77
|
+
~/.local/share/lcu/current/bin/lcu doctor --non-interactive
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
You want `Original Mac provider loaded; app listing and app-state methods are available`. Privacy
|
|
81
|
+
permissions are granted on first use, not here.
|
|
82
|
+
|
|
83
|
+
### 2. Install the plugin into a profile
|
|
84
|
+
|
|
85
|
+
Installing makes the plugin's one row — an LCU host — active in that profile. It opens nothing at load
|
|
86
|
+
time.
|
|
87
|
+
|
|
88
|
+
```sh
|
|
89
|
+
dsh plugin --profile <profile> add dsh-plugin-lcu
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
For the **desktop** app's managed profile, the CLI refuses; install it through the app's plugin
|
|
93
|
+
manager (Settings ▸ Plugins) instead, which runs the same pnpm operation.
|
|
94
|
+
|
|
95
|
+
### 3. Generate the presets
|
|
96
|
+
|
|
97
|
+
DSH agent presets have **no inheritance**: a preset's `config.plugins` is its complete plugin list, and
|
|
98
|
+
a patch replaces a whole entry rather than merging into it. So a custom preset must restate its base.
|
|
99
|
+
Rather than hand-copying that list, generate it from the preset that is actually installed:
|
|
100
|
+
|
|
101
|
+
```sh
|
|
102
|
+
node node_modules/dsh-plugin-lcu/scripts/gen-presets.mjs --profile ~/.dsh/profiles/<profile>
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
This writes a marked block into that profile's `cordis.patch.yml` containing two presets:
|
|
106
|
+
|
|
107
|
+
| Preset | Base | Adds |
|
|
108
|
+
|---|---|---|
|
|
109
|
+
| `daily` | the shipped `ptc` preset | `subagent_codex` enabled |
|
|
110
|
+
| `heavy` | the same, with `tool-presentation: both` | everything above, and this plugin attaches |
|
|
111
|
+
|
|
112
|
+
Re-run it after a DSH upgrade so the copies keep up. `--with-heavy` is implied; `--dry-run` prints
|
|
113
|
+
without writing, and `--out FILE` writes somewhere else.
|
|
114
|
+
|
|
115
|
+
> `heavy` sets the tool presentation to `both` on purpose. In pure `ptc` presentation the model only
|
|
116
|
+
> sees `run_code`, so `js` would have to be nested as a JavaScript string inside another JavaScript
|
|
117
|
+
> program. `both` keeps `js` directly callable.
|
|
118
|
+
|
|
119
|
+
### 4. Configure which modes get the capability
|
|
120
|
+
|
|
121
|
+
The plugin is one root row with a `presets` allowlist. Edit the installed
|
|
122
|
+
`cordis.patch.yml` (or the profile patch) to match the preset ids you generated:
|
|
123
|
+
|
|
124
|
+
```yaml
|
|
125
|
+
- id: lcu
|
|
126
|
+
name: 'dsh-plugin-lcu'
|
|
127
|
+
config:
|
|
128
|
+
presets:
|
|
129
|
+
- heavy
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### 5. Restart, then make one call
|
|
133
|
+
|
|
134
|
+
Restart the harness — plugin **code and configuration changes are not hot-reloaded**. Then start a
|
|
135
|
+
task in the `heavy` mode (displayed as **重活**) and ask it to do something harmless:
|
|
136
|
+
|
|
137
|
+
> Use the `js` tool to run `await cua.getState();` and tell me which apps are running.
|
|
138
|
+
|
|
139
|
+
The first time an app is touched, the runtime asks for approval. See below.
|
|
140
|
+
|
|
141
|
+
### Optional: enable Chrome
|
|
142
|
+
|
|
143
|
+
```yaml
|
|
144
|
+
config:
|
|
145
|
+
chrome: true
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Then:
|
|
149
|
+
|
|
150
|
+
```sh
|
|
151
|
+
~/.local/share/lcu/current/bin/lcu browser install
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Enable the official ChatGPT extension in the Chrome profile you want to drive, and restart Chrome (or
|
|
155
|
+
toggle the extension at `chrome://extensions`) so it reconnects through LCU's relay rather than Codex's.
|
|
156
|
+
`lcu browser status` reports whether the connector points at this installation. Sites stay
|
|
157
|
+
exact-origin approvals.
|
|
158
|
+
|
|
159
|
+
### Optional: pre-approve sites
|
|
160
|
+
|
|
161
|
+
Every site the runtime wants to use asks once. To skip the prompt for origins you trust, list exact
|
|
162
|
+
origins — the message must round-trip through `new URL(...).origin` unchanged:
|
|
163
|
+
|
|
164
|
+
```yaml
|
|
165
|
+
config:
|
|
166
|
+
allowedOrigins:
|
|
167
|
+
- http://localhost:3000
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
The diagnostic log names every origin that was asked, which is the easiest way to discover them.
|
|
171
|
+
|
|
172
|
+
## Use
|
|
173
|
+
|
|
174
|
+
Pick an enabled mode when you start a task. The tools are attached per Agent: a session in any other
|
|
175
|
+
mode never sees them, and never spawns the runtime.
|
|
176
|
+
|
|
177
|
+
Typical asks:
|
|
178
|
+
|
|
179
|
+
```
|
|
180
|
+
Take a screenshot of the Finder window and tell me its resolution.
|
|
181
|
+
List my current Chrome tabs.
|
|
182
|
+
Open Safari, go to example.com and read the page title.
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
## Configuration
|
|
186
|
+
|
|
187
|
+
| Field | Default | Meaning |
|
|
188
|
+
|---|---|---|
|
|
189
|
+
| `command` | `~/.local/share/lcu/current/bin/lcu` | LCU launcher. Set it for a custom `--prefix`. |
|
|
190
|
+
| `chrome` | `false` | Pass `--chrome` to enable the browser surface. |
|
|
191
|
+
| `audio` | `false` | Pass `--audio` to enable the runtime's computer-audio API. |
|
|
192
|
+
| `presets` | `["heavy"]` | Agent preset ids whose sessions get the tools. |
|
|
193
|
+
| `allowedOrigins` | `[]` | Exact HTTP(S) origins answered without asking. Invalid entries are dropped, never widened. |
|
|
194
|
+
| `sectionOrder` | `0` | Prompt section order for the injected LCU instructions. |
|
|
195
|
+
|
|
196
|
+
## Approvals and the security model
|
|
197
|
+
|
|
198
|
+
The model cannot approve anything. Every decision is a person's:
|
|
199
|
+
|
|
200
|
+
- **Per-app approval.** The runtime asks before it uses an app. The plugin renders its own choices —
|
|
201
|
+
*Allow once*, *Allow for this session* and *Always allow* when the runtime offers them, and
|
|
202
|
+
*Decline* — through DSH's question surface. An answer is mapped back to exactly what was offered; a
|
|
203
|
+
scope the runtime did not offer cannot be granted.
|
|
204
|
+
- **Site approval.** Browser access asks per exact origin. `allowedOrigins` only ever matches an exact
|
|
205
|
+
origin; a trailing slash, a path, or different case is a different origin and is asked, not granted.
|
|
206
|
+
- **The agent's own host is never approvable.** Computer use can click anything an approved app shows,
|
|
207
|
+
including the approval prompt itself. The guard refuses the application hosting the agent — its
|
|
208
|
+
process ancestry and a list of agent hosts and terminals — before any question is asked.
|
|
209
|
+
- **Fail closed.** No question surface, a dismissed prompt, an unrecognized request shape, or an
|
|
210
|
+
aborted call all end as *cancel*, which the runtime treats as a refusal.
|
|
211
|
+
|
|
212
|
+
LCU keeps no permission cache of its own; `Always allow` is remembered by the runtime, per app.
|
|
213
|
+
|
|
214
|
+
## Understand the implementation
|
|
215
|
+
|
|
216
|
+
```
|
|
217
|
+
src/connection.ts the MCP client: handshake, tool discovery, calls, elicitation, lifecycle
|
|
218
|
+
src/approval.ts approval-shape recognition and label→value mapping
|
|
219
|
+
src/host-guard.ts the anti-self-approval guard
|
|
220
|
+
src/tool.ts tool definitions, text projection, durable screenshots
|
|
221
|
+
src/index.ts the plugin: per-Agent attach, instructions, turn_ended, approvals
|
|
222
|
+
src/diag.ts the attach/approval diagnostic log
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
**No MCP SDK dependency.** The harness's own MCP bridge declares `capabilities: {}` and therefore
|
|
226
|
+
cannot answer elicitation — which is exactly how LCU asks for approval — and pulling a second SDK into a
|
|
227
|
+
profile plugin would pin a version the host does not own. MCP over stdio is newline-delimited JSON-RPC,
|
|
228
|
+
so the wire is owned here. Together with type-only imports of the DSH packages, the plugin has **no
|
|
229
|
+
runtime dependencies at all**.
|
|
230
|
+
|
|
231
|
+
**Tools are registered per Agent, not at mount.** The server owns the tool schemas, so they can only be
|
|
232
|
+
fetched after the handshake. An Agent's connection is opened when the Agent is created or when it
|
|
233
|
+
commits a preset choice, and everything the plugin contributes is registered into that Agent's own
|
|
234
|
+
context, so it unwinds on disposal.
|
|
235
|
+
|
|
236
|
+
**Both preset timings are handled.** A new task is created with the deployment default and the picker's
|
|
237
|
+
choice is applied afterwards, so `agent/created` alone would see the wrong composition; the registry
|
|
238
|
+
re-emits `agent-preset/selected`, and the plugin reacts to that too.
|
|
239
|
+
|
|
240
|
+
**Lazy by construction.** Nothing starts at load time. No enabled session, no `lcu` process.
|
|
241
|
+
|
|
242
|
+
**Instructions are injected.** The server's `initialize.instructions` becomes a prompt section on the
|
|
243
|
+
Agent. It is short by design — the API manual lives in the `js` tool description and in the first tool
|
|
244
|
+
result.
|
|
245
|
+
|
|
246
|
+
## Troubleshooting
|
|
247
|
+
|
|
248
|
+
Everything the plugin decides about attaching and approving is appended to:
|
|
249
|
+
|
|
250
|
+
```
|
|
251
|
+
~/.dsh/lcu-diag.log
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
It rotates by starting over past 1 MB. `LCU_DIAG=0` disables it. This is the first place to look: the
|
|
255
|
+
harness has no plugin-log surface a running session can read, and a failing `agent/created` listener is
|
|
256
|
+
otherwise swallowed silently.
|
|
257
|
+
|
|
258
|
+
| Symptom | Cause and fix |
|
|
259
|
+
|---|---|
|
|
260
|
+
| Tools never appear in an enabled mode | Check the log for `decide … composed=`. If the composed preset is not in `presets`, fix the allowlist. If there is no `agent-preset/selected` line, the mode was never committed. |
|
|
261
|
+
| `no userQuestions service -> cancel (fail closed)` | The approval surface is not mounted in this profile. |
|
|
262
|
+
| `refusing to approve the app hosting this agent` | Working as designed; ask for a different app. |
|
|
263
|
+
| Calls blocked after a turn | The runtime's turn cleanup had not settled; the plugin retries it before the next call and refuses until it does. |
|
|
264
|
+
| `lcu doctor` reports a socket-path error | The signed helper binds under your home folder and refuses a path over 103 bytes. Use an account with a shorter home path. |
|
|
265
|
+
| Attach fails with a spawn error | Run `~/.local/share/lcu/current/bin/lcu doctor` directly, then check `command` in the config. |
|
|
266
|
+
|
|
267
|
+
`node scripts/probe-lcu.mjs` talks to LCU with no harness involved and prints the protocol version,
|
|
268
|
+
server identity, instructions length and the tool list — useful to separate a plugin problem from an
|
|
269
|
+
LCU problem.
|
|
270
|
+
|
|
271
|
+
## Companion tools
|
|
272
|
+
|
|
273
|
+
`scripts/` also ships two tools for the sibling Codex subagent bundle, because the same profile
|
|
274
|
+
usually wants both:
|
|
275
|
+
|
|
276
|
+
- **`update-codex.mjs`** — keeps the profile's `@openai/codex` on the newest release that still passes
|
|
277
|
+
three gates (handshake, protocol-schema assertions, and a real turn), rolling back automatically when
|
|
278
|
+
one fails. The published `@deepseek-ai/dsh-subagent-codex` pins `0.153.4`, which does not serve every
|
|
279
|
+
current ChatGPT-account model; this bumps it through a profile-level, scoped pnpm override. See
|
|
280
|
+
`node scripts/update-codex.mjs --help` for `--check`, `--verify-only`, `--to` and `--rollback`.
|
|
281
|
+
- **`codex-baseline.json`** — the last version that passed all three gates.
|
|
282
|
+
|
|
283
|
+
## Known limitations and deferred work
|
|
284
|
+
|
|
285
|
+
- **A delegated child cannot be asked for approval.** DSH only accepts a human answer for a live
|
|
286
|
+
runtime root, so a subagent's LCU approval fails closed. Subagents can perform read-only work that
|
|
287
|
+
needs no approval; anything that needs one must be driven from the top-level session.
|
|
288
|
+
- **macOS turn cleanup can outlive the host's wait.** The signed helper occasionally answers the
|
|
289
|
+
`turn-ended` step slowly. The runtime keeps cleaning in the background and retries; the plugin blocks
|
|
290
|
+
the next call until it settles rather than acting on a half-torn-down desktop.
|
|
291
|
+
- **One LCU connection per Agent.** LCU's JavaScript session is per connection and its approvals are
|
|
292
|
+
bound to a real session and turn, so sharing one connection across Agents would interleave both.
|
|
293
|
+
- **`chrome` needs the extension.** Enabling the flag without the official ChatGPT extension, or without
|
|
294
|
+
`lcu browser install`, yields no browser surface.
|
|
295
|
+
- **Not verified on Linux.** See [Requirements](#requirements).
|
|
296
|
+
|
|
297
|
+
## Development
|
|
298
|
+
|
|
299
|
+
```sh
|
|
300
|
+
npm install
|
|
301
|
+
npm run typecheck # strict, noUncheckedIndexedAccess, exactOptionalPropertyTypes
|
|
302
|
+
npm run build # emits lib/
|
|
303
|
+
npm test # node --test, no build step
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
The connection suite talks to the **real** installed `lcu` and skips itself when none is present, so
|
|
307
|
+
`npm test` is meaningful locally and still passes on CI. The approval, projection and guard suites are
|
|
308
|
+
pure and always run.
|
|
309
|
+
|
|
310
|
+
Plugin code and configuration are **not hot-reloaded** by the harness: a running process keeps the
|
|
311
|
+
module it loaded. Rebuild and restart to see a change.
|
|
312
|
+
|
|
313
|
+
## License
|
|
314
|
+
|
|
315
|
+
MIT. LCU is MIT (Amont Labs); the ChatGPT application and its instructions remain under their own
|
|
316
|
+
terms and are used from your local installation.
|
package/cordis.patch.yml
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# The dsh-plugin-lcu bundle patch.
|
|
2
|
+
#
|
|
3
|
+
# One row. The plugin owns every LCU connection and registers the model-facing
|
|
4
|
+
# computer-use tools per Agent; it starts nothing at load time, so a session pays
|
|
5
|
+
# for the CUA runtime only when its Agent preset is listed here.
|
|
6
|
+
#
|
|
7
|
+
# `presets` is an explicit allowlist rather than a capability the preset
|
|
8
|
+
# declares, because the server owns the tool schemas: they can only be fetched
|
|
9
|
+
# after the MCP handshake, which happens once a matching Agent is created.
|
|
10
|
+
#
|
|
11
|
+
# `chrome: true` enables LCU's browser surface (`lcu --chrome`). It also needs
|
|
12
|
+
# the official ChatGPT extension enabled in the Chrome profile and
|
|
13
|
+
# `lcu browser install`; sites stay exact-origin approvals.
|
|
14
|
+
|
|
15
|
+
- insert:
|
|
16
|
+
- id: lcu
|
|
17
|
+
name: 'dsh-plugin-lcu'
|
|
18
|
+
config:
|
|
19
|
+
presets:
|
|
20
|
+
- heavy
|
|
21
|
+
chrome: true
|
|
22
|
+
# allowedOrigins:
|
|
23
|
+
# - http://localhost:3000
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Development-only declarations for the harness packages this plugin consumes.
|
|
3
|
+
*
|
|
4
|
+
* Transcription of the surfaces actually used, from the running harness's own
|
|
5
|
+
* inspection output. Deliberately not published: the host supplies the real
|
|
6
|
+
* packages at runtime and every import of them here is type-only, so nothing in
|
|
7
|
+
* this file reaches the built plugin.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
// @deepseek-ai/dsh-agent
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
|
|
14
|
+
declare module '@deepseek-ai/dsh-agent' {
|
|
15
|
+
import type { Context } from '@deepseek-ai/cordis'
|
|
16
|
+
|
|
17
|
+
export type SessionId = string & { readonly __brand?: 'SessionId' }
|
|
18
|
+
|
|
19
|
+
/** The live session an agent drives; its log is the durable source of truth. */
|
|
20
|
+
export interface AgentSession {
|
|
21
|
+
readonly header: {
|
|
22
|
+
readonly cwd?: string
|
|
23
|
+
/** Id of the agent preset this session's agent was composed from. */
|
|
24
|
+
readonly agentPreset?: string
|
|
25
|
+
}
|
|
26
|
+
requestHeader(): { readonly config?: { readonly provider?: string; readonly model?: string } } | undefined
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface Agent {
|
|
30
|
+
readonly id: SessionId
|
|
31
|
+
readonly options: { readonly provider?: string; readonly model?: string }
|
|
32
|
+
readonly session: AgentSession
|
|
33
|
+
/** Agent-scoped context: contributions unwind on disposal. */
|
|
34
|
+
readonly ctx: Context
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
// @deepseek-ai/dsh-tools
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
|
|
42
|
+
declare module '@deepseek-ai/dsh-tools' {
|
|
43
|
+
import type { Agent } from '@deepseek-ai/dsh-agent'
|
|
44
|
+
|
|
45
|
+
/** A model-facing tool schema, as assembly projects it. */
|
|
46
|
+
export interface ToolSchema {
|
|
47
|
+
readonly name: string
|
|
48
|
+
readonly description: string
|
|
49
|
+
readonly parameters: Record<string, unknown>
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** One model-facing content block. */
|
|
53
|
+
export type ContentBlock =
|
|
54
|
+
| { readonly type: 'text'; readonly text: string }
|
|
55
|
+
| { readonly type: 'image'; readonly attachment: ImageAttachmentRef }
|
|
56
|
+
| { readonly type: string; readonly [key: string]: unknown }
|
|
57
|
+
|
|
58
|
+
/** A durable image reference minted by the attachment store. */
|
|
59
|
+
export interface ImageAttachmentRef {
|
|
60
|
+
readonly attachmentId: string
|
|
61
|
+
readonly mediaType: string
|
|
62
|
+
readonly bytes: number
|
|
63
|
+
readonly width: number
|
|
64
|
+
readonly height: number
|
|
65
|
+
readonly name?: string
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Immutable identity plus cooperation surface for one call. */
|
|
69
|
+
export interface ToolRunContext {
|
|
70
|
+
readonly callId: string
|
|
71
|
+
readonly name: string
|
|
72
|
+
readonly arguments: unknown
|
|
73
|
+
readonly agent?: Agent
|
|
74
|
+
readonly signal: AbortSignal
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Normalized outcome handed to post-execute policy and projection. */
|
|
78
|
+
export interface ToolExecutionResult {
|
|
79
|
+
readonly value: unknown
|
|
80
|
+
readonly content: readonly ContentBlock[]
|
|
81
|
+
readonly isError?: boolean
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** The execution identity a projection callback receives. */
|
|
85
|
+
export interface ToolExecution extends ToolRunContext {}
|
|
86
|
+
|
|
87
|
+
/** Declares the tool's canonical JSON value and its text fallback. */
|
|
88
|
+
export interface ToolOutputDefinition {
|
|
89
|
+
readonly schema: Record<string, unknown>
|
|
90
|
+
render(args: unknown, value: never): ContentBlock[]
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** A complete tool contribution. */
|
|
94
|
+
export interface ToolDefinition extends ToolSchema {
|
|
95
|
+
readonly output: ToolOutputDefinition
|
|
96
|
+
execute(args: never, exec: ToolRunContext): Promise<unknown>
|
|
97
|
+
projectContent?(exec: Readonly<ToolExecution>, result: Readonly<ToolExecutionResult>): ContentBlock[] | undefined
|
|
98
|
+
finalizeContent?(exec: Readonly<ToolExecution>, result: Readonly<ToolExecutionResult>): ContentBlock[] | undefined
|
|
99
|
+
readonly timeoutMs?: number
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** The tool registry a context exposes. */
|
|
103
|
+
export interface ToolRegistry {
|
|
104
|
+
register(definition: ToolDefinition): () => void
|
|
105
|
+
schemas(agent?: Agent): readonly ToolSchema[]
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// ---------------------------------------------------------------------------
|
|
110
|
+
// @deepseek-ai/dsh-system-prompt
|
|
111
|
+
// ---------------------------------------------------------------------------
|
|
112
|
+
|
|
113
|
+
declare module '@deepseek-ai/dsh-system-prompt' {
|
|
114
|
+
/** One ordered prompt section. */
|
|
115
|
+
export interface PromptSection {
|
|
116
|
+
readonly name: string
|
|
117
|
+
readonly order: number
|
|
118
|
+
readonly text: string | ((context: unknown) => string)
|
|
119
|
+
readonly interpolate?: boolean
|
|
120
|
+
readonly complete?: boolean
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export interface SystemPromptRegistry {
|
|
124
|
+
section(section: PromptSection): () => void
|
|
125
|
+
context(context: { readonly name: string; readonly order: number; readonly text: string | ((context: unknown) => string) }): () => void
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// ---------------------------------------------------------------------------
|
|
130
|
+
// @deepseek-ai/dsh-attachment
|
|
131
|
+
// ---------------------------------------------------------------------------
|
|
132
|
+
|
|
133
|
+
declare module '@deepseek-ai/dsh-attachment' {
|
|
134
|
+
import type { ImageAttachmentRef } from '@deepseek-ai/dsh-tools'
|
|
135
|
+
|
|
136
|
+
/** One decoded image ready for durable storage. */
|
|
137
|
+
export interface SaveImageAttachment {
|
|
138
|
+
readonly data: Buffer
|
|
139
|
+
readonly mediaType: string
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export interface AttachmentStore {
|
|
143
|
+
saveImages(images: readonly SaveImageAttachment[]): Promise<readonly ImageAttachmentRef[]>
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// ---------------------------------------------------------------------------
|
|
148
|
+
// @deepseek-ai/dsh-llm
|
|
149
|
+
// ---------------------------------------------------------------------------
|
|
150
|
+
|
|
151
|
+
declare module '@deepseek-ai/dsh-llm' {
|
|
152
|
+
export interface ModelInfo {
|
|
153
|
+
readonly inputModalities?: readonly string[]
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export interface LlmService {
|
|
157
|
+
resolveModelInfo(provider: string, model: string, signal: AbortSignal): Promise<ModelInfo>
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// ---------------------------------------------------------------------------
|
|
162
|
+
// @deepseek-ai/dsh-user-questions
|
|
163
|
+
// ---------------------------------------------------------------------------
|
|
164
|
+
|
|
165
|
+
declare module '@deepseek-ai/dsh-user-questions' {
|
|
166
|
+
import type { Agent } from '@deepseek-ai/dsh-agent'
|
|
167
|
+
|
|
168
|
+
export interface AskUserQuestionOption {
|
|
169
|
+
readonly label: string
|
|
170
|
+
readonly description?: string
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export interface AskUserQuestionItem {
|
|
174
|
+
readonly id: string
|
|
175
|
+
readonly question: string
|
|
176
|
+
readonly detail?: string
|
|
177
|
+
readonly header?: string
|
|
178
|
+
readonly options?: readonly AskUserQuestionOption[]
|
|
179
|
+
readonly multiSelect?: boolean
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export interface AskUserQuestionAnswer {
|
|
183
|
+
readonly answers: readonly {
|
|
184
|
+
readonly id: string
|
|
185
|
+
readonly selected: readonly string[]
|
|
186
|
+
readonly custom?: string
|
|
187
|
+
}[]
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export interface UserQuestionsService {
|
|
191
|
+
ask(request: {
|
|
192
|
+
readonly questions: readonly AskUserQuestionItem[]
|
|
193
|
+
readonly agent?: Agent
|
|
194
|
+
readonly signal?: AbortSignal
|
|
195
|
+
}): Promise<AskUserQuestionAnswer>
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// ---------------------------------------------------------------------------
|
|
200
|
+
// @deepseek-ai/dsh-computer-use
|
|
201
|
+
// ---------------------------------------------------------------------------
|
|
202
|
+
|
|
203
|
+
declare module '@deepseek-ai/dsh-computer-use' {
|
|
204
|
+
export type ComputerUseProviderName = string & { readonly __brand?: 'ComputerUseProviderName' }
|
|
205
|
+
|
|
206
|
+
export interface ComputerUseRegistry {
|
|
207
|
+
/** Reserves the sole provider slot; a second registration fails. */
|
|
208
|
+
register(name: ComputerUseProviderName): () => Promise<void>
|
|
209
|
+
readonly providerName?: ComputerUseProviderName
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export function ComputerUseProviderName(name: string): ComputerUseProviderName
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// ---------------------------------------------------------------------------
|
|
216
|
+
// @deepseek-ai/dsh-agent-preset-registry
|
|
217
|
+
// ---------------------------------------------------------------------------
|
|
218
|
+
|
|
219
|
+
declare module '@deepseek-ai/dsh-agent-preset-registry' {
|
|
220
|
+
import type { Context } from '@deepseek-ai/cordis'
|
|
221
|
+
|
|
222
|
+
export interface AgentPresetsService {
|
|
223
|
+
/**
|
|
224
|
+
* Read the preset a live Agent actually uses.
|
|
225
|
+
*
|
|
226
|
+
* This is the authoritative answer: the session header records the
|
|
227
|
+
* deployment default at creation, which the new-task picker may replace
|
|
228
|
+
* afterwards.
|
|
229
|
+
*/
|
|
230
|
+
composedPreset(agentCtx: Context): string | undefined
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// ---------------------------------------------------------------------------
|
|
235
|
+
// @deepseek-ai/cordis
|
|
236
|
+
// ---------------------------------------------------------------------------
|
|
237
|
+
|
|
238
|
+
declare module '@deepseek-ai/cordis' {
|
|
239
|
+
import type { Agent } from '@deepseek-ai/dsh-agent'
|
|
240
|
+
import type { AttachmentStore } from '@deepseek-ai/dsh-attachment'
|
|
241
|
+
import type { ComputerUseRegistry } from '@deepseek-ai/dsh-computer-use'
|
|
242
|
+
import type { LlmService } from '@deepseek-ai/dsh-llm'
|
|
243
|
+
import type { SystemPromptRegistry } from '@deepseek-ai/dsh-system-prompt'
|
|
244
|
+
import type { ToolRegistry } from '@deepseek-ai/dsh-tools'
|
|
245
|
+
import type { UserQuestionsService } from '@deepseek-ai/dsh-user-questions'
|
|
246
|
+
import type { AgentPresetsService } from '@deepseek-ai/dsh-agent-preset-registry'
|
|
247
|
+
|
|
248
|
+
/** The live-Agent registry, keyed by session id. */
|
|
249
|
+
export interface AgentRegistry {
|
|
250
|
+
get(id: string): Agent | undefined
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
export interface Logger {
|
|
254
|
+
debug(message: string): void
|
|
255
|
+
info(message: string): void
|
|
256
|
+
warn(message: string): void
|
|
257
|
+
error(message: string): void
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* The plugin context, narrowed to the services this plugin consumes.
|
|
262
|
+
*
|
|
263
|
+
* Optional services are read with `get()` so a composition without them still
|
|
264
|
+
* activates everything else.
|
|
265
|
+
*/
|
|
266
|
+
export interface Context {
|
|
267
|
+
readonly tools: ToolRegistry
|
|
268
|
+
readonly systemPrompt: SystemPromptRegistry
|
|
269
|
+
readonly logger: Logger
|
|
270
|
+
get(name: 'attachments'): AttachmentStore | undefined
|
|
271
|
+
get(name: 'llm'): LlmService | undefined
|
|
272
|
+
get(name: 'userQuestions'): UserQuestionsService | undefined
|
|
273
|
+
get(name: 'computerUse'): ComputerUseRegistry | undefined
|
|
274
|
+
get(name: 'agentPresets'): AgentPresetsService | undefined
|
|
275
|
+
get(name: 'agents'): AgentRegistry | undefined
|
|
276
|
+
get(name: string): unknown
|
|
277
|
+
effect(callback: () => (() => void | Promise<void>)): () => void
|
|
278
|
+
on(event: 'agent/created', listener: (payload: { agent: Agent; signal?: AbortSignal }) => void | Promise<void>): () => void
|
|
279
|
+
on(
|
|
280
|
+
event: 'agent/turn-stopping',
|
|
281
|
+
listener: (payload: { agent: Agent; turn: number; signal: AbortSignal }) => void | Promise<void>,
|
|
282
|
+
): () => void
|
|
283
|
+
/**
|
|
284
|
+
* The registry re-emits a session's committed preset choice. This fires
|
|
285
|
+
* AFTER `agent/created`: a new task is created with the deployment default
|
|
286
|
+
* and the picker's choice is applied on this event, so it is the only
|
|
287
|
+
* reliable moment to react to the preset a session will actually use.
|
|
288
|
+
*/
|
|
289
|
+
on(event: 'agent-preset/selected', listener: (sessionId: string, agentPreset: string) => void | Promise<void>): () => void
|
|
290
|
+
on(event: string, listener: (...args: never[]) => unknown): () => void
|
|
291
|
+
}
|
|
292
|
+
}
|