dsh-pstack 0.2.0-beta.3 → 0.2.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/README.md +35 -16
- package/README.zh.md +36 -17
- package/assets/pstack-config-entry.png +0 -0
- package/cordis.patch.yml +12 -0
- package/dist/client.mjs +2448 -0
- package/dist/client.mjs.map +1 -0
- package/dist/index.mjs +1 -1
- package/dist/{paths-BT77khwm.mjs → paths-yji9SNak.mjs} +0 -1
- package/dist/pstack/docs/harness/dsh.md +7 -7
- package/dist/remote.d.mts +27 -0
- package/dist/remote.mjs +235 -0
- package/dist/role-catalog.d.mts +35 -0
- package/dist/role-catalog.mjs +122 -0
- package/dist/role-config-a4JpVdc-.mjs +379 -0
- package/dist/role-manifest-79cnFfDM.mjs +112 -0
- package/dist/role-manifest-8OERX9-s.d.mts +90 -0
- package/dist/role-wire-CAJsVA6e.d.mts +54 -0
- package/dist/roles.d.mts +18 -0
- package/dist/roles.mjs +293 -0
- package/dist/script.mjs +1 -1
- package/dist/skills/authored/decision-audit/SKILL.md +21 -17
- package/dist/skills/authored/decision-audit/SKILL.zh.md +21 -17
- package/dist/skills/authored/decision-audit/references/recover-commit.md +3 -3
- package/dist/skills/authored/decision-audit/references/recover-commit.zh.md +3 -3
- package/dist/skills/authored/decision-write/SKILL.md +11 -9
- package/dist/skills/authored/decision-write/SKILL.zh.md +11 -9
- package/dist/skills/authored/decision-write/references/land.md +3 -3
- package/dist/skills/authored/decision-write/references/land.zh.md +3 -3
- package/dist/skills/authored/decision-write/references/scope.md +16 -0
- package/dist/skills/authored/decision-write/references/scope.zh.md +16 -0
- package/dist/skills/authored/docs-drift/SKILL.md +120 -0
- package/dist/skills/authored/docs-drift/SKILL.zh.md +120 -0
- package/dist/skills/authored/setup-project-docs/SKILL.md +14 -9
- package/dist/skills/authored/setup-project-docs/SKILL.zh.md +14 -9
- package/dist/skills/authored/setup-project-docs/references/decision-conventions.md +3 -1
- package/dist/skills/authored/setup-project-docs/references/decision-conventions.zh.md +3 -1
- package/dist/subagent.d.mts +27 -20
- package/dist/subagent.mjs +71 -448
- package/package.json +34 -3
package/dist/client.mjs
ADDED
|
@@ -0,0 +1,2448 @@
|
|
|
1
|
+
window.__ModuleLoader__.load({
|
|
2
|
+
id: "dsh-pstack",
|
|
3
|
+
factory: (require) => {
|
|
4
|
+
var module = { exports: {} };
|
|
5
|
+
var exports = module.exports;
|
|
6
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
7
|
+
let react = require("react");
|
|
8
|
+
let react_jsx_runtime = require("react/jsx-runtime");
|
|
9
|
+
let react_dom = require("react-dom");
|
|
10
|
+
// src/internal/role-manifest.ts
|
|
11
|
+
/** The 17 roles, in manifest order. */
|
|
12
|
+
const ROLE_MANIFEST = [
|
|
13
|
+
{
|
|
14
|
+
key: "feature, refactoring",
|
|
15
|
+
shape: "single",
|
|
16
|
+
autoPanel: false
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
key: "bug-fix",
|
|
20
|
+
shape: "single",
|
|
21
|
+
autoPanel: false
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
key: "perf-issue",
|
|
25
|
+
shape: "single",
|
|
26
|
+
autoPanel: false
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
key: "hillclimb",
|
|
30
|
+
shape: "single",
|
|
31
|
+
autoPanel: false
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
key: "judgment and prose",
|
|
35
|
+
shape: "single",
|
|
36
|
+
autoPanel: false
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
key: "hardest tasks",
|
|
40
|
+
shape: "single",
|
|
41
|
+
autoPanel: false
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
key: "how explorer",
|
|
45
|
+
shape: "single",
|
|
46
|
+
autoPanel: false
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
key: "how explainer",
|
|
50
|
+
shape: "single",
|
|
51
|
+
autoPanel: false
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
key: "why investigators",
|
|
55
|
+
shape: "single",
|
|
56
|
+
autoPanel: false
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
key: "why synthesizer",
|
|
60
|
+
shape: "single",
|
|
61
|
+
autoPanel: false
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
key: "reflect tooling",
|
|
65
|
+
shape: "single",
|
|
66
|
+
autoPanel: false
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
key: "reflect judgment, divergent, synthesizer",
|
|
70
|
+
shape: "single",
|
|
71
|
+
autoPanel: false
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
key: "arena runners",
|
|
75
|
+
shape: "list",
|
|
76
|
+
autoPanel: true
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
key: "arena cross-judge pool",
|
|
80
|
+
shape: "list",
|
|
81
|
+
autoPanel: false
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
key: "swarm workers",
|
|
85
|
+
shape: "list",
|
|
86
|
+
autoPanel: false
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
key: "architect runners",
|
|
90
|
+
shape: "list",
|
|
91
|
+
autoPanel: true
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
key: "interrogate reviewers",
|
|
95
|
+
shape: "list",
|
|
96
|
+
autoPanel: true
|
|
97
|
+
}
|
|
98
|
+
];
|
|
99
|
+
function isRoleKey(value) {
|
|
100
|
+
return ROLE_MANIFEST.some((role) => role.key === value);
|
|
101
|
+
}
|
|
102
|
+
// src/remote.ts
|
|
103
|
+
/** npm package that owns these methods. */
|
|
104
|
+
const REMOTE_PACKAGE = "dsh-pstack";
|
|
105
|
+
/** Cordis service key and wire namespace of the Host role service. */
|
|
106
|
+
const ROLES_SERVICE = "pstackRoles";
|
|
107
|
+
function jsonCodec(typeSymbol, parse, decode) {
|
|
108
|
+
return {
|
|
109
|
+
mode: "strict",
|
|
110
|
+
typeSymbol,
|
|
111
|
+
create: () => ({ parse }),
|
|
112
|
+
...decode === void 0 ? {} : { decode }
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
function parameter(wire, typeSymbol, parse) {
|
|
116
|
+
return {
|
|
117
|
+
name: wire,
|
|
118
|
+
wire,
|
|
119
|
+
source: "json",
|
|
120
|
+
codec: jsonCodec(typeSymbol, parse)
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
function parseRecord(value, subject) {
|
|
124
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new TypeError(`${subject} must be an object`);
|
|
125
|
+
return value;
|
|
126
|
+
}
|
|
127
|
+
function parseRoute(value, subject) {
|
|
128
|
+
const record = parseRecord(value, subject);
|
|
129
|
+
if (record["kind"] === "inherit") {
|
|
130
|
+
const alias = record["alias"] ?? "inherit-parent";
|
|
131
|
+
if (alias !== "inherit-parent" && alias !== "auto") throw new TypeError(`${subject}.alias must be "inherit-parent" or "auto"`);
|
|
132
|
+
return {
|
|
133
|
+
kind: "inherit",
|
|
134
|
+
alias
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
if (record["kind"] !== "route") throw new TypeError(`${subject}.kind is unknown`);
|
|
138
|
+
const provider = record["provider"];
|
|
139
|
+
const model = record["model"];
|
|
140
|
+
const effort = record["effort"];
|
|
141
|
+
if (typeof provider !== "string" || provider.trim() === "") throw new TypeError(`${subject}.provider must be a non-empty string`);
|
|
142
|
+
if (typeof model !== "string" || model.trim() === "") throw new TypeError(`${subject}.model must be a non-empty string`);
|
|
143
|
+
if (effort !== void 0 && (typeof effort !== "string" || effort.trim() === "")) throw new TypeError(`${subject}.effort must be a non-empty string when present`);
|
|
144
|
+
return {
|
|
145
|
+
kind: "route",
|
|
146
|
+
provider,
|
|
147
|
+
model,
|
|
148
|
+
...typeof effort === "string" ? { effort } : {}
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
/** Parse one save request from the wire. */
|
|
152
|
+
function parseSaveRoleRequest(value) {
|
|
153
|
+
const record = parseRecord(value, "the save request");
|
|
154
|
+
if (typeof record["key"] !== "string") throw new TypeError("the save request must name a role");
|
|
155
|
+
const routes = record["routes"];
|
|
156
|
+
if (!Array.isArray(routes)) throw new TypeError("the save request must carry a route list");
|
|
157
|
+
return {
|
|
158
|
+
key: record["key"],
|
|
159
|
+
routes: routes.map((route, index) => parseRoute(route, `routes[${index}]`))
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
function parseRoleFileIssue(value) {
|
|
163
|
+
const record = parseRecord(value, "a role file issue");
|
|
164
|
+
if (typeof record["message"] !== "string") throw new TypeError("a role file issue needs a message");
|
|
165
|
+
const line = record["line"];
|
|
166
|
+
if (line !== void 0 && typeof line !== "number") throw new TypeError("a role file issue line must be a number");
|
|
167
|
+
return {
|
|
168
|
+
...typeof line === "number" ? { line } : {},
|
|
169
|
+
message: record["message"]
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
function parseRoleView(value, index) {
|
|
173
|
+
const record = parseRecord(value, `roles[${index}]`);
|
|
174
|
+
if (typeof record["key"] !== "string" || !isRoleKey(record["key"])) throw new TypeError(`roles[${index}].key is not in the role manifest`);
|
|
175
|
+
if (record["shape"] !== "single" && record["shape"] !== "list") throw new TypeError(`roles[${index}].shape is unknown`);
|
|
176
|
+
if (typeof record["configured"] !== "boolean") throw new TypeError(`roles[${index}].configured must be a boolean`);
|
|
177
|
+
const routes = record["routes"];
|
|
178
|
+
const issues = record["issues"];
|
|
179
|
+
if (!Array.isArray(routes) || !Array.isArray(issues)) throw new TypeError(`roles[${index}] must carry routes and issues arrays`);
|
|
180
|
+
const line = record["line"];
|
|
181
|
+
return {
|
|
182
|
+
key: record["key"],
|
|
183
|
+
shape: record["shape"],
|
|
184
|
+
configured: record["configured"],
|
|
185
|
+
...typeof line === "number" ? { line } : {},
|
|
186
|
+
routes: routes.map((route, routeIndex) => parseRoute(route, `roles[${index}].routes[${routeIndex}]`)),
|
|
187
|
+
issues: issues.map((issue) => String(issue))
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
/** Parse a role-file snapshot from the wire. */
|
|
191
|
+
function parseRoleFileSnapshot(value) {
|
|
192
|
+
const record = parseRecord(value, "the role file snapshot");
|
|
193
|
+
const status = record["status"];
|
|
194
|
+
if (status !== "missing" && status !== "loaded" && status !== "unreadable") throw new TypeError("the role file snapshot status is unknown");
|
|
195
|
+
if (typeof record["path"] !== "string") throw new TypeError("the role file snapshot path must be a string");
|
|
196
|
+
if (typeof record["canSave"] !== "boolean") throw new TypeError("the role file snapshot canSave must be a boolean");
|
|
197
|
+
const roles = record["roles"];
|
|
198
|
+
const issues = record["issues"];
|
|
199
|
+
if (!Array.isArray(roles) || !Array.isArray(issues)) throw new TypeError("the role file snapshot must carry roles and issues arrays");
|
|
200
|
+
const reason = record["reason"];
|
|
201
|
+
const budget = record["budget"];
|
|
202
|
+
return {
|
|
203
|
+
path: record["path"],
|
|
204
|
+
status,
|
|
205
|
+
...typeof reason === "string" ? { reason } : {},
|
|
206
|
+
...typeof budget === "string" ? { budget } : {},
|
|
207
|
+
roles: roles.map((role, index) => parseRoleView(role, index)),
|
|
208
|
+
issues: issues.map(parseRoleFileIssue),
|
|
209
|
+
canSave: record["canSave"]
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
/** Parse a save result from the wire. */
|
|
213
|
+
function parseSaveRoleResult(value) {
|
|
214
|
+
const record = parseRecord(value, "the save result");
|
|
215
|
+
if (record["ok"] === true) return {
|
|
216
|
+
ok: true,
|
|
217
|
+
snapshot: parseRoleFileSnapshot(record["snapshot"])
|
|
218
|
+
};
|
|
219
|
+
if (record["ok"] === false && typeof record["reason"] === "string") return {
|
|
220
|
+
ok: false,
|
|
221
|
+
reason: record["reason"]
|
|
222
|
+
};
|
|
223
|
+
throw new TypeError("the save result must be ok with a snapshot or refused with a reason");
|
|
224
|
+
}
|
|
225
|
+
/** Parse the live catalogue the page offers. */
|
|
226
|
+
function parseCatalogue(value) {
|
|
227
|
+
const record = parseRecord(value, "the catalogue");
|
|
228
|
+
const providers = record["providers"];
|
|
229
|
+
if (!Array.isArray(providers)) throw new TypeError("the catalogue must carry providers");
|
|
230
|
+
if (typeof record["truncated"] !== "boolean") throw new TypeError("the catalogue truncated flag must be a boolean");
|
|
231
|
+
return {
|
|
232
|
+
providers: providers.map((provider, index) => {
|
|
233
|
+
const entry = parseRecord(provider, `providers[${index}]`);
|
|
234
|
+
if (typeof entry["provider"] !== "string" || typeof entry["name"] !== "string") throw new TypeError(`providers[${index}] needs provider and name strings`);
|
|
235
|
+
const models = entry["models"];
|
|
236
|
+
if (!Array.isArray(models)) throw new TypeError(`providers[${index}].models must be an array`);
|
|
237
|
+
return {
|
|
238
|
+
provider: entry["provider"],
|
|
239
|
+
name: entry["name"],
|
|
240
|
+
...typeof entry["error"] === "string" ? { error: entry["error"] } : {},
|
|
241
|
+
models: models.map((model, modelIndex) => {
|
|
242
|
+
const item = parseRecord(model, `providers[${index}].models[${modelIndex}]`);
|
|
243
|
+
if (typeof item["route"] !== "string" || typeof item["id"] !== "string" || typeof item["name"] !== "string") throw new TypeError(`providers[${index}].models[${modelIndex}] needs route, id, and name strings`);
|
|
244
|
+
const efforts = item["efforts"];
|
|
245
|
+
if (!Array.isArray(efforts) || efforts.some((effort) => typeof effort !== "string")) throw new TypeError(`providers[${index}].models[${modelIndex}].efforts must be strings`);
|
|
246
|
+
return {
|
|
247
|
+
route: item["route"],
|
|
248
|
+
id: item["id"],
|
|
249
|
+
name: item["name"],
|
|
250
|
+
...typeof item["description"] === "string" ? { description: item["description"] } : {},
|
|
251
|
+
...typeof item["contextWindow"] === "number" ? { contextWindow: item["contextWindow"] } : {},
|
|
252
|
+
...typeof item["maxTokens"] === "number" ? { maxTokens: item["maxTokens"] } : {},
|
|
253
|
+
efforts,
|
|
254
|
+
...typeof item["defaultEffort"] === "string" ? { defaultEffort: item["defaultEffort"] } : {},
|
|
255
|
+
...typeof item["error"] === "string" ? { error: item["error"] } : {}
|
|
256
|
+
};
|
|
257
|
+
})
|
|
258
|
+
};
|
|
259
|
+
}),
|
|
260
|
+
truncated: record["truncated"]
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
function directMethod(options) {
|
|
264
|
+
const descriptor = {
|
|
265
|
+
id: `${REMOTE_PACKAGE}#${ROLES_SERVICE}/${options.method}`,
|
|
266
|
+
service: ROLES_SERVICE,
|
|
267
|
+
namespace: ROLES_SERVICE,
|
|
268
|
+
method: options.method,
|
|
269
|
+
invocation: { kind: "direct" },
|
|
270
|
+
parameters: options.parameters,
|
|
271
|
+
result: options.result,
|
|
272
|
+
...options.cancellation === true ? { cancellation: { parameter: "signal" } } : {}
|
|
273
|
+
};
|
|
274
|
+
const input = { create() {
|
|
275
|
+
return { parse(value) {
|
|
276
|
+
const record = parseRecord(value, "the method input");
|
|
277
|
+
const parsed = {};
|
|
278
|
+
for (const item of options.parameters) if (record[item.wire] !== void 0) {
|
|
279
|
+
const codec = item.codec;
|
|
280
|
+
parsed[item.wire] = codec.create().parse(record[item.wire]);
|
|
281
|
+
}
|
|
282
|
+
return parsed;
|
|
283
|
+
} };
|
|
284
|
+
} };
|
|
285
|
+
return {
|
|
286
|
+
...descriptor,
|
|
287
|
+
input: () => input,
|
|
288
|
+
invoke(remote, named, signal) {
|
|
289
|
+
const namespace = Reflect.get(remote, ROLES_SERVICE);
|
|
290
|
+
const values = options.parameters.map((item) => named[item.wire]);
|
|
291
|
+
if (options.cancellation === true) values.push(signal);
|
|
292
|
+
return namespace[options.method](...values);
|
|
293
|
+
}
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
const statusMethod = directMethod({
|
|
297
|
+
method: "status",
|
|
298
|
+
parameters: [],
|
|
299
|
+
result: jsonCodec("PstackRoleFileSnapshot", parseRoleFileSnapshot, parseRoleFileSnapshot),
|
|
300
|
+
invoke: (remote) => remote.pstackRoles.status()
|
|
301
|
+
});
|
|
302
|
+
const catalogueMethod = directMethod({
|
|
303
|
+
method: "catalogue",
|
|
304
|
+
parameters: [],
|
|
305
|
+
result: jsonCodec("PstackRoleCatalogue", parseCatalogue, parseCatalogue),
|
|
306
|
+
cancellation: true,
|
|
307
|
+
invoke: (remote, _input, signal) => remote.pstackRoles.catalogue(signal)
|
|
308
|
+
});
|
|
309
|
+
const saveMethod = directMethod({
|
|
310
|
+
method: "save",
|
|
311
|
+
parameters: [parameter("request", "PstackSaveRoleRequest", (value) => {
|
|
312
|
+
parseSaveRoleRequest(value);
|
|
313
|
+
return value;
|
|
314
|
+
})],
|
|
315
|
+
result: jsonCodec("PstackSaveRoleResult", parseSaveRoleResult, parseSaveRoleResult),
|
|
316
|
+
cancellation: true,
|
|
317
|
+
invoke: (remote, request, signal) => remote.pstackRoles.save(request, signal)
|
|
318
|
+
});
|
|
319
|
+
/** Mountable contribution the Client passes to `ctx.remote.$mount()`. */
|
|
320
|
+
const pstackRolesRemote = {
|
|
321
|
+
package: REMOTE_PACKAGE,
|
|
322
|
+
descriptors: [
|
|
323
|
+
statusMethod,
|
|
324
|
+
catalogueMethod,
|
|
325
|
+
saveMethod
|
|
326
|
+
],
|
|
327
|
+
methods: {
|
|
328
|
+
[`${ROLES_SERVICE}/status`]: statusMethod,
|
|
329
|
+
[`${ROLES_SERVICE}/catalogue`]: catalogueMethod,
|
|
330
|
+
[`${ROLES_SERVICE}/save`]: saveMethod
|
|
331
|
+
}
|
|
332
|
+
};
|
|
333
|
+
// src/internal/optional-plugins.ts
|
|
334
|
+
/** Group order shared by the page and its tests. */
|
|
335
|
+
const OPTIONAL_PLUGIN_GROUPS = [
|
|
336
|
+
"sessions",
|
|
337
|
+
"verification",
|
|
338
|
+
"multi-agent"
|
|
339
|
+
];
|
|
340
|
+
/** The complete optional-plugin catalogue in documentation order. */
|
|
341
|
+
const OPTIONAL_PLUGINS = [
|
|
342
|
+
{
|
|
343
|
+
id: "OPT-01",
|
|
344
|
+
group: "sessions",
|
|
345
|
+
packages: ["@deepseek-ai/dsh-experimental-worktree", "@deepseek-ai/dsh-experimental-tool-worktree"],
|
|
346
|
+
bundle: "@deepseek-ai/dsh-experimental-tool-worktree",
|
|
347
|
+
recommended: true
|
|
348
|
+
},
|
|
349
|
+
{
|
|
350
|
+
id: "OPT-02",
|
|
351
|
+
group: "sessions",
|
|
352
|
+
packages: ["@deepseek-ai/dsh-experimental-forge-bundle"],
|
|
353
|
+
bundle: "@deepseek-ai/dsh-experimental-forge-bundle",
|
|
354
|
+
suggested: true
|
|
355
|
+
},
|
|
356
|
+
{
|
|
357
|
+
id: "OPT-03",
|
|
358
|
+
group: "verification",
|
|
359
|
+
packages: [
|
|
360
|
+
"@deepseek-ai/dsh-browser-use",
|
|
361
|
+
"@deepseek-ai/dsh-experimental-browser-use-stagehand-native",
|
|
362
|
+
"@deepseek-ai/dsh-experimental-browser-use-playwright-mcp",
|
|
363
|
+
"@deepseek-ai/dsh-experimental-browser-use-chrome-devtools-mcp"
|
|
364
|
+
]
|
|
365
|
+
},
|
|
366
|
+
{
|
|
367
|
+
id: "OPT-04",
|
|
368
|
+
group: "verification",
|
|
369
|
+
packages: ["@deepseek-ai/dsh-cua-bundle"],
|
|
370
|
+
bundle: "@deepseek-ai/dsh-cua-bundle"
|
|
371
|
+
},
|
|
372
|
+
{
|
|
373
|
+
id: "OPT-05",
|
|
374
|
+
group: "verification",
|
|
375
|
+
packages: ["@deepseek-ai/dsh-experimental-terminal-bundle"],
|
|
376
|
+
bundle: "@deepseek-ai/dsh-experimental-terminal-bundle"
|
|
377
|
+
},
|
|
378
|
+
{
|
|
379
|
+
id: "OPT-06",
|
|
380
|
+
group: "sessions",
|
|
381
|
+
packages: ["@deepseek-ai/dsh-experimental-session-search"],
|
|
382
|
+
bundle: "@deepseek-ai/dsh-experimental-session-search",
|
|
383
|
+
recommended: true
|
|
384
|
+
},
|
|
385
|
+
{
|
|
386
|
+
id: "OPT-07",
|
|
387
|
+
group: "verification",
|
|
388
|
+
packages: ["@deepseek-ai/dsh-mcp-client"]
|
|
389
|
+
},
|
|
390
|
+
{
|
|
391
|
+
id: "OPT-08",
|
|
392
|
+
group: "multi-agent",
|
|
393
|
+
packages: ["@deepseek-ai/dsh-experimental-agent-team-profile"],
|
|
394
|
+
bundle: "@deepseek-ai/dsh-experimental-agent-team-profile",
|
|
395
|
+
conflict: true,
|
|
396
|
+
switchable: false
|
|
397
|
+
},
|
|
398
|
+
{
|
|
399
|
+
id: "OPT-09",
|
|
400
|
+
group: "multi-agent",
|
|
401
|
+
packages: ["@deepseek-ai/dsh-experimental-ralph-bundle"],
|
|
402
|
+
bundle: "@deepseek-ai/dsh-experimental-ralph-bundle"
|
|
403
|
+
}
|
|
404
|
+
];
|
|
405
|
+
/** The default recommendation ids, in catalogue order. */
|
|
406
|
+
function defaultRecommendationIds() {
|
|
407
|
+
return OPTIONAL_PLUGINS.filter((plugin) => plugin.recommended === true).map((plugin) => plugin.id);
|
|
408
|
+
}
|
|
409
|
+
// src/client/optional-plugins-state.ts
|
|
410
|
+
/**
|
|
411
|
+
* Pure state and small async operations for the optional-plugin recommendation
|
|
412
|
+
* section. Keeping status derivation and the one-click sequence here lets the
|
|
413
|
+
* browser tests cover every decision without mounting the Plugins page.
|
|
414
|
+
*
|
|
415
|
+
* The two wire shapes below mirror the public `BundleInfo` and `ChangeResult`
|
|
416
|
+
* fields this page reads from `pluginManager` (DSH 0.2.2-alpha.2), the same way
|
|
417
|
+
* `remote.ts` hand-writes the role Remote contract.
|
|
418
|
+
*/
|
|
419
|
+
/** Read one bundle's display status, including platform and file failures. */
|
|
420
|
+
function optionalPluginStatus(bundle) {
|
|
421
|
+
if (bundle === void 0) return "missing";
|
|
422
|
+
if (bundle.error?.code === "unsupported-platform") return "unsupported-platform";
|
|
423
|
+
if (bundle.availability === "missing") return "missing";
|
|
424
|
+
if (bundle.error !== void 0) return "error";
|
|
425
|
+
return bundle.enabled ? "enabled" : "off";
|
|
426
|
+
}
|
|
427
|
+
/** Join the catalogue with the Host's bundle list. */
|
|
428
|
+
function optionalPluginRows(bundles) {
|
|
429
|
+
const byName = new Map((bundles ?? []).map((bundle) => [bundle.name, bundle]));
|
|
430
|
+
return OPTIONAL_PLUGINS.map((definition) => {
|
|
431
|
+
const bundle = definition.bundle === void 0 ? void 0 : byName.get(definition.bundle);
|
|
432
|
+
const status = definition.bundle === void 0 ? "missing" : optionalPluginStatus(bundle);
|
|
433
|
+
return {
|
|
434
|
+
definition,
|
|
435
|
+
status,
|
|
436
|
+
enabled: status === "enabled",
|
|
437
|
+
canToggle: definition.bundle !== void 0 && definition.switchable !== false && (status === "off" || status === "enabled")
|
|
438
|
+
};
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
/** How many default-recommended plugins are not on yet, for activation guidance. */
|
|
442
|
+
function remainingRecommendedCount(rows) {
|
|
443
|
+
return rows.filter((row) => row.definition.recommended === true && row.status !== "enabled").length;
|
|
444
|
+
}
|
|
445
|
+
/**
|
|
446
|
+
* Enable the default recommendation one bundle at a time and keep going after
|
|
447
|
+
* failures so the summary reports every attempted step.
|
|
448
|
+
*/
|
|
449
|
+
async function enableRecommendedPlugins(api, rows) {
|
|
450
|
+
const outcomes = [];
|
|
451
|
+
let applied = 0;
|
|
452
|
+
let restartRequired = 0;
|
|
453
|
+
let failed = 0;
|
|
454
|
+
let skipped = 0;
|
|
455
|
+
for (const row of rows) {
|
|
456
|
+
if (row.definition.recommended !== true || row.definition.bundle === void 0) continue;
|
|
457
|
+
if (row.status === "enabled") continue;
|
|
458
|
+
if (row.status !== "off") {
|
|
459
|
+
skipped += 1;
|
|
460
|
+
continue;
|
|
461
|
+
}
|
|
462
|
+
try {
|
|
463
|
+
const result = await api.setBundleEnabled(row.definition.bundle, true);
|
|
464
|
+
outcomes.push({
|
|
465
|
+
id: row.definition.id,
|
|
466
|
+
bundle: row.definition.bundle,
|
|
467
|
+
application: result.application
|
|
468
|
+
});
|
|
469
|
+
if (result.application === "applied") applied += 1;
|
|
470
|
+
else if (result.application === "restart-required") restartRequired += 1;
|
|
471
|
+
else failed += 1;
|
|
472
|
+
} catch {
|
|
473
|
+
outcomes.push({
|
|
474
|
+
id: row.definition.id,
|
|
475
|
+
bundle: row.definition.bundle,
|
|
476
|
+
application: "error"
|
|
477
|
+
});
|
|
478
|
+
failed += 1;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
return {
|
|
482
|
+
applied,
|
|
483
|
+
restartRequired,
|
|
484
|
+
failed,
|
|
485
|
+
skipped,
|
|
486
|
+
outcomes
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
/** The one-time activation guidance marker. */
|
|
490
|
+
const GUIDANCE_DISMISSED_KEY = "dsh-pstack.recommended-plugins.guidance.v1";
|
|
491
|
+
/** Browser storage, or undefined outside a browser or when access throws. */
|
|
492
|
+
function browserStorage() {
|
|
493
|
+
if (typeof window === "undefined") return void 0;
|
|
494
|
+
try {
|
|
495
|
+
return window.localStorage;
|
|
496
|
+
} catch {
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
/** Whether the activation guidance was already dismissed in this browser. */
|
|
501
|
+
function guidanceDismissed(storage) {
|
|
502
|
+
try {
|
|
503
|
+
return storage?.getItem(GUIDANCE_DISMISSED_KEY) === "1";
|
|
504
|
+
} catch {
|
|
505
|
+
return false;
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
/** Remember that the activation guidance must not appear again. */
|
|
509
|
+
function dismissGuidance(storage) {
|
|
510
|
+
try {
|
|
511
|
+
storage?.setItem(GUIDANCE_DISMISSED_KEY, "1");
|
|
512
|
+
} catch {}
|
|
513
|
+
}
|
|
514
|
+
// src/client/OptionalPluginsPage.tsx
|
|
515
|
+
/**
|
|
516
|
+
* Recommended optional plugins on the dsh-pstack bundle page, plus the
|
|
517
|
+
* one-time activation guidance offered after the bundle is switched on.
|
|
518
|
+
*
|
|
519
|
+
* The page only reads `pluginManager.listBundles()` when it opens. Every
|
|
520
|
+
* profile change goes through a button the person pressed.
|
|
521
|
+
*/
|
|
522
|
+
const COLOR_TEXT$2 = "var(--dsw-alias-label-primary)";
|
|
523
|
+
const COLOR_SECONDARY$1 = "var(--dsw-alias-label-secondary)";
|
|
524
|
+
const COLOR_CAPTION$1 = "var(--dsw-alias-label-caption)";
|
|
525
|
+
const COLOR_BORDER$1 = "var(--dsw-alias-border-l3)";
|
|
526
|
+
const COLOR_SURFACE$1 = "var(--dsw-alias-bg-layer-1)";
|
|
527
|
+
const COLOR_ERROR$1 = "var(--dsw-alias-state-error-primary)";
|
|
528
|
+
const COLOR_SUCCESS$1 = "var(--dsw-alias-state-success-primary)";
|
|
529
|
+
const COLOR_WARN$1 = "var(--dsw-alias-state-warn-primary)";
|
|
530
|
+
const STATUS_KEY = {
|
|
531
|
+
enabled: "pluginStatusEnabled",
|
|
532
|
+
off: "pluginStatusOff",
|
|
533
|
+
missing: "pluginStatusMissing",
|
|
534
|
+
"unsupported-platform": "pluginStatusUnsupportedPlatform",
|
|
535
|
+
error: "pluginStatusError"
|
|
536
|
+
};
|
|
537
|
+
const GROUP_KEY = {
|
|
538
|
+
sessions: "groupSessions",
|
|
539
|
+
verification: "groupVerification",
|
|
540
|
+
"multi-agent": "groupMultiAgent"
|
|
541
|
+
};
|
|
542
|
+
const COPY = {
|
|
543
|
+
"OPT-01": {
|
|
544
|
+
title: "opt01Title",
|
|
545
|
+
detail: "opt01Detail"
|
|
546
|
+
},
|
|
547
|
+
"OPT-02": {
|
|
548
|
+
title: "opt02Title",
|
|
549
|
+
detail: "opt02Detail"
|
|
550
|
+
},
|
|
551
|
+
"OPT-03": {
|
|
552
|
+
title: "opt03Title",
|
|
553
|
+
detail: "opt03Detail"
|
|
554
|
+
},
|
|
555
|
+
"OPT-04": {
|
|
556
|
+
title: "opt04Title",
|
|
557
|
+
detail: "opt04Detail"
|
|
558
|
+
},
|
|
559
|
+
"OPT-05": {
|
|
560
|
+
title: "opt05Title",
|
|
561
|
+
detail: "opt05Detail"
|
|
562
|
+
},
|
|
563
|
+
"OPT-06": {
|
|
564
|
+
title: "opt06Title",
|
|
565
|
+
detail: "opt06Detail"
|
|
566
|
+
},
|
|
567
|
+
"OPT-07": {
|
|
568
|
+
title: "opt07Title",
|
|
569
|
+
detail: "opt07Detail"
|
|
570
|
+
},
|
|
571
|
+
"OPT-08": {
|
|
572
|
+
title: "opt08Title",
|
|
573
|
+
detail: "opt08Detail"
|
|
574
|
+
},
|
|
575
|
+
"OPT-09": {
|
|
576
|
+
title: "opt09Title",
|
|
577
|
+
detail: "opt09Detail"
|
|
578
|
+
}
|
|
579
|
+
};
|
|
580
|
+
function statusColor$1(status) {
|
|
581
|
+
if (status === "enabled") return COLOR_SUCCESS$1;
|
|
582
|
+
if (status === "error") return COLOR_ERROR$1;
|
|
583
|
+
if (status === "missing" || status === "unsupported-platform") return COLOR_WARN$1;
|
|
584
|
+
return COLOR_CAPTION$1;
|
|
585
|
+
}
|
|
586
|
+
function buttonStyle$1(primary = false) {
|
|
587
|
+
return {
|
|
588
|
+
border: `1px solid ${primary ? "transparent" : COLOR_BORDER$1}`,
|
|
589
|
+
borderRadius: 8,
|
|
590
|
+
padding: "5px 12px",
|
|
591
|
+
cursor: "pointer",
|
|
592
|
+
font: "inherit",
|
|
593
|
+
background: primary ? "var(--dsw-alias-brand-primary)" : "transparent",
|
|
594
|
+
color: primary ? "var(--dsw-alias-label-primary-foreground)" : COLOR_TEXT$2
|
|
595
|
+
};
|
|
596
|
+
}
|
|
597
|
+
function tagStyle(color) {
|
|
598
|
+
return {
|
|
599
|
+
border: `1px solid ${color}`,
|
|
600
|
+
borderRadius: 999,
|
|
601
|
+
padding: "1px 8px",
|
|
602
|
+
color,
|
|
603
|
+
fontSize: 12,
|
|
604
|
+
whiteSpace: "nowrap"
|
|
605
|
+
};
|
|
606
|
+
}
|
|
607
|
+
/** Whether one row is a package group the plugin manager cannot switch. */
|
|
608
|
+
function isNonBundleRow(row) {
|
|
609
|
+
return row.definition.bundle === void 0;
|
|
610
|
+
}
|
|
611
|
+
/** Text for one change result. */
|
|
612
|
+
function changeResultText(application, t, reason) {
|
|
613
|
+
if (application === "applied") return t("resultApplied");
|
|
614
|
+
if (application === "restart-required") return t("resultRestartRequired");
|
|
615
|
+
if (application === "failed" || application === "error") return t("resultFailed", { reason: reason ?? application });
|
|
616
|
+
if (application === "overridden") return t("resultOverridden");
|
|
617
|
+
return t("resultCancelled");
|
|
618
|
+
}
|
|
619
|
+
function errorText(error) {
|
|
620
|
+
if (typeof error === "object" && error !== null && "message" in error) return String(error.message);
|
|
621
|
+
return String(error);
|
|
622
|
+
}
|
|
623
|
+
/** Render the recommendation section; all decisions come from props. */
|
|
624
|
+
function OptionalPluginsView(props) {
|
|
625
|
+
const { rows, status, loadError, busyId, bulkBusy = false, messages, summary, onToggle, onEnableRecommended, onRetry, t } = props;
|
|
626
|
+
const recommendationIds = defaultRecommendationIds();
|
|
627
|
+
const offRecommended = rows.filter((row) => row.definition.recommended === true && row.status === "off").length;
|
|
628
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
629
|
+
"data-optional-plugins": true,
|
|
630
|
+
style: {
|
|
631
|
+
display: "grid",
|
|
632
|
+
gap: 12,
|
|
633
|
+
padding: "4px 0 18px",
|
|
634
|
+
color: COLOR_TEXT$2
|
|
635
|
+
},
|
|
636
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
|
|
637
|
+
style: {
|
|
638
|
+
display: "grid",
|
|
639
|
+
gap: 6
|
|
640
|
+
},
|
|
641
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
|
|
642
|
+
style: {
|
|
643
|
+
margin: 0,
|
|
644
|
+
fontSize: 16
|
|
645
|
+
},
|
|
646
|
+
children: t("optionalPluginsTitle")
|
|
647
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
648
|
+
style: {
|
|
649
|
+
margin: 0,
|
|
650
|
+
color: COLOR_SECONDARY$1,
|
|
651
|
+
fontSize: 13,
|
|
652
|
+
lineHeight: 1.5
|
|
653
|
+
},
|
|
654
|
+
children: t("optionalPluginsSubtitle")
|
|
655
|
+
})]
|
|
656
|
+
}), status === "loading" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
657
|
+
style: { color: COLOR_SECONDARY$1 },
|
|
658
|
+
children: t("optionalLoading")
|
|
659
|
+
}) : status === "error" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
660
|
+
style: {
|
|
661
|
+
display: "flex",
|
|
662
|
+
gap: 10,
|
|
663
|
+
alignItems: "center",
|
|
664
|
+
color: COLOR_ERROR$1
|
|
665
|
+
},
|
|
666
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("optionalLoadFailed", { message: loadError ?? "" }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
667
|
+
type: "button",
|
|
668
|
+
style: buttonStyle$1(),
|
|
669
|
+
onClick: onRetry,
|
|
670
|
+
children: t("optionalRetry")
|
|
671
|
+
})]
|
|
672
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
673
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
674
|
+
style: {
|
|
675
|
+
display: "flex",
|
|
676
|
+
gap: 8,
|
|
677
|
+
alignItems: "center",
|
|
678
|
+
flexWrap: "wrap"
|
|
679
|
+
},
|
|
680
|
+
children: [
|
|
681
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
682
|
+
type: "button",
|
|
683
|
+
style: buttonStyle$1(true),
|
|
684
|
+
onClick: onEnableRecommended,
|
|
685
|
+
disabled: bulkBusy || offRecommended === 0,
|
|
686
|
+
children: bulkBusy ? t("enablingRecommended") : t("enableRecommended")
|
|
687
|
+
}),
|
|
688
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
689
|
+
type: "button",
|
|
690
|
+
style: buttonStyle$1(),
|
|
691
|
+
onClick: onRetry,
|
|
692
|
+
disabled: bulkBusy,
|
|
693
|
+
children: t("optionalRefresh")
|
|
694
|
+
}),
|
|
695
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
696
|
+
style: {
|
|
697
|
+
color: COLOR_CAPTION$1,
|
|
698
|
+
fontSize: 12
|
|
699
|
+
},
|
|
700
|
+
children: t("recommendedHint", { ids: recommendationIds.join(t("listSeparator")) })
|
|
701
|
+
})
|
|
702
|
+
]
|
|
703
|
+
}),
|
|
704
|
+
summary === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
705
|
+
"data-optional-summary": true,
|
|
706
|
+
style: {
|
|
707
|
+
color: COLOR_SECONDARY$1,
|
|
708
|
+
fontSize: 12
|
|
709
|
+
},
|
|
710
|
+
children: summary
|
|
711
|
+
}),
|
|
712
|
+
OPTIONAL_PLUGIN_GROUPS.map((group) => {
|
|
713
|
+
const groupRows = rows.filter((row) => row.definition.group === group);
|
|
714
|
+
if (groupRows.length === 0) return null;
|
|
715
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
716
|
+
style: {
|
|
717
|
+
display: "grid",
|
|
718
|
+
gap: 8
|
|
719
|
+
},
|
|
720
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", {
|
|
721
|
+
style: {
|
|
722
|
+
margin: "6px 0 0",
|
|
723
|
+
fontSize: 13,
|
|
724
|
+
color: COLOR_SECONDARY$1
|
|
725
|
+
},
|
|
726
|
+
children: t(GROUP_KEY[group])
|
|
727
|
+
}), groupRows.map((row) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(OptionalPluginCard, {
|
|
728
|
+
row,
|
|
729
|
+
busy: busyId === row.definition.id || bulkBusy,
|
|
730
|
+
message: messages[row.definition.id],
|
|
731
|
+
onToggle,
|
|
732
|
+
t
|
|
733
|
+
}, row.definition.id))]
|
|
734
|
+
}, group);
|
|
735
|
+
})
|
|
736
|
+
] })]
|
|
737
|
+
});
|
|
738
|
+
}
|
|
739
|
+
function OptionalPluginCard(props) {
|
|
740
|
+
const { row, busy, message, onToggle, t } = props;
|
|
741
|
+
const { definition } = row;
|
|
742
|
+
const copy = COPY[definition.id];
|
|
743
|
+
const nonBundle = isNonBundleRow(row);
|
|
744
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("article", {
|
|
745
|
+
"data-optional-plugin": definition.id,
|
|
746
|
+
"data-optional-status": row.status,
|
|
747
|
+
style: {
|
|
748
|
+
display: "grid",
|
|
749
|
+
gap: 8,
|
|
750
|
+
border: `1px solid ${COLOR_BORDER$1}`,
|
|
751
|
+
borderRadius: 12,
|
|
752
|
+
padding: 12,
|
|
753
|
+
background: COLOR_SURFACE$1
|
|
754
|
+
},
|
|
755
|
+
children: [
|
|
756
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
|
|
757
|
+
style: {
|
|
758
|
+
display: "flex",
|
|
759
|
+
gap: 8,
|
|
760
|
+
alignItems: "baseline",
|
|
761
|
+
flexWrap: "wrap"
|
|
762
|
+
},
|
|
763
|
+
children: [
|
|
764
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", {
|
|
765
|
+
style: { fontSize: 14 },
|
|
766
|
+
children: t(copy.title)
|
|
767
|
+
}),
|
|
768
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
769
|
+
style: {
|
|
770
|
+
color: COLOR_CAPTION$1,
|
|
771
|
+
fontSize: 12
|
|
772
|
+
},
|
|
773
|
+
children: definition.id
|
|
774
|
+
}),
|
|
775
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
776
|
+
"data-optional-status-label": true,
|
|
777
|
+
style: tagStyle(statusColor$1(row.status)),
|
|
778
|
+
children: t(STATUS_KEY[row.status])
|
|
779
|
+
}),
|
|
780
|
+
definition.recommended === true ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
781
|
+
style: tagStyle(COLOR_SUCCESS$1),
|
|
782
|
+
children: t("recommendedTag")
|
|
783
|
+
}) : null,
|
|
784
|
+
definition.suggested === true ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
785
|
+
style: tagStyle(COLOR_SECONDARY$1),
|
|
786
|
+
children: t("suggestedTag")
|
|
787
|
+
}) : null,
|
|
788
|
+
definition.conflict === true ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
789
|
+
style: tagStyle(COLOR_ERROR$1),
|
|
790
|
+
children: t("conflictTag")
|
|
791
|
+
}) : null
|
|
792
|
+
]
|
|
793
|
+
}),
|
|
794
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
|
|
795
|
+
style: {
|
|
796
|
+
color: COLOR_SECONDARY$1,
|
|
797
|
+
fontSize: 12,
|
|
798
|
+
overflowWrap: "anywhere",
|
|
799
|
+
fontFamily: "var(--dsh-font-family-code)"
|
|
800
|
+
},
|
|
801
|
+
children: definition.packages.join(" · ")
|
|
802
|
+
}),
|
|
803
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
804
|
+
style: {
|
|
805
|
+
margin: 0,
|
|
806
|
+
color: COLOR_SECONDARY$1,
|
|
807
|
+
fontSize: 12,
|
|
808
|
+
lineHeight: 1.5
|
|
809
|
+
},
|
|
810
|
+
children: t(copy.detail)
|
|
811
|
+
}),
|
|
812
|
+
definition.conflict === true ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
813
|
+
role: "alert",
|
|
814
|
+
style: {
|
|
815
|
+
margin: 0,
|
|
816
|
+
color: COLOR_ERROR$1,
|
|
817
|
+
fontSize: 12,
|
|
818
|
+
lineHeight: 1.5
|
|
819
|
+
},
|
|
820
|
+
children: t("conflictWarning")
|
|
821
|
+
}) : null,
|
|
822
|
+
nonBundle ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
823
|
+
style: {
|
|
824
|
+
margin: 0,
|
|
825
|
+
color: COLOR_CAPTION$1,
|
|
826
|
+
fontSize: 12
|
|
827
|
+
},
|
|
828
|
+
children: t("nonBundleGuidance")
|
|
829
|
+
}) : null,
|
|
830
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("footer", {
|
|
831
|
+
style: {
|
|
832
|
+
display: "flex",
|
|
833
|
+
gap: 8,
|
|
834
|
+
alignItems: "center",
|
|
835
|
+
flexWrap: "wrap"
|
|
836
|
+
},
|
|
837
|
+
children: [
|
|
838
|
+
row.canToggle ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
839
|
+
type: "button",
|
|
840
|
+
style: buttonStyle$1(),
|
|
841
|
+
disabled: busy,
|
|
842
|
+
onClick: () => onToggle(definition.id, row.status !== "enabled"),
|
|
843
|
+
children: row.status === "enabled" ? t("disablePlugin") : t("enablePlugin")
|
|
844
|
+
}) : null,
|
|
845
|
+
busy ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
846
|
+
style: {
|
|
847
|
+
color: COLOR_CAPTION$1,
|
|
848
|
+
fontSize: 12
|
|
849
|
+
},
|
|
850
|
+
children: t("pluginWorking")
|
|
851
|
+
}) : null,
|
|
852
|
+
message === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
853
|
+
"data-optional-message": true,
|
|
854
|
+
style: {
|
|
855
|
+
color: message.tone === "ok" ? COLOR_SUCCESS$1 : COLOR_ERROR$1,
|
|
856
|
+
fontSize: 12
|
|
857
|
+
},
|
|
858
|
+
children: message.text
|
|
859
|
+
})
|
|
860
|
+
]
|
|
861
|
+
})
|
|
862
|
+
]
|
|
863
|
+
});
|
|
864
|
+
}
|
|
865
|
+
/** Loads bundle status and owns the recommendation section's two actions. */
|
|
866
|
+
function OptionalPluginsSection(props) {
|
|
867
|
+
const { optionalPlugins: api, t } = props;
|
|
868
|
+
const [bundles, setBundles] = (0, react.useState)(void 0);
|
|
869
|
+
const [loadError, setLoadError] = (0, react.useState)(void 0);
|
|
870
|
+
const [status, setStatus] = (0, react.useState)("loading");
|
|
871
|
+
const [busyId, setBusyId] = (0, react.useState)(void 0);
|
|
872
|
+
const [bulkBusy, setBulkBusy] = (0, react.useState)(false);
|
|
873
|
+
const [messages, setMessages] = (0, react.useState)({});
|
|
874
|
+
const [summary, setSummary] = (0, react.useState)(void 0);
|
|
875
|
+
const [reloadToken, setReloadToken] = (0, react.useState)(0);
|
|
876
|
+
(0, react.useEffect)(() => {
|
|
877
|
+
let cancelled = false;
|
|
878
|
+
setStatus("loading");
|
|
879
|
+
setLoadError(void 0);
|
|
880
|
+
api.listBundles().then((next) => {
|
|
881
|
+
if (cancelled) return;
|
|
882
|
+
setBundles(next);
|
|
883
|
+
setStatus("ready");
|
|
884
|
+
}, (error) => {
|
|
885
|
+
if (cancelled) return;
|
|
886
|
+
setLoadError(errorText(error));
|
|
887
|
+
setStatus("error");
|
|
888
|
+
});
|
|
889
|
+
return () => {
|
|
890
|
+
cancelled = true;
|
|
891
|
+
};
|
|
892
|
+
}, [api, reloadToken]);
|
|
893
|
+
const rows = optionalPluginRows(bundles);
|
|
894
|
+
const reload = () => setReloadToken((value) => value + 1);
|
|
895
|
+
const toggle = (id, enabled) => {
|
|
896
|
+
const bundle = rows.find((candidate) => candidate.definition.id === id)?.definition.bundle;
|
|
897
|
+
if (bundle === void 0) return;
|
|
898
|
+
setBusyId(id);
|
|
899
|
+
setMessages((current) => {
|
|
900
|
+
const { [id]: _ignored, ...rest } = current;
|
|
901
|
+
return rest;
|
|
902
|
+
});
|
|
903
|
+
api.setBundleEnabled(bundle, enabled).then((result) => {
|
|
904
|
+
setBusyId(void 0);
|
|
905
|
+
setMessages((current) => ({
|
|
906
|
+
...current,
|
|
907
|
+
[id]: {
|
|
908
|
+
tone: result.application === "failed" ? "error" : "ok",
|
|
909
|
+
text: changeResultText(result.application, t, result.error?.diagnostic ?? result.error?.code)
|
|
910
|
+
}
|
|
911
|
+
}));
|
|
912
|
+
reload();
|
|
913
|
+
}, (error) => {
|
|
914
|
+
setBusyId(void 0);
|
|
915
|
+
setMessages((current) => ({
|
|
916
|
+
...current,
|
|
917
|
+
[id]: {
|
|
918
|
+
tone: "error",
|
|
919
|
+
text: changeResultText("error", t, errorText(error))
|
|
920
|
+
}
|
|
921
|
+
}));
|
|
922
|
+
reload();
|
|
923
|
+
});
|
|
924
|
+
};
|
|
925
|
+
const enableAll = () => {
|
|
926
|
+
setBulkBusy(true);
|
|
927
|
+
setSummary(void 0);
|
|
928
|
+
setMessages({});
|
|
929
|
+
enableRecommendedPlugins(api, rows).then((result) => {
|
|
930
|
+
const next = {};
|
|
931
|
+
for (const outcome of result.outcomes) next[outcome.id] = {
|
|
932
|
+
tone: outcome.application === "failed" || outcome.application === "error" ? "error" : "ok",
|
|
933
|
+
text: changeResultText(outcome.application, t)
|
|
934
|
+
};
|
|
935
|
+
setMessages(next);
|
|
936
|
+
setSummary(t("recommendedSummary", {
|
|
937
|
+
applied: result.applied,
|
|
938
|
+
restart: result.restartRequired,
|
|
939
|
+
failed: result.failed,
|
|
940
|
+
skipped: result.skipped
|
|
941
|
+
}));
|
|
942
|
+
setBulkBusy(false);
|
|
943
|
+
reload();
|
|
944
|
+
});
|
|
945
|
+
};
|
|
946
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(OptionalPluginsView, {
|
|
947
|
+
rows,
|
|
948
|
+
status,
|
|
949
|
+
loadError,
|
|
950
|
+
busyId,
|
|
951
|
+
bulkBusy,
|
|
952
|
+
messages,
|
|
953
|
+
summary,
|
|
954
|
+
onToggle: toggle,
|
|
955
|
+
onEnableRecommended: enableAll,
|
|
956
|
+
onRetry: reload,
|
|
957
|
+
t
|
|
958
|
+
});
|
|
959
|
+
}
|
|
960
|
+
/** Pure view of the one-time activation guidance. */
|
|
961
|
+
function OptionalPluginsActivationView(props) {
|
|
962
|
+
const { count, onOpen, onDismiss, t } = props;
|
|
963
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
964
|
+
"data-optional-activation": true,
|
|
965
|
+
style: {
|
|
966
|
+
display: "flex",
|
|
967
|
+
gap: 10,
|
|
968
|
+
alignItems: "center",
|
|
969
|
+
flexWrap: "wrap",
|
|
970
|
+
border: `1px solid ${COLOR_BORDER$1}`,
|
|
971
|
+
borderRadius: 12,
|
|
972
|
+
padding: "10px 12px",
|
|
973
|
+
background: COLOR_SURFACE$1,
|
|
974
|
+
color: COLOR_TEXT$2
|
|
975
|
+
},
|
|
976
|
+
children: [
|
|
977
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
978
|
+
style: { fontSize: 13 },
|
|
979
|
+
children: t("guidanceTitle", { count })
|
|
980
|
+
}),
|
|
981
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
982
|
+
type: "button",
|
|
983
|
+
style: buttonStyle$1(true),
|
|
984
|
+
onClick: onOpen,
|
|
985
|
+
children: t("guidanceOpen")
|
|
986
|
+
}),
|
|
987
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
988
|
+
type: "button",
|
|
989
|
+
style: buttonStyle$1(),
|
|
990
|
+
onClick: onDismiss,
|
|
991
|
+
children: t("guidanceDismiss")
|
|
992
|
+
})
|
|
993
|
+
]
|
|
994
|
+
});
|
|
995
|
+
}
|
|
996
|
+
/** One-time guidance shown after dsh-pstack itself is switched on. */
|
|
997
|
+
function OptionalPluginsActivation(props) {
|
|
998
|
+
const { optionalPlugins: api, t, onDismiss, onOpenDetails } = props;
|
|
999
|
+
const [count, setCount] = (0, react.useState)(void 0);
|
|
1000
|
+
(0, react.useEffect)(() => {
|
|
1001
|
+
if (guidanceDismissed(browserStorage())) {
|
|
1002
|
+
setCount(0);
|
|
1003
|
+
return;
|
|
1004
|
+
}
|
|
1005
|
+
let cancelled = false;
|
|
1006
|
+
api.listBundles().then((bundles) => {
|
|
1007
|
+
if (cancelled) return;
|
|
1008
|
+
setCount(remainingRecommendedCount(optionalPluginRows(bundles)));
|
|
1009
|
+
}, () => {
|
|
1010
|
+
if (cancelled) return;
|
|
1011
|
+
setCount(0);
|
|
1012
|
+
});
|
|
1013
|
+
return () => {
|
|
1014
|
+
cancelled = true;
|
|
1015
|
+
};
|
|
1016
|
+
}, [api]);
|
|
1017
|
+
(0, react.useEffect)(() => {
|
|
1018
|
+
if (count !== void 0 && count > 0) dismissGuidance(browserStorage());
|
|
1019
|
+
}, [count]);
|
|
1020
|
+
if (count === void 0 || count === 0) return null;
|
|
1021
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(OptionalPluginsActivationView, {
|
|
1022
|
+
count,
|
|
1023
|
+
onOpen: () => {
|
|
1024
|
+
dismissGuidance(browserStorage());
|
|
1025
|
+
onOpenDetails();
|
|
1026
|
+
},
|
|
1027
|
+
onDismiss: () => {
|
|
1028
|
+
dismissGuidance(browserStorage());
|
|
1029
|
+
onDismiss();
|
|
1030
|
+
},
|
|
1031
|
+
t
|
|
1032
|
+
});
|
|
1033
|
+
}
|
|
1034
|
+
// src/client/state.ts
|
|
1035
|
+
/** The implicit value of a role absent from the file. */
|
|
1036
|
+
function inheritRoute(alias = "inherit-parent") {
|
|
1037
|
+
return {
|
|
1038
|
+
kind: "inherit",
|
|
1039
|
+
alias
|
|
1040
|
+
};
|
|
1041
|
+
}
|
|
1042
|
+
/** Copy every role into an editable draft, falling back to `inherit-parent`. */
|
|
1043
|
+
function draftsFromSnapshot(snapshot) {
|
|
1044
|
+
const drafts = {};
|
|
1045
|
+
for (const role of snapshot.roles) drafts[role.key] = role.routes.length === 0 ? [inheritRoute()] : role.routes.map((route) => ({ ...route }));
|
|
1046
|
+
return drafts;
|
|
1047
|
+
}
|
|
1048
|
+
/** The exact save payload one card sends. */
|
|
1049
|
+
function saveRequestFor(key, routes) {
|
|
1050
|
+
return {
|
|
1051
|
+
key,
|
|
1052
|
+
routes: routes.map((route) => ({ ...route }))
|
|
1053
|
+
};
|
|
1054
|
+
}
|
|
1055
|
+
/** Display text of one route, matching the role-file grammar. */
|
|
1056
|
+
function formatRoute(route) {
|
|
1057
|
+
if (route.kind === "inherit") return route.alias;
|
|
1058
|
+
return route.effort === void 0 ? `${route.provider}/${route.model}` : `${route.provider}/${route.model} [${route.effort}]`;
|
|
1059
|
+
}
|
|
1060
|
+
/** Replace one route in place. */
|
|
1061
|
+
function replaceRoute(routes, index, route) {
|
|
1062
|
+
return routes.map((current, position) => position === index ? route : current);
|
|
1063
|
+
}
|
|
1064
|
+
/** Append one route, defaulting to `inherit-parent` for a new list entry. */
|
|
1065
|
+
function appendRoute(routes) {
|
|
1066
|
+
return [...routes, inheritRoute()];
|
|
1067
|
+
}
|
|
1068
|
+
/** Remove one route without ever leaving a list role empty. */
|
|
1069
|
+
function removeRoute(routes, index) {
|
|
1070
|
+
if (routes.length <= 1) return [...routes];
|
|
1071
|
+
return routes.filter((_route, position) => position !== index);
|
|
1072
|
+
}
|
|
1073
|
+
/** Provider entries in catalogue order. */
|
|
1074
|
+
function providerChoices(catalogue) {
|
|
1075
|
+
return catalogue?.providers ?? [];
|
|
1076
|
+
}
|
|
1077
|
+
/** The models one provider advertises, or an empty list when the provider is gone. */
|
|
1078
|
+
function modelChoices(catalogue, provider) {
|
|
1079
|
+
return catalogue?.providers.find((entry) => entry.provider === provider)?.models ?? [];
|
|
1080
|
+
}
|
|
1081
|
+
/** One model's declared efforts. */
|
|
1082
|
+
function effortChoices(catalogue, provider, model) {
|
|
1083
|
+
return modelChoices(catalogue, provider).find((entry) => entry.id === model)?.efforts ?? [];
|
|
1084
|
+
}
|
|
1085
|
+
/** Client-side shape check; the Host validates providers and efforts on save. */
|
|
1086
|
+
function draftIssues(role, routes) {
|
|
1087
|
+
if (routes.length === 0) return ["keep at least one route"];
|
|
1088
|
+
if (role.shape === "single" && routes.length !== 1) return ["a single-route role needs exactly one route"];
|
|
1089
|
+
return [];
|
|
1090
|
+
}
|
|
1091
|
+
// src/client/locales.ts
|
|
1092
|
+
const NS$1 = "pstackRoles";
|
|
1093
|
+
/** Source dictionary: its key set is the contract every locale must satisfy. */
|
|
1094
|
+
const base$1 = {
|
|
1095
|
+
title: "Role model configuration",
|
|
1096
|
+
subtitle: "Edit $DSH_HOME/pstack-models.mdc. Saving a role changes only that role.",
|
|
1097
|
+
file: "File",
|
|
1098
|
+
status: "Status",
|
|
1099
|
+
statusMissing: "missing",
|
|
1100
|
+
statusLoaded: "loaded",
|
|
1101
|
+
statusUnreadable: "unreadable",
|
|
1102
|
+
budget: "Budget",
|
|
1103
|
+
fileIssues: "File issues",
|
|
1104
|
+
line: "Line {line}",
|
|
1105
|
+
role: "Role",
|
|
1106
|
+
configuredLine: "line {line}",
|
|
1107
|
+
notConfigured: "not configured",
|
|
1108
|
+
inheritParent: "inherit-parent",
|
|
1109
|
+
auto: "auto",
|
|
1110
|
+
provider: "Provider",
|
|
1111
|
+
unavailable: "unavailable",
|
|
1112
|
+
model: "Model",
|
|
1113
|
+
effort: "Effort",
|
|
1114
|
+
effortUnset: "not set",
|
|
1115
|
+
effortDefault: "default ({effort})",
|
|
1116
|
+
effortBaselineTitle: "Effort adapts to the task",
|
|
1117
|
+
effortBaselineIntro: "This is the starting level. It can move up or down as the task changes in difficulty; it is not fixed.",
|
|
1118
|
+
addRoute: "Add route",
|
|
1119
|
+
removeRoute: "Remove route",
|
|
1120
|
+
save: "Save",
|
|
1121
|
+
saving: "Saving…",
|
|
1122
|
+
saved: "Saved",
|
|
1123
|
+
reset: "Reset",
|
|
1124
|
+
resetting: "Resetting…",
|
|
1125
|
+
loadFailed: "Could not read the role configuration: {message}",
|
|
1126
|
+
retry: "Retry",
|
|
1127
|
+
loading: "Loading role configuration…",
|
|
1128
|
+
unreadableBody: "This file cannot be edited until it becomes readable: {reason}",
|
|
1129
|
+
noCatalogue: "The live model directory is unavailable; provider and model lists are empty.",
|
|
1130
|
+
saveRejected: "Save rejected: {reason}",
|
|
1131
|
+
roleIssues: "Role file issue: {message}",
|
|
1132
|
+
catalogueUnavailable: "Model directory unavailable",
|
|
1133
|
+
truncated: "The model directory was truncated; some providers or models are not listed.",
|
|
1134
|
+
roleFeatureRefactoringName: "Feature & refactoring",
|
|
1135
|
+
roleFeatureRefactoringDescription: "For new features and code refactoring.",
|
|
1136
|
+
roleBugFixName: "Bug fix",
|
|
1137
|
+
roleBugFixDescription: "For diagnosing and fixing defects.",
|
|
1138
|
+
rolePerfIssueName: "Performance",
|
|
1139
|
+
rolePerfIssueDescription: "For investigating and fixing performance issues.",
|
|
1140
|
+
roleHillclimbName: "Hillclimb",
|
|
1141
|
+
roleHillclimbDescription: "For iterative improvements guided by measurable results.",
|
|
1142
|
+
roleJudgmentAndProseName: "Judgment & prose",
|
|
1143
|
+
roleJudgmentAndProseDescription: "For judgment calls, writing, and refining prose.",
|
|
1144
|
+
roleHardestTasksName: "Hardest tasks",
|
|
1145
|
+
roleHardestTasksDescription: "For complex design, concurrency, algorithms, and other difficult tasks.",
|
|
1146
|
+
roleHowExplorerName: "How · explorer",
|
|
1147
|
+
roleHowExplorerDescription: "For exploring code and tracing how a system works.",
|
|
1148
|
+
roleHowExplainerName: "How · explainer",
|
|
1149
|
+
roleHowExplainerDescription: "For explaining architecture and runtime behavior.",
|
|
1150
|
+
roleWhyInvestigatorsName: "Why · investigators",
|
|
1151
|
+
roleWhyInvestigatorsDescription: "For gathering evidence about decisions and their rationale.",
|
|
1152
|
+
roleWhySynthesizerName: "Why · synthesizer",
|
|
1153
|
+
roleWhySynthesizerDescription: "For synthesizing evidence into a cited explanation.",
|
|
1154
|
+
roleReflectToolingName: "Reflect · tooling",
|
|
1155
|
+
roleReflectToolingDescription: "For reviewing tooling lessons from the current session.",
|
|
1156
|
+
roleReflectJudgmentDivergentSynthesizerName: "Reflect · judgment & synthesis",
|
|
1157
|
+
roleReflectJudgmentDivergentSynthesizerDescription: "For reviewing judgments, challenging conclusions, and synthesizing lessons.",
|
|
1158
|
+
roleArenaRunnersName: "Arena · runners",
|
|
1159
|
+
roleArenaRunnersDescription: "For generating competing candidates in parallel.",
|
|
1160
|
+
roleArenaCrossJudgePoolName: "Arena · judges",
|
|
1161
|
+
roleArenaCrossJudgePoolDescription: "For independently comparing Arena candidates.",
|
|
1162
|
+
roleSwarmWorkersName: "Swarm · workers",
|
|
1163
|
+
roleSwarmWorkersDescription: "For parallel implementation and verification tasks.",
|
|
1164
|
+
roleArchitectRunnersName: "Architect · runners",
|
|
1165
|
+
roleArchitectRunnersDescription: "For proposing competing designs before implementation.",
|
|
1166
|
+
roleInterrogateReviewersName: "Interrogate · reviewers",
|
|
1167
|
+
roleInterrogateReviewersDescription: "For adversarial review of changes and designs."
|
|
1168
|
+
};
|
|
1169
|
+
/** Role keys map to the exact locale keys that name and describe them. */
|
|
1170
|
+
const roleCopyKeys = {
|
|
1171
|
+
"feature, refactoring": {
|
|
1172
|
+
name: "roleFeatureRefactoringName",
|
|
1173
|
+
description: "roleFeatureRefactoringDescription"
|
|
1174
|
+
},
|
|
1175
|
+
"bug-fix": {
|
|
1176
|
+
name: "roleBugFixName",
|
|
1177
|
+
description: "roleBugFixDescription"
|
|
1178
|
+
},
|
|
1179
|
+
"perf-issue": {
|
|
1180
|
+
name: "rolePerfIssueName",
|
|
1181
|
+
description: "rolePerfIssueDescription"
|
|
1182
|
+
},
|
|
1183
|
+
hillclimb: {
|
|
1184
|
+
name: "roleHillclimbName",
|
|
1185
|
+
description: "roleHillclimbDescription"
|
|
1186
|
+
},
|
|
1187
|
+
"judgment and prose": {
|
|
1188
|
+
name: "roleJudgmentAndProseName",
|
|
1189
|
+
description: "roleJudgmentAndProseDescription"
|
|
1190
|
+
},
|
|
1191
|
+
"hardest tasks": {
|
|
1192
|
+
name: "roleHardestTasksName",
|
|
1193
|
+
description: "roleHardestTasksDescription"
|
|
1194
|
+
},
|
|
1195
|
+
"how explorer": {
|
|
1196
|
+
name: "roleHowExplorerName",
|
|
1197
|
+
description: "roleHowExplorerDescription"
|
|
1198
|
+
},
|
|
1199
|
+
"how explainer": {
|
|
1200
|
+
name: "roleHowExplainerName",
|
|
1201
|
+
description: "roleHowExplainerDescription"
|
|
1202
|
+
},
|
|
1203
|
+
"why investigators": {
|
|
1204
|
+
name: "roleWhyInvestigatorsName",
|
|
1205
|
+
description: "roleWhyInvestigatorsDescription"
|
|
1206
|
+
},
|
|
1207
|
+
"why synthesizer": {
|
|
1208
|
+
name: "roleWhySynthesizerName",
|
|
1209
|
+
description: "roleWhySynthesizerDescription"
|
|
1210
|
+
},
|
|
1211
|
+
"reflect tooling": {
|
|
1212
|
+
name: "roleReflectToolingName",
|
|
1213
|
+
description: "roleReflectToolingDescription"
|
|
1214
|
+
},
|
|
1215
|
+
"reflect judgment, divergent, synthesizer": {
|
|
1216
|
+
name: "roleReflectJudgmentDivergentSynthesizerName",
|
|
1217
|
+
description: "roleReflectJudgmentDivergentSynthesizerDescription"
|
|
1218
|
+
},
|
|
1219
|
+
"arena runners": {
|
|
1220
|
+
name: "roleArenaRunnersName",
|
|
1221
|
+
description: "roleArenaRunnersDescription"
|
|
1222
|
+
},
|
|
1223
|
+
"arena cross-judge pool": {
|
|
1224
|
+
name: "roleArenaCrossJudgePoolName",
|
|
1225
|
+
description: "roleArenaCrossJudgePoolDescription"
|
|
1226
|
+
},
|
|
1227
|
+
"swarm workers": {
|
|
1228
|
+
name: "roleSwarmWorkersName",
|
|
1229
|
+
description: "roleSwarmWorkersDescription"
|
|
1230
|
+
},
|
|
1231
|
+
"architect runners": {
|
|
1232
|
+
name: "roleArchitectRunnersName",
|
|
1233
|
+
description: "roleArchitectRunnersDescription"
|
|
1234
|
+
},
|
|
1235
|
+
"interrogate reviewers": {
|
|
1236
|
+
name: "roleInterrogateReviewersName",
|
|
1237
|
+
description: "roleInterrogateReviewersDescription"
|
|
1238
|
+
}
|
|
1239
|
+
};
|
|
1240
|
+
/** Complete dictionaries, one entry per supported locale. */
|
|
1241
|
+
const catalogs$1 = {
|
|
1242
|
+
en: base$1,
|
|
1243
|
+
zh: {
|
|
1244
|
+
title: "角色模型配置",
|
|
1245
|
+
subtitle: "编辑 $DSH_HOME/pstack-models.mdc。每次保存只改动你保存的那个角色。",
|
|
1246
|
+
file: "文件",
|
|
1247
|
+
status: "状态",
|
|
1248
|
+
statusMissing: "缺失",
|
|
1249
|
+
statusLoaded: "已加载",
|
|
1250
|
+
statusUnreadable: "不可读",
|
|
1251
|
+
budget: "预算",
|
|
1252
|
+
fileIssues: "文件问题",
|
|
1253
|
+
line: "第 {line} 行",
|
|
1254
|
+
role: "角色",
|
|
1255
|
+
configuredLine: "第 {line} 行",
|
|
1256
|
+
notConfigured: "未配置",
|
|
1257
|
+
inheritParent: "继承父级",
|
|
1258
|
+
auto: "自动",
|
|
1259
|
+
provider: "提供方",
|
|
1260
|
+
unavailable: "不可用",
|
|
1261
|
+
model: "模型",
|
|
1262
|
+
effort: "推理强度",
|
|
1263
|
+
effortUnset: "未指定",
|
|
1264
|
+
effortDefault: "默认({effort})",
|
|
1265
|
+
effortBaselineTitle: "推理强度随任务难度调整",
|
|
1266
|
+
effortBaselineIntro: "这里选择的是起步值,实际强度会根据任务难度上下浮动,不是固定值。",
|
|
1267
|
+
addRoute: "添加路由",
|
|
1268
|
+
removeRoute: "删除路由",
|
|
1269
|
+
save: "保存",
|
|
1270
|
+
saving: "保存中…",
|
|
1271
|
+
saved: "已保存",
|
|
1272
|
+
reset: "重置",
|
|
1273
|
+
resetting: "重置中…",
|
|
1274
|
+
loadFailed: "无法读取角色配置:{message}",
|
|
1275
|
+
retry: "重试",
|
|
1276
|
+
loading: "正在读取角色配置…",
|
|
1277
|
+
unreadableBody: "文件可读之前无法编辑:{reason}",
|
|
1278
|
+
noCatalogue: "实时模型目录不可用,提供方与模型列表为空。",
|
|
1279
|
+
saveRejected: "保存被拒绝:{reason}",
|
|
1280
|
+
roleIssues: "角色文件问题:{message}",
|
|
1281
|
+
catalogueUnavailable: "模型目录不可用",
|
|
1282
|
+
truncated: "模型目录已截断;部分提供方或模型未显示。",
|
|
1283
|
+
roleFeatureRefactoringName: "功能开发与重构",
|
|
1284
|
+
roleFeatureRefactoringDescription: "用于新功能开发和代码重构。",
|
|
1285
|
+
roleBugFixName: "缺陷修复",
|
|
1286
|
+
roleBugFixDescription: "用于定位并修复缺陷。",
|
|
1287
|
+
rolePerfIssueName: "性能优化",
|
|
1288
|
+
rolePerfIssueDescription: "用于排查并解决性能问题。",
|
|
1289
|
+
roleHillclimbName: "迭代优化",
|
|
1290
|
+
roleHillclimbDescription: "用于根据可量化的结果持续改进。",
|
|
1291
|
+
roleJudgmentAndProseName: "判断与文字",
|
|
1292
|
+
roleJudgmentAndProseDescription: "用于需要判断、写作或润色的任务。",
|
|
1293
|
+
roleHardestTasksName: "高难度任务",
|
|
1294
|
+
roleHardestTasksDescription: "用于复杂设计、并发、算法等高难度任务。",
|
|
1295
|
+
roleHowExplorerName: "How · 代码探索",
|
|
1296
|
+
roleHowExplorerDescription: "用于探索代码,梳理系统的工作方式。",
|
|
1297
|
+
roleHowExplainerName: "How · 原理讲解",
|
|
1298
|
+
roleHowExplainerDescription: "用于解释架构和运行流程。",
|
|
1299
|
+
roleWhyInvestigatorsName: "Why · 证据调查",
|
|
1300
|
+
roleWhyInvestigatorsDescription: "用于调查决策依据并收集证据。",
|
|
1301
|
+
roleWhySynthesizerName: "Why · 证据综合",
|
|
1302
|
+
roleWhySynthesizerDescription: "用于综合证据,形成有出处的解释。",
|
|
1303
|
+
roleReflectToolingName: "Reflect · 工具复盘",
|
|
1304
|
+
roleReflectToolingDescription: "用于复盘当前会话中的工具使用经验。",
|
|
1305
|
+
roleReflectJudgmentDivergentSynthesizerName: "Reflect · 判断与综合",
|
|
1306
|
+
roleReflectJudgmentDivergentSynthesizerDescription: "用于复盘判断、挑战结论并综合经验。",
|
|
1307
|
+
roleArenaRunnersName: "Arena · 候选方案",
|
|
1308
|
+
roleArenaRunnersDescription: "用于并行生成相互竞争的候选方案。",
|
|
1309
|
+
roleArenaCrossJudgePoolName: "Arena · 交叉评审",
|
|
1310
|
+
roleArenaCrossJudgePoolDescription: "用于独立比较 Arena 的候选方案。",
|
|
1311
|
+
roleSwarmWorkersName: "Swarm · 并行执行",
|
|
1312
|
+
roleSwarmWorkersDescription: "用于并行执行实现和验证任务。",
|
|
1313
|
+
roleArchitectRunnersName: "Architect · 方案设计",
|
|
1314
|
+
roleArchitectRunnersDescription: "用于在实现前并行提出候选设计。",
|
|
1315
|
+
roleInterrogateReviewersName: "Interrogate · 对抗评审",
|
|
1316
|
+
roleInterrogateReviewersDescription: "用于对改动或设计进行对抗性评审。"
|
|
1317
|
+
}
|
|
1318
|
+
};
|
|
1319
|
+
/** Localized role name and usage, keyed by the exact role-file key. */
|
|
1320
|
+
function rolePresentation(key, t) {
|
|
1321
|
+
const copy = roleCopyKeys[key];
|
|
1322
|
+
return {
|
|
1323
|
+
name: t(copy.name),
|
|
1324
|
+
description: t(copy.description)
|
|
1325
|
+
};
|
|
1326
|
+
}
|
|
1327
|
+
// src/client/RoleSettingsPage.tsx
|
|
1328
|
+
/** Role configuration section inside the bundle configuration dialog for `$DSH_HOME/pstack-models.mdc`. */
|
|
1329
|
+
const COLOR_TEXT$1 = "var(--dsw-alias-label-primary)";
|
|
1330
|
+
const COLOR_SECONDARY = "var(--dsw-alias-label-secondary)";
|
|
1331
|
+
const COLOR_CAPTION = "var(--dsw-alias-label-caption)";
|
|
1332
|
+
const COLOR_BORDER = "var(--dsw-alias-border-l3)";
|
|
1333
|
+
const COLOR_SURFACE = "var(--dsw-alias-bg-layer-1)";
|
|
1334
|
+
const COLOR_ERROR = "var(--dsw-alias-state-error-primary)";
|
|
1335
|
+
const COLOR_SUCCESS = "var(--dsw-alias-state-success-primary)";
|
|
1336
|
+
const COLOR_WARN = "var(--dsw-alias-state-warn-primary)";
|
|
1337
|
+
const FONT_CODE = "var(--dsh-font-family-code)";
|
|
1338
|
+
function statusColor(status) {
|
|
1339
|
+
if (status === "loaded") return COLOR_SUCCESS;
|
|
1340
|
+
if (status === "unreadable") return COLOR_ERROR;
|
|
1341
|
+
return COLOR_WARN;
|
|
1342
|
+
}
|
|
1343
|
+
function buttonStyle(primary = false) {
|
|
1344
|
+
return {
|
|
1345
|
+
border: `1px solid ${primary ? "transparent" : COLOR_BORDER}`,
|
|
1346
|
+
borderRadius: 8,
|
|
1347
|
+
padding: "5px 12px",
|
|
1348
|
+
cursor: "pointer",
|
|
1349
|
+
font: "inherit",
|
|
1350
|
+
background: primary ? "var(--dsw-alias-brand-primary)" : "transparent",
|
|
1351
|
+
color: primary ? "var(--dsw-alias-label-primary-foreground)" : COLOR_TEXT$1
|
|
1352
|
+
};
|
|
1353
|
+
}
|
|
1354
|
+
function dangerButtonStyle() {
|
|
1355
|
+
return {
|
|
1356
|
+
border: `1px solid color-mix(in srgb, ${COLOR_ERROR} 55%, transparent)`,
|
|
1357
|
+
borderRadius: 8,
|
|
1358
|
+
padding: "5px 12px",
|
|
1359
|
+
cursor: "pointer",
|
|
1360
|
+
font: "inherit",
|
|
1361
|
+
background: `color-mix(in srgb, ${COLOR_ERROR} 10%, transparent)`,
|
|
1362
|
+
color: COLOR_ERROR
|
|
1363
|
+
};
|
|
1364
|
+
}
|
|
1365
|
+
function fieldStyle(basis, fullWidth = false) {
|
|
1366
|
+
return {
|
|
1367
|
+
flex: fullWidth ? "1 1 auto" : `1 1 ${basis}px`,
|
|
1368
|
+
width: fullWidth ? "100%" : 0,
|
|
1369
|
+
minWidth: 0,
|
|
1370
|
+
height: 34,
|
|
1371
|
+
padding: "5px 8px",
|
|
1372
|
+
boxSizing: "border-box",
|
|
1373
|
+
border: `1px solid ${COLOR_BORDER}`,
|
|
1374
|
+
borderRadius: 8,
|
|
1375
|
+
background: COLOR_SURFACE,
|
|
1376
|
+
color: COLOR_TEXT$1,
|
|
1377
|
+
font: "inherit",
|
|
1378
|
+
lineHeight: "22px",
|
|
1379
|
+
textOverflow: "ellipsis"
|
|
1380
|
+
};
|
|
1381
|
+
}
|
|
1382
|
+
function statusLabel(t, status) {
|
|
1383
|
+
if (status === "loaded") return t("statusLoaded");
|
|
1384
|
+
if (status === "unreadable") return t("statusUnreadable");
|
|
1385
|
+
return t("statusMissing");
|
|
1386
|
+
}
|
|
1387
|
+
function routeSelection(route) {
|
|
1388
|
+
return route.kind === "inherit" ? `__inherit__:${route.alias}` : route.provider;
|
|
1389
|
+
}
|
|
1390
|
+
function fromSelection(selection) {
|
|
1391
|
+
if (selection === "__inherit__:inherit-parent") return inheritRoute("inherit-parent");
|
|
1392
|
+
if (selection === "__inherit__:auto") return inheritRoute("auto");
|
|
1393
|
+
return {
|
|
1394
|
+
kind: "route",
|
|
1395
|
+
provider: selection,
|
|
1396
|
+
model: ""
|
|
1397
|
+
};
|
|
1398
|
+
}
|
|
1399
|
+
function RoleRouteRow(props) {
|
|
1400
|
+
const { role, index, route, routes, catalogue, readOnly, onChange, onRemove, t } = props;
|
|
1401
|
+
const providers = providerChoices(catalogue);
|
|
1402
|
+
const models = route.kind === "route" ? modelChoices(catalogue, route.provider) : [];
|
|
1403
|
+
const efforts = route.kind === "route" ? effortChoices(catalogue, route.provider, route.model) : [];
|
|
1404
|
+
const providerMissing = route.kind === "route" && !providers.some((entry) => entry.provider === route.provider);
|
|
1405
|
+
const modelMissing = route.kind === "route" && !models.some((entry) => entry.id === route.model) && route.model !== "";
|
|
1406
|
+
const effortMissing = route.kind === "route" && route.effort !== void 0 && route.effort !== "" && !efforts.includes(route.effort);
|
|
1407
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1408
|
+
style: {
|
|
1409
|
+
display: "grid",
|
|
1410
|
+
gap: 8
|
|
1411
|
+
},
|
|
1412
|
+
children: [
|
|
1413
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
1414
|
+
"aria-label": `${role.key} ${t("provider")}`,
|
|
1415
|
+
value: routeSelection(route),
|
|
1416
|
+
disabled: readOnly,
|
|
1417
|
+
onChange: (event) => {
|
|
1418
|
+
const next = fromSelection(event.target.value);
|
|
1419
|
+
if (next.kind === "route") {
|
|
1420
|
+
const firstModel = modelChoices(catalogue, next.provider)[0];
|
|
1421
|
+
return onChange(index, {
|
|
1422
|
+
...next,
|
|
1423
|
+
model: firstModel?.id ?? "",
|
|
1424
|
+
...firstModel?.defaultEffort === void 0 ? {} : { effort: firstModel.defaultEffort }
|
|
1425
|
+
});
|
|
1426
|
+
}
|
|
1427
|
+
onChange(index, next);
|
|
1428
|
+
},
|
|
1429
|
+
style: fieldStyle(0, true),
|
|
1430
|
+
children: [
|
|
1431
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
1432
|
+
value: "__inherit__:inherit-parent",
|
|
1433
|
+
children: t("inheritParent")
|
|
1434
|
+
}),
|
|
1435
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
1436
|
+
value: "__inherit__:auto",
|
|
1437
|
+
children: t("auto")
|
|
1438
|
+
}),
|
|
1439
|
+
providers.map((provider) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
1440
|
+
value: provider.provider,
|
|
1441
|
+
children: provider.name === provider.provider ? provider.provider : `${provider.name} (${provider.provider})`
|
|
1442
|
+
}, provider.provider)),
|
|
1443
|
+
providerMissing ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
1444
|
+
value: route.provider,
|
|
1445
|
+
children: `${route.provider} (${t("unavailable")})`
|
|
1446
|
+
}) : null
|
|
1447
|
+
]
|
|
1448
|
+
}),
|
|
1449
|
+
route.kind === "route" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1450
|
+
style: {
|
|
1451
|
+
display: "flex",
|
|
1452
|
+
gap: 8,
|
|
1453
|
+
alignItems: "center",
|
|
1454
|
+
flexWrap: "wrap"
|
|
1455
|
+
},
|
|
1456
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
1457
|
+
"aria-label": `${role.key} ${t("model")}`,
|
|
1458
|
+
value: route.model,
|
|
1459
|
+
disabled: readOnly,
|
|
1460
|
+
onChange: (event) => {
|
|
1461
|
+
const model = event.target.value;
|
|
1462
|
+
const entry = models.find((candidate) => candidate.id === model);
|
|
1463
|
+
onChange(index, {
|
|
1464
|
+
kind: "route",
|
|
1465
|
+
provider: route.provider,
|
|
1466
|
+
model,
|
|
1467
|
+
...entry?.defaultEffort === void 0 ? {} : { effort: entry.defaultEffort }
|
|
1468
|
+
});
|
|
1469
|
+
},
|
|
1470
|
+
style: fieldStyle(220),
|
|
1471
|
+
children: [
|
|
1472
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
1473
|
+
value: "",
|
|
1474
|
+
disabled: true,
|
|
1475
|
+
children: t("model")
|
|
1476
|
+
}),
|
|
1477
|
+
models.map((model) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
1478
|
+
value: model.id,
|
|
1479
|
+
children: model.id
|
|
1480
|
+
}, model.id)),
|
|
1481
|
+
modelMissing ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
1482
|
+
value: route.model,
|
|
1483
|
+
children: route.model
|
|
1484
|
+
}) : null
|
|
1485
|
+
]
|
|
1486
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
1487
|
+
"aria-label": `${role.key} ${t("effort")}`,
|
|
1488
|
+
value: route.effort ?? "",
|
|
1489
|
+
disabled: readOnly,
|
|
1490
|
+
onChange: (event) => {
|
|
1491
|
+
const effort = event.target.value;
|
|
1492
|
+
onChange(index, {
|
|
1493
|
+
kind: "route",
|
|
1494
|
+
provider: route.provider,
|
|
1495
|
+
model: route.model,
|
|
1496
|
+
...effort === "" ? {} : { effort }
|
|
1497
|
+
});
|
|
1498
|
+
},
|
|
1499
|
+
style: fieldStyle(140),
|
|
1500
|
+
children: [
|
|
1501
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
1502
|
+
value: "",
|
|
1503
|
+
children: t("effortUnset")
|
|
1504
|
+
}),
|
|
1505
|
+
efforts.map((effort) => {
|
|
1506
|
+
const defaultEffort = models.find((candidate) => candidate.id === route.model)?.defaultEffort === effort;
|
|
1507
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
1508
|
+
value: effort,
|
|
1509
|
+
children: defaultEffort ? t("effortDefault", { effort }) : effort
|
|
1510
|
+
}, effort);
|
|
1511
|
+
}),
|
|
1512
|
+
effortMissing ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
1513
|
+
value: route.effort,
|
|
1514
|
+
children: route.effort
|
|
1515
|
+
}) : null
|
|
1516
|
+
]
|
|
1517
|
+
})]
|
|
1518
|
+
}) : null,
|
|
1519
|
+
routes.length > 1 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1520
|
+
type: "button",
|
|
1521
|
+
style: {
|
|
1522
|
+
...dangerButtonStyle(),
|
|
1523
|
+
justifySelf: "start"
|
|
1524
|
+
},
|
|
1525
|
+
onClick: () => onRemove(index),
|
|
1526
|
+
disabled: readOnly,
|
|
1527
|
+
children: t("removeRoute")
|
|
1528
|
+
}) : null
|
|
1529
|
+
]
|
|
1530
|
+
});
|
|
1531
|
+
}
|
|
1532
|
+
function RoleCard(props) {
|
|
1533
|
+
const { role, routes, catalogue, busy, readOnly, message, onRouteChange, onAddRoute, onRemoveRoute, onSave, onReset, t } = props;
|
|
1534
|
+
const issues = draftIssues(role, routes);
|
|
1535
|
+
const roleText = rolePresentation(role.key, t);
|
|
1536
|
+
const canReset = routes.length !== 1 || routes[0]?.kind !== "inherit" || routes[0]?.alias !== "inherit-parent";
|
|
1537
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
1538
|
+
style: {
|
|
1539
|
+
border: `1px solid ${COLOR_BORDER}`,
|
|
1540
|
+
borderRadius: 12,
|
|
1541
|
+
padding: 14,
|
|
1542
|
+
display: "grid",
|
|
1543
|
+
gap: 10,
|
|
1544
|
+
background: COLOR_SURFACE
|
|
1545
|
+
},
|
|
1546
|
+
children: [
|
|
1547
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
|
|
1548
|
+
style: {
|
|
1549
|
+
display: "grid",
|
|
1550
|
+
gap: 5
|
|
1551
|
+
},
|
|
1552
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1553
|
+
style: {
|
|
1554
|
+
display: "flex",
|
|
1555
|
+
gap: 10,
|
|
1556
|
+
alignItems: "baseline",
|
|
1557
|
+
flexWrap: "wrap"
|
|
1558
|
+
},
|
|
1559
|
+
children: [
|
|
1560
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", {
|
|
1561
|
+
style: {
|
|
1562
|
+
color: COLOR_TEXT$1,
|
|
1563
|
+
fontSize: 14
|
|
1564
|
+
},
|
|
1565
|
+
children: roleText.name
|
|
1566
|
+
}),
|
|
1567
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
|
|
1568
|
+
style: {
|
|
1569
|
+
color: COLOR_CAPTION,
|
|
1570
|
+
fontFamily: FONT_CODE,
|
|
1571
|
+
fontSize: 11
|
|
1572
|
+
},
|
|
1573
|
+
children: role.key
|
|
1574
|
+
}),
|
|
1575
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1576
|
+
style: {
|
|
1577
|
+
color: COLOR_CAPTION,
|
|
1578
|
+
fontSize: 12
|
|
1579
|
+
},
|
|
1580
|
+
children: role.configured ? t("configuredLine", { line: role.line ?? "?" }) : t("notConfigured")
|
|
1581
|
+
})
|
|
1582
|
+
]
|
|
1583
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1584
|
+
style: {
|
|
1585
|
+
margin: 0,
|
|
1586
|
+
color: COLOR_SECONDARY,
|
|
1587
|
+
fontSize: 12,
|
|
1588
|
+
lineHeight: 1.5
|
|
1589
|
+
},
|
|
1590
|
+
children: roleText.description
|
|
1591
|
+
})]
|
|
1592
|
+
}),
|
|
1593
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
|
|
1594
|
+
style: {
|
|
1595
|
+
color: COLOR_SECONDARY,
|
|
1596
|
+
fontFamily: FONT_CODE,
|
|
1597
|
+
fontSize: 12
|
|
1598
|
+
},
|
|
1599
|
+
children: readOnly ? t("statusUnreadable") : routes.map((route) => formatRoute(route)).join(" · ")
|
|
1600
|
+
}),
|
|
1601
|
+
role.issues.map((issue, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1602
|
+
style: {
|
|
1603
|
+
color: COLOR_ERROR,
|
|
1604
|
+
fontSize: 12
|
|
1605
|
+
},
|
|
1606
|
+
children: t("roleIssues", { message: issue })
|
|
1607
|
+
}, `${issue}-${index}`)),
|
|
1608
|
+
readOnly ? null : routes.map((route, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RoleRouteRow, {
|
|
1609
|
+
role,
|
|
1610
|
+
index,
|
|
1611
|
+
route,
|
|
1612
|
+
routes,
|
|
1613
|
+
catalogue,
|
|
1614
|
+
readOnly,
|
|
1615
|
+
onChange: onRouteChange,
|
|
1616
|
+
onRemove: onRemoveRoute,
|
|
1617
|
+
t
|
|
1618
|
+
}, index)),
|
|
1619
|
+
issues.map((issue, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1620
|
+
style: {
|
|
1621
|
+
color: COLOR_ERROR,
|
|
1622
|
+
fontSize: 12
|
|
1623
|
+
},
|
|
1624
|
+
children: issue
|
|
1625
|
+
}, `${issue}-${index}`)),
|
|
1626
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("footer", {
|
|
1627
|
+
style: {
|
|
1628
|
+
display: "flex",
|
|
1629
|
+
gap: 8,
|
|
1630
|
+
alignItems: "center",
|
|
1631
|
+
flexWrap: "wrap"
|
|
1632
|
+
},
|
|
1633
|
+
children: [
|
|
1634
|
+
role.shape === "list" && !readOnly ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1635
|
+
type: "button",
|
|
1636
|
+
style: buttonStyle(),
|
|
1637
|
+
onClick: onAddRoute,
|
|
1638
|
+
disabled: busy,
|
|
1639
|
+
children: t("addRoute")
|
|
1640
|
+
}) : null,
|
|
1641
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1642
|
+
type: "button",
|
|
1643
|
+
style: buttonStyle(true),
|
|
1644
|
+
onClick: onSave,
|
|
1645
|
+
disabled: readOnly || busy || issues.length > 0,
|
|
1646
|
+
children: busy ? t("saving") : t("save")
|
|
1647
|
+
}),
|
|
1648
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1649
|
+
type: "button",
|
|
1650
|
+
style: buttonStyle(),
|
|
1651
|
+
onClick: onReset,
|
|
1652
|
+
disabled: readOnly || busy || !canReset,
|
|
1653
|
+
children: busy ? t("resetting") : t("reset")
|
|
1654
|
+
}),
|
|
1655
|
+
message === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1656
|
+
style: {
|
|
1657
|
+
color: message.tone === "ok" ? COLOR_SUCCESS : COLOR_ERROR,
|
|
1658
|
+
fontSize: 12
|
|
1659
|
+
},
|
|
1660
|
+
children: message.text
|
|
1661
|
+
})
|
|
1662
|
+
]
|
|
1663
|
+
})
|
|
1664
|
+
]
|
|
1665
|
+
});
|
|
1666
|
+
}
|
|
1667
|
+
function RoleEffortNote({ t }) {
|
|
1668
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
1669
|
+
style: {
|
|
1670
|
+
border: `1px solid ${COLOR_BORDER}`,
|
|
1671
|
+
borderRadius: 12,
|
|
1672
|
+
padding: "12px 14px",
|
|
1673
|
+
display: "grid",
|
|
1674
|
+
gap: 6,
|
|
1675
|
+
background: COLOR_SURFACE,
|
|
1676
|
+
color: COLOR_SECONDARY,
|
|
1677
|
+
fontSize: 12,
|
|
1678
|
+
lineHeight: 1.5
|
|
1679
|
+
},
|
|
1680
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", {
|
|
1681
|
+
style: {
|
|
1682
|
+
color: COLOR_TEXT$1,
|
|
1683
|
+
fontSize: 13
|
|
1684
|
+
},
|
|
1685
|
+
children: t("effortBaselineTitle")
|
|
1686
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1687
|
+
style: { margin: 0 },
|
|
1688
|
+
children: t("effortBaselineIntro")
|
|
1689
|
+
})]
|
|
1690
|
+
});
|
|
1691
|
+
}
|
|
1692
|
+
/** Pure view: all rendering decisions are explicit props. */
|
|
1693
|
+
function RoleSettingsView(props) {
|
|
1694
|
+
const { snapshot, catalogue, drafts, busyKey, messages, loadError, onRetry, t } = props;
|
|
1695
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1696
|
+
style: {
|
|
1697
|
+
display: "grid",
|
|
1698
|
+
gap: 16,
|
|
1699
|
+
padding: "4px 0 20px",
|
|
1700
|
+
color: COLOR_TEXT$1
|
|
1701
|
+
},
|
|
1702
|
+
children: [
|
|
1703
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
|
|
1704
|
+
style: {
|
|
1705
|
+
display: "grid",
|
|
1706
|
+
gap: 6
|
|
1707
|
+
},
|
|
1708
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
|
|
1709
|
+
style: {
|
|
1710
|
+
margin: 0,
|
|
1711
|
+
fontSize: 16
|
|
1712
|
+
},
|
|
1713
|
+
children: t("title")
|
|
1714
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1715
|
+
style: {
|
|
1716
|
+
margin: 0,
|
|
1717
|
+
color: COLOR_SECONDARY,
|
|
1718
|
+
fontSize: 13,
|
|
1719
|
+
lineHeight: 1.5
|
|
1720
|
+
},
|
|
1721
|
+
children: t("subtitle")
|
|
1722
|
+
})]
|
|
1723
|
+
}),
|
|
1724
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(RoleEffortNote, { t }),
|
|
1725
|
+
snapshot === null ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1726
|
+
style: {
|
|
1727
|
+
display: "flex",
|
|
1728
|
+
gap: 10,
|
|
1729
|
+
alignItems: "center",
|
|
1730
|
+
color: COLOR_SECONDARY
|
|
1731
|
+
},
|
|
1732
|
+
children: [loadError === void 0 ? t("loading") : t("loadFailed", { message: loadError }), loadError === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1733
|
+
type: "button",
|
|
1734
|
+
style: buttonStyle(),
|
|
1735
|
+
onClick: onRetry,
|
|
1736
|
+
children: t("retry")
|
|
1737
|
+
})]
|
|
1738
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
1739
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
1740
|
+
style: {
|
|
1741
|
+
border: `1px solid ${COLOR_BORDER}`,
|
|
1742
|
+
borderRadius: 12,
|
|
1743
|
+
padding: 14,
|
|
1744
|
+
display: "grid",
|
|
1745
|
+
gap: 6,
|
|
1746
|
+
background: COLOR_SURFACE
|
|
1747
|
+
},
|
|
1748
|
+
children: [
|
|
1749
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1750
|
+
style: {
|
|
1751
|
+
display: "flex",
|
|
1752
|
+
gap: 8,
|
|
1753
|
+
alignItems: "baseline",
|
|
1754
|
+
flexWrap: "wrap"
|
|
1755
|
+
},
|
|
1756
|
+
children: [
|
|
1757
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1758
|
+
style: {
|
|
1759
|
+
fontSize: 12,
|
|
1760
|
+
color: COLOR_CAPTION
|
|
1761
|
+
},
|
|
1762
|
+
children: t("file")
|
|
1763
|
+
}),
|
|
1764
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
|
|
1765
|
+
style: {
|
|
1766
|
+
fontFamily: FONT_CODE,
|
|
1767
|
+
fontSize: 12,
|
|
1768
|
+
overflowWrap: "anywhere"
|
|
1769
|
+
},
|
|
1770
|
+
children: snapshot.path
|
|
1771
|
+
}),
|
|
1772
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1773
|
+
style: {
|
|
1774
|
+
fontSize: 12,
|
|
1775
|
+
padding: "1px 8px",
|
|
1776
|
+
borderRadius: 999,
|
|
1777
|
+
border: `1px solid ${statusColor(snapshot.status)}`,
|
|
1778
|
+
color: statusColor(snapshot.status)
|
|
1779
|
+
},
|
|
1780
|
+
children: statusLabel(t, snapshot.status)
|
|
1781
|
+
})
|
|
1782
|
+
]
|
|
1783
|
+
}),
|
|
1784
|
+
snapshot.budget === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1785
|
+
style: {
|
|
1786
|
+
fontSize: 12,
|
|
1787
|
+
color: COLOR_SECONDARY
|
|
1788
|
+
},
|
|
1789
|
+
children: [
|
|
1790
|
+
t("budget"),
|
|
1791
|
+
": ",
|
|
1792
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
|
|
1793
|
+
style: { fontFamily: FONT_CODE },
|
|
1794
|
+
children: snapshot.budget
|
|
1795
|
+
})
|
|
1796
|
+
]
|
|
1797
|
+
}),
|
|
1798
|
+
snapshot.status === "unreadable" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1799
|
+
style: {
|
|
1800
|
+
fontSize: 12,
|
|
1801
|
+
color: COLOR_ERROR
|
|
1802
|
+
},
|
|
1803
|
+
children: t("unreadableBody", { reason: snapshot.reason ?? "" })
|
|
1804
|
+
}) : null
|
|
1805
|
+
]
|
|
1806
|
+
}),
|
|
1807
|
+
snapshot.issues.length === 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
1808
|
+
style: {
|
|
1809
|
+
display: "grid",
|
|
1810
|
+
gap: 4
|
|
1811
|
+
},
|
|
1812
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", {
|
|
1813
|
+
style: { fontSize: 13 },
|
|
1814
|
+
children: t("fileIssues")
|
|
1815
|
+
}), snapshot.issues.map((issue, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1816
|
+
style: {
|
|
1817
|
+
color: COLOR_ERROR,
|
|
1818
|
+
fontSize: 12
|
|
1819
|
+
},
|
|
1820
|
+
children: issue.line === void 0 ? issue.message : `${t("line", { line: issue.line })}: ${issue.message}`
|
|
1821
|
+
}, `${issue.message}-${index}`))]
|
|
1822
|
+
}),
|
|
1823
|
+
catalogue === void 0 || catalogue.providers.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1824
|
+
style: {
|
|
1825
|
+
color: COLOR_WARN,
|
|
1826
|
+
fontSize: 12
|
|
1827
|
+
},
|
|
1828
|
+
children: t("noCatalogue")
|
|
1829
|
+
}) : catalogue.truncated ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1830
|
+
style: {
|
|
1831
|
+
color: COLOR_CAPTION,
|
|
1832
|
+
fontSize: 12
|
|
1833
|
+
},
|
|
1834
|
+
children: t("truncated")
|
|
1835
|
+
}) : null,
|
|
1836
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1837
|
+
style: {
|
|
1838
|
+
display: "grid",
|
|
1839
|
+
gap: 10
|
|
1840
|
+
},
|
|
1841
|
+
children: snapshot.roles.map((role) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RoleCard, {
|
|
1842
|
+
role,
|
|
1843
|
+
routes: drafts[role.key] ?? [inheritRoute()],
|
|
1844
|
+
catalogue,
|
|
1845
|
+
busy: busyKey === role.key,
|
|
1846
|
+
readOnly: !snapshot.canSave,
|
|
1847
|
+
message: messages[role.key],
|
|
1848
|
+
onRouteChange: (index, route) => props.onRouteChange(role.key, index, route),
|
|
1849
|
+
onAddRoute: () => props.onAddRoute(role.key),
|
|
1850
|
+
onRemoveRoute: (index) => props.onRemoveRoute(role.key, index),
|
|
1851
|
+
onSave: () => props.onSave(role.key),
|
|
1852
|
+
onReset: () => props.onReset(role.key),
|
|
1853
|
+
t
|
|
1854
|
+
}, role.key))
|
|
1855
|
+
})
|
|
1856
|
+
] })
|
|
1857
|
+
]
|
|
1858
|
+
});
|
|
1859
|
+
}
|
|
1860
|
+
function remoteError(error) {
|
|
1861
|
+
if (typeof error === "object" && error !== null && "message" in error) return String(error.message);
|
|
1862
|
+
return String(error);
|
|
1863
|
+
}
|
|
1864
|
+
/** Section container: owns loading, drafts, and the two Remote calls. */
|
|
1865
|
+
function RoleSettingsPage(props) {
|
|
1866
|
+
const { api, t } = props;
|
|
1867
|
+
const [snapshot, setSnapshot] = (0, react.useState)(null);
|
|
1868
|
+
const [catalogue, setCatalogue] = (0, react.useState)(void 0);
|
|
1869
|
+
const [drafts, setDrafts] = (0, react.useState)({});
|
|
1870
|
+
const [messages, setMessages] = (0, react.useState)({});
|
|
1871
|
+
const [busyKey, setBusyKey] = (0, react.useState)(void 0);
|
|
1872
|
+
const [loadError, setLoadError] = (0, react.useState)(void 0);
|
|
1873
|
+
const [reloadToken, setReloadToken] = (0, react.useState)(0);
|
|
1874
|
+
(0, react.useEffect)(() => {
|
|
1875
|
+
let cancelled = false;
|
|
1876
|
+
setLoadError(void 0);
|
|
1877
|
+
Promise.all([api.status(), api.catalogue()]).then(([nextSnapshot, nextCatalogue]) => {
|
|
1878
|
+
if (cancelled) return;
|
|
1879
|
+
setSnapshot(nextSnapshot);
|
|
1880
|
+
setCatalogue(nextCatalogue);
|
|
1881
|
+
setDrafts(draftsFromSnapshot(nextSnapshot));
|
|
1882
|
+
}, (error) => {
|
|
1883
|
+
if (cancelled) return;
|
|
1884
|
+
setLoadError(remoteError(error));
|
|
1885
|
+
});
|
|
1886
|
+
return () => {
|
|
1887
|
+
cancelled = true;
|
|
1888
|
+
};
|
|
1889
|
+
}, [api, reloadToken]);
|
|
1890
|
+
const updateRole = (key, next) => {
|
|
1891
|
+
setDrafts((current) => ({
|
|
1892
|
+
...current,
|
|
1893
|
+
[key]: next
|
|
1894
|
+
}));
|
|
1895
|
+
setMessages((current) => {
|
|
1896
|
+
const { [key]: _ignored, ...rest } = current;
|
|
1897
|
+
return rest;
|
|
1898
|
+
});
|
|
1899
|
+
};
|
|
1900
|
+
const save = (key, routes) => {
|
|
1901
|
+
setBusyKey(key);
|
|
1902
|
+
setMessages((current) => {
|
|
1903
|
+
const { [key]: _ignored, ...rest } = current;
|
|
1904
|
+
return rest;
|
|
1905
|
+
});
|
|
1906
|
+
api.save(saveRequestFor(key, routes)).then((result) => {
|
|
1907
|
+
setBusyKey(void 0);
|
|
1908
|
+
if (!result.ok) {
|
|
1909
|
+
setMessages((current) => ({
|
|
1910
|
+
...current,
|
|
1911
|
+
[key]: {
|
|
1912
|
+
tone: "error",
|
|
1913
|
+
text: t("saveRejected", { reason: result.reason })
|
|
1914
|
+
}
|
|
1915
|
+
}));
|
|
1916
|
+
return;
|
|
1917
|
+
}
|
|
1918
|
+
setSnapshot(result.snapshot);
|
|
1919
|
+
setDrafts((current) => ({
|
|
1920
|
+
...current,
|
|
1921
|
+
[key]: draftsFromSnapshot(result.snapshot)[key] ?? [inheritRoute()]
|
|
1922
|
+
}));
|
|
1923
|
+
setMessages((current) => ({
|
|
1924
|
+
...current,
|
|
1925
|
+
[key]: {
|
|
1926
|
+
tone: "ok",
|
|
1927
|
+
text: t("saved")
|
|
1928
|
+
}
|
|
1929
|
+
}));
|
|
1930
|
+
}, (error) => {
|
|
1931
|
+
setBusyKey(void 0);
|
|
1932
|
+
setMessages((current) => ({
|
|
1933
|
+
...current,
|
|
1934
|
+
[key]: {
|
|
1935
|
+
tone: "error",
|
|
1936
|
+
text: t("saveRejected", { reason: remoteError(error) })
|
|
1937
|
+
}
|
|
1938
|
+
}));
|
|
1939
|
+
});
|
|
1940
|
+
};
|
|
1941
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RoleSettingsView, {
|
|
1942
|
+
snapshot,
|
|
1943
|
+
catalogue,
|
|
1944
|
+
drafts,
|
|
1945
|
+
busyKey,
|
|
1946
|
+
messages,
|
|
1947
|
+
loadError,
|
|
1948
|
+
onRetry: () => setReloadToken((value) => value + 1),
|
|
1949
|
+
onRouteChange: (key, index, route) => {
|
|
1950
|
+
const current = drafts[key] ?? [inheritRoute()];
|
|
1951
|
+
updateRole(key, replaceRoute(current, index, route));
|
|
1952
|
+
},
|
|
1953
|
+
onAddRoute: (key) => updateRole(key, appendRoute(drafts[key] ?? [inheritRoute()])),
|
|
1954
|
+
onRemoveRoute: (key, index) => updateRole(key, removeRoute(drafts[key] ?? [inheritRoute()], index)),
|
|
1955
|
+
onSave: (key) => save(key, drafts[key] ?? [inheritRoute()]),
|
|
1956
|
+
onReset: (key) => save(key, [inheritRoute()]),
|
|
1957
|
+
t
|
|
1958
|
+
});
|
|
1959
|
+
}
|
|
1960
|
+
// src/client/PstackBundleConfigDialog.tsx
|
|
1961
|
+
/**
|
|
1962
|
+
* Configure action and Settings-style dialog for the dsh-pstack bundle detail
|
|
1963
|
+
* page.
|
|
1964
|
+
*
|
|
1965
|
+
* The bundle's configuration used to render inline on the detail page. It now
|
|
1966
|
+
* opens from the detail page header through `plugins.detail.actions`: the
|
|
1967
|
+
* action renders only for the dsh-pstack bundle, and the dialog keeps the
|
|
1968
|
+
* plugin manager's page untouched while it is open.
|
|
1969
|
+
*/
|
|
1970
|
+
const COLOR_TEXT = "var(--dsw-alias-label-primary)";
|
|
1971
|
+
const overlayStyle = {
|
|
1972
|
+
position: "fixed",
|
|
1973
|
+
inset: 0,
|
|
1974
|
+
zIndex: 1e3,
|
|
1975
|
+
display: "flex",
|
|
1976
|
+
alignItems: "center",
|
|
1977
|
+
justifyContent: "center"
|
|
1978
|
+
};
|
|
1979
|
+
const maskStyle = {
|
|
1980
|
+
position: "absolute",
|
|
1981
|
+
inset: 0,
|
|
1982
|
+
background: "var(--dsw-alias-bg-mask-1)",
|
|
1983
|
+
backdropFilter: "var(--dsw-mask-blur)"
|
|
1984
|
+
};
|
|
1985
|
+
const panelStyle = {
|
|
1986
|
+
position: "relative",
|
|
1987
|
+
zIndex: 1,
|
|
1988
|
+
display: "grid",
|
|
1989
|
+
gridTemplateColumns: "204px minmax(0, 1fr)",
|
|
1990
|
+
gridTemplateRows: "auto minmax(0, 1fr)",
|
|
1991
|
+
gridTemplateAreas: "'title header' 'nav options'",
|
|
1992
|
+
width: 800,
|
|
1993
|
+
height: "min(800px, calc(100vh - 48px))",
|
|
1994
|
+
maxWidth: "calc(100vw - 48px)",
|
|
1995
|
+
borderRadius: "var(--dsw-radius-panel)",
|
|
1996
|
+
overflow: "hidden",
|
|
1997
|
+
background: "var(--dsw-alias-bg-layer-2)",
|
|
1998
|
+
boxShadow: "var(--dsw-elevation-prominent)",
|
|
1999
|
+
color: COLOR_TEXT
|
|
2000
|
+
};
|
|
2001
|
+
const navTitleStyle = {
|
|
2002
|
+
gridArea: "title",
|
|
2003
|
+
padding: "22px 12px 8px 24px",
|
|
2004
|
+
fontSize: 16,
|
|
2005
|
+
lineHeight: "24px",
|
|
2006
|
+
fontWeight: 500,
|
|
2007
|
+
color: COLOR_TEXT
|
|
2008
|
+
};
|
|
2009
|
+
const navStyle = {
|
|
2010
|
+
gridArea: "nav",
|
|
2011
|
+
minWidth: 0,
|
|
2012
|
+
minHeight: 0,
|
|
2013
|
+
display: "flex",
|
|
2014
|
+
flexDirection: "column",
|
|
2015
|
+
padding: "10px 12px 0",
|
|
2016
|
+
boxSizing: "border-box"
|
|
2017
|
+
};
|
|
2018
|
+
const navListStyle = {
|
|
2019
|
+
display: "flex",
|
|
2020
|
+
flexDirection: "column",
|
|
2021
|
+
gap: 4,
|
|
2022
|
+
minHeight: 0,
|
|
2023
|
+
overflowY: "auto"
|
|
2024
|
+
};
|
|
2025
|
+
const navLabelStyle = {
|
|
2026
|
+
flex: 1,
|
|
2027
|
+
minWidth: 0,
|
|
2028
|
+
overflow: "hidden",
|
|
2029
|
+
whiteSpace: "nowrap",
|
|
2030
|
+
textOverflow: "ellipsis"
|
|
2031
|
+
};
|
|
2032
|
+
const headerStyle = {
|
|
2033
|
+
gridArea: "header",
|
|
2034
|
+
display: "flex",
|
|
2035
|
+
alignItems: "flex-start",
|
|
2036
|
+
justifyContent: "flex-end",
|
|
2037
|
+
height: 54,
|
|
2038
|
+
padding: "20px 14px 8px 10px",
|
|
2039
|
+
boxSizing: "border-box"
|
|
2040
|
+
};
|
|
2041
|
+
const closeStyle = {
|
|
2042
|
+
display: "inline-flex",
|
|
2043
|
+
alignItems: "center",
|
|
2044
|
+
justifyContent: "center",
|
|
2045
|
+
width: 28,
|
|
2046
|
+
height: 28,
|
|
2047
|
+
padding: 0,
|
|
2048
|
+
border: "none",
|
|
2049
|
+
borderRadius: "var(--dsw-radius-sm)",
|
|
2050
|
+
background: "transparent",
|
|
2051
|
+
cursor: "pointer",
|
|
2052
|
+
color: COLOR_TEXT
|
|
2053
|
+
};
|
|
2054
|
+
const optionsStyle = {
|
|
2055
|
+
gridArea: "options",
|
|
2056
|
+
minWidth: 0,
|
|
2057
|
+
minHeight: 0,
|
|
2058
|
+
padding: "0 24px 24px",
|
|
2059
|
+
overflowY: "auto"
|
|
2060
|
+
};
|
|
2061
|
+
function actionButtonStyle() {
|
|
2062
|
+
return {
|
|
2063
|
+
display: "inline-flex",
|
|
2064
|
+
alignItems: "center",
|
|
2065
|
+
gap: 6,
|
|
2066
|
+
height: 28,
|
|
2067
|
+
padding: "0 12px",
|
|
2068
|
+
border: "0.5px solid transparent",
|
|
2069
|
+
borderRadius: "var(--dsw-radius-sm)",
|
|
2070
|
+
boxShadow: "inset 0 0 0 var(--dsh-hairline-width, 0.5px) var(--dsw-alias-border-l3)",
|
|
2071
|
+
background: "transparent",
|
|
2072
|
+
color: COLOR_TEXT,
|
|
2073
|
+
cursor: "pointer",
|
|
2074
|
+
font: "inherit",
|
|
2075
|
+
fontSize: 12,
|
|
2076
|
+
lineHeight: "18px",
|
|
2077
|
+
whiteSpace: "nowrap"
|
|
2078
|
+
};
|
|
2079
|
+
}
|
|
2080
|
+
function navCellStyle(active) {
|
|
2081
|
+
return {
|
|
2082
|
+
display: "flex",
|
|
2083
|
+
alignItems: "center",
|
|
2084
|
+
gap: 8,
|
|
2085
|
+
height: 40,
|
|
2086
|
+
padding: "9px 16px 9px 12px",
|
|
2087
|
+
boxSizing: "border-box",
|
|
2088
|
+
border: "none",
|
|
2089
|
+
borderRadius: "var(--dsw-radius-md)",
|
|
2090
|
+
background: active ? "var(--dsw-specific-sidebar-nav-item-active)" : "transparent",
|
|
2091
|
+
cursor: "pointer",
|
|
2092
|
+
fontFamily: "inherit",
|
|
2093
|
+
fontSize: 14,
|
|
2094
|
+
lineHeight: "22px",
|
|
2095
|
+
fontWeight: 400,
|
|
2096
|
+
color: COLOR_TEXT,
|
|
2097
|
+
textAlign: "left"
|
|
2098
|
+
};
|
|
2099
|
+
}
|
|
2100
|
+
/** Header action that opens the configuration dialog for the dsh-pstack bundle only. */
|
|
2101
|
+
function PstackBundleConfigAction(props) {
|
|
2102
|
+
const [open, setOpen] = (0, react.useState)(false);
|
|
2103
|
+
if (props.subject.kind !== "bundle" || props.subject.pkg.name !== "dsh-pstack") return null;
|
|
2104
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(PstackConfigureButton, {
|
|
2105
|
+
t: props.t,
|
|
2106
|
+
onClick: () => setOpen(true)
|
|
2107
|
+
}), open ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PstackBundleConfigDialog, {
|
|
2108
|
+
api: props.api,
|
|
2109
|
+
optionalPlugins: props.optionalPlugins,
|
|
2110
|
+
rolesT: props.rolesT,
|
|
2111
|
+
pluginsT: props.t,
|
|
2112
|
+
onClose: () => setOpen(false)
|
|
2113
|
+
}) : null] });
|
|
2114
|
+
}
|
|
2115
|
+
function GearIcon() {
|
|
2116
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
|
|
2117
|
+
width: "14",
|
|
2118
|
+
height: "14",
|
|
2119
|
+
viewBox: "0 0 24 24",
|
|
2120
|
+
fill: "none",
|
|
2121
|
+
stroke: "currentColor",
|
|
2122
|
+
strokeWidth: "1.8",
|
|
2123
|
+
strokeLinecap: "round",
|
|
2124
|
+
strokeLinejoin: "round",
|
|
2125
|
+
"aria-hidden": "true",
|
|
2126
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
|
|
2127
|
+
cx: "12",
|
|
2128
|
+
cy: "12",
|
|
2129
|
+
r: "3"
|
|
2130
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09a1.65 1.65 0 0 0-1-1.51 1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09a1.65 1.65 0 0 0 1.51-1 1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33h.09a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82v.09a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z" })]
|
|
2131
|
+
});
|
|
2132
|
+
}
|
|
2133
|
+
/** Small outlined button that opens the dialog. */
|
|
2134
|
+
function PstackConfigureButton(props) {
|
|
2135
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
2136
|
+
type: "button",
|
|
2137
|
+
"data-pstack-configure-action": true,
|
|
2138
|
+
style: actionButtonStyle(),
|
|
2139
|
+
onClick: props.onClick,
|
|
2140
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(GearIcon, {}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: props.t("configureAction") })]
|
|
2141
|
+
});
|
|
2142
|
+
}
|
|
2143
|
+
/** Portaled container: owns the active section and the Escape key. */
|
|
2144
|
+
function PstackBundleConfigDialog(props) {
|
|
2145
|
+
const [active, setActive] = (0, react.useState)("plugins");
|
|
2146
|
+
const { onClose } = props;
|
|
2147
|
+
(0, react.useEffect)(() => {
|
|
2148
|
+
const onKeyDown = (event) => {
|
|
2149
|
+
if (event.key === "Escape") onClose();
|
|
2150
|
+
};
|
|
2151
|
+
document.addEventListener("keydown", onKeyDown);
|
|
2152
|
+
return () => document.removeEventListener("keydown", onKeyDown);
|
|
2153
|
+
}, [onClose]);
|
|
2154
|
+
return (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(PstackBundleConfigDialogView, {
|
|
2155
|
+
...props,
|
|
2156
|
+
active,
|
|
2157
|
+
onSelect: (section) => setActive(section)
|
|
2158
|
+
}), document.body);
|
|
2159
|
+
}
|
|
2160
|
+
function CloseIcon() {
|
|
2161
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
2162
|
+
width: "14",
|
|
2163
|
+
height: "14",
|
|
2164
|
+
viewBox: "0 0 14 14",
|
|
2165
|
+
"aria-hidden": "true",
|
|
2166
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
2167
|
+
d: "M2 2l10 10M12 2L2 12",
|
|
2168
|
+
stroke: "currentColor",
|
|
2169
|
+
strokeWidth: "1.5",
|
|
2170
|
+
strokeLinecap: "round"
|
|
2171
|
+
})
|
|
2172
|
+
});
|
|
2173
|
+
}
|
|
2174
|
+
/** Pure view of the Settings-style modal: mask, centered card, nav rail, and section body. */
|
|
2175
|
+
function PstackBundleConfigDialogView(props) {
|
|
2176
|
+
const { active, onSelect, onClose, api, optionalPlugins, rolesT, pluginsT } = props;
|
|
2177
|
+
const titleId = (0, react.useId)();
|
|
2178
|
+
const sections = [{
|
|
2179
|
+
id: "plugins",
|
|
2180
|
+
label: pluginsT("configurePluginsSection")
|
|
2181
|
+
}, {
|
|
2182
|
+
id: "roles",
|
|
2183
|
+
label: pluginsT("configureRolesSection")
|
|
2184
|
+
}];
|
|
2185
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2186
|
+
style: overlayStyle,
|
|
2187
|
+
role: "presentation",
|
|
2188
|
+
"data-pstack-config-dialog": true,
|
|
2189
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2190
|
+
style: maskStyle,
|
|
2191
|
+
"aria-hidden": "true",
|
|
2192
|
+
onClick: onClose
|
|
2193
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2194
|
+
role: "dialog",
|
|
2195
|
+
"aria-modal": "true",
|
|
2196
|
+
"aria-labelledby": titleId,
|
|
2197
|
+
style: panelStyle,
|
|
2198
|
+
children: [
|
|
2199
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2200
|
+
id: titleId,
|
|
2201
|
+
style: navTitleStyle,
|
|
2202
|
+
children: pluginsT("configureTitle")
|
|
2203
|
+
}),
|
|
2204
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("nav", {
|
|
2205
|
+
"aria-label": pluginsT("configureSections"),
|
|
2206
|
+
style: navStyle,
|
|
2207
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2208
|
+
style: navListStyle,
|
|
2209
|
+
children: sections.map((section) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2210
|
+
type: "button",
|
|
2211
|
+
style: navCellStyle(section.id === active),
|
|
2212
|
+
"aria-current": section.id === active ? "true" : void 0,
|
|
2213
|
+
autoFocus: section.id === active,
|
|
2214
|
+
"data-pstack-config-section-tab": section.id,
|
|
2215
|
+
onClick: () => onSelect(section.id),
|
|
2216
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2217
|
+
style: navLabelStyle,
|
|
2218
|
+
children: section.label
|
|
2219
|
+
})
|
|
2220
|
+
}, section.id))
|
|
2221
|
+
})
|
|
2222
|
+
}),
|
|
2223
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2224
|
+
style: headerStyle,
|
|
2225
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2226
|
+
type: "button",
|
|
2227
|
+
"aria-label": pluginsT("configureClose"),
|
|
2228
|
+
style: closeStyle,
|
|
2229
|
+
onClick: onClose,
|
|
2230
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CloseIcon, {})
|
|
2231
|
+
})
|
|
2232
|
+
}),
|
|
2233
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2234
|
+
style: optionsStyle,
|
|
2235
|
+
"data-pstack-config-section": active,
|
|
2236
|
+
children: active === "plugins" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(OptionalPluginsSection, {
|
|
2237
|
+
optionalPlugins,
|
|
2238
|
+
t: pluginsT
|
|
2239
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RoleSettingsPage, {
|
|
2240
|
+
api,
|
|
2241
|
+
t: rolesT
|
|
2242
|
+
})
|
|
2243
|
+
})
|
|
2244
|
+
]
|
|
2245
|
+
})]
|
|
2246
|
+
});
|
|
2247
|
+
}
|
|
2248
|
+
// src/client/optional-plugins-locales.ts
|
|
2249
|
+
const NS = "pstackPlugins";
|
|
2250
|
+
/** Complete dictionaries, one entry per supported locale. */
|
|
2251
|
+
const catalogs = {
|
|
2252
|
+
en: {
|
|
2253
|
+
configureAction: "Configure",
|
|
2254
|
+
configureTitle: "Configure dsh-pstack",
|
|
2255
|
+
configureSections: "Configuration sections",
|
|
2256
|
+
configurePluginsSection: "Recommended plugins",
|
|
2257
|
+
configureRolesSection: "Role configuration",
|
|
2258
|
+
configureClose: "Close",
|
|
2259
|
+
optionalPluginsTitle: "Recommended plugins",
|
|
2260
|
+
optionalPluginsSubtitle: "Optional DSH plugins that complete pstack flows. Nothing is enabled automatically; turn on only what you need.",
|
|
2261
|
+
groupSessions: "Sessions and isolation",
|
|
2262
|
+
groupVerification: "Verification and evidence",
|
|
2263
|
+
groupMultiAgent: "Multi-agent",
|
|
2264
|
+
recommendedTag: "Recommended",
|
|
2265
|
+
suggestedTag: "GitHub PR suggestion",
|
|
2266
|
+
conflictTag: "Conflict",
|
|
2267
|
+
optionalLoading: "Loading optional plugin status…",
|
|
2268
|
+
optionalLoadFailed: "Could not read optional plugin status: {message}",
|
|
2269
|
+
optionalRetry: "Retry",
|
|
2270
|
+
optionalRefresh: "Refresh",
|
|
2271
|
+
enableRecommended: "Enable recommended plugins",
|
|
2272
|
+
enablingRecommended: "Enabling recommended plugins…",
|
|
2273
|
+
recommendedSummary: "{applied} applied, {restart} need a restart, {failed} failed, {skipped} skipped",
|
|
2274
|
+
recommendedHint: "Default recommendations: {ids}.",
|
|
2275
|
+
listSeparator: ", ",
|
|
2276
|
+
enablePlugin: "Enable",
|
|
2277
|
+
disablePlugin: "Disable",
|
|
2278
|
+
pluginWorking: "Working…",
|
|
2279
|
+
pluginStatusEnabled: "enabled",
|
|
2280
|
+
pluginStatusOff: "off",
|
|
2281
|
+
pluginStatusMissing: "missing",
|
|
2282
|
+
pluginStatusUnsupportedPlatform: "unsupported-platform",
|
|
2283
|
+
pluginStatusError: "error",
|
|
2284
|
+
resultApplied: "Applied",
|
|
2285
|
+
resultRestartRequired: "Restart required",
|
|
2286
|
+
resultFailed: "Failed: {reason}",
|
|
2287
|
+
resultOverridden: "Overridden by a higher layer",
|
|
2288
|
+
resultCancelled: "Cancelled",
|
|
2289
|
+
conflictWarning: "Do not enable Agent Teams and pstack_subagent in one profile: Agent Teams disables subagent and subagent_fork and replaces the child controls.",
|
|
2290
|
+
nonBundleGuidance: "Not a bundle: configure it in the DSH documentation; this page cannot switch it.",
|
|
2291
|
+
guidanceTitle: "{count} recommended plugins are still off",
|
|
2292
|
+
guidanceOpen: "Review recommended plugins",
|
|
2293
|
+
guidanceDismiss: "Dismiss",
|
|
2294
|
+
opt01Title: "Worktree isolation",
|
|
2295
|
+
opt01Detail: "Creates local worktrees for /arena, /swarm, and /architect; the worktree tool changes the Session directory.",
|
|
2296
|
+
opt02Title: "GitHub PR workspace",
|
|
2297
|
+
opt02Detail: "PR panels, checks, reviews, ready state, and merge for /babysit, /shipping, and /autopilot-*; needs a logged-in gh.",
|
|
2298
|
+
opt03Title: "Browser verification",
|
|
2299
|
+
opt03Detail: "Browser navigation, input, screenshots, and ARIA snapshots; pick one provider and a supported model.",
|
|
2300
|
+
opt04Title: "Computer use",
|
|
2301
|
+
opt04Detail: "macOS desktop clicking, typing, screenshots, and permission pages in a PTC Session.",
|
|
2302
|
+
opt05Title: "Persistent terminal",
|
|
2303
|
+
opt05Detail: "Six global terminal tools keep a shell or REPL alive across calls for CLI and TUI verification.",
|
|
2304
|
+
opt06Title: "Session search",
|
|
2305
|
+
opt06Detail: "Five read-only tools search and trace earlier Sessions with the same cwd.",
|
|
2306
|
+
opt07Title: "MCP client",
|
|
2307
|
+
opt07Detail: "Mounts MCP servers as mcp__<server>__<tool> for /why and /teach evidence work.",
|
|
2308
|
+
opt08Title: "Agent Teams",
|
|
2309
|
+
opt08Detail: "Lead and teammate roles, a shared task board, and nine team tools; conflicts with pstack_subagent.",
|
|
2310
|
+
opt09Title: "Ralph loop",
|
|
2311
|
+
opt09Detail: "A foreground ralph loop starts a fresh child each round for /autonomous-run."
|
|
2312
|
+
},
|
|
2313
|
+
zh: {
|
|
2314
|
+
configureAction: "配置",
|
|
2315
|
+
configureTitle: "配置 dsh-pstack",
|
|
2316
|
+
configureSections: "配置分区",
|
|
2317
|
+
configurePluginsSection: "推荐插件",
|
|
2318
|
+
configureRolesSection: "角色配置",
|
|
2319
|
+
configureClose: "关闭",
|
|
2320
|
+
optionalPluginsTitle: "推荐插件",
|
|
2321
|
+
optionalPluginsSubtitle: "补全 pstack 流程的可选 DSH 插件。页面不会自动启用任何插件,只按需开启。",
|
|
2322
|
+
groupSessions: "会话与隔离",
|
|
2323
|
+
groupVerification: "验证与证据",
|
|
2324
|
+
groupMultiAgent: "多代理",
|
|
2325
|
+
recommendedTag: "推荐",
|
|
2326
|
+
suggestedTag: "GitHub PR 用户建议",
|
|
2327
|
+
conflictTag: "冲突",
|
|
2328
|
+
optionalLoading: "正在读取可选插件状态…",
|
|
2329
|
+
optionalLoadFailed: "无法读取可选插件状态:{message}",
|
|
2330
|
+
optionalRetry: "重试",
|
|
2331
|
+
optionalRefresh: "刷新",
|
|
2332
|
+
enableRecommended: "一键启用推荐项",
|
|
2333
|
+
enablingRecommended: "正在启用推荐项…",
|
|
2334
|
+
recommendedSummary: "成功 {applied} 项,需重启 {restart} 项,失败 {failed} 项,跳过 {skipped} 项",
|
|
2335
|
+
recommendedHint: "默认推荐 {ids}。",
|
|
2336
|
+
listSeparator: "、",
|
|
2337
|
+
enablePlugin: "启用",
|
|
2338
|
+
disablePlugin: "关闭",
|
|
2339
|
+
pluginWorking: "处理中…",
|
|
2340
|
+
pluginStatusEnabled: "已启用",
|
|
2341
|
+
pluginStatusOff: "未启用",
|
|
2342
|
+
pluginStatusMissing: "缺失",
|
|
2343
|
+
pluginStatusUnsupportedPlatform: "平台不支持",
|
|
2344
|
+
pluginStatusError: "错误",
|
|
2345
|
+
resultApplied: "已生效",
|
|
2346
|
+
resultRestartRequired: "需要重启",
|
|
2347
|
+
resultFailed: "失败:{reason}",
|
|
2348
|
+
resultOverridden: "被更高层覆盖",
|
|
2349
|
+
resultCancelled: "已取消",
|
|
2350
|
+
conflictWarning: "不要在同一 profile 中同时启用 Agent Teams 与 pstack_subagent:Agent Teams 会禁用 subagent 和 subagent_fork,并替换子代理控制。",
|
|
2351
|
+
nonBundleGuidance: "非 bundle 项:请按 DSH 文档配置,本页不提供开关。",
|
|
2352
|
+
guidanceTitle: "还有 {count} 个推荐插件未开启",
|
|
2353
|
+
guidanceOpen: "查看推荐插件",
|
|
2354
|
+
guidanceDismiss: "忽略",
|
|
2355
|
+
opt01Title: "工作树隔离",
|
|
2356
|
+
opt01Detail: "为 /arena、/swarm 和 /architect 创建本地工作树;worktree 工具会切换 Session 目录。",
|
|
2357
|
+
opt02Title: "GitHub PR 工作区",
|
|
2358
|
+
opt02Detail: "提供 PR 面板、checks、review、mark ready 与 merge,服务 /babysit、/shipping 和 /autopilot-*;需要已登录的 gh。",
|
|
2359
|
+
opt03Title: "浏览器验证",
|
|
2360
|
+
opt03Detail: "提供浏览器导航、输入、截图和 ARIA 快照;需选择一个提供方和受支持的模型。",
|
|
2361
|
+
opt04Title: "桌面操作",
|
|
2362
|
+
opt04Detail: "在 PTC Session 中操作 macOS 应用,支持点击、输入、截图与权限页。",
|
|
2363
|
+
opt05Title: "常驻终端",
|
|
2364
|
+
opt05Detail: "六个全局终端工具让 shell 或 REPL 跨调用保持存活,服务 CLI 与 TUI 验证。",
|
|
2365
|
+
opt06Title: "会话搜索",
|
|
2366
|
+
opt06Detail: "五个只读工具按相同 cwd 搜索和追踪历史 Session。",
|
|
2367
|
+
opt07Title: "MCP 客户端",
|
|
2368
|
+
opt07Detail: "挂载 MCP server,工具以 mcp__<server>__<tool> 出现在 /why 和 /teach 的证据调查中。",
|
|
2369
|
+
opt08Title: "Agent Teams",
|
|
2370
|
+
opt08Detail: "Lead/teammate 角色、共享任务板和九个团队工具;与 pstack_subagent 冲突。",
|
|
2371
|
+
opt09Title: "Ralph 循环",
|
|
2372
|
+
opt09Detail: "前台 ralph 循环每轮启动新子代理,服务 /autonomous-run。"
|
|
2373
|
+
}
|
|
2374
|
+
};
|
|
2375
|
+
// src/client/index.ts
|
|
2376
|
+
/** Browser services this plugin needs before `apply` runs. */
|
|
2377
|
+
const inject = [
|
|
2378
|
+
"remote",
|
|
2379
|
+
"slots",
|
|
2380
|
+
"locale",
|
|
2381
|
+
"remote.pluginManager"
|
|
2382
|
+
];
|
|
2383
|
+
/** Mount the Remote contribution and register the bundle configuration action and activation guidance. */
|
|
2384
|
+
async function apply(ctx) {
|
|
2385
|
+
const disposeRemote = await ctx.remote.$mount(pstackRolesRemote);
|
|
2386
|
+
ctx.effect(() => ctx.locale.register(NS$1, { ...catalogs$1 }), "pstack-roles.locale");
|
|
2387
|
+
ctx.effect(() => ctx.locale.register(NS, { ...catalogs }), "pstack-plugins.locale");
|
|
2388
|
+
const pluginManager = ctx.remote.pluginManager;
|
|
2389
|
+
const optionalPlugins = {
|
|
2390
|
+
listBundles: async () => {
|
|
2391
|
+
const result = await pluginManager.listBundles();
|
|
2392
|
+
if (!result.ok) throw result.error;
|
|
2393
|
+
return result.value;
|
|
2394
|
+
},
|
|
2395
|
+
setBundleEnabled: async (name, enabled) => {
|
|
2396
|
+
const result = await pluginManager.setBundleEnabled(name, enabled);
|
|
2397
|
+
if (!result.ok) throw result.error;
|
|
2398
|
+
return result.value;
|
|
2399
|
+
}
|
|
2400
|
+
};
|
|
2401
|
+
ctx.inject(["remote.pstackRoles"], (scope) => {
|
|
2402
|
+
const namespace = scope.remote.pstackRoles;
|
|
2403
|
+
const api = {
|
|
2404
|
+
status: async () => {
|
|
2405
|
+
const result = await namespace.status();
|
|
2406
|
+
if (!result.ok) throw result.error;
|
|
2407
|
+
return result.value;
|
|
2408
|
+
},
|
|
2409
|
+
catalogue: async () => {
|
|
2410
|
+
const result = await namespace.catalogue();
|
|
2411
|
+
if (!result.ok) throw result.error;
|
|
2412
|
+
return result.value;
|
|
2413
|
+
},
|
|
2414
|
+
save: async (request) => {
|
|
2415
|
+
const result = await namespace.save(request);
|
|
2416
|
+
if (!result.ok) return {
|
|
2417
|
+
ok: false,
|
|
2418
|
+
reason: result.error.message
|
|
2419
|
+
};
|
|
2420
|
+
return result.value;
|
|
2421
|
+
}
|
|
2422
|
+
};
|
|
2423
|
+
scope.slots.inject("plugins.detail.actions", () => scope.slots.register({
|
|
2424
|
+
name: "plugins.detail.actions",
|
|
2425
|
+
id: "dsh-pstack",
|
|
2426
|
+
locale: NS,
|
|
2427
|
+
inject: () => ({
|
|
2428
|
+
api,
|
|
2429
|
+
optionalPlugins,
|
|
2430
|
+
rolesT: ctx.locale.bind(NS$1)
|
|
2431
|
+
})
|
|
2432
|
+
}, PstackBundleConfigAction));
|
|
2433
|
+
scope.slots.inject("plugins.bundle.activation", () => scope.slots.register({
|
|
2434
|
+
name: "plugins.bundle.activation",
|
|
2435
|
+
key: "dsh-pstack",
|
|
2436
|
+
locale: NS,
|
|
2437
|
+
inject: () => ({ optionalPlugins })
|
|
2438
|
+
}, OptionalPluginsActivation));
|
|
2439
|
+
});
|
|
2440
|
+
return disposeRemote;
|
|
2441
|
+
}
|
|
2442
|
+
exports.apply = apply;
|
|
2443
|
+
exports.inject = inject;
|
|
2444
|
+
return module.exports;
|
|
2445
|
+
}
|
|
2446
|
+
});
|
|
2447
|
+
|
|
2448
|
+
//# sourceMappingURL=client.mjs.map
|