@sisylabs/kernel 0.1.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/README.md ADDED
@@ -0,0 +1,80 @@
1
+ # @sisylabs/kernel
2
+
3
+ Shared plugin contracts for the [Sisyphus](https://github.com/Devilsparta/Sisyphus) AI-native VSCode platform. Every Sisyphus plugin imports types from here; that's the whole point of the package.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install --save-dev @sisylabs/kernel
9
+ ```
10
+
11
+ Most of what you'll use are `import type` declarations — the package emits a tiny amount of runtime code only for the UI registries (`@sisylabs/kernel/ui`), and `KernelEvents` constants.
12
+
13
+ ## Authoring a plugin
14
+
15
+ ```typescript
16
+ import type {
17
+ SisyphusPlugin,
18
+ AgentImpl,
19
+ SkillDescriptor,
20
+ SkillHandler,
21
+ } from '@sisylabs/kernel';
22
+
23
+ const myAgent: AgentImpl = {
24
+ descriptor: {
25
+ id: 'my-plugin.agent.hello',
26
+ displayName: 'Hello',
27
+ description: 'A friendly greeter',
28
+ spawnHint: 'when the user says hi',
29
+ triggerKeywords: ['hi', 'hello'],
30
+ },
31
+ async run(userMessage, ctx) {
32
+ ctx.emit({ type: 'token', text: `Hi! You said: ${userMessage}` });
33
+ ctx.emit({ type: 'done', reason: 'stop' });
34
+ },
35
+ };
36
+
37
+ const plugin: SisyphusPlugin = {
38
+ manifest: {
39
+ id: 'my-plugin',
40
+ displayName: 'My Plugin',
41
+ version: '0.1.0',
42
+ contributes: { agents: [myAgent.descriptor] },
43
+ },
44
+ agents: [myAgent],
45
+ };
46
+
47
+ export default plugin;
48
+ ```
49
+
50
+ Compile to ESM with esbuild or tsc; ship `dist/index.mjs` as your daemon entry. The Sisyphus daemon SEA binary has no TypeScript loader, so prod plugins must be pre-compiled JS.
51
+
52
+ `package.json` must include `"keywords": ["sisyphus-plugin"]` to show up in npm registry search, plus a `sisyphus` block:
53
+
54
+ ```json
55
+ {
56
+ "keywords": ["sisyphus-plugin"],
57
+ "sisyphus": {
58
+ "id": "my-plugin",
59
+ "version": "0.1.0",
60
+ "contributes": {
61
+ "agents": ["my-plugin.agent.hello"]
62
+ }
63
+ }
64
+ }
65
+ ```
66
+
67
+ ## What's exported
68
+
69
+ - **`@sisylabs/kernel`** — all types: `SisyphusPlugin`, `PluginManifest`, `AgentImpl`, `AgentDescriptor`, `AgentRunContext`, `AgentEvent`, `SkillDescriptor`, `SkillHandler`, `ViewDescriptor`, `CardDescriptor`, `CardInstance`, `PluginContext`, `PluginStorage`, `RegistryAPI`, `Disposable`, `ChatMessage`, plus `KernelEvents` constants and `IPCMessage` envelopes.
70
+ - **`@sisylabs/kernel/ui`** — browser-only runtime helpers your plugin's `./ui` entry uses to register views, card renderers, and React context providers: `registerView` / `registerCardRenderer` / `registerProvider` (and their list / get / dispose siblings).
71
+
72
+ ## Plugin RPC protocol
73
+
74
+ If you're curious how plugins talk to the daemon — they don't share the daemon's process. Each activated plugin runs as its own child process and talks JSON-RPC over stdio. Full protocol: see `docs/plugin-rpc.md` in the [main repo](https://github.com/Devilsparta/Sisyphus/blob/main/docs/plugin-rpc.md).
75
+
76
+ You don't have to think about the wire format though — the daemon does the marshalling and hands your code the same `ctx` it always had.
77
+
78
+ ## License
79
+
80
+ MIT
@@ -0,0 +1,313 @@
1
+ /**
2
+ * @sisylabs/kernel — shared plugin contracts.
3
+ *
4
+ * Single source of truth for daemon ↔ UI ↔ plugin types.
5
+ *
6
+ * M2 architecture: multi-agent. The kernel hosts a Router that picks among
7
+ * agents contributed by plugins. Each agent runs its own LLM call and streams
8
+ * events directly to the UI (transparent pass-through through the daemon).
9
+ * Plugins describe when their agent should be spawned via `spawnHint`.
10
+ *
11
+ * See wiki: concepts/sisyphus-plugin-architecture.md
12
+ */
13
+ export type Region = 'activity-bar' | 'side' | 'main' | 'bottom';
14
+ /**
15
+ * A view is a unit rendered into a Panel Layout region.
16
+ * Metadata travels through the IPC; the renderer (React component / iframe /
17
+ * web component) lives on the UI side and is resolved by id.
18
+ */
19
+ export interface ViewDescriptor {
20
+ /** Namespace-prefixed id, e.g. "plugin-base.view.canvas". */
21
+ id: string;
22
+ region: Region;
23
+ title: string;
24
+ /** Optional icon identifier (lucide name or URI). */
25
+ icon?: string;
26
+ defaultVisible?: boolean;
27
+ }
28
+ export interface CardDescriptor {
29
+ /** Namespace-prefixed type, e.g. "plugin-base.card.jsx". */
30
+ type: string;
31
+ }
32
+ export interface CardInstance {
33
+ type: string;
34
+ payload: unknown;
35
+ meta?: {
36
+ id?: string;
37
+ createdAt?: number;
38
+ };
39
+ }
40
+ export interface OpenAIToolSchema {
41
+ type: 'function';
42
+ function: {
43
+ name: string;
44
+ description?: string;
45
+ parameters?: Record<string, unknown>;
46
+ };
47
+ }
48
+ export interface SkillDescriptor {
49
+ /** Namespace-prefixed id, e.g. "plugin-base.skill.run-shell". */
50
+ id: string;
51
+ schema: OpenAIToolSchema;
52
+ }
53
+ export type SkillHandler = (args: Record<string, unknown>, ctx: SkillContext) => Promise<unknown>;
54
+ export interface SkillContext {
55
+ conversationId: string;
56
+ }
57
+ /**
58
+ * Metadata describing an agent. The router uses `spawnHint` (a short natural-
59
+ * language clause like "use when the user wants to build a React UI") to pick
60
+ * between agents when multiple are registered.
61
+ */
62
+ export interface AgentDescriptor {
63
+ /** Namespace-prefixed id, e.g. "plugin-base.agent.react-designer". */
64
+ id: string;
65
+ displayName: string;
66
+ /** Short user-facing description shown in the UI. */
67
+ description: string;
68
+ /**
69
+ * Router hint: when should this agent be spawned? Read by the router (and
70
+ * eventually fed to an LLM router prompt in M4+) and by the UI to explain
71
+ * available capabilities.
72
+ */
73
+ spawnHint: string;
74
+ /**
75
+ * Optional explicit keywords the router uses for cheap deterministic
76
+ * matching (M3 strategy). Higher precision than parsing the prose
77
+ * spawnHint. M4+ will treat these as bias signals layered atop an LLM
78
+ * router.
79
+ */
80
+ triggerKeywords?: string[];
81
+ /**
82
+ * Tie-breaker / fan-out ordering signal (M14).
83
+ * Higher = preferred. When the router fan-outs across multiple agents
84
+ * (or chooses among tied candidates), it picks by descending priority.
85
+ * Default 0 when omitted.
86
+ */
87
+ priority?: number;
88
+ }
89
+ /**
90
+ * Streaming events an agent emits during a run. They pass-through the daemon
91
+ * to the UI without router interpretation; the router only observes `done`
92
+ * for completion / retry decisions.
93
+ *
94
+ * `source` is injected by the router at emit time (agents shouldn't set it
95
+ * themselves); it identifies which agent produced the event. Vital for
96
+ * fan-out / spawnAgent traces so the UI can route events to the right cell.
97
+ */
98
+ interface EventBase {
99
+ source?: string;
100
+ }
101
+ export type AgentEvent = (EventBase & {
102
+ type: 'token';
103
+ text: string;
104
+ }) | (EventBase & {
105
+ type: 'reasoning';
106
+ text: string;
107
+ }) | (EventBase & {
108
+ type: 'card';
109
+ card: CardInstance;
110
+ }) | (EventBase & {
111
+ type: 'tool_call';
112
+ id: string;
113
+ skill: string;
114
+ args: Record<string, unknown>;
115
+ }) | (EventBase & {
116
+ type: 'tool_result';
117
+ id: string;
118
+ result?: unknown;
119
+ error?: string;
120
+ }) | (EventBase & {
121
+ type: 'done';
122
+ reason: 'stop' | 'error' | 'cancelled';
123
+ error?: string;
124
+ });
125
+ /**
126
+ * Context passed to an agent's `run`. The `emit` callback funnels events back
127
+ * through the daemon to the UI; `signal` is the abort signal the router (or
128
+ * the user) can fire to cancel a long-running agent.
129
+ */
130
+ export interface AgentRunContext {
131
+ conversationId: string;
132
+ history: ChatMessage[];
133
+ emit: (event: AgentEvent) => void;
134
+ signal: AbortSignal;
135
+ /**
136
+ * Invoke a registered skill by id. The daemon dispatches to whichever
137
+ * plugin owns the skill handler. Agents should also `emit` matching
138
+ * `tool_call` / `tool_result` events so the UI can show the invocation
139
+ * trace; the platform does NOT emit those events automatically (the agent
140
+ * may choose to call a skill silently).
141
+ */
142
+ invokeSkill: (id: string, args: Record<string, unknown>) => Promise<unknown>;
143
+ /**
144
+ * Snapshot of every skill currently registered with the daemon. Agents
145
+ * use this to build the `tools` array for an LLM tool-calling call:
146
+ * each skill's `schema` is already OpenAI tools format.
147
+ *
148
+ * Note: OpenAI's function name pattern disallows '.', so skill.schema
149
+ * .function.name will not be the namespaced skill.id — the agent must
150
+ * keep its own name→id map when dispatching the LLM's tool_calls.
151
+ */
152
+ querySkills: () => SkillDescriptor[];
153
+ /**
154
+ * Spawn another agent as a sub-task (M14). The sub-agent's events stream
155
+ * through to the UI tagged with the sub-agent's id as `source`. Awaits
156
+ * the sub-agent to completion; its `done` is forwarded but does NOT end
157
+ * the parent's run — the parent must still emit its own `done`.
158
+ *
159
+ * The same skill-ACL applies to the sub-agent (it can only invoke its
160
+ * own plugin's skills + whatever its manifest declares).
161
+ */
162
+ spawnAgent: (agentId: string, userMessage: string) => Promise<void>;
163
+ }
164
+ /**
165
+ * A plugin-provided agent implementation. The daemon registers these and the
166
+ * router routes user messages to whichever one matches the situation.
167
+ *
168
+ * Contract: `run` MUST emit a final `{type:'done'}` event so the router knows
169
+ * the agent has completed (and can fire retries / handle timeouts). Failing
170
+ * to emit `done` leaves the conversation in an indeterminate state.
171
+ */
172
+ export interface AgentImpl {
173
+ descriptor: AgentDescriptor;
174
+ run(userMessage: string, ctx: AgentRunContext): Promise<void>;
175
+ }
176
+ export interface PluginManifest {
177
+ /** Plugin id; used as namespace prefix for all contributed ids. */
178
+ id: string;
179
+ displayName?: string;
180
+ version: string;
181
+ /** Other plugin ids this one depends on. Daemon load order is topo-sorted. */
182
+ dependencies?: string[];
183
+ contributes?: {
184
+ views?: ViewDescriptor[];
185
+ cards?: CardDescriptor[];
186
+ skills?: SkillDescriptor[];
187
+ agents?: AgentDescriptor[];
188
+ };
189
+ /**
190
+ * Cross-plugin dependencies the daemon will gate at invocation time.
191
+ * By default a plugin's agents can only invoke skills under their own
192
+ * namespace; to call another plugin's skill the id must be listed here.
193
+ *
194
+ * Cross-plugin view/card use is mediated by registry queries (already
195
+ * shared) so doesn't need a declaration. Skills are different because
196
+ * they're invocable side-effects.
197
+ */
198
+ requires?: {
199
+ skills?: string[];
200
+ };
201
+ /**
202
+ * Path (relative to the plugin's package.json) of a browser-loadable
203
+ * ESM bundle exporting the plugin's UI surface (views, cardRenderers,
204
+ * providers). The daemon serves this file at
205
+ * GET /api/plugins/<id>/ui.mjs so the UI can `await import()` it at
206
+ * runtime — no static UI-side `import '@sisylabs/plugin-x/ui'`
207
+ * needed in production.
208
+ *
209
+ * Default: "./dist/ui.mjs" when unset.
210
+ * Plugins without a UI surface (daemon-only) can omit by setting "".
211
+ */
212
+ uiEntry?: string;
213
+ }
214
+ export interface SisyphusPlugin {
215
+ manifest: PluginManifest;
216
+ onActivate?: (ctx: PluginContext) => void | Promise<void>;
217
+ onDeactivate?: (ctx: PluginContext) => void | Promise<void>;
218
+ /** AgentImpls declared by this plugin. Daemon registers each on activation. */
219
+ agents?: AgentImpl[];
220
+ /** Keyed by skill id (must match a SkillDescriptor in the manifest). */
221
+ skillHandlers?: Record<string, SkillHandler>;
222
+ }
223
+ export interface PluginContext {
224
+ registry: RegistryAPI;
225
+ storage: PluginStorage;
226
+ log: (level: 'debug' | 'info' | 'warn' | 'error', msg: string, meta?: unknown) => void;
227
+ }
228
+ /**
229
+ * Per-plugin persistent key-value storage. Daemon provides a sandboxed
230
+ * instance to each plugin (keyed by manifest id) so plugin state survives
231
+ * restarts. Values must be JSON-serializable.
232
+ *
233
+ * Concurrency model: writes are debounced and best-effort durable; callers
234
+ * needn't await `set`/`delete` for correctness during the run, but the
235
+ * daemon flushes pending writes on shutdown.
236
+ */
237
+ export interface PluginStorage {
238
+ get<T = unknown>(key: string): Promise<T | undefined>;
239
+ set(key: string, value: unknown): Promise<void>;
240
+ delete(key: string): Promise<void>;
241
+ clear(): Promise<void>;
242
+ keys(): Promise<string[]>;
243
+ }
244
+ export interface ChatMessage {
245
+ role: 'system' | 'user' | 'assistant' | 'tool';
246
+ content: string;
247
+ cards?: CardInstance[];
248
+ }
249
+ export interface RegistryAPI {
250
+ registerView(view: ViewDescriptor): Disposable;
251
+ registerCard(card: CardDescriptor): Disposable;
252
+ registerSkill(skill: SkillDescriptor, handler: SkillHandler): Disposable;
253
+ registerAgent(agent: AgentImpl): Disposable;
254
+ queryViews(filter?: {
255
+ region?: Region;
256
+ }): ViewDescriptor[];
257
+ queryCards(): CardDescriptor[];
258
+ querySkills(): SkillDescriptor[];
259
+ queryAgents(): AgentDescriptor[];
260
+ }
261
+ export interface Disposable {
262
+ dispose(): void;
263
+ }
264
+ export interface IPCRequest<T = unknown> {
265
+ type: 'request';
266
+ id: string;
267
+ method: string;
268
+ params?: T;
269
+ }
270
+ export interface IPCResponse<T = unknown> {
271
+ type: 'response';
272
+ id: string;
273
+ result?: T;
274
+ error?: {
275
+ code: number;
276
+ message: string;
277
+ data?: unknown;
278
+ };
279
+ }
280
+ export interface IPCEvent<T = unknown> {
281
+ type: 'event';
282
+ event: string;
283
+ data: T;
284
+ }
285
+ export type IPCMessage = IPCRequest | IPCResponse | IPCEvent;
286
+ export declare const KernelEvents: {
287
+ readonly PlatformReady: "platform.ready";
288
+ readonly RegistrySnapshot: "registry.snapshot";
289
+ readonly RegistryViewAdded: "registry.view.added";
290
+ readonly RegistryViewRemoved: "registry.view.removed";
291
+ readonly RegistryCardAdded: "registry.card.added";
292
+ readonly RegistryCardRemoved: "registry.card.removed";
293
+ readonly RegistrySkillAdded: "registry.skill.added";
294
+ readonly RegistrySkillRemoved: "registry.skill.removed";
295
+ readonly RegistryAgentAdded: "registry.agent.added";
296
+ readonly RegistryAgentRemoved: "registry.agent.removed";
297
+ /**
298
+ * Dev-mode only: emitted by the daemon when a plugin's UI bundle file
299
+ * (dist/ui.mjs) changes on disk. UI hot-reloads the plugin in response.
300
+ * Payload: { pluginId: string, version: number } — version is a
301
+ * monotonic counter so the UI cache-busts dynamic imports cleanly.
302
+ */
303
+ readonly PluginUiBundleChanged: "plugin.ui.bundle.changed";
304
+ };
305
+ export type KernelEventName = (typeof KernelEvents)[keyof typeof KernelEvents];
306
+ export interface RegistrySnapshot {
307
+ views: ViewDescriptor[];
308
+ cards: CardDescriptor[];
309
+ skills: SkillDescriptor[];
310
+ agents: AgentDescriptor[];
311
+ }
312
+ export {};
313
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js ADDED
@@ -0,0 +1,34 @@
1
+ /**
2
+ * @sisylabs/kernel — shared plugin contracts.
3
+ *
4
+ * Single source of truth for daemon ↔ UI ↔ plugin types.
5
+ *
6
+ * M2 architecture: multi-agent. The kernel hosts a Router that picks among
7
+ * agents contributed by plugins. Each agent runs its own LLM call and streams
8
+ * events directly to the UI (transparent pass-through through the daemon).
9
+ * Plugins describe when their agent should be spawned via `spawnHint`.
10
+ *
11
+ * See wiki: concepts/sisyphus-plugin-architecture.md
12
+ */
13
+ // Well-known kernel event names. Plugins must use their own namespace-prefixed
14
+ // event names (e.g. "plugin-base.chat.token") to avoid collisions.
15
+ export const KernelEvents = {
16
+ PlatformReady: 'platform.ready',
17
+ RegistrySnapshot: 'registry.snapshot',
18
+ RegistryViewAdded: 'registry.view.added',
19
+ RegistryViewRemoved: 'registry.view.removed',
20
+ RegistryCardAdded: 'registry.card.added',
21
+ RegistryCardRemoved: 'registry.card.removed',
22
+ RegistrySkillAdded: 'registry.skill.added',
23
+ RegistrySkillRemoved: 'registry.skill.removed',
24
+ RegistryAgentAdded: 'registry.agent.added',
25
+ RegistryAgentRemoved: 'registry.agent.removed',
26
+ /**
27
+ * Dev-mode only: emitted by the daemon when a plugin's UI bundle file
28
+ * (dist/ui.mjs) changes on disk. UI hot-reloads the plugin in response.
29
+ * Payload: { pluginId: string, version: number } — version is a
30
+ * monotonic counter so the UI cache-busts dynamic imports cleanly.
31
+ */
32
+ PluginUiBundleChanged: 'plugin.ui.bundle.changed',
33
+ };
34
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAkUH,+EAA+E;AAC/E,mEAAmE;AACnE,MAAM,CAAC,MAAM,YAAY,GAAG;IAC1B,aAAa,EAAE,gBAAgB;IAC/B,gBAAgB,EAAE,mBAAmB;IACrC,iBAAiB,EAAE,qBAAqB;IACxC,mBAAmB,EAAE,uBAAuB;IAC5C,iBAAiB,EAAE,qBAAqB;IACxC,mBAAmB,EAAE,uBAAuB;IAC5C,kBAAkB,EAAE,sBAAsB;IAC1C,oBAAoB,EAAE,wBAAwB;IAC9C,kBAAkB,EAAE,sBAAsB;IAC1C,oBAAoB,EAAE,wBAAwB;IAC9C;;;;;OAKG;IACH,qBAAqB,EAAE,0BAA0B;CACzC,CAAC"}
package/dist/ui.d.ts ADDED
@@ -0,0 +1,33 @@
1
+ /**
2
+ * @sisylabs/kernel/ui — UI-side runtime registries.
3
+ *
4
+ * These maps live in browser-loaded code only. They're in the kernel package
5
+ * (not the ui package) so plugins can register renderers without depending on
6
+ * `@sisylabs/ui` — which would create a cycle, since the ui package depends
7
+ * on plugins for their UI bundles.
8
+ *
9
+ * React is `import type`-only — TypeScript strips it from emit, so the
10
+ * daemon's `@sisylabs/kernel` entry stays React-runtime-free.
11
+ */
12
+ import type { ComponentType, ReactNode } from 'react';
13
+ import type { Region, ViewDescriptor } from './index';
14
+ export interface UIViewEntry {
15
+ descriptor: ViewDescriptor;
16
+ Component: ComponentType;
17
+ }
18
+ export declare function registerView(descriptor: ViewDescriptor, Component: ComponentType): () => void;
19
+ export declare function getView(id: string): UIViewEntry | undefined;
20
+ export declare function listViews(filter?: {
21
+ region?: Region;
22
+ }): UIViewEntry[];
23
+ export type CardRendererComponent = ComponentType<{
24
+ payload: unknown;
25
+ }>;
26
+ export declare function registerCardRenderer(type: string, Component: CardRendererComponent): () => void;
27
+ export declare function getCardRenderer(type: string): CardRendererComponent | undefined;
28
+ export type ProviderComponent = ComponentType<{
29
+ children: ReactNode;
30
+ }>;
31
+ export declare function registerProvider(Component: ProviderComponent): () => void;
32
+ export declare function getProviders(): ProviderComponent[];
33
+ //# sourceMappingURL=ui.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ui.d.ts","sourceRoot":"","sources":["../src/ui.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACtD,OAAO,KAAK,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAItD,MAAM,WAAW,WAAW;IAC1B,UAAU,EAAE,cAAc,CAAC;IAC3B,SAAS,EAAE,aAAa,CAAC;CAC1B;AAID,wBAAgB,YAAY,CAC1B,UAAU,EAAE,cAAc,EAC1B,SAAS,EAAE,aAAa,GACvB,MAAM,IAAI,CAQZ;AAED,wBAAgB,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS,CAE3D;AAED,wBAAgB,SAAS,CAAC,MAAM,CAAC,EAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,WAAW,EAAE,CAKrE;AAID,MAAM,MAAM,qBAAqB,GAAG,aAAa,CAAC;IAAE,OAAO,EAAE,OAAO,CAAA;CAAE,CAAC,CAAC;AAIxE,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,qBAAqB,GAC/B,MAAM,IAAI,CAQZ;AAED,wBAAgB,eAAe,CAC7B,IAAI,EAAE,MAAM,GACX,qBAAqB,GAAG,SAAS,CAEnC;AAQD,MAAM,MAAM,iBAAiB,GAAG,aAAa,CAAC;IAAE,QAAQ,EAAE,SAAS,CAAA;CAAE,CAAC,CAAC;AAIvE,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,iBAAiB,GAAG,MAAM,IAAI,CAMzE;AAED,wBAAgB,YAAY,IAAI,iBAAiB,EAAE,CAElD"}
package/dist/ui.js ADDED
@@ -0,0 +1,45 @@
1
+ const views = new Map();
2
+ export function registerView(descriptor, Component) {
3
+ if (views.has(descriptor.id)) {
4
+ throw new Error(`View id collision: ${descriptor.id}`);
5
+ }
6
+ views.set(descriptor.id, { descriptor, Component });
7
+ return () => {
8
+ views.delete(descriptor.id);
9
+ };
10
+ }
11
+ export function getView(id) {
12
+ return views.get(id);
13
+ }
14
+ export function listViews(filter) {
15
+ const all = Array.from(views.values());
16
+ return filter?.region
17
+ ? all.filter((e) => e.descriptor.region === filter.region)
18
+ : all;
19
+ }
20
+ const cardRenderers = new Map();
21
+ export function registerCardRenderer(type, Component) {
22
+ if (cardRenderers.has(type)) {
23
+ throw new Error(`Card renderer collision: ${type}`);
24
+ }
25
+ cardRenderers.set(type, Component);
26
+ return () => {
27
+ cardRenderers.delete(type);
28
+ };
29
+ }
30
+ export function getCardRenderer(type) {
31
+ return cardRenderers.get(type);
32
+ }
33
+ const providers = [];
34
+ export function registerProvider(Component) {
35
+ providers.push(Component);
36
+ return () => {
37
+ const idx = providers.indexOf(Component);
38
+ if (idx !== -1)
39
+ providers.splice(idx, 1);
40
+ };
41
+ }
42
+ export function getProviders() {
43
+ return [...providers];
44
+ }
45
+ //# sourceMappingURL=ui.js.map
package/dist/ui.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ui.js","sourceRoot":"","sources":["../src/ui.ts"],"names":[],"mappings":"AAqBA,MAAM,KAAK,GAAG,IAAI,GAAG,EAAuB,CAAC;AAE7C,MAAM,UAAU,YAAY,CAC1B,UAA0B,EAC1B,SAAwB;IAExB,IAAI,KAAK,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,CAAC,EAAE;QAC5B,MAAM,IAAI,KAAK,CAAC,sBAAsB,UAAU,CAAC,EAAE,EAAE,CAAC,CAAC;KACxD;IACD,KAAK,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,EAAE,EAAE,UAAU,EAAE,SAAS,EAAE,CAAC,CAAC;IACpD,OAAO,GAAG,EAAE;QACV,KAAK,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;IAC9B,CAAC,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,OAAO,CAAC,EAAU;IAChC,OAAO,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;AACvB,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,MAA4B;IACpD,MAAM,GAAG,GAAG,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC;IACvC,OAAO,MAAM,EAAE,MAAM;QACnB,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,MAAM,KAAK,MAAM,CAAC,MAAM,CAAC;QAC1D,CAAC,CAAC,GAAG,CAAC;AACV,CAAC;AAMD,MAAM,aAAa,GAAG,IAAI,GAAG,EAAiC,CAAC;AAE/D,MAAM,UAAU,oBAAoB,CAClC,IAAY,EACZ,SAAgC;IAEhC,IAAI,aAAa,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE;QAC3B,MAAM,IAAI,KAAK,CAAC,4BAA4B,IAAI,EAAE,CAAC,CAAC;KACrD;IACD,aAAa,CAAC,GAAG,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;IACnC,OAAO,GAAG,EAAE;QACV,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAC7B,CAAC,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,eAAe,CAC7B,IAAY;IAEZ,OAAO,aAAa,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;AACjC,CAAC;AAUD,MAAM,SAAS,GAAwB,EAAE,CAAC;AAE1C,MAAM,UAAU,gBAAgB,CAAC,SAA4B;IAC3D,SAAS,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IAC1B,OAAO,GAAG,EAAE;QACV,MAAM,GAAG,GAAG,SAAS,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;QACzC,IAAI,GAAG,KAAK,CAAC,CAAC;YAAE,SAAS,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;IAC3C,CAAC,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,YAAY;IAC1B,OAAO,CAAC,GAAG,SAAS,CAAC,CAAC;AACxB,CAAC"}
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "@sisylabs/kernel",
3
+ "version": "0.1.0",
4
+ "description": "Sisyphus kernel — shared plugin contracts (types) and UI runtime registries. The contract surface every Sisyphus plugin imports.",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/Devilsparta/Sisyphus.git",
9
+ "directory": "packages/kernel"
10
+ },
11
+ "homepage": "https://github.com/Devilsparta/Sisyphus#readme",
12
+ "keywords": [
13
+ "sisyphus",
14
+ "sisyphus-plugin-sdk",
15
+ "ai",
16
+ "plugin-system"
17
+ ],
18
+ "type": "module",
19
+ "main": "./dist/index.js",
20
+ "types": "./dist/index.d.ts",
21
+ "exports": {
22
+ ".": {
23
+ "types": "./dist/index.d.ts",
24
+ "default": "./dist/index.js"
25
+ },
26
+ "./ui": {
27
+ "types": "./dist/ui.d.ts",
28
+ "default": "./dist/ui.js"
29
+ },
30
+ "./package.json": "./package.json"
31
+ },
32
+ "files": [
33
+ "dist",
34
+ "src",
35
+ "README.md"
36
+ ],
37
+ "peerDependencies": {
38
+ "react": "^18 || ^19"
39
+ },
40
+ "peerDependenciesMeta": {
41
+ "react": {
42
+ "optional": true
43
+ }
44
+ },
45
+ "devDependencies": {
46
+ "@types/react": "^18.3.0",
47
+ "typescript": "^5"
48
+ },
49
+ "publishConfig": {
50
+ "access": "public"
51
+ },
52
+ "scripts": {
53
+ "typecheck": "tsc --noEmit",
54
+ "build": "rm -rf dist && tsc -p tsconfig.build.json"
55
+ }
56
+ }
package/src/index.ts ADDED
@@ -0,0 +1,363 @@
1
+ /**
2
+ * @sisylabs/kernel — shared plugin contracts.
3
+ *
4
+ * Single source of truth for daemon ↔ UI ↔ plugin types.
5
+ *
6
+ * M2 architecture: multi-agent. The kernel hosts a Router that picks among
7
+ * agents contributed by plugins. Each agent runs its own LLM call and streams
8
+ * events directly to the UI (transparent pass-through through the daemon).
9
+ * Plugins describe when their agent should be spawned via `spawnHint`.
10
+ *
11
+ * See wiki: concepts/sisyphus-plugin-architecture.md
12
+ */
13
+
14
+ // ─── Panel Layout (VSCode-Lite, fixed four regions) ──────────────────────────
15
+
16
+ export type Region = 'activity-bar' | 'side' | 'main' | 'bottom';
17
+
18
+ /**
19
+ * A view is a unit rendered into a Panel Layout region.
20
+ * Metadata travels through the IPC; the renderer (React component / iframe /
21
+ * web component) lives on the UI side and is resolved by id.
22
+ */
23
+ export interface ViewDescriptor {
24
+ /** Namespace-prefixed id, e.g. "plugin-base.view.canvas". */
25
+ id: string;
26
+ region: Region;
27
+ title: string;
28
+ /** Optional icon identifier (lucide name or URI). */
29
+ icon?: string;
30
+ defaultVisible?: boolean;
31
+ }
32
+
33
+ // ─── Chat cards ──────────────────────────────────────────────────────────────
34
+
35
+ export interface CardDescriptor {
36
+ /** Namespace-prefixed type, e.g. "plugin-base.card.jsx". */
37
+ type: string;
38
+ }
39
+
40
+ export interface CardInstance {
41
+ type: string;
42
+ payload: unknown;
43
+ meta?: { id?: string; createdAt?: number };
44
+ }
45
+
46
+ // ─── Skills (OpenAI-tools-format capabilities) ───────────────────────────────
47
+
48
+ export interface OpenAIToolSchema {
49
+ type: 'function';
50
+ function: {
51
+ name: string;
52
+ description?: string;
53
+ parameters?: Record<string, unknown>;
54
+ };
55
+ }
56
+
57
+ export interface SkillDescriptor {
58
+ /** Namespace-prefixed id, e.g. "plugin-base.skill.run-shell". */
59
+ id: string;
60
+ schema: OpenAIToolSchema;
61
+ }
62
+
63
+ export type SkillHandler = (
64
+ args: Record<string, unknown>,
65
+ ctx: SkillContext,
66
+ ) => Promise<unknown>;
67
+
68
+ export interface SkillContext {
69
+ conversationId: string;
70
+ }
71
+
72
+ // ─── Agents (M2 multi-agent architecture) ────────────────────────────────────
73
+
74
+ /**
75
+ * Metadata describing an agent. The router uses `spawnHint` (a short natural-
76
+ * language clause like "use when the user wants to build a React UI") to pick
77
+ * between agents when multiple are registered.
78
+ */
79
+ export interface AgentDescriptor {
80
+ /** Namespace-prefixed id, e.g. "plugin-base.agent.react-designer". */
81
+ id: string;
82
+ displayName: string;
83
+ /** Short user-facing description shown in the UI. */
84
+ description: string;
85
+ /**
86
+ * Router hint: when should this agent be spawned? Read by the router (and
87
+ * eventually fed to an LLM router prompt in M4+) and by the UI to explain
88
+ * available capabilities.
89
+ */
90
+ spawnHint: string;
91
+ /**
92
+ * Optional explicit keywords the router uses for cheap deterministic
93
+ * matching (M3 strategy). Higher precision than parsing the prose
94
+ * spawnHint. M4+ will treat these as bias signals layered atop an LLM
95
+ * router.
96
+ */
97
+ triggerKeywords?: string[];
98
+ /**
99
+ * Tie-breaker / fan-out ordering signal (M14).
100
+ * Higher = preferred. When the router fan-outs across multiple agents
101
+ * (or chooses among tied candidates), it picks by descending priority.
102
+ * Default 0 when omitted.
103
+ */
104
+ priority?: number;
105
+ }
106
+
107
+ /**
108
+ * Streaming events an agent emits during a run. They pass-through the daemon
109
+ * to the UI without router interpretation; the router only observes `done`
110
+ * for completion / retry decisions.
111
+ *
112
+ * `source` is injected by the router at emit time (agents shouldn't set it
113
+ * themselves); it identifies which agent produced the event. Vital for
114
+ * fan-out / spawnAgent traces so the UI can route events to the right cell.
115
+ */
116
+ interface EventBase {
117
+ source?: string;
118
+ }
119
+ export type AgentEvent =
120
+ | (EventBase & { type: 'token'; text: string })
121
+ | (EventBase & { type: 'reasoning'; text: string })
122
+ | (EventBase & { type: 'card'; card: CardInstance })
123
+ | (EventBase & {
124
+ type: 'tool_call';
125
+ id: string;
126
+ skill: string;
127
+ args: Record<string, unknown>;
128
+ })
129
+ | (EventBase & {
130
+ type: 'tool_result';
131
+ id: string;
132
+ result?: unknown;
133
+ error?: string;
134
+ })
135
+ | (EventBase & {
136
+ type: 'done';
137
+ reason: 'stop' | 'error' | 'cancelled';
138
+ error?: string;
139
+ });
140
+
141
+ /**
142
+ * Context passed to an agent's `run`. The `emit` callback funnels events back
143
+ * through the daemon to the UI; `signal` is the abort signal the router (or
144
+ * the user) can fire to cancel a long-running agent.
145
+ */
146
+ export interface AgentRunContext {
147
+ conversationId: string;
148
+ history: ChatMessage[];
149
+ emit: (event: AgentEvent) => void;
150
+ signal: AbortSignal;
151
+ /**
152
+ * Invoke a registered skill by id. The daemon dispatches to whichever
153
+ * plugin owns the skill handler. Agents should also `emit` matching
154
+ * `tool_call` / `tool_result` events so the UI can show the invocation
155
+ * trace; the platform does NOT emit those events automatically (the agent
156
+ * may choose to call a skill silently).
157
+ */
158
+ invokeSkill: (id: string, args: Record<string, unknown>) => Promise<unknown>;
159
+ /**
160
+ * Snapshot of every skill currently registered with the daemon. Agents
161
+ * use this to build the `tools` array for an LLM tool-calling call:
162
+ * each skill's `schema` is already OpenAI tools format.
163
+ *
164
+ * Note: OpenAI's function name pattern disallows '.', so skill.schema
165
+ * .function.name will not be the namespaced skill.id — the agent must
166
+ * keep its own name→id map when dispatching the LLM's tool_calls.
167
+ */
168
+ querySkills: () => SkillDescriptor[];
169
+ /**
170
+ * Spawn another agent as a sub-task (M14). The sub-agent's events stream
171
+ * through to the UI tagged with the sub-agent's id as `source`. Awaits
172
+ * the sub-agent to completion; its `done` is forwarded but does NOT end
173
+ * the parent's run — the parent must still emit its own `done`.
174
+ *
175
+ * The same skill-ACL applies to the sub-agent (it can only invoke its
176
+ * own plugin's skills + whatever its manifest declares).
177
+ */
178
+ spawnAgent: (agentId: string, userMessage: string) => Promise<void>;
179
+ }
180
+
181
+ /**
182
+ * A plugin-provided agent implementation. The daemon registers these and the
183
+ * router routes user messages to whichever one matches the situation.
184
+ *
185
+ * Contract: `run` MUST emit a final `{type:'done'}` event so the router knows
186
+ * the agent has completed (and can fire retries / handle timeouts). Failing
187
+ * to emit `done` leaves the conversation in an indeterminate state.
188
+ */
189
+ export interface AgentImpl {
190
+ descriptor: AgentDescriptor;
191
+ run(userMessage: string, ctx: AgentRunContext): Promise<void>;
192
+ }
193
+
194
+ // ─── Plugin manifest (lives in package.json "sisyphus" field) ────────────────
195
+
196
+ export interface PluginManifest {
197
+ /** Plugin id; used as namespace prefix for all contributed ids. */
198
+ id: string;
199
+ displayName?: string;
200
+ version: string;
201
+ /** Other plugin ids this one depends on. Daemon load order is topo-sorted. */
202
+ dependencies?: string[];
203
+ contributes?: {
204
+ views?: ViewDescriptor[];
205
+ cards?: CardDescriptor[];
206
+ skills?: SkillDescriptor[];
207
+ agents?: AgentDescriptor[];
208
+ };
209
+ /**
210
+ * Cross-plugin dependencies the daemon will gate at invocation time.
211
+ * By default a plugin's agents can only invoke skills under their own
212
+ * namespace; to call another plugin's skill the id must be listed here.
213
+ *
214
+ * Cross-plugin view/card use is mediated by registry queries (already
215
+ * shared) so doesn't need a declaration. Skills are different because
216
+ * they're invocable side-effects.
217
+ */
218
+ requires?: {
219
+ skills?: string[];
220
+ };
221
+ /**
222
+ * Path (relative to the plugin's package.json) of a browser-loadable
223
+ * ESM bundle exporting the plugin's UI surface (views, cardRenderers,
224
+ * providers). The daemon serves this file at
225
+ * GET /api/plugins/<id>/ui.mjs so the UI can `await import()` it at
226
+ * runtime — no static UI-side `import '@sisylabs/plugin-x/ui'`
227
+ * needed in production.
228
+ *
229
+ * Default: "./dist/ui.mjs" when unset.
230
+ * Plugins without a UI surface (daemon-only) can omit by setting "".
231
+ */
232
+ uiEntry?: string;
233
+ }
234
+
235
+ // ─── Plugin runtime entry (default export from plugin's main) ────────────────
236
+
237
+ export interface SisyphusPlugin {
238
+ manifest: PluginManifest;
239
+
240
+ // Lifecycle
241
+ onActivate?: (ctx: PluginContext) => void | Promise<void>;
242
+ onDeactivate?: (ctx: PluginContext) => void | Promise<void>;
243
+
244
+ /** AgentImpls declared by this plugin. Daemon registers each on activation. */
245
+ agents?: AgentImpl[];
246
+
247
+ /** Keyed by skill id (must match a SkillDescriptor in the manifest). */
248
+ skillHandlers?: Record<string, SkillHandler>;
249
+ }
250
+
251
+ export interface PluginContext {
252
+ registry: RegistryAPI;
253
+ storage: PluginStorage;
254
+ log: (
255
+ level: 'debug' | 'info' | 'warn' | 'error',
256
+ msg: string,
257
+ meta?: unknown,
258
+ ) => void;
259
+ }
260
+
261
+ /**
262
+ * Per-plugin persistent key-value storage. Daemon provides a sandboxed
263
+ * instance to each plugin (keyed by manifest id) so plugin state survives
264
+ * restarts. Values must be JSON-serializable.
265
+ *
266
+ * Concurrency model: writes are debounced and best-effort durable; callers
267
+ * needn't await `set`/`delete` for correctness during the run, but the
268
+ * daemon flushes pending writes on shutdown.
269
+ */
270
+ export interface PluginStorage {
271
+ get<T = unknown>(key: string): Promise<T | undefined>;
272
+ set(key: string, value: unknown): Promise<void>;
273
+ delete(key: string): Promise<void>;
274
+ clear(): Promise<void>;
275
+ keys(): Promise<string[]>;
276
+ }
277
+
278
+ export interface ChatMessage {
279
+ role: 'system' | 'user' | 'assistant' | 'tool';
280
+ content: string;
281
+ cards?: CardInstance[];
282
+ }
283
+
284
+ // ─── Registry API (central service-discovery contract) ───────────────────────
285
+ //
286
+ // All plugin contributions flow through this API. No implicit discovery, no
287
+ // global pollution. Disposing a registration must fully revoke the capability
288
+ // (UI removes view, agent service stops offering skill, etc).
289
+
290
+ export interface RegistryAPI {
291
+ registerView(view: ViewDescriptor): Disposable;
292
+ registerCard(card: CardDescriptor): Disposable;
293
+ registerSkill(skill: SkillDescriptor, handler: SkillHandler): Disposable;
294
+ registerAgent(agent: AgentImpl): Disposable;
295
+
296
+ queryViews(filter?: { region?: Region }): ViewDescriptor[];
297
+ queryCards(): CardDescriptor[];
298
+ querySkills(): SkillDescriptor[];
299
+ queryAgents(): AgentDescriptor[];
300
+ }
301
+
302
+ export interface Disposable {
303
+ dispose(): void;
304
+ }
305
+
306
+ // ─── IPC envelopes (HTTP + WebSocket) ───────────────────────────────────────
307
+ //
308
+ // Wire format decision (2026-05-20): custom protocol with three frame kinds
309
+ // distinguished by a `type` discriminator. See wiki — chosen over JSON-RPC 2.0
310
+ // for clearer separation between request/response (RPC) and event (pubsub).
311
+
312
+ export interface IPCRequest<T = unknown> {
313
+ type: 'request';
314
+ id: string;
315
+ method: string;
316
+ params?: T;
317
+ }
318
+
319
+ export interface IPCResponse<T = unknown> {
320
+ type: 'response';
321
+ id: string;
322
+ result?: T;
323
+ error?: { code: number; message: string; data?: unknown };
324
+ }
325
+
326
+ export interface IPCEvent<T = unknown> {
327
+ type: 'event';
328
+ event: string;
329
+ data: T;
330
+ }
331
+
332
+ export type IPCMessage = IPCRequest | IPCResponse | IPCEvent;
333
+
334
+ // Well-known kernel event names. Plugins must use their own namespace-prefixed
335
+ // event names (e.g. "plugin-base.chat.token") to avoid collisions.
336
+ export const KernelEvents = {
337
+ PlatformReady: 'platform.ready',
338
+ RegistrySnapshot: 'registry.snapshot',
339
+ RegistryViewAdded: 'registry.view.added',
340
+ RegistryViewRemoved: 'registry.view.removed',
341
+ RegistryCardAdded: 'registry.card.added',
342
+ RegistryCardRemoved: 'registry.card.removed',
343
+ RegistrySkillAdded: 'registry.skill.added',
344
+ RegistrySkillRemoved: 'registry.skill.removed',
345
+ RegistryAgentAdded: 'registry.agent.added',
346
+ RegistryAgentRemoved: 'registry.agent.removed',
347
+ /**
348
+ * Dev-mode only: emitted by the daemon when a plugin's UI bundle file
349
+ * (dist/ui.mjs) changes on disk. UI hot-reloads the plugin in response.
350
+ * Payload: { pluginId: string, version: number } — version is a
351
+ * monotonic counter so the UI cache-busts dynamic imports cleanly.
352
+ */
353
+ PluginUiBundleChanged: 'plugin.ui.bundle.changed',
354
+ } as const;
355
+
356
+ export type KernelEventName = (typeof KernelEvents)[keyof typeof KernelEvents];
357
+
358
+ export interface RegistrySnapshot {
359
+ views: ViewDescriptor[];
360
+ cards: CardDescriptor[];
361
+ skills: SkillDescriptor[];
362
+ agents: AgentDescriptor[];
363
+ }
package/src/ui.ts ADDED
@@ -0,0 +1,93 @@
1
+ /**
2
+ * @sisylabs/kernel/ui — UI-side runtime registries.
3
+ *
4
+ * These maps live in browser-loaded code only. They're in the kernel package
5
+ * (not the ui package) so plugins can register renderers without depending on
6
+ * `@sisylabs/ui` — which would create a cycle, since the ui package depends
7
+ * on plugins for their UI bundles.
8
+ *
9
+ * React is `import type`-only — TypeScript strips it from emit, so the
10
+ * daemon's `@sisylabs/kernel` entry stays React-runtime-free.
11
+ */
12
+ import type { ComponentType, ReactNode } from 'react';
13
+ import type { Region, ViewDescriptor } from './index';
14
+
15
+ // ─── View registry ─────────────────────────────────────────────────────────
16
+
17
+ export interface UIViewEntry {
18
+ descriptor: ViewDescriptor;
19
+ Component: ComponentType;
20
+ }
21
+
22
+ const views = new Map<string, UIViewEntry>();
23
+
24
+ export function registerView(
25
+ descriptor: ViewDescriptor,
26
+ Component: ComponentType,
27
+ ): () => void {
28
+ if (views.has(descriptor.id)) {
29
+ throw new Error(`View id collision: ${descriptor.id}`);
30
+ }
31
+ views.set(descriptor.id, { descriptor, Component });
32
+ return () => {
33
+ views.delete(descriptor.id);
34
+ };
35
+ }
36
+
37
+ export function getView(id: string): UIViewEntry | undefined {
38
+ return views.get(id);
39
+ }
40
+
41
+ export function listViews(filter?: { region?: Region }): UIViewEntry[] {
42
+ const all = Array.from(views.values());
43
+ return filter?.region
44
+ ? all.filter((e) => e.descriptor.region === filter.region)
45
+ : all;
46
+ }
47
+
48
+ // ─── Card renderer registry ────────────────────────────────────────────────
49
+
50
+ export type CardRendererComponent = ComponentType<{ payload: unknown }>;
51
+
52
+ const cardRenderers = new Map<string, CardRendererComponent>();
53
+
54
+ export function registerCardRenderer(
55
+ type: string,
56
+ Component: CardRendererComponent,
57
+ ): () => void {
58
+ if (cardRenderers.has(type)) {
59
+ throw new Error(`Card renderer collision: ${type}`);
60
+ }
61
+ cardRenderers.set(type, Component);
62
+ return () => {
63
+ cardRenderers.delete(type);
64
+ };
65
+ }
66
+
67
+ export function getCardRenderer(
68
+ type: string,
69
+ ): CardRendererComponent | undefined {
70
+ return cardRenderers.get(type);
71
+ }
72
+
73
+ // ─── Provider registry ─────────────────────────────────────────────────────
74
+ //
75
+ // Plugins that need to wrap the host with a React context provider register
76
+ // it here. The host renders a <ProviderStack> that nests them around the
77
+ // layout shell. Order: registration order, outermost first.
78
+
79
+ export type ProviderComponent = ComponentType<{ children: ReactNode }>;
80
+
81
+ const providers: ProviderComponent[] = [];
82
+
83
+ export function registerProvider(Component: ProviderComponent): () => void {
84
+ providers.push(Component);
85
+ return () => {
86
+ const idx = providers.indexOf(Component);
87
+ if (idx !== -1) providers.splice(idx, 1);
88
+ };
89
+ }
90
+
91
+ export function getProviders(): ProviderComponent[] {
92
+ return [...providers];
93
+ }