@modesto_ma/dsh-guanxiang 0.2.0-beta.2 → 0.2.0-beta.4

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/lib/client.js CHANGED
@@ -48,8 +48,12 @@ window.__ModuleLoader__.load({
48
48
  const SETTINGS_ROUTE = "/guanxiang/settings";
49
49
  /** POST:立即重新拉取全部仓库,或 body `{ url }` 指定的一个仓库。 */
50
50
  const SYNC_ROUTE = "/guanxiang/sync";
51
- /** PUT:body `{ tokenEnv, value }` 把令牌存入 DSH 凭据库;DELETE:body `{ tokenEnv }` 删除已存的值。只接受已保存的访问令牌配置引用的名称和分析接口令牌的名称。 */
51
+ /** PUT:body `{ tokenEnv, value }` 把分析接口令牌存入 DSH 凭据库;DELETE:body `{ tokenEnv }` 删除已存的值。只接受分析接口令牌的名称。 */
52
52
  const TOKEN_ROUTE = "/guanxiang/token";
53
+ /** POST:body {@link HostAddRequest} 经 dsh-repo 添加主机;DELETE:body `{ url }` 删除面板添加的主机。 */
54
+ const HOSTS_ROUTE = "/guanxiang/hosts";
55
+ /** PUT:body `{ url, value }` 经 dsh-repo 保存主机令牌;DELETE:body `{ url }` 清除已存的令牌。 */
56
+ const HOST_TOKEN_ROUTE = "/guanxiang/host-token";
53
57
  //#endregion
54
58
  //#region src/client/api.ts
55
59
  /**
@@ -129,8 +133,8 @@ window.__ModuleLoader__.load({
129
133
  })).state;
130
134
  }
131
135
  /**
132
- * Store an access token in DSH's credential store.
133
- * @param tokenEnv - credential reference named by a saved access-token configuration.
136
+ * Store the analyze token in DSH's credential store.
137
+ * @param tokenEnv - the analyze token's credential reference.
134
138
  * @param value - token value.
135
139
  * @returns the new state, which reports only whether the token is set.
136
140
  */
@@ -144,8 +148,8 @@ window.__ModuleLoader__.load({
144
148
  })).state;
145
149
  }
146
150
  /**
147
- * Remove a stored access token.
148
- * @param tokenEnv - credential reference named by a saved access-token configuration.
151
+ * Remove the stored analyze token.
152
+ * @param tokenEnv - the analyze token's credential reference.
149
153
  * @returns the new state.
150
154
  */
151
155
  async function clearToken(tokenEnv) {
@@ -154,11 +158,60 @@ window.__ModuleLoader__.load({
154
158
  body: JSON.stringify({ tokenEnv })
155
159
  })).state;
156
160
  }
161
+ /**
162
+ * Add a dsh-repo host, with its token.
163
+ * @param host - kind, address, alias, and token.
164
+ * @returns the new state.
165
+ */
166
+ async function addHost(host) {
167
+ return (await request(HOSTS_ROUTE, {
168
+ method: "POST",
169
+ body: JSON.stringify(host)
170
+ })).state;
171
+ }
172
+ /**
173
+ * Remove a host added from a panel.
174
+ * @param url - host address.
175
+ * @returns the new state.
176
+ */
177
+ async function removeHost(url) {
178
+ return (await request(HOSTS_ROUTE, {
179
+ method: "DELETE",
180
+ body: JSON.stringify({ url })
181
+ })).state;
182
+ }
183
+ /**
184
+ * Store a host's token.
185
+ * @param url - host address.
186
+ * @param value - token value.
187
+ * @returns the new state, which reports only whether the token is set.
188
+ */
189
+ async function saveHostToken(url, value) {
190
+ return (await request(HOST_TOKEN_ROUTE, {
191
+ method: "PUT",
192
+ body: JSON.stringify({
193
+ url,
194
+ value
195
+ })
196
+ })).state;
197
+ }
198
+ /**
199
+ * Remove a host's stored token.
200
+ * @param url - host address.
201
+ * @returns the new state.
202
+ */
203
+ async function clearHostToken(url) {
204
+ return (await request(HOST_TOKEN_ROUTE, {
205
+ method: "DELETE",
206
+ body: JSON.stringify({ url })
207
+ })).state;
208
+ }
157
209
  //#endregion
