@rebon/cli-win32-x64 1.3.0 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +10 -0
  3. package/README.md +203 -144
  4. package/THIRD_PARTY_NOTICES.txt +1232 -1232
  5. package/bin/rebon.js +22 -22
  6. package/lib/windows-managed.js +390 -390
  7. package/package.json +9 -2
  8. package/payload/compose-runtime/package.json +17 -17
  9. package/payload/compose-runtime/payload/compose/shims/dsh-anonymous-user-id.js +10 -10
  10. package/payload/compose-runtime/payload/compose/shims/dsh-credentials.js +13 -13
  11. package/payload/compose-runtime/payload/compose/shims/dsh-launch-environment.js +21 -21
  12. package/payload/compose-runtime/payload/compose/shims/dsh-llm.js +313 -313
  13. package/payload/compose-runtime/payload/compose/shims/dsh-settings.js +43 -43
  14. package/payload/compose-runtime/payload/compose/shims/dsh-tools.js +20 -20
  15. package/payload/compose-runtime/payload/compose/shims/dsh-web.js +10 -10
  16. package/payload/compose-runtime/payload/compose/shims/llm-adapter.js +32 -32
  17. package/payload/compose-runtime/payload/compose/shims/zod-lite.js +49 -49
  18. package/payload/compose-runtime/payload/vendor/cordis/LICENSE +21 -21
  19. package/payload/compose-runtime/payload/vendor/cordis/index.js +1530 -1530
  20. package/payload/compose-runtime/payload/vendor/cosmokit/LICENSE +21 -21
  21. package/payload/compose-runtime/payload/vendor/cosmokit/index.mjs +357 -357
  22. package/payload/compose-runtime/payload/vendor/dsh/LICENSE-agent +21 -21
  23. package/payload/compose-runtime/payload/vendor/dsh/LICENSE-agent-loop +21 -21
  24. package/payload/compose-runtime/payload/vendor/dsh/LICENSE-llm-core +21 -21
  25. package/payload/compose-runtime/payload/vendor/dsh/LICENSE-llm-deepseek +21 -21
  26. package/payload/compose-runtime/payload/vendor/dsh/LICENSE-logger-console +21 -21
  27. package/payload/compose-runtime/payload/vendor/dsh/LICENSE-scope +21 -21
  28. package/payload/compose-runtime/payload/vendor/dsh/LICENSE-session +21 -21
  29. package/payload/compose-runtime/payload/vendor/dsh/LICENSE-system-prompt +21 -21
  30. package/payload/compose-runtime/payload/vendor/dsh/LICENSE-timeout +21 -21
  31. package/payload/compose-runtime/payload/vendor/dsh/LICENSE-timer +21 -21
  32. package/payload/compose-runtime/payload/vendor/dsh/LICENSE-tool-todo +21 -21
  33. package/payload/compose-runtime/payload/vendor/dsh/LICENSE-tool-web +21 -21
  34. package/payload/compose-runtime/payload/vendor/dsh/LICENSE-tools-schema +21 -21
  35. package/payload/compose-runtime/payload/vendor/dsh/LICENSE-turndown +21 -21
  36. package/payload/compose-runtime/payload/vendor/dsh/LICENSE-turndown-plugin-gfm +21 -21
  37. package/payload/compose-runtime/payload/vendor/dsh/LICENSE-web-search-exa +21 -21
  38. package/payload/compose-runtime/payload/vendor/dsh/agent-loop.js +1193 -1193
  39. package/payload/compose-runtime/payload/vendor/dsh/agent.js +714 -714
  40. package/payload/compose-runtime/payload/vendor/dsh/llm-core.js +269 -269
  41. package/payload/compose-runtime/payload/vendor/dsh/llm-deepseek.js +672 -672
  42. package/payload/compose-runtime/payload/vendor/dsh/logger-console.js +83 -83
  43. package/payload/compose-runtime/payload/vendor/dsh/scope.js +287 -287
  44. package/payload/compose-runtime/payload/vendor/dsh/session.js +1668 -1668
  45. package/payload/compose-runtime/payload/vendor/dsh/system-prompt.js +309 -309
  46. package/payload/compose-runtime/payload/vendor/dsh/timeout.js +100 -100
  47. package/payload/compose-runtime/payload/vendor/dsh/timer.js +128 -128
  48. package/payload/compose-runtime/payload/vendor/dsh/tool-todo.js +143 -143
  49. package/payload/compose-runtime/payload/vendor/dsh/tool-web.js +1527 -1527
  50. package/payload/compose-runtime/payload/vendor/dsh/tools-schema.js +849 -849
  51. package/payload/compose-runtime/payload/vendor/dsh/web-search-exa.js +122 -122
  52. package/payload/compose-runtime/payload/vendor/eventsource-parser/LICENSE +21 -21
  53. package/payload/compose-runtime/payload/vendor/eventsource-parser/index.js +177 -177
  54. package/payload/compose-runtime/payload/vendor/eventsource-parser/stream.js +48 -48
  55. package/payload/compose-runtime/payload/vendor/schemastery/LICENSE +21 -21
  56. package/payload/compose-runtime/payload/vendor/schemastery/index.mjs +656 -656
  57. package/payload/compose-runtime/payload-manifests.json +48 -48
  58. package/payload/compose-runtime/src/bridge.mjs +193 -195
  59. package/payload/compose-runtime/src/credentials-runtime.mjs +50 -50
  60. package/payload/compose-runtime/src/eventsource-stream.mjs +48 -48
  61. package/payload/compose-runtime/src/index.mjs +7 -7
  62. package/payload/compose-runtime/src/llm-runtime.mjs +326 -326
  63. package/payload/compose-runtime/src/loader.mjs +78 -78
  64. package/payload/compose-runtime/src/loop-assembly.mjs +237 -237
  65. package/payload/compose-runtime/src/payload.mjs +49 -49
  66. package/payload/compose-runtime/src/plugin.mjs +59 -59
  67. package/payload/compose-runtime/src/realm.mjs +223 -223
  68. package/payload/compose-runtime/src/registry.mjs +191 -191
  69. package/payload/compose-runtime/src/resolve.mjs +115 -115
  70. package/payload/compose-runtime/src/systemprompt-runtime.mjs +67 -67
  71. package/payload/compose-runtime/src/tools-runtime.mjs +239 -239
  72. package/payload/compose-runtime/src/web-runtime.mjs +209 -209
  73. package/payload/plugin-host/package.json +11 -11
  74. package/payload/plugin-host/src/bridge.mjs +64 -64
  75. package/payload/plugin-host/src/cli.mjs +147 -147
  76. package/payload/plugin-host/src/framing.mjs +113 -113
  77. package/payload/plugin-host/src/host.mjs +691 -691
  78. package/payload/plugin-host/src/json.mjs +208 -208
  79. package/payload/plugin-host/src/lifecycle.mjs +114 -114
  80. package/payload/plugin-host/src/loader.mjs +183 -183
  81. package/payload/plugin-host/src/methods.mjs +377 -377
  82. package/payload/plugin-host/src/ownership.mjs +57 -57
  83. package/payload/plugin-host/src/protocol.mjs +129 -129
  84. package/payload/plugin-host/src/sdk.mjs +7 -7
  85. package/payload/rebon-boa-helper.exe +0 -0
  86. package/payload/rebon-browser-mcp.exe +0 -0
  87. package/payload/rebon-computer-use.exe +0 -0
  88. package/payload/rebon-lsp-mcp.exe +0 -0
  89. package/payload/rebon.exe +2 -2
  90. package/payload/sandbox-win.exe +0 -0
  91. package/scripts/postinstall.js +112 -112
