dsh-webui-studio 0.1.0 → 0.2.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/PRODUCT.md +11 -7
- package/README.md +69 -21
- package/README.zh-CN.md +64 -20
- package/dist/bridge.js +10 -10
- package/dist/studio.css +1 -1
- package/dist/studio.js +16691 -10202
- package/docs/bidirectional-connection-handoff.md +729 -0
- package/docs/harmony-api-requirements.md +17 -13
- package/docs/remote-development.md +80 -0
- package/lib/bridge/element-style-selector.d.ts +1 -0
- package/lib/bridge/element-style-selector.js +53 -0
- package/lib/contracts.d.ts +152 -83
- package/lib/contracts.js +0 -2
- package/lib/host/agent.d.ts +15 -13
- package/lib/host/agent.js +213 -56
- package/lib/host/automatic-patch.d.ts +9 -0
- package/lib/host/automatic-patch.js +433 -0
- package/lib/host/backend.d.ts +134 -4
- package/lib/host/backend.js +465 -114
- package/lib/host/drafts.d.ts +1 -1
- package/lib/host/drafts.js +65 -15
- package/lib/host/element-source.d.ts +9 -0
- package/lib/host/element-source.js +295 -0
- package/lib/host/mcp.d.ts +4 -0
- package/lib/host/mcp.js +97 -0
- package/lib/host/preview-draft.d.ts +22 -0
- package/lib/host/preview-draft.js +162 -0
- package/lib/host/preview-port.d.ts +8 -0
- package/lib/host/preview-port.js +32 -0
- package/lib/host/preview-worker.d.ts +65 -2
- package/lib/host/preview-worker.js +203 -76
- package/lib/host/preview.d.ts +26 -5
- package/lib/host/preview.js +130 -49
- package/lib/host/readiness.d.ts +2 -2
- package/lib/host/readiness.js +14 -17
- package/lib/host/routes.d.ts +1 -7
- package/lib/host/routes.js +41 -50
- package/lib/host/runtime-profile.d.ts +2 -1
- package/lib/host/runtime-profile.js +30 -8
- package/lib/host/source-resolution.d.ts +11 -1
- package/lib/host/source-resolution.js +69 -24
- package/lib/host/studio-service.d.ts +129 -0
- package/lib/host/studio-service.js +53 -0
- package/lib/index.d.ts +5 -0
- package/lib/index.js +64 -30
- package/lib/studio-remote.d.ts +126 -0
- package/lib/studio-remote.js +188 -0
- package/lib/variable-tree.d.ts +2 -0
- package/lib/variable-tree.js +13 -0
- package/package.json +62 -25
- package/studio.patch.yml +12 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { lstat, readFile, realpath } from 'node:fs/promises';
|
|
1
|
+
import { lstat, readFile, readdir, realpath } from 'node:fs/promises';
|
|
2
2
|
import { isAbsolute, join, relative, resolve, sep } from 'node:path';
|
|
3
3
|
import { fileURLToPath } from 'node:url';
|
|
4
4
|
const MAX_SOURCE_BYTES = 1024 * 1024;
|
|
@@ -16,6 +16,16 @@ function relativeSource(path) {
|
|
|
16
16
|
}
|
|
17
17
|
return normalized;
|
|
18
18
|
}
|
|
19
|
+
function clientExport(manifest) {
|
|
20
|
+
if (typeof manifest.exports !== 'object' || manifest.exports === null)
|
|
21
|
+
return undefined;
|
|
22
|
+
const entry = manifest.exports['./client'];
|
|
23
|
+
const target = typeof entry === 'string' ? entry
|
|
24
|
+
: typeof entry === 'object' && entry !== null && typeof entry.default === 'string'
|
|
25
|
+
? entry.default
|
|
26
|
+
: undefined;
|
|
27
|
+
return target === undefined ? undefined : relativeSource(target);
|
|
28
|
+
}
|
|
19
29
|
function sourceReference(file) {
|
|
20
30
|
const trimmed = file.trim();
|
|
21
31
|
if (trimmed.startsWith('file:')) {
|
|
@@ -54,33 +64,44 @@ async function packageRoot(path, expectedName) {
|
|
|
54
64
|
try {
|
|
55
65
|
const root = await realpath(path);
|
|
56
66
|
const manifest = JSON.parse(await readFile(join(root, 'package.json'), 'utf8'));
|
|
57
|
-
|
|
67
|
+
const client = clientExport(manifest);
|
|
68
|
+
return manifest.name === expectedName
|
|
69
|
+
? { name: expectedName, root, kind: 'dependency', ...(client === undefined ? {} : { client }) }
|
|
70
|
+
: undefined;
|
|
58
71
|
}
|
|
59
72
|
catch {
|
|
60
73
|
return undefined;
|
|
61
74
|
}
|
|
62
75
|
}
|
|
63
|
-
async function
|
|
64
|
-
const
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
76
|
+
async function installedPackageNames(nodeModules) {
|
|
77
|
+
const entries = (await readdir(nodeModules)).filter(name => !name.startsWith('.'));
|
|
78
|
+
const names = await Promise.all(entries.map(async (name) => name.startsWith('@')
|
|
79
|
+
? readdir(join(nodeModules, name)).then(packages => packages.filter(item => !item.startsWith('.')).map(item => `${name}/${item}`))
|
|
80
|
+
: [name]));
|
|
81
|
+
return names.flat();
|
|
82
|
+
}
|
|
83
|
+
async function packageRoots(draftRoot, nodeModulesDirs) {
|
|
84
|
+
const roots = [];
|
|
85
|
+
if (draftRoot !== undefined) {
|
|
86
|
+
const draft = await realpath(draftRoot);
|
|
87
|
+
const draftManifest = JSON.parse(await readFile(join(draft, 'package.json'), 'utf8'));
|
|
88
|
+
const draftClient = clientExport(draftManifest);
|
|
89
|
+
roots.push({
|
|
90
|
+
name: draftManifest.name,
|
|
70
91
|
root: draft,
|
|
71
92
|
kind: 'draft',
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
const installed = await packageRoot(join(profileDir, 'node_modules', ...name.split('/')), name);
|
|
75
|
-
if (installed !== undefined)
|
|
76
|
-
roots.push(installed);
|
|
93
|
+
...(draftClient === undefined ? {} : { client: draftClient }),
|
|
94
|
+
});
|
|
77
95
|
}
|
|
78
|
-
for (const
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
96
|
+
for (const nodeModules of nodeModulesDirs) {
|
|
97
|
+
for (const name of await installedPackageNames(nodeModules)) {
|
|
98
|
+
const installed = await packageRoot(join(nodeModules, ...name.split('/')), name);
|
|
99
|
+
if (installed !== undefined)
|
|
100
|
+
roots.push(installed);
|
|
101
|
+
}
|
|
82
102
|
}
|
|
83
|
-
return roots.filter((item, index, all) => all.findIndex(candidate => candidate.root === item.root
|
|
103
|
+
return roots.filter((item, index, all) => all.findIndex(candidate => candidate.root === item.root
|
|
104
|
+
|| item.kind === 'dependency' && candidate.kind === 'dependency' && candidate.name === item.name) === index);
|
|
84
105
|
}
|
|
85
106
|
function result(source, file, kind, confidence, packageName) {
|
|
86
107
|
return {
|
|
@@ -104,13 +125,30 @@ async function exactMatch(path, roots) {
|
|
|
104
125
|
const root = matches[0];
|
|
105
126
|
return root === undefined ? undefined : { root, file: posix(relative(root.root, target)) };
|
|
106
127
|
}
|
|
128
|
+
async function pluginClientMatch(path, roots) {
|
|
129
|
+
const route = path.trim().replace(/[?#].*$/, '');
|
|
130
|
+
const root = roots.find(item => route === `/plugins/${item.name}/client.js` && item.client !== undefined);
|
|
131
|
+
return root?.client === undefined ? undefined : exactMatch(resolve(root.root, root.client), [root]);
|
|
132
|
+
}
|
|
107
133
|
export class StudioSourceResolver {
|
|
134
|
+
draftRoot;
|
|
135
|
+
profileDir;
|
|
136
|
+
packageDirs;
|
|
108
137
|
#roots;
|
|
109
|
-
constructor(draftRoot, profileDir) {
|
|
110
|
-
this
|
|
138
|
+
constructor(draftRoot, profileDir, packageDirs = []) {
|
|
139
|
+
this.draftRoot = draftRoot;
|
|
140
|
+
this.profileDir = profileDir;
|
|
141
|
+
this.packageDirs = packageDirs;
|
|
142
|
+
}
|
|
143
|
+
roots() {
|
|
144
|
+
return this.#roots ??= packageRoots(this.draftRoot, [join(this.profileDir, 'node_modules'), ...this.packageDirs]);
|
|
111
145
|
}
|
|
112
146
|
async resolve(source) {
|
|
113
|
-
const roots = await this
|
|
147
|
+
const roots = await this.roots();
|
|
148
|
+
const pluginClient = await pluginClientMatch(source.file, roots);
|
|
149
|
+
if (pluginClient !== undefined) {
|
|
150
|
+
return result(source, pluginClient.file, pluginClient.root.kind, 'exact', pluginClient.root.name);
|
|
151
|
+
}
|
|
114
152
|
const reference = sourceReference(source.file);
|
|
115
153
|
if (reference.absolute !== undefined) {
|
|
116
154
|
const match = await exactMatch(reference.absolute, roots);
|
|
@@ -130,10 +168,13 @@ export class StudioSourceResolver {
|
|
|
130
168
|
return result(source, source.file, reference.generated ? 'generated' : 'unknown', 'candidate');
|
|
131
169
|
}
|
|
132
170
|
async readDependency(packageName, file) {
|
|
171
|
+
return (await this.readDependencyTarget(packageName, file)).source;
|
|
172
|
+
}
|
|
173
|
+
async readDependencyTarget(packageName, file) {
|
|
133
174
|
const relativeFile = relativeSource(file);
|
|
134
175
|
if (packageName === '' || relativeFile === undefined)
|
|
135
176
|
throw new Error('dependency source reference is invalid');
|
|
136
|
-
const roots = (await this
|
|
177
|
+
const roots = (await this.roots()).filter(root => root.kind === 'dependency' && root.name === packageName);
|
|
137
178
|
if (roots.length !== 1)
|
|
138
179
|
throw new Error(`dependency package ${JSON.stringify(packageName)} is not uniquely installed in Preview`);
|
|
139
180
|
const match = await exactMatch(resolve(roots[0].root, relativeFile), roots);
|
|
@@ -149,6 +190,10 @@ export class StudioSourceResolver {
|
|
|
149
190
|
const content = await readFile(target);
|
|
150
191
|
if (content.includes(0))
|
|
151
192
|
throw new Error('binary dependency sources cannot be read');
|
|
152
|
-
|
|
193
|
+
const manifest = JSON.parse(await readFile(join(roots[0].root, 'package.json'), 'utf8'));
|
|
194
|
+
if (typeof manifest.version !== 'string' || manifest.version === '') {
|
|
195
|
+
throw new Error(`dependency package ${JSON.stringify(packageName)} does not declare a version`);
|
|
196
|
+
}
|
|
197
|
+
return { package: packageName, file: relativeFile, version: manifest.version, source: content.toString('utf8') };
|
|
153
198
|
}
|
|
154
199
|
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
2
|
+
import { TypertRemoteService } from '@deepseek-ai/dsh-typert-protocol';
|
|
3
|
+
import type { TypertContribution } from '@deepseek-ai/dsh-typert-registry/types';
|
|
4
|
+
import type { StudioAutomaticPatchRequest, StudioCreateDraftInput, StudioElementStyleSource, StudioPreviewUpdate, StudioSourceLocation, StudioWorkspaceState } from '../contracts.js';
|
|
5
|
+
import { StudioBackend } from './backend.js';
|
|
6
|
+
export declare const STUDIO_LOCAL: TypertContribution;
|
|
7
|
+
export declare class StudioService extends TypertRemoteService {
|
|
8
|
+
private readonly backend;
|
|
9
|
+
constructor(ctx: Context, backend: StudioBackend);
|
|
10
|
+
currentGet(signal: AbortSignal): import("../contracts.js").StudioCurrentInstanceView;
|
|
11
|
+
currentPreviewStatus(signal: AbortSignal): import("../contracts.js").StudioPreviewStatus;
|
|
12
|
+
currentPreviewUpdate(input: StudioPreviewUpdate, signal: AbortSignal): import("../contracts.js").StudioPreviewStatus;
|
|
13
|
+
currentResolveSource(input: {
|
|
14
|
+
source: StudioSourceLocation;
|
|
15
|
+
}, signal: AbortSignal): Promise<import("../contracts.js").StudioSourceCandidate>;
|
|
16
|
+
currentAgentCreate(input: {
|
|
17
|
+
agentPreset?: string;
|
|
18
|
+
}, signal: AbortSignal): Promise<import("../contracts.js").StudioAgentBinding>;
|
|
19
|
+
currentAgentAttach(input: {
|
|
20
|
+
sessionId: string;
|
|
21
|
+
}, signal: AbortSignal): Promise<import("../contracts.js").StudioAgentBinding>;
|
|
22
|
+
currentAgentLeave(signal: AbortSignal): Promise<import("../contracts.js").StudioCurrentInstanceView>;
|
|
23
|
+
draftsList(signal: AbortSignal): Promise<import("../contracts.js").StudioDraftView[]>;
|
|
24
|
+
draftsCreate(input: StudioCreateDraftInput, signal: AbortSignal): Promise<import("../contracts.js").StudioDraftView>;
|
|
25
|
+
draftsRename(input: {
|
|
26
|
+
draftId: string;
|
|
27
|
+
label: string;
|
|
28
|
+
}, signal: AbortSignal): Promise<import("../contracts.js").StudioDraftView>;
|
|
29
|
+
draftsExport(input: {
|
|
30
|
+
draftId: string;
|
|
31
|
+
}, signal: AbortSignal): Promise<import("../contracts.js").StudioDraftView>;
|
|
32
|
+
draftsStart(input: {
|
|
33
|
+
draftId: string;
|
|
34
|
+
}, signal: AbortSignal): Promise<import("../contracts.js").StudioDraftView>;
|
|
35
|
+
draftsStop(input: {
|
|
36
|
+
draftId: string;
|
|
37
|
+
}, signal: AbortSignal): Promise<import("../contracts.js").StudioDraftView>;
|
|
38
|
+
workspaceGet(signal: AbortSignal): Promise<StudioWorkspaceState>;
|
|
39
|
+
workspaceUpdate(input: StudioWorkspaceState, signal: AbortSignal): Promise<StudioWorkspaceState>;
|
|
40
|
+
harmonyProfile(input: {
|
|
41
|
+
draftId: string;
|
|
42
|
+
}, signal: AbortSignal): Promise<import("../contracts.js").StudioHarmonyProfile>;
|
|
43
|
+
harmonyInspect(input: {
|
|
44
|
+
draftId: string;
|
|
45
|
+
package?: string;
|
|
46
|
+
file?: string;
|
|
47
|
+
}, signal: AbortSignal): Promise<import("dsh-harmony").HarmonyInspection>;
|
|
48
|
+
harmonyUpdateProfile(input: {
|
|
49
|
+
draftId: string;
|
|
50
|
+
order?: string[];
|
|
51
|
+
patchOrder?: string[];
|
|
52
|
+
disabled?: string[];
|
|
53
|
+
}, signal: AbortSignal): Promise<import("../contracts.js").StudioHarmonyProfileUpdateResult>;
|
|
54
|
+
projectState(input: {
|
|
55
|
+
draftId: string;
|
|
56
|
+
}, signal: AbortSignal): Promise<import("../contracts.js").StudioProjectState>;
|
|
57
|
+
projectActivate(input: {
|
|
58
|
+
draftId: string;
|
|
59
|
+
graphRev: string;
|
|
60
|
+
}, signal: AbortSignal): Promise<import("../contracts.js").StudioProjectState>;
|
|
61
|
+
projectFiles(input: {
|
|
62
|
+
draftId: string;
|
|
63
|
+
}, signal: AbortSignal): Promise<import("../contracts.js").StudioProjectFile[]>;
|
|
64
|
+
projectReadFile(input: {
|
|
65
|
+
draftId: string;
|
|
66
|
+
path: string;
|
|
67
|
+
}, signal: AbortSignal): Promise<{
|
|
68
|
+
path: string;
|
|
69
|
+
content: string;
|
|
70
|
+
}>;
|
|
71
|
+
projectWriteFile(input: {
|
|
72
|
+
draftId: string;
|
|
73
|
+
path: string;
|
|
74
|
+
content: string;
|
|
75
|
+
}, signal: AbortSignal): Promise<{
|
|
76
|
+
path: string;
|
|
77
|
+
saved: true;
|
|
78
|
+
}>;
|
|
79
|
+
projectBuild(input: {
|
|
80
|
+
draftId: string;
|
|
81
|
+
}, signal: AbortSignal): Promise<import("../contracts.js").StudioBuildResult>;
|
|
82
|
+
projectCancelBuild(input: {
|
|
83
|
+
draftId: string;
|
|
84
|
+
}, signal: AbortSignal): Promise<{
|
|
85
|
+
canceled: boolean;
|
|
86
|
+
}>;
|
|
87
|
+
elementsStyles(input: {
|
|
88
|
+
draftId: string;
|
|
89
|
+
}, signal: AbortSignal): Promise<StudioElementStyleSource[]>;
|
|
90
|
+
elementsSaveSource(input: {
|
|
91
|
+
draftId: string;
|
|
92
|
+
styles: StudioElementStyleSource[];
|
|
93
|
+
}, signal: AbortSignal): Promise<{
|
|
94
|
+
files: string[];
|
|
95
|
+
}>;
|
|
96
|
+
patchesAnalyzeAutomatic(input: StudioAutomaticPatchRequest & {
|
|
97
|
+
draftId: string;
|
|
98
|
+
}, signal: AbortSignal): Promise<import("../contracts.js").StudioAutomaticPatchPlan>;
|
|
99
|
+
patchesCreateAutomatic(input: StudioAutomaticPatchRequest & {
|
|
100
|
+
draftId: string;
|
|
101
|
+
}, signal: AbortSignal): Promise<import("../contracts.js").StudioAutomaticPatchWriteResult>;
|
|
102
|
+
readinessInspect(input: {
|
|
103
|
+
draftId: string;
|
|
104
|
+
}, signal: AbortSignal): Promise<import("../contracts.js").StudioReadinessReport>;
|
|
105
|
+
readinessPack(input: {
|
|
106
|
+
draftId: string;
|
|
107
|
+
}, signal: AbortSignal): Promise<import("../contracts.js").StudioReadinessReport>;
|
|
108
|
+
previewStatus(input: {
|
|
109
|
+
draftId: string;
|
|
110
|
+
}, signal: AbortSignal): Promise<import("../contracts.js").StudioPreviewStatus>;
|
|
111
|
+
previewUpdate(input: StudioPreviewUpdate & {
|
|
112
|
+
draftId: string;
|
|
113
|
+
}, signal: AbortSignal): Promise<import("../contracts.js").StudioPreviewStatus>;
|
|
114
|
+
previewResolveSource(input: {
|
|
115
|
+
draftId: string;
|
|
116
|
+
source: StudioSourceLocation;
|
|
117
|
+
}, signal: AbortSignal): Promise<import("../contracts.js").StudioSourceCandidate>;
|
|
118
|
+
agentCreate(input: {
|
|
119
|
+
draftId: string;
|
|
120
|
+
agentPreset?: string;
|
|
121
|
+
}, signal: AbortSignal): Promise<import("../contracts.js").StudioAgentBinding>;
|
|
122
|
+
agentAttach(input: {
|
|
123
|
+
draftId: string;
|
|
124
|
+
sessionId: string;
|
|
125
|
+
}, signal: AbortSignal): Promise<import("../contracts.js").StudioAgentBinding>;
|
|
126
|
+
agentLeave(input: {
|
|
127
|
+
draftId: string;
|
|
128
|
+
}, signal: AbortSignal): Promise<import("../contracts.js").StudioDraftView>;
|
|
129
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { TypertRemoteService } from '@deepseek-ai/dsh-typert-protocol';
|
|
2
|
+
import { STUDIO_INVOCATIONS } from '../studio-remote.js';
|
|
3
|
+
export const STUDIO_LOCAL = {
|
|
4
|
+
package: 'dsh-webui-studio/studio',
|
|
5
|
+
face: 'host',
|
|
6
|
+
schemas: [],
|
|
7
|
+
model: { services: [], events: [], objects: [] },
|
|
8
|
+
invocations: STUDIO_INVOCATIONS,
|
|
9
|
+
};
|
|
10
|
+
export class StudioService extends TypertRemoteService {
|
|
11
|
+
backend;
|
|
12
|
+
constructor(ctx, backend) {
|
|
13
|
+
super(ctx, 'studio');
|
|
14
|
+
this.backend = backend;
|
|
15
|
+
}
|
|
16
|
+
currentGet(signal) { signal.throwIfAborted(); return this.backend.currentGet(); }
|
|
17
|
+
currentPreviewStatus(signal) { signal.throwIfAborted(); return this.backend.currentPreviewStatus(); }
|
|
18
|
+
currentPreviewUpdate(input, signal) { signal.throwIfAborted(); return this.backend.currentPreviewUpdate(input); }
|
|
19
|
+
currentResolveSource(input, signal) { signal.throwIfAborted(); return this.backend.currentResolveSource(input); }
|
|
20
|
+
currentAgentCreate(input, signal) { signal.throwIfAborted(); return this.backend.currentAgentCreate(input); }
|
|
21
|
+
currentAgentAttach(input, signal) { signal.throwIfAborted(); return this.backend.currentAgentAttach(input); }
|
|
22
|
+
currentAgentLeave(signal) { signal.throwIfAborted(); return this.backend.currentAgentLeave(); }
|
|
23
|
+
draftsList(signal) { signal.throwIfAborted(); return this.backend.draftsList(); }
|
|
24
|
+
draftsCreate(input, signal) { signal.throwIfAborted(); return this.backend.draftsCreate(input); }
|
|
25
|
+
draftsRename(input, signal) { signal.throwIfAborted(); return this.backend.draftsRename(input); }
|
|
26
|
+
draftsExport(input, signal) { signal.throwIfAborted(); return this.backend.draftsExport(input); }
|
|
27
|
+
draftsStart(input, signal) { signal.throwIfAborted(); return this.backend.draftsStart(input); }
|
|
28
|
+
draftsStop(input, signal) { signal.throwIfAborted(); return this.backend.draftsStop(input); }
|
|
29
|
+
workspaceGet(signal) { signal.throwIfAborted(); return this.backend.workspaceGet(); }
|
|
30
|
+
workspaceUpdate(input, signal) { signal.throwIfAborted(); return this.backend.workspaceUpdate(input); }
|
|
31
|
+
harmonyProfile(input, signal) { signal.throwIfAborted(); return this.backend.harmonyProfile(input); }
|
|
32
|
+
harmonyInspect(input, signal) { signal.throwIfAborted(); return this.backend.harmonyInspect(input); }
|
|
33
|
+
harmonyUpdateProfile(input, signal) { signal.throwIfAborted(); return this.backend.harmonyUpdateProfile(input); }
|
|
34
|
+
projectState(input, signal) { signal.throwIfAborted(); return this.backend.projectState(input); }
|
|
35
|
+
projectActivate(input, signal) { signal.throwIfAborted(); return this.backend.projectActivate(input); }
|
|
36
|
+
projectFiles(input, signal) { signal.throwIfAborted(); return this.backend.projectFiles(input); }
|
|
37
|
+
projectReadFile(input, signal) { signal.throwIfAborted(); return this.backend.projectReadFile(input); }
|
|
38
|
+
projectWriteFile(input, signal) { signal.throwIfAborted(); return this.backend.projectWriteFile(input); }
|
|
39
|
+
projectBuild(input, signal) { return this.backend.projectBuild(input, signal); }
|
|
40
|
+
projectCancelBuild(input, signal) { signal.throwIfAborted(); return this.backend.projectCancelBuild(input); }
|
|
41
|
+
elementsStyles(input, signal) { signal.throwIfAborted(); return this.backend.elementsStyles(input); }
|
|
42
|
+
elementsSaveSource(input, signal) { signal.throwIfAborted(); return this.backend.elementsSaveSource(input); }
|
|
43
|
+
patchesAnalyzeAutomatic(input, signal) { signal.throwIfAborted(); return this.backend.patchesAnalyzeAutomatic(input); }
|
|
44
|
+
patchesCreateAutomatic(input, signal) { signal.throwIfAborted(); return this.backend.patchesCreateAutomatic(input); }
|
|
45
|
+
readinessInspect(input, signal) { signal.throwIfAborted(); return this.backend.readinessInspect(input); }
|
|
46
|
+
readinessPack(input, signal) { signal.throwIfAborted(); return this.backend.readinessPack(input); }
|
|
47
|
+
previewStatus(input, signal) { signal.throwIfAborted(); return this.backend.previewStatus(input); }
|
|
48
|
+
previewUpdate(input, signal) { signal.throwIfAborted(); return this.backend.previewUpdate(input); }
|
|
49
|
+
previewResolveSource(input, signal) { signal.throwIfAborted(); return this.backend.previewResolveSource(input); }
|
|
50
|
+
agentCreate(input, signal) { signal.throwIfAborted(); return this.backend.agentCreate(input); }
|
|
51
|
+
agentAttach(input, signal) { signal.throwIfAborted(); return this.backend.agentAttach(input); }
|
|
52
|
+
agentLeave(input, signal) { signal.throwIfAborted(); return this.backend.agentLeave(input); }
|
|
53
|
+
}
|
package/lib/index.d.ts
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import type { Context } from '@deepseek-ai/cordis';
|
|
2
|
+
import '@deepseek-ai/cordis-plugin-loader';
|
|
3
|
+
import '@deepseek-ai/dsh-client-modules';
|
|
2
4
|
import '@deepseek-ai/dsh-agent';
|
|
3
5
|
import '@deepseek-ai/dsh-host-apiproxy';
|
|
4
6
|
import '@deepseek-ai/dsh-host-webserver';
|
|
7
|
+
import '@deepseek-ai/dsh-skill';
|
|
5
8
|
import '@deepseek-ai/dsh-system-prompt';
|
|
6
9
|
import '@deepseek-ai/dsh-subprocess';
|
|
7
10
|
import '@deepseek-ai/dsh-tools';
|
|
11
|
+
import 'dsh-harmony';
|
|
8
12
|
export declare const name = "harmony-studio";
|
|
9
13
|
export declare const inject: string[];
|
|
10
14
|
export declare function apply(ctx: Context): void;
|
|
@@ -13,6 +17,7 @@ export { StudioBuildError, StudioBuildRunner, resolveBuildArgv } from './host/bu
|
|
|
13
17
|
export type { StudioBuildOutput } from './host/build.js';
|
|
14
18
|
export { StudioDraftRegistry, dshHomeFromProfile } from './host/drafts.js';
|
|
15
19
|
export { inspectReadiness, StudioPackRunner } from './host/readiness.js';
|
|
20
|
+
export { createStudioMcpRoute, STUDIO_MCP_PATH } from './host/mcp.js';
|
|
16
21
|
export { StudioPreviewSupervisor } from './host/preview.js';
|
|
17
22
|
export { createStudioRoutes, isTrustedStudioRequest } from './host/routes.js';
|
|
18
23
|
export { StudioWorkspaceStore } from './host/workspace.js';
|
package/lib/index.js
CHANGED
|
@@ -1,67 +1,101 @@
|
|
|
1
|
-
import { randomBytes } from 'node:crypto';
|
|
2
1
|
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { randomBytes } from 'node:crypto';
|
|
3
|
+
import '@deepseek-ai/cordis-plugin-loader';
|
|
4
|
+
import '@deepseek-ai/dsh-client-modules';
|
|
3
5
|
import '@deepseek-ai/dsh-agent';
|
|
4
6
|
import '@deepseek-ai/dsh-host-apiproxy';
|
|
5
7
|
import '@deepseek-ai/dsh-host-webserver';
|
|
8
|
+
import '@deepseek-ai/dsh-skill';
|
|
6
9
|
import '@deepseek-ai/dsh-system-prompt';
|
|
7
10
|
import '@deepseek-ai/dsh-subprocess';
|
|
8
11
|
import '@deepseek-ai/dsh-tools';
|
|
12
|
+
import 'dsh-harmony';
|
|
9
13
|
import { STUDIO_PATH } from './contracts.js';
|
|
10
14
|
import { StudioBackend } from './host/backend.js';
|
|
11
15
|
import { dshHomeFromProfile, StudioDraftRegistry, studioCommands } from './host/drafts.js';
|
|
16
|
+
import { createStudioMcpRoute } from './host/mcp.js';
|
|
12
17
|
import { applyPreviewWorker } from './host/preview-worker.js';
|
|
13
18
|
import { createStudioRoutes } from './host/routes.js';
|
|
19
|
+
import { STUDIO_LOCAL, StudioService } from './host/studio-service.js';
|
|
14
20
|
import { StudioWorkspaceStore } from './host/workspace.js';
|
|
15
21
|
export const name = 'harmony-studio';
|
|
16
|
-
export const inject = ['
|
|
22
|
+
export const inject = ['webServer'];
|
|
23
|
+
const runtimeInject = ['harmony', 'agents', 'tools', 'skills', 'systemPrompt', 'webServer', 'subprocess', 'loader', 'clientModules', 'typert'];
|
|
17
24
|
export function apply(ctx) {
|
|
18
|
-
ctx.
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
if (previewRoot !== undefined) {
|
|
33
|
-
const controlToken = process.env.DSH_STUDIO_PREVIEW_CONTROL_TOKEN;
|
|
25
|
+
if (ctx.webServer.host !== '127.0.0.1') {
|
|
26
|
+
ctx.logger.warn('harmony-studio: Studio is disabled because dsh web is not bound to 127.0.0.1');
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const assets = {
|
|
30
|
+
script: readFileSync(new URL('../dist/studio.js', import.meta.url)),
|
|
31
|
+
style: readFileSync(new URL('../dist/studio.css', import.meta.url)),
|
|
32
|
+
bridge: readFileSync(new URL('../dist/bridge.js', import.meta.url)),
|
|
33
|
+
icon: readFileSync(new URL('../assets/harmony-icon.png', import.meta.url)),
|
|
34
|
+
iconMono: readFileSync(new URL('../assets/harmony-icon-mono.png', import.meta.url)),
|
|
35
|
+
};
|
|
36
|
+
const previewRoot = process.env.DSH_STUDIO_PREVIEW_DRAFT_ROOT;
|
|
37
|
+
if (previewRoot !== undefined) {
|
|
38
|
+
ctx.inject(runtimeInject, previewCtx => {
|
|
34
39
|
const parentOrigin = process.env.DSH_STUDIO_PREVIEW_PARENT_ORIGIN;
|
|
35
40
|
const bridgeCapability = process.env.DSH_STUDIO_PREVIEW_BRIDGE_CAPABILITY;
|
|
36
|
-
|
|
41
|
+
const packageDirsSource = process.env.DSH_STUDIO_PREVIEW_PACKAGE_DIRS;
|
|
42
|
+
if (parentOrigin === undefined || bridgeCapability === undefined || packageDirsSource === undefined) {
|
|
37
43
|
throw new Error('harmony-studio: Preview worker environment is incomplete');
|
|
38
44
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
45
|
+
const packageDirs = JSON.parse(packageDirsSource);
|
|
46
|
+
if (!Array.isArray(packageDirs) || !packageDirs.every(item => typeof item === 'string')) {
|
|
47
|
+
throw new Error('harmony-studio: Preview package directories are invalid');
|
|
48
|
+
}
|
|
49
|
+
applyPreviewWorker(previewCtx, previewCtx.harmony, {
|
|
50
|
+
root: previewRoot,
|
|
51
|
+
packageDirs,
|
|
52
|
+
parentOrigin,
|
|
53
|
+
bridgeCapability,
|
|
54
|
+
bridge: assets.bridge,
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
let runtimeReady = false;
|
|
60
|
+
ctx.effect(() => {
|
|
61
|
+
const dispose = createStudioRoutes(assets, () => runtimeReady).map(route => ctx.webServer.register(route));
|
|
62
|
+
return () => {
|
|
63
|
+
for (const stop of dispose.reverse())
|
|
64
|
+
stop();
|
|
65
|
+
};
|
|
66
|
+
}, 'harmony-studio: static routes');
|
|
67
|
+
ctx.inject(runtimeInject, runtimeCtx => runtimeCtx.effect(() => {
|
|
68
|
+
const host = `127.0.0.1:${runtimeCtx.webServer.port}`;
|
|
69
|
+
const dshHome = dshHomeFromProfile(runtimeCtx.harmony.profile().dir);
|
|
70
|
+
const currentBridgeCapability = randomBytes(24).toString('base64url');
|
|
71
|
+
const backend = new StudioBackend(runtimeCtx.harmony, runtimeCtx.agents, runtimeCtx.subprocess, new StudioDraftRegistry(dshHome), new StudioWorkspaceStore(dshHome), studioCommands, `http://${host}`, currentBridgeCapability);
|
|
72
|
+
new StudioService(runtimeCtx, backend);
|
|
46
73
|
const dispose = [
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
74
|
+
runtimeCtx.typert.register(STUDIO_LOCAL),
|
|
75
|
+
runtimeCtx.webServer.register(createStudioMcpRoute(backend)),
|
|
76
|
+
runtimeCtx.webServer.tapIndex(html => {
|
|
77
|
+
const script = `<script>window.__DSH_STUDIO_PREVIEW__=${JSON.stringify({
|
|
78
|
+
parentOrigin: `http://${host}`,
|
|
79
|
+
capability: currentBridgeCapability,
|
|
80
|
+
})}</script><script src="${STUDIO_PATH}/bridge.js"></script>`;
|
|
50
81
|
const head = html.indexOf('<head>');
|
|
51
82
|
return head === -1 ? `${script}${html}` : `${html.slice(0, head + 6)}${script}${html.slice(head + 6)}`;
|
|
52
83
|
}),
|
|
53
84
|
];
|
|
85
|
+
runtimeReady = true;
|
|
54
86
|
return async () => {
|
|
87
|
+
runtimeReady = false;
|
|
55
88
|
for (const stop of dispose.reverse())
|
|
56
89
|
stop();
|
|
57
90
|
await backend.dispose();
|
|
58
91
|
};
|
|
59
|
-
}, 'harmony-studio: routes');
|
|
92
|
+
}, 'harmony-studio: runtime routes'));
|
|
60
93
|
}
|
|
61
94
|
export { StudioBackend } from './host/backend.js';
|
|
62
95
|
export { StudioBuildError, StudioBuildRunner, resolveBuildArgv } from './host/build.js';
|
|
63
96
|
export { StudioDraftRegistry, dshHomeFromProfile } from './host/drafts.js';
|
|
64
97
|
export { inspectReadiness, StudioPackRunner } from './host/readiness.js';
|
|
98
|
+
export { createStudioMcpRoute, STUDIO_MCP_PATH } from './host/mcp.js';
|
|
65
99
|
export { StudioPreviewSupervisor } from './host/preview.js';
|
|
66
100
|
export { createStudioRoutes, isTrustedStudioRequest } from './host/routes.js';
|
|
67
101
|
export { StudioWorkspaceStore } from './host/workspace.js';
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import type { InvocationDescriptor, RemoteResult, TypertRemoteContribution } from '@deepseek-ai/dsh-typert-protocol';
|
|
2
|
+
import type { StudioAgentBinding, StudioAutomaticPatchPlan, StudioAutomaticPatchRequest, StudioAutomaticPatchWriteResult, StudioBuildResult, StudioCreateDraftInput, StudioCurrentInstanceView, StudioDraftView, StudioElementStyleSource, StudioHarmonyInspection, StudioHarmonyProfile, StudioHarmonyProfileUpdateResult, StudioPreviewStatus, StudioPreviewUpdate, StudioProjectFile, StudioProjectState, StudioReadinessReport, StudioSourceCandidate, StudioSourceLocation, StudioWorkspaceState } from './contracts.js';
|
|
3
|
+
export interface StudioRemote {
|
|
4
|
+
currentGet(signal?: AbortSignal): Promise<RemoteResult<StudioCurrentInstanceView>>;
|
|
5
|
+
currentPreviewStatus(signal?: AbortSignal): Promise<RemoteResult<StudioPreviewStatus>>;
|
|
6
|
+
currentPreviewUpdate(input: StudioPreviewUpdate, signal?: AbortSignal): Promise<RemoteResult<StudioPreviewStatus>>;
|
|
7
|
+
currentResolveSource(input: {
|
|
8
|
+
source: StudioSourceLocation;
|
|
9
|
+
}, signal?: AbortSignal): Promise<RemoteResult<StudioSourceCandidate>>;
|
|
10
|
+
currentAgentCreate(input: {
|
|
11
|
+
agentPreset?: string;
|
|
12
|
+
}, signal?: AbortSignal): Promise<RemoteResult<StudioAgentBinding>>;
|
|
13
|
+
currentAgentAttach(input: {
|
|
14
|
+
sessionId: string;
|
|
15
|
+
}, signal?: AbortSignal): Promise<RemoteResult<StudioAgentBinding>>;
|
|
16
|
+
currentAgentLeave(signal?: AbortSignal): Promise<RemoteResult<StudioCurrentInstanceView>>;
|
|
17
|
+
draftsList(signal?: AbortSignal): Promise<RemoteResult<StudioDraftView[]>>;
|
|
18
|
+
draftsCreate(input: StudioCreateDraftInput, signal?: AbortSignal): Promise<RemoteResult<StudioDraftView>>;
|
|
19
|
+
draftsRename(input: {
|
|
20
|
+
draftId: string;
|
|
21
|
+
label: string;
|
|
22
|
+
}, signal?: AbortSignal): Promise<RemoteResult<StudioDraftView>>;
|
|
23
|
+
draftsExport(input: {
|
|
24
|
+
draftId: string;
|
|
25
|
+
}, signal?: AbortSignal): Promise<RemoteResult<StudioDraftView>>;
|
|
26
|
+
draftsStart(input: {
|
|
27
|
+
draftId: string;
|
|
28
|
+
}, signal?: AbortSignal): Promise<RemoteResult<StudioDraftView>>;
|
|
29
|
+
draftsStop(input: {
|
|
30
|
+
draftId: string;
|
|
31
|
+
}, signal?: AbortSignal): Promise<RemoteResult<StudioDraftView>>;
|
|
32
|
+
workspaceGet(signal?: AbortSignal): Promise<RemoteResult<StudioWorkspaceState>>;
|
|
33
|
+
workspaceUpdate(input: StudioWorkspaceState, signal?: AbortSignal): Promise<RemoteResult<StudioWorkspaceState>>;
|
|
34
|
+
harmonyProfile(input: {
|
|
35
|
+
draftId: string;
|
|
36
|
+
}, signal?: AbortSignal): Promise<RemoteResult<StudioHarmonyProfile>>;
|
|
37
|
+
harmonyInspect(input: {
|
|
38
|
+
draftId: string;
|
|
39
|
+
package?: string;
|
|
40
|
+
file?: string;
|
|
41
|
+
}, signal?: AbortSignal): Promise<RemoteResult<StudioHarmonyInspection>>;
|
|
42
|
+
harmonyUpdateProfile(input: {
|
|
43
|
+
draftId: string;
|
|
44
|
+
order?: string[];
|
|
45
|
+
patchOrder?: string[];
|
|
46
|
+
disabled?: string[];
|
|
47
|
+
}, signal?: AbortSignal): Promise<RemoteResult<StudioHarmonyProfileUpdateResult>>;
|
|
48
|
+
projectState(input: {
|
|
49
|
+
draftId: string;
|
|
50
|
+
}, signal?: AbortSignal): Promise<RemoteResult<StudioProjectState>>;
|
|
51
|
+
projectActivate(input: {
|
|
52
|
+
draftId: string;
|
|
53
|
+
graphRev: string;
|
|
54
|
+
}, signal?: AbortSignal): Promise<RemoteResult<StudioProjectState>>;
|
|
55
|
+
projectFiles(input: {
|
|
56
|
+
draftId: string;
|
|
57
|
+
}, signal?: AbortSignal): Promise<RemoteResult<StudioProjectFile[]>>;
|
|
58
|
+
projectReadFile(input: {
|
|
59
|
+
draftId: string;
|
|
60
|
+
path: string;
|
|
61
|
+
}, signal?: AbortSignal): Promise<RemoteResult<{
|
|
62
|
+
path: string;
|
|
63
|
+
content: string;
|
|
64
|
+
}>>;
|
|
65
|
+
projectWriteFile(input: {
|
|
66
|
+
draftId: string;
|
|
67
|
+
path: string;
|
|
68
|
+
content: string;
|
|
69
|
+
}, signal?: AbortSignal): Promise<RemoteResult<{
|
|
70
|
+
path: string;
|
|
71
|
+
saved: true;
|
|
72
|
+
}>>;
|
|
73
|
+
projectBuild(input: {
|
|
74
|
+
draftId: string;
|
|
75
|
+
}, signal?: AbortSignal): Promise<RemoteResult<StudioBuildResult>>;
|
|
76
|
+
projectCancelBuild(input: {
|
|
77
|
+
draftId: string;
|
|
78
|
+
}, signal?: AbortSignal): Promise<RemoteResult<{
|
|
79
|
+
canceled: boolean;
|
|
80
|
+
}>>;
|
|
81
|
+
elementsStyles(input: {
|
|
82
|
+
draftId: string;
|
|
83
|
+
}, signal?: AbortSignal): Promise<RemoteResult<StudioElementStyleSource[]>>;
|
|
84
|
+
elementsSaveSource(input: {
|
|
85
|
+
draftId: string;
|
|
86
|
+
styles: StudioElementStyleSource[];
|
|
87
|
+
}, signal?: AbortSignal): Promise<RemoteResult<{
|
|
88
|
+
files: string[];
|
|
89
|
+
}>>;
|
|
90
|
+
patchesAnalyzeAutomatic(input: StudioAutomaticPatchRequest & {
|
|
91
|
+
draftId: string;
|
|
92
|
+
}, signal?: AbortSignal): Promise<RemoteResult<StudioAutomaticPatchPlan>>;
|
|
93
|
+
patchesCreateAutomatic(input: StudioAutomaticPatchRequest & {
|
|
94
|
+
draftId: string;
|
|
95
|
+
}, signal?: AbortSignal): Promise<RemoteResult<StudioAutomaticPatchWriteResult>>;
|
|
96
|
+
readinessInspect(input: {
|
|
97
|
+
draftId: string;
|
|
98
|
+
}, signal?: AbortSignal): Promise<RemoteResult<StudioReadinessReport>>;
|
|
99
|
+
readinessPack(input: {
|
|
100
|
+
draftId: string;
|
|
101
|
+
}, signal?: AbortSignal): Promise<RemoteResult<StudioReadinessReport>>;
|
|
102
|
+
previewStatus(input: {
|
|
103
|
+
draftId: string;
|
|
104
|
+
}, signal?: AbortSignal): Promise<RemoteResult<StudioPreviewStatus>>;
|
|
105
|
+
previewUpdate(input: StudioPreviewUpdate & {
|
|
106
|
+
draftId: string;
|
|
107
|
+
}, signal?: AbortSignal): Promise<RemoteResult<StudioPreviewStatus>>;
|
|
108
|
+
previewResolveSource(input: {
|
|
109
|
+
draftId: string;
|
|
110
|
+
source: StudioSourceLocation;
|
|
111
|
+
}, signal?: AbortSignal): Promise<RemoteResult<StudioSourceCandidate>>;
|
|
112
|
+
agentCreate(input: {
|
|
113
|
+
draftId: string;
|
|
114
|
+
agentPreset?: string;
|
|
115
|
+
}, signal?: AbortSignal): Promise<RemoteResult<StudioAgentBinding>>;
|
|
116
|
+
agentAttach(input: {
|
|
117
|
+
draftId: string;
|
|
118
|
+
sessionId: string;
|
|
119
|
+
}, signal?: AbortSignal): Promise<RemoteResult<StudioAgentBinding>>;
|
|
120
|
+
agentLeave(input: {
|
|
121
|
+
draftId: string;
|
|
122
|
+
}, signal?: AbortSignal): Promise<RemoteResult<StudioDraftView>>;
|
|
123
|
+
}
|
|
124
|
+
export declare function invokeStudioRemote(remote: StudioRemote, method: string, payload: any, signal?: AbortSignal): Promise<RemoteResult<unknown>> | undefined;
|
|
125
|
+
export declare const STUDIO_INVOCATIONS: readonly InvocationDescriptor[];
|
|
126
|
+
export declare const STUDIO_REMOTE: TypertRemoteContribution;
|