@chatpanel/events 0.102.0 → 0.103.1

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 (138) 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 +1 -4
  62. package/pdf-layout.js +7 -253
  63. package/project-run.js +20 -444
  64. package/project.js +1 -170
  65. package/promotion.js +1 -171
  66. package/queue.js +1 -98
  67. package/reach.js +1 -23
  68. package/recipe-tool.js +2 -161
  69. package/recipe.js +1 -264
  70. package/record-list.js +2 -110
  71. package/recruit.js +3 -456
  72. package/redaction-tokens.js +1 -61
  73. package/ref.js +1 -55
  74. package/registry.js +1 -240
  75. package/route-graph.js +1 -115
  76. package/route-strategies.js +2 -232
  77. package/router.js +2 -880
  78. package/rrf.js +1 -89
  79. package/rules.js +1 -142
  80. package/schedule.js +1 -663
  81. package/scm-connection.js +1 -180
  82. package/scopes.js +1 -12
  83. package/scorecard.js +1 -335
  84. package/search-engines.js +1 -81
  85. package/skill-manifest.js +1 -330
  86. package/skill-scan.js +3 -209
  87. package/skill-sources.js +1 -162
  88. package/skill-vars.js +2 -252
  89. package/slash-commands.js +1 -158
  90. package/source-gate.js +1 -92
  91. package/sources-retrieval.js +5 -186
  92. package/sources.js +1 -273
  93. package/store.js +1 -171
  94. package/structured.js +4 -901
  95. package/subject-kinds.js +1 -5
  96. package/subject-name.js +1 -96
  97. package/sync-plan.js +1 -213
  98. package/synthesis.js +3 -123
  99. package/tags.js +1 -203
  100. package/team-board.js +6 -337
  101. package/team-cache.js +2 -63
  102. package/team-observe.js +1 -118
  103. package/team-org.js +1 -442
  104. package/team-plan.js +4 -131
  105. package/team-record.js +1 -225
  106. package/team-run.js +9 -652
  107. package/team-subtask.js +2 -191
  108. package/team-task.js +2 -124
  109. package/team-tool.js +2 -196
  110. package/team-trail.js +2 -73
  111. package/team-worklog.js +2 -149
  112. package/team.js +1 -302
  113. package/text-search.js +1 -155
  114. package/theme.js +17 -154
  115. package/titles.js +4 -285
  116. package/tool-discovery.js +1 -87
  117. package/tool-dispatch.js +5 -230
  118. package/tool-groups.js +1 -81
  119. package/tool-hints.js +10 -185
  120. package/tool-loop-guard.js +4 -182
  121. package/tool-need.js +2 -124
  122. package/tool-result.js +2 -438
  123. package/tool-round.js +0 -0
  124. package/tool-schema.js +1 -155
  125. package/tool-traits.js +1 -182
  126. package/toolset.js +2 -88
  127. package/trajectory.js +1 -530
  128. package/turn-loop.js +4 -440
  129. package/upcast.js +1 -37
  130. package/vault.js +2 -250
  131. package/view.js +1 -88
  132. package/voice-intents.js +2 -1488
  133. package/voice-speaker.js +1 -98
  134. package/weather-tool.js +1 -65
  135. package/weather.js +2 -211
  136. package/web-search-tool.js +14 -102
  137. package/web-search.js +1 -160
  138. package/widget.js +1 -153
