@ran-sh/dsh-crew 0.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/marketplace.json +17 -0
- package/.claude-plugin/plugin.json +8 -0
- package/.mcp.json +8 -0
- package/LICENSE +21 -0
- package/README.de.md +359 -0
- package/README.es.md +359 -0
- package/README.fr.md +359 -0
- package/README.hi.md +359 -0
- package/README.id.md +359 -0
- package/README.ja.md +359 -0
- package/README.ko.md +359 -0
- package/README.md +360 -0
- package/README.pt.md +359 -0
- package/README.ru.md +359 -0
- package/README.th.md +359 -0
- package/README.tr.md +359 -0
- package/README.vi.md +359 -0
- package/README.zh-TW.md +359 -0
- package/README.zh.md +305 -0
- package/agents/ds-flash.md +26 -0
- package/agents/ds-pro.md +32 -0
- package/agents/ds-reviewer.md +23 -0
- package/agents/ds-worker.md +22 -0
- package/codex/agents/ds-flash.toml +30 -0
- package/codex/agents/ds-pro.toml +31 -0
- package/codex/agents/ds-reviewer.toml +28 -0
- package/codex/agents/ds-worker.toml +28 -0
- package/codex/prompts/dsh-config.md +3 -0
- package/codex/prompts/dsh-status.md +1 -0
- package/commands/config.md +11 -0
- package/commands/off.md +5 -0
- package/commands/on.md +5 -0
- package/commands/status.md +5 -0
- package/cordis.patch.yml +4 -0
- package/docs/images/dsh-crew-host.png +0 -0
- package/docs/images/dsh-crew-jobs.png +0 -0
- package/docs/images/dsh-crew-logo.png +0 -0
- package/docs/images/dsh-crew-overview.png +0 -0
- package/lib/client.js +2765 -0
- package/package.json +125 -0
- package/scripts/build-client.mjs +28 -0
- package/scripts/live-crew-smoke.mjs +39 -0
- package/scripts/live-policy-matrix.mjs +177 -0
- package/scripts/policy-probe.mjs +101 -0
- package/scripts/setup.mjs +294 -0
- package/scripts/smoke-real.mjs +110 -0
- package/scripts/smoke.mjs +78 -0
- package/scripts/verify-installer-fix.mjs +26 -0
- package/src/adaptive-routing.mjs +260 -0
- package/src/client/activation-summary.tsx +64 -0
- package/src/client/entry.tsx +236 -0
- package/src/client/index.tsx +1120 -0
- package/src/config-readiness.mjs +59 -0
- package/src/delivery.mjs +205 -0
- package/src/dsh-cli-runtime.mjs +251 -0
- package/src/failure-classification.mjs +172 -0
- package/src/hub/entry.mjs +98 -0
- package/src/hub/index.mjs +757 -0
- package/src/hub-client.mjs +132 -0
- package/src/hub-compatibility.mjs +49 -0
- package/src/i18n.mjs +19 -0
- package/src/install/cli.mjs +28 -0
- package/src/install/install-legacy.mjs +460 -0
- package/src/install/install.mjs +451 -0
- package/src/jobs.mjs +275 -0
- package/src/mcp-runtime.mjs +257 -0
- package/src/model-catalog.mjs +173 -0
- package/src/model-routing.mjs +391 -0
- package/src/multimodal.mjs +0 -0
- package/src/policy-legacy.mjs +830 -0
- package/src/policy.mjs +197 -0
- package/src/readiness-matrix.mjs +169 -0
- package/src/runtime-controls.mjs +90 -0
- package/src/runtime-identity.mjs +108 -0
- package/src/server.mjs +477 -0
- package/src/status-shard.mjs +52 -0
- package/src/structured-error-code.mjs +39 -0
- package/src/vision-route.mjs +138 -0
- package/src/workflow-runtime.mjs +567 -0
- package/src/workflow.mjs +160 -0
- package/src/workspace-audit.mjs +231 -0
- package/src/workspace-isolation.mjs +306 -0
- package/statusline/statusline.sh +14 -0
- package/statusline/worker-segment.sh +35 -0
- package/worker.cordis.yml +77 -0
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
// Read-only adapter from the DSH LLM registry to a credential-free Settings
|
|
2
|
+
// payload. Provider/model discovery stays owned by Harness; Crew only reads
|
|
3
|
+
// its registered routes and advertised catalog.
|
|
4
|
+
|
|
5
|
+
export const MODEL_CATALOG_UNAVAILABLE = 'MODEL_CATALOG_UNAVAILABLE';
|
|
6
|
+
|
|
7
|
+
export const CATALOG_HEALTH_CODES = Object.freeze({
|
|
8
|
+
EMPTY_CATALOG: 'EMPTY_CATALOG',
|
|
9
|
+
EMPTY_PROVIDER_MODELS: 'EMPTY_PROVIDER_MODELS',
|
|
10
|
+
PROVIDER_MODEL_LIST_FAILED: 'PROVIDER_MODEL_LIST_FAILED',
|
|
11
|
+
HARNESS_DEFAULT_PROVIDER_MISSING: 'HARNESS_DEFAULT_PROVIDER_MISSING',
|
|
12
|
+
HARNESS_DEFAULT_MODEL_UNADVERTISED: 'HARNESS_DEFAULT_MODEL_UNADVERTISED',
|
|
13
|
+
CATALOG_CONSTRAINED: 'CATALOG_CONSTRAINED',
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
function catalogError() {
|
|
17
|
+
return Object.assign(new Error('Unable to read Harness model catalog.'), { code: MODEL_CATALOG_UNAVAILABLE });
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function normalizeProvider(raw) {
|
|
21
|
+
if (!raw || typeof raw.id !== 'string' || raw.id.trim() === '') return null;
|
|
22
|
+
const id = raw.id.trim();
|
|
23
|
+
return { id, name: typeof raw.name === 'string' && raw.name.trim() ? raw.name.trim() : id };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function normalizeModels(raw, provider) {
|
|
27
|
+
if (!Array.isArray(raw)) return [];
|
|
28
|
+
const seen = new Set();
|
|
29
|
+
const result = [];
|
|
30
|
+
for (const item of raw) {
|
|
31
|
+
if (!item || typeof item.id !== 'string' || item.id.trim() === '') continue;
|
|
32
|
+
const id = item.id.trim();
|
|
33
|
+
if (seen.has(id)) continue;
|
|
34
|
+
seen.add(id);
|
|
35
|
+
result.push({
|
|
36
|
+
id,
|
|
37
|
+
name: typeof item.name === 'string' && item.name.trim() ? item.name.trim() : id,
|
|
38
|
+
...(typeof item.description === 'string' && item.description.trim() ? { description: item.description.trim() } : {}),
|
|
39
|
+
...(Array.isArray(item.inputModalities) ? { inputModalities: item.inputModalities.filter((v) => v === 'text' || v === 'image') } : {}),
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
return result;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function safeDefault(getCurrentSelection) {
|
|
46
|
+
try {
|
|
47
|
+
const raw = typeof getCurrentSelection === 'function' ? getCurrentSelection() : undefined;
|
|
48
|
+
if (!raw || typeof raw.provider !== 'string' || typeof raw.model !== 'string') return null;
|
|
49
|
+
return {
|
|
50
|
+
provider: raw.provider,
|
|
51
|
+
model: raw.model,
|
|
52
|
+
...(typeof raw.reasoningEffort === 'string' ? { reasoningEffort: raw.reasoningEffort } : {}),
|
|
53
|
+
};
|
|
54
|
+
} catch {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function healthHint(code, level, { provider, model, signal } = {}) {
|
|
60
|
+
return {
|
|
61
|
+
code,
|
|
62
|
+
level,
|
|
63
|
+
...(typeof provider === 'string' && provider ? { provider } : {}),
|
|
64
|
+
...(typeof model === 'string' && model ? { model } : {}),
|
|
65
|
+
...(typeof signal === 'string' && signal ? { signal } : {}),
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Analyze only the effective, already-sanitized Harness catalog.
|
|
71
|
+
*
|
|
72
|
+
* This intentionally does not know an "expected" third-party catalog. A
|
|
73
|
+
* narrow advertised list is therefore informational only: it can point a user
|
|
74
|
+
* toward upstream/provider configuration, but it is never proof of a bad
|
|
75
|
+
* override and never changes routing eligibility.
|
|
76
|
+
*/
|
|
77
|
+
export function analyzeCatalogHealth({ providers = [], harnessDefault = null } = {}) {
|
|
78
|
+
const safeProviders = Array.isArray(providers) ? providers : [];
|
|
79
|
+
const hints = [];
|
|
80
|
+
|
|
81
|
+
if (safeProviders.length === 0) {
|
|
82
|
+
hints.push(healthHint(CATALOG_HEALTH_CODES.EMPTY_CATALOG, 'warning'));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
for (const provider of safeProviders) {
|
|
86
|
+
const providerId = typeof provider?.id === 'string' ? provider.id : null;
|
|
87
|
+
const models = Array.isArray(provider?.models) ? provider.models : [];
|
|
88
|
+
if (provider?.error === 'MODEL_LIST_FAILED') {
|
|
89
|
+
hints.push(healthHint(CATALOG_HEALTH_CODES.PROVIDER_MODEL_LIST_FAILED, 'warning', { provider: providerId }));
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
if (models.length === 0) {
|
|
93
|
+
// Some providers intentionally accept exact model ids without advertising
|
|
94
|
+
// them. Keep this informational rather than turning it into a failure.
|
|
95
|
+
hints.push(healthHint(CATALOG_HEALTH_CODES.EMPTY_PROVIDER_MODELS, 'info', { provider: providerId }));
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (harnessDefault && typeof harnessDefault.provider === 'string' && typeof harnessDefault.model === 'string') {
|
|
100
|
+
const defaultProvider = safeProviders.find((provider) => provider?.id === harnessDefault.provider);
|
|
101
|
+
if (!defaultProvider) {
|
|
102
|
+
hints.push(healthHint(CATALOG_HEALTH_CODES.HARNESS_DEFAULT_PROVIDER_MISSING, 'warning', {
|
|
103
|
+
provider: harnessDefault.provider,
|
|
104
|
+
model: harnessDefault.model,
|
|
105
|
+
}));
|
|
106
|
+
} else if (defaultProvider.error !== 'MODEL_LIST_FAILED' && Array.isArray(defaultProvider.models) && defaultProvider.models.length > 0) {
|
|
107
|
+
const advertised = defaultProvider.models.some((model) => model?.id === harnessDefault.model);
|
|
108
|
+
if (!advertised) {
|
|
109
|
+
hints.push(healthHint(CATALOG_HEALTH_CODES.HARNESS_DEFAULT_MODEL_UNADVERTISED, 'info', {
|
|
110
|
+
provider: harnessDefault.provider,
|
|
111
|
+
model: harnessDefault.model,
|
|
112
|
+
}));
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// A single provider advertising only one or two explicit models is a useful
|
|
118
|
+
// signal because local/provider overrides can produce this shape. It is only
|
|
119
|
+
// an INFO heuristic: legitimate providers can also have intentionally small
|
|
120
|
+
// catalogs, so Crew must not claim an override or route around it.
|
|
121
|
+
const healthyProviders = safeProviders.filter((provider) => provider?.error !== 'MODEL_LIST_FAILED');
|
|
122
|
+
const totalAdvertised = healthyProviders.reduce(
|
|
123
|
+
(sum, provider) => sum + (Array.isArray(provider?.models) ? provider.models.length : 0),
|
|
124
|
+
0,
|
|
125
|
+
);
|
|
126
|
+
if (healthyProviders.length === 1 && totalAdvertised > 0 && totalAdvertised <= 2) {
|
|
127
|
+
hints.push(healthHint(CATALOG_HEALTH_CODES.CATALOG_CONSTRAINED, 'info', {
|
|
128
|
+
provider: healthyProviders[0].id,
|
|
129
|
+
signal: 'single-provider-small-explicit-catalog',
|
|
130
|
+
}));
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const warningCount = hints.filter((hint) => hint.level === 'warning').length;
|
|
134
|
+
const infoCount = hints.filter((hint) => hint.level === 'info').length;
|
|
135
|
+
return {
|
|
136
|
+
schema_version: 1,
|
|
137
|
+
warning_count: warningCount,
|
|
138
|
+
info_count: infoCount,
|
|
139
|
+
attention: warningCount > 0,
|
|
140
|
+
hints,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export async function readHarnessModelCatalog({ llm, getCurrentSelection, now = () => new Date().toISOString() } = {}) {
|
|
145
|
+
if (!llm || typeof llm.listProviders !== 'function' || typeof llm.listModels !== 'function') throw catalogError();
|
|
146
|
+
let rawProviders;
|
|
147
|
+
try {
|
|
148
|
+
rawProviders = llm.listProviders();
|
|
149
|
+
} catch {
|
|
150
|
+
throw catalogError();
|
|
151
|
+
}
|
|
152
|
+
if (!Array.isArray(rawProviders)) throw catalogError();
|
|
153
|
+
const providers = rawProviders.map(normalizeProvider).filter(Boolean);
|
|
154
|
+
let failed = 0;
|
|
155
|
+
const hydrated = await Promise.all(providers.map(async (provider) => {
|
|
156
|
+
try {
|
|
157
|
+
return { ...provider, models: normalizeModels(await llm.listModels(provider.id), provider.id) };
|
|
158
|
+
} catch {
|
|
159
|
+
failed += 1;
|
|
160
|
+
return { ...provider, models: [], error: 'MODEL_LIST_FAILED' };
|
|
161
|
+
}
|
|
162
|
+
}));
|
|
163
|
+
const harnessDefault = safeDefault(getCurrentSelection);
|
|
164
|
+
return {
|
|
165
|
+
providers: hydrated,
|
|
166
|
+
provider_count: hydrated.length,
|
|
167
|
+
model_count: hydrated.reduce((sum, provider) => sum + provider.models.length, 0),
|
|
168
|
+
harness_default: harnessDefault,
|
|
169
|
+
refreshed_at: now(),
|
|
170
|
+
partial: failed > 0,
|
|
171
|
+
health: analyzeCatalogHealth({ providers: hydrated, harnessDefault }),
|
|
172
|
+
};
|
|
173
|
+
}
|
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
// Pure Harness-backed worker model selection. A worker tier describes a role,
|
|
2
|
+
// not a fixed model: explicit provider/model priorities win, fresh configs may
|
|
3
|
+
// use a tier-specific preferred model id, and Harness Default is the final
|
|
4
|
+
// fallback. Catalog membership is advisory; provider registration is the
|
|
5
|
+
// routing boundary.
|
|
6
|
+
|
|
7
|
+
import { rankAdaptiveCandidates } from './adaptive-routing.mjs';
|
|
8
|
+
|
|
9
|
+
export const DEFAULT_TIER_MODEL_PREFERENCES = Object.freeze({
|
|
10
|
+
flash: 'deepseek-v4-flash',
|
|
11
|
+
pro: 'deepseek-v4-pro',
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
// v0.2 role → default preferred model class. A role describes who does the
|
|
15
|
+
// work; the model class is only the fresh-config recommendation until a
|
|
16
|
+
// priority list or Harness Default takes over.
|
|
17
|
+
export const DEFAULT_ROLE_MODEL_PREFERENCES = Object.freeze({
|
|
18
|
+
worker: 'deepseek-v4-flash',
|
|
19
|
+
reviewer: 'deepseek-v4-pro',
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
export const MODEL_FALLBACKS = ['harness-default'];
|
|
23
|
+
export const NO_WORKER_MODEL_AVAILABLE = 'NO_WORKER_MODEL_AVAILABLE';
|
|
24
|
+
export const MODEL_SELECTION_TRACE_VERSION = 1;
|
|
25
|
+
export const MODEL_SELECTION_REASON_CODES = Object.freeze({
|
|
26
|
+
PROVIDER_UNAVAILABLE: 'PROVIDER_UNAVAILABLE',
|
|
27
|
+
PREFERRED_MODEL_UNAVAILABLE: 'PREFERRED_MODEL_UNAVAILABLE',
|
|
28
|
+
PREFERRED_MODEL_AMBIGUOUS: 'PREFERRED_MODEL_AMBIGUOUS',
|
|
29
|
+
ADAPTIVE_DEPRIORITIZED: 'ADAPTIVE_DEPRIORITIZED',
|
|
30
|
+
HARNESS_DEFAULT_INVALID: 'HARNESS_DEFAULT_INVALID',
|
|
31
|
+
HARNESS_DEFAULT_PROVIDER_UNAVAILABLE: 'HARNESS_DEFAULT_PROVIDER_UNAVAILABLE',
|
|
32
|
+
PRIMARY_CANDIDATES_EXHAUSTED: 'PRIMARY_CANDIDATES_EXHAUSTED',
|
|
33
|
+
ESCALATION_CANDIDATES_EXHAUSTED: 'ESCALATION_CANDIDATES_EXHAUSTED',
|
|
34
|
+
NO_AVAILABLE_MODEL: 'NO_AVAILABLE_MODEL',
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
export function normalizeModelRef(raw) {
|
|
38
|
+
if (!raw || typeof raw !== 'object') return null;
|
|
39
|
+
const provider = typeof raw.provider === 'string' ? raw.provider.trim() : '';
|
|
40
|
+
const model = typeof raw.model === 'string' ? raw.model.trim() : '';
|
|
41
|
+
return provider && model ? { provider, model } : null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function modelRefKey(raw) {
|
|
45
|
+
const ref = normalizeModelRef(raw);
|
|
46
|
+
return ref ? `${ref.provider}\0${ref.model}` : '';
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function normalizeModelPriority(raw) {
|
|
50
|
+
if (!Array.isArray(raw)) return [];
|
|
51
|
+
const seen = new Set();
|
|
52
|
+
const result = [];
|
|
53
|
+
for (const value of raw) {
|
|
54
|
+
const ref = normalizeModelRef(value);
|
|
55
|
+
if (!ref) continue;
|
|
56
|
+
const key = modelRefKey(ref);
|
|
57
|
+
if (seen.has(key)) continue;
|
|
58
|
+
seen.add(key);
|
|
59
|
+
result.push(ref);
|
|
60
|
+
}
|
|
61
|
+
return result;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function providerMap(catalog) {
|
|
65
|
+
const map = new Map();
|
|
66
|
+
for (const raw of catalog?.providers ?? []) {
|
|
67
|
+
if (!raw || typeof raw.id !== 'string' || raw.id === '') continue;
|
|
68
|
+
map.set(raw.id, raw);
|
|
69
|
+
}
|
|
70
|
+
return map;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function normalizeAttempt(value) {
|
|
74
|
+
return Number.isInteger(value) && value >= 0 ? value : 0;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function normalizeModelClassHint(value) {
|
|
78
|
+
return value === 'flash' || value === 'pro' ? value : null;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function traceBase({
|
|
82
|
+
role = 'worker',
|
|
83
|
+
logicalAttempt = 0,
|
|
84
|
+
modelClassHint = null,
|
|
85
|
+
strategy = 'legacy-tier',
|
|
86
|
+
candidateSet = 'primary',
|
|
87
|
+
escalationReason = null,
|
|
88
|
+
} = {}) {
|
|
89
|
+
return {
|
|
90
|
+
version: MODEL_SELECTION_TRACE_VERSION,
|
|
91
|
+
role: role === 'reviewer' ? 'reviewer' : 'worker',
|
|
92
|
+
logical_attempt: normalizeAttempt(logicalAttempt),
|
|
93
|
+
model_class_hint: normalizeModelClassHint(modelClassHint),
|
|
94
|
+
strategy: typeof strategy === 'string' && strategy ? strategy : 'legacy-tier',
|
|
95
|
+
candidate_set: typeof candidateSet === 'string' && candidateSet ? candidateSet : 'primary',
|
|
96
|
+
ordered_candidates: [],
|
|
97
|
+
selected: null,
|
|
98
|
+
selection_source: null,
|
|
99
|
+
fallback_reason: null,
|
|
100
|
+
escalation_reason: typeof escalationReason === 'string' && escalationReason ? escalationReason : null,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function candidateDecision(ref, source, status, { reasonCode, advertised } = {}) {
|
|
105
|
+
return {
|
|
106
|
+
provider: ref?.provider ?? null,
|
|
107
|
+
model: ref?.model ?? null,
|
|
108
|
+
source,
|
|
109
|
+
status,
|
|
110
|
+
...(reasonCode ? { reason_code: reasonCode } : {}),
|
|
111
|
+
...(advertised === false ? { advertised: false } : {}),
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function selectTrace(trace, ref, source, { advertised, fallbackReason } = {}) {
|
|
116
|
+
trace.ordered_candidates.push(candidateDecision(ref, source, 'selected', { advertised }));
|
|
117
|
+
trace.selected = { provider: ref.provider, model: ref.model, source };
|
|
118
|
+
trace.selection_source = source;
|
|
119
|
+
trace.fallback_reason = fallbackReason ?? null;
|
|
120
|
+
return trace;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function rankForTrace(trace, candidates, {
|
|
124
|
+
adaptive,
|
|
125
|
+
adaptiveHealth,
|
|
126
|
+
explicitPriority = false,
|
|
127
|
+
} = {}) {
|
|
128
|
+
const ranked = rankAdaptiveCandidates(candidates, {
|
|
129
|
+
config: adaptive,
|
|
130
|
+
healthStore: adaptiveHealth,
|
|
131
|
+
role: trace.role,
|
|
132
|
+
explicitPriority,
|
|
133
|
+
});
|
|
134
|
+
if (ranked.trace.enabled) trace.adaptive = ranked.trace;
|
|
135
|
+
return ranked;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Build a one-candidate trace for transports that intentionally bypass the
|
|
140
|
+
* Harness catalog (DeepSeek Official strict mode / standalone legacy mode).
|
|
141
|
+
*/
|
|
142
|
+
export function buildDirectSelectionTrace({
|
|
143
|
+
role = 'worker',
|
|
144
|
+
logicalAttempt = 0,
|
|
145
|
+
modelClassHint = null,
|
|
146
|
+
strategy = 'legacy-strict',
|
|
147
|
+
candidateSet = 'primary',
|
|
148
|
+
provider,
|
|
149
|
+
model,
|
|
150
|
+
source = 'legacy-strict',
|
|
151
|
+
escalationReason = null,
|
|
152
|
+
} = {}) {
|
|
153
|
+
const trace = traceBase({ role, logicalAttempt, modelClassHint, strategy, candidateSet, escalationReason });
|
|
154
|
+
const ref = normalizeModelRef({ provider, model });
|
|
155
|
+
if (!ref) {
|
|
156
|
+
trace.fallback_reason = MODEL_SELECTION_REASON_CODES.NO_AVAILABLE_MODEL;
|
|
157
|
+
return trace;
|
|
158
|
+
}
|
|
159
|
+
return selectTrace(trace, ref, source);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Add workflow-only context after a transport has resolved the model. This is
|
|
164
|
+
* intentionally metadata-only: it never changes provider/model selection.
|
|
165
|
+
*/
|
|
166
|
+
export function enrichSelectionTrace(trace, {
|
|
167
|
+
role,
|
|
168
|
+
logicalAttempt,
|
|
169
|
+
modelClassHint,
|
|
170
|
+
escalationReason,
|
|
171
|
+
} = {}) {
|
|
172
|
+
const base = trace && typeof trace === 'object'
|
|
173
|
+
? {
|
|
174
|
+
...trace,
|
|
175
|
+
ordered_candidates: Array.isArray(trace.ordered_candidates)
|
|
176
|
+
? trace.ordered_candidates.map((item) => ({ ...item }))
|
|
177
|
+
: [],
|
|
178
|
+
selected: trace.selected && typeof trace.selected === 'object' ? { ...trace.selected } : null,
|
|
179
|
+
...(trace.adaptive && typeof trace.adaptive === 'object'
|
|
180
|
+
? {
|
|
181
|
+
adaptive: {
|
|
182
|
+
...trace.adaptive,
|
|
183
|
+
candidates: Array.isArray(trace.adaptive.candidates)
|
|
184
|
+
? trace.adaptive.candidates.map((item) => ({ ...item }))
|
|
185
|
+
: [],
|
|
186
|
+
},
|
|
187
|
+
}
|
|
188
|
+
: {}),
|
|
189
|
+
}
|
|
190
|
+
: traceBase({ role, logicalAttempt, modelClassHint, escalationReason });
|
|
191
|
+
if (role === 'worker' || role === 'reviewer') base.role = role;
|
|
192
|
+
if (Number.isInteger(logicalAttempt) && logicalAttempt >= 0) base.logical_attempt = logicalAttempt;
|
|
193
|
+
if (modelClassHint === 'flash' || modelClassHint === 'pro' || modelClassHint === null) {
|
|
194
|
+
base.model_class_hint = modelClassHint;
|
|
195
|
+
}
|
|
196
|
+
if (typeof escalationReason === 'string' && escalationReason) base.escalation_reason = escalationReason;
|
|
197
|
+
else if (logicalAttempt === 0) base.escalation_reason = null;
|
|
198
|
+
return base;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export function resolveWorkerModel({
|
|
202
|
+
tier,
|
|
203
|
+
priority,
|
|
204
|
+
priorityConfigured = false,
|
|
205
|
+
catalog,
|
|
206
|
+
harnessDefault,
|
|
207
|
+
fallback = 'harness-default',
|
|
208
|
+
preferredModelId = DEFAULT_TIER_MODEL_PREFERENCES[tier],
|
|
209
|
+
traceContext = {},
|
|
210
|
+
adaptive,
|
|
211
|
+
adaptiveHealth,
|
|
212
|
+
} = {}) {
|
|
213
|
+
const providers = providerMap(catalog);
|
|
214
|
+
const normalizedPriority = normalizeModelPriority(priority);
|
|
215
|
+
const prioritySource = typeof traceContext.prioritySource === 'string' && traceContext.prioritySource
|
|
216
|
+
? traceContext.prioritySource
|
|
217
|
+
: 'priority';
|
|
218
|
+
const trace = traceBase({
|
|
219
|
+
role: traceContext.role ?? 'worker',
|
|
220
|
+
logicalAttempt: traceContext.logicalAttempt ?? 0,
|
|
221
|
+
modelClassHint: traceContext.modelClassHint ?? null,
|
|
222
|
+
strategy: traceContext.strategy ?? 'legacy-tier',
|
|
223
|
+
candidateSet: traceContext.candidateSet ?? 'primary',
|
|
224
|
+
escalationReason: traceContext.escalationReason ?? null,
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
// Explicit priority — including an intentionally empty configured list — is
|
|
228
|
+
// authoritative. Adaptive mode records that bypass but never reorders it.
|
|
229
|
+
if (priorityConfigured || normalizedPriority.length > 0) {
|
|
230
|
+
rankForTrace(trace, normalizedPriority, { adaptive, adaptiveHealth, explicitPriority: true });
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
for (let index = 0; index < normalizedPriority.length; index++) {
|
|
234
|
+
const ref = normalizedPriority[index];
|
|
235
|
+
const provider = providers.get(ref.provider);
|
|
236
|
+
if (!provider) {
|
|
237
|
+
trace.ordered_candidates.push(candidateDecision(ref, prioritySource, 'skipped', {
|
|
238
|
+
reasonCode: MODEL_SELECTION_REASON_CODES.PROVIDER_UNAVAILABLE,
|
|
239
|
+
}));
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
const advertised = (provider.models ?? []).some((model) => model?.id === ref.model);
|
|
243
|
+
selectTrace(trace, ref, prioritySource, { advertised });
|
|
244
|
+
return {
|
|
245
|
+
ok: true,
|
|
246
|
+
...ref,
|
|
247
|
+
source: 'priority',
|
|
248
|
+
matchedPriorityIndex: index,
|
|
249
|
+
...(advertised ? {} : { advertised: false }),
|
|
250
|
+
selection_trace: trace,
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// A manually managed list, including an intentionally empty list, replaces
|
|
255
|
+
// the fresh-config recommendation rather than silently re-inserting it.
|
|
256
|
+
if (!priorityConfigured && normalizedPriority.length === 0 && typeof preferredModelId === 'string') {
|
|
257
|
+
const matches = [];
|
|
258
|
+
for (const provider of providers.values()) {
|
|
259
|
+
if ((provider.models ?? []).some((model) => model?.id === preferredModelId)) {
|
|
260
|
+
matches.push({ provider: provider.id, model: preferredModelId });
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
if (matches.length === 1) {
|
|
264
|
+
rankForTrace(trace, matches, { adaptive, adaptiveHealth });
|
|
265
|
+
selectTrace(trace, matches[0], 'preferred-default');
|
|
266
|
+
return { ok: true, ...matches[0], source: 'preferred-default', selection_trace: trace };
|
|
267
|
+
}
|
|
268
|
+
if (matches.length > 1) {
|
|
269
|
+
const preferredProvider = normalizeModelRef(harnessDefault)?.provider;
|
|
270
|
+
const deterministicMatch = matches.find((candidate) => candidate.provider === preferredProvider) ?? null;
|
|
271
|
+
const baseline = deterministicMatch
|
|
272
|
+
? [deterministicMatch, ...matches.filter((candidate) => candidate.provider !== deterministicMatch.provider)]
|
|
273
|
+
: matches;
|
|
274
|
+
const ranked = rankForTrace(trace, baseline, { adaptive, adaptiveHealth });
|
|
275
|
+
const adaptiveChoice = ranked.trace.decision_supported ? ranked.candidates[0] : null;
|
|
276
|
+
const match = adaptiveChoice ?? deterministicMatch;
|
|
277
|
+
if (match) {
|
|
278
|
+
const decisionOrder = adaptiveChoice ? ranked.candidates : matches;
|
|
279
|
+
for (const candidate of decisionOrder) {
|
|
280
|
+
if (candidate.provider === match.provider && candidate.model === match.model) continue;
|
|
281
|
+
trace.ordered_candidates.push(candidateDecision(candidate, 'preferred-default', 'skipped', {
|
|
282
|
+
reasonCode: adaptiveChoice
|
|
283
|
+
? MODEL_SELECTION_REASON_CODES.ADAPTIVE_DEPRIORITIZED
|
|
284
|
+
: MODEL_SELECTION_REASON_CODES.PREFERRED_MODEL_AMBIGUOUS,
|
|
285
|
+
}));
|
|
286
|
+
}
|
|
287
|
+
selectTrace(trace, match, 'preferred-default');
|
|
288
|
+
return { ok: true, ...match, source: 'preferred-default', selection_trace: trace };
|
|
289
|
+
}
|
|
290
|
+
for (const candidate of matches) {
|
|
291
|
+
trace.ordered_candidates.push(candidateDecision(candidate, 'preferred-default', 'skipped', {
|
|
292
|
+
reasonCode: MODEL_SELECTION_REASON_CODES.PREFERRED_MODEL_AMBIGUOUS,
|
|
293
|
+
}));
|
|
294
|
+
}
|
|
295
|
+
} else {
|
|
296
|
+
rankForTrace(trace, [], { adaptive, adaptiveHealth });
|
|
297
|
+
trace.ordered_candidates.push(candidateDecision(
|
|
298
|
+
{ provider: null, model: preferredModelId },
|
|
299
|
+
'preferred-default',
|
|
300
|
+
'skipped',
|
|
301
|
+
{ reasonCode: MODEL_SELECTION_REASON_CODES.PREFERRED_MODEL_UNAVAILABLE },
|
|
302
|
+
));
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
if (adaptive?.enabled === true && trace.adaptive === undefined) {
|
|
307
|
+
rankForTrace(trace, [], { adaptive, adaptiveHealth });
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
if (fallback === 'harness-default') {
|
|
311
|
+
const defaultRef = normalizeModelRef(harnessDefault);
|
|
312
|
+
if (!defaultRef) {
|
|
313
|
+
trace.ordered_candidates.push(candidateDecision(null, 'harness-default', 'skipped', {
|
|
314
|
+
reasonCode: MODEL_SELECTION_REASON_CODES.HARNESS_DEFAULT_INVALID,
|
|
315
|
+
}));
|
|
316
|
+
} else if (!providers.has(defaultRef.provider)) {
|
|
317
|
+
trace.ordered_candidates.push(candidateDecision(defaultRef, 'harness-default', 'skipped', {
|
|
318
|
+
reasonCode: MODEL_SELECTION_REASON_CODES.HARNESS_DEFAULT_PROVIDER_UNAVAILABLE,
|
|
319
|
+
}));
|
|
320
|
+
} else {
|
|
321
|
+
const fallbackReason = traceContext.candidateSet === 'escalation'
|
|
322
|
+
? MODEL_SELECTION_REASON_CODES.ESCALATION_CANDIDATES_EXHAUSTED
|
|
323
|
+
: MODEL_SELECTION_REASON_CODES.PRIMARY_CANDIDATES_EXHAUSTED;
|
|
324
|
+
selectTrace(trace, defaultRef, 'harness-default', { fallbackReason });
|
|
325
|
+
return {
|
|
326
|
+
ok: true,
|
|
327
|
+
...defaultRef,
|
|
328
|
+
source: 'harness-default',
|
|
329
|
+
...(typeof harnessDefault.reasoningEffort === 'string' && harnessDefault.reasoningEffort
|
|
330
|
+
? { reasoningEffort: harnessDefault.reasoningEffort }
|
|
331
|
+
: {}),
|
|
332
|
+
selection_trace: trace,
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
trace.fallback_reason = MODEL_SELECTION_REASON_CODES.NO_AVAILABLE_MODEL;
|
|
337
|
+
return {
|
|
338
|
+
ok: false,
|
|
339
|
+
code: NO_WORKER_MODEL_AVAILABLE,
|
|
340
|
+
message: `No Harness model is available for the ${tier ?? 'requested'} worker.`,
|
|
341
|
+
selection_trace: trace,
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* v0.2 role-based model selection. Turns a role's model policy (from
|
|
347
|
+
* policy.resolveModelPolicy) into an ordered selection:
|
|
348
|
+
* attempt 0 → policy.priority (primary / cheap candidates)
|
|
349
|
+
* attempt >= 1 → policy.escalation_priority (strong / escalation candidates)
|
|
350
|
+
* otherwise → Harness Default fallback
|
|
351
|
+
* The output shape matches resolveWorkerModel (provider/model/source/...) plus
|
|
352
|
+
* role + attempt so selection provenance lands in job metadata.
|
|
353
|
+
*/
|
|
354
|
+
export function resolveModel({
|
|
355
|
+
role = 'worker',
|
|
356
|
+
attempt = 0,
|
|
357
|
+
policy,
|
|
358
|
+
catalog,
|
|
359
|
+
harnessDefault,
|
|
360
|
+
adaptiveHealth,
|
|
361
|
+
} = {}) {
|
|
362
|
+
const p = policy && typeof policy === 'object' ? policy : {};
|
|
363
|
+
const escalated = Number.isInteger(attempt) && attempt > 0;
|
|
364
|
+
const candidates = escalated ? p.escalation_priority : p.priority;
|
|
365
|
+
const configured = escalated
|
|
366
|
+
? p.escalation_priority_configured === true || (Array.isArray(candidates) && candidates.length > 0)
|
|
367
|
+
: p.priorityConfigured === true || (Array.isArray(candidates) && candidates.length > 0);
|
|
368
|
+
// Escalation never re-picks the fresh "preferred" role default: an empty
|
|
369
|
+
// escalation pool falls through to Harness Default instead.
|
|
370
|
+
const preferredModelId = escalated ? undefined : DEFAULT_ROLE_MODEL_PREFERENCES[role] ?? undefined;
|
|
371
|
+
const result = resolveWorkerModel({
|
|
372
|
+
tier: role,
|
|
373
|
+
priority: candidates,
|
|
374
|
+
priorityConfigured: configured,
|
|
375
|
+
catalog,
|
|
376
|
+
harnessDefault,
|
|
377
|
+
fallback: p.fallback === 'harness-default' ? 'harness-default' : 'harness-default',
|
|
378
|
+
preferredModelId,
|
|
379
|
+
adaptive: p.adaptive,
|
|
380
|
+
adaptiveHealth,
|
|
381
|
+
traceContext: {
|
|
382
|
+
role,
|
|
383
|
+
logicalAttempt: attempt,
|
|
384
|
+
strategy: p.strategy ?? (role === 'reviewer' ? 'strong' : 'balanced'),
|
|
385
|
+
candidateSet: escalated ? 'escalation' : 'primary',
|
|
386
|
+
prioritySource: escalated ? 'escalation-priority' : 'priority',
|
|
387
|
+
},
|
|
388
|
+
});
|
|
389
|
+
if (!result.ok) return { ...result, role, attempt };
|
|
390
|
+
return { ...result, role, attempt };
|
|
391
|
+
}
|
|
Binary file
|