@ran-sh/dsh-crew 2.2.0 → 2.2.2

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 (57) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/official-web-bridge/lib/client.js +1 -1
  3. package/official-web-bridge/overlay-entry.mjs +5 -1
  4. package/official-web-bridge/package.json +1 -1
  5. package/package.json +1 -1
  6. package/scripts/setup.mjs +39 -33
  7. package/scripts/verify-npm-install.mjs +2 -2
  8. package/src/adaptive-routing.mjs +2 -2
  9. package/src/ci-evidence.mjs +1 -1
  10. package/src/client/quick-panel.tsx +6 -2
  11. package/src/credential-lifecycle.mjs +0 -3
  12. package/src/credential-purge-state.mjs +1 -5
  13. package/src/credential-reference.mjs +0 -8
  14. package/src/crew-workspaces.mjs +1 -1
  15. package/src/delivery.mjs +2 -2
  16. package/src/dsh-cli-runtime.mjs +227 -30
  17. package/src/extension-contract.mjs +1 -1
  18. package/src/failure-classification.mjs +0 -10
  19. package/src/hub/entry.mjs +1 -1
  20. package/src/hub/index.mjs +57 -7
  21. package/src/i18n.mjs +0 -2
  22. package/src/install/crew-paths.mjs +16 -11
  23. package/src/install/crew-skill.mjs +1 -1
  24. package/src/install/desktop-profile.mjs +168 -0
  25. package/src/install/harness-config-import.mjs +204 -0
  26. package/src/install/install-legacy.mjs +3 -3
  27. package/src/install/npx-lifecycle.mjs +210 -125
  28. package/src/install/official-web.mjs +1 -1
  29. package/src/install/windows-startup.mjs +5 -5
  30. package/src/install/windows-supervisor-adapter.mjs +3 -2
  31. package/src/install/windows-supervisor-lifecycle.mjs +2 -2
  32. package/src/install/zcode.mjs +0 -2
  33. package/src/job-contracts.mjs +1 -1
  34. package/src/job-identity.mjs +2 -2
  35. package/src/jobs.mjs +3 -4
  36. package/src/model-routing.mjs +3 -4
  37. package/src/model-schedule.mjs +0 -1
  38. package/src/official-web-bridge.mjs +3 -1
  39. package/src/policy-legacy.mjs +10 -12
  40. package/src/provider-inventory.mjs +1 -1
  41. package/src/provider-layer-migration.mjs +0 -2
  42. package/src/provider-settings-store.mjs +19 -1
  43. package/src/readiness-matrix.mjs +1 -1
  44. package/src/release-in-use.mjs +1 -1
  45. package/src/role-profiles.mjs +2 -2
  46. package/src/runtime-controls.mjs +1 -1
  47. package/src/runtime-identity.mjs +1 -1
  48. package/src/status-shard.mjs +1 -1
  49. package/src/supervisor/restart-request.mjs +4 -4
  50. package/src/workflow-runtime.mjs +1 -2
  51. package/src/workspace-context.mjs +2 -2
  52. package/src/workspace-isolation.mjs +4 -4
  53. package/src/workspace-lock.mjs +2 -3
  54. package/scripts/policy-probe.mjs +0 -101
  55. package/scripts/verify-crew-ui-polish.mjs +0 -145
  56. package/scripts/verify-history-ui.mjs +0 -98
  57. 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.2",
4
4
  "description": "Dispatch subtasks to DeepSeek Harness (DSH) agents as native subagents with live progress",
