dsh-bash-terminal-ts 0.7.1 → 0.7.2-compat.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/dsh.plugin.json CHANGED
@@ -1,22 +1,22 @@
1
- {
2
- "id": "dsh-bash-terminal-ts",
3
- "name": "dsh-bash-terminal-ts",
4
- "version": "0.7.1",
5
- "description": {
6
- "en": "One shell tool that runs commands through PowerShell, Git Bash, MSYS2 or WSL on Windows, with a user-chosen default terminal in the Web UI settings.",
7
- "zh": "一个 shell 工具,可在 Windows 上通过 PowerShell / Git Bash / MSYS2 / WSL 执行命令,默认终端在 Web UI 设置中选择。"
8
- },
9
- "homepage": "https://github.com/drscrewdriver/dsh-bash-terminal-ts",
10
- "repository": {
11
- "type": "git",
12
- "url": "https://github.com/drscrewdriver/dsh-bash-terminal-ts.git"
13
- },
14
- "license": "MIT",
15
- "engines": {
16
- "dsh": ">=0.2.0-rc.1 <0.2.1-0"
17
- },
18
- "components": {
19
- "host": "lib/index.js",
20
- "client": "dist/client.js"
21
- }
22
- }
1
+ {
2
+ "id": "dsh-bash-terminal-ts",
3
+ "name": "dsh-bash-terminal-ts",
4
+ "version": "0.7.2-compat.2",
5
+ "description": {
6
+ "en": "One shell tool that runs commands through PowerShell, Git Bash, MSYS2 or WSL on Windows, with a user-chosen default terminal in the Web UI settings.",
7
+ "zh": "一个 shell 工具,可在 Windows 上通过 PowerShell / Git Bash / MSYS2 / WSL 执行命令,默认终端在 Web UI 设置中选择。"
8
+ },
9
+ "homepage": "https://github.com/drscrewdriver/dsh-bash-terminal-ts",
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "https://github.com/drscrewdriver/dsh-bash-terminal-ts.git"
13
+ },
14
+ "license": "MIT",
15
+ "engines": {
16
+ "dsh": ">=0.1.0-rc.8 <0.2.1-0"
17
+ },
18
+ "components": {
19
+ "host": "lib/index.js",
20
+ "client": "dist/client.js"
21
+ }
22
+ }
package/lib/client.js CHANGED
@@ -24,18 +24,14 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
24
24
  // src/client.tsx
25
25
  var client_exports = {};
26
26
  __export(client_exports, {
27
- apply: () => apply,
28
- inject: () => inject
27
+ apply: () => apply
29
28
  });
30
29
  module.exports = __toCommonJS(client_exports);
31
30
  var import_react = require("react");
32
- var import_dsh_client_store = require("@deepseek-ai/dsh-client-store");
33
- var import_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
34
31
  var import_jsx_runtime = require("react/jsx-runtime");
35
32
  var SETTINGS_NS = "settings.bash-terminal";
36
- var ENTRY_ID = "tool-bash-terminal";
37
33
  var SHELLS = ["powershell", "gitbash", "msys2", "wsl"];
