@chatpanel/events 0.102.0 → 0.104.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 (139) hide show
  1. package/adapters.js +1 -83
  2. package/adaptive-tool-policy.js +1 -45
  3. package/agent.js +2 -255
  4. package/attribution.js +1 -132
  5. package/backup-envelope.js +1 -221
  6. package/board-tool.js +2 -165
  7. package/budget.js +1 -116
  8. package/capability.js +1 -126
  9. package/citations.js +5 -127
  10. package/client-prefs.js +1 -148
  11. package/context-attachments.js +8 -144
  12. package/cowriter-router.js +1 -83
  13. package/cowriter-writer.js +19 -193
  14. package/cowriter.js +1 -190
  15. package/curate.js +3 -509
  16. package/distance.js +1 -124
  17. package/engine.js +1 -131
  18. package/entitlement.js +1 -332
  19. package/entity.js +1 -354
  20. package/event.js +1 -175
  21. package/extraction.js +4 -293
  22. package/failover.js +1 -93
  23. package/find-tool.js +1 -66
  24. package/flowchart.js +2 -644
  25. package/gate.js +1 -74
  26. package/harness.js +4 -101
  27. package/index.js +1 -345
  28. package/invariants.js +1 -174
  29. package/job.js +3 -147
  30. package/kernel.js +1 -255
  31. package/knowledge-derive.js +1 -267
  32. package/knowledge.js +3 -230
  33. package/library.js +11 -280
  34. package/loop.js +1 -132
  35. package/manifest.js +1 -107
  36. package/markdown-authoring.js +11 -288
  37. package/markdown-render.js +0 -0
  38. package/mcp-client.js +8 -358
  39. package/mcp-dispatch.js +1 -52
  40. package/mcp-errors.js +1 -106
  41. package/mcp-manager.js +1 -127
  42. package/media-transcript.js +8 -531
  43. package/meeting-analyzers.js +1 -83
  44. package/meeting-insights.js +7 -164
  45. package/meeting-shape.js +1 -282
  46. package/meeting-text.js +5 -94
  47. package/memory.js +2 -638
  48. package/model-candidates.js +1 -358
  49. package/model-health.js +1 -182
  50. package/model-ledger.js +1 -228
  51. package/model-picker.js +1 -188
  52. package/note-actions.js +15 -246
  53. package/note-graph.js +1 -158
  54. package/note-links.js +1 -68
  55. package/note-mentions.js +8 -114
  56. package/note-plan.js +9 -107
  57. package/note-research.js +1 -135
  58. package/observability.js +1 -119
  59. package/omni.js +1 -125
  60. package/order.js +1 -78
  61. package/package.json +4 -5
  62. package/pdf-layout.js +7 -253
  63. package/policy.js +1 -0
  64. package/project-run.js +20 -444
  65. package/project.js +1 -170
  66. package/promotion.js +1 -171
  67. package/queue.js +1 -98
  68. package/reach.js +1 -23
  69. package/recipe-tool.js +2 -161
  70. package/recipe.js +1 -264
  71. package/record-list.js +2 -110
  72. package/recruit.js +3 -456
  73. package/redaction-tokens.js +1 -61
  74. package/ref.js +1 -55
  75. package/registry.js +1 -240
  76. package/route-graph.js +1 -115
  77. package/route-strategies.js +2 -232
  78. package/router.js +2 -880
  79. package/rrf.js +1 -89
  80. package/rules.js +1 -142
  81. package/schedule.js +1 -663
  82. package/scm-connection.js +1 -180
  83. package/scopes.js +1 -12
  84. package/scorecard.js +1 -335
  85. package/search-engines.js +1 -81
  86. package/skill-manifest.js +1 -330
  87. package/skill-scan.js +3 -209
  88. package/skill-sources.js +1 -162
  89. package/skill-vars.js +2 -252
  90. package/slash-commands.js +1 -158
  91. package/source-gate.js +1 -92
  92. package/sources-retrieval.js +5 -186
  93. package/sources.js +1 -273
  94. package/store.js +1 -171
  95. package/structured.js +4 -901
  96. package/subject-kinds.js +1 -5
  97. package/subject-name.js +1 -96
  98. package/sync-plan.js +1 -213
  99. package/synthesis.js +3 -123
  100. package/tags.js +1 -203
  101. package/team-board.js +6 -337
  102. package/team-cache.js +2 -63
  103. package/team-observe.js +1 -118
  104. package/team-org.js +1 -442
  105. package/team-plan.js +4 -131
  106. package/team-record.js +1 -225
  107. package/team-run.js +9 -652
  108. package/team-subtask.js +2 -191
  109. package/team-task.js +2 -124
  110. package/team-tool.js +2 -196
  111. package/team-trail.js +2 -73
  112. package/team-worklog.js +2 -149
  113. package/team.js +1 -302
  114. package/text-search.js +1 -155
  115. package/theme.js +17 -154
  116. package/titles.js +4 -285
  117. package/tool-discovery.js +1 -87
  118. package/tool-dispatch.js +5 -230
  119. package/tool-groups.js +1 -81
  120. package/tool-hints.js +10 -185
  121. package/tool-loop-guard.js +4 -182
  122. package/tool-need.js +2 -124
  123. package/tool-result.js +2 -438
  124. package/tool-round.js +0 -0
  125. package/tool-schema.js +1 -155
  126. package/tool-traits.js +1 -182
  127. package/toolset.js +2 -88
  128. package/trajectory.js +1 -530
  129. package/turn-loop.js +4 -440
  130. package/upcast.js +1 -37
  131. package/vault.js +2 -250
  132. package/view.js +1 -88
  133. package/voice-intents.js +2 -1488
  134. package/voice-speaker.js +1 -98
  135. package/weather-tool.js +1 -65
  136. package/weather.js +2 -211
  137. package/web-search-tool.js +14 -102
  138. package/web-search.js +1 -160
  139. package/widget.js +1 -153
