@dsh-workbench/core 0.1.15 → 0.1.17

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 (74) hide show
  1. package/README.md +19 -0
  2. package/bin/dsh-workbench +5 -1
  3. package/client.js +660 -128
  4. package/cordis.patch.yml +22 -0
  5. package/dist/client/client.body.js +660 -128
  6. package/dist/client/index.js +660 -128
  7. package/dist/host/index.js +1733 -324
  8. package/dist/types/app/client.d.ts +124 -9
  9. package/dist/types/app/layouts/market/add-module-text.d.ts +2 -0
  10. package/dist/types/app/layouts/market/use-add-module.d.ts +4 -3
  11. package/dist/types/app/layouts/market/use-module-versions.d.ts +2 -0
  12. package/dist/types/app/layouts/rightbar/MarketView.d.ts +3 -1
  13. package/dist/types/app/layouts/rightbar/__tests__/market-rows-plugins.test.d.ts +1 -0
  14. package/dist/types/app/layouts/rightbar/market-mock.d.ts +9 -0
  15. package/dist/types/app/layouts/rightbar/market-plugin-rows.d.ts +8 -0
  16. package/dist/types/app/layouts/rightbar/market-rows.d.ts +14 -3
  17. package/dist/types/app/layouts/rightbar/registry.d.ts +0 -1
  18. package/dist/types/app/units/__tests__/manifests.test.d.ts +1 -0
  19. package/dist/types/app/units/fixture-unit-mock.d.ts +3 -3
  20. package/dist/types/app/units/host.d.ts +2 -2
  21. package/dist/types/app/units/manifests.d.ts +5 -0
  22. package/dist/types/app/units/registry.d.ts +10 -2
  23. package/dist/types/modules/panels/__tests__/market-env.test.d.ts +1 -0
  24. package/dist/types/modules/panels/__tests__/market-plugin.test.d.ts +1 -0
  25. package/dist/types/modules/panels/__tests__/market-route.test.d.ts +1 -0
  26. package/dist/types/modules/panels/__tests__/plugin-actions.test.d.ts +1 -0
  27. package/dist/types/modules/panels/__tests__/plugin-ledger.test.d.ts +1 -0
  28. package/dist/types/modules/panels/__tests__/plugin-section.test.d.ts +1 -0
  29. package/dist/types/modules/panels/__tests__/scope.test.d.ts +1 -0
  30. package/dist/types/modules/panels/__tests__/service-release.test.d.ts +1 -0
  31. package/dist/types/modules/panels/index.d.ts +2 -2
  32. package/dist/types/modules/panels/model/config.d.ts +27 -9
  33. package/dist/types/modules/panels/model/market-dispatch.d.ts +9 -0
  34. package/dist/types/modules/panels/model/market-env.d.ts +20 -0
  35. package/dist/types/modules/panels/model/market-route.d.ts +26 -0
  36. package/dist/types/modules/panels/model/market-types.d.ts +71 -0
  37. package/dist/types/modules/panels/model/market.d.ts +7 -66
  38. package/dist/types/modules/panels/model/plugin-actions.d.ts +50 -0
  39. package/dist/types/modules/panels/model/plugin-ledger.d.ts +22 -0
  40. package/dist/types/modules/panels/model/plugin-name.d.ts +8 -0
  41. package/dist/types/modules/panels/model/plugin-name.test.d.ts +1 -0
  42. package/dist/types/modules/panels/model/plugin-section.d.ts +13 -0
  43. package/dist/types/modules/panels/model/service.d.ts +8 -6
  44. package/dist/types/shared/api/bundle-install.d.ts +8 -1
  45. package/dist/types/shared/api/host/__tests__/registry-declaration.test.d.ts +1 -0
  46. package/dist/types/shared/api/host/__tests__/unit-roster-facts.test.d.ts +1 -0
  47. package/dist/types/shared/api/host/bundle-routes.d.ts +1 -0
  48. package/dist/types/shared/api/host/installed-module.d.ts +7 -0
  49. package/dist/types/shared/api/host/mount-units.d.ts +5 -3
  50. package/dist/types/shared/api/host/package-content.d.ts +6 -0
  51. package/dist/types/shared/api/host/package-content.test.d.ts +1 -0
  52. package/dist/types/shared/api/host/package-kind.d.ts +21 -0
  53. package/dist/types/shared/api/host/package-kind.test.d.ts +1 -0
  54. package/dist/types/shared/api/host/package-route.d.ts +23 -0
  55. package/dist/types/shared/api/host/package-route.test.d.ts +1 -0
  56. package/dist/types/shared/api/host/registry-declaration.d.ts +23 -0
  57. package/dist/types/shared/api/host/runtime-seed.d.ts +40 -0
  58. package/dist/types/shared/api/host/runtime-seed.test.d.ts +1 -0
  59. package/dist/types/shared/api/host/unit-registry.d.ts +33 -4
  60. package/dist/types/shared/api/host/unit-roster-facts.d.ts +5 -0
  61. package/dist/types/shared/api/installed-plugins.d.ts +12 -0
  62. package/dist/types/shared/api/unit-manifests.d.ts +5 -0
  63. package/dist/types/shared/api/unit-manifests.test.d.ts +1 -0
  64. package/dist/types/shared/hooks/bundle-queries.d.ts +2 -0
  65. package/dist/types/shared/hooks/use-facts.d.ts +1 -0
  66. package/docs/HANDOFF.md +82 -44
  67. package/docs/OPERATIONS.md +22 -1
  68. package/package.json +5 -4
  69. package/scripts/seed-workshop.sh +216 -0
  70. package/skills/workbench-module-diy/BUILD.md +5 -5
  71. package/skills/workbench-module-diy/DESIGN.md +7 -1
  72. package/skills/workbench-module-diy/REVIEW.md +3 -2
  73. package/skills/workbench-module-diy/TOOLING.md +2 -2
  74. package/skills/workbench-module-diy/TOOLS.md +9 -2
