@morlay/dsh-desktop-host 0.2.1 → 0.2.2

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 CHANGED
@@ -1,61 +1,15 @@
1
1
  # @morlay/dsh-desktop-host
2
2
 
3
- 桌面部署里那个 **host 进程**:按 `desktop` profile 把 Web 应用启起来,然后**在进程内接管 `webServer`**——
4
- 不监听任何端口,Electron 壳把该应用自己的协议(`<app name>://app/*`)的请求经字节管道(FD 3/4)喂进来。
3
+ 桌面部署里那个 **host 进程**:按 `desktop` profile 把 Web 应用启起来,然后**在进程内接管 `webServer`**——不监听任何端口,Electron 壳把该应用自己的协议(`<app name>://app/*`)的请求经字节管道(FD 3/4)喂进来。
5
4
 
6
- 上游 `apps/desktop-host`(`@deepseek-ai/dsh-desktop-host`)是 `private: true` 的应用,外部装不到,所以这里
7
- 是它的**变体包**:argv / IPC 契约、`lib/index.js` 入口路径照旧,两处本地差别——Office 组合不再挂
8
- `@deepseek-ai/dsh-skill-office`(见 [`src/office.ts`](./src/office.ts)),载波从「监听端口 + 壳认证反向代理」
9
- 换成「宿主内 webServer 替身 + 管道」(见 [`src/webserver.ts`](./src/webserver.ts) 与桌面层的
10
- [设计 桌面无端口传输与窗口对齐](../dsh-desktopify/.agents/designs/20260920-桌面无端口传输与窗口对齐.md))。
5
+ 上游 `apps/desktop-host`(`@deepseek-ai/dsh-desktop-host`)是 `private: true` 的应用,外部装不到,所以这里是它的**变体包**:argv / IPC 契约、`lib/index.js` 入口路径照旧。
11
6
 
12
7
  ## 用法
13
8
 
14
9
  被 [`@morlay/dsh-desktopify`](../dsh-desktopify/README.md) 使用,不单独跑:
15
10
 
16
- - `dev` / `bundle` 把本包落位到部署的 `<runtime>/node_modules/@morlay/dsh-desktop-host`:`dev` 整份复制源码树,
17
- `bundle` 按 manifest `files` 复制。两条路都要带上这四样——`lib/index.js`(启动入口)、
18
- `lib/webserver.js`(桌面 patch 行按 `../lib/webserver.js` 加载)、`lib/wire.js`、
19
- `config/desktop.cordis.patch.yml`(入口按 `../config/desktop.cordis.patch.yml` 作为 `patchFiles` 读),
20
- 缺任一样 host 都起不来;
21
- - 壳按 `<runtime>/node_modules/@morlay/dsh-desktop-host/lib/index.js` 启动它(stdio 五元组:
22
- FD 3/4 是管道,FD 5 是 Node IPC),argv 为
23
- `[runtimeDir, projectDir, primaryRuntime, pnpmEntry, nodeBin]`(上游 0.1.7 删掉 `runProfile` 的 `resolutionMode` 后少一格);
24
- - IPC:`ready { protocolVersion }` / `fatal { message, diagnostic }`(`diagnostic` 是完整 `util.inspect` 的错误,
25
- 与上游 0.1.7-alpha.2 同形;壳的 dialog 用 `message`,完整诊断走 stderr),另外收 `shutdown`。
11
+ - `dev` / `bundle` 把本包落位到部署的 `<runtime>/node_modules/@morlay/dsh-desktop-host`(`dev` 整份复制源码树,`bundle` 按 manifest `files` 复制),四样必须都在——`lib/index.js`(启动入口)、`lib/webserver.js`(桌面 patch 行按 `../lib/webserver.js` 加载)、`lib/wire.js`、`config/desktop.cordis.patch.yml`(入口按它作 `patchFiles` 读);
12
+ - 壳按 `lib/index.js` 启动它(stdio 五元组:FD 3/4 是管道,FD 5 是 Node IPC),argv 为 `[entry, runtimeDir, projectDir, primaryRuntime, pnpmEntry?, nodeBin?]`;
13
+ - IPC:`ready { protocolVersion }` / `fatal { message, diagnostic }`,另外收 `shutdown`。
26
14
 
