@ran-sh/dsh-crew 0.3.7 → 0.4.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/README.md +52 -67
- package/README.zh.md +52 -68
- package/docs/gpt-relay-extension.md +103 -0
- package/docs/job-contracts.md +107 -0
- package/docs/readiness-matrix.md +73 -0
- package/lib/client.js +3446 -3446
- package/official-web-bridge/cordis.patch.yml +4 -0
- package/official-web-bridge/entry.mjs +1 -0
- package/official-web-bridge/lib/client.js +3446 -0
- package/official-web-bridge/package.json +26 -0
- package/package.json +8 -3
- package/scripts/build-client.mjs +5 -1
- package/scripts/verify-official-bridge-e2e.mjs +180 -0
- package/src/dsh-cli-runtime.mjs +219 -208
- package/src/extension-contract.mjs +78 -0
- package/src/failure-classification.mjs +29 -0
- package/src/hub/index.mjs +343 -62
- package/src/information-flow.mjs +67 -0
- package/src/install/npx-lifecycle.mjs +149 -5
- package/src/install/official-web.mjs +132 -0
- package/src/job-contracts.mjs +218 -0
- package/src/mcp-runtime.mjs +16 -8
- package/src/official-web-bridge.mjs +211 -0
- package/src/role-profiles.mjs +107 -0
- package/src/runtime-identity.mjs +6 -1
- package/src/server.mjs +141 -98
- package/src/workflow-runtime.mjs +109 -10
- package/src/workspace-context.mjs +146 -0
- package/src/workspace-readiness.mjs +32 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ran-sh/dsh-crew-web-bridge",
|
|
3
|
+
"version": "0.3.8",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./entry.mjs",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./entry.mjs",
|
|
9
|
+
"./client": "./lib/client.js",
|
|
10
|
+
"./package.json": "./package.json"
|
|
11
|
+
},
|
|
12
|
+
"dsh": {
|
|
13
|
+
"bundle": {
|
|
14
|
+
"patch": "./cordis.patch.yml"
|
|
15
|
+
},
|
|
16
|
+
"client": {
|
|
17
|
+
"inject": [
|
|
18
|
+
"@deepseek-ai/dsh-client-runtime",
|
|
19
|
+
"@deepseek-ai/dsh-client-locale",
|
|
20
|
+
"@deepseek-ai/dsh-client-ui-slots",
|
|
21
|
+
"@deepseek-ai/dsh-client-ui-settings"
|
|
22
|
+
],
|
|
23
|
+
"platform": "web"
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ran-sh/dsh-crew",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./src/hub/entry.mjs",
|
|
6
6
|
"bin": {
|
|
@@ -25,10 +25,11 @@
|
|
|
25
25
|
"mcp": "node src/server.mjs",
|
|
26
26
|
"build:client": "tsdown src/client/entry.tsx --format cjs --platform browser --target es2022 --tsconfig tsconfig.client.json --deps.never-bundle react --deps.never-bundle react/jsx-runtime --deps.never-bundle react-dom --deps.never-bundle react-dom/client --out-dir .client-build --clean --logLevel warn && node scripts/build-client.mjs",
|
|
27
27
|
"verify:npm-install": "node scripts/verify-npm-install.mjs",
|
|
28
|
-
"verify:npm-install:official": "node scripts/verify-npm-install.mjs --with-official-dsh"
|
|
28
|
+
"verify:npm-install:official": "node scripts/verify-npm-install.mjs --with-official-dsh",
|
|
29
|
+
"verify:official-bridge": "node scripts/verify-official-bridge-e2e.mjs"
|
|
29
30
|
},
|
|
30
31
|
"dependencies": {
|
|
31
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
32
|
+
"@modelcontextprotocol/sdk": "^1.25.4",
|
|
32
33
|
"zod": "^3.24.0"
|
|
33
34
|
},
|
|
34
35
|
"exports": {
|
|
@@ -116,12 +117,16 @@
|
|
|
116
117
|
"commands",
|
|
117
118
|
"codex",
|
|
118
119
|
"statusline",
|
|
120
|
+
"official-web-bridge",
|
|
119
121
|
"cordis.patch.yml",
|
|
120
122
|
"worker.cordis.yml",
|
|
121
123
|
".claude-plugin",
|
|
122
124
|
".mcp.json",
|
|
123
125
|
"README.md",
|
|
124
126
|
"README.*.md",
|
|
127
|
+
"docs/job-contracts.md",
|
|
128
|
+
"docs/readiness-matrix.md",
|
|
129
|
+
"docs/gpt-relay-extension.md",
|
|
125
130
|
"docs/images/dsh-crew-logo.png",
|
|
126
131
|
"docs/images/dsh-crew-overview.png",
|
|
127
132
|
"docs/images/dsh-crew-host.png",
|
package/scripts/build-client.mjs
CHANGED
|
@@ -24,5 +24,9 @@ const wrapped = [
|
|
|
24
24
|
].join('\n');
|
|
25
25
|
await mkdir(join(root, 'lib'), { recursive: true });
|
|
26
26
|
await writeFile(join(root, 'lib', 'client.js'), wrapped);
|
|
27
|
+
const bridgeManifest = JSON.parse(await readFile(join(root, 'official-web-bridge', 'package.json'), 'utf8'));
|
|
28
|
+
const bridgeWrapped = wrapped.replace(JSON.stringify(pluginId), JSON.stringify(bridgeManifest.name));
|
|
29
|
+
await mkdir(join(root, 'official-web-bridge', 'lib'), { recursive: true });
|
|
30
|
+
await writeFile(join(root, 'official-web-bridge', 'lib', 'client.js'), bridgeWrapped);
|
|
27
31
|
await rm(buildDir, { recursive: true, force: true });
|
|
28
|
-
console.log(`built lib/client.js (${wrapped.length} bytes)
|
|
32
|
+
console.log(`built lib/client.js and official-web-bridge/lib/client.js (${wrapped.length} bytes) from ${compiledEntries[0]}`);
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
// Real, disposable official-UI acceptance for the 3080 -> isolated 3210 bridge.
|
|
2
|
+
// It never edits the user's official profile or Crew installation.
|
|
3
|
+
|
|
4
|
+
import { spawn, spawnSync } from 'node:child_process';
|
|
5
|
+
import {
|
|
6
|
+
existsSync,
|
|
7
|
+
mkdirSync,
|
|
8
|
+
mkdtempSync,
|
|
9
|
+
readFileSync,
|
|
10
|
+
realpathSync,
|
|
11
|
+
rmSync,
|
|
12
|
+
symlinkSync,
|
|
13
|
+
writeFileSync,
|
|
14
|
+
} from 'node:fs';
|
|
15
|
+
import { homedir, tmpdir } from 'node:os';
|
|
16
|
+
import { createServer } from 'node:net';
|
|
17
|
+
import { dirname, join, resolve } from 'node:path';
|
|
18
|
+
import { fileURLToPath } from 'node:url';
|
|
19
|
+
import { npxInstall, npxIntegrate } from '../src/install/npx-lifecycle.mjs';
|
|
20
|
+
|
|
21
|
+
const root = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
|
22
|
+
const candidateVersion = JSON.parse(readFileSync(join(root, 'package.json'), 'utf8')).version;
|
|
23
|
+
if (typeof candidateVersion !== 'string' || candidateVersion === '') throw new Error('candidate package version is unavailable');
|
|
24
|
+
const realHome = homedir();
|
|
25
|
+
const runtimeRoot = join(realHome, '.config', 'dsh-crew', 'harness', 'runtime');
|
|
26
|
+
const runtimeModule = join(runtimeRoot, 'node_modules', '@deepseek-ai', 'dsh', 'lib', 'bin.js');
|
|
27
|
+
if (!existsSync(runtimeModule)) throw new Error('real Crew DSH runtime is not installed');
|
|
28
|
+
|
|
29
|
+
const sandbox = mkdtempSync(join(tmpdir(), 'dsh-crew-official-e2e-'));
|
|
30
|
+
const officialHome = join(sandbox, '.dsh');
|
|
31
|
+
const crewRoot = join(sandbox, '.config', 'dsh-crew');
|
|
32
|
+
const harnessHome = join(crewRoot, 'harness');
|
|
33
|
+
const profileRoot = join(officialHome, 'profiles', 'web');
|
|
34
|
+
const logs = [];
|
|
35
|
+
let official;
|
|
36
|
+
|
|
37
|
+
async function reservePort() {
|
|
38
|
+
const server = createServer();
|
|
39
|
+
await new Promise((resolveListen, reject) => {
|
|
40
|
+
server.once('error', reject);
|
|
41
|
+
server.listen(0, '127.0.0.1', resolveListen);
|
|
42
|
+
});
|
|
43
|
+
const port = server.address().port;
|
|
44
|
+
await new Promise((resolveClose) => server.close(resolveClose));
|
|
45
|
+
return port;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const officialPort = await reservePort();
|
|
49
|
+
const crewPort = await reservePort();
|
|
50
|
+
const officialUrl = `http://127.0.0.1:${officialPort}`;
|
|
51
|
+
const crewUrl = `http://127.0.0.1:${crewPort}`;
|
|
52
|
+
|
|
53
|
+
function assert(condition, message) {
|
|
54
|
+
if (!condition) throw new Error(message);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async function waitFor(url, { timeout = 30_000 } = {}) {
|
|
58
|
+
const deadline = Date.now() + timeout;
|
|
59
|
+
let last;
|
|
60
|
+
while (Date.now() < deadline) {
|
|
61
|
+
try {
|
|
62
|
+
const response = await fetch(url, { signal: AbortSignal.timeout(2_000) });
|
|
63
|
+
if (response.ok) return response;
|
|
64
|
+
last = `HTTP ${response.status}`;
|
|
65
|
+
} catch (error) { last = error?.message ?? String(error); }
|
|
66
|
+
await new Promise((resolveWait) => setTimeout(resolveWait, 250));
|
|
67
|
+
}
|
|
68
|
+
throw new Error(`timed out waiting for ${url}: ${last}`);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async function waitForPageMarker(url, marker, { timeout = 30_000 } = {}) {
|
|
72
|
+
const deadline = Date.now() + timeout;
|
|
73
|
+
while (Date.now() < deadline) {
|
|
74
|
+
try {
|
|
75
|
+
const response = await fetch(url, { signal: AbortSignal.timeout(2_000) });
|
|
76
|
+
const body = await response.text();
|
|
77
|
+
if (response.ok && body.includes(marker)) return body;
|
|
78
|
+
} catch {}
|
|
79
|
+
await new Promise((resolveWait) => setTimeout(resolveWait, 250));
|
|
80
|
+
}
|
|
81
|
+
throw new Error(`timed out waiting for ${marker} in ${url}`);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function stopOwnedProcesses() {
|
|
85
|
+
if (process.platform !== 'win32') return;
|
|
86
|
+
const escaped = sandbox.replace(/'/g, "''");
|
|
87
|
+
const command = [
|
|
88
|
+
`$sandbox = '${escaped}'`,
|
|
89
|
+
"$processes = Get-CimInstance Win32_Process -Filter \"Name='node.exe'\"",
|
|
90
|
+
'foreach ($process in $processes) {',
|
|
91
|
+
' if ($process.CommandLine -and $process.CommandLine.Contains($sandbox)) { Stop-Process -Id $process.ProcessId -Force }',
|
|
92
|
+
'}',
|
|
93
|
+
].join('; ');
|
|
94
|
+
spawnSync('powershell.exe', ['-NoProfile', '-NonInteractive', '-Command', command], { windowsHide: true });
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
mkdirSync(profileRoot, { recursive: true });
|
|
99
|
+
writeFileSync(join(profileRoot, 'package.json'), JSON.stringify({
|
|
100
|
+
name: 'dsh-profile-web',
|
|
101
|
+
private: true,
|
|
102
|
+
dependencies: {},
|
|
103
|
+
dsh: { profile: { bundles: ['@deepseek-ai/dsh-base', '@deepseek-ai/dsh-web-app'] } },
|
|
104
|
+
}, null, 2));
|
|
105
|
+
writeFileSync(join(profileRoot, 'cordis.patch.yml'), '[]\n');
|
|
106
|
+
writeFileSync(join(profileRoot, 'pnpm-workspace.yaml'), 'packages:\n - .\n\nnodeLinker: hoisted\nautoInstallPeers: false\n');
|
|
107
|
+
|
|
108
|
+
const currentConfig = join(realHome, '.config', 'dsh-crew', 'config.json');
|
|
109
|
+
let sandboxConfig = {};
|
|
110
|
+
if (existsSync(currentConfig)) {
|
|
111
|
+
mkdirSync(crewRoot, { recursive: true });
|
|
112
|
+
sandboxConfig = JSON.parse(readFileSync(currentConfig, 'utf8'));
|
|
113
|
+
}
|
|
114
|
+
mkdirSync(crewRoot, { recursive: true });
|
|
115
|
+
writeFileSync(join(crewRoot, 'config.json'), JSON.stringify({ ...sandboxConfig, hub_url: crewUrl }, null, 2));
|
|
116
|
+
|
|
117
|
+
const installer = {
|
|
118
|
+
installCodex: () => ({ ok: true, actions: [] }),
|
|
119
|
+
installClaudeCode: async () => ({ ok: true, actions: [] }),
|
|
120
|
+
installStatus: () => ({ codex: { installed: true }, claude: { installed: true } }),
|
|
121
|
+
};
|
|
122
|
+
const installed = await npxInstall({
|
|
123
|
+
home: sandbox,
|
|
124
|
+
sourceRoot: root,
|
|
125
|
+
installer,
|
|
126
|
+
log: (line) => logs.push(line),
|
|
127
|
+
ensureRuntime: async () => {
|
|
128
|
+
const target = join(harnessHome, 'runtime');
|
|
129
|
+
mkdirSync(dirname(target), { recursive: true });
|
|
130
|
+
symlinkSync(realpathSync(runtimeRoot), target, process.platform === 'win32' ? 'junction' : 'dir');
|
|
131
|
+
return { ok: true, version: '0.1.1-rc.2' };
|
|
132
|
+
},
|
|
133
|
+
});
|
|
134
|
+
assert(installed.ok, `disposable install failed: ${logs.join(' | ')}`);
|
|
135
|
+
const integrated = await npxIntegrate({ home: sandbox, log: (line) => logs.push(line) });
|
|
136
|
+
assert(integrated.ok, `disposable integrate failed: ${logs.join(' | ')}`);
|
|
137
|
+
|
|
138
|
+
official = spawn(process.execPath, [
|
|
139
|
+
runtimeModule, '--profile', 'web', '--host', '127.0.0.1', '--port', String(officialPort), '--no-open',
|
|
140
|
+
], {
|
|
141
|
+
env: { ...process.env, HOME: sandbox, USERPROFILE: sandbox, DSH_HOME: officialHome, DSH_CREW_BRIDGE_TARGET: crewUrl },
|
|
142
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
143
|
+
windowsHide: true,
|
|
144
|
+
});
|
|
145
|
+
const capture = (chunk) => { if (logs.join('\n').length < 20_000) logs.push(String(chunk)); };
|
|
146
|
+
official.stdout.on('data', capture);
|
|
147
|
+
official.stderr.on('data', capture);
|
|
148
|
+
|
|
149
|
+
await waitForPageMarker(`${officialUrl}/`, '@ran-sh/dsh-crew-web-bridge');
|
|
150
|
+
const bridgeStatus = await (await waitFor(`${officialUrl}/_dsh/dsh-crew/bridge-status`)).json();
|
|
151
|
+
assert(bridgeStatus.mode === 'official-3080-isolated-3210', 'bridge status mode mismatch');
|
|
152
|
+
const proxiedRuntime = await (await waitFor(`${officialUrl}/_dsh/dsh-crew/runtime`, { timeout: 45_000 })).json();
|
|
153
|
+
assert(proxiedRuntime.runtime_version === candidateVersion, `proxied runtime version mismatch (${proxiedRuntime.runtime_version} != ${candidateVersion})`);
|
|
154
|
+
const directRuntime = await (await waitFor(`${crewUrl}/_dsh/dsh-crew/runtime`)).json();
|
|
155
|
+
assert(directRuntime.runtime_version === candidateVersion, `direct runtime version mismatch (${directRuntime.runtime_version} != ${candidateVersion})`);
|
|
156
|
+
const models = await (await waitFor(`${officialUrl}/_dsh/dsh-crew/models`)).json();
|
|
157
|
+
assert(Array.isArray(models.providers), 'proxied model catalog missing providers');
|
|
158
|
+
|
|
159
|
+
console.log(JSON.stringify({
|
|
160
|
+
ok: true,
|
|
161
|
+
official_ui: officialUrl,
|
|
162
|
+
isolated_backend: crewUrl,
|
|
163
|
+
runtime_version: proxiedRuntime.runtime_version,
|
|
164
|
+
provider_count: models.providers.length,
|
|
165
|
+
official_profile: profileRoot,
|
|
166
|
+
crew_home: harnessHome,
|
|
167
|
+
}));
|
|
168
|
+
} catch (error) {
|
|
169
|
+
console.error(`OFFICIAL_BRIDGE_E2E_FAIL=${error?.message ?? error}`);
|
|
170
|
+
console.error(logs.join('\n').slice(-8_000));
|
|
171
|
+
process.exitCode = 1;
|
|
172
|
+
} finally {
|
|
173
|
+
if (official && official.exitCode === null) official.kill();
|
|
174
|
+
stopOwnedProcesses();
|
|
175
|
+
await new Promise((resolveWait) => setTimeout(resolveWait, 1_500));
|
|
176
|
+
const resolvedSandbox = resolve(sandbox);
|
|
177
|
+
assert(resolvedSandbox.startsWith(resolve(tmpdir())), 'refusing to remove non-temp sandbox');
|
|
178
|
+
if (process.env.DSH_CREW_E2E_KEEP === '1') console.error(`OFFICIAL_BRIDGE_E2E_SANDBOX=${resolvedSandbox}`);
|
|
179
|
+
else rmSync(resolvedSandbox, { recursive: true, force: true, maxRetries: 10, retryDelay: 250 });
|
|
180
|
+
}
|