38
- var ROW_CSS = ".btRow{border-bottom:1px solid var(--dsw-alias-border-l2);align-items:center;gap:8px;padding:16px 0;display:flex}.btRowText{flex-direction:column;flex:1;gap:4px;min-width:0;padding-right:48px;display:flex}.btTitle{color:var(--dsw-alias-label-primary);font-size:14px;font-weight:400;line-height:22px}.btDesc{color:var(--dsw-alias-label-tertiary);font-size:12px;font-weight:400;line-height:18px}.btSelector{background:var(--dsw-alias-bg-module-platform);height:36px;font:inherit;color:var(--dsw-alias-label-primary);cursor:pointer;border:none;border-radius:18px;align-items:center;gap:12px;padding:0 14px;font-size:14px;line-height:22px;display:inline-flex}.btSelector:hover{background:var(--dsw-alias-interactive-bg-hover)}.btChevron{flex:none}";
34
+ var ROW_CSS = ".btRow{border-bottom:1px solid var(--dsw-alias-border-l2);align-items:center;gap:8px;padding:16px 0;display:flex}.btRowText{flex-direction:column;flex:1;gap:4px;min-width:0;padding-right:48px;display:flex}.btTitle{color:var(--dsw-alias-label-primary);font-size:14px;font-weight:400;line-height:22px}.btDesc{color:var(--dsw-alias-label-tertiary);font-size:12px;font-weight:400;line-height:18px}.btSelector{background:var(--dsw-alias-bg-module-platform);height:36px;font:inherit;color:var(--dsw-alias-label-primary);cursor:pointer;border:none;border-radius:18px;align-items:center;gap:12px;padding:0 14px;font-size:14px;line-height:22px;display:inline-flex}.btSelector:hover{background:var(--dsw-alias-interactive-bg-hover)}.btGear{position:fixed;right:16px;bottom:110px;z-index:10000;width:40px;height:40px;border-radius:50%;border:1px solid rgba(127,127,127,.4);background:rgba(30,30,32,.85);color:inherit;cursor:pointer;font-size:17px;box-shadow:0 4px 16px rgba(0,0,0,.35)}.btOverlay{position:fixed;inset:0;z-index:10000;background:rgba(0,0,0,.45)}.btPanel{position:fixed;top:6vh;left:50%;transform:translateX(-50%);width:min(760px,92vw);max-height:84vh;overflow-y:auto;z-index:10001;background:var(--dsw-alias-bg-module-platform,rgba(30,30,32,.92));border:1px solid var(--dsw-alias-border-l2,rgba(127,127,127,.3));border-radius:14px;padding:20px;color:var(--dsw-alias-label-primary)}";
39
35
  if (typeof document !== "undefined" && document.querySelector('style[data-plugin-css="bash-terminal-row"]') === null) {
40
36
  const tag = document.createElement("style");
41
37
  tag.dataset.plugin = "dsh-bash-terminal-ts";
@@ -50,7 +46,7 @@ var zh = {
50
46
  "shell.gitbash": "Git Bash",
51
47
  "shell.msys2": "MSYS2",
52
48
  "shell.wsl": "WSL",
53
- "shell.unavailable": "\u8BBE\u7F6E\u9762\u4E0D\u53EF\u7528\uFF1A\u5BBF\u4E3B\u672A\u63D0\u4F9B\u672C\u63D2\u4EF6\u7684 configForms \u6761\u76EE\uFF08\u68C0\u67E5 cordis.patch.yml \u7684 entry id\uFF09"
49
+ "shell.unavailable": "\u8BBE\u7F6E\u9762\u4E0D\u53EF\u7528:\u5BBF\u4E3B\u914D\u7F6E\u6865\u672A\u54CD\u5E94(\u68C0\u67E5\u5BBF\u4E3B\u662F\u5426\u4E3A compat \u7248)"
54
50
  };
55
51
  var en = {
56
52
  "shell.title": "Default terminal",
@@ -59,7 +55,7 @@ var en = {
59
55
  "shell.gitbash": "Git Bash",
60
56
  "shell.msys2": "MSYS2",
61
57
  "shell.wsl": "WSL",
62
- "shell.unavailable": "Settings unavailable: the host exposes no configForms entry for this plugin (check the cordis.patch.yml entry id)"
58
+ "shell.unavailable": "Settings unavailable: the host config bridge did not respond (is the host a compat build?)"
63
59
  };
64
60
  var fr = {
65
61
  "shell.title": "Terminal par d\xE9faut",
@@ -68,136 +64,172 @@ var fr = {
68
64
  "shell.gitbash": "Git Bash",
69
65
  "shell.msys2": "MSYS2",
70
66
  "shell.wsl": "WSL",
71
- "shell.unavailable": "Param\xE8tres indisponibles : l'h\xF4te n'expose aucune entr\xE9e configForms pour ce plugin (v\xE9rifiez l'entry id du cordis.patch.yml)"
67
+ "shell.unavailable": "Param\xE8tres indisponibles: le pont de configuration de l'h\xF4te n'a pas r\xE9pondu"
72
68
  };
73
69
  var de = {
74
- "shell.title": "Standard-Terminal",
75
- "shell.description": "Terminal, das vom Shell-Tool verwendet wird",
70
+ "shell.title": "Standardterminal",
71
+ "shell.description": "Terminal f\xFCr den Shell-Befehl",
76
72
  "shell.powershell": "PowerShell",
77
73
  "shell.gitbash": "Git Bash",
78
74
  "shell.msys2": "MSYS2",
79
75
  "shell.wsl": "WSL",
80
- "shell.unavailable": "Einstellungen nicht verf\xFCgbar: Der Host stellt keinen configForms-Eintrag f\xFCr dieses Plugin bereit (Entry-ID in cordis.patch.yml pr\xFCfen)"
76
+ "shell.unavailable": "Einstellungen nicht verf\xFCgbar: Die Konfigurationsbr\xFCcke des Hosts hat nicht geantwortet"
81
77
  };
82
78
  var it = {
83
79
  "shell.title": "Terminale predefinito",
84
- "shell.description": "Terminale usato dallo strumento shell",
80
+ "shell.description": "Terminale usato dal comando shell",
85
81
  "shell.powershell": "PowerShell",
86
82
  "shell.gitbash": "Git Bash",
87
83
  "shell.msys2": "MSYS2",
88
84
  "shell.wsl": "WSL",
89
- "shell.unavailable": "Impostazioni non disponibili: l'host non espone alcuna voce configForms per questo plugin (verifica l'entry id in cordis.patch.yml)"
85
+ "shell.unavailable": "Impostazioni non disponibili: il bridge di configurazione dell'host non ha risposto"
90
86
  };
91
87
  var ru = {
92
88
  "shell.title": "\u0422\u0435\u0440\u043C\u0438\u043D\u0430\u043B \u043F\u043E \u0443\u043C\u043E\u043B\u0447\u0430\u043D\u0438\u044E",
93
- "shell.description": "\u0422\u0435\u0440\u043C\u0438\u043D\u0430\u043B, \u0438\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0435\u043C\u044B\u0439 \u0438\u043D\u0441\u0442\u0440\u0443\u043C\u0435\u043D\u0442\u043E\u043C shell",
89
+ "shell.description": "\u0422\u0435\u0440\u043C\u0438\u043D\u0430\u043B \u0434\u043B\u044F \u043A\u043E\u043C\u0430\u043D\u0434 \u043E\u0431\u043E\u043B\u043E\u0447\u043A\u0438",
94
90
  "shell.powershell": "PowerShell",
95
91
  "shell.gitbash": "Git Bash",
96
92
  "shell.msys2": "MSYS2",
97
93
  "shell.wsl": "WSL",
98
- "shell.unavailable": "\u041D\u0430\u0441\u0442\u0440\u043E\u0439\u043A\u0438 \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u043D\u044B: \u0445\u043E\u0441\u0442 \u043D\u0435 \u043F\u0440\u0435\u0434\u043E\u0441\u0442\u0430\u0432\u043B\u044F\u0435\u0442 \u0437\u0430\u043F\u0438\u0441\u044C configForms \u0434\u043B\u044F \u044D\u0442\u043E\u0433\u043E \u043F\u043B\u0430\u0433\u0438\u043D\u0430 (\u043F\u0440\u043E\u0432\u0435\u0440\u044C\u0442\u0435 entry id \u0432 cordis.patch.yml)"
94
+ "shell.unavailable": "\u041D\u0430\u0441\u0442\u0440\u043E\u0439\u043A\u0438 \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u043D\u044B: \u043C\u043E\u0441\u0442 \u043A\u043E\u043D\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438 \u0445\u043E\u0441\u0442\u0430 \u043D\u0435 \u043E\u0442\u0432\u0435\u0442\u0438\u043B"
99
95
  };
100
96
  var es = {
101
- "shell.title": "Terminal predeterminado",
102
- "shell.description": "Terminal que usa la herramienta shell",
97
+ "shell.title": "Terminal predeterminada",
98
+ "shell.description": "Terminal usada por el comando shell",
103
99
  "shell.powershell": "PowerShell",
104
100
  "shell.gitbash": "Git Bash",
105
101
  "shell.msys2": "MSYS2",
106
102
  "shell.wsl": "WSL",
107
- "shell.unavailable": "Configuraci\xF3n no disponible: el host no expone ninguna entrada configForms para este plugin (comprueba el entry id de cordis.patch.yml)"
103
+ "shell.unavailable": "Configuraci\xF3n no disponible: el puente de configuraci\xF3n del host no respondi\xF3"
108
104
  };
109
- var inject = ["slots", "locale", "configForms"];
110
- function ShellPreferenceRow({ t, useStore, setShell }) {
111
- const shell = useStore((s) => s.shell);
112
- const writable = useStore((s) => s.writable);
113
- const status = useStore((s) => s.status);
114
- const [open, setOpen] = (0, import_react.useState)(false);
115
- const items = SHELLS.map((id) => ({ id, label: t("shell." + id) }));
105
+ var zhDictTitle = () => zh["shell.title"] ?? "Terminal";
106
+ async function bridgeConfig() {
107
+ try {
108
+ const r = await fetch("/api/bash-terminal/config", {
109
+ method: "POST",
110
+ headers: { "content-type": "application/json" },
111
+ body: "{}"
112
+ });
113
+ const d = await r.json();
114
+ return d && d.ok ? d.value : null;
115
+ } catch {
116
+ return null;
117
+ }
118
+ }
119
+ async function bridgeWrite(value) {
120
+ try {
121
+ const r = await fetch("/api/bash-terminal/config", {
122
+ method: "POST",
123
+ headers: { "content-type": "application/json" },
124
+ body: JSON.stringify({ defaultShell: value })
125
+ });
126
+ const d = await r.json();
127
+ return !!(d && d.ok);
128
+ } catch {
129
+ return false;
130
+ }
131
+ }
132
+ function ShellPreferenceRow() {
133
+ const [shell, setShell] = (0, import_react.useState)("powershell");
134
+ const [status, setStatus] = (0, import_react.useState)("loading");
135
+ (0, import_react.useEffect)(() => {
136
+ let alive = true;
137
+ bridgeConfig().then((v) => {
138
+ if (!alive) return;
139
+ if (v && v.defaultShell) {
140
+ setShell(v.defaultShell);
141
+ setStatus("ready");
142
+ } else {
143
+ setStatus("unavailable");
144
+ }
145
+ });
146
+ return () => {
147
+ alive = false;
148
+ };
149
+ }, []);
150
+ const change = (value) => {
151
+ setShell(value);
152
+ void bridgeWrite(value);
153
+ };
116
154
  if (status === "unavailable") {
117
155
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "btRow", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "btRowText", children: [
118
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "btTitle", children: t("shell.title") }),
119
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "btDesc", children: t("shell.unavailable") })
156
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "btTitle", children: zh["shell.title"] }),
157
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "btDesc", children: zh["shell.unavailable"] })
120
158
  ] }) });
