dsh-context 0.62.3 → 0.64.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.
package/lib/index.d.ts CHANGED
@@ -157,6 +157,39 @@ interface TimelineState {
157
157
  time: number;
158
158
  firstToken?: number;
159
159
  };
160
+ /**
161
+ * The open step's CLOSED user-wait intervals (real log instants), pushed
162
+ * replace-style as each wait settles: an approval decision (its asked →
163
+ * decided pair closes via `stepApprovals`) and an `ask_user_question`
164
+ * call's whole tool window (its body is the answer wait itself, in both
165
+ * the blocking and the timed schema's foreground phase). `step/end`
166
+ * subtracts their clamped union from the step window to price the
167
+ * request's `activeMs`, then DELETES the slot. Same arm/remove lifecycle
168
+ * as `stepStart` — never written in place, so no CloneKey covers it.
169
+ */
170
+ stepWaits?: {
171
+ start: number;
172
+ end: number;
173
+ }[];
174
+ /**
175
+ * The open step's PENDING approval waits (request id → its `approval/asked`
176
+ * instant), armed only while a step is open and disarmed by the matching
177
+ * `approval/decided`. A crash mid-decision leaves the pair dangling —
178
+ * resume repair appends no synthetic decision — so `step/end` closes the
179
+ * survivors at its own instant. One record, not a map: pending approvals
180
+ * are a handful at most, and the rebuild-on-change copy stays trivial.
181
+ * Deleted at `step/start` (a superseded step's leftovers) and `step/end`.
182
+ */
183
+ stepApprovals?: Record<string, number>;
184
+ /**
185
+ * The seq of the request record committed by the open step's
186
+ * `assistant/message` — the back-pointer `step/end` stamps `activeMs`
187
+ * onto. It stays the requests TAIL until then (nothing pushes between a
188
+ * step's message and its end, and the turn trim only drops the oldest),
189
+ * so the stamp is an O(1) tail write guarded by the seq. Same arm/remove
190
+ * lifecycle as `stepStart`.
191
+ */
192
+ stepRequestSeq?: number;
160
193
  /**
161
194
  * The timing strip's painted spans (shared/types.ts TimingSpan): every
162
195
  * completed step's time slices in log order, stamped with their REAL
@@ -364,6 +397,8 @@ type DefaultToolSort = 'size' | 'count' | 'name';
364
397
  type DefaultPlacement = 'all' | 'tab' | 'sidebar';
365
398
  /** Whether the Context Insights panel's sidebar entry is offered at all. */
366
399
  type InsightsEntry = 'show' | 'hide';
400
+ /** Whether the trend chart mounts with the per-request duration curve overlaid. */
401
+ type DefaultDurationCurve = 'show' | 'hide';
367
402
  interface Snapshot {
368
403
  ok: boolean;
369
404
  /**
@@ -790,6 +825,15 @@ interface RequestRecord {
790
825
  */
791
826
  cacheRead?: number;
792
827
  output?: number;
828
+ /**
829
+ * The request's step ACTIVE milliseconds (`step/start` → `step/end`: the
830
+ * model call plus its tool runs) minus the in-step user waits — approval
831
+ * decisions and `ask_user_question` answer windows — the trend chart's
832
+ * duration overlay. Stamped at `step/end` onto the step's committed record;
833
+ * absent while that step is still open (the live tail) or when the step
834
+ * closed without one (an unpaired/hostile log).
835
+ */
836
+ activeMs?: number;
793
837
  /**
794
838
  * Turn-mode aggregate marker, set by the Client's aggregateByTurn (one bar
795
839
  * per turn shows its LAST step's record). The Host never sets it.
@@ -913,6 +957,8 @@ export interface Config {
913
957
  defaultFileSort?: DefaultFileSort;
914
958
  /** Whether the Context Insights panel's sidebar entry is offered. */
915
959
  insightsEntry?: InsightsEntry;
960
+ /** Whether the trend chart mounts with the duration curve overlaid. */
961
+ defaultDurationCurve?: DefaultDurationCurve;
916
962
  }
917
963
  /** The cordis `Config` validator and the Config-form generation's served schema. */
