@dsh-plus/agent-preset-rea 0.1.0 → 0.1.2

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/lib/client.js CHANGED
@@ -19,6 +19,64 @@ window.__ModuleLoader__.load({
19
19
  children: props.label
20
20
  });
21
21
  }
22
+ function TextField(props) {
23
+ const p = props.prefix ?? "dshp";
24
+ const invalid = props.invalid === true;
25
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
26
+ className: `${p}-field`,
27
+ children: [
28
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
29
+ className: `${p}-head`,
30
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
31
+ className: `${p}-label`,
32
+ htmlFor: props.id,
33
+ children: props.label
34
+ }), props.badge !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
35
+ className: `${p}-badge ${props.badge.set ? `${p}-badgeSet` : `${p}-badgeUnset`}`,
36
+ children: props.badge.text
37
+ }) : null]
38
+ }),
39
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
40
+ id: props.id,
41
+ className: `${p}-input${invalid ? ` ${p}-inputInvalid` : ""}`,
42
+ type: props.password === true ? "password" : "text",
43
+ inputMode: props.numeric === true ? "numeric" : void 0,
44
+ autoComplete: props.password === true ? "off" : void 0,
45
+ "aria-invalid": invalid || void 0,
46
+ value: props.value,
47
+ disabled: props.disabled === true,
48
+ onChange: (event) => props.onEdit(event.target.value)
49
+ }),
50
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
51
+ className: invalid ? `${p}-invalid` : `${p}-hint`,
52
+ children: invalid ? props.invalidLabel ?? "" : props.hint ?? ""
53
+ })
54
+ ]
55
+ });
56
+ }
57
+ /**
58
+ * 勾选行:自绘圆角扁平开关(switch),对齐官方 WebUI 无原生 checkbox 的
59
+ * 设计语言(官方选择类控件全走胶囊/菜单,见 cardCss 的 -switch 规则)。
60
+ * 语义保持 checkbox(无障碍树与表单语义不变),视觉自绘。
61
+ */
62
+ function CheckRow(props) {
63
+ const p = props.prefix ?? "dshp";
64
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
65
+ className: `${p}-checkRow`,
66
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
67
+ id: props.id,
68
+ type: "checkbox",
69
+ role: "switch",
70
+ "aria-checked": props.checked,
71
+ checked: props.checked,
72
+ disabled: props.disabled === true,
73
+ onChange: (event) => props.onEdit(event.target.checked)
74
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
75
+ htmlFor: props.id,
76
+ children: props.label
77
+ })]
78
+ });
79
+ }
22
80
  //#endregion
23
81
  //#region ../shared/src/client/card.tsx
