@agent-stash/dsh-skill-scout 0.0.0-stage → 0.2.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,3708 @@
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
+ /**
660
+ * 建议在界面上的状态。summarizing 与 drafting 只存在于 Host 内存,表示发现模型正在写摘要、
661
+ * 生成模型正在写草稿;其余与存储里的状态一致。
662
+ */
663
+ const ItemStateSchema = /* @__PURE__ */ picklist([
664
+ "detected",
665
+ "summarizing",
666
+ "proposed",
667
+ "skipped",
668
+ "drafting",
669
+ "ready",
670
+ "failed",
671
+ "dismissed",
672
+ "written"
673
+ ]);
674
+ /** 模型调用的两个阶段:摘要与草稿。 */
675
+ const StageViewSchema = /* @__PURE__ */ picklist(["summary", "draft"]);
676
+ const DecisionSchema = /* @__PURE__ */ picklist([
677
+ "skip",
678
+ "suggest_create",
679
+ "suggest_improve"
680
+ ]);
681
+ const FailureKindViewSchema = /* @__PURE__ */ picklist([
682
+ "timeout",
683
+ "model",
684
+ "format",
685
+ "evidence",
686
+ "unsafe"
687
+ ]);
688
+ /** 证据来自的一次任务。 */
689
+ const SessionRefSchema = /* @__PURE__ */ object({
690
+ sessionId: /* @__PURE__ */ string(),
691
+ episodeId: /* @__PURE__ */ string(),
692
+ at: /* @__PURE__ */ number(),
693
+ goal: /* @__PURE__ */ string(),
694
+ outcome: /* @__PURE__ */ string()
695
+ });
696
+ /** 收件箱里的一条建议。 */
697
+ const ItemViewSchema = /* @__PURE__ */ object({
698
+ id: /* @__PURE__ */ string(),
699
+ kind: SignalKindViewSchema,
700
+ state: ItemStateSchema,
701
+ /** 摘要给出的标题;还没有摘要时为 null,界面用 goal 代替。 */
702
+ title: Nullable(/* @__PURE__ */ string()),
703
+ goal: /* @__PURE__ */ string(),
704
+ /** 规则在这组任务里找到的共有步骤,原来的写法。 */
705
+ steps: /* @__PURE__ */ array(/* @__PURE__ */ string()),
706
+ /** 摘要:在会话里发现了什么。 */
707
+ found: Nullable(/* @__PURE__ */ string()),
708
+ /** 摘要:Skill 做什么;改进时是追加的内容补上了什么。 */
709
+ description: Nullable(/* @__PURE__ */ string()),
710
+ /** 摘要:什么时候用。 */
711
+ trigger: Nullable(/* @__PURE__ */ string()),
712
+ /** 摘要:打算写进 Skill 的步骤或追加的内容。 */
713
+ outline: /* @__PURE__ */ array(/* @__PURE__ */ string()),
714
+ /** 摘要:照着做的风险。 */
715
+ risks: /* @__PURE__ */ array(/* @__PURE__ */ string()),
716
+ /** 摘要:怎么确认照着做成功了。 */
717
+ verification: Nullable(/* @__PURE__ */ string()),
718
+ /** 摘要判为跳过的理由。 */
719
+ reason: Nullable(/* @__PURE__ */ string()),
720
+ occurrences: /* @__PURE__ */ number(),
721
+ sessions: /* @__PURE__ */ array(SessionRefSchema),
722
+ score: /* @__PURE__ */ number(),
723
+ detectedAt: /* @__PURE__ */ number(),
724
+ updatedAt: /* @__PURE__ */ number(),
725
+ decision: Nullable(DecisionSchema),
726
+ /** 新建的 Skill 名称或要改进的 Skill。 */
727
+ skill: Nullable(/* @__PURE__ */ string()),
728
+ cautions: /* @__PURE__ */ array(/* @__PURE__ */ string()),
729
+ failure: Nullable(/* @__PURE__ */ object({
730
+ stage: StageViewSchema,
731
+ kind: FailureKindViewSchema,
732
+ message: /* @__PURE__ */ string(),
733
+ at: /* @__PURE__ */ number(),
734
+ attempts: /* @__PURE__ */ number()
735
+ })),
736
+ write: Nullable(/* @__PURE__ */ object({
737
+ at: /* @__PURE__ */ number(),
738
+ scope: /* @__PURE__ */ picklist([
739
+ "project",
740
+ "user",
741
+ "existing"
742
+ ]),
743
+ skill: /* @__PURE__ */ string(),
744
+ path: /* @__PURE__ */ string(),
745
+ discovered: /* @__PURE__ */ boolean(),
746
+ /** DSH 还没列出,后台仍在核对。 */
747
+ checking: /* @__PURE__ */ boolean()
748
+ })),
749
+ dismissedSimilar: /* @__PURE__ */ boolean()
750
+ });
751
+ /** 一个阶段此刻使用的模型;没有可用模型时为 null。 */
752
+ const ModelViewSchema = Nullable(/* @__PURE__ */ object({
753
+ provider: /* @__PURE__ */ string(),
754
+ model: /* @__PURE__ */ string(),
755
+ effort: Nullable(/* @__PURE__ */ string()),
756
+ source: /* @__PURE__ */ picklist(["config", "default"])
757
+ }));
758
+ const InboxViewSchema = /* @__PURE__ */ object({
759
+ status: /* @__PURE__ */ object({
760
+ discovery: /* @__PURE__ */ boolean(),
761
+ /** 后台自动写摘要。 */
762
+ summarize: /* @__PURE__ */ boolean(),
763
+ notify: /* @__PURE__ */ picklist(["badge", "quiet"]),
764
+ scope: /* @__PURE__ */ picklist(["project", "user"]),
765
+ models: /* @__PURE__ */ object({
766
+ summary: ModelViewSchema,
767
+ draft: ModelViewSchema
768
+ }),
769
+ /** 本进程持有工作锁,负责分析与后台摘要。 */
770
+ lead: /* @__PURE__ */ boolean(),
771
+ scanning: /* @__PURE__ */ boolean(),
772
+ /** 回合已结束、等静默期过去后再分析的会话数。 */
773
+ waiting: /* @__PURE__ */ number(),
774
+ /** 正在进行的摘要与草稿请求数。 */
775
+ working: /* @__PURE__ */ number(),
776
+ usage: /* @__PURE__ */ object({
777
+ /** 今天的后台摘要次数与上限。 */
778
+ summaries: /* @__PURE__ */ number(),
779
+ limit: /* @__PURE__ */ number(),
780
+ /** 今天用户发起的摘要与草稿次数。 */
781
+ requested: /* @__PURE__ */ number(),
782
+ inputTokens: /* @__PURE__ */ number(),
783
+ outputTokens: /* @__PURE__ */ number()
784
+ }),
785
+ lastError: Nullable(/* @__PURE__ */ object({
786
+ kind: /* @__PURE__ */ string(),
787
+ message: /* @__PURE__ */ string(),
788
+ at: /* @__PURE__ */ number()
789
+ })),
790
+ analyzed: /* @__PURE__ */ object({
791
+ sessions: /* @__PURE__ */ number(),
792
+ episodes: /* @__PURE__ */ number()
793
+ })
794
+ }),
795
+ items: /* @__PURE__ */ array(ItemViewSchema)
796
+ });
797
+ /** 证据里的一次操作。 */
798
+ const EvidenceOpSchema = /* @__PURE__ */ object({
799
+ /** 证据编号,与模型引用一致,例如 `E1.42`。 */
800
+ ref: /* @__PURE__ */ string(),
801
+ kind: /* @__PURE__ */ picklist([
802
+ "read",
803
+ "edit",
804
+ "exec",
805
+ "delegate"
806
+ ]),
807
+ label: /* @__PURE__ */ string(),
808
+ ok: /* @__PURE__ */ boolean(),
809
+ exit: Nullable(/* @__PURE__ */ number()),
810
+ error: Nullable(/* @__PURE__ */ string()),
811
+ /** 在结果判定或恢复链里的角色。 */
812
+ role: Nullable(/* @__PURE__ */ picklist([
813
+ "verify",
814
+ "failure",
815
+ "fix",
816
+ "recovered"
817
+ ])),
818
+ /** 摘要或草稿引用了它。 */
819
+ cited: /* @__PURE__ */ boolean()
820
+ });
821
+ const EvidenceEpisodeSchema = /* @__PURE__ */ object({
822
+ ref: /* @__PURE__ */ string(),
823
+ episodeId: /* @__PURE__ */ string(),
824
+ sessionId: /* @__PURE__ */ string(),
825
+ at: /* @__PURE__ */ number(),
826
+ cwd: Nullable(/* @__PURE__ */ string()),
827
+ goal: /* @__PURE__ */ string(),
828
+ outcome: /* @__PURE__ */ string(),
829
+ reads: /* @__PURE__ */ number(),
830
+ ops: /* @__PURE__ */ array(EvidenceOpSchema),
831
+ reply: Nullable(/* @__PURE__ */ string()),
832
+ steering: /* @__PURE__ */ array(/* @__PURE__ */ string()),
833
+ followup: Nullable(/* @__PURE__ */ string())
834
+ });
835
+ /** 可编辑的 Skill 字段。 */
836
+ const SkillFieldsSchema = /* @__PURE__ */ object({
837
+ name: /* @__PURE__ */ pipe(/* @__PURE__ */ string(), /* @__PURE__ */ trim(), /* @__PURE__ */ regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/u), /* @__PURE__ */ maxLength(64)),
838
+ description: /* @__PURE__ */ pipe(/* @__PURE__ */ string(), /* @__PURE__ */ trim(), /* @__PURE__ */ minLength(1), /* @__PURE__ */ maxLength(500)),
839
+ whenToUse: /* @__PURE__ */ pipe(/* @__PURE__ */ string(), /* @__PURE__ */ trim(), /* @__PURE__ */ maxLength(500)),
840
+ body: /* @__PURE__ */ pipe(/* @__PURE__ */ string(), /* @__PURE__ */ trim(), /* @__PURE__ */ minLength(1), /* @__PURE__ */ maxLength(2e4))
841
+ });
842
+ /** 一次模型调用的来源与用量。 */
843
+ const CallViewSchema = Nullable(/* @__PURE__ */ object({
844
+ provider: /* @__PURE__ */ string(),
845
+ model: /* @__PURE__ */ string(),
846
+ at: /* @__PURE__ */ number(),
847
+ inputTokens: /* @__PURE__ */ number(),
848
+ outputTokens: /* @__PURE__ */ number()
849
+ }));
850
+ /** 一条建议的完整信息:证据、摘要与草稿。 */
851
+ const DetailViewSchema = /* @__PURE__ */ object({
852
+ item: ItemViewSchema,
853
+ episodes: /* @__PURE__ */ array(EvidenceEpisodeSchema),
854
+ summary: CallViewSchema,
855
+ draftCall: CallViewSchema,
856
+ /** suggest_create 的草稿字段。 */
857
+ draft: Nullable(SkillFieldsSchema),
858
+ /** suggest_improve 的追加内容。 */
859
+ section: Nullable(/* @__PURE__ */ string())
860
+ });
861
+ /** 写入前的预览。 */
862
+ const PreviewViewSchema = /* @__PURE__ */ variant("kind", [/* @__PURE__ */ object({
863
+ kind: /* @__PURE__ */ literal("create"),
864
+ scope: /* @__PURE__ */ picklist(["project", "user"]),
865
+ path: /* @__PURE__ */ string(),
866
+ content: /* @__PURE__ */ string(),
867
+ exists: /* @__PURE__ */ boolean()
868
+ }), /* @__PURE__ */ object({
869
+ kind: /* @__PURE__ */ literal("improve"),
870
+ skill: /* @__PURE__ */ string(),
871
+ path: Nullable(/* @__PURE__ */ string()),
872
+ /** 目标在 DSH 扫描的项目或用户 Skill 目录里,可以写入。 */
873
+ writable: /* @__PURE__ */ boolean(),
874
+ before: /* @__PURE__ */ string(),
875
+ after: /* @__PURE__ */ string(),
876
+ baseHash: /* @__PURE__ */ string()
877
+ })]);
878
+ const ActionResultSchema = /* @__PURE__ */ variant("ok", [/* @__PURE__ */ object({
879
+ ok: /* @__PURE__ */ literal(true),
880
+ inbox: InboxViewSchema,
881
+ preview: /* @__PURE__ */ optional(PreviewViewSchema)
882
+ }), /* @__PURE__ */ object({
883
+ ok: /* @__PURE__ */ literal(false),
884
+ error: /* @__PURE__ */ object({
885
+ kind: /* @__PURE__ */ picklist([
886
+ "invalid",
887
+ "missing",
888
+ "busy",
889
+ "no-model",
890
+ "exists",
891
+ "changed",
892
+ "outside",
893
+ "unsafe"
894
+ ]),
895
+ message: /* @__PURE__ */ string()
896
+ }),
897
+ inbox: InboxViewSchema
898
+ })]);
899
+ /** 非 2xx 且不是操作失败的响应体。 */
900
+ const ErrorBodySchema = /* @__PURE__ */ object({ error: /* @__PURE__ */ string() });
901
+ //#endregion
902
+ //#region src/client/api.ts
903
+ /** 浏览器端访问 Host 路由:响应按 shared.ts 的 schema 校验。 */
904
+ /** 解析响应;操作失败(409/404)也带收件箱,按结果返回而不是抛出。 */
905
+ async function parse(response, schema, path) {
906
+ const body = await response.json();
907
+ const result = /* @__PURE__ */ safeParse(schema, body);
908
+ if (result.success) return result.output;
909
+ const error = /* @__PURE__ */ safeParse(ErrorBodySchema, body);
910
+ throw new Error(error.success ? error.output.error : `${path} 返回了 ${String(response.status)}`);
911
+ }
912
+ /** 读取收件箱与工作状态。 */
913
+ async function fetchInbox(signal) {
914
+ return parse(await fetch(INBOX_PATH, {
915
+ headers: { accept: "application/json" },
916
+ cache: "no-store",
917
+ ...signal === void 0 ? {} : { signal }
918
+ }), InboxViewSchema, INBOX_PATH);
919
+ }
920
+ /** 读取一条建议的证据与草稿。 */
921
+ async function fetchDetail(id, signal) {
922
+ return parse(await fetch(`${DETAIL_PATH}?${new URLSearchParams({ id }).toString()}`, {
923
+ headers: { accept: "application/json" },
924
+ cache: "no-store",
925
+ ...signal === void 0 ? {} : { signal }
926
+ }), DetailViewSchema, DETAIL_PATH);
927
+ }
928
+ /** 提交一个操作。 */
929
+ async function postAction(action) {
930
+ return parse(await fetch(ACTION_PATH, {
931
+ method: "POST",
932
+ headers: {
933
+ accept: "application/json",
934
+ "content-type": "application/json"
935
+ },
936
+ body: JSON.stringify(action)
937
+ }), ActionResultSchema, ACTION_PATH);
938
+ }
939
+ //#endregion
940
+ //#region \0dsh-css:src/client/dialog.module.css.mjs
941
+ 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}}";
942
+ const tag$3 = "@agent-stash/dsh-skill-scout/dialog.module.css";
943
+ if (document.querySelector(`style[data-plugin-css=${JSON.stringify(tag$3)}]`) === null) {
944
+ const style = document.createElement("style");
945
+ style.dataset.plugin = "@agent-stash/dsh-skill-scout";
946
+ style.dataset.pluginCss = tag$3;
947
+ style.textContent = css$3;
948
+ document.head.appendChild(style);
949
+ }
950
+ var dialog_module_css_default = {
951
+ "body": "EFkxwG_body",
952
+ "close": "EFkxwG_close",
953
+ "dialog": "EFkxwG_dialog",
954
+ "enter": "EFkxwG_enter",
955
+ "footer": "EFkxwG_footer",
956
+ "header": "EFkxwG_header",
957
+ "heading": "EFkxwG_heading",
958
+ "subtitle": "EFkxwG_subtitle",
959
+ "title": "EFkxwG_title"
960
+ };
961
+ //#endregion
962
+ //#region src/client/dialog.tsx
963
+ /**
964
+ * 模态对话框:原生 `<dialog>` 加 `showModal()`,卡片外观取自宿主 `Modal`,宽度适合并排阅读证据与预览。
965
+ * 挂上时进入 top layer,卸载时浏览器把焦点还给打开前的控件。初始焦点给带 `data-autofocus` 的控件。
966
+ * `busy` 期间点遮罩、按 Escape 与关闭按钮都不生效。
967
+ */
968
+ /** 点击落在卡片矩形之外,即遮罩上。 */
969
+ function onBackdrop(event) {
970
+ if (event.target !== event.currentTarget) return false;
971
+ const box = event.currentTarget.getBoundingClientRect();
972
+ return event.clientX < box.left || event.clientX > box.right || event.clientY < box.top || event.clientY > box.bottom;
973
+ }
974
+ function Dialog({ title, closeLabel, subtitle, busy, onClose, children, footer }) {
975
+ const element = (0, react.useRef)(null);
976
+ const pressedOutside = (0, react.useRef)(false);
977
+ const attach = (0, react.useCallback)((node) => {
978
+ if (node === null) {
979
+ element.current?.close();
980
+ element.current = null;
981
+ return;
982
+ }
983
+ element.current = node;
984
+ if (!node.open) node.showModal();
985
+ node.querySelector("[data-autofocus]")?.focus();
986
+ }, []);
987
+ const close = () => {
988
+ if (!busy) onClose();
989
+ };
990
+ /** Escape 触发的 cancel 一律接管,忙时不关闭。 */
991
+ const onCancel = (event) => {
992
+ event.preventDefault();
993
+ close();
994
+ };
995
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("dialog", {
996
+ ref: attach,
997
+ "aria-label": title,
998
+ "aria-busy": busy || void 0,
999
+ className: dialog_module_css_default.dialog,
1000
+ onCancel,
1001
+ onPointerDown: (event) => {
1002
+ pressedOutside.current = onBackdrop(event);
1003
+ },
1004
+ onClick: (event) => {
1005
+ if (pressedOutside.current && onBackdrop(event)) close();
1006
+ pressedOutside.current = false;
1007
+ },
1008
+ children: [
1009
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1010
+ className: dialog_module_css_default.header,
1011
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1012
+ className: dialog_module_css_default.heading,
1013
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
1014
+ className: dialog_module_css_default.title,
1015
+ children: title
1016
+ }), subtitle !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1017
+ className: dialog_module_css_default.subtitle,
1018
+ children: subtitle
1019
+ })]
1020
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1021
+ type: "button",
1022
+ className: dialog_module_css_default.close,
1023
+ "aria-label": closeLabel,
1024
+ disabled: busy,
1025
+ onClick: close,
1026
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCloseOutlineRegular, { size: 14 })
1027
+ })]
1028
+ }),
1029
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1030
+ className: dialog_module_css_default.body,
1031
+ children
1032
+ }),
1033
+ footer !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1034
+ className: dialog_module_css_default.footer,
1035
+ children: footer
1036
+ })
1037
+ ]
1038
+ });
1039
+ }
1040
+ //#endregion
1041
+ //#region \0dsh-css:src/client/detail.module.css.mjs
1042
+ 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_block{flex-direction:column;gap:6px;display:flex}.FSsC8q_steps{color:var(--dsw-alias-label-tertiary);flex-direction:column;gap:2px;margin:0;padding:0 0 0 20px;font-size:12px;line-height:18px;display:flex}.FSsC8q_steps code{font-family:var(--dsw-font-family-mono,ui-monospace, monospace);color:var(--dsw-alias-label-primary);overflow-wrap:anywhere}.FSsC8q_summary{grid-template-columns:max-content 1fr;gap:10px 16px;margin:0;font-size:13px;line-height:20px;display:grid}.FSsC8q_summary dt{color:var(--dsw-alias-label-tertiary)}.FSsC8q_summary dd{min-width:0;color:var(--dsw-alias-label-primary);overflow-wrap:anywhere;margin:0}.FSsC8q_planTarget{flex-wrap:wrap;align-items:baseline;gap:6px;margin:0;display:flex}.FSsC8q_planTarget code{font-family:var(--dsw-font-family-mono,ui-monospace, monospace);font-size:12px}.FSsC8q_outline{color:var(--dsw-alias-label-secondary);margin:4px 0 0;padding:0 0 0 20px}.FSsC8q_risks{margin:0;padding:0 0 0 16px}.FSsC8q_working{flex-direction:column;gap:10px;display:flex}.FSsC8q_workingLabel{color:var(--dsw-alias-label-secondary);margin:0;font-size:12px;line-height:18px}.FSsC8q_skeleton{border-radius:var(--dsw-radius-lg);box-shadow:0 0 0 .5px var(--dsw-alias-border-l2);flex-direction:column;gap:10px;padding:16px;display:flex}.FSsC8q_skeletonBar{border-radius:var(--dsw-radius-xs);background:var(--dsw-alias-bg-skeleton);height:10px;animation:1.1s cubic-bezier(.36,0,.64,1) infinite both FSsC8q_scout-skeleton}.FSsC8q_skeletonGap{height:4px}@keyframes FSsC8q_scout-skeleton{0%,to{opacity:1}50%{opacity:.5}}@media (prefers-reduced-motion:reduce){.FSsC8q_skeletonBar{animation:none}}.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_toolbarActions{align-items:center;gap:4px;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}";
1043
+ const tag$2 = "@agent-stash/dsh-skill-scout/detail.module.css";
1044
+ if (document.querySelector(`style[data-plugin-css=${JSON.stringify(tag$2)}]`) === null) {
1045
+ const style = document.createElement("style");
1046
+ style.dataset.plugin = "@agent-stash/dsh-skill-scout";
1047
+ style.dataset.pluginCss = tag$2;
1048
+ style.textContent = css$2;
1049
+ document.head.appendChild(style);
1050
+ }
1051
+ var detail_module_css_default = {
1052
+ "actionError": "FSsC8q_actionError",
1053
+ "block": "FSsC8q_block",
1054
+ "callout": "FSsC8q_callout",
1055
+ "caution": "FSsC8q_caution",
1056
+ "editor": "FSsC8q_editor",
1057
+ "episode": "FSsC8q_episode",
1058
+ "episodeHead": "FSsC8q_episodeHead",
1059
+ "episodeText": "FSsC8q_episodeText",
1060
+ "facts": "FSsC8q_facts",
1061
+ "failure": "FSsC8q_failure",
1062
+ "failureDetail": "FSsC8q_failureDetail",
1063
+ "field": "FSsC8q_field",
1064
+ "fieldLabel": "FSsC8q_fieldLabel",
1065
+ "goal": "FSsC8q_goal",
1066
+ "heading": "FSsC8q_heading",
1067
+ "input": "FSsC8q_input",
1068
+ "markdown": "FSsC8q_markdown",
1069
+ "meta": "FSsC8q_meta",
1070
+ "mono": "FSsC8q_mono",
1071
+ "note": "FSsC8q_note",
1072
+ "op": "FSsC8q_op",
1073
+ "opBody": "FSsC8q_opBody",
1074
+ "opError": "FSsC8q_opError",
1075
+ "opGlyph": "FSsC8q_opGlyph",
1076
+ "opLabel": "FSsC8q_opLabel",
1077
+ "opRef": "FSsC8q_opRef",
1078
+ "ops": "FSsC8q_ops",
1079
+ "opTags": "FSsC8q_opTags",
1080
+ "outline": "FSsC8q_outline",
1081
+ "panel": "FSsC8q_panel",
1082
+ "path": "FSsC8q_path",
1083
+ "planTarget": "FSsC8q_planTarget",
1084
+ "ref": "FSsC8q_ref",
1085
+ "risks": "FSsC8q_risks",
1086
+ "scout-skeleton": "FSsC8q_scout-skeleton",
1087
+ "skeleton": "FSsC8q_skeleton",
1088
+ "skeletonBar": "FSsC8q_skeletonBar",
1089
+ "skeletonGap": "FSsC8q_skeletonGap",
1090
+ "spacer": "FSsC8q_spacer",
1091
+ "stack": "FSsC8q_stack",
1092
+ "steps": "FSsC8q_steps",
1093
+ "summary": "FSsC8q_summary",
1094
+ "tabs": "FSsC8q_tabs",
1095
+ "tag": "FSsC8q_tag",
1096
+ "toolbar": "FSsC8q_toolbar",
1097
+ "toolbarActions": "FSsC8q_toolbarActions",
1098
+ "working": "FSsC8q_working",
1099
+ "workingLabel": "FSsC8q_workingLabel",
1100
+ "written": "FSsC8q_written",
1101
+ "writtenPath": "FSsC8q_writtenPath"
1102
+ };
1103
+ //#endregion
1104
+ //#region src/client/format.ts
1105
+ const MINUTE = 6e4;
1106
+ const HOUR = 60 * MINUTE;
1107
+ const DAY = 24 * HOUR;
1108
+ /** 相对时间:刚刚、N 分钟前、N 小时前、N 天前。 */
1109
+ function ago(t, at, now) {
1110
+ const elapsed = Math.max(0, now - at);
1111
+ if (elapsed < MINUTE) return t("time.now");
1112
+ if (elapsed < HOUR) return t("time.minutes", { count: Math.floor(elapsed / MINUTE) });
1113
+ if (elapsed < DAY) return t("time.hours", { count: Math.floor(elapsed / HOUR) });
1114
+ return t("time.days", { count: Math.floor(elapsed / DAY) });
1115
+ }
1116
+ /** 「N 个会话」。 */
1117
+ function sessionCount(t, count) {
1118
+ return t(count === 1 ? "meta.sessions.one" : "meta.sessions.other", { count });
1119
+ }
1120
+ /** 卡片标题:摘要里的标题,还没有摘要时用用户请求。 */
1121
+ function titleOf(item) {
1122
+ return item.title ?? item.goal;
1123
+ }
1124
+ /** 状态文案;失败按阶段区分。 */
1125
+ function stateLabel(t, item) {
1126
+ return item.state === "failed" && item.failure?.stage === "draft" ? t("state.draftFailed") : t(`state.${item.state}`);
1127
+ }
1128
+ /** 模型正在处理这条建议。 */
1129
+ function inFlight(item) {
1130
+ return item.state === "summarizing" || item.state === "drafting";
1131
+ }
1132
+ /** 一行元信息:信号类型 · 会话数 · 时间。 */
1133
+ function metaOf(t, item, now) {
1134
+ return [
1135
+ t(`kind.${item.kind}`),
1136
+ sessionCount(t, item.occurrences),
1137
+ ago(t, item.updatedAt, now)
1138
+ ].join(" · ");
1139
+ }
1140
+ /** 失败在草稿阶段的建议仍有摘要,留在待决定的分组里。 */
1141
+ function draftFailed(item) {
1142
+ return item.state === "failed" && item.failure?.stage === "draft";
1143
+ }
1144
+ /** 等用户决定的建议:标题栏的数字只数这些。 */
1145
+ function needsReview(item) {
1146
+ return item.state === "proposed" || item.state === "drafting" || item.state === "ready" || draftFailed(item);
1147
+ }
1148
+ /** 按状态分组:待决定、本地发现、历史。 */
1149
+ function groupItems(items) {
1150
+ return {
1151
+ review: items.filter(needsReview),
1152
+ found: items.filter((item) => item.state === "detected" || item.state === "summarizing" || item.state === "failed" && !draftFailed(item)),
1153
+ history: items.filter((item) => item.state === "written" || item.state === "skipped" || item.state === "dismissed")
1154
+ };
1155
+ }
1156
+ /** 把 `~` 开头之外的长路径缩短到末尾几段。 */
1157
+ function shortPath(path) {
1158
+ const parts = path.split("/");
1159
+ return parts.length <= 4 ? path : `…/${parts.slice(-4).join("/")}`;
1160
+ }
1161
+ //#endregion
1162
+ //#region src/client/detail.tsx
1163
+ /**
1164
+ * 一条建议的详情:摘要、证据、Skill 预览与编辑、文件改动,以及生成摘要、同意生成草稿、保存、忽略、撤销。
1165
+ * 证据、摘要与草稿从 Host 读取;建议的状态跟随收件箱刷新,状态或更新时间变化时重新读取详情。
1166
+ */
1167
+ function message(error) {
1168
+ return error instanceof Error ? error.message : String(error);
1169
+ }
1170
+ /** Markdown 与 diff 视图的文案。 */
1171
+ function useLabels(t) {
1172
+ return (0, react.useMemo)(() => {
1173
+ const toolbar = {
1174
+ codeLabel: t("code.label"),
1175
+ wrapLabel: t("code.wrap"),
1176
+ unwrapLabel: t("code.unwrap")
1177
+ };
1178
+ return {
1179
+ markdown: {
1180
+ code: {
1181
+ copyLabel: t("code.copy"),
1182
+ copiedLabel: t("code.copied"),
1183
+ toolbarLabels: toolbar
1184
+ },
1185
+ footnotes: t("code.footnotes")
1186
+ },
1187
+ diff: {
1188
+ ...toolbar,
1189
+ copy: t("code.copy"),
1190
+ copied: t("code.copied"),
1191
+ collapse: t("diff.collapse"),
1192
+ collapseAria: t("diff.collapseAria"),
1193
+ expand: (count) => t("diff.expand", { count }),
1194
+ expandAria: (count) => t("diff.expand", { count })
1195
+ }
1196
+ };
1197
+ }, [t]);
1198
+ }
1199
+ const ROLE_KEYS = {
1200
+ verify: "role.verify",
1201
+ failure: "role.failure",
1202
+ fix: "role.fix",
1203
+ recovered: "role.recovered"
1204
+ };
1205
+ function Episode({ episode, now, openSession, t }) {
1206
+ const outcome = `outcome.${episode.outcome}`;
1207
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
1208
+ className: detail_module_css_default.episode,
1209
+ "aria-label": episode.goal,
1210
+ children: [
1211
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1212
+ className: detail_module_css_default.episodeHead,
1213
+ children: [
1214
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1215
+ className: detail_module_css_default.ref,
1216
+ children: episode.ref
1217
+ }),
1218
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1219
+ className: detail_module_css_default.episodeText,
1220
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1221
+ className: detail_module_css_default.goal,
1222
+ children: episode.goal
1223
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
1224
+ className: detail_module_css_default.meta,
1225
+ children: [
1226
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1227
+ "data-outcome": episode.outcome,
1228
+ children: isKey(outcome) ? t(outcome) : episode.outcome
1229
+ }),
1230
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: ago(t, episode.at, now) }),
1231
+ episode.cwd !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1232
+ className: detail_module_css_default.path,
1233
+ children: shortPath(episode.cwd)
1234
+ })
1235
+ ]
1236
+ })]
1237
+ }),
1238
+ openSession !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1239
+ size: "sm",
1240
+ variant: "ghost",
1241
+ onClick: () => openSession(episode.sessionId),
1242
+ children: t("detail.openSession")
1243
+ })
1244
+ ]
1245
+ }),
1246
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ol", {
1247
+ className: detail_module_css_default.ops,
1248
+ "aria-label": t("evidence.list", { ref: episode.ref }),
1249
+ children: episode.ops.map((op) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
1250
+ className: detail_module_css_default.op,
1251
+ "data-ok": op.ok,
1252
+ "data-cited": op.cited || void 0,
1253
+ children: [
1254
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1255
+ className: detail_module_css_default.opGlyph,
1256
+ "aria-hidden": "true",
1257
+ 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 })
1258
+ }),
1259
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1260
+ className: detail_module_css_default.opBody,
1261
+ children: [
1262
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
1263
+ className: detail_module_css_default.opLabel,
1264
+ "data-kind": op.kind,
1265
+ children: op.label
1266
+ }),
1267
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1268
+ className: detail_module_css_default.opTags,
1269
+ children: [
1270
+ op.role !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1271
+ className: detail_module_css_default.tag,
1272
+ children: t(ROLE_KEYS[op.role])
1273
+ }),
1274
+ op.cited && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1275
+ className: detail_module_css_default.tag,
1276
+ "data-cited": "",
1277
+ children: t("role.cited")
1278
+ }),
1279
+ op.exit !== null && op.exit !== 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1280
+ className: detail_module_css_default.tag,
1281
+ children: t("evidence.exit", { code: op.exit })
1282
+ })
1283
+ ]
1284
+ }),
1285
+ op.error !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1286
+ className: detail_module_css_default.opError,
1287
+ children: op.error
1288
+ })
1289
+ ]
1290
+ }),
1291
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1292
+ className: detail_module_css_default.opRef,
1293
+ children: op.ref
1294
+ })
1295
+ ]
1296
+ }, op.ref))
1297
+ }),
1298
+ episode.reads > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1299
+ className: detail_module_css_default.note,
1300
+ children: t(episode.reads === 1 ? "evidence.reads.one" : "evidence.reads.other", { count: episode.reads })
1301
+ }),
1302
+ keyed(episode.steering).map(({ key, text }) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
1303
+ className: detail_module_css_default.note,
1304
+ children: [
1305
+ t("evidence.steering"),
1306
+ ": ",
1307
+ text
1308
+ ]
1309
+ }, key)),
1310
+ episode.followup !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
1311
+ className: detail_module_css_default.note,
1312
+ children: [
1313
+ t("evidence.followup"),
1314
+ ": ",
1315
+ episode.followup
1316
+ ]
1317
+ })
1318
+ ]
1319
+ });
1320
+ }
1321
+ const KEYS = /* @__PURE__ */ new Set([
1322
+ "outcome.verified",
1323
+ "outcome.succeeded",
1324
+ "outcome.unverified",
1325
+ "outcome.disputed",
1326
+ "outcome.failed",
1327
+ "outcome.aborted"
1328
+ ]);
1329
+ function isKey(key) {
1330
+ return KEYS.has(key);
1331
+ }
1332
+ function FieldsEditor({ fields, onChange, t }) {
1333
+ const id = (0, react.useId)();
1334
+ const field = (key, rows, mono = false) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
1335
+ className: detail_module_css_default.field,
1336
+ htmlFor: `${id}-${key}`,
1337
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1338
+ className: detail_module_css_default.fieldLabel,
1339
+ children: t(`skill.${key}`)
1340
+ }), rows === 1 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1341
+ id: `${id}-${key}`,
1342
+ className: detail_module_css_default.input,
1343
+ value: fields[key],
1344
+ spellCheck: false,
1345
+ onChange: (event) => onChange({
1346
+ ...fields,
1347
+ [key]: event.target.value
1348
+ })
1349
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
1350
+ id: `${id}-${key}`,
1351
+ className: mono ? `${detail_module_css_default.input} ${detail_module_css_default.mono}` : detail_module_css_default.input,
1352
+ rows,
1353
+ value: fields[key],
1354
+ onChange: (event) => onChange({
1355
+ ...fields,
1356
+ [key]: event.target.value
1357
+ })
1358
+ })]
1359
+ });
1360
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1361
+ className: detail_module_css_default.editor,
1362
+ children: [
1363
+ field("name", 1),
1364
+ field("description", 2),
1365
+ field("whenToUse", 2),
1366
+ field("body", 14, true)
1367
+ ]
1368
+ });
1369
+ }
1370
+ /** 给可能重复的文本行配上稳定的 key:文本加上它第几次出现。 */
1371
+ function keyed(lines) {
1372
+ const seen = /* @__PURE__ */ new Map();
1373
+ return lines.map((text) => {
1374
+ const count = (seen.get(text) ?? 0) + 1;
1375
+ seen.set(text, count);
1376
+ return {
1377
+ key: `${text}#${String(count)}`,
1378
+ text
1379
+ };
1380
+ });
1381
+ }
1382
+ /** 摘要占位条的宽度:几行短文本。 */
1383
+ const SUMMARY_BARS = [
1384
+ "88%",
1385
+ "72%",
1386
+ "46%"
1387
+ ];
1388
+ /** 草稿占位条的宽度,按 SKILL.md 的排版:标题、描述,空一行,一节标题与几行步骤;空串是段间空行。 */
1389
+ const DRAFT_BARS = [
1390
+ "36%",
1391
+ "94%",
1392
+ "82%",
1393
+ "",
1394
+ "22%",
1395
+ "90%",
1396
+ "74%",
1397
+ "84%",
1398
+ "58%"
1399
+ ];
1400
+ /** 相邻占位条的起伏错开的时间,连起来是一道自上而下的波。 */
1401
+ const BAR_STAGGER = 70;
1402
+ /** 模型写作时的占位:按最终排版画出的几条横条,依次快速明暗起伏;减少动态效果时静止。 */
1403
+ function Skeleton({ bars }) {
1404
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1405
+ className: detail_module_css_default.skeleton,
1406
+ "aria-hidden": "true",
1407
+ children: keyed(bars).map(({ key, text: width }, index) => width === "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: detail_module_css_default.skeletonGap }, key) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1408
+ className: detail_module_css_default.skeletonBar,
1409
+ style: {
1410
+ width,
1411
+ animationDelay: `${String(index * BAR_STAGGER)}ms`
1412
+ }
1413
+ }, key))
1414
+ });
1415
+ }
1416
+ /** 模型正在处理时的一行状态与占位。 */
1417
+ function Working({ label, bars }) {
1418
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1419
+ className: detail_module_css_default.working,
1420
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1421
+ className: detail_module_css_default.workingLabel,
1422
+ role: "status",
1423
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.TextShimmer, {
1424
+ active: true,
1425
+ children: label
1426
+ })
1427
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Skeleton, { bars })]
1428
+ });
1429
+ }
1430
+ /** 一条建议的详情对话框。 */
1431
+ function Detail({ id, item, inbox, loadDetail, models, defaultScope, openSession, t, onClose }) {
1432
+ const tabsId = (0, react.useId)();
1433
+ const labels = useLabels(t);
1434
+ const [detail, setDetail] = (0, react.useState)({ status: "loading" });
1435
+ /** 用户选中的页签;为 null 时跟随状态:草稿生成中与可以保存时看 Skill,其余看摘要。 */
1436
+ const [chosenTab, setChosenTab] = (0, react.useState)(null);
1437
+ const [scope, setScope] = (0, react.useState)(defaultScope);
1438
+ const [editing, setEditing] = (0, react.useState)(false);
1439
+ const [fields, setFields] = (0, react.useState)(null);
1440
+ const [section, setSection] = (0, react.useState)(null);
1441
+ const [preview, setPreview] = (0, react.useState)(null);
1442
+ const [busy, setBusy] = (0, react.useState)(null);
1443
+ const [error, setError] = (0, react.useState)(null);
1444
+ const [now] = (0, react.useState)(() => Date.now());
1445
+ const version = `${item.state}:${String(item.updatedAt)}`;
1446
+ (0, react.useEffect)(() => {
1447
+ const controller = new AbortController();
1448
+ loadDetail(id, version, controller.signal).then((value) => {
1449
+ if (controller.signal.aborted) return;
1450
+ setDetail({
1451
+ status: "ready",
1452
+ value
1453
+ });
1454
+ }, (reason) => {
1455
+ if (controller.signal.aborted) return;
1456
+ setDetail({
1457
+ status: "error",
1458
+ error: message(reason)
1459
+ });
1460
+ });
1461
+ return () => controller.abort();
1462
+ }, [
1463
+ id,
1464
+ version,
1465
+ loadDetail
1466
+ ]);
1467
+ const value = detail.status === "ready" ? detail.value : null;
1468
+ const create = item.decision === "suggest_create";
1469
+ const improve = item.decision === "suggest_improve";
1470
+ const proposal = create || improve;
1471
+ const drafted = value !== null && (value.draft !== null || value.section !== null);
1472
+ const draft = fields ?? value?.draft ?? null;
1473
+ const shownSection = section ?? value?.section ?? null;
1474
+ const parsed = draft === null ? null : /* @__PURE__ */ safeParse(SkillFieldsSchema, draft);
1475
+ const invalid = create && parsed !== null && !parsed.success;
1476
+ const draftFailed = item.state === "failed" && item.failure?.stage === "draft";
1477
+ /** 摘要已经给出计划,等用户同意生成草稿。 */
1478
+ const awaitingConsent = proposal && (item.state === "proposed" || draftFailed);
1479
+ const wantPreview = drafted && item.state === "ready";
1480
+ (0, react.useEffect)(() => {
1481
+ if (!wantPreview || invalid) return void 0;
1482
+ let stale = false;
1483
+ const timer = setTimeout(() => {
1484
+ setPreview((current) => current === null ? { status: "loading" } : current);
1485
+ const action = {
1486
+ action: "preview",
1487
+ id,
1488
+ ...create ? { scope } : {},
1489
+ ...fields === null ? {} : { fields },
1490
+ ...section === null ? {} : { section }
1491
+ };
1492
+ inbox.act(action).then((result) => {
1493
+ if (stale) return;
1494
+ if (result.ok && result.preview !== void 0) setPreview({
1495
+ status: "ready",
1496
+ value: result.preview
1497
+ });
1498
+ else setPreview({
1499
+ status: "error",
1500
+ error: result.ok ? "" : t(`error.${result.error.kind}`)
1501
+ });
1502
+ }, (reason) => {
1503
+ if (!stale) setPreview({
1504
+ status: "error",
1505
+ error: message(reason)
1506
+ });
1507
+ });
1508
+ }, fields === null && section === null ? 0 : 300);
1509
+ return () => {
1510
+ stale = true;
1511
+ clearTimeout(timer);
1512
+ };
1513
+ }, [
1514
+ wantPreview,
1515
+ invalid,
1516
+ id,
1517
+ create,
1518
+ scope,
1519
+ fields,
1520
+ section,
1521
+ inbox,
1522
+ t
1523
+ ]);
1524
+ const run = async (kind, action, notice) => {
1525
+ setBusy(kind);
1526
+ setError(null);
1527
+ try {
1528
+ const result = await inbox.act(action, notice);
1529
+ if (!result.ok) setError(t(`error.${result.error.kind}`));
1530
+ return result.ok;
1531
+ } catch (reason) {
1532
+ setError(t("error.request", { error: message(reason) }));
1533
+ return false;
1534
+ } finally {
1535
+ setBusy(null);
1536
+ }
1537
+ };
1538
+ const skillName = item.skill ?? "";
1539
+ const summarize = () => {
1540
+ run("summarize", {
1541
+ action: "summarize",
1542
+ id
1543
+ });
1544
+ };
1545
+ /** 同意摘要,生成草稿;之前的编辑与预览作废,页签回到跟随状态。 */
1546
+ const generate = () => {
1547
+ setFields(null);
1548
+ setSection(null);
1549
+ setEditing(false);
1550
+ setPreview(null);
1551
+ setChosenTab(null);
1552
+ run("draft", {
1553
+ action: "draft",
1554
+ id
1555
+ });
1556
+ };
1557
+ const dismiss = async (similar) => {
1558
+ if (await run("dismiss", {
1559
+ action: "dismiss",
1560
+ id,
1561
+ similar
1562
+ }, {
1563
+ key: similar ? "notice.dismissedSimilar" : "notice.dismissed",
1564
+ params: {},
1565
+ undo: {
1566
+ action: "restore",
1567
+ id
1568
+ }
1569
+ })) onClose();
1570
+ };
1571
+ const save = async () => {
1572
+ if (await run("save", {
1573
+ action: "write",
1574
+ id,
1575
+ ...create ? { scope } : {},
1576
+ ...fields === null ? {} : { fields },
1577
+ ...section === null ? {} : { section },
1578
+ ...preview?.status === "ready" && preview.value.kind === "improve" ? { baseHash: preview.value.baseHash } : {}
1579
+ }, {
1580
+ key: "notice.written",
1581
+ params: { skill: draft?.name ?? skillName },
1582
+ undo: {
1583
+ action: "undo",
1584
+ id
1585
+ }
1586
+ })) {
1587
+ setEditing(false);
1588
+ setPreview(null);
1589
+ }
1590
+ };
1591
+ const tabs = [
1592
+ {
1593
+ value: "summary",
1594
+ label: t("detail.tab.summary")
1595
+ },
1596
+ ...item.state === "drafting" || drafted ? [{
1597
+ value: "skill",
1598
+ label: t("detail.tab.skill")
1599
+ }] : [],
1600
+ ...item.state === "ready" && drafted ? [{
1601
+ value: "changes",
1602
+ label: t("detail.tab.changes")
1603
+ }] : [],
1604
+ {
1605
+ value: "evidence",
1606
+ label: t("detail.tab.evidence")
1607
+ }
1608
+ ];
1609
+ const autoTab = item.state === "drafting" || item.state === "ready" ? "skill" : "summary";
1610
+ const wanted = chosenTab ?? autoTab;
1611
+ const activeTab = tabs.some((entry) => entry.value === wanted) ? wanted : "summary";
1612
+ const subtitle = [
1613
+ t(`kind.${item.kind}`),
1614
+ sessionCount(t, item.occurrences),
1615
+ value?.draftCall != null ? t("detail.draftedBy", { model: value.draftCall.model }) : value?.summary != null ? t("detail.summarizedBy", { model: value.summary.model }) : stateLabel(t, item)
1616
+ ].join(" · ");
1617
+ const failure = (stage) => item.state === "failed" && item.failure?.stage === stage ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
1618
+ className: detail_module_css_default.failure,
1619
+ role: "alert",
1620
+ children: [t(stage === "summary" ? "detail.failed.summary" : "detail.failed.draft", { kind: t(`failure.${item.failure.kind}`) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1621
+ className: detail_module_css_default.failureDetail,
1622
+ children: item.failure.message
1623
+ })]
1624
+ }) : null;
1625
+ /** 规则匹配到的步骤,原来的写法。 */
1626
+ const steps = () => item.steps.length === 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
1627
+ className: detail_module_css_default.block,
1628
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
1629
+ className: detail_module_css_default.heading,
1630
+ children: t("detail.steps")
1631
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ol", {
1632
+ className: detail_module_css_default.steps,
1633
+ children: keyed(item.steps).map(({ key, text }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", { children: text }) }, key))
1634
+ })]
1635
+ });
1636
+ /** 摘要里的方案:发现了什么、Skill 做什么、何时使用、将要新建或改进哪个 Skill 与提纲、风险、验证方式。 */
1637
+ const plan = () => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("dl", {
1638
+ className: detail_module_css_default.summary,
1639
+ children: [
1640
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", { children: t("detail.found") }),
1641
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", { children: item.found }),
1642
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", { children: t("detail.description") }),
1643
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", { children: item.description }),
1644
+ item.trigger !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", { children: t("detail.trigger") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", { children: item.trigger })] }),
1645
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", { children: t("detail.plan") }),
1646
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("dd", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
1647
+ className: detail_module_css_default.planTarget,
1648
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t(create ? "detail.plan.create" : "detail.plan.improve") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", { children: skillName })]
1649
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ol", {
1650
+ className: detail_module_css_default.outline,
1651
+ children: keyed(item.outline).map(({ key, text }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: text }, key))
1652
+ })] }),
1653
+ item.risks.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", { children: t("detail.risks") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
1654
+ className: detail_module_css_default.risks,
1655
+ children: keyed(item.risks).map(({ key, text }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: text }, key))
1656
+ }) })] }),
1657
+ item.verification !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", { children: t("detail.verification") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", { children: item.verification })] })
1658
+ ]
1659
+ });
1660
+ const summaryTab = () => {
1661
+ if (item.state === "summarizing") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1662
+ className: detail_module_css_default.stack,
1663
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Working, {
1664
+ label: t("detail.summarizing", { model: models.summary ?? "" }),
1665
+ bars: SUMMARY_BARS
1666
+ }), steps()]
1667
+ });
1668
+ if (proposal) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1669
+ className: detail_module_css_default.stack,
1670
+ children: [plan(), awaitingConsent && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1671
+ className: detail_module_css_default.callout,
1672
+ children: [failure("draft"), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1673
+ className: detail_module_css_default.note,
1674
+ children: models.draft === null ? t("detail.noDraftModel") : t("detail.consent", { model: models.draft })
1675
+ })]
1676
+ })]
1677
+ });
1678
+ if (item.decision === "skip") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1679
+ className: detail_module_css_default.stack,
1680
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1681
+ className: detail_module_css_default.callout,
1682
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t("detail.skipped") }), item.reason !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1683
+ className: detail_module_css_default.note,
1684
+ children: item.reason
1685
+ })]
1686
+ }), steps()]
1687
+ });
1688
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1689
+ className: detail_module_css_default.stack,
1690
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1691
+ className: detail_module_css_default.callout,
1692
+ children: [
1693
+ failure("summary"),
1694
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t("detail.local") }),
1695
+ item.state !== "written" && item.state !== "dismissed" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1696
+ className: detail_module_css_default.note,
1697
+ children: models.summary === null ? t("detail.noSummaryModel") : t("detail.summaryNote", {
1698
+ count: item.sessions.length,
1699
+ model: models.summary
1700
+ })
1701
+ })
1702
+ ]
1703
+ }), steps()]
1704
+ });
1705
+ };
1706
+ const evidence = () => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1707
+ className: detail_module_css_default.stack,
1708
+ children: value?.episodes.map((episode) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Episode, {
1709
+ episode,
1710
+ now,
1711
+ openSession,
1712
+ t
1713
+ }, episode.episodeId))
1714
+ });
1715
+ const cautions = () => item.cautions.length === 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1716
+ className: detail_module_css_default.caution,
1717
+ role: "note",
1718
+ children: t("detail.cautions", { list: item.cautions.map((caution) => {
1719
+ const key = `caution.${caution}`;
1720
+ return isCaution(key) ? t(key) : caution;
1721
+ }).join(", ") })
1722
+ });
1723
+ /** 草稿页签的工具栏:编辑、还原,以及按同一份摘要重新生成。 */
1724
+ const toolbar = (lead) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1725
+ className: detail_module_css_default.toolbar,
1726
+ children: [lead, /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1727
+ className: detail_module_css_default.toolbarActions,
1728
+ children: [
1729
+ item.state === "ready" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1730
+ size: "sm",
1731
+ variant: "ghost",
1732
+ disabled: busy !== null,
1733
+ onClick: generate,
1734
+ children: t("detail.redraft")
1735
+ }),
1736
+ (create ? fields : section) !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1737
+ size: "sm",
1738
+ variant: "ghost",
1739
+ onClick: () => create ? setFields(null) : setSection(null),
1740
+ children: t("skill.reset")
1741
+ }),
1742
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1743
+ size: "sm",
1744
+ variant: "ghost",
1745
+ onClick: () => setEditing(!editing),
1746
+ disabled: item.state !== "ready",
1747
+ children: editing ? t("skill.preview") : t("skill.edit")
1748
+ })
1749
+ ]
1750
+ })]
1751
+ });
1752
+ const skill = () => {
1753
+ if (item.state === "drafting") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Working, {
1754
+ label: t("detail.drafting", { model: models.draft ?? "" }),
1755
+ bars: DRAFT_BARS
1756
+ });
1757
+ if (create && draft !== null) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1758
+ className: detail_module_css_default.stack,
1759
+ children: [
1760
+ cautions(),
1761
+ toolbar(/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {})),
1762
+ editing ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(FieldsEditor, {
1763
+ fields: draft,
1764
+ onChange: setFields,
1765
+ t
1766
+ }), invalid && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1767
+ className: detail_module_css_default.failure,
1768
+ role: "alert",
1769
+ children: t("skill.invalid")
1770
+ })] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("dl", {
1771
+ className: detail_module_css_default.facts,
1772
+ children: [
1773
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", { children: t("skill.name") }),
1774
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", { children: draft.name }) }),
1775
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", { children: t("skill.description") }),
1776
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", { children: draft.description }),
1777
+ 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 })] })
1778
+ ]
1779
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1780
+ className: detail_module_css_default.markdown,
1781
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.MarkdownText, {
1782
+ text: draft.body,
1783
+ labels: labels.markdown
1784
+ })
1785
+ })] })
1786
+ ]
1787
+ });
1788
+ if (improve && shownSection !== null) {
1789
+ const notWritable = preview?.status === "ready" && preview.value.kind === "improve" && !preview.value.writable;
1790
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1791
+ className: detail_module_css_default.stack,
1792
+ children: [
1793
+ notWritable && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1794
+ className: detail_module_css_default.caution,
1795
+ children: t("skill.notWritable", { skill: skillName })
1796
+ }),
1797
+ cautions(),
1798
+ toolbar(/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1799
+ className: detail_module_css_default.heading,
1800
+ children: t("skill.section", { skill: skillName })
1801
+ })),
1802
+ editing ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
1803
+ className: `${detail_module_css_default.input} ${detail_module_css_default.mono}`,
1804
+ "aria-label": t("skill.section", { skill: skillName }),
1805
+ rows: 12,
1806
+ value: shownSection,
1807
+ onChange: (event) => setSection(event.target.value)
1808
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1809
+ className: detail_module_css_default.markdown,
1810
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.MarkdownText, {
1811
+ text: shownSection,
1812
+ labels: labels.markdown
1813
+ })
1814
+ })
1815
+ ]
1816
+ });
1817
+ }
1818
+ return null;
1819
+ };
1820
+ const changes = () => {
1821
+ if (preview === null || preview.status === "loading") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1822
+ className: detail_module_css_default.note,
1823
+ role: "status",
1824
+ children: t("changes.loading")
1825
+ });
1826
+ if (preview.status === "error") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1827
+ className: detail_module_css_default.failure,
1828
+ role: "alert",
1829
+ children: t("changes.error", { error: preview.error })
1830
+ });
1831
+ const file = preview.value;
1832
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1833
+ className: detail_module_css_default.stack,
1834
+ children: [file.kind === "create" && file.exists && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1835
+ className: detail_module_css_default.caution,
1836
+ children: t("changes.exists")
1837
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.DiffBlock, {
1838
+ diffs: [file.kind === "create" ? {
1839
+ path: file.path,
1840
+ oldText: null,
1841
+ newText: file.content
1842
+ } : {
1843
+ path: file.path ?? file.skill,
1844
+ oldText: file.before,
1845
+ newText: file.after
1846
+ }],
1847
+ labels: labels.diff,
1848
+ maxLines: 400
1849
+ })]
1850
+ });
1851
+ };
1852
+ /** 右侧的主操作:随阶段从生成摘要、同意生成草稿到保存。 */
1853
+ const primary = () => {
1854
+ if (item.state === "summarizing" || item.state === "drafting") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1855
+ size: "sm",
1856
+ variant: "primary",
1857
+ disabled: true,
1858
+ children: stateLabel(t, item)
1859
+ });
1860
+ if (awaitingConsent) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1861
+ size: "sm",
1862
+ variant: "primary",
1863
+ "data-autofocus": "",
1864
+ disabled: models.draft === null || busy !== null,
1865
+ onClick: generate,
1866
+ children: draftFailed ? t("detail.redraft") : create ? t("detail.draft.create") : t("detail.draft.improve")
1867
+ });
1868
+ if (item.state === "ready") {
1869
+ const writable = !(preview?.status === "ready" && preview.value.kind === "improve" && !preview.value.writable);
1870
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [create && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.SegmentedControl, {
1871
+ id: `${tabsId}-scope`,
1872
+ label: t("scope.label"),
1873
+ value: scope,
1874
+ disabled: busy !== null,
1875
+ options: [{
1876
+ value: "project",
1877
+ label: t("scope.project")
1878
+ }, {
1879
+ value: "user",
1880
+ label: t("scope.user")
1881
+ }],
1882
+ onChange: setScope
1883
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1884
+ size: "sm",
1885
+ variant: "primary",
1886
+ "data-autofocus": "",
1887
+ disabled: busy !== null || invalid || !writable,
1888
+ onClick: () => void save(),
1889
+ children: busy === "save" ? t("action.saving") : create ? t("action.save") : t("action.improve", { skill: skillName })
1890
+ })] });
1891
+ }
1892
+ const again = item.decision === "skip" || item.failure !== null;
1893
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1894
+ size: "sm",
1895
+ variant: item.decision === "skip" ? "outline" : "primary",
1896
+ "data-autofocus": "",
1897
+ disabled: models.summary === null || busy !== null,
1898
+ onClick: summarize,
1899
+ children: again ? t("detail.resummarize") : t("detail.summarize")
1900
+ });
1901
+ };
1902
+ const footer = () => {
1903
+ if (item.state === "written" && item.write !== null) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
1904
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
1905
+ className: detail_module_css_default.written,
1906
+ role: "status",
1907
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1908
+ className: detail_module_css_default.writtenPath,
1909
+ children: t("written.path", { path: shortPath(item.write.path) })
1910
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: item.write.discovered ? t("written.discovered") : item.write.checking ? t("written.checking") : t("written.missing") })]
1911
+ }),
1912
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: detail_module_css_default.spacer }),
1913
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1914
+ size: "sm",
1915
+ variant: "outline",
1916
+ disabled: busy !== null,
1917
+ onClick: () => void run("undo", {
1918
+ action: "undo",
1919
+ id
1920
+ }, {
1921
+ key: "notice.undone",
1922
+ params: { skill: item.write?.skill ?? skillName },
1923
+ undo: null
1924
+ }),
1925
+ children: t("action.undo")
1926
+ }),
1927
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1928
+ size: "sm",
1929
+ variant: "primary",
1930
+ onClick: onClose,
1931
+ "data-autofocus": "",
1932
+ children: t("action.done")
1933
+ })
1934
+ ] });
1935
+ 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, {
1936
+ size: "sm",
1937
+ variant: "outline",
1938
+ disabled: busy !== null,
1939
+ onClick: () => void run("restore", {
1940
+ action: "restore",
1941
+ id
1942
+ }, {
1943
+ key: "notice.restored",
1944
+ params: {},
1945
+ undo: null
1946
+ }),
1947
+ children: t("action.restore")
1948
+ })] });
1949
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
1950
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1951
+ size: "sm",
1952
+ variant: "ghost",
1953
+ disabled: busy !== null,
1954
+ onClick: () => void dismiss(false),
1955
+ children: t("action.dismiss")
1956
+ }),
1957
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1958
+ size: "sm",
1959
+ variant: "ghost",
1960
+ disabled: busy !== null,
1961
+ onClick: () => void dismiss(true),
1962
+ children: t("action.dismissSimilar")
1963
+ }),
1964
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: detail_module_css_default.spacer }),
1965
+ primary()
1966
+ ] });
1967
+ };
1968
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Dialog, {
1969
+ title: titleOf(item),
1970
+ subtitle,
1971
+ closeLabel: t("detail.close"),
1972
+ busy: busy === "save" || busy === "undo",
1973
+ onClose,
1974
+ footer: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [error !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1975
+ className: detail_module_css_default.actionError,
1976
+ role: "alert",
1977
+ children: error
1978
+ }), footer()] }),
1979
+ children: [
1980
+ detail.status === "loading" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1981
+ className: detail_module_css_default.note,
1982
+ role: "status",
1983
+ children: t("detail.loading")
1984
+ }),
1985
+ detail.status === "error" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
1986
+ className: detail_module_css_default.failure,
1987
+ role: "alert",
1988
+ children: [
1989
+ t("detail.error"),
1990
+ " ",
1991
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1992
+ className: detail_module_css_default.failureDetail,
1993
+ children: detail.error
1994
+ })
1995
+ ]
1996
+ }),
1997
+ value !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1998
+ className: detail_module_css_default.tabs,
1999
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.SegmentedControl, {
2000
+ id: tabsId,
2001
+ label: t("detail.tabs"),
2002
+ value: activeTab,
2003
+ options: tabs,
2004
+ onChange: setChosenTab
2005
+ })
2006
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2007
+ className: detail_module_css_default.panel,
2008
+ role: "tabpanel",
2009
+ "aria-labelledby": `${tabsId}-${activeTab}`,
2010
+ children: [
2011
+ activeTab === "summary" && summaryTab(),
2012
+ activeTab === "evidence" && evidence(),
2013
+ activeTab === "skill" && skill(),
2014
+ activeTab === "changes" && changes()
2015
+ ]
2016
+ })] })
2017
+ ]
2018
+ });
2019
+ }
2020
+ const CAUTIONS = /* @__PURE__ */ new Set([
2021
+ "caution.sudo",
2022
+ "caution.force-push",
2023
+ "caution.no-verify",
2024
+ "caution.chmod-777",
2025
+ "caution.recursive-delete"
2026
+ ]);
2027
+ function isCaution(key) {
2028
+ return CAUTIONS.has(key);
2029
+ }
2030
+ //#endregion
2031
+ //#region src/client/hooks.ts
2032
+ /** 订阅 Observable 的 React Hook。 */
2033
+ function useSnapshot(source) {
2034
+ return (0, react.useSyncExternalStore)(source.subscribe, source.getSnapshot, source.getSnapshot);
2035
+ }
2036
+ //#endregion
2037
+ //#region \0dsh-css:src/client/inbox.module.css.mjs
2038
+ 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=summarizing] .Wfa-jG_cardTitle,.Wfa-jG_card[data-state=failed]:not([data-stage=draft]) .Wfa-jG_cardTitle{color:var(--dsw-alias-label-secondary)}.Wfa-jG_cardSummary{-webkit-line-clamp:2;color:var(--dsw-alias-label-secondary);overflow-wrap:anywhere;-webkit-box-orient:vertical;font-size:12px;line-height:17px;display:-webkit-box;overflow:hidden}.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}}";
2039
+ const tag$1 = "@agent-stash/dsh-skill-scout/inbox.module.css";
2040
+ if (document.querySelector(`style[data-plugin-css=${JSON.stringify(tag$1)}]`) === null) {
2041
+ const style = document.createElement("style");
2042
+ style.dataset.plugin = "@agent-stash/dsh-skill-scout";
2043
+ style.dataset.pluginCss = tag$1;
2044
+ style.textContent = css$1;
2045
+ document.head.appendChild(style);
2046
+ }
2047
+ var inbox_module_css_default = {
2048
+ "badge": "Wfa-jG_badge",
2049
+ "banner": "Wfa-jG_banner",
2050
+ "caption": "Wfa-jG_caption",
2051
+ "card": "Wfa-jG_card",
2052
+ "cardMeta": "Wfa-jG_cardMeta",
2053
+ "cardSummary": "Wfa-jG_cardSummary",
2054
+ "cardTitle": "Wfa-jG_cardTitle",
2055
+ "dismiss": "Wfa-jG_dismiss",
2056
+ "empty": "Wfa-jG_empty",
2057
+ "emptyBody": "Wfa-jG_emptyBody",
2058
+ "emptyTitle": "Wfa-jG_emptyTitle",
2059
+ "errorRow": "Wfa-jG_errorRow",
2060
+ "head": "Wfa-jG_head",
2061
+ "historyToggle": "Wfa-jG_historyToggle",
2062
+ "link": "Wfa-jG_link",
2063
+ "list": "Wfa-jG_list",
2064
+ "notice": "Wfa-jG_notice",
2065
+ "noticeText": "Wfa-jG_noticeText",
2066
+ "open": "Wfa-jG_open",
2067
+ "panel": "Wfa-jG_panel",
2068
+ "panel-in": "Wfa-jG_panel-in",
2069
+ "root": "Wfa-jG_root",
2070
+ "scroll": "Wfa-jG_scroll",
2071
+ "section": "Wfa-jG_section",
2072
+ "sectionTitle": "Wfa-jG_sectionTitle",
2073
+ "state": "Wfa-jG_state",
2074
+ "title": "Wfa-jG_title",
2075
+ "trigger": "Wfa-jG_trigger",
2076
+ "working": "Wfa-jG_working"
2077
+ };
2078
+ //#endregion
2079
+ //#region src/client/inbox.tsx
2080
+ /**
2081
+ * 会话标题栏里的经验提炼入口:一个图标按钮,有新建议时带一个小计数;点开是收件箱浮层,
2082
+ * 点卡片打开详情对话框。入口在第一次读取完成前不渲染,避免标题栏闪动。
2083
+ * 计数只算打开收件箱之后出现的、等用户决定的摘要与草稿,看过即清零;详情里正打开的那条在眼前,不计入。
2084
+ */
2085
+ const SEEN_KEY = "agent-stash-skill-scout:seen";
2086
+ const SeenSchema = /* @__PURE__ */ pipe(/* @__PURE__ */ string(), /* @__PURE__ */ parseJson(), /* @__PURE__ */ array(/* @__PURE__ */ string()));
2087
+ /**
2088
+ * 以 localStorage 保存看过的建议 id,只保留最近的一部分。
2089
+ * localStorage 是页面共享的外部存储:内容损坏时当作没有记录,写不进去(无痕模式、配额已满)时只记在内存里,
2090
+ * 两种情况都只影响角标计数。
2091
+ */
2092
+ function createSeenStore(storage) {
2093
+ let memory = [];
2094
+ const read = () => {
2095
+ const raw = storage.getItem(SEEN_KEY);
2096
+ if (raw === null) return memory;
2097
+ const parsed = /* @__PURE__ */ safeParse(SeenSchema, raw);
2098
+ return parsed.success ? [.../* @__PURE__ */ new Set([...parsed.output, ...memory])] : memory;
2099
+ };
2100
+ return {
2101
+ has: (id) => read().includes(id),
2102
+ add(ids) {
2103
+ memory = [.../* @__PURE__ */ new Set([...read(), ...ids])].slice(-200);
2104
+ try {
2105
+ storage.setItem(SEEN_KEY, JSON.stringify(memory));
2106
+ } catch (error) {
2107
+ if (!(error instanceof DOMException)) throw error;
2108
+ }
2109
+ }
2110
+ };
2111
+ }
2112
+ const MEASURE_STYLE = {
2113
+ visibility: "hidden",
2114
+ left: 0,
2115
+ top: 0
2116
+ };
2117
+ /** 等用户决定的摘要与草稿。 */
2118
+ function awaiting(inbox) {
2119
+ return (inbox?.items ?? []).filter((item) => item.state === "proposed" || item.state === "ready").map((item) => item.id);
2120
+ }
2121
+ /** 新建议数:等用户决定、还没看过,也不是详情里正打开的那条。 */
2122
+ function unseen(inbox, seen, viewing) {
2123
+ return awaiting(inbox).filter((id) => id !== viewing && !seen.has(id)).length;
2124
+ }
2125
+ function Card({ item, now, t, onOpen, onDismiss }) {
2126
+ const title = titleOf(item);
2127
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
2128
+ className: inbox_module_css_default.card,
2129
+ "data-state": item.state,
2130
+ "data-stage": item.failure?.stage,
2131
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
2132
+ type: "button",
2133
+ className: inbox_module_css_default.open,
2134
+ "data-item": item.id,
2135
+ onClick: () => onOpen(item.id),
2136
+ children: [
2137
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2138
+ className: inbox_module_css_default.cardTitle,
2139
+ children: title
2140
+ }),
2141
+ item.description !== null && item.state !== "written" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2142
+ className: inbox_module_css_default.cardSummary,
2143
+ children: item.description
2144
+ }),
2145
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2146
+ className: inbox_module_css_default.cardMeta,
2147
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2148
+ className: inbox_module_css_default.state,
2149
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.TextShimmer, {
2150
+ active: inFlight(item),
2151
+ children: stateLabel(t, item)
2152
+ })
2153
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: metaOf(t, item, now) })]
2154
+ })
2155
+ ]
2156
+ }), onDismiss !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2157
+ type: "button",
2158
+ className: inbox_module_css_default.dismiss,
2159
+ "aria-label": `${t("action.dismiss")}: ${title}`,
2160
+ title: t("action.dismiss"),
2161
+ onClick: () => onDismiss(item.id),
2162
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCloseOutlineRegular, { size: 12 })
2163
+ })]
2164
+ });
2165
+ }
2166
+ function Banner({ inbox, t }) {
2167
+ const { status } = inbox;
2168
+ const pending = inbox.items.some((item) => item.state === "detected" || item.state === "failed");
2169
+ let key = null;
2170
+ if (!status.discovery) key = "banner.discoveryOff";
2171
+ else if (status.models.summary === null && pending) key = "banner.noModel";
2172
+ else if (!status.lead) key = "banner.secondary";
2173
+ if (key === null) return null;
2174
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2175
+ className: inbox_module_css_default.banner,
2176
+ children: t(key)
2177
+ });
2178
+ }
2179
+ function PanelBody({ inbox, now, t, onOpen, onDismiss }) {
2180
+ const [history, setHistory] = (0, react.useState)(false);
2181
+ const groups = groupItems(inbox.items);
2182
+ if (groups.review.length === 0 && groups.found.length === 0 && groups.history.length === 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2183
+ className: inbox_module_css_default.empty,
2184
+ children: [
2185
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Banner, {
2186
+ inbox,
2187
+ t
2188
+ }),
2189
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2190
+ className: inbox_module_css_default.emptyTitle,
2191
+ children: t("panel.empty.title")
2192
+ }),
2193
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2194
+ className: inbox_module_css_default.emptyBody,
2195
+ children: t("panel.empty.body")
2196
+ }),
2197
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2198
+ className: inbox_module_css_default.caption,
2199
+ children: t("panel.analyzed", { sessions: inbox.status.analyzed.sessions })
2200
+ })
2201
+ ]
2202
+ });
2203
+ const section = (key, items) => items.length === 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
2204
+ className: inbox_module_css_default.section,
2205
+ "aria-label": t(key),
2206
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
2207
+ className: inbox_module_css_default.sectionTitle,
2208
+ children: t(key)
2209
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
2210
+ className: inbox_module_css_default.list,
2211
+ children: items.map((item) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Card, {
2212
+ item,
2213
+ now,
2214
+ t,
2215
+ onOpen,
2216
+ onDismiss: inFlight(item) ? void 0 : onDismiss
2217
+ }, item.id))
2218
+ })]
2219
+ });
2220
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
2221
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Banner, {
2222
+ inbox,
2223
+ t
2224
+ }),
2225
+ section("section.review", groups.review),
2226
+ section("section.found", groups.found),
2227
+ groups.history.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
2228
+ className: inbox_module_css_default.section,
2229
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2230
+ type: "button",
2231
+ className: inbox_module_css_default.historyToggle,
2232
+ "aria-expanded": history,
2233
+ onClick: () => setHistory(!history),
2234
+ children: history ? t("history.hide") : t("history.show", { count: groups.history.length })
2235
+ }), history && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
2236
+ className: inbox_module_css_default.list,
2237
+ children: groups.history.map((item) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Card, {
2238
+ item,
2239
+ now,
2240
+ t,
2241
+ onOpen
2242
+ }, item.id))
2243
+ })]
2244
+ })
2245
+ ] });
2246
+ }
2247
+ /** 标题栏入口与收件箱浮层。 */
2248
+ function InboxEntry({ inbox, seen, loadDetail, openSession, t }) {
2249
+ const state = useSnapshot(inbox);
2250
+ const [open, setOpen] = (0, react.useState)(false);
2251
+ const [now, setNow] = (0, react.useState)(() => Date.now());
2252
+ const [detailId, setDetailId] = (0, react.useState)(null);
2253
+ /** 详情关闭、收件箱重新打开后要聚焦的卡片。 */
2254
+ const [returnTo, setReturnTo] = (0, react.useState)(null);
2255
+ const [, setSeenVersion] = (0, react.useState)(0);
2256
+ const rootRef = (0, react.useRef)(null);
2257
+ const triggerRef = (0, react.useRef)(null);
2258
+ const panelRef = (0, react.useRef)(null);
2259
+ const position = (0, _deepseek_ai_dsh_client_ui_primitives.useAnchoredPosition)({
2260
+ open,
2261
+ anchorRef: triggerRef,
2262
+ panelRef,
2263
+ side: "bottom",
2264
+ gap: 5,
2265
+ margin: 16
2266
+ });
2267
+ (0, _deepseek_ai_dsh_client_ui_primitives.useDismissOnOutsidePointer)(rootRef, open && detailId === null, setOpen, panelRef);
2268
+ (0, react.useEffect)(() => {
2269
+ if (!open || returnTo === null || position === null) return;
2270
+ const panelElement = panelRef.current;
2271
+ (panelElement?.querySelector(`[data-item="${CSS.escape(returnTo)}"]`) ?? panelElement)?.focus();
2272
+ }, [
2273
+ open,
2274
+ returnTo,
2275
+ position
2276
+ ]);
2277
+ (0, react.useEffect)(() => {
2278
+ if (state.inbox === null) return;
2279
+ const ids = awaiting(state.inbox).filter((id) => open || id === detailId);
2280
+ if (ids.some((id) => !seen.has(id))) seen.add(ids);
2281
+ }, [
2282
+ open,
2283
+ detailId,
2284
+ state.inbox,
2285
+ seen
2286
+ ]);
2287
+ if (state.inbox === null && state.status !== "error") return null;
2288
+ const fresh = unseen(state.inbox, seen, detailId);
2289
+ const badge = state.inbox?.status.notify === "badge" && fresh > 0;
2290
+ const label = fresh === 0 ? t("entry.label") : t(fresh === 1 ? "entry.count.one" : "entry.count.other", { count: fresh });
2291
+ const asking = (state.inbox?.status.working ?? 0) > 0;
2292
+ const working = asking || state.inbox?.status.scanning === true;
2293
+ /** 打开收件箱:刷新状态,并把当前的待决定建议记为看过。 */
2294
+ const show = () => {
2295
+ seen.add(awaiting(state.inbox));
2296
+ setSeenVersion((version) => version + 1);
2297
+ setNow(Date.now());
2298
+ setOpen(true);
2299
+ inbox.refresh();
2300
+ };
2301
+ const close = () => {
2302
+ setOpen(false);
2303
+ setReturnTo(null);
2304
+ setSeenVersion((version) => version + 1);
2305
+ triggerRef.current?.focus();
2306
+ };
2307
+ const onKeyDown = (event) => {
2308
+ if (event.key !== "Escape" || !open || detailId !== null) return;
2309
+ event.preventDefault();
2310
+ close();
2311
+ };
2312
+ const openDetail = (id) => {
2313
+ setOpen(false);
2314
+ setDetailId(id);
2315
+ };
2316
+ const closeDetail = () => {
2317
+ setReturnTo(detailId);
2318
+ setDetailId(null);
2319
+ show();
2320
+ };
2321
+ const dismiss = (id) => {
2322
+ inbox.act({
2323
+ action: "dismiss",
2324
+ id,
2325
+ similar: false
2326
+ }, {
2327
+ key: "notice.dismissed",
2328
+ params: {},
2329
+ undo: {
2330
+ action: "restore",
2331
+ id
2332
+ }
2333
+ });
2334
+ };
2335
+ const detailItem = detailId === null ? void 0 : state.inbox?.items.find((item) => item.id === detailId);
2336
+ const notice = state.notice;
2337
+ const panel = open ? (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2338
+ ref: panelRef,
2339
+ className: inbox_module_css_default.panel,
2340
+ style: position ?? MEASURE_STYLE,
2341
+ role: "dialog",
2342
+ "aria-label": t("panel.title"),
2343
+ tabIndex: -1,
2344
+ onKeyDown,
2345
+ children: [
2346
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2347
+ className: inbox_module_css_default.head,
2348
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
2349
+ className: inbox_module_css_default.title,
2350
+ children: t("panel.title")
2351
+ }), working && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2352
+ className: inbox_module_css_default.working,
2353
+ role: "status",
2354
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.TextShimmer, {
2355
+ active: true,
2356
+ children: asking ? t("panel.working") : t("panel.analyzing")
2357
+ })
2358
+ })]
2359
+ }),
2360
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2361
+ className: inbox_module_css_default.scroll,
2362
+ children: [
2363
+ state.status === "error" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2364
+ className: inbox_module_css_default.errorRow,
2365
+ role: "alert",
2366
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("panel.error") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2367
+ type: "button",
2368
+ className: inbox_module_css_default.link,
2369
+ onClick: () => void inbox.refresh(),
2370
+ children: t("panel.retry")
2371
+ })]
2372
+ }),
2373
+ state.inbox === null && state.status === "loading" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2374
+ className: inbox_module_css_default.caption,
2375
+ role: "status",
2376
+ children: t("panel.loading")
2377
+ }),
2378
+ state.inbox !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PanelBody, {
2379
+ inbox: state.inbox,
2380
+ now,
2381
+ t,
2382
+ onOpen: openDetail,
2383
+ onDismiss: dismiss
2384
+ })
2385
+ ]
2386
+ }),
2387
+ notice !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2388
+ className: inbox_module_css_default.notice,
2389
+ role: "status",
2390
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2391
+ className: inbox_module_css_default.noticeText,
2392
+ children: t(notice.key, notice.params)
2393
+ }), notice.undo !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2394
+ type: "button",
2395
+ className: inbox_module_css_default.link,
2396
+ onClick: () => {
2397
+ const action = notice.undo;
2398
+ inbox.clearNotice();
2399
+ if (action !== null) inbox.act(action);
2400
+ },
2401
+ children: t("notice.undo")
2402
+ })]
2403
+ })
2404
+ ]
2405
+ }), document.body) : null;
2406
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2407
+ ref: rootRef,
2408
+ className: inbox_module_css_default.root,
2409
+ onKeyDown,
2410
+ children: [
2411
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
2412
+ ref: triggerRef,
2413
+ type: "button",
2414
+ className: inbox_module_css_default.trigger,
2415
+ "aria-label": label,
2416
+ "aria-expanded": open,
2417
+ "aria-haspopup": "dialog",
2418
+ title: t("entry.label"),
2419
+ onClick: () => open ? close() : show(),
2420
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconSkillOutlineRegular, { size: 16 }), badge && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2421
+ className: inbox_module_css_default.badge,
2422
+ "aria-hidden": "true",
2423
+ children: fresh > 9 ? "9+" : fresh
2424
+ })]
2425
+ }),
2426
+ panel,
2427
+ detailId !== null && detailItem !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Detail, {
2428
+ id: detailId,
2429
+ item: detailItem,
2430
+ inbox,
2431
+ loadDetail,
2432
+ models: {
2433
+ summary: state.inbox?.status.models.summary?.model ?? null,
2434
+ draft: state.inbox?.status.models.draft?.model ?? null
2435
+ },
2436
+ defaultScope: state.inbox?.status.scope ?? "project",
2437
+ openSession,
2438
+ t,
2439
+ onClose: closeDetail
2440
+ })
2441
+ ]
2442
+ });
2443
+ }
2444
+ //#endregion
2445
+ //#region src/client/locale.ts
2446
+ const SCOUT_LOCALE = "agent-stash-skill-scout";
2447
+ /** 两种语言的字典。 */
2448
+ const SCOUT_DICTIONARIES = {
2449
+ en: {
2450
+ "entry.label": "Skill suggestions",
2451
+ "entry.count.one": "Skill suggestions, {count} new",
2452
+ "entry.count.other": "Skill suggestions, {count} new",
2453
+ "panel.title": "Skill suggestions",
2454
+ "panel.analyzing": "Analyzing sessions",
2455
+ "panel.working": "Asking the model",
2456
+ "panel.loading": "Loading",
2457
+ "panel.error": "Could not load suggestions.",
2458
+ "panel.retry": "Retry",
2459
+ "panel.empty.title": "No suggestions",
2460
+ "panel.empty.body": "A suggestion appears when a workflow repeats across sessions or a fix passes a check.",
2461
+ "panel.analyzed": "{sessions} sessions analyzed",
2462
+ "section.review": "Needs your decision",
2463
+ "section.found": "Found locally",
2464
+ "history.show": "History ({count})",
2465
+ "history.hide": "Hide history",
2466
+ "banner.noModel": "No discovery model is available. Choose one in Skill Scout settings.",
2467
+ "banner.discoveryOff": "Session analysis is off.",
2468
+ "banner.secondary": "Another DSH window is analyzing sessions.",
2469
+ "notice.dismissed": "Dismissed",
2470
+ "notice.dismissedSimilar": "Dismissed this and similar suggestions",
2471
+ "notice.restored": "Restored",
2472
+ "notice.written": "Saved {skill}",
2473
+ "notice.undone": "Undid the change to {skill}",
2474
+ "notice.undo": "Undo",
2475
+ "kind.repeated": "Repeated workflow",
2476
+ "kind.recovery": "Verified fix",
2477
+ "kind.complex": "Long verified task",
2478
+ "meta.sessions.one": "{count} session",
2479
+ "meta.sessions.other": "{count} sessions",
2480
+ "state.detected": "No summary yet",
2481
+ "state.summarizing": "Summarizing",
2482
+ "state.proposed": "Summary ready",
2483
+ "state.skipped": "Not worth saving",
2484
+ "state.drafting": "Drafting",
2485
+ "state.ready": "Draft ready",
2486
+ "state.failed": "Summary failed",
2487
+ "state.draftFailed": "Draft failed",
2488
+ "state.dismissed": "Dismissed",
2489
+ "state.written": "Saved",
2490
+ "time.now": "just now",
2491
+ "time.minutes": "{count} min ago",
2492
+ "time.hours": "{count} h ago",
2493
+ "time.days": "{count} d ago",
2494
+ "detail.close": "Close",
2495
+ "detail.tab.summary": "Summary",
2496
+ "detail.tab.evidence": "Evidence",
2497
+ "detail.tab.skill": "Skill",
2498
+ "detail.tab.changes": "Changes",
2499
+ "detail.tabs": "Suggestion sections",
2500
+ "detail.why": "Why",
2501
+ "detail.loading": "Loading the evidence",
2502
+ "detail.error": "Could not load this suggestion.",
2503
+ "detail.summarizedBy": "Summary by {model}",
2504
+ "detail.draftedBy": "Draft by {model}",
2505
+ "detail.local": "Local rules found this in tool results. No model has read it yet.",
2506
+ "detail.steps": "Steps the rules matched",
2507
+ "detail.summaryNote": "A summary sends redacted evidence from {count} tasks to {model}: requests, messages during the task, commands, exit codes, and one error line per failed command. It returns a few lines, not a Skill.",
2508
+ "detail.summarize": "Summarize",
2509
+ "detail.resummarize": "Summarize again",
2510
+ "detail.summarizing": "{model} is writing the summary",
2511
+ "detail.noSummaryModel": "No discovery model is available.",
2512
+ "detail.found": "Found",
2513
+ "detail.description": "Purpose",
2514
+ "detail.trigger": "When to use",
2515
+ "detail.plan": "Plan",
2516
+ "detail.risks": "Risks",
2517
+ "detail.verification": "Verify",
2518
+ "detail.plan.create": "Create",
2519
+ "detail.plan.improve": "Add to",
2520
+ "detail.consent": "Drafting sends the same evidence and this plan to {model}, which writes the Skill. You review and edit it before anything is saved.",
2521
+ "detail.noDraftModel": "No drafting model is available.",
2522
+ "detail.draft.create": "Draft Skill",
2523
+ "detail.draft.improve": "Draft addition",
2524
+ "detail.redraft": "Draft again",
2525
+ "detail.drafting": "{model} is drafting the Skill",
2526
+ "detail.skipped": "The summary found nothing worth saving.",
2527
+ "detail.failed.summary": "Summary failed: {kind}",
2528
+ "detail.failed.draft": "Drafting failed: {kind}",
2529
+ "detail.openSession": "Open session",
2530
+ "detail.cautions": "Check before saving: {list}",
2531
+ "failure.timeout": "timed out",
2532
+ "failure.model": "model error",
2533
+ "failure.format": "the reply did not match the expected format",
2534
+ "failure.evidence": "the reply was not supported by the evidence",
2535
+ "failure.unsafe": "the reply contained unsafe content",
2536
+ "outcome.verified": "Check passed",
2537
+ "outcome.succeeded": "Succeeded",
2538
+ "outcome.unverified": "Unverified",
2539
+ "outcome.disputed": "User disagreed",
2540
+ "outcome.failed": "Failed",
2541
+ "outcome.aborted": "Cancelled",
2542
+ "role.verify": "check passed",
2543
+ "role.failure": "failed",
2544
+ "role.fix": "fix",
2545
+ "role.recovered": "passed after fix",
2546
+ "role.cited": "cited",
2547
+ "evidence.reads.one": "{count} read not shown",
2548
+ "evidence.reads.other": "{count} reads not shown",
2549
+ "evidence.exit": "exit {code}",
2550
+ "evidence.steering": "During the task",
2551
+ "evidence.followup": "Next message",
2552
+ "evidence.list": "Operations in task {ref}",
2553
+ "caution.sudo": "uses sudo",
2554
+ "caution.force-push": "force-pushes",
2555
+ "caution.no-verify": "skips hooks",
2556
+ "caution.chmod-777": "chmod 777",
2557
+ "caution.recursive-delete": "deletes recursively",
2558
+ "skill.name": "Name",
2559
+ "skill.description": "Description",
2560
+ "skill.whenToUse": "When to use",
2561
+ "skill.body": "Instructions",
2562
+ "skill.edit": "Edit",
2563
+ "skill.preview": "Preview",
2564
+ "skill.reset": "Reset",
2565
+ "skill.invalid": "Name must be lowercase words joined by hyphens; description and instructions are required.",
2566
+ "skill.section": "Section added to {skill}",
2567
+ "skill.notWritable": "{skill} is not in a project or user Skill folder, so Skill Scout cannot change it.",
2568
+ "changes.loading": "Preparing the file",
2569
+ "changes.error": "Could not prepare the file: {error}",
2570
+ "changes.exists": "A file already exists at this path.",
2571
+ "scope.label": "Save to",
2572
+ "scope.project": "Project",
2573
+ "scope.user": "User",
2574
+ "action.save": "Save Skill",
2575
+ "action.improve": "Add to {skill}",
2576
+ "action.saving": "Saving",
2577
+ "action.dismiss": "Dismiss",
2578
+ "action.dismissSimilar": "Dismiss similar",
2579
+ "action.restore": "Restore",
2580
+ "action.undo": "Undo",
2581
+ "action.done": "Done",
2582
+ "written.path": "Saved to {path}",
2583
+ "written.discovered": "DSH lists the Skill.",
2584
+ "written.checking": "Waiting for DSH to list the Skill.",
2585
+ "written.missing": "DSH does not list the Skill yet. Check the file.",
2586
+ "error.invalid": "This action does not apply to the suggestion in its current state.",
2587
+ "error.missing": "The suggestion no longer exists.",
2588
+ "error.busy": "This suggestion is already with the model.",
2589
+ "error.no-model": "No model is available for this step.",
2590
+ "error.exists": "A Skill file already exists at that path.",
2591
+ "error.changed": "The file changed after the preview. Nothing was written.",
2592
+ "error.outside": "The target is outside the project and user Skill folders.",
2593
+ "error.unsafe": "The content contains a credential. Remove it and try again.",
2594
+ "error.request": "The request failed: {error}",
2595
+ "code.copy": "Copy",
2596
+ "code.copied": "Copied",
2597
+ "code.label": "Code",
2598
+ "code.wrap": "Wrap lines",
2599
+ "code.unwrap": "Do not wrap lines",
2600
+ "code.footnotes": "Footnotes",
2601
+ "diff.collapse": "Collapse",
2602
+ "diff.collapseAria": "Collapse the diff",
2603
+ "diff.expand": "Show {count} more lines",
2604
+ "settings.summary": "Suggests Skills from workflows that worked in finished sessions.",
2605
+ "settings.unavailable": "Settings are not available.",
2606
+ "settings.readOnly": "This profile is read-only.",
2607
+ "settings.saveFailed": "Could not save. The settings changed elsewhere or the write was refused.",
2608
+ "settings.save": "Save",
2609
+ "settings.saving": "Saving",
2610
+ "settings.section.discovery": "Discovery",
2611
+ "settings.section.summary": "Summaries",
2612
+ "settings.section.draft": "Drafting",
2613
+ "settings.section.limits": "Limits",
2614
+ "settings.section.privacy": "Privacy",
2615
+ "settings.section.inbox": "Inbox",
2616
+ "settings.discovery": "Analyze finished sessions",
2617
+ "settings.discoveryHint": "Runs on this machine and reads only the session logs DSH already keeps.",
2618
+ "settings.summarize": "Summarize in the background",
2619
+ "settings.summarizeHint": "Sends redacted evidence of strong findings to the discovery model for a short summary. A Skill is drafted only after you approve its summary.",
2620
+ "settings.summaryModel": "Discovery model",
2621
+ "settings.summaryModelHint": "Writes the summaries; a small, fast model is enough. The default follows the Agent's default model.",
2622
+ "settings.draftModel": "Drafting model",
2623
+ "settings.draftModelHint": "Writes the Skill after you approve a summary. The default follows the Agent's default model.",
2624
+ "settings.modelDefault": "Default ({model})",
2625
+ "settings.modelDefaultUnknown": "Default model",
2626
+ "settings.modelLoading": "Loading models",
2627
+ "settings.modelError": "Could not load the model list.",
2628
+ "settings.modelMissing": "{model} (not available)",
2629
+ "settings.effort": "Reasoning effort",
2630
+ "settings.effortHint": "Lower effort is cheaper and faster.",
2631
+ "settings.effortDefault": "Model default",
2632
+ "settings.summaryTimeout": "Timeout",
2633
+ "settings.summaryTimeoutHint": "Seconds before a summary is abandoned.",
2634
+ "settings.draftTimeout": "Timeout",
2635
+ "settings.draftTimeoutHint": "Seconds before a draft is abandoned.",
2636
+ "settings.dailySummaries": "Summaries per day",
2637
+ "settings.dailySummariesHint": "Background summaries allowed each day. Your own requests do not count.",
2638
+ "settings.summaryInterval": "Summary interval",
2639
+ "settings.summaryIntervalHint": "Minutes between background summaries.",
2640
+ "settings.quietPeriod": "Quiet period",
2641
+ "settings.quietPeriodHint": "Minutes a session stays idle before it is analyzed.",
2642
+ "settings.historyDays": "History",
2643
+ "settings.historyDaysHint": "Days of past sessions to analyze.",
2644
+ "settings.maxSessions": "Startup scan",
2645
+ "settings.maxSessionsHint": "Past sessions read when DSH starts.",
2646
+ "settings.minOccurrences": "Repeats needed",
2647
+ "settings.minOccurrencesHint": "Independent sessions before a repeated workflow counts.",
2648
+ "settings.evidenceBudget": "Evidence budget",
2649
+ "settings.evidenceBudgetHint": "Characters of evidence sent with one request.",
2650
+ "settings.readOutput": "Include error lines",
2651
+ "settings.readOutputHint": "Keep one redacted line from each failed command as evidence.",
2652
+ "settings.redactPatterns": "Extra redaction",
2653
+ "settings.redactPatternsHint": "One regular expression per line. Matches are replaced before anything is stored or sent.",
2654
+ "settings.redactInvalid": "Line {line} is not a valid regular expression.",
2655
+ "settings.notify": "Header badge",
2656
+ "settings.notifyHint": "Show the number of new suggestions on the header icon.",
2657
+ "settings.notify.badge": "Count",
2658
+ "settings.notify.quiet": "Icon only",
2659
+ "settings.scope": "New Skills go to",
2660
+ "settings.scopeHint": "Project: .agents/skills in the repository. User: ~/.agents/skills.",
2661
+ "settings.prompt": "Summary guidance",
2662
+ "settings.promptHint": "Replaces the built-in guidance for deciding what is worth saving. The reply format is always added.",
2663
+ "settings.promptPlaceholder": "Built-in guidance",
2664
+ "unit.seconds": "s",
2665
+ "unit.minutes": "min",
2666
+ "unit.days": "days",
2667
+ "unit.sessions": "sessions",
2668
+ "unit.requests": "requests",
2669
+ "unit.characters": "chars"
2670
+ },
2671
+ zh: {
2672
+ "entry.label": "Skill 建议",
2673
+ "entry.count.one": "Skill 建议,{count} 条新建议",
2674
+ "entry.count.other": "Skill 建议,{count} 条新建议",
2675
+ "panel.title": "Skill 建议",
2676
+ "panel.analyzing": "正在分析会话",
2677
+ "panel.working": "正在请求模型",
2678
+ "panel.loading": "正在加载",
2679
+ "panel.error": "无法加载建议。",
2680
+ "panel.retry": "重试",
2681
+ "panel.empty.title": "暂无建议",
2682
+ "panel.empty.body": "同一流程在多个会话里重复出现,或某次修复经检查命令验证后,这里会出现建议。",
2683
+ "panel.analyzed": "已分析 {sessions} 个会话",
2684
+ "section.review": "待你决定",
2685
+ "section.found": "本地发现",
2686
+ "history.show": "历史({count})",
2687
+ "history.hide": "收起历史",
2688
+ "banner.noModel": "没有可用的发现模型,请在经验提炼的设置里选择。",
2689
+ "banner.discoveryOff": "会话分析已关闭。",
2690
+ "banner.secondary": "另一个 DSH 窗口正在分析会话。",
2691
+ "notice.dismissed": "已忽略",
2692
+ "notice.dismissedSimilar": "已忽略这条及类似建议",
2693
+ "notice.restored": "已恢复",
2694
+ "notice.written": "已保存 {skill}",
2695
+ "notice.undone": "已撤销对 {skill} 的改动",
2696
+ "notice.undo": "撤销",
2697
+ "kind.repeated": "重复流程",
2698
+ "kind.recovery": "已验证的修复",
2699
+ "kind.complex": "经验证的长任务",
2700
+ "meta.sessions.one": "{count} 个会话",
2701
+ "meta.sessions.other": "{count} 个会话",
2702
+ "state.detected": "尚未摘要",
2703
+ "state.summarizing": "正在摘要",
2704
+ "state.proposed": "摘要待确认",
2705
+ "state.skipped": "不值得保存",
2706
+ "state.drafting": "正在生成",
2707
+ "state.ready": "草稿待保存",
2708
+ "state.failed": "摘要失败",
2709
+ "state.draftFailed": "生成失败",
2710
+ "state.dismissed": "已忽略",
2711
+ "state.written": "已保存",
2712
+ "time.now": "刚刚",
2713
+ "time.minutes": "{count} 分钟前",
2714
+ "time.hours": "{count} 小时前",
2715
+ "time.days": "{count} 天前",
2716
+ "detail.close": "关闭",
2717
+ "detail.tab.summary": "摘要",
2718
+ "detail.tab.evidence": "证据",
2719
+ "detail.tab.skill": "Skill",
2720
+ "detail.tab.changes": "改动",
2721
+ "detail.tabs": "建议的内容",
2722
+ "detail.why": "理由",
2723
+ "detail.loading": "正在加载证据",
2724
+ "detail.error": "无法加载这条建议。",
2725
+ "detail.summarizedBy": "摘要:{model}",
2726
+ "detail.draftedBy": "草稿:{model}",
2727
+ "detail.local": "由本地规则依据工具结果发现,还没有发给任何模型。",
2728
+ "detail.steps": "规则匹配到的步骤",
2729
+ "detail.summaryNote": "生成摘要会把 {count} 个任务的脱敏证据发给 {model}:用户请求、任务中的插话、命令、退出码,以及每条失败命令的一行错误输出。模型只返回几行摘要,不写 Skill。",
2730
+ "detail.summarize": "生成摘要",
2731
+ "detail.resummarize": "重新摘要",
2732
+ "detail.summarizing": "{model} 正在写摘要",
2733
+ "detail.noSummaryModel": "没有可用的发现模型。",
2734
+ "detail.found": "发现",
2735
+ "detail.description": "用途",
2736
+ "detail.trigger": "何时使用",
2737
+ "detail.plan": "将要做",
2738
+ "detail.risks": "风险",
2739
+ "detail.verification": "验证方式",
2740
+ "detail.plan.create": "新建",
2741
+ "detail.plan.improve": "追加到",
2742
+ "detail.consent": "生成草稿会把同样的证据和这份计划发给 {model},由它写出 Skill。保存之前你可以审阅和修改。",
2743
+ "detail.noDraftModel": "没有可用的生成模型。",
2744
+ "detail.draft.create": "生成 Skill 草稿",
2745
+ "detail.draft.improve": "生成追加内容",
2746
+ "detail.redraft": "重新生成",
2747
+ "detail.drafting": "{model} 正在生成 Skill",
2748
+ "detail.skipped": "摘要认为没有值得保存的内容。",
2749
+ "detail.failed.summary": "摘要失败:{kind}",
2750
+ "detail.failed.draft": "生成草稿失败:{kind}",
2751
+ "detail.openSession": "打开会话",
2752
+ "detail.cautions": "保存前请确认:{list}",
2753
+ "failure.timeout": "超时",
2754
+ "failure.model": "模型出错",
2755
+ "failure.format": "回复格式不符合约定",
2756
+ "failure.evidence": "回复没有证据支持",
2757
+ "failure.unsafe": "回复含有不安全的内容",
2758
+ "outcome.verified": "检查通过",
2759
+ "outcome.succeeded": "已完成",
2760
+ "outcome.unverified": "未验证",
2761
+ "outcome.disputed": "用户表示不对",
2762
+ "outcome.failed": "失败",
2763
+ "outcome.aborted": "已取消",
2764
+ "role.verify": "检查通过",
2765
+ "role.failure": "失败",
2766
+ "role.fix": "修复",
2767
+ "role.recovered": "修复后通过",
2768
+ "role.cited": "被引用",
2769
+ "evidence.reads.one": "另有 {count} 次读取未列出",
2770
+ "evidence.reads.other": "另有 {count} 次读取未列出",
2771
+ "evidence.exit": "退出码 {code}",
2772
+ "evidence.steering": "任务中插话",
2773
+ "evidence.followup": "下一条消息",
2774
+ "evidence.list": "任务 {ref} 的操作",
2775
+ "caution.sudo": "使用 sudo",
2776
+ "caution.force-push": "强制推送",
2777
+ "caution.no-verify": "跳过钩子",
2778
+ "caution.chmod-777": "chmod 777",
2779
+ "caution.recursive-delete": "递归删除",
2780
+ "skill.name": "名称",
2781
+ "skill.description": "描述",
2782
+ "skill.whenToUse": "适用场景",
2783
+ "skill.body": "内容",
2784
+ "skill.edit": "编辑",
2785
+ "skill.preview": "预览",
2786
+ "skill.reset": "还原",
2787
+ "skill.invalid": "名称只能是用连字符连接的小写单词;描述与内容不能为空。",
2788
+ "skill.section": "追加到 {skill} 的内容",
2789
+ "skill.notWritable": "{skill} 不在项目或用户的 Skill 目录里,经验提炼无法修改它。",
2790
+ "changes.loading": "正在生成文件",
2791
+ "changes.error": "无法生成文件:{error}",
2792
+ "changes.exists": "这个路径已经有文件。",
2793
+ "scope.label": "保存到",
2794
+ "scope.project": "项目",
2795
+ "scope.user": "用户",
2796
+ "action.save": "保存 Skill",
2797
+ "action.improve": "追加到 {skill}",
2798
+ "action.saving": "正在保存",
2799
+ "action.dismiss": "忽略",
2800
+ "action.dismissSimilar": "忽略类似建议",
2801
+ "action.restore": "恢复",
2802
+ "action.undo": "撤销",
2803
+ "action.done": "完成",
2804
+ "written.path": "已保存到 {path}",
2805
+ "written.discovered": "DSH 已列出这个 Skill。",
2806
+ "written.checking": "正在等 DSH 列出这个 Skill。",
2807
+ "written.missing": "DSH 还没有列出这个 Skill,请检查文件。",
2808
+ "error.invalid": "这条建议当前的状态不支持这个操作。",
2809
+ "error.missing": "这条建议已不存在。",
2810
+ "error.busy": "这条建议正在等模型回复。",
2811
+ "error.no-model": "这一步没有可用的模型。",
2812
+ "error.exists": "这个路径已经有 Skill 文件。",
2813
+ "error.changed": "预览之后文件被改过,没有写入任何内容。",
2814
+ "error.outside": "目标不在项目或用户的 Skill 目录里。",
2815
+ "error.unsafe": "内容里有凭据,删除后再试。",
2816
+ "error.request": "请求失败:{error}",
2817
+ "code.copy": "复制",
2818
+ "code.copied": "已复制",
2819
+ "code.label": "代码",
2820
+ "code.wrap": "自动换行",
2821
+ "code.unwrap": "不换行",
2822
+ "code.footnotes": "脚注",
2823
+ "diff.collapse": "收起",
2824
+ "diff.collapseAria": "收起改动",
2825
+ "diff.expand": "展开其余 {count} 行",
2826
+ "settings.summary": "从已结束会话里做成过的流程中提炼 Skill 建议。",
2827
+ "settings.unavailable": "设置不可用。",
2828
+ "settings.readOnly": "当前 profile 是只读的。",
2829
+ "settings.saveFailed": "保存失败:设置已在别处改动,或写入被拒绝。",
2830
+ "settings.save": "保存",
2831
+ "settings.saving": "正在保存",
2832
+ "settings.section.discovery": "发现",
2833
+ "settings.section.summary": "摘要",
2834
+ "settings.section.draft": "生成 Skill",
2835
+ "settings.section.limits": "限额",
2836
+ "settings.section.privacy": "隐私",
2837
+ "settings.section.inbox": "收件箱",
2838
+ "settings.discovery": "分析已结束的会话",
2839
+ "settings.discoveryHint": "在本机运行,只读取 DSH 已保存的会话日志。",
2840
+ "settings.summarize": "后台生成摘要",
2841
+ "settings.summarizeHint": "把较强发现的脱敏证据发给发现模型,只生成简短摘要。你同意摘要之后才会生成 Skill 草稿。",
2842
+ "settings.summaryModel": "发现模型",
2843
+ "settings.summaryModelHint": "写摘要用,小而快的模型就够。默认跟随 Agent 的默认模型。",
2844
+ "settings.draftModel": "生成模型",
2845
+ "settings.draftModelHint": "你同意摘要后写 Skill 用。默认跟随 Agent 的默认模型。",
2846
+ "settings.modelDefault": "默认({model})",
2847
+ "settings.modelDefaultUnknown": "默认模型",
2848
+ "settings.modelLoading": "正在加载模型",
2849
+ "settings.modelError": "无法加载模型列表。",
2850
+ "settings.modelMissing": "{model}(不可用)",
2851
+ "settings.effort": "推理强度",
2852
+ "settings.effortHint": "强度越低,越便宜也越快。",
2853
+ "settings.effortDefault": "模型默认",
2854
+ "settings.summaryTimeout": "超时",
2855
+ "settings.summaryTimeoutHint": "单次摘要等待的秒数。",
2856
+ "settings.draftTimeout": "超时",
2857
+ "settings.draftTimeoutHint": "单次生成等待的秒数。",
2858
+ "settings.dailySummaries": "每日摘要次数",
2859
+ "settings.dailySummariesHint": "每天允许的后台摘要次数,手动发起的不计入。",
2860
+ "settings.summaryInterval": "摘要间隔",
2861
+ "settings.summaryIntervalHint": "两次后台摘要之间的分钟数。",
2862
+ "settings.quietPeriod": "静默期",
2863
+ "settings.quietPeriodHint": "会话空闲多少分钟后开始分析。",
2864
+ "settings.historyDays": "历史范围",
2865
+ "settings.historyDaysHint": "分析最近多少天的会话。",
2866
+ "settings.maxSessions": "启动时扫描",
2867
+ "settings.maxSessionsHint": "DSH 启动时读取的历史会话数。",
2868
+ "settings.minOccurrences": "重复次数",
2869
+ "settings.minOccurrencesHint": "重复流程至少要出现在多少个独立会话里。",
2870
+ "settings.evidenceBudget": "证据上限",
2871
+ "settings.evidenceBudgetHint": "单次请求发送的证据字符数。",
2872
+ "settings.readOutput": "包含错误行",
2873
+ "settings.readOutputHint": "每条失败命令保留一行脱敏后的输出作为证据。",
2874
+ "settings.redactPatterns": "追加脱敏规则",
2875
+ "settings.redactPatternsHint": "每行一个正则表达式,匹配内容在存储与发送前被替换。",
2876
+ "settings.redactInvalid": "第 {line} 行不是有效的正则表达式。",
2877
+ "settings.notify": "标题栏角标",
2878
+ "settings.notifyHint": "在标题栏图标上显示新建议的数量。",
2879
+ "settings.notify.badge": "显示数量",
2880
+ "settings.notify.quiet": "只显示图标",
2881
+ "settings.scope": "新 Skill 保存到",
2882
+ "settings.scopeHint": "项目:仓库里的 .agents/skills。用户:~/.agents/skills。",
2883
+ "settings.prompt": "摘要要求",
2884
+ "settings.promptHint": "替换内置的「是否值得保存」判断标准,回复格式的约定始终保留。",
2885
+ "settings.promptPlaceholder": "内置的判断标准",
2886
+ "unit.seconds": "秒",
2887
+ "unit.minutes": "分钟",
2888
+ "unit.days": "天",
2889
+ "unit.sessions": "个",
2890
+ "unit.requests": "次",
2891
+ "unit.characters": "字符"
2892
+ }
2893
+ };
2894
+ /** 注册两种语言的字典,返回注销函数。 */
2895
+ function registerLocale(locale) {
2896
+ const disposers = Object.entries(SCOUT_DICTIONARIES).map(([language, dictionary]) => locale.register(SCOUT_LOCALE, language, { ...dictionary }));
2897
+ return () => {
2898
+ for (const dispose of disposers) dispose();
2899
+ };
2900
+ }
2901
+ //#endregion
2902
+ //#region \0dsh-css:src/client/settings.module.css.mjs
2903
+ 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)}";
2904
+ const tag = "@agent-stash/dsh-skill-scout/settings.module.css";
2905
+ if (document.querySelector(`style[data-plugin-css=${JSON.stringify(tag)}]`) === null) {
2906
+ const style = document.createElement("style");
2907
+ style.dataset.plugin = "@agent-stash/dsh-skill-scout";
2908
+ style.dataset.pluginCss = tag;
2909
+ style.textContent = css;
2910
+ document.head.appendChild(style);
2911
+ }
2912
+ var settings_module_css_default = {
2913
+ "area": "TurwOW_area",
2914
+ "control": "TurwOW_control",
2915
+ "field": "TurwOW_field",
2916
+ "hint": "TurwOW_hint",
2917
+ "input": "TurwOW_input",
2918
+ "label": "TurwOW_label",
2919
+ "mono": "TurwOW_mono",
2920
+ "number": "TurwOW_number",
2921
+ "page": "TurwOW_page",
2922
+ "section": "TurwOW_section",
2923
+ "select": "TurwOW_select",
2924
+ "status": "TurwOW_status",
2925
+ "text": "TurwOW_text",
2926
+ "title": "TurwOW_title",
2927
+ "unit": "TurwOW_unit",
2928
+ "wide": "TurwOW_wide"
2929
+ };
2930
+ //#endregion
2931
+ //#region src/client/settings.tsx
2932
+ /**
2933
+ * 插件页里经验提炼条目的配置页。改动先暂存,点保存才按读到的版本号写回 profile;
2934
+ * 写入被拒绝或传输失败时保留暂存值并提示。发现模型(写摘要)与生成模型(写 Skill 草稿)
2935
+ * 分别从 Host 的模型目录里选,留空时跟随 Agent 的默认模型。
2936
+ */
2937
+ /** 与 Host `Config` 相同的默认值。 */
2938
+ const DEFAULT_SETTINGS = {
2939
+ discovery: true,
2940
+ summarize: false,
2941
+ summaryProvider: "",
2942
+ summaryModel: "",
2943
+ summaryEffort: "",
2944
+ summaryTimeout: 60,
2945
+ draftProvider: "",
2946
+ draftModel: "",
2947
+ draftEffort: "",
2948
+ draftTimeout: 180,
2949
+ dailySummaries: 6,
2950
+ summaryInterval: 10,
2951
+ quietPeriod: 2,
2952
+ historyDays: 30,
2953
+ maxSessions: 200,
2954
+ minOccurrences: 2,
2955
+ evidenceBudget: 24e3,
2956
+ readOutput: true,
2957
+ redactPatterns: [],
2958
+ notify: "badge",
2959
+ scope: "project",
2960
+ prompt: ""
2961
+ };
2962
+ /** 数值字段的范围与单位,与 Host `Config` 一致。 */
2963
+ const NUMBERS = {
2964
+ summaryTimeout: {
2965
+ min: 10,
2966
+ max: 600,
2967
+ unit: "unit.seconds"
2968
+ },
2969
+ draftTimeout: {
2970
+ min: 10,
2971
+ max: 900,
2972
+ unit: "unit.seconds"
2973
+ },
2974
+ dailySummaries: {
2975
+ min: 1,
2976
+ max: 100,
2977
+ unit: "unit.requests"
2978
+ },
2979
+ summaryInterval: {
2980
+ min: 1,
2981
+ max: 1440,
2982
+ unit: "unit.minutes"
2983
+ },
2984
+ quietPeriod: {
2985
+ min: 0,
2986
+ max: 120,
2987
+ unit: "unit.minutes"
2988
+ },
2989
+ historyDays: {
2990
+ min: 1,
2991
+ max: 365,
2992
+ unit: "unit.days"
2993
+ },
2994
+ maxSessions: {
2995
+ min: 1,
2996
+ max: 2e3,
2997
+ unit: "unit.sessions"
2998
+ },
2999
+ minOccurrences: {
3000
+ min: 2,
3001
+ max: 10,
3002
+ unit: "unit.sessions"
3003
+ },
3004
+ evidenceBudget: {
3005
+ min: 4e3,
3006
+ max: 2e5,
3007
+ unit: "unit.characters"
3008
+ }
3009
+ };
3010
+ /** 设置服务快照值的结构;缺失或类型不符的字段取默认值。 */
3011
+ const SettingsSchema = /* @__PURE__ */ object({
3012
+ discovery: /* @__PURE__ */ fallback(/* @__PURE__ */ boolean(), DEFAULT_SETTINGS.discovery),
3013
+ summarize: /* @__PURE__ */ fallback(/* @__PURE__ */ boolean(), DEFAULT_SETTINGS.summarize),
3014
+ summaryProvider: /* @__PURE__ */ fallback(/* @__PURE__ */ string(), DEFAULT_SETTINGS.summaryProvider),
3015
+ summaryModel: /* @__PURE__ */ fallback(/* @__PURE__ */ string(), DEFAULT_SETTINGS.summaryModel),
3016
+ summaryEffort: /* @__PURE__ */ fallback(/* @__PURE__ */ string(), DEFAULT_SETTINGS.summaryEffort),
3017
+ summaryTimeout: /* @__PURE__ */ fallback(/* @__PURE__ */ number(), DEFAULT_SETTINGS.summaryTimeout),
3018
+ draftProvider: /* @__PURE__ */ fallback(/* @__PURE__ */ string(), DEFAULT_SETTINGS.draftProvider),
3019
+ draftModel: /* @__PURE__ */ fallback(/* @__PURE__ */ string(), DEFAULT_SETTINGS.draftModel),
3020
+ draftEffort: /* @__PURE__ */ fallback(/* @__PURE__ */ string(), DEFAULT_SETTINGS.draftEffort),
3021
+ draftTimeout: /* @__PURE__ */ fallback(/* @__PURE__ */ number(), DEFAULT_SETTINGS.draftTimeout),
3022
+ dailySummaries: /* @__PURE__ */ fallback(/* @__PURE__ */ number(), DEFAULT_SETTINGS.dailySummaries),
3023
+ summaryInterval: /* @__PURE__ */ fallback(/* @__PURE__ */ number(), DEFAULT_SETTINGS.summaryInterval),
3024
+ quietPeriod: /* @__PURE__ */ fallback(/* @__PURE__ */ number(), DEFAULT_SETTINGS.quietPeriod),
3025
+ historyDays: /* @__PURE__ */ fallback(/* @__PURE__ */ number(), DEFAULT_SETTINGS.historyDays),
3026
+ maxSessions: /* @__PURE__ */ fallback(/* @__PURE__ */ number(), DEFAULT_SETTINGS.maxSessions),
3027
+ minOccurrences: /* @__PURE__ */ fallback(/* @__PURE__ */ number(), DEFAULT_SETTINGS.minOccurrences),
3028
+ evidenceBudget: /* @__PURE__ */ fallback(/* @__PURE__ */ number(), DEFAULT_SETTINGS.evidenceBudget),
3029
+ readOutput: /* @__PURE__ */ fallback(/* @__PURE__ */ boolean(), DEFAULT_SETTINGS.readOutput),
3030
+ redactPatterns: /* @__PURE__ */ fallback(/* @__PURE__ */ array(/* @__PURE__ */ string()), [...DEFAULT_SETTINGS.redactPatterns]),
3031
+ notify: /* @__PURE__ */ fallback(/* @__PURE__ */ picklist(["badge", "quiet"]), DEFAULT_SETTINGS.notify),
3032
+ scope: /* @__PURE__ */ fallback(/* @__PURE__ */ picklist(["project", "user"]), DEFAULT_SETTINGS.scope),
3033
+ prompt: /* @__PURE__ */ fallback(/* @__PURE__ */ string(), DEFAULT_SETTINGS.prompt)
3034
+ });
3035
+ /** 从设置服务的快照值读出完整设置。 */
3036
+ function parseSettings(value) {
3037
+ return parse$1(SettingsSchema, typeof value === "object" && value !== null ? value : {});
3038
+ }
3039
+ const SETTING_KEYS = [
3040
+ "discovery",
3041
+ "summarize",
3042
+ "summaryProvider",
3043
+ "summaryModel",
3044
+ "summaryEffort",
3045
+ "summaryTimeout",
3046
+ "draftProvider",
3047
+ "draftModel",
3048
+ "draftEffort",
3049
+ "draftTimeout",
3050
+ "dailySummaries",
3051
+ "summaryInterval",
3052
+ "quietPeriod",
3053
+ "historyDays",
3054
+ "maxSessions",
3055
+ "minOccurrences",
3056
+ "evidenceBudget",
3057
+ "readOutput",
3058
+ "redactPatterns",
3059
+ "notify",
3060
+ "scope",
3061
+ "prompt"
3062
+ ];
3063
+ const NUMBER_KEYS = [
3064
+ "summaryTimeout",
3065
+ "draftTimeout",
3066
+ "dailySummaries",
3067
+ "summaryInterval",
3068
+ "quietPeriod",
3069
+ "historyDays",
3070
+ "maxSessions",
3071
+ "minOccurrences",
3072
+ "evidenceBudget"
3073
+ ];
3074
+ /** 暂存值与已保存值的差异,转换成写操作。 */
3075
+ function diffOps(saved, staged) {
3076
+ return SETTING_KEYS.flatMap((key) => {
3077
+ const next = staged[key];
3078
+ return JSON.stringify(saved[key]) === JSON.stringify(next) ? [] : [{
3079
+ op: "set",
3080
+ path: [key],
3081
+ value: next
3082
+ }];
3083
+ });
3084
+ }
3085
+ /** 正则源码能否编译。 */
3086
+ function compiles(source) {
3087
+ try {
3088
+ return new RegExp(source, "u").source !== "";
3089
+ } catch (error) {
3090
+ return false;
3091
+ }
3092
+ }
3093
+ /** 每行一个正则;返回第一条不合法的行号(从 1 开始),都合法时返回 null。 */
3094
+ function invalidPattern(lines) {
3095
+ const index = lines.findIndex((line) => line.trim() !== "" && !compiles(line));
3096
+ return index === -1 ? null : index + 1;
3097
+ }
3098
+ function Field({ id, label, hint, children, wide = false }) {
3099
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3100
+ className: wide ? `${settings_module_css_default.field} ${settings_module_css_default.wide}` : settings_module_css_default.field,
3101
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3102
+ className: settings_module_css_default.text,
3103
+ children: [id === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3104
+ className: settings_module_css_default.label,
3105
+ children: label
3106
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
3107
+ className: settings_module_css_default.label,
3108
+ htmlFor: id,
3109
+ children: label
3110
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3111
+ className: settings_module_css_default.hint,
3112
+ children: hint
3113
+ })]
3114
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3115
+ className: settings_module_css_default.control,
3116
+ children
3117
+ })]
3118
+ });
3119
+ }
3120
+ /** 模型选项的值:`provider\u0000model`,空串表示默认模型。 */
3121
+ const SEPARATOR = "\0";
3122
+ function modelValue(provider, model) {
3123
+ return provider === "" || model === "" ? "" : `${provider}${SEPARATOR}${model}`;
3124
+ }
3125
+ /** 每个阶段的 provider、模型与推理强度对应的设置字段。 */
3126
+ const MODEL_KEYS = {
3127
+ summary: {
3128
+ provider: "summaryProvider",
3129
+ model: "summaryModel",
3130
+ effort: "summaryEffort"
3131
+ },
3132
+ draft: {
3133
+ provider: "draftProvider",
3134
+ model: "draftModel",
3135
+ effort: "draftEffort"
3136
+ }
3137
+ };
3138
+ function ModelFields({ stage, staged: values, catalog, disabled, onChange, t }) {
3139
+ const id = (0, react.useId)();
3140
+ const keys = MODEL_KEYS[stage];
3141
+ const staged = {
3142
+ provider: values[keys.provider],
3143
+ model: values[keys.model],
3144
+ effort: values[keys.effort]
3145
+ };
3146
+ const groups = catalog.status === "ready" ? catalog.catalog.groups : [];
3147
+ const current = modelValue(staged.provider, staged.model);
3148
+ const fallback = catalog.status === "ready" ? catalog.catalog.default : null;
3149
+ const fallbackName = fallback === null ? null : groups.find((group) => group.id === fallback.provider)?.models.find((model) => model.id === fallback.model)?.name ?? fallback.model;
3150
+ const routeProvider = staged.provider === "" ? fallback?.provider ?? "" : staged.provider;
3151
+ const routeModel = staged.model === "" ? fallback?.model ?? "" : staged.model;
3152
+ const efforts = (groups.find((group) => group.id === routeProvider)?.models.find((model) => model.id === routeModel))?.reasoning?.efforts ?? [];
3153
+ const listed = groups.some((group) => group.models.some((model) => modelValue(group.id, model.id) === current));
3154
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Field, {
3155
+ id: `${id}-model`,
3156
+ label: t(stage === "summary" ? "settings.summaryModel" : "settings.draftModel"),
3157
+ hint: t(stage === "summary" ? "settings.summaryModelHint" : "settings.draftModelHint"),
3158
+ children: [
3159
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
3160
+ id: `${id}-model`,
3161
+ className: settings_module_css_default.select,
3162
+ value: current,
3163
+ disabled,
3164
+ onChange: (event) => {
3165
+ const [provider = "", model = ""] = event.target.value.split(SEPARATOR);
3166
+ onChange({
3167
+ [keys.provider]: provider,
3168
+ [keys.model]: model,
3169
+ [keys.effort]: ""
3170
+ });
3171
+ },
3172
+ children: [
3173
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
3174
+ value: "",
3175
+ children: fallbackName === null ? t("settings.modelDefaultUnknown") : t("settings.modelDefault", { model: fallbackName })
3176
+ }),
3177
+ current !== "" && !listed && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
3178
+ value: current,
3179
+ children: t("settings.modelMissing", { model: staged.model })
3180
+ }),
3181
+ groups.map((group) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("optgroup", {
3182
+ label: group.name,
3183
+ children: group.models.map((model) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
3184
+ value: modelValue(group.id, model.id),
3185
+ children: model.name
3186
+ }, model.id))
3187
+ }, group.id))
3188
+ ]
3189
+ }),
3190
+ catalog.status === "loading" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3191
+ className: settings_module_css_default.status,
3192
+ children: t("settings.modelLoading")
3193
+ }),
3194
+ catalog.status === "error" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3195
+ className: settings_module_css_default.status,
3196
+ role: "alert",
3197
+ children: t("settings.modelError")
3198
+ })
3199
+ ]
3200
+ }), efforts.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
3201
+ id: `${id}-effort`,
3202
+ label: t("settings.effort"),
3203
+ hint: t("settings.effortHint"),
3204
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
3205
+ id: `${id}-effort`,
3206
+ className: settings_module_css_default.select,
3207
+ value: staged.effort,
3208
+ disabled,
3209
+ onChange: (event) => onChange({ [keys.effort]: event.target.value }),
3210
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
3211
+ value: "",
3212
+ children: t("settings.effortDefault")
3213
+ }), efforts.map((effort) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
3214
+ value: effort.id,
3215
+ children: effort.name
3216
+ }, effort.id))]
3217
+ })
3218
+ })] });
3219
+ }
3220
+ /**
3221
+ * 创建配置页组件。
3222
+ * @param catalog - 模型目录的读取状态;页面打开时订阅。
3223
+ */
3224
+ function createSettingsPage(catalog) {
3225
+ return function SettingsPage({ view, form, t }) {
3226
+ const translate = t;
3227
+ const id = (0, react.useId)();
3228
+ const saved = parseSettings(form?.state.value);
3229
+ const [draft, setDraft] = (0, react.useState)({});
3230
+ const [patterns, setPatterns] = (0, react.useState)(null);
3231
+ const [saving, setSaving] = (0, react.useState)(false);
3232
+ const [failed, setFailed] = (0, react.useState)(false);
3233
+ const models = useSnapshot(catalog);
3234
+ if (view === "summary" || form === void 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: translate("settings.summary") });
3235
+ const lines = patterns === null ? saved.redactPatterns : patterns.split("\n");
3236
+ const staged = {
3237
+ ...saved,
3238
+ ...draft,
3239
+ redactPatterns: lines.map((line) => line.trim()).filter((line) => line !== "")
3240
+ };
3241
+ const ops = diffOps(saved, staged);
3242
+ const badLine = invalidPattern(lines);
3243
+ const badNumber = NUMBER_KEYS.some((key) => {
3244
+ const value = staged[key];
3245
+ return !Number.isInteger(value) || value < NUMBERS[key].min || value > NUMBERS[key].max;
3246
+ });
3247
+ const writable = form.state.writable && !saving;
3248
+ const update = (next) => {
3249
+ setDraft((current) => ({
3250
+ ...current,
3251
+ ...next
3252
+ }));
3253
+ setFailed(false);
3254
+ };
3255
+ const discard = () => {
3256
+ setDraft({});
3257
+ setPatterns(null);
3258
+ setFailed(false);
3259
+ };
3260
+ const save = async () => {
3261
+ setSaving(true);
3262
+ setFailed(false);
3263
+ try {
3264
+ if (await form.mutate(ops, form.state.revision)) discard();
3265
+ else setFailed(true);
3266
+ } catch (error) {
3267
+ setFailed(true);
3268
+ } finally {
3269
+ setSaving(false);
3270
+ }
3271
+ };
3272
+ const toggle = (key, label, hint) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
3273
+ label: translate(label),
3274
+ hint: translate(hint),
3275
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Switch, {
3276
+ checked: staged[key],
3277
+ label: translate(label),
3278
+ disabled: !writable,
3279
+ onChange: (next) => update({ [key]: next })
3280
+ })
3281
+ });
3282
+ const number = (key) => {
3283
+ const range = NUMBERS[key];
3284
+ const fieldId = `${id}-${key}`;
3285
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
3286
+ id: fieldId,
3287
+ label: translate(`settings.${key}`),
3288
+ hint: translate(`settings.${key}Hint`),
3289
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
3290
+ className: settings_module_css_default.number,
3291
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
3292
+ id: fieldId,
3293
+ className: settings_module_css_default.input,
3294
+ type: "number",
3295
+ inputMode: "numeric",
3296
+ min: range.min,
3297
+ max: range.max,
3298
+ step: 1,
3299
+ value: Number.isFinite(staged[key]) ? staged[key] : "",
3300
+ disabled: !writable,
3301
+ "aria-invalid": staged[key] < range.min || staged[key] > range.max || void 0,
3302
+ onChange: (event) => update({ [key]: event.target.valueAsNumber })
3303
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3304
+ className: settings_module_css_default.unit,
3305
+ children: translate(range.unit)
3306
+ })]
3307
+ })
3308
+ });
3309
+ };
3310
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.SettingsForm, {
3311
+ labels: {
3312
+ unavailable: translate("settings.unavailable"),
3313
+ readOnly: translate("settings.readOnly"),
3314
+ saveFailed: translate("settings.saveFailed"),
3315
+ save: translate("settings.save"),
3316
+ saving: translate("settings.saving")
3317
+ },
3318
+ state: {
3319
+ available: form.state.status === "ready",
3320
+ writable: form.state.writable,
3321
+ dirty: ops.length > 0,
3322
+ invalid: badLine !== null || badNumber,
3323
+ saving,
3324
+ failed
3325
+ },
3326
+ onSave: () => void save(),
3327
+ onDiscard: discard,
3328
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3329
+ className: settings_module_css_default.page,
3330
+ children: [
3331
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
3332
+ className: settings_module_css_default.section,
3333
+ children: [
3334
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
3335
+ className: settings_module_css_default.title,
3336
+ children: translate("settings.section.discovery")
3337
+ }),
3338
+ toggle("discovery", "settings.discovery", "settings.discoveryHint"),
3339
+ number("historyDays"),
3340
+ number("maxSessions"),
3341
+ number("quietPeriod"),
3342
+ number("minOccurrences")
3343
+ ]
3344
+ }),
3345
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
3346
+ className: settings_module_css_default.section,
3347
+ children: [
3348
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
3349
+ className: settings_module_css_default.title,
3350
+ children: translate("settings.section.summary")
3351
+ }),
3352
+ toggle("summarize", "settings.summarize", "settings.summarizeHint"),
3353
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelFields, {
3354
+ stage: "summary",
3355
+ staged,
3356
+ catalog: models,
3357
+ disabled: !writable,
3358
+ onChange: update,
3359
+ t: translate
3360
+ }),
3361
+ number("summaryTimeout"),
3362
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
3363
+ id: `${id}-prompt`,
3364
+ label: translate("settings.prompt"),
3365
+ hint: translate("settings.promptHint"),
3366
+ wide: true,
3367
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
3368
+ id: `${id}-prompt`,
3369
+ className: `${settings_module_css_default.input} ${settings_module_css_default.area}`,
3370
+ rows: 4,
3371
+ placeholder: translate("settings.promptPlaceholder"),
3372
+ value: staged.prompt,
3373
+ disabled: !writable,
3374
+ onChange: (event) => update({ prompt: event.target.value })
3375
+ })
3376
+ })
3377
+ ]
3378
+ }),
3379
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
3380
+ className: settings_module_css_default.section,
3381
+ children: [
3382
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
3383
+ className: settings_module_css_default.title,
3384
+ children: translate("settings.section.draft")
3385
+ }),
3386
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelFields, {
3387
+ stage: "draft",
3388
+ staged,
3389
+ catalog: models,
3390
+ disabled: !writable,
3391
+ onChange: update,
3392
+ t: translate
3393
+ }),
3394
+ number("draftTimeout")
3395
+ ]
3396
+ }),
3397
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
3398
+ className: settings_module_css_default.section,
3399
+ children: [
3400
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
3401
+ className: settings_module_css_default.title,
3402
+ children: translate("settings.section.limits")
3403
+ }),
3404
+ number("dailySummaries"),
3405
+ number("summaryInterval"),
3406
+ number("evidenceBudget")
3407
+ ]
3408
+ }),
3409
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
3410
+ className: settings_module_css_default.section,
3411
+ children: [
3412
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
3413
+ className: settings_module_css_default.title,
3414
+ children: translate("settings.section.privacy")
3415
+ }),
3416
+ toggle("readOutput", "settings.readOutput", "settings.readOutputHint"),
3417
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Field, {
3418
+ id: `${id}-redact`,
3419
+ label: translate("settings.redactPatterns"),
3420
+ hint: translate("settings.redactPatternsHint"),
3421
+ wide: true,
3422
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
3423
+ id: `${id}-redact`,
3424
+ className: `${settings_module_css_default.input} ${settings_module_css_default.area} ${settings_module_css_default.mono}`,
3425
+ rows: 3,
3426
+ spellCheck: false,
3427
+ value: patterns ?? saved.redactPatterns.join("\n"),
3428
+ disabled: !writable,
3429
+ "aria-invalid": badLine !== null || void 0,
3430
+ onChange: (event) => {
3431
+ setPatterns(event.target.value);
3432
+ setFailed(false);
3433
+ }
3434
+ }), badLine !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3435
+ className: settings_module_css_default.status,
3436
+ role: "alert",
3437
+ children: translate("settings.redactInvalid", { line: badLine })
3438
+ })]
3439
+ })
3440
+ ]
3441
+ }),
3442
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
3443
+ className: settings_module_css_default.section,
3444
+ children: [
3445
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
3446
+ className: settings_module_css_default.title,
3447
+ children: translate("settings.section.inbox")
3448
+ }),
3449
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
3450
+ label: translate("settings.notify"),
3451
+ hint: translate("settings.notifyHint"),
3452
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.SegmentedControl, {
3453
+ id: `${id}-notify`,
3454
+ label: translate("settings.notify"),
3455
+ value: staged.notify,
3456
+ disabled: !writable,
3457
+ options: [{
3458
+ value: "badge",
3459
+ label: translate("settings.notify.badge")
3460
+ }, {
3461
+ value: "quiet",
3462
+ label: translate("settings.notify.quiet")
3463
+ }],
3464
+ onChange: (notify) => update({ notify })
3465
+ })
3466
+ }),
3467
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
3468
+ label: translate("settings.scope"),
3469
+ hint: translate("settings.scopeHint"),
3470
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.SegmentedControl, {
3471
+ id: `${id}-scope`,
3472
+ label: translate("settings.scope"),
3473
+ value: staged.scope,
3474
+ disabled: !writable,
3475
+ options: [{
3476
+ value: "project",
3477
+ label: translate("scope.project")
3478
+ }, {
3479
+ value: "user",
3480
+ label: translate("scope.user")
3481
+ }],
3482
+ onChange: (scope) => update({ scope })
3483
+ })
3484
+ })
3485
+ ]
3486
+ })
3487
+ ]
3488
+ })
3489
+ });
3490
+ };
3491
+ }
3492
+ //#endregion
3493
+ //#region src/client/store.ts
3494
+ /** 模型正在写摘要或草稿时的读取间隔:回复一到,界面在一秒内换掉进行中的占位。 */
3495
+ const MODEL_INTERVAL = 1e3;
3496
+ /** 分析会话或核对写入时的读取间隔。 */
3497
+ const BUSY_INTERVAL = 3e3;
3498
+ /** 空闲时的读取间隔。 */
3499
+ const IDLE_INTERVAL = 45e3;
3500
+ /** 操作提示停留的时间。 */
3501
+ const NOTICE_TTL = 8e3;
3502
+ const INITIAL = {
3503
+ status: "idle",
3504
+ inbox: null,
3505
+ error: null,
3506
+ notice: null
3507
+ };
3508
+ /**
3509
+ * 下一次读取的间隔:模型调用进行中最短;分析会话或写入后核对 DSH 能否发现时居中;其余时候最长。
3510
+ * @internal 导出供测试使用。
3511
+ */
3512
+ function pollInterval(inbox) {
3513
+ if (inbox === null) return IDLE_INTERVAL;
3514
+ if (inbox.status.working > 0) return MODEL_INTERVAL;
3515
+ if (inbox.status.scanning || inbox.items.some((item) => item.write?.checking === true)) return BUSY_INTERVAL;
3516
+ return IDLE_INTERVAL;
3517
+ }
3518
+ /**
3519
+ * 创建收件箱状态。
3520
+ * @param transport - 读取与写入 Host 路由的函数。
3521
+ */
3522
+ function createInboxStore(transport) {
3523
+ let state = INITIAL;
3524
+ const listeners = /* @__PURE__ */ new Set();
3525
+ let timer;
3526
+ let noticeTimer;
3527
+ let generation = 0;
3528
+ const set = (next) => {
3529
+ state = next;
3530
+ for (const listener of listeners) listener();
3531
+ };
3532
+ const schedule = () => {
3533
+ clearTimeout(timer);
3534
+ if (listeners.size === 0) return;
3535
+ timer = setTimeout(() => {
3536
+ if (document.visibilityState === "hidden") schedule();
3537
+ else refresh();
3538
+ }, pollInterval(state.inbox));
3539
+ };
3540
+ async function refresh() {
3541
+ const current = ++generation;
3542
+ if (state.inbox === null) set({
3543
+ ...state,
3544
+ status: "loading",
3545
+ error: null
3546
+ });
3547
+ try {
3548
+ const inbox = await transport.fetchInbox();
3549
+ if (current === generation) set({
3550
+ ...state,
3551
+ status: "ready",
3552
+ inbox,
3553
+ error: null
3554
+ });
3555
+ } catch (error) {
3556
+ if (current === generation) set({
3557
+ ...state,
3558
+ status: "error",
3559
+ error: error instanceof Error ? error.message : String(error)
3560
+ });
3561
+ } finally {
3562
+ if (current === generation) schedule();
3563
+ }
3564
+ }
3565
+ const onFocus = () => {
3566
+ if (listeners.size > 0) refresh();
3567
+ };
3568
+ const showNotice = (notice) => {
3569
+ clearTimeout(noticeTimer);
3570
+ set({
3571
+ ...state,
3572
+ notice
3573
+ });
3574
+ if (notice !== null) noticeTimer = setTimeout(() => set({
3575
+ ...state,
3576
+ notice: null
3577
+ }), NOTICE_TTL);
3578
+ };
3579
+ return {
3580
+ getSnapshot: () => state,
3581
+ subscribe(listener) {
3582
+ listeners.add(listener);
3583
+ if (listeners.size === 1) {
3584
+ window.addEventListener("focus", onFocus);
3585
+ refresh();
3586
+ }
3587
+ return () => {
3588
+ listeners.delete(listener);
3589
+ if (listeners.size === 0) {
3590
+ window.removeEventListener("focus", onFocus);
3591
+ clearTimeout(timer);
3592
+ }
3593
+ };
3594
+ },
3595
+ refresh,
3596
+ async act(action, notice) {
3597
+ const result = await transport.postAction(action);
3598
+ generation += 1;
3599
+ set({
3600
+ ...state,
3601
+ status: "ready",
3602
+ inbox: result.inbox,
3603
+ error: null
3604
+ });
3605
+ if (result.ok && notice !== void 0) showNotice(notice);
3606
+ schedule();
3607
+ return result;
3608
+ },
3609
+ clearNotice() {
3610
+ showNotice(null);
3611
+ },
3612
+ dispose() {
3613
+ clearTimeout(timer);
3614
+ clearTimeout(noticeTimer);
3615
+ window.removeEventListener("focus", onFocus);
3616
+ listeners.clear();
3617
+ }
3618
+ };
3619
+ }
3620
+ //#endregion
3621
+ //#region src/client/source.ts
3622
+ function createSnapshotSource(initial, load) {
3623
+ let value = initial;
3624
+ let loaded = false;
3625
+ let generation = 0;
3626
+ const listeners = /* @__PURE__ */ new Set();
3627
+ const run = () => {
3628
+ loaded = true;
3629
+ const current = ++generation;
3630
+ const set = (next) => {
3631
+ if (current !== generation) return;
3632
+ value = next;
3633
+ for (const listener of listeners) listener();
3634
+ };
3635
+ load(set).catch(() => set(initial));
3636
+ };
3637
+ return {
3638
+ getSnapshot: () => value,
3639
+ subscribe(listener) {
3640
+ listeners.add(listener);
3641
+ if (!loaded) run();
3642
+ return () => {
3643
+ listeners.delete(listener);
3644
+ };
3645
+ },
3646
+ reload() {
3647
+ if (loaded) run();
3648
+ }
3649
+ };
3650
+ }
3651
+ //#endregion
3652
+ //#region src/client/index.ts
3653
+ /**
3654
+ * 浏览器半部入口:注册文案、会话标题栏里的收件箱入口,以及插件页里的配置页。
3655
+ * 收件箱状态全局共用一份,所有会话的标题栏入口订阅同一个 store。
3656
+ */
3657
+ /** 宿主提醒入口的 order 是 20;本入口排在它后面。 */
3658
+ const HEADER_ORDER = 30;
3659
+ const inject = [
3660
+ "slots",
3661
+ "locale",
3662
+ "remote",
3663
+ "remote.session"
3664
+ ];
3665
+ function apply(ctx) {
3666
+ ctx.effect(() => registerLocale(ctx.locale), "skill-scout: locale");
3667
+ const inbox = createInboxStore({
3668
+ fetchInbox: () => fetchInbox(),
3669
+ postAction
3670
+ });
3671
+ ctx.effect(() => () => inbox.dispose(), "skill-scout: inbox");
3672
+ ctx.on("connection/reset", () => void inbox.refresh());
3673
+ const face = {
3674
+ inbox,
3675
+ seen: createSeenStore(window.localStorage),
3676
+ loadDetail: (id, _version, signal) => fetchDetail(id, signal),
3677
+ openSession: (sessionId) => ctx.get("uiWorkspace")?.openSession(sessionId)
3678
+ };
3679
+ ctx.slots.inject("conversation.session.header.utilities", () => ctx.slots.register({
3680
+ name: "conversation.session.header.utilities",
3681
+ id: "skill-scout-inbox",
3682
+ order: HEADER_ORDER,
3683
+ locale: SCOUT_LOCALE,
3684
+ inject: () => face
3685
+ }, InboxEntry));
3686
+ const catalog = createSnapshotSource({ status: "loading" }, async (set) => {
3687
+ const response = await ctx.remote.session.modelCatalog();
3688
+ set(response.ok ? {
3689
+ status: "ready",
3690
+ catalog: response.value
3691
+ } : { status: "error" });
3692
+ });
3693
+ ctx.effect(() => ctx.remote.$on("llm/adapters-updated", () => catalog.reload()), "skill-scout: adapters");
3694
+ ctx.effect(() => ctx.remote.$on("llm/models-updated", () => catalog.reload()), "skill-scout: models");
3695
+ ctx.slots.inject("plugins.row.config", () => ctx.slots.register({
3696
+ name: "plugins.row.config",
3697
+ key: `${PACKAGE_NAME}#${ROW_ID}`,
3698
+ locale: SCOUT_LOCALE
3699
+ }, createSettingsPage(catalog)));
3700
+ }
3701
+ //#endregion
3702
+ exports.apply = apply;
3703
+ exports.inject = inject;
3704
+ return module.exports;
3705
+ }
3706
+ });
3707
+
3708
+ //# sourceMappingURL=client.js.map