918
964
  export declare const Config: z$1<Schemastery.ObjectS<NoInfer<{
@@ -929,6 +975,7 @@ export declare const Config: z$1<Schemastery.ObjectS<NoInfer<{
929
975
  defaultToolSort: z$1<"count" | "name" | "size", "count" | "name" | "size", Mode>;
930
976
  defaultFileSort: z$1<"count" | "latest" | "path", "count" | "latest" | "path", Mode>;
931
977
  insightsEntry: z$1<"hide" | "show", "hide" | "show", Mode>;
978
+ defaultDurationCurve: z$1<"hide" | "show", "hide" | "show", Mode>;
932
979
  }>>, Schemastery.ObjectT<NoInfer<{
933
980
  maxRequestSteps: z$1<number, number, Mode extends "volatile" | "volatile-defined" ? "volatile-defined" : "defined">;
934
981
  maxKeptTurns: z$1<number, number, Mode extends "volatile" | "volatile-defined" ? "volatile-defined" : "defined">;
@@ -943,6 +990,7 @@ export declare const Config: z$1<Schemastery.ObjectS<NoInfer<{
943
990
  defaultToolSort: z$1<"count" | "name" | "size", "count" | "name" | "size", Mode>;
944
991
  defaultFileSort: z$1<"count" | "latest" | "path", "count" | "latest" | "path", Mode>;
945
992
  insightsEntry: z$1<"hide" | "show", "hide" | "show", Mode>;
993
+ defaultDurationCurve: z$1<"hide" | "show", "hide" | "show", Mode>;
946
994
  }>>, "plain">;
947
995
  //#endregion
948
996
  //#region src/host/index.d.ts
package/lib/index.js CHANGED
@@ -259,8 +259,8 @@ function estimateBlocks(blocks) {
259
259
  }
260
260
  /**
261
261
  * Price one surface message exactly like dsh's token-meter estimate:
262
- * an empty-content assistant/message projects to NO message (it only hosts
263
- * usage), so it prices 0; every other message pays content + role framing.
262
+ * an empty-content assistant/developer event projects to NO message, so it
263
+ * prices 0; every other message pays content + role framing.
264
264
  */
265
265
  function estimateMessage(message, emptyIsZero = false) {
266
266
  if (emptyIsZero && (message === null || message === void 0 || !Array.isArray(message.content) || message.content.length === 0)) return 0;
@@ -844,6 +844,7 @@ function createTimelineState() {
844
844
  function categoryOf(type, message) {
845
845
  if (type === "assistant/message") return "assistant";
846
846
  if (type === "tool/result") return "tool";
847
+ if (type === "developer/message") return "inject";
847
848
  const kind = message?.source?.kind;
848
849
  if (kind === "skill-invocation" || kind === "skill-catalog") return "skill";
849
850
  if (isInjection(message?.source)) return "inject";
@@ -943,12 +944,9 @@ function removeSurfaceSeqs(st, claimed, goneSeq) {
943
944
  return removed;
944
945
  }
945
946
  /**
946
- * The message nested under an event payload's `message` field
947
- * (`system/message`, `assistant/message`, `tool/result`) — read structurally
948
- * because the fold needs a TOTAL read over untrusted payloads: a malformed
949
- * message reads null here, never throws or guesses a shape. Purely a type
950
- * cast: `deriveEventMessage` returns `event.data` for a user/message (no
951
- * `data.message` indirection), so its result feeds every other case.
947
+ * A message nested under data.message. A structural read keeps malformed
948
+ * payloads total and handles developer messages absent from the V3
949
+ * dependency's deriveEventMessage. User messages instead live in data itself.
952
950
  */
953
951
  function messageOf$1(data) {
954
952
  const message = data?.message;
@@ -988,7 +986,7 @@ function applySurface(st, ev, type, data, message) {
988
986
  seq: ev.seq,
989
987
  time: ev.time,
990
988
  cat,
991
- tokens: estimateMessage(message, type === "assistant/message")
989
+ tokens: estimateMessage(message, type === "assistant/message" || type === "developer/message")
992
990
  };
993
991
  const imgs = imageCountOf(message?.content);
994
992
  if (imgs > 0) node.imgs = imgs;
@@ -1397,13 +1395,38 @@ function applyTimeline(state, event, bounds) {
1397
1395
  const s = ensure([]);
1398
1396
  s.stepStart = { time: event.time };
1399
1397
  delete s.stepSpans;
1398
+ delete s.stepWaits;
1399
+ delete s.stepApprovals;
1400
+ delete s.stepRequestSeq;
1400
1401
  break;
1401
1402
  }
1402
1403
  case "step/end": {
1403
1404
  const start = state.stepStart;
1404
1405
  if (start === void 0) return state;
1405
- const s = ensure(["timing"]);
1406
+ const s = ensure(state.stepRequestSeq !== void 0 ? ["timing", "requests"] : ["timing"]);
1406
1407
  ensureTiming(s).wallMs += durOf(start.time, event.time);
1408
+ const waits = [...state.stepWaits ?? []];
1409
+ for (const id in state.stepApprovals) waits.push({
1410
+ start: state.stepApprovals[id],
1411
+ end: event.time
1412
+ });
1413
+ waits.sort((a, b) => a.start - b.start || a.end - b.end);
1414
+ let waitMs = 0;
1415
+ let waitCursor = start.time;
1416
+ for (const wait of waits) {
1417
+ const from = Math.max(waitCursor, wait.start);
1418
+ const to = Math.min(wait.end, event.time);
1419
+ if (to > from) {
1420
+ waitMs += to - from;
1421
+ waitCursor = to;
1422
+ }
1423
+ }
1424
+ const activeMs = durOf(start.time, event.time) - waitMs;
1425
+ const last = state.requests.at(-1);
1426
+ if (last !== void 0 && last.seq === state.stepRequestSeq) s.requests[s.requests.length - 1] = {
1427
+ ...last,
1428
+ activeMs
1429
+ };
1407
1430
  const painted = [];
1408
1431
  for (const span of state.stepSpans ?? []) {
1409
1432
  const from = Math.max(start.time, span.start);
@@ -1440,11 +1463,43 @@ function applyTimeline(state, event, bounds) {
1440
1463
  s.spans = [...state.spans, ...flushed];
1441
1464
  delete s.stepStart;
1442
1465
  delete s.stepSpans;
1466
+ delete s.stepWaits;
1467
+ delete s.stepApprovals;
1468
+ delete s.stepRequestSeq;
1443
1469
  bumpDetailRev(s);
1444
1470
  break;
1445
1471
  }
1446
- case "user/message": {
1447
- const msg = deriveEventMessage(event);
1472
+ case "approval/asked": {
1473
+ const id = data?.id;
1474
+ if (state.stepStart === void 0 || typeof id !== "string") return state;
1475
+ const s = ensure([]);
1476
+ s.stepApprovals = {
1477
+ ...state.stepApprovals,
1478
+ [id]: event.time
1479
+ };
1480
+ break;
1481
+ }
1482
+ case "approval/decided": {
1483
+ const id = data?.id;
1484
+ const open = state.stepApprovals;
1485
+ const askedAt = typeof id === "string" ? open?.[id] : void 0;
1486
+ if (open === void 0 || askedAt === void 0) return state;
1487
+ const s = ensure([]);
1488
+ s.stepWaits = [...state.stepWaits ?? [], {
1489
+ start: askedAt,
1490
+ end: event.time
1491
+ }];
1492
+ const kept = {};
1493
+ for (const k in open) if (k !== id) kept[k] = open[k];
1494
+ if (Object.keys(kept).length > 0) s.stepApprovals = kept;
1495
+ else delete s.stepApprovals;
1496
+ break;
1497
+ }
1498
+ case "user/message":
1499
+ case "developer/message": {
1500
+ const developer = event.type === "developer/message";
1501
+ const msg = developer ? messageOf$1(data) : deriveEventMessage(event);
1502
+ if (developer && (msg === null || !Array.isArray(msg.content))) return state;
1448
1503
  const s = ensure([
1449
1504
  "surface",
1450
1505
  "sums",
@@ -1454,15 +1509,15 @@ function applyTimeline(state, event, bounds) {
1454
1509
  bumpDetailRev(s);
1455
1510
  const node = applySurface(s, event, event.type, data, msg);
1456
1511
  const source = msg?.source;
1457
- if (isInjection(source)) {
1512
+ if (developer || isInjection(source)) {
1458
1513
  const rec = {
1459
1514
  seq: event.seq,
1460
1515
  time: event.time,
1461
1516
  kind: "inject",
1462
- form: typeof source.form === "string" && source.form !== "" ? source.form : "context",
1517
+ form: typeof source?.form === "string" && source.form !== "" ? source.form : "context",
1463
1518
  tokens: node.tokens
1464
1519
  };
1465
- if (source.kind === "skill-invocation") {
1520
+ if (source?.kind === "skill-invocation") {
1466
1521
  rec.sub = "skill";
1467
1522
  rec.name = typeof source.name === "string" ? source.name : "?";
1468
1523
  } else {
@@ -1471,7 +1526,7 @@ function applyTimeline(state, event, bounds) {
1471
1526
  rec.name = label;
1472
1527
  node.name = label;
1473
1528
  }
1474
- if (source.form === "notice" && typeof source.summary === "string" && source.summary !== "") rec.detail = source.summary;
1529
+ if (source?.form === "notice" && typeof source.summary === "string" && source.summary !== "") rec.detail = source.summary;
1475
1530
  }
1476
1531
  s.events.push(rec);
1477
1532
  } else {
@@ -1498,6 +1553,10 @@ function applyTimeline(state, event, bounds) {
1498
1553
  bumpDetailRev(s);
1499
1554
  const node = applySurface(s, event, event.type, data, toolMsg);
1500
1555
  if (node.tool === ASK_USER_TOOL) s.humanInputs = (s.humanInputs ?? 0) + 1;
1556
+ if (pendingEntry !== void 0 && pendingEntry.name === ASK_USER_TOOL && state.stepStart !== void 0) s.stepWaits = [...state.stepWaits ?? [], {
1557
+ start: pendingEntry.start,
1558
+ end: event.time
1559
+ }];
1501
1560
  if (pendingEntry !== void 0) pushFileOps(s, opsOfCall({
1502
1561
  seq: event.seq,
1503
1562
  time: event.time,
@@ -1565,6 +1624,7 @@ function applyTimeline(state, event, bounds) {
1565
1624
  };
1566
1625
  if (data && typeof data.turn === "number") record.turn = data.turn;
1567
1626
  if (data && typeof data.step === "number") record.step = data.step;
1627
+ if (state.stepStart !== void 0) s.stepRequestSeq = record.seq;
1568
1628
  let output = null;
1569
1629
  if (usage !== null && typeof usage === "object") {
1570
1630
  const input = tokenCountOf(usage.inputTokens);
@@ -2794,7 +2854,8 @@ const Config = z$1.object({
2794
2854
  "latest",
2795
2855
  "path"
2796
2856
  ]).default("count").loose()),
2797
- insightsEntry: volatileField(z$1.union(["show", "hide"]).default("show").loose())
2857
+ insightsEntry: volatileField(z$1.union(["show", "hide"]).default("show").loose()),
2858
+ defaultDurationCurve: volatileField(z$1.union(["show", "hide"]).default("show").loose())
2798
2859
  });
2799
2860
  /** Resolve the fold's retention bounds (the schema fills every default). */
2800
2861
  function resolveBounds(config) {
@@ -3174,6 +3235,11 @@ const requestRecordSchema = z.object({
3174
3235
  skill: z.number().int().nonnegative().optional(),
3175
3236
  cacheRead: z.number().int().nonnegative().optional(),
3176
3237
  output: z.number().int().nonnegative().optional(),
3238
+ /**
3239
+ * The request's step active milliseconds (see RequestRecord.activeMs).
3240
+ * Optional so a record whose step never closed (the live tail) still parses.
3241
+ */
3242
+ activeMs: z.number().nonnegative().optional(),
3177
3243
  stepCount: z.number().int().positive().optional()
3178
3244
  }).strict();
3179
3245
  const contextEventSchema = z.object({
@@ -3373,6 +3439,12 @@ const timelineStateSchema = z.object({
3373
3439
  time: z.number(),
3374
3440
  firstToken: z.number().optional()
3375
3441
  }).strict().optional(),
3442
+ stepWaits: z.array(z.object({
3443
+ start: z.number(),
3444
+ end: z.number()
3445
+ }).strict()).optional(),
3446
+ stepApprovals: z.record(z.string(), z.number()).optional(),
3447
+ stepRequestSeq: z.number().optional(),
3376
3448
  callNames: z.record(z.string(), z.object({
3377
3449
  name: z.string(),
3378
3450
  start: z.number(),
@@ -3432,7 +3504,7 @@ function createContextTimelineDefinition(config, slim) {
3432
3504
  },
3433
3505
  init: () => createTimelineState(),
3434
3506
  apply: (state, event) => applyTimeline(state, event, bounds),
3435
- stateVersion: 24
3507
+ stateVersion: 26
3436
3508
  };
3437
3509
  }
3438
3510
  //#endregion
@@ -3555,7 +3627,8 @@ const SettingsSchema = z$1.object({
3555
3627
  "latest",
3556
3628
  "path"
3557
3629
  ]).default("count").loose(),
3558
- insightsEntry: z$1.union(["show", "hide"]).default("show").loose()
3630
+ insightsEntry: z$1.union(["show", "hide"]).default("show").loose(),
3631
+ defaultDurationCurve: z$1.union(["show", "hide"]).default("show").loose()
3559
3632
  });
3560
3633
  /** Serve the namespace while a settings provider with the register face is composed; inert otherwise. */
3561
3634
  function installSettings(ctx) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-context",
3
- "version": "0.62.3",
3
+ "version": "0.64.0",
4
4
  "description": "A DeepSeek Harness plugin for context insight and management, with context dashboard and context command, for understanding how the context is made of, and how it evolves.",
5
5
  "icon": "icon.svg",
6
6
  "author": "bowenliang123",