@gatherthread/dsh-host 0.1.0-alpha.6 → 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 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.6
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
  ```
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
3
  "name": "@gatherthread/dsh-host",
4
- "version": "0.1.0-alpha.6",
4
+ "version": "0.1.0-alpha.7",
5
5
  "license": "Apache-2.0",
6
6
  "defaultEnabled": false,
7
7
  "hostEntry": "@gatherthread/dsh-host",
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("sync/")
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 {
@@ -420,7 +499,7 @@ function detail(label, value) {
420
499
  function parseNativeState(value) {
421
500
  exactObject(value, [
422
501
  "schemaVersion", "integration", "authorization", "compatibility", "runtime",
423
- "officialServerUrl", "serverUrl", "deviceName", "route", "projectCount", "bindings", "localSync", "pairing", "recoverableError",
502
+ "officialServerUrl", "serverUrl", "deviceName", "route", "projectCount", "bindings", "localSync", "codeSync", "pairing", "recoverableError",
424
503
  ]);
425
504
  if (value.schemaVersion !== 2 || value.integration !== "gatherthread") throw new Error("invalid native status identity");
426
505
  if (!AUTHORIZATION_STATES.has(value.authorization)) throw new Error("invalid native authorization state");
@@ -444,6 +523,7 @@ function parseNativeState(value) {
444
523
  let route;
445
524
  let bindings;
446
525
  let localSync;
526
+ let codeSync;
447
527
  if (value.route !== undefined || value.bindings !== undefined || value.projectCount !== undefined) {
448
528
  if (value.route === undefined || !Number.isSafeInteger(value.projectCount) || value.projectCount < 0
449
529
  || !Array.isArray(value.bindings) || value.bindings.length > 100
@@ -484,6 +564,35 @@ function parseNativeState(value) {
484
564
  }
485
565
  return { ...sync };
486
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
+ });
487
596
  }
488
597
  let pairing;
489
598
  if (value.pairing !== undefined) {
@@ -519,7 +628,7 @@ function parseNativeState(value) {
519
628
  ...(officialServerUrl === undefined ? {} : { officialServerUrl }),
520
629
  ...(serverUrl === undefined ? {} : { serverUrl }),
521
630
  ...(value.deviceName === undefined ? {} : { deviceName: value.deviceName }),
522
- ...(route === undefined ? {} : { route, projectCount: value.projectCount, bindings, localSync }),
631
+ ...(route === undefined ? {} : { route, projectCount: value.projectCount, bindings, localSync, codeSync }),
523
632
  ...(pairing === undefined ? {} : { pairing }),
524
633
  ...(value.recoverableError === undefined ? {} : { recoverableError: value.recoverableError }),
525
634
  };