158
210
  //#region src/client/page.tsx
159
211
  /**
160
212
  * 左侧栏「观象」主区页面:应用仓库、访问令牌、日志下载、分析接口、高级(含 SKILL 改进)五个
161
- * 分页,共用一份草稿和底部保存条;令牌值不进草稿,单独存入 DSH 凭据库。
213
+ * 分页,共用一份草稿和底部保存条。访问令牌分页管理 dsh-repo 的主机,改动立即
214
+ * 生效,不进草稿;令牌值存入 DSH 凭据库。
162
215
  * 仓库拉取中时每 2 秒刷新状态。
163
216
  *
164
217
  * @module dsh-guanxiang/client/page
@@ -195,9 +248,7 @@ window.__ModuleLoader__.load({
195
248
  }
196
249
  function toDraft(settings) {
197
250
  return {
198
- reposDir: settings.reposDir,
199
251
  repos: settings.repos.map((repo) => ({ ...repo })),
200
- gitCredentials: settings.gitCredentials.map((credential) => ({ ...credential })),
201
252
  fetchIntervalMin: trimNumber(settings.fetchIntervalMs / MINUTE),
202
253
  maxAppsPerRequest: String(settings.maxAppsPerRequest),
203
254
  dispatchTimeoutMin: trimNumber(settings.dispatchTimeoutMs / MINUTE),
@@ -221,17 +272,10 @@ window.__ModuleLoader__.load({
221
272
  const maxFiles = positive(draft.skillMaxFiles, "提议文件数上限");
222
273
  if (!Number.isInteger(maxFiles)) throw new Error("提议文件数上限必须是整数");
223
274
  return {
224
- reposDir: draft.reposDir.trim(),
225
275
  repos: draft.repos.map((repo) => repo.branch === void 0 || repo.branch.trim() === "" ? { url: repo.url.trim() } : {
226
276
  url: repo.url.trim(),
227
277
  branch: repo.branch.trim()
228
278
  }),
229
- gitCredentials: draft.gitCredentials.map((credential) => ({
230
- url: credential.url.trim(),
231
- ...credential.alias === void 0 || credential.alias.trim() === "" ? {} : { alias: credential.alias.trim() },
232
- username: credential.username.trim(),
233
- tokenEnv: credential.tokenEnv.trim()
234
- })),
235
279
  fetchIntervalMs: Math.round(positive(draft.fetchIntervalMin, "更新间隔", true) * MINUTE),
236
280
  maxAppsPerRequest: maxApps,
237
281
  dispatchTimeoutMs: Math.round(positive(draft.dispatchTimeoutMin, "派发超时") * MINUTE),
@@ -465,25 +509,15 @@ window.__ModuleLoader__.load({
465
509
  }
466
510
  }) : null,
467
511
  tab === "tokens" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TokensTab, {
468
- draft,
469
- setDraft,
470
512
  state,
471
513
  busy,
472
- onAdd: async (credential, token) => {
473
- const added = (await run(() => saveSettings({
474
- ...state.settings,
475
- gitCredentials: [...state.settings.gitCredentials, credential]
476
- }), token === "" ? `已添加 ${credential.url},请填入令牌` : `已添加 ${credential.url}`, false))?.settings.gitCredentials[state.settings.gitCredentials.length];
477
- if (added === void 0) return false;
478
- setDraft((current) => current === void 0 || current.gitCredentials.some((item) => item.url === added.url) ? current : {
479
- ...current,
480
- gitCredentials: [...current.gitCredentials, added]
481
- });
482
- return token === "" || await run(() => saveToken(added.tokenEnv, token), `已添加 ${added.url} 并保存令牌,使用它的仓库会重新拉取`, false) !== void 0;
514
+ onAdd: async (host) => await run(() => addHost(host), `已添加 ${host.url},仓库会重新拉取`, false) !== void 0,
515
+ onSave: async (url, value) => await run(() => saveHostToken(url, value), `已保存 ${url} 的令牌,仓库会重新拉取`, false) !== void 0,
516
+ onClear: (url) => {
517
+ run(() => clearHostToken(url), `已清除面板保存的 ${url} 令牌`, false);
483
518
  },
484
- onSave: async (tokenEnv, value) => await run(() => saveToken(tokenEnv, value), `已保存 ${tokenEnv},使用它的仓库会重新拉取`, false) !== void 0,
485
- onClear: (tokenEnv) => {
486
- run(() => clearToken(tokenEnv), `已清除面板保存的 ${tokenEnv}`, false);
519
+ onRemove: (url) => {
520
+ run(() => removeHost(url), `已删除 ${url}`, false);
487
521
  }
488
522
  }) : null,
489
523
  tab === "download" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DownloadTab, {
@@ -495,7 +529,7 @@ window.__ModuleLoader__.load({
495
529
  draft,
496
530
  setDraft,
497
531
  analyze: state.analyze,
498
- credentialsAvailable: state.credentials.available,
532
+ credentialsAvailable: state.credentialsAvailable,
499
533
  overridden: state.overridden,
500
534
  busy,
501
535
  onSave: async (value) => await run(() => saveToken(state.analyze.tokenEnv, value), "已保存分析接口令牌,下一个请求起生效", false) !== void 0,
@@ -636,11 +670,10 @@ window.__ModuleLoader__.load({
636
670
  formatTime(current.commitTime),
637
671
  ")· 拉取于 ",
638
672
  formatTime(current.syncedAt),
639
- " · ",
640
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
673
+ current.dir === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [" · ", /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
641
674
  className: "gx__mono",
642
675
  children: current.dir
643
- })
676
+ })] })
644
677
  ]
645
678
  }) : null,
646
679
  saved && current.error !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
@@ -708,15 +741,21 @@ window.__ModuleLoader__.load({
708
741
  })
709
742
  ]
710
743
  }),
744
+ state.hosts.availability === "ready" ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
745
+ className: "gx__notice gx__notice--error",
746
+ children: state.hosts.availability === "missing" ? "没有加载 dsh-repo 插件,仓库无法拉取。请安装 dsh-repo。" : "dsh-repo 版本过旧,仓库无法拉取。请升级 dsh-repo。"
747
+ }),
711
748
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
712
749
  className: "gx__secFoot",
713
750
  children: [
714
- "使用本机已有的 git 凭据(SSH key 或 credential helper)拉取;https 地址可在「访问令牌」中配置个人访问令牌。不留分支时跟随远端默认分支。每个仓库需包含 ",
751
+ "由 dsh-repo 拉取到它的镜像目录",
752
+ state.hosts.mirrorDir === void 0 ? "" : `(${state.hosts.mirrorDir})`,
753
+ "。https 地址使用「访问令牌」中对应主机的令牌,ssh 地址和其他主机使用本机已有的 git 凭据(SSH key 或 credential helper)。不留分支时跟随远端默认分支。每个仓库需包含 ",
715
754
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
716
755
  className: "gx__mono",
717
756
  children: ".dsh/guanxiang.yaml"
718
757
  }),
719
- " 与其中声明的日志分析 SKILL。删除地址不会删除本地目录。"
758
+ " 与其中声明的日志分析 SKILL。删除地址不会删除本地镜像。"
720
759
  ]
721
760
  }),
722
761
  state.repos.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
@@ -731,15 +770,19 @@ window.__ModuleLoader__.load({
731
770
  ]
732
771
  });
733
772
  }
734
- const DEFAULT_USERNAME = "oauth2";
735
- /** Prefix of the credential keys the panel allocates; namespaced so an unrelated `GITLAB_TOKEN` in the environment is not picked up. */
736
- const DEFAULT_TOKEN_ENV = "GUANXIANG_GITLAB_TOKEN";
737
773
  /** Credential store layers of `@deepseek-ai/dsh-credentials-local` other than its own file. */
