@perkos/perkos-a2a 0.12.34 → 0.12.36
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/README.md +25 -21
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +225 -43
- package/dist/index.js.map +3 -3
- package/dist/openclaw-supervisor-client.d.ts +15 -0
- package/dist/openclaw-supervisor-client.d.ts.map +1 -1
- package/dist/openclaw-supervisor-client.js +207 -21
- package/dist/openclaw-supervisor-client.js.map +1 -1
- package/dist/openclaw-supervisor.d.ts +8 -0
- package/dist/openclaw-supervisor.d.ts.map +1 -1
- package/dist/openclaw-supervisor.js +63 -2
- package/dist/openclaw-supervisor.js.map +1 -1
- package/dist/repair.d.ts +1 -0
- package/dist/repair.d.ts.map +1 -1
- package/dist/repair.js +126 -136
- package/dist/repair.js.map +1 -1
- package/dist/supervisor-protocol.d.ts +12 -0
- package/dist/supervisor-protocol.d.ts.map +1 -1
- package/dist/supervisor-protocol.js +5 -1
- package/dist/supervisor-protocol.js.map +1 -1
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -183,24 +183,24 @@ schema, and protects identity, relay/chat credentials, endpoints, runtime kind,
|
|
|
183
183
|
and session key. Bind-mounted `openclaw.json` files are updated in place when an
|
|
184
184
|
atomic rename is unavailable, while a persistent backup and the quarantined
|
|
185
185
|
legacy extension remain recoverable. A process lock prevents two simultaneous
|
|
186
|
-
repairs from registering the same agent. Version 0.12.
|
|
186
|
+
repairs from registering the same agent. Version 0.12.35 writes the signed
|
|
187
187
|
attempt before OpenClaw can load the newly enabled plugin, so a clean first
|
|
188
188
|
generation started during `config validate` is attributable to that exact
|
|
189
189
|
command rather than being rejected as pre-restart evidence.
|
|
190
190
|
|
|
191
191
|
Every successful install is anchored by a private, content-addressed receipt;
|
|
192
192
|
the resumable repair journal rolls forward after an install has committed
|
|
193
|
-
instead of restoring an incompatible pre-upgrade source.
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
It validates config and plugin health,
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
193
|
+
instead of restoring an incompatible pre-upgrade source. The plugin writes
|
|
194
|
+
HMAC-bound runtime evidence proving that the expected version and normalized
|
|
195
|
+
config were actually loaded. The receipt, journal, attempt, and runtime status
|
|
196
|
+
files are created with owner-only permissions.
|
|
197
|
+
|
|
198
|
+
It validates config and plugin health, then reads only plugin-owned runtime
|
|
199
|
+
evidence. It never mutates `commands.restart`, sends `SIGUSR1`, waits for an
|
|
200
|
+
OpenClaw gateway drain, launches a second relay/chat client, or scrapes gateway
|
|
201
|
+
status/logs. Clean installs complete through hot reload. Certified upgrades use
|
|
202
|
+
a fenced supervisor rollover while the gateway and its active model/runtime
|
|
203
|
+
remain untouched. Relay, registration, heartbeat, and chat authentication must
|
|
204
204
|
remain healthy—with no duplicate plugin or registration messages—for three
|
|
205
205
|
continuous minutes and at least three fresh successful heartbeats. Success or
|
|
206
206
|
failure is emitted as one JSON object; secret values are never printed.
|
|
@@ -220,13 +220,15 @@ READY and accept the matching claim. The supervisor records its PID, process
|
|
|
220
220
|
start identity, boot id, claim time, and transport counts in private health
|
|
221
221
|
evidence so `connect` can distinguish a certified owner from stale files.
|
|
222
222
|
|
|
223
|
-
For a clean first install, OpenClaw's PID 1 hot reload remains supported
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
223
|
+
For a clean first install, OpenClaw's PID 1 hot reload remains supported without
|
|
224
|
+
a gateway restart. A certified upgrade fences and stops the old supervisor,
|
|
225
|
+
waits for `STOPPED` plus socket/process exit, starts the supervisor CLI from the
|
|
226
|
+
new verified install root, and requires target-version health before opening a
|
|
227
|
+
single relay client. When upgrading a pre-supervisor or otherwise uncertified
|
|
228
|
+
installation, `connect` exits promptly with the machine-readable action
|
|
229
|
+
`ACTION_REQUIRED_REAL_RESTART`; rerunning the same idempotent command after an
|
|
230
|
+
out-of-band service/container restart resumes the migration without replacing
|
|
231
|
+
identity or credentials.
|
|
230
232
|
|
|
231
233
|
On successful verification, `connect` removes the active-attempt file and marks
|
|
232
234
|
the private repair journal `done`. Stale OpenClaw install metadata is reported
|
|
@@ -237,14 +239,16 @@ version; stale metadata is never allowed to weaken those identity checks.
|
|
|
237
239
|
Each claim carries the parent PID/start identity, boot id, instance id, and
|
|
238
240
|
generation. Before accepting a newer generation, the supervisor waits for
|
|
239
241
|
confirmed transport shutdown; a timeout fails closed with
|
|
240
|
-
`OWNER_STOP_TIMEOUT`.
|
|
242
|
+
`OWNER_STOP_TIMEOUT`. A private TTL rollover lock serializes concurrent upgrades,
|
|
243
|
+
and health certifies the supervisor package version and real install root. Lease
|
|
244
|
+
and health evidence are owner-only files and never
|
|
241
245
|
contain relay or chat credentials. This ownership boundary applies to OpenClaw;
|
|
242
246
|
Hermes/custom runtimes keep their existing runtime-owned model and transport
|
|
243
247
|
routing.
|
|
244
248
|
|
|
245
249
|
```bash
|
|
246
250
|
A2A_RELAY_API_KEY='<credential-from-invitation>' \
|
|
247
|
-
npx --yes @perkos/perkos-a2a@0.12.
|
|
251
|
+
npx --yes @perkos/perkos-a2a@0.12.36 connect \
|
|
248
252
|
--artifact ./perkos-a2a.tgz \
|
|
249
253
|
--sha256 '<sha256-from-the-official-invitation>' \
|
|
250
254
|
--agent-name '<agent-name>' \
|
package/dist/index.d.ts
CHANGED
|
@@ -53,5 +53,12 @@ export declare function runOpenClawChatTurn(api: any, config: A2APluginConfig, s
|
|
|
53
53
|
export declare function formatRecentChatContext(messages: ChatMessage[], currentMessageId: string): string;
|
|
54
54
|
/** The identity the relay treats as exclusive; names are display metadata. */
|
|
55
55
|
export declare function a2aLifecycleKey(config: A2APluginConfig): string;
|
|
56
|
+
/**
|
|
57
|
+
* OpenClaw may briefly call register while a plugin entry is being rewritten.
|
|
58
|
+
* Treat partial/empty config as an inert, valid shape instead of crashing the
|
|
59
|
+
* whole service on string operations. The repair CLI restores the complete
|
|
60
|
+
* identity atomically before the service is enabled.
|
|
61
|
+
*/
|
|
62
|
+
export declare function normalizeOpenClawPluginConfig(value: unknown): A2APluginConfig;
|
|
56
63
|
export default function register(api: any): void;
|
|
57
64
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAaH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAClD,OAAO,KAAK,EAGV,WAAW,EAEZ,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC1D,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,cAAc,YAAY,CAAC;AA0C3B,kEAAkE;AAClE,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAItF;AAED,KAAK,oBAAoB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AASpD;;;;;GAKG;AACH,wBAAgB,2BAA2B,CACzC,KAAK,EAAE,OAAO,EACd,oBAAoB,EAAE,MAAM,GAC3B,OAAO,CA2BT;AAED;;;;;GAKG;AACH,wBAAgB,6BAA6B,CAC3C,KAAK,EAAE,oBAAoB,EAC3B,eAAe,CAAC,EAAE,MAAM,GACvB,oBAAoB,CA+BtB;AAiED,KAAK,qBAAqB,GAAG;IAC3B,QAAQ,CAAC,EAAE,KAAK,CAAC;QACf,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,OAAO,CAAC,EAAE,OAAO,CAAC;QAClB,WAAW,CAAC,EAAE,OAAO,CAAC;QACtB,YAAY,CAAC,EAAE,OAAO,CAAC;KACxB,CAAC,CAAC;IACH,IAAI,CAAC,EAAE;QACL,yBAAyB,CAAC,EAAE,MAAM,CAAC;QACnC,qBAAqB,CAAC,EAAE,MAAM,CAAC;KAChC,CAAC;CACH,CAAC;AAEF,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,qBAAqB,GAAG,MAAM,GAAG,IAAI,CAYrF;AAQD,wBAAsB,mBAAmB,CACvC,GAAG,EAAE,GAAG,EACR,MAAM,EAAE,eAAe,EACvB,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,MAAM,EACd,gBAAgB,EAAE,MAAM,EACxB,MAAM,EAAE;IAAE,IAAI,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,CAAC;IAAC,KAAK,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,CAAA;CAAE,GAClF,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAuFxB;AAYD,oFAAoF;AACpF,wBAAgB,uBAAuB,CACrC,QAAQ,EAAE,WAAW,EAAE,EACvB,gBAAgB,EAAE,MAAM,GACvB,MAAM,CAWR;AA4CD,8EAA8E;AAC9E,wBAAgB,eAAe,CAAC,MAAM,EAAE,eAAe,GAAG,MAAM,CAe/D;AAED,MAAM,CAAC,OAAO,UAAU,QAAQ,CAAC,GAAG,EAAE,GAAG,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAaH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAClD,OAAO,KAAK,EAGV,WAAW,EAEZ,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC1D,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,cAAc,YAAY,CAAC;AA0C3B,kEAAkE;AAClE,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAItF;AAED,KAAK,oBAAoB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AASpD;;;;;GAKG;AACH,wBAAgB,2BAA2B,CACzC,KAAK,EAAE,OAAO,EACd,oBAAoB,EAAE,MAAM,GAC3B,OAAO,CA2BT;AAED;;;;;GAKG;AACH,wBAAgB,6BAA6B,CAC3C,KAAK,EAAE,oBAAoB,EAC3B,eAAe,CAAC,EAAE,MAAM,GACvB,oBAAoB,CA+BtB;AAiED,KAAK,qBAAqB,GAAG;IAC3B,QAAQ,CAAC,EAAE,KAAK,CAAC;QACf,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,OAAO,CAAC,EAAE,OAAO,CAAC;QAClB,WAAW,CAAC,EAAE,OAAO,CAAC;QACtB,YAAY,CAAC,EAAE,OAAO,CAAC;KACxB,CAAC,CAAC;IACH,IAAI,CAAC,EAAE;QACL,yBAAyB,CAAC,EAAE,MAAM,CAAC;QACnC,qBAAqB,CAAC,EAAE,MAAM,CAAC;KAChC,CAAC;CACH,CAAC;AAEF,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,qBAAqB,GAAG,MAAM,GAAG,IAAI,CAYrF;AAQD,wBAAsB,mBAAmB,CACvC,GAAG,EAAE,GAAG,EACR,MAAM,EAAE,eAAe,EACvB,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,MAAM,EACd,gBAAgB,EAAE,MAAM,EACxB,MAAM,EAAE;IAAE,IAAI,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,CAAC;IAAC,KAAK,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,CAAA;CAAE,GAClF,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAuFxB;AAYD,oFAAoF;AACpF,wBAAgB,uBAAuB,CACrC,QAAQ,EAAE,WAAW,EAAE,EACvB,gBAAgB,EAAE,MAAM,GACvB,MAAM,CAWR;AA4CD,8EAA8E;AAC9E,wBAAgB,eAAe,CAAC,MAAM,EAAE,eAAe,GAAG,MAAM,CAe/D;AAMD;;;;;GAKG;AACH,wBAAgB,6BAA6B,CAAC,KAAK,EAAE,OAAO,GAAG,eAAe,CAc7E;AAED,MAAM,CAAC,OAAO,UAAU,QAAQ,CAAC,GAAG,EAAE,GAAG,QAozBxC"}
|
package/dist/index.js
CHANGED
|
@@ -18429,7 +18429,7 @@ var require_view = __commonJS({
|
|
|
18429
18429
|
var debug = require_src()("express:view");
|
|
18430
18430
|
var path = __require("path");
|
|
18431
18431
|
var fs = __require("fs");
|
|
18432
|
-
var
|
|
18432
|
+
var dirname4 = path.dirname;
|
|
18433
18433
|
var basename = path.basename;
|
|
18434
18434
|
var extname = path.extname;
|
|
18435
18435
|
var join4 = path.join;
|
|
@@ -18468,7 +18468,7 @@ var require_view = __commonJS({
|
|
|
18468
18468
|
for (var i = 0; i < roots.length && !path2; i++) {
|
|
18469
18469
|
var root = roots[i];
|
|
18470
18470
|
var loc = resolve4(root, name);
|
|
18471
|
-
var dir =
|
|
18471
|
+
var dir = dirname4(loc);
|
|
18472
18472
|
var file = basename(loc);
|
|
18473
18473
|
path2 = this.resolve(dir, file);
|
|
18474
18474
|
}
|
|
@@ -27718,7 +27718,7 @@ var A2AServer = class {
|
|
|
27718
27718
|
// src/index.ts
|
|
27719
27719
|
import { randomUUID as randomUUID7 } from "crypto";
|
|
27720
27720
|
import { readFileSync as readFileSync5 } from "node:fs";
|
|
27721
|
-
import { dirname as
|
|
27721
|
+
import { dirname as dirname3, resolve as resolve3 } from "node:path";
|
|
27722
27722
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
27723
27723
|
|
|
27724
27724
|
// src/agentic-actions.ts
|
|
@@ -28339,10 +28339,10 @@ function isNotFound(err) {
|
|
|
28339
28339
|
// src/openclaw-supervisor-client.ts
|
|
28340
28340
|
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
28341
28341
|
import { spawn } from "node:child_process";
|
|
28342
|
-
import { existsSync, readFileSync as readFileSync3, unlinkSync } from "node:fs";
|
|
28342
|
+
import { closeSync, existsSync, mkdirSync as mkdirSync2, openSync, readFileSync as readFileSync3, realpathSync, unlinkSync, writeFileSync as writeFileSync2 } from "node:fs";
|
|
28343
28343
|
import { homedir as homedir3 } from "node:os";
|
|
28344
28344
|
import net from "node:net";
|
|
28345
|
-
import { resolve } from "node:path";
|
|
28345
|
+
import { dirname as dirname2, resolve } from "node:path";
|
|
28346
28346
|
import { fileURLToPath } from "node:url";
|
|
28347
28347
|
|
|
28348
28348
|
// src/supervisor-protocol.ts
|
|
@@ -28352,6 +28352,7 @@ import { tmpdir } from "node:os";
|
|
|
28352
28352
|
import { dirname, join as join2 } from "node:path";
|
|
28353
28353
|
var SUPERVISOR_PROTOCOL_VERSION = 2;
|
|
28354
28354
|
var SUPERVISOR_RENEW_MS = 5e3;
|
|
28355
|
+
var CURRENT_PROCESS_EPOCH_MS = Math.floor(Date.now() - process.uptime() * 1e3);
|
|
28355
28356
|
function supervisorOwnerHash(lifecycleKey) {
|
|
28356
28357
|
return createHash2("sha256").update(lifecycleKey).digest("hex");
|
|
28357
28358
|
}
|
|
@@ -28367,6 +28368,9 @@ function supervisorSocketPath(stateDir, ownerHash) {
|
|
|
28367
28368
|
function supervisorBootstrapPath(stateDir, ownerHash) {
|
|
28368
28369
|
return join2(supervisorDir(stateDir), `${ownerHash}.bootstrap.json`);
|
|
28369
28370
|
}
|
|
28371
|
+
function supervisorRolloverLockPath(stateDir, ownerHash) {
|
|
28372
|
+
return join2(supervisorDir(stateDir), `${ownerHash}.rollover.lock`);
|
|
28373
|
+
}
|
|
28370
28374
|
function currentBootId() {
|
|
28371
28375
|
try {
|
|
28372
28376
|
return readFileSync2("/proc/sys/kernel/random/boot_id", "utf8").trim();
|
|
@@ -28380,7 +28384,7 @@ function processStartIdentity(pid = process.pid) {
|
|
|
28380
28384
|
const afterName = stat.slice(stat.lastIndexOf(")") + 2).trim().split(/\s+/u);
|
|
28381
28385
|
return `linux-start-ticks:${afterName[19] ?? "unknown"}`;
|
|
28382
28386
|
} catch {
|
|
28383
|
-
if (pid === process.pid) return `epoch-ms:${
|
|
28387
|
+
if (pid === process.pid) return `epoch-ms:${CURRENT_PROCESS_EPOCH_MS}`;
|
|
28384
28388
|
return "unknown";
|
|
28385
28389
|
}
|
|
28386
28390
|
}
|
|
@@ -28390,6 +28394,14 @@ function encodeSupervisorMessage(message) {
|
|
|
28390
28394
|
}
|
|
28391
28395
|
|
|
28392
28396
|
// src/openclaw-supervisor-client.ts
|
|
28397
|
+
function processIsAlive(pid) {
|
|
28398
|
+
try {
|
|
28399
|
+
process.kill(pid, 0);
|
|
28400
|
+
return true;
|
|
28401
|
+
} catch {
|
|
28402
|
+
return false;
|
|
28403
|
+
}
|
|
28404
|
+
}
|
|
28393
28405
|
var OpenClawSupervisorClient = class {
|
|
28394
28406
|
stateDir;
|
|
28395
28407
|
ownerHash;
|
|
@@ -28398,6 +28410,7 @@ var OpenClawSupervisorClient = class {
|
|
|
28398
28410
|
logger;
|
|
28399
28411
|
handlers;
|
|
28400
28412
|
supervisorCliPath;
|
|
28413
|
+
spawnSupervisor;
|
|
28401
28414
|
lease;
|
|
28402
28415
|
socket = null;
|
|
28403
28416
|
startPromise = null;
|
|
@@ -28410,10 +28423,16 @@ var OpenClawSupervisorClient = class {
|
|
|
28410
28423
|
this.stateDir = resolve(input.stateDir ?? process.env.OPENCLAW_STATE_DIR ?? `${homedir3()}/.openclaw`);
|
|
28411
28424
|
this.ownerHash = input.ownerHash;
|
|
28412
28425
|
this.socketPath = supervisorSocketPath(this.stateDir, this.ownerHash);
|
|
28413
|
-
|
|
28426
|
+
let installRoot = resolve(input.config.installRoot);
|
|
28427
|
+
try {
|
|
28428
|
+
installRoot = realpathSync(installRoot);
|
|
28429
|
+
} catch {
|
|
28430
|
+
}
|
|
28431
|
+
this.config = { ...input.config, installRoot };
|
|
28414
28432
|
this.logger = input.logger;
|
|
28415
28433
|
this.handlers = input.handlers;
|
|
28416
28434
|
this.supervisorCliPath = input.supervisorCliPath;
|
|
28435
|
+
this.spawnSupervisor = input.spawnSupervisor;
|
|
28417
28436
|
this.lease = {
|
|
28418
28437
|
ownerKey: input.ownerHash,
|
|
28419
28438
|
instanceId: input.instanceId,
|
|
@@ -28439,15 +28458,18 @@ var OpenClawSupervisorClient = class {
|
|
|
28439
28458
|
this.stopped = false;
|
|
28440
28459
|
const starting = (async () => {
|
|
28441
28460
|
await this.connectOrSpawn();
|
|
28442
|
-
|
|
28443
|
-
|
|
28444
|
-
|
|
28445
|
-
|
|
28446
|
-
|
|
28447
|
-
|
|
28448
|
-
}
|
|
28461
|
+
let claim;
|
|
28462
|
+
try {
|
|
28463
|
+
claim = await this.claimCurrentSupervisor();
|
|
28464
|
+
} catch (error) {
|
|
28465
|
+
if (!String(error).includes("SUPERVISOR_TARGET_MISMATCH")) throw error;
|
|
28466
|
+
claim = await this.rolloverSupervisor("target-mismatch");
|
|
28467
|
+
}
|
|
28468
|
+
if (!this.healthMatchesTarget(claim.health)) {
|
|
28469
|
+
claim = await this.rolloverSupervisor("legacy-health-missing-target");
|
|
28470
|
+
}
|
|
28449
28471
|
const health = claim?.health;
|
|
28450
|
-
if (claim?.state !== "READY" || health?.schemaVersion !== 2 || health.authority !== "process-external-supervisor" || health.state !== "running" || health.ownerKey !== this.ownerHash || health.instanceId !== this.lease.instanceId || health.generation !== this.lease.generation || health.relayClientCount !== (this.config.relay?.enabled && this.config.relay.url ? 1 : 0) || !health.claimedAt) {
|
|
28472
|
+
if (claim?.state !== "READY" || health?.schemaVersion !== 2 || health.authority !== "process-external-supervisor" || health.state !== "running" || health.ownerKey !== this.ownerHash || health.instanceId !== this.lease.instanceId || health.generation !== this.lease.generation || !this.healthMatchesTarget(health) || health.relayClientCount !== (this.config.relay?.enabled && this.config.relay.url ? 1 : 0) || !health.claimedAt) {
|
|
28451
28473
|
throw new Error("OWNERSHIP_PRIMITIVE_UNAVAILABLE:supervisor claim did not return certified READY/CLAIMED health");
|
|
28452
28474
|
}
|
|
28453
28475
|
this.health = health;
|
|
@@ -28460,6 +28482,148 @@ var OpenClawSupervisorClient = class {
|
|
|
28460
28482
|
this.startPromise = starting;
|
|
28461
28483
|
return starting;
|
|
28462
28484
|
}
|
|
28485
|
+
claimCurrentSupervisor() {
|
|
28486
|
+
return this.requestWithMessage({
|
|
28487
|
+
protocolVersion: 2,
|
|
28488
|
+
type: "claim",
|
|
28489
|
+
requestId: randomUUID5(),
|
|
28490
|
+
lease: this.freshLease(),
|
|
28491
|
+
config: this.config
|
|
28492
|
+
}, 15e3);
|
|
28493
|
+
}
|
|
28494
|
+
healthMatchesTarget(health) {
|
|
28495
|
+
if (!health || health.targetVersion !== this.config.version || !health.installRoot) return false;
|
|
28496
|
+
let healthRoot = resolve(health.installRoot);
|
|
28497
|
+
try {
|
|
28498
|
+
healthRoot = realpathSync(healthRoot);
|
|
28499
|
+
} catch {
|
|
28500
|
+
}
|
|
28501
|
+
return healthRoot === this.config.installRoot;
|
|
28502
|
+
}
|
|
28503
|
+
async rolloverSupervisor(reason) {
|
|
28504
|
+
const releaseLock = await this.acquireRolloverLock();
|
|
28505
|
+
try {
|
|
28506
|
+
this.closeSocket(new Error("SUPERVISOR_ROLLOVER_RECHECK"));
|
|
28507
|
+
await this.connectOrSpawn();
|
|
28508
|
+
try {
|
|
28509
|
+
const current = await this.claimCurrentSupervisor();
|
|
28510
|
+
if (this.healthMatchesTarget(current.health)) return current;
|
|
28511
|
+
const stopped = await this.requestWithMessage({
|
|
28512
|
+
protocolVersion: 2,
|
|
28513
|
+
type: "release",
|
|
28514
|
+
requestId: randomUUID5(),
|
|
28515
|
+
lease: this.freshLease()
|
|
28516
|
+
}, 8e3);
|
|
28517
|
+
if (stopped?.state !== "STOPPED") throw new Error("OWNER_STOP_TIMEOUT:legacy supervisor did not return STOPPED");
|
|
28518
|
+
} catch (error) {
|
|
28519
|
+
if (!String(error).includes("SUPERVISOR_TARGET_MISMATCH")) throw error;
|
|
28520
|
+
const stopped = await this.requestWithMessage({
|
|
28521
|
+
protocolVersion: 2,
|
|
28522
|
+
type: "prepareRollover",
|
|
28523
|
+
requestId: randomUUID5(),
|
|
28524
|
+
lease: this.freshLease(),
|
|
28525
|
+
targetVersion: this.config.version,
|
|
28526
|
+
targetInstallRoot: this.config.installRoot
|
|
28527
|
+
}, 8e3);
|
|
28528
|
+
if (stopped?.state !== "STOPPED") throw new Error("OWNER_STOP_TIMEOUT:supervisor rollover did not return STOPPED");
|
|
28529
|
+
}
|
|
28530
|
+
const oldHealth = this.health;
|
|
28531
|
+
this.closeSocket(new Error("SUPERVISOR_ROLLOVER"));
|
|
28532
|
+
await this.waitForSupervisorStop(oldHealth, 1e4);
|
|
28533
|
+
const target = await this.claimTargetAfterRollover(reason);
|
|
28534
|
+
this.logger.info(`[perkos-a2a] Supervisor rollover complete targetVersion=${this.config.version}`);
|
|
28535
|
+
return target;
|
|
28536
|
+
} finally {
|
|
28537
|
+
releaseLock();
|
|
28538
|
+
}
|
|
28539
|
+
}
|
|
28540
|
+
/**
|
|
28541
|
+
* OpenClaw can register two adjacent hot-reload generations while its config
|
|
28542
|
+
* file is settling. The first generation may successfully bootstrap the
|
|
28543
|
+
* target supervisor just as its client socket is replaced. Reconnect to that
|
|
28544
|
+
* single fenced supervisor instead of abandoning the agent offline.
|
|
28545
|
+
*/
|
|
28546
|
+
async claimTargetAfterRollover(reason) {
|
|
28547
|
+
let lastError;
|
|
28548
|
+
for (let attempt = 1; attempt <= 3; attempt += 1) {
|
|
28549
|
+
try {
|
|
28550
|
+
await this.connectOrSpawn();
|
|
28551
|
+
const target = await this.claimCurrentSupervisor();
|
|
28552
|
+
if (!this.healthMatchesTarget(target.health)) {
|
|
28553
|
+
throw new Error(`SUPERVISOR_ROLLOVER_TARGET_MISMATCH:${reason}`);
|
|
28554
|
+
}
|
|
28555
|
+
return target;
|
|
28556
|
+
} catch (error) {
|
|
28557
|
+
lastError = error;
|
|
28558
|
+
this.closeSocket(new Error("SUPERVISOR_ROLLOVER_TARGET_RETRY"));
|
|
28559
|
+
this.logger.error(
|
|
28560
|
+
`[perkos-a2a] Supervisor target claim retry ${attempt}/3: ${error instanceof Error ? error.message : String(error)}`
|
|
28561
|
+
);
|
|
28562
|
+
if (attempt < 3) await new Promise((resolveWait) => setTimeout(resolveWait, 250));
|
|
28563
|
+
}
|
|
28564
|
+
}
|
|
28565
|
+
throw new Error(
|
|
28566
|
+
`SUPERVISOR_ROLLOVER_TARGET_START_FAILED:${lastError instanceof Error ? lastError.message : String(lastError)}`
|
|
28567
|
+
);
|
|
28568
|
+
}
|
|
28569
|
+
async acquireRolloverLock(timeoutMs = 15e3) {
|
|
28570
|
+
const path = supervisorRolloverLockPath(this.stateDir, this.ownerHash);
|
|
28571
|
+
mkdirSync2(dirname2(path), { recursive: true, mode: 448 });
|
|
28572
|
+
const deadline = Date.now() + timeoutMs;
|
|
28573
|
+
while (Date.now() < deadline) {
|
|
28574
|
+
try {
|
|
28575
|
+
const fd = openSync(path, "wx", 384);
|
|
28576
|
+
writeFileSync2(fd, `${JSON.stringify({
|
|
28577
|
+
schemaVersion: 1,
|
|
28578
|
+
instanceId: this.lease.instanceId,
|
|
28579
|
+
pid: process.pid,
|
|
28580
|
+
processStartTime: processStartIdentity(),
|
|
28581
|
+
expiresAt: new Date(Date.now() + 2e4).toISOString()
|
|
28582
|
+
})}
|
|
28583
|
+
`);
|
|
28584
|
+
closeSync(fd);
|
|
28585
|
+
return () => {
|
|
28586
|
+
try {
|
|
28587
|
+
const lock = JSON.parse(readFileSync3(path, "utf8"));
|
|
28588
|
+
if (lock.instanceId === this.lease.instanceId) unlinkSync(path);
|
|
28589
|
+
} catch {
|
|
28590
|
+
}
|
|
28591
|
+
};
|
|
28592
|
+
} catch (error) {
|
|
28593
|
+
if (error.code !== "EEXIST") throw error;
|
|
28594
|
+
try {
|
|
28595
|
+
const lock = JSON.parse(readFileSync3(path, "utf8"));
|
|
28596
|
+
const expired = !lock.expiresAt || Date.parse(lock.expiresAt) <= Date.now();
|
|
28597
|
+
const observedStart = lock.pid ? processStartIdentity(lock.pid) : "unknown";
|
|
28598
|
+
const reusedLinuxPid = observedStart.startsWith("linux-start-ticks:") && Boolean(lock.processStartTime?.startsWith("linux-start-ticks:")) && observedStart !== lock.processStartTime;
|
|
28599
|
+
const dead = !lock.pid || !processIsAlive(lock.pid) || reusedLinuxPid;
|
|
28600
|
+
if (expired || dead) {
|
|
28601
|
+
unlinkSync(path);
|
|
28602
|
+
continue;
|
|
28603
|
+
}
|
|
28604
|
+
} catch {
|
|
28605
|
+
try {
|
|
28606
|
+
unlinkSync(path);
|
|
28607
|
+
} catch {
|
|
28608
|
+
}
|
|
28609
|
+
continue;
|
|
28610
|
+
}
|
|
28611
|
+
await new Promise((resolveWait) => setTimeout(resolveWait, 100));
|
|
28612
|
+
}
|
|
28613
|
+
}
|
|
28614
|
+
throw new Error("ROLLOVER_LOCK_TIMEOUT");
|
|
28615
|
+
}
|
|
28616
|
+
async waitForSupervisorStop(health, timeoutMs) {
|
|
28617
|
+
const deadline = Date.now() + timeoutMs;
|
|
28618
|
+
while (Date.now() < deadline) {
|
|
28619
|
+
const socketGone = !existsSync(this.socketPath);
|
|
28620
|
+
const processGone = !health || processStartIdentity(health.pid) !== health.supervisorStartTime;
|
|
28621
|
+
if (socketGone && (processGone || health?.state === "stopped")) return;
|
|
28622
|
+
if (socketGone && processStartIdentity(health?.pid ?? -1) === "unknown") return;
|
|
28623
|
+
await new Promise((resolveWait) => setTimeout(resolveWait, 100));
|
|
28624
|
+
}
|
|
28625
|
+
throw new Error("OWNER_STOP_TIMEOUT:old supervisor process/socket remained active");
|
|
28626
|
+
}
|
|
28463
28627
|
stop() {
|
|
28464
28628
|
if (this.stopPromise) return this.stopPromise;
|
|
28465
28629
|
if (this.stopped && !this.socket) return Promise.resolve();
|
|
@@ -28541,27 +28705,29 @@ var OpenClawSupervisorClient = class {
|
|
|
28541
28705
|
} catch {
|
|
28542
28706
|
}
|
|
28543
28707
|
const cliPath = this.supervisorCliPath ?? fileURLToPath(new URL("./openclaw-supervisor-cli.js", import.meta.url));
|
|
28544
|
-
if (!existsSync(cliPath)) {
|
|
28545
|
-
throw new Error(`OWNERSHIP_PRIMITIVE_UNAVAILABLE:supervisor CLI is missing at ${cliPath}`);
|
|
28546
|
-
}
|
|
28708
|
+
if (!this.spawnSupervisor && !existsSync(cliPath)) throw new Error(`OWNERSHIP_PRIMITIVE_UNAVAILABLE:supervisor CLI is missing at ${cliPath}`);
|
|
28547
28709
|
const bootstrapPath = supervisorBootstrapPath(this.stateDir, this.ownerHash);
|
|
28548
28710
|
try {
|
|
28549
28711
|
unlinkSync(bootstrapPath);
|
|
28550
28712
|
} catch {
|
|
28551
28713
|
}
|
|
28552
28714
|
let childExit;
|
|
28553
|
-
|
|
28554
|
-
|
|
28555
|
-
|
|
28556
|
-
|
|
28557
|
-
|
|
28558
|
-
|
|
28559
|
-
|
|
28560
|
-
|
|
28561
|
-
|
|
28562
|
-
|
|
28563
|
-
|
|
28564
|
-
|
|
28715
|
+
if (this.spawnSupervisor) {
|
|
28716
|
+
await this.spawnSupervisor();
|
|
28717
|
+
} else {
|
|
28718
|
+
const child = spawn(process.execPath, [cliPath, "--state-dir", this.stateDir, "--owner-key", this.ownerHash], {
|
|
28719
|
+
detached: true,
|
|
28720
|
+
stdio: "ignore",
|
|
28721
|
+
env: { ...process.env }
|
|
28722
|
+
});
|
|
28723
|
+
child.once("error", (error) => {
|
|
28724
|
+
childExit = `spawn error: ${error.message}`;
|
|
28725
|
+
});
|
|
28726
|
+
child.once("exit", (code, signal) => {
|
|
28727
|
+
childExit = `exit code=${code ?? "null"} signal=${signal ?? "none"}`;
|
|
28728
|
+
});
|
|
28729
|
+
child.unref();
|
|
28730
|
+
}
|
|
28565
28731
|
const deadline = Date.now() + 1e4;
|
|
28566
28732
|
let lastError;
|
|
28567
28733
|
while (Date.now() < deadline) {
|
|
@@ -28692,10 +28858,10 @@ var OpenClawSupervisorClient = class {
|
|
|
28692
28858
|
import { createHmac } from "node:crypto";
|
|
28693
28859
|
import {
|
|
28694
28860
|
chmodSync as chmodSync2,
|
|
28695
|
-
mkdirSync as
|
|
28861
|
+
mkdirSync as mkdirSync3,
|
|
28696
28862
|
readFileSync as readFileSync4,
|
|
28697
28863
|
renameSync as renameSync2,
|
|
28698
|
-
writeFileSync as
|
|
28864
|
+
writeFileSync as writeFileSync3
|
|
28699
28865
|
} from "node:fs";
|
|
28700
28866
|
import { homedir as homedir4 } from "node:os";
|
|
28701
28867
|
import { join as join3, resolve as resolve2 } from "node:path";
|
|
@@ -28754,10 +28920,10 @@ function recordRuntimeLoadEvidence(input) {
|
|
|
28754
28920
|
bootId: currentBootId(),
|
|
28755
28921
|
pluginRoot: resolve2(input.pluginRoot)
|
|
28756
28922
|
};
|
|
28757
|
-
|
|
28923
|
+
mkdirSync3(evidenceDir, { recursive: true, mode: 448 });
|
|
28758
28924
|
const target = runtimeStatusPath(input.stateDir);
|
|
28759
28925
|
const temporary = `${target}.${process.pid}.tmp`;
|
|
28760
|
-
|
|
28926
|
+
writeFileSync3(temporary, `${JSON.stringify(status, null, 2)}
|
|
28761
28927
|
`, { mode: 384 });
|
|
28762
28928
|
renameSync2(temporary, target);
|
|
28763
28929
|
chmodSync2(target, 384);
|
|
@@ -29466,22 +29632,36 @@ function a2aLifecycleKey(config) {
|
|
|
29466
29632
|
}
|
|
29467
29633
|
const heartbeatUrl = config.platform?.heartbeatUrl || "";
|
|
29468
29634
|
const idFromHeartbeat = heartbeatUrl.match(/\/agents\/([^/]+)\/heartbeat(?:\?|$)/u)?.[1];
|
|
29469
|
-
const agentId = config.platform?.agentId || idFromHeartbeat || config.agentName
|
|
29635
|
+
const agentId = config.platform?.agentId || idFromHeartbeat || safeAgentName(config.agentName).toLowerCase();
|
|
29470
29636
|
return `${relayUrl}|${agentId}`;
|
|
29471
29637
|
}
|
|
29472
|
-
function
|
|
29473
|
-
|
|
29474
|
-
|
|
29475
|
-
|
|
29476
|
-
|
|
29477
|
-
|
|
29638
|
+
function safeAgentName(value) {
|
|
29639
|
+
return typeof value === "string" && value.trim() ? value.trim() : "agent";
|
|
29640
|
+
}
|
|
29641
|
+
function normalizeOpenClawPluginConfig(value) {
|
|
29642
|
+
const raw = value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
29643
|
+
const platform = raw.platform && typeof raw.platform === "object" ? raw.platform : void 0;
|
|
29644
|
+
const heartbeatAgentId = platform?.heartbeatUrl?.match(/\/agents\/([^/]+)\/heartbeat/u)?.[1];
|
|
29645
|
+
const agentName = safeAgentName(raw.agentName || platform?.agentId || heartbeatAgentId);
|
|
29646
|
+
return {
|
|
29647
|
+
...raw,
|
|
29648
|
+
agentName,
|
|
29649
|
+
port: typeof raw.port === "number" ? raw.port : 5050,
|
|
29650
|
+
skills: Array.isArray(raw.skills) ? raw.skills : [],
|
|
29651
|
+
peers: raw.peers && typeof raw.peers === "object" ? raw.peers : {}
|
|
29478
29652
|
};
|
|
29653
|
+
}
|
|
29654
|
+
function register(api) {
|
|
29655
|
+
const pluginConfig = normalizeOpenClawPluginConfig(
|
|
29656
|
+
api.config?.plugins?.entries?.["perkos-a2a"]?.config
|
|
29657
|
+
);
|
|
29479
29658
|
const logger = api.logger || console;
|
|
29480
29659
|
const pluginVersion = loadedPluginVersion();
|
|
29660
|
+
const pluginRoot = resolve3(dirname3(fileURLToPath2(import.meta.url)), "..");
|
|
29481
29661
|
recordRuntimeLoadEvidence({
|
|
29482
29662
|
config: pluginConfig,
|
|
29483
29663
|
version: pluginVersion,
|
|
29484
|
-
pluginRoot
|
|
29664
|
+
pluginRoot,
|
|
29485
29665
|
logger
|
|
29486
29666
|
});
|
|
29487
29667
|
const server = new A2AServer(pluginConfig, logger, { relayOwnership: "external-required" });
|
|
@@ -29738,7 +29918,7 @@ function register(api) {
|
|
|
29738
29918
|
if (pluginConfig.chat?.enabled && !chatApiKey) {
|
|
29739
29919
|
logger.error("[perkos-chat] chat.enabled=true but no apiKey (set chat.apiKey or fall back from relay.apiKey)");
|
|
29740
29920
|
}
|
|
29741
|
-
const platformAgentId = pluginConfig.platform?.agentId || pluginConfig.platform?.heartbeatUrl?.match(/\/agents\/([^/]+)\/heartbeat/u)?.[1] || pluginConfig.agentName
|
|
29921
|
+
const platformAgentId = pluginConfig.platform?.agentId || pluginConfig.platform?.heartbeatUrl?.match(/\/agents\/([^/]+)\/heartbeat/u)?.[1] || safeAgentName(pluginConfig.agentName).toLowerCase();
|
|
29742
29922
|
supervisorClient = new OpenClawSupervisorClient({
|
|
29743
29923
|
ownerHash: supervisorOwnerHash(lifecycleKey),
|
|
29744
29924
|
generation: Date.now(),
|
|
@@ -29747,6 +29927,7 @@ function register(api) {
|
|
|
29747
29927
|
agentName: pluginConfig.agentName,
|
|
29748
29928
|
agentId: platformAgentId,
|
|
29749
29929
|
version: pluginVersion,
|
|
29930
|
+
installRoot: pluginRoot,
|
|
29750
29931
|
relay: pluginConfig.relay,
|
|
29751
29932
|
chat: chatEnabled ? {
|
|
29752
29933
|
enabled: true,
|
|
@@ -30182,6 +30363,7 @@ export {
|
|
|
30182
30363
|
extractOpenClawChatReply,
|
|
30183
30364
|
formatRecentChatContext,
|
|
30184
30365
|
isWebhookEvent,
|
|
30366
|
+
normalizeOpenClawPluginConfig,
|
|
30185
30367
|
omitUnavailableAgentHarness,
|
|
30186
30368
|
openclawChatSessionKey,
|
|
30187
30369
|
runOpenClawChatTurn
|