dsh-harbor-evolution 0.7.2 → 0.8.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.
@@ -0,0 +1,294 @@
1
+ import { createHash, randomBytes } from 'node:crypto'
2
+ import path from 'node:path'
3
+
4
+ import { foldSessionDiagnosticIndex } from './session-projection.js'
5
+
6
+ function canonicalize(value) {
7
+ if (Array.isArray(value)) return value.map(canonicalize)
8
+ if (value && typeof value === 'object') {
9
+ return Object.fromEntries(
10
+ Object.keys(value).sort().map(key => [key, canonicalize(value[key])]),
11
+ )
12
+ }
13
+ return value
14
+ }
15
+
16
+ export function canonicalDigest(value, namespace) {
17
+ const body = JSON.stringify(canonicalize(value))
18
+ return `sha256:${createHash('sha256').update(namespace).update('\0').update(body).digest('hex')}`
19
+ }
20
+
21
+ function sessionHeaderIdentity(header, effectiveAgentPreset) {
22
+ return {
23
+ version: header?.version,
24
+ id: header?.id,
25
+ createdAt: header?.createdAt,
26
+ cwd: header?.cwd,
27
+ parentSession: header?.parentSession,
28
+ seedLength: header?.seedLength,
29
+ origin: header?.origin,
30
+ delegationDepth: header?.delegationDepth,
31
+ ...(effectiveAgentPreset === undefined ? {} : { agentPreset: effectiveAgentPreset }),
32
+ }
33
+ }
34
+
35
+ function sameProjectRoot(value, projectRoot) {
36
+ if (typeof value !== 'string' || !path.isAbsolute(value)) return false
37
+ return path.resolve(value) === path.resolve(projectRoot)
38
+ }
39
+
40
+ function isoTime(value) {
41
+ return Number.isSafeInteger(value) && value > 0 ? new Date(value).toISOString() : null
42
+ }
43
+
44
+ function safeIdentity(value, rawSessionId) {
45
+ const text = typeof value === 'string' ? value : ''
46
+ if (rawSessionId && text.includes(rawSessionId)) return '[redacted-identity]'
47
+ if (
48
+ /(?:api[_-]?key|token|secret|password|authorization|bearer\s+)/i.test(text)
49
+ || /(?:^|[\\/])(?:Users|home|private|tmp|var|etc|opt|Volumes)(?:[\\/]|$)/.test(text)
50
+ || /\b(?:sk|rk|pk)-[A-Za-z0-9_-]{12,}\b/.test(text)
51
+ ) return '[redacted-identity]'
52
+ return text.slice(0, 160)
53
+ }
54
+
55
+ async function mapConcurrent(values, concurrency, mapper) {
56
+ const result = new Array(values.length)
57
+ let cursor = 0
58
+ const workers = Array.from({ length: Math.min(concurrency, values.length) }, async () => {
59
+ while (cursor < values.length) {
60
+ const index = cursor
61
+ cursor += 1
62
+ try {
63
+ result[index] = { status: 'fulfilled', value: await mapper(values[index], index) }
64
+ } catch (reason) {
65
+ result[index] = { status: 'rejected', reason }
66
+ }
67
+ }
68
+ })
69
+ await Promise.all(workers)
70
+ return result
71
+ }
72
+
73
+ function publicSelection(item, index) {
74
+ const agentPreset = item.index.effectiveAgentPreset ?? item.header.agentPreset
75
+ return {
76
+ trialId: item.trialId,
77
+ title: `历史会话 ${index + 1}`,
78
+ createdAt: isoTime(item.header.createdAt),
79
+ lastActivityAt: isoTime(item.index.lastActivityAt),
80
+ turnCount: item.index.turnCount,
81
+ humanMessageCount: item.index.humanMessageCount,
82
+ assistantMessageCount: item.index.assistantMessageCount,
83
+ toolCallCount: item.index.toolCallCount,
84
+ lastTurnReason: item.index.lastTurnReason,
85
+ agentPreset: agentPreset ? safeIdentity(agentPreset, item.rawSessionId) : null,
86
+ modelRoutes: item.index.modelRoutes.map(route => ({
87
+ provider: safeIdentity(route.provider, item.rawSessionId),
88
+ model: safeIdentity(route.model, item.rawSessionId),
89
+ ...(route.reasoning_effort ? { reasoning_effort: safeIdentity(route.reasoning_effort, item.rawSessionId) } : {}),
90
+ })),
91
+ }
92
+ }
93
+
94
+ export async function selectRecentSessions({
95
+ sessionQuery,
96
+ projectRoot,
97
+ currentSessionId,
98
+ limit = 10,
99
+ maxSessionReads = 100,
100
+ concurrency = 4,
101
+ createdAfter,
102
+ signal,
103
+ }) {
104
+ if (!sessionQuery || typeof sessionQuery.readSession !== 'function') {
105
+ throw new Error('DSH_SESSION_QUERY_UNAVAILABLE: this DSH Profile does not expose the Session Query service')
106
+ }
107
+ if (!Number.isInteger(limit) || limit < 1 || limit > 10) {
108
+ throw new Error('SESSION_LIMIT_INVALID: limit must be an integer from 1 to 10')
109
+ }
110
+ if (createdAfter !== undefined && (!Number.isSafeInteger(createdAfter) || createdAfter < 0)) {
111
+ throw new Error('SESSION_CREATED_AFTER_INVALID: createdAfter must be a valid timestamp')
112
+ }
113
+ const listed = typeof sessionQuery.filterSessions === 'function'
114
+ ? await sessionQuery.filterSessions([
115
+ { kind: 'cwd', values: [projectRoot] },
116
+ ...(createdAfter === undefined ? [] : [{ kind: 'created-at', from: createdAfter }]),
117
+ ], signal)
118
+ : await sessionQuery.listSessions(signal)
119
+ const excludedCounts = {
120
+ outsideWorkspace: 0,
121
+ beforeCreatedAfter: 0,
122
+ currentSession: 0,
123
+ subagent: 0,
124
+ forkOrChild: 0,
125
+ openTurn: 0,
126
+ noDirectHumanInput: 0,
127
+ noAssistantOutput: 0,
128
+ userAborted: 0,
129
+ harborInternal: 0,
130
+ empty: 0,
131
+ unreadable: 0,
132
+ }
133
+ const candidates = []
134
+ for (const record of Array.isArray(listed) ? listed : []) {
135
+ const header = record?.header ?? {}
136
+ if (!sameProjectRoot(header.cwd, projectRoot)) {
137
+ excludedCounts.outsideWorkspace += 1
138
+ } else if (header.id === currentSessionId) {
139
+ excludedCounts.currentSession += 1
140
+ } else if (createdAfter !== undefined && Number(header.createdAt) < createdAfter) {
141
+ excludedCounts.beforeCreatedAfter += 1
142
+ } else if (header.origin === 'subagent') {
143
+ excludedCounts.subagent += 1
144
+ } else if (
145
+ header.parentSession !== undefined
146
+ || Number(header.seedLength ?? 0) > 0
147
+ || Number(header.delegationDepth ?? 0) > 0
148
+ ) {
149
+ excludedCounts.forkOrChild += 1
150
+ } else {
151
+ candidates.push(record)
152
+ }
153
+ }
154
+ if (candidates.length > maxSessionReads) {
155
+ throw new Error(
156
+ `SESSION_SELECTION_TOO_EXPENSIVE: ${candidates.length} exact Session reads exceed maxSessionReads=${maxSessionReads}; preview again with createdAfter to narrow the scan`,
157
+ )
158
+ }
159
+
160
+ const snapshots = await mapConcurrent(candidates, concurrency, async record => {
161
+ const snapshot = await sessionQuery.readSession(record.header.id)
162
+ if (snapshot?.session?.id !== record.header.id) {
163
+ throw new Error('Session Query returned a mismatched Session header')
164
+ }
165
+ if (!sameProjectRoot(snapshot.session.cwd, projectRoot)) {
166
+ throw new Error('Session Query changed the Session workspace boundary')
167
+ }
168
+ return snapshot
169
+ })
170
+ const eligible = []
171
+ for (const outcome of snapshots) {
172
+ if (outcome.status === 'rejected') {
173
+ excludedCounts.unreadable += 1
174
+ continue
175
+ }
176
+ const snapshot = outcome.value
177
+ const index = foldSessionDiagnosticIndex(snapshot.events, snapshot.session)
178
+ if (index.lastSeq === null) {
179
+ excludedCounts.empty += 1
180
+ continue
181
+ }
182
+ if (index.openTurn) {
183
+ excludedCounts.openTurn += 1
184
+ continue
185
+ }
186
+ if (index.lastTurnReason === 'aborted') {
187
+ excludedCounts.userAborted += 1
188
+ continue
189
+ }
190
+ if (index.humanMessageCount < 1) {
191
+ excludedCounts.noDirectHumanInput += 1
192
+ continue
193
+ }
194
+ if (index.assistantMessageCount < 1) {
195
+ excludedCounts.noAssistantOutput += 1
196
+ continue
197
+ }
198
+ if (index.hasHarborToolCall) {
199
+ excludedCounts.harborInternal += 1
200
+ continue
201
+ }
202
+ if (/harbor/i.test(String(index.effectiveAgentPreset ?? ''))) {
203
+ excludedCounts.harborInternal += 1
204
+ continue
205
+ }
206
+ const header = sessionHeaderIdentity(snapshot.session, index.effectiveAgentPreset)
207
+ const sourceDigest = canonicalDigest(
208
+ { session: header, events: snapshot.events },
209
+ 'harbor-dsh-session-source-v1',
210
+ )
211
+ const sourceRef = canonicalDigest(
212
+ { id: snapshot.session.id, header },
213
+ 'harbor-dsh-session-source-ref-v1',
214
+ )
215
+ eligible.push({
216
+ rawSessionId: snapshot.session.id,
217
+ header,
218
+ events: snapshot.events,
219
+ index,
220
+ sourceDigest,
221
+ sourceRef,
222
+ capturedThroughSeq: index.lastSeq,
223
+ trialId: `session-${sourceRef.slice('sha256:'.length, 'sha256:'.length + 12)}`,
224
+ })
225
+ }
226
+ eligible.sort((left, right) => (
227
+ right.index.lastActivityAt - left.index.lastActivityAt
228
+ || left.sourceRef.localeCompare(right.sourceRef)
229
+ ))
230
+ const selected = eligible.slice(0, limit)
231
+ return {
232
+ selected,
233
+ publicSelected: selected.map(publicSelection),
234
+ excludedCounts,
235
+ warnings: excludedCounts.unreadable
236
+ ? [`${excludedCounts.unreadable} Session(s) could not be read and were excluded.`]
237
+ : [],
238
+ }
239
+ }
240
+
241
+ export function verifySessionSnapshot(expected, snapshot, projectRoot) {
242
+ if (snapshot?.session?.id !== expected.rawSessionId || !sameProjectRoot(snapshot?.session?.cwd, projectRoot)) {
243
+ return false
244
+ }
245
+ const index = foldSessionDiagnosticIndex(snapshot.events, snapshot.session)
246
+ if (index.lastSeq !== expected.capturedThroughSeq || index.openTurn) return false
247
+ const digest = canonicalDigest(
248
+ { session: sessionHeaderIdentity(snapshot.session, index.effectiveAgentPreset), events: snapshot.events },
249
+ 'harbor-dsh-session-source-v1',
250
+ )
251
+ return digest === expected.sourceDigest
252
+ }
253
+
254
+ export class SessionSelectionTokenStore {
255
+ constructor({ ttlMs = 15 * 60 * 1000, now = () => Date.now(), randomToken } = {}) {
256
+ this.ttlMs = ttlMs
257
+ this.now = now
258
+ this.randomToken = randomToken ?? (() => randomBytes(32).toString('base64url'))
259
+ this.tokens = new Map()
260
+ }
261
+
262
+ issue(value) {
263
+ this.purge()
264
+ const token = this.randomToken()
265
+ const expiresAt = this.now() + this.ttlMs
266
+ this.tokens.set(token, { ...value, expiresAt })
267
+ return { token, expiresAt }
268
+ }
269
+
270
+ consume(token, { ownerSessionId, projectRoot }) {
271
+ const stored = this.tokens.get(token)
272
+ if (!stored) throw new Error('SESSION_SELECTION_TOKEN_INVALID: preview again before running the diagnostic')
273
+ // Consume before validation so a stolen or failed token cannot be replayed.
274
+ this.tokens.delete(token)
275
+ if (stored.ownerSessionId !== ownerSessionId) {
276
+ throw new Error('SESSION_SELECTION_TOKEN_OWNER_MISMATCH: the token belongs to another Agent Session')
277
+ }
278
+ if (path.resolve(stored.projectRoot) !== path.resolve(projectRoot)) {
279
+ throw new Error('SESSION_SELECTION_TOKEN_WORKSPACE_MISMATCH: the token belongs to another workspace')
280
+ }
281
+ if (stored.expiresAt <= this.now()) {
282
+ throw new Error('SESSION_SELECTION_TOKEN_EXPIRED: preview again before running the diagnostic')
283
+ }
284
+ this.purge()
285
+ return stored
286
+ }
287
+
288
+ purge() {
289
+ const now = this.now()
290
+ for (const [token, value] of this.tokens) {
291
+ if (value.expiresAt <= now) this.tokens.delete(token)
292
+ }
293
+ }
294
+ }
package/lib/setup.js CHANGED
@@ -5,14 +5,14 @@ import process from 'node:process'
5
5
  import { fileURLToPath } from 'node:url'