24
82
  const IDLE_STATUS = {
@@ -194,6 +252,27 @@ window.__ModuleLoader__.load({
194
252
  };
195
253
  }
196
254
  //#endregion
255
+ //#region ../shared/src/client/fetch.ts
256
+ async function parse(res, guard) {
257
+ const body = await res.json();
258
+ if (!res.ok) throw new Error(body.error ?? `HTTP ${res.status}`);
259
+ if (guard !== void 0 && !guard(body)) throw new Error(`响应结构不符合预期(${res.url})`);
260
+ return body;
261
+ }
262
+ /** GET 同源 JSON 端点。 */
263
+ async function getJson(route, guard) {
264
+ return parse(await fetch(route, { credentials: "same-origin" }), guard);
265
+ }
266
+ /** POST 同源 JSON 端点(body 可省略)。 */
267
+ async function postJson(route, body, guard) {
268
+ return parse(await fetch(route, {
269
+ method: "POST",
270
+ credentials: "same-origin",
271
+ headers: { "content-type": "application/json" },
272
+ ...body === void 0 ? {} : { body: JSON.stringify(body) }
273
+ }), guard);
274
+ }
275
+ //#endregion
197
276
  //#region ../shared/src/client/i18n.ts
198
277
  /** 卡片通用文案(save/discard/unsaved/loading 等四插件共有键)。 */
199
278
  const commonZh = {
@@ -436,32 +515,608 @@ ${extra}
436
515
  const SETTINGS_NS = "dsh-plus-agent-preset-rea";
437
516
  //#endregion
438
517
  //#region src/client/draft.ts
439
- /** 解析值 → 草稿(非字符串一律视为空串)。 */
518
+ /** 布尔字段收窄(缺席或类型不符回落默认值)。 */
519
+ function boolOf(value, fallback) {
520
+ return typeof value === "boolean" ? value : fallback;
521
+ }
522
+ /** 解析值 → 草稿(类型不符一律回落默认)。 */
440
523
  function draftFromValue(value) {
441
- return { personaPrefix: typeof value.personaPrefix === "string" ? value.personaPrefix : "" };
524
+ return {
525
+ personaPrefix: typeof value.personaPrefix === "string" ? value.personaPrefix : "",
526
+ proxyPort: typeof value.proxyPort === "number" ? String(value.proxyPort) : "",
527
+ mcpEnabled: boolOf(value.mcpEnabled, true),
528
+ skillEnabled: boolOf(value.skillEnabled, true),
529
+ reverseSkillEnabled: boolOf(value.reverseSkillEnabled, true)
530
+ };
531
+ }
532
+ /**
533
+ * 代理端口文本 → 端口号。
534
+ * @returns 空串按 0(不用代理);非纯数字或越界返回 null(调用方据此判非法)。
535
+ */
536
+ function parseProxyPort(text) {
537
+ const trimmed = text.trim();
538
+ if (trimmed === "") return 0;
539
+ if (!/^\d+$/.test(trimmed)) return null;
540
+ const port = Number(trimmed);
541
+ return Number.isInteger(port) && port >= 0 && port <= 65535 ? port : null;
442
542
  }
443
543
  /** 草稿 → 提交形状(只回写本卡片负责的字段)。 */
444
544
  function toPatch(draft) {
445
- return { personaPrefix: draft.personaPrefix };
545
+ return {
546
+ personaPrefix: draft.personaPrefix,
547
+ proxyPort: parseProxyPort(draft.proxyPort) ?? 0,
548
+ mcpEnabled: draft.mcpEnabled,
549
+ skillEnabled: draft.skillEnabled,
550
+ reverseSkillEnabled: draft.reverseSkillEnabled
551
+ };
552
+ }
553
+ //#endregion
554
+ //#region src/client/api.ts
555
+ /**
556
+ * 受管安装/更新的端点通道(状态查询 + 三类动作)。
557
+ * 配置读写仍走 `ctx.remote.settings` 直连(见 card.tsx),这里只补官方传输没有的动作。
558
+ *
559
+ * 响应都经类型谓词在边界收窄:宿主契约漂移时立即报错,而不是渲染期白屏。
560
+ * @module agent-preset-rea/client/api
561
+ */
562
+ const ROUTE_BASE = "/dsh-plus/agent-preset-rea";
563
+ /** JobDto 守卫。 */
564
+ function isJob(value) {
565
+ if (typeof value !== "object" || value === null) return false;
566
+ const job = value;
567
+ return typeof job["id"] === "number" && typeof job["kind"] === "string" && typeof job["state"] === "string" && Array.isArray(job["log"]) && typeof job["progress"] === "object" && job["progress"] !== null;
568
+ }
569
+ /** StatusDto 守卫(结构性字段缺席即判不符)。 */
570
+ function isStatus(value) {
571
+ if (typeof value !== "object" || value === null) return false;
572
+ const body = value;
573
+ const install = body["install"];
574
+ const skill = body["skill"];
575
+ const mcp = body["mcp"];
576
+ const reverse = body["reverse"];
577
+ return typeof install === "object" && install !== null && typeof skill === "object" && skill !== null && typeof mcp === "object" && mcp !== null && typeof reverse === "object" && reverse !== null && typeof body["config"] === "object" && body["config"] !== null;
578
+ }
579
+ /** JobActionDto 守卫。 */
580
+ const isJobAction = (value) => {
581
+ if (typeof value !== "object" || value === null) return false;
582
+ const job = value["job"];
583
+ return job === null || job === void 0 || isJob(job);
584
+ };
585
+ /** 查询安装/skill/MCP 行状态与当前作业。 */
586
+ async function fetchStatus() {
587
+ return getJson(`${ROUTE_BASE}/status`, isStatus);
588
+ }
589
+ /**
590
+ * 启动作业。
591
+ * @param kind - `install` = 安装/更新 REA 包;`skill` = 更新 REA skill 副本;
592
+ * `reverse` = 更新 reverse-skill(GitHub 源)。
593
+ * @param proxyPort - 草稿里当前填的代理端口(未保存也即时生效)。
594
+ */
595
+ async function startJob(kind, proxyPort) {
596
+ return postJson(`${ROUTE_BASE}/${kind}`, { proxyPort }, isJobAction);
597
+ }
598
+ /** 取消当前作业。 */
599
+ async function cancelJob() {
600
+ return postJson(`${ROUTE_BASE}/cancel`, void 0, isJobAction);
601
+ }
602
+ //#endregion
603
+ //#region src/client/install.tsx
604
+ /**
605
+ * 卡片的「REA 安装与更新」区:只读状态表 + 四个动作(安装/更新 REA、更新 REA skill、
606
+ * 更新 reverse-skill、取消作业)+ 运行中作业的阶段进度条、已用时与日志尾部。
607
+ *
608
+ * 状态与作业都来自宿主半的同源端点(见 client/api.ts 与宿主 routes.ts):这里只管
609
+ * 轮询与呈现。作业在跑时按 {@link PROGRESS_POLL_MS} 轮询——进度是宿主侧的真事实,
610
+ * 浏览器不自己造百分比。
611
+ * @module agent-preset-rea/client/install
612
+ */
613
+ /** 作业运行中的状态轮询间隔(毫秒)。 */
614
+ const PROGRESS_POLL_MS = 1e3;
615
+ /** 错误对象 → 人话。 */
616
+ function messageOf(error) {
617
+ return error instanceof Error ? error.message : String(error);
618
+ }
619
+ /** 起止时间 → 已用时文本。 */
620
+ function elapsedOf(job) {
621
+ const start = Date.parse(job.startedAt);
622
+ if (Number.isNaN(start)) return "—";
623
+ const end = job.finishedAt === null ? Date.now() : Date.parse(job.finishedAt);
624
+ const seconds = Math.max(0, Math.round((end - start) / 1e3));
625
+ if (seconds < 60) return `${seconds}s`;
626
+ return `${Math.floor(seconds / 60)}m${String(seconds % 60).padStart(2, "0")}s`;
627
+ }
628
+ /** 作业状态 → 徽标类名后缀(视觉区分运行中/成功/失败)。 */
629
+ const STATE_CLASS = {
630
+ running: "Running",
631
+ ok: "Ok",
632
+ error: "Error",
633
+ cancelled: "Cancelled"
634
+ };
635
+ /** 作业种类 → 文案键。 */
636
+ const JOB_LABEL = {
637
+ install: "jobInstall",
638
+ skill: "jobSkill",
639
+ reverse: "jobReverse"
640
+ };
641
+ /** 进度条百分比(防御性收窄,避免越界宽度)。 */
642
+ function widthOf(percent) {
643
+ return `${Math.max(0, Math.min(100, percent))}%`;
644
+ }
645
+ /** 作业进度块:状态徽标 + 阶段 + 进度条 + 日志尾部。 */
646
+ function JobPanel(props) {
647
+ const { t, job } = props;
648
+ const stateKey = job.state === "running" ? "jobRunning" : job.state === "ok" ? "jobOk" : job.state === "error" ? "jobError" : "jobCancelled";
649
+ const lines = job.log.slice(-12);
650
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
651
+ className: "apr-job",
652
+ children: [
653
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
654
+ className: "apr-jobHead",
655
+ children: [
656
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
657
+ className: `apr-pill apr-pill${STATE_CLASS[job.state]}`,
658
+ children: t(stateKey)
659
+ }),
660
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
661
+ className: "apr-jobTitle",
662
+ children: t(JOB_LABEL[job.kind])
663
+ }),
664
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
665
+ className: "apr-hint",
666
+ children: [
667
+ t("jobPhase"),
668
+ " ",
669
+ job.progress.step,
670
+ "/",
671
+ job.progress.steps,
672
+ "·",
673
+ job.progress.phase
674
+ ]
675
+ }),
676
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
677
+ className: "apr-hint",
678
+ children: [
679
+ t("jobElapsed"),
680
+ " ",
681
+ elapsedOf(job)
682
+ ]
683
+ })
684
+ ]
685
+ }),
686
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
687
+ className: "apr-bar",
688
+ role: "progressbar",
689
+ "aria-valuemin": 0,
690
+ "aria-valuemax": 100,
691
+ "aria-valuenow": job.progress.percent,
692
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
693
+ className: "apr-barFill",
694
+ style: { width: widthOf(job.progress.percent) }
695
+ })
696
+ }),
697
+ job.error === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
698
+ className: "apr-error",
699
+ children: job.error
700
+ }),
701
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("details", {
702
+ className: "apr-logBox",
703
+ open: job.state !== "ok",
704
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("summary", {
705
+ className: "apr-logSummary",
706
+ children: t("jobLog")
707
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("pre", {
708
+ className: "apr-log",
709
+ children: lines.length === 0 ? t("jobLogEmpty") : lines.join("\n")
710
+ })]
711
+ })
712
+ ]
713
+ });
714
+ }
715
+ /** 一行状态(键 + 值)。 */
716
+ function StatusRow(props) {
717
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
718
+ className: "apr-installRow",
719
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", {
720
+ className: "apr-installKey",
721
+ children: props.label
722
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", {
723
+ className: "apr-installVal",
724
+ children: props.children
725
+ })]
726
+ });
727
+ }
728
+ /** 状态徽标(已就绪 / 未就绪 / 已关闭)。 */
729
+ function Pill(props) {
730
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
731
+ className: `apr-pill ${props.ok ? "apr-pillOk" : "apr-pillMiss"}`,
732
+ children: props.label
733
+ });
734
+ }
735
+ /** REA 包与 MCP 行状态。 */
736
+ function InstallRows(props) {
737
+ const { t, status } = props;
738
+ const mcpLabel = !status.mcp.enabled ? status.config.mcpEnabled ? t("mcpMissing") : t("mcpOff") : status.mcp.mode === "custom" ? t("mcpCustom") : t("mcpInstalled");
739
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
740
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(StatusRow, {
741
+ label: t("installPackage"),
742
+ children: [
743
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Pill, {
744
+ ok: status.install.installed,
745
+ label: t(status.install.installed ? "installed" : "notInstalled")
746
+ }),
747
+ status.install.version === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
748
+ className: "apr-mono",
749
+ children: status.install.version
750
+ }),
751
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
752
+ className: "apr-hint",
753
+ children: [
754
+ t("installTarget"),
755
+ " ",
756
+ status.config.reaVersion
757
+ ]
758
+ })
759
+ ]
760
+ }),
761
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatusRow, {
762
+ label: t("installPath"),
763
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
764
+ className: "apr-mono",
765
+ children: status.install.root
766
+ })
767
+ }),
768
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(StatusRow, {
769
+ label: t("mcpRow"),
770
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
771
+ className: "apr-mono",
772
+ children: mcpLabel
773
+ }), status.mcp.command === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("code", {
774
+ className: "apr-mono",
775
+ children: [
776
+ status.mcp.command,
777
+ " ",
778
+ status.mcp.args.join(" ")
779
+ ]
780
+ })]
781
+ })
782
+ ] });
783
+ }
784
+ /** REA 官方 skill 状态。 */
785
+ function SkillRows(props) {
786
+ const { t, skill, enabled } = props;
787
+ const version = skill.version === null ? "" : ` · ${skill.version}`;
788
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(StatusRow, {
789
+ label: t("skillSource"),
790
+ children: [
791
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Pill, {
792
+ ok: enabled,
793
+ label: t(enabled ? "enabled" : "disabled")
794
+ }),
795
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
796
+ className: "apr-mono",
797
+ children: t(skill.source === "updated" ? "skillUpdated" : "skillBundled")
798
+ }),
799
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
800
+ className: "apr-hint",
801
+ children: [
802
+ t("skillFiles"),
803
+ " ",
804
+ skill.files,
805
+ version
806
+ ]
807
+ }),
808
+ skill.updatedAt === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
809
+ className: "apr-hint",
810
+ children: [
811
+ t("updatedAt"),
812
+ " ",
813
+ skill.updatedAt
814
+ ]
815
+ })
816
+ ]
817
+ });
818
+ }
819
+ /** reverse-skill(GitHub 源)状态。 */
820
+ function ReverseRows(props) {
821
+ const { t, reverse, enabled } = props;
822
+ const active = reverse.activeGroups.length === 0 ? "—" : reverse.activeGroups.join(", ");
823
+ const all = reverse.groups.length === 0 ? "—" : reverse.groups.join(", ");
824
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
825
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(StatusRow, {
826
+ label: t("reverseSource"),
827
+ children: [
828
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Pill, {
829
+ ok: reverse.installed,
830
+ label: t(reverse.installed ? "installed" : "notInstalled")
831
+ }),
832
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
833
+ className: "apr-mono",
834
+ children: reverse.repo
835
+ }),
836
+ reverse.sha === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
837
+ className: "apr-hint",
838
+ children: [
839
+ t("reverseCommit"),
840
+ " ",
841
+ reverse.sha.slice(0, 12),
842
+ reverse.version === null ? "" : ` · v${reverse.version}`
843
+ ]
844
+ })
845
+ ]
846
+ }),
847
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(StatusRow, {
848
+ label: t("reverseGroups"),
849
+ children: [
850
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Pill, {
851
+ ok: enabled,
852
+ label: t(enabled ? "enabled" : "disabled")
853
+ }),
854
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
855
+ className: "apr-mono",
856
+ children: active
857
+ }),
858
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
859
+ className: "apr-hint",
860
+ children: [
861
+ t("reverseGroupsHint"),
862
+ " ",
863
+ all
864
+ ]
865
+ })
866
+ ]
867
+ }),
868
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(StatusRow, {
869
+ label: t("reversePath"),
870
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
871
+ className: "apr-mono",
872
+ children: reverse.tree
873
+ }), reverse.updatedAt === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
874
+ className: "apr-hint",
875
+ children: [
876
+ t("updatedAt"),
877
+ " ",
878
+ reverse.updatedAt
879
+ ]
880
+ })]
881
+ })
882
+ ] });
883
+ }
884
+ /** 只读状态表:REA 包 / MCP 行 / REA skill / reverse-skill。 */
885
+ function StatusTable(props) {
886
+ const { t, status } = props;
887
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("dl", {
888
+ className: "apr-install",
889
+ children: [
890
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InstallRows, {
891
+ t,
892
+ status
893
+ }),
894
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SkillRows, {
895
+ t,
896
+ skill: status.skill,
897
+ enabled: status.config.skillEnabled
898
+ }),
899
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ReverseRows, {
900
+ t,
901
+ reverse: status.reverse,
902
+ enabled: status.config.reverseSkillEnabled
903
+ })
904
+ ]
905
+ });
906
+ }
907
+ /** 动作按钮行。 */
908
+ function ActionRow(props) {
909
+ const { t, busy, running, disabled, proxyPort, onAction, onCancel } = props;
910
+ const blocked = disabled || busy || running || proxyPort === null;
911
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
912
+ className: "apr-btnRow",
913
+ children: [
914
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
915
+ prefix: "apr",
916
+ variant: "primary",
917
+ label: t("installAction"),
918
+ disabled: blocked,
919
+ onClick: () => onAction("install")
920
+ }),
921
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
922
+ prefix: "apr",
923
+ label: t("skillAction"),
924
+ disabled: blocked,
925
+ onClick: () => onAction("skill")
926
+ }),
927
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
928
+ prefix: "apr",
929
+ label: t("reverseAction"),
930
+ disabled: blocked,
931
+ onClick: () => onAction("reverse")
932
+ }),
933
+ running ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
934
+ prefix: "apr",
935
+ label: t("cancelAction"),
936
+ disabled: busy,
937
+ onClick: onCancel
938
+ }) : null
939
+ ]
940
+ });
941
+ }
942
+ function InstallSection(props) {
943
+ const { t, disabled, proxyPort } = props;
944
+ const [status, setStatus] = (0, react.useState)(null);
945
+ const [error, setError] = (0, react.useState)(null);
946
+ const [busy, setBusy] = (0, react.useState)(false);
947
+ const alive = (0, react.useRef)(true);
948
+ (0, react.useEffect)(() => () => {
949
+ alive.current = false;
950
+ }, []);
951
+ const load = (0, react.useCallback)(async () => {
952
+ try {
953
+ const next = await fetchStatus();
954
+ if (!alive.current) return;
955
+ setStatus(next);
956
+ setError(null);
957
+ } catch (failure) {
958
+ if (alive.current) setError(`${t("statusFailed")}${messageOf(failure)}`);
959
+ }
960
+ }, [t]);
961
+ (0, react.useEffect)(() => {
962
+ load();
963
+ }, [load]);
964
+ const running = status?.job?.state === "running";
965
+ (0, react.useEffect)(() => {
966
+ if (running !== true) return;
967
+ const timer = window.setInterval(() => {
968
+ load();
969
+ }, PROGRESS_POLL_MS);
970
+ return () => {
971
+ window.clearInterval(timer);
972
+ };
973
+ }, [running, load]);
974
+ const runAction = (0, react.useCallback)((action) => {
975
+ setBusy(true);
976
+ setError(null);
977
+ action().then((result) => {
978
+ if (!alive.current) return;
979
+ setStatus((prev) => prev === null ? prev : {
980
+ ...prev,
981
+ job: result.job
982
+ });
983
+ load();
984
+ }).catch((failure) => {
985
+ if (alive.current) setError(`${t("actionFailed")}${messageOf(failure)}`);
986
+ }).finally(() => {
987
+ if (alive.current) setBusy(false);
988
+ });
989
+ }, [load, t]);
990
+ const job = status?.job ?? null;
991
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
992
+ className: "apr-field",
993
+ children: [
994
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
995
+ className: "apr-head",
996
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
997
+ className: "apr-label",
998
+ children: t("installSection")
999
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
1000
+ prefix: "apr",
1001
+ label: t("refreshStatus"),
1002
+ disabled: busy || running === true,
1003
+ onClick: () => {
1004
+ load();
1005
+ }
1006
+ })]
1007
+ }),
1008
+ status === null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1009
+ className: "apr-hint",
1010
+ children: t("statusLoading")
1011
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatusTable, {
1012
+ t,
1013
+ status
1014
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ActionRow, {
1015
+ t,
1016
+ busy,
1017
+ running: running === true,
1018
+ disabled,
1019
+ proxyPort,
1020
+ onAction: (kind) => {
1021
+ runAction(() => startJob(kind, proxyPort ?? 0));
1022
+ },
1023
+ onCancel: () => {
1024
+ runAction(() => cancelJob());
1025
+ }
1026
+ })] }),
1027
+ job === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(JobPanel, {
1028
+ t,
1029
+ job
1030
+ }),
1031
+ error === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1032
+ className: "apr-error",
1033
+ children: error
1034
+ }),
1035
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1036
+ className: "apr-hint",
1037
+ children: t("installHint")
1038
+ })
1039
+ ]
1040
+ });
1041
+ }
1042
+ //#endregion
1043
+ //#region src/client/toggles.tsx
1044
+ /**
1045
+ * 卡片的「能力开关」区:三个一键开关(REA MCP 行 / REA 官方 skill / reverse-skill
1046
+ * 技能包)。三者都是活动字段,保存后本预设换代注册——新建会话按新装配起(REA 工具
1047
+ * 与 skill 根的有无),已存在会话保持既有装配。
1048
+ *
1049
+ * 开关只管「挂不挂」,不管「装没装」:关掉不会删磁盘上的安装副本(更新/回滚仍是
1050
+ * 显式动作,见 install.tsx)。
1051
+ * @module agent-preset-rea/client/toggles
1052
+ */
1053
+ /** 三个开关的键与文案键。 */
1054
+ const TOGGLES = [
1055
+ {
1056
+ field: "mcpEnabled",
1057
+ id: "apr-toggle-mcp",
1058
+ label: "toggleMcp",
1059
+ hint: "toggleMcpHint"
1060
+ },
1061
+ {
1062
+ field: "skillEnabled",
1063
+ id: "apr-toggle-skill",
1064
+ label: "toggleSkill",
1065
+ hint: "toggleSkillHint"
1066
+ },
1067
+ {
1068
+ field: "reverseSkillEnabled",
1069
+ id: "apr-toggle-reverse",
1070
+ label: "toggleReverse",
1071
+ hint: "toggleReverseHint"
1072
+ }
1073
+ ];
1074
+ function ToggleSection(props) {
1075
+ const { t, draft, disabled, onEdit } = props;
1076
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
1077
+ className: "apr-field",
1078
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1079
+ className: "apr-head",
1080
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1081
+ className: "apr-label",
1082
+ children: t("toggleSection")
1083
+ })
1084
+ }), TOGGLES.map((toggle) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(CheckRow, {
1085
+ prefix: "apr",
1086
+ id: toggle.id,
1087
+ label: t(toggle.label),
1088
+ checked: draft[toggle.field],
1089
+ disabled,
1090
+ onEdit: (checked) => onEdit({ [toggle.field]: checked })
1091
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1092
+ className: "apr-hint",
1093
+ children: t(toggle.hint)
1094
+ })] }, toggle.field))]
1095
+ });
446
1096
  }
