@dsh-workbench/core 0.3.10 → 0.3.11

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.
Files changed (82) hide show
  1. package/README.md +19 -7
  2. package/bin/dsh-workbench +2 -2
  3. package/client.js +1648 -973
  4. package/cordis.patch.yml +60 -35
  5. package/dist/client/client.body.js +1648 -973
  6. package/dist/client/index.js +1333 -658
  7. package/dist/host/index.js +2896 -2334
  8. package/dist/types/app/client.d.ts +2 -1
  9. package/dist/types/app/layouts/env-gate/boot-gate.d.ts +1 -1
  10. package/dist/types/app/layouts/market/use-module-versions.d.ts +13 -2
  11. package/dist/types/app/layouts/rightbar/MarketView.d.ts +6 -3
  12. package/dist/types/app/layouts/rightbar/market-act.d.ts +6 -3
  13. package/dist/types/app/layouts/rightbar/market-settle-hold.d.ts +2 -0
  14. package/dist/types/app/layouts/rightbar/market-settle.d.ts +20 -0
  15. package/dist/types/app/layouts/rightbar/market-update-verdict.d.ts +41 -0
  16. package/dist/types/app/layouts/rightbar/market-view-text.d.ts +4 -1
  17. package/dist/types/app/layouts/rightbar/store.d.ts +14 -0
  18. package/dist/types/app/layouts/sidebar/LongTaskDot.d.ts +12 -0
  19. package/dist/types/app/layouts/sidebar/LongTaskHover.d.ts +12 -0
  20. package/dist/types/app/layouts/sidebar/edition-boot.d.ts +24 -0
  21. package/dist/types/app/layouts/sidebar/edition.d.ts +22 -0
  22. package/dist/types/app/layouts/workbench-settings/ConfigSyncItem.d.ts +5 -0
  23. package/dist/types/app/layouts/workbench-settings/SettingsItem.d.ts +17 -0
  24. package/dist/types/app/layouts/workbench-settings/SidebarEditionItem.d.ts +11 -0
  25. package/dist/types/app/layouts/workbench-settings/WorkbenchSettingsSection.d.ts +11 -0
  26. package/dist/types/modules/panels/model/approval.d.ts +12 -4
  27. package/dist/types/modules/panels/model/diy-browser-tools.d.ts +1 -1
  28. package/dist/types/modules/panels/model/diy-context.d.ts +31 -14
  29. package/dist/types/modules/panels/model/diy-page.d.ts +3 -2
  30. package/dist/types/modules/panels/model/diy-record.d.ts +3 -3
  31. package/dist/types/modules/panels/model/diy-report.d.ts +6 -7
  32. package/dist/types/modules/panels/model/market-dependency-update.d.ts +21 -0
  33. package/dist/types/modules/panels/model/market-dispatch.d.ts +1 -0
  34. package/dist/types/modules/panels/model/market-types.d.ts +1 -0
  35. package/dist/types/modules/panels/model/market-update-bundle.d.ts +25 -0
  36. package/dist/types/modules/panels/model/market-verify.d.ts +31 -0
  37. package/dist/types/modules/panels/unit.d.ts +3 -4
  38. package/dist/types/shared/api/bundle-install.d.ts +13 -0
  39. package/dist/types/shared/api/host/approval-gate.d.ts +39 -0
  40. package/dist/types/shared/api/host/bundle-routes.d.ts +2 -1
  41. package/dist/types/shared/api/host/diy-artifacts.d.ts +14 -0
  42. package/dist/types/shared/api/host/diy-modules.d.ts +41 -0
  43. package/dist/types/shared/api/host/host-routes.d.ts +13 -0
  44. package/dist/types/shared/api/host/permission-mode.d.ts +9 -0
  45. package/dist/types/shared/api/host/runtime-seed.d.ts +2 -0
  46. package/dist/types/shared/api/host/self-update.d.ts +9 -2
  47. package/dist/types/shared/api/host/sidebar-edition.d.ts +46 -0
  48. package/dist/types/shared/api/host/workspace-root.d.ts +13 -0
  49. package/dist/types/shared/api/sidebar-edition.d.ts +14 -0
  50. package/dist/types/shared/api/unit-board.d.ts +20 -0
  51. package/dist/types/shared/api/write-outcome.d.ts +15 -0
  52. package/dist/types/shared/components/IconModules.d.ts +8 -0
  53. package/dist/types/shared/components/blocker-action.d.ts +16 -0
  54. package/dist/types/shared/hooks/sidebar-edition.d.ts +7 -0
  55. package/dist/types/shared/hooks/use-capabilities.d.ts +5 -0
  56. package/dist/types/shared/hooks/use-capability-gate.d.ts +1 -2
  57. package/dist/types/shared/hooks/use-host-write.d.ts +1 -1
  58. package/dist/types/shared/hooks/use-unit-board.d.ts +1 -1
  59. package/docs/HANDOFF.md +3 -3
  60. package/docs/OPERATIONS.md +16 -5
  61. package/package.json +3 -3
  62. package/preset/diy.patch.yml +2 -2
  63. package/profile/isolation.patch.yml +11 -0
  64. package/skills/workbench-module-diy/BUILD.md +62 -131
  65. package/skills/workbench-module-diy/COPY.md +89 -0
  66. package/skills/workbench-module-diy/DATASOURCE.md +84 -112
  67. package/skills/workbench-module-diy/DESIGN.md +75 -109
  68. package/skills/workbench-module-diy/FAMILY.md +70 -54
  69. package/skills/workbench-module-diy/KIT.md +77 -133
  70. package/skills/workbench-module-diy/LINKAGE.md +66 -105
  71. package/skills/workbench-module-diy/PANEL.md +79 -171
  72. package/skills/workbench-module-diy/REVIEW.md +67 -81
  73. package/skills/workbench-module-diy/SKILL.md +57 -96
  74. package/skills/workbench-module-diy/STEPS.md +66 -69
  75. package/skills/workbench-module-diy/TOOLING.md +34 -51
  76. package/skills/workbench-module-diy/TOOLS.md +110 -103
  77. package/skills/workbench-module-diy/WIRING.md +51 -83
  78. package/skills/workbench-module-diy/WRITES.md +89 -142
  79. package/dist/types/app/layouts/config-sync/ConfigSyncSection.d.ts +0 -5
  80. package/dist/types/shared/api/host/diy-workshop.d.ts +0 -23
  81. package/dist/types/shared/api/host/workshop-root.d.ts +0 -16
  82. /package/dist/types/shared/{api/host/diy-workshop.test.d.ts → components/blocker-action.test.d.ts} +0 -0
