@jesonliu/lark-claudecode-bridge 1.3.1 → 1.3.3

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.
@@ -304,6 +304,7 @@ tr.empty-row td { text-align: center; padding: 22px 10px; color: var(--muted); }
304
304
  .tag {
305
305
  display: inline-flex; align-items: center; gap: 5px;
306
306
  padding: 2px 10px; border-radius: 999px; font-size: 11.5px; font-weight: 500;
307
+ white-space: nowrap; /* 状态标签(如「未随本页启动」)不折行 */
307
308
  }
308
309
  .tag::before { content: ''; width: 6px; height: 6px; border-radius: 50%; background: currentColor; flex: 0 0 auto; }
309
310
  .tag.ok { background: var(--ok-bg); color: var(--ok-tx); }
@@ -458,8 +459,8 @@ body.drawer-resizing { cursor: ew-resize; user-select: none; }
458
459
  .msel:focus-within { border-color: var(--primary); box-shadow: 0 0 0 3px var(--primary-ring); }
459
460
  .msel-chips { margin: 4px 0; }
460
461
  .msel-chips:empty { display: none; }
461
- .msel-input { border: 0; background: none; box-shadow: none; width: 100%; padding: 6px 2px; font-size: 13.5px; }
462
- .msel-input:focus { border: 0; box-shadow: none; } /* 覆盖全局 input:focus(焦点态由 .msel:focus-within 呈现) */
462
+ .msel .msel-input { border: 0; background: none; box-shadow: none; width: 100%; padding: 6px 2px; font-size: 13.5px; }
463
+ .msel .msel-input:focus { border: 0; box-shadow: none; } /* 覆盖全局 input:focus(焦点态由 .msel:focus-within 呈现);带 .msel 前缀提特异性,防被 input[type=text] (0,1,1) 压制 */
463
464
  .combo-item.active { background: var(--primary-weak); } /* 键盘高亮(:hover 已有) */
464
465
  .msel-check { float: right; color: var(--primary); font-weight: 650; }
465
466
  .chip.ghost { border-style: dashed; color: var(--muted); }
@@ -1,6 +1,6 @@
1
1
  // ============ 应用(列表 + 抽屉编辑;输入实时写回 S.doc.apps,抽屉「保存」一次落盘) ============
2
2
  import { S, $, esc, toast, api, snapDoc, saveDoc } from '../core.js';
3
- import { openDrawer, closeDrawer, cancelDrawer, confirmDialog, multiSelectField } from '../ui.js';
3
+ import { openDrawer, closeDrawer, cancelDrawer, confirmDialog, multiSelectField, singleSelectField } from '../ui.js';
4
4
 
5
5
  // ---- 分身白名单多选候选(/api/skills + /api/plugins;60s TTL + in-flight 去重 + stale-if-error) ----
6
6
  /** 纯函数(可测):技能清单 → 多选候选。插件来源 value 用 `plugin:skill` 限定格式
@@ -38,6 +38,20 @@ export function toPluginOptions(plugins) {
38
38
  return out;
39
39
  }
40
40
 
41
+ /** 纯函数(可测):档案 → 模型单选候选(models ∪ 默认 model 去重,默认模型带 tag 便于辨认)。
42
+ * 空档案(未配置任何模型)返回空数组,由组件展示 emptyText */
43
+ export function toProfileModelOptions(prof) {
44
+ if (!prof) return [];
45
+ const seen = new Set();
46
+ const models = [];
47
+ for (const m of [...(prof.models || []), ...(prof.model ? [prof.model] : [])]) {
48
+ if (seen.has(m)) continue;
49
+ seen.add(m);
50
+ models.push(m);
51
+ }
52
+ return models.map((m) => ({ value: m, tag: m === prof.model ? '默认' : '' }));
53
+ }
54
+
41
55
  const CAND = { at: 0, skills: null, plugins: null, skillsError: '', pluginsError: '', inflight: null };
42
56
  const CAND_TTL = 60_000;
43
57
  /** 拉候选(失败时保留旧缓存继续用;全新失败返回 error 字段,组件空候选但已选值不受影响) */
