dsh-context 0.60.0 → 0.62.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,28 @@ interface TimelineState {
157
157
  time: number;
158
158
  firstToken?: number;
159
159
  };
160
+ /**
161
+ * The timing strip's painted spans (shared/types.ts TimingSpan): every
162
+ * completed step's time slices in log order, stamped with their REAL
163
+ * instants — the TTFT wait, the decode blocks in stream order, the tool-run
164
+ * windows, and the in-step residue. The client packs them gapless, each
165
+ * band's width its share of the cumulative active time (idle time BETWEEN
166
+ * steps carries no span and takes no track). Bounded by TIMING_SPANS_MAX
167
+ * (newest tail kept). Flushed per step at `step/end` from the `stepSpans`
168
+ * accumulator.
169
+ */
170
+ spans: TimingSpan[];
171
+ /**
172
+ * The open step's accumulated painted spans: the assistant message pushes
173
+ * the model slices (TTFT + the decode blocks), each paired tool result its
174
+ * run window; `step/end` clamps them into the step window, de-overlaps
175
+ * first-wins (parallel runs paint their union), fills the holes with the
176
+ * residue kind, and flushes the tiling into `spans`. Absent until the first
177
+ * in-step span; DELETED at the flush (the plain-JSON precondition). Same
178
+ * arm/remove lifecycle as `stepStart` — never written in place, always
179
+ * replaced, so no CloneKey covers it.
180
+ */
181
+ stepSpans?: TimingSpan[];
160
182
  /**
161
183
  * Tool callId → the call's name, start instant, and raw arguments, armed by
162
184
  * `tool/call` and DELETED when its `tool/result` folds in (one result per
@@ -469,6 +491,13 @@ interface Snapshot {
469
491
  */
470
492
  fileOps?: FileOpRecord[];
471
493
  fileOpsFloor?: number;
494
+ /**
495
+ * The timing strip's painted spans (see TimingSpan) — present on the INLINE
496
+ * wire value and on the detail payload, absent from the slim head (they ride
497
+ * the detail channel there) and from rows folded before the collection
498
+ * existed (older hosts; the client then shows no strip).
499
+ */
500
+ spans?: TimingSpan[];
472
501
  }
473
502
  /**
474
503
  * The on-demand DETAIL payload of the split `contextTimeline` generation —
@@ -509,6 +538,13 @@ interface ContextTimelineDetail {
509
538
  fileOps?: FileOpRecord[];
510
539
  /** The newest dropped op's seq when the op log trimmed (coverage honesty, same family as archiveFloor). */
511
540
  fileOpsFloor?: number;
541
+ /**
542
+ * The timing strip's painted spans (see TimingSpan): the completed steps'
543
+ * time slices in log order, positioned by their real instants. Additive-
544
+ * optional: a payload folded before the collection existed (older hosts)
545
+ * serves without it, and the timing card shows no strip then.
546
+ */
547
+ spans?: TimingSpan[];
512
548
  }
513
549
  /**
514
550
  * One executed file operation (a settled file-tool call with a resolved
@@ -559,6 +595,22 @@ interface FileOpRecord {
559
595
  * delivered projection is by definition available); kept for wire compatibility with the snapshot shape.
560
596
  */
561
597
  type ContextTimeline = Snapshot;
