@gatherthread/dsh-host 0.1.0-alpha.5 → 0.1.0-alpha.7
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 +20 -5
- package/bundle/manifest.json +1 -1
- package/client/client.js +119 -3
- package/cordis.patch.yml +18 -0
- package/dist/bundle/native-plugin.js +18213 -1064
- package/dist/bundle/plugin.js +16300 -444
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -19,7 +19,7 @@ The package does not install a DSH runtime dependency.
|
|
|
19
19
|
After `@gatherthread/dsh-host` is published, the ordinary three-step path is:
|
|
20
20
|
|
|
21
21
|
```text
|
|
22
|
-
npx @deepseek-ai/dsh@0.1.2-rc.1 plugin --profile web add @gatherthread/dsh-host@0.1.0-alpha.
|
|
22
|
+
npx @deepseek-ai/dsh@0.1.2-rc.1 plugin --profile web add @gatherthread/dsh-host@0.1.0-alpha.7
|
|
23
23
|
npx @deepseek-ai/dsh@0.1.2-rc.1 web
|
|
24
24
|
# In DSH: Settings -> GatherThread / 共序 -> Sign in and pair
|
|
25
25
|
```
|
|
@@ -98,13 +98,20 @@ bounded exponential delay.
|
|
|
98
98
|
|
|
99
99
|
Every eligible Session has its own DSH Session id, HTTP abort scope, connector
|
|
100
100
|
state file, runtime identity, heartbeat, canonical cursor and durable outbox.
|
|
101
|
-
The project gate bounds the whole claim-to-settlement operation.
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
101
|
+
The project gate bounds the whole claim-to-settlement operation. Claims are
|
|
102
|
+
leased and renewed by accepted progress, so a Session that stops producing work
|
|
103
|
+
releases its request for the exact recorded runtime to reclaim; the gate still bounds this
|
|
104
|
+
connector's own concurrency while it holds one. Removing or downgrading a Session
|
|
105
|
+
cancels its queued permit before it can claim a request. Existing single-Session configuration remains
|
|
105
106
|
valid when `bindingMode` is omitted; both modes resolve the real current actor
|
|
106
107
|
before applying Solo permissions.
|
|
107
108
|
|
|
109
|
+
During an active prompt, newly durable DSH Host events queue a rate-limited,
|
|
110
|
+
generic progress marker; event bursts coalesce. The marker carries no event text,
|
|
111
|
+
reasoning, headers, or private stream data, but renews the exact claim attempt. Progress, completion,
|
|
112
|
+
and request-linked tool outbox entries are rebound to the renewed attempt after a
|
|
113
|
+
crash recovery, while stale attempts remain fenced by the server.
|
|
114
|
+
|
|
108
115
|
The installed native plugin stores one account-level model route and reconciles
|
|
109
116
|
every active Project visible to the paired GatherThread identity. Adding a
|
|
110
117
|
Project starts its isolated manager without another pairing step. Archiving a
|
|
@@ -112,6 +119,14 @@ Project or revoking access stops only that Project manager; its local workspace
|
|
|
112
119
|
and DSH Session history are retained. A failure in one Project does not stop
|
|
113
120
|
healthy peers, and discovery retries periodically.
|
|
114
121
|
|
|
122
|
+
For a `deepseek-official` route, the plugin also advertises the exact models and
|
|
123
|
+
reasoning efforts returned by DSH's public LLM catalog. The GatherThread work
|
|
124
|
+
page may select one advertised combination for an individual Agent request.
|
|
125
|
+
That selection is installed only on the borrowed Agent while the request runs
|
|
126
|
+
and is removed in `finally`; it does not rewrite DSH Web's saved model choice.
|
|
127
|
+
Unsupported combinations fail closed. Older plugins and non-DeepSeek routes
|
|
128
|
+
retain the account route's fixed provider and model.
|
|
129
|
+
|
|
115
130
|
An existing schema-v1 single-Project pairing keeps its device credential when
|
|
116
131
|
the plugin is upgraded, but deliberately drops the old model binding. The user
|
|
117
132
|
must choose the DSH model and explicitly select **Connect all accessible
|
package/bundle/manifest.json
CHANGED
package/client/client.js
CHANGED
|
@@ -105,7 +105,9 @@ function GatherThreadStatusPanel({ connection, sessions }) {
|
|
|
105
105
|
}
|
|
106
106
|
onSuccess?.(next);
|
|
107
107
|
} catch {
|
|
108
|
-
setNotice(endpoint.startsWith("
|
|
108
|
+
setNotice(endpoint.startsWith("code/")
|
|
109
|
+
? "代码同步未完成。请检查本机授权、项目权限和云端版本;不会强制覆盖文件。"
|
|
110
|
+
: endpoint.startsWith("sync/")
|
|
109
111
|
? "上传操作未完成。请检查连接和会话权限后重试。"
|
|
110
112
|
: "操作未完成。请检查服务器地址、当前授权和模型配置。");
|
|
111
113
|
} finally {
|
|
@@ -181,9 +183,86 @@ function GatherThreadStatusPanel({ connection, sessions }) {
|
|
|
181
183
|
setProvider,
|
|
182
184
|
setModel,
|
|
183
185
|
runAction,
|
|
186
|
+
}),
|
|
187
|
+
nativeState === undefined || unavailable ? null : renderCodeSyncControls(nativeState, busy, runAction));
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function renderCodeSyncControls(state, busy, runAction) {
|
|
191
|
+
if (!state.codeSync?.length) return null;
|
|
192
|
+
const actions = [
|
|
193
|
+
["code_sync_status", "检查状态"],
|
|
194
|
+
["code_upload", "上传代码"],
|
|
195
|
+
["code_download", "下载更新"],
|
|
196
|
+
["code_recover", "恢复到新目录"],
|
|
197
|
+
];
|
|
198
|
+
return React.createElement("section", { style: styles.connectionCard, "aria-label": "项目代码同步" },
|
|
199
|
+
React.createElement("h3", { style: styles.sectionTitle }, "项目代码 · Git"),
|
|
200
|
+
React.createElement("p", { style: styles.muted },
|
|
201
|
+
"代码与会话上传独立。每位成员使用自己的云端分支;项目成员均可读取代码。上传前请检查源文件,不上传密钥或私人资料。"),
|
|
202
|
+
...state.codeSync.map((project) => {
|
|
203
|
+
const invoke = (action) => void runAction("code/action", { projectId: project.projectId, action });
|
|
204
|
+
const status = project.status;
|
|
205
|
+
return React.createElement("details", { key: project.projectId, style: { ...styles.row, display: "block" } },
|
|
206
|
+
React.createElement("summary", { style: styles.sessionTitle }, project.projectName),
|
|
207
|
+
React.createElement("div", { style: styles.form },
|
|
208
|
+
React.createElement("label", { style: styles.syncToggle },
|
|
209
|
+
React.createElement("input", {
|
|
210
|
+
type: "checkbox", checked: project.authorized, disabled: busy,
|
|
211
|
+
onChange: (event) => void runAction("code/authorize", {
|
|
212
|
+
projectId: project.projectId, enabled: event.target.checked,
|
|
213
|
+
}),
|
|
214
|
+
}), "允许此 DSH 同步该项目代码"),
|
|
215
|
+
React.createElement("p", { style: styles.muted }, "仅访问此项目已经绑定的本地目录,不改动原 Git 分支或暂存区。下载要求本地没有未上传改动;恢复始终新建目录,不更换当前 Agent 工作目录。"),
|
|
216
|
+
status ? React.createElement("p", { style: styles.sessionMeta },
|
|
217
|
+
status.local_status_unknown ? "已恢复云端副本;原工作区状态未知,请单独检查。" : status.enabled
|
|
218
|
+
? `${status.file_count} 个源文件 · ${status.local_changes} 项待上传 · ${status.excluded_count} 项已排除 · 云端 ${status.cloud_commit?.slice(0, 8) ?? "尚无版本"}`
|
|
219
|
+
: "请先由项目创建者在 GatherThread 的「项目代码」中启用 Git。") : null,
|
|
220
|
+
status?.needs_download ? React.createElement("p", { style: styles.notice }, "云端有新版本。请先下载;本地有改动时请恢复到新目录比较,不会自动覆盖。") : null,
|
|
221
|
+
project.authorized ? React.createElement("label", { style: styles.syncToggle },
|
|
222
|
+
React.createElement("input", {
|
|
223
|
+
type: "checkbox", checked: status?.automatic_upload === true,
|
|
224
|
+
disabled: busy || !status?.enabled,
|
|
225
|
+
onChange: (event) => invoke(event.target.checked ? "code_auto_upload_enable" : "code_auto_upload_disable"),
|
|
226
|
+
}), "空闲时自动上传本地代码至云端") : null,
|
|
227
|
+
React.createElement("div", { style: styles.syncActions }, ...actions.map(([action, label]) => (
|
|
228
|
+
React.createElement("button", {
|
|
229
|
+
key: action, type: "button", style: styles.compactButton,
|
|
230
|
+
disabled: busy || !project.authorized || (action !== "code_sync_status" && !status?.enabled),
|
|
231
|
+
onClick: () => {
|
|
232
|
+
if (action === "code_recover" && !window.confirm("将云端代码恢复到当前项目旁的新目录,原目录和会话保持不变。继续?")) return;
|
|
233
|
+
invoke(action);
|
|
234
|
+
},
|
|
235
|
+
}, label)
|
|
236
|
+
))),
|
|
237
|
+
status?.recovery_directory ? React.createElement("p", { role: "status", style: styles.notice },
|
|
238
|
+
`已恢复至项目同级目录:${status.recovery_directory}。在本地 Agent 中打开该目录继续工作;原会话保持不变。`) : null,
|
|
239
|
+
project.error ? React.createElement("p", { role: "status", style: styles.notice }, codeSyncErrorLabel(project.error)) : null,
|
|
240
|
+
));
|
|
184
241
|
}));
|
|
185
242
|
}
|
|
186
243
|
|
|
244
|
+
function codeSyncErrorLabel(code) {
|
|
245
|
+
const labels = {
|
|
246
|
+
code_sync_disabled: "请先允许此 DSH 同步项目代码。",
|
|
247
|
+
code_sync_busy: "Agent 正在工作,请等待本轮结束后再同步。",
|
|
248
|
+
code_workspace_busy: "Agent 正在工作,请等待本轮结束后再同步。",
|
|
249
|
+
code_sync_dirty: "本地有未上传修改。先上传,或恢复云端版本到新目录比较。",
|
|
250
|
+
code_sync_recovery_required: "本地目录缺失或上次下载中断,请先将云端副本恢复到新目录。",
|
|
251
|
+
code_sync_forbidden: "代码访问被拒绝,请检查该设备的登录状态与项目权限。",
|
|
252
|
+
code_sync_conflict: "云端版本已变化。请先安全下载或恢复到新目录比较,未覆盖任何云端版本。",
|
|
253
|
+
code_conflict: "云端版本冲突。请检查最新分支,保留本地修改后重试。",
|
|
254
|
+
code_stale_head: "云端已有新版本。请恢复到新目录比较,不要覆盖未上传的本地修改。",
|
|
255
|
+
code_merge_conflict: "分支存在冲突。请保留本地修改,比较云端版本并处理冲突。",
|
|
256
|
+
code_storage_quota_exceeded: "项目代码存储额度已满,请联系服务管理员。",
|
|
257
|
+
code_storage_check_required: "代码写入因存储检查暂时停止,请联系服务管理员;会话同步仍可使用。",
|
|
258
|
+
code_git_unavailable: "Git 不可用,请检查服务器与本机的 Git 安装。",
|
|
259
|
+
code_secret_detected: "检测到可能的密钥,已阻止上传。请检查项目文件。",
|
|
260
|
+
code_not_enabled: "请先由项目创建者在 GatherThread 启用项目 Git。",
|
|
261
|
+
code_sync_secret: "检测到可能的密钥,已阻止上传。请检查项目文件。",
|
|
262
|
+
};
|
|
263
|
+
return labels[code] ?? "代码同步未完成。请检查授权、文件和云端状态后重试;会话同步不受影响。";
|
|
264
|
+
}
|
|
265
|
+
|
|
187
266
|
async function readHostState(connection, signal) {
|
|
188
267
|
if (typeof connection?.rpc?.call === "function") {
|
|
189
268
|
try {
|
|
@@ -302,6 +381,13 @@ function renderNativeControls(input) {
|
|
|
302
381
|
const models = selectedProvider?.models ?? [];
|
|
303
382
|
controls.push(
|
|
304
383
|
connectionSummary(state),
|
|
384
|
+
// Reaching this branch means authorization is already "paired": the
|
|
385
|
+
// "unpaired" and "pairing" states are handled by earlier branches. The
|
|
386
|
+
// browser approval succeeded, but without a provider and model no runtime
|
|
387
|
+
// is registered, so a bare stopped connection is indistinguishable from a
|
|
388
|
+
// fresh install. State the remaining step explicitly.
|
|
389
|
+
React.createElement("p", { key: "select-model-notice", style: styles.notice },
|
|
390
|
+
"配对完成后,尚未选择 DSH Provider 与 Model。完成选择并点击下方按钮后,本机 DSH 运行时才会注册到 GatherThread;在此之前,GatherThread 网页无法发现这台 DSH。"),
|
|
305
391
|
input.catalog === undefined
|
|
306
392
|
? React.createElement("button", {
|
|
307
393
|
key: "load-catalog",
|
|
@@ -413,7 +499,7 @@ function detail(label, value) {
|
|
|
413
499
|
function parseNativeState(value) {
|
|
414
500
|
exactObject(value, [
|
|
415
501
|
"schemaVersion", "integration", "authorization", "compatibility", "runtime",
|
|
416
|
-
"officialServerUrl", "serverUrl", "deviceName", "route", "projectCount", "bindings", "localSync", "pairing", "recoverableError",
|
|
502
|
+
"officialServerUrl", "serverUrl", "deviceName", "route", "projectCount", "bindings", "localSync", "codeSync", "pairing", "recoverableError",
|
|
417
503
|
]);
|
|
418
504
|
if (value.schemaVersion !== 2 || value.integration !== "gatherthread") throw new Error("invalid native status identity");
|
|
419
505
|
if (!AUTHORIZATION_STATES.has(value.authorization)) throw new Error("invalid native authorization state");
|
|
@@ -437,6 +523,7 @@ function parseNativeState(value) {
|
|
|
437
523
|
let route;
|
|
438
524
|
let bindings;
|
|
439
525
|
let localSync;
|
|
526
|
+
let codeSync;
|
|
440
527
|
if (value.route !== undefined || value.bindings !== undefined || value.projectCount !== undefined) {
|
|
441
528
|
if (value.route === undefined || !Number.isSafeInteger(value.projectCount) || value.projectCount < 0
|
|
442
529
|
|| !Array.isArray(value.bindings) || value.bindings.length > 100
|
|
@@ -477,6 +564,35 @@ function parseNativeState(value) {
|
|
|
477
564
|
}
|
|
478
565
|
return { ...sync };
|
|
479
566
|
});
|
|
567
|
+
const codeSyncValue = value.codeSync ?? [];
|
|
568
|
+
if (!Array.isArray(codeSyncValue) || codeSyncValue.length > 100) throw new Error("invalid code sync status");
|
|
569
|
+
codeSync = codeSyncValue.map((project) => {
|
|
570
|
+
exactObject(project, ["projectId", "projectName", "authorized", "status", "error"]);
|
|
571
|
+
boundedText(project.projectId, 128);
|
|
572
|
+
boundedText(project.projectName, 160);
|
|
573
|
+
if (typeof project.authorized !== "boolean") throw new Error("invalid code sync authorization");
|
|
574
|
+
if (project.error !== undefined && !/^code_[a-z_]{1,60}$/u.test(project.error)) throw new Error("invalid code sync error");
|
|
575
|
+
if (project.status !== undefined) {
|
|
576
|
+
const status = project.status;
|
|
577
|
+
exactObject(status, ["enabled", "automatic_upload", "local_changes", "file_count", "excluded_count", "base_commit", "cloud_commit", "branch_id", "needs_download", "recovery_directory", "local_status_unknown"]);
|
|
578
|
+
if (status.local_status_unknown !== undefined && typeof status.local_status_unknown !== "boolean") throw new Error("invalid local status flag");
|
|
579
|
+
for (const key of ["enabled", "automatic_upload", "needs_download"]) {
|
|
580
|
+
if (typeof status[key] !== "boolean") throw new Error("invalid code sync flag");
|
|
581
|
+
}
|
|
582
|
+
for (const key of ["local_changes", "file_count", "excluded_count"]) {
|
|
583
|
+
if (!Number.isSafeInteger(status[key]) || status[key] < 0) throw new Error("invalid code sync count");
|
|
584
|
+
}
|
|
585
|
+
for (const key of ["base_commit", "cloud_commit"]) {
|
|
586
|
+
if (status[key] !== null && !/^[a-f0-9]{40}$/u.test(status[key])) throw new Error("invalid code commit");
|
|
587
|
+
}
|
|
588
|
+
if (status.branch_id !== null) boundedText(status.branch_id, 128);
|
|
589
|
+
if (status.recovery_directory !== undefined) {
|
|
590
|
+
boundedText(status.recovery_directory, 255);
|
|
591
|
+
if (/[\\/\u0000-\u001f]/u.test(status.recovery_directory)) throw new Error("invalid recovery directory");
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
return { ...project };
|
|
595
|
+
});
|
|
480
596
|
}
|
|
481
597
|
let pairing;
|
|
482
598
|
if (value.pairing !== undefined) {
|
|
@@ -512,7 +628,7 @@ function parseNativeState(value) {
|
|
|
512
628
|
...(officialServerUrl === undefined ? {} : { officialServerUrl }),
|
|
513
629
|
...(serverUrl === undefined ? {} : { serverUrl }),
|
|
514
630
|
...(value.deviceName === undefined ? {} : { deviceName: value.deviceName }),
|
|
515
|
-
...(route === undefined ? {} : { route, projectCount: value.projectCount, bindings, localSync }),
|
|
631
|
+
...(route === undefined ? {} : { route, projectCount: value.projectCount, bindings, localSync, codeSync }),
|
|
516
632
|
...(pairing === undefined ? {} : { pairing }),
|
|
517
633
|
...(value.recoverableError === undefined ? {} : { recoverableError: value.recoverableError }),
|
|
518
634
|
};
|
package/cordis.patch.yml
CHANGED
|
@@ -12,3 +12,21 @@
|
|
|
12
12
|
inject: [agents, sessions, sessionPersistence, sessionQuery, sessionTitle, workspaceRegistry, llm, credentials, connection]
|
|
13
13
|
config:
|
|
14
14
|
enabled: true
|
|
15
|
+
|
|
16
|
+
# DSH 0.1.5-rc.x dropped `webServer` from the Connection plugin's own inject list
|
|
17
|
+
# (`["webServer", "credentials"]` became `["credentials"]`), moving it into a
|
|
18
|
+
# nested scope that covers only DSH's new index-injection feature. Connection
|
|
19
|
+
# still reads `owner.webServer` from its *providing* context -- cordis starts
|
|
20
|
+
# every service lookup at the shadow origin, `(ctx[symbols.shadow] ?? ctx).fiber`
|
|
21
|
+
# -- so `connection.rpc.handle()` raises "cannot get property \"webServer\"
|
|
22
|
+
# without inject" for every caller, and the whole plugin tree fails to load.
|
|
23
|
+
# 0.1.2-rc.1 kept the dependency at the top level, so restoring it here is what
|
|
24
|
+
# makes the Host plugin load on both versions.
|
|
25
|
+
#
|
|
26
|
+
# A row patch replaces `inject` wholesale rather than merging, so each entry the
|
|
27
|
+
# 0.1.5 row already declares is repeated: `webRuntime` still feeds this row's
|
|
28
|
+
# `trustedHosts: !!js ctx.webRuntime.trustedHosts` expression, and `credentials`
|
|
29
|
+
# is repeated so the row stays self-sufficient no matter how the Loader folds a
|
|
30
|
+
# row's list together with the module's own `inject` export.
|
|
31
|
+
- id: connection
|
|
32
|
+
inject: [webRuntime, credentials, webServer]
|