dsh-context 0.66.1 → 0.67.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.js CHANGED
@@ -1,12 +1,12 @@
1
1
  /**
2
- * dsh-context v0.66.1
2
+ * dsh-context v0.67.0
3
3
  * 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.
4
4
  * @author bowenliang123
5
5
  * @license Apache-2.0
6
6
  * @homepage https://github.com/bowenliang123/dsh-context
7
- * @built 2026-10-09T09:32:06+00:00
8
- * @commit def8b2b74a8e
9
- * @commit-date 2026-10-09T17:29:50+08:00
7
+ * @built 2026-10-10T10:12:42+00:00
8
+ * @commit df136da57dac
9
+ * @commit-date 2026-10-10T18:10:23+08:00
10
10
  */
11
11
 
12
12
  import { createRequire } from "node:module";
@@ -18,21 +18,6 @@ import { dirname, isAbsolute, join, normalize, relative } from "node:path";
18
18
  import { fileURLToPath } from "node:url";
19
19
  import { getHeapStatistics } from "node:v8";
20
20
  import { randomUUID } from "node:crypto";
21
- //#region src/shared/providers.ts
22
- const MODELS_DEV_PROVIDER_IDS = {
23
- "deepseek-official": "deepseek",
24
- "deepseek-account": "deepseek",
25
- "kimi-coding": "moonshotai",
26
- "minimax-cn": "minimax",
27
- "zai-coding-cn": "zhipuai"
28
- };
29
- function modelsDevProviderOf(dshProviderId) {
30
- return MODELS_DEV_PROVIDER_IDS[dshProviderId] ?? dshProviderId;
31
- }
32
- function isDeepSeekProvider(dshProviderId) {
33
- return modelsDevProviderOf(dshProviderId) === "deepseek";
34
- }
35
- //#endregion
36
21
  //#region src/shared/estimate.ts
37
22
  const CHARS_PER_TOKEN$1 = 4;
38
23
  const ROLE_OVERHEAD$1 = 4;
@@ -548,7 +533,7 @@ function opsOfCall(input) {
548
533
  }];
549
534
  }
550
535
  //#endregion
551
- //#region src/host/fold.ts
536
+ //#region src/host/foldState.ts
552
537
  function trimToLastTurns(requests, maxTurns) {
553
538
  let runs = 0;
554
539
  let start = requests.length;
@@ -624,6 +609,280 @@ function createTimelineState() {
624
609
  spans: []
625
610
  };
626
611
  }
