@luchenglong/cli-bridge 0.2.9 → 0.2.11
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/CHANGELOG.md +73 -0
- package/PROVIDER-CONTRACT.md +56 -5
- package/docs/HANDOVER.md +109 -15
- package/dynamic/claude.host.js +362 -106
- package/dynamic/codebuddy.host.js +1426 -1378
- package/dynamic/codex.host.js +78 -103
- package/dynamic/mimo.host.js +720 -514
- package/dynamic/mimo.host.template.mjs +2 -0
- package/dynamic/opencode.host.js +740 -584
- package/dynamic/opencode.host.template.mjs +2 -0
- package/home-plugin/cli-bridge-indicator/lib/client.js +590 -600
- package/home-plugin/cli-bridge-indicator/lib/index.mjs +48 -0
- package/home-plugin/cli-bridge-indicator/lib/preset-declarations.mjs +1 -1
- package/mcp/cli-bridge-mcp.mjs +3 -3
- package/mcp/providers/codebuddy.mjs +427 -427
- package/package.json +3 -3
- package/providers/claude-endpoint.mjs +132 -0
- package/providers/claude-models.mjs +141 -101
- package/providers/claude.mjs +88 -6
- package/providers/codebuddy-catalog.mjs +367 -599
- package/providers/codebuddy-consts.mjs +72 -72
- package/providers/codebuddy-models-source.mjs +254 -0
- package/providers/codebuddy-models-tool.mjs +106 -106
- package/providers/codebuddy-schemas.mjs +46 -46
- package/providers/codebuddy.mjs +600 -578
- package/providers/codex-cli.mjs +33 -99
- package/providers/codex.mjs +45 -4
- package/providers/mimo-pure.mjs +270 -111
- package/providers/mimo.mjs +453 -517
- package/providers/opencode-exe.mjs +190 -63
- package/providers/opencode.mjs +553 -587
- package/scripts/build.mjs +261 -261
|
@@ -1,600 +1,590 @@
|
|
|
1
|
-
// cli-bridge-indicator — browser half (home-level plugin),一盏灯聚合本机 CLI。
|
|
2
|
-
// 每 1.2s 轮询 GET /cli-bridge/status → { providers:{...} },每个 provider 画一组 pill(有活动=全局
|
|
3
|
-
// 显示;空转「就绪」灯=仅本会话在该 preset 时显示)。settings 分区字段全由后端 meta 驱动。
|
|
4
|
-
// running→蓝呼吸 ⟳ ok→绿 ✓ failed→红 ✗ fallback→琥珀 ↩ idle→灰 标签。
|
|
5
|
-
//
|
|
6
|
-
// ⚠⚠⚠ 红线 4(历史事故:agy v1.6.1 / codebuddy v1.1.8):下方 __ModuleLoader__.load({ id })
|
|
7
|
-
// 必须【逐字等于 npm 包名】"@luchenglong/cli-bridge"(scoped 包=含 @scope/ 的完整名)。
|
|
8
|
-
// client-modules 的 graph row 以 package name 为 id(exports["./client"] 归属包),bundle
|
|
9
|
-
// 脚本执行后按 loaded without registering "<packageName>" 校验;一旦不等 → 整个 client
|
|
10
|
-
// combo 加载失败 → DSH 启动致命屏 → 强制安全模式。slot id 与 CSS 标记是另一命名空间,可自取。
|
|
11
|
-
// 改包名 = 必改此 id,二者绑定,永远一起改。
|
|
12
|
-
|
|
13
|
-
window.__ModuleLoader__.load({
|
|
14
|
-
id: "@luchenglong/cli-bridge",
|
|
15
|
-
factory: (require) => {
|
|
16
|
-
var module = { exports: {} };
|
|
17
|
-
var exports = module.exports;
|
|
18
|
-
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
19
|
-
|
|
20
|
-
let react = require("react");
|
|
21
|
-
|
|
22
|
-
// provider 展示元数据(与后端 registry 对齐;渲染顺序)。
|
|
23
|
-
// ⚠ 仅作 fallback:新 CLI 接入时后端视图(providers 键 + scheduling.all/labels)是
|
|
24
|
-
// 权威轴,本表不更新也只丢 presetId 的「就绪灯」优化,卡片/标签页/调度自动跟上。
|
|
25
|
-
var PROVIDERS = [
|
|
26
|
-
{ id: "agy", label: "AGY", presetId: "cordis-agy" },
|
|
27
|
-
{ id: "codebuddy", label: "CB", presetId: "codebuddy-first" },
|
|
28
|
-
{ id: "mimo", label: "MIMO", presetId: "cordis-mimo" }
|
|
29
|
-
];
|
|
30
|
-
const PRESET_ID_OF = {};
|
|
31
|
-
for (const m of PROVIDERS) PRESET_ID_OF[m.id] = m.presetId;
|
|
32
|
-
const FALLBACK_LABEL = {};
|
|
33
|
-
for (const m of PROVIDERS) FALLBACK_LABEL[m.id] = m.label;
|
|
34
|
-
|
|
35
|
-
// 后端驱动 id 轴:从调度视图(scheduling.all + labels)或 status 视图取全量 id,
|
|
36
|
-
// 本地 PROVIDERS 仅补 presetId(就绪灯判定用)。新增 provider = 后端 registry 一行,
|
|
37
|
-
// 本文件零改动。
|
|
38
|
-
function providerMetas(sc, extra) {
|
|
39
|
-
const ids = (sc && Array.isArray(sc.all)) ? sc.all.slice()
|
|
40
|
-
: (extra && Array.isArray(extra.all) ? extra.all.slice()
|
|
41
|
-
: PROVIDERS.map(function (m) { return m.id; }));
|
|
42
|
-
const labels = (sc && sc.labels) || (extra && extra.labels) || {};
|
|
43
|
-
const out = [];
|
|
44
|
-
for (const id of ids) {
|
|
45
|
-
out.push({ id: id, label: labels[id] || FALLBACK_LABEL[id] || String(id).toUpperCase(), presetId: PRESET_ID_OF[id] || null });
|
|
46
|
-
}
|
|
47
|
-
return out;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const CSS = [
|
|
51
|
-
".clb-row{display:inline-flex;align-items:center;gap:6px}",
|
|
52
|
-
".clb-ind{
|
|
53
|
-
".clb-ind
|
|
54
|
-
".clb-dot{
|
|
55
|
-
".clb-
|
|
56
|
-
".clb-
|
|
57
|
-
"
|
|
58
|
-
".clb-
|
|
59
|
-
".clb-
|
|
60
|
-
".clb-
|
|
61
|
-
".clb-
|
|
62
|
-
".clb-
|
|
63
|
-
"
|
|
64
|
-
".clb-pop-
|
|
65
|
-
|
|
66
|
-
".
|
|
67
|
-
".
|
|
68
|
-
".
|
|
69
|
-
".
|
|
70
|
-
".
|
|
71
|
-
".
|
|
72
|
-
|
|
73
|
-
".
|
|
74
|
-
".
|
|
75
|
-
".
|
|
76
|
-
".
|
|
77
|
-
".
|
|
78
|
-
".
|
|
79
|
-
|
|
80
|
-
".clbs-
|
|
81
|
-
".clbs-
|
|
82
|
-
".clbs-
|
|
83
|
-
".clbs-
|
|
84
|
-
".clbs-
|
|
85
|
-
".clbs-
|
|
86
|
-
".clbs-
|
|
87
|
-
".clbs-
|
|
88
|
-
".clbs-
|
|
89
|
-
".clbs-
|
|
90
|
-
".clbs-
|
|
91
|
-
".clbs-
|
|
92
|
-
|
|
93
|
-
".clbs-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
"
|
|
97
|
-
"
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
"
|
|
105
|
-
"
|
|
106
|
-
"
|
|
107
|
-
"
|
|
108
|
-
"
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
"
|
|
112
|
-
"
|
|
113
|
-
"
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
"
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
"
|
|
126
|
-
".
|
|
127
|
-
|
|
128
|
-
".
|
|
129
|
-
".
|
|
130
|
-
".
|
|
131
|
-
".
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
(function () {
|
|
182
|
-
|
|
183
|
-
react.createElement("
|
|
184
|
-
react.createElement("
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
: (
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
react.
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
react.createElement("
|
|
339
|
-
react.createElement("
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
react.createElement("
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
const
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
//
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
}, []);
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
.
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
};
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
.
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
};
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
//
|
|
536
|
-
const
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
} }, function () { return react.createElement(SettingsPanel, null); });
|
|
592
|
-
});
|
|
593
|
-
});
|
|
594
|
-
}
|
|
595
|
-
|
|
596
|
-
exports.apply = apply;
|
|
597
|
-
exports.inject = ["slots"];
|
|
598
|
-
return module.exports;
|
|
599
|
-
}
|
|
600
|
-
});
|
|
1
|
+
// cli-bridge-indicator — browser half (home-level plugin),一盏灯聚合本机 CLI。
|
|
2
|
+
// 每 1.2s 轮询 GET /cli-bridge/status → { providers:{...} },每个 provider 画一组 pill(有活动=全局
|
|
3
|
+
// 显示;空转「就绪」灯=仅本会话在该 preset 时显示)。settings 分区字段全由后端 meta 驱动。
|
|
4
|
+
// running→蓝呼吸 ⟳ ok→绿 ✓ failed→红 ✗ fallback→琥珀 ↩ idle→灰 标签。
|
|
5
|
+
//
|
|
6
|
+
// ⚠⚠⚠ 红线 4(历史事故:agy v1.6.1 / codebuddy v1.1.8):下方 __ModuleLoader__.load({ id })
|
|
7
|
+
// 必须【逐字等于 npm 包名】"@luchenglong/cli-bridge"(scoped 包=含 @scope/ 的完整名)。
|
|
8
|
+
// client-modules 的 graph row 以 package name 为 id(exports["./client"] 归属包),bundle
|
|
9
|
+
// 脚本执行后按 loaded without registering "<packageName>" 校验;一旦不等 → 整个 client
|
|
10
|
+
// combo 加载失败 → DSH 启动致命屏 → 强制安全模式。slot id 与 CSS 标记是另一命名空间,可自取。
|
|
11
|
+
// 改包名 = 必改此 id,二者绑定,永远一起改。
|
|
12
|
+
|
|
13
|
+
window.__ModuleLoader__.load({
|
|
14
|
+
id: "@luchenglong/cli-bridge",
|
|
15
|
+
factory: (require) => {
|
|
16
|
+
var module = { exports: {} };
|
|
17
|
+
var exports = module.exports;
|
|
18
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
19
|
+
|
|
20
|
+
let react = require("react");
|
|
21
|
+
|
|
22
|
+
// provider 展示元数据(与后端 registry 对齐;渲染顺序)。
|
|
23
|
+
// ⚠ 仅作 fallback:新 CLI 接入时后端视图(providers 键 + scheduling.all/labels)是
|
|
24
|
+
// 权威轴,本表不更新也只丢 presetId 的「就绪灯」优化,卡片/标签页/调度自动跟上。
|
|
25
|
+
var PROVIDERS = [
|
|
26
|
+
{ id: "agy", label: "AGY", presetId: "cordis-agy" },
|
|
27
|
+
{ id: "codebuddy", label: "CB", presetId: "codebuddy-first" },
|
|
28
|
+
{ id: "mimo", label: "MIMO", presetId: "cordis-mimo" }
|
|
29
|
+
];
|
|
30
|
+
const PRESET_ID_OF = {};
|
|
31
|
+
for (const m of PROVIDERS) PRESET_ID_OF[m.id] = m.presetId;
|
|
32
|
+
const FALLBACK_LABEL = {};
|
|
33
|
+
for (const m of PROVIDERS) FALLBACK_LABEL[m.id] = m.label;
|
|
34
|
+
|
|
35
|
+
// 后端驱动 id 轴:从调度视图(scheduling.all + labels)或 status 视图取全量 id,
|
|
36
|
+
// 本地 PROVIDERS 仅补 presetId(就绪灯判定用)。新增 provider = 后端 registry 一行,
|
|
37
|
+
// 本文件零改动。
|
|
38
|
+
function providerMetas(sc, extra) {
|
|
39
|
+
const ids = (sc && Array.isArray(sc.all)) ? sc.all.slice()
|
|
40
|
+
: (extra && Array.isArray(extra.all) ? extra.all.slice()
|
|
41
|
+
: PROVIDERS.map(function (m) { return m.id; }));
|
|
42
|
+
const labels = (sc && sc.labels) || (extra && extra.labels) || {};
|
|
43
|
+
const out = [];
|
|
44
|
+
for (const id of ids) {
|
|
45
|
+
out.push({ id: id, label: labels[id] || FALLBACK_LABEL[id] || String(id).toUpperCase(), presetId: PRESET_ID_OF[id] || null });
|
|
46
|
+
}
|
|
47
|
+
return out;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const CSS = [
|
|
51
|
+
".clb-row{display:inline-flex;align-items:center;gap:6px}.clb-ind{display:inline-flex;align-items:center;gap:6px;height:24px;padding:0 9px;border-radius:12px;border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-1);font-size:12px;line-height:1;color:var(--dsw-alias-label-secondary);white-space:nowrap;user-select:none}",
|
|
52
|
+
".clb-ind:hover{border-color:var(--dsw-alias-border-l2);cursor:pointer}.clb-dot{width:8px;height:8px;border-radius:50%;flex:0 0 auto;background:var(--dsw-alias-label-secondary)}",
|
|
53
|
+
".clb-ind b{font-weight:600}.clb-run .clb-dot{background:var(--dsw-static-blue-500,#3b82f6);animation:clb-pulse 1s ease-in-out infinite}",
|
|
54
|
+
".clb-ok .clb-dot{background:var(--dsw-static-green-500,#22c55e)}.clb-fail .clb-dot{background:var(--dsw-alias-state-error-primary)}",
|
|
55
|
+
".clb-fb .clb-dot{background:var(--dsw-alias-state-warn-primary)}.clb-run{color:var(--dsw-static-blue-500,#3b82f6);border-color:var(--dsw-static-blue-500,#3b82f6)}",
|
|
56
|
+
".clb-ok{color:var(--dsw-static-green-500,#22c55e);border-color:var(--dsw-static-green-500,#22c55e)}.clb-fb{color:var(--dsw-alias-state-warn-primary);border-color:var(--dsw-alias-state-warn-primary)}",
|
|
57
|
+
"@keyframes clb-pulse{0%{opacity:1;transform:scale(1)}50%{opacity:.35;transform:scale(.72)}100%{opacity:1;transform:scale(1)}}.clb-pop-overlay{position:fixed;inset:0;background:rgba(0,0,0,.32);z-index:10000;display:flex;align-items:center;justify-content:center}",
|
|
58
|
+
".clb-pop-panel{width:560px;max-width:92vw;max-height:76vh;display:flex;flex-direction:column;background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l2);border-radius:12px;box-shadow:0 12px 40px rgba(0,0,0,.35);font-size:12px;line-height:1.5;color:var(--dsw-alias-label-primary);overflow:hidden}.clb-pop-head{display:flex;align-items:center;justify-content:space-between;padding:10px 14px;border-bottom:1px solid var(--dsw-alias-border-l1);font-weight:600}",
|
|
59
|
+
".clb-pop-close{border:1px solid var(--dsw-alias-border-l1);background:transparent;color:var(--dsw-alias-label-secondary);border-radius:6px;width:22px;height:22px;line-height:1;font-size:13px;cursor:pointer}.clb-pop-close:hover{color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-border-l2)}",
|
|
60
|
+
".clb-pop-body{overflow:auto;padding:12px 14px}.clb-pop-sec{margin-bottom:14px;padding-bottom:12px;border-bottom:1px dashed var(--dsw-alias-border-l1)}",
|
|
61
|
+
".clb-pop-sec:last-child{border-bottom:none;margin-bottom:0;padding-bottom:0}.clb-pop-sech{font-weight:600;margin-bottom:6px}",
|
|
62
|
+
".clb-pop-proj{margin:6px 0;padding:8px 10px;border:1px solid var(--dsw-alias-border-l1);border-radius:8px}.clb-pop-proj-head{font-weight:600;margin-bottom:4px}",
|
|
63
|
+
".clb-pop-mono{font-family:Consolas,Menlo,monospace;font-size:11px;color:var(--dsw-alias-label-secondary)}.clb-pop-line{padding:1px 0}",
|
|
64
|
+
".clb-pop-cur{background:rgba(59,130,246,.12);border-radius:4px;padding:3px 6px;margin:4px 0}.clb-pop-empty{color:var(--dsw-alias-label-secondary);padding:4px 0}",
|
|
65
|
+
// ── 设置面板 ──────────────────────────────────────────────────────────
|
|
66
|
+
".clbs-root{display:flex;flex-direction:column;gap:16px;max-width:720px;font-size:13px;line-height:1.55;color:var(--dsw-alias-label-primary)}.clbs-h{font-size:14px;font-weight:600}",
|
|
67
|
+
".clbs-sub{color:var(--dsw-alias-label-secondary);font-size:12px;margin-top:2px}.clbs-card{border:1px solid var(--dsw-alias-border-l1);border-radius:8px;padding:12px 14px}",
|
|
68
|
+
".clbs-card-h{font-weight:600;font-size:13px;margin-bottom:10px}.clbs-tabs{display:flex;gap:6px;flex-wrap:wrap;border-bottom:1px solid var(--dsw-alias-border-l1);padding-bottom:10px}",
|
|
69
|
+
".clbs-tab{display:inline-flex;align-items:center;gap:6px;height:26px;padding:0 12px;border-radius:13px;border:1px solid var(--dsw-alias-border-l1);background:transparent;color:var(--dsw-alias-label-secondary);font-size:12px;cursor:pointer;font-family:inherit}.clbs-tab:hover{color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-border-l2)}",
|
|
70
|
+
".clbs-tab-on{background:var(--dsw-static-blue-500,#3b82f6);border-color:var(--dsw-static-blue-500,#3b82f6);color:#fff;font-weight:600}.clbs-tab-dot{width:7px;height:7px;border-radius:50%;background:var(--dsw-static-green-500,#22c55e);flex:none}",
|
|
71
|
+
".clbs-tab-dot-off{background:var(--dsw-alias-label-tertiary,var(--dsw-alias-border-l2))}.clbs-tab-rank{font-size:10px;opacity:.8}",
|
|
72
|
+
|
|
73
|
+
".clbs-card-off{opacity:.62}.clbs-card-hrow{display:flex;align-items:center;gap:8px;margin-bottom:10px}",
|
|
74
|
+
".clbs-card-t{font-weight:600;font-size:13px;flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.clbs-chip{display:inline-flex;align-items:center;height:18px;padding:0 8px;border-radius:9px;font-size:11px;font-weight:600;white-space:nowrap}",
|
|
75
|
+
".clbs-chip-on{background:rgba(34,197,94,.14);color:var(--dsw-static-green-500,#22c55e)}.clbs-chip-off{background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-tertiary,var(--dsw-alias-label-secondary))}",
|
|
76
|
+
".clbs-chip-rank{background:var(--dsw-static-blue-500,#3b82f6);color:#fff}.clbs-grid{display:grid;grid-template-columns:130px 1fr;gap:8px 12px;align-items:center}",
|
|
77
|
+
".clbs-label{color:var(--dsw-alias-label-secondary);text-align:right;font-size:12px}.clbs-ctl{display:flex;flex-direction:column;gap:4px;min-width:0}",
|
|
78
|
+
".clbs-note{color:var(--dsw-alias-label-tertiary,var(--dsw-alias-label-secondary));font-size:11px}.clbs-input,.clbs-select{width:100%;box-sizing:border-box;height:28px;padding:0 8px;border-radius:6px;border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-input,var(--dsw-alias-bg-layer-2));color:var(--dsw-alias-label-primary);font-size:12px;font-family:inherit;outline:none}",
|
|
79
|
+
".clbs-input:focus,.clbs-select:focus{border-color:var(--dsw-alias-border-brand,var(--dsw-static-blue-500,#3b82f6))}.clbs-check{display:flex;align-items:center;gap:6px;font-size:12px;color:var(--dsw-alias-label-secondary);cursor:pointer;user-select:none}",
|
|
80
|
+
".clbs-actions{display:flex;align-items:center;gap:10px}.clbs-btn{height:28px;padding:0 14px;border-radius:6px;border:1px solid var(--dsw-alias-border-brand,var(--dsw-static-blue-500,#3b82f6));background:var(--dsw-static-blue-500,#3b82f6);color:#fff;font-size:12px;cursor:pointer}",
|
|
81
|
+
".clbs-btn:hover{opacity:.9}.clbs-btn[disabled]{opacity:.6;cursor:default}",
|
|
82
|
+
".clbs-refresh{display:flex;align-items:center;gap:8px;flex-wrap:wrap}.clbs-refresh-msg{font-size:11px;color:var(--dsw-alias-label-secondary)}",
|
|
83
|
+
".clbs-btn:disabled{opacity:.5;cursor:default}.clbs-btn-ghost{border:1px solid var(--dsw-alias-border-l1);background:transparent;color:var(--dsw-alias-label-secondary)}",
|
|
84
|
+
".clbs-msg{font-size:12px}.clbs-msg-ok{color:var(--dsw-static-green-500,#22c55e)}",
|
|
85
|
+
".clbs-msg-err{color:var(--dsw-alias-state-error-primary)}.clbs-diag{margin-top:8px;border:1px solid var(--dsw-alias-border-l1);border-radius:8px;overflow:hidden}",
|
|
86
|
+
".clbs-diag-h{padding:6px 10px;background:var(--dsw-alias-bg-layer-2);font-weight:600;font-size:12px;border-bottom:1px solid var(--dsw-alias-border-l1)}.clbs-diag-row{display:grid;grid-template-columns:150px 1fr auto;gap:8px;padding:7px 10px;border-bottom:1px dashed var(--dsw-alias-border-l1);align-items:center;font-size:12px}",
|
|
87
|
+
".clbs-diag-row:last-child{border-bottom:none}.clbs-diag-mono{font-family:Consolas,Menlo,monospace;font-size:11px;color:var(--dsw-alias-label-secondary);word-break:break-all}",
|
|
88
|
+
".clbs-badge{display:inline-flex;align-items:center;gap:4px;height:18px;padding:0 7px;border-radius:9px;font-size:11px;white-space:nowrap}.clbs-badge-ok{color:var(--dsw-static-green-500,#22c55e);border:1px solid var(--dsw-static-green-500,#22c55e)}",
|
|
89
|
+
".clbs-badge-warn{color:var(--dsw-alias-state-warn-primary);border:1px solid var(--dsw-alias-state-warn-primary)}.clbs-badge-err{color:var(--dsw-alias-state-error-primary);border:1px solid var(--dsw-alias-state-error-primary)}",
|
|
90
|
+
".clbs-disp-row{display:grid;grid-template-columns:auto auto 1fr auto;gap:10px;align-items:center;padding:6px 0;border-bottom:1px dashed var(--dsw-alias-border-l1)}.clbs-disp-row:last-child{border-bottom:none}",
|
|
91
|
+
".clbs-disp-name{font-weight:600;font-size:12px}.clbs-disp-off .clbs-disp-name{color:var(--dsw-alias-label-tertiary,var(--dsw-alias-label-secondary));font-weight:400}",
|
|
92
|
+
".clbs-disp-ord{display:inline-flex;align-items:center;justify-content:center;min-width:18px;height:18px;border-radius:9px;background:var(--dsw-static-blue-500,#3b82f6);color:#fff;font-size:11px;font-weight:600}.clbs-disp-btn{width:24px;height:22px;border-radius:5px;border:1px solid var(--dsw-alias-border-l1);background:transparent;color:var(--dsw-alias-label-secondary);font-size:12px;line-height:1;cursor:pointer;padding:0}",
|
|
93
|
+
".clbs-disp-btn:hover{border-color:var(--dsw-alias-border-l2);color:var(--dsw-alias-label-primary)}.clbs-disp-btn:disabled{opacity:.3;cursor:default}",
|
|
94
|
+
].join("");
|
|
95
|
+
if (typeof document !== "undefined" && document.querySelector('style[data-plugin-css="cli-bridge-indicator"]') === null) {
|
|
96
|
+
const tag = document.createElement("style");
|
|
97
|
+
tag.setAttribute("data-plugin", "cli-bridge-indicator");
|
|
98
|
+
tag.setAttribute("data-plugin-css", "cli-bridge-indicator");
|
|
99
|
+
tag.textContent = CSS;
|
|
100
|
+
document.head.appendChild(tag);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function pillClass(state) {
|
|
104
|
+
if (state === "running") return " clb-run";
|
|
105
|
+
if (state === "ok") return " clb-ok";
|
|
106
|
+
if (state === "failed") return " clb-fail";
|
|
107
|
+
if (state === "fallback") return " clb-fb";
|
|
108
|
+
return "";
|
|
109
|
+
}
|
|
110
|
+
function pillText(label, state, running) {
|
|
111
|
+
if (state === "running") return "\u27F3 " + label + (running > 1 ? " \u00D7" + running : "");
|
|
112
|
+
if (state === "ok") return "\u2713 " + label;
|
|
113
|
+
if (state === "failed") return "\u2717 " + label;
|
|
114
|
+
if (state === "fallback") return "\u21A9 " + label;
|
|
115
|
+
return label;
|
|
116
|
+
}
|
|
117
|
+
function argText(a) {
|
|
118
|
+
if (a === undefined || a === null) return "";
|
|
119
|
+
try { const j = JSON.stringify(a); return j.length > 140 ? j.slice(0, 137) + "\u2026" : j; } catch (e) { return String(a); }
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function Pill(props) {
|
|
123
|
+
const p = props.p, label = props.label;
|
|
124
|
+
const parts = [];
|
|
125
|
+
parts.push("project: " + (p.name || p.cwd));
|
|
126
|
+
if (p.current) { const c = p.current; parts.push("step " + c.stepIndex + " \u2192 " + c.tool + (c.args ? " " + argText(c.args) : "")); }
|
|
127
|
+
else if (p.running > 0) parts.push("(starting / thinking)");
|
|
128
|
+
if (p.trail && p.trail.length) parts.push("recent: " + p.trail.slice(-3).map(function (e) { return e.state + " " + e.tool; }).join(" | "));
|
|
129
|
+
if (p.lastStatus) parts.push("last=" + p.lastStatus + (p.lastSessionId ? " " + String(p.lastSessionId).slice(0, 8) : ""));
|
|
130
|
+
if (p.lastBackend) parts.push("backend=" + p.lastBackend);
|
|
131
|
+
return react.createElement("div", { className: "clb-ind" + pillClass(p.state), title: label.toLowerCase() + " [" + p.state + (p.running > 0 ? " \u00D7" + p.running : "") + "] " + p.cwd + "\n" + parts.join("\n"), onClick: props.onClick },
|
|
132
|
+
react.createElement("span", { className: "clb-dot" }),
|
|
133
|
+
react.createElement("span", null, react.createElement("b", null, pillText(label, p.state, p.running))));
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function ProjectRow(props) {
|
|
137
|
+
const p = props.p;
|
|
138
|
+
const head = (p.state === "running" ? "\u27F3 " : p.state === "ok" ? "\u2713 " : p.state === "failed" ? "\u2717 " : p.state === "fallback" ? "\u21A9 " : "") + (p.name || p.cwd);
|
|
139
|
+
const badge = "[" + p.state + (p.running > 0 ? " \u00D7" + p.running : "") + "]";
|
|
140
|
+
return react.createElement("div", { className: "clb-pop-proj" },
|
|
141
|
+
react.createElement("div", { className: "clb-pop-proj-head" }, head, " ", react.createElement("span", { className: "clb-pop-mono" }, badge)),
|
|
142
|
+
react.createElement("div", { className: "clb-pop-line clb-pop-mono" }, p.cwd),
|
|
143
|
+
(function () {
|
|
144
|
+
if (p.current) { const c = p.current; return react.createElement("div", { className: "clb-pop-cur" },
|
|
145
|
+
react.createElement("div", null, "当前: step " + c.stepIndex + " \u2192 " + c.tool),
|
|
146
|
+
react.createElement("div", { className: "clb-pop-mono" }, c.args ? argText(c.args) : "")); }
|
|
147
|
+
if (p.running > 0) return react.createElement("div", { className: "clb-pop-cur" }, "(starting / thinking\u2026)");
|
|
148
|
+
return null;
|
|
149
|
+
})(),
|
|
150
|
+
(p.trail && p.trail.length) ? react.createElement("div", null,
|
|
151
|
+
react.createElement("div", { className: "clb-pop-line", style: { marginTop: "6px", color: "var(--dsw-alias-label-secondary)" } }, "最近步骤:"),
|
|
152
|
+
p.trail.slice(-6).map(function (e, j) {
|
|
153
|
+
return react.createElement("div", { key: "t" + j, className: "clb-pop-line clb-pop-mono" },
|
|
154
|
+
"[" + e.state + "] step " + e.stepIndex + " " + e.tool + (e.args ? " " + argText(e.args) : ""));
|
|
155
|
+
})) : null,
|
|
156
|
+
(p.running > 0 && p.updatedAt) ? react.createElement("div", { className: "clb-pop-line", style: { marginTop: "6px", color: (Date.now() - p.updatedAt > 90000 ? "var(--dsw-alias-state-warn-primary)" : "var(--dsw-alias-label-secondary)") } },
|
|
157
|
+
"无活动 " + Math.max(0, Math.round((Date.now() - p.updatedAt) / 1000)) + "s" + (Date.now() - p.updatedAt > 90000 ? "(长任务请耐心;疑似卡住可取消重试)" : "")) : null,
|
|
158
|
+
(p.lastStatus) ? react.createElement("div", { className: "clb-pop-line clb-pop-mono", style: { marginTop: "6px" } },
|
|
159
|
+
"last=" + p.lastStatus + (p.lastSessionId ? " " + String(p.lastSessionId).slice(0, 8) : "")) : null);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function Popup(props) {
|
|
163
|
+
const s = props.s, onClose = props.onClose;
|
|
164
|
+
const sections = [];
|
|
165
|
+
const metas = providerMetas(null, (s && s.providers) ? { all: Object.keys(s.providers), labels: (s.scheduling && s.scheduling.labels) } : null);
|
|
166
|
+
for (const meta of metas) {
|
|
167
|
+
const b = (s && s.providers && s.providers[meta.id]) || null;
|
|
168
|
+
const ready = props.iAm[meta.id];
|
|
169
|
+
const hasProjects = b && Array.isArray(b.projects) && b.projects.length;
|
|
170
|
+
if (!hasProjects && !(ready === true || (ready === null && b && b.presetActive))) continue;
|
|
171
|
+
let rows;
|
|
172
|
+
if (hasProjects) rows = b.projects.map(function (p, i) { return react.createElement(ProjectRow, { key: p.cwd || ("p" + i), p: p }); });
|
|
173
|
+
else rows = react.createElement("div", { className: "clb-pop-empty" }, "(就绪,暂无活动)");
|
|
174
|
+
const modeText = ready === true ? " · 本会话 " + meta.label + " 优先" : (ready === false ? " · 普通模式" : (b && b.presetActive ? " · " + meta.label + " 优先(其他会话)" : " · 普通模式"));
|
|
175
|
+
sections.push(react.createElement("div", { key: meta.id, className: "clb-pop-sec" },
|
|
176
|
+
react.createElement("div", { className: "clb-pop-sech" }, meta.label + " 状态" + (b && b.state ? " · " + b.state + (b.running > 0 ? " (" + b.running + " running)" : "") : "") + modeText),
|
|
177
|
+
rows));
|
|
178
|
+
}
|
|
179
|
+
if (!sections.length) sections.push(react.createElement("div", { className: "clb-pop-empty" }, "暂无 CLI Bridge 活动"));
|
|
180
|
+
return react.createElement("div", { className: "clb-pop-overlay", onClick: onClose },
|
|
181
|
+
react.createElement("div", { className: "clb-pop-panel", onClick: function (e) { e.stopPropagation(); } },
|
|
182
|
+
react.createElement("div", { className: "clb-pop-head" },
|
|
183
|
+
react.createElement("span", null, "CLI Bridge"),
|
|
184
|
+
react.createElement("button", { className: "clb-pop-close", title: "关闭 (Esc)", onClick: onClose }, "\u2715")),
|
|
185
|
+
react.createElement("div", { className: "clb-pop-body" }, sections)));
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// per-session preset 三态判定(同 codebuddy v1.1.3:host 名单 + 客户端摘要双通道,
|
|
189
|
+
// 任一肯定即肯定;都否定才否定;无结论回退全局租约)。myPreset 是会话的单个
|
|
190
|
+
// agentPreset 值,对每个 provider 比较各自的 presetId。
|
|
191
|
+
const PRESET_UNKNOWN = "\u0000unknown";
|
|
192
|
+
function subscribeNoop() { return function () { }; }
|
|
193
|
+
function presetOfSummary(sum) {
|
|
194
|
+
if (!sum) return PRESET_UNKNOWN;
|
|
195
|
+
if (sum.projectionValues && typeof sum.projectionValues.agentPreset === "string") return sum.projectionValues.agentPreset;
|
|
196
|
+
if (typeof sum.agentPreset === "string") return sum.agentPreset;
|
|
197
|
+
return PRESET_UNKNOWN;
|
|
198
|
+
}
|
|
199
|
+
function presetOfState(state, sessionId) {
|
|
200
|
+
try { if (!state || !sessionId || !state.byId) return PRESET_UNKNOWN; return presetOfSummary(state.byId[sessionId]); }
|
|
201
|
+
catch (e) { return PRESET_UNKNOWN; }
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function Indicator(props) {
|
|
205
|
+
const p = props || {};
|
|
206
|
+
const sessionId = (typeof p.sessionId === "string" && p.sessionId) || (typeof p.injectedSessionId === "string" && p.injectedSessionId) || undefined;
|
|
207
|
+
const useSess = typeof p.useSessions === "function" ? p.useSessions : null;
|
|
208
|
+
const sessionsSvc = p.sessionsSvc;
|
|
209
|
+
const st = react.useState(null); const s = st[0]; const setS = st[1];
|
|
210
|
+
const ot = react.useState(false); const open = ot[0]; const setOpen = ot[1];
|
|
211
|
+
let myPreset;
|
|
212
|
+
if (useSess) {
|
|
213
|
+
myPreset = useSess(function (state) { return presetOfState(state, sessionId); });
|
|
214
|
+
} else {
|
|
215
|
+
myPreset = react.useSyncExternalStore(
|
|
216
|
+
(sessionsSvc && sessionsSvc.list) ? sessionsSvc.list.subscribe : subscribeNoop,
|
|
217
|
+
function () {
|
|
218
|
+
try {
|
|
219
|
+
if (!sessionsSvc || !sessionsSvc.list) return PRESET_UNKNOWN;
|
|
220
|
+
return presetOfState(sessionsSvc.list.getSnapshot(), sessionId);
|
|
221
|
+
} catch (e) { return PRESET_UNKNOWN; }
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
const presetKnown = myPreset !== PRESET_UNKNOWN;
|
|
225
|
+
const metas = providerMetas(null, (s && s.providers) ? { all: Object.keys(s.providers), labels: (s.scheduling && s.scheduling.labels) } : null);
|
|
226
|
+
const iAm = {};
|
|
227
|
+
for (const meta of metas) {
|
|
228
|
+
const b = (s && s.providers && s.providers[meta.id]) || null;
|
|
229
|
+
const hostList = (b && Array.isArray(b.presetSessions)) ? b.presetSessions : null;
|
|
230
|
+
const inHostList = !!(hostList && sessionId && hostList.indexOf(sessionId) >= 0);
|
|
231
|
+
const clientSaysYes = presetKnown && myPreset === meta.presetId;
|
|
232
|
+
iAm[meta.id] = (inHostList || clientSaysYes)
|
|
233
|
+
? true
|
|
234
|
+
: (presetKnown ? false
|
|
235
|
+
: ((hostList && hostList.length > 0 && sessionId) ? false : null));
|
|
236
|
+
}
|
|
237
|
+
react.useEffect(function () {
|
|
238
|
+
let alive = true; let timerId = null;
|
|
239
|
+
const tick = function () {
|
|
240
|
+
fetch("/cli-bridge/status", { cache: "no-store" })
|
|
241
|
+
.then(function (r) { return r.ok ? r.json() : null; })
|
|
242
|
+
.then(function (v) { if (alive) setS(v); })
|
|
243
|
+
.catch(function () { });
|
|
244
|
+
};
|
|
245
|
+
tick(); timerId = setInterval(tick, 1200);
|
|
246
|
+
return function () { alive = false; if (timerId !== null) clearInterval(timerId); };
|
|
247
|
+
}, []);
|
|
248
|
+
react.useEffect(function () {
|
|
249
|
+
if (!open) return;
|
|
250
|
+
const h = function (e) { if (e.key === "Escape") setOpen(false); };
|
|
251
|
+
window.addEventListener("keydown", h);
|
|
252
|
+
return function () { window.removeEventListener("keydown", h); };
|
|
253
|
+
}, [open]);
|
|
254
|
+
const pills = [];
|
|
255
|
+
// 陈旧过滤(0.2.1 修复「指示灯长期存在」):stale 项目(>10min 无活动且未运行)不再
|
|
256
|
+
// 渲染成常亮灯。历史事故:一次失败后 ✗ AGY 灯永久挂在那 —— 弹窗里仍列 stale 项目作
|
|
257
|
+
// 历史,但 pill 行只认新鲜活动。
|
|
258
|
+
for (const meta of metas) {
|
|
259
|
+
const b = (s && s.providers && s.providers[meta.id]) || null;
|
|
260
|
+
if (!b) continue;
|
|
261
|
+
const fresh = (Array.isArray(b.projects) ? b.projects : []).filter(function (p) { return !p.stale; });
|
|
262
|
+
const readyShow = iAm[meta.id] === true || (iAm[meta.id] === null && b.presetActive);
|
|
263
|
+
if (fresh.length) {
|
|
264
|
+
for (let i = 0; i < fresh.length; i++) pills.push(react.createElement(Pill, { key: meta.id + ":" + (fresh[i].cwd || i), p: fresh[i], label: meta.label, onClick: function () { setOpen(true); } }));
|
|
265
|
+
} else if (readyShow) {
|
|
266
|
+
const state = b.state || "idle";
|
|
267
|
+
let text = meta.label + " 就绪";
|
|
268
|
+
if (state === "running") text = meta.label + " 工作中" + (b.running > 1 ? " \u00D7" + b.running : "");
|
|
269
|
+
else if (state === "ok") text = meta.label;
|
|
270
|
+
else if (state === "failed") text = meta.label + " 失败";
|
|
271
|
+
else if (state === "fallback") text = "本地回退";
|
|
272
|
+
pills.push(react.createElement("div", { key: meta.id + ":ready", className: "clb-ind" + pillClass(state), title: meta.id + " state=" + state + " running=" + (b.running || 0), onClick: function () { setOpen(true); } },
|
|
273
|
+
react.createElement("span", { className: "clb-dot" }), react.createElement("span", null, text)));
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
if (!pills.length) return null;
|
|
277
|
+
const light = react.createElement("div", { className: "clb-row" }, pills);
|
|
278
|
+
if (!open) return light;
|
|
279
|
+
return react.createElement(react.Fragment, null, light,
|
|
280
|
+
react.createElement(Popup, { s: s, iAm: iAm, onClose: function () { setOpen(false); } }));
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// ── 设置面板(settings.section):字段由后端 providers[id].meta.fields 驱动 ──
|
|
284
|
+
// 「手动拉取」按钮(契约 §3.2):面板**不自动探测**(避免首屏被 spawnSync 拖慢,
|
|
285
|
+
// 也避免探不到时下拉空着分不清"没模型"与"没拉到")。用户点按钮才 POST 到
|
|
286
|
+
// /cli-bridge/settings/models,拿到 options 后只更新展示态,不落盘。
|
|
287
|
+
function RefreshRow(props) {
|
|
288
|
+
const st = react.useState(""), busy = st[0], setBusy = st[1];
|
|
289
|
+
const ms = react.useState(""), msg = ms[0], setMsg = ms[1];
|
|
290
|
+
const run = function () {
|
|
291
|
+
setBusy("拉取中…"); setMsg("");
|
|
292
|
+
fetch("/cli-bridge/settings/models", {
|
|
293
|
+
method: "POST", headers: { "Content-Type": "application/json" },
|
|
294
|
+
body: JSON.stringify({ provider: props.provider })
|
|
295
|
+
}).then(function (r) { return r.json(); }).then(function (j) {
|
|
296
|
+
setBusy("");
|
|
297
|
+
if (!j || !j.ok) { setMsg("✗ " + ((j && j.error) || "拉取失败")); return; }
|
|
298
|
+
const list = Array.isArray(j.options) ? j.options : [];
|
|
299
|
+
if (!list.length) { setMsg("✗ 端点未返回任何可用项"); return; }
|
|
300
|
+
props.onOptions(list);
|
|
301
|
+
setMsg("✓ 已获取 " + list.length + " 项" + (j.source ? "(来源:" + j.source + ")" : ""));
|
|
302
|
+
}).catch(function (e) { setBusy(""); setMsg("✗ " + String((e && e.message) || e)); });
|
|
303
|
+
};
|
|
304
|
+
return react.createElement("div", { className: "clbs-refresh" },
|
|
305
|
+
react.createElement("button", { type: "button", className: "clbs-btn", disabled: !!busy, onClick: run },
|
|
306
|
+
busy || (props.label || "拉取可用项")),
|
|
307
|
+
msg ? react.createElement("span", { className: "clbs-refresh-msg" }, msg) : null);
|
|
308
|
+
}
|
|
309
|
+
function Field(props) {
|
|
310
|
+
const f = props.field, val = props.value, onChange = props.onChange, secretState = props.secret;
|
|
311
|
+
// 手动拉取的展示态:拿到后只用它渲染下拉,用户选中仍走正常 onChange→POST 保存。
|
|
312
|
+
const fs2 = react.useState(null); const fetched = fs2[0]; const setFetched = fs2[1];
|
|
313
|
+
if (f.type === "select") {
|
|
314
|
+
const opts = fetched || (Array.isArray(f.options) ? f.options : []);
|
|
315
|
+
const isKnown = opts.some(function (o) { return String(o.value) === String(val); });
|
|
316
|
+
const CUSTOM = "__clb_custom__";
|
|
317
|
+
// wantCustom:用户显式选了「自定义…」后保持输入框可见(即使此刻值为空)。
|
|
318
|
+
const wc = react.useState(false); const wantCustom = wc[0]; const setWantCustom = wc[1];
|
|
319
|
+
const showCustom = f.allowCustom && (wantCustom || (val !== "" && !isKnown));
|
|
320
|
+
const sel = showCustom && (wantCustom || !isKnown) && val !== "" ? val : (val === "" && wantCustom ? CUSTOM : val);
|
|
321
|
+
const onPick = function (e) {
|
|
322
|
+
const v = e.target.value;
|
|
323
|
+
if (v === CUSTOM) { setWantCustom(true); onChange(""); return; }
|
|
324
|
+
setWantCustom(false); onChange(v);
|
|
325
|
+
};
|
|
326
|
+
return react.createElement("div", { style: { display: "flex", flexDirection: "column", gap: "4px" } },
|
|
327
|
+
react.createElement("select", { className: "clbs-select", value: sel, onChange: onPick },
|
|
328
|
+
react.createElement("option", { key: "__empty", value: "" }, f.placeholder || "(留空 = 默认)"),
|
|
329
|
+
opts.map(function (o) { return react.createElement("option", { key: o.value, value: o.value }, o.label || o.value); }),
|
|
330
|
+
f.allowCustom ? react.createElement("option", { key: "__custom", value: CUSTOM }, "自定义…(手动输入)") : null),
|
|
331
|
+
showCustom ? react.createElement("input", { className: "clbs-input", value: val, onChange: function (e) { onChange(e.target.value); }, placeholder: "手动输入值", spellCheck: false }) : null,
|
|
332
|
+
f.refresh ? react.createElement(RefreshRow, { provider: props.providerId, label: f.refresh.label, onOptions: setFetched }) : null,
|
|
333
|
+
f.note ? react.createElement("div", { className: "clbs-note" }, f.note) : null);
|
|
334
|
+
}
|
|
335
|
+
if (f.secret) {
|
|
336
|
+
return react.createElement("div", { className: "clbs-ctl" },
|
|
337
|
+
react.createElement("input", { className: "clbs-input", type: "password", value: val, onChange: function (e) { onChange(e.target.value); }, placeholder: secretState && secretState.set ? ("已保存 " + secretState.hint + ",留空保持不变") : (f.placeholder || "留空保持不变"), spellCheck: false, autoComplete: "off" }),
|
|
338
|
+
react.createElement("label", { className: "clbs-check" },
|
|
339
|
+
react.createElement("input", { type: "checkbox", checked: !!props.wantClear, onChange: function (e) { props.onClear(e.target.checked); } }),
|
|
340
|
+
"清除已保存的值"),
|
|
341
|
+
f.note ? react.createElement("div", { className: "clbs-note" }, f.note) : null);
|
|
342
|
+
}
|
|
343
|
+
return react.createElement("div", { className: "clbs-ctl" },
|
|
344
|
+
react.createElement("input", { className: "clbs-input", value: val, onChange: function (e) { onChange(e.target.value); }, placeholder: f.placeholder || "", spellCheck: false }),
|
|
345
|
+
f.note ? react.createElement("div", { className: "clbs-note" }, f.note) : null);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// ── 调度卡(0.2.0):勾选启用哪些 CLI + 上下移排定调用优先级(顺序=优先级)──
|
|
349
|
+
// ord 数组 = 全量 id 的展示/排序顺序;enabled = 启用的 id 集合。未启用者沉到列表
|
|
350
|
+
// 底部但仍可 ↑↓(改的是 priority,不代表顺序变了就自动启用)。优先级只对启用项有意义,
|
|
351
|
+
// 后端 order 派生自 priority ∩ enabled,UI 这里把 disabled 项以灰显示但保留其 priority 位。
|
|
352
|
+
function DispatchCard(props) {
|
|
353
|
+
const ord = props.ord, enabled = props.enabled, labels = props.labels;
|
|
354
|
+
const toggle = props.onToggle, move = props.onMove;
|
|
355
|
+
const supported = props.supported !== false;
|
|
356
|
+
const anyEnabled = enabled.length > 0;
|
|
357
|
+
const zh = (function () { try { return String(navigator.language || "").toLowerCase().indexOf("zh") === 0; } catch (e) { return false; } })();
|
|
358
|
+
const rankOf = {};
|
|
359
|
+
// 展示序 = ord 全量;序号徽标只对启用项计数。
|
|
360
|
+
let n = 0;
|
|
361
|
+
for (const id of ord) { if (enabled.indexOf(id) >= 0) { rankOf[id] = (++n); } }
|
|
362
|
+
return react.createElement("div", { className: "clbs-card" },
|
|
363
|
+
react.createElement("div", { className: "clbs-card-h" }, zh ? "调度 · 启用与优先级" : "Dispatch · enable & priority"),
|
|
364
|
+
react.createElement("div", { className: "clbs-sub", style: { margin: "-4px 0 8px" } },
|
|
365
|
+
zh ? "勾选要启用的 CLI(决定调用面数量),↑↓ 排定智能体的调用优先级(首位最高)。保存后 preset / 动态 / MCP 三形态生效(MCP 需重启子进程)。"
|
|
366
|
+
: "Check which CLIs to enable, use ↑↓ to set call priority (top = highest)."),
|
|
367
|
+
supported ? null
|
|
368
|
+
: react.createElement("div", { className: "clbs-msg clbs-msg-err", style: { marginBottom: "8px" } },
|
|
369
|
+
zh ? "当前运行的宿主尚不支持调度(本面板为新版本):请重启 DSH 后重新打开设置再修改启用/优先级。"
|
|
370
|
+
: "The running host does not support scheduling yet; restart DSH before changing this card."),
|
|
371
|
+
ord.map(function (id, idx) {
|
|
372
|
+
const on = enabled.indexOf(id) >= 0;
|
|
373
|
+
return react.createElement("div", { key: id, className: "clbs-disp-row" + (on ? "" : " clbs-disp-off") },
|
|
374
|
+
react.createElement("label", { className: "clbs-check" },
|
|
375
|
+
react.createElement("input", { type: "checkbox", checked: on, disabled: !supported, onChange: function () { toggle(id); } })),
|
|
376
|
+
react.createElement("span", null,
|
|
377
|
+
on ? react.createElement("span", { className: "clbs-disp-ord" }, String(rankOf[id])) : react.createElement("span", { className: "clbs-disp-ord", style: { background: "transparent", color: "var(--dsw-alias-label-tertiary,var(--dsw-alias-label-secondary))", border: "1px dashed var(--dsw-alias-border-l1)" }, title: zh ? "未启用" : "disabled" }, "—"),
|
|
378
|
+
react.createElement("span", { className: "clbs-disp-name", style: { marginLeft: "8px" } }, (labels && labels[id]) || id)),
|
|
379
|
+
react.createElement("span", null),
|
|
380
|
+
react.createElement("span", { style: { display: "inline-flex", gap: "4px" } },
|
|
381
|
+
react.createElement("button", { className: "clbs-disp-btn", type: "button", disabled: idx === 0 || !supported, onClick: function () { move(idx, -1); }, title: zh ? "上移(提高优先级)" : "up" }, "\u2191"),
|
|
382
|
+
react.createElement("button", { className: "clbs-disp-btn", type: "button", disabled: idx === ord.length - 1 || !supported, onClick: function () { move(idx, 1); }, title: zh ? "下移(降低优先级)" : "down" }, "\u2193")));
|
|
383
|
+
}),
|
|
384
|
+
!anyEnabled ? react.createElement("div", { className: "clbs-msg clbs-msg-err", style: { marginTop: "8px" } },
|
|
385
|
+
zh ? "⚠ 全部禁用:三形态都不会注册任何 CLI 工具。" : "All disabled: no CLI tools will be registered.") : null);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
function ProviderSettingsCard(props) {
|
|
389
|
+
const meta = props.meta, view = props.view, form = props.form, setField = props.setField, clears = props.clears, setClear = props.setClear;
|
|
390
|
+
if (!view || !view.present) return null;
|
|
391
|
+
const fields = (view.meta && Array.isArray(view.meta.fields)) ? view.meta.fields : [];
|
|
392
|
+
// 卡片头状态条:调度启用/禁用 + 优先级名次(来自调度视图,与调度卡同源,两处永远一致)。
|
|
393
|
+
const chip = props.on ? react.createElement("span", { className: "clbs-chip clbs-chip-on" }, props.zh ? "启用" : "on")
|
|
394
|
+
: react.createElement("span", { className: "clbs-chip clbs-chip-off" }, props.zh ? "未启用" : "off");
|
|
395
|
+
const rank = props.on && props.rank ? react.createElement("span", { className: "clbs-chip clbs-chip-rank", title: props.zh ? "调用优先级" : "call priority" }, "#" + props.rank) : null;
|
|
396
|
+
return react.createElement("div", { className: "clbs-card" + (props.on ? "" : " clbs-card-off") },
|
|
397
|
+
react.createElement("div", { className: "clbs-card-hrow" },
|
|
398
|
+
react.createElement("span", { className: "clbs-card-t" }, meta.label + (view.meta && view.meta.titleSuffix ? " · " + view.meta.titleSuffix : "")),
|
|
399
|
+
rank, chip),
|
|
400
|
+
fields.length ? react.createElement("div", { className: "clbs-grid" },
|
|
401
|
+
fields.map(function (f) {
|
|
402
|
+
return react.createElement(react.Fragment, { key: f.key },
|
|
403
|
+
react.createElement("label", { className: "clbs-label" }, f.label || f.key),
|
|
404
|
+
react.createElement(Field, {
|
|
405
|
+
field: f, providerId: meta.id, value: form[f.key] || "", secret: view.secrets && view.secrets[f.key], wantClear: !!(clears[meta.id] && clears[meta.id][f.key]),
|
|
406
|
+
onChange: function (v) { setField(meta.id, f.key, v); }, onClear: function (b) { setClear(meta.id, f.key, b); }
|
|
407
|
+
}));
|
|
408
|
+
})) : react.createElement("div", { className: "clbs-note" }, "(无可配置项)"),
|
|
409
|
+
(!props.on) ? react.createElement("div", { className: "clbs-note", style: { marginTop: "8px" } },
|
|
410
|
+
props.zh ? "此 CLI 已在「调度」卡停用:三形态不会注册它的工具/政策段。下方偏好仍会保存并在重新启用后生效。"
|
|
411
|
+
: "Disabled in the dispatch card: no tools/policy will register. Preferences below are still saved.") : null,
|
|
412
|
+
(view.diagnosis) ? react.createElement("div", { className: "clbs-diag" },
|
|
413
|
+
react.createElement("div", { className: "clbs-diag-h" }, "诊断"),
|
|
414
|
+
react.createElement("div", { style: { padding: "7px 10px", fontSize: "12px" } }, view.diagnosis)) : null);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
function SettingsPanel() {
|
|
418
|
+
const vt = react.useState(null); const view = vt[0]; const setView = vt[1];
|
|
419
|
+
const ft = react.useState({}); const form = ft[0]; const setForm = ft[1];
|
|
420
|
+
const ct = react.useState({}); const clears = ct[0]; const setClears = ct[1];
|
|
421
|
+
const mt = react.useState(null); const msg = mt[0]; const setMsg = mt[1];
|
|
422
|
+
const lt = react.useState(false); const loading = lt[0]; const setLoading = lt[1];
|
|
423
|
+
// 调度状态:disp.ord = 全量 id 的排序顺序(= priority),disp.enabled = 启用集合。
|
|
424
|
+
const dt = react.useState({ ord: [], enabled: [] }); const disp = dt[0]; const setDisp = dt[1];
|
|
425
|
+
// 标签页选择(0.2.2 横向卡片):必须在早期 return 之前声明(React hook 顺序恒定)。
|
|
426
|
+
const tt = react.useState("global"); const tab = tt[0]; const setTab = tt[1];
|
|
427
|
+
|
|
428
|
+
const adopt = react.useCallback(function (v) {
|
|
429
|
+
setView(v);
|
|
430
|
+
const next = {};
|
|
431
|
+
// 后端驱动:以视图 providers 键为轴(新增 provider 零改动),本地表仅兜底。
|
|
432
|
+
const viewIds = (v && v.providers) ? Object.keys(v.providers) : PROVIDERS.map(function (m) { return m.id; });
|
|
433
|
+
for (const meta of providerMetas(v && v.scheduling, { all: viewIds })) {
|
|
434
|
+
const pv = (v && v.providers && v.providers[meta.id]) || {};
|
|
435
|
+
next[meta.id] = Object.assign({}, pv.values || {});
|
|
436
|
+
const secs = pv.secrets || {};
|
|
437
|
+
for (const k of Object.keys(secs)) next[meta.id][k] = "";
|
|
438
|
+
}
|
|
439
|
+
setForm(next);
|
|
440
|
+
// 调度视图:后端 scheduling.priority = 全量 id 的完整顺序(未启用的也在其中,
|
|
441
|
+
// 保留其位置,灰显可移动);scheduling.enabled = 启用集合。启用项的序号徽标
|
|
442
|
+
// = 它们在 priority 里的相对次序(后端 order 同派生规则,UI 与生效面一致)。
|
|
443
|
+
const sc = (v && v.scheduling) || {};
|
|
444
|
+
const all = Array.isArray(sc.all) ? sc.all : PROVIDERS.map(function (m) { return m.id; });
|
|
445
|
+
const en = Array.isArray(sc.enabled) ? sc.enabled.filter(function (x) { return all.indexOf(x) >= 0; }) : all.slice();
|
|
446
|
+
const srcOrd = Array.isArray(sc.priority) && sc.priority.length ? sc.priority : (Array.isArray(sc.order) ? sc.order : en);
|
|
447
|
+
const ord = srcOrd.filter(function (x) { return all.indexOf(x) >= 0; });
|
|
448
|
+
for (const id of all) if (ord.indexOf(id) < 0) ord.push(id);
|
|
449
|
+
setDisp({ ord: ord, enabled: en });
|
|
450
|
+
}, []);
|
|
451
|
+
const reload = react.useCallback(function () {
|
|
452
|
+
fetch("/cli-bridge/settings", { cache: "no-store" })
|
|
453
|
+
.then(function (r) { return r.ok ? r.json() : Promise.reject(new Error("HTTP " + r.status)); })
|
|
454
|
+
.then(adopt)
|
|
455
|
+
.catch(function (e) { setMsg({ kind: "err", text: "读取失败:" + e.message }); });
|
|
456
|
+
}, [adopt]);
|
|
457
|
+
react.useEffect(function () { reload(); }, [reload]);
|
|
458
|
+
|
|
459
|
+
const setField = function (id, k, v) { setForm(function (f) { const n = Object.assign({}, f); n[id] = Object.assign({}, n[id]); n[id][k] = v; return n; }); };
|
|
460
|
+
const setClear = function (id, k, b) { setClears(function (c) { const n = Object.assign({}, c); n[id] = Object.assign({}, n[id]); n[id][k] = b; return n; }); };
|
|
461
|
+
|
|
462
|
+
const dispToggle = function (id) {
|
|
463
|
+
setDisp(function (d) {
|
|
464
|
+
const en = d.enabled.indexOf(id) >= 0 ? d.enabled.filter(function (x) { return x !== id; }) : d.enabled.concat([id]);
|
|
465
|
+
return { ord: d.ord, enabled: en };
|
|
466
|
+
});
|
|
467
|
+
};
|
|
468
|
+
const dispMove = function (idx, delta) {
|
|
469
|
+
setDisp(function (d) {
|
|
470
|
+
const j = idx + delta;
|
|
471
|
+
if (idx < 0 || j < 0 || idx >= d.ord.length || j >= d.ord.length) return d;
|
|
472
|
+
const ord = d.ord.slice();
|
|
473
|
+
const tmp = ord[idx]; ord[idx] = ord[j]; ord[j] = tmp;
|
|
474
|
+
return { ord: ord, enabled: d.enabled };
|
|
475
|
+
});
|
|
476
|
+
};
|
|
477
|
+
const dispLabels = (view && view.scheduling && view.scheduling.labels) || {};
|
|
478
|
+
|
|
479
|
+
const save = function () {
|
|
480
|
+
if (!view || loading) return;
|
|
481
|
+
setLoading(true); setMsg(null);
|
|
482
|
+
const body = {};
|
|
483
|
+
for (const meta of providerMetas(view.scheduling, { all: view.providers ? Object.keys(view.providers) : null })) {
|
|
484
|
+
const pv = (view.providers && view.providers[meta.id]) || {};
|
|
485
|
+
if (!pv.present) continue;
|
|
486
|
+
const fields = (pv.meta && Array.isArray(pv.meta.fields)) ? pv.meta.fields : [];
|
|
487
|
+
const part = {};
|
|
488
|
+
for (const f of fields) {
|
|
489
|
+
const v = (form[meta.id] && form[meta.id][f.key]) || "";
|
|
490
|
+
if (f.secret) {
|
|
491
|
+
if (v) part[f.key] = v;
|
|
492
|
+
else if (clears[meta.id] && clears[meta.id][f.key]) part[f.key + "Clear"] = true;
|
|
493
|
+
} else part[f.key] = v;
|
|
494
|
+
}
|
|
495
|
+
body[meta.id] = part;
|
|
496
|
+
}
|
|
497
|
+
// 调度区:enabled = 启用集合,priority = ord 全量顺序(后端 order 派生 ∩ enabled)。
|
|
498
|
+
// 旧宿主(无 scheduling 视图)会静默丢弃 _scheduling —— 运行中面板与新 client 存在
|
|
499
|
+
// 过渡窗口,此时跳过调度字段并在保存后明确提示,避免「假成功」。
|
|
500
|
+
const schedSupported = !!(view && view.scheduling);
|
|
501
|
+
if (schedSupported) body._scheduling = { enabled: disp.enabled.slice(), priority: disp.ord.slice() };
|
|
502
|
+
fetch("/cli-bridge/settings", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body) })
|
|
503
|
+
.then(function (r) { return r.json().then(function (j) { return { code: r.status, j: j }; }); })
|
|
504
|
+
.then(function (res) {
|
|
505
|
+
setLoading(false);
|
|
506
|
+
if (res.code === 200 && res.j && res.j.ok) {
|
|
507
|
+
setClears({}); if (res.j.settings) adopt(res.j.settings);
|
|
508
|
+
setMsg({ kind: schedSupported ? "ok" : "err", text: schedSupported
|
|
509
|
+
? "已保存 ✓ 调度 / 偏好即时生效(preset、动态形态下次会话装配即读;MCP 子进程需重启才刷新工具面)"
|
|
510
|
+
: "已保存(provider 偏好);宿主不支持调度字段,重启 DSH 后在面板重新保存启用/优先级。" });
|
|
511
|
+
} else setMsg({ kind: "err", text: "保存失败:" + ((res.j && res.j.error) || ("HTTP " + res.code)) });
|
|
512
|
+
})
|
|
513
|
+
.catch(function (e) { setLoading(false); setMsg({ kind: "err", text: "保存失败:" + e.message }); });
|
|
514
|
+
};
|
|
515
|
+
|
|
516
|
+
if (!view) {
|
|
517
|
+
return react.createElement("div", { className: "clbs-root" },
|
|
518
|
+
react.createElement("div", { className: "clbs-h" }, "CLI Bridge"),
|
|
519
|
+
react.createElement("div", { className: "clbs-sub" }, msg ? msg.text : "加载设置…"));
|
|
520
|
+
}
|
|
521
|
+
const zh = (function () { try { return String(navigator.language || "").toLowerCase().indexOf("zh") === 0; } catch (e) { return false; } })();
|
|
522
|
+
// 名次表:启用项在 ord 中的相对次序 = 后端 order(priority ∩ enabled)同源派生。
|
|
523
|
+
const rankOf = {};
|
|
524
|
+
(function () { let n = 0; for (const id of disp.ord) { if (disp.enabled.indexOf(id) >= 0) rankOf[id] = (++n); } })();
|
|
525
|
+
// id 轴 = 后端视图(scheduling.all ∪ providers 键),本地表只补 presetId。
|
|
526
|
+
const orderedMeta = (function () {
|
|
527
|
+
const byId = {};
|
|
528
|
+
for (const m of providerMetas(view.scheduling, { all: view.providers ? Object.keys(view.providers) : null })) byId[m.id] = m;
|
|
529
|
+
const ordered = disp.ord.map(function (id) { return byId[id]; }).filter(Boolean);
|
|
530
|
+
for (const id in byId) if (!ordered.some(function (m) { return m.id === id; })) ordered.push(byId[id]);
|
|
531
|
+
return ordered;
|
|
532
|
+
})();
|
|
533
|
+
// 横向标签页模式(0.2.2):一排名字点击切换,页面永远只有一屏高度;
|
|
534
|
+
// 未来加第 4、5 个 CLI 只是多一个 tab(tabs 容器 flex-wrap 自动换行),
|
|
535
|
+
// 不再纵向堆叠。首排「总设置」= 调度(启用数量 + 优先级),其余 = 各 CLI 偏好。
|
|
536
|
+
const tabs = [react.createElement("button", {
|
|
537
|
+
key: "global", type: "button", className: "clbs-tab" + (tab === "global" ? " clbs-tab-on" : ""),
|
|
538
|
+
onClick: function () { setTab("global"); }
|
|
539
|
+
}, zh ? "总设置" : "Global")];
|
|
540
|
+
for (const meta of orderedMeta) {
|
|
541
|
+
const on = disp.enabled.indexOf(meta.id) >= 0;
|
|
542
|
+
const hasView = !!(view.providers && view.providers[meta.id] && view.providers[meta.id].present);
|
|
543
|
+
if (!hasView) continue;
|
|
544
|
+
tabs.push(react.createElement("button", {
|
|
545
|
+
key: meta.id, type: "button", className: "clbs-tab" + (tab === meta.id ? " clbs-tab-on" : ""),
|
|
546
|
+
title: (on ? (zh ? "已启用" : "enabled") : zh ? "未启用" : "disabled") + (on && rankOf[meta.id] ? " · " + (zh ? "优先级" : "priority") + " #" + rankOf[meta.id] : ""),
|
|
547
|
+
onClick: function () { setTab(meta.id); }
|
|
548
|
+
},
|
|
549
|
+
react.createElement("span", { className: "clbs-tab-dot" + (on ? "" : " clbs-tab-dot-off") }),
|
|
550
|
+
(dispLabels && dispLabels[meta.id]) || meta.label,
|
|
551
|
+
on && rankOf[meta.id] ? react.createElement("span", { className: "clbs-tab-rank" }, "#" + rankOf[meta.id]) : null));
|
|
552
|
+
}
|
|
553
|
+
const activeMeta = (function () { for (const m of orderedMeta) if (m.id === tab) return m; return null; })();
|
|
554
|
+
const activeOn = tab !== "global" && disp.enabled.indexOf(tab) >= 0;
|
|
555
|
+
return react.createElement("div", { className: "clbs-root" },
|
|
556
|
+
react.createElement("div", null,
|
|
557
|
+
react.createElement("div", { className: "clbs-h" }, "CLI Bridge"),
|
|
558
|
+
react.createElement("div", { className: "clbs-sub" }, "AGY / CodeBuddy / MiMo 三条 CLI 桥接的派发偏好。保存后对各自的 preset、动态插件与 MCP 三种形态即时生效。")),
|
|
559
|
+
react.createElement("div", { className: "clbs-tabs" }, tabs),
|
|
560
|
+
tab === "global"
|
|
561
|
+
? react.createElement(DispatchCard, { ord: disp.ord, enabled: disp.enabled, labels: dispLabels, onToggle: dispToggle, onMove: dispMove, supported: !!(view && view.scheduling) })
|
|
562
|
+
: (activeMeta ? react.createElement(ProviderSettingsCard, { key: activeMeta.id, meta: activeMeta, on: activeOn, rank: rankOf[activeMeta.id], zh: zh, view: view.providers && view.providers[activeMeta.id], form: form[activeMeta.id] || {}, setField: setField, clears: clears, setClear: setClear }) : null),
|
|
563
|
+
react.createElement("div", { className: "clbs-actions" },
|
|
564
|
+
react.createElement("button", { className: "clbs-btn", type: "button", onClick: save, disabled: loading }, loading ? "保存中…" : "保存全部"),
|
|
565
|
+
react.createElement("button", { className: "clbs-btn clbs-btn-ghost", type: "button", onClick: function () { setClears({}); reload(); } }, "重载"),
|
|
566
|
+
msg ? react.createElement("span", { className: "clbs-msg " + (msg.kind === "ok" ? "clbs-msg-ok" : "clbs-msg-err") }, msg.text) : null));
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
function apply(ctx) {
|
|
570
|
+
if (typeof ctx.inject !== "function") return;
|
|
571
|
+
ctx.inject(["slots"], function (scope) {
|
|
572
|
+
const slots = scope.get("slots");
|
|
573
|
+
if (slots === undefined) return;
|
|
574
|
+
scope.slots.inject("conversation.session.header.utilities", function () {
|
|
575
|
+
return slots.register({ name: "conversation.session.header.utilities", id: "cli-bridge-indicator-home", order: 50, inject: function (injectedSessionId) { return { injectedSessionId: injectedSessionId, sessionsSvc: scope.get("sessions") }; } }, function (props) { return react.createElement(Indicator, props); });
|
|
576
|
+
});
|
|
577
|
+
scope.slots.inject("settings.section", function () {
|
|
578
|
+
return slots.register({ name: "settings.section", id: "cli-bridge-settings", order: 7, label: function () {
|
|
579
|
+
try { return (String(navigator.language || "").toLowerCase().indexOf("zh") === 0) ? "CLI Bridge 桥接" : "CLI Bridge"; }
|
|
580
|
+
catch (e) { return "CLI Bridge"; }
|
|
581
|
+
} }, function () { return react.createElement(SettingsPanel, null); });
|
|
582
|
+
});
|
|
583
|
+
});
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
exports.apply = apply;
|
|
587
|
+
exports.inject = ["slots"];
|
|
588
|
+
return module.exports;
|
|
589
|
+
}
|
|
590
|
+
});
|