@@ -1,223 +1,223 @@
1
- // The composition realm: one Cordis context, shared by every entry.
2
- //
3
- // This is the whole reason the composition exists rather than each plugin
4
- // living alone. Cordis plugins reach each other through services on a context —
5
- // `inject: ['tools']`, `ctx.llm.registerAdapter(...)` — and that only works if
6
- // they are mounted on the same one. So the plane's "one plugin, one load" is
7
- // the outside view, and inside the host they all land here.
8
- //
9
- // Two things live here that a flat list of plugins cannot express, and both are
10
- // composition structure rather than plugin facts, which is why rebon describes
11
- // them once when the composition is created rather than per load:
12
- //
13
- // * **groups** — a container that is not a module. Unloading one is unloading
14
- // its members, which rebon does by naming them; nothing is loaded for the
15
- // group itself.
16
- // * **isolate** — Cordis's own realm narrowing. The agent loop needs its own
17
- // `systemPrompt` because the real dsh service and rebon's seat share that
18
- // name; without isolation one of them loses.
19
- //
20
- // Ordering note: this module imports Cordis by bare specifier, so it must not
21
- // be imported before `installModuleResolution()` has run. Everything that
22
- // reaches it does so through a dynamic import for exactly that reason.
23
- import { Context } from 'cordis';
24
- import { OWNER, RegistrationSink, SINK } from './registry.mjs';
25
- import RebonLlmRuntime from './llm-runtime.mjs';
26
- import RebonCredentials from './credentials-runtime.mjs';
27
- import RebonToolsRuntime from './tools-runtime.mjs';
28
- import RebonWebRuntime from './web-runtime.mjs';
29
- import RebonSystemPromptRuntime from './systemprompt-runtime.mjs';
30
-
31
- /** The built-in seats, in mount order: a dsh `inject` is satisfied at mount
32
- * time, so a seat that arrives after the plugin needing it never arrives. */
33
- const BUILTIN_SEATS = ['llm', 'credentials', 'tools', 'web', 'systemPrompt'];
34
-
35
- let state;
36
-
37
- class ComposeError extends Error {
38
- constructor(code, message) {
39
- super(message);
40
- this.code = code;
41
- this.name = 'ComposeError';
42
- }
43
- }
44
-
45
- /// Applies one node's `isolate` declaration to its parent context.
46
- ///
47
- /// Cordis semantics, unchanged: `true` mints an entry-local realm; a string
48
- /// labels a realm SHARED by every node using the same label, so an in-group
49
- /// provider serves in-group consumers while the outside world keeps its own
50
- /// service under the same name.
51
- function derive(parent, node, realms) {
52
- let derived = parent;
53
- if (node.isolate && typeof node.isolate === 'object') {
54
- for (const [service, label] of Object.entries(node.isolate)) {
55
- if (label === true) {
56
- derived = derived.isolate(service);
57
- continue;
58
- }
59
- const key = `${service} ${label}`;
60
- if (!realms.has(key)) realms.set(key, Symbol(`${service}#${label}`));
61
- derived = derived.isolate(service, realms.get(key));
62
- }
63
- }
64
- return derived;
65
- }
66
-
67
- /// Walks the declared structure into a placement per entry id.
68
- ///
69
- /// The value is the context an entry is mounted *under*, not the entry's own —
70
- /// that one is derived per load, because it carries the load's registration
71
- /// sink and each load needs its own.
72
- function place(nodes, parent, placements, groups, realms) {
73
- for (const node of nodes ?? []) {
74
- const id = node?.id;
75
- if (typeof id !== 'string' || id.length === 0) {
76
- throw new ComposeError('[BAD_STRUCTURE]', 'every entry in the composition structure needs an id');
77
- }
78
- if (placements.has(id) || groups.has(id)) {
79
- throw new ComposeError('[BAD_STRUCTURE]', `duplicate entry id ${JSON.stringify(id)}`);
80
- }
81
- const derived = derive(parent, node, realms);
82
- if (Array.isArray(node.group)) {
83
- groups.set(id, derived);
84
- place(node.group, derived, placements, groups, realms);
85
- continue;
86
- }
87
- placements.set(id, derived);
88
- }
89
- }
90
-
91
- /// Creates the realm. Called once, by the composition control plugin.
92
- export async function createRealm({ web = null, entries = [] } = {}) {
93
- if (state !== undefined) {
94
- throw new ComposeError('[REALM_EXISTS]', 'the composition realm is already created');
95
- }
96
- const ctx = new Context();
97
- const placements = new Map();
98
- const groups = new Map();
99
- const realms = new Map();
100
- place(entries, ctx, placements, groups, realms);
101
-
102
- await ctx.plugin(RebonLlmRuntime);
103
- await ctx.plugin(RebonCredentials);
104
- await ctx.plugin(RebonToolsRuntime);
105
- await ctx.plugin(RebonWebRuntime, { web });
106
- await ctx.plugin(RebonSystemPromptRuntime);
107
-
108
- state = { ctx, placements, groups, realms, fibers: new Map(), sinks: new Map() };
109
- return state;
110
- }
111
-
112
- /// The realm, or a refusal that names the ordering mistake.
113
- export function realm() {
114
- if (state === undefined) {
115
- throw new ComposeError(
116
- '[NO_REALM]',
117
- 'the composition realm does not exist yet; rebon loads the composition control plugin before any entry',
118
- );
119
- }
120
- return state;
121
- }
122
-
123
- /// Tears the realm down. Only tests need this; a real host exits instead.
124
- export async function destroyRealm() {
125
- if (state === undefined) return;
126
- for (const pluginId of [...state.fibers.keys()].reverse()) await disposeEntry(pluginId);
127
- await state.ctx.dispose?.();
128
- state = undefined;
129
- }
130
-
131
- export function builtinSeats() {
132
- return [...BUILTIN_SEATS];
133
- }
134
-
135
- /// The services a Cordis plugin cannot start without.
136
- ///
137
- /// Cordis accepts both spellings: a bare array is all-required, an object
138
- /// separates `required` from `optional`.
139
- function requiredInjects(plugin) {
140
- const inject = plugin?.inject;
141
- if (Array.isArray(inject)) return inject;
142
- if (inject && typeof inject === 'object' && Array.isArray(inject.required)) return inject.required;
143
- return [];
144
- }
145
-
146
- /// Mounts one Cordis entry and hands back what it registered.
147
- ///
148
- /// The sink rides on the entry's own derived context, which is what lets a seat
149
- /// attribute a registration synchronously — the moment a plugin registers, the
150
- /// context it registered through says which plugin it was.
151
- export async function mountEntry(request, plugin) {
152
- const live = realm();
153
- const { pluginId } = request;
154
- if (live.groups.has(pluginId)) {
155
- throw new ComposeError(
156
- '[GROUP_NOT_LOADABLE]',
157
- `${pluginId} is a group in the composition structure, not a module; load its members`,
158
- );
159
- }
160
- if (live.fibers.has(pluginId)) {
161
- throw new ComposeError('[PLUGIN_ALREADY_LOADED]', `${pluginId} is already mounted`);
162
- }
163
- const parent = live.placements.get(pluginId) ?? live.ctx;
164
- const sink = new RegistrationSink(pluginId, request);
165
- const derived = parent.extend({ [OWNER]: pluginId, [SINK]: sink });
166
- // Refused before mounting rather than waited for. Cordis lets a plugin wait
167
- // for a service that has not arrived yet, and inside one composition that is
168
- // a feature — but a load is a call rebon is waiting on, and a plugin waiting
169
- // forever is a call that never answers. So a composition entry's required
170
- // services must be there when it loads, and rebon orders entries so they
171
- // are. Saying which one is missing turns a hang into a diagnosis.
172
- for (const service of requiredInjects(plugin)) {
173
- if (derived.get(service) === undefined) {
174
- throw new ComposeError(
175
- '[MISSING_INJECT]',
176
- `${pluginId} requires service ${JSON.stringify(service)}, which nothing provides where it is mounted`,
177
- );
178
- }
179
- }
180
- let fiber;
181
- try {
182
- fiber = derived.plugin(plugin, request.config ?? undefined);
183
- await fiber;
184
- } catch (cause) {
185
- // Whatever half-mounted has to go: a fiber left behind would keep serving
186
- // through registrations rebon was never told about.
187
- try {
188
- await fiber?.dispose();
189
- } catch {}
190
- if (cause?.code) throw cause;
191
- throw new ComposeError('[ACTIVATE_FAILED]', `mounting ${pluginId} failed: ${cause?.message ?? cause}`);
192
- }
193
- // Every catalog read has to finish before the report is sealed: a provider
194
- // reported without its models would have rebon resolving against a table
195
- // that is still being filled in.
196
- await live.ctx.llm.settle();
197
- sink.seal();
198
- live.fibers.set(pluginId, fiber);
199
- live.sinks.set(pluginId, sink);
200
- return sink;
201
- }
202
-
203
- /// Disposes one entry. Idempotent: the host retires a plugin once, but a realm
204
- /// torn down underneath it must not turn that into a second failure.
205
- export async function disposeEntry(pluginId) {
206
- if (state === undefined) return;
207
- const fiber = state.fibers.get(pluginId);
208
- state.fibers.delete(pluginId);
209
- state.sinks.delete(pluginId);
210
- if (fiber === undefined) return;
211
- await fiber.dispose();
212
- }
213
-
214
- /// What one entry reported beyond the protocol's own fields.
215
- export function reportFor(pluginId) {
216
- const sink = realm().sinks.get(pluginId);
217
- return sink === undefined ? undefined : sink.report();
218
- }
219
-
220
- /// Every entry currently mounted, in load order.
221
- export function mounted() {
222
- return [...realm().fibers.keys()];
223
- }
1
+ // The composition realm: one Cordis context, shared by every entry.
2
+ //
3
+ // This is the whole reason the composition exists rather than each plugin
4
+ // living alone. Cordis plugins reach each other through services on a context —
5
+ // `inject: ['tools']`, `ctx.llm.registerAdapter(...)` — and that only works if
6
+ // they are mounted on the same one. So the plane's "one plugin, one load" is
7
+ // the outside view, and inside the host they all land here.
8
+ //
9
+ // Two things live here that a flat list of plugins cannot express, and both are
10
+ // composition structure rather than plugin facts, which is why rebon describes
11
+ // them once when the composition is created rather than per load:
12
+ //
13
+ // * **groups** — a container that is not a module. Unloading one is unloading
14
+ // its members, which rebon does by naming them; nothing is loaded for the
15
+ // group itself.
16
+ // * **isolate** — Cordis's own realm narrowing. The agent loop needs its own
17
+ // `systemPrompt` because the real dsh service and rebon's seat share that
18
+ // name; without isolation one of them loses.
19
+ //
20
+ // Ordering note: this module imports Cordis by bare specifier, so it must not
21
+ // be imported before `installModuleResolution()` has run. Everything that
22
+ // reaches it does so through a dynamic import for exactly that reason.
23
+ import { Context } from 'cordis';
24
+ import { OWNER, RegistrationSink, SINK } from './registry.mjs';
25
+ import RebonLlmRuntime from './llm-runtime.mjs';
26
+ import RebonCredentials from './credentials-runtime.mjs';
27
+ import RebonToolsRuntime from './tools-runtime.mjs';
28
+ import RebonWebRuntime from './web-runtime.mjs';
29
+ import RebonSystemPromptRuntime from './systemprompt-runtime.mjs';
30
+
31
+ /** The built-in seats, in mount order: a dsh `inject` is satisfied at mount
32
+ * time, so a seat that arrives after the plugin needing it never arrives. */
33
+ const BUILTIN_SEATS = ['llm', 'credentials', 'tools', 'web', 'systemPrompt'];
34
+
35
+ let state;
36
+
37
+ class ComposeError extends Error {
38
+ constructor(code, message) {
39
+ super(message);
40
+ this.code = code;
41
+ this.name = 'ComposeError';
42
+ }
43
+ }
44
+
45
+ /// Applies one node's `isolate` declaration to its parent context.
46
+ ///
47
+ /// Cordis semantics, unchanged: `true` mints an entry-local realm; a string
48
+ /// labels a realm SHARED by every node using the same label, so an in-group
49
+ /// provider serves in-group consumers while the outside world keeps its own
50
+ /// service under the same name.
51
+ function derive(parent, node, realms) {
52
+ let derived = parent;
53
+ if (node.isolate && typeof node.isolate === 'object') {
54
+ for (const [service, label] of Object.entries(node.isolate)) {
55
+ if (label === true) {
56
+ derived = derived.isolate(service);
57
+ continue;
58
+ }
59
+ const key = `${service} ${label}`;
60
+ if (!realms.has(key)) realms.set(key, Symbol(`${service}#${label}`));
61
+ derived = derived.isolate(service, realms.get(key));
62
+ }
63
+ }
64
+ return derived;
65
+ }
66
+
67
+ /// Walks the declared structure into a placement per entry id.
68
+ ///
69
+ /// The value is the context an entry is mounted *under*, not the entry's own —
70
+ /// that one is derived per load, because it carries the load's registration
71
+ /// sink and each load needs its own.
72
+ function place(nodes, parent, placements, groups, realms) {
73
+ for (const node of nodes ?? []) {
74
+ const id = node?.id;
75
+ if (typeof id !== 'string' || id.length === 0) {
76
+ throw new ComposeError('[BAD_STRUCTURE]', 'every entry in the composition structure needs an id');
77
+ }
78
+ if (placements.has(id) || groups.has(id)) {
79
+ throw new ComposeError('[BAD_STRUCTURE]', `duplicate entry id ${JSON.stringify(id)}`);
80
+ }
81
+ const derived = derive(parent, node, realms);
82
+ if (Array.isArray(node.group)) {
83
+ groups.set(id, derived);
84
+ place(node.group, derived, placements, groups, realms);
85
+ continue;
86
+ }
87
+ placements.set(id, derived);
88
+ }
89
+ }
90
+
91
+ /// Creates the realm. Called once, by the composition control plugin.
92
+ export async function createRealm({ web = null, entries = [] } = {}) {
93
+ if (state !== undefined) {
94
+ throw new ComposeError('[REALM_EXISTS]', 'the composition realm is already created');
95
+ }
96
+ const ctx = new Context();
97
+ const placements = new Map();
98
+ const groups = new Map();
99
+ const realms = new Map();
100
+ place(entries, ctx, placements, groups, realms);
101
+
102
+ await ctx.plugin(RebonLlmRuntime);
103
+ await ctx.plugin(RebonCredentials);
104
+ await ctx.plugin(RebonToolsRuntime);
105
+ await ctx.plugin(RebonWebRuntime, { web });
106
+ await ctx.plugin(RebonSystemPromptRuntime);
107
+
108
+ state = { ctx, placements, groups, realms, fibers: new Map(), sinks: new Map() };
109
+ return state;
110
+ }
111
+
112
+ /// The realm, or a refusal that names the ordering mistake.
113
+ export function realm() {
114
+ if (state === undefined) {
115
+ throw new ComposeError(
116
+ '[NO_REALM]',
117
+ 'the composition realm does not exist yet; rebon loads the composition control plugin before any entry',
118
+ );
119
+ }
120
+ return state;
121
+ }
122
+
123
+ /// Tears the realm down. Only tests need this; a real host exits instead.
124
+ export async function destroyRealm() {
125
+ if (state === undefined) return;
126
+ for (const pluginId of [...state.fibers.keys()].reverse()) await disposeEntry(pluginId);
127
+ await state.ctx.dispose?.();
128
+ state = undefined;
129
+ }
130
+
131
+ export function builtinSeats() {
132
+ return [...BUILTIN_SEATS];
133
+ }
134
+
135
+ /// The services a Cordis plugin cannot start without.
136
+ ///
137
+ /// Cordis accepts both spellings: a bare array is all-required, an object
138
+ /// separates `required` from `optional`.
139
+ function requiredInjects(plugin) {
140
+ const inject = plugin?.inject;
141
+ if (Array.isArray(inject)) return inject;
142
+ if (inject && typeof inject === 'object' && Array.isArray(inject.required)) return inject.required;
143
+ return [];
144
+ }
145
+
146
+ /// Mounts one Cordis entry and hands back what it registered.
147
+ ///
148
+ /// The sink rides on the entry's own derived context, which is what lets a seat
149
+ /// attribute a registration synchronously — the moment a plugin registers, the
150
+ /// context it registered through says which plugin it was.
151
+ export async function mountEntry(request, plugin) {
152
+ const live = realm();
153
+ const { pluginId } = request;
154
+ if (live.groups.has(pluginId)) {
155
+ throw new ComposeError(
156
+ '[GROUP_NOT_LOADABLE]',
157
+ `${pluginId} is a group in the composition structure, not a module; load its members`,
158
+ );
159
+ }
160
+ if (live.fibers.has(pluginId)) {
161
+ throw new ComposeError('[PLUGIN_ALREADY_LOADED]', `${pluginId} is already mounted`);
162
+ }
163
+ const parent = live.placements.get(pluginId) ?? live.ctx;
164
+ const sink = new RegistrationSink(pluginId, request);
165
+ const derived = parent.extend({ [OWNER]: pluginId, [SINK]: sink });
166
+ // Refused before mounting rather than waited for. Cordis lets a plugin wait
167
+ // for a service that has not arrived yet, and inside one composition that is
168
+ // a feature — but a load is a call rebon is waiting on, and a plugin waiting
169
+ // forever is a call that never answers. So a composition entry's required
170
+ // services must be there when it loads, and rebon orders entries so they
171
+ // are. Saying which one is missing turns a hang into a diagnosis.
172
+ for (const service of requiredInjects(plugin)) {
173
+ if (derived.get(service) === undefined) {
174
+ throw new ComposeError(
175
+ '[MISSING_INJECT]',
176
+ `${pluginId} requires service ${JSON.stringify(service)}, which nothing provides where it is mounted`,
177
+ );
178
+ }
179
+ }
180
+ let fiber;
181
+ try {
182
+ fiber = derived.plugin(plugin, request.config ?? undefined);
183
+ await fiber;
184
+ } catch (cause) {
185
+ // Whatever half-mounted has to go: a fiber left behind would keep serving
186
+ // through registrations rebon was never told about.
187
+ try {
188
+ await fiber?.dispose();
189
+ } catch {}
190
+ if (cause?.code) throw cause;
191
+ throw new ComposeError('[ACTIVATE_FAILED]', `mounting ${pluginId} failed: ${cause?.message ?? cause}`);
192
+ }
193
+ // Every catalog read has to finish before the report is sealed: a provider
194
+ // reported without its models would have rebon resolving against a table
195
+ // that is still being filled in.
196
+ await live.ctx.llm.settle();
197
+ sink.seal();
198
+ live.fibers.set(pluginId, fiber);
199
+ live.sinks.set(pluginId, sink);
200
+ return sink;
201
+ }
202
+
203
+ /// Disposes one entry. Idempotent: the host retires a plugin once, but a realm
204
+ /// torn down underneath it must not turn that into a second failure.
205
+ export async function disposeEntry(pluginId) {
206
+ if (state === undefined) return;
207
+ const fiber = state.fibers.get(pluginId);
208
+ state.fibers.delete(pluginId);
209
+ state.sinks.delete(pluginId);
210
+ if (fiber === undefined) return;
211
+ await fiber.dispose();
212
+ }
213
+
214
+ /// What one entry reported beyond the protocol's own fields.
215
+ export function reportFor(pluginId) {
216
+ const sink = realm().sinks.get(pluginId);
217
+ return sink === undefined ? undefined : sink.report();
218
+ }
219
+
220
+ /// Every entry currently mounted, in load order.
221
+ export function mounted() {
222
+ return [...realm().fibers.keys()];
223
+ }