5
5
  "author": {
6
6
  "name": "ZSeven-W"
@@ -61,7 +61,7 @@ function PanelHeader({ title, eyebrow, description, href, linkText, children })
61
61
  //#endregion
62
62
  //#region src/client/quick-panel.tsx
63
63
  const API = "/_dsh/dsh-crew";
64
- const FULL = "http://127.0.0.1:3210/";
64
+ const FULL = "http://127.0.0.1:3210/_dsh/dsh-crew/control-plane";
65
65
  const RESTART_KEYS = /* @__PURE__ */ new Set([]);
66
66
  const T = {
67
67
  zh: {
@@ -4,6 +4,10 @@ import { readFileSync } from 'node:fs';
4
4
 
5
5
  const PREFIX = '/_dsh/dsh-crew';
6
6
  const BACKEND = 'http://127.0.0.1:3210';
7
+ // The control plane's session belongs to the hub's own process, so the only usable
8
+ // entry is the hub's redirect: it mints the session and 302s into it. Advertising
9
+ // the bare port here sent operators to "dsh web authentication required".
10
+ const CONTROL_PLANE = `${BACKEND}${PREFIX}/control-plane`;
7
11
  const REVISION = JSON.parse(readFileSync(new URL('./package.json', import.meta.url), 'utf8')).dshCrewFrontendRevision ?? null;
8
12
 
9
13
  function trusted(req) {
@@ -21,7 +25,7 @@ export function registerFrontend(ctx, { fetchImpl = globalThis.fetch } = {}) {
21
25
  const disposers = [];
22
26
  disposers.push(webServer.register({ kind: 'exact', path: `${PREFIX}/bridge-status`, handler(req, res) {
23
27
  if (!trusted(req)) return send(res, 403, { ok: false, code: 'LOCAL_SAME_ORIGIN_ONLY' });
24
- send(res, 200, { ok: true, surface: 'official-bridge', ui_role: 'quick-controls', frontend_revision: REVISION, full_control_plane_url: `${BACKEND}/` });
28
+ send(res, 200, { ok: true, surface: 'official-bridge', ui_role: 'quick-controls', frontend_revision: REVISION, full_control_plane_url: CONTROL_PLANE });
25
29
  } }));
26
30
  for (const suffix of ['/quick-config', '/quick-status']) {
27
31
  disposers.push(webServer.register({ kind: 'exact', path: `${PREFIX}${suffix}`, async handler(req, res) {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ran-sh/dsh-crew-web-bridge",
3
- "version": "0.3.8",
3
+ "version": "0.3.9",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "main": "./entry.mjs",
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.2",
4
4
  "type": "module",
5
5
  "main": "./src/hub/entry.mjs",
6
6
  "bin": {
package/scripts/setup.mjs CHANGED
@@ -15,12 +15,13 @@ import { crewDshHome, crewProfileDir, CREW_PROFILE_NAME, claudeIntegrationLine }
15
15
  import {
16
16
  ensureCrewDshRuntime,
17
17
  ensureCrewPluginRegistration,
18
+ ensureCrewWebProfile,
18
19
  resolveDshCli,
19
- runResolvedDsh,
20
20
  describeDshCli,
21
21
  removeCrewPluginRegistration,
22
22
  TARGET_DSH_VERSION,
23
23
  } from '../src/dsh-cli-runtime.mjs';
24
+ import { integrationRoot } from '../src/install/crew-paths.mjs';
24
25
 
25
26
  const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..');
26
27
 
@@ -81,11 +82,12 @@ function run(cmd, args, opts = {}) {
81
82
  * may use `npx -y` to fetch DSH, while status/uninstall probes never trigger a
82
83
  * network download merely by checking availability.
83
84
  *
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.
85
+ * The returned descriptor is the CLI base only, reported to the operator as
86
+ * `description`; the dedicated dsh-crew profile and the Crew-owned DSH_HOME
87
+ * are supplied by the install flow. The official ``web`` profile is never a
88
+ * target.
87
89
  */
88
- export function detectDsh({ allowDownload = false, home = homedir(), includeCompatibility = true } = {}) {
90
+ function detectDsh({ allowDownload = false, home = homedir(), includeCompatibility = true } = {}) {
89
91
  const resolved = resolveDshCli({
90
92
  home,
91
93
  allowDownload,
@@ -95,18 +97,6 @@ export function detectDsh({ allowDownload = false, home = homedir(), includeComp
95
97
  return { ...resolved, cli: resolved.command, description: describeDshCli(resolved) };
96
98
  }
97
99
 
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
100
  export function readPackageName(root = ROOT) {
111
101
  try { return JSON.parse(readFileSync(join(root, 'package.json'), 'utf8')).name; } catch { return null; }
112
102
  }
@@ -117,20 +107,11 @@ export function checkRoot(root = ROOT) {
117
107
  && existsSync(join(root, 'cordis.patch.yml'));
118
108
  }
119
109
 
120
- export function depsPresent(root = ROOT) {
110
+ function depsPresent(root = ROOT) {
121
111
  return existsSync(join(root, 'node_modules', '@modelcontextprotocol', 'sdk'))
122
112
  && existsSync(join(root, 'node_modules', '.bin', 'tsdown'));
123
113
  }
124
114
 
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
115
  function mark(log, ok, text) {
135
116
  log(`${ok ? '✓' : '✗'} ${text}`);
136
117
  return ok;
@@ -167,6 +148,8 @@ export async function setupInstall({
167
148
  home = homedir(),
168
149
  installer = realInstaller,
169
150
  depRunner = null,
151
+ ensureRuntime = null,
152
+ ensureWebProfile = null,
170
153
  } = {}) {
171
154
  log('DSH Crew installer');
172
155
  if (!checkRoot(root)) return { ok: false, error: `not a dsh-crew checkout: ${root}` };
@@ -217,8 +200,15 @@ export async function setupInstall({
217
200
  error: `Crew runtime ${dsh.version} does not match ${TARGET_DSH_VERSION}; use the transactional update path (dsh-crew update) to migrate the cohort`,
218
201
  };
219
202
  }
220
- if (!dryRun && (!dsh || dsh.kind === 'npx-local')) {
221
- const boot = ensureCrewDshRuntime({ home });
203
+ if (!dryRun && (!dsh || !dsh.reusable)) {
204
+ // The Windows launcher only starts a Crew-owned CLI entry: a global
205
+ // official dsh or a transient npx download cannot pass its preflight, so
206
+ // the launcher would report "DSH CLI was not found" on a machine that has
207
+ // a perfectly good global dsh. A machine without a reusable runtime
208
+ // provisions one here — before the launcher ever runs — and the web
209
+ // profile scaffold below completes what that preflight and the supervisor
210
+ // require. Idempotent: a matching reusable runtime is reused as-is.
211
+ const boot = (ensureRuntime ?? ensureCrewDshRuntime)({ home });
222
212
  if (boot.ok) dsh = boot.cli;
223
213
  else log(`- reusable Crew DSH runtime unavailable (${boot.code ?? 'unknown'}); trying compatibility fallback`);
224
214
  }
@@ -240,11 +230,27 @@ export async function setupInstall({
240
230
  return { ok: false, error: 'DSH profile link failed' };
241
231
  }
242
232
  mark(log, true, `DSH crew profile linked offline (dedicated Crew DSH_HOME, profile dsh-crew; ${registration.changed ? 'updated' : 'already current'})`);
233
+ // The launcher preflight and the supervisor both require
234
+ // <crew DSH_HOME>/profiles/web; without it a fresh install deadlocked at
235
+ // the preflight ("The Harness web profile is missing") and the supervisor
236
+ // heartbeat timed out. Same idempotent scaffold the runtime step creates
237
+ // on the npx-managed path.
238
+ const webProfile = (ensureWebProfile ?? ensureCrewWebProfile)({ home });
239
+ if (!webProfile.ok) {
240
+ log(`✗ Crew web profile bootstrap failed (${webProfile.code ?? 'unknown'})`);
241
+ return { ok: false, error: 'Crew web profile bootstrap failed' };
242
+ }
243
+ if (webProfile.created) mark(log, true, 'Crew web profile scaffold created');
243
244
  }
244
245
 
246
+ // The host integrations must be written against the same root readiness
247
+ // derives (integrationRoot) — see activateRelease. After registration the
248
+ // link resolves to this checkout, so this is the loader link path.
249
+ const integrationWriteRoot = integrationRoot({ home, root, name });
250
+
245
251
  if (dryRun) mark(log, true, 'Codex Desktop integration (dry-run)');
246
252
  else {
247
- const r = installer.installCodex ? installer.installCodex({ home, root }) : realInstaller.installCodex({ home, root });
253
+ const r = installer.installCodex ? installer.installCodex({ home, root: integrationWriteRoot }) : realInstaller.installCodex({ home, root: integrationWriteRoot });
248
254
  mark(log, r.ok !== false, r.ok === false ? `Codex Desktop integration failed: ${(r.actions ?? []).join('; ')}` : 'Codex Desktop integration');
249
255
  if (r.ok === false) return { ok: false, error: 'Codex integration failed' };
250
256
  }
@@ -252,8 +258,8 @@ export async function setupInstall({
252
258
  if (dryRun) mark(log, true, 'ZCode integration (dry-run)');
253
259
  else {
254
260
  const r = installer.installZCode
255
- ? installer.installZCode({ home, root })
256
- : realInstaller.installZCode({ home, root });
261
+ ? installer.installZCode({ home, root: integrationWriteRoot })
262
+ : realInstaller.installZCode({ home, root: integrationWriteRoot });
257
263
  if (r.ok === false) {
258
264
  mark(log, false, `ZCode integration failed (${r.code ?? 'unknown'})`);
259
265
  return { ok: false, error: 'ZCode integration failed' };
@@ -274,7 +280,7 @@ export async function setupInstall({
274
280
  }
275
281
 
276
282
  if (commandExists('claude')) {
277
- const r = await runClaudeIntegrationStep({ dryRun, log, root, home, installer });
283
+ const r = await runClaudeIntegrationStep({ dryRun, log, root: integrationWriteRoot, home, installer });
278
284
  if (!r.ok) return r;
279
285
  } else log('- Claude Code not detected, skipped');
280
286
 
@@ -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,8 +9,12 @@ 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
- const FULL = 'http://127.0.0.1:3210/';
12
+ // The control plane needs its own session, and this panel runs in a DIFFERENT
13
+ // harness process (the desktop app, or the 3080 frontend): its connection service
14
+ // cannot mint a token for 3210, so linking the bare port landed the operator on
15
+ // "dsh web authentication required". The hub's own endpoint mints the session and
16
+ // redirects, so no token is ever published to a page or a file.
17
+ const FULL = 'http://127.0.0.1:3210/_dsh/dsh-crew/control-plane';
14
18
 
15
19
  type ModelEntry = { provider: string; model: string };
16
20
  type QuickConfig = {
@@ -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(/^\|/, '')