@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.
- package/adapters.js +1 -83
- package/adaptive-tool-policy.js +1 -45
- package/agent.js +2 -255
- package/attribution.js +1 -132
- package/backup-envelope.js +1 -221
- package/board-tool.js +2 -165
- package/budget.js +1 -116
- package/capability.js +1 -126
- package/citations.js +5 -127
- package/client-prefs.js +1 -148
- package/context-attachments.js +8 -144
- package/cowriter-router.js +1 -83
- package/cowriter-writer.js +19 -193
- package/cowriter.js +1 -190
- package/curate.js +3 -509
- package/distance.js +1 -124
- package/engine.js +1 -131
- package/entitlement.js +1 -332
- package/entity.js +1 -354
- package/event.js +1 -175
- package/extraction.js +4 -293
- package/failover.js +1 -93
- package/find-tool.js +1 -66
- package/flowchart.js +2 -644
- package/gate.js +1 -74
- package/harness.js +4 -101
- package/index.js +1 -345
- package/invariants.js +1 -174
- package/job.js +3 -147
- package/kernel.js +1 -255
- package/knowledge-derive.js +1 -267
- package/knowledge.js +3 -230
- package/library.js +11 -280
- package/loop.js +1 -132
- package/manifest.js +1 -107
- package/markdown-authoring.js +11 -288
- package/markdown-render.js +0 -0
- package/mcp-client.js +8 -358
- package/mcp-dispatch.js +1 -52
- package/mcp-errors.js +1 -106
- package/mcp-manager.js +1 -127
- package/media-transcript.js +8 -531
- package/meeting-analyzers.js +1 -83
- package/meeting-insights.js +7 -164
- package/meeting-shape.js +1 -282
- package/meeting-text.js +5 -94
- package/memory.js +2 -638
- package/model-candidates.js +1 -358
- package/model-health.js +1 -182
- package/model-ledger.js +1 -228
- package/model-picker.js +1 -188
- package/note-actions.js +15 -246
- package/note-graph.js +1 -158
- package/note-links.js +1 -68
- package/note-mentions.js +8 -114
- package/note-plan.js +9 -107
- package/note-research.js +1 -135
- package/observability.js +1 -119
- package/omni.js +1 -125
- package/order.js +1 -78
- package/package.json +4 -5
- package/pdf-layout.js +7 -253
- package/policy.js +1 -0
- package/project-run.js +20 -444
- package/project.js +1 -170
- package/promotion.js +1 -171
- package/queue.js +1 -98
- package/reach.js +1 -23
- package/recipe-tool.js +2 -161
- package/recipe.js +1 -264
- package/record-list.js +2 -110
- package/recruit.js +3 -456
- package/redaction-tokens.js +1 -61
- package/ref.js +1 -55
- package/registry.js +1 -240
- package/route-graph.js +1 -115
- package/route-strategies.js +2 -232
- package/router.js +2 -880
- package/rrf.js +1 -89
- package/rules.js +1 -142
- package/schedule.js +1 -663
- package/scm-connection.js +1 -180
- package/scopes.js +1 -12
- package/scorecard.js +1 -335
- package/search-engines.js +1 -81
- package/skill-manifest.js +1 -330
- package/skill-scan.js +3 -209
- package/skill-sources.js +1 -162
- package/skill-vars.js +2 -252
- package/slash-commands.js +1 -158
- package/source-gate.js +1 -92
- package/sources-retrieval.js +5 -186
- package/sources.js +1 -273
- package/store.js +1 -171
- package/structured.js +4 -901
- package/subject-kinds.js +1 -5
- package/subject-name.js +1 -96
- package/sync-plan.js +1 -213
- package/synthesis.js +3 -123
- package/tags.js +1 -203
- package/team-board.js +6 -337
- package/team-cache.js +2 -63
- package/team-observe.js +1 -118
- package/team-org.js +1 -442
- package/team-plan.js +4 -131
- package/team-record.js +1 -225
- package/team-run.js +9 -652
- package/team-subtask.js +2 -191
- package/team-task.js +2 -124
- package/team-tool.js +2 -196
- package/team-trail.js +2 -73
- package/team-worklog.js +2 -149
- package/team.js +1 -302
- package/text-search.js +1 -155
- package/theme.js +17 -154
- package/titles.js +4 -285
- package/tool-discovery.js +1 -87
- package/tool-dispatch.js +5 -230
- package/tool-groups.js +1 -81
- package/tool-hints.js +10 -185
- package/tool-loop-guard.js +4 -182
- package/tool-need.js +2 -124
- package/tool-result.js +2 -438
- package/tool-round.js +0 -0
- package/tool-schema.js +1 -155
- package/tool-traits.js +1 -182
- package/toolset.js +2 -88
- package/trajectory.js +1 -530
- package/turn-loop.js +4 -440
- package/upcast.js +1 -37
- package/vault.js +2 -250
- package/view.js +1 -88
- package/voice-intents.js +2 -1488
- package/voice-speaker.js +1 -98
- package/weather-tool.js +1 -65
- package/weather.js +2 -211
- package/web-search-tool.js +14 -102
- package/web-search.js +1 -160
- package/widget.js +1 -153
package/skill-sources.js
CHANGED
|
@@ -1,162 +1 @@
|
|
|
1
|
-
|
|
2
|
-
//
|
|
3
|
-
// The instinct when adding "browse a skills hub" is to build the panel the MCP registry
|
|
4
|
-
// bar was built as: one file that knows one API. That is how `history-rag.js` came to
|
|
5
|
-
// hand-write three loaders, and how adding a fourth meant editing four files. The places
|
|
6
|
-
// skills come from are already several — a local bridge, skills.sh, GitHub taps, a site
|
|
7
|
-
// publishing /.well-known/skills, and whatever appears next — and they differ only in how
|
|
8
|
-
// three functions are implemented.
|
|
9
|
-
//
|
|
10
|
-
// So a source is a registration. Register one and browsing, searching and installing gain
|
|
11
|
-
// it at once, because each consumes the registry rather than a list.
|
|
12
|
-
//
|
|
13
|
-
// A SOURCE IS A CAPABILITY. `reads` is the declared access statement (P10) and `trust` is
|
|
14
|
-
// the level everything it produces inherits — a source cannot promote its own skills, and
|
|
15
|
-
// nothing fetched is ever `built-in`.
|
|
16
|
-
//
|
|
17
|
-
// ISOLATION IS THE POINT. One hub being down, slow, or returning garbage must cost that
|
|
18
|
-
// hub's section of the results and nothing else. A registry whose failure mode is "no
|
|
19
|
-
// skills anywhere" would be worse than the hardcoded list it replaced.
|
|
20
|
-
//
|
|
21
|
-
// WHAT IS SHARED IS THE CONTRACT, NOT THE SOURCE. Searching is pure orchestration and
|
|
22
|
-
// lives here; the fetch a real source performs needs a platform — the extension's
|
|
23
|
-
// `secureFetch`, the bridge's Node http, a mobile client's own stack — and is injected at
|
|
24
|
-
// registration, the same split `adapters.js` makes for execution.
|
|
25
|
-
|
|
26
|
-
import { DATA_SCOPES } from './scopes.js';
|
|
27
|
-
|
|
28
|
-
export class SkillSourceError extends Error {
|
|
29
|
-
constructor(code, message) { super(message); this.name = 'SkillSourceError'; this.code = code; }
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Trust a source confers on everything it produces. There is no `built-in` here on
|
|
34
|
-
* purpose: only skills compiled into the product are ours, and no registration can claim
|
|
35
|
-
* that. `local` is a source on this machine (the bridge's store); `community` is anything
|
|
36
|
-
* off the network.
|
|
37
|
-
*/
|
|
38
|
-
export const SOURCE_TRUST = Object.freeze(['local', 'community']);
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Declare a source.
|
|
42
|
-
*
|
|
43
|
-
* @param id stable id; also the `origin.source` stamped on everything it yields
|
|
44
|
-
* @param label human name, for the picker and for provenance
|
|
45
|
-
* @param trust 'local' | 'community' — inherited, never self-assigned per skill
|
|
46
|
-
* @param reads declared access, e.g. ['net'] for a hub, [] for the local bridge
|
|
47
|
-
* @param list ({ query, cursor }) -> { items, nextCursor? }. `items` are RECORDS
|
|
48
|
-
* (name + description + files), never bodies — level 0 of the ladder
|
|
49
|
-
* @param read (id) -> a full record including its prompt. Level 1
|
|
50
|
-
* @param readFile (id, path) -> { path, text }. Level 2, optional: a source with no
|
|
51
|
-
* packages simply omits it
|
|
52
|
-
* @param available () -> boolean|Promise<boolean>. A source that cannot answer right now
|
|
53
|
-
* (bridge down, no network) is ABSENT rather than broken — the difference
|
|
54
|
-
* between "nothing here" and an error the user must interpret
|
|
55
|
-
*/
|
|
56
|
-
export function defineSkillSource({
|
|
57
|
-
id, label, trust = 'community', reads = [], list, read, readFile = null, available = null,
|
|
58
|
-
}) {
|
|
59
|
-
if (!id || typeof id !== 'string') throw new SkillSourceError('BAD_SOURCE', 'source.id required');
|
|
60
|
-
if (!SOURCE_TRUST.includes(trust)) {
|
|
61
|
-
throw new SkillSourceError('BAD_SOURCE', `source '${id}': trust must be one of ${SOURCE_TRUST}`);
|
|
62
|
-
}
|
|
63
|
-
if (!Array.isArray(reads) || !reads.every((r) => DATA_SCOPES.includes(r))) {
|
|
64
|
-
throw new SkillSourceError('BAD_SOURCE', `source '${id}': reads must be within ${DATA_SCOPES}`);
|
|
65
|
-
}
|
|
66
|
-
if (typeof list !== 'function') throw new SkillSourceError('BAD_SOURCE', `source '${id}': list() required`);
|
|
67
|
-
if (typeof read !== 'function') throw new SkillSourceError('BAD_SOURCE', `source '${id}': read() required`);
|
|
68
|
-
return Object.freeze({
|
|
69
|
-
id,
|
|
70
|
-
label: label || id,
|
|
71
|
-
trust,
|
|
72
|
-
reads: Object.freeze([...reads]),
|
|
73
|
-
list,
|
|
74
|
-
read,
|
|
75
|
-
readFile: typeof readFile === 'function' ? readFile : null,
|
|
76
|
-
available: typeof available === 'function' ? available : () => true,
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/** The registry a host binds sources into. Insertion-ordered, so results are stable. */
|
|
81
|
-
export function createSkillSourceRegistry() {
|
|
82
|
-
const sources = new Map();
|
|
83
|
-
return {
|
|
84
|
-
/** Register a source. Returns its remover, so registration is revertible (P15). */
|
|
85
|
-
add(source) {
|
|
86
|
-
if (sources.has(source.id)) throw new SkillSourceError('DUPLICATE', `source '${source.id}' already registered`);
|
|
87
|
-
sources.set(source.id, source);
|
|
88
|
-
return () => sources.delete(source.id);
|
|
89
|
-
},
|
|
90
|
-
|
|
91
|
-
list() { return [...sources.values()]; },
|
|
92
|
-
get(id) { return sources.get(id) || null; },
|
|
93
|
-
has(id) { return sources.has(id); },
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Ask every available source at once.
|
|
97
|
-
*
|
|
98
|
-
* Returns one section PER SOURCE rather than a merged list: a merged list would have
|
|
99
|
-
* to rank across hubs that share no scoring, and it would hide which source an entry
|
|
100
|
-
* came from at exactly the moment that matters. A source that throws yields an
|
|
101
|
-
* `error` section and never rejects the call.
|
|
102
|
-
*/
|
|
103
|
-
async search({ query = '', cursor = '', only = null } = {}) {
|
|
104
|
-
const wanted = [...sources.values()].filter((s) => !only || only.includes(s.id));
|
|
105
|
-
return Promise.all(wanted.map(async (source) => {
|
|
106
|
-
try {
|
|
107
|
-
if (!(await source.available())) return { source: source.id, label: source.label, items: [], absent: true };
|
|
108
|
-
const page = (await source.list({ query, cursor })) || {};
|
|
109
|
-
const items = Array.isArray(page.items) ? page.items : [];
|
|
110
|
-
return {
|
|
111
|
-
source: source.id,
|
|
112
|
-
label: source.label,
|
|
113
|
-
trust: source.trust,
|
|
114
|
-
// Provenance is stamped HERE, from the registration — a source that could
|
|
115
|
-
// label its own results could label them as something more trusted.
|
|
116
|
-
items: items.map((skill) => stampOrigin(skill, source)),
|
|
117
|
-
...(page.nextCursor ? { nextCursor: page.nextCursor } : {}),
|
|
118
|
-
};
|
|
119
|
-
} catch (e) {
|
|
120
|
-
return { source: source.id, label: source.label, items: [], error: String(e?.message || e) };
|
|
121
|
-
}
|
|
122
|
-
}));
|
|
123
|
-
},
|
|
124
|
-
|
|
125
|
-
/** One skill, body included, with provenance stamped the same way. */
|
|
126
|
-
async read(sourceId, skillId) {
|
|
127
|
-
const source = sources.get(sourceId);
|
|
128
|
-
if (!source) throw new SkillSourceError('UNKNOWN_SOURCE', `no source '${sourceId}'`);
|
|
129
|
-
const skill = await source.read(skillId);
|
|
130
|
-
return skill ? stampOrigin(skill, source) : null;
|
|
131
|
-
},
|
|
132
|
-
|
|
133
|
-
async readFile(sourceId, skillId, path) {
|
|
134
|
-
const source = sources.get(sourceId);
|
|
135
|
-
if (!source) throw new SkillSourceError('UNKNOWN_SOURCE', `no source '${sourceId}'`);
|
|
136
|
-
if (!source.readFile) throw new SkillSourceError('NO_FILES', `source '${sourceId}' has no package files`);
|
|
137
|
-
return source.readFile(skillId, path);
|
|
138
|
-
},
|
|
139
|
-
};
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* Stamp where a record came from, overriding whatever it claimed.
|
|
144
|
-
*
|
|
145
|
-
* A skill arriving from a source does not get to say which source it came from, or that
|
|
146
|
-
* it is ours: `builtin` is cleared here, and `trustOf()` then derives `community` from the
|
|
147
|
-
* presence of an origin. The record's own `origin.hash` survives — that is the content
|
|
148
|
-
* identity the scanner and the update check compare against, and only the fetcher knows it.
|
|
149
|
-
*/
|
|
150
|
-
function stampOrigin(skill, source) {
|
|
151
|
-
if (!skill || typeof skill !== 'object') return skill;
|
|
152
|
-
const claimed = skill.origin && typeof skill.origin === 'object' ? skill.origin : {};
|
|
153
|
-
return {
|
|
154
|
-
...skill,
|
|
155
|
-
builtin: false,
|
|
156
|
-
origin: {
|
|
157
|
-
...claimed,
|
|
158
|
-
source: source.id,
|
|
159
|
-
id: String(claimed.id || skill.id || ''),
|
|
160
|
-
},
|
|
161
|
-
};
|
|
162
|
-
}
|
|
1
|
+
import{DATA_SCOPES as l}from"./scopes.js";class s extends Error{constructor(r,n){super(n),this.name="SkillSourceError",this.code=r}}const f=Object.freeze(["local","community"]);function S({id:e,label:r,trust:n="community",reads:i=[],list:o,read:t,readFile:a=null,available:u=null}){if(!e||typeof e!="string")throw new s("BAD_SOURCE","source.id required");if(!f.includes(n))throw new s("BAD_SOURCE",`source '${e}': trust must be one of ${f}`);if(!Array.isArray(i)||!i.every(c=>l.includes(c)))throw new s("BAD_SOURCE",`source '${e}': reads must be within ${l}`);if(typeof o!="function")throw new s("BAD_SOURCE",`source '${e}': list() required`);if(typeof t!="function")throw new s("BAD_SOURCE",`source '${e}': read() required`);return Object.freeze({id:e,label:r||e,trust:n,reads:Object.freeze([...i]),list:o,read:t,readFile:typeof a=="function"?a:null,available:typeof u=="function"?u:()=>!0})}function m(){const e=new Map;return{add(r){if(e.has(r.id))throw new s("DUPLICATE",`source '${r.id}' already registered`);return e.set(r.id,r),()=>e.delete(r.id)},list(){return[...e.values()]},get(r){return e.get(r)||null},has(r){return e.has(r)},async search({query:r="",cursor:n="",only:i=null}={}){const o=[...e.values()].filter(t=>!i||i.includes(t.id));return Promise.all(o.map(async t=>{try{if(!await t.available())return{source:t.id,label:t.label,items:[],absent:!0};const a=await t.list({query:r,cursor:n})||{},u=Array.isArray(a.items)?a.items:[];return{source:t.id,label:t.label,trust:t.trust,items:u.map(c=>d(c,t)),...a.nextCursor?{nextCursor:a.nextCursor}:{}}}catch(a){return{source:t.id,label:t.label,items:[],error:String(a?.message||a)}}}))},async read(r,n){const i=e.get(r);if(!i)throw new s("UNKNOWN_SOURCE",`no source '${r}'`);const o=await i.read(n);return o?d(o,i):null},async readFile(r,n,i){const o=e.get(r);if(!o)throw new s("UNKNOWN_SOURCE",`no source '${r}'`);if(!o.readFile)throw new s("NO_FILES",`source '${r}' has no package files`);return o.readFile(n,i)}}}function d(e,r){if(!e||typeof e!="object")return e;const n=e.origin&&typeof e.origin=="object"?e.origin:{};return{...e,builtin:!1,origin:{...n,source:r.id,id:String(n.id||e.id||"")}}}export{f as SOURCE_TRUST,s as SkillSourceError,m as createSkillSourceRegistry,S as defineSkillSource};
|
package/skill-vars.js
CHANGED
|
@@ -1,253 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
//
|
|
3
|
-
// A skill prompt can interpolate a few runtime values: what the user typed, the
|
|
4
|
-
// selection, the page URL/title, today's date. Until this module existed that set
|
|
5
|
-
// lived as a regex chain inside the extension's side panel, and nothing else knew
|
|
6
|
-
// it — so three things drifted apart:
|
|
7
|
-
//
|
|
8
|
-
// • the EDITOR advertised placeholders in a placeholder attribute,
|
|
9
|
-
// • the PANEL substituted a different set,
|
|
10
|
-
// • prompt-assist was told to "preserve any {{placeholders}} verbatim" without
|
|
11
|
-
// being told which ones exist — so the model invented {{content}}, guarded it,
|
|
12
|
-
// and the user shipped a prompt with a slot nothing would ever fill.
|
|
13
|
-
//
|
|
14
|
-
// That last failure is silent by construction: an unknown placeholder is just text,
|
|
15
|
-
// so the model receives the literal characters `{{content}}` and usually papers over
|
|
16
|
-
// it. The skill looks broken for a reason no surface names. Declaring the set once
|
|
17
|
-
// makes the same list authoritative for the editor's lint, the assist prompt, and
|
|
18
|
-
// substitution — a fourth client (mobile, the gateway, the bridge) inherits it
|
|
19
|
-
// rather than re-deriving it.
|
|
20
|
-
//
|
|
21
|
-
// Platform access is INJECTED, never imported: the page URL comes from `chrome.tabs`
|
|
22
|
-
// in the extension, from the request in the gateway, and from nothing at all in a
|
|
23
|
-
// batch run. This module knows the contract; the host knows how to satisfy it.
|
|
1
|
+
class d extends Error{constructor(e,o){super(o),this.name="SkillVarError",this.code=e}}const u=Object.freeze([Object.freeze({name:"input",source:"args",labelled:!0,summary:"What the user typed after the /command — or whatever is already in the composer when they pick the skill from the menu."}),Object.freeze({name:"selection",source:"resolver",summary:"The text selected on the page right now."}),Object.freeze({name:"url",source:"resolver",summary:"The active tab's URL."}),Object.freeze({name:"title",source:"resolver",summary:"The active tab's title."}),Object.freeze({name:"date",source:"resolver",summary:"Today's date."})]),w=Object.freeze(u.map(t=>t.name)),m=new Map(u.map(t=>[t.name,t]));function g(t){return m.get(String(t||"").trim().toLowerCase())||null}const p=/\{\{\s*([a-z_][a-z0-9_-]*)\s*(?::([^}]*))?\s*\}\}/gi;function k(t){const e=g(t);if(!e)throw new d("UNKNOWN_VAR",`unknown skill variable '${t}'`);return e.labelled?new RegExp(`\\{\\{\\s*${e.name}(?::[^}]*)?\\s*\\}\\}`,"gi"):new RegExp(`\\{\\{\\s*${e.name}\\s*\\}\\}`,"gi")}function x(t){const e=[],o=String(t||"");p.lastIndex=0;let n=p.exec(o);for(;n;){const r=n[1].toLowerCase();e.push({name:r,label:(n[2]||"").trim(),raw:n[0],index:n.index,known:m.has(r)}),n=p.exec(o)}return e}function y(t,e){const o=t.length,n=e.length;let r=Array.from({length:n+1},(s,i)=>i);for(let s=1;s<=o;s+=1){const i=[s];for(let a=1;a<=n;a+=1)i[a]=Math.min(r[a]+1,i[a-1]+1,r[a-1]+(t[s-1]===e[a-1]?0:1));r=i}return r[n]}const S=new Set(["content","text","body","draft","document","doc","args","arguments","query","question","task","request","prompt","message","user_input","userinput"]);function b(t){const e=String(t||"").trim().toLowerCase();if(!e||m.has(e))return"";if(S.has(e))return"input";let o="",n=1/0;for(const r of w){const s=y(e,r);s<n&&(n=s,o=r)}return n<=Math.max(2,Math.floor(e.length/3))?o:""}function f(t){const e=x(t),o=[],n=[],r=new Set;for(const s of e)r.has(s.name)||(r.add(s.name),s.known?o.push(s.name):n.push({name:s.name,raw:s.raw,suggestion:b(s.name)}));return{known:o,unknown:n,hasInput:r.has("input")}}async function v(t,{args:e="",resolvers:o={}}={}){const n=String(t||""),r=f(n);if(!r.known.length)return{text:n,filled:[],empty:[],unknown:r.unknown};const s=[],i=[];let a=n;for(const c of r.known){let l="";if(c==="input")l=e==null?"":String(e);else{const h=o[c];if(typeof h=="function")try{l=await h()??""}catch{l=""}l=String(l)}l.trim()?s.push(c):i.push(c),a=a.replace(k(c),()=>l)}return{text:a,filled:s,empty:i,unknown:r.unknown}}async function V(t,{args:e="",resolvers:o={}}={}){const n=String(t||""),r=String(e||"").trim(),s=f(n).hasInput,i=n+(!s&&r?`
|
|
24
2
|
|
|
25
|
-
export
|
|
26
|
-
constructor(code, message) { super(message); this.name = 'SkillVarError'; this.code = code; }
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* The complete set. `source` says where the value comes from, which is what the
|
|
31
|
-
* lint and the assist guidance need to explain it:
|
|
32
|
-
* 'args' — the text the user supplied with the invocation
|
|
33
|
-
* 'resolver' — the host fills it (page URL, title, selection, date)
|
|
34
|
-
*/
|
|
35
|
-
export const SKILL_VARS = Object.freeze([
|
|
36
|
-
Object.freeze({
|
|
37
|
-
name: 'input',
|
|
38
|
-
source: 'args',
|
|
39
|
-
labelled: true, // {{input:label}} — the label is a hint to the author, not sent
|
|
40
|
-
summary: 'What the user typed after the /command — or whatever is already in the composer when they pick the skill from the menu.',
|
|
41
|
-
}),
|
|
42
|
-
Object.freeze({
|
|
43
|
-
name: 'selection',
|
|
44
|
-
source: 'resolver',
|
|
45
|
-
summary: 'The text selected on the page right now.',
|
|
46
|
-
}),
|
|
47
|
-
Object.freeze({
|
|
48
|
-
name: 'url',
|
|
49
|
-
source: 'resolver',
|
|
50
|
-
summary: "The active tab's URL.",
|
|
51
|
-
}),
|
|
52
|
-
Object.freeze({
|
|
53
|
-
name: 'title',
|
|
54
|
-
source: 'resolver',
|
|
55
|
-
summary: "The active tab's title.",
|
|
56
|
-
}),
|
|
57
|
-
Object.freeze({
|
|
58
|
-
name: 'date',
|
|
59
|
-
source: 'resolver',
|
|
60
|
-
summary: "Today's date.",
|
|
61
|
-
}),
|
|
62
|
-
]);
|
|
63
|
-
|
|
64
|
-
export const SKILL_VAR_NAMES = Object.freeze(SKILL_VARS.map((v) => v.name));
|
|
65
|
-
|
|
66
|
-
const BY_NAME = new Map(SKILL_VARS.map((v) => [v.name, v]));
|
|
67
|
-
|
|
68
|
-
export function skillVar(name) {
|
|
69
|
-
return BY_NAME.get(String(name || '').trim().toLowerCase()) || null;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// Any {{ token }}, with an optional :label. Deliberately permissive on what it
|
|
73
|
-
// CAPTURES — an unknown name has to be recognised as a placeholder before it can be
|
|
74
|
-
// reported as unknown. A pattern that only matched known names would make the
|
|
75
|
-
// {{content}} class of bug invisible all over again.
|
|
76
|
-
const TOKEN = /\{\{\s*([a-z_][a-z0-9_-]*)\s*(?::([^}]*))?\s*\}\}/gi;
|
|
77
|
-
|
|
78
|
-
/** The token pattern for one variable — `g` and `i`, fresh each call (no lastIndex sharing). */
|
|
79
|
-
export function skillVarPattern(name) {
|
|
80
|
-
const v = skillVar(name);
|
|
81
|
-
if (!v) throw new SkillVarError('UNKNOWN_VAR', `unknown skill variable '${name}'`);
|
|
82
|
-
return v.labelled
|
|
83
|
-
? new RegExp(`\\{\\{\\s*${v.name}(?::[^}]*)?\\s*\\}\\}`, 'gi')
|
|
84
|
-
: new RegExp(`\\{\\{\\s*${v.name}\\s*\\}\\}`, 'gi');
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Every placeholder in a prompt, in source order.
|
|
89
|
-
* -> [{ name, label, raw, index, known }]
|
|
90
|
-
*/
|
|
91
|
-
export function parseSkillVars(text) {
|
|
92
|
-
const out = [];
|
|
93
|
-
const src = String(text || '');
|
|
94
|
-
TOKEN.lastIndex = 0;
|
|
95
|
-
let m = TOKEN.exec(src);
|
|
96
|
-
while (m) {
|
|
97
|
-
const name = m[1].toLowerCase();
|
|
98
|
-
out.push({
|
|
99
|
-
name,
|
|
100
|
-
label: (m[2] || '').trim(),
|
|
101
|
-
raw: m[0],
|
|
102
|
-
index: m.index,
|
|
103
|
-
known: BY_NAME.has(name),
|
|
104
|
-
});
|
|
105
|
-
m = TOKEN.exec(src);
|
|
106
|
-
}
|
|
107
|
-
return out;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// Damerau-free Levenshtein, bounded by the short names we compare. Enough to turn
|
|
111
|
-
// {{content}} → {{input}}? No — those are not near neighbours, which is the point:
|
|
112
|
-
// a bad suggestion is worse than none, so `suggestSkillVar` falls back to the input
|
|
113
|
-
// slot only when the unknown name READS like a content slot.
|
|
114
|
-
function distance(a, b) {
|
|
115
|
-
const m = a.length;
|
|
116
|
-
const n = b.length;
|
|
117
|
-
let prev = Array.from({ length: n + 1 }, (_, j) => j);
|
|
118
|
-
for (let i = 1; i <= m; i += 1) {
|
|
119
|
-
const row = [i];
|
|
120
|
-
for (let j = 1; j <= n; j += 1) {
|
|
121
|
-
row[j] = Math.min(
|
|
122
|
-
prev[j] + 1,
|
|
123
|
-
row[j - 1] + 1,
|
|
124
|
-
prev[j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1),
|
|
125
|
-
);
|
|
126
|
-
}
|
|
127
|
-
prev = row;
|
|
128
|
-
}
|
|
129
|
-
return prev[n];
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
// Names a model reaches for when it invents a "put the user's text here" slot. These
|
|
133
|
-
// are NOT aliases — nothing substitutes them, because silently filling a placeholder
|
|
134
|
-
// the user never agreed to is how a prompt starts meaning something else. They only
|
|
135
|
-
// make the suggestion in the lint concrete.
|
|
136
|
-
const INPUT_SHAPED = new Set([
|
|
137
|
-
'content', 'text', 'body', 'draft', 'document', 'doc', 'args', 'arguments',
|
|
138
|
-
'query', 'question', 'task', 'request', 'prompt', 'message', 'user_input', 'userinput',
|
|
139
|
-
]);
|
|
140
|
-
|
|
141
|
-
/** Nearest known variable for a misspelling or an invented name, or '' if none is close. */
|
|
142
|
-
export function suggestSkillVar(name) {
|
|
143
|
-
const q = String(name || '').trim().toLowerCase();
|
|
144
|
-
if (!q || BY_NAME.has(q)) return '';
|
|
145
|
-
if (INPUT_SHAPED.has(q)) return 'input';
|
|
146
|
-
let best = '';
|
|
147
|
-
let bestD = Infinity;
|
|
148
|
-
for (const known of SKILL_VAR_NAMES) {
|
|
149
|
-
const d = distance(q, known);
|
|
150
|
-
if (d < bestD) { bestD = d; best = known; }
|
|
151
|
-
}
|
|
152
|
-
// Two edits, or a third of a longer name — enough to catch a transposition
|
|
153
|
-
// ("titel") without turning an unrelated word into a confident wrong guess.
|
|
154
|
-
return bestD <= Math.max(2, Math.floor(q.length / 3)) ? best : '';
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
/**
|
|
158
|
-
* Static check of a prompt, for the skill editor.
|
|
159
|
-
* -> { known: [names], unknown: [{ name, raw, suggestion }], hasInput: boolean }
|
|
160
|
-
*
|
|
161
|
-
* `hasInput` is what decides whether the caller's text goes INTO the prompt or gets
|
|
162
|
-
* appended after it, so the editor can explain which of the two will happen.
|
|
163
|
-
*/
|
|
164
|
-
export function lintSkillPrompt(text) {
|
|
165
|
-
const seen = parseSkillVars(text);
|
|
166
|
-
const known = [];
|
|
167
|
-
const unknown = [];
|
|
168
|
-
const dedupe = new Set();
|
|
169
|
-
for (const tok of seen) {
|
|
170
|
-
if (dedupe.has(tok.name)) continue;
|
|
171
|
-
dedupe.add(tok.name);
|
|
172
|
-
if (tok.known) known.push(tok.name);
|
|
173
|
-
else unknown.push({ name: tok.name, raw: tok.raw, suggestion: suggestSkillVar(tok.name) });
|
|
174
|
-
}
|
|
175
|
-
return { known, unknown, hasInput: dedupe.has('input') };
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
/**
|
|
179
|
-
* Fill a prompt's placeholders.
|
|
180
|
-
*
|
|
181
|
-
* @param text the skill prompt
|
|
182
|
-
* @param args the user's text, for {{input}}
|
|
183
|
-
* @param resolvers { selection, url, title, date } — each () => string | Promise<string>.
|
|
184
|
-
* A resolver is called ONLY when its variable actually appears, so a
|
|
185
|
-
* prompt without {{selection}} never pays for a tab read. A missing
|
|
186
|
-
* resolver, or one that throws, yields '' and is reported in `empty`
|
|
187
|
-
* rather than failing the turn.
|
|
188
|
-
*
|
|
189
|
-
* -> { text, filled: [names], empty: [names], unknown: [{ name, raw, suggestion }] }
|
|
190
|
-
*
|
|
191
|
-
* UNKNOWN PLACEHOLDERS ARE LEFT ALONE. Rewriting a user's prompt because we did not
|
|
192
|
-
* recognise a token would be a silent edit of the thing they wrote; reporting it lets
|
|
193
|
-
* the caller say so out loud, which is the actual fix.
|
|
194
|
-
*/
|
|
195
|
-
export async function substituteSkillVars(text, { args = '', resolvers = {} } = {}) {
|
|
196
|
-
const src = String(text || '');
|
|
197
|
-
const lint = lintSkillPrompt(src);
|
|
198
|
-
if (!lint.known.length) return { text: src, filled: [], empty: [], unknown: lint.unknown };
|
|
199
|
-
|
|
200
|
-
const filled = [];
|
|
201
|
-
const empty = [];
|
|
202
|
-
let out = src;
|
|
203
|
-
|
|
204
|
-
for (const name of lint.known) {
|
|
205
|
-
let value = '';
|
|
206
|
-
if (name === 'input') {
|
|
207
|
-
value = args == null ? '' : String(args);
|
|
208
|
-
} else {
|
|
209
|
-
const resolve = resolvers[name];
|
|
210
|
-
if (typeof resolve === 'function') {
|
|
211
|
-
try {
|
|
212
|
-
value = (await resolve()) ?? '';
|
|
213
|
-
} catch {
|
|
214
|
-
value = ''; // a dead tab or a denied permission is an empty slot, not a failure
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
value = String(value);
|
|
218
|
-
}
|
|
219
|
-
if (value.trim()) filled.push(name);
|
|
220
|
-
else empty.push(name);
|
|
221
|
-
out = out.replace(skillVarPattern(name), () => value);
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
return { text: out, filled, empty, unknown: lint.unknown };
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
/**
|
|
228
|
-
* The prompt a `/command args` sends.
|
|
229
|
-
*
|
|
230
|
-
* The typed args fill `{{input}}` when the author left a slot for them, and are appended
|
|
231
|
-
* after the prompt when not — never both, which is how "/fix this sentence" once landed
|
|
232
|
-
* in the prompt twice. Everything else `substituteSkillVars` reports (an empty selection,
|
|
233
|
-
* an invented placeholder) comes back untouched, so the caller can say so out loud.
|
|
234
|
-
*/
|
|
235
|
-
export async function expandSkillPrompt(prompt, { args = '', resolvers = {} } = {}) {
|
|
236
|
-
const src = String(prompt || '');
|
|
237
|
-
const a = String(args || '').trim();
|
|
238
|
-
const inline = lintSkillPrompt(src).hasInput;
|
|
239
|
-
const body = src + (!inline && a ? `\n\n${a}` : '');
|
|
240
|
-
return substituteSkillVars(body, { args: a, resolvers });
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
/**
|
|
244
|
-
* The sentence prompt-assist needs so the model stops inventing placeholders.
|
|
245
|
-
* Generated from SKILL_VARS so a variable added here reaches the assist prompt
|
|
246
|
-
* without anyone remembering to update a string.
|
|
247
|
-
*/
|
|
248
|
-
export function skillVarGuidance() {
|
|
249
|
-
const list = SKILL_VARS.map((v) => `{{${v.name}}} (${v.summary})`).join(' ');
|
|
250
|
-
return `The prompt may use ONLY these placeholders, and they are filled at run time: ${list} `
|
|
251
|
-
+ 'Preserve the ones already present verbatim, and never invent another — an unrecognised '
|
|
252
|
-
+ `placeholder is sent to the model as literal text. For the user's own text use {{input}}.`;
|
|
253
|
-
}
|
|
3
|
+
${r}`:"");return v(i,{args:r,resolvers:o})}function O(){return`The prompt may use ONLY these placeholders, and they are filled at run time: ${u.map(e=>`{{${e.name}}} (${e.summary})`).join(" ")} Preserve the ones already present verbatim, and never invent another — an unrecognised placeholder is sent to the model as literal text. For the user's own text use {{input}}.`}export{u as SKILL_VARS,w as SKILL_VAR_NAMES,d as SkillVarError,V as expandSkillPrompt,f as lintSkillPrompt,x as parseSkillVars,g as skillVar,O as skillVarGuidance,k as skillVarPattern,v as substituteSkillVars,b as suggestSkillVar};
|
package/slash-commands.js
CHANGED
|
@@ -1,158 +1 @@
|
|
|
1
|
-
|
|
2
|
-
//
|
|
3
|
-
// A skill, a saved recipe and a client's built-in command all answer to a slash. Which
|
|
4
|
-
// ones exist, how a half-typed prefix is matched, what a chosen item inserts, and how a
|
|
5
|
-
// sent run is LABELLED all lived in the extension's side panel, and the desktop had to copy
|
|
6
|
-
// them the day its composer wanted `/summarize` too. This module is that copy, made the
|
|
7
|
-
// original: a third client (mobile, a channel bot) inherits the grammar instead of
|
|
8
|
-
// re-deriving it.
|
|
9
|
-
//
|
|
10
|
-
// What is NOT here is deliberate:
|
|
11
|
-
// • the BUILT-IN list — `/search`, `/history`, `/monitor` — is what a client can DO,
|
|
12
|
-
// and a desktop with no live meeting has no `/tldr`. Each client passes its own.
|
|
13
|
-
// • the prompt expansion — `expandSkillPrompt` in skill-vars.js, so a composer that only
|
|
14
|
-
// needs the menu never pays for the variable layer.
|
|
15
|
-
// • the entitlement — `skillsAllowed` is decided by the caller against its licence.
|
|
16
|
-
|
|
17
|
-
function normalizePrefix(prefix) {
|
|
18
|
-
return String(prefix || '')
|
|
19
|
-
.toLowerCase()
|
|
20
|
-
.replace(/^\//, '')
|
|
21
|
-
.replace(/\s+/g, ' ');
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function skillItem(skill) {
|
|
25
|
-
return {
|
|
26
|
-
type: 'skill',
|
|
27
|
-
command: skill.command || '',
|
|
28
|
-
icon: skill.icon || '🎓',
|
|
29
|
-
description: skill.description || skill.name || '',
|
|
30
|
-
skill,
|
|
31
|
-
};
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// A saved recipe is a /command too. It is not a skill: nothing is expanded into a prompt.
|
|
35
|
-
// The line becomes a plain request to run it, and the `recipe` tool does the rest.
|
|
36
|
-
function recipeItem(recipe) {
|
|
37
|
-
return { type: 'recipe', command: recipe.name || '', icon: '🧩', description: recipe.description || 'Saved recipe', recipe };
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// A saved TEAM answers to a slash the same way: `/research <request>` is a request to run
|
|
41
|
-
// it, and the `team` tool does the rest.
|
|
42
|
-
function teamItem(team) {
|
|
43
|
-
// A solo team (team-org.js soloTeam) IS an agent, invokable on its own: drawn as one.
|
|
44
|
-
const solo = !!team.origin?.agent;
|
|
45
|
-
return { type: 'team', command: team.name || '', icon: solo ? '🧑💻' : '🧑🤝🧑', description: team.description || (solo ? 'Agent' : 'Agent team'), team, ...(solo ? { agent: team.origin.agent } : {}) };
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/** Skills that are switched on. Absence of the flag means enabled (older records have none). */
|
|
49
|
-
export function enabledSkills(skills) {
|
|
50
|
-
return (Array.isArray(skills) ? skills : []).filter((s) => !!s && s.enabled !== false);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* The items a composer offers for what has been typed so far.
|
|
55
|
-
*
|
|
56
|
-
* @param builtins the client's own commands — `{ command, icon, description, feature? }`.
|
|
57
|
-
* One with a `feature` is shown LOCKED when `features[feature]` is
|
|
58
|
-
* false: a Free user should still discover what Pro unlocks.
|
|
59
|
-
* @param skills the user's skills; only enabled ones with a command are offered
|
|
60
|
-
* @param recipes saved recipes; enabled ones by name
|
|
61
|
-
* @param prefix what follows the slash, possibly with a subcommand ("history m")
|
|
62
|
-
* @param skillsAllowed whether skills are offered at all (Pro on the extension)
|
|
63
|
-
* @param features `{ liveMeetings: true }` — what the licence unlocks
|
|
64
|
-
*/
|
|
65
|
-
export function slashCommandItems({
|
|
66
|
-
builtins = [],
|
|
67
|
-
skills = [],
|
|
68
|
-
recipes = [],
|
|
69
|
-
teams = [],
|
|
70
|
-
prefix = '',
|
|
71
|
-
skillsAllowed = false,
|
|
72
|
-
features = {},
|
|
73
|
-
} = {}) {
|
|
74
|
-
const normalized = normalizePrefix(prefix);
|
|
75
|
-
const own = (Array.isArray(builtins) ? builtins : []).map((item) => ({
|
|
76
|
-
type: 'builtin',
|
|
77
|
-
...item,
|
|
78
|
-
locked: !!item.feature && !features[item.feature],
|
|
79
|
-
}));
|
|
80
|
-
const skillItems = skillsAllowed ? enabledSkills(skills).map(skillItem) : [];
|
|
81
|
-
const recipeItems = (recipes || []).filter((r) => r && r.enabled !== false && r.name).map(recipeItem);
|
|
82
|
-
const teamItems = (teams || []).filter((t) => t && t.enabled !== false && t.name).map(teamItem);
|
|
83
|
-
return [...own, ...skillItems, ...recipeItems, ...teamItems]
|
|
84
|
-
.filter((item) => item.command && item.command.toLowerCase().startsWith(normalized))
|
|
85
|
-
.slice(0, 12);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/** Is the composer's text a slash command still being typed — the moment to show the menu? */
|
|
89
|
-
export const SLASH_TYPING_RE = /^\/([a-z0-9_-]*(?:\s+[a-z0-9_-]*)?)$/i;
|
|
90
|
-
|
|
91
|
-
/** "/summarize the thread" → the enabled skill whose command is `summarize`, and the rest. */
|
|
92
|
-
export function matchSlashSkill(text, skills = []) {
|
|
93
|
-
const m = /^\/([a-z0-9_-]+)\s*([\s\S]*)$/i.exec(String(text || ''));
|
|
94
|
-
if (!m) return null;
|
|
95
|
-
const skill = enabledSkills(skills).find((s) => String(s.command || '').toLowerCase() === m[1].toLowerCase());
|
|
96
|
-
return skill ? { skill, args: m[2].trim() } : null;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/** "/open_bug Crash on start" → the recipe, and the rest of the line as its input. */
|
|
100
|
-
export function matchSlashRecipe(text, recipes = []) {
|
|
101
|
-
const m = /^\/([a-z0-9_-]+)\s*([\s\S]*)$/i.exec(String(text || ''));
|
|
102
|
-
if (!m) return null;
|
|
103
|
-
const recipe = (recipes || []).find((r) => r && r.enabled !== false && String(r.name || '').toLowerCase() === m[1].toLowerCase());
|
|
104
|
-
return recipe ? { recipe, args: m[2].trim() } : null;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/** What the model receives for a recipe command: a request, not a prompt expansion. */
|
|
108
|
-
export function recipeInvocationText(recipe, args = '') {
|
|
109
|
-
const a = String(args || '').trim();
|
|
110
|
-
return `Run the saved recipe "${recipe.name}"${a ? ` with this input: ${a}` : ''}. Use the recipe tool; if a parameter is missing, ask for it.`;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
/** "/research compare A and B" → the team, and the rest of the line as its request. */
|
|
114
|
-
export function matchSlashTeam(text, teams = []) {
|
|
115
|
-
const m = /^\/([a-z0-9_-]+)\s*([\s\S]*)$/i.exec(String(text || ''));
|
|
116
|
-
if (!m) return null;
|
|
117
|
-
const team = (teams || []).find((t) => t && t.enabled !== false && String(t.name || '').toLowerCase() === m[1].toLowerCase());
|
|
118
|
-
return team ? { team, args: m[2].trim() } : null;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
/** What the model receives for a team command: a request to run it, never a prompt expansion. */
|
|
122
|
-
export function teamInvocationText(team, args = '') {
|
|
123
|
-
const a = String(args || '').trim();
|
|
124
|
-
if (team.origin?.agent) return `Run the agent "${team.name}" (a one-role team of that name)${a ? ` on this request: ${a}` : ''}. Use the team tool; if the request is unclear, ask first.`;
|
|
125
|
-
return `Run the saved team "${team.name}"${a ? ` on this request: ${a}` : ''}. Use the team tool; if the request is unclear, ask first.`;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
export function slashCommandInsert(item) {
|
|
129
|
-
return item?.command ? `/${item.command} ` : '/';
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* How a skill invocation should READ once it has been sent.
|
|
134
|
-
*
|
|
135
|
-
* The model must receive the whole prompt — that is what a skill IS. The user's own bubble
|
|
136
|
-
* echoing it back is a separate question with a different answer: a screen of instructions
|
|
137
|
-
* they wrote themselves buries the thread the skill was asked to read. So the send keeps
|
|
138
|
-
* the expansion as the message CONTENT (model, exports and memory capture see exactly what
|
|
139
|
-
* they always saw) and carries this beside it, for display only.
|
|
140
|
-
*
|
|
141
|
-
* Null for anything that is not a command, so callers attach it unconditionally and older
|
|
142
|
-
* messages — which have none — keep rendering as they did.
|
|
143
|
-
*/
|
|
144
|
-
export function skillInvocationOf(skill, args = '') {
|
|
145
|
-
if (!skill?.command) return null;
|
|
146
|
-
return {
|
|
147
|
-
command: skill.command,
|
|
148
|
-
args: String(args || '').trim(),
|
|
149
|
-
name: skill.name || '',
|
|
150
|
-
icon: skill.icon || '🎓',
|
|
151
|
-
};
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
/** The one line an invocation is worth: `/command args`, exactly as typed. */
|
|
155
|
-
export function skillInvocationLabel(inv) {
|
|
156
|
-
if (!inv?.command) return '';
|
|
157
|
-
return `/${inv.command}${inv.args ? ` ${inv.args}` : ''}`;
|
|
158
|
-
}
|
|
1
|
+
function p(e){return String(e||"").toLowerCase().replace(/^\//,"").replace(/\s+/g," ")}function g(e){return{type:"skill",command:e.command||"",icon:e.icon||"🎓",description:e.description||e.name||"",skill:e}}function h(e){return{type:"recipe",command:e.name||"",icon:"🧩",description:e.description||"Saved recipe",recipe:e}}function S(e){const t=!!e.origin?.agent;return{type:"team",command:e.name||"",icon:t?"🧑💻":"🧑🤝🧑",description:e.description||(t?"Agent":"Agent team"),team:e,...t?{agent:e.origin.agent}:{}}}function i(e){return(Array.isArray(e)?e:[]).filter(t=>!!t&&t.enabled!==!1)}function x({builtins:e=[],skills:t=[],recipes:n=[],teams:o=[],prefix:a="",skillsAllowed:s=!1,features:c={}}={}){const m=p(a),u=(Array.isArray(e)?e:[]).map(r=>({type:"builtin",...r,locked:!!r.feature&&!c[r.feature]})),l=s?i(t).map(g):[],f=(n||[]).filter(r=>r&&r.enabled!==!1&&r.name).map(h),d=(o||[]).filter(r=>r&&r.enabled!==!1&&r.name).map(S);return[...u,...l,...f,...d].filter(r=>r.command&&r.command.toLowerCase().startsWith(m)).slice(0,12)}const $=/^\/([a-z0-9_-]*(?:\s+[a-z0-9_-]*)?)$/i;function I(e,t=[]){const n=/^\/([a-z0-9_-]+)\s*([\s\S]*)$/i.exec(String(e||""));if(!n)return null;const o=i(t).find(a=>String(a.command||"").toLowerCase()===n[1].toLowerCase());return o?{skill:o,args:n[2].trim()}:null}function w(e,t=[]){const n=/^\/([a-z0-9_-]+)\s*([\s\S]*)$/i.exec(String(e||""));if(!n)return null;const o=(t||[]).find(a=>a&&a.enabled!==!1&&String(a.name||"").toLowerCase()===n[1].toLowerCase());return o?{recipe:o,args:n[2].trim()}:null}function C(e,t=""){const n=String(t||"").trim();return`Run the saved recipe "${e.name}"${n?` with this input: ${n}`:""}. Use the recipe tool; if a parameter is missing, ask for it.`}function L(e,t=[]){const n=/^\/([a-z0-9_-]+)\s*([\s\S]*)$/i.exec(String(e||""));if(!n)return null;const o=(t||[]).find(a=>a&&a.enabled!==!1&&String(a.name||"").toLowerCase()===n[1].toLowerCase());return o?{team:o,args:n[2].trim()}:null}function y(e,t=""){const n=String(t||"").trim();return e.origin?.agent?`Run the agent "${e.name}" (a one-role team of that name)${n?` on this request: ${n}`:""}. Use the team tool; if the request is unclear, ask first.`:`Run the saved team "${e.name}"${n?` on this request: ${n}`:""}. Use the team tool; if the request is unclear, ask first.`}function b(e){return e?.command?`/${e.command} `:"/"}function z(e,t=""){return e?.command?{command:e.command,args:String(t||"").trim(),name:e.name||"",icon:e.icon||"🎓"}:null}function A(e){return e?.command?`/${e.command}${e.args?` ${e.args}`:""}`:""}export{$ as SLASH_TYPING_RE,i as enabledSkills,w as matchSlashRecipe,I as matchSlashSkill,L as matchSlashTeam,C as recipeInvocationText,A as skillInvocationLabel,z as skillInvocationOf,b as slashCommandInsert,x as slashCommandItems,y as teamInvocationText};
|