447
1097
  //#endregion
448
1098
  //#region src/client/card.tsx
449
1099
  /**
450
- * 「逆向工程」预设配置卡片:唯一字段是 persona 提示词。
1100
+ * 「逆向工程」预设配置卡片:提示词(活动字段)+ 代理端口(活动字段)+ 安装维护区。
451
1101
  *
452
1102
  * 经 injectPluginConfigCard 注册(插件页 plugins.row.config / plugins.bundle.config,
453
1103
  * view 分发 summary/page);外壳与草稿语义走 @dsh-plus/shared/client 套件
454
1104
  * (CardChrome + useNamespaceDraft:staged 草稿、脏判定、revision fencing、
455
- * Host 更新不覆盖在途编辑)。
1105
+ * Host 更新不覆盖在途编辑),安装维护区见 client/install.tsx。
456
1106
  *
457
- * 为什么只有这一个字段:`Config` 里只有 `personaPrefix` 标了 `.volatile()`
458
- * (对用户的承诺=必须给编辑器);MCP 参数、超时、附加 skill 根等属部署方平面,
459
- * 由 profile 的 cordis.patch.yml 行级配置覆盖,因此卡片底部只作说明。
460
- * 保存后 loader 原位提交活动引用并广播 `loader/volatile-update`,宿主半据此换代
461
- * 注册预设声明——新建会话即用新提示词,已存在会话保持原装配。
1107
+ * 五个活动字段(`Config` 里标 `.volatile()` 的)都在这里给出编辑器:
1108
+ * - `personaPrefix`:system-prompt 的 persona 前缀段;
1109
+ * - `proxyPort`:安装/更新时的本机代理端口;
1110
+ * - `mcpEnabled` / `skillEnabled` / `reverseSkillEnabled`:三个能力开关(见 toggles.tsx)。
1111
+ * 其余字段(受管目录、版本钉、skill 组、MCP 参数、超时、附加 skill 根)属部署方平面,
1112
+ * 由 profile 的 cordis.patch.yml 行级配置覆盖,卡片只读展示关键事实。
1113
+ *
1114
+ * 安装维护为什么不放在注册期自动做:启动期 `npx`/npm 安装会跑依赖安装脚本
1115
+ * (原生 prebuild 下载),卡住即吊死预设激活与整个名册;因此改成显式动作
1116
+ * ——后台作业 + 轮询实时进度,作业完成后宿主半换代注册,新建会话即用新事实。
462
1117
  * @module agent-preset-rea/client/card
463
1118
  */