27
- 出口:`.`(启动入口)、`./webserver`(无端口 `webServer` 服务,供 patch 行加载)、`./wire`
28
- (管道分帧,壳构建时内联,部署里不需要单独解析它)。
29
-
30
- ## 运行时做的事
31
-
32
- 1. `runProfile` 装配 `desktop` profile(保住 profileContext / proxy / fail-loud / appReady / shutdown 语义);
33
- 2. 桌面 patch 禁用上游 `webserver` 行,插入本包的 `webServer` 替身——同一份路由面
34
- (`register` / `registerFallback` / `registerUpgrade` / `tapIndex` / `renderIndex` / `collectIndexInjections` /
35
- `port` / `host`),请求由 `dispatch(Request)` 从管道喂进来;
36
- 3. 接管浏览器认证:页面由壳独占、没有网络入口,`connection` 的 `requestRejection` 与 `authorizeIndex`
37
- 被改写为放行(理由与代价见[债务 桌面宿主覆写 connection 认证方法](../dsh-desktopify/.agents/debts/20260920-桌面宿主覆写connection认证方法.md));
38
- 4. 注入客户端 transport(`__DSH_TRANSPORT__ = { ownsHost, openStream }`)并注册 `/.dsh/remote-stream`
39
- (双向 NDJSON:请求体首行定 endpoint/payload、后续行是上行项,响应体是下行帧 →
40
- `typertGateway.wireStream.open`);
41
- 5. 桌面 patch 还按 id 停掉整条**账号/登录面**(`deepseek-account`、`account-controller`、
42
- `llm-deepseek-account`、`ui-settings-account`):登录要的回环地址回调在无端口下没有入口,
43
- 装着只会撞死路——理由与代价见 [ADR 桌面档不含登录与账号面](./.agents/adrs/20260928-桌面档不含登录与账号面.md)。
44
- 模型路径不受影响(`credentials` + `llm-pi-ai` 的 `ollama` 路由),探针
45
- [`tool/verify-desktop-account-plane.mts`](./tool/verify-desktop-account-plane.mts) 判这两件事。
46
-
47
- ## 依赖边界
48
-
49
- `@deepseek-ai/*` 全部由**部署自己的** `node_modules` 提供(与上游同边界):
50
-
51
- - `dependencies`:本包入口与替身真正 import 的 `@deepseek-ai/dsh`(含 `./profile-boot` 子路径)、
52
- `@deepseek-ai/dsh-app-boot`、`@deepseek-ai/dsh-home-paths`、`@deepseek-ai/dsh-host-webserver`
53
- (`renderIndexInjections`)、`@deepseek-ai/schemastery`(替身的 Config schema);
54
- - `peerDependencies`:`@deepseek-ai/cordis`;
55
- - 类型面 `@deepseek-ai/dsh-client-connection` / `@deepseek-ai/dsh-api-gateway` 只在 devDependencies
56
- (空 import 带 Context merge,构建后即被擦除);
57
- - vendor 里的 `workspace-dependencies` 模块在构建期内联,运行期不依赖 vendor 路径。
58
-
59
- 清单只声明**这份代码真正用到**的包:上游那份 app manifest 里的组合依赖
60
- (`@deepseek-ai/dsh-skill-office`、`dsh-agent`、`dsh-jobs`、`dsh-tools` …)属于部署的官方闭包,
61
- 由 `@morlay/dsh-desktopify` 的部署步骤按 profile 提供,不该由本包声明。
15
+ 运行时做的事(profile 装配、`webServer` 替身、认证接管、客户端 transport 与账号面)、出口面与依赖边界见[设计 桌面host的运行时面与依赖边界](./.agents/designs/20260929-桌面host的运行时面与依赖边界.md);载波形态的取舍见[设计 桌面无端口传输与窗口对齐](../dsh-desktopify/.agents/designs/20260920-桌面无端口传输与窗口对齐.md)。
package/lib/index.js CHANGED
@@ -25,19 +25,12 @@ var office_exports = /* @__PURE__ */ __exportAll({
25
25
  apply: () => apply,
26
26
  name: () => name
27
27
  });
28
- /** Loader identity for the application-owned workspace dependency composition. */
29
28
  const name = "desktop-office";
30
- /**
31
- * Enable the bundled authoring dependencies in the Desktop profile.
32
- * @param ctx - Profile scope; child plugins declare their own service requirements.
33
- * @param config - Bundled payload source and Harness-home installation root.
34
- */
35
29
  async function apply(ctx, config) {
36
30
  await ctx.plugin(workspaceDependencies, config);
37
31
  }
