@rebon/cli-linux-arm64 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.
@@ -1,149 +0,0 @@
1
- // rebon-loop-assembly: the composition-side glue that lets the REAL
2
- // dsh-agent-loop drive a turn inside this isolate (assembly doc:
3
- // docs/dsh-agent-loop-assembly.md §4).
4
- //
5
- // Three duties, all rebon-owned (the dsh packages stay unmodified):
6
- // 1. Tool-schema supply: registers a dsh systemPrompt tool provider that
7
- // merges the composition's own registered definitions with the R5 core
8
- // seat catalog (describeTools) — the same two sources the tools seat's
9
- // scheduler dispatches into, so model-visible and dispatchable stay in
10
- // step (local definition shadows a seat name in BOTH places).
11
- // 2. Observability: relays every dsh `session/event` (chunks included —
12
- // the embedder's session face streams from them) and `agent/error`
13
- // onto the kernel event plane (`loop:event` / `loop:agent-error`),
14
- // stamped with the embedder's `tag` so co-resident loop hosts stay
15
- // distinguishable.
16
- // 3. Drive: `loop:control` serve target (followup/steer/cancel/status)
17
- // is the embedder's inbound face; config `{kickoff: "..."}` remains as
18
- // an e2e self-drive convenience.
19
- import { emit, describeTools, logger } from 'rebon';
20
- import { createUserMessage, errorChain } from '@deepseek-ai/dsh-llm';
21
- import { registerServeTarget } from './serve-dispatch.js';
22
-
23
- /** Best-effort warn: the kernel logger seat is optional (tools-runtime rule). */
24
- function warn(message) {
25
- try {
26
- logger.warn(message);
27
- } catch {
28
- console.warn(message);
29
- }
30
- }
31
-
32
- export const name = 'rebon-loop-assembly';
33
- export const inject = ['systemPrompt', 'tools'];
34
-
35
- export async function apply(ctx, config = {}) {
36
- // Embedder-chosen discriminator: with several loop hosts in one process,
37
- // every relayed event carries the owning host's tag so kernel-plane
38
- // subscribers can tell their loop apart.
39
- const tag = typeof config.tag === 'string' && config.tag.length > 0 ? config.tag : undefined;
40
- const stamp = (payload) => (tag === undefined ? payload : { tag, ...payload });
41
- // Optional prompt contribution into the loop realm's REAL dsh
42
- // systemPrompt (this is the loop's own prompt plane, not rebon's P2.8
43
- // seat — the isolate keeps them apart).
44
- if (config.section !== undefined && config.section !== null && typeof config.section === 'object') {
45
- ctx.systemPrompt.section(config.section);
46
- }
47
-
48
- let seatSchemas = [];
49
- try {
50
- const catalog = await describeTools();
51
- seatSchemas = (Array.isArray(catalog) ? catalog : []).map((tool) => ({
52
- name: tool.name,
53
- description: String(tool.description ?? ''),
54
- parameters: tool.inputSchema ?? { type: 'object' },
55
- }));
56
- } catch (error) {
57
- // An unbound tool plane is a legal composition (prompt-only loop); the
58
- // schemas face is then just the composition's own registrations.
59
- warn(`loop-assembly: R5 seat catalog unavailable: ${String(error?.message ?? error)}`);
60
- }
61
-
62
- ctx.systemPrompt.tools(() => {
63
- const merged = new Map();
64
- for (const schema of seatSchemas) merged.set(schema.name, schema);
65
- for (const [toolName, def] of ctx.tools.defs) {
66
- merged.set(toolName, {
67
- name: toolName,
68
- description: String(def.description ?? ''),
69
- parameters: def.parameters ?? { type: 'object' },
70
- });
71
- }
72
- return { schemas: [...merged.values()] };
73
- });
74
-
75
- // Full-fidelity relay: the dsh session log IS the loop's truth, and the
76
- // embedder's session face (streaming UI, transcript projection) needs
77
- // every event — chunks included.
78
- ctx.on('session/event', (session, event) => {
79
- emit('loop:event', stamp({
80
- sessionId: String(session.id),
81
- seq: event.seq,
82
- type: event.type,
83
- data: event.data ?? null,
84
- }));
85
- });
86
-
87
- ctx.on('agent/error', (payload) => {
88
- emit('loop:agent-error', stamp({
89
- agentId: String(payload.agent?.id ?? ''),
90
- turn: payload.turn,
91
- step: payload.step,
92
- error: errorChain(payload.error),
93
- }));
94
- });
95
-
96
- const live = new Map();
97
- const kicked = new Set();
98
- ctx.on('agent/created', ({ agent }) => {
99
- live.set(String(agent.id), agent);
100
- emit('loop:agent-created', stamp({ agentId: String(agent.id) }));
101
- const kickoff = config.kickoff;
102
- if (typeof kickoff !== 'string' || kickoff.length === 0) return;
103
- if (kicked.has(agent.id)) return;
104
- kicked.add(agent.id);
105
- agent.followup(createUserMessage({
106
- content: [{ type: 'text', text: kickoff }],
107
- source: { kind: 'user' },
108
- }));
109
- });
110
- ctx.on('agent/disposed', ({ agent }) => {
111
- live.delete(String(agent.id));
112
- });
113
-
114
- // Inbound control face: the embedder drives the loop over the serve
115
- // plane (request/response — the same plane rebon uses to call
116
- // composition tools). Commands address the single configured agent by
117
- // default, or a specific one via `agentId`.
118
- const resolveAgent = (input) => {
119
- if (typeof input?.agentId === 'string') return live.get(input.agentId);
120
- if (live.size === 1) return live.values().next().value;
121
- return undefined;
122
- };
123
- const userMessage = (text) => createUserMessage({
124
- content: [{ type: 'text', text: String(text) }],
125
- source: { kind: 'user' },
126
- });
127
- registerServeTarget('loop:control', (input) => {
128
- const agent = resolveAgent(input);
129
- if (agent === undefined) {
130
- const known = [...live.keys()];
131
- throw new Error(`loop:control: no live agent${input?.agentId ? ` "${input.agentId}"` : ''} (live: ${known.join(', ') || '(none)'})`);
132
- }
133
- switch (input?.kind) {
134
- case 'followup':
135
- agent.followup(userMessage(input.text ?? ''));
136
- return { accepted: true, agentId: String(agent.id) };
137
- case 'steer':
138
- agent.steer(userMessage(input.text ?? ''));
139
- return { accepted: true, agentId: String(agent.id) };
140
- case 'cancel':
141
- agent.cancel({ kind: 'user' });
142
- return { accepted: true, agentId: String(agent.id) };
143
- case 'status':
144
- return { agentId: String(agent.id), status: agent.status };
145
- default:
146
- throw new Error(`loop:control: unknown command kind ${JSON.stringify(input?.kind)}`);
147
- }
148
- });
149
- }
@@ -1,82 +0,0 @@
1
- // Shared serve pump over the tool-serve ops (P1.6/P2.7): one pump per
2
- // composition isolate, exact-name target dispatch. The tools seat registers
3
- // each tool under its own name; the web seat registers providers under the
4
- // reserved `web:<kind>:<id>` namespace. Handlers run concurrently (one slow
5
- // call never head-of-line blocks the pump); `cancel` aborts the per-call
6
- // AbortController.
7
- //
8
- // Envelope contract with the Rust side: `{ok: <handler return>}` on
9
- // success; `{err: message, code?}` on throw (a thrown `code` property —
10
- // dsh WebError/HarnessError — rides along for machine routing).
11
-
12
- const core = globalThis.Deno.core;
13
-
14
- const targets = new Map(); // target name -> async (input, signal, id) => value
15
- const aborts = new Map(); // serve call id -> AbortController
16
- let pumping = false;
17
-
18
- /**
19
- * Register one serve target. Exact-name dispatch; duplicates throw. Returns
20
- * the disposer.
21
- */
22
- export function registerServeTarget(name, handler) {
23
- if (targets.has(name)) {
24
- throw new Error(`serve target "${name}" is already registered`);
25
- }
26
- targets.set(name, handler);
27
- return () => targets.delete(name);
28
- }
29
-
30
- /** Start the pump once; later calls are no-ops. */
31
- export function ensureServePump() {
32
- if (pumping) return;
33
- pumping = true;
34
- void pump();
35
- }
36
-
37
- async function pump() {
38
- while (true) {
39
- const instr = JSON.parse(await core.ops.op_tool_serve_next());
40
- if (instr.closed) break;
41
- if (instr.kind === 'cancel') {
42
- aborts.get(instr.id)?.abort();
43
- continue;
44
- }
45
- if (instr.kind !== 'request') continue;
46
- void serve(instr.id, instr.tool, instr.input);
47
- }
48
- }
49
-
50
- async function serve(id, target, input) {
51
- const handler = targets.get(target);
52
- if (!handler) {
53
- emitEnvelope(id, {
54
- err: `[UNKNOWN_TOOL] target "${target}" is not registered in the composition`,
55
- });
56
- return;
57
- }
58
- const abort = new AbortController();
59
- aborts.set(id, abort);
60
- try {
61
- const value = await handler(input, abort.signal, id);
62
- emitEnvelope(id, { ok: value });
63
- } catch (e) {
64
- emitEnvelope(id, {
65
- err: String(e?.message ?? e),
66
- ...(typeof e?.code === 'string' ? { code: e.code } : {}),
67
- });
68
- } finally {
69
- aborts.delete(id);
70
- }
71
- }
72
-
73
- function emitEnvelope(id, envelope) {
74
- try {
75
- core.ops.op_tool_serve_emit(id, JSON.stringify(envelope));
76
- } catch (e) {
77
- core.ops.op_tool_serve_emit(
78
- id,
79
- JSON.stringify({ err: `serve result not serializable: ${String(e?.message ?? e)}` }),
80
- );
81
- }
82
- }
@@ -1,74 +0,0 @@
1
- // Minimal dsh-compatible `ctx.systemPrompt` seat for the composition host
2
- // (P2.8-JS, docs/dsh-tools-assembly.md / todo-atomization P2.8).
3
- //
4
- // API shape follows dsh packages/core/system-prompt: plugins call
5
- // `ctx.systemPrompt.section({name, order, text})` and get back the exact
6
- // disposer. Faithful validation subset: duplicate names throw the dsh
7
- // message, non-finite orders throw the dsh TypeError. Registrations mirror
8
- // into the kernel's process-level `system-prompt` service — riding the F1
9
- // effect ledger, so a hard-killed composition never leaves stale sections —
10
- // and rebon's own prompt assembly (P2.8 registry) renders them inside its
11
- // stable runtime-context plane, sorted by (order, name).
12
- //
13
- // Deliberately NOT provided (v1 honesty boundary): provider-function
14
- // `text` (dsh resolves it per assembly with an AssembleContext we do not
15
- // have — a static composition registers static text), `complete` sections
16
- // (replacing rebon's own prompt belongs to the P3.12 loop seat, not a
17
- // side door), `context()` / variables / tool-order surfaces. All fail
18
- // loudly rather than silently degrade.
19
- import { Service } from 'cordis';
20
- import { callService } from 'rebon';
21
-
22
- export default class RebonSystemPromptRuntime extends Service {
23
- constructor(ctx) {
24
- super(ctx, 'systemPrompt');
25
- this.names = new Set();
26
- }
27
-
28
- /** Register one ordered prompt section; returns the exact disposer. */
29
- section(entry) {
30
- const name = entry?.name;
31
- if (typeof name !== 'string' || name.trim().length === 0) {
32
- throw new TypeError('systemPrompt.section: section.name must be a non-empty string');
33
- }
34
- if (!Number.isFinite(entry.order)) {
35
- throw new TypeError(`prompt section "${name}" order must be a finite number`);
36
- }
37
- if (typeof entry.text === 'function') {
38
- throw new Error(
39
- `prompt section "${name}": provider-function text is not supported in the embedded runtime (register static text)`,
40
- );
41
- }
42
- if (typeof entry.text !== 'string') {
43
- throw new TypeError(`prompt section "${name}" text must be a string`);
44
- }
45
- if (entry.complete) {
46
- throw new Error(
47
- `prompt section "${name}": complete-section replacement is not supported in the embedded runtime`,
48
- );
49
- }
50
- if (this.names.has(name)) {
51
- throw new Error(`prompt section "${name}" is already registered in this scope`);
52
- }
53
- const runtime = this;
54
- // Caller-fork RAII through the Service proxy (llm/tools/web same
55
- // pattern): plugin disposal unwinds the kernel mirror. Owner captured
56
- // synchronously for ledger attribution (single-plugin unload sweep).
57
- const owner = this.ctx[Symbol.for('rebon.composeOwner')] ?? '';
58
- return this.ctx.effect(() => {
59
- const registered = callService('system-prompt', 'register', {
60
- name,
61
- order: entry.order,
62
- text: entry.text,
63
- }, owner);
64
- runtime.names.add(name);
65
- const token = registered?.token;
66
- return () => {
67
- runtime.names.delete(name);
68
- try {
69
- callService('system-prompt', 'unregister', { name, token });
70
- } catch {}
71
- };
72
- });
73
- }
74
- }
@@ -1,228 +0,0 @@
1
- // Minimal dsh-compatible `ctx.tools` seat for the composition host (P1.6,
2
- // docs/dsh-tools-assembly.md).
3
- //
4
- // API shape follows dsh packages/core/tools: plugins call
5
- // `ctx.tools.register(defineTool({...}))` and get back the exact disposer.
6
- // Definitions arrive with `parameters` already compiled to JSON Schema and
7
- // argument validation baked into `execute` (the vendored dsh-tools schema
8
- // layer does both). Each registration is mirrored into the kernel's
9
- // process-level `tool-registry` service — that call rides the bridge effect
10
- // ledger, so a hard-killed composition can never leave the kernel table
11
- // dirty — and execution requests come back over the shared serve pump
12
- // (serve-dispatch.js, target = the tool name), running the registered body
13
- // in this isolate.
14
- //
15
- // Deliberately NOT provided (v1 honesty boundary, see the assembly doc):
16
- // `restrict`/`guard` (dsh agent-scope concepts), code mode, output-schema
17
- // enforcement of the canonical value, presenter projections. `exec` carries
18
- // the identity fields plus an `agent.session.append` bridge that lands as
19
- // the kernel event `compose:session/append` — observable, but not a claim
20
- // of rebon transcript persistence (that is the P2.9 session seat).
21
- import { Service } from 'cordis';
22
- import { callService, emit, invokeTool } from 'rebon';
23
- import { TOOL_RUNTIME_SCHEDULER } from '@deepseek-ai/dsh-tools';
24
- import { ensureServePump, registerServeTarget } from './serve-dispatch.js';
25
-
26
- /** Extract a `[BRACKET_CODE]` prefix from a seat error message, if any. */
27
- function bracketCode(message) {
28
- const match = /^\[([A-Z_]+)\]/.exec(message);
29
- return match ? match[1] : undefined;
30
- }
31
-
32
- /** Per-run context/conclusion collection installed by scheduler.prepare. */
33
- const runStates = new WeakMap();
34
-
35
- export default class RebonToolsRuntime extends Service {
36
- constructor(ctx) {
37
- super(ctx, 'tools');
38
- this.names = new Set();
39
- // Local definition table for the scheduler's in-composition dispatch
40
- // (register() below keeps it in step with the kernel mirror).
41
- this.defs = new Map();
42
- ensureServePump();
43
-
44
- // ---- dsh scheduler contract (loop round) ----
45
- //
46
- // agent-loop's tool-calls scheduler drives tools through this
47
- // symbol-keyed view (dsh's private loop↔tools seam, contract
48
- // transcribed from packages/core/tools/src/index.ts:451). Dispatch
49
- // resolution: a composition-registered definition runs in-isolate;
50
- // anything else goes to the R5 core seat, whose refusals (stable
51
- // bracketed codes) come back as error RESULTS for the model, not
52
- // crashes. v1 honesty boundary: no pre/post-execute pipeline —
53
- // prepare always dispatches, finalize/finish pass through.
54
- const runtime = this;
55
- this[TOOL_RUNTIME_SCHEDULER] = {
56
- async prepare(exec) {
57
- const state = { contexts: [], concluded: false };
58
- const run = {
59
- ...exec,
60
- rootCallId: exec.rootCallId ?? exec.callId,
61
- deferContext(message) {
62
- state.contexts.push(message);
63
- },
64
- concludeTurn() {
65
- state.concluded = true;
66
- },
67
- };
68
- runStates.set(run, state);
69
- return { kind: 'dispatch', exec: run };
70
- },
71
- async dispatch(exec) {
72
- const state = runStates.get(exec) ?? { contexts: [], concluded: false };
73
- try {
74
- const def = runtime.defs.get(exec.name);
75
- let content;
76
- if (def !== undefined) {
77
- const value = await def.execute(exec.arguments, exec);
78
- content = def.output.render(exec.arguments, value);
79
- } else {
80
- const value = await invokeTool(exec.name, exec.arguments ?? {});
81
- content = [{
82
- type: 'text',
83
- text: typeof value === 'string' ? value : JSON.stringify(value),
84
- }];
85
- }
86
- return {
87
- kind: 'final-result',
88
- result: {
89
- content,
90
- isError: false,
91
- ...(state.contexts.length > 0 ? { additionalContexts: state.contexts } : {}),
92
- ...(state.concluded ? { concludesTurn: true } : {}),
93
- },
94
- };
95
- } catch (error) {
96
- const message = String(error?.message ?? error);
97
- return {
98
- kind: 'final-result',
99
- result: {
100
- content: [{ type: 'text', text: `Error: ${message}` }],
101
- isError: true,
102
- error: {
103
- message,
104
- info: {
105
- name: error?.name ?? 'Error',
106
- code: bracketCode(message) ?? 'TOOL_FAILED',
107
- },
108
- },
109
- },
110
- };
111
- }
112
- },
113
- async finalize(_exec, result) {
114
- return result;
115
- },
116
- finish(_exec, result) {
117
- return result;
118
- },
119
- };
120
- }
121
-
122
- /**
123
- * dsh concurrency classification. v1: everything is exclusive — strictly
124
- * serial scheduling is always semantically correct, and it sidesteps the
125
- * parallel pool until `isConcurrencySafe` mapping is wired.
126
- */
127
- executionMode(_exec) {
128
- return { kind: 'exclusive' };
129
- }
130
-
131
- /**
132
- * Register one dsh-shaped tool definition. Mirrors dsh
133
- * `ToolRuntime.register` validation; returns the exact disposer.
134
- */
135
- register(definition) {
136
- const name = definition?.name;
137
- if (typeof name !== 'string' || name.trim().length === 0) {
138
- throw new TypeError('tools.register: definition.name must be a non-empty string');
139
- }
140
- if (name.startsWith('web:')) {
141
- throw new Error(`tool name "${name}" uses the reserved web-provider serve namespace`);
142
- }
143
- const output = definition.output;
144
- if (
145
- output === undefined || typeof output !== 'object'
146
- || typeof output.render !== 'function'
147
- ) {
148
- throw new TypeError(`tool "${name}" must declare output { schema, render }`);
149
- }
150
- if (typeof definition.execute !== 'function') {
151
- throw new TypeError(`tool "${name}" must declare execute()`);
152
- }
153
- const timeoutMs = definition.timeoutMs;
154
- if (timeoutMs !== undefined && (!Number.isFinite(timeoutMs) || timeoutMs <= 0)) {
155
- throw new TypeError(`tool "${name}" timeoutMs must be a positive finite number`);
156
- }
157
- if (this.names.has(name)) {
158
- throw new Error(`tool "${name}" is already registered in this scope`);
159
- }
160
- const runtime = this;
161
- // `this.ctx` is the CALLER's context through the Service proxy, so the
162
- // effect (and with it the kernel mirror) unwinds when the registering
163
- // plugin disposes — cordis RAII, llm-runtime same pattern. ctx.effect
164
- // returns the disposer, which dsh's register contract hands back.
165
- // Ledger attribution: the composition-entry id, captured synchronously
166
- // (never ambient — async interleavings must not mis-attribute).
167
- const owner = this.ctx[Symbol.for('rebon.composeOwner')] ?? '';
168
- return this.ctx.effect(() => {
169
- const registered = callService('tool-registry', 'register', {
170
- name,
171
- description: String(definition.description ?? ''),
172
- inputSchema: definition.parameters ?? { type: 'object' },
173
- }, owner);
174
- if (registered?.shadowed) {
175
- // Same rule as R3: builtin always wins at dispatch; loud, not
176
- // fatal — and best-effort, because the Rust registry already
177
- // logged it and a missing logger seat must not fail registration.
178
- const message = `composition tool "${name}" shares a builtin tool's name; the builtin wins`;
179
- try {
180
- callService('logger', 'warn', { message });
181
- } catch {
182
- console.warn(message);
183
- }
184
- }
185
- runtime.names.add(name);
186
- runtime.defs.set(name, definition);
187
- const disposeServe = registerServeTarget(name, (input, signal, id) =>
188
- runtime._execute(definition, input, signal, id),
189
- );
190
- const token = registered?.token;
191
- return () => {
192
- disposeServe();
193
- runtime.names.delete(name);
194
- runtime.defs.delete(name);
195
- try {
196
- callService('tool-registry', 'unregister', { name, token });
197
- } catch {}
198
- };
199
- });
200
- }
201
-
202
- async _execute(definition, input, signal, id) {
203
- const callId = `compose:${id}`;
204
- const tool = definition.name;
205
- const exec = {
206
- callId,
207
- rootCallId: callId,
208
- name: tool,
209
- arguments: input,
210
- signal,
211
- // v1 bridge: dsh tools write per-session facts through
212
- // `exec.agent.session.append`; here that lands as a kernel event
213
- // the embedder can observe. No persistence claim.
214
- agent: {
215
- session: {
216
- append(type, data) {
217
- emit('compose:session/append', { type, data, tool, callId });
218
- },
219
- },
220
- },
221
- };
222
- // defineTool's execute validates arguments first (ToolArgsError with
223
- // INVALID_ARGS semantics flows into the err envelope's message).
224
- const value = await definition.execute(input, exec);
225
- const content = definition.output.render(input, value);
226
- return { content, isError: false };
227
- }
228
- }