@byokit/openclaw 0.18.3 → 0.20.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 +9 -0
- package/README.md +36 -3
- package/dist/engine.d.ts +8 -0
- package/dist/engine.js +112 -1
- package/dist/helpers.d.ts +25 -0
- package/dist/helpers.js +70 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1 -0
- package/dist/kit.d.ts +15 -1
- package/dist/kit.js +23 -0
- package/dist/runs.d.ts +2 -0
- package/dist/runs.js +16 -6
- package/dist/types.d.ts +10 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.20.0 (2026-10-19)
|
|
6
|
+
|
|
7
|
+
- FEAT: `ensureHelper(member, helper, { account, persona })` gives a member several helpers, each with its own persona and its own sessions (`agent:<member>:h-<helper>:…`) on one account the member already signed in, the API-key lane included: runs go to the engine agent already holding that account, so no credential is copied and no sign-in starts. `run({ helper })` speaks as that helper with its persona as the system text; `RunRef.helper` names it on `ToolHost.gate` and `call`, and its approvals stay the member's.
|
|
8
|
+
- SECURITY: helper requests fail closed with a typed `HelperError` before any engine request: an invalid id or persona, caller `system` text on a helper run, an account the member does not hold, a removed account (never moved to another sign-in), a session key outside the helper, a helper key run without `helper`, or `move` of a helper key. A helper gains no tool, builtin or session-key prefix the member's run would not.
|
|
9
|
+
|
|
10
|
+
## 0.19.0 (2026-10-19)
|
|
11
|
+
|
|
12
|
+
- FEAT: connect Composio through the pinned engine's native MCP OAuth flow, with browser URL handoff and native sign-out.
|
|
13
|
+
|
|
5
14
|
## 0.18.3 (2026-10-19)
|
|
6
15
|
|
|
7
16
|
- 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.
|
package/README.md
CHANGED
|
@@ -390,6 +390,8 @@ apps can offer them when the app or person opts in, labelled API key (billed per
|
|
|
390
390
|
`local`) so the app can label what is billed. Proxy routes, compatibility aliases, local runtimes stay off. Native Claude Code sign-in is offered by default. The kit re-checks the installed Claude login at each setup detection and, once it is signed in, restarts the engine onto it. A plan key (OpenCode Go: `kit.addAccount(member, { authChoice: 'opencode-go' }, on)`) asks for `Plan key`; `paste` it and the engine live-tests it before `done`. A paired device starts the same sign-in with either `via` and pastes the key over the sealed link, which keeps it on the host. A run reaches the pinned engine's static Go models (default `opencode-go/deepseek-v4-pro`); the kit keeps OpenCode Go's `x-opencode-session` header on every request, the setup probe included. A live-only Go model such as Muse needs no app config: the kit registers the plan's live catalog with each model's API, so `run({ model: 'opencode-go/muse-spark-1.3-contributor' })` runs on the account (or with `auth: 'apiKey'` on a Go key); an app that sets `config.models.providers['opencode-go'].models` keeps its own list. A choice the pinned Gateway cannot set up has readiness `no_upstream_flow` and is never offered. Pass the accounts the app offers in `KitOptions.offered` (the provider, auth choice or alias each route carries, e.g. `'openrouter'`); the kit allow-lists the plugin of every route those name, so an opted-in sign-in is not blocked. An app can still name a plugin directly in `config.plugins.allow`, and pass the route's `choice` explicitly to `kit.signIn()`.
|
|
391
391
|
Offered provider plugins are allowed by default alongside the app's plugin ids. A route whose provider plugin ships outside the engine (Z.AI's GLM Coding Plan, for one) has readiness `needs_plugin` until the app calls `await kit.installPlugin(route.needs.plugin)`: it installs that plugin into the kit's own engine at exactly the engine's version and pinned npm integrity, keeps it allowed across restarts, and rejects `PluginInstallError` (`code` `version` or `integrity`) with nothing installed on a mismatch. Discovery never installs.
|
|
392
392
|
|
|
393
|
+
Composio uses its hosted MCP endpoint with the pinned OpenClaw engine's native OAuth client; BYOKit does not maintain a Composio app/tool list or install a separate plugin. `beginComposioSignIn()` configures `https://connect.composio.dev/mcp` for Streamable HTTP OAuth and returns `{ url }`; open it in the browser, then call `completeComposioSignIn()` to wait for OpenClaw's callback and token exchange. OpenClaw stores the OAuth credentials in its engine-owned SQLite state; when the app supplies `authSeal`, the kit seals that state at rest with other engine data. `signOutComposio()` invokes native MCP logout. Composio usage is billed under the person's own Composio plan.
|
|
394
|
+
|
|
393
395
|
## API at a glance
|
|
394
396
|
|
|
395
397
|
Entries:
|
|
@@ -401,7 +403,7 @@ Entries:
|
|
|
401
403
|
|
|
402
404
|
| Export | What it does |
|
|
403
405
|
| --- | --- |
|
|
404
|
-
| `OpenClawKit` (`.`) | Prepares, starts, supervises and stops the engine. `call` / `callDynamic` pass through to the Gateway, `onEvent` listens. Helpers: `ensureMember`, `routes`, `providers`, `signedIn`, `signIn`, `signOut`, `removeKey`, `migrateRetainedLogin`, `confirmRetainedLogin`, `toolNames`, `run`, `steer`, `abort`, `approvals`, `onApproval`, `decide`, `allowOnce`, `disallowOnce`, `patchConfig`, `getConfigKey`, `setConfigKey`, `memoryLimited`, `doctorContext` |
|
|
406
|
+
| `OpenClawKit` (`.`) | Prepares, starts, supervises and stops the engine. `call` / `callDynamic` pass through to the Gateway, `onEvent` listens. Helpers: `ensureMember`, `ensureHelper`, `routes`, `providers`, `signedIn`, `signIn`, `signOut`, `removeKey`, `migrateRetainedLogin`, `confirmRetainedLogin`, `toolNames`, `run`, `steer`, `abort`, `approvals`, `onApproval`, `decide`, `allowOnce`, `disallowOnce`, `patchConfig`, `getConfigKey`, `setConfigKey`, `memoryLimited`, `doctorContext` |
|
|
405
407
|
| `ENGINE_VERSION`, `PROTOCOL_VERSION`, `OPERATOR_SCOPES` (`.`) | The pinned engine version, its protocol and the operator scopes the kit connects with |
|
|
406
408
|
| `KitOptions`, `RunSpec`, `RunEvent`, `RunEnd`, `RunUsage`, `PlanWindow`, `Route`, `Approval`, `Decision`, `ToolSpec`, `ToolHost`, `KitState`, ... (`.`) | Public types (docs/runtime-kits.md §5.2) |
|
|
407
409
|
| `GatewayMethods`, `GatewayMethod`, `GatewayParams`, `GatewayResult`, `GatewayEventName`, `GatewayEventPayload` (`.`) | The generated pass-through tables, so an app can write its own generic wrapper over `call` / `onEvent` once |
|
|
@@ -440,11 +442,42 @@ durably revokes its recorded subtree, closes parked gates/active calls, then ask
|
|
|
440
442
|
revokes its subtree. An engine resume with a different run ID, or a dispatch with no retained receipt, is not
|
|
441
443
|
silently granted authority. No prefix grants or `allowOnce` bypass are used.
|
|
442
444
|
|
|
443
|
-
**
|
|
444
|
-
|
|
445
|
+
**Targets keep their own sign-in:** a target retains its own engine credentials, persona, workspace and history; a
|
|
446
|
+
member's own helpers run on the member's account (below). This option never copies credentials or gives helpers
|
|
447
|
+
another person's account. Legacy
|
|
445
448
|
in-flight jobs may retain their exact old session keys and approval owners; all new helper work uses the new
|
|
446
449
|
mapping. Never copy agent SQLite or replay old sent actions. See the [binding contract](../../docs/runtime-kits.md#native-delegated-tool-admission-opt-in).
|
|
447
450
|
|
|
451
|
+
## Helpers on a member's account
|
|
452
|
+
|
|
453
|
+
A member can keep several helpers, each with its own persona and its own conversations, all on one account the
|
|
454
|
+
member already signed in. Nothing is copied and nobody signs in again: a helper's runs go to the engine agent that
|
|
455
|
+
already holds that account, under the helper's own session keys, with the helper's persona as the system text.
|
|
456
|
+
|
|
457
|
+
```ts
|
|
458
|
+
import { OpenClawKit, HelperError } from '@byokit/openclaw';
|
|
459
|
+
|
|
460
|
+
declare const kit: OpenClawKit;
|
|
461
|
+
const research = await kit.ensureHelper('umer', 'research', { account: 'openai', persona: 'You are Umer\'s research helper.' });
|
|
462
|
+
// research.sessionPrefix === 'agent:umer:h-research:'
|
|
463
|
+
const end = await kit.run({ member: 'umer', helper: 'research', sessionKey: `${research.sessionPrefix}main`,
|
|
464
|
+
message: 'Summarise the notes' });
|
|
465
|
+
try {
|
|
466
|
+
await kit.run({ member: 'umer', helper: 'ghost', sessionKey: 'agent:umer:h-ghost:main', message: 'hi' });
|
|
467
|
+
} catch (e) {
|
|
468
|
+
if (e instanceof HelperError) console.log(e.code, end.ok); // 'unknown_helper', before any request
|
|
469
|
+
}
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
`account` is one of `kit.accounts(member)`, the API-key lane (`'<provider>.key'`) included; a key-lane helper's run
|
|
473
|
+
names that account, as any paid run does. A helper's run is bound to its account (pick, rests and locks apply), and
|
|
474
|
+
`RunRef.helper` names it on `ToolHost.gate` and `call`; its approvals are the member's. The persona is the run's whole
|
|
475
|
+
system text: a helper run refuses `system`. Refusals are a typed `HelperError` before any engine request: `invalid`
|
|
476
|
+
(ids, persona, `system`), `unknown_account`, `bound` (another account), `unknown_helper` (not ensured, or its account
|
|
477
|
+
was removed: never moved to another sign-in) and `session` (a key outside the helper, a helper key without `helper`,
|
|
478
|
+
or `move` of one). Helpers share their account's engine agent (its id and workspace files); a helper gains no tool,
|
|
479
|
+
builtin or key prefix the member's run would not. See [Helpers](../../docs/runtime-kits.md#583-helpers).
|
|
480
|
+
|
|
448
481
|
## Owned browser (unqualified handoff)
|
|
449
482
|
|
|
450
483
|
`browser: { executablePath, members }` opts into isolated member Chromium profiles. It requires a closed
|
package/dist/engine.d.ts
CHANGED
|
@@ -62,6 +62,14 @@ export declare class Engine {
|
|
|
62
62
|
output: string;
|
|
63
63
|
}>;
|
|
64
64
|
private get path();
|
|
65
|
+
private runOwned;
|
|
66
|
+
private composioMcpLogin?;
|
|
67
|
+
private composioMcpContext;
|
|
68
|
+
beginComposioMcpLogin(): Promise<{
|
|
69
|
+
url: string;
|
|
70
|
+
}>;
|
|
71
|
+
completeComposioMcpLogin(): Promise<void>;
|
|
72
|
+
signOutComposio(): Promise<void>;
|
|
65
73
|
private get claudeFolder();
|
|
66
74
|
doctorContext(): {
|
|
67
75
|
entry: string;
|
package/dist/engine.js
CHANGED
|
@@ -341,6 +341,115 @@ export class Engine {
|
|
|
341
341
|
});
|
|
342
342
|
}
|
|
343
343
|
get path() { return ['/usr/bin', '/bin', ...this.o.enginePath ?? []].join(':'); }
|
|
344
|
+
async runOwned(binary, args, env, timeoutMs, input) {
|
|
345
|
+
return new Promise((resolve, reject) => {
|
|
346
|
+
const child = spawn(binary, args, { cwd: this.root, env, stdio: ['pipe', 'pipe', 'pipe'] });
|
|
347
|
+
let output = '', timedOut = false;
|
|
348
|
+
this.started.add(child);
|
|
349
|
+
const keep = (chunk) => { output = (output + chunk.toString()).slice(-65_536); };
|
|
350
|
+
child.stdout.on('data', keep);
|
|
351
|
+
child.stderr.on('data', keep);
|
|
352
|
+
const timer = setTimeout(() => { timedOut = true; child.kill('SIGKILL'); }, timeoutMs);
|
|
353
|
+
child.once('error', error => { clearTimeout(timer); this.started.forget(child); reject(error); });
|
|
354
|
+
child.once('close', code => { clearTimeout(timer); this.started.forget(child); resolve({ code: timedOut ? null : code, output }); });
|
|
355
|
+
child.stdin.end(input);
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
composioMcpLogin;
|
|
359
|
+
composioMcpContext() {
|
|
360
|
+
const { entry, env: inherited } = this.doctorContext();
|
|
361
|
+
const home = join(this.root, 'home');
|
|
362
|
+
return { entry, env: { ...inherited, HOME: home, XDG_CONFIG_HOME: join(home, '.config'),
|
|
363
|
+
XDG_DATA_HOME: join(home, '.local/share'), XDG_STATE_HOME: join(home, '.local/state') } };
|
|
364
|
+
}
|
|
365
|
+
beginComposioMcpLogin() {
|
|
366
|
+
if (this.composioMcpLogin)
|
|
367
|
+
throw new Error('Composio MCP sign-in is already pending.');
|
|
368
|
+
const { entry, env } = this.composioMcpContext();
|
|
369
|
+
const child = spawn(process.execPath, [entry, 'mcp', 'login', 'composio'], {
|
|
370
|
+
cwd: join(this.root, 'home'), env, stdio: ['ignore', 'pipe', 'pipe'],
|
|
371
|
+
});
|
|
372
|
+
this.started.add(child);
|
|
373
|
+
let output = '', urlFound = false;
|
|
374
|
+
let resolveUrl, rejectUrl;
|
|
375
|
+
let resolveDone;
|
|
376
|
+
const url = new Promise((resolve, reject) => { resolveUrl = resolve; rejectUrl = reject; });
|
|
377
|
+
const done = new Promise(resolve => { resolveDone = resolve; });
|
|
378
|
+
const timer = setTimeout(() => { child.kill('SIGKILL'); rejectUrl(new Error('OpenClaw MCP login did not return an authorization URL.')); }, 30_000);
|
|
379
|
+
const capture = (chunk) => {
|
|
380
|
+
output = (output + chunk.toString()).slice(-65_536);
|
|
381
|
+
const raw = output.match(/https:\/\/[^\s"'<>]+/)?.[0];
|
|
382
|
+
if (!raw || urlFound)
|
|
383
|
+
return;
|
|
384
|
+
try {
|
|
385
|
+
const parsed = new URL(raw);
|
|
386
|
+
if (parsed.protocol !== 'https:' || !['connect.composio.dev', 'login.composio.dev'].includes(parsed.hostname)
|
|
387
|
+
|| parsed.username || parsed.password)
|
|
388
|
+
throw new Error('OpenClaw returned an unsafe Composio OAuth URL.');
|
|
389
|
+
urlFound = true;
|
|
390
|
+
clearTimeout(timer);
|
|
391
|
+
resolveUrl(parsed.href);
|
|
392
|
+
}
|
|
393
|
+
catch (error) {
|
|
394
|
+
clearTimeout(timer);
|
|
395
|
+
rejectUrl(error);
|
|
396
|
+
child.kill('SIGKILL');
|
|
397
|
+
}
|
|
398
|
+
};
|
|
399
|
+
child.stdout.on('data', capture);
|
|
400
|
+
child.stderr.on('data', capture);
|
|
401
|
+
child.once('error', error => {
|
|
402
|
+
clearTimeout(timer);
|
|
403
|
+
rejectUrl(error);
|
|
404
|
+
this.started.forget(child);
|
|
405
|
+
if (this.composioMcpLogin?.done === done && !urlFound)
|
|
406
|
+
this.composioMcpLogin = undefined;
|
|
407
|
+
resolveDone({ code: null, output });
|
|
408
|
+
});
|
|
409
|
+
child.once('close', code => {
|
|
410
|
+
clearTimeout(timer);
|
|
411
|
+
this.started.forget(child);
|
|
412
|
+
if (!urlFound) {
|
|
413
|
+
rejectUrl(new Error(`OpenClaw MCP login exited before authorization URL (exit ${code}).`));
|
|
414
|
+
if (this.composioMcpLogin?.done === done)
|
|
415
|
+
this.composioMcpLogin = undefined;
|
|
416
|
+
}
|
|
417
|
+
resolveDone({ code, output });
|
|
418
|
+
});
|
|
419
|
+
this.composioMcpLogin = { url, done, child };
|
|
420
|
+
return url.then(value => ({ url: value }));
|
|
421
|
+
}
|
|
422
|
+
async completeComposioMcpLogin() {
|
|
423
|
+
const pending = this.composioMcpLogin;
|
|
424
|
+
if (!pending)
|
|
425
|
+
throw new Error('No Composio MCP sign-in is pending.');
|
|
426
|
+
try {
|
|
427
|
+
await pending.url;
|
|
428
|
+
let timer;
|
|
429
|
+
const timedOut = new Promise(resolve => { timer = setTimeout(() => resolve(null), 10 * 60_000); timer.unref(); });
|
|
430
|
+
const result = await Promise.race([pending.done, timedOut]);
|
|
431
|
+
clearTimeout(timer);
|
|
432
|
+
if (!result) {
|
|
433
|
+
pending.child.kill('SIGKILL');
|
|
434
|
+
await pending.done;
|
|
435
|
+
throw new Error('Composio MCP sign-in timed out.');
|
|
436
|
+
}
|
|
437
|
+
if (result.code !== 0)
|
|
438
|
+
throw new Error(`Composio MCP sign-in did not complete (OpenClaw exit ${result.code}).`);
|
|
439
|
+
}
|
|
440
|
+
finally {
|
|
441
|
+
if (this.composioMcpLogin?.done === pending.done)
|
|
442
|
+
this.composioMcpLogin = undefined;
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
async signOutComposio() {
|
|
446
|
+
if (this.composioMcpLogin)
|
|
447
|
+
throw new Error('Cannot sign out while Composio MCP sign-in is pending.');
|
|
448
|
+
const { entry, env } = this.composioMcpContext();
|
|
449
|
+
const result = await this.runOwned(process.execPath, [entry, 'mcp', 'logout', 'composio'], env, 30_000);
|
|
450
|
+
if (result.code !== 0)
|
|
451
|
+
throw new Error(`Composio MCP sign-out failed (OpenClaw exit ${result.code}).`);
|
|
452
|
+
}
|
|
344
453
|
// The engine's own Claude folder: the fallback login writes it and, only with claudeFallback, the gateway reads it.
|
|
345
454
|
get claudeFolder() { return join(this.root, 'home', '.claude'); }
|
|
346
455
|
doctorContext() {
|
|
@@ -350,7 +459,8 @@ export class Engine {
|
|
|
350
459
|
OPENCLAW_STATE_DIR: join(this.root, 'state'), OPENCLAW_CONFIG_PATH: join(this.root, 'openclaw.json'),
|
|
351
460
|
// Upstream eagerly imports/refreshes Codex OAuth. Only the CLI may see the person's login.
|
|
352
461
|
CODEX_HOME: join(home, '.codex'),
|
|
353
|
-
XDG_CACHE_HOME: join(home, '.cache'),
|
|
462
|
+
XDG_CACHE_HOME: join(home, '.cache'),
|
|
463
|
+
...this.claudeFallback ? { CLAUDE_CONFIG_DIR: this.claudeFolder } : {},
|
|
354
464
|
TMPDIR: join(this.root, 'tmp'), npm_config_cache: join(this.root, 'npm-cache'), npm_config_userconfig: join(this.root, 'npmrc'),
|
|
355
465
|
OPENCLAW_NO_RESPAWN: '1', OPENCLAW_SKIP_CHANNELS: '1', OPENCLAW_DISABLE_BONJOUR: '1',
|
|
356
466
|
OPENCLAW_EXEC_SHELL_SNAPSHOT: '0', OPENCLAW_LOAD_SHELL_ENV: '0', OPENCLAW_GATEWAY_TOKEN: this.token,
|
|
@@ -679,6 +789,7 @@ export class Engine {
|
|
|
679
789
|
await this.starting?.catch(() => { });
|
|
680
790
|
const child = this.child;
|
|
681
791
|
await this.started.terminate(delay);
|
|
792
|
+
this.composioMcpLogin = undefined;
|
|
682
793
|
if (child && child.exitCode === null && child.signalCode === null)
|
|
683
794
|
throw new Error('engine did not stop; credential store still in use');
|
|
684
795
|
this.child = undefined;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type Binding } from './accounts.ts';
|
|
2
|
+
import type { Account, AccountId, Helper, Member } from './types.ts';
|
|
3
|
+
export declare const HELPER_ID: RegExp;
|
|
4
|
+
/** A session key tail of some helper: only a run naming that helper speaks there. */
|
|
5
|
+
export declare const HELPER_TAIL: RegExp;
|
|
6
|
+
export type HelperErrorCode = 'invalid' | 'unknown_account' | 'bound' | 'unknown_helper' | 'session';
|
|
7
|
+
/** A refused helper request, before any run: `code` says which rule refused it. */
|
|
8
|
+
export declare class HelperError extends Error {
|
|
9
|
+
readonly code: HelperErrorCode;
|
|
10
|
+
constructor(code: HelperErrorCode, message: string);
|
|
11
|
+
}
|
|
12
|
+
export declare function createHelpers(ctx: {
|
|
13
|
+
root: string;
|
|
14
|
+
list(member: Member): Promise<Account[]>;
|
|
15
|
+
}): {
|
|
16
|
+
ensure(member: Member, helper: string, o: {
|
|
17
|
+
account: AccountId;
|
|
18
|
+
persona: string;
|
|
19
|
+
}): Promise<Helper>;
|
|
20
|
+
resolve(member: Member, helper: string): {
|
|
21
|
+
agentId: string;
|
|
22
|
+
binding: Binding;
|
|
23
|
+
persona: string;
|
|
24
|
+
};
|
|
25
|
+
};
|
package/dist/helpers.js
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// Helpers (5.8.3): a member's personas on one account the member already signed in. A helper is its own session
|
|
2
|
+
// namespace `agent:<member>:h-<helper>:` run on the agent holding that account, with its own persona as the run's system
|
|
3
|
+
// text: the pin shares no sign-in between agents (a shared agentDir is refused, authInheritance is global), so no agent
|
|
4
|
+
// is made and nothing is copied. Its record is a kit-owned file; the engine is never asked.
|
|
5
|
+
import { mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
|
|
6
|
+
import { join } from 'node:path';
|
|
7
|
+
import { loadIndex } from './accounts.js';
|
|
8
|
+
import { KEY_PREFIX, MEMBER_ID } from './members.js';
|
|
9
|
+
export const HELPER_ID = /^[a-z][a-z0-9-]{0,15}$/;
|
|
10
|
+
/** A session key tail of some helper: only a run naming that helper speaks there. */
|
|
11
|
+
export const HELPER_TAIL = /^h-[a-z][a-z0-9-]{0,15}:/;
|
|
12
|
+
const PERSONA_MAX = 8192;
|
|
13
|
+
/** A refused helper request, before any run: `code` says which rule refused it. */
|
|
14
|
+
export class HelperError extends Error {
|
|
15
|
+
code;
|
|
16
|
+
constructor(code, message) {
|
|
17
|
+
super(message);
|
|
18
|
+
this.name = 'HelperError';
|
|
19
|
+
this.code = code;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
export function createHelpers(ctx) {
|
|
23
|
+
const valid = (member, helper) => typeof member === 'string' && MEMBER_ID.test(member)
|
|
24
|
+
&& !member.startsWith(KEY_PREFIX) && typeof helper === 'string' && HELPER_ID.test(helper);
|
|
25
|
+
const path = (member, helper) => join(ctx.root, 'helpers', member, `${helper}.json`);
|
|
26
|
+
const read = (member, helper) => {
|
|
27
|
+
try {
|
|
28
|
+
const saved = JSON.parse(readFileSync(path(member, helper), 'utf8'));
|
|
29
|
+
return saved?.v === 1 && typeof saved.account === 'string' && typeof saved.agent === 'string'
|
|
30
|
+
&& typeof saved.persona === 'string' ? saved : undefined;
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
const view = (member, helper, saved) => ({ member, helper, account: saved.account, agentId: saved.agent, sessionPrefix: `agent:${member}:h-${helper}:` });
|
|
37
|
+
return {
|
|
38
|
+
async ensure(member, helper, o) {
|
|
39
|
+
const persona = o?.persona;
|
|
40
|
+
// The persona is the helper's whole system text: bounded, plain text (newlines and tabs only).
|
|
41
|
+
if (!valid(member, helper) || typeof persona !== 'string' || !persona.trim() || persona.length > PERSONA_MAX
|
|
42
|
+
|| /[\u0000-\u0008\u000b-\u001f\u007f]/.test(persona))
|
|
43
|
+
throw new HelperError('invalid', `invalid helper: ${String(member)}/${String(helper)}`);
|
|
44
|
+
if (!(await ctx.list(member)).some((row) => row.id === o.account))
|
|
45
|
+
throw new HelperError('unknown_account', `unknown account: ${String(o.account)}`);
|
|
46
|
+
const agent = loadIndex(ctx.root, member).file.accounts[o.account]?.agent;
|
|
47
|
+
if (!agent)
|
|
48
|
+
throw new HelperError('unknown_account', `unknown account: ${o.account}`);
|
|
49
|
+
const before = read(member, helper);
|
|
50
|
+
if (before && (before.account !== o.account || before.agent !== agent))
|
|
51
|
+
throw new HelperError('bound', `helper ${helper} uses another account`);
|
|
52
|
+
const saved = { v: 1, account: o.account, agent, persona };
|
|
53
|
+
const file = path(member, helper);
|
|
54
|
+
mkdirSync(join(file, '..'), { recursive: true, mode: 0o700 });
|
|
55
|
+
writeFileSync(`${file}.tmp`, JSON.stringify(saved), { mode: 0o600 });
|
|
56
|
+
renameSync(`${file}.tmp`, file);
|
|
57
|
+
return view(member, helper, saved);
|
|
58
|
+
},
|
|
59
|
+
// A run's helper, fail closed: ensured by this kit, on an account the member's index still names on the same agent.
|
|
60
|
+
// A removed account leaves the helper unusable; it never moves to another sign-in.
|
|
61
|
+
resolve(member, helper) {
|
|
62
|
+
const saved = valid(member, helper) ? read(member, helper) : undefined;
|
|
63
|
+
const entry = saved && loadIndex(ctx.root, member).file.accounts[saved.account];
|
|
64
|
+
if (!saved || entry?.agent !== saved.agent)
|
|
65
|
+
throw new HelperError('unknown_helper', `unknown helper: ${String(helper)}`);
|
|
66
|
+
return { agentId: saved.agent, persona: saved.persona,
|
|
67
|
+
binding: saved.agent === member ? { agentId: member } : { agentId: saved.agent, id: saved.account } };
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
}
|
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, 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, 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';
|
|
@@ -7,6 +7,7 @@ export { OpenClawKit, type KitOptions, type RetainedLogin } from './kit.ts';
|
|
|
7
7
|
export type { AddKeyHandle, AddKeyResult, KeyFailureReason } from './keys.ts';
|
|
8
8
|
export { stateWords, toAccountView, words, type AccountView, type WordKey } from './words.ts';
|
|
9
9
|
export { chooseAccount, consider, resolveSelection, type RoomOf } from './pick.ts';
|
|
10
|
+
export { HelperError, type HelperErrorCode } from './helpers.ts';
|
|
10
11
|
export { AuthStoreUnreadableError, AuthStoreSealSizeError } from './auth-store.ts';
|
|
11
12
|
export { EngineAlreadyRunningError } from './engine-status.ts';
|
|
12
13
|
export { PluginInstallError, type PluginInstallFailure } from './plugins.ts';
|
package/dist/index.js
CHANGED
|
@@ -4,6 +4,7 @@ export { ENGINE_VERSION, OPERATOR_SCOPES, PROTOCOL_VERSION } from './constants.j
|
|
|
4
4
|
export { OpenClawKit } from './kit.js';
|
|
5
5
|
export { stateWords, toAccountView, words } from './words.js';
|
|
6
6
|
export { chooseAccount, consider, resolveSelection } from './pick.js';
|
|
7
|
+
export { HelperError } from './helpers.js';
|
|
7
8
|
export { AuthStoreUnreadableError, AuthStoreSealSizeError } from './auth-store.js';
|
|
8
9
|
export { EngineAlreadyRunningError } from './engine-status.js';
|
|
9
10
|
export { PluginInstallError } from './plugins.js';
|
package/dist/kit.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { SealingAdapter } from '@byokit/secrets';
|
|
|
2
2
|
import { type AddKeyHandle } from './keys.ts';
|
|
3
3
|
import { type RouteView } from './routes.ts';
|
|
4
4
|
import type { BrowserHost, BrowserOptions } from './browser.ts';
|
|
5
|
-
import type { Account, AccountId, AccountPick, Approval, CallOptions, Decision, Defaults, GatewayMethod, GatewayParams, GatewayResult, GatewayTransport, Hello, KitState, KitEventName, KitEventPayload, LearningCapture, LearningMode, Member, ModelInfo, MoveResult, Room, RunEnd, RunEvent, RunRef, RunSelection, RunSpec, OutputSchema, SchemaOutput, SignInOptions, SignInView, ChannelChat, TelegramChannel, ToolHost, ToolSpec } from './types.ts';
|
|
5
|
+
import type { Account, AccountId, AccountPick, Approval, CallOptions, Decision, Defaults, GatewayMethod, GatewayParams, GatewayResult, GatewayTransport, Hello, Helper, KitState, KitEventName, KitEventPayload, LearningCapture, LearningMode, Member, ModelInfo, MoveResult, Room, RunEnd, RunEvent, RunRef, RunSelection, RunSpec, OutputSchema, SchemaOutput, SignInOptions, SignInView, ChannelChat, TelegramChannel, ToolHost, ToolSpec } from './types.ts';
|
|
6
6
|
export type KitOptions = {
|
|
7
7
|
stateDir: string;
|
|
8
8
|
authSeal?: SealingAdapter;
|
|
@@ -68,6 +68,7 @@ export declare class OpenClawKit {
|
|
|
68
68
|
attach(member: Member, runId: string, fromSeq: number, signal: AbortSignal): AsyncIterable<import("./types.ts").RunFrame>;
|
|
69
69
|
private installs;
|
|
70
70
|
private accountsCtl?;
|
|
71
|
+
private helpersCtl?;
|
|
71
72
|
private readonly bridge;
|
|
72
73
|
private readonly delegations;
|
|
73
74
|
private readonly keys;
|
|
@@ -129,8 +130,21 @@ export declare class OpenClawKit {
|
|
|
129
130
|
agentId: string;
|
|
130
131
|
workspace: string;
|
|
131
132
|
}>;
|
|
133
|
+
/** One of the member's helpers (5.8.3): its own persona and sessions on the member's `account`, no new sign-in. */
|
|
134
|
+
ensureHelper(member: Member, helper: string, o: {
|
|
135
|
+
account: AccountId;
|
|
136
|
+
persona: string;
|
|
137
|
+
}): Promise<Helper>;
|
|
132
138
|
private signInCtx;
|
|
133
139
|
routes(): RouteView[];
|
|
140
|
+
/** Start Composio's hosted MCP OAuth flow through the pinned engine; return the URL for the app to open. */
|
|
141
|
+
beginComposioSignIn(): Promise<{
|
|
142
|
+
url: string;
|
|
143
|
+
}>;
|
|
144
|
+
/** Wait for OpenClaw's native MCP OAuth callback and token exchange to finish. */
|
|
145
|
+
completeComposioSignIn(): Promise<void>;
|
|
146
|
+
/** Clear the engine-owned native MCP OAuth credential for Composio. */
|
|
147
|
+
signOutComposio(): Promise<void>;
|
|
134
148
|
/** B7 (5.7): install one external provider plugin a `needs_plugin` route names into this kit's own engine, at its
|
|
135
149
|
* pin; resolves once the restarted gateway lists it enabled at that version. Discovery never installs. Serialized:
|
|
136
150
|
* concurrent installs would race the allow-list update and double-spawn the installer into one engine tree. */
|
package/dist/kit.js
CHANGED
|
@@ -14,6 +14,7 @@ import { Engine, canonicalStateDir, refusedOnly } from './engine.js';
|
|
|
14
14
|
import { AuthStoreSealSizeError } from './auth-store.js';
|
|
15
15
|
import { gatewayTransport } from './transport.js';
|
|
16
16
|
import { createMembers } from './members.js';
|
|
17
|
+
import { createHelpers, HELPER_TAIL, HelperError } from './helpers.js';
|
|
17
18
|
import { createAccounts, memberOfAccount } from './accounts.js';
|
|
18
19
|
import { createLocks } from './locks.js';
|
|
19
20
|
import { keyAgentId, KEY_PREFIX, MEMBER_ID } from './members.js';
|
|
@@ -448,6 +449,7 @@ export class OpenClawKit {
|
|
|
448
449
|
}
|
|
449
450
|
installs = Promise.resolve();
|
|
450
451
|
accountsCtl;
|
|
452
|
+
helpersCtl;
|
|
451
453
|
// One bridge and one approval surface per kit: listeners and run registrations survive reconnects (B3).
|
|
452
454
|
// Only the socket itself is per-connection. Approvals always reaches the live transport through this.request().
|
|
453
455
|
bridge;
|
|
@@ -833,6 +835,7 @@ export class OpenClawKit {
|
|
|
833
835
|
ensure: (member) => this.ensureMember(member), locks: this.locks,
|
|
834
836
|
signIn: (agentId, o, on, fresh) => startSignIn({ ...this.signInCtx(), ensure: async () => ({ agentId }), fresh }, agentId, o, on),
|
|
835
837
|
routes: this.routeMap, log: this.o.log, reseal: () => this.engine.authStore.reseal() });
|
|
838
|
+
this.helpersCtl = createHelpers({ root: this.engine.root, list: (member) => this.accounts(member) });
|
|
836
839
|
// The fail-closed tool bridge feeds the kit's single Approval surface (5.9, O5).
|
|
837
840
|
await Promise.race([this.bridge.start(), exit]);
|
|
838
841
|
this.off.push(transport.onEvent((e) => this.approvalsCtl.handleEvent(e)));
|
|
@@ -1068,6 +1071,12 @@ export class OpenClawKit {
|
|
|
1068
1071
|
await attaching;
|
|
1069
1072
|
return result;
|
|
1070
1073
|
}
|
|
1074
|
+
/** One of the member's helpers (5.8.3): its own persona and sessions on the member's `account`, no new sign-in. */
|
|
1075
|
+
ensureHelper(member, helper, o) {
|
|
1076
|
+
if (!this.helpersCtl)
|
|
1077
|
+
return Promise.reject(new Error('gateway not ready'));
|
|
1078
|
+
return this.helpersCtl.ensure(member, helper, o);
|
|
1079
|
+
}
|
|
1071
1080
|
// sign-in (5.7): every body here is the module's, with this kit's transport, members and ports.
|
|
1072
1081
|
signInCtx() {
|
|
1073
1082
|
return { request: (method, params, o) => this.request()(method, params, o),
|
|
@@ -1089,6 +1098,17 @@ export class OpenClawKit {
|
|
|
1089
1098
|
routes() {
|
|
1090
1099
|
return routeTable({ plugins: installedPlugins(this.engine.root) });
|
|
1091
1100
|
}
|
|
1101
|
+
/** Start Composio's hosted MCP OAuth flow through the pinned engine; return the URL for the app to open. */
|
|
1102
|
+
async beginComposioSignIn() {
|
|
1103
|
+
await this.patchConfig({ mcp: { servers: { composio: {
|
|
1104
|
+
url: 'https://connect.composio.dev/mcp', transport: 'streamable-http', auth: 'oauth', enabled: true,
|
|
1105
|
+
} } } });
|
|
1106
|
+
return this.engine.beginComposioMcpLogin();
|
|
1107
|
+
}
|
|
1108
|
+
/** Wait for OpenClaw's native MCP OAuth callback and token exchange to finish. */
|
|
1109
|
+
completeComposioSignIn() { return this.engine.completeComposioMcpLogin(); }
|
|
1110
|
+
/** Clear the engine-owned native MCP OAuth credential for Composio. */
|
|
1111
|
+
signOutComposio() { return this.engine.signOutComposio(); }
|
|
1092
1112
|
/** B7 (5.7): install one external provider plugin a `needs_plugin` route names into this kit's own engine, at its
|
|
1093
1113
|
* pin; resolves once the restarted gateway lists it enabled at that version. Discovery never installs. Serialized:
|
|
1094
1114
|
* concurrent installs would race the allow-list update and double-spawn the installer into one engine tree. */
|
|
@@ -1215,6 +1235,8 @@ export class OpenClawKit {
|
|
|
1215
1235
|
move(ref, to) {
|
|
1216
1236
|
if (!this.accountsCtl)
|
|
1217
1237
|
return Promise.reject(new Error('gateway not ready'));
|
|
1238
|
+
if (HELPER_TAIL.test(String(ref.sessionKey).replace(/^agent:[^:]+:/, '')))
|
|
1239
|
+
return Promise.reject(new HelperError('session', 'refused: a helper\'s session stays on its account'));
|
|
1218
1240
|
return this.accountsCtl.move(ref.member, ref.sessionKey, to);
|
|
1219
1241
|
}
|
|
1220
1242
|
/** Sign in one more account: on the member agent while it holds none of the provider, else on a new sealed agent. */
|
|
@@ -1336,6 +1358,7 @@ export class OpenClawKit {
|
|
|
1336
1358
|
bridge: { register: (run, tools, runId) => this.bridge.register(run, tools && this.o.browser ? [...tools, 'request_sign_in'] : tools, runId) },
|
|
1337
1359
|
tools: new Set(this.toolNames()), delegations: this.delegations,
|
|
1338
1360
|
...(this.accountsCtl ? { accounts: this.accountsCtl } : {}),
|
|
1361
|
+
...(this.helpersCtl ? { helpers: this.helpersCtl } : {}),
|
|
1339
1362
|
locks: this.locks,
|
|
1340
1363
|
routes: this.routeMap,
|
|
1341
1364
|
});
|
package/dist/runs.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Bridge } from './bridge.ts';
|
|
2
|
+
import { type createHelpers } from './helpers.ts';
|
|
2
3
|
import type { Delegations } from './delegations.ts';
|
|
3
4
|
import type { createAccounts } from './accounts.ts';
|
|
4
5
|
import type { createLocks } from './locks.ts';
|
|
@@ -40,6 +41,7 @@ export declare function createRuns(ctx: {
|
|
|
40
41
|
}>;
|
|
41
42
|
bridge: Pick<Bridge, 'register'>;
|
|
42
43
|
delegations?: Delegations;
|
|
44
|
+
helpers?: Pick<ReturnType<typeof createHelpers>, 'resolve'>;
|
|
43
45
|
tools: ReadonlySet<string>;
|
|
44
46
|
accounts?: Pick<ReturnType<typeof createAccounts>, 'bind' | 'keyRefusal' | 'pick' | 'rest'>;
|
|
45
47
|
locks?: ReturnType<typeof createLocks>;
|
package/dist/runs.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// Runs: one Gateway run per spec, streamed through typed events, ending in a classified RunEnd (5.8, O8).
|
|
2
2
|
import { randomUUID } from 'node:crypto';
|
|
3
3
|
import { setTimeout as delay } from 'node:timers/promises';
|
|
4
|
+
import { HELPER_TAIL, HelperError } from './helpers.js';
|
|
4
5
|
import { authStatus } from './auth-status.js';
|
|
5
6
|
import { outputSchema } from './output.js';
|
|
6
7
|
import { words } from './words.js';
|
|
@@ -163,6 +164,14 @@ export function createRuns(ctx) {
|
|
|
163
164
|
// of one of the member's account agents is the member's too; binding below checks the index names it.
|
|
164
165
|
if (!spec.sessionKey.startsWith(`agent:${spec.member}:`) && !(ctx.accounts && !keyAgent && spec.sessionKey.startsWith(`agent:${spec.member}--`)))
|
|
165
166
|
throw new Error(`refused: "${spec.sessionKey}" is not a session of member "${spec.member}"`);
|
|
167
|
+
const tail = spec.sessionKey.slice(spec.sessionKey.indexOf(':', 'agent:'.length) + 1);
|
|
168
|
+
// A helper (5.8.3) speaks only under `agent:<member>:h-<helper>:`, with its persona as the whole system text, and
|
|
169
|
+
// only a run naming that helper speaks there. Refused before any request.
|
|
170
|
+
if (spec.helper !== undefined && (!ctx.helpers || keyAgent || spec.system !== undefined))
|
|
171
|
+
throw new HelperError('invalid', `refused: helper ${String(spec.helper)} runs on its own account with its own persona`);
|
|
172
|
+
const helper = spec.helper === undefined ? undefined : ctx.helpers.resolve(spec.member, spec.helper);
|
|
173
|
+
if (helper ? !spec.sessionKey.startsWith(`agent:${spec.member}:h-${spec.helper}:`) : HELPER_TAIL.test(tail))
|
|
174
|
+
throw new HelperError('session', `refused: "${spec.sessionKey}" is not a session of ${helper ? `helper "${spec.helper}"` : 'this run'}`);
|
|
166
175
|
if (spec.delegates) {
|
|
167
176
|
if (spec.register === false || !ctx.delegations)
|
|
168
177
|
throw new Error('delegation requires a registered run');
|
|
@@ -173,16 +182,16 @@ export function createRuns(ctx) {
|
|
|
173
182
|
throw new Error('refused: idempotencyKey must be a non-empty string');
|
|
174
183
|
const idempotencyKey = spec.idempotencyKey ?? randomUUID();
|
|
175
184
|
const output = preparedOutput ?? (spec.schema === undefined ? undefined : outputSchema(spec.schema));
|
|
176
|
-
const system = [spec.system, output?.prompt].filter(Boolean).join('\n\n');
|
|
185
|
+
const system = [helper?.persona ?? spec.system, output?.prompt].filter(Boolean).join('\n\n');
|
|
177
186
|
const picked = spec.model === undefined ? undefined : account(spec.model);
|
|
178
187
|
for (const need of spec.needs ?? [])
|
|
179
188
|
account(need);
|
|
180
189
|
for (const tool of spec.tools ?? [])
|
|
181
190
|
if (!ctx.tools.has(tool))
|
|
182
191
|
throw new Error(`refused: "${tool}" is not one of this kit's tools`);
|
|
183
|
-
const tail = spec.sessionKey.slice(spec.sessionKey.indexOf(':', 'agent:'.length) + 1);
|
|
184
192
|
// Runs step 1 (5.15): every run first finds the account its session is bound to, the API-key lane included.
|
|
185
|
-
|
|
193
|
+
// A helper is bound to the agent whose sign-in it reads, as a conversation held there is.
|
|
194
|
+
const binding = helper?.binding ?? (ctx.accounts ? await ctx.accounts.bind(spec.member, spec.sessionKey) : undefined);
|
|
186
195
|
// Step 2 on the API-key lane: its own lane binding continues; any other binding is refused.
|
|
187
196
|
if (keyAgent && ctx.accounts && binding) {
|
|
188
197
|
const message = await ctx.accounts.keyRefusal(spec.member, binding);
|
|
@@ -191,7 +200,7 @@ export function createRuns(ctx) {
|
|
|
191
200
|
}
|
|
192
201
|
// A run that waited for its agent's lock across a move (5.15) binds again under it: a changed binding means its
|
|
193
202
|
// session went to another account, so it ends before any event and the caller's retry lands there.
|
|
194
|
-
const moved = async () => !!binding && (await ctx.accounts.bind(spec.member, spec.sessionKey))?.agentId !== binding.agentId;
|
|
203
|
+
const moved = async () => !helper && !!binding && (await ctx.accounts.bind(spec.member, spec.sessionKey))?.agentId !== binding.agentId;
|
|
195
204
|
const movedEnd = { ok: false, kind: 'other', message: words('run.moved') };
|
|
196
205
|
// Step 3: with `account` and `needs` both absent, on a key no account agent holds (or an API-key run, which binds only
|
|
197
206
|
// to its lane), exactly the run on the agent it always was (a bound key under its shared lock).
|
|
@@ -325,8 +334,9 @@ export function createRuns(ctx) {
|
|
|
325
334
|
}
|
|
326
335
|
});
|
|
327
336
|
try {
|
|
328
|
-
|
|
329
|
-
|
|
337
|
+
const ref = { sessionKey, member: spec.member, ...(spec.helper !== undefined ? { helper: spec.helper } : {}) };
|
|
338
|
+
releaseDelegation = spec.delegates ? ctx.delegations.register(ref, idempotencyKey, spec.tools, spec.delegates, spec.sessionKey) : undefined;
|
|
339
|
+
release = spec.register !== false ? ctx.bridge.register(ref, spec.tools, idempotencyKey) : undefined;
|
|
330
340
|
if (picked && !chosen) {
|
|
331
341
|
// The picked account must be the one called: check it is signed in for this member before the run, since
|
|
332
342
|
// the engine would otherwise fail the run only after admitting it. An explicit provider/model is strict on
|
package/dist/types.d.ts
CHANGED
|
@@ -19,6 +19,7 @@ export interface ToolSpec {
|
|
|
19
19
|
export interface RunRef {
|
|
20
20
|
sessionKey: string;
|
|
21
21
|
member: Member;
|
|
22
|
+
helper?: string;
|
|
22
23
|
meta?: unknown;
|
|
23
24
|
runId?: string;
|
|
24
25
|
agentId?: string;
|
|
@@ -31,6 +32,15 @@ export interface RunRef {
|
|
|
31
32
|
runId: string;
|
|
32
33
|
};
|
|
33
34
|
}
|
|
35
|
+
/** One of a member's helpers (5.8.3): its own persona and sessions (`sessionPrefix`) on `account`, run by `agentId`, the
|
|
36
|
+
* engine agent already holding that sign-in. */
|
|
37
|
+
export type Helper = {
|
|
38
|
+
member: Member;
|
|
39
|
+
helper: string;
|
|
40
|
+
account: AccountId;
|
|
41
|
+
agentId: string;
|
|
42
|
+
sessionPrefix: string;
|
|
43
|
+
};
|
|
34
44
|
export type DelegatedRun = RunRef & Required<Pick<RunRef, 'runId' | 'agentId' | 'parent' | 'root'>>;
|
|
35
45
|
export type DelegatedEvent = {
|
|
36
46
|
type: 'admitted' | 'event' | 'ended';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@byokit/openclaw",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.20.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",
|