@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 +7 -0
- package/README.md +7 -0
- package/dist/bridge.js +4 -2
- package/dist/constants.d.ts +1 -0
- package/dist/constants.js +5 -2
- package/dist/delegations.d.ts +17 -3
- package/dist/delegations.js +107 -11
- package/dist/keys.js +14 -2
- package/dist/runs.js +1 -1
- package/package.json +2 -2
- package/plugin/catalog.js +13 -0
- package/plugin/index.js +8 -4
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
|
|
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
|
|
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
|
package/dist/constants.d.ts
CHANGED
|
@@ -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',
|
package/dist/delegations.d.ts
CHANGED
|
@@ -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[]
|
|
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
|
-
|
|
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;
|
package/dist/delegations.js
CHANGED
|
@@ -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
|
-
|
|
115
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
-
//
|
|
164
|
-
|
|
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
|
-
|
|
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;
|