@dsh-plus/agent-preset-rea 0.0.0-stage → 0.1.1
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 +1197 -0
- package/lib/index.d.ts +431 -0
- package/lib/index.js +1224 -0
- package/package.json +61 -3
- package/skills/reverse-engineer-anything/LICENSE +21 -0
- package/skills/reverse-engineer-anything/SKILL.md +342 -0
- package/skills/reverse-engineer-anything/references/android-applications.md +34 -0
- package/skills/reverse-engineer-anything/references/connection-and-recovery.md +127 -0
- package/skills/reverse-engineer-anything/references/evidence-workflows.md +71 -0
- package/skills/reverse-engineer-anything/references/javascript-applications.md +175 -0
- package/skills/reverse-engineer-anything/references/native-and-artifacts.md +277 -0
- package/skills/reverse-engineer-anything/references/runtime-observation.md +78 -0
- package/src/client/api.ts +76 -0
- package/src/client/card.tsx +145 -0
- package/src/client/client.ts +51 -0
- package/src/client/draft.ts +55 -0
- package/src/client/i18n.ts +134 -0
- package/src/client/install.tsx +274 -0
- package/src/client/styles.ts +57 -0
- package/src/config.ts +121 -0
- package/src/definition.ts +245 -0
- package/src/dto.ts +87 -0
- package/src/exec.ts +155 -0
- package/src/index.ts +186 -0
- package/src/jobs.ts +145 -0
- package/src/manager.ts +419 -0
- package/src/mcp.ts +83 -0
- package/src/ns.ts +9 -0
- package/src/prompt.ts +25 -0
- package/src/registration.ts +86 -0
- package/src/routes.ts +181 -0
- package/src/skill.ts +34 -0
- package/README.md +0 -3
package/lib/client.js
ADDED
|
@@ -0,0 +1,1197 @@
|
|
|
1
|
+
window.__ModuleLoader__.load({
|
|
2
|
+
id: "@dsh-plus/agent-preset-rea",
|
|
3
|
+
factory: (require) => {
|
|
4
|
+
var module = { exports: {} };
|
|
5
|
+
var exports = module.exports;
|
|
6
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
7
|
+
let react_jsx_runtime = require("react/jsx-runtime");
|
|
8
|
+
let react = require("react");
|
|
9
|
+
require("@deepseek-ai/dsh-client-ui-primitives");
|
|
10
|
+
//#region ../shared/src/client/fields.tsx
|
|
11
|
+
/** 卡片按钮(与 CardChrome 页脚同一套类名与视觉)。 */
|
|
12
|
+
function Button(props) {
|
|
13
|
+
const p = props.prefix ?? "dshp";
|
|
14
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
15
|
+
type: "button",
|
|
16
|
+
className: `${p}-btn ${props.variant === "primary" ? `${p}-btnPrimary` : `${p}-btnGhost`}`,
|
|
17
|
+
disabled: props.disabled === true,
|
|
18
|
+
onClick: props.onClick,
|
|
19
|
+
children: props.label
|
|
20
|
+
});
|
|
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
|
+
//#endregion
|
|
58
|
+
//#region ../shared/src/client/card.tsx
|
|
59
|
+
const IDLE_STATUS = {
|
|
60
|
+
kind: "idle",
|
|
61
|
+
text: ""
|
|
62
|
+
};
|
|
63
|
+
function CardChrome(props) {
|
|
64
|
+
const p = props.prefix;
|
|
65
|
+
const badge = props.statusBadge;
|
|
66
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
67
|
+
className: `${p}-section`,
|
|
68
|
+
"aria-label": props.title,
|
|
69
|
+
children: [
|
|
70
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
71
|
+
className: `${p}-sectionHead`,
|
|
72
|
+
children: [
|
|
73
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
|
|
74
|
+
className: `${p}-sectionTitle`,
|
|
75
|
+
children: props.title
|
|
76
|
+
}),
|
|
77
|
+
badge !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
78
|
+
className: `${p}-sectionBadge ${badge.on ? `${p}-statusOn` : `${p}-statusOff`}`,
|
|
79
|
+
children: badge.text
|
|
80
|
+
}) : null,
|
|
81
|
+
props.dirty ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
82
|
+
className: `${p}-pending`,
|
|
83
|
+
children: props.dirtyLabel
|
|
84
|
+
}) : null
|
|
85
|
+
]
|
|
86
|
+
}),
|
|
87
|
+
props.description === "" ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
88
|
+
className: `${p}-sectionDesc`,
|
|
89
|
+
children: props.description
|
|
90
|
+
}),
|
|
91
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
92
|
+
className: `${p}-sectionBody`,
|
|
93
|
+
children: [
|
|
94
|
+
props.readOnlyNotice !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
95
|
+
className: `${p}-readOnly`,
|
|
96
|
+
role: "status",
|
|
97
|
+
children: props.readOnlyNotice
|
|
98
|
+
}) : null,
|
|
99
|
+
props.children,
|
|
100
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
101
|
+
className: `${p}-sectionFooter`,
|
|
102
|
+
children: [props.status !== void 0 && props.status.kind !== "idle" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
103
|
+
className: `${p}-status${props.status.kind === "error" ? ` ${p}-statusError` : ""}`,
|
|
104
|
+
role: "status",
|
|
105
|
+
children: props.status.text
|
|
106
|
+
}) : null, props.actions.map((action) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
|
|
107
|
+
prefix: p,
|
|
108
|
+
label: action.label,
|
|
109
|
+
variant: action.variant,
|
|
110
|
+
disabled: action.disabled,
|
|
111
|
+
onClick: action.onClick
|
|
112
|
+
}, action.key))]
|
|
113
|
+
})
|
|
114
|
+
]
|
|
115
|
+
})
|
|
116
|
+
]
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
/** 取值未到达时的占位:与 CardChrome 同形态,避免宿主页里闪一次卡片外壳。 */
|
|
120
|
+
function CardLoading(props) {
|
|
121
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("section", {
|
|
122
|
+
className: `${props.prefix}-section`,
|
|
123
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
124
|
+
className: `${props.prefix}-readOnly`,
|
|
125
|
+
role: "status",
|
|
126
|
+
children: props.text
|
|
127
|
+
})
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
//#endregion
|
|
131
|
+
//#region ../shared/src/client/config-slots.ts
|
|
132
|
+
/** dsh-plus 聚合 bundle 的包名(各插件 row 均经其补丁层插入 profile)。 */
|
|
133
|
+
const DSH_PLUS_BUNDLE = "@dsh-plus/bundle-main";
|
|
134
|
+
/**
|
|
135
|
+
* 把一张配置卡片注册进插件页的两个配置槽位。
|
|
136
|
+
* 两个槽位都由 0.2.1-alpha.2 的插件页声明,任一缺席即报错——不做版本探测。
|
|
137
|
+
*/
|
|
138
|
+
function injectPluginConfigCard(slots, reg) {
|
|
139
|
+
const base = {
|
|
140
|
+
locale: reg.ns,
|
|
141
|
+
inject: reg.inject
|
|
142
|
+
};
|
|
143
|
+
slots.inject("plugins.row.config", () => slots.register({
|
|
144
|
+
name: "plugins.row.config",
|
|
145
|
+
key: `${DSH_PLUS_BUNDLE}#${reg.rowId}`,
|
|
146
|
+
...base
|
|
147
|
+
}, reg.component));
|
|
148
|
+
slots.inject("plugins.bundle.config", () => slots.register({
|
|
149
|
+
name: "plugins.bundle.config",
|
|
150
|
+
key: reg.pkg,
|
|
151
|
+
...base
|
|
152
|
+
}, reg.component));
|
|
153
|
+
}
|
|
154
|
+
//#endregion
|
|
155
|
+
//#region ../shared/src/client/draft.ts
|
|
156
|
+
/**
|
|
157
|
+
* 单命名空间 staged 草稿(简单配置卡片共用):读 scope 快照、播种草稿、
|
|
158
|
+
* 脏判定、保存(revision fencing + 重新装载)与状态行。
|
|
159
|
+
*
|
|
160
|
+
* 语义与官方 staged 表单一致:Host 更新不覆盖在途编辑,只有保存才写回;
|
|
161
|
+
* 保存成功后按 Host 解析值重置草稿。`from`/`patch` 参与 effect 与 useMemo
|
|
162
|
+
* 依赖,必须是模块级稳定函数(不要内联箭头函数)。
|
|
163
|
+
* @module @dsh-plus/shared/client/draft
|
|
164
|
+
*/
|
|
165
|
+
function useNamespaceDraft(options) {
|
|
166
|
+
const { scope, api, from, patch, saveFailedLabel } = options;
|
|
167
|
+
const snapshot = (0, react.useSyncExternalStore)((listener) => scope.subscribe(listener), () => scope.getSnapshot());
|
|
168
|
+
const value = snapshot.value;
|
|
169
|
+
const [draft, setDraftState] = (0, react.useState)(null);
|
|
170
|
+
const [saving, setSaving] = (0, react.useState)(false);
|
|
171
|
+
const [status, setStatus] = (0, react.useState)(IDLE_STATUS);
|
|
172
|
+
(0, react.useEffect)(() => {
|
|
173
|
+
if (value === void 0 || draft !== null) return;
|
|
174
|
+
setDraftState(from(value));
|
|
175
|
+
}, [
|
|
176
|
+
value,
|
|
177
|
+
draft,
|
|
178
|
+
from
|
|
179
|
+
]);
|
|
180
|
+
const dirty = (0, react.useMemo)(() => value !== void 0 && draft !== null && JSON.stringify(patch(draft)) !== JSON.stringify(patch(from(value))), [
|
|
181
|
+
value,
|
|
182
|
+
draft,
|
|
183
|
+
from,
|
|
184
|
+
patch
|
|
185
|
+
]);
|
|
186
|
+
const setDraft = (next) => {
|
|
187
|
+
setDraftState(next);
|
|
188
|
+
setStatus(IDLE_STATUS);
|
|
189
|
+
};
|
|
190
|
+
const edit = (part) => {
|
|
191
|
+
if (draft === null) return;
|
|
192
|
+
setDraft({
|
|
193
|
+
...draft,
|
|
194
|
+
...part
|
|
195
|
+
});
|
|
196
|
+
};
|
|
197
|
+
const reset = () => {
|
|
198
|
+
if (value === void 0) return;
|
|
199
|
+
setDraft(from(value));
|
|
200
|
+
};
|
|
201
|
+
const save = () => {
|
|
202
|
+
if (draft === null) return;
|
|
203
|
+
setSaving(true);
|
|
204
|
+
const revision = scope.getSnapshot().revision;
|
|
205
|
+
api.update(patch(draft), revision).then(async () => {
|
|
206
|
+
await scope.load();
|
|
207
|
+
const next = scope.getSnapshot().value;
|
|
208
|
+
if (next !== void 0) setDraftState(from(next));
|
|
209
|
+
setStatus(IDLE_STATUS);
|
|
210
|
+
}).catch((error) => {
|
|
211
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
212
|
+
setStatus({
|
|
213
|
+
kind: "error",
|
|
214
|
+
text: `${saveFailedLabel}${message}`
|
|
215
|
+
});
|
|
216
|
+
}).finally(() => setSaving(false));
|
|
217
|
+
};
|
|
218
|
+
return {
|
|
219
|
+
value,
|
|
220
|
+
draft,
|
|
221
|
+
dirty,
|
|
222
|
+
saving,
|
|
223
|
+
status,
|
|
224
|
+
disabled: !snapshot.writable,
|
|
225
|
+
setDraft,
|
|
226
|
+
edit,
|
|
227
|
+
reset,
|
|
228
|
+
save
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
//#endregion
|
|
232
|
+
//#region ../shared/src/client/fetch.ts
|
|
233
|
+
async function parse(res, guard) {
|
|
234
|
+
const body = await res.json();
|
|
235
|
+
if (!res.ok) throw new Error(body.error ?? `HTTP ${res.status}`);
|
|
236
|
+
if (guard !== void 0 && !guard(body)) throw new Error(`响应结构不符合预期(${res.url})`);
|
|
237
|
+
return body;
|
|
238
|
+
}
|
|
239
|
+
/** GET 同源 JSON 端点。 */
|
|
240
|
+
async function getJson(route, guard) {
|
|
241
|
+
return parse(await fetch(route, { credentials: "same-origin" }), guard);
|
|
242
|
+
}
|
|
243
|
+
/** POST 同源 JSON 端点(body 可省略)。 */
|
|
244
|
+
async function postJson(route, body, guard) {
|
|
245
|
+
return parse(await fetch(route, {
|
|
246
|
+
method: "POST",
|
|
247
|
+
credentials: "same-origin",
|
|
248
|
+
headers: { "content-type": "application/json" },
|
|
249
|
+
...body === void 0 ? {} : { body: JSON.stringify(body) }
|
|
250
|
+
}), guard);
|
|
251
|
+
}
|
|
252
|
+
//#endregion
|
|
253
|
+
//#region ../shared/src/client/i18n.ts
|
|
254
|
+
/** 卡片通用文案(save/discard/unsaved/loading 等四插件共有键)。 */
|
|
255
|
+
const commonZh = {
|
|
256
|
+
save: "保存",
|
|
257
|
+
saving: "保存中…",
|
|
258
|
+
discard: "放弃",
|
|
259
|
+
unsaved: "未保存",
|
|
260
|
+
loading: "加载中…",
|
|
261
|
+
unavailable: "配置服务不可用。",
|
|
262
|
+
readOnly: "当前部署无 settings provider,配置为只读;请在 profile 的 cordis.patch.yml 行级配置。",
|
|
263
|
+
invalidNumber: "请输入正整数。",
|
|
264
|
+
collapse: "收起",
|
|
265
|
+
expand: "展开",
|
|
266
|
+
enabledOn: "已启用",
|
|
267
|
+
enabledOff: "未启用",
|
|
268
|
+
saveFailed: "保存失败,请检查填写内容。",
|
|
269
|
+
confirm: "确认",
|
|
270
|
+
cancel: "取消"
|
|
271
|
+
};
|
|
272
|
+
const commonEn = {
|
|
273
|
+
save: "Save",
|
|
274
|
+
saving: "Saving…",
|
|
275
|
+
discard: "Discard",
|
|
276
|
+
unsaved: "Unsaved",
|
|
277
|
+
loading: "Loading…",
|
|
278
|
+
unavailable: "Settings service unavailable.",
|
|
279
|
+
readOnly: "No settings provider in this deployment; config is read-only. Configure it at the profile row in cordis.patch.yml.",
|
|
280
|
+
invalidNumber: "Enter a positive integer.",
|
|
281
|
+
collapse: "Collapse",
|
|
282
|
+
expand: "Expand",
|
|
283
|
+
enabledOn: "On",
|
|
284
|
+
enabledOff: "Off",
|
|
285
|
+
saveFailed: "Save failed; check the fields.",
|
|
286
|
+
confirm: "Confirm",
|
|
287
|
+
cancel: "Cancel"
|
|
288
|
+
};
|
|
289
|
+
/**
|
|
290
|
+
* own 覆盖同键合并(返回新对象,入参不可变)。
|
|
291
|
+
* 返回类型保留两侧键集合,调用方据此派生 DictKey。
|
|
292
|
+
*/
|
|
293
|
+
function mergeDict(base, own) {
|
|
294
|
+
return {
|
|
295
|
+
...base,
|
|
296
|
+
...own
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
//#endregion
|
|
300
|
+
//#region ../shared/src/client/scope.ts
|
|
301
|
+
/**
|
|
302
|
+
* remote.settings 直连实现的命名空间 scope(官方配置页 tab 同款模式)。
|
|
303
|
+
* 读:describe 取命名空间视图(脱敏 value/revision + 顶层 writable);
|
|
304
|
+
* 刷新:remote `settings/document-updated`(按命名空间过滤)与
|
|
305
|
+
* `connection/reset`;generation 防旧读覆盖新发布。任何页面 origin
|
|
306
|
+
* (loopback 或 tailnet 信任域名)下行为一致。
|
|
307
|
+
* effect 归属调用方 fiber(label 需含插件名以便诊断)。
|
|
308
|
+
*/
|
|
309
|
+
function createSettingsScope(c, ns, label) {
|
|
310
|
+
const remote = c.get("remote");
|
|
311
|
+
let state = {
|
|
312
|
+
status: "loading",
|
|
313
|
+
value: void 0,
|
|
314
|
+
revision: void 0,
|
|
315
|
+
writable: false
|
|
316
|
+
};
|
|
317
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
318
|
+
let generation = 0;
|
|
319
|
+
const publish = (next) => {
|
|
320
|
+
state = next;
|
|
321
|
+
for (const listener of [...listeners]) listener();
|
|
322
|
+
};
|
|
323
|
+
const load = async () => {
|
|
324
|
+
const gen = ++generation;
|
|
325
|
+
let view;
|
|
326
|
+
try {
|
|
327
|
+
const response = await remote.settings.describe();
|
|
328
|
+
if (!response.ok) return;
|
|
329
|
+
view = response.value;
|
|
330
|
+
} catch {
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
if (gen !== generation) return;
|
|
334
|
+
const namespace = view.namespaces.find((candidate) => candidate.ns === ns);
|
|
335
|
+
if (namespace === void 0) {
|
|
336
|
+
publish({
|
|
337
|
+
status: "unavailable",
|
|
338
|
+
value: void 0,
|
|
339
|
+
revision: void 0,
|
|
340
|
+
writable: view.writable
|
|
341
|
+
});
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
publish({
|
|
345
|
+
status: "ready",
|
|
346
|
+
value: namespace.value,
|
|
347
|
+
revision: namespace.revision,
|
|
348
|
+
writable: view.writable
|
|
349
|
+
});
|
|
350
|
+
};
|
|
351
|
+
const refresh = (namespace) => {
|
|
352
|
+
if (namespace !== void 0 && namespace !== ns) return;
|
|
353
|
+
load();
|
|
354
|
+
};
|
|
355
|
+
const disposers = [remote.$on("settings/document-updated", refresh), c.on("connection/reset", () => {
|
|
356
|
+
load();
|
|
357
|
+
})];
|
|
358
|
+
c.effect(() => () => {
|
|
359
|
+
for (const dispose of disposers) dispose();
|
|
360
|
+
}, label);
|
|
361
|
+
load();
|
|
362
|
+
return {
|
|
363
|
+
getSnapshot: () => state,
|
|
364
|
+
subscribe: (listener) => {
|
|
365
|
+
listeners.add(listener);
|
|
366
|
+
return () => {
|
|
367
|
+
listeners.delete(listener);
|
|
368
|
+
};
|
|
369
|
+
},
|
|
370
|
+
load
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
/** 以 remote.settings 构造命名空间绑定的 api 面。 */
|
|
374
|
+
function createNamespaceApi(remote, ns) {
|
|
375
|
+
return {
|
|
376
|
+
update: async (patch, expectedRevision) => {
|
|
377
|
+
const response = await remote.update(ns, patch, expectedRevision);
|
|
378
|
+
if (!response.ok) throw new Error(response.error.message ?? "settings update failed");
|
|
379
|
+
},
|
|
380
|
+
describe: async () => {
|
|
381
|
+
const response = await remote.describe();
|
|
382
|
+
if (!response.ok) throw new Error(response.error.message ?? "settings describe failed");
|
|
383
|
+
return response.value;
|
|
384
|
+
}
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
//#endregion
|
|
388
|
+
//#region ../shared/src/client/styles.ts
|
|
389
|
+
/**
|
|
390
|
+
* 配置分节样式工厂(各插件 client 的公共收编版)。
|
|
391
|
+
*
|
|
392
|
+
* 目标宿主 dsh 0.2.1-alpha.2:插件页把配置视图放进已有页面级内边距的容器里
|
|
393
|
+
* (`div.detailSections[data-plugin-config]` 或 `section.detailSection[data-plugin-config]`),
|
|
394
|
+
* 外层无边框——故这里输出的是**无边框分节**(小节标题 13px/600、字段 12px 0、
|
|
395
|
+
* 横向零内边距、页脚非 sticky),度量对齐官方设置页
|
|
396
|
+
* (`@deepseek-ai/dsh-client-ui-primitives` 的 settings-form)。
|
|
397
|
+
*
|
|
398
|
+
* 移动端与可达性(767px 断点 + pointer:coarse,对齐 ui-mobile-fit 约定):
|
|
399
|
+
* - 窄屏 input/textarea 16px(防 iOS 聚焦缩放)、可点目标 ≥44px;
|
|
400
|
+
* - 页脚按钮等宽换行、状态文字独占一行。
|
|
401
|
+
* 沿用官方 data-plugin / data-plugin-css 约定(HMR 据此卸载),不覆盖上游选择器。
|
|
402
|
+
* @module @dsh-plus/shared/client/styles
|
|
403
|
+
*/
|
|
404
|
+
/** 生成全套分节 CSS;prefix 为插件类名前缀(如 'dne'),extra 为插件特有规则。 */
|
|
405
|
+
function cardCss(prefix, extra = "") {
|
|
406
|
+
return `
|
|
407
|
+
.${prefix}-section{box-sizing:border-box;flex-direction:column;width:100%;min-width:0;padding:16px 0;display:flex}
|
|
408
|
+
.${prefix}-sectionHead{align-items:center;gap:8px;min-width:0;display:flex}
|
|
409
|
+
.${prefix}-sectionTitle{min-width:0;color:var(--dsw-alias-label-primary);flex:1;margin:0;font-size:13px;font-weight:600;line-height:1.5}
|
|
410
|
+
.${prefix}-sectionBadge{white-space:nowrap;border-radius:999px;flex:none;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}
|
|
411
|
+
.${prefix}-sectionDesc{color:var(--dsw-alias-label-tertiary);margin:4px 0 0;font-size:12px;line-height:1.5}
|
|
412
|
+
.${prefix}-sectionBody{flex-direction:column;min-width:0;display:flex}
|
|
413
|
+
.${prefix}-sectionFooter{align-items:center;gap:8px;padding-top:16px;display:flex;flex-wrap:wrap}
|
|
414
|
+
.${prefix}-pending{white-space:nowrap;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);border-radius:999px;flex:none;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}
|
|
415
|
+
.${prefix}-statusOn{background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary)}
|
|
416
|
+
.${prefix}-statusOff{color:var(--dsw-alias-label-tertiary)}
|
|
417
|
+
.${prefix}-field{flex-direction:column;gap:6px;padding:12px 0;display:flex}
|
|
418
|
+
.${prefix}-field+.${prefix}-field{border-top:1px solid var(--dsw-alias-border-l2)}
|
|
419
|
+
.${prefix}-head{align-items:center;gap:8px;display:flex}
|
|
420
|
+
.${prefix}-label{min-width:0;color:var(--dsw-alias-label-primary);flex:1;font-size:13px;font-weight:500;line-height:1.5}
|
|
421
|
+
.${prefix}-badge{white-space:nowrap;border-radius:999px;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}
|
|
422
|
+
.${prefix}-badgeSet{background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary)}
|
|
423
|
+
.${prefix}-badgeUnset{color:var(--dsw-alias-label-tertiary)}
|
|
424
|
+
.${prefix}-input{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);height:34px;font:inherit;color:var(--dsw-alias-label-primary);border-radius:8px;padding:0 12px;font-size:13px;transition:border-color .18s,background .18s}
|
|
425
|
+
.${prefix}-input:hover:not(:disabled):not(:focus-visible){border-color:var(--dsw-alias-label-dimmed)}
|
|
426
|
+
.${prefix}-input:focus-visible{border-color:var(--dsw-alias-brand-primary);outline:none}
|
|
427
|
+
.${prefix}-input:disabled{color:var(--dsw-alias-label-tertiary);cursor:default}
|
|
428
|
+
.${prefix}-inputInvalid{border-color:var(--dsw-alias-label-error)}
|
|
429
|
+
.${prefix}-select{appearance:none;background:var(--dsw-alias-bg-module-platform);border:none;cursor:pointer;background-image:linear-gradient(45deg,transparent 50%,var(--dsw-alias-label-secondary) 50%),linear-gradient(135deg,var(--dsw-alias-label-secondary) 50%,transparent 50%);background-position:calc(100% - 16px) 50%,calc(100% - 11px) 50%;background-size:5px 5px;background-repeat:no-repeat;color:var(--dsw-alias-label-primary);font:inherit;font-size:13px;height:34px;box-sizing:border-box;border-radius:8px;padding:0 30px 0 12px;transition:background-color .18s}
|
|
430
|
+
.${prefix}-select:hover:not(:disabled){background-color:var(--dsw-alias-interactive-bg-hover)}
|
|
431
|
+
.${prefix}-hint{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:1.5}
|
|
432
|
+
.${prefix}-invalid{color:var(--dsw-alias-label-error);margin:0;font-size:12px;line-height:1.5}
|
|
433
|
+
.${prefix}-checkRow{align-items:center;gap:10px;display:flex;padding:6px 0}
|
|
434
|
+
.${prefix}-checkRow label{color:var(--dsw-alias-label-primary);font-size:13px;line-height:1.5;cursor:pointer;flex:1;min-width:0}
|
|
435
|
+
/* 自绘圆角扁平开关(官方 UI 无可见原生 checkbox,选择类全走胶囊/菜单) */
|
|
436
|
+
.${prefix}-checkRow input{appearance:none;-webkit-appearance:none;position:relative;box-sizing:border-box;background:var(--dsw-alias-bg-module-platform);border:none;border-radius:999px;width:34px;height:20px;flex:none;cursor:pointer;transition:background .18s;margin:0}
|
|
437
|
+
.${prefix}-checkRow input::after{content:"";position:absolute;top:2px;left:2px;background:var(--dsw-alias-label-primary);border-radius:999px;width:16px;height:16px;transition:left .18s cubic-bezier(.4,0,.2,1)}
|
|
438
|
+
.${prefix}-checkRow input:checked{background:var(--dsw-alias-brand-primary)}
|
|
439
|
+
.${prefix}-checkRow input:checked::after{left:16px;background:var(--dsw-alias-bg-base)}
|
|
440
|
+
.${prefix}-checkRow input:disabled{opacity:.4;cursor:default}
|
|
441
|
+
.${prefix}-checkRow input:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}
|
|
442
|
+
.${prefix}-groupLabel{color:var(--dsw-alias-label-secondary);font-size:12px;font-weight:600;padding:12px 0 2px;margin:0}
|
|
443
|
+
.${prefix}-readOnly{color:var(--dsw-alias-label-tertiary);margin:12px 0 0;font-size:12px;line-height:1.5}
|
|
444
|
+
.${prefix}-warn{color:var(--dsw-alias-label-error);margin:12px 0 0;font-size:12px;line-height:1.5}
|
|
445
|
+
.${prefix}-status{min-width:0;color:var(--dsw-alias-label-secondary);flex:1;margin:0;font-size:12px;line-height:1.5}
|
|
446
|
+
.${prefix}-statusError{color:var(--dsw-alias-label-error)}
|
|
447
|
+
.${prefix}-btn{appearance:none;font:inherit;cursor:pointer;border:1px solid #0000;border-radius:8px;padding:5px 14px;font-size:13px;line-height:1.5}
|
|
448
|
+
.${prefix}-btnGhost{border-color:var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);background:0 0}
|
|
449
|
+
.${prefix}-btnGhost:hover:not(:disabled){color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-dimmed)}
|
|
450
|
+
.${prefix}-btnPrimary{background:var(--dsw-alias-label-primary);color:var(--dsw-alias-bg-layer-3)}
|
|
451
|
+
.${prefix}-btn:disabled{opacity:.4;cursor:default}
|
|
452
|
+
.${prefix}-btn:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}
|
|
453
|
+
@media (max-width:767px){
|
|
454
|
+
.${prefix}-input,.${prefix}-select{font-size:16px;height:40px}
|
|
455
|
+
.${prefix}-checkRow{min-height:44px;padding:8px 0}
|
|
456
|
+
.${prefix}-status{flex-basis:100%;flex:none}
|
|
457
|
+
.${prefix}-btn{min-height:44px;flex:1;min-width:96px}
|
|
458
|
+
}
|
|
459
|
+
@media (pointer:coarse){
|
|
460
|
+
.${prefix}-btn{min-height:44px}
|
|
461
|
+
.${prefix}-checkRow{min-height:44px}
|
|
462
|
+
.${prefix}-input,.${prefix}-select{height:40px}
|
|
463
|
+
}
|
|
464
|
+
${extra}
|
|
465
|
+
`;
|
|
466
|
+
}
|
|
467
|
+
/**
|
|
468
|
+
* 幂等注入样式标签(官方 data-plugin-css 约定);返回新标签,
|
|
469
|
+
* 已存在或环境无 document 时返回 null(调用方 effect 卸载用)。
|
|
470
|
+
*/
|
|
471
|
+
function injectCardStyle(pluginId, css) {
|
|
472
|
+
if (typeof document === "undefined") return null;
|
|
473
|
+
const tagId = `${pluginId}/card.css`;
|
|
474
|
+
if (document.querySelector(`style[data-plugin-css=${JSON.stringify(tagId)}]`) !== null) return null;
|
|
475
|
+
const tag = document.createElement("style");
|
|
476
|
+
tag.dataset.plugin = pluginId;
|
|
477
|
+
tag.dataset.pluginCss = tagId;
|
|
478
|
+
tag.textContent = css;
|
|
479
|
+
document.head.appendChild(tag);
|
|
480
|
+
return tag;
|
|
481
|
+
}
|
|
482
|
+
//#endregion
|
|
483
|
+
//#region src/ns.ts
|
|
484
|
+
/**
|
|
485
|
+
* settings 命名空间字面量(纯常量,零依赖,浏览器半可安全引入)。
|
|
486
|
+
*
|
|
487
|
+
* 服务端以字面量参与 `Config` 的活动字段(dsh-settings 对命名空间做语法校验),
|
|
488
|
+
* 浏览器半把它作为配置卡片的注册键(插件页配置卡片挂在 row/bundle config 槽位上,
|
|
489
|
+
* 与命名空间解耦,见 shared/client/config-slots);同一字面量两处共用,防止漂移。
|
|
490
|
+
* @module @dsh-plus/agent-preset-rea/ns
|
|
491
|
+
*/
|
|
492
|
+
const SETTINGS_NS = "dsh-plus-agent-preset-rea";
|
|
493
|
+
/** 解析值 → 草稿(类型不符一律回落默认)。 */
|
|
494
|
+
function draftFromValue(value) {
|
|
495
|
+
return {
|
|
496
|
+
personaPrefix: typeof value.personaPrefix === "string" ? value.personaPrefix : "",
|
|
497
|
+
proxyPort: typeof value.proxyPort === "number" ? String(value.proxyPort) : ""
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
/**
|
|
501
|
+
* 代理端口文本 → 端口号。
|
|
502
|
+
* @returns 空串按 0(不用代理);非纯数字或越界返回 null(调用方据此判非法)。
|
|
503
|
+
*/
|
|
504
|
+
function parseProxyPort(text) {
|
|
505
|
+
const trimmed = text.trim();
|
|
506
|
+
if (trimmed === "") return 0;
|
|
507
|
+
if (!/^\d+$/.test(trimmed)) return null;
|
|
508
|
+
const port = Number(trimmed);
|
|
509
|
+
return Number.isInteger(port) && port >= 0 && port <= 65535 ? port : null;
|
|
510
|
+
}
|
|
511
|
+
/** 草稿 → 提交形状(只回写本卡片负责的字段)。 */
|
|
512
|
+
function toPatch(draft) {
|
|
513
|
+
return {
|
|
514
|
+
personaPrefix: draft.personaPrefix,
|
|
515
|
+
proxyPort: parseProxyPort(draft.proxyPort) ?? 0
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
//#endregion
|
|
519
|
+
//#region src/client/api.ts
|
|
520
|
+
/**
|
|
521
|
+
* 受管安装/更新的端点通道(状态查询 + 三类动作)。
|
|
522
|
+
* 配置读写仍走 `ctx.remote.settings` 直连(见 card.tsx),这里只补官方传输没有的动作。
|
|
523
|
+
*
|
|
524
|
+
* 响应都经类型谓词在边界收窄:宿主契约漂移时立即报错,而不是渲染期白屏。
|
|
525
|
+
* @module agent-preset-rea/client/api
|
|
526
|
+
*/
|
|
527
|
+
const ROUTE_BASE = "/dsh-plus/agent-preset-rea";
|
|
528
|
+
/** JobDto 守卫。 */
|
|
529
|
+
function isJob(value) {
|
|
530
|
+
if (typeof value !== "object" || value === null) return false;
|
|
531
|
+
const job = value;
|
|
532
|
+
return typeof job["id"] === "number" && typeof job["kind"] === "string" && typeof job["state"] === "string" && Array.isArray(job["log"]) && typeof job["progress"] === "object" && job["progress"] !== null;
|
|
533
|
+
}
|
|
534
|
+
/** StatusDto 守卫(结构性字段缺席即判不符)。 */
|
|
535
|
+
function isStatus(value) {
|
|
536
|
+
if (typeof value !== "object" || value === null) return false;
|
|
537
|
+
const body = value;
|
|
538
|
+
const install = body["install"];
|
|
539
|
+
const skill = body["skill"];
|
|
540
|
+
const mcp = body["mcp"];
|
|
541
|
+
return typeof install === "object" && install !== null && typeof skill === "object" && skill !== null && typeof mcp === "object" && mcp !== null && typeof body["config"] === "object" && body["config"] !== null;
|
|
542
|
+
}
|
|
543
|
+
/** JobActionDto 守卫。 */
|
|
544
|
+
const isJobAction = (value) => {
|
|
545
|
+
if (typeof value !== "object" || value === null) return false;
|
|
546
|
+
const job = value["job"];
|
|
547
|
+
return job === null || job === void 0 || isJob(job);
|
|
548
|
+
};
|
|
549
|
+
/** 查询安装/skill/MCP 行状态与当前作业。 */
|
|
550
|
+
async function fetchStatus() {
|
|
551
|
+
return getJson(`${ROUTE_BASE}/status`, isStatus);
|
|
552
|
+
}
|
|
553
|
+
/**
|
|
554
|
+
* 启动作业。
|
|
555
|
+
* @param kind - `install` = 安装/更新 REA 包;`skill` = 拉取并替换 skill 副本。
|
|
556
|
+
* @param proxyPort - 草稿里当前填的代理端口(未保存也即时生效)。
|
|
557
|
+
*/
|
|
558
|
+
async function startJob(kind, proxyPort) {
|
|
559
|
+
return postJson(`${ROUTE_BASE}/${kind}`, { proxyPort }, isJobAction);
|
|
560
|
+
}
|
|
561
|
+
/** 取消当前作业。 */
|
|
562
|
+
async function cancelJob() {
|
|
563
|
+
return postJson(`${ROUTE_BASE}/cancel`, void 0, isJobAction);
|
|
564
|
+
}
|
|
565
|
+
//#endregion
|
|
566
|
+
//#region src/client/install.tsx
|
|
567
|
+
/**
|
|
568
|
+
* 卡片的「REA 安装与更新」区:只读状态表 + 三个动作(安装/更新 REA、更新 skill、
|
|
569
|
+
* 取消作业)+ 运行中作业的阶段进度条、已用时与日志尾部。
|
|
570
|
+
*
|
|
571
|
+
* 状态与作业都来自宿主半的同源端点(见 client/api.ts 与宿主 routes.ts):这里只管
|
|
572
|
+
* 轮询与呈现。作业在跑时按 {@link PROGRESS_POLL_MS} 轮询——进度是宿主侧的真事实,
|
|
573
|
+
* 浏览器不自己造百分比。
|
|
574
|
+
* @module agent-preset-rea/client/install
|
|
575
|
+
*/
|
|
576
|
+
/** 作业运行中的状态轮询间隔(毫秒)。 */
|
|
577
|
+
const PROGRESS_POLL_MS = 1e3;
|
|
578
|
+
/** 错误对象 → 人话。 */
|
|
579
|
+
function messageOf(error) {
|
|
580
|
+
return error instanceof Error ? error.message : String(error);
|
|
581
|
+
}
|
|
582
|
+
/** 起止时间 → 已用时文本。 */
|
|
583
|
+
function elapsedOf(job) {
|
|
584
|
+
const start = Date.parse(job.startedAt);
|
|
585
|
+
if (Number.isNaN(start)) return "—";
|
|
586
|
+
const end = job.finishedAt === null ? Date.now() : Date.parse(job.finishedAt);
|
|
587
|
+
const seconds = Math.max(0, Math.round((end - start) / 1e3));
|
|
588
|
+
if (seconds < 60) return `${seconds}s`;
|
|
589
|
+
return `${Math.floor(seconds / 60)}m${String(seconds % 60).padStart(2, "0")}s`;
|
|
590
|
+
}
|
|
591
|
+
/** 作业状态 → 徽标类名后缀(视觉区分运行中/成功/失败)。 */
|
|
592
|
+
const STATE_CLASS = {
|
|
593
|
+
running: "Running",
|
|
594
|
+
ok: "Ok",
|
|
595
|
+
error: "Error",
|
|
596
|
+
cancelled: "Cancelled"
|
|
597
|
+
};
|
|
598
|
+
/** 作业进度块:状态徽标 + 阶段 + 进度条 + 日志尾部。 */
|
|
599
|
+
function JobPanel(props) {
|
|
600
|
+
const { t, job } = props;
|
|
601
|
+
const stateKey = job.state === "running" ? "jobRunning" : job.state === "ok" ? "jobOk" : job.state === "error" ? "jobError" : "jobCancelled";
|
|
602
|
+
const lines = job.log.slice(-12);
|
|
603
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
604
|
+
className: "apr-job",
|
|
605
|
+
children: [
|
|
606
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
607
|
+
className: "apr-jobHead",
|
|
608
|
+
children: [
|
|
609
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
610
|
+
className: `apr-pill apr-pill${STATE_CLASS[job.state]}`,
|
|
611
|
+
children: t(stateKey)
|
|
612
|
+
}),
|
|
613
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
614
|
+
className: "apr-jobTitle",
|
|
615
|
+
children: t(job.kind === "install" ? "jobInstall" : "jobSkill")
|
|
616
|
+
}),
|
|
617
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
618
|
+
className: "apr-hint",
|
|
619
|
+
children: [
|
|
620
|
+
t("jobPhase"),
|
|
621
|
+
" ",
|
|
622
|
+
job.progress.step,
|
|
623
|
+
"/",
|
|
624
|
+
job.progress.steps,
|
|
625
|
+
"·",
|
|
626
|
+
job.progress.phase
|
|
627
|
+
]
|
|
628
|
+
}),
|
|
629
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
630
|
+
className: "apr-hint",
|
|
631
|
+
children: [
|
|
632
|
+
t("jobElapsed"),
|
|
633
|
+
" ",
|
|
634
|
+
elapsedOf(job)
|
|
635
|
+
]
|
|
636
|
+
})
|
|
637
|
+
]
|
|
638
|
+
}),
|
|
639
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
640
|
+
className: "apr-bar",
|
|
641
|
+
role: "progressbar",
|
|
642
|
+
"aria-valuemin": 0,
|
|
643
|
+
"aria-valuemax": 100,
|
|
644
|
+
"aria-valuenow": job.progress.percent,
|
|
645
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
646
|
+
className: "apr-barFill",
|
|
647
|
+
style: { width: `${job.progress.percent}%` }
|
|
648
|
+
})
|
|
649
|
+
}),
|
|
650
|
+
job.error === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
651
|
+
className: "apr-error",
|
|
652
|
+
children: job.error
|
|
653
|
+
}),
|
|
654
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("details", {
|
|
655
|
+
className: "apr-logBox",
|
|
656
|
+
open: job.state !== "ok",
|
|
657
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("summary", {
|
|
658
|
+
className: "apr-logSummary",
|
|
659
|
+
children: t("jobLog")
|
|
660
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("pre", {
|
|
661
|
+
className: "apr-log",
|
|
662
|
+
children: lines.length === 0 ? t("jobLogEmpty") : lines.join("\n")
|
|
663
|
+
})]
|
|
664
|
+
})
|
|
665
|
+
]
|
|
666
|
+
});
|
|
667
|
+
}
|
|
668
|
+
/** 一行状态(键 + 值)。 */
|
|
669
|
+
function StatusRow(props) {
|
|
670
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
671
|
+
className: "apr-installRow",
|
|
672
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", {
|
|
673
|
+
className: "apr-installKey",
|
|
674
|
+
children: props.label
|
|
675
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", {
|
|
676
|
+
className: "apr-installVal",
|
|
677
|
+
children: props.children
|
|
678
|
+
})]
|
|
679
|
+
});
|
|
680
|
+
}
|
|
681
|
+
/** 只读状态表:REA 包 / 受管目录 / skill 来源 / MCP 行形态。 */
|
|
682
|
+
function StatusTable(props) {
|
|
683
|
+
const { t, status } = props;
|
|
684
|
+
const skillLabel = status.skill.source === "updated" ? t("skillUpdated") : t("skillBundled");
|
|
685
|
+
const mcpLabel = !status.mcp.enabled ? status.config.mcpEnabled ? t("mcpMissing") : t("mcpOff") : status.mcp.mode === "custom" ? t("mcpCustom") : t("mcpInstalled");
|
|
686
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("dl", {
|
|
687
|
+
className: "apr-install",
|
|
688
|
+
children: [
|
|
689
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(StatusRow, {
|
|
690
|
+
label: t("installPackage"),
|
|
691
|
+
children: [
|
|
692
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
693
|
+
className: `apr-pill ${status.install.installed ? "apr-pillOk" : "apr-pillMiss"}`,
|
|
694
|
+
children: t(status.install.installed ? "installed" : "notInstalled")
|
|
695
|
+
}),
|
|
696
|
+
status.install.version === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
697
|
+
className: "apr-mono",
|
|
698
|
+
children: status.install.version
|
|
699
|
+
}),
|
|
700
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
701
|
+
className: "apr-hint",
|
|
702
|
+
children: [
|
|
703
|
+
t("installTarget"),
|
|
704
|
+
" ",
|
|
705
|
+
status.config.reaVersion
|
|
706
|
+
]
|
|
707
|
+
})
|
|
708
|
+
]
|
|
709
|
+
}),
|
|
710
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatusRow, {
|
|
711
|
+
label: t("installPath"),
|
|
712
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
|
|
713
|
+
className: "apr-mono",
|
|
714
|
+
children: status.install.root
|
|
715
|
+
})
|
|
716
|
+
}),
|
|
717
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(StatusRow, {
|
|
718
|
+
label: t("skillSource"),
|
|
719
|
+
children: [
|
|
720
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
721
|
+
className: "apr-mono",
|
|
722
|
+
children: skillLabel
|
|
723
|
+
}),
|
|
724
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
725
|
+
className: "apr-hint",
|
|
726
|
+
children: [
|
|
727
|
+
t("skillFiles"),
|
|
728
|
+
" ",
|
|
729
|
+
status.skill.files,
|
|
730
|
+
status.skill.version === null ? "" : ` · ${status.skill.version}`
|
|
731
|
+
]
|
|
732
|
+
}),
|
|
733
|
+
status.skill.updatedAt === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
734
|
+
className: "apr-hint",
|
|
735
|
+
children: [
|
|
736
|
+
t("updatedAt"),
|
|
737
|
+
" ",
|
|
738
|
+
status.skill.updatedAt
|
|
739
|
+
]
|
|
740
|
+
})
|
|
741
|
+
]
|
|
742
|
+
}),
|
|
743
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(StatusRow, {
|
|
744
|
+
label: t("mcpRow"),
|
|
745
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
746
|
+
className: "apr-mono",
|
|
747
|
+
children: mcpLabel
|
|
748
|
+
}), status.mcp.command === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("code", {
|
|
749
|
+
className: "apr-mono",
|
|
750
|
+
children: [
|
|
751
|
+
status.mcp.command,
|
|
752
|
+
" ",
|
|
753
|
+
status.mcp.args.join(" ")
|
|
754
|
+
]
|
|
755
|
+
})]
|
|
756
|
+
})
|
|
757
|
+
]
|
|
758
|
+
});
|
|
759
|
+
}
|
|
760
|
+
function InstallSection(props) {
|
|
761
|
+
const { t, disabled, proxyPort } = props;
|
|
762
|
+
const [status, setStatus] = (0, react.useState)(null);
|
|
763
|
+
const [error, setError] = (0, react.useState)(null);
|
|
764
|
+
const [busy, setBusy] = (0, react.useState)(false);
|
|
765
|
+
const alive = (0, react.useRef)(true);
|
|
766
|
+
(0, react.useEffect)(() => () => {
|
|
767
|
+
alive.current = false;
|
|
768
|
+
}, []);
|
|
769
|
+
const load = (0, react.useCallback)(async () => {
|
|
770
|
+
try {
|
|
771
|
+
const next = await fetchStatus();
|
|
772
|
+
if (!alive.current) return;
|
|
773
|
+
setStatus(next);
|
|
774
|
+
setError(null);
|
|
775
|
+
} catch (failure) {
|
|
776
|
+
if (alive.current) setError(`${t("statusFailed")}${messageOf(failure)}`);
|
|
777
|
+
}
|
|
778
|
+
}, [t]);
|
|
779
|
+
(0, react.useEffect)(() => {
|
|
780
|
+
load();
|
|
781
|
+
}, [load]);
|
|
782
|
+
const running = status?.job?.state === "running";
|
|
783
|
+
(0, react.useEffect)(() => {
|
|
784
|
+
if (running !== true) return;
|
|
785
|
+
const timer = window.setInterval(() => {
|
|
786
|
+
load();
|
|
787
|
+
}, PROGRESS_POLL_MS);
|
|
788
|
+
return () => {
|
|
789
|
+
window.clearInterval(timer);
|
|
790
|
+
};
|
|
791
|
+
}, [running, load]);
|
|
792
|
+
const runAction = (0, react.useCallback)((action) => {
|
|
793
|
+
setBusy(true);
|
|
794
|
+
setError(null);
|
|
795
|
+
action().then((result) => {
|
|
796
|
+
if (!alive.current) return;
|
|
797
|
+
setStatus((prev) => prev === null ? prev : {
|
|
798
|
+
...prev,
|
|
799
|
+
job: result.job
|
|
800
|
+
});
|
|
801
|
+
load();
|
|
802
|
+
}).catch((failure) => {
|
|
803
|
+
if (alive.current) setError(`${t("actionFailed")}${messageOf(failure)}`);
|
|
804
|
+
}).finally(() => {
|
|
805
|
+
if (alive.current) setBusy(false);
|
|
806
|
+
});
|
|
807
|
+
}, [load, t]);
|
|
808
|
+
const blocked = disabled || busy || running === true;
|
|
809
|
+
const job = status?.job ?? null;
|
|
810
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
811
|
+
className: "apr-field",
|
|
812
|
+
children: [
|
|
813
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
814
|
+
className: "apr-head",
|
|
815
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
816
|
+
className: "apr-label",
|
|
817
|
+
children: t("installSection")
|
|
818
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
|
|
819
|
+
prefix: "apr",
|
|
820
|
+
label: t("refreshStatus"),
|
|
821
|
+
disabled: busy || running === true,
|
|
822
|
+
onClick: () => {
|
|
823
|
+
load();
|
|
824
|
+
}
|
|
825
|
+
})]
|
|
826
|
+
}),
|
|
827
|
+
status === null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
828
|
+
className: "apr-hint",
|
|
829
|
+
children: t("statusLoading")
|
|
830
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatusTable, {
|
|
831
|
+
t,
|
|
832
|
+
status
|
|
833
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
834
|
+
className: "apr-btnRow",
|
|
835
|
+
children: [
|
|
836
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
|
|
837
|
+
prefix: "apr",
|
|
838
|
+
variant: "primary",
|
|
839
|
+
label: t("installAction"),
|
|
840
|
+
disabled: blocked || proxyPort === null,
|
|
841
|
+
onClick: () => {
|
|
842
|
+
runAction(() => startJob("install", proxyPort ?? 0));
|
|
843
|
+
}
|
|
844
|
+
}),
|
|
845
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
|
|
846
|
+
prefix: "apr",
|
|
847
|
+
label: t("skillAction"),
|
|
848
|
+
disabled: blocked || proxyPort === null,
|
|
849
|
+
onClick: () => {
|
|
850
|
+
runAction(() => startJob("skill", proxyPort ?? 0));
|
|
851
|
+
}
|
|
852
|
+
}),
|
|
853
|
+
running === true ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
|
|
854
|
+
prefix: "apr",
|
|
855
|
+
label: t("cancelAction"),
|
|
856
|
+
disabled: busy,
|
|
857
|
+
onClick: () => {
|
|
858
|
+
runAction(() => cancelJob());
|
|
859
|
+
}
|
|
860
|
+
}) : null
|
|
861
|
+
]
|
|
862
|
+
})] }),
|
|
863
|
+
job === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(JobPanel, {
|
|
864
|
+
t,
|
|
865
|
+
job
|
|
866
|
+
}),
|
|
867
|
+
error === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
868
|
+
className: "apr-error",
|
|
869
|
+
children: error
|
|
870
|
+
}),
|
|
871
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
872
|
+
className: "apr-hint",
|
|
873
|
+
children: t("installHint")
|
|
874
|
+
})
|
|
875
|
+
]
|
|
876
|
+
});
|
|
877
|
+
}
|
|
878
|
+
//#endregion
|
|
879
|
+
//#region src/client/card.tsx
|
|
880
|
+
/**
|
|
881
|
+
* 「逆向工程」预设配置卡片:提示词(活动字段)+ 代理端口(活动字段)+ 安装维护区。
|
|
882
|
+
*
|
|
883
|
+
* 经 injectPluginConfigCard 注册(插件页 plugins.row.config / plugins.bundle.config,
|
|
884
|
+
* view 分发 summary/page);外壳与草稿语义走 @dsh-plus/shared/client 套件
|
|
885
|
+
* (CardChrome + useNamespaceDraft:staged 草稿、脏判定、revision fencing、
|
|
886
|
+
* Host 更新不覆盖在途编辑),安装维护区见 client/install.tsx。
|
|
887
|
+
*
|
|
888
|
+
* 两个活动字段(`Config` 里标 `.volatile()` 的)都在这里给出编辑器:
|
|
889
|
+
* - `personaPrefix`:system-prompt 的 persona 前缀段;
|
|
890
|
+
* - `proxyPort`:安装/更新时的本机代理端口。
|
|
891
|
+
* 其余字段(安装位置、MCP 参数、超时、附加 skill 根)属部署方平面,由 profile 的
|
|
892
|
+
* cordis.patch.yml 行级配置覆盖,卡片只读展示关键事实。
|
|
893
|
+
*
|
|
894
|
+
* 安装维护为什么不放在注册期自动做:启动期 `npx`/npm 安装会跑依赖安装脚本
|
|
895
|
+
* (原生 prebuild 下载),卡住即吊死预设激活与整个名册;因此改成显式动作
|
|
896
|
+
* ——后台作业 + 轮询实时进度,作业完成后宿主半换代注册,新建会话即用新事实。
|
|
897
|
+
* @module agent-preset-rea/client/card
|
|
898
|
+
*/
|
|
899
|
+
/** 卡片动作:放弃 / 保存(脏且可写且输入合法时才可用)。 */
|
|
900
|
+
function cardActions(options) {
|
|
901
|
+
return [{
|
|
902
|
+
key: "discard",
|
|
903
|
+
label: options.t("discard"),
|
|
904
|
+
disabled: !options.dirty || options.saving,
|
|
905
|
+
onClick: options.onDiscard
|
|
906
|
+
}, {
|
|
907
|
+
key: "save",
|
|
908
|
+
label: options.t(options.saving ? "saving" : "save"),
|
|
909
|
+
variant: "primary",
|
|
910
|
+
disabled: !options.dirty || options.saving || options.disabled || options.invalid,
|
|
911
|
+
onClick: options.onSave
|
|
912
|
+
}];
|
|
913
|
+
}
|
|
914
|
+
function ReaCard(props) {
|
|
915
|
+
const { t, scope, api } = props;
|
|
916
|
+
const state = useNamespaceDraft({
|
|
917
|
+
scope,
|
|
918
|
+
api,
|
|
919
|
+
from: draftFromValue,
|
|
920
|
+
patch: toPatch,
|
|
921
|
+
saveFailedLabel: t("saveFailed")
|
|
922
|
+
});
|
|
923
|
+
if (props.view === "summary") return t("summaryLine");
|
|
924
|
+
const draft = state.draft;
|
|
925
|
+
if (state.value === void 0 || draft === null) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CardLoading, {
|
|
926
|
+
prefix: "apr",
|
|
927
|
+
text: t("loading")
|
|
928
|
+
});
|
|
929
|
+
const disabled = state.disabled;
|
|
930
|
+
const port = parseProxyPort(draft.proxyPort);
|
|
931
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(CardChrome, {
|
|
932
|
+
prefix: "apr",
|
|
933
|
+
title: t("title"),
|
|
934
|
+
description: t("description"),
|
|
935
|
+
dirty: state.dirty,
|
|
936
|
+
dirtyLabel: t("unsaved"),
|
|
937
|
+
readOnlyNotice: disabled ? t("readOnly") : void 0,
|
|
938
|
+
status: state.status,
|
|
939
|
+
actions: cardActions({
|
|
940
|
+
t,
|
|
941
|
+
dirty: state.dirty,
|
|
942
|
+
saving: state.saving,
|
|
943
|
+
disabled,
|
|
944
|
+
invalid: port === null,
|
|
945
|
+
onDiscard: state.reset,
|
|
946
|
+
onSave: state.save
|
|
947
|
+
}),
|
|
948
|
+
children: [
|
|
949
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
950
|
+
className: "apr-field",
|
|
951
|
+
children: [
|
|
952
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
953
|
+
className: "apr-head",
|
|
954
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
|
|
955
|
+
className: "apr-label",
|
|
956
|
+
htmlFor: "apr-prompt",
|
|
957
|
+
children: t("prompt")
|
|
958
|
+
})
|
|
959
|
+
}),
|
|
960
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
|
|
961
|
+
id: "apr-prompt",
|
|
962
|
+
className: "apr-textarea",
|
|
963
|
+
rows: 10,
|
|
964
|
+
spellCheck: false,
|
|
965
|
+
placeholder: t("promptPlaceholder"),
|
|
966
|
+
value: draft.personaPrefix,
|
|
967
|
+
disabled,
|
|
968
|
+
onChange: (event) => state.edit({ personaPrefix: event.target.value })
|
|
969
|
+
}),
|
|
970
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
971
|
+
className: "apr-hint",
|
|
972
|
+
children: t("promptHint")
|
|
973
|
+
}),
|
|
974
|
+
draft.personaPrefix.trim() === "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
975
|
+
className: "apr-hint",
|
|
976
|
+
children: t("promptEmpty")
|
|
977
|
+
}) : null
|
|
978
|
+
]
|
|
979
|
+
}),
|
|
980
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TextField, {
|
|
981
|
+
prefix: "apr",
|
|
982
|
+
id: "apr-proxy",
|
|
983
|
+
label: t("proxyPort"),
|
|
984
|
+
hint: t("proxyPortHint"),
|
|
985
|
+
invalid: port === null,
|
|
986
|
+
invalidLabel: t("proxyPortInvalid"),
|
|
987
|
+
numeric: true,
|
|
988
|
+
value: draft.proxyPort,
|
|
989
|
+
disabled,
|
|
990
|
+
onEdit: (text) => state.edit({ proxyPort: text })
|
|
991
|
+
}),
|
|
992
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(InstallSection, {
|
|
993
|
+
t,
|
|
994
|
+
disabled,
|
|
995
|
+
proxyPort: port
|
|
996
|
+
})
|
|
997
|
+
]
|
|
998
|
+
});
|
|
999
|
+
}
|
|
1000
|
+
//#endregion
|
|
1001
|
+
//#region src/client/i18n.ts
|
|
1002
|
+
/**
|
|
1003
|
+
* 配置卡片文案(zh/en)。经 ctx.locale.register 注册、bind 取用,与官方卡片同机制。
|
|
1004
|
+
* 公共键(save/discard/unsaved 等)来自 shared 的 common 字典,本文件只维护业务键。
|
|
1005
|
+
* @module @dsh-plus/agent-preset-rea/client/i18n
|
|
1006
|
+
*/
|
|
1007
|
+
const NS = "dsh-plus-agent-preset-rea";
|
|
1008
|
+
const ownZh = {
|
|
1009
|
+
title: "逆向工程预设",
|
|
1010
|
+
description: "REA 提示词与安装维护:提示词留空即空 persona,工具指引与运行时上下文照常注入。",
|
|
1011
|
+
summaryLine: "编辑「逆向工程」预设的提示词,并安装/更新 REA 与 skill",
|
|
1012
|
+
prompt: "系统提示词(persona 前缀)",
|
|
1013
|
+
promptHint: "写入 system-prompt 的 persona 前缀段,仅对「逆向工程」预设的新会话生效;留空 = 不注入 persona 段(官方工具指引、项目指令与运行时上下文不受影响)。保存后新建会话即用新值。",
|
|
1014
|
+
promptPlaceholder: "例如:先做证据分级,再动手改二进制;每一步给出可复核的证据链……",
|
|
1015
|
+
promptEmpty: "当前为空:该预设不注入 persona 段。",
|
|
1016
|
+
saveFailed: "保存失败:",
|
|
1017
|
+
installSection: "REA 安装与更新",
|
|
1018
|
+
installHint: "安装/更新在后台执行:把 rea-agents 装进下面的受管目录(先装 staging 再换目录,失败不留半截安装树),作业完成后本预设自动换代——新建会话即带 mcp__rea__* 工具。安装位置与 MCP 命令属部署方平面(profile 的 cordis.patch.yml),此处只读展示。",
|
|
1019
|
+
installPackage: "REA 包",
|
|
1020
|
+
installPath: "受管安装目录",
|
|
1021
|
+
installVersion: "版本",
|
|
1022
|
+
installTarget: "目标版本",
|
|
1023
|
+
installed: "已安装",
|
|
1024
|
+
notInstalled: "未安装",
|
|
1025
|
+
skillSource: "REA skill",
|
|
1026
|
+
skillBundled: "随包副本",
|
|
1027
|
+
skillUpdated: "手动更新副本",
|
|
1028
|
+
skillFiles: "文件",
|
|
1029
|
+
updatedAt: "更新时间",
|
|
1030
|
+
mcpRow: "MCP 行",
|
|
1031
|
+
mcpInstalled: "已挂载(受管安装入口)",
|
|
1032
|
+
mcpCustom: "已挂载(显式 mcpCommand)",
|
|
1033
|
+
mcpMissing: "未挂载:未安装 REA,该行已禁用(不会在启动期拉取)",
|
|
1034
|
+
mcpOff: "已按配置关闭",
|
|
1035
|
+
commandLabel: "命令",
|
|
1036
|
+
refreshStatus: "刷新状态",
|
|
1037
|
+
installAction: "安装 / 更新 REA",
|
|
1038
|
+
skillAction: "更新 skill",
|
|
1039
|
+
cancelAction: "取消作业",
|
|
1040
|
+
statusLoading: "正在读取安装状态…",
|
|
1041
|
+
statusFailed: "状态查询失败:",
|
|
1042
|
+
actionFailed: "操作失败:",
|
|
1043
|
+
jobRunning: "作业进行中",
|
|
1044
|
+
jobOk: "作业完成",
|
|
1045
|
+
jobError: "作业失败",
|
|
1046
|
+
jobCancelled: "作业已取消",
|
|
1047
|
+
jobInstall: "安装 REA",
|
|
1048
|
+
jobSkill: "更新 skill",
|
|
1049
|
+
jobPhase: "阶段",
|
|
1050
|
+
jobLog: "日志",
|
|
1051
|
+
jobStartedAt: "开始于",
|
|
1052
|
+
jobFinishedAt: "结束于",
|
|
1053
|
+
jobElapsed: "已用时",
|
|
1054
|
+
jobLogEmpty: "(暂无输出)",
|
|
1055
|
+
proxyPort: "安装代理端口",
|
|
1056
|
+
proxyPortHint: "安装/更新时供 npm 与原生依赖(node-pty)prebuild 下载使用的本机代理端口;0 或留空 = 直连。未保存也用于本次安装,保存后长期生效。",
|
|
1057
|
+
proxyPortInvalid: "代理端口必须是 0..65535 的整数。"
|
|
1058
|
+
};
|
|
1059
|
+
const ownEn = {
|
|
1060
|
+
title: "Reverse-engineering preset",
|
|
1061
|
+
description: "REA prompt and installation: an empty prompt means a blank persona; tool guidance and runtime context stay in place.",
|
|
1062
|
+
summaryLine: "Edit the “逆向工程” prompt and install/update REA and its skill",
|
|
1063
|
+
prompt: "System prompt (persona prefix)",
|
|
1064
|
+
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.",
|
|
1065
|
+
promptPlaceholder: "e.g. Grade the evidence first, then patch the binary; every step needs a checkable chain of evidence…",
|
|
1066
|
+
promptEmpty: "Currently empty: this preset injects no persona section.",
|
|
1067
|
+
saveFailed: "Save failed: ",
|
|
1068
|
+
installSection: "REA installation",
|
|
1069
|
+
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.",
|
|
1070
|
+
installPackage: "REA package",
|
|
1071
|
+
installPath: "Managed install dir",
|
|
1072
|
+
installVersion: "Version",
|
|
1073
|
+
installTarget: "Target version",
|
|
1074
|
+
installed: "Installed",
|
|
1075
|
+
notInstalled: "Not installed",
|
|
1076
|
+
skillSource: "REA skill",
|
|
1077
|
+
skillBundled: "Bundled copy",
|
|
1078
|
+
skillUpdated: "Manually updated copy",
|
|
1079
|
+
skillFiles: "Files",
|
|
1080
|
+
updatedAt: "Updated at",
|
|
1081
|
+
mcpRow: "MCP row",
|
|
1082
|
+
mcpInstalled: "Mounted (managed install entry)",
|
|
1083
|
+
mcpCustom: "Mounted (explicit mcpCommand)",
|
|
1084
|
+
mcpMissing: "Not mounted: REA is not installed, the row stays disabled (nothing is fetched at boot)",
|
|
1085
|
+
mcpOff: "Disabled by config",
|
|
1086
|
+
commandLabel: "Command",
|
|
1087
|
+
refreshStatus: "Refresh status",
|
|
1088
|
+
installAction: "Install / update REA",
|
|
1089
|
+
skillAction: "Update skill",
|
|
1090
|
+
cancelAction: "Cancel job",
|
|
1091
|
+
statusLoading: "Reading install status…",
|
|
1092
|
+
statusFailed: "Status query failed: ",
|
|
1093
|
+
actionFailed: "Action failed: ",
|
|
1094
|
+
jobRunning: "Job running",
|
|
1095
|
+
jobOk: "Job finished",
|
|
1096
|
+
jobError: "Job failed",
|
|
1097
|
+
jobCancelled: "Job cancelled",
|
|
1098
|
+
jobInstall: "Install REA",
|
|
1099
|
+
jobSkill: "Update skill",
|
|
1100
|
+
jobPhase: "Phase",
|
|
1101
|
+
jobLog: "Log",
|
|
1102
|
+
jobStartedAt: "Started",
|
|
1103
|
+
jobFinishedAt: "Finished",
|
|
1104
|
+
jobElapsed: "Elapsed",
|
|
1105
|
+
jobLogEmpty: "(no output yet)",
|
|
1106
|
+
proxyPort: "Install proxy port",
|
|
1107
|
+
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.",
|
|
1108
|
+
proxyPortInvalid: "Proxy port must be an integer in 0..65535."
|
|
1109
|
+
};
|
|
1110
|
+
const zh = mergeDict(commonZh, ownZh);
|
|
1111
|
+
const en = mergeDict(commonEn, ownEn);
|
|
1112
|
+
//#endregion
|
|
1113
|
+
//#region src/client/styles.ts
|
|
1114
|
+
/**
|
|
1115
|
+
* 配置卡片样式:基础规则走 @dsh-plus/shared/client 的 cardCss('apr')
|
|
1116
|
+
* (按钮/字段/徽标等类名由共享控件生成,本文件**不**重复定义),这里只补
|
|
1117
|
+
* 插件特有规则:提示词多行框、安装状态表、作业进度条与日志。
|
|
1118
|
+
* 沿用官方 data-plugin / data-plugin-css 约定(HMR 据此卸载)。
|
|
1119
|
+
* 颜色与度量全部走官方主题变量(--dsw-alias-*),不写死色值。
|
|
1120
|
+
* @module agent-preset-rea/client/styles
|
|
1121
|
+
*/
|
|
1122
|
+
const PLUGIN_ID = "@dsh-plus/agent-preset-rea";
|
|
1123
|
+
const cardCssAll = cardCss("apr", `
|
|
1124
|
+
.apr-textarea{box-sizing:border-box;width:100%;min-height:180px;resize:vertical;font:inherit;font-size:13px;line-height:1.6;color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-3);border:1px solid var(--dsw-alias-border-l2);border-radius:8px;padding:10px 12px;transition:border-color .18s,background .18s}
|
|
1125
|
+
.apr-textarea:hover:not(:disabled):not(:focus-visible){border-color:var(--dsw-alias-label-dimmed)}
|
|
1126
|
+
.apr-textarea:focus-visible{border-color:var(--dsw-alias-brand-primary);outline:none}
|
|
1127
|
+
.apr-textarea:disabled{color:var(--dsw-alias-label-tertiary);cursor:default}
|
|
1128
|
+
.apr-textarea::placeholder{color:var(--dsw-alias-label-tertiary)}
|
|
1129
|
+
.apr-btnRow{display:flex;flex-wrap:wrap;gap:8px;margin-top:10px}
|
|
1130
|
+
.apr-install{margin:6px 0 0;padding:0;display:flex;flex-direction:column;gap:6px}
|
|
1131
|
+
.apr-installRow{display:flex;gap:10px;align-items:baseline;flex-wrap:wrap}
|
|
1132
|
+
.apr-installKey{flex:none;width:118px;margin:0;font-size:12px;color:var(--dsw-alias-label-tertiary)}
|
|
1133
|
+
.apr-installVal{display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin:0;font-size:13px;color:var(--dsw-alias-label-primary)}
|
|
1134
|
+
.apr-mono{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px;word-break:break-all}
|
|
1135
|
+
.apr-pill{flex:none;padding:2px 8px;border-radius:999px;font-size:11px;line-height:16px}
|
|
1136
|
+
.apr-pillOk{background:var(--dsw-alias-state-success-secondary);color:var(--dsw-alias-state-success-primary)}
|
|
1137
|
+
.apr-pillMiss,.apr-pillError,.apr-pillCancelled{background:var(--dsw-alias-state-error-secondary);color:var(--dsw-alias-state-error-primary)}
|
|
1138
|
+
.apr-pillRunning{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}
|
|
1139
|
+
.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)}
|
|
1140
|
+
.apr-jobHead{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
|
|
1141
|
+
.apr-jobTitle{font-size:13px;font-weight:500;color:var(--dsw-alias-label-primary)}
|
|
1142
|
+
.apr-bar{margin-top:8px;height:6px;border-radius:999px;background:var(--dsw-alias-interactive-bg-hover);overflow:hidden}
|
|
1143
|
+
.apr-barFill{height:100%;background:var(--dsw-alias-brand-primary);transition:width .3s}
|
|
1144
|
+
.apr-logBox{margin-top:8px}
|
|
1145
|
+
.apr-logSummary{font-size:12px;color:var(--dsw-alias-label-tertiary);cursor:pointer}
|
|
1146
|
+
.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)}
|
|
1147
|
+
.apr-error{margin:4px 0 0;font-size:12px;color:var(--dsw-alias-state-error-primary)}
|
|
1148
|
+
@media (max-width:767px){
|
|
1149
|
+
.apr-textarea{font-size:16px;min-height:140px}
|
|
1150
|
+
.apr-installKey{width:100%}
|
|
1151
|
+
}
|
|
1152
|
+
`);
|
|
1153
|
+
/** 幂等注入样式标签;返回标签(已存在或环境无 document 时为 null)。 */
|
|
1154
|
+
function injectStyle() {
|
|
1155
|
+
return injectCardStyle(PLUGIN_ID, cardCssAll);
|
|
1156
|
+
}
|
|
1157
|
+
//#endregion
|
|
1158
|
+
//#region src/client/client.ts
|
|
1159
|
+
const name = "dsh-plus-agent-preset-rea";
|
|
1160
|
+
/** 浏览器半需要的 cordis 服务 key(loader 据此注入;package.json 的 dsh.client.inject 管包加载顺序)。 */
|
|
1161
|
+
const inject = [
|
|
1162
|
+
"slots",
|
|
1163
|
+
"locale",
|
|
1164
|
+
"remote",
|
|
1165
|
+
"remote.settings"
|
|
1166
|
+
];
|
|
1167
|
+
function apply(ctx) {
|
|
1168
|
+
const c = ctx;
|
|
1169
|
+
const tag = injectStyle();
|
|
1170
|
+
c.effect(() => () => {
|
|
1171
|
+
tag?.remove();
|
|
1172
|
+
}, "agent-preset-rea: style");
|
|
1173
|
+
c.effect(() => c.locale.register(NS, {
|
|
1174
|
+
zh,
|
|
1175
|
+
en
|
|
1176
|
+
}), "agent-preset-rea: locale");
|
|
1177
|
+
const scope = createSettingsScope(c, SETTINGS_NS, "agent-preset-rea: settings scope");
|
|
1178
|
+
const api = createNamespaceApi(c.get("remote").settings, SETTINGS_NS);
|
|
1179
|
+
injectPluginConfigCard(c.slots, {
|
|
1180
|
+
ns: SETTINGS_NS,
|
|
1181
|
+
rowId: "dsh-plus-agent-preset-rea",
|
|
1182
|
+
pkg: PLUGIN_ID,
|
|
1183
|
+
component: ReaCard,
|
|
1184
|
+
inject: () => ({
|
|
1185
|
+
t: c.locale.bind(NS),
|
|
1186
|
+
scope,
|
|
1187
|
+
api
|
|
1188
|
+
})
|
|
1189
|
+
});
|
|
1190
|
+
}
|
|
1191
|
+
//#endregion
|
|
1192
|
+
exports.apply = apply;
|
|
1193
|
+
exports.inject = inject;
|
|
1194
|
+
exports.name = name;
|
|
1195
|
+
return module.exports;
|
|
1196
|
+
}
|
|
1197
|
+
});
|