738
774
  const TOKEN_SOURCES = {
739
775
  "env": "启动环境变量",
740
776
  "project-env": "项目 .env",
741
777
  "user-env": "$DSH_HOME/.env"
742
778
  };
779
+ const HOST_KINDS = [{
780
+ id: "gitlab",
781
+ label: "GitLab"
782
+ }, {
783
+ id: "github",
784
+ label: "GitHub"
785
+ }];
743
786
  function tokenPill(token) {
744
787
  if (token === void 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
745
788
  className: "gx__pill",
@@ -763,89 +806,75 @@ window.__ModuleLoader__.load({
763
806
  ]
764
807
  });
765
808
  }
766
- /**
767
- * First of `GUANXIANG_GITLAB_TOKEN`, `GUANXIANG_GITLAB_TOKEN_2`, … that no
768
- * saved or drafted entry uses.
769
- * @param used - keys already in use.
770
- * @returns a free key.
771
- */
772
- function freeTokenEnv(used) {
773
- if (!used.has(DEFAULT_TOKEN_ENV)) return DEFAULT_TOKEN_ENV;
774
- let index = 2;
775
- while (used.has(`${DEFAULT_TOKEN_ENV}_${index}`)) index += 1;
776
- return `${DEFAULT_TOKEN_ENV}_${index}`;
777
- }
778
- function TokensTab({ draft, setDraft, state, busy, onAdd, onSave, onClear }) {
809
+ function TokensTab({ state, busy, onAdd, onSave, onClear, onRemove }) {
810
+ const [kind, setKind] = (0, react.useState)("gitlab");
779
811
  const [url, setUrl] = (0, react.useState)("");
780
812
  const [alias, setAlias] = (0, react.useState)("");
781
813
  const [token, setToken] = (0, react.useState)("");
782
- const [addError, setAddError] = (0, react.useState)();
783
- const [adding, setAdding] = (0, react.useState)(false);
784
814
  const [values, setValues] = (0, react.useState)({});
785
- const tokens = (0, react.useMemo)(() => new Map(state.credentials.tokens.map((item) => [item.tokenEnv, item])), [state.credentials.tokens]);
786
- const saved = (0, react.useMemo)(() => new Set(state.settings.gitCredentials.map((credential) => credential.tokenEnv)), [state.settings.gitCredentials]);
815
+ const { availability, hosts, error } = state.hosts;
787
816
  const complete = url.trim() !== "" && alias.trim() !== "" && token.trim() !== "";
788
817
  const add = () => {
789
- if (!complete || busy || adding) return;
790
- const address = url.trim();
791
- if (state.settings.gitCredentials.some((credential) => credential.url === address)) {
792
- setAddError("这个地址已经添加过了,请在上面的列表里更新它的令牌。");
793
- return;
794
- }
795
- setAddError(void 0);
796
- setAdding(true);
797
- const tokenEnv = freeTokenEnv(/* @__PURE__ */ new Set([...saved, ...draft.gitCredentials.map((credential) => credential.tokenEnv)]));
818
+ if (!complete || busy) return;
798
819
  onAdd({
799
- url: address,
820
+ kind,
821
+ url: url.trim(),
800
822
  alias: alias.trim(),
801
- username: DEFAULT_USERNAME,
802
- tokenEnv
803
- }, token.trim()).then((ok) => {
823
+ token: token.trim()
824
+ }).then((ok) => {
804
825
  if (!ok) return;
805
826
  setUrl("");
806
827
  setAlias("");
807
828
  setToken("");
808
- }).finally(() => {
809
- setAdding(false);
810
829
  });
811
830
  };
812
- const save = (name) => {
813
- const value = values[name]?.trim() ?? "";
814
- if (value === "" || busy || adding) return;
815
- onSave(name, value).then((ok) => {
831
+ const save = (address) => {
832
+ const value = values[address]?.trim() ?? "";
833
+ if (value === "" || busy) return;
834
+ onSave(address, value).then((ok) => {
816
835
  if (ok) setValues((current) => ({
817
836
  ...current,
818
- [name]: ""
837
+ [address]: ""
819
838
  }));
820
839
  });
821
840
  };
822
841
  const submitOnEnter = (event) => {
823
842
  if (event.key === "Enter") add();
824
843
  };
844
+ if (availability !== "ready") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
845
+ className: "gx__sec",
846
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
847
+ className: "gx__secTitle",
848
+ children: "访问令牌"
849
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
850
+ className: "gx__notice gx__notice--error",
851
+ children: availability === "missing" ? "访问令牌由 dsh-repo 插件管理,当前没有加载 dsh-repo。请安装 dsh-repo。" : "dsh-repo 版本过旧,不能在这里管理访问令牌。请升级 dsh-repo。"
852
+ })]
853
+ });
825
854
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
826
855
  className: "gx__sec",
