@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.
- package/dist/api/blocker-table.d.ts +9 -0
- package/dist/api/blocker-table.js +130 -0
- package/dist/api/blocker.d.ts +30 -0
- package/dist/api/blocker.js +1 -0
- package/dist/api/context-ref.d.ts +10 -0
- package/dist/api/context-ref.js +40 -0
- package/dist/api/dsh-client.d.ts +170 -0
- package/dist/api/dsh-client.js +1 -0
- package/dist/api/dsh.d.ts +153 -0
- package/dist/api/dsh.js +1 -0
- package/dist/api/facts.d.ts +27 -0
- package/dist/api/facts.js +146 -0
- package/dist/api/host-write.d.ts +12 -0
- package/dist/api/host-write.js +17 -0
- package/dist/api/long-task.d.ts +18 -0
- package/dist/api/long-task.js +22 -0
- package/dist/api/unit-board.d.ts +14 -0
- package/dist/api/unit-board.js +29 -0
- package/dist/api/unit.d.ts +54 -0
- package/dist/api/unit.js +1 -0
- package/dist/api/workbench-client.d.ts +19 -0
- package/dist/api/workbench-client.js +33 -0
- package/dist/api/workbench-contract.d.ts +32 -0
- package/dist/api/workbench-contract.js +1 -0
- package/dist/components/ApprovalDetail.d.ts +27 -0
- package/dist/components/ApprovalDetail.js +33 -0
- package/dist/components/CardHead.d.ts +16 -0
- package/dist/components/CardHead.js +14 -0
- package/dist/components/ChoiceGroup.d.ts +14 -0
- package/dist/components/ChoiceGroup.js +7 -0
- package/dist/components/LongTaskRow.d.ts +16 -0
- package/dist/components/LongTaskRow.js +28 -0
- package/dist/components/MockStrip.d.ts +14 -0
- package/dist/components/MockStrip.js +7 -0
- package/dist/components/ResourceStates.d.ts +18 -0
- package/dist/components/ResourceStates.js +17 -0
- package/dist/components/SaveBar.d.ts +12 -0
- package/dist/components/SaveBar.js +6 -0
- package/dist/components/StateDot.d.ts +5 -0
- package/dist/components/StateDot.js +10 -0
- package/dist/components/board-module.d.ts +67 -0
- package/dist/components/board-module.js +1 -0
- package/dist/components/icons.d.ts +18 -0
- package/dist/components/icons.js +81 -0
- package/dist/config/constants.d.ts +27 -0
- package/dist/config/constants.js +27 -0
- package/dist/config/storage.d.ts +4 -0
- package/dist/config/storage.js +27 -0
- package/dist/host/__tests__/fakes.d.ts +41 -0
- package/dist/host/__tests__/fakes.js +76 -0
- package/dist/host/__tests__/preflight-fixtures.d.ts +19 -0
- package/dist/host/__tests__/preflight-fixtures.js +47 -0
- package/dist/host/cli-error.d.ts +8 -0
- package/dist/host/cli-error.js +70 -0
- package/dist/host/context-inject.d.ts +24 -0
- package/dist/host/context-inject.js +74 -0
- package/dist/host/data-root.d.ts +3 -0
- package/dist/host/data-root.js +12 -0
- package/dist/host/events.d.ts +9 -0
- package/dist/host/events.js +70 -0
- package/dist/host/facts.d.ts +24 -0
- package/dist/host/facts.js +44 -0
- package/dist/host/git.d.ts +55 -0
- package/dist/host/git.js +64 -0
- package/dist/host/http.d.ts +36 -0
- package/dist/host/http.js +242 -0
- package/dist/host/long-task.d.ts +58 -0
- package/dist/host/long-task.js +148 -0
- package/dist/host/mount-units.d.ts +52 -0
- package/dist/host/mount-units.js +186 -0
- package/dist/host/opencli-runtime.d.ts +22 -0
- package/dist/host/opencli-runtime.js +46 -0
- package/dist/host/opencli.d.ts +37 -0
- package/dist/host/opencli.js +152 -0
- package/dist/host/plugin-source.d.ts +31 -0
- package/dist/host/plugin-source.js +81 -0
- package/dist/host/preflight.d.ts +58 -0
- package/dist/host/preflight.js +233 -0
- package/dist/host/probe-cache.d.ts +6 -0
- package/dist/host/probe-cache.js +19 -0
- package/dist/host/probe-libs.d.ts +45 -0
- package/dist/host/probe-libs.js +61 -0
- package/dist/host/probe-units.d.ts +17 -0
- package/dist/host/probe-units.js +102 -0
- package/dist/host/probe-verdict.d.ts +7 -0
- package/dist/host/probe-verdict.js +143 -0
- package/dist/host/provision.d.ts +14 -0
- package/dist/host/provision.js +12 -0
- package/dist/host/shell.d.ts +14 -0
- package/dist/host/shell.js +23 -0
- package/dist/host/tool-approval.d.ts +29 -0
- package/dist/host/tool-approval.js +190 -0
- package/dist/host/tool-kit.d.ts +19 -0
- package/dist/host/tool-kit.js +37 -0
- package/dist/host/tool-scope.d.ts +32 -0
- package/dist/host/tool-scope.js +84 -0
- package/dist/host/unit-face.d.ts +66 -0
- package/dist/host/unit-face.js +1 -0
- package/dist/host/unit-libs.d.ts +37 -0
- package/dist/host/unit-libs.js +1 -0
- package/dist/host/unit-readiness.d.ts +7 -0
- package/dist/host/unit-readiness.js +5 -0
- package/dist/host/unit-registry.d.ts +9 -0
- package/dist/host/unit-registry.js +33 -0
- package/dist/host/value-handoff.d.ts +16 -0
- package/dist/host/value-handoff.js +21 -0
- package/dist/host/version-range.d.ts +2 -0
- package/dist/host/version-range.js +73 -0
- package/dist/host/workspace-store.d.ts +19 -0
- package/dist/host/workspace-store.js +133 -0
- package/dist/host/write-result.d.ts +1 -0
- package/dist/host/write-result.js +18 -0
- package/dist/host.d.ts +19 -0
- package/dist/host.js +19 -0
- package/dist/i18n/en.d.ts +2 -0
- package/dist/i18n/en.js +162 -0
- package/dist/i18n/index.d.ts +172 -0
- package/dist/i18n/index.js +3 -0
- package/dist/i18n/merge.d.ts +6 -0
- package/dist/i18n/merge.js +40 -0
- package/dist/i18n/zh.d.ts +163 -0
- package/dist/i18n/zh.js +162 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.js +33 -0
- package/dist/lib/blocker-action.d.ts +13 -0
- package/dist/lib/blocker-action.js +12 -0
- package/dist/lib/busy.d.ts +5 -0
- package/dist/lib/busy.js +7 -0
- package/dist/lib/react.d.ts +10 -0
- package/dist/lib/react.js +9 -0
- package/dist/lib/text.d.ts +4 -0
- package/dist/lib/text.js +48 -0
- package/dist/lib/ui.d.ts +11 -0
- package/dist/lib/ui.js +37 -0
- package/package.json +44 -0
- package/src/api/__tests__/blocker-table.test.ts +253 -0
- package/src/api/__tests__/facts.test.ts +274 -0
- package/src/api/__tests__/host-write.test.ts +55 -0
- package/src/api/__tests__/long-task.test.ts +52 -0
- package/src/api/__tests__/unit-board.test.ts +76 -0
- package/src/api/blocker-table.ts +155 -0
- package/src/api/blocker.ts +25 -0
- package/src/api/context-ref.ts +47 -0
- package/src/api/dsh-client.ts +176 -0
- package/src/api/dsh.ts +156 -0
- package/src/api/facts.ts +166 -0
- package/src/api/host-write.ts +27 -0
- package/src/api/long-task.ts +38 -0
- package/src/api/unit-board.ts +44 -0
- package/src/api/unit.ts +59 -0
- package/src/api/workbench-client.test.ts +92 -0
- package/src/api/workbench-client.ts +59 -0
- package/src/api/workbench-contract.ts +40 -0
- package/src/components/ApprovalDetail.tsx +57 -0
- package/src/components/CardHead.tsx +74 -0
- package/src/components/ChoiceGroup.tsx +38 -0
- package/src/components/LongTaskRow.tsx +65 -0
- package/src/components/MockStrip.tsx +40 -0
- package/src/components/ResourceStates.tsx +44 -0
- package/src/components/SaveBar.tsx +31 -0
- package/src/components/StateDot.tsx +31 -0
- package/src/components/board-module.ts +73 -0
- package/src/components/icons.tsx +232 -0
- package/src/config/constants.test.ts +41 -0
- package/src/config/constants.ts +52 -0
- package/src/config/storage.test.ts +82 -0
- package/src/config/storage.ts +25 -0
- package/src/host/__tests__/fakes.ts +116 -0
- package/src/host/__tests__/opencli-runtime.test.ts +46 -0
- package/src/host/cli-error.ts +77 -0
- package/src/host/data-root.ts +13 -0
- package/src/host/facts.ts +69 -0
- package/src/host/git.ts +99 -0
- package/src/host/http.ts +311 -0
- package/src/host/long-task.ts +235 -0
- package/src/host/opencli-runtime.ts +66 -0
- package/src/host/opencli.ts +210 -0
- package/src/host/plugin-source.ts +145 -0
- package/src/host/provision.ts +34 -0
- package/src/host/shell.ts +42 -0
- package/src/host/tool-approval.ts +243 -0
- package/src/host/tool-kit.ts +56 -0
- package/src/host/unit-face.ts +65 -0
- package/src/host/unit-libs.ts +41 -0
- package/src/host/value-handoff.ts +38 -0
- package/src/host/workspace-store.ts +161 -0
- package/src/host/write-result.ts +16 -0
- package/src/host.ts +19 -0
- package/src/i18n/en.ts +170 -0
- package/src/i18n/index.test.ts +15 -0
- package/src/i18n/index.ts +12 -0
- package/src/i18n/merge.test.ts +60 -0
- package/src/i18n/merge.ts +54 -0
- package/src/i18n/zh.test.ts +40 -0
- package/src/i18n/zh.ts +170 -0
- package/src/index.ts +33 -0
- package/src/lib/blocker-action.test.ts +40 -0
- package/src/lib/blocker-action.ts +28 -0
- package/src/lib/busy.test.ts +27 -0
- package/src/lib/busy.ts +11 -0
- package/src/lib/react.test.ts +26 -0
- package/src/lib/react.ts +16 -0
- package/src/lib/text.test.ts +92 -0
- package/src/lib/text.ts +46 -0
- package/src/lib/ui.test.ts +93 -0
- package/src/lib/ui.ts +38 -0
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import type { ModuleBlocker } from '../blocker';
|
|
3
|
+
import type { CapabilityPayload } from '../workbench-contract';
|
|
4
|
+
import { gateOf, probeKeyOf, splitBlocker } from '../unit-board';
|
|
5
|
+
|
|
6
|
+
function capability(status: CapabilityPayload['status']): CapabilityPayload {
|
|
7
|
+
return { unit: 'issues', status, required: [], missing: [], detail: '测试用的结论' };
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function blocker(id: string, severity: ModuleBlocker['severity']): ModuleBlocker {
|
|
11
|
+
return { id, severity, title: id, actions: [] };
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
describe('gateOf(能力门)', () => {
|
|
15
|
+
it('★ 还没探到(null)→ 照常取数:探测只是个前置检查,不许把卡片按住', () => {
|
|
16
|
+
expect(gateOf(null, 'capability')).toEqual({ gated: true });
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('ok → 取数;缺插件/缺命令 → 不取(那次平台读注定失败)', () => {
|
|
20
|
+
expect(gateOf(capability('ok'), 'capability')).toEqual({ gated: true });
|
|
21
|
+
expect(gateOf(capability('missing-plugin'), 'capability')).toEqual({ gated: false });
|
|
22
|
+
expect(gateOf(capability('missing-commands'), 'capability')).toEqual({ gated: false });
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('★ 探不动(unchecked)→ **照常取数**:那是"问不到",不是"没装"(ADR-0016)', () => {
|
|
26
|
+
expect(gateOf(capability('unchecked'), 'capability')).toEqual({ gated: true });
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("mode: 'none' → 永远取数(套件自己决定不要这道门)", () => {
|
|
30
|
+
expect(gateOf(capability('missing-plugin'), 'none')).toEqual({ gated: true });
|
|
31
|
+
expect(gateOf(null, 'none')).toEqual({ gated: true });
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
describe('splitBlocker(一条阻断 + 至多一条提示)', () => {
|
|
36
|
+
it('没有候选 → 两条都没有', () => {
|
|
37
|
+
expect(splitBlocker([])).toEqual({});
|
|
38
|
+
expect(splitBlocker([undefined, undefined])).toEqual({});
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('★ 第一条 block 赢(一张卡上压两条阻断,用户不知道该按哪个)', () => {
|
|
42
|
+
const first = blocker('一', 'block');
|
|
43
|
+
const second = blocker('二', 'block');
|
|
44
|
+
|
|
45
|
+
expect(splitBlocker([first, second])).toEqual({ blocker: first });
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('只有 notice → 就是 notice(不拦人)', () => {
|
|
49
|
+
const notice = blocker('降级', 'notice');
|
|
50
|
+
|
|
51
|
+
expect(splitBlocker([undefined, notice])).toEqual({ notice });
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('★ 有阻断时丢掉提示:那一刻提示只是噪声', () => {
|
|
55
|
+
const block = blocker('缺插件', 'block');
|
|
56
|
+
const notice = blocker('降级', 'notice');
|
|
57
|
+
|
|
58
|
+
expect(splitBlocker([notice, block])).toEqual({ blocker: block });
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
describe('probeKeyOf(出错搭车强探的防抖键)', () => {
|
|
63
|
+
it('没有错误 → null(调用方据此不探)', () => {
|
|
64
|
+
expect(probeKeyOf(undefined)).toBeNull();
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('★ 键就是错误码:码没变就不再探(否则"错误 → 强探 → 仍然错误"会转圈)', () => {
|
|
68
|
+
expect(probeKeyOf({ code: 'auth-required', message: '未登录' })).toBe('auth-required');
|
|
69
|
+
expect(probeKeyOf({ code: 'auth-required', message: '未登录(另一个环境)' })).toBe('auth-required');
|
|
70
|
+
expect(probeKeyOf({ code: 'missing-plugin', message: '缺插件' })).toBe('missing-plugin');
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('空码给一个稳定的兜底键(不许变成"每次都算新错误")', () => {
|
|
74
|
+
expect(probeKeyOf({ code: '', message: '没说什么' })).toBe('unknown');
|
|
75
|
+
});
|
|
76
|
+
});
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import type { Translate } from './dsh-client';
|
|
2
|
+
import type { CapabilityPayload } from './workbench-contract';
|
|
3
|
+
import type { ApiError } from './workbench-client';
|
|
4
|
+
import type { BlockerAction, ModuleBlocker } from './blocker';
|
|
5
|
+
import { shortenPath } from '../lib/ui.js';
|
|
6
|
+
|
|
7
|
+
function retryAction(t: Translate): BlockerAction {
|
|
8
|
+
return { kind: 'retry', label: t('blocker.retry') };
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function copyAction(t: Translate, text: string): BlockerAction {
|
|
12
|
+
return { kind: 'copy', label: t('blocker.copy'), text };
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function blockerOfError(
|
|
16
|
+
error: ApiError | undefined,
|
|
17
|
+
t: Translate,
|
|
18
|
+
context: { path?: string | null; platform?: string } = {},
|
|
19
|
+
): ModuleBlocker {
|
|
20
|
+
if (error === undefined) {
|
|
21
|
+
return {
|
|
22
|
+
id: 'unknown',
|
|
23
|
+
severity: 'block',
|
|
24
|
+
title: t('blocker.unknown.title'),
|
|
25
|
+
actions: [retryAction(t)],
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
switch (error.code) {
|
|
30
|
+
case 'auth-required': {
|
|
31
|
+
const path = shortenPath(context.path ?? '');
|
|
32
|
+
void path;
|
|
33
|
+
const actions: BlockerAction[] =
|
|
34
|
+
error.helpUrl === undefined
|
|
35
|
+
? [retryAction(t)]
|
|
36
|
+
: [
|
|
37
|
+
{ kind: 'open-url', label: t('blocker.login.go'), url: error.helpUrl, inline: true },
|
|
38
|
+
retryAction(t),
|
|
39
|
+
];
|
|
40
|
+
return {
|
|
41
|
+
id: 'auth-required',
|
|
42
|
+
severity: 'block',
|
|
43
|
+
title:
|
|
44
|
+
context.platform === undefined
|
|
45
|
+
? t('blocker.login.titleGeneric')
|
|
46
|
+
: t('blocker.login.title', { platform: context.platform }),
|
|
47
|
+
hint: error.helpUrl === undefined ? t('blocker.login.manual') : t('blocker.login.hint'),
|
|
48
|
+
...(error.detail === undefined ? {} : { detail: error.detail }),
|
|
49
|
+
actions,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
case 'permission-denied': {
|
|
54
|
+
const actions: BlockerAction[] =
|
|
55
|
+
error.helpUrl === undefined
|
|
56
|
+
? [retryAction(t)]
|
|
57
|
+
: [
|
|
58
|
+
{ kind: 'open-url', label: t('blocker.forbidden.open'), url: error.helpUrl },
|
|
59
|
+
{ kind: 'retry', label: t('blocker.retry') },
|
|
60
|
+
];
|
|
61
|
+
return {
|
|
62
|
+
id: 'permission-denied',
|
|
63
|
+
severity: 'block',
|
|
64
|
+
title:
|
|
65
|
+
context.platform === undefined
|
|
66
|
+
? t('blocker.forbidden.titleGeneric')
|
|
67
|
+
: t('blocker.forbidden.title', { platform: context.platform }),
|
|
68
|
+
hint: t('blocker.forbidden.hint'),
|
|
69
|
+
...(error.detail === undefined ? {} : { detail: error.detail }),
|
|
70
|
+
actions,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
case 'cli-timeout':
|
|
75
|
+
return {
|
|
76
|
+
id: 'cli-timeout',
|
|
77
|
+
severity: 'notice',
|
|
78
|
+
title: error.message,
|
|
79
|
+
hint: error.hint ?? t('blocker.timeout.hint'),
|
|
80
|
+
...(error.detail === undefined ? {} : { detail: error.detail }),
|
|
81
|
+
actions: [retryAction(t)],
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
case 'cli-exec-failed':
|
|
85
|
+
return {
|
|
86
|
+
id: 'cli-exec-failed',
|
|
87
|
+
severity: 'block',
|
|
88
|
+
title: error.message,
|
|
89
|
+
hint: error.hint ?? t('blocker.exec.hint'),
|
|
90
|
+
...(error.detail === undefined ? {} : { detail: error.detail }),
|
|
91
|
+
actions: [retryAction(t)],
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
default:
|
|
95
|
+
return {
|
|
96
|
+
id: error.code,
|
|
97
|
+
severity: 'block',
|
|
98
|
+
title: error.message,
|
|
99
|
+
...(error.hint === undefined ? {} : { hint: error.hint }),
|
|
100
|
+
...(error.detail === undefined ? {} : { detail: error.detail }),
|
|
101
|
+
actions: [retryAction(t)],
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function blockerOfCapability(capability: CapabilityPayload, t: Translate): ModuleBlocker | undefined {
|
|
107
|
+
if (capability.status === 'ok') return undefined;
|
|
108
|
+
|
|
109
|
+
const fixAction: BlockerAction | undefined =
|
|
110
|
+
capability.fix === undefined
|
|
111
|
+
? undefined
|
|
112
|
+
: {
|
|
113
|
+
kind: 'run-fix',
|
|
114
|
+
label: capability.fix === 'install' ? t('blocker.fix.install') : t('blocker.fix.update'),
|
|
115
|
+
unit: capability.unit,
|
|
116
|
+
fix: capability.fix,
|
|
117
|
+
};
|
|
118
|
+
const sourceUnknown = capability.fix === undefined && capability.install !== undefined && capability.install !== 'ready';
|
|
119
|
+
const install: readonly BlockerAction[] = [
|
|
120
|
+
...(fixAction === undefined ? [] : [fixAction]),
|
|
121
|
+
...(sourceUnknown ? [{ kind: 'open-market' as const, label: t('blocker.market.open'), unit: capability.unit }] : []),
|
|
122
|
+
...(capability.command === undefined || sourceUnknown ? [] : [copyAction(t, capability.command)]),
|
|
123
|
+
];
|
|
124
|
+
|
|
125
|
+
if (capability.status === 'missing-plugin') {
|
|
126
|
+
return {
|
|
127
|
+
id: 'unit-missing-plugin',
|
|
128
|
+
severity: 'block',
|
|
129
|
+
title: t('env.unit.missingPlugin'),
|
|
130
|
+
hint: t('env.unit.hint'),
|
|
131
|
+
...(capability.detail === undefined ? {} : { detail: capability.detail }),
|
|
132
|
+
actions: [...install, retryAction(t)],
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (capability.status === 'missing-commands') {
|
|
137
|
+
return {
|
|
138
|
+
id: 'unit-missing-commands',
|
|
139
|
+
severity: 'block',
|
|
140
|
+
title: t('env.unit.missingCommands'),
|
|
141
|
+
hint: t('env.unit.missingCommandsHint'),
|
|
142
|
+
...(capability.detail === undefined ? {} : { detail: capability.detail }),
|
|
143
|
+
actions: [...install, retryAction(t)],
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return {
|
|
148
|
+
id: 'unit-unchecked',
|
|
149
|
+
severity: 'notice',
|
|
150
|
+
title: t('env.unit.unchecked'),
|
|
151
|
+
hint: t('env.unit.uncheckedHint'),
|
|
152
|
+
...(capability.detail === undefined ? {} : { detail: capability.detail }),
|
|
153
|
+
actions: [retryAction(t)],
|
|
154
|
+
};
|
|
155
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export type BlockerAction =
|
|
2
|
+
| {
|
|
3
|
+
readonly kind: 'open-url';
|
|
4
|
+
readonly label: string;
|
|
5
|
+
readonly url: string;
|
|
6
|
+
readonly inline?: boolean;
|
|
7
|
+
}
|
|
8
|
+
| { readonly kind: 'copy'; readonly label: string; readonly text: string }
|
|
9
|
+
| { readonly kind: 'retry'; readonly label: string }
|
|
10
|
+
| {
|
|
11
|
+
readonly kind: 'run-fix';
|
|
12
|
+
readonly label: string;
|
|
13
|
+
readonly unit: string;
|
|
14
|
+
readonly fix: 'install' | 'update';
|
|
15
|
+
}
|
|
16
|
+
| { readonly kind: 'open-market'; readonly label: string; readonly unit: string };
|
|
17
|
+
|
|
18
|
+
export interface ModuleBlocker {
|
|
19
|
+
readonly id: string;
|
|
20
|
+
readonly severity: 'block' | 'notice';
|
|
21
|
+
readonly title: string;
|
|
22
|
+
readonly hint?: string;
|
|
23
|
+
readonly detail?: string;
|
|
24
|
+
readonly actions: readonly BlockerAction[];
|
|
25
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export const CONTEXT_MARKER_NAME = '工作台';
|
|
2
|
+
|
|
3
|
+
export interface ContextRef {
|
|
4
|
+
readonly kind: string;
|
|
5
|
+
readonly parts: readonly string[];
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const MARKER = new RegExp(`@${CONTEXT_MARKER_NAME}:([a-z][a-z0-9-]*:[A-Za-z0-9._~%#/-]+)`, 'g');
|
|
9
|
+
|
|
10
|
+
const TRAILING_PUNCTUATION = /[.,;:!?]+$/;
|
|
11
|
+
|
|
12
|
+
export function encodeRef(ref: ContextRef): string {
|
|
13
|
+
return `${ref.kind}:${ref.parts.join('/')}`;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function decodeRef(text: string): ContextRef | undefined {
|
|
17
|
+
const at = text.indexOf(':');
|
|
18
|
+
if (at <= 0 || at === text.length - 1) return undefined;
|
|
19
|
+
const kind = text.slice(0, at);
|
|
20
|
+
if (!/^[a-z][a-z0-9-]*$/.test(kind)) return undefined;
|
|
21
|
+
const parts = text.slice(at + 1).split('/').filter((part) => part !== '');
|
|
22
|
+
if (parts.length === 0) return undefined;
|
|
23
|
+
return { kind, parts };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function markerBody(encoded: string): string {
|
|
27
|
+
return `${CONTEXT_MARKER_NAME}:${encoded}`;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function buildMarker(ref: ContextRef): string {
|
|
31
|
+
return `@${markerBody(encodeRef(ref))}`;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function parseMarkers(text: string): ContextRef[] {
|
|
35
|
+
const seen = new Set<string>();
|
|
36
|
+
const refs: ContextRef[] = [];
|
|
37
|
+
for (const match of String(text ?? '').matchAll(MARKER)) {
|
|
38
|
+
const raw = match[1].replace(TRAILING_PUNCTUATION, '');
|
|
39
|
+
const ref = decodeRef(raw);
|
|
40
|
+
if (ref === undefined) continue;
|
|
41
|
+
const key = encodeRef(ref);
|
|
42
|
+
if (seen.has(key)) continue;
|
|
43
|
+
seen.add(key);
|
|
44
|
+
refs.push(ref);
|
|
45
|
+
}
|
|
46
|
+
return refs;
|
|
47
|
+
}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
export interface WorkspaceView {
|
|
4
|
+
readonly workspaceId: string;
|
|
5
|
+
readonly path: string;
|
|
6
|
+
readonly title: string;
|
|
7
|
+
readonly sessionIds: readonly string[];
|
|
8
|
+
readonly createdAt: number;
|
|
9
|
+
readonly updatedAt: number;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface WorkspaceSnapshot {
|
|
13
|
+
readonly items: readonly WorkspaceView[];
|
|
14
|
+
readonly archivedSessionIds: readonly string[];
|
|
15
|
+
readonly pinnedSessionIds: readonly string[];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface SessionSummary {
|
|
19
|
+
readonly id: string;
|
|
20
|
+
readonly displayTitle: string;
|
|
21
|
+
readonly blank: boolean;
|
|
22
|
+
readonly running: boolean;
|
|
23
|
+
readonly updatedAt: number;
|
|
24
|
+
readonly origin?: string;
|
|
25
|
+
readonly retainedBy: Readonly<Partial<Record<string, number>>>;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface SubagentCatalogEntry {
|
|
29
|
+
readonly id: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface SessionsSnapshot {
|
|
33
|
+
readonly byId: Readonly<Record<string, SessionSummary>>;
|
|
34
|
+
readonly projectionsBySession: Readonly<
|
|
35
|
+
Record<string, { values?: { subagentCatalog?: readonly SubagentCatalogEntry[] } } | undefined>
|
|
36
|
+
>;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface SessionStatus {
|
|
40
|
+
readonly running?: boolean;
|
|
41
|
+
readonly completionUnread?: boolean;
|
|
42
|
+
readonly pendingInteraction?: { readonly kind?: string };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export type SessionStatusSnapshot = ReadonlyMap<string, SessionStatus>;
|
|
46
|
+
|
|
47
|
+
export interface PanelInfo {
|
|
48
|
+
readonly activePanelId: string | null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface PanelEntry {
|
|
52
|
+
readonly id: string;
|
|
53
|
+
readonly order: number;
|
|
54
|
+
readonly label: string | (() => string);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export type SnapshotSelectorHook<T> = <R>(selector: (state: T) => R) => R;
|
|
58
|
+
|
|
59
|
+
export type RenderSlot = (
|
|
60
|
+
key: string,
|
|
61
|
+
owner?: Record<string, unknown>,
|
|
62
|
+
options?: { fallback?: ReactNode; hookContext?: unknown; only?: string },
|
|
63
|
+
) => ReactNode;
|
|
64
|
+
|
|
65
|
+
export type Translate = (key: string, params?: Record<string, unknown>) => string;
|
|
66
|
+
|
|
67
|
+
export interface SlotRegisterOptions {
|
|
68
|
+
name: string;
|
|
69
|
+
priority?: number;
|
|
70
|
+
key?: string;
|
|
71
|
+
id?: string;
|
|
72
|
+
order?: number;
|
|
73
|
+
locale?: string;
|
|
74
|
+
label?: string | (() => string);
|
|
75
|
+
children?: Record<string, unknown>;
|
|
76
|
+
inject?: (scope?: unknown) => Record<string, unknown>;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export interface DshSlots {
|
|
80
|
+
inject(name: string, callback: () => void | (() => void) | Iterable<() => void>): () => void;
|
|
81
|
+
register(options: SlotRegisterOptions, component: unknown): () => void;
|
|
82
|
+
entriesOfSlot(name: string): Array<{ options: SlotRegisterOptions }>;
|
|
83
|
+
subscribe(name: string, callback: () => void): () => void;
|
|
84
|
+
specDynamic?(name: string): unknown;
|
|
85
|
+
onMutate?(callback: (name: string) => void): () => void;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export interface DshLocale {
|
|
89
|
+
register(namespace: string, dictionaries: Record<string, Record<string, string>>): () => void;
|
|
90
|
+
bind(namespace: string): Translate;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export interface DshLayout {
|
|
94
|
+
selectPanel(panelId: string | null): void;
|
|
95
|
+
toggleSidebar(): void;
|
|
96
|
+
openRightbar(track: boolean, fullscreen: boolean): void;
|
|
97
|
+
closeRightbar(): void;
|
|
98
|
+
readonly panels?: DshLayoutPanels;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export interface DshLayoutPanels {
|
|
102
|
+
setRightbar?(px: number): void;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export interface DshShortcutCatalog {
|
|
106
|
+
readonly catalog: unknown;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export interface DshUiWorkspace {
|
|
110
|
+
openWorkspace(workspaceId: string): Promise<unknown>;
|
|
111
|
+
openSession(sessionId: string): void;
|
|
112
|
+
startSession(workspaceId: string): void;
|
|
113
|
+
pickDirectory(): Promise<string | null>;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export interface DshWorkspaces {
|
|
117
|
+
create(request: { path: string }): Promise<{ workspaceId?: string } | undefined>;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export interface DshReferenceInsert {
|
|
121
|
+
readonly source: string;
|
|
122
|
+
readonly ref: string;
|
|
123
|
+
readonly label: string;
|
|
124
|
+
readonly appearance?: 'session' | 'file' | 'folder';
|
|
125
|
+
readonly clipboardText: string;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export interface DshTokenSpan {
|
|
129
|
+
readonly start: number;
|
|
130
|
+
readonly end: number;
|
|
131
|
+
readonly draftRev: number;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export interface DshSessionInput {
|
|
135
|
+
caretSpan?: () => { start: number; end: number };
|
|
136
|
+
readonly state: { getSnapshot(): { draftRev: number } | undefined };
|
|
137
|
+
insertReference(ref: DshReferenceInsert, span: DshTokenSpan): boolean;
|
|
138
|
+
notify(level: 'info' | 'error', text: string): void;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export interface DshInputTriggers {
|
|
142
|
+
registerSource(source: {
|
|
143
|
+
readonly trigger: '@';
|
|
144
|
+
readonly name: string;
|
|
145
|
+
readonly order?: number;
|
|
146
|
+
candidates: () => Promise<readonly unknown[]>;
|
|
147
|
+
onPick: () => undefined;
|
|
148
|
+
lexicon?: (session: unknown) => readonly string[] | undefined;
|
|
149
|
+
subscribeLexicon?: (session: unknown, listener: () => void) => () => void;
|
|
150
|
+
readonly codec?: {
|
|
151
|
+
clipboardText(ref: string): string;
|
|
152
|
+
serialize(ref: string, signal?: AbortSignal): Promise<string>;
|
|
153
|
+
};
|
|
154
|
+
}): () => void;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export interface DshConversationService {
|
|
158
|
+
readonly input: { for(actx: unknown): DshSessionInput };
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export interface DshSessionsService {
|
|
162
|
+
readonly list: { getSnapshot(): SessionsSnapshot };
|
|
163
|
+
scope(sessionId: string): unknown | undefined;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export interface DshClientContext {
|
|
167
|
+
get<T = unknown>(name: string): T | undefined;
|
|
168
|
+
provide(name: string, value: unknown): void;
|
|
169
|
+
effect(effect: () => void | (() => void), label?: string): () => void;
|
|
170
|
+
readonly slots: DshSlots;
|
|
171
|
+
readonly locale: DshLocale;
|
|
172
|
+
readonly layout: DshLayout;
|
|
173
|
+
readonly uiWorkspace: DshUiWorkspace;
|
|
174
|
+
readonly workspaces: DshWorkspaces;
|
|
175
|
+
readonly shortcuts: DshShortcutCatalog;
|
|
176
|
+
}
|
package/src/api/dsh.ts
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
export interface DshLogger {
|
|
2
|
+
info?(message: string): void;
|
|
3
|
+
warn?(message: string): void;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export interface DshRequest {
|
|
7
|
+
readonly method?: string;
|
|
8
|
+
readonly url?: string;
|
|
9
|
+
[Symbol.asyncIterator]?(): AsyncIterator<Uint8Array>;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface DshResponse {
|
|
13
|
+
statusCode: number;
|
|
14
|
+
setHeader(name: string, value: string): void;
|
|
15
|
+
end(body?: string): void;
|
|
16
|
+
writeHead?(status: number, headers: Record<string, string>): void;
|
|
17
|
+
write?(chunk: string): void;
|
|
18
|
+
on?(event: 'close', listener: () => void): void;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface DshWebRoute {
|
|
22
|
+
readonly kind: 'prefix' | 'exact';
|
|
23
|
+
readonly path: string;
|
|
24
|
+
readonly handler: (req: DshRequest, res: DshResponse) => void | Promise<void>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface DshProfileContext {
|
|
28
|
+
readonly dir?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface DshToolSchema {
|
|
32
|
+
readonly name: string;
|
|
33
|
+
readonly [key: string]: unknown;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface DshPromptAssembly {
|
|
37
|
+
readonly tools?: readonly DshToolSchema[] | undefined;
|
|
38
|
+
readonly [key: string]: unknown;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface DshAssemblyContext {
|
|
42
|
+
readonly agent?: unknown;
|
|
43
|
+
readonly scope?: unknown;
|
|
44
|
+
readonly [key: string]: unknown;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface DshHostContext {
|
|
48
|
+
on?: {
|
|
49
|
+
(
|
|
50
|
+
event: 'system-prompt/assemble',
|
|
51
|
+
listener: (
|
|
52
|
+
assembly: DshPromptAssembly,
|
|
53
|
+
context: DshAssemblyContext,
|
|
54
|
+
next: () => Promise<DshPromptAssembly>,
|
|
55
|
+
) => Promise<DshPromptAssembly>,
|
|
56
|
+
): (() => void) | undefined;
|
|
57
|
+
} & {
|
|
58
|
+
(
|
|
59
|
+
event: 'tools/pre-execute',
|
|
60
|
+
listener: (
|
|
61
|
+
exec: {
|
|
62
|
+
name?: string;
|
|
63
|
+
arguments?: unknown;
|
|
64
|
+
readonly agent?: DshToolRunContext['agent'];
|
|
65
|
+
},
|
|
66
|
+
next: () => Promise<
|
|
67
|
+
| { kind: 'allow' }
|
|
68
|
+
| { kind: 'deny'; reason: string }
|
|
69
|
+
| { kind: 'cancel' }
|
|
70
|
+
| { kind: 'ask'; reason?: string; displayReason?: Record<string, string> }
|
|
71
|
+
>,
|
|
72
|
+
) => Promise<
|
|
73
|
+
| { kind: 'allow' }
|
|
74
|
+
| { kind: 'deny'; reason: string }
|
|
75
|
+
| { kind: 'cancel' }
|
|
76
|
+
| { kind: 'ask'; reason?: string; displayReason?: Record<string, string> }
|
|
77
|
+
>,
|
|
78
|
+
): (() => void) | undefined;
|
|
79
|
+
} & {
|
|
80
|
+
(
|
|
81
|
+
event: 'tools/post-execute',
|
|
82
|
+
listener: (
|
|
83
|
+
exec: { name?: string; arguments?: unknown },
|
|
84
|
+
result: unknown,
|
|
85
|
+
next: () => unknown,
|
|
86
|
+
) => unknown,
|
|
87
|
+
): (() => void) | undefined;
|
|
88
|
+
} & {
|
|
89
|
+
(
|
|
90
|
+
event: 'agent/created',
|
|
91
|
+
listener: (payload: { agent?: unknown }) => void,
|
|
92
|
+
): (() => void) | undefined;
|
|
93
|
+
} & {
|
|
94
|
+
(
|
|
95
|
+
event: 'agent/pre-step',
|
|
96
|
+
listener: (
|
|
97
|
+
payload: { agent?: unknown; messages?: readonly unknown[] },
|
|
98
|
+
next: () => Promise<{ kind: string; messages?: readonly unknown[] }>,
|
|
99
|
+
) => Promise<{ kind: string; messages?: readonly unknown[] }>,
|
|
100
|
+
): (() => void) | undefined;
|
|
101
|
+
};
|
|
102
|
+
get<T = unknown>(name: string): T | undefined;
|
|
103
|
+
provide(name: string, value: unknown): () => void;
|
|
104
|
+
effect(effect: () => void | (() => void), label?: string): () => void;
|
|
105
|
+
readonly logger?: DshLogger;
|
|
106
|
+
readonly webServer: { register(route: DshWebRoute): () => void };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export interface DshShellExecution {
|
|
110
|
+
result(): Promise<{
|
|
111
|
+
exitCode: number | null;
|
|
112
|
+
timedOut: boolean;
|
|
113
|
+
aborted: boolean;
|
|
114
|
+
stdout: { text: string; truncated: boolean };
|
|
115
|
+
stderr: { text: string; truncated: boolean };
|
|
116
|
+
}>;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export interface DshShell {
|
|
120
|
+
resolve(request: {
|
|
121
|
+
command: string;
|
|
122
|
+
workdir?: string;
|
|
123
|
+
timeoutMs?: number;
|
|
124
|
+
onExpiry?: 'kill' | 'none';
|
|
125
|
+
stdoutMaxBytes?: number;
|
|
126
|
+
signal?: AbortSignal;
|
|
127
|
+
}): unknown;
|
|
128
|
+
execute(spec: unknown): Promise<DshShellExecution>;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export interface DshWorkspaceRegistry {
|
|
132
|
+
get(id: string): { id: string; path?: string; title?: string } | undefined;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export interface DshToolDefinition {
|
|
136
|
+
name: string;
|
|
137
|
+
description: string;
|
|
138
|
+
parameters: Record<string, unknown>;
|
|
139
|
+
output: {
|
|
140
|
+
schema: Record<string, unknown>;
|
|
141
|
+
render(args: unknown, value: unknown): Array<Record<string, unknown>>;
|
|
142
|
+
};
|
|
143
|
+
execute(args: any, exec: DshToolRunContext): Promise<unknown>;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export interface DshToolRunContext {
|
|
147
|
+
readonly agent?:
|
|
148
|
+
| {
|
|
149
|
+
readonly session?: { readonly header?: { readonly cwd?: string } | undefined } | undefined;
|
|
150
|
+
}
|
|
151
|
+
| undefined;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export interface DshToolRegistry {
|
|
155
|
+
register(definition: DshToolDefinition): () => void;
|
|
156
|
+
}
|