121
159
  }
122
160
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "btRow", children: [
123
161
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "btRowText", children: [
124
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "btTitle", children: t("shell.title") }),
125
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "btDesc", children: t("shell.description") })
162
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "btTitle", children: zh["shell.title"] }),
163
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "btDesc", children: zh["shell.description"] })
126
164
  ] }),
127
165
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
128
- import_dsh_client_ui_primitives.Menu,
166
+ "select",
129
167
  {
130
- open,
131
- onClose: () => setOpen(false),
132
- items,
133
- selectedId: shell,
134
- onSelect: (id) => {
135
- setOpen(false);
136
- setShell(id);
137
- },
138
- align: "end",
139
- portal: true,
140
- anchor: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
141
- "button",
142
- {
143
- type: "button",
144
- className: "btSelector",
145
- "aria-haspopup": "menu",
146
- "aria-expanded": open,
147
- disabled: !writable,
148
- onClick: () => setOpen(!open),
149
- style: !writable ? { opacity: 0.5, cursor: "not-allowed" } : void 0,
150
- children: [
151
- t("shell." + shell),
152
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.IconChevronDownOutlineMedium, { className: "btChevron" })
153
- ]
154
- }
155
- )
168
+ className: "btSelector",
169
+ value: shell,
170
+ onChange: (e) => change(e.target.value),
171
+ children: SHELLS.map((id) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: id, children: zh["shell." + id] ?? id }, id))
156
172
  }
