@rebon/cli-win32-x64 1.2.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 (98) 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 -128
  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 -0
  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 -0
  36. package/payload/compose-runtime/payload/vendor/dsh/LICENSE-turndown-plugin-gfm +21 -0
  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 -0
  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
  92. package/payload/compose-runtime/payload/compose/credentials-runtime.js +0 -38
  93. package/payload/compose-runtime/payload/compose/llm-runtime.js +0 -330
  94. package/payload/compose-runtime/payload/compose/loop-assembly.js +0 -149
  95. package/payload/compose-runtime/payload/compose/serve-dispatch.js +0 -82
  96. package/payload/compose-runtime/payload/compose/systemprompt-runtime.js +0 -74
  97. package/payload/compose-runtime/payload/compose/tools-runtime.js +0 -228
  98. package/payload/compose-runtime/payload/compose/web-runtime.js +0 -204
@@ -1,67 +1,67 @@
1
- // The composition's `ctx.systemPrompt` seat, on the plugin plane.
2
- //
3
- // Ported from `js/compose/systemprompt-runtime.js`. The dsh-facing surface is
4
- // unchanged: plugins call `ctx.systemPrompt.section({name, order, text})` and
5
- // get the exact disposer back, duplicate names throw dsh's message, non-finite
6
- // orders throw dsh's TypeError.
7
- //
8
- // What changed is where the section goes. It used to be
9
- // `callService('system-prompt', 'register', …)` — a synchronous call into a
10
- // kernel living in the same process, compensated by an `unregister` in the
11
- // disposer. Neither survives a process boundary: the call has to be awaited and
12
- // the disposer cannot await. So a section is *reported*: collected here, handed
13
- // to rebon with the rest of the load report, and withdrawn when the plugin
14
- // unloads.
15
- //
16
- // Deliberately NOT provided (v1 honesty boundary, unchanged): provider-function
17
- // `text` (dsh resolves it per assembly with an AssembleContext this side does
18
- // not have — a static composition registers static text), `complete` sections
19
- // (replacing rebon's own prompt belongs to the loop seat, not a side door),
20
- // `context()` / variables / tool-order surfaces. All fail loudly rather than
21
- // silently degrade.
22
- import { Service } from 'cordis';
23
- import { sinkOf } from './registry.mjs';
24
-
25
- export default class RebonSystemPromptRuntime extends Service {
26
- constructor(ctx) {
27
- super(ctx, 'systemPrompt');
28
- this.names = new Set();
29
- }
30
-
31
- /** Register one ordered prompt section; returns the exact disposer. */
32
- section(entry) {
33
- const name = entry?.name;
34
- if (typeof name !== 'string' || name.trim().length === 0) {
35
- throw new TypeError('systemPrompt.section: section.name must be a non-empty string');
36
- }
37
- if (!Number.isFinite(entry.order)) {
38
- throw new TypeError(`prompt section "${name}" order must be a finite number`);
39
- }
40
- if (typeof entry.text === 'function') {
41
- throw new Error(
42
- `prompt section "${name}": provider-function text is not supported in the composition (register static text)`,
43
- );
44
- }
45
- if (typeof entry.text !== 'string') {
46
- throw new TypeError(`prompt section "${name}" text must be a string`);
47
- }
48
- if (entry.complete) {
49
- throw new Error(
50
- `prompt section "${name}": complete-section replacement is not supported in the composition`,
51
- );
52
- }
53
- if (this.names.has(name)) {
54
- throw new Error(`prompt section "${name}" is already registered in this scope`);
55
- }
56
- const runtime = this;
57
- // Caller-fork RAII through the Service proxy (llm/tools/web same pattern):
58
- // the registering plugin's disposal takes the section with it.
59
- sinkOf(this.ctx)?.section({ name, order: entry.order, text: entry.text });
60
- return this.ctx.effect(() => {
61
- runtime.names.add(name);
62
- return () => {
63
- runtime.names.delete(name);
64
- };
65
- });
66
- }
67
- }
1
+ // The composition's `ctx.systemPrompt` seat, on the plugin plane.
2
+ //
3
+ // Ported from `js/compose/systemprompt-runtime.js`. The dsh-facing surface is
4
+ // unchanged: plugins call `ctx.systemPrompt.section({name, order, text})` and
5
+ // get the exact disposer back, duplicate names throw dsh's message, non-finite
6
+ // orders throw dsh's TypeError.
7
+ //
8
+ // What changed is where the section goes. It used to be
9
+ // `callService('system-prompt', 'register', …)` — a synchronous call into a
10
+ // kernel living in the same process, compensated by an `unregister` in the
11
+ // disposer. Neither survives a process boundary: the call has to be awaited and
12
+ // the disposer cannot await. So a section is *reported*: collected here, handed
13
+ // to rebon with the rest of the load report, and withdrawn when the plugin
14
+ // unloads.
15
+ //
16
+ // Deliberately NOT provided (v1 honesty boundary, unchanged): provider-function
17
+ // `text` (dsh resolves it per assembly with an AssembleContext this side does
18
+ // not have — a static composition registers static text), `complete` sections
19
+ // (replacing rebon's own prompt belongs to the loop seat, not a side door),
20
+ // `context()` / variables / tool-order surfaces. All fail loudly rather than
21
+ // silently degrade.
22
+ import { Service } from 'cordis';
23
+ import { sinkOf } from './registry.mjs';
24
+
25
+ export default class RebonSystemPromptRuntime extends Service {
26
+ constructor(ctx) {
27
+ super(ctx, 'systemPrompt');
28
+ this.names = new Set();
29
+ }
30
+
31
+ /** Register one ordered prompt section; returns the exact disposer. */
32
+ section(entry) {
33
+ const name = entry?.name;
34
+ if (typeof name !== 'string' || name.trim().length === 0) {
35
+ throw new TypeError('systemPrompt.section: section.name must be a non-empty string');
36
+ }
37
+ if (!Number.isFinite(entry.order)) {
38
+ throw new TypeError(`prompt section "${name}" order must be a finite number`);
39
+ }
40
+ if (typeof entry.text === 'function') {
41
+ throw new Error(
42
+ `prompt section "${name}": provider-function text is not supported in the composition (register static text)`,
43
+ );
44
+ }
45
+ if (typeof entry.text !== 'string') {
46
+ throw new TypeError(`prompt section "${name}" text must be a string`);
47
+ }
48
+ if (entry.complete) {
49
+ throw new Error(
50
+ `prompt section "${name}": complete-section replacement is not supported in the composition`,
51
+ );
52
+ }
53
+ if (this.names.has(name)) {
54
+ throw new Error(`prompt section "${name}" is already registered in this scope`);
55
+ }
56
+ const runtime = this;
57
+ // Caller-fork RAII through the Service proxy (llm/tools/web same pattern):
58
+ // the registering plugin's disposal takes the section with it.
59
+ sinkOf(this.ctx)?.section({ name, order: entry.order, text: entry.text });
60
+ return this.ctx.effect(() => {
61
+ runtime.names.add(name);
62
+ return () => {
63
+ runtime.names.delete(name);
64
+ };
65
+ });
66
+ }
67
+ }
@@ -1,239 +1,239 @@
1
- // The composition's `ctx.tools` seat, on the plugin plane.
2
- //
3
- // Ported from `js/compose/tools-runtime.js`. The dsh-facing surface is
4
- // unchanged: plugins call `ctx.tools.register(defineTool({...}))` and get the
5
- // exact disposer back, definitions arrive with `parameters` already compiled to
6
- // JSON Schema and argument validation baked into `execute`, and the loop drives
7
- // tools through the symbol-keyed scheduler contract.
8
- //
9
- // What changed:
10
- //
11
- // * **The serve pump is gone.** `registerServeTarget(name, …)` and
12
- // `op_tool_serve_next` are replaced by the tool being a *declared* tool of
13
- // the loading plugin, called back through `tool/call`. A tool is offered to
14
- // a model, so the registration carries the description and input schema
15
- // rather than a name alone — which is what the plane needs anyway.
16
- // * **`callService('tool-registry', 'register', …)` is gone.** The tool is
17
- // reported by `plugin/load` and withdrawn by `plugin/unload` draining, so
18
- // the compensating `unregister` a disposer used to make — and could not
19
- // have made across a process boundary — has nothing left to compensate.
20
- // Shadowing a builtin name is rebon's judgement to make and rebon's to log;
21
- // the composition no longer hears about it, which is why the warning that
22
- // used to be printed here is not.
23
- // * **`exec.agent.session.append` publishes.** It was a kernel event emitted
24
- // from inside the isolate; it is `event/emit` now, declared as
25
- // `publishedTopics`, and it is the reason a tool-providing plugin declares
26
- // `compose:session/append`.
27
- import { Service } from 'cordis';
28
- import { TOOL_RUNTIME_SCHEDULER } from '@deepseek-ai/dsh-tools';
29
- import { invokeTool, publish, withCall } from './bridge.mjs';
30
- import { sinkOf } from './registry.mjs';
31
-
32
- /** The topic a tool body's session writes land on. */
33
- export const SESSION_APPEND_TOPIC = 'compose:session/append';
34
-
35
- /** Extract a `[BRACKET_CODE]` prefix from a seat error message, if any. */
36
- function bracketCode(message) {
37
- const match = /^\[([A-Z_]+)\]/.exec(message);
38
- return match ? match[1] : undefined;
39
- }
40
-
41
- /** Per-run context/conclusion collection installed by scheduler.prepare. */
42
- const runStates = new WeakMap();
43
-
44
- export default class RebonToolsRuntime extends Service {
45
- constructor(ctx) {
46
- super(ctx, 'tools');
47
- this.names = new Set();
48
- // Local definition table for the scheduler's in-composition dispatch
49
- // (register() below keeps it in step with what rebon was told).
50
- this.defs = new Map();
51
- }
52
-
53
- /**
54
- * dsh scheduler contract.
55
- *
56
- * agent-loop's tool-calls scheduler drives tools through this symbol-keyed
57
- * view (dsh's private loop↔tools seam, contract transcribed from
58
- * packages/core/tools/src/index.ts:451). Dispatch resolution: a
59
- * composition-registered definition runs here; anything else goes to rebon's
60
- * own tools through `tool/invoke`, whose refusals (stable bracketed codes)
61
- * come back as error RESULTS for the model, not crashes. v1 honesty
62
- * boundary: no pre/post-execute pipeline — prepare always dispatches,
63
- * finalize/finish pass through.
64
- *
65
- * A getter rather than a field on purpose: read through the Cordis Service
66
- * proxy, `this.ctx` is the *caller's* context — the loop plugin driving the
67
- * turn. That is the identity a `tool/invoke` made on the loop's own schedule
68
- * has to carry, and capturing it here is the only place it is still known.
69
- */
70
- get [TOOL_RUNTIME_SCHEDULER]() {
71
- const runtime = this;
72
- const via = this.ctx;
73
- return {
74
- async prepare(exec) {
75
- const state = { contexts: [], concluded: false };
76
- const run = {
77
- ...exec,
78
- rootCallId: exec.rootCallId ?? exec.callId,
79
- deferContext(message) {
80
- state.contexts.push(message);
81
- },
82
- concludeTurn() {
83
- state.concluded = true;
84
- },
85
- };
86
- runStates.set(run, state);
87
- return { kind: 'dispatch', exec: run };
88
- },
89
- dispatch: (exec) => runtime._dispatch(exec, via),
90
- async finalize(_exec, result) {
91
- return result;
92
- },
93
- finish(_exec, result) {
94
- return result;
95
- },
96
- };
97
- }
98
-
99
- async _dispatch(exec, via) {
100
- const state = runStates.get(exec) ?? { contexts: [], concluded: false };
101
- try {
102
- const def = this.defs.get(exec.name);
103
- let content;
104
- if (def !== undefined) {
105
- const value = await def.execute(exec.arguments, exec);
106
- content = def.output.render(exec.arguments, value);
107
- } else {
108
- const value = await invokeTool(exec.name, exec.arguments ?? {}, { via });
109
- content = [{
110
- type: 'text',
111
- text: typeof value === 'string' ? value : JSON.stringify(value),
112
- }];
113
- }
114
- return {
115
- kind: 'final-result',
116
- result: {
117
- content,
118
- isError: false,
119
- ...(state.contexts.length > 0 ? { additionalContexts: state.contexts } : {}),
120
- ...(state.concluded ? { concludesTurn: true } : {}),
121
- },
122
- };
123
- } catch (error) {
124
- const message = String(error?.message ?? error);
125
- return {
126
- kind: 'final-result',
127
- result: {
128
- content: [{ type: 'text', text: `Error: ${message}` }],
129
- isError: true,
130
- error: {
131
- message,
132
- info: {
133
- name: error?.name ?? 'Error',
134
- code: error?.code ?? bracketCode(message) ?? 'TOOL_FAILED',
135
- },
136
- },
137
- },
138
- };
139
- }
140
- }
141
-
142
- /**
143
- * dsh concurrency classification. v1: everything is exclusive — strictly
144
- * serial scheduling is always semantically correct, and it sidesteps the
145
- * parallel pool until `isConcurrencySafe` mapping is wired.
146
- */
147
- executionMode(_exec) {
148
- return { kind: 'exclusive' };
149
- }
150
-
151
- /**
152
- * Register one dsh-shaped tool definition. Mirrors dsh
153
- * `ToolRuntime.register` validation; returns the exact disposer.
154
- */
155
- register(definition) {
156
- const name = definition?.name;
157
- if (typeof name !== 'string' || name.trim().length === 0) {
158
- throw new TypeError('tools.register: definition.name must be a non-empty string');
159
- }
160
- const output = definition.output;
161
- if (
162
- output === undefined || typeof output !== 'object'
163
- || typeof output.render !== 'function'
164
- ) {
165
- throw new TypeError(`tool "${name}" must declare output { schema, render }`);
166
- }
167
- if (typeof definition.execute !== 'function') {
168
- throw new TypeError(`tool "${name}" must declare execute()`);
169
- }
170
- const timeoutMs = definition.timeoutMs;
171
- if (timeoutMs !== undefined && (!Number.isFinite(timeoutMs) || timeoutMs <= 0)) {
172
- throw new TypeError(`tool "${name}" timeoutMs must be a positive finite number`);
173
- }
174
- if (this.names.has(name)) {
175
- throw new Error(`tool "${name}" is already registered in this scope`);
176
- }
177
- const runtime = this;
178
- // `this.ctx` is the CALLER's context through the Service proxy, so the
179
- // registration is attributed to the plugin that made it and the effect
180
- // unwinds when that plugin disposes — Cordis RAII, llm-runtime same
181
- // pattern. Read synchronously: which plugin is registering is a fact
182
- // about now, and an await would let another one interleave.
183
- const sink = sinkOf(this.ctx);
184
- sink?.tool(
185
- {
186
- name,
187
- description: String(definition.description ?? ''),
188
- inputSchema: definition.parameters ?? { type: 'object' },
189
- },
190
- (input, callCtx) => withCall(callCtx, () => runtime._execute(name, input, callCtx)),
191
- );
192
- return this.ctx.effect(() => {
193
- runtime.names.add(name);
194
- runtime.defs.set(name, definition);
195
- return () => {
196
- runtime.names.delete(name);
197
- runtime.defs.delete(name);
198
- };
199
- });
200
- }
201
-
202
- /// Runs one registered tool for a `tool/call`.
203
- ///
204
- /// Resolved through the live table rather than closing over the definition:
205
- /// a tool whose plugin disposed it is gone, and answering with a body that
206
- /// is no longer registered would make disposal a lie.
207
- async _execute(name, input, callCtx) {
208
- const definition = this.defs.get(name);
209
- if (definition === undefined) {
210
- const error = new Error(`tool "${name}" is no longer registered in this composition`);
211
- error.code = '[STALE_PROVIDER]';
212
- throw error;
213
- }
214
- const callId = `compose:${callCtx.scopeId ?? 'scope'}:${name}`;
215
- const exec = {
216
- callId,
217
- rootCallId: callId,
218
- name,
219
- arguments: input,
220
- signal: callCtx.signal,
221
- // dsh tools write per-session facts through `exec.agent.session.append`.
222
- // On the plane that is a published event: the tool is telling rebon
223
- // something happened, and rebon decides who hears it.
224
- agent: {
225
- session: {
226
- append(type, data) {
227
- void publish(SESSION_APPEND_TOPIC, { type, data, tool: name, callId })
228
- .catch(() => {});
229
- },
230
- },
231
- },
232
- };
233
- // defineTool's execute validates arguments first (ToolArgsError with
234
- // INVALID_ARGS semantics flows out as this call's terminal).
235
- const value = await definition.execute(input, exec);
236
- const content = definition.output.render(input, value);
237
- return { content, isError: false };
238
- }
239
- }
1
+ // The composition's `ctx.tools` seat, on the plugin plane.
2
+ //
3
+ // Ported from `js/compose/tools-runtime.js`. The dsh-facing surface is
4
+ // unchanged: plugins call `ctx.tools.register(defineTool({...}))` and get the
5
+ // exact disposer back, definitions arrive with `parameters` already compiled to
6
+ // JSON Schema and argument validation baked into `execute`, and the loop drives
7
+ // tools through the symbol-keyed scheduler contract.
8
+ //
9
+ // What changed:
10
+ //
11
+ // * **The serve pump is gone.** `registerServeTarget(name, …)` and
12
+ // `op_tool_serve_next` are replaced by the tool being a *declared* tool of
13
+ // the loading plugin, called back through `tool/call`. A tool is offered to
14
+ // a model, so the registration carries the description and input schema
15
+ // rather than a name alone — which is what the plane needs anyway.
16
+ // * **`callService('tool-registry', 'register', …)` is gone.** The tool is
17
+ // reported by `plugin/load` and withdrawn by `plugin/unload` draining, so
18
+ // the compensating `unregister` a disposer used to make — and could not
19
+ // have made across a process boundary — has nothing left to compensate.
20
+ // Shadowing a builtin name is rebon's judgement to make and rebon's to log;
21
+ // the composition no longer hears about it, which is why the warning that
22
+ // used to be printed here is not.
23
+ // * **`exec.agent.session.append` publishes.** It was a kernel event emitted
24
+ // from inside the isolate; it is `event/emit` now, declared as
25
+ // `publishedTopics`, and it is the reason a tool-providing plugin declares
26
+ // `compose:session/append`.
27
+ import { Service } from 'cordis';
28
+ import { TOOL_RUNTIME_SCHEDULER } from '@deepseek-ai/dsh-tools';
29
+ import { invokeTool, publish, withCall } from './bridge.mjs';
30
+ import { sinkOf } from './registry.mjs';
31
+
32
+ /** The topic a tool body's session writes land on. */
33
+ export const SESSION_APPEND_TOPIC = 'compose:session/append';
34
+
35
+ /** Extract a `[BRACKET_CODE]` prefix from a seat error message, if any. */
36
+ function bracketCode(message) {
37
+ const match = /^\[([A-Z_]+)\]/.exec(message);
38
+ return match ? match[1] : undefined;
39
+ }
40
+
41
+ /** Per-run context/conclusion collection installed by scheduler.prepare. */
42
+ const runStates = new WeakMap();
43
+
44
+ export default class RebonToolsRuntime extends Service {
45
+ constructor(ctx) {
46
+ super(ctx, 'tools');
47
+ this.names = new Set();
48
+ // Local definition table for the scheduler's in-composition dispatch
49
+ // (register() below keeps it in step with what rebon was told).
50
+ this.defs = new Map();
51
+ }
52
+
53
+ /**
54
+ * dsh scheduler contract.
55
+ *
56
+ * agent-loop's tool-calls scheduler drives tools through this symbol-keyed
57
+ * view (dsh's private loop↔tools seam, contract transcribed from
58
+ * packages/core/tools/src/index.ts:451). Dispatch resolution: a
59
+ * composition-registered definition runs here; anything else goes to rebon's
60
+ * own tools through `tool/invoke`, whose refusals (stable bracketed codes)
61
+ * come back as error RESULTS for the model, not crashes. v1 honesty
62
+ * boundary: no pre/post-execute pipeline — prepare always dispatches,
63
+ * finalize/finish pass through.
64
+ *
65
+ * A getter rather than a field on purpose: read through the Cordis Service
66
+ * proxy, `this.ctx` is the *caller's* context — the loop plugin driving the
67
+ * turn. That is the identity a `tool/invoke` made on the loop's own schedule
68
+ * has to carry, and capturing it here is the only place it is still known.
69
+ */
70
+ get [TOOL_RUNTIME_SCHEDULER]() {
71
+ const runtime = this;
72
+ const via = this.ctx;
73
+ return {
74
+ async prepare(exec) {
75
+ const state = { contexts: [], concluded: false };
76
+ const run = {
77
+ ...exec,
78
+ rootCallId: exec.rootCallId ?? exec.callId,
79
+ deferContext(message) {
80
+ state.contexts.push(message);
81
+ },
82
+ concludeTurn() {
83
+ state.concluded = true;
84
+ },
85
+ };
86
+ runStates.set(run, state);
87
+ return { kind: 'dispatch', exec: run };
88
+ },
89
+ dispatch: (exec) => runtime._dispatch(exec, via),
90
+ async finalize(_exec, result) {
91
+ return result;
92
+ },
93
+ finish(_exec, result) {
94
+ return result;
95
+ },
96
+ };
97
+ }
98
+
99
+ async _dispatch(exec, via) {
100
+ const state = runStates.get(exec) ?? { contexts: [], concluded: false };
101
+ try {
102
+ const def = this.defs.get(exec.name);
103
+ let content;
104
+ if (def !== undefined) {
105
+ const value = await def.execute(exec.arguments, exec);
106
+ content = def.output.render(exec.arguments, value);
107
+ } else {
108
+ const value = await invokeTool(exec.name, exec.arguments ?? {}, { via });
109
+ content = [{
110
+ type: 'text',
111
+ text: typeof value === 'string' ? value : JSON.stringify(value),
112
+ }];
113
+ }
114
+ return {
115
+ kind: 'final-result',
116
+ result: {
117
+ content,
118
+ isError: false,
119
+ ...(state.contexts.length > 0 ? { additionalContexts: state.contexts } : {}),
120
+ ...(state.concluded ? { concludesTurn: true } : {}),
121
+ },
122
+ };
123
+ } catch (error) {
124
+ const message = String(error?.message ?? error);
125
+ return {
126
+ kind: 'final-result',
127
+ result: {
128
+ content: [{ type: 'text', text: `Error: ${message}` }],
129
+ isError: true,
130
+ error: {
131
+ message,
132
+ info: {
133
+ name: error?.name ?? 'Error',
134
+ code: error?.code ?? bracketCode(message) ?? 'TOOL_FAILED',
135
+ },
136
+ },
137
+ },
138
+ };
139
+ }
140
+ }
141
+
142
+ /**
143
+ * dsh concurrency classification. v1: everything is exclusive — strictly
144
+ * serial scheduling is always semantically correct, and it sidesteps the
145
+ * parallel pool until `isConcurrencySafe` mapping is wired.
146
+ */
147
+ executionMode(_exec) {
148
+ return { kind: 'exclusive' };
149
+ }
150
+
151
+ /**
152
+ * Register one dsh-shaped tool definition. Mirrors dsh
153
+ * `ToolRuntime.register` validation; returns the exact disposer.
154
+ */
155
+ register(definition) {
156
+ const name = definition?.name;
157
+ if (typeof name !== 'string' || name.trim().length === 0) {
158
+ throw new TypeError('tools.register: definition.name must be a non-empty string');
159
+ }
160
+ const output = definition.output;
161
+ if (
162
+ output === undefined || typeof output !== 'object'
163
+ || typeof output.render !== 'function'
164
+ ) {
165
+ throw new TypeError(`tool "${name}" must declare output { schema, render }`);
166
+ }
167
+ if (typeof definition.execute !== 'function') {
168
+ throw new TypeError(`tool "${name}" must declare execute()`);
169
+ }
170
+ const timeoutMs = definition.timeoutMs;
171
+ if (timeoutMs !== undefined && (!Number.isFinite(timeoutMs) || timeoutMs <= 0)) {
172
+ throw new TypeError(`tool "${name}" timeoutMs must be a positive finite number`);
173
+ }
174
+ if (this.names.has(name)) {
175
+ throw new Error(`tool "${name}" is already registered in this scope`);
176
+ }
177
+ const runtime = this;
178
+ // `this.ctx` is the CALLER's context through the Service proxy, so the
179
+ // registration is attributed to the plugin that made it and the effect
180
+ // unwinds when that plugin disposes — Cordis RAII, llm-runtime same
181
+ // pattern. Read synchronously: which plugin is registering is a fact
182
+ // about now, and an await would let another one interleave.
183
+ const sink = sinkOf(this.ctx);
184
+ sink?.tool(
185
+ {
186
+ name,
187
+ description: String(definition.description ?? ''),
188
+ inputSchema: definition.parameters ?? { type: 'object' },
189
+ },
190
+ (input, callCtx) => withCall(callCtx, () => runtime._execute(name, input, callCtx)),
191
+ );
192
+ return this.ctx.effect(() => {
193
+ runtime.names.add(name);
194
+ runtime.defs.set(name, definition);
195
+ return () => {
196
+ runtime.names.delete(name);
197
+ runtime.defs.delete(name);
198
+ };
199
+ });
200
+ }
201
+
202
+ /// Runs one registered tool for a `tool/call`.
203
+ ///
204
+ /// Resolved through the live table rather than closing over the definition:
205
+ /// a tool whose plugin disposed it is gone, and answering with a body that
206
+ /// is no longer registered would make disposal a lie.
207
+ async _execute(name, input, callCtx) {
208
+ const definition = this.defs.get(name);
209
+ if (definition === undefined) {
210
+ const error = new Error(`tool "${name}" is no longer registered in this composition`);
211
+ error.code = '[STALE_PROVIDER]';
212
+ throw error;
213
+ }
214
+ const callId = `compose:${callCtx.scopeId ?? 'scope'}:${name}`;
215
+ const exec = {
216
+ callId,
217
+ rootCallId: callId,
218
+ name,
219
+ arguments: input,
220
+ signal: callCtx.signal,
221
+ // dsh tools write per-session facts through `exec.agent.session.append`.
222
+ // On the plane that is a published event: the tool is telling rebon
223
+ // something happened, and rebon decides who hears it.
224
+ agent: {
225
+ session: {
226
+ append(type, data) {
227
+ void publish(SESSION_APPEND_TOPIC, { type, data, tool: name, callId })
228
+ .catch(() => {});
229
+ },
230
+ },
231
+ },
232
+ };
233
+ // defineTool's execute validates arguments first (ToolArgsError with
234
+ // INVALID_ARGS semantics flows out as this call's terminal).
235
+ const value = await definition.execute(input, exec);
236
+ const content = definition.output.render(input, value);
237
+ return { content, isError: false };
238
+ }
239
+ }