agentlife 1.4.1 → 1.5.1
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/dist/index.js +150 -9
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2070,6 +2070,24 @@ function visionSurfaceCount(state) {
|
|
|
2070
2070
|
n++;
|
|
2071
2071
|
return n;
|
|
2072
2072
|
}
|
|
2073
|
+
function specialistHasAnySurface(state, specialistId) {
|
|
2074
|
+
if (!state.surfaceDb)
|
|
2075
|
+
return false;
|
|
2076
|
+
for (const id of state.surfaceDb.keys()) {
|
|
2077
|
+
if (state.surfaceDb.getAgentId(id) === specialistId)
|
|
2078
|
+
return true;
|
|
2079
|
+
}
|
|
2080
|
+
return false;
|
|
2081
|
+
}
|
|
2082
|
+
function specialistIdFromSessionKey(sessionKey) {
|
|
2083
|
+
if (!sessionKey)
|
|
2084
|
+
return null;
|
|
2085
|
+
const parts = sessionKey.split(":");
|
|
2086
|
+
if (parts.length < 2 || parts[0] !== "agent")
|
|
2087
|
+
return null;
|
|
2088
|
+
const id = parts[1];
|
|
2089
|
+
return id && id.length > 0 ? id : null;
|
|
2090
|
+
}
|
|
2073
2091
|
async function sendSystemMessage(state, agentId, message, idempotencyKey, log) {
|
|
2074
2092
|
if (!state.runCommand) {
|
|
2075
2093
|
log(`[cold-start] sendSystemMessage ${idempotencyKey}: skipped — runCommand not available`);
|
|
@@ -2176,6 +2194,11 @@ async function transition(state, runtime, log, trigger) {
|
|
|
2176
2194
|
return cur;
|
|
2177
2195
|
}
|
|
2178
2196
|
if (cur.phase === "AWAITING_BASELINE") {
|
|
2197
|
+
const specialistId = specialistIdFromSessionKey(cur.actionSessionKey);
|
|
2198
|
+
if (specialistId && !specialistHasAnySurface(state, specialistId)) {
|
|
2199
|
+
log(`[cold-start] ${specialistId} replied in AWAITING_BASELINE with no surface ` + `attributed — [system:start] contract violation (expected a warmup-* input surface)`);
|
|
2200
|
+
return enterFailed(state, log, "agent_produced_no_surface");
|
|
2201
|
+
}
|
|
2179
2202
|
return cur;
|
|
2180
2203
|
}
|
|
2181
2204
|
return cur;
|
|
@@ -5289,9 +5312,12 @@ import * as fs7 from "node:fs";
|
|
|
5289
5312
|
import * as os5 from "node:os";
|
|
5290
5313
|
import * as path9 from "node:path";
|
|
5291
5314
|
var AGENTLIFE_DIR = path9.join(os5.homedir(), ".openclaw", "agentlife");
|
|
5292
|
-
var DEVICE_FILE = path9.join(AGENTLIFE_DIR, "device.json");
|
|
5293
5315
|
var TUNNEL_FILE = path9.join(AGENTLIFE_DIR, "tunnel.json");
|
|
5294
5316
|
var BIN_DIR = path9.join(AGENTLIFE_DIR, "bin");
|
|
5317
|
+
var PAIR_REQUEST_MARKER = path9.join(AGENTLIFE_DIR, "pair-requested");
|
|
5318
|
+
var IDENTITY_DIR = path9.join(os5.homedir(), ".agentlife");
|
|
5319
|
+
var DEVICE_FILE = path9.join(IDENTITY_DIR, "device.json");
|
|
5320
|
+
var LEGACY_DEVICE_FILE = path9.join(AGENTLIFE_DIR, "device.json");
|
|
5295
5321
|
var CLOUDFLARED_BIN = os5.platform() === "win32" ? "cloudflared.exe" : "cloudflared";
|
|
5296
5322
|
var CLOUDFLARED_PATH = path9.join(BIN_DIR, CLOUDFLARED_BIN);
|
|
5297
5323
|
var API_BASE = process.env.AGENTLIFE_API_BASE || "https://api.agentlife.app";
|
|
@@ -5306,9 +5332,12 @@ function createInitialState() {
|
|
|
5306
5332
|
tunnelInfo: null,
|
|
5307
5333
|
process: null,
|
|
5308
5334
|
restartTimer: null,
|
|
5335
|
+
provisionRetryTimer: null,
|
|
5309
5336
|
stopped: false,
|
|
5310
5337
|
readyPromise: promise,
|
|
5311
|
-
readyResolve: resolver
|
|
5338
|
+
readyResolve: resolver,
|
|
5339
|
+
inFlight: null,
|
|
5340
|
+
binPath: null
|
|
5312
5341
|
};
|
|
5313
5342
|
}
|
|
5314
5343
|
var state = createInitialState();
|
|
@@ -5336,6 +5365,10 @@ function stopCloudflaredSupervisor() {
|
|
|
5336
5365
|
clearTimeout(state.restartTimer);
|
|
5337
5366
|
state.restartTimer = null;
|
|
5338
5367
|
}
|
|
5368
|
+
if (state.provisionRetryTimer) {
|
|
5369
|
+
clearTimeout(state.provisionRetryTimer);
|
|
5370
|
+
state.provisionRetryTimer = null;
|
|
5371
|
+
}
|
|
5339
5372
|
if (state.process) {
|
|
5340
5373
|
try {
|
|
5341
5374
|
state.process.kill("SIGTERM");
|
|
@@ -5344,10 +5377,30 @@ function stopCloudflaredSupervisor() {
|
|
|
5344
5377
|
}
|
|
5345
5378
|
}
|
|
5346
5379
|
async function bootstrap() {
|
|
5347
|
-
const result = await
|
|
5348
|
-
state.tunnelInfo = result;
|
|
5380
|
+
const result = await runBootstrap();
|
|
5349
5381
|
state.readyResolve(result);
|
|
5350
5382
|
}
|
|
5383
|
+
async function runBootstrap() {
|
|
5384
|
+
if (state.inFlight)
|
|
5385
|
+
return state.inFlight;
|
|
5386
|
+
const promise = doBootstrap().then((result) => {
|
|
5387
|
+
if (result)
|
|
5388
|
+
state.tunnelInfo = result;
|
|
5389
|
+
return result;
|
|
5390
|
+
}).finally(() => {
|
|
5391
|
+
state.inFlight = null;
|
|
5392
|
+
});
|
|
5393
|
+
state.inFlight = promise;
|
|
5394
|
+
return promise;
|
|
5395
|
+
}
|
|
5396
|
+
function triggerProvision() {
|
|
5397
|
+
if (state.stopped)
|
|
5398
|
+
return;
|
|
5399
|
+
requestPair();
|
|
5400
|
+
runBootstrap().catch((err) => {
|
|
5401
|
+
console.warn("[cloudflared-supervisor] triggered provision failed:", err?.message ?? err);
|
|
5402
|
+
});
|
|
5403
|
+
}
|
|
5351
5404
|
async function doBootstrap() {
|
|
5352
5405
|
ensureDirs();
|
|
5353
5406
|
const identity = loadOrCreateDeviceIdentity();
|
|
@@ -5357,29 +5410,84 @@ async function doBootstrap() {
|
|
|
5357
5410
|
return null;
|
|
5358
5411
|
}
|
|
5359
5412
|
let tunnelInfo = loadCachedTunnel();
|
|
5360
|
-
if (!tunnelInfo
|
|
5413
|
+
if (!tunnelInfo) {
|
|
5414
|
+
if (!isPairRequested()) {
|
|
5415
|
+
console.log("[cloudflared-supervisor] no tunnel cached and no pair request — staying idle (LAN-only)");
|
|
5416
|
+
return null;
|
|
5417
|
+
}
|
|
5418
|
+
console.log("[cloudflared-supervisor] pair requested — provisioning fresh tunnel");
|
|
5361
5419
|
tunnelInfo = await provisionTunnel(identity);
|
|
5362
5420
|
if (!tunnelInfo) {
|
|
5363
|
-
console.
|
|
5421
|
+
console.log("[cloudflared-supervisor] provision attempt did not yield a tunnel — will retry on next pair request");
|
|
5364
5422
|
return null;
|
|
5365
5423
|
}
|
|
5366
5424
|
persistTunnel(tunnelInfo);
|
|
5425
|
+
clearPairRequest();
|
|
5426
|
+
} else if (isPairRequested()) {
|
|
5427
|
+
console.log("[cloudflared-supervisor] pair requested with cache present — re-provisioning");
|
|
5428
|
+
const refreshed = await provisionTunnel(identity);
|
|
5429
|
+
if (refreshed) {
|
|
5430
|
+
tunnelInfo = refreshed;
|
|
5431
|
+
persistTunnel(tunnelInfo);
|
|
5432
|
+
} else {
|
|
5433
|
+
console.warn("[cloudflared-supervisor] re-provision failed — keeping cached tunnel");
|
|
5434
|
+
}
|
|
5435
|
+
clearPairRequest();
|
|
5367
5436
|
}
|
|
5368
5437
|
console.log(`[cloudflared-supervisor] tunnel ready: ${tunnelInfo.tunnelUrl}`);
|
|
5369
5438
|
startCloudflaredProcess(binPath, tunnelInfo.tunnelToken);
|
|
5370
5439
|
return tunnelInfo;
|
|
5371
5440
|
}
|
|
5441
|
+
function isPairRequested() {
|
|
5442
|
+
return fs7.existsSync(PAIR_REQUEST_MARKER);
|
|
5443
|
+
}
|
|
5444
|
+
function requestPair() {
|
|
5445
|
+
try {
|
|
5446
|
+
if (!fs7.existsSync(AGENTLIFE_DIR))
|
|
5447
|
+
fs7.mkdirSync(AGENTLIFE_DIR, { recursive: true, mode: 448 });
|
|
5448
|
+
fs7.writeFileSync(PAIR_REQUEST_MARKER, String(Date.now()), { mode: 384 });
|
|
5449
|
+
} catch (err) {
|
|
5450
|
+
console.warn(`[cloudflared-supervisor] failed to write pair-request marker: ${err?.message ?? err}`);
|
|
5451
|
+
}
|
|
5452
|
+
}
|
|
5453
|
+
function clearPairRequest() {
|
|
5454
|
+
try {
|
|
5455
|
+
fs7.unlinkSync(PAIR_REQUEST_MARKER);
|
|
5456
|
+
} catch {}
|
|
5457
|
+
}
|
|
5458
|
+
function parseRetryAfter(value) {
|
|
5459
|
+
const n = value == null ? NaN : parseInt(value.trim(), 10);
|
|
5460
|
+
if (!Number.isFinite(n) || n < 1)
|
|
5461
|
+
return 60;
|
|
5462
|
+
return Math.min(n, 3600);
|
|
5463
|
+
}
|
|
5464
|
+
function scheduleProvisionRetry(delayMs) {
|
|
5465
|
+
if (state.stopped)
|
|
5466
|
+
return;
|
|
5467
|
+
if (state.provisionRetryTimer) {
|
|
5468
|
+
clearTimeout(state.provisionRetryTimer);
|
|
5469
|
+
state.provisionRetryTimer = null;
|
|
5470
|
+
}
|
|
5471
|
+
state.provisionRetryTimer = setTimeout(() => {
|
|
5472
|
+
state.provisionRetryTimer = null;
|
|
5473
|
+
if (state.stopped || state.tunnelInfo)
|
|
5474
|
+
return;
|
|
5475
|
+
triggerProvision();
|
|
5476
|
+
}, delayMs);
|
|
5477
|
+
}
|
|
5372
5478
|
function ensureDirs() {
|
|
5373
5479
|
if (!fs7.existsSync(AGENTLIFE_DIR))
|
|
5374
5480
|
fs7.mkdirSync(AGENTLIFE_DIR, { recursive: true, mode: 448 });
|
|
5375
5481
|
if (!fs7.existsSync(BIN_DIR))
|
|
5376
5482
|
fs7.mkdirSync(BIN_DIR, { recursive: true, mode: 448 });
|
|
5483
|
+
if (!fs7.existsSync(IDENTITY_DIR))
|
|
5484
|
+
fs7.mkdirSync(IDENTITY_DIR, { recursive: true, mode: 448 });
|
|
5377
5485
|
}
|
|
5378
|
-
function
|
|
5379
|
-
if (!fs7.existsSync(
|
|
5486
|
+
function readIdentityFile(filePath) {
|
|
5487
|
+
if (!fs7.existsSync(filePath))
|
|
5380
5488
|
return null;
|
|
5381
5489
|
try {
|
|
5382
|
-
const raw = fs7.readFileSync(
|
|
5490
|
+
const raw = fs7.readFileSync(filePath, "utf-8");
|
|
5383
5491
|
const parsed = JSON.parse(raw);
|
|
5384
5492
|
if (typeof parsed.deviceId === "string" && typeof parsed.deviceSecret === "string") {
|
|
5385
5493
|
return { deviceId: parsed.deviceId, deviceSecret: parsed.deviceSecret };
|
|
@@ -5389,10 +5497,35 @@ function loadDeviceIdentity() {
|
|
|
5389
5497
|
return null;
|
|
5390
5498
|
}
|
|
5391
5499
|
}
|
|
5500
|
+
function loadDeviceIdentity() {
|
|
5501
|
+
const current = readIdentityFile(DEVICE_FILE);
|
|
5502
|
+
if (current)
|
|
5503
|
+
return current;
|
|
5504
|
+
const legacy = readIdentityFile(LEGACY_DEVICE_FILE);
|
|
5505
|
+
if (!legacy)
|
|
5506
|
+
return null;
|
|
5507
|
+
try {
|
|
5508
|
+
if (!fs7.existsSync(IDENTITY_DIR))
|
|
5509
|
+
fs7.mkdirSync(IDENTITY_DIR, { recursive: true, mode: 448 });
|
|
5510
|
+
fs7.writeFileSync(DEVICE_FILE, JSON.stringify(legacy, null, 2), { mode: 384 });
|
|
5511
|
+
try {
|
|
5512
|
+
fs7.chmodSync(DEVICE_FILE, 384);
|
|
5513
|
+
} catch {}
|
|
5514
|
+
try {
|
|
5515
|
+
fs7.unlinkSync(LEGACY_DEVICE_FILE);
|
|
5516
|
+
} catch {}
|
|
5517
|
+
console.log(`[cloudflared-supervisor] migrated device.json to ${DEVICE_FILE}`);
|
|
5518
|
+
} catch (err) {
|
|
5519
|
+
console.warn(`[cloudflared-supervisor] device.json migration failed: ${err?.message ?? err}`);
|
|
5520
|
+
}
|
|
5521
|
+
return legacy;
|
|
5522
|
+
}
|
|
5392
5523
|
function loadOrCreateDeviceIdentity() {
|
|
5393
5524
|
const existing = loadDeviceIdentity();
|
|
5394
5525
|
if (existing)
|
|
5395
5526
|
return existing;
|
|
5527
|
+
if (!fs7.existsSync(IDENTITY_DIR))
|
|
5528
|
+
fs7.mkdirSync(IDENTITY_DIR, { recursive: true, mode: 448 });
|
|
5396
5529
|
const identity = {
|
|
5397
5530
|
deviceId: crypto2.randomUUID(),
|
|
5398
5531
|
deviceSecret: crypto2.randomBytes(32).toString("base64url")
|
|
@@ -5436,6 +5569,12 @@ async function provisionTunnel(identity) {
|
|
|
5436
5569
|
});
|
|
5437
5570
|
if (!response.ok) {
|
|
5438
5571
|
const body = await response.text();
|
|
5572
|
+
if (response.status === 503) {
|
|
5573
|
+
const retryAfter = parseRetryAfter(response.headers.get("retry-after"));
|
|
5574
|
+
console.warn(`[cloudflared-supervisor] provision rate-limited by server (HTTP 503); ` + `scheduling retry in ${retryAfter}s`);
|
|
5575
|
+
scheduleProvisionRetry(retryAfter * 1000);
|
|
5576
|
+
return null;
|
|
5577
|
+
}
|
|
5439
5578
|
console.warn(`[cloudflared-supervisor] provision HTTP ${response.status}: ${body.slice(0, 200)}`);
|
|
5440
5579
|
return null;
|
|
5441
5580
|
}
|
|
@@ -5718,6 +5857,8 @@ function registerWebApp(api) {
|
|
|
5718
5857
|
return true;
|
|
5719
5858
|
const bootstrapToken = mintBootstrapToken();
|
|
5720
5859
|
const tunnelInfo = getTunnelInfo();
|
|
5860
|
+
if (!tunnelInfo?.tunnelUrl)
|
|
5861
|
+
triggerProvision();
|
|
5721
5862
|
const payload = { bootstrapToken };
|
|
5722
5863
|
if (tunnelInfo?.tunnelUrl)
|
|
5723
5864
|
payload.tunnelUrl = tunnelInfo.tunnelUrl;
|