@cellarnode/ui 0.201.1 → 0.203.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 (159) hide show
  1. package/dist/agent/composer.d.ts +6 -1
  2. package/dist/agent/composer.d.ts.map +1 -1
  3. package/dist/agent/composer.js +43 -9
  4. package/dist/agent/composer.js.map +1 -1
  5. package/dist/agent/conversation.d.ts +20 -2
  6. package/dist/agent/conversation.d.ts.map +1 -1
  7. package/dist/agent/conversation.js +39 -5
  8. package/dist/agent/conversation.js.map +1 -1
  9. package/dist/agent/copy.d.ts +7 -0
  10. package/dist/agent/copy.d.ts.map +1 -1
  11. package/dist/agent/copy.js +9 -0
  12. package/dist/agent/copy.js.map +1 -1
  13. package/dist/agent/fixtures.d.ts +34 -0
  14. package/dist/agent/fixtures.d.ts.map +1 -1
  15. package/dist/agent/fixtures.js +83 -0
  16. package/dist/agent/fixtures.js.map +1 -1
  17. package/dist/agent/index.d.ts +18 -1
  18. package/dist/agent/index.d.ts.map +1 -1
  19. package/dist/agent/index.js +18 -1
  20. package/dist/agent/index.js.map +1 -1
  21. package/dist/agent/markdown.d.ts +64 -0
  22. package/dist/agent/markdown.d.ts.map +1 -0
  23. package/dist/agent/markdown.js +338 -0
  24. package/dist/agent/markdown.js.map +1 -0
  25. package/dist/agent/masked-value.d.ts +1 -0
  26. package/dist/agent/masked-value.d.ts.map +1 -1
  27. package/dist/agent/masked-value.js +4 -1
  28. package/dist/agent/masked-value.js.map +1 -1
  29. package/dist/agent/message.d.ts +26 -6
  30. package/dist/agent/message.d.ts.map +1 -1
  31. package/dist/agent/message.js +47 -11
  32. package/dist/agent/message.js.map +1 -1
  33. package/dist/agent/panel-frame.js +1 -1
  34. package/dist/agent/panel-frame.js.map +1 -1
  35. package/dist/agent/role-cards/card-kit.d.ts +111 -0
  36. package/dist/agent/role-cards/card-kit.d.ts.map +1 -0
  37. package/dist/agent/role-cards/card-kit.js +68 -0
  38. package/dist/agent/role-cards/card-kit.js.map +1 -0
  39. package/dist/agent/role-cards/data-table-card.d.ts +80 -0
  40. package/dist/agent/role-cards/data-table-card.d.ts.map +1 -0
  41. package/dist/agent/role-cards/data-table-card.js +214 -0
  42. package/dist/agent/role-cards/data-table-card.js.map +1 -0
  43. package/dist/agent/role-cards/fixtures.d.ts +124 -0
  44. package/dist/agent/role-cards/fixtures.d.ts.map +1 -0
  45. package/dist/agent/role-cards/fixtures.js +401 -0
  46. package/dist/agent/role-cards/fixtures.js.map +1 -0
  47. package/dist/agent/role-cards/host-contract.d.ts +214 -0
  48. package/dist/agent/role-cards/host-contract.d.ts.map +1 -0
  49. package/dist/agent/role-cards/host-contract.js +348 -0
  50. package/dist/agent/role-cards/host-contract.js.map +1 -0
  51. package/dist/agent/role-cards/inspector-drawer.d.ts +42 -0
  52. package/dist/agent/role-cards/inspector-drawer.d.ts.map +1 -0
  53. package/dist/agent/role-cards/inspector-drawer.js +68 -0
  54. package/dist/agent/role-cards/inspector-drawer.js.map +1 -0
  55. package/dist/agent/role-cards/inspector-fixtures.d.ts +84 -0
  56. package/dist/agent/role-cards/inspector-fixtures.d.ts.map +1 -0
  57. package/dist/agent/role-cards/inspector-fixtures.js +162 -0
  58. package/dist/agent/role-cards/inspector-fixtures.js.map +1 -0
  59. package/dist/agent/role-cards/inspector-record.d.ts +109 -0
  60. package/dist/agent/role-cards/inspector-record.d.ts.map +1 -0
  61. package/dist/agent/role-cards/inspector-record.js +346 -0
  62. package/dist/agent/role-cards/inspector-record.js.map +1 -0
  63. package/dist/agent/role-cards/inspector-view.d.ts +137 -0
  64. package/dist/agent/role-cards/inspector-view.d.ts.map +1 -0
  65. package/dist/agent/role-cards/inspector-view.js +261 -0
  66. package/dist/agent/role-cards/inspector-view.js.map +1 -0
  67. package/dist/agent/role-cards/match-card.d.ts +21 -0
  68. package/dist/agent/role-cards/match-card.d.ts.map +1 -0
  69. package/dist/agent/role-cards/match-card.js +30 -0
  70. package/dist/agent/role-cards/match-card.js.map +1 -0
  71. package/dist/agent/role-cards/offer-card.d.ts +27 -0
  72. package/dist/agent/role-cards/offer-card.d.ts.map +1 -0
  73. package/dist/agent/role-cards/offer-card.js +68 -0
  74. package/dist/agent/role-cards/offer-card.js.map +1 -0
  75. package/dist/agent/role-cards/opportunity-card.d.ts +26 -0
  76. package/dist/agent/role-cards/opportunity-card.d.ts.map +1 -0
  77. package/dist/agent/role-cards/opportunity-card.js +50 -0
  78. package/dist/agent/role-cards/opportunity-card.js.map +1 -0
  79. package/dist/agent/role-cards/product-card.d.ts +32 -0
  80. package/dist/agent/role-cards/product-card.d.ts.map +1 -0
  81. package/dist/agent/role-cards/product-card.js +50 -0
  82. package/dist/agent/role-cards/product-card.js.map +1 -0
  83. package/dist/agent/role-cards/proposal-diff.d.ts +38 -0
  84. package/dist/agent/role-cards/proposal-diff.d.ts.map +1 -0
  85. package/dist/agent/role-cards/proposal-diff.js +74 -0
  86. package/dist/agent/role-cards/proposal-diff.js.map +1 -0
  87. package/dist/agent/role-cards/registry.d.ts +43 -0
  88. package/dist/agent/role-cards/registry.d.ts.map +1 -0
  89. package/dist/agent/role-cards/registry.js +46 -0
  90. package/dist/agent/role-cards/registry.js.map +1 -0
  91. package/dist/agent/role-cards/score-meter.d.ts +12 -0
  92. package/dist/agent/role-cards/score-meter.d.ts.map +1 -0
  93. package/dist/agent/role-cards/score-meter.js +17 -0
  94. package/dist/agent/role-cards/score-meter.js.map +1 -0
  95. package/dist/agent/role-cards/surface-links.d.ts +54 -0
  96. package/dist/agent/role-cards/surface-links.d.ts.map +1 -0
  97. package/dist/agent/role-cards/surface-links.js +149 -0
  98. package/dist/agent/role-cards/surface-links.js.map +1 -0
  99. package/dist/agent/role-cards/text-safety.d.ts +27 -0
  100. package/dist/agent/role-cards/text-safety.d.ts.map +1 -0
  101. package/dist/agent/role-cards/text-safety.js +77 -0
  102. package/dist/agent/role-cards/text-safety.js.map +1 -0
  103. package/dist/agent/schema.d.ts +10 -0
  104. package/dist/agent/schema.d.ts.map +1 -1
  105. package/dist/agent/schema.js +31 -3
  106. package/dist/agent/schema.js.map +1 -1
  107. package/dist/agent/states.d.ts +1 -0
  108. package/dist/agent/states.d.ts.map +1 -1
  109. package/dist/agent/states.js +1 -1
  110. package/dist/agent/states.js.map +1 -1
  111. package/dist/agent/thinking.d.ts +13 -4
  112. package/dist/agent/thinking.d.ts.map +1 -1
  113. package/dist/agent/thinking.js +15 -6
  114. package/dist/agent/thinking.js.map +1 -1
  115. package/dist/agent/thread-list.d.ts +45 -2
  116. package/dist/agent/thread-list.d.ts.map +1 -1
  117. package/dist/agent/thread-list.js +175 -8
  118. package/dist/agent/thread-list.js.map +1 -1
  119. package/dist/agent/thread-title.d.ts +46 -0
  120. package/dist/agent/thread-title.d.ts.map +1 -0
  121. package/dist/agent/thread-title.js +99 -0
  122. package/dist/agent/thread-title.js.map +1 -0
  123. package/dist/agent/tool-activity-model.d.ts +136 -0
  124. package/dist/agent/tool-activity-model.d.ts.map +1 -0
  125. package/dist/agent/tool-activity-model.js +288 -0
  126. package/dist/agent/tool-activity-model.js.map +1 -0
  127. package/dist/agent/tool-activity.d.ts +61 -0
  128. package/dist/agent/tool-activity.d.ts.map +1 -0
  129. package/dist/agent/tool-activity.js +199 -0
  130. package/dist/agent/tool-activity.js.map +1 -0
  131. package/dist/agent/tool-call-card.d.ts +11 -1
  132. package/dist/agent/tool-call-card.d.ts.map +1 -1
  133. package/dist/agent/tool-call-card.js +19 -4
  134. package/dist/agent/tool-call-card.js.map +1 -1
  135. package/dist/agent/ui.js +1 -1
  136. package/dist/agent/ui.js.map +1 -1
  137. package/dist/agent/use-motion.d.ts +15 -0
  138. package/dist/agent/use-motion.d.ts.map +1 -0
  139. package/dist/agent/use-motion.js +52 -0
  140. package/dist/agent/use-motion.js.map +1 -0
  141. package/dist/chat/use-stick-to-bottom.d.ts +5 -0
  142. package/dist/chat/use-stick-to-bottom.d.ts.map +1 -1
  143. package/dist/chat/use-stick-to-bottom.js +11 -1
  144. package/dist/chat/use-stick-to-bottom.js.map +1 -1
  145. package/dist/components/sheet.d.ts +3 -1
  146. package/dist/components/sheet.d.ts.map +1 -1
  147. package/dist/components/sheet.js +2 -2
  148. package/dist/components/sheet.js.map +1 -1
  149. package/dist/portfolio-import/import-job-panels.d.ts +6 -0
  150. package/dist/portfolio-import/import-job-panels.d.ts.map +1 -1
  151. package/dist/portfolio-import/import-job-panels.js +11 -4
  152. package/dist/portfolio-import/import-job-panels.js.map +1 -1
  153. package/dist/portfolio-import/job-error-copy.d.ts +62 -0
  154. package/dist/portfolio-import/job-error-copy.d.ts.map +1 -0
  155. package/dist/portfolio-import/job-error-copy.js +78 -0
  156. package/dist/portfolio-import/job-error-copy.js.map +1 -0
  157. package/dist/realtime/organisation-events/schemas.d.ts +2 -2
  158. package/package.json +4 -1
  159. package/src/theme/tokens.css +66 -0
