dsh-decision-layer 0.7.1 → 0.7.2

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.
Files changed (57) hide show
  1. package/README.en.md +8 -8
  2. package/README.md +10 -10
  3. package/lib/client.js +39 -14
  4. package/lib/host/backend.d.ts +15 -0
  5. package/lib/host/backend.js +149 -0
  6. package/lib/host/config-store.d.ts +12 -0
  7. package/lib/host/config-store.js +254 -0
  8. package/lib/host/danger-gate.d.ts +45 -0
  9. package/lib/host/danger-gate.js +196 -0
  10. package/lib/host/excerpt.d.ts +25 -0
  11. package/lib/host/excerpt.js +62 -0
  12. package/lib/host/index.d.ts +6 -0
  13. package/lib/host/index.js +290 -0
  14. package/lib/host/log-analyze.d.ts +121 -0
  15. package/lib/host/log-analyze.js +254 -0
  16. package/lib/host/log-read.d.ts +4 -0
  17. package/lib/host/log-read.js +34 -0
  18. package/lib/host/log-store.d.ts +15 -0
  19. package/lib/host/log-store.js +98 -0
  20. package/lib/host/loop-guard.d.ts +10 -0
  21. package/lib/host/loop-guard.js +52 -0
  22. package/lib/host/manager-api.d.ts +20 -0
  23. package/lib/host/manager-api.js +174 -0
  24. package/lib/host/narrowing.d.ts +48 -0
  25. package/lib/host/narrowing.js +213 -0
  26. package/lib/host/quality-trend.d.ts +14 -0
  27. package/lib/host/quality-trend.js +22 -0
  28. package/lib/host/self-check.d.ts +27 -0
  29. package/lib/host/self-check.js +104 -0
  30. package/lib/host/session-state.d.ts +19 -0
  31. package/lib/host/session-state.js +288 -0
  32. package/lib/host/state-store.d.ts +14 -0
  33. package/lib/host/state-store.js +109 -0
  34. package/lib/host/steer-once.d.ts +13 -0
  35. package/lib/host/steer-once.js +25 -0
  36. package/lib/host/task-completion.d.ts +29 -0
  37. package/lib/host/task-completion.js +148 -0
  38. package/lib/host/types.d.ts +343 -0
  39. package/lib/host/types.js +11 -0
  40. package/package.json +17 -10
  41. package/service/.gitkeep +0 -0
  42. package/service/backend.mjs +0 -127
  43. package/service/config-store.mjs +0 -311
  44. package/service/danger-gate.mjs +0 -182
  45. package/service/dsh.mjs +0 -288
  46. package/service/log-analyze.mjs +0 -257
  47. package/service/log-read.mjs +0 -27
  48. package/service/log-store.mjs +0 -82
  49. package/service/loop-guard.mjs +0 -51
  50. package/service/manager-api.mjs +0 -137
  51. package/service/narrowing.mjs +0 -226
  52. package/service/quality-trend.mjs +0 -29
  53. package/service/self-check.mjs +0 -131
  54. package/service/session-state.mjs +0 -260
  55. package/service/state-store.mjs +0 -100
  56. package/service/steer-once.mjs +0 -25
  57. package/service/task-completion.mjs +0 -163
