@klarkxy/dsh-self-improvement 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,897 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ let react = require("react");
3
+ let react_jsx_runtime = require("react/jsx-runtime");
4
+ //#region ../dsh-ai-services/src/client-utils.ts
5
+ /** The Editor retains its manuscript session separately; native DSH exposes the main-view binding. */
6
+ function selectedSessionId(client) {
7
+ return client.uiWorkspace.current ? client.uiWorkspace.current.getSnapshot()?.sessionId ?? "" : client.uiSession.adapter.current.getSnapshot().key ?? "";
8
+ }
9
+ /** Owner props win; native settings only supplies close, so resolve its selected session. */
10
+ function useNativeSeat(client, props) {
11
+ const current = (0, react.useSyncExternalStore)((0, react.useCallback)((fn) => (client.uiWorkspace.current ?? client.uiSession.adapter.current).subscribe(fn), [client]), (0, react.useCallback)(() => selectedSessionId(client), [client]), () => "");
12
+ const language = (0, react.useSyncExternalStore)((0, react.useCallback)((fn) => client.locale.subscribe(fn), [client]), (0, react.useCallback)(() => client.locale.getSnapshot().active, [client]), () => "en");
13
+ const row = props && typeof props === "object" ? props : {};
14
+ const owner = row.owner && typeof row.owner === "object" ? row.owner : {};
15
+ const sessionId = typeof row.sessionId === "string" ? row.sessionId : typeof owner.sessionId === "string" ? owner.sessionId : current;
16
+ const requestedLocale = row.locale ?? owner.locale ?? language;
17
+ return {
18
+ sessionId,
19
+ locale: String(requestedLocale).startsWith("zh") ? "zh" : "en",
20
+ hidden: row.hidden === true || owner.hidden === true
21
+ };
22
+ }
23
+ /** Refresh on native log boundaries/reconnect/focus. Read-only settling is bounded to an observed job. */
24
+ function useFeatureRefresh(client, sessionId, refresh, working = false, enabled = true) {
25
+ const refreshRef = (0, react.useRef)(refresh);
26
+ refreshRef.current = refresh;
27
+ (0, react.useEffect)(() => {
28
+ if (!enabled) return;
29
+ let timer;
30
+ const queue = () => {
31
+ if (typeof document !== "undefined" && document.visibilityState === "hidden") return;
32
+ clearTimeout(timer);
33
+ timer = setTimeout(() => refreshRef.current(), 150);
34
+ };
35
+ const stopEvents = (sessionId ? client.sessions.binding?.(sessionId)?.eventSource : void 0)?.subscribe(queue);
36
+ const stopConnection = client.connection?.generation?.subscribe(queue);
37
+ const visibility = () => {
38
+ if (document.visibilityState === "visible") queue();
39
+ };
40
+ window.addEventListener("focus", queue);
41
+ document.addEventListener("visibilitychange", visibility);
42
+ document.addEventListener("toggle", queue, true);
43
+ return () => {
44
+ clearTimeout(timer);
45
+ stopEvents?.();
46
+ stopConnection?.();
47
+ window.removeEventListener("focus", queue);
48
+ document.removeEventListener("visibilitychange", visibility);
49
+ document.removeEventListener("toggle", queue, true);
50
+ };
51
+ }, [
52
+ client,
53
+ sessionId,
54
+ enabled
55
+ ]);
56
+ (0, react.useEffect)(() => {
57
+ if (!enabled || !working) return;
58
+ let delay = 250;
59
+ const until = Date.now() + 3e5;
60
+ let timer;
61
+ const tick = () => {
62
+ if (Date.now() >= until) return;
63
+ if (document.visibilityState !== "hidden") refreshRef.current();
64
+ delay = Math.min(delay * 2, 2e3);
65
+ timer = setTimeout(tick, delay);
66
+ };
67
+ timer = setTimeout(tick, delay);
68
+ return () => clearTimeout(timer);
69
+ }, [
70
+ client,
71
+ sessionId,
72
+ working,
73
+ enabled
74
+ ]);
75
+ }
76
+ //#endregion
77
+ //#region src/contracts.ts
78
+ const SELF_IMPROVEMENT_RPC_CHANNEL = "/dsh-self-improvement";
79
+ const MEMORY_UNAVAILABLE_MESSAGE = "记忆服务不可用。请先单独启用「记忆」插件;启用自我改进不会自动打开记忆。";
80
+ const MEMORY_UNAVAILABLE_MESSAGE_EN = "Memory is unavailable. Enable the Memory plugin separately; turning on self-improvement does not enable Memory.";
81
+ //#endregion
82
+ //#region src/rpc-result.ts
83
+ function unwrap(result) {
84
+ if (!result.ok) throw new Error(result.error.message);
85
+ return result.value;
86
+ }
87
+ //#endregion
88
+ //#region src/skills.ts
89
+ function downloadMarkdown(filename, markdown, doc = typeof document === "undefined" ? void 0 : document) {
90
+ if (!doc) return false;
91
+ const blob = new Blob([markdown], { type: "text/markdown;charset=utf-8" });
92
+ const url = URL.createObjectURL(blob);
93
+ const anchor = doc.createElement("a");
94
+ anchor.href = url;
95
+ anchor.setAttribute("download", filename);
96
+ anchor.rel = "noopener";
97
+ doc.body.appendChild(anchor);
98
+ anchor.click();
99
+ anchor.remove();
100
+ URL.revokeObjectURL(url);
101
+ return true;
102
+ }
103
+ function exportRevocationCopy(locale) {
104
+ return locale === "en" ? "Export record revoked. Already downloaded files are not recalled." : "已撤回导出记录。不会收回或删除已下载的文件。";
105
+ }
106
+ function skillExportStateLabel(record, locale) {
107
+ if (record.exportState === "recorded") return locale === "en" ? "Download recorded" : "已记录下载";
108
+ if (record.exportState === "revoked") return locale === "en" ? "Download record revoked" : "已撤回下载记录";
109
+ return locale === "en" ? "Not downloaded" : "未下载";
110
+ }
111
+ //#endregion
112
+ //#region src/review-lifetime.ts
113
+ function createReviewGeneration(start = 0) {
114
+ let current = start;
115
+ return {
116
+ current: () => current,
117
+ next: () => {
118
+ current += 1;
119
+ return current;
120
+ },
121
+ isCurrent: (token) => token === current
122
+ };
123
+ }
124
+ function reviewRequestStillCurrent(input) {
125
+ return input.gate.isCurrent(input.token) && !input.signal.aborted && input.sessionId === input.viewSessionId;
126
+ }
127
+ /** Capture session, generation token, and abort lifetime before the first await. */
128
+ function beginReviewRequest(gate, sessionId, previous) {
129
+ previous?.abort();
130
+ const controller = new AbortController();
131
+ return {
132
+ sessionId,
133
+ token: gate.next(),
134
+ signal: controller.signal,
135
+ controller
136
+ };
137
+ }
138
+ function disposeReviewRequest(gate, controller) {
139
+ controller.abort();
140
+ gate.next();
141
+ }
142
+ function shouldSkipReviewRefresh(input) {
143
+ return input.busy || input.editing === true;
144
+ }
145
+ async function loadReviewSnapshot(input) {
146
+ const captured = input.sessionId;
147
+ const raw = await input.rpc("status", { sessionId: captured }, input.signal);
148
+ if (!reviewRequestStillCurrent({
149
+ token: input.token,
150
+ gate: input.gate,
151
+ signal: input.signal,
152
+ sessionId: captured,
153
+ viewSessionId: input.viewSessionId()
154
+ })) return void 0;
155
+ return unwrap(raw);
156
+ }
157
+ /** Read-only status peek; uses an independent signal so it cannot abort an in-flight write. */
158
+ async function peekReviewSnapshot(input) {
159
+ if (shouldSkipReviewRefresh({
160
+ busy: input.busy(),
161
+ editing: input.editing?.() === true
162
+ })) return void 0;
163
+ const token = input.token;
164
+ if (!input.gate.isCurrent(token) || token === 0) return void 0;
165
+ const next = await loadReviewSnapshot({
166
+ rpc: input.rpc,
167
+ sessionId: input.sessionId,
168
+ token,
169
+ gate: input.gate,
170
+ signal: new AbortController().signal,
171
+ viewSessionId: input.viewSessionId
172
+ });
173
+ if (!next) return void 0;
174
+ if (shouldSkipReviewRefresh({
175
+ busy: input.busy(),
176
+ editing: input.editing?.() === true
177
+ })) return void 0;
178
+ return next;
179
+ }
180
+ async function exportSkillIfCurrent(input) {
181
+ const captured = input.sessionId;
182
+ const still = () => reviewRequestStillCurrent({
183
+ token: input.token,
184
+ gate: input.gate,
185
+ signal: input.signal,
186
+ sessionId: captured,
187
+ viewSessionId: input.viewSessionId()
188
+ });
189
+ const prepared = unwrap(await input.rpc("skill.export", {
190
+ id: input.record.id,
191
+ expectedRevision: input.record.revision,
192
+ sessionId: captured
193
+ }, input.signal));
194
+ if (!still()) return "stale";
195
+ const started = (input.download ?? downloadMarkdown)(prepared.filename, prepared.markdown);
196
+ if (!still()) return "stale";
197
+ if (!started) throw new Error("未能开始下载。");
198
+ unwrap(await input.rpc("skill.exported", {
199
+ id: prepared.skill.id,
200
+ expectedRevision: prepared.skill.revision,
201
+ filename: prepared.filename,
202
+ sessionId: captured
203
+ }, input.signal));
204
+ if (!still()) return "stale";
205
+ return "recorded";
206
+ }
207
+ //#endregion
208
+ //#region src/client.tsx
209
+ const name = "dsh-self-improvement-client";
210
+ const inject = [
211
+ "slots",
212
+ "connection",
213
+ "sessions",
214
+ "locale",
215
+ "uiWorkspace",
216
+ "uiSession"
217
+ ];
218
+ function parseSeatProps(props) {
219
+ const row = props && typeof props === "object" ? props : {};
220
+ const nested = row.owner && typeof row.owner === "object" ? row.owner : void 0;
221
+ return {
222
+ sessionId: typeof row.sessionId === "string" && row.sessionId ? row.sessionId : typeof nested?.sessionId === "string" ? nested.sessionId : "",
223
+ locale: row.locale === "en" || nested?.locale === "en" ? "en" : "zh",
224
+ hidden: row.hidden === true || nested?.hidden === true
225
+ };
226
+ }
227
+ function memoryUnavailableCopy(locale) {
228
+ return locale === "en" ? MEMORY_UNAVAILABLE_MESSAGE_EN : MEMORY_UNAVAILABLE_MESSAGE;
229
+ }
230
+ function statusLabel(status, locale) {
231
+ return (locale === "en" ? {
232
+ candidate: "Candidate",
233
+ active: "Active",
234
+ rejected: "Rejected",
235
+ superseded: "Superseded",
236
+ revoked: "Revoked",
237
+ deleted: "Deleted"
238
+ } : {
239
+ candidate: "候选",
240
+ active: "已生效",
241
+ rejected: "已拒绝",
242
+ superseded: "已替代",
243
+ revoked: "已撤回",
244
+ deleted: "已删除"
245
+ })[status];
246
+ }
247
+ const emptySnapshot = () => ({
248
+ memoryAvailable: false,
249
+ memoryMessage: MEMORY_UNAVAILABLE_MESSAGE,
250
+ generation: 0,
251
+ storageFailed: false,
252
+ lessons: [],
253
+ skills: []
254
+ });
255
+ function LessonEvidence({ record, locale }) {
256
+ return record.evidence.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
257
+ className: "si-meta",
258
+ children: locale === "en" ? "No evidence refs." : "没有依据引用。"
259
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
260
+ className: "si-evidence",
261
+ children: record.evidence.map((ref) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", { children: [
262
+ ref.kind,
263
+ " · ",
264
+ ref.sessionId,
265
+ "#",
266
+ ref.seq,
267
+ ref.excerpt ? ` — ${ref.excerpt}` : ""
268
+ ] }, `${ref.sessionId}:${ref.seq}:${ref.kind}`))
269
+ });
270
+ }
271
+ function ConfirmButton(props) {
272
+ const [armed, setArmed] = (0, react.useState)(false);
273
+ const timer = (0, react.useRef)(void 0);
274
+ (0, react.useEffect)(() => () => clearTimeout(timer.current), []);
275
+ function disarm() {
276
+ clearTimeout(timer.current);
277
+ setArmed(false);
278
+ }
279
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
280
+ type: "button",
281
+ className: "si-danger",
282
+ disabled: props.disabled,
283
+ onClick: () => {
284
+ if (!armed) {
285
+ setArmed(true);
286
+ timer.current = setTimeout(() => setArmed(false), 3e3);
287
+ return;
288
+ }
289
+ disarm();
290
+ props.onConfirm();
291
+ },
292
+ onBlur: disarm,
293
+ children: armed ? props.confirmLabel : props.label
294
+ });
295
+ }
296
+ function LessonList(props) {
297
+ const { lessons, locale, busy, projectId, onPromote, onReject, onRevoke } = props;
298
+ if (lessons.length === 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
299
+ className: "si-empty",
300
+ children: locale === "en" ? "No lessons yet." : "还没有教训。"
301
+ });
302
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ol", {
303
+ className: "si-list",
304
+ children: lessons.map((record) => {
305
+ const candidate = record.status === "candidate";
306
+ const active = record.status === "active";
307
+ const foreignProject = record.scope.kind === "project" && Boolean(projectId) && record.scope.projectId !== projectId;
308
+ const scope = record.scope.kind === "global" ? locale === "en" ? "global" : "全局" : record.scope.projectId;
309
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
310
+ className: "si-card",
311
+ "data-status": record.status,
312
+ children: [
313
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", { children: record.title }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
314
+ className: "si-meta",
315
+ children: [
316
+ statusLabel(record.status, locale),
317
+ " · ",
318
+ scope
319
+ ]
320
+ })] }),
321
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: record.content }),
322
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LessonEvidence, {
323
+ record,
324
+ locale
325
+ }),
326
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
327
+ className: "si-actions",
328
+ children: [
329
+ active && record.scope.kind === "project" && !foreignProject ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
330
+ type: "button",
331
+ disabled: busy,
332
+ onClick: () => onPromote(record),
333
+ children: locale === "en" ? "Promote to global" : "提升为全局"
334
+ }) : null,
335
+ candidate ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConfirmButton, {
336
+ disabled: busy,
337
+ label: locale === "en" ? "Reject" : "拒绝",
338
+ confirmLabel: locale === "en" ? "Confirm reject?" : "确认拒绝?",
339
+ onConfirm: () => onReject(record)
340
+ }) : null,
341
+ active ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConfirmButton, {
342
+ disabled: busy,
343
+ label: locale === "en" ? "Revoke" : "撤回",
344
+ confirmLabel: locale === "en" ? "Confirm revoke?" : "确认撤回?",
345
+ onConfirm: () => onRevoke(record)
346
+ }) : null,
347
+ foreignProject ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
348
+ className: "si-meta",
349
+ children: locale === "en" ? "Other project — promotion is blocked here." : "其他项目的教训,此处不能提升。"
350
+ }) : null
351
+ ]
352
+ })
353
+ ]
354
+ }, record.id);
355
+ })
356
+ });
357
+ }
358
+ function SkillList(props) {
359
+ const { skills, locale, busy } = props;
360
+ if (skills.length === 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
361
+ className: "si-empty",
362
+ children: locale === "en" ? "No skill drafts." : "没有技能草稿。"
363
+ });
364
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ol", {
365
+ className: "si-list",
366
+ children: skills.map((record) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
367
+ className: "si-card",
368
+ "data-skill-status": record.status,
369
+ "data-export": record.exportState,
370
+ children: [
371
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", { children: record.title }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
372
+ className: "si-meta",
373
+ children: [
374
+ record.status,
375
+ " · ",
376
+ skillExportStateLabel(record, locale)
377
+ ]
378
+ })] }),
379
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("pre", {
380
+ className: "si-preview",
381
+ children: record.markdown
382
+ }),
383
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
384
+ className: "si-actions",
385
+ children: [
386
+ record.status === "preview" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
387
+ type: "button",
388
+ disabled: busy,
389
+ onClick: () => props.onAccept(record),
390
+ children: locale === "en" ? "Accept draft" : "接受草稿"
391
+ }) : null,
392
+ record.status === "preview" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
393
+ type: "button",
394
+ disabled: busy,
395
+ onClick: () => props.onReject(record),
396
+ children: locale === "en" ? "Reject draft" : "拒绝草稿"
397
+ }) : null,
398
+ record.status === "accepted" || record.status === "preview" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
399
+ type: "button",
400
+ disabled: busy,
401
+ onClick: () => props.onExport(record),
402
+ children: locale === "en" ? "Download Markdown" : "下载 Markdown"
403
+ }) : null,
404
+ record.exportState === "recorded" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConfirmButton, {
405
+ disabled: busy,
406
+ label: locale === "en" ? "Revoke export record" : "撤回导出记录",
407
+ confirmLabel: locale === "en" ? "Confirm revoke export record?" : "确认撤回导出记录?",
408
+ onConfirm: () => props.onUnexport(record)
409
+ }) : null,
410
+ record.status === "accepted" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConfirmButton, {
411
+ disabled: busy,
412
+ label: locale === "en" ? "Revoke skill" : "撤回技能",
413
+ confirmLabel: locale === "en" ? "Confirm revoke skill?" : "确认撤回技能?",
414
+ onConfirm: () => props.onRevoke(record)
415
+ }) : null
416
+ ]
417
+ })
418
+ ]
419
+ }, record.id))
420
+ });
421
+ }
422
+ function ReviewPanel({ client, sessionId, locale }) {
423
+ const tabsId = (0, react.useId)();
424
+ const gate = (0, react.useRef)(createReviewGeneration());
425
+ const sessionRef = (0, react.useRef)(sessionId);
426
+ const workRef = (0, react.useRef)(null);
427
+ sessionRef.current = sessionId;
428
+ const [tab, setTab] = (0, react.useState)("lessons");
429
+ const [snapshot, setSnapshot] = (0, react.useState)();
430
+ const [busy, setBusy] = (0, react.useState)(false);
431
+ const [note, setNote] = (0, react.useState)("");
432
+ const [error, setError] = (0, react.useState)("");
433
+ const [selected, setSelected] = (0, react.useState)([]);
434
+ const busyRef = (0, react.useRef)(false);
435
+ busyRef.current = busy;
436
+ function rpc(endpoint, payload, signal) {
437
+ return client.connection.rpc.call(SELF_IMPROVEMENT_RPC_CHANNEL, endpoint, payload, signal);
438
+ }
439
+ async function call(endpoint, payload = {}) {
440
+ return unwrap(await rpc(endpoint, payload));
441
+ }
442
+ (0, react.useEffect)(() => {
443
+ const request = beginReviewRequest(gate.current, sessionId, workRef.current);
444
+ workRef.current = request.controller;
445
+ setError("");
446
+ setNote("");
447
+ loadReviewSnapshot({
448
+ rpc,
449
+ sessionId: request.sessionId,
450
+ token: request.token,
451
+ gate: gate.current,
452
+ signal: request.signal,
453
+ viewSessionId: () => sessionRef.current
454
+ }).then((next) => {
455
+ if (next === void 0) return;
456
+ setSnapshot(next);
457
+ }).catch((cause) => {
458
+ if (!reviewRequestStillCurrent({
459
+ token: request.token,
460
+ gate: gate.current,
461
+ signal: request.signal,
462
+ sessionId: request.sessionId,
463
+ viewSessionId: sessionRef.current
464
+ })) return;
465
+ setError(cause instanceof Error ? cause.message : locale === "en" ? "Unable to read self-improvement state." : "无法读取自我改进状态。");
466
+ });
467
+ return () => disposeReviewRequest(gate.current, workRef.current ?? request.controller);
468
+ }, [
469
+ client,
470
+ sessionId,
471
+ locale
472
+ ]);
473
+ useFeatureRefresh(client, sessionId, () => {
474
+ peekReviewSnapshot({
475
+ rpc,
476
+ sessionId: sessionRef.current,
477
+ token: gate.current.current(),
478
+ gate: gate.current,
479
+ viewSessionId: () => sessionRef.current,
480
+ busy: () => busyRef.current,
481
+ editing: () => false
482
+ }).then((next) => {
483
+ if (next) setSnapshot(next);
484
+ }).catch(() => {});
485
+ }, snapshot?.extracting === true, true);
486
+ async function action(run) {
487
+ const request = beginReviewRequest(gate.current, sessionId, workRef.current);
488
+ workRef.current = request.controller;
489
+ setBusy(true);
490
+ setNote("");
491
+ setError("");
492
+ const still = () => reviewRequestStillCurrent({
493
+ token: request.token,
494
+ gate: gate.current,
495
+ signal: request.signal,
496
+ sessionId: request.sessionId,
497
+ viewSessionId: sessionRef.current
498
+ });
499
+ try {
500
+ await run({
501
+ sessionId: request.sessionId,
502
+ token: request.token,
503
+ signal: request.signal
504
+ });
505
+ } catch (cause) {
506
+ if (!still()) return;
507
+ setError(cause instanceof Error ? cause.message : locale === "en" ? "Action failed." : "操作失败。");
508
+ const next = await loadReviewSnapshot({
509
+ rpc,
510
+ sessionId: request.sessionId,
511
+ token: request.token,
512
+ gate: gate.current,
513
+ signal: request.signal,
514
+ viewSessionId: () => sessionRef.current
515
+ }).catch(() => void 0);
516
+ if (next) setSnapshot(next);
517
+ } finally {
518
+ if (still()) setBusy(false);
519
+ }
520
+ }
521
+ const data = snapshot ?? emptySnapshot();
522
+ const visibleLessons = data.lessons;
523
+ const body = /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
524
+ error ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
525
+ role: "alert",
526
+ className: "si-error",
527
+ children: error
528
+ }) : null,
529
+ note ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
530
+ role: "status",
531
+ children: note
532
+ }) : null,
533
+ data.storageFailed ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
534
+ role: "alert",
535
+ children: locale === "en" ? "Save failed; previous state was kept." : "保存失败,已保留上一次成功的状态。"
536
+ }) : null,
537
+ data.memoryAvailable ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
538
+ role: "alert",
539
+ className: "si-error",
540
+ children: memoryUnavailableCopy(locale)
541
+ }),
542
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
543
+ className: "si-tabs",
544
+ role: "tablist",
545
+ "aria-label": locale === "en" ? "Self-improvement" : "自我改进",
546
+ onKeyDown: (event) => {
547
+ if (![
548
+ "ArrowLeft",
549
+ "ArrowRight",
550
+ "Home",
551
+ "End"
552
+ ].includes(event.key)) return;
553
+ const buttons = [...event.currentTarget.querySelectorAll("[role=\"tab\"]")];
554
+ const index = buttons.indexOf(event.target);
555
+ if (index < 0) return;
556
+ event.preventDefault();
557
+ const next = event.key === "Home" ? 0 : event.key === "End" ? buttons.length - 1 : (index + (event.key === "ArrowRight" ? 1 : -1) + buttons.length) % buttons.length;
558
+ buttons[next]?.focus();
559
+ buttons[next]?.click();
560
+ },
561
+ children: [["lessons", locale === "en" ? "Lessons" : "教训"], ["skills", locale === "en" ? "Skills" : "技能草稿"]].map(([key, label]) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
562
+ type: "button",
563
+ role: "tab",
564
+ id: `${tabsId}-${key}-tab`,
565
+ "aria-controls": `${tabsId}-${key}-panel`,
566
+ "aria-selected": tab === key,
567
+ tabIndex: tab === key ? 0 : -1,
568
+ onClick: () => setTab(key),
569
+ children: label
570
+ }, key))
571
+ }),
572
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
573
+ role: "tabpanel",
574
+ id: `${tabsId}-lessons-panel`,
575
+ "aria-labelledby": `${tabsId}-lessons-tab`,
576
+ hidden: tab !== "lessons",
577
+ tabIndex: 0,
578
+ children: [
579
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
580
+ className: "si-meta",
581
+ children: locale === "en" ? "Extracted lessons take effect automatically and join prompts; revoke any time to retire one." : "摘录的教训自动生效并进入提示,可随时撤回。"
582
+ }),
583
+ sessionId ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
584
+ type: "button",
585
+ disabled: busy || !data.memoryAvailable,
586
+ onClick: () => void action(async (ctx) => {
587
+ if (!reviewRequestStillCurrent({
588
+ token: ctx.token,
589
+ gate: gate.current,
590
+ signal: ctx.signal,
591
+ sessionId: ctx.sessionId,
592
+ viewSessionId: sessionRef.current
593
+ })) return;
594
+ await call("extract", { sessionId: ctx.sessionId });
595
+ const next = await loadReviewSnapshot({
596
+ rpc,
597
+ sessionId: ctx.sessionId,
598
+ token: ctx.token,
599
+ gate: gate.current,
600
+ signal: ctx.signal,
601
+ viewSessionId: () => sessionRef.current
602
+ });
603
+ if (!next) return;
604
+ setSnapshot(next);
605
+ setNote(locale === "en" ? "Extracted from this session when evidence was sufficient." : "已按明确依据尝试摘录。");
606
+ }),
607
+ children: locale === "en" ? "Extract from this session" : "从本会话摘录"
608
+ }) : null,
609
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LessonList, {
610
+ lessons: visibleLessons,
611
+ locale,
612
+ busy: busy || !data.memoryAvailable,
613
+ projectId: data.projectId,
614
+ onPromote: (record) => void action(async (ctx) => {
615
+ await call("accept", {
616
+ id: record.id,
617
+ expectedRevision: record.revision,
618
+ scope: "global",
619
+ sessionId: ctx.sessionId
620
+ });
621
+ const next = await loadReviewSnapshot({
622
+ rpc,
623
+ sessionId: ctx.sessionId,
624
+ token: ctx.token,
625
+ gate: gate.current,
626
+ signal: ctx.signal,
627
+ viewSessionId: () => sessionRef.current
628
+ });
629
+ if (next) setSnapshot(next);
630
+ }),
631
+ onReject: (record) => void action(async (ctx) => {
632
+ await call("reject", {
633
+ id: record.id,
634
+ expectedRevision: record.revision,
635
+ sessionId: ctx.sessionId
636
+ });
637
+ const next = await loadReviewSnapshot({
638
+ rpc,
639
+ sessionId: ctx.sessionId,
640
+ token: ctx.token,
641
+ gate: gate.current,
642
+ signal: ctx.signal,
643
+ viewSessionId: () => sessionRef.current
644
+ });
645
+ if (next) setSnapshot(next);
646
+ }),
647
+ onRevoke: (record) => void action(async (ctx) => {
648
+ await call("revoke", {
649
+ id: record.id,
650
+ expectedRevision: record.revision,
651
+ sessionId: ctx.sessionId
652
+ });
653
+ const next = await loadReviewSnapshot({
654
+ rpc,
655
+ sessionId: ctx.sessionId,
656
+ token: ctx.token,
657
+ gate: gate.current,
658
+ signal: ctx.signal,
659
+ viewSessionId: () => sessionRef.current
660
+ });
661
+ if (next) setSnapshot(next);
662
+ })
663
+ })
664
+ ]
665
+ }),
666
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
667
+ role: "tabpanel",
668
+ id: `${tabsId}-skills-panel`,
669
+ "aria-labelledby": `${tabsId}-skills-tab`,
670
+ hidden: tab !== "skills",
671
+ tabIndex: 0,
672
+ children: skillManagement()
673
+ })
674
+ ] });
675
+ function skillManagement() {
676
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
677
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("fieldset", {
678
+ className: "si-select",
679
+ disabled: busy || !data.memoryAvailable,
680
+ children: [
681
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("legend", { children: locale === "en" ? "Active lessons for a skill draft" : "从已生效的教训生成草稿" }),
682
+ data.lessons.filter((record) => record.status === "active").map((record) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
683
+ type: "checkbox",
684
+ checked: selected.includes(record.id),
685
+ onChange: (event) => setSelected((current) => event.target.checked ? [...current, record.id] : current.filter((id) => id !== record.id))
686
+ }), record.title] }, record.id)),
687
+ data.lessons.some((record) => record.status === "active") ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
688
+ className: "si-meta",
689
+ children: locale === "en" ? "No active lessons yet." : "还没有已生效的教训。"
690
+ })
691
+ ]
692
+ }),
693
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
694
+ type: "button",
695
+ disabled: busy || selected.length === 0 || !data.memoryAvailable,
696
+ onClick: () => void action(async (ctx) => {
697
+ await call("skill.preview", {
698
+ lessonIds: selected,
699
+ sessionId: ctx.sessionId
700
+ });
701
+ const next = await loadReviewSnapshot({
702
+ rpc,
703
+ sessionId: ctx.sessionId,
704
+ token: ctx.token,
705
+ gate: gate.current,
706
+ signal: ctx.signal,
707
+ viewSessionId: () => sessionRef.current
708
+ });
709
+ if (!next) return;
710
+ setSnapshot(next);
711
+ setTab("skills");
712
+ }),
713
+ children: locale === "en" ? "Preview skill Markdown" : "预览技能 Markdown"
714
+ }),
715
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SkillList, {
716
+ skills: data.skills,
717
+ locale,
718
+ busy,
719
+ onAccept: (record) => void action(async (ctx) => {
720
+ await call("skill.accept", {
721
+ id: record.id,
722
+ expectedRevision: record.revision,
723
+ sessionId: ctx.sessionId
724
+ });
725
+ const next = await loadReviewSnapshot({
726
+ rpc,
727
+ sessionId: ctx.sessionId,
728
+ token: ctx.token,
729
+ gate: gate.current,
730
+ signal: ctx.signal,
731
+ viewSessionId: () => sessionRef.current
732
+ });
733
+ if (next) setSnapshot(next);
734
+ }),
735
+ onReject: (record) => void action(async (ctx) => {
736
+ await call("skill.reject", {
737
+ id: record.id,
738
+ expectedRevision: record.revision,
739
+ sessionId: ctx.sessionId
740
+ });
741
+ const next = await loadReviewSnapshot({
742
+ rpc,
743
+ sessionId: ctx.sessionId,
744
+ token: ctx.token,
745
+ gate: gate.current,
746
+ signal: ctx.signal,
747
+ viewSessionId: () => sessionRef.current
748
+ });
749
+ if (next) setSnapshot(next);
750
+ }),
751
+ onRevoke: (record) => void action(async (ctx) => {
752
+ await call("skill.revoke", {
753
+ id: record.id,
754
+ expectedRevision: record.revision,
755
+ sessionId: ctx.sessionId
756
+ });
757
+ const next = await loadReviewSnapshot({
758
+ rpc,
759
+ sessionId: ctx.sessionId,
760
+ token: ctx.token,
761
+ gate: gate.current,
762
+ signal: ctx.signal,
763
+ viewSessionId: () => sessionRef.current
764
+ });
765
+ if (next) setSnapshot(next);
766
+ }),
767
+ onExport: (record) => void action(async (ctx) => {
768
+ if (await exportSkillIfCurrent({
769
+ rpc,
770
+ sessionId: ctx.sessionId,
771
+ token: ctx.token,
772
+ gate: gate.current,
773
+ signal: ctx.signal,
774
+ viewSessionId: () => sessionRef.current,
775
+ record
776
+ }) !== "recorded") return;
777
+ const next = await loadReviewSnapshot({
778
+ rpc,
779
+ sessionId: ctx.sessionId,
780
+ token: ctx.token,
781
+ gate: gate.current,
782
+ signal: ctx.signal,
783
+ viewSessionId: () => sessionRef.current
784
+ });
785
+ if (!next) return;
786
+ setSnapshot(next);
787
+ setNote(locale === "en" ? "Browser download started. Destination is chosen in the save dialog." : "已开始浏览器下载,保存位置由系统对话框决定。");
788
+ }),
789
+ onUnexport: (record) => void action(async (ctx) => {
790
+ await call("skill.unexport", {
791
+ id: record.id,
792
+ expectedRevision: record.revision,
793
+ sessionId: ctx.sessionId
794
+ });
795
+ const next = await loadReviewSnapshot({
796
+ rpc,
797
+ sessionId: ctx.sessionId,
798
+ token: ctx.token,
799
+ gate: gate.current,
800
+ signal: ctx.signal,
801
+ viewSessionId: () => sessionRef.current
802
+ });
803
+ if (!next) return;
804
+ setSnapshot(next);
805
+ setNote(exportRevocationCopy(locale));
806
+ })
807
+ })
808
+ ] });
809
+ }
810
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
811
+ className: "si-settings",
812
+ "data-testid": "self-improvement-settings",
813
+ "data-session": sessionId || void 0,
814
+ children: [sessionId ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
815
+ className: "si-meta",
816
+ children: locale === "en" ? "Select a session to view lessons for the current project." : "选择一个会话后可查看当前项目的教训。"
817
+ }), body]
818
+ });
819
+ }
820
+ const styles = `
821
+ .si-settings{max-width:760px;color:inherit;font:400 var(--font-size-2,14px)/1.5 var(--default-font-family,system-ui,sans-serif)}
822
+ .si-settings{display:grid;gap:16px}
823
+ .si-settings p{margin:0;line-height:1.5}
824
+ .si-meta,.si-empty{font-size:var(--font-size-1,13px);color:var(--gray-11,inherit)}
825
+ .si-error{color:var(--red-11,#b42318)}
826
+ .si-card{display:grid;gap:8px;padding:12px 14px;border:1px solid var(--gray-6,color-mix(in srgb,currentColor 15%,transparent));border-radius:12px}
827
+ .si-card h3{margin:0;font-size:var(--font-size-3,16px);font-weight:600}
828
+ .si-list{margin:0;padding:0;list-style:none}
829
+ .si-evidence{margin:0;padding-left:1.2em;font-size:var(--font-size-1,13px)}
830
+ .si-actions{display:flex;flex-wrap:wrap;gap:8px}
831
+ .si-settings button:not([role="tab"]){min-height:34px;padding:6px 12px;border:1px solid color-mix(in srgb,currentColor 25%,transparent);border-radius:8px;background:transparent;color:inherit;cursor:pointer;font:inherit;justify-self:start;transition:background-color 150ms ease,color 150ms ease,border-color 150ms ease,box-shadow 150ms ease,transform 150ms ease}
832
+ .si-settings button:not([role="tab"]):hover:not(:disabled){background:var(--gray-3,color-mix(in srgb,currentColor 6%,transparent));border-color:color-mix(in srgb,currentColor 35%,transparent)}
833
+ .si-settings button:not([role="tab"]):active:not(:disabled){transform:scale(.97)}
834
+ .si-danger:hover:not(:disabled){border-color:var(--red-11,#b42318);color:var(--red-11,#b42318);background:color-mix(in srgb,var(--red-11,#b42318) 8%,transparent)}
835
+ .si-settings button:disabled{opacity:.45;cursor:not-allowed}
836
+ .si-tabs{display:flex;gap:20px;border-bottom:1px solid var(--gray-6,color-mix(in srgb,currentColor 15%,transparent))}
837
+ .si-tabs button[role="tab"]{padding:8px 0;border:0;border-bottom:2px solid transparent;border-radius:0;background:transparent;color:var(--gray-11,inherit);cursor:pointer;font:inherit;transition:color 150ms ease,border-color 150ms ease}
838
+ .si-tabs button[role="tab"]:hover{color:inherit}
839
+ .si-tabs button[aria-selected="true"]{border-bottom-color:var(--accent-9,#3b82f6);color:var(--accent-11,inherit);font-weight:600}
840
+ .si-settings [role="tabpanel"]{border-radius:12px}
841
+ .si-settings :focus-visible{outline:2px solid var(--accent-9,currentColor);outline-offset:3px}
842
+ .si-preview{margin:0;padding:8px 10px;border-radius:8px;background:var(--gray-2,color-mix(in srgb,currentColor 4%,transparent));white-space:pre-wrap;overflow:auto;max-height:240px;font:400 var(--font-size-1,13px)/1.45 var(--code-font-family,ui-monospace,monospace)}
843
+ .si-select{margin:0;border:1px solid var(--gray-6,color-mix(in srgb,currentColor 15%,transparent));border-radius:10px;padding:8px 12px;display:grid;gap:6px}
844
+ .si-select label{display:flex;gap:8px;align-items:flex-start}
845
+ @media(prefers-reduced-motion:reduce){.si-settings button{transition:none}}
846
+ `;
847
+ function reviewPanelKey(sessionId, locale) {
848
+ return `${sessionId}:${locale}`;
849
+ }
850
+ function SelfImprovementSettings({ client, props }) {
851
+ const seat = useNativeSeat(client, props);
852
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ReviewPanel, {
853
+ client,
854
+ sessionId: seat.sessionId,
855
+ locale: seat.locale
856
+ }, reviewPanelKey(seat.sessionId, seat.locale));
857
+ }
858
+ function apply(ctx) {
859
+ const client = ctx;
860
+ ctx.effect(() => {
861
+ if (typeof document === "undefined") return () => {};
862
+ const style = document.createElement("style");
863
+ style.setAttribute("data-plugin", "@klarkxy/dsh-self-improvement");
864
+ style.textContent = styles;
865
+ document.head.appendChild(style);
866
+ return () => style.remove();
867
+ }, "self-improvement.styles");
868
+ ctx.effect(() => client.slots.inject("settings.section", () => client.slots.register({
869
+ name: "settings.section",
870
+ id: "self-improvement",
871
+ order: 85,
872
+ label: "自我改进"
873
+ }, (props) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SelfImprovementSettings, {
874
+ client,
875
+ props
876
+ }))), "self-improvement.settings");
877
+ }
878
+ //#endregion
879
+ exports.ReviewPanel = ReviewPanel;
880
+ exports.SelfImprovementSettings = SelfImprovementSettings;
881
+ exports.apply = apply;
882
+ exports.beginReviewRequest = beginReviewRequest;
883
+ exports.createReviewGeneration = createReviewGeneration;
884
+ exports.disposeReviewRequest = disposeReviewRequest;
885
+ exports.exportSkillIfCurrent = exportSkillIfCurrent;
886
+ exports.inject = inject;
887
+ exports.loadReviewSnapshot = loadReviewSnapshot;
888
+ exports.memoryUnavailableCopy = memoryUnavailableCopy;
889
+ exports.name = name;
890
+ exports.parseSeatProps = parseSeatProps;
891
+ exports.peekReviewSnapshot = peekReviewSnapshot;
892
+ exports.reviewPanelKey = reviewPanelKey;
893
+ exports.reviewRequestStillCurrent = reviewRequestStillCurrent;
894
+ exports.shouldSkipReviewRefresh = shouldSkipReviewRefresh;
895
+ exports.unwrap = unwrap;
896
+
897
+ //# sourceMappingURL=client.inner.cjs.map