827
856
  children: [
828
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("h2", {
857
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
829
858
  className: "gx__secTitle",
830
- children: ["GitLab 访问令牌 ", /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Overridden, {
831
- keys: state.overridden,
832
- name: "gitCredentials"
833
- })]
859
+ children: "访问令牌"
834
860
  }),
835
- state.credentials.available ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
861
+ state.credentialsAvailable ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
836
862
  className: "gx__notice gx__notice--error",
837
863
  children: "当前 DSH 组合没有加载凭据服务(@deepseek-ai/dsh-credentials-local),无法保存或读取令牌。"
838
864
  }),
865
+ error === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
866
+ className: "gx__notice gx__notice--error",
867
+ children: ["无法读取 dsh-repo 的主机:", error]
868
+ }),
839
869
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
840
870
  className: "gx__list",
841
871
  children: [
842
- draft.gitCredentials.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
872
+ hosts.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
843
873
  className: "gx__emptyRow",
844
- children: "还没有访问令牌。用 https 地址拉取 GitLab 私有仓库时,在下面填入 GitLab 地址、令牌别名和令牌。"
874
+ children: "还没有主机。用 https 地址拉取私有仓库时,在下面填入 GitLab 或 GitHub 地址、别名和令牌。"
845
875
  }) : null,