@@ -0,0 +1,148 @@
1
+ import { createSteerOnce } from "./steer-once.js";
2
+ import { errorReason, isObject, isProbability } from "./types.js";
3
+ const MAX_STATE = 8 * 1024;
4
+ const MAX_CONDITION = 512;
5
+ const MAX_CONDITIONS = 20;
6
+ const MAX_EVIDENCE = 40;
7
+ const STEER_TEXT = "\u672C\u56DE\u5408\u4EFB\u52A1\u5B8C\u6210\u6838\u5BF9\u53D1\u73B0\u6709\u660E\u786E\u4EA4\u4ED8\u6761\u4EF6\u5C1A\u672A\u6EE1\u8DB3\uFF1A\u8BF7\u5BF9\u7167\u7528\u6237\u5217\u51FA\u7684\u6761\u4EF6\u9010\u9879\u590D\u6838\uFF0C\u8865\u5B8C\u672A\u5B8C\u6210\u7684\u90E8\u5206\u3002";
8
+ function extractConditions(userText) {
9
+ const text = typeof userText === "string" ? userText : "";
10
+ if (!text.trim()) return [];
11
+ const conditions = [];
12
+ const push = (value) => {
13
+ const item = value.trim().replace(/\s+/g, " ").slice(0, MAX_CONDITION);
14
+ if (item && !conditions.includes(item)) conditions.push(item);
15
+ };
16
+ const lines = text.split(/\r?\n/);
17
+ let matchedStructured = false;
18
+ for (const line of lines) {
19
+ const numbered = line.match(/^\s*(?:[((]?\d+[)).、]|\d+\s*[.)、])\s+(.*)$/)?.[1];
20
+ const bullet = line.match(/^\s*[-*•]\s+(.*)$/)?.[1];
21
+ if (numbered?.trim()) {
22
+ push(numbered);
23
+ matchedStructured = true;
24
+ } else if (bullet?.trim()) {
25
+ push(bullet);
26
+ matchedStructured = true;
27
+ }
28
+ }
29
+ if (!matchedStructured) {
30
+ const compact = text.trim();
31
+ if (!/[\r\n]/.test(compact)) {
32
+ const parts = compact.split(/[、,,]/).map((part) => part.trim()).filter(Boolean);
33
+ if (parts.length >= 2) for (const part of parts) push(part);
34
+ }
35
+ }
36
+ return conditions.slice(0, MAX_CONDITIONS);
37
+ }
38
+ function completionQuestions(conditions, evidence, answer) {
39
+ const items = Array.isArray(conditions) ? conditions.slice(0, MAX_CONDITIONS) : [];
40
+ const tools = Array.isArray(evidence) ? evidence.filter((item) => isObject(item) && typeof item.tool === "string").slice(0, MAX_EVIDENCE).map((item) => ({ tool: item.tool.slice(0, 128), outcome: item.outcome === "error" ? "error" : "ok" })) : [];
41
+ const state = {
42
+ conditions: items.map((text, index) => ({ id: `c${index}`, text })),
43
+ toolEvidence: tools,
44
+ finalReply: String(answer ?? "").slice(0, MAX_STATE),
45
+ note: "\u5DE5\u5177\u8BC1\u636E\uFF08toolEvidence\uFF09\u662F\u5224\u5B9A\u4F9D\u636E\uFF1BfinalReply \u81EA\u79F0\u5B8C\u6210\u4E0D\u4F5C\u4E3A\u8BC1\u636E\u3002\u7F3A\u5C11\u8BC1\u636E\u65F6\u9009 insufficient_evidence\u3002"
46
+ };
47
+ const questions = Object.fromEntries(items.map((text, index) => [`c${index}`, {
48
+ type: "choice",
49
+ instructions: {
50
+ condition: text,
51
+ question: "\u6839\u636E\u5F53\u524D\u5DE5\u5177\u8BC1\u636E\u4E0E\u6700\u7EC8\u56DE\u590D\uFF0C\u8FD9\u4E2A\u4EA4\u4ED8\u6761\u4EF6\u7684\u5B8C\u6210\u60C5\u51B5\u5C5E\u4E8E\u54EA\u4E00\u79CD\uFF1F"
52
+ },
53
+ criteria: {
54
+ satisfied: "\u6709\u5DE5\u5177\u8BC1\u636E\u6216\u7528\u6237\u660E\u786E\u786E\u8BA4\u8868\u660E\u8BE5\u6761\u4EF6\u5DF2\u5B8C\u6210",
55
+ unsatisfied: "\u6709\u8BC1\u636E\u8868\u660E\u8BE5\u6761\u4EF6\u672A\u5B8C\u6210\u6216\u505A\u9519",
56
+ insufficient_evidence: "\u73B0\u6709\u8BC1\u636E\u4E0D\u8DB3\u4EE5\u5224\u65AD\u8BE5\u6761\u4EF6\u662F\u5426\u5B8C\u6210"
57
+ }
58
+ }]));
59
+ return { state, questions };
60
+ }
61
+ const DEFAULT_MIN_CONFIDENCE = 0.4;
62
+ function createTaskCompletion({ evaluate, sessions, steer, settings, minConfidence = DEFAULT_MIN_CONFIDENCE } = {}) {
63
+ if (typeof evaluate !== "function") throw new Error("Task completion requires an evaluator");
64
+ const steerOnce = createSteerOnce();
65
+ const resolveSettings = async () => {
66
+ let raw = {};
67
+ try {
68
+ raw = (typeof settings === "function" ? await settings() : settings) ?? {};
69
+ } catch {
70
+ raw = {};
71
+ }
72
+ const mode = raw?.mode === "steer" ? "steer" : "observe";
73
+ const minConditions = Number.isSafeInteger(raw?.minConditions) && raw.minConditions >= 2 ? raw.minConditions : 2;
74
+ const confidenceFloor = isProbability(raw?.minConfidence) ? raw.minConfidence : minConfidence;
75
+ return { mode, minConditions, minConfidence: confidenceFloor };
76
+ };
77
+ const record = (turn, outcome, detail = {}) => {
78
+ const id = turn?.agent?.session?.id;
79
+ if (!sessions || typeof id !== "string" || typeof sessions.recordComplete !== "function") return;
80
+ try {
81
+ sessions.recordComplete(id, outcome, detail);
82
+ } catch {
83
+ }
84
+ if (typeof sessions.log !== "function") return;
85
+ try {
86
+ sessions.log(id, { kind: "complete", outcome, ...detail });
87
+ } catch {
88
+ }
89
+ };
90
+ const skip = { evaluated: false, steered: false };
91
+ return {
92
+ async review(turn) {
93
+ const output = typeof turn?.output === "string" ? turn.output.trim() : "";
94
+ const { mode, minConditions, minConfidence: confidenceFloor } = await resolveSettings();
95
+ const conditions = extractConditions(turn?.userText);
96
+ if (conditions.length < minConditions) return { ...skip };
97
+ const evidence = Array.isArray(turn?.evidence) ? turn.evidence : [];
98
+ if (evidence.length === 0) return { ...skip };
99
+ const id = turn?.agent?.session?.id;
100
+ if (sessions && typeof id === "string") {
101
+ try {
102
+ if (sessions.snapshot(id).enabled === false) return { ...skip };
103
+ } catch {
104
+ return { ...skip };
105
+ }
106
+ }
107
+ let answers;
108
+ try {
109
+ const result = await evaluate(completionQuestions(conditions, evidence, output), { signal: turn?.signal });
110
+ answers = isObject(result?.answers) ? result.answers : void 0;
111
+ } catch (error) {
112
+ record(turn, "error", { reason: errorReason(error) });
113
+ return { ...skip };
114
+ }
115
+ if (!answers) {
116
+ record(turn, "error", { reason: "invalid-response" });
117
+ return { ...skip };
118
+ }
119
+ let satisfied = 0, unsatisfied = 0, insufficient = 0;
120
+ let unsatisfiedMinConf = 1;
121
+ for (let index = 0; index < conditions.length; index++) {
122
+ const answer = answers[`c${index}`];
123
+ if (!isObject(answer) || answer.type !== "choice" || typeof answer.choice !== "string") {
124
+ insufficient++;
125
+ continue;
126
+ }
127
+ if (answer.choice === "satisfied") satisfied++;
128
+ else if (answer.choice === "unsatisfied") {
129
+ unsatisfied++;
130
+ if (isProbability(answer.confidence)) unsatisfiedMinConf = Math.min(unsatisfiedMinConf, answer.confidence);
131
+ } else insufficient++;
132
+ }
133
+ const outcome = unsatisfied > 0 ? "unsatisfied" : "ok";
134
+ const detail = { conditions: conditions.length, satisfied, unsatisfied, insufficient };
135
+ const confident = unsatisfied > 0 && unsatisfiedMinConf >= confidenceFloor;
136
+ const steered = mode === "steer" && confident && typeof steer === "function" && turn?.agent ? steerOnce.attempt(turn, () => steer(turn.agent, STEER_TEXT)) : false;
137
+ if (steered) detail.steered = true;
138
+ record(turn, outcome, detail);
139
+ return { evaluated: true, steered, satisfied, unsatisfied, insufficient };
140
+ }
141
+ };
142
+ }
143
+ export {
144
+ DEFAULT_MIN_CONFIDENCE,
145
+ completionQuestions,
146
+ createTaskCompletion,
147
+ extractConditions
148
+ };
@@ -0,0 +1,343 @@
1
+ /**
2
+ * Shared types and guards for the host half.
3
+ *
4
+ * Three kinds of shape appear here, and they are deliberately kept apart:
5
+ * - wire contracts (EvaluateInput / EvaluateResult) — the one protocol with the
6
+ * adjudication backend, validated in backend.ts;
7
+ * - configuration (ResolvedConfig / *Settings) — validated in config-store.ts;
8
+ * - host runtime surfaces (HostContext, ToolExec, AgentLike) — our own
9
+ * structural view of what the Cordis host hands us. They are declared here
10
+ * instead of imported from @deepseek-ai/* so a host minor upgrade cannot
11
+ * break compilation: every field is optional and every boundary re-validates.
12
+ */
13
+ import type { IncomingMessage, ServerResponse } from 'node:http';
14
+ export type JsonObject = Record<string, unknown>;
15
+ export declare const isObject: (value: unknown) => value is JsonObject;
16
+ export declare const isProbability: (value: unknown) => value is number;
17
+ /** The stable reason code a backend failure carries, or a generic fallback. */
18
+ export declare const errorReason: (error: unknown, fallback?: string) => string;
19
+ export type ChoiceQuestion = {
20
+ type: 'choice';
21
+ instructions: unknown;
22
+ criteria: Record<string, unknown>;
23
+ };
24
+ export type ScoreQuestion = {
25
+ type: 'score';
26
+ instructions: unknown;
27
+ criteria: readonly unknown[];
28
+ };
29
+ export type NoulQuestion = {
30
+ type: 'noul';
31
+ instructions: unknown;
32
+ criteria?: unknown;
33
+ };
34
+ export type Question = ChoiceQuestion | ScoreQuestion | NoulQuestion;
35
+ export type QuestionMap = Record<string, Question>;
36
+ export type EvaluateInput = {
37
+ state: unknown;
38
+ questions: QuestionMap;
39
+ };
40
+ export type NoulAnswer = {
41
+ type: 'noul';
42
+ noul: number;
43
+ };
44
+ export type ScoredAnswer = {
45
+ confidence: number;
46
+ probabilities: Record<string, number>;
47
+ };
48
+ export type ScoreAnswer = ScoredAnswer & {
49
+ type: 'score';
50
+ score: number;
51
+ };
52
+ export type ChoiceAnswer = ScoredAnswer & {
53
+ type: 'choice';
54
+ choice: string;
55
+ };
56
+ export type Answer = NoulAnswer | ScoreAnswer | ChoiceAnswer;
57
+ export type AnswerMap = Record<string, Answer>;
58
+ export type EvaluateResult = {
59
+ model: string;
60
+ answers: AnswerMap;
61
+ usage?: {
62
+ input_tokens: number;
63
+ output_tokens: number;
64
+ };
65
+ };
66
+ export type Evaluator = (input: EvaluateInput, options?: {
67
+ signal?: AbortSignal;
68
+ }) => Promise<EvaluateResult>;
69
+ export type CheckMode = 'observe' | 'steer';
70
+ export type NarrowMode = 'observe' | 'enforce';
71
+ export type BatchMode = 'auto' | 'single' | 'split';
72
+ export type FeatureKey = 'gate' | 'check' | 'narrow' | 'complete';
73
+ export type Severity = 'normal' | 'warn' | 'severe';
74
+ export type DangerRuleAdditions = {
75
+ addToolNames?: string[];
76
+ removeToolNames?: string[];
77
+ addCommandPatterns?: string[];
78
+ addPathPatterns?: string[];
79
+ };
80
+ /** Compiled rules: the built-in patterns plus the configured additions. */
81
+ export type DangerRuleSet = {
82
+ toolNames: readonly string[];
83
+ commandPatterns: readonly RegExp[];
84
+ pathPatterns: readonly RegExp[];
85
+ };
86
+ export type GateSettings = {
87
+ minConfidence?: number;
88
+ };
89
+ export type CheckSettings = {
90
+ mode?: CheckMode;
91
+ rubric?: string[];
92
+ lowScoreThreshold?: number;
93
+ minConfidence?: number;
94
+ trendMinConfidence?: number;
95
+ trendRun?: number;
96
+ trendSevereRun?: number;
97
+ };
98
+ export type NarrowSettings = {
99
+ mode?: NarrowMode;
100
+ threshold?: number;
101
+ keepPrefixes?: string[];
102
+ maxCandidates?: number;
103
+ batchMode?: BatchMode;
104
+ };
105
+ export type CompleteSettings = {
106
+ mode?: CheckMode;
107
+ minConditions?: number;
108
+ minConfidence?: number;
109
+ };
110
+ export type Features = Partial<Record<FeatureKey, boolean>>;
111
+ /** What the backend needs from the config to make one request. */
112
+ export type BackendConfig = {
113
+ url: string;
114
+ apiKey: string;
115
+ model: string;
116
+ httpApprovedUrl: string;
117
+ };
118
+ /** Fully resolved configuration; every field the decision points read. */
119
+ export type ResolvedConfig = {
120
+ url: string;
121
+ apiKey: string;
122
+ model: string;
123
+ httpApprovedUrl: string;
124
+ dangerRules?: DangerRuleAdditions;
125
+ gateSettings?: GateSettings;
126
+ checkSettings?: CheckSettings;
127
+ narrowSettings?: NarrowSettings;
128
+ completeSettings?: CompleteSettings;
129
+ features?: Features;
130
+ };
131
+ /** What the config page may read back: the API key is reduced to a flag. */
132
+ export type ConfigView = {
133
+ url: string;
134
+ model: string;
135
+ apiKeySet: boolean;
136
+ httpApprovedUrl: string;
137
+ dangerRules?: DangerRuleAdditions;
138
+ gateSettings?: GateSettings;
139
+ checkSettings?: CheckSettings;
140
+ narrowSettings?: NarrowSettings;
141
+ completeSettings?: CompleteSettings;
142
+ features?: Features;
143
+ };
144
+ /** The quality-trend knobs, shared by the live path and the log backtest. */
145
+ export type TrendConfig = {
146
+ trendMinConfidence?: number;
147
+ trendRun?: number;
148
+ trendSevereRun?: number;
149
+ lowScoreThreshold?: number;
150
+ };
151
+ export type LogKind = 'gate' | 'check' | 'narrow' | 'complete';
152
+ /**
153
+ * One decision-log row. Fields are optional because rows are also read back
154
+ * from disk: persisted snapshots are untrusted input, and only `kind` is
155
+ * enforced before a row is accepted.
156
+ */
157
+ export type LogRow = {
158
+ id?: string;
159
+ at?: number;
160
+ kind: LogKind;
161
+ outcome?: string;
162
+ tool?: string;
163
+ suggestion?: string;
164
+ action?: string;
165
+ reason?: string;
166
+ subject?: string;
167
+ task?: string;
168
+ turn?: number;
169
+ command?: string;
170
+ path?: string;
171
+ mode?: string;
172
+ score?: number;
173
+ confidence?: number;
174
+ dropped?: number;
175
+ kept?: number;
176
+ applied?: boolean;
177
+ candidates?: number;
178
+ conditions?: number;
179
+ satisfied?: number;
180
+ unsatisfied?: number;
181
+ insufficient?: number;
182
+ steered?: boolean;
183
+ lowConfidence?: boolean;
184
+ tools?: string[];
185
+ };
186
+ /** The research-log sink record: the panel row plus its session tag. */
187
+ export type SessionSinkRow = LogRow & {
188
+ sessionId: string;
189
+ };
190
+ export type SessionCounters = {
191
+ attempts: number;
192
+ failures: number;
193
+ };
194
+ export type GateCounters = SessionCounters & {
195
+ ask: number;
196
+ deny: number;
197
+ allow: number;
198
+ actual: {
199
+ allow: number;
200
+ deny: number;
201
+ error: number;
202
+ };
203
+ };
204
+ export type CheckCounters = SessionCounters & {
205
+ low: number;
206
+ run: number;
207
+ severity: Severity;
208
+ };
209
+ export type NarrowCounters = SessionCounters & {
210
+ applied: number;
211
+ dropped: number;
212
+ };
213
+ export type CompleteCounters = SessionCounters & {
214
+ unsatisfied: number;
215
+ satisfied: number;
216
+ insufficient: number;
217
+ steered: number;
218
+ };
219
+ /** Live per-session state (the durable snapshot's payload). */
220
+ export type SessionState = {
221
+ enabled: boolean;
222
+ attempts: number;
223
+ failures: number;
224
+ gate: GateCounters;
225
+ check: CheckCounters;
226
+ narrow: NarrowCounters;
227
+ complete: CompleteCounters;
228
+ log: LogRow[];
229
+ };
230
+ /** The panel-facing projection; sections without attempts are omitted. */
231
+ export type SessionSnapshot = {
232
+ enabled: boolean;
233
+ hasAutomaticDecisions: boolean;
234
+ attempts: number;
235
+ failures: number;
236
+ gate?: GateCounters;
237
+ check?: {
238
+ attempts: number;
239
+ failures: number;
240
+ low: number;
241
+ trend: {
242
+ run: number;
243
+ severity: Severity;
244
+ };
245
+ };
246
+ narrow?: NarrowCounters;
247
+ complete?: CompleteCounters;
248
+ log?: LogRow[];
249
+ };
250
+ /** The counter shape rebuilt from persisted JSONL, without the master switch. */
251
+ export type SessionSeed = Omit<SessionState, 'enabled'>;
252
+ /**
253
+ * The slice of the session store the decision points use. Every method is
254
+ * optional: a test or an older host may inject only the parts it cares about.
255
+ */
256
+ export type SessionsFace = {
257
+ snapshot: (id: string) => SessionSnapshot;
258
+ log?: (id: string, entry: Record<string, unknown>) => void;
259
+ record?: (id: string, kind: 'gate', outcome: string) => void;
260
+ recordActual?: (id: string, outcome: 'allow' | 'deny' | 'error') => void;
261
+ recordCheck?: (id: string, outcome: string, trend?: Record<string, unknown>) => void;
262
+ recordNarrow?: (id: string, outcome: string, dropped: number, applied: boolean) => void;
263
+ recordComplete?: (id: string, outcome: string, detail?: Record<string, unknown>) => void;
264
+ ensureLoaded?: (id: string) => Promise<unknown>;
265
+ setEnabled?: (id: string, enabled: boolean) => void;
266
+ };
267
+ export type ToolCandidate = {
268
+ name: string;
269
+ description?: string;
270
+ };
271
+ export type AgentLike = {
272
+ session?: {
273
+ id?: string;
274
+ deriveMessages?: () => unknown;
275
+ };
276
+ steer?: (message: unknown) => unknown;
277
+ ctx?: {
278
+ tools?: {
279
+ restrict?: (options: {
280
+ allow: string[];
281
+ }) => (() => void) | undefined;
282
+ };
283
+ };
284
+ };
285
+ export type ToolExec = {
286
+ name?: string;
287
+ arguments?: unknown;
288
+ agent?: AgentLike;
289
+ signal?: AbortSignal;
290
+ [key: string]: unknown;
291
+ };
292
+ export type NextStep = () => unknown;
293
+ export type ToolSchema = {
294
+ name: string;
295
+ description?: string;
296
+ };
297
+ export type ToolRegistration = {
298
+ name: string;
299
+ description: string;
300
+ parameters: Record<string, unknown>;
301
+ output: Record<string, unknown>;
302
+ timeoutMs?: number;
303
+ execute: (args: unknown, exec: ToolExec) => unknown;
304
+ };
305
+ export type SkillRegistration = {
306
+ name: string;
307
+ description: string;
308
+ content: string;
309
+ source: string;
310
+ };
311
+ export type ManagerRoute = {
312
+ kind: 'exact';
313
+ path: string;
314
+ handler: (req: IncomingMessage, res: ServerResponse) => Promise<void>;
315
+ };
316
+ export type WebServerFace = {
317
+ port: number;
318
+ host?: string;
319
+ register: (route: ManagerRoute) => () => void;
320
+ };
321
+ export type WebFace = {
322
+ webServer: WebServerFace;
323
+ };
324
+ export type ToolsFace = {
325
+ guard?: (guard: (exec: ToolExec) => string | undefined) => unknown;
326
+ register: (tool: ToolRegistration) => unknown;
327
+ schemas?: (agent?: AgentLike) => unknown;
328
+ };
329
+ export type SkillsFace = {
330
+ register: (skill: SkillRegistration) => unknown;
331
+ };
332
+ /**
333
+ * The Cordis context our apply() needs. Handlers are typed per call site
334
+ * because each event carries a different payload; `never[]` here keeps them
335
+ * assignable without leaking `any` into the implementation.
336
+ */
337
+ export type HostContext = {
338
+ on: (event: string, handler: (...args: never[]) => unknown) => (() => void) | undefined;
339
+ effect?: (install: () => (() => void) | void) => unknown;
340
+ tools: ToolsFace;
341
+ skills: SkillsFace;
342
+ inject: (keys: string[], install: (web: WebFace) => (() => void) | void) => unknown;
343
+ };
@@ -0,0 +1,11 @@
1
+ const isObject = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
2
+ const isProbability = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0 && value <= 1;
3
+ const errorReason = (error, fallback = "unreachable") => {
4
+ const reason = error?.reason;
5
+ return typeof reason === "string" ? reason : fallback;
6
+ };
7
+ export {
8
+ errorReason,
9
+ isObject,
10
+ isProbability
11
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-decision-layer",
3
- "version": "0.7.1",
3
+ "version": "0.7.2",
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",
@@ -14,11 +14,11 @@
14
14
  "license": "MIT",
15
15
  "type": "module",
16
16
  "icon": "./icon.svg",
17
- "main": "./service/dsh.mjs",
17
+ "main": "./lib/host/index.js",
18
18
  "files": [
19
- "service",
20
- "skills",
19
+ "lib/host",
21
20
  "lib/client.js",
21
+ "skills",
22
22
  "locale/*.json",
23
23
  "docs/zh/DETAIL.md",
24
24
  "docs/en/DETAIL.md",
@@ -36,20 +36,25 @@
36
36
  "url": "https://github.com/xbzbing/dsh-decision-layer.git"
37
37
  },
38
38
  "scripts": {
39
- "test": "node --test tests/*.test.mjs",
40
- "build:client": "tsc -p tsconfig.client.json && node scripts/build-client.mjs",
41
- "check": "npm run build:client && npm test",
39
+ "build": "node build.mjs",
40
+ "typecheck": "tsc -p tsconfig.host.json --noEmit && tsc -p tsconfig.client.json",
41
+ "test": "node build.mjs && node --test tests/*.test.mjs",
42
+ "check": "npm test && npm run pack:assert",
42
43
  "pack:assert": "node scripts/assert-pack.mjs"
43
44
  },
44
45
  "exports": {
45
- ".": "./service/dsh.mjs",
46
+ ".": {
47
+ "types": "./lib/host/index.d.ts",
48
+ "default": "./lib/host/index.js"
49
+ },
46
50
  "./client": "./lib/client.js",
47
51
  "./locale/*.json": "./locale/*.json",
48
52
  "./package.json": "./package.json"
49
53
  },
50
54
  "peerDependencies": {
51
55
  "@deepseek-ai/cordis": "~4.0.4",
52
- "@deepseek-ai/dsh": ">=0.1.7-rc.2"
56
+ "@deepseek-ai/dsh": ">=0.1.7-rc.2",
57
+ "@deepseek-ai/dsh-llm": ">=0.1.7-rc.2"
53
58
  },
54
59
  "devDependencies": {
55
60
  "@deepseek-ai/dsh-client-locale": "0.1.7-rc.2",
@@ -58,6 +63,7 @@
58
63
  "@deepseek-ai/dsh-client-ui-renderer": "0.1.7-rc.2",
59
64
  "@deepseek-ai/dsh-client-ui-slots": "0.1.7-rc.2",
60
65
  "@testing-library/react": "^16.3.3",
66
+ "@types/node": "^20.19.0",
61
67
  "@types/react": "~18.3.1",
62
68
  "@types/react-dom": "~18.3.0",
63
69
  "esbuild": "^0.25.12",
@@ -80,5 +86,6 @@
80
86
  ],
81
87
  "platform": "web"
82
88
  }
83
- }
89
+ },
90
+ "types": "./lib/host/index.d.ts"
84
91
  }
package/service/.gitkeep DELETED
File without changes