@agent-stash/dsh-skill-scout 0.0.0-stage → 0.1.0

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/dist/client.js ADDED
@@ -0,0 +1,3325 @@
1
+ window.__ModuleLoader__.load({
2
+ id: "@agent-stash/dsh-skill-scout",
3
+ factory: (require) => {
4
+ var module = { exports: {} };
5
+ var exports = module.exports;
6
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
+ let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
8
+ let react = require("react");
9
+ let react_dom = require("react-dom");
10
+ let react_jsx_runtime = require("react/jsx-runtime");
11
+ const DEFAULT_CONFIG = {
12
+ lang: void 0,
13
+ message: void 0,
14
+ abortEarly: void 0,
15
+ abortPipeEarly: void 0
16
+ };
17
+ /**
18
+ * Returns the global configuration.
19
+ *
20
+ * @param config The config to merge.
21
+ *
22
+ * @returns The configuration.
23
+ */
24
+ /* @__NO_SIDE_EFFECTS__ */
25
+ function getGlobalConfig(config$1) {
26
+ if (!config$1 && true) return DEFAULT_CONFIG;
27
+ return {
28
+ lang: config$1?.lang ?? void 0,
29
+ message: config$1?.message,
30
+ abortEarly: config$1?.abortEarly ?? void 0,
31
+ abortPipeEarly: config$1?.abortPipeEarly ?? void 0
32
+ };
33
+ }
34
+ /**
35
+ * Stringifies an unknown input to a literal or type string.
36
+ *
37
+ * @param input The unknown input.
38
+ *
39
+ * @returns A literal or type string.
40
+ *
41
+ * @internal
42
+ */
43
+ /* @__NO_SIDE_EFFECTS__ */
44
+ function _stringify(input) {
45
+ const type = typeof input;
46
+ if (type === "string") return `"${input}"`;
47
+ if (type === "number" || type === "bigint" || type === "boolean") return `${input}`;
48
+ if (type === "object" || type === "function") return (input && Object.getPrototypeOf(input)?.constructor?.name) ?? "null";
49
+ return type;
50
+ }
51
+ /**
52
+ * Adds an issue to the dataset.
53
+ *
54
+ * @param context The issue context.
55
+ * @param label The issue label.
56
+ * @param dataset The input dataset.
57
+ * @param config The configuration.
58
+ * @param other The optional props.
59
+ *
60
+ * @internal
61
+ */
62
+ function _addIssue(context, label, dataset, config$1, other) {
63
+ const input = other && "input" in other ? other.input : dataset.value;
64
+ const expected = other?.expected ?? context.expects ?? null;
65
+ const received = other?.received ?? /* @__PURE__ */ _stringify(input);
66
+ const issue = {
67
+ kind: context.kind,
68
+ type: context.type,
69
+ input,
70
+ expected,
71
+ received,
72
+ message: `Invalid ${label}: ${expected ? `Expected ${expected} but r` : "R"}eceived ${received}`,
73
+ requirement: context.requirement,
74
+ path: other?.path,
75
+ issues: other?.issues,
76
+ lang: config$1.lang,
77
+ abortEarly: config$1.abortEarly,
78
+ abortPipeEarly: config$1.abortPipeEarly
79
+ };
80
+ const isSchema = context.kind === "schema";
81
+ const message$1 = other?.message ?? context.message ?? (context.reference, issue.lang, void 0) ?? (isSchema ? (issue.lang, void 0) : null) ?? config$1.message ?? (issue.lang, void 0);
82
+ if (message$1 !== void 0) issue.message = typeof message$1 === "function" ? message$1(issue) : message$1;
83
+ if (isSchema) dataset.typed = false;
84
+ if (dataset.issues) dataset.issues.push(issue);
85
+ else dataset.issues = [issue];
86
+ }
87
+ /**
88
+ * Compares two values using the SameValueZero algorithm, which treats `NaN`
89
+ * as equal to itself unlike `===`.
90
+ *
91
+ * @param value1 The first value.
92
+ * @param value2 The second value.
93
+ *
94
+ * @returns Whether the values are equal.
95
+ *
96
+ * @internal
97
+ */
98
+ /* @__NO_SIDE_EFFECTS__ */
99
+ function _isSameValueZero(value1, value2) {
100
+ return value1 === value2 || Number.isNaN(value1) && Number.isNaN(value2);
101
+ }
102
+ /**
103
+ * Joins multiple `expects` values with the given separator.
104
+ *
105
+ * @param values The `expects` values.
106
+ * @param separator The separator.
107
+ *
108
+ * @returns The joined `expects` property.
109
+ *
110
+ * @internal
111
+ */
112
+ /* @__NO_SIDE_EFFECTS__ */
113
+ function _joinExpects(values$1, separator) {
114
+ const list = [...new Set(values$1)];
115
+ if (list.length > 1) return `(${list.join(` ${separator} `)})`;
116
+ return list[0] ?? "never";
117
+ }
118
+ /**
119
+ * Eagerly creates and attaches the Standard Schema properties of a schema.
120
+ *
121
+ * Hint: The contextual `this` type includes the standard properties that are
122
+ * attached before the schema is returned.
123
+ *
124
+ * @param schema The schema to attach standard properties to.
125
+ *
126
+ * @returns The schema with standard properties attached.
127
+ *
128
+ * @internal
129
+ */
130
+ function _standardSchema(schema) {
131
+ schema["~standard"] = {
132
+ version: 1,
133
+ vendor: "valibot",
134
+ validate: (value$1) => schema["~run"]({ value: value$1 }, /* @__PURE__ */ getGlobalConfig())
135
+ };
136
+ return schema;
137
+ }
138
+ /**
139
+ * A Valibot error with useful information.
140
+ */
141
+ var ValiError = class extends Error {
142
+ /**
143
+ * Creates a Valibot error with useful information.
144
+ *
145
+ * @param issues The error issues.
146
+ */
147
+ constructor(issues) {
148
+ super(issues[0].message);
149
+ this.name = "ValiError";
150
+ this.issues = issues;
151
+ }
152
+ };
153
+ /* @__NO_SIDE_EFFECTS__ */
154
+ function maxLength(requirement, message$1) {
155
+ return {
156
+ kind: "validation",
157
+ type: "max_length",
158
+ reference: maxLength,
159
+ async: false,
160
+ expects: `<=${requirement}`,
161
+ requirement,
162
+ message: message$1,
163
+ "~run"(dataset, config$1) {
164
+ if (dataset.typed && dataset.value.length > this.requirement) _addIssue(this, "length", dataset, config$1, { received: `${dataset.value.length}` });
165
+ return dataset;
166
+ }
167
+ };
168
+ }
169
+ /* @__NO_SIDE_EFFECTS__ */
170
+ function minLength(requirement, message$1) {
171
+ return {
172
+ kind: "validation",
173
+ type: "min_length",
174
+ reference: minLength,
175
+ async: false,
176
+ expects: `>=${requirement}`,
177
+ requirement,
178
+ message: message$1,
179
+ "~run"(dataset, config$1) {
180
+ if (dataset.typed && dataset.value.length < this.requirement) _addIssue(this, "length", dataset, config$1, { received: `${dataset.value.length}` });
181
+ return dataset;
182
+ }
183
+ };
184
+ }
185
+ /* @__NO_SIDE_EFFECTS__ */
186
+ function parseJson(config$1, message$1) {
187
+ return {
188
+ kind: "transformation",
189
+ type: "parse_json",
190
+ reference: parseJson,
191
+ config: config$1,
192
+ message: message$1,
193
+ async: false,
194
+ "~run"(dataset, config$2) {
195
+ try {
196
+ dataset.value = JSON.parse(dataset.value, this.config?.reviver);
197
+ } catch (error) {
198
+ if (error instanceof Error) {
199
+ _addIssue(this, "JSON", dataset, config$2, { received: `"${error.message}"` });
200
+ dataset.typed = false;
201
+ } else throw error;
202
+ }
203
+ return dataset;
204
+ }
205
+ };
206
+ }
207
+ /* @__NO_SIDE_EFFECTS__ */
208
+ function regex(requirement, message$1) {
209
+ return {
210
+ kind: "validation",
211
+ type: "regex",
212
+ reference: regex,
213
+ async: false,
214
+ expects: `${requirement}`,
215
+ requirement,
216
+ message: message$1,
217
+ "~run"(dataset, config$1) {
218
+ if (dataset.typed && !this.requirement.test(dataset.value)) _addIssue(this, "format", dataset, config$1);
219
+ return dataset;
220
+ }
221
+ };
222
+ }
223
+ /**
224
+ * Creates a trim transformation action.
225
+ *
226
+ * @returns A trim action.
227
+ */
228
+ /* @__NO_SIDE_EFFECTS__ */
229
+ function trim() {
230
+ return {
231
+ kind: "transformation",
232
+ type: "trim",
233
+ reference: trim,
234
+ async: false,
235
+ "~run"(dataset) {
236
+ dataset.value = dataset.value.trim();
237
+ return dataset;
238
+ }
239
+ };
240
+ }
241
+ const ABORT_EARLY_CONFIG = { abortEarly: true };
242
+ /**
243
+ * Returns the fallback value of the schema.
244
+ *
245
+ * @param schema The schema to get it from.
246
+ * @param dataset The output dataset if available.
247
+ * @param config The config if available.
248
+ *
249
+ * @returns The fallback value.
250
+ */
251
+ /* @__NO_SIDE_EFFECTS__ */
252
+ function getFallback(schema, dataset, config$1) {
253
+ return typeof schema.fallback === "function" ? schema.fallback(dataset, config$1) : schema.fallback;
254
+ }
255
+ /**
256
+ * Returns a fallback value as output if the input does not match the schema.
257
+ *
258
+ * @param schema The schema to catch.
259
+ * @param fallback The fallback value.
260
+ *
261
+ * @returns The passed schema.
262
+ */
263
+ /* @__NO_SIDE_EFFECTS__ */
264
+ function fallback(schema, fallback$1) {
265
+ return _standardSchema({
266
+ ...schema,
267
+ fallback: fallback$1,
268
+ "~run"(dataset, config$1) {
269
+ const outputDataset = schema["~run"](dataset, config$1);
270
+ return outputDataset.issues ? {
271
+ typed: true,
272
+ value: /* @__PURE__ */ getFallback(this, outputDataset, config$1)
273
+ } : outputDataset;
274
+ }
275
+ });
276
+ }
277
+ /**
278
+ * Returns the default value of the schema.
279
+ *
280
+ * @param schema The schema to get it from.
281
+ * @param dataset The input dataset if available.
282
+ * @param config The config if available.
283
+ *
284
+ * @returns The default value.
285
+ */
286
+ /* @__NO_SIDE_EFFECTS__ */
287
+ function getDefault(schema, dataset, config$1) {
288
+ return typeof schema.default === "function" ? schema.default(dataset, config$1) : schema.default;
289
+ }
290
+ /* @__NO_SIDE_EFFECTS__ */
291
+ function array(item, message$1) {
292
+ return _standardSchema({
293
+ kind: "schema",
294
+ type: "array",
295
+ reference: array,
296
+ expects: "Array",
297
+ async: false,
298
+ item,
299
+ message: message$1,
300
+ "~run"(dataset, config$1) {
301
+ const input = dataset.value;
302
+ if (Array.isArray(input)) {
303
+ dataset.typed = true;
304
+ dataset.value = [];
305
+ for (let key = 0; key < input.length; key++) {
306
+ const value$1 = input[key];
307
+ const itemDataset = this.item["~run"]({ value: value$1 }, config$1);
308
+ if (itemDataset.issues) {
309
+ const pathItem = {
310
+ type: "array",
311
+ origin: "value",
312
+ input,
313
+ key,
314
+ value: value$1
315
+ };
316
+ for (const issue of itemDataset.issues) {
317
+ if (issue.path) issue.path.unshift(pathItem);
318
+ else issue.path = [pathItem];
319
+ dataset.issues?.push(issue);
320
+ }
321
+ if (!dataset.issues) dataset.issues = itemDataset.issues;
322
+ if (config$1.abortEarly) {
323
+ dataset.typed = false;
324
+ break;
325
+ }
326
+ }
327
+ if (!itemDataset.typed) dataset.typed = false;
328
+ dataset.value.push(itemDataset.value);
329
+ }
330
+ } else _addIssue(this, "type", dataset, config$1);
331
+ return dataset;
332
+ }
333
+ });
334
+ }
335
+ /* @__NO_SIDE_EFFECTS__ */
336
+ function boolean(message$1) {
337
+ return _standardSchema({
338
+ kind: "schema",
339
+ type: "boolean",
340
+ reference: boolean,
341
+ expects: "boolean",
342
+ async: false,
343
+ message: message$1,
344
+ "~run"(dataset, config$1) {
345
+ if (typeof dataset.value === "boolean") dataset.typed = true;
346
+ else _addIssue(this, "type", dataset, config$1);
347
+ return dataset;
348
+ }
349
+ });
350
+ }
351
+ /* @__NO_SIDE_EFFECTS__ */
352
+ function literal(literal_, message$1) {
353
+ return _standardSchema({
354
+ kind: "schema",
355
+ type: "literal",
356
+ reference: literal,
357
+ expects: /* @__PURE__ */ _stringify(literal_),
358
+ async: false,
359
+ literal: literal_,
360
+ message: message$1,
361
+ "~run"(dataset, config$1) {
362
+ if (/* @__PURE__ */ _isSameValueZero(dataset.value, this.literal)) dataset.typed = true;
363
+ else _addIssue(this, "type", dataset, config$1);
364
+ return dataset;
365
+ }
366
+ });
367
+ }
368
+ /* @__NO_SIDE_EFFECTS__ */
369
+ function nullable(wrapped, default_) {
370
+ return _standardSchema({
371
+ kind: "schema",
372
+ type: "nullable",
373
+ reference: nullable,
374
+ expects: `(${wrapped.expects} | null)`,
375
+ async: false,
376
+ wrapped,
377
+ default: default_,
378
+ "~run"(dataset, config$1) {
379
+ if (dataset.value === null) {
380
+ if (this.default !== void 0) dataset.value = /* @__PURE__ */ getDefault(this, dataset, config$1);
381
+ if (dataset.value === null) {
382
+ dataset.typed = true;
383
+ return dataset;
384
+ }
385
+ }
386
+ return this.wrapped["~run"](dataset, config$1);
387
+ }
388
+ });
389
+ }
390
+ /* @__NO_SIDE_EFFECTS__ */
391
+ function number(message$1) {
392
+ return _standardSchema({
393
+ kind: "schema",
394
+ type: "number",
395
+ reference: number,
396
+ expects: "number",
397
+ async: false,
398
+ message: message$1,
399
+ "~run"(dataset, config$1) {
400
+ if (typeof dataset.value === "number" && !isNaN(dataset.value)) dataset.typed = true;
401
+ else _addIssue(this, "type", dataset, config$1);
402
+ return dataset;
403
+ }
404
+ });
405
+ }
406
+ /* @__NO_SIDE_EFFECTS__ */
407
+ function object(entries$1, message$1) {
408
+ return _standardSchema({
409
+ kind: "schema",
410
+ type: "object",
411
+ reference: object,
412
+ expects: "Object",
413
+ async: false,
414
+ entries: entries$1,
415
+ message: message$1,
416
+ "~run"(dataset, config$1) {
417
+ const input = dataset.value;
418
+ if (input && typeof input === "object") {
419
+ dataset.typed = true;
420
+ dataset.value = {};
421
+ for (const key in this.entries) {
422
+ const valueSchema = this.entries[key];
423
+ if (key in input || (valueSchema.type === "exact_optional" || valueSchema.type === "optional" || valueSchema.type === "nullish") && valueSchema.default !== void 0) {
424
+ const value$1 = key in input ? input[key] : /* @__PURE__ */ getDefault(valueSchema);
425
+ const valueDataset = valueSchema["~run"]({ value: value$1 }, config$1);
426
+ if (valueDataset.issues) {
427
+ const pathItem = {
428
+ type: "object",
429
+ origin: "value",
430
+ input,
431
+ key,
432
+ value: value$1
433
+ };
434
+ for (const issue of valueDataset.issues) {
435
+ if (issue.path) issue.path.unshift(pathItem);
436
+ else issue.path = [pathItem];
437
+ dataset.issues?.push(issue);
438
+ }
439
+ if (!dataset.issues) dataset.issues = valueDataset.issues;
440
+ if (config$1.abortEarly) {
441
+ dataset.typed = false;
442
+ break;
443
+ }
444
+ }
445
+ if (!valueDataset.typed) dataset.typed = false;
446
+ dataset.value[key] = valueDataset.value;
447
+ } else if (valueSchema.fallback !== void 0) dataset.value[key] = /* @__PURE__ */ getFallback(valueSchema);
448
+ else if (valueSchema.type !== "exact_optional" && valueSchema.type !== "optional" && valueSchema.type !== "nullish") {
449
+ _addIssue(this, "key", dataset, config$1, {
450
+ input: void 0,
451
+ expected: `"${key}"`,
452
+ path: [{
453
+ type: "object",
454
+ origin: "key",
455
+ input,
456
+ key,
457
+ value: input[key]
458
+ }]
459
+ });
460
+ if (config$1.abortEarly) break;
461
+ }
462
+ }
463
+ } else _addIssue(this, "type", dataset, config$1);
464
+ return dataset;
465
+ }
466
+ });
467
+ }
468
+ /* @__NO_SIDE_EFFECTS__ */
469
+ function optional(wrapped, default_) {
470
+ return _standardSchema({
471
+ kind: "schema",
472
+ type: "optional",
473
+ reference: optional,
474
+ expects: `(${wrapped.expects} | undefined)`,
475
+ async: false,
476
+ wrapped,
477
+ default: default_,
478
+ "~run"(dataset, config$1) {
479
+ if (dataset.value === void 0) {
480
+ if (this.default !== void 0) dataset.value = /* @__PURE__ */ getDefault(this, dataset, config$1);
481
+ if (dataset.value === void 0) {
482
+ dataset.typed = true;
483
+ return dataset;
484
+ }
485
+ }
486
+ return this.wrapped["~run"](dataset, config$1);
487
+ }
488
+ });
489
+ }
490
+ /* @__NO_SIDE_EFFECTS__ */
491
+ function picklist(options, message$1) {
492
+ return _standardSchema({
493
+ kind: "schema",
494
+ type: "picklist",
495
+ reference: picklist,
496
+ expects: /* @__PURE__ */ _joinExpects(options.map(_stringify), "|"),
497
+ async: false,
498
+ options,
499
+ message: message$1,
500
+ "~run"(dataset, config$1) {
501
+ if (this.options.includes(dataset.value)) dataset.typed = true;
502
+ else _addIssue(this, "type", dataset, config$1);
503
+ return dataset;
504
+ }
505
+ });
506
+ }
507
+ /* @__NO_SIDE_EFFECTS__ */
508
+ function string(message$1) {
509
+ return _standardSchema({
510
+ kind: "schema",
511
+ type: "string",
512
+ reference: string,
513
+ expects: "string",
514
+ async: false,
515
+ message: message$1,
516
+ "~run"(dataset, config$1) {
517
+ if (typeof dataset.value === "string") dataset.typed = true;
518
+ else _addIssue(this, "type", dataset, config$1);
519
+ return dataset;
520
+ }
521
+ });
522
+ }
523
+ /* @__NO_SIDE_EFFECTS__ */
524
+ function variant(key, options, message$1) {
525
+ return _standardSchema({
526
+ kind: "schema",
527
+ type: "variant",
528
+ reference: variant,
529
+ expects: "Object",
530
+ async: false,
531
+ key,
532
+ options,
533
+ message: message$1,
534
+ "~run"(dataset, config$1) {
535
+ const input = dataset.value;
536
+ if (input && typeof input === "object") {
537
+ let outputDataset;
538
+ let maxDiscriminatorPriority = 0;
539
+ let invalidDiscriminatorKey = this.key;
540
+ let expectedDiscriminators = [];
541
+ const parseOptions = (variant$1, allKeys) => {
542
+ for (const schema of variant$1.options) {
543
+ if (schema.type === "variant") parseOptions(schema, new Set(allKeys).add(schema.key));
544
+ else {
545
+ let keysAreValid = true;
546
+ let currentPriority = 0;
547
+ for (const currentKey of allKeys) {
548
+ const discriminatorSchema = schema.entries[currentKey];
549
+ if (currentKey in input ? discriminatorSchema["~run"]({
550
+ typed: false,
551
+ value: input[currentKey]
552
+ }, ABORT_EARLY_CONFIG).issues : discriminatorSchema.type !== "exact_optional" && discriminatorSchema.type !== "optional" && discriminatorSchema.type !== "nullish") {
553
+ keysAreValid = false;
554
+ if (invalidDiscriminatorKey !== currentKey && (maxDiscriminatorPriority < currentPriority || maxDiscriminatorPriority === currentPriority && currentKey in input && !(invalidDiscriminatorKey in input))) {
555
+ maxDiscriminatorPriority = currentPriority;
556
+ invalidDiscriminatorKey = currentKey;
557
+ expectedDiscriminators = [];
558
+ }
559
+ if (invalidDiscriminatorKey === currentKey) expectedDiscriminators.push(schema.entries[currentKey].expects);
560
+ break;
561
+ }
562
+ currentPriority++;
563
+ }
564
+ if (keysAreValid) {
565
+ const optionDataset = schema["~run"]({ value: input }, config$1);
566
+ if (!outputDataset || !outputDataset.typed && optionDataset.typed) outputDataset = optionDataset;
567
+ }
568
+ }
569
+ if (outputDataset && !outputDataset.issues) break;
570
+ }
571
+ };
572
+ parseOptions(this, /* @__PURE__ */ new Set([this.key]));
573
+ if (outputDataset) return outputDataset;
574
+ _addIssue(this, "type", dataset, config$1, {
575
+ input: input[invalidDiscriminatorKey],
576
+ expected: /* @__PURE__ */ _joinExpects(expectedDiscriminators, "|"),
577
+ path: [{
578
+ type: "object",
579
+ origin: "value",
580
+ input,
581
+ key: invalidDiscriminatorKey,
582
+ value: input[invalidDiscriminatorKey]
583
+ }]
584
+ });
585
+ } else _addIssue(this, "type", dataset, config$1);
586
+ return dataset;
587
+ }
588
+ });
589
+ }
590
+ /**
591
+ * Parses an unknown input based on a schema.
592
+ *
593
+ * @param schema The schema to be used.
594
+ * @param input The input to be parsed.
595
+ * @param config The parse configuration.
596
+ *
597
+ * @returns The parsed input.
598
+ */
599
+ function parse$1(schema, input, config$1) {
600
+ const dataset = schema["~run"]({ value: input }, /* @__PURE__ */ getGlobalConfig(config$1));
601
+ if (dataset.issues) throw new ValiError(dataset.issues);
602
+ return dataset.value;
603
+ }
604
+ /* @__NO_SIDE_EFFECTS__ */
605
+ function pipe(...pipe$1) {
606
+ return _standardSchema({
607
+ ...pipe$1[0],
608
+ pipe: pipe$1,
609
+ "~run"(dataset, config$1) {
610
+ for (const item of pipe$1) if (item.kind !== "metadata") {
611
+ if (dataset.issues && (item.kind === "schema" || item.kind === "transformation")) {
612
+ dataset.typed = false;
613
+ break;
614
+ }
615
+ if (!dataset.issues || !config$1.abortEarly && !config$1.abortPipeEarly) dataset = item["~run"](dataset, config$1);
616
+ }
617
+ return dataset;
618
+ }
619
+ });
620
+ }
621
+ /**
622
+ * Parses an unknown input based on a schema.
623
+ *
624
+ * @param schema The schema to be used.
625
+ * @param input The input to be parsed.
626
+ * @param config The parse configuration.
627
+ *
628
+ * @returns The parse result.
629
+ */
630
+ /* @__NO_SIDE_EFFECTS__ */
631
+ function safeParse(schema, input, config$1) {
632
+ const dataset = schema["~run"]({ value: input }, /* @__PURE__ */ getGlobalConfig(config$1));
633
+ return {
634
+ typed: dataset.typed,
635
+ success: !dataset.issues,
636
+ output: dataset.value,
637
+ issues: dataset.issues
638
+ };
639
+ }
640
+ //#endregion
641
+ //#region src/shared.ts
642
+ /**
643
+ * Host 与浏览器两半共用的路由路径、视图 schema 与请求 schema。
644
+ * 视图里的文本都已在 Host 侧脱敏、裁剪;浏览器收到响应后按同一份 schema 校验。
645
+ */
646
+ const ROUTE_BASE = "/agent-stash-skill-scout";
647
+ const INBOX_PATH = `${ROUTE_BASE}/inbox`;
648
+ const DETAIL_PATH = `${ROUTE_BASE}/detail`;
649
+ const ACTION_PATH = `${ROUTE_BASE}/action`;
650
+ /** 插件页里本插件配置页的键:`<包名>#<条目 id>`。 */
651
+ const PACKAGE_NAME = "@agent-stash/dsh-skill-scout";
652
+ const ROW_ID = "agent-stash-skill-scout";
653
+ const Nullable = (schema) => /* @__PURE__ */ nullable(schema);
654
+ const SignalKindViewSchema = /* @__PURE__ */ picklist([
655
+ "repeated",
656
+ "recovery",
657
+ "complex"
658
+ ]);
659
+ /** 建议在界面上的状态;reviewing 只存在于 Host 内存,表示模型正在复核它。 */
660
+ const ItemStateSchema = /* @__PURE__ */ picklist([
661
+ "detected",
662
+ "reviewing",
663
+ "ready",
664
+ "skipped",
665
+ "failed",
666
+ "dismissed",
667
+ "written"
668
+ ]);
669
+ const DecisionSchema = /* @__PURE__ */ picklist([
670
+ "skip",
671
+ "suggest_create",
672
+ "suggest_improve"
673
+ ]);
674
+ const FailureKindViewSchema = /* @__PURE__ */ picklist([
675
+ "timeout",
676
+ "model",
677
+ "format",
678
+ "evidence",
679
+ "unsafe"
680
+ ]);
681
+ /** 证据来自的一次任务。 */
682
+ const SessionRefSchema = /* @__PURE__ */ object({
683
+ sessionId: /* @__PURE__ */ string(),
684
+ episodeId: /* @__PURE__ */ string(),
685
+ at: /* @__PURE__ */ number(),
686
+ goal: /* @__PURE__ */ string(),
687
+ outcome: /* @__PURE__ */ string()
688
+ });
689
+ /** 收件箱里的一条建议。 */
690
+ const ItemViewSchema = /* @__PURE__ */ object({
691
+ id: /* @__PURE__ */ string(),
692
+ kind: SignalKindViewSchema,
693
+ state: ItemStateSchema,
694
+ /** 模型给出的标题;未复核时为 null,界面用 goal 代替。 */
695
+ title: Nullable(/* @__PURE__ */ string()),
696
+ goal: /* @__PURE__ */ string(),
697
+ /** 模型给出的理由。 */
698
+ reason: Nullable(/* @__PURE__ */ string()),
699
+ occurrences: /* @__PURE__ */ number(),
700
+ sessions: /* @__PURE__ */ array(SessionRefSchema),
701
+ score: /* @__PURE__ */ number(),
702
+ detectedAt: /* @__PURE__ */ number(),
703
+ updatedAt: /* @__PURE__ */ number(),
704
+ decision: Nullable(DecisionSchema),
705
+ /** 新建的 Skill 名称或要改进的 Skill。 */
706
+ skill: Nullable(/* @__PURE__ */ string()),
707
+ cautions: /* @__PURE__ */ array(/* @__PURE__ */ string()),
708
+ failure: Nullable(/* @__PURE__ */ object({
709
+ kind: FailureKindViewSchema,
710
+ message: /* @__PURE__ */ string(),
711
+ at: /* @__PURE__ */ number(),
712
+ attempts: /* @__PURE__ */ number()
713
+ })),
714
+ write: Nullable(/* @__PURE__ */ object({
715
+ at: /* @__PURE__ */ number(),
716
+ scope: /* @__PURE__ */ picklist([
717
+ "project",
718
+ "user",
719
+ "existing"
720
+ ]),
721
+ skill: /* @__PURE__ */ string(),
722
+ path: /* @__PURE__ */ string(),
723
+ discovered: /* @__PURE__ */ boolean(),
724
+ /** DSH 还没列出,后台仍在核对。 */
725
+ checking: /* @__PURE__ */ boolean()
726
+ })),
727
+ dismissedSimilar: /* @__PURE__ */ boolean()
728
+ });
729
+ const InboxViewSchema = /* @__PURE__ */ object({
730
+ status: /* @__PURE__ */ object({
731
+ discovery: /* @__PURE__ */ boolean(),
732
+ review: /* @__PURE__ */ boolean(),
733
+ notify: /* @__PURE__ */ picklist(["badge", "quiet"]),
734
+ scope: /* @__PURE__ */ picklist(["project", "user"]),
735
+ model: Nullable(/* @__PURE__ */ object({
736
+ provider: /* @__PURE__ */ string(),
737
+ model: /* @__PURE__ */ string(),
738
+ effort: Nullable(/* @__PURE__ */ string()),
739
+ source: /* @__PURE__ */ picklist(["config", "default"])
740
+ })),
741
+ /** 本进程持有工作锁,负责分析与复核。 */
742
+ lead: /* @__PURE__ */ boolean(),
743
+ scanning: /* @__PURE__ */ boolean(),
744
+ /** 回合已结束、等静默期过去后再分析的会话数。 */
745
+ waiting: /* @__PURE__ */ number(),
746
+ reviewing: Nullable(/* @__PURE__ */ string()),
747
+ usage: /* @__PURE__ */ object({
748
+ reviews: /* @__PURE__ */ number(),
749
+ limit: /* @__PURE__ */ number(),
750
+ inputTokens: /* @__PURE__ */ number(),
751
+ outputTokens: /* @__PURE__ */ number()
752
+ }),
753
+ lastError: Nullable(/* @__PURE__ */ object({
754
+ kind: /* @__PURE__ */ string(),
755
+ message: /* @__PURE__ */ string(),
756
+ at: /* @__PURE__ */ number()
757
+ })),
758
+ analyzed: /* @__PURE__ */ object({
759
+ sessions: /* @__PURE__ */ number(),
760
+ episodes: /* @__PURE__ */ number()
761
+ })
762
+ }),
763
+ items: /* @__PURE__ */ array(ItemViewSchema)
764
+ });
765
+ /** 证据里的一次操作。 */
766
+ const EvidenceOpSchema = /* @__PURE__ */ object({
767
+ /** 证据编号,与模型引用一致,例如 `E1.42`。 */
768
+ ref: /* @__PURE__ */ string(),
769
+ kind: /* @__PURE__ */ picklist([
770
+ "read",
771
+ "edit",
772
+ "exec",
773
+ "delegate"
774
+ ]),
775
+ label: /* @__PURE__ */ string(),
776
+ ok: /* @__PURE__ */ boolean(),
777
+ exit: Nullable(/* @__PURE__ */ number()),
778
+ error: Nullable(/* @__PURE__ */ string()),
779
+ /** 在结果判定或恢复链里的角色。 */
780
+ role: Nullable(/* @__PURE__ */ picklist([
781
+ "verify",
782
+ "failure",
783
+ "fix",
784
+ "recovered"
785
+ ])),
786
+ /** 模型的结论引用了它。 */
787
+ cited: /* @__PURE__ */ boolean()
788
+ });
789
+ const EvidenceEpisodeSchema = /* @__PURE__ */ object({
790
+ ref: /* @__PURE__ */ string(),
791
+ episodeId: /* @__PURE__ */ string(),
792
+ sessionId: /* @__PURE__ */ string(),
793
+ at: /* @__PURE__ */ number(),
794
+ cwd: Nullable(/* @__PURE__ */ string()),
795
+ goal: /* @__PURE__ */ string(),
796
+ outcome: /* @__PURE__ */ string(),
797
+ reads: /* @__PURE__ */ number(),
798
+ ops: /* @__PURE__ */ array(EvidenceOpSchema),
799
+ reply: Nullable(/* @__PURE__ */ string()),
800
+ followup: Nullable(/* @__PURE__ */ string())
801
+ });
802
+ /** 可编辑的 Skill 字段。 */
803
+ const SkillFieldsSchema = /* @__PURE__ */ object({
804
+ name: /* @__PURE__ */ pipe(/* @__PURE__ */ string(), /* @__PURE__ */ trim(), /* @__PURE__ */ regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/u), /* @__PURE__ */ maxLength(64)),
805
+ description: /* @__PURE__ */ pipe(/* @__PURE__ */ string(), /* @__PURE__ */ trim(), /* @__PURE__ */ minLength(1), /* @__PURE__ */ maxLength(500)),
806
+ whenToUse: /* @__PURE__ */ pipe(/* @__PURE__ */ string(), /* @__PURE__ */ trim(), /* @__PURE__ */ maxLength(500)),
807
+ body: /* @__PURE__ */ pipe(/* @__PURE__ */ string(), /* @__PURE__ */ trim(), /* @__PURE__ */ minLength(1), /* @__PURE__ */ maxLength(2e4))
808
+ });
809
+ /** 一条建议的完整信息:证据、模型结论与草稿。 */
810
+ const DetailViewSchema = /* @__PURE__ */ object({
811
+ item: ItemViewSchema,
812
+ episodes: /* @__PURE__ */ array(EvidenceEpisodeSchema),
813
+ review: Nullable(/* @__PURE__ */ object({
814
+ provider: /* @__PURE__ */ string(),
815
+ model: /* @__PURE__ */ string(),
816
+ at: /* @__PURE__ */ number(),
817
+ inputTokens: /* @__PURE__ */ number(),
818
+ outputTokens: /* @__PURE__ */ number()
819
+ })),
820
+ /** suggest_create 的草稿字段。 */
821
+ draft: Nullable(SkillFieldsSchema),
822
+ /** suggest_improve 的追加内容。 */
823
+ section: Nullable(/* @__PURE__ */ string())
824
+ });
825
+ /** 写入前的预览。 */
826
+ const PreviewViewSchema = /* @__PURE__ */ variant("kind", [/* @__PURE__ */ object({
827
+ kind: /* @__PURE__ */ literal("create"),
828
+ scope: /* @__PURE__ */ picklist(["project", "user"]),
829
+ path: /* @__PURE__ */ string(),
830
+ content: /* @__PURE__ */ string(),
831
+ exists: /* @__PURE__ */ boolean()
832
+ }), /* @__PURE__ */ object({
833
+ kind: /* @__PURE__ */ literal("improve"),
834
+ skill: /* @__PURE__ */ string(),
835
+ path: Nullable(/* @__PURE__ */ string()),
836
+ /** 目标在 DSH 扫描的项目或用户 Skill 目录里,可以写入。 */
837
+ writable: /* @__PURE__ */ boolean(),
838
+ before: /* @__PURE__ */ string(),
839
+ after: /* @__PURE__ */ string(),
840
+ baseHash: /* @__PURE__ */ string()
841
+ })]);
842
+ const ActionResultSchema = /* @__PURE__ */ variant("ok", [/* @__PURE__ */ object({
843
+ ok: /* @__PURE__ */ literal(true),
844
+ inbox: InboxViewSchema,
845
+ preview: /* @__PURE__ */ optional(PreviewViewSchema)
846
+ }), /* @__PURE__ */ object({
847
+ ok: /* @__PURE__ */ literal(false),
848
+ error: /* @__PURE__ */ object({
849
+ kind: /* @__PURE__ */ picklist([
850
+ "invalid",
851
+ "missing",
852
+ "busy",
853
+ "no-model",
854
+ "exists",
855
+ "changed",
856
+ "outside",
857
+ "unsafe"
858
+ ]),
859
+ message: /* @__PURE__ */ string()
860
+ }),
861
+ inbox: InboxViewSchema
862
+ })]);
863
+ /** 非 2xx 且不是操作失败的响应体。 */
864
+ const ErrorBodySchema = /* @__PURE__ */ object({ error: /* @__PURE__ */ string() });
865
+ //#endregion
866
+ //#region src/client/api.ts
867
+ /** 浏览器端访问 Host 路由:响应按 shared.ts 的 schema 校验。 */
868
+ /** 解析响应;操作失败(409/404)也带收件箱,按结果返回而不是抛出。 */
869
+ async function parse(response, schema, path) {
870
+ const body = await response.json();
871
+ const result = /* @__PURE__ */ safeParse(schema, body);
872
+ if (result.success) return result.output;
873
+ const error = /* @__PURE__ */ safeParse(ErrorBodySchema, body);
874
+ throw new Error(error.success ? error.output.error : `${path} 返回了 ${String(response.status)}`);
875
+ }
876
+ /** 读取收件箱与工作状态。 */
877
+ async function fetchInbox(signal) {
878
+ return parse(await fetch(INBOX_PATH, {
879
+ headers: { accept: "application/json" },
880
+ cache: "no-store",
881
+ ...signal === void 0 ? {} : { signal }
882
+ }), InboxViewSchema, INBOX_PATH);
883
+ }
884
+ /** 读取一条建议的证据与草稿。 */
885
+ async function fetchDetail(id, signal) {
886
+ return parse(await fetch(`${DETAIL_PATH}?${new URLSearchParams({ id }).toString()}`, {
887
+ headers: { accept: "application/json" },
888
+ cache: "no-store",
889
+ ...signal === void 0 ? {} : { signal }
890
+ }), DetailViewSchema, DETAIL_PATH);
891
+ }
892
+ /** 提交一个操作。 */
893
+ async function postAction(action) {
894
+ return parse(await fetch(ACTION_PATH, {
895
+ method: "POST",
896
+ headers: {
897
+ accept: "application/json",
898
+ "content-type": "application/json"
899
+ },
900
+ body: JSON.stringify(action)
901
+ }), ActionResultSchema, ACTION_PATH);
902
+ }
903
+ //#endregion
904
+ //#region \0dsh-css:src/client/dialog.module.css.mjs
905
+ const css$3 = ".EFkxwG_dialog{box-sizing:border-box;border-radius:var(--dsw-radius-panel);background:var(--dsw-alias-bg-layer-2);width:min(760px,100% - 48px);max-width:none;height:min(720px,100% - 48px);max-height:none;box-shadow:var(--dsw-elevation-prominent);color:var(--dsw-alias-label-primary);border:0;margin:auto;padding:0;font-size:14px;line-height:22px;overflow:hidden}.EFkxwG_dialog[open]{animation:EFkxwG_enter var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease);flex-direction:column;display:flex}.EFkxwG_dialog::backdrop{background:var(--dsw-alias-bg-mask-1);backdrop-filter:var(--dsw-mask-blur);animation:EFkxwG_enter var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease)}.EFkxwG_dialog:focus{outline:none}@keyframes EFkxwG_enter{0%{opacity:0}to{opacity:1}}.EFkxwG_header{flex:none;justify-content:space-between;align-items:flex-start;gap:12px;padding:22px 14px 12px 24px;display:flex}.EFkxwG_heading{flex-direction:column;gap:2px;min-width:0;display:flex}.EFkxwG_title{color:var(--dsw-alias-label-primary);overflow-wrap:anywhere;margin:0;font-size:16px;font-weight:500;line-height:24px}.EFkxwG_subtitle{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:18px}.EFkxwG_close{border-radius:var(--dsw-radius-sm);width:28px;height:28px;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:0;flex:none;justify-content:center;align-items:center;padding:0;display:inline-flex}@media (hover:hover){.EFkxwG_close:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}}.EFkxwG_close:disabled{opacity:.4;cursor:default}.EFkxwG_close:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:2px}.EFkxwG_body{flex-direction:column;flex:auto;min-height:0;padding:2px 24px 16px;display:flex;overflow-y:auto}.EFkxwG_footer{border-top:.5px solid var(--dsw-alias-border-l2);flex-wrap:wrap;flex:none;align-items:center;gap:8px;padding:14px 24px 20px;display:flex}@media (prefers-reduced-motion:reduce){.EFkxwG_dialog[open],.EFkxwG_dialog::backdrop{animation:none}}";
906
+ const tag$3 = "@agent-stash/dsh-skill-scout/dialog.module.css";
907
+ if (document.querySelector(`style[data-plugin-css=${JSON.stringify(tag$3)}]`) === null) {
908
+ const style = document.createElement("style");
909
+ style.dataset.plugin = "@agent-stash/dsh-skill-scout";
910
+ style.dataset.pluginCss = tag$3;
911
+ style.textContent = css$3;
912
+ document.head.appendChild(style);
913
+ }
914
+ var dialog_module_css_default = {
915
+ "body": "EFkxwG_body",
916
+ "close": "EFkxwG_close",
917
+ "dialog": "EFkxwG_dialog",
918
+ "enter": "EFkxwG_enter",
919
+ "footer": "EFkxwG_footer",
920
+ "header": "EFkxwG_header",
921
+ "heading": "EFkxwG_heading",
922
+ "subtitle": "EFkxwG_subtitle",
923
+ "title": "EFkxwG_title"
924
+ };
925
+ //#endregion
926
+ //#region src/client/dialog.tsx
927
+ /**
928
+ * 模态对话框:原生 `<dialog>` 加 `showModal()`,卡片外观取自宿主 `Modal`,宽度适合并排阅读证据与预览。
929
+ * 挂上时进入 top layer,卸载时浏览器把焦点还给打开前的控件。初始焦点给带 `data-autofocus` 的控件。
930
+ * `busy` 期间点遮罩、按 Escape 与关闭按钮都不生效。
931
+ */
932
+ /** 点击落在卡片矩形之外,即遮罩上。 */
933
+ function onBackdrop(event) {
934
+ if (event.target !== event.currentTarget) return false;
935
+ const box = event.currentTarget.getBoundingClientRect();
936
+ return event.clientX < box.left || event.clientX > box.right || event.clientY < box.top || event.clientY > box.bottom;
937
+ }
938
+ function Dialog({ title, closeLabel, subtitle, busy, onClose, children, footer }) {
939
+ const element = (0, react.useRef)(null);
940
+ const pressedOutside = (0, react.useRef)(false);
941
+ const attach = (0, react.useCallback)((node) => {
942
+ if (node === null) {
943
+ element.current?.close();
944
+ element.current = null;
945
+ return;
946
+ }
947
+ element.current = node;
948
+ if (!node.open) node.showModal();
949
+ node.querySelector("[data-autofocus]")?.focus();
950
+ }, []);
951
+ const close = () => {
952
+ if (!busy) onClose();
953
+ };
954
+ /** Escape 触发的 cancel 一律接管,忙时不关闭。 */
955
+ const onCancel = (event) => {
956
+ event.preventDefault();
957
+ close();
958
+ };
959
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("dialog", {
960
+ ref: attach,
961
+ "aria-label": title,
962
+ "aria-busy": busy || void 0,
963
+ className: dialog_module_css_default.dialog,
964
+ onCancel,
965
+ onPointerDown: (event) => {
966
+ pressedOutside.current = onBackdrop(event);
967
+ },
968
+ onClick: (event) => {
969
+ if (pressedOutside.current && onBackdrop(event)) close();
970
+ pressedOutside.current = false;
971
+ },
972
+ children: [
973
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
974
+ className: dialog_module_css_default.header,
975
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
976
+ className: dialog_module_css_default.heading,
977
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
978
+ className: dialog_module_css_default.title,
979
+ children: title
980
+ }), subtitle !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
981
+ className: dialog_module_css_default.subtitle,
982
+ children: subtitle
983
+ })]
984
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
985
+ type: "button",
986
+ className: dialog_module_css_default.close,
987
+ "aria-label": closeLabel,
988
+ disabled: busy,
989
+ onClick: close,
990
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCloseOutlineRegular, { size: 14 })
991
+ })]
992
+ }),
993
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
994
+ className: dialog_module_css_default.body,
995
+ children
996
+ }),
997
+ footer !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
998
+ className: dialog_module_css_default.footer,
999
+ children: footer
1000
+ })
1001
+ ]
1002
+ });
1003
+ }
1004
+ //#endregion
1005
+ //#region \0dsh-css:src/client/detail.module.css.mjs
1006
+ const css$2 = ".FSsC8q_tabs{z-index:1;background:var(--dsw-alias-bg-layer-2);padding:0 0 12px;position:sticky;top:0}.FSsC8q_panel{min-width:0}.FSsC8q_stack{flex-direction:column;gap:14px;min-width:0;display:flex}.FSsC8q_heading{color:var(--dsw-alias-label-secondary);margin:0;font-size:12px;font-weight:600;line-height:18px}.FSsC8q_why p{color:var(--dsw-alias-label-primary);margin:4px 0 0;font-size:13px;line-height:20px}.FSsC8q_callout{border-radius:var(--dsw-radius-lg);background:var(--dsw-alias-interactive-bg-hover);flex-direction:column;gap:8px;margin:0;padding:12px 14px;font-size:13px;line-height:20px;display:flex}.FSsC8q_callout p{margin:0}.FSsC8q_note{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:18px}.FSsC8q_caution{border-radius:var(--dsw-radius-md);background:var(--dsw-alias-state-warn-tertiary);color:var(--dsw-alias-state-warn-label);margin:0;padding:8px 12px;font-size:12px;line-height:18px}.FSsC8q_failure{color:var(--dsw-alias-state-error-label,var(--dsw-alias-state-warn-label));flex-direction:column;gap:2px;margin:0;font-size:13px;line-height:20px;display:flex}.FSsC8q_failureDetail{color:var(--dsw-alias-label-tertiary);overflow-wrap:anywhere;font-size:12px;line-height:18px}.FSsC8q_episode{border-top:.5px solid var(--dsw-alias-border-l2);flex-direction:column;gap:8px;padding:12px 0 0;display:flex}.FSsC8q_episodeHead{align-items:flex-start;gap:10px;display:flex}.FSsC8q_ref{border-radius:var(--dsw-radius-xs);background:var(--dsw-alias-interactive-bg-hover);min-width:24px;color:var(--dsw-alias-label-secondary);text-align:center;font-variant-numeric:tabular-nums;flex:none;padding:1px 6px;font-size:11px;line-height:18px}.FSsC8q_episodeText{flex-direction:column;flex:1;gap:2px;min-width:0;display:flex}.FSsC8q_goal{-webkit-line-clamp:2;color:var(--dsw-alias-label-primary);overflow-wrap:anywhere;-webkit-box-orient:vertical;margin:0;font-size:13px;line-height:20px;display:-webkit-box;overflow:hidden}.FSsC8q_meta{color:var(--dsw-alias-label-tertiary);flex-wrap:wrap;gap:4px 10px;margin:0;font-size:12px;line-height:18px;display:flex}.FSsC8q_path{font-family:var(--dsw-font-family-mono,ui-monospace, monospace);font-size:11px}.FSsC8q_ops{flex-direction:column;margin:0;padding:0 0 0 34px;list-style:none;display:flex}.FSsC8q_op{border-radius:var(--dsw-radius-md);align-items:flex-start;gap:8px;padding:4px 8px;font-size:12px;line-height:18px;display:flex}.FSsC8q_op[data-cited]{background:var(--dsw-alias-interactive-bg-hover)}.FSsC8q_opGlyph{width:14px;height:18px;color:var(--dsw-alias-label-tertiary);flex:none;justify-content:center;align-items:center;display:inline-flex}.FSsC8q_op[data-ok=false] .FSsC8q_opGlyph{color:var(--dsw-alias-state-warn-label)}.FSsC8q_opBody{flex-wrap:wrap;flex:1;align-items:baseline;gap:2px 8px;min-width:0;display:flex}.FSsC8q_opLabel{min-width:0;font-family:var(--dsw-font-family-mono,ui-monospace, monospace);color:var(--dsw-alias-label-primary);overflow-wrap:anywhere;font-size:12px}.FSsC8q_opLabel[data-kind=edit]{color:var(--dsw-alias-label-secondary)}.FSsC8q_opTags{flex-wrap:wrap;gap:4px;display:inline-flex}.FSsC8q_tag{border-radius:var(--dsw-radius-xs);background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-secondary);padding:0 6px;font-size:11px;line-height:16px}.FSsC8q_tag[data-cited]{color:var(--dsw-alias-state-business-primary)}.FSsC8q_opError{color:var(--dsw-alias-label-tertiary);font-family:var(--dsw-font-family-mono,ui-monospace, monospace);overflow-wrap:anywhere;flex-basis:100%;font-size:11px}.FSsC8q_opRef{color:var(--dsw-alias-label-caption,var(--dsw-alias-label-tertiary));font-variant-numeric:tabular-nums;flex:none;font-size:11px}.FSsC8q_toolbar{justify-content:space-between;align-items:center;gap:8px;display:flex}.FSsC8q_facts{grid-template-columns:max-content 1fr;gap:4px 16px;margin:0;font-size:13px;line-height:20px;display:grid}.FSsC8q_facts dt{color:var(--dsw-alias-label-tertiary)}.FSsC8q_facts dd{overflow-wrap:anywhere;min-width:0;margin:0}.FSsC8q_markdown{border-radius:var(--dsw-radius-lg);background:var(--dsw-alias-bg-layer-1,var(--dsw-alias-bg-base));box-shadow:0 0 0 .5px var(--dsw-alias-border-l2);padding:4px 16px}.FSsC8q_editor{flex-direction:column;gap:12px;display:flex}.FSsC8q_field{flex-direction:column;gap:4px;display:flex}.FSsC8q_fieldLabel{color:var(--dsw-alias-label-secondary);font-size:12px;font-weight:500;line-height:18px}.FSsC8q_input{box-sizing:border-box;border-radius:var(--dsw-radius-md);background:var(--dsw-alias-bg-layer-1,var(--dsw-alias-bg-base));width:100%;box-shadow:0 0 0 .5px var(--dsw-alias-border-l2);color:var(--dsw-alias-label-primary);font:inherit;resize:vertical;border:0;padding:7px 10px;font-size:13px;line-height:20px}.FSsC8q_input:focus-visible{box-shadow:0 0 0 1.5px var(--dsw-alias-state-business-primary);outline:none}.FSsC8q_mono{font-family:var(--dsw-font-family-mono,ui-monospace, monospace);font-size:12px;line-height:18px}.FSsC8q_spacer{flex:1}.FSsC8q_actionError{color:var(--dsw-alias-state-error-label,var(--dsw-alias-state-warn-label));flex-basis:100%;margin:0;font-size:12px;line-height:18px}.FSsC8q_written{min-width:0;color:var(--dsw-alias-label-secondary);flex-direction:column;margin:0;font-size:12px;line-height:18px;display:flex}.FSsC8q_writtenPath{color:var(--dsw-alias-label-primary);font-family:var(--dsw-font-family-mono,ui-monospace, monospace);overflow-wrap:anywhere}";
1007
+ const tag$2 = "@agent-stash/dsh-skill-scout/detail.module.css";
1008
+ if (document.querySelector(`style[data-plugin-css=${JSON.stringify(tag$2)}]`) === null) {
1009
+ const style = document.createElement("style");
1010
+ style.dataset.plugin = "@agent-stash/dsh-skill-scout";
1011
+ style.dataset.pluginCss = tag$2;
1012
+ style.textContent = css$2;
1013
+ document.head.appendChild(style);
1014
+ }
1015
+ var detail_module_css_default = {
1016
+ "actionError": "FSsC8q_actionError",
1017
+ "callout": "FSsC8q_callout",
1018
+ "caution": "FSsC8q_caution",
1019
+ "editor": "FSsC8q_editor",
1020
+ "episode": "FSsC8q_episode",
1021
+ "episodeHead": "FSsC8q_episodeHead",
1022
+ "episodeText": "FSsC8q_episodeText",
1023
+ "facts": "FSsC8q_facts",
1024
+ "failure": "FSsC8q_failure",
1025
+ "failureDetail": "FSsC8q_failureDetail",
1026
+ "field": "FSsC8q_field",
1027
+ "fieldLabel": "FSsC8q_fieldLabel",
1028
+ "goal": "FSsC8q_goal",
1029
+ "heading": "FSsC8q_heading",
1030
+ "input": "FSsC8q_input",
1031
+ "markdown": "FSsC8q_markdown",
1032
+ "meta": "FSsC8q_meta",
1033
+ "mono": "FSsC8q_mono",
1034
+ "note": "FSsC8q_note",
1035
+ "op": "FSsC8q_op",
1036
+ "opBody": "FSsC8q_opBody",
1037
+ "opError": "FSsC8q_opError",
1038
+ "opGlyph": "FSsC8q_opGlyph",
1039
+ "opLabel": "FSsC8q_opLabel",
1040
+ "opRef": "FSsC8q_opRef",
1041
+ "ops": "FSsC8q_ops",
1042
+ "opTags": "FSsC8q_opTags",
1043
+ "panel": "FSsC8q_panel",
1044
+ "path": "FSsC8q_path",
1045
+ "ref": "FSsC8q_ref",
1046
+ "spacer": "FSsC8q_spacer",
1047
+ "stack": "FSsC8q_stack",
1048
+ "tabs": "FSsC8q_tabs",
1049
+ "tag": "FSsC8q_tag",
1050
+ "toolbar": "FSsC8q_toolbar",
1051
+ "why": "FSsC8q_why",
1052
+ "written": "FSsC8q_written",
1053
+ "writtenPath": "FSsC8q_writtenPath"
1054
+ };
1055
+ //#endregion
1056
+ //#region src/client/format.ts
1057
+ const MINUTE = 6e4;
1058
+ const HOUR = 60 * MINUTE;
1059
+ const DAY = 24 * HOUR;
1060
+ /** 相对时间:刚刚、N 分钟前、N 小时前、N 天前。 */
1061
+ function ago(t, at, now) {
1062
+ const elapsed = Math.max(0, now - at);
1063
+ if (elapsed < MINUTE) return t("time.now");
1064
+ if (elapsed < HOUR) return t("time.minutes", { count: Math.floor(elapsed / MINUTE) });
1065
+ if (elapsed < DAY) return t("time.hours", { count: Math.floor(elapsed / HOUR) });
1066
+ return t("time.days", { count: Math.floor(elapsed / DAY) });
1067
+ }
1068
+ /** 「N 个会话」。 */
1069
+ function sessionCount(t, count) {
1070
+ return t(count === 1 ? "meta.sessions.one" : "meta.sessions.other", { count });
1071
+ }
1072
+ /** 卡片标题:模型标题,未复核时用用户请求。 */
1073
+ function titleOf(item) {
1074
+ return item.title ?? item.goal;
1075
+ }
1076
+ /** 一行元信息:信号类型 · 会话数 · 时间。 */
1077
+ function metaOf(t, item, now) {
1078
+ return [
1079
+ t(`kind.${item.kind}`),
1080
+ sessionCount(t, item.occurrences),
1081
+ ago(t, item.updatedAt, now)
1082
+ ].join(" · ");
1083
+ }
1084
+ /** 按状态分组:可保存、本地发现(含复核中与失败)、历史。 */
1085
+ function groupItems(items) {
1086
+ return {
1087
+ ready: items.filter((item) => item.state === "ready"),
1088
+ found: items.filter((item) => item.state === "detected" || item.state === "reviewing" || item.state === "failed"),
1089
+ history: items.filter((item) => item.state === "written" || item.state === "skipped" || item.state === "dismissed")
1090
+ };
1091
+ }
1092
+ /** 把 `~` 开头之外的长路径缩短到末尾几段。 */
1093
+ function shortPath(path) {
1094
+ const parts = path.split("/");
1095
+ return parts.length <= 4 ? path : `…/${parts.slice(-4).join("/")}`;
1096
+ }
1097
+ //#endregion
1098
+ //#region src/client/detail.tsx
1099
+ /**
1100
+ * 一条建议的详情:证据、Skill 预览与编辑、文件改动,以及保存、忽略、撤销。
1101
+ * 证据与草稿从 Host 读取;建议的状态跟随收件箱刷新,状态或更新时间变化时重新读取详情。
1102
+ */
1103
+ function message(error) {
1104
+ return error instanceof Error ? error.message : String(error);
1105
+ }
1106
+ /** Markdown 与 diff 视图的文案。 */
1107
+ function useLabels(t) {
1108
+ return (0, react.useMemo)(() => {
1109
+ const toolbar = {
1110
+ codeLabel: t("code.label"),
1111
+ wrapLabel: t("code.wrap"),
1112
+ unwrapLabel: t("code.unwrap")
1113
+ };
1114
+ return {
1115
+ markdown: {
1116
+ code: {
1117
+ copyLabel: t("code.copy"),
1118
+ copiedLabel: t("code.copied"),
1119
+ toolbarLabels: toolbar
1120
+ },
1121
+ footnotes: t("code.footnotes")
1122
+ },
1123
+ diff: {
1124
+ ...toolbar,
1125
+ copy: t("code.copy"),
1126
+ copied: t("code.copied"),
1127
+ collapse: t("diff.collapse"),
1128
+ collapseAria: t("diff.collapseAria"),
1129
+ expand: (count) => t("diff.expand", { count }),
1130
+ expandAria: (count) => t("diff.expand", { count })
1131
+ }
1132
+ };
1133
+ }, [t]);
1134
+ }
1135
+ const ROLE_KEYS = {
1136
+ verify: "role.verify",
1137
+ failure: "role.failure",
1138
+ fix: "role.fix",
1139
+ recovered: "role.recovered"
1140
+ };
1141
+ function Episode({ episode, now, openSession, t }) {
1142
+ const outcome = `outcome.${episode.outcome}`;
1143
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
1144
+ className: detail_module_css_default.episode,
1145
+ "aria-label": episode.goal,
1146
+ children: [
1147
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1148
+ className: detail_module_css_default.episodeHead,
1149
+ children: [
1150
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1151
+ className: detail_module_css_default.ref,
1152
+ children: episode.ref
1153
+ }),
1154
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1155
+ className: detail_module_css_default.episodeText,
1156
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1157
+ className: detail_module_css_default.goal,
1158
+ children: episode.goal
1159
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
1160
+ className: detail_module_css_default.meta,
1161
+ children: [
1162
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1163
+ "data-outcome": episode.outcome,
1164
+ children: isKey(outcome) ? t(outcome) : episode.outcome
1165
+ }),
1166
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: ago(t, episode.at, now) }),
1167
+ episode.cwd !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1168
+ className: detail_module_css_default.path,
1169
+ children: shortPath(episode.cwd)
1170
+ })
1171
+ ]
1172
+ })]
1173
+ }),
1174
+ openSession !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1175
+ size: "sm",
1176
+ variant: "ghost",
1177
+ onClick: () => openSession(episode.sessionId),
1178
+ children: t("detail.openSession")
1179
+ })
1180
+ ]
1181
+ }),
1182
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ol", {
1183
+ className: detail_module_css_default.ops,
1184
+ "aria-label": t("evidence.list", { ref: episode.ref }),
1185
+ children: episode.ops.map((op) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
1186
+ className: detail_module_css_default.op,
1187
+ "data-ok": op.ok,
1188
+ "data-cited": op.cited || void 0,
1189
+ children: [
1190
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1191
+ className: detail_module_css_default.opGlyph,
1192
+ "aria-hidden": "true",
1193
+ children: op.ok ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCheckOutlineRegular, { size: 12 }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCloseOutlineRegular, { size: 12 })
1194
+ }),
1195
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1196
+ className: detail_module_css_default.opBody,
1197
+ children: [
1198
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
1199
+ className: detail_module_css_default.opLabel,
1200
+ "data-kind": op.kind,
1201
+ children: op.label
1202
+ }),
1203
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1204
+ className: detail_module_css_default.opTags,
1205
+ children: [
1206
+ op.role !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1207
+ className: detail_module_css_default.tag,
1208
+ children: t(ROLE_KEYS[op.role])
1209
+ }),
1210
+ op.cited && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1211
+ className: detail_module_css_default.tag,
1212
+ "data-cited": "",
1213
+ children: t("role.cited")
1214
+ }),
1215
+ op.exit !== null && op.exit !== 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1216
+ className: detail_module_css_default.tag,
1217
+ children: t("evidence.exit", { code: op.exit })
1218
+ })
1219
+ ]
1220
+ }),
1221
+ op.error !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1222
+ className: detail_module_css_default.opError,
1223
+ children: op.error
1224
+ })
1225
+ ]
1226
+ }),
1227
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1228
+ className: detail_module_css_default.opRef,
1229
+ children: op.ref
1230
+ })
1231
+ ]
1232
+ }, op.ref))
1233
+ }),
1234
+ episode.reads > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1235
+ className: detail_module_css_default.note,
1236
+ children: t(episode.reads === 1 ? "evidence.reads.one" : "evidence.reads.other", { count: episode.reads })
1237
+ }),
1238
+ episode.followup !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
1239
+ className: detail_module_css_default.note,
1240
+ children: [
1241
+ t("evidence.followup"),
1242
+ ": ",
1243
+ episode.followup
1244
+ ]
1245
+ })
1246
+ ]
1247
+ });
1248
+ }
1249
+ const KEYS = /* @__PURE__ */ new Set([
1250
+ "outcome.verified",
1251
+ "outcome.succeeded",
1252
+ "outcome.unverified",
1253
+ "outcome.disputed",
1254
+ "outcome.failed",
1255
+ "outcome.aborted"
1256
+ ]);
1257
+ function isKey(key) {
1258
+ return KEYS.has(key);
1259
+ }
1260
+ function FieldsEditor({ fields, onChange, t }) {
1261
+ const id = (0, react.useId)();
1262
+ const field = (key, rows, mono = false) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
1263
+ className: detail_module_css_default.field,
1264
+ htmlFor: `${id}-${key}`,
1265
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1266
+ className: detail_module_css_default.fieldLabel,
1267
+ children: t(`skill.${key}`)
1268
+ }), rows === 1 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1269
+ id: `${id}-${key}`,
1270
+ className: detail_module_css_default.input,
1271
+ value: fields[key],
1272
+ spellCheck: false,
1273
+ onChange: (event) => onChange({
1274
+ ...fields,
1275
+ [key]: event.target.value
1276
+ })
1277
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
1278
+ id: `${id}-${key}`,
1279
+ className: mono ? `${detail_module_css_default.input} ${detail_module_css_default.mono}` : detail_module_css_default.input,
1280
+ rows,
1281
+ value: fields[key],
1282
+ onChange: (event) => onChange({
1283
+ ...fields,
1284
+ [key]: event.target.value
1285
+ })
1286
+ })]
1287
+ });
1288
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1289
+ className: detail_module_css_default.editor,
1290
+ children: [
1291
+ field("name", 1),
1292
+ field("description", 2),
1293
+ field("whenToUse", 2),
1294
+ field("body", 14, true)
1295
+ ]
1296
+ });
1297
+ }
1298
+ /** 一条建议的详情对话框。 */
1299
+ function Detail({ id, item, inbox, loadDetail, modelName, defaultScope, openSession, t, onClose }) {
1300
+ const tabsId = (0, react.useId)();
1301
+ const labels = useLabels(t);
1302
+ const [detail, setDetail] = (0, react.useState)({ status: "loading" });
1303
+ const [tab, setTab] = (0, react.useState)("evidence");
1304
+ const [scope, setScope] = (0, react.useState)(defaultScope);
1305
+ const [editing, setEditing] = (0, react.useState)(false);
1306
+ const [fields, setFields] = (0, react.useState)(null);
1307
+ const [section, setSection] = (0, react.useState)(null);
1308
+ const [preview, setPreview] = (0, react.useState)(null);
1309
+ const [busy, setBusy] = (0, react.useState)(null);
1310
+ const [error, setError] = (0, react.useState)(null);
1311
+ const [now] = (0, react.useState)(() => Date.now());
1312
+ const version = `${item.state}:${String(item.updatedAt)}`;
1313
+ (0, react.useEffect)(() => {
1314
+ const controller = new AbortController();
1315
+ loadDetail(id, version, controller.signal).then((value) => {
1316
+ if (controller.signal.aborted) return;
1317
+ setDetail({
1318
+ status: "ready",
1319
+ value
1320
+ });
1321
+ }, (reason) => {
1322
+ if (controller.signal.aborted) return;
1323
+ setDetail({
1324
+ status: "error",
1325
+ error: message(reason)
1326
+ });
1327
+ });
1328
+ return () => controller.abort();
1329
+ }, [
1330
+ id,
1331
+ version,
1332
+ loadDetail
1333
+ ]);
1334
+ const value = detail.status === "ready" ? detail.value : null;
1335
+ const create = item.decision === "suggest_create";
1336
+ const improve = item.decision === "suggest_improve";
1337
+ const reviewed = create || improve;
1338
+ const draft = fields ?? value?.draft ?? null;
1339
+ const shownSection = section ?? value?.section ?? null;
1340
+ const parsed = draft === null ? null : /* @__PURE__ */ safeParse(SkillFieldsSchema, draft);
1341
+ const invalid = create && parsed !== null && !parsed.success;
1342
+ const wantPreview = reviewed && item.state === "ready" && value !== null;
1343
+ (0, react.useEffect)(() => {
1344
+ if (!wantPreview || invalid) return void 0;
1345
+ let stale = false;
1346
+ const timer = setTimeout(() => {
1347
+ setPreview((current) => current === null ? { status: "loading" } : current);
1348
+ const action = {
1349
+ action: "preview",
1350
+ id,
1351
+ ...create ? { scope } : {},
1352
+ ...fields === null ? {} : { fields },
1353
+ ...section === null ? {} : { section }
1354
+ };
1355
+ inbox.act(action).then((result) => {
1356
+ if (stale) return;
1357
+ if (result.ok && result.preview !== void 0) setPreview({
1358
+ status: "ready",
1359
+ value: result.preview
1360
+ });
1361
+ else setPreview({
1362
+ status: "error",
1363
+ error: result.ok ? "" : t(`error.${result.error.kind}`)
1364
+ });
1365
+ }, (reason) => {
1366
+ if (!stale) setPreview({
1367
+ status: "error",
1368
+ error: message(reason)
1369
+ });
1370
+ });
1371
+ }, fields === null && section === null ? 0 : 300);
1372
+ return () => {
1373
+ stale = true;
1374
+ clearTimeout(timer);
1375
+ };
1376
+ }, [
1377
+ wantPreview,
1378
+ invalid,
1379
+ id,
1380
+ create,
1381
+ scope,
1382
+ fields,
1383
+ section,
1384
+ inbox,
1385
+ t
1386
+ ]);
1387
+ const run = async (kind, action, notice) => {
1388
+ setBusy(kind);
1389
+ setError(null);
1390
+ try {
1391
+ const result = await inbox.act(action, notice);
1392
+ if (!result.ok) setError(t(`error.${result.error.kind}`));
1393
+ return result.ok;
1394
+ } catch (reason) {
1395
+ setError(t("error.request", { error: message(reason) }));
1396
+ return false;
1397
+ } finally {
1398
+ setBusy(null);
1399
+ }
1400
+ };
1401
+ const skillName = item.skill ?? "";
1402
+ const dismiss = async (similar) => {
1403
+ if (await run("dismiss", {
1404
+ action: "dismiss",
1405
+ id,
1406
+ similar
1407
+ }, {
1408
+ key: similar ? "notice.dismissedSimilar" : "notice.dismissed",
1409
+ params: {},
1410
+ undo: {
1411
+ action: "restore",
1412
+ id
1413
+ }
1414
+ })) onClose();
1415
+ };
1416
+ const save = async () => {
1417
+ if (await run("save", {
1418
+ action: "write",
1419
+ id,
1420
+ ...create ? { scope } : {},
1421
+ ...fields === null ? {} : { fields },
1422
+ ...section === null ? {} : { section },
1423
+ ...preview?.status === "ready" && preview.value.kind === "improve" ? { baseHash: preview.value.baseHash } : {}
1424
+ }, {
1425
+ key: "notice.written",
1426
+ params: { skill: draft?.name ?? skillName },
1427
+ undo: {
1428
+ action: "undo",
1429
+ id
1430
+ }
1431
+ })) {
1432
+ setEditing(false);
1433
+ setPreview(null);
1434
+ }
1435
+ };
1436
+ const tabs = [{
1437
+ value: "evidence",
1438
+ label: t("detail.tab.evidence")
1439
+ }, ...reviewed ? [{
1440
+ value: "skill",
1441
+ label: t("detail.tab.skill")
1442
+ }, ...item.state === "ready" ? [{
1443
+ value: "changes",
1444
+ label: t("detail.tab.changes")
1445
+ }] : []] : []];
1446
+ const activeTab = tabs.some((entry) => entry.value === tab) ? tab : "evidence";
1447
+ const subtitle = [
1448
+ t(`kind.${item.kind}`),
1449
+ sessionCount(t, item.occurrences),
1450
+ value?.review === null || value === null ? t(`state.${item.state}`) : t("detail.reviewedBy", { model: value.review.model })
1451
+ ].join(" · ");
1452
+ const reviewPanel = () => {
1453
+ if (reviewed || item.state === "dismissed" || item.state === "written") return null;
1454
+ if (item.state === "skipped") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1455
+ className: detail_module_css_default.callout,
1456
+ children: t("detail.skipped")
1457
+ });
1458
+ if (item.state === "reviewing") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1459
+ className: detail_module_css_default.callout,
1460
+ role: "status",
1461
+ children: t("detail.reviewing", { model: modelName ?? "" })
1462
+ });
1463
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1464
+ className: detail_module_css_default.callout,
1465
+ children: [
1466
+ item.failure !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
1467
+ className: detail_module_css_default.failure,
1468
+ role: "alert",
1469
+ children: [t("detail.failed", { kind: t(`failure.${item.failure.kind}`) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1470
+ className: detail_module_css_default.failureDetail,
1471
+ children: item.failure.message
1472
+ })]
1473
+ }),
1474
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t("detail.local") }),
1475
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1476
+ className: detail_module_css_default.note,
1477
+ children: modelName === null ? t("detail.noModel") : t("detail.reviewNote", {
1478
+ count: item.sessions.length,
1479
+ model: modelName
1480
+ })
1481
+ }),
1482
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1483
+ size: "sm",
1484
+ variant: "outline",
1485
+ "data-autofocus": "",
1486
+ disabled: modelName === null || busy !== null,
1487
+ onClick: () => void run("review", {
1488
+ action: "analyze",
1489
+ id
1490
+ }),
1491
+ children: item.failure === null ? t("detail.review") : t("detail.retry")
1492
+ }) })
1493
+ ]
1494
+ });
1495
+ };
1496
+ const evidence = () => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1497
+ className: detail_module_css_default.stack,
1498
+ children: [
1499
+ reviewPanel(),
1500
+ item.reason !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
1501
+ className: detail_module_css_default.why,
1502
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
1503
+ className: detail_module_css_default.heading,
1504
+ children: t("detail.why")
1505
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: item.reason })]
1506
+ }),
1507
+ item.cautions.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1508
+ className: detail_module_css_default.caution,
1509
+ role: "note",
1510
+ children: t("detail.cautions", { list: item.cautions.map((caution) => {
1511
+ const key = `caution.${caution}`;
1512
+ return isCaution(key) ? t(key) : caution;
1513
+ }).join(", ") })
1514
+ }),
1515
+ value?.episodes.map((episode) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Episode, {
1516
+ episode,
1517
+ now,
1518
+ openSession,
1519
+ t
1520
+ }, episode.episodeId))
1521
+ ]
1522
+ });
1523
+ const skill = () => {
1524
+ if (create && draft !== null) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1525
+ className: detail_module_css_default.stack,
1526
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1527
+ className: detail_module_css_default.toolbar,
1528
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1529
+ size: "sm",
1530
+ variant: "ghost",
1531
+ onClick: () => setEditing(!editing),
1532
+ disabled: item.state !== "ready",
1533
+ children: editing ? t("skill.preview") : t("skill.edit")
1534
+ }), fields !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1535
+ size: "sm",
1536
+ variant: "ghost",
1537
+ onClick: () => setFields(null),
1538
+ children: t("skill.reset")
1539
+ })]
1540
+ }), editing ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(FieldsEditor, {
1541
+ fields: draft,
1542
+ onChange: setFields,
1543
+ t
1544
+ }), invalid && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1545
+ className: detail_module_css_default.failure,
1546
+ role: "alert",
1547
+ children: t("skill.invalid")
1548
+ })] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("dl", {
1549
+ className: detail_module_css_default.facts,
1550
+ children: [
1551
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", { children: t("skill.name") }),
1552
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", { children: draft.name }) }),
1553
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", { children: t("skill.description") }),
1554
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", { children: draft.description }),
1555
+ draft.whenToUse !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", { children: t("skill.whenToUse") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", { children: draft.whenToUse })] })
1556
+ ]
1557
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1558
+ className: detail_module_css_default.markdown,
1559
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.MarkdownText, {
1560
+ text: draft.body,
1561
+ labels: labels.markdown
1562
+ })
1563
+ })] })]
1564
+ });
1565
+ if (improve && shownSection !== null) {
1566
+ const notWritable = preview?.status === "ready" && preview.value.kind === "improve" && !preview.value.writable;
1567
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1568
+ className: detail_module_css_default.stack,
1569
+ children: [
1570
+ notWritable && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1571
+ className: detail_module_css_default.caution,
1572
+ children: t("skill.notWritable", { skill: skillName })
1573
+ }),
1574
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1575
+ className: detail_module_css_default.toolbar,
1576
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1577
+ className: detail_module_css_default.heading,
1578
+ children: t("skill.section", { skill: skillName })
1579
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1580
+ size: "sm",
1581
+ variant: "ghost",
1582
+ onClick: () => setEditing(!editing),
1583
+ disabled: item.state !== "ready",
1584
+ children: editing ? t("skill.preview") : t("skill.edit")
1585
+ })]
1586
+ }),
1587
+ editing ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
1588
+ className: `${detail_module_css_default.input} ${detail_module_css_default.mono}`,
1589
+ "aria-label": t("skill.section", { skill: skillName }),
1590
+ rows: 12,
1591
+ value: shownSection,
1592
+ onChange: (event) => setSection(event.target.value)
1593
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1594
+ className: detail_module_css_default.markdown,
1595
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.MarkdownText, {
1596
+ text: shownSection,
1597
+ labels: labels.markdown
1598
+ })
1599
+ })
1600
+ ]
1601
+ });
1602
+ }
1603
+ return null;
1604
+ };
1605
+ const changes = () => {
1606
+ if (preview === null || preview.status === "loading") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1607
+ className: detail_module_css_default.note,
1608
+ role: "status",
1609
+ children: t("changes.loading")
1610
+ });
1611
+ if (preview.status === "error") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1612
+ className: detail_module_css_default.failure,
1613
+ role: "alert",
1614
+ children: t("changes.error", { error: preview.error })
1615
+ });
1616
+ const file = preview.value;
1617
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1618
+ className: detail_module_css_default.stack,
1619
+ children: [file.kind === "create" && file.exists && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1620
+ className: detail_module_css_default.caution,
1621
+ children: t("changes.exists")
1622
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.DiffBlock, {
1623
+ diffs: [file.kind === "create" ? {
1624
+ path: file.path,
1625
+ oldText: null,
1626
+ newText: file.content
1627
+ } : {
1628
+ path: file.path ?? file.skill,
1629
+ oldText: file.before,
1630
+ newText: file.after
1631
+ }],
1632
+ labels: labels.diff,
1633
+ maxLines: 400
1634
+ })]
1635
+ });
1636
+ };
1637
+ const footer = () => {
1638
+ if (item.state === "written" && item.write !== null) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
1639
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
1640
+ className: detail_module_css_default.written,
1641
+ role: "status",
1642
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1643
+ className: detail_module_css_default.writtenPath,
1644
+ children: t("written.path", { path: shortPath(item.write.path) })
1645
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: item.write.discovered ? t("written.discovered") : item.write.checking ? t("written.checking") : t("written.missing") })]
1646
+ }),
1647
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: detail_module_css_default.spacer }),
1648
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1649
+ size: "sm",
1650
+ variant: "outline",
1651
+ disabled: busy !== null,
1652
+ onClick: () => void run("undo", {
1653
+ action: "undo",
1654
+ id
1655
+ }, {
1656
+ key: "notice.undone",
1657
+ params: { skill: item.write?.skill ?? skillName },
1658
+ undo: null
1659
+ }),
1660
+ children: t("action.undo")
1661
+ }),
1662
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1663
+ size: "sm",
1664
+ variant: "primary",
1665
+ onClick: onClose,
1666
+ "data-autofocus": "",
1667
+ children: t("action.done")
1668
+ })
1669
+ ] });
1670
+ if (item.state === "dismissed") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: detail_module_css_default.spacer }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1671
+ size: "sm",
1672
+ variant: "outline",
1673
+ disabled: busy !== null,
1674
+ onClick: () => void run("restore", {
1675
+ action: "restore",
1676
+ id
1677
+ }, {
1678
+ key: "notice.restored",
1679
+ params: {},
1680
+ undo: null
1681
+ }),
1682
+ children: t("action.restore")
1683
+ })] });
1684
+ const writable = !(preview?.status === "ready" && preview.value.kind === "improve" && !preview.value.writable);
1685
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
1686
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1687
+ size: "sm",
1688
+ variant: "ghost",
1689
+ disabled: busy !== null,
1690
+ onClick: () => void dismiss(false),
1691
+ children: t("action.dismiss")
1692
+ }),
1693
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1694
+ size: "sm",
1695
+ variant: "ghost",
1696
+ disabled: busy !== null,
1697
+ onClick: () => void dismiss(true),
1698
+ children: t("action.dismissSimilar")
1699
+ }),
1700
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: detail_module_css_default.spacer }),
1701
+ item.state === "ready" && create && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.SegmentedControl, {
1702
+ id: `${tabsId}-scope`,
1703
+ label: t("scope.label"),
1704
+ value: scope,
1705
+ disabled: busy !== null,
1706
+ options: [{
1707
+ value: "project",
1708
+ label: t("scope.project")
1709
+ }, {
1710
+ value: "user",
1711
+ label: t("scope.user")
1712
+ }],
1713
+ onChange: setScope
1714
+ }),
1715
+ item.state === "ready" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1716
+ size: "sm",
1717
+ variant: "primary",
1718
+ "data-autofocus": "",
1719
+ disabled: busy !== null || invalid || !writable,
1720
+ onClick: () => void save(),
1721
+ children: busy === "save" ? t("action.saving") : create ? t("action.save") : t("action.improve", { skill: skillName })
1722
+ })
1723
+ ] });
1724
+ };
1725
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Dialog, {
1726
+ title: titleOf(item),
1727
+ subtitle,
1728
+ closeLabel: t("detail.close"),
1729
+ busy: busy === "save" || busy === "undo",
1730
+ onClose,
1731
+ footer: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [error !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1732
+ className: detail_module_css_default.actionError,
1733
+ role: "alert",
1734
+ children: error
1735
+ }), footer()] }),
1736
+ children: [
1737
+ detail.status === "loading" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1738
+ className: detail_module_css_default.note,
1739
+ role: "status",
1740
+ children: t("detail.loading")
1741
+ }),
1742
+ detail.status === "error" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
1743
+ className: detail_module_css_default.failure,
1744
+ role: "alert",
1745
+ children: [
1746
+ t("detail.error"),
1747
+ " ",
1748
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1749
+ className: detail_module_css_default.failureDetail,
1750
+ children: detail.error
1751
+ })
1752
+ ]
1753
+ }),
1754
+ value !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [tabs.length > 1 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1755
+ className: detail_module_css_default.tabs,
1756
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.SegmentedControl, {
1757
+ id: tabsId,
1758
+ label: t("detail.tabs"),
1759
+ value: activeTab,
1760
+ options: tabs,
1761
+ onChange: setTab
1762
+ })
1763
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1764
+ className: detail_module_css_default.panel,
1765
+ role: tabs.length > 1 ? "tabpanel" : void 0,
1766
+ "aria-labelledby": tabs.length > 1 ? `${tabsId}-${activeTab}` : void 0,
1767
+ children: [
1768
+ activeTab === "evidence" && evidence(),
1769
+ activeTab === "skill" && skill(),
1770
+ activeTab === "changes" && changes()
1771
+ ]
1772
+ })] })
1773
+ ]
1774
+ });
1775
+ }
1776
+ const CAUTIONS = /* @__PURE__ */ new Set([
1777
+ "caution.sudo",
1778
+ "caution.force-push",
1779
+ "caution.no-verify",
1780
+ "caution.chmod-777",
1781
+ "caution.recursive-delete"
1782
+ ]);
1783
+ function isCaution(key) {
1784
+ return CAUTIONS.has(key);
1785
+ }
1786
+ //#endregion
1787
+ //#region src/client/hooks.ts
1788
+ /** 订阅 Observable 的 React Hook。 */
1789
+ function useSnapshot(source) {
1790
+ return (0, react.useSyncExternalStore)(source.subscribe, source.getSnapshot, source.getSnapshot);
1791
+ }
1792
+ //#endregion
1793
+ //#region \0dsh-css:src/client/inbox.module.css.mjs
1794
+ const css$1 = ".Wfa-jG_root{margin-left:4px;position:relative}.Wfa-jG_trigger{width:28px;height:28px;color:var(--dsw-alias-label-tertiary);cursor:pointer;background:0 0;border:0;border-radius:28px;flex:none;justify-content:center;align-items:center;padding:0;display:inline-flex;position:relative}.Wfa-jG_trigger:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:1px;color:var(--dsw-alias-label-secondary)}@media (hover:hover){.Wfa-jG_trigger:hover{color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-interactive-bg-hover)}}.Wfa-jG_trigger[aria-expanded=true]{color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-interactive-bg-hover)}.Wfa-jG_trigger svg{flex:none}.Wfa-jG_badge{box-sizing:border-box;background:var(--dsw-alias-label-secondary);min-width:14px;height:14px;color:var(--dsw-alias-bg-layer-1,var(--dsw-alias-bg-base));text-align:center;font-variant-numeric:tabular-nums;pointer-events:none;border-radius:7px;padding:0 3px;font-size:9px;font-weight:600;line-height:14px;position:absolute;top:1px;right:0}.Wfa-jG_panel{z-index:100;box-sizing:border-box;background:var(--dsw-specific-menu);width:360px;max-width:calc(100vw - 32px);max-height:min(480px,100vh - 140px);backdrop-filter:var(--dsw-menu-backdrop-filter);--dsw-elevation-stroke-color:var(--dsw-alias-border-l1);box-shadow:var(--dsw-elevation-prominent);color:var(--dsw-alias-label-primary);animation:Wfa-jG_panel-in .14s var(--ds-ease-in-out,ease);border:0;border-radius:16px;flex-direction:column;padding:4px;display:flex;position:fixed}.Wfa-jG_panel:focus{outline:none}@keyframes Wfa-jG_panel-in{0%{opacity:0;transform:translateY(-2px)}to{opacity:1;transform:none}}.Wfa-jG_head{flex:none;justify-content:space-between;align-items:center;gap:8px;padding:8px 10px 6px 12px;display:flex}.Wfa-jG_title{color:var(--dsw-alias-label-secondary);margin:0;font-size:12px;font-weight:600;line-height:18px}.Wfa-jG_working{color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:16px}.Wfa-jG_scroll{--dsh-scrollbar-thumb:var(--dsw-alias-scrollbar-bg-l2);--dsh-scrollbar-thumb-hover:var(--dsw-alias-scrollbar-hover-l2);flex-direction:column;flex:auto;gap:6px;min-height:0;display:flex;overflow-y:auto}.Wfa-jG_section{flex-direction:column;display:flex}.Wfa-jG_sectionTitle{color:var(--dsw-alias-label-tertiary);margin:0;padding:5px 12px 2px;font-size:11px;font-weight:500;line-height:16px}.Wfa-jG_list{flex-direction:column;gap:1px;margin:0;padding:0;list-style:none;display:flex}.Wfa-jG_card{border-radius:10px;align-items:flex-start;gap:4px;display:flex;position:relative}@media (hover:hover){.Wfa-jG_card:hover{background:var(--dsw-alias-interactive-bg-hover)}}.Wfa-jG_open{min-width:0;color:inherit;font:inherit;text-align:left;cursor:pointer;background:0 0;border:0;border-radius:10px;flex-direction:column;flex:1;gap:2px;padding:8px 4px 8px 12px;display:flex}.Wfa-jG_open:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:-2px}.Wfa-jG_cardTitle{-webkit-line-clamp:2;overflow-wrap:anywhere;-webkit-box-orient:vertical;font-size:13px;line-height:18px;display:-webkit-box;overflow:hidden}.Wfa-jG_card[data-state=detected] .Wfa-jG_cardTitle,.Wfa-jG_card[data-state=failed] .Wfa-jG_cardTitle,.Wfa-jG_card[data-state=reviewing] .Wfa-jG_cardTitle{color:var(--dsw-alias-label-secondary)}.Wfa-jG_cardMeta{color:var(--dsw-alias-label-tertiary);flex-wrap:wrap;gap:2px 6px;font-size:11px;line-height:16px;display:flex}.Wfa-jG_state{color:var(--dsw-alias-label-secondary)}.Wfa-jG_card[data-state=failed] .Wfa-jG_state{color:var(--dsw-alias-state-warn-label)}.Wfa-jG_dismiss{width:22px;height:22px;color:var(--dsw-alias-label-tertiary);cursor:pointer;opacity:0;background:0 0;border:0;border-radius:6px;flex:none;justify-content:center;align-items:center;margin:7px 6px 0 0;padding:0;display:inline-flex}.Wfa-jG_card:hover .Wfa-jG_dismiss,.Wfa-jG_card:focus-within .Wfa-jG_dismiss{opacity:1}.Wfa-jG_dismiss:focus-visible{opacity:1;outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:-2px}@media (hover:hover){.Wfa-jG_dismiss:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-interactive-bg-hover)}}@media (hover:none){.Wfa-jG_dismiss{opacity:1}}.Wfa-jG_historyToggle{color:var(--dsw-alias-label-tertiary);font:inherit;cursor:pointer;background:0 0;border:0;border-radius:6px;align-self:flex-start;margin:2px 0 2px 8px;padding:2px 4px;font-size:11px;line-height:16px}.Wfa-jG_historyToggle:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary)}@media (hover:hover){.Wfa-jG_historyToggle:hover{color:var(--dsw-alias-label-secondary)}}.Wfa-jG_banner{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-secondary);border-radius:10px;margin:2px 4px 4px;padding:8px 10px;font-size:12px;line-height:18px}.Wfa-jG_empty{flex-direction:column;gap:4px;padding:10px 12px 14px;display:flex}.Wfa-jG_emptyTitle{color:var(--dsw-alias-label-primary);margin:0;font-size:13px;line-height:20px}.Wfa-jG_emptyBody{color:var(--dsw-alias-label-secondary);margin:0;font-size:12px;line-height:18px}.Wfa-jG_caption{color:var(--dsw-alias-label-tertiary);margin:4px 0 0;padding:0 12px;font-size:11px;line-height:16px}.Wfa-jG_empty .Wfa-jG_caption{padding:0}.Wfa-jG_errorRow{color:var(--dsw-alias-state-warn-label);justify-content:space-between;align-items:center;gap:8px;padding:8px 12px;font-size:12px;line-height:18px;display:flex}.Wfa-jG_link{color:var(--dsw-alias-state-business-primary);font:inherit;cursor:pointer;background:0 0;border:0;padding:0;font-size:12px}.Wfa-jG_link:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:2px;border-radius:4px}.Wfa-jG_notice{border-top:.5px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);flex:none;justify-content:space-between;align-items:center;gap:8px;margin-top:4px;padding:8px 12px;font-size:12px;line-height:18px;display:flex}.Wfa-jG_noticeText{overflow-wrap:anywhere;min-width:0}@media (prefers-reduced-motion:reduce){.Wfa-jG_panel{animation:none}}";
1795
+ const tag$1 = "@agent-stash/dsh-skill-scout/inbox.module.css";
1796
+ if (document.querySelector(`style[data-plugin-css=${JSON.stringify(tag$1)}]`) === null) {
1797
+ const style = document.createElement("style");
1798
+ style.dataset.plugin = "@agent-stash/dsh-skill-scout";
1799
+ style.dataset.pluginCss = tag$1;
1800
+ style.textContent = css$1;
1801
+ document.head.appendChild(style);
1802
+ }
1803
+ var inbox_module_css_default = {
1804
+ "badge": "Wfa-jG_badge",
1805
+ "banner": "Wfa-jG_banner",
1806
+ "caption": "Wfa-jG_caption",
1807
+ "card": "Wfa-jG_card",
1808
+ "cardMeta": "Wfa-jG_cardMeta",
1809
+ "cardTitle": "Wfa-jG_cardTitle",
1810
+ "dismiss": "Wfa-jG_dismiss",
1811
+ "empty": "Wfa-jG_empty",
1812
+ "emptyBody": "Wfa-jG_emptyBody",
1813
+ "emptyTitle": "Wfa-jG_emptyTitle",
1814
+ "errorRow": "Wfa-jG_errorRow",
1815
+ "head": "Wfa-jG_head",
1816
+ "historyToggle": "Wfa-jG_historyToggle",
1817
+ "link": "Wfa-jG_link",
1818
+ "list": "Wfa-jG_list",
1819
+ "notice": "Wfa-jG_notice",
1820
+ "noticeText": "Wfa-jG_noticeText",
1821
+ "open": "Wfa-jG_open",
1822
+ "panel": "Wfa-jG_panel",
1823
+ "panel-in": "Wfa-jG_panel-in",
1824
+ "root": "Wfa-jG_root",
1825
+ "scroll": "Wfa-jG_scroll",
1826
+ "section": "Wfa-jG_section",
1827
+ "sectionTitle": "Wfa-jG_sectionTitle",
1828
+ "state": "Wfa-jG_state",
1829
+ "title": "Wfa-jG_title",
1830
+ "trigger": "Wfa-jG_trigger",
1831
+ "working": "Wfa-jG_working"
1832
+ };
1833
+ //#endregion
1834
+ //#region src/client/inbox.tsx
1835
+ /**
1836
+ * 会话标题栏里的经验提炼入口:一个图标按钮,有新建议时带一个小计数;点开是收件箱浮层,
1837
+ * 点卡片打开详情对话框。入口在第一次读取完成前不渲染,避免标题栏闪动。
1838
+ * 计数只算打开收件箱之后出现的可保存建议,看过即清零。
1839
+ */
1840
+ const SEEN_KEY = "agent-stash-skill-scout:seen";
1841
+ const SeenSchema = /* @__PURE__ */ pipe(/* @__PURE__ */ string(), /* @__PURE__ */ parseJson(), /* @__PURE__ */ array(/* @__PURE__ */ string()));
1842
+ /**
1843
+ * 以 localStorage 保存看过的建议 id,只保留最近的一部分。
1844
+ * localStorage 是页面共享的外部存储:内容损坏时当作没有记录,写不进去(无痕模式、配额已满)时只记在内存里,
1845
+ * 两种情况都只影响角标计数。
1846
+ */
1847
+ function createSeenStore(storage) {
1848
+ let memory = [];
1849
+ const read = () => {
1850
+ const raw = storage.getItem(SEEN_KEY);
1851
+ if (raw === null) return memory;
1852
+ const parsed = /* @__PURE__ */ safeParse(SeenSchema, raw);
1853
+ return parsed.success ? [.../* @__PURE__ */ new Set([...parsed.output, ...memory])] : memory;
1854
+ };
1855
+ return {
1856
+ has: (id) => read().includes(id),
1857
+ add(ids) {
1858
+ memory = [.../* @__PURE__ */ new Set([...read(), ...ids])].slice(-200);
1859
+ try {
1860
+ storage.setItem(SEEN_KEY, JSON.stringify(memory));
1861
+ } catch (error) {
1862
+ if (!(error instanceof DOMException)) throw error;
1863
+ }
1864
+ }
1865
+ };
1866
+ }
1867
+ const MEASURE_STYLE = {
1868
+ visibility: "hidden",
1869
+ left: 0,
1870
+ top: 0
1871
+ };
1872
+ /** 新建议数:可保存且还没看过。 */
1873
+ function unseen(inbox, seen) {
1874
+ if (inbox === null) return 0;
1875
+ return inbox.items.filter((item) => item.state === "ready" && !seen.has(item.id)).length;
1876
+ }
1877
+ function Card({ item, now, t, onOpen, onDismiss }) {
1878
+ const title = titleOf(item);
1879
+ const showState = item.state !== "ready";
1880
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
1881
+ className: inbox_module_css_default.card,
1882
+ "data-state": item.state,
1883
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1884
+ type: "button",
1885
+ className: inbox_module_css_default.open,
1886
+ "data-item": item.id,
1887
+ onClick: () => onOpen(item.id),
1888
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1889
+ className: inbox_module_css_default.cardTitle,
1890
+ children: title
1891
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1892
+ className: inbox_module_css_default.cardMeta,
1893
+ children: [showState && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1894
+ className: inbox_module_css_default.state,
1895
+ children: t(`state.${item.state}`)
1896
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: metaOf(t, item, now) })]
1897
+ })]
1898
+ }), onDismiss !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1899
+ type: "button",
1900
+ className: inbox_module_css_default.dismiss,
1901
+ "aria-label": `${t("action.dismiss")}: ${title}`,
1902
+ title: t("action.dismiss"),
1903
+ onClick: () => onDismiss(item.id),
1904
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCloseOutlineRegular, { size: 12 })
1905
+ })]
1906
+ });
1907
+ }
1908
+ function Banner({ inbox, t }) {
1909
+ const { status } = inbox;
1910
+ const pending = inbox.items.some((item) => item.state === "detected" || item.state === "failed");
1911
+ let key = null;
1912
+ if (!status.discovery) key = "banner.discoveryOff";
1913
+ else if (status.model === null && pending) key = "banner.noModel";
1914
+ else if (!status.lead) key = "banner.secondary";
1915
+ if (key === null) return null;
1916
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1917
+ className: inbox_module_css_default.banner,
1918
+ children: t(key)
1919
+ });
1920
+ }
1921
+ function PanelBody({ inbox, now, t, onOpen, onDismiss }) {
1922
+ const [history, setHistory] = (0, react.useState)(false);
1923
+ const groups = groupItems(inbox.items);
1924
+ if (groups.ready.length === 0 && groups.found.length === 0 && groups.history.length === 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1925
+ className: inbox_module_css_default.empty,
1926
+ children: [
1927
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Banner, {
1928
+ inbox,
1929
+ t
1930
+ }),
1931
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1932
+ className: inbox_module_css_default.emptyTitle,
1933
+ children: t("panel.empty.title")
1934
+ }),
1935
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1936
+ className: inbox_module_css_default.emptyBody,
1937
+ children: t("panel.empty.body")
1938
+ }),
1939
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1940
+ className: inbox_module_css_default.caption,
1941
+ children: t("panel.analyzed", { sessions: inbox.status.analyzed.sessions })
1942
+ })
1943
+ ]
1944
+ });
1945
+ const section = (key, items) => items.length === 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
1946
+ className: inbox_module_css_default.section,
1947
+ "aria-label": t(key),
1948
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
1949
+ className: inbox_module_css_default.sectionTitle,
1950
+ children: t(key)
1951
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
1952
+ className: inbox_module_css_default.list,
1953
+ children: items.map((item) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Card, {
1954
+ item,
1955
+ now,
1956
+ t,
1957
+ onOpen,
1958
+ onDismiss: item.state === "reviewing" ? void 0 : onDismiss
1959
+ }, item.id))
1960
+ })]
1961
+ });
1962
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
1963
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Banner, {
1964
+ inbox,
1965
+ t
1966
+ }),
1967
+ section("section.ready", groups.ready),
1968
+ section("section.found", groups.found),
1969
+ groups.history.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
1970
+ className: inbox_module_css_default.section,
1971
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1972
+ type: "button",
1973
+ className: inbox_module_css_default.historyToggle,
1974
+ "aria-expanded": history,
1975
+ onClick: () => setHistory(!history),
1976
+ children: history ? t("history.hide") : t("history.show", { count: groups.history.length })
1977
+ }), history && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
1978
+ className: inbox_module_css_default.list,
1979
+ children: groups.history.map((item) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Card, {
1980
+ item,
1981
+ now,
1982
+ t,
1983
+ onOpen
1984
+ }, item.id))
1985
+ })]
1986
+ })
1987
+ ] });
1988
+ }
1989
+ /** 标题栏入口与收件箱浮层。 */
1990
+ function InboxEntry({ inbox, seen, loadDetail, openSession, t }) {
1991
+ const state = useSnapshot(inbox);
1992
+ const [open, setOpen] = (0, react.useState)(false);
1993
+ const [now, setNow] = (0, react.useState)(() => Date.now());
1994
+ const [detailId, setDetailId] = (0, react.useState)(null);
1995
+ /** 详情关闭、收件箱重新打开后要聚焦的卡片。 */
1996
+ const [returnTo, setReturnTo] = (0, react.useState)(null);
1997
+ const [, setSeenVersion] = (0, react.useState)(0);
1998
+ const rootRef = (0, react.useRef)(null);
1999
+ const triggerRef = (0, react.useRef)(null);
2000
+ const panelRef = (0, react.useRef)(null);
2001
+ const position = (0, _deepseek_ai_dsh_client_ui_primitives.useAnchoredPosition)({
2002
+ open,
2003
+ anchorRef: triggerRef,
2004
+ panelRef,
2005
+ side: "bottom",
2006
+ gap: 5,
2007
+ margin: 16
2008
+ });
2009
+ (0, _deepseek_ai_dsh_client_ui_primitives.useDismissOnOutsidePointer)(rootRef, open && detailId === null, setOpen, panelRef);
2010
+ (0, react.useEffect)(() => {
2011
+ if (!open || returnTo === null || position === null) return;
2012
+ const panelElement = panelRef.current;
2013
+ (panelElement?.querySelector(`[data-item="${CSS.escape(returnTo)}"]`) ?? panelElement)?.focus();
2014
+ }, [
2015
+ open,
2016
+ returnTo,
2017
+ position
2018
+ ]);
2019
+ (0, react.useEffect)(() => {
2020
+ if (!open || state.inbox === null) return;
2021
+ const ready = state.inbox.items.filter((item) => item.state === "ready").map((item) => item.id);
2022
+ if (ready.some((id) => !seen.has(id))) seen.add(ready);
2023
+ }, [
2024
+ open,
2025
+ state.inbox,
2026
+ seen
2027
+ ]);
2028
+ if (state.inbox === null && state.status !== "error") return null;
2029
+ const fresh = unseen(state.inbox, seen);
2030
+ const badge = state.inbox?.status.notify === "badge" && fresh > 0;
2031
+ const label = fresh === 0 ? t("entry.label") : t(fresh === 1 ? "entry.count.one" : "entry.count.other", { count: fresh });
2032
+ const working = state.inbox !== null && (state.inbox.status.scanning || state.inbox.status.reviewing !== null);
2033
+ /** 打开收件箱:刷新状态,并把当前的可保存建议记为看过。 */
2034
+ const show = () => {
2035
+ const ready = state.inbox?.items.filter((item) => item.state === "ready").map((item) => item.id) ?? [];
2036
+ seen.add(ready);
2037
+ setSeenVersion((version) => version + 1);
2038
+ setNow(Date.now());
2039
+ setOpen(true);
2040
+ inbox.refresh();
2041
+ };
2042
+ const close = () => {
2043
+ setOpen(false);
2044
+ setReturnTo(null);
2045
+ setSeenVersion((version) => version + 1);
2046
+ triggerRef.current?.focus();
2047
+ };
2048
+ const onKeyDown = (event) => {
2049
+ if (event.key !== "Escape" || !open || detailId !== null) return;
2050
+ event.preventDefault();
2051
+ close();
2052
+ };
2053
+ const openDetail = (id) => {
2054
+ setOpen(false);
2055
+ setDetailId(id);
2056
+ };
2057
+ const closeDetail = () => {
2058
+ setReturnTo(detailId);
2059
+ setDetailId(null);
2060
+ show();
2061
+ };
2062
+ const dismiss = (id) => {
2063
+ inbox.act({
2064
+ action: "dismiss",
2065
+ id,
2066
+ similar: false
2067
+ }, {
2068
+ key: "notice.dismissed",
2069
+ params: {},
2070
+ undo: {
2071
+ action: "restore",
2072
+ id
2073
+ }
2074
+ });
2075
+ };
2076
+ const detailItem = detailId === null ? void 0 : state.inbox?.items.find((item) => item.id === detailId);
2077
+ const notice = state.notice;
2078
+ const panel = open ? (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2079
+ ref: panelRef,
2080
+ className: inbox_module_css_default.panel,
2081
+ style: position ?? MEASURE_STYLE,
2082
+ role: "dialog",
2083
+ "aria-label": t("panel.title"),
2084
+ tabIndex: -1,
2085
+ onKeyDown,
2086
+ children: [
2087
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2088
+ className: inbox_module_css_default.head,
2089
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
2090
+ className: inbox_module_css_default.title,
2091
+ children: t("panel.title")
2092
+ }), working && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2093
+ className: inbox_module_css_default.working,
2094
+ role: "status",
2095
+ children: state.inbox?.status.reviewing === null ? t("panel.analyzing") : t("panel.reviewing")
2096
+ })]
2097
+ }),
2098
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2099
+ className: inbox_module_css_default.scroll,
2100
+ children: [
2101
+ state.status === "error" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2102
+ className: inbox_module_css_default.errorRow,
2103
+ role: "alert",
2104
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("panel.error") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2105
+ type: "button",
2106
+ className: inbox_module_css_default.link,
2107
+ onClick: () => void inbox.refresh(),
2108
+ children: t("panel.retry")
2109
+ })]
2110
+ }),
2111
+ state.inbox === null && state.status === "loading" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2112
+ className: inbox_module_css_default.caption,
2113
+ role: "status",
2114
+ children: t("panel.loading")
2115
+ }),
2116
+ state.inbox !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PanelBody, {
2117
+ inbox: state.inbox,
2118
+ now,
2119
+ t,
2120
+ onOpen: openDetail,
2121
+ onDismiss: dismiss
2122
+ })
2123
+ ]
2124
+ }),
2125
+ notice !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2126
+ className: inbox_module_css_default.notice,
2127
+ role: "status",
2128
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2129
+ className: inbox_module_css_default.noticeText,
2130
+ children: t(notice.key, notice.params)
2131
+ }), notice.undo !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2132
+ type: "button",
2133
+ className: inbox_module_css_default.link,
2134
+ onClick: () => {
2135
+ const action = notice.undo;
2136
+ inbox.clearNotice();
2137
+ if (action !== null) inbox.act(action);
2138
+ },
2139
+ children: t("notice.undo")
2140
+ })]
2141
+ })
2142
+ ]
2143
+ }), document.body) : null;
2144
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2145
+ ref: rootRef,
2146
+ className: inbox_module_css_default.root,
2147
+ onKeyDown,
2148
+ children: [
2149
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
2150
+ ref: triggerRef,
2151
+ type: "button",
2152
+ className: inbox_module_css_default.trigger,
2153
+ "aria-label": label,
2154
+ "aria-expanded": open,
2155
+ "aria-haspopup": "dialog",
2156
+ title: t("entry.label"),
2157
+ onClick: () => open ? close() : show(),
2158
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconSkillOutlineRegular, { size: 16 }), badge && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2159
+ className: inbox_module_css_default.badge,
2160
+ "aria-hidden": "true",
2161
+ children: fresh > 9 ? "9+" : fresh
2162
+ })]
2163
+ }),
2164
+ panel,
2165
+ detailId !== null && detailItem !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Detail, {
2166
+ id: detailId,
2167
+ item: detailItem,
2168
+ inbox,
2169
+ loadDetail,
2170
+ modelName: state.inbox?.status.model?.model ?? null,
2171
+ defaultScope: state.inbox?.status.scope ?? "project",
2172
+ openSession,
2173
+ t,
2174
+ onClose: closeDetail
2175
+ })
2176
+ ]
2177
+ });
2178
+ }
2179
+ //#endregion
2180
+ //#region src/client/locale.ts
2181
+ const SCOUT_LOCALE = "agent-stash-skill-scout";
2182
+ /** 两种语言的字典。 */
2183
+ const SCOUT_DICTIONARIES = {
2184
+ en: {
2185
+ "entry.label": "Skill suggestions",
2186
+ "entry.count.one": "Skill suggestions, {count} new",
2187
+ "entry.count.other": "Skill suggestions, {count} new",
2188
+ "panel.title": "Skill suggestions",
2189
+ "panel.analyzing": "Analyzing sessions",
2190
+ "panel.reviewing": "Reviewing",
2191
+ "panel.loading": "Loading",
2192
+ "panel.error": "Could not load suggestions.",
2193
+ "panel.retry": "Retry",
2194
+ "panel.empty.title": "No suggestions",
2195
+ "panel.empty.body": "A suggestion appears when a workflow repeats across sessions or a fix passes a check.",
2196
+ "panel.analyzed": "{sessions} sessions analyzed",
2197
+ "section.ready": "Ready to save",
2198
+ "section.found": "Found locally",
2199
+ "history.show": "History ({count})",
2200
+ "history.hide": "Hide history",
2201
+ "banner.noModel": "No review model is available. Choose one in Skill Scout settings.",
2202
+ "banner.reviewOff": "Background review is off. Open a finding to review it.",
2203
+ "banner.discoveryOff": "Session analysis is off.",
2204
+ "banner.secondary": "Another DSH window is analyzing sessions.",
2205
+ "notice.dismissed": "Dismissed",
2206
+ "notice.dismissedSimilar": "Dismissed this and similar suggestions",
2207
+ "notice.restored": "Restored",
2208
+ "notice.written": "Saved {skill}",
2209
+ "notice.undone": "Undid the change to {skill}",
2210
+ "notice.undo": "Undo",
2211
+ "kind.repeated": "Repeated workflow",
2212
+ "kind.recovery": "Verified fix",
2213
+ "kind.complex": "Long verified task",
2214
+ "meta.sessions.one": "{count} session",
2215
+ "meta.sessions.other": "{count} sessions",
2216
+ "state.detected": "Not reviewed",
2217
+ "state.reviewing": "Reviewing",
2218
+ "state.ready": "Ready",
2219
+ "state.skipped": "Not worth saving",
2220
+ "state.failed": "Review failed",
2221
+ "state.dismissed": "Dismissed",
2222
+ "state.written": "Saved",
2223
+ "time.now": "just now",
2224
+ "time.minutes": "{count} min ago",
2225
+ "time.hours": "{count} h ago",
2226
+ "time.days": "{count} d ago",
2227
+ "detail.close": "Close",
2228
+ "detail.tab.evidence": "Evidence",
2229
+ "detail.tab.skill": "Skill",
2230
+ "detail.tab.changes": "Changes",
2231
+ "detail.tabs": "Suggestion sections",
2232
+ "detail.why": "Why",
2233
+ "detail.loading": "Loading the evidence",
2234
+ "detail.error": "Could not load this suggestion.",
2235
+ "detail.reviewedBy": "Reviewed by {model}",
2236
+ "detail.local": "Found by local rules from tool results. Not reviewed yet.",
2237
+ "detail.reviewNote": "Review sends a redacted summary of {count} tasks to {model}: requests, commands, exit codes, and one error line per failed command.",
2238
+ "detail.review": "Review with model",
2239
+ "detail.reviewing": "Reviewing with {model}",
2240
+ "detail.noModel": "No review model is available.",
2241
+ "detail.skipped": "The review found nothing worth saving.",
2242
+ "detail.failed": "Review failed: {kind}",
2243
+ "detail.retry": "Review again",
2244
+ "detail.openSession": "Open session",
2245
+ "detail.cautions": "Check before saving: {list}",
2246
+ "failure.timeout": "timed out",
2247
+ "failure.model": "model error",
2248
+ "failure.format": "the reply did not match the expected format",
2249
+ "failure.evidence": "the reply was not supported by the evidence",
2250
+ "failure.unsafe": "the reply contained unsafe content",
2251
+ "outcome.verified": "Check passed",
2252
+ "outcome.succeeded": "Succeeded",
2253
+ "outcome.unverified": "Unverified",
2254
+ "outcome.disputed": "User disagreed",
2255
+ "outcome.failed": "Failed",
2256
+ "outcome.aborted": "Cancelled",
2257
+ "role.verify": "check passed",
2258
+ "role.failure": "failed",
2259
+ "role.fix": "fix",
2260
+ "role.recovered": "passed after fix",
2261
+ "role.cited": "cited",
2262
+ "evidence.reads.one": "{count} read not shown",
2263
+ "evidence.reads.other": "{count} reads not shown",
2264
+ "evidence.exit": "exit {code}",
2265
+ "evidence.followup": "Next message",
2266
+ "evidence.list": "Operations in task {ref}",
2267
+ "caution.sudo": "uses sudo",
2268
+ "caution.force-push": "force-pushes",
2269
+ "caution.no-verify": "skips hooks",
2270
+ "caution.chmod-777": "chmod 777",
2271
+ "caution.recursive-delete": "deletes recursively",
2272
+ "skill.name": "Name",
2273
+ "skill.description": "Description",
2274
+ "skill.whenToUse": "When to use",
2275
+ "skill.body": "Instructions",
2276
+ "skill.edit": "Edit",
2277
+ "skill.preview": "Preview",
2278
+ "skill.reset": "Reset",
2279
+ "skill.invalid": "Name must be lowercase words joined by hyphens; description and instructions are required.",
2280
+ "skill.section": "Section added to {skill}",
2281
+ "skill.notWritable": "{skill} is not in a project or user Skill folder, so Skill Scout cannot change it.",
2282
+ "changes.loading": "Preparing the file",
2283
+ "changes.error": "Could not prepare the file: {error}",
2284
+ "changes.exists": "A file already exists at this path.",
2285
+ "scope.label": "Save to",
2286
+ "scope.project": "Project",
2287
+ "scope.user": "User",
2288
+ "action.save": "Save Skill",
2289
+ "action.improve": "Add to {skill}",
2290
+ "action.saving": "Saving",
2291
+ "action.dismiss": "Dismiss",
2292
+ "action.dismissSimilar": "Dismiss similar",
2293
+ "action.restore": "Restore",
2294
+ "action.undo": "Undo",
2295
+ "action.done": "Done",
2296
+ "written.path": "Saved to {path}",
2297
+ "written.discovered": "DSH lists the Skill.",
2298
+ "written.checking": "Waiting for DSH to list the Skill.",
2299
+ "written.missing": "DSH does not list the Skill yet. Check the file.",
2300
+ "error.invalid": "This action does not apply to the suggestion in its current state.",
2301
+ "error.missing": "The suggestion no longer exists.",
2302
+ "error.busy": "Another review is running. Try again when it finishes.",
2303
+ "error.no-model": "No review model is available.",
2304
+ "error.exists": "A Skill file already exists at that path.",
2305
+ "error.changed": "The file changed after the preview. Nothing was written.",
2306
+ "error.outside": "The target is outside the project and user Skill folders.",
2307
+ "error.unsafe": "The content contains a credential. Remove it and try again.",
2308
+ "error.request": "The request failed: {error}",
2309
+ "code.copy": "Copy",
2310
+ "code.copied": "Copied",
2311
+ "code.label": "Code",
2312
+ "code.wrap": "Wrap lines",
2313
+ "code.unwrap": "Do not wrap lines",
2314
+ "code.footnotes": "Footnotes",
2315
+ "diff.collapse": "Collapse",
2316
+ "diff.collapseAria": "Collapse the diff",
2317
+ "diff.expand": "Show {count} more lines",
2318
+ "settings.summary": "Suggests Skills from workflows that worked in finished sessions.",
2319
+ "settings.unavailable": "Settings are not available.",
2320
+ "settings.readOnly": "This profile is read-only.",
2321
+ "settings.saveFailed": "Could not save. The settings changed elsewhere or the write was refused.",
2322
+ "settings.save": "Save",
2323
+ "settings.saving": "Saving",
2324
+ "settings.section.discovery": "Discovery",
2325
+ "settings.section.review": "Model review",
2326
+ "settings.section.limits": "Limits",
2327
+ "settings.section.privacy": "Privacy",
2328
+ "settings.section.inbox": "Inbox",
2329
+ "settings.discovery": "Analyze finished sessions",
2330
+ "settings.discoveryHint": "Runs on this machine and reads only the session logs DSH already keeps.",
2331
+ "settings.review": "Review in the background",
2332
+ "settings.reviewHint": "Sends redacted evidence of strong findings to the review model. When off, a review runs only when you ask for one.",
2333
+ "settings.model": "Review model",
2334
+ "settings.modelHint": "The default follows the Agent's default model.",
2335
+ "settings.modelDefault": "Default ({model})",
2336
+ "settings.modelDefaultUnknown": "Default model",
2337
+ "settings.modelLoading": "Loading models",
2338
+ "settings.modelError": "Could not load the model list.",
2339
+ "settings.modelMissing": "{model} (not available)",
2340
+ "settings.effort": "Reasoning effort",
2341
+ "settings.effortHint": "Lower effort is cheaper and faster.",
2342
+ "settings.effortDefault": "Model default",
2343
+ "settings.timeout": "Timeout",
2344
+ "settings.timeoutHint": "Seconds before a review is abandoned.",
2345
+ "settings.dailyReviews": "Reviews per day",
2346
+ "settings.dailyReviewsHint": "Background reviews allowed each day.",
2347
+ "settings.reviewInterval": "Review interval",
2348
+ "settings.reviewIntervalHint": "Minutes between background reviews.",
2349
+ "settings.quietPeriod": "Quiet period",
2350
+ "settings.quietPeriodHint": "Minutes a session stays idle before it is analyzed.",
2351
+ "settings.historyDays": "History",
2352
+ "settings.historyDaysHint": "Days of past sessions to analyze.",
2353
+ "settings.maxSessions": "Startup scan",
2354
+ "settings.maxSessionsHint": "Past sessions read when DSH starts.",
2355
+ "settings.minOccurrences": "Repeats needed",
2356
+ "settings.minOccurrencesHint": "Independent sessions before a repeated workflow counts.",
2357
+ "settings.evidenceBudget": "Evidence budget",
2358
+ "settings.evidenceBudgetHint": "Characters of evidence sent with one review.",
2359
+ "settings.readOutput": "Include error lines",
2360
+ "settings.readOutputHint": "Keep one redacted line from each failed command as evidence.",
2361
+ "settings.redactPatterns": "Extra redaction",
2362
+ "settings.redactPatternsHint": "One regular expression per line. Matches are replaced before anything is stored or sent.",
2363
+ "settings.redactInvalid": "Line {line} is not a valid regular expression.",
2364
+ "settings.notify": "Header badge",
2365
+ "settings.notifyHint": "Show the number of new suggestions on the header icon.",
2366
+ "settings.notify.badge": "Count",
2367
+ "settings.notify.quiet": "Icon only",
2368
+ "settings.scope": "New Skills go to",
2369
+ "settings.scopeHint": "Project: .agents/skills in the repository. User: ~/.agents/skills.",
2370
+ "settings.prompt": "Review guidance",
2371
+ "settings.promptHint": "Replaces the built-in guidance. The reply format is always added.",
2372
+ "settings.promptPlaceholder": "Built-in guidance",
2373
+ "unit.seconds": "s",
2374
+ "unit.minutes": "min",
2375
+ "unit.days": "days",
2376
+ "unit.sessions": "sessions",
2377
+ "unit.characters": "chars"
2378
+ },
2379
+ zh: {
2380
+ "entry.label": "Skill 建议",
2381
+ "entry.count.one": "Skill 建议,{count} 条新建议",
2382
+ "entry.count.other": "Skill 建议,{count} 条新建议",
2383
+ "panel.title": "Skill 建议",
2384
+ "panel.analyzing": "正在分析会话",
2385
+ "panel.reviewing": "正在复核",
2386
+ "panel.loading": "正在加载",
2387
+ "panel.error": "无法加载建议。",
2388
+ "panel.retry": "重试",
2389
+ "panel.empty.title": "暂无建议",
2390
+ "panel.empty.body": "同一流程在多个会话里重复出现,或某次修复经检查命令验证后,这里会出现建议。",
2391
+ "panel.analyzed": "已分析 {sessions} 个会话",
2392
+ "section.ready": "可以保存",
2393
+ "section.found": "本地发现",
2394
+ "history.show": "历史({count})",
2395
+ "history.hide": "收起历史",
2396
+ "banner.noModel": "没有可用的复核模型,请在经验提炼的设置里选择。",
2397
+ "banner.reviewOff": "后台复核已关闭,打开一条发现即可手动复核。",
2398
+ "banner.discoveryOff": "会话分析已关闭。",
2399
+ "banner.secondary": "另一个 DSH 窗口正在分析会话。",
2400
+ "notice.dismissed": "已忽略",
2401
+ "notice.dismissedSimilar": "已忽略这条及类似建议",
2402
+ "notice.restored": "已恢复",
2403
+ "notice.written": "已保存 {skill}",
2404
+ "notice.undone": "已撤销对 {skill} 的改动",
2405
+ "notice.undo": "撤销",
2406
+ "kind.repeated": "重复流程",
2407
+ "kind.recovery": "已验证的修复",
2408
+ "kind.complex": "经验证的长任务",
2409
+ "meta.sessions.one": "{count} 个会话",
2410
+ "meta.sessions.other": "{count} 个会话",
2411
+ "state.detected": "未复核",
2412
+ "state.reviewing": "复核中",
2413
+ "state.ready": "可以保存",
2414
+ "state.skipped": "不值得保存",
2415
+ "state.failed": "复核失败",
2416
+ "state.dismissed": "已忽略",
2417
+ "state.written": "已保存",
2418
+ "time.now": "刚刚",
2419
+ "time.minutes": "{count} 分钟前",
2420
+ "time.hours": "{count} 小时前",
2421
+ "time.days": "{count} 天前",
2422
+ "detail.close": "关闭",
2423
+ "detail.tab.evidence": "证据",
2424
+ "detail.tab.skill": "Skill",
2425
+ "detail.tab.changes": "改动",
2426
+ "detail.tabs": "建议的内容",
2427
+ "detail.why": "理由",
2428
+ "detail.loading": "正在加载证据",
2429
+ "detail.error": "无法加载这条建议。",
2430
+ "detail.reviewedBy": "由 {model} 复核",
2431
+ "detail.local": "由本地规则依据工具结果发现,尚未复核。",
2432
+ "detail.reviewNote": "复核会把 {count} 个任务的脱敏摘要发给 {model}:用户请求、命令、退出码,以及每条失败命令的一行错误输出。",
2433
+ "detail.review": "用模型复核",
2434
+ "detail.reviewing": "{model} 正在复核",
2435
+ "detail.noModel": "没有可用的复核模型。",
2436
+ "detail.skipped": "复核认为没有值得保存的内容。",
2437
+ "detail.failed": "复核失败:{kind}",
2438
+ "detail.retry": "重新复核",
2439
+ "detail.openSession": "打开会话",
2440
+ "detail.cautions": "保存前请确认:{list}",
2441
+ "failure.timeout": "超时",
2442
+ "failure.model": "模型出错",
2443
+ "failure.format": "回复格式不符合约定",
2444
+ "failure.evidence": "回复没有证据支持",
2445
+ "failure.unsafe": "回复含有不安全的内容",
2446
+ "outcome.verified": "检查通过",
2447
+ "outcome.succeeded": "已完成",
2448
+ "outcome.unverified": "未验证",
2449
+ "outcome.disputed": "用户表示不对",
2450
+ "outcome.failed": "失败",
2451
+ "outcome.aborted": "已取消",
2452
+ "role.verify": "检查通过",
2453
+ "role.failure": "失败",
2454
+ "role.fix": "修复",
2455
+ "role.recovered": "修复后通过",
2456
+ "role.cited": "被引用",
2457
+ "evidence.reads.one": "另有 {count} 次读取未列出",
2458
+ "evidence.reads.other": "另有 {count} 次读取未列出",
2459
+ "evidence.exit": "退出码 {code}",
2460
+ "evidence.followup": "下一条消息",
2461
+ "evidence.list": "任务 {ref} 的操作",
2462
+ "caution.sudo": "使用 sudo",
2463
+ "caution.force-push": "强制推送",
2464
+ "caution.no-verify": "跳过钩子",
2465
+ "caution.chmod-777": "chmod 777",
2466
+ "caution.recursive-delete": "递归删除",
2467
+ "skill.name": "名称",
2468
+ "skill.description": "描述",
2469
+ "skill.whenToUse": "适用场景",
2470
+ "skill.body": "内容",
2471
+ "skill.edit": "编辑",
2472
+ "skill.preview": "预览",
2473
+ "skill.reset": "还原",
2474
+ "skill.invalid": "名称只能是用连字符连接的小写单词;描述与内容不能为空。",
2475
+ "skill.section": "追加到 {skill} 的内容",
2476
+ "skill.notWritable": "{skill} 不在项目或用户的 Skill 目录里,经验提炼无法修改它。",
2477
+ "changes.loading": "正在生成文件",
2478
+ "changes.error": "无法生成文件:{error}",
2479
+ "changes.exists": "这个路径已经有文件。",
2480
+ "scope.label": "保存到",
2481
+ "scope.project": "项目",
2482
+ "scope.user": "用户",
2483
+ "action.save": "保存 Skill",
2484
+ "action.improve": "追加到 {skill}",
2485
+ "action.saving": "正在保存",
2486
+ "action.dismiss": "忽略",
2487
+ "action.dismissSimilar": "忽略类似建议",
2488
+ "action.restore": "恢复",
2489
+ "action.undo": "撤销",
2490
+ "action.done": "完成",
2491
+ "written.path": "已保存到 {path}",
2492
+ "written.discovered": "DSH 已列出这个 Skill。",
2493
+ "written.checking": "正在等 DSH 列出这个 Skill。",
2494
+ "written.missing": "DSH 还没有列出这个 Skill,请检查文件。",
2495
+ "error.invalid": "这条建议当前的状态不支持这个操作。",
2496
+ "error.missing": "这条建议已不存在。",
2497
+ "error.busy": "另一条建议正在复核,完成后再试。",
2498
+ "error.no-model": "没有可用的复核模型。",
2499
+ "error.exists": "这个路径已经有 Skill 文件。",
2500
+ "error.changed": "预览之后文件被改过,没有写入任何内容。",
2501
+ "error.outside": "目标不在项目或用户的 Skill 目录里。",
2502
+ "error.unsafe": "内容里有凭据,删除后再试。",
2503
+ "error.request": "请求失败:{error}",
2504
+ "code.copy": "复制",
2505
+ "code.copied": "已复制",
2506
+ "code.label": "代码",
2507
+ "code.wrap": "自动换行",
2508
+ "code.unwrap": "不换行",
2509
+ "code.footnotes": "脚注",
2510
+ "diff.collapse": "收起",
2511
+ "diff.collapseAria": "收起改动",
2512
+ "diff.expand": "展开其余 {count} 行",
2513
+ "settings.summary": "从已结束会话里做成过的流程中提炼 Skill 建议。",
2514
+ "settings.unavailable": "设置不可用。",
2515
+ "settings.readOnly": "当前 profile 是只读的。",
2516
+ "settings.saveFailed": "保存失败:设置已在别处改动,或写入被拒绝。",
2517
+ "settings.save": "保存",
2518
+ "settings.saving": "正在保存",
2519
+ "settings.section.discovery": "发现",
2520
+ "settings.section.review": "模型复核",
2521
+ "settings.section.limits": "限额",
2522
+ "settings.section.privacy": "隐私",
2523
+ "settings.section.inbox": "收件箱",
2524
+ "settings.discovery": "分析已结束的会话",
2525
+ "settings.discoveryHint": "在本机运行,只读取 DSH 已保存的会话日志。",
2526
+ "settings.review": "后台复核",
2527
+ "settings.reviewHint": "把较强发现的脱敏证据发给复核模型。关闭时只在你手动要求时复核。",
2528
+ "settings.model": "复核模型",
2529
+ "settings.modelHint": "默认跟随 Agent 的默认模型。",
2530
+ "settings.modelDefault": "默认({model})",
2531
+ "settings.modelDefaultUnknown": "默认模型",
2532
+ "settings.modelLoading": "正在加载模型",
2533
+ "settings.modelError": "无法加载模型列表。",
2534
+ "settings.modelMissing": "{model}(不可用)",
2535
+ "settings.effort": "推理强度",
2536
+ "settings.effortHint": "强度越低,越便宜也越快。",
2537
+ "settings.effortDefault": "模型默认",
2538
+ "settings.timeout": "超时",
2539
+ "settings.timeoutHint": "单次复核等待的秒数。",
2540
+ "settings.dailyReviews": "每日复核次数",
2541
+ "settings.dailyReviewsHint": "每天允许的后台复核次数。",
2542
+ "settings.reviewInterval": "复核间隔",
2543
+ "settings.reviewIntervalHint": "两次后台复核之间的分钟数。",
2544
+ "settings.quietPeriod": "静默期",
2545
+ "settings.quietPeriodHint": "会话空闲多少分钟后开始分析。",
2546
+ "settings.historyDays": "历史范围",
2547
+ "settings.historyDaysHint": "分析最近多少天的会话。",
2548
+ "settings.maxSessions": "启动时扫描",
2549
+ "settings.maxSessionsHint": "DSH 启动时读取的历史会话数。",
2550
+ "settings.minOccurrences": "重复次数",
2551
+ "settings.minOccurrencesHint": "重复流程至少要出现在多少个独立会话里。",
2552
+ "settings.evidenceBudget": "证据上限",
2553
+ "settings.evidenceBudgetHint": "单次复核发送的证据字符数。",
2554
+ "settings.readOutput": "包含错误行",
2555
+ "settings.readOutputHint": "每条失败命令保留一行脱敏后的输出作为证据。",
2556
+ "settings.redactPatterns": "追加脱敏规则",
2557
+ "settings.redactPatternsHint": "每行一个正则表达式,匹配内容在存储与发送前被替换。",
2558
+ "settings.redactInvalid": "第 {line} 行不是有效的正则表达式。",
2559
+ "settings.notify": "标题栏角标",
2560
+ "settings.notifyHint": "在标题栏图标上显示新建议的数量。",
2561
+ "settings.notify.badge": "显示数量",
2562
+ "settings.notify.quiet": "只显示图标",
2563
+ "settings.scope": "新 Skill 保存到",
2564
+ "settings.scopeHint": "项目:仓库里的 .agents/skills。用户:~/.agents/skills。",
2565
+ "settings.prompt": "复核要求",
2566
+ "settings.promptHint": "替换内置的判断标准,回复格式的约定始终保留。",
2567
+ "settings.promptPlaceholder": "内置的判断标准",
2568
+ "unit.seconds": "秒",
2569
+ "unit.minutes": "分钟",
2570
+ "unit.days": "天",
2571
+ "unit.sessions": "个",
2572
+ "unit.characters": "字符"
2573
+ }
2574
+ };
2575
+ /** 注册两种语言的字典,返回注销函数。 */
2576
+ function registerLocale(locale) {
2577
+ const disposers = Object.entries(SCOUT_DICTIONARIES).map(([language, dictionary]) => locale.register(SCOUT_LOCALE, language, { ...dictionary }));
2578
+ return () => {
2579
+ for (const dispose of disposers) dispose();
2580
+ };
2581
+ }
2582
+ //#endregion
2583
+ //#region \0dsh-css:src/client/settings.module.css.mjs
2584
+ const css = ".TurwOW_page{flex-direction:column;gap:20px;display:flex}.TurwOW_section{flex-direction:column;display:flex}.TurwOW_title{color:var(--dsw-alias-label-secondary);margin:0 0 4px;font-size:12px;font-weight:600;line-height:18px}.TurwOW_field{border-bottom:.5px solid var(--dsw-alias-border-l2);justify-content:space-between;align-items:center;gap:16px;min-height:48px;padding:8px 0;display:flex}.TurwOW_section .TurwOW_field:last-child{border-bottom:0}.TurwOW_wide{flex-direction:column;align-items:stretch;gap:8px}.TurwOW_text{flex-direction:column;gap:2px;min-width:0;display:flex}.TurwOW_label{color:var(--dsw-alias-label-primary);font-size:13px;line-height:20px}.TurwOW_hint{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px}.TurwOW_control{flex-direction:column;flex:none;align-items:flex-end;gap:4px;max-width:60%;display:flex}.TurwOW_wide .TurwOW_control{align-items:stretch;max-width:none}.TurwOW_input,.TurwOW_select{box-sizing:border-box;border-radius:var(--dsw-radius-md);background:var(--dsw-alias-bg-layer-1,var(--dsw-alias-bg-base));min-height:28px;box-shadow:0 0 0 .5px var(--dsw-alias-border-l2);color:var(--dsw-alias-label-primary);font:inherit;border:0;padding:4px 8px;font-size:13px;line-height:20px}.TurwOW_select{max-width:260px}.TurwOW_input:focus-visible,.TurwOW_select:focus-visible{box-shadow:0 0 0 1.5px var(--dsw-alias-state-business-primary);outline:none}.TurwOW_input[aria-invalid=true]{box-shadow:0 0 0 1px var(--dsw-alias-state-warn-label)}.TurwOW_input:disabled,.TurwOW_select:disabled{opacity:.5}.TurwOW_number{align-items:center;gap:6px;display:inline-flex}.TurwOW_number .TurwOW_input{text-align:right;font-variant-numeric:tabular-nums;width:92px}.TurwOW_unit{min-width:40px;color:var(--dsw-alias-label-tertiary);font-size:12px}.TurwOW_area{resize:vertical;width:100%}.TurwOW_mono{font-family:var(--dsw-font-family-mono,ui-monospace, monospace);font-size:12px}.TurwOW_status{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px}.TurwOW_status[role=alert]{color:var(--dsw-alias-state-warn-label)}";
2585
+ const tag = "@agent-stash/dsh-skill-scout/settings.module.css";
2586
+ if (document.querySelector(`style[data-plugin-css=${JSON.stringify(tag)}]`) === null) {
2587
+ const style = document.createElement("style");
2588
+ style.dataset.plugin = "@agent-stash/dsh-skill-scout";
2589
+ style.dataset.pluginCss = tag;
2590
+ style.textContent = css;
2591
+ document.head.appendChild(style);
2592
+ }
2593
+ var settings_module_css_default = {
2594
+ "area": "TurwOW_area",
2595
+ "control": "TurwOW_control",
2596
+ "field": "TurwOW_field",
2597
+ "hint": "TurwOW_hint",
2598
+ "input": "TurwOW_input",
2599
+ "label": "TurwOW_label",
2600
+ "mono": "TurwOW_mono",
2601
+ "number": "TurwOW_number",
2602
+ "page": "TurwOW_page",
2603
+ "section": "TurwOW_section",
2604
+ "select": "TurwOW_select",
2605
+ "status": "TurwOW_status",
2606
+ "text": "TurwOW_text",
2607
+ "title": "TurwOW_title",
2608
+ "unit": "TurwOW_unit",
2609
+ "wide": "TurwOW_wide"
2610
+ };
2611
+ //#endregion
2612
+ //#region src/client/settings.tsx
2613
+ /**
2614
+ * 插件页里经验提炼条目的配置页。改动先暂存,点保存才按读到的版本号写回 profile;
2615
+ * 写入被拒绝或传输失败时保留暂存值并提示。复核模型从 Host 的模型目录里选,
2616
+ * 留空时跟随 Agent 的默认模型。
2617
+ */
2618
+ /** 与 Host `Config` 相同的默认值。 */
2619
+ const DEFAULT_SETTINGS = {
2620
+ discovery: true,
2621
+ review: false,
2622
+ provider: "",
2623
+ model: "",
2624
+ effort: "",
2625
+ timeout: 90,
2626
+ dailyReviews: 6,
2627
+ reviewInterval: 10,
2628
+ quietPeriod: 2,
2629
+ historyDays: 30,
2630
+ maxSessions: 200,
2631
+ minOccurrences: 2,
2632
+ evidenceBudget: 24e3,
2633
+ readOutput: true,
2634
+ redactPatterns: [],
2635
+ notify: "badge",
2636
+ scope: "project",
2637
+ prompt: ""
2638
+ };
2639
+ /** 数值字段的范围与单位,与 Host `Config` 一致。 */
2640
+ const NUMBERS = {
2641
+ timeout: {
2642
+ min: 10,
2643
+ max: 600,
2644
+ unit: "unit.seconds"
2645
+ },
2646
+ dailyReviews: {
2647
+ min: 1,
2648
+ max: 100,
2649
+ unit: "unit.sessions"
2650
+ },
2651
+ reviewInterval: {
2652
+ min: 1,
2653
+ max: 1440,
2654
+ unit: "unit.minutes"
2655
+ },
2656
+ quietPeriod: {
2657
+ min: 0,
2658
+ max: 120,
2659
+ unit: "unit.minutes"
2660
+ },
2661
+ historyDays: {
2662
+ min: 1,
2663
+ max: 365,
2664
+ unit: "unit.days"
2665
+ },
2666
+ maxSessions: {
2667
+ min: 1,
2668
+ max: 2e3,
2669
+ unit: "unit.sessions"
2670
+ },
2671
+ minOccurrences: {
2672
+ min: 2,
2673
+ max: 10,
2674
+ unit: "unit.sessions"
2675
+ },
2676
+ evidenceBudget: {
2677
+ min: 4e3,
2678
+ max: 2e5,
2679
+ unit: "unit.characters"
2680
+ }
2681
+ };
2682
+ /** 设置服务快照值的结构;缺失或类型不符的字段取默认值。 */
2683
+ const SettingsSchema = /* @__PURE__ */ object({
2684
+ discovery: /* @__PURE__ */ fallback(/* @__PURE__ */ boolean(), DEFAULT_SETTINGS.discovery),
2685
+ review: /* @__PURE__ */ fallback(/* @__PURE__ */ boolean(), DEFAULT_SETTINGS.review),
2686
+ provider: /* @__PURE__ */ fallback(/* @__PURE__ */ string(), DEFAULT_SETTINGS.provider),
2687
+ model: /* @__PURE__ */ fallback(/* @__PURE__ */ string(), DEFAULT_SETTINGS.model),
2688
+ effort: /* @__PURE__ */ fallback(/* @__PURE__ */ string(), DEFAULT_SETTINGS.effort),
2689
+ timeout: /* @__PURE__ */ fallback(/* @__PURE__ */ number(), DEFAULT_SETTINGS.timeout),
2690
+ dailyReviews: /* @__PURE__ */ fallback(/* @__PURE__ */ number(), DEFAULT_SETTINGS.dailyReviews),
2691
+ reviewInterval: /* @__PURE__ */ fallback(/* @__PURE__ */ number(), DEFAULT_SETTINGS.reviewInterval),
2692
+ quietPeriod: /* @__PURE__ */ fallback(/* @__PURE__ */ number(), DEFAULT_SETTINGS.quietPeriod),
2693
+ historyDays: /* @__PURE__ */ fallback(/* @__PURE__ */ number(), DEFAULT_SETTINGS.historyDays),
2694
+ maxSessions: /* @__PURE__ */ fallback(/* @__PURE__ */ number(), DEFAULT_SETTINGS.maxSessions),
2695
+ minOccurrences: /* @__PURE__ */ fallback(/* @__PURE__ */ number(), DEFAULT_SETTINGS.minOccurrences),
2696
+ evidenceBudget: /* @__PURE__ */ fallback(/* @__PURE__ */ number(), DEFAULT_SETTINGS.evidenceBudget),
2697
+ readOutput: /* @__PURE__ */ fallback(/* @__PURE__ */ boolean(), DEFAULT_SETTINGS.readOutput),
2698
+ redactPatterns: /* @__PURE__ */ fallback(/* @__PURE__ */ array(/* @__PURE__ */ string()), [...DEFAULT_SETTINGS.redactPatterns]),
2699
+ notify: /* @__PURE__ */ fallback(/* @__PURE__ */ picklist(["badge", "quiet"]), DEFAULT_SETTINGS.notify),
2700
+ scope: /* @__PURE__ */ fallback(/* @__PURE__ */ picklist(["project", "user"]), DEFAULT_SETTINGS.scope),
2701
+ prompt: /* @__PURE__ */ fallback(/* @__PURE__ */ string(), DEFAULT_SETTINGS.prompt)
2702
+ });
2703
+ /** 从设置服务的快照值读出完整设置。 */
2704
+ function parseSettings(value) {
2705
+ return parse$1(SettingsSchema, typeof value === "object" && value !== null ? value : {});
2706
+ }
2707
+ const SETTING_KEYS = [
2708
+ "discovery",
2709
+ "review",
2710
+ "provider",
2711
+ "model",
2712
+ "effort",
2713
+ "timeout",
2714
+ "dailyReviews",
2715
+ "reviewInterval",
2716
+ "quietPeriod",
2717
+ "historyDays",
2718
+ "maxSessions",
2719
+ "minOccurrences",
2720
+ "evidenceBudget",
2721
+ "readOutput",
2722
+ "redactPatterns",
2723
+ "notify",
2724
+ "scope",
2725
+ "prompt"
2726
+ ];
2727
+ const NUMBER_KEYS = [
2728
+ "timeout",
2729
+ "dailyReviews",
2730
+ "reviewInterval",
2731
+ "quietPeriod",
2732
+ "historyDays",
2733
+ "maxSessions",
2734
+ "minOccurrences",
2735
+ "evidenceBudget"
2736
+ ];
2737
+ /** 暂存值与已保存值的差异,转换成写操作。 */
2738
+ function diffOps(saved, staged) {
2739
+ return SETTING_KEYS.flatMap((key) => {
2740
+ const next = staged[key];
2741
+ return JSON.stringify(saved[key]) === JSON.stringify(next) ? [] : [{
2742
+ op: "set",
2743
+ path: [key],
2744
+ value: next
2745
+ }];
2746
+ });
2747
+ }
2748
+ /** 正则源码能否编译。 */
2749
+ function compiles(source) {
2750
+ try {
2751
+ return new RegExp(source, "u").source !== "";
2752
+ } catch (error) {
2753
+ return false;
2754
+ }
2755
+ }
2756
+ /** 每行一个正则;返回第一条不合法的行号(从 1 开始),都合法时返回 null。 */
2757
+ function invalidPattern(lines) {
2758
+ const index = lines.findIndex((line) => line.trim() !== "" && !compiles(line));
2759
+ return index === -1 ? null : index + 1;
2760
+ }
2761
+ function Field({ id, label, hint, children, wide = false }) {
2762
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2763
+ className: wide ? `${settings_module_css_default.field} ${settings_module_css_default.wide}` : settings_module_css_default.field,
2764
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2765
+ className: settings_module_css_default.text,
2766
+ children: [id === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2767
+ className: settings_module_css_default.label,
2768
+ children: label
2769
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
2770
+ className: settings_module_css_default.label,
2771
+ htmlFor: id,
2772
+ children: label
2773
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2774
+ className: settings_module_css_default.hint,
2775
+ children: hint
2776
+ })]
2777
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2778
+ className: settings_module_css_default.control,
2779
+ children
2780
+ })]
2781
+ });
2782
+ }
2783
+ /** 模型选项的值:`provider\u0000model`,空串表示默认模型。 */
2784
+ const SEPARATOR = "\0";
2785
+ function modelValue(provider, model) {
2786
+ return provider === "" || model === "" ? "" : `${provider}${SEPARATOR}${model}`;
2787
+ }
2788
+ function ModelFields({ staged, catalog, disabled, onChange, t }) {
2789
+ const id = (0, react.useId)();
2790
+ const groups = catalog.status === "ready" ? catalog.catalog.groups : [];
2791
+ const current = modelValue(staged.provider, staged.model);
2792
+ const fallback = catalog.status === "ready" ? catalog.catalog.default : null;
2793
+ const fallbackName = fallback === null ? null : groups.find((group) => group.id === fallback.provider)?.models.find((model) => model.id === fallback.model)?.name ?? fallback.model;
2794
+ const routeProvider = staged.provider === "" ? fallback?.provider ?? "" : staged.provider;
2795
+ const routeModel = staged.model === "" ? fallback?.model ?? "" : staged.model;
2796
+ const efforts = (groups.find((group) => group.id === routeProvider)?.models.find((model) => model.id === routeModel))?.reasoning?.efforts ?? [];
2797
+ const listed = groups.some((group) => group.models.some((model) => modelValue(group.id, model.id) === current));
2798
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Field, {
2799
+ id: `${id}-model`,
2800
+ label: t("settings.model"),
2801
+ hint: t("settings.modelHint"),
2802
+ children: [
2803
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
2804
+ id: `${id}-model`,
2805
+ className: settings_module_css_default.select,
2806
+ value: current,
2807
+ disabled,
2808
+ onChange: (event) => {
2809
+ const [provider = "", model = ""] = event.target.value.split(SEPARATOR);
2810
+ onChange({
2811
+ provider,
2812
+ model,
2813
+ effort: ""
2814
+ });
2815
+ },
2816
+ children: [
2817
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
2818
+ value: "",
2819
+ children: fallbackName === null ? t("settings.modelDefaultUnknown") : t("settings.modelDefault", { model: fallbackName })
2820
+ }),
2821
+ current !== "" && !listed && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
2822
+ value: current,
2823
+ children: t("settings.modelMissing", { model: staged.model })
2824
+ }),
2825
+ groups.map((group) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("optgroup", {
2826
+ label: group.name,
2827
+ children: group.models.map((model) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
2828
+ value: modelValue(group.id, model.id),
2829
+ children: model.name
2830
+ }, model.id))
2831
+ }, group.id))
2832
+ ]
2833
+ }),
2834
+ catalog.status === "loading" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2835
+ className: settings_module_css_default.status,
2836
+ children: t("settings.modelLoading")
2837
+ }),
2838
+ catalog.status === "error" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2839
+ className: settings_module_css_default.status,
2840
+ role: "alert",
2841
+ children: t("settings.modelError")
2842
+ })
2843
+ ]
2844
+ }), efforts.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
2845
+ id: `${id}-effort`,
2846
+ label: t("settings.effort"),
2847
+ hint: t("settings.effortHint"),
2848
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
2849
+ id: `${id}-effort`,
2850
+ className: settings_module_css_default.select,
2851
+ value: staged.effort,
2852
+ disabled,
2853
+ onChange: (event) => onChange({ effort: event.target.value }),
2854
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
2855
+ value: "",
2856
+ children: t("settings.effortDefault")
2857
+ }), efforts.map((effort) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
2858
+ value: effort.id,
2859
+ children: effort.name
2860
+ }, effort.id))]
2861
+ })
2862
+ })] });
2863
+ }
2864
+ /**
2865
+ * 创建配置页组件。
2866
+ * @param catalog - 模型目录的读取状态;页面打开时订阅。
2867
+ */
2868
+ function createSettingsPage(catalog) {
2869
+ return function SettingsPage({ view, form, t }) {
2870
+ const translate = t;
2871
+ const id = (0, react.useId)();
2872
+ const saved = parseSettings(form?.state.value);
2873
+ const [draft, setDraft] = (0, react.useState)({});
2874
+ const [patterns, setPatterns] = (0, react.useState)(null);
2875
+ const [saving, setSaving] = (0, react.useState)(false);
2876
+ const [failed, setFailed] = (0, react.useState)(false);
2877
+ const models = useSnapshot(catalog);
2878
+ if (view === "summary" || form === void 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: translate("settings.summary") });
2879
+ const lines = patterns === null ? saved.redactPatterns : patterns.split("\n");
2880
+ const staged = {
2881
+ ...saved,
2882
+ ...draft,
2883
+ redactPatterns: lines.map((line) => line.trim()).filter((line) => line !== "")
2884
+ };
2885
+ const ops = diffOps(saved, staged);
2886
+ const badLine = invalidPattern(lines);
2887
+ const badNumber = NUMBER_KEYS.some((key) => {
2888
+ const value = staged[key];
2889
+ return !Number.isInteger(value) || value < NUMBERS[key].min || value > NUMBERS[key].max;
2890
+ });
2891
+ const writable = form.state.writable && !saving;
2892
+ const update = (next) => {
2893
+ setDraft((current) => ({
2894
+ ...current,
2895
+ ...next
2896
+ }));
2897
+ setFailed(false);
2898
+ };
2899
+ const discard = () => {
2900
+ setDraft({});
2901
+ setPatterns(null);
2902
+ setFailed(false);
2903
+ };
2904
+ const save = async () => {
2905
+ setSaving(true);
2906
+ setFailed(false);
2907
+ try {
2908
+ if (await form.mutate(ops, form.state.revision)) discard();
2909
+ else setFailed(true);
2910
+ } catch (error) {
2911
+ setFailed(true);
2912
+ } finally {
2913
+ setSaving(false);
2914
+ }
2915
+ };
2916
+ const toggle = (key, label, hint) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
2917
+ label: translate(label),
2918
+ hint: translate(hint),
2919
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Switch, {
2920
+ checked: staged[key],
2921
+ label: translate(label),
2922
+ disabled: !writable,
2923
+ onChange: (next) => update({ [key]: next })
2924
+ })
2925
+ });
2926
+ const number = (key) => {
2927
+ const range = NUMBERS[key];
2928
+ const fieldId = `${id}-${key}`;
2929
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
2930
+ id: fieldId,
2931
+ label: translate(`settings.${key}`),
2932
+ hint: translate(`settings.${key}Hint`),
2933
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2934
+ className: settings_module_css_default.number,
2935
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2936
+ id: fieldId,
2937
+ className: settings_module_css_default.input,
2938
+ type: "number",
2939
+ inputMode: "numeric",
2940
+ min: range.min,
2941
+ max: range.max,
2942
+ step: 1,
2943
+ value: Number.isFinite(staged[key]) ? staged[key] : "",
2944
+ disabled: !writable,
2945
+ "aria-invalid": staged[key] < range.min || staged[key] > range.max || void 0,
2946
+ onChange: (event) => update({ [key]: event.target.valueAsNumber })
2947
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2948
+ className: settings_module_css_default.unit,
2949
+ children: translate(range.unit)
2950
+ })]
2951
+ })
2952
+ });
2953
+ };
2954
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.SettingsForm, {
2955
+ labels: {
2956
+ unavailable: translate("settings.unavailable"),
2957
+ readOnly: translate("settings.readOnly"),
2958
+ saveFailed: translate("settings.saveFailed"),
2959
+ save: translate("settings.save"),
2960
+ saving: translate("settings.saving")
2961
+ },
2962
+ state: {
2963
+ available: form.state.status === "ready",
2964
+ writable: form.state.writable,
2965
+ dirty: ops.length > 0,
2966
+ invalid: badLine !== null || badNumber,
2967
+ saving,
2968
+ failed
2969
+ },
2970
+ onSave: () => void save(),
2971
+ onDiscard: discard,
2972
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2973
+ className: settings_module_css_default.page,
2974
+ children: [
2975
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
2976
+ className: settings_module_css_default.section,
2977
+ children: [
2978
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
2979
+ className: settings_module_css_default.title,
2980
+ children: translate("settings.section.discovery")
2981
+ }),
2982
+ toggle("discovery", "settings.discovery", "settings.discoveryHint"),
2983
+ number("historyDays"),
2984
+ number("maxSessions"),
2985
+ number("quietPeriod"),
2986
+ number("minOccurrences")
2987
+ ]
2988
+ }),
2989
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
2990
+ className: settings_module_css_default.section,
2991
+ children: [
2992
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
2993
+ className: settings_module_css_default.title,
2994
+ children: translate("settings.section.review")
2995
+ }),
2996
+ toggle("review", "settings.review", "settings.reviewHint"),
2997
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelFields, {
2998
+ staged,
2999
+ catalog: models,
3000
+ disabled: !writable,
3001
+ onChange: update,
3002
+ t: translate
3003
+ }),
3004
+ number("timeout"),
3005
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
3006
+ id: `${id}-prompt`,
3007
+ label: translate("settings.prompt"),
3008
+ hint: translate("settings.promptHint"),
3009
+ wide: true,
3010
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
3011
+ id: `${id}-prompt`,
3012
+ className: `${settings_module_css_default.input} ${settings_module_css_default.area}`,
3013
+ rows: 4,
3014
+ placeholder: translate("settings.promptPlaceholder"),
3015
+ value: staged.prompt,
3016
+ disabled: !writable,
3017
+ onChange: (event) => update({ prompt: event.target.value })
3018
+ })
3019
+ })
3020
+ ]
3021
+ }),
3022
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
3023
+ className: settings_module_css_default.section,
3024
+ children: [
3025
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
3026
+ className: settings_module_css_default.title,
3027
+ children: translate("settings.section.limits")
3028
+ }),
3029
+ number("dailyReviews"),
3030
+ number("reviewInterval"),
3031
+ number("evidenceBudget")
3032
+ ]
3033
+ }),
3034
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
3035
+ className: settings_module_css_default.section,
3036
+ children: [
3037
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
3038
+ className: settings_module_css_default.title,
3039
+ children: translate("settings.section.privacy")
3040
+ }),
3041
+ toggle("readOutput", "settings.readOutput", "settings.readOutputHint"),
3042
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Field, {
3043
+ id: `${id}-redact`,
3044
+ label: translate("settings.redactPatterns"),
3045
+ hint: translate("settings.redactPatternsHint"),
3046
+ wide: true,
3047
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
3048
+ id: `${id}-redact`,
3049
+ className: `${settings_module_css_default.input} ${settings_module_css_default.area} ${settings_module_css_default.mono}`,
3050
+ rows: 3,
3051
+ spellCheck: false,
3052
+ value: patterns ?? saved.redactPatterns.join("\n"),
3053
+ disabled: !writable,
3054
+ "aria-invalid": badLine !== null || void 0,
3055
+ onChange: (event) => {
3056
+ setPatterns(event.target.value);
3057
+ setFailed(false);
3058
+ }
3059
+ }), badLine !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3060
+ className: settings_module_css_default.status,
3061
+ role: "alert",
3062
+ children: translate("settings.redactInvalid", { line: badLine })
3063
+ })]
3064
+ })
3065
+ ]
3066
+ }),
3067
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
3068
+ className: settings_module_css_default.section,
3069
+ children: [
3070
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
3071
+ className: settings_module_css_default.title,
3072
+ children: translate("settings.section.inbox")
3073
+ }),
3074
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
3075
+ label: translate("settings.notify"),
3076
+ hint: translate("settings.notifyHint"),
3077
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.SegmentedControl, {
3078
+ id: `${id}-notify`,
3079
+ label: translate("settings.notify"),
3080
+ value: staged.notify,
3081
+ disabled: !writable,
3082
+ options: [{
3083
+ value: "badge",
3084
+ label: translate("settings.notify.badge")
3085
+ }, {
3086
+ value: "quiet",
3087
+ label: translate("settings.notify.quiet")
3088
+ }],
3089
+ onChange: (notify) => update({ notify })
3090
+ })
3091
+ }),
3092
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
3093
+ label: translate("settings.scope"),
3094
+ hint: translate("settings.scopeHint"),
3095
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.SegmentedControl, {
3096
+ id: `${id}-scope`,
3097
+ label: translate("settings.scope"),
3098
+ value: staged.scope,
3099
+ disabled: !writable,
3100
+ options: [{
3101
+ value: "project",
3102
+ label: translate("scope.project")
3103
+ }, {
3104
+ value: "user",
3105
+ label: translate("scope.user")
3106
+ }],
3107
+ onChange: (scope) => update({ scope })
3108
+ })
3109
+ })
3110
+ ]
3111
+ })
3112
+ ]
3113
+ })
3114
+ });
3115
+ };
3116
+ }
3117
+ //#endregion
3118
+ //#region src/client/store.ts
3119
+ /** 进行中时的读取间隔。 */
3120
+ const BUSY_INTERVAL = 3e3;
3121
+ /** 空闲时的读取间隔。 */
3122
+ const IDLE_INTERVAL = 45e3;
3123
+ /** 操作提示停留的时间。 */
3124
+ const NOTICE_TTL = 8e3;
3125
+ const INITIAL = {
3126
+ status: "idle",
3127
+ inbox: null,
3128
+ error: null,
3129
+ notice: null
3130
+ };
3131
+ /** 有后台工作正在进行:分析、复核,或写入后核对 DSH 能否发现。 */
3132
+ function busy(inbox) {
3133
+ return inbox !== null && (inbox.status.scanning || inbox.status.reviewing !== null || inbox.items.some((item) => item.write?.checking === true));
3134
+ }
3135
+ /**
3136
+ * 创建收件箱状态。
3137
+ * @param transport - 读取与写入 Host 路由的函数。
3138
+ */
3139
+ function createInboxStore(transport) {
3140
+ let state = INITIAL;
3141
+ const listeners = /* @__PURE__ */ new Set();
3142
+ let timer;
3143
+ let noticeTimer;
3144
+ let generation = 0;
3145
+ const set = (next) => {
3146
+ state = next;
3147
+ for (const listener of listeners) listener();
3148
+ };
3149
+ const schedule = () => {
3150
+ clearTimeout(timer);
3151
+ if (listeners.size === 0) return;
3152
+ timer = setTimeout(() => {
3153
+ if (document.visibilityState === "hidden") schedule();
3154
+ else refresh();
3155
+ }, busy(state.inbox) ? BUSY_INTERVAL : IDLE_INTERVAL);
3156
+ };
3157
+ async function refresh() {
3158
+ const current = ++generation;
3159
+ if (state.inbox === null) set({
3160
+ ...state,
3161
+ status: "loading",
3162
+ error: null
3163
+ });
3164
+ try {
3165
+ const inbox = await transport.fetchInbox();
3166
+ if (current === generation) set({
3167
+ ...state,
3168
+ status: "ready",
3169
+ inbox,
3170
+ error: null
3171
+ });
3172
+ } catch (error) {
3173
+ if (current === generation) set({
3174
+ ...state,
3175
+ status: "error",
3176
+ error: error instanceof Error ? error.message : String(error)
3177
+ });
3178
+ } finally {
3179
+ if (current === generation) schedule();
3180
+ }
3181
+ }
3182
+ const onFocus = () => {
3183
+ if (listeners.size > 0) refresh();
3184
+ };
3185
+ const showNotice = (notice) => {
3186
+ clearTimeout(noticeTimer);
3187
+ set({
3188
+ ...state,
3189
+ notice
3190
+ });
3191
+ if (notice !== null) noticeTimer = setTimeout(() => set({
3192
+ ...state,
3193
+ notice: null
3194
+ }), NOTICE_TTL);
3195
+ };
3196
+ return {
3197
+ getSnapshot: () => state,
3198
+ subscribe(listener) {
3199
+ listeners.add(listener);
3200
+ if (listeners.size === 1) {
3201
+ window.addEventListener("focus", onFocus);
3202
+ refresh();
3203
+ }
3204
+ return () => {
3205
+ listeners.delete(listener);
3206
+ if (listeners.size === 0) {
3207
+ window.removeEventListener("focus", onFocus);
3208
+ clearTimeout(timer);
3209
+ }
3210
+ };
3211
+ },
3212
+ refresh,
3213
+ async act(action, notice) {
3214
+ const result = await transport.postAction(action);
3215
+ generation += 1;
3216
+ set({
3217
+ ...state,
3218
+ status: "ready",
3219
+ inbox: result.inbox,
3220
+ error: null
3221
+ });
3222
+ if (result.ok && notice !== void 0) showNotice(notice);
3223
+ schedule();
3224
+ return result;
3225
+ },
3226
+ clearNotice() {
3227
+ showNotice(null);
3228
+ },
3229
+ dispose() {
3230
+ clearTimeout(timer);
3231
+ clearTimeout(noticeTimer);
3232
+ window.removeEventListener("focus", onFocus);
3233
+ listeners.clear();
3234
+ }
3235
+ };
3236
+ }
3237
+ //#endregion
3238
+ //#region src/client/source.ts
3239
+ function createSnapshotSource(initial, load) {
3240
+ let value = initial;
3241
+ let loaded = false;
3242
+ let generation = 0;
3243
+ const listeners = /* @__PURE__ */ new Set();
3244
+ const run = () => {
3245
+ loaded = true;
3246
+ const current = ++generation;
3247
+ const set = (next) => {
3248
+ if (current !== generation) return;
3249
+ value = next;
3250
+ for (const listener of listeners) listener();
3251
+ };
3252
+ load(set).catch(() => set(initial));
3253
+ };
3254
+ return {
3255
+ getSnapshot: () => value,
3256
+ subscribe(listener) {
3257
+ listeners.add(listener);
3258
+ if (!loaded) run();
3259
+ return () => {
3260
+ listeners.delete(listener);
3261
+ };
3262
+ },
3263
+ reload() {
3264
+ if (loaded) run();
3265
+ }
3266
+ };
3267
+ }
3268
+ //#endregion
3269
+ //#region src/client/index.ts
3270
+ /**
3271
+ * 浏览器半部入口:注册文案、会话标题栏里的收件箱入口,以及插件页里的配置页。
3272
+ * 收件箱状态全局共用一份,所有会话的标题栏入口订阅同一个 store。
3273
+ */
3274
+ /** 宿主提醒入口的 order 是 20;本入口排在它后面。 */
3275
+ const HEADER_ORDER = 30;
3276
+ const inject = [
3277
+ "slots",
3278
+ "locale",
3279
+ "remote",
3280
+ "remote.session"
3281
+ ];
3282
+ function apply(ctx) {
3283
+ ctx.effect(() => registerLocale(ctx.locale), "skill-scout: locale");
3284
+ const inbox = createInboxStore({
3285
+ fetchInbox: () => fetchInbox(),
3286
+ postAction
3287
+ });
3288
+ ctx.effect(() => () => inbox.dispose(), "skill-scout: inbox");
3289
+ ctx.on("connection/reset", () => void inbox.refresh());
3290
+ const face = {
3291
+ inbox,
3292
+ seen: createSeenStore(window.localStorage),
3293
+ loadDetail: (id, _version, signal) => fetchDetail(id, signal),
3294
+ openSession: (sessionId) => ctx.get("uiWorkspace")?.openSession(sessionId)
3295
+ };
3296
+ ctx.slots.inject("conversation.session.header.utilities", () => ctx.slots.register({
3297
+ name: "conversation.session.header.utilities",
3298
+ id: "skill-scout-inbox",
3299
+ order: HEADER_ORDER,
3300
+ locale: SCOUT_LOCALE,
3301
+ inject: () => face
3302
+ }, InboxEntry));
3303
+ const catalog = createSnapshotSource({ status: "loading" }, async (set) => {
3304
+ const response = await ctx.remote.session.modelCatalog();
3305
+ set(response.ok ? {
3306
+ status: "ready",
3307
+ catalog: response.value
3308
+ } : { status: "error" });
3309
+ });
3310
+ ctx.effect(() => ctx.remote.$on("llm/adapters-updated", () => catalog.reload()), "skill-scout: adapters");
3311
+ ctx.effect(() => ctx.remote.$on("llm/models-updated", () => catalog.reload()), "skill-scout: models");
3312
+ ctx.slots.inject("plugins.row.config", () => ctx.slots.register({
3313
+ name: "plugins.row.config",
3314
+ key: `${PACKAGE_NAME}#${ROW_ID}`,
3315
+ locale: SCOUT_LOCALE
3316
+ }, createSettingsPage(catalog)));
3317
+ }
3318
+ //#endregion
3319
+ exports.apply = apply;
3320
+ exports.inject = inject;
3321
+ return module.exports;
3322
+ }
3323
+ });
3324
+
3325
+ //# sourceMappingURL=client.js.map