464
- /** 卡片动作:放弃 / 保存(仅脏且可写时可用)。 */
1119
+ /** 卡片动作:放弃 / 保存(脏且可写且输入合法时才可用)。 */
465
1120
  function cardActions(options) {
466
1121
  return [{
467
1122
  key: "discard",
@@ -472,7 +1127,7 @@ ${extra}
472
1127
  key: "save",
473
1128
  label: options.t(options.saving ? "saving" : "save"),
474
1129
  variant: "primary",
475
- disabled: !options.dirty || options.saving || options.disabled,
1130
+ disabled: !options.dirty || options.saving || options.disabled || options.invalid,
476
1131
  onClick: options.onSave
477
1132
  }];
478
1133
  }
@@ -492,7 +1147,8 @@ ${extra}
492
1147
  text: t("loading")
493
1148
  });
494
1149
  const disabled = state.disabled;
495
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CardChrome, {
1150
+ const port = parseProxyPort(draft.proxyPort);
1151
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(CardChrome, {
496
1152
  prefix: "apr",
497
1153
  title: t("title"),
498
1154
  description: t("description"),
@@ -505,40 +1161,66 @@ ${extra}
505
1161
  dirty: state.dirty,
506
1162
  saving: state.saving,
507
1163
  disabled,
1164
+ invalid: port === null,
508
1165
  onDiscard: state.reset,
509
1166
  onSave: state.save
510
1167
  }),
511
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
512
- className: "apr-field",
513
- children: [
514
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
515
- className: "apr-head",
516
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
517
- className: "apr-label",
518
- htmlFor: "apr-prompt",
519
- children: t("prompt")
520
- })
521
- }),
522
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
523
- id: "apr-prompt",
524
- className: "apr-textarea",
525
- rows: 10,
526
- spellCheck: false,
527
- placeholder: t("promptPlaceholder"),
528
- value: draft.personaPrefix,
529
- disabled,
530
- onChange: (event) => state.edit({ personaPrefix: event.target.value })
531
- }),
532
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
533
- className: "apr-hint",
534
- children: t("promptHint")
535
- }),
536
- draft.personaPrefix.trim() === "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
537
- className: "apr-hint",
538
- children: t("promptEmpty")
539
- }) : null
540
- ]
541
- })
1168
+ children: [
1169
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1170
+ className: "apr-field",
1171
+ children: [
1172
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1173
+ className: "apr-head",
1174
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
1175
+ className: "apr-label",
1176
+ htmlFor: "apr-prompt",
1177
+ children: t("prompt")
1178
+ })
1179
+ }),
1180
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
1181
+ id: "apr-prompt",
1182
+ className: "apr-textarea",
1183
+ rows: 10,
1184
+ spellCheck: false,
1185
+ placeholder: t("promptPlaceholder"),
1186
+ value: draft.personaPrefix,
1187
+ disabled,
1188
+ onChange: (event) => state.edit({ personaPrefix: event.target.value })
1189
+ }),
1190
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1191
+ className: "apr-hint",
1192
+ children: t("promptHint")
1193
+ }),
1194
+ draft.personaPrefix.trim() === "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1195
+ className: "apr-hint",
1196
+ children: t("promptEmpty")
1197
+ }) : null
1198
+ ]
1199
+ }),
1200
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TextField, {
1201
+ prefix: "apr",
1202
+ id: "apr-proxy",
1203
+ label: t("proxyPort"),
1204
+ hint: t("proxyPortHint"),
1205
+ invalid: port === null,
1206
+ invalidLabel: t("proxyPortInvalid"),
1207
+ numeric: true,
1208
+ value: draft.proxyPort,
1209
+ disabled,
1210
+ onEdit: (text) => state.edit({ proxyPort: text })
1211
+ }),
1212
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToggleSection, {
1213
+ t,
1214
+ draft,
1215
+ disabled,
1216
+ onEdit: state.edit
1217
+ }),
1218
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InstallSection, {
1219
+ t,
1220
+ disabled,
1221
+ proxyPort: port
1222
+ })
1223
+ ]
542
1224
  });
