@ran-sh/dsh-crew 2.2.0 → 2.2.1

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 (49) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/package.json +1 -1
  3. package/scripts/setup.mjs +6 -27
  4. package/scripts/verify-npm-install.mjs +2 -2
  5. package/src/adaptive-routing.mjs +2 -2
  6. package/src/ci-evidence.mjs +1 -1
  7. package/src/client/quick-panel.tsx +0 -1
  8. package/src/credential-lifecycle.mjs +0 -3
  9. package/src/credential-purge-state.mjs +1 -5
  10. package/src/credential-reference.mjs +0 -8
  11. package/src/crew-workspaces.mjs +1 -1
  12. package/src/delivery.mjs +2 -2
  13. package/src/dsh-cli-runtime.mjs +18 -22
  14. package/src/extension-contract.mjs +1 -1
  15. package/src/failure-classification.mjs +0 -10
  16. package/src/hub/entry.mjs +1 -1
  17. package/src/hub/index.mjs +0 -1
  18. package/src/i18n.mjs +0 -2
  19. package/src/install/crew-skill.mjs +1 -1
  20. package/src/install/install-legacy.mjs +3 -3
  21. package/src/install/npx-lifecycle.mjs +60 -112
  22. package/src/install/official-web.mjs +1 -1
  23. package/src/install/windows-startup.mjs +5 -5
  24. package/src/install/windows-supervisor-adapter.mjs +3 -2
  25. package/src/install/windows-supervisor-lifecycle.mjs +2 -2
  26. package/src/install/zcode.mjs +0 -2
  27. package/src/job-contracts.mjs +1 -1
  28. package/src/job-identity.mjs +2 -2
  29. package/src/jobs.mjs +3 -4
  30. package/src/model-routing.mjs +3 -4
  31. package/src/model-schedule.mjs +0 -1
  32. package/src/policy-legacy.mjs +10 -12
  33. package/src/provider-inventory.mjs +1 -1
  34. package/src/provider-layer-migration.mjs +0 -2
  35. package/src/readiness-matrix.mjs +1 -1
  36. package/src/release-in-use.mjs +1 -1
  37. package/src/role-profiles.mjs +2 -2
  38. package/src/runtime-controls.mjs +1 -1
  39. package/src/runtime-identity.mjs +1 -1
  40. package/src/status-shard.mjs +1 -1
  41. package/src/supervisor/restart-request.mjs +4 -4
  42. package/src/workflow-runtime.mjs +1 -2
  43. package/src/workspace-context.mjs +2 -2
  44. package/src/workspace-isolation.mjs +4 -4
  45. package/src/workspace-lock.mjs +2 -3
  46. package/scripts/policy-probe.mjs +0 -101
  47. package/scripts/verify-crew-ui-polish.mjs +0 -145
  48. package/scripts/verify-history-ui.mjs +0 -98
  49. package/scripts/verify-installer-fix.mjs +0 -26
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-crew",
3
- "version": "2.2.0",
3
+ "version": "2.2.1",
4
4
  "description": "Dispatch subtasks to DeepSeek Harness (DSH) agents as native subagents with live progress",
