@byokit/openclaw 0.24.0 → 0.25.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.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,13 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.25.0 (2026-10-20)
6
+
7
+ - Dependency update: pins @byokit/accounts 0.29.0.
8
+
9
+ - FEAT: a MiniMax API key now lists and runs any model MiniMax's own `/models` lists, with no BYOKit model list: `kit.models({ authChoice: 'minimax-global-api' })` answers the vendor's models, and `addKey({ ..., model })` takes an id the list lags (such as `MiniMax-M3.1-Flash-Preview`) with the route's own capabilities (image input included) instead of refusing it or falling back to `MiniMax-M3`.
10
+ - FIX: When a child a run sent work to (`sessions_send`, `timeoutSeconds: 0`) finished, the engine's own reply run bringing its report back to the sender's session was refused `unknown run` at every app tool. That return run is now admitted as the sender: same member and helper, same app-tool subset and delegates, and the sender's system text as its persona; `RunRef.parent` names the finished child. Any other run on that session is still refused.
11
+
5
12
  ## 0.24.0 (2026-10-20)
6
13
 
7
14
  - FEAT: a helper's delegated run now speaks as its helper. A native `sessions_send` carries no system text, so the kit's plugin adds the helper's recorded persona (from `ensureHelper`) as system context to the admitted helper child's run, and only to it. No app change: Chief's `helperDelegates` send to `agent:<agent>:h-<helper>:<rest>` is enough. The kit plugin's config entry now sets `hooks.allowConversationAccess: true` for this hook. `sessions_spawn` children get no persona.
package/README.md CHANGED
@@ -452,6 +452,13 @@ and terminal observations with exact `runId`, `agentId`, `member`, `sessionKey`,
452
452
  The same identity reaches `ToolHost`; each delegated action has its own gate/approval. Tool arguments/results
453
453
  are omitted from this event surface. An accepted native dispatch is not finished work or proof of delivery.
454
454
 
455
+ When a sent child finishes, the engine starts its own reply run on the sender's session to bring the report back.
456
+ The kit admits that exact run as the sender: same member and helper, same app-tool subset and delegates, the sender's
457
+ `system` as its persona, `RunRef.parent` naming the finished child. Only a run whose prompt the engine headed with that
458
+ child's session, on the sender's exact key, within a minute of the child's (or the previous return turn's) end, binds;
459
+ any other run there stays unknown. Return runs are not on `delegatedRuns()` or `byokit.delegated`; read them from the
460
+ engine's `agent` events on the sender's key. The pinned engine starts no reply run for a child that took over 30 s.
461
+
455
462
  Admitted exact run IDs survive parent settlement and are reconciled on reconnect/start; a child the engine no longer
456
463
  knows (as after an engine restart) ends `lost`, and a dispatch that finds the journal full reconciles first. `abort(rootSessionKey)`
457
464
  durably revokes its recorded subtree, closes parked gates/active calls, then asks the engine to abort; a child key
package/dist/bridge.js CHANGED
@@ -332,11 +332,13 @@ export class Bridge {
332
332
  delegations.event(message.event);
333
333
  this.reply(socket, { ok: true });
334
334
  }
335
- // The plugin's prompt hook on a helper key: an admitted helper child speaks as its helper (5.8.3).
335
+ // The plugin's prompt hook on a helper key or an inter-session prompt: an admitted helper child speaks as its
336
+ // helper (5.8.3), a return run as the kit run that sent the work (5.8.4).
336
337
  else if (message.kind === 'delegation-persona' && typeof message.key === 'string') {
337
338
  const runId = typeof message.runId === 'string' ? message.runId : undefined;
338
339
  // A kit run carries its own persona; only an engine-created child is looked up (it may wait for its receipt).
339
- const persona = runId && this.isRegisteredRun(message.key, runId) ? undefined : await delegations.persona(message.key, runId);
340
+ const persona = runId && this.isRegisteredRun(message.key, runId) ? undefined
341
+ : await delegations.persona(message.key, runId, typeof message.header === 'string' ? message.header : undefined);
340
342
  this.reply(socket, persona === undefined ? {} : { persona });
341
343
  }