@@ -0,0 +1,31 @@
1
+ import type { BundleChange } from '../../../shared/api/bundle-install';
2
+ import type { InstalledModule } from '../../../shared/api/host/installed-module';
3
+ import type { UnitCapability } from '../../../shared/api/host/probe-units';
4
+ import type { DependencyTarget } from './market-dependency';
5
+ /**
6
+ * 更新跑完拿盘上的真实结果核对一次:dsh 回 `applied` 只代表"这一跳跑完了",不代表版本真的动了
7
+ * (源上给的版本可能不是最新、`pnpm` 也可能按供应策略暂缓)。判据是**点下去那一版 vs 盘上这一版**:
8
+ * 没动就是 `no-change`,装完读不到模块就是 `module-gone`;两种情况都会把红字交给界面,而不是假装成功。
9
+ * @param input.change 管理器回的那一跳;`before` 点下去之前盘上的版本;`after` 跑完再读的盘上结果。
10
+ * @param input.restored 装丢之后回滚成功时装回去的版本(失败的那一跳仍然是失败)。
11
+ * @returns 校正后的 `change`(成功时带 `version` = 盘上实际版本)与 `restored`。
12
+ */
13
+ export declare function verifyBundleUpdate(input: {
14
+ readonly change: BundleChange;
15
+ readonly before: string | undefined;
16
+ readonly after: InstalledModule;
17
+ readonly restored?: string | undefined;
18
+ }): {
19
+ readonly change: BundleChange;
20
+ readonly restored?: string;
21
+ };
22
+ /**
23
+ * 命令那一跳(升 opencli 插件 / 库)跑完还落后时给一句人话:`undefined` = 真追上。
24
+ * @param after 跑完重新探到的能力格;只认它自己的 `fix === 'update'`,别的失败不在这里下结论。
25
+ */
26
+ export declare function supplyUpdateStillBehind(after: UnitCapability): string | undefined;
27
+ /**
28
+ * 共用依赖行更新完还落后(插件 `outdated` 或库 `outdated` / `unsatisfied`)时给一句人话。
29
+ * @param target 这一行代表的那份依赖;`capabilities` 是跑完后**强探**过的那批声明者。
30
+ */
31
+ export declare function dependencyStillBehind(target: DependencyTarget, capabilities: ReadonlyMap<string, UnitCapability>): string | undefined;
@@ -14,7 +14,6 @@ export declare const MODULES_UPDATE_DEPENDENCY_TOOL = "workbench_modules_update_
14
14
  export declare const MODULES_UPDATE_PLUGIN_TOOL = "workbench_modules_update_plugin";
15
15
  export declare const MODULES_UNINSTALL_PLUGIN_TOOL = "workbench_modules_uninstall_plugin";
16
16
  export declare const MODULES_UPDATE_WORKBENCH_TOOL = "workbench_modules_update_workbench";
17
- export declare const DIY_LIST_TOOL = "workbench_diy_list";
18
17
  export declare const DIY_STATUS_TOOL = "workbench_diy_status";
19
18
  export declare const DIY_PANEL_TOOL = "workbench_diy_panel";
20
19
  export declare const DIY_COMPONENTS_TOOL = "workbench_diy_components";
@@ -49,11 +48,11 @@ export declare const DIY_DEBUG_START_TOOL = "workbench_diy_debug_start";
49
48
  export declare const DIY_DEBUG_PAUSE_TOOL = "workbench_diy_debug_pause";
50
49
  export declare const DIY_DEBUG_RESUME_TOOL = "workbench_diy_debug_resume";
51
50
  export declare const DIY_DEBUG_STOP_TOOL = "workbench_diy_debug_stop";
52
- export declare const ALL_TOOLS: readonly ["workbench_panels_list", "workbench_panels_show", "workbench_panels_hide", "workbench_panels_move", "workbench_panels_reset", "workbench_panels_follow", "workbench_modules_list", "workbench_modules_install", "workbench_modules_update", "workbench_modules_uninstall", "workbench_modules_install_bundle", "workbench_modules_update_dependency", "workbench_modules_update_plugin", "workbench_modules_uninstall_plugin", "workbench_modules_update_workbench", "workbench_diy_list", "workbench_diy_status", "workbench_diy_panel", "workbench_diy_components", "workbench_diy_recon", "workbench_diy_spec", "workbench_diy_install", "workbench_diy_show", "workbench_diy_state", "workbench_diy_html", "workbench_diy_find", "workbench_diy_click", "workbench_diy_hover", "workbench_diy_type", "workbench_diy_fill", "workbench_diy_keys", "workbench_diy_scroll", "workbench_diy_wait", "workbench_diy_eval", "workbench_diy_screenshot", "workbench_diy_network", "workbench_diy_dialog", "workbench_diy_logs_watch", "workbench_diy_logs_read", "workbench_diy_logs_stop", "workbench_diy_record_prepare", "workbench_diy_record_read", "workbench_diy_record_detail", "workbench_diy_record_stop", "workbench_diy_debug_start", "workbench_diy_debug_pause", "workbench_diy_debug_resume", "workbench_diy_debug_stop", "workbench_long_tasks_list", "workbench_long_tasks_stop"];
51
+ export declare const ALL_TOOLS: readonly ["workbench_panels_list", "workbench_panels_show", "workbench_panels_hide", "workbench_panels_move", "workbench_panels_reset", "workbench_panels_follow", "workbench_modules_list", "workbench_modules_install", "workbench_modules_update", "workbench_modules_uninstall", "workbench_modules_install_bundle", "workbench_modules_update_dependency", "workbench_modules_update_plugin", "workbench_modules_uninstall_plugin", "workbench_modules_update_workbench", "workbench_diy_status", "workbench_diy_panel", "workbench_diy_components", "workbench_diy_recon", "workbench_diy_spec", "workbench_diy_install", "workbench_diy_show", "workbench_diy_state", "workbench_diy_html", "workbench_diy_find", "workbench_diy_click", "workbench_diy_hover", "workbench_diy_type", "workbench_diy_fill", "workbench_diy_keys", "workbench_diy_scroll", "workbench_diy_wait", "workbench_diy_eval", "workbench_diy_screenshot", "workbench_diy_network", "workbench_diy_dialog", "workbench_diy_logs_watch", "workbench_diy_logs_read", "workbench_diy_logs_stop", "workbench_diy_record_prepare", "workbench_diy_record_read", "workbench_diy_record_detail", "workbench_diy_record_stop", "workbench_diy_debug_start", "workbench_diy_debug_pause", "workbench_diy_debug_resume", "workbench_diy_debug_stop", "workbench_long_tasks_list", "workbench_long_tasks_stop"];
53
52
  export type PanelsToolName = (typeof ALL_TOOLS)[number];