846
- draft.gitCredentials.map((credential, index) => {
847
- const status = saved.has(credential.tokenEnv) ? tokens.get(credential.tokenEnv) : void 0;
848
- const value = values[credential.tokenEnv] ?? "";
876
+ hosts.map((host) => {
877
+ const value = values[host.url] ?? "";
849
878
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
850
879
  className: "gx__row gx__row--top",
851
880
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
@@ -853,17 +882,28 @@ window.__ModuleLoader__.load({
853
882
  children: [
854
883
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
855
884
  className: "gx__rowLabel",
856
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: credential.alias ?? credential.url }), tokenPill(status)]
885
+ children: [
886
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: host.alias ?? host.url }),
887
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
888
+ className: "gx__tag",
889
+ children: host.kind === "github" ? "GitHub" : "GitLab"
890
+ }),
891
+ host.origin === "config" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
892
+ className: "gx__tag",
893
+ children: "cordis.yml"
894
+ }) : null,
895
+ tokenPill(host.token)
896
+ ]
857
897
  }),
858
- credential.alias === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
898
+ host.alias === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
859
899
  className: "gx__rowSummary gx__mono",
860
- children: credential.url
900
+ children: host.url
861
901
  }),
862
- status?.error === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
902
+ host.token.error === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
863
903
  className: "gx__rowError",
864
- children: status.error
904
+ children: host.token.error
865
905
  }),
866
- status !== void 0 && status.writable ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
906
+ host.token.writable ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
867
907
  className: "gx__addRow gx__addRow--flush",