543
1225
  }
544
1226
  //#endregion
@@ -551,32 +1233,148 @@ ${extra}
551
1233
  const NS = "dsh-plus-agent-preset-rea";
552
1234
  const ownZh = {
553
1235
  title: "逆向工程预设",
554
- description: "REA 提示词:留空即空 persona,工具指引与运行时上下文照常由官方装配注入。",
555
- summaryLine: "编辑「逆向工程」预设的 persona 提示词",
1236
+ description: "REA 提示词与安装维护:提示词留空即空 persona,工具指引与运行时上下文照常注入。",
1237
+ summaryLine: "编辑「逆向工程」预设的提示词、开关 REA MCP 与两类 skill,并安装/更新",
556
1238
  prompt: "系统提示词(persona 前缀)",
557
1239
  promptHint: "写入 system-prompt 的 persona 前缀段,仅对「逆向工程」预设的新会话生效;留空 = 不注入 persona 段(官方工具指引、项目指令与运行时上下文不受影响)。保存后新建会话即用新值。",
558
1240
  promptPlaceholder: "例如:先做证据分级,再动手改二进制;每一步给出可复核的证据链……",
559
1241
  promptEmpty: "当前为空:该预设不注入 persona 段。",
560
- saveFailed: "保存失败:"
1242
+ saveFailed: "保存失败:",
1243
+ installSection: "REA 安装与更新",
1244
+ installHint: "安装/更新在后台执行:把 rea-agents 装进下面的受管目录(先装 staging 再换目录,失败不留半截安装树),作业完成后本预设自动换代——新建会话即带 mcp__rea__* 工具。安装位置与 MCP 命令属部署方平面(profile 的 cordis.patch.yml),此处只读展示。",
1245
+ installPackage: "REA 包",
1246
+ installPath: "受管安装目录",
1247
+ installVersion: "版本",
1248
+ installTarget: "目标版本",
1249
+ installed: "已安装",
1250
+ notInstalled: "未安装",
1251
+ skillSource: "REA skill",
1252
+ skillBundled: "随包副本",
1253
+ skillUpdated: "手动更新副本",
1254
+ skillFiles: "文件",
1255
+ updatedAt: "更新时间",
1256
+ mcpRow: "MCP 行",
1257
+ mcpInstalled: "已挂载(受管安装入口)",
1258
+ mcpCustom: "已挂载(显式 mcpCommand)",
1259
+ mcpMissing: "未挂载:未安装 REA,该行已禁用(不会在启动期拉取)",
1260
+ mcpOff: "已按配置关闭",
1261
+ commandLabel: "命令",
1262
+ refreshStatus: "刷新状态",
1263
+ installAction: "安装 / 更新 REA",
1264
+ skillAction: "更新 skill",
1265
+ cancelAction: "取消作业",
1266
+ statusLoading: "正在读取安装状态…",
1267
+ statusFailed: "状态查询失败:",
1268
+ actionFailed: "操作失败:",
1269
+ jobRunning: "作业进行中",
1270
+ jobOk: "作业完成",
1271
+ jobError: "作业失败",
1272
+ jobCancelled: "作业已取消",
1273
+ jobInstall: "安装 REA",
1274
+ jobSkill: "更新 skill",
1275
+ jobPhase: "阶段",
1276
+ jobLog: "日志",
1277
+ jobStartedAt: "开始于",
1278
+ jobFinishedAt: "结束于",
1279
+ jobElapsed: "已用时",
1280
+ jobLogEmpty: "(暂无输出)",
1281
+ toggleSection: "能力开关",
1282
+ toggleMcp: "REA MCP 工具(mcp__rea__*)",
1283
+ toggleMcpHint: "关掉即从本预设摘掉 REA MCP 行,模型面看不到 mcp__rea__* 工具;REA 仍留在磁盘上,随时可开回来(保存后新建会话生效)。",
1284
+ toggleSkill: "REA 官方 skill",
1285
+ toggleSkillHint: "关掉即不挂 REA 官方 skill 根(reverse-engineer-anything),其余 skill 根不受影响。",
1286
+ toggleReverse: "reverse-skill 技能包",
1287
+ toggleReverseHint: "关掉即不挂 zhaoxuya520/reverse-skill 的技能组(默认挂主包根:路由技能 + 各模块技能);安装副本仍在,可从下面重新更新。",
1288
+ enabled: "已挂载",
1289
+ disabled: "已关闭",
1290
+ reverseSource: "reverse-skill",
1291
+ reverseCommit: "提交",
1292
+ reverseGroups: "技能组",
1293
+ reverseGroupsHint: "树内可选组:",
1294
+ reversePath: "技能包目录",
1295
+ reverseAction: "更新 reverse-skill",
1296
+ jobReverse: "更新 reverse-skill",
1297
+ proxyPort: "安装代理端口",
1298
+ proxyPortHint: "安装/更新时供 npm 与原生依赖(node-pty)prebuild 下载使用的本机代理端口;0 或留空 = 直连。未保存也用于本次安装,保存后长期生效。",
1299
+ proxyPortInvalid: "代理端口必须是 0..65535 的整数。"
561
1300
  };