package/pdf-layout.js CHANGED
@@ -1,258 +1,12 @@
1
- // A PDF's text items something a person (or a model) can read.
2
- //
3
- // A PDF does not contain paragraphs. It contains positioned glyph runs: "Introduction" at
4
- // (72, 690), "and this is" at (72, 674), "why" at (140, 674). Every PDF engine — pdf.js in
5
- // a browser, a native renderer on mobile, a CLI extractor on the bridge — hands back that
6
- // same list of runs with the same transform matrix, and every one of them leaves the job of
7
- // turning runs into READING ORDER to the caller. Done naively (join the items with spaces)
8
- // a two-column paper becomes an interleaved ransom note, and every hyphenated line break
9
- // becomes a broken word the model then cannot match against a query.
10
- //
11
- // So the reconstruction lives here — pure, testable, engine-agnostic — and the client
12
- // contributes only the engine that produced the items.
13
- //
14
- // THE ITEM SHAPE (pdf.js `getTextContent().items`, and what other engines are adapted to):
15
- // { str, transform: [a, b, c, d, x, y], width, height, hasEOL }
16
- // x/y are in PDF user space: y grows UPWARD from the bottom of the page.
1
+ function d(e){const l=Array.isArray(e?.transform)?e.transform:[1,0,0,1,0,0];return{text:String(e?.str??""),x:Number(l[4])||0,y:Number(l[5])||0,width:Number(e?.width)||0,size:Math.abs(Number(l[3]))||Number(e?.height)||0,eol:!!e?.hasEOL}}function m(e,{tolerance:l=.5}={}){const s=(Array.isArray(e)?e:[]).map(d).filter(t=>t.text!=="");if(!s.length)return[];const i=[];for(const t of s){const n=Math.max(1,(t.size||10)*l),o=i[i.length-1];o&&Math.abs(o.y-t.y)<=n?(o.items.push(t),t.size>o.size&&(o.size=t.size)):i.push({y:t.y,size:t.size||10,items:[t]})}return i.map(t=>{const n=t.items.slice().sort((o,c)=>o.x-c.x);return{y:t.y,size:t.size,x:n[0].x,right:Math.max(...n.map(o=>o.x+o.width)),text:z(n,t.size)}})}function z(e,l){let s="",i=null;for(const t of e)i!==null&&t.x-i>(l||10)*.2&&!/\s$/.test(s)&&!/^\s/.test(t.text)&&(s+=" "),s+=t.text,i=t.x+t.width;return s.replace(/\s+/g," ").trim()}function b(e,{pageWidth:l=0}={}){const s=(Array.isArray(e)?e:[]).slice();if(s.length<6)return s.sort((r,a)=>a.y-r.y);const t=(l||Math.max(...s.map(r=>r.right)))/2,n=s.filter(r=>r.right<=t*1.05),o=s.filter(r=>r.x>=t*.95),c=s.filter(r=>!n.includes(r)&&!o.includes(r));if(!(n.length>=3&&o.length>=3&&c.length<=s.length*.2))return s.sort((r,a)=>a.y-r.y);const h=(r,a)=>a.y-r.y;return[...c.filter(r=>r.y>Math.max(...n.map(a=>a.y),0)).sort(h),...n.sort(h),...o.sort(h),...c.filter(r=>r.y<=Math.max(...n.map(a=>a.y),0)).sort(h)]}function M(e){const l=(Array.isArray(e)?e:[]).filter(t=>t&&t.text);if(!l.length)return[];const s=[];for(const t of l){const n=s[s.length-1],o=n?.lines[n.lines.length-1],c=n&&Math.abs(n.lines[0].x-t.x)<=(t.size||10)*1.5,f=o&&Math.abs((o.size||10)-(t.size||10))<=(o.size||10)*.15;c&&f?n.lines.push(t):s.push({lines:[t]})}const i=[];for(const t of s){const n=t.lines,o=[];for(let a=1;a<n.length;a++)o.push(Math.abs(n[a-1].y-n[a].y));const c=A(o,.3)||(n[0].size||10)*1.2,f=Math.max(...n.map(a=>a.right)),h=Math.min(...n.map(a=>a.x));let r="";for(let a=0;a<n.length;a++){const u=n[a],p=n[a-1],g=p?Math.abs(p.y-u.y):0,x=u.x>h+(u.size||10)*.8,y=p?p.right<f-(u.size||10)*3:!1;r&&(g>c*1.5||x||y)?(i.push(r),r=u.text):r?/(\w)-$/.test(r)?r=r.replace(/-$/,"")+u.text.replace(/^\s+/,""):r+=" "+u.text:r=u.text}r&&i.push(r)}return i.map(t=>t.replace(/\s+/g," ").trim()).filter(Boolean)}function A(e,l){const s=e.filter(i=>Number.isFinite(i)&&i>0).sort((i,t)=>i-t);return s.length?s[Math.min(s.length-1,Math.floor(l*(s.length-1)))]:0}function S(e,{pageWidth:l=0,...s}={}){const i=m(e,s);return M(b(i,{pageWidth:l})).join(`
17
2
 
18
- /** One text item the numbers this file reasons about. */
19
- function place(item) {
20
- const t = Array.isArray(item?.transform) ? item.transform : [1, 0, 0, 1, 0, 0];
21
- return {
22
- text: String(item?.str ?? ''),
23
- x: Number(t[4]) || 0,
24
- y: Number(t[5]) || 0,
25
- width: Number(item?.width) || 0,
26
- // The transform's vertical scale is the font size; `height` is often 0 on the items
27
- // real documents produce, and a zero line height makes every line one paragraph.
28
- size: Math.abs(Number(t[3])) || Number(item?.height) || 0,
29
- eol: !!item?.hasEOL,
30
- };
31
- }
3
+ `)}const $=2e5;function w(e){const l=Array.isArray(e)?e:[];return l.length?l.reduce((i,t)=>i+String(t?.text||"").length,0)<l.length*40:!1}function F({meta:e={},pages:l=[],maxChars:s=$}={}){const i=Array.isArray(l)?l:[],t=[];e.title&&t.push(`# ${e.title}`);const n=[];e.author&&n.push(`Author: ${e.author}`),i.length&&n.push(`Pages: ${e.pageCount||i.length}`),e.url&&n.push(`URL: ${e.url}`),n.length&&t.push(n.join(" · "));const o=i.map(h=>{const r=String(h?.text||"").trim();return r?`[page ${h.page}]
4
+ ${r}`:""}).filter(Boolean).join(`
32
5
 
33
- /**
34
- * Items → lines, by shared baseline.
35
- *
36
- * Tolerance is a FRACTION OF THE FONT SIZE, not a fixed number of points: a footnote at 7pt
37
- * and a heading at 24pt do not agree on what "the same line" means, and a constant that
38
- * works for body text either splits headings or merges footnotes.
39
- */
40
- export function linesFromItems(items, { tolerance = 0.5 } = {}) {
41
- const placed = (Array.isArray(items) ? items : []).map(place).filter((p) => p.text !== '');
42
- if (!placed.length) return [];
43
- const lines = [];
44
- for (const p of placed) {
45
- const tol = Math.max(1, (p.size || 10) * tolerance);
46
- // Compare against the most recent line only: items arrive in content-stream order, so a
47
- // matching baseline further back belongs to an earlier column, not to this line.
48
- const last = lines[lines.length - 1];
49
- if (last && Math.abs(last.y - p.y) <= tol) {
50
- last.items.push(p);
51
- if (p.size > last.size) last.size = p.size;
52
- } else {
53
- lines.push({ y: p.y, size: p.size || 10, items: [p] });
54
- }
55
- }
56
- return lines.map((line) => {
57
- const sorted = line.items.slice().sort((a, b) => a.x - b.x);
58
- return {
59
- y: line.y,
60
- size: line.size,
61
- x: sorted[0].x,
62
- right: Math.max(...sorted.map((i) => i.x + i.width)),
63
- text: joinRun(sorted, line.size),
64
- };
65
- });
66
- }
6
+ `);let c=t.length?`${t.join(`
67
7
 
68
- /**
69
- * Glyph runs on one line → a string.
70
- *
71
- * PDFs encode a space either as a space character or as a horizontal gap with nothing in
72
- * it, and which one you get depends on the producer. Joining on the character alone loses
73
- * every word break in documents from the second kind; joining every item with a space puts
74
- * one inside every kerned pair in documents from the first.
75
- */
76
- function joinRun(sorted, size) {
77
- let out = '';
78
- let prevRight = null;
79
- for (const item of sorted) {
80
- if (prevRight !== null) {
81
- const gap = item.x - prevRight;
82
- const needsSpace = gap > (size || 10) * 0.2;
83
- if (needsSpace && !/\s$/.test(out) && !/^\s/.test(item.text)) out += ' ';
84
- }
85
- out += item.text;
86
- prevRight = item.x + item.width;
87
- }
88
- return out.replace(/\s+/g, ' ').trim();
89
- }
8
+ `)}
90
9
 
91
- /**
92
- * Lines → columns, in reading order.
93
- *
94
- * Two-column layouts are the case that makes naive extraction useless, and they are most of
95
- * academic and technical PDF reading. A column is detected as a vertical band that lines
96
- * cluster into; if the page is one column (or the split is not clean) this returns the lines
97
- * unchanged rather than inventing a split, because a wrong split is worse than none.
98
- */
99
- export function orderLines(lines, { pageWidth = 0 } = {}) {
100
- const list = (Array.isArray(lines) ? lines : []).slice();
101
- if (list.length < 6) return list.sort((a, b) => b.y - a.y);
102
- const width = pageWidth || Math.max(...list.map((l) => l.right));
103
- const mid = width / 2;
104
- const left = list.filter((l) => l.right <= mid * 1.05);
105
- const right = list.filter((l) => l.x >= mid * 0.95);
106
- const spanning = list.filter((l) => !left.includes(l) && !right.includes(l));
107
- // A real two-column page has substantial text on BOTH sides and few lines crossing the
108
- // gutter. Anything else is a single column with a stray indent or a wide table.
109
- const twoColumn = left.length >= 3 && right.length >= 3
110
- && spanning.length <= list.length * 0.2;
111
- if (!twoColumn) return list.sort((a, b) => b.y - a.y);
112
- const byY = (a, b) => b.y - a.y;
113
- // Headers and footers that span the gutter keep their vertical position relative to the
114
- // column they sit above; sorting them into the left column first is the conventional and
115
- // least-surprising reading order.
116
- return [...spanning.filter((l) => l.y > Math.max(...left.map((x) => x.y), 0)).sort(byY),
117
- ...left.sort(byY), ...right.sort(byY),
118
- ...spanning.filter((l) => l.y <= Math.max(...left.map((x) => x.y), 0)).sort(byY)];
119
- }
10
+ ${o}`:o,f=!1;return c.length>s&&(c=`${c.slice(0,s)}
120
11
 
121
- /**
122
- * Lines → paragraphs.
123
- *
124
- * A new paragraph is a bigger-than-usual vertical gap, a first-line indent, or a line that
125
- * ended well short of the right margin. A word broken across lines with a hyphen is put
126
- * back together — otherwise "distri-\nbuted" never matches a search for "distributed", and
127
- * the model reads a word that does not exist.
128
- *
129
- * MARGINS ARE MEASURED PER BLOCK, NOT PER PAGE. On a two-column page every line in the left
130
- * column ends far short of the page's right edge, so a page-wide margin makes every single
131
- * line "short" and therefore its own paragraph — which is what a first attempt at this did.
132
- * Lines that share a left edge are one block; a change of block is itself a paragraph break,
133
- * because it is a change of column or of indentation level.
134
- */
135
- export function paragraphsFromLines(lines) {
136
- const list = (Array.isArray(lines) ? lines : []).filter((l) => l && l.text);
137
- if (!list.length) return [];
138
-
139
- // A block is a run of lines that share a left edge AND a font size. The size half is not
140
- // cosmetic: a 14pt heading is wider than the 10pt lines under it, so a block containing
141
- // both takes its right margin from the heading — and then every body line is "short" and
142
- // every one of them becomes its own paragraph.
143
- const blocks = [];
144
- for (const line of list) {
145
- const cur = blocks[blocks.length - 1];
146
- const prev = cur?.lines[cur.lines.length - 1];
147
- const sameEdge = cur && Math.abs(cur.lines[0].x - line.x) <= (line.size || 10) * 1.5;
148
- const sameSize = prev && Math.abs((prev.size || 10) - (line.size || 10)) <= (prev.size || 10) * 0.15;
149
- if (sameEdge && sameSize) cur.lines.push(line);
150
- else blocks.push({ lines: [line] });
151
- }
152
-
153
- const paras = [];
154
- for (const block of blocks) {
155
- const rows = block.lines;
156
- const gaps = [];
157
- for (let i = 1; i < rows.length; i++) gaps.push(Math.abs(rows[i - 1].y - rows[i].y));
158
- // A LOW percentile, not the median: in a three-line block whose second gap IS the
159
- // paragraph break, the median sits between the line gap and the break and neither is
160
- // then unusual. The common gap is a line gap, and it lives at the bottom of the range.
161
- const typical = percentile(gaps, 0.3) || (rows[0].size || 10) * 1.2;
162
- const rightEdge = Math.max(...rows.map((l) => l.right));
163
- const leftEdge = Math.min(...rows.map((l) => l.x));
164
-
165
- let cur = '';
166
- for (let i = 0; i < rows.length; i++) {
167
- const line = rows[i];
168
- const prev = rows[i - 1];
169
- const gap = prev ? Math.abs(prev.y - line.y) : 0;
170
- const indented = line.x > leftEdge + (line.size || 10) * 0.8;
171
- const prevShort = prev ? prev.right < rightEdge - (line.size || 10) * 3 : false;
172
- if (cur && (gap > typical * 1.5 || indented || prevShort)) {
173
- paras.push(cur);
174
- cur = line.text;
175
- } else if (!cur) {
176
- cur = line.text;
177
- } else if (/(\w)-$/.test(cur)) {
178
- cur = cur.replace(/-$/, '') + line.text.replace(/^\s+/, '');
179
- } else {
180
- cur += ' ' + line.text;
181
- }
182
- }
183
- if (cur) paras.push(cur);
184
- }
185
- return paras.map((p) => p.replace(/\s+/g, ' ').trim()).filter(Boolean);
186
- }
187
-
188
- /** Nearest-rank percentile of the positive values, 0 when there are none. */
189
- function percentile(nums, q) {
190
- const list = nums.filter((n) => Number.isFinite(n) && n > 0).sort((a, b) => a - b);
191
- if (!list.length) return 0;
192
- return list[Math.min(list.length - 1, Math.floor(q * (list.length - 1)))];
193
- }
194
-
195
- /** One page's items → its text. */
196
- export function pageTextFromItems(items, { pageWidth = 0, ...opts } = {}) {
197
- const lines = linesFromItems(items, opts);
198
- return paragraphsFromLines(orderLines(lines, { pageWidth })).join('\n\n');
199
- }
200
-
201
- export const PDF_MAX_CHARS = 200_000;
202
-
203
- /**
204
- * Is this PDF a picture of a document rather than a document?
205
- *
206
- * A scanned page has a text layer of nothing, and every extractor answers with an empty
207
- * string. Returning that silently is the worst outcome: the user sees a summary of nothing
208
- * and no explanation. Detecting it lets the caller say "this needs OCR" — which is an
209
- * answer, where an empty attachment is a mystery.
210
- */
211
- export function looksScanned(pages) {
212
- const list = Array.isArray(pages) ? pages : [];
213
- if (!list.length) return false;
214
- const chars = list.reduce((n, p) => n + String(p?.text || '').length, 0);
215
- return chars < list.length * 40;
216
- }
217
-
218
- /**
219
- * Pages → the document a caller attaches.
220
- *
221
- * Page markers are kept because a citation into a 90-page PDF is worth nothing without one,
222
- * and because they are the only thing that tells a model the document has an order at all.
223
- */
224
- export function buildPdfDocument({ meta = {}, pages = [], maxChars = PDF_MAX_CHARS } = {}) {
225
- const list = Array.isArray(pages) ? pages : [];
226
- const head = [];
227
- if (meta.title) head.push(`# ${meta.title}`);
228
- const facts = [];
229
- if (meta.author) facts.push(`Author: ${meta.author}`);
230
- if (list.length) facts.push(`Pages: ${meta.pageCount || list.length}`);
231
- if (meta.url) facts.push(`URL: ${meta.url}`);
232
- if (facts.length) head.push(facts.join(' · '));
233
-
234
- const body = list
235
- .map((p) => {
236
- const text = String(p?.text || '').trim();
237
- return text ? `[page ${p.page}]\n${text}` : '';
238
- })
239
- .filter(Boolean)
240
- .join('\n\n');
241
-
242
- let text = head.length ? `${head.join('\n\n')}\n\n${body}` : body;
243
- let truncated = false;
244
- if (text.length > maxChars) {
245
- text = `${text.slice(0, maxChars)}\n\n…[PDF truncated at ${maxChars} characters]`;
246
- truncated = true;
247
- }
248
- return {
249
- title: meta.title || 'PDF',
250
- url: meta.url || '',
251
- pageCount: meta.pageCount || list.length,
252
- pagesRead: list.length,
253
- scanned: looksScanned(list),
254
- truncated,
255
- text,
256
- chars: text.length,
257
- };
258
- }
12
+ …[PDF truncated at ${s} characters]`,f=!0),{title:e.title||"PDF",url:e.url||"",pageCount:e.pageCount||i.length,pagesRead:i.length,scanned:w(i),truncated:f,text:c,chars:c.length}}export{$ as PDF_MAX_CHARS,F as buildPdfDocument,m as linesFromItems,w as looksScanned,b as orderLines,S as pageTextFromItems,M as paragraphsFromLines};
package/policy.js ADDED
@@ -0,0 +1 @@
1
+ class a extends Error{constructor(n){super(n),this.name="PolicyError"}}const x=1,C="policy",d=Object.freeze(["off","basic","full"]),A=Object.freeze({off:0,basic:1,full:2}),O=Object.freeze(["gateway","endpoints","redaction","features","agents"]),k=Object.freeze(["webllm","meetings","channels","agents"]),w=Object.freeze(["plan","default","acceptEdits","bypassPermissions"]),U=Object.freeze({plan:0,default:1,acceptEdits:2,bypassPermissions:3}),T=Object.freeze(["api.chatpanel.net","dl.chatpanel.net","huggingface.co","registry.npmjs.org","api.github.com"]),f=e=>typeof e=="string"?e.trim():"",c=e=>Array.isArray(e)?e.map(f).filter(Boolean):[];function y(e){const n=f(e).toLowerCase().replace(/^https?:\/\//,"").replace(/\/.*$/,"");if(!n)return"";if(!/^(\*\.)?[a-z0-9.-]+(:\d{1,5})?$/.test(n)&&!/^\[[0-9a-f:]+\](:\d{1,5})?$/.test(n)&&n!=="*")throw new a(`invalid host pattern "${e}"`);return n}function h(e){if(e==null)return h({});if(typeof e!="object"||Array.isArray(e))throw new a("policy must be an object");const n=e.v==null?x:Number(e.v);if(n!==x)throw new a(`unsupported policy version ${e.v}`);const t={v:n};if(e.org!=null){const r=f(e.org.id);if(!r||!/^[a-z0-9][a-z0-9._-]{0,63}$/i.test(r))throw new a("org.id must be a short identifier");t.org={id:r,name:f(e.org.name)||r}}const o=(r,l)=>{if(r==null)return;if(typeof r!="object")throw new a(`${l} must be an object`);const u={};return r.allow!=null&&(u.allow=c(r.allow).map(y).filter(Boolean)),r.deny!=null&&(u.deny=c(r.deny).map(y).filter(Boolean)),u},i=o(e.endpoints,"endpoints");i&&(t.endpoints=i);const s=o(e.egress,"egress");if(s&&(t.egress=s),e.models!=null){if(typeof e.models!="object")throw new a("models must be an object");t.models={},e.models.allow!=null&&(t.models.allow=c(e.models.allow)),e.models.deny!=null&&(t.models.deny=c(e.models.deny))}if(e.agents!=null){if(typeof e.agents!="object")throw new a("agents must be an object");const r={};if(e.agents.allow!=null&&(r.allow=c(e.agents.allow).map(l=>l.toLowerCase())),e.agents.deny!=null&&(r.deny=c(e.agents.deny).map(l=>l.toLowerCase())),e.agents.permissionModeCap!=null){const l=f(e.agents.permissionModeCap);if(!w.includes(l))throw new a(`agents.permissionModeCap must be one of ${w.join(", ")}`);r.permissionModeCap=l}t.agents=r}if(e.routing!=null){if(typeof e.routing!="object")throw new a("routing must be an object");const r=Array.isArray(e.routing.rules)?e.routing.rules:[];t.routing={rules:r.map((l,u)=>{if(!l||typeof l!="object")throw new a(`routing.rules[${u}] must be an object`);const j=c(l.models),$=c(l.endpoints).map(y).filter(Boolean);if(!j.length)throw new a(`routing.rules[${u}] needs at least one model pattern`);if(!$.length)throw new a(`routing.rules[${u}] needs at least one endpoint`);return{models:j,endpoints:$}})}}if(e.redaction!=null){if(typeof e.redaction!="object")throw new a("redaction must be an object");const r={};if(e.redaction.floor!=null){const l=f(e.redaction.floor).toLowerCase();if(!d.includes(l))throw new a(`redaction.floor must be one of ${d.join(", ")}`);r.floor=l}e.redaction.dictionary!=null&&(r.dictionary=c(e.redaction.dictionary)),e.redaction.requireBeforeCloud!=null&&(r.requireBeforeCloud=!!e.redaction.requireBeforeCloud),t.redaction=r}if(e.client!=null){if(typeof e.client!="object")throw new a("client must be an object");const r={};if(e.client.gatewayUrl!=null){const l=f(e.client.gatewayUrl);if(l&&!/^https?:\/\/[^\s/]+(\/.*)?$/.test(l))throw new a("client.gatewayUrl must be an http(s) URL");r.gatewayUrl=l.replace(/\/+$/,"")}if(e.client.managedRequired!=null&&(r.managedRequired=!!e.client.managedRequired),e.client.lock!=null){const l=c(e.client.lock);for(const u of l)if(!O.includes(u))throw new a(`client.lock: unknown setting "${u}" (one of ${O.join(", ")})`);r.lock=[...new Set(l)]}if(e.client.features!=null){if(typeof e.client.features!="object")throw new a("client.features must be an object");r.features={};for(const[l,u]of Object.entries(e.client.features)){if(!k.includes(l))throw new a(`client.features: unknown feature "${l}" (one of ${k.join(", ")})`);r.features[l]=!!u}}t.client=r}return t}function p(e){const n=f(e).toLowerCase();if(!n)return{host:"",port:""};try{const t=new URL(/^[a-z][a-z0-9+.-]*:\/\//.test(n)?n:`http://${n}`);return{host:t.hostname.replace(/^\[|\]$/g,""),port:t.port||(t.protocol==="https:"?"443":t.protocol==="http:"?"80":"")}}catch{return{host:n.split(":")[0],port:""}}}function S(e,n,t){if(e==="*")return!0;const[o,i]=e.startsWith("[")?[e.replace(/^\[([^\]]+)\].*$/,"$1"),e.replace(/^\[[^\]]+\]:?/,"")]:(()=>{const s=e.lastIndexOf(":");return s>0?[e.slice(0,s),e.slice(s+1)]:[e,""]})();if(i&&i!==t)return!1;if(o.startsWith("*.")){const s=o.slice(1);return n.endsWith(s)&&n.length>s.length}return n===o}function g(e,n,t){return(e||[]).some(o=>S(o,n,t))}function m(e){return new RegExp(`^${String(e).split("*").map(n=>n.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")).join(".*")}$`,"i")}function E(e,n){const t=e?.endpoints;if(!t)return!0;const{host:o,port:i}=p(n);return!o||g(t.deny,o,i)?!1:t.allow&&t.allow.length?g(t.allow,o,i):!0}function R(e,n){const t=e?.models;if(!t)return!0;const o=f(n);return(t.deny||[]).some(i=>m(i).test(o))?!1:t.allow&&t.allow.length?t.allow.some(i=>m(i).test(o)):!0}function z(e,n){const t=e?.agents;if(!t)return!0;const o=f(n).toLowerCase();return(t.deny||[]).includes(o)?!1:t.allow&&t.allow.length?t.allow.includes(o):!0}function M(e,n){const t=e?.agents?.permissionModeCap,o=w.includes(n)?n:"default";return t?U[o]<=U[t]?o:t:o}function I(e,{model:n="",endpoint:t=""}={}){if(!e)return{ok:!0,reason:""};if(n&&!R(e,n))return{ok:!1,reason:`model "${n}" is not allowed by your organisation's policy`};if(t&&!E(e,t))return{ok:!1,reason:`endpoint ${p(t).host} is not allowed by your organisation's policy`};const o=e.routing?.rules||[];if(n&&t&&o.length){const i=o.find(s=>s.models.some(r=>m(r).test(n)));if(i){const{host:s,port:r}=p(t);if(!g(i.endpoints,s,r))return{ok:!1,reason:`model "${n}" may only be sent to ${i.endpoints.join(", ")} under your organisation's policy`}}}return{ok:!0,reason:""}}function L(e,n){const t=e?.redaction?.floor,o=d.includes(n)?n:"basic";return t?A[o]>=A[t]?o:t:o}function N(e,n,{local:t=!1}={}){const o=e?.egress;if(!o)return!0;const{host:i,port:s}=p(n);return!i||g(o.deny,i,s)?!1:t||T.includes(i)||e.endpoints?.allow?.length&&g(e.endpoints.allow,i,s)&&!g(e.endpoints?.deny,i,s)?!0:g(o.allow,i,s)}function _(e,n){return!!e?.client?.lock?.includes(n)}function P(e,n){const t=e?.client?.features;return!t||t[n]==null?!0:!!t[n]}function B(e,n){const t={...n||{}},o=[];if(!e)return{settings:t,changed:o};const i=e.client||{};if(i.gatewayUrl&&t.gatewayUrl!==i.gatewayUrl&&(t.gatewayUrl=i.gatewayUrl,o.push("gatewayUrl")),Array.isArray(t.endpoints)&&e.endpoints){const s=t.endpoints.filter(r=>!r||typeof r!="object"?!1:!r.baseUrl||i.gatewayUrl&&p(r.baseUrl).host===p(i.gatewayUrl).host&&p(r.baseUrl).port===p(i.gatewayUrl).port?!0:E(e,r.baseUrl));s.length!==t.endpoints.length&&(t.endpoints=s,o.push("endpoints"))}if(Array.isArray(t.endpoints)&&e.agents){const s=t.endpoints.filter(r=>r?.kind!=="bridge"||z(e,r.bridgeAgent||r.agent||r.id));s.length!==t.endpoints.length&&(t.endpoints=s,o.push("agents"))}if(Array.isArray(t.endpoints)&&!P(e,"webllm")){const s=t.endpoints.filter(r=>r?.kind!=="webllm");s.length!==t.endpoints.length&&(t.endpoints=s,o.push("webllm"))}if(e.redaction?.floor&&(t.redaction?.tier!=null||t.redactionTier!=null)){const s=t.redaction?.tier!=null?t.redaction.tier:t.redactionTier,r=L(e,s);r!==s&&(t.redaction?.tier!=null?t.redaction={...t.redaction,tier:r}:t.redactionTier=r,o.push("redaction"))}return{settings:t,changed:o}}function q(e){const n=e?.org?.name||e?.org?.id;return n?`Managed by ${n}`:"Managed"}function b(e){return Array.isArray(e)?`[${e.map(b).join(",")}]`:e&&typeof e=="object"?`{${Object.keys(e).sort().map(n=>`${JSON.stringify(n)}:${b(e[n])}`).join(",")}}`:JSON.stringify(e)}async function H(e,{subtle:n=null}={}){const t=n||globalThis.crypto?.subtle;if(!t)throw new a("no WebCrypto available on this runtime");const o=new TextEncoder().encode(b(h(e))),i=await t.digest("SHA-256",o);return[...new Uint8Array(i)].map(s=>s.toString(16).padStart(2,"0")).join("")}async function D(e,{verify:n,...t}={}){if(typeof n!="function")throw new a("verifyPolicyToken needs a verify function (entitlement.js verifySignedToken)");const o=await n(e,{...t,typ:C});if(!o||!o.policy)return null;try{return h(o.policy)}catch{return null}}export{k as FEATURES,O as LOCKABLE,w as PERMISSION_MODES,C as POLICY_TOKEN_TYPE,x as POLICY_VERSION,T as PRODUCT_HOSTS,a as PolicyError,d as REDACTION_TIERS,z as agentAllowed,B as applyPolicyToSettings,N as egressAllowed,E as endpointAllowed,P as featureEnabled,m as globToRegExp,R as modelAllowed,h as normalizeOrgPolicy,M as permissionMode,H as policyHash,q as policyLabel,L as redactionTier,I as routeAllowed,_ as settingLocked,D as verifyPolicyToken};