868
908
  children: [
869
909
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
@@ -871,33 +911,33 @@ window.__ModuleLoader__.load({
871
911
  type: "password",
872
912
  autoComplete: "new-password",
873
913
  spellCheck: false,
874
- placeholder: status.configured ? "输入新令牌以替换" : "粘贴令牌",
914
+ placeholder: host.token.configured ? "输入新令牌以替换" : "粘贴令牌",
875
915
  value,
876
916
  onChange: (event) => {
877
917
  setValues({
878
918
  ...values,
879
- [credential.tokenEnv]: event.target.value
919
+ [host.url]: event.target.value
880
920
  });
881
921
  },
882
922
  onKeyDown: (event) => {
883
- if (event.key === "Enter") save(credential.tokenEnv);
923
+ if (event.key === "Enter") save(host.url);
884
924
  }
885
925
  }),
886
926
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
887
927
  type: "button",
888
928
  className: "gx__btn",
889
- disabled: busy || adding || value.trim() === "",
929
+ disabled: busy || value.trim() === "",
890
930
  onClick: () => {
891
- save(credential.tokenEnv);
931
+ save(host.url);
892
932
  },
893
933
  children: "保存令牌"
894
934
  }),
895
- status.configured && status.source === "file" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
935
+ host.token.configured && host.token.source === "file" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
896
936
  type: "button",
897
937
  className: "gx__btn gx__btn--ghost",
898
938
  disabled: busy,
899
939
  onClick: () => {
900
- onClear(credential.tokenEnv);
940
+ onClear(host.url);
901
941
  },
902
942
  children: "清除"
903
943
  }) : null
@@ -906,30 +946,42 @@ window.__ModuleLoader__.load({
906
946
  ]
907
947
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
908
948
  className: "gx__rowCtl",
909
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
949
+ children: host.origin === "added" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
910
950
  type: "button",
911
951
  className: "gx__btn gx__btn--danger",
952
+ disabled: busy,
912
953
  onClick: () => {
913
- setDraft({
914
- ...draft,
915
- gitCredentials: draft.gitCredentials.filter((_, position) => position !== index)
916
- });
954
+ onRemove(host.url);
917
955
  },
918
956
  children: "删除"
919
- })
957
+ }) : null
920
958
  })]
921
- }, credential.url);
959
+ }, host.url);
922
960
  }),
923
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
961
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
924
962
  className: "gx__addForm",
925
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
963
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
926
964
  className: "gx__addRow gx__addRow--flush",