562
1301
  const ownEn = {
563
1302
  title: "Reverse-engineering preset",
564
- description: "REA prompt: leave it empty for a blank persona; official tool guidance and runtime context stay in place.",
565
- summaryLine: "Edit the persona prompt of the “逆向工程” preset",
1303
+ description: "REA prompt and installation: an empty prompt means a blank persona; tool guidance and runtime context stay in place.",
1304
+ summaryLine: "Edit the “逆向工程” prompt, toggle REA MCP/skills, install and update them",
566
1305
  prompt: "System prompt (persona prefix)",
567
1306
  promptHint: "Written to the persona-prefix section of the system prompt for new sessions of this preset only. Empty means no persona section (official tool guidance, project instructions and runtime context are unaffected). New sessions pick up the saved value.",
568
1307
  promptPlaceholder: "e.g. Grade the evidence first, then patch the binary; every step needs a checkable chain of evidence…",
569
1308
  promptEmpty: "Currently empty: this preset injects no persona section.",
570
- saveFailed: "Save failed: "
1309
+ saveFailed: "Save failed: ",
1310
+ installSection: "REA installation",
1311
+ installHint: "Install/update runs in the background: rea-agents goes into the managed directory below (staged install, then directory swap, so a failure leaves no half tree). When the job finishes the preset re-registers — new sessions get the mcp__rea__* tools. The install location and MCP command belong to the deployment plane (profile cordis.patch.yml) and are shown read-only.",
1312
+ installPackage: "REA package",
1313
+ installPath: "Managed install dir",
1314
+ installVersion: "Version",
1315
+ installTarget: "Target version",
1316
+ installed: "Installed",
1317
+ notInstalled: "Not installed",
1318
+ skillSource: "REA skill",
1319
+ skillBundled: "Bundled copy",
1320
+ skillUpdated: "Manually updated copy",
1321
+ skillFiles: "Files",
1322
+ updatedAt: "Updated at",
1323
+ mcpRow: "MCP row",
1324
+ mcpInstalled: "Mounted (managed install entry)",
1325
+ mcpCustom: "Mounted (explicit mcpCommand)",
1326
+ mcpMissing: "Not mounted: REA is not installed, the row stays disabled (nothing is fetched at boot)",
1327
+ mcpOff: "Disabled by config",
1328
+ commandLabel: "Command",
1329
+ refreshStatus: "Refresh status",
1330
+ installAction: "Install / update REA",
1331
+ skillAction: "Update skill",
1332
+ cancelAction: "Cancel job",
1333
+ statusLoading: "Reading install status…",
1334
+ statusFailed: "Status query failed: ",
1335
+ actionFailed: "Action failed: ",
1336
+ jobRunning: "Job running",
1337
+ jobOk: "Job finished",
1338
+ jobError: "Job failed",
1339
+ jobCancelled: "Job cancelled",
1340
+ jobInstall: "Install REA",
1341
+ jobSkill: "Update skill",
1342
+ jobPhase: "Phase",
1343
+ jobLog: "Log",
1344
+ jobStartedAt: "Started",
1345
+ jobFinishedAt: "Finished",
1346
+ jobElapsed: "Elapsed",
1347
+ jobLogEmpty: "(no output yet)",
1348
+ toggleSection: "Capability switches",
1349
+ toggleMcp: "REA MCP tools (mcp__rea__*)",
1350
+ toggleMcpHint: "Turning this off drops the REA MCP row from this preset, so mcp__rea__* tools disappear; REA stays on disk and can be switched back on. New sessions pick up the change.",
1351
+ toggleSkill: "REA official skill",
1352
+ toggleSkillHint: "Turning this off unmounts the bundled REA skill root (reverse-engineer-anything); other skill roots are unaffected.",
1353
+ toggleReverse: "reverse-skill pack",
1354
+ toggleReverseHint: "Turning this off unmounts the skill groups of zhaoxuya520/reverse-skill (the main pack root is mounted by default: router skill plus module skills). The installed copy stays on disk and can be updated again below.",
1355
+ enabled: "Mounted",
1356
+ disabled: "Off",
1357
+ reverseSource: "reverse-skill",
1358
+ reverseCommit: "Commit",
1359
+ reverseGroups: "Skill groups",
1360
+ reverseGroupsHint: "Available in tree: ",
1361
+ reversePath: "Pack directory",
1362
+ reverseAction: "Update reverse-skill",
1363
+ jobReverse: "Update reverse-skill",
1364
+ proxyPort: "Install proxy port",
1365
+ proxyPortHint: "Local proxy port used by npm and by the native node-pty prebuild download during install/update; 0 or empty means direct. The unsaved value is used for the next install and applies permanently once saved.",
1366
+ proxyPortInvalid: "Proxy port must be an integer in 0..65535."
571
1367
  };
