@daweifu/capability-menu 0.1.2 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +50 -19
- package/README.md +55 -24
- package/cordis.patch.yml +15 -24
- package/lib/client.d.ts +239 -32
- package/lib/client.d.ts.map +1 -1
- package/lib/client.js +1955 -198
- package/lib/constants.js +33 -0
- package/lib/constants.js.map +1 -0
- package/lib/index.js +1 -1
- package/lib/invoke.js +72 -2
- package/lib/invoke.js.map +1 -1
- package/lib/locations.js +541 -0
- package/lib/locations.js.map +1 -0
- package/lib/patch-file.js +192 -0
- package/lib/patch-file.js.map +1 -0
- package/lib/policy.js +235 -27
- package/lib/policy.js.map +1 -1
- package/lib/registry.js +279 -42
- package/lib/registry.js.map +1 -1
- package/lib/search.js +10 -2
- package/lib/search.js.map +1 -1
- package/lib/server/remote.js +164 -4
- package/lib/server/remote.js.map +1 -1
- package/lib/types/constants.d.ts +33 -0
- package/lib/types/constants.d.ts.map +1 -0
- package/lib/types/index.d.ts +1 -1
- package/lib/types/invoke.d.ts.map +1 -1
- package/lib/types/locations.d.ts +142 -0
- package/lib/types/locations.d.ts.map +1 -0
- package/lib/types/patch-file.d.ts +46 -0
- package/lib/types/patch-file.d.ts.map +1 -0
- package/lib/types/policy.d.ts +45 -0
- package/lib/types/policy.d.ts.map +1 -1
- package/lib/types/registry.d.ts +71 -16
- package/lib/types/registry.d.ts.map +1 -1
- package/lib/types/search.d.ts.map +1 -1
- package/lib/types/server/remote.d.ts +65 -2
- package/lib/types/server/remote.d.ts.map +1 -1
- package/package.json +24 -23
package/lib/client.js
CHANGED
|
@@ -284,7 +284,7 @@ function prefixIssues(path, issues) {
|
|
|
284
284
|
return iss;
|
|
285
285
|
});
|
|
286
286
|
}
|
|
287
|
-
function unwrapMessage(message) {
|
|
287
|
+
function unwrapMessage$1(message) {
|
|
288
288
|
return typeof message === "string" ? message : message?.message;
|
|
289
289
|
}
|
|
290
290
|
function attachSchema(issues, start, inst) {
|
|
@@ -299,7 +299,7 @@ function finalizeIssue(iss, ctx, config) {
|
|
|
299
299
|
else iss.schema = iss.inst;
|
|
300
300
|
}
|
|
301
301
|
const schemaError = iss.schema !== iss.inst ? iss.schema?._zod.def?.error : void 0;
|
|
302
|
-
const message = iss.message ? iss.message : unwrapMessage(iss.inst?._zod.def?.error?.(iss)) ?? unwrapMessage(schemaError?.(iss)) ?? unwrapMessage(ctx?.error?.(iss)) ?? unwrapMessage(config.customError?.(iss)) ?? unwrapMessage(config.localeError?.(iss)) ?? "Invalid input";
|
|
302
|
+
const message = iss.message ? iss.message : unwrapMessage$1(iss.inst?._zod.def?.error?.(iss)) ?? unwrapMessage$1(schemaError?.(iss)) ?? unwrapMessage$1(ctx?.error?.(iss)) ?? unwrapMessage$1(config.customError?.(iss)) ?? unwrapMessage$1(config.localeError?.(iss)) ?? "Invalid input";
|
|
303
303
|
const { inst: _inst, schema: _schema, continue: _continue, input: _input, ...rest } = iss;
|
|
304
304
|
rest.path ?? (rest.path = []);
|
|
305
305
|
rest.message = message;
|
|
@@ -5190,12 +5190,57 @@ function superRefine(fn, params) {
|
|
|
5190
5190
|
* namespace and exports a `TYPERT_REMOTE` contribution that the browser
|
|
5191
5191
|
* plugin mounts via `ctx.remote.$mount(...)`.
|
|
5192
5192
|
*/
|
|
5193
|
+
const stringRecord$schema = record(string(), string());
|
|
5194
|
+
const mcpLocation$schema = object({
|
|
5195
|
+
id: string().readonly(),
|
|
5196
|
+
serverName: string().readonly(),
|
|
5197
|
+
transport: union([literal("stdio"), literal("streamable-http")]).readonly(),
|
|
5198
|
+
disabled: boolean().readonly(),
|
|
5199
|
+
command: string().optional().readonly(),
|
|
5200
|
+
args: array(string()).optional().readonly(),
|
|
5201
|
+
env: stringRecord$schema.optional().readonly(),
|
|
5202
|
+
cwd: string().optional().readonly(),
|
|
5203
|
+
url: string().optional().readonly(),
|
|
5204
|
+
headers: stringRecord$schema.optional().readonly(),
|
|
5205
|
+
toolCallTimeoutMs: number().optional().readonly()
|
|
5206
|
+
});
|
|
5207
|
+
const skillLocation$schema = object({
|
|
5208
|
+
name: string().readonly(),
|
|
5209
|
+
path: string().readonly(),
|
|
5210
|
+
linked: boolean().readonly(),
|
|
5211
|
+
valid: boolean().readonly(),
|
|
5212
|
+
root: union([literal("user"), literal("project")]).readonly(),
|
|
5213
|
+
entryDir: string().readonly(),
|
|
5214
|
+
target: string().optional().readonly()
|
|
5215
|
+
});
|
|
5216
|
+
const mcpInput$schema = object({
|
|
5217
|
+
serverName: string(),
|
|
5218
|
+
transport: union([literal("stdio"), literal("streamable-http")]),
|
|
5219
|
+
command: string().optional(),
|
|
5220
|
+
args: array(string()).optional(),
|
|
5221
|
+
env: stringRecord$schema.optional(),
|
|
5222
|
+
cwd: string().optional(),
|
|
5223
|
+
url: string().optional(),
|
|
5224
|
+
headers: stringRecord$schema.optional(),
|
|
5225
|
+
toolCallTimeoutMs: number().optional()
|
|
5226
|
+
});
|
|
5227
|
+
const mcpUpdateInput$schema = object({
|
|
5228
|
+
transport: union([literal("stdio"), literal("streamable-http")]),
|
|
5229
|
+
command: string().optional(),
|
|
5230
|
+
args: array(string()).optional(),
|
|
5231
|
+
env: stringRecord$schema.optional(),
|
|
5232
|
+
cwd: string().optional(),
|
|
5233
|
+
url: string().optional(),
|
|
5234
|
+
headers: stringRecord$schema.optional(),
|
|
5235
|
+
toolCallTimeoutMs: number().optional()
|
|
5236
|
+
});
|
|
5193
5237
|
const capabilityRow$schema = object({
|
|
5194
5238
|
id: string().readonly(),
|
|
5195
5239
|
kind: union([literal("tool"), literal("skill")]).readonly(),
|
|
5196
5240
|
name: string().readonly(),
|
|
5197
5241
|
server: string().optional().readonly(),
|
|
5198
5242
|
source: string().optional().readonly(),
|
|
5243
|
+
preset: string().optional().readonly(),
|
|
5199
5244
|
class: union([
|
|
5200
5245
|
literal("resident"),
|
|
5201
5246
|
literal("on-demand"),
|
|
@@ -5229,7 +5274,8 @@ const toolDetail$schema = object({
|
|
|
5229
5274
|
provider: string().readonly(),
|
|
5230
5275
|
serverName: string().optional().readonly(),
|
|
5231
5276
|
path: string().optional().readonly(),
|
|
5232
|
-
source: string().optional().readonly()
|
|
5277
|
+
source: string().optional().readonly(),
|
|
5278
|
+
preset: string().optional().readonly()
|
|
5233
5279
|
}).readonly(),
|
|
5234
5280
|
tags: array(string()).readonly(),
|
|
5235
5281
|
stats: object({
|
|
@@ -5257,7 +5303,7 @@ const TYPERT_REMOTE = {
|
|
|
5257
5303
|
},
|
|
5258
5304
|
sourceLocation: {
|
|
5259
5305
|
file: "src/server/remote.ts",
|
|
5260
|
-
line:
|
|
5306
|
+
line: 61,
|
|
5261
5307
|
column: 3
|
|
5262
5308
|
}
|
|
5263
5309
|
},
|
|
@@ -5284,7 +5330,7 @@ const TYPERT_REMOTE = {
|
|
|
5284
5330
|
},
|
|
5285
5331
|
sourceLocation: {
|
|
5286
5332
|
file: "src/server/remote.ts",
|
|
5287
|
-
line:
|
|
5333
|
+
line: 67,
|
|
5288
5334
|
column: 3
|
|
5289
5335
|
}
|
|
5290
5336
|
},
|
|
@@ -5302,7 +5348,25 @@ const TYPERT_REMOTE = {
|
|
|
5302
5348
|
},
|
|
5303
5349
|
sourceLocation: {
|
|
5304
5350
|
file: "src/server/remote.ts",
|
|
5305
|
-
line:
|
|
5351
|
+
line: 73,
|
|
5352
|
+
column: 3
|
|
5353
|
+
}
|
|
5354
|
+
},
|
|
5355
|
+
{
|
|
5356
|
+
id: "@daweifu/capability-menu#capabilityPolicy/refresh",
|
|
5357
|
+
service: "capabilityPolicy",
|
|
5358
|
+
namespace: "capabilityPolicy",
|
|
5359
|
+
method: "refresh",
|
|
5360
|
+
invocation: { kind: "direct" },
|
|
5361
|
+
parameters: [],
|
|
5362
|
+
result: {
|
|
5363
|
+
mode: "strict",
|
|
5364
|
+
typeSymbol: "void",
|
|
5365
|
+
schema: _undefined()
|
|
5366
|
+
},
|
|
5367
|
+
sourceLocation: {
|
|
5368
|
+
file: "src/server/remote.ts",
|
|
5369
|
+
line: 84,
|
|
5306
5370
|
column: 3
|
|
5307
5371
|
}
|
|
5308
5372
|
},
|
|
@@ -5339,7 +5403,7 @@ const TYPERT_REMOTE = {
|
|
|
5339
5403
|
},
|
|
5340
5404
|
sourceLocation: {
|
|
5341
5405
|
file: "src/server/remote.ts",
|
|
5342
|
-
line:
|
|
5406
|
+
line: 85,
|
|
5343
5407
|
column: 3
|
|
5344
5408
|
}
|
|
5345
5409
|
},
|
|
@@ -5375,7 +5439,7 @@ const TYPERT_REMOTE = {
|
|
|
5375
5439
|
},
|
|
5376
5440
|
sourceLocation: {
|
|
5377
5441
|
file: "src/server/remote.ts",
|
|
5378
|
-
line:
|
|
5442
|
+
line: 91,
|
|
5379
5443
|
column: 3
|
|
5380
5444
|
}
|
|
5381
5445
|
},
|
|
@@ -5402,7 +5466,7 @@ const TYPERT_REMOTE = {
|
|
|
5402
5466
|
},
|
|
5403
5467
|
sourceLocation: {
|
|
5404
5468
|
file: "src/server/remote.ts",
|
|
5405
|
-
line:
|
|
5469
|
+
line: 79,
|
|
5406
5470
|
column: 3
|
|
5407
5471
|
}
|
|
5408
5472
|
},
|
|
@@ -5420,29 +5484,1121 @@ const TYPERT_REMOTE = {
|
|
|
5420
5484
|
},
|
|
5421
5485
|
sourceLocation: {
|
|
5422
5486
|
file: "src/server/remote.ts",
|
|
5423
|
-
line:
|
|
5487
|
+
line: 104,
|
|
5488
|
+
column: 3
|
|
5489
|
+
}
|
|
5490
|
+
},
|
|
5491
|
+
{
|
|
5492
|
+
id: "@daweifu/capability-menu#capabilityPolicy/listLocations",
|
|
5493
|
+
service: "capabilityPolicy",
|
|
5494
|
+
namespace: "capabilityPolicy",
|
|
5495
|
+
method: "listLocations",
|
|
5496
|
+
invocation: { kind: "direct" },
|
|
5497
|
+
parameters: [],
|
|
5498
|
+
result: {
|
|
5499
|
+
mode: "strict",
|
|
5500
|
+
typeSymbol: "@daweifu/capability-menu#McpLocation[]",
|
|
5501
|
+
schema: array(mcpLocation$schema)
|
|
5502
|
+
},
|
|
5503
|
+
sourceLocation: {
|
|
5504
|
+
file: "src/server/remote.ts",
|
|
5505
|
+
line: 193,
|
|
5506
|
+
column: 3
|
|
5507
|
+
}
|
|
5508
|
+
},
|
|
5509
|
+
{
|
|
5510
|
+
id: "@daweifu/capability-menu#capabilityPolicy/addLocation",
|
|
5511
|
+
service: "capabilityPolicy",
|
|
5512
|
+
namespace: "capabilityPolicy",
|
|
5513
|
+
method: "addLocation",
|
|
5514
|
+
invocation: { kind: "direct" },
|
|
5515
|
+
parameters: [{
|
|
5516
|
+
name: "input",
|
|
5517
|
+
wire: "input",
|
|
5518
|
+
source: "json",
|
|
5519
|
+
codec: {
|
|
5520
|
+
mode: "strict",
|
|
5521
|
+
typeSymbol: "@daweifu/capability-menu#McpInput",
|
|
5522
|
+
schema: mcpInput$schema
|
|
5523
|
+
}
|
|
5524
|
+
}],
|
|
5525
|
+
result: {
|
|
5526
|
+
mode: "strict",
|
|
5527
|
+
typeSymbol: "string",
|
|
5528
|
+
schema: string()
|
|
5529
|
+
},
|
|
5530
|
+
sourceLocation: {
|
|
5531
|
+
file: "src/server/remote.ts",
|
|
5532
|
+
line: 199,
|
|
5533
|
+
column: 3
|
|
5534
|
+
}
|
|
5535
|
+
},
|
|
5536
|
+
{
|
|
5537
|
+
id: "@daweifu/capability-menu#capabilityPolicy/removeLocation",
|
|
5538
|
+
service: "capabilityPolicy",
|
|
5539
|
+
namespace: "capabilityPolicy",
|
|
5540
|
+
method: "removeLocation",
|
|
5541
|
+
invocation: { kind: "direct" },
|
|
5542
|
+
parameters: [{
|
|
5543
|
+
name: "id",
|
|
5544
|
+
wire: "id",
|
|
5545
|
+
source: "json",
|
|
5546
|
+
codec: {
|
|
5547
|
+
mode: "strict",
|
|
5548
|
+
typeSymbol: "string",
|
|
5549
|
+
schema: string()
|
|
5550
|
+
}
|
|
5551
|
+
}],
|
|
5552
|
+
result: {
|
|
5553
|
+
mode: "strict",
|
|
5554
|
+
typeSymbol: "boolean",
|
|
5555
|
+
schema: boolean()
|
|
5556
|
+
},
|
|
5557
|
+
sourceLocation: {
|
|
5558
|
+
file: "src/server/remote.ts",
|
|
5559
|
+
line: 205,
|
|
5560
|
+
column: 3
|
|
5561
|
+
}
|
|
5562
|
+
},
|
|
5563
|
+
{
|
|
5564
|
+
id: "@daweifu/capability-menu#capabilityPolicy/updateLocation",
|
|
5565
|
+
service: "capabilityPolicy",
|
|
5566
|
+
namespace: "capabilityPolicy",
|
|
5567
|
+
method: "updateLocation",
|
|
5568
|
+
invocation: { kind: "direct" },
|
|
5569
|
+
parameters: [{
|
|
5570
|
+
name: "id",
|
|
5571
|
+
wire: "id",
|
|
5572
|
+
source: "json",
|
|
5573
|
+
codec: {
|
|
5574
|
+
mode: "strict",
|
|
5575
|
+
typeSymbol: "string",
|
|
5576
|
+
schema: string()
|
|
5577
|
+
}
|
|
5578
|
+
}, {
|
|
5579
|
+
name: "input",
|
|
5580
|
+
wire: "input",
|
|
5581
|
+
source: "json",
|
|
5582
|
+
codec: {
|
|
5583
|
+
mode: "strict",
|
|
5584
|
+
typeSymbol: "@daweifu/capability-menu#McpUpdateInput",
|
|
5585
|
+
schema: mcpUpdateInput$schema
|
|
5586
|
+
}
|
|
5587
|
+
}],
|
|
5588
|
+
result: {
|
|
5589
|
+
mode: "strict",
|
|
5590
|
+
typeSymbol: "boolean",
|
|
5591
|
+
schema: boolean()
|
|
5592
|
+
},
|
|
5593
|
+
sourceLocation: {
|
|
5594
|
+
file: "src/server/remote.ts",
|
|
5595
|
+
line: 211,
|
|
5596
|
+
column: 3
|
|
5597
|
+
}
|
|
5598
|
+
},
|
|
5599
|
+
{
|
|
5600
|
+
id: "@daweifu/capability-menu#capabilityPolicy/listSkillLocations",
|
|
5601
|
+
service: "capabilityPolicy",
|
|
5602
|
+
namespace: "capabilityPolicy",
|
|
5603
|
+
method: "listSkillLocations",
|
|
5604
|
+
invocation: { kind: "direct" },
|
|
5605
|
+
parameters: [],
|
|
5606
|
+
result: {
|
|
5607
|
+
mode: "strict",
|
|
5608
|
+
typeSymbol: "@daweifu/capability-menu#SkillLocation[]",
|
|
5609
|
+
schema: array(skillLocation$schema)
|
|
5610
|
+
},
|
|
5611
|
+
sourceLocation: {
|
|
5612
|
+
file: "src/server/remote.ts",
|
|
5613
|
+
line: 229,
|
|
5614
|
+
column: 3
|
|
5615
|
+
}
|
|
5616
|
+
},
|
|
5617
|
+
{
|
|
5618
|
+
id: "@daweifu/capability-menu#capabilityPolicy/addSkillLocation",
|
|
5619
|
+
service: "capabilityPolicy",
|
|
5620
|
+
namespace: "capabilityPolicy",
|
|
5621
|
+
method: "addSkillLocation",
|
|
5622
|
+
invocation: { kind: "direct" },
|
|
5623
|
+
parameters: [{
|
|
5624
|
+
name: "dir",
|
|
5625
|
+
wire: "dir",
|
|
5626
|
+
source: "json",
|
|
5627
|
+
codec: {
|
|
5628
|
+
mode: "strict",
|
|
5629
|
+
typeSymbol: "string",
|
|
5630
|
+
schema: string()
|
|
5631
|
+
}
|
|
5632
|
+
}, {
|
|
5633
|
+
name: "projectPath",
|
|
5634
|
+
wire: "projectPath",
|
|
5635
|
+
source: "json",
|
|
5636
|
+
acceptsUndefined: true,
|
|
5637
|
+
codec: {
|
|
5638
|
+
mode: "strict",
|
|
5639
|
+
typeSymbol: "string",
|
|
5640
|
+
schema: string().optional()
|
|
5641
|
+
}
|
|
5642
|
+
}],
|
|
5643
|
+
result: {
|
|
5644
|
+
mode: "strict",
|
|
5645
|
+
typeSymbol: "string",
|
|
5646
|
+
schema: string()
|
|
5647
|
+
},
|
|
5648
|
+
sourceLocation: {
|
|
5649
|
+
file: "src/server/remote.ts",
|
|
5650
|
+
line: 252,
|
|
5651
|
+
column: 3
|
|
5652
|
+
}
|
|
5653
|
+
},
|
|
5654
|
+
{
|
|
5655
|
+
id: "@daweifu/capability-menu#capabilityPolicy/removeSkillLocation",
|
|
5656
|
+
service: "capabilityPolicy",
|
|
5657
|
+
namespace: "capabilityPolicy",
|
|
5658
|
+
method: "removeSkillLocation",
|
|
5659
|
+
invocation: { kind: "direct" },
|
|
5660
|
+
parameters: [{
|
|
5661
|
+
name: "name",
|
|
5662
|
+
wire: "name",
|
|
5663
|
+
source: "json",
|
|
5664
|
+
codec: {
|
|
5665
|
+
mode: "strict",
|
|
5666
|
+
typeSymbol: "string",
|
|
5667
|
+
schema: string()
|
|
5668
|
+
}
|
|
5669
|
+
}, {
|
|
5670
|
+
name: "entryDir",
|
|
5671
|
+
wire: "entryDir",
|
|
5672
|
+
source: "json",
|
|
5673
|
+
acceptsUndefined: true,
|
|
5674
|
+
codec: {
|
|
5675
|
+
mode: "strict",
|
|
5676
|
+
typeSymbol: "string",
|
|
5677
|
+
schema: string().optional()
|
|
5678
|
+
}
|
|
5679
|
+
}],
|
|
5680
|
+
result: {
|
|
5681
|
+
mode: "strict",
|
|
5682
|
+
typeSymbol: "boolean",
|
|
5683
|
+
schema: boolean()
|
|
5684
|
+
},
|
|
5685
|
+
sourceLocation: {
|
|
5686
|
+
file: "src/server/remote.ts",
|
|
5687
|
+
line: 258,
|
|
5688
|
+
column: 3
|
|
5689
|
+
}
|
|
5690
|
+
},
|
|
5691
|
+
{
|
|
5692
|
+
id: "@daweifu/capability-menu#capabilityPolicy/adoptSkillLocation",
|
|
5693
|
+
service: "capabilityPolicy",
|
|
5694
|
+
namespace: "capabilityPolicy",
|
|
5695
|
+
method: "adoptSkillLocation",
|
|
5696
|
+
invocation: { kind: "direct" },
|
|
5697
|
+
parameters: [{
|
|
5698
|
+
name: "name",
|
|
5699
|
+
wire: "name",
|
|
5700
|
+
source: "json",
|
|
5701
|
+
codec: {
|
|
5702
|
+
mode: "strict",
|
|
5703
|
+
typeSymbol: "string",
|
|
5704
|
+
schema: string()
|
|
5705
|
+
}
|
|
5706
|
+
}],
|
|
5707
|
+
result: {
|
|
5708
|
+
mode: "strict",
|
|
5709
|
+
typeSymbol: "string",
|
|
5710
|
+
schema: string()
|
|
5711
|
+
},
|
|
5712
|
+
sourceLocation: {
|
|
5713
|
+
file: "src/server/remote.ts",
|
|
5714
|
+
line: 259,
|
|
5424
5715
|
column: 3
|
|
5425
5716
|
}
|
|
5717
|
+
},
|
|
5718
|
+
{
|
|
5719
|
+
id: "@daweifu/capability-menu#capabilityPolicy/updateSkillLocation",
|
|
5720
|
+
service: "capabilityPolicy",
|
|
5721
|
+
namespace: "capabilityPolicy",
|
|
5722
|
+
method: "updateSkillLocation",
|
|
5723
|
+
invocation: { kind: "direct" },
|
|
5724
|
+
parameters: [
|
|
5725
|
+
{
|
|
5726
|
+
name: "name",
|
|
5727
|
+
wire: "name",
|
|
5728
|
+
source: "json",
|
|
5729
|
+
codec: {
|
|
5730
|
+
mode: "strict",
|
|
5731
|
+
typeSymbol: "string",
|
|
5732
|
+
schema: string()
|
|
5733
|
+
}
|
|
5734
|
+
},
|
|
5735
|
+
{
|
|
5736
|
+
name: "dir",
|
|
5737
|
+
wire: "dir",
|
|
5738
|
+
source: "json",
|
|
5739
|
+
codec: {
|
|
5740
|
+
mode: "strict",
|
|
5741
|
+
typeSymbol: "string",
|
|
5742
|
+
schema: string()
|
|
5743
|
+
}
|
|
5744
|
+
},
|
|
5745
|
+
{
|
|
5746
|
+
name: "entryDir",
|
|
5747
|
+
wire: "entryDir",
|
|
5748
|
+
source: "json",
|
|
5749
|
+
acceptsUndefined: true,
|
|
5750
|
+
codec: {
|
|
5751
|
+
mode: "strict",
|
|
5752
|
+
typeSymbol: "string",
|
|
5753
|
+
schema: string().optional()
|
|
5754
|
+
}
|
|
5755
|
+
}
|
|
5756
|
+
],
|
|
5757
|
+
result: {
|
|
5758
|
+
mode: "strict",
|
|
5759
|
+
typeSymbol: "boolean",
|
|
5760
|
+
schema: boolean()
|
|
5761
|
+
},
|
|
5762
|
+
sourceLocation: {
|
|
5763
|
+
file: "src/server/remote.ts",
|
|
5764
|
+
line: 264,
|
|
5765
|
+
column: 3
|
|
5766
|
+
}
|
|
5767
|
+
},
|
|
5768
|
+
{
|
|
5769
|
+
id: "@daweifu/capability-menu#capabilityPolicy/catalogVersion",
|
|
5770
|
+
service: "capabilityPolicy",
|
|
5771
|
+
namespace: "capabilityPolicy",
|
|
5772
|
+
method: "catalogVersion",
|
|
5773
|
+
invocation: { kind: "direct" },
|
|
5774
|
+
parameters: [],
|
|
5775
|
+
result: {
|
|
5776
|
+
mode: "strict",
|
|
5777
|
+
typeSymbol: "number",
|
|
5778
|
+
schema: number()
|
|
5779
|
+
},
|
|
5780
|
+
sourceLocation: {
|
|
5781
|
+
file: "src/server/remote.ts",
|
|
5782
|
+
line: 317,
|
|
5783
|
+
column: 3
|
|
5784
|
+
}
|
|
5785
|
+
}
|
|
5786
|
+
]
|
|
5787
|
+
};
|
|
5788
|
+
//#endregion
|
|
5789
|
+
//#region src/client/store.ts
|
|
5790
|
+
/** Unwrap a RemoteResult-like, throwing a readable error on failure. */
|
|
5791
|
+
function unwrap(result, what) {
|
|
5792
|
+
if (!result.ok) throw new Error(`${what} failed: ${result.error.code}: ${result.error.message}`);
|
|
5793
|
+
return result.value;
|
|
5794
|
+
}
|
|
5795
|
+
/**
|
|
5796
|
+
* Unwrap a RemoteResult-like for a user-facing mutation.
|
|
5797
|
+
*
|
|
5798
|
+
* The policy service's messages are already written for the person at the
|
|
5799
|
+
* dialog (Chinese, naming the offending path), so the RPC name and error code
|
|
5800
|
+
* that `unwrap` adds for diagnostics are dropped here: they would surface as
|
|
5801
|
+
* `capabilityPolicy.addSkillLocation failed: <code>: 目录中没有 SKILL.md:…`
|
|
5802
|
+
* in a dialog that is otherwise plain Chinese.
|
|
5803
|
+
*/
|
|
5804
|
+
function unwrapMessage(result) {
|
|
5805
|
+
if (!result.ok) throw new Error(result.error.message);
|
|
5806
|
+
return result.value;
|
|
5807
|
+
}
|
|
5808
|
+
/**
|
|
5809
|
+
* The last successful snapshot, at module scope.
|
|
5810
|
+
*
|
|
5811
|
+
* The section is remounted every time the settings tab is opened, and a cold
|
|
5812
|
+
* `classifyAll` can be slow once per page load (the plugin's mount awaits a full
|
|
5813
|
+
* catalog enumeration before the surface has data). Keeping the previous rows
|
|
5814
|
+
* lets a remount paint them immediately and revalidate behind the user instead
|
|
5815
|
+
* of showing a loading placeholder. This is a view cache only — every read is
|
|
5816
|
+
* still followed by an authoritative one.
|
|
5817
|
+
*/
|
|
5818
|
+
let lastSnapshot;
|
|
5819
|
+
/** The previously loaded snapshot, when this page session has one. */
|
|
5820
|
+
function cachedSnapshot() {
|
|
5821
|
+
return lastSnapshot;
|
|
5822
|
+
}
|
|
5823
|
+
/** Load the classification list from the remote, remembering it for remounts. */
|
|
5824
|
+
async function loadSnapshot(remote) {
|
|
5825
|
+
lastSnapshot = { rows: unwrap(await remote.classifyAll(), "capabilityPolicy.classifyAll") };
|
|
5826
|
+
return lastSnapshot;
|
|
5827
|
+
}
|
|
5828
|
+
//#endregion
|
|
5829
|
+
//#region src/client/LocationModal.tsx
|
|
5830
|
+
/**
|
|
5831
|
+
* 能力来源弹窗:注册与编辑 MCP 服务器 / Skill 目录。
|
|
5832
|
+
*
|
|
5833
|
+
* Two modes, one form implementation:
|
|
5834
|
+
*
|
|
5835
|
+
* - **注册** — opened from the header's 注册能力 button. Carries the two
|
|
5836
|
+
* sub-tabs (MCP servers / skill directories) and opens on the one matching
|
|
5837
|
+
* the main tab the user is on, so one button is context-sensitive. No list of
|
|
5838
|
+
* what is already registered: that lives on the entries themselves, behind
|
|
5839
|
+
* the per-row 编辑 buttons, where it is actually actionable.
|
|
5840
|
+
* - **编辑** — opened from a row's 编辑 button with the entry prefilled.
|
|
5841
|
+
*
|
|
5842
|
+
* Only 注册 / 保存 / 移除 are offered. Temporarily silencing a source is the
|
|
5843
|
+
* exposure policy's job (classify the capability as Disabled), so there is no
|
|
5844
|
+
* second enable switch here.
|
|
5845
|
+
*/
|
|
5846
|
+
const CSS_ID$1 = "capability-menu-location-css";
|
|
5847
|
+
const CSS$1 = `
|
|
5848
|
+
.lm-block{display:flex;flex-direction:column;gap:8px}
|
|
5849
|
+
.lm-btn{border:1px solid var(--dsw-alias-border-l2);border-radius:6px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-secondary);font:inherit;font-size:12px;line-height:20px;padding:0 10px;cursor:pointer;white-space:nowrap}
|
|
5850
|
+
.lm-btn:hover{border-color:var(--dsw-alias-border-l3);background:var(--dsw-alias-interactive-bg-hover)}
|
|
5851
|
+
.lm-btn--danger:hover{color:#b3261e;border-color:#b3261e}
|
|
5852
|
+
.lm-btn:disabled{opacity:.55;cursor:default}
|
|
5853
|
+
/* Each field is its own two-column grid with a fixed label track.
|
|
5854
|
+
A single form-wide grid looked tidier but was fragile: it needed
|
|
5855
|
+
.lm-field{display:contents}, which turned every hint into a free-floating grid
|
|
5856
|
+
item, and one extra half-width item pushed the following labels into column 2 and
|
|
5857
|
+
their inputs into column 1 — which is exactly how the streamable-http layout got
|
|
5858
|
+
scrambled (that branch has an odd number of items). Per-field grids mean a hint
|
|
5859
|
+
can only ever affect its own field, and a fixed label track (rather than
|
|
5860
|
+
min-width, which sizes per row) keeps every label column identical. */
|
|
5861
|
+
.lm-form{display:flex;flex-direction:column;gap:6px}
|
|
5862
|
+
.lm-field{--lm-label-w:104px;display:grid;grid-template-columns:var(--lm-label-w) 1fr;gap:2px 8px;align-items:start}
|
|
5863
|
+
.lm-label{grid-column:1;min-width:0;overflow-wrap:anywhere;font-size:12px;line-height:20px;color:var(--dsw-alias-label-tertiary)}
|
|
5864
|
+
/* width:100% must come with border-box: the field carries a 1px border plus 8px of
|
|
5865
|
+
padding, which would otherwise overflow the column and clip. */
|
|
5866
|
+
.lm-input{grid-column:2;width:100%;box-sizing:border-box;min-width:0;border:1px solid var(--dsw-alias-border-l2);border-radius:6px;background:var(--dsw-alias-bg-layer-1);color:inherit;font:inherit;font-size:12px;line-height:20px;padding:2px 8px}
|
|
5867
|
+
.lm-input:disabled{opacity:.6}
|
|
5868
|
+
textarea.lm-input{resize:vertical;font-family:var(--dsw-font-markdown-code-block-font-family)}
|
|
5869
|
+
/* Lives inside its field, in the content column: it lines up with the box it
|
|
5870
|
+
annotates and takes the caption colour so it does not read as another label. */
|
|
5871
|
+
.lm-hint{grid-column:2;margin:0;font-size:11px;line-height:16px;color:var(--dsw-alias-label-caption)}
|
|
5872
|
+
.lm-error{margin:0;font-size:12px;color:#b3261e;word-break:break-word}
|
|
5873
|
+
.lm-actions{display:flex;gap:8px;justify-content:flex-end;padding:4px 0 0}
|
|
5874
|
+
/* Removal is irreversible, so it takes over the action row for one confirmation
|
|
5875
|
+
step: the message states the consequence, then the buttons act. */
|
|
5876
|
+
.lm-confirm{display:flex;align-items:center;gap:8px;flex-wrap:wrap;padding:8px 10px;border:1px solid #b3261e;border-radius:6px}
|
|
5877
|
+
.lm-confirm-msg{flex:1 1 auto;margin:0;font-size:12px;line-height:18px;word-break:break-word}
|
|
5878
|
+
.lm-confirm-actions{display:flex;gap:8px;flex-shrink:0}
|
|
5879
|
+
/* The modal shell itself carries no padding (it is shared with the read-only
|
|
5880
|
+
catalog viewer, whose body supplies its own); the form supplies it here. */
|
|
5881
|
+
.lm-body{padding:14px 16px;overflow:auto;flex:1 1 auto;min-height:0}
|
|
5882
|
+
`;
|
|
5883
|
+
if (typeof document !== "undefined" && document.querySelector(`style[data-css-id="${CSS_ID$1}"]`) === null) {
|
|
5884
|
+
const tag = document.createElement("style");
|
|
5885
|
+
tag.dataset.cssId = CSS_ID$1;
|
|
5886
|
+
tag.textContent = CSS$1;
|
|
5887
|
+
document.head.appendChild(tag);
|
|
5888
|
+
}
|
|
5889
|
+
/** Render a key/value map as `Key: Value` lines. */
|
|
5890
|
+
function toLines(record) {
|
|
5891
|
+
if (record === void 0) return "";
|
|
5892
|
+
return Object.entries(record).map(([key, value]) => `${key}: ${value}`).join("\n");
|
|
5893
|
+
}
|
|
5894
|
+
/** Parse `Key: Value` lines into a map; blank lines and lines without `:` are skipped. */
|
|
5895
|
+
function fromLines(text) {
|
|
5896
|
+
const out = {};
|
|
5897
|
+
for (const line of text.split("\n")) {
|
|
5898
|
+
const trimmed = line.trim();
|
|
5899
|
+
if (trimmed.length === 0) continue;
|
|
5900
|
+
const at = trimmed.indexOf(":");
|
|
5901
|
+
if (at <= 0) continue;
|
|
5902
|
+
const key = trimmed.slice(0, at).trim();
|
|
5903
|
+
const value = trimmed.slice(at + 1).trim();
|
|
5904
|
+
if (key.length > 0) out[key] = value;
|
|
5905
|
+
}
|
|
5906
|
+
return Object.keys(out).length > 0 ? out : void 0;
|
|
5907
|
+
}
|
|
5908
|
+
function LocationModal(props) {
|
|
5909
|
+
const { remote, t, defaultKind = "mcp", editMcp, editSkill, onClose, onChanged } = props;
|
|
5910
|
+
const editing = editMcp !== void 0 || editSkill !== void 0;
|
|
5911
|
+
const [subTab, setSubTab] = (0, react.useState)(editSkill !== void 0 ? "skill" : editMcp !== void 0 ? "mcp" : defaultKind);
|
|
5912
|
+
const [error, setError] = (0, react.useState)(null);
|
|
5913
|
+
const [busy, setBusy] = (0, react.useState)(false);
|
|
5914
|
+
/**
|
|
5915
|
+
* Which action is waiting for confirmation, if any. Two need one: 移除, and
|
|
5916
|
+
* 保存 on an entry that is a real directory — repointing unlinks the entry
|
|
5917
|
+
* first, which recursively deletes a real directory's contents.
|
|
5918
|
+
*/
|
|
5919
|
+
const [confirm, setConfirm] = (0, react.useState)(null);
|
|
5920
|
+
const [serverName, setServerName] = (0, react.useState)(editMcp?.serverName ?? "");
|
|
5921
|
+
const [transport, setTransport] = (0, react.useState)(editMcp?.transport ?? "streamable-http");
|
|
5922
|
+
const [command, setCommand] = (0, react.useState)(editMcp?.command ?? "");
|
|
5923
|
+
const [args, setArgs] = (0, react.useState)((editMcp?.args ?? []).join(" "));
|
|
5924
|
+
const [cwd, setCwd] = (0, react.useState)(editMcp?.cwd ?? "");
|
|
5925
|
+
const [env, setEnv] = (0, react.useState)(toLines(editMcp?.env));
|
|
5926
|
+
const [url, setUrl] = (0, react.useState)(editMcp?.url ?? "");
|
|
5927
|
+
const [headers, setHeaders] = (0, react.useState)(toLines(editMcp?.headers));
|
|
5928
|
+
const [timeoutSec, setTimeoutSec] = (0, react.useState)(editMcp?.toolCallTimeoutMs === void 0 ? "" : String(Math.round(editMcp.toolCallTimeoutMs / 1e3)));
|
|
5929
|
+
const [skillDir, setSkillDir] = (0, react.useState)(editSkill?.target ?? editSkill?.path ?? "");
|
|
5930
|
+
const [skillRoot, setSkillRoot] = (0, react.useState)(editSkill?.root ?? "user");
|
|
5931
|
+
/** A path inside the project; the server derives the project root from it. */
|
|
5932
|
+
const [projectPath, setProjectPath] = (0, react.useState)("");
|
|
5933
|
+
/**
|
|
5934
|
+
* What the operator sees this entry called. They got here from a row named by
|
|
5935
|
+
* the skill's declared name, so the messages use that and not the entry's own
|
|
5936
|
+
* name — which is only ever handed back to the server, since that is what
|
|
5937
|
+
* addresses the symlink on disk.
|
|
5938
|
+
*/
|
|
5939
|
+
const skillLabel = editSkill?.skillName ?? editSkill?.name ?? "";
|
|
5940
|
+
const title = (0, react.useMemo)(() => {
|
|
5941
|
+
if (editMcp !== void 0) return t("editMcp");
|
|
5942
|
+
if (editSkill !== void 0) return t("editSkillNamed", { name: skillLabel });
|
|
5943
|
+
return t("registerCapability");
|
|
5944
|
+
}, [
|
|
5945
|
+
editMcp,
|
|
5946
|
+
editSkill,
|
|
5947
|
+
skillLabel,
|
|
5948
|
+
t
|
|
5949
|
+
]);
|
|
5950
|
+
/** Run a mutation; on success close and ask the parent to re-pull. */
|
|
5951
|
+
const submit = (0, react.useCallback)(async (action, noticeFor) => {
|
|
5952
|
+
if (busy) return;
|
|
5953
|
+
setBusy(true);
|
|
5954
|
+
try {
|
|
5955
|
+
const changed = await action();
|
|
5956
|
+
if (changed === false) throw new Error(t("entryNotFound"));
|
|
5957
|
+
setError(null);
|
|
5958
|
+
onChanged(noticeFor?.(changed));
|
|
5959
|
+
onClose();
|
|
5960
|
+
} catch (e) {
|
|
5961
|
+
setError(String(e));
|
|
5962
|
+
} finally {
|
|
5963
|
+
setBusy(false);
|
|
5964
|
+
}
|
|
5965
|
+
}, [
|
|
5966
|
+
busy,
|
|
5967
|
+
onChanged,
|
|
5968
|
+
onClose,
|
|
5969
|
+
t
|
|
5970
|
+
]);
|
|
5971
|
+
const submitMcp = (0, react.useCallback)(() => {
|
|
5972
|
+
const trimmedTimeout = timeoutSec.trim();
|
|
5973
|
+
const timeoutMs = trimmedTimeout.length === 0 ? void 0 : Math.round(Number(trimmedTimeout) * 1e3);
|
|
5974
|
+
if (timeoutMs !== void 0 && !Number.isFinite(timeoutMs)) {
|
|
5975
|
+
setError(t("timeoutInvalid"));
|
|
5976
|
+
return;
|
|
5977
|
+
}
|
|
5978
|
+
const parsedEnv = fromLines(env);
|
|
5979
|
+
const parsedHeaders = fromLines(headers);
|
|
5980
|
+
const common = {
|
|
5981
|
+
transport,
|
|
5982
|
+
...transport === "stdio" ? {
|
|
5983
|
+
command: command.trim(),
|
|
5984
|
+
...args.trim().length > 0 ? { args: args.trim().split(/\s+/) } : {},
|
|
5985
|
+
...parsedEnv !== void 0 ? { env: parsedEnv } : {},
|
|
5986
|
+
...cwd.trim().length > 0 ? { cwd: cwd.trim() } : {}
|
|
5987
|
+
} : {
|
|
5988
|
+
url: url.trim(),
|
|
5989
|
+
...parsedHeaders !== void 0 ? { headers: parsedHeaders } : {}
|
|
5990
|
+
},
|
|
5991
|
+
...timeoutMs !== void 0 ? { toolCallTimeoutMs: timeoutMs } : {}
|
|
5992
|
+
};
|
|
5993
|
+
submit(async () => editMcp !== void 0 ? unwrapMessage(await remote.updateLocation(editMcp.id, common)) : unwrapMessage(await remote.addLocation({
|
|
5994
|
+
serverName: serverName.trim(),
|
|
5995
|
+
...common
|
|
5996
|
+
})));
|
|
5997
|
+
}, [
|
|
5998
|
+
submit,
|
|
5999
|
+
remote,
|
|
6000
|
+
editMcp,
|
|
6001
|
+
serverName,
|
|
6002
|
+
transport,
|
|
6003
|
+
command,
|
|
6004
|
+
args,
|
|
6005
|
+
cwd,
|
|
6006
|
+
env,
|
|
6007
|
+
url,
|
|
6008
|
+
headers,
|
|
6009
|
+
timeoutSec,
|
|
6010
|
+
t
|
|
6011
|
+
]);
|
|
6012
|
+
const submitSkill = (0, react.useCallback)(() => {
|
|
6013
|
+
const asked = skillRoot === "project" ? projectPath.trim() : void 0;
|
|
6014
|
+
submit(async () => editSkill !== void 0 ? unwrapMessage(await remote.updateSkillLocation(editSkill.name, skillDir.trim(), editSkill.entryDir)) : unwrapMessage(await remote.addSkillLocation(skillDir.trim(), asked)), (result) => editSkill !== void 0 ? t("skillRepointed", { dir: skillDir.trim() }) : t("skillRegisteredAt", { path: String(result) }));
|
|
6015
|
+
}, [
|
|
6016
|
+
submit,
|
|
6017
|
+
remote,
|
|
6018
|
+
editSkill,
|
|
6019
|
+
skillDir,
|
|
6020
|
+
skillRoot,
|
|
6021
|
+
projectPath,
|
|
6022
|
+
t
|
|
6023
|
+
]);
|
|
6024
|
+
const remove = (0, react.useCallback)(() => {
|
|
6025
|
+
if (editMcp !== void 0) {
|
|
6026
|
+
submit(async () => unwrapMessage(await remote.removeLocation(editMcp.id)));
|
|
6027
|
+
return;
|
|
6028
|
+
}
|
|
6029
|
+
if (editSkill !== void 0) submit(async () => unwrapMessage(await remote.removeSkillLocation(editSkill.name, editSkill.entryDir)));
|
|
6030
|
+
}, [
|
|
6031
|
+
submit,
|
|
6032
|
+
remote,
|
|
6033
|
+
editMcp,
|
|
6034
|
+
editSkill
|
|
6035
|
+
]);
|
|
6036
|
+
/**
|
|
6037
|
+
* What removal actually costs, which differs by target: an MCP row only loses
|
|
6038
|
+
* its config, a linked skill only loses its symlink — but `removeSkill`
|
|
6039
|
+
* recursively deletes a *real* directory in the skill root, so that case has to
|
|
6040
|
+
* say so before the click, not after.
|
|
6041
|
+
*/
|
|
6042
|
+
const removalWarning = (0, react.useMemo)(() => {
|
|
6043
|
+
if (editMcp !== void 0) return t("confirmRemoveMcp", { name: editMcp.serverName });
|
|
6044
|
+
if (editSkill === void 0) return "";
|
|
6045
|
+
return editSkill.linked ? t("confirmRemoveSkillLink", { name: skillLabel }) : t("confirmRemoveSkillDir", { name: skillLabel });
|
|
6046
|
+
}, [
|
|
6047
|
+
editMcp,
|
|
6048
|
+
editSkill,
|
|
6049
|
+
skillLabel,
|
|
6050
|
+
t
|
|
6051
|
+
]);
|
|
6052
|
+
/** Saving needs a confirmation only when it would delete a real directory. */
|
|
6053
|
+
const needsSaveConfirm = editSkill !== void 0 && !editSkill.linked;
|
|
6054
|
+
/** The action row, or the confirmation step that has replaced it. */
|
|
6055
|
+
const renderActions = (onSubmit) => confirm !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
6056
|
+
className: "lm-confirm",
|
|
6057
|
+
role: "alert",
|
|
6058
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
6059
|
+
className: "lm-confirm-msg",
|
|
6060
|
+
children: confirm === "save" ? t("saveConfirmRealDir", { name: skillLabel }) : removalWarning
|
|
6061
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
6062
|
+
className: "lm-confirm-actions",
|
|
6063
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
6064
|
+
type: "button",
|
|
6065
|
+
className: "lm-btn",
|
|
6066
|
+
disabled: busy,
|
|
6067
|
+
onClick: () => setConfirm(null),
|
|
6068
|
+
children: t("cancel")
|
|
6069
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
6070
|
+
type: "button",
|
|
6071
|
+
className: "lm-btn lm-btn--danger",
|
|
6072
|
+
disabled: busy,
|
|
6073
|
+
onClick: () => {
|
|
6074
|
+
if (confirm === "save") onSubmit();
|
|
6075
|
+
else remove();
|
|
6076
|
+
},
|
|
6077
|
+
children: confirm === "save" ? t("confirmSaveAnyway") : t("confirmRemove")
|
|
6078
|
+
})]
|
|
6079
|
+
})]
|
|
6080
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
6081
|
+
className: "lm-actions",
|
|
6082
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
6083
|
+
type: "button",
|
|
6084
|
+
className: "lm-btn",
|
|
6085
|
+
disabled: busy,
|
|
6086
|
+
onClick: () => {
|
|
6087
|
+
if (needsSaveConfirm) setConfirm("save");
|
|
6088
|
+
else onSubmit();
|
|
6089
|
+
},
|
|
6090
|
+
children: editing ? t("save") : t("register")
|
|
6091
|
+
}), editing && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
6092
|
+
type: "button",
|
|
6093
|
+
className: "lm-btn lm-btn--danger",
|
|
6094
|
+
disabled: busy,
|
|
6095
|
+
onClick: () => setConfirm("remove"),
|
|
6096
|
+
children: t("remove")
|
|
6097
|
+
})]
|
|
6098
|
+
});
|
|
6099
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
6100
|
+
className: "mc-preview-mask",
|
|
6101
|
+
onClick: onClose,
|
|
6102
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
6103
|
+
className: "mc-preview",
|
|
6104
|
+
onClick: (e) => e.stopPropagation(),
|
|
6105
|
+
children: [
|
|
6106
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
6107
|
+
className: "mc-preview-head",
|
|
6108
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
6109
|
+
className: "mc-preview-title",
|
|
6110
|
+
children: title
|
|
6111
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
6112
|
+
type: "button",
|
|
6113
|
+
className: "mc-preview-close",
|
|
6114
|
+
onClick: onClose,
|
|
6115
|
+
children: t("previewClose")
|
|
6116
|
+
})]
|
|
6117
|
+
}),
|
|
6118
|
+
!editing && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
6119
|
+
className: "mc-subtabs mc-catalog-tabs",
|
|
6120
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
6121
|
+
className: "mc-tab-group",
|
|
6122
|
+
role: "tablist",
|
|
6123
|
+
"aria-label": t("registerCapability"),
|
|
6124
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
6125
|
+
type: "button",
|
|
6126
|
+
role: "tab",
|
|
6127
|
+
className: "mc-tab",
|
|
6128
|
+
"aria-selected": subTab === "mcp",
|
|
6129
|
+
"data-active": subTab === "mcp" ? "true" : void 0,
|
|
6130
|
+
onClick: () => setSubTab("mcp"),
|
|
6131
|
+
children: t("mcpServers")
|
|
6132
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
6133
|
+
type: "button",
|
|
6134
|
+
role: "tab",
|
|
6135
|
+
className: "mc-tab",
|
|
6136
|
+
"aria-selected": subTab === "skill",
|
|
6137
|
+
"data-active": subTab === "skill" ? "true" : void 0,
|
|
6138
|
+
onClick: () => setSubTab("skill"),
|
|
6139
|
+
children: t("skillDirs")
|
|
6140
|
+
})]
|
|
6141
|
+
})
|
|
6142
|
+
}),
|
|
6143
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
6144
|
+
className: "lm-body",
|
|
6145
|
+
children: [error !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
6146
|
+
className: "lm-error",
|
|
6147
|
+
children: error
|
|
6148
|
+
}), subTab === "mcp" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
6149
|
+
className: "lm-block",
|
|
6150
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
6151
|
+
className: "lm-form",
|
|
6152
|
+
children: [
|
|
6153
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
6154
|
+
className: "lm-field",
|
|
6155
|
+
children: [
|
|
6156
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
6157
|
+
className: "lm-label",
|
|
6158
|
+
children: t("transport")
|
|
6159
|
+
}),
|
|
6160
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
6161
|
+
className: "lm-input",
|
|
6162
|
+
value: transport,
|
|
6163
|
+
onChange: (e) => setTransport(e.target.value === "stdio" ? "stdio" : "streamable-http"),
|
|
6164
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
6165
|
+
value: "stdio",
|
|
6166
|
+
children: t("transportStdio")
|
|
6167
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
6168
|
+
value: "streamable-http",
|
|
6169
|
+
children: t("transportHttp")
|
|
6170
|
+
})]
|
|
6171
|
+
}),
|
|
6172
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
6173
|
+
className: "lm-hint",
|
|
6174
|
+
children: transport === "stdio" ? t("transportHintStdio") : t("transportHintHttp")
|
|
6175
|
+
})
|
|
6176
|
+
]
|
|
6177
|
+
}),
|
|
6178
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
6179
|
+
className: "lm-field",
|
|
6180
|
+
children: [
|
|
6181
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
6182
|
+
className: "lm-label",
|
|
6183
|
+
children: t("serverName")
|
|
6184
|
+
}),
|
|
6185
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
6186
|
+
className: "lm-input",
|
|
6187
|
+
value: serverName,
|
|
6188
|
+
disabled: editMcp !== void 0,
|
|
6189
|
+
onChange: (e) => setServerName(e.target.value)
|
|
6190
|
+
}),
|
|
6191
|
+
editMcp !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
6192
|
+
className: "lm-hint",
|
|
6193
|
+
children: t("serverNameImmutable")
|
|
6194
|
+
})
|
|
6195
|
+
]
|
|
6196
|
+
}),
|
|
6197
|
+
transport === "stdio" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
6198
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
6199
|
+
className: "lm-field",
|
|
6200
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
6201
|
+
className: "lm-label",
|
|
6202
|
+
children: t("command")
|
|
6203
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
6204
|
+
className: "lm-input",
|
|
6205
|
+
value: command,
|
|
6206
|
+
onChange: (e) => setCommand(e.target.value)
|
|
6207
|
+
})]
|
|
6208
|
+
}),
|
|
6209
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
6210
|
+
className: "lm-field",
|
|
6211
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
6212
|
+
className: "lm-label",
|
|
6213
|
+
children: t("args")
|
|
6214
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
6215
|
+
className: "lm-input",
|
|
6216
|
+
value: args,
|
|
6217
|
+
onChange: (e) => setArgs(e.target.value)
|
|
6218
|
+
})]
|
|
6219
|
+
}),
|
|
6220
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
6221
|
+
className: "lm-field",
|
|
6222
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
6223
|
+
className: "lm-label",
|
|
6224
|
+
children: t("cwd")
|
|
6225
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
6226
|
+
className: "lm-input",
|
|
6227
|
+
value: cwd,
|
|
6228
|
+
onChange: (e) => setCwd(e.target.value)
|
|
6229
|
+
})]
|
|
6230
|
+
}),
|
|
6231
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
6232
|
+
className: "lm-field",
|
|
6233
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
6234
|
+
className: "lm-label",
|
|
6235
|
+
children: t("env")
|
|
6236
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
|
|
6237
|
+
className: "lm-input",
|
|
6238
|
+
rows: 3,
|
|
6239
|
+
value: env,
|
|
6240
|
+
onChange: (e) => setEnv(e.target.value)
|
|
6241
|
+
})]
|
|
6242
|
+
})
|
|
6243
|
+
] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
6244
|
+
className: "lm-field",
|
|
6245
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
6246
|
+
className: "lm-label",
|
|
6247
|
+
children: t("url")
|
|
6248
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
6249
|
+
className: "lm-input",
|
|
6250
|
+
value: url,
|
|
6251
|
+
onChange: (e) => setUrl(e.target.value)
|
|
6252
|
+
})]
|
|
6253
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
6254
|
+
className: "lm-field",
|
|
6255
|
+
children: [
|
|
6256
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
6257
|
+
className: "lm-label",
|
|
6258
|
+
children: t("headers")
|
|
6259
|
+
}),
|
|
6260
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
|
|
6261
|
+
className: "lm-input",
|
|
6262
|
+
rows: 4,
|
|
6263
|
+
value: headers,
|
|
6264
|
+
onChange: (e) => setHeaders(e.target.value)
|
|
6265
|
+
}),
|
|
6266
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
6267
|
+
className: "lm-hint",
|
|
6268
|
+
children: t("headersHint")
|
|
6269
|
+
})
|
|
6270
|
+
]
|
|
6271
|
+
})] }),
|
|
6272
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
6273
|
+
className: "lm-field",
|
|
6274
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
6275
|
+
className: "lm-label",
|
|
6276
|
+
children: t("timeout")
|
|
6277
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
6278
|
+
className: "lm-input",
|
|
6279
|
+
value: timeoutSec,
|
|
6280
|
+
placeholder: "60",
|
|
6281
|
+
onChange: (e) => setTimeoutSec(e.target.value)
|
|
6282
|
+
})]
|
|
6283
|
+
}),
|
|
6284
|
+
renderActions(() => void submitMcp())
|
|
6285
|
+
]
|
|
6286
|
+
})
|
|
6287
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
6288
|
+
className: "lm-block",
|
|
6289
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
6290
|
+
className: "lm-form",
|
|
6291
|
+
children: [
|
|
6292
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
6293
|
+
className: "lm-field",
|
|
6294
|
+
children: [
|
|
6295
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
6296
|
+
className: "lm-label",
|
|
6297
|
+
children: t("skillRoot")
|
|
6298
|
+
}),
|
|
6299
|
+
editSkill !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
6300
|
+
className: "lm-input",
|
|
6301
|
+
value: t(editSkill.root === "project" ? "skillRootProject" : "skillRootUser"),
|
|
6302
|
+
disabled: true
|
|
6303
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
6304
|
+
className: "lm-input",
|
|
6305
|
+
value: skillRoot,
|
|
6306
|
+
onChange: (e) => setSkillRoot(e.target.value === "project" ? "project" : "user"),
|
|
6307
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
6308
|
+
value: "user",
|
|
6309
|
+
children: t("skillRootUser")
|
|
6310
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
6311
|
+
value: "project",
|
|
6312
|
+
children: t("skillRootProject")
|
|
6313
|
+
})]
|
|
6314
|
+
}),
|
|
6315
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
6316
|
+
className: "lm-hint",
|
|
6317
|
+
children: skillRoot === "project" || editSkill?.root === "project" ? t("skillRootHintProject") : t("skillRootHintUser")
|
|
6318
|
+
})
|
|
6319
|
+
]
|
|
6320
|
+
}),
|
|
6321
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
6322
|
+
className: "lm-field",
|
|
6323
|
+
children: [
|
|
6324
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
6325
|
+
className: "lm-label",
|
|
6326
|
+
children: t("skillDirPath")
|
|
6327
|
+
}),
|
|
6328
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
6329
|
+
className: "lm-input",
|
|
6330
|
+
value: skillDir,
|
|
6331
|
+
onChange: (e) => setSkillDir(e.target.value)
|
|
6332
|
+
}),
|
|
6333
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
6334
|
+
className: "lm-hint",
|
|
6335
|
+
children: t("skillDirHint")
|
|
6336
|
+
})
|
|
6337
|
+
]
|
|
6338
|
+
}),
|
|
6339
|
+
editSkill === void 0 && skillRoot === "project" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
6340
|
+
className: "lm-field",
|
|
6341
|
+
children: [
|
|
6342
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
6343
|
+
className: "lm-label",
|
|
6344
|
+
children: t("skillProjectPath")
|
|
6345
|
+
}),
|
|
6346
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
6347
|
+
className: "lm-input",
|
|
6348
|
+
value: projectPath,
|
|
6349
|
+
onChange: (e) => setProjectPath(e.target.value)
|
|
6350
|
+
}),
|
|
6351
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
6352
|
+
className: "lm-hint",
|
|
6353
|
+
children: t("skillProjectPathHint")
|
|
6354
|
+
})
|
|
6355
|
+
]
|
|
6356
|
+
}),
|
|
6357
|
+
renderActions(() => void submitSkill())
|
|
6358
|
+
]
|
|
6359
|
+
})
|
|
6360
|
+
})]
|
|
6361
|
+
})
|
|
6362
|
+
]
|
|
6363
|
+
})
|
|
6364
|
+
});
|
|
6365
|
+
}
|
|
6366
|
+
/**
|
|
6367
|
+
* dsh's source labels for a project's own skill roots — the `project`-prefixed
|
|
6368
|
+
* providers of `@deepseek-ai/dsh-skill-filesystem`, which scan
|
|
6369
|
+
* `<projectRoot>/.dsh/skills` and `<projectRoot>/.agents/skills`. Every other
|
|
6370
|
+
* label (`user-dsh`, `user-agents`, `bundled`, `custom`, `runtime`) is a
|
|
6371
|
+
* user-level root. Shared so the server's location manager and the browser's
|
|
6372
|
+
* 全局 / 项目 grouping agree on what "project scoped" means.
|
|
6373
|
+
*/
|
|
6374
|
+
const PROJECT_SKILL_SOURCES = /* @__PURE__ */ new Set(["project-dsh", "project-agents"]);
|
|
6375
|
+
/**
|
|
6376
|
+
* Source labels for user-level skill roots whose entries this plugin can adopt:
|
|
6377
|
+
* linking the skill's own directory into the user root (where the entry becomes
|
|
6378
|
+
* editable) without touching its content. `user-dsh` is excluded because that
|
|
6379
|
+
* root is already managed, and `bundled` because a packaged skill should not be
|
|
6380
|
+
* shadowed by a user copy.
|
|
6381
|
+
*/
|
|
6382
|
+
const ADOPTABLE_SKILL_SOURCES = /* @__PURE__ */ new Set(["user-agents", "custom"]);
|
|
6383
|
+
//#endregion
|
|
6384
|
+
//#region src/client/skillGroups.ts
|
|
6385
|
+
/**
|
|
6386
|
+
* What the capability list decides about *which* rows it shows, kept out of
|
|
6387
|
+
* `CapabilitySection.tsx` as pure functions.
|
|
6388
|
+
*
|
|
6389
|
+
* The split is deliberate: the grouping rules are where this plugin got its one
|
|
6390
|
+
* real mis-grouping (preset skills filed under 全局技能), and they are also the
|
|
6391
|
+
* only part of the client a test can reach — this repo has no DOM harness, so
|
|
6392
|
+
* anything that lives inside the component can only be checked by hand.
|
|
6393
|
+
*/
|
|
6394
|
+
/** Group tools by server name; skills stay flat (no server), both name-sorted. */
|
|
6395
|
+
function groupRows(rows) {
|
|
6396
|
+
const byServer = /* @__PURE__ */ new Map();
|
|
6397
|
+
const skills = [];
|
|
6398
|
+
for (const row of rows) {
|
|
6399
|
+
if (row.kind === "skill") {
|
|
6400
|
+
skills.push(row);
|
|
6401
|
+
continue;
|
|
6402
|
+
}
|
|
6403
|
+
const server = row.server ?? "built-in";
|
|
6404
|
+
const list = byServer.get(server);
|
|
6405
|
+
if (list === void 0) byServer.set(server, [row]);
|
|
6406
|
+
else list.push(row);
|
|
6407
|
+
}
|
|
6408
|
+
return {
|
|
6409
|
+
servers: [...byServer.entries()].map(([server, tools]) => ({
|
|
6410
|
+
server,
|
|
6411
|
+
tools: [...tools].sort((a, b) => a.name.localeCompare(b.name))
|
|
6412
|
+
})).sort((a, b) => a.server.localeCompare(b.server)),
|
|
6413
|
+
skills: [...skills].sort((a, b) => a.name.localeCompare(b.name))
|
|
6414
|
+
};
|
|
6415
|
+
}
|
|
6416
|
+
/**
|
|
6417
|
+
* Compile the box. A `/…/` wrapper is optional — it is stripped when present, so
|
|
6418
|
+
* both `cordis|ptc` and `/cordis|ptc/` are read as patterns — and it keeps the
|
|
6419
|
+
* common case of pasting a slash-delimited pattern from working.
|
|
6420
|
+
*/
|
|
6421
|
+
function compileNameFilter(input) {
|
|
6422
|
+
const raw = input.trim();
|
|
6423
|
+
const needle = raw.toLowerCase();
|
|
6424
|
+
if (raw === "") return {
|
|
6425
|
+
needle,
|
|
6426
|
+
regex: false,
|
|
6427
|
+
test: () => true
|
|
6428
|
+
};
|
|
6429
|
+
const pattern = raw.length > 1 && raw.startsWith("/") && raw.endsWith("/") ? raw.slice(1, -1) : raw;
|
|
6430
|
+
try {
|
|
6431
|
+
const compiled = new RegExp(pattern, "i");
|
|
6432
|
+
return {
|
|
6433
|
+
needle,
|
|
6434
|
+
regex: true,
|
|
6435
|
+
test: (values) => values.some((v) => v !== void 0 && compiled.test(v))
|
|
6436
|
+
};
|
|
6437
|
+
} catch {
|
|
6438
|
+
return {
|
|
6439
|
+
needle,
|
|
6440
|
+
regex: false,
|
|
6441
|
+
test: (values) => values.some((v) => v !== void 0 && v.toLowerCase().includes(needle))
|
|
6442
|
+
};
|
|
6443
|
+
}
|
|
6444
|
+
}
|
|
6445
|
+
/**
|
|
6446
|
+
* Everything a row is matched against: its own name, plus the dimensions that
|
|
6447
|
+
* name the group it sits in — the MCP server (or the label the built-in group is
|
|
6448
|
+
* drawn with), the skill's source root, and the preset that ships it.
|
|
6449
|
+
*
|
|
6450
|
+
* Group headings are labels rather than row fields, which is exactly why typing
|
|
6451
|
+
* 系统 used to find nothing: the server id is `built-in`, and 系统内置 is only how
|
|
6452
|
+
* that id is painted. Both spellings are searchable now.
|
|
6453
|
+
*/
|
|
6454
|
+
function searchableText(row, labels = {}) {
|
|
6455
|
+
const values = [row.name];
|
|
6456
|
+
if (row.server !== void 0) {
|
|
6457
|
+
values.push(row.server);
|
|
6458
|
+
if (row.server === "built-in") values.push(labels.builtIn);
|
|
6459
|
+
}
|
|
6460
|
+
if (row.source !== void 0) values.push(row.source, labels.source?.(row.source));
|
|
6461
|
+
values.push(row.preset);
|
|
6462
|
+
return values.filter((v) => v !== void 0 && v !== "");
|
|
6463
|
+
}
|
|
6464
|
+
/**
|
|
6465
|
+
* Narrow a list by the filter. Applied *before* grouping so a server header's
|
|
6466
|
+
* own counts describe the rows actually listed under it.
|
|
6467
|
+
*/
|
|
6468
|
+
function filterRows(rows, filter, labels = {}) {
|
|
6469
|
+
if (filter.needle === "") return [...rows];
|
|
6470
|
+
return rows.filter((row) => filter.test(searchableText(row, labels)));
|
|
6471
|
+
}
|
|
6472
|
+
/**
|
|
6473
|
+
* Split skill rows across the three sub-tabs.
|
|
6474
|
+
*
|
|
6475
|
+
* Preset skills are split off by their `preset` field and never by `source`: a
|
|
6476
|
+
* preset's own `skill-filesystem` labels its skills `custom`, exactly like a
|
|
6477
|
+
* user-configured `customSkillDirs` entry, so a source-based rule drags both
|
|
6478
|
+
* into 全局技能 — filing a preset asset as global is precisely the mis-grouping
|
|
6479
|
+
* this split exists to fix. Rows keep the order they arrive in, so callers that
|
|
6480
|
+
* pass name-sorted rows get name-sorted groups.
|
|
6481
|
+
*/
|
|
6482
|
+
function splitSkillGroups(skills) {
|
|
6483
|
+
const presetSkills = [];
|
|
6484
|
+
const projectSkills = [];
|
|
6485
|
+
const globalSkills = [];
|
|
6486
|
+
const presetGroups = [];
|
|
6487
|
+
for (const skill of skills) {
|
|
6488
|
+
const preset = skill.preset;
|
|
6489
|
+
if (preset !== void 0) {
|
|
6490
|
+
presetSkills.push(skill);
|
|
6491
|
+
const group = presetGroups.find(([id]) => id === preset);
|
|
6492
|
+
if (group === void 0) presetGroups.push([preset, [skill]]);
|
|
6493
|
+
else group[1].push(skill);
|
|
6494
|
+
continue;
|
|
5426
6495
|
}
|
|
5427
|
-
|
|
5428
|
-
|
|
6496
|
+
if (PROJECT_SKILL_SOURCES.has(skill.source ?? "")) projectSkills.push(skill);
|
|
6497
|
+
else globalSkills.push(skill);
|
|
6498
|
+
}
|
|
6499
|
+
presetGroups.sort(([a], [b]) => a.localeCompare(b));
|
|
6500
|
+
return {
|
|
6501
|
+
presetSkills,
|
|
6502
|
+
presetGroups,
|
|
6503
|
+
projectSkills,
|
|
6504
|
+
globalSkills
|
|
6505
|
+
};
|
|
6506
|
+
}
|
|
6507
|
+
/**
|
|
6508
|
+
* The sub-tab the Skills panel actually shows: the requested one, unless it is
|
|
6509
|
+
* 预设技能 while no preset ships skills. That tab does not exist then, and a
|
|
6510
|
+
* selection left on it must not survive — deployed presets are re-scanned on
|
|
6511
|
+
* every refresh, so the tab can disappear under the operator.
|
|
6512
|
+
*/
|
|
6513
|
+
function resolveSkillTab(selected, groups) {
|
|
6514
|
+
return selected === "preset" && groups.presetSkills.length === 0 ? "global" : selected;
|
|
6515
|
+
}
|
|
6516
|
+
/** Rows in one tier. Counts the *displayed* rows, not the whole catalog. */
|
|
6517
|
+
function countByClass(rows, cls) {
|
|
6518
|
+
return rows.reduce((n, row) => row.class === cls ? n + 1 : n, 0);
|
|
6519
|
+
}
|
|
5429
6520
|
//#endregion
|
|
5430
|
-
//#region src/client/
|
|
5431
|
-
/**
|
|
5432
|
-
|
|
5433
|
-
|
|
5434
|
-
|
|
6521
|
+
//#region src/client/revalidate.ts
|
|
6522
|
+
/**
|
|
6523
|
+
* When to re-read the catalog, and how often to look — the decisions behind the
|
|
6524
|
+
* management page keeping itself fresh, kept as pure functions.
|
|
6525
|
+
*
|
|
6526
|
+
* Wiring lives in `CapabilitySection.tsx` (timers, the `visibilitychange`
|
|
6527
|
+
* listener, the two subscriptions); everything that could be got wrong lives
|
|
6528
|
+
* here, where a test can reach it. This repo has no DOM harness.
|
|
6529
|
+
*/
|
|
6530
|
+
/**
|
|
6531
|
+
* How often a visible, idle page asks the host for its catalog version.
|
|
6532
|
+
*
|
|
6533
|
+
* This is the resolution of the one case nothing on the wire announces — a file
|
|
6534
|
+
* dropped into an already-registered skill directory — so it doubles as "how
|
|
6535
|
+
* long will I stare at a stale list after an agent changes something next to
|
|
6536
|
+
* me". 5s keeps that from being annoying; the cost is one tiny request per tick,
|
|
6537
|
+
* and only while this settings page is on screen.
|
|
6538
|
+
*/
|
|
6539
|
+
const POLL_INTERVAL_MS = 5e3;
|
|
6540
|
+
/** Ceiling for the failure backoff, so a long outage does not spin. */
|
|
6541
|
+
const POLL_BACKOFF_MAX_MS = 6e4;
|
|
6542
|
+
/**
|
|
6543
|
+
* How long one version sample may take before it is treated as a failure.
|
|
6544
|
+
*
|
|
6545
|
+
* Without this, a request that neither resolves nor rejects would end the poll
|
|
6546
|
+
* for good: the next tick is only scheduled after the current one settles, and
|
|
6547
|
+
* nothing else restarts the effect while the page stays visible and idle.
|
|
6548
|
+
*/
|
|
6549
|
+
const POLL_SAMPLE_TIMEOUT_MS = 15e3;
|
|
6550
|
+
/**
|
|
6551
|
+
* Delay before the next version poll.
|
|
6552
|
+
*
|
|
6553
|
+
* `failures` is the run of consecutive failures ending at this decision: 0
|
|
6554
|
+
* means the last sample succeeded and the plain interval applies, otherwise the
|
|
6555
|
+
* delay doubles until it hits the ceiling. This is what turns "the carrier
|
|
6556
|
+
* died" from a hot loop into a slow retry that heals itself when it comes back.
|
|
6557
|
+
*/
|
|
6558
|
+
function pollDelayMs(failures) {
|
|
6559
|
+
if (!Number.isFinite(failures) || failures <= 0) return POLL_INTERVAL_MS;
|
|
6560
|
+
return Math.min(POLL_INTERVAL_MS * 2 ** failures, POLL_BACKOFF_MAX_MS);
|
|
5435
6561
|
}
|
|
5436
|
-
/**
|
|
5437
|
-
|
|
5438
|
-
|
|
6562
|
+
/**
|
|
6563
|
+
* Whether a version sample means the catalog moved under the page.
|
|
6564
|
+
*
|
|
6565
|
+
* Deliberately `!==` rather than `>`: the counter is in-process, so it restarts
|
|
6566
|
+
* from zero when the plugin reloads, and a plain "greater than" test would sit
|
|
6567
|
+
* there believing a fresh host was the same host. `undefined` (nothing sampled
|
|
6568
|
+
* yet) only records — the page has just read the snapshot, and re-reading it
|
|
6569
|
+
* because it has no baseline would be busywork.
|
|
6570
|
+
*/
|
|
6571
|
+
function versionChanged(seen, current) {
|
|
6572
|
+
return seen !== void 0 && seen !== current;
|
|
6573
|
+
}
|
|
6574
|
+
/**
|
|
6575
|
+
* The value of `work`, or a rejection if it has not settled within `ms`.
|
|
6576
|
+
*
|
|
6577
|
+
* Used so a carrier that accepts a request and then goes quiet degrades into
|
|
6578
|
+
* the same retry path as a carrier that refuses one.
|
|
6579
|
+
*/
|
|
6580
|
+
function withDeadline(work, ms) {
|
|
6581
|
+
return new Promise((resolve, reject) => {
|
|
6582
|
+
const timer = setTimeout(() => reject(/* @__PURE__ */ new Error(`timed out after ${ms}ms`)), ms);
|
|
6583
|
+
work.then((value) => {
|
|
6584
|
+
clearTimeout(timer);
|
|
6585
|
+
resolve(value);
|
|
6586
|
+
}, (error) => {
|
|
6587
|
+
clearTimeout(timer);
|
|
6588
|
+
reject(error);
|
|
6589
|
+
});
|
|
6590
|
+
});
|
|
6591
|
+
}
|
|
6592
|
+
/** Whether a signal may act now, or has to be parked until the page is idle. */
|
|
6593
|
+
function canRevalidate(gate) {
|
|
6594
|
+
return gate.visible && gate.ready && !gate.busy && !gate.rebuilding;
|
|
5439
6595
|
}
|
|
5440
6596
|
//#endregion
|
|
5441
6597
|
//#region src/client/CapabilitySection.tsx
|
|
5442
6598
|
/**
|
|
5443
6599
|
* ⚠️ VERIFIED AGAINST REAL rc.8 CLIENT API.
|
|
5444
6600
|
*
|
|
5445
|
-
* React section for the
|
|
6601
|
+
* React section for the 能力菜单 settings tab. Follows the real dsh client
|
|
5446
6602
|
* pattern (see `dsh-client-ui-settings-plugins`): two tabs (工具 / Skills)
|
|
5447
6603
|
* under one heading, each listing capabilities with a clickable class chip.
|
|
5448
6604
|
*
|
|
@@ -5482,9 +6638,15 @@ const CSS = `
|
|
|
5482
6638
|
.mc-section{display:flex;flex-direction:column;gap:12px;color:var(--dsw-alias-label-primary)}
|
|
5483
6639
|
.mc-heading{margin:0;font-size:18px;font-weight:600}
|
|
5484
6640
|
.mc-desc{margin:0;color:var(--dsw-alias-label-tertiary);font-size:13px}
|
|
6641
|
+
/* 说明行右侧放只读文档入口:把它从头部的计数行挪出来,计数多(Tools 常驻 ·
|
|
6642
|
+
167)时那一行不再因此折行。 */
|
|
6643
|
+
.mc-desc-row{display:flex;align-items:center;justify-content:space-between;gap:12px}
|
|
6644
|
+
.mc-desc-row .mc-desc{flex:1 1 auto;min-width:0}
|
|
5485
6645
|
.mc-summary{display:flex;gap:12px;flex-wrap:wrap;justify-content:flex-end;align-items:center;padding-bottom:8px}
|
|
5486
6646
|
.mc-catalog-btn{border:1px solid var(--dsw-alias-border-l2);border-radius:6px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-secondary);font:inherit;font-size:12px;line-height:20px;padding:0 10px;cursor:pointer;white-space:nowrap}
|
|
5487
6647
|
.mc-catalog-btn:hover{border-color:var(--dsw-alias-border-l3);background:var(--dsw-alias-interactive-bg-hover)}
|
|
6648
|
+
/* 忙碌态靠变淡表示,而不是换文案:换文案会改按钮宽度,把这行挤到第二行。 */
|
|
6649
|
+
.mc-catalog-btn:disabled{cursor:default;opacity:.6}
|
|
5488
6650
|
.mc-catalog-tabs{padding:8px 16px 0}
|
|
5489
6651
|
.mc-catalog-path{padding:8px 16px 0;margin:0;font-size:12px;line-height:18px;color:var(--dsw-alias-label-tertiary);word-break:break-all}
|
|
5490
6652
|
.mc-chip{display:inline-flex;align-items:center;gap:6px;font-size:12px;line-height:20px;white-space:nowrap}
|
|
@@ -5508,6 +6670,15 @@ body[data-ds-dark-theme] .mc-chip--resident{color:#96b6d1}
|
|
|
5508
6670
|
body[data-ds-dark-theme] .mc-chip--on-demand{color:#d4b26b}
|
|
5509
6671
|
body[data-ds-dark-theme] .mc-chip--disabled{color:#b8abad}
|
|
5510
6672
|
.mc-tabs{border-bottom:1px solid var(--dsw-alias-border-l2);display:flex;align-items:flex-end;justify-content:space-between;gap:22px}
|
|
6673
|
+
/* 名字过滤框:两个 tab 共用一条,宽度占满,与下方列表左对齐。
|
|
6674
|
+
它属于上面的主 tab 栏,不属于下面的列表,所以两侧都把 .mc-section 的 12px
|
|
6675
|
+
栏距收掉一部分:下划线—6px—输入框—8px—全局/项目 子页签文字(后者里的 4px
|
|
6676
|
+
是子页签自己的 padding-top)。改之前是 12+12=24px 和 12+4=16px,读起来像
|
|
6677
|
+
两个不相干的块。 */
|
|
6678
|
+
.mc-filter{display:flex;padding-top:0;margin-top:-6px;margin-bottom:-8px}
|
|
6679
|
+
.mc-filter input{box-sizing:border-box;width:100%;min-width:0;padding:6px 10px;border:1px solid var(--dsw-alias-border-l2);border-radius:6px;background:var(--dsw-alias-bg-layer-1);color:inherit;font:inherit;font-size:13px;line-height:18px}
|
|
6680
|
+
.mc-filter input:hover{border-color:var(--dsw-alias-border-l3)}
|
|
6681
|
+
.mc-filter input:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:1px;border-color:transparent}
|
|
5511
6682
|
/* Skills sub-tab bar (全局技能/项目技能): same underline chrome, no right-side summary. */
|
|
5512
6683
|
.mc-subtabs{border-bottom:1px solid var(--dsw-alias-border-l2);display:flex;align-items:flex-end;gap:22px}
|
|
5513
6684
|
.mc-tab-group{display:flex;align-items:flex-end;gap:22px}
|
|
@@ -5515,7 +6686,19 @@ body[data-ds-dark-theme] .mc-chip--disabled{color:#b8abad}
|
|
|
5515
6686
|
.mc-tab:hover,.mc-tab[data-active=true]{color:var(--dsw-alias-label-primary)}
|
|
5516
6687
|
.mc-tab[data-active=true]:after,.mc-tab:focus-visible:after{background:var(--dsw-alias-label-primary);content:"";border-radius:2px 2px 0 0;height:2px;position:absolute;bottom:-1px;left:0;right:0}
|
|
5517
6688
|
.mc-tab:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:2px;color:var(--dsw-alias-label-primary);border-radius:2px}
|
|
5518
|
-
|
|
6689
|
+
/* 面板的第一行必须和另一个 tab 的第一行从过滤框下沿出发走同样远,否则切 tab 时
|
|
6690
|
+
首行会跳一下。这里留 4px,加上 .mc-section 的 4px 栏距(过滤框那侧被 -8px 收
|
|
6691
|
+
掉后剩 4px)= 8px,正好是 Skills 面板子页签文字的位置(4px 栏距 + 子页签自己
|
|
6692
|
+
的 4px padding-top)。原来这里是 12px,Tools 的首个分组因此落在过滤框下 16px
|
|
6693
|
+
处,比「全局技能」那行低一半。 */
|
|
6694
|
+
.mc-panel{min-width:0;padding-top:4px}
|
|
6695
|
+
/* Skills panel: its first row is the 全局技能/项目技能 sub-tab bar, and that bar
|
|
6696
|
+
belongs to the header stack above it (rule → name filter → sub-tabs) rather
|
|
6697
|
+
than to the list below. So the panel adds no padding of its own, and the
|
|
6698
|
+
sub-tab labels keep a 4px top padding instead of the 7px they share with the
|
|
6699
|
+
primary tabs. */
|
|
6700
|
+
.mc-panel--tight{padding-top:0}
|
|
6701
|
+
.mc-panel--tight .mc-subtabs .mc-tab{padding-top:4px}
|
|
5519
6702
|
.mc-panel-inner{display:flex;flex-direction:column;gap:14px}
|
|
5520
6703
|
.mc-group{border:1px solid var(--dsw-alias-border-l2);border-radius:8px;overflow:hidden}
|
|
5521
6704
|
.mc-group-header{box-sizing:border-box;display:flex;align-items:center;gap:10px;width:100%;min-width:0;padding:10px 12px;background:var(--dsw-alias-bg-layer-1);border:0;color:inherit;font:inherit;text-align:left;cursor:pointer}
|
|
@@ -5536,6 +6719,11 @@ body[data-ds-dark-theme] .mc-chip--disabled{color:#b8abad}
|
|
|
5536
6719
|
body[data-ds-dark-theme] .mc-count--resident{color:#96b6d1}
|
|
5537
6720
|
body[data-ds-dark-theme] .mc-count--on-demand{color:#d4b26b}
|
|
5538
6721
|
body[data-ds-dark-theme] .mc-count--disabled{color:#b8abad}
|
|
6722
|
+
/* 「编辑」是动作,不是分类计数:沿用 chip 的尺寸以对齐,但用次要文字色、hover
|
|
6723
|
+
才变蓝加下划线。裸用 .mc-count 时会继承 .mc-section 的 label-primary(浅色
|
|
6724
|
+
主题下近黑),夹在着色的计数 chip 旁会读成一枚没有圆点的黑色 chip。 */
|
|
6725
|
+
.mc-count--action{color:var(--dsw-alias-label-secondary)}
|
|
6726
|
+
.mc-count--action:hover{color:var(--dsw-alias-state-business-primary);text-decoration:underline}
|
|
5539
6727
|
.mc-tools{border-top:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-base)}
|
|
5540
6728
|
.mc-tool{display:flex;align-items:center;gap:10px;padding:7px 12px 7px 26px;font-size:13px;line-height:20px;cursor:pointer}
|
|
5541
6729
|
.mc-tool:hover{background:var(--dsw-alias-interactive-bg-hover)}
|
|
@@ -5547,14 +6735,30 @@ body[data-ds-dark-theme] .mc-count--disabled{color:#b8abad}
|
|
|
5547
6735
|
.mc-tag{font-size:11px;line-height:18px;padding:0 8px;border-radius:999px;border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-tertiary)}
|
|
5548
6736
|
.mc-empty{padding:16px;text-align:center;font-size:13px;color:var(--dsw-alias-label-tertiary);border:1px dashed var(--dsw-alias-border-l2);border-radius:8px}
|
|
5549
6737
|
.mc-error{padding:12px;border:1px solid var(--dsw-alias-state-error-primary);border-radius:8px;color:var(--dsw-alias-state-error-primary);font-size:13px}
|
|
5550
|
-
.mc-error
|
|
6738
|
+
.mc-error p{margin:0}
|
|
6739
|
+
.mc-error p.mc-error-hint{margin-top:8px;color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px}
|
|
6740
|
+
.mc-error-actions{display:flex;justify-content:flex-end;margin-top:10px}
|
|
5551
6741
|
.mc-notice{padding:10px 12px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;color:var(--dsw-alias-label-secondary);font-size:13px}
|
|
5552
6742
|
.mc-skill{border:1px solid var(--dsw-alias-border-l2);border-radius:8px;overflow:hidden;background:var(--dsw-alias-bg-layer-1)}
|
|
5553
6743
|
.mc-skill-row{box-sizing:border-box;display:flex;align-items:center;gap:10px;width:100%;min-width:0;padding:10px 12px;background:0 0;border:0;color:inherit;font:inherit;text-align:left;cursor:pointer}
|
|
5554
6744
|
.mc-skill-row:hover{background:var(--dsw-alias-interactive-bg-hover)}
|
|
5555
6745
|
.mc-skill-name{font-weight:600;font-size:14px;line-height:20px;flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
5556
6746
|
.mc-skill-meta{margin-left:auto;display:flex;align-items:center;gap:8px;flex:0 1 auto;min-width:0}
|
|
6747
|
+
/* A row has no room for prose: let a long source label truncate instead of
|
|
6748
|
+
pushing the row's buttons past the right edge. */
|
|
6749
|
+
.mc-source{font-size:11px;line-height:18px;color:var(--dsw-alias-label-caption);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;min-width:0;flex:0 1 auto}
|
|
6750
|
+
/* One sentence to confirm a row action: the shared modal chrome, sized down. */
|
|
6751
|
+
.mc-confirm-dialog{width:min(420px,calc(100vw - 48px))}
|
|
6752
|
+
.mc-confirm-body{display:flex;flex-direction:column;gap:12px;padding:14px 16px;font-size:13px;line-height:20px;color:var(--dsw-alias-label-secondary)}
|
|
6753
|
+
.mc-confirm-body p{margin:0}
|
|
6754
|
+
/* A directory has no spaces to break at, and the modal is where it can show in
|
|
6755
|
+
full: wrap it instead of truncating it the way a row's 来源 slot must. */
|
|
6756
|
+
.mc-confirm-path{font-family:var(--dsw-font-markdown-code-block-font-family);word-break:break-all}
|
|
6757
|
+
.mc-confirm-actions{display:flex;justify-content:flex-end;gap:8px}
|
|
5557
6758
|
.mc-skill-body{border-top:1px solid var(--dsw-alias-border-l1);padding:8px 12px 12px}
|
|
6759
|
+
/* 预设技能组:预设 id 是页签之下的分组小标题,不是新一级页签。 */
|
|
6760
|
+
.mc-skill-group{display:flex;flex-direction:column;gap:8px;min-width:0}
|
|
6761
|
+
.mc-skill-group-title{font-size:12px;line-height:18px;font-weight:600;color:var(--dsw-alias-label-secondary);padding:0 2px}
|
|
5558
6762
|
.mc-tree{display:flex;flex-direction:column;gap:2px;font-size:13px;line-height:20px}
|
|
5559
6763
|
.mc-tree-row{display:flex;align-items:center;gap:8px;padding:3px 4px;border-radius:6px;cursor:pointer;min-width:0}
|
|
5560
6764
|
.mc-tree-row:hover{background:var(--dsw-alias-interactive-bg-hover)}
|
|
@@ -5577,37 +6781,54 @@ if (typeof document !== "undefined" && document.querySelector(`style[data-css-id
|
|
|
5577
6781
|
tag.textContent = CSS;
|
|
5578
6782
|
document.head.appendChild(tag);
|
|
5579
6783
|
}
|
|
5580
|
-
/**
|
|
5581
|
-
|
|
5582
|
-
|
|
5583
|
-
|
|
5584
|
-
|
|
5585
|
-
|
|
5586
|
-
|
|
5587
|
-
|
|
5588
|
-
|
|
5589
|
-
|
|
5590
|
-
|
|
5591
|
-
|
|
5592
|
-
|
|
5593
|
-
|
|
5594
|
-
|
|
5595
|
-
|
|
5596
|
-
|
|
5597
|
-
|
|
5598
|
-
|
|
5599
|
-
|
|
5600
|
-
|
|
5601
|
-
|
|
5602
|
-
|
|
5603
|
-
|
|
6784
|
+
/**
|
|
6785
|
+
* dsh's skill source labels, shown on rows this plugin cannot edit — so "why is
|
|
6786
|
+
* there no 编辑 button" has a visible answer. Labels that name one directory are
|
|
6787
|
+
* shown as that path; the rest name a category and go through the dictionary, so
|
|
6788
|
+
* none of dsh's internal labels (`custom`, `bundled`, …) reach the operator raw.
|
|
6789
|
+
*/
|
|
6790
|
+
const SOURCE_PATHS = {
|
|
6791
|
+
"user-dsh": "~/.dsh/skills",
|
|
6792
|
+
"user-agents": "~/.agents/skills",
|
|
6793
|
+
"project-dsh": "<project>/.dsh/skills",
|
|
6794
|
+
"project-agents": "<project>/.agents/skills"
|
|
6795
|
+
};
|
|
6796
|
+
/** Source labels that name no single directory; shown via `t`. */
|
|
6797
|
+
const SOURCE_LABEL_KEYS = {
|
|
6798
|
+
"custom": "sourceCustom",
|
|
6799
|
+
"bundled": "sourceBundled"
|
|
6800
|
+
};
|
|
6801
|
+
/** How to name a skill's source root on a row we cannot manage. */
|
|
6802
|
+
function sourceLabel(source, t) {
|
|
6803
|
+
if (source === void 0) return t("skillUnmanaged");
|
|
6804
|
+
const path = SOURCE_PATHS[source];
|
|
6805
|
+
if (path !== void 0) return path;
|
|
6806
|
+
const key = SOURCE_LABEL_KEYS[source];
|
|
6807
|
+
return key === void 0 ? source : t(key);
|
|
5604
6808
|
}
|
|
5605
|
-
|
|
5606
|
-
|
|
6809
|
+
/**
|
|
6810
|
+
* Whether the document is visible. A hidden settings tab does no polling and
|
|
6811
|
+
* takes no re-reads; coming back is itself a revalidation trigger.
|
|
6812
|
+
*/
|
|
6813
|
+
function useDocumentVisible() {
|
|
6814
|
+
const [visible, setVisible] = (0, react.useState)(true);
|
|
6815
|
+
(0, react.useEffect)(() => {
|
|
6816
|
+
const update = () => setVisible(document.visibilityState !== "hidden");
|
|
6817
|
+
update();
|
|
6818
|
+
document.addEventListener("visibilitychange", update);
|
|
6819
|
+
return () => document.removeEventListener("visibilitychange", update);
|
|
6820
|
+
}, []);
|
|
6821
|
+
return visible;
|
|
5607
6822
|
}
|
|
5608
6823
|
function CapabilitySection(props) {
|
|
5609
|
-
const { remote, t, mountError,
|
|
5610
|
-
const [state, setState] = (0, react.useState)(
|
|
6824
|
+
const { remote, t, mountError, subscribeSignals } = props;
|
|
6825
|
+
const [state, setState] = (0, react.useState)(() => {
|
|
6826
|
+
const cached = cachedSnapshot();
|
|
6827
|
+
return cached === void 0 ? { status: "loading" } : {
|
|
6828
|
+
status: "ready",
|
|
6829
|
+
snapshot: cached
|
|
6830
|
+
};
|
|
6831
|
+
});
|
|
5611
6832
|
const [busy, setBusy] = (0, react.useState)(false);
|
|
5612
6833
|
const [notice, setNotice] = (0, react.useState)(null);
|
|
5613
6834
|
const [openServers, setOpenServers] = (0, react.useState)(/* @__PURE__ */ new Set());
|
|
@@ -5626,15 +6847,41 @@ function CapabilitySection(props) {
|
|
|
5626
6847
|
snapshot
|
|
5627
6848
|
})));
|
|
5628
6849
|
} catch (e) {
|
|
5629
|
-
setState({
|
|
6850
|
+
setState((prev) => prev.status === "ready" ? prev : {
|
|
5630
6851
|
status: "error",
|
|
5631
6852
|
message: String(e)
|
|
5632
6853
|
});
|
|
6854
|
+
setNotice(String(e));
|
|
5633
6855
|
}
|
|
5634
6856
|
}, [remote, mountError]);
|
|
5635
6857
|
(0, react.useEffect)(() => {
|
|
5636
6858
|
reload();
|
|
5637
6859
|
}, [reload]);
|
|
6860
|
+
const [refreshing, setRefreshing] = (0, react.useState)(false);
|
|
6861
|
+
/**
|
|
6862
|
+
* Rebuild the host catalog, then re-read the classification list. Used after
|
|
6863
|
+
* registering a new source so the list reflects it without waiting for the
|
|
6864
|
+
* scheduler's debounce window.
|
|
6865
|
+
*/
|
|
6866
|
+
const refreshCatalog = (0, react.useCallback)(async () => {
|
|
6867
|
+
if (refreshing) return;
|
|
6868
|
+
setRefreshing(true);
|
|
6869
|
+
try {
|
|
6870
|
+
if (remote !== void 0) unwrap(await remote.refresh(), "capabilityPolicy.refresh");
|
|
6871
|
+
await reload();
|
|
6872
|
+
setNotice(null);
|
|
6873
|
+
} catch (e) {
|
|
6874
|
+
console.error("[capability-menu] refresh failed:", e);
|
|
6875
|
+
setNotice(t("refreshFailed"));
|
|
6876
|
+
} finally {
|
|
6877
|
+
setRefreshing(false);
|
|
6878
|
+
}
|
|
6879
|
+
}, [
|
|
6880
|
+
refreshing,
|
|
6881
|
+
remote,
|
|
6882
|
+
reload,
|
|
6883
|
+
t
|
|
6884
|
+
]);
|
|
5638
6885
|
const toggleServer = (0, react.useCallback)((server) => {
|
|
5639
6886
|
setOpenServers((prev) => {
|
|
5640
6887
|
const next = new Set(prev);
|
|
@@ -5643,10 +6890,99 @@ function CapabilitySection(props) {
|
|
|
5643
6890
|
return next;
|
|
5644
6891
|
});
|
|
5645
6892
|
}, []);
|
|
6893
|
+
const visible = useDocumentVisible();
|
|
6894
|
+
const [pendingSignal, setPendingSignal] = (0, react.useState)(false);
|
|
6895
|
+
const seenVersion = (0, react.useRef)(void 0);
|
|
6896
|
+
const pollFailures = (0, react.useRef)(0);
|
|
6897
|
+
const gate = (0, react.useRef)({
|
|
6898
|
+
visible: true,
|
|
6899
|
+
ready: false,
|
|
6900
|
+
busy: false,
|
|
6901
|
+
rebuilding: false
|
|
6902
|
+
});
|
|
6903
|
+
(0, react.useEffect)(() => {
|
|
6904
|
+
gate.current = {
|
|
6905
|
+
visible,
|
|
6906
|
+
ready: state.status === "ready",
|
|
6907
|
+
busy,
|
|
6908
|
+
rebuilding: refreshing
|
|
6909
|
+
};
|
|
6910
|
+
});
|
|
6911
|
+
/**
|
|
6912
|
+
* A signal that the host catalog may have moved. Runs now when the page is
|
|
6913
|
+
* idle; otherwise parks until it is, so a signal that lands mid-click is
|
|
6914
|
+
* never dropped (and never repaints the row under the cursor).
|
|
6915
|
+
*/
|
|
6916
|
+
const onExternalSignal = (0, react.useCallback)(() => {
|
|
6917
|
+
if (canRevalidate(gate.current)) refreshCatalog();
|
|
6918
|
+
else setPendingSignal(true);
|
|
6919
|
+
}, [refreshCatalog]);
|
|
6920
|
+
(0, react.useEffect)(() => {
|
|
6921
|
+
return subscribeSignals?.(onExternalSignal);
|
|
6922
|
+
}, [subscribeSignals, onExternalSignal]);
|
|
6923
|
+
(0, react.useEffect)(() => {
|
|
6924
|
+
if (!pendingSignal) return;
|
|
6925
|
+
if (!canRevalidate({
|
|
6926
|
+
visible,
|
|
6927
|
+
ready: state.status === "ready",
|
|
6928
|
+
busy,
|
|
6929
|
+
rebuilding: refreshing
|
|
6930
|
+
})) return;
|
|
6931
|
+
setPendingSignal(false);
|
|
6932
|
+
refreshCatalog();
|
|
6933
|
+
}, [
|
|
6934
|
+
pendingSignal,
|
|
6935
|
+
visible,
|
|
6936
|
+
state.status,
|
|
6937
|
+
busy,
|
|
6938
|
+
refreshing,
|
|
6939
|
+
refreshCatalog
|
|
6940
|
+
]);
|
|
6941
|
+
(0, react.useEffect)(() => {
|
|
6942
|
+
if (!visible || state.status !== "ready" || busy || refreshing) return;
|
|
6943
|
+
let stopped = false;
|
|
6944
|
+
let timer;
|
|
6945
|
+
const tick = async () => {
|
|
6946
|
+
try {
|
|
6947
|
+
const version = unwrap(await withDeadline(remote.catalogVersion(), POLL_SAMPLE_TIMEOUT_MS), "capabilityPolicy.catalogVersion");
|
|
6948
|
+
pollFailures.current = 0;
|
|
6949
|
+
if (versionChanged(seenVersion.current, version)) {
|
|
6950
|
+
seenVersion.current = version;
|
|
6951
|
+
await reload();
|
|
6952
|
+
}
|
|
6953
|
+
} catch {
|
|
6954
|
+
pollFailures.current += 1;
|
|
6955
|
+
}
|
|
6956
|
+
if (!stopped) timer = setTimeout(() => void tick(), pollDelayMs(pollFailures.current));
|
|
6957
|
+
};
|
|
6958
|
+
timer = setTimeout(() => void tick(), 0);
|
|
6959
|
+
return () => {
|
|
6960
|
+
stopped = true;
|
|
6961
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
6962
|
+
};
|
|
6963
|
+
}, [
|
|
6964
|
+
visible,
|
|
6965
|
+
state.status,
|
|
6966
|
+
busy,
|
|
6967
|
+
refreshing,
|
|
6968
|
+
remote,
|
|
6969
|
+
reload
|
|
6970
|
+
]);
|
|
5646
6971
|
/** Move one or more capability ids to the next class in the click cycle. */
|
|
5647
6972
|
const cycleClass = (0, react.useCallback)(async (ids, kind) => {
|
|
5648
6973
|
if (ids.length === 0 || busy) return;
|
|
5649
6974
|
setBusy(true);
|
|
6975
|
+
const from = ids.map((id) => state.status === "ready" ? state.snapshot.rows.find((r) => r.id === id)?.class : void 0).find((c) => c !== void 0) ?? "on-demand";
|
|
6976
|
+
const to = NEXT_CLASS[from];
|
|
6977
|
+
const moved = new Set(ids);
|
|
6978
|
+
setState((prev) => prev.status === "ready" ? {
|
|
6979
|
+
status: "ready",
|
|
6980
|
+
snapshot: { rows: prev.snapshot.rows.map((row) => moved.has(row.id) ? {
|
|
6981
|
+
...row,
|
|
6982
|
+
class: to,
|
|
6983
|
+
classLabel: void 0
|
|
6984
|
+
} : row) }
|
|
6985
|
+
} : prev);
|
|
5650
6986
|
try {
|
|
5651
6987
|
const config = unwrap(await remote.getConfig(), "capabilityPolicy.getConfig");
|
|
5652
6988
|
const key = kind === "skill" ? "skills" : "tools";
|
|
@@ -5660,13 +6996,11 @@ function CapabilitySection(props) {
|
|
|
5660
6996
|
const raw = set && typeof set === "object" && !Array.isArray(set) ? set[cls] : void 0;
|
|
5661
6997
|
lists[cls] = Array.isArray(raw) ? raw.filter((x) => typeof x === "string") : [];
|
|
5662
6998
|
}
|
|
5663
|
-
const from = ids.map((id) => state.status === "ready" ? state.snapshot.rows.find((r) => r.id === id)?.class : void 0).find((c) => c !== void 0) ?? "on-demand";
|
|
5664
|
-
const to = NEXT_CLASS[from];
|
|
5665
6999
|
for (const cls of CLASS_KEYS) lists[cls] = lists[cls].filter((id) => !ids.includes(id));
|
|
5666
7000
|
lists[to] = [...lists[to], ...ids];
|
|
5667
7001
|
const nextLists = {};
|
|
5668
7002
|
for (const cls of CLASS_KEYS) nextLists[cls] = lists[cls];
|
|
5669
|
-
await remote.updateConfig({ [key]: nextLists });
|
|
7003
|
+
unwrap(await remote.updateConfig({ [key]: nextLists }), "capabilityPolicy.updateConfig");
|
|
5670
7004
|
const next = await loadSnapshot(remote);
|
|
5671
7005
|
setState({
|
|
5672
7006
|
status: "ready",
|
|
@@ -5697,7 +7031,22 @@ function CapabilitySection(props) {
|
|
|
5697
7031
|
}),
|
|
5698
7032
|
state.status === "error" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5699
7033
|
className: "mc-error",
|
|
5700
|
-
children: [
|
|
7034
|
+
children: [
|
|
7035
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: state.message }),
|
|
7036
|
+
state.message.includes("Failed to fetch") && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
7037
|
+
className: "mc-error-hint",
|
|
7038
|
+
children: t("carrierFailureHint")
|
|
7039
|
+
}),
|
|
7040
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
7041
|
+
className: "mc-error-actions",
|
|
7042
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
7043
|
+
type: "button",
|
|
7044
|
+
className: "mc-catalog-btn",
|
|
7045
|
+
onClick: () => void reload(),
|
|
7046
|
+
children: t("retry")
|
|
7047
|
+
})
|
|
7048
|
+
})
|
|
7049
|
+
]
|
|
5701
7050
|
}),
|
|
5702
7051
|
notice !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5703
7052
|
className: "mc-notice",
|
|
@@ -5712,18 +7061,33 @@ function CapabilitySection(props) {
|
|
|
5712
7061
|
t,
|
|
5713
7062
|
onTabChange: setActiveTab,
|
|
5714
7063
|
onToggleServer: toggleServer,
|
|
5715
|
-
onCycle: cycleClass
|
|
7064
|
+
onCycle: cycleClass,
|
|
7065
|
+
onRefresh: () => void refreshCatalog(),
|
|
7066
|
+
onNotice: setNotice
|
|
5716
7067
|
})
|
|
5717
7068
|
]
|
|
5718
7069
|
});
|
|
5719
7070
|
}
|
|
5720
7071
|
function ReadyBody(props) {
|
|
5721
|
-
const { remote, snapshot, openServers, busy, activeTab, t, onTabChange, onToggleServer, onCycle } = props;
|
|
5722
|
-
|
|
5723
|
-
const
|
|
5724
|
-
const
|
|
7072
|
+
const { remote, snapshot, openServers, busy, activeTab, t, onTabChange, onToggleServer, onCycle, onRefresh, onNotice } = props;
|
|
7073
|
+
/** Name filter, shared by both tabs: it narrows whichever list is shown. */
|
|
7074
|
+
const [filter, setFilter] = (0, react.useState)("");
|
|
7075
|
+
const nameFilter = compileNameFilter(filter);
|
|
7076
|
+
const needle = nameFilter.needle;
|
|
7077
|
+
const shownRows = filterRows(snapshot.rows, nameFilter, {
|
|
7078
|
+
builtIn: t("builtInGroup"),
|
|
7079
|
+
source: (source) => sourceLabel(source, t)
|
|
7080
|
+
});
|
|
7081
|
+
const { servers, skills } = groupRows(shownRows);
|
|
7082
|
+
const { presetSkills, presetGroups, projectSkills, globalSkills } = splitSkillGroups(skills);
|
|
5725
7083
|
const [skillTab, setSkillTab] = (0, react.useState)("global");
|
|
5726
|
-
const
|
|
7084
|
+
const activeSkillTab = resolveSkillTab(skillTab, {
|
|
7085
|
+
presetSkills,
|
|
7086
|
+
presetGroups,
|
|
7087
|
+
projectSkills,
|
|
7088
|
+
globalSkills
|
|
7089
|
+
});
|
|
7090
|
+
const statRows = activeTab === "tools" ? shownRows.filter((r) => r.kind === "tool") : activeSkillTab === "project" ? projectSkills : activeSkillTab === "preset" ? presetSkills : globalSkills;
|
|
5727
7091
|
const summary = CLASS_KEYS.map((cls) => ({
|
|
5728
7092
|
cls,
|
|
5729
7093
|
count: countByClass(statRows, cls)
|
|
@@ -5758,6 +7122,93 @@ function ReadyBody(props) {
|
|
|
5758
7122
|
/** 能力目录弹层状态:查看三档策略配置 + 按需能力目录文件。 */
|
|
5759
7123
|
const [catalogDocs, setCatalogDocs] = (0, react.useState)(null);
|
|
5760
7124
|
const [catalogTab, setCatalogTab] = (0, react.useState)("policy");
|
|
7125
|
+
/** 注册能力 modal; its default sub-tab follows `activeTab`. */
|
|
7126
|
+
const [registerOpen, setRegisterOpen] = (0, react.useState)(false);
|
|
7127
|
+
/** 编辑 target; at most one is set. */
|
|
7128
|
+
const [editMcp, setEditMcp] = (0, react.useState)(void 0);
|
|
7129
|
+
const [editSkill, setEditSkill] = (0, react.useState)(void 0);
|
|
7130
|
+
/**
|
|
7131
|
+
* Skill directories registered under the skill root. Only those can be
|
|
7132
|
+
* edited: a project or bundled skill has no row to repoint, so its row gets
|
|
7133
|
+
* no 编辑 button rather than a button that always fails.
|
|
7134
|
+
*/
|
|
7135
|
+
const [editableSkills, setEditableSkills] = (0, react.useState)(/* @__PURE__ */ new Set());
|
|
7136
|
+
const skillSignature = skills.map((skill) => `${skill.id}\u0000${skill.source ?? ""}`).join("");
|
|
7137
|
+
(0, react.useEffect)(() => {
|
|
7138
|
+
let cancelled = false;
|
|
7139
|
+
(async () => {
|
|
7140
|
+
try {
|
|
7141
|
+
const rows = unwrap(await remote.listSkillLocations(), "capabilityPolicy.listSkillLocations");
|
|
7142
|
+
if (!cancelled) setEditableSkills(new Set(rows.map((row) => row.skillName ?? row.name)));
|
|
7143
|
+
} catch {}
|
|
7144
|
+
})();
|
|
7145
|
+
return () => {
|
|
7146
|
+
cancelled = true;
|
|
7147
|
+
};
|
|
7148
|
+
}, [remote, skillSignature]);
|
|
7149
|
+
/** Open the 编辑 form for the MCP server behind a Tools-tab group header. */
|
|
7150
|
+
const openMcpEdit = (0, react.useCallback)(async (serverName) => {
|
|
7151
|
+
try {
|
|
7152
|
+
const found = unwrap(await remote.listLocations(), "capabilityPolicy.listLocations").find((row) => row.serverName === serverName);
|
|
7153
|
+
if (found === void 0) {
|
|
7154
|
+
onNotice(t("notEditable"));
|
|
7155
|
+
return;
|
|
7156
|
+
}
|
|
7157
|
+
setEditMcp(found);
|
|
7158
|
+
} catch (e) {
|
|
7159
|
+
onNotice(String(e));
|
|
7160
|
+
}
|
|
7161
|
+
}, [
|
|
7162
|
+
remote,
|
|
7163
|
+
t,
|
|
7164
|
+
onNotice
|
|
7165
|
+
]);
|
|
7166
|
+
/**
|
|
7167
|
+
* Adopt a skill that dsh discovered from a user-level root this plugin reads
|
|
7168
|
+
* but does not manage: link its own directory into the user root so it becomes
|
|
7169
|
+
* an editable entry. The content is untouched.
|
|
7170
|
+
*/
|
|
7171
|
+
const adoptSkill = (0, react.useCallback)(async (name) => {
|
|
7172
|
+
try {
|
|
7173
|
+
const entry = unwrap(await remote.adoptSkillLocation(name), "capabilityPolicy.adoptSkillLocation");
|
|
7174
|
+
onNotice(t("skillAdopted", { path: entry }));
|
|
7175
|
+
onRefresh();
|
|
7176
|
+
} catch (e) {
|
|
7177
|
+
onNotice(String(e));
|
|
7178
|
+
}
|
|
7179
|
+
}, [
|
|
7180
|
+
remote,
|
|
7181
|
+
t,
|
|
7182
|
+
onNotice,
|
|
7183
|
+
onRefresh
|
|
7184
|
+
]);
|
|
7185
|
+
/**
|
|
7186
|
+
* Open the 编辑 form for a managed skill entry. `name` is the skill's declared
|
|
7187
|
+
* name, which is what the row and the panel address it by — an entry is
|
|
7188
|
+
* matched on that rather than on its own, possibly different, name. A bare
|
|
7189
|
+
* name can exist in both the user root and a project root, so the row's source
|
|
7190
|
+
* decides which entry the click meant; without it we fall back to the first
|
|
7191
|
+
* match.
|
|
7192
|
+
*/
|
|
7193
|
+
const openSkillEdit = (0, react.useCallback)(async (name, source) => {
|
|
7194
|
+
try {
|
|
7195
|
+
const rows = unwrap(await remote.listSkillLocations(), "capabilityPolicy.listSkillLocations");
|
|
7196
|
+
const wanted = PROJECT_SKILL_SOURCES.has(source ?? "") ? "project" : "user";
|
|
7197
|
+
const named = rows.filter((row) => (row.skillName ?? row.name) === name);
|
|
7198
|
+
const found = named.find((row) => row.root === wanted) ?? named[0];
|
|
7199
|
+
if (found === void 0) {
|
|
7200
|
+
onNotice(t("notEditable"));
|
|
7201
|
+
return;
|
|
7202
|
+
}
|
|
7203
|
+
setEditSkill(found);
|
|
7204
|
+
} catch (e) {
|
|
7205
|
+
onNotice(String(e));
|
|
7206
|
+
}
|
|
7207
|
+
}, [
|
|
7208
|
+
remote,
|
|
7209
|
+
t,
|
|
7210
|
+
onNotice
|
|
7211
|
+
]);
|
|
5761
7212
|
/** Fetch and show the two read-only catalog documents. */
|
|
5762
7213
|
const openCatalogDocs = (0, react.useCallback)(async () => {
|
|
5763
7214
|
setCatalogDocs({ status: "loading" });
|
|
@@ -5779,9 +7230,17 @@ function ReadyBody(props) {
|
|
|
5779
7230
|
className: "mc-heading",
|
|
5780
7231
|
children: t("title")
|
|
5781
7232
|
}),
|
|
5782
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
5783
|
-
className: "mc-desc",
|
|
5784
|
-
children:
|
|
7233
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
7234
|
+
className: "mc-desc-row",
|
|
7235
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
7236
|
+
className: "mc-desc",
|
|
7237
|
+
children: t("desc")
|
|
7238
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
7239
|
+
type: "button",
|
|
7240
|
+
className: "mc-catalog-btn",
|
|
7241
|
+
onClick: () => void openCatalogDocs(),
|
|
7242
|
+
children: t("viewCatalog")
|
|
7243
|
+
})]
|
|
5785
7244
|
}),
|
|
5786
7245
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5787
7246
|
className: "mc-tabs",
|
|
@@ -5822,11 +7281,22 @@ function ReadyBody(props) {
|
|
|
5822
7281
|
}, cls)), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
5823
7282
|
type: "button",
|
|
5824
7283
|
className: "mc-catalog-btn",
|
|
5825
|
-
onClick: () =>
|
|
5826
|
-
children: t("
|
|
7284
|
+
onClick: () => setRegisterOpen(true),
|
|
7285
|
+
children: t("registerCapability")
|
|
5827
7286
|
})]
|
|
5828
7287
|
})]
|
|
5829
7288
|
}),
|
|
7289
|
+
snapshot.rows.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
7290
|
+
className: "mc-filter",
|
|
7291
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
7292
|
+
type: "search",
|
|
7293
|
+
value: filter,
|
|
7294
|
+
placeholder: t("filterByName"),
|
|
7295
|
+
"aria-label": t("filterByName"),
|
|
7296
|
+
title: t("filterHint"),
|
|
7297
|
+
onChange: (e) => setFilter(e.target.value)
|
|
7298
|
+
})
|
|
7299
|
+
}),
|
|
5830
7300
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5831
7301
|
role: "tabpanel",
|
|
5832
7302
|
hidden: activeTab !== "tools",
|
|
@@ -5835,7 +7305,7 @@ function ReadyBody(props) {
|
|
|
5835
7305
|
className: "mc-panel-inner",
|
|
5836
7306
|
children: servers.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
5837
7307
|
className: "mc-empty",
|
|
5838
|
-
children: t("emptyTools")
|
|
7308
|
+
children: needle === "" ? t("emptyTools") : t("filterNoMatch")
|
|
5839
7309
|
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: servers.map(({ server, tools }) => {
|
|
5840
7310
|
const open = openServers.has(server);
|
|
5841
7311
|
const counts = CLASS_KEYS.map((cls) => ({
|
|
@@ -5863,15 +7333,15 @@ function ReadyBody(props) {
|
|
|
5863
7333
|
}),
|
|
5864
7334
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5865
7335
|
className: "mc-server-name",
|
|
5866
|
-
children: server ===
|
|
7336
|
+
children: server === "built-in" ? t("builtInGroup") : server
|
|
5867
7337
|
}),
|
|
5868
7338
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5869
7339
|
className: "mc-server-count",
|
|
5870
7340
|
children: t("toolCount", { count: tools.length })
|
|
5871
7341
|
}),
|
|
5872
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
7342
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
5873
7343
|
className: "mc-server-meta",
|
|
5874
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7344
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5875
7345
|
className: "mc-counts",
|
|
5876
7346
|
children: counts.filter(({ count }) => count > 0).map(({ cls, count }) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
5877
7347
|
type: "button",
|
|
@@ -5892,7 +7362,16 @@ function ReadyBody(props) {
|
|
|
5892
7362
|
count
|
|
5893
7363
|
]
|
|
5894
7364
|
}, cls))
|
|
5895
|
-
})
|
|
7365
|
+
}), server !== "built-in" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
7366
|
+
type: "button",
|
|
7367
|
+
className: "mc-count mc-count--action",
|
|
7368
|
+
disabled: busy,
|
|
7369
|
+
onClick: (e) => {
|
|
7370
|
+
e.stopPropagation();
|
|
7371
|
+
openMcpEdit(server);
|
|
7372
|
+
},
|
|
7373
|
+
children: t("edit")
|
|
7374
|
+
})]
|
|
5896
7375
|
})
|
|
5897
7376
|
]
|
|
5898
7377
|
}), open && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
@@ -5943,55 +7422,88 @@ function ReadyBody(props) {
|
|
|
5943
7422
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5944
7423
|
role: "tabpanel",
|
|
5945
7424
|
hidden: activeTab !== "skills",
|
|
5946
|
-
className: "mc-panel",
|
|
7425
|
+
className: "mc-panel mc-panel--tight",
|
|
5947
7426
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5948
7427
|
className: "mc-panel-inner",
|
|
5949
7428
|
children: skills.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
5950
7429
|
className: "mc-empty",
|
|
5951
|
-
children: t("emptySkills")
|
|
7430
|
+
children: needle === "" ? t("emptySkills") : t("filterNoMatch")
|
|
5952
7431
|
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5953
7432
|
className: "mc-subtabs",
|
|
5954
7433
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5955
7434
|
className: "mc-tab-group",
|
|
5956
7435
|
role: "tablist",
|
|
5957
7436
|
"aria-label": t("skillsGroup"),
|
|
5958
|
-
children: [
|
|
5959
|
-
|
|
5960
|
-
|
|
5961
|
-
|
|
5962
|
-
|
|
5963
|
-
|
|
5964
|
-
|
|
5965
|
-
|
|
5966
|
-
|
|
5967
|
-
|
|
5968
|
-
|
|
5969
|
-
|
|
5970
|
-
|
|
5971
|
-
|
|
5972
|
-
|
|
5973
|
-
|
|
5974
|
-
|
|
7437
|
+
children: [
|
|
7438
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
7439
|
+
type: "button",
|
|
7440
|
+
role: "tab",
|
|
7441
|
+
className: "mc-tab",
|
|
7442
|
+
"aria-selected": activeSkillTab === "global",
|
|
7443
|
+
"data-active": activeSkillTab === "global" ? "true" : void 0,
|
|
7444
|
+
onClick: () => setSkillTab("global"),
|
|
7445
|
+
children: t("globalSkills")
|
|
7446
|
+
}),
|
|
7447
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
7448
|
+
type: "button",
|
|
7449
|
+
role: "tab",
|
|
7450
|
+
className: "mc-tab",
|
|
7451
|
+
"aria-selected": activeSkillTab === "project",
|
|
7452
|
+
"data-active": activeSkillTab === "project" ? "true" : void 0,
|
|
7453
|
+
onClick: () => setSkillTab("project"),
|
|
7454
|
+
children: t("projectSkills")
|
|
7455
|
+
}),
|
|
7456
|
+
presetSkills.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
7457
|
+
type: "button",
|
|
7458
|
+
role: "tab",
|
|
7459
|
+
className: "mc-tab",
|
|
7460
|
+
"aria-selected": activeSkillTab === "preset",
|
|
7461
|
+
"data-active": activeSkillTab === "preset" ? "true" : void 0,
|
|
7462
|
+
onClick: () => setSkillTab("preset"),
|
|
7463
|
+
children: t("presetSkills")
|
|
7464
|
+
})
|
|
7465
|
+
]
|
|
5975
7466
|
})
|
|
5976
|
-
}),
|
|
7467
|
+
}), activeSkillTab === "global" ? globalSkills.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SkillList, {
|
|
5977
7468
|
skills: globalSkills,
|
|
5978
7469
|
remote,
|
|
5979
7470
|
busy,
|
|
5980
7471
|
t,
|
|
5981
|
-
onCycle
|
|
7472
|
+
onCycle,
|
|
7473
|
+
editableSkills,
|
|
7474
|
+
onEditSkill: openSkillEdit,
|
|
7475
|
+
onAdoptSkill: adoptSkill
|
|
5982
7476
|
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
5983
7477
|
className: "mc-empty",
|
|
5984
7478
|
children: t("emptyGlobalSkills")
|
|
5985
|
-
}) : projectSkills.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SkillList, {
|
|
7479
|
+
}) : activeSkillTab === "project" ? projectSkills.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SkillList, {
|
|
5986
7480
|
skills: projectSkills,
|
|
5987
7481
|
remote,
|
|
5988
7482
|
busy,
|
|
5989
7483
|
t,
|
|
5990
|
-
onCycle
|
|
7484
|
+
onCycle,
|
|
7485
|
+
editableSkills,
|
|
7486
|
+
onEditSkill: openSkillEdit,
|
|
7487
|
+
onAdoptSkill: adoptSkill
|
|
5991
7488
|
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
5992
7489
|
className: "mc-empty",
|
|
5993
7490
|
children: t("emptyProjectSkills")
|
|
5994
|
-
})]
|
|
7491
|
+
}) : presetGroups.map(([preset, group]) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
7492
|
+
className: "mc-skill-group",
|
|
7493
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
7494
|
+
className: "mc-skill-group-title",
|
|
7495
|
+
children: preset
|
|
7496
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SkillList, {
|
|
7497
|
+
skills: group,
|
|
7498
|
+
remote,
|
|
7499
|
+
busy,
|
|
7500
|
+
t,
|
|
7501
|
+
onCycle,
|
|
7502
|
+
editableSkills,
|
|
7503
|
+
onEditSkill: openSkillEdit,
|
|
7504
|
+
onAdoptSkill: adoptSkill
|
|
7505
|
+
})]
|
|
7506
|
+
}, preset))] })
|
|
5995
7507
|
})
|
|
5996
7508
|
}),
|
|
5997
7509
|
toolDetail !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
@@ -6035,6 +7547,22 @@ function ReadyBody(props) {
|
|
|
6035
7547
|
]
|
|
6036
7548
|
})
|
|
6037
7549
|
}),
|
|
7550
|
+
(registerOpen || editMcp !== void 0 || editSkill !== void 0) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LocationModal, {
|
|
7551
|
+
remote,
|
|
7552
|
+
t,
|
|
7553
|
+
defaultKind: activeTab === "skills" ? "skill" : "mcp",
|
|
7554
|
+
...editMcp !== void 0 ? { editMcp } : {},
|
|
7555
|
+
...editSkill !== void 0 ? { editSkill } : {},
|
|
7556
|
+
onClose: () => {
|
|
7557
|
+
setRegisterOpen(false);
|
|
7558
|
+
setEditMcp(void 0);
|
|
7559
|
+
setEditSkill(void 0);
|
|
7560
|
+
},
|
|
7561
|
+
onChanged: (notice) => {
|
|
7562
|
+
onRefresh();
|
|
7563
|
+
if (notice !== void 0) onNotice(notice);
|
|
7564
|
+
}
|
|
7565
|
+
}),
|
|
6038
7566
|
catalogDocs !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
6039
7567
|
className: "mc-preview-mask",
|
|
6040
7568
|
onClick: () => setCatalogDocs(null),
|
|
@@ -6108,7 +7636,9 @@ function ReadyBody(props) {
|
|
|
6108
7636
|
] });
|
|
6109
7637
|
}
|
|
6110
7638
|
function SkillList(props) {
|
|
6111
|
-
const { skills, remote, busy, t, onCycle } = props;
|
|
7639
|
+
const { skills, remote, busy, t, onCycle, editableSkills, onEditSkill, onAdoptSkill } = props;
|
|
7640
|
+
/** Skill id whose 纳入管理 is waiting for a confirmation click. */
|
|
7641
|
+
const [confirmAdopt, setConfirmAdopt] = (0, react.useState)(null);
|
|
6112
7642
|
const [openSkill, setOpenSkill] = (0, react.useState)(null);
|
|
6113
7643
|
const [tree, setTree] = (0, react.useState)({
|
|
6114
7644
|
open: {},
|
|
@@ -6277,89 +7807,169 @@ function SkillList(props) {
|
|
|
6277
7807
|
}, relPath);
|
|
6278
7808
|
});
|
|
6279
7809
|
};
|
|
6280
|
-
|
|
6281
|
-
|
|
6282
|
-
|
|
6283
|
-
|
|
6284
|
-
|
|
6285
|
-
|
|
6286
|
-
|
|
6287
|
-
|
|
6288
|
-
"
|
|
6289
|
-
|
|
6290
|
-
|
|
6291
|
-
|
|
6292
|
-
|
|
6293
|
-
|
|
6294
|
-
|
|
6295
|
-
|
|
6296
|
-
|
|
6297
|
-
|
|
6298
|
-
|
|
6299
|
-
|
|
6300
|
-
|
|
6301
|
-
|
|
6302
|
-
|
|
6303
|
-
|
|
6304
|
-
|
|
6305
|
-
|
|
6306
|
-
|
|
6307
|
-
|
|
6308
|
-
|
|
6309
|
-
|
|
6310
|
-
|
|
6311
|
-
|
|
6312
|
-
|
|
6313
|
-
|
|
6314
|
-
|
|
6315
|
-
|
|
6316
|
-
|
|
6317
|
-
|
|
6318
|
-
|
|
6319
|
-
|
|
6320
|
-
|
|
6321
|
-
|
|
6322
|
-
|
|
6323
|
-
|
|
7810
|
+
/** The row the 纳入管理 confirmation is for, so the dialog can name it and
|
|
7811
|
+
show the directory the click will link. */
|
|
7812
|
+
const confirmAdoptRow = confirmAdopt === null ? void 0 : skills.find((row) => row.id === confirmAdopt);
|
|
7813
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
7814
|
+
skills.map((skill) => {
|
|
7815
|
+
const open = openSkill === skill.id;
|
|
7816
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
7817
|
+
className: "mc-skill",
|
|
7818
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
7819
|
+
className: "mc-skill-row",
|
|
7820
|
+
role: "button",
|
|
7821
|
+
tabIndex: 0,
|
|
7822
|
+
"aria-expanded": open,
|
|
7823
|
+
onClick: () => void toggleSkill(skill.id),
|
|
7824
|
+
onKeyDown: (e) => {
|
|
7825
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
7826
|
+
e.preventDefault();
|
|
7827
|
+
toggleSkill(skill.id);
|
|
7828
|
+
}
|
|
7829
|
+
},
|
|
7830
|
+
children: [
|
|
7831
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconTriangleRightFill14, {
|
|
7832
|
+
size: 12,
|
|
7833
|
+
className: `mc-chevron${open ? " mc-chevron--open" : ""}`
|
|
7834
|
+
}),
|
|
7835
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7836
|
+
className: "mc-skill-name",
|
|
7837
|
+
children: skill.name
|
|
7838
|
+
}),
|
|
7839
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
7840
|
+
className: "mc-skill-meta",
|
|
7841
|
+
children: [
|
|
7842
|
+
skill.mandatory && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7843
|
+
className: "mc-tag",
|
|
7844
|
+
children: t("mandatory")
|
|
7845
|
+
}),
|
|
7846
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
7847
|
+
type: "button",
|
|
7848
|
+
className: `mc-count mc-count--${skill.class}`,
|
|
7849
|
+
disabled: busy || skill.mandatory,
|
|
7850
|
+
title: skill.classLabel,
|
|
7851
|
+
onClick: (e) => {
|
|
7852
|
+
e.stopPropagation();
|
|
7853
|
+
onCycle([skill.id], "skill");
|
|
7854
|
+
},
|
|
7855
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7856
|
+
className: `mc-dot mc-dot--${skill.class}`,
|
|
7857
|
+
"aria-hidden": "true"
|
|
7858
|
+
}), t(CLASS_SHORT_KEYS[skill.class])]
|
|
7859
|
+
}),
|
|
7860
|
+
!editableSkills.has(skill.id) && (skill.preset !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7861
|
+
className: "mc-source",
|
|
7862
|
+
title: t("skillFromPresetHint"),
|
|
7863
|
+
children: t("skillFromPreset", { preset: skill.preset })
|
|
7864
|
+
}) : ADOPTABLE_SKILL_SOURCES.has(skill.source ?? "") ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
7865
|
+
type: "button",
|
|
7866
|
+
className: "mc-count mc-count--action",
|
|
7867
|
+
disabled: busy,
|
|
7868
|
+
title: t("adoptSkillHint"),
|
|
7869
|
+
onClick: (e) => {
|
|
7870
|
+
e.stopPropagation();
|
|
7871
|
+
setConfirmAdopt(skill.id);
|
|
7872
|
+
},
|
|
7873
|
+
children: t("adoptSkill")
|
|
7874
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7875
|
+
className: "mc-source",
|
|
7876
|
+
title: t("skillSourceHint"),
|
|
7877
|
+
children: t("skillSource", { source: sourceLabel(skill.source, t) })
|
|
7878
|
+
})),
|
|
7879
|
+
editableSkills.has(skill.id) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
7880
|
+
type: "button",
|
|
7881
|
+
className: "mc-count mc-count--action",
|
|
7882
|
+
disabled: busy,
|
|
7883
|
+
onClick: (e) => {
|
|
7884
|
+
e.stopPropagation();
|
|
7885
|
+
onEditSkill(skill.id, skill.source);
|
|
7886
|
+
},
|
|
7887
|
+
children: t("edit")
|
|
7888
|
+
})
|
|
7889
|
+
]
|
|
7890
|
+
})
|
|
7891
|
+
]
|
|
7892
|
+
}), open && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
7893
|
+
className: "mc-skill-body",
|
|
7894
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
7895
|
+
className: "mc-tree",
|
|
7896
|
+
children: renderEntries(tree.dirs[`${skill.id}:`], skill.id, "") ?? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
7897
|
+
className: "mc-tree-row",
|
|
7898
|
+
children: "…"
|
|
7899
|
+
})
|
|
6324
7900
|
})
|
|
6325
|
-
]
|
|
6326
|
-
}
|
|
6327
|
-
|
|
6328
|
-
|
|
6329
|
-
|
|
6330
|
-
|
|
6331
|
-
|
|
6332
|
-
|
|
7901
|
+
})]
|
|
7902
|
+
}, skill.id);
|
|
7903
|
+
}),
|
|
7904
|
+
confirmAdopt !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
7905
|
+
className: "mc-preview-mask",
|
|
7906
|
+
onClick: () => setConfirmAdopt(null),
|
|
7907
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
7908
|
+
className: "mc-preview mc-confirm-dialog",
|
|
7909
|
+
onClick: (e) => e.stopPropagation(),
|
|
7910
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
7911
|
+
className: "mc-preview-head",
|
|
7912
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7913
|
+
className: "mc-preview-title",
|
|
7914
|
+
children: t("adoptSkillTitle", { name: confirmAdoptRow?.name ?? confirmAdopt })
|
|
6333
7915
|
})
|
|
6334
|
-
})
|
|
6335
|
-
|
|
6336
|
-
|
|
6337
|
-
|
|
6338
|
-
|
|
6339
|
-
|
|
6340
|
-
|
|
6341
|
-
|
|
6342
|
-
|
|
6343
|
-
|
|
6344
|
-
|
|
6345
|
-
|
|
6346
|
-
|
|
6347
|
-
|
|
6348
|
-
|
|
6349
|
-
|
|
6350
|
-
|
|
6351
|
-
|
|
6352
|
-
|
|
7916
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
7917
|
+
className: "mc-confirm-body",
|
|
7918
|
+
children: [
|
|
7919
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t("adoptSkillHint") }),
|
|
7920
|
+
confirmAdoptRow !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
7921
|
+
className: "mc-confirm-path",
|
|
7922
|
+
children: t("skillSource", { source: confirmAdoptRow.path ?? sourceLabel(confirmAdoptRow.source, t) })
|
|
7923
|
+
}),
|
|
7924
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
7925
|
+
className: "mc-confirm-actions",
|
|
7926
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
7927
|
+
type: "button",
|
|
7928
|
+
className: "mc-catalog-btn",
|
|
7929
|
+
onClick: () => setConfirmAdopt(null),
|
|
7930
|
+
children: t("cancel")
|
|
7931
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
7932
|
+
type: "button",
|
|
7933
|
+
className: "mc-catalog-btn",
|
|
7934
|
+
onClick: () => {
|
|
7935
|
+
const id = confirmAdopt;
|
|
7936
|
+
setConfirmAdopt(null);
|
|
7937
|
+
onAdoptSkill(id);
|
|
7938
|
+
},
|
|
7939
|
+
children: t("adoptSkill")
|
|
7940
|
+
})]
|
|
7941
|
+
})
|
|
7942
|
+
]
|
|
6353
7943
|
})]
|
|
6354
|
-
})
|
|
6355
|
-
|
|
6356
|
-
|
|
6357
|
-
|
|
6358
|
-
|
|
6359
|
-
|
|
6360
|
-
|
|
7944
|
+
})
|
|
7945
|
+
}),
|
|
7946
|
+
preview !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
7947
|
+
className: "mc-preview-mask",
|
|
7948
|
+
onClick: () => setPreview(null),
|
|
7949
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
7950
|
+
className: "mc-preview",
|
|
7951
|
+
onClick: (e) => e.stopPropagation(),
|
|
7952
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
7953
|
+
className: "mc-preview-head",
|
|
7954
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7955
|
+
className: "mc-preview-title",
|
|
7956
|
+
children: preview.relPath
|
|
7957
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
7958
|
+
type: "button",
|
|
7959
|
+
className: "mc-preview-close",
|
|
7960
|
+
onClick: () => setPreview(null),
|
|
7961
|
+
children: t("previewClose")
|
|
7962
|
+
})]
|
|
7963
|
+
}), preview.content !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("pre", {
|
|
7964
|
+
className: "mc-preview-body",
|
|
7965
|
+
children: preview.content
|
|
7966
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
7967
|
+
className: "mc-preview-hint",
|
|
7968
|
+
children: preview.error ?? "…"
|
|
7969
|
+
})]
|
|
7970
|
+
})
|
|
6361
7971
|
})
|
|
6362
|
-
|
|
7972
|
+
] });
|
|
6363
7973
|
}
|
|
6364
7974
|
//#endregion
|
|
6365
7975
|
//#region src/client/index.ts
|
|
@@ -6374,28 +7984,29 @@ const inject = [
|
|
|
6374
7984
|
"locale",
|
|
6375
7985
|
"remote"
|
|
6376
7986
|
];
|
|
6377
|
-
/** Register the
|
|
7987
|
+
/** Register the 能力菜单 section once `settings.section` is on the ledger. */
|
|
6378
7988
|
async function apply(ctx) {
|
|
6379
7989
|
const zh = {
|
|
6380
|
-
nav: "
|
|
6381
|
-
title: "
|
|
7990
|
+
nav: "能力菜单",
|
|
7991
|
+
title: "能力菜单",
|
|
6382
7992
|
desc: "管理工具与技能的 常驻 / 按需 / 禁用 三档分类。",
|
|
6383
7993
|
resident: "Resident(常驻上下文)",
|
|
6384
7994
|
"on-demand": "On-demand(按需发现)",
|
|
6385
7995
|
disabled: "Disabled(禁用)",
|
|
6386
|
-
kind: "类型",
|
|
6387
|
-
class: "分类",
|
|
6388
7996
|
tool: "tool",
|
|
6389
7997
|
skill: "skill",
|
|
6390
7998
|
mandatory: "meta",
|
|
6391
|
-
rules: "规则",
|
|
6392
7999
|
toolsGroup: "Tools",
|
|
6393
8000
|
skillsGroup: "Skills",
|
|
6394
8001
|
builtInGroup: "系统内置",
|
|
6395
8002
|
globalSkills: "全局技能",
|
|
6396
8003
|
projectSkills: "项目技能",
|
|
8004
|
+
presetSkills: "预设技能",
|
|
6397
8005
|
emptyGlobalSkills: "暂无全局技能",
|
|
6398
8006
|
emptyProjectSkills: "暂无项目技能",
|
|
8007
|
+
filterByName: "按名字、分组或正则过滤…",
|
|
8008
|
+
filterHint: "忽略大小写。匹配工具/技能名、MCP server、技能来源标签与预设 id;也可以直接写正则,例如 cordis|ptc 或 /^mcp__dbx/。",
|
|
8009
|
+
filterNoMatch: "没有匹配的能力(清空过滤框可恢复完整列表)",
|
|
6399
8010
|
emptyTools: "暂无工具",
|
|
6400
8011
|
emptySkills: "暂无 Skill",
|
|
6401
8012
|
toolCount: "{count} 个工具",
|
|
@@ -6407,10 +8018,69 @@ async function apply(ctx) {
|
|
|
6407
8018
|
previewClose: "关闭",
|
|
6408
8019
|
detailNotFound: "未找到该工具的详情",
|
|
6409
8020
|
cycleOverridden: "分类未生效:{count} 个能力被更高优先级规则覆盖(如通配规则),可移除对应通配规则后重试",
|
|
6410
|
-
|
|
8021
|
+
refreshFailed: "刷新失败,请查看日志",
|
|
8022
|
+
retry: "重试",
|
|
8023
|
+
carrierFailureHint: "请求未到达服务端:浏览器与 dsh 之间的连接已断开(Failed to fetch 属传输层失败,服务端方法未执行)。若刚重启过 dsh web,请先硬刷新页面(Ctrl/Cmd+Shift+R)再重试。",
|
|
8024
|
+
registerCapability: "注册能力",
|
|
8025
|
+
editMcp: "编辑 MCP 服务器",
|
|
8026
|
+
editSkillNamed: "编辑 Skill 目录:{name}",
|
|
8027
|
+
edit: "编辑",
|
|
8028
|
+
save: "保存",
|
|
8029
|
+
remove: "移除",
|
|
8030
|
+
cancel: "取消",
|
|
8031
|
+
confirmRemove: "确认移除",
|
|
8032
|
+
confirmRemoveMcp: "移除 MCP 服务器「{name}」?其 patch 文件中的配置行(含 headers、环境变量)将被删除,mcp__{name}__* 工具随之消失。",
|
|
8033
|
+
confirmRemoveSkillLink: "移除 Skill「{name}」?仅删除 ~/.dsh/skills 下的软链,源目录不受影响。",
|
|
8034
|
+
confirmRemoveSkillDir: "移除 Skill「{name}」?该项位于 ~/.dsh/skills 下且非软链,将连同文件递归删除,且不可恢复。",
|
|
8035
|
+
confirmSaveAnyway: "仍然保存",
|
|
8036
|
+
saveConfirmRealDir: "「{name}」这个条目是真实目录、不是软链:保存会先把它连同文件递归删除,再改为指向新路径,且不可恢复。",
|
|
8037
|
+
register: "注册",
|
|
8038
|
+
notEditable: "该项未在注册表中(可能是项目技能或内置技能),无法编辑。",
|
|
8039
|
+
entryNotFound: "目标已不存在,可能已被移除。",
|
|
8040
|
+
mcpServers: "MCP 服务器",
|
|
8041
|
+
skillDirs: "Skill 目录",
|
|
8042
|
+
skillDirPath: "技能来源路径",
|
|
8043
|
+
skillRoot: "技能位置",
|
|
8044
|
+
skillRootUser: "全局技能(~/.dsh/skills)",
|
|
8045
|
+
skillRootProject: "项目技能(<项目根>/.dsh/skills)",
|
|
8046
|
+
skillRootHintUser: "所有会话都能看到该技能。",
|
|
8047
|
+
skillRootHintProject: "只对 cwd 落在该项目内的会话可见。",
|
|
8048
|
+
skillProjectPath: "目标项目路径",
|
|
8049
|
+
skillProjectPathHint: "项目内任意一个已存在的路径即可;项目根按 dsh 的规则确定(向上找最近的 .git),技能写进 <项目根>/.dsh/skills。",
|
|
8050
|
+
skillRegisteredAt: "已注册到 {path}",
|
|
8051
|
+
skillRepointed: "已改为指向 {dir}",
|
|
8052
|
+
skillAdopted: "已纳入管理:{path}",
|
|
8053
|
+
skillSource: "来源:{source}",
|
|
8054
|
+
skillSourceHint: "该技能由 dsh 从其它根发现,不在本插件管理的技能目录里,因此没有「编辑」。",
|
|
8055
|
+
skillFromPreset: "来自预设 {preset}",
|
|
8056
|
+
skillFromPresetHint: "该技能随 agent preset 分发,只对挂载了该预设的会话可见;不能纳入管理(那会把它软链进全局技能根,对所有会话生效)。",
|
|
8057
|
+
skillUnmanaged: "未纳入管理",
|
|
8058
|
+
sourceCustom: "自定义技能目录",
|
|
8059
|
+
sourceBundled: "随 dsh 预置",
|
|
8060
|
+
adoptSkill: "纳入管理",
|
|
8061
|
+
adoptSkillHint: "把它软链进 dsh 的技能根目录(默认 ~/.dsh/skills),内容不动;之后就能编辑与移除了。",
|
|
8062
|
+
adoptSkillTitle: "纳入管理「{name}」?",
|
|
8063
|
+
skillDirHint: "目录内需有 SKILL.md(按 dsh 的 skill 格式校验)。技能名以 SKILL.md 里声明的为准,目录名只决定注册后的软链名,两者不同也可以。",
|
|
8064
|
+
serverName: "serverName",
|
|
8065
|
+
serverNameImmutable: "serverName 不可修改:它构成工具名前缀 mcp__<serverName>__<tool>,并已被既有会话历史与权限规则引用;修改后这些记录将不再匹配。",
|
|
8066
|
+
transport: "传输方式",
|
|
8067
|
+
transportStdio: "stdio(本地子进程)",
|
|
8068
|
+
transportHttp: "streamable-http(HTTP MCP 端点)",
|
|
8069
|
+
transportHintStdio: "由客户端将该 MCP server 作为子进程启动,JSON-RPC 报文经标准输入 / 输出交换。",
|
|
8070
|
+
transportHintHttp: "该 MCP server 作为独立进程运行,可服务多个客户端连接;客户端经单个支持 POST / GET 的 HTTP 端点(MCP endpoint)与其通信。",
|
|
8071
|
+
command: "命令",
|
|
8072
|
+
args: "参数(空格分隔)",
|
|
8073
|
+
cwd: "工作目录",
|
|
8074
|
+
env: "环境变量",
|
|
8075
|
+
url: "URL",
|
|
8076
|
+
headers: "请求头",
|
|
8077
|
+
headersHint: "每行一个 Header,格式 Key: Value;Bearer token 等认证凭据在此填写。",
|
|
8078
|
+
timeout: "超时(秒)",
|
|
8079
|
+
timeoutInvalid: "超时须为数字,单位为秒。",
|
|
8080
|
+
viewCatalog: "策略与目录",
|
|
6411
8081
|
catalogPolicy: "三档策略配置",
|
|
6412
8082
|
catalogOnDemand: "按需能力目录",
|
|
6413
|
-
catalogPolicyNote: "
|
|
8083
|
+
catalogPolicyNote: "实时生成;点选改动会在停手后自动写回 cordis.patch.yml(未列出规则的能力默认常驻)",
|
|
6414
8084
|
catalogDisabled: "按需能力目录未启用(catalogFile 为空)",
|
|
6415
8085
|
catalogUnreadable: "按需能力目录文件读取失败"
|
|
6416
8086
|
};
|
|
@@ -6421,19 +8091,20 @@ async function apply(ctx) {
|
|
|
6421
8091
|
resident: "Resident",
|
|
6422
8092
|
"on-demand": "On-demand",
|
|
6423
8093
|
disabled: "Disabled",
|
|
6424
|
-
kind: "kind",
|
|
6425
|
-
class: "class",
|
|
6426
8094
|
tool: "tool",
|
|
6427
8095
|
skill: "skill",
|
|
6428
8096
|
mandatory: "meta",
|
|
6429
|
-
rules: "rules",
|
|
6430
8097
|
toolsGroup: "Tools",
|
|
6431
8098
|
skillsGroup: "Skills",
|
|
6432
8099
|
builtInGroup: "System built-in",
|
|
6433
8100
|
globalSkills: "Global skills",
|
|
6434
8101
|
projectSkills: "Project skills",
|
|
8102
|
+
presetSkills: "Preset skills",
|
|
6435
8103
|
emptyGlobalSkills: "No global skills",
|
|
6436
8104
|
emptyProjectSkills: "No project skills",
|
|
8105
|
+
filterByName: "Filter by name, group, or regex…",
|
|
8106
|
+
filterHint: "Case-insensitive. Matches tool/skill names, MCP servers, skill source labels and preset ids; a regular expression works too, e.g. cordis|ptc or /^mcp__dbx/.",
|
|
8107
|
+
filterNoMatch: "No capability matches (clear the filter to see the full list)",
|
|
6437
8108
|
emptyTools: "No tools",
|
|
6438
8109
|
emptySkills: "No skills",
|
|
6439
8110
|
toolCount: "{count} tools",
|
|
@@ -6445,10 +8116,69 @@ async function apply(ctx) {
|
|
|
6445
8116
|
previewClose: "Close",
|
|
6446
8117
|
detailNotFound: "Tool detail not found",
|
|
6447
8118
|
cycleOverridden: "Classification not applied: {count} capability(ies) overridden by a higher-priority rule (e.g. a wildcard). Remove the matching wildcard rule and retry.",
|
|
6448
|
-
|
|
8119
|
+
refreshFailed: "Refresh failed; see the log",
|
|
8120
|
+
retry: "Retry",
|
|
8121
|
+
carrierFailureHint: "The request never reached the server: the browser connection to dsh is broken (\"Failed to fetch\" is a transport failure, so no server method ran). If dsh web was just restarted, hard-refresh the page (Ctrl/Cmd+Shift+R) and retry.",
|
|
8122
|
+
registerCapability: "Register capability",
|
|
8123
|
+
editMcp: "Edit MCP server",
|
|
8124
|
+
editSkillNamed: "Edit skill directory: {name}",
|
|
8125
|
+
edit: "Edit",
|
|
8126
|
+
save: "Save",
|
|
8127
|
+
remove: "Remove",
|
|
8128
|
+
cancel: "Cancel",
|
|
8129
|
+
confirmRemove: "Confirm removal",
|
|
8130
|
+
confirmRemoveMcp: "Remove the MCP server \"{name}\"? Its config row in the patch file (headers and environment included) is deleted, and the mcp__{name}__* tools disappear with it.",
|
|
8131
|
+
confirmRemoveSkillLink: "Unregister the skill \"{name}\"? Only the symlink under ~/.dsh/skills is removed; the source directory is left untouched.",
|
|
8132
|
+
confirmRemoveSkillDir: "Remove the skill \"{name}\"? It sits under ~/.dsh/skills and is not a symlink, so it will be deleted recursively with all of its files. This cannot be undone.",
|
|
8133
|
+
confirmSaveAnyway: "Save anyway",
|
|
8134
|
+
saveConfirmRealDir: "The entry \"{name}\" is a real directory, not a symlink: saving deletes it and its files recursively before repointing it, and that cannot be undone.",
|
|
8135
|
+
register: "Register",
|
|
8136
|
+
notEditable: "This entry is not in the registry (a project or bundled skill), so it cannot be edited.",
|
|
8137
|
+
entryNotFound: "The target no longer exists; it may have been removed.",
|
|
8138
|
+
mcpServers: "MCP servers",
|
|
8139
|
+
skillDirs: "Skill directories",
|
|
8140
|
+
skillDirPath: "Skill source path",
|
|
8141
|
+
skillRoot: "Skill location",
|
|
8142
|
+
skillRootUser: "Global (~/.dsh/skills)",
|
|
8143
|
+
skillRootProject: "Project (<projectRoot>/.dsh/skills)",
|
|
8144
|
+
skillRootHintUser: "Visible to every session.",
|
|
8145
|
+
skillRootHintProject: "Visible only to sessions whose cwd sits inside that project.",
|
|
8146
|
+
skillProjectPath: "Target project path",
|
|
8147
|
+
skillProjectPathHint: "Any existing path inside the project; the project root is derived the way dsh derives it (nearest .git above it) and the skill is written under <projectRoot>/.dsh/skills.",
|
|
8148
|
+
skillRegisteredAt: "Registered at {path}",
|
|
8149
|
+
skillRepointed: "Repointed to {dir}",
|
|
8150
|
+
skillAdopted: "Adopted: {path}",
|
|
8151
|
+
skillSource: "Source: {source}",
|
|
8152
|
+
skillSourceHint: "dsh found this skill in another root, not in a skill directory this plugin manages, so there is no Edit button.",
|
|
8153
|
+
skillFromPreset: "From preset {preset}",
|
|
8154
|
+
skillFromPresetHint: "This skill ships with an agent preset and is visible only to sessions that mount it. It cannot be adopted (adopting would link it into the global skill root, making it apply to every session).",
|
|
8155
|
+
skillUnmanaged: "Not managed here",
|
|
8156
|
+
sourceCustom: "custom skill dirs",
|
|
8157
|
+
sourceBundled: "bundled with dsh",
|
|
8158
|
+
adoptSkill: "Adopt",
|
|
8159
|
+
adoptSkillHint: "Link it into dsh's skill root (default ~/.dsh/skills); the content is untouched, and it becomes editable and removable.",
|
|
8160
|
+
adoptSkillTitle: "Adopt \"{name}\"?",
|
|
8161
|
+
skillDirHint: "The directory must hold a SKILL.md in dsh's skill format. The skill name is the one declared in SKILL.md; the directory name only names the link this registers, so the two may differ.",
|
|
8162
|
+
serverName: "serverName",
|
|
8163
|
+
serverNameImmutable: "serverName is immutable: it forms the tool name prefix mcp__<serverName>__<tool> and is referenced by existing session history and permission rules, which stop matching once it changes.",
|
|
8164
|
+
transport: "Transport",
|
|
8165
|
+
transportStdio: "stdio (local subprocess)",
|
|
8166
|
+
transportHttp: "streamable-http (HTTP MCP endpoint)",
|
|
8167
|
+
transportHintStdio: "The client launches the MCP server as a subprocess and exchanges JSON-RPC messages over its standard input and output.",
|
|
8168
|
+
transportHintHttp: "The server runs as an independent process that can handle multiple client connections; the client reaches it over a single HTTP endpoint (the MCP endpoint) supporting POST and GET.",
|
|
8169
|
+
command: "Command",
|
|
8170
|
+
args: "Args (space separated)",
|
|
8171
|
+
cwd: "Working directory",
|
|
8172
|
+
env: "Environment",
|
|
8173
|
+
url: "URL",
|
|
8174
|
+
headers: "Headers",
|
|
8175
|
+
headersHint: "One header per line as Key: Value; credentials such as a Bearer token go here.",
|
|
8176
|
+
timeout: "Timeout (seconds)",
|
|
8177
|
+
timeoutInvalid: "Timeout must be a number of seconds.",
|
|
8178
|
+
viewCatalog: "Policy & catalog",
|
|
6449
8179
|
catalogPolicy: "Policy (effective)",
|
|
6450
8180
|
catalogOnDemand: "On-demand catalog",
|
|
6451
|
-
catalogPolicyNote: "Generated live from the effective policy
|
|
8181
|
+
catalogPolicyNote: "Generated live from the effective policy; clicked changes are written back to cordis.patch.yml once you stop. Capabilities without a rule default to Resident.",
|
|
6452
8182
|
catalogDisabled: "On-demand catalog emission is disabled (catalogFile is empty).",
|
|
6453
8183
|
catalogUnreadable: "Failed to read the on-demand catalog file."
|
|
6454
8184
|
};
|
|
@@ -6473,18 +8203,45 @@ async function apply(ctx) {
|
|
|
6473
8203
|
return;
|
|
6474
8204
|
}
|
|
6475
8205
|
};
|
|
8206
|
+
/**
|
|
8207
|
+
* The two signals that say "the host catalog may have moved, and this page did
|
|
8208
|
+
* not ask": a settings document edit, and a carrier reconnect.
|
|
8209
|
+
*
|
|
8210
|
+
* `settings/document-updated` is one of the events dsh forwards to browsers
|
|
8211
|
+
* (so it arrives over the same validated channel as the remote calls) and it
|
|
8212
|
+
* fires for external edits too — composition files are edited outside the
|
|
8213
|
+
* browser, and nothing else on the wire announces that. `connection/reset` is
|
|
8214
|
+
* the client's own reconnect signal rather than a forwarded one, which is why
|
|
8215
|
+
* it is a plain local `ctx.on`; dsh's own settings sections listen to exactly
|
|
8216
|
+
* this pair, including the first connection, which closes the window where a
|
|
8217
|
+
* change lands between the initial read and the subscription.
|
|
8218
|
+
*
|
|
8219
|
+
* Neither subscription is fatal: the section falls back to its version poll.
|
|
8220
|
+
*/
|
|
8221
|
+
const subscribeSignals = (listener) => {
|
|
8222
|
+
const disposers = [];
|
|
8223
|
+
const forwarded = ctx.remote;
|
|
8224
|
+
try {
|
|
8225
|
+
if (typeof forwarded.$on !== "function") throw new Error("ctx.remote.$on is unavailable");
|
|
8226
|
+
disposers.push(forwarded.$on("settings/document-updated", () => listener()));
|
|
8227
|
+
} catch (error) {
|
|
8228
|
+
console.error("[capability-menu] settings/document-updated subscription failed:", error);
|
|
8229
|
+
}
|
|
8230
|
+
try {
|
|
8231
|
+
disposers.push(ctx.on("connection/reset", () => listener()));
|
|
8232
|
+
} catch (error) {
|
|
8233
|
+
console.error("[capability-menu] connection/reset subscription failed:", error);
|
|
8234
|
+
}
|
|
8235
|
+
return () => {
|
|
8236
|
+
for (const dispose of disposers) dispose();
|
|
8237
|
+
};
|
|
8238
|
+
};
|
|
6476
8239
|
const injected = () => {
|
|
6477
|
-
const namespace = remote();
|
|
6478
|
-
const remoteKeys = namespace == null ? void 0 : Object.keys(namespace).filter((k) => [
|
|
6479
|
-
"getConfig",
|
|
6480
|
-
"updateConfig",
|
|
6481
|
-
"classifyAll"
|
|
6482
|
-
].includes(k)).join(",");
|
|
6483
8240
|
return {
|
|
6484
|
-
remote:
|
|
8241
|
+
remote: remote(),
|
|
6485
8242
|
t,
|
|
6486
|
-
|
|
6487
|
-
...
|
|
8243
|
+
subscribeSignals,
|
|
8244
|
+
...mountError !== void 0 ? { mountError } : {}
|
|
6488
8245
|
};
|
|
6489
8246
|
};
|
|
6490
8247
|
ctx.slots.inject("settings.section", () => ctx.slots.register({
|