@@ -63,15 +77,15 @@ function renderApps(el) {
63
77
  el.innerHTML = `
64
78
  <div class="card">
65
79
  <h3>飞书应用(机器人)</h3>
66
- <div class="desc">每个应用一条独立长连接与会话池。点击行或「编辑」在抽屉中配置;App Secret 已脱敏,留空 = 保持不变。凭证 / 名称 / 域名 / 默认工作区 / 并发 / 人格 / 角色改动需重启 lcb start;触发词、显式插件与环境变量改动下一条消息热生效。</div>
80
+ <div class="desc">每个应用一条独立长连接与会话池。点击行或「编辑」在抽屉中配置;App Secret 已脱敏,留空 = 保持不变。凭证 / 名称 / 域名 / 默认工作区 / 并发 / 人格 / 角色改动需重启 lcb start;触发词与环境变量改动下一条消息热生效。</div>
67
81
  <div class="list-toolbar">
68
82
  <button class="btn primary" id="addApp">+ 新增应用</button>
69
83
  </div>
70
84
  <table>
71
- <thead><tr><th>名称</th><th>App ID</th><th style="width:80px">域名</th><th>默认工作区</th><th style="width:110px">状态</th><th style="width:64px"></th></tr></thead>
85
+ <thead><tr><th>名称</th><th>App ID</th><th style="width:130px">域名</th><th>默认工作区</th><th style="width:130px">状态</th><th style="width:64px"></th></tr></thead>
72
86
  <tbody id="appsBody"></tbody>
73
87
  </table>
74
- <div class="footer-note">⚠️ 应用段改动需重启 lcb start 后生效(触发词 / 显式插件除外,下一条消息自动生效)。</div>
88
+ <div class="footer-note">⚠️ 应用段改动需重启 lcb start 后生效(触发词与环境变量除外,下一条消息自动生效)。</div>
75
89
  </div>`;
76
90
  const statusTag = (app) => {
77
91
  const st = S.status?.apps?.find((s) => s.name === (app.name || app.app_id))?.started;
@@ -79,15 +93,22 @@ function renderApps(el) {
79
93
  : st === false ? '<span class="tag err">启动失败</span>'
80
94
  : '<span class="tag off">未随本页启动</span>';
81
95
  };
82
- $('#appsBody').innerHTML = apps.map((app, i) => `
96
+ // 该应用实际启用的模型:profile_model 覆盖优先,其次档案默认 model(与后端 resolveAppProfileModel 展示口径一致)
97
+ const profiles = (S.doc.claude && S.doc.claude.profiles) || [];
98
+ const profileModelOf = (app) =>
99
+ app.profile_model || (profiles.find((p) => p.name === app.profile)?.model || '');
100
+ $('#appsBody').innerHTML = apps.map((app, i) => {
101
+ const model = profileModelOf(app);
102
+ return `
83
103
  <tr data-i="${i}" style="cursor:pointer">
84
- <td><b>${esc(app.name || app.app_id || '(未命名)')}</b>${app.role === 'deputy' ? ' <span class="tag off">分身</span>' : ''}${app.profile ? ` <span class="tag warn">${esc(app.profile)}</span>` : ''}</td>
104
+ <td><b>${esc(app.name || app.app_id || '(未命名)')}</b>${app.role === 'deputy' ? ' <span class="tag off">分身</span>' : ''}${app.profile ? ` <span class="tag warn">${esc(model ? `${app.profile} · ${model}` : app.profile)}</span>` : ''}</td>
85
105
  <td><code>${esc(app.app_id || '')}</code></td>
86
- <td>${esc(app.domain === 'lark' ? 'lark' : 'feishu')}</td>
106
+ <td>${esc(app.domain === 'lark' ? 'lark(国际)' : 'feishu(国内)')}</td>
87
107
  <td>${esc(app.default_workspace || '(全局默认)')}</td>
88
108
  <td>${statusTag(app)}</td>
89
109
  <td><button class="btn sm">编辑</button></td>
90
- </tr>`).join('');
110
+ </tr>`;
111
+ }).join('');
91
112
  $('#appsBody').querySelectorAll('tr').forEach((tr) =>
92
113
  tr.onclick = () => openAppDrawer(apps, Number(tr.dataset.i), el));
93
114
  $('#addApp').onclick = () => {
@@ -141,7 +162,7 @@ function openAppDrawer(apps, idx, el, snap) {
141
162
  <option value="primary" ${app.role !== 'deputy' ? 'selected' : ''}>主机器人(全权限,配对码准入)</option>
142
163
  <option value="deputy" ${app.role === 'deputy' ? 'selected' : ''}>分身机器人(限定技能 / 插件 / 工作区,艾特即用)</option>
143
164
  </select>
144
- <h4 style="margin:18px 0 2px;font-size:13.5px">厂商档案(该机器人专用的模型厂商;改动下一条消息热生效)</h4>
165
+ <h4 style="margin:18px 0 2px;font-size:13.5px">模型配置(该机器人专用的模型厂商与模型;改动下一条消息热生效)</h4>
145
166
  <div class="hint" style="margin-bottom:6px">未配置 = 跟随全局(「Claude 认证」页当前生效配置)。档案须先在「Claude 认证」页创建;认证经独立 settings 文件注入(优先级高于全局),模型经 --model 参数注入。</div>
146
167
  <div class="row" style="align-items:end">
147
168
  <div><label>档案</label>
@@ -150,8 +171,7 @@ function openAppDrawer(apps, idx, el, snap) {
150
171
  ${profiles.map((p) => `<option value="${esc(p.name)}" ${app.profile === p.name ? 'selected' : ''}>${esc(p.name)}</option>`).join('')}
151
172
  </select></div>
152
173
  <div id="profileModelBox" style="flex:1;${app.profile ? '' : 'display:none'}"><label>模型(从该档案候选中选择;留空 = 档案默认模型)</label>
153
- <input type="text" data-f="profile_model" list="profileModelList" value="${esc(app.profile_model || '')}" placeholder="留空 = 档案默认模型">
154
- <datalist id="profileModelList"></datalist></div>
174
+ <div id="profileModelMount"></div></div>
155
175
  </div>
156
176
  <div id="deputyFields" style="${app.role === 'deputy' ? '' : 'display:none'}">
157
177
  <div class="hint" style="margin:8px 0 2px">分身必填「允许的技能」与「允许的工作区」;未列入白名单的技能 / 插件不加载,任务锁定在第一个允许的工作区;管理命令在分身里一律不可用。改动需重启生效。</div>
@@ -168,12 +188,6 @@ function openAppDrawer(apps, idx, el, snap) {
168
188
  <div class="hint" style="margin-bottom:6px">match:以 <code>/</code> 开头 或 关键词 = 消息首词精确匹配;</br>rewrite:可用 <code>{text}</code>=原文全文、<code>{args}</code>=首词后的参数;</br>补充追问:命中先追问补充内容,你的下一条消息与原文合并后再改写入队(斜杠 / 等命令消息除外)。</br>PS. 可指向 bridge 本地命令(如 match=新会话、rewrite=/new,飞书菜单场景常用——bridge 本地执行,不透传给 Claude Code);本地命令原文(/stop 等)不被劫持;按序首个命中生效。</div>
169
189
  <table><thead><tr><th style="width:38%">match</th><th>rewrite</th><th style="width:76px" title="开启后命中先追问补充内容,下一条消息与原文合并再执行(两段式触发)">追问补充</th><th style="width:44px"></th></tr></thead><tbody data-list="triggers"></tbody></table>
170
190
  <button class="btn sm" data-add="triggers" style="margin-top:6px">+ 添加规则</button>
171
- <!-- 飞书应用级插件 -->
172
- <h4 style="margin:18px 0 2px;font-size:13.5px">显式插件(本地插件源码目录;同名优先于 ~/.claude 自动发现;热生效)</h4>
173
- <div class="hint" style="margin-bottom:6px">须为包含 <code>.claude-plugin/plugin.json</code> 的目录;一般用户无须配置,多用于开发期直指源码。</div>
174
- <table><thead><tr><th style="width:30%">name</th><th>path</th><th style="width:44px"></th></tr></thead><tbody data-list="plugins"></tbody></table>
175
- <button class="btn sm" data-add="plugins" style="margin-top:6px">+ 添加插件</button>
176
-
177
191
  <h4 style="margin:18px 0 2px;font-size:13.5px">环境变量(注入该机器人 Claude 子进程,覆盖本机同名值;改动需重启生效)</h4>
178
192
  <div class="hint" style="margin-bottom:6px">~/.claude/settings.json 已配置的键会被 CLI 自身应用且优先,无须在此重复。</div>
179
193
  <table><thead><tr><th style="width:38%">KEY</th><th>VALUE</th><th style="width:44px"></th></tr></thead><tbody data-list="env"></tbody></table>
@@ -209,18 +223,30 @@ function openAppDrawer(apps, idx, el, snap) {
209
223
  else delete app.role;
210
224
  deputyFields.style.display = roleSel.value === 'deputy' ? '' : 'none';
211
225
  });
212
- // ---- 厂商档案:切换 select 联动「模型覆盖」框显隐 + datalist(所选档案的候选模型 + 默认模型) ----
213
- // 通用 [data-f] 绑定负责赋值/删键,此处只管联动展示;模型值是否在候选集内由后端运行期 warn
226
+ // ---- 厂商档案:模型 = 可搜索单选 combo(候选 = 所选档案的模型候选 ∪ 默认模型)----
227
+ // 保存从 app 对象取值:combo onChange 实时写回(与分身白名单 multiSelectField 同构),
228
+ // 不走 [data-f] 通用绑定(combo 非原生 input)。初始 mount 只填候选不清值(打开抽屉
229
+ // 不能丢已存配置);切换档案时清空模型覆盖(留空 = 跟随新档案默认模型,旧档案的
230
+ // profile_model 残留会串档案——后端对非候选值 warn 回退,前端直接不残留)
214
231
  const profileSel = body.querySelector('[data-f="profile"]');
215
232
  const profileModelBox = body.querySelector('#profileModelBox');
216
- const fillProfileModels = () => {
217
- const prof = profileByName(profileSel.value);
218
- const list = prof ? [...(prof.models || []), ...(prof.model && !(prof.models || []).includes(prof.model) ? [prof.model] : [])] : [];
219
- body.querySelector('#profileModelList').innerHTML = list.map((m) => `<option value="${esc(m)}"></option>`).join('');
233
+ const modelCombo = singleSelectField({
234
+ mount: body.querySelector('#profileModelMount'),
235
+ value: app.profile_model || '', // 遗留值(含已不在候选的)ghost 展示可清除
236
+ placeholder: '输入关键词搜索选择;留空 = 档案默认模型',
237
+ emptyText: '该档案未配置候选模型(到「Claude 认证」页为档案添加模型)',
238
+ onChange: (v) => { if (v) app.profile_model = v; else delete app.profile_model; },
239
+ });
240
+ const applyProfileModels = () => {
241
+ modelCombo.setOptions(toProfileModelOptions(profileByName(profileSel.value)));
220
242
  profileModelBox.style.display = profileSel.value ? '' : 'none';
221
243
  };
222
- profileSel.addEventListener('change', fillProfileModels);
223
- fillProfileModels();
244
+ applyProfileModels(); // 初次填充:不动 value、不动 app.profile_model
245
+ profileSel.addEventListener('change', () => {
246
+ applyProfileModels();
247
+ modelCombo.setValue(''); // 静默清空 → 显式删键(切「(跟随全局)」同样命中:隐藏 + 清空)
248
+ delete app.profile_model;
249
+ });
224
250
  // ---- 分身白名单(可搜索多选):onChange 实时写回 app——空数组删键,语义与旧 textarea 一致 ----
225
251
  const mselInst = {};
226
252
  for (const f of ['allowed_skills', 'allowed_workspaces', 'allowed_plugins']) {
@@ -248,10 +274,9 @@ function openAppDrawer(apps, idx, el, snap) {
248
274
  c.pluginsError ? `候选加载失败:${c.pluginsError}(已选项不受影响)` : (c.plugins?.length ? undefined : '暂无已安装插件'),
249
275
  );
250
276
  });
251
- // ---- 触发词 / 显式插件:数组行编辑,实时写回 ----
277
+ // ---- 触发词:数组行编辑,实时写回 ----(显式插件区块已移除,统一走「插件」页全局配置;存量 apps[].plugins 数据继续生效)
252
278
  const LIST_DEFS = {
253
279
  triggers: { key: 'triggers', cols: [['match', '/produce 或 关键词'], ['rewrite', '请执行内容生产流程:{args}']], check: ['ask_detail', '补充'] },
254
- plugins: { key: 'plugins', cols: [['name', 'my-plugin'], ['path', 'F:/dev/my-plugin']] },
255
280
  };
256
281
  const renderPairList = (name) => {
257
282
  const def = LIST_DEFS[name];
@@ -338,7 +363,7 @@ function openAppDrawer(apps, idx, el, snap) {
338
363
  const badWs = (app.allowed_workspaces || []).filter((w) => !wsNames.includes(w));
339
364
  if (badWs.length) return toast(`「允许的工作区」含未定义的工作区:${badWs.join('、')}(请先在工作区列表中添加)`, true);
340
365
  }
341
- // 清理半填的空行:triggers/plugins 行两列全空剔除;env 以 key 非空为准(syncEnv 已过滤)。数组空则整键删除
366
+ // 清理半填的空行:triggers 行两列全空剔除;env 以 key 非空为准(syncEnv 已过滤)。数组空则整键删除
342
367
  for (const name of Object.keys(LIST_DEFS)) {
343
368
  const def = LIST_DEFS[name];
344
369
  const list = app[def.key];
@@ -144,11 +144,13 @@ async function runUpdateFlow() {
144
144
  export function larkCliStatHtml(r) {
145
145
  if (!r) return '<span class="hint">未检查</span>';
146
146
  if (!r.installed) return '<span class="tag off">未安装</span>';
147
- // authorized 之外一律呈现为「未授权」(含探测不出的 unknown):UI 不留没有出口的状态,
148
- // 两种情况都能点授权按钮。detail 悬浮可见(unknown 现已带诊断线索),便于自查。
147
+ // authorized 之外一律呈现为可点授权按钮的警示态(含探测不出的 unknown):UI 不留没有出口的状态。
148
+ // expired 单列(曾授权但 refresh token 失效):与「从未授权」区分,detail 悬浮带原因与过期日期。
149
149
  const authTag = r.auth?.state === 'authorized'
150
150
  ? '<span class="tag ok">已授权</span>'
151
- : `<span class="tag warn"${r.auth?.detail ? ` title="${esc(r.auth.detail)}"` : ''}>未授权</span>`;
151
+ : r.auth?.state === 'expired'
152
+ ? `<span class="tag warn"${r.auth?.detail ? ` title="${esc(r.auth.detail)}"` : ''}>已过期</span>`
153
+ : `<span class="tag warn"${r.auth?.detail ? ` title="${esc(r.auth.detail)}"` : ''}>未授权</span>`;
152
154
  // skill 字段为旧后端所无(undefined 时不显示),与 auth 解耦展示
153
155
  const skillTag = r.skill
154
156
  ? (r.skill.installed ? '<span class="tag ok">SKILL</span>' : '<span class="tag warn">SKILL 未装</span>')
@@ -166,7 +168,7 @@ export function larkCliStatHtml(r) {
166
168
  * 探测不出(unknown)**不**算未配置:误判会把已配好应用的用户反复推去重配,按「扫码授权」处理。
167
169
  */
168
170
  export function larkCliPrimaryLabel(r) {
169
- if (r?.auth?.state === 'authorized') return '重新授权';
171
+ if (r?.auth?.state === 'authorized' || r?.auth?.state === 'expired') return '重新授权';
170
172
  return larkNotConfigured(r) ? '配置应用' : '扫码授权';
171
173
  }
172
174
 
@@ -498,14 +500,6 @@ export const page = {
498
500
  </table>
499
501
  </div>
500
502
  </div>
501
- <div class="card">
502
- <h3>机器人应用</h3>
503
- <table><thead><tr><th>名称</th><th>App ID</th><th>状态</th></tr></thead><tbody>
504
- ${(st.apps || []).map((a) => `<tr><td>${esc(a.name)}</td><td><code>${esc(a.appId)}</code></td><td>${
505
- a.started === true ? '<span class="tag ok">运行中</span>' : a.started === false ? '<span class="tag err">启动失败</span>' : '<span class="tag off">未随本页启动</span>'
506
- }</td></tr>`).join('') || '<tr><td colspan="3" class="hint">无</td></tr>'}
507
- </tbody></table>
508
- </div>
509
503
  <div class="card">
510
504
  <h3>常用命令</h3>
511
505
  <div class="desc">在飞书聊天框输入 <code>/</code> 触发;其它 / 开头消息原文透传给 Claude Code。斜杠面板需先到「斜杠命令」页注册(补齐内置命令)。</div>
@@ -8,10 +8,10 @@ let comboHideBound = false;
8
8
  async function renderPlugins(el) {
9
9
  el.innerHTML = `
10
10
  <div class="card">
11
- <h3>Claude Code 插件</h3>
12
- <div class="desc">两处目录的已启用插件都会自动加载(managed 托管目录优先):本机 <code>~/.claude</code>(来源「本机」,与本机 claude CLI 共用)与 bridge 托管目录 <code id="pluginDir">…</code>。装好后下一条消息自动加载。飞书端同样可用 <code>/plugin install xxx@marketplace</code>(仅管理员)。</div>
11
+ <h3>Claude Code 插件 <button class="btn sm" id="plCheckUpdate" style="margin-left:8px">检查更新</button></h3>
12
+ <div class="desc">两处目录的已启用插件都会自动加载(managed 托管目录优先):本机 <code>~/.claude</code>(来源「本机」,与本机 claude CLI 共用)与 bridge 托管目录 <code id="pluginDir">…</code>。装好后下一条消息自动加载。飞书端同样可用 <code>/plugin install xxx@marketplace</code>(仅管理员)。打开页面自动检查远程市场新版本(5 分钟内不重复,只检测不升级,升级点单行「更新」)。</div>
13
13
  <table><thead><tr><th>插件</th><th>市场</th><th>来源</th><th>版本</th><th style="width:90px">状态</th><th style="width:250px">操作</th></tr></thead>
14
- <tbody id="plBody"><tr><td colspan="6" class="hint">加载中…</td></tr></tbody></table>
14
+ <tbody id="plBody"><tr><td colspan="6" class="hint">加载中…(首次含远程市场索引检查,可能需数十秒)</td></tr></tbody></table>
15
15
  </div>
16
16
  <div class="card">
17
17
  <h3>安装 / 市场</h3>
@@ -54,15 +54,18 @@ async function renderPlugins(el) {
54
54
  'marketplace-add': '添加中…', 'marketplace-remove': '移除中…', enable: '处理中…', disable: '处理中…',
55
55
  };
56
56
  const opNames = { install: '安装', uninstall: '卸载', update: '更新', 'update-all': '全部更新', 'marketplace-add': '添加市场', 'marketplace-remove': '移除市场', enable: '启用', disable: '停用' };
57
- const load = async () => {
57
+ // force=true(手动「检查更新」)绕过 5 分钟节流立即刷新远程市场索引;开页自动检测传空。
58
+ // 刷新只拉最新版本号不升级插件本体;失败后端降级返回本地数据并附 refreshWarning
59
+ const load = async (force) => {
58
60
  try {
59
- const r = await api('GET', '/api/plugins');
61
+ const r = await api('GET', `/api/plugins?refresh=1${force ? '&force=1' : ''}`);
62
+ if (r.refreshWarning) toast(`远程市场索引刷新失败(版本号可能滞后):${r.refreshWarning}`, true);
60
63
  installed = r.plugins || [];
61
64
  $('#pluginDir').textContent = r.configDir;
62
65
  const body = $('#plBody');
63
66
  if (!installed.length) {
64
67
  body.innerHTML = '<tr><td colspan="6" class="hint">尚未安装任何插件</td></tr>';
65
- return;
68
+ return true;
66
69
  }
67
70
  body.innerHTML = installed.map((p) => `
68
71
  <tr>
@@ -100,9 +103,11 @@ async function renderPlugins(el) {
100
103
  if (op === 'enable' || op === 'disable') act(op, arg, dir, b);
101
104
  else runOpDialog(op, arg, dir, b);
102
105
  });
106
+ return true;
103
107
  } catch (e) {
104
108
  installed = [];
105
109
  $('#plBody').innerHTML = `<tr><td colspan="6" class="hint">加载失败:${esc(e.message)}</td></tr>`;
110
+ return false;
106
111
  }
107
112
  };
108
113
  /** 轻量操作(enable/disable 亚秒级):按钮瞬时 loading + toast,不弹框 */
@@ -279,6 +284,23 @@ async function renderPlugins(el) {
279
284
  runOpDialog('install', selectedInstall, $('#plDir').value, $('#plDoInstall'));
280
285
  };
281
286
  $('#plDir').onchange = loadAvailable; // 安装目标目录切换 → 可装清单随之变化
287
+ // 手动「检查更新」:force 绕过节流立即刷新远程市场索引,汇总提示有新版的插件数
288
+ //(只检测新版本号,升级仍由用户逐个点「更新」/「全部更新」)
289
+ $('#plCheckUpdate').onclick = async () => {
290
+ const btn = $('#plCheckUpdate');
291
+ const old = btn.textContent;
292
+ btn.disabled = true;
293
+ btn.textContent = '检查中…';
294
+ try {
295
+ const ok = await load(true);
296
+ if (!ok) return; // 加载失败已在表格内提示,不再误报「全部最新」
297
+ const n = installed.filter((p) => p.latestVersion).length;
298
+ toast(n ? `${n} 个插件有新版本(见列表「→ 版本」标注,逐个点「更新」升级)` : '已检查:所有插件均为最新版本');
299
+ } finally {
300
+ btn.disabled = false;
301
+ btn.textContent = old;
302
+ }
303
+ };
282
304
  $('#plDoMarketAdd').onclick = () => {
283
305
  const v = $('#plMarket').value.trim();
284
306
  if (!v) return toast('请填写 marketplace git 地址或本机路径', true);
@@ -426,3 +426,109 @@ export function multiSelectField({ mount, options = [], value = [], placeholder
426
426
  destroy() { mselLive.delete(inst); root.remove(); },
427
427
  };
428
428
  }
429
+
430
+ /**
431
+ * 可搜索单选字段(应用抽屉「档案模型」等,Select2 单选形态):收起态当前值直接显示在
432
+ * 输入框内;聚焦即进入过滤态(清空输入露出 placeholder、面板全量展开,选中项以 ✓ 标记
433
+ * ——单选不做"已选值额外加 chip",避免与面板 ✓ 重复展示)。清除手势 = 聚焦后空输入退格。
434
+ * 值只能来自候选 options;初始 value 不在候选中的遗留值以虚线 ghost chip 展示(chip 是
435
+ * 遗留值唯一展示处)、可点 × 清除。事件边界与 multiSelectField 同款:
436
+ * mousedown+preventDefault 防失焦竞态、ESC 面板开时仅关面板(stopPropagation 防误关抽屉)、
437
+ * Enter/方向键在过滤结果内导航(Enter 选中即收起——单选无连续选取场景)。
438
+ *
439
+ * @returns {{root: Element, setValue(v: string): void, setOptions(list: Array, emptyText?: string): void, destroy(): void}}
440
+ */
441
+ export function singleSelectField({ mount, options = [], value = '', placeholder = '', emptyText = '暂无可选项', onChange = () => {} }) {
442
+ ensureMselDocListener();
443
+ const root = document.createElement('div');
444
+ root.className = 'msel';
445
+ root.innerHTML = `
446
+ <div class="chips msel-chips"></div>
447
+ <input type="text" class="msel-input" placeholder="${esc(placeholder)}" autocomplete="off">
448
+ <div class="combo-panel msel-panel" hidden></div>`;
449
+ mount.appendChild(root);
450
+ const chipsBox = root.querySelector('.msel-chips');
451
+ const input = root.querySelector('.msel-input');
452
+ const panel = root.querySelector('.msel-panel');
453
+ let opts = options.slice();
454
+ let emptyTextCur = emptyText;
455
+ let selected = String(value || '');
456
+ let highlight = 0;
457
+ let inputAsFilter = false; // false = input 显示的是选中值(展示态);true = 用户输入的过滤词
458
+
459
+ // 展示态渲染:非 ghost 已选值不渲染 chip(面板 ✓ 是唯一选中标记),值回显进输入框;
460
+ // ghost 遗留值(不在候选)以虚线 chip 展示——那是它唯一展示处。过滤中不动输入框。
461
+ const renderDisplay = () => {
462
+ const ghost = !!selected && !opts.some((o) => o.value === selected);
463
+ if (ghost) {
464
+ chipsBox.innerHTML = `<span class="chip ghost" title="已不在候选列表,可点击 × 清除">${esc(selected)}<button type="button" aria-label="清除">×</button></span>`;
465
+ chipsBox.querySelector('button').onclick = () => { selected = ''; emit(); };
466
+ } else chipsBox.innerHTML = '';
467
+ if (!inputAsFilter) input.value = ghost ? '' : (selected ? (opts.find((o) => o.value === selected)?.label || selected) : '');
468
+ };
469
+ const paintPanel = () => {
470
+ if (!opts.length) { panel.innerHTML = `<div class="combo-empty">${esc(emptyTextCur)}</div>`; return; }
471
+ const { list, truncated } = filterMselOptions(opts, input.value);
472
+ if (!list.length) { panel.innerHTML = '<div class="combo-empty">无匹配项</div>'; return; }
473
+ panel.innerHTML = list.map((o, i) => `
474
+ <div class="combo-item${i === highlight ? ' active' : ''}" data-val="${esc(o.value)}">
475
+ <b>${esc(o.label || o.value)}</b>${o.tag ? ` <span class="tag off"${o.tagTitle ? ` title="${esc(o.tagTitle)}"` : ''}>${esc(o.tag)}</span>` : ''}${o.value === selected ? '<span class="msel-check">✓</span>' : ''}
476
+ ${o.desc ? `<div class="hint">${esc(o.desc)}</div>` : ''}
477
+ </div>`).join('')
478
+ + (truncated ? '<div class="combo-empty">仅显示前 50 项,继续输入缩小范围</div>' : '');
479
+ panel.querySelectorAll('.combo-item').forEach((el) => {
480
+ el.addEventListener('mousedown', (e) => { e.preventDefault(); pick(el.dataset.val); });
481
+ });
482
+ };
483
+ const showPanel = () => { if (panel.hidden) panel.hidden = false; paintPanel(); };
484
+ // 统一收口:ESC / pick / 外点三路关面板都在此恢复展示态(回显选中值、丢掉未确认的过滤词)
485
+ const hidePanel = () => { panel.hidden = true; inputAsFilter = false; renderDisplay(); };
486
+ const pick = (v) => { selected = v; hidePanel(); emit(); };
487
+ const emit = () => { renderDisplay(); if (!panel.hidden) paintPanel(); onChange(selected); };
488
+
489
+ input.addEventListener('focus', () => {
490
+ inputAsFilter = false;
491
+ input.value = ''; // 程序化清空不触发 input 事件,placeholder 露出作搜索提示
492
+ highlight = 0; // 过滤列表将由收窄变全量,残留索引会让 Enter 选错项
493
+ showPanel();
494
+ });
495
+ // pick/ESC 收起后焦点仍留在 input 上,再点击不触发 focus——补一条重开路径(与 focus 幂等同款)
496
+ input.addEventListener('click', () => {
497
+ if (panel.hidden) { inputAsFilter = false; input.value = ''; highlight = 0; showPanel(); }
498
+ });
499
+ input.addEventListener('input', () => { inputAsFilter = true; highlight = 0; showPanel(); });
500
+ input.addEventListener('keydown', (e) => {
501
+ // 面板开时 ESC 只关面板:阻止冒泡到 document 的抽屉关闭监听(面板已关则放行,符合直觉)
502
+ if (e.key === 'Escape' && !panel.hidden) { e.stopPropagation(); hidePanel(); return; }
503
+ // 空输入框退格 = 清除已选(回空 = 跟随默认),与多选「删最后一个 chip」同直觉
504
+ if (e.key === 'Backspace' && !input.value && selected) { e.preventDefault(); selected = ''; emit(); return; }
505
+ if (!opts.length) return;
506
+ const { list } = filterMselOptions(opts, input.value);
507
+ if (!list.length) return;
508
+ if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
509
+ e.preventDefault();
510
+ highlight = panel.hidden ? 0 : (highlight + (e.key === 'ArrowDown' ? 1 : -1) + list.length) % list.length;
511
+ showPanel();
512
+ panel.querySelector('.combo-item.active')?.scrollIntoView({ block: 'nearest' });
513
+ } else if (e.key === 'Enter' && !panel.hidden) {
514
+ e.preventDefault();
515
+ pick(list[highlight]?.value ?? list[0].value); // 选中即收起:单选无连续选取
516
+ }
517
+ });
518
+
519
+ const inst = { root, hidePanel };
520
+ mselLive.add(inst);
521
+ renderDisplay();
522
+ return {
523
+ root,
524
+ // 静默设值:不触发 onChange(切档案联动清空等程序化操作用,用户交互才走 onChange)
525
+ setValue(v) { selected = String(v || ''); renderDisplay(); },
526
+ setOptions(list, nextEmptyText) {
527
+ opts = list.slice();
528
+ if (nextEmptyText !== undefined) emptyTextCur = nextEmptyText;
529
+ renderDisplay(); // ghost 态随候选变化
530
+ if (!panel.hidden) paintPanel();
531
+ },
532
+ destroy() { mselLive.delete(inst); root.remove(); },
533
+ };
534
+ }
@@ -56,6 +56,14 @@ export interface MarketplaceCatalog {
56
56
  * 单个市场清单损坏按市场粒度跳过,不影响其余。
57
57
  */
58
58
  export declare function listAvailablePlugins(claudeConfigDir: string): MarketplaceCatalog[];
59
+ /**
60
+ * 该目录是否注册了会物化副本的远程市场(github/git/url):决定 marketplace update 是否值得跑。
61
+ * 判定:
62
+ * 1. known_marketplaces.json 任一条目 source 非 directory/file → true(未知 source 类型按远程处理,宁刷勿漏);
63
+ * 2. 兜底:plugins/marketplaces 下存在子目录(远程市场 clone 的痕迹,登记表缺失/旧版 CLI 场景)→ true。
64
+ * 本地 directory/file 市场读原路径实时生效无须刷新——这正是「本地插件升级立即可见、远程仍显旧版本」的根源。
65
+ */
66
+ export declare function hasRemoteMarketplaces(claudeConfigDir: string): boolean;
59
67
  /**
60
68
  * 显式配置(config.yaml apps[].plugins,开发期指源码目录)与自动发现结果合并:
61
69
  * 按插件名去重,显式优先——防「显式指 dev 副本 + 缓存里正式版」双载导致技能重复。
@@ -165,6 +165,30 @@ export function listAvailablePlugins(claudeConfigDir) {
165
165
  }
166
166
  return out;
167
167
  }
168
+ /**
169
+ * 该目录是否注册了会物化副本的远程市场(github/git/url):决定 marketplace update 是否值得跑。
170
+ * 判定:
171
+ * 1. known_marketplaces.json 任一条目 source 非 directory/file → true(未知 source 类型按远程处理,宁刷勿漏);
172
+ * 2. 兜底:plugins/marketplaces 下存在子目录(远程市场 clone 的痕迹,登记表缺失/旧版 CLI 场景)→ true。
173
+ * 本地 directory/file 市场读原路径实时生效无须刷新——这正是「本地插件升级立即可见、远程仍显旧版本」的根源。
174
+ */
175
+ export function hasRemoteMarketplaces(claudeConfigDir) {
176
+ const known = readJson(join(claudeConfigDir, 'plugins', 'known_marketplaces.json'));
177
+ if (known.ok) {
178
+ for (const info of Object.values(known.doc)) {
179
+ const sourceType = info?.source?.source;
180
+ if (sourceType !== 'directory' && sourceType !== 'file')
181
+ return true;
182
+ }
183
+ }
184
+ try {
185
+ return readdirSync(join(claudeConfigDir, 'plugins', 'marketplaces'), { withFileTypes: true })
186
+ .some((e) => e.isDirectory());
187
+ }
188
+ catch {
189
+ return false; // 市场根目录不存在 = 未添加过任何远程市场
190
+ }
191
+ }
168
192
  function parseInstalled(dir, installedPath, settingsPath) {
169
193
  const installed = readJson(installedPath);
170
194
  if (!installed.ok) {
@@ -1 +1 @@
1
- {"version":3,"file":"plugin-discovery.js","sourceRoot":"","sources":["../../src/executor/plugin-discovery.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"plugin-discovery.js","sourceRoot":"","sources":["../../src/executor/plugin-discovery.ts"],"names":[],"mappings":"AAAA,sDAAsD;AACtD,qEAAqE;AACrE,2EAA2E;AAC3E,0DAA0D;AAC1D,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,WAAW,EAAE,QAAQ,EAAe,MAAM,SAAS,CAAC;AACvF,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAuBjC,mEAAmE;AACnE,wCAAwC;AACxC,MAAM,KAAK,GAAG,IAAI,GAAG,EAA0D,CAAC;AAChF,uCAAuC;AACvC,MAAM,MAAM,GAAG,IAAI,GAAG,EAAU,CAAC;AAEjC,SAAS,QAAQ,CAAC,GAAW,EAAE,MAAc,EAAE,MAAc;IAC3D,MAAM,CAAC,GAAG,GAAG,GAAG,KAAK,MAAM,EAAE,CAAC;IAC9B,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;QAAE,OAAO;IAC1B,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;IACd,OAAO,CAAC,IAAI,CAAC,UAAU,MAAM,EAAE,CAAC,CAAC;AACnC,CAAC;AAED,SAAS,QAAQ,CAAI,IAAY;IAC/B,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC;IAC9D,IAAI,CAAC;QACH,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAM,EAAE,CAAC;IACxE,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;IAC1E,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,eAAe,CAAC,eAAuB;IACrD,MAAM,aAAa,GAAG,IAAI,CAAC,eAAe,EAAE,SAAS,EAAE,wBAAwB,CAAC,CAAC;IACjF,MAAM,YAAY,GAAG,IAAI,CAAC,eAAe,EAAE,eAAe,CAAC,CAAC;IAC5D,mEAAmE;IACnE,IAAI,CAAC,UAAU,CAAC,aAAa,CAAC;QAAE,OAAO,EAAE,CAAC;IAE1C,0BAA0B;IAC1B,IAAI,MAAM,GAAG,EAAE,CAAC;IAChB,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,QAAQ,CAAC,aAAa,CAAC,CAAC,OAAO,IAAI,UAAU,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IACjH,CAAC;IAAC,MAAM,CAAC,CAAC,8BAA8B,CAAC,CAAC;IAC1C,MAAM,GAAG,GAAG,KAAK,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC;IACvC,IAAI,GAAG,IAAI,MAAM,IAAI,GAAG,CAAC,MAAM,KAAK,MAAM;QAAE,OAAO,GAAG,CAAC,MAAM,CAAC;IAE9D,MAAM,MAAM,GAAG,cAAc,CAAC,eAAe,EAAE,aAAa,EAAE,YAAY,CAAC,CAAC;IAC5E,KAAK,CAAC,GAAG,CAAC,eAAe,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IAC/C,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,4DAA4D;AAC5D,MAAM,UAAU,qBAAqB,CAAC,eAAuB;IAC3D,KAAK,CAAC,MAAM,CAAC,eAAe,CAAC,CAAC;AAChC,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,kBAAkB,CAAC,eAAuB;IACxD,MAAM,CAAC,GAAG,QAAQ,CAAc,IAAI,CAAC,eAAe,EAAE,eAAe,CAAC,CAAC,CAAC;IACxE,IAAI,CAAC,CAAC,CAAC,EAAE;QAAE,OAAO,IAAI,GAAG,EAAE,CAAC;IAC5B,MAAM,GAAG,GAAG,CAAC,CAAC,GAAG,CAAC,cAAc,CAAC;IACjC,IAAI,CAAC,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ;QAAE,OAAO,IAAI,GAAG,EAAE,CAAC;IACtD,OAAO,IAAI,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC;AAClE,CAAC;AAED,sDAAsD;AACtD,MAAM,UAAU,oBAAoB,CAAC,eAAuB;IAG1D,MAAM,SAAS,GAAG,QAAQ,CAAsB,IAAI,CAAC,eAAe,EAAE,SAAS,EAAE,wBAAwB,CAAC,CAAC,CAAC;IAC5G,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,SAAS,CAAC,GAAG,CAAC,OAAO,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAC5D,MAAM,QAAQ,GAAG,QAAQ,CAAc,IAAI,CAAC,eAAe,EAAE,eAAe,CAAC,CAAC,CAAC;IAC/E,MAAM,UAAU,GAAG,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,cAAc,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IAC1E,MAAM,GAAG,GAAkH,EAAE,CAAC;IAC9H,KAAK,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,GAAG,CAAC,OAAO,IAAI,EAAE,CAAC,EAAE,CAAC;QACtE,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC;YAAE,SAAS;QACnC,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,KAAK,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC;QAC/D,MAAM,EAAE,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAC5B,GAAG,CAAC,IAAI,CAAC;YACP,GAAG;YACH,IAAI,EAAE,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG;YACrC,WAAW,EAAE,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS;YACnD,OAAO,EAAE,UAAU,CAAC,GAAG,CAAC,KAAK,IAAI;YACjC,GAAG,CAAC,OAAO,IAAI,EAAE,WAAW,KAAK,QAAQ,IAAI,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAChG,GAAG,CAAC,OAAO,IAAI,EAAE,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACxE,CAAC,CAAC;IACL,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAgCD,SAAS,WAAW,CAAC,IAAY,EAAE,GAAmB;IACpD,MAAM,OAAO,GAA6B,EAAE,CAAC;IAC7C,KAAK,MAAM,CAAC,IAAI,GAAG,CAAC,OAAO,IAAI,EAAE,EAAE,CAAC;QAClC,IAAI,OAAO,CAAC,EAAE,IAAI,KAAK,QAAQ,IAAI,CAAC,CAAC,CAAC,IAAI;YAAE,SAAS;QACrD,OAAO,CAAC,IAAI,CAAC;YACX,IAAI,EAAE,CAAC,CAAC,IAAI;YACZ,GAAG,CAAC,OAAO,CAAC,CAAC,WAAW,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC5E,GAAG,CAAC,OAAO,CAAC,CAAC,OAAO,KAAK,QAAQ,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAC9E,CAAC,CAAC;IACL,CAAC;IACD,OAAO;QACL,IAAI;QACJ,GAAG,CAAC,OAAO,GAAG,CAAC,WAAW,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,GAAG,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAChF,OAAO;KACR,CAAC;AACJ,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,oBAAoB,CAAC,eAAuB;IAC1D,MAAM,UAAU,GAAG,IAAI,CAAC,eAAe,EAAE,SAAS,EAAE,cAAc,CAAC,CAAC;IACpE,MAAM,GAAG,GAAyB,EAAE,CAAC;IACrC,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,IAAI,OAAiB,CAAC;IACtB,IAAI,CAAC;QACH,OAAO,GAAG,WAAW,CAAC,UAAU,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;IAC7D,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,GAAG,EAAE,CAAC,CAAC,oCAAoC;IACpD,CAAC;IACD,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,IAAI,CAAC,KAAK,CAAC,WAAW,EAAE;YAAE,SAAS;QACnC,MAAM,QAAQ,GAAG,QAAQ,CAAiB,IAAI,CAAC,UAAU,EAAE,KAAK,CAAC,IAAI,EAAE,gBAAgB,EAAE,kBAAkB,CAAC,CAAC,CAAC;QAC9G,IAAI,CAAC,QAAQ,CAAC,EAAE;YAAE,SAAS;QAC3B,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACrB,GAAG,CAAC,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,IAAI,EAAE,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC;IAClD,CAAC;IACD,MAAM,KAAK,GAAG,QAAQ,CAAuB,IAAI,CAAC,eAAe,EAAE,SAAS,EAAE,yBAAyB,CAAC,CAAC,CAAC;IAC1G,IAAI,KAAK,CAAC,EAAE,EAAE,CAAC;QACb,KAAK,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;YACrD,IAAI,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC;gBAAE,SAAS;YAC7B,MAAM,UAAU,GAAG,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC;YACxC,IAAI,UAAU,KAAK,WAAW,IAAI,UAAU,KAAK,MAAM;gBAAE,SAAS,CAAC,cAAc;YACjF,mFAAmF;YACnF,MAAM,YAAY,GAAG,UAAU,KAAK,MAAM,IAAI,OAAO,IAAI,CAAC,MAAM,EAAE,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,MAAM,CAAC,IAAI;gBACrG,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI;gBAClB,CAAC,CAAC,OAAO,IAAI,CAAC,eAAe,KAAK,QAAQ,IAAI,IAAI,CAAC,eAAe;oBAChE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,eAAe,EAAE,gBAAgB,EAAE,kBAAkB,CAAC;oBAClE,CAAC,CAAC,EAAE,CAAC;YACT,IAAI,CAAC,YAAY;gBAAE,SAAS;YAC5B,MAAM,QAAQ,GAAG,QAAQ,CAAiB,YAAY,CAAC,CAAC;YACxD,IAAI,CAAC,QAAQ,CAAC,EAAE;gBAAE,SAAS;YAC3B,GAAG,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC;QAC5C,CAAC;IACH,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,qBAAqB,CAAC,eAAuB;IAC3D,MAAM,KAAK,GAAG,QAAQ,CAAuB,IAAI,CAAC,eAAe,EAAE,SAAS,EAAE,yBAAyB,CAAC,CAAC,CAAC;IAC1G,IAAI,KAAK,CAAC,EAAE,EAAE,CAAC;QACb,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;YAC5C,MAAM,UAAU,GAAG,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC;YACxC,IAAI,UAAU,KAAK,WAAW,IAAI,UAAU,KAAK,MAAM;gBAAE,OAAO,IAAI,CAAC;QACvE,CAAC;IACH,CAAC;IACD,IAAI,CAAC;QACH,OAAO,WAAW,CAAC,IAAI,CAAC,eAAe,EAAE,SAAS,EAAE,cAAc,CAAC,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC;aAC1F,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC;IAClC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC,CAAC,wBAAwB;IACxC,CAAC;AACH,CAAC;AAED,SAAS,cAAc,CAAC,GAAW,EAAE,aAAqB,EAAE,YAAoB;IAC9E,MAAM,SAAS,GAAG,QAAQ,CAAsB,aAAa,CAAC,CAAC;IAC/D,IAAI,CAAC,SAAS,CAAC,EAAE,EAAE,CAAC;QAClB,QAAQ,CAAC,GAAG,EAAE,iBAAiB,EAAE,+BAA+B,SAAS,CAAC,KAAK,YAAY,CAAC,CAAC;QAC7F,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,MAAM,GAAG,GAAG,SAAS,CAAC,GAAG,CAAC;IAC1B,IAAI,GAAG,CAAC,OAAO,KAAK,CAAC,EAAE,CAAC;QACtB,QAAQ,CAAC,GAAG,EAAE,mBAAmB,EAAE,8BAA8B,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,yBAAyB,CAAC,CAAC;QAC/G,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC;IAClD,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAEpC,2DAA2D;IAC3D,gCAAgC;IAChC,MAAM,QAAQ,GAAG,QAAQ,CAAc,YAAY,CAAC,CAAC;IACrD,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;QACjB,QAAQ,CAAC,GAAG,EAAE,gBAAgB,EAAE,sBAAsB,QAAQ,CAAC,KAAK,uBAAuB,CAAC,CAAC;QAC7F,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,MAAM,OAAO,GAAG,QAAQ,CAAC,GAAG,CAAC,cAAc,IAAI,EAAE,CAAC;IAClD,MAAM,WAAW,GAAG,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC;IAC1F,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC7B,QAAQ,CAAC,GAAG,EAAE,cAAc,EAAE,OAAO,OAAO,CAAC,MAAM,sDAAsD,CAAC,CAAC;QAC3G,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,MAAM,GAAG,GAAuB,EAAE,CAAC;IACnC,KAAK,MAAM,GAAG,IAAI,WAAW,EAAE,CAAC;QAC9B,MAAM,IAAI,GAAG,GAAG,CAAC,OAAO,EAAE,CAAC,GAAG,CAAC,CAAC;QAChC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC;YAAE,SAAS,CAAC,4BAA4B;QAChE,sCAAsC;QACtC,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,KAAK,KAAK,MAAM,CAAC,CAAC;QACpD,IAAI,CAAC,IAAI;YAAE,SAAS;QACpB,MAAM,WAAW,GAAG,OAAO,IAAI,CAAC,WAAW,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,CAAC;QACjF,IAAI,CAAC,WAAW,EAAE,CAAC;YACjB,QAAQ,CAAC,GAAG,EAAE,WAAW,GAAG,EAAE,EAAE,MAAM,GAAG,0BAA0B,CAAC,CAAC;YACrE,SAAS;QACX,CAAC;QACD,IAAI,CAAC,UAAU,CAAC,WAAW,CAAC,EAAE,CAAC;YAC7B,QAAQ,CAAC,GAAG,EAAE,WAAW,GAAG,EAAE,EAAE,MAAM,GAAG,aAAa,WAAW,gBAAgB,CAAC,CAAC;YACnF,SAAS;QACX,CAAC;QACD,MAAM,EAAE,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAC5B,GAAG,CAAC,IAAI,CAAC;YACP,IAAI,EAAE,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG;YACrC,WAAW,EAAE,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS;YACnD,IAAI,EAAE,WAAW;YACjB,OAAO,EAAE,OAAO,IAAI,CAAC,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS;SACrE,CAAC,CAAC;IACL,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,kBAAkB,CAChC,QAAiC,EACjC,UAA8B;IAE9B,MAAM,GAAG,GAA6E,EAAE,CAAC;IACzF,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,KAAK,MAAM,CAAC,IAAI,QAAQ,IAAI,EAAE,EAAE,CAAC;QAC/B,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC;YAAE,SAAS;QAC/B,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QACjB,GAAG,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC,CAAC;IAC/D,CAAC;IACD,KAAK,MAAM,CAAC,IAAI,UAAU,EAAE,CAAC;QAC3B,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC;YAAE,SAAS;QAC/B,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QACjB,GAAG,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE,CAAC,CAAC;IACjE,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC"}
@@ -26,6 +26,22 @@ export interface PluginUpdateDetail {
26
26
  export interface PluginUpdateAllResult extends PluginCliResult {
27
27
  details: PluginUpdateDetail[];
28
28
  }
29
+ /** 市场索引刷新结果(GET /api/plugins?refresh=1 附带;失败降级不阻断列表返回) */
30
+ export interface MarketplaceRefreshResult {
31
+ refreshed: boolean;
32
+ reason?: 'no-remote' | 'throttled';
33
+ warning?: string;
34
+ }
35
+ /**
36
+ * 刷新市场索引(仅 marketplace update 拉最新清单,绝不升级已装插件——与 updateAllPlugins
37
+ * 语义刻意区分,供插件页「检测远程新版本」使用)。远程市场的清单被 CLI clone 物化在
38
+ * plugins/marketplaces/ 下,不刷新就永远显示旧版本(本地 directory/file 市场读原路径实时,
39
+ * 无须刷新)。无远程市场的目录直接跳过省一次 spawn;节流窗内跳过;并发共用同一 in-flight;
40
+ * 失败/超时/CLI 缺失返回 warning 不抛——调用方仍返回本地清单数据。
41
+ */
42
+ export declare function refreshMarketplaceIndex(claudeConfigDir: string, opts?: {
43
+ force?: boolean;
44
+ }): Promise<MarketplaceRefreshResult>;
29
45
  /**
30
46
  * 「全部更新」:先刷新市场索引(marketplace update),再逐个更新已装插件
31
47
  * (plugin update name@marketplace)。关键语义:marketplace update 只是拉取市场仓库
@@ -4,7 +4,7 @@
4
4
  // 环境仅白名单透传 + CLAUDE_CONFIG_DIR:插件装到 bridge 解析的配置目录(managed 模式与 ~/.claude 隔离)
5
5
  import { spawn } from 'node:child_process';
6
6
  import { createRequire } from 'node:module';
7
- import { listAvailablePlugins, listInstalledPlugins } from './plugin-discovery.js';
7
+ import { hasRemoteMarketplaces, listAvailablePlugins, listInstalledPlugins } from './plugin-discovery.js';
8
8
  /** 平台二进制包候选(与 sdk.mjs 解析序一致:linux 优先 musl 变体;win32 带 .exe) */
9
9
  function cliCandidates() {
10
10
  const { platform, arch } = process;
@@ -85,6 +85,50 @@ export function runPluginCli(args, opts) {
85
85
  }
86
86
  /** 单插件 update 的超时:比 install 略紧(通常只拉一个小仓库,不含完整 marketplace clone) */
87
87
  const UPDATE_TIMEOUT_MS = 3 * 60 * 1000;
88
+ /** 只拉清单远快于 install;卡死也不拖垮页面加载(双目录并行刷新时单目录上限) */
89
+ const MARKETPLACE_REFRESH_TIMEOUT_MS = 45_000;
90
+ /** 节流窗:开页即检测新版本,但 5 分钟内不重复 spawn(失败也记时,防网络故障下每次开页都白等;force 绕过) */
91
+ const MARKETPLACE_REFRESH_THROTTLE_MS = 5 * 60 * 1000;
92
+ const mpRefreshAt = new Map();
93
+ const mpRefreshInflight = new Map();
94
+ /**
95
+ * 刷新市场索引(仅 marketplace update 拉最新清单,绝不升级已装插件——与 updateAllPlugins
96
+ * 语义刻意区分,供插件页「检测远程新版本」使用)。远程市场的清单被 CLI clone 物化在
97
+ * plugins/marketplaces/ 下,不刷新就永远显示旧版本(本地 directory/file 市场读原路径实时,
98
+ * 无须刷新)。无远程市场的目录直接跳过省一次 spawn;节流窗内跳过;并发共用同一 in-flight;
99
+ * 失败/超时/CLI 缺失返回 warning 不抛——调用方仍返回本地清单数据。
100
+ */
101
+ export async function refreshMarketplaceIndex(claudeConfigDir, opts = {}) {
102
+ if (!hasRemoteMarketplaces(claudeConfigDir))
103
+ return { refreshed: false, reason: 'no-remote' };
104
+ const running = mpRefreshInflight.get(claudeConfigDir);
105
+ if (running)
106
+ return running;
107
+ if (!opts.force && Date.now() - (mpRefreshAt.get(claudeConfigDir) ?? 0) < MARKETPLACE_REFRESH_THROTTLE_MS) {
108
+ return { refreshed: false, reason: 'throttled' };
109
+ }
110
+ const p = (async () => {
111
+ try {
112
+ const r = await runPluginCli(['marketplace', 'update'], {
113
+ claudeConfigDir,
114
+ timeoutMs: MARKETPLACE_REFRESH_TIMEOUT_MS,
115
+ });
116
+ return r.ok
117
+ ? { refreshed: true }
118
+ : { refreshed: false, warning: `市场索引刷新失败:${tail(r.text, 200)}` };
119
+ }
120
+ catch (e) {
121
+ // resolveBundledCliPath 缺失等同步抛错也会落到这里(async 函数内同步抛 = rejected)
122
+ return { refreshed: false, warning: `市场索引刷新异常:${e instanceof Error ? e.message : String(e)}` };
123
+ }
124
+ finally {
125
+ // 失败也记节流时间戳:网络故障下不会每次开页都重等 45s;手动 force 是即时重试出口
126
+ mpRefreshAt.set(claudeConfigDir, Date.now());
127
+ }
128
+ })().finally(() => mpRefreshInflight.delete(claudeConfigDir));
129
+ mpRefreshInflight.set(claudeConfigDir, p);
130
+ return p;
131
+ }
88
132
  /**
89
133
  * 「全部更新」:先刷新市场索引(marketplace update),再逐个更新已装插件
90
134
  * (plugin update name@marketplace)。关键语义:marketplace update 只是拉取市场仓库