@ran-sh/dsh-crew 1.5.1 → 1.6.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.
@@ -506,15 +506,6 @@ export function normalizeGlobalConfig(raw = {}) {
506
506
  else { flashState = flashState ?? 'auto'; proState = proState ?? 'auto'; }
507
507
  }
508
508
 
509
- // Capability switches: an old config with provider=off must not flip the
510
- // capability back on; anything else keeps the capability enabled.
511
- const visionEnabled = has('vision_enabled')
512
- ? normalizeEnabled(raw.vision_enabled)
513
- : raw.vision_provider !== 'off';
514
- const imagegenEnabled = has('imagegen_enabled')
515
- ? normalizeEnabled(raw.imagegen_enabled)
516
- : raw.imagegen_provider !== 'off';
517
-
518
509
  const normalized = {
519
510
  ...raw,
520
511
  subagents_enabled: normalizeEnabled(raw.subagents_enabled),
@@ -534,8 +525,6 @@ export function normalizeGlobalConfig(raw = {}) {
534
525
  pro_model_priority: normalizeModelPriority(raw.pro_model_priority),
535
526
  pro_model_priority_configured: raw.pro_model_priority_configured === true || normalizeModelPriority(raw.pro_model_priority).length > 0,
536
527
  pro_model_fallback: raw.pro_model_fallback === 'harness-default' ? raw.pro_model_fallback : 'harness-default',
537
- vision_enabled: visionEnabled,
538
- imagegen_enabled: imagegenEnabled,
539
528
  };
540
529
  // This normalized legacy view is the single input to the v0.2 canonical
541
530
  // migration, so every consumer sees the same worker/reviewer model policy
@@ -788,43 +777,4 @@ export function validateRoles(raw) {
788
777
  if (!seen.has(r)) { seen.add(r); out.push(r); }
789
778
  }
790
779
  return { roles: out, dropped };
791
- }
792
-
793
- // ---------- multimodal capability ----------
794
-
795
- /** Capability availability after both the switch and the provider are checked. */
796
- export function getCapabilities(config) {
797
- const c = config;
798
- const vision = {
799
- enabled: normalizeBool(c.vision_enabled, true),
800
- provider: c.vision_provider ?? 'off',
801
- providerOff: c.vision_provider === 'off',
802
- usable: normalizeBool(c.vision_enabled, true) && c.vision_provider !== 'off',
803
- };
804
- const imagegen = {
805
- enabled: normalizeBool(c.imagegen_enabled, true),
806
- provider: c.imagegen_provider ?? 'off',
807
- providerOff: c.imagegen_provider === 'off',
808
- usable: normalizeBool(c.imagegen_enabled, true) && c.imagegen_provider !== 'off',
809
- };
810
- return { vision, imagegen };
811
- }
812
-
813
- /**
814
- * Registration plan for the hub's multimodal bridge: which tools to register
815
- * and whether the vision route (deepseek-vision adapter + transcription
816
- * waterfall) should be installed. Tool names are the decision points; the
817
- * hub applies the plan at plugin boot, so capability switches take effect
818
- * after a DSH restart.
819
- */
820
- export function getMultimodalRegistrationPlan(config) {
821
- const { vision, imagegen } = getCapabilities(config);
822
- return {
823
- tools: {
824
- describe_image: vision.usable,
825
- generate_image: imagegen.usable,
826
- },
827
- visionRoute: vision.usable,
828
- requiresRestart: true,
829
- };
830
- }
780
+ }
@@ -92,7 +92,7 @@ function modelIds(provider) {
92
92
  * Build a secret-free provider inventory from live Harness and Crew-owned
93
93
  * observations. No credential value is accepted or copied into the result.
94
94
  */
95
- export function buildProviderInventory({ catalog = {}, declarations = [], policy = {}, tombstones = {}, activeJobs = [], multimodalRefs = {}, additionalProviderIds = [] } = {}) {
95
+ export function buildProviderInventory({ catalog = {}, declarations = [], policy = {}, tombstones = {}, activeJobs = [], additionalProviderIds = [] } = {}) {
96
96
  const catalogProviders = Array.isArray(catalog?.providers) ? catalog.providers : [];
97
97
  const safeDeclarations = Array.isArray(declarations) ? declarations : [];
98
98
  const ids = [];
@@ -121,7 +121,6 @@ export function buildProviderInventory({ catalog = {}, declarations = [], policy
121
121
  ? activeJobs.filter((job) => normalizeProviderId(job?.provider) === id
122
122
  && !['done', 'failed', 'cancelled'].includes(job?.status)).length
123
123
  : Number.isInteger(activeJobs?.[id]) ? Math.max(0, activeJobs[id]) : 0;
124
- const multimodal = Array.isArray(multimodalRefs?.[id]) ? multimodalRefs[id].length : Number.isInteger(multimodalRefs?.[id]) ? Math.max(0, multimodalRefs[id]) : 0;
125
124
  const declarationFile = text(declaration?.file);
126
125
  const locator = text(declaration?.locator);
127
126
 
@@ -173,7 +172,6 @@ export function buildProviderInventory({ catalog = {}, declarations = [], policy
173
172
  reviewer_priority: findPriorityIndex(policy?.reviewer ?? policy?.review, 'priority', id),
174
173
  reviewer_escalation: findPriorityIndex(policy?.reviewer ?? policy?.review, 'escalation_priority', id),
175
174
  active_jobs: active,
176
- multimodal_refs: multimodal,
177
175
  },
178
176
  desired_state: tombstoned ? 'absent' : 'present',
179
177
  activation: declared ? 'restart-required' : 'live',
@@ -41,9 +41,6 @@ export const RUNTIME_SETTING_ACTIVATION = Object.freeze({
41
41
  pro_model_fallback: Object.freeze({ global: N, session: null }),
42
42
  adaptive_routing: Object.freeze({ global: N, session: null, note: 'Opt-in provider health ordering is read for the next Hub model selection; existing explicit priorities remain authoritative.' }),
43
43
  isolation: Object.freeze({ global: N, session: null }),
44
- vision_provider: Object.freeze({ global: N, session: null }),
45
- vision_model: Object.freeze({ global: N, session: null }),
46
- imagegen_provider: Object.freeze({ global: N, session: null }),
47
44
 
48
45
  // These values are copied into the MCP session defaults at process startup.
49
46
  // dsh_worker_config can override them for subsequent workflows in that same
@@ -56,11 +53,8 @@ export const RUNTIME_SETTING_ACTIVATION = Object.freeze({
56
53
  preset_flash: Object.freeze({ global: S, session: N }),
57
54
  preset_pro: Object.freeze({ global: S, session: N }),
58
55
 
59
- // hub-client freezes its base URL at module initialization. Crew Vision and
60
- // Image Generation tool registration is also decided at Hub plugin boot.
56
+ // hub-client freezes its base URL at module initialization.
61
57
  hub_url: Object.freeze({ global: R, session: null }),
62
- vision_enabled: Object.freeze({ global: R, session: null }),
63
- imagegen_enabled: Object.freeze({ global: R, session: null }),
64
58
  });
65
59
 
66
60
  export function activationForSetting(key) {
@@ -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.5.1';
39
+ export const RUNTIME_VERSION = '1.6.1';
40
40
  export const HUB_PROTOCOL_VERSION = 1;
41
41
 
42
42
  export const HUB_CAPABILITIES = Object.freeze([
package/src/server.mjs CHANGED
@@ -305,10 +305,9 @@ const SAFE_GLOBAL_KEYS = [
305
305
  'default_tier', 'default_effort', 'mode', 'default_timeout_seconds', 'hub_url',
306
306
  'tier_policy', 'escalate_on_failure', 'subagents_enabled', 'collaboration_mode',
307
307
  'main_agent_mode', 'flash_state', 'pro_state', 'flash_roles', 'pro_roles',
308
- 'pro_reviews_flash', 'worker_provider_mode', 'vision_enabled', 'imagegen_enabled',
308
+ 'pro_reviews_flash', 'worker_provider_mode',
309
309
  'flash_model_priority', 'flash_model_priority_configured', 'flash_model_fallback',
310
310
  'pro_model_priority', 'pro_model_priority_configured', 'pro_model_fallback',
311
- 'vision_provider', 'vision_model', 'imagegen_provider',
312
311
  'preset_flash', 'preset_pro',
313
312
  ];
314
313
 
@@ -1,71 +0,0 @@
1
- import { mkdtempSync, mkdirSync, lstatSync, openSync, readSync, closeSync, renameSync, rmSync } from 'node:fs';
2
- import { basename, dirname, isAbsolute, join } from 'node:path';
3
-
4
- export async function downloadImageBytes(url, { fetchImpl = globalThis.fetch, timeoutMs = 60_000, maxBytes = 64 * 1024 * 1024 } = {}) {
5
- if (!['http:', 'https:'].includes(new URL(url).protocol)) throw new Error('unsupported image URL protocol');
6
- const controller = new AbortController();
7
- let reader;
8
- let timer;
9
- const timeout = new Promise((_, reject) => {
10
- timer = setTimeout(() => {
11
- controller.abort();
12
- reject(new Error('image download timed out'));
13
- }, timeoutMs);
14
- });
15
- const download = async () => {
16
- const response = await fetchImpl(url, { signal: controller.signal });
17
- controller.signal.throwIfAborted();
18
- if (!response.ok) throw new Error(`image download failed: HTTP ${response.status}`);
19
- if (Number(response.headers.get('content-length')) > maxBytes) throw new Error('image download exceeds size limit');
20
- if (!response.body) throw new Error('image download has no body');
21
- reader = response.body.getReader();
22
- const chunks = [];
23
- let bytes = 0;
24
- while (true) {
25
- const { done, value } = await reader.read();
26
- controller.signal.throwIfAborted();
27
- if (done) break;
28
- bytes += value.byteLength;
29
- if (bytes > maxBytes) throw new Error('image download exceeds size limit');
30
- chunks.push(Buffer.from(value));
31
- }
32
- return Buffer.concat(chunks, bytes);
33
- };
34
- try { return await Promise.race([download(), timeout]); }
35
- finally {
36
- clearTimeout(timer);
37
- controller.abort();
38
- if (reader) void reader.cancel().catch(() => {});
39
- }
40
- }
41
-
42
- function validateImage(file) {
43
- const info = lstatSync(file);
44
- if (!info.isFile() || info.size === 0 || info.size > 64 * 1024 * 1024) throw new Error('generated image has invalid type or size');
45
- const head = Buffer.alloc(32);
46
- const fd = openSync(file, 'r');
47
- let length;
48
- try { length = readSync(fd, head, 0, head.length, 0); } finally { closeSync(fd); }
49
- const bytes = head.subarray(0, length);
50
- const png = bytes.subarray(0, 8).equals(Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]));
51
- const jpeg = bytes.length >= 3 && bytes[0] === 255 && bytes[1] === 216 && bytes[2] === 255;
52
- const gif = ['GIF87a', 'GIF89a'].includes(bytes.subarray(0, 6).toString('ascii'));
53
- const webp = bytes.subarray(0, 4).toString('ascii') === 'RIFF' && bytes.subarray(8, 12).toString('ascii') === 'WEBP';
54
- if (!png && !jpeg && !gif && !webp) throw new Error('generated output does not have a supported image signature');
55
- }
56
-
57
- /** Stage each request separately; an existing destination never proves success. */
58
- export async function withGeneratedImageOutput(outputPath, generate) {
59
- if (!isAbsolute(outputPath)) throw new Error('output_path must be absolute');
60
- mkdirSync(dirname(outputPath), { recursive: true });
61
- const stage = mkdtempSync(join(dirname(outputPath), '.dsh-image-'));
62
- const stagedFile = join(stage, basename(outputPath));
63
- try {
64
- await generate(stagedFile);
65
- validateImage(stagedFile);
66
- renameSync(stagedFile, outputPath);
67
- return outputPath;
68
- } finally {
69
- rmSync(stage, { recursive: true, force: true });
70
- }
71
- }