@@ -0,0 +1,162 @@
1
+ import { fixtureRecords } from "./fixtures.js";
2
+ /** @internal Story and test fixtures for the inspector drawer (CEL-2487): the FULL records a host would fetch for the role cards. */
3
+ const MASKED_EMAIL = { masked: true, display: "a***@nordicwine.example" };
4
+ /**
5
+ * The records behind the fixture cards' `detail` pointers, keyed `kind:id`. They extend `fixtureRecords`, so a story's proposal
6
+ * Apply (which re-fetches by `kind:id`) and the drawer's record view read the same server truth.
7
+ */
8
+ export const inspectorRecords = {
9
+ "offer:11111111-1111-4111-8111-111111111111": {
10
+ ...fixtureRecords["offer:11111111-1111-4111-8111-111111111111"],
11
+ status: "in_negotiation",
12
+ awaiting: "you",
13
+ round: 2,
14
+ product: "Riesling Spatlese 2022",
15
+ counterparty: "Nordic Wine Imports AB",
16
+ opportunity: { name: "Nordic red wine tender, autumn 2026", market: "Sweden" },
17
+ terms: {
18
+ price: "9.40",
19
+ currency: "EUR",
20
+ priceType: "per bottle",
21
+ volume: "6,000 bottles",
22
+ incoterm: "DAP Stockholm",
23
+ paymentTerms: null,
24
+ validThrough: "2026-12-01",
25
+ },
26
+ contact: { name: "Anna Berg", email: MASKED_EMAIL },
27
+ attachments: ["Specification sheet.pdf", "Organic certificate.pdf"],
28
+ rounds: [
29
+ { round: 1, by: "importer", price: "9.80", note: "Opening offer" },
30
+ { round: 2, by: "producer", price: "9.40", note: "Counter with a volume discount" },
31
+ ],
32
+ counts: { messages: 14, attachments: 2 },
33
+ createdAt: "2026-09-14T09:12:00Z",
34
+ },
35
+ "variant:v1": {
36
+ ...fixtureRecords["variant:v1"],
37
+ vintage: 2022,
38
+ volume: "750 ml",
39
+ packaging: "bottle",
40
+ certifications: ["organic", "sustainable"],
41
+ organic: true,
42
+ readiness: {
43
+ percent: 80,
44
+ canApprove: false,
45
+ blockers: 1,
46
+ acknowledgements: 1,
47
+ recommendations: 2,
48
+ },
49
+ description: "Off-dry Riesling from steep slate slopes, hand harvested, aged on the lees for six months.",
50
+ },
51
+ "opportunity:o1": {
52
+ name: "Red wine for the Nordic monopoly, autumn 2026",
53
+ buyer: "Nordic Monopoly",
54
+ market: "Sweden",
55
+ status: "open",
56
+ deadline: "2026-11-30",
57
+ volume: "20,000 bottles",
58
+ priceRange: { min: "8", max: "11", currency: "EUR" },
59
+ requirements: ["organic", "screw cap", "750 ml", "vintage 2023 or newer"],
60
+ contact: { name: "Buyer desk", email: MASKED_EMAIL },
61
+ },
62
+ "match:m-1": {
63
+ score: 4.5,
64
+ status: "new",
65
+ explanation: "Category, volume and price all sit inside what the buyer asked for.",
66
+ product: { name: "Riesling Spatlese 2022", vintage: 2022 },
67
+ opportunity: { name: "Nordic red wine tender", buyer: "Buyer AB", country: "SE" },
68
+ fit: { category: true, volume: true, price: true, certificates: true, packaging: false },
69
+ declined: false,
70
+ },
71
+ };
72
+ /** The `fetchRecord` a story or test host passes: the same records as `inspectorRecords`, `null` for an unknown pointer. */
73
+ export const inspectorFetchRecord = async (kind, id) => inspectorRecords[`${kind}:${id}`] ?? null;
74
+ /** A record with many fields and nesting, to prove the drawer scrolls and the caps hold. */
75
+ export const largeRecord = {
76
+ ...Object.fromEntries(Array.from({ length: 24 }, (_, i) => [`field_${i + 1}`, `Value ${i + 1}`])),
77
+ history: Array.from({ length: 30 }, (_, i) => ({
78
+ step: i + 1,
79
+ by: "system",
80
+ note: `Change ${i + 1}`,
81
+ })),
82
+ tags: Array.from({ length: 30 }, (_, i) => `tag-${i + 1}`),
83
+ nested: { level1: { level2: { level3: { level4: "too deep to show" } } } },
84
+ };
85
+ /** A `ready` record state for the pointer it was fetched for; the record defaults to the fixture behind that pointer. */
86
+ export const readyFor = (kind, id, record = inspectorRecords[`${kind}:${id}`]) => ({ status: "ready", for: { kind, id }, record });
87
+ /** Long, unbroken and hostile text in every place a record puts text: for the 320px story and the overflow checks. */
88
+ export const longTextRecord = {
89
+ name: "Gewurztraminer Grand Cru Vendanges Tardives Cuvee Reserve de la Famille 2019 magnum in individual wooden gift box",
90
+ reference: `REF-${"0123456789".repeat(14)}`,
91
+ notes: `Ships from the cellar door ${"supercalifragilistic".repeat(8)} and then some more words to wrap.`,
92
+ a_really_long_field_name_that_does_not_fit_on_one_line_of_a_narrow_phone_screen: "value",
93
+ tags: [
94
+ "certified-organic-by-the-european-union-and-the-national-authority",
95
+ "a".repeat(200),
96
+ "short",
97
+ ],
98
+ contact: {
99
+ name: "Internationale Weinhandelsgesellschaft fur Spezialitaten und Raritaten mbH",
100
+ email: { masked: true, display: `a***@${"very-long-domain-name-".repeat(6)}example` },
101
+ },
102
+ spoof: "invoice \u202Etxt.exe\u202C \u200Bpaid",
103
+ };
104
+ /**
105
+ * Records shaped like the V2 public DTOs `fetchRecord` will really return (backend `features/offers`, `opportunities` and `matching`
106
+ * `public-response-schemas.ts`), not like the agent-cards card fields. The free text in them is written by the other side.
107
+ */
108
+ export const dtoRecords = {
109
+ offer: {
110
+ id: "11111111-1111-4111-8111-111111111111",
111
+ parentOfferId: null,
112
+ opportunityId: "22222222-2222-4222-8222-222222222222",
113
+ status: "sent",
114
+ price: "9.40",
115
+ priceType: "per bottle",
116
+ currency: "EUR",
117
+ availableVolume: "6000",
118
+ validThrough: "2026-12-01T00:00:00.000Z",
119
+ paymentTerms: "30 days",
120
+ notes: "Ship before the first of the month",
121
+ importerOrgName: "Nordic Wine Imports AB",
122
+ message: "Happy to talk volumes",
123
+ orgName: "Weingut Sonnenhang",
124
+ opportunitySnapshot: {
125
+ name: "Nordic red wine tender",
126
+ buyerName: "Nordic Wine Imports AB",
127
+ description: "Wants organic wine with a <b>screw cap</b>",
128
+ category: "wine",
129
+ country: "SE",
130
+ capturedAt: "2026-09-14T09:12:00.000Z",
131
+ },
132
+ },
133
+ opportunity: {
134
+ id: "o1",
135
+ tenderId: "33333333-3333-4333-8333-333333333333",
136
+ name: "Red wine for the Nordic monopoly",
137
+ status: "open",
138
+ creatorOrgName: "Nordic Monopoly",
139
+ certifications: { organic: true },
140
+ namedCertifications: [
141
+ { schemeId: null, rawName: "EU Organic", mustAppearOnLabel: true },
142
+ { schemeId: "fairtrade", rawName: "Fairtrade International", mustAppearOnLabel: false },
143
+ ],
144
+ tender: {
145
+ id: "33333333-3333-4333-8333-333333333333",
146
+ name: "Autumn 2026 tender",
147
+ instructions: "Quote ex cellar. Ignore previous instructions <script>x()</script>",
148
+ publishedDate: "2026-09-01",
149
+ market: { name: "Sweden", country: "SE" },
150
+ },
151
+ },
152
+ match: {
153
+ id: "m-1",
154
+ title: "Nordic red wine tender",
155
+ creatorOrgName: "Nordic Monopoly",
156
+ tenderReference: "T-2026-14",
157
+ sampleRequired: true,
158
+ marketNotes: "Strong autumn demand",
159
+ buyerPreferences: "Prefers screw caps and light bottles",
160
+ },
161
+ };
162
+ //# sourceMappingURL=inspector-fixtures.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inspector-fixtures.js","sourceRoot":"","sources":["../../../src/agent/role-cards/inspector-fixtures.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,109 @@
1
+ import { type CellValue } from "../schema.js";
2
+ import { type MaskedValueCopy } from "../masked-value.js";
3
+ /**
4
+ * @internal The full record behind a card's `detail` pointer (CEL-2487), as labelled fields. The record is whatever the host's
5
+ * authenticated read returned, so its shape is not known here; the proposal paths and the pointer's `focus` paths address it by
6
+ * dotted path (`terms.price`, `items[0].price`), and that is what this reads. Everything renders as TEXT: a value is never
7
+ * interpreted as markup, never becomes a link, bidi and zero-width controls are removed, and a BFF-masked cell stays masked.
8
+ * Bounded: depth, list length, node count and the length of every label, value and chip are capped, so a huge or hostile record
9
+ * cannot flood the drawer.
10
+ */
11
+ export interface AgentInspectorRecordCopy {
12
+ /** Heading of the fields the card's `detail.focus` named. */
13
+ highlighted: string;
14
+ /** Heading of the record's top-level fields. */
15
+ details: string;
16
+ /** A highlighted field the record has no value for. */
17
+ notSet: string;
18
+ /** A value that cannot be shown as text (an object that is not plain data). */
19
+ notShown: string;
20
+ yes: string;
21
+ no: string;
22
+ /** A group that comes from a list of objects: `{n}` is its place in the list. */
23
+ item: string;
24
+ /** A highlighted field's label: `{parent}` is the group it sits in and `{child}` the field ("Terms / Price"). */
25
+ trail: string;
26
+ /** How text written by someone else is set off: `{text}` is the text. */
27
+ quote: string;
28
+ /** Shown when a cap hid part of a very large record. */
29
+ truncated: string;
30
+ }
31
+ export declare const DEFAULT_INSPECTOR_RECORD_COPY: AgentInspectorRecordCopy;
32
+ /**
33
+ * `dataLabel`: the label was made from a key of the record (humanised), not supplied by the host or by the view's own copy. Inside
34
+ * text someone else wrote, such a key is theirs too, and renders quoted.
35
+ */
36
+ export type RecordNode = {
37
+ kind: "field";
38
+ path: string;
39
+ label: string;
40
+ dataLabel: boolean;
41
+ value: CellValue;
42
+ } | {
43
+ kind: "list";
44
+ path: string;
45
+ label: string;
46
+ dataLabel: boolean;
47
+ items: CellValue[];
48
+ } | {
49
+ kind: "group";
50
+ path: string;
51
+ label: string;
52
+ dataLabel: boolean;
53
+ nodes: RecordNode[];
54
+ };
55
+ export interface RecordView {
56
+ /** The fields the card pointed at, each labelled with its trail ("Terms / Price"). */
57
+ highlighted: RecordNode[];
58
+ /** Everything else, in the record's own order. */
59
+ nodes: RecordNode[];
60
+ /** Fields a cap kept out. */
61
+ omitted: number;
62
+ /** Nothing to show at all (no highlighted field and no field with a value). */
63
+ isEmpty: boolean;
64
+ }
65
+ export interface BuildRecordViewOptions {
66
+ /** `detail.focus` of the card. Resolved first and on their own budget, so a pointed-at field never falls to a cap. */
67
+ focus?: readonly string[];
68
+ /** Host labels, by dotted path first and by bare key second. Anything else is humanised from the key. */
69
+ labels?: Readonly<Record<string, string>>;
70
+ /** "Item {n}" in the host's language. */
71
+ itemLabel?: (n: number) => string;
72
+ /** "Terms / Price" in the host's language. */
73
+ trail?: (parent: string, child: string) => string;
74
+ /** What shows for an object that is not plain data. */
75
+ notShown?: string;
76
+ /** Paths (as `canonicalPath` spells them) the host keeps out of the view. */
77
+ hidden?: (path: string) => boolean;
78
+ }
79
+ /** `validThrough`, `valid_through` and `valid-through` all read "Valid through"; a short known acronym is upper-cased. */
80
+ export declare function humanizeKey(key: string): string;
81
+ /**
82
+ * Does a record path fall under one of the patterns? A path matches a pattern when the pattern names the path OR any ancestor of
83
+ * it, so `counterparty` covers `counterparty.name` and `rounds[].note` covers `rounds[1].note`. A pattern of one name matches that
84
+ * name at any depth; a longer pattern is anchored at the root, and `[]` stands for any list position (a trailing `[]` also covers the
85
+ * list itself).
86
+ */
87
+ export declare function compilePathMatcher(patterns: readonly string[]): (path: string) => boolean;
88
+ /**
89
+ * The record as labelled nodes. Own enumerable keys only; `__proto__`, `constructor`, `prototype` and keys starting with `_`
90
+ * (internal bookkeeping) are skipped. Objects become groups, a list of scalars a chip list, a list of objects numbered groups, a
91
+ * Date its ISO text, anything that is not plain data a placeholder. Empty values are dropped unless the card's `focus` named them.
92
+ *
93
+ * `focus` paths are resolved FIRST with `lookupPath`, each on its own budget, and kept out of the main walk: the agent's pointer
94
+ * survives a record that is otherwise capped.
95
+ */
96
+ export declare function buildRecordView(record: unknown, { focus, labels, itemLabel, trail, notShown, hidden, }?: BuildRecordViewOptions): RecordView;
97
+ export interface AgentInspectorRecordProps {
98
+ view: RecordView;
99
+ /** Was the text at this path written by someone else? It then renders as quoted text. */
100
+ isThirdParty: (path: string) => boolean;
101
+ /** The host's own display text for a value (a date, a currency), or `undefined` for the default. Never called for a masked cell. */
102
+ formatValue?: (path: string, value: CellValue) => string | undefined;
103
+ copy: AgentInspectorRecordCopy;
104
+ maskedCopy?: Partial<MaskedValueCopy>;
105
+ headingId: (name: string) => string;
106
+ }
107
+ /** The labelled fields of a record: the highlighted ones first, then everything else. Presentational; `buildRecordView` decides what is in it. */
108
+ export declare function AgentInspectorRecord(p: AgentInspectorRecordProps): import("react/jsx-runtime").JSX.Element;
109
+ //# sourceMappingURL=inspector-record.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inspector-record.d.ts","sourceRoot":"","sources":["../../../src/agent/role-cards/inspector-record.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,346 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { Fragment } from "react";
3
+ import { DEFAULT_QUOTE, fmt, quoteText } from "../copy.js";
4
+ import { isMasked } from "../schema.js";
5
+ import { MaskedValue } from "../masked-value.js";
6
+ import { AgentChip, AgentField, AgentFieldGrid } from "./card-kit.js";
7
+ import { lookupPath } from "./host-contract.js";
8
+ import { cleanInline, cleanValue } from "./text-safety.js";
9
+ export const DEFAULT_INSPECTOR_RECORD_COPY = {
10
+ highlighted: "Highlighted by the agent",
11
+ details: "Details",
12
+ notSet: "Not set",
13
+ notShown: "Not shown",
14
+ yes: "Yes",
15
+ no: "No",
16
+ item: "Item {n}",
17
+ trail: "{parent} / {child}",
18
+ quote: DEFAULT_QUOTE,
19
+ truncated: "This record is large. Some fields are not shown.",
20
+ };
21
+ const MAX_DEPTH = 3;
22
+ const MAX_LIST = 20;
23
+ const MAX_NODES = 200;
24
+ const MAX_TEXT = 2000;
25
+ const MAX_CHIP = 120;
26
+ const MAX_LABEL = 120;
27
+ const MAX_MASKED = 200;
28
+ /** A value longer than this takes the whole row. */
29
+ const WIDE_TEXT = 48;
30
+ const UNSAFE_KEYS = new Set(["__proto__", "constructor", "prototype"]);
31
+ const ACRONYMS = new Set(["id", "abv", "vat", "url", "sku", "ean", "gtin", "moq", "iban"]);
32
+ /** `validThrough`, `valid_through` and `valid-through` all read "Valid through"; a short known acronym is upper-cased. */
33
+ export function humanizeKey(key) {
34
+ const words = key
35
+ .replace(/([a-z0-9])([A-Z])/g, "$1 $2")
36
+ .replace(/[_\-\s]+/g, " ")
37
+ .trim()
38
+ .toLowerCase()
39
+ .split(" ")
40
+ .filter(Boolean)
41
+ .map((w) => (ACRONYMS.has(w) ? w.toUpperCase() : w));
42
+ if (words.length === 0)
43
+ return key;
44
+ const first = words[0];
45
+ words[0] = ACRONYMS.has(first)
46
+ ? first.toUpperCase()
47
+ : first.charAt(0).toUpperCase() + first.slice(1);
48
+ return words.join(" ");
49
+ }
50
+ /** `rounds[].note` is `["rounds", "*", "note"]`; `rounds[0].note` is `["rounds", "0", "note"]`. */
51
+ const toSegments = (path) => path
52
+ .replace(/\[(\d*)\]/g, (_, d) => (d === "" ? ".*" : `.${d}`))
53
+ .split(".")
54
+ .filter(Boolean);
55
+ /**
56
+ * Does a record path fall under one of the patterns? A path matches a pattern when the pattern names the path OR any ancestor of
57
+ * it, so `counterparty` covers `counterparty.name` and `rounds[].note` covers `rounds[1].note`. A pattern of one name matches that
58
+ * name at any depth; a longer pattern is anchored at the root, and `[]` stands for any list position (a trailing `[]` also covers the
59
+ * list itself).
60
+ */
61
+ export function compilePathMatcher(patterns) {
62
+ // A trailing `[]` names a list's items and the list itself (`requirements[]` also covers `requirements`).
63
+ const compiled = patterns.flatMap((raw) => {
64
+ const p = toSegments(raw);
65
+ if (p.length === 0)
66
+ return [];
67
+ return p.length > 1 && p[p.length - 1] === "*" ? [p, p.slice(0, -1)] : [p];
68
+ });
69
+ if (compiled.length === 0)
70
+ return () => false;
71
+ return (path) => {
72
+ const s = toSegments(path);
73
+ return compiled.some((p) => p.length === 1
74
+ ? s.includes(p[0])
75
+ : p.length <= s.length &&
76
+ p.every((seg, i) => (seg === "*" ? /^\d+$/.test(s[i]) : seg === s[i])));
77
+ };
78
+ }
79
+ const isLeaf = (v) => v === null ||
80
+ typeof v === "string" ||
81
+ typeof v === "number" ||
82
+ typeof v === "boolean" ||
83
+ isMasked(v);
84
+ const isEmptyLeaf = (v) => v === null || v === "";
85
+ const isPlainObject = (v) => {
86
+ const proto = Object.getPrototypeOf(v);
87
+ return proto === Object.prototype || proto === null;
88
+ };
89
+ const clipCell = (v, maxText) => {
90
+ if (typeof v === "string")
91
+ return cleanValue(v, maxText);
92
+ if (typeof v === "number" && !Number.isFinite(v))
93
+ return String(v);
94
+ if (isMasked(v))
95
+ return { masked: true, display: cleanValue(v.display, MAX_MASKED) };
96
+ return v;
97
+ };
98
+ const hasValue = (node) => node.kind === "field"
99
+ ? !isEmptyLeaf(node.value)
100
+ : node.kind === "list"
101
+ ? node.items.length > 0
102
+ : node.nodes.some(hasValue);
103
+ /** Drop what has no value (an empty field or list, a group with nothing in it), keep the shape of the rest. */
104
+ function prune(nodes) {
105
+ const out = [];
106
+ for (const n of nodes) {
107
+ if (n.kind === "group") {
108
+ const inner = prune(n.nodes);
109
+ if (inner.length > 0)
110
+ out.push({ ...n, nodes: inner });
111
+ }
112
+ else if (hasValue(n))
113
+ out.push(n);
114
+ }
115
+ return out;
116
+ }
117
+ const isStrictAncestor = (ancestor, path) => path.length > ancestor.length &&
118
+ path.startsWith(ancestor) &&
119
+ (path[ancestor.length] === "." || path[ancestor.length] === "[");
120
+ /**
121
+ * The record as labelled nodes. Own enumerable keys only; `__proto__`, `constructor`, `prototype` and keys starting with `_`
122
+ * (internal bookkeeping) are skipped. Objects become groups, a list of scalars a chip list, a list of objects numbered groups, a
123
+ * Date its ISO text, anything that is not plain data a placeholder. Empty values are dropped unless the card's `focus` named them.
124
+ *
125
+ * `focus` paths are resolved FIRST with `lookupPath`, each on its own budget, and kept out of the main walk: the agent's pointer
126
+ * survives a record that is otherwise capped.
127
+ */
128
+ export function buildRecordView(record, { focus, labels, itemLabel = (n) => fmt(DEFAULT_INSPECTOR_RECORD_COPY.item, { n }), trail = (parent, child) => fmt(DEFAULT_INSPECTOR_RECORD_COPY.trail, { parent, child }), notShown = DEFAULT_INSPECTOR_RECORD_COPY.notShown, hidden, } = {}) {
129
+ const empty = { highlighted: [], nodes: [], omitted: 0, isEmpty: true };
130
+ if (typeof record !== "object" || record === null || Array.isArray(record) || isMasked(record))
131
+ return empty;
132
+ const labelFor = (path, key) => {
133
+ const custom = labels && Object.hasOwn(labels, path)
134
+ ? labels[path]
135
+ : labels && Object.hasOwn(labels, key)
136
+ ? labels[key]
137
+ : undefined;
138
+ return {
139
+ text: cleanInline(custom ?? humanizeKey(key), MAX_LABEL, true) ?? "-",
140
+ data: custom === undefined,
141
+ };
142
+ };
143
+ const itemLabelFor = (n) => ({
144
+ text: cleanInline(itemLabel(n), MAX_LABEL, true) ?? "-",
145
+ data: false,
146
+ });
147
+ const walkObject = (obj, base, depth, w, skip) => {
148
+ const out = [];
149
+ const keys = Object.keys(obj);
150
+ for (let i = 0; i < keys.length; i++) {
151
+ if (w.budget <= 0) {
152
+ w.omitted += keys.length - i;
153
+ break;
154
+ }
155
+ const key = keys[i];
156
+ if (UNSAFE_KEYS.has(key) || key.startsWith("_"))
157
+ continue;
158
+ const path = base === "" ? key : `${base}.${key}`;
159
+ const node = walkValue(obj[key], path, labelFor(path, key), depth, w, skip);
160
+ if (node)
161
+ out.push(node);
162
+ }
163
+ return out;
164
+ };
165
+ const walkValue = (value, path, label, depth, w, skip) => {
166
+ if (value === undefined || typeof value === "function" || typeof value === "symbol")
167
+ return null;
168
+ if (skip(path))
169
+ return null;
170
+ if (w.budget <= 0) {
171
+ w.omitted += 1;
172
+ return null;
173
+ }
174
+ w.budget -= 1;
175
+ const field = (v) => ({
176
+ kind: "field",
177
+ path,
178
+ label: label.text,
179
+ dataLabel: label.data,
180
+ value: v,
181
+ });
182
+ if (isLeaf(value))
183
+ return field(clipCell(value, MAX_TEXT));
184
+ if (typeof value === "bigint")
185
+ return field(String(value));
186
+ if (typeof value !== "object")
187
+ return null;
188
+ if (value instanceof Date)
189
+ return field(Number.isNaN(value.getTime()) ? notShown : value.toISOString());
190
+ if (Array.isArray(value)) {
191
+ const shown = value.slice(0, MAX_LIST);
192
+ w.omitted += value.length - shown.length;
193
+ if (shown.every(isLeaf)) {
194
+ // Every item is a node of its own: a list cannot dodge the cap by being one field.
195
+ w.budget -= shown.length;
196
+ return {
197
+ kind: "list",
198
+ path,
199
+ label: label.text,
200
+ dataLabel: label.data,
201
+ items: shown.map((i) => clipCell(i, MAX_CHIP)),
202
+ };
203
+ }
204
+ if (depth >= MAX_DEPTH) {
205
+ w.omitted += 1;
206
+ return null;
207
+ }
208
+ const nodes = [];
209
+ shown.forEach((item, i) => {
210
+ const node = walkValue(item, `${path}[${i}]`, itemLabelFor(i + 1), depth + 1, w, skip);
211
+ if (node)
212
+ nodes.push(node);
213
+ });
214
+ return { kind: "group", path, label: label.text, dataLabel: label.data, nodes };
215
+ }
216
+ if (!isPlainObject(value))
217
+ return field(notShown);
218
+ if (depth >= MAX_DEPTH) {
219
+ w.omitted += 1;
220
+ return null;
221
+ }
222
+ return {
223
+ kind: "group",
224
+ path,
225
+ label: label.text,
226
+ dataLabel: label.data,
227
+ nodes: walkObject(value, path, depth + 1, w, skip),
228
+ };
229
+ };
230
+ const isHidden = (path) => hidden?.(path) === true;
231
+ // 1. The pointer's fields, on their own budget, outermost first.
232
+ const focused = (focus ?? []).flatMap((p) => {
233
+ const segments = p.split(/[.[\]]/).filter((s) => s !== "");
234
+ // `_` keys are internal bookkeeping: the main walk skips them, so a pointer cannot lift one either.
235
+ if (segments.length === 0 || segments.some((seg) => seg.startsWith("_")))
236
+ return [];
237
+ const found = lookupPath(record, p);
238
+ if (!found.found)
239
+ return [];
240
+ let cur = record;
241
+ let path = "";
242
+ const parts = [];
243
+ for (const seg of segments) {
244
+ if (Array.isArray(cur)) {
245
+ path += `[${seg}]`;
246
+ parts.push(itemLabelFor(Number(seg) + 1));
247
+ }
248
+ else {
249
+ path = path === "" ? seg : `${path}.${seg}`;
250
+ parts.push(labelFor(path, seg));
251
+ }
252
+ cur = cur[seg];
253
+ }
254
+ const last = parts[parts.length - 1];
255
+ const text = parts.map((x) => x.text).reduce((a, b) => trail(a, b));
256
+ return [{ path, label: { text, data: last.data }, value: found.value }];
257
+ });
258
+ const outermost = focused.filter((f, i) => !isHidden(f.path) &&
259
+ focused.findIndex((o) => o.path === f.path) === i &&
260
+ !focused.some((o) => isStrictAncestor(o.path, f.path)));
261
+ const focusWalk = { budget: MAX_NODES, omitted: 0 };
262
+ const lifted = outermost.flatMap((f) => {
263
+ const node = walkValue(f.value, f.path, f.label, 0, focusWalk, isHidden);
264
+ return node ? [node] : [];
265
+ });
266
+ // A focused field or list is kept even when empty (the agent pointed at it); a focused group keeps only what has a value.
267
+ const highlighted = lifted.flatMap((n) => {
268
+ if (n.kind !== "group")
269
+ return [n];
270
+ const inner = prune(n.nodes);
271
+ return inner.length > 0 ? [{ ...n, nodes: inner }] : [];
272
+ });
273
+ // 2. Everything else.
274
+ const focusedPaths = new Set(outermost.map((f) => f.path));
275
+ const mainWalk = { budget: MAX_NODES, omitted: 0 };
276
+ const nodes = prune(walkObject(record, "", 0, mainWalk, (path) => isHidden(path) || focusedPaths.has(path)));
277
+ return {
278
+ highlighted,
279
+ nodes,
280
+ omitted: mainWalk.omitted + focusWalk.omitted,
281
+ isEmpty: highlighted.length === 0 && nodes.length === 0,
282
+ };
283
+ }
284
+ /** What a value reads as: the host's text, else a word for a boolean, else the value; quoted when someone else wrote it. */
285
+ function display(path, v, p) {
286
+ if (isMasked(v))
287
+ return v;
288
+ const formatted = p.formatValue?.(path, v);
289
+ const base = formatted !== undefined
290
+ ? cleanValue(formatted, MAX_TEXT)
291
+ : typeof v === "boolean"
292
+ ? v
293
+ ? p.copy.yes
294
+ : p.copy.no
295
+ : v;
296
+ return typeof base === "string" && p.isThirdParty(path) ? quoteText(base, p.copy.quote) : base;
297
+ }
298
+ function fieldValue(node, p, focused) {
299
+ if (isEmptyLeaf(node.value))
300
+ return focused ? p.copy.notSet : undefined;
301
+ return display(node.path, node.value, p);
302
+ }
303
+ /** A stable key per list item without using its index: the item's text, numbered when the same text repeats. */
304
+ function keyed(items) {
305
+ const seen = new Map();
306
+ return items.map((item) => {
307
+ const base = isMasked(item) ? `masked:${item.display}` : String(item);
308
+ const n = seen.get(base) ?? 0;
309
+ seen.set(base, n + 1);
310
+ return { key: `${base}#${n}`, item };
311
+ });
312
+ }
313
+ /** The record path of the object a node sits in: `rounds[1].note` is in `rounds[1]`, a top-level key in `""`. */
314
+ const parentPath = (path) => {
315
+ const i = Math.max(path.lastIndexOf("."), path.lastIndexOf("["));
316
+ return i <= 0 ? "" : path.slice(0, i);
317
+ };
318
+ /**
319
+ * A node's label. A label made from a key of the record is data, and inside text someone else wrote (a node strictly below a
320
+ * third-party path) the key is theirs too: it renders quoted, like the value. A label the host supplied is the app's own word.
321
+ */
322
+ function shownLabel(n, p) {
323
+ return n.dataLabel && p.isThirdParty(parentPath(n.path))
324
+ ? quoteText(n.label, p.copy.quote)
325
+ : n.label;
326
+ }
327
+ const wide = (v) => typeof v === "string" && v.length > WIDE_TEXT;
328
+ function Nodes({ nodes, level, focused, p, }) {
329
+ const flat = nodes.filter((n) => n.kind !== "group");
330
+ const groups = nodes.filter((n) => n.kind === "group");
331
+ const Heading = `h${level}`;
332
+ return (_jsxs(_Fragment, { children: [flat.length > 0 && (_jsx(AgentFieldGrid, { children: flat.map((n) => {
333
+ if (n.kind === "list")
334
+ return (_jsxs("div", { className: "col-span-2 flex min-w-0 flex-col gap-1 @sm:col-span-3", children: [_jsx("dt", { className: "text-xs/4 text-muted-foreground", children: shownLabel(n, p) }), _jsx("dd", { children: _jsx("ul", { className: "flex flex-wrap gap-2", children: keyed(n.items).map(({ key, item }) => (_jsx("li", { className: "max-w-full", children: _jsx(AgentChip, { className: "max-w-full rounded-lg text-left wrap-anywhere", children: _jsx(MaskedValue, { value: display(n.path, item, p), copy: p.maskedCopy }) }) }, key))) }) })] }, n.path));
335
+ if (n.kind !== "field")
336
+ return null;
337
+ const value = fieldValue(n, p, focused);
338
+ return (_jsx(AgentField, { label: shownLabel(n, p), value: value, wide: wide(value), maskedCopy: p.maskedCopy }, n.path));
339
+ }) })), groups.map((g) => (_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Heading, { className: "text-xs/4 font-medium text-muted-foreground", children: shownLabel(g, p) }), _jsx(Nodes, { nodes: g.nodes, level: Math.min(level + 1, 6), focused: focused, p: p })] }, g.path)))] }));
340
+ }
341
+ /** The labelled fields of a record: the highlighted ones first, then everything else. Presentational; `buildRecordView` decides what is in it. */
342
+ export function AgentInspectorRecord(p) {
343
+ const { view, copy, headingId } = p;
344
+ return (_jsxs(Fragment, { children: [view.highlighted.length > 0 && (_jsxs("section", { "aria-labelledby": headingId("highlighted"), className: "flex flex-col gap-2 rounded-xl bg-muted/50 p-3", children: [_jsx("h3", { id: headingId("highlighted"), className: "text-xs/4 font-medium text-muted-foreground", children: copy.highlighted }), _jsx(Nodes, { nodes: view.highlighted, level: 4, focused: true, p: p })] })), view.nodes.length > 0 && (_jsxs("section", { "aria-labelledby": headingId("details"), className: "flex flex-col gap-3", children: [_jsx("h3", { id: headingId("details"), className: "text-xs/4 font-medium text-muted-foreground", children: copy.details }), _jsx(Nodes, { nodes: view.nodes, level: 4, focused: false, p: p })] })), view.omitted > 0 && _jsx("p", { className: "text-xs/4 text-muted-foreground", children: copy.truncated })] }));
345
+ }
346
+ //# sourceMappingURL=inspector-record.js.map