dsh-agent-crew-desktop-ui 0.0.0-stage → 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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Tushar
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.
@@ -0,0 +1,37 @@
1
+ # Bilingual-pair consistency record for README.md (docs/i18n/README.md): per heading
2
+ # section, a hash of its English and Chinese blocks outside code blocks and generated regions.
3
+ # After editing either side, bring the other along and re-record with:
4
+ # pnpm run verify-translation-pairing --write packages/experimental/crew-desktop-ui/README.md
5
+ /:
6
+ en: fd84da234a63d5ac
7
+ zh: 5bc3ca2b5adc0c9e
8
+ /deepseek-ai-dsh-crew-desktop-ui:
9
+ en: d9282c8f0ed1e841
10
+ zh: 9fa2fad28e1494ba
11
+ /deepseek-ai-dsh-crew-desktop-ui/summary:
12
+ en: bb704a49986ef54f
13
+ zh: 6fee0ba186f3a14a
14
+ /deepseek-ai-dsh-crew-desktop-ui/table-of-contents:
15
+ en: d152484eb41ac6b4
16
+ zh: b00dddf4c88f0a45
17
+ /deepseek-ai-dsh-crew-desktop-ui/use-this-package:
18
+ en: 22a5fe3d507185ba
19
+ zh: 1c697ad3fbd72143
20
+ /deepseek-ai-dsh-crew-desktop-ui/understand-the-implementation:
21
+ en: b558c66fc9af80cc
22
+ zh: 3c6d1ca637bd0716
23
+ /deepseek-ai-dsh-crew-desktop-ui/further-exploration:
24
+ en: 9ba2bb6048edd305
25
+ zh: 865d444094927932
26
+ /deepseek-ai-dsh-crew-desktop-ui/model-experience:
27
+ en: 03c852ffd232fc8f
28
+ zh: 624575ba9df489ce
29
+ /deepseek-ai-dsh-crew-desktop-ui/model-experience/kv-cache-effect:
30
+ en: ca75c51c89c2c9b0
31
+ zh: 477b768cc9dcda1a
32
+ /deepseek-ai-dsh-crew-desktop-ui/known-limitations-and-deferred-work:
33
+ en: ff61a9db8e974b46
34
+ zh: d2359dfa404afa96
35
+ /deepseek-ai-dsh-crew-desktop-ui/known-limitations-and-deferred-work/dev-note:
36
+ en: 1b1046904f84d318
37
+ zh: d2b3b084ce7576fa
package/README.md CHANGED
@@ -1,3 +1,93 @@
1
- # Temporary Holding Version
1
+ ---
2
+ description: "Browser half of crew desktop: the right-panel Desktop tab with the latest tool-result screenshot, where computer use runs, and Pause, Take over and Resume."
3
+ kind: "package-reference"
4
+ ---
2
5
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
6
+ # dsh-agent-crew-desktop-ui
7
+
8
+ English | [中文](README.zh.md)
9
+
10
+ ## Summary
11
+
12
+ This client plugin adds a **Desktop** tab to the right panel. It shows the newest screenshot any tool returned in the Session, labelled with the tool and time, where computer use runs, and whether the agent is working. **Pause** stops the current turn; **Take over** stops it and shows that you are in control until you resume or send a message. The screen updates after each agent action and is not a live stream.
13
+
14
+ ## Table of Contents
15
+
16
+ - [Use this package](#use-this-package)
17
+ - [Understand the implementation](#understand-the-implementation)
18
+ - [Further Exploration](#further-exploration)
19
+ - [Model Experience](#model-experience)
20
+ - [Known Limitations and Deferred Work](#known-limitations-and-deferred-work)
21
+ - [Dev Note](#dev-note)
22
+
23
+ -----
24
+
25
+ <a id="use-this-package"></a>
26
+ ## Use this package
27
+
28
+ The [crew-desktop](../crew-desktop/README.md) bundle mounts this row. Open **Desktop** from the right panel's guide in any Session.
29
+
30
+ | Control | Effect |
31
+ |---|---|
32
+ | **Pause** | Stops the agent's current turn through the Session's cancel; enabled while the agent works |
33
+ | **Take over** | Stops a running turn, then shows **You're in control** |
34
+ | **Resume** | Clears the in-control notice; the agent continues when you send it a message |
35
+
36
+ A new turn, for example after you send a message, also ends the in-control notice. If stopping fails, the tab says so and the agent stays in control.
37
+
38
+ -----
39
+
40
+ <a id="understand-the-implementation"></a>
41
+ ## Understand the implementation
42
+
43
+ <details>
44
+ <summary>Implementation internals — click to expand</summary>
45
+
46
+ The tab reads the Session's loaded event window. The frame is the last `image` block of the newest `tool/result`, named after the matching `tool/call`; offloaded images are skipped. Its bytes come from the Session's `readAttachment` and are shown as a data URL. The model keeps one Desktop state per Session binding, follows its sources only while the tab is open, and reads the location from `crewDesktop.where()` once.
47
+
48
+ | Source | Contents |
49
+ |---|---|
50
+ | [`src/client/frame.ts`](src/client/frame.ts) | Frame selection and image encoding |
51
+ | [`src/client/desktop-model.ts`](src/client/desktop-model.ts) | Per-Session state, Pause, Take over and Resume |
52
+ | [`src/client/DesktopTab.tsx`](src/client/DesktopTab.tsx) | Tab body |
53
+ | [`src/client/index.ts`](src/client/index.ts) | Tab type, Remote and slot registration |
54
+ | [`src/client/locales.ts`](src/client/locales.ts) | Copy |
55
+ | [`tests/`](tests/) | Frame selection, states, labels and stopping |
56
+
57
+ </details>
58
+
59
+ -----
60
+
61
+ <a id="further-exploration"></a>
62
+ ## Further Exploration
63
+
64
+ - [crew-desktop](../crew-desktop/README.md) — the bundle and its Remote.
65
+ - [ui-sidebar-right](../../client/ui-sidebar-right/README.md) — right-panel tabs.
66
+ - [session-controller](../../api/session-controller/README.md) — cancel and attachments.
67
+
68
+ <a id="model-experience"></a>
69
+ ## Model Experience
70
+
71
+ None, as the package is a browser tab that registers nothing model-facing.
72
+
73
+ #### KV Cache effect
74
+
75
+ None; this package neither assembles nor sends a provider request.
76
+
77
+ ## Known Limitations and Deferred Work
78
+
79
+ <a id="known-limitations-and-deferred-work"></a>
80
+
81
+ - Frames come only from images tools return, so the tab shows nothing until an agent takes a screenshot; a continuous stream (for example over VNC) is deferred.
82
+ - Only the loaded part of the Session is searched; an older screenshot beyond it is not shown.
83
+ - Take over does not lock the computer; it stops the agent and marks that you are in control.
84
+
85
+ <a id="dev-note"></a>
86
+ ### Dev Note
87
+
88
+ <details>
89
+ <summary>Working context for maintainers — click to expand</summary>
90
+
91
+ Tests drive the tab with a hand-built event window and Session.
92
+
93
+ </details>
package/README.zh.md ADDED
@@ -0,0 +1,93 @@
1
+ ---
2
+ description: "crew 桌面的浏览器端:右侧面板的桌面标签页,显示最新的工具结果截图、电脑使用的运行位置,以及暂停、接管和恢复。"
3
+ kind: "package-reference"
4
+ ---
5
+
6
+ # dsh-agent-crew-desktop-ui
7
+
8
+ [English](README.md) | 中文
9
+
10
+ ## 概述
11
+
12
+ 本客户端插件在右侧面板添加 **桌面** 标签页。它显示 Session 中任意工具返回的最新截图,并标注工具和时间,同时显示电脑使用的运行位置以及智能体是否在工作。**暂停** 停止当前轮次;**接管** 停止它并显示由你控制,直到你恢复或发送消息。画面在每次智能体操作后更新,不是实时画面。
13
+
14
+ ## 目录
15
+
16
+ - [使用本包](#use-this-package)
17
+ - [实现原理](#understand-the-implementation)
18
+ - [延伸阅读](#further-exploration)
19
+ - [模型体验](#model-experience)
20
+ - [已知限制与后续工作](#known-limitations-and-deferred-work)
21
+ - [开发备注](#dev-note)
22
+
23
+ -----
24
+
25
+ <a id="use-this-package"></a>
26
+ ## 使用本包
27
+
28
+ [crew-desktop](../crew-desktop/README.zh.md) bundle 挂载此行。在任意 Session 中从右侧面板的引导页打开 **桌面**。
29
+
30
+ | 控件 | 作用 |
31
+ |---|---|
32
+ | **暂停** | 通过 Session 的取消停止智能体当前轮次;智能体工作时可用 |
33
+ | **接管** | 停止正在运行的轮次,然后显示 **由你控制** |
34
+ | **恢复** | 清除由你控制的提示;你发送消息后智能体继续 |
35
+
36
+ 新的轮次(例如你发送消息后)也会结束由你控制的提示。如果停止失败,标签页会说明,智能体继续控制。
37
+
38
+ -----
39
+
40
+ <a id="understand-the-implementation"></a>
41
+ ## 实现原理
42
+
43
+ <details>
44
+ <summary>实现细节——点击展开</summary>
45
+
46
+ 标签页读取 Session 已加载的事件窗口。画面是最新 `tool/result` 中最后一个 `image` 块,以对应的 `tool/call` 命名;已卸载的图片会被跳过。图片字节来自 Session 的 `readAttachment`,以 data URL 显示。模型为每个 Session 绑定保留一份桌面状态,只在标签页打开时跟随其来源,并只读取一次 `crewDesktop.where()` 的位置。
47
+
48
+ | 源文件 | 内容 |
49
+ |---|---|
50
+ | [`src/client/frame.ts`](src/client/frame.ts) | 画面选择与图片编码 |
51
+ | [`src/client/desktop-model.ts`](src/client/desktop-model.ts) | 每个 Session 的状态、暂停、接管与恢复 |
52
+ | [`src/client/DesktopTab.tsx`](src/client/DesktopTab.tsx) | 标签页内容 |
53
+ | [`src/client/index.ts`](src/client/index.ts) | 标签页类型、Remote 与插槽注册 |
54
+ | [`src/client/locales.ts`](src/client/locales.ts) | 文案 |
55
+ | [`tests/`](tests/) | 画面选择、状态、标签与停止 |
56
+
57
+ </details>
58
+
59
+ -----
60
+
61
+ <a id="further-exploration"></a>
62
+ ## 延伸阅读
63
+
64
+ - [crew-desktop](../crew-desktop/README.zh.md) — bundle 及其 Remote。
65
+ - [ui-sidebar-right](../../client/ui-sidebar-right/README.zh.md) — 右侧面板标签页。
66
+ - [session-controller](../../api/session-controller/README.zh.md) — 取消与附件。
67
+
68
+ <a id="model-experience"></a>
69
+ ## 模型体验
70
+
71
+ 无,本包是浏览器标签页,不注册任何面向模型的内容。
72
+
73
+ #### KV Cache 影响
74
+
75
+ 无;本包既不组装也不发送模型提供方请求。
76
+
77
+ ## 已知限制与后续工作
78
+
79
+ <a id="known-limitations-and-deferred-work"></a>
80
+
81
+ - 画面只来自工具返回的图片,因此在智能体截图之前标签页没有内容;连续画面(例如通过 VNC)留待以后。
82
+ - 只搜索 Session 已加载的部分;更早的截图不会显示。
83
+ - 接管不会锁定电脑;它停止智能体并标明由你控制。
84
+
85
+ <a id="dev-note"></a>
86
+ ### 开发备注
87
+
88
+ <details>
89
+ <summary>维护者工作背景——点击展开</summary>
90
+
91
+ 测试用手工构造的事件窗口和 Session 驱动标签页。
92
+
93
+ </details>
package/lib/client.js ADDED
@@ -0,0 +1,743 @@
1
+ window.__ModuleLoader__.load({
2
+ id: "dsh-agent-crew-desktop-ui",
3
+ factory: (require) => {
4
+ var module = { exports: {} };
5
+ var exports = module.exports;
6
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
+ let zod = require("zod");
8
+ let react_jsx_runtime = require("react/jsx-runtime");
9
+ let react = require("react");
10
+ let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
11
+ //#region ../../util/brand/src/index.ts
12
+ /**
13
+ * Apply a compile-time string brand without changing the value.
14
+ * @param value - string admitted by the domain that owns the target brand.
15
+ * @returns the same string with the requested compile-time brand.
16
+ */
17
+ function brandString(value) {
18
+ return value;
19
+ }
20
+ //#endregion
21
+ //#region ../../core/session/src/types.ts
22
+ /**
23
+ * Brand a string as a {@link SessionId}.
24
+ * @param id - the raw session id string.
25
+ * @returns the same string with the session-id brand.
26
+ */
27
+ function SessionId(id) {
28
+ return brandString(id);
29
+ }
30
+ //#endregion
31
+ //#region ../../typert/protocol/src/remote-info.ts
32
+ /**
33
+ * Retain one generated definition per endpoint without constructing parsers or projections.
34
+ * The class instance builds its shared descriptor/method index on first access;
35
+ * input schemas and JSON Schema projections are cached only when requested.
36
+ * @param spec - compiler-produced method facts and Zod factories.
37
+ * @param zod - the generated artifact's Zod runtime for named-input parsers.
38
+ * @returns a lazy, mountable info instance with no registration or transport effects.
39
+ */
40
+ function createTypertRemoteInfo(spec, zod) {
41
+ return new RemoteInfo(spec, zod);
42
+ }
43
+ var RemoteInfo = class {
44
+ spec;
45
+ zod;
46
+ index;
47
+ constructor(spec, zod) {
48
+ this.spec = spec;
49
+ this.zod = zod;
50
+ }
51
+ get package() {
52
+ return this.spec.package;
53
+ }
54
+ get descriptors() {
55
+ return this.resolve().descriptors;
56
+ }
57
+ get methods() {
58
+ return this.resolve().methods;
59
+ }
60
+ resolve() {
61
+ if (this.index !== void 0) return this.index;
62
+ const zod = this.zod;
63
+ const descriptors = [];
64
+ const methods = Object.create(null);
65
+ for (const [endpoint, definition] of Object.entries(this.spec.methods)) {
66
+ const separator = endpoint.indexOf("/");
67
+ const namespace = endpoint.slice(0, separator);
68
+ const method = endpoint.slice(separator + 1);
69
+ const parameters = definition.parameters.map((parameter) => ({
70
+ ...parameter,
71
+ wire: parameter.wire ?? parameter.name
72
+ }));
73
+ const descriptor = {
74
+ ...definition,
75
+ id: `${this.spec.package}#${endpoint}`,
76
+ namespace,
77
+ method,
78
+ parameters,
79
+ result: definition.result.description === void 0 ? definition.result : new ResultValue(definition.result)
80
+ };
81
+ if (definition.invocation.kind === "direct") {
82
+ const inputs = [];
83
+ methods[endpoint] = Object.assign(descriptor, {
84
+ input(options) {
85
+ const strict = options?.strict === true;
86
+ return inputs[strict ? 1 : 0] ??= new NamedInput(parameters, strict, zod);
87
+ },
88
+ invoke(remote, input, signal) {
89
+ const named = input;
90
+ const values = parameters.map((parameter) => named[parameter.wire]);
91
+ if (definition.cancellation !== void 0) values.push(signal);
92
+ const receiver = Reflect.get(remote, namespace);
93
+ return Reflect.get(receiver, method).apply(receiver, values);
94
+ }
95
+ });
96
+ }
97
+ descriptors.push(descriptor);
98
+ }
99
+ return this.index = {
100
+ descriptors,
101
+ methods
102
+ };
103
+ }
104
+ };
105
+ function optional(parameter) {
106
+ return parameter.source === "json" && parameter.acceptsUndefined === true;
107
+ }
108
+ var NamedInput = class {
109
+ parameters;
110
+ strict;
111
+ zod;
112
+ schema;
113
+ projection;
114
+ constructor(parameters, strict, zod) {
115
+ this.parameters = parameters;
116
+ this.strict = strict;
117
+ this.zod = zod;
118
+ }
119
+ codec(parameter) {
120
+ return this.strict ? parameter.strictCodec : parameter.codec;
121
+ }
122
+ create = () => this.schema ??= this.zod.strictObject(Object.fromEntries(this.parameters.map((parameter) => {
123
+ const value = this.codec(parameter).create();
124
+ return [parameter.wire, optional(parameter) ? value.optional() : value.nonoptional()];
125
+ })));
126
+ get jsonSchema() {
127
+ return this.project().jsonSchema;
128
+ }
129
+ get jsonSchemaError() {
130
+ return this.project().jsonSchemaError;
131
+ }
132
+ project() {
133
+ if (this.projection !== void 0) return this.projection;
134
+ const unavailable = this.parameters.find((parameter) => this.codec(parameter).jsonSchema === void 0);
135
+ if (unavailable !== void 0) return this.projection = { jsonSchemaError: this.codec(unavailable).jsonSchemaError ?? `No JSON Schema for parameter ${unavailable.wire}` };
136
+ return this.projection = { jsonSchema: {
137
+ type: "object",
138
+ properties: Object.fromEntries(this.parameters.map((parameter) => [parameter.wire, {
139
+ ...this.codec(parameter).jsonSchema,
140
+ ...parameter.description === void 0 ? {} : { description: parameter.description }
141
+ }])),
142
+ required: this.parameters.filter((parameter) => !optional(parameter)).map((parameter) => parameter.wire),
143
+ additionalProperties: false
144
+ } };
145
+ }
146
+ };
147
+ var ResultValue = class {
148
+ spec;
149
+ projection;
150
+ constructor(spec) {
151
+ this.spec = spec;
152
+ }
153
+ get mode() {
154
+ return this.spec.mode;
155
+ }
156
+ get typeSymbol() {
157
+ return this.spec.typeSymbol;
158
+ }
159
+ get create() {
160
+ return this.spec.create;
161
+ }
162
+ get decode() {
163
+ return this.spec.decode;
164
+ }
165
+ get encode() {
166
+ return this.spec.encode;
167
+ }
168
+ get description() {
169
+ return this.spec.description;
170
+ }
171
+ get jsonSchemaError() {
172
+ return this.spec.jsonSchemaError;
173
+ }
174
+ get jsonSchema() {
175
+ if (this.projection === void 0) {
176
+ const schema = this.spec.jsonSchema;
177
+ this.projection = { value: schema === void 0 ? void 0 : {
178
+ ...schema,
179
+ description: this.spec.description
180
+ } };
181
+ }
182
+ return this.projection.value;
183
+ }
184
+ };
185
+ //#endregion
186
+ //#region ../crew-desktop/lib/typert.remote-client.js
187
+ let _deepseek_ai_dsh_crew_desktop_crewDesktop_where_result$schema$value;
188
+ const _deepseek_ai_dsh_crew_desktop_crewDesktop_where_result$schema = () => _deepseek_ai_dsh_crew_desktop_crewDesktop_where_result$schema$value ??= zod.z.object({
189
+ "provider": zod.z.string().readonly().optional().describe("Registered computer-use provider, absent while none is mounted."),
190
+ "host": zod.z.string().readonly().describe("Host machine name."),
191
+ "platform": zod.z.string().readonly().describe("Host operating system, as Node reports it.")
192
+ });
193
+ const TYPERT_REMOTE = createTypertRemoteInfo({
194
+ package: "dsh-agent-crew-desktop",
195
+ methods: { "crewDesktop/where": {
196
+ service: "crewDesktop",
197
+ description: "Where computer use runs.",
198
+ invocation: { kind: "direct" },
199
+ parameters: [],
200
+ result: {
201
+ mode: "strict",
202
+ typeSymbol: "dsh-agent-crew-desktop/types#DesktopLocation",
203
+ create: _deepseek_ai_dsh_crew_desktop_crewDesktop_where_result$schema,
204
+ description: "the provider name, when one is registered, and the host machine.",
205
+ jsonSchema: {
206
+ "type": "object",
207
+ "properties": {
208
+ "provider": {
209
+ "type": "string",
210
+ "description": "Registered computer-use provider, absent while none is mounted."
211
+ },
212
+ "host": {
213
+ "type": "string",
214
+ "description": "Host machine name."
215
+ },
216
+ "platform": {
217
+ "type": "string",
218
+ "description": "Host operating system, as Node reports it."
219
+ }
220
+ },
221
+ "required": ["host", "platform"],
222
+ "additionalProperties": true
223
+ }
224
+ },
225
+ sourceLocation: {
226
+ "file": "packages/experimental/crew-desktop/src/index.ts",
227
+ "line": 42,
228
+ "column": 3
229
+ }
230
+ } }
231
+ }, zod.z);
232
+ //#endregion
233
+ //#region lib/types/client/frame.js
234
+ /**
235
+ * Find the newest tool-result image.
236
+ * @param entries - the Session's loaded event window.
237
+ * @returns the latest frame, or undefined when no tool has returned an image.
238
+ */
239
+ function latestFrame(entries) {
240
+ const tools = /* @__PURE__ */ new Map();
241
+ let latest;
242
+ for (const entry of entries) {
243
+ if (entry.type !== "event") continue;
244
+ const event = entry.event;
245
+ if (event.type === "tool/call") tools.set(event.data.callId, event.data.name);
246
+ else if (event.type === "tool/result") {
247
+ const image = event.data.message.content.findLast((block) => block.type === "image");
248
+ if (image?.type === "image" && image.offloaded !== true) latest = {
249
+ attachment: image.attachment,
250
+ tool: tools.get(event.data.message.toolCallId) ?? "tool",
251
+ time: event.time,
252
+ seq: event.seq
253
+ };
254
+ }
255
+ }
256
+ return latest;
257
+ }
258
+ /**
259
+ * Encode image bytes as a data URL the tab can show without another request.
260
+ * @param bytes - encoded image.
261
+ * @param mediaType - its verified media type.
262
+ * @returns a `data:` URL.
263
+ */
264
+ function dataUrl(bytes, mediaType) {
265
+ let binary = "";
266
+ for (let index = 0; index < bytes.length; index += 32768) binary += String.fromCharCode(...bytes.subarray(index, index + 32768));
267
+ return `data:${mediaType};base64,${btoa(binary)}`;
268
+ }
269
+ //#endregion
270
+ //#region lib/types/client/desktop-model.js
271
+ /** Observable Desktop state for one Session; it follows its sources only while the tab is open. */
272
+ var DesktopModel = class {
273
+ events;
274
+ session;
275
+ where;
276
+ state = {
277
+ frame: void 0,
278
+ image: void 0,
279
+ imageFailed: false,
280
+ running: false,
281
+ takenOver: false,
282
+ location: void 0,
283
+ locationFailed: false,
284
+ stopFailed: false
285
+ };
286
+ listeners = /* @__PURE__ */ new Set();
287
+ detach;
288
+ revision = -1;
289
+ locating = false;
290
+ /** Current state. */
291
+ source = {
292
+ getSnapshot: () => this.state,
293
+ subscribe: (listener) => {
294
+ this.listeners.add(listener);
295
+ if (this.listeners.size === 1) this.attach();
296
+ return () => {
297
+ this.listeners.delete(listener);
298
+ if (this.listeners.size === 0) {
299
+ this.detach?.();
300
+ this.detach = void 0;
301
+ }
302
+ };
303
+ }
304
+ };
305
+ /**
306
+ * @param events - the Session's event window.
307
+ * @param session - the Session, for running state, images and stopping.
308
+ * @param where - reads where computer use runs; undefined when the reply was lost.
309
+ */
310
+ constructor(events, session, where) {
311
+ this.events = events;
312
+ this.session = session;
313
+ this.where = where;
314
+ }
315
+ publish(next) {
316
+ this.state = {
317
+ ...this.state,
318
+ ...next
319
+ };
320
+ for (const listener of [...this.listeners]) listener();
321
+ }
322
+ attach() {
323
+ const offEvents = this.events.subscribe(() => {
324
+ this.readFrame();
325
+ });
326
+ const offSession = this.session.subscribe(() => {
327
+ this.readRunning();
328
+ });
329
+ this.detach = () => {
330
+ offEvents();
331
+ offSession();
332
+ };
333
+ this.readFrame();
334
+ this.readRunning();
335
+ if (this.locating) return;
336
+ this.locating = true;
337
+ this.where().then((location) => {
338
+ this.publish({
339
+ location,
340
+ locationFailed: location === void 0
341
+ });
342
+ });
343
+ }
344
+ readRunning() {
345
+ const running = this.session.getSnapshot().running;
346
+ if (running === this.state.running) return;
347
+ this.publish(running ? {
348
+ running,
349
+ takenOver: false
350
+ } : { running });
351
+ }
352
+ readFrame() {
353
+ const window = this.events.getSnapshot();
354
+ if (window.revision === this.revision) return;
355
+ this.revision = window.revision;
356
+ const frame = latestFrame(window.entries);
357
+ if (frame?.seq === this.state.frame?.seq) return;
358
+ this.publish({
359
+ frame,
360
+ image: void 0,
361
+ imageFailed: false
362
+ });
363
+ if (frame === void 0) return;
364
+ this.session.readAttachment(frame.attachment).then((result) => {
365
+ if (this.state.frame?.seq !== frame.seq) return;
366
+ this.publish(result.ok ? { image: dataUrl(result.value.data, frame.attachment.mediaType) } : { imageFailed: true });
367
+ }, () => {
368
+ if (this.state.frame?.seq === frame.seq) this.publish({ imageFailed: true });
369
+ });
370
+ }
371
+ async stop() {
372
+ const ok = (await this.session.cancel().catch(() => void 0))?.ok === true;
373
+ this.publish({ stopFailed: !ok });
374
+ return ok;
375
+ }
376
+ /**
377
+ * Stop the agent's current turn.
378
+ * @returns whether the Host accepted the stop.
379
+ */
380
+ pause() {
381
+ return this.stop();
382
+ }
383
+ /**
384
+ * Stop the agent and mark that the user is in control.
385
+ * @returns whether the agent was stopped.
386
+ */
387
+ async takeOver() {
388
+ if (this.state.running && !await this.stop()) return false;
389
+ this.publish({
390
+ takenOver: true,
391
+ stopFailed: false
392
+ });
393
+ return true;
394
+ }
395
+ /** Hand control back; the agent continues when the user sends it a message. */
396
+ resume() {
397
+ this.publish({ takenOver: false });
398
+ }
399
+ };
400
+ //#endregion
401
+ //#region \0dsh-css:/Users/tushar/Developer/deepseek-harness-fork/packages/experimental/crew-desktop-ui/src/client/DesktopTab.module.css.mjs
402
+ const css = "[data-mode]{--c:var(--dsw-alias-state-idle-primary)}[data-mode=working]{--c:var(--dsw-alias-state-business-primary)}[data-mode=taken]{--c:var(--dsw-alias-state-warn-primary)}.zK5j4a_tab{background:var(--dsw-alias-bg-base);height:100%;color:var(--dsw-alias-label-primary);flex-direction:column;gap:12px;padding:16px;font-size:13px;display:flex;overflow-y:auto}.zK5j4a_head{align-items:flex-start;gap:12px;display:flex}.zK5j4a_headText{flex:1;min-width:0}.zK5j4a_title{letter-spacing:-.01em;margin:0;font-size:17px;font-weight:650;line-height:1.2}.zK5j4a_status{color:var(--dsw-alias-label-tertiary);margin:2px 0 0;font-size:12px}.zK5j4a_pill{background:color-mix(in srgb, var(--c) 14%, transparent);white-space:nowrap;border-radius:999px;flex:none;align-items:center;gap:6px;padding:2px 8px;font-size:11px;font-weight:550;display:inline-flex}.zK5j4a_pill:before{content:\"\";background:var(--c);border-radius:50%;width:6px;height:6px}.zK5j4a_pill[data-mode=working]:before{animation:1.6s ease-in-out infinite zK5j4a_pulse}@keyframes zK5j4a_pulse{50%{opacity:.35}}.zK5j4a_where{border-radius:var(--dsw-radius-md);background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-secondary);align-items:flex-start;gap:8px;margin:0;padding:8px 10px;font-size:12px;display:flex}.zK5j4a_where[data-off]{color:var(--dsw-alias-label-tertiary)}.zK5j4a_whereIcon{color:var(--dsw-alias-label-tertiary);flex:none;margin-top:1px}.zK5j4a_banner{border-radius:var(--dsw-radius-md);background:color-mix(in srgb, var(--dsw-alias-state-warn-primary) 14%, transparent);color:var(--dsw-alias-label-primary);margin:0;padding:10px 12px;font-size:12px}.zK5j4a_error{color:var(--dsw-alias-state-error-primary);margin:0;font-size:12px}.zK5j4a_screen{border:1px solid var(--dsw-alias-border-l2);border-radius:var(--dsw-radius-md);background:var(--dsw-alias-bg-layer-1);justify-content:center;align-items:center;min-height:180px;margin:0;padding:6px;transition:border-color .2s,box-shadow .2s;display:flex;position:relative}.zK5j4a_screen[data-mode=taken]{border-color:var(--c);box-shadow:0 0 0 3px color-mix(in srgb, var(--c) 18%, transparent)}.zK5j4a_screen img{border-radius:6px;max-width:100%;height:auto;display:block}.zK5j4a_badge{color:#fff;max-width:calc(100% - 28px);font-family:var(--dsw-font-mono,ui-monospace, monospace);text-overflow:ellipsis;white-space:nowrap;backdrop-filter:blur(6px);background:#14161ab8;border-radius:999px;padding:3px 8px;font-size:11px;position:absolute;bottom:14px;right:14px;overflow:hidden}.zK5j4a_empty{max-width:30ch;color:var(--dsw-alias-label-tertiary);text-align:center;flex-direction:column;align-items:center;gap:8px;margin:0;padding:16px;font-size:12px;display:flex}.zK5j4a_caption,.zK5j4a_hint{color:var(--dsw-alias-label-tertiary);margin:0;font-size:11px}.zK5j4a_caption{margin-top:-6px}.zK5j4a_hint{text-align:center}.zK5j4a_actions{grid-template-columns:1fr 1fr;gap:8px;margin-top:auto;display:grid}.zK5j4a_wide{grid-column:1/-1}@media (prefers-reduced-motion:reduce){.zK5j4a_pill:before{animation:none!important}.zK5j4a_screen{transition:none}}";
403
+ const tagId = "dsh-agent-crew-desktop-ui/DesktopTab.module.css";
404
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
405
+ const tag = document.createElement("style");
406
+ tag.dataset.plugin = "dsh-agent-crew-desktop-ui";
407
+ tag.dataset.pluginCss = tagId;
408
+ tag.textContent = css;
409
+ document.head.appendChild(tag);
410
+ }
411
+ var DesktopTab_module_css_default = {
412
+ "actions": "zK5j4a_actions",
413
+ "badge": "zK5j4a_badge",
414
+ "banner": "zK5j4a_banner",
415
+ "caption": "zK5j4a_caption",
416
+ "empty": "zK5j4a_empty",
417
+ "error": "zK5j4a_error",
418
+ "head": "zK5j4a_head",
419
+ "headText": "zK5j4a_headText",
420
+ "hint": "zK5j4a_hint",
421
+ "pill": "zK5j4a_pill",
422
+ "pulse": "zK5j4a_pulse",
423
+ "screen": "zK5j4a_screen",
424
+ "status": "zK5j4a_status",
425
+ "tab": "zK5j4a_tab",
426
+ "title": "zK5j4a_title",
427
+ "where": "zK5j4a_where",
428
+ "whereIcon": "zK5j4a_whereIcon",
429
+ "wide": "zK5j4a_wide"
430
+ };
431
+ //#endregion
432
+ //#region lib/types/client/DesktopTab.js
433
+ /** Right-panel Desktop tab: the latest screen an agent saw, where computer use runs, and Pause, Take over and Resume. */
434
+ function when(time) {
435
+ return new Date(time).toLocaleString(void 0, {
436
+ month: "short",
437
+ day: "numeric",
438
+ hour: "numeric",
439
+ minute: "2-digit",
440
+ second: "2-digit"
441
+ });
442
+ }
443
+ function Monitor() {
444
+ return (0, react_jsx_runtime.jsxs)("svg", {
445
+ width: "16",
446
+ height: "16",
447
+ viewBox: "0 0 16 16",
448
+ "aria-hidden": "true",
449
+ className: DesktopTab_module_css_default.whereIcon,
450
+ children: [(0, react_jsx_runtime.jsx)("rect", {
451
+ x: "1.5",
452
+ y: "2.5",
453
+ width: "13",
454
+ height: "9",
455
+ rx: "1.5",
456
+ fill: "none",
457
+ stroke: "currentColor",
458
+ strokeWidth: "1.4"
459
+ }), (0, react_jsx_runtime.jsx)("path", {
460
+ d: "M5.5 14h5M8 11.5V14",
461
+ stroke: "currentColor",
462
+ strokeWidth: "1.4",
463
+ strokeLinecap: "round"
464
+ })]
465
+ });
466
+ }
467
+ /**
468
+ * Render the Desktop tab. Nothing stops the agent without a click.
469
+ * @param props - Desktop state, actions and copy.
470
+ * @returns the tab body.
471
+ */
472
+ function DesktopTab({ useDesktop, pause, takeOver, resume, t }) {
473
+ const state = useDesktop((value) => value);
474
+ const [busy, setBusy] = (0, react.useState)(false);
475
+ const run = (action) => {
476
+ setBusy(true);
477
+ action().finally(() => {
478
+ setBusy(false);
479
+ });
480
+ };
481
+ const { frame, location } = state;
482
+ const where = state.locationFailed ? t("where.failed") : location === void 0 ? t("where.loading") : location.provider === void 0 ? t("where.none", { host: location.host }) : t("where.provider", {
483
+ provider: location.provider,
484
+ host: location.host
485
+ });
486
+ const mode = state.takenOver ? "taken" : state.running ? "working" : "idle";
487
+ return (0, react_jsx_runtime.jsxs)("section", {
488
+ className: DesktopTab_module_css_default.tab,
489
+ "aria-label": t("tab.title"),
490
+ children: [
491
+ (0, react_jsx_runtime.jsxs)("header", {
492
+ className: DesktopTab_module_css_default.head,
493
+ children: [(0, react_jsx_runtime.jsxs)("div", {
494
+ className: DesktopTab_module_css_default.headText,
495
+ children: [(0, react_jsx_runtime.jsx)("h2", {
496
+ className: DesktopTab_module_css_default.title,
497
+ children: t("tab.title")
498
+ }), !state.takenOver && (0, react_jsx_runtime.jsx)("p", {
499
+ className: DesktopTab_module_css_default.status,
500
+ role: "status",
501
+ children: state.running ? t("status.running") : t("status.idle")
502
+ })]
503
+ }), (0, react_jsx_runtime.jsx)("span", {
504
+ className: DesktopTab_module_css_default.pill,
505
+ "data-mode": mode,
506
+ "aria-hidden": "true",
507
+ children: t(`pill.${mode}`)
508
+ })]
509
+ }),
510
+ (0, react_jsx_runtime.jsxs)("p", {
511
+ className: DesktopTab_module_css_default.where,
512
+ "data-off": state.locationFailed || location?.provider === void 0 || void 0,
513
+ children: [(0, react_jsx_runtime.jsx)(Monitor, {}), where]
514
+ }),
515
+ state.takenOver && (0, react_jsx_runtime.jsx)("p", {
516
+ className: DesktopTab_module_css_default.banner,
517
+ role: "status",
518
+ children: t("status.takenOver")
519
+ }),
520
+ state.stopFailed && (0, react_jsx_runtime.jsx)("p", {
521
+ className: DesktopTab_module_css_default.error,
522
+ role: "alert",
523
+ children: t("action.failed")
524
+ }),
525
+ (0, react_jsx_runtime.jsx)("figure", {
526
+ className: DesktopTab_module_css_default.screen,
527
+ "data-mode": mode,
528
+ children: frame === void 0 ? (0, react_jsx_runtime.jsxs)("p", {
529
+ className: DesktopTab_module_css_default.empty,
530
+ children: [(0, react_jsx_runtime.jsx)(Monitor, {}), t("frame.empty")]
531
+ }) : state.image !== void 0 ? (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsx)("img", {
532
+ src: state.image,
533
+ alt: t("frame.alt", {
534
+ tool: frame.tool,
535
+ time: when(frame.time)
536
+ }),
537
+ width: frame.attachment.width,
538
+ height: frame.attachment.height
539
+ }), (0, react_jsx_runtime.jsxs)("figcaption", {
540
+ className: DesktopTab_module_css_default.badge,
541
+ "aria-hidden": "true",
542
+ children: [
543
+ frame.tool,
544
+ " · ",
545
+ when(frame.time)
546
+ ]
547
+ })] }) : (0, react_jsx_runtime.jsx)("p", {
548
+ className: state.imageFailed ? DesktopTab_module_css_default.error : DesktopTab_module_css_default.empty,
549
+ children: t(state.imageFailed ? "frame.failed" : "frame.loading")
550
+ })
551
+ }),
552
+ frame !== void 0 && (0, react_jsx_runtime.jsx)("p", {
553
+ className: DesktopTab_module_css_default.caption,
554
+ children: t("frame.note")
555
+ }),
556
+ (0, react_jsx_runtime.jsx)("div", {
557
+ className: DesktopTab_module_css_default.actions,
558
+ children: state.takenOver ? (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
559
+ variant: "primary",
560
+ className: DesktopTab_module_css_default.wide,
561
+ icon: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconPlayOutlineRegular, { size: 14 }),
562
+ "aria-label": t("action.resumeLabel"),
563
+ onClick: resume,
564
+ children: t("action.resume")
565
+ }) : (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
566
+ variant: "outline",
567
+ icon: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconPauseOutlineRegular, { size: 14 }),
568
+ disabled: busy || !state.running,
569
+ "aria-label": t("action.pauseLabel"),
570
+ onClick: () => {
571
+ run(pause);
572
+ },
573
+ children: t("action.pause")
574
+ }), (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
575
+ variant: "primary",
576
+ disabled: busy,
577
+ "aria-label": t("action.takeOverLabel"),
578
+ onClick: () => {
579
+ run(takeOver);
580
+ },
581
+ children: t("action.takeOver")
582
+ })] })
583
+ }),
584
+ (0, react_jsx_runtime.jsx)("p", {
585
+ className: DesktopTab_module_css_default.hint,
586
+ children: t(state.takenOver ? "hint.resume" : "hint.stop")
587
+ })
588
+ ]
589
+ });
590
+ }
591
+ //#endregion
592
+ //#region lib/types/client/locales.js
593
+ /** Desktop tab copy. */
594
+ const en = {
595
+ "tab.title": "Desktop",
596
+ "tab.description": "See the screen an agent is using, and pause it or take over",
597
+ "where.provider": "Computer use: {provider} on {host}",
598
+ "where.loading": "Checking where computer use runs…",
599
+ "where.none": "No computer-use provider is on, so agents can’t use this computer ({host}). Turn on Computer Use from Plugins to allow it.",
600
+ "where.failed": "Couldn’t reach dsh to check where computer use runs.",
601
+ "status.running": "The agent is working",
602
+ "status.idle": "The agent is not running",
603
+ "status.takenOver": "You’re in control. The agent stays stopped until you resume or send it a message.",
604
+ "frame.alt": "Screen from {tool} at {time}",
605
+ "frame.note": "Updates after each agent action. It is not a live stream.",
606
+ "pill.working": "Working",
607
+ "pill.idle": "Idle",
608
+ "pill.taken": "You’re in control",
609
+ "hint.stop": "Pause stops the current turn. Take over also keeps the agent stopped.",
610
+ "hint.resume": "Resume hands control back. The agent continues on your next message.",
611
+ "frame.empty": "No screen yet. When an agent takes a screenshot while using the computer, it appears here.",
612
+ "frame.loading": "Loading the latest screen…",
613
+ "frame.failed": "Couldn’t load the latest screen.",
614
+ "action.pause": "Pause",
615
+ "action.pauseLabel": "Pause the agent’s current turn",
616
+ "action.takeOver": "Take over",
617
+ "action.takeOverLabel": "Stop the agent and take control of the computer",
618
+ "action.resume": "Resume",
619
+ "action.resumeLabel": "Hand control back to the agent",
620
+ "action.failed": "Couldn’t stop the agent. Try again."
621
+ };
622
+ /** Chinese copy. */
623
+ const zh = {
624
+ "tab.title": "桌面",
625
+ "tab.description": "查看智能体正在使用的屏幕,并暂停或接管",
626
+ "where.provider": "电脑使用:{provider},运行于 {host}",
627
+ "where.loading": "正在检查电脑使用的运行位置…",
628
+ "where.none": "没有开启电脑使用提供方,智能体无法使用这台电脑({host})。可在插件中开启电脑使用。",
629
+ "where.failed": "无法连接 dsh 检查电脑使用的运行位置。",
630
+ "status.running": "智能体正在工作",
631
+ "status.idle": "智能体未运行",
632
+ "status.takenOver": "由你控制。在你恢复或给它发送消息之前,智能体保持停止。",
633
+ "frame.alt": "来自 {tool} 的屏幕,{time}",
634
+ "frame.note": "每次智能体操作后更新,不是实时画面。",
635
+ "pill.working": "工作中",
636
+ "pill.idle": "空闲",
637
+ "pill.taken": "由你控制",
638
+ "hint.stop": "暂停会停止当前轮次。接管还会让智能体保持停止。",
639
+ "hint.resume": "恢复即交还控制权。智能体会在你下一条消息时继续。",
640
+ "frame.empty": "还没有屏幕画面。智能体使用电脑时截取的屏幕会显示在这里。",
641
+ "frame.loading": "正在加载最新屏幕…",
642
+ "frame.failed": "无法加载最新屏幕。",
643
+ "action.pause": "暂停",
644
+ "action.pauseLabel": "暂停智能体当前的轮次",
645
+ "action.takeOver": "接管",
646
+ "action.takeOverLabel": "停止智能体并接管电脑",
647
+ "action.resume": "恢复",
648
+ "action.resumeLabel": "把控制权交还给智能体",
649
+ "action.failed": "无法停止智能体,请重试。"
650
+ };
651
+ //#endregion
652
+ //#region lib/types/client/index.js
653
+ /** Registries the tab and the Remote need. */
654
+ const inject = [
655
+ "locale",
656
+ "sessions",
657
+ "slots",
658
+ "sidebarRightTabs",
659
+ "remote"
660
+ ];
661
+ /** Desktop tab implementation identity and keyed Slot dispatch key. */
662
+ const DESKTOP_TAB_ID = "dsh-agent-crew-desktop-ui";
663
+ /**
664
+ * Register the tab type, mount the Remote, then the tab body for the same lifetime.
665
+ * @param ctx - Client plugin context.
666
+ * @returns disposer removing the tab before the Remote.
667
+ */
668
+ async function apply(ctx) {
669
+ ctx.effect(() => ctx.locale.register("crewDesktop", {
670
+ en,
671
+ zh
672
+ }), "crew-desktop-ui: locale");
673
+ const t = ctx.locale.bind("crewDesktop");
674
+ ctx.effect(() => ctx.sidebarRightTabs.register({
675
+ id: DESKTOP_TAB_ID,
676
+ kind: "crew-desktop",
677
+ title: () => t("tab.title"),
678
+ guide: [{
679
+ id: "desktop",
680
+ order: 62,
681
+ title: () => t("tab.title"),
682
+ description: () => t("tab.description")
683
+ }]
684
+ }), "crew-desktop-ui: tab type");
685
+ const unmount = await ctx.remote.$mount(TYPERT_REMOTE);
686
+ const ui = ctx.inject(["remote.crewDesktop"], (inner) => {
687
+ const where = async () => {
688
+ try {
689
+ const result = await inner.remote.crewDesktop.where();
690
+ return result.ok ? result.value : void 0;
691
+ } catch (_error) {
692
+ return;
693
+ }
694
+ };
695
+ const models = /* @__PURE__ */ new WeakMap();
696
+ inner.slots.inject("sidebar.right.pane.tab", () => inner.slots.register({
697
+ name: "sidebar.right.pane.tab",
698
+ key: DESKTOP_TAB_ID,
699
+ locale: "crewDesktop",
700
+ inject: (key) => {
701
+ const sessionId = SessionId(key);
702
+ const binding = inner.sessions.binding(sessionId);
703
+ if (binding === void 0) throw new Error(`crew-desktop-ui: unknown Session ${sessionId}`);
704
+ let model = models.get(binding);
705
+ if (model === void 0) {
706
+ model = new DesktopModel(binding.eventSource, binding.session, where);
707
+ models.set(binding, model);
708
+ }
709
+ const current = model;
710
+ return {
711
+ hooks: { desktop: current.source },
712
+ pause: () => current.pause(),
713
+ takeOver: () => current.takeOver(),
714
+ resume: () => {
715
+ current.resume();
716
+ }
717
+ };
718
+ }
719
+ }, DesktopTab));
720
+ });
721
+ try {
722
+ await ui;
723
+ } catch (error) {
724
+ await ui.dispose();
725
+ await unmount();
726
+ throw error;
727
+ }
728
+ return async () => {
729
+ await ui.dispose();
730
+ await unmount();
731
+ };
732
+ }
733
+ //#endregion
734
+ exports.DESKTOP_TAB_ID = DESKTOP_TAB_ID;
735
+ exports.DesktopModel = DesktopModel;
736
+ exports.apply = apply;
737
+ exports.inject = inject;
738
+ exports.latestFrame = latestFrame;
739
+ return module.exports;
740
+ }
741
+ });
742
+
743
+ //# sourceMappingURL=client.js.map
package/lib/index.js ADDED
@@ -0,0 +1,6 @@
1
+ //#region lib/types/index.js
2
+ /** Browser registration is discovered through the package client declaration; the Host half is crew-desktop. */
3
+ /** Register the browser-only contribution without Host state. */
4
+ function apply() {}
5
+ //#endregion
6
+ export { apply };
@@ -0,0 +1,20 @@
1
+ import type { HostObservable, InjectFace, PropsLocale } from '@deepseek-ai/dsh-client-ui-slots';
2
+ import type { DesktopState } from './desktop-model.ts';
3
+ /** What the tab reads and does. */
4
+ export interface DesktopTabInjected {
5
+ hooks: {
6
+ desktop: HostObservable<DesktopState>;
7
+ };
8
+ readonly pause: () => Promise<boolean>;
9
+ readonly takeOver: () => Promise<boolean>;
10
+ readonly resume: () => void;
11
+ }
12
+ type Props = InjectFace<DesktopTabInjected> & PropsLocale<'crewDesktop'>;
13
+ /**
14
+ * Render the Desktop tab. Nothing stops the agent without a click.
15
+ * @param props - Desktop state, actions and copy.
16
+ * @returns the tab body.
17
+ */
18
+ export declare function DesktopTab({ useDesktop, pause, takeOver, resume, t }: Props): import("react").JSX.Element;
19
+ export {};
20
+ //# sourceMappingURL=DesktopTab.d.ts.map
@@ -0,0 +1,59 @@
1
+ /** Per-Session Desktop state: the latest frame and its image, whether the agent runs, and whether the user took over. */
2
+ import type { HostObservable } from '@deepseek-ai/dsh-client-ui-slots';
3
+ import type { SessionEventSource, SessionFace } from '@deepseek-ai/dsh-api-session-controller/client';
4
+ import type { DesktopLocation } from 'dsh-agent-crew-desktop/types';
5
+ import { type DesktopFrame } from './frame.ts';
6
+ /** What the Desktop tab shows. */
7
+ export interface DesktopState {
8
+ readonly frame: DesktopFrame | undefined;
9
+ /** Loaded image of `frame`, as a data URL. */
10
+ readonly image: string | undefined;
11
+ readonly imageFailed: boolean;
12
+ readonly running: boolean;
13
+ /** The user took over: the agent was stopped and stays stopped until they resume. */
14
+ readonly takenOver: boolean;
15
+ readonly location: DesktopLocation | undefined;
16
+ /** Whether reading the location failed. */
17
+ readonly locationFailed: boolean;
18
+ /** The last Pause or Take over could not stop the agent. */
19
+ readonly stopFailed: boolean;
20
+ }
21
+ /** The Session calls the model needs. */
22
+ export type DesktopSession = Pick<SessionFace, 'getSnapshot' | 'subscribe' | 'cancel' | 'readAttachment'>;
23
+ /** Observable Desktop state for one Session; it follows its sources only while the tab is open. */
24
+ export declare class DesktopModel {
25
+ private readonly events;
26
+ private readonly session;
27
+ private readonly where;
28
+ private state;
29
+ private readonly listeners;
30
+ private detach;
31
+ private revision;
32
+ private locating;
33
+ /** Current state. */
34
+ readonly source: HostObservable<DesktopState>;
35
+ /**
36
+ * @param events - the Session's event window.
37
+ * @param session - the Session, for running state, images and stopping.
38
+ * @param where - reads where computer use runs; undefined when the reply was lost.
39
+ */
40
+ constructor(events: SessionEventSource, session: DesktopSession, where: () => Promise<DesktopLocation | undefined>);
41
+ private publish;
42
+ private attach;
43
+ private readRunning;
44
+ private readFrame;
45
+ private stop;
46
+ /**
47
+ * Stop the agent's current turn.
48
+ * @returns whether the Host accepted the stop.
49
+ */
50
+ pause(): Promise<boolean>;
51
+ /**
52
+ * Stop the agent and mark that the user is in control.
53
+ * @returns whether the agent was stopped.
54
+ */
55
+ takeOver(): Promise<boolean>;
56
+ /** Hand control back; the agent continues when the user sends it a message. */
57
+ resume(): void;
58
+ }
59
+ //# sourceMappingURL=desktop-model.d.ts.map
@@ -0,0 +1,28 @@
1
+ /** Pick the screen an agent saw last: the newest image any tool returned in the loaded part of the Session. */
2
+ import type { SessionEventLikeEntry } from '@deepseek-ai/dsh-api-session-controller/client';
3
+ import type { ImageBlock } from '@deepseek-ai/dsh-llm';
4
+ /** One screenshot frame and where it came from. */
5
+ export interface DesktopFrame {
6
+ /** Stored image to load. */
7
+ readonly attachment: ImageBlock['attachment'];
8
+ /** Tool that returned it, such as a computer-use tool or `run_code`. */
9
+ readonly tool: string;
10
+ /** When the result was recorded, in epoch milliseconds. */
11
+ readonly time: number;
12
+ /** Session sequence of the result. */
13
+ readonly seq: number;
14
+ }
15
+ /**
16
+ * Find the newest tool-result image.
17
+ * @param entries - the Session's loaded event window.
18
+ * @returns the latest frame, or undefined when no tool has returned an image.
19
+ */
20
+ export declare function latestFrame(entries: readonly SessionEventLikeEntry[]): DesktopFrame | undefined;
21
+ /**
22
+ * Encode image bytes as a data URL the tab can show without another request.
23
+ * @param bytes - encoded image.
24
+ * @param mediaType - its verified media type.
25
+ * @returns a `data:` URL.
26
+ */
27
+ export declare function dataUrl(bytes: Uint8Array, mediaType: string): string;
28
+ //# sourceMappingURL=frame.d.ts.map
@@ -0,0 +1,21 @@
1
+ /** Mount the crew desktop Remote and register the right-panel Desktop tab. */
2
+ import type { Context } from '@deepseek-ai/cordis';
3
+ import { type DesktopKey } from './locales.ts';
4
+ export { latestFrame, type DesktopFrame } from './frame.ts';
5
+ export { DesktopModel, type DesktopState } from './desktop-model.ts';
6
+ declare module '@deepseek-ai/dsh-client-ui-slots' {
7
+ interface LocaleNamespaceMap {
8
+ crewDesktop: DesktopKey;
9
+ }
10
+ }
11
+ /** Registries the tab and the Remote need. */
12
+ export declare const inject: string[];
13
+ /** Desktop tab implementation identity and keyed Slot dispatch key. */
14
+ export declare const DESKTOP_TAB_ID = "dsh-agent-crew-desktop-ui";
15
+ /**
16
+ * Register the tab type, mount the Remote, then the tab body for the same lifetime.
17
+ * @param ctx - Client plugin context.
18
+ * @returns disposer removing the tab before the Remote.
19
+ */
20
+ export declare function apply(ctx: Context): Promise<() => Promise<void>>;
21
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,34 @@
1
+ /** Desktop tab copy. */
2
+ export declare const en: {
3
+ 'tab.title': string;
4
+ 'tab.description': string;
5
+ 'where.provider': string;
6
+ 'where.loading': string;
7
+ 'where.none': string;
8
+ 'where.failed': string;
9
+ 'status.running': string;
10
+ 'status.idle': string;
11
+ 'status.takenOver': string;
12
+ 'frame.alt': string;
13
+ 'frame.note': string;
14
+ 'pill.working': string;
15
+ 'pill.idle': string;
16
+ 'pill.taken': string;
17
+ 'hint.stop': string;
18
+ 'hint.resume': string;
19
+ 'frame.empty': string;
20
+ 'frame.loading': string;
21
+ 'frame.failed': string;
22
+ 'action.pause': string;
23
+ 'action.pauseLabel': string;
24
+ 'action.takeOver': string;
25
+ 'action.takeOverLabel': string;
26
+ 'action.resume': string;
27
+ 'action.resumeLabel': string;
28
+ 'action.failed': string;
29
+ };
30
+ /** Keys shared by every dictionary. */
31
+ export type DesktopKey = keyof typeof en;
32
+ /** Chinese copy. */
33
+ export declare const zh: Record<DesktopKey, string>;
34
+ //# sourceMappingURL=locales.d.ts.map
@@ -0,0 +1,4 @@
1
+ /** Browser registration is discovered through the package client declaration; the Host half is crew-desktop. */
2
+ /** Register the browser-only contribution without Host state. */
3
+ export declare function apply(): void;
4
+ //# sourceMappingURL=index.d.ts.map
package/locale/de.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "meta": {
3
+ "title": "Crew-Desktop",
4
+ "description": "Der Desktop-Tab: der letzte Bildschirm, den ein Agent bei der Computernutzung gesehen hat, mit Pausieren und Übernehmen."
5
+ }
6
+ }
package/locale/en.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "meta": {
3
+ "title": "Crew desktop",
4
+ "description": "The Desktop tab: the latest screen an agent saw while using the computer, with Pause and Take over."
5
+ }
6
+ }
package/locale/es.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "meta": {
3
+ "title": "Escritorio de la crew",
4
+ "description": "La pestaña Escritorio: la última pantalla que vio un agente al usar el ordenador, con Pausar y Tomar el control."
5
+ }
6
+ }
package/locale/fr.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "meta": {
3
+ "title": "Bureau de la crew",
4
+ "description": "L’onglet Bureau : le dernier écran vu par un agent pendant qu’il utilisait l’ordinateur, avec Pause et Prendre la main."
5
+ }
6
+ }
package/locale/ja.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "meta": {
3
+ "title": "クルーのデスクトップ",
4
+ "description": "デスクトップタブ:エージェントがコンピューター操作中に最後に見た画面を、一時停止と操作の引き継ぎとともに表示します。"
5
+ }
6
+ }
package/locale/pt.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "meta": {
3
+ "title": "Área de trabalho da crew",
4
+ "description": "A aba Área de trabalho: a última tela que um agente viu ao usar o computador, com Pausar e Assumir o controle."
5
+ }
6
+ }
package/locale/ru.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "meta": {
3
+ "title": "Рабочий стол команды",
4
+ "description": "Вкладка «Рабочий стол»: последний экран, который агент видел при работе с компьютером, с кнопками «Пауза» и «Взять управление»."
5
+ }
6
+ }
package/locale/zh.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "meta": {
3
+ "title": "Crew 桌面",
4
+ "description": "桌面标签页:智能体使用电脑时看到的最新画面,并提供暂停和接管。"
5
+ }
6
+ }
package/package.json CHANGED
@@ -1,6 +1,70 @@
1
1
  {
2
2
  "name": "dsh-agent-crew-desktop-ui",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "description": "Browser half of crew desktop: the right-panel Desktop tab with the latest screenshot, Pause and Take over",
4
+ "version": "0.1.0",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git+https://github.com/tusharcora/dsh-agent-crew.git",
8
+ "directory": "packages/crew-desktop-ui"
9
+ },
10
+ "type": "module",
11
+ "main": "lib/index.js",
12
+ "types": "lib/types/index.d.ts",
13
+ "exports": {
14
+ ".": {
15
+ "types": "./lib/types/index.d.ts",
16
+ "default": "./lib/index.js"
17
+ },
18
+ "./src/*": "./src/*",
19
+ "./package.json": "./package.json",
20
+ "./client": {
21
+ "types": "./lib/types/client/index.d.ts",
22
+ "default": "./lib/client.js"
23
+ },
24
+ "./locale/*.json": "./locale/*.json"
25
+ },
26
+ "dsh": {
27
+ "client": {
28
+ "platform": "web",
29
+ "inject": [
30
+ "@deepseek-ai/dsh-api-remotes",
31
+ "@deepseek-ai/dsh-api-session-controller",
32
+ "@deepseek-ai/dsh-client-locale",
33
+ "@deepseek-ai/dsh-client-ui-renderer",
34
+ "@deepseek-ai/dsh-client-ui-sidebar-right"
35
+ ]
36
+ }
37
+ },
38
+ "license": "MIT",
39
+ "peerDependencies": {
40
+ "@deepseek-ai/cordis": "~4.1.0"
41
+ },
42
+ "files": [
43
+ "locale/*.json",
44
+ "lib/index.js",
45
+ "lib/client.js",
46
+ "lib/types/**/*.d.ts",
47
+ "LICENSE"
48
+ ],
49
+ "peerDependenciesMeta": {
50
+ "@deepseek-ai/cordis": {
51
+ "optional": true
52
+ }
53
+ },
54
+ "keywords": [
55
+ "dsh",
56
+ "dsh-plugin",
57
+ "deepseek-harness",
58
+ "deepseek-harness-plugin",
59
+ "deepseek",
60
+ "dsh-agent-crew"
61
+ ],
62
+ "author": "Tushar (https://github.com/tusharcora)",
63
+ "homepage": "https://github.com/tusharcora/dsh-agent-crew#readme",
64
+ "bugs": {
65
+ "url": "https://github.com/tusharcora/dsh-agent-crew/issues"
66
+ },
67
+ "engines": {
68
+ "dsh": ">=0.2.2-rc.1 <0.3.0"
69
+ }
70
+ }