dsh-session-eval 0.2.0 → 0.2.1

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/CHANGELOG.md ADDED
@@ -0,0 +1,30 @@
1
+ # Changelog
2
+
3
+ ## 0.2.1 — 2026-09-26
4
+
5
+ - **Fix: ship the bundle overlay.** v0.2.0 declared `dsh.bundle.patch: ./cordis.patch.yml`
6
+ in `package.json` but the file was never committed, so the published tarball did not
7
+ contain it and `dsh web` failed at startup with
8
+ `failed to read overlay .../dsh-session-eval/cordis.patch.yml: ENOENT`.
9
+ The overlay (mounting `/eval`, `/eval-diff`, `/eval-history`) is now in the tarball;
10
+ verified with `npm pack --dry-run`.
11
+ - **Fix: peer ranges match the current harness.** `@deepseek-ai/*` peers and dev
12
+ dependencies moved from `^0.1.1-rc.2` to `^0.1.5-rc.3` (semver prerelease ranges do not
13
+ cross `0.1.1-rc.x` → `0.1.5-rc.3`, which let stale `0.1.1-rc.2` copies install into
14
+ profiles and shadow the running harness). `@deepseek-ai/cordis` pinned to `4.0.2`,
15
+ matching the official plugins at this harness level.
16
+ - Added this changelog; `files` always listed it but it was never written.
17
+
18
+ ## 0.2.0 — 2026-09-25
19
+
20
+ - New `/eval-history [N] [--json]` command: lists the current workspace's recent sessions
21
+ (same `sessionQuery` seam as `/archive`), grades the last N, and diffs first-vs-last for
22
+ a trend verdict. Caps the manual id-picking that `/eval-diff` required.
23
+ - Version string single-sourced from `src/version.ts` (replaces four hardcoded `v0.1.0`
24
+ strings).
25
+ - 8 new tests (35 total) and `typecheck` script.
26
+
27
+ ## 0.1.0 — 2026-09-24
28
+
29
+ - Initial release: `/eval` grade cards from persisted session logs (tool-error rate,
30
+ follow-up signals, tool load), `/eval-diff` regression comparison, no LLM judge.
@@ -0,0 +1,6 @@
1
+ # The dsh-session-eval bundle patch: mounts /eval, /eval-diff, and /eval-history.
2
+ # Applied as one insert; requires `commands` and `sessionQuery` services,
3
+ # which the shipped dsh-base / web bundles already provide.
4
+ - insert:
5
+ - id: session-eval
6
+ name: 'dsh-session-eval'
package/lib/index.d.ts CHANGED
@@ -132,10 +132,57 @@ declare function executeEval(ctx: Context, invocation: CommandInvocation, seam?:
132
132
  /** Execute /eval-diff against the session-query seam. */
133
133
  declare function executeEvalDiff(ctx: Context, invocation: CommandInvocation, seam?: QuerySeam): Promise<CommandResult>;
134
134
  //#endregion
135
+ //#region src/historyCommand.d.ts
136
+ declare const EVAL_HISTORY_USAGE = "Usage: /eval-history [N] [--json] (N = last N sessions, 1-20, default 5)";
137
+ /** Args for /eval-history. */
138
+ interface EvalHistoryArgs {
139
+ readonly count: number;
140
+ readonly json: boolean;
141
+ }
142
+ /** Parse /eval-history input; returns args or a usage-error string. */
143
+ declare function parseEvalHistoryArgs(rawInput: string): EvalHistoryArgs | string;
144
+ /** One graded session in a history report. */
145
+ interface HistoryEntry {
146
+ readonly sessionId: string;
147
+ readonly createdAt: number | null;
148
+ readonly metrics: SessionMetrics;
149
+ readonly card: GradeCard;
150
+ }
151
+ /** The full /eval-history report. */
152
+ interface HistoryReport {
153
+ readonly entries: readonly HistoryEntry[];
154
+ readonly trend: TrendReport | null;
155
+ }
156
+ /** The slice of sessionQuery this command consumes (structural, for testability). */
157
+ interface HistorySeam {
158
+ listSessions(): Promise<ReadonlyArray<{
159
+ header: {
160
+ id: unknown;
161
+ createdAt: unknown;
162
+ cwd?: unknown;
163
+ };
164
+ }>>;
165
+ readSession(id: ReturnType<typeof SessionId>): Promise<{
166
+ events: readonly unknown[];
167
+ }>;
168
+ }
169
+ /** Render a history report as terminal text. */
170
+ declare function renderHistory(report: HistoryReport): string;
171
+ /** Execute /eval-history against the session-query seam. */
172
+ declare function executeEvalHistory(ctx: Context, invocation: CommandInvocation, seam?: HistorySeam): Promise<CommandResult>;
173
+ //#endregion
174
+ //#region src/version.d.ts
175
+ /**
176
+ * Single source of truth for the plugin version string.
177
+ *
178
+ * @module dsh-session-eval/version
179
+ */
180
+ declare const VERSION = "0.2.1";
181
+ //#endregion
135
182
  //#region src/index.d.ts
136
183
  declare const name = "session-eval";
137
184
  declare const inject: string[];
138
- /** Plugin entry: mount the /eval and /eval-diff commands. */
185
+ /** Plugin entry: mount the /eval, /eval-diff and /eval-history commands. */
139
186
  declare function apply(ctx: Context): void;
140
187
  //#endregion
141
- export { type DimensionDelta, type DimensionGrade, EVAL_DIFF_USAGE, EVAL_USAGE, type EvalArgs, type EvalDiffArgs, type EvalEvent, type EvalEventKind, type Grade, type GradeCard, type SessionMetrics, type TrendReport, adaptEvents, apply, compareSessions, computeMetrics, executeEval, executeEvalDiff, gradeSession, id8, inject, name, parseEvalArgs, parseEvalDiffArgs, renderCard, renderTrend };
188
+ export { type DimensionDelta, type DimensionGrade, EVAL_DIFF_USAGE, EVAL_HISTORY_USAGE, EVAL_USAGE, type EvalArgs, type EvalDiffArgs, type EvalEvent, type EvalEventKind, type EvalHistoryArgs, type Grade, type GradeCard, type HistoryEntry, type HistoryReport, type HistorySeam, type SessionMetrics, type TrendReport, VERSION, adaptEvents, apply, compareSessions, computeMetrics, executeEval, executeEvalDiff, executeEvalHistory, gradeSession, id8, inject, name, parseEvalArgs, parseEvalDiffArgs, parseEvalHistoryArgs, renderCard, renderHistory, renderTrend };
package/lib/index.js CHANGED
@@ -221,6 +221,14 @@ function compareSessions(before, after) {
221
221
  };
222
222
  }