927
965
  children: [
928
966
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
929
967
  className: "gx__field",
930
- children: ["GitLab 地址", /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
968
+ children: ["类型", /* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
969
+ className: "gx__input",
970
+ value: kind,
971
+ onChange: (event) => {
972
+ setKind(event.target.value);
973
+ },
974
+ children: HOST_KINDS.map((item) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
975
+ value: item.id,
976
+ children: item.label
977
+ }, item.id))
978
+ })]
979
+ }),
980
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
981
+ className: "gx__field",
982
+ children: ["地址", /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
931
983
  className: "gx__input",
932
- placeholder: "https://gitlab.example.com",
984
+ placeholder: kind === "github" ? "https://github.com" : "https://gitlab.example.com",
933
985
  value: url,
934
986
  onChange: (event) => {
935
987
  setUrl(event.target.value);
@@ -939,9 +991,9 @@ window.__ModuleLoader__.load({
939
991
  }),
940
992
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
941
993
  className: "gx__field",
942
- children: ["令牌别名", /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
994
+ children: ["别名", /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
943
995
  className: "gx__input",
944
- placeholder: "例如 guanxiang",
996
+ placeholder: "例如 公司 GitLab",
945
997
  value: alias,
946
998
  onChange: (event) => {
947
999
  setAlias(event.target.value);
@@ -967,21 +1019,18 @@ window.__ModuleLoader__.load({
967
1019
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
968
1020
  type: "button",
969
1021
  className: "gx__btn gx__btn--end",
970
- disabled: busy || adding || !complete,
1022
+ disabled: busy || !complete,
971
1023
  onClick: add,
972
1024
  children: "添加"
973
1025
  })
974
1026
  ]
975
- }), addError === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
976
- className: "gx__rowError",
977
- children: addError
978
- })]
1027
+ })
979
1028
  })
980
1029
  ]
981
1030
  }),
982
1031
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
983
1032
  className: "gx__secFoot",
984
- children: "地址在这个前缀下的 https 仓库使用对应令牌(多个匹配时取路径最长的),ssh 地址不受影响。拉取需要 read_repository 权限,开启 SKILL 改进时还需要 write_repository。令牌保存在 DSH 凭据库(默认 $DSH_HOME/.credentials.yaml,仅本机用户可读),面板只显示是否已设置;令牌只经环境变量交给 git,不写入设置文件、仓库配置、日志或模型上下文。"
1033
+ children: "主机和令牌由 dsh-repo 管理,添加、删除和保存令牌立即生效,不需要点保存。该主机下的 https 仓库使用对应令牌,ssh 地址不受影响。GitLab 拉取需要 read_repository 权限,开启 SKILL 改进时需要 api 权限;GitHub 需要 Contents 与 Pull requests 读写权限。令牌保存在 DSH 凭据库(默认 $DSH_HOME/.credentials.yaml,仅本机用户可读),面板只显示是否已设置。cordis.yml 中配置的主机只能在这里更新令牌。"
985
1034
  })
986
1035
  ]
987
1036
  });
@@ -1320,36 +1369,9 @@ window.__ModuleLoader__.load({
1320
1369
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
1321
1370
  className: "gx__secTitle",
1322
1371
  children: "仓库"
1323
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1372
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1324
1373
  className: "gx__list",
1325
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1326
- className: "gx__row",
1327
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1328
- className: "gx__rowText",
1329
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1330
- className: "gx__rowLabel",
1331
- children: ["拉取目录 ", /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Overridden, {
1332
- keys: overridden,
1333
- name: "reposDir"
1334
- })]
1335
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1336
- className: "gx__rowSummary",
1337
- children: "每个仓库拉取到该目录下的同名子目录,观象只读使用。"
1338
- })]
1339
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1340
- className: "gx__rowCtl",
1341
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1342
- className: "gx__input gx__input--wide gx__mono",
1343
- value: draft.reposDir,
1344
- onChange: (event) => {
1345
- setDraft({
1346
- ...draft,
1347
- reposDir: event.target.value
1348
- });
1349
- }
1350
- })
1351
- })]
1352
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(NumberRow, {
1374
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(NumberRow, {
1353
1375
  label: "更新间隔",
1354
1376
  summary: "输入 /guanxiang 时,距上次拉取超过该时间的仓库会先更新;0 表示每次都更新。",
1355
1377
  unit: "分钟",
@@ -1361,7 +1383,7 @@ window.__ModuleLoader__.load({
1361
1383
  fetchIntervalMin: value
1362
1384
  });
1363
1385
  }
1364
- })]
1386
+ })
1365
1387
  })]
1366
1388
  }),
1367
1389
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
@@ -1418,7 +1440,7 @@ window.__ModuleLoader__.load({
1418
1440
  })]
1419
1441
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1420
1442
  className: "gx__rowSummary",
1421
- children: "子 Agent 发现应用的日志分析 SKILL 有欠缺时可以写出修改提议;经用户授权后,观象用访问令牌把它推送到新分支并创建合并请求(GitLab,令牌需要 write_repository 权限)。"
1443
+ children: "子 Agent 发现应用的日志分析 SKILL 有欠缺时可以写出修改提议;经用户授权后,观象经 dsh-repo 把它提交到新分支并创建合并请求(GitLab 或 GitHub,需要安装 dsh-repo 并配置该仓库主机的令牌)。"
1422
1444
  })]
1423
1445
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1424
1446
  className: "gx__rowCtl",