@chatpanel/events 0.6.0 → 0.8.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/capability.js +2 -1
- package/index.js +3 -1
- package/package.json +5 -1
- package/scopes.js +12 -0
- package/skill-manifest.js +1 -1
- package/skill-sources.js +162 -0
package/capability.js
CHANGED
|
@@ -11,8 +11,9 @@
|
|
|
11
11
|
// failure mode this exists to prevent.
|
|
12
12
|
|
|
13
13
|
import { CLASSES, EFFECTS, EGRESS, ACTOR_KINDS, SCOPE_KINDS, EventError } from './event.js';
|
|
14
|
+
import { DATA_SCOPES } from './scopes.js';
|
|
14
15
|
|
|
15
|
-
export
|
|
16
|
+
export { DATA_SCOPES } from './scopes.js';
|
|
16
17
|
|
|
17
18
|
const str = (v) => typeof v === 'string' && v.length > 0;
|
|
18
19
|
const strs = (v, allowed = null) => Array.isArray(v) && v.every((x) => str(x) && (!allowed || allowed.includes(x)));
|
package/index.js
CHANGED
|
@@ -19,7 +19,7 @@ export { REF_KINDS, RESOLUTION, makeRef, isRef, resolveRef } from './ref.js';
|
|
|
19
19
|
export { linearize, compareEvents, causesAreWellFormed } from './order.js';
|
|
20
20
|
export { UPCASTERS, upcast, upcastAll } from './upcast.js';
|
|
21
21
|
export {
|
|
22
|
-
|
|
22
|
+
validateCapability, validateInvocation, canSatisfy,
|
|
23
23
|
toModelSchema, toModelSchemas,
|
|
24
24
|
} from './capability.js';
|
|
25
25
|
export { checkInvariants, INVARIANTS } from './invariants.js';
|
|
@@ -43,6 +43,8 @@ export { createManifest, ManifestError, SOURCES } from './manifest.js';
|
|
|
43
43
|
export { createKernel, meetDecisions, KernelError, REQUIRED_PLUGINS, ALLOW_ALL } from './kernel.js';
|
|
44
44
|
export { replay, formatReport, parseJsonl, toJsonl } from './harness.js';
|
|
45
45
|
export { compileQuery, findMatches, matchIndexFor, expandReplacement, replaceMatch, replaceAll, replaceAllInRange, MAX_MATCHES } from './text-search.js';
|
|
46
|
+
export { DATA_SCOPES } from './scopes.js';
|
|
47
|
+
export { SOURCE_TRUST, SkillSourceError, defineSkillSource, createSkillSourceRegistry } from './skill-sources.js';
|
|
46
48
|
export { SKILL_MANIFEST_VERSION, SKILL_CONTEXTS, SKILL_HISTORY_SCOPES, SKILL_MCP_MODES, SKILL_TRUST, SKILL_FILE_KINDS, SKILL_UPCASTERS, SkillManifestError, isSafeSkillPath, originOf, trustOf, skillFiles, needsBridge, declaredAccess, originLabel, sameSkillOrigin, skillIsStale, validateSkill, upcastSkill, upcastSkills, normalizeSkill } from './skill-manifest.js';
|
|
47
49
|
export { SKILL_VARS, SKILL_VAR_NAMES, skillVar, skillVarPattern, parseSkillVars, lintSkillPrompt, suggestSkillVar, substituteSkillVars, skillVarGuidance, SkillVarError } from './skill-vars.js';
|
|
48
50
|
export { outlineOf, parseListItem, continueList, indentSelection, toggleWrap, toggleLinePrefix, toggleTask, toggleLink, docStats, selectionStats } from './markdown-authoring.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chatpanel/events",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "The canonical ChatPanel event-log and capability contracts \u2014 typed durable facts, clock-free deterministic linearization, schema upcasting, and the invariants the replay harness asserts. Pure, dependency-free ESM shared by the ChatPanel extension, gateway and bridge.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
@@ -24,8 +24,10 @@
|
|
|
24
24
|
"./route-graph.js": "./route-graph.js",
|
|
25
25
|
"./router.js": "./router.js",
|
|
26
26
|
"./rules.js": "./rules.js",
|
|
27
|
+
"./scopes.js": "./scopes.js",
|
|
27
28
|
"./search-engines.js": "./search-engines.js",
|
|
28
29
|
"./skill-manifest.js": "./skill-manifest.js",
|
|
30
|
+
"./skill-sources.js": "./skill-sources.js",
|
|
29
31
|
"./skill-vars.js": "./skill-vars.js",
|
|
30
32
|
"./sources-retrieval.js": "./sources-retrieval.js",
|
|
31
33
|
"./sources.js": "./sources.js",
|
|
@@ -58,8 +60,10 @@
|
|
|
58
60
|
"route-graph.js",
|
|
59
61
|
"router.js",
|
|
60
62
|
"rules.js",
|
|
63
|
+
"scopes.js",
|
|
61
64
|
"search-engines.js",
|
|
62
65
|
"skill-manifest.js",
|
|
66
|
+
"skill-sources.js",
|
|
63
67
|
"skill-vars.js",
|
|
64
68
|
"sources-retrieval.js",
|
|
65
69
|
"sources.js",
|
package/scopes.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// scopes.js — the data-scope vocabulary, on its own so it can travel alone.
|
|
2
|
+
//
|
|
3
|
+
// One list names what anything in ChatPanel may touch. Capabilities declare `reads`
|
|
4
|
+
// and `writes` from it, sources declare `reads`, and a skill package declares `reads`
|
|
5
|
+
// — three declarations, one vocabulary, or "what may this reach" gets three answers.
|
|
6
|
+
//
|
|
7
|
+
// It is a separate module rather than a constant inside capability.js because the
|
|
8
|
+
// consumers have very different weights. The bridge vendors the skill contract and has
|
|
9
|
+
// zero runtime dependencies by design; pulling the capability machinery and the event
|
|
10
|
+
// schema behind it to reach a five-element array would be the transitive-graph mistake
|
|
11
|
+
// the extension's first-paint budget exists to prevent, one repo over.
|
|
12
|
+
export const DATA_SCOPES = Object.freeze(['notes', 'meetings', 'chats', 'page', 'files', 'net']);
|
package/skill-manifest.js
CHANGED
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
// default, and the upcast chain exists from the start so adding v3 does not mean
|
|
26
26
|
// rewriting every reader.
|
|
27
27
|
|
|
28
|
-
import { DATA_SCOPES } from './
|
|
28
|
+
import { DATA_SCOPES } from './scopes.js';
|
|
29
29
|
|
|
30
30
|
export class SkillManifestError extends Error {
|
|
31
31
|
constructor(code, message) { super(message); this.name = 'SkillManifestError'; this.code = code; }
|
package/skill-sources.js
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
// skill-sources.js — where skills can come from, as a contract instead of a panel.
|
|
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
|
+
}
|