@ran-sh/dsh-crew 1.0.2 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/README.md +4 -0
  2. package/README.zh.md +4 -0
  3. package/codex/AGENTS.md +101 -92
  4. package/docs/ui-surfaces.md +48 -21
  5. package/lib/client.js +235 -34
  6. package/official-web-bridge/lib/client.js +368 -4532
  7. package/official-web-bridge/package.json +1 -2
  8. package/package.json +130 -67
  9. package/scripts/build-client.mjs +30 -15
  10. package/scripts/remove-legacy-official-bridge.ps1 +89 -0
  11. package/scripts/setup.mjs +152 -140
  12. package/scripts/verify-npm-install.mjs +311 -310
  13. package/scripts/verify-official-bridge-e2e.mjs +12 -1
  14. package/src/client/host-readiness.mjs +62 -59
  15. package/src/client/index.tsx +117 -24
  16. package/src/client/quick-entry.tsx +10 -0
  17. package/src/client/quick-panel.tsx +295 -0
  18. package/src/client/surface-detection.mjs +43 -30
  19. package/src/credential-reference.mjs +38 -0
  20. package/src/dsh-cli-runtime.mjs +500 -40
  21. package/src/dsh-cohort.mjs +20 -0
  22. package/src/hub/index.mjs +1602 -1016
  23. package/src/install/npx-lifecycle.mjs +1983 -468
  24. package/src/install/official-web.mjs +36 -73
  25. package/src/jobs.mjs +20 -24
  26. package/src/model-catalog.mjs +8 -1
  27. package/src/official-web-bridge.mjs +329 -249
  28. package/src/provider-delete-adapters.mjs +165 -17
  29. package/src/provider-inventory.mjs +17 -3
  30. package/src/provider-layer-migration-adapters.mjs +759 -0
  31. package/src/provider-layer-migration.mjs +198 -0
  32. package/src/provider-lifecycle-state.mjs +4 -1
  33. package/src/provider-profile-store.mjs +193 -4
  34. package/src/provider-settings-store.mjs +82 -6
  35. package/src/provider-store-lock.mjs +67 -0
  36. package/src/runtime-identity.mjs +24 -1
  37. package/src/supervisor/restart-request.mjs +202 -0
  38. package/windows/start-dsh-crew.ps1 +783 -370
  39. package/worker.cordis.yml +32 -46
  40. package/zcode/AGENTS.md +35 -26
@@ -1,16 +1,11 @@
1
1
  import {
2
- copyFileSync,
3
2
  existsSync,
4
3
  lstatSync,
5
- mkdirSync,
6
4
  readFileSync,
7
5
  realpathSync,
8
- unlinkSync,
9
- writeFileSync,
10
6
  } from 'node:fs';
11
7
  import { homedir } from 'node:os';
12
- import { dirname, join } from 'node:path';
13
- import { ensurePluginRegistration, removeCrewPluginRegistration } from '../dsh-cli-runtime.mjs';
8
+ import { join } from 'node:path';
14
9
 
15
10
  export const OFFICIAL_BRIDGE_PACKAGE = '@ran-sh/dsh-crew-web-bridge';
16
11
  const STATE_FILENAME = 'official-web.json';
@@ -30,12 +25,6 @@ function readState(home) {
30
25
  } catch { return null; }
31
26
  }
32
27
 
