@dsh-workbench/shared 0.1.0

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 (206) hide show
  1. package/dist/api/blocker-table.d.ts +9 -0
  2. package/dist/api/blocker-table.js +130 -0
  3. package/dist/api/blocker.d.ts +30 -0
  4. package/dist/api/blocker.js +1 -0
  5. package/dist/api/context-ref.d.ts +10 -0
  6. package/dist/api/context-ref.js +40 -0
  7. package/dist/api/dsh-client.d.ts +170 -0
  8. package/dist/api/dsh-client.js +1 -0
  9. package/dist/api/dsh.d.ts +153 -0
  10. package/dist/api/dsh.js +1 -0
  11. package/dist/api/facts.d.ts +27 -0
  12. package/dist/api/facts.js +146 -0
  13. package/dist/api/host-write.d.ts +12 -0
  14. package/dist/api/host-write.js +17 -0
  15. package/dist/api/long-task.d.ts +18 -0
  16. package/dist/api/long-task.js +22 -0
  17. package/dist/api/unit-board.d.ts +14 -0
  18. package/dist/api/unit-board.js +29 -0
  19. package/dist/api/unit.d.ts +54 -0
  20. package/dist/api/unit.js +1 -0
  21. package/dist/api/workbench-client.d.ts +19 -0
  22. package/dist/api/workbench-client.js +33 -0
  23. package/dist/api/workbench-contract.d.ts +32 -0
  24. package/dist/api/workbench-contract.js +1 -0
  25. package/dist/components/ApprovalDetail.d.ts +27 -0
  26. package/dist/components/ApprovalDetail.js +33 -0
  27. package/dist/components/CardHead.d.ts +16 -0
  28. package/dist/components/CardHead.js +14 -0
  29. package/dist/components/ChoiceGroup.d.ts +14 -0
  30. package/dist/components/ChoiceGroup.js +7 -0
  31. package/dist/components/LongTaskRow.d.ts +16 -0
  32. package/dist/components/LongTaskRow.js +28 -0
  33. package/dist/components/MockStrip.d.ts +14 -0
  34. package/dist/components/MockStrip.js +7 -0
  35. package/dist/components/ResourceStates.d.ts +18 -0
  36. package/dist/components/ResourceStates.js +17 -0
  37. package/dist/components/SaveBar.d.ts +12 -0
  38. package/dist/components/SaveBar.js +6 -0
  39. package/dist/components/StateDot.d.ts +5 -0
  40. package/dist/components/StateDot.js +10 -0
  41. package/dist/components/board-module.d.ts +67 -0
  42. package/dist/components/board-module.js +1 -0
  43. package/dist/components/icons.d.ts +18 -0
  44. package/dist/components/icons.js +81 -0
  45. package/dist/config/constants.d.ts +27 -0
  46. package/dist/config/constants.js +27 -0
  47. package/dist/config/storage.d.ts +4 -0
  48. package/dist/config/storage.js +27 -0
  49. package/dist/host/__tests__/fakes.d.ts +41 -0
  50. package/dist/host/__tests__/fakes.js +76 -0
  51. package/dist/host/__tests__/preflight-fixtures.d.ts +19 -0
  52. package/dist/host/__tests__/preflight-fixtures.js +47 -0
  53. package/dist/host/cli-error.d.ts +8 -0
  54. package/dist/host/cli-error.js +70 -0
  55. package/dist/host/context-inject.d.ts +24 -0
  56. package/dist/host/context-inject.js +74 -0
  57. package/dist/host/data-root.d.ts +3 -0
  58. package/dist/host/data-root.js +12 -0
  59. package/dist/host/events.d.ts +9 -0
  60. package/dist/host/events.js +70 -0
  61. package/dist/host/facts.d.ts +24 -0
  62. package/dist/host/facts.js +44 -0
  63. package/dist/host/git.d.ts +55 -0
  64. package/dist/host/git.js +64 -0
  65. package/dist/host/http.d.ts +36 -0
  66. package/dist/host/http.js +242 -0
  67. package/dist/host/long-task.d.ts +58 -0
  68. package/dist/host/long-task.js +148 -0
  69. package/dist/host/mount-units.d.ts +52 -0
  70. package/dist/host/mount-units.js +186 -0
  71. package/dist/host/opencli-runtime.d.ts +22 -0
  72. package/dist/host/opencli-runtime.js +46 -0
  73. package/dist/host/opencli.d.ts +37 -0
  74. package/dist/host/opencli.js +152 -0
  75. package/dist/host/plugin-source.d.ts +31 -0
  76. package/dist/host/plugin-source.js +81 -0
  77. package/dist/host/preflight.d.ts +58 -0
  78. package/dist/host/preflight.js +233 -0
  79. package/dist/host/probe-cache.d.ts +6 -0
  80. package/dist/host/probe-cache.js +19 -0
  81. package/dist/host/probe-libs.d.ts +45 -0
  82. package/dist/host/probe-libs.js +61 -0
  83. package/dist/host/probe-units.d.ts +17 -0
  84. package/dist/host/probe-units.js +102 -0
  85. package/dist/host/probe-verdict.d.ts +7 -0
  86. package/dist/host/probe-verdict.js +143 -0
  87. package/dist/host/provision.d.ts +14 -0
  88. package/dist/host/provision.js +12 -0
  89. package/dist/host/shell.d.ts +14 -0
  90. package/dist/host/shell.js +23 -0
  91. package/dist/host/tool-approval.d.ts +29 -0
  92. package/dist/host/tool-approval.js +190 -0
  93. package/dist/host/tool-kit.d.ts +19 -0
  94. package/dist/host/tool-kit.js +37 -0
  95. package/dist/host/tool-scope.d.ts +32 -0
  96. package/dist/host/tool-scope.js +84 -0
  97. package/dist/host/unit-face.d.ts +66 -0
  98. package/dist/host/unit-face.js +1 -0
  99. package/dist/host/unit-libs.d.ts +37 -0
  100. package/dist/host/unit-libs.js +1 -0
  101. package/dist/host/unit-readiness.d.ts +7 -0
  102. package/dist/host/unit-readiness.js +5 -0
  103. package/dist/host/unit-registry.d.ts +9 -0
  104. package/dist/host/unit-registry.js +33 -0
  105. package/dist/host/value-handoff.d.ts +16 -0
  106. package/dist/host/value-handoff.js +21 -0
  107. package/dist/host/version-range.d.ts +2 -0
  108. package/dist/host/version-range.js +73 -0
  109. package/dist/host/workspace-store.d.ts +19 -0
  110. package/dist/host/workspace-store.js +133 -0
  111. package/dist/host/write-result.d.ts +1 -0
  112. package/dist/host/write-result.js +18 -0
  113. package/dist/host.d.ts +19 -0
  114. package/dist/host.js +19 -0
  115. package/dist/i18n/en.d.ts +2 -0
  116. package/dist/i18n/en.js +162 -0
  117. package/dist/i18n/index.d.ts +172 -0
  118. package/dist/i18n/index.js +3 -0
  119. package/dist/i18n/merge.d.ts +6 -0
  120. package/dist/i18n/merge.js +40 -0
  121. package/dist/i18n/zh.d.ts +163 -0
  122. package/dist/i18n/zh.js +162 -0
  123. package/dist/index.d.ts +33 -0
  124. package/dist/index.js +33 -0
  125. package/dist/lib/blocker-action.d.ts +13 -0
  126. package/dist/lib/blocker-action.js +12 -0
  127. package/dist/lib/busy.d.ts +5 -0
  128. package/dist/lib/busy.js +7 -0
  129. package/dist/lib/react.d.ts +10 -0
  130. package/dist/lib/react.js +9 -0
  131. package/dist/lib/text.d.ts +4 -0
  132. package/dist/lib/text.js +48 -0
  133. package/dist/lib/ui.d.ts +11 -0
  134. package/dist/lib/ui.js +37 -0
  135. package/package.json +44 -0
  136. package/src/api/__tests__/blocker-table.test.ts +253 -0
  137. package/src/api/__tests__/facts.test.ts +274 -0
  138. package/src/api/__tests__/host-write.test.ts +55 -0
  139. package/src/api/__tests__/long-task.test.ts +52 -0
  140. package/src/api/__tests__/unit-board.test.ts +76 -0
  141. package/src/api/blocker-table.ts +155 -0
  142. package/src/api/blocker.ts +25 -0
  143. package/src/api/context-ref.ts +47 -0
  144. package/src/api/dsh-client.ts +176 -0
  145. package/src/api/dsh.ts +156 -0
  146. package/src/api/facts.ts +166 -0
  147. package/src/api/host-write.ts +27 -0
  148. package/src/api/long-task.ts +38 -0
  149. package/src/api/unit-board.ts +44 -0
  150. package/src/api/unit.ts +59 -0
  151. package/src/api/workbench-client.test.ts +92 -0
  152. package/src/api/workbench-client.ts +59 -0
  153. package/src/api/workbench-contract.ts +40 -0
  154. package/src/components/ApprovalDetail.tsx +57 -0
  155. package/src/components/CardHead.tsx +74 -0
  156. package/src/components/ChoiceGroup.tsx +38 -0
  157. package/src/components/LongTaskRow.tsx +65 -0
  158. package/src/components/MockStrip.tsx +40 -0
  159. package/src/components/ResourceStates.tsx +44 -0
  160. package/src/components/SaveBar.tsx +31 -0
  161. package/src/components/StateDot.tsx +31 -0
  162. package/src/components/board-module.ts +73 -0
  163. package/src/components/icons.tsx +232 -0
  164. package/src/config/constants.test.ts +41 -0
  165. package/src/config/constants.ts +52 -0
  166. package/src/config/storage.test.ts +82 -0
  167. package/src/config/storage.ts +25 -0
  168. package/src/host/__tests__/fakes.ts +116 -0
  169. package/src/host/__tests__/opencli-runtime.test.ts +46 -0
  170. package/src/host/cli-error.ts +77 -0
  171. package/src/host/data-root.ts +13 -0
  172. package/src/host/facts.ts +69 -0
  173. package/src/host/git.ts +99 -0
  174. package/src/host/http.ts +311 -0
  175. package/src/host/long-task.ts +235 -0
  176. package/src/host/opencli-runtime.ts +66 -0
  177. package/src/host/opencli.ts +210 -0
  178. package/src/host/plugin-source.ts +145 -0
  179. package/src/host/provision.ts +34 -0
  180. package/src/host/shell.ts +42 -0
  181. package/src/host/tool-approval.ts +243 -0
  182. package/src/host/tool-kit.ts +56 -0
  183. package/src/host/unit-face.ts +65 -0
  184. package/src/host/unit-libs.ts +41 -0
  185. package/src/host/value-handoff.ts +38 -0
  186. package/src/host/workspace-store.ts +161 -0
  187. package/src/host/write-result.ts +16 -0
  188. package/src/host.ts +19 -0
  189. package/src/i18n/en.ts +170 -0
  190. package/src/i18n/index.test.ts +15 -0
  191. package/src/i18n/index.ts +12 -0
  192. package/src/i18n/merge.test.ts +60 -0
  193. package/src/i18n/merge.ts +54 -0
  194. package/src/i18n/zh.test.ts +40 -0
  195. package/src/i18n/zh.ts +170 -0
  196. package/src/index.ts +33 -0
  197. package/src/lib/blocker-action.test.ts +40 -0
  198. package/src/lib/blocker-action.ts +28 -0
  199. package/src/lib/busy.test.ts +27 -0
  200. package/src/lib/busy.ts +11 -0
  201. package/src/lib/react.test.ts +26 -0
  202. package/src/lib/react.ts +16 -0
  203. package/src/lib/text.test.ts +92 -0
  204. package/src/lib/text.ts +46 -0
  205. package/src/lib/ui.test.ts +93 -0
  206. package/src/lib/ui.ts +38 -0
