dsh-code-server-app 0.3.65 → 0.3.66
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.en.md +47 -15
- package/README.md +44 -25
- package/cordis.patch.yml +7 -6
- package/lib/bridge-session.mjs +29 -9
- package/lib/client.js +660 -276
- package/lib/index.js +234 -112
- package/package.json +1 -1
- package/vendor/VENDOR.json +1 -1
package/lib/index.js
CHANGED
|
@@ -105,37 +105,60 @@ if (z === null || z === undefined) {
|
|
|
105
105
|
|
|
106
106
|
export const name = 'code-server';
|
|
107
107
|
// connection:client↔host 通道(web 走 webServer 的 /api,desktop 走 IPC 帧管道);
|
|
108
|
-
// settings
|
|
108
|
+
// settings:**设置的数据域**。两条线都有这个服务(所以它可以是硬依赖),但**模型不同**:
|
|
109
|
+
// · 旧线(rc 0.1.5-rc.x;也含"座位已搬到插件页、数据面还是旧的"0.1.6-alpha.2):
|
|
110
|
+
// `settings.register(ns, schema)` → `scope.get()/watch()`,值持久化在官方 settings 文档;
|
|
111
|
+
// · 新线(alpha ≥ 0.1.7-alpha.1):`register/get/watch` 已删除,配置就是**本插件条目自己的 Config**
|
|
112
|
+
// (可编辑字段必须 `.volatile()`),读值走 `config.<field>.get()` 活引用,变更通知走
|
|
113
|
+
// `settings/document-updated`。
|
|
114
|
+
// 两条线的选择在 apply 里**按能力探测**(`typeof settings.register === 'function'`),不比较版本号。
|
|
109
115
|
export const inject = ['connection', 'settings'];
|
|
110
116
|
|
|
111
|
-
/**
|
|
117
|
+
/** profile 条目 id(cordis.patch.yml 的 `- id: code-server`)。
|
|
118
|
+
* 新线里它同时也是**设置命名空间**:`ctx.configForms.get(entryId)` 与宿主的 settings.describe
|
|
119
|
+
* 都按条目 id 取用(表单只展示"活动且可唯一定位"的条目)。客户端半部的 ENTRY_ID 必须与此一致。 */
|
|
112
120
|
export const SETTINGS_NS = 'code-server';
|
|
113
121
|
|
|
114
|
-
/**
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
|
|
122
|
+
/**
|
|
123
|
+
* 可配置字段表(**唯一真源**):两条数据线共用同一份字段定义,只在"是否 volatile"上分叉。
|
|
124
|
+
*
|
|
125
|
+
* 为什么必须分叉(0.3.66 实测):
|
|
126
|
+
* · 新线(alpha ≥ 0.1.7-alpha.1)把插件配置模型换成"插件条目自己的 Config":
|
|
127
|
+
* `ctx.settings.register(ns, schema)` 已删除;宿主只把 **`.volatile()` 字段**当作该条目的可编辑表单
|
|
128
|
+
* (settings.describe 按 volatile 投影并明确报错 `Config field "x" is not volatile`),
|
|
129
|
+
* 且保存会**就地重解析**这个叶子(不重挂插件)⇒ 字段必须 volatile,读值必须走 config.<field>.get()。
|
|
130
|
+
* · 旧线要把同一份 schema 注册进官方 settings 域(`settings.register(SETTINGS_NS, schema)`),
|
|
131
|
+
* 而**那里不能用带 volatile 的 schema**:schemastery 3.18.3 起 volatile 是**解析期**行为
|
|
132
|
+
* (`createVolatile(value)` 在 schema 里完成),旧的 settings 域不认识它,会把活引用直接交给线路
|
|
133
|
+
* (JSON 化后是空对象 `{}`)⇒ 卡片读到的全是空值。所以旧线注册 `SettingsSchema`(同字段、无 volatile)。
|
|
134
|
+
* · rc 线的 schemastery 是 3.18.2,**根本没有 `.volatile()`**(3.18.3 才加)⇒ 无条件调用会在模块
|
|
135
|
+
* 求值期抛 `TypeError`、整棵插件树加载失败。故一律经 `vol()` 探测能力后再包。
|
|
136
|
+
*
|
|
137
|
+
* 旧设置文档里残留的 reserveComposer / windowedOpen / fileOpenScope 不再出现在 schema 里 ——
|
|
138
|
+
* schemastery 的 object 非 strict 会把未知键原样 merge 进解析结果,既不报错也不影响取值,故无需迁移。
|
|
139
|
+
*/
|
|
140
|
+
const SETTING_FIELDS = {
|
|
118
141
|
/** 服务方式:loopback(默认)独立回环端口;dsh 挂到 DSH webServer 的 /code-server
|
|
119
142
|
* (同源、无额外端口、复用 DSH 的 Host/Origin + cookie 防护;需要 DSH 提供 webServer 服务,
|
|
120
143
|
* 缺失或注册失败时自动回退 loopback)。没有卡片行:改 cordis.patch.yml 的 config.serve
|
|
121
|
-
*
|
|
122
|
-
serve: z.union([z.const('loopback'), z.const('dsh')]).default('loopback'),
|
|
144
|
+
* 或设置里的 code-server.serve。 */
|
|
145
|
+
serve: () => z.union([z.const('loopback'), z.const('dsh')]).default('loopback'),
|
|
123
146
|
/** keepResident=true(默认)客户端在宿主 running 后把 IDE 预先加载到"停放区",
|
|
124
147
|
* 切标签/收起侧栏不再重载;false 则只在打开面板时才加载(省内存)。 */
|
|
125
|
-
keepResident: z.boolean().default(true),
|
|
148
|
+
keepResident: () => z.boolean().default(true),
|
|
126
149
|
/** 认领类型(0.2.11):按扩展名决定哪些文件交给 VS Code 打开(分号分隔,`*` = 其余类型也认领,
|
|
127
150
|
* `!ext` = 不认领,排除优先)。**不再区分 session/absolute 作用域** —— 所有
|
|
128
151
|
* `dsh-resource://file/**` 地址一视同仁。语法、默认值与解析见 lib/claim-types.js。 */
|
|
129
|
-
claimExtensions: z.string().default(DEFAULT_CLAIM_EXTENSIONS),
|
|
152
|
+
claimExtensions: () => z.string().default(DEFAULT_CLAIM_EXTENSIONS),
|
|
130
153
|
/** fullscreenOnOpen=true(默认):打开 Code Server 标签(含点开产物/交付文件)时,
|
|
131
154
|
* 客户端把右侧栏切到全屏(铺满窗口);false 则保持 DSH 默认的 push(与对话并排)。
|
|
132
155
|
* 只影响"打开那一刻":之后用户点「退出全屏」不会被抢回去。 */
|
|
133
|
-
fullscreenOnOpen: z.boolean().default(true),
|
|
156
|
+
fullscreenOnOpen: () => z.boolean().default(true),
|
|
134
157
|
/** editorBridge=true(默认):启用「编辑器桥」(0.3.0)—— 树内扩展 dshcs-editor-bridge 与
|
|
135
158
|
* host 之间建立**只读**通道,给 agent 提供 only-the-editor-knows 的上下文(未保存缓冲区、
|
|
136
159
|
* 诊断、活动选区),并让编辑器里的动作能驱动 DSH。关闭后不写 bridge.json、不注册编辑器工具,
|
|
137
160
|
* 扩展会休眠(它读不到配置就不做任何事)。安全模型见 lib/bridge.mjs 顶部。 */
|
|
138
|
-
editorBridge: z.boolean().default(true),
|
|
161
|
+
editorBridge: () => z.boolean().default(true),
|
|
139
162
|
/** fim=false(默认):**实验性**的 FIM(幽灵)补全。开启后宿主的编辑器桥多一条**有界的模型调用**
|
|
140
163
|
* 路由(`POST /complete`),编辑器里的内联补全(灰字)由它供给;宿主为此注册一个自己的 LLM
|
|
141
164
|
* 适配器路由 `dshcs-fim`(走 Completions API 的 `/beta/completions`)。
|
|
@@ -144,18 +167,37 @@ export const Config = z.object({
|
|
|
144
167
|
* 不外发内容);②模型在"不该补的位置"会硬凑(实测 2/2 复现),需要编辑器侧判据兜;
|
|
145
168
|
* ③这条调用**不进 DSH 的 token 计量**(一次性调用不是 loop 请求,不写会话日志),
|
|
146
169
|
* 用量只在扩展状态栏与设置卡里可见。安全模型见 lib/fim-adapter.mjs 顶部。 */
|
|
147
|
-
fim: z.boolean().default(false),
|
|
170
|
+
fim: () => z.boolean().default(false),
|
|
148
171
|
/** 停顿多久才发一次补全请求(毫秒)。太小 ⇒ 打字过程中反复触发;太大 ⇒ 失去"补全"的意义。
|
|
149
172
|
* 取值在消费处夹到 [100, 3000](clampDebounce),设置里填什么都不至于打到链路上。 */
|
|
150
|
-
fimDebounceMs: z.number().default(250),
|
|
173
|
+
fimDebounceMs: () => z.number().default(250),
|
|
151
174
|
/** 是否允许多行补全(true 默认)。false ⇒ 宿主只回第一行(空首行 = 这次不补)。
|
|
152
175
|
* 关掉它是最省心的安全阀:实测模型在"不该补的位置"会硬凑,多行会把这种噪声放大。 */
|
|
153
|
-
fimMultiline: z.boolean().default(true),
|
|
176
|
+
fimMultiline: () => z.boolean().default(true),
|
|
154
177
|
/** 按 glob 禁用补全的文件(分号 / 逗号 / 空白 / 换行分隔;空 = 不禁用)。
|
|
155
178
|
* 语义:`*` 不跨目录、`**` 跨目录、不含 `/` 的模式只匹配文件名、`/` 结尾视作 `/**`。
|
|
156
179
|
* 两侧都会判:扩展侧先判(不发请求),宿主侧再判(防线在服务端)。 */
|
|
157
|
-
fimDisableGlobs: z.string().default(''),
|
|
158
|
-
}
|
|
180
|
+
fimDisableGlobs: () => z.string().default(''),
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
/** volatile 包装(**能力探测**,不看版本号):rc 线的 schemastery 3.18.2 没有这个方法。 */
|
|
184
|
+
const vol = (schema) => (typeof schema.volatile === 'function' ? schema.volatile() : schema);
|
|
185
|
+
|
|
186
|
+
/** 用同一份字段表建一条 schema;`wrap` 决定字段是否 volatile。 */
|
|
187
|
+
function settingShape(wrap) {
|
|
188
|
+
const shape = {};
|
|
189
|
+
for (const [key, make] of Object.entries(SETTING_FIELDS)) shape[key] = wrap(make());
|
|
190
|
+
return shape;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/** **本插件条目的 Config**(新线:alpha ≥ 0.1.7-alpha.1 的配置模型)。
|
|
194
|
+
* 字段全部 volatile:这是"宿主把该条目暴露成可编辑表单 + 保存就地重解析"的前提。 */
|
|
195
|
+
export const Config = z.object(settingShape(vol));
|
|
196
|
+
|
|
197
|
+
/** **旧线注册用 schema**(rc 0.1.5-rc.x 与 0.1.6-alpha.2 的 `settings.register`):
|
|
198
|
+
* 同字段、**不带 volatile** —— 原因见 SETTING_FIELDS 上方那段(schemastery 的 volatile 是解析期行为,
|
|
199
|
+
* 旧的 settings 域会把活引用交给线路,卡片会读到空对象)。 */
|
|
200
|
+
export const SettingsSchema = z.object(settingShape((schema) => schema));
|
|
159
201
|
|
|
160
202
|
const DEFAULT_CONFIG = {
|
|
161
203
|
bin: '',
|
|
@@ -663,8 +705,49 @@ let activeBridgeDispatch = null;
|
|
|
663
705
|
export async function apply(ctx, config) {
|
|
664
706
|
const cfg = { ...DEFAULT_CONFIG, ...(config ?? {}) };
|
|
665
707
|
|
|
666
|
-
// ----
|
|
708
|
+
// ---- 设置:两条数据线(见文件头与 SETTING_FIELDS 的说明) ----
|
|
709
|
+
// 判据是**能力**不是版本号:旧线的 settings 服务有 register(rc 0.1.5-rc.x,以及数据面还没换的
|
|
710
|
+
// 0.1.6-alpha.2);新线(alpha ≥ 0.1.7-alpha.1)删掉了它,配置就是本条目自己的 volatile Config。
|
|
667
711
|
const settingsSvc = ctx.get('settings');
|
|
712
|
+
const legacySettings = settingsSvc !== undefined && settingsSvc !== null
|
|
713
|
+
&& typeof settingsSvc.register === 'function';
|
|
714
|
+
|
|
715
|
+
/**
|
|
716
|
+
* 读一个配置字段的**当前值**。
|
|
717
|
+
*
|
|
718
|
+
* 新线:宿主编译期把 volatile 叶子换成**活引用**(`{ get() }`,见 cosmokit 的 createVolatile),
|
|
719
|
+
* 保存(表单 mutate → profile patch → loader 重解析)只就地更新这个叶子、**不重挂插件**,
|
|
720
|
+
* 所以每次都现读引用才是"实时值"。旧线(以及 rc 线 3.18.2 的 schemastery,它没有 volatile)
|
|
721
|
+
* 上 config.<field> 就是普通值 —— 同一个读法两条线都对。
|
|
722
|
+
*/
|
|
723
|
+
const cfgValue = (field) => {
|
|
724
|
+
const leaf = config === null || config === undefined ? undefined : config[field];
|
|
725
|
+
if (leaf !== null && typeof leaf === 'object' && typeof leaf.get === 'function') return leaf.get();
|
|
726
|
+
return leaf;
|
|
727
|
+
};
|
|
728
|
+
/** 有效值 = volatile 活叶子 / 行配置 config > 代码内默认(DEFAULT_CONFIG)。 */
|
|
729
|
+
const readCfg = (field) => {
|
|
730
|
+
const live = cfgValue(field);
|
|
731
|
+
return live === undefined ? cfg[field] : live;
|
|
732
|
+
};
|
|
733
|
+
/**
|
|
734
|
+
* 从**当前配置**拼出与旧线 `scope.get()` 同形的一份快照,交给同一个 commit 函数。
|
|
735
|
+
* 默认值就地取 DEFAULT_CONFIG / normalize,与旧线 register 分支里的兜底逐项对齐。
|
|
736
|
+
*/
|
|
737
|
+
const readSettingsSnapshot = () => ({
|
|
738
|
+
serve: readCfg('serve') === 'dsh' ? 'dsh' : 'loopback',
|
|
739
|
+
keepResident: typeof readCfg('keepResident') === 'boolean' ? readCfg('keepResident') : true,
|
|
740
|
+
claimExtensions: typeof readCfg('claimExtensions') === 'string'
|
|
741
|
+
? normalizeClaimExtensions(readCfg('claimExtensions'))
|
|
742
|
+
: DEFAULT_CLAIM_EXTENSIONS,
|
|
743
|
+
fullscreenOnOpen: typeof readCfg('fullscreenOnOpen') === 'boolean' ? readCfg('fullscreenOnOpen') : true,
|
|
744
|
+
editorBridge: readCfg('editorBridge') !== false,
|
|
745
|
+
fim: readCfg('fim') === true,
|
|
746
|
+
fimDebounceMs: clampDebounce(readCfg('fimDebounceMs')),
|
|
747
|
+
fimMultiline: readCfg('fimMultiline') !== false,
|
|
748
|
+
fimDisableGlobs: typeof readCfg('fimDisableGlobs') === 'string' ? readCfg('fimDisableGlobs') : '',
|
|
749
|
+
});
|
|
750
|
+
|
|
668
751
|
// connection 是 host↔client 通道(必需);提前取出,供 DSH 同源挂载的 fence 使用。
|
|
669
752
|
const connection = ctx.get('connection');
|
|
670
753
|
if (connection === undefined || connection.fetch === undefined) {
|
|
@@ -675,107 +758,146 @@ export async function apply(ctx, config) {
|
|
|
675
758
|
let keepResident = true; // 客户端常驻预热(0.2.2)
|
|
676
759
|
let claimExtensions = DEFAULT_CLAIM_EXTENSIONS; // 认领类型清单(0.2.11,取代 fileOpenScope)
|
|
677
760
|
let fullscreenOnOpen = true; // 客户端"打开标签即全屏右侧栏"(0.2.9)
|
|
678
|
-
/** 编辑器桥开关(0.3.0):行配置 cfg.editorBridge
|
|
679
|
-
let bridgeSetting =
|
|
680
|
-
/** FIM 补全开关(实验性,0.3.61):行配置 cfg.fim
|
|
681
|
-
let fimSetting =
|
|
761
|
+
/** 编辑器桥开关(0.3.0):行配置 cfg.editorBridge 是种子,设置里可改并实时生效。 */
|
|
762
|
+
let bridgeSetting = readCfg('editorBridge') !== false;
|
|
763
|
+
/** FIM 补全开关(实验性,0.3.61):行配置 cfg.fim 是种子,设置里可改并实时生效。默认关。 */
|
|
764
|
+
let fimSetting = readCfg('fim') === true;
|
|
682
765
|
/** FIM 的三个子项(0.3.62):停顿毫秒数 / 是否多行 / 按 glob 禁用(原始文本,消费时解析)。 */
|
|
683
|
-
let fimDebounceMs = clampDebounce(
|
|
684
|
-
let fimMultiline =
|
|
685
|
-
let fimDisableGlobs = typeof
|
|
686
|
-
|
|
766
|
+
let fimDebounceMs = clampDebounce(readCfg('fimDebounceMs'));
|
|
767
|
+
let fimMultiline = readCfg('fimMultiline') !== false;
|
|
768
|
+
let fimDisableGlobs = typeof readCfg('fimDisableGlobs') === 'string' ? readCfg('fimDisableGlobs') : '';
|
|
769
|
+
/**
|
|
770
|
+
* 把一份设置快照落到本地状态上,并在**值真的变了**时执行随动副作用。
|
|
771
|
+
*
|
|
772
|
+
* 两条线的触发点不同,但落到这里是同一段代码:
|
|
773
|
+
* · 旧线:`scope.watch(commitSettings)`(首次装配另走 initial=true,不触发副作用);
|
|
774
|
+
* · 新线:`ctx.on('settings/document-updated', …)` → readSettingsSnapshot() → 这里。
|
|
775
|
+
* 副作用的清单(与 0.3.0 起的语义逐条一致):编辑器桥上下线、FIM 适配器注册/注销、
|
|
776
|
+
* FIM 三个子项即时生效、状态里的四元组(serve/keepResident/claimExtensions/fullscreenOnOpen)。
|
|
777
|
+
*/
|
|
778
|
+
const commitSettings = (next, opts) => {
|
|
779
|
+
const initial = opts !== undefined && opts.initial === true;
|
|
780
|
+
if (next != null && (next.serve === 'dsh' || next.serve === 'loopback')) {
|
|
781
|
+
if (next.serve !== serveSetting) {
|
|
782
|
+
serveSetting = next.serve;
|
|
783
|
+
console.log(`[code-server] serve updated: ${serveSetting}(下次启动生效)`);
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
if (next != null && typeof next.editorBridge === 'boolean') {
|
|
787
|
+
if (next.editorBridge !== bridgeSetting) {
|
|
788
|
+
bridgeSetting = next.editorBridge;
|
|
789
|
+
console.log(`[code-server] editorBridge updated: ${bridgeSetting}`);
|
|
790
|
+
// 关掉 → 立刻下线(关监听口 + 删配置 + 注销工具);开启 → 起监听口,IDE 在跑就补一份配置。
|
|
791
|
+
// 首次装配(initial)不做这件事:桥的启动由末尾那个 ctx.effect 负责,重复做会白起一次监听口。
|
|
792
|
+
if (initial !== true) {
|
|
793
|
+
if (bridgeSetting === false) {
|
|
794
|
+
clearBridgeRuntime();
|
|
795
|
+
void stopBridgeListener();
|
|
796
|
+
} else {
|
|
797
|
+
void ensureBridgeListener().then(() => {
|
|
798
|
+
if (state.status === 'running' && state.pid !== null) {
|
|
799
|
+
bridgeToken ??= mintBridgeToken();
|
|
800
|
+
syncBridgeRuntime({ pid: state.pid, startedAt: state.startedAt });
|
|
801
|
+
}
|
|
802
|
+
});
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
// FIM(实验性):开 → 注册我们自己的 LLM 适配器路由(编辑器补全要靠它);
|
|
808
|
+
// 关 → 注销。翻转立刻生效,不需要重启宿主(扩展侧下一趟 /sync 就会看到 enabled 变了)。
|
|
809
|
+
if (next != null && typeof next.fim === 'boolean') {
|
|
810
|
+
if (next.fim !== fimSetting) {
|
|
811
|
+
fimSetting = next.fim;
|
|
812
|
+
console.log(`[code-server] fim updated: ${fimSetting}(实验性 FIM 补全)`);
|
|
813
|
+
if (initial !== true) void ensureFimAdapter();
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
// FIM 的三个子项(0.3.62):都**即时生效**,不需要重启宿主 —— 扩展每趟 /sync 都会拿到新值
|
|
817
|
+
// (停顿与 glob 是扩展侧行为,多行由宿主在返回前裁)。改动只打一行日志,便于事后对齐现场。
|
|
818
|
+
if (next != null && next.fimDebounceMs !== undefined) {
|
|
819
|
+
const clamped = clampDebounce(next.fimDebounceMs);
|
|
820
|
+
if (clamped !== fimDebounceMs) {
|
|
821
|
+
fimDebounceMs = clamped;
|
|
822
|
+
console.log(`[code-server] fimDebounceMs updated: ${fimDebounceMs}ms`);
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
if (next != null && typeof next.fimMultiline === 'boolean') {
|
|
826
|
+
if (next.fimMultiline !== fimMultiline) {
|
|
827
|
+
fimMultiline = next.fimMultiline;
|
|
828
|
+
console.log(`[code-server] fimMultiline updated: ${fimMultiline}`);
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
if (next != null && typeof next.fimDisableGlobs === 'string') {
|
|
832
|
+
if (next.fimDisableGlobs !== fimDisableGlobs) {
|
|
833
|
+
fimDisableGlobs = next.fimDisableGlobs;
|
|
834
|
+
console.log(`[code-server] fimDisableGlobs updated: ${compileGlobList(fimDisableGlobs).length} 条`);
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
if (next != null && typeof next.keepResident === 'boolean') {
|
|
838
|
+
if (next.keepResident !== keepResident) console.log(`[code-server] keepResident updated: ${next.keepResident}`);
|
|
839
|
+
keepResident = next.keepResident;
|
|
840
|
+
}
|
|
841
|
+
if (next != null && typeof next.claimExtensions === 'string') {
|
|
842
|
+
const normalized = normalizeClaimExtensions(next.claimExtensions);
|
|
843
|
+
if (normalized !== claimExtensions) {
|
|
844
|
+
claimExtensions = normalized;
|
|
845
|
+
console.log(`[code-server] claimExtensions updated: ${claimExtensions} (${describeClaimPolicy(claimExtensions)})`);
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
if (next != null && typeof next.fullscreenOnOpen === 'boolean') {
|
|
849
|
+
if (next.fullscreenOnOpen !== fullscreenOnOpen) {
|
|
850
|
+
fullscreenOnOpen = next.fullscreenOnOpen;
|
|
851
|
+
console.log(`[code-server] fullscreenOnOpen updated: ${fullscreenOnOpen}`);
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
};
|
|
855
|
+
|
|
856
|
+
if (legacySettings) {
|
|
857
|
+
// ---- 旧线:settings 命名空间(rc 0.1.5-rc.x;也含 0.1.6-alpha.2) ----
|
|
687
858
|
try {
|
|
688
|
-
|
|
859
|
+
// 注册**不带 volatile** 的 SettingsSchema:旧的 settings 域会把 volatile 活引用直接交给线路。
|
|
860
|
+
const scope = settingsSvc.register(SETTINGS_NS, SettingsSchema);
|
|
689
861
|
const rawDoc = settingsSvc.get(SETTINGS_NS);
|
|
690
862
|
if (rawDoc !== undefined && rawDoc !== null) {
|
|
691
|
-
|
|
692
|
-
serveSetting = resolved && resolved.serve === 'dsh' ? 'dsh' : 'loopback';
|
|
693
|
-
keepResident = resolved && typeof resolved.keepResident === 'boolean' ? resolved.keepResident : true;
|
|
694
|
-
claimExtensions = resolved && typeof resolved.claimExtensions === 'string'
|
|
695
|
-
? normalizeClaimExtensions(resolved.claimExtensions)
|
|
696
|
-
: DEFAULT_CLAIM_EXTENSIONS;
|
|
697
|
-
fullscreenOnOpen = resolved && typeof resolved.fullscreenOnOpen === 'boolean' ? resolved.fullscreenOnOpen : true;
|
|
698
|
-
bridgeSetting = resolved && typeof resolved.editorBridge === 'boolean' ? resolved.editorBridge : bridgeSetting;
|
|
699
|
-
fimSetting = resolved && typeof resolved.fim === 'boolean' ? resolved.fim : fimSetting;
|
|
700
|
-
fimDebounceMs = clampDebounce(resolved && resolved.fimDebounceMs !== undefined ? resolved.fimDebounceMs : fimDebounceMs);
|
|
701
|
-
fimMultiline = resolved && typeof resolved.fimMultiline === 'boolean' ? resolved.fimMultiline : fimMultiline;
|
|
702
|
-
fimDisableGlobs = resolved && typeof resolved.fimDisableGlobs === 'string' ? resolved.fimDisableGlobs : fimDisableGlobs;
|
|
863
|
+
commitSettings(scope.get(), { initial: true });
|
|
703
864
|
}
|
|
704
|
-
scope.watch((next) => {
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
void ensureFimAdapter();
|
|
736
|
-
}
|
|
737
|
-
}
|
|
738
|
-
// FIM 的三个子项(0.3.62):都**即时生效**,不需要重启宿主 —— 扩展每趟 /sync 都会拿到新值
|
|
739
|
-
// (停顿与 glob 是扩展侧行为,多行由宿主在返回前裁)。改动只打一行日志,便于事后对齐现场。
|
|
740
|
-
if (next != null && next.fimDebounceMs !== undefined) {
|
|
741
|
-
const clamped = clampDebounce(next.fimDebounceMs);
|
|
742
|
-
if (clamped !== fimDebounceMs) {
|
|
743
|
-
fimDebounceMs = clamped;
|
|
744
|
-
console.log(`[code-server] fimDebounceMs updated: ${fimDebounceMs}ms`);
|
|
745
|
-
}
|
|
746
|
-
}
|
|
747
|
-
if (next != null && typeof next.fimMultiline === 'boolean') {
|
|
748
|
-
if (next.fimMultiline !== fimMultiline) {
|
|
749
|
-
fimMultiline = next.fimMultiline;
|
|
750
|
-
console.log(`[code-server] fimMultiline updated: ${fimMultiline}`);
|
|
751
|
-
}
|
|
752
|
-
}
|
|
753
|
-
if (next != null && typeof next.fimDisableGlobs === 'string') {
|
|
754
|
-
if (next.fimDisableGlobs !== fimDisableGlobs) {
|
|
755
|
-
fimDisableGlobs = next.fimDisableGlobs;
|
|
756
|
-
console.log(`[code-server] fimDisableGlobs updated: ${compileGlobList(fimDisableGlobs).length} 条`);
|
|
757
|
-
}
|
|
758
|
-
}
|
|
759
|
-
if (next != null && typeof next.keepResident === 'boolean') {
|
|
760
|
-
keepResident = next.keepResident;
|
|
761
|
-
console.log(`[code-server] keepResident updated: ${keepResident}`);
|
|
762
|
-
}
|
|
763
|
-
if (next != null && typeof next.claimExtensions === 'string') {
|
|
764
|
-
const normalized = normalizeClaimExtensions(next.claimExtensions);
|
|
765
|
-
if (normalized !== claimExtensions) {
|
|
766
|
-
claimExtensions = normalized;
|
|
767
|
-
console.log(`[code-server] claimExtensions updated: ${claimExtensions} (${describeClaimPolicy(claimExtensions)})`);
|
|
768
|
-
}
|
|
769
|
-
}
|
|
770
|
-
if (next != null && typeof next.fullscreenOnOpen === 'boolean') {
|
|
771
|
-
if (next.fullscreenOnOpen !== fullscreenOnOpen) {
|
|
772
|
-
fullscreenOnOpen = next.fullscreenOnOpen;
|
|
773
|
-
console.log(`[code-server] fullscreenOnOpen updated: ${fullscreenOnOpen}`);
|
|
865
|
+
scope.watch((next) => { commitSettings(next); });
|
|
866
|
+
} catch (error) {
|
|
867
|
+
console.error(`[code-server] settings unavailable; using defaults (claimExtensions=${DEFAULT_CLAIM_EXTENSIONS}, fullscreenOnOpen=true, keepResident=true): ${error.message}`);
|
|
868
|
+
}
|
|
869
|
+
} else {
|
|
870
|
+
// ---- 新线:本条目自己的 volatile Config(alpha ≥ 0.1.7-alpha.1) ----
|
|
871
|
+
// 先按活叶子装一遍初值(不触发副作用),再把后续变更挂到 settings 的文档事件上:
|
|
872
|
+
// volatile 叶子的就地重解析不重挂插件,所以随动必须由事件驱动。
|
|
873
|
+
commitSettings(readSettingsSnapshot(), { initial: true });
|
|
874
|
+
ctx.on('settings/document-updated', () => { commitSettings(readSettingsSnapshot()); });
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
// ---- 自带配置页的声明(0.3.66) ----
|
|
878
|
+
// 本插件的设置面是客户端半部自绘的表单(插件页的 `plugins.bundle.config`),不是按 schema 自动生成的页面。
|
|
879
|
+
// 上游要求:这类插件在 apply 中于**可选的** `ctx.inject(['settings'], …)` 子级内以 effect 注册
|
|
880
|
+
// `configure({ auto: false }, ctx.fiber)` —— 子级指明策略所属的插件 fiber(迟加载/被替换的 Settings
|
|
881
|
+
// 服务也会采用该策略),而"业务插件无需 Settings 即可运行"这条性质靠可选的 inject 保住。
|
|
882
|
+
// 策略只影响"要不要自动生成页面",不移除配置读写(表单/描述/mutate 仍照常)。
|
|
883
|
+
if (typeof ctx.inject === 'function') {
|
|
884
|
+
try {
|
|
885
|
+
ctx.inject(['settings'], (child) => {
|
|
886
|
+
const svc = typeof child.get === 'function' ? child.get('settings') : child.settings;
|
|
887
|
+
if (svc === undefined || svc === null || typeof svc.configure !== 'function') return; // 旧线没有这个能力
|
|
888
|
+
child.effect(() => {
|
|
889
|
+
try {
|
|
890
|
+
return svc.configure({ auto: false }, ctx.fiber);
|
|
891
|
+
} catch (error) {
|
|
892
|
+
// 同一个 fiber 注册两次会抛(Settings presentation is already configured for this plugin
|
|
893
|
+
// instance)。热重载下 apply 可能重跑,而"没有自动页面"只是外观问题 ⇒ 记一条日志就够。
|
|
894
|
+
console.warn(`[code-server] settings presentation 未注册(不影响配置读写):${error && error.message ? error.message : error}`);
|
|
895
|
+
return () => {};
|
|
774
896
|
}
|
|
775
|
-
}
|
|
897
|
+
}, 'code-server: settings presentation (auto: false)');
|
|
776
898
|
});
|
|
777
899
|
} catch (error) {
|
|
778
|
-
console.
|
|
900
|
+
console.warn(`[code-server] settings presentation 未注册(不影响配置读写):${error && error.message ? error.message : error}`);
|
|
779
901
|
}
|
|
780
902
|
}
|
|
781
903
|
|
|
@@ -789,7 +911,7 @@ export async function apply(ctx, config) {
|
|
|
789
911
|
|
|
790
912
|
/** 生效的服务方式:loopback(默认,独立回环端口)| dsh(DSH 同源挂载)。 */
|
|
791
913
|
function requestedServe() {
|
|
792
|
-
const want = serveSetting === 'dsh' ||
|
|
914
|
+
const want = serveSetting === 'dsh' || readCfg('serve') === 'dsh' ? 'dsh' : 'loopback';
|
|
793
915
|
if (want === 'loopback') return 'loopback';
|
|
794
916
|
if (webServerSvc === undefined || dshMount === null) {
|
|
795
917
|
console.warn('[code-server] serve=dsh 但 webServer 不可用/挂载失败 → 回退 loopback');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-code-server-app",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.66",
|
|
4
4
|
"description": "VS Code (from a code-server release) inside DSH: a right-sidebar tab driven by the plugin's own launcher over the in-process VS Code server (lib/launcher.mjs), plus an editor bridge (assets/extensions/dshcs-editor-bridge) that gives the agent what only the editor knows (unsaved buffers, language-server diagnostics, the active selection) and lets editor gestures drive the session. The bridge is completely read-only: all four of its routes (/health, /sync, /old, /event) are reads. Asking DSH from the editor opens a floating dialog inside the DSH page (draggable, resizable, bottom-right): the plugin's client half sends the question as user input (POST /api/code-server/ask/send) and renders that session's new content with DSH's own Markdown renderer - the same @deepseek-ai/dsh-client-ui-primitives instance the UI uses, required from the page's module table, so typography, highlighting and math match the interface and a renderer version mismatch is impossible. Thinking appears as collapsed rows and approval requests (writing outside the workspace / running commands) can be answered in place: whole-plugin write route POST /api/code-server/ask/approve, constrained to pending in-process requests and the outcomes allowed-once/rejected; dialog-first for 5 minutes, handed back to the official card the moment the dialog closes, never auto-approved. There is no build step anywhere: lib/client.js (client half including the dialog panel) and the extension are committed hand-written source, devDependencies holds a single test-only entry, and the published package carries no frontend bundle. Also: the tab claims DSH file addresses (dsh-resource://file/**) by file type (setting claimExtensions), so produced-file chips, delivered-file previews and inline prose mentions open in the workbench; the IDE is a resident surface moved with Element.moveBefore instead of being remounted, so switching sidebar tabs does not reload it; opening the tab switches the right sidebar to fullscreen by default (setting fullscreenOnOpen); a workspace switch re-navigates the workbench with the new ?folder= without restarting the IDE process. Requires a DSH with the right-sidebar services (sidebarRightTabs/sidebarRight, >= 0.1.5-alpha.1); older DSH versions only get an upgrade notice on the settings page. Two serving modes: loopback port (default) or same-origin mount on DSH's own webServer (/code-server, protected by ctx.connection.requestRejection). The diff the bridge opens always has the real pre-write text on the left, taken from result.value.before in tools/post-execute into a bounded snapshot cache and fetched by opaque key over the read-only GET /old, so files that are not open in the editor work too. No code-server Node layer, no argon2, no C++ toolchain.",
|
|
5
5
|
"homepage": "https://github.com/jinsiyu/dsh-code-server-app",
|
|
6
6
|
"repository": {
|
package/vendor/VENDOR.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"vscodeVersion": "1.138.0",
|
|
4
4
|
"productPath": "stable-59c988c744a240b05b039f57b856a5312f19d5b1",
|
|
5
5
|
"layout": "vscode-only",
|
|
6
|
-
"preparedAt": "2026-09-
|
|
6
|
+
"preparedAt": "2026-09-22T13:10:27.713Z",
|
|
7
7
|
"source": "registry",
|
|
8
8
|
"node": "v24.20.0",
|
|
9
9
|
"platform": "linux",
|