33
- function writeState(home, value) {
34
- const file = officialWebIntegrationStateFile({ home });
35
- mkdirSync(dirname(file), { recursive: true });
36
- writeFileSync(file, JSON.stringify(value, null, 2) + '\n');
37
- }
38
-
39
28
  function validOfficialManifest(file) {
40
29
  if (!existsSync(file)) return { ok: false, code: 'OFFICIAL_WEB_PROFILE_NOT_FOUND' };
41
30
  try {
@@ -50,83 +39,57 @@ function validOfficialManifest(file) {
50
39
  } catch { return { ok: false, code: 'OFFICIAL_WEB_PROFILE_INVALID' }; }
51
40
  }
52
41
 
53
- function makeBackup({ home, profileManifest, previous }) {
54
- if (previous?.backup_file && existsSync(previous.backup_file)) return previous.backup_file;
55
- const backupDir = join(home, '.config', 'dsh-crew', 'backups');
56
- mkdirSync(backupDir, { recursive: true });
57
- const backupFile = join(backupDir, `official-web-package-${Date.now()}.json`);
58
- copyFileSync(profileManifest, backupFile);
59
- return backupFile;
60
- }
42
+ export const OFFICIAL_WEB_READ_ONLY_CODE = 'OFFICIAL_WEB_PROFILE_READ_ONLY';
61
43
 
44
+ // The official ~/.dsh/profiles/web tree is a read-only boundary. Crew must
45
+ // never install, update, register, unlink, repair, or mutate anything under
46
+ // ~/.dsh; the isolated 3210 Crew backend is the only runtime Crew owns.
47
+ // ensure/remove therefore fail closed instead of writing the official
48
+ // profile. Status probing stays read-only.
62
49
  export function ensureOfficialWebIntegration({ home = homedir(), releaseDir } = {}) {
63
- let resolvedRelease;
64
- try { resolvedRelease = realpathSync(releaseDir); } catch { return { ok: false, code: 'RELEASE_NOT_FOUND' }; }
65
- const bridgeRoot = join(resolvedRelease, 'official-web-bridge');
66
- const profileRoot = officialWebProfileDir({ home });
67
- const profileManifest = join(profileRoot, 'package.json');
68
- const profile = validOfficialManifest(profileManifest);
69
- if (!profile.ok) return profile;
70
- const previous = readState(home);
71
- const backupFile = makeBackup({ home, profileManifest, previous });
72
- const registration = ensurePluginRegistration({
73
- profileRoot,
74
- root: bridgeRoot,
75
- name: OFFICIAL_BRIDGE_PACKAGE,
76
- createProfile: false,
77
- });
78
- if (!registration.ok) {
79
- return { ok: false, code: registration.code === 'CREW_PROFILE_METADATA_INVALID' ? 'OFFICIAL_WEB_PROFILE_INVALID' : registration.code };
80
- }
81
- const stateChanged = previous?.enabled !== true || previous?.release_dir !== resolvedRelease || previous?.backup_file !== backupFile;
82
- writeState(home, {
83
- enabled: true,
84
- release_dir: resolvedRelease,
85
- backup_file: backupFile,
86
- package: OFFICIAL_BRIDGE_PACKAGE,
87
- });
88
- return { ...registration, changed: registration.changed || stateChanged, backupFile };
50
+ return { ok: false, code: OFFICIAL_WEB_READ_ONLY_CODE };
89
51
  }
90
52
 
91
- export function officialWebIntegrationStatus({ home = homedir(), releaseDir } = {}) {
92
- const state = readState(home);
93
- if (state?.enabled !== true) return { enabled: false, healthy: false, state };
53
+ // Project the legacy full-bridge state file. The old `enabled: true` shape
54
+ // is DEPRECATED: dsh-crew no longer desires any control of the 3080 official
55
+ // web profile (the native 3210 page is the full control plane, and the 3080
56
+ // quick surface is optional). Status probing stays read-only; the projection
57
+ // makes the legacy record diagnostic instead of an active intent.
58
+ function projectLegacyState(state, home, releaseDir) {
59
+ if (!state || state?.enabled !== true) {
60
+ return { enabled: false, legacy_present: false, desired: false, healthy: false, state };
61
+ }
94
62
  const expectedRelease = releaseDir ?? state.release_dir;
95
63
  let expectedBridge;
96
64
  try { expectedBridge = realpathSync(join(expectedRelease, 'official-web-bridge')); } catch {
97
- return { enabled: true, healthy: false, code: 'BRIDGE_RELEASE_MISSING', state };
65
+ return { enabled: false, legacy_present: true, desired: false, healthy: false, code: 'BRIDGE_RELEASE_MISSING', state };
98
66
  }
99
67
  const profileRoot = officialWebProfileDir({ home });
100
68
  const profile = validOfficialManifest(join(profileRoot, 'package.json'));
101
- if (!profile.ok) return { enabled: true, healthy: false, code: profile.code, state };
69
+ if (!profile.ok) {
70
+ return { enabled: false, legacy_present: true, desired: false, healthy: false, code: profile.code, state };
71
+ }
102
72
  const dependency = profile.manifest.dependencies?.[OFFICIAL_BRIDGE_PACKAGE];
103
73
  const bundled = profile.manifest.dsh.profile.bundles.includes(OFFICIAL_BRIDGE_PACKAGE);
104
74
  const linkPath = join(profileRoot, 'node_modules', ...OFFICIAL_BRIDGE_PACKAGE.split('/'));
105
75
  let linked = false;
106
76
  try { linked = lstatSync(linkPath).isSymbolicLink() && realpathSync(linkPath) === expectedBridge; } catch {}
107
- return { enabled: true, healthy: Boolean(dependency && bundled && linked), state, linkPath, expectedBridge };
77
+ return {
78
+ enabled: false,
79
+ legacy_present: true,
80
+ desired: false,
81
+ healthy: Boolean(dependency && bundled && linked),
82
+ removal_requires_manual_official_profile_action: true,
83
+ state,
84
+ linkPath,
85
+ expectedBridge,
86
+ };
87
+ }
88
+
89
+ export function officialWebIntegrationStatus({ home = homedir(), releaseDir } = {}) {
90
+ return projectLegacyState(readState(home), home, releaseDir);
108
91
  }
109
92
 
110
93
  export function removeOfficialWebIntegration({ home = homedir(), remember = true, preserveIntent = false } = {}) {
111
- const profileRoot = officialWebProfileDir({ home });
112
- const profileManifest = join(profileRoot, 'package.json');
113
- const profile = validOfficialManifest(profileManifest);
114
- if (!profile.ok && profile.code !== 'OFFICIAL_WEB_PROFILE_NOT_FOUND') return profile;
115
- let removed = false;
116
- if (profile.ok) {
117
- const result = removeCrewPluginRegistration({ home, name: OFFICIAL_BRIDGE_PACKAGE, profileRoot });
118
- if (!result.ok) return { ok: false, code: result.code === 'CREW_PROFILE_METADATA_INVALID' ? 'OFFICIAL_WEB_PROFILE_INVALID' : result.code };
119
- removed = result.removed;
120
- }
121
- const previous = readState(home);
122
- if (remember) writeState(home, {
123
- ...(previous ?? {}),
124
- enabled: preserveIntent ? previous?.enabled === true : false,
125
- package: OFFICIAL_BRIDGE_PACKAGE,
126
- });
127
- else {
128
- const stateFile = officialWebIntegrationStateFile({ home });
129
- if (existsSync(stateFile)) unlinkSync(stateFile);
130
- }
131
- return { ok: true, removed };
94
+ return { ok: false, code: OFFICIAL_WEB_READ_ONLY_CODE };
132
95
  }
package/src/jobs.mjs CHANGED
@@ -1,6 +1,12 @@
1
1
  // Job manager: each job runs one dsh-jsonrpc-agent runtime via the DSH SDK.
2
2
  // Progress derived from session.event notifications; status mirrored to a
3
3
  // JSON file so the Claude Code statusline (and anything else) can render it.
4
+ //
5
+ // alpha.5 SDK surface: DeepSeekHarness takes HarnessClientOptions at the top
6
+ // level (no `launch` wrapper). The SDK resolves the installed same-version
7
+ // dsh CLI itself and enforces the same-version manifest check, so dsh-crew
8
+ // never passes an explicit dshBin that would bypass it. The worker rides the
9
+ // `sdk-minimal` profile with the Crew overlay applied as a --patch.
4
10
 
5
11
  import { DeepSeekHarness } from '@deepseek-ai/dsh-sdk-client';
6
12
  import { readFileSync, mkdirSync, existsSync } from 'node:fs';
@@ -8,7 +14,6 @@ import { createShardWriter } from './status-shard.mjs';
8
14
  import { fileURLToPath } from 'node:url';
9
15
  import { dirname, join, resolve } from 'node:path';
10
16
  import { homedir } from 'node:os';
11
- import { createRequire } from 'node:module';
12
17
  import { appendDeliveryInstructions, parseDeliveryReport, formatDeliveryMetadata } from './delivery.mjs';
13
18
  import { captureWorkspaceBaseline, captureWorkspaceDiff, NOT_A_GIT_REPOSITORY } from './workspace-audit.mjs';
14
19
  import { buildOutcome, JOB_PHASES } from './workflow.mjs';
@@ -17,19 +22,10 @@ import { buildDirectSelectionTrace } from './model-routing.mjs';
17
22
 
18
23
  const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..');
19
24
  const CONFIG_DIR = join(homedir(), '.config', 'dsh-crew');
25
+ const CREW_DSH_HOME = join(homedir(), '.config', 'dsh-crew', 'harness');
20
26
  const STATUS_FILE = join(CONFIG_DIR, 'status.json');
21
27
  const CORDIS = join(ROOT, 'worker.cordis.yml');
22
28
 
23
- // The worker agent entry. The pnpm .bin shim on Windows is a POSIX shell
24
- // script that Node spawn cannot execute (ENOENT), so we always launch
25
- // `node <lib/bin.js> <cordis>` via process.execPath — cross-platform.
26
- const requireAgents = createRequire(import.meta.url);
27
- let AGENT_JS = null;
28
- for (const spec of ['@deepseek-ai/dsh-sdk-jsonrpc-demo/bin', '@deepseek-ai/dsh-sdk-jsonrpc-demo/lib/bin.js']) {
29
- try { AGENT_JS = requireAgents.resolve(spec); break; } catch {}
30
- }
31
- if (!AGENT_JS) AGENT_JS = join(ROOT, 'node_modules', '@deepseek-ai', 'dsh-sdk-jsonrpc-demo', 'lib', 'bin.js');
32
-
33
29
  export const TIERS = {
34
30
  flash: { model: 'deepseek-v4-flash', label: 'V4 Flash' },
35
31
  pro: { model: 'deepseek-v4-pro', label: 'V4 Pro' },
@@ -115,7 +111,7 @@ export function startJob({
115
111
  if (!tierInfo) throw new Error(`unknown tier "${tier}" (expected: ${Object.keys(TIERS).join(', ')})`);
116
112
  if (!ROLES[role]) throw new Error(`unknown role "${role}" (expected: ${Object.keys(ROLES).join(', ')})`);
117
113
  if (!['off', 'high', 'max'].includes(effort)) throw new Error(`unknown effort "${effort}" (expected: off, high, max)`);
118
- if (!existsSync(AGENT_JS)) throw new Error(`dsh-jsonrpc-agent not installed at ${AGENT_JS}; run pnpm install in ${ROOT}`);
114
+ if (!existsSync(CORDIS)) throw new Error(`worker overlay not found at ${CORDIS}`);
119
115
  const workspace = resolve(cwd ?? process.cwd());
120
116
  // The worker always gets the auditable Delivery Contract appended (unless it
121
117
  // already carries one), so its final message follows ## Diff / ## Tests /
@@ -128,22 +124,22 @@ export function startJob({
128
124
  }
129
125
 
130
126
  const harness = new DeepSeekHarness({
131
- launch: {
132
- command: process.execPath,
133
- args: [AGENT_JS, CORDIS],
134
- cwd: workspace,
135
- env: {
136
- ...process.env,
137
- ...dotEnv,
138
- DSH_CWD: workspace,
139
- DSH_SESSION_ROOT: join(CONFIG_DIR, 'sessions'),
140
- DSH_REASONING_EFFORT: effort,
141
- },
142
- requestTimeoutMs: timeoutMs,
127
+ profile: 'sdk-minimal',
128
+ patches: [CORDIS],
129
+ dshHome: CREW_DSH_HOME,
130
+ processCwd: workspace,
131
+ env: {
132
+ ...process.env,
133
+ ...dotEnv,
134
+ DSH_CWD: workspace,
135
+ DSH_SESSION_ROOT: join(CONFIG_DIR, 'sessions'),
136
+ DSH_REASONING_EFFORT: effort,
143
137
  },
138
+ requestTimeoutMs: timeoutMs,
144
139
  cwd: workspace,
145
140
  provider: 'deepseek-official',
146
141
  model: tierInfo.model,
142
+ reasoningEffort: effort,
147
143
  maxTokens,
148
144
  });
149
145
 
@@ -20,7 +20,14 @@ function catalogError() {
20
20
  function normalizeProvider(raw) {
21
21
  if (!raw || typeof raw.id !== 'string' || raw.id.trim() === '') return null;
22
22
  const id = raw.id.trim();
23
- return { id, name: typeof raw.name === 'string' && raw.name.trim() ? raw.name.trim() : id };
23
+ const adapterOwned = typeof raw.adapter_owned === 'boolean'
24
+ ? raw.adapter_owned
25
+ : typeof raw.adapterOwned === 'boolean' ? raw.adapterOwned : undefined;
26
+ return {
27
+ id,
28
+ name: typeof raw.name === 'string' && raw.name.trim() ? raw.name.trim() : id,
29
+ ...(adapterOwned === undefined ? {} : { adapter_owned: adapterOwned }),
30
+ };
24
31
  }
25
32
 
26
33
  function normalizeModels(raw, provider) {