@gatherthread/dsh-host 0.1.0-beta.1 → 0.1.0-beta.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 +26 -20
- package/bundle/manifest.json +5 -5
- package/client/client.js +45 -9
- package/dist/bundle/native-plugin.js +1344 -143
- package/dist/bundle/plugin.js +1275 -121
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -7,38 +7,40 @@ installation, GatherThread does not load in DSH or alter the GatherThread
|
|
|
7
7
|
server, Web app, Codex bridge, or Claude adapter.
|
|
8
8
|
|
|
9
9
|
The normal path is the published `@deepseek-ai/dsh` CLI, not a DSH source
|
|
10
|
-
checkout. The verified distribution is `@deepseek-ai/dsh@0.1.
|
|
11
|
-
`web` profile and its public `sessionPersistence
|
|
12
|
-
|
|
13
|
-
`
|
|
10
|
+
checkout. The verified distribution is `@deepseek-ai/dsh@0.1.7-rc.2` with the
|
|
11
|
+
`web` profile and its public `sessionPersistence` recovery surface. The
|
|
12
|
+
source-only path remains fixed to `dsh-v0.1.7-rc.2`, commit
|
|
13
|
+
`477b4f420553e8a52c2fbccc464d7561b239c443`, and is only an advanced diagnostic
|
|
14
14
|
fallback. All version-sensitive Host calls remain inside `src/dsh-compat.ts`.
|
|
15
15
|
The package does not install a DSH runtime dependency.
|
|
16
16
|
|
|
17
17
|
## Plugin-first connection
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
Use **Connect DeepSeek Harness → Download packages for this server** in GatherThread to download its source-matched DSH plugin TGZ. Replace the example path below with the downloaded file's full path:
|
|
20
20
|
|
|
21
21
|
```text
|
|
22
|
-
npx @deepseek-ai/dsh@0.1.
|
|
23
|
-
npx @deepseek-ai/dsh@0.1.
|
|
22
|
+
npx @deepseek-ai/dsh@0.1.7-rc.2 plugin --profile web add "/absolute/path/to/LOCAL_DSH_PLUGIN.tgz"
|
|
23
|
+
npx @deepseek-ai/dsh@0.1.7-rc.2 web
|
|
24
24
|
# In DSH: Settings -> GatherThread / 共序 -> Sign in and pair
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
+
On Windows, use a quoted path such as `C:/Users/you/Downloads/your-plugin.tgz`. The registry's `@gatherthread/dsh-host@0.1.0-beta.1` is an older build and does not include the current message-deletion safeguards. If server downloads are unavailable, [build the matching source checkout](../../docs/DSH_CONNECT.md#source-checkout-fallback); do not substitute the old npm build.
|
|
28
|
+
|
|
27
29
|
Check the active CLI with `npx @deepseek-ai/dsh --version`. The normal start
|
|
28
|
-
command already pins `0.1.
|
|
30
|
+
command already pins `0.1.7-rc.2`. The pinned DSH does not
|
|
29
31
|
expose the root CLI version as a stable Host service, so the package does not
|
|
30
32
|
inspect npx cache paths or claim runtime-version introspection. The install
|
|
31
33
|
command pins the exact verified CLI, while every required Host/Client service is
|
|
32
34
|
validated structurally and fails closed before a request can be claimed.
|
|
33
35
|
|
|
34
|
-
`dsh plugin --profile web add` is DSH
|
|
36
|
+
`dsh plugin --profile web add` is the pinned DSH release's official package installation
|
|
35
37
|
mechanism. It initializes a missing `web` profile on first use, invokes pnpm,
|
|
36
|
-
and appends only packages declaring `dsh.bundle`. DSH
|
|
38
|
+
and appends only packages declaring `dsh.bundle`. The pinned DSH has no verified
|
|
37
39
|
public plugin marketplace, marketplace index, or registration workflow. This
|
|
38
40
|
repository change prepares the package but does not publish it or modify a real
|
|
39
41
|
DSH profile.
|
|
40
42
|
|
|
41
|
-
Inside DSH, choose the
|
|
43
|
+
Inside DSH, choose the GatherThread public service or enter one custom
|
|
42
44
|
server origin. The same field covers an HTTPS LAN host, self-hosted deployment,
|
|
43
45
|
or Tailscale address; plain HTTP is accepted only on loopback. The plugin opens
|
|
44
46
|
an outbound HTTPS connection. A five-minute, single-use short code is confirmed
|
|
@@ -52,7 +54,7 @@ Before removal, use **Disconnect** inside the DSH panel to clear the local
|
|
|
52
54
|
grant, then run:
|
|
53
55
|
|
|
54
56
|
```text
|
|
55
|
-
npx @deepseek-ai/dsh@0.1.
|
|
57
|
+
npx @deepseek-ai/dsh@0.1.7-rc.2 plugin --profile web remove @gatherthread/dsh-host
|
|
56
58
|
```
|
|
57
59
|
|
|
58
60
|
The official command removes only this package dependency and profile layer;
|
|
@@ -62,7 +64,7 @@ advanced/offline diagnostics, not the normal user path.
|
|
|
62
64
|
|
|
63
65
|
## GitHub code synchronization
|
|
64
66
|
|
|
65
|
-
Beta
|
|
67
|
+
Beta 2 includes direct GitHub controls and requires matching server, Web app and native plugin releases. The project owner first connects an existing repository and base branch in Web. Install Git 2.38+ and GitHub CLI on the device, then open **Settings → GatherThread / 共序 → 项目代码 · GitHub(推荐)**. Click **连接 GitHub · 打开浏览器** to start local browser sign-in if needed, then authorize that exact project/repository configuration. `gh auth login --hostname github.com --git-protocol https --web` remains a local fallback. This consent is separate from GT Cloud and conversation sync; a new GitHub binding automatically uploads settled changes while idle and can be switched off. Configuration changes, including pause/resume, require renewed local authorization.
|
|
66
68
|
|
|
67
69
|
The Web app may request code operations only on your selected online runtime and cannot grant this local consent. Code and GitHub credentials travel directly to GitHub. GitHub permissions remain independent of GT membership; removing a GT member or project does not revoke GitHub access or delete GitHub data. See [Project code collaboration](../../docs/CODE_SYNC.md) for setup, recovery, limits and GitHub workflow-trigger risks.
|
|
68
70
|
|
|
@@ -70,10 +72,13 @@ GitHub 接入仍为**未发布源码预览**,需使用匹配的服务端、网
|
|
|
70
72
|
|
|
71
73
|
## Security and recovery
|
|
72
74
|
|
|
73
|
-
Only allowlisted public text/tool fields leave DSH.
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
75
|
+
Only allowlisted public text/tool fields leave DSH. An exact GatherThread-claimed
|
|
76
|
+
prompt can additionally stream native-exposed thinking and real plans. The live
|
|
77
|
+
listener verifies owned Agent, Session and execution; the npm fallback requires
|
|
78
|
+
DSH's genuine Agent-loop marker rather than a model-name match. Raw request
|
|
79
|
+
bodies, headers, replay state, tool-private metadata and unrelated native streams
|
|
80
|
+
stay outside the mapper. Tool arguments are parsed and redacted before upload;
|
|
81
|
+
incomplete JSON emits only a preparation marker. The connector holds one active request, persists a private atomic
|
|
77
82
|
cursor/outbox, reuses DSH's official Session resume/repair path, and releases its
|
|
78
83
|
listeners, timer, HTTP abort signal, Agent handle, and write ownership on unload.
|
|
79
84
|
|
|
@@ -114,9 +119,10 @@ cancels its queued permit before it can claim a request. Existing single-Session
|
|
|
114
119
|
valid when `bindingMode` is omitted; both modes resolve the real current actor
|
|
115
120
|
before applying Solo permissions.
|
|
116
121
|
|
|
117
|
-
During an active prompt,
|
|
118
|
-
|
|
119
|
-
|
|
122
|
+
During an active claimed prompt, selected native-visible answer, thinking, tool
|
|
123
|
+
and plan updates coalesce into bounded redacted snapshots. Generic activity
|
|
124
|
+
markers carry no native content and remain separate from presence. Accepted
|
|
125
|
+
progress renews the exact claim attempt. Progress, completion,
|
|
120
126
|
and request-linked tool outbox entries are rebound to the renewed attempt after a
|
|
121
127
|
crash recovery, while stale attempts remain fenced by the server.
|
|
122
128
|
|
package/bundle/manifest.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
3
|
"name": "@gatherthread/dsh-host",
|
|
4
|
-
"version": "0.1.0-beta.
|
|
4
|
+
"version": "0.1.0-beta.2",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"defaultEnabled": false,
|
|
7
7
|
"hostEntry": "@gatherthread/dsh-host",
|
|
@@ -15,15 +15,15 @@
|
|
|
15
15
|
"primary": {
|
|
16
16
|
"distribution": "npm",
|
|
17
17
|
"package": "@deepseek-ai/dsh",
|
|
18
|
-
"version": "0.1.
|
|
18
|
+
"version": "0.1.7-rc.2",
|
|
19
19
|
"profile": "web",
|
|
20
20
|
"persistenceProbe": "list"
|
|
21
21
|
},
|
|
22
22
|
"sourceFallback": {
|
|
23
23
|
"distribution": "source",
|
|
24
|
-
"tag": "dsh-v0.1.
|
|
25
|
-
"version": "0.1.
|
|
26
|
-
"commit": "
|
|
24
|
+
"tag": "dsh-v0.1.7-rc.2",
|
|
25
|
+
"version": "0.1.7-rc.2",
|
|
26
|
+
"commit": "477b4f420553e8a52c2fbccc464d7561b239c443",
|
|
27
27
|
"verifiedProfiles": ["headless", "web"],
|
|
28
28
|
"persistenceProbe": "stat"
|
|
29
29
|
}
|
package/client/client.js
CHANGED
|
@@ -35,6 +35,21 @@ function GatherThreadStatusPanel({ connection, sessions }) {
|
|
|
35
35
|
const [provider, setProvider] = React.useState("");
|
|
36
36
|
const [model, setModel] = React.useState("");
|
|
37
37
|
const visibleSessionSignature = React.useRef("");
|
|
38
|
+
/** Recovery observed from both status polls and action responses. */
|
|
39
|
+
const lastRecoverableError = React.useRef(undefined);
|
|
40
|
+
|
|
41
|
+
const observeRouteRecovery = (next, configureResponse = false) => {
|
|
42
|
+
const recovery = next?.recoverableError;
|
|
43
|
+
// A failed configure is a new revoked choice even if the error code has not
|
|
44
|
+
// changed. Steady polls must leave an operator's new selection alone.
|
|
45
|
+
if (recovery === "route_unavailable"
|
|
46
|
+
&& (configureResponse || lastRecoverableError.current !== "route_unavailable")) {
|
|
47
|
+
setCatalog(undefined);
|
|
48
|
+
setProvider("");
|
|
49
|
+
setModel("");
|
|
50
|
+
}
|
|
51
|
+
lastRecoverableError.current = recovery;
|
|
52
|
+
};
|
|
38
53
|
|
|
39
54
|
React.useEffect(() => {
|
|
40
55
|
let disposed = false;
|
|
@@ -60,6 +75,7 @@ function GatherThreadStatusPanel({ connection, sessions }) {
|
|
|
60
75
|
}
|
|
61
76
|
setSnapshot(result.snapshot);
|
|
62
77
|
setNativeState(result.nativeState);
|
|
78
|
+
observeRouteRecovery(result.nativeState);
|
|
63
79
|
setUnavailable(false);
|
|
64
80
|
timer = setTimeout(refresh, POLL_INTERVAL_MS);
|
|
65
81
|
}
|
|
@@ -95,10 +111,12 @@ function GatherThreadStatusPanel({ connection, sessions }) {
|
|
|
95
111
|
const next = endpoint === "catalog/get" ? parseCatalog(value) : parseNativeState(value);
|
|
96
112
|
if (endpoint === "catalog/get") {
|
|
97
113
|
setCatalog(next);
|
|
98
|
-
const
|
|
99
|
-
setProvider(
|
|
100
|
-
setModel((current) =>
|
|
114
|
+
const selectedProvider = next.providers.find((entry) => entry.id === provider) ?? next.providers[0];
|
|
115
|
+
setProvider(selectedProvider?.id ?? "");
|
|
116
|
+
setModel((current) => selectedProvider?.models.some((entry) => entry.id === current)
|
|
117
|
+
? current : selectedProvider?.models[0]?.id ?? "");
|
|
101
118
|
} else {
|
|
119
|
+
observeRouteRecovery(next, endpoint === "connection/configure");
|
|
102
120
|
setNativeState(next);
|
|
103
121
|
setSnapshot(next.runtime);
|
|
104
122
|
if (endpoint === "connection/configure") await sessions.refresh();
|
|
@@ -184,6 +202,7 @@ function GatherThreadStatusPanel({ connection, sessions }) {
|
|
|
184
202
|
setDeviceName,
|
|
185
203
|
setProvider,
|
|
186
204
|
setModel,
|
|
205
|
+
setCatalog,
|
|
187
206
|
runAction,
|
|
188
207
|
}),
|
|
189
208
|
nativeState === undefined || unavailable ? null : renderCodeSyncControls(nativeState, busy, runAction, true),
|
|
@@ -435,8 +454,9 @@ function renderNativeControls(input) {
|
|
|
435
454
|
);
|
|
436
455
|
} else if (state.route === undefined) {
|
|
437
456
|
const providers = input.catalog?.providers ?? [];
|
|
438
|
-
const selectedProvider = providers.find((entry) => entry.id === input.provider)
|
|
457
|
+
const selectedProvider = providers.find((entry) => entry.id === input.provider);
|
|
439
458
|
const models = selectedProvider?.models ?? [];
|
|
459
|
+
const validSelection = models.some((entry) => entry.id === input.model);
|
|
440
460
|
controls.push(
|
|
441
461
|
connectionSummary(state),
|
|
442
462
|
// Reaching this branch means authorization is already "paired": the
|
|
@@ -445,7 +465,12 @@ function renderNativeControls(input) {
|
|
|
445
465
|
// is registered, so a bare stopped connection is indistinguishable from a
|
|
446
466
|
// fresh install. State the remaining step explicitly.
|
|
447
467
|
React.createElement("p", { key: "select-model-notice", style: styles.notice },
|
|
448
|
-
|
|
468
|
+
// A dropped route is not the same situation as a fresh pairing, and saying
|
|
469
|
+
// so is the whole point of not repointing the model silently: the reader has
|
|
470
|
+
// to know their previous choice no longer exists before picking another.
|
|
471
|
+
state.recoverableError === "route_unavailable"
|
|
472
|
+
? "DSH 当前已不再提供此前为该连接选择的模型,因此该连接已停止并取消注册。请重新选择 Provider 与 Model;在此之前的旧模型不会被自动替换。"
|
|
473
|
+
: "配对完成后,尚未选择 DSH Provider 与 Model。完成选择并点击下方按钮后,本机 DSH 运行时才会注册到 GatherThread;在此之前,GatherThread 网页无法发现这台 DSH。"),
|
|
449
474
|
input.catalog === undefined
|
|
450
475
|
? React.createElement("button", {
|
|
451
476
|
key: "load-catalog",
|
|
@@ -459,6 +484,7 @@ function renderNativeControls(input) {
|
|
|
459
484
|
style: styles.form,
|
|
460
485
|
onSubmit: (event) => {
|
|
461
486
|
event.preventDefault();
|
|
487
|
+
if (input.busy || !validSelection) return;
|
|
462
488
|
void input.runAction("connection/configure", {
|
|
463
489
|
provider: input.provider,
|
|
464
490
|
model: input.model,
|
|
@@ -487,7 +513,7 @@ function renderNativeControls(input) {
|
|
|
487
513
|
)),
|
|
488
514
|
React.createElement("button", {
|
|
489
515
|
type: "submit",
|
|
490
|
-
disabled: input.busy || !
|
|
516
|
+
disabled: input.busy || !validSelection,
|
|
491
517
|
style: styles.primaryButton,
|
|
492
518
|
}, input.busy ? "正在连接…" : "连接全部可访问项目")),
|
|
493
519
|
disconnectButton(input),
|
|
@@ -540,11 +566,12 @@ function selectControl(entries, value, setValue, keyOf, labelOf) {
|
|
|
540
566
|
return React.createElement("select", {
|
|
541
567
|
required: true,
|
|
542
568
|
value,
|
|
543
|
-
style: styles.
|
|
569
|
+
style: styles.select,
|
|
544
570
|
onChange: (event) => setValue(event.target.value),
|
|
545
571
|
}, ...entries.map((entry) => React.createElement("option", {
|
|
546
572
|
key: keyOf(entry),
|
|
547
573
|
value: keyOf(entry),
|
|
574
|
+
style: styles.option,
|
|
548
575
|
}, labelOf(entry))));
|
|
549
576
|
}
|
|
550
577
|
|
|
@@ -563,7 +590,7 @@ function parseNativeState(value) {
|
|
|
563
590
|
if (!AUTHORIZATION_STATES.has(value.authorization)) throw new Error("invalid native authorization state");
|
|
564
591
|
exactObject(value.compatibility, ["package", "version", "profile"]);
|
|
565
592
|
if (value.compatibility.package !== "@deepseek-ai/dsh"
|
|
566
|
-
|| value.compatibility.version !== "0.1.
|
|
593
|
+
|| value.compatibility.version !== "0.1.7-rc.2"
|
|
567
594
|
|| value.compatibility.profile !== "web") throw new Error("incompatible native DSH Host");
|
|
568
595
|
const runtime = parseSnapshot(value.runtime);
|
|
569
596
|
const optionalUrl = (url) => {
|
|
@@ -697,7 +724,8 @@ function parseNativeState(value) {
|
|
|
697
724
|
}
|
|
698
725
|
if (value.recoverableError !== undefined
|
|
699
726
|
&& value.recoverableError !== "pairing_failed"
|
|
700
|
-
&& value.recoverableError !== "connection_failed"
|
|
727
|
+
&& value.recoverableError !== "connection_failed"
|
|
728
|
+
&& value.recoverableError !== "route_unavailable") throw new Error("invalid native recovery state");
|
|
701
729
|
return {
|
|
702
730
|
schemaVersion: 2,
|
|
703
731
|
integration: "gatherthread",
|
|
@@ -845,6 +873,14 @@ const styles = {
|
|
|
845
873
|
field: { display: "grid", gap: 6 },
|
|
846
874
|
fieldLabel: { fontSize: 12, fontWeight: 600, opacity: 0.76 },
|
|
847
875
|
input: { width: "100%", minHeight: 36, boxSizing: "border-box", padding: "7px 10px", borderRadius: 9, border: "1px solid rgba(127,127,127,.28)", background: "transparent", color: "inherit", font: "inherit" },
|
|
876
|
+
// A dropdown owns a popup it cannot inherit: the list surface is painted from the
|
|
877
|
+
// control's own background and each row from its option, while the text keeps
|
|
878
|
+
// following the panel. Sharing `input` therefore drew the model names in the
|
|
879
|
+
// panel's foreground on the UA's own surface — white on white under a dark harness
|
|
880
|
+
// theme. Both the control and the rows name the system field pair instead, which
|
|
881
|
+
// the UA resolves against the used color scheme, so neither can match the other.
|
|
882
|
+
select: { width: "100%", minHeight: 36, boxSizing: "border-box", padding: "7px 10px", borderRadius: 9, border: "1px solid rgba(127,127,127,.28)", background: "Field", color: "FieldText", font: "inherit" },
|
|
883
|
+
option: { background: "Field", color: "FieldText" },
|
|
848
884
|
primaryButton: { minHeight: 36, padding: "8px 12px", borderRadius: 9, border: "1px solid Highlight", background: "Highlight", color: "HighlightText", WebkitTextFillColor: "HighlightText", font: "inherit", fontWeight: 600, cursor: "pointer" },
|
|
849
885
|
secondaryButton: { minHeight: 36, padding: "8px 12px", borderRadius: 9, border: "1px solid rgba(127,127,127,.3)", background: "transparent", color: "inherit", font: "inherit", cursor: "pointer" },
|
|
850
886
|
linkButton: { display: "inline-flex", alignItems: "center", justifyContent: "center", minHeight: 36, padding: "0 12px", borderRadius: 9, border: "1px solid rgba(127,127,127,.3)", color: "inherit", textDecoration: "none", fontWeight: 600 },
|