38
32
  //#endregion
39
33
  //#region src/transport.ts
40
- /** 注入 index 的 transport 行:声明页面拥有 Host,并给出 Gateway 流载体(下行流 + 上行项)。 */
41
34
  const DESKTOP_TRANSPORT_SCRIPT = `globalThis.__DSH_TRANSPORT__={
42
35
  _desktop:true,
43
36
  ownsHost:true,
@@ -73,9 +66,8 @@ const DESKTOP_TRANSPORT_SCRIPT = `globalThis.__DSH_TRANSPORT__={
73
66
  },
74
67
  fail(message){failure=new Error(message);ended=true;notify()},
75
68
  })
76
- // 取消必须结束迭代:abort 只解除 IPC 监听,挂在这一句等待上的消费方再也收不到
77
- // end 帧;不叫醒它就等于 dispose(RemoteStream 会 await iterator.return)永不落定——
78
- // 撤回 / 重试后的窗口重建正是卡在这里,页面既收不到新窗口也没有报错。
69
+ // 取消必须结束迭代:不叫醒它,等在这一句上的消费方收不到 end 帧,dispose(await iterator.return)
70
+ // 也永不落定——窗口重建会卡在这里。
79
71
  const onAbort=()=>{stream.cancel();ended=true;notify()}
80
72
  signal.addEventListener('abort',onAbort,{once:true})
81
73
  if(signal.aborted)onAbort()
@@ -108,12 +100,6 @@ const DESKTOP_TRANSPORT_SCRIPT = `globalThis.__DSH_TRANSPORT__={
108
100
  }
109
101
  }
110
102
  console.info('[dsh-desktop] transport installed',Object.keys(globalThis.__DSH_TRANSPORT__))`;