157
173
  )
158
174
  ] });
159
175
  }
160
- var zhDictTitle = () => zh["shell.title"] ?? "\u7EC8\u7AEF";
176
+ function SettingsOverlayEntry() {
177
+ const [open, setOpen] = (0, import_react.useState)(false);
178
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { position: "fixed", right: "16px", bottom: "110px", zIndex: 1e4 }, children: [
179
+ open ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "btOverlay", onClick: () => setOpen(false) }) : null,
180
+ open ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "btPanel", children: [
181
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
182
+ "button",
183
+ {
184
+ type: "button",
185
+ className: "btSelector",
186
+ style: { float: "right", height: "30px", fontSize: "12px", marginBottom: "8px" },
187
+ onClick: () => setOpen(false),
188
+ children: "\u2715"
189
+ }
190
+ ),
191
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ShellPreferenceRow, {})
192
+ ] }) : null,
193
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "btGear", title: zhDictTitle(), onClick: () => setOpen(!open), children: "\u2699" })
194
+ ] });
195
+ }
161
196
  function apply(ctx) {
162
- ctx.effect(() => ctx.locale.register(SETTINGS_NS, { zh, en, fr, de, it, ru, es }), "bash-terminal: settings dictionaries");
163
- const scope = ctx.configForms.get(ENTRY_ID);
164
- const store = (0, import_dsh_client_store.defineStore)({
165
- init: () => ({ shell: "powershell", revision: -1, writable: false, status: "loading" }),
166
- actions: {
167
- sync: (d, shell, revision, writable, status) => {
168
- if (revision !== void 0 && revision <= d.revision) return;
169
- if (shell !== void 0) d.shell = shell;
170
- if (revision !== void 0) d.revision = revision;
171
- if (writable !== void 0) d.writable = writable;
172
- if (status !== void 0) d.status = status;
173
- }
197
+ const safe = (tag, fn) => {
198
+ try {
199
+ fn();
200
+ } catch (e) {
201
+ console.warn(`[dsh-bash-terminal-ts] client surface '${tag}' failed:`, e);
174
202
  }
175
- });
176
- let bound;
177
- const push = (snap) => {
178
- bound?.sync(snap.value?.defaultShell, snap.revision, snap.writable, snap.status);
179
203
  };
180
- ctx.slots.inject(
181
- "dsh-family.tab",
182
- () => ctx.slots.register(
183
- {
184
- name: "dsh-family.tab",
185
- id: "bash-terminal-shell",
186
- order: 60,
187
- label: () => zhDictTitle(),
188
- store,
189
- locale: SETTINGS_NS,
190
- inject: (actions) => {
191
- bound = actions;
192
- push(scope.getSnapshot());
193
- return { setShell: (value) => void scope.set("defaultShell", value) };
194
- }
195
- },
196
- ShellPreferenceRow
197
- ),
198
- "bash-terminal: settings row"
199
- );
200
- ctx.effect(() => scope.subscribe(() => push(scope.getSnapshot())), "bash-terminal: settings watch");
204
+ safe("locale", () => {
205
+ ctx.effect(
206
+ () => ctx.locale.register(SETTINGS_NS, { zh, en, fr, de, it, ru, es }),
207
+ "bash-terminal: settings dictionaries"
208
+ );
209
+ });
210
+ safe("dsh-family.tab", () => {
211
+ ctx.slots.inject(
212
+ "dsh-family.tab",
213
+ () => ctx.slots.register(
214
+ {
215
+ name: "dsh-family.tab",
216
+ id: "bash-terminal-shell",
217
+ order: 60,
218
+ label: zhDictTitle,
219
+ locale: SETTINGS_NS,
220
+ inject: () => ({})
221
+ },
222
+ () => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ShellPreferenceRow, {})
223
+ ),
224
+ "bash-terminal: settings row"
225
+ );
226
+ });
227
+ safe("shell.overlay", () => {
228
+ ctx.slots.inject(
229
+ "shell.overlay",
230
+ () => ctx.slots.register({ name: "shell.overlay", id: "bash-terminal-settings" }, () => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SettingsOverlayEntry, {}))
231
+ );
232
+ });
201
233
  }