package/tool-traits.js CHANGED
@@ -1,182 +1 @@
1
- // What a tool DOES to the world read from its annotations, guessed from its name, and
2
- // turned into the three decisions every host was making by hand.
3
- //
4
- // MCP tools carry `annotations` (readOnlyHint, destructiveHint, idempotentHint,
5
- // openWorldHint) and nothing in any ChatPanel client read them. Meanwhile each client
6
- // decided separately whether a call could run alongside another (never — every round ran
7
- // one call at a time), whether a repeat could be answered from the last result (a
8
- // hand-kept list of seven tool names), and whether an action deserved a confirmation
9
- // (per-action, per-site rules with no idea what the tool itself declared). Three
10
- // decisions, one fact underneath: is this a read, and could it destroy something.
11
- //
12
- // Annotations win when present. When absent, the NAME is read — `get_`, `list_`,
13
- // `search_` on one side, `delete_`, `purge_`, `revoke_` on the other — with the
14
- // conservative default in the middle: a tool nobody can classify is a write, not a read,
15
- // and "unknown" is never promoted to "safe".
16
- //
17
- // Class R: strings in, booleans out. No I/O, so the extension, desktop, gateway and bridge
18
- // give the same answer for the same spec.
19
-
20
- const READ_VERBS = new Set([
21
- 'get', 'list', 'search', 'read', 'find', 'fetch', 'query', 'describe', 'lookup', 'look', 'show',
22
- 'view', 'count', 'check', 'status', 'recall', 'inspect', 'screenshot', 'preview', 'browse', 'grep',
23
- 'glob', 'head', 'tail', 'cat', 'ls', 'stat', 'resolve', 'detect', 'validate', 'explain', 'summarize',
24
- 'summarise', 'summary', 'history', 'recent', 'latest', 'whoami', 'info', 'render', 'compare', 'diff',
25
- 'ping', 'health', 'select', 'retrieve', 'load', 'peek', 'watch', 'tools', 'schema', 'suggest', 'smart',
26
- ]);
27
-
28
- // Anything that changes the world without necessarily destroying part of it. A name that
29
- // carries one of these is never a read, whatever else it carries: `search_and_replace`
30
- // searches, and replaces.
31
- const WRITE_VERBS = new Set([
32
- 'create', 'update', 'set', 'post', 'put', 'send', 'write', 'add', 'edit', 'move', 'rename',
33
- 'upload', 'run', 'execute', 'exec', 'click', 'type', 'submit', 'save', 'insert', 'patch', 'apply',
34
- 'merge', 'push', 'commit', 'start', 'stop', 'enable', 'disable', 'assign', 'close', 'reopen',
35
- 'login', 'logout', 'install', 'replace', 'transfer', 'pay', 'order', 'book', 'schedule',
36
- 'publish', 'deploy', 'restart', 'import', 'sync', 'remember', 'label', 'tag', 'mark', 'fill',
37
- 'press', 'scroll', 'navigate', 'open', 'goto', 'drag', 'invoke', 'call', 'trigger', 'act',
38
- ]);
39
-
40
- const DESTRUCTIVE_VERBS = new Set([
41
- 'delete', 'remove', 'drop', 'purge', 'destroy', 'reset', 'clear', 'kill', 'wipe', 'revoke',
42
- 'truncate', 'erase', 'overwrite', 'rm', 'rmdir', 'uninstall', 'terminate', 'ban', 'force',
43
- 'unlink', 'discard', 'forget',
44
- ]);
45
-
46
- // `mcp_<server>__<tool>` → `<tool>`; `jira:delete-issue` → `delete-issue`.
47
- export function bareToolName(name) {
48
- const s = String(name || '');
49
- const i = s.lastIndexOf('__');
50
- return i >= 0 ? s.slice(i + 2) : s.replace(/^mcp[_-]/i, '');
51
- }
52
-
53
- const tokensOf = (name) => bareToolName(name).toLowerCase().split(/[^a-z0-9]+/).filter(Boolean);
54
-
55
- function hasHint(a) {
56
- return !!a && typeof a === 'object' && ['readOnlyHint', 'destructiveHint', 'idempotentHint', 'openWorldHint'].some((k) => typeof a[k] === 'boolean');
57
- }
58
-
59
- /**
60
- * @returns {{ readOnly: boolean, destructive: boolean, idempotent: boolean, openWorld: boolean, source: 'annotations'|'heuristic' }}
61
- */
62
- export function toolTraits(spec) {
63
- const name = typeof spec === 'string' ? spec : spec?.name;
64
- const a = typeof spec === 'object' && spec ? spec.annotations : null;
65
- const toks = tokensOf(name);
66
- const namedDestructive = toks.some((t) => DESTRUCTIVE_VERBS.has(t));
67
- const namedWrite = namedDestructive || toks.some((t) => WRITE_VERBS.has(t));
68
- // A read verb anywhere (`web_search`, `issue_get`) — as long as nothing in the name writes.
69
- const namedRead = !namedWrite && toks.some((t) => READ_VERBS.has(t));
70
-
71
- if (hasHint(a)) {
72
- const readOnly = a.readOnlyHint === true;
73
- // The spec's default for an absent destructiveHint is TRUE, which would put every
74
- // `create_issue` behind a confirmation. Explicit wins; absent falls back to the name.
75
- const destructive = readOnly ? false : (typeof a.destructiveHint === 'boolean' ? a.destructiveHint : namedDestructive);
76
- return {
77
- readOnly,
78
- destructive,
79
- idempotent: readOnly || a.idempotentHint === true,
80
- openWorld: typeof a.openWorldHint === 'boolean' ? a.openWorldHint : true,
81
- source: 'annotations',
82
- };
83
- }
84
- return { readOnly: namedRead, destructive: namedDestructive, idempotent: namedRead, openWorld: true, source: 'heuristic' };
85
- }
86
-
87
- /** Two reads never race each other for the world; a write is a barrier. */
88
- export const canRunConcurrently = (t) => !!t?.readOnly;
89
-
90
- /** A read asked twice has one answer, so the second can be served from the first. */
91
- export const isCacheable = (t) => !!t?.readOnly;
92
-
93
- /** Destroying is the one thing a person should be asked about, whatever the site rules say. */
94
- export const needsConfirmation = (t) => t?.destructive === true;
95
-
96
- /** Name → traits for a whole toolset, including the tools a dispatcher hides. */
97
- export function traitsIndex(specs = []) {
98
- const index = new Map();
99
- for (const s of specs) if (s?.name) index.set(s.name, toolTraits(s));
100
- return index;
101
- }
102
-
103
- /**
104
- * Ask before a destructive call — the gate, as a toolset wrapper with the dialog injected.
105
- *
106
- * Page actions have had a confirmation card since the beginning; a tool on a connected
107
- * server that DECLARES itself destructive (`destructiveHint`, or a name like `delete_repo`)
108
- * had none: the model called it and it ran. This asks first, on the REAL tool name even
109
- * when a dispatcher hides it, and refuses when there is nobody to ask — a background turn
110
- * with no window cannot destroy anything a person did not approve.
111
- *
112
- * @param confirm async ({ name, input, via }) => 'allow' | 'always' | 'deny'. `always`
113
- * allows this tool for the rest of the toolset's life (one session).
114
- * Omit it on a surface with no window: destructive calls are then refused
115
- * with a message the model can act on.
116
- * @param only `(name) => boolean` — which top-level tools the gate covers (a host that
117
- * already confirms its page and note tools passes its remote set).
118
- * @param traitsOf `(name, input) => traits`; defaults to the toolset's index, then the name.
119
- */
120
- export function withDestructiveGate(toolset, { confirm = null, only = () => true, traitsOf = null, effectiveName = defaultEffectiveName } = {}) {
121
- if (!toolset || typeof toolset.execute !== 'function') return toolset;
122
- const base = toolset.execute.bind(toolset);
123
- const byName = new Map((toolset.specs || []).filter((s) => s?.name).map((s) => [s.name, s]));
124
- const allowed = new Set();
125
- const traits = traitsOf || ((name, input) => {
126
- const eff = effectiveName(name, input);
127
- return toolset.traits?.get(eff) || toolset.traits?.get(name) || toolTraits(byName.get(eff) || byName.get(name) || { name: eff });
128
- });
129
- return {
130
- ...toolset,
131
- async execute(name, input, meta) {
132
- if (!only(name)) return base(name, input, meta);
133
- const t = traits(name, input);
134
- if (!needsConfirmation(t)) return base(name, input, meta);
135
- const eff = effectiveName(name, input);
136
- if (allowed.has(eff)) return base(name, input, meta);
137
- if (!confirm) {
138
- return JSON.stringify({
139
- error: `"${eff}" is a destructive action and needs the user's confirmation, which this surface cannot ask for. Do not retry it; tell the user what you would do and let them run it from the side panel.`,
140
- blocked: true, needsConfirmation: true, tool: eff,
141
- });
142
- }
143
- const decision = await confirm({ name: eff, input, via: name === eff ? null : name, traits: t });
144
- if (decision === 'always') allowed.add(eff);
145
- if (decision !== 'allow' && decision !== 'always') {
146
- return JSON.stringify({ error: `The user DECLINED "${eff}". Do not retry it — stop and ask the user how to proceed.`, blocked: true, declined: true, tool: eff });
147
- }
148
- return base(name, input, meta);
149
- },
150
- };
151
- }
152
-
153
- // A dispatcher carries the real action in `input.action`; every name-based policy must
154
- // see through it or `mcp {action:"mcp_x__delete_repo"}` is judged by the name "mcp" — and
155
- // `page {action:'screenshot'}` taken four times looks like a stuck loop instead of a look.
156
- // One definition: the extension, the desktop and the gate each had their own copy.
157
- export function effectiveToolName(name, input) {
158
- const action = input && typeof input === 'object' ? input.action : null;
159
- return typeof action === 'string' && action ? action : name;
160
- }
161
- const defaultEffectiveName = effectiveToolName;
162
-
163
- // Local tools whose reads may overlap in one round: they touch the user's own data or the
164
- // network, never the one tab a page tool is driving. Everything not remote and not here
165
- // runs one at a time, whatever its name says — a wrong "parallel" races the world, a wrong
166
- // "serial" only costs latency. The `find` dispatcher is here as a whole: everything behind
167
- // it is a read of the user's data or the web (its writes are separate tools by design).
168
- //
169
- // ONE list. The extension and the desktop each kept their own and they drifted within
170
- // weeks — the desktop serialised `recall` and `skill_open` that the extension overlapped.
171
- export const PARALLEL_LOCAL_RE = /^(find$|history_|web_search$|weather$|get_result$|skill_open$|skill_file$|recall$|memory_recall$|meeting_live_transcript$)/;
172
-
173
- /**
174
- * May this call share a batch with its neighbours? Read-only by its traits, not pinned
175
- * serial by the toolset, and either remote (its own server) or on the local overlap list.
176
- */
177
- export function parallelEligible(tools, call, traits) {
178
- if (!traits?.readOnly) return false;
179
- if (tools?.serialTools?.has(call.name)) return false;
180
- const eff = effectiveToolName(call.name, call.input);
181
- return !!tools?.remoteTools?.has(call.name) || PARALLEL_LOCAL_RE.test(eff) || PARALLEL_LOCAL_RE.test(call.name);
182
- }
1
+ const w=new Set(["get","list","search","read","find","fetch","query","describe","lookup","look","show","view","count","check","status","recall","inspect","screenshot","preview","browse","grep","glob","head","tail","cat","ls","stat","resolve","detect","validate","explain","summarize","summarise","summary","history","recent","latest","whoami","info","render","compare","diff","ping","health","select","retrieve","load","peek","watch","tools","schema","suggest","smart"]),b=new Set(["create","update","set","post","put","send","write","add","edit","move","rename","upload","run","execute","exec","click","type","submit","save","insert","patch","apply","merge","push","commit","start","stop","enable","disable","assign","close","reopen","login","logout","install","replace","transfer","pay","order","book","schedule","publish","deploy","restart","import","sync","remember","label","tag","mark","fill","press","scroll","navigate","open","goto","drag","invoke","call","trigger","act"]),v=new Set(["delete","remove","drop","purge","destroy","reset","clear","kill","wipe","revoke","truncate","erase","overwrite","rm","rmdir","uninstall","terminate","ban","force","unlink","discard","forget"]);function k(e){const t=String(e||""),r=t.lastIndexOf("__");return r>=0?t.slice(r+2):t.replace(/^mcp[_-]/i,"")}const x=e=>k(e).toLowerCase().split(/[^a-z0-9]+/).filter(Boolean);function _(e){return!!e&&typeof e=="object"&&["readOnlyHint","destructiveHint","idempotentHint","openWorldHint"].some(t=>typeof e[t]=="boolean")}function h(e){const t=typeof e=="string"?e:e?.name,r=typeof e=="object"&&e?e.annotations:null,c=x(t),l=c.some(o=>v.has(o)),d=!(l||c.some(o=>b.has(o)))&&c.some(o=>w.has(o));if(_(r)){const o=r.readOnlyHint===!0,f=o?!1:typeof r.destructiveHint=="boolean"?r.destructiveHint:l;return{readOnly:o,destructive:f,idempotent:o||r.idempotentHint===!0,openWorld:typeof r.openWorldHint=="boolean"?r.openWorldHint:!0,source:"annotations"}}return{readOnly:d,destructive:l,idempotent:d,openWorld:!0,source:"heuristic"}}const H=e=>!!e?.readOnly,S=e=>!!e?.readOnly,E=e=>e?.destructive===!0;function $(e=[]){const t=new Map;for(const r of e)r?.name&&t.set(r.name,h(r));return t}function R(e,{confirm:t=null,only:r=()=>!0,traitsOf:c=null,effectiveName:l=O}={}){if(!e||typeof e.execute!="function")return e;const u=e.execute.bind(e),d=new Map((e.specs||[]).filter(n=>n?.name).map(n=>[n.name,n])),o=new Set,f=c||((n,s)=>{const i=l(n,s);return e.traits?.get(i)||e.traits?.get(n)||h(d.get(i)||d.get(n)||{name:i})});return{...e,async execute(n,s,i){if(!r(n))return u(n,s,i);const m=f(n,s);if(!E(m))return u(n,s,i);const a=l(n,s);if(o.has(a))return u(n,s,i);if(!t)return JSON.stringify({error:`"${a}" is a destructive action and needs the user's confirmation, which this surface cannot ask for. Do not retry it; tell the user what you would do and let them run it from the side panel.`,blocked:!0,needsConfirmation:!0,tool:a});const p=await t({name:a,input:s,via:n===a?null:n,traits:m});return p==="always"&&o.add(a),p!=="allow"&&p!=="always"?JSON.stringify({error:`The user DECLINED "${a}". Do not retry it — stop and ask the user how to proceed.`,blocked:!0,declined:!0,tool:a}):u(n,s,i)}}}function y(e,t){const r=t&&typeof t=="object"?t.action:null;return typeof r=="string"&&r?r:e}const O=y,g=/^(find$|history_|web_search$|weather$|get_result$|skill_open$|skill_file$|recall$|memory_recall$|meeting_live_transcript$)/;function T(e,t,r){if(!r?.readOnly||e?.serialTools?.has(t.name))return!1;const c=y(t.name,t.input);return!!e?.remoteTools?.has(t.name)||g.test(c)||g.test(t.name)}export{g as PARALLEL_LOCAL_RE,k as bareToolName,H as canRunConcurrently,y as effectiveToolName,S as isCacheable,E as needsConfirmation,T as parallelEligible,h as toolTraits,$ as traitsIndex,R as withDestructiveGate};
package/toolset.js CHANGED
@@ -1,89 +1,3 @@
1
- // A generic tool registry ANY number of tool providers merged into the one shape a model
2
- // loop consumes: `{ specs, execute, system }`.
3
- //
4
- // A provider is `{ specs: ToolSpec[], execute(name, input, meta) => string | {text, note,
5
- // image}, system?: string, remote?: boolean, serial?: boolean, traits?: Map, reach?: [] }`.
6
- // ToolSpec is `{ name, description, parameters (JSON schema), annotations? }`.
7
- //
8
- // This lived in the extension for as long as the extension was the only client with a tool
9
- // loop. The desktop grew one, and the second copy of "first provider to claim a name wins"
10
- // would have been the second place that rule could quietly differ. Everything here is
11
- // input → output; the one platform-flavoured thing — the shared MCP guidance a client
12
- // prepends when any `mcp_*` tool is present — is INJECTED, so this file carries no prompt
13
- // text of its own and stays off the extension's first-paint budget by exactly the bytes
14
- // the old copy cost.
1
+ const l=/^mcp[_-]/i;function S(g,{mcpSystem:i=""}={}){const s=(g||[]).filter(t=>t&&t.specs?.length);if(!s.length)return;const o=[],a=new Map,h=new Set,c=new Map,m=new Set,d=[],f=new Map;for(const t of s){const n=t.remote===!0;if(t.traits instanceof Map)for(const[e,r]of t.traits)c.has(e)||c.set(e,r);if(Array.isArray(t.reach)&&t.specs.length===1)for(const e of t.reach)e?.name&&!f.has(e.name)&&(f.set(e.name,t.specs[0].name),d.push(e));for(const e of t.specs)a.has(e.name)||(o.push(e),a.set(e.name,t.execute),(n||l.test(String(e.name||"")))&&h.add(e.name),t.serial===!0&&m.add(e.name))}if(!o.length)return;const p=o.some(t=>l.test(String(t?.name||"")))?String((typeof i=="function"?i():i)||""):"",M=[p,...s.map(t=>t.system)].map(t=>String(t||"").trim()).filter(Boolean).join(`
15
2
 
16
- const REMOTE_NAME_RE = /^mcp[_-]/i;
17
-
18
- /**
19
- * @param providers the tool providers, in the order the model should read them
20
- * @param mcpSystem the shared MCP rules — a string, or a function returning one, consulted
21
- * only when an `mcp_*` tool is present so a turn without MCP pays nothing
22
- * @returns the merged toolset, or `undefined` when no provider brought a tool
23
- */
24
- export function buildToolset(providers, { mcpSystem = '' } = {}) {
25
- const list = (providers || []).filter((p) => p && p.specs?.length);
26
- if (!list.length) return undefined;
27
-
28
- const specs = [];
29
- const route = new Map(); // tool name -> the provider.execute that owns it
30
- // Tools that call a REMOTE server — from a provider flagged remote, or (fallback) whose
31
- // name matches the mcp_ convention. The PII harness uses this exact set to keep private
32
- // data off remote tools under "redact remote".
33
- const remoteTools = new Set();
34
- // What each HIDDEN tool does — a dispatcher's own index of the tools behind it
35
- // (tool-traits.js). Top-level specs carry `annotations` and are classified at run time.
36
- const traits = new Map();
37
- // Tools that must run one at a time even when read-only: page tools share ONE tab.
38
- const serialTools = new Set();
39
- // The tools a dispatcher hides, and which dispatcher: a recipe step names the real tool.
40
- const reach = [];
41
- const hiddenVia = new Map();
42
- for (const p of list) {
43
- const providerRemote = p.remote === true;
44
- if (p.traits instanceof Map) for (const [k, v] of p.traits) if (!traits.has(k)) traits.set(k, v);
45
- if (Array.isArray(p.reach) && p.specs.length === 1) {
46
- for (const h of p.reach) if (h?.name && !hiddenVia.has(h.name)) { hiddenVia.set(h.name, p.specs[0].name); reach.push(h); }
47
- }
48
- for (const s of p.specs) {
49
- if (route.has(s.name)) continue; // first provider to claim a name wins
50
- specs.push(s);
51
- route.set(s.name, p.execute);
52
- if (providerRemote || REMOTE_NAME_RE.test(String(s.name || ''))) remoteTools.add(s.name);
53
- if (p.serial === true) serialTools.add(s.name);
54
- }
55
- }
56
- if (!specs.length) return undefined;
57
-
58
- // Generic MCP rules ONCE (not repeated per server), then each provider's own inventory.
59
- const hasMcp = specs.some((s) => REMOTE_NAME_RE.test(String(s?.name || '')));
60
- const shared = hasMcp ? String((typeof mcpSystem === 'function' ? mcpSystem() : mcpSystem) || '') : '';
61
- const parts = [shared, ...list.map((p) => p.system)];
62
- const system = parts.map((x) => String(x || '').trim()).filter(Boolean).join('\n\n') || undefined;
63
- // WHICH blurb costs what — one total for the whole preamble is visible but unattributable,
64
- // and a number nobody can attribute is a number nobody can reduce.
65
- const systemParts = {};
66
- if (shared.trim()) systemParts.mcp = Math.round(shared.length / 4);
67
- for (const p of list) {
68
- const t = Math.round(String(p.system || '').trim().length / 4);
69
- // Named by the dispatcher tool it owns — 'page', 'find', 'mcp' — which is what the
70
- // reader sees in the tools list and can act on.
71
- if (t) systemParts[p.id || p.specs[0]?.name || 'group'] = t;
72
- }
73
-
74
- return {
75
- specs,
76
- system,
77
- systemParts,
78
- remoteTools,
79
- traits,
80
- serialTools,
81
- reach,
82
- hiddenVia,
83
- async execute(name, input, meta = {}) {
84
- const fn = route.get(name);
85
- if (!fn) return JSON.stringify({ error: `Unknown tool: ${name}` });
86
- return fn(name, input, meta);
87
- },
88
- };
89
- }
3
+ `)||void 0,u={};p.trim()&&(u.mcp=Math.round(p.length/4));for(const t of s){const n=Math.round(String(t.system||"").trim().length/4);n&&(u[t.id||t.specs[0]?.name||"group"]=n)}return{specs:o,system:M,systemParts:u,remoteTools:h,traits:c,serialTools:m,reach:d,hiddenVia:f,async execute(t,n,e={}){const r=a.get(t);return r?r(t,n,e):JSON.stringify({error:`Unknown tool: ${t}`})}}}export{S as buildToolset};