@byokit/openclaw 0.17.0 → 0.18.3
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 +17 -0
- package/dist/accounts.d.ts +1 -0
- package/dist/accounts.js +22 -5
- package/dist/bridge.js +3 -0
- package/dist/device.d.ts +7 -2
- package/dist/device.js +2 -1
- package/dist/index.d.ts +1 -1
- package/dist/kit.d.ts +2 -0
- package/dist/kit.js +10 -2
- package/dist/link.js +31 -3
- package/dist/runs.d.ts +21 -1
- package/dist/runs.js +87 -2
- package/dist/types.d.ts +11 -0
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,23 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.18.3 (2026-10-19)
|
|
6
|
+
|
|
7
|
+
- A signed-in Claude Code login is an account in the kit's account list, and its Claude CLI route's models are listed under it.
|
|
8
|
+
|
|
9
|
+
## 0.18.2 (2026-10-19)
|
|
10
|
+
|
|
11
|
+
- A signed-in ChatGPT login lists the Codex CLI route's models in the account model list, marked available.
|
|
12
|
+
|
|
13
|
+
## 0.18.1 (2026-10-18)
|
|
14
|
+
|
|
15
|
+
- Dependency update: pins @byokit/accounts 0.28.1.
|
|
16
|
+
- FIX: (from @byokit/accounts 0.28.1) Installed Codex readiness retries a transient account check once and distinguishes timeouts, startup, pipe, exit, protocol and unreadable-reply failures from a confirmed signed-out account, with safe typed probe metadata and no provider messages or stderr.
|
|
17
|
+
|
|
18
|
+
## 0.18.0 (2026-10-18)
|
|
19
|
+
|
|
20
|
+
- Reattach a paired device to a live-kit run by its existing idempotency key: sequenced replay then live events, a bounded journal with typed truncation, and grant-member isolation. Device disconnect does not interrupt the run; engine-restart reconstruction is not included.
|
|
21
|
+
|
|
5
22
|
## 0.17.0 (2026-10-18)
|
|
6
23
|
|
|
7
24
|
- Add opt-in `RunSpec.delegates` for native `sessions_send`/`sessions_spawn` app-tool admission and typed delegated events, retained exact receipts and subtree cancellation. Targets retain their own accounts; helper/person account sharing is not included.
|
package/dist/accounts.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { createLocks } from './locks.ts';
|
|
|
2
2
|
import { type RouteView } from './routes.ts';
|
|
3
3
|
import type { Account, AccountId, AccountPick, Defaults, GatewayTransport, Member, ModelInfo, MoveResult, Room, RunSelection, SignInOptions, SignInView } from './types.ts';
|
|
4
4
|
export declare const modelsOf: (provider: string, answer: unknown) => ModelInfo[];
|
|
5
|
+
export declare const modelsOfAccount: (row: Pick<Account, 'provider' | 'state'>, answer: unknown) => ModelInfo[];
|
|
5
6
|
type IndexEntry = {
|
|
6
7
|
agent: string;
|
|
7
8
|
name: string;
|
package/dist/accounts.js
CHANGED
|
@@ -16,7 +16,7 @@ import { KEY_PREFIX, MEMBER_ID, NEW_MEMBER_ID, keyAgentId, untilListed, untilUnl
|
|
|
16
16
|
import { routes, routeFor } from './routes.js';
|
|
17
17
|
import { words } from './words.js';
|
|
18
18
|
const record = (v) => !!v && typeof v === 'object' && !Array.isArray(v);
|
|
19
|
-
// `models.list { agentId, view: '
|
|
19
|
+
// `models.list { agentId, view: 'all' }` rows of the account's provider, mapped to `provider/model`. The pin
|
|
20
20
|
// reports no per-plan availability, so `why: 'plan'` only comes from a run ending `plan`, never from here.
|
|
21
21
|
export const modelsOf = (provider, answer) => {
|
|
22
22
|
const rows = record(answer) && Array.isArray(answer.models) ? answer.models : [];
|
|
@@ -48,6 +48,16 @@ export const modelsOf = (provider, answer) => {
|
|
|
48
48
|
}
|
|
49
49
|
return out;
|
|
50
50
|
};
|
|
51
|
+
// The account's models: its provider's rows, plus the Codex CLI route's rows when the account is ready. The pin reports
|
|
52
|
+
// those as missing-auth (no auth reaches the Codex route from the ChatGPT login), so the kit, which holds the login, is the
|
|
53
|
+
// signal for them.
|
|
54
|
+
export const modelsOfAccount = (row, answer) => {
|
|
55
|
+
const own = modelsOf(row.provider, answer);
|
|
56
|
+
if (row.provider !== 'openai' || row.state !== 'ready')
|
|
57
|
+
return own;
|
|
58
|
+
const codex = modelsOf('openai-codex', answer).map(({ why: _why, ...m }) => ({ ...m, available: true }));
|
|
59
|
+
return [...own, ...codex];
|
|
60
|
+
};
|
|
51
61
|
const ACCOUNT_AGENT = /^[a-z][a-z0-9-]{0,31}--[0-9a-f]{6}$/;
|
|
52
62
|
const usable = (status) => status === 'ok' || status === 'expiring' || status === 'static';
|
|
53
63
|
const IDENTITY = 'IDENTITY.md';
|
|
@@ -512,7 +522,14 @@ export function createAccounts(ctx) {
|
|
|
512
522
|
}
|
|
513
523
|
return { rows, dirty };
|
|
514
524
|
};
|
|
515
|
-
|
|
525
|
+
// A detected Claude Code login has no profile in the kit: it stays in Claude Code. The engine's detection says whether
|
|
526
|
+
// it is there, and a detected one is the member's signed-in claude-cli profile for this read.
|
|
527
|
+
const claudeDetected = (agentId) => ctx.request('openclaw.setup.detect', { agentId }, { timeoutMs: 20_000 })
|
|
528
|
+
.then((r) => r.candidates?.some((c) => c.kind === 'claude-cli' && c.credentials === true) === true, () => false);
|
|
529
|
+
const gather = async (agentId) => {
|
|
530
|
+
const [roster, profiles, claude] = await Promise.all([rosterOf(), profilesOf(agentId), claudeDetected(agentId)]);
|
|
531
|
+
return [roster, claude && !profiles.some((p) => p.provider === 'claude-cli') ? [...profiles, { provider: 'claude-cli', status: 'ok' }] : profiles];
|
|
532
|
+
};
|
|
516
533
|
const buildRows = async (member, agentId) => {
|
|
517
534
|
const file = read(member);
|
|
518
535
|
const [roster, profiles] = await gather(agentId);
|
|
@@ -638,7 +655,7 @@ export function createAccounts(ctx) {
|
|
|
638
655
|
return readModels(row, entry);
|
|
639
656
|
}
|
|
640
657
|
// The shared hold covers the read, so no kit read of the agent is in flight while it is removed.
|
|
641
|
-
const readModels = (row, entry) => ctx.locks.shared(entry.agent, async () =>
|
|
658
|
+
const readModels = (row, entry) => ctx.locks.shared(entry.agent, async () => modelsOfAccount(row, await ctx.request('models.list', { agentId: entry.agent, view: 'all' })));
|
|
642
659
|
async function room(member, id, demand) {
|
|
643
660
|
check(member);
|
|
644
661
|
await ctx.ensure(member);
|
|
@@ -812,8 +829,8 @@ export function createAccounts(ctx) {
|
|
|
812
829
|
return failed('start_failed', words('move.taken', { name: row.name }));
|
|
813
830
|
// The session's model when the target lists it, else the target's first available; the read takes no shared
|
|
814
831
|
// hold (this move holds the target exclusively). With no answer the fork keeps the parent's own selection.
|
|
815
|
-
const models = await ctx.request('models.list', { agentId: entry.agent, view: '
|
|
816
|
-
.then((answer) =>
|
|
832
|
+
const models = await ctx.request('models.list', { agentId: entry.agent, view: 'all' })
|
|
833
|
+
.then((answer) => modelsOfAccount(row, answer), () => []);
|
|
817
834
|
const current = typeof session.model !== 'string' ? undefined
|
|
818
835
|
: (typeof session.modelProvider === 'string' && !session.model.includes('/') ? `${session.modelProvider}/${session.model}` : session.model).toLowerCase();
|
|
819
836
|
const model = models.find((m) => m.id.toLowerCase() === current)?.id ?? models.find((m) => m.available)?.id;
|
package/dist/bridge.js
CHANGED
|
@@ -3,6 +3,7 @@ import { randomBytes } from 'node:crypto';
|
|
|
3
3
|
import { existsSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
4
4
|
import { createServer } from 'node:net';
|
|
5
5
|
import { rm } from 'node:fs/promises';
|
|
6
|
+
import { provider } from '@byokit/accounts';
|
|
6
7
|
import { words } from './words.js';
|
|
7
8
|
/** Newline-framed JSON, one request per connection; anything larger is not a gate request. */
|
|
8
9
|
const FRAME_CAP = 1_000_000;
|
|
@@ -38,6 +39,8 @@ export function writePlugin(dir, o) {
|
|
|
38
39
|
name: 'BYOKit bridge',
|
|
39
40
|
activation: { onStartup: true },
|
|
40
41
|
cliBackends: ['openai-codex'],
|
|
42
|
+
// The Codex CLI route lists the ChatGPT catalogue's models; the engine lists a plugin route's rows only from here.
|
|
43
|
+
modelCatalog: { providers: { 'openai-codex': { models: [provider('chatgpt').models.strong, provider('chatgpt').models.fast].map((id) => ({ id, name: id })) } }, discovery: { 'openai-codex': 'static' } },
|
|
41
44
|
contracts: { tools: o.tools.map((t) => t.name), ...(o.browser ? { agentToolResultMiddleware: ['openclaw', 'codex'] } : {}) },
|
|
42
45
|
configSchema: { type: 'object', additionalProperties: false },
|
|
43
46
|
};
|
package/dist/device.d.ts
CHANGED
|
@@ -2,9 +2,9 @@ import type { BrowserDevice, SignInRefusedWhy } from './browser.ts';
|
|
|
2
2
|
import type { DeviceLink } from '@byokit/pair';
|
|
3
3
|
import type { RouteView } from './routes.ts';
|
|
4
4
|
import { type AccountView } from './words.ts';
|
|
5
|
-
import type { Approval, Decision, GatewayEventName, GatewayEventPayload, GatewayMethod, GatewayParams, GatewayResult, KitState, RunEnd, OutputSchema, SchemaOutput, RunEvent, SignInView, AccountRef } from './types.ts';
|
|
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, RunUsage, SignInView, OutputSchema, SchemaOutput } from './types.ts';
|
|
7
|
+
export type { Approval, 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]: {
|
|
@@ -30,11 +30,13 @@ export type SessionRow = {
|
|
|
30
30
|
/** The frame `oc.run`'s stream ends with: the run's `RunEnd`, beside its events. */
|
|
31
31
|
export type DeviceEndFrame<T = unknown> = {
|
|
32
32
|
type: 'end';
|
|
33
|
+
seq: number;
|
|
33
34
|
end: RunEnd<T>;
|
|
34
35
|
};
|
|
35
36
|
/** `oc.run`'s options beside the message: the member's own session, account, and the kit's run options. */
|
|
36
37
|
export type DeviceRunOptions<S extends OutputSchema | undefined = OutputSchema | undefined> = {
|
|
37
38
|
schema?: S;
|
|
39
|
+
idempotencyKey?: string;
|
|
38
40
|
sessionKey?: string;
|
|
39
41
|
account?: AccountRef | 'default' | 'auto';
|
|
40
42
|
needs?: string[];
|
|
@@ -96,6 +98,9 @@ export type OpenClawDevice = {
|
|
|
96
98
|
signOut(p: string): Promise<void>;
|
|
97
99
|
sessions(): Promise<SessionRow[]>;
|
|
98
100
|
run<const S extends OutputSchema | undefined = undefined>(message: string, o?: DeviceRunOptions<S>): AsyncIterable<DeviceRunEvent | DeviceEndFrame<SchemaOutput<S>>>;
|
|
101
|
+
attach(runId: string, o: {
|
|
102
|
+
fromSeq: number;
|
|
103
|
+
}): AsyncIterable<RunFrame>;
|
|
99
104
|
steer(k: string, t: string, o?: {
|
|
100
105
|
auth?: 'apiKey';
|
|
101
106
|
}): Promise<void>;
|
package/dist/device.js
CHANGED
|
@@ -361,8 +361,9 @@ export function openclawDevice(link) {
|
|
|
361
361
|
await link.request('oc.signout', { provider: p });
|
|
362
362
|
},
|
|
363
363
|
sessions: () => link.request('oc.sessions'),
|
|
364
|
+
attach: (runId, o) => liveStream(() => link.stream('oc.attach', { runId, fromSeq: o.fromSeq })),
|
|
364
365
|
run: (message, o) => {
|
|
365
|
-
const inner = liveStream(() => link.stream('oc.run', { message, ...(o?.sessionKey ? { sessionKey: checkKey(o.sessionKey) } : {}),
|
|
366
|
+
const inner = liveStream(() => link.stream('oc.run', { message, ...(o?.idempotencyKey !== undefined ? { idempotencyKey: o.idempotencyKey } : {}), ...(o?.sessionKey ? { sessionKey: checkKey(o.sessionKey) } : {}),
|
|
366
367
|
...(o?.account !== undefined ? { account: o.account } : {}),
|
|
367
368
|
...(o?.needs !== undefined ? { needs: o.needs } : {}),
|
|
368
369
|
...(o?.model !== undefined ? { model: o.model } : {}), ...(o?.system !== undefined ? { system: o.system } : {}),
|
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, RunRef, 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, 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, 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.d.ts
CHANGED
|
@@ -64,6 +64,8 @@ export declare class OpenClawKit {
|
|
|
64
64
|
private members?;
|
|
65
65
|
private readonly locks;
|
|
66
66
|
private readonly routeMap;
|
|
67
|
+
private readonly runReplay;
|
|
68
|
+
attach(member: Member, runId: string, fromSeq: number, signal: AbortSignal): AsyncIterable<import("./types.ts").RunFrame>;
|
|
67
69
|
private installs;
|
|
68
70
|
private accountsCtl?;
|
|
69
71
|
private readonly bridge;
|
package/dist/kit.js
CHANGED
|
@@ -18,7 +18,7 @@ import { createAccounts, memberOfAccount } from './accounts.js';
|
|
|
18
18
|
import { createLocks } from './locks.js';
|
|
19
19
|
import { keyAgentId, KEY_PREFIX, MEMBER_ID } from './members.js';
|
|
20
20
|
import { confirmRetainedLogin as confirmLogin, migrateRetainedLogin as migrateLogin } from './migrate.js';
|
|
21
|
-
import { createRuns, signedInProviders } from './runs.js';
|
|
21
|
+
import { createRuns, RunReplay, signedInProviders } from './runs.js';
|
|
22
22
|
import { authStatus, createAuthStatus } from './auth-status.js';
|
|
23
23
|
import { outputSchema } from './output.js';
|
|
24
24
|
import { createKeys } from './keys.js';
|
|
@@ -442,6 +442,10 @@ export class OpenClawKit {
|
|
|
442
442
|
members;
|
|
443
443
|
locks = createLocks();
|
|
444
444
|
routeMap = new Map();
|
|
445
|
+
runReplay = new RunReplay();
|
|
446
|
+
attach(member, runId, fromSeq, signal) {
|
|
447
|
+
return this.runReplay.attach(member, runId, fromSeq, signal);
|
|
448
|
+
}
|
|
445
449
|
installs = Promise.resolve();
|
|
446
450
|
accountsCtl;
|
|
447
451
|
// One bridge and one approval surface per kit: listeners and run registrations survive reconnects (B3).
|
|
@@ -948,6 +952,8 @@ export class OpenClawKit {
|
|
|
948
952
|
});
|
|
949
953
|
}
|
|
950
954
|
closed(why) {
|
|
955
|
+
if (why === 'engine exited')
|
|
956
|
+
this.runReplay.clear();
|
|
951
957
|
// A first start's loop owns an engine exit until it reaches ready; other closes are the transport client's to retry.
|
|
952
958
|
if (this.startRun && !this.reachedReady) {
|
|
953
959
|
if (why === 'engine exited')
|
|
@@ -976,6 +982,7 @@ export class OpenClawKit {
|
|
|
976
982
|
return delay(retryAt - Date.now(), undefined, { signal });
|
|
977
983
|
}
|
|
978
984
|
async stop() {
|
|
985
|
+
this.runReplay.clear();
|
|
979
986
|
this.stopping = true;
|
|
980
987
|
this.reachedReady = false;
|
|
981
988
|
this.startRun?.abort();
|
|
@@ -1311,10 +1318,11 @@ export class OpenClawKit {
|
|
|
1311
1318
|
confirmRetainedLogin(member, source) {
|
|
1312
1319
|
return confirmLogin({ ...this.signInCtx(), seal: this.o.authSeal, log: this.o.log }, member, source);
|
|
1313
1320
|
}
|
|
1314
|
-
// runs (5.8): the facade
|
|
1321
|
+
// runs (5.8): the facade retains the live journal; every dispatch delegates to the O8 module
|
|
1315
1322
|
// with this kit's live transport, member table and bridge (spec item O9).
|
|
1316
1323
|
runs() {
|
|
1317
1324
|
return createRuns({
|
|
1325
|
+
replay: this.runReplay,
|
|
1318
1326
|
request: (method, params, o) => this.request()(method, params, o),
|
|
1319
1327
|
authStatus: this.admission.read,
|
|
1320
1328
|
invalidateAuth: this.admission.invalidate,
|
package/dist/link.js
CHANGED
|
@@ -30,8 +30,9 @@ function memberSessions(result, owns) {
|
|
|
30
30
|
}
|
|
31
31
|
/** `oc.run`'s run options, type-checked; the kit's own checks (account, tool names) still apply. */
|
|
32
32
|
function runOptions(args) {
|
|
33
|
-
const { message, model, account, needs, auth, system, images, thinking, tools, schema } = args;
|
|
33
|
+
const { message, idempotencyKey, model, account, needs, auth, system, images, thinking, tools, schema } = args;
|
|
34
34
|
const ok = typeof message === 'string'
|
|
35
|
+
&& (idempotencyKey === undefined || typeof idempotencyKey === 'string' && idempotencyKey.length > 0)
|
|
35
36
|
&& (model === undefined || typeof model === 'string')
|
|
36
37
|
&& (account === undefined || typeof account === 'string')
|
|
37
38
|
&& (needs === undefined || (Array.isArray(needs) && needs.every((n) => typeof n === 'string')))
|
|
@@ -46,6 +47,7 @@ function runOptions(args) {
|
|
|
46
47
|
outputSchema(schema);
|
|
47
48
|
return {
|
|
48
49
|
message: message,
|
|
50
|
+
...(idempotencyKey === undefined ? {} : { idempotencyKey: idempotencyKey }),
|
|
49
51
|
...(schema === undefined ? {} : { schema: schema }),
|
|
50
52
|
...(model === undefined ? {} : { model: model }),
|
|
51
53
|
...(account === undefined ? {} : { account: account }),
|
|
@@ -561,6 +563,31 @@ export function openclawLink(kit, o) {
|
|
|
561
563
|
}
|
|
562
564
|
return;
|
|
563
565
|
}
|
|
566
|
+
if (req.op === 'oc.attach') {
|
|
567
|
+
if (grant.role === 'view')
|
|
568
|
+
throw refused();
|
|
569
|
+
const args = isRecord(req.args) ? req.args : {};
|
|
570
|
+
if (typeof args.runId !== 'string' || !args.runId.length || !Number.isSafeInteger(args.fromSeq) || args.fromSeq < 0)
|
|
571
|
+
throw refused();
|
|
572
|
+
const controller = new AbortController();
|
|
573
|
+
let frames;
|
|
574
|
+
try {
|
|
575
|
+
frames = kit.attach(member, args.runId, args.fromSeq, controller.signal);
|
|
576
|
+
}
|
|
577
|
+
catch {
|
|
578
|
+
throw refused();
|
|
579
|
+
}
|
|
580
|
+
s.onEnd = () => controller.abort();
|
|
581
|
+
try {
|
|
582
|
+
for await (const frame of frames)
|
|
583
|
+
await s.write(`${JSON.stringify(frame)}\n`);
|
|
584
|
+
s.end();
|
|
585
|
+
}
|
|
586
|
+
finally {
|
|
587
|
+
controller.abort();
|
|
588
|
+
}
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
564
591
|
if (req.op === 'oc.run') {
|
|
565
592
|
if (grant.role === 'view')
|
|
566
593
|
throw refused();
|
|
@@ -579,8 +606,9 @@ export function openclawLink(kit, o) {
|
|
|
579
606
|
tail = tail.then(() => s.write(`${JSON.stringify(frame)}\n`)).catch(() => { });
|
|
580
607
|
};
|
|
581
608
|
try {
|
|
582
|
-
|
|
583
|
-
|
|
609
|
+
let seq = 0;
|
|
610
|
+
const end = await kit.run({ member, sessionKey, ...options }, (e) => { seq = e.seq; send(e); });
|
|
611
|
+
send({ type: 'end', seq: seq + 1, end });
|
|
584
612
|
await tail;
|
|
585
613
|
s.end();
|
|
586
614
|
}
|
package/dist/runs.d.ts
CHANGED
|
@@ -4,14 +4,33 @@ import type { createAccounts } from './accounts.ts';
|
|
|
4
4
|
import type { createLocks } from './locks.ts';
|
|
5
5
|
import { authStatus } from './auth-status.ts';
|
|
6
6
|
import { outputSchema, type OutputSchema, type SchemaOutput } from './output.ts';
|
|
7
|
-
import type { GatewayTransport, Member, RunEnd, RunEvent, RunSpec } from './types.ts';
|
|
7
|
+
import type { GatewayTransport, Member, RunEnd, RunEvent, RunFrame, RunSpec } from './types.ts';
|
|
8
8
|
export declare const USABLE: Set<string>;
|
|
9
9
|
/**
|
|
10
10
|
* The providers a `models.authStatus` answer shows usable (the rule a picked account is checked by), lowercased and
|
|
11
11
|
* unique; undefined while the engine has no prepared status (unknown, not none).
|
|
12
12
|
*/
|
|
13
13
|
export declare function signedInProviders(status: unknown): string[] | undefined;
|
|
14
|
+
type Journal = {
|
|
15
|
+
member: Member;
|
|
16
|
+
frames: Exclude<RunFrame, {
|
|
17
|
+
type: 'truncated';
|
|
18
|
+
}>[];
|
|
19
|
+
done: boolean;
|
|
20
|
+
wake: Set<() => void>;
|
|
21
|
+
};
|
|
22
|
+
/** Live-kit only: bounded history, with readers pulling by cursor rather than growing per-reader queues. */
|
|
23
|
+
export declare class RunReplay {
|
|
24
|
+
private readonly runs;
|
|
25
|
+
begin(member: Member, id: string): Journal | undefined;
|
|
26
|
+
push(row: Journal | undefined, frame: Exclude<RunFrame, {
|
|
27
|
+
type: 'truncated';
|
|
28
|
+
}>): void;
|
|
29
|
+
clear(): void;
|
|
30
|
+
attach(member: Member, id: string, fromSeq: number, signal: AbortSignal): AsyncIterable<RunFrame>;
|
|
31
|
+
}
|
|
14
32
|
export declare function createRuns(ctx: {
|
|
33
|
+
replay?: RunReplay;
|
|
15
34
|
request: GatewayTransport['request'];
|
|
16
35
|
authStatus?: (agentId: string, refresh: boolean, native: boolean) => ReturnType<typeof authStatus>;
|
|
17
36
|
invalidateAuth?: () => void;
|
|
@@ -32,3 +51,4 @@ export declare function createRuns(ctx: {
|
|
|
32
51
|
steer(k: string, t: string): Promise<void>;
|
|
33
52
|
abort(k: string): Promise<void>;
|
|
34
53
|
};
|
|
54
|
+
export {};
|
package/dist/runs.js
CHANGED
|
@@ -71,6 +71,74 @@ function account(model) {
|
|
|
71
71
|
throw new Error(`refused: "${model}" is not a provider/model reference`);
|
|
72
72
|
return { provider, model: model.slice(model.indexOf('/') + 1) }; // the engine lowercases ids
|
|
73
73
|
}
|
|
74
|
+
/** Live-kit only: bounded history, with readers pulling by cursor rather than growing per-reader queues. */
|
|
75
|
+
export class RunReplay {
|
|
76
|
+
runs = new Map();
|
|
77
|
+
begin(member, id) {
|
|
78
|
+
if (this.runs.has(id))
|
|
79
|
+
return undefined; // engine-cache retry must not replace the original stream
|
|
80
|
+
const row = { member, frames: [], done: false, wake: new Set() };
|
|
81
|
+
this.runs.set(id, row);
|
|
82
|
+
return row;
|
|
83
|
+
}
|
|
84
|
+
push(row, frame) {
|
|
85
|
+
if (!row)
|
|
86
|
+
return;
|
|
87
|
+
row.frames.push(frame);
|
|
88
|
+
if (row.frames.length > 32)
|
|
89
|
+
row.frames.shift();
|
|
90
|
+
row.done = frame.type === 'end';
|
|
91
|
+
for (const wake of row.wake)
|
|
92
|
+
wake();
|
|
93
|
+
if (row.done) {
|
|
94
|
+
const completed = [...this.runs].filter(([, r]) => r.done);
|
|
95
|
+
for (const [id] of completed.slice(0, -128))
|
|
96
|
+
this.runs.delete(id);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
clear() {
|
|
100
|
+
for (const row of this.runs.values())
|
|
101
|
+
for (const wake of row.wake)
|
|
102
|
+
wake();
|
|
103
|
+
this.runs.clear();
|
|
104
|
+
}
|
|
105
|
+
attach(member, id, fromSeq, signal) {
|
|
106
|
+
const row = this.runs.get(id);
|
|
107
|
+
if (row && row.member !== member)
|
|
108
|
+
throw new Error('foreign run');
|
|
109
|
+
const runs = this.runs;
|
|
110
|
+
return (async function* () {
|
|
111
|
+
let cursor = fromSeq;
|
|
112
|
+
while (!signal.aborted) {
|
|
113
|
+
if (!row || runs.get(id) !== row) {
|
|
114
|
+
yield { type: 'truncated', reason: 'not-found' };
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
const firstSeq = row.frames[0]?.seq ?? 1;
|
|
118
|
+
const lastSeq = row.frames.at(-1)?.seq ?? 0;
|
|
119
|
+
if (cursor < firstSeq - 1 || cursor > lastSeq) {
|
|
120
|
+
yield { type: 'truncated', reason: 'truncated', firstSeq };
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
const next = row.frames.find(f => f.seq > cursor);
|
|
124
|
+
if (next) {
|
|
125
|
+
cursor = next.seq;
|
|
126
|
+
yield next;
|
|
127
|
+
if (next.type === 'end')
|
|
128
|
+
return;
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
if (row.done)
|
|
132
|
+
return;
|
|
133
|
+
await new Promise(resolve => {
|
|
134
|
+
const wake = () => { row.wake.delete(wake); signal.removeEventListener('abort', wake); resolve(); };
|
|
135
|
+
row.wake.add(wake);
|
|
136
|
+
signal.addEventListener('abort', wake, { once: true });
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
})();
|
|
140
|
+
}
|
|
141
|
+
}
|
|
74
142
|
export function createRuns(ctx) {
|
|
75
143
|
// The plan window the engine last read for the run's provider: `models.authStatus` answers from its cache without
|
|
76
144
|
// blocking. Nothing reported, or no answer in time, is no window.
|
|
@@ -90,7 +158,7 @@ export function createRuns(ctx) {
|
|
|
90
158
|
ctx.invalidateAuth?.();
|
|
91
159
|
return { ok: false, ...classified, message };
|
|
92
160
|
};
|
|
93
|
-
const
|
|
161
|
+
const execute = async (spec, on, keyAgent, preparedOutput) => {
|
|
94
162
|
// Member boundary first: a member never speaks in another member's session, refused before any request. A key
|
|
95
163
|
// of one of the member's account agents is the member's too; binding below checks the index names it.
|
|
96
164
|
if (!spec.sessionKey.startsWith(`agent:${spec.member}:`) && !(ctx.accounts && !keyAgent && spec.sessionKey.startsWith(`agent:${spec.member}--`)))
|
|
@@ -351,8 +419,25 @@ export function createRuns(ctx) {
|
|
|
351
419
|
releaseDelegation?.();
|
|
352
420
|
}
|
|
353
421
|
};
|
|
422
|
+
const replay = ctx.replay ?? new RunReplay();
|
|
354
423
|
return {
|
|
355
|
-
run,
|
|
424
|
+
run: async (spec, on, keyAgent, preparedOutput) => {
|
|
425
|
+
if (spec.idempotencyKey !== undefined && (typeof spec.idempotencyKey !== 'string' || !spec.idempotencyKey.length))
|
|
426
|
+
throw new Error('refused: idempotencyKey must be a non-empty string');
|
|
427
|
+
const id = spec.idempotencyKey ?? randomUUID();
|
|
428
|
+
const row = replay.begin(spec.member, id);
|
|
429
|
+
let seq = 0;
|
|
430
|
+
const emit = (e) => { const event = { ...e, seq: ++seq }; replay.push(row, event); on?.(event); };
|
|
431
|
+
try {
|
|
432
|
+
const end = await execute({ ...spec, idempotencyKey: id }, emit, keyAgent, preparedOutput);
|
|
433
|
+
replay.push(row, { type: 'end', seq: ++seq, end });
|
|
434
|
+
return end;
|
|
435
|
+
}
|
|
436
|
+
catch (error) {
|
|
437
|
+
replay.push(row, { type: 'end', seq: ++seq, end: failure(error instanceof Error ? error.message : String(error)) });
|
|
438
|
+
throw error;
|
|
439
|
+
}
|
|
440
|
+
},
|
|
356
441
|
steer: (k, t) => ctx.request('sessions.steer', { sessionKey: ctx.routes.get(k)?.session ?? k, message: t }),
|
|
357
442
|
abort: async (k) => { await ctx.request('chat.abort', { sessionKey: ctx.routes.get(k)?.session ?? k }); },
|
|
358
443
|
};
|
package/dist/types.d.ts
CHANGED
|
@@ -80,6 +80,8 @@ export interface RunSpec<S extends OutputSchema | undefined = OutputSchema | und
|
|
|
80
80
|
register?: boolean;
|
|
81
81
|
}
|
|
82
82
|
export type RunEvent = {
|
|
83
|
+
seq: number;
|
|
84
|
+
} & ({
|
|
83
85
|
type: 'started';
|
|
84
86
|
} | {
|
|
85
87
|
type: 'thinking';
|
|
@@ -103,6 +105,15 @@ export type RunEvent = {
|
|
|
103
105
|
input?: Record<string, unknown>;
|
|
104
106
|
output?: unknown;
|
|
105
107
|
error?: boolean;
|
|
108
|
+
});
|
|
109
|
+
export type RunFrame = RunEvent | {
|
|
110
|
+
type: 'end';
|
|
111
|
+
seq: number;
|
|
112
|
+
end: RunEnd;
|
|
113
|
+
} | {
|
|
114
|
+
type: 'truncated';
|
|
115
|
+
reason: 'truncated' | 'not-found';
|
|
116
|
+
firstSeq?: number;
|
|
106
117
|
};
|
|
107
118
|
/** Token usage of one run as the engine totals it (every model call of the run, compaction included). */
|
|
108
119
|
export type RunUsage = {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@byokit/openclaw",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.3",
|
|
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.
|
|
63
|
+
"@byokit/accounts": "0.28.1",
|
|
64
64
|
"@byokit/pair": "0.10.4",
|
|
65
65
|
"@byokit/relay": "0.8.2",
|
|
66
66
|
"@byokit/discover": "0.8.4",
|