@byokit/openclaw 0.3.4 → 0.3.6
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 +16 -0
- package/README.md +8 -1
- package/dist/auth-store.js +28 -20
- package/dist/engine-status.d.ts +6 -0
- package/dist/engine-status.js +26 -0
- package/dist/engine.d.ts +3 -1
- package/dist/engine.js +78 -13
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/kit.js +4 -0
- package/dist/types.d.ts +1 -1
- package/dist/words.js +2 -0
- package/dist/words.json +1 -0
- package/package.json +5 -5
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.3.6 (2026-10-01)
|
|
6
|
+
|
|
7
|
+
- FIX: Live orphan gateways recover after the engine rewrites its process title. Recovery verifies the recorded launch pid and process start time with the executable and isolated store paths; ambiguous ownership preserves the saved sign-in state.
|
|
8
|
+
|
|
9
|
+
- Dependency update: pins @byokit/ui-core 0.5.0.
|
|
10
|
+
- Dependency update: pins @byokit/reach 0.5.0.
|
|
11
|
+
- Dependency update: pins @byokit/relay 0.5.0.
|
|
12
|
+
- Dependency update: pins @byokit/link 0.7.0.
|
|
13
|
+
- SECURITY: (from @byokit/link 0.7.0) `Host.shortCode()` adds a machine-key commitment to typed pairing; `pairWithCode()` verifies it before disclosing device identity or asking for approval. Legacy codes remain compatible; use the full new code when relay lookup is untrusted.
|
|
14
|
+
|
|
15
|
+
## 0.3.5 (2026-10-01)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
- FIX: Failed engine starts no longer remove another live gateway’s guards or saved sign-in state. Verified dead-host gateways stop gracefully before restart; ambiguous ownership returns a typed engine-already-running error and preserves the store.
|
|
20
|
+
|
|
5
21
|
## 0.3.4 (2026-09-30)
|
|
6
22
|
|
|
7
23
|
|
package/README.md
CHANGED
|
@@ -232,7 +232,14 @@ before any restoration; a wrong key, tampering, or a missing adapter rejects. Fi
|
|
|
232
232
|
have mode 0600 and directories 0700. File symlinks are sealed only when their fully resolved targets are
|
|
233
233
|
regular files inside the isolated engine root; they restore as regular files at the link paths. Outside-root,
|
|
234
234
|
dangling and directory symlinks (including loops), sockets, FIFOs and devices are skipped.
|
|
235
|
-
One kit owns the store at a time
|
|
235
|
+
One kit owns the store at a time. After a host crash, preparation stops a verified same-user gateway
|
|
236
|
+
only when its host lock owner is dead, waits up to three seconds for exit, then seals and restores the
|
|
237
|
+
leftover state under the acquired lock. If process identity is ambiguous (including unavailable process
|
|
238
|
+
inspection or a rewritten command line), another host is alive, or shutdown times out, `prepare()` and
|
|
239
|
+
`start()` reject with exported `EngineAlreadyRunningError` (`code: 'engine-already-running'`). The kit reports
|
|
240
|
+
`{ phase: 'failed', why: 'engine-already-running' }`; show `stateWords(kit.state)` and retry after the other
|
|
241
|
+
session stops. Failed-start cleanup and `stop()` on an instance with no ownership preserve the other
|
|
242
|
+
writer's pid, lock, sealed snapshot and live state.
|
|
236
243
|
Sealing copies the whole store through memory, so startup and stop cost grows with session history.
|
|
237
244
|
|
|
238
245
|
The migration doctor temporarily opens the same store and reseals it even when import fails. Once the gateway
|
package/dist/auth-store.js
CHANGED
|
@@ -2,20 +2,10 @@
|
|
|
2
2
|
// Seal the complete isolated stores, including journals, only after their writer has exited.
|
|
3
3
|
import { chmodSync, existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, writeFileSync, closeSync, fsyncSync, openSync } from 'node:fs';
|
|
4
4
|
import { dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
|
|
5
|
+
import { EngineAlreadyRunningError, pidAlive as live } from "./engine-status.js";
|
|
5
6
|
const encoder = new TextEncoder();
|
|
6
7
|
const archive = (name) => /^(auth-profiles|auth-state|auth|oauth)\.json\.(migrated-.+|sqlite-import\..+\.bak)$/.test(name)
|
|
7
8
|
|| name.endsWith('.moved-to-engine');
|
|
8
|
-
const live = (pid) => {
|
|
9
|
-
if (!Number.isSafeInteger(pid) || pid < 1)
|
|
10
|
-
return false;
|
|
11
|
-
try {
|
|
12
|
-
process.kill(pid, 0);
|
|
13
|
-
return true;
|
|
14
|
-
}
|
|
15
|
-
catch (error) {
|
|
16
|
-
return error.code !== 'ESRCH';
|
|
17
|
-
}
|
|
18
|
-
};
|
|
19
9
|
function regular(path) {
|
|
20
10
|
if (!lstatSync(path).isFile())
|
|
21
11
|
throw new Error(`credential store requires regular files: ${path}`);
|
|
@@ -108,18 +98,16 @@ export class AuthStore {
|
|
|
108
98
|
return;
|
|
109
99
|
if (existsSync(this.file) && !this.o.seal)
|
|
110
100
|
throw new Error('authSeal required for sealed credential store');
|
|
111
|
-
if (!this.o.seal)
|
|
112
|
-
return;
|
|
113
101
|
// Never race a live gateway or another kit, including an orphan after its host crashed.
|
|
114
102
|
const gateway = join(this.o.root, 'gateway.pid');
|
|
115
103
|
if (existsSync(gateway) && live(Number(readFileSync(gateway, 'utf8'))))
|
|
116
|
-
throw new
|
|
104
|
+
throw new EngineAlreadyRunningError();
|
|
117
105
|
if (existsSync(this.lock)) {
|
|
118
106
|
if (!lstatSync(this.lock).isDirectory() || lstatSync(this.lock).isSymbolicLink())
|
|
119
107
|
throw new Error('invalid credential lock');
|
|
120
108
|
const owner = join(this.lock, 'pid');
|
|
121
109
|
if (!existsSync(owner) || live(Number(readFileSync(owner, 'utf8'))))
|
|
122
|
-
throw new
|
|
110
|
+
throw new EngineAlreadyRunningError();
|
|
123
111
|
// Only one stale-owner recovery can proceed. Recheck after claiming it so a stale reader
|
|
124
112
|
// cannot remove the lock a newer owner has just acquired.
|
|
125
113
|
const recovery = join(this.lock, 'recovery');
|
|
@@ -127,17 +115,36 @@ export class AuthStore {
|
|
|
127
115
|
mkdirSync(recovery, { mode: 0o700 });
|
|
128
116
|
}
|
|
129
117
|
catch {
|
|
130
|
-
throw new
|
|
118
|
+
throw new EngineAlreadyRunningError();
|
|
131
119
|
}
|
|
132
120
|
if (!existsSync(owner) || live(Number(readFileSync(owner, 'utf8')))) {
|
|
133
121
|
rmSync(recovery, { recursive: true });
|
|
134
|
-
throw new
|
|
122
|
+
throw new EngineAlreadyRunningError();
|
|
135
123
|
}
|
|
136
124
|
rmSync(this.lock, { recursive: true });
|
|
137
125
|
}
|
|
138
|
-
|
|
139
|
-
|
|
126
|
+
try {
|
|
127
|
+
mkdirSync(this.lock, { mode: 0o700 });
|
|
128
|
+
}
|
|
129
|
+
catch (error) {
|
|
130
|
+
if (error.code === 'EEXIST')
|
|
131
|
+
throw new EngineAlreadyRunningError();
|
|
132
|
+
throw error;
|
|
133
|
+
}
|
|
140
134
|
this.owned = true;
|
|
135
|
+
try {
|
|
136
|
+
writeFileSync(join(this.lock, 'pid'), String(process.pid), { mode: 0o600, flag: 'wx' });
|
|
137
|
+
// Recheck under the acquired lock before removing a dead writer's guard.
|
|
138
|
+
if (existsSync(gateway)) {
|
|
139
|
+
if (live(Number(readFileSync(gateway, 'utf8'))))
|
|
140
|
+
throw new EngineAlreadyRunningError();
|
|
141
|
+
removeMarker(gateway);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
catch (error) {
|
|
145
|
+
this.release();
|
|
146
|
+
throw error;
|
|
147
|
+
}
|
|
141
148
|
}
|
|
142
149
|
release() {
|
|
143
150
|
if (!this.owned)
|
|
@@ -297,7 +304,8 @@ export class AuthStore {
|
|
|
297
304
|
}
|
|
298
305
|
stop() {
|
|
299
306
|
return this.serial(async () => {
|
|
300
|
-
this.
|
|
307
|
+
if (!this.owned)
|
|
308
|
+
return;
|
|
301
309
|
await this.archives();
|
|
302
310
|
await this.persist();
|
|
303
311
|
this.active = false;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** A live or unverifiable writer still owns the isolated engine state. Retry after it stops. */
|
|
2
|
+
export declare class EngineAlreadyRunningError extends Error {
|
|
3
|
+
readonly code = "engine-already-running";
|
|
4
|
+
constructor();
|
|
5
|
+
}
|
|
6
|
+
export declare function pidAlive(pid: number): boolean;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
/** A live or unverifiable writer still owns the isolated engine state. Retry after it stops. */
|
|
3
|
+
export class EngineAlreadyRunningError extends Error {
|
|
4
|
+
code = 'engine-already-running';
|
|
5
|
+
constructor() {
|
|
6
|
+
super('engine already running; credential store is in use');
|
|
7
|
+
this.name = 'EngineAlreadyRunningError';
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
export function pidAlive(pid) {
|
|
11
|
+
if (!Number.isSafeInteger(pid) || pid < 1)
|
|
12
|
+
return true; // An invalid guard is ambiguous, never stale.
|
|
13
|
+
try {
|
|
14
|
+
process.kill(pid, 0);
|
|
15
|
+
// An orphan can remain a zombie until its new parent reaps it; it has no writer left.
|
|
16
|
+
if (process.platform === 'linux') {
|
|
17
|
+
const stat = readFileSync(`/proc/${pid}/stat`, 'utf8');
|
|
18
|
+
if (stat.slice(stat.lastIndexOf(')') + 2).startsWith('Z '))
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
return error.code !== 'ESRCH';
|
|
25
|
+
}
|
|
26
|
+
}
|
package/dist/engine.d.ts
CHANGED
|
@@ -36,7 +36,8 @@ export declare class Engine {
|
|
|
36
36
|
doctor(timeoutMs: number): {
|
|
37
37
|
status: number | null;
|
|
38
38
|
};
|
|
39
|
-
private
|
|
39
|
+
private verifiedOrphan;
|
|
40
|
+
private recoverOrphan;
|
|
40
41
|
start(): Promise<{
|
|
41
42
|
port: number;
|
|
42
43
|
token: string;
|
|
@@ -45,6 +46,7 @@ export declare class Engine {
|
|
|
45
46
|
private locked;
|
|
46
47
|
private startOnce;
|
|
47
48
|
private launch;
|
|
49
|
+
private removeOwnedPid;
|
|
48
50
|
withAuthStore<T>(task: () => Promise<T>): Promise<T>;
|
|
49
51
|
stop(): Promise<void>;
|
|
50
52
|
}
|
package/dist/engine.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { spawn, spawnSync } from 'node:child_process';
|
|
2
2
|
import { randomBytes, generateKeyPairSync } from 'node:crypto';
|
|
3
|
-
import { closeSync, existsSync, mkdirSync, openSync, readFileSync, writeFileSync, copyFileSync, rmSync } from 'node:fs';
|
|
3
|
+
import { closeSync, existsSync, mkdirSync, openSync, readFileSync, writeFileSync, copyFileSync, rmSync, realpathSync, statSync } from 'node:fs';
|
|
4
4
|
import { createServer } from 'node:net';
|
|
5
5
|
import { join, dirname } from 'node:path';
|
|
6
6
|
import { fileURLToPath } from 'node:url';
|
|
7
7
|
import { setTimeout as delay } from 'node:timers/promises';
|
|
8
8
|
import { AuthStore } from "./auth-store.js";
|
|
9
|
+
import { EngineAlreadyRunningError, pidAlive } from "./engine-status.js";
|
|
9
10
|
import { ENGINE_VERSION } from "./constants.js";
|
|
10
11
|
import { reconcileConfig } from "./config.js";
|
|
11
12
|
import { writePlugin, resolveBridge } from "./bridge.js";
|
|
@@ -16,6 +17,14 @@ const putChanged = (path, data) => {
|
|
|
16
17
|
if (!existsSync(path) || readFileSync(path, 'utf8') !== data)
|
|
17
18
|
writeFileSync(path, data, { mode: 0o600 });
|
|
18
19
|
};
|
|
20
|
+
function processStartTime(pid) {
|
|
21
|
+
const stat = readFileSync(`/proc/${pid}/stat`, 'utf8');
|
|
22
|
+
// comm can contain spaces and parentheses; field 22 is index 19 after its final ')'.
|
|
23
|
+
const startTime = stat.slice(stat.lastIndexOf(')') + 2).split(' ')[19];
|
|
24
|
+
if (!startTime || !/^\d+$/.test(startTime))
|
|
25
|
+
throw new Error('unverifiable engine start time');
|
|
26
|
+
return startTime;
|
|
27
|
+
}
|
|
19
28
|
const supports = (list, value) => !list ||
|
|
20
29
|
(!list.includes(`!${value}`) && (list.includes('any') || list.every(item => item.startsWith('!')) || list.includes(value)));
|
|
21
30
|
function installMatches(dir) {
|
|
@@ -93,6 +102,7 @@ export class Engine {
|
|
|
93
102
|
async prepareOnce() {
|
|
94
103
|
if (!this.o.authSeal && existsSync(join(this.root, 'auth-store.sealed')))
|
|
95
104
|
throw new Error('authSeal required for sealed credential store');
|
|
105
|
+
await this.recoverOrphan();
|
|
96
106
|
for (const d of [this.root, ...(!this.o.authSeal ? [join(this.root, 'home'), join(this.root, 'state')] : []), join(this.root, 'tmp'), join(this.root, 'install-home'), join(this.root, 'npm-cache'), join(this.o.stateDir, 'logs'), this.dir])
|
|
97
107
|
mkdirSync(d, { recursive: true, mode: 0o700 });
|
|
98
108
|
try {
|
|
@@ -168,19 +178,64 @@ export class Engine {
|
|
|
168
178
|
const result = spawnSync(process.execPath, [entry, 'doctor', '--fix', '--yes', '--non-interactive'], { cwd: env.HOME, env, timeout: timeoutMs, stdio: 'pipe' });
|
|
169
179
|
return { status: result.status };
|
|
170
180
|
}
|
|
171
|
-
|
|
181
|
+
verifiedOrphan(pid) {
|
|
182
|
+
if (process.platform !== 'linux' || !process.getuid)
|
|
183
|
+
return false;
|
|
184
|
+
try {
|
|
185
|
+
const proc = `/proc/${pid}`;
|
|
186
|
+
const identity = JSON.parse(readFileSync(join(this.root, 'gateway.identity'), 'utf8'));
|
|
187
|
+
const env = readFileSync(`${proc}/environ`, 'utf8').split('\0');
|
|
188
|
+
const home = join(this.root, 'home');
|
|
189
|
+
const owner = join(this.root, 'auth-store.lock', 'pid');
|
|
190
|
+
return statSync(proc).uid === process.getuid()
|
|
191
|
+
&& Number(readFileSync(join(this.root, 'gateway.pid'), 'utf8')) === pid
|
|
192
|
+
&& identity.pid === pid && identity.startTime === processStartTime(pid)
|
|
193
|
+
&& realpathSync(`${proc}/exe`) === realpathSync(process.execPath)
|
|
194
|
+
&& realpathSync(`${proc}/cwd`) === realpathSync(home)
|
|
195
|
+
&& env.includes(`HOME=${home}`)
|
|
196
|
+
&& env.includes(`OPENCLAW_STATE_DIR=${join(this.root, 'state')}`)
|
|
197
|
+
&& env.includes(`OPENCLAW_CONFIG_PATH=${join(this.root, 'openclaw.json')}`)
|
|
198
|
+
&& existsSync(owner) && !pidAlive(Number(readFileSync(owner, 'utf8')));
|
|
199
|
+
}
|
|
200
|
+
catch {
|
|
201
|
+
return false;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
async recoverOrphan() {
|
|
205
|
+
if (this.child && this.child.exitCode === null && this.child.signalCode === null)
|
|
206
|
+
return;
|
|
172
207
|
const path = join(this.root, 'gateway.pid');
|
|
173
208
|
if (!existsSync(path))
|
|
174
209
|
return;
|
|
175
210
|
const pid = Number(readFileSync(path, 'utf8'));
|
|
176
|
-
if (!
|
|
177
|
-
return;
|
|
211
|
+
if (!pidAlive(pid))
|
|
212
|
+
return; // AuthStore removes stale guards only under its lock.
|
|
213
|
+
const occupied = () => { this.state('failed', 'engine-already-running'); return new EngineAlreadyRunningError(); };
|
|
214
|
+
if (!this.verifiedOrphan(pid))
|
|
215
|
+
throw occupied();
|
|
216
|
+
// Verify a stable process identity again immediately before signalling this pid, never its group.
|
|
178
217
|
try {
|
|
179
|
-
const
|
|
180
|
-
if (
|
|
181
|
-
|
|
218
|
+
const before = processStartTime(pid);
|
|
219
|
+
if (!this.verifiedOrphan(pid) || before !== processStartTime(pid))
|
|
220
|
+
throw occupied();
|
|
221
|
+
}
|
|
222
|
+
catch {
|
|
223
|
+
if (!pidAlive(pid))
|
|
224
|
+
return;
|
|
225
|
+
throw occupied();
|
|
182
226
|
}
|
|
183
|
-
|
|
227
|
+
try {
|
|
228
|
+
process.kill(pid, 'SIGTERM');
|
|
229
|
+
}
|
|
230
|
+
catch (error) {
|
|
231
|
+
if (error.code !== 'ESRCH')
|
|
232
|
+
throw occupied();
|
|
233
|
+
}
|
|
234
|
+
for (let i = 0; i < 15 && pidAlive(pid); i++)
|
|
235
|
+
await delay(200);
|
|
236
|
+
if (pidAlive(pid))
|
|
237
|
+
throw occupied();
|
|
238
|
+
// The new store owner now seals crash leftovers under its lock before restoring them.
|
|
184
239
|
}
|
|
185
240
|
start() {
|
|
186
241
|
if (this.starting)
|
|
@@ -215,7 +270,9 @@ export class Engine {
|
|
|
215
270
|
return this.launch();
|
|
216
271
|
}
|
|
217
272
|
catch (error) {
|
|
218
|
-
await
|
|
273
|
+
// A launch failure after spawn still has a writer; the caller's stop must await its exit.
|
|
274
|
+
if (!this.child)
|
|
275
|
+
await this.authStore.stop();
|
|
219
276
|
throw error;
|
|
220
277
|
}
|
|
221
278
|
}
|
|
@@ -229,7 +286,6 @@ export class Engine {
|
|
|
229
286
|
return { port: this.port, token: this.token, identityPath };
|
|
230
287
|
if (this.child && this.child.exitCode === null)
|
|
231
288
|
return { port: this.port, token: this.token, identityPath };
|
|
232
|
-
this.stalePid();
|
|
233
289
|
this.state('starting');
|
|
234
290
|
const { entry, env } = this.doctorContext();
|
|
235
291
|
const fd = openSync(join(this.o.stateDir, 'logs', 'openclaw.log'), 'a', 0o600);
|
|
@@ -250,12 +306,15 @@ export class Engine {
|
|
|
250
306
|
this.state('failed', 'exited');
|
|
251
307
|
throw new Error('engine spawn failed');
|
|
252
308
|
}
|
|
309
|
+
if (process.platform === 'linux') {
|
|
310
|
+
writeFileSync(join(this.root, 'gateway.identity'), JSON.stringify({ pid: child.pid, startTime: processStartTime(child.pid) }), { mode: 0o600 });
|
|
311
|
+
}
|
|
253
312
|
writeFileSync(join(this.root, 'gateway.pid'), String(child.pid), { mode: 0o600 });
|
|
254
313
|
child.once('exit', (code) => {
|
|
255
314
|
if (this.child !== child || this.stopping)
|
|
256
315
|
return;
|
|
257
316
|
this.child = undefined;
|
|
258
|
-
|
|
317
|
+
this.removeOwnedPid(child.pid);
|
|
259
318
|
void (async () => {
|
|
260
319
|
await this.authStore.stop();
|
|
261
320
|
if (this.stopping)
|
|
@@ -275,6 +334,13 @@ export class Engine {
|
|
|
275
334
|
});
|
|
276
335
|
return { port: this.port, token: this.token, identityPath };
|
|
277
336
|
}
|
|
337
|
+
removeOwnedPid(pid) {
|
|
338
|
+
const path = join(this.root, 'gateway.pid');
|
|
339
|
+
if (pid && existsSync(path) && readFileSync(path, 'utf8') === String(pid)) {
|
|
340
|
+
rmSync(path, { force: true });
|
|
341
|
+
rmSync(join(this.root, 'gateway.identity'), { force: true });
|
|
342
|
+
}
|
|
343
|
+
}
|
|
278
344
|
withAuthStore(task) { return this.authStore.offline(task); }
|
|
279
345
|
async stop() {
|
|
280
346
|
this.stopping = true;
|
|
@@ -299,10 +365,9 @@ export class Engine {
|
|
|
299
365
|
if (child && child.exitCode === null && child.signalCode === null)
|
|
300
366
|
throw new Error('engine did not stop; credential store still in use');
|
|
301
367
|
this.child = undefined;
|
|
302
|
-
|
|
368
|
+
this.removeOwnedPid(child?.pid);
|
|
303
369
|
if (!this.credentialsLocked)
|
|
304
370
|
await this.authStore.stop();
|
|
305
|
-
rmSync(this.bridgeSock, { force: true });
|
|
306
371
|
this.state('stopped');
|
|
307
372
|
}
|
|
308
373
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -2,3 +2,4 @@ export { ENGINE_VERSION, OPERATOR_SCOPES, PROTOCOL_VERSION } from './constants.t
|
|
|
2
2
|
export type { Approval, CallOptions, Decision, GatewayEventName, GatewayEventPayload, GatewayMethod, GatewayParams, GatewayResult, GatewayTransport, GateResult, Hello, KitState, Member, PlanWindow, Route, RunEnd, RunEvent, RunRef, RunSpec, RunUsage, SignInOptions, SignInView, ToolHost, ToolSpec, } from './types.ts';
|
|
3
3
|
export { OpenClawKit, type KitOptions, type RetainedLogin } from './kit.ts';
|
|
4
4
|
export { stateWords, toAccountView, words, type AccountView, type WordKey } from './words.ts';
|
|
5
|
+
export { EngineAlreadyRunningError } from './engine-status.ts';
|
package/dist/index.js
CHANGED
package/dist/kit.js
CHANGED
|
@@ -503,6 +503,10 @@ export class OpenClawKit {
|
|
|
503
503
|
if (transport && this.transport === transport)
|
|
504
504
|
await this.disconnect();
|
|
505
505
|
await this.engine.stop();
|
|
506
|
+
if (error instanceof Error && 'code' in error && error.code === 'engine-already-running') {
|
|
507
|
+
this.setState({ phase: 'failed', why: 'engine-already-running' });
|
|
508
|
+
throw error;
|
|
509
|
+
}
|
|
506
510
|
if (needsUpdate)
|
|
507
511
|
this.setState({ phase: 'needs-update', why: 'version' });
|
|
508
512
|
if (!needsUpdate && !this.stopping)
|
package/dist/types.d.ts
CHANGED
|
@@ -115,7 +115,7 @@ export type Decision = {
|
|
|
115
115
|
};
|
|
116
116
|
export type KitState = {
|
|
117
117
|
phase: 'stopped' | 'installing' | 'starting' | 'repairing' | 'ready' | 'restarting' | 'failed' | 'needs-update' | 'locked';
|
|
118
|
-
why?: 'install' | 'handshake' | 'exited' | 'port' | 'version';
|
|
118
|
+
why?: 'install' | 'handshake' | 'exited' | 'port' | 'version' | 'engine-already-running';
|
|
119
119
|
retryAt?: number;
|
|
120
120
|
};
|
|
121
121
|
export type Hello = {
|
package/dist/words.js
CHANGED
|
@@ -19,6 +19,8 @@ const PHASE_WORDS = {
|
|
|
19
19
|
};
|
|
20
20
|
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
|
+
if (s.phase === 'failed' && s.why === 'engine-already-running')
|
|
23
|
+
return words('engine.alreadyRunning');
|
|
22
24
|
return s.phase === 'stopped' ? '' : words(PHASE_WORDS[s.phase]);
|
|
23
25
|
}
|
|
24
26
|
export function toAccountView(view, ready) {
|
package/dist/words.json
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
"engine.locked": "Your saved sign-in is locked. Unlock your password storage, then try again.",
|
|
6
6
|
"engine.ready": "Ready.",
|
|
7
7
|
"engine.restarting": "Something stopped. Starting it again by itself.",
|
|
8
|
+
"engine.alreadyRunning": "Your saved sign-in is in use. Try again after the other session stops.",
|
|
8
9
|
"engine.failed": "This computer couldn't start the helper. Restart the app to try again.",
|
|
9
10
|
"engine.needsUpdate": "This app needs an update to keep working.",
|
|
10
11
|
"member.signedOut": "Sign in with {name} to start.",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@byokit/openclaw",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.6",
|
|
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",
|
|
@@ -52,9 +52,9 @@
|
|
|
52
52
|
"prepack": "tsc -b && node ../../scripts/fix-words-dts.cjs"
|
|
53
53
|
},
|
|
54
54
|
"dependencies": {
|
|
55
|
-
"@byokit/link": "0.
|
|
56
|
-
"@byokit/relay": "0.
|
|
57
|
-
"@byokit/reach": "0.
|
|
55
|
+
"@byokit/link": "0.7.0",
|
|
56
|
+
"@byokit/relay": "0.5.0",
|
|
57
|
+
"@byokit/reach": "0.5.0",
|
|
58
58
|
"@byokit/seal": "0.2.0",
|
|
59
59
|
"@byokit/secrets": "0.5.0",
|
|
60
60
|
"@openclaw/gateway-client": "2026.8.1",
|
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
"ws": "8.21.3"
|
|
63
63
|
},
|
|
64
64
|
"devDependencies": {
|
|
65
|
-
"@byokit/ui-core": "0.
|
|
65
|
+
"@byokit/ui-core": "0.5.0"
|
|
66
66
|
},
|
|
67
67
|
"publishConfig": {
|
|
68
68
|
"access": "public"
|