dsh-harbor-evolution 0.7.1 → 0.7.3
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/README.md +11 -6
- package/index.js +55 -16
- package/lib/candidate.js +58 -5
- package/lib/client.js +195 -54
- package/lib/dashboard.js +141 -20
- package/lib/evolution.js +139 -20
- package/lib/model-runtime.js +46 -5
- package/lib/runtime-identity.js +7 -0
- package/lib/service.js +187 -33
- package/lib/setup.js +2 -2
- package/lib/version.js +128 -0
- package/lib/web.js +5 -1
- package/package.json +8 -3
- package/skills/evolve-agent-with-harbor/SKILL.md +81 -7
- package/skills/evolve-agent-with-harbor/evals/evals.json +56 -0
- package/skills/evolve-agent-with-harbor/references/evaluator-upgrade.md +31 -0
- package/skills/evolve-agent-with-harbor/references/initialization.md +10 -3
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Installable DeepSeek Harness Plugin + Skill for running stable Harbor evaluation and controlled Agent evolution loops, with a native DSH Web dashboard.
|
|
4
4
|
|
|
5
|
-
The package gives DSH
|
|
5
|
+
The package gives DSH fourteen strict Harbor tools, dedicated Tool cards, a nine-stage Evaluation Workbench, an installation Doctor, and the model- and user-invocable `evolve-agent-with-harbor` Skill. The Skill starts with four user-facing concepts—Dataset (what to test), Generator (who answers), Evaluator plus criteria (what good means), and Optimizer (who improves it)—then compiles accepted choices into the strict Evaluation Stack. A DSH Generator may explicitly pin the current default model as a non-secret Candidate identity while retaining the per-Job Host Broker credential boundary. The Plugin validates Dataset identity, checks Trial Lifecycle and Score Validity, governs independent Ground Truth meta-evaluation, diagnoses evidence provenance, limits each iteration to one controlled Candidate change, and invokes the Promotion Gate only as an explicit action.
|
|
6
6
|
|
|
7
7
|
## Install
|
|
8
8
|
|
|
@@ -14,10 +14,10 @@ npx --yes dsh-harbor-evolution@latest setup --project-root "$PWD"
|
|
|
14
14
|
|
|
15
15
|
The setup command installs both required runtimes:
|
|
16
16
|
|
|
17
|
-
- `harbor-dsh-evolution==0.7.
|
|
18
|
-
- `dsh-harbor-evolution@0.7.
|
|
17
|
+
- `harbor-dsh-evolution==0.7.3` in a managed Python environment.
|
|
18
|
+
- `dsh-harbor-evolution@0.7.3` in the selected DSH profile.
|
|
19
19
|
|
|
20
|
-
It then stores the absolute Harbor executable paths and `projectRoot` in the profile's `harbor-evolution` block and verifies the integration. Existing unrelated profile entries are preserved, and rerunning setup updates the same block.
|
|
20
|
+
It then stores the absolute Harbor executable paths and a fallback `projectRoot` in the profile's `harbor-evolution` block and verifies the integration. Agent Tool calls always use the calling session's absolute working directory as their project root; the configured value remains the Web Workbench and non-Agent fallback. Existing unrelated profile entries are preserved, and rerunning setup updates the same block.
|
|
21
21
|
|
|
22
22
|
The default profile is `web`. Use `--profile headless` only when that is the profile you actually run. See all options with:
|
|
23
23
|
|
|
@@ -37,6 +37,7 @@ Users may provide a single Query or Dataset path, a Generator curl or local Agen
|
|
|
37
37
|
The Plugin registers:
|
|
38
38
|
|
|
39
39
|
- `harbor_candidate_snapshot`
|
|
40
|
+
- `harbor_model_binding`
|
|
40
41
|
- `harbor_evolution_init`
|
|
41
42
|
- `harbor_evolution_doctor`
|
|
42
43
|
- `harbor_dataset_validate`
|
|
@@ -51,7 +52,7 @@ In the `web` profile, the same package also registers:
|
|
|
51
52
|
- descriptor-authorized Evaluator/Rubric source editing for `script` and `llm-as-judge` implementations, with optimistic concurrency and mandatory new identities;
|
|
52
53
|
- a `harbor-dsh-evaluator/v1` interface shared by deterministic scripts and LLM-as-Judge implementations;
|
|
53
54
|
- compact result cards for all Harbor Tool calls;
|
|
54
|
-
- a `Harbor Evolution` Settings section that checks the configured project, Evaluation Stack, Jobs directory, and CLI paths.
|
|
55
|
+
- a `Harbor Evolution` Settings section that checks the configured project, Evaluation Stack, Jobs directory, and CLI paths, supports process-local `projectRoot` reload, and checks npm for a newer formal release without silently installing it.
|
|
55
56
|
|
|
56
57
|
The Web UI is intentionally read-only. Starting an evaluation or deciding promotion remains an explicit Agent + Skill workflow, so a page refresh can never launch an expensive Job.
|
|
57
58
|
|
|
@@ -63,6 +64,10 @@ Before each Job, the Plugin snapshots the current DSH Agent selection—provider
|
|
|
63
64
|
|
|
64
65
|
`harbor_eval_run`, `harbor_context_preview`, and `harbor_evolution_doctor` inherit that selection by default. Advanced callers can override `candidateProvider` and `candidateModel` only as a pair, plus an optional `candidateReasoningEffort`. `openai-codex` performs a GPT Auth sign-in check before Harbor starts. The resulting model binding is part of Context v2 comparison identity, so any provider/model/reasoning change requires a new baseline.
|
|
65
66
|
|
|
67
|
+
`harbor_model_binding` returns the current default selection as a credential-free `model-binding.json` draft. Once included before Candidate snapshot, it enters the Candidate digest and becomes the required Job model identity. Conflicting Job or Plugin overrides fail before Harbor starts. Even for `openai-codex`, the Candidate receives only the short-lived Broker capability—never the Host OAuth file or an upstream API key.
|
|
68
|
+
|
|
69
|
+
When Settings opens, the Host performs a bounded npm registry check and caches successful results. An available release is shown with its exact installer command and release link. The browser never installs, rewrites a DSH profile, or restarts DSH; registry failures are non-blocking.
|
|
70
|
+
|
|
66
71
|
`harbor_eval_result` defaults to the stable Summary. Use `view=job`, `view=dataset`, `view=progress`, `view=trial` plus a returned `trialId`, or `view=governance` to inspect sanitized instructions, generated output, evidence, and evaluator source without coupling the Agent to artifact file paths.
|
|
67
72
|
|
|
68
73
|
## What setup writes
|
|
@@ -79,7 +84,7 @@ The selected profile receives one id-targeted override:
|
|
|
79
84
|
pythonPath: ""
|
|
80
85
|
```
|
|
81
86
|
|
|
82
|
-
Keep `pythonPath` empty for the published Python package. `candidatePath`, `datasetPath`, `jobPath`, and `policyPath`
|
|
87
|
+
Keep `pythonPath` empty for the published Python package. For Agent Tool calls, `projectRoot` is replaced by the calling session's working directory for that call. `candidatePath`, `datasetPath`, `jobPath`, and `policyPath` remain constrained to that request-local root, so concurrent sessions cannot redirect each other's Harbor operations.
|
|
83
88
|
|
|
84
89
|
For source development from the repository:
|
|
85
90
|
|
package/index.js
CHANGED
|
@@ -6,6 +6,7 @@ import { fileURLToPath } from 'node:url'
|
|
|
6
6
|
|
|
7
7
|
import { loadBundledSkill } from './lib/official-skill.js'
|
|
8
8
|
import { CandidateModelRuntime } from './lib/model-runtime.js'
|
|
9
|
+
import { RUNTIME_POLICY } from './lib/runtime-identity.js'
|
|
9
10
|
import { EvolutionService } from './lib/service.js'
|
|
10
11
|
import { installDashboardWeb } from './lib/web.js'
|
|
11
12
|
|
|
@@ -26,7 +27,6 @@ export const Config = Schema.object({
|
|
|
26
27
|
jobsDir: Schema.string().default('jobs'),
|
|
27
28
|
harborBin: Schema.string().default(''),
|
|
28
29
|
harborDshBin: Schema.string().default(''),
|
|
29
|
-
dshVersion: Schema.string().default('0.1.0-rc.6'),
|
|
30
30
|
agentImportPath: Schema.string().default('harbor_dsh_evolution.agent:DshCandidateAgent'),
|
|
31
31
|
pluginImportPath: Schema.string().default('dsh-evolution'),
|
|
32
32
|
pythonPath: Schema.string().default(''),
|
|
@@ -47,15 +47,30 @@ function jsonTool(definition, execute) {
|
|
|
47
47
|
schema: { type: 'string' },
|
|
48
48
|
render: (_args, value) => [{ type: 'text', text: value }],
|
|
49
49
|
},
|
|
50
|
-
async execute(args) {
|
|
51
|
-
return JSON.stringify(await execute(args), null, 2)
|
|
50
|
+
async execute(args, exec) {
|
|
51
|
+
return JSON.stringify(await execute(args, exec), null, 2)
|
|
52
52
|
},
|
|
53
53
|
})
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
+
function toolProjectRoot(exec) {
|
|
57
|
+
const cwd = exec?.agent?.session?.header?.cwd
|
|
58
|
+
if (typeof cwd !== 'string' || !path.isAbsolute(cwd)) {
|
|
59
|
+
throw new Error('Harbor tools require an Agent session with an absolute working directory')
|
|
60
|
+
}
|
|
61
|
+
return path.resolve(cwd)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function synchronizeWorkbenchProjectRoot(service, exec) {
|
|
65
|
+
const projectRoot = toolProjectRoot(exec)
|
|
66
|
+
service.activateProjectRoot(projectRoot, 'agent-session')
|
|
67
|
+
return projectRoot
|
|
68
|
+
}
|
|
69
|
+
|
|
56
70
|
export function apply(ctx, config) {
|
|
57
71
|
const resolved = {
|
|
58
72
|
...config,
|
|
73
|
+
runtimePolicy: RUNTIME_POLICY,
|
|
59
74
|
projectRoot: path.resolve(config.projectRoot),
|
|
60
75
|
harborBin: config.harborBin || process.env.HARBOR_BIN || checkoutExecutable('harbor'),
|
|
61
76
|
harborDshBin: config.harborDshBin || process.env.HARBOR_DSH_BIN || checkoutExecutable('harbor-dsh'),
|
|
@@ -66,7 +81,12 @@ export function apply(ctx, config) {
|
|
|
66
81
|
),
|
|
67
82
|
}
|
|
68
83
|
const modelRuntime = new CandidateModelRuntime(ctx, resolved)
|
|
69
|
-
const
|
|
84
|
+
const metadata = { pluginVersion: packageJson.version, projectRootSource: 'configured' }
|
|
85
|
+
const service = new EvolutionService(resolved, metadata, modelRuntime)
|
|
86
|
+
const serviceForTool = exec => {
|
|
87
|
+
const projectRoot = synchronizeWorkbenchProjectRoot(service, exec)
|
|
88
|
+
return new EvolutionService({ ...resolved, projectRoot }, metadata, modelRuntime)
|
|
89
|
+
}
|
|
70
90
|
|
|
71
91
|
ctx.skills.register(loadBundledSkill())
|
|
72
92
|
installDashboardWeb(ctx, service)
|
|
@@ -79,13 +99,20 @@ export function apply(ctx, config) {
|
|
|
79
99
|
candidateId: { type: 'string' },
|
|
80
100
|
version: { type: 'string' },
|
|
81
101
|
},
|
|
82
|
-
}, args =>
|
|
102
|
+
}, (args, exec) => serviceForTool(exec).snapshot(args)))
|
|
103
|
+
|
|
104
|
+
ctx.tools.register(jsonTool({
|
|
105
|
+
name: 'harbor_model_binding',
|
|
106
|
+
description: 'Freeze the current DSH default provider, model, and reasoning identity into a non-secret model-binding.json draft. Runtime access still uses the short-lived Host Model Broker capability.',
|
|
107
|
+
parameters: {},
|
|
108
|
+
}, (_args, exec) => serviceForTool(exec).modelBinding()))
|
|
83
109
|
|
|
84
110
|
ctx.tools.register(jsonTool({
|
|
85
111
|
name: 'harbor_evolution_init',
|
|
86
112
|
description: 'Compile an accepted Dataset, Generator, Evaluator/criteria, and Optimizer onboarding card into a strict, non-overwriting Evaluation Stack project. Detailed identity fields are internal tool inputs, not a user questionnaire.',
|
|
87
113
|
parameters: {
|
|
88
114
|
datasetPath: { type: 'string', required: true },
|
|
115
|
+
workspaceSubdir: { type: 'string', description: 'Optional namespace under the current project root. Defaults to the project root; use it to host multiple independent Harbor projects.' },
|
|
89
116
|
stackId: { type: 'string', required: true },
|
|
90
117
|
stackVersion: { type: 'string', required: true },
|
|
91
118
|
datasetId: { type: 'string', required: true },
|
|
@@ -101,7 +128,7 @@ export function apply(ctx, config) {
|
|
|
101
128
|
policyVersion: { type: 'string', required: true },
|
|
102
129
|
minImprovement: { type: 'number', required: true },
|
|
103
130
|
},
|
|
104
|
-
}, args =>
|
|
131
|
+
}, (args, exec) => serviceForTool(exec).initialize(args)))
|
|
105
132
|
|
|
106
133
|
ctx.tools.register(jsonTool({
|
|
107
134
|
name: 'harbor_evolution_doctor',
|
|
@@ -116,7 +143,17 @@ export function apply(ctx, config) {
|
|
|
116
143
|
candidateModel: { type: 'string' },
|
|
117
144
|
candidateReasoningEffort: { type: 'string' },
|
|
118
145
|
},
|
|
119
|
-
}, args =>
|
|
146
|
+
}, (args, exec) => serviceForTool(exec).doctor(args)))
|
|
147
|
+
|
|
148
|
+
ctx.tools.register(jsonTool({
|
|
149
|
+
name: 'harbor_quick_diagnostic_init',
|
|
150
|
+
description: 'Create a non-overwriting Harbor 1.4 wiring diagnostic with one Query, a minimal Host-model Candidate, a runnable Task, and an explicit non-promotion Evaluator. The supplied Rubric is recorded as a draft but is not treated as executed.',
|
|
151
|
+
parameters: {
|
|
152
|
+
query: { type: 'string', required: true },
|
|
153
|
+
rubric: { type: 'string', required: true },
|
|
154
|
+
workspaceSubdir: { type: 'string', description: 'Defaults to harbor-diagnostic under the current Agent session directory.' },
|
|
155
|
+
},
|
|
156
|
+
}, (args, exec) => serviceForTool(exec).quickDiagnostic(args)))
|
|
120
157
|
|
|
121
158
|
ctx.tools.register(jsonTool({
|
|
122
159
|
name: 'harbor_dataset_validate',
|
|
@@ -124,7 +161,7 @@ export function apply(ctx, config) {
|
|
|
124
161
|
parameters: {
|
|
125
162
|
datasetPath: { type: 'string', required: true },
|
|
126
163
|
},
|
|
127
|
-
}, args =>
|
|
164
|
+
}, (args, exec) => serviceForTool(exec).validateDataset(args)))
|
|
128
165
|
|
|
129
166
|
ctx.tools.register(jsonTool({
|
|
130
167
|
name: 'harbor_context_preview',
|
|
@@ -140,7 +177,7 @@ export function apply(ctx, config) {
|
|
|
140
177
|
candidateModel: { type: 'string' },
|
|
141
178
|
candidateReasoningEffort: { type: 'string' },
|
|
142
179
|
},
|
|
143
|
-
}, args =>
|
|
180
|
+
}, (args, exec) => serviceForTool(exec).previewContext(args)))
|
|
144
181
|
|
|
145
182
|
ctx.tools.register(jsonTool({
|
|
146
183
|
name: 'harbor_eval_run',
|
|
@@ -158,7 +195,7 @@ export function apply(ctx, config) {
|
|
|
158
195
|
candidateModel: { type: 'string' },
|
|
159
196
|
candidateReasoningEffort: { type: 'string' },
|
|
160
197
|
},
|
|
161
|
-
}, args =>
|
|
198
|
+
}, (args, exec) => serviceForTool(exec).run(args)))
|
|
162
199
|
|
|
163
200
|
ctx.tools.register(jsonTool({
|
|
164
201
|
name: 'harbor_eval_result',
|
|
@@ -170,7 +207,7 @@ export function apply(ctx, config) {
|
|
|
170
207
|
compareJob: { type: 'string', description: 'Optional previous Job for view=governance impact analysis' },
|
|
171
208
|
since: { type: 'string', description: 'Optional ISO timestamp for incremental progress changes' },
|
|
172
209
|
},
|
|
173
|
-
}, args =>
|
|
210
|
+
}, (args, exec) => serviceForTool(exec).result(args)))
|
|
174
211
|
|
|
175
212
|
ctx.tools.register(jsonTool({
|
|
176
213
|
name: 'harbor_evaluator_inspect',
|
|
@@ -178,7 +215,7 @@ export function apply(ctx, config) {
|
|
|
178
215
|
parameters: {
|
|
179
216
|
stackPath: { type: 'string', description: 'Defaults to .harbor/evaluation-stack.yml' },
|
|
180
217
|
},
|
|
181
|
-
}, args =>
|
|
218
|
+
}, (args, exec) => serviceForTool(exec).evaluatorInspect(args)))
|
|
182
219
|
|
|
183
220
|
ctx.tools.register(jsonTool({
|
|
184
221
|
name: 'harbor_evaluator_update',
|
|
@@ -191,13 +228,14 @@ export function apply(ctx, config) {
|
|
|
191
228
|
newEvaluatorVersion: { type: 'string', required: true },
|
|
192
229
|
newStackVersion: { type: 'string', required: true },
|
|
193
230
|
},
|
|
194
|
-
}, args =>
|
|
231
|
+
}, (args, exec) => serviceForTool(exec).evaluator(args)))
|
|
195
232
|
|
|
196
233
|
ctx.tools.register(jsonTool({
|
|
197
234
|
name: 'harbor_ground_truth_init',
|
|
198
235
|
description: 'Create a non-overwriting Ground Truth draft for evaluator meta-evaluation. GT may be human, programmatic, consensus, model, or external, but must have explicit provenance and remain independent of the Candidate evaluator.',
|
|
199
236
|
parameters: {
|
|
200
237
|
outputPath: { type: 'string', description: 'Defaults to .harbor/ground-truth.json' },
|
|
238
|
+
evaluationRoot: { type: 'string', description: 'Optional evaluation workspace root used to register custom Ground Truth paths.' },
|
|
201
239
|
groundTruthId: { type: 'string', required: true },
|
|
202
240
|
version: { type: 'string', required: true },
|
|
203
241
|
sourceKind: { type: 'string', required: true, description: 'human, programmatic, consensus, model, or external' },
|
|
@@ -205,7 +243,7 @@ export function apply(ctx, config) {
|
|
|
205
243
|
provenance: { type: 'string', required: true },
|
|
206
244
|
criteria: { type: 'string', required: true, description: 'Comma-separated criterion ids' },
|
|
207
245
|
},
|
|
208
|
-
}, args =>
|
|
246
|
+
}, (args, exec) => serviceForTool(exec).groundTruthInitialize(args)))
|
|
209
247
|
|
|
210
248
|
ctx.tools.register(jsonTool({
|
|
211
249
|
name: 'harbor_evaluator_meta_evaluate',
|
|
@@ -214,8 +252,9 @@ export function apply(ctx, config) {
|
|
|
214
252
|
groundTruthPath: { type: 'string', description: 'Defaults to .harbor/ground-truth.json' },
|
|
215
253
|
observationsPath: { type: 'string', required: true },
|
|
216
254
|
outputPath: { type: 'string', description: 'Defaults to .harbor/meta-evaluation-report.json' },
|
|
255
|
+
evaluationRoot: { type: 'string', description: 'Optional evaluation workspace root used to register custom report paths.' },
|
|
217
256
|
},
|
|
218
|
-
}, args =>
|
|
257
|
+
}, (args, exec) => serviceForTool(exec).evaluatorMetaEvaluate(args)))
|
|
219
258
|
|
|
220
259
|
ctx.tools.register(jsonTool({
|
|
221
260
|
name: 'harbor_candidate_compare',
|
|
@@ -225,5 +264,5 @@ export function apply(ctx, config) {
|
|
|
225
264
|
candidateJob: { type: 'string', required: true },
|
|
226
265
|
policyPath: { type: 'string', required: true },
|
|
227
266
|
},
|
|
228
|
-
}, args =>
|
|
267
|
+
}, (args, exec) => serviceForTool(exec).compare(args)))
|
|
229
268
|
}
|
package/lib/candidate.js
CHANGED
|
@@ -2,7 +2,10 @@ import { createHash } from 'node:crypto'
|
|
|
2
2
|
import { mkdir, readFile, readdir, stat, writeFile } from 'node:fs/promises'
|
|
3
3
|
import path from 'node:path'
|
|
4
4
|
|
|
5
|
+
import { CANDIDATE_ACP_PACKAGE, DSH_RUNTIME_VERSION, RUNTIME_POLICY } from './runtime-identity.js'
|
|
6
|
+
|
|
5
7
|
export const MANIFEST_NAME = 'candidate-manifest.json'
|
|
8
|
+
export const MODEL_BINDING_NAME = 'model-binding.json'
|
|
6
9
|
const DIGEST_PREFIX = Buffer.from('harbor-dsh-candidate-v1\0')
|
|
7
10
|
const EXCLUDED_DIRS = new Set(['.git', 'node_modules', '__pycache__', '.harbor-runtime'])
|
|
8
11
|
const EXCLUDED_FILES = new Set([MANIFEST_NAME, '.DS_Store'])
|
|
@@ -16,6 +19,7 @@ const CREDENTIAL_FILES = new Set([
|
|
|
16
19
|
'id_rsa',
|
|
17
20
|
'id_ed25519',
|
|
18
21
|
])
|
|
22
|
+
const MODEL_BINDING_KEYS = new Set(['schema_version', 'source', 'provider', 'model', 'reasoning_effort'])
|
|
19
23
|
|
|
20
24
|
async function walk(root, current = root) {
|
|
21
25
|
const entries = await readdir(current, { withFileTypes: true })
|
|
@@ -33,6 +37,45 @@ async function walk(root, current = root) {
|
|
|
33
37
|
return files.sort((a, b) => Buffer.compare(Buffer.from(a.relative), Buffer.from(b.relative)))
|
|
34
38
|
}
|
|
35
39
|
|
|
40
|
+
export async function loadModelBinding(candidateDir) {
|
|
41
|
+
const pathname = path.join(path.resolve(candidateDir), MODEL_BINDING_NAME)
|
|
42
|
+
let value
|
|
43
|
+
try {
|
|
44
|
+
value = JSON.parse(await readFile(pathname, 'utf8'))
|
|
45
|
+
} catch (error) {
|
|
46
|
+
if (error.code === 'ENOENT') return undefined
|
|
47
|
+
if (error instanceof SyntaxError) throw new Error(`${MODEL_BINDING_NAME} is not valid JSON`)
|
|
48
|
+
throw error
|
|
49
|
+
}
|
|
50
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value)) {
|
|
51
|
+
throw new Error(`${MODEL_BINDING_NAME} must be an object`)
|
|
52
|
+
}
|
|
53
|
+
const unknown = Object.keys(value).filter(key => !MODEL_BINDING_KEYS.has(key)).sort()
|
|
54
|
+
if (unknown.length) {
|
|
55
|
+
throw new Error(`${MODEL_BINDING_NAME} contains unsupported or secret-bearing fields: ${unknown.join(', ')}`)
|
|
56
|
+
}
|
|
57
|
+
if (value.schema_version !== 1) throw new Error(`${MODEL_BINDING_NAME} requires schema_version=1`)
|
|
58
|
+
const source = typeof value.source === 'string' ? value.source.trim() : ''
|
|
59
|
+
const provider = typeof value.provider === 'string' ? value.provider.trim() : ''
|
|
60
|
+
const model = typeof value.model === 'string' ? value.model.trim() : ''
|
|
61
|
+
if (!source || !provider || !model) {
|
|
62
|
+
throw new Error(`${MODEL_BINDING_NAME} requires non-empty source, provider, and model`)
|
|
63
|
+
}
|
|
64
|
+
const reasoningEffort = value.reasoning_effort === undefined
|
|
65
|
+
? undefined
|
|
66
|
+
: typeof value.reasoning_effort === 'string' ? value.reasoning_effort.trim() : ''
|
|
67
|
+
if (value.reasoning_effort !== undefined && !reasoningEffort) {
|
|
68
|
+
throw new Error(`${MODEL_BINDING_NAME} reasoning_effort must be a non-empty string when present`)
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
schema_version: 1,
|
|
72
|
+
source,
|
|
73
|
+
provider,
|
|
74
|
+
model,
|
|
75
|
+
...(reasoningEffort ? { reasoning_effort: reasoningEffort } : {}),
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
36
79
|
export async function computeCandidate(candidateDir) {
|
|
37
80
|
const root = path.resolve(candidateDir)
|
|
38
81
|
if (!(await stat(root)).isDirectory()) throw new Error(`Candidate path is not a directory: ${root}`)
|
|
@@ -83,6 +126,7 @@ async function validateCandidateContract(root) {
|
|
|
83
126
|
if (credentialPaths.length > 0) {
|
|
84
127
|
throw new Error(`Candidate contains credential-bearing files: ${credentialPaths.join(', ')}; inject credentials at runtime instead`)
|
|
85
128
|
}
|
|
129
|
+
await loadModelBinding(root)
|
|
86
130
|
}
|
|
87
131
|
|
|
88
132
|
export async function snapshotCandidate(candidateDir, options = {}) {
|
|
@@ -96,11 +140,18 @@ export async function snapshotCandidate(candidateDir, options = {}) {
|
|
|
96
140
|
}
|
|
97
141
|
const candidateId = options.candidateId ?? packageJson.name
|
|
98
142
|
const version = options.version ?? packageJson.version
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
throw new Error('Candidate id, version, and runtime version must not be empty; set package.json name/version or pass explicit values')
|
|
143
|
+
if (!candidateId || !version) {
|
|
144
|
+
throw new Error('Candidate id and version must not be empty; set package.json name/version or pass explicit values')
|
|
102
145
|
}
|
|
103
146
|
const computed = await computeCandidate(root)
|
|
147
|
+
const metadata = { ...(options.metadata ?? {}) }
|
|
148
|
+
const modelBinding = await loadModelBinding(root)
|
|
149
|
+
if (modelBinding) {
|
|
150
|
+
if (metadata.model_binding !== undefined && JSON.stringify(metadata.model_binding) !== JSON.stringify(modelBinding)) {
|
|
151
|
+
throw new Error('Candidate metadata model_binding must match model-binding.json')
|
|
152
|
+
}
|
|
153
|
+
metadata.model_binding = modelBinding
|
|
154
|
+
}
|
|
104
155
|
const manifest = {
|
|
105
156
|
schema_version: 1,
|
|
106
157
|
candidate_id: String(candidateId),
|
|
@@ -109,11 +160,13 @@ export async function snapshotCandidate(candidateDir, options = {}) {
|
|
|
109
160
|
created_at: new Date().toISOString(),
|
|
110
161
|
runtime: {
|
|
111
162
|
kind: 'deepseek-harness',
|
|
112
|
-
|
|
163
|
+
policy: RUNTIME_POLICY,
|
|
164
|
+
version: DSH_RUNTIME_VERSION,
|
|
165
|
+
package: CANDIDATE_ACP_PACKAGE,
|
|
113
166
|
transport: 'acp',
|
|
114
167
|
},
|
|
115
168
|
files: computed.files,
|
|
116
|
-
metadata
|
|
169
|
+
metadata,
|
|
117
170
|
}
|
|
118
171
|
await mkdir(root, { recursive: true })
|
|
119
172
|
await writeFile(path.join(root, MANIFEST_NAME), `${JSON.stringify(manifest, null, 2)}\n`)
|