@byokit/openclaw 0.6.2 → 0.6.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 CHANGED
@@ -2,6 +2,22 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.6.3 (2026-10-07)
6
+
7
+ - Dependency update: pins @byokit/relay 0.5.3.
8
+ - Dependency update: pins @byokit/link 0.7.1.
9
+
10
+ - FIX: Route `anthropic-cli` now reports provider `claude-cli`, the id `signedIn`, `providers()` and runs already use for a Claude Code login, so `kit.signedIn(member, route.provider)` answers true once Claude Code is signed in. **Deprecated:** the old provider `anthropic` on this route; it stays readable as `route.deprecatedProvider` (`'anthropic'`) through 0.7.x and is removed in 0.8.0. Match this route by `provider === 'claude-cli'` (or by `choice === 'anthropic-cli'`). The route id `anthropic:cli:anthropic-cli` is kept through 0.7.x and becomes `claude-cli:cli:anthropic-cli` in 0.8.0; choice `anthropic-cli` and the `{ provider: 'claude-cli', via: 'browser' }` link selector are unchanged. `anthropic` remains the API-billed provider only: a Claude Code login never makes `signedIn(member, 'anthropic')` true.
11
+ FIX: Engine start on macOS no longer sticks on a dead-but-unreaped guard process. `pidAlive` detects zombies portably — `ps` state where there is no `/proc` — instead of Linux-only `/proc`, so a stale guard reads as stopped and start can recover. A live pid still reads as owned, an invalid guard still reads as ambiguous, and anything unexpected from the probe keeps the guard. Linux answers are unchanged.
12
+ - FIX: Code sign-ins whose pinned engine prints the code only into a note now hand the caller the code, link, expiry (`expiresAt`) and instructions (`message`) in the same `SignInView` shape as structured codes, and wait out device approval instead of failing with `gateway request timeout for wizard.next` after two minutes.
13
+ - The O16 day-usage engine tests no longer fail when a run straddles UTC midnight: a case that could still be running at midnight starts just after it, so the day (and month) it reads is the day its engine stamps charges in.
14
+ - The `relay` option takes anything with `RelayClient`'s `notify`, so a host can hand the kit a relay it opens after `Host.open` (the client needs the open host). A `RelayClient` still fits; behaviour is unchanged.
15
+ - FIX: The sealed credential store is written as `v: 1` again. 0.6.2's `v: 2` tag made every earlier kit (through 0.6.1) refuse an intact store with `invalid sealed credential store`, so a host rolled back to an earlier kit could not start and lost its saved sign-in. `v: 2` stores still restore and re-seal as `v: 1`.
16
+ - A saved store the key cannot open (a different key, damaged or tampered bytes) or whose payload is not a credential snapshot no longer blocks engine start: it is kept as `auth-store.sealed.unreadable-<ms>` (never overwritten or deleted), the engine starts signed out, and the kit reports `{ phase: 'ready', why: 'sign-in-reset' }` with the words "Your saved sign-in couldn't be opened, so it was kept aside. Sign in again." until a sign-in through the kit completes.
17
+ - FIX: Every wizard `text` step now reaches the caller as `SignInView.prompt`, not only sensitive ones. A non-sensitive step shows the engine's question as written (`github-copilot-enterprise` asks for the Enterprise domain first), so the sign-in no longer waits 15 minutes on a screen with nothing to answer. Once a step is sensitive (or the choice is `setup-token`), the prompt stays the fixed `Sign-in token` label and no gateway prose is shown, as before.
18
+ - FIX: `setup-token` is no longer offered. The pinned Gateway has no app-guided sign-in for it and refuses every attempt with "That provider setup is not available on this Gateway.", so it is listed with `readiness: 'no_upstream_flow'`, the link no longer maps `{ provider: 'anthropic', via: 'browser' }` to it. `plugins.allow` now holds the plugin of every default-eligible bundled route rather than only the ready ones, so `anthropic-cli` and `apiKey` keep the `anthropic` plugin allowed without app config.
19
+ - The R1 restart-recovery probe accepts both restart-time gate denials for the unowned recovering session (HEAD's `unknown run` and the published kit's `can't check this action right now`); any other transcript text still fails the denial assertion.
20
+
5
21
  ## 0.6.2 (2026-10-05)