342
344
  else
@@ -2,4 +2,5 @@ export declare const ENGINE_VERSION = "2026.8.35";
2
2
  export declare const PROTOCOL_VERSION = 4;
3
3
  export declare const LIVE_CATALOGS: string[];
4
4
  export declare const REGISTERED_CATALOGS: string[];
5
+ export declare const OPEN_CATALOGS: string[];
5
6
  export declare const OPERATOR_SCOPES: readonly ['operator.read', 'operator.write', 'operator.admin', 'operator.approvals', 'operator.questions', 'operator.pairing', 'operator.talk'];
package/dist/constants.js CHANGED
@@ -4,10 +4,13 @@ export const ENGINE_VERSION = '2026.8.35';
4
4
  export const PROTOCOL_VERSION = 4;
5
5
  // Providers whose live catalog lists and checks a key's chosen model (5.6), through the catalog hook. OpenRouter is
6
6
  // pay-as-you-go: its key takes any model its own catalog lists, with no plan to include it.
7
- export const LIVE_CATALOGS = ['opencode-go', 'openrouter'];
7
+ export const LIVE_CATALOGS = ['opencode-go', 'openrouter', 'minimax'];
8
8
  // Live catalogs the kit registers whole. The engine canonicalizes Google preview ids on config write, so OpenRouter's
9
9
  // list collides with itself (`google/x-preview` and `google/x`); an OpenRouter add registers only the chosen model.