54
- export declare const READ_TOOLS: readonly ["workbench_panels_list", "workbench_modules_list", "workbench_diy_list", "workbench_diy_status", "workbench_diy_panel", "workbench_diy_components", "workbench_diy_recon", "workbench_diy_spec", "workbench_diy_state", "workbench_diy_html", "workbench_diy_find", "workbench_diy_click", "workbench_diy_hover", "workbench_diy_type", "workbench_diy_fill", "workbench_diy_keys", "workbench_diy_scroll", "workbench_diy_wait", "workbench_diy_eval", "workbench_diy_screenshot", "workbench_diy_network", "workbench_diy_dialog", "workbench_diy_logs_watch", "workbench_diy_logs_read", "workbench_diy_logs_stop", "workbench_diy_record_read", "workbench_diy_record_detail", "workbench_diy_record_stop", "workbench_long_tasks_list"];
53
+ export declare const READ_TOOLS: readonly ["workbench_panels_list", "workbench_modules_list", "workbench_diy_status", "workbench_diy_panel", "workbench_diy_components", "workbench_diy_recon", "workbench_diy_spec", "workbench_diy_state", "workbench_diy_html", "workbench_diy_find", "workbench_diy_click", "workbench_diy_hover", "workbench_diy_type", "workbench_diy_fill", "workbench_diy_keys", "workbench_diy_scroll", "workbench_diy_wait", "workbench_diy_eval", "workbench_diy_screenshot", "workbench_diy_network", "workbench_diy_dialog", "workbench_diy_logs_watch", "workbench_diy_logs_read", "workbench_diy_logs_stop", "workbench_diy_record_read", "workbench_diy_record_detail", "workbench_diy_record_stop", "workbench_long_tasks_list"];
55
54
  export declare const WRITE_TOOLS: readonly ["workbench_panels_show", "workbench_panels_hide", "workbench_panels_move", "workbench_panels_reset", "workbench_panels_follow", "workbench_modules_install", "workbench_modules_update", "workbench_modules_uninstall", "workbench_modules_install_bundle", "workbench_modules_update_dependency", "workbench_modules_update_plugin", "workbench_modules_uninstall_plugin", "workbench_modules_update_workbench", "workbench_diy_install", "workbench_diy_show", "workbench_diy_record_prepare", "workbench_diy_debug_start", "workbench_diy_debug_pause", "workbench_diy_debug_resume", "workbench_diy_debug_stop", "workbench_long_tasks_stop"];
56
- export declare const DIY_TOOLS: readonly ["workbench_diy_list", "workbench_diy_status", "workbench_diy_panel", "workbench_diy_components", "workbench_diy_recon", "workbench_diy_spec", "workbench_diy_install", "workbench_diy_show", "workbench_diy_state", "workbench_diy_html", "workbench_diy_find", "workbench_diy_click", "workbench_diy_hover", "workbench_diy_type", "workbench_diy_fill", "workbench_diy_keys", "workbench_diy_scroll", "workbench_diy_wait", "workbench_diy_eval", "workbench_diy_screenshot", "workbench_diy_network", "workbench_diy_dialog", "workbench_diy_logs_watch", "workbench_diy_logs_read", "workbench_diy_logs_stop"];
55
+ export declare const DIY_TOOLS: readonly ["workbench_diy_status", "workbench_diy_panel", "workbench_diy_components", "workbench_diy_recon", "workbench_diy_spec", "workbench_diy_install", "workbench_diy_show", "workbench_diy_state", "workbench_diy_html", "workbench_diy_find", "workbench_diy_click", "workbench_diy_hover", "workbench_diy_type", "workbench_diy_fill", "workbench_diy_keys", "workbench_diy_scroll", "workbench_diy_wait", "workbench_diy_eval", "workbench_diy_screenshot", "workbench_diy_network", "workbench_diy_dialog", "workbench_diy_logs_watch", "workbench_diy_logs_read", "workbench_diy_logs_stop"];
57
56
  export declare const DIY_RECORD_TOOLS: readonly ["workbench_diy_record_prepare", "workbench_diy_record_read", "workbench_diy_record_detail", "workbench_diy_record_stop"];
58
57
  export declare const DIY_DEBUG_TOOLS: readonly ["workbench_diy_debug_start", "workbench_diy_debug_pause", "workbench_diy_debug_resume", "workbench_diy_debug_stop"];
59
58
  export declare const panelsUnit: UnitManifest;
@@ -42,6 +42,17 @@ export interface BundleInspectionPayload {
42
42
  readonly inspection: BundleInspection;
43
43
  }
44
44
  export type BundleApplication = 'applied' | 'restart-required' | 'overridden' | 'failed' | 'cancelled';
45
+ export declare const SELF_UPDATE_STATES: readonly ["running", "ok", "rolled-back", "failed"];
46
+ export type SelfUpdateState = (typeof SELF_UPDATE_STATES)[number];
47
+ export interface WorkbenchSelfUpdate {
48
+ readonly state: SelfUpdateState;
49
+ readonly profile?: string;
50
+ readonly from?: string;
51
+ readonly to?: string;
52
+ readonly version?: string;
53
+ readonly at?: string;
54
+ readonly tail?: string;
55
+ }
45
56
  export declare const RESTART_REASONS: readonly ["no-hmr", "package-moved", "host-module", "unmanaged-plugin", "module-comparison"];
46
57
  export type RestartReason = (typeof RESTART_REASONS)[number];
47
58
  export type BundleChangeStage = 'install' | 'enable' | 'remove';
