@khorsheed/dsh-context-lens 0.0.0-stage → 0.1.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.
Files changed (94) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/README.en.md +53 -0
  3. package/README.i18n.yaml +6 -0
  4. package/README.md +52 -2
  5. package/cordis.patch.yml +13 -0
  6. package/lib/client.js +26953 -0
  7. package/lib/index.js +3943 -0
  8. package/lib/typert.host.d.ts +3 -0
  9. package/lib/typert.host.js +1569 -0
  10. package/lib/typert.remote-client.d.ts +40 -0
  11. package/lib/typert.remote-client.js +1202 -0
  12. package/lib/types/changes.d.ts +72 -0
  13. package/lib/types/changes.js +328 -0
  14. package/lib/types/client/AgentTabs.d.ts +34 -0
  15. package/lib/types/client/AgentTabs.js +89 -0
  16. package/lib/types/client/ChangeList.d.ts +28 -0
  17. package/lib/types/client/ChangeList.js +109 -0
  18. package/lib/types/client/ConfigFacts.d.ts +21 -0
  19. package/lib/types/client/ConfigFacts.js +30 -0
  20. package/lib/types/client/HeaderUtility.d.ts +12 -0
  21. package/lib/types/client/HeaderUtility.js +8 -0
  22. package/lib/types/client/InspectorPanel.d.ts +5 -0
  23. package/lib/types/client/InspectorPanel.js +486 -0
  24. package/lib/types/client/RawView.d.ts +18 -0
  25. package/lib/types/client/RawView.js +8 -0
  26. package/lib/types/client/RequestFacts.d.ts +19 -0
  27. package/lib/types/client/RequestFacts.js +39 -0
  28. package/lib/types/client/RoomDecisionsView.d.ts +24 -0
  29. package/lib/types/client/RoomDecisionsView.js +52 -0
  30. package/lib/types/client/SchemaFields.d.ts +13 -0
  31. package/lib/types/client/SchemaFields.js +96 -0
  32. package/lib/types/client/SegCtl.d.ts +32 -0
  33. package/lib/types/client/SegCtl.js +12 -0
  34. package/lib/types/client/SegmentBlocks.d.ts +28 -0
  35. package/lib/types/client/SegmentBlocks.js +225 -0
  36. package/lib/types/client/TimingView.d.ts +17 -0
  37. package/lib/types/client/TimingView.js +98 -0
  38. package/lib/types/client/TokenView.d.ts +21 -0
  39. package/lib/types/client/TokenView.js +22 -0
  40. package/lib/types/client/ToolCatalog.d.ts +9 -0
  41. package/lib/types/client/ToolCatalog.js +51 -0
  42. package/lib/types/client/Trend.d.ts +25 -0
  43. package/lib/types/client/Trend.js +100 -0
  44. package/lib/types/client/TurnAction.d.ts +14 -0
  45. package/lib/types/client/TurnAction.js +31 -0
  46. package/lib/types/client/TurnMenu.d.ts +36 -0
  47. package/lib/types/client/TurnMenu.js +50 -0
  48. package/lib/types/client/aggregate.d.ts +141 -0
  49. package/lib/types/client/aggregate.js +213 -0
  50. package/lib/types/client/contract.d.ts +58 -0
  51. package/lib/types/client/contract.js +1 -0
  52. package/lib/types/client/definition.d.ts +47 -0
  53. package/lib/types/client/definition.js +25 -0
  54. package/lib/types/client/icons-local.d.ts +19 -0
  55. package/lib/types/client/icons-local.js +3 -0
  56. package/lib/types/client/icons.d.ts +18 -0
  57. package/lib/types/client/icons.js +2 -0
  58. package/lib/types/client/index.d.ts +42 -0
  59. package/lib/types/client/index.js +139 -0
  60. package/lib/types/client/locales.d.ts +365 -0
  61. package/lib/types/client/locales.js +705 -0
  62. package/lib/types/client/preset-visibility.d.ts +99 -0
  63. package/lib/types/client/preset-visibility.js +108 -0
  64. package/lib/types/client/scroll-sync.d.ts +54 -0
  65. package/lib/types/client/scroll-sync.js +118 -0
  66. package/lib/types/client/timing.d.ts +29 -0
  67. package/lib/types/client/timing.js +117 -0
  68. package/lib/types/client/wire.d.ts +13 -0
  69. package/lib/types/client/wire.js +11 -0
  70. package/lib/types/index.d.ts +32 -0
  71. package/lib/types/index.js +25 -0
  72. package/lib/types/log-lane.d.ts +198 -0
  73. package/lib/types/log-lane.js +841 -0
  74. package/lib/types/messages.d.ts +96 -0
  75. package/lib/types/messages.js +254 -0
  76. package/lib/types/prefix-diff.d.ts +4 -0
  77. package/lib/types/prefix-diff.js +20 -0
  78. package/lib/types/remote.d.ts +45 -0
  79. package/lib/types/remote.js +99 -0
  80. package/lib/types/room.d.ts +66 -0
  81. package/lib/types/room.js +101 -0
  82. package/lib/types/segmenter.d.ts +100 -0
  83. package/lib/types/segmenter.js +184 -0
  84. package/lib/types/service.d.ts +145 -0
  85. package/lib/types/service.js +493 -0
  86. package/lib/types/store.d.ts +424 -0
  87. package/lib/types/store.js +1920 -0
  88. package/lib/types/tool-summary.d.ts +6 -0
  89. package/lib/types/tool-summary.js +7 -0
  90. package/lib/types/types.d.ts +703 -0
  91. package/lib/types/types.js +18 -0
  92. package/locale/en.json +6 -0
  93. package/locale/zh.json +6 -0
  94. package/package.json +133 -3
