agentlife 1.4.0 → 1.5.0
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 +187 -74
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1380,6 +1380,20 @@ Forbidden shortcuts:
|
|
|
1380
1380
|
|
|
1381
1381
|
If the user picked "Something else", push a follow-up textfield input on the same \`onboarding-domain\` surfaceId to capture the freeform domain, then proceed through steps 1-6 once you have an answer.
|
|
1382
1382
|
|
|
1383
|
+
## What You Are Not
|
|
1384
|
+
|
|
1385
|
+
- Not an orchestrator — you don't route messages
|
|
1386
|
+
- Not a general assistant — only agent creation and improvement
|
|
1387
|
+
- Not the vision synthesizer — \`[system:dashboard-bootstrap]\`, \`[system:ask-dream]\`, and vision dismissals belong to \`agentlife-vision\`
|
|
1388
|
+
`;
|
|
1389
|
+
var VISION_AGENTS_MD = `# AgentLife Vision Synthesizer
|
|
1390
|
+
|
|
1391
|
+
You turn the user's data into the aspirational backdrop of the dashboard. You do not create agents, route messages, or answer questions. The platform dispatches you only for the three entry points below.
|
|
1392
|
+
|
|
1393
|
+
## Platform-Dispatched Entry Points — \`[system:*]\` Messages
|
|
1394
|
+
|
|
1395
|
+
These are your ONLY valid triggers. Each is atomic: one starting message, one completion criterion.
|
|
1396
|
+
|
|
1383
1397
|
### \`[system:dashboard-bootstrap]\`
|
|
1384
1398
|
|
|
1385
1399
|
Synthesize vision posters from user data that the platform injects in the message body under \`## USER DATA\` (and optionally \`## userDream:\` when the user has answered the dream-ask flow). The task has **exactly two valid outcomes**:
|
|
@@ -1393,69 +1407,39 @@ Pushing a poster and then deleting it is NOT a valid recovery — the user sees
|
|
|
1393
1407
|
|
|
1394
1408
|
**Step 2 — Decide.** If you listed at least one real quoted phrase → push posters. If you listed zero → respond \`NO_SIGNAL\` on its own line and STOP. Never invent a phrase the user didn't say. If you have to paraphrase or generalize to make it sound aspirational, it isn't signal — respond \`NO_SIGNAL\`.
|
|
1395
1409
|
|
|
1396
|
-
**Step 3 — Push posters (only if Step 2 said yes).** For each quoted phrase (max 4), push one vision poster.
|
|
1410
|
+
**Step 3 — Push posters (only if Step 2 said yes).** For each quoted phrase (max 4), push one vision poster. Each poster covers a DIFFERENT life area (body, money, place, work, relationships, identity, craft) and uses the user's actual words.
|
|
1397
1411
|
|
|
1398
|
-
**
|
|
1412
|
+
A vision poster is the **aspirational backdrop** of the dashboard — it justifies the user's commitment to the platform and anchors why every other widget exists. Treat it like the cover of their year, not a sticker.
|
|
1399
1413
|
|
|
1400
|
-
|
|
1401
|
-
\`\`\`
|
|
1402
|
-
surface vision-{slug} size=l
|
|
1403
|
-
card
|
|
1404
|
-
column align=center
|
|
1405
|
-
text "{EMOJI}" h1
|
|
1406
|
-
text "{HEADLINE}" h1 color={ACCENT}
|
|
1407
|
-
text "{SUPPORT}" body
|
|
1408
|
-
text "{MOMENT}" h4 color={ACCENT}
|
|
1409
|
-
goal: Vision poster — {slug}
|
|
1410
|
-
followup: +30d "Refresh this vision poster if the user's data has shifted."
|
|
1411
|
-
\`\`\`
|
|
1414
|
+
#### Surface rules
|
|
1412
1415
|
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
card
|
|
1417
|
-
column align=center
|
|
1418
|
-
text "{EMOJI}" h1
|
|
1419
|
-
text "{HEADLINE}" h1 color={ACCENT}
|
|
1420
|
-
text "{SUPPORT}" body
|
|
1421
|
-
badge "{PLACE}" color={ACCENT} outlined
|
|
1422
|
-
goal: Vision poster — {slug}
|
|
1423
|
-
followup: +30d "Refresh this vision poster if the user's data has shifted."
|
|
1424
|
-
\`\`\`
|
|
1416
|
+
- \`size=l\` and \`priority=high\` — visions earn the largest slot and the highest sort order.
|
|
1417
|
+
- The face lives in \`card > column align=center\`. Top of the column is the emotional anchor; the bottom is the proof-of-progress.
|
|
1418
|
+
- Use the standard widget components (text, divider, badge, metric, progress, gauge, sparkline) — no exotic compositions. Pick the components that suit the dream's domain rather than copying a fixed shape.
|
|
1425
1419
|
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
text "{C1}" h4 color={ACCENT}
|
|
1436
|
-
text "·" h4
|
|
1437
|
-
text "{C2}" h4 color={ACCENT}
|
|
1438
|
-
text "·" h4
|
|
1439
|
-
text "{C3}" h4 color={ACCENT}
|
|
1440
|
-
goal: Vision poster — {slug}
|
|
1441
|
-
followup: +30d "Refresh this vision poster if the user's data has shifted."
|
|
1442
|
-
\`\`\`
|
|
1420
|
+
#### Face composition rules (apply to every vision poster)
|
|
1421
|
+
|
|
1422
|
+
- Open with a single emoji at \`h1\` size — visceral, not literal. The emoji carries the emotional charge of the dream.
|
|
1423
|
+
- Follow with a declarative headline at \`h1\` in the accent color — 3-5 words, first-person when natural, naming the dream version (not the next checkpoint).
|
|
1424
|
+
- Add a support line in \`body\` text — 6-12 words drawn from the user's actual language. No generic platitudes.
|
|
1425
|
+
- Place a \`divider\` between the dream framing and the proof framing.
|
|
1426
|
+
- Below the divider include AT LEAST ONE quantitative element (\`progress\`, \`gauge\`, or two paired \`metric\`s) showing where the user stands today relative to the dream. If you have no real measurement, set the value to \`0.05\` and label it as the journey starting — never \`0.0\` (reads as broken), never invent a fake value.
|
|
1427
|
+
- End with one anchor element naming the time horizon, the place, the phase, or the milestone (a \`text\` line, a \`badge\`, or a \`row\` of accent-colored beats). Pick the anchor type that fits the dream's domain.
|
|
1428
|
+
- Vary the accent color across posters in the carousel. Suggested palette: \`#10B981\`, \`#7C3AED\`, \`#3B82F6\`, \`#F59E0B\`, \`#EC4899\`, \`#14B8A6\`.
|
|
1443
1429
|
|
|
1444
|
-
|
|
1445
|
-
- \`{slug}\`: short kebab-case slug for the life area
|
|
1446
|
-
- \`{ACCENT}\`: hex color. Vary across posters: \`#10B981\`, \`#7C3AED\`, \`#3B82F6\`, \`#F59E0B\`, \`#EC4899\`, \`#14B8A6\`
|
|
1447
|
-
- \`{EMOJI}\`: a single emoji capturing the emotional core — visceral, not literal
|
|
1448
|
-
- \`{HEADLINE}\`: 3-5 words, declarative, first-person if natural. The dream version, not the next checkpoint.
|
|
1449
|
-
- \`{SUPPORT}\`: 6-12 words, one short sentence. Concrete supporting context in the user's actual language.
|
|
1450
|
-
- \`{MOMENT}\` (Template A): 1-3 words, a time anchor
|
|
1451
|
-
- \`{PLACE}\` (Template B): 1-3 words, a real geographic place name (not a date)
|
|
1452
|
-
- \`{C1}, {C2}, {C3}\` (Template C): 1-2 words each, concept beats from the user's vocabulary
|
|
1430
|
+
#### Metadata rules (every poster must carry all four)
|
|
1453
1431
|
|
|
1454
|
-
|
|
1432
|
+
- \`goal:\` — ONE sentence in the user's voice naming the actual aspiration. References at least one concrete element from USER DATA (a quoted phrase, a number, a place, a domain term). Never write \`Vision poster — {slug}\` or any meta-label — that fails the platform's goal-validation rule (it could have been written before the work).
|
|
1433
|
+
- \`detail:\` — markdown body that earns the poster a place on the paywall. Required sections: a short "Why this matters" paragraph grounded in the user's data; a quote block with 1-2 verbatim phrases from USER DATA; a list of the 2-3 measurable signals (proxies) that would tell a specialist agent the user is on track, each tied to a real database table or habit; one named near-term milestone within the followup window.
|
|
1434
|
+
- \`followup:\` — duration scaled to the dream's natural rhythm: body/habit dreams 14-30d, money/career 30-90d, place/identity 90-180d. The instruction MUST name which proxies to query, the threshold separating "on track" from "drift", and the resulting action (push an update, reframe, or stay silent). \`Refresh this vision if data has shifted\` is forbidden — it could have been written before the work.
|
|
1435
|
+
- \`context:\` — JSON pointers the followup needs: domain tag, the user's quoted phrases that drove this poster, and \`{name, source}\` entries for each proxy so the next invocation knows where to read fresh data. Lookup keys only, no cached values.
|
|
1455
1436
|
|
|
1456
|
-
|
|
1437
|
+
#### Universal rules
|
|
1457
1438
|
|
|
1458
|
-
|
|
1439
|
+
- **Variety:** across the carousel, do not reuse the same anchor element type twice. If two posters end on a \`badge\`, rework one to use a \`row\` of beats or a \`text\` anchor. Composition variety is what stops the dashboard reading as a template grid.
|
|
1440
|
+
- **Goal & followup validation:** before pushing each poster, re-read its \`goal:\` and \`followup:\` lines and ask "could I have written these EXACT words before reading USER DATA?" If yes, rewrite — they're templates, not work product.
|
|
1441
|
+
- **Language consistency:** detect the primary language the user writes in from the data. Write ALL posters in that single language. Never mix languages across the carousel.
|
|
1442
|
+
- **Quality bar:** better to push 2 strong posters than 4 mixed-quality ones. If you cannot find a real, named, repeated dream for a domain, SKIP that domain. Never invent. Never fill a slot with weather data, agent infrastructure, or generic placeholder language. If a poster names tools/agents instead of outcomes, it is wrong — drop it.
|
|
1459
1443
|
|
|
1460
1444
|
After all pushes, respond \`done\` and STOP. If you cannot find signal, respond \`NO_SIGNAL\` alone on its own line and STOP.
|
|
1461
1445
|
|
|
@@ -1501,9 +1485,9 @@ After pushing, respond \`done\`.
|
|
|
1501
1485
|
When \`[action:choice]\` arrives on \`dismiss-alt-vision-{slug}\`, append one line to your feedback memory via exec, then \`done\`:
|
|
1502
1486
|
|
|
1503
1487
|
\`\`\`
|
|
1504
|
-
exec mkdir -p ~/.openclaw/workspace-agentlife-
|
|
1488
|
+
exec mkdir -p ~/.openclaw/workspace-agentlife-vision/memory && \\
|
|
1505
1489
|
echo "- $(date -u +%Y-%m-%dT%H:%M:%SZ) vision-{slug}: {reason}" \\
|
|
1506
|
-
>> ~/.openclaw/workspace-agentlife-
|
|
1490
|
+
>> ~/.openclaw/workspace-agentlife-vision/memory/vision-feedback.md
|
|
1507
1491
|
\`\`\`
|
|
1508
1492
|
|
|
1509
1493
|
This file is injected as "Previously rejected dreams" into the next \`[system:dashboard-bootstrap]\` so future synthesis avoids the same misreads.
|
|
@@ -1512,8 +1496,9 @@ Do NOT push a confirmation widget. Do NOT push \`delete vision-{slug}\` — the
|
|
|
1512
1496
|
|
|
1513
1497
|
## What You Are Not
|
|
1514
1498
|
|
|
1515
|
-
- Not
|
|
1516
|
-
- Not
|
|
1499
|
+
- Not a builder — agent creation and improvement belong to \`agentlife-builder\`
|
|
1500
|
+
- Not an orchestrator — you never route or answer user questions
|
|
1501
|
+
- Not a chat agent — you produce widgets or \`NO_SIGNAL\`/\`done\` markers only
|
|
1517
1502
|
`;
|
|
1518
1503
|
var SUPERVISOR_AGENTS_MD = `# Dashboard Supervisor
|
|
1519
1504
|
|
|
@@ -1617,6 +1602,12 @@ var PROVISIONED_AGENTS = [
|
|
|
1617
1602
|
agentsMd: BUILDER_AGENTS_MD,
|
|
1618
1603
|
tools: { profile: "full", alsoAllow: ["agentlife_push"] }
|
|
1619
1604
|
},
|
|
1605
|
+
{
|
|
1606
|
+
id: "agentlife-vision",
|
|
1607
|
+
name: "AgentLife Vision",
|
|
1608
|
+
agentsMd: VISION_AGENTS_MD,
|
|
1609
|
+
tools: { profile: "full", alsoAllow: ["agentlife_push"] }
|
|
1610
|
+
},
|
|
1620
1611
|
{
|
|
1621
1612
|
id: "supervisor",
|
|
1622
1613
|
name: "Supervisor",
|
|
@@ -2105,8 +2096,8 @@ async function composeBootstrapMessage(state, runtime, userDream) {
|
|
|
2105
2096
|
`);
|
|
2106
2097
|
}
|
|
2107
2098
|
async function readRejectedDreams() {
|
|
2108
|
-
const
|
|
2109
|
-
const feedbackFile = path4.join(
|
|
2099
|
+
const visionWorkspace = path4.join(os2.homedir(), ".openclaw", "workspace-agentlife-vision");
|
|
2100
|
+
const feedbackFile = path4.join(visionWorkspace, "memory", "vision-feedback.md");
|
|
2110
2101
|
try {
|
|
2111
2102
|
const content = await fs3.readFile(feedbackFile, "utf-8");
|
|
2112
2103
|
return content.trim().slice(0, 4000);
|
|
@@ -2272,7 +2263,7 @@ async function enterPhase(state, runtime, log, next, opts = {}) {
|
|
|
2272
2263
|
if (next === "SYNTHESIZING") {
|
|
2273
2264
|
const msg = await composeBootstrapMessage(state, runtime, opts.userDream);
|
|
2274
2265
|
const idem = `cold-start-bootstrap-${now}-${retryCount}`;
|
|
2275
|
-
actionSessionKey = await sendSystemMessage(state, "agentlife-
|
|
2266
|
+
actionSessionKey = await sendSystemMessage(state, "agentlife-vision", msg, idem, log);
|
|
2276
2267
|
lastActionAt = Date.now();
|
|
2277
2268
|
}
|
|
2278
2269
|
if (next === "AWAITING_DREAM") {
|
|
@@ -2287,7 +2278,7 @@ async function enterPhase(state, runtime, log, next, opts = {}) {
|
|
|
2287
2278
|
"with userDream: injected after the user answers."
|
|
2288
2279
|
].join(`
|
|
2289
2280
|
`);
|
|
2290
|
-
actionSessionKey = await sendSystemMessage(state, "agentlife-
|
|
2281
|
+
actionSessionKey = await sendSystemMessage(state, "agentlife-vision", msg, idem, log);
|
|
2291
2282
|
lastActionAt = Date.now();
|
|
2292
2283
|
}
|
|
2293
2284
|
writeState(state, { actionSessionKey, lastActionAt });
|
|
@@ -3570,11 +3561,15 @@ async function restoreConfigFromBackup(api, backupPath) {
|
|
|
3570
3561
|
fileCfg.session ??= {};
|
|
3571
3562
|
fileCfg.session.agentToAgent ??= {};
|
|
3572
3563
|
fileCfg.session.agentToAgent.maxPingPongTurns = backup.maxPingPongTurns;
|
|
3564
|
+
} else {
|
|
3565
|
+
delete fileCfg.session?.agentToAgent?.maxPingPongTurns;
|
|
3573
3566
|
}
|
|
3574
3567
|
if (backup.maxConcurrent != null) {
|
|
3575
3568
|
fileCfg.agents ??= {};
|
|
3576
3569
|
fileCfg.agents.defaults ??= {};
|
|
3577
3570
|
fileCfg.agents.defaults.maxConcurrent = backup.maxConcurrent;
|
|
3571
|
+
} else {
|
|
3572
|
+
delete fileCfg.agents?.defaults?.maxConcurrent;
|
|
3578
3573
|
}
|
|
3579
3574
|
await writeRawConfigToDisk(fileCfg);
|
|
3580
3575
|
await fs4.unlink(backupPath);
|
|
@@ -4248,7 +4243,7 @@ function sendToInternalSession(state, agentId, message, idempotencyKey) {
|
|
|
4248
4243
|
});
|
|
4249
4244
|
}
|
|
4250
4245
|
}
|
|
4251
|
-
var SKIP_WIDGET_CHECK = new Set(["agentlife", "agentlife-builder", "supervisor"]);
|
|
4246
|
+
var SKIP_WIDGET_CHECK = new Set(["agentlife", "agentlife-builder", "agentlife-vision", "supervisor"]);
|
|
4252
4247
|
function registerActivityHooks(api, state) {
|
|
4253
4248
|
api.on("llm_output", (event, ctx) => {
|
|
4254
4249
|
if (state.disabled)
|
|
@@ -5294,9 +5289,12 @@ import * as fs7 from "node:fs";
|
|
|
5294
5289
|
import * as os5 from "node:os";
|
|
5295
5290
|
import * as path9 from "node:path";
|
|
5296
5291
|
var AGENTLIFE_DIR = path9.join(os5.homedir(), ".openclaw", "agentlife");
|
|
5297
|
-
var DEVICE_FILE = path9.join(AGENTLIFE_DIR, "device.json");
|
|
5298
5292
|
var TUNNEL_FILE = path9.join(AGENTLIFE_DIR, "tunnel.json");
|
|
5299
5293
|
var BIN_DIR = path9.join(AGENTLIFE_DIR, "bin");
|
|
5294
|
+
var PAIR_REQUEST_MARKER = path9.join(AGENTLIFE_DIR, "pair-requested");
|
|
5295
|
+
var IDENTITY_DIR = path9.join(os5.homedir(), ".agentlife");
|
|
5296
|
+
var DEVICE_FILE = path9.join(IDENTITY_DIR, "device.json");
|
|
5297
|
+
var LEGACY_DEVICE_FILE = path9.join(AGENTLIFE_DIR, "device.json");
|
|
5300
5298
|
var CLOUDFLARED_BIN = os5.platform() === "win32" ? "cloudflared.exe" : "cloudflared";
|
|
5301
5299
|
var CLOUDFLARED_PATH = path9.join(BIN_DIR, CLOUDFLARED_BIN);
|
|
5302
5300
|
var API_BASE = process.env.AGENTLIFE_API_BASE || "https://api.agentlife.app";
|
|
@@ -5311,9 +5309,12 @@ function createInitialState() {
|
|
|
5311
5309
|
tunnelInfo: null,
|
|
5312
5310
|
process: null,
|
|
5313
5311
|
restartTimer: null,
|
|
5312
|
+
provisionRetryTimer: null,
|
|
5314
5313
|
stopped: false,
|
|
5315
5314
|
readyPromise: promise,
|
|
5316
|
-
readyResolve: resolver
|
|
5315
|
+
readyResolve: resolver,
|
|
5316
|
+
inFlight: null,
|
|
5317
|
+
binPath: null
|
|
5317
5318
|
};
|
|
5318
5319
|
}
|
|
5319
5320
|
var state = createInitialState();
|
|
@@ -5341,6 +5342,10 @@ function stopCloudflaredSupervisor() {
|
|
|
5341
5342
|
clearTimeout(state.restartTimer);
|
|
5342
5343
|
state.restartTimer = null;
|
|
5343
5344
|
}
|
|
5345
|
+
if (state.provisionRetryTimer) {
|
|
5346
|
+
clearTimeout(state.provisionRetryTimer);
|
|
5347
|
+
state.provisionRetryTimer = null;
|
|
5348
|
+
}
|
|
5344
5349
|
if (state.process) {
|
|
5345
5350
|
try {
|
|
5346
5351
|
state.process.kill("SIGTERM");
|
|
@@ -5349,10 +5354,30 @@ function stopCloudflaredSupervisor() {
|
|
|
5349
5354
|
}
|
|
5350
5355
|
}
|
|
5351
5356
|
async function bootstrap() {
|
|
5352
|
-
const result = await
|
|
5353
|
-
state.tunnelInfo = result;
|
|
5357
|
+
const result = await runBootstrap();
|
|
5354
5358
|
state.readyResolve(result);
|
|
5355
5359
|
}
|
|
5360
|
+
async function runBootstrap() {
|
|
5361
|
+
if (state.inFlight)
|
|
5362
|
+
return state.inFlight;
|
|
5363
|
+
const promise = doBootstrap().then((result) => {
|
|
5364
|
+
if (result)
|
|
5365
|
+
state.tunnelInfo = result;
|
|
5366
|
+
return result;
|
|
5367
|
+
}).finally(() => {
|
|
5368
|
+
state.inFlight = null;
|
|
5369
|
+
});
|
|
5370
|
+
state.inFlight = promise;
|
|
5371
|
+
return promise;
|
|
5372
|
+
}
|
|
5373
|
+
function triggerProvision() {
|
|
5374
|
+
if (state.stopped)
|
|
5375
|
+
return;
|
|
5376
|
+
requestPair();
|
|
5377
|
+
runBootstrap().catch((err) => {
|
|
5378
|
+
console.warn("[cloudflared-supervisor] triggered provision failed:", err?.message ?? err);
|
|
5379
|
+
});
|
|
5380
|
+
}
|
|
5356
5381
|
async function doBootstrap() {
|
|
5357
5382
|
ensureDirs();
|
|
5358
5383
|
const identity = loadOrCreateDeviceIdentity();
|
|
@@ -5362,29 +5387,84 @@ async function doBootstrap() {
|
|
|
5362
5387
|
return null;
|
|
5363
5388
|
}
|
|
5364
5389
|
let tunnelInfo = loadCachedTunnel();
|
|
5365
|
-
if (!tunnelInfo
|
|
5390
|
+
if (!tunnelInfo) {
|
|
5391
|
+
if (!isPairRequested()) {
|
|
5392
|
+
console.log("[cloudflared-supervisor] no tunnel cached and no pair request — staying idle (LAN-only)");
|
|
5393
|
+
return null;
|
|
5394
|
+
}
|
|
5395
|
+
console.log("[cloudflared-supervisor] pair requested — provisioning fresh tunnel");
|
|
5366
5396
|
tunnelInfo = await provisionTunnel(identity);
|
|
5367
5397
|
if (!tunnelInfo) {
|
|
5368
|
-
console.
|
|
5398
|
+
console.log("[cloudflared-supervisor] provision attempt did not yield a tunnel — will retry on next pair request");
|
|
5369
5399
|
return null;
|
|
5370
5400
|
}
|
|
5371
5401
|
persistTunnel(tunnelInfo);
|
|
5402
|
+
clearPairRequest();
|
|
5403
|
+
} else if (isPairRequested()) {
|
|
5404
|
+
console.log("[cloudflared-supervisor] pair requested with cache present — re-provisioning");
|
|
5405
|
+
const refreshed = await provisionTunnel(identity);
|
|
5406
|
+
if (refreshed) {
|
|
5407
|
+
tunnelInfo = refreshed;
|
|
5408
|
+
persistTunnel(tunnelInfo);
|
|
5409
|
+
} else {
|
|
5410
|
+
console.warn("[cloudflared-supervisor] re-provision failed — keeping cached tunnel");
|
|
5411
|
+
}
|
|
5412
|
+
clearPairRequest();
|
|
5372
5413
|
}
|
|
5373
5414
|
console.log(`[cloudflared-supervisor] tunnel ready: ${tunnelInfo.tunnelUrl}`);
|
|
5374
5415
|
startCloudflaredProcess(binPath, tunnelInfo.tunnelToken);
|
|
5375
5416
|
return tunnelInfo;
|
|
5376
5417
|
}
|
|
5418
|
+
function isPairRequested() {
|
|
5419
|
+
return fs7.existsSync(PAIR_REQUEST_MARKER);
|
|
5420
|
+
}
|
|
5421
|
+
function requestPair() {
|
|
5422
|
+
try {
|
|
5423
|
+
if (!fs7.existsSync(AGENTLIFE_DIR))
|
|
5424
|
+
fs7.mkdirSync(AGENTLIFE_DIR, { recursive: true, mode: 448 });
|
|
5425
|
+
fs7.writeFileSync(PAIR_REQUEST_MARKER, String(Date.now()), { mode: 384 });
|
|
5426
|
+
} catch (err) {
|
|
5427
|
+
console.warn(`[cloudflared-supervisor] failed to write pair-request marker: ${err?.message ?? err}`);
|
|
5428
|
+
}
|
|
5429
|
+
}
|
|
5430
|
+
function clearPairRequest() {
|
|
5431
|
+
try {
|
|
5432
|
+
fs7.unlinkSync(PAIR_REQUEST_MARKER);
|
|
5433
|
+
} catch {}
|
|
5434
|
+
}
|
|
5435
|
+
function parseRetryAfter(value) {
|
|
5436
|
+
const n = value == null ? NaN : parseInt(value.trim(), 10);
|
|
5437
|
+
if (!Number.isFinite(n) || n < 1)
|
|
5438
|
+
return 60;
|
|
5439
|
+
return Math.min(n, 3600);
|
|
5440
|
+
}
|
|
5441
|
+
function scheduleProvisionRetry(delayMs) {
|
|
5442
|
+
if (state.stopped)
|
|
5443
|
+
return;
|
|
5444
|
+
if (state.provisionRetryTimer) {
|
|
5445
|
+
clearTimeout(state.provisionRetryTimer);
|
|
5446
|
+
state.provisionRetryTimer = null;
|
|
5447
|
+
}
|
|
5448
|
+
state.provisionRetryTimer = setTimeout(() => {
|
|
5449
|
+
state.provisionRetryTimer = null;
|
|
5450
|
+
if (state.stopped || state.tunnelInfo)
|
|
5451
|
+
return;
|
|
5452
|
+
triggerProvision();
|
|
5453
|
+
}, delayMs);
|
|
5454
|
+
}
|
|
5377
5455
|
function ensureDirs() {
|
|
5378
5456
|
if (!fs7.existsSync(AGENTLIFE_DIR))
|
|
5379
5457
|
fs7.mkdirSync(AGENTLIFE_DIR, { recursive: true, mode: 448 });
|
|
5380
5458
|
if (!fs7.existsSync(BIN_DIR))
|
|
5381
5459
|
fs7.mkdirSync(BIN_DIR, { recursive: true, mode: 448 });
|
|
5460
|
+
if (!fs7.existsSync(IDENTITY_DIR))
|
|
5461
|
+
fs7.mkdirSync(IDENTITY_DIR, { recursive: true, mode: 448 });
|
|
5382
5462
|
}
|
|
5383
|
-
function
|
|
5384
|
-
if (!fs7.existsSync(
|
|
5463
|
+
function readIdentityFile(filePath) {
|
|
5464
|
+
if (!fs7.existsSync(filePath))
|
|
5385
5465
|
return null;
|
|
5386
5466
|
try {
|
|
5387
|
-
const raw = fs7.readFileSync(
|
|
5467
|
+
const raw = fs7.readFileSync(filePath, "utf-8");
|
|
5388
5468
|
const parsed = JSON.parse(raw);
|
|
5389
5469
|
if (typeof parsed.deviceId === "string" && typeof parsed.deviceSecret === "string") {
|
|
5390
5470
|
return { deviceId: parsed.deviceId, deviceSecret: parsed.deviceSecret };
|
|
@@ -5394,10 +5474,35 @@ function loadDeviceIdentity() {
|
|
|
5394
5474
|
return null;
|
|
5395
5475
|
}
|
|
5396
5476
|
}
|
|
5477
|
+
function loadDeviceIdentity() {
|
|
5478
|
+
const current = readIdentityFile(DEVICE_FILE);
|
|
5479
|
+
if (current)
|
|
5480
|
+
return current;
|
|
5481
|
+
const legacy = readIdentityFile(LEGACY_DEVICE_FILE);
|
|
5482
|
+
if (!legacy)
|
|
5483
|
+
return null;
|
|
5484
|
+
try {
|
|
5485
|
+
if (!fs7.existsSync(IDENTITY_DIR))
|
|
5486
|
+
fs7.mkdirSync(IDENTITY_DIR, { recursive: true, mode: 448 });
|
|
5487
|
+
fs7.writeFileSync(DEVICE_FILE, JSON.stringify(legacy, null, 2), { mode: 384 });
|
|
5488
|
+
try {
|
|
5489
|
+
fs7.chmodSync(DEVICE_FILE, 384);
|
|
5490
|
+
} catch {}
|
|
5491
|
+
try {
|
|
5492
|
+
fs7.unlinkSync(LEGACY_DEVICE_FILE);
|
|
5493
|
+
} catch {}
|
|
5494
|
+
console.log(`[cloudflared-supervisor] migrated device.json to ${DEVICE_FILE}`);
|
|
5495
|
+
} catch (err) {
|
|
5496
|
+
console.warn(`[cloudflared-supervisor] device.json migration failed: ${err?.message ?? err}`);
|
|
5497
|
+
}
|
|
5498
|
+
return legacy;
|
|
5499
|
+
}
|
|
5397
5500
|
function loadOrCreateDeviceIdentity() {
|
|
5398
5501
|
const existing = loadDeviceIdentity();
|
|
5399
5502
|
if (existing)
|
|
5400
5503
|
return existing;
|
|
5504
|
+
if (!fs7.existsSync(IDENTITY_DIR))
|
|
5505
|
+
fs7.mkdirSync(IDENTITY_DIR, { recursive: true, mode: 448 });
|
|
5401
5506
|
const identity = {
|
|
5402
5507
|
deviceId: crypto2.randomUUID(),
|
|
5403
5508
|
deviceSecret: crypto2.randomBytes(32).toString("base64url")
|
|
@@ -5441,6 +5546,12 @@ async function provisionTunnel(identity) {
|
|
|
5441
5546
|
});
|
|
5442
5547
|
if (!response.ok) {
|
|
5443
5548
|
const body = await response.text();
|
|
5549
|
+
if (response.status === 503) {
|
|
5550
|
+
const retryAfter = parseRetryAfter(response.headers.get("retry-after"));
|
|
5551
|
+
console.warn(`[cloudflared-supervisor] provision rate-limited by server (HTTP 503); ` + `scheduling retry in ${retryAfter}s`);
|
|
5552
|
+
scheduleProvisionRetry(retryAfter * 1000);
|
|
5553
|
+
return null;
|
|
5554
|
+
}
|
|
5444
5555
|
console.warn(`[cloudflared-supervisor] provision HTTP ${response.status}: ${body.slice(0, 200)}`);
|
|
5445
5556
|
return null;
|
|
5446
5557
|
}
|
|
@@ -5723,6 +5834,8 @@ function registerWebApp(api) {
|
|
|
5723
5834
|
return true;
|
|
5724
5835
|
const bootstrapToken = mintBootstrapToken();
|
|
5725
5836
|
const tunnelInfo = getTunnelInfo();
|
|
5837
|
+
if (!tunnelInfo?.tunnelUrl)
|
|
5838
|
+
triggerProvision();
|
|
5726
5839
|
const payload = { bootstrapToken };
|
|
5727
5840
|
if (tunnelInfo?.tunnelUrl)
|
|
5728
5841
|
payload.tunnelUrl = tunnelInfo.tunnelUrl;
|
|
@@ -5940,7 +6053,7 @@ Setup code: ${setupCode}
|
|
|
5940
6053
|
|
|
5941
6054
|
// hooks/bootstrap.ts
|
|
5942
6055
|
var SKIP_GUIDANCE_AGENTS = new Set(["agentlife"]);
|
|
5943
|
-
var SKIP_AGENTS_MD_INJECTION = new Set(["agentlife", "agentlife-builder", "quick", "supervisor"]);
|
|
6056
|
+
var SKIP_AGENTS_MD_INJECTION = new Set(["agentlife", "agentlife-builder", "agentlife-vision", "quick", "supervisor"]);
|
|
5944
6057
|
function registerBootstrapHook(api, state2) {
|
|
5945
6058
|
api.registerService({
|
|
5946
6059
|
id: "agentlife-bootstrap-hook",
|