dsh-pstack 0.2.0 → 0.3.0-beta.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/dist/client.mjs CHANGED
@@ -7,6 +7,98 @@ window.__ModuleLoader__.load({
7
7
  let react = require("react");
8
8
  let react_jsx_runtime = require("react/jsx-runtime");
9
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
+ }
10
102
  // src/remote.ts
11
103
  /** npm package that owns these methods. */
12
104
  const REMOTE_PACKAGE = "dsh-pstack";
@@ -79,7 +171,7 @@ window.__ModuleLoader__.load({
79
171
  }
80
172
  function parseRoleView(value, index) {
81
173
  const record = parseRecord(value, `roles[${index}]`);
82
- if (typeof record["key"] !== "string") throw new TypeError(`roles[${index}].key must be a string`);
174
+ if (typeof record["key"] !== "string" || !isRoleKey(record["key"])) throw new TypeError(`roles[${index}].key is not in the role manifest`);
83
175
  if (record["shape"] !== "single" && record["shape"] !== "list") throw new TypeError(`roles[${index}].shape is unknown`);
84
176
  if (typeof record["configured"] !== "boolean") throw new TypeError(`roles[${index}].configured must be a boolean`);
85
177
  const routes = record["routes"];
@@ -247,19 +339,13 @@ window.__ModuleLoader__.load({
247
339
  ];
248
340
  /** The complete optional-plugin catalogue in documentation order. */
249
341
  const OPTIONAL_PLUGINS = [
250
- {
251
- id: "OPT-01",
252
- group: "sessions",
253
- packages: ["@deepseek-ai/dsh-experimental-worktree", "@deepseek-ai/dsh-experimental-tool-worktree"],
254
- bundle: "@deepseek-ai/dsh-experimental-tool-worktree",
255
- recommended: true
256
- },
257
342
  {
258
343
  id: "OPT-02",
259
344
  group: "sessions",
260
345
  packages: ["@deepseek-ai/dsh-experimental-forge-bundle"],
261
346
  bundle: "@deepseek-ai/dsh-experimental-forge-bundle",
262
- suggested: true
347
+ recommended: true,
348
+ exclusiveWith: ["@deepseek-ai/dsh-experimental-worktree", "@deepseek-ai/dsh-experimental-tool-worktree"]
263
349
  },
264
350
  {
265
351
  id: "OPT-03",
@@ -310,6 +396,10 @@ window.__ModuleLoader__.load({
310
396
  bundle: "@deepseek-ai/dsh-experimental-ralph-bundle"
311
397
  }
312
398
  ];
399
+ /** The default recommendation ids, in catalogue order. */
400
+ function defaultRecommendationIds() {
401
+ return OPTIONAL_PLUGINS.filter((plugin) => plugin.recommended === true).map((plugin) => plugin.id);
402
+ }
313
403
  // src/client/optional-plugins-state.ts
314
404
  /**
315
405
  * Pure state and small async operations for the optional-plugin recommendation
@@ -317,9 +407,22 @@ window.__ModuleLoader__.load({
317
407
  * browser tests cover every decision without mounting the Plugins page.
318
408
  *
319
409
  * The two wire shapes below mirror the public `BundleInfo` and `ChangeResult`
320
- * fields this page reads from `pluginManager` (DSH 0.2.2-alpha.2), the same way
410
+ * fields this page reads from `pluginManager` (DSH 0.2.2-alpha.3), the same way
321
411
  * `remote.ts` hand-writes the role Remote contract.
322
412
  */
413
+ /** Bundle names the Host currently reports as enabled. */
414
+ function enabledBundleNames(bundles) {
415
+ return new Set((bundles ?? []).filter((bundle) => bundle.enabled).map((bundle) => bundle.name));
416
+ }
417
+ /**
418
+ * Mutual-exclusion bundles of one row that are already enabled. The page must
419
+ * not switch this row on while any of them stays on (for example the standalone
420
+ * worktree bundle next to the unified forge bundle).
421
+ */
422
+ function conflictingEnabledBundles(definition, bundles) {
423
+ const enabled = enabledBundleNames(bundles);
424
+ return (definition.exclusiveWith ?? []).filter((name) => enabled.has(name));
425
+ }
323
426
  /** Read one bundle's display status, including platform and file failures. */
324
427
  function optionalPluginStatus(bundle) {
325
428
  if (bundle === void 0) return "missing";
@@ -334,11 +437,13 @@ window.__ModuleLoader__.load({
334
437
  return OPTIONAL_PLUGINS.map((definition) => {
335
438
  const bundle = definition.bundle === void 0 ? void 0 : byName.get(definition.bundle);
336
439
  const status = definition.bundle === void 0 ? "missing" : optionalPluginStatus(bundle);
440
+ const blockedBy = conflictingEnabledBundles(definition, bundles);
337
441
  return {
338
442
  definition,
339
443
  status,
340
444
  enabled: status === "enabled",
341
- canToggle: definition.bundle !== void 0 && definition.switchable !== false && (status === "off" || status === "enabled")
445
+ canToggle: definition.bundle !== void 0 && definition.switchable !== false && (status === "enabled" || status === "off" && blockedBy.length === 0),
446
+ blockedBy
342
447
  };
343
448
  });
344
449
  }
@@ -363,6 +468,16 @@ window.__ModuleLoader__.load({
363
468
  skipped += 1;
364
469
  continue;
365
470
  }
471
+ if (row.blockedBy.length > 0) {
472
+ outcomes.push({
473
+ id: row.definition.id,
474
+ bundle: row.definition.bundle,
475
+ application: "skipped",
476
+ blockedBy: row.blockedBy
477
+ });
478
+ skipped += 1;
479
+ continue;
480
+ }
366
481
  try {
367
482
  const result = await api.setBundleEnabled(row.definition.bundle, true);
368
483
  outcomes.push({
@@ -444,10 +559,6 @@ window.__ModuleLoader__.load({
444
559
  "multi-agent": "groupMultiAgent"
445
560
  };
446
561
  const COPY = {
447
- "OPT-01": {
448
- title: "opt01Title",
449
- detail: "opt01Detail"
450
- },
451
562
  "OPT-02": {
452
563
  title: "opt02Title",
453
564
  detail: "opt02Detail"
@@ -516,6 +627,7 @@ window.__ModuleLoader__.load({
516
627
  function changeResultText(application, t, reason) {
517
628
  if (application === "applied") return t("resultApplied");
518
629
  if (application === "restart-required") return t("resultRestartRequired");
630
+ if (application === "skipped") return t("resultSkippedConflict", { bundles: reason ?? "" });
519
631
  if (application === "failed" || application === "error") return t("resultFailed", { reason: reason ?? application });
520
632
  if (application === "overridden") return t("resultOverridden");
521
633
  return t("resultCancelled");
@@ -527,6 +639,7 @@ window.__ModuleLoader__.load({
527
639
  /** Render the recommendation section; all decisions come from props. */
528
640
  function OptionalPluginsView(props) {
529
641
  const { rows, status, loadError, busyId, bulkBusy = false, messages, summary, onToggle, onEnableRecommended, onRetry, t } = props;
642
+ const recommendationIds = defaultRecommendationIds();
530
643
  const offRecommended = rows.filter((row) => row.definition.recommended === true && row.status === "off").length;
531
644
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
532
645
  "data-optional-plugins": true,
@@ -600,7 +713,7 @@ window.__ModuleLoader__.load({
600
713
  color: COLOR_CAPTION$1,
601
714
  fontSize: 12
602
715
  },
603
- children: t("recommendedHint")
716
+ children: t("recommendedHint", { ids: recommendationIds.join(t("listSeparator")) })
604
717
  })
605
718
  ]
606
719
  }),
@@ -684,10 +797,6 @@ window.__ModuleLoader__.load({
684
797
  style: tagStyle(COLOR_SUCCESS$1),
685
798
  children: t("recommendedTag")
686
799
  }) : null,
687
- definition.suggested === true ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
688
- style: tagStyle(COLOR_SECONDARY$1),
689
- children: t("suggestedTag")
690
- }) : null,
691
800
  definition.conflict === true ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
692
801
  style: tagStyle(COLOR_ERROR$1),
693
802
  children: t("conflictTag")
@@ -722,6 +831,16 @@ window.__ModuleLoader__.load({
722
831
  },
723
832
  children: t("conflictWarning")
724
833
  }) : null,
834
+ row.blockedBy.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
835
+ role: "alert",
836
+ style: {
837
+ margin: 0,
838
+ color: COLOR_ERROR$1,
839
+ fontSize: 12,
840
+ lineHeight: 1.5
841
+ },
842
+ children: t("mutexWarning", { bundles: row.blockedBy.join(t("listSeparator")) })
843
+ }) : null,
725
844
  nonBundle ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
726
845
  style: {
727
846
  margin: 0,
@@ -796,8 +915,19 @@ window.__ModuleLoader__.load({
796
915
  const rows = optionalPluginRows(bundles);
797
916
  const reload = () => setReloadToken((value) => value + 1);
798
917
  const toggle = (id, enabled) => {
799
- const bundle = rows.find((candidate) => candidate.definition.id === id)?.definition.bundle;
800
- if (bundle === void 0) return;
918
+ const row = rows.find((candidate) => candidate.definition.id === id);
919
+ const bundle = row?.definition.bundle;
920
+ if (row === void 0 || bundle === void 0) return;
921
+ if (enabled && row.blockedBy.length > 0) {
922
+ setMessages((current) => ({
923
+ ...current,
924
+ [id]: {
925
+ tone: "error",
926
+ text: t("resultSkippedConflict", { bundles: row.blockedBy.join(t("listSeparator")) })
927
+ }
928
+ }));
929
+ return;
930
+ }
801
931
  setBusyId(id);
802
932
  setMessages((current) => {
803
933
  const { [id]: _ignored, ...rest } = current;
@@ -831,10 +961,13 @@ window.__ModuleLoader__.load({
831
961
  setMessages({});
832
962
  enableRecommendedPlugins(api, rows).then((result) => {
833
963
  const next = {};
834
- for (const outcome of result.outcomes) next[outcome.id] = {
835
- tone: outcome.application === "failed" || outcome.application === "error" ? "error" : "ok",
836
- text: changeResultText(outcome.application, t)
837
- };
964
+ for (const outcome of result.outcomes) {
965
+ const blocked = outcome.application === "skipped";
966
+ next[outcome.id] = {
967
+ tone: outcome.application === "failed" || outcome.application === "error" || blocked ? "error" : "ok",
968
+ text: changeResultText(outcome.application, t, blocked ? (outcome.blockedBy ?? []).join(t("listSeparator")) : void 0)
969
+ };
970
+ }
838
971
  setMessages(next);
839
972
  setSummary(t("recommendedSummary", {
840
973
  applied: result.applied,
@@ -1222,10 +1355,7 @@ window.__ModuleLoader__.load({
1222
1355
  /** Localized role name and usage, keyed by the exact role-file key. */
1223
1356
  function rolePresentation(key, t) {
1224
1357
  const copy = roleCopyKeys[key];
1225
- return copy === void 0 ? {
1226
- name: key,
1227
- description: ""
1228
- } : {
1358
+ return {
1229
1359
  name: t(copy.name),
1230
1360
  description: t(copy.description)
1231
1361
  };
@@ -1257,6 +1387,17 @@ window.__ModuleLoader__.load({
1257
1387
  color: primary ? "var(--dsw-alias-label-primary-foreground)" : COLOR_TEXT$1
1258
1388
  };
1259
1389
  }
1390
+ function dangerButtonStyle() {
1391
+ return {
1392
+ border: `1px solid color-mix(in srgb, ${COLOR_ERROR} 55%, transparent)`,
1393
+ borderRadius: 8,
1394
+ padding: "5px 12px",
1395
+ cursor: "pointer",
1396
+ font: "inherit",
1397
+ background: `color-mix(in srgb, ${COLOR_ERROR} 10%, transparent)`,
1398
+ color: COLOR_ERROR
1399
+ };
1400
+ }
1260
1401
  function fieldStyle(basis, fullWidth = false) {
1261
1402
  return {
1262
1403
  flex: fullWidth ? "1 1 auto" : `1 1 ${basis}px`,
@@ -1414,7 +1555,7 @@ window.__ModuleLoader__.load({
1414
1555
  routes.length > 1 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1415
1556
  type: "button",
1416
1557
  style: {
1417
- ...buttonStyle(),
1558
+ ...dangerButtonStyle(),
1418
1559
  justifySelf: "start"
1419
1560
  },
1420
1561
  onClick: () => onRemove(index),
@@ -2157,7 +2298,6 @@ window.__ModuleLoader__.load({
2157
2298
  groupVerification: "Verification and evidence",
2158
2299
  groupMultiAgent: "Multi-agent",
2159
2300
  recommendedTag: "Recommended",
2160
- suggestedTag: "GitHub PR suggestion",
2161
2301
  conflictTag: "Conflict",
2162
2302
  optionalLoading: "Loading optional plugin status…",
2163
2303
  optionalLoadFailed: "Could not read optional plugin status: {message}",
@@ -2166,7 +2306,8 @@ window.__ModuleLoader__.load({
2166
2306
  enableRecommended: "Enable recommended plugins",
2167
2307
  enablingRecommended: "Enabling recommended plugins…",
2168
2308
  recommendedSummary: "{applied} applied, {restart} need a restart, {failed} failed, {skipped} skipped",
2169
- recommendedHint: "Default recommendations: OPT-01, OPT-06.",
2309
+ recommendedHint: "Default recommendations: {ids}.",
2310
+ listSeparator: ", ",
2170
2311
  enablePlugin: "Enable",
2171
2312
  disablePlugin: "Disable",
2172
2313
  pluginWorking: "Working…",
@@ -2180,15 +2321,15 @@ window.__ModuleLoader__.load({
2180
2321
  resultFailed: "Failed: {reason}",
2181
2322
  resultOverridden: "Overridden by a higher layer",
2182
2323
  resultCancelled: "Cancelled",
2324
+ resultSkippedConflict: "Skipped: turn off {bundles} first",
2183
2325
  conflictWarning: "Do not enable Agent Teams and pstack_subagent in one profile: Agent Teams disables subagent and subagent_fork and replaces the child controls.",
2184
2326
  nonBundleGuidance: "Not a bundle: configure it in the DSH documentation; this page cannot switch it.",
2327
+ mutexWarning: "This conflicts with {bundles}; turn it off first.",
2185
2328
  guidanceTitle: "{count} recommended plugins are still off",
2186
2329
  guidanceOpen: "Review recommended plugins",
2187
2330
  guidanceDismiss: "Dismiss",
2188
- opt01Title: "Worktree isolation",
2189
- opt01Detail: "Creates local worktrees for /arena, /swarm, and /architect; the worktree tool changes the Session directory.",
2190
- opt02Title: "GitHub PR workspace",
2191
- opt02Detail: "PR panels, checks, reviews, ready state, and merge for /babysit, /shipping, and /autopilot-*; needs a logged-in gh.",
2331
+ opt02Title: "Change requests & worktrees",
2332
+ opt02Detail: "One switch for PR panels, checks, reviews, ready state, merge, worktree Sessions, and the model's create_worktree tool for /babysit, /shipping, and /autopilot-*; needs a logged-in gh for GitHub status.",
2192
2333
  opt03Title: "Browser verification",
2193
2334
  opt03Detail: "Browser navigation, input, screenshots, and ARIA snapshots; pick one provider and a supported model.",
2194
2335
  opt04Title: "Computer use",
@@ -2217,7 +2358,6 @@ window.__ModuleLoader__.load({
2217
2358
  groupVerification: "验证与证据",
2218
2359
  groupMultiAgent: "多代理",
2219
2360
  recommendedTag: "推荐",
2220
- suggestedTag: "GitHub PR 用户建议",
2221
2361
  conflictTag: "冲突",
2222
2362
  optionalLoading: "正在读取可选插件状态…",
2223
2363
  optionalLoadFailed: "无法读取可选插件状态:{message}",
@@ -2226,7 +2366,8 @@ window.__ModuleLoader__.load({
2226
2366
  enableRecommended: "一键启用推荐项",
2227
2367
  enablingRecommended: "正在启用推荐项…",
2228
2368
  recommendedSummary: "成功 {applied} 项,需重启 {restart} 项,失败 {failed} 项,跳过 {skipped} 项",
2229
- recommendedHint: "默认推荐 OPT-01、OPT-06。",
2369
+ recommendedHint: "默认推荐 {ids}。",
2370
+ listSeparator: "、",
2230
2371
  enablePlugin: "启用",
2231
2372
  disablePlugin: "关闭",
2232
2373
  pluginWorking: "处理中…",
@@ -2240,15 +2381,15 @@ window.__ModuleLoader__.load({
2240
2381
  resultFailed: "失败:{reason}",
2241
2382
  resultOverridden: "被更高层覆盖",
2242
2383
  resultCancelled: "已取消",
2384
+ resultSkippedConflict: "已跳过:请先关闭 {bundles}",
2243
2385
  conflictWarning: "不要在同一 profile 中同时启用 Agent Teams 与 pstack_subagent:Agent Teams 会禁用 subagent 和 subagent_fork,并替换子代理控制。",
2244
2386
  nonBundleGuidance: "非 bundle 项:请按 DSH 文档配置,本页不提供开关。",
2387
+ mutexWarning: "此项与 {bundles} 冲突;请先关闭它。",
2245
2388
  guidanceTitle: "还有 {count} 个推荐插件未开启",
2246
2389
  guidanceOpen: "查看推荐插件",
2247
2390
  guidanceDismiss: "忽略",
2248
- opt01Title: "工作树隔离",
2249
- opt01Detail: "为 /arena、/swarm 和 /architect 创建本地工作树;worktree 工具会切换 Session 目录。",
2250
- opt02Title: "GitHub PR 工作区",
2251
- opt02Detail: "提供 PR 面板、checks、review、mark ready 与 merge,服务 /babysit、/shipping 和 /autopilot-*;需要已登录的 gh。",
2391
+ opt02Title: "Change requests & worktrees",
2392
+ opt02Detail: "同一开关提供 PR 面板、checks、review、mark ready、merge、工作树 Session 与模型侧 create_worktree 工具,服务 /babysit、/shipping 和 /autopilot-*;GitHub 状态需要已登录的 gh。",
2252
2393
  opt03Title: "浏览器验证",
2253
2394
  opt03Detail: "提供浏览器导航、输入、截图和 ARIA 快照;需选择一个提供方和受支持的模型。",
2254
2395
  opt04Title: "桌面操作",