223
223
  //#endregion
224
+ //#region src/version.ts
225
+ /**
226
+ * Single source of truth for the plugin version string.
227
+ *
228
+ * @module dsh-session-eval/version
229
+ */
230
+ const VERSION = "0.2.1";
231
+ //#endregion
224
232
  //#region src/evalCommand.ts
225
233
  const EVAL_USAGE = "Usage: /eval [--id <sessionId>] [--json]";
226
234
  const EVAL_DIFF_USAGE = "Usage: /eval-diff <beforeSessionId> <afterSessionId> [--json]";
@@ -319,14 +327,14 @@ async function executeEval(ctx, invocation, seam = ctx.sessionQuery) {
319
327
  if (parsed.json === true) return {
320
328
  kind: "success",
321
329
  text: JSON.stringify({
322
- generator: "dsh-session-eval v0.1.0",
330
+ generator: "dsh-session-eval v0.2.1",
323
331
  metrics,
324
332
  card
325
333
  }, null, 2)
326
334
  };
327
335
  return {
328
336
  kind: "success",
329
- text: renderCard(card, "dsh-session-eval v0.1.0")
337
+ text: renderCard(card, "dsh-session-eval v0.2.1")
330
338
  };
331
339
  }
332
340
  /** Execute /eval-diff against the session-query seam. */