572
1368
  const zh = mergeDict(commonZh, ownZh);
573
1369
  const en = mergeDict(commonEn, ownEn);
574
1370
  //#endregion
575
1371
  //#region src/client/styles.ts
576
1372
  /**
577
- * 配置卡片样式:基础规则走 @dsh-plus/shared/client 的 cardCss('apr'),
578
- * 本文件只补插件特有规则(提示词多行输入框)。
1373
+ * 配置卡片样式:基础规则走 @dsh-plus/shared/client 的 cardCss('apr')
1374
+ * (按钮/字段/徽标等类名由共享控件生成,本文件**不**重复定义),这里只补
1375
+ * 插件特有规则:提示词多行框、安装状态表、作业进度条与日志。
579
1376
  * 沿用官方 data-plugin / data-plugin-css 约定(HMR 据此卸载)。
1377
+ * 颜色与度量全部走官方主题变量(--dsw-alias-*),不写死色值。
580
1378
  * @module agent-preset-rea/client/styles
581
1379
  */
582
1380
  const PLUGIN_ID = "@dsh-plus/agent-preset-rea";
@@ -586,8 +1384,28 @@ ${extra}
586
1384
  .apr-textarea:focus-visible{border-color:var(--dsw-alias-brand-primary);outline:none}
587
1385
  .apr-textarea:disabled{color:var(--dsw-alias-label-tertiary);cursor:default}
