@deepseek-ai/dsh-client-connection 0.1.0-rc.7 → 0.1.1-rc.1
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.i18n.yaml +2 -2
- package/README.md +1 -1
- package/README.zh.md +2 -2
- package/lib/client.js +146 -25
- package/lib/types/client/index.d.ts +21 -1
- package/lib/types/client/rpc.d.ts +4 -1
- package/package.json +19 -13
package/README.i18n.yaml
CHANGED
|
@@ -2,5 +2,5 @@
|
|
|
2
2
|
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
|
3
3
|
# after editing either side, bring the other along and re-record with:
|
|
4
4
|
# pnpm run verify-translation-pairing --write packages/client/connection/README.md
|
|
5
|
-
README.md:
|
|
6
|
-
README.zh.md:
|
|
5
|
+
README.md: a7562b9dac57930b1abc0b76b9079a6865a38b35
|
|
6
|
+
README.zh.md: 24c56e598ebd4b5ca39e433c5782399909f528b8
|
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
English | [中文](README.zh.md)
|
|
4
4
|
|
|
5
|
-
Wire consumer layer: the client plugin's apply mounts `ctx.connection` (shared api client + current-page loopback state + observable generation-scoped `hostDescription` + single-consumer stream-loop starter); the export face carries the wire contract types, the `AbstractApiClient` abstraction, and the loop's sink/config types. Each successful readiness handshake publishes the exact `host.describe` value before `onConnected`; generation loss and explicit stop clear it, so native-capability consumers never retain a disconnected answer. The browser carrier uses HTTP POST for unary and respond operations and opens one downlink-only WebSocket each for `events.mux` and `events.host`; the in-process carrier satisfies the same two-stream abstraction. The Host half owns the single `/api` route and its Fetch bridge; a registered Typert interceptor claims its Remote endpoints before the API Proxy fallback. Loopback hostname classification stays package-internal: the `/api` Host fence and WebSocket upgrades use it directly, while other client plugins consume the derived `ctx.connection.isLoopback` state. The node half's `/api` route pins the privileged method set (`host.pickDirectory`, `host.openPath`, and the whole configuration plane — `settings.describe`/`openDocument`/`update`/`replace`/`mutate` and `credentials.describe`/`set`/`unset`; reads and native actions included, since describing returns the exposed configuration, opening acts on the Host desktop, and probing an arbitrary reference reports where a credential comes from — and the agent-preset authoring plane, `agentPreset.read`/`copy`/`openDocument`/`remove`, since a composition names the plugins a session runs, so reading one is reconnaissance, and copy/remove/openDocument manage the roster and drive the host desktop (authoring is copy-only, so none of them accepts composition text or a path); `agentPreset.list` and `agentPreset.select` stay out — the roster carries only ids and trust, and choosing a preset grants nothing `session.create`'s own `agentPreset` did not, over a default that already carries bash) to loopback by passing the trust fence with an empty trust list — a declared `trustedHosts` authority reaches every other method, while these stay loopback-local until a real authentication layer exists. The platform carriers and ConnectionController loop are package-internal; apply selects and drives them. The downlink boundary is documented in the [WebSocket downlink carrier Agent Note](../../../.agents/notes/implemented/architecture/2026-08-04-websocket-downlink-carrier.md).
|
|
5
|
+
Wire consumer layer: the client plugin's apply mounts `ctx.connection` (shared api client + current-page loopback state + observable generation-scoped `hostDescription` + single-consumer stream-loop starter); the export face carries the wire contract types, the `AbstractApiClient` abstraction, and the loop's sink/config types. Each successful readiness handshake publishes the exact `host.describe` value before `onConnected`; generation loss and explicit stop clear it, so native-capability consumers never retain a disconnected answer. The browser carrier uses HTTP POST for unary and respond operations and opens one downlink-only WebSocket each for `events.mux` and `events.host`; the in-process carrier satisfies the same two-stream abstraction. The exported `ClientTransportHooks` names the page global `__DSH_TRANSPORT__` that replaces the browser carrier wholesale: the served web app leaves it unset and gets HTTP + WebSocket, while a shell owning a different physical transport (the worker preview's postMessage tunnel) provides `createApiClient` and `fetch` — plus `loadBundle` when it also owns bundle bytes — instead of forking the plugin. The Host half owns the single `/api` route and its Fetch bridge; a registered Typert interceptor claims its Remote endpoints before the API Proxy fallback. Loopback hostname classification stays package-internal: the `/api` Host fence and WebSocket upgrades use it directly, while other client plugins consume the derived `ctx.connection.isLoopback` state. The node half's `/api` route pins the privileged method set (`host.pickDirectory`, `host.openPath`, and the whole configuration plane — `settings.describe`/`openDocument`/`update`/`replace`/`mutate` and `credentials.describe`/`set`/`unset`; reads and native actions included, since describing returns the exposed configuration, opening acts on the Host desktop, and probing an arbitrary reference reports where a credential comes from — and the agent-preset authoring plane, `agentPreset.read`/`copy`/`openDocument`/`remove`, since a composition names the plugins a session runs, so reading one is reconnaissance, and copy/remove/openDocument manage the roster and drive the host desktop (authoring is copy-only, so none of them accepts composition text or a path); `agentPreset.list` and `agentPreset.select` stay out — the roster carries only ids and trust, and choosing a preset grants nothing `session.create`'s own `agentPreset` did not, over a default that already carries bash) to loopback by passing the trust fence with an empty trust list — a declared `trustedHosts` authority reaches every other method, while these stay loopback-local until a real authentication layer exists. The platform carriers and ConnectionController loop are package-internal; apply selects and drives them. The downlink boundary is documented in the [WebSocket downlink carrier Agent Note](../../../.agents/notes/implemented/architecture/2026-08-04-websocket-downlink-carrier.md).
|
|
6
6
|
|
|
7
7
|
## /api browser-trust fence
|
|
8
8
|
|
package/README.zh.md
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
[English](README.md) | 中文
|
|
4
4
|
|
|
5
|
-
协议消费层:客户端插件的 apply 会挂载 `ctx.connection`(共享 API 客户端 + 当前页面的 loopback 状态 + 可观察且按 generation 生效的 `hostDescription` + 单消费方流循环启动器);导出表层携带协议约定类型、`AbstractApiClient` 抽象,以及循环的 sink/配置类型。每次就绪握手成功后,都会在 `onConnected` 之前发布完整的 `host.describe` 值;generation 失效或显式 stop 会清空它,因此原生能力消费者不会保留已经断线的判断。浏览器载体以 HTTP POST 发送 unary/respond,并为 `events.mux` 与 `events.host` 各开一条只下行的 WebSocket
|
|
5
|
+
协议消费层:客户端插件的 apply 会挂载 `ctx.connection`(共享 API 客户端 + 当前页面的 loopback 状态 + 可观察且按 generation 生效的 `hostDescription` + 单消费方流循环启动器);导出表层携带协议约定类型、`AbstractApiClient` 抽象,以及循环的 sink/配置类型。每次就绪握手成功后,都会在 `onConnected` 之前发布完整的 `host.describe` 值;generation 失效或显式 stop 会清空它,因此原生能力消费者不会保留已经断线的判断。浏览器载体以 HTTP POST 发送 unary/respond,并为 `events.mux` 与 `events.host` 各开一条只下行的 WebSocket;进程内载体满足同一双流抽象。导出的 `ClientTransportHooks` 命名了整体替换浏览器载体的页面全局量 `__DSH_TRANSPORT__`:served web app 不设置它、走 HTTP + WebSocket;拥有另一种物理传输的壳(worker 预览的 postMessage 隧道)则在此提供 `createApiClient` 与 `fetch`——当它同时持有 bundle 字节时再加 `loadBundle`——而不必 fork 本插件。Host half 持有唯一 `/api` route 及其 Fetch bridge;已注册的 Typert interceptor 会先认领自己的 Remote endpoint,未认领请求再回退 API Proxy。Loopback hostname 判定逻辑留在包内部:`/api` Host fence 与 WebSocket upgrade 会直接使用它,其他客户端插件则消费派生的 `ctx.connection.isLoopback` 状态。node 半侧的 `/api` 路由让特权方法集(`host.pickDirectory`、`host.openPath`,以及整个配置面——`settings.describe`/`openDocument`/`update`/`replace`/`mutate` 与 `credentials.describe`/`set`/`unset`;读取与原生操作也在内,因为 describe 会返回已暴露的配置、打开操作会作用于 Host 桌面,而探测任意引用会报出某条凭据来自何处——以及 agent(智能体) preset 的创作面 `agentPreset.read`/`copy`/`openDocument`/`remove`,因为组装指明了一个会话所运行的插件,读取它是侦察,而 copy/remove/openDocument 管理名单并驱动宿主桌面(创作只有复制一种写入,因此这些方法都不接收组装文本或路径);`agentPreset.list` 与 `agentPreset.select` 不在其中——名单只携带 id 与信任级别,而选择一个 preset 并不比 `session.create` 自带的 `agentPreset` 多给任何能力,何况默认 preset 本就带着 bash)以空信任表过信任 fence,从而钉在回环——已声明的 `trustedHosts` 授权可达其余全部方法,而这些方法在真正的认证层出现之前仍只限回环本机。平台载体与 ConnectionController 循环属于包内部;apply 负责选择并驱动它们。下行边界见 [WebSocket 下行载体 Agent Note](../../../.agents/notes/implemented/architecture/2026-08-04-websocket-downlink-carrier.zh.md)。
|
|
6
6
|
|
|
7
7
|
## /api 浏览器信任栅栏
|
|
8
8
|
|
|
9
|
-
node 半侧在桥接或 upgrade 前守卫 `/api` 下的每个入口(`src/api-request-trust.ts`)。每个请求——无论是否带浏览器标记——`Host` 都必须是回环地址权威,或与某个 `trustedHosts` 条目匹配:带端口的 `host:port` 条目精确匹配,不带端口的条目匹配任意端口,两侧均经 WHATWG 归一化后比较(DNS rebinding 防御)。刻意不为无浏览器标记的 HTTP 请求开捷径:明文 HTTP 下浏览器的图片与导航读取既不带 `Origin` 也不带 Fetch-Metadata,因此无标记请求仍可能是被重绑页面发起的、响应可被读走的读取,而 Host 是重绑唯一伪造不了的请求头;WebSocket 浏览器握手会带 `Origin` 并通过同一道比较。非浏览器客户端经由回环地址、部署推导的 LAN IP 字面量或已声明的权威通过同一道栅栏。当标记存在时,如附带 `Origin`,则它必须与 Host 权威完全一致;显式的 `sec-fetch-site: cross-site` 标记一律拒绝。不是纯的、规范形 `host[:port]` 权威的 `trustedHosts` 条目——即 WHATWG 解析读回后与原文不完全一致的——会让插件加载明确报错:否则解析会悄悄授权 `harness.internal/path` 这类笔误里的 hostname,或把悬空冒号、补零端口放大成任意端口授权。HTTP 失败在任何 RPC 分发之前以纯 403 应答,upgrade 失败在启动任何事件流前拒绝握手。非回环组合必须显式信任其服务权威:Web 运行时从全接口服务器配置推导 LAN IP 字面量,cordis.yml 中的 `trustedHosts` 与 CLI(命令行界面)的 `--trusted-host` flag 则声明具名权威。`dsh web --host 0.0.0.0` 在远程访问具备认证层之前有意不受支持。这道栅栏是可达性策略,而不是认证;Web 载体不提供认证层。决策记录:[api 浏览器信任边界 Agent Note](../../../.agents/notes/implemented/architecture/2026-07-28-api-browser-trust-boundary.md)。
|
|
9
|
+
node 半侧在桥接或 upgrade 前守卫 `/api` 下的每个入口(`src/api-request-trust.ts`)。每个请求——无论是否带浏览器标记——`Host` 都必须是回环地址权威,或与某个 `trustedHosts` 条目匹配:带端口的 `host:port` 条目精确匹配,不带端口的条目匹配任意端口,两侧均经 WHATWG 归一化后比较(DNS rebinding 防御)。刻意不为无浏览器标记的 HTTP 请求开捷径:明文 HTTP 下浏览器的图片与导航读取既不带 `Origin` 也不带 Fetch-Metadata,因此无标记请求仍可能是被重绑页面发起的、响应可被读走的读取,而 Host 是重绑唯一伪造不了的请求头;WebSocket 浏览器握手会带 `Origin` 并通过同一道比较。非浏览器客户端经由回环地址、部署推导的 LAN IP 字面量或已声明的权威通过同一道栅栏。当标记存在时,如附带 `Origin`,则它必须与 Host 权威完全一致;显式的 `sec-fetch-site: cross-site` 标记一律拒绝。不是纯的、规范形 `host[:port]` 权威的 `trustedHosts` 条目——即 WHATWG 解析读回后与原文不完全一致的——会让插件加载明确报错:否则解析会悄悄授权 `harness.internal/path` 这类笔误里的 hostname,或把悬空冒号、补零端口放大成任意端口授权。HTTP 失败在任何 RPC 分发之前以纯 403 应答,upgrade 失败在启动任何事件流前拒绝握手。非回环组合必须显式信任其服务权威:Web 运行时从全接口服务器配置推导 LAN IP 字面量,cordis.yml 中的 `trustedHosts` 与 CLI(命令行界面)的 `--trusted-host` flag 则声明具名权威。`dsh web --host 0.0.0.0` 在远程访问具备认证层之前有意不受支持。这道栅栏是可达性策略,而不是认证;Web 载体不提供认证层。决策记录:[api 浏览器信任边界 Agent Note](../../../.agents/notes/implemented/architecture/2026-07-28-api-browser-trust-boundary.zh.md)。
|
|
10
10
|
|
|
11
11
|
## `/api` WebSocket 下行
|
|
12
12
|
|
package/lib/client.js
CHANGED
|
@@ -5263,8 +5263,9 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5263
5263
|
workspaceId: workspaceIdSchema.optional(),
|
|
5264
5264
|
cwd: string().optional(),
|
|
5265
5265
|
sessionId: sessionIdSchema.optional(),
|
|
5266
|
-
agentPreset: string().optional()
|
|
5267
|
-
|
|
5266
|
+
agentPreset: string().optional(),
|
|
5267
|
+
reuseWorkspaceBlank: literal(true).optional()
|
|
5268
|
+
}).refine((payload) => payload.workspaceId === void 0 || payload.cwd === void 0, { message: "session.create accepts workspaceId or cwd, not both" }).refine((payload) => payload.reuseWorkspaceBlank !== true || payload.workspaceId !== void 0 && payload.sessionId !== void 0, { message: "session.create reuseWorkspaceBlank requires workspaceId and sessionId" });
|
|
5268
5269
|
/** session.create response value. */
|
|
5269
5270
|
const sessionCreateValueSchema = object({
|
|
5270
5271
|
sessionId: sessionIdSchema,
|
|
@@ -5360,6 +5361,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5360
5361
|
maxImagesPerMessage: number().int().positive(),
|
|
5361
5362
|
maxMessageImageBytes: number().int().positive(),
|
|
5362
5363
|
maxImagePixels: number().int().positive(),
|
|
5364
|
+
maxImageDimension: number().int().positive(),
|
|
5363
5365
|
mediaTypes: array(string())
|
|
5364
5366
|
});
|
|
5365
5367
|
/** session.history response value (projections rides the tail page only). */
|
|
@@ -5707,6 +5709,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5707
5709
|
provider: string().optional(),
|
|
5708
5710
|
model: string().optional(),
|
|
5709
5711
|
attachedSessions: number().int().nonnegative(),
|
|
5712
|
+
home: string(),
|
|
5710
5713
|
canOpenPath: boolean()
|
|
5711
5714
|
});
|
|
5712
5715
|
object({});
|
|
@@ -7084,7 +7087,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
7084
7087
|
toolTurn(67, "grep", "{\"pattern\":\"SEARCH_MAX_LINES\",\"path\":\"packages/client\"}", SEARCH_MATCHES_TEXT);
|
|
7085
7088
|
toolTurn(68, "glob", "{\"pattern\":\"**/SearchBlock*\",\"path\":\"packages/client\"}", SEARCH_PATHS_TEXT);
|
|
7086
7089
|
toolTurn(69, "read", `{"file_path":${JSON.stringify(READ_SAMPLE_PATH)},"offset":${READ_SAMPLE_FIRST_LINE}}`, READ_SAMPLE_TEXT);
|
|
7087
|
-
toolTurn(70, "web_search", "{\"
|
|
7090
|
+
toolTurn(70, "web_search", "{\"queries\":[\"deepseek harness architecture\"]}", "Search results for deepseek harness architecture.");
|
|
7088
7091
|
toolTurn(71, "web_fetch", "{\"url\":\"https://www.deepseek.com/blog/harness-architecture\"}", "# Harness architecture\n\nEverything is a plugin.");
|
|
7089
7092
|
push({
|
|
7090
7093
|
type: "turn/start",
|
|
@@ -7264,7 +7267,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
7264
7267
|
};
|
|
7265
7268
|
case "web_search": return {
|
|
7266
7269
|
card: "generic",
|
|
7267
|
-
title: `Search ${
|
|
7270
|
+
title: `Search ${(Array.isArray(args.queries) ? args.queries.filter((query) => typeof query === "string" && query !== "") : []).join(", ")}`,
|
|
7268
7271
|
kind: "search",
|
|
7269
7272
|
rawInput: args
|
|
7270
7273
|
};
|
|
@@ -7357,29 +7360,37 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
7357
7360
|
}
|
|
7358
7361
|
}
|
|
7359
7362
|
/**
|
|
7360
|
-
* Fixture parallel of the plan unit's
|
|
7361
|
-
*
|
|
7362
|
-
*
|
|
7363
|
-
*
|
|
7363
|
+
* Fixture parallel of the plan unit's lifecycle fold. The paired
|
|
7364
|
+
* `command/done` retains successful plan selections and drops failures;
|
|
7365
|
+
* `plan/mode` commits one. `wanted` is exposed for the prompt boundary (the
|
|
7366
|
+
* fixture's step/start parallel).
|
|
7364
7367
|
*/
|
|
7365
7368
|
function foldPlan(log) {
|
|
7366
7369
|
let active = false;
|
|
7367
7370
|
let wanted = null;
|
|
7371
|
+
let running = null;
|
|
7368
7372
|
for (const event of log) {
|
|
7369
7373
|
const item = event;
|
|
7370
7374
|
if (item.type === "command/run" && item.data?.["name"] === "plan") {
|
|
7371
7375
|
const args = item.data["args"];
|
|
7372
7376
|
if (typeof args !== "string") continue;
|
|
7373
|
-
|
|
7377
|
+
running = {
|
|
7378
|
+
commandId: item.data["commandId"],
|
|
7379
|
+
wanted: args.trim() !== "off"
|
|
7380
|
+
};
|
|
7381
|
+
} else if (item.type === "command/done" && item.data !== void 0 && running !== null && item.data["commandId"] === running.commandId) {
|
|
7382
|
+
wanted = item.data["kind"] === "success" && running.wanted !== active ? running.wanted : null;
|
|
7383
|
+
running = null;
|
|
7374
7384
|
} else if (item.type === "plan/mode") {
|
|
7375
7385
|
active = item.data?.["active"] === true;
|
|
7376
7386
|
wanted = null;
|
|
7377
7387
|
}
|
|
7378
7388
|
}
|
|
7389
|
+
const selected = running?.wanted ?? wanted;
|
|
7379
7390
|
return {
|
|
7380
7391
|
active,
|
|
7381
|
-
pending:
|
|
7382
|
-
wanted
|
|
7392
|
+
pending: selected !== null && selected !== active,
|
|
7393
|
+
wanted: selected
|
|
7383
7394
|
};
|
|
7384
7395
|
}
|
|
7385
7396
|
/** The plan projection's wire view over the full log. */
|
|
@@ -7619,6 +7630,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
7619
7630
|
maxImagesPerMessage: 20,
|
|
7620
7631
|
maxMessageImageBytes: 100 * 1024 * 1024,
|
|
7621
7632
|
maxImagePixels: 4e7,
|
|
7633
|
+
maxImageDimension: 2e3,
|
|
7622
7634
|
mediaTypes: [
|
|
7623
7635
|
"image/png",
|
|
7624
7636
|
"image/jpeg",
|
|
@@ -7985,6 +7997,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
7985
7997
|
let attachedSessions = options.empty ? 0 : 1;
|
|
7986
7998
|
const wid = (raw) => raw;
|
|
7987
7999
|
const fixtureEpoch = (/* @__PURE__ */ new Date(Date.now() - 3e5)).toISOString();
|
|
8000
|
+
const FIXTURE_HOME = "/home/fixture";
|
|
7988
8001
|
const workspaces = options.empty ? [] : [{
|
|
7989
8002
|
workspaceId: wid("fx-ws-fixture"),
|
|
7990
8003
|
path: "/tmp/fixture",
|
|
@@ -7996,10 +8009,16 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
7996
8009
|
],
|
|
7997
8010
|
createdAt: fixtureEpoch,
|
|
7998
8011
|
updatedAt: fixtureEpoch
|
|
8012
|
+
}, {
|
|
8013
|
+
workspaceId: wid("fx-ws-home"),
|
|
8014
|
+
path: `${FIXTURE_HOME}/Documents/project`,
|
|
8015
|
+
title: "project",
|
|
8016
|
+
sessionIds: [],
|
|
8017
|
+
createdAt: fixtureEpoch,
|
|
8018
|
+
updatedAt: fixtureEpoch
|
|
7999
8019
|
}];
|
|
8000
8020
|
let nextWorkspace = 1;
|
|
8001
8021
|
const archivedSessionIds = [];
|
|
8002
|
-
const FIXTURE_HOME = "/home/fixture";
|
|
8003
8022
|
const directoryTree = new Map([
|
|
8004
8023
|
["/", ["home"]],
|
|
8005
8024
|
["/home", ["fixture"]],
|
|
@@ -8227,7 +8246,10 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
8227
8246
|
{
|
|
8228
8247
|
name: "goal",
|
|
8229
8248
|
description: "set or view the goal for a long-running task",
|
|
8230
|
-
input: {
|
|
8249
|
+
input: {
|
|
8250
|
+
hint: "<objective>",
|
|
8251
|
+
images: true
|
|
8252
|
+
}
|
|
8231
8253
|
},
|
|
8232
8254
|
{
|
|
8233
8255
|
name: "permission",
|
|
@@ -8237,17 +8259,59 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
8237
8259
|
{
|
|
8238
8260
|
name: "plan",
|
|
8239
8261
|
description: "Enter or leave plan mode",
|
|
8240
|
-
input: {
|
|
8262
|
+
input: {
|
|
8263
|
+
hint: "[off|message]",
|
|
8264
|
+
images: true
|
|
8265
|
+
}
|
|
8241
8266
|
}
|
|
8242
8267
|
]
|
|
8243
8268
|
};
|
|
8244
8269
|
},
|
|
8245
|
-
execute(id, line) {
|
|
8270
|
+
execute(id, line, images = []) {
|
|
8246
8271
|
const missing = requireGoalSession(id);
|
|
8247
8272
|
if (missing !== void 0) return missing;
|
|
8248
8273
|
const match = /^\/(\S+)((?:\s.*)?)$/.exec(line.trim());
|
|
8249
8274
|
const name = match?.[1];
|
|
8250
8275
|
const args = match?.[2] ?? "";
|
|
8276
|
+
if (images.length > 0 && name !== void 0 && [
|
|
8277
|
+
"permission",
|
|
8278
|
+
"goal",
|
|
8279
|
+
"compact",
|
|
8280
|
+
"echo",
|
|
8281
|
+
"plan"
|
|
8282
|
+
].includes(name)) {
|
|
8283
|
+
const rejection = name !== "goal" && name !== "plan" ? `/${name} does not accept image attachments` : name === "goal" && args.trim() === "" ? "Image attachments only accompany a goal objective: /goal <objective> or /goal edit <objective>." : name === "plan" && args.trim() === "off" ? "Image attachments cannot accompany /plan off." : void 0;
|
|
8284
|
+
if (rejection !== void 0) {
|
|
8285
|
+
const commandId = `fx-cmd-${logOf(id).length}`;
|
|
8286
|
+
append(id, {
|
|
8287
|
+
type: "command/run",
|
|
8288
|
+
data: {
|
|
8289
|
+
commandId,
|
|
8290
|
+
name,
|
|
8291
|
+
args,
|
|
8292
|
+
source: { kind: "user" }
|
|
8293
|
+
}
|
|
8294
|
+
});
|
|
8295
|
+
const result = {
|
|
8296
|
+
kind: "error",
|
|
8297
|
+
text: rejection
|
|
8298
|
+
};
|
|
8299
|
+
append(id, {
|
|
8300
|
+
type: "command/done",
|
|
8301
|
+
data: {
|
|
8302
|
+
commandId,
|
|
8303
|
+
...result
|
|
8304
|
+
}
|
|
8305
|
+
});
|
|
8306
|
+
return {
|
|
8307
|
+
ok: true,
|
|
8308
|
+
value: {
|
|
8309
|
+
commandId,
|
|
8310
|
+
result
|
|
8311
|
+
}
|
|
8312
|
+
};
|
|
8313
|
+
}
|
|
8314
|
+
}
|
|
8251
8315
|
if (name === "permission") {
|
|
8252
8316
|
const preset = args.trim();
|
|
8253
8317
|
const commandId = `fx-cmd-${logOf(id).length}`;
|
|
@@ -8409,6 +8473,50 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
8409
8473
|
activation: projection.goal.phase === "active" ? "armed" : "disarmed"
|
|
8410
8474
|
});
|
|
8411
8475
|
/** Canonical fixture implementation of the generated Goal Remote contract. */
|
|
8476
|
+
/** Canonical fixture implementation of the generated reference-discovery Remote contracts. */
|
|
8477
|
+
const referenceRemotes = {
|
|
8478
|
+
files(id, query) {
|
|
8479
|
+
const missing = requireGoalSession(id);
|
|
8480
|
+
if (missing !== void 0) return missing;
|
|
8481
|
+
const needle = query.toLocaleLowerCase();
|
|
8482
|
+
return {
|
|
8483
|
+
ok: true,
|
|
8484
|
+
value: [
|
|
8485
|
+
{
|
|
8486
|
+
path: "notes",
|
|
8487
|
+
kind: "directory"
|
|
8488
|
+
},
|
|
8489
|
+
{
|
|
8490
|
+
path: "README.md",
|
|
8491
|
+
kind: "file"
|
|
8492
|
+
},
|
|
8493
|
+
{
|
|
8494
|
+
path: "notes/demo.txt",
|
|
8495
|
+
kind: "file"
|
|
8496
|
+
}
|
|
8497
|
+
].filter((item) => item.path.toLocaleLowerCase().includes(needle))
|
|
8498
|
+
};
|
|
8499
|
+
},
|
|
8500
|
+
sessions(id, query) {
|
|
8501
|
+
const missing = requireGoalSession(id);
|
|
8502
|
+
if (missing !== void 0) return missing;
|
|
8503
|
+
const needle = query.toLocaleLowerCase();
|
|
8504
|
+
return {
|
|
8505
|
+
ok: true,
|
|
8506
|
+
value: sessions.filter((item) => item.sessionId !== id).filter((item) => String(item.sessionId).toLocaleLowerCase().includes(needle) || item.cwd?.toLocaleLowerCase().includes(needle) === true).map((item) => {
|
|
8507
|
+
const label = item.sessionId === sid("fx-beta") ? "Fixture child session" : String(item.sessionId);
|
|
8508
|
+
const encoded = btoa(JSON.stringify(item.sessionId)).replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/u, "");
|
|
8509
|
+
return {
|
|
8510
|
+
sessionId: item.sessionId,
|
|
8511
|
+
label,
|
|
8512
|
+
...item.cwd === void 0 ? {} : { cwd: item.cwd },
|
|
8513
|
+
createdAt: item.updatedAt,
|
|
8514
|
+
mention: `@[${label}](dsh-session:${encoded})`
|
|
8515
|
+
};
|
|
8516
|
+
})
|
|
8517
|
+
};
|
|
8518
|
+
}
|
|
8519
|
+
};
|
|
8412
8520
|
const goalRemotes = {
|
|
8413
8521
|
create(id, request) {
|
|
8414
8522
|
const missing = requireGoalSession(id);
|
|
@@ -9204,11 +9312,18 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
9204
9312
|
message: `no session ${id}`,
|
|
9205
9313
|
details: { sessionId: id }
|
|
9206
9314
|
});
|
|
9207
|
-
if (options.rejectPrompt)
|
|
9208
|
-
|
|
9209
|
-
|
|
9210
|
-
|
|
9211
|
-
|
|
9315
|
+
if (options.rejectPrompt) {
|
|
9316
|
+
if (content.some((block) => block.type === "image")) return err(request, {
|
|
9317
|
+
code: "attachment-error",
|
|
9318
|
+
message: "fixture: image side exceeds the deployment limit",
|
|
9319
|
+
details: { reason: "IMAGE_DIMENSION_TOO_LARGE" }
|
|
9320
|
+
});
|
|
9321
|
+
return err(request, {
|
|
9322
|
+
code: "agent-busy",
|
|
9323
|
+
message: "fixture: prompt rejected before acceptance",
|
|
9324
|
+
details: { reason: "fixture-prompt-rejection" }
|
|
9325
|
+
});
|
|
9326
|
+
}
|
|
9212
9327
|
summary.updatedAt = Date.now();
|
|
9213
9328
|
summary.blank = false;
|
|
9214
9329
|
const userText = content.map((b) => b.type === "text" ? b.text : "").join("");
|
|
@@ -9319,6 +9434,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
9319
9434
|
version: "0.0.0-fixture",
|
|
9320
9435
|
cwd: "/tmp/fixture",
|
|
9321
9436
|
attachedSessions,
|
|
9437
|
+
home: FIXTURE_HOME,
|
|
9322
9438
|
canOpenPath: true
|
|
9323
9439
|
}),
|
|
9324
9440
|
pickDirectory: (request) => ok(request, { path: `${FIXTURE_HOME}/Documents/project` }),
|
|
@@ -9831,7 +9947,9 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
9831
9947
|
const sessionId = args.agentId;
|
|
9832
9948
|
switch (endpoint) {
|
|
9833
9949
|
case "commands/list": return Promise.resolve(commandRemotes.list(sessionId));
|
|
9834
|
-
case "commands/execute": return Promise.resolve(commandRemotes.execute(sessionId, args.line));
|
|
9950
|
+
case "commands/execute": return Promise.resolve(commandRemotes.execute(sessionId, args.line, args.images ?? []));
|
|
9951
|
+
case "fileReferences/list": return Promise.resolve(referenceRemotes.files(sessionId, args.query ?? ""));
|
|
9952
|
+
case "sessionReferenceResolver/candidates": return Promise.resolve(referenceRemotes.sessions(sessionId, args.query ?? ""));
|
|
9835
9953
|
case "goals/create": return Promise.resolve(goalRemotes.create(sessionId, {
|
|
9836
9954
|
objective: args.request?.objective,
|
|
9837
9955
|
...args.request?.maxGoalRounds === void 0 ? {} : { maxGoalRounds: args.request.maxGoalRounds }
|
|
@@ -10083,9 +10201,11 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
10083
10201
|
const ENDPOINT_SEGMENT_PATTERN = /^[A-Za-z0-9_$.-]+$/;
|
|
10084
10202
|
/**
|
|
10085
10203
|
* Create the browser-backed generic RPC caller.
|
|
10204
|
+
* @param doFetch - transport override; defaults to the page's global fetch.
|
|
10086
10205
|
* @returns caller that owns request correlation and response-envelope validation.
|
|
10087
10206
|
*/
|
|
10088
|
-
function createWebConnectionRpc() {
|
|
10207
|
+
function createWebConnectionRpc(doFetch) {
|
|
10208
|
+
const send = doFetch ?? ((input, init) => globalThis.fetch(input, init));
|
|
10089
10209
|
return { async call(channel, endpoint, payload, signal) {
|
|
10090
10210
|
assertTarget(channel, endpoint);
|
|
10091
10211
|
const rpcId = RpcId(randomUuid());
|
|
@@ -10095,7 +10215,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
10095
10215
|
method: endpoint,
|
|
10096
10216
|
payload
|
|
10097
10217
|
};
|
|
10098
|
-
const response = await
|
|
10218
|
+
const response = await send(new URL(`${channel}/${endpoint}`, resolveBase()), {
|
|
10099
10219
|
method: "POST",
|
|
10100
10220
|
headers: { "content-type": "application/json" },
|
|
10101
10221
|
body: JSON.stringify(message),
|
|
@@ -10143,8 +10263,9 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
10143
10263
|
function apply(ctx) {
|
|
10144
10264
|
const pageLocation = typeof location === "undefined" ? void 0 : location;
|
|
10145
10265
|
const fixtureClient = pageLocation !== void 0 && new URLSearchParams(pageLocation.search).has("fixture") ? new FixtureApiClient() : void 0;
|
|
10146
|
-
const
|
|
10147
|
-
const
|
|
10266
|
+
const transport = globalThis.__DSH_TRANSPORT__;
|
|
10267
|
+
const api = fixtureClient ?? transport?.createApiClient() ?? new WebApiClient();
|
|
10268
|
+
const rpc = fixtureClient?.rpc ?? createWebConnectionRpc(transport?.fetch);
|
|
10148
10269
|
let started = false;
|
|
10149
10270
|
let description;
|
|
10150
10271
|
const descriptionListeners = /* @__PURE__ */ new Set();
|
|
@@ -6,11 +6,13 @@
|
|
|
6
6
|
import type { Context } from '@deepseek-ai/cordis';
|
|
7
7
|
import type { HostDescription, IApiClient } from './api.ts';
|
|
8
8
|
import { type ConnectionConfig, type ConnectionSinks, type ConnectionState } from './connection.ts';
|
|
9
|
+
import { type RpcFetch } from './rpc.ts';
|
|
9
10
|
import type { ClientConnectionRpc } from '../rpc.ts';
|
|
10
11
|
export type { ApiProxy, SessionsApi, SessionSearchItem, SessionSummary, PromptContentPart, HostApi, EventsApi, MuxFrame, HostFrame, ApprovalResponsePayload, QuestionResponsePayload, HistoryEntry, ToolEventView, DirectoryEntry, DirectoryListing, ToolCallView, ToolResultView, WorkspaceApi, WorkspaceId, WorkspaceView, SkillsApi, SkillEntry, ModelCatalogFailure, ModelCatalogModel, ModelProviderGroup, ModelReasoning, MessageId, ModelReasoningEffort, ModelSelection, QueueAction, QueuedInboxItem, SessionModels, SubagentsApi, SubagentAddress, SubagentCatalog, SubagentListEntry, SubagentPromptReceipt, JobView, RpcRequest, RpcResponse, RpcResult, RpcError, RpcErrorCode, ClientRequest, ServerResponse, ServerRequest, ClientResponse, RpcMessage, RpcReceipt, HostDescription, IApiClient, SessionId, SessionEvent, ContentBlock, StreamChunk, GoalsApi, GoalRef, SettingsApi, SettingsNamespaceView, SettingsPathOpView, SettingsSecretView, CredentialsApi, CredentialView, ConfigurableProviderView, DiscoveredModelView, LlmApi, } from './api.ts';
|
|
11
12
|
export { RpcId, AbstractApiClient, transportError, } from './api.ts';
|
|
12
13
|
export type { ConnectionConfig, ConnectionSinks, ConnectionState };
|
|
13
14
|
export type { ClientConnectionRpc } from '../rpc.ts';
|
|
15
|
+
export type { RpcFetch } from './rpc.ts';
|
|
14
16
|
/** Observable Host description published by each completed connection handshake. */
|
|
15
17
|
export interface HostDescriptionSource {
|
|
16
18
|
/** Latest connected-generation description; absent before connect and while reconnecting. */
|
|
@@ -20,6 +22,24 @@ export interface HostDescriptionSource {
|
|
|
20
22
|
}
|
|
21
23
|
/** Required services (none — this is the wire root). */
|
|
22
24
|
export declare const inject: string[];
|
|
25
|
+
/**
|
|
26
|
+
* Carrier override installed on the page global before plugin boot. The served
|
|
27
|
+
* web app leaves it unset and gets HTTP + WebSocket; a shell that owns a
|
|
28
|
+
* different physical transport (the worker preview's postMessage tunnel)
|
|
29
|
+
* provides both halves here instead of forking this plugin.
|
|
30
|
+
*/
|
|
31
|
+
export interface ClientTransportHooks {
|
|
32
|
+
/** Build the API carrier: unary calls plus the two downstream event streams. */
|
|
33
|
+
createApiClient(): IApiClient;
|
|
34
|
+
/** Transport for generic unary RPC channels (the Typert gateway). */
|
|
35
|
+
fetch: RpcFetch;
|
|
36
|
+
/**
|
|
37
|
+
* Bundle transport for the module system, present when the carrier also owns
|
|
38
|
+
* bundle bytes (the worker tunnel). Absent in the served web app, whose
|
|
39
|
+
* bundles load over HTTP.
|
|
40
|
+
*/
|
|
41
|
+
loadBundle?(url: string): Promise<void>;
|
|
42
|
+
}
|
|
23
43
|
/**
|
|
24
44
|
* The ctx.connection service API: the API client plus a one-shot
|
|
25
45
|
* controller starter (the runtime plugin supplies sinks when its object layer
|
|
@@ -30,7 +50,7 @@ export interface ConnectionHandle {
|
|
|
30
50
|
readonly api: IApiClient;
|
|
31
51
|
/** Whether the current page authority is loopback; non-browser contexts default to true. */
|
|
32
52
|
readonly isLoopback: boolean;
|
|
33
|
-
/** Generation-scoped Host facts, including native path-open capability. */
|
|
53
|
+
/** Generation-scoped Host facts, including the account home and native path-open capability. */
|
|
34
54
|
readonly hostDescription: HostDescriptionSource;
|
|
35
55
|
/** Generic logical RPC channels over the same Connection transport. */
|
|
36
56
|
readonly rpc: ClientConnectionRpc;
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
/** Browser caller for generic Connection unary RPC channels. */
|
|
2
2
|
import type { ClientConnectionRpc } from '../rpc.ts';
|
|
3
|
+
/** Transport this caller posts through; same signature as the global `fetch`. */
|
|
4
|
+
export type RpcFetch = (input: URL, init: RequestInit) => Promise<Response>;
|
|
3
5
|
/**
|
|
4
6
|
* Create the browser-backed generic RPC caller.
|
|
7
|
+
* @param doFetch - transport override; defaults to the page's global fetch.
|
|
5
8
|
* @returns caller that owns request correlation and response-envelope validation.
|
|
6
9
|
*/
|
|
7
|
-
export declare function createWebConnectionRpc(): ClientConnectionRpc;
|
|
10
|
+
export declare function createWebConnectionRpc(doFetch?: RpcFetch): ClientConnectionRpc;
|
|
8
11
|
//# sourceMappingURL=rpc.d.ts.map
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deepseek-ai/dsh-client-connection",
|
|
3
3
|
"description": "Wire consumer layer: HTTP-up/WebSocket-down client, ConnectionController dual streams with reconnect, and fixture api",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.1-rc.1",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -39,13 +39,7 @@
|
|
|
39
39
|
"license": "MIT",
|
|
40
40
|
"dependencies": {
|
|
41
41
|
"ws": "^8.21.0",
|
|
42
|
-
"@deepseek-ai/
|
|
43
|
-
"@deepseek-ai/dsh-host-apiproxy": "^0.1.0-rc.7",
|
|
44
|
-
"@deepseek-ai/dsh-commands": "^0.1.0-rc.7",
|
|
45
|
-
"@deepseek-ai/dsh-session": "^0.1.0-rc.7",
|
|
46
|
-
"@deepseek-ai/dsh-tools": "^0.1.0-rc.7",
|
|
47
|
-
"@deepseek-ai/schemastery": "^3.18.1",
|
|
48
|
-
"@deepseek-ai/dsh-llm": "^0.1.0-rc.7"
|
|
42
|
+
"@deepseek-ai/schemastery": "^3.18.1"
|
|
49
43
|
},
|
|
50
44
|
"files": [
|
|
51
45
|
"lib/index.js",
|
|
@@ -54,14 +48,26 @@
|
|
|
54
48
|
"lib/types/**/*.d.ts"
|
|
55
49
|
],
|
|
56
50
|
"peerDependencies": {
|
|
57
|
-
"@deepseek-ai/dsh-
|
|
58
|
-
"@deepseek-ai/dsh-
|
|
59
|
-
"@deepseek-ai/cordis": "^4.0.1"
|
|
51
|
+
"@deepseek-ai/dsh-host-webserver": "^0.1.1-rc.1",
|
|
52
|
+
"@deepseek-ai/dsh-invariants": "^0.1.1-rc.1",
|
|
53
|
+
"@deepseek-ai/cordis": "^4.0.1",
|
|
54
|
+
"@deepseek-ai/dsh-attachment": "^0.1.1-rc.1",
|
|
55
|
+
"@deepseek-ai/dsh-host-apiproxy": "^0.1.1-rc.1",
|
|
56
|
+
"@deepseek-ai/dsh-commands": "^0.1.1-rc.1",
|
|
57
|
+
"@deepseek-ai/dsh-llm": "^0.1.1-rc.1",
|
|
58
|
+
"@deepseek-ai/dsh-tools": "^0.1.1-rc.1",
|
|
59
|
+
"@deepseek-ai/dsh-session": "^0.1.1-rc.1"
|
|
60
60
|
},
|
|
61
61
|
"devDependencies": {
|
|
62
62
|
"@types/ws": "^8.18.1",
|
|
63
|
-
"@deepseek-ai/dsh-
|
|
63
|
+
"@deepseek-ai/dsh-host-webserver": "^0.1.1-rc.1",
|
|
64
|
+
"@deepseek-ai/dsh-invariants": "^0.1.1-rc.1",
|
|
64
65
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
65
|
-
"@deepseek-ai/dsh-
|
|
66
|
+
"@deepseek-ai/dsh-attachment": "^0.1.1-rc.1",
|
|
67
|
+
"@deepseek-ai/dsh-host-apiproxy": "^0.1.1-rc.1",
|
|
68
|
+
"@deepseek-ai/dsh-llm": "^0.1.1-rc.1",
|
|
69
|
+
"@deepseek-ai/dsh-session": "^0.1.1-rc.1",
|
|
70
|
+
"@deepseek-ai/dsh-tools": "^0.1.1-rc.1",
|
|
71
|
+
"@deepseek-ai/dsh-commands": "^0.1.1-rc.1"
|
|
66
72
|
}
|
|
67
73
|
}
|