202
234
 
203
235
  return module.exports;
package/lib/index.js CHANGED
@@ -27,6 +27,8 @@ import { ESCALATION_TARGETS, TOOL_ABORTED, approveEscalation, defineTool, escala
27
27
  import { createTerminalRegistry, terminalTool } from "./terminal.js";
28
28
  /** Stable Cordis plugin name. */
29
29
  export const name = "bash-terminal";
30
+ /** Composition entry id (the `tool-bash-terminal` row the bundle patch declares). */
31
+ const ENTRY_ID = "tool-bash-terminal";
30
32
  /** Services required before the tool can register. */
31
33
  export const inject = ["tools", "systemPrompt", "shellEnv", "subprocess", "settings", "sandbox", "sandboxPolicy"];
32
34
  /** The terminal backends this tool exposes, in catalog order. */
@@ -501,7 +503,13 @@ export function apply(ctx, config = {}) {
501
503
  // 0.1.7: `defaultShell` is a `.volatile()` config field — the loader hands
502
504
  // `apply` a live ref and the latest value is resolved on every read; there
503
505
  // is no settings registration call anymore (removed with the 0.1.7 host).
506
+ // compat-legacy: ≤0.1.6 的宿主没有配置热重载,bridge 写入走这个覆盖值
507
+ // (rc.1+ 上 volatile 与覆盖值同源同写,二者一致)。
508
+ let shellOverride;
504
509
  const currentShell = () => {
510
+ if (typeof shellOverride === "string" && SHELLS.includes(shellOverride)) {
511
+ return shellOverride;
512
+ }
505
513
  const v = config.defaultShell;
506
514
  if (typeof v === "string")
507
515
  return v;
@@ -533,6 +541,99 @@ export function apply(ctx, config = {}) {
533
541
  order: 105,
534
542
  text: "Use the shell tool for terminal commands: it runs in the terminal the user chose in Settings -> General -> Default terminal (PowerShell, Git Bash, MSYS2, or WSL) and honors the DSH sandbox. Prefer it over the pwsh tool for everyday commands; keep the pwsh tool for cases that specifically need the sandboxed PowerShell surface."
535
543
  });
544
+ // compat-legacy: 六线统一配置通道(≤0.1.6 无 client-store 模块表,client 走本
545
+ // bridge 读写 defaultShell)。写路径 = settings.mutate;≤0.1.6 的 loader 不会自动
546
+ // 注册命名空间,复用 perm-gate 自门控(not registered → register → 重试);
547
+ // rc.1+ 命名空间由 loader 占用,该分支自然不命中。GET 带回当前值与可选集。
548
+ // 宿主 context 类型未收录 inject/局部服务形状,compat 桥按宽化面调用。
549
+ const compatCtx = ctx;
550
+ compatCtx.inject(["webServer", "settings"], (svcCtx) => {
551
+ const svc = svcCtx.settings;
552
+ const writeShell = async (value) => {
553
+ if (typeof svc.mutate !== "function") {
554
+ // 显式降级:该线 settings 服务无 mutate(理论仅最老线),覆盖值仅本进程
555
+ // 生效;持久化请改 cordis.patch.yml 的 defaultShell。
556
+ throw new Error("settings service exposes no mutate on this host line - defaultShell applies for this process only; persist via cordis.patch.yml");
557
+ }
558
+ shellOverride = value;
559
+ try {
560
+ await svc.mutate(ENTRY_ID, [{ op: "set", path: ["defaultShell"], value }]);
561
+ }
562
+ catch (e) {
563
+ const message = e instanceof Error ? e.message : String(e);
564
+ if (!/not registered/i.test(message) || typeof svc.register !== "function") {
565
+ throw e;
566
+ }
567
+ console.warn(`[dsh-bash-terminal-ts] legacy line: registering settings namespace ${ENTRY_ID}`);
568
+ svc.register(ENTRY_ID, Config, { base: { defaultShell: value } });
569
+ await svc.mutate(ENTRY_ID, [{ op: "set", path: ["defaultShell"], value }]);
570
+ console.warn("[dsh-bash-terminal-ts] legacy settings namespace registered");
571
+ }
572
+ };
573
+ svcCtx.effect(() => {
574
+ const json = (res, status, body) => {
575
+ res.writeHead(status, { "content-type": "application/json; charset=utf-8" });
576
+ res.end(JSON.stringify(body));
577
+ };
578
+ const handler = (req, res) => {
579
+ const addr = req.socket?.remoteAddress ?? "";
580
+ if (addr !== "127.0.0.1" && addr !== "::1" && addr !== "::ffff:127.0.0.1") {
581
+ json(res, 403, { ok: false, code: "loopback-only" });
582
+ return;
583
+ }
584
+ if (req.method === "GET") {
585
+ // 缝内省:compat 验证用——当前线 ctx.subprocess 的真实能力面
586
+ const seam = ctx;
587
+ const sp = seam.subprocess;
588
+ json(res, 200, {
589
+ ok: true,
590
+ value: {
591
+ defaultShell: currentShell(),
592
+ shells: SHELLS,
593
+ seam: { spawn: typeof sp?.spawn, spawnTerminal: typeof sp?.spawnTerminal },
594
+ },
595
+ });
596
+ return;
597
+ }
598
+ if (req.method !== "POST") {
599
+ json(res, 405, { ok: false, code: "method-not-allowed" });
600
+ return;
601
+ }
602
+ const chunks = [];
603
+ let size = 0;
604
+ req.on("data", (c) => {
605
+ size += c.length;
606
+ if (size <= 65536)
607
+ chunks.push(c);
608
+ });
609
+ req.on("end", () => {
610
+ let body = {};
611
+ try {
612
+ body = JSON.parse(Buffer.concat(chunks).toString("utf8") || "{}");
613
+ }
614
+ catch {
615
+ json(res, 400, { ok: false, code: "bad-json" });
616
+ return;
617
+ }
618
+ const value = typeof body.defaultShell === "string" ? body.defaultShell : "";
619
+ if (!SHELLS.includes(value)) {
620
+ json(res, 400, { ok: false, code: "invalid-shell" });
621
+ return;
622
+ }
623
+ void writeShell(value)
624
+ .then(() => json(res, 200, { ok: true, value: { defaultShell: value } }))
625
+ .catch((e) => json(res, 500, { ok: false, code: "write-failed", message: e instanceof Error ? e.message : String(e) }));
626
+ });
627
+ };
628
+ const registered = [
629
+ svcCtx.webServer.register({ kind: "exact", path: "/api/bash-terminal/config", handler }),
630
+ ];
631
+ return () => {
632
+ for (const dispose of registered)
633
+ dispose();
634
+ };
635
+ }, "bash-terminal: compat config bridge");
636
+ });
536
637
  const toolName = "shell";
537
638
  const terminalRegistry = createTerminalRegistry(ctx);
538
639
  ctx.tools.register(terminalTool(ctx, terminalRegistry, paths, () => currentShell()));