@@ -8,7 +8,7 @@ export interface BundleRegistries {
8
8
  readonly fallbackRegistries: readonly BundleRegistry[];
9
9
  readonly resolved: string | null;
10
10
  }
11
- export type BundleInspectProblem = 'invalid-spec' | 'already-installed' | 'not-found' | 'not-a-package' | 'not-a-bundle' | 'network' | 'unknown';
11
+ export type BundleInspectProblem = 'invalid-spec' | 'already-installed' | 'not-found' | 'not-a-package' | 'not-a-bundle' | 'not-a-workbench-module' | 'not-a-module-or-plugin' | 'network' | 'unknown';
12
12
  export type BundleSpecKind = 'registry' | 'path' | 'git' | 'tarball';
13
13
  export interface BundleInspectionAccepted {
14
14
  readonly status: 'accepted';
@@ -74,10 +74,16 @@ export interface BundleSupplyPayload {
74
74
  readonly state: BundleSupplyState;
75
75
  readonly reason?: string;
76
76
  }
77
+ export interface BundleInstalledPlugin {
78
+ readonly kind: 'opencli-plugin';
79
+ readonly pkg: string;
80
+ readonly name?: string;
81
+ }
77
82
  export interface BundleInstallPayload {
78
83
  readonly change: BundleChange;
79
84
  readonly module: BundleModulePayload;
80
85
  readonly supply: BundleSupplyPayload;
86
+ readonly plugin?: BundleInstalledPlugin;
81
87
  }
82
88
  export interface BundleRemovePayload {
83
89
  readonly change: BundleChange;
@@ -92,6 +98,7 @@ export interface ModuleVersion {
92
98
  readonly installed: string;
93
99
  readonly latest?: string;
94
100
  readonly local?: boolean;
101
+ readonly refused?: readonly string[];
95
102
  }
96
103
  export interface ModuleVersionsPayload {
97
104
  readonly modules: readonly ModuleVersion[];
@@ -1,5 +1,6 @@
1
1
  import type { DshHostContext, DshShell } from '@dsh-workbench/shared';
2
2
  import { type RouteHandler } from '@dsh-workbench/shared/host';
3
3
  export declare function bundleRegistriesHandler(ctx: DshHostContext): RouteHandler;
4
+ export declare function resetRefusalLog(): void;
4
5
  export declare function moduleVersionsHandler(ctx: DshHostContext, shell: DshShell | undefined): RouteHandler;
5
6
  export declare function bundleInspectHandler(ctx: DshHostContext): RouteHandler;
@@ -22,5 +22,12 @@ export interface ProfileModule {
22
22
  readonly registry?: string;
23
23
  readonly local?: boolean;
24
24
  }
25
+ export declare function profileManifestOf(profileDir: string | null): Readonly<Record<string, unknown>> | undefined;
25
26
  export declare function profileModules(profileDir: string | null): readonly ProfileModule[];
27
+ export declare function moduleDeclarationOf(profileDir: string | null, pkg: string): unknown;
28
+ export interface DeclarationGap {
29
+ readonly pkg: string;
30
+ readonly problems: readonly string[];
31
+ }
32
+ export declare function declarationGaps(profileDir: string | null): readonly DeclarationGap[] | undefined;
26
33
  export declare function readInstalledModule(profileDir: string | null, pkg: string): InstalledModule;
@@ -1,14 +1,15 @@
1
1
  import type { DshHostContext } from '@dsh-workbench/shared';
2
2
  import type { UnitManifest } from '@dsh-workbench/shared/host';
3
3
  import { type RouteTable } from '@dsh-workbench/shared/host';
4
- import type { ContextRenderer, UnitHostFace, UnitMountDeps, UnitMountResult, UnitCall } from '@dsh-workbench/shared/host';
4
+ import type { ContextRenderer, UnitMountDeps, UnitMountResult, UnitCall } from '@dsh-workbench/shared/host';
5
+ import type { HostUnit } from './unit-registry';
5
6
  import { type UnitReadiness } from './unit-readiness';
6
7
  export interface UnitFailure {
7
8
  readonly id: string;
8
9
  readonly reason: string;
9
10
  }
10
11
  export interface MountedUnit {
11
- readonly face: UnitHostFace;
12
+ readonly host: HostUnit;
12
13
  readonly result: UnitMountResult;
13
14
  readonly readiness: UnitReadiness;
14
15
  }
@@ -40,13 +41,14 @@ interface NamedEntry {
40
41
  handle(payload: unknown): unknown;
41
42
  }
42
43
  export declare function failureReasonOf(error: unknown): string;
43
- export declare function mountUnits(faces: readonly UnitHostFace[], deps: {
44
+ export declare function mountUnits(units: readonly HostUnit[], deps: {
44
45
  ctx: DshHostContext;
45
46
  logger?: {
46
47
  info?(m: string): void;
47
48
  warn?(m: string): void;
48
49
  } | undefined;
49
50
  manifests: () => readonly UnitManifest[];
51
+ ownedUnits?: () => readonly string[];
50
52
  opencliCommand?: string;
51
53
  probe?: UnitMountDeps['probe'];
52
54
  store?: UnitMountDeps['store'];
@@ -0,0 +1,6 @@
1
+ import type { DshShell } from '@dsh-workbench/shared';
2
+ export declare function withPackageDir<T>(options: {
3
+ readonly shell?: DshShell | undefined;
4
+ readonly spec: string;
5
+ readonly registry?: string | undefined;
6
+ }, visit: (dir: string) => T): Promise<T | undefined>;
@@ -0,0 +1,21 @@
1
+ export type PackageKind = 'opencli-plugin' | 'dsh-bundle' | 'both' | 'neither';
2
+ export interface PackageVerdict {
3
+ readonly kind: PackageKind;
4
+ readonly pkg?: string;
5
+ readonly pluginName?: string;
6
+ }
7
+ export interface NpmRef {
8
+ readonly name: string;
9
+ readonly version?: string;
10
+ }
11
+ export declare const OPENCLI_MANIFEST_FILE = "opencli-plugin.json";
12
+ export declare function splitNpmSpec(spec: string): NpmRef | undefined;
13
+ export declare function isCommandFile(name: string): boolean;
14
+ export declare function pluginManifestOf(dir: string): Readonly<Record<string, unknown>> | undefined;
15
+ export declare function hasCommandFile(dir: string): boolean;
16
+ export declare function pluginAt(dir: string): {
17
+ readonly name?: string;
18
+ } | undefined;
19
+ export declare function dshMarkersOf(raw: unknown): boolean;
20
+ export declare function verdictOf(dir: string): PackageVerdict | undefined;
21
+ export declare function refusalReason(): string;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,23 @@
1
+ import type { DshShell } from '@dsh-workbench/shared';
2
+ import { type ResolvedPluginSource } from '@dsh-workbench/shared/host';
3
+ export type PackageRouteKind = 'opencli-plugin' | 'dsh-bundle' | 'neither';
4
+ export interface PackageRoute {
5
+ readonly kind: PackageRouteKind;
6
+ readonly pkg?: string;
7
+ readonly pluginName?: string;
8
+ }
9
+ export declare const LOCAL_PLUGIN_ENV = "DSH_WB_OPENCLI_PLUGIN_PATH";
10
+ export declare const PLUGIN_REGISTRY_ENV = "DSH_WB_OPENCLI_PLUGIN_REGISTRY";
11
+ export declare function packageRouteOf(options: {
12
+ readonly spec: string;
13
+ readonly shell?: DshShell | undefined;
14
+ readonly registry?: string | undefined;
15
+ readonly force?: boolean;
16
+ }): Promise<PackageRoute | undefined>;
17
+ export declare function resetPackageRouteCache(): void;
18
+ export declare function pluginInstallPlan(options: {
19
+ readonly spec: string;
20
+ readonly command: string;
21
+ readonly registry?: string | undefined;
22
+ readonly pluginName?: string | undefined;
23
+ }): ResolvedPluginSource;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,23 @@
1
+ import type { DshShell } from '@dsh-workbench/shared';
2
+ export declare function localDirOf(spec: string): string | undefined;
3
+ export declare function packageManifestOf(dir: string): Readonly<Record<string, unknown>> | undefined;
4
+ export declare function dshRecordOf(dir: string): unknown | undefined;
5
+ export declare function packageNameOf(dir: string): string | undefined;
6
+ export declare function pathDeclarationOf(spec: string): unknown | undefined;
7
+ export interface RegistryDshLookup {
8
+ readonly readable: boolean;
9
+ readonly raw: unknown;
10
+ }
11
+ export declare function registryDshOf(options: {
12
+ shell: DshShell;
13
+ name: string;
14
+ registry?: string | undefined;
15
+ force?: boolean;
16
+ }): Promise<RegistryDshLookup>;
17
+ export declare function registryDeclarationOf(options: {
18
+ shell: DshShell;
19
+ name: string;
20
+ registry: string;
21
+ force?: boolean;
22
+ }): Promise<unknown | undefined>;
23
+ export declare function declarationRefusalOf(name: string, raw: unknown): readonly string[];
@@ -0,0 +1,40 @@
1
+ import { spawn } from 'node:child_process';
2
+ import type { DshHostContext } from '@dsh-workbench/shared';
3
+ export declare const WORKSHOP_SKILLS_DIR = "workbench-module-diy";
4
+ export type SeedLog = (message: string) => void;
5
+ export interface RuntimeSeedPaths {
6
+ readonly root: string;
7
+ readonly entryDir: string;
8
+ readonly opencliRoot: string;
9
+ readonly opencliBinDir: string;
10
+ readonly opencliBin: string;
11
+ readonly opencliHome: string;
12
+ readonly workshop: string;
13
+ readonly workshopPackages: string;
14
+ readonly workshopManifest: string;
15
+ readonly skillsLink: string;
16
+ }
17
+ export interface RuntimeSeedOptions {
18
+ readonly root: string;
19
+ readonly home: string;
20
+ readonly packageRoot: string;
21
+ readonly log?: SeedLog;
22
+ }
23
+ export declare function runtimeSeedPaths(root: string): RuntimeSeedPaths;
24
+ export declare function runtimeSeedRootOf(ctx: DshHostContext): string | null;
25
+ export declare function packageRootOf(moduleUrl: string): string;
26
+ export declare function isInstalledCopy(packageRoot: string): boolean;
27
+ export declare function opencliWrapperScript(packageRoot: string, home: string): string;
28
+ export interface SeedCommand {
29
+ readonly file: string;
30
+ readonly args: readonly string[];
31
+ readonly env: Readonly<Record<string, string>>;
32
+ readonly log: string;
33
+ }
34
+ export declare function workshopSeedPlan(packageRoot: string, home: string, root: string): {
35
+ readonly entry: SeedCommand;
36
+ readonly full: SeedCommand;
37
+ };
38
+ export declare function runDetached(command: SeedCommand, onError: SeedLog, spawnImpl?: typeof spawn): void;
39
+ export declare function prependPath(entryDir: string, env?: NodeJS.ProcessEnv): boolean;
40
+ export declare function seedRuntimeFiles(options: RuntimeSeedOptions): readonly string[];
@@ -0,0 +1 @@
1
+ export {};
@@ -1,9 +1,38 @@
1
1
  import type { UnitManifest } from '@dsh-workbench/shared';
2
- import type { UnitHostFace } from '@dsh-workbench/shared/host';
2
+ import type { UnitModuleFace, UnitOwnedFace } from '@dsh-workbench/shared/host';
3
+ export interface UnitRefusal {
4
+ readonly unit: string;
5
+ readonly pkg?: string;
6
+ readonly reasons: readonly string[];
7
+ }
8
+ export type UnitSource = {
9
+ readonly kind: 'package';
10
+ readonly pkg: string;
11
+ } | {
12
+ readonly kind: 'owned';
13
+ };
14
+ export interface HostUnit {
15
+ readonly unit: string;
16
+ readonly manifest: UnitManifest;
17
+ readonly face: UnitModuleFace;
18
+ readonly source: UnitSource;
19
+ }
20
+ export interface UnitDeclarationSource {
21
+ readonly pkg: string;
22
+ readonly declaration: unknown;
23
+ }
3
24
  export interface UnitRegistry {
4
- register(face: UnitHostFace): () => void;
5
- all(): readonly UnitHostFace[];
25
+ register(face: UnitModuleFace): () => void;
26
+ registerOwned(face: UnitOwnedFace): () => void;
27
+ all(): readonly HostUnit[];
28
+ owned(): readonly string[];
29
+ refusals(): readonly UnitRefusal[];
6
30
  manifests(): readonly UnitManifest[];
7
31
  subscribe(listener: () => void): () => void;
8
32
  }
9
- export declare function createUnitRegistry(): UnitRegistry;
33
+ export declare function createUnitRegistry(options: {
34
+ declarationOf: (unit: string) => UnitDeclarationSource | undefined;
35
+ logger?: {
36
+ warn?(message: string): void;
37
+ } | undefined;
38
+ }): UnitRegistry;
@@ -0,0 +1,5 @@
1
+ import type { UnitRegistry } from './unit-registry';
2
+ export declare function bindRosterFacts(options: {
3
+ readonly units: UnitRegistry;
4
+ readonly bump: (facts: readonly string[]) => void;
5
+ }): () => void;
@@ -0,0 +1,12 @@
1
+ export declare const INSTALLED_PLUGINS_ROUTE = "panels/plugins";
2
+ export interface InstalledPlugin {
3
+ readonly pkg: string;
4
+ readonly name?: string;
5
+ readonly installed?: string;
6
+ readonly latest?: string;
7
+ readonly local?: boolean;
8
+ readonly broken?: boolean;
9
+ }
10
+ export interface InstalledPluginsPayload {
11
+ readonly plugins: readonly InstalledPlugin[];
12
+ }
@@ -0,0 +1,5 @@
1
+ import type { UnitManifest } from '@dsh-workbench/shared';
2
+ export declare const UNIT_MANIFESTS_ROUTE = "unit-manifests";
3
+ export interface UnitManifestsPayload {
4
+ readonly manifests: readonly UnitManifest[];
5
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -1,5 +1,7 @@
1
1
  import type { ApiResponse } from '@dsh-workbench/shared';
2
2
  import { type BundleInspectionPayload, type BundleRegistriesPayload, type ModuleVersionsPayload } from '../api/bundle-install';
3
+ import { type InstalledPluginsPayload } from '../api/installed-plugins';
3
4
  export declare function readBundleRegistries(): Promise<ApiResponse<BundleRegistriesPayload>>;
4
5
  export declare function readBundleInspection(spec: string, registry?: string): Promise<ApiResponse<BundleInspectionPayload>>;
5
6
  export declare function readModuleVersions(force?: boolean): Promise<ApiResponse<ModuleVersionsPayload>>;
7
+ export declare function readInstalledPlugins(force?: boolean): Promise<ApiResponse<InstalledPluginsPayload>>;
@@ -1,2 +1,3 @@
1
1
  export declare function useFactRevision(fact: string): number;
2
+ export declare function subscribeFactRevision(fact: string, listener: () => void): () => void;
2
3
  export declare function useFactInvalidate(): (fact?: string) => void;
package/docs/HANDOFF.md CHANGED
@@ -1,37 +1,55 @@
1
1
  # 把工作台给别人用
2
2
 
3
- 工作台是一个 **dsh 插件包**,发在内网源上(地址只写在 `package.json` 的
4
- `publishConfig.registry` 一处)。别人装的是**独立 profile**(`workbench`)——
5
- 他自己那份 `dsh web` 一行不改,两边可以同时开着。
3
+ 工作台是一个 **dsh 插件包**,现在发在**公网 npm** 上:`@dsh-workbench/core`
4
+ (实测 `npm view @dsh-workbench/core version` 出号,`npm pack` 的产物里
5
+ `dsh.bundle.patch` / `cordis.patch.yml` / `client.js` / `dist/host/index.js` 都在)。
6
+ 所以**对方不需要配任何私有源**就能装。
6
7
 
7
- 一句话:**登录内网源 → `setup` → 起**。
8
+ 两种给法,先挑一种:
9
+
10
+ | 给法 | 对方要做的事 | 换来的 |
11
+ | --- | --- | --- |
12
+ | **A. 装进他自己的 `dsh web`**(最短) | Plugins → Add plugin → `@dsh-workbench/core` → Enable now → **刷新页面** | 就在那份 web 里换成工作台;**同一个 profile、同一份会话**、同一个端口 |
13
+ | **B. 独立实例**(要数据分家就选它) | `npx --yes @dsh-workbench/core@latest setup` → `npx @dsh-workbench/core` | 另一个 profile、另一个端口(:3081),他自己那份 `dsh web` **一行不改** |
14
+
15
+ 一条路径一句话:**A = 点一下装(单 profile);B = `setup` 然后起(双 profile)**。
16
+ 取舍与代价见 [`docs/adr/0037`](../../../docs/adr/0037-workbench-installs-into-any-profile.md)。
8
17
 
9
18
  ## 一、对方机器上要有的
10
19
 
11
20
  | # | 东西 | 为什么 | 怎么有 |
12
21
  | --- | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------- |
13
22
  | 1 | `dsh` | 本体。本仓库在 `0.1.7-rc.2` 上验过 | `npm i -g @deepseek-ai/dsh`(公网 npm 上有) |
14
- | 2 | `node` + **`pnpm`** | dsh 的 `plugin` 子命令是**转发给 pnpm** 的;没有 pnpm 会直接报 `dsh: pnpm was not found` | 各自装 |
15
- | 3 | 跑过一次 `dsh web` | `setup` 拿 `~/.dsh/profiles/web` 当模板(bundle 清单 + 设置快照 + patch 点名的依赖)。没有它会**明确报错**,不会静默建出半个 profile | `dsh web` |
16
- | 4 | 内网源登录 | 包带 `@pagoda` scope,公网上没有它 | 见第二节 |
17
- | 5 | Coding 账号 + opencli 登录态 | 面板的数据来自 opencli 的 coding 插件 | 见第四节 |
23
+ | 2 | `node` + **`pnpm`** | dsh 的 `plugin` 子命令(以及 web 的 Plugins 页)都是**转发给 pnpm** 的;没有 pnpm 会直接报 `dsh: pnpm was not found` | 各自装 |
24
+ | 3 | 跑过一次 `dsh web` | 走 A 需要它给出那个 profile;走 B 时 `setup` 拿 `~/.dsh/profiles/web` 当模板。没有它会**明确报错**,不会静默建出半个 profile | `dsh web` |
25
+ | 4 | 一个能解析这个包的源 | 包在**公网 npm** 上,默认源即可。只有将来改发内网源时,才要 `npm config set @<scope>:registry …` + `npm login`(带 scope 的包只去 `.npmrc` 里给这个 scope 指的源找) | `npm view @dsh-workbench/core version` 出号即可 |
26
+ | 5 | Coding 账号 + opencli 登录态 | 面板的数据来自 opencli 的 coding 插件 | 见第五节 |
18
27
 
19
- ## 二、两步装
28
+ ## 二、A:装进他自己的 `dsh web`(插件页,一条龙)
20
29
 
21
- ```bash
22
- # ① 让 pnpm/npx 知道 @pagoda 这个 scope 在哪个源,并登录(各一次)
23
- npm config set @pagoda:registry "$NPM_REGISTRY"
24
- npm login --registry=$NPM_REGISTRY
30
+ 在 `dsh web`(:3080)里:
31
+
32
+ 1. 侧栏 **Plugins → Add plugin**,填 `@dsh-workbench/core`;
33
+ 2. **Install → Enable now**;
34
+ 3. **刷新一次页面**(客户端 bundle 是页面加载时装进去的)。
35
+
36
+ 装完左栏就是工作台、右侧多出面板列。缺的那几件(隔离 opencli 的入口、`dsh-wb-module`、
37
+ DIY 工坊骨架、可写根)由**外壳自己**补齐 —— 不用跑 `setup`,也不用起第二个实例。
38
+
39
+ 差别(先跟对方说清):**会话 / 存储不分家**(接着用他原来那份)、**没有独立端口与进程**、
40
+ **改外壳源码不热重载**、DIY 工坊依赖是首次启动在后台装的。
25
41
 
26
- # ② 建 profile + 把工作台装进去,然后起(占 :3081,dsh web 继续用 :3080)
42
+ ## 三、B:两步装(独立 profile,占 :3081)
43
+
44
+ ```bash
45
+ # ① 建 profile + 把工作台装进去,然后起(占 :3081,dsh web 继续用 :3080)
27
46
  npx --yes @dsh-workbench/core@latest setup
28
47
  npx @dsh-workbench/core
29
48
  ```
30
49
 
31
- **为什么必须有 `@pagoda:registry` 那一行**:不带 scope 的 `dsh-workbench`
32
- 公网上已经有别人的一个(`lee259/dsh-workbench`),所以我们的包必须带 scope;
33
- 而带 scope 的包,pnpm / npx 只会去 `.npmrc` 里**给这个 scope 指的那个源**找它 ——
34
- 不设这一行,症状是 `404 Not Found - GET https://registry.npmjs.org/@pagoda%2fdsh-workbench`。
50
+ > 若团队后来把包改发到内网源,这两条命令之前要各加一次:
51
+ > `npm config set @<scope>:registry "$NPM_REGISTRY"` 与 `npm login --registry=$NPM_REGISTRY`。
52
+ > 不设的话症状是 `404 Not Found - GET https://registry.npmjs.org/@<scope>%2F…`。
35
53
 
36
54
  > ⚠️ **0.1.6 的已知缺陷**:`setup` 可能死在
37
55
  > `…/@dsh-workbench/core/scripts/seed-profile.sh: Permission denied`(发出去的包里随包脚本丢了
@@ -76,7 +94,7 @@ Settings → 插件里把 `@dsh-workbench/core` 关掉后,工作台会**退化
76
94
  要重新同步得重跑 `setup`。有意共享的只有两样:**API 凭据**(`~/.dsh/.credentials.yaml`,
77
95
  一机一把 key)和 `$DSH_HOME/cordis.patch.yml`(机器公共层,对所有 profile 实时生效)。
78
96
 
79
- ## 三、不想用 npx?仓库副本也可以
97
+ ## 四、不想用 npx?仓库副本也可以
80
98
 
81
99
  `bin/dsh-workbench` 认得出自己是不是仓库副本(看有没有 `.git`):
82
100
 
@@ -88,7 +106,7 @@ git clone <仓库> && cd dsh-workbench && pnpm install
88
106
 
89
107
  两种身份装出来的东西**同一个包名**,所以对方以后换成正装也不会打架。
90
108
 
91
- ## 四、数据通路(面板要有数据)
109
+ ## 五、数据通路(面板要有数据)
92
110
 
93
111
  1. **opencli 隔离运行时**。第一次打开工作台,启动体检会**自己装**它(`@jackwener/opencli`,
94
112
  公网包),落在 `$DSH_HOME/workbench/opencli/toolchain/`,**与对方本机的 `opencli` 完全隔离**
@@ -142,35 +160,52 @@ git clone <仓库> && cd dsh-workbench && pnpm install
142
160
  装的时候 `bin/opencli-isolated` 会把 `~/.npmrc` 指回去,就为了拿到源的 token;
143
161
  npm 缓存则一律落在那台机器自己的隔离运行时里(**不碰** `~/.npm`)。
144
162
 
145
- ## 五、不用管的
163
+ ## 六、不用管的
146
164
 
147
165
  - **`dsh web` 一行不改**:`./scripts/verify.sh` 是外部证据 —— 比对 web 的 profile 哈希、
148
166
  两个 profile 的组合树差异、数据是否分家,而不是自证。
149
167
  - 会话 / 存储 / 附件各自独立;工作台是**另一个端口、另一个进程**,可与 `dsh web` 同开。
150
168
  - 改工作台设置只落在 `profiles/workbench/cordis.patch.yml`。
151
169
 
152
- ## 六、发版(发起人)
170
+ ## 七、发版(发起人)
153
171
 
154
172
  ```bash
155
173
  cd <仓库根>
156
- pnpm release:dry # 门禁 + 发布计划 + 打包清单校验,不联网、不真发
157
- pnpm release # 真发(发前会再跑一次门禁)
174
+ pnpm release:dry # 彩排:门禁 → 选版本号 → 构建 → publish --dry-run;不上传,结束回滚版本号
175
+ pnpm release # 真发:上面那一串 + 逐包上传 + git 收尾(提交 / tag / 推送)
158
176
  ```
159
177
 
160
- 提版本号之后再发:`npm version patch`。脚本把几个坑写成了**硬失败**:
178
+ 不用先 `npm version`:版本号由脚本选(交互,或 `--target 0.2.0`),同步到根与所有包的
179
+ manifest;彩排结束、以及「零上传」的失败,都会自动回滚。
180
+
181
+ **门禁整次发布只跑一遍**(preflight 那次 `pnpm run check`;`--resume` 也跑)。发布走
182
+ `pnpm -r publish --ignore-scripts`:各包的 `prepublishOnly` / `prepack` 是留给**手敲**
183
+ `pnpm publish` 的人兜底的(外壳那份 = 整仓门禁);不关掉它们,pnpm 会在**彩排与真发各再跑
184
+ 一遍**整门禁 —— 一次发布就是 3 遍门禁 + 3 遍 vitest + 每个包多两轮构建(实测 pnpm 10.32.1:
185
+ `--dry-run` 一样触发)。来历见
186
+ [`.scratch/dsh-ui-alignment/spec.md`](../../../.scratch/dsh-ui-alignment/spec.md) §18.45。
187
+
188
+ 脚本把这几件事变成**硬失败**(都在任何上传之前):
189
+
190
+ - **工作区不干净** → 默认拒(发布会把它们 `git add -A` 一起提交),`--allow-dirty` 才放行;
191
+ - **目标版本必须高于当前版本**;目标版本若在源上**已完整存在**,直接拒(要补发用 `--resume`);
192
+ - **npm 没登录** → 就地拉起 `npm login`;
193
+ - **彩排不过**(`publish --dry-run` 打不出包)→ 零上传退出。
194
+
195
+ 只能自己盯、脚本不查的两条:
161
196
 
162
197
  - **`dist/` 在 `.gitignore` 里**:npm 打包的兜底就是 `.gitignore`,不在 `files` 里显式列出
163
- 就会漏掉构建产物 —— 症状不是报错,而是"别人装上后宿主侧起不来";
164
- - 版本号在源上已被占(pnpm 到最后一步才报,白等一轮门禁);
165
- - `private: true` 没摘;
198
+ 就会漏掉构建产物 —— 症状不是报错,而是"别人装上后宿主侧起不来"(外壳与 `module-hello` 的
199
+ `files` 都显式列了 `dist` / `client.js`,但发布脚本不核这件事);
166
200
  - **随包脚本的可执行位**:`pnpm publish` 只给 `package.json#bin` 点名的那**一个**文件 755,
167
201
  其余在 tarball 里一律 644(`npm pack` 会保留,而 git 里的 x 位不进 tarball)——
168
202
  所以随包脚本一律不吃 x 位:启动器里 `bash` 呼 `seed-profile.sh`、opencli 那条稳定路径写成
169
- 自己拥有的转发脚本,`selfcheck` 有一条判据钉着。这类错**本机复现不出来**,只在别人机器上炸
203
+ 自己拥有的转发脚本。这条由外壳 `selfcheck` 的判据钉着(门禁里跑)。
204
+ 这类错**本机复现不出来**,只在别人机器上炸
170
205
  (0.1.6 就是这么坏在 `seed-profile.sh: Permission denied` 上的,见
171
206
  [`.scratch/dsh-ui-alignment/spec.md`](../../../.scratch/dsh-ui-alignment/spec.md) §18.41)。
172
207
 
173
- ## 七、版本与兼容(实话)
208
+ ## 八、版本与兼容(实话)
174
209
 
175
210
  `package.json` 的 `peerDependencies` 里**没有** `@deepseek-ai/dsh`,这是有意的:
176
211
  dsh 目前是 `0.1.7-rc.2`(预发布),声明范围会让版本不一致的人**装不上**;不声明则是
@@ -195,29 +230,31 @@ dsh 会用这条做**安装预检**(`evaluatePluginCompatibility`),不满
195
230
  ## 发布(一个脚本,一条命令)
196
231
 
197
232
  ```bash
198
- pnpm release # 门禁 → 计划 → 逐包:登录 → 版本没被占 → 打包清单 → 发
199
- pnpm release:dry # 不联网:门禁 → 计划 → 打包清单校验
233
+ pnpm release # 门禁 → 选版本号 → 构建 → 彩排 → 逐包上传 → git 收尾
234
+ pnpm release:dry # 同上,但 publish 只 dry-run:零上传,结束回滚版本号
200
235
  ```
201
236
 
202
- `scripts/release.mjs` 是**唯一**的发布入口(曾经的 `publish.sh` + `release-plan.mjs` 已合并进来)。
203
- 谁要发、发到哪、要发什么,全部从各包自述清单读,不写死名单:
237
+ `scripts/release.mjs` 是**唯一**的发布入口。发什么、发到哪、谁先谁后,都从各包自述清单读:
204
238
 
205
- - **发不发** = 有 `publishConfig.registry`,或 `publishConfig.access === 'public'`(默认 npmjs);`private: true` 一律不发;
206
- - **发什么** = 包自己声明的 `exports` / `bin` / `dsh.bundle.patch` 那几份文件必须在(`dist/`、`client.js` 在 `.gitignore` 里,漏了 `files` 的症状是"装上后宿主侧起不来",不是报错);
207
- - **什么顺序** = 依赖优先(`shared` → `core` → 用到它们的模块);依赖的包这次不发就报错;
208
- - **公网包**用 `--access public`,内网源不加这个参数。
239
+ - **发不发** = `packages/*` 一级目录里,非 `private` 且自述了 `publishConfig`(`access` 或 `registry`);
240
+ 私有模块必须显式写发布目标这条,由门禁的 `contracts` 判据守着(不许默认落到公网);
241
+ - **发到哪** = 包自己的 `publishConfig.registry`(pnpm 在 recursive publish 里逐包取它),没写的
242
+ 落到脚本顶部的 `REGISTRY`(npmjs);本仓现在这 4 个包都发公网,命令一律带 `--access public`。
243
+ 注意 `inspectTarget` **只查 npmjs** —— "这个版本发出去没有"对发内网源的包看不准;
244
+ - **什么顺序** = pnpm 自己拓扑排序(`shared` → `core` → 用到它们的模块);
245
+ - **发什么** = pnpm 按各包 `files` / `.gitignore` 打包;**打包清单没人替你核**(见上一章
246
+ 「只能自己盯」)。要看清单用 `dsh-wb-module publish`:它只 dry-run、打印清单,不发布。
209
247
 
210
- 它替你把四件事变成硬失败:**门禁绿 · 计划成立 · 打包清单真的有产物 · 这个版本号在源上还没人占**。
211
- 发布用 `pnpm publish`(不是 `npm publish`):只有 pnpm 会把 `workspace:*` 改写成真实版本,
248
+ 发布必须走 `pnpm publish`(不是 `npm publish`):只有 pnpm 会把 `workspace:*` 改写成真实版本,
212
249
  `npm pack` / `npm publish` 不会 —— 直接发别人装不上。
213
250
 
214
- ### 版本同步规则(脚本会替你拦)
251
+ ### 版本同步规则(约定 —— 门禁只守其中一条)
215
252
 
216
253
  | 关系 | 规则 |
217
254
  | --- | --- |
218
- | 模块 ↔ 外壳 | 模块 `peerDependencies['@dsh-workbench/core'] = ^0.1.0`。**`^0.x` 锁次版本**:外壳升到 `0.2.0` 时 `^0.1.0` 不再满足,脚本当场报 —— 要把每个模块的范围一起改(或一起升) |
255
+ | 模块 ↔ 外壳 | 模块 `peerDependencies['@dsh-workbench/core'] = ^0.1.0`。**`^0.x` 锁次版本**:外壳升到 `0.2.0` 时 `^0.1.0` 不再满足 —— 要把每个模块的范围一起改(或一起升)。门禁只守"peer 依赖存在"(`contracts` 判据),**范围合不合体没人替你算**,靠自觉 |
219
256
  | `shared` ↔ 外壳/模块 | 依赖写的是 `workspace:*`,发布时会被改写成**当时那个版本号** → `shared` 必须先发;`shared` 升版后,用到它的包要重新发一次才会带上新范围 |
220
- | `module-tools` ↔ `init` 模板 | 模板里 `@dsh-workbench/core` / `module-tools` 的范围要跟着本仓版本走,否则 `init` 生成的是过期范围(脚本已检查这一条) |
257
+ | `module-tools` ↔ `init` 模板 | 模板里 `@dsh-workbench/core` / `module-tools` 的范围要跟着本仓版本走,否则 `init` 生成的是过期范围 —— **现在没有任何判据检查它**(上一版 release 脚本查过,换成现在这份之后这条没了),升外壳次版本时记得一起改 |
221
258
 
222
259
  ### 中断了怎么续跑(发布是可中断的)
223
260
 
@@ -228,7 +265,8 @@ pnpm release:resume # 从现场恢复;**已在源上的包自动跳过**
228
265
  ```
229
266
 
230
267
  - **不带 `--resume` 重跑会被拒**:目标版本必须高于当前版本;源上若已有同名同版本,脚本会说"先提版本号"。这正是为续跑设计的语义(不是 bug)。
268
+ - **`--resume` 照样跑一遍门禁**:断了之后很可能改过包,而门禁整次发布只有 preflight 那一处。真要跳过用 `--skip-gate`(发布不会再补跑一遍)。
231
269
  - `pnpm release:dry` 是**彩排**:改版本号 → 构建 → `publish --dry-run` → **回滚本地版本号**,不上传。
232
270
  - 发布**成功之后**才做 git 收尾:`git add -A` → `commit -m "chore: 发布版本 v<ver>"` → `tag v<ver>` → `push` → `push origin v<ver>`。git 收尾失败时包已经发出去了,脚本会把该手打的命令打出来,并提示可以 `pnpm release:resume`。
233
- - 常用旗标:`--target <ver>` 跳出版本选择 · `-y/--yes` 跳过确认 · `--allow-dirty` · `--skip-gate`(不推荐) · `--skip-rehearsal`(不推荐)。
271
+ - 常用旗标:`--target <ver>` 跳出版本选择 · `-y/--yes` 跳过确认 · `--allow-dirty` · `--skip-gate` / `--skip-rehearsal`(都不推荐) · `--skip-build`(用磁盘上现有产物 —— `prepack` 已关,不再兜底重建)。
234
272
  - 依赖:`enquirer`(交互选择版本)与 `semver`(版本比较)是根上的 devDependencies。
@@ -28,7 +28,28 @@
28
28
  前提:`dsh` 和 `pnpm` 在 PATH 上(`dsh plugin` 是转发给 pnpm 的),
29
29
  且 `~/.dsh/profiles/web` 已存在(跑过一次 `dsh web` 即可)。
30
30
 
31
- **给别人用**(发包到内网源、对方要登录内网源 / Coding)见 [`HANDOFF.md`](HANDOFF.md)。
31
+ **给别人用**(两条路:插件页装进他自己的 web / 独立实例 `setup`;包在公网 npm 上)见 [`HANDOFF.md`](HANDOFF.md)。
32
+
33
+ ### 变体:装进**当前** profile(在 `dsh web` 里装,不起第二个实例)
34
+
35
+ `dsh web` 侧栏 **Plugins → Add plugin** → 填 `@dsh-workbench/core`(公网 npm 上有,默认源即可)
36
+ → **Install → Enable now** → **刷新页面**。装的就是 `dsh web` 自己那个 profile。
37
+
38
+ **完成判据**:刷新后左栏是工作台的、右侧出现面板列;`$DSH_HOME/workbench/opencli/bin/opencli`
39
+ 存在且指向本包 `bin/opencli-isolated`;`$DSH_HOME/workbench/bin/dsh-wb-module` 可执行
40
+ (这三条不再依赖启动器 —— 宿主自举写的)。
41
+
42
+ 这条路上没有启动器,缺的几件由**宿主自举**补齐(建目录、写隔离 opencli 入口、前插 `PATH`、
43
+ 后台建 DIY 工坊骨架),可写根由 bundle 层那一行 `sandbox-policy` 放行 —— 见
44
+ [ADR-0037](../../../docs/adr/0037-workbench-installs-into-any-profile.md)。
45
+
46
+ 自举是**静默**的:它写了什么只在宿主日志里,后台脚本(工坊骨架 / 依赖)的输出在
47
+ `$DSH_HOME/workbench/seed.log`。DIY 那个工坊“怎么都没建起来”时先看这两个地方。
48
+
49
+ 差别:**会话 / 存储不隔离**(接着用你原来那份)、**没有独立端口与进程**、
50
+ **改外壳源码不热重载**、DIY 工坊依赖是首次启动在后台装的(几百 MB,失败只落日志)。
51
+
52
+ 撤销:Plugins 页卸载 `@dsh-workbench/core`(配置与数据不清);`~/.dsh/workbench/**` 想清就手删。
32
53
 
33
54
  ## 日常
34
55
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dsh-workbench/core",
3
- "version": "0.1.15",
3
+ "version": "0.1.17",
4
4
  "description": "个人工作台:Agent 工作台外壳(左栏 + 常驻对话 + 自研右侧数据面板)。",
5
5
  "type": "module",
6
6
  "icon": "./icon.svg",
@@ -22,7 +22,7 @@
22
22
  "bin": {
23
23
  "dsh-workbench": "./bin/dsh-workbench"
24
24
  },
25
- "//files": "npm 的兜底是 .gitignore —— 而 dist/ 在 .gitignore 里,不显式列出就会**漏掉宿主入口**(exports['.'] 指向它)。发出去之前用 npm pack --dry-run 数一遍 dist/ 有几条。skills/ 是 DIY 模式挂给 agent 的指引(经 skill-filesystem 的 customSkillDirs 挂载),漏了不报错、只是 DIY 的 agent 什么都不知道 —— selfcheck 有一条判据盯着它。",
25
+ "//files": "npm 的兜底是 .gitignore —— 而 dist/ 在 .gitignore 里,不显式列出就会**漏掉宿主入口**(exports['.'] 指向它)。发出去之前用 npm pack --dry-run 数一遍 dist/ 有几条。skills/ 是 DIY 模式挂给 agent 的指引(经 skill-filesystem 的 customSkillDirs 挂载),漏了不报错、只是 DIY 的 agent 什么都不知道 —— selfcheck 有一条判据盯着它。scripts/ 下是**随包出货的脚本**:启动器与宿主自举都要跑它们(seed-profile.sh / seed-workshop.sh),漏一个的症状是「仓库里怎么都对、装出去那台静默什么也没做」。",
26
26
  "files": [
27
27
  "dist",
28
28
  "client.js",
@@ -33,6 +33,7 @@
33
33
  "skills",
34
34
  "preset",
35
35
  "scripts/seed-profile.sh",
36
+ "scripts/seed-workshop.sh",
36
37
  "README.md",
37
38
  "docs/HANDOFF.md",
38
39
  "docs/OPERATIONS.md"
@@ -81,12 +82,12 @@
81
82
  "vite": "^8.3.1",
82
83
  "vitest": "^5.0.2"
83
84
  },
84
- "//publishConfig": "只影响**发布这个包**,不影响别人解析依赖。内网源地址在这一处(交接文档里写给对方的也是这一条)。",
85
+ "//publishConfig": "只影响**发布这个包**,不影响别人解析依赖。现在发**公网 npm**(scripts/release.mjs 走 registry.npmjs.org),所以这里只有 access;哪天真要改发内网源,地址写在这一段的 registry 里(release.mjs 会优先读它)。",
85
86
  "publishConfig": {
86
87
  "access": "public"
87
88
  },
88
89
  "dependencies": {
89
- "@dsh-workbench/shared": "0.1.15"
90
+ "@dsh-workbench/shared": "0.1.17"
90
91
  },
91
92
  "scripts": {
92
93
  "build": "node scripts/build.mjs && node scripts/build-types.mjs",