@ran-sh/dsh-crew 1.2.0-rc.6 → 1.3.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/.claude-plugin/plugin.json +1 -1
- package/docs/installation.md +30 -0
- package/package.json +49 -49
- package/scripts/verify-official-bridge-e2e.mjs +2 -1
- package/src/dsh-cohort.mjs +1 -1
- package/src/history/runner.mjs +2 -1
- package/src/history/runtime.mjs +2 -1
- package/src/runtime-identity.mjs +49 -7
- package/windows/start-dsh-crew.ps1 +270 -36
- package/worker.cordis.yml +9 -5
package/docs/installation.md
CHANGED
|
@@ -90,6 +90,36 @@ pnpm run build:client
|
|
|
90
90
|
npm pack --dry-run
|
|
91
91
|
```
|
|
92
92
|
|
|
93
|
+
## Harness CLI selection
|
|
94
|
+
|
|
95
|
+
The Crew launcher and the 3210 Hub boot from one Crew-managed Harness entry,
|
|
96
|
+
chosen in this order:
|
|
97
|
+
|
|
98
|
+
1. `DSH_CREW_DSH_CLI`, when set to an explicit `@deepseek-ai/dsh` entry.
|
|
99
|
+
2. The Crew-managed npm runtime at `<crew home>/runtime` (the normal install
|
|
100
|
+
path, written by `dsh-crew update`). It carries whatever cohort the installed
|
|
101
|
+
Crew release pinned; the launcher never substitutes a different version.
|
|
102
|
+
3. A Crew-managed source cohort, discovered through a
|
|
103
|
+
`<crew home>/runtime-source-<label>.json` sidecar. The sidecar's recorded
|
|
104
|
+
`version` must equal the checkout's own `apps/cli/package.json`, so a stale
|
|
105
|
+
sidecar can never pin a half-updated tree.
|
|
106
|
+
|
|
107
|
+
For the npm runtime the launcher uses the Crew home as `DSH_HOME`; a source
|
|
108
|
+
cohort is its own home, because its profiles, sessions and settings live inside
|
|
109
|
+
that tree. The launcher then derives `dsh_version` from the verified
|
|
110
|
+
`@deepseek-ai/dsh` manifest and requires the Hub to report that same version, so
|
|
111
|
+
a process left over from a previous cohort is never treated as healthy.
|
|
112
|
+
|
|
113
|
+
Only override `DSH_CREW_DSH_CLI` deliberately — it wins over the managed runtime.
|
|
114
|
+
Never point it at the official `~/.dsh` tree, which the Crew launcher treats as a
|
|
115
|
+
read-only boundary.
|
|
116
|
+
|
|
117
|
+
To build a source cohort instead of the npm runtime (for example to test an
|
|
118
|
+
unpublished tag), clone the tag into `<crew home>/runtime-source-<label>`, build
|
|
119
|
+
its CLI, and write the matching sidecar. On Windows a source install needs the
|
|
120
|
+
native toolchain for its `fs-ext` dependency; a failed native build is reported
|
|
121
|
+
as a runtime failure rather than silently falling back to another cohort.
|
|
122
|
+
|
|
93
123
|
## Rollback vs uninstall
|
|
94
124
|
|
|
95
125
|
```bash
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ran-sh/dsh-crew",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./src/hub/entry.mjs",
|
|
6
6
|
"bin": {
|
|
@@ -41,30 +41,30 @@
|
|
|
41
41
|
"./package.json": "./package.json"
|
|
42
42
|
},
|
|
43
43
|
"peerDependencies": {
|
|
44
|
-
"@deepseek-ai/dsh": "0.1.
|
|
45
|
-
"@deepseek-ai/dsh-agent": "0.1.
|
|
46
|
-
"@deepseek-ai/dsh-agent-loop": "0.1.
|
|
47
|
-
"@deepseek-ai/dsh-agent-presets": "0.1.
|
|
48
|
-
"@deepseek-ai/dsh-bash-local": "0.1.
|
|
49
|
-
"@deepseek-ai/dsh-client-locale": "0.1.
|
|
50
|
-
"@deepseek-ai/dsh-client-ui-renderer": "0.1.
|
|
51
|
-
"@deepseek-ai/dsh-client-ui-settings": "0.1.
|
|
52
|
-
"@deepseek-ai/dsh-compaction-basic": "0.1.
|
|
53
|
-
"@deepseek-ai/dsh-fs-local": "0.1.
|
|
54
|
-
"@deepseek-ai/dsh-fs-observation-policy": "0.1.
|
|
55
|
-
"@deepseek-ai/dsh-llm": "0.1.
|
|
56
|
-
"@deepseek-ai/dsh-llm-deepseek": "0.1.
|
|
57
|
-
"@deepseek-ai/dsh-sandbox-local": "0.1.
|
|
58
|
-
"@deepseek-ai/dsh-sandbox-policy": "0.1.
|
|
59
|
-
"@deepseek-ai/dsh-sdk-client": "0.1.
|
|
60
|
-
"@deepseek-ai/dsh-sdk-jsonrpc-server": "0.1.
|
|
61
|
-
"@deepseek-ai/dsh-sdk-minimal": "0.1.
|
|
62
|
-
"@deepseek-ai/dsh-session": "0.1.
|
|
63
|
-
"@deepseek-ai/dsh-session-persistence-jsonl": "0.1.
|
|
64
|
-
"@deepseek-ai/dsh-subprocess-local": "0.1.
|
|
65
|
-
"@deepseek-ai/dsh-token-meter": "0.1.
|
|
66
|
-
"@deepseek-ai/dsh-tool-fs": "0.1.
|
|
67
|
-
"@deepseek-ai/dsh-tool-todo": "0.1.
|
|
44
|
+
"@deepseek-ai/dsh": "0.1.5-rc.2",
|
|
45
|
+
"@deepseek-ai/dsh-agent": "0.1.5-rc.2",
|
|
46
|
+
"@deepseek-ai/dsh-agent-loop": "0.1.5-rc.2",
|
|
47
|
+
"@deepseek-ai/dsh-agent-presets": "0.1.5-rc.2",
|
|
48
|
+
"@deepseek-ai/dsh-bash-local": "0.1.5-rc.2",
|
|
49
|
+
"@deepseek-ai/dsh-client-locale": "0.1.5-rc.2",
|
|
50
|
+
"@deepseek-ai/dsh-client-ui-renderer": "0.1.5-rc.2",
|
|
51
|
+
"@deepseek-ai/dsh-client-ui-settings": "0.1.5-rc.2",
|
|
52
|
+
"@deepseek-ai/dsh-compaction-basic": "0.1.5-rc.2",
|
|
53
|
+
"@deepseek-ai/dsh-fs-local": "0.1.5-rc.2",
|
|
54
|
+
"@deepseek-ai/dsh-fs-observation-policy": "0.1.5-rc.2",
|
|
55
|
+
"@deepseek-ai/dsh-llm": "0.1.5-rc.2",
|
|
56
|
+
"@deepseek-ai/dsh-llm-deepseek": "0.1.5-rc.2",
|
|
57
|
+
"@deepseek-ai/dsh-sandbox-local": "0.1.5-rc.2",
|
|
58
|
+
"@deepseek-ai/dsh-sandbox-policy": "0.1.5-rc.2",
|
|
59
|
+
"@deepseek-ai/dsh-sdk-client": "0.1.5-rc.2",
|
|
60
|
+
"@deepseek-ai/dsh-sdk-jsonrpc-server": "0.1.5-rc.2",
|
|
61
|
+
"@deepseek-ai/dsh-sdk-minimal": "0.1.5-rc.2",
|
|
62
|
+
"@deepseek-ai/dsh-session": "0.1.5-rc.2",
|
|
63
|
+
"@deepseek-ai/dsh-session-persistence-jsonl": "0.1.5-rc.2",
|
|
64
|
+
"@deepseek-ai/dsh-subprocess-local": "0.1.5-rc.2",
|
|
65
|
+
"@deepseek-ai/dsh-token-meter": "0.1.5-rc.2",
|
|
66
|
+
"@deepseek-ai/dsh-tool-fs": "0.1.5-rc.2",
|
|
67
|
+
"@deepseek-ai/dsh-tool-todo": "0.1.5-rc.2",
|
|
68
68
|
"react": "^18.0.0",
|
|
69
69
|
"react-dom": "^18.0.0"
|
|
70
70
|
},
|
|
@@ -149,30 +149,30 @@
|
|
|
149
149
|
}
|
|
150
150
|
},
|
|
151
151
|
"devDependencies": {
|
|
152
|
-
"@deepseek-ai/dsh": "0.1.
|
|
153
|
-
"@deepseek-ai/dsh-agent": "0.1.
|
|
154
|
-
"@deepseek-ai/dsh-agent-loop": "0.1.
|
|
155
|
-
"@deepseek-ai/dsh-agent-presets": "0.1.
|
|
156
|
-
"@deepseek-ai/dsh-bash-local": "0.1.
|
|
157
|
-
"@deepseek-ai/dsh-client-locale": "0.1.
|
|
158
|
-
"@deepseek-ai/dsh-client-ui-renderer": "0.1.
|
|
159
|
-
"@deepseek-ai/dsh-client-ui-settings": "0.1.
|
|
160
|
-
"@deepseek-ai/dsh-compaction-basic": "0.1.
|
|
161
|
-
"@deepseek-ai/dsh-fs-local": "0.1.
|
|
162
|
-
"@deepseek-ai/dsh-fs-observation-policy": "0.1.
|
|
163
|
-
"@deepseek-ai/dsh-llm": "0.1.
|
|
164
|
-
"@deepseek-ai/dsh-llm-deepseek": "0.1.
|
|
165
|
-
"@deepseek-ai/dsh-sandbox-local": "0.1.
|
|
166
|
-
"@deepseek-ai/dsh-sandbox-policy": "0.1.
|
|
167
|
-
"@deepseek-ai/dsh-sdk-client": "0.1.
|
|
168
|
-
"@deepseek-ai/dsh-sdk-jsonrpc-server": "0.1.
|
|
169
|
-
"@deepseek-ai/dsh-sdk-minimal": "0.1.
|
|
170
|
-
"@deepseek-ai/dsh-session": "0.1.
|
|
171
|
-
"@deepseek-ai/dsh-session-persistence-jsonl": "0.1.
|
|
172
|
-
"@deepseek-ai/dsh-subprocess-local": "0.1.
|
|
173
|
-
"@deepseek-ai/dsh-token-meter": "0.1.
|
|
174
|
-
"@deepseek-ai/dsh-tool-fs": "0.1.
|
|
175
|
-
"@deepseek-ai/dsh-tool-todo": "0.1.
|
|
152
|
+
"@deepseek-ai/dsh": "0.1.5-rc.2",
|
|
153
|
+
"@deepseek-ai/dsh-agent": "0.1.5-rc.2",
|
|
154
|
+
"@deepseek-ai/dsh-agent-loop": "0.1.5-rc.2",
|
|
155
|
+
"@deepseek-ai/dsh-agent-presets": "0.1.5-rc.2",
|
|
156
|
+
"@deepseek-ai/dsh-bash-local": "0.1.5-rc.2",
|
|
157
|
+
"@deepseek-ai/dsh-client-locale": "0.1.5-rc.2",
|
|
158
|
+
"@deepseek-ai/dsh-client-ui-renderer": "0.1.5-rc.2",
|
|
159
|
+
"@deepseek-ai/dsh-client-ui-settings": "0.1.5-rc.2",
|
|
160
|
+
"@deepseek-ai/dsh-compaction-basic": "0.1.5-rc.2",
|
|
161
|
+
"@deepseek-ai/dsh-fs-local": "0.1.5-rc.2",
|
|
162
|
+
"@deepseek-ai/dsh-fs-observation-policy": "0.1.5-rc.2",
|
|
163
|
+
"@deepseek-ai/dsh-llm": "0.1.5-rc.2",
|
|
164
|
+
"@deepseek-ai/dsh-llm-deepseek": "0.1.5-rc.2",
|
|
165
|
+
"@deepseek-ai/dsh-sandbox-local": "0.1.5-rc.2",
|
|
166
|
+
"@deepseek-ai/dsh-sandbox-policy": "0.1.5-rc.2",
|
|
167
|
+
"@deepseek-ai/dsh-sdk-client": "0.1.5-rc.2",
|
|
168
|
+
"@deepseek-ai/dsh-sdk-jsonrpc-server": "0.1.5-rc.2",
|
|
169
|
+
"@deepseek-ai/dsh-sdk-minimal": "0.1.5-rc.2",
|
|
170
|
+
"@deepseek-ai/dsh-session": "0.1.5-rc.2",
|
|
171
|
+
"@deepseek-ai/dsh-session-persistence-jsonl": "0.1.5-rc.2",
|
|
172
|
+
"@deepseek-ai/dsh-subprocess-local": "0.1.5-rc.2",
|
|
173
|
+
"@deepseek-ai/dsh-token-meter": "0.1.5-rc.2",
|
|
174
|
+
"@deepseek-ai/dsh-tool-fs": "0.1.5-rc.2",
|
|
175
|
+
"@deepseek-ai/dsh-tool-todo": "0.1.5-rc.2",
|
|
176
176
|
"@types/react": "^18.3.0",
|
|
177
177
|
"react": "^18.3.1",
|
|
178
178
|
"react-dom": "^18.3.1",
|
|
@@ -17,6 +17,7 @@ import { createServer } from 'node:net';
|
|
|
17
17
|
import { dirname, join, resolve } from 'node:path';
|
|
18
18
|
import { fileURLToPath } from 'node:url';
|
|
19
19
|
import { npxInstall, npxIntegrate } from '../src/install/npx-lifecycle.mjs';
|
|
20
|
+
import { TARGET_DSH_VERSION } from '../src/dsh-cohort.mjs';
|
|
20
21
|
|
|
21
22
|
// RETIRED as a release gate: the legacy 3080 -> 3210 bridge is retired and
|
|
22
23
|
// npxIntegrate()/npxDetach() always return OFFICIAL_WEB_PROFILE_READ_ONLY, so
|
|
@@ -139,7 +140,7 @@ try {
|
|
|
139
140
|
const target = join(harnessHome, 'runtime');
|
|
140
141
|
mkdirSync(dirname(target), { recursive: true });
|
|
141
142
|
symlinkSync(realpathSync(runtimeRoot), target, process.platform === 'win32' ? 'junction' : 'dir');
|
|
142
|
-
return { ok: true, version:
|
|
143
|
+
return { ok: true, version: TARGET_DSH_VERSION };
|
|
143
144
|
},
|
|
144
145
|
});
|
|
145
146
|
assert(installed.ok, `disposable install failed: ${logs.join(' | ')}`);
|
package/src/dsh-cohort.mjs
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// resolve to this exact version. All other modules re-export from here so a
|
|
5
5
|
// cohort bump touches exactly one file (plus package.json's 48 DSH pins).
|
|
6
6
|
export const DSH_CLI_PACKAGE = '@deepseek-ai/dsh';
|
|
7
|
-
export const TARGET_DSH_VERSION = '0.1.
|
|
7
|
+
export const TARGET_DSH_VERSION = '0.1.5-rc.2';
|
|
8
8
|
export const TARGET_DSH_SPEC = `${DSH_CLI_PACKAGE}@${TARGET_DSH_VERSION}`;
|
|
9
9
|
|
|
10
10
|
// Retained-cohort support: when a release's manifest pins an older DSH
|
package/src/history/runner.mjs
CHANGED
|
@@ -6,6 +6,7 @@ import { acquireUpdateLock, releaseUpdateLock, createCrewSupervisor } from '../i
|
|
|
6
6
|
import { readMaintenanceSession } from '../supervisor/restart-request.mjs';
|
|
7
7
|
import { runHistoryOperation } from './operation.mjs';
|
|
8
8
|
import { readHistoryState } from './state.mjs';
|
|
9
|
+
import { TARGET_DSH_VERSION } from '../dsh-cohort.mjs';
|
|
9
10
|
|
|
10
11
|
async function portFree() {
|
|
11
12
|
return new Promise(resolve => {
|
|
@@ -40,7 +41,7 @@ export async function runProductionHistory({ id, recover = false } = {}) {
|
|
|
40
41
|
const runtime = await response.json();
|
|
41
42
|
return response.ok && runtime.ok === true && runtime.service === 'dsh-crew-hub' && runtime.profile === 'dsh-crew'
|
|
42
43
|
&& runtime.execution_plane === 'hub-3210' && runtime.listen_port === 3210 && runtime.protocol_version === 1
|
|
43
|
-
&& runtime.dsh_version ===
|
|
44
|
+
&& runtime.dsh_version === TARGET_DSH_VERSION && typeof runtime.runtime_id === 'string' && runtime.runtime_id !== s.runtimeId;
|
|
44
45
|
} catch { return false; }
|
|
45
46
|
},
|
|
46
47
|
});
|
package/src/history/runtime.mjs
CHANGED
|
@@ -3,13 +3,14 @@ import { join, resolve } from 'node:path';
|
|
|
3
3
|
import { fileURLToPath } from 'node:url';
|
|
4
4
|
import { spawn } from 'node:child_process';
|
|
5
5
|
import { getHubRuntimeIdentity } from '../runtime-identity.mjs';
|
|
6
|
+
import { TARGET_DSH_VERSION } from '../dsh-cohort.mjs';
|
|
6
7
|
import { createHistoryService } from './service.mjs';
|
|
7
8
|
import { registerHistoryHttp } from './http.mjs';
|
|
8
9
|
|
|
9
10
|
export function registerRuntimeHistory(ctx) {
|
|
10
11
|
const crewRoot = join(homedir(), '.config', 'dsh-crew');
|
|
11
12
|
const runtime = getHubRuntimeIdentity();
|
|
12
|
-
const owned = process.platform === 'win32' && runtime.dsh_version ===
|
|
13
|
+
const owned = process.platform === 'win32' && runtime.dsh_version === TARGET_DSH_VERSION
|
|
13
14
|
&& resolve(process.env.DSH_HOME ?? '').toLowerCase() === resolve(join(crewRoot, 'harness')).toLowerCase();
|
|
14
15
|
if (!owned) return () => {};
|
|
15
16
|
return ctx.inject(['webServer', 'sessionPersistence', 'agents'], host => {
|
package/src/runtime-identity.mjs
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// - HUB_PROTOCOL_VERSION changes only when Hub <-> MCP wire semantics become
|
|
6
6
|
// incompatible.
|
|
7
7
|
//
|
|
8
|
-
// The Harness/DSH cohort version (e.g. 0.1.
|
|
8
|
+
// The Harness/DSH cohort version (e.g. 0.1.5-rc.2) is a THIRD, independent
|
|
9
9
|
// domain: it identifies the installed @deepseek-ai/dsh package generation,
|
|
10
10
|
// never the Crew release. Verifiers must compare the cohort against
|
|
11
11
|
// dsh_version, never against runtime_version.
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
import { randomUUID } from 'node:crypto';
|
|
17
17
|
import { existsSync, readFileSync } from 'node:fs';
|
|
18
18
|
import { homedir } from 'node:os';
|
|
19
|
-
import { join } from 'node:path';
|
|
19
|
+
import { dirname, extname, join, resolve } from 'node:path';
|
|
20
20
|
import {
|
|
21
21
|
PRODUCTION_EXECUTION_PLANE,
|
|
22
22
|
PRODUCTION_PROFILE,
|
|
@@ -36,7 +36,7 @@ export {
|
|
|
36
36
|
// included in the identity contract.
|
|
37
37
|
const RUNTIME_ID = randomUUID();
|
|
38
38
|
|
|
39
|
-
export const RUNTIME_VERSION = '1.
|
|
39
|
+
export const RUNTIME_VERSION = '1.3.0';
|
|
40
40
|
export const HUB_PROTOCOL_VERSION = 1;
|
|
41
41
|
|
|
42
42
|
export const HUB_CAPABILITIES = Object.freeze([
|
|
@@ -92,6 +92,43 @@ function normalizedCapabilities(value) {
|
|
|
92
92
|
return [...new Set(value.filter((item) => typeof item === 'string' && item.trim()).map((item) => item.trim()))];
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
+
function readVersionManifest(file) {
|
|
96
|
+
try {
|
|
97
|
+
const parsed = JSON.parse(readFileSync(file, 'utf8'));
|
|
98
|
+
if (parsed?.name !== '@deepseek-ai/dsh') return null;
|
|
99
|
+
return typeof parsed.version === 'string' && parsed.version.trim() ? parsed.version.trim() : null;
|
|
100
|
+
} catch { return null; }
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Resolve the @deepseek-ai/dsh manifest belonging to an explicit CLI entry.
|
|
105
|
+
*
|
|
106
|
+
* This intentionally accepts only a package manifest whose name is the
|
|
107
|
+
* official Harness package. It lets a Crew-owned source checkout (for example
|
|
108
|
+
* the GitHub-only dsh-v0.1.3-alpha.1 tag) report its real cohort without ever
|
|
109
|
+
* reading the official ~/.dsh profile or trusting an arbitrary package.json.
|
|
110
|
+
*/
|
|
111
|
+
function readExplicitCliVersion(entry) {
|
|
112
|
+
if (typeof entry !== 'string' || !entry.trim()) return null;
|
|
113
|
+
let cursor;
|
|
114
|
+
try { cursor = resolve(entry); } catch { return null; }
|
|
115
|
+
if (extname(cursor).toLowerCase() !== '.js') {
|
|
116
|
+
// A Windows shim normally lives at <root>/node_modules/.bin/dsh.cmd.
|
|
117
|
+
// Starting at its parent still finds the sibling @deepseek-ai/dsh tree.
|
|
118
|
+
cursor = dirname(cursor);
|
|
119
|
+
}
|
|
120
|
+
for (let depth = 0; depth < 12; depth += 1) {
|
|
121
|
+
const direct = readVersionManifest(join(cursor, 'package.json'));
|
|
122
|
+
if (direct) return direct;
|
|
123
|
+
const nested = readVersionManifest(join(cursor, 'node_modules', '@deepseek-ai', 'dsh', 'package.json'));
|
|
124
|
+
if (nested) return nested;
|
|
125
|
+
const parent = dirname(cursor);
|
|
126
|
+
if (parent === cursor) break;
|
|
127
|
+
cursor = parent;
|
|
128
|
+
}
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
|
|
95
132
|
export function getHubRuntimeIdentity() {
|
|
96
133
|
return {
|
|
97
134
|
service: 'dsh-crew-hub',
|
|
@@ -109,10 +146,15 @@ export function getHubRuntimeIdentity() {
|
|
|
109
146
|
}
|
|
110
147
|
|
|
111
148
|
// Installed Harness cohort version (the @deepseek-ai/dsh package under the
|
|
112
|
-
// Crew-owned DSH home
|
|
113
|
-
// "unknown cohort", never as a match.
|
|
114
|
-
// verifiers may compare against TARGET_DSH_VERSION.
|
|
115
|
-
export function readCrewDshVersion({ home = homedir() } = {}) {
|
|
149
|
+
// Crew-owned DSH home, or an explicit Crew-owned CLI source checkout). Null
|
|
150
|
+
// when unreadable: callers treat null as "unknown cohort", never as a match.
|
|
151
|
+
// This is the ONLY field cohort verifiers may compare against TARGET_DSH_VERSION.
|
|
152
|
+
export function readCrewDshVersion({ home = homedir(), env = process.env } = {}) {
|
|
153
|
+
// Only the Crew-scoped override is trusted here. A generic DSH_CLI may
|
|
154
|
+
// point at the official web profile and must never become Crew provenance.
|
|
155
|
+
const explicit = env?.DSH_CREW_DSH_CLI;
|
|
156
|
+
const explicitVersion = readExplicitCliVersion(explicit);
|
|
157
|
+
if (explicitVersion) return explicitVersion;
|
|
116
158
|
try {
|
|
117
159
|
const file = join(home, '.config', 'dsh-crew', 'harness', 'runtime', 'node_modules', '@deepseek-ai', 'dsh', 'package.json');
|
|
118
160
|
if (!existsSync(file)) return null;
|
|
@@ -9,7 +9,95 @@ Set-StrictMode -Version Latest
|
|
|
9
9
|
$ErrorActionPreference = 'Stop'
|
|
10
10
|
|
|
11
11
|
$crewHome = Join-Path $env:USERPROFILE '.config\dsh-crew\harness'
|
|
12
|
-
$
|
|
12
|
+
$configuredDshCli = [string] $env:DSH_CREW_DSH_CLI
|
|
13
|
+
if ($env:DSH_CREW_LAUNCHER_TEST_IMPORT -eq '1') { $configuredDshCli = '' }
|
|
14
|
+
if (-not $configuredDshCli.Trim() -and $env:DSH_CREW_LAUNCHER_TEST_IMPORT -ne '1') {
|
|
15
|
+
# Crew-managed npm runtime: the normal install path, written by dsh-crew
|
|
16
|
+
# update and carrying whatever cohort that release pinned. An explicit
|
|
17
|
+
# DSH_CREW_DSH_CLI always wins so an operator can still pin another entry.
|
|
18
|
+
$runtimeEntry = Join-Path $crewHome 'runtime\node_modules\@deepseek-ai\dsh\lib\bin.js'
|
|
19
|
+
if (Test-Path -LiteralPath $runtimeEntry -PathType Leaf) {
|
|
20
|
+
$configuredDshCli = $runtimeEntry
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
if (-not $configuredDshCli.Trim() -and $env:DSH_CREW_LAUNCHER_TEST_IMPORT -ne '1') {
|
|
24
|
+
# Crew-managed source cohort: a runtime-source-<label>.json sidecar names the
|
|
25
|
+
# CLI entry. The sidecar's recorded version must equal the checkout's own
|
|
26
|
+
# manifest, so a stale sidecar can never pin a half-updated tree.
|
|
27
|
+
foreach ($sidecar in @(Get-ChildItem -LiteralPath $crewHome -Filter 'runtime-source-*.json' -File -ErrorAction SilentlyContinue | Sort-Object Name)) {
|
|
28
|
+
try {
|
|
29
|
+
$metadata = Get-Content -LiteralPath $sidecar.FullName -Raw | ConvertFrom-Json
|
|
30
|
+
if ($metadata.managed_by -ne 'dsh-crew') { continue }
|
|
31
|
+
$candidate = [string] $metadata.cli_entry
|
|
32
|
+
if ([string]::IsNullOrWhiteSpace($candidate)) { continue }
|
|
33
|
+
$sourceRoot = [IO.Path]::GetFullPath((Join-Path $crewHome ([IO.Path]::GetFileNameWithoutExtension($sidecar.Name))))
|
|
34
|
+
$candidateFull = [IO.Path]::GetFullPath($candidate)
|
|
35
|
+
$sourcePrefix = $sourceRoot.TrimEnd([IO.Path]::DirectorySeparatorChar) + [IO.Path]::DirectorySeparatorChar
|
|
36
|
+
$sourceManifest = Join-Path $sourceRoot 'apps\cli\package.json'
|
|
37
|
+
$sourceVersion = if (Test-Path -LiteralPath $sourceManifest -PathType Leaf) {
|
|
38
|
+
[string] (Get-Content -LiteralPath $sourceManifest -Raw | ConvertFrom-Json).version
|
|
39
|
+
} else { $null }
|
|
40
|
+
$candidateValid = $sourceVersion -and $sourceVersion -eq [string] $metadata.version `
|
|
41
|
+
-and $candidateFull.StartsWith($sourcePrefix, [StringComparison]::OrdinalIgnoreCase) `
|
|
42
|
+
-and (Test-Path -LiteralPath $candidateFull -PathType Leaf)
|
|
43
|
+
if ($candidateValid) {
|
|
44
|
+
$configuredDshCli = $candidateFull
|
|
45
|
+
break
|
|
46
|
+
}
|
|
47
|
+
} catch { }
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
$dshCli = if ($configuredDshCli.Trim()) {
|
|
51
|
+
try { [IO.Path]::GetFullPath($configuredDshCli) } catch { $configuredDshCli }
|
|
52
|
+
} else {
|
|
53
|
+
Join-Path $crewHome 'runtime\node_modules\.bin\dsh.cmd'
|
|
54
|
+
}
|
|
55
|
+
if ($configuredDshCli.Trim()) { $env:DSH_CREW_DSH_CLI = $dshCli }
|
|
56
|
+
# DSH_HOME that owns the selected CLI. The managed npm runtime shares the Crew
|
|
57
|
+
# home; a Crew-managed source cohort is its own home (its profiles, sessions and
|
|
58
|
+
# settings live inside that tree). Derived from the selected entry so an explicit
|
|
59
|
+
# DSH_CREW_DSH_CLI override resolves correctly too.
|
|
60
|
+
$dshHome = $crewHome
|
|
61
|
+
$sourceRootMatch = [regex]::Match($dshCli, '^(?<root>.+?[\\/]runtime-source-[^\\/]+)[\\/]')
|
|
62
|
+
if ($sourceRootMatch.Success) {
|
|
63
|
+
$candidateRoot = $sourceRootMatch.Groups['root'].Value
|
|
64
|
+
if (Test-Path -LiteralPath (Join-Path $candidateRoot 'profiles') -PathType Container) { $dshHome = $candidateRoot }
|
|
65
|
+
}
|
|
66
|
+
$dshCliIsNodeEntry = [IO.Path]::GetExtension($dshCli).ToLowerInvariant() -eq '.js'
|
|
67
|
+
$dshCommand = if ($dshCliIsNodeEntry) {
|
|
68
|
+
(Get-Command node.exe -CommandType Application -ErrorAction Stop | Select-Object -First 1).Source
|
|
69
|
+
} else { $dshCli }
|
|
70
|
+
|
|
71
|
+
function Get-CrewDshManifest {
|
|
72
|
+
param([string] $Entry)
|
|
73
|
+
if (-not $Entry) { return $null }
|
|
74
|
+
try { $cursor = [IO.Path]::GetFullPath($Entry) } catch { return $null }
|
|
75
|
+
if ([IO.Path]::GetExtension($cursor).ToLowerInvariant() -eq '.js') {
|
|
76
|
+
$cursor = Split-Path -Parent $cursor
|
|
77
|
+
} else {
|
|
78
|
+
$cursor = Split-Path -Parent $cursor
|
|
79
|
+
}
|
|
80
|
+
for ($depth = 0; $depth -lt 12 -and $cursor; $depth += 1) {
|
|
81
|
+
$candidates = @(
|
|
82
|
+
(Join-Path $cursor 'package.json'),
|
|
83
|
+
(Join-Path $cursor 'node_modules\@deepseek-ai\dsh\package.json')
|
|
84
|
+
)
|
|
85
|
+
foreach ($candidate in $candidates) {
|
|
86
|
+
if (-not (Test-Path -LiteralPath $candidate -PathType Leaf)) { continue }
|
|
87
|
+
try {
|
|
88
|
+
$manifest = Get-Content -LiteralPath $candidate -Raw | ConvertFrom-Json
|
|
89
|
+
$knownOfficial = -not $configuredDshCli.Trim() -or $manifest.name -eq '@deepseek-ai/dsh'
|
|
90
|
+
if ($knownOfficial -and [string] $manifest.version) { return $manifest }
|
|
91
|
+
} catch { }
|
|
92
|
+
}
|
|
93
|
+
$parent = Split-Path -Parent $cursor
|
|
94
|
+
if ($parent -eq $cursor) { break }
|
|
95
|
+
$cursor = $parent
|
|
96
|
+
}
|
|
97
|
+
return $null
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
$dshManifest = Get-CrewDshManifest -Entry $dshCli
|
|
13
101
|
$logRoot = if ($env:TEMP) { $env:TEMP } else { [System.IO.Path]::GetTempPath() }
|
|
14
102
|
$launcherLog = Join-Path $logRoot 'dsh-crew-launcher.log'
|
|
15
103
|
$startedAt = Get-Date
|
|
@@ -25,7 +113,7 @@ try {
|
|
|
25
113
|
} finally { $hashStream.Dispose() }
|
|
26
114
|
} catch { $launcherHelperHash = $null }
|
|
27
115
|
$services = @(
|
|
28
|
-
[pscustomobject]@{ Name = 'Crew backend'; Profile = 'dsh-crew'; Home = $
|
|
116
|
+
[pscustomobject]@{ Name = 'Crew backend'; Profile = 'dsh-crew'; Home = $dshHome; Port = 3210; Url = 'http://127.0.0.1:3210'; CrewOwned = $true; State = 'pending'; Process = $null; RootPid = $null; RootStartedAtUtcTicks = $null; ListenerPid = $null; ListenerStartedAtUtcTicks = $null; ConsecutiveFailures = 0; LastError = $null }
|
|
29
117
|
)
|
|
30
118
|
|
|
31
119
|
function Write-LaunchLog {
|
|
@@ -58,7 +146,7 @@ function Resolve-OfficialHarnessCommand {
|
|
|
58
146
|
}
|
|
59
147
|
|
|
60
148
|
function Test-OfficialHarnessListener {
|
|
61
|
-
param([int] $OwnerPid, [pscustomobject] $Official)
|
|
149
|
+
param([int] $OwnerPid, [pscustomobject] $Official, [string] $Profile = 'web')
|
|
62
150
|
if ($OwnerPid -le 0) { return $false }
|
|
63
151
|
try {
|
|
64
152
|
$owner = Get-CimInstance Win32_Process -Filter ('ProcessId={0}' -f $OwnerPid) -ErrorAction Stop
|
|
@@ -66,11 +154,154 @@ function Test-OfficialHarnessListener {
|
|
|
66
154
|
$line = ([string] $owner.CommandLine).Replace('/', '\')
|
|
67
155
|
$entry = [regex]::Escape($Official.Entry.Replace('/', '\'))
|
|
68
156
|
$node = [regex]::Escape($Official.NodePath.Replace('/', '\'))
|
|
69
|
-
$
|
|
157
|
+
$profileToken = [regex]::Escape($Profile)
|
|
158
|
+
$pattern = '^\s*(?:"?' + $node + '"?|"?node(?:\.exe)?"?)\s+"?' + $entry + '"?\s+(?:"?' + $profileToken + '"?|--profile\s+"?' + $profileToken + '"?)(?:\s|$)'
|
|
70
159
|
return $line -match $pattern
|
|
71
160
|
} catch { return $false }
|
|
72
161
|
}
|
|
73
162
|
|
|
163
|
+
function Test-CrewDshCliPreflight {
|
|
164
|
+
if ($null -eq $dshManifest -or [string]::IsNullOrWhiteSpace([string] $dshManifest.version)) {
|
|
165
|
+
throw "The DSH CLI at $dshCli is not a verified @deepseek-ai/dsh package entry."
|
|
166
|
+
}
|
|
167
|
+
if ($dshCliIsNodeEntry -and -not (Test-Path -LiteralPath (Join-Path (Join-Path $dshHome 'profiles') 'web\package.json') -PathType Leaf)) {
|
|
168
|
+
throw ('The Harness web profile is missing under {0}. Run: dsh-crew update' -f $dshHome)
|
|
169
|
+
}
|
|
170
|
+
$versionArgs = if ($dshCliIsNodeEntry) { @($dshCli, '--version') } else { @('--version') }
|
|
171
|
+
$reported = try { (& $dshCommand @versionArgs 2>&1 | Out-String).Trim() } catch { '' }
|
|
172
|
+
if ($LASTEXITCODE -ne 0 -or $reported -ne [string] $dshManifest.version) {
|
|
173
|
+
throw ('DSH CLI preflight failed: manifest={0}, reported={1}' -f $dshManifest.version, ($reported -replace '\s+', ' '))
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function Get-CrewWebSessionUrl {
|
|
178
|
+
param([string] $OutputLog = $null)
|
|
179
|
+
$logs = if ($OutputLog) {
|
|
180
|
+
@(Get-Item -LiteralPath $OutputLog -ErrorAction SilentlyContinue)
|
|
181
|
+
} else {
|
|
182
|
+
@(Get-ChildItem -LiteralPath $logRoot -Filter 'dsh-crew-web-*.out.log' -File -ErrorAction SilentlyContinue | Sort-Object LastWriteTime -Descending)
|
|
183
|
+
}
|
|
184
|
+
foreach ($log in $logs) {
|
|
185
|
+
try {
|
|
186
|
+
$text = Get-Content -LiteralPath $log.FullName -Raw -ErrorAction Stop
|
|
187
|
+
$match = [regex]::Match($text, 'https?://127\.0\.0\.1:3080/\?token=[^\s\r\n]+')
|
|
188
|
+
if ($match.Success) { return $match.Value }
|
|
189
|
+
} catch { }
|
|
190
|
+
}
|
|
191
|
+
return $null
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function Open-CrewBrowserUrl {
|
|
195
|
+
param([Parameter(Mandatory = $true)] [string] $Url)
|
|
196
|
+
$edge = Get-Command msedge.exe -CommandType Application -ErrorAction SilentlyContinue | Select-Object -First 1
|
|
197
|
+
if (-not $edge) {
|
|
198
|
+
$edgeCandidates = @(
|
|
199
|
+
@(
|
|
200
|
+
(Join-Path ${env:ProgramFiles(x86)} 'Microsoft\Edge\Application\msedge.exe'),
|
|
201
|
+
(Join-Path $env:ProgramFiles 'Microsoft\Edge\Application\msedge.exe'),
|
|
202
|
+
(Join-Path $env:LOCALAPPDATA 'Microsoft\Edge\Application\msedge.exe')
|
|
203
|
+
) | Where-Object { $_ -and (Test-Path -LiteralPath $_ -PathType Leaf) }
|
|
204
|
+
)
|
|
205
|
+
if ($edgeCandidates.Count -gt 0) { $edge = [pscustomobject]@{ Source = $edgeCandidates[0] } }
|
|
206
|
+
}
|
|
207
|
+
if ($edge) {
|
|
208
|
+
Start-Process -FilePath $edge.Source -ArgumentList @('--new-window', $Url) | Out-Null
|
|
209
|
+
return
|
|
210
|
+
}
|
|
211
|
+
Start-Process $Url | Out-Null
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function Test-CrewWebSessionUrl {
|
|
215
|
+
param([string] $Url)
|
|
216
|
+
if (-not $Url) { return $false }
|
|
217
|
+
try {
|
|
218
|
+
# The token endpoint intentionally answers with a 303 + Set-Cookie and
|
|
219
|
+
# redirects to `/`; do not follow it here or PowerShell loses the cookie
|
|
220
|
+
# and falsely reports the authenticated page as 404.
|
|
221
|
+
$response = Invoke-WebRequest -UseBasicParsing -MaximumRedirection 0 -Uri $Url -TimeoutSec 3
|
|
222
|
+
return [int] $response.StatusCode -ge 200 -and [int] $response.StatusCode -lt 400
|
|
223
|
+
} catch {
|
|
224
|
+
$status = if ($_.Exception.Response) { [int] $_.Exception.Response.StatusCode } else { 0 }
|
|
225
|
+
return $status -in @(301, 302, 303, 307, 308)
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function Open-CrewManagedFrontend {
|
|
230
|
+
param([int] $TimeoutSeconds = 90)
|
|
231
|
+
# The 3080 frontend boots from the same Crew-managed Harness entry as 3210,
|
|
232
|
+
# against that entry's own DSH_HOME (npm runtime -> Crew home, source cohort
|
|
233
|
+
# -> its own tree). No official ~/.dsh state is read or written here.
|
|
234
|
+
$managedHome = $dshHome
|
|
235
|
+
$managedProfile = 'web'
|
|
236
|
+
$profileManifest = Join-Path (Join-Path $managedHome 'profiles') ('{0}\package.json' -f $managedProfile)
|
|
237
|
+
if (-not $dshCliIsNodeEntry -or -not (Test-Path -LiteralPath $profileManifest -PathType Leaf)) {
|
|
238
|
+
return $false
|
|
239
|
+
}
|
|
240
|
+
$official = [pscustomobject]@{ NodePath = $dshCommand; Entry = $dshCli; Profile = $managedProfile }
|
|
241
|
+
$frontend = Get-OfficialFrontendOverlay
|
|
242
|
+
$mutex = New-Object System.Threading.Mutex($false, 'Local\DSHCrewOfficialFrontendLauncher')
|
|
243
|
+
$locked = $false
|
|
244
|
+
try {
|
|
245
|
+
try { $locked = $mutex.WaitOne(30000) } catch [System.Threading.AbandonedMutexException] { $locked = $true }
|
|
246
|
+
if (-not $locked) { throw 'Another desktop launch is still starting the Crew-managed Harness frontend.' }
|
|
247
|
+
$port = Get-PortState -Port 3080
|
|
248
|
+
if ($port.State -eq 'unknown') { throw $port.Error }
|
|
249
|
+
if ($port.State -eq 'occupied') {
|
|
250
|
+
if (-not (Test-OfficialHarnessListener -OwnerPid $port.Pid -Official $official -Profile $managedProfile)) {
|
|
251
|
+
throw 'Port 3080 is occupied by a non-Crew-managed process; it was left untouched.'
|
|
252
|
+
}
|
|
253
|
+
if (-not (Test-OfficialWebReady)) { throw 'The Crew-managed 3080 frontend is not ready; its process was left running.' }
|
|
254
|
+
$existingUrl = Get-CrewWebSessionUrl
|
|
255
|
+
if ($existingUrl -and (Test-CrewWebSessionUrl -Url $existingUrl)) {
|
|
256
|
+
Open-CrewBrowserUrl -Url $existingUrl
|
|
257
|
+
Write-LaunchLog 'Opened the existing Crew-managed Harness frontend on 3080 with its session URL.'
|
|
258
|
+
return $true
|
|
259
|
+
}
|
|
260
|
+
Write-LaunchLog 'The existing Crew-managed 3080 session URL is stale; restarting the verified listener.' 'WARN'
|
|
261
|
+
Stop-Process -Id $port.Pid -Force -ErrorAction Stop
|
|
262
|
+
$freeDeadline = (Get-Date).AddSeconds(5)
|
|
263
|
+
do { Start-Sleep -Milliseconds 250; $port = Get-PortState -Port 3080 } while ($port.State -ne 'free' -and (Get-Date) -lt $freeDeadline)
|
|
264
|
+
if ($port.State -ne 'free') { throw 'The stale Crew-managed 3080 listener did not release the port.' }
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
$stamp = '{0}-{1}' -f (Get-Date -Format 'yyyyMMdd-HHmmssfff'), $PID
|
|
268
|
+
$stdout = Join-Path $logRoot ('dsh-crew-web-{0}.out.log' -f $stamp)
|
|
269
|
+
$stderr = Join-Path $logRoot ('dsh-crew-web-{0}.err.log' -f $stamp)
|
|
270
|
+
$previousHome = $env:DSH_HOME
|
|
271
|
+
$previousCli = $env:DSH_CREW_DSH_CLI
|
|
272
|
+
try {
|
|
273
|
+
$env:DSH_HOME = $managedHome
|
|
274
|
+
$env:DSH_CREW_DSH_CLI = $dshCli
|
|
275
|
+
$arguments = @($dshCli, '--profile', $managedProfile, '--patch', ('"{0}"' -f $frontend.Path), '--host', '127.0.0.1', '--port', '3080')
|
|
276
|
+
$process = Start-Process -FilePath $dshCommand -ArgumentList $arguments -WindowStyle Hidden -PassThru `
|
|
277
|
+
-RedirectStandardOutput $stdout -RedirectStandardError $stderr
|
|
278
|
+
} finally {
|
|
279
|
+
$env:DSH_HOME = $previousHome
|
|
280
|
+
$env:DSH_CREW_DSH_CLI = $previousCli
|
|
281
|
+
}
|
|
282
|
+
Write-LaunchLog ('Started Crew-managed Harness on 3080; PID={0}. Logs: {1}, {2}' -f $process.Id, $stdout, $stderr)
|
|
283
|
+
$deadline = (Get-Date).AddSeconds($TimeoutSeconds)
|
|
284
|
+
do {
|
|
285
|
+
$port = Get-PortState -Port 3080
|
|
286
|
+
if ($port.State -eq 'occupied') {
|
|
287
|
+
if (-not (Test-OfficialHarnessListener -OwnerPid $port.Pid -Official $official -Profile $managedProfile)) {
|
|
288
|
+
throw 'Port 3080 became occupied by an unverified process; it was left untouched.'
|
|
289
|
+
}
|
|
290
|
+
if (Test-OfficialWebReady) {
|
|
291
|
+
$startedUrl = Get-CrewWebSessionUrl -OutputLog $stdout
|
|
292
|
+
if ($startedUrl) { Open-CrewBrowserUrl -Url $startedUrl; return $true }
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
if ($process.HasExited) { throw ('Crew-managed Harness exited before readiness. Diagnostic log: {0}' -f $stderr) }
|
|
296
|
+
Start-Sleep -Milliseconds 500
|
|
297
|
+
} while ((Get-Date) -lt $deadline)
|
|
298
|
+
throw ('The Crew-managed Harness did not become ready within {0}s; inspect {1}.' -f $TimeoutSeconds, $stderr)
|
|
299
|
+
} finally {
|
|
300
|
+
if ($locked) { $mutex.ReleaseMutex() }
|
|
301
|
+
$mutex.Dispose()
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
74
305
|
function Test-OfficialWebReady {
|
|
75
306
|
try {
|
|
76
307
|
$response = Invoke-WebRequest -UseBasicParsing -Uri 'http://127.0.0.1:3080/' -TimeoutSec 2
|
|
@@ -82,28 +313,28 @@ function Test-OfficialWebReady {
|
|
|
82
313
|
}
|
|
83
314
|
}
|
|
84
315
|
|
|
85
|
-
function Get-OfficialFrontendOverlay {
|
|
86
|
-
$frontendRoot = Join-Path $env:USERPROFILE '.config\dsh-crew\frontend'
|
|
87
|
-
$path = Join-Path $frontendRoot 'official-web.patch.json'
|
|
88
|
-
if (-not (Test-Path -LiteralPath $path -PathType Leaf)) { throw 'Crew frontend overlay is missing. Run dsh-crew update with the current GitHub candidate.' }
|
|
89
|
-
# Windows PowerShell 5.1 keeps a JSON root array as one pipeline object;
|
|
90
|
-
# direct `$patch[0].insert[0]` therefore resolves `insert` as IList.Insert
|
|
91
|
-
# instead of indexing the JSON property. Flatten both PowerShell generations
|
|
92
|
-
# explicitly and read properties through PSObject so StrictMode reports a
|
|
93
|
-
# useful validation error instead of "property id cannot be found".
|
|
94
|
-
$parsed = Get-Content -LiteralPath $path -Raw | ConvertFrom-Json
|
|
95
|
-
$patch = @($parsed | ForEach-Object { $_ })
|
|
96
|
-
$root = if ($patch.Count -eq 1) { $patch[0] } else { $null }
|
|
97
|
-
$insertProperty = if ($null -ne $root) { $root.PSObject.Properties['insert'] } else { $null }
|
|
98
|
-
$insertValue = if ($null -ne $insertProperty) { $insertProperty.Value } else { $null }
|
|
99
|
-
$inserts = @($insertValue | ForEach-Object { $_ })
|
|
100
|
-
$bridge = if ($inserts.Count -eq 1) { $inserts[0] } else { $null }
|
|
101
|
-
$idProperty = if ($null -ne $bridge) { $bridge.PSObject.Properties['id'] } else { $null }
|
|
102
|
-
$nameProperty = if ($null -ne $bridge) { $bridge.PSObject.Properties['name'] } else { $null }
|
|
103
|
-
if ($patch.Count -ne 1 -or $inserts.Count -ne 1 -or $null -eq $idProperty -or [string] $idProperty.Value -ne 'dsh-crew-official-web-bridge' -or $null -eq $nameProperty) {
|
|
104
|
-
throw 'Crew frontend overlay has an unexpected structure.'
|
|
105
|
-
}
|
|
106
|
-
$uri = [Uri] ([string] $nameProperty.Value)
|
|
316
|
+
function Get-OfficialFrontendOverlay {
|
|
317
|
+
$frontendRoot = Join-Path $env:USERPROFILE '.config\dsh-crew\frontend'
|
|
318
|
+
$path = Join-Path $frontendRoot 'official-web.patch.json'
|
|
319
|
+
if (-not (Test-Path -LiteralPath $path -PathType Leaf)) { throw 'Crew frontend overlay is missing. Run dsh-crew update with the current GitHub candidate.' }
|
|
320
|
+
# Windows PowerShell 5.1 keeps a JSON root array as one pipeline object;
|
|
321
|
+
# direct `$patch[0].insert[0]` therefore resolves `insert` as IList.Insert
|
|
322
|
+
# instead of indexing the JSON property. Flatten both PowerShell generations
|
|
323
|
+
# explicitly and read properties through PSObject so StrictMode reports a
|
|
324
|
+
# useful validation error instead of "property id cannot be found".
|
|
325
|
+
$parsed = Get-Content -LiteralPath $path -Raw | ConvertFrom-Json
|
|
326
|
+
$patch = @($parsed | ForEach-Object { $_ })
|
|
327
|
+
$root = if ($patch.Count -eq 1) { $patch[0] } else { $null }
|
|
328
|
+
$insertProperty = if ($null -ne $root) { $root.PSObject.Properties['insert'] } else { $null }
|
|
329
|
+
$insertValue = if ($null -ne $insertProperty) { $insertProperty.Value } else { $null }
|
|
330
|
+
$inserts = @($insertValue | ForEach-Object { $_ })
|
|
331
|
+
$bridge = if ($inserts.Count -eq 1) { $inserts[0] } else { $null }
|
|
332
|
+
$idProperty = if ($null -ne $bridge) { $bridge.PSObject.Properties['id'] } else { $null }
|
|
333
|
+
$nameProperty = if ($null -ne $bridge) { $bridge.PSObject.Properties['name'] } else { $null }
|
|
334
|
+
if ($patch.Count -ne 1 -or $inserts.Count -ne 1 -or $null -eq $idProperty -or [string] $idProperty.Value -ne 'dsh-crew-official-web-bridge' -or $null -eq $nameProperty) {
|
|
335
|
+
throw 'Crew frontend overlay has an unexpected structure.'
|
|
336
|
+
}
|
|
337
|
+
$uri = [Uri] ([string] $nameProperty.Value)
|
|
107
338
|
if (-not $uri.IsFile) { throw 'Crew frontend must load from its local snapshot.' }
|
|
108
339
|
$entry = [IO.Path]::GetFullPath($uri.LocalPath)
|
|
109
340
|
$prefix = [IO.Path]::GetFullPath((Join-Path $frontendRoot 'revisions')) + [IO.Path]::DirectorySeparatorChar
|
|
@@ -126,6 +357,10 @@ function Test-OfficialFrontendAttached {
|
|
|
126
357
|
|
|
127
358
|
function Open-OfficialFrontend {
|
|
128
359
|
param([int] $TimeoutSeconds = 90)
|
|
360
|
+
# Preferred path: the Crew-managed Harness serves 3080 with the Crew panel
|
|
361
|
+
# already loaded. The legacy fallback below (separate official install in
|
|
362
|
+
# ~/.dsh) is only reached when no Crew-managed frontend is available.
|
|
363
|
+
if (Open-CrewManagedFrontend -TimeoutSeconds $TimeoutSeconds) { return }
|
|
129
364
|
$official = Resolve-OfficialHarnessCommand
|
|
130
365
|
$frontend = Get-OfficialFrontendOverlay
|
|
131
366
|
$mutex = New-Object System.Threading.Mutex($false, 'Local\DSHCrewOfficialFrontendLauncher')
|
|
@@ -196,12 +431,10 @@ function Get-HealthState {
|
|
|
196
431
|
# the process cannot be proven to match the tree it will next boot.
|
|
197
432
|
$expectedDshVersion = $null
|
|
198
433
|
$diskReadable = $false
|
|
199
|
-
$
|
|
200
|
-
if (
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
if ($expectedDshVersion) { $diskReadable = $true }
|
|
204
|
-
} catch { $expectedDshVersion = $null }
|
|
434
|
+
$activeDshManifest = Get-CrewDshManifest -Entry $dshCli
|
|
435
|
+
if ($null -ne $activeDshManifest -and [string] $activeDshManifest.version) {
|
|
436
|
+
$expectedDshVersion = [string] $activeDshManifest.version
|
|
437
|
+
$diskReadable = $true
|
|
205
438
|
}
|
|
206
439
|
$cohortMatches = $diskReadable -and $runtime.dsh_version -eq $expectedDshVersion
|
|
207
440
|
if ($response.ok -eq $true -and $version -and $cohortMatches) {
|
|
@@ -814,7 +1047,8 @@ function Start-CrewService {
|
|
|
814
1047
|
try {
|
|
815
1048
|
$env:DSH_HOME = $Service.Home
|
|
816
1049
|
$arguments = @('--profile', $Service.Profile, '--host', '127.0.0.1', '--port', [string] $Service.Port, '--no-open')
|
|
817
|
-
$
|
|
1050
|
+
$launchArguments = if ($dshCliIsNodeEntry) { @($dshCli) + $arguments } else { $arguments }
|
|
1051
|
+
$process = Start-Process -FilePath $dshCommand -ArgumentList $launchArguments -WindowStyle Hidden -PassThru `
|
|
818
1052
|
-RedirectStandardOutput $stdout -RedirectStandardError $stderr
|
|
819
1053
|
$Service.Process = $process
|
|
820
1054
|
$Service.RootPid = $process.Id
|
|
@@ -1034,14 +1268,14 @@ if ($env:DSH_CREW_LAUNCHER_TEST_IMPORT -eq '1') { return }
|
|
|
1034
1268
|
try {
|
|
1035
1269
|
New-Item -ItemType Directory -Path $logRoot -Force | Out-Null
|
|
1036
1270
|
Write-LaunchLog ('Launcher started; mode={0}; user={1}' -f $Mode, $env:USERNAME)
|
|
1037
|
-
if ($Mode -eq 'open') { Open-OfficialFrontend }
|
|
1038
|
-
|
|
1039
1271
|
if (-not (Test-Path -LiteralPath $dshCli -PathType Leaf)) {
|
|
1040
|
-
throw "DSH CLI was not found at $dshCli.
|
|
1272
|
+
throw "DSH CLI was not found at $dshCli. Configure DSH_CREW_DSH_CLI to a Crew-owned official CLI entry, or run: npm install -g @ran-sh/dsh-crew@latest; dsh-crew update"
|
|
1041
1273
|
}
|
|
1274
|
+
Test-CrewDshCliPreflight
|
|
1042
1275
|
if (-not (Test-Path -LiteralPath (Join-Path $crewHome 'profiles\dsh-crew\package.json') -PathType Leaf)) {
|
|
1043
1276
|
throw "The isolated dsh-crew profile is missing under $crewHome. Run: dsh-crew update"
|
|
1044
1277
|
}
|
|
1278
|
+
if ($Mode -eq 'open') { Open-OfficialFrontend }
|
|
1045
1279
|
# Background/watch modes remain independent of the official frontend.
|
|
1046
1280
|
|
|
1047
1281
|
if ($Mode -eq 'watch') {
|
package/worker.cordis.yml
CHANGED
|
@@ -20,15 +20,11 @@
|
|
|
20
20
|
mode: !!js process.env.DSH_SANDBOX_MODE ?? 'workspace-write'
|
|
21
21
|
workspaceRoot: !!js process.env.DSH_CWD ?? process.cwd()
|
|
22
22
|
|
|
23
|
-
- id: fs-local
|
|
24
|
-
config:
|
|
25
|
-
cwd: !!js process.env.DSH_CWD ?? process.cwd()
|
|
26
|
-
|
|
27
23
|
- id: system-prompt
|
|
28
24
|
config:
|
|
29
25
|
includeHarnessIdentity: false
|
|
30
26
|
includeRuntimeContext: false
|
|
31
|
-
|
|
27
|
+
personaPrefix: !!js process.env.DSH_SYSTEM_PROMPT ?? 'You are a focused coding worker. Complete the assigned task, then summarize what you did in your final message.'
|
|
32
28
|
|
|
33
29
|
- id: agent-loop
|
|
34
30
|
config:
|
|
@@ -40,6 +36,14 @@
|
|
|
40
36
|
compression: zstd
|
|
41
37
|
|
|
42
38
|
- insert:
|
|
39
|
+
# sdk-minimal dropped its own fs-local row in the 0.1.5 cohort, but the
|
|
40
|
+
# Crew tool suite still injects the `fs` service, so Crew owns the row and
|
|
41
|
+
# keeps the worker's editor rooted at the task workspace.
|
|
42
|
+
- id: fs-local
|
|
43
|
+
name: '@deepseek-ai/dsh-fs-local'
|
|
44
|
+
config:
|
|
45
|
+
cwd: !!js process.env.DSH_CWD ?? process.cwd()
|
|
46
|
+
|
|
43
47
|
- id: fs-observation-policy
|
|
44
48
|
name: '@deepseek-ai/dsh-fs-observation-policy'
|
|
45
49
|
|