612
+ //#endregion
613
+ //#region src/shared/providers.ts
614
+ const MODELS_DEV_PROVIDER_IDS = {
615
+ "deepseek-official": "deepseek",
616
+ "deepseek-account": "deepseek",
617
+ "kimi-coding": "moonshotai",
618
+ "minimax-cn": "minimax",
619
+ "zai-coding-cn": "zhipuai"
620
+ };
621
+ function modelsDevProviderOf(dshProviderId) {
622
+ return MODELS_DEV_PROVIDER_IDS[dshProviderId] ?? dshProviderId;
623
+ }
624
+ function isDeepSeekProvider(dshProviderId) {
625
+ return modelsDevProviderOf(dshProviderId) === "deepseek";
626
+ }
627
+ //#endregion
628
+ //#region src/host/foldMetering.ts
629
+ function tokenCountOf(value) {
630
+ if (typeof value === "number") return Number.isFinite(value) ? Math.max(0, Math.round(value)) : null;
631
+ if (typeof value === "string" && value.trim() !== "") {
632
+ const parsed = Number(value);
633
+ return Number.isFinite(parsed) ? Math.max(0, Math.round(parsed)) : null;
634
+ }
635
+ return null;
636
+ }
637
+ function isPeakUtc(time) {
638
+ const at = new Date(time);
639
+ const day = at.getUTCDay();
640
+ if (day === 0 || day === 6) return false;
641
+ const h = at.getUTCHours();
642
+ return h >= 1 && h < 4 || h >= 6 && h < 10;
643
+ }
644
+ function addBilledUsage(prev, provider, model, time, usage) {
645
+ const period = isDeepSeekProvider(provider) && !isPeakUtc(time) ? "off" : "peak";
646
+ const models = prev?.[provider] ?? {};
647
+ const periods = models[model] ?? {};
648
+ const b = periods[period] ?? {
649
+ uncached: 0,
650
+ cacheRead: 0,
651
+ cacheWrite: 0,
652
+ output: 0
653
+ };
654
+ const nextPeriods = { ...periods };
655
+ nextPeriods[period] = {
656
+ uncached: b.uncached + usage.uncached,
657
+ cacheRead: b.cacheRead + usage.cacheRead,
658
+ cacheWrite: b.cacheWrite + usage.cacheWrite,
659
+ output: b.output + usage.output
660
+ };
661
+ const nextModels = {
662
+ ...models,
663
+ [model]: nextPeriods
664
+ };
665
+ return {
666
+ ...prev ?? {},
667
+ [provider]: nextModels
668
+ };
669
+ }
670
+ function copyCostUsage(cost) {
671
+ const copy = {};
672
+ for (const provider in cost) {
673
+ const models = {};
674
+ for (const model in cost[provider]) {
675
+ const periods = cost[provider][model];
676
+ const modelCopy = {};
677
+ if (periods.peak !== void 0) modelCopy.peak = { ...periods.peak };
678
+ if (periods.off !== void 0) modelCopy.off = { ...periods.off };
679
+ models[model] = modelCopy;
680
+ }
681
+ copy[provider] = models;
682
+ }
683
+ return copy;
684
+ }
685
+ function accumulateCost(st, time, usage) {
686
+ const model = st.model;
687
+ if (model === void 0) return;
688
+ st.cost = addBilledUsage(st.cost, st.provider ?? "", model, time, usage);
689
+ }
690
+ const TOOL_TIMING_CAP = 16;
691
+ const DECODE_KINDS = [
692
+ "reasoning",
693
+ "text",
694
+ "toolarg"
695
+ ];
696
+ function durOf(from, to) {
697
+ if (!Number.isFinite(from) || !Number.isFinite(to)) return 0;
698
+ return Math.max(0, to - from);
699
+ }
700
+ function ensureTiming(st) {
701
+ if (st.timing === void 0) st.timing = {
702
+ wallMs: 0,
703
+ ttftMs: 0,
704
+ genMs: 0,
705
+ calls: 0,
706
+ toolsMs: 0,
707
+ toolCalls: 0,
708
+ tools: {}
709
+ };
710
+ return st.timing;
711
+ }
712
+ const DECODE_FIELDS = {
713
+ reasoning: {
714
+ ms: "reasoningMs",
715
+ n: "reasoningBlocks"
716
+ },
717
+ text: {
718
+ ms: "textMs",
719
+ n: "textBlocks"
720
+ },
721
+ toolarg: {
722
+ ms: "toolArgMs",
723
+ n: "toolArgBlocks"
724
+ }
725
+ };
726
+ function addDecode(timing, kind, ms, blocks) {
727
+ const field = DECODE_FIELDS[kind];
728
+ if (ms > 0) timing[field.ms] = (timing[field.ms] ?? 0) + ms;
729
+ if (blocks > 0) timing[field.n] = (timing[field.n] ?? 0) + blocks;
730
+ }
731
+ function bumpToolTotals(timing, name, ms) {
732
+ if (name === "__proto__") return;
733
+ if (!Object.hasOwn(timing.tools, name)) {
734
+ if (Object.keys(timing.tools).length >= TOOL_TIMING_CAP) {
735
+ let minKey = "";
736
+ let minMs = Infinity;
737
+ for (const k in timing.tools) if (timing.tools[k].ms < minMs) {
738
+ minMs = timing.tools[k].ms;
739
+ minKey = k;
740
+ }
741
+ const kept = {};
742
+ for (const k in timing.tools) if (k !== minKey) kept[k] = timing.tools[k];
743
+ timing.tools = kept;
744
+ }
745
+ timing.tools[name] = {
746
+ calls: 1,
747
+ ms
748
+ };
749
+ return;
750
+ }
751
+ const cur = timing.tools[name];
752
+ timing.tools[name] = {
753
+ calls: cur.calls + 1,
754
+ ms: cur.ms + ms
755
+ };
756
+ }
757
+ //#endregion
758
+ //#region src/host/foldWire.ts
759
+ function headFieldsOf(state) {
760
+ const surfaceTotal = state.sums.user + state.sums.inject + state.sums.skill + state.sums.assistant + state.sums.tool;
761
+ const result = {
762
+ ok: true,
763
+ ...state.model !== void 0 ? { model: state.model } : {},
764
+ ...state.provider !== void 0 ? { provider: state.provider } : {},
765
+ ...state.contextWindow !== void 0 ? { contextWindow: state.contextWindow } : {},
766
+ current: {
767
+ system: state.systemTokens,
768
+ tools: state.toolsTokens,
769
+ user: state.sums.user,
770
+ inject: state.sums.inject,
771
+ skill: state.sums.skill,
772
+ assistant: state.sums.assistant,
773
+ tool: state.sums.tool,
774
+ total: surfaceTotal + state.systemTokens + state.toolsTokens
775
+ },
776
+ images: state.surface.reduce((n, node) => n + (node.imgs ?? 0), 0),
777
+ toolCalls: state.surface.reduce((n, node) => node.cat === "tool" || node.cat === "skill" && node.tool !== void 0 ? n + 1 : n, 0),
778
+ humanInputs: state.humanInputs ?? 0,
779
+ answers: state.answers ?? 0,
780
+ ...state.lastUser !== void 0 ? { lastUser: state.lastUser } : {},
781
+ requests: [],
782
+ events: [],
783
+ nodes: [],
784
+ droppedNodes: 0,
785
+ archive: []
786
+ };
787
+ if (state.cost !== void 0) result.cost = copyCostUsage(state.cost);
788
+ if (state.timing !== void 0) {
789
+ const tools = {};
790
+ for (const k in state.timing.tools) tools[k] = { ...state.timing.tools[k] };
791
+ result.timing = {
792
+ ...state.timing,
793
+ tools
794
+ };
795
+ }
796
+ if (state.systems !== void 0 && state.systems.length > 0) result.systems = state.systems.map((n) => ({ ...n }));
797
+ return result;
798
+ }
799
+ function detailCollectionsOf(state, bounds) {
800
+ const result = {
801
+ requests: state.requests.map((r) => ({ ...r })),
802
+ events: state.events.map((e) => ({ ...e })),
803
+ nodes: [],
804
+ droppedNodes: 0,
805
+ archive: state.archived.map((n) => ({ ...n })),
806
+ fileOps: state.fileOps.map((o) => ({ ...o })),
807
+ ...state.fileOpsFloor !== void 0 ? { fileOpsFloor: state.fileOpsFloor } : {},
808
+ spans: state.spans.map((s) => ({ ...s }))
809
+ };
810
+ const overflowCount = Math.max(0, state.surface.length - bounds.maxNodes);
811
+ const overflow = state.surface.slice(0, overflowCount);
812
+ const tail = state.surface.slice(overflowCount);
813
+ const pinned = overflow.filter((n) => n.cat === "inject" || n.cat === "skill");
814
+ result.nodes = pinned.length > 0 ? [...pinned, ...tail] : tail;
815
+ result.droppedNodes = overflowCount - pinned.length;
816
+ if (result.droppedNodes > 0) {
817
+ let floor = 0;
818
+ for (const n of overflow) if (n.cat !== "inject" && n.cat !== "skill") floor = Math.max(floor, n.seq);
819
+ result.surfaceFloor = floor;
820
+ }
821
+ if (state.archiveFloor !== void 0) result.archiveFloor = state.archiveFloor;
822
+ const requests = result.requests;
823
+ const events = result.events;
824
+ let ri = 0;
825
+ for (const ev of events) {
826
+ while (ri < requests.length && requests[ri].seq <= ev.seq) ri++;
827
+ const next = requests.at(ri);
828
+ const prev = ri > 0 ? requests.at(ri - 1) : void 0;
829
+ if (next !== void 0 && typeof next.turn === "number" && typeof next.step === "number") {
830
+ ev.turn = next.turn;
831
+ ev.step = next.step;
832
+ }
833
+ if (prev !== void 0 && typeof prev.turn === "number" && typeof prev.step === "number") {
834
+ ev.fromTurn = prev.turn;
835
+ ev.fromStep = prev.step;
836
+ }
837
+ }
838
+ return result;
839
+ }
840
+ function buildTimelineHead(state) {
841
+ const result = headFieldsOf(state);
842
+ const turns = new Set();
843
+ for (const r of state.requests) turns.add(r.turn ?? 0);
844
+ let injects = 0;
845
+ let compactions = 0;
846
+ let prunes = 0;
847
+ const skills = new Set();
848
+ for (const e of state.events) {
849
+ if (e.kind === "inject") injects++;
850
+ else if (e.kind === "compaction") compactions++;
851
+ else if (e.kind === "prune") prunes++;
852
+ if (e.sub === "skill" && typeof e.name === "string" && e.name !== "") skills.add(e.name);
853
+ }
854
+ result.counts = {
855
+ turns: turns.size,
856
+ steps: state.requests.length,
857
+ injects,
858
+ compactions,
859
+ prunes,
860
+ skills: skills.size
861
+ };
862
+ const last = state.requests.at(-1);
863
+ if (last !== void 0) result.last = {
864
+ seq: last.seq,
865
+ total: last.total,
866
+ ...typeof last.prompt === "number" ? { prompt: last.prompt } : {}
867
+ };
868
+ result.detailRev = state.detailRev ?? 0;
869
+ return result;
870
+ }
871
+ function buildTimelineDetail(state, bounds) {
872
+ return {
873
+ rev: state.detailRev ?? 0,
874
+ head: buildTimelineHead(state),
875
+ ...detailCollectionsOf(state, bounds)
876
+ };
877
+ }
878
+ function buildTimelineView(state, bounds) {
879
+ return {
880
+ ...headFieldsOf(state),
881
+ ...detailCollectionsOf(state, bounds)
882
+ };
883
+ }
884
+ //#endregion
885
+ //#region src/host/fold.ts
627
886
  function categoryOf(type, message) {
628
887
  if (type === "assistant/message") return "assistant";
629
888
  if (type === "tool/result") return "tool";
@@ -727,191 +986,79 @@ function applySurface(st, ev, type, message) {
727
986
  } else if (type === "tool/result") {
728
987
  const srcId = source?.callId;
729
988
  const toolEntry = typeof srcId === "string" ? st.callNames[srcId] : void 0;
730
- if (toolEntry !== void 0) {
731
- node.tool = toolEntry.name;
732
- const timing = ensureTiming(st);
733
- const dur = durOf(toolEntry.start, ev.time);
734
- timing.toolsMs += dur;
735
- timing.toolCalls += 1;
736
- bumpToolTotals(timing, toolEntry.name, dur);
737
- if (st.stepStart !== void 0) st.stepSpans = [...st.stepSpans ?? [], {
738
- kind: "tools",
739
- start: toolEntry.start,
740
- end: ev.time
741
- }];
742
- }
743
- if (typeof srcId === "string") {
744
- const kept = {};
745
- for (const k in st.callNames) if (k !== srcId) kept[k] = st.callNames[k];
746
- st.callNames = kept;
747
- }
748
- if (message?.isError === true) node.err = true;
749
- } else if (source?.kind === "skill-invocation") node.skill = typeof source.name === "string" ? source.name : "?";
750
- else if (source?.kind === "plugin") {
751
- if (source.form === "notice" && typeof source.summary === "string") node.text = source.summary;
752
- else if (source.form === "snapshot" && Array.isArray(source.sections)) node.text = source.sections.map((s) => s?.name).filter(Boolean).join(", ").slice(0, 80);
753
- else {
754
- const ptext = firstText(message?.content);
755
- if (ptext !== "") node.text = ptext;
756
- }
757
- } else {
758
- const utext = firstText(message?.content);
759
- if (utext !== "") node.text = utext;
760
- }
761
- const shadowedSeqs = st.pendingShadowedSeqs;
762
- const shadowEventSeq = st.pendingShadowEventSeq;
763
- delete st.pendingShadowedSeqs;
764
- delete st.pendingShadowEventSeq;
765
- const op = replaceRangeOf(ev.surfaceOp);
766
- if (op !== null) {
767
- if (Array.isArray(shadowedSeqs) && shadowedSeqs.length > 0) {
768
- const removed = removeSurfaceSeqs(st, new Set(shadowedSeqs), ev.seq);
769
- st.sums[cat] += node.tokens;
770
- st.surface.push(node);
771
- if (shadowEventSeq !== void 0) {
772
- const removedSum = removed.reduce((sum, n) => sum + n.tokens, 0);
773
- const i = st.events.findIndex((e) => e.seq === shadowEventSeq);
774
- if (i >= 0) st.events[i] = {
775
- ...st.events[i],
776
- tokens: Math.max(0, removedSum - node.tokens)
777
- };
778
- }
779
- return node;
780
- }
781
- let si = -1;
782
- let ei = -1;
783
- for (let i = 0; i < st.surface.length; i++) {
784
- if (si < 0 && st.surface[i].seq === op.start) si = i;
785
- if (st.surface[i].seq === op.end) {
786
- ei = i;
787
- break;
788
- }
789
- }
790
- if (si >= 0 && ei >= si) {
791
- const removed = st.surface.splice(si, ei - si + 1, node);
792
- archiveRemoved(st, removed, ev.seq);
793
- for (const r of removed) st.sums[r.cat] -= r.tokens;
794
- st.sums[cat] += node.tokens;
795
- return node;
796
- }
797
- }
798
- st.surface.push(node);
799
- st.sums[cat] += node.tokens;
800
- return node;
801
- }
802
- function tokenCountOf(value) {
803
- if (typeof value === "number") return Number.isFinite(value) ? Math.max(0, Math.round(value)) : null;
804
- if (typeof value === "string" && value.trim() !== "") {
805
- const parsed = Number(value);
806
- return Number.isFinite(parsed) ? Math.max(0, Math.round(parsed)) : null;
807
- }
808
- return null;
809
- }
810
- function isPeakUtc(time) {
811
- const at = new Date(time);
812
- const day = at.getUTCDay();
813
- if (day === 0 || day === 6) return false;
814
- const h = at.getUTCHours();
815
- return h >= 1 && h < 4 || h >= 6 && h < 10;
816
- }
817
- function addBilledUsage(prev, provider, model, time, usage) {
818
- const period = isDeepSeekProvider(provider) && !isPeakUtc(time) ? "off" : "peak";
819
- const models = prev?.[provider] ?? {};
820
- const periods = models[model] ?? {};
821
- const b = periods[period] ?? {
822
- uncached: 0,
823
- cacheRead: 0,
824
- cacheWrite: 0,
825
- output: 0
826
- };
827
- const nextPeriods = { ...periods };
828
- nextPeriods[period] = {
829
- uncached: b.uncached + usage.input,
830
- cacheRead: b.cacheRead + usage.cacheRead,
831
- cacheWrite: b.cacheWrite + usage.cacheWrite,
832
- output: b.output + usage.output
833
- };
834
- const nextModels = {
835
- ...models,
836
- [model]: nextPeriods
837
- };
838
- return {
839
- ...prev ?? {},
840
- [provider]: nextModels
841
- };
842
- }
843
- function accumulateCost(st, time, usage) {
844
- const model = st.model;
845
- if (model === void 0) return;
846
- st.cost = addBilledUsage(st.cost, st.provider ?? "", model, time, usage);
847
- }
848
- const TOOL_TIMING_CAP = 16;
849
- const ASK_USER_TOOL = "ask_user_question";
850
- const DECODE_KINDS = [
851
- "reasoning",
852
- "text",
853
- "toolarg"
854
- ];
855
- function durOf(from, to) {
856
- if (!Number.isFinite(from) || !Number.isFinite(to)) return 0;
857
- return Math.max(0, to - from);
858
- }
859
- function ensureTiming(st) {
860
- if (st.timing === void 0) st.timing = {
861
- wallMs: 0,
862
- ttftMs: 0,
863
- genMs: 0,
864
- calls: 0,
865
- toolsMs: 0,
866
- toolCalls: 0,
867
- tools: {}
868
- };
869
- return st.timing;
870
- }
871
- const DECODE_FIELDS = {
872
- reasoning: {
873
- ms: "reasoningMs",
874
- n: "reasoningBlocks"
875
- },
876
- text: {
877
- ms: "textMs",
878
- n: "textBlocks"
879
- },
880
- toolarg: {
881
- ms: "toolArgMs",
882
- n: "toolArgBlocks"
989
+ if (toolEntry !== void 0) {
990
+ node.tool = toolEntry.name;
991
+ const timing = ensureTiming(st);
992
+ const dur = durOf(toolEntry.start, ev.time);
993
+ timing.toolsMs += dur;
994
+ timing.toolCalls += 1;
995
+ bumpToolTotals(timing, toolEntry.name, dur);
996
+ if (st.stepStart !== void 0) st.stepSpans = [...st.stepSpans ?? [], {
997
+ kind: "tools",
998
+ start: toolEntry.start,
999
+ end: ev.time
1000
+ }];
1001
+ }
1002
+ if (typeof srcId === "string") {
1003
+ const kept = {};
1004
+ for (const k in st.callNames) if (k !== srcId) kept[k] = st.callNames[k];
1005
+ st.callNames = kept;
1006
+ }
1007
+ if (message?.isError === true) node.err = true;
1008
+ } else if (source?.kind === "skill-invocation") node.skill = typeof source.name === "string" ? source.name : "?";
1009
+ else if (source?.kind === "plugin") {
1010
+ if (source.form === "notice" && typeof source.summary === "string") node.text = source.summary;
1011
+ else if (source.form === "snapshot" && Array.isArray(source.sections)) node.text = source.sections.map((s) => s?.name).filter(Boolean).join(", ").slice(0, 80);
1012
+ else {
1013
+ const ptext = firstText(message?.content);
1014
+ if (ptext !== "") node.text = ptext;
1015
+ }
1016
+ } else {
1017
+ const utext = firstText(message?.content);
1018
+ if (utext !== "") node.text = utext;
883
1019
  }
884
- };
885
- function addDecode(timing, kind, ms, blocks) {
886
- const field = DECODE_FIELDS[kind];
887
- if (ms > 0) timing[field.ms] = (timing[field.ms] ?? 0) + ms;
888
- if (blocks > 0) timing[field.n] = (timing[field.n] ?? 0) + blocks;
889
- }
890
- function bumpToolTotals(timing, name, ms) {
891
- if (!Object.hasOwn(timing.tools, name)) {
892
- if (Object.keys(timing.tools).length >= TOOL_TIMING_CAP) {
893
- let minKey = "";
894
- let minMs = Infinity;
895
- for (const k in timing.tools) if (timing.tools[k].ms < minMs) {
896
- minMs = timing.tools[k].ms;
897
- minKey = k;
1020
+ const shadowedSeqs = st.pendingShadowedSeqs;
1021
+ const shadowEventSeq = st.pendingShadowEventSeq;
1022
+ delete st.pendingShadowedSeqs;
1023
+ delete st.pendingShadowEventSeq;
1024
+ const op = replaceRangeOf(ev.surfaceOp);
1025
+ if (op !== null) {
1026
+ if (Array.isArray(shadowedSeqs) && shadowedSeqs.length > 0) {
1027
+ const removed = removeSurfaceSeqs(st, new Set(shadowedSeqs), ev.seq);
1028
+ st.sums[cat] += node.tokens;
1029
+ st.surface.push(node);
1030
+ if (shadowEventSeq !== void 0) {
1031
+ const removedSum = removed.reduce((sum, n) => sum + n.tokens, 0);
1032
+ const i = st.events.findIndex((e) => e.seq === shadowEventSeq);
1033
+ if (i >= 0) st.events[i] = {
1034
+ ...st.events[i],
1035
+ tokens: Math.max(0, removedSum - node.tokens)
1036
+ };
898
1037
  }
899
- const kept = {};
900
- for (const k in timing.tools) if (k !== minKey) kept[k] = timing.tools[k];
901
- timing.tools = kept;
1038
+ return node;
1039
+ }
1040
+ let si = -1;
1041
+ let ei = -1;
1042
+ for (let i = 0; i < st.surface.length; i++) {
1043
+ if (si < 0 && st.surface[i].seq === op.start) si = i;
1044
+ if (st.surface[i].seq === op.end) {
1045
+ ei = i;
1046
+ break;
1047
+ }
1048
+ }
1049
+ if (si >= 0 && ei >= si) {
1050
+ const removed = st.surface.splice(si, ei - si + 1, node);
1051
+ archiveRemoved(st, removed, ev.seq);
1052
+ for (const r of removed) st.sums[r.cat] -= r.tokens;
1053
+ st.sums[cat] += node.tokens;
1054
+ return node;
902
1055
  }
903
- timing.tools[name] = {
904
- calls: 1,
905
- ms
906
- };
907
- return;
908
1056
  }
909
- const cur = timing.tools[name];
910
- timing.tools[name] = {
911
- calls: cur.calls + 1,
912
- ms: cur.ms + ms
913
- };
1057
+ st.surface.push(node);
1058
+ st.sums[cat] += node.tokens;
1059
+ return node;
914
1060
  }
1061
+ const ASK_USER_TOOL = "ask_user_question";
915
1062
  const CLONE_ALL = [
916
1063
  "surface",
917
1064
  "sums",
@@ -1273,7 +1420,7 @@ function applyTimeline(state, event, bounds) {
1273
1420
  if (cacheRead !== null) record.cacheRead = cacheRead;
1274
1421
  if (output !== null) record.output = output;
1275
1422
  accumulateCost(s, event.time, {
1276
- input: input ?? 0,
1423
+ uncached: input ?? 0,
1277
1424
  cacheRead: cacheRead ?? 0,
1278
1425
  cacheWrite: cacheWrite ?? 0,
1279
1426
  output: output ?? 0
@@ -1367,145 +1514,6 @@ function applyTimeline(state, event, bounds) {
1367
1514
  }
1368
1515
  return state;
1369
1516
  }
1370
- function headFieldsOf(state) {
1371
- const surfaceTotal = state.sums.user + state.sums.inject + state.sums.skill + state.sums.assistant + state.sums.tool;
1372
- const result = {
1373
- ok: true,
1374
- ...state.model !== void 0 ? { model: state.model } : {},
1375
- ...state.provider !== void 0 ? { provider: state.provider } : {},
1376
- ...state.contextWindow !== void 0 ? { contextWindow: state.contextWindow } : {},
1377
- current: {
1378
- system: state.systemTokens,
1379
- tools: state.toolsTokens,
1380
- user: state.sums.user,
1381
- inject: state.sums.inject,
1382
- skill: state.sums.skill,
1383
- assistant: state.sums.assistant,
1384
- tool: state.sums.tool,
1385
- total: surfaceTotal + state.systemTokens + state.toolsTokens
1386
- },
1387
- images: state.surface.reduce((n, node) => n + (node.imgs ?? 0), 0),
1388
- toolCalls: state.surface.reduce((n, node) => node.cat === "tool" || node.cat === "skill" && node.tool !== void 0 ? n + 1 : n, 0),
1389
- humanInputs: state.humanInputs ?? 0,
1390
- answers: state.answers ?? 0,
1391
- ...state.lastUser !== void 0 ? { lastUser: state.lastUser } : {},
1392
- requests: [],
1393
- events: [],
1394
- nodes: [],
1395
- droppedNodes: 0,
1396
- archive: []
1397
- };
1398
- if (state.cost !== void 0) {
1399
- const cost = {};
1400
- for (const provider in state.cost) {
1401
- const models = {};
1402
- for (const model in state.cost[provider]) {
1403
- const periods = state.cost[provider][model];
1404
- const copy = {};
1405
- if (periods.peak !== void 0) copy.peak = { ...periods.peak };
1406
- if (periods.off !== void 0) copy.off = { ...periods.off };
1407
- models[model] = copy;
1408
- }
1409
- cost[provider] = models;
1410
- }
1411
- result.cost = cost;
1412
- }
1413
- if (state.timing !== void 0) {
1414
- const tools = {};
1415
- for (const k in state.timing.tools) tools[k] = { ...state.timing.tools[k] };
1416
- result.timing = {
1417
- ...state.timing,
1418
- tools
1419
- };
1420
- }
1421
- if (state.systems !== void 0 && state.systems.length > 0) result.systems = state.systems.map((n) => ({ ...n }));
1422
- return result;
1423
- }
1424
- function detailCollectionsOf(state, bounds) {
1425
- const result = {
1426
- requests: state.requests.map((r) => ({ ...r })),
1427
- events: state.events.map((e) => ({ ...e })),
1428
- nodes: [],
1429
- droppedNodes: 0,
1430
- archive: state.archived.map((n) => ({ ...n })),
1431
- fileOps: state.fileOps.map((o) => ({ ...o })),
1432
- ...state.fileOpsFloor !== void 0 ? { fileOpsFloor: state.fileOpsFloor } : {},
1433
- spans: state.spans.map((s) => ({ ...s }))
1434
- };
1435
- const overflowCount = Math.max(0, state.surface.length - bounds.maxNodes);
1436
- const overflow = state.surface.slice(0, overflowCount);
1437
- const tail = state.surface.slice(overflowCount);
1438
- const pinned = overflow.filter((n) => n.cat === "inject" || n.cat === "skill");
1439
- result.nodes = pinned.length > 0 ? [...pinned, ...tail] : tail;
1440
- result.droppedNodes = overflowCount - pinned.length;
1441
- if (result.droppedNodes > 0) {
1442
- let floor = 0;
1443
- for (const n of overflow) if (n.cat !== "inject" && n.cat !== "skill") floor = Math.max(floor, n.seq);
1444
- result.surfaceFloor = floor;
1445
- }
1446
- if (state.archiveFloor !== void 0) result.archiveFloor = state.archiveFloor;
1447
- const requests = result.requests;
1448
- const events = result.events;
1449
- let ri = 0;
1450
- for (const ev of events) {
1451
- while (ri < requests.length && requests[ri].seq <= ev.seq) ri++;
1452
- const next = requests.at(ri);
1453
- const prev = ri > 0 ? requests.at(ri - 1) : void 0;
1454
- if (next !== void 0 && typeof next.turn === "number" && typeof next.step === "number") {
1455
- ev.turn = next.turn;
1456
- ev.step = next.step;
1457
- }
1458
- if (prev !== void 0 && typeof prev.turn === "number" && typeof prev.step === "number") {
1459
- ev.fromTurn = prev.turn;
1460
- ev.fromStep = prev.step;
1461
- }
1462
- }
1463
- return result;
1464
- }
1465
- function buildTimelineHead(state) {
1466
- const result = headFieldsOf(state);
1467
- const turns = new Set();
1468
- for (const r of state.requests) turns.add(r.turn ?? 0);
1469
- let injects = 0;
1470
- let compactions = 0;
1471
- let prunes = 0;
1472
- const skills = new Set();
1473
- for (const e of state.events) {
1474
- if (e.kind === "inject") injects++;
1475
- else if (e.kind === "compaction") compactions++;
1476
- else if (e.kind === "prune") prunes++;
1477
- if (e.sub === "skill" && typeof e.name === "string" && e.name !== "") skills.add(e.name);
1478
- }
1479
- result.counts = {
1480
- turns: turns.size,
1481
- steps: state.requests.length,
1482
- injects,
1483
- compactions,
1484
- prunes,
1485
- skills: skills.size
1486
- };
1487
- const last = state.requests.at(-1);
1488
- if (last !== void 0) result.last = {
1489
- seq: last.seq,
1490
- total: last.total,
1491
- ...typeof last.prompt === "number" ? { prompt: last.prompt } : {}
1492
- };
1493
- result.detailRev = state.detailRev ?? 0;
1494
- return result;
1495
- }
1496
- function buildTimelineDetail(state, bounds) {
1497
- return {
1498
- rev: state.detailRev ?? 0,
1499
- head: buildTimelineHead(state),
1500
- ...detailCollectionsOf(state, bounds)
1501
- };
1502
- }
1503
- function buildTimelineView(state, bounds) {
1504
- return {
1505
- ...headFieldsOf(state),
1506
- ...detailCollectionsOf(state, bounds)
1507
- };
1508
- }
1509
1517
  //#endregion
1510
1518
  //#region src/host/config.ts
1511
1519
  const DEFAULT_BOUNDS = {
@@ -1865,7 +1873,7 @@ function billedBucketsOf(value) {
1865
1873
  const output = tokenCountOf(usage.outputTokens);
1866
1874
  if (input === null && cacheRead === null && cacheWrite === null && output === null) return null;
1867
1875
  return {
1868
- input: input ?? 0,
1876
+ uncached: input ?? 0,
1869
1877
  cacheRead: cacheRead ?? 0,
1870
1878
  cacheWrite: cacheWrite ?? 0,
1871
1879
  output: output ?? 0
@@ -1993,7 +2001,7 @@ function applyActivity(state, event) {
1993
2001
  let cost = prev?.cost;
1994
2002
  if (buckets !== null && state.model !== void 0) cost = addBilledUsage(cost, state.provider ?? "", state.model, initiated, buckets);
1995
2003
  return withDayEntry(state, key, {
1996
- tokens: (prev === void 0 ? 0 : prev.tokens) + (buckets === null ? 0 : buckets.input + buckets.cacheRead + buckets.cacheWrite + buckets.output),
2004
+ tokens: (prev === void 0 ? 0 : prev.tokens) + (buckets === null ? 0 : buckets.uncached + buckets.cacheRead + buckets.cacheWrite + buckets.output),
1997
2005
  requests: (prev === void 0 ? 0 : prev.requests) + 1,
1998
2006
  ...cost !== void 0 ? { cost } : {},
1999
2007
  ...prev?.skills !== void 0 ? { skills: prev.skills } : {}
@@ -2007,7 +2015,11 @@ function createContextActivityDefinition() {
2007
2015
  apply: (state, event) => applyActivity(state, event),
2008
2016
  wire: {
2009
2017
  viewSchema: contextActivitySchema,
2010
- view: (state) => ({ days: Object.fromEntries(Object.entries(state.days).map(([k, v]) => [k, { ...v }])) })
2018
+ view: (state) => ({ days: Object.fromEntries(Object.entries(state.days).map(([k, v]) => [k, {
2019
+ ...v,
2020
+ ...v.cost !== void 0 ? { cost: copyCostUsage(v.cost) } : {},
2021
+ ...v.skills !== void 0 ? { skills: Object.fromEntries(Object.entries(v.skills).map(([n, t]) => [n, { ...t }])) } : {}
2022
+ }])) })
2011
2023
  },
2012
2024
  stateVersion: 4
2013
2025
  };
@@ -2172,19 +2184,36 @@ function createToolAttribution(ctx) {
2172
2184
  return { ownerOf: (name) => mcpSourceOf(name) ?? live.get(name) ?? pinnedSourceOf(name) ?? (boot.has(name) ? "<unknown-plugin>" : void 0) };
2173
2185
  }
2174
2186
  //#endregion
2187
+ //#region src/host/record.ts
2188
+ function asRecord(value) {
2189
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
2190
+ }
2191
+ //#endregion
2175
2192
  //#region src/host/connection.ts
2176
2193
  function fetchRouteRegistrar(connection) {
2177
2194
  return typeof connection?.fetch?.register === "function" ? connection.fetch.register.bind(connection.fetch) : void 0;
2178
2195
  }
2196
+ function registerPostRoute(c, connection, path, fetch, label) {
2197
+ const register = fetchRouteRegistrar(connection);
2198
+ if (register === void 0) return false;
2199
+ try {
2200
+ c.effect(() => register({
2201
+ path,
2202
+ methods: ["POST"],
2203
+ requestBody: "buffered",
2204
+ fetch
2205
+ }), label);
2206
+ } catch {
2207
+ return false;
2208
+ }
2209
+ return true;
2210
+ }
2179
2211
  //#endregion
2180
2212
  //#region src/host/balance.ts
2181
2213
  const BALANCE_ROUTE = "/api/dsh-context/balance";
2182
2214
  const DEEPSEEK_SETTINGS_NS = "llm-deepseek";
2183
2215
  const PUBLIC_BASE_URL = "https://api.deepseek.com";
2184
2216
  const FETCH_TIMEOUT_MS = 1e4;
2185
- function asRecord$2(value) {
2186
- return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
2187
- }
2188
2217
  function amountOf(value) {
2189
2218
  const n = typeof value === "string" ? Number(value) : value;
2190
2219
  return typeof n === "number" && Number.isFinite(n) && n >= 0 ? n : null;
@@ -2197,9 +2226,9 @@ function deepseekSectionOf(ctx) {
2197
2226
  if (!Array.isArray(rows)) return null;
2198
2227
  let shaped = null;
2199
2228
  for (const row of rows) try {
2200
- const record = asRecord$2(row);
2229
+ const record = asRecord(row);
2201
2230
  if (record === null) continue;
2202
- const value = asRecord$2(record.value);
2231
+ const value = asRecord(record.value);
2203
2232
  if (value === null || typeof value.apiKeyEnv !== "string" || value.apiKeyEnv === "") continue;
2204
2233
  if (record.ns === DEEPSEEK_SETTINGS_NS || record.ns === "llm-deepseek-api-key") return value;
2205
2234
  shaped ??= value;
@@ -2232,12 +2261,12 @@ async function resolveFacts(ctx) {
2232
2261
  };
2233
2262
  }
2234
2263
  function balanceOfPayload(value) {
2235
- const data = asRecord$2(value);
2264
+ const data = asRecord(value);
2236
2265
  if (data === null) return null;
2237
2266
  const infos = Array.isArray(data.balance_infos) ? data.balance_infos : [];
2238
2267
  const balances = [];
2239
2268
  for (const info of infos) try {
2240
- const entry = asRecord$2(info);
2269
+ const entry = asRecord(info);
2241
2270
  if (entry === null) continue;
2242
2271
  const currency = entry.currency;
2243
2272
  const granted = amountOf(entry.granted_balance);
@@ -2285,8 +2314,6 @@ async function readBalance(facts) {
2285
2314
  }
2286
2315
  function watchBalanceChannel(ctx) {
2287
2316
  ctx.inject(["connection"], (c) => {
2288
- const register = fetchRouteRegistrar(c.get("connection"));
2289
- if (register === void 0) return;
2290
2317
  const handler = async () => {
2291
2318
  const facts = await resolveFacts(ctx);
2292
2319
  const value = facts !== null ? await readBalance(facts) : null;
@@ -2295,16 +2322,7 @@ function watchBalanceChannel(ctx) {
2295
2322
  value
2296
2323
  }, { headers: { "cache-control": "no-store" } });
2297
2324
  };
2298
- try {
2299
- c.effect(() => register({
2300
- path: BALANCE_ROUTE,
2301
- methods: ["POST"],
2302
- requestBody: "buffered",
2303
- fetch: handler
2304
- }), "dsh-context: balance route");
2305
- } catch {
2306
- return;
2307
- }
2325
+ registerPostRoute(c, c.get("connection"), BALANCE_ROUTE, handler, "dsh-context: balance route");
2308
2326
  });
2309
2327
  }
2310
2328
  //#endregion
@@ -2340,11 +2358,8 @@ function makeColdReadGate(reading = v8Reading) {
2340
2358
  const BACKFILL_ROUTE = "/api/dsh-context/backfill";
2341
2359
  const YIELD_MS = 100;
2342
2360
  const UNSUPPORTED_FORMAT = "SessionFormatUnsupported";
2343
- function asRecord$1(value) {
2344
- return value !== null && typeof value === "object" ? value : null;
2345
- }
2346
2361
  function headerOf(record) {
2347
- const header = asRecord$1(asRecord$1(record)?.header);
2362
+ const header = asRecord(asRecord(record)?.header);
2348
2363
  if (header === null) return null;
2349
2364
  if (typeof header.id !== "string" || header.id === "") return null;
2350
2365
  return header;
@@ -2352,7 +2367,7 @@ function headerOf(record) {
2352
2367
  const PROBE_KEYS = ["contextActivity", "contextTimeline"];
2353
2368
  function servesRows(cache, header) {
2354
2369
  try {
2355
- const values = asRecord$1(asRecord$1(cache.cachedSnapshot(header, PROBE_KEYS))?.values);
2370
+ const values = asRecord(asRecord(cache.cachedSnapshot(header, PROBE_KEYS))?.values);
2356
2371
  return values !== null && values.contextActivity !== void 0 && values.contextTimeline !== void 0;
2357
2372
  } catch {
2358
2373
  return false;
@@ -2362,7 +2377,7 @@ async function readColdLog(persistence, id, signal) {
2362
2377
  const handle = await persistence.open(id, "read", { signal });
2363
2378
  let events;
2364
2379
  try {
2365
- const result = asRecord$1(await handle.read(0, void 0, { signal }))?.events;
2380
+ const result = asRecord(await handle.read(0, void 0, { signal }))?.events;
2366
2381
  events = Array.isArray(result) ? result : [];
2367
2382
  } catch (error) {
2368
2383
  try {
@@ -2387,7 +2402,7 @@ function isLive(sessions, id) {
2387
2402
  }
2388
2403
  function isUnsupportedFormat(error) {
2389
2404
  try {
2390
- return String(asRecord$1(error)?.name).startsWith(UNSUPPORTED_FORMAT);
2405
+ return String(asRecord(error)?.name).startsWith(UNSUPPORTED_FORMAT);
2391
2406
  } catch {
2392
2407
  return false;
2393
2408
  }
@@ -2409,22 +2424,12 @@ function watchActivityBackfill(ctx, coldReads = makeColdReadGate()) {
2409
2424
  launch();
2410
2425
  };
2411
2426
  const routeFiber = ctx.inject(["connection"], (c) => {
2412
- const register = fetchRouteRegistrar(c.get("connection"));
2413
- if (register === void 0) return;
2414
- try {
2415
- c.effect(() => register({
2416
- path: BACKFILL_ROUTE,
2417
- methods: ["POST"],
2418
- requestBody: "buffered",
2419
- fetch: () => {
2420
- requested = true;
2421
- tryLaunch();
2422
- return Response.json({ ok: true }, { headers: { "cache-control": "no-store" } });
2423
- }
2424
- }), "dsh-context: backfill route");
2425
- } catch {
2426
- return;
2427
- }
2427
+ const handler = () => {
2428
+ requested = true;
2429
+ tryLaunch();
2430
+ return Response.json({ ok: true }, { headers: { "cache-control": "no-store" } });
2431
+ };
2432
+ registerPostRoute(c, c.get("connection"), BACKFILL_ROUTE, handler, "dsh-context: backfill route");
2428
2433
  });
2429
2434
  const runnerFiber = ctx.inject([
2430
2435
  "sessionQuery",
@@ -2433,13 +2438,13 @@ function watchActivityBackfill(ctx, coldReads = makeColdReadGate()) {
2433
2438
  "sessions"
2434
2439
  ], (raw) => {
2435
2440
  const injected = raw;
2436
- const query = asRecord$1(injected.sessionQuery);
2437
- const cache = asRecord$1(injected.sessionProjectionCache);
2438
- const persistence = asRecord$1(injected.sessionPersistence);
2441
+ const query = asRecord(injected.sessionQuery);
2442
+ const cache = asRecord(injected.sessionProjectionCache);
2443
+ const persistence = asRecord(injected.sessionPersistence);
2439
2444
  if (query === null || typeof query.listSessions !== "function") return;
2440
2445
  if (cache === null || typeof cache.cachedSnapshot !== "function" || typeof cache.coldSnapshot !== "function") return;
2441
2446
  if (persistence === null || typeof persistence.open !== "function") return;
2442
- const sessions = asRecord$1(injected.sessions);
2447
+ const sessions = asRecord(injected.sessions);
2443
2448
  const abort = new AbortController();
2444
2449
  const run = async () => {
2445
2450
  const listed = await query.listSessions(abort.signal);
@@ -2489,9 +2494,6 @@ function watchActivityBackfill(ctx, coldReads = makeColdReadGate()) {
2489
2494
  for (const fiber of [routeFiber, runnerFiber]) fiber?.dispose?.();
2490
2495
  };
2491
2496
  }
2492
- //#endregion
2493
- //#region src/host/detail.ts
2494
- const DETAIL_ROUTE = "/api/dsh-context/detail";
2495
2497
  function reply(value) {
2496
2498
  return Response.json(value, { headers: { "cache-control": "no-store" } });
2497
2499
  }
@@ -2508,9 +2510,8 @@ function watchDetailChannel(ctx, bounds, coldReads = makeColdReadGate()) {
2508
2510
  const gate = { live: false };
2509
2511
  ctx.inject(["connection", "sessions"], (c) => {
2510
2512
  const sessions = c.get("sessions");
2511
- const register = fetchRouteRegistrar(c.get("connection"));
2512
2513
  const getSession = typeof sessions?.get === "function" ? sessions.get.bind(sessions) : void 0;
2513
- if (register === void 0 || getSession === void 0) return;
2514
+ if (getSession === void 0) return;
2514
2515
  const projections = ctx.sessionProjections;
2515
2516
  const handler = async (request) => {
2516
2517
  let sessionId;
@@ -2560,16 +2561,7 @@ function watchDetailChannel(ctx, bounds, coldReads = makeColdReadGate()) {
2560
2561
  return failure("gateway/internal", err instanceof Error ? err.message : String(err));
2561
2562
  }
2562
2563
  };
2563
- try {
2564
- c.effect(() => register({
2565
- path: DETAIL_ROUTE,
2566
- methods: ["POST"],
2567
- requestBody: "buffered",
2568
- fetch: handler
2569
- }), "dsh-context: detail route");
2570
- } catch {
2571
- return;
2572
- }
2564
+ if (!registerPostRoute(c, c.get("connection"), "/api/dsh-context/detail", handler, "dsh-context: detail route")) return;
2573
2565
  gate.live = true;
2574
2566
  return () => {
2575
2567
  gate.live = false;
@@ -2787,9 +2779,6 @@ function watchStepIdentity(ctx) {
2787
2779
  //#region src/host/skills.ts
2788
2780
  const SKILLS_ROUTE = "/api/dsh-context/skills";
2789
2781
  const MAX_SKILLS = 500;
2790
- function asRecord(value) {
2791
- return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
2792
- }
2793
2782
  function stringOf(value) {
2794
2783
  return typeof value === "string" && value !== "" ? value : void 0;
2795
2784
  }
@@ -2874,8 +2863,6 @@ async function resolveRegistry(ctx, sessionId, agentPreset) {
2874
2863
  }
2875
2864
  function watchSkillCatalog(ctx) {
2876
2865
  ctx.inject(["connection"], (c) => {
2877
- const register = fetchRouteRegistrar(c.get("connection"));
2878
- if (register === void 0) return;
2879
2866
  const handler = async (request) => {
2880
2867
  let value = null;
2881
2868
  let lease;
@@ -2908,16 +2895,7 @@ function watchSkillCatalog(ctx) {
2908
2895
  value
2909
2896
  }, { headers: { "cache-control": "no-store" } });
2910
2897
  };
2911
- try {
2912
- c.effect(() => register({
2913
- path: SKILLS_ROUTE,
2914
- methods: ["POST"],
2915
- requestBody: "buffered",
2916
- fetch: handler
2917
- }), "dsh-context: skills route");
2918
- } catch {
2919
- return;
2920
- }
2898
+ registerPostRoute(c, c.get("connection"), SKILLS_ROUTE, handler, "dsh-context: skills route");
2921
2899
  });
2922
2900
  }
2923
2901
  //#endregion