@byokit/openclaw 0.3.5 → 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 CHANGED
@@ -2,6 +2,16 @@
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
+
5
15
  ## 0.3.5 (2026-10-01)
6
16
 
7
17
 
package/dist/engine.js CHANGED
@@ -17,6 +17,14 @@ const putChanged = (path, data) => {
17
17
  if (!existsSync(path) || readFileSync(path, 'utf8') !== data)
18
18
  writeFileSync(path, data, { mode: 0o600 });
19
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
+ }
20
28
  const supports = (list, value) => !list ||
21
29
  (!list.includes(`!${value}`) && (list.includes('any') || list.every(item => item.startsWith('!')) || list.includes(value)));
22
30
  function installMatches(dir) {
@@ -175,13 +183,14 @@ export class Engine {
175
183
  return false;
176
184
  try {
177
185
  const proc = `/proc/${pid}`;
178
- const cmd = readFileSync(`${proc}/cmdline`, 'utf8').split('\0');
186
+ const identity = JSON.parse(readFileSync(join(this.root, 'gateway.identity'), 'utf8'));
179
187
  const env = readFileSync(`${proc}/environ`, 'utf8').split('\0');
180
188
  const home = join(this.root, 'home');
181
189
  const owner = join(this.root, 'auth-store.lock', 'pid');
182
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)
183
193
  && realpathSync(`${proc}/exe`) === realpathSync(process.execPath)
184
- && cmd.indexOf(this.entry) > 0 && cmd[cmd.indexOf(this.entry) + 1] === 'gateway'
185
194
  && realpathSync(`${proc}/cwd`) === realpathSync(home)
186
195
  && env.includes(`HOME=${home}`)
187
196
  && env.includes(`OPENCLAW_STATE_DIR=${join(this.root, 'state')}`)
@@ -205,13 +214,9 @@ export class Engine {
205
214
  if (!this.verifiedOrphan(pid))
206
215
  throw occupied();
207
216
  // Verify a stable process identity again immediately before signalling this pid, never its group.
208
- const identity = () => {
209
- const stat = readFileSync(`/proc/${pid}/stat`, 'utf8');
210
- return stat.slice(stat.lastIndexOf(')') + 2).split(' ')[19]; // starttime, not a reusable pid alone
211
- };
212
217
  try {
213
- const before = identity();
214
- if (!before || !this.verifiedOrphan(pid) || before !== identity())
218
+ const before = processStartTime(pid);
219
+ if (!this.verifiedOrphan(pid) || before !== processStartTime(pid))
215
220
  throw occupied();
216
221
  }
217
222
  catch {
@@ -301,6 +306,9 @@ export class Engine {
301
306
  this.state('failed', 'exited');
302
307
  throw new Error('engine spawn failed');
303
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
+ }
304
312
  writeFileSync(join(this.root, 'gateway.pid'), String(child.pid), { mode: 0o600 });
305
313
  child.once('exit', (code) => {
306
314
  if (this.child !== child || this.stopping)
@@ -328,8 +336,10 @@ export class Engine {
328
336
  }
329
337
  removeOwnedPid(pid) {
330
338
  const path = join(this.root, 'gateway.pid');
331
- if (pid && existsSync(path) && readFileSync(path, 'utf8') === String(pid))
339
+ if (pid && existsSync(path) && readFileSync(path, 'utf8') === String(pid)) {
332
340
  rmSync(path, { force: true });
341
+ rmSync(join(this.root, 'gateway.identity'), { force: true });
342
+ }
333
343
  }
334
344
  withAuthStore(task) { return this.authStore.offline(task); }
335
345
  async stop() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@byokit/openclaw",
3
- "version": "0.3.5",
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.6.0",
56
- "@byokit/relay": "0.4.2",
57
- "@byokit/reach": "0.4.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.4.0"
65
+ "@byokit/ui-core": "0.5.0"
66
66
  },
67
67
  "publishConfig": {
68
68
  "access": "public"