5
5
  "author": {
6
6
  "name": "ZSeven-W"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ran-sh/dsh-crew",
3
- "version": "2.2.0",
3
+ "version": "2.2.1",
4
4
  "type": "module",
5
5
  "main": "./src/hub/entry.mjs",
6
6
  "bin": {
package/scripts/setup.mjs CHANGED
@@ -16,7 +16,6 @@ import {
16
16
  ensureCrewDshRuntime,
17
17
  ensureCrewPluginRegistration,
18
18
  resolveDshCli,
19
- runResolvedDsh,
20
19
  describeDshCli,
21
20
  removeCrewPluginRegistration,
22
21
  TARGET_DSH_VERSION,
@@ -81,11 +80,12 @@ function run(cmd, args, opts = {}) {
81
80
  * may use `npx -y` to fetch DSH, while status/uninstall probes never trigger a
82
81
  * network download merely by checking availability.
83
82
  *
84
- * The returned descriptor is the CLI base only; every invocation is built by
85
- * `runDsh` with the dedicated dsh-crew profile and the Crew DSH_HOME in the
86
- * child process environment. The official ``web`` profile is never a target.
83
+ * The returned descriptor is the CLI base only, reported to the operator as
84
+ * `description`; the dedicated dsh-crew profile and the Crew-owned DSH_HOME
85
+ * are supplied by the install flow. The official ``web`` profile is never a
86
+ * target.
87
87
  */
88
- export function detectDsh({ allowDownload = false, home = homedir(), includeCompatibility = true } = {}) {
88
+ function detectDsh({ allowDownload = false, home = homedir(), includeCompatibility = true } = {}) {
89
89
  const resolved = resolveDshCli({
90
90
  home,
91
91
  allowDownload,
@@ -95,18 +95,6 @@ export function detectDsh({ allowDownload = false, home = homedir(), includeComp
95
95
  return { ...resolved, cli: resolved.command, description: describeDshCli(resolved) };
96
96
  }
97
97
 
98
- /**
99
- * Run a DSH plugin command against the dedicated Crew profile under the
100
- * Crew-owned DSH_HOME. `home` is the "user home" base used to derive that
101
- * isolated home, so tests can point it at a disposable root.
102
- */
103
- export function runDsh(dsh, args, { home = homedir() } = {}) {
104
- const resolved = dsh?.command
105
- ? dsh
106
- : { kind: dsh?.kind ?? 'legacy', command: dsh?.cli ?? dsh, args: [] };
107
- return runResolvedDsh(resolved, ['plugin', '--profile', CREW_PROFILE_NAME, ...args], { home });
108
- }
109
-
110
98
  export function readPackageName(root = ROOT) {
111
99
  try { return JSON.parse(readFileSync(join(root, 'package.json'), 'utf8')).name; } catch { return null; }
112
100
  }
@@ -117,20 +105,11 @@ export function checkRoot(root = ROOT) {
117
105
  && existsSync(join(root, 'cordis.patch.yml'));
118
106
  }
119
107
 
120
- export function depsPresent(root = ROOT) {
108
+ function depsPresent(root = ROOT) {
121
109
  return existsSync(join(root, 'node_modules', '@modelcontextprotocol', 'sdk'))
122
110
  && existsSync(join(root, 'node_modules', '.bin', 'tsdown'));
123
111
  }
124
112
 
125
- export function profileHasPackage(home, name, profileRoot = crewProfileDir({ home })) {
126
- const pkgFile = join(profileRoot, 'package.json');
127
- if (!existsSync(pkgFile)) return false;
128
- try {
129
- const pkg = JSON.parse(readFileSync(pkgFile, 'utf8'));
130
- return Boolean(pkg.dependencies?.[name]) || Boolean((pkg.dsh?.profile?.bundles ?? []).includes(name));
131
- } catch { return false; }
132
- }
133
-
134
113
  function mark(log, ok, text) {
135
114
  log(`${ok ? '✓' : '✗'} ${text}`);
136
115
  return ok;
@@ -24,7 +24,7 @@ const DSH_PACKAGE = '@deepseek-ai/dsh';
24
24
  const DSH_PREFIX = '@deepseek-ai/dsh-';
25
25
  const MAX_DIRECT_PEERS = 64;
26
26
 
27
- export function resolveNpmInvocation({
27
+ function resolveNpmInvocation({
28
28
  platform = process.platform,
29
29
  nodePath = process.execPath,
30
30
  fileExists = existsSync,
@@ -144,7 +144,7 @@ function manifestUrl(registryUrl, name, version) {
144
144
  return `${registryUrl.replace(/\/+$/, '')}/${encodedName}/${encodeURIComponent(version)}`;
145
145
  }
146
146
 
147
- export async function fetchPublicManifest(name, version, {
147
+ async function fetchPublicManifest(name, version, {
148
148
  registryUrl = registry,
149
149
  fetchImpl = globalThis.fetch,
150
150
  } = {}) {
@@ -5,8 +5,8 @@
5
5
  // caller may record only outcomes Crew already observed, and routing decisions
6
6
  // expose bounded evidence suitable for model-selection trace metadata.
7
7
 
8
- export const ADAPTIVE_ROUTING_VERSION = 1;
9
- export const ADAPTIVE_ROUTING_DEFAULTS = Object.freeze({
8
+ const ADAPTIVE_ROUTING_VERSION = 1;
9
+ const ADAPTIVE_ROUTING_DEFAULTS = Object.freeze({
10
10
  enabled: false,
11
11
  window_size: 8,
12
12
  min_samples: 2,
@@ -29,7 +29,7 @@
29
29
  // one; nothing here reads credentials from disk, and the value is never logged,
30
30
  // returned, or included in an evidence record.
31
31
 
32
- export const CI_EVIDENCE_REPO = 'Ran-sh/dsh-crew';
32
+ const CI_EVIDENCE_REPO = 'Ran-sh/dsh-crew';
33
33
 
34
34
  // The row each CI job validates. A platform with no job in the workflow cannot
35
35
  // be evidenced by any run, so `macos_smoke` is deliberately absent rather than
@@ -9,7 +9,6 @@ import { PanelHeader, PanelStyles } from './panel-chrome';
9
9
  // (/_dsh/dsh-crew/quick-config, /quick-status). Runtime maintenance stays on 3210.
10
10
 
11
11
  const API = '/_dsh/dsh-crew';
12
- const CREW_CONTROL_PLANE_URL = 'http://127.0.0.1:3210/';
13
12
  const FULL = 'http://127.0.0.1:3210/';
14
13
 
15
14
  type ModelEntry = { provider: string; model: string };
@@ -4,9 +4,6 @@
4
4
 
5
5
  import { createHash, randomUUID } from 'node:crypto';
6
6
 
7
- export const CREDENTIAL_PURGE_STATES = Object.freeze([
8
- 'PLANNED', 'REFERENCE_RECHECKED', 'PURGE_PENDING_CONFIRMATION', 'PURGED', 'VERIFIED', 'FAILED',
9
- ]);
10
7
  const ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,191}$/;
11
8
  const REVISION_PATTERN = /^[a-f0-9]{64}$/;
12
9
 
@@ -36,7 +36,7 @@ export function markCredentialPurged(input, plan) {
36
36
  };
37
37
  }
38
38
 
39
- export function markCredentialPurgeUnverified(input, plan) {
39
+ function markCredentialPurgeUnverified(input, plan) {
40
40
  const state = normalizeCredentialPurgeState(input);
41
41
  if (!validId(plan?.reference_id) || !validId(plan?.plan_id)) return state;
42
42
  const purged = { ...state.purged };
@@ -53,7 +53,3 @@ export function recordCredentialPurgeOutcome(input, plan, result) {
53
53
  if (result?.state === 'PURGED') return markCredentialPurgeUnverified(input, plan);
54
54
  return normalizeCredentialPurgeState(input);
55
55
  }
56
-
57
- export function isCredentialPurged(input, referenceId) {
58
- return Object.prototype.hasOwnProperty.call(normalizeCredentialPurgeState(input).purged, referenceId);
59
- }
@@ -28,11 +28,3 @@ export function classifyCredentialReference(value, { kind = 'env' } = {}) {
28
28
  : HANDLE_REFERENCE.test(raw) && !SECRET_LIKE.test(raw) && !EMBEDDED_SECRET.test(raw);
29
29
  return { present: true, value: valid ? raw : null, redacted: !valid };
30
30
  }
31
-
32
- export function sanitizeCredentialReference(value, options = {}) {
33
- return classifyCredentialReference(value, options).value;
34
- }
35
-
36
- export function hasCredentialReference(value, options = {}) {
37
- return classifyCredentialReference(value, options).present;
38
- }
@@ -32,7 +32,7 @@ import { acquireWorkspaceLock, DEFAULT_LOCK_WAIT_MS } from './workspace-lock.mjs
32
32
 
33
33
  export const CREW_WORKSPACE_WORKER = 'dsh-crew-worker';
34
34
  export const CREW_WORKSPACE_REVIEW = 'dsh-crew-review';
35
- export const CREW_WORKSPACE_NAMES = Object.freeze([CREW_WORKSPACE_WORKER, CREW_WORKSPACE_REVIEW]);
35
+ const CREW_WORKSPACE_NAMES = Object.freeze([CREW_WORKSPACE_WORKER, CREW_WORKSPACE_REVIEW]);
36
36
  export const WORKSPACE_CONFLICT = 'WORKSPACE_CONFLICT';
37
37
 
38
38
  /** The workspace a role runs in. Only the reviewer is separated out. */
package/src/delivery.mjs CHANGED
@@ -11,7 +11,7 @@
11
11
 
12
12
  export const DELIVERY_SECTIONS = ['Diff', 'Tests', 'Risks'];
13
13
  export const OPTIONAL_DELIVERY_SECTIONS = ['Unverified'];
14
- export const ALL_DELIVERY_SECTIONS = [...DELIVERY_SECTIONS, ...OPTIONAL_DELIVERY_SECTIONS];
14
+ const ALL_DELIVERY_SECTIONS = [...DELIVERY_SECTIONS, ...OPTIONAL_DELIVERY_SECTIONS];
15
15
 
16
16
  export const REVIEW_SECTIONS = ['Review Findings', 'Evidence', 'Risks', 'Verdict'];
17
17
 
@@ -129,7 +129,7 @@ export function appendDeliveryInstructions(task, { tier, isReview } = {}) {
129
129
  * visible evidence. Two parsers gave two answers about one section; there is now
130
130
  * one, and it lives here.
131
131
  */
132
- export function parseTestRow(line) {
132
+ function parseTestRow(line) {
133
133
  const normalized = String(line ?? '')
134
134
  .replace(/^\s*[-*+]\s+/, '')
135
135
  .replace(/^\|/, '')
@@ -35,7 +35,7 @@ import {
35
35
  // Re-exported so existing importers keep working while the cohort value now
36
36
  // lives in exactly one place (src/dsh-cohort.mjs).
37
37
  export { DSH_CLI_PACKAGE, TARGET_DSH_VERSION, TARGET_DSH_SPEC };
38
- export const CREW_DSH_RUNTIME_DIRNAME = 'runtime';
38
+ const CREW_DSH_RUNTIME_DIRNAME = 'runtime';
39
39
  const CREW_PROFILE_DEFAULT_BUNDLES = ['@deepseek-ai/dsh-base', '@deepseek-ai/dsh-web-app'];
40
40
  const PROFILE_PATCH_TEMPLATE = '[]\n';
41
41
  const PROFILE_PNPM_WORKSPACE = 'packages:\n - .\n\nnodeLinker: hoisted\nautoInstallPeers: false\n';
@@ -73,11 +73,6 @@ export function crewDshRuntimeVersionDir({ home = homedir(), version = TARGET_DS
73
73
  return join(crewDshHome({ home }), `runtime-${version}`);
74
74
  }
75
75
 
76
- export function crewDshRuntimeEntry({ home = homedir(), platform = process.platform } = {}) {
77
- const suffix = platform === 'win32' ? '.cmd' : '';
78
- return join(crewDshRuntimeRoot({ home }), 'node_modules', '.bin', `dsh${suffix}`);
79
- }
80
-
81
76
  export function crewDshRuntimeModule({ home = homedir() } = {}) {
82
77
  return join(crewDshRuntimeRoot({ home }), 'node_modules', '@deepseek-ai', 'dsh', 'lib', 'bin.js');
83
78
  }
@@ -736,35 +731,36 @@ export async function restoreRetainedRuntime({
736
731
  }
737
732
 
738
733
  // GC retained runtimes that no release pins. A retained cohort is needed only
739
- // while some managed release (current or retained) declares it as its exact
740
- // @deepseek-ai/dsh dependency. Best-effort; never throws.
741
- export function gcRetainedRuntimes({ home = homedir(), releases = [], log = () => {} } = {}) {
734
+ // while some managed release resolves to it, so the caller passes the resolved
735
+ // cohort versions (see resolveReleaseCohort: manifest pin, then the
736
+ // release-cohort.json sidecar). Best-effort; never throws — a root that is
737
+ // missing, unreadable or not a directory prunes nothing rather than failing
738
+ // the update that just committed.
739
+ export function gcRetainedRuntimes({ home = homedir(), cohorts = [], log = () => {} } = {}) {
742
740
  const root = retainedRuntimesRoot({ home });
743
- if (!existsSync(root)) return [];
744
- const needed = new Set();
745
- for (const release of releases) {
746
- const spec = payloadDshSpec(release);
747
- if (spec && /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(spec)) needed.add(spec);
748
- }
741
+ const needed = new Set(cohorts.filter((spec) => typeof spec === 'string' && EXACT_COHORT_RE.test(spec)));
742
+ let names;
743
+ try { names = readdirSync(root); } catch { return []; }
749
744
  const removed = [];
750
- for (const name of readdirSync(root)) {
745
+ for (const name of names) {
746
+ if (needed.has(name)) continue;
751
747
  const dir = join(root, name);
752
- if (!needed.has(name)) {
753
- try { rmSync(dir, { recursive: true, force: true }); removed.push(name); } catch { /* best effort */ }
754
- }
748
+ try { rmSync(dir, { recursive: true, force: true }); removed.push(name); } catch { /* best effort */ }
755
749
  }
756
750
  return removed;
757
751
  }
758
752
 
753
+ const EXACT_COHORT_RE = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/;
754
+
759
755
  // Read the exact @deepseek-ai/dsh pin from a payload manifest (dependencies
760
756
  // first, then peerDependencies). Exact pins only: a range or absence yields
761
757
  // null so callers fail closed instead of guessing a cohort.
762
758
  function payloadDshSpec(manifest) {
763
759
  if (!manifest || typeof manifest !== 'object') return null;
764
760
  const direct = manifest.dependencies?.['@deepseek-ai/dsh'];
765
- if (typeof direct === 'string' && /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(direct)) return direct;
761
+ if (typeof direct === 'string' && EXACT_COHORT_RE.test(direct)) return direct;
766
762
  const peer = manifest.peerDependencies?.['@deepseek-ai/dsh'];
767
- if (typeof peer === 'string' && /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(peer)) return peer;
763
+ if (typeof peer === 'string' && EXACT_COHORT_RE.test(peer)) return peer;
768
764
  return null;
769
765
  }
770
766
 
@@ -964,7 +960,7 @@ function ensureDirectoryLink(linkPath, targetRoot) {
964
960
  * command. The profile manifest and one loader-visible directory link are the
965
961
  * only state changed; no dependency resolution or policy bypass is attempted.
966
962
  */
967
- export function ensurePluginRegistration({
963
+ function ensurePluginRegistration({
968
964
  profileRoot,
969
965
  root,
970
966
  name,
@@ -4,7 +4,7 @@
4
4
 
5
5
  import { validateModelCallabilityV2 } from './model-callability-contract.mjs';
6
6
 
7
- export const EXTENSION_CONTRACT_SCHEMA_VERSION = 1;
7
+ const EXTENSION_CONTRACT_SCHEMA_VERSION = 1;
8
8
 
9
9
  function row(matrix, id) {
10
10
  return Array.isArray(matrix?.rows) ? matrix.rows.find((entry) => entry?.id === id) : undefined;
@@ -3,16 +3,6 @@
3
3
  // Classification consumes structured status/codes/outcomes only. It never
4
4
  // parses raw exception text, provider responses, credentials, quotas or logs.
5
5
 
6
- export const FAILURE_CATEGORIES = Object.freeze([
7
- 'none',
8
- 'policy',
9
- 'compatibility',
10
- 'provider',
11
- 'runtime',
12
- 'verification',
13
- 'cancelled',
14
- ]);
15
-
16
6
  export const FAILURE_REASON_CODES = Object.freeze({
17
7
  NONE: 'NONE',
18
8
  CANCELLED: 'CANCELLED',
package/src/hub/entry.mjs CHANGED
@@ -68,7 +68,7 @@ export function recordAdaptiveJobOutcome(job, store = getProcessAdaptiveHealthSt
68
68
  * a chained promise preserves all legacy completion/waiter behavior and runs
69
69
  * this observer only after the Hub's own finalizer populated endedAt/status.
70
70
  */
71
- export function installAdaptiveHealthObserver() {
71
+ function installAdaptiveHealthObserver() {
72
72
  if (WorkerRegistry.prototype[ADAPTIVE_OBSERVER_INSTALLED] === true) return false;
73
73
  const originalSpawn = WorkerRegistry.prototype.spawn;
74
74
  Object.defineProperty(WorkerRegistry.prototype, ADAPTIVE_OBSERVER_INSTALLED, {
package/src/hub/index.mjs CHANGED
@@ -119,7 +119,6 @@ export const QUICK_CONFIG_KEYS = Object.freeze([
119
119
  const LEGACY_TIER_MODELS = { flash: 'deepseek-v4-flash', pro: 'deepseek-v4-pro' };
120
120
  // Local copy (the hub must not import jobs.mjs, which pulls the DSH SDK into
121
121
  // the profile realm): a valid dispatch role set.
122
- const ROLES = { worker: true, reviewer: true };
123
122
  const CONFIG_DIR = join(homedir(), '.config', 'dsh-crew');
124
123
  const CREDENTIAL_PURGE_STATE_FILE = join(CONFIG_DIR, 'credential-purge-lifecycle.json');
125
124
  const RECOVERY_ACTION_PATTERN = /^[a-f0-9]{32}$/u;
package/src/i18n.mjs CHANGED
@@ -13,7 +13,5 @@ export function setLang(lang) {
13
13
  return LANG;
14
14
  }
15
15
 
16
- export function getLang() { return LANG; }
17
-
18
16
  /** Pick the string for the active locale. Both branches are cheap literals. */
19
17
  export function tr(zh, en) { return LANG === 'zh' ? zh : en; }
@@ -42,7 +42,7 @@ export function crewSkillFiles({ home = homedir(), env = process.env } = {}) {
42
42
  }
43
43
 
44
44
  /** The skill template shipped in the payload. */
45
- export function crewSkillSource({ root }) {
45
+ function crewSkillSource({ root }) {
46
46
  return join(root, 'skills', CREW_SKILL_NAME, 'SKILL.md');
47
47
  }
48
48
 
@@ -57,7 +57,7 @@ const REGEX_MAY_FOLLOW = new Set([
57
57
  * from the raw text, so a keyword inside a comment or a string is not a
58
58
  * declaration and a quote inside a regex is not a string.
59
59
  */
60
- export function tokenizeModuleSource(source) {
60
+ function tokenizeModuleSource(source) {
61
61
  const tokens = [];
62
62
  const text = String(source ?? '');
63
63
  let i = 0;
@@ -748,7 +748,7 @@ export function pickClaudeCommand(candidates, { platform = process.platform } =
748
748
  ?? list[0];
749
749
  }
750
750
 
751
- export function resolveClaudeCommand({ platform = process.platform } = {}) {
751
+ function resolveClaudeCommand({ platform = process.platform } = {}) {
752
752
  const probe = spawnSync(platform === 'win32' ? 'where' : 'which', ['claude'], {
753
753
  encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'],
754
754
  });
@@ -1019,7 +1019,7 @@ export async function installClaudeCode({ home = homedir(), statusline = false,
1019
1019
  }
1020
1020
 
1021
1021
  // Keep CLI transaction ordering testable without changing the operator's home.
1022
- export async function refreshClaudePlugin({ home, root, actions = [], claudeCommand, runStep = runClaudeStep }) {
1022
+ async function refreshClaudePlugin({ home, root, actions = [], claudeCommand, runStep = runClaudeStep }) {
1023
1023
  // Each step runs to completion — including ending the whole process tree on a
1024
1024
  // timeout — before the next one starts, and the settle window is the widest any
1025
1025
  // step asks for. Two things this replaces: an `uninstall` left running past its