@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,52 @@
1
+ import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
2
+ /**
3
+ * The 房间 tab: the decision-model calls the room made during this turn (its
4
+ * own scenarios — automatic task filing, automatic report timing), listed
5
+ * like requests (#n · scenario, the probability the outcome was taken with,
6
+ * input → output tokens, latency) and opened like one: the model facts, the
7
+ * state the room sent, the fixed question, the candidates with their
8
+ * probabilities and the outcome — or, through `{ }`, the request and response
9
+ * as JSON.
10
+ *
11
+ * @module @khorsheed/dsh-context-lens/client
12
+ */
13
+ import { useState } from 'react';
14
+ import { fmt } from "./aggregate.js";
15
+ import css from './TokenView.module.css';
16
+ import raw from './RawView.module.css';
17
+ import own from './RoomDecisionsView.module.css';
18
+ /** The scenario's display name. */
19
+ function scenarioName(scenario, t) {
20
+ return scenario === 'task-filing' ? t('room.scenario.filing') : scenario === 'report-timing' ? t('room.scenario.timing') : scenario;
21
+ }
22
+ /** A JSON value as compact display text. */
23
+ function show(value) {
24
+ return typeof value === 'string' ? value : JSON.stringify(value, null, 2);
25
+ }
26
+ /** The candidates and their probabilities, from the recorded response. */
27
+ function candidates(row, t) {
28
+ const answers = Array.isArray(row.response) ? row.response : [];
29
+ const answer = answers[0];
30
+ if (answer === undefined)
31
+ return [];
32
+ if (answer.probabilities !== undefined)
33
+ return Object.entries(answer.probabilities);
34
+ // A yes / no question reports the yes probability as its answer.
35
+ return typeof answer.answer === 'number' ? [[t('room.yes'), answer.answer], [t('room.no'), 1 - answer.answer]] : [];
36
+ }
37
+ /** The outcome sentence. */
38
+ function verdict(row, t) {
39
+ if (row.failure !== undefined)
40
+ return t('room.verdict.failed', { reason: row.failure.reason });
41
+ if (row.scenario === 'task-filing') {
42
+ return row.about.task === undefined ? t('room.verdict.new') : t('room.verdict.task', { number: String(row.about.task.number), title: row.about.task.title });
43
+ }
44
+ return t(row.outcome === 'later' ? 'room.verdict.later' : 'room.verdict.now', { from: row.about.from ?? '', to: row.about.to ?? '' });
45
+ }
46
+ /** The 房间 tab body. */
47
+ export function RoomDecisionsView({ decisions, selectedId, onSelect, t }) {
48
+ const [json, setJson] = useState(false);
49
+ const index = Math.max(0, decisions.findIndex(row => row.id === selectedId));
50
+ const row = decisions[index];
51
+ return _jsxs("div", { className: css.md, children: [_jsx("nav", { className: css.reqList, "aria-label": t('room.tab'), children: decisions.map((entry, i) => (_jsxs("button", { type: "button", className: entry === row ? css.reqItemSel : css.reqItem, "aria-pressed": entry === row, onClick: () => onSelect(entry.id), children: [_jsxs("span", { className: css.reqName, children: ["#", i + 1, " \u00B7 ", scenarioName(entry.scenario, t)] }), _jsx("span", { className: entry.failure === undefined ? own.prob : own.failed, children: entry.failure !== undefined ? t('room.failed') : entry.probability === undefined ? '—' : entry.probability.toFixed(2) }), _jsx("span", { className: css.reqIo, children: entry.inputTokens === undefined ? '—' : `${fmt(entry.inputTokens)} → ${fmt(entry.outputTokens ?? 0)}` }), _jsx("span", { className: css.reqTime, children: entry.latencyMs === undefined ? '—' : `${(entry.latencyMs / 1000).toFixed(1)}s` })] }, entry.id))) }), row !== undefined && _jsxs("div", { className: css.reqDetail, children: [_jsxs("div", { className: css.detailHead, children: [_jsxs("b", { children: ["#", index + 1, " \u00B7 ", scenarioName(row.scenario, t)] }), _jsx("button", { type: "button", className: css.jsonToggle, "aria-label": t('view.raw'), title: t('view.raw'), "aria-pressed": json, onClick: () => setJson(value => !value), children: '{ }' })] }), _jsxs("div", { className: css.metrics, children: [_jsxs("div", { children: [_jsx("span", { children: t('room.model') }), _jsx("b", { className: own.small, children: row.model }), _jsx("span", { children: "typesafe" })] }), _jsxs("div", { children: [_jsx("span", { children: t('req.input') }), _jsxs("b", { children: [row.inputTokens === undefined ? '—' : fmt(row.inputTokens), _jsx("small", { children: "tok" })] }), _jsxs("span", { children: [t('req.output'), " ", row.outputTokens === undefined ? '—' : fmt(row.outputTokens), " tok"] })] }), _jsxs("div", { children: [_jsx("span", { children: t('lens.duration') }), _jsx("b", { children: row.latencyMs === undefined ? '—' : `${(row.latencyMs / 1000).toFixed(1)}s` }), _jsx("span", {})] }), _jsxs("div", { children: [_jsx("span", { children: t('room.cache') }), _jsx("b", { className: own.small, children: row.cached === undefined ? '—' : row.cached ? t('room.cacheHit') : t('room.cacheMiss') }), _jsx("span", {})] })] }), json ? _jsxs(_Fragment, { children: [_jsxs("section", { children: [_jsx("h3", { className: css.sectionTitle, children: t('room.request') }), _jsx("pre", { className: raw.pre, children: JSON.stringify({ model: row.model, ...row.request }, null, 2) })] }), _jsxs("section", { children: [_jsx("h3", { className: css.sectionTitle, children: t('room.response') }), _jsx("pre", { className: raw.pre, children: JSON.stringify(row.failure === undefined ? row.response ?? null : { failure: row.failure }, null, 2) })] })] }) : _jsxs(_Fragment, { children: [_jsxs("section", { children: [_jsxs("h3", { className: css.sectionTitle, children: [t('room.state'), _jsx("span", { children: t('room.stateNote') })] }), _jsx("dl", { className: own.kv, children: Object.entries((row.request.state ?? {})).map(([key, value]) => _jsxs("div", { children: [_jsx("dt", { children: key }), _jsx("dd", { children: show(value) })] }, key)) })] }), _jsxs("section", { children: [_jsxs("h3", { className: css.sectionTitle, children: [t('room.question'), _jsx("span", { children: t('room.questionNote') })] }), _jsx("p", { className: own.box, children: show((Array.isArray(row.request.questions) ? row.request.questions[0]?.instructions : undefined) ?? '') })] }), candidates(row, t).length > 0 && _jsxs("section", { children: [_jsx("h3", { className: css.sectionTitle, children: t('room.candidates') }), _jsx("div", { className: own.opts, children: candidates(row, t).map(([label, p]) => (_jsxs("div", { className: own.opt, children: [_jsx("span", { children: label }), _jsx("span", { className: own.bar, children: _jsx("i", { style: { width: `${Math.round(p * 100)}%` } }) }), _jsx("span", { className: own.pc, children: p.toFixed(2) })] }, label))) })] }), _jsxs("p", { className: row.failure === undefined ? own.verdict : own.verdictFailed, children: [_jsx("b", { children: t('room.verdict') }), verdict(row, t)] })] })] })] });
52
+ }
@@ -0,0 +1,13 @@
1
+ /** Read-only JSON Schema presentation. Raw capture remains the lossless fallback. */
2
+ import type { InspectorTabProps } from './contract.ts';
3
+ type T = InspectorTabProps['t'];
4
+ export declare function schemaRecord(value: unknown): value is Record<string, unknown>;
5
+ export declare function SchemaFields({ name, value, required, depth, t }: {
6
+ readonly name?: string;
7
+ readonly value: unknown;
8
+ readonly required?: boolean;
9
+ readonly depth?: number;
10
+ readonly t: T;
11
+ }): import("react").JSX.Element;
12
+ export {};
13
+ //# sourceMappingURL=SchemaFields.d.ts.map
@@ -0,0 +1,96 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import css from './SegmentBlocks.module.css';
3
+ export function schemaRecord(value) {
4
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
5
+ }
6
+ const MAX_DEPTH = 12;
7
+ const labels = {
8
+ oneOf: 'schema.oneOf', anyOf: 'schema.anyOf', allOf: 'schema.allOf',
9
+ items: 'schema.items', prefixItems: 'schema.prefixItems', additionalItems: 'schema.additionalItems',
10
+ additionalProperties: 'schema.additionalProperties', unevaluatedProperties: 'schema.unevaluatedProperties', unevaluatedItems: 'schema.unevaluatedItems',
11
+ contains: 'schema.contains', not: 'schema.not', if: 'schema.if', then: 'schema.then', else: 'schema.else', propertyNames: 'schema.propertyNames',
12
+ patternProperties: 'schema.patternProperties', dependentSchemas: 'schema.dependentSchemas', $defs: 'schema.definitions', definitions: 'schema.definitions',
13
+ enum: 'schema.enum', const: 'schema.const', default: 'schema.default', examples: 'schema.examples',
14
+ minimum: 'schema.minimum', maximum: 'schema.maximum', exclusiveMinimum: 'schema.exclusiveMinimum', exclusiveMaximum: 'schema.exclusiveMaximum', multipleOf: 'schema.multipleOf',
15
+ minLength: 'schema.minLength', maxLength: 'schema.maxLength', pattern: 'schema.pattern', format: 'schema.format',
16
+ minItems: 'schema.minItems', maxItems: 'schema.maxItems', uniqueItems: 'schema.uniqueItems', minContains: 'schema.minContains', maxContains: 'schema.maxContains',
17
+ minProperties: 'schema.minProperties', maxProperties: 'schema.maxProperties', dependentRequired: 'schema.dependentRequired',
18
+ $ref: 'schema.ref', title: 'schema.title', deprecated: 'schema.deprecated', readOnly: 'schema.readOnly', writeOnly: 'schema.writeOnly',
19
+ };
20
+ const label = (key, t) => Object.hasOwn(labels, key) ? t(labels[key]) : key;
21
+ const alternatives = new Set(['oneOf', 'anyOf', 'allOf', 'prefixItems']);
22
+ const maps = new Set(['patternProperties', '$defs', 'definitions', 'dependentSchemas']);
23
+ const singleSchemas = new Set(['items', 'additionalItems', 'additionalProperties', 'unevaluatedProperties', 'unevaluatedItems', 'contains', 'not', 'if', 'then', 'else', 'propertyNames']);
24
+ function explicitTypes(value, depth = 0) {
25
+ if (!schemaRecord(value) || depth >= MAX_DEPTH)
26
+ return null;
27
+ if (typeof value.type === 'string')
28
+ return [value.type];
29
+ if (Array.isArray(value.type) && value.type.length > 0 && value.type.every(x => typeof x === 'string'))
30
+ return value.type;
31
+ const union = value.oneOf ?? value.anyOf;
32
+ if (!Array.isArray(union) || union.length === 0)
33
+ return null;
34
+ const types = union.map(x => explicitTypes(x, depth + 1));
35
+ return types.every(x => x !== null) ? [...new Set(types.flat())] : null;
36
+ }
37
+ function typeLabel(value, t) {
38
+ if (value === false)
39
+ return t('schema.never');
40
+ if (value === true)
41
+ return t('schema.any');
42
+ const types = explicitTypes(value);
43
+ if (types)
44
+ return types.join(' | ');
45
+ if (schemaRecord(value) && '$ref' in value)
46
+ return t('schema.reference');
47
+ if (schemaRecord(value) && ['allOf', 'oneOf', 'anyOf'].some(k => k in value))
48
+ return t('schema.composite');
49
+ return t('schema.unspecified');
50
+ }
51
+ /** Unknown keywords and extension values retain their full shape without dumping JSON blocks. */
52
+ function ValueTree({ value, depth, t }) {
53
+ if (depth >= MAX_DEPTH)
54
+ return _jsx("span", { children: t('schema.deep') });
55
+ if (Array.isArray(value))
56
+ return value.length ? _jsx("div", { className: css.schemaValues, children: value.map((v, i) => _jsx("div", { children: _jsx(ValueTree, { value: v, depth: depth + 1, t: t }) }, i)) }) : _jsx("code", { children: "[]" });
57
+ if (schemaRecord(value))
58
+ return Object.keys(value).length ? _jsx("dl", { className: css.schemaFacts, children: Object.entries(value).map(([key, v]) => _jsxs("div", { children: [_jsx("dt", { children: key }), _jsx("dd", { children: _jsx(ValueTree, { value: v, depth: depth + 1, t: t }) })] }, key)) }) : _jsx("span", { children: t('schema.emptyObject') });
59
+ return _jsx("code", { children: JSON.stringify(value) });
60
+ }
61
+ function Constraint({ name, value, depth, t }) {
62
+ if (alternatives.has(name) && Array.isArray(value))
63
+ return _jsxs("div", { className: css.schemaGroup, children: [_jsx("div", { className: css.schemaCaption, children: label(name, t) }), _jsx("div", { className: css.schemaChildren, children: value.map((v, i) => _jsx(SchemaFields, { name: t(name === 'prefixItems' ? 'schema.position' : 'schema.branch', { count: String(i + 1) }), value: v, depth: depth + 1, t: t }, i)) })] });
64
+ if (maps.has(name) && schemaRecord(value))
65
+ return _jsxs("div", { className: css.schemaGroup, children: [_jsx("div", { className: css.schemaCaption, children: label(name, t) }), _jsx("div", { className: css.schemaChildren, children: Object.entries(value).map(([key, v]) => _jsx(SchemaFields, { name: key, value: v, depth: depth + 1, t: t }, key)) })] });
66
+ if (['additionalProperties', 'additionalItems', 'unevaluatedProperties', 'unevaluatedItems'].includes(name) && typeof value === 'boolean')
67
+ return _jsx("dl", { className: css.schemaFacts, children: _jsxs("div", { children: [_jsx("dt", { children: label(name, t) }), _jsx("dd", { children: t(value ? 'schema.allowed' : 'schema.forbidden') })] }) });
68
+ if (singleSchemas.has(name) && (schemaRecord(value) || typeof value === 'boolean'))
69
+ return _jsx("div", { className: css.schemaChildren, children: _jsx(SchemaFields, { name: label(name, t), value: value, depth: depth + 1, t: t }) });
70
+ // Older drafts use items arrays for tuples; retain their position, not a union.
71
+ if (name === 'items' && Array.isArray(value))
72
+ return _jsx(Constraint, { name: "prefixItems", value: value, depth: depth, t: t });
73
+ return _jsx("dl", { className: css.schemaFacts, children: _jsxs("div", { children: [_jsx("dt", { children: label(name, t) }), _jsx("dd", { children: _jsx(ValueTree, { value: value, depth: depth + 1, t: t }) })] }) });
74
+ }
75
+ export function SchemaFields({ name, value, required = false, depth = 0, t }) {
76
+ if (depth >= MAX_DEPTH)
77
+ return _jsxs("div", { className: css.schemaCaption, children: [name, " \u00B7 ", t('schema.deep')] });
78
+ if (!schemaRecord(value) && typeof value !== 'boolean')
79
+ return _jsxs("div", { className: css.schemaCaption, children: [name, " \u00B7 ", t('schema.unreadable')] });
80
+ const fields = schemaRecord(value) ? value : {};
81
+ const properties = schemaRecord(fields.properties) ? fields.properties : null;
82
+ const requiredNames = Array.isArray(fields.required) && fields.required.every(x => typeof x === 'string') ? fields.required : null;
83
+ const remainingRequired = requiredNames?.filter(key => !properties || !Object.hasOwn(properties, key)) ?? [];
84
+ return _jsxs("div", { className: name ? css.parameter : css.schemaRoot, children: [name ? _jsxs("div", { className: css.parameterHead, children: [_jsx("code", { children: name }), _jsx("span", { children: typeLabel(value, t) }), required && _jsx("b", { children: t('lens.required') })] })
85
+ : (!properties || fields.type !== undefined && fields.type !== 'object') && _jsx("div", { className: css.schemaCaption, children: typeLabel(value, t) }), typeof fields.description === 'string' && _jsx("div", { className: css.prose, children: fields.description }), remainingRequired.length > 0 && _jsx(Constraint, { name: t('schema.otherRequired'), value: remainingRequired, depth: depth, t: t }), Object.entries(fields).filter(([key, v]) => {
86
+ if (key === 'type')
87
+ return !(typeof v === 'string' || Array.isArray(v) && v.length > 0 && v.every(x => typeof x === 'string'));
88
+ if (key === 'properties')
89
+ return !properties;
90
+ if (key === 'required')
91
+ return !requiredNames;
92
+ if (key === 'description')
93
+ return typeof v !== 'string';
94
+ return true;
95
+ }).map(([key, v]) => _jsx(Constraint, { name: key, value: v, depth: depth, t: t }, key)), properties && _jsx("div", { className: name ? css.schemaChildren : css.parameters, children: Object.entries(properties).map(([key, v]) => _jsx(SchemaFields, { name: key, value: v, required: requiredNames?.includes(key) ?? false, depth: depth + 1, t: t }, key)) })] });
96
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * The segmented switch of the reference design (`segCtl`): the header's
3
+ * Token/耗时 mini switch and the footer's 结构化/原始 JSON/wire body view
4
+ * switch. A disabled option renders dim with its hint as the tooltip (the
5
+ * wire-body enhancement seat).
6
+ *
7
+ * @module @khorsheed/dsh-context-lens/client
8
+ */
9
+ import type { ReactNode } from 'react';
10
+ /** One segment of the switch. */
11
+ export interface SegCtlOption<Id extends string = string> {
12
+ readonly id: Id;
13
+ readonly label: ReactNode;
14
+ /** Disabled options render dim and never fire (the wire-body seat). */
15
+ readonly disabled?: boolean | undefined;
16
+ /** Tooltip on a disabled option. */
17
+ readonly hint?: string | undefined;
18
+ /** A small trailing tag (the `≥0.1.2` marker). */
19
+ readonly tag?: string | undefined;
20
+ }
21
+ export interface SegCtlProps<Id extends string = string> {
22
+ readonly options: readonly SegCtlOption<Id>[];
23
+ readonly value: Id;
24
+ readonly onChange: (id: Id) => void;
25
+ /** The group's accessible name. */
26
+ readonly label: string;
27
+ /** The header's compact variant. */
28
+ readonly mini?: boolean | undefined;
29
+ }
30
+ /** The pill segmented control. */
31
+ export declare function SegCtl<Id extends string>({ options, value, onChange, label, mini }: SegCtlProps<Id>): import("react").JSX.Element;
32
+ //# sourceMappingURL=SegCtl.d.ts.map
@@ -0,0 +1,12 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import css from './SegCtl.module.css';
3
+ /** The pill segmented control. */
4
+ export function SegCtl({ options, value, onChange, label, mini = false }) {
5
+ return (_jsx("div", { className: mini ? css.mini : css.root, role: "group", "aria-label": label, children: options.map(option => {
6
+ const selected = option.id === value;
7
+ if (option.disabled === true) {
8
+ return (_jsxs("button", { type: "button", className: css.dim, disabled: true, title: option.hint, children: [option.label, option.tag !== undefined && _jsx("span", { className: css.tag, children: option.tag })] }, option.id));
9
+ }
10
+ return (_jsxs("button", { type: "button", className: selected ? css.on : css.off, "aria-pressed": selected, onClick: () => { onChange(option.id); }, children: [option.label, option.tag !== undefined && _jsx("span", { className: css.tag, children: option.tag })] }, option.id));
11
+ }) }));
12
+ }
@@ -0,0 +1,28 @@
1
+ import type { CapturedRequest, RequestLink, SegmentCategory } from '../types.ts';
2
+ import { type CompositionFilter } from './aggregate.ts';
3
+ import type { InspectorTabProps } from './contract.ts';
4
+ type T = InspectorTabProps['t'];
5
+ export declare function categoryLabelKey(category: SegmentCategory): `cat.${SegmentCategory}`;
6
+ export interface SegmentBlocksProps {
7
+ readonly request: CapturedRequest;
8
+ readonly filterCat: CompositionFilter;
9
+ readonly onOpenRequest?: ((link: RequestLink) => void) | undefined;
10
+ readonly onClearFilter?: () => void;
11
+ readonly previousResponseNumber: number | null;
12
+ readonly t: T;
13
+ }
14
+ /** Filtering changes rows only: group counts, bars and sizes keep their full-request meaning. */
15
+ export declare function SegmentBlocks({ request, filterCat, onClearFilter, onOpenRequest, t }: SegmentBlocksProps): import("react").JSX.Element;
16
+ /** The output body is visible, including reasoning and arguments, without opening a wrapper. */
17
+ export declare function OutputBlock({ request, t }: {
18
+ readonly request: CapturedRequest;
19
+ readonly t: T;
20
+ }): import("react").JSX.Element;
21
+ export declare function Exchange({ request, previousResponseNumber, onOpenRequest, t }: {
22
+ readonly request: CapturedRequest;
23
+ readonly previousResponseNumber: number | null;
24
+ readonly onOpenRequest?: ((link: RequestLink) => void) | undefined;
25
+ readonly t: T;
26
+ }): import("react").JSX.Element;
27
+ export {};
28
+ //# sourceMappingURL=SegmentBlocks.d.ts.map
@@ -0,0 +1,225 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /** Compact composition rows and immediately readable exchange cards. */
3
+ import { useEffect, useLayoutEffect, useRef, useState } from 'react';
4
+ import { duplicateStats, fmt, fmtK, fmtSpan } from "./aggregate.js";
5
+ import css from './SegmentBlocks.module.css';
6
+ import { IconChevronDownOutlineMedium } from "./icons.js";
7
+ import { ToolCatalog } from "./ToolCatalog.js";
8
+ export function categoryLabelKey(category) { return `cat.${category}`; }
9
+ function parseAssistant(full) {
10
+ try {
11
+ const value = JSON.parse(full);
12
+ if (value && typeof value === 'object' && !Array.isArray(value))
13
+ return value;
14
+ }
15
+ catch { /* Plain assistant text is also valid. */ }
16
+ return null;
17
+ }
18
+ function BodyText({ text }) {
19
+ let code = false;
20
+ try {
21
+ const value = JSON.parse(text);
22
+ code = value !== null && typeof value === 'object';
23
+ }
24
+ catch { /* Prose stays verbatim. */ }
25
+ return _jsx("pre", { className: code ? css.pre : css.prose, children: text });
26
+ }
27
+ function ItemFull({ item }) {
28
+ const a = item.category === 'assistant' ? parseAssistant(item.full) : null;
29
+ if (!a || (!a.reasoning && !a.text && !a.calls?.length))
30
+ return _jsx(BodyText, { text: item.full });
31
+ return _jsxs(_Fragment, { children: [a.reasoning && _jsxs(_Fragment, { children: [_jsx("div", { className: css.fullLabel, children: "reasoning" }), _jsx(BodyText, { text: a.reasoning })] }), a.text && _jsxs(_Fragment, { children: [_jsx("div", { className: css.fullLabel, children: "text" }), _jsx(BodyText, { text: a.text })] }), a.calls?.map((c, i) => _jsxs("div", { children: [_jsxs("div", { className: css.fullLabel, children: ["tool_call \u00B7 ", c.name] }), _jsx("pre", { className: css.pre, children: c.arguments })] }, i))] });
32
+ }
33
+ /** Compression is provenance, not another copy of the active context. */
34
+ function RewriteMeta({ item, onOpenRequest, t }) {
35
+ if (!item.compactionId && !item.pruning)
36
+ return null;
37
+ return _jsxs("div", { className: css.rewriteMeta, children: [item.compactionId && _jsxs(_Fragment, { children: [_jsx("span", { children: t('compaction.checkpoint') }), item.compactionRequest && onOpenRequest
38
+ ? _jsxs("button", { type: "button", onClick: () => onOpenRequest(item.compactionRequest), children: [t('compaction.inspect'), " \u2197"] })
39
+ : _jsx("span", { children: t('compaction.unavailable') })] }), item.pruning && _jsxs(_Fragment, { children: [_jsx("span", { children: t('pruning.changed', { before: fmt(item.pruning.beforeChars), after: fmt(item.pruning.afterChars) }) }), item.pruning.before && onOpenRequest && _jsxs("button", { type: "button", onClick: () => onOpenRequest(item.pruning.before), children: [t('pruning.before'), " \u2197"] })] })] });
40
+ }
41
+ const itemNumber = (item) => item.index < 0 ? '—' : String(item.index).padStart(3, '0');
42
+ /** The list stays dense; navigation is shared across system/history/added layers. */
43
+ function MessageRows({ items, groups, highlight, reading, onOpenRequest, t }) {
44
+ return _jsx(_Fragment, { children: items.map((item, i) => {
45
+ const open = reading.expanded.has(item);
46
+ const group = groups.get(item.full) ?? [];
47
+ const peers = group.filter(other => other !== item);
48
+ const next = items[i + 1];
49
+ return _jsxs("details", { ref: el => reading.register(item, el), className: css.item, open: open, "data-highlight": highlight, "data-jump": reading.flash === item, children: [_jsxs("summary", { className: css.row, title: item.preview, onClick: e => { e.preventDefault(); reading.toggle(item); }, children: [_jsx("span", { className: css.idx, children: itemNumber(item) }), _jsx("i", { className: css.dot, "data-cat": item.category }), _jsx("span", { className: css.type, children: t(categoryLabelKey(item.category)) }), _jsx("span", { className: css.preview, children: item.preview }), _jsxs("span", { className: css.rowBadges, children: [item.pruning && _jsx("span", { className: css.badge, children: t('pruning.badge') }), group.length > 1 && _jsxs("span", { className: css.badge, children: ["\u00D7", group.length] }), item.isError && _jsx("span", { className: css.errorNote, children: t('lens.errors') })] }), _jsx("span", { className: css.number, children: fmt(item.chars) }), _jsx(IconChevronDownOutlineMedium, { size: 16, className: css.chevron }), open && _jsxs("span", { className: css.readActions, children: [_jsx("span", { children: t('lens.collapse') }), _jsxs("button", { type: "button", disabled: !next, onClick: e => {
50
+ e.preventDefault();
51
+ e.stopPropagation();
52
+ if (next)
53
+ reading.next(item, next);
54
+ }, children: [t('lens.nextItem'), " \u2193"] })] })] }), open && _jsxs("div", { className: css.full, children: [item.isError && _jsx("div", { className: css.errorNote, children: t('lens.errors') }), _jsxs("div", { className: `${css.fullLabel} ${css.messageMeta}`, children: [_jsxs("span", { children: [item.source, item.sourceTurn === undefined ? '' : ` · ${t('lens.sourceTurn', { turn: String(item.sourceTurn) })}`, item.durationMs === undefined ? '' : ` · ${fmtSpan(item.durationMs)}`] }), peers.length > 0 && _jsxs("span", { className: css.duplicateLinks, children: [_jsx("span", { children: t('lens.sameWith') }), peers.map(other => _jsxs("button", { type: "button", onClick: () => reading.jump(item, other), children: [itemNumber(other), " \u00B7 ", t(categoryLabelKey(other.category)), " \u2197"] }, other.index)), _jsx("span", { children: t('lens.sameFullText') })] }), reading.destination === item && reading.returnTo && _jsxs("button", { type: "button", className: css.returnLink, onClick: () => reading.back(item), children: [t('lens.returnItem', { index: itemNumber(reading.returnTo) }), " \u21A9"] })] }), _jsx(RewriteMeta, { item: item, onOpenRequest: onOpenRequest, t: t }), _jsx(ItemFull, { item: item })] })] }, item.index);
55
+ }) });
56
+ }
57
+ /** Filtering changes rows only: group counts, bars and sizes keep their full-request meaning. */
58
+ export function SegmentBlocks({ request, filterCat, onClearFilter, onOpenRequest, t }) {
59
+ const { system, tools, history, added } = request.segments;
60
+ const [openLayers, setOpenLayers] = useState(new Set(['added']));
61
+ const duplicates = duplicateStats([...system, ...history, ...added]);
62
+ const groups = new Map();
63
+ for (const item of [...system, ...history, ...added]) {
64
+ if ((duplicates.counts.get(item.full) ?? 0) > 1) {
65
+ const group = groups.get(item.full) ?? [];
66
+ group.push(item);
67
+ groups.set(item.full, group);
68
+ }
69
+ }
70
+ const [expanded, setExpanded] = useState(new Set());
71
+ const [target, setTarget] = useState(null);
72
+ const [flash, setFlash] = useState(null);
73
+ const [trail, setTrail] = useState([]);
74
+ const [destination, setDestination] = useState();
75
+ const refs = useRef(new Map());
76
+ useLayoutEffect(() => {
77
+ if (!target)
78
+ return;
79
+ const row = refs.current.get(target);
80
+ if (!row)
81
+ return;
82
+ row.scrollIntoView?.({ block: 'start', behavior: 'instant' });
83
+ row.querySelector('summary')?.focus({ preventScroll: true });
84
+ setFlash(target);
85
+ setTarget(null);
86
+ }, [target, filterCat]);
87
+ useEffect(() => {
88
+ if (!flash)
89
+ return;
90
+ const timer = setTimeout(() => setFlash(null), 1600);
91
+ return () => clearTimeout(timer);
92
+ }, [flash]);
93
+ const matches = (x) => filterCat === null || (filterCat === '__dup' ? (duplicates.counts.get(x.full) ?? 0) > 1 : x.category === filterCat);
94
+ useEffect(() => {
95
+ if (filterCat === null)
96
+ return;
97
+ setOpenLayers(previous => {
98
+ const next = new Set(previous);
99
+ next.add('history');
100
+ for (const id of ['system', 'added'])
101
+ if (request.segments[id].some(x => filterCat === '__dup' ? (duplicates.counts.get(x.full) ?? 0) > 1 : x.category === filterCat))
102
+ next.add(id);
103
+ if (filterCat === 'tool-definition')
104
+ next.add('tools');
105
+ return next;
106
+ });
107
+ }, [filterCat, request]);
108
+ const navigate = (from, to) => {
109
+ if (!matches(to))
110
+ onClearFilter?.();
111
+ setOpenLayers(previous => {
112
+ const next = new Set(previous);
113
+ for (const id of ['system', 'history', 'added'])
114
+ if (request.segments[id].includes(to))
115
+ next.add(id);
116
+ return next;
117
+ });
118
+ setExpanded(previous => { const next = new Set(previous); next.delete(from); next.add(to); return next; });
119
+ setTarget(to);
120
+ };
121
+ const reading = {
122
+ expanded, flash, returnTo: trail.at(-1), destination,
123
+ register: (item, element) => { if (element)
124
+ refs.current.set(item, element);
125
+ else
126
+ refs.current.delete(item); },
127
+ toggle: item => {
128
+ if (expanded.has(item))
129
+ setTarget(item);
130
+ setExpanded(previous => { const next = new Set(previous); next.has(item) ? next.delete(item) : next.add(item); return next; });
131
+ setTrail([]);
132
+ setDestination(undefined);
133
+ },
134
+ next: (from, to) => { setTrail([]); setDestination(undefined); navigate(from, to); },
135
+ jump: (from, to) => { setTrail(previous => [...previous, from]); setDestination(to); navigate(from, to); },
136
+ back: from => {
137
+ const to = trail.at(-1);
138
+ if (!to)
139
+ return;
140
+ setTrail(previous => previous.slice(0, -1));
141
+ setDestination(to);
142
+ navigate(from, to);
143
+ },
144
+ };
145
+ const layers = [
146
+ { id: 'system', items: system, count: system.length, chars: system.reduce((n, x) => n + x.chars, 0), unit: 'cat.rows' },
147
+ { id: 'tools', items: [], count: tools.length, chars: tools.reduce((n, x) => n + x.chars, 0), unit: 'seg.toolsFrom' },
148
+ { id: 'history', items: history, count: history.length, chars: history.reduce((n, x) => n + x.chars, 0), unit: 'lens.messages' },
149
+ { id: 'added', items: added, count: added.length, chars: added.reduce((n, x) => n + x.chars, 0), unit: 'lens.messages' },
150
+ ];
151
+ const total = layers.reduce((n, l) => n + l.chars, 0);
152
+ const seen = new Set();
153
+ return _jsx(_Fragment, { children: layers.map(layer => {
154
+ const open = openLayers.has(layer.id);
155
+ const mix = new Map();
156
+ if (layer.id === 'tools')
157
+ mix.set('tool-definition', layer.chars);
158
+ else
159
+ for (const x of layer.items)
160
+ mix.set(x.category, (mix.get(x.category) ?? 0) + x.chars);
161
+ const rows = layer.items.filter(matches);
162
+ let extra = 0;
163
+ for (const item of layer.items) {
164
+ if (item.full.length < 40)
165
+ continue;
166
+ if (seen.has(item.full))
167
+ extra += item.chars;
168
+ seen.add(item.full);
169
+ }
170
+ const names = { system: 'lens.layerSystem', tools: 'lens.layerTools', history: 'lens.layerHistory', added: 'lens.layerAdded' };
171
+ return _jsxs("details", { className: css.layer, open: open, children: [_jsxs("summary", { className: css.layerHead, onClick: e => { e.preventDefault(); setOpenLayers(previous => { const next = new Set(previous); next.has(layer.id) ? next.delete(layer.id) : next.add(layer.id); return next; }); }, children: [_jsx(IconChevronDownOutlineMedium, { size: 16, className: css.chevron }), _jsxs("span", { className: css.layerName, children: [t(names[layer.id]), _jsx("small", { children: t(layer.unit, { count: String(layer.count) }) })] }), _jsxs("span", { className: css.mix, children: [_jsx("span", { className: css.mini, style: { width: `${total ? Math.max(3, layer.chars / total * 100) : 0}%` }, children: [...mix].sort((a, b) => b[1] - a[1]).map(([category, n]) => _jsx("i", { "data-cat": category, style: { flex: `${n} 1 0` } }, category)) }), extra > 0 && _jsx("span", { className: css.badge, children: t('lens.repeatedChars', { count: fmtK(extra) }) }), layer.id === 'tools' && tools.some(x => x.added) && _jsx("span", { className: css.badge, children: t('lens.newTools', { count: String(tools.filter(x => x.added).length) }) })] }), _jsx("span", { className: css.number, children: fmt(layer.chars) })] }), open && _jsx("div", { className: css.rows, children: layer.id === 'tools' ? ((filterCat === null || filterCat === 'tool-definition') && tools.length > 0 ? _jsx(ToolCatalog, { tools: tools, t: t }) : _jsx("div", { className: css.emptyNote, children: t(filterCat ? 'lens.noMatch' : 'seg.toolsEmpty') }))
172
+ : rows.length ? _jsx(MessageRows, { items: rows, groups: groups, highlight: filterCat === '__dup', reading: reading, onOpenRequest: onOpenRequest, t: t })
173
+ : _jsx("div", { className: css.emptyNote, children: t(filterCat ? 'lens.noMatch' : layer.id === 'system' ? 'seg.systemEmpty' : 'seg.empty') }) })] }, layer.id);
174
+ }) });
175
+ }
176
+ function Clamped({ text, t }) {
177
+ const [open, setOpen] = useState(false);
178
+ const long = text.length > 300;
179
+ return _jsxs(_Fragment, { children: [_jsx("pre", { className: css.clamp, "data-open": open, "data-long": long, children: text }), long && _jsx("button", { className: css.more, type: "button", onClick: () => setOpen(v => !v), children: open ? t('lens.collapse') : t('lens.expand', { count: fmt(text.length) }) })] });
180
+ }
181
+ function ArgumentValue({ name, value }) {
182
+ const [open, setOpen] = useState(false);
183
+ return _jsxs(_Fragment, { children: [_jsx("span", { className: css.argKey, children: name }), _jsx("button", { type: "button", className: css.argValue, "aria-label": name, "aria-expanded": open, title: value, onClick: () => setOpen(v => !v), children: value })] });
184
+ }
185
+ function Arguments({ raw, t }) {
186
+ let obj;
187
+ try {
188
+ obj = JSON.parse(raw);
189
+ }
190
+ catch {
191
+ return _jsx(Clamped, { text: raw, t: t });
192
+ }
193
+ if (!obj || typeof obj !== 'object')
194
+ return _jsx(Clamped, { text: raw, t: t });
195
+ return _jsx("div", { className: css.args, children: Object.entries(obj).map(([k, v]) => _jsx(ArgumentValue, { name: k, value: typeof v === 'string' ? v : JSON.stringify(v) }, k)) });
196
+ }
197
+ function Reasoning({ text, ms, t }) {
198
+ const [open, setOpen] = useState(false);
199
+ return _jsxs("div", { className: css.think, children: [_jsxs("button", { type: "button", "aria-expanded": open, onClick: () => setOpen(v => !v), children: [_jsx("b", { children: t('lens.reasoning') }), " \u00B7 ", ms === undefined ? '—' : fmtSpan(ms), " \u00B7 ", t('chain.chars', { count: fmt(text.length) })] }), _jsx("div", { className: css.thinkText, "data-open": open, children: text })] });
200
+ }
201
+ /** Result text is attached by call ID, including same-name and parallel calls. */
202
+ function ToolCallCard({ call, t }) {
203
+ const [open, setOpen] = useState(false);
204
+ const execution = call.execution;
205
+ const failed = execution?.status === 'failed';
206
+ const result = execution?.result;
207
+ const status = execution?.status ?? 'unknown';
208
+ return _jsxs("div", { className: css.call, "data-status": status, children: [_jsxs("div", { className: css.callHead, children: [_jsx("span", { children: t('lens.calls') }), _jsx("b", { children: call.name }), _jsx("span", { className: css.callStatus, "data-status": status, children: t(`tool.status.${status}`) }), _jsx("span", { className: css.number, children: fmt(call.arguments.length) })] }), _jsx(Arguments, { raw: call.arguments, t: t }), execution && execution.status !== 'running' && _jsxs("div", { className: css.result, "data-error": failed, children: [_jsxs("div", { className: css.resultHead, children: [_jsx("b", { children: t('tool.result') }), result !== undefined && _jsx("button", { type: "button", className: css.more, "aria-expanded": open, onClick: () => setOpen(v => !v), children: open ? t('lens.collapse') : t(failed ? 'tool.expand' : 'tool.show', { count: fmt(result.length) }) })] }), result === undefined ? _jsx("div", { className: css.emptyNote, children: t('tool.noResult') })
209
+ : (failed || open) && _jsx("pre", { className: css.resultText, "data-open": open, children: result || t('tool.emptyResult') })] })] });
210
+ }
211
+ /** The output body is visible, including reasoning and arguments, without opening a wrapper. */
212
+ export function OutputBlock({ request, t }) {
213
+ const response = request.response;
214
+ return _jsxs("div", { className: css.output, children: [request.status === 'failed' && request.error && _jsx("div", { className: css.errorNote, children: request.error }), response === undefined ? _jsx("div", { className: css.emptyNote, children: t(request.status === 'streaming' ? 'out.streaming' : 'lens.noResponse') }) : _jsxs(_Fragment, { children: [response.reasoning && _jsx(Reasoning, { text: response.reasoning, ms: request.timing.reasoningMs, t: t }), response.text && _jsx(Clamped, { text: response.text, t: t }), response.toolCalls.map((call, i) => _jsx(ToolCallCard, { call: call, t: t }, call.callId ?? i))] })] });
215
+ }
216
+ function InputCard({ item, from, repeats, onOpenRequest, t }) {
217
+ const [open, setOpen] = useState(false);
218
+ const a = item.category === 'assistant' ? parseAssistant(item.full) : null;
219
+ const body = a ? [a.text, ...(a.calls ?? []).map(c => `→ ${c.name}`)].filter(Boolean).join('\n') || item.preview : item.full || item.preview;
220
+ return _jsxs("div", { className: css.message, "data-error": item.isError, children: [_jsxs("button", { type: "button", className: css.messageHead, "aria-expanded": open, onClick: () => setOpen(v => !v), children: [_jsx("i", { className: css.dot, "data-cat": item.category }), _jsx("b", { children: t(categoryLabelKey(item.category)) }), _jsx("span", { className: css.source, title: from, children: from }), repeats > 1 && _jsxs("span", { className: css.badge, children: ["\u00D7", repeats] }), _jsx("span", { className: css.number, children: fmt(item.chars) })] }), item.isError && _jsx("div", { className: css.errorNote, children: t('lens.errors') }), _jsx(RewriteMeta, { item: item, onOpenRequest: onOpenRequest, t: t }), open ? _jsxs("div", { className: css.messageFull, children: [_jsx("div", { className: css.fullLabel, children: item.sourceTurn === undefined ? '' : t('lens.sourceTurn', { turn: String(item.sourceTurn) }) }), _jsx(ItemFull, { item: item })] }) : _jsx("div", { className: css.messageBody, onClick: () => setOpen(true), children: body })] });
221
+ }
222
+ export function Exchange({ request, previousResponseNumber, onOpenRequest, t }) {
223
+ const duplicates = duplicateStats([...request.segments.system, ...request.segments.history, ...request.segments.added]);
224
+ return _jsxs("section", { "aria-label": t('lens.exchange'), children: [_jsx("h3", { className: css.exchangeTitle, children: t('lens.exchange') }), _jsxs("div", { className: css.exchange, children: [_jsxs("div", { className: css.column, children: [_jsxs("div", { className: css.columnLabel, children: [t(request.compaction?.inputPartial ? 'compaction.region' : 'lens.newInput'), " \u00B7 ", request.segments.added.length] }), request.segments.added.map((item, i) => _jsx(InputCard, { item: item, onOpenRequest: onOpenRequest, repeats: duplicates.counts.get(item.full) ?? 0, t: t, from: item.category === 'assistant' && previousResponseNumber !== null ? t('row.prevResponse', { seq: String(previousResponseNumber) }) : item.category === 'tool-result' && item.sourceRequestNumber !== undefined ? `${item.source} · ${t('row.callResult', { seq: String(item.sourceRequestNumber) })}` : item.source }, i))] }), _jsx("div", { className: css.arrow, "aria-hidden": "true", children: "\u2192" }), _jsxs("div", { className: css.column, role: "region", "aria-label": t('out.title'), children: [_jsxs("div", { className: css.columnLabel, children: [_jsx("span", { children: t('out.title') }), _jsxs("span", { children: [request.usage ? fmt(request.usage.outputTokens) : '—', " tok"] })] }), _jsx(OutputBlock, { request: request, t: t })] })] })] });
225
+ }
@@ -0,0 +1,17 @@
1
+ import { type ReactNode } from 'react';
2
+ import type { AgentScopeOverview, RequestConfig, TurnTiming } from '../types.ts';
3
+ import type { InspectorTabProps } from './contract.ts';
4
+ type T = InspectorTabProps['t'];
5
+ export interface TimingViewProps {
6
+ readonly timing: TurnTiming | null;
7
+ readonly scopes: readonly AgentScopeOverview[];
8
+ readonly filterScopeId: string | undefined;
9
+ readonly filterIsSubagent: boolean;
10
+ readonly config: RequestConfig | undefined;
11
+ readonly onOpenRequest: (seq: number, scopeId: string) => void;
12
+ readonly onSelectScope?: (scopeId: string) => void;
13
+ readonly t: T;
14
+ }
15
+ export declare function TimingView({ timing, scopes, filterScopeId, onOpenRequest, onSelectScope, t, }: TimingViewProps): ReactNode;
16
+ export {};
17
+ //# sourceMappingURL=TimingView.d.ts.map
@@ -0,0 +1,98 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Fragment, useState } from 'react';
3
+ import { requestNumberText, cachePct, fmtK, fmtSpan, inputTotal, summaryText, TIMING_KINDS, } from "./aggregate.js";
4
+ import { scopeTiming } from "./timing.js";
5
+ import css from './TimingView.module.css';
6
+ import { ScopeConfigs } from "./ConfigFacts.js";
7
+ /** All agents have separate lanes; cards and tables always describe the selected agent. */
8
+ /** Stretches of one lane's window with no activity, tagged by where they sit. */
9
+ function idleSpans(lanes, from, to) {
10
+ const out = [];
11
+ let cursor = from;
12
+ for (const lane of [...lanes].sort((a, b) => a.startedAt - b.startedAt)) {
13
+ if (lane.startedAt > cursor)
14
+ out.push({ from: cursor, to: Math.min(lane.startedAt, to), head: cursor === from, tail: false });
15
+ cursor = Math.max(cursor, lane.startedAt + lane.durationMs);
16
+ }
17
+ if (cursor < to)
18
+ out.push({ from: cursor, to, head: lanes.length === 0, tail: lanes.length > 0 });
19
+ return out.filter((g) => g.to > g.from);
20
+ }
21
+ /** Each non-tool row's timeline color, so the table and the timeline legend read alike. */
22
+ const NON_BUCKET = { wait: 'wait', text: 'text', direct: 'reasoning', slack: 'other' };
23
+ export function TimingView({ timing, scopes, filterScopeId, onOpenRequest, onSelectScope, t, }) {
24
+ const [sort, setSort] = useState('total');
25
+ const [open, setOpen] = useState(new Set(['wait']));
26
+ const scope = scopes.find((s) => s.scopeId === (filterScopeId ?? 'main')) ?? scopes[0];
27
+ if (!timing || !scope)
28
+ return _jsx("div", { role: "status", children: t('state.loading') });
29
+ const requestBySeq = new Map(scopes.flatMap(s => s.requests).map(r => [r.seq, r]));
30
+ const numberOf = (seq) => requestNumberText(requestBySeq.get(seq));
31
+ const data = scopeTiming(timing, scope);
32
+ const rows = [...data.tools].sort((a, b) => b[sort] - a[sort]);
33
+ const keys = ['calls', 'errors', 'think', 'args', 'exec', 'total'];
34
+ const nonKeys = ['wait', 'text', 'direct', 'slack'];
35
+ const totalTools = rows.reduce((n, r) => n + r.total, 0);
36
+ const start = timing.startedAt ?? Math.min(...scopes.map((s) => s.startedAt));
37
+ const end = Math.max(start + timing.totalMs, ...timing.lanes.map((l) => l.startedAt + l.durationMs));
38
+ const span = Math.max(1, end - start);
39
+ /** Idle time, split by where it sits (rows only when present). */
40
+ const idleRows = [['lens.gapsHead', data.gapsHead], ['lens.gaps', data.gapsBetween], ['lens.gapsTail', data.gapsTail]]
41
+ .filter(([, ms]) => ms >= 50);
42
+ /** The non-tool table's share cue scales to its largest row. */
43
+ const nonMax = Math.max(1, ...nonKeys.map((k) => [...data.non[k].values()].reduce((n, ms) => n + ms, 0)), ...idleRows.map(([, ms]) => ms));
44
+ return (_jsxs("div", { className: css.scroll, children: [_jsx(ScopeConfigs, { scope: scope, onOpenRequest: onOpenRequest, t: t }), _jsx("div", { className: css.timeSummary, children: [
45
+ [
46
+ scope.kind === 'main' ? t('time.total') : t('time.totalScope'),
47
+ data.total,
48
+ t('lens.equation'),
49
+ ],
50
+ [
51
+ t('lens.model'),
52
+ data.model,
53
+ `${scope.requests.length} · ${t('time.bucket.reasoning')} ${fmtSpan(data.think)}`,
54
+ ],
55
+ [
56
+ t('lens.tools'),
57
+ data.exec,
58
+ `${t('lens.calls')} ${rows.reduce((n, r) => n + r.calls, 0)} · ${t('lens.errors')} ${rows.reduce((n, r) => n + r.errors, 0)}`,
59
+ ],
60
+ [t('lens.other'), data.gaps, [
61
+ ...data.gapsHead >= 50 ? [`${t('lens.gapsHead')} ${fmtSpan(data.gapsHead)}`] : [],
62
+ ...data.gapsBetween >= 50 ? [`${t('lens.gaps')} ${fmtSpan(data.gapsBetween)}`] : [],
63
+ ...data.gapsTail >= 50 ? [`${t('lens.gapsTail')} ${fmtSpan(data.gapsTail)}`] : [],
64
+ ].join(' · ') || t('lens.gaps')],
65
+ ].map(([label, value, sub]) => (_jsxs("div", { className: css.cell, children: [_jsx("div", { className: css.k, children: label }), _jsx("div", { className: css.v, children: fmtSpan(Number(value)) }), _jsx("div", { className: css.k, children: sub })] }, String(label)))) }), scopes.some(s => s.requests.some(r => r.timingApproximate)) && _jsx("div", { className: css.stateNote, children: t('compaction.timingHint') }), data.overlap > 0 && (_jsx("div", { className: css.stateNote, children: t('lens.overlap') })), scopes.length > 1 && (_jsx("div", { className: css.stateNote, children: t('lens.nested') })), _jsxs("section", { children: [_jsx("h3", { children: t('lens.timeline') }), _jsxs("div", { className: css.legend, children: [TIMING_KINDS.map((k) => (_jsxs("span", { children: [_jsx("i", { className: css.dot, "data-bucket": k }), " ", t(`time.bucket.${k}`)] }, k))), _jsxs("span", { children: [_jsx("i", { className: css.dotIdle }), " ", t('time.idle')] })] }), scopes.map((s) => (_jsxs("div", { className: css.lane, "data-dim": scopes.length > 1 ? String(s.scopeId !== scope.scopeId) : undefined, children: [_jsx("button", { type: "button", className: css.laneName, onClick: () => onSelectScope?.(s.scopeId), children: s.kind === 'main' ? t('lens.main') : (s.label ?? s.scopeId) }), _jsxs("div", { className: css.timeline, children: [idleSpans(timing.lanes.filter((l) => l.scopeId === s.scopeId), s.kind === 'main' ? start : s.startedAt, s.kind === 'main' ? end : (s.endedAt ?? end))
66
+ .filter((g) => g.to - g.from >= Math.max(200, span * 0.01))
67
+ .map((g) => {
68
+ const where = g.head ? t('lens.gapsHead') : g.tail ? t('lens.gapsTail') : t('lens.gaps');
69
+ const label = `${t('time.idle')} · ${where} ${fmtSpan(g.to - g.from)}`;
70
+ return _jsx("span", { className: css.idle, title: label, "aria-label": label, style: { left: `${((g.from - start) / span) * 100}%`, width: `${((g.to - g.from) / span) * 100}%` } }, `idle-${g.from}`);
71
+ }), timing.lanes
72
+ .filter((l) => l.scopeId === s.scopeId)
73
+ .map((l, i) => (_jsx("button", { type: "button", className: css.blk, "data-bucket": l.kind, title: `#${numberOf(l.requestSeq)} ${l.tool ?? t(`time.bucket.${l.kind}`)} · ${fmtSpan(l.durationMs)}`, "aria-label": `#${numberOf(l.requestSeq)} ${t(`time.bucket.${l.kind}`)}`, style: {
74
+ left: `${((l.startedAt - start) / span) * 100}%`,
75
+ width: `${Math.max(0.3, (l.durationMs / span) * 100)}%`,
76
+ }, onClick: () => onOpenRequest(l.requestSeq, l.scopeId) }, i)))] })] }, s.scopeId))), _jsxs("div", { className: css.tlScale, children: [_jsx("span", { children: "0" }), _jsx("span", { children: fmtSpan(span / 2) }), _jsx("span", { children: fmtSpan(span) })] })] }), _jsxs("section", { children: [_jsxs("h3", { className: css.titleRow, children: [t('lens.toolTime'), _jsx("abbr", { className: css.info, title: t('lens.attribution'), children: "i" })] }), _jsx("div", { className: css.tableScroll, children: _jsxs("table", { className: css.tt, children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { children: t('lens.tool') }), keys.map((k) => (_jsx("th", { "aria-sort": sort === k ? 'descending' : 'none', "data-sorted": sort === k, children: _jsxs("button", { type: "button", onClick: () => setSort(k), children: [t(`lens.${k}`), sort === k ? ' ↓' : ''] }) }, k)))] }) }), _jsx("tbody", { children: rows.map((r) => (_jsxs("tr", { children: [_jsx("td", { children: r.tool }), keys.map((k) => (_jsx("td", { "data-zero": r[k] === 0 || (k !== 'calls' && k !== 'errors' && r[k] < 50), "data-error": k === 'errors' && r[k] > 0, "data-total": k === 'total', style: k === 'total' ? { ['--share']: String(r.total / Math.max(1, ...rows.map((v) => v.total))) } : undefined, children: _jsx("span", { children: k === 'calls' || k === 'errors' ? r[k] : fmtSpan(r[k]) }) }, k)))] }, r.tool))) }), _jsx("tfoot", { children: _jsxs("tr", { children: [_jsx("td", { children: t('lens.total') }), keys.map((k) => {
77
+ const sum = rows.reduce((n, r) => n + r[k], 0);
78
+ return _jsx("td", { "data-zero": sum === 0, children: k === 'calls' || k === 'errors' ? sum : fmtSpan(sum) }, k);
79
+ })] }) })] }) })] }), _jsxs("section", { children: [_jsx("h3", { children: t('lens.nonTool') }), _jsx("div", { className: css.tableScroll, children: _jsxs("table", { className: css.tt, children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { children: t('lens.type') }), _jsx("th", { children: t('lens.calls') }), _jsx("th", { children: t('lens.longest') }), _jsx("th", { "data-sorted": "true", children: t('lens.total') })] }) }), _jsxs("tbody", { children: [_jsx("tr", { className: css.ttGroup, children: _jsx("td", { colSpan: 4, children: t('time.groupModel') }) }), nonKeys.map((k) => {
80
+ const items = [...data.non[k]]
81
+ .filter(([, ms]) => ms > 0)
82
+ .sort((a, b) => b[1] - a[1]);
83
+ if (!items.length)
84
+ return null;
85
+ const sum = items.reduce((n, [, ms]) => n + ms, 0);
86
+ return (_jsxs(Fragment, { children: [_jsxs("tr", { "data-open": open.has(k), children: [_jsx("td", { children: _jsxs("button", { type: "button", className: css.ttToggle, "aria-expanded": open.has(k), onClick: () => setOpen((p) => {
87
+ const n = new Set(p);
88
+ if (n.has(k))
89
+ n.delete(k);
90
+ else
91
+ n.add(k);
92
+ return n;
93
+ }), children: [_jsx("span", { className: css.ttChev, "aria-hidden": "true", children: "\u203A" }), _jsx("i", { className: css.ttSwatch, "data-bucket": NON_BUCKET[k] }), t(`lens.${k}`)] }) }), _jsx("td", { children: items.length }), _jsxs("td", { children: ["#", numberOf(items[0][0]), " ", fmtSpan(items[0][1])] }), _jsx("td", { "data-total": "true", "data-zero": sum < 50, style: { ['--share']: String(sum / nonMax) }, children: _jsx("span", { children: fmtSpan(sum) }) })] }), open.has(k) && items.map(([seq, ms], i) => {
94
+ const r = scope.requests.find((r) => r.seq === seq);
95
+ return (_jsxs("tr", { className: css.ttDetail, "data-last": i === items.length - 1, children: [_jsx("td", { children: _jsxs("button", { type: "button", onClick: () => onOpenRequest(seq, scope.scopeId), children: [_jsxs("span", { className: css.ttReq, children: ["#", numberOf(seq)] }), summaryText(r.responseSummary, t('resp.directAnswer'), t('compaction.title'), t('resp.thinking'))] }) }), _jsx("td", {}), _jsx("td", { className: css.ttMeta, children: k === 'wait' && r.usage ? `${t('lens.hit')} ${cachePct(r.usage) ?? '—'}% · ${fmtK(inputTotal(r.usage))}` : '' }), _jsx("td", { children: fmtSpan(ms) })] }, seq));
96
+ })] }, k));
97
+ }), idleRows.length > 0 && _jsx("tr", { className: css.ttGroup, children: _jsx("td", { colSpan: 4, children: t('time.idle') }) }), idleRows.map(([key, ms]) => (_jsxs("tr", { children: [_jsx("td", { children: _jsxs("span", { className: css.ttToggle, children: [_jsx("span", { className: css.ttChev, "aria-hidden": "true" }), _jsx("i", { className: css.ttSwatch, "data-bucket": "idle" }), t(key)] }) }), _jsx("td", { "data-zero": "true", children: "\u2014" }), _jsx("td", { "data-zero": "true", children: "\u2014" }), _jsx("td", { "data-total": "true", style: { ['--share']: String(ms / nonMax) }, children: _jsx("span", { children: fmtSpan(ms) }) })] }, key)))] }), _jsx("tfoot", { children: _jsxs("tr", { children: [_jsx("td", { children: t('lens.total') }), _jsx("td", {}), _jsx("td", {}), _jsx("td", { children: fmtSpan(data.total - totalTools) })] }) })] }) })] })] }));
98
+ }