10
- export const REGISTERED_CATALOGS = ['opencode-go'];
10
+ export const REGISTERED_CATALOGS = ['opencode-go', 'minimax'];
11
+ // Pay-as-you-go catalogs whose vendor list can lag a model that already runs: an id the list lacks is warned about, never
12
+ // refused, and takes the capabilities of the route's own models.
13
+ export const OPEN_CATALOGS = ['minimax'];
11
14
  export const OPERATOR_SCOPES = [
12
15
  'operator.read',
13
16
  'operator.write',
@@ -1,16 +1,26 @@
1
1
  import type { DelegatedEvent, DelegatedRun, GatewayTransport, RunRef } from './types.ts';
2
+ type Back = {
3
+ member: string;
4
+ helper?: string;
5
+ tools: string[];
6
+ helpers?: Record<string, string[]>;
7
+ };
2
8
  type Grant = {
3
9
  ref: DelegatedRun;
4
10
  tools: string[];
5
11
  delegates: Record<string, string[]>;
6
12
  helpers?: Record<string, string[]>;
7
13
  sourceKey: string;
14
+ back?: Back;
8
15
  };
9
16
  export declare class Delegations {
10
17
  private loaded;
11
18
  private roots;
12
19
  private children;
13
20
  private pending;
21
+ private personas;
22
+ private armed;
23
+ private returns;
14
24
  private changed;
15
25
  private early;
16
26
  constructor(privateOptions: {
@@ -31,15 +41,19 @@ export declare class Delegations {
31
41
  private load;
32
42
  private save;
33
43
  validate(delegates: Record<string, string[]>, member?: string, helpers?: Record<string, string[]>): void;
34
- register(ref: RunRef, runId: string, tools: readonly string[] | undefined, delegates: Record<string, string[]>, sourceKey?: string, helpers?: Record<string, string[]>): () => void;
44
+ register(ref: RunRef, runId: string, tools: readonly string[] | undefined, delegates: Record<string, string[]>, sourceKey?: string, helpers?: Record<string, string[]>, system?: string): () => void;
35
45
  list(): DelegatedRun[];
36
46
  has(key: string, runId: string): boolean;
37
47
  private full;
38
48
  prepare(m: Record<string, any>): Promise<boolean>;
39
49
  receipt(m: Record<string, any>): void;
40
50
  resolve(key: string, runId?: string): Promise<Grant | undefined>;
41
- /** The persona of the admitted helper child on this exact key and run, as its own run would carry it (5.8.3). */
42
- persona(key: string, runId?: string): Promise<string | undefined>;
51
+ /** The persona of the admitted helper child on this exact key and run, as its own run would carry it (5.8.3), or of
52
+ * the return run on its sender's key (5.8.4): `header` is the engine's inter-session header on that run's prompt. */
53
+ persona(key: string, runId?: string, header?: string): Promise<string | undefined>;
54
+ private bind;
55
+ private returned;
56
+ private arm;
43
57
  private bound;
44
58
  event(event: Record<string, any>): void;
45
59
  private ended;
@@ -5,6 +5,11 @@ import { classify } from './classify.js';
5
5
  import { HELPER_ID, HelperError, helperOf } from './helpers.js';
6
6
  // A resting end whose message names no time rests the account this long, as a kit run's does (runs.ts).
7
7
  const REST_MS = 300_000;
8
+ // A return binds within this long of its child's end (or of its previous turn's end): the engine's own reply step waits
9
+ // at most 60 s for the child before it starts.
10
+ const RETURN_MS = 60_000;
11
+ // The engine's own header on an inter-session prompt (pin `annotateInterSessionPromptText`), naming the source session.
12
+ const INTER_SESSION = /^\[Inter-session message\] sourceSession=(\S+)(?: sourceChannel=\S+)? sourceTool=sessions_send isUser=false$/;
8
13
  const object = (v) => !!v && typeof v === 'object' && !Array.isArray(v);
9
14
  const agentOf = (key) => /^agent:([^:]+):.+/.exec(key)?.[1];
10
15
  const actionKey = (m) => JSON.stringify([m.key, m.runId, m.callId]);
@@ -13,6 +18,10 @@ export class Delegations {
13
18
  roots = new Map();
14
19
  children = new Map();
15
20
  pending = new Map();
21
+ // In memory only (no prompt is persisted): a kit run's system text by its run id, its return runs' persona.
22
+ personas = new Map();
23
+ armed = [];
24
+ returns = new Map();
16
25
  changed = new Set();
17
26
  early = new Map();
18
27
  constructor(privateOptions) { this.o = privateOptions; }
@@ -56,9 +65,13 @@ export class Delegations {
56
65
  this.o.helper(member, helper); // never ensured, or its account gone: unknown_helper
57
66
  }
58
67
  }
59
- register(ref, runId, tools, delegates, sourceKey = ref.sessionKey, helpers) {
68
+ register(ref, runId, tools, delegates, sourceKey = ref.sessionKey, helpers, system = '') {
60
69
  this.load();
61
70
  this.validate(delegates, ref.member, helpers);
71
+ // ponytail: the oldest 256 kit runs' system texts are kept; a child of an older one gets no return run (fail closed).
72
+ this.personas.set(runId, system);
73
+ while (this.personas.size > 256)
74
+ this.personas.delete(this.personas.keys().next().value);
62
75
  const root = { ref: { ...ref, runId, root: { sessionKey: ref.sessionKey, runId } },
63
76
  tools: [...tools ?? this.o.tools], delegates: structuredClone(delegates), ...(helpers ? { helpers: structuredClone(helpers) } : {}), sourceKey };
64
77
  this.roots.set(runId, root);
@@ -68,7 +81,7 @@ export class Delegations {
68
81
  list() { this.load(); return [...this.children.values()].map(g => structuredClone(g.ref)); }
69
82
  has(key, runId) {
70
83
  this.load();
71
- return (this.roots.get(runId) ?? this.children.get(runId))?.ref.sessionKey === key;
84
+ return (this.roots.get(runId) ?? this.children.get(runId) ?? this.returns.get(runId))?.ref.sessionKey === key;
72
85
  }
73
86
  full() {
74
87
  for (const [id, p] of this.pending)
@@ -83,7 +96,7 @@ export class Delegations {
83
96
  // ponytail: a full journal of live children re-reads all 1024 per dispatch; index ends by age if that shows up.
84
97
  if (this.full())
85
98
  await this.reconcile();
86
- const parent = this.roots.get(m.runId) ?? this.children.get(m.runId);
99
+ const parent = this.roots.get(m.runId) ?? this.children.get(m.runId) ?? this.returns.get(m.runId);
87
100
  if (!parent || parent.ref.sessionKey !== m.key)
88
101
  return false;
89
102
  if (typeof m.callId !== 'string' || !m.callId || !object(m.input))
@@ -111,8 +124,11 @@ export class Delegations {
111
124
  }
112
125
  if (this.full())
113
126
  throw new Error('delegated run journal is full');
114
- this.pending.set(actionKey(m), { parent: structuredClone(parent), agent: agent,
115
- ...(send ? { key: m.input.sessionKey } : {}), ...(helper !== undefined ? { helper } : {}), expires: Date.now() + 60_000 });
127
+ const { persona: _, ...grant } = parent; // a return's persona is never persisted
128
+ this.pending.set(actionKey(m), { parent: structuredClone(grant), agent: agent,
129
+ ...(send ? { key: m.input.sessionKey } : {}), ...(helper !== undefined ? { helper } : {}),
130
+ // The engine brings a sent child's report back to the sender's key, as a return run (5.8.4); a spawn's is not one.
131
+ ...(send && !this.children.has(m.runId) ? { back: true } : {}), expires: Date.now() + 60_000 });
116
132
  this.save();
117
133
  return true;
118
134
  }
@@ -143,7 +159,9 @@ export class Delegations {
143
159
  parent: { sessionKey: p.parent.ref.sessionKey, runId: p.parent.ref.runId }, root: p.parent.ref.root, tools };
144
160
  const grant = { ref, delegates: p.parent.delegates,
145
161
  ...(p.parent.helpers && ref.member === p.parent.ref.member ? { helpers: p.parent.helpers } : {}),
146
- sourceKey: p.parent.sourceKey, tools };
162
+ sourceKey: p.parent.sourceKey, tools,
163
+ ...(p.back ? { back: { member: p.parent.ref.member, ...(p.parent.ref.helper !== undefined ? { helper: p.parent.ref.helper } : {}),
164
+ tools: p.parent.tools, ...(p.parent.helpers ? { helpers: p.parent.helpers } : {}) } } : {}) };
147
165
  // A steered/already-admitted run keeps its original parent and cannot acquire a broader grant.
148
166
  if (this.children.has(ref.runId)) {
149
167
  this.save();
@@ -160,6 +178,13 @@ export class Delegations {
160
178
  this.load();
161
179
  if (!runId)
162
180
  return undefined;
181
+ const back = this.returns.get(runId);
182
+ if (back?.ref.sessionKey === key) {
183
+ if (back.ref.helper === undefined || this.bound(back.ref.member, back.ref.helper, back.ref.agentId))
184
+ return back;
185
+ this.returned(runId);
186
+ return undefined;
187
+ }
163
188
  const get = () => { const g = this.children.get(runId); return g?.ref.sessionKey === key ? g : undefined; };
164
189
  if (!get() && [...this.pending.values()].some(p => p.expires > Date.now())) {
165
190
  await new Promise(resolve => {
@@ -179,11 +204,68 @@ export class Delegations {
179
204
  }
180
205
  return grant;
181
206
  }
182
- /** The persona of the admitted helper child on this exact key and run, as its own run would carry it (5.8.3). */
183
- async persona(key, runId) {
207
+ /** The persona of the admitted helper child on this exact key and run, as its own run would carry it (5.8.3), or of
208
+ * the return run on its sender's key (5.8.4): `header` is the engine's inter-session header on that run's prompt. */
209
+ async persona(key, runId, header) {
210
+ this.load();
211
+ const source = header === undefined ? undefined : INTER_SESSION.exec(header)?.[1];
212
+ if (runId && source !== undefined && !this.returns.has(runId) && !this.children.has(runId))
213
+ await this.bind(key, runId, source);
214
+ const back = runId ? this.returns.get(runId) : undefined;
215
+ if (back?.ref.sessionKey === key)
216
+ return back.persona || undefined;
217
+ if (!/^agent:[^:]+:h-/.test(key))
218
+ return undefined;
184
219
  const helper = (await this.resolve(key, runId))?.ref;
185
220
  return helper?.helper === undefined ? undefined : this.o.helper(helper.member, helper.helper).persona;
186
221
  }
222
+ // The engine's reply step for a sent child starts a fresh run on the sender's exact key, its prompt headed by the
223
+ // child's exact session (pin `runSessionsSendA2AFlow`): bound only to a return armed by that child's own terminal end,
224
+ // one run at a time. A child still running when its report arrives is waited for briefly.
225
+ async bind(key, runId, source) {
226
+ const find = () => {
227
+ const now = Date.now();
228
+ this.armed = this.armed.filter(a => a.expires > now);
229
+ return this.armed.find(a => a.key === key && a.child.sessionKey === source && a.live === undefined);
230
+ };
231
+ if (!find() && [...this.children.values()].some(g => g.back && g.ref.sessionKey === source && g.ref.parent.sessionKey === key)) {
232
+ await new Promise(resolve => {
233
+ const done = () => { clearTimeout(timer); this.changed.delete(check); resolve(); };
234
+ const check = () => { if (find())
235
+ done(); };
236
+ const timer = setTimeout(done, 5_000);
237
+ this.changed.add(check);
238
+ });
239
+ }
240
+ const armed = find();
241
+ if (!armed)
242
+ return;
243
+ armed.live = runId;
244
+ this.returns.set(runId, { ...structuredClone(armed.grant), ref: { ...structuredClone(armed.grant.ref), runId }, persona: armed.persona });
245
+ }
246
+ // A return run ended: its report's next turn on the same key may bind within the window (the engine's ping-pong).
247
+ returned(runId) {
248
+ const back = this.returns.get(runId);
249
+ if (!back)
250
+ return;
251
+ this.returns.delete(runId);
252
+ this.o.revoke(back.ref.sessionKey, runId);
253
+ const armed = this.armed.find(a => a.live === runId);
254
+ if (armed) {
255
+ armed.live = undefined;
256
+ armed.expires = Date.now() + RETURN_MS;
257
+ }
258
+ }
259
+ arm(child) {
260
+ const { back, ref } = child, persona = this.personas.get(ref.root.runId);
261
+ if (!back || persona === undefined || this.armed.length >= 64)
262
+ return; // the root's persona unknown (a restart): none
263
+ const key = ref.parent.sessionKey;
264
+ this.armed.push({ key, child: { sessionKey: ref.sessionKey, runId: ref.runId }, persona, expires: Date.now() + RETURN_MS,
265
+ grant: { ref: { sessionKey: key, member: back.member, ...(back.helper !== undefined ? { helper: back.helper } : {}),
266
+ agentId: agentOf(key), parent: { sessionKey: ref.sessionKey, runId: ref.runId }, root: ref.root, tools: back.tools },
267
+ tools: back.tools, delegates: child.delegates, ...(back.helpers ? { helpers: back.helpers } : {}), sourceKey: child.sourceKey } });
268
+ }
187
269
  bound(member, helper, agent) {
188
270
  try {
189
271
  return this.o.helper(member, helper).agentId === agent;
@@ -194,6 +276,12 @@ export class Delegations {
194
276
  }
195
277
  event(event) {
196
278
  this.load();
279
+ const back = this.returns.get(event.runId);
280
+ if (back) {
281
+ if (event.sessionKey === back.ref.sessionKey && event.stream === 'lifecycle' && ['end', 'error'].includes(event.data?.phase))
282
+ this.returned(event.runId);
283
+ return;
284
+ }
197
285
  const grant = this.children.get(event.runId);
198
286
  if (!grant) {
199
287
  if (this.pending.size && typeof event.runId === 'string' && this.early.size < 64) {
@@ -232,6 +320,8 @@ export class Delegations {
232
320
  }
233
321
  end(grant, end) {
234
322
  this.children.delete(grant.ref.runId);
323
+ if (end.reason === 'terminal')
324
+ this.arm(grant); // before save: a return run waiting on this end sees it armed
235
325
  this.save();
236
326
  this.o.revoke(grant.ref.sessionKey, grant.ref.runId);
237
327
  this.o.emit({ type: 'ended', run: structuredClone(grant.ref), ...end });
@@ -260,7 +350,7 @@ export class Delegations {
260
350
  async abort(key) {
261
351
  this.load();
262
352
  const ids = new Set();
263
- for (const g of [...this.roots.values(), ...this.children.values()])
353
+ for (const g of [...this.roots.values(), ...this.children.values(), ...this.returns.values()])
264
354
  if (g.ref.sessionKey === key || g.ref.parent?.sessionKey === key || g.ref.root.sessionKey === key || g.sourceKey === key)
265
355
  ids.add(g.ref.runId);
266
356
  for (;;) {
@@ -272,6 +362,10 @@ export class Delegations {
272
362
  break;
273
363
  }
274
364
  let removed = false;
365
+ const armed = this.armed.length;
366
+ this.armed = this.armed.filter(a => a.key !== key && a.grant.ref.root.sessionKey !== key && !ids.has(a.child.runId) && !ids.has(a.grant.ref.root.runId));
367
+ if (this.armed.length !== armed)
368
+ removed = true;
275
369
  for (const [id, p] of this.pending)
276
370
  if (ids.has(p.parent.ref.runId) || p.parent.ref.sessionKey === key || p.parent.ref.root.sessionKey === key || p.parent.sourceKey === key) {
277
371
  this.pending.delete(id);
@@ -280,9 +374,11 @@ export class Delegations {
280
374
  if (!ids.size && !removed)
281
375
  return;
282
376
  const cancelled = [...this.children.values()].filter(g => ids.has(g.ref.runId));
283
- const revoked = [...this.roots.values(), ...cancelled].filter(g => ids.has(g.ref.runId));
284
- for (const id of ids)
377
+ const revoked = [...this.roots.values(), ...this.returns.values(), ...cancelled].filter(g => ids.has(g.ref.runId));
378
+ for (const id of ids) {
285
379
  this.roots.delete(id);
380
+ this.returns.delete(id);
381
+ }
286
382
  for (const g of cancelled)
287
383
  this.children.delete(g.ref.runId);
288
384
  this.save(); // revoke durably before any awaited transport call
package/dist/keys.js CHANGED
@@ -5,7 +5,7 @@ import { activate } from './activation.js';
5
5
  import { keyAgentId, MEMBER_ID, KEY_PREFIX, untilListed } from './members.js';
6
6
  import { providerOf } from './pick.js';
7
7
  import { kitId, modelsOf } from './accounts.js';
8
- import { LIVE_CATALOGS, REGISTERED_CATALOGS } from './constants.js';
8
+ import { LIVE_CATALOGS, OPEN_CATALOGS, REGISTERED_CATALOGS } from './constants.js';
9
9
  export function createKeys(ctx) {
10
10
  // Serialize key replacement and key runs, so an in-flight run never sees a half-installed replacement.
11
11
  const tails = new Map();
@@ -93,6 +93,16 @@ export function createKeys(ctx) {
93
93
  throw new Error(keyed.length ? `${authChoice} takes no API key: add the key with ${keyed.map((r) => r.choice).join(' or ')}.`
94
94
  : `${authChoice} takes no API key.`);
95
95
  };
96
+ // The vendor's list can lag a model that runs: an id it lacks rides the route's own models (the richest row's api,
97
+ // base URL and context, every input modality any row takes), unpriced. Nothing is added to an empty list.
98
+ const unlisted = (provider, rows, model) => {
99
+ if (!rows.length || rows.some((m) => kitId(provider, String(m.id)) === model))
100
+ return [];
101
+ const base = rows.reduce((a, b) => (Number(b.contextWindow) || 0) > (Number(a.contextWindow) || 0) ? b : a);
102
+ const input = ['text', 'image'].filter((i) => rows.some((m) => Array.isArray(m.input) && m.input.includes(i)));
103
+ const id = model.slice(provider.length + 1);
104
+ return [{ ...base, id, name: id, input, cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 } }];
105
+ };
96
106
  const listed = (provider, rows) => modelsOf(provider, { models: rows.map((m) => ({ ...m, provider })) });
97
107
  const models = async (member, o) => {
98
108
  if (!check(member))
@@ -217,6 +227,8 @@ export function createKeys(ctx) {
217
227
  if (result.late)
218
228
  return abandon(failure('retry', 'busy'));
219
229
  const rows = model === undefined ? [] : await catalog(member, route.provider);
230
+ const lagging = model !== undefined && OPEN_CATALOGS.includes(route.provider) ? unlisted(route.provider, rows, model) : [];
231
+ rows.push(...lagging);
220
232
  if (model !== undefined && !listed(route.provider, rows).some((m) => m.id === model && m.available))
221
233
  return abandon(failure('not_included', 'model'));
222
234
  // Prepare before publishing the ready marker: failed preparation remains unusable even if clear fails.
@@ -229,7 +241,7 @@ export function createKeys(ctx) {
229
241
  if (ac.signal.aborted)
230
242
  return abandon(cancelled());
231
243
  // A live catalog the kit does not register whole (OpenRouter) registers the chosen model alone.
232
- if (model !== undefined && !REGISTERED_CATALOGS.includes(route.provider))
244
+ if (model !== undefined && (!REGISTERED_CATALOGS.includes(route.provider) || lagging.length))
233
245
  await ctx.registerModels(route.provider, rows.filter((m) => kitId(route.provider, String(m.id)) === model)).catch(() => { });
234
246
  if (model !== undefined && LIVE_CATALOGS.includes(route.provider) && !(await registered(route.provider, model)))
235
247
  return abandon(failure('retry', 'model'));
package/dist/runs.js CHANGED
@@ -347,7 +347,7 @@ export function createRuns(ctx) {
347
347
  try {
348
348
  const ref = { sessionKey, member: spec.member, ...(spec.helper !== undefined ? { helper: spec.helper } : {}) };
349
349
  releaseDelegation = spec.delegates || spec.helperDelegates
350
- ? ctx.delegations.register(ref, idempotencyKey, spec.tools, spec.delegates ?? {}, spec.sessionKey, spec.helperDelegates) : undefined;
350
+ ? ctx.delegations.register(ref, idempotencyKey, spec.tools, spec.delegates ?? {}, spec.sessionKey, spec.helperDelegates, system) : undefined;
351
351
  release = spec.register !== false ? ctx.bridge.register(ref, spec.tools, idempotencyKey) : undefined;
352
352
  if (picked && !chosen) {
353
353
  // The picked account must be the one called: check it is signed in for this member before the run, since
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@byokit/openclaw",
3
- "version": "0.24.0",
3
+ "version": "0.25.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",
@@ -60,7 +60,7 @@
60
60
  "prepack": "tsc -b && node ../../scripts/fix-words-dts.cjs"
61
61
  },
62
62
  "dependencies": {
63
- "@byokit/accounts": "0.28.2",
63
+ "@byokit/accounts": "0.29.0",
64
64
  "@byokit/pair": "0.10.4",
65
65
  "@byokit/relay": "0.8.2",
66
66
  "@byokit/discover": "0.8.4",
package/plugin/catalog.js CHANGED
@@ -38,6 +38,19 @@ export function registerCatalog(api) {
38
38
  const models = [...live.models, ...(pinned?.models ?? []).filter((m) => !liveIds.has(m.id))]
39
39
  .map(({ provider: _p, apiKey: _k, headers: _h, ...model }) =>
40
40
  (endpoint.has(model.api) ? { ...model, baseUrl: endpoint.get(model.api) } : model));
41
+ // MiniMax's engine catalog is a fixed list: its vendor `/models` lists newer ids, which join it with the richest
42
+ // row's api, base URL and capabilities (the vendor lists ids only). A failed fetch keeps the engine's list.
43
+ if (provider === 'minimax' && models.length) {
44
+ const base = models.reduce((a, b) => (b.contextWindow ?? 0) > (a.contextWindow ?? 0) ? b : a);
45
+ const input = ['text', 'image'].filter((i) => models.some((m) => m.input?.includes(i)));
46
+ const listed = await fetch(`${String(base.baseUrl).replace(/\/$/, '')}/v1/models`,
47
+ { headers: { authorization: `Bearer ${apiKey}`, 'anthropic-version': '2023-06-01' }, signal: AbortSignal.timeout(10_000) })
48
+ .then((r) => r.ok ? r.json() : null, () => null);
49
+ const have = new Set(models.map((m) => m.id));
50
+ for (const m of Array.isArray(listed?.data) ? listed.data : [])
51
+ if (typeof m?.id === 'string' && m.id && !have.has(m.id))
52
+ models.push({ ...base, id: m.id, name: m.display_name || m.id, input, cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 } });
53
+ }
41
54
  return respond(true, { ok: true, models });
42
55
  }
43
56
  respond(true, { ok: false, reason: unreadable ? 'failed' : 'no-key' });
package/plugin/index.js CHANGED
@@ -160,12 +160,16 @@ export default {
160
160
  result: { details: d && { status: d.status, runId: d.runId, sessionKey: d.sessionKey, childSessionKey: d.childSessionKey } } });
161
161
  });
162
162
  // A native send carries no system text: an admitted helper child gets its helper's persona from the kit (5.8.3).
163
- // Any other run, or no answer, adds nothing; the persona is the helper's voice, never a grant.
164
- api.on('before_prompt_build', async (_event, ctx) => {
163
+ // The engine's reply step bringing a sent child's report back (an inter-session prompt) asks too: the kit binds that
164
+ // exact run as the sender's return run (5.8.4) and answers the sender's persona. Any other run, or no answer, adds
165
+ // nothing; the persona is a voice, never a grant.
166
+ api.on('before_prompt_build', async (event, ctx) => {
165
167
  const key = ctx?.sessionKey;
166
- if (typeof key !== 'string' || !/^agent:[^:]+:h-[a-z][a-z0-9-]{0,15}:/.test(key)) return undefined;
168
+ const header = typeof event?.prompt === 'string' && event.prompt.startsWith('[Inter-session message] ')
169
+ ? event.prompt.slice(0, 1024).split('\n', 1)[0] : undefined;
170
+ if (typeof key !== 'string' || (!header && !/^agent:[^:]+:h-[a-z][a-z0-9-]{0,15}:/.test(key))) return undefined;
167
171
  try {
168
- const reply = await bridgeRequest({ kind: 'delegation-persona', key, runId: ctx?.runId }, { timeoutMs: 10_000 });
172
+ const reply = await bridgeRequest({ kind: 'delegation-persona', key, runId: ctx?.runId, ...(header ? { header } : {}) }, { timeoutMs: 10_000 });
169
173
  if (typeof reply?.persona === 'string' && reply.persona) return { appendSystemContext: reply.persona };
170
174
  } catch { /* no kit: nothing added */ }
171
175
  return undefined;