@byokit/openclaw 0.22.2 → 0.24.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 +10 -0
- package/README.md +14 -7
- package/dist/bridge.js +15 -8
- package/dist/config.js +3 -2
- package/dist/delegations.d.ts +5 -1
- package/dist/delegations.js +33 -7
- package/dist/device.d.ts +1 -1
- package/dist/engine.d.ts +5 -4
- package/dist/engine.js +10 -3
- package/dist/index.d.ts +1 -1
- package/dist/kit.js +2 -1
- package/dist/runs.d.ts +2 -1
- package/dist/runs.js +9 -6
- package/dist/signin.d.ts +2 -2
- package/dist/signin.js +3 -2
- package/dist/testing/fake-gateway.js +16 -6
- package/dist/types.d.ts +17 -3
- package/package.json +1 -1
- package/plugin/index.js +17 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.24.0 (2026-10-20)
|
|
6
|
+
|
|
7
|
+
- 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.
|
|
8
|
+
- FEAT: a tracked `sessions_spawn` into the run's own agent (`delegates` naming it, e.g. Chief spawning under its own signed-in agent) reaches the engine as its own-agent spawn, so an app `subagents.allowAgents` list that names only other agents no longer refuses it.
|
|
9
|
+
- FIX: a delegated child the engine no longer knows (as after an engine restart, when its end never reached the kit) now ends `lost` (new `DelegatedEvent.reason`, outcome unknown) at reconnect instead of staying admitted forever, and a dispatch that finds the 1024-entry journal full reconciles with the engine first, so those children can no longer block delegation. One unreadable `agent.wait` no longer fails the whole reconnect.
|
|
10
|
+
|
|
11
|
+
## 0.23.0 (2026-10-20)
|
|
12
|
+
|
|
13
|
+
- FEATURE: A signed-out `claude-cli` run now says why: its end carries `cause: { code: 'needs_cli_login', cli: 'claude', home, configDir?, signIn }` (the new `CliLoginCause` type, also on `./device`). `signIn: true` means `kit.signIn(member, { authChoice: 'anthropic-cli' })` runs the Claude Code login into the kit's own folder (subscription billing); `signIn: false` means the engine reads the person's own Claude Code login, so they run `claude` on that machine. An `anthropic-cli` sign-in the kit cannot run carries the same `cause` on its failed view. `kind`, `message` and every other run end are unchanged.
|
|
14
|
+
|
|
5
15
|
## 0.22.2 (2026-10-19)
|
|
6
16
|
|
|
7
17
|
- Dependency update: pins @byokit/accounts 0.28.2.
|
package/README.md
CHANGED
|
@@ -131,7 +131,9 @@ API key (billed per use) entry still requires explicit selection; there is no su
|
|
|
131
131
|
To choose the model for a run, pass `model: 'provider/model'` in the run spec (for example
|
|
132
132
|
`'openai/gpt-5.1'`, where `provider` is an id `kit.providers(member)` lists). That provider is the one called and billed for this run only, with no fallback to another
|
|
133
133
|
provider or model. If the member isn't signed in to it, the run ends `{ ok: false, kind: 'signed-out' }` and the
|
|
134
|
-
engine is never called.
|
|
134
|
+
engine is never called. A signed-out `claude-cli` run also carries `cause: { code: 'needs_cli_login', cli: 'claude', home, configDir?, signIn }`:
|
|
135
|
+
with `signIn: true` offer `kit.signIn(member, { authChoice: 'anthropic-cli' })`, which runs the Claude Code login; with
|
|
136
|
+
`signIn: false` ask the person to run `claude` on this machine to sign in. Leave `model` out to keep the engine's own choice. A specific sign-in (`@profile`) can't be
|
|
135
137
|
picked per run: the pinned engine may still switch to another sign-in for the same provider.
|
|
136
138
|
|
|
137
139
|
To let the kit choose among a member's accounts, pass `account: 'auto'` (the account with the most room left, decided
|
|
@@ -441,14 +443,17 @@ Pass `delegates: { scout: ['report'] }` on `kit.run` to permit native delegation
|
|
|
441
443
|
that app-tool subset. Ensure the target member first and configure the engine's own A2A/visibility/subagent policy;
|
|
442
444
|
this option does not enable engine tools or bypass `host.gate`. The parent subset narrows descendants too.
|
|
443
445
|
Tracked `sessions_send` requires an explicit full `sessionKey` and `timeoutSeconds: 0`; tracked `sessions_spawn`
|
|
444
|
-
requires an explicit allowed `agentId` and native runtime (not ACP).
|
|
446
|
+
requires an explicit allowed `agentId` and native runtime (not ACP). A tracked spawn into the run's own agent
|
|
447
|
+
(`delegates: { chief: [...] }` on a `chief` run) goes to the engine as its own-agent spawn, so an app
|
|
448
|
+
`subagents.allowAgents` list for other agents does not refuse it. Unknown children fail closed.
|
|
445
449
|
|
|
446
450
|
`kit.delegatedRuns()` lists admitted children. `kit.onEvent('byokit.delegated', fn)` reports admission, native streams
|
|
447
451
|
and terminal observations with exact `runId`, `agentId`, `member`, `sessionKey`, `parent` and `root` identity.
|
|
448
452
|
The same identity reaches `ToolHost`; each delegated action has its own gate/approval. Tool arguments/results
|
|
449
453
|
are omitted from this event surface. An accepted native dispatch is not finished work or proof of delivery.
|
|
450
454
|
|
|
451
|
-
Admitted exact run IDs survive parent settlement and are reconciled on reconnect/start
|
|
455
|
+
Admitted exact run IDs survive parent settlement and are reconciled on reconnect/start; a child the engine no longer
|
|
456
|
+
knows (as after an engine restart) ends `lost`, and a dispatch that finds the journal full reconciles first. `abort(rootSessionKey)`
|
|
452
457
|
durably revokes its recorded subtree, closes parked gates/active calls, then asks the engine to abort; a child key
|
|
453
458
|
revokes its subtree. An engine resume with a different run ID, or a dispatch with no retained receipt, is not
|
|
454
459
|
silently granted authority. No prefix grants or `allowOnce` bypass are used.
|
|
@@ -494,8 +499,9 @@ above, builtin gating on): the engine's own `sessions_send` to the exact key `ag
|
|
|
494
499
|
(`timeoutSeconds: 0`) is admitted from its receipt as writer's run, `RunRef.helper: 'writer'` on every gate and call.
|
|
495
500
|
A target that is not a helper key, a helper on another account, one never ensured, or one the run was not given is
|
|
496
501
|
refused before dispatch; the parent's tool result reads `session: …`, `bound: …` or `unknown_helper: …`.
|
|
497
|
-
`abort(rootSessionKey)` revokes the children, and a helper whose account is removed loses them at their next call.
|
|
498
|
-
|
|
502
|
+
`abort(rootSessionKey)` revokes the children, and a helper whose account is removed loses them at their next call. A
|
|
503
|
+
native send carries no system text, so the kit adds the helper's recorded persona as system context to the admitted
|
|
504
|
+
child's run; a `sessions_spawn` child gets none.
|
|
499
505
|
|
|
500
506
|
Conversations from before helpers (plain member sessions) move into a helper with
|
|
501
507
|
`kit.adoptHelperSession({ member, sessionKey }, helper)`: the whole transcript forks to
|
|
@@ -508,8 +514,9 @@ both. A helper key, another member's key or a helper never ensured is a typed `H
|
|
|
508
514
|
|
|
509
515
|
`kit.onEvent('byokit.delegated', fn)` follows each helper child start to finish: `admitted` (with `run.helper` and
|
|
510
516
|
`run.tools`, its app-tool subset), the engine's events, then one `ended` whose `reason` is `terminal`, `cancelled` (an
|
|
511
|
-
abort, or its admission ended), `error` (with `message`)
|
|
512
|
-
|
|
517
|
+
abort, or its admission ended), `error` (with `message`), `lost` (the engine no longer knows the run: outcome unknown)
|
|
518
|
+
or `resting`: the model's usage limit ended it, and the kit rests the helper's account `until` the stated reset, as a
|
|
519
|
+
kit run's resting end does. An approval raised by a helper's
|
|
513
520
|
run names it in `Approval.helper`.
|
|
514
521
|
|
|
515
522
|
## Owned browser (unqualified handoff)
|
package/dist/bridge.js
CHANGED
|
@@ -316,28 +316,35 @@ export class Bridge {
|
|
|
316
316
|
return;
|
|
317
317
|
}
|
|
318
318
|
if (message.kind.startsWith('delegation-') && this.delegations) {
|
|
319
|
-
|
|
319
|
+
const delegations = this.delegations;
|
|
320
|
+
void (async () => {
|
|
320
321
|
if (message.kind === 'delegation-prepare') {
|
|
321
322
|
if (typeof message.key !== 'string' || typeof message.runId !== 'string'
|
|
322
|
-
|| (!this.isRegisteredRun(message.key, message.runId) && !
|
|
323
|
+
|| (!this.isRegisteredRun(message.key, message.runId) && !delegations.has(message.key, message.runId)))
|
|
323
324
|
return this.deny(socket, 'unknown delegation parent');
|
|
324
|
-
this.reply(socket, { tracked:
|
|
325
|
+
this.reply(socket, { tracked: await delegations.prepare(message) });
|
|
325
326
|
}
|
|
326
327
|
else if (message.kind === 'delegation-receipt') {
|
|
327
|
-
|
|
328
|
+
delegations.receipt(message);
|
|
328
329
|
this.reply(socket, { ok: true });
|
|
329
330
|
}
|
|
330
331
|
else if (message.kind === 'delegation-event' && isRecord(message.event)) {
|
|
331
|
-
|
|
332
|
+
delegations.event(message.event);
|
|
332
333
|
this.reply(socket, { ok: true });
|
|
333
334
|
}
|
|
335
|
+
// The plugin's prompt hook on a helper key: an admitted helper child speaks as its helper (5.8.3).
|
|
336
|
+
else if (message.kind === 'delegation-persona' && typeof message.key === 'string') {
|
|
337
|
+
const runId = typeof message.runId === 'string' ? message.runId : undefined;
|
|
338
|
+
// 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
|
+
this.reply(socket, persona === undefined ? {} : { persona });
|
|
341
|
+
}
|
|
334
342
|
else
|
|
335
343
|
this.deny(socket, 'invalid delegation message');
|
|
336
|
-
}
|
|
337
|
-
catch (error) {
|
|
344
|
+
})().catch((error) => {
|
|
338
345
|
// A typed helper refusal (5.8.3) reaches the parent's tool result with its code.
|
|
339
346
|
this.deny(socket, error instanceof HelperError ? `${error.code}: ${error.message}` : error instanceof Error ? error.message : 'delegation unavailable');
|
|
340
|
-
}
|
|
347
|
+
});
|
|
341
348
|
return;
|
|
342
349
|
}
|
|
343
350
|
if (message.kind === 'gate')
|
package/dist/config.js
CHANGED
|
@@ -100,7 +100,9 @@ export function reconcileConfig(saved, o) {
|
|
|
100
100
|
...o.plugins ?? [],
|
|
101
101
|
...telegram ? ['telegram'] : []])];
|
|
102
102
|
c.plugins.entries ??= {};
|
|
103
|
-
|
|
103
|
+
// Conversation access lets the plugin's prompt hook give an admitted helper child its persona (5.8.3).
|
|
104
|
+
c.plugins.entries[o.pluginId] = merge(c.plugins.entries[o.pluginId] ?? {}, { hooks: { timeouts: { before_tool_call: 200_000 },
|
|
105
|
+
allowConversationAccess: true } });
|
|
104
106
|
if (o.browser) {
|
|
105
107
|
// Replace, never merge caller-controlled profiles/targets. The dead default fails closed without our hook.
|
|
106
108
|
c.browser = { enabled: true, defaultProfile: 'byokit-none', evaluateEnabled: false,
|
|
@@ -108,7 +110,6 @@ export function reconcileConfig(saved, o) {
|
|
|
108
110
|
profiles: { 'byokit-none': { cdpUrl: 'http://127.0.0.1:1', attachOnly: true }, ...o.browser.profiles } };
|
|
109
111
|
c.plugins.allow = [...new Set([...c.plugins.allow, 'browser'])];
|
|
110
112
|
c.plugins.entries.browser = { enabled: true };
|
|
111
|
-
c.plugins.entries[o.pluginId].hooks.allowConversationAccess = true;
|
|
112
113
|
c.tools ??= {};
|
|
113
114
|
const browserTools = [...new Set([...(c.tools.alsoAllow ?? []), 'browser', 'request_sign_in', ...o.browser.tools])];
|
|
114
115
|
// The stock schema rejects allow + alsoAllow in one scope; an explicit closed allow must absorb additions.
|
package/dist/delegations.d.ts
CHANGED
|
@@ -20,6 +20,7 @@ export declare class Delegations {
|
|
|
20
20
|
helper(member: string, helper: string): {
|
|
21
21
|
agentId: string;
|
|
22
22
|
account: string;
|
|
23
|
+
persona: string;
|
|
23
24
|
};
|
|
24
25
|
rest(member: string, account: string, until: number): Promise<void>;
|
|
25
26
|
request: GatewayTransport['request'];
|
|
@@ -33,9 +34,12 @@ export declare class Delegations {
|
|
|
33
34
|
register(ref: RunRef, runId: string, tools: readonly string[] | undefined, delegates: Record<string, string[]>, sourceKey?: string, helpers?: Record<string, string[]>): () => void;
|
|
34
35
|
list(): DelegatedRun[];
|
|
35
36
|
has(key: string, runId: string): boolean;
|
|
36
|
-
|
|
37
|
+
private full;
|
|
38
|
+
prepare(m: Record<string, any>): Promise<boolean>;
|
|
37
39
|
receipt(m: Record<string, any>): void;
|
|
38
40
|
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>;
|
|
39
43
|
private bound;
|
|
40
44
|
event(event: Record<string, any>): void;
|
|
41
45
|
private ended;
|
package/dist/delegations.js
CHANGED
|
@@ -70,8 +70,19 @@ export class Delegations {
|
|
|
70
70
|
this.load();
|
|
71
71
|
return (this.roots.get(runId) ?? this.children.get(runId))?.ref.sessionKey === key;
|
|
72
72
|
}
|
|
73
|
-
|
|
73
|
+
full() {
|
|
74
|
+
for (const [id, p] of this.pending)
|
|
75
|
+
if (p.expires < Date.now())
|
|
76
|
+
this.pending.delete(id);
|
|
77
|
+
return this.pending.size + this.children.size >= 1024;
|
|
78
|
+
}
|
|
79
|
+
async prepare(m) {
|
|
74
80
|
this.load();
|
|
81
|
+
// Children whose end never reached the kit would fill the journal for good: settle them with the engine first,
|
|
82
|
+
// before the parent is read, so an abort meanwhile is seen.
|
|
83
|
+
// ponytail: a full journal of live children re-reads all 1024 per dispatch; index ends by age if that shows up.
|
|
84
|
+
if (this.full())
|
|
85
|
+
await this.reconcile();
|
|
75
86
|
const parent = this.roots.get(m.runId) ?? this.children.get(m.runId);
|
|
76
87
|
if (!parent || parent.ref.sessionKey !== m.key)
|
|
77
88
|
return false;
|
|
@@ -98,10 +109,7 @@ export class Delegations {
|
|
|
98
109
|
throw new HelperError('session', `refused: "${String(m.input.sessionKey ?? agent)}" is not a helper session`);
|
|
99
110
|
throw new Error('delegation target or mode refused');
|
|
100
111
|
}
|
|
101
|
-
|
|
102
|
-
if (p.expires < Date.now())
|
|
103
|
-
this.pending.delete(id);
|
|
104
|
-
if (this.pending.size + this.children.size >= 1024)
|
|
112
|
+
if (this.full())
|
|
105
113
|
throw new Error('delegated run journal is full');
|
|
106
114
|
this.pending.set(actionKey(m), { parent: structuredClone(parent), agent: agent,
|
|
107
115
|
...(send ? { key: m.input.sessionKey } : {}), ...(helper !== undefined ? { helper } : {}), expires: Date.now() + 60_000 });
|
|
@@ -171,6 +179,11 @@ export class Delegations {
|
|
|
171
179
|
}
|
|
172
180
|
return grant;
|
|
173
181
|
}
|
|
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) {
|
|
184
|
+
const helper = (await this.resolve(key, runId))?.ref;
|
|
185
|
+
return helper?.helper === undefined ? undefined : this.o.helper(helper.member, helper.helper).persona;
|
|
186
|
+
}
|
|
174
187
|
bound(member, helper, agent) {
|
|
175
188
|
try {
|
|
176
189
|
return this.o.helper(member, helper).agentId === agent;
|
|
@@ -223,12 +236,25 @@ export class Delegations {
|
|
|
223
236
|
this.o.revoke(grant.ref.sessionKey, grant.ref.runId);
|
|
224
237
|
this.o.emit({ type: 'ended', run: structuredClone(grant.ref), ...end });
|
|
225
238
|
}
|
|
239
|
+
// An exact recorded run the engine finished ends as it ended. One the engine no longer knows (no job, no queued turn,
|
|
240
|
+
// no live run, as after an engine restart: a plain `timeout` in the `queue` phase that never reached a provider) ends
|
|
241
|
+
// `lost`. A live or queued run stays admitted; a run whose wait fails stays for the next reconcile.
|
|
226
242
|
async reconcile() {
|
|
227
243
|
for (const ref of this.list()) {
|
|
228
|
-
|
|
244
|
+
let end;
|
|
245
|
+
try {
|
|
246
|
+
end = await this.o.request('agent.wait', { runId: ref.runId, timeoutMs: 1 });
|
|
247
|
+
}
|
|
248
|
+
catch {
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
229
251
|
const grant = this.children.get(ref.runId);
|
|
230
|
-
if (grant
|
|
252
|
+
if (!grant)
|
|
253
|
+
continue;
|
|
254
|
+
if (end.status === 'ok' || end.status === 'error')
|
|
231
255
|
this.end(grant, { reason: end.status === 'ok' ? 'terminal' : 'error' });
|
|
256
|
+
else if (end.status === 'timeout' && end.timeoutPhase === 'queue' && end.providerStarted === false)
|
|
257
|
+
this.end(grant, { reason: 'lost' });
|
|
232
258
|
}
|
|
233
259
|
}
|
|
234
260
|
async abort(key) {
|
package/dist/device.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ import type { RouteView } from './routes.ts';
|
|
|
4
4
|
import { type AccountView } from './words.ts';
|
|
5
5
|
import type { Approval, Decision, GatewayEventName, GatewayEventPayload, GatewayMethod, GatewayParams, GatewayResult, KitState, RunEnd, OutputSchema, SchemaOutput, RunEvent, RunFrame, SignInView, AccountRef } from './types.ts';
|
|
6
6
|
export type { GatewayEventName, GatewayEventPayload, GatewayMethod, GatewayParams, GatewayResult } from './types.ts';
|
|
7
|
-
export type { Approval, Decision, KitState, PlanWindow, Route, RunEnd, RunEvent, RunFrame, RunUsage, SignInView, OutputSchema, SchemaOutput } from './types.ts';
|
|
7
|
+
export type { Approval, CliLoginCause, Decision, KitState, PlanWindow, Route, RunEnd, RunEvent, RunFrame, RunUsage, SignInView, OutputSchema, SchemaOutput } from './types.ts';
|
|
8
8
|
/** One `oc.events` frame: a member's Gateway event, typed by name, or an approval add/resolve (7.2). */
|
|
9
9
|
export type OpenClawLinkEvent = {
|
|
10
10
|
[E in GatewayEventName]: {
|
package/dist/engine.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { remoteCliHost } from '@byokit/accounts/cli';
|
|
2
2
|
import { AuthStore, AuthStoreSealSizeError } from './auth-store.ts';
|
|
3
3
|
import type { KitOptions } from './kit.ts';
|
|
4
|
-
import type { KitState, ToolSpec } from './types.ts';
|
|
4
|
+
import type { CliLoginCause, KitState, ToolSpec } from './types.ts';
|
|
5
5
|
/** The app's stateDir as an absolute path with no symlinked ancestor (macOS /tmp and /var are symlinks):
|
|
6
6
|
* the native Claude login's accounts store refuses any other, so a relative or symlinked stateDir failed it. */
|
|
7
7
|
export declare function canonicalStateDir(dir: string): string;
|
|
@@ -83,9 +83,10 @@ export declare class Engine {
|
|
|
83
83
|
refreshInstalledClaudeLogin(): Promise<boolean>;
|
|
84
84
|
/** The fallback Claude sign-in for a machine whose installed Claude Code is signed out: it signs into the engine's folder. */
|
|
85
85
|
nativeClaudeLogin(): ReturnType<typeof remoteCliHost>;
|
|
86
|
-
/**
|
|
87
|
-
*
|
|
88
|
-
|
|
86
|
+
/** Where a Claude Code login must be for this engine's gateway to read it. `signIn: false` when a fallback sign-in
|
|
87
|
+
* would write a folder the gateway does not read: it started on the person's login location (installed sign-in, or
|
|
88
|
+
* a probe with no answer), and an in-process restart keeps that env. */
|
|
89
|
+
claudeLoginCause(): CliLoginCause;
|
|
89
90
|
resetNativeClaudeLogin(): Promise<void>;
|
|
90
91
|
doctor(timeoutMs: number): {
|
|
91
92
|
status: number | null;
|
package/dist/engine.js
CHANGED
|
@@ -528,9 +528,16 @@ export class Engine {
|
|
|
528
528
|
this.claudeLogin = remoteCliHost({ cli, bins });
|
|
529
529
|
return this.claudeLogin;
|
|
530
530
|
}
|
|
531
|
-
/**
|
|
532
|
-
*
|
|
533
|
-
|
|
531
|
+
/** Where a Claude Code login must be for this engine's gateway to read it. `signIn: false` when a fallback sign-in
|
|
532
|
+
* would write a folder the gateway does not read: it started on the person's login location (installed sign-in, or
|
|
533
|
+
* a probe with no answer), and an in-process restart keeps that env. */
|
|
534
|
+
claudeLoginCause() {
|
|
535
|
+
if (this.o.spawnEngine && !this.claudeFallback) {
|
|
536
|
+
const { HOME, CLAUDE_CONFIG_DIR } = personal();
|
|
537
|
+
return { code: 'needs_cli_login', cli: 'claude', home: HOME, ...CLAUDE_CONFIG_DIR ? { configDir: CLAUDE_CONFIG_DIR } : {}, signIn: false };
|
|
538
|
+
}
|
|
539
|
+
return { code: 'needs_cli_login', cli: 'claude', home: join(this.root, 'home'), configDir: this.claudeFolder, signIn: true };
|
|
540
|
+
}
|
|
534
541
|
resetNativeClaudeLogin() {
|
|
535
542
|
if (this.claudeReset)
|
|
536
543
|
return this.claudeReset;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { ENGINE_VERSION, OPERATOR_SCOPES, PROTOCOL_VERSION } from './constants.ts';
|
|
2
|
-
export type { Account, AccountId, AccountPick, AccountRef, Approval, CallOptions, ChannelChat, ChannelMessage, Considered, Decision, Defaults, ModelInfo, MoveResult, PickWhy, Room, RoomSpan, RunSelection, SignInState, GatewayEventName, GatewayEventPayload, GatewayMethod, GatewayParams, GatewayResult, GatewayTransport, GateResult, Hello, KitState, KitEventName, KitEventPayload, BrowserPing, LearningCapture, LearningMode, Member, PlanWindow, Route, RunEnd, RunEvent, RunFrame, RunRef, Helper, DelegatedRun, DelegatedEvent, RunSpec, OutputSchema, SchemaOutput, RunUsage, ThinkingAsked, ThinkingReport, ThinkingUsed, SignInOptions, SignInView, TelegramChannel, ToolHost, ToolSpec, } from './types.ts';
|
|
2
|
+
export type { Account, AccountId, AccountPick, AccountRef, Approval, CallOptions, CliLoginCause, ChannelChat, ChannelMessage, Considered, Decision, Defaults, ModelInfo, MoveResult, PickWhy, Room, RoomSpan, RunSelection, SignInState, GatewayEventName, GatewayEventPayload, GatewayMethod, GatewayParams, GatewayResult, GatewayTransport, GateResult, Hello, KitState, KitEventName, KitEventPayload, BrowserPing, LearningCapture, LearningMode, Member, PlanWindow, Route, RunEnd, RunEvent, RunFrame, RunRef, Helper, DelegatedRun, DelegatedEvent, RunSpec, OutputSchema, SchemaOutput, RunUsage, ThinkingAsked, ThinkingReport, ThinkingUsed, SignInOptions, SignInView, TelegramChannel, ToolHost, ToolSpec, } from './types.ts';
|
|
3
3
|
export type { GatewayMethods } from './generated/methods.ts';
|
|
4
4
|
export type { JsonValue } from './output.ts';
|
|
5
5
|
export type { RouteFacts, RouteView } from './routes.ts';
|
package/dist/kit.js
CHANGED
|
@@ -1120,7 +1120,7 @@ export class OpenClawKit {
|
|
|
1120
1120
|
ensure: (member) => this.ensureMember(member), callbackPort: this.o.callbackPort ?? 1455,
|
|
1121
1121
|
nativeClaudeLogin: () => this.engine.nativeClaudeLogin(),
|
|
1122
1122
|
resetNativeClaudeLogin: () => this.engine.resetNativeClaudeLogin(),
|
|
1123
|
-
|
|
1123
|
+
claudeLogin: () => this.engine.claudeLoginCause(),
|
|
1124
1124
|
onDisconnect: (fn) => { this.signInDisconnects.add(fn); return () => { this.signInDisconnects.delete(fn); }; },
|
|
1125
1125
|
stopSignal: this.engine.stopSignal,
|
|
1126
1126
|
authConfig: () => this.authSnapshot(),
|
|
@@ -1387,6 +1387,7 @@ export class OpenClawKit {
|
|
|
1387
1387
|
request: (method, params, o) => this.request()(method, params, o),
|
|
1388
1388
|
authStatus: this.admission.read,
|
|
1389
1389
|
invalidateAuth: this.admission.invalidate,
|
|
1390
|
+
claudeLogin: () => this.engine.claudeLoginCause(),
|
|
1390
1391
|
onEvent: (fn) => {
|
|
1391
1392
|
this.listeners.add(fn);
|
|
1392
1393
|
return () => {
|
package/dist/runs.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ import type { createAccounts } from './accounts.ts';
|
|
|
5
5
|
import type { createLocks } from './locks.ts';
|
|
6
6
|
import { authStatus } from './auth-status.ts';
|
|
7
7
|
import { outputSchema, type OutputSchema, type SchemaOutput } from './output.ts';
|
|
8
|
-
import type { GatewayTransport, Member, RunEnd, RunEvent, RunFrame, RunSpec } from './types.ts';
|
|
8
|
+
import type { CliLoginCause, GatewayTransport, Member, RunEnd, RunEvent, RunFrame, RunSpec } from './types.ts';
|
|
9
9
|
export declare const USABLE: Set<string>;
|
|
10
10
|
/**
|
|
11
11
|
* The providers a `models.authStatus` answer shows usable (the rule a picked account is checked by), lowercased and
|
|
@@ -35,6 +35,7 @@ export declare function createRuns(ctx: {
|
|
|
35
35
|
request: GatewayTransport['request'];
|
|
36
36
|
authStatus?: (agentId: string, refresh: boolean, native: boolean) => ReturnType<typeof authStatus>;
|
|
37
37
|
invalidateAuth?: () => void;
|
|
38
|
+
claudeLogin?: () => CliLoginCause;
|
|
38
39
|
onEvent: GatewayTransport['onEvent'];
|
|
39
40
|
ensure(member: Member): Promise<{
|
|
40
41
|
agentId: string;
|
package/dist/runs.js
CHANGED
|
@@ -154,11 +154,14 @@ export function createRuns(ctx) {
|
|
|
154
154
|
return undefined;
|
|
155
155
|
}
|
|
156
156
|
};
|
|
157
|
-
|
|
157
|
+
// A signed-out claude-cli run names where its Claude Code login is missing (`cause`).
|
|
158
|
+
const cause = (provider) => provider === 'claude-cli' && ctx.claudeLogin ? { cause: ctx.claudeLogin() } : {};
|
|
159
|
+
const signedOut = (provider, message) => ({ ok: false, kind: 'signed-out', message, ...cause(provider) });
|
|
160
|
+
const failure = (message, provider) => {
|
|
158
161
|
const classified = classify(message);
|
|
159
162
|
if (classified.kind === 'signed-out')
|
|
160
163
|
ctx.invalidateAuth?.();
|
|
161
|
-
return { ok: false, ...classified, message };
|
|
164
|
+
return { ok: false, ...classified, message, ...classified.kind === 'signed-out' ? cause(provider) : {} };
|
|
162
165
|
};
|
|
163
166
|
const execute = async (spec, on, keyAgent, preparedOutput) => {
|
|
164
167
|
// Member boundary first: a member never speaks in another member's session, refused before any request. A key
|
|
@@ -268,7 +271,7 @@ export function createRuns(ctx) {
|
|
|
268
271
|
try {
|
|
269
272
|
on?.({ type: 'account', account: pick.account.id, model: pick.model, sessionKey, how: pick.how, why: pick.why, considered: pick.considered });
|
|
270
273
|
if (!ready)
|
|
271
|
-
return
|
|
274
|
+
return signedOut(pick.account.provider, `${pick.account.name} is not signed in`);
|
|
272
275
|
return await start(spec, on, { agentId: agent, sessionKey, idempotencyKey, system,
|
|
273
276
|
picked: pick.model ? await accountOf(pick.model) : undefined, output, chosen: pick }); // '' = no override
|
|
274
277
|
}
|
|
@@ -359,7 +362,7 @@ export function createRuns(ctx) {
|
|
|
359
362
|
return { ok: false, kind: 'other', message: String(auth.unavailable.message ?? 'account status unavailable') };
|
|
360
363
|
const row = (auth.providers ?? []).find((e) => (typeof e === 'string' ? e : e?.provider) === picked.provider);
|
|
361
364
|
if (!row || !usable(row))
|
|
362
|
-
return
|
|
365
|
+
return signedOut(picked.provider, `${picked.provider} is not signed in for ${spec.member}`);
|
|
363
366
|
}
|
|
364
367
|
// One `agent` request: its interim `accepted` frame names the run, its final frame (after the run) carries the
|
|
365
368
|
// run's usage. The wait below still decides how the run ended. Thinking is clamped to the catalog before the
|
|
@@ -425,11 +428,11 @@ export function createRuns(ctx) {
|
|
|
425
428
|
return { ok: false, aborted: true };
|
|
426
429
|
const message = typeof result.error === 'string' ? result.error
|
|
427
430
|
: typeof result.message === 'string' ? result.message : '';
|
|
428
|
-
return failure(message);
|
|
431
|
+
return failure(message, picked?.provider);
|
|
429
432
|
}
|
|
430
433
|
catch (error) {
|
|
431
434
|
const message = error instanceof Error ? error.message : String(error);
|
|
432
|
-
return failure(message);
|
|
435
|
+
return failure(message, picked?.provider);
|
|
433
436
|
}
|
|
434
437
|
finally {
|
|
435
438
|
ended = true;
|
package/dist/signin.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { remoteCliHost } from '@byokit/accounts/cli';
|
|
2
2
|
import { authStatus } from './auth-status.ts';
|
|
3
|
-
import type { GatewayTransport, Member, SignInOptions, SignInView } from './types.ts';
|
|
3
|
+
import type { CliLoginCause, GatewayTransport, Member, SignInOptions, SignInView } from './types.ts';
|
|
4
4
|
export type SignInCtx = {
|
|
5
5
|
request: GatewayTransport['request'];
|
|
6
6
|
authStatus?: (agentId: string, refresh: boolean, native: boolean) => ReturnType<typeof authStatus>;
|
|
@@ -10,7 +10,7 @@ export type SignInCtx = {
|
|
|
10
10
|
callbackPort: number;
|
|
11
11
|
nativeClaudeLogin?(): ReturnType<typeof remoteCliHost>;
|
|
12
12
|
resetNativeClaudeLogin?(): Promise<void>;
|
|
13
|
-
|
|
13
|
+
claudeLogin?(): CliLoginCause;
|
|
14
14
|
onDisconnect?(fn: () => void): () => void;
|
|
15
15
|
authConfig(): string;
|
|
16
16
|
restarted(authBefore: string | undefined, signal?: AbortSignal): Promise<boolean>;
|
package/dist/signin.js
CHANGED
|
@@ -241,8 +241,9 @@ export function signIn(ctx, member, o, on) {
|
|
|
241
241
|
};
|
|
242
242
|
native = ctx.nativeClaudeLogin?.();
|
|
243
243
|
if (!await detected()) {
|
|
244
|
-
|
|
245
|
-
|
|
244
|
+
const cause = ctx.claudeLogin?.();
|
|
245
|
+
if (cause?.signIn === false)
|
|
246
|
+
return { state: 'failed', via, why: 'failed', error: 'Claude Code is installed here, but its sign-in could not be checked. Run claude once on this machine to sign in, then try again.', cause };
|
|
246
247
|
if (!native)
|
|
247
248
|
return { state: 'failed', via, why: 'failed', error: 'Claude Code sign-in is not available on this engine.' };
|
|
248
249
|
let started = await native.start('claude', { signal });
|
|
@@ -329,6 +329,12 @@ export function fakeGateway(script, o) {
|
|
|
329
329
|
setTimeout(() => {
|
|
330
330
|
void (async () => {
|
|
331
331
|
const table = bridgeSock ? pluginTable(bridgeSock) : undefined;
|
|
332
|
+
// As the plugin's prompt hook: a child on a helper key asks the kit for its helper's persona (5.8.3).
|
|
333
|
+
if (child && bridgeSock && /^agent:[^:]+:h-[a-z][a-z0-9-]{0,15}:/.test(run.sessionKey)) {
|
|
334
|
+
const reply = await bridgeRequest(bridgeSock, { kind: 'delegation-persona', key: run.sessionKey, runId: run.runId }).catch(() => ({}));
|
|
335
|
+
calls.push({ method: 'before_prompt_build', params: { sessionKey: run.sessionKey, runId: run.runId,
|
|
336
|
+
...(typeof reply.persona === 'string' ? { appendSystemContext: reply.persona } : {}) } });
|
|
337
|
+
}
|
|
332
338
|
let n = 0;
|
|
333
339
|
for (const call of toolCalls(message)) {
|
|
334
340
|
const toolCallId = `call-${++n}`;
|
|
@@ -688,14 +694,18 @@ export function fakeGateway(script, o) {
|
|
|
688
694
|
return { status: 'cancelled' };
|
|
689
695
|
},
|
|
690
696
|
agent: (p, bridgeSock) => startRun(p, bridgeSock),
|
|
691
|
-
|
|
692
|
-
|
|
697
|
+
// The pin: a run it never had (or forgot, as after a restart) waits out as a `timeout` in the `queue` phase that never
|
|
698
|
+
// reached a provider; a live run past `timeoutMs` is a `timeout` while it drains.
|
|
699
|
+
'agent.wait': (p) => new Promise((resolve) => {
|
|
700
|
+
const runId = String(p.runId);
|
|
701
|
+
const run = runs.get(runId);
|
|
693
702
|
if (!run)
|
|
694
|
-
return
|
|
703
|
+
return resolve({ runId, status: 'timeout', timeoutPhase: 'queue', providerStarted: false });
|
|
695
704
|
if (run.result)
|
|
696
|
-
resolve(run.result);
|
|
697
|
-
|
|
698
|
-
|
|
705
|
+
return resolve(run.result);
|
|
706
|
+
run.waiters.push(resolve);
|
|
707
|
+
if (typeof p.timeoutMs === 'number')
|
|
708
|
+
setTimeout(() => resolve({ runId, status: 'timeout', timeoutPhase: 'gateway_draining' }), p.timeoutMs).unref();
|
|
699
709
|
}),
|
|
700
710
|
'sessions.steer': () => ({}),
|
|
701
711
|
// The pin: a key the agent holds answers its canonical key; a missing one errors unless `allowMissing`, which answers `{ ok: false }`.
|
package/dist/types.d.ts
CHANGED
|
@@ -46,12 +46,13 @@ export type DelegatedRun = RunRef & Required<Pick<RunRef, 'runId' | 'agentId' |
|
|
|
46
46
|
tools: string[];
|
|
47
47
|
};
|
|
48
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`)
|
|
50
|
-
* `cancelled` (aborted by the kit or the engine, or its admission ended)
|
|
49
|
+
* `terminal` (finished), `error` (`message`), `resting` (its helper's account hit its limit and rests `until`),
|
|
50
|
+
* `cancelled` (aborted by the kit or the engine, or its admission ended) or `lost` (the engine no longer knows the run,
|
|
51
|
+
* as after an engine restart: outcome unknown, admission ended). */
|
|
51
52
|
export type DelegatedEvent = {
|
|
52
53
|
type: 'admitted' | 'event' | 'ended';
|
|
53
54
|
run: DelegatedRun;
|
|
54
|
-
reason?: 'terminal' | 'error' | 'resting' | 'cancelled';
|
|
55
|
+
reason?: 'terminal' | 'error' | 'resting' | 'cancelled' | 'lost';
|
|
55
56
|
until?: number;
|
|
56
57
|
message?: string;
|
|
57
58
|
event?: {
|
|
@@ -180,6 +181,18 @@ export type RunEnd<T = unknown> = {
|
|
|
180
181
|
kind: 'signed-out' | 'resting' | 'plan' | 'network' | 'other' | 'output';
|
|
181
182
|
until?: number;
|
|
182
183
|
message: string;
|
|
184
|
+
cause?: CliLoginCause;
|
|
185
|
+
};
|
|
186
|
+
/** Why a `claude-cli` run or `anthropic-cli` sign-in failed: Claude Code has no login where this engine reads it (5.5).
|
|
187
|
+
* `signIn` true: `signIn(member, { authChoice: 'anthropic-cli' })` runs that login into `configDir` (subscription
|
|
188
|
+
* billing, the person's Claude plan). False: the engine reads the person's own login, which the kit never writes; the
|
|
189
|
+
* person runs `claude` on this machine, as `home`, to sign in (`configDir` only when they set CLAUDE_CONFIG_DIR). */
|
|
190
|
+
export type CliLoginCause = {
|
|
191
|
+
code: 'needs_cli_login';
|
|
192
|
+
cli: 'claude';
|
|
193
|
+
home: string;
|
|
194
|
+
configDir?: string;
|
|
195
|
+
signIn: boolean;
|
|
183
196
|
};
|
|
184
197
|
export type SignInOptions = {
|
|
185
198
|
authChoice: string;
|
|
@@ -196,6 +209,7 @@ export type SignInView = {
|
|
|
196
209
|
prompt?: string;
|
|
197
210
|
error?: string;
|
|
198
211
|
why?: 'busy' | 'declined' | 'expired' | 'failed';
|
|
212
|
+
cause?: CliLoginCause;
|
|
199
213
|
};
|
|
200
214
|
export type Approval = {
|
|
201
215
|
id: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@byokit/openclaw",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.24.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",
|
package/plugin/index.js
CHANGED
|
@@ -159,6 +159,17 @@ export default {
|
|
|
159
159
|
callId: ctx?.toolCallId ?? event.toolCallId,
|
|
160
160
|
result: { details: d && { status: d.status, runId: d.runId, sessionKey: d.sessionKey, childSessionKey: d.childSessionKey } } });
|
|
161
161
|
});
|
|
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) => {
|
|
165
|
+
const key = ctx?.sessionKey;
|
|
166
|
+
if (typeof key !== 'string' || !/^agent:[^:]+:h-[a-z][a-z0-9-]{0,15}:/.test(key)) return undefined;
|
|
167
|
+
try {
|
|
168
|
+
const reply = await bridgeRequest({ kind: 'delegation-persona', key, runId: ctx?.runId }, { timeoutMs: 10_000 });
|
|
169
|
+
if (typeof reply?.persona === 'string' && reply.persona) return { appendSystemContext: reply.persona };
|
|
170
|
+
} catch { /* no kit: nothing added */ }
|
|
171
|
+
return undefined;
|
|
172
|
+
});
|
|
162
173
|
api.registerAgentEventSubscription?.({ id: 'byokit-delegated-events', handle(event) {
|
|
163
174
|
if (!['assistant', 'tool', 'thinking', 'lifecycle'].includes(event.stream)) return;
|
|
164
175
|
const data = Object.fromEntries(['text', 'name', 'phase', 'toolCallId', 'isError', 'progressTokens', 'aborted', 'error', 'provider', 'model']
|
|
@@ -281,6 +292,12 @@ export default {
|
|
|
281
292
|
callId: ctx?.toolCallId ?? event.toolCallId, tool: event.toolName, input: params });
|
|
282
293
|
if (prepared.allow === false) return { block: true, blockReason: prepared.reason };
|
|
283
294
|
} catch { return { block: true, blockReason: 'delegation admission unavailable' }; }
|
|
295
|
+
// A tracked spawn into the run's own agent goes without `agentId`, the engine's own-agent spawn, so an app's
|
|
296
|
+
// `subagents.allowAgents` list for other agents never refuses it. The kit already admitted that exact agent.
|
|
297
|
+
// Hook params merge over the model's, so the key is overridden, not left out.
|
|
298
|
+
const own = ctx?.agentId ?? /^agent:([^:]+):/.exec(key)?.[1];
|
|
299
|
+
if (event.toolName === 'sessions_spawn' && typeof own === 'string' && params.agentId === own)
|
|
300
|
+
return { params: { ...params, agentId: undefined } };
|
|
284
301
|
}
|
|
285
302
|
if (builtin) return BROWSER && event.toolName === 'browser' ? { params } : undefined;
|
|
286
303
|
// The bridge mints a permit for permitted tools and admits the rest ticket-side; either way the run key
|