@@ -0,0 +1,37 @@
1
+ import { WorkbenchDataError } from './opencli';
2
+ export function sessionWorkspace(exec) {
3
+ const cwd = exec?.agent?.session?.header?.cwd;
4
+ return typeof cwd === 'string' && cwd.trim() !== '' ? cwd.trim() : null;
5
+ }
6
+ export function textOutput(text) {
7
+ return [{ type: 'text', text }];
8
+ }
9
+ export function textRender(_args, value) {
10
+ const text = value?.text;
11
+ return textOutput(typeof text === 'string' ? text : JSON.stringify(value));
12
+ }
13
+ export const TEXT_OUTPUT = { schema: { type: 'object', properties: {}, additionalProperties: true }, render: textRender };
14
+ export function jsonText(value) {
15
+ return JSON.stringify(value);
16
+ }
17
+ const UNCLASSIFIED = 'cli-unclassified';
18
+ export function commandHintFor(names) {
19
+ return async (command, run) => {
20
+ try {
21
+ return await run();
22
+ }
23
+ catch (error) {
24
+ if (!(error instanceof WorkbenchDataError) || error.code !== UNCLASSIFIED)
25
+ throw error;
26
+ const advice = `这条命令(${command})在这份 ${names.plugin} 插件里可能不存在(插件版本旧就会缺):` +
27
+ `用 workbench_modules(action=update, id=${names.suiteId})升级插件,或在面板的模块状态里点更新,再重试。`;
28
+ throw new WorkbenchDataError(error.code, `${error.message}。${advice}`, {
29
+ ...(error.detail === undefined ? {} : { detail: error.detail }),
30
+ hint: `升级:workbench_modules(action=update, id=${names.suiteId})。` +
31
+ `终端确认:opencli ${names.plugin} ${command} --help${error.hint === undefined ? '' : `\n${error.hint}`}`,
32
+ ...(error.helpUrl === undefined ? {} : { helpUrl: error.helpUrl }),
33
+ ...(error.project === undefined ? {} : { project: error.project }),
34
+ });
35
+ }
36
+ };
37
+ }
@@ -0,0 +1,32 @@
1
+ import type { DshPromptAssembly } from '@dsh-workbench/shared';
2
+ export interface ScopedToolRegistry {
3
+ restrict(filter: {
4
+ readonly allow?: readonly string[];
5
+ readonly deny?: readonly string[];
6
+ }): () => void;
7
+ }
8
+ export interface ToolScopeAgent {
9
+ readonly ctx?: {
10
+ readonly tools?: ScopedToolRegistry | undefined;
11
+ get?<T = unknown>(name: string): T | undefined;
12
+ } | undefined;
13
+ readonly session?: {
14
+ readonly header?: {
15
+ readonly cwd?: string;
16
+ } | undefined;
17
+ } | undefined;
18
+ }
19
+ export interface ToolScopeOptions {
20
+ deniedTools(workspace: string | null): readonly string[];
21
+ logger?: {
22
+ info?(message: string): void;
23
+ warn?(message: string): void;
24
+ } | undefined;
25
+ }
26
+ export interface ToolScope {
27
+ apply(agent: unknown): void;
28
+ narrow(assembly: DshPromptAssembly, agent: unknown): DshPromptAssembly;
29
+ reapply(): void;
30
+ disposeAll(): void;
31
+ }
32
+ export declare function createToolScope(options: ToolScopeOptions): ToolScope;
@@ -0,0 +1,84 @@
1
+ function agentOf(value) {
2
+ return typeof value === 'object' && value !== null ? value : undefined;
3
+ }
4
+ function workspaceOf(handle) {
5
+ const cwd = handle.session?.header?.cwd;
6
+ return typeof cwd === 'string' && cwd !== '' ? cwd : null;
7
+ }
8
+ export function createToolScope(options) {
9
+ const applied = new WeakMap();
10
+ const seen = new Set();
11
+ function toolsOf(handle) {
12
+ const byProperty = handle.ctx?.tools;
13
+ if (byProperty?.restrict !== undefined)
14
+ return byProperty;
15
+ const byGet = handle.ctx?.get?.('tools');
16
+ return byGet?.restrict === undefined ? undefined : byGet;
17
+ }
18
+ function sync(handle) {
19
+ const tools = toolsOf(handle);
20
+ if (tools === undefined) {
21
+ options.logger?.warn?.('[dsh-workbench] 拿不到 agent 作用域的工具面:这次不收窄(下次再试)');
22
+ return;
23
+ }
24
+ const workspace = workspaceOf(handle);
25
+ const deny = [...options.deniedTools(workspace)];
26
+ const signature = `${workspace ?? ''}\u0000${deny.join(',')}`;
27
+ const previous = applied.get(handle);
28
+ if (previous !== undefined && previous.signature === signature)
29
+ return;
30
+ if (previous === undefined)
31
+ seen.add(new WeakRef(handle));
32
+ previous?.dispose();
33
+ if (deny.length === 0) {
34
+ options.logger?.info?.('[dsh-workbench] 这个工作区启用了全部套件:不收窄工具面');
35
+ applied.delete(handle);
36
+ return;
37
+ }
38
+ try {
39
+ applied.set(handle, { signature, dispose: tools.restrict({ deny }) });
40
+ options.logger?.info?.(`[dsh-workbench] 按工作区收窄工具面:${workspace ?? '(未知工作区)'} 里收起 ${deny.length} 个工具(${deny.join(', ')})`);
41
+ }
42
+ catch (error) {
43
+ options.logger?.warn?.(`[dsh-workbench] 收窄工具面失败:${String(error)}`);
44
+ }
45
+ }
46
+ return {
47
+ apply(agent) {
48
+ const handle = agentOf(agent);
49
+ if (handle === undefined)
50
+ return;
51
+ sync(handle);
52
+ },
53
+ narrow(assembly, agent) {
54
+ const handle = agentOf(agent);
55
+ const tools = assembly?.tools;
56
+ if (handle === undefined || tools === undefined)
57
+ return assembly;
58
+ const deny = new Set(options.deniedTools(workspaceOf(handle)));
59
+ if (deny.size === 0)
60
+ return assembly;
61
+ const kept = tools.filter((tool) => !deny.has(tool.name));
62
+ return kept.length === tools.length ? assembly : { ...assembly, tools: kept };
63
+ },
64
+ reapply() {
65
+ for (const ref of [...seen]) {
66
+ const handle = ref.deref();
67
+ if (handle === undefined)
68
+ seen.delete(ref);
69
+ else
70
+ sync(handle);
71
+ }
72
+ },
73
+ disposeAll() {
74
+ for (const ref of [...seen]) {
75
+ const handle = ref.deref();
76
+ if (handle === undefined)
77
+ continue;
78
+ applied.get(handle)?.dispose();
79
+ applied.delete(handle);
80
+ }
81
+ seen.clear();
82
+ },
83
+ };
84
+ }
@@ -0,0 +1,66 @@
1
+ import type { DshHostContext } from '../api/dsh.js';
2
+ import type { UnitManifest } from '../api/unit.js';
3
+ import type { RouteTable } from '../host/http.js';
4
+ import type { LongTaskBase, LongTaskHost, LongTaskRegistry } from '../host/long-task.js';
5
+ import type { ProvisionHandler } from '../host/provision.js';
6
+ import type { UnitCapability } from './unit-libs';
7
+ import type { ApprovalCall, ApprovalFacts } from '../host/tool-approval.js';
8
+ import type { ValueHandoff } from '../host/value-handoff.js';
9
+ import type { WorkspaceStore } from '../host/workspace-store.js';
10
+ export interface UnitMountDeps {
11
+ readonly ctx: DshHostContext;
12
+ readonly logger?: {
13
+ info?(message: string): void;
14
+ warn?(message: string): void;
15
+ } | undefined;
16
+ readonly manifests: () => readonly UnitManifest[];
17
+ readonly opencliCommand?: string;
18
+ readonly store: WorkspaceStore;
19
+ readonly values?: ValueHandoff | undefined;
20
+ readonly probe?: (unit: string, options?: {
21
+ readonly force?: boolean;
22
+ }) => Promise<UnitCapability | undefined>;
23
+ readonly longTasks?: LongTaskHost | undefined;
24
+ }
25
+ export type ContextRenderer = (ref: {
26
+ readonly kind: string;
27
+ readonly parts: readonly string[];
28
+ }) => Promise<string> | string;
29
+ export type UnitWarmRead = readonly string[];
30
+ export interface UnitCall {
31
+ readonly source: string;
32
+ readonly args: Readonly<Record<string, unknown>>;
33
+ readonly result: unknown;
34
+ }
35
+ export interface UnitMountResult {
36
+ readonly routes: RouteTable;
37
+ registerTools(): Array<() => void>;
38
+ readonly factsOf?: ((call: UnitCall) => readonly string[] | undefined) | undefined;
39
+ readonly gate?: {
40
+ deniedTools(workspace: string | null): readonly string[];
41
+ subscribe(listener: () => void): () => void;
42
+ };
43
+ readonly context?: {
44
+ readonly kinds: readonly string[];
45
+ render(ref: {
46
+ readonly kind: string;
47
+ readonly parts: readonly string[];
48
+ }): Promise<string> | string;
49
+ };
50
+ readonly warmReads?: (() => readonly UnitWarmRead[]) | undefined;
51
+ readonly prefs?: {
52
+ readonly resource: string;
53
+ handle(payload: unknown): unknown;
54
+ };
55
+ readonly actions?: readonly {
56
+ readonly resource: string;
57
+ handle(payload: unknown): unknown;
58
+ }[];
59
+ readonly provision?: ProvisionHandler;
60
+ readonly longTasks?: LongTaskRegistry<LongTaskBase> | undefined;
61
+ readonly approvalDetail?: ((call: ApprovalCall) => ApprovalFacts | undefined | Promise<ApprovalFacts | undefined>) | undefined;
62
+ }
63
+ export interface UnitHostFace {
64
+ readonly manifest: UnitManifest;
65
+ mount(deps: UnitMountDeps): UnitMountResult;
66
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,37 @@
1
+ import type { PluginSourceProblem } from './plugin-source';
2
+ import type { PluginRelease, UnitLibNeed, UnitPluginNeed } from '../api/unit';
3
+ export interface UnitLibState {
4
+ readonly name: string;
5
+ readonly purpose: string;
6
+ readonly status: 'ok' | 'outdated' | 'missing' | 'unsatisfied';
7
+ readonly installed?: string;
8
+ readonly latest?: string;
9
+ readonly command?: string;
10
+ }
11
+ export interface UnitCommands {
12
+ readonly unit: string;
13
+ readonly plugins?: readonly UnitPluginNeed[];
14
+ readonly libs?: readonly UnitLibNeed[];
15
+ }
16
+ export interface UnitPluginState {
17
+ readonly name: string;
18
+ readonly status: "ok" | "outdated" | "missing";
19
+ readonly missing: readonly string[];
20
+ readonly install?: "ready" | PluginSourceProblem;
21
+ readonly command?: string;
22
+ readonly release?: PluginRelease;
23
+ }
24
+ export interface UnitCapability {
25
+ readonly unit: string;
26
+ readonly status: "ok" | "missing-commands" | "missing-plugin" | "unchecked";
27
+ readonly plugin?: string;
28
+ readonly required: readonly string[];
29
+ readonly missing: readonly string[];
30
+ readonly detail: string;
31
+ readonly command?: string;
32
+ readonly fix?: "install" | "update";
33
+ readonly install?: "ready" | PluginSourceProblem;
34
+ readonly plugins?: readonly UnitPluginState[];
35
+ readonly libs?: readonly UnitLibState[];
36
+ readonly released?: boolean;
37
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,7 @@
1
+ import type { UnitManifest } from '@dsh-workbench/shared';
2
+ export interface UnitReadiness {
3
+ readonly canMount: boolean;
4
+ readonly missingHard: readonly string[];
5
+ readonly missingSoft: readonly string[];
6
+ }
7
+ export declare function unitReadiness(manifest: UnitManifest, lookup: (name: string) => unknown): UnitReadiness;
@@ -0,0 +1,5 @@
1
+ export function unitReadiness(manifest, lookup) {
2
+ const missingHard = manifest.host.inject.filter((name) => lookup(name) === undefined);
3
+ const missingSoft = (manifest.host.optional ?? []).filter((name) => lookup(name) === undefined);
4
+ return { canMount: missingHard.length === 0, missingHard, missingSoft };
5
+ }
@@ -0,0 +1,9 @@
1
+ import type { UnitManifest } from '@dsh-workbench/shared';
2
+ import type { UnitHostFace } from './unit-face';
3
+ export interface UnitRegistry {
4
+ register(face: UnitHostFace): () => void;
5
+ all(): readonly UnitHostFace[];
6
+ manifests(): readonly UnitManifest[];
7
+ subscribe(listener: () => void): () => void;
8
+ }
9
+ export declare function createUnitRegistry(): UnitRegistry;
@@ -0,0 +1,33 @@
1
+ export function createUnitRegistry() {
2
+ const faces = [];
3
+ const ids = new Set();
4
+ const listeners = new Set();
5
+ function notify() {
6
+ for (const listener of listeners)
7
+ listener();
8
+ }
9
+ return {
10
+ register(face) {
11
+ const id = face.manifest.id;
12
+ if (ids.has(id))
13
+ throw new Error(`套件 id 重复:${id}(id 会进持久化配置,不能撞)`);
14
+ ids.add(id);
15
+ faces.push(face);
16
+ notify();
17
+ return () => {
18
+ const index = faces.indexOf(face);
19
+ if (index < 0)
20
+ return;
21
+ faces.splice(index, 1);
22
+ ids.delete(id);
23
+ notify();
24
+ };
25
+ },
26
+ all: () => faces,
27
+ manifests: () => faces.map((face) => face.manifest),
28
+ subscribe(listener) {
29
+ listeners.add(listener);
30
+ return () => listeners.delete(listener);
31
+ },
32
+ };
33
+ }
@@ -0,0 +1,16 @@
1
+ export interface HandoffEntry {
2
+ readonly fact: string;
3
+ readonly key: string;
4
+ }
5
+ export interface HandoffValue {
6
+ readonly value: unknown;
7
+ readonly at: number;
8
+ }
9
+ export interface ValueHandoff {
10
+ hand(entry: HandoffEntry, value: unknown, now?: number): void;
11
+ take(entry: HandoffEntry, now?: number): HandoffValue | undefined;
12
+ }
13
+ export declare function handoffId(entry: HandoffEntry): string;
14
+ export declare function createValueHandoff(options?: {
15
+ ttlMs?: number;
16
+ }): ValueHandoff;
@@ -0,0 +1,21 @@
1
+ import { HANDOFF_TTL_MS } from '../config/constants.js';
2
+ export function handoffId(entry) {
3
+ return `${String(entry.fact.length)}:${entry.fact}${entry.key}`;
4
+ }
5
+ export function createValueHandoff(options = {}) {
6
+ const ttlMs = options.ttlMs ?? HANDOFF_TTL_MS;
7
+ const held = new Map();
8
+ return {
9
+ hand(entry, value, now = Date.now()) {
10
+ held.set(handoffId(entry), { value, at: now });
11
+ },
12
+ take(entry, now = Date.now()) {
13
+ const id = handoffId(entry);
14
+ const hit = held.get(id);
15
+ if (hit === undefined)
16
+ return undefined;
17
+ held.delete(id);
18
+ return now - hit.at >= ttlMs ? undefined : hit;
19
+ },
20
+ };
21
+ }
@@ -0,0 +1,2 @@
1
+ export type VersionVerdict = 'satisfied' | 'unsatisfied' | 'malformed';
2
+ export declare function checkVersionRange(contract: string | undefined, version: string): VersionVerdict;
@@ -0,0 +1,73 @@
1
+ import { compareVersions } from './release';
2
+ function readable(version) {
3
+ return compareVersions(version, '0.0.0') !== undefined;
4
+ }
5
+ function segmentsOf(version) {
6
+ if (!readable(version))
7
+ return undefined;
8
+ return version.split('.').map((part) => Number.parseInt(part, 10));
9
+ }
10
+ function caretUpper(base) {
11
+ const [major = 0, minor = 0, patch = 0] = base;
12
+ if (major > 0)
13
+ return `${major + 1}.0.0`;
14
+ return minor > 0 ? `0.${minor + 1}.0` : `0.0.${patch + 1}`;
15
+ }
16
+ function tildeUpper(base) {
17
+ const [major = 0, minor = 0] = base;
18
+ return `${major}.${minor + 1}.0`;
19
+ }
20
+ function comparator(token, version) {
21
+ const match = /^(>=|<=|>|<|=|\^|~)?\s*(.+)$/.exec(token.trim());
22
+ if (match === null)
23
+ return undefined;
24
+ const [, operator = '', raw = ''] = match;
25
+ const base = segmentsOf(raw.trim());
26
+ if (base === undefined)
27
+ return undefined;
28
+ const at = (other) => compareVersions(version, other);
29
+ switch (operator) {
30
+ case '^':
31
+ return (at(raw) ?? -1) >= 0 && (at(caretUpper(base)) ?? 1) < 0;
32
+ case '~':
33
+ return (at(raw) ?? -1) >= 0 && (at(tildeUpper(base)) ?? 1) < 0;
34
+ default:
35
+ break;
36
+ }
37
+ const compared = at(raw.trim());
38
+ if (compared === undefined)
39
+ return undefined;
40
+ switch (operator) {
41
+ case '>=':
42
+ return compared >= 0;
43
+ case '<=':
44
+ return compared <= 0;
45
+ case '>':
46
+ return compared > 0;
47
+ case '<':
48
+ return compared < 0;
49
+ case '=':
50
+ case '':
51
+ return compared === 0;
52
+ default:
53
+ return undefined;
54
+ }
55
+ }
56
+ export function checkVersionRange(contract, version) {
57
+ const spec = contract?.trim() ?? '';
58
+ if (spec === '')
59
+ return 'satisfied';
60
+ if (!readable(version))
61
+ return 'malformed';
62
+ const tokens = spec.split(/\s+/).filter((token) => token !== '');
63
+ if (tokens.length === 0)
64
+ return 'satisfied';
65
+ for (const token of tokens) {
66
+ const verdict = comparator(token, version);
67
+ if (verdict === undefined)
68
+ return 'malformed';
69
+ if (!verdict)
70
+ return 'unsatisfied';
71
+ }
72
+ return 'satisfied';
73
+ }
@@ -0,0 +1,19 @@
1
+ export interface ModuleStore {
2
+ readonly dir: string;
3
+ read(name: string): unknown | null;
4
+ write(name: string, value: unknown): void;
5
+ remove(name: string): void;
6
+ list(): string[];
7
+ }
8
+ export interface WorkspaceStore {
9
+ for(unitId: string, workspace: string | null): ModuleStore;
10
+ machine(unitId: string): ModuleStore;
11
+ scan(unitId: string): ModuleStore[];
12
+ }
13
+ export declare function workspaceKey(workspace: string): string;
14
+ export declare function createWorkspaceStore(options: {
15
+ readonly root?: string | null | undefined;
16
+ readonly logger?: {
17
+ warn?(message: string): void;
18
+ } | undefined;
19
+ }): WorkspaceStore;
@@ -0,0 +1,133 @@
1
+ import { createHash } from 'node:crypto';
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import { workspacesRoot } from '../host/data-root.js';
5
+ function slug(raw, max = 40) {
6
+ const cleaned = raw
7
+ .replace(/[^A-Za-z0-9._-]+/g, '-')
8
+ .replace(/^[-.]+|[-.]+$/g, '')
9
+ .slice(0, max);
10
+ return cleaned === '' ? 'workspace' : cleaned;
11
+ }
12
+ export function workspaceKey(workspace) {
13
+ const abs = path.resolve(workspace);
14
+ return `${slug(path.basename(abs))}-${createHash('sha1').update(abs).digest('hex').slice(0, 8)}`;
15
+ }
16
+ function inside(dir, name) {
17
+ const trimmed = name.trim();
18
+ if (trimmed === '' || trimmed === '.' || trimmed === '..') {
19
+ throw new Error(`模块文件夹里只接受相对文件名,收到的是「${name}」`);
20
+ }
21
+ if (trimmed.includes('\0') || path.isAbsolute(trimmed)) {
22
+ throw new Error(`模块只能写自己的文件夹:不接受绝对路径「${name}」`);
23
+ }
24
+ const full = path.resolve(dir, trimmed);
25
+ if (full !== dir && !full.startsWith(dir + path.sep)) {
26
+ throw new Error(`模块只能写自己的文件夹:路径越界「${name}」`);
27
+ }
28
+ return full;
29
+ }
30
+ function memoryStore(dir) {
31
+ const files = new Map();
32
+ return {
33
+ dir,
34
+ read: (name) => {
35
+ inside(dir, name);
36
+ return files.get(name) ?? null;
37
+ },
38
+ write: (name, value) => {
39
+ inside(dir, name);
40
+ files.set(name, value);
41
+ },
42
+ remove: (name) => {
43
+ inside(dir, name);
44
+ files.delete(name);
45
+ },
46
+ list: () => [...files.keys()].sort(),
47
+ };
48
+ }
49
+ function fileStore(dir) {
50
+ const ensureFor = (file) => {
51
+ fs.mkdirSync(path.dirname(file), { recursive: true });
52
+ };
53
+ return {
54
+ dir,
55
+ read: (name) => {
56
+ const file = inside(dir, name);
57
+ try {
58
+ return JSON.parse(fs.readFileSync(file, 'utf8'));
59
+ }
60
+ catch {
61
+ return null;
62
+ }
63
+ },
64
+ write: (name, value) => {
65
+ const file = inside(dir, name);
66
+ ensureFor(file);
67
+ const tmp = `${file}.tmp`;
68
+ fs.writeFileSync(tmp, `${JSON.stringify(value, null, 2)}\n`, 'utf8');
69
+ fs.renameSync(tmp, file);
70
+ },
71
+ remove: (name) => {
72
+ const file = inside(dir, name);
73
+ try {
74
+ fs.unlinkSync(file);
75
+ }
76
+ catch {
77
+ }
78
+ },
79
+ list: () => {
80
+ try {
81
+ return fs
82
+ .readdirSync(dir, { withFileTypes: true })
83
+ .filter((entry) => entry.isFile())
84
+ .map((entry) => entry.name)
85
+ .sort();
86
+ }
87
+ catch {
88
+ return [];
89
+ }
90
+ },
91
+ };
92
+ }
93
+ export function createWorkspaceStore(options) {
94
+ const root = options.root === null ? null : options.root ?? workspacesRoot();
95
+ let warned = false;
96
+ const memory = new Map();
97
+ const fallback = (dir) => {
98
+ if (!warned) {
99
+ warned = true;
100
+ options.logger?.warn?.('[dsh-workbench] 没有可写的数据根:模块配置只在本进程内生效(见 shared/api/host/data-root.ts)');
101
+ }
102
+ const hit = memory.get(dir);
103
+ if (hit !== undefined)
104
+ return hit;
105
+ const created = memoryStore(dir);
106
+ memory.set(dir, created);
107
+ return created;
108
+ };
109
+ const open = (parts) => root === null
110
+ ? fallback(path.join('/_memory', ...parts))
111
+ : fileStore(path.join(root, ...parts.map((part) => slug(part))));
112
+ return {
113
+ for: (unitId, workspace) => workspace === null || workspace.trim() === ''
114
+ ? fallback(path.join('/_memory', slug(unitId)))
115
+ : open([workspaceKey(workspace), unitId]),
116
+ machine: (unitId) => open(['_machine', unitId]),
117
+ scan: (unitId) => {
118
+ if (root === null)
119
+ return [];
120
+ let entries;
121
+ try {
122
+ entries = fs.readdirSync(root, { withFileTypes: true })
123
+ .filter((entry) => entry.isDirectory() && !entry.name.startsWith('_'))
124
+ .map((entry) => entry.name)
125
+ .sort();
126
+ }
127
+ catch {
128
+ return [];
129
+ }
130
+ return entries.map((key) => fileStore(path.join(root, key, slug(unitId))));
131
+ },
132
+ };
133
+ }
@@ -0,0 +1 @@
1
+ export declare function writeResultForClient(value: unknown, code: string): unknown;
@@ -0,0 +1,18 @@
1
+ export function writeResultForClient(value, code) {
2
+ if (typeof value !== 'object' || value === null)
3
+ return value;
4
+ const box = value;
5
+ const existing = box['error'];
6
+ if (box['ok'] !== false || (typeof existing === 'object' && existing !== null))
7
+ return value;
8
+ const text = box['text'];
9
+ const detail = box['detail'];
10
+ return {
11
+ ok: false,
12
+ error: {
13
+ code,
14
+ message: typeof text === 'string' && text.trim() !== '' ? text : '这一步没有成功,也没说为什么',
15
+ ...(typeof detail === 'string' ? { detail } : {}),
16
+ },
17
+ };
18
+ }
package/dist/host.d.ts ADDED
@@ -0,0 +1,19 @@
1
+ export * from './host/__tests__/fakes';
2
+ export * from './host/cli-error';
3
+ export * from './host/data-root';
4
+ export * from './host/facts';
5
+ export * from './host/git';
6
+ export * from './host/http';
7
+ export * from './host/long-task';
8
+ export * from './host/opencli-runtime';
9
+ export * from './host/opencli';
10
+ export * from './host/plugin-source';
11
+ export * from './host/provision';
12
+ export * from './host/shell';
13
+ export * from './host/tool-approval';
14
+ export * from './host/tool-kit';
15
+ export * from './host/unit-face';
16
+ export * from './host/unit-libs';
17
+ export * from './host/value-handoff';
18
+ export * from './host/workspace-store';
19
+ export * from './host/write-result';
package/dist/host.js ADDED
@@ -0,0 +1,19 @@
1
+ export * from './host/__tests__/fakes';
2
+ export * from './host/cli-error';
3
+ export * from './host/data-root';
4
+ export * from './host/facts';
5
+ export * from './host/git';
6
+ export * from './host/http';
7
+ export * from './host/long-task';
8
+ export * from './host/opencli-runtime';
9
+ export * from './host/opencli';
10
+ export * from './host/plugin-source';
11
+ export * from './host/provision';
12
+ export * from './host/shell';
13
+ export * from './host/tool-approval';
14
+ export * from './host/tool-kit';
15
+ export * from './host/unit-face';
16
+ export * from './host/unit-libs';
17
+ export * from './host/value-handoff';
18
+ export * from './host/workspace-store';
19
+ export * from './host/write-result';
@@ -0,0 +1,2 @@
1
+ import type { WorkbenchMessages } from './zh';
2
+ export declare const en: Record<keyof WorkbenchMessages, string>;