@@ -350,20 +358,133 @@ async function executeEvalDiff(ctx, invocation, seam = ctx.sessionQuery) {
350
358
  if (parsed.json === true) return {
351
359
  kind: "success",
352
360
  text: JSON.stringify({
353
- generator: "dsh-session-eval v0.1.0",
361
+ generator: "dsh-session-eval v0.2.1",
362
+ report
363
+ }, null, 2)
364
+ };
365
+ return {
366
+ kind: "success",
367
+ text: renderTrend(report, "dsh-session-eval v0.2.1")
368
+ };
369
+ }
370
+ //#endregion
371
+ //#region src/historyCommand.ts
372
+ const EVAL_HISTORY_USAGE = "Usage: /eval-history [N] [--json] (N = last N sessions, 1-20, default 5)";
373
+ /** Parse /eval-history input; returns args or a usage-error string. */
374
+ function parseEvalHistoryArgs(rawInput) {
375
+ const tokens = rawInput.trim().split(/\s+/).filter((t) => t.length > 0);
376
+ let count = 5;
377
+ let json = false;
378
+ let sawCount = false;
379
+ for (const token of tokens) {
380
+ if (token === "--json") {
381
+ if (json) return `--json may be given only once.\n${EVAL_HISTORY_USAGE}`;
382
+ json = true;
383
+ continue;
384
+ }
385
+ if (token.startsWith("--")) return `Unknown argument: ${token}\n${EVAL_HISTORY_USAGE}`;
386
+ if (sawCount) return `Expected at most one count argument.\n${EVAL_HISTORY_USAGE}`;
387
+ if (!/^\d+$/.test(token)) return `N must be a positive integer.\n${EVAL_HISTORY_USAGE}`;
388
+ const value = Number(token);
389
+ if (value < 1 || value > 20) return `N must be between 1 and 20.\n${EVAL_HISTORY_USAGE}`;
390
+ count = value;
391
+ sawCount = true;
392
+ }
393
+ return {
394
+ count,
395
+ json
396
+ };
397
+ }
398
+ function toMillis(createdAt) {
399
+ if (typeof createdAt === "number" && Number.isFinite(createdAt)) return createdAt;
400
+ if (typeof createdAt === "string") {
401
+ const parsed = Date.parse(createdAt);
402
+ if (Number.isFinite(parsed)) return parsed;
403
+ }
404
+ return null;
405
+ }
406
+ function stamp(ms) {
407
+ if (ms === null) return "?";
408
+ return new Date(ms).toISOString().slice(0, 16).replace("T", " ");
409
+ }
410
+ /** Render a history report as terminal text. */
411
+ function renderHistory(report) {
412
+ const lines = [];
413
+ if (report.entries.length === 0) return "No sessions found in this workspace; nothing to grade.";
414
+ lines.push(`Session history — last ${report.entries.length} in this workspace`);
415
+ for (const entry of report.entries) {
416
+ const m = entry.metrics;
417
+ lines.push(` ${stamp(entry.createdAt)} ${id8(entry.sessionId)} ${String(entry.card.overall).padEnd(2)} ${m.userTurns} turns, ${m.toolCalls} tools`);
418
+ }
419
+ const trend = report.trend;
420
+ if (trend !== null) lines.push(`Trend (first → last): ${trend.overall} — ${trend.summary}`);
421
+ return lines.join("\n");
422
+ }
423
+ /** Execute /eval-history against the session-query seam. */
424
+ async function executeEvalHistory(ctx, invocation, seam = ctx.sessionQuery) {
425
+ const parsed = parseEvalHistoryArgs(invocation.rawInput);
426
+ if (typeof parsed === "string") return {
427
+ kind: "error",
428
+ text: parsed
429
+ };
430
+ const ownCwd = invocation.agent.session.header?.cwd;
431
+ let records;
432
+ try {
433
+ records = await seam.listSessions();
434
+ } catch (error) {
435
+ return {
436
+ kind: "error",
437
+ text: `Could not list sessions: ${error instanceof Error ? error.message : String(error)}`
438
+ };
439
+ }
440
+ const recent = records.map((record) => record.header).filter((header) => header != null).filter((header) => ownCwd === void 0 || header.cwd === ownCwd).map((header) => ({
441
+ id: String(header.id),
442
+ createdAt: toMillis(header.createdAt)
443
+ })).sort((a, b) => (a.createdAt ?? 0) - (b.createdAt ?? 0)).slice(-parsed.count);
444
+ if (recent.length === 0) return {
445
+ kind: "success",
446
+ text: "No sessions found in this workspace; nothing to grade."
447
+ };
448
+ const entries = [];
449
+ for (const header of recent) {
450
+ let events;
451
+ try {
452
+ events = (await seam.readSession(SessionId(header.id))).events;
453
+ } catch (error) {
454
+ return {
455
+ kind: "error",
456
+ text: `Could not read session ${id8(header.id)}: ${error instanceof Error ? error.message : String(error)}`
457
+ };
458
+ }
459
+ const metrics = computeMetrics(header.id, adaptEvents(events));
460
+ entries.push({
461
+ sessionId: header.id,
462
+ createdAt: header.createdAt,
463
+ metrics,
464
+ card: gradeSession(metrics)
465
+ });
466
+ }
467
+ const report = {
468
+ entries,
469
+ trend: entries.length >= 2 ? compareSessions(entries[0].metrics, entries[entries.length - 1].metrics) : null
470
+ };
471
+ if (parsed.json === true) return {
472
+ kind: "success",
473
+ text: JSON.stringify({
474
+ generator: `dsh-session-eval v${VERSION}`,
354
475
  report
355
476
  }, null, 2)
356
477
  };
357
478
  return {
358
479
  kind: "success",
359
- text: renderTrend(report, "dsh-session-eval v0.1.0")
480
+ text: renderHistory(report)
360
481
  };
361
482
  }