@@ -59,6 +70,7 @@ export interface BundleChange {
59
70
  readonly changed: boolean;
60
71
  readonly target: string;
61
72
  readonly bundle?: string;
73
+ readonly version?: string;
62
74
  readonly errorCode?: string;
63
75
  readonly errorDetail?: string;
64
76
  readonly packageResult?: BundlePackageResult;
@@ -120,4 +132,5 @@ export interface WorkbenchVersion {
120
132
  export interface ModuleVersionsPayload {
121
133
  readonly modules: readonly ModuleVersion[];
122
134
  readonly workbench?: WorkbenchVersion;
135
+ readonly selfUpdate?: WorkbenchSelfUpdate;
123
136
  }
@@ -0,0 +1,39 @@
1
+ import type { DshHostContext, DshToolRunContext } from '@dsh-workbench/shared';
2
+ import { type ToolUnit, type UnitMountResult } from '@dsh-workbench/shared/host';
3
+ /**
4
+ * dsh 交来的 `tools/pre-execute` 现场:工具名、实参与发起它的 agent。
5
+ */
6
+ export interface ApprovalGateExec {
7
+ readonly name?: string;
8
+ readonly arguments?: unknown;
9
+ readonly agent?: DshToolRunContext['agent'];
10
+ }
11
+ /**
12
+ * 外壳这一拍能交出的判定:与 dsh 的 pre-execute 判定同形。
13
+ */
14
+ export type ApprovalGateDecision = {
15
+ readonly kind: 'allow';
16
+ } | {
17
+ readonly kind: 'deny';
18
+ readonly reason: string;
19
+ } | {
20
+ readonly kind: 'cancel';
21
+ } | {
22
+ readonly kind: 'ask';
23
+ readonly reason?: string;
24
+ readonly displayReason?: Record<string, string>;
25
+ };
26
+ /**
27
+ * 造审批监听器要从当前挂载表现取的三件事:这把工具属于哪个套件、那个套件的面、以及哪些工具声明了附注。
28
+ */
29
+ export interface ApprovalGateDeps {
30
+ readonly ctx: DshHostContext;
31
+ readonly toolOf: (name: string) => ToolUnit | undefined;
32
+ readonly faceOf: (unit: string) => UnitMountResult | undefined;
33
+ readonly declaredNotes: () => ReadonlySet<string>;
34
+ }
35
+ /**
36
+ * 造 `tools/pre-execute` 的监听器:先让后面那道闸判,只有它说 allow 才轮到外壳的写审批。
37
+ * 完全权限下不再把 `ask` 交出去(dsh 的 `never` 会直接拒),认领与 `{blocked}` 仍由 `approvalFor` 守。
38
+ */
39
+ export declare function createApprovalGate(deps: ApprovalGateDeps): (exec: ApprovalGateExec, next: () => Promise<ApprovalGateDecision>) => Promise<ApprovalGateDecision>;
@@ -1,7 +1,8 @@
1
1
  import type { DshHostContext, DshShell } from '@dsh-workbench/shared';
2
2
  import { type RouteHandler } from '@dsh-workbench/shared/host';
3
+ import { type WorkbenchSelfUpdate } from '../bundle-install.js';
3
4
  export declare function bundleRegistriesHandler(ctx: DshHostContext): RouteHandler;
4
5
  export declare function resetRefusalLog(): void;
5
6
  export declare function resetHoldLog(): void;
6
- export declare function moduleVersionsHandler(ctx: DshHostContext, shell: DshShell | undefined, running?: string | undefined): RouteHandler;
7
+ export declare function moduleVersionsHandler(ctx: DshHostContext, shell: DshShell | undefined, running?: string | undefined, selfUpdate?: (() => WorkbenchSelfUpdate | undefined) | undefined): RouteHandler;
7
8
  export declare function bundleInspectHandler(ctx: DshHostContext): RouteHandler;
@@ -0,0 +1,14 @@
1
+ import type { DshHostContext } from '@dsh-workbench/shared';
2
+ /**
3
+ * 这次会话的取证材料落哪儿(截图 / `rec-*.jsonl` / `net-*.jsonl`):外壳自己的目录,按工作区分桶
4
+ * (目录名 + 路径哈希前 8 位)。**不落用户仓库、也不落任何模块目录** —— 它在 opencli 的可写根内。
5
+ * @param ctx - 宿主上下文(要 `dshHomePath` 服务)。
6
+ * @param root - 工作区根(会话的登记 path)。
7
+ * @returns 绝对路径;这台机器没有 `dshHomePath` 时抛错。
8
+ */
9
+ export declare function artifactsDirOf(ctx: DshHostContext, root: string): string;
10
+ /**
11
+ * 把这个目录建出来(宿主进程直接建 —— 不走 agent 的 shell,沙箱只放行会话工作区)。
12
+ * @param dir - 要建的绝对路径;已存在就什么都不做。
13
+ */
14
+ export declare function ensureDirOf(dir: string): void;
@@ -0,0 +1,41 @@
1
+ export interface DiyModule {
2
+ readonly unit: string;
3
+ readonly name: string;
4
+ readonly description: string;
5
+ readonly dir: string;
6
+ readonly installed: boolean;
7
+ }
8
+ export declare function insideRoot(root: string, target: string): boolean;
9
+ export type WorkspaceShape = 'workspace' | 'package' | 'bare';
10
+ /**
11
+ * 这个工作区**像什么**(只用来把话说清楚,不用来拒绝任何事):自己声明了 workspace(`pnpm-workspace.yaml`
12
+ * 或 `package.json` 的 `workspaces`)就是 `workspace`;有 `package.json` 但没声明 workspace 是 `package`;
13
+ * 连 `package.json` 都没有是 `bare`(空项目 / 一堆散文件都算这一档 —— DIY 的看与调不挑结构)。
14
+ * @param root - 工作区根。
15
+ * @returns 这三种形状之一。
16
+ */
17
+ export declare function shapeOf(root: string): WorkspaceShape;
18
+ /**
19
+ * 这个工作区里有哪些 DIY 模块:工作区根自己的 `package.json`、加上它声明的 workspace 包目录
20
+ * (`pnpm-workspace.yaml` 的 `packages:`,或 `package.json` 的 `workspaces`),凡在
21
+ * `dsh.workbench.unit` 里报了 id 的都算(ADR-0038 的声明协议)。
22
+ * **只读声明、不猜目录**:工作区是什么结构都行(空项目 / 单包 / monorepo),找不到就是空数组。
23
+ * @param root - 工作区根(会话的登记 path)。
24
+ * @param installed - 已经装进 profile 的单元 id(会标在每一行上)。
25
+ * @returns 按 unit 排好序的模块表;一个都没有就给空数组。
26
+ */
27
+ export declare function modulesIn(root: string, installed?: readonly string[]): readonly DiyModule[];
28
+ export declare function readDesignOfModule(dir: string): unknown;
29
+ export interface SyncState {
30
+ readonly state: 'ok' | 'syncing' | 'failed';
31
+ readonly version: string;
32
+ readonly at: string;
33
+ readonly reason?: string;
34
+ }
35
+ export declare function syncStateOf(workspaceRoot: string): SyncState | undefined;
36
+ export interface Toolchain {
37
+ readonly core: string;
38
+ readonly moduleTools: string;
39
+ readonly shared: string;
40
+ }
41
+ export declare function toolchainOf(workspaceRoot: string): Toolchain | undefined;
@@ -0,0 +1,13 @@
1
+ import type { DshHostContext } from '@dsh-workbench/shared';
2
+ import type { FactLog, RouteTable } from '@dsh-workbench/shared/host';
3
+ import type { OpencliResolution } from './opencli-command';
4
+ /**
5
+ * 外壳的几条读口路由(健康 / 事实 / 插件 / 模块版本 / 装前预检)。
6
+ * @param input.ctx 宿主上下文;`running` 是**加载它的进程**里那一版(不是盘上),自更新后两者会不同。
7
+ */
8
+ export declare function createHostRoutes(input: {
9
+ readonly ctx: DshHostContext;
10
+ readonly facts: FactLog;
11
+ readonly opencli: OpencliResolution;
12
+ readonly running: string | undefined;
13
+ }): RouteTable;
@@ -0,0 +1,9 @@
1
+ import type { DshHostContext } from '@dsh-workbench/shared';
2
+ /**
3
+ * 这个会话是不是「完全权限」:审批策略 `never` 且文件边界 `danger-full-access`。
4
+ * 两件都成立时,工作台自己那层写审批不再返回 `ask` —— dsh 在 `never` 下会把 `ask` 直接拒掉。
5
+ * 缺服务、读不到会话或读的时候抛了,一律当成不是:照旧问、照旧被拒,不偷偷放行。
6
+ */
7
+ export declare function fullPermissionOf(ctx: DshHostContext, agent: {
8
+ readonly session?: unknown;
9
+ } | undefined): boolean;
@@ -20,6 +20,8 @@ export interface RuntimeSeedOptions {
20
20
  }
21
21
  export declare function runtimeSeedPaths(root: string): RuntimeSeedPaths;
22
22
  export declare function runtimeSeedRootOf(ctx: DshHostContext): string | null;
23
+ export declare function coversWritableRoot(workspaceRoot: string, root: string): boolean;
24
+ export declare function warnUnwritableRoot(ctx: DshHostContext): void;
23
25
  export declare function packageRootOf(moduleUrl: string): string;
24
26
  export declare function isInstalledCopy(packageRoot: string): boolean;
25
27
  export declare function opencliWrapperScript(packageRoot: string, home: string): string;
@@ -1,7 +1,7 @@
1
1
  import { spawn } from 'node:child_process';
2
2
  import { type SeedCommand } from './runtime-seed';
3
- export declare const SELF_UPDATE_STATES: readonly ["running", "ok", "rolled-back", "failed"];
4
- export type SelfUpdateState = (typeof SELF_UPDATE_STATES)[number];
3
+ import { SELF_UPDATE_STATES, type SelfUpdateState, type WorkbenchSelfUpdate } from '../bundle-install.js';
4
+ export { SELF_UPDATE_STATES, type SelfUpdateState };
5
5
  export interface SelfUpdatePaths {
6
6
  readonly dir: string;
7
7
  readonly script: string;
@@ -43,3 +43,10 @@ export declare function selfUpdateMarker(request: SelfUpdateRequest, state: Self
43
43
  export declare function selfUpdateScript(request: SelfUpdateRequest, paths: SelfUpdatePaths): string;
44
44
  export declare function planSelfUpdate(request: SelfUpdateRequest, now?: Date): SelfUpdatePlan;
45
45
  export declare function startSelfUpdate(request: SelfUpdateRequest, onError: (message: string) => void, spawnImpl?: typeof spawn): SelfUpdateStart;
46
+ /**
47
+ * 读外面那个自更新进程留下的现场(marker + 日志尾):状态是 `running` / `ok` / `rolled-back` / `failed`,
48
+ * `tail` 是日志最后几行(最多 800 字);没跑过、marker 坏了或状态不认识都给 `undefined`。
49
+ * @param home DSH_HOME(自更新的现场在 `<home>/workbench` 下)。
50
+ * @param profile 只认这个 profile 的现场(同一份 home 下几个 profile 共用一个目录);不传就不过滤。
51
+ */
52
+ export declare function readSelfUpdateView(home: string, profile?: string): WorkbenchSelfUpdate | undefined;
@@ -0,0 +1,46 @@
1
+ import type { PostResource, RouteHandler } from '@dsh-workbench/shared/host';
2
+ import type { SidebarEdition, SidebarEditionApplied } from '../sidebar-edition.js';
3
+ /**
4
+ * 左栏档位读写要的宿主现场:当前 profile 目录(拿不到就是 `null`)。
5
+ */
6
+ export interface SidebarEditionDeps {
7
+ readonly profileDir: string | null;
8
+ readonly reconcile?: (() => Promise<void>) | undefined;
9
+ }
10
+ /**
11
+ * 从 profile patch 的文本判当前左栏是哪一档:有一行 `- id: ui-sidebar` 且
12
+ * `disabled: true` 就是自定义,否则官方。
13
+ * @param text profile 的 cordis.patch.yml 全文。
14
+ * @returns 左栏档位。
15
+ */
16
+ export declare function editionOfPatch(text: string): SidebarEdition;
17
+ /**
18
+ * 把 profile patch 改成指定的左栏档位:自定义写一行 `- id: ui-sidebar` +
19
+ * `disabled: true`,官方删掉我们写的那一行(块里还有别的字段时只删 disabled)。
20
+ * @param text profile 的 cordis.patch.yml 全文。
21
+ * @param edition 目标档位。
22
+ * @returns 新全文;已经是目标档位时原样返回。
23
+ */
24
+ export declare function withEdition(text: string, edition: SidebarEdition): string;
25
+ /**
26
+ * 读口:按 profile patch 里那行 `ui-sidebar` 报当前是官方还是自定义。
27
+ * @param deps 当前 profile 目录。
28
+ * @returns `{ ok: true, edition }`;拿不到 profile 时 `{ ok: false, error }`。
29
+ */
30
+ export declare function sidebarEditionHandler(deps: SidebarEditionDeps): RouteHandler;
31
+ /**
32
+ * 写口:把 profile patch 改成目标档位,写完读回来确认;不改别的行。
33
+ * @param deps 当前 profile 目录。
34
+ * @param payload `{ edition: 'official' | 'custom' }`。
35
+ * @returns 写成功给新档位与“要刷新”;失败给人话。
36
+ */
37
+ export declare function sidebarEditionApply(deps: SidebarEditionDeps, payload: unknown): Promise<SidebarEditionApplied | {
38
+ readonly ok: false;
39
+ readonly text: string;
40
+ }>;
41
+ /**
42
+ * 挂到 `actions` 通道的那一格写口。
43
+ * @param deps 当前 profile 目录。
44
+ * @returns 一个 `PostResource`。
45
+ */
46
+ export declare function sidebarEditionAction(deps: SidebarEditionDeps): PostResource;
@@ -0,0 +1,13 @@
1
+ import type { DshHostContext } from '@dsh-workbench/shared';
2
+ export interface SessionWorkspace {
3
+ readonly root: string;
4
+ readonly title: string;
5
+ }
6
+ /**
7
+ * 这条会话落在哪个登记工作区:从 cwd 逐级往上问 `workspaceRegistry.resolveByPath`,命中就取那一份
8
+ * 登记 path(会话开在子目录时也落到工作区根)。**不做任何"像不像工坊"的猜测** —— 查不到就是 `null`。
9
+ * @param ctx - 宿主上下文(要 `workspaceRegistry` 服务)。
10
+ * @param workspace - 会话的 cwd;`null` / 空串表示不知道。
11
+ * @returns 登记工作区的根与标题;查不到给 `null`。
12
+ */
13
+ export declare function sessionWorkspaceOf(ctx: DshHostContext, workspace: string | null): Promise<SessionWorkspace | null>;
@@ -0,0 +1,14 @@
1
+ export declare const SIDEBAR_EDITION_ROUTE = "sidebar-edition";
2
+ export declare const SIDEBAR_EDITION_RESOURCE = "sidebarEdition";
3
+ export type SidebarEdition = 'official' | 'custom';
4
+ export interface SidebarEditionSnapshot {
5
+ readonly edition: SidebarEdition;
6
+ }
7
+ export interface SidebarEditionApplied {
8
+ readonly ok: true;
9
+ readonly edition: SidebarEdition;
10
+ readonly reloadRequired: boolean;
11
+ }
12
+ export interface SidebarEditionRequest {
13
+ readonly edition: SidebarEdition;
14
+ }
@@ -0,0 +1,20 @@
1
+ import type { ApiError, CapabilityPayload, GateMode, ModuleBlocker, UnitBlockers, UnitGate } from '@dsh-workbench/shared';
2
+ /**
3
+ * 判这张卡现在能不能画。
4
+ * @param capability 环境读数;还没读到给 null。
5
+ * @param mode 闸档。
6
+ * @returns mode 为 none 或读数还没到都放行(先放行让它读);读到明确不通过(ok 与 unchecked 之外)才拦。
7
+ */
8
+ export declare function gateOf(capability: CapabilityPayload | null, mode: GateMode): UnitGate;
9
+ /**
10
+ * 把候选卡住态分档,每档只留第一个。
11
+ * @param candidates 候选(可能含 undefined),按优先级从高到低给。
12
+ * @returns 分出的 blocker 与 notice;已经有 blocker 时不带 notice(挡住就不必再提示)。
13
+ */
14
+ export declare function splitBlocker(candidates: readonly (ModuleBlocker | undefined)[]): UnitBlockers;
15
+ /**
16
+ * 由读口错误算出「出错搭车强探」的防抖键:同一个键只自动重探一次。
17
+ * @param error 读口错误;undefined 表示这次没有错误。
18
+ * @returns 键字符串;空 code 归一到 unknown。
19
+ */
20
+ export declare function probeKeyOf(error: ApiError | undefined): string | null;
@@ -0,0 +1,15 @@
1
+ import type { ApiResponse, WriteChannel, WriteOutcome } from '@dsh-workbench/shared';
2
+ /**
3
+ * 由通道与资源拼出写路由。
4
+ * @param channel 写通道。
5
+ * @param resource 该通道下的资源名。
6
+ * @returns 路由字符串;provision 只有一段,其余是 channel/resource。
7
+ */
8
+ export declare function writeRoute(channel: WriteChannel, resource: string): string;
9
+ /**
10
+ * 把写通道的响应翻成 WriteOutcome。
11
+ * @param response 写口回的响应;null 表示请求本身没成(网络断了,宿主没答)。
12
+ * @param networkText 拿不到任何服务端文案时兜底的那句。
13
+ * @returns 失败时 message 优先用服务端自己的话,没有才用响应正文或 networkText。
14
+ */
15
+ export declare function writeOutcomeOf<T>(response: ApiResponse<T> | null, networkText: string): WriteOutcome<T>;
@@ -0,0 +1,8 @@
1
+ import type { IconProps } from '@dsh-workbench/shared';
2
+ /**
3
+ * 四宫格:"模块"这个概念的通用单色图标(侧栏、面板那颗钮、市场节的标题);默认 16px。
4
+ * 外壳自己用(`IconBrandMark` 那几枚在 kit 上,这枚不出口)。
5
+ * @param props.size - 边长像素,同时给 `width` 与 `height`。
6
+ * @returns 一枚 `aria-hidden` 的 svg。
7
+ */
8
+ export declare function IconModules({ size }: IconProps): JSX.Element;
@@ -0,0 +1,16 @@
1
+ import type { BlockerActionShape, BlockerLabelState, BlockerLabelTranslate } from '@dsh-workbench/shared';
2
+ /**
3
+ * 这颗钮此刻该不该按成等待态:这一轮忙还没收,或有一支正在跑。
4
+ * @param busy - 这一轮忙是不是还在。
5
+ * @param running - 正在跑的那一支的 label;没有就给 `null`。
6
+ * @returns 该等待给 `true`,否则 `false`。
7
+ */
8
+ export declare function blockerPending(busy: boolean, running: string | null): boolean;
9
+ /**
10
+ * 这颗钮此刻该显示什么字:正在重试说「重试中…」,正在跑的那一支说「修复中…」,刚复制过的说「已复制」,其余原样。
11
+ * @param action - 这颗钮的形状(行为 + 平时文案)。
12
+ * @param state - 此刻的运行态。
13
+ * @param t - i18n 取词函数。
14
+ * @returns 要显示的文案;三种临时态都不成立时就是 `action.label`。
15
+ */
16
+ export declare function blockerActionLabel(action: BlockerActionShape, state: BlockerLabelState, t: BlockerLabelTranslate): string;
@@ -0,0 +1,7 @@
1
+ import type { SidebarEditionSnapshot } from '../api/sidebar-edition';
2
+ /**
3
+ * 启动时读一次「左栏用哪一档」:这是静态组合决定的值(改了要刷新页面),不是事实流,
4
+ * 所以不走 `useApiResource` 的订阅那一路;调用点只在客户端 apply 里一次。
5
+ * @returns 宿主写的档位;读不到时 `undefined`(按官方 + 官方没落地再兜底)。
6
+ */
7
+ export declare function readSidebarEdition(): Promise<SidebarEditionSnapshot | undefined>;
@@ -30,6 +30,11 @@ export declare function subscribeProbe(listener: () => void): () => void;
30
30
  * 能力表那条事实的名字:写完之后 `useFactInvalidate(CAPABILITY_FACT)` 标它,读者自动重取。
31
31
  */
32
32
  export declare const CAPABILITY_FACT = "capability";
33
+ /**
34
+ * 写完之后把读数作废:写之前发出去的在飞探测即使回来也不许发布(它探的是改之前的样子)。
35
+ * 配一次 `force` 的读,界面才不会拿着旧能力表再摆一次「更新」。
36
+ */
37
+ export declare function bumpCapabilityEpoch(): void;
33
38
  /**
34
39
  * 当前整张能力表的取值口(没探过是 `null`),给 `useSyncExternalStore` 用;渲染里请用 `useCapabilities`。
35
40
  */
@@ -1,5 +1,4 @@
1
- import type { CapabilityPayload } from '@dsh-workbench/shared';
2
- import { type GateMode } from '@dsh-workbench/shared';
1
+ import type { CapabilityPayload, GateMode } from '@dsh-workbench/shared';
3
2
  import { type CapabilityMap } from './use-capabilities';
4
3
  /**
5
4
  * 一格的能力门:`capability` 是这一格声明的能力(没读到是 `null`)、`gated` 放行没(为真 = 这一格能画)、
@@ -1,4 +1,4 @@
1
- import { type WriteChannel, type WriteOutcome } from '@dsh-workbench/shared';
1
+ import type { WriteChannel, WriteOutcome } from '@dsh-workbench/shared';
2
2
  /**
3
3
  * 一个写通道的句柄:`busy` 在发、`failed` / `detail` 上一条人话失败(没有是 `null`)、
4
4
  * `send(body, { silent })` 回 `WriteOutcome`、`fail(message, detail?)` 本地就知道不行时自己塞一条、`clear()` 清掉。
@@ -1,6 +1,6 @@
1
1
  import type { Translate } from '@dsh-workbench/shared';
2
2
  import type { ModuleBlocker } from '@dsh-workbench/shared';
3
- import { type UnitBlockers } from '@dsh-workbench/shared';
3
+ import type { UnitBlockers } from '@dsh-workbench/shared';
4
4
  import type { CapabilityPayload } from '@dsh-workbench/shared';
5
5
  import type { ApiError } from '@dsh-workbench/shared';
6
6
  import type { ResourceState } from './use-api-resource';
package/docs/HANDOFF.md CHANGED
@@ -71,7 +71,7 @@ npx @dsh-workbench/core
71
71
 
72
72
  - **要配什么就在 `:3081` 配**,落在 `profiles/workbench/cordis.patch.yml` —— 那是它唯一的那份设置文档;
73
73
  - **想用 web 那边已经配好的行**(模型供应商 / API key 引用 / 搜索源 / MCP)⇒ `:3081` →
74
- `Meta+,` → 「配置同步」→ 选来源 `web` → 勾行 → 同步(即生效,不用重启)。
74
+ `Meta+,` → 「工作台设置」→「配置同步」→ 选来源 `web` → 勾行 → 同步(即生效,不用重启)。
75
75
  它读的是那个 profile 的 `cordis.patch.yml`,按行搬,只搬**显式配过**的行;
76
76
  同 id 两边内容不同时**默认不覆盖**,要覆盖得自己勾;行点名的包没装会当场说出来。
77
77
  - **web 后来改了不会自动过来** —— 再打开这一页,那几行会重新以「差异」标签出现在这一页,拉一次即可。
@@ -111,7 +111,7 @@ Settings → 插件里把 `@dsh-workbench/core` 关掉后,工作台会**退化
111
111
  "升级要重启一次"一致,不必手动删工坊。
112
112
 
113
113
  **设置不在这一步继承**(这里以前快照 web 的 patch、并继承被它点名的依赖):新机器上
114
- `setup` 完的工作台**没有模型供应商**,第一次打开去 设置 → 「配置同步」拉一次即可 ——
114
+ `setup` 完的工作台**没有模型供应商**,第一次打开去 设置 → 「工作台设置」→「配置同步」拉一次即可 ——
115
115
  这样每一行都归工作台自己,改得动。有意共享的只有两样:**API 凭据**
116
116
  (`~/.dsh/.credentials.yaml`,一机一把 key)和 `$DSH_HOME/cordis.patch.yml`(机器公共层,
117
117
  对所有 profile 实时生效)。
@@ -245,7 +245,7 @@ manifest;彩排结束、以及「零上传」的失败,都会自动回滚。
245
245
  `dsh 0.2.0-rc.2`(当时 npm `latest` 还是它)+ 工作台,工作台照常起得来,**只有 DIY 模式整个不见、
246
246
  一句错都不报** —— `preset/diy.patch.yml` 是从 dsh 0.2.1-rc.2 那份 `standard.patch.yml` 派生的,
247
247
  里面一行 `tool-schedule` 在 0.2.0 上根本不存在,preset 挂不起来;而 dsh 的模式选择器**只列挂得起来的**
248
- (healthy presets only),于是"工作台 DIY"从用户面前消失。同一代的 `uiWorkspace.startSession()`
248
+ (healthy presets only),于是"工作台 DIY 模式"从用户面前消失。同一代的 `uiWorkspace.startSession()`
249
249
  也**不收** `agentPreset`(0.2.1-alpha.3 才收),所以这不是"少一把工具"的降级档,是 DIY 模式整块不可用。
250
250
 
251
251
  因此**声明一个下限**(而不是"谁都能装"):dsh 自 `0.2.1-alpha.4` 起预检**先读 `engines.dsh`**
@@ -44,8 +44,17 @@
44
44
  (这三条不再依赖启动器 —— 宿主自举写的)。
45
45
 
46
46
  这条路上没有启动器,缺的几件由**宿主自举**补齐(建目录、写隔离 opencli 入口、前插 `PATH`、
47
- 后台建 DIY 工坊骨架),可写根由 bundle 层那一行 `sandbox-policy` 放行 —— 见
48
- [ADR-0037](../../../docs/adr/0037-workbench-installs-into-any-profile.md)。
47
+ 后台建 DIY 工坊骨架)。**可写根不在 bundle 层**(2026-10-22 挪走:那行是在线启用时才生效的,
48
+ 会让 dsh 0.2.1 重组时把 session-controller 撞成 failed —— 症状是启用后新建会话报
49
+ `gateway/service-unavailable`,只能重启)。要用市场装模块这类走执行缝的命令时,把
50
+ [`profile/sandbox.patch.yml`](../profile/sandbox.patch.yml) 那一行写进这个 profile 的
51
+ `cordis.patch.yml`,**退出再启动**一次;宿主启动时若发现可写根盖不住 `$DSH_HOME/workbench`,
52
+ 会在日志里打一条 warn。见 [ADR-0037](../../../docs/adr/0037-workbench-installs-into-any-profile.md)
53
+ 的 2026-10-22 补记。
54
+
55
+ **踩到了 `sessionController ... unavailable`**:退出并重新打开(宿主进程重起即可,数据不丢)。
56
+ 在线 toggle `@dsh-workbench/core` 触发的那次重组会把 session-controller 留在 failed,重试/再
57
+ toggle 都救不回来。
49
58
 
50
59
  自举是**静默**的:它写了什么只在宿主日志里,后台脚本(工坊骨架 / 依赖)的输出在
51
60
  `$DSH_HOME/workbench/seed.log`。DIY 那个工坊“怎么都没建起来”时先看这两个地方。
@@ -65,10 +74,11 @@
65
74
  | 开发(监听改动 + 起工作台) | `pnpm run dev` —— profile 不对会先 `setup`;`:3081` 已在跑就只监听 |
66
75
  | 换端口 | `WORKBENCH_PORT=3091 ./bin/dsh-workbench` |
67
76
  | 改工作台设置 | 在 `:3081` 的 Settings 页改,落在 `profiles/workbench/cordis.patch.yml`。**这是工作台唯一的那份设置文档**(没有别处叠在它上面),所以模型供应商 / 搜索源 / MCP 每一行都改得动 |
68
- | 用 web 那边已经配好的行 | `:3081` → `Meta+,` → 「配置同步」→ 选来源(默认 `web`)→ 勾行 → 同步(`dsh-hmr` 会重组,**不用重启**)。只搬你在那个 profile 里**显式配过**的行;行点名的包没装时页面会说出来(ADR-0044) |
77
+ | 换左栏(官方 / 自定义) | `Meta+,` →「工作台设置」→「会话列表」:默认官方;选自定义会往 `profiles/workbench/cordis.patch.yml` 写一行 `ui-sidebar: disabled: true`,**页面自动刷新**后生效(不用重启,会话不断;组合没跟上时客户端最多再自刷 2 次)。第一次从旧版升上来要重启一次(bundle 层在启动时读) |
78
+ | 用 web 那边已经配好的行 | `:3081` → `Meta+,` → 「工作台设置」→「配置同步」→ 选来源(默认 `web`)→ 勾行 → 同步(`dsh-hmr` 会重组,**不用重启**)。只搬你在那个 profile 里**显式配过**的行;行点名的包没装时页面会说出来(ADR-0044) |
69
79
  | 改 web 设置 | 在 `:3080` 改,落在 `profiles/web/cordis.patch.yml`。工作台**不会**再自动跟着变 —— 要就跟上面那条同步一次;不想跟就不动 |
70
80
  | 重建 profile | `mv ~/.dsh/profiles/workbench ~/.dsh/profiles/workbench.bak` 后重跑 seed |
71
- | **别在设置里关掉工作台** | Settings → 插件里 `@dsh-workbench/core` 那个开关**不要关**。关掉=把它从 profile 的 `bundles` 里移除 → 我们那份 bundle patch(含"禁用出厂左栏")**整份不再应用** → 页面退化成**原版 dsh web**(左栏回来、右侧面板消失),**数据一个都不丢**。恢复:进程还在时把开关打开(左栏的「插件」入口也在;进不去设置就按 `Meta+,`);已经重启过的,启动器读到"没选中"会**直接拒绝启动**并给一条修复命令 —— 跑 `./bin/dsh-workbench setup`(幂等,把这一项补回 `dsh.profile.bundles`)即可。实测过这条降级路径,不是推断 |
81
+ | **别在设置里关掉工作台** | Settings → 插件里 `@dsh-workbench/core` 那个开关**不要关**。关掉=把它从 profile 的 `bundles` 里移除 → 我们那份 bundle patch(插入工作台插件那几行)**整份不再应用** → 页面退化成**原版 dsh web**(左栏回来、右侧面板消失),**数据一个都不丢**。恢复:进程还在时把开关打开(左栏的「插件」入口也在;进不去设置就按 `Meta+,`);已经重启过的,启动器读到"没选中"会**直接拒绝启动**并给一条修复命令 —— 跑 `./bin/dsh-workbench setup`(幂等,把这一项补回 `dsh.profile.bundles`)即可。实测过这条降级路径,不是推断 |
72
82
 
73
83
  ## 开发循环(改完不用重启)
74
84
 
@@ -109,7 +119,8 @@ WORKBENCH_PORT=3091 pnpm run dev # 换端口(监听与实例同一个端口
109
119
 
110
120
  0. **代码接线** —— 槽位名 / key / id / locale 是否对得上(语法检查抓不到这类错)
111
121
  1. **web 是否被动过** —— 与 `--record` 的哈希逐一比对
112
- 2. **组合树** —— `dsh workbench --dump-config`:工作台行在位、出厂 `ui-sidebar` 被禁用、
122
+ 2. **组合树** —— `dsh workbench --dump-config`:工作台行在位、左栏按设置二选一
123
+ (默认官方 `ui-sidebar` 在位;选过自定义则是 profile 里那行 `disabled: true`)、
113
124
  启动器里没有 web 设置的叠层(脚本直接判,见 ADR-0044)。两棵树**本来就不该相同**
114
125
  (工作台不再继承 web 的设置),差异只打出来给人看
115
126
  3. **数据是否分家** —— `~/.dsh/sessions` 与 `~/.dsh/workbench/sessions` 各自独立
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dsh-workbench/core",
3
- "version": "0.3.10",
3
+ "version": "0.3.11",
4
4
  "description": "个人工作台:Agent 工作台外壳(左栏 + 常驻对话 + 自研右侧数据面板)。",
5
5
  "type": "module",
6
6
  "icon": "./icon.svg",
@@ -62,7 +62,7 @@
62
62
  ]
63
63
  }
64
64
  },
65
- "//engines": "DIY 模式(preset/diy.patch.yml)是从 **dsh 出厂那份 standard.patch.yml** 派生的,所以这份外壳只跟着它派生的那一代 dsh 走:dsh 太旧时那个 preset 挂不起来,而 dsh 的模式选择器**只列挂得起来的** ⇒ 用户面前「工作台 DIY」整个不见,一句错都不报(实测 0.2.0-rc.2:缺 dsh-tool-schedule)。这里声明下限,让 dsh 自己的兼容性预检在**装的时候**拒绝(自 0.2.1-alpha.4 起 engines.dsh 优先于 peer)。⚠️ **不写 peerDependencies['@deepseek-ai/dsh']**:pnpm 的 auto-install-peers(v11 连 optional 也装)会把整套 dsh 拖进 profile。旧到不认识 engines.dsh 的 dsh(0.2.0 及更早)由启动器那道硬门禁兜 —— 见 bin/dsh-workbench。判据:selfcheck 的「声明下限 = 验过的 dsh 版本」那条。",
65
+ "//engines": "DIY 模式(preset/diy.patch.yml)是从 **dsh 出厂那份 standard.patch.yml** 派生的,所以这份外壳只跟着它派生的那一代 dsh 走:dsh 太旧时那个 preset 挂不起来,而 dsh 的模式选择器**只列挂得起来的** ⇒ 用户面前「工作台 DIY 模式」整个不见,一句错都不报(实测 0.2.0-rc.2:缺 dsh-tool-schedule)。这里声明下限,让 dsh 自己的兼容性预检在**装的时候**拒绝(自 0.2.1-alpha.4 起 engines.dsh 优先于 peer)。⚠️ **不写 peerDependencies['@deepseek-ai/dsh']**:pnpm 的 auto-install-peers(v11 连 optional 也装)会把整套 dsh 拖进 profile。旧到不认识 engines.dsh 的 dsh(0.2.0 及更早)由启动器那道硬门禁兜 —— 见 bin/dsh-workbench。判据:selfcheck 的「声明下限 = 验过的 dsh 版本」那条。",
66
66
  "engines": {
67
67
  "dsh": ">=0.2.1-rc.2"
68
68
  },
@@ -91,7 +91,7 @@
91
91
  "access": "public"
92
92
  },
93
93
  "dependencies": {
94
- "@dsh-workbench/shared": "0.3.10"
94
+ "@dsh-workbench/shared": "0.3.11"
95
95
  },
96
96
  "scripts": {
97
97
  "build": "node scripts/build.mjs && node scripts/build-types.mjs",
@@ -9,8 +9,8 @@
9
9
  name: '@deepseek-ai/dsh-agent-preset'
10
10
  config:
11
11
  id: workbench-diy
12
- name: 工作台 DIY
13
- description: 用大白话说你要在面板上看到什么、数据从哪来 —— agent 在工作台的工坊里把模块造出来,装上就上板。不用写代码。
12
+ name: 工作台 DIY 模式
13
+ description: 看 / 调这个工作区的面板,或者给它造一块新内容 —— 用大白话说就行。任何工作区都能用(空项目也算);agent 会看面板、抓请求,必要时把模块造出来装上。不用写代码。
14
14
  order: 9
15
15
  plugins:
16
16
  - id: persona
@@ -33,3 +33,14 @@
33
33
  name: '@deepseek-ai/dsh-attachment-local'
34
34
  config:
35
35
  dshHome: !!js dshHomePath('workbench')
36
+
37
+ # 可写根:**无会话**的插件命令(市场装模块、自举跑的那几条)回落到它。
38
+ #
39
+ # 跟启动器的 profile/sandbox.patch.yml 同值,两处都在是因为入口不同:启动器每次启动叠
40
+ # 那一层;直接用 `dsh workbench` 起的没有 `--patch`,就靠这一行。两处同值 ⇒ 谁先谁后都不算变更,
41
+ # 不会触发在线重组。**别把它放回 bundle 层**(在线启用会撞 session-controller 的重组竞态,
42
+ # 见 ADR-0037 的 2026-10-22 补记)。
43
+ - id: sandbox-policy
44
+ config:
45
+ mode: !!js process.env.DSH_PERMISSION_MODE ?? 'workspace-write'
46
+ workspaceRoot: !!js dshHomePath('workbench')