@byokit/openclaw 0.19.0 → 0.21.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.
@@ -281,15 +281,45 @@ export function fakeGateway(script, o) {
281
281
  for (const waiter of run.waiters.splice(0))
282
282
  waiter(result);
283
283
  };
284
- const emitRun = (run, stream, data) => emit('agent', { runId: run.runId, seq: run.seq++, stream, ts: Date.now(), data });
285
- /** The `agent` handler: the run's scripted turn, its `[tool NAME {json}]` calls through the real bridge. */
286
- const startRun = (params, bridgeSock) => {
284
+ const emitRun = (run, stream, data) => {
285
+ emit('agent', { runId: run.runId, seq: run.seq++, stream, ts: Date.now(), data });
286
+ const child = run.child;
287
+ if (child)
288
+ child.relay = child.relay.then(() => bridgeRequest(child.sock, { kind: 'delegation-event',
289
+ event: { runId: run.runId, sessionKey: run.sessionKey, stream, data } })).catch(() => { });
290
+ };
291
+ /**
292
+ * A native `sessions_send` as the engine and plugin play it: the parent's dispatch is tracked before it goes, then the
293
+ * exact target session runs as an engine-created child and the receipt names its run (delegated admission, 5.8).
294
+ */
295
+ const nativeSend = async (run, parentRunId, input, callId, sock) => {
296
+ const ids = { key: run.sessionKey, runId: parentRunId, callId };
297
+ const prepared = await bridgeRequest(sock, { kind: 'delegation-prepare', ...ids, tool: 'sessions_send', input });
298
+ if (prepared.allow === false)
299
+ return { isError: true, output: String(prepared.reason) };
300
+ const sessionKey = String(input.sessionKey);
301
+ const agentId = /^agent:([^:]+):/.exec(sessionKey)?.[1];
302
+ // Like the pin: a send reaches only a session that already exists (its agent's main excepted).
303
+ const child = agentId && (sessions.has(`${agentId}\n${sessionKey}`) || sessionKey === `agent:${agentId}:main`)
304
+ ? startRun({ agentId, sessionKey, message: input.message }, sock, true) : undefined;
305
+ const details = child ? { status: 'accepted', runId: child.runId, sessionKey }
306
+ : { status: 'error', error: `No session found: ${sessionKey}`, sessionKey };
307
+ await bridgeRequest(sock, { kind: 'delegation-receipt', ...ids, result: { details } });
308
+ return { isError: !child, output: JSON.stringify(details) };
309
+ };
310
+ /** The `agent` handler: the run's scripted turn, its `[tool NAME {json}]` calls through the real bridge. An
311
+ * engine-created `child` names its own run on the bridge, as the plugin does with the engine's run id. */
312
+ const startRun = (params, bridgeSock, child = false) => {
287
313
  if (params.agentId != null && !agents.has(String(params.agentId)))
288
314
  throw new Error(`unknown agent: ${params.agentId}`);
289
315
  // Like the engine: the provider/model the run is called on, its own override or the member's (here openai).
290
316
  const run = { runId: randomUUID(), agentId: String(params.agentId ?? 'main'), sessionKey: String(params.sessionKey ?? ''), seq: 0,
291
317
  provider: String(params.provider ?? 'openai'), model: String(params.model ?? 'gpt-5.1'), waiters: [] };
292
318
  runs.set(run.runId, run);
319
+ if (child) {
320
+ run.child = { sock: bridgeSock, relay: Promise.resolve() };
321
+ emitRun(run, 'lifecycle', { phase: 'start' });
322
+ }
293
323
  const session = sessionOf(run.agentId, run.sessionKey);
294
324
  session.provider = run.provider;
295
325
  session.model = run.model;
@@ -309,13 +339,17 @@ export function fakeGateway(script, o) {
309
339
  let isError = false;
310
340
  if (bridgeSock && !(builtin && table?.gateBuiltins === false)) {
311
341
  try {
312
- const gate = await bridgeRequest(bridgeSock, { kind: 'gate', key: run.sessionKey, tool: call.name, input: call.input });
342
+ const owner = child ? { runId: run.runId } : {};
343
+ const gate = await bridgeRequest(bridgeSock, { kind: 'gate', key: run.sessionKey, tool: call.name, input: call.input, ...owner });
313
344
  if (!gate.allow) {
314
345
  isError = true;
315
346
  output = String(gate.reason ?? 'blocked');
316
347
  }
348
+ else if (builtin && call.name === 'sessions_send') {
349
+ ({ isError, output } = await nativeSend(run, child ? run.runId : String(params.idempotencyKey ?? ''), call.input, toolCallId, bridgeSock));
350
+ }
317
351
  else if (!builtin) {
318
- const done = await bridgeRequest(bridgeSock, { kind: 'call', key: run.sessionKey, permit: gate.permit, tool: call.name, input: call.input });
352
+ const done = await bridgeRequest(bridgeSock, { kind: 'call', key: run.sessionKey, permit: gate.permit, tool: call.name, input: call.input, ...owner });
319
353
  isError = done.ok !== true;
320
354
  output = String(done.ok === true ? done.text : done.reason);
321
355
  }
@@ -332,7 +366,14 @@ export function fakeGateway(script, o) {
332
366
  append(session, { role: 'assistant', content: [{ type: 'text', text }] });
333
367
  // The engine sums the run's usage (zero buckets omitted); the fake counts characters.
334
368
  run.usage = { input: message.length, output: text.length, total: message.length + text.length };
335
- setTimeout(() => finish(run, { status: 'ok', terminalReply: { text } }), ABORT_WINDOW_MS);
369
+ setTimeout(() => {
370
+ if (run.result || !child)
371
+ return finish(run, { status: 'ok', terminalReply: { text } });
372
+ // A child's lifecycle end, as the engine streams it: `hit the limit` plays the model's usage-limit error.
373
+ const error = /hit the limit/i.test(message) ? 'You have hit your ChatGPT usage limit (plus plan). Try again in ~30 min.' : undefined;
374
+ emitRun(run, 'lifecycle', error ? { phase: 'error', error } : { phase: 'end' });
375
+ finish(run, error ? { status: 'error', error } : { status: 'ok', terminalReply: { text } });
376
+ }, ABORT_WINDOW_MS);
336
377
  })();
337
378
  }, 0);
338
379
  return { runId: run.runId, status: 'accepted' };
package/dist/types.d.ts CHANGED
@@ -19,6 +19,7 @@ export interface ToolSpec {
19
19
  export interface RunRef {
20
20
  sessionKey: string;
21
21
  member: Member;
22
+ helper?: string;
22
23
  meta?: unknown;
23
24
  runId?: string;
24
25
  agentId?: string;
@@ -31,11 +32,28 @@ export interface RunRef {
31
32
  runId: string;
32
33
  };
33
34
  }
34
- export type DelegatedRun = RunRef & Required<Pick<RunRef, 'runId' | 'agentId' | 'parent' | 'root'>>;
35
+ /** One of a member's helpers (5.8.3): its own persona and sessions (`sessionPrefix`) on `account`, run by `agentId`, the
36
+ * engine agent already holding that sign-in. */
37
+ export type Helper = {
38
+ member: Member;
39
+ helper: string;
40
+ account: AccountId;
41
+ agentId: string;
42
+ sessionPrefix: string;
43
+ };
44
+ /** An admitted engine-created child (5.8): `tools` is the app-tool subset it may call (the parent's, narrowed). */
45
+ export type DelegatedRun = RunRef & Required<Pick<RunRef, 'runId' | 'agentId' | 'parent' | 'root'>> & {
46
+ tools: string[];
47
+ };
48
+ /** A delegated child's lifecycle (5.8.3 S3): `admitted`, its engine `event`s, then one `ended` whose `reason` is
49
+ * `terminal` (finished), `error` (`message`), `resting` (its helper's account hit its limit and rests `until`) or
50
+ * `cancelled` (aborted by the kit or the engine, or its admission ended). */
35
51
  export type DelegatedEvent = {
36
52
  type: 'admitted' | 'event' | 'ended';
37
53
  run: DelegatedRun;
38
- reason?: 'terminal' | 'cancelled';
54
+ reason?: 'terminal' | 'error' | 'resting' | 'cancelled';
55
+ until?: number;
56
+ message?: string;
39
57
  event?: {
40
58
  stream: string;
41
59
  data?: Record<string, unknown>;
@@ -77,6 +95,9 @@ export interface RunSpec<S extends OutputSchema | undefined = OutputSchema | und
77
95
  tools?: string[];
78
96
  /** Exact target engine agent IDs and their app-tool subsets; does not share credentials. */
79
97
  delegates?: Record<string, string[]>;
98
+ /** The member's ensured helpers (5.8.3) this run may hand work to, and their app-tool subsets: an exact native send to
99
+ * `agent:<the helper's agent>:h-<helper>:<rest>` is admitted as that helper's run (RunRef.helper). */
100
+ helperDelegates?: Record<string, string[]>;
80
101
  register?: boolean;
81
102
  }
82
103
  export type RunEvent = {
@@ -181,6 +202,7 @@ export type Approval = {
181
202
  source: 'gate' | 'exec' | 'plugin' | 'question';
182
203
  member: Member;
183
204
  sessionKey?: string;
205
+ helper?: string;
184
206
  tool?: string;
185
207
  summary: string;
186
208
  input?: unknown;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@byokit/openclaw",
3
- "version": "0.19.0",
3
+ "version": "0.21.0",
4
4
  "description": "The OpenClaw runtime kit: the pinned engine's full operator surface with plain-words helpers for members, sign-in, runs and approvals.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",