598
+ /**
599
+ * One painted span of the timing card's session-time strip (the card's bottom
600
+ * band): a completed step's time slice — the TTFT wait, one decode block (in
601
+ * stream order), one tool-run window, or an in-step residue gap — folded
602
+ * host-side at `step/end`. The instants are the slice's REAL occurrence times
603
+ * (the hover tip reads them); the strip packs the spans gapless in log order,
604
+ * each band's width its share of the session's cumulative active time (idle
605
+ * time BETWEEN steps carries no span and takes no track). `end` > `start`
606
+ * always (the fold drops zero/negative slices).
607
+ */
608
+ interface TimingSpan {
609
+ /** The slice kind — the timing card's own slice vocabulary (same keys, same colors). */
610
+ kind: 'ttft' | 'reasoning' | 'text' | 'toolarg' | 'tools' | 'other';
611
+ start: number;
612
+ end: number;
613
+ }
562
614
  /**
563
615
  * Cumulative billed-token totals for one pricing bucket of the session-cost
564
616
  * estimate (host-folded, never trimmed — running totals over the COMPLETE
package/lib/index.js CHANGED
@@ -414,6 +414,43 @@ function decodeTallyOfStream(stream, endTime) {
414
414
  return tally;
415
415
  }
416
416
  /**
417
+ * The ORDERED decode spans of one embedded assistant stream — the same
418
+ * ownership rule as {@link decodeTallyOfStream} (each `block-start` marker
419
+ * owns the interval up to the next one, the last one up to `endTime`), but
420
+ * keeping the sequence: the timing strip paints the decode window in stream
421
+ * order. Total over untrusted input, mirroring the tally: a malformed record
422
+ * is skipped whole, an unknown `blockType` leaves its interval unspanned
423
+ * (the caller's residue fill owns it), a zero/negative-length span drops
424
+ * out, and a stream with no marker (or not an array) yields none.
425
+ */
426
+ function decodeSpansOfStream(stream, endTime) {
427
+ const spans = [];
428
+ if (!Array.isArray(stream) || !Number.isFinite(endTime)) return spans;
429
+ let kind;
430
+ let since = 0;
431
+ const close = (to) => {
432
+ if (kind !== void 0 && to > since) spans.push({
433
+ kind,
434
+ start: since,
435
+ end: to
436
+ });
437
+ };
438
+ for (const record of stream) {
439
+ if (record === null || typeof record !== "object") continue;
440
+ const r = record;
441
+ if (r.type !== "chunk" || r.chunk === null || typeof r.chunk !== "object") continue;
442
+ const chunk = r.chunk;
443
+ if (chunk.type !== "block-start") continue;
444
+ const time = r.time;
445
+ if (typeof time !== "number" || !Number.isFinite(time)) continue;
446
+ close(time);
447
+ kind = decodeKindOfBlock(chunk.blockType);
448
+ since = time;
449
+ }
450
+ close(endTime);
451
+ return spans;
452
+ }
453
+ /**
417
454
  * The first token's instant inside one PACKED delta run (`text-chunks` /
418
455
  * `reasoning-chunks` / `tool-call-chunks`): the run's base time plus the
419
456
  * accumulated inter-member deltas, taken at the first qualifying member —
@@ -758,6 +795,7 @@ function trimState(st, bounds) {
758
795
  st.turnRuns = countTurnRuns(st.requests);
759
796
  }
760
797
  if (st.events.length > bounds.maxEvents) st.events = st.events.slice(-bounds.maxEvents);
798
+ if (st.spans.length > TIMING_SPANS_MAX) st.spans = st.spans.slice(-TIMING_SPANS_MAX);
761
799
  if (st.fileOps.length > bounds.maxFileOps) {
762
800
  const drop = st.fileOps.length - bounds.maxFileOps;
763
801
  st.fileOpsFloor = Math.max(st.fileOpsFloor ?? 0, st.fileOps[drop - 1].seq);
@@ -775,6 +813,13 @@ function trimState(st, bounds) {
775
813
  }
776
814
  }
777
815
  }
816
+ /**
817
+ * Bound on the timing strip's painted spans (TimelineState.spans): a count
818
+ * cap, newest tail kept — the same hardcoded-cap family as SYSTEM_NODES_MAX.
819
+ * At ~6–8 spans per step the cap covers a few hundred recent steps, far past
820
+ * the strip's pixel resolution on any real pane.
821
+ */
822
+ const TIMING_SPANS_MAX = 2e3;
778
823
  function createTimelineState() {
779
824
  return {
780
825
  surface: [],
@@ -792,7 +837,8 @@ function createTimelineState() {
792
837
  events: [],
793
838
  archived: [],
794
839
  callNames: {},
795
- fileOps: []
840
+ fileOps: [],
841
+ spans: []
796
842
  };
797
843
  }
798
844
  function categoryOf(type, message) {
@@ -969,6 +1015,11 @@ function applySurface(st, ev, type, data, message) {
969
1015
  timing.toolsMs += dur;
970
1016
  timing.toolCalls += 1;
971
1017
  bumpToolTotals(timing, toolEntry.name, dur);
1018
+ if (st.stepStart !== void 0) st.stepSpans = [...st.stepSpans ?? [], {
1019
+ kind: "tools",
1020
+ start: toolEntry.start,
1021
+ end: ev.time
1022
+ }];
972
1023
  }
973
1024
  if (typeof srcId === "string" || typeof blockId === "string") {
974
1025
  const kept = {};
@@ -1345,6 +1396,7 @@ function applyTimeline(state, event, bounds) {
1345
1396
  case "step/start": {
1346
1397
  const s = ensure([]);
1347
1398
  s.stepStart = { time: event.time };
1399
+ delete s.stepSpans;
1348
1400
  break;
1349
1401
  }
1350
1402
  case "step/end": {
@@ -1352,7 +1404,43 @@ function applyTimeline(state, event, bounds) {
1352
1404
  if (start === void 0) return state;
1353
1405
  const s = ensure(["timing"]);
1354
1406
  ensureTiming(s).wallMs += durOf(start.time, event.time);
1407
+ const painted = [];
1408
+ for (const span of state.stepSpans ?? []) {
1409
+ const from = Math.max(start.time, span.start);
1410
+ const to = Math.min(event.time, span.end);
1411
+ if (to > from) painted.push({
1412
+ kind: span.kind,
1413
+ start: from,
1414
+ end: to
1415
+ });
1416
+ }
1417
+ painted.sort((a, b) => a.start - b.start || a.end - b.end);
1418
+ const flushed = [];
1419
+ let cursor = start.time;
1420
+ for (const span of painted) {
1421
+ const from = Math.max(cursor, span.start);
1422
+ if (span.end <= from) continue;
1423
+ if (from > cursor) flushed.push({
1424
+ kind: "other",
1425
+ start: cursor,
1426
+ end: from
1427
+ });
1428
+ flushed.push({
1429
+ kind: span.kind,
1430
+ start: from,
1431
+ end: span.end
1432
+ });
1433
+ cursor = span.end;
1434
+ }
1435
+ if (event.time > cursor) flushed.push({
1436
+ kind: "other",
1437
+ start: cursor,
1438
+ end: event.time
1439
+ });
1440
+ s.spans = [...state.spans, ...flushed];
1355
1441
  delete s.stepStart;
1442
+ delete s.stepSpans;
1443
+ bumpDetailRev(s);
1356
1444
  break;
1357
1445
  }
1358
1446
  case "user/message": {
@@ -1506,6 +1594,24 @@ function applyTimeline(state, event, bounds) {
1506
1594
  if (firstToken !== void 0) {
1507
1595
  timing.ttftMs += durOf(stepStart.time, firstToken);
1508
1596
  timing.genMs += durOf(firstToken, event.time);
1597
+ const decodeBlocks = decodeSpansOfStream(data?.stream, event.time);
1598
+ let decodeStart = firstToken;
1599
+ for (const block of decodeBlocks) decodeStart = Math.min(decodeStart, block.start);
1600
+ const modelSpans = [{
1601
+ kind: "ttft",
1602
+ start: stepStart.time,
1603
+ end: decodeStart
1604
+ }];
1605
+ for (const block of decodeBlocks) {
1606
+ const to = Math.min(event.time, block.end);
1607
+ if (to <= block.start) continue;
1608
+ modelSpans.push({
1609
+ kind: block.kind,
1610
+ start: modelSpans.length === 1 ? decodeStart : block.start,
1611
+ end: to
1612
+ });
1613
+ }
1614
+ s.stepSpans = [...s.stepSpans ?? [], ...modelSpans];
1509
1615
  if (output !== null) {
1510
1616
  timing.speedMs = (timing.speedMs ?? 0) + durOf(firstToken, event.time);
1511
1617
  timing.speedTokens = (timing.speedTokens ?? 0) + output;
@@ -1641,7 +1747,8 @@ function detailCollectionsOf(state, bounds) {
1641
1747
  droppedNodes: 0,
1642
1748
  archive: state.archived.map((n) => ({ ...n })),
1643
1749
  fileOps: state.fileOps.map((o) => ({ ...o })),
1644
- ...state.fileOpsFloor !== void 0 ? { fileOpsFloor: state.fileOpsFloor } : {}
1750
+ ...state.fileOpsFloor !== void 0 ? { fileOpsFloor: state.fileOpsFloor } : {},
1751
+ spans: state.spans.map((s) => ({ ...s }))
1645
1752
  };
1646
1753
  const overflowCount = Math.max(0, state.surface.length - bounds.maxNodes);
1647
1754
  const overflow = state.surface.slice(0, overflowCount);
@@ -3103,6 +3210,19 @@ const fileOpSchema = z.object({
3103
3210
  pattern: z.literal(true).optional(),
3104
3211
  gone: z.number().int().nonnegative().optional()
3105
3212
  }).strict();
3213
+ /** One painted span of the timing strip (shared/types.ts TimingSpan). */
3214
+ const timingSpanSchema = z.object({
3215
+ kind: z.enum([
3216
+ "ttft",
3217
+ "reasoning",
3218
+ "text",
3219
+ "toolarg",
3220
+ "tools",
3221
+ "other"
3222
+ ]),
3223
+ start: z.number(),
3224
+ end: z.number()
3225
+ }).strict();
3106
3226
  const currentSchema = z.object({
3107
3227
  system: z.number().int().nonnegative(),
3108
3228
  tools: z.number().int().nonnegative(),
@@ -3198,7 +3318,8 @@ const contextTimelineSchema = z.object({
3198
3318
  surfaceFloor: z.number().int().nonnegative().optional(),
3199
3319
  archiveFloor: z.number().int().nonnegative().optional(),
3200
3320
  fileOps: z.array(fileOpSchema).optional(),
3201
- fileOpsFloor: z.number().int().nonnegative().optional()
3321
+ fileOpsFloor: z.number().int().nonnegative().optional(),
3322
+ spans: z.array(timingSpanSchema).optional()
3202
3323
  }).strict();
3203
3324
  /**
3204
3325
  * The persisted fold-state schema (the registry's `stateSchema`
@@ -3245,6 +3366,8 @@ const timelineStateSchema = z.object({
3245
3366
  detailRev: z.number().int().nonnegative().optional(),
3246
3367
  fileOps: z.array(fileOpSchema),
3247
3368
  fileOpsFloor: z.number().int().nonnegative().optional(),
3369
+ spans: z.array(timingSpanSchema),
3370
+ stepSpans: z.array(timingSpanSchema).optional(),
3248
3371
  pendingCodeOps: z.record(z.string(), z.array(fileOpSchema)).optional()
3249
3372
  });
3250
3373
  /**
@@ -3292,7 +3415,7 @@ function createContextTimelineDefinition(config, slim) {
3292
3415
  },
3293
3416
  init: () => createTimelineState(),
3294
3417
  apply: (state, event) => applyTimeline(state, event, bounds),
3295
- stateVersion: 22
3418
+ stateVersion: 23
3296
3419
  };
3297
3420
  }
3298
3421
  //#endregion
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-context",
3
- "version": "0.60.0",
3
+ "version": "0.62.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",
@@ -69,7 +69,8 @@
69
69
  "compatibility": {
70
70
  "dshReleases": {
71
71
  "0.1.5-rc.1": "compatible",
72
- "0.1.7-rc.2": "compatible"
72
+ "0.1.7-rc.2": "compatible",
73
+ "0.2.0-rc.2": "compatible"
73
74
  }
74
75
  }
75
76
  },