588
1386
  .apr-textarea::placeholder{color:var(--dsw-alias-label-tertiary)}
1387
+ .apr-btnRow{display:flex;flex-wrap:wrap;gap:8px;margin-top:10px}
1388
+ .apr-install{margin:6px 0 0;padding:0;display:flex;flex-direction:column;gap:6px}
1389
+ .apr-installRow{display:flex;gap:10px;align-items:baseline;flex-wrap:wrap}
1390
+ .apr-installKey{flex:none;width:118px;margin:0;font-size:12px;color:var(--dsw-alias-label-tertiary)}
1391
+ .apr-installVal{display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin:0;font-size:13px;color:var(--dsw-alias-label-primary)}
1392
+ .apr-mono{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px;word-break:break-all}
1393
+ .apr-pill{flex:none;padding:2px 8px;border-radius:999px;font-size:11px;line-height:16px}
1394
+ .apr-pillOk{background:var(--dsw-alias-state-success-secondary);color:var(--dsw-alias-state-success-primary)}
1395
+ .apr-pillMiss,.apr-pillError,.apr-pillCancelled{background:var(--dsw-alias-state-error-secondary);color:var(--dsw-alias-state-error-primary)}
1396
+ .apr-pillRunning{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}
1397
+ .apr-job{margin-top:12px;padding:10px 12px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-bg-layer-3)}
1398
+ .apr-jobHead{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
1399
+ .apr-jobTitle{font-size:13px;font-weight:500;color:var(--dsw-alias-label-primary)}
1400
+ .apr-bar{margin-top:8px;height:6px;border-radius:999px;background:var(--dsw-alias-interactive-bg-hover);overflow:hidden}
1401
+ .apr-barFill{height:100%;background:var(--dsw-alias-brand-primary);transition:width .3s}
1402
+ .apr-logBox{margin-top:8px}
1403
+ .apr-logSummary{font-size:12px;color:var(--dsw-alias-label-tertiary);cursor:pointer}
1404
+ .apr-log{margin:6px 0 0;max-height:200px;overflow:auto;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px;line-height:1.5;white-space:pre-wrap;word-break:break-all;color:var(--dsw-alias-label-tertiary)}
1405
+ .apr-error{margin:4px 0 0;font-size:12px;color:var(--dsw-alias-state-error-primary)}
589
1406
  @media (max-width:767px){
590
1407
  .apr-textarea{font-size:16px;min-height:140px}
1408
+ .apr-installKey{width:100%}
591
1409
  }
592
1410
  `);
593
1411
  /** 幂等注入样式标签;返回标签(已存在或环境无 document 时为 null)。 */