362
483
  //#endregion
363
484
  //#region src/index.ts
364
485
  const name = "session-eval";
365
486
  const inject = ["commands", "sessionQuery"];
366
- /** Plugin entry: mount the /eval and /eval-diff commands. */
487
+ /** Plugin entry: mount the /eval, /eval-diff and /eval-history commands. */
367
488
  function apply(ctx) {
368
489
  ctx.effect(function* () {
369
490
  yield ctx.commands.register({
@@ -376,7 +497,12 @@ function apply(ctx) {
376
497
  description: "Compare two sessions (before, after) and report which dimensions improved or regressed",
377
498
  handler: (invocation) => executeEvalDiff(ctx, invocation)
378
499
  });
500
+ yield ctx.commands.register({
501
+ name: "eval-history",
502
+ description: "Grade the last N sessions in this workspace (default 5) and show whether the trend is improving",
503
+ handler: (invocation) => executeEvalHistory(ctx, invocation)
504
+ });
379
505
  }, "session-eval lifecycle");
380
506
  }
381
507
  //#endregion
382
- export { EVAL_DIFF_USAGE, EVAL_USAGE, adaptEvents, apply, compareSessions, computeMetrics, executeEval, executeEvalDiff, gradeSession, id8, inject, name, parseEvalArgs, parseEvalDiffArgs, renderCard, renderTrend };
508
+ export { EVAL_DIFF_USAGE, EVAL_HISTORY_USAGE, EVAL_USAGE, VERSION, adaptEvents, apply, compareSessions, computeMetrics, executeEval, executeEvalDiff, executeEvalHistory, gradeSession, id8, inject, name, parseEvalArgs, parseEvalDiffArgs, parseEvalHistoryArgs, renderCard, renderHistory, renderTrend };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-session-eval",
3
3
  "description": "Retrospective agent-session evaluation for DeepSeek Harness: deterministic grade cards from persisted session logs, cross-session regression diffs, era comparison — no benchmark runs, no LLM judges.",
4
- "version": "0.2.0",
4
+ "version": "0.2.1",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -38,21 +38,21 @@
38
38
  "regression"
39
39
  ],
40
40
  "peerDependencies": {
41
- "@deepseek-ai/cordis": "^4.0.1",
42
- "@deepseek-ai/dsh-commands": "^0.1.1-rc.2",
43
- "@deepseek-ai/dsh-llm": "^0.1.1-rc.2",
44
- "@deepseek-ai/dsh-session": "^0.1.1-rc.2",
45
- "@deepseek-ai/dsh-session-query": "^0.1.1-rc.2",
46
- "@deepseek-ai/dsh-tools": "^0.1.1-rc.2"
41
+ "@deepseek-ai/cordis": "4.0.2",
42
+ "@deepseek-ai/dsh-commands": "^0.1.5-rc.3",
43
+ "@deepseek-ai/dsh-llm": "^0.1.5-rc.3",
44
+ "@deepseek-ai/dsh-session": "^0.1.5-rc.3",
45
+ "@deepseek-ai/dsh-session-query": "^0.1.5-rc.3",
46
+ "@deepseek-ai/dsh-tools": "^0.1.5-rc.3"
47
47
  },
48
48
  "devDependencies": {
49
49
  "@types/node": "^26.2.0",
50
50
  "tsdown": "^0.22.14",
51
51
  "typescript": "^7.0.2",
52
52
  "vitest": "^4.1.11",
53
- "@deepseek-ai/cordis": "^4.0.1",
54
- "@deepseek-ai/dsh-commands": "^0.1.1-rc.2",
55
- "@deepseek-ai/dsh-session": "^0.1.1-rc.2"
53
+ "@deepseek-ai/cordis": "4.0.2",
54
+ "@deepseek-ai/dsh-commands": "^0.1.5-rc.3",
55
+ "@deepseek-ai/dsh-session": "^0.1.5-rc.3"
56
56
  },
57
57
  "scripts": {
58
58
  "bundle": "tsdown",