@@ -0,0 +1,99 @@
1
+ /**
2
+ * The preset-composition visibility of the context-lens surfaces — the
3
+ * registration-level gate for the right-Sidebar tab type and its two entry
4
+ * points (the turn action row, the session-header utility).
5
+ *
6
+ * The criterion answers "can THIS session reach the context-lens
7
+ * capture" over the two grant paths this package's own row can take
8
+ * (the canvas double-check template, `docs/plugin-visibility.md` 判据轴):
9
+ *
10
+ * 1. **Profile-root mount** (this package's own cordis.patch.yml — the only
11
+ * shape the bundle patch ships): the inventory's `entries` carry an
12
+ * ENABLED `@khorsheed/dsh-context-lens` row → visible for every
13
+ * session. This is the common case; capture is deployment-wide.
14
+ * 2. **Preset mount** (a composition that instead names the row from a
15
+ * preset's agent.cordis.yml): visible exactly when the CURRENT session's
16
+ * preset composition names that row.
17
+ *
18
+ * Every unreadable path fails OPEN (visible): no pluginInventory namespace,
19
+ * a pending/failed RPC, a missing or `broken` preset group, a session with
20
+ * no preset, and the no-session home state (a frame-level surface has no
21
+ * preset input until a session exists — showing there is the neutral state,
22
+ * not a leak).
23
+ *
24
+ * Hidden means NOT registered: the guide enumerates the tab-type registry,
25
+ * opened tabs are stored per session (an ungranted session's layout never
26
+ * held one), and a kind with no registrant renders the host's designed
27
+ * `tab.unavailable` fallback ("a real state, not a defect").
28
+ * @module @khorsheed/dsh-context-lens/client/preset-visibility
29
+ */
30
+ import type { Context } from '@deepseek-ai/cordis';
31
+ import type { SessionId } from '@deepseek-ai/dsh-session/types';
32
+ import type { RemoteResult } from '@deepseek-ai/dsh-typert-protocol';
33
+ /** The criterion's row: this package's own composition entry (root-mounted by its bundle patch). */
34
+ export declare const REQUEST_INSPECTOR_ROW_MODULE = "@khorsheed/dsh-context-lens";
35
+ /**
36
+ * Minimal structural mirror of the official pluginInventory snapshot — the
37
+ * entries slice (root grants) plus the agentPresets slice (preset grants),
38
+ * duck-typed and host-version tolerant.
39
+ */
40
+ export interface InspectorPluginInventorySnapshot {
41
+ readonly entries?: readonly {
42
+ readonly moduleName: string;
43
+ readonly enabled?: boolean;
44
+ }[];
45
+ readonly agentPresets?: readonly {
46
+ readonly id: string;
47
+ readonly broken?: string;
48
+ readonly rows: readonly {
49
+ readonly moduleName: string;
50
+ readonly enabled?: boolean;
51
+ }[];
52
+ }[];
53
+ }
54
+ /**
55
+ * The surfaces' visibility controller: one inventory fetch per page
56
+ * (composition data changes only with preset files, which a reload re-reads
57
+ * anyway), re-evaluated by the toggles on every session switch.
58
+ */
59
+ export declare class InspectorTabVisibility {
60
+ private readonly ctx;
61
+ /** The inventory answer; null while pending/failed and on namespace-less hosts. */
62
+ private composition;
63
+ private readonly listeners;
64
+ /**
65
+ * @param ctx - client root context.
66
+ * @param pluginInventory - the OFFICIAL namespace, probed through ctx.get
67
+ * (never injected — declaring it would pend the whole client on a host
68
+ * without it, and the criterion must fail open there).
69
+ */
70
+ constructor(ctx: Context, pluginInventory: {
71
+ list: () => Promise<RemoteResult<InspectorPluginInventorySnapshot>>;
72
+ } | undefined);
73
+ show(sessionId: SessionId | undefined): boolean;
74
+ /** Subscribe to criterion-input changes (session switches, the inventory answer). */
75
+ subscribe(listener: () => void): () => void;
76
+ private notify;
77
+ }
78
+ /**
79
+ * One registration's toggle (the room template): registered exactly while the
80
+ * criterion passes for the current session, disposed otherwise. Works for the
81
+ * sidebarRightTabs registry (a direct inject) and for a `ctx.slots.inject`
82
+ * registration alike — both return their disposer synchronously.
83
+ */
84
+ export declare class RegistrationToggle {
85
+ private readonly register;
86
+ private readonly show;
87
+ private dispose;
88
+ private ready;
89
+ /**
90
+ * @param register - mounts the entry and returns its disposer.
91
+ * @param show - the current decision (re-read on every sync).
92
+ */
93
+ constructor(register: () => () => void, show: () => boolean);
94
+ /** The registry is reachable — decisions may register now. */
95
+ setReady(ready: boolean): void;
96
+ /** Re-evaluate: register while shown and ready, dispose otherwise. */
97
+ sync(): void;
98
+ }
99
+ //# sourceMappingURL=preset-visibility.d.ts.map
@@ -0,0 +1,108 @@
1
+ /** The criterion's row: this package's own composition entry (root-mounted by its bundle patch). */
2
+ export const REQUEST_INSPECTOR_ROW_MODULE = '@khorsheed/dsh-context-lens';
3
+ /** The current session's agent preset, the dual read per host line. */
4
+ function presetOf(row) {
5
+ const value = row
6
+ ?.projectionValues?.agentPreset
7
+ ?? row?.agentPreset;
8
+ return typeof value === 'string' ? value : undefined;
9
+ }
10
+ /**
11
+ * The surfaces' visibility controller: one inventory fetch per page
12
+ * (composition data changes only with preset files, which a reload re-reads
13
+ * anyway), re-evaluated by the toggles on every session switch.
14
+ */
15
+ export class InspectorTabVisibility {
16
+ ctx;
17
+ /** The inventory answer; null while pending/failed and on namespace-less hosts. */
18
+ composition = null;
19
+ listeners = new Set();
20
+ /**
21
+ * @param ctx - client root context.
22
+ * @param pluginInventory - the OFFICIAL namespace, probed through ctx.get
23
+ * (never injected — declaring it would pend the whole client on a host
24
+ * without it, and the criterion must fail open there).
25
+ */
26
+ constructor(ctx, pluginInventory) {
27
+ this.ctx = ctx;
28
+ if (pluginInventory !== undefined) {
29
+ void pluginInventory.list().then(result => {
30
+ if (!result.ok)
31
+ return;
32
+ this.composition = result.value ?? null;
33
+ this.notify();
34
+ }).catch(() => { });
35
+ }
36
+ // Session switches re-decide the registrations (the guide enumerates
37
+ // registrations, not components).
38
+ ctx.effect(() => ctx.sessions.list.subscribe(() => { this.notify(); }), 'context-lens: visibility session follower');
39
+ }
40
+ show(sessionId) {
41
+ if (this.composition === null)
42
+ return true;
43
+ // Path 1: a root-mounted capture row serves every session — a
44
+ // deployment-level constant, checked before any per-session question.
45
+ if (this.composition.entries?.some(row => row.moduleName === REQUEST_INSPECTOR_ROW_MODULE && row.enabled === true) === true)
46
+ return true;
47
+ if (sessionId === undefined)
48
+ return true;
49
+ const preset = presetOf(this.ctx.sessions.list.getSnapshot().byId[sessionId]);
50
+ if (preset === undefined)
51
+ return true;
52
+ const group = this.composition.agentPresets?.find(candidate => candidate.id === preset);
53
+ if (group === undefined || group.broken !== undefined)
54
+ return true;
55
+ // Path 2: the preset-mounted row grants this session.
56
+ return group.rows.some(row => row.moduleName === REQUEST_INSPECTOR_ROW_MODULE && row.enabled !== false);
57
+ }
58
+ /** Subscribe to criterion-input changes (session switches, the inventory answer). */
59
+ subscribe(listener) {
60
+ this.listeners.add(listener);
61
+ return () => { this.listeners.delete(listener); };
62
+ }
63
+ notify() {
64
+ for (const listener of this.listeners)
65
+ listener();
66
+ }
67
+ }
68
+ /**
69
+ * One registration's toggle (the room template): registered exactly while the
70
+ * criterion passes for the current session, disposed otherwise. Works for the
71
+ * sidebarRightTabs registry (a direct inject) and for a `ctx.slots.inject`
72
+ * registration alike — both return their disposer synchronously.
73
+ */
74
+ export class RegistrationToggle {
75
+ register;
76
+ show;
77
+ dispose;
78
+ ready = false;
79
+ /**
80
+ * @param register - mounts the entry and returns its disposer.
81
+ * @param show - the current decision (re-read on every sync).
82
+ */
83
+ constructor(register, show) {
84
+ this.register = register;
85
+ this.show = show;
86
+ }
87
+ /** The registry is reachable — decisions may register now. */
88
+ setReady(ready) {
89
+ this.ready = ready;
90
+ if (!ready && this.dispose !== undefined) {
91
+ this.dispose();
92
+ this.dispose = undefined;
93
+ }
94
+ this.sync();
95
+ }
96
+ /** Re-evaluate: register while shown and ready, dispose otherwise. */
97
+ sync() {
98
+ if (!this.ready)
99
+ return;
100
+ if (this.show()) {
101
+ this.dispose ??= this.register();
102
+ }
103
+ else if (this.dispose !== undefined) {
104
+ this.dispose();
105
+ this.dispose = undefined;
106
+ }
107
+ }
108
+ }
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Conversation scroll-sync over the chat view's DOM anchors — the sanctioned
3
+ * last-resort pattern (AGENTS.md: a DOM anchor must carry a fallback). There
4
+ * is no public slot/service for conversation scrolling (the capability is
5
+ * internal to ui-chat's ChatView `use-chat-viewport.ts`); the seam is
6
+ * registered in docs/upstream-seam-registry.md (S20) with this anchor as the
7
+ * recorded workaround.
8
+ *
9
+ * The anchors are the host's own: `[data-conversation-scroll]` marks the
10
+ * conversation scrollport (ui-conversation ConversationContent), and turn
11
+ * rows carry `[data-chat-turn="<hostTurn>"]` (ui-chat ChatNodeSeat /
12
+ * ChatGroupSeat — the same selector `scrollToTurnAtOrAfter` uses). The panel's
13
+ * real turns key on the host turn number (`TurnListItem.hostTurn`), so the
14
+ * anchor lookup needs no mapping; purpose pseudo-turns have no hostTurn and
15
+ * never scroll.
16
+ *
17
+ * Every failure path is a silent no-op: no conversation mounted, the turn
18
+ * paged out of the virtualized list, a missing scroll primitive — nothing
19
+ * here may throw.
20
+ *
21
+ * @module @khorsheed/dsh-context-lens/client/scroll-sync
22
+ */
23
+ /** The conversation scrollport selector (ui-conversation ConversationContent). */
24
+ export declare const CONVERSATION_SCROLLPORT = "[data-conversation-scroll]";
25
+ /**
26
+ * Eligible turn rows: the host's own filter from `scrollToTurnAtOrAfter`
27
+ * (skips hidden rows and rows nested inside a hidden subtree).
28
+ */
29
+ export declare const TURN_ROW_ELIGIBLE = "[data-chat-turn]:not([hidden]):not([hidden] *)";
30
+ /** The motion preference; a host without matchMedia (jsdom) defaults to smooth. */
31
+ export declare function preferredScrollBehavior(): 'smooth' | 'auto';
32
+ /** Whether the chat has rendered this host turn's anchor row (a jump can land on it). */
33
+ export declare function hasTurnAnchor(hostTurn: number, root?: ParentNode): boolean;
34
+ /**
35
+ * Scroll the conversation so one host turn's anchor row leads the viewport:
36
+ * the exact `[data-chat-turn="<turn>"]` row first; a paged-out turn lands on
37
+ * the nearest mounted turn ≥ it in DOM order (the host's
38
+ * `scrollToTurnAtOrAfter` semantics); no eligible row is a silent no-op.
39
+ * @param hostTurn - the host's turn number (`TurnListItem.hostTurn`).
40
+ * @param root - the DOM scope (document in production).
41
+ * @param behavior - scroll motion (respects prefers-reduced-motion by default).
42
+ * @returns whether anything scrolled.
43
+ */
44
+ export declare function scrollConversationToTurn(hostTurn: number, root?: ParentNode, behavior?: 'smooth' | 'auto'): boolean;
45
+ /**
46
+ * Scroll the conversation to its floor (the 最新 button): the scrollport's
47
+ * own `scrollTo` when present, else the largest mounted turn row as the
48
+ * anchor fallback. No conversation mounted is a silent no-op.
49
+ * @param root - the DOM scope (document in production).
50
+ * @param behavior - scroll motion (respects prefers-reduced-motion by default).
51
+ * @returns whether anything scrolled.
52
+ */
53
+ export declare function scrollConversationToBottom(root?: ParentNode, behavior?: 'smooth' | 'auto'): boolean;
54
+ //# sourceMappingURL=scroll-sync.d.ts.map
@@ -0,0 +1,118 @@
1
+ /**
2
+ * Conversation scroll-sync over the chat view's DOM anchors — the sanctioned
3
+ * last-resort pattern (AGENTS.md: a DOM anchor must carry a fallback). There
4
+ * is no public slot/service for conversation scrolling (the capability is
5
+ * internal to ui-chat's ChatView `use-chat-viewport.ts`); the seam is
6
+ * registered in docs/upstream-seam-registry.md (S20) with this anchor as the
7
+ * recorded workaround.
8
+ *
9
+ * The anchors are the host's own: `[data-conversation-scroll]` marks the
10
+ * conversation scrollport (ui-conversation ConversationContent), and turn
11
+ * rows carry `[data-chat-turn="<hostTurn>"]` (ui-chat ChatNodeSeat /
12
+ * ChatGroupSeat — the same selector `scrollToTurnAtOrAfter` uses). The panel's
13
+ * real turns key on the host turn number (`TurnListItem.hostTurn`), so the
14
+ * anchor lookup needs no mapping; purpose pseudo-turns have no hostTurn and
15
+ * never scroll.
16
+ *
17
+ * Every failure path is a silent no-op: no conversation mounted, the turn
18
+ * paged out of the virtualized list, a missing scroll primitive — nothing
19
+ * here may throw.
20
+ *
21
+ * @module @khorsheed/dsh-context-lens/client/scroll-sync
22
+ */
23
+ /** The conversation scrollport selector (ui-conversation ConversationContent). */
24
+ export const CONVERSATION_SCROLLPORT = '[data-conversation-scroll]';
25
+ /**
26
+ * Eligible turn rows: the host's own filter from `scrollToTurnAtOrAfter`
27
+ * (skips hidden rows and rows nested inside a hidden subtree).
28
+ */
29
+ export const TURN_ROW_ELIGIBLE = '[data-chat-turn]:not([hidden]):not([hidden] *)';
30
+ /** The motion preference; a host without matchMedia (jsdom) defaults to smooth. */
31
+ export function preferredScrollBehavior() {
32
+ if (typeof window === 'undefined' || typeof window.matchMedia !== 'function')
33
+ return 'smooth';
34
+ return window.matchMedia('(prefers-reduced-motion: reduce)').matches ? 'auto' : 'smooth';
35
+ }
36
+ /** The on-screen conversation's scrollport, or null when no conversation is mounted. */
37
+ function scrollport(root) {
38
+ try {
39
+ return root.querySelector(CONVERSATION_SCROLLPORT);
40
+ }
41
+ catch {
42
+ return null;
43
+ }
44
+ }
45
+ /** Whether the chat has rendered this host turn's anchor row (a jump can land on it). */
46
+ export function hasTurnAnchor(hostTurn, root = document) {
47
+ try {
48
+ return (scrollport(root) ?? root).querySelector(`[data-chat-turn="${hostTurn}"]:not([hidden]):not([hidden] *)`) !== null;
49
+ }
50
+ catch {
51
+ return false;
52
+ }
53
+ }
54
+ /**
55
+ * Scroll the conversation so one host turn's anchor row leads the viewport:
56
+ * the exact `[data-chat-turn="<turn>"]` row first; a paged-out turn lands on
57
+ * the nearest mounted turn ≥ it in DOM order (the host's
58
+ * `scrollToTurnAtOrAfter` semantics); no eligible row is a silent no-op.
59
+ * @param hostTurn - the host's turn number (`TurnListItem.hostTurn`).
60
+ * @param root - the DOM scope (document in production).
61
+ * @param behavior - scroll motion (respects prefers-reduced-motion by default).
62
+ * @returns whether anything scrolled.
63
+ */
64
+ export function scrollConversationToTurn(hostTurn, root = document, behavior = preferredScrollBehavior()) {
65
+ try {
66
+ const scope = scrollport(root) ?? root;
67
+ const exact = scope.querySelector(`[data-chat-turn="${hostTurn}"]:not([hidden]):not([hidden] *)`);
68
+ if (exact !== null) {
69
+ exact.scrollIntoView({ behavior, block: 'start' });
70
+ return true;
71
+ }
72
+ for (const row of scope.querySelectorAll(TURN_ROW_ELIGIBLE)) {
73
+ const value = Number(row.dataset.chatTurn);
74
+ if (Number.isSafeInteger(value) && value >= hostTurn) {
75
+ row.scrollIntoView({ behavior, block: 'start' });
76
+ return true;
77
+ }
78
+ }
79
+ return false;
80
+ }
81
+ catch {
82
+ return false;
83
+ }
84
+ }
85
+ /**
86
+ * Scroll the conversation to its floor (the 最新 button): the scrollport's
87
+ * own `scrollTo` when present, else the largest mounted turn row as the
88
+ * anchor fallback. No conversation mounted is a silent no-op.
89
+ * @param root - the DOM scope (document in production).
90
+ * @param behavior - scroll motion (respects prefers-reduced-motion by default).
91
+ * @returns whether anything scrolled.
92
+ */
93
+ export function scrollConversationToBottom(root = document, behavior = preferredScrollBehavior()) {
94
+ try {
95
+ const port = scrollport(root);
96
+ if (port !== null && typeof port.scrollTo === 'function') {
97
+ ;
98
+ port.scrollTo({ top: port.scrollHeight, behavior });
99
+ return true;
100
+ }
101
+ let latest = null;
102
+ let max = Number.NEGATIVE_INFINITY;
103
+ for (const row of (port ?? root).querySelectorAll(TURN_ROW_ELIGIBLE)) {
104
+ const value = Number(row.dataset.chatTurn);
105
+ if (Number.isSafeInteger(value) && value >= max) {
106
+ max = value;
107
+ latest = row;
108
+ }
109
+ }
110
+ if (latest === null)
111
+ return false;
112
+ latest.scrollIntoView({ behavior, block: 'end' });
113
+ return true;
114
+ }
115
+ catch {
116
+ return false;
117
+ }
118
+ }
@@ -0,0 +1,29 @@
1
+ import type { AgentScopeOverview, TurnTiming } from '../types.ts';
2
+ /** Partition one agent's elapsed time. Concurrent spans share their overlap, never double-count it. */
3
+ export declare function scopeTiming(timing: TurnTiming, scope: AgentScopeOverview): {
4
+ total: number;
5
+ model: number;
6
+ exec: number;
7
+ think: number;
8
+ gaps: number;
9
+ gapsHead: number;
10
+ gapsBetween: number;
11
+ gapsTail: number;
12
+ overlap: number;
13
+ tools: {
14
+ tool: string;
15
+ calls: number;
16
+ errors: number;
17
+ think: number;
18
+ args: number;
19
+ exec: number;
20
+ total: number;
21
+ }[];
22
+ non: {
23
+ wait: Map<number, number>;
24
+ text: Map<number, number>;
25
+ direct: Map<number, number>;
26
+ slack: Map<number, number>;
27
+ };
28
+ };
29
+ //# sourceMappingURL=timing.d.ts.map
@@ -0,0 +1,117 @@
1
+ /** Partition one agent's elapsed time. Concurrent spans share their overlap, never double-count it. */
2
+ export function scopeTiming(timing, scope) {
3
+ const lanes = timing.lanes.filter((l) => l.scopeId === scope.scopeId && l.durationMs >= 0);
4
+ const start = scope.kind === 'main'
5
+ ? (timing.startedAt ?? scope.startedAt)
6
+ : scope.startedAt;
7
+ const end = scope.kind === 'main'
8
+ ? start + timing.totalMs
9
+ : (scope.endedAt ??
10
+ Math.max(start, ...lanes.map((l) => l.startedAt + l.durationMs)));
11
+ const points = [
12
+ ...new Set([
13
+ start,
14
+ end,
15
+ ...lanes.flatMap((l) => [
16
+ Math.max(start, l.startedAt),
17
+ Math.min(end, l.startedAt + l.durationMs),
18
+ ]),
19
+ ]),
20
+ ]
21
+ .filter((t) => t >= start && t <= end)
22
+ .sort((a, b) => a - b);
23
+ const amounts = new Map();
24
+ let gaps = 0, overlap = 0;
25
+ // Idle time splits by where it sits: before the first activity, between
26
+ // activities, or after the last one (a turn the host closed long after its
27
+ // final request reads very differently from slow tools).
28
+ const firstAt = lanes.length ? Math.min(...lanes.map((l) => l.startedAt)) : end;
29
+ const lastAt = lanes.length ? Math.max(...lanes.map((l) => l.startedAt + l.durationMs)) : start;
30
+ let gapsHead = 0, gapsTail = 0;
31
+ for (let i = 1; i < points.length; i++) {
32
+ const a = points[i - 1], b = points[i];
33
+ const active = lanes.filter((l) => l.startedAt < b && l.startedAt + l.durationMs > a);
34
+ if (!active.length) {
35
+ gaps += b - a;
36
+ if (b <= firstAt)
37
+ gapsHead += b - a;
38
+ else if (a >= lastAt)
39
+ gapsTail += b - a;
40
+ }
41
+ if (active.length > 1)
42
+ overlap += b - a;
43
+ for (const lane of active)
44
+ amounts.set(lane, (amounts.get(lane) ?? 0) + (b - a) / active.length);
45
+ }
46
+ const tools = new Map();
47
+ const get = (tool) => {
48
+ let row = tools.get(tool);
49
+ if (!row) {
50
+ row = { tool, calls: 0, errors: 0, think: 0, args: 0, exec: 0, total: 0 };
51
+ tools.set(tool, row);
52
+ }
53
+ return row;
54
+ };
55
+ const non = {
56
+ wait: new Map(),
57
+ text: new Map(),
58
+ direct: new Map(),
59
+ slack: new Map(),
60
+ };
61
+ const put = (key, seq, ms) => non[key].set(seq, (non[key].get(seq) ?? 0) + ms);
62
+ let model = 0, exec = 0, think = 0;
63
+ for (const request of scope.requests) {
64
+ const rs = lanes.filter((l) => l.requestSeq === request.seq);
65
+ const args = rs.filter((l) => l.kind === 'tool-args' && l.tool !== undefined);
66
+ const executions = rs.filter((l) => l.kind === 'tool-exec' && l.tool !== undefined);
67
+ const calls = args.length ? args : executions;
68
+ for (const call of calls)
69
+ get(call.tool).calls++;
70
+ for (const lane of rs) {
71
+ const ms = amounts.get(lane) ?? 0;
72
+ if (lane.kind === 'tool-exec') {
73
+ exec += ms;
74
+ if (lane.tool) {
75
+ const row = get(lane.tool);
76
+ row.exec += ms;
77
+ if (lane.isError)
78
+ row.errors++;
79
+ }
80
+ }
81
+ else {
82
+ model += ms;
83
+ if (lane.kind === 'reasoning') {
84
+ think += ms;
85
+ if (calls.length)
86
+ for (const call of calls)
87
+ get(call.tool).think += ms / calls.length;
88
+ else
89
+ put('direct', request.seq, ms);
90
+ }
91
+ else if (lane.kind === 'tool-args' && lane.tool)
92
+ get(lane.tool).args += ms;
93
+ else
94
+ put(lane.kind === 'wait'
95
+ ? 'wait'
96
+ : lane.kind === 'text'
97
+ ? 'text'
98
+ : 'slack', request.seq, ms);
99
+ }
100
+ }
101
+ }
102
+ for (const row of tools.values())
103
+ row.total = row.think + row.args + row.exec;
104
+ return {
105
+ total: Math.max(0, end - start),
106
+ model,
107
+ exec,
108
+ think,
109
+ gaps,
110
+ gapsHead,
111
+ gapsBetween: Math.max(0, gaps - gapsHead - gapsTail),
112
+ gapsTail,
113
+ overlap,
114
+ tools: [...tools.values()],
115
+ non,
116
+ };
117
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * The four verbs' wire face, at FULL descriptor arity: the gateway runtime
3
+ * enforces each verb's declared parameter count exactly (`prepareInvocation`
4
+ * throws before any network traffic on a mismatch — a trailing optional
5
+ * parameter counts), so every call passes the trailing `filter` explicitly.
6
+ * The panel never filters; server-side consumers pass their own allow-list.
7
+ *
8
+ * @module @khorsheed/dsh-context-lens/client/wire
9
+ */
10
+ import type { InspectorRemote, InspectorTabInjected } from './contract.ts';
11
+ /** The injected face's verbs over the generated Remote, always at full arity. */
12
+ export declare function inspectorWire(remote: InspectorRemote): InspectorTabInjected;
13
+ //# sourceMappingURL=wire.d.ts.map
@@ -0,0 +1,11 @@
1
+ /** The injected face's verbs over the generated Remote, always at full arity. */
2
+ export function inspectorWire(remote) {
3
+ return {
4
+ getSessionTrend: sessionId => remote.getSessionTrend(sessionId, undefined),
5
+ listTurns: sessionId => remote.listTurns(sessionId, undefined),
6
+ loadTurns: (sessionId, end) => remote.loadTurns(sessionId, end, undefined),
7
+ getTurn: (sessionId, turnSeq) => remote.getTurn(sessionId, turnSeq, undefined),
8
+ getRequest: (sessionId, seq) => remote.getRequest(sessionId, seq, undefined),
9
+ getTurnTiming: (sessionId, turnSeq) => remote.getTurnTiming(sessionId, turnSeq, undefined),
10
+ };
11
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Request-inspector plugin, host half: provides the capture service core and
3
+ * mounts its Remote face. All capture rides read-only listeners — the
4
+ * `llm/stream` waterfall tee, `system-prompt/assemble` section attribution,
5
+ * `session/event` turn/tool correlation — plus a lazy child-log lane through
6
+ * `sessionPersistence` for fresh-process subagent runs. The plugin observes
7
+ * requests; it never rewrites them, and a capture failure never touches the
8
+ * model path.
9
+ *
10
+ * @module @khorsheed/dsh-context-lens
11
+ */
12
+ import type { Context } from '@deepseek-ai/cordis';
13
+ import { type InspectorConfig } from './service.ts';
14
+ export { RequestInspectorRemoteService } from './remote.ts';
15
+ export type { RequestInspectorRemoteConfig } from './remote.ts';
16
+ export { InspectorService } from './service.ts';
17
+ export type { InspectorConfig } from './service.ts';
18
+ export * from './types.ts';
19
+ /** Cordis plugin name used by loader diagnostics. */
20
+ export declare const name = "context-lens";
21
+ /**
22
+ * Plugin body: provide the service core, then mount the Remote data face.
23
+ *
24
+ * No `static inject`: every seam the capture touches is an event name or a
25
+ * probed service, so a composition missing one (no persistence backend, a host
26
+ * without these events) simply yields an empty panel instead of a failed boot.
27
+ *
28
+ * @param ctx - owning Cordis context.
29
+ * @param config - optional plugin config.
30
+ */
31
+ export declare function apply(ctx: Context, config?: InspectorConfig): void;
32
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,25 @@
1
+ import { InspectorService } from "./service.js";
2
+ import { RequestInspectorRemoteService } from "./remote.js";
3
+ export { RequestInspectorRemoteService } from "./remote.js";
4
+ export { InspectorService } from "./service.js";
5
+ export * from "./types.js";
6
+ /** Cordis plugin name used by loader diagnostics. */
7
+ export const name = 'context-lens';
8
+ /**
9
+ * Plugin body: provide the service core, then mount the Remote data face.
10
+ *
11
+ * No `static inject`: every seam the capture touches is an event name or a
12
+ * probed service, so a composition missing one (no persistence backend, a host
13
+ * without these events) simply yields an empty panel instead of a failed boot.
14
+ *
15
+ * @param ctx - owning Cordis context.
16
+ * @param config - optional plugin config.
17
+ */
18
+ export function apply(ctx, config = {}) {
19
+ const service = new InspectorService(ctx, config);
20
+ ctx.provide('requestInspector', service);
21
+ ctx.plugin(RequestInspectorRemoteService, {});
22
+ ctx.effect(() => () => {
23
+ service.dispose();
24
+ }, 'context-lens: capture shutdown');
25
+ }