@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,48 +1,48 @@
1
- {
2
- "version": 1,
3
- "note": "What rebon declares on behalf of the packages it ships. Every plugin on the plane loads against a manifest — the ceiling on what it may register and reach — and a third-party package declares its own under `capabilities.kernelPlugins` in its `rebon-plugin.json`. These packages are vendored into rebon's payload, so rebon is the one that has to say what they do. `root` names which package the `entry` is relative to. It defaults to `package`, which in this table means the vendored JS tree an entry belongs to; `payload` says the same thing explicitly, and `runtime` names the composition runtime itself. `invokableTools: \"$rebon/tools\"` expands to every tool this build exposes to the plane — the honest declaration for a plugin that runs whatever tool a model picks.",
4
- "packages": {
5
- "cordis": { "entry": "vendor/cordis/index.js" },
6
- "@deepseek-ai/cordis-plugin-timer": { "entry": "vendor/dsh/timer.js" },
7
- "@deepseek-ai/cordis-plugin-logger-console": { "entry": "vendor/dsh/logger-console.js" },
8
-
9
- "@deepseek-ai/dsh-llm-deepseek": {
10
- "entry": "vendor/dsh/llm-deepseek.js",
11
- "llmProviders": ["deepseek-official"],
12
- "seats": ["credentials"]
13
- },
14
- "@deepseek-ai/dsh-tool-todo": {
15
- "entry": "vendor/dsh/tool-todo.js",
16
- "tools": ["todo_write"],
17
- "publishedTopics": ["compose:session/append"]
18
- },
19
- "@deepseek-ai/dsh-tool-web": {
20
- "entry": "vendor/dsh/tool-web.js",
21
- "tools": ["web_search", "web_fetch"],
22
- "invokableTools": ["WebSearch", "WebFetch"],
23
- "publishedTopics": ["compose:session/append"]
24
- },
25
- "@deepseek-ai/dsh-web-search-exa": {
26
- "entry": "vendor/dsh/web-search-exa.js",
27
- "services": ["web:search:exa"],
28
- "seats": ["credentials"]
29
- },
30
-
31
- "@deepseek-ai/dsh-session": { "entry": "vendor/dsh/session.js" },
32
- "@deepseek-ai/dsh-agent": { "entry": "vendor/dsh/agent.js" },
33
- "@deepseek-ai/dsh-system-prompt": { "entry": "vendor/dsh/system-prompt.js" },
34
- "@deepseek-ai/dsh-agent-loop": {
35
- "entry": "vendor/dsh/agent-loop.js",
36
- "invokableTools": "$rebon/tools",
37
- "seats": ["credentials"]
38
- },
39
-
40
- "rebon-loop-assembly": {
41
- "root": "runtime",
42
- "entry": "src/loop-assembly.mjs",
43
- "services": ["loop:control"],
44
- "publishedTopics": ["loop:event", "loop:agent-error", "loop:agent-created"],
45
- "seats": ["logger"]
46
- }
47
- }
48
- }
1
+ {
2
+ "version": 1,
3
+ "note": "What rebon declares on behalf of the packages it ships. Every plugin on the plane loads against a manifest — the ceiling on what it may register and reach — and a third-party package declares its own under `capabilities.kernelPlugins` in its `rebon-plugin.json`. These packages are vendored into rebon's payload, so rebon is the one that has to say what they do. `root` names which package the `entry` is relative to. It defaults to `package`, which in this table means the vendored JS tree an entry belongs to; `payload` says the same thing explicitly, and `runtime` names the composition runtime itself. `invokableTools: \"$rebon/tools\"` expands to every tool this build exposes to the plane — the honest declaration for a plugin that runs whatever tool a model picks.",
4
+ "packages": {
5
+ "cordis": { "entry": "vendor/cordis/index.js" },
6
+ "@deepseek-ai/cordis-plugin-timer": { "entry": "vendor/dsh/timer.js" },
7
+ "@deepseek-ai/cordis-plugin-logger-console": { "entry": "vendor/dsh/logger-console.js" },
8
+
9
+ "@deepseek-ai/dsh-llm-deepseek": {
10
+ "entry": "vendor/dsh/llm-deepseek.js",
11
+ "llmProviders": ["deepseek-official"],
12
+ "seats": ["credentials"]
13
+ },
14
+ "@deepseek-ai/dsh-tool-todo": {
15
+ "entry": "vendor/dsh/tool-todo.js",
16
+ "tools": ["todo_write"],
17
+ "publishedTopics": ["compose:session/append"]
18
+ },
19
+ "@deepseek-ai/dsh-tool-web": {
20
+ "entry": "vendor/dsh/tool-web.js",
21
+ "tools": ["web_search", "web_fetch"],
22
+ "invokableTools": ["WebSearch", "WebFetch"],
23
+ "publishedTopics": ["compose:session/append"]
24
+ },
25
+ "@deepseek-ai/dsh-web-search-exa": {
26
+ "entry": "vendor/dsh/web-search-exa.js",
27
+ "services": ["web:search:exa"],
28
+ "seats": ["credentials"]
29
+ },
30
+
31
+ "@deepseek-ai/dsh-session": { "entry": "vendor/dsh/session.js" },
32
+ "@deepseek-ai/dsh-agent": { "entry": "vendor/dsh/agent.js" },
33
+ "@deepseek-ai/dsh-system-prompt": { "entry": "vendor/dsh/system-prompt.js" },
34
+ "@deepseek-ai/dsh-agent-loop": {
35
+ "entry": "vendor/dsh/agent-loop.js",
36
+ "invokableTools": "$rebon/tools",
37
+ "seats": ["credentials"]
38
+ },
39
+
40
+ "rebon-loop-assembly": {
41
+ "root": "runtime",
42
+ "entry": "src/loop-assembly.mjs",
43
+ "services": ["loop:control"],
44
+ "publishedTopics": ["loop:event", "loop:agent-error", "loop:agent-created"],
45
+ "seats": ["logger"]
46
+ }
47
+ }
48
+ }
@@ -1,195 +1,193 @@
1
- // The composition's doorway to rebon, on the plugin plane.
2
- //
3
- // On deno_core this was `js/bridge.js`: seven synchronous ops reaching a
4
- // kernel that lived in the same process. Across a process boundary none of
5
- // that survives unchanged, and the differences are the protocol's, not this
6
- // module's:
7
- //
8
- // * **Everything is async.** `callService` returned a value from an op; a
9
- // `seat/call` is a request that has to be answered. The one place this
10
- // bites is a cordis disposer, which is synchronous and cannot await — see
11
- // the note on `seat()` below.
12
- // * **There is no ambient kernel.** A call is only meaningful inside the
13
- // scope incarnation it was made for, so the doorway exists only inside a
14
- // handler. `AsyncLocalStorage` is what carries the handler's context down
15
- // to code that has no way to be handed it — a dsh credential provider is
16
- // called by an adapter that knows nothing about rebon.
17
- // * **Registration is not a call.** `op_rebon_call_service('model-router',
18
- // 'register', …)` and its compensating `unregister` are gone. What a
19
- // composition provides is reported once, in the `plugin/load` ready
20
- // report, and revoked by `plugin/unload` draining. That is the only shape
21
- // that works: the unregisters lived in cordis disposers.
22
- import { AsyncLocalStorage } from 'node:async_hooks';
23
- import { sessionOf } from './registry.mjs';
24
-
25
- const calls = new AsyncLocalStorage();
26
-
27
- /// Runs `fn` with `ctx` as the call everything underneath reaches through.
28
- ///
29
- /// Every handler the composition serves — service, tool, llm adapter, event
30
- /// delivery — is wrapped in this, so anything it calls can find its way back
31
- /// to rebon without the call context being threaded through dsh code that has
32
- /// no parameter for it.
33
- export function withCall(ctx, fn) {
34
- return calls.run(ctx, fn);
35
- }
36
-
37
- /// The call this code is running inside.
38
- export function currentCall() {
39
- return calls.getStore();
40
- }
41
-
42
- /// The call this code is running inside, or a refusal that says why not.
43
- ///
44
- /// The refusal is worth its own code: "cannot read properties of undefined" at
45
- /// the bottom of a dsh stack says nothing, whereas this names the actual rule —
46
- /// rebon is reachable from inside a call, and a disposer or a module top level
47
- /// is not inside one.
48
- function require(what, via) {
49
- const ctx = calls.getStore();
50
- if (ctx !== undefined) return ctx;
51
- // Nothing is being answered, so this is a plugin acting on its own schedule.
52
- // `via` is the Cordis context of the plugin whose code is running, and the
53
- // session it is attached to is the honest identity for what it does — the
54
- // work is still that plugin's, in that session. Without a session there is
55
- // no identity to use, and inventing one would make the ledger a guess.
56
- const session = sessionOf(via);
57
- if (session !== undefined) return session;
58
- throw new Error(
59
- `[NO_CALL_CONTEXT] ${what} is not reachable from here: no call rebon made is in flight, `
60
- + 'and this plugin is not attached to a session. Teardown paths must rely on unload draining instead',
61
- );
62
- }
63
-
64
- /// Calls a kernel seat: the composition's `credentials`, `settings`, `web` and
65
- /// the rest of what rebon installs a plugin to use.
66
- ///
67
- /// A request, therefore answerable, therefore **not** usable from a cordis
68
- /// disposer: `dispose()` is synchronous and a cross-process call cannot be.
69
- /// Nothing in the composition needs it to be — the calls that used to happen
70
- /// on the way out were unregistrations, and those are the protocol's job now.
71
- export function seat(name, method, params = null, options = {}) {
72
- const { via, ...call } = options;
73
- return require(`seat ${JSON.stringify(name)}`, via).seat(name, method, params, call);
74
- }
75
-
76
- /// Invokes one rebon core tool on the session this call belongs to.
77
- ///
78
- /// Three gates stand in front of it and only the first is here: the plugin's
79
- /// manifest must declare the tool, rebon's build must expose it, and the
80
- /// embedder's broker decides whether this run is permitted.
81
- export function invokeTool(tool, input = null, options = {}) {
82
- const { via, ...call } = options;
83
- return require(`tool ${JSON.stringify(tool)}`, via).invoke(tool, input, call);
84
- }
85
-
86
- /// Publishes one event onto rebon's event plane.
87
- ///
88
- /// Not the same thing as `emit` below, and the difference is the audience: a
89
- /// chunk is a piece of the answer the caller of *this* call is reading, an
90
- /// event is a fact anyone listening may act on. The composition's runtime
91
- /// events — a dsh tool body appending to the session log — are the second kind.
92
- ///
93
- /// Declared as `publishedTopics` in the manifest, separately from what the
94
- /// composition listens to.
95
- export function publish(topic, event = null, options = {}) {
96
- const { via, ...call } = options;
97
- return require(`topic ${JSON.stringify(topic)}`, via).publish(topic, event, call);
98
- }
99
-
100
- /// Emits one piece of a streamed answer on the call this code is serving.
101
- ///
102
- /// This is the transport `op_llm_emit` and `op_tool_serve_emit` used to be,
103
- /// with the pumps gone: a chunk belongs to a call, and the call is the one the
104
- /// handler was entered with rather than an id looked up in a table.
105
- export function emit(piece) {
106
- return require('emit').emit(piece);
107
- }
108
-
109
- /// The workspace root of the session this call belongs to, or undefined
110
- /// outside a call.
111
- export function workspaceRoot() {
112
- return calls.getStore()?.workspaceRoot;
113
- }
114
-
115
- /// The abort signal rebon raises when it asks this call to stop.
116
- export function callSignal() {
117
- return calls.getStore()?.signal;
118
- }
119
-
120
- /// Diagnostics.
121
- ///
122
- /// Best-effort on purpose, and never a reason for a call to fail: a log line
123
- /// that cannot be delivered is not worth failing the work that produced it.
124
- /// Inside a call it goes to the kernel's `logger` seat, so the line is
125
- /// attributed to a scope; outside one it goes to stderr, which the supervisor
126
- /// already collects as the host's diagnostic tail. stdout is never touched —
127
- /// that is the protocol's channel.
128
- /// A token bucket per level, so one plugin in a logging loop cannot fill the
129
- /// worker's log.
130
- ///
131
- /// Here rather than on rebon's side because the cheapest byte is the one that
132
- /// never crosses the process boundary, and because this side already knows how
133
- /// many it dropped. Drops are reported, at most once a second: replacing a
134
- /// blind spot with a quieter blind spot is not an improvement, and a reader who
135
- /// sees a gap deserves to know it is a gap.
136
- const RATE = { perSecond: 20, burst: 100 };
137
- const buckets = new Map();
138
-
139
- function admit(level) {
140
- const now = Date.now();
141
- let bucket = buckets.get(level);
142
- if (bucket === undefined) {
143
- bucket = { tokens: RATE.burst, last: now, dropped: 0, reportedAt: 0 };
144
- buckets.set(level, bucket);
145
- }
146
- bucket.tokens = Math.min(
147
- RATE.burst,
148
- bucket.tokens + ((now - bucket.last) / 1000) * RATE.perSecond,
149
- );
150
- bucket.last = now;
151
- if (bucket.tokens >= 1) {
152
- bucket.tokens -= 1;
153
- return { allowed: true, dropped: 0 };
154
- }
155
- bucket.dropped += 1;
156
- if (now - bucket.reportedAt >= 1000) {
157
- bucket.reportedAt = now;
158
- const dropped = bucket.dropped;
159
- bucket.dropped = 0;
160
- return { allowed: false, dropped };
161
- }
162
- return { allowed: false, dropped: 0 };
163
- }
164
-
165
- const LEVELS = ['trace', 'debug', 'info', 'warn', 'error'];
166
- export const logger = Object.freeze(Object.fromEntries(LEVELS.map((level) => [
167
- level,
168
- (message, options = {}) => {
169
- const line = String(message);
170
- // Inside a call, that call is who is speaking. Outside one — a timer, an
171
- // agent loop driving its own turn — `via` names the plugin, and the
172
- // session it is attached to is the handle that can still reach the seat.
173
- //
174
- // This is the same ladder `require()` climbs, with one difference that
175
- // matters: it does not throw. A log line is not worth failing a plugin
176
- // over, and a refusal here would be the very silence this is fixing.
177
- // T19/T19b lost three rounds to lines that went to stderr and were only
178
- // ever read when the host died.
179
- const ctx = calls.getStore() ?? sessionOf(options.via);
180
- if (ctx === undefined) {
181
- process.stderr.write(`[compose:${level}] ${line}\n`);
182
- return;
183
- }
184
- const { allowed, dropped } = admit(level);
185
- if (dropped > 0) {
186
- void ctx
187
- .seat('logger', 'warn', { message: `${dropped} ${level} line(s) dropped: too many, too fast` })
188
- .catch(() => {});
189
- }
190
- if (!allowed) return;
191
- void ctx.seat('logger', level, { message: line }).catch(() => {
192
- process.stderr.write(`[compose:${level}] ${line}\n`);
193
- });
194
- },
195
- ])));
1
+ // The composition's doorway to rebon, on the plugin plane.
2
+ //
3
+ // The plane is across a process boundary, so there are no synchronous ops
4
+ // into an in-process kernel here. Three consequences follow, and they are the
5
+ // protocol's rather than this module's:
6
+ //
7
+ // * **Everything is async.** `callService` returned a value from an op; a
8
+ // `seat/call` is a request that has to be answered. The one place this
9
+ // bites is a cordis disposer, which is synchronous and cannot await — see
10
+ // the note on `seat()` below.
11
+ // * **There is no ambient kernel.** A call is only meaningful inside the
12
+ // scope incarnation it was made for, so the doorway exists only inside a
13
+ // handler. `AsyncLocalStorage` is what carries the handler's context down
14
+ // to code that has no way to be handed it — a dsh credential provider is
15
+ // called by an adapter that knows nothing about rebon.
16
+ // * **Registration is not a call.** What a composition provides is
17
+ // reported once, in the `plugin/load` ready report, and revoked by
18
+ // `plugin/unload` draining. It has to be that shape: a compensating
19
+ // `unregister` would have to run from a cordis disposer, and a disposer
20
+ // cannot await the answer.
21
+ import { AsyncLocalStorage } from 'node:async_hooks';
22
+ import { sessionOf } from './registry.mjs';
23
+
24
+ const calls = new AsyncLocalStorage();
25
+
26
+ /// Runs `fn` with `ctx` as the call everything underneath reaches through.
27
+ ///
28
+ /// Every handler the composition serves — service, tool, llm adapter, event
29
+ /// delivery — is wrapped in this, so anything it calls can find its way back
30
+ /// to rebon without the call context being threaded through dsh code that has
31
+ /// no parameter for it.
32
+ export function withCall(ctx, fn) {
33
+ return calls.run(ctx, fn);
34
+ }
35
+
36
+ /// The call this code is running inside.
37
+ export function currentCall() {
38
+ return calls.getStore();
39
+ }
40
+
41
+ /// The call this code is running inside, or a refusal that says why not.
42
+ ///
43
+ /// The refusal is worth its own code: "cannot read properties of undefined" at
44
+ /// the bottom of a dsh stack says nothing, whereas this names the actual rule —
45
+ /// rebon is reachable from inside a call, and a disposer or a module top level
46
+ /// is not inside one.
47
+ function require(what, via) {
48
+ const ctx = calls.getStore();
49
+ if (ctx !== undefined) return ctx;
50
+ // Nothing is being answered, so this is a plugin acting on its own schedule.
51
+ // `via` is the Cordis context of the plugin whose code is running, and the
52
+ // session it is attached to is the honest identity for what it does — the
53
+ // work is still that plugin's, in that session. Without a session there is
54
+ // no identity to use, and inventing one would make the ledger a guess.
55
+ const session = sessionOf(via);
56
+ if (session !== undefined) return session;
57
+ throw new Error(
58
+ `[NO_CALL_CONTEXT] ${what} is not reachable from here: no call rebon made is in flight, `
59
+ + 'and this plugin is not attached to a session. Teardown paths must rely on unload draining instead',
60
+ );
61
+ }
62
+
63
+ /// Calls a kernel seat: the composition's `credentials`, `settings`, `web` and
64
+ /// the rest of what rebon installs a plugin to use.
65
+ ///
66
+ /// A request, therefore answerable, therefore **not** usable from a cordis
67
+ /// disposer: `dispose()` is synchronous and a cross-process call cannot be.
68
+ /// Nothing in the composition needs it to be — the calls that used to happen
69
+ /// on the way out were unregistrations, and those are the protocol's job now.
70
+ export function seat(name, method, params = null, options = {}) {
71
+ const { via, ...call } = options;
72
+ return require(`seat ${JSON.stringify(name)}`, via).seat(name, method, params, call);
73
+ }
74
+
75
+ /// Invokes one rebon core tool on the session this call belongs to.
76
+ ///
77
+ /// Three gates stand in front of it and only the first is here: the plugin's
78
+ /// manifest must declare the tool, rebon's build must expose it, and the
79
+ /// embedder's broker decides whether this run is permitted.
80
+ export function invokeTool(tool, input = null, options = {}) {
81
+ const { via, ...call } = options;
82
+ return require(`tool ${JSON.stringify(tool)}`, via).invoke(tool, input, call);
83
+ }
84
+
85
+ /// Publishes one event onto rebon's event plane.
86
+ ///
87
+ /// Not the same thing as `emit` below, and the difference is the audience: a
88
+ /// chunk is a piece of the answer the caller of *this* call is reading, an
89
+ /// event is a fact anyone listening may act on. The composition's runtime
90
+ /// events — a dsh tool body appending to the session log — are the second kind.
91
+ ///
92
+ /// Declared as `publishedTopics` in the manifest, separately from what the
93
+ /// composition listens to.
94
+ export function publish(topic, event = null, options = {}) {
95
+ const { via, ...call } = options;
96
+ return require(`topic ${JSON.stringify(topic)}`, via).publish(topic, event, call);
97
+ }
98
+
99
+ /// Emits one piece of a streamed answer on the call this code is serving.
100
+ ///
101
+ /// This is the transport `op_llm_emit` and `op_tool_serve_emit` used to be,
102
+ /// with the pumps gone: a chunk belongs to a call, and the call is the one the
103
+ /// handler was entered with rather than an id looked up in a table.
104
+ export function emit(piece) {
105
+ return require('emit').emit(piece);
106
+ }
107
+
108
+ /// The workspace root of the session this call belongs to, or undefined
109
+ /// outside a call.
110
+ export function workspaceRoot() {
111
+ return calls.getStore()?.workspaceRoot;
112
+ }
113
+
114
+ /// The abort signal rebon raises when it asks this call to stop.
115
+ export function callSignal() {
116
+ return calls.getStore()?.signal;
117
+ }
118
+
119
+ /// Diagnostics.
120
+ ///
121
+ /// Best-effort on purpose, and never a reason for a call to fail: a log line
122
+ /// that cannot be delivered is not worth failing the work that produced it.
123
+ /// Inside a call it goes to the kernel's `logger` seat, so the line is
124
+ /// attributed to a scope; outside one it goes to stderr, which the supervisor
125
+ /// already collects as the host's diagnostic tail. stdout is never touched —
126
+ /// that is the protocol's channel.
127
+ /// A token bucket per level, so one plugin in a logging loop cannot fill the
128
+ /// worker's log.
129
+ ///
130
+ /// Here rather than on rebon's side because the cheapest byte is the one that
131
+ /// never crosses the process boundary, and because this side already knows how
132
+ /// many it dropped. Drops are reported, at most once a second: replacing a
133
+ /// blind spot with a quieter blind spot is not an improvement, and a reader who
134
+ /// sees a gap deserves to know it is a gap.
135
+ const RATE = { perSecond: 20, burst: 100 };
136
+ const buckets = new Map();
137
+
138
+ function admit(level) {
139
+ const now = Date.now();
140
+ let bucket = buckets.get(level);
141
+ if (bucket === undefined) {
142
+ bucket = { tokens: RATE.burst, last: now, dropped: 0, reportedAt: 0 };
143
+ buckets.set(level, bucket);
144
+ }
145
+ bucket.tokens = Math.min(
146
+ RATE.burst,
147
+ bucket.tokens + ((now - bucket.last) / 1000) * RATE.perSecond,
148
+ );
149
+ bucket.last = now;
150
+ if (bucket.tokens >= 1) {
151
+ bucket.tokens -= 1;
152
+ return { allowed: true, dropped: 0 };
153
+ }
154
+ bucket.dropped += 1;
155
+ if (now - bucket.reportedAt >= 1000) {
156
+ bucket.reportedAt = now;
157
+ const dropped = bucket.dropped;
158
+ bucket.dropped = 0;
159
+ return { allowed: false, dropped };
160
+ }
161
+ return { allowed: false, dropped: 0 };
162
+ }
163
+
164
+ const LEVELS = ['trace', 'debug', 'info', 'warn', 'error'];
165
+ export const logger = Object.freeze(Object.fromEntries(LEVELS.map((level) => [
166
+ level,
167
+ (message, options = {}) => {
168
+ const line = String(message);
169
+ // Inside a call, that call is who is speaking. Outside one — a timer, an
170
+ // agent loop driving its own turn — `via` names the plugin, and the
171
+ // session it is attached to is the handle that can still reach the seat.
172
+ //
173
+ // This is the same ladder `require()` climbs, with one difference that
174
+ // matters: it does not throw. A log line is not worth failing a plugin over,
175
+ // and a line that goes to stderr is only ever read when the host dies — the
176
+ // very silence this is fixing.
177
+ const ctx = calls.getStore() ?? sessionOf(options.via);
178
+ if (ctx === undefined) {
179
+ process.stderr.write(`[compose:${level}] ${line}\n`);
180
+ return;
181
+ }
182
+ const { allowed, dropped } = admit(level);
183
+ if (dropped > 0) {
184
+ void ctx
185
+ .seat('logger', 'warn', { message: `${dropped} ${level} line(s) dropped: too many, too fast` })
186
+ .catch(() => {});
187
+ }
188
+ if (!allowed) return;
189
+ void ctx.seat('logger', level, { message: line }).catch(() => {
190
+ process.stderr.write(`[compose:${level}] ${line}\n`);
191
+ });
192
+ },
193
+ ])));
@@ -1,50 +1,50 @@
1
- // The composition's `ctx.credentials` seat, on the plugin plane.
2
- //
3
- // dsh configuration carries credential *references* (environment-variable
4
- // names); the provider owns the values. This resolves a reference through
5
- // rebon's `credentials` seat, whose `resolveEnv` runs the fail-closed
6
- // authorize waterfall before touching the environment — so an unauthorized or
7
- // unset reference is simply "absent", and the consumer's own missing-credential
8
- // diagnosis fires (dsh-llm's `MISSING_CREDENTIAL`) instead of this seat
9
- // inventing one.
10
- //
11
- // Ported from `js/compose/credentials-runtime.js`. The one change is that the
12
- // seat call is now a request across a process boundary, which means it is
13
- // awaited — and that it can only be made from inside a call rebon is waiting
14
- // on, or by the plugin being attached to a session. Both hold where a
15
- // credential is actually wanted: inside a model turn rebon asked for, or inside
16
- // one an agent loop is running in a session of its own. `bridge.mjs` names the
17
- // refusal if anything ever tries from a disposer or a module top level.
18
- import { Service } from 'cordis';
19
- import { seat } from './bridge.mjs';
20
-
21
- export default class RebonCredentials extends Service {
22
- constructor(ctx) {
23
- super(ctx, 'credentials');
24
- }
25
-
26
- /** dsh CredentialProvider.resolve: `{value, source}` or undefined. */
27
- async resolve(ref) {
28
- try {
29
- // `via` is this seat's caller — the plugin whose code wants the
30
- // credential. Inside a call that changes nothing; outside one it is what
31
- // lets an agent loop's own model turn resolve a key at all, attributed to
32
- // the plugin and session it belongs to.
33
- const out = await seat('credentials', 'resolveEnv', { ref: String(ref) }, { via: this.ctx });
34
- const value = out?.value;
35
- if (typeof value === 'string' && value.length > 0) {
36
- return { value, source: 'env' };
37
- }
38
- } catch {
39
- // A refusal from the seat (no authorizer, unset variable, bad ref) and
40
- // an absent value are the same fact to a consumer: not configured here.
41
- }
42
- return undefined;
43
- }
44
-
45
- /** dsh CredentialProvider.describe: facts only, never the value. */
46
- async describe(ref) {
47
- const hit = await this.resolve(ref);
48
- return { configured: hit !== undefined, ...(hit ? { source: hit.source } : {}), writable: false };
49
- }
50
- }
1
+ // The composition's `ctx.credentials` seat, on the plugin plane.
2
+ //
3
+ // dsh configuration carries credential *references* (environment-variable
4
+ // names); the provider owns the values. This resolves a reference through
5
+ // rebon's `credentials` seat, whose `resolveEnv` runs the fail-closed
6
+ // authorize waterfall before touching the environment — so an unauthorized or
7
+ // unset reference is simply "absent", and the consumer's own missing-credential
8
+ // diagnosis fires (dsh-llm's `MISSING_CREDENTIAL`) instead of this seat
9
+ // inventing one.
10
+ //
11
+ // Ported from `js/compose/credentials-runtime.js`. The one change is that the
12
+ // seat call is now a request across a process boundary, which means it is
13
+ // awaited — and that it can only be made from inside a call rebon is waiting
14
+ // on, or by the plugin being attached to a session. Both hold where a
15
+ // credential is actually wanted: inside a model turn rebon asked for, or inside
16
+ // one an agent loop is running in a session of its own. `bridge.mjs` names the
17
+ // refusal if anything ever tries from a disposer or a module top level.
18
+ import { Service } from 'cordis';
19
+ import { seat } from './bridge.mjs';
20
+
21
+ export default class RebonCredentials extends Service {
22
+ constructor(ctx) {
23
+ super(ctx, 'credentials');
24
+ }
25
+
26
+ /** dsh CredentialProvider.resolve: `{value, source}` or undefined. */
27
+ async resolve(ref) {
28
+ try {
29
+ // `via` is this seat's caller — the plugin whose code wants the
30
+ // credential. Inside a call that changes nothing; outside one it is what
31
+ // lets an agent loop's own model turn resolve a key at all, attributed to
32
+ // the plugin and session it belongs to.
33
+ const out = await seat('credentials', 'resolveEnv', { ref: String(ref) }, { via: this.ctx });
34
+ const value = out?.value;
35
+ if (typeof value === 'string' && value.length > 0) {
36
+ return { value, source: 'env' };
37
+ }
38
+ } catch {
39
+ // A refusal from the seat (no authorizer, unset variable, bad ref) and
40
+ // an absent value are the same fact to a consumer: not configured here.
41
+ }
42
+ return undefined;
43
+ }
44
+
45
+ /** dsh CredentialProvider.describe: facts only, never the value. */
46
+ async describe(ref) {
47
+ const hit = await this.resolve(ref);
48
+ return { configured: hit !== undefined, ...(hit ? { source: hit.source } : {}), writable: false };
49
+ }
50
+ }