6
22
 
7
23
  FIX: The browser broker now holds its CDP endpoint back until Chromium's initial tab exists, so `/json/list` can never serve an empty browser to an attaching engine on a cold or slow start; a browser that never lists a tab fails the launch honestly instead.
package/README.md CHANGED
@@ -102,6 +102,9 @@ just to read a final result: on cache eviction that would start another run. App
102
102
  recovery. Typed `kit.call('agent', ...)` remains exact upstream pass-through. Details and real-engine proof are
103
103
  in [the run contract](../../docs/runtime-kits.md#58-runs-members-and-streams).
104
104
 
105
+ Every code route shows its code the same way: a waiting view carries `code`, `url`, `expiresAt` (epoch ms, when
106
+ the engine gives a lifetime) and the engine's own instructions in `message`, including routes whose pinned engine
107
+ prints the code only into a note's text.
105
108
  Device approval can take longer than two minutes. The kit waits through the engine's advertised code lifetime
106
109
  (`expiresInMinutes` on the pin, or `expires_in` seconds when supplied), including progress pulls. If the engine
107
110
  supplies no lifetime, it owns the deadline. Ordinary wizard requests keep their 120-second timeout.
@@ -109,7 +112,7 @@ supplies no lifetime, it owns the deadline. Ordinary wizard requests keep their
109
112
  `done` returns a typed `SignInView`: `why: 'expired'` for an expired code, `why: 'declined'` for cancellation,
110
113
  with a plain sentence in `error`. `SignInOptions` is exported from the host entry.
111
114
 
112
- For a subscription setup-token or any sensitive wizard text step, pass the token once through the same
115
+ For any sensitive wizard text step, pass the secret once through the same
113
116
  `signIn.paste(token)` channel and clear the input afterwards. The kit displays only a fixed entry label;
114
117
  subsequent wizard errors, links and codes are withheld, and failures use a fixed message rather than
115
118
  engine text that could echo the token. It is sent only as this member's wizard answer, never in views,
@@ -288,7 +291,6 @@ state: ready | protocol: 4
288
291
  event: {"type":"text","text":"fake: Say hello."}
289
292
  event: {"type":"text","text":"fake: Say hello."}
290
293
  end: {"ok":true,"text":"fake: Say hello.","usage":{"input":10,"output":16,"total":26}}
291
- route: setup-token subscription setup_token
292
294
  route: github-copilot subscription code
293
295
  route: github-copilot-enterprise subscription code
294
296
  route: minimax-global-oauth subscription code
@@ -481,7 +483,9 @@ separately; dual-wrap rotation retains old wrapping keys for backups.
481
483
  Without `authSeal`, engine credentials remain plaintext. With it, successful `prepare()` and `stop()` leave
482
484
  only a sealed snapshot, `auth-store.sealed`, for the sealed credential paths; regenerable caches stay on
483
485
  disk unsealed. The adapter authenticates the snapshot
484
- before any restoration; a wrong key, tampering, or a missing adapter rejects. Files restored for the engine
486
+ before any restoration; a missing adapter rejects. A store the key cannot open (a wrong key,
487
+ damaged or tampered bytes) or whose payload is not a credential snapshot is kept aside as
488
+ `auth-store.sealed.unreadable-<ms>` and the engine starts signed out (`why: 'sign-in-reset'`); sign in again. Files restored for the engine
485
489
  have mode 0600 and directories 0700. File symlinks are sealed only when their fully resolved targets are
486
490
  regular files inside the isolated engine root; they restore as regular files at the link paths. Outside-root,
487
491
  dangling and directory symlinks (including loops), sockets, FIFOs and devices are skipped.
@@ -536,4 +540,4 @@ frozen (docs/runtime-kits.md §5).
536
540
 
537
541
  Apache-2.0. See [LICENSE](LICENSE); the bundled engine's MIT text is [engine/OPENCLAW-LICENSE](engine/OPENCLAW-LICENSE).
538
542
 
539
- Claude: `anthropic-cli` uses your own unmodified Claude Code login on this machine (provider `claude-cli`, plugin `anthropic`; subscription billing), with login kept in Claude Code under the kit’s isolated `HOME=<stateDir>/openclaw/home` and `CLAUDE_CONFIG_DIR=<stateDir>/openclaw/home/.claude`; sign in there with `claude auth login`, then call `signIn`. Activation live-tests the route; native sign-out is through Claude Code. `apiKey` is explicit API billing (“API key (billed per use)”, `offer: false`): `signIn` asks for the key through `paste`, then engine activation verifies it. The kit never imports Claude credentials or implements direct Claude.ai OAuth; [Anthropic’s terms](https://code.claude.com/docs/en/legal-and-compliance#authentication-and-credential-use) apply.
543
+ Claude: `anthropic-cli` uses your own unmodified Claude Code login on this machine (provider `claude-cli`, plugin `anthropic`; subscription billing; `kit.signedIn(member, 'claude-cli')` is true once Claude Code is signed in, and `'anthropic'` stays the API-billed provider a Claude Code login never satisfies. Before 0.7.0 this route reported provider `anthropic`; that value is deprecated, kept as `deprecatedProvider` until 0.8.0), with login kept in Claude Code under the kit’s isolated `HOME=<stateDir>/openclaw/home` and `CLAUDE_CONFIG_DIR=<stateDir>/openclaw/home/.claude`; sign in there with `claude auth login`, then call `signIn`. Activation live-tests the route; native sign-out is through Claude Code. `apiKey` is explicit API billing (“API key (billed per use)”, `offer: false`): `signIn` asks for the key through `paste`, then engine activation verifies it. The `setup-token` choice is listed but never offered: the pinned Gateway has no app-guided sign-in for it. The kit never imports Claude credentials or implements direct Claude.ai OAuth; [Anthropic’s terms](https://code.claude.com/docs/en/legal-and-compliance#authentication-and-credential-use) apply.
package/SECURITY.md CHANGED
@@ -28,7 +28,10 @@ resolve with `phase: 'locked'`, do not restore plaintext or launch the engine, a
28
28
  retries after unlock. Opt-in dual wrapping opens through an owner-only host key without prompting;
29
29
  its protection is only as strong as that file, which must stay outside sealed-store backups.
30
30
  Unlocked keyring-only snapshots upgrade atomically under the store's exclusive lock after verifying
31
- the replacement decrypts to identical contents. Other sealing failures still reject.
31
+ the replacement decrypts to identical contents. A store the key cannot authenticate, or whose authentic
32
+ payload is not a snapshot, is never restored, overwritten or deleted: it is kept as
33
+ `auth-store.sealed.unreadable-<ms>` and the engine starts signed out (`why: 'sign-in-reset'`). Other sealing
34
+ failures still reject.
32
35
 
33
36
  ## Secrets at rest and in memory
34
37
 
@@ -2,6 +2,8 @@ import type { SealingAdapter } from '@byokit/secrets';
2
2
  /** True for the regenerable cache paths the collector never seals; tests assert these are all that stay at rest. */
3
3
  export declare function cached(name: string): boolean;
4
4
  export declare class AuthStore {
5
+ /** Set when an unreadable store was kept aside and the engine starts signed out; the kit clears it on sign-in. */
6
+ reset: boolean;
5
7
  private active;
6
8
  private owned;
7
9
  private queue;
@@ -21,6 +23,8 @@ export declare class AuthStore {
21
23
  private acquire;
22
24
  private release;
23
25
  private read;
26
+ /** Never overwrite or delete a store this kit cannot open: keep its bytes aside and start signed out. */
27
+ private setAside;
24
28
  private collect;
25
29
  /** Remove the live credential trees: `state` entirely (it is all credential state, including the
26
30
  * runtime entries the collector skips), and in `home` exactly the sealed paths — regenerable caches
@@ -3,7 +3,7 @@
3
3
  // `home` — never the regenerable tool caches, transcripts and logs a signed-in home accumulates.
4
4
  // Sealing those too once produced a snapshot past the runtime string limit and aborted boot.
5
5
  import { chmodSync, existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, rmdirSync, writeFileSync, closeSync, fsyncSync, openSync } from 'node:fs';
6
- import { dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
6
+ import { basename, dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
7
7
  import { EngineAlreadyRunningError, pidAlive as live } from "./engine-status.js";
8
8
  const encoder = new TextEncoder();
9
9
  const archive = (name) => /^(auth-profiles|auth-state|auth|oauth)\.json\.(migrated-.+|sqlite-import\..+\.bak)$/.test(name)
@@ -89,6 +89,8 @@ function snapshot(text) {
89
89
  return s;
90
90
  }
91
91
  export class AuthStore {
92
+ /** Set when an unreadable store was kept aside and the engine starts signed out; the kit clears it on sign-in. */
93
+ reset = false;
92
94
  active = false;
93
95
  owned = false;
94
96
  queue = Promise.resolve();
@@ -174,8 +176,22 @@ export class AuthStore {
174
176
  regular(this.file);
175
177
  const seal = this.o.seal;
176
178
  const bytes = readFileSync(this.file);
177
- const text = seal.decryptString(bytes);
178
- const saved = snapshot(text);
179
+ let text;
180
+ try {
181
+ text = seal.decryptString(bytes);
182
+ }
183
+ catch (error) {
184
+ if (error?.code !== 'auth-failed')
185
+ throw error;
186
+ return this.setAside('could not be authenticated');
187
+ }
188
+ let saved;
189
+ try {
190
+ saved = snapshot(text);
191
+ }
192
+ catch {
193
+ return this.setAside('is not a credential snapshot');
194
+ }
179
195
  const upgraded = seal.upgrade?.(bytes);
180
196
  if (upgraded) {
181
197
  if (seal.decryptString(Buffer.from(upgraded)) !== text)
@@ -184,8 +200,17 @@ export class AuthStore {
184
200
  }
185
201
  return saved;
186
202
  }
203
+ /** Never overwrite or delete a store this kit cannot open: keep its bytes aside and start signed out. */
204
+ setAside(why) {
205
+ const aside = `${this.file}.unreadable-${Date.now()}`;
206
+ renameSync(this.file, aside);
207
+ syncDir(this.o.root);
208
+ this.reset = true;
209
+ this.o.log?.(`sealed credential store ${why}; kept as ${basename(aside)}; sign in again`);
210
+ return undefined;
211
+ }
187
212
  collect() {
188
- const s = { v: 2, dirs: [], files: [] };
213
+ const s = { v: 1, dirs: [], files: [] };
189
214
  const root = realpathSync(this.o.root);
190
215
  const walk = (path) => {
191
216
  const name = relative(this.o.root, path).split('\\').join('/');
@@ -272,7 +297,7 @@ export class AuthStore {
272
297
  put(this.file, sealed);
273
298
  if (this.o.seal.decryptString(readFileSync(this.file)) !== payload)
274
299
  throw new Error('credential seal verification failed');
275
- if (previous?.v === 1)
300
+ if (previous?.dirs.some(cached))
276
301
  this.o.log?.('sealed credential store re-sealed: tool caches no longer sealed');
277
302
  put(this.cleanup, encoder.encode('1'));
278
303
  this.remove(saved);
package/dist/config.js CHANGED
@@ -56,7 +56,10 @@ export function reconcileConfig(saved, o) {
56
56
  c.plugins.load ??= {};
57
57
  c.plugins.load.paths = [...new Set([...(c.plugins.load.paths ?? []).filter((p) => !p.includes('byokit-openclaw-bridge')), o.pluginDir])];
58
58
  c.plugins.allow = [...new Set([...(c.plugins.allow ?? []), o.pluginId,
59
- ...routes().filter(route => route.offer && route.plugin).map(route => route.plugin)])];
59
+ // Every default-eligible bundled route, not only the ready ones: anthropic-cli waits on its binary and
60
+ // shares its plugin with the explicit apiKey route, so neither may depend on another route being offered.
61
+ ...routes().filter(route => route.offerPolicy === 'default' && !route.needs?.plugin && route.plugin)
62
+ .map(route => route.plugin)])];
60
63
  c.plugins.entries ??= {};
61
64
  c.plugins.entries[o.pluginId] = merge(c.plugins.entries[o.pluginId] ?? {}, { hooks: { timeouts: { before_tool_call: 200_000 } } });
62
65
  if (o.browser) {
@@ -1,4 +1,4 @@
1
- import type { ChildProcess } from 'node:child_process';
1
+ import { type ChildProcess } from 'node:child_process';
2
2
  /** Every process the kit itself spawned, held for shutdown by pid. */
3
3
  export declare class StartedProcesses {
4
4
  private readonly started;
@@ -16,4 +16,4 @@ export declare class EngineAlreadyRunningError extends Error {
16
16
  readonly code = "engine-already-running";
17
17
  constructor();
18
18
  }
19
- export declare function pidAlive(pid: number): boolean;
19
+ export declare function pidAlive(pid: number, platform?: NodeJS.Platform): boolean;
@@ -1,4 +1,5 @@
1
1
  import { readFileSync } from 'node:fs';
2
+ import { spawnSync } from 'node:child_process';
2
3
  /** Every process the kit itself spawned, held for shutdown by pid. */
3
4
  export class StartedProcesses {
4
5
  started = new Set();
@@ -38,20 +39,40 @@ export class EngineAlreadyRunningError extends Error {
38
39
  this.name = 'EngineAlreadyRunningError';
39
40
  }
40
41
  }
41
- export function pidAlive(pid) {
42
+ export function pidAlive(pid, platform = process.platform) {
42
43
  if (!Number.isSafeInteger(pid) || pid < 1)
43
44
  return true; // An invalid guard is ambiguous, never stale.
44
45
  try {
45
46
  process.kill(pid, 0);
46
47
  // An orphan can remain a zombie until its new parent reaps it; it has no writer left.
47
- if (process.platform === 'linux') {
48
+ if (platform === 'linux') {
48
49
  const stat = readFileSync(`/proc/${pid}/stat`, 'utf8');
49
50
  if (stat.slice(stat.lastIndexOf(')') + 2).startsWith('Z '))
50
51
  return false;
51
52
  }
53
+ else if (platform !== 'win32' && psReportsZombie(pid)) {
54
+ // No /proc here (notably macOS): only a definite zombie reads as stale.
55
+ return false;
56
+ }
52
57
  return true;
53
58
  }
54
59
  catch (error) {
55
60
  return error.code !== 'ESRCH';
56
61
  }
57
62
  }
63
+ /**
64
+ * Why ps: `ps -o stat=` exists on macOS and Linux with the same leading-letter state
65
+ * convention, so a `Z` first letter names the same kernel fact the /proc read names on
66
+ * Linux. Linux keeps its /proc read (no subprocess, unchanged answers); every other Unix
67
+ * asks ps. Only positive `Z` evidence flips the answer — a missing ps, an empty reply,
68
+ * or gibberish keeps the guard, so a reused live pid is never stolen.
69
+ */
70
+ function psReportsZombie(pid) {
71
+ try {
72
+ const probed = spawnSync('ps', ['-o', 'stat=', '-p', String(pid)], { encoding: 'utf8', timeout: 5000 });
73
+ return (probed.stdout ?? '').trimStart().startsWith('Z');
74
+ }
75
+ catch {
76
+ return false;
77
+ }
78
+ }
package/dist/engine.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { AuthStore } from './auth-store.ts';
1
2
  import type { KitOptions } from './kit.ts';
2
3
  import type { KitState, ToolSpec } from './types.ts';
3
4
  export type EngineOptions = Pick<KitOptions, 'stateDir' | 'engineDir' | 'npmPath' | 'enginePath' | 'config' | 'appOwnedSessions' | 'installPolicy' | 'log' | 'bridge' | 'authSeal'> & {
@@ -24,7 +25,7 @@ export declare class Engine {
24
25
  private child?;
25
26
  private readonly started;
26
27
  private starting?;
27
- private readonly authStore;
28
+ readonly authStore: AuthStore;
28
29
  private stopping;
29
30
  private credentialsLocked;
30
31
  private repaired;
package/dist/kit.js CHANGED
@@ -736,7 +736,7 @@ export class OpenClawKit {
736
736
  await this.bridge.start();
737
737
  this.off.push(transport.onEvent((e) => this.approvalsCtl.handleEvent(e)));
738
738
  this.failures = 0;
739
- this.setState({ phase: 'ready' });
739
+ this.setState(this.engine.authStore.reset ? { phase: 'ready', why: 'sign-in-reset' } : { phase: 'ready' });
740
740
  if (this.o.browser)
741
741
  await this.checkBrowserTools();
742
742
  // Replays the engine's native approval lists over the now-live transport (N9).
@@ -882,7 +882,14 @@ export class OpenClawKit {
882
882
  return engineProviders(this.signInCtx(), member).then((names) => names.includes(provider)).catch(() => false);
883
883
  }
884
884
  signIn(member, o, on) {
885
- return startSignIn(this.signInCtx(), member, o, on);
885
+ return startSignIn(this.signInCtx(), member, o, (view) => {
886
+ if (view.state === 'done' && this.engine.authStore.reset) {
887
+ this.engine.authStore.reset = false;
888
+ if (this.current.phase === 'ready')
889
+ this.setState({ phase: 'ready' });
890
+ }
891
+ on(view);
892
+ });
886
893
  }
887
894
  signOut(member, provider) {
888
895
  return engineSignOut(this.signInCtx(), member, provider);
package/dist/link.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import type { BrowserHost } from './browser.ts';
2
2
  import { type IncomingMessage, type ServerResponse } from 'node:http';
3
3
  import { type Grant, type Host, type HostOptions } from '@byokit/link';
4
- import { type ServeIngress, type Via } from '@byokit/reach';
4
+ import { type ServeIngress, type Via } from '@byokit/discover';
5
5
  import type { PushAction, RelayClient } from '@byokit/relay';
6
6
  import type { OpenClawKit } from './kit.ts';
7
7
  import type { Member } from './types.ts';
@@ -9,7 +9,7 @@ import type { Member } from './types.ts';
9
9
  export type OpenClawLinkOptions = {
10
10
  memberOf: (grant: Grant) => Member | undefined;
11
11
  passThrough?: (method: string, grant: Grant) => boolean;
12
- relay?: RelayClient;
12
+ relay?: Pick<RelayClient, 'notify'>;
13
13
  };
14
14
  /** What `openclawLink` hands the link host: its request/stream handlers plus the kit's push entry point. */
15
15
  export type OpenClawLinkHost = Pick<HostOptions, 'handle' | 'stream' | 'allow' | 'onGrantRemoved'> & {
package/dist/link.js CHANGED
@@ -6,7 +6,7 @@ import { readFileSync } from 'node:fs';
6
6
  import { createServer } from 'node:http';
7
7
  import { WebSocketServer } from 'ws';
8
8
  import { PublicLinkError, } from '@byokit/link';
9
- import { reach } from '@byokit/reach';
9
+ import { reach } from '@byokit/discover';
10
10
  import { ENGINE_VERSION } from "./constants.js";
11
11
  import { b64urlDecode, b64urlEncode, sealNotice } from "./notices.js";
12
12
  import { routeFor } from "./routes.js";
package/dist/routes.json CHANGED
@@ -122,7 +122,7 @@
122
122
  },
123
123
  {
124
124
  "choice": "anthropic-cli",
125
- "provider": "anthropic",
125
+ "provider": "claude-cli",
126
126
  "plugin": "anthropic",
127
127
  "billing": "subscription",
128
128
  "via": "cli",
@@ -134,6 +134,7 @@
134
134
  "provider": "claude-cli",
135
135
  "via": "browser"
136
136
  },
137
+ "deprecatedProvider": "anthropic",
137
138
  "reason": "Anthropic Claude CLI: subscription sign-in.",
138
139
  "source": "extensions/anthropic/openclaw.plugin.json (2026.8.1)",
139
140
  "revision": "2026.8.1",
@@ -173,13 +174,9 @@
173
174
  "via": "setup_token",
174
175
  "auth": "token",
175
176
  "prerequisite": null,
176
- "offer": true,
177
- "offerPolicy": "default",
178
- "legacy": {
179
- "provider": "anthropic",
180
- "via": "browser"
181
- },
182
- "reason": "Anthropic setup-token: subscription sign-in.",
177
+ "offer": false,
178
+ "offerPolicy": "explicit",
179
+ "reason": "The pinned Gateway has no app-guided sign-in for this choice.",
183
180
  "source": "extensions/anthropic/openclaw.plugin.json (2026.8.1)",
184
181
  "revision": "2026.8.1",
185
182
  "checked": "2026-10-02",
@@ -201,7 +198,7 @@
201
198
  "id": "anthropic",
202
199
  "method": "setup-token",
203
200
  "revision": "ea806575e6450e4d1efdfc72c19f04be982a1b9b",
204
- "flow": "present"
201
+ "flow": "absent"
205
202
  }
206
203
  },
207
204
  {
package/dist/signin.js CHANGED
@@ -15,6 +15,19 @@ const CANCEL_MS = 10_000;
15
15
  const RECONFIRM_MS = 60_000;
16
16
  const TURNS = 200;
17
17
  const PASTE_MS = 15 * 60_000;
18
+ // Two of the pin's code routes print their code only into a note's text ("Code: X" with
19
+ // "Code expires in N minutes", or "enter the code X." with "Expires at: <ISO>"), never as `deviceCode`.
20
+ // Read it back so every code route reaches the caller in the one structured shape.
21
+ const noteCode = (step) => {
22
+ const text = step.type === 'note' ? step.message ?? '' : '';
23
+ const code = /^Code: (\S+)$/m.exec(text)?.[1] ?? /enter the code (\S+?)\.?$/m.exec(text)?.[1];
24
+ if (!code)
25
+ return undefined;
26
+ const minutes = /Code expires in (\d+) minutes/.exec(text)?.[1];
27
+ const at = Date.parse(/Expires at: (\S+)/.exec(text)?.[1] ?? '');
28
+ return { code, message: text, ...(minutes ? { expiresInMinutes: Number(minutes) }
29
+ : Number.isFinite(at) ? { expires_in: Math.max(0, Math.floor((at - Date.now()) / 1_000)) } : {}) };
30
+ };
18
31
  const cut = (value) => (value instanceof Error ? value.message : String(value)).slice(0, 200);
19
32
  const pullOptions = (signal) => ({ timeoutMs: PULL_MS, signal });
20
33
  /**
@@ -268,19 +281,22 @@ export function signIn(ctx, member, o, on) {
268
281
  step = undefined;
269
282
  if (current.type === 'text' && current.sensitive)
270
283
  sensitive = true;
271
- if (current.deviceCode && current.type !== 'text') {
284
+ const deviceCode = current.type === 'text' ? undefined : current.deviceCode ?? noteCode(current);
285
+ if (deviceCode) {
272
286
  stopApproval();
273
287
  approval = true;
274
288
  sawDeviceCode = true;
275
289
  // The pin converts its provider duration to minutes. Prefer exact seconds if the engine supplies them.
276
- const duration = current.deviceCode.expires_in !== undefined
277
- ? Number(current.deviceCode.expires_in) * 1_000
278
- : Number(current.deviceCode.expiresInMinutes) * 60_000;
279
- if (Number.isFinite(duration) && duration >= 0) {
290
+ const duration = deviceCode.expires_in !== undefined
291
+ ? Number(deviceCode.expires_in) * 1_000
292
+ : Number(deviceCode.expiresInMinutes) * 60_000;
293
+ const timed = Number.isFinite(duration) && duration >= 0;
294
+ if (timed)
280
295
  codeTimer = setTimeout(() => { codeExpired = true; owner.abort(); }, duration);
281
- }
296
+ const message = deviceCode.message ?? current.message;
282
297
  // The code is on the card; acknowledging it lets the engine poll the provider itself.
283
- say({ ...(current.deviceCode.code ? { code: current.deviceCode.code } : {}), ...(current.externalUrl ? { url: current.externalUrl } : {}) });
298
+ say({ ...(deviceCode.code ? { code: deviceCode.code } : {}), ...(current.externalUrl ? { url: current.externalUrl } : {}),
299
+ ...(timed ? { expiresAt: Date.now() + duration } : {}), ...(message ? { message } : {}) });
284
300
  const next = await answer(current);
285
301
  if (next.done) {
286
302
  terminal = next;
@@ -292,8 +308,9 @@ export function signIn(ctx, member, o, on) {
292
308
  if (current.type !== 'progress')
293
309
  stopApproval();
294
310
  if (current.type === 'text') {
295
- if (current.sensitive)
296
- say({ prompt: 'Sign-in token' });
311
+ // Every text step is a question the person must answer, so it is always asked. A non-sensitive one is not a
312
+ // secret by the engine's own flag: its question is shown as written. Once anything is sensitive, only the fixed label is.
313
+ say({ prompt: sensitive ? 'Sign-in token' : current.message || 'Paste what the sign-in page gave you' });
297
314
  const value = await waitForPaste();
298
315
  if (value === undefined)
299
316
  return signal.aborted ? cancelled() : expired();
package/dist/types.d.ts CHANGED
@@ -103,6 +103,8 @@ export type SignInView = {
103
103
  via: 'browser' | 'code';
104
104
  url?: string;
105
105
  code?: string;
106
+ expiresAt?: number;
107
+ message?: string;
106
108
  prompt?: string;
107
109
  error?: string;
108
110
  why?: 'busy' | 'declined' | 'expired' | 'failed';
@@ -125,7 +127,7 @@ export type Decision = {
125
127
  };
126
128
  export type KitState = {
127
129
  phase: 'stopped' | 'installing' | 'starting' | 'repairing' | 'ready' | 'restarting' | 'failed' | 'needs-update' | 'locked';
128
- why?: 'install' | 'handshake' | 'exited' | 'port' | 'version' | 'engine-already-running' | 'engine-patch';
130
+ why?: 'install' | 'handshake' | 'exited' | 'port' | 'version' | 'engine-already-running' | 'engine-patch' | 'sign-in-reset';
129
131
  retryAt?: number;
130
132
  patchSet?: string | null;
131
133
  };
@@ -157,6 +159,10 @@ export type Route = {
157
159
  provider: string;
158
160
  via: 'browser' | 'code';
159
161
  };
162
+ /** @deprecated This route's `provider` before 0.7.0 (`anthropic` on `anthropic-cli`); use `provider` (`claude-cli`).
163
+ * Discovery data only, never a sign-in or `signedIn` id. Removed in 0.8.0, when that route's id becomes
164
+ * `claude-cli:cli:anthropic-cli`. */
165
+ deprecatedProvider?: string;
160
166
  billingFrom?: 'source' | 'host';
161
167
  group?: 'models' | 'services';
162
168
  platforms?: {
package/dist/words.js CHANGED
@@ -21,6 +21,8 @@ export function stateWords(s) {
21
21
  // 'stopped' is never on screen (4.3 covers what a person can see), so 5.14 has no row for it.
22
22
  if (s.phase === 'failed' && s.why === 'engine-already-running')
23
23
  return words('engine.alreadyRunning');
24
+ if (s.why === 'sign-in-reset')
25
+ return words('engine.signInAgain');
24
26
  return s.phase === 'stopped' ? '' : words(PHASE_WORDS[s.phase]);
25
27
  }
26
28
  export function toAccountView(view, ready) {
package/dist/words.json CHANGED
@@ -3,6 +3,7 @@
3
3
  "engine.starting": "Starting up…",
4
4
  "engine.repairing": "Fixing a small problem with the setup. This takes a moment.",
5
5
  "engine.locked": "Your saved sign-in is locked. Unlock your password storage, then try again.",
6
+ "engine.signInAgain": "Your saved sign-in couldn't be opened, so it was kept aside. Sign in again.",
6
7
  "engine.ready": "Ready.",
7
8
  "engine.restarting": "Something stopped. Starting it again by itself.",
8
9
  "engine.alreadyRunning": "Your saved sign-in is in use. Try again after the other session stops.",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@byokit/openclaw",
3
- "version": "0.6.2",
3
+ "version": "0.6.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,9 +60,9 @@
60
60
  "prepack": "tsc -b && node ../../scripts/fix-words-dts.cjs"
61
61
  },
62
62
  "dependencies": {
63
- "@byokit/link": "0.7.0",
64
- "@byokit/relay": "0.5.2",
65
- "@byokit/reach": "0.6.0",
63
+ "@byokit/link": "0.7.1",
64
+ "@byokit/relay": "0.5.3",
65
+ "@byokit/discover": "0.6.0",
66
66
  "@byokit/seal": "0.3.0",
67
67
  "@byokit/secrets": "0.5.0",
68
68
  "@openclaw/gateway-client": "2026.9.6",