dsh-decision-layer 0.7.4 → 0.7.5
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.en.md +5 -3
- package/README.md +5 -3
- package/docs/en/DETAIL.md +1 -0
- package/docs/zh/DETAIL.md +1 -0
- package/lib/client.js +297 -3
- package/lib/host/backend.d.ts +3 -2
- package/lib/host/backend.js +61 -43
- package/lib/host/checkpoint-review.d.ts +27 -0
- package/lib/host/checkpoint-review.js +334 -0
- package/lib/host/config-store.js +21 -1
- package/lib/host/danger-gate.js +8 -6
- package/lib/host/evaluation-trace.d.ts +8 -0
- package/lib/host/evaluation-trace.js +70 -0
- package/lib/host/execution-observer.d.ts +26 -0
- package/lib/host/execution-observer.js +168 -0
- package/lib/host/index.d.ts +1 -0
- package/lib/host/index.js +61 -16
- package/lib/host/log-analyze.d.ts +18 -0
- package/lib/host/log-analyze.js +63 -6
- package/lib/host/log-row.d.ts +3 -0
- package/lib/host/log-row.js +66 -0
- package/lib/host/narrowing.js +11 -9
- package/lib/host/self-check.js +10 -5
- package/lib/host/session-state.d.ts +3 -1
- package/lib/host/session-state.js +30 -46
- package/lib/host/task-completion.js +6 -4
- package/lib/host/types.d.ts +43 -3
- package/package.json +1 -1
package/lib/host/narrowing.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createEvaluationTrace } from "./evaluation-trace.js";
|
|
1
2
|
import { taskExcerpt } from "./excerpt.js";
|
|
2
3
|
import { errorReason, isObject, isProbability } from "./types.js";
|
|
3
4
|
const narrowApplied = (record) => {
|
|
@@ -114,14 +115,14 @@ function createNarrowing({
|
|
|
114
115
|
} catch {
|
|
115
116
|
}
|
|
116
117
|
};
|
|
117
|
-
const evaluateSplit = async (state, tools, signal) => {
|
|
118
|
+
const evaluateSplit = async (evaluate2, state, tools, signal) => {
|
|
118
119
|
const answers = /* @__PURE__ */ Object.create(null);
|
|
119
120
|
let anyOk = false;
|
|
120
121
|
for (let i = 0; i < tools.length; i += concurrency) {
|
|
121
122
|
const group = tools.slice(i, i + concurrency);
|
|
122
123
|
const settled = await Promise.all(group.map(async (tool) => {
|
|
123
124
|
try {
|
|
124
|
-
const reply = await
|
|
125
|
+
const reply = await evaluate2(relevanceQuestions(state, [tool]), { signal });
|
|
125
126
|
const answer = reply?.answers?.[tool.name];
|
|
126
127
|
return isObject(answer) ? [tool.name, answer] : void 0;
|
|
127
128
|
} catch {
|
|
@@ -161,28 +162,29 @@ function createNarrowing({
|
|
|
161
162
|
const effectiveMode = overCap ? "observe" : mode;
|
|
162
163
|
const judged = overCap ? optional.slice(0, cap) : optional;
|
|
163
164
|
const judgedNames = judged.map((tool) => tool.name);
|
|
165
|
+
const trace = createEvaluationTrace(evaluate);
|
|
164
166
|
let result = null;
|
|
165
167
|
if (batchMode === "split" || adaptiveSplit) {
|
|
166
|
-
result = await evaluateSplit(turn?.state, judged, turn?.signal);
|
|
168
|
+
result = await evaluateSplit(trace.evaluate, turn?.state, judged, turn?.signal);
|
|
167
169
|
if (!result) {
|
|
168
|
-
record(turn, "error", { reason: "unreachable", mode: effectiveMode });
|
|
170
|
+
record(turn, "error", { ...trace.detail(), reason: "unreachable", mode: effectiveMode });
|
|
169
171
|
return { ...skip, keep: names, mode: effectiveMode };
|
|
170
172
|
}
|
|
171
173
|
} else {
|
|
172
174
|
try {
|
|
173
|
-
const reply = await evaluate(relevanceQuestions(turn?.state, judged), { signal: turn?.signal });
|
|
175
|
+
const reply = await trace.evaluate(relevanceQuestions(turn?.state, judged), { signal: turn?.signal });
|
|
174
176
|
result = reply;
|
|
175
177
|
} catch (error) {
|
|
176
178
|
const reason = errorReason(error);
|
|
177
179
|
if (batchMode === "auto" && reason === "http-error") {
|
|
178
180
|
adaptiveSplit = true;
|
|
179
|
-
result = await evaluateSplit(turn?.state, judged, turn?.signal);
|
|
181
|
+
result = await evaluateSplit(trace.evaluate, turn?.state, judged, turn?.signal);
|
|
180
182
|
if (!result) {
|
|
181
|
-
record(turn, "error", { reason, mode: effectiveMode });
|
|
183
|
+
record(turn, "error", { ...trace.detail(), reason, mode: effectiveMode });
|
|
182
184
|
return { ...skip, keep: names, mode: effectiveMode };
|
|
183
185
|
}
|
|
184
186
|
} else {
|
|
185
|
-
record(turn, "error", { reason, mode: effectiveMode });
|
|
187
|
+
record(turn, "error", { ...trace.detail(), reason, mode: effectiveMode });
|
|
186
188
|
return { ...skip, keep: names, mode: effectiveMode };
|
|
187
189
|
}
|
|
188
190
|
}
|
|
@@ -198,7 +200,7 @@ function createNarrowing({
|
|
|
198
200
|
detail.reason = "too-many-candidates";
|
|
199
201
|
detail.candidates = optional.length;
|
|
200
202
|
} else if (dropped > 0 && !trustworthy) detail.reason = "low-keep";
|
|
201
|
-
record(turn, dropped > 0 ? "applied" : "ok", detail);
|
|
203
|
+
record(turn, dropped > 0 ? "applied" : "ok", { ...detail, ...trace.detail() });
|
|
202
204
|
return { evaluated: true, applied, keep, drop, mode: effectiveMode, overCap, trustworthy };
|
|
203
205
|
}
|
|
204
206
|
};
|
package/lib/host/self-check.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createEvaluationTrace } from "./evaluation-trace.js";
|
|
1
2
|
import { taskExcerpt } from "./excerpt.js";
|
|
2
3
|
import { createSteerOnce } from "./steer-once.js";
|
|
3
4
|
import { errorReason, isProbability } from "./types.js";
|
|
@@ -90,16 +91,17 @@ function createSelfCheck({ evaluate, sessions, steer, settings, minConfidence =
|
|
|
90
91
|
const taskDetail = task ? { task } : {};
|
|
91
92
|
if (Number.isSafeInteger(turn?.turn) && turn?.turn >= 0) taskDetail.turn = turn?.turn;
|
|
92
93
|
const { mode, rubric, lowScoreThreshold, minConfidence: confidenceFloor, trendMinConfidence, trendRun, trendSevereRun } = await resolveSettings();
|
|
94
|
+
const trace = createEvaluationTrace(evaluate);
|
|
93
95
|
let answer;
|
|
94
96
|
try {
|
|
95
|
-
const result = await evaluate(scoreQuestion(output, rubric, turn?.context), { signal: turn?.signal });
|
|
97
|
+
const result = await trace.evaluate(scoreQuestion(output, rubric, turn?.context), { signal: turn?.signal });
|
|
96
98
|
answer = result?.answers?.quality;
|
|
97
99
|
} catch (error) {
|
|
98
|
-
record(turn, "error", { ...taskDetail, reason: errorReason(error) });
|
|
100
|
+
record(turn, "error", { ...taskDetail, ...trace.detail(), reason: errorReason(error) });
|
|
99
101
|
return { evaluated: false, lowScore: false, steered: false };
|
|
100
102
|
}
|
|
101
103
|
if (!answer || answer.type !== "score" || typeof answer.score !== "number" || !Number.isFinite(answer.score)) {
|
|
102
|
-
record(turn, "error", { ...taskDetail, reason: "invalid-response" });
|
|
104
|
+
record(turn, "error", { ...taskDetail, ...trace.detail(), reason: "invalid-response" });
|
|
103
105
|
return { evaluated: false, lowScore: false, steered: false };
|
|
104
106
|
}
|
|
105
107
|
const confidence = typeof answer.confidence === "number" && Number.isFinite(answer.confidence) ? answer.confidence : void 0;
|
|
@@ -107,10 +109,13 @@ function createSelfCheck({ evaluate, sessions, steer, settings, minConfidence =
|
|
|
107
109
|
const lowScore = answer.score <= lowScoreThreshold;
|
|
108
110
|
const trend = { confident: lowScore && confidence !== void 0 && confidence >= trendMinConfidence, trendRun, trendSevereRun };
|
|
109
111
|
if (lowScore && confidence !== void 0 && !trend.confident) detail.lowConfidence = true;
|
|
110
|
-
|
|
111
|
-
|
|
112
|
+
if (!lowScore) {
|
|
113
|
+
record(turn, "ok", { score: answer.score, ...detail, ...trace.detail(), steered: false }, trend);
|
|
114
|
+
return { evaluated: true, lowScore: false, steered: false, score: answer.score };
|
|
115
|
+
}
|
|
112
116
|
const confident = confidence !== void 0 && confidence >= confidenceFloor;
|
|
113
117
|
const steered = mode === "steer" && confident && typeof steer === "function" && turn?.agent ? steerOnce.attempt(turn, () => steer(turn.agent, STEER_TEXT)) : false;
|
|
118
|
+
record(turn, "low", { score: answer.score, ...detail, ...trace.detail(), steered }, trend);
|
|
114
119
|
return { evaluated: true, lowScore: true, steered, score: answer.score };
|
|
115
120
|
}
|
|
116
121
|
};
|
|
@@ -5,8 +5,9 @@ export type SessionStateOptions = {
|
|
|
5
5
|
onLog?: (entry: SessionSinkRow) => void;
|
|
6
6
|
loadState?: (id: string) => Promise<unknown> | unknown;
|
|
7
7
|
persistState?: (id: string, state: SessionState) => unknown;
|
|
8
|
+
onEnabled?: (id: string, enabled: boolean) => void;
|
|
8
9
|
};
|
|
9
|
-
export declare function createSessionState({ maxSessions, maxLogEntries, onLog, loadState, persistState }?: SessionStateOptions): {
|
|
10
|
+
export declare function createSessionState({ maxSessions, maxLogEntries, onLog, loadState, persistState, onEnabled }?: SessionStateOptions): {
|
|
10
11
|
ensureLoaded(id: unknown): Promise<void>;
|
|
11
12
|
setEnabled(id: unknown, enabled: unknown): void;
|
|
12
13
|
snapshot(id: unknown): SessionSnapshot;
|
|
@@ -15,5 +16,6 @@ export declare function createSessionState({ maxSessions, maxLogEntries, onLog,
|
|
|
15
16
|
recordCheck(id: unknown, outcome: string, trend?: Record<string, unknown>): void;
|
|
16
17
|
recordNarrow(id: unknown, outcome: string, dropped?: number, applied?: boolean): void;
|
|
17
18
|
recordComplete(id: unknown, outcome: string, detail?: Record<string, unknown>): void;
|
|
19
|
+
recordCheckpoint(id: unknown, outcome: string): void;
|
|
18
20
|
log(id: unknown, entry: Record<string, unknown>): void;
|
|
19
21
|
};
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
|
-
import {
|
|
2
|
+
import { sanitizeLogRow } from "./log-row.js";
|
|
3
3
|
import { advanceRun, severityOf } from "./quality-trend.js";
|
|
4
|
-
const KINDS = ["gate", "check", "narrow", "complete"];
|
|
5
4
|
const SEVERITIES = ["normal", "warn", "severe"];
|
|
6
5
|
function sessionKey(value) {
|
|
7
6
|
if (typeof value !== "string" || !value.trim() || value.length > 512) throw new Error("Invalid session ID");
|
|
@@ -15,15 +14,13 @@ function emptyCounters() {
|
|
|
15
14
|
check: { attempts: 0, failures: 0, low: 0, run: 0, severity: "normal" },
|
|
16
15
|
narrow: { attempts: 0, failures: 0, applied: 0, dropped: 0 },
|
|
17
16
|
complete: { attempts: 0, failures: 0, unsatisfied: 0, satisfied: 0, insufficient: 0, steered: 0 },
|
|
17
|
+
checkpoint: { attempts: 0, failures: 0, concern: 0, uncertain: 0, skipped: 0 },
|
|
18
18
|
log: []
|
|
19
19
|
};
|
|
20
20
|
}
|
|
21
21
|
function emptyState() {
|
|
22
22
|
return { enabled: true, ...emptyCounters() };
|
|
23
23
|
}
|
|
24
|
-
function safeTool(value) {
|
|
25
|
-
return typeof value === "string" && value.trim() ? value.trim().slice(0, 128) : void 0;
|
|
26
|
-
}
|
|
27
24
|
const toCount = (value) => Number.isSafeInteger(value) && value >= 0 ? value : 0;
|
|
28
25
|
const asRecord = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
29
26
|
function coerceState(raw) {
|
|
@@ -71,12 +68,12 @@ function coerceState(raw) {
|
|
|
71
68
|
state.complete.insufficient = toCount(k.insufficient);
|
|
72
69
|
state.complete.steered = toCount(k.steered);
|
|
73
70
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
71
|
+
const cp = asRecord(record.checkpoint);
|
|
72
|
+
if (cp) for (const key of ["attempts", "failures", "concern", "uncertain", "skipped"]) state.checkpoint[key] = toCount(cp[key]);
|
|
73
|
+
if (Array.isArray(record.log)) state.log = record.log.map(sanitizeLogRow).filter((row) => Boolean(row));
|
|
77
74
|
return state;
|
|
78
75
|
}
|
|
79
|
-
function createSessionState({ maxSessions = 1024, maxLogEntries = 50, onLog, loadState, persistState } = {}) {
|
|
76
|
+
function createSessionState({ maxSessions = 1024, maxLogEntries = 50, onLog, loadState, persistState, onEnabled } = {}) {
|
|
80
77
|
if (!Number.isSafeInteger(maxSessions) || maxSessions < 1) throw new Error("Invalid session limit");
|
|
81
78
|
if (!Number.isSafeInteger(maxLogEntries) || maxLogEntries < 1) throw new Error("Invalid log limit");
|
|
82
79
|
const sessions = /* @__PURE__ */ new Map();
|
|
@@ -142,6 +139,10 @@ function createSessionState({ maxSessions = 1024, maxLogEntries = 50, onLog, loa
|
|
|
142
139
|
if (typeof enabled !== "boolean") throw new Error("Invalid enabled value");
|
|
143
140
|
const state = writable(id);
|
|
144
141
|
state.enabled = enabled;
|
|
142
|
+
try {
|
|
143
|
+
onEnabled?.(sessionKey(id), enabled);
|
|
144
|
+
} catch {
|
|
145
|
+
}
|
|
145
146
|
bump(sessionKey(id), state);
|
|
146
147
|
},
|
|
147
148
|
snapshot(id) {
|
|
@@ -152,6 +153,7 @@ function createSessionState({ maxSessions = 1024, maxLogEntries = 50, onLog, loa
|
|
|
152
153
|
if (state.check.attempts > 0) value.check = { attempts: state.check.attempts, failures: state.check.failures, low: state.check.low, trend: { run: state.check.run, severity: state.check.severity } };
|
|
153
154
|
if (state.narrow.attempts > 0) value.narrow = { ...state.narrow };
|
|
154
155
|
if (state.complete.attempts > 0) value.complete = { ...state.complete };
|
|
156
|
+
if (state.checkpoint.attempts > 0 || state.checkpoint.skipped > 0) value.checkpoint = { ...state.checkpoint };
|
|
155
157
|
if (state.log.length > 0) value.log = state.log.map((entry) => ({ ...entry }));
|
|
156
158
|
return value;
|
|
157
159
|
},
|
|
@@ -233,48 +235,30 @@ function createSessionState({ maxSessions = 1024, maxLogEntries = 50, onLog, loa
|
|
|
233
235
|
}
|
|
234
236
|
bump(sessionKey(id), state);
|
|
235
237
|
},
|
|
236
|
-
|
|
237
|
-
if (
|
|
238
|
-
const
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
if (typeof entry.subject === "string" && entry.subject.trim()) record.subject = redactCredentials(entry.subject).replace(/\s+/g, " ").trim().slice(0, 120);
|
|
247
|
-
if (typeof entry.command === "string" && entry.command.trim()) record.command = entry.command.replace(/\s+/g, " ").trim().slice(0, 512);
|
|
248
|
-
if (typeof entry.path === "string" && entry.path.trim()) record.path = entry.path.replace(/\s+/g, " ").trim().slice(0, 512);
|
|
249
|
-
if (typeof entry.task === "string" && entry.task.trim()) record.task = redactCredentials(entry.task).replace(/\s+/g, " ").trim().slice(0, 512);
|
|
250
|
-
if (Number.isSafeInteger(entry.turn) && entry.turn >= 0) record.turn = entry.turn;
|
|
251
|
-
if (Number.isSafeInteger(entry.step) && entry.step >= 0) record.step = entry.step;
|
|
252
|
-
if (typeof entry.mode === "string" && entry.mode) record.mode = entry.mode.slice(0, 16);
|
|
253
|
-
if (typeof entry.score === "number" && Number.isFinite(entry.score)) record.score = Math.round(entry.score * 100) / 100;
|
|
254
|
-
if (typeof entry.confidence === "number" && Number.isFinite(entry.confidence)) record.confidence = Math.round(entry.confidence * 100) / 100;
|
|
255
|
-
if (Number.isSafeInteger(entry.dropped) && entry.dropped >= 0) record.dropped = entry.dropped;
|
|
256
|
-
if (Number.isSafeInteger(entry.kept) && entry.kept >= 0) record.kept = entry.kept;
|
|
257
|
-
if (typeof entry.applied === "boolean") record.applied = entry.applied;
|
|
258
|
-
if (Number.isSafeInteger(entry.candidates) && entry.candidates >= 0) record.candidates = entry.candidates;
|
|
259
|
-
if (Number.isSafeInteger(entry.conditions) && entry.conditions >= 0) record.conditions = entry.conditions;
|
|
260
|
-
if (Number.isSafeInteger(entry.satisfied) && entry.satisfied >= 0) record.satisfied = entry.satisfied;
|
|
261
|
-
if (Number.isSafeInteger(entry.unsatisfied) && entry.unsatisfied >= 0) record.unsatisfied = entry.unsatisfied;
|
|
262
|
-
if (Number.isSafeInteger(entry.insufficient) && entry.insufficient >= 0) record.insufficient = entry.insufficient;
|
|
263
|
-
if (entry.steered === true) record.steered = true;
|
|
264
|
-
if (entry.lowConfidence === true) record.lowConfidence = true;
|
|
265
|
-
if (Array.isArray(entry.tools)) {
|
|
266
|
-
const names = entry.tools.filter((name) => typeof name === "string" && Boolean(name.trim())).slice(0, 12).map((name) => name.trim().slice(0, 64));
|
|
267
|
-
if (names.length > 0) record.tools = names;
|
|
238
|
+
recordCheckpoint(id, outcome) {
|
|
239
|
+
if (!["ok", "concern", "uncertain", "error", "skipped"].includes(outcome)) throw new Error("Invalid checkpoint outcome");
|
|
240
|
+
const state = writable(id);
|
|
241
|
+
const checkpoint = state.checkpoint;
|
|
242
|
+
if (outcome === "skipped") checkpoint.skipped++;
|
|
243
|
+
else {
|
|
244
|
+
checkpoint.attempts++;
|
|
245
|
+
if (outcome === "error") checkpoint.failures++;
|
|
246
|
+
else if (outcome === "concern") checkpoint.concern++;
|
|
247
|
+
else if (outcome === "uncertain") checkpoint.uncertain++;
|
|
268
248
|
}
|
|
249
|
+
bump(sessionKey(id), state);
|
|
250
|
+
},
|
|
251
|
+
log(id, entry) {
|
|
252
|
+
const sanitized = sanitizeLogRow(entry);
|
|
253
|
+
if (!sanitized) throw new Error("Invalid log entry");
|
|
254
|
+
const fullRow = { ...sanitized, id: randomUUID(), at: Date.now() };
|
|
255
|
+
const record = { ...fullRow };
|
|
256
|
+
if (record.tools) record.tools = record.tools.slice(0, 12).map((name) => name.slice(0, 64));
|
|
269
257
|
const state = writable(id);
|
|
270
258
|
state.log.push(record);
|
|
271
259
|
if (state.log.length > maxLogEntries) state.log.splice(0, state.log.length - maxLogEntries);
|
|
272
260
|
if (sink) {
|
|
273
|
-
const full = { ...
|
|
274
|
-
if (Array.isArray(entry.tools)) {
|
|
275
|
-
const allNames = entry.tools.filter((name) => typeof name === "string" && Boolean(name.trim())).map((name) => name.trim());
|
|
276
|
-
if (allNames.length > 0) full.tools = allNames;
|
|
277
|
-
}
|
|
261
|
+
const full = { ...fullRow, sessionId: sessionKey(id) };
|
|
278
262
|
try {
|
|
279
263
|
sink(full);
|
|
280
264
|
} catch {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createEvaluationTrace } from "./evaluation-trace.js";
|
|
1
2
|
import { taskExcerpt } from "./excerpt.js";
|
|
2
3
|
import { createSteerOnce } from "./steer-once.js";
|
|
3
4
|
import { errorReason, isObject, isProbability } from "./types.js";
|
|
@@ -122,16 +123,17 @@ function createTaskCompletion({ evaluate, sessions, steer, settings, minConfiden
|
|
|
122
123
|
return { ...skip };
|
|
123
124
|
}
|
|
124
125
|
}
|
|
126
|
+
const trace = createEvaluationTrace(evaluate);
|
|
125
127
|
let answers;
|
|
126
128
|
try {
|
|
127
|
-
const result = await evaluate(completionQuestions(conditions, evidence, output, turn?.context), { signal: turn?.signal });
|
|
129
|
+
const result = await trace.evaluate(completionQuestions(conditions, evidence, output, turn?.context), { signal: turn?.signal });
|
|
128
130
|
answers = isObject(result?.answers) ? result.answers : void 0;
|
|
129
131
|
} catch (error) {
|
|
130
|
-
record(turn, "error", { ...taskDetail, reason: errorReason(error) });
|
|
132
|
+
record(turn, "error", { ...taskDetail, ...trace.detail(), reason: errorReason(error) });
|
|
131
133
|
return { ...skip };
|
|
132
134
|
}
|
|
133
135
|
if (!answers) {
|
|
134
|
-
record(turn, "error", { ...taskDetail, reason: "invalid-response" });
|
|
136
|
+
record(turn, "error", { ...taskDetail, ...trace.detail(), reason: "invalid-response" });
|
|
135
137
|
return { ...skip };
|
|
136
138
|
}
|
|
137
139
|
let satisfied = 0, unsatisfied = 0, insufficient = 0;
|
|
@@ -153,7 +155,7 @@ function createTaskCompletion({ evaluate, sessions, steer, settings, minConfiden
|
|
|
153
155
|
const confident = unsatisfied > 0 && unsatisfiedMinConf >= confidenceFloor;
|
|
154
156
|
const steered = mode === "steer" && confident && typeof steer === "function" && turn?.agent ? steerOnce.attempt(turn, () => steer(turn.agent, STEER_TEXT)) : false;
|
|
155
157
|
if (steered) detail.steered = true;
|
|
156
|
-
record(turn, outcome, detail);
|
|
158
|
+
record(turn, outcome, { ...detail, ...trace.detail(), steered });
|
|
157
159
|
return { evaluated: true, steered, satisfied, unsatisfied, insufficient };
|
|
158
160
|
}
|
|
159
161
|
};
|
package/lib/host/types.d.ts
CHANGED
|
@@ -55,13 +55,24 @@ export type ChoiceAnswer = ScoredAnswer & {
|
|
|
55
55
|
};
|
|
56
56
|
export type Answer = NoulAnswer | ScoreAnswer | ChoiceAnswer;
|
|
57
57
|
export type AnswerMap = Record<string, Answer>;
|
|
58
|
+
export type EvaluationTelemetry = {
|
|
59
|
+
requestId: string;
|
|
60
|
+
latencyMs: number;
|
|
61
|
+
requestBytes?: number;
|
|
62
|
+
model?: string;
|
|
63
|
+
inputTokens?: number;
|
|
64
|
+
outputTokens?: number;
|
|
65
|
+
backendAttempts?: number;
|
|
66
|
+
};
|
|
58
67
|
export type EvaluateResult = {
|
|
59
68
|
model: string;
|
|
60
69
|
answers: AnswerMap;
|
|
61
70
|
usage?: {
|
|
62
|
-
input_tokens
|
|
63
|
-
output_tokens
|
|
71
|
+
input_tokens?: number;
|
|
72
|
+
output_tokens?: number;
|
|
64
73
|
};
|
|
74
|
+
/** Local measurement only; never part of the backend wire request. */
|
|
75
|
+
telemetry?: EvaluationTelemetry;
|
|
65
76
|
};
|
|
66
77
|
export type Evaluator = (input: EvaluateInput, options?: {
|
|
67
78
|
signal?: AbortSignal;
|
|
@@ -102,6 +113,19 @@ export type NarrowSettings = {
|
|
|
102
113
|
maxCandidates?: number;
|
|
103
114
|
batchMode?: BatchMode;
|
|
104
115
|
};
|
|
116
|
+
export type CheckpointSettings = {
|
|
117
|
+
enabled?: boolean;
|
|
118
|
+
heartbeatSteps?: number;
|
|
119
|
+
failureThreshold?: number;
|
|
120
|
+
timeoutMs?: number;
|
|
121
|
+
};
|
|
122
|
+
export type CheckpointTrigger = 'heartbeat' | 'unresolved_failure' | 'verification_after_change';
|
|
123
|
+
export type CheckpointOutcome = 'ok' | 'concern' | 'uncertain' | 'error' | 'skipped';
|
|
124
|
+
export type CheckpointCounters = SessionCounters & {
|
|
125
|
+
concern: number;
|
|
126
|
+
uncertain: number;
|
|
127
|
+
skipped: number;
|
|
128
|
+
};
|
|
105
129
|
export type CompleteSettings = {
|
|
106
130
|
mode?: CheckMode;
|
|
107
131
|
minConditions?: number;
|
|
@@ -126,6 +150,7 @@ export type ResolvedConfig = {
|
|
|
126
150
|
checkSettings?: CheckSettings;
|
|
127
151
|
narrowSettings?: NarrowSettings;
|
|
128
152
|
completeSettings?: CompleteSettings;
|
|
153
|
+
checkpointSettings?: CheckpointSettings;
|
|
129
154
|
features?: Features;
|
|
130
155
|
};
|
|
131
156
|
/** What the config page may read back: the API key is reduced to a flag. */
|
|
@@ -139,6 +164,7 @@ export type ConfigView = {
|
|
|
139
164
|
checkSettings?: CheckSettings;
|
|
140
165
|
narrowSettings?: NarrowSettings;
|
|
141
166
|
completeSettings?: CompleteSettings;
|
|
167
|
+
checkpointSettings?: CheckpointSettings;
|
|
142
168
|
features?: Features;
|
|
143
169
|
};
|
|
144
170
|
/** The quality-trend knobs, shared by the live path and the log backtest. */
|
|
@@ -148,7 +174,7 @@ export type TrendConfig = {
|
|
|
148
174
|
trendSevereRun?: number;
|
|
149
175
|
lowScoreThreshold?: number;
|
|
150
176
|
};
|
|
151
|
-
export type LogKind = 'gate' | 'check' | 'narrow' | 'complete';
|
|
177
|
+
export type LogKind = 'gate' | 'check' | 'narrow' | 'complete' | 'checkpoint';
|
|
152
178
|
/**
|
|
153
179
|
* One decision-log row. Fields are optional because rows are also read back
|
|
154
180
|
* from disk: persisted snapshots are untrusted input, and only `kind` is
|
|
@@ -183,6 +209,17 @@ export type LogRow = {
|
|
|
183
209
|
steered?: boolean;
|
|
184
210
|
lowConfidence?: boolean;
|
|
185
211
|
tools?: string[];
|
|
212
|
+
trigger?: CheckpointTrigger;
|
|
213
|
+
throughSeq?: number;
|
|
214
|
+
evidenceCount?: number;
|
|
215
|
+
requestId?: string;
|
|
216
|
+
latencyMs?: number;
|
|
217
|
+
requestBytes?: number;
|
|
218
|
+
model?: string;
|
|
219
|
+
inputTokens?: number;
|
|
220
|
+
outputTokens?: number;
|
|
221
|
+
backendAttempts?: number;
|
|
222
|
+
callId?: string;
|
|
186
223
|
};
|
|
187
224
|
/** The research-log sink record: the panel row plus its session tag. */
|
|
188
225
|
export type SessionSinkRow = LogRow & {
|
|
@@ -226,6 +263,7 @@ export type SessionState = {
|
|
|
226
263
|
check: CheckCounters;
|
|
227
264
|
narrow: NarrowCounters;
|
|
228
265
|
complete: CompleteCounters;
|
|
266
|
+
checkpoint: CheckpointCounters;
|
|
229
267
|
log: LogRow[];
|
|
230
268
|
};
|
|
231
269
|
/** The panel-facing projection; sections without attempts are omitted. */
|
|
@@ -246,6 +284,7 @@ export type SessionSnapshot = {
|
|
|
246
284
|
};
|
|
247
285
|
narrow?: NarrowCounters;
|
|
248
286
|
complete?: CompleteCounters;
|
|
287
|
+
checkpoint?: CheckpointCounters;
|
|
249
288
|
log?: LogRow[];
|
|
250
289
|
};
|
|
251
290
|
/** The counter shape rebuilt from persisted JSONL, without the master switch. */
|
|
@@ -262,6 +301,7 @@ export type SessionsFace = {
|
|
|
262
301
|
recordCheck?: (id: string, outcome: string, trend?: Record<string, unknown>) => void;
|
|
263
302
|
recordNarrow?: (id: string, outcome: string, dropped: number, applied: boolean) => void;
|
|
264
303
|
recordComplete?: (id: string, outcome: string, detail?: Record<string, unknown>) => void;
|
|
304
|
+
recordCheckpoint?: (id: string, outcome: CheckpointOutcome) => void;
|
|
265
305
|
ensureLoaded?: (id: string) => Promise<unknown>;
|
|
266
306
|
setEnabled?: (id: string, enabled: boolean) => void;
|
|
267
307
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-decision-layer",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.5",
|
|
4
4
|
"description": "A structured decision layer for the DeepSeek Harness agent loop: a danger-call gate, output self-check, tool narrowing, and loop guard backed by a pluggable adjudication model.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"dsh",
|