6
6
 
7
7
  import { runProcess } from './process.js'
8
-
9
- export const DSH_VERSION = '0.1.0-rc.6'
8
+ import { DSH_RUNTIME_VERSION } from './runtime-identity.js'
10
9
 
11
10
  const packageJson = JSON.parse(
12
11
  await readFile(new URL('../package.json', import.meta.url), 'utf8'),
13
12
  )
14
13
 
15
14
  export const INTEGRATION_VERSION = packageJson.version
15
+ export const DSH_VERSION = DSH_RUNTIME_VERSION
16
16
 
17
17
  function requireValue(args, index, flag) {
18
18
  const value = args[index + 1]
@@ -281,9 +281,7 @@ export async function setupIntegration(raw = {}, dependencies = {}) {
281
281
  const patchChanged = await writeProfilePatch(config.patchFile, config)
282
282
  const harborVersion = await run(config.harborBin, ['--version'], { timeoutMs: 10_000 })
283
283
  const plugins = await run(config.harborBin, ['plugins', 'list'], { timeoutMs: 10_000 })
284
- if (!plugins.stdout.includes('dsh-evolution')) {
285
- throw new Error('Harbor installed, but its dsh-evolution plugin entry point was not discovered')
286
- }
284
+ verifyHarborPlugins(plugins.stdout)
287
285
  await run(config.harborDshBin, ['--help'], { timeoutMs: 10_000 })
288
286
 
289
287
  return {
@@ -295,6 +293,14 @@ export async function setupIntegration(raw = {}, dependencies = {}) {
295
293
  }
296
294
  }
297
295
 
296
+ export function verifyHarborPlugins(output) {
297
+ const missing = ['dsh-evolution', 'dsh-historical-evaluation']
298
+ .filter(name => !String(output).includes(name))
299
+ if (missing.length) {
300
+ throw new Error(`Harbor installed, but these plugin entry points were not discovered: ${missing.join(', ')}`)
301
+ }
302
+ }
303
+
298
304
  function shellQuote(value) {
299
305
  return `'${String(value).replaceAll("'", `'"'"'`)}'`
300
306
  }
package/lib/version.js ADDED
@@ -0,0 +1,128 @@
1
+ export const NPM_PACKAGE_NAME = 'dsh-harbor-evolution'
2
+ export const NPM_LATEST_URL = `https://registry.npmjs.org/${NPM_PACKAGE_NAME}/latest`
3
+ export const RELEASES_URL = 'https://github.com/istarwyh/harbor-self-evolving/releases'
4
+
5
+ const DEFAULT_CACHE_TTL_MS = 6 * 60 * 60 * 1_000
6
+ const DEFAULT_FAILURE_TTL_MS = 5 * 60 * 1_000
7
+ const DEFAULT_TIMEOUT_MS = 2_500
8
+
9
+ export function parseSemver(value) {
10
+ const match = String(value ?? '').match(/^v?(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/)
11
+ if (!match) return undefined
12
+ return {
13
+ major: Number(match[1]),
14
+ minor: Number(match[2]),
15
+ patch: Number(match[3]),
16
+ prerelease: match[4] ? match[4].split('.') : [],
17
+ }
18
+ }
19
+
20
+ function compareIdentifier(left, right) {
21
+ const leftNumber = /^\d+$/.test(left) ? Number(left) : undefined
22
+ const rightNumber = /^\d+$/.test(right) ? Number(right) : undefined
23
+ if (leftNumber !== undefined && rightNumber !== undefined) return Math.sign(leftNumber - rightNumber)
24
+ if (leftNumber !== undefined) return -1
25
+ if (rightNumber !== undefined) return 1
26
+ return left.localeCompare(right)
27
+ }
28
+
29
+ export function compareSemver(leftValue, rightValue) {
30
+ const left = parseSemver(leftValue)
31
+ const right = parseSemver(rightValue)
32
+ if (!left || !right) return undefined
33
+ for (const key of ['major', 'minor', 'patch']) {
34
+ if (left[key] !== right[key]) return Math.sign(left[key] - right[key])
35
+ }
36
+ if (!left.prerelease.length && !right.prerelease.length) return 0
37
+ if (!left.prerelease.length) return 1
38
+ if (!right.prerelease.length) return -1
39
+ const length = Math.max(left.prerelease.length, right.prerelease.length)
40
+ for (let index = 0; index < length; index += 1) {
41
+ if (left.prerelease[index] === undefined) return -1
42
+ if (right.prerelease[index] === undefined) return 1
43
+ const comparison = compareIdentifier(left.prerelease[index], right.prerelease[index])
44
+ if (comparison) return Math.sign(comparison)
45
+ }
46
+ return 0
47
+ }
48
+
49
+ function shellQuote(value) {
50
+ return `'${String(value).replaceAll("'", `'"'"'`)}'`
51
+ }
52
+
53
+ export function renderUpdateCommand(latestVersion, projectRoot) {
54
+ if (!parseSemver(latestVersion)) throw new Error('latestVersion must be a valid semantic version')
55
+ return `npx --yes ${NPM_PACKAGE_NAME}@${latestVersion} setup --project-root ${shellQuote(projectRoot)}`
56
+ }
57
+
58
+ function buildResult(currentVersion, latestVersion, projectRoot, checkedAt, options = {}) {
59
+ const comparison = compareSemver(currentVersion, latestVersion)
60
+ if (comparison === undefined) {
61
+ return { status: 'unavailable', currentVersion, checkedAt, source: options.source, stale: options.stale }
62
+ }
63
+ const updateAvailable = comparison < 0
64
+ return {
65
+ status: updateAvailable ? 'update-available' : 'up-to-date',
66
+ currentVersion,
67
+ latestVersion,
68
+ checkedAt,
69
+ source: options.source,
70
+ stale: options.stale,
71
+ releaseUrl: updateAvailable ? `${RELEASES_URL}/tag/v${latestVersion}` : RELEASES_URL,
72
+ command: updateAvailable ? renderUpdateCommand(latestVersion, projectRoot) : undefined,
73
+ }
74
+ }
75
+
76
+ export function createVersionChecker(options = {}) {
77
+ const fetchImpl = options.fetchImpl ?? globalThis.fetch
78
+ const now = options.now ?? Date.now
79
+ const cacheTtlMs = options.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS
80
+ const failureTtlMs = options.failureTtlMs ?? DEFAULT_FAILURE_TTL_MS
81
+ const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS
82
+ let successful
83
+ let failedAt = 0
84
+
85
+ return async function checkVersion({ currentVersion, projectRoot, refresh = false }) {
86
+ const checkedAt = new Date(now()).toISOString()
87
+ if (!refresh && successful && now() < successful.expiresAt) {
88
+ return buildResult(currentVersion, successful.latestVersion, projectRoot, successful.checkedAt, { source: 'cache' })
89
+ }
90
+ if (!refresh && !successful && failedAt && now() - failedAt < failureTtlMs) {
91
+ return { status: 'unavailable', currentVersion, checkedAt, source: 'cache' }
92
+ }
93
+ if (typeof fetchImpl !== 'function') {
94
+ failedAt = now()
95
+ return { status: 'unavailable', currentVersion, checkedAt, source: 'host' }
96
+ }
97
+
98
+ const controller = new AbortController()
99
+ const timeout = setTimeout(() => controller.abort(), timeoutMs)
100
+ try {
101
+ const response = await fetchImpl(NPM_LATEST_URL, {
102
+ headers: { accept: 'application/json' },
103
+ redirect: 'error',
104
+ signal: controller.signal,
105
+ })
106
+ if (!response.ok) throw new Error('registry request failed')
107
+ const manifest = await response.json()
108
+ if (manifest?.name !== NPM_PACKAGE_NAME || !parseSemver(manifest.version)) {
109
+ throw new Error('registry response is invalid')
110
+ }
111
+ successful = {
112
+ latestVersion: manifest.version,
113
+ checkedAt,
114
+ expiresAt: now() + cacheTtlMs,
115
+ }
116
+ failedAt = 0
117
+ return buildResult(currentVersion, successful.latestVersion, projectRoot, checkedAt, { source: 'registry' })
118
+ } catch {
119
+ failedAt = now()
120
+ if (successful) {
121
+ return buildResult(currentVersion, successful.latestVersion, projectRoot, successful.checkedAt, { source: 'cache', stale: true })
122
+ }
123
+ return { status: 'unavailable', currentVersion, checkedAt, source: 'registry' }
124
+ } finally {
125
+ clearTimeout(timeout)
126
+ }
127
+ }
128
+ }
package/lib/web.js CHANGED
@@ -8,6 +8,8 @@ export const COMPARE_ROUTE = '/_dsh/harbor-evolution/compare'
8
8
  export const GOVERNANCE_ROUTE = '/_dsh/harbor-evolution/governance'
9
9
  export const EVALUATOR_ROUTE = '/_dsh/harbor-evolution/evaluator'
10
10
  export const META_ROUTE = '/_dsh/harbor-evolution/meta'
11
+ export const PROJECT_ROOT_ROUTE = '/_dsh/harbor-evolution/project-root'
12
+ export const VERSION_ROUTE = '/_dsh/harbor-evolution/version'
11
13
  const MAX_MUTATION_BYTES = 256 * 1024
12
14
 
13
15
  function sendJson(response, status, body) {
@@ -64,7 +66,7 @@ export function createApiHandler(load, code = 'request-failed') {
64
66
  }
65
67
 
66
68
  export function createDashboardHandler(service) {
67
- return createApiHandler(() => service.dashboard(), 'dashboard-unavailable')
69
+ return createApiHandler(args => service.dashboard(args), 'dashboard-unavailable')
68
70
  }
69
71
 
70
72
  export function createMutationHandler(update, code = 'update-failed') {
@@ -116,6 +118,8 @@ export function installDashboardWeb(ctx, service) {
116
118
  [GOVERNANCE_ROUTE, createApiHandler(args => service.governance(args), 'governance-unavailable')],
117
119
  [EVALUATOR_ROUTE, createMutationHandler(args => service.evaluator(args), 'evaluator-update-failed')],
118
120
  [META_ROUTE, createApiHandler(args => service.meta(args), 'meta-evaluation-unavailable')],
121
+ [VERSION_ROUTE, createApiHandler(args => service.version(args), 'version-check-unavailable')],
122
+ [PROJECT_ROOT_ROUTE, createMutationHandler(args => service.setProjectRoot(args), 'project-root-update-failed')],
119
123
  ]
120
124
  for (const [route, handler] of routes) {
121
125
  webCtx.effect(() => webCtx.webServer.register({ kind: 'exact', path: route, handler }), `harbor-evolution: ${route}`)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-harbor-evolution",
3
- "version": "0.7.2",
3
+ "version": "0.8.0",
4
4
  "description": "DeepSeek Harness plugin and bundled Skill for safely evolving Cordis Candidates with Harbor.",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -9,6 +9,11 @@
9
9
  "./client": "./lib/client.js",
10
10
  "./cordis.patch.yml": "./cordis.patch.yml",
11
11
  "./schemas/evaluation-result.schema.json": "./schemas/evaluation-result.schema.json",
12
+ "./schemas/evaluation-result-v2.schema.json": "./schemas/evaluation-result-v2.schema.json",
13
+ "./schemas/historical-generation-batch.schema.json": "./schemas/historical-generation-batch.schema.json",
14
+ "./schemas/dsh-session-observation.schema.json": "./schemas/dsh-session-observation.schema.json",
15
+ "./schemas/historical-evaluation-context.schema.json": "./schemas/historical-evaluation-context.schema.json",
16
+ "./schemas/historical-evaluation-summary.schema.json": "./schemas/historical-evaluation-summary.schema.json",
12
17
  "./schemas/ground-truth.schema.json": "./schemas/ground-truth.schema.json",
13
18
  "./schemas/evaluator-observations.schema.json": "./schemas/evaluator-observations.schema.json",
14
19
  "./schemas/meta-evaluation-report.schema.json": "./schemas/meta-evaluation-report.schema.json",
@@ -48,9 +53,14 @@
48
53
  "platform": "web"
49
54
  }
50
55
  },
56
+ "harborEvolution": {
57
+ "runtimePolicy": "follow-latest",
58
+ "dshRuntimeVersion": "latest",
59
+ "candidateAcpPackage": "@deepseek-ai/dsh-acp-demo@latest"
60
+ },
51
61
  "peerDependencies": {
52
- "@deepseek-ai/dsh-skill": "^0.1.0-rc.6",
53
- "@deepseek-ai/dsh-tools": "^0.1.0-rc.6"
62
+ "@deepseek-ai/dsh-skill": ">=0.1.0-rc.6",
63
+ "@deepseek-ai/dsh-tools": ">=0.1.0-rc.6"
54
64
  },
55
65
  "dependencies": {
56
66
  "@deepseek-ai/schemastery": "3.18.1"
@@ -0,0 +1,69 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://github.com/istarwyh/harbor-self-evolving/schemas/dsh-session-observation.schema.json",
4
+ "title": "DSH Session Observation v1",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": ["schema_version", "protocol", "record_kind", "execution_mode", "trial_id", "source", "generator", "task", "visible_transcript", "execution", "feedback", "completeness", "redaction", "digest"],
8
+ "properties": {
9
+ "schema_version": { "const": 1 },
10
+ "protocol": { "const": "dsh-session-observation/v1" },
11
+ "record_kind": { "const": "dsh-session" },
12
+ "execution_mode": { "const": "observe-existing" },
13
+ "trial_id": { "type": "string", "minLength": 1 },
14
+ "source": {
15
+ "type": "object",
16
+ "required": ["ref", "captured_through_seq", "source_digest", "created_at", "last_activity_at", "last_turn_reason", "session_format_version"],
17
+ "properties": {
18
+ "ref": { "$ref": "#/$defs/digest" },
19
+ "captured_through_seq": { "type": "integer", "minimum": 0 },
20
+ "source_digest": { "$ref": "#/$defs/digest" },
21
+ "created_at": { "type": ["string", "null"], "format": "date-time" },
22
+ "last_activity_at": { "type": ["string", "null"], "format": "date-time" },
23
+ "last_turn_reason": { "type": ["string", "null"] },
24
+ "session_format_version": { "type": "integer", "minimum": 0 }
25
+ }
26
+ },
27
+ "generator": { "type": "object", "required": ["agent_preset", "model_segments"] },
28
+ "task": {
29
+ "type": "object",
30
+ "required": ["title", "initial_user_goal", "turn_count"],
31
+ "properties": {
32
+ "title": { "type": "string", "minLength": 1 },
33
+ "initial_user_goal": { "type": "string", "minLength": 1 },
34
+ "turn_count": { "type": "integer", "minimum": 1 }
35
+ }
36
+ },
37
+ "visible_transcript": {
38
+ "type": "array",
39
+ "minItems": 2,
40
+ "items": {
41
+ "type": "object",
42
+ "additionalProperties": false,
43
+ "required": ["event_seq", "message_ref", "role", "content", "time"],
44
+ "properties": {
45
+ "event_seq": { "type": "integer", "minimum": 0 },
46
+ "message_ref": { "$ref": "#/$defs/digest" },
47
+ "role": { "enum": ["user", "assistant"] },
48
+ "content": {
49
+ "type": "array",
50
+ "minItems": 1,
51
+ "items": {
52
+ "type": "object",
53
+ "additionalProperties": false,
54
+ "required": ["type", "text"],
55
+ "properties": { "type": { "const": "text" }, "text": { "type": "string", "minLength": 1 } }
56
+ }
57
+ },
58
+ "time": { "type": ["string", "null"], "format": "date-time" }
59
+ }
60
+ }
61
+ },
62
+ "execution": { "type": "object", "required": ["tools", "turns", "usage"] },
63
+ "feedback": { "type": "object", "required": ["items"] },
64
+ "completeness": { "type": "object", "required": ["transcript_complete", "tool_payloads_complete", "attachments_complete", "truncations"] },
65
+ "redaction": { "type": "object", "required": ["replacements", "truncations", "omitted_blocks"] },
66
+ "digest": { "$ref": "#/$defs/digest" }
67
+ },
68
+ "$defs": { "digest": { "type": "string", "pattern": "^sha256:[0-9a-f]{64}$" } }
69
+ }
@@ -0,0 +1,45 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://github.com/istarwyh/harbor-self-evolving/schemas/evaluation-result-v2.schema.json",
4
+ "title": "Harbor DSH Evaluation Result v2",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": ["schema_version", "protocol", "criteria", "aggregate"],
8
+ "properties": {
9
+ "schema_version": { "const": 2 },
10
+ "protocol": { "const": "evaluation-result/v2" },
11
+ "criteria": {
12
+ "type": "array",
13
+ "minItems": 1,
14
+ "items": {
15
+ "type": "object",
16
+ "additionalProperties": false,
17
+ "required": ["id", "status", "score", "reason", "recommendation", "evidence_refs"],
18
+ "properties": {
19
+ "id": { "type": "string", "minLength": 1 },
20
+ "status": { "enum": ["scored", "not-applicable", "insufficient-evidence", "evaluation-error"] },
21
+ "score": { "type": ["number", "null"], "enum": [0, 0.5, 1, null] },
22
+ "reason": { "type": "string", "minLength": 1 },
23
+ "recommendation": { "type": "string", "minLength": 1 },
24
+ "evidence_refs": { "type": "array", "items": { "type": "string", "minLength": 1 } }
25
+ },
26
+ "allOf": [
27
+ { "if": { "properties": { "status": { "const": "scored" } } }, "then": { "properties": { "score": { "enum": [0, 0.5, 1] } } } },
28
+ { "if": { "properties": { "status": { "enum": ["not-applicable", "insufficient-evidence", "evaluation-error"] } } }, "then": { "properties": { "score": { "type": "null" } } } }
29
+ ]
30
+ }
31
+ },
32
+ "aggregate": {
33
+ "type": "object",
34
+ "additionalProperties": false,
35
+ "required": ["metric_id", "value", "scored_criteria", "total_criteria", "coverage"],
36
+ "properties": {
37
+ "metric_id": { "type": "string", "minLength": 1 },
38
+ "value": { "type": ["number", "null"], "minimum": 0, "maximum": 1 },
39
+ "scored_criteria": { "type": "integer", "minimum": 0 },
40
+ "total_criteria": { "type": "integer", "minimum": 1 },
41
+ "coverage": { "type": "number", "minimum": 0, "maximum": 1 }
42
+ }
43
+ }
44
+ }
45
+ }