111
- /**
112
- * 桌面形态下放行 `connection` 的两处浏览器认证:`requestRejection`(`/api` 路由与
113
- * 各插件 handler 的自查)与 `authorizeIndex`(`frontend-static` 的 index 渲染)。
114
- *
115
- * 页面由壳独占、没有网络入口,认证没有对象;这里直接改写服务实例的方法。
116
- */
117
103
  function takeOverDesktopAuthentication(ctx) {
118
104
  const connection = ctx.get("connection");
119
105
  if (connection === void 0) throw new Error("dsh desktop: the composition did not provide the connection service");
@@ -124,13 +110,11 @@ function takeOverDesktopAuthentication(ctx) {
124
110
  function isRecord(value) {
125
111
  return typeof value === "object" && value !== null && !Array.isArray(value);
126
112
  }
127
- /** 桌面流请求体:首行是 open(endpoint / payload),后续每行是一道上行项。 */
128
113
  async function* readStreamFrames(request) {
129
114
  const decoder = new DesktopStreamBodyDecoder();
130
115
  for await (const chunk of request) for (const frame of decoder.push(chunk)) yield frame;
131
116
  for (const frame of decoder.finish()) yield frame;
132
117
  }
133
- /** 校验首行:它决定这条流开在哪个 endpoint 上。 */
134
118
  function parseStreamOpening(value, subject) {
135
119
  if (!isRecord(value) || typeof value.endpoint !== "string") throw new Error(`dsh desktop: invalid stream request ${subject}`);
136
120
  return {
@@ -198,7 +182,6 @@ function streamHandler(ctx) {
198
182
  }
199
183
  };
200
184
  }
201
- /** 注入 transport 行并注册 Gateway 流路由;随插件 fiber 一起撤销。 */
202
185
  function installDesktopTransport(ctx) {
203
186
  ctx.on("webserver/index-inject", (table) => {
204
187
  table.push({
@@ -215,24 +198,11 @@ function installDesktopTransport(ctx) {
215
198
  }
216
199
  //#endregion
217
200
  //#region src/index.ts
218
- /**
219
- * 桌面部署里的 host 进程入口:按 `desktop` profile 装配 Web 应用,并把请求经字节管道
220
- * (FD 3/4)交给宿主内的无端口 `webServer`。
221
- *
222
- * argv:`[runtimeDir, projectDir, primaryRuntime, pnpmEntry?, nodeBin?]`;
223
- * IPC:`ready` / `fatal`,另收 `shutdown`。
224
- */
225
201
  const DESKTOP_PATCH = fileURLToPath(new URL("../config/desktop.cordis.patch.yml", import.meta.url));
226
202
  function errorMessage(error) {
227
203
  return error instanceof Error ? error.message : String(error);
228
204
  }
229
- /** 诊断上限:壳把它写进日志,再长也没有额外价值。 */
230
205
  const MAX_FATAL_DIAGNOSTIC_CHARS = 65536;
231
- /**
232
- * fatal 事件:`message` 给壳的 dialog 用,`diagnostic` 是完整 `inspect`(`code` / `syscall` /
233
- * `path` / `cause` 链,stack 行里没有)——stderr 字节与这条 IPC 谁先到不确定,壳报告的是它
234
- * 先看到的那一条,所以诊断得跟 IPC 一起走。
235
- */
236
206
  function fatalEvent(error) {
237
207
  return {
238
208
  type: "fatal",
package/lib/webserver.js CHANGED
@@ -4,14 +4,6 @@ import { Service } from "@deepseek-ai/cordis";
4
4
  import z from "@deepseek-ai/schemastery";
5
5
  import { renderIndexInjections } from "@deepseek-ai/dsh-host-webserver";
6
6
  //#region src/webserver.ts
7
- /**
8
- * 桌面形态的 `webServer` 服务:与上游同名的路由载体,但不监听任何端口。
9
- *
10
- * 上游 `@deepseek-ai/dsh-host-webserver` 在激活时 `listen`。桌面宿主自己拥有页面,
11
- * 没有网络入口,于是这里保留同一份服务面(register / registerFallback / registerUpgrade /
12
- * tapIndex / renderIndex / collectIndexInjections / port / host),把请求由 `dispatch`
13
- * 从字节管道直接喂进来。
14
- */
15
7
  function deferred() {
16
8
  let resolve;
17
9
  return {
@@ -21,14 +13,12 @@ function deferred() {
21
13
  resolve
22
14
  };
23
15
  }
24
- /** 由 handler 写出的 node 风格响应:写出头部即对外可见,`end` 收尾响应体流。 */
25
16
  var SyntheticResponse = class extends EventEmitter {
26
17
  statusCode = 200;
27
18
  statusMessage = "";
28
19
  headersSent = false;
29
20
  writableEnded = false;
30
21
  destroyed = false;
31
- /** 上游中间件按 `res.socket === undefined` 识别「没有真实连接」。 */
32
22
  socket = void 0;
33
23
  body;
34
24
  started;
@@ -102,7 +92,6 @@ var SyntheticResponse = class extends EventEmitter {
102
92
  this.emit("close");
103
93
  return this;
104
94
  }
105
- /** handler 抛错:未写头时以 500 收场,已写头则中断响应体。 */
106
95
  fail(error) {
107
96
  if (this.destroyed) return;
108
97
  if (!this.headersSent) {
@@ -133,7 +122,6 @@ function toBytes(chunk) {
133
122
  function errorMessage(error) {
134
123
  return error instanceof Error ? error.message : String(error);
135
124
  }
136
- /** 把一条 Fetch 请求合成 node 风格的 req/res,供注册在这些表上的 handler 使用。 */
137
125
  function createRequest(request, url) {
138
126
  const stream = Readable.from(request.body === null ? [] : consume(request.body));
139
127
  const headers = {};
@@ -176,7 +164,6 @@ var PortlessWebServer = class extends Service {
176
164
  super(ctx, "webServer");
177
165
  this.config = config;
178
166
  }
179
- /** 没有真实监听:报配置值(缺省 0),只用于拼展示用 URL。 */
180
167
  get port() {
181
168
  return this.config.port ?? 0;
182
169
  }
@@ -225,7 +212,6 @@ var PortlessWebServer = class extends Service {
225
212
  renderIndex(html) {
226
213
  return this.applyIndexTaps(renderIndexInjections(html, this.collectIndexInjections()));
227
214
  }
228
- /** 把一条管道请求分派给注册表;没有匹配也没有 fallback 时回 404。 */
229
215
  async dispatch(request) {
230
216
  const url = new URL(request.url);
231
217
  let pathname;
package/lib/wire.js CHANGED
@@ -1,35 +1,19 @@
1
1
  import { StringDecoder } from "node:string_decoder";
2
2
  //#region src/wire.ts
3
- /**
4
- * 桌面 host 与 Electron 壳之间的字节管道协议:请求帧(壳 → host,FD 3)与响应帧(host → 壳,FD 4)。
5
- *
6
- * 帧头 13 字节:magic(4) | type(1) | streamId(4) | payloadLength(4)。data 帧是原始字节,
7
- * 其余帧是 JSON 或空载荷。两侧共用这一份实现,避免编解码各写一遍。
8
- */
9
3
  const DESKTOP_HOST_PROTOCOL_VERSION = 1;
10
- /** 桌面流的宿主路径:页面/主进程都用它(两侧共享一份,避免路径写两处)。 */
11
4
  const DESKTOP_STREAM_PATH = "/.dsh/remote-stream";
12
- /**
13
- * 桌面流请求体的首行:定下这条逻辑流的 endpoint 与 payload,后续行都是上行项
14
- * (客户端 → 宿主;`$events` 这类宿主自己的流被上游释放,不会读到它们)。
15
- */
16
5
  function encodeDesktopStreamOpen(endpoint, payload) {
17
6
  return `${JSON.stringify({
18
7
  endpoint,
19
8
  payload
20
9
  })}\n`;
21
10
  }
22
- /** 桌面流请求体里的一道上行项。 */
23
11
  function encodeDesktopStreamItem(item) {
24
12
  return `${JSON.stringify(item)}\n`;
25
13
  }
26
- /** 壳写请求帧的管道描述符。 */
27
14
  const DESKTOP_REQUEST_PIPE_FD = 3;
28
- /** host 写响应帧的管道描述符。 */
29
15
  const DESKTOP_RESPONSE_PIPE_FD = 4;
30
- /** Node 生命周期 IPC 通道的描述符(ready / fatal / shutdown)。 */
31
16
  const DESKTOP_CONTROL_IPC_FD = 5;
32
- /** 单个 data 帧携带的最大原始字节数。 */
33
17
  const DESKTOP_PIPE_CHUNK_BYTES = 65536;
34
18
  const FRAME_MAGIC = 1146308659;
35
19
  const FRAME_HEADER_BYTES = 13;
@@ -66,35 +50,27 @@ function encodeFrame(type, streamId, payload, dataType, direction) {
66
50
  function jsonPayload(type, streamId, value, dataType, direction) {
67
51
  return encodeFrame(type, streamId, Buffer.from(JSON.stringify(value), "utf8"), dataType, direction);
68
52
  }
69
- /** 编码开启一个请求流的元数据帧(壳侧)。 */
70
53
  function encodeDesktopRequestStart(streamId, request) {
71
54
  return jsonPayload(REQUEST_FRAME_START, streamId, request, REQUEST_FRAME_DATA, "request");
72
55
  }
73
- /** 编码一个受限的请求体原始分片(壳侧)。 */
74
56
  function encodeDesktopRequestData(streamId, data) {
75
57
  return encodeFrame(REQUEST_FRAME_DATA, streamId, Buffer.from(data), REQUEST_FRAME_DATA, "request");
76
58
  }
77
- /** 编码请求体正常结束(壳侧)。 */
78
59
  function encodeDesktopRequestEnd(streamId) {
79
60
  return encodeFrame(REQUEST_FRAME_END, streamId, Buffer.alloc(0), REQUEST_FRAME_DATA, "request");
80
61
  }
81
- /** 编码取消一个请求及其响应(壳侧)。 */
82
62
  function encodeDesktopRequestCancel(streamId) {
83
63
  return encodeFrame(REQUEST_FRAME_CANCEL, streamId, Buffer.alloc(0), REQUEST_FRAME_DATA, "request");
84
64
  }
85
- /** 编码响应元数据帧(host 侧)。 */
86
65
  function encodeDesktopResponseStart(streamId, response) {
87
66
  return jsonPayload(RESPONSE_FRAME_START, streamId, response, RESPONSE_FRAME_DATA, "response");
88
67
  }
89
- /** 编码一个受限的响应体原始分片(host 侧)。 */
90
68
  function encodeDesktopResponseData(streamId, data) {
91
69
  return encodeFrame(RESPONSE_FRAME_DATA, streamId, Buffer.from(data), RESPONSE_FRAME_DATA, "response");
92
70
  }
93
- /** 编码响应正常结束(host 侧)。 */
94
71
  function encodeDesktopResponseEnd(streamId) {
95
72
  return encodeFrame(RESPONSE_FRAME_END, streamId, Buffer.alloc(0), RESPONSE_FRAME_DATA, "response");
96
73
  }
97
- /** 编码一次响应失败,不把 Error 对象跨进程传递(host 侧)。 */
98
74
  function encodeDesktopResponseError(streamId, message) {
99
75
  return jsonPayload(RESPONSE_FRAME_ERROR, streamId, { message }, RESPONSE_FRAME_DATA, "response");
100
76
  }
@@ -120,19 +96,13 @@ function parseJson(payload, subject) {
120
96
  function assertEmpty(payload, subject) {
121
97
  if (payload.byteLength !== 0) throw new Error(`dsh desktop: ${subject} frame carried a payload`);
122
98
  }
123
- /**
124
- * 增量解码桌面流请求体:按行取 JSON,首行是 `open`(endpoint / payload),后续每行是一道上行项。
125
- * 字节边界可能切开多字节字符,所以先过 `StringDecoder` 再切行。
126
- */
127
99
  var DesktopStreamBodyDecoder = class {
128
100
  text = new StringDecoder("utf8");
129
101
  buffer = "";
130
- /** 追加字节并返回所有已完整的行的解析值(空行忽略)。 */
131
102
  push(chunk) {
132
103
  this.buffer += this.text.write(Buffer.from(chunk));
133
104
  return this.take(false);
134
105
  }
135
- /** 请求体结束:交付末行(没有换行收尾时也存在)。 */
136
106
  finish() {
137
107
  this.buffer += this.text.end();
138
108
  return this.take(true);
@@ -161,10 +131,8 @@ function parseStreamLine(line) {
161
131
  throw new Error(`dsh desktop: stream body line is not JSON: ${error instanceof Error ? error.message : String(error)}`);
162
132
  }
163
133
  }
164
- /** 增量解码 host 侧从请求管道收到的请求帧。 */
165
134
  var DesktopHostRequestDecoder = class {
166
135
  buffer = Buffer.alloc(0);
167
- /** 追加字节并返回所有已完整的请求帧。 */
168
136
  push(chunk) {
169
137
  this.buffer = this.buffer.byteLength === 0 ? chunk : Buffer.concat([this.buffer, chunk]);
170
138
  const frames = [];
@@ -174,7 +142,6 @@ var DesktopHostRequestDecoder = class {
174
142
  frames.push(frame);
175
143
  }
176
144
  }
177
- /** EOF 落在帧中间时报错。 */
178
145
  finish() {
179
146
  if (this.buffer.byteLength !== 0) throw new Error("dsh desktop: request pipe ended inside a frame");
180
147
  }
@@ -222,10 +189,8 @@ var DesktopHostRequestDecoder = class {
222
189
  };
223
190
  }
224
191
  };
225
- /** 增量解码壳侧从响应管道收到的响应帧。 */
226
192
  var DesktopHostResponseDecoder = class {
227
193
  buffer = Buffer.alloc(0);
228
- /** 追加字节并返回所有已完整的响应帧。 */
229
194
  push(chunk) {
230
195
  this.buffer = this.buffer.byteLength === 0 ? chunk : Buffer.concat([this.buffer, chunk]);
231
196
  const frames = [];
@@ -235,7 +200,6 @@ var DesktopHostResponseDecoder = class {
235
200
  frames.push(frame);
236
201
  }
237
202
  }
238
- /** EOF 落在帧中间时报错。 */
239
203
  finish() {
240
204
  if (this.buffer.byteLength !== 0) throw new Error("dsh desktop: response pipe ended inside a frame");
241
205
  }
@@ -286,14 +250,12 @@ var DesktopHostResponseDecoder = class {
286
250
  return { message: value.message };
287
251
  }
288
252
  };
289
- /** IPC 事件校验(壳侧)。 */
290
253
  function isDesktopHostEvent(value) {
291
254
  if (!isRecord(value)) return false;
292
255
  if (value.type === "ready") return value.protocolVersion === 1;
293
256
  if (value.type === "fatal") return typeof value.message === "string" && (value.diagnostic === void 0 || typeof value.diagnostic === "string");
294
257
  return false;
295
258
  }
296
- /** IPC 命令校验(host 侧)。 */
297
259
  function isDesktopHostCommand(value) {
298
260
  return isRecord(value) && value.type === "shutdown";
299
261
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@morlay/dsh-desktop-host",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "description": "Desktop host process for dsh-desktopify: boots the desktop profile and serves it through an in-process, portless webServer over byte pipes — upstream apps/desktop-host with the web carrier taken over and the Office skill mount removed.",
5
5
  "keywords": [
6
6
  "desktop",