@phronesis-io/openclaw-eigenflux 0.0.14 → 0.0.16
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 +314 -171
- package/openclaw.plugin.json +1 -6
- package/package.json +2 -3
- package/skills/ef-broadcast/references/contract.md +4 -1
- package/skills/ef-broadcast/references/feed.md +6 -4
- package/skills/ef-profile/SKILL.md +1 -1
- package/skills/ef-profile/references/onboarding.md +1 -1
- package/skills/ef-trading/SKILL.md +191 -0
- package/skills/ef-trading/references/kovaloop.md +67 -0
- package/skills/ef-trading/references/orders.md +154 -0
- package/skills/ef-trading/references/services.md +126 -0
- package/skills/ef-localdev/SKILL.md +0 -151
package/dist/index.js
CHANGED
|
@@ -444,10 +444,104 @@ var EigenFluxStreamClient = class {
|
|
|
444
444
|
}
|
|
445
445
|
};
|
|
446
446
|
|
|
447
|
+
// src/openclaw-context.ts
|
|
448
|
+
var import_node_fs = require("fs");
|
|
449
|
+
var import_node_path = require("path");
|
|
450
|
+
var import_node_os = require("os");
|
|
451
|
+
var MEMORY_DIR_REL = ["workspace", "memory"];
|
|
452
|
+
var EMPTY_CONTEXT = { memoryDirs: [], sessionSnippets: [] };
|
|
453
|
+
function resolveOpenClawStateDir(logger) {
|
|
454
|
+
try {
|
|
455
|
+
const mod = require("openclaw/plugin-sdk/memory-core-host-runtime-core");
|
|
456
|
+
const dir = mod.resolveStateDir?.();
|
|
457
|
+
if (dir && typeof dir === "string") return dir;
|
|
458
|
+
} catch (err) {
|
|
459
|
+
logger.debug(`resolveOpenClawStateDir: SDK resolveStateDir unavailable: ${err instanceof Error ? err.message : String(err)}`);
|
|
460
|
+
}
|
|
461
|
+
const cwd = process.cwd();
|
|
462
|
+
if (cwd && (0, import_node_fs.existsSync)((0, import_node_path.join)(cwd, "agents"))) return cwd;
|
|
463
|
+
const home = (0, import_node_path.join)((0, import_node_os.homedir)(), ".openclaw");
|
|
464
|
+
if ((0, import_node_fs.existsSync)((0, import_node_path.join)(home, "agents"))) return home;
|
|
465
|
+
return void 0;
|
|
466
|
+
}
|
|
467
|
+
var MAX_SESSION_TURNS = 12;
|
|
468
|
+
var MAX_SESSION_SNIPPET_CHARS = 280;
|
|
469
|
+
function collectOpenClawContext(stateDir, logger, options) {
|
|
470
|
+
const agentName = options?.agentName ?? "main";
|
|
471
|
+
const memoryDir = (0, import_node_path.join)(stateDir, ...MEMORY_DIR_REL);
|
|
472
|
+
return {
|
|
473
|
+
memoryDirs: (0, import_node_fs.existsSync)(memoryDir) ? [memoryDir] : [],
|
|
474
|
+
sessionSnippets: readSessionSnippets(stateDir, agentName, logger)
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
function readSessionSnippets(stateDir, agentName, logger) {
|
|
478
|
+
try {
|
|
479
|
+
const dir = (0, import_node_path.join)(stateDir, "agents", agentName, "sessions");
|
|
480
|
+
const latest = latestSessionFile(dir);
|
|
481
|
+
if (!latest) return [];
|
|
482
|
+
const lines = (0, import_node_fs.readFileSync)(latest, "utf-8").split("\n");
|
|
483
|
+
const snippets = [];
|
|
484
|
+
for (let i = lines.length - 1; i >= 0 && snippets.length < MAX_SESSION_TURNS; i -= 1) {
|
|
485
|
+
const line = lines[i].trim();
|
|
486
|
+
if (!line) continue;
|
|
487
|
+
const text = extractTurnText(line);
|
|
488
|
+
if (text) snippets.push(text);
|
|
489
|
+
}
|
|
490
|
+
return snippets.reverse();
|
|
491
|
+
} catch (err) {
|
|
492
|
+
logger.debug(`readSessionSnippets: ${err instanceof Error ? err.message : String(err)}`);
|
|
493
|
+
return [];
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
function latestSessionFile(dir) {
|
|
497
|
+
let newest;
|
|
498
|
+
let entries;
|
|
499
|
+
try {
|
|
500
|
+
entries = (0, import_node_fs.readdirSync)(dir);
|
|
501
|
+
} catch {
|
|
502
|
+
return void 0;
|
|
503
|
+
}
|
|
504
|
+
for (const name of entries) {
|
|
505
|
+
if (!name.endsWith(".jsonl") || name.endsWith(".trajectory.jsonl")) continue;
|
|
506
|
+
const path4 = (0, import_node_path.join)(dir, name);
|
|
507
|
+
try {
|
|
508
|
+
const mtime = (0, import_node_fs.statSync)(path4).mtimeMs;
|
|
509
|
+
if (!newest || mtime > newest.mtime) newest = { path: path4, mtime };
|
|
510
|
+
} catch {
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
return newest?.path;
|
|
514
|
+
}
|
|
515
|
+
function extractTurnText(line) {
|
|
516
|
+
let obj;
|
|
517
|
+
try {
|
|
518
|
+
obj = JSON.parse(line);
|
|
519
|
+
} catch {
|
|
520
|
+
return void 0;
|
|
521
|
+
}
|
|
522
|
+
if (!obj || typeof obj !== "object") return void 0;
|
|
523
|
+
const message = obj.message;
|
|
524
|
+
if (!message || typeof message !== "object") return void 0;
|
|
525
|
+
const role = message.role;
|
|
526
|
+
if (role !== "user" && role !== "assistant") return void 0;
|
|
527
|
+
const content = message.content;
|
|
528
|
+
let text = "";
|
|
529
|
+
if (typeof content === "string") {
|
|
530
|
+
text = content;
|
|
531
|
+
} else if (Array.isArray(content)) {
|
|
532
|
+
text = content.filter((c) => !!c && typeof c === "object" && c.type === "text" && typeof c.text === "string").map((c) => c.text).join(" ");
|
|
533
|
+
}
|
|
534
|
+
text = text.trim();
|
|
535
|
+
if (!text) return void 0;
|
|
536
|
+
if (/EIGENFLUX_FEED_PAYLOAD|profile is due for|EigenFlux feed payload/i.test(text)) return void 0;
|
|
537
|
+
if (/^(NO_REPLY|HEARTBEAT_OK|Triggered a silent profile refresh)/.test(text)) return void 0;
|
|
538
|
+
const oneLine = text.replace(/\s+/g, " ");
|
|
539
|
+
return oneLine.length > MAX_SESSION_SNIPPET_CHARS ? `${oneLine.slice(0, MAX_SESSION_SNIPPET_CHARS)}\u2026` : oneLine;
|
|
540
|
+
}
|
|
541
|
+
|
|
447
542
|
// src/profile-refresher.ts
|
|
448
543
|
var REFRESH_WINDOW_START = 1;
|
|
449
544
|
var REFRESH_WINDOW_END = 5;
|
|
450
|
-
var ITEMS_LIMIT = 30;
|
|
451
545
|
var EigenFluxProfileRefresher = class {
|
|
452
546
|
constructor(config) {
|
|
453
547
|
this.timeoutId = null;
|
|
@@ -472,6 +566,18 @@ var EigenFluxProfileRefresher = class {
|
|
|
472
566
|
}
|
|
473
567
|
this.config.logger.info(`Stopped profile refresher for server=${this.config.serverName}`);
|
|
474
568
|
}
|
|
569
|
+
/**
|
|
570
|
+
* Run a refresh immediately, out of band from the daily timer. Intended for
|
|
571
|
+
* manual verification (`/eigenflux refresh`) so the full silent loop can be
|
|
572
|
+
* exercised on demand instead of waiting for the 1–5 AM window. Runs
|
|
573
|
+
* independently of the timer state — the command path may hold a refresher
|
|
574
|
+
* instance that was never start()ed, and a manual one-shot should still work.
|
|
575
|
+
* The next scheduled refresh (if any) is left untouched.
|
|
576
|
+
*/
|
|
577
|
+
async triggerNow() {
|
|
578
|
+
this.config.logger.info(`Manual profile refresh triggered for server=${this.config.serverName}`);
|
|
579
|
+
await this.refresh({ manual: true });
|
|
580
|
+
}
|
|
475
581
|
scheduleNext() {
|
|
476
582
|
if (!this.running) return;
|
|
477
583
|
const delay = msUntilNextRefresh(/* @__PURE__ */ new Date());
|
|
@@ -491,57 +597,98 @@ var EigenFluxProfileRefresher = class {
|
|
|
491
597
|
this.scheduleNext();
|
|
492
598
|
}, delay);
|
|
493
599
|
}
|
|
494
|
-
async refresh() {
|
|
600
|
+
async refresh(opts = {}) {
|
|
495
601
|
this.config.logger.info(`Running profile refresh for server=${this.config.serverName}`);
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
{ logger: this.config.logger }
|
|
506
|
-
)
|
|
507
|
-
]);
|
|
508
|
-
if (!this.running) return;
|
|
509
|
-
if (profileResult.kind === "auth_required" || itemsResult.kind === "auth_required") {
|
|
510
|
-
this.config.logger.warn(`Profile refresh: auth required for server=${this.config.serverName}`);
|
|
511
|
-
await this.config.onAuthRequired();
|
|
512
|
-
return;
|
|
602
|
+
let context = EMPTY_CONTEXT;
|
|
603
|
+
if (this.config.collectContext) {
|
|
604
|
+
try {
|
|
605
|
+
context = await this.config.collectContext() ?? EMPTY_CONTEXT;
|
|
606
|
+
} catch (err) {
|
|
607
|
+
this.config.logger.warn(
|
|
608
|
+
`Context collection failed: ${err instanceof Error ? err.message : String(err)}`
|
|
609
|
+
);
|
|
610
|
+
}
|
|
513
611
|
}
|
|
514
|
-
|
|
515
|
-
|
|
612
|
+
const memoryDirs = context.memoryDirs ?? [];
|
|
613
|
+
const sessionSnippets = context.sessionSnippets ?? [];
|
|
614
|
+
if (memoryDirs.length === 0 && sessionSnippets.length === 0) {
|
|
615
|
+
this.config.logger.info("Profile refresh skipped: no memory/session context");
|
|
616
|
+
this.emitTelemetry({ outcome: "skipped_no_context", memory_dirs: 0, session_snippets: 0, prompt_bytes: 0, delivered: false });
|
|
516
617
|
return;
|
|
517
618
|
}
|
|
518
|
-
if (
|
|
519
|
-
|
|
619
|
+
if (!opts.manual && !this.running) return;
|
|
620
|
+
const args = [
|
|
621
|
+
"profile",
|
|
622
|
+
"refresh-prompt",
|
|
623
|
+
"-s",
|
|
624
|
+
this.config.serverName,
|
|
625
|
+
...memoryDirs.flatMap((d) => ["--memory-dir", d]),
|
|
626
|
+
...sessionSnippets.flatMap((s) => ["--session-snippet", s])
|
|
627
|
+
];
|
|
628
|
+
const result = await execEigenflux(this.config.eigenfluxBin, args, {
|
|
629
|
+
logger: this.config.logger,
|
|
630
|
+
parseJson: false
|
|
631
|
+
});
|
|
632
|
+
if (!opts.manual && !this.running) return;
|
|
633
|
+
if (result.kind === "auth_required") {
|
|
634
|
+
this.config.logger.warn(`Profile refresh: auth required for server=${this.config.serverName}`);
|
|
635
|
+
this.emitTelemetry({ outcome: "auth_required", memory_dirs: memoryDirs.length, session_snippets: sessionSnippets.length, prompt_bytes: 0, delivered: false });
|
|
636
|
+
await this.config.onAuthRequired();
|
|
520
637
|
return;
|
|
521
638
|
}
|
|
522
|
-
if (
|
|
523
|
-
this.config.logger.error(`
|
|
639
|
+
if (result.kind === "not_installed") {
|
|
640
|
+
this.config.logger.error(`eigenflux CLI not found (bin=${this.config.eigenfluxBin})`);
|
|
641
|
+
this.emitTelemetry({ outcome: "not_installed", memory_dirs: memoryDirs.length, session_snippets: sessionSnippets.length, prompt_bytes: 0, delivered: false });
|
|
524
642
|
return;
|
|
525
643
|
}
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
this.
|
|
644
|
+
if (result.kind !== "success") {
|
|
645
|
+
this.config.logger.error(`refresh-prompt failed: ${result.error.message}`);
|
|
646
|
+
this.emitTelemetry({ outcome: "error", memory_dirs: memoryDirs.length, session_snippets: sessionSnippets.length, prompt_bytes: 0, delivered: false });
|
|
529
647
|
return;
|
|
530
648
|
}
|
|
531
|
-
const
|
|
532
|
-
if (
|
|
533
|
-
this.config.logger.info("Profile refresh skipped: no
|
|
649
|
+
const prompt = (result.data ?? "").trim();
|
|
650
|
+
if (!prompt) {
|
|
651
|
+
this.config.logger.info("Profile refresh skipped: CLI produced no prompt");
|
|
652
|
+
this.emitTelemetry({ outcome: "skipped_no_context", memory_dirs: memoryDirs.length, session_snippets: sessionSnippets.length, prompt_bytes: 0, delivered: false });
|
|
534
653
|
return;
|
|
535
654
|
}
|
|
536
|
-
|
|
655
|
+
this.config.logger.info(
|
|
656
|
+
`Profile refresh context: memory_dirs=${memoryDirs.length}, session_snippets=${sessionSnippets.length}`
|
|
657
|
+
);
|
|
537
658
|
try {
|
|
538
|
-
if (!this.running) return;
|
|
659
|
+
if (!opts.manual && !this.running) return;
|
|
539
660
|
await this.config.onRefreshPrompt(prompt);
|
|
540
661
|
this.config.logger.info(`Profile refresh prompt delivered for server=${this.config.serverName}`);
|
|
662
|
+
this.emitTelemetry({
|
|
663
|
+
outcome: "delivered",
|
|
664
|
+
memory_dirs: memoryDirs.length,
|
|
665
|
+
session_snippets: sessionSnippets.length,
|
|
666
|
+
prompt_bytes: Buffer.byteLength(prompt, "utf-8"),
|
|
667
|
+
delivered: true
|
|
668
|
+
});
|
|
541
669
|
} catch (err) {
|
|
542
670
|
this.config.logger.error(
|
|
543
671
|
`Profile refresh delivery failed: ${err instanceof Error ? err.message : String(err)}`
|
|
544
672
|
);
|
|
673
|
+
this.emitTelemetry({
|
|
674
|
+
outcome: "delivery_failed",
|
|
675
|
+
memory_dirs: memoryDirs.length,
|
|
676
|
+
session_snippets: sessionSnippets.length,
|
|
677
|
+
prompt_bytes: Buffer.byteLength(prompt, "utf-8"),
|
|
678
|
+
delivered: false
|
|
679
|
+
});
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
/**
|
|
683
|
+
* Emit one structured layer-1 telemetry line. Grep `profile_refresh_telemetry`
|
|
684
|
+
* in plugin logs to confirm the daily refresh fired and was handed off for
|
|
685
|
+
* silent delivery. Best-effort: never throws.
|
|
686
|
+
*/
|
|
687
|
+
emitTelemetry(t) {
|
|
688
|
+
try {
|
|
689
|
+
const payload = { server: this.config.serverName, ...t };
|
|
690
|
+
this.config.logger.info(`profile_refresh_telemetry ${JSON.stringify(payload)}`);
|
|
691
|
+
} catch {
|
|
545
692
|
}
|
|
546
693
|
}
|
|
547
694
|
};
|
|
@@ -556,41 +703,6 @@ function msUntilNextRefresh(now) {
|
|
|
556
703
|
}
|
|
557
704
|
return target.getTime() - now.getTime();
|
|
558
705
|
}
|
|
559
|
-
function buildRefreshPrompt(profile, items) {
|
|
560
|
-
const name = profile.profile?.agent_name ?? "(unknown)";
|
|
561
|
-
const bio = profile.profile?.bio || "(empty)";
|
|
562
|
-
const totalItems = profile.influence?.total_items ?? 0;
|
|
563
|
-
const totalConsumed = profile.influence?.total_consumed ?? 0;
|
|
564
|
-
const totalScored = (profile.influence?.total_scored_1 ?? 0) + (profile.influence?.total_scored_2 ?? 0);
|
|
565
|
-
const lines = [
|
|
566
|
-
"Your EigenFlux profile is due for a refresh. Below is your current profile",
|
|
567
|
-
"and recent broadcast activity.",
|
|
568
|
-
"",
|
|
569
|
-
"## Current Profile",
|
|
570
|
-
`- Name: ${name}`,
|
|
571
|
-
`- Bio: ${bio}`,
|
|
572
|
-
`- Influence: ${totalItems} items published, ${totalConsumed} consumed, ${totalScored} scored`,
|
|
573
|
-
"",
|
|
574
|
-
"## Recent Broadcasts"
|
|
575
|
-
];
|
|
576
|
-
for (const item of items) {
|
|
577
|
-
const summary = item.summary || "(no summary)";
|
|
578
|
-
let line = `- [${item.broadcast_type ?? "unknown"}] ${summary}`;
|
|
579
|
-
if (item.keywords) line += ` (keywords: ${item.keywords})`;
|
|
580
|
-
if (item.total_score && item.total_score > 0) line += ` (score: ${item.total_score})`;
|
|
581
|
-
lines.push(line);
|
|
582
|
-
}
|
|
583
|
-
lines.push(
|
|
584
|
-
"",
|
|
585
|
-
"## Instructions",
|
|
586
|
-
"1. Write a concise bio (2-4 sentences) reflecting current focus areas and expertise.",
|
|
587
|
-
"2. Incorporate patterns from recent broadcasts \u2014 topics, domains, interests.",
|
|
588
|
-
"3. Preserve still-relevant info from the current bio.",
|
|
589
|
-
"4. If not enough new activity to meaningfully update, do nothing.",
|
|
590
|
-
'5. To update, run: eigenflux profile update --bio "YOUR NEW BIO"'
|
|
591
|
-
);
|
|
592
|
-
return lines.join("\n");
|
|
593
|
-
}
|
|
594
706
|
|
|
595
707
|
// src/settings-reporter.ts
|
|
596
708
|
function resolveAgentMode(env = process.env) {
|
|
@@ -772,26 +884,6 @@ var CredentialsLoader = class {
|
|
|
772
884
|
credentialsPath: this.credentialsPath
|
|
773
885
|
};
|
|
774
886
|
}
|
|
775
|
-
/**
|
|
776
|
-
* Restore credentials from a backup object (e.g. OpenClaw config).
|
|
777
|
-
* Only writes if no local credentials.json exists yet.
|
|
778
|
-
* Returns true if credentials were restored.
|
|
779
|
-
*/
|
|
780
|
-
restoreFromBackup(backup) {
|
|
781
|
-
if (fs.existsSync(this.credentialsPath)) {
|
|
782
|
-
this.logger.debug(`[credential-restore] skip: credentials.json already exists at ${this.credentialsPath}`);
|
|
783
|
-
return false;
|
|
784
|
-
}
|
|
785
|
-
if (!backup?.access_token) {
|
|
786
|
-
this.logger.debug(`[credential-restore] skip: backup has no access_token`);
|
|
787
|
-
return false;
|
|
788
|
-
}
|
|
789
|
-
this.saveAccessToken(backup.access_token, backup.email, backup.expires_at);
|
|
790
|
-
this.logger.info(
|
|
791
|
-
`[credential-restore] restored from backup: email=${backup.email ?? "n/a"}, path=${this.credentialsPath}`
|
|
792
|
-
);
|
|
793
|
-
return true;
|
|
794
|
-
}
|
|
795
887
|
saveAccessToken(token, email, expiresAt) {
|
|
796
888
|
this.logger.info(`Saving access token: path=${this.credentialsPath}, email=${email ?? "n/a"}`);
|
|
797
889
|
try {
|
|
@@ -914,7 +1006,8 @@ function normalizeReplyTarget(value, options) {
|
|
|
914
1006
|
}
|
|
915
1007
|
|
|
916
1008
|
// src/config.ts
|
|
917
|
-
var PLUGIN_VERSION = "0.0.
|
|
1009
|
+
var PLUGIN_VERSION = "0.0.16";
|
|
1010
|
+
var EXPECTED_CLI_VERSION = "0.0.13";
|
|
918
1011
|
var DEFAULT_EIGENFLUX_BIN = "eigenflux";
|
|
919
1012
|
var DEFAULT_SESSION_KEY = "main";
|
|
920
1013
|
var DEFAULT_AGENT_ID = "main";
|
|
@@ -1004,6 +1097,30 @@ async function discoverServers(eigenfluxBin, logger) {
|
|
|
1004
1097
|
logger?.error(`eigenflux server list failed: ${result.error.message}`);
|
|
1005
1098
|
return { kind: "ok", servers: [] };
|
|
1006
1099
|
}
|
|
1100
|
+
async function getInstalledCliVersion(eigenfluxBin, logger) {
|
|
1101
|
+
const result = await execEigenflux(
|
|
1102
|
+
eigenfluxBin,
|
|
1103
|
+
["version"],
|
|
1104
|
+
{ logger }
|
|
1105
|
+
);
|
|
1106
|
+
if (result.kind === "success" && typeof result.data?.cli_version === "string") {
|
|
1107
|
+
return result.data.cli_version;
|
|
1108
|
+
}
|
|
1109
|
+
return null;
|
|
1110
|
+
}
|
|
1111
|
+
function isCliOutdated(installed, target) {
|
|
1112
|
+
if (!installed) return false;
|
|
1113
|
+
const parse = (v) => v.split(".").slice(0, 3).map((part) => parseInt(part, 10));
|
|
1114
|
+
const a = parse(installed);
|
|
1115
|
+
const b = parse(target);
|
|
1116
|
+
for (let i = 0; i < 3; i++) {
|
|
1117
|
+
const x = a[i] ?? 0;
|
|
1118
|
+
const y = b[i] ?? 0;
|
|
1119
|
+
if (Number.isNaN(x) || Number.isNaN(y)) return false;
|
|
1120
|
+
if (x !== y) return x < y;
|
|
1121
|
+
}
|
|
1122
|
+
return false;
|
|
1123
|
+
}
|
|
1007
1124
|
function resolveEigenfluxHome(baseDir) {
|
|
1008
1125
|
const envHome = process.env.EIGENFLUX_HOME;
|
|
1009
1126
|
if (envHome) {
|
|
@@ -1069,7 +1186,8 @@ var PLUGIN_CONFIG = {
|
|
|
1069
1186
|
DEFAULT_AGENT_ID,
|
|
1070
1187
|
DEFAULT_OPENCLAW_CLI_BIN,
|
|
1071
1188
|
HOST_KIND,
|
|
1072
|
-
PLUGIN_VERSION
|
|
1189
|
+
PLUGIN_VERSION,
|
|
1190
|
+
EXPECTED_CLI_VERSION
|
|
1073
1191
|
};
|
|
1074
1192
|
|
|
1075
1193
|
// src/notification-route-resolver.ts
|
|
@@ -1686,8 +1804,8 @@ async function resolveNotificationRoute(config, logger, options = {}) {
|
|
|
1686
1804
|
}
|
|
1687
1805
|
|
|
1688
1806
|
// src/agent-prompt-templates.ts
|
|
1689
|
-
var
|
|
1690
|
-
var
|
|
1807
|
+
var import_node_fs2 = require("fs");
|
|
1808
|
+
var import_node_path2 = require("path");
|
|
1691
1809
|
var FEED_OUTPUT_CONTRACT_FALLBACK = [
|
|
1692
1810
|
"OUTPUT CONTRACT \u2014 non-negotiable subset of references/feed.md (full procedure there):",
|
|
1693
1811
|
"1. Triage silently: push items relevant to the user, discard the rest. Never",
|
|
@@ -1713,8 +1831,8 @@ var FEED_OUTPUT_CONTRACT_FALLBACK = [
|
|
|
1713
1831
|
].join("\n");
|
|
1714
1832
|
function loadFeedOutputContract() {
|
|
1715
1833
|
try {
|
|
1716
|
-
return (0,
|
|
1717
|
-
(0,
|
|
1834
|
+
return (0, import_node_fs2.readFileSync)(
|
|
1835
|
+
(0, import_node_path2.join)(__dirname, "../skills/ef-broadcast/references/contract.md"),
|
|
1718
1836
|
"utf-8"
|
|
1719
1837
|
).trim();
|
|
1720
1838
|
} catch {
|
|
@@ -1772,6 +1890,18 @@ function buildNotInstalledPromptTemplate({
|
|
|
1772
1890
|
`\`${installCommand}\``
|
|
1773
1891
|
].join("\n");
|
|
1774
1892
|
}
|
|
1893
|
+
function buildOutdatedPromptTemplate({
|
|
1894
|
+
installed,
|
|
1895
|
+
expected,
|
|
1896
|
+
updateCommand
|
|
1897
|
+
}) {
|
|
1898
|
+
return [
|
|
1899
|
+
"[EIGENFLUX_CLI_OUTDATED]",
|
|
1900
|
+
`The EigenFlux CLI is outdated (installed=${installed}, this plugin expects >=${expected}).`,
|
|
1901
|
+
"Please update it to the latest version. You can run:",
|
|
1902
|
+
`\`${updateCommand}\``
|
|
1903
|
+
].join("\n");
|
|
1904
|
+
}
|
|
1775
1905
|
function buildPmStreamEventPromptTemplate(event, context) {
|
|
1776
1906
|
const data = event.data ?? {};
|
|
1777
1907
|
const parts = [];
|
|
@@ -1809,6 +1939,7 @@ var EigenFluxNotifier = class {
|
|
|
1809
1939
|
}
|
|
1810
1940
|
async deliver(message, options) {
|
|
1811
1941
|
const targetKey = options?.targetSessionKey;
|
|
1942
|
+
const silent = options?.silent === true;
|
|
1812
1943
|
if (targetKey) {
|
|
1813
1944
|
await this.drainPendingCleanups();
|
|
1814
1945
|
const baseRoute = await this.resolveRoute();
|
|
@@ -1823,7 +1954,7 @@ var EigenFluxNotifier = class {
|
|
|
1823
1954
|
this.logger.info(
|
|
1824
1955
|
`Delivery route resolved: source=targeted-oneshot, ${formatRouteForLog(route)}, message_preview=${previewMessage(message)}`
|
|
1825
1956
|
);
|
|
1826
|
-
const result = await this.attemptDelivery(message, route, { skipHeartbeat: true });
|
|
1957
|
+
const result = await this.attemptDelivery(message, route, { skipHeartbeat: true, silent });
|
|
1827
1958
|
if (result.result.ok) {
|
|
1828
1959
|
this.logDispatch(result.result);
|
|
1829
1960
|
} else {
|
|
@@ -1836,7 +1967,7 @@ var EigenFluxNotifier = class {
|
|
|
1836
1967
|
this.logger.info(
|
|
1837
1968
|
`Delivery route resolved: source=${initial.source}, ${formatRouteForLog(initial.route)}, message_preview=${previewMessage(message)}`
|
|
1838
1969
|
);
|
|
1839
|
-
const firstAttempt = await this.attemptDelivery(message, initial.route);
|
|
1970
|
+
const firstAttempt = await this.attemptDelivery(message, initial.route, { silent });
|
|
1840
1971
|
if (firstAttempt.result.ok) {
|
|
1841
1972
|
await this.rememberRouteIfChanged(firstAttempt.finalRoute, initial.source);
|
|
1842
1973
|
this.logDispatch(firstAttempt.result);
|
|
@@ -1851,7 +1982,7 @@ var EigenFluxNotifier = class {
|
|
|
1851
1982
|
this.logger.info(
|
|
1852
1983
|
`Retrying delivery with fresh route: source=${fallback.source}, ${formatRouteForLog(fallback.route)}`
|
|
1853
1984
|
);
|
|
1854
|
-
const retry = await this.attemptDelivery(message, fallback.route);
|
|
1985
|
+
const retry = await this.attemptDelivery(message, fallback.route, { silent });
|
|
1855
1986
|
if (retry.result.ok) {
|
|
1856
1987
|
await this.rememberRouteIfChanged(retry.finalRoute, fallback.source);
|
|
1857
1988
|
this.logDispatch(retry.result);
|
|
@@ -1868,11 +1999,12 @@ var EigenFluxNotifier = class {
|
|
|
1868
1999
|
return false;
|
|
1869
2000
|
}
|
|
1870
2001
|
async attemptDelivery(message, route, options = {}) {
|
|
2002
|
+
const silent = options.silent === true;
|
|
1871
2003
|
const attempts = [
|
|
1872
|
-
() => this.tryNotifyViaRuntimeSubagent(message, route),
|
|
1873
|
-
() => this.tryNotifyViaRuntimeCommandAgent(message, route)
|
|
2004
|
+
() => this.tryNotifyViaRuntimeSubagent(message, route, silent),
|
|
2005
|
+
() => this.tryNotifyViaRuntimeCommandAgent(message, route, silent)
|
|
1874
2006
|
];
|
|
1875
|
-
if (!options.skipHeartbeat) {
|
|
2007
|
+
if (!options.skipHeartbeat && !silent) {
|
|
1876
2008
|
attempts.push(
|
|
1877
2009
|
() => this.tryNotifyViaRuntimeHeartbeat(message, route),
|
|
1878
2010
|
() => this.tryNotifyViaRuntimeCommandHeartbeat(message)
|
|
@@ -1902,7 +2034,7 @@ var EigenFluxNotifier = class {
|
|
|
1902
2034
|
errors
|
|
1903
2035
|
};
|
|
1904
2036
|
}
|
|
1905
|
-
async tryNotifyViaRuntimeSubagent(message, route) {
|
|
2037
|
+
async tryNotifyViaRuntimeSubagent(message, route, silent = false) {
|
|
1906
2038
|
const runtimeSubagent = this.runtime.subagent;
|
|
1907
2039
|
if (!runtimeSubagent || typeof runtimeSubagent.run !== "function") {
|
|
1908
2040
|
return {
|
|
@@ -1912,13 +2044,14 @@ var EigenFluxNotifier = class {
|
|
|
1912
2044
|
};
|
|
1913
2045
|
}
|
|
1914
2046
|
try {
|
|
2047
|
+
const deliver = !silent;
|
|
1915
2048
|
this.logger.info(
|
|
1916
|
-
`Attempting runtime.subagent delivery: ${formatRouteForLog(route)}, deliver
|
|
2049
|
+
`Attempting runtime.subagent delivery: ${formatRouteForLog(route)}, deliver=${deliver}`
|
|
1917
2050
|
);
|
|
1918
2051
|
const { runId } = await runtimeSubagent.run({
|
|
1919
2052
|
sessionKey: route.sessionKey,
|
|
1920
2053
|
message,
|
|
1921
|
-
deliver
|
|
2054
|
+
deliver,
|
|
1922
2055
|
idempotencyKey: (0, import_node_crypto.randomUUID)()
|
|
1923
2056
|
});
|
|
1924
2057
|
if (typeof runtimeSubagent.waitForRun === "function") {
|
|
@@ -1948,10 +2081,10 @@ var EigenFluxNotifier = class {
|
|
|
1948
2081
|
};
|
|
1949
2082
|
}
|
|
1950
2083
|
}
|
|
1951
|
-
async tryNotifyViaRuntimeCommandAgent(message, route) {
|
|
2084
|
+
async tryNotifyViaRuntimeCommandAgent(message, route, silent = false) {
|
|
1952
2085
|
return this.runRuntimeCommand(
|
|
1953
2086
|
"runtime.command.agent",
|
|
1954
|
-
this.buildAgentCliArgs(message, route),
|
|
2087
|
+
this.buildAgentCliArgs(message, route, silent),
|
|
1955
2088
|
route
|
|
1956
2089
|
);
|
|
1957
2090
|
}
|
|
@@ -2035,16 +2168,18 @@ var EigenFluxNotifier = class {
|
|
|
2035
2168
|
};
|
|
2036
2169
|
}
|
|
2037
2170
|
}
|
|
2038
|
-
buildAgentCliArgs(message, route) {
|
|
2171
|
+
buildAgentCliArgs(message, route, silent = false) {
|
|
2039
2172
|
const args = [
|
|
2040
2173
|
this.config.openclawCliBin,
|
|
2041
2174
|
"agent",
|
|
2042
2175
|
"--message",
|
|
2043
2176
|
message,
|
|
2044
2177
|
"--agent",
|
|
2045
|
-
route.agentId
|
|
2046
|
-
"--deliver"
|
|
2178
|
+
route.agentId
|
|
2047
2179
|
];
|
|
2180
|
+
if (!silent) {
|
|
2181
|
+
args.push("--deliver");
|
|
2182
|
+
}
|
|
2048
2183
|
if (route.replyChannel) {
|
|
2049
2184
|
args.push("--reply-channel", route.replyChannel);
|
|
2050
2185
|
}
|
|
@@ -2201,7 +2336,7 @@ function formatCommandArgsForLog(argv) {
|
|
|
2201
2336
|
}
|
|
2202
2337
|
|
|
2203
2338
|
// src/index.ts
|
|
2204
|
-
var COMMAND_NAMES = ["auth", "profile", "servers", "feed", "pm", "here", "version"];
|
|
2339
|
+
var COMMAND_NAMES = ["auth", "profile", "refresh", "servers", "feed", "pm", "here", "version"];
|
|
2205
2340
|
var COMMAND_NAME_SET = new Set(COMMAND_NAMES);
|
|
2206
2341
|
var DEFAULT_ROUTING = {
|
|
2207
2342
|
sessionKey: PLUGIN_CONFIG.DEFAULT_SESSION_KEY,
|
|
@@ -2227,6 +2362,7 @@ function registerPlugin(api) {
|
|
|
2227
2362
|
const store = createInMemoryPluginStore();
|
|
2228
2363
|
let runtimes = [];
|
|
2229
2364
|
let notInstalledPromptDelivered = false;
|
|
2365
|
+
let outdatedPromptDelivered = false;
|
|
2230
2366
|
api.registerService({
|
|
2231
2367
|
id: "eigenflux:discovery",
|
|
2232
2368
|
start: async () => {
|
|
@@ -2263,6 +2399,23 @@ function registerPlugin(api) {
|
|
|
2263
2399
|
await runtime.streamClient.start();
|
|
2264
2400
|
runtime.profileRefresher.start();
|
|
2265
2401
|
}
|
|
2402
|
+
if (!outdatedPromptDelivered) {
|
|
2403
|
+
const installedVersion = await getInstalledCliVersion(pluginConfig.eigenfluxBin, logger);
|
|
2404
|
+
if (isCliOutdated(installedVersion, PLUGIN_CONFIG.EXPECTED_CLI_VERSION)) {
|
|
2405
|
+
outdatedPromptDelivered = true;
|
|
2406
|
+
logger.warn(
|
|
2407
|
+
`EigenFlux CLI outdated (installed=${installedVersion}, expected>=${PLUGIN_CONFIG.EXPECTED_CLI_VERSION}); delivering upgrade prompt`
|
|
2408
|
+
);
|
|
2409
|
+
await deliverOutdatedPrompt(
|
|
2410
|
+
api,
|
|
2411
|
+
logger,
|
|
2412
|
+
pluginConfig,
|
|
2413
|
+
installedVersion,
|
|
2414
|
+
PLUGIN_CONFIG.EXPECTED_CLI_VERSION,
|
|
2415
|
+
store
|
|
2416
|
+
);
|
|
2417
|
+
}
|
|
2418
|
+
}
|
|
2266
2419
|
},
|
|
2267
2420
|
stop: async () => {
|
|
2268
2421
|
logger.info("Stopping EigenFlux discovery service...");
|
|
@@ -2276,6 +2429,7 @@ function registerPlugin(api) {
|
|
|
2276
2429
|
}
|
|
2277
2430
|
runtimes = [];
|
|
2278
2431
|
notInstalledPromptDelivered = false;
|
|
2432
|
+
outdatedPromptDelivered = false;
|
|
2279
2433
|
}
|
|
2280
2434
|
});
|
|
2281
2435
|
registerCommand(
|
|
@@ -2323,11 +2477,6 @@ var PLUGIN_CONFIG_SCHEMA = (0, import_plugin_entry.buildJsonPluginConfigSchema)(
|
|
|
2323
2477
|
replyAccountId: { type: "string" }
|
|
2324
2478
|
}
|
|
2325
2479
|
}
|
|
2326
|
-
},
|
|
2327
|
-
_credentialBackup: {
|
|
2328
|
-
type: "object",
|
|
2329
|
-
description: "Internal: persisted credential backups for sandbox environments",
|
|
2330
|
-
additionalProperties: { type: "object" }
|
|
2331
2480
|
}
|
|
2332
2481
|
}
|
|
2333
2482
|
});
|
|
@@ -2342,48 +2491,6 @@ var index_default = (0, import_plugin_entry.definePluginEntry)({
|
|
|
2342
2491
|
}
|
|
2343
2492
|
});
|
|
2344
2493
|
var INSTALL_COMMAND = "curl -fsSL https://eigenflux.ai/install.sh | bash";
|
|
2345
|
-
var _lastBackedUpToken = {};
|
|
2346
|
-
function backupCredentialsToConfig(api, logger, credentialsLoader, serverName) {
|
|
2347
|
-
const authState = credentialsLoader.loadAuthState();
|
|
2348
|
-
if (authState.status !== "available") {
|
|
2349
|
-
logger.debug(`[credential-backup] skip: credentials not available for server=${serverName}`);
|
|
2350
|
-
return;
|
|
2351
|
-
}
|
|
2352
|
-
if (_lastBackedUpToken[serverName] === authState.accessToken) {
|
|
2353
|
-
return;
|
|
2354
|
-
}
|
|
2355
|
-
const backup = {
|
|
2356
|
-
access_token: authState.accessToken,
|
|
2357
|
-
email: authState.email,
|
|
2358
|
-
expires_at: authState.expiresAt ?? Date.now() + 30 * 24 * 60 * 60 * 1e3,
|
|
2359
|
-
server: serverName,
|
|
2360
|
-
backed_up_at: Date.now()
|
|
2361
|
-
};
|
|
2362
|
-
logger.info(
|
|
2363
|
-
`[credential-backup] backing up credentials to OpenClaw config: server=${serverName}, email=${authState.email ?? "n/a"}`
|
|
2364
|
-
);
|
|
2365
|
-
try {
|
|
2366
|
-
api.runtime.config.mutateConfigFile({
|
|
2367
|
-
afterWrite: { mode: "none", reason: "eigenflux credential backup" },
|
|
2368
|
-
mutate(draft) {
|
|
2369
|
-
var _a, _b, _c, _d;
|
|
2370
|
-
draft.plugins ?? (draft.plugins = {});
|
|
2371
|
-
(_a = draft.plugins).entries ?? (_a.entries = {});
|
|
2372
|
-
(_b = draft.plugins.entries)["openclaw-eigenflux"] ?? (_b["openclaw-eigenflux"] = {});
|
|
2373
|
-
(_c = draft.plugins.entries["openclaw-eigenflux"]).config ?? (_c.config = {});
|
|
2374
|
-
(_d = draft.plugins.entries["openclaw-eigenflux"].config)._credentialBackup ?? (_d._credentialBackup = {});
|
|
2375
|
-
draft.plugins.entries["openclaw-eigenflux"].config._credentialBackup[serverName] = backup;
|
|
2376
|
-
}
|
|
2377
|
-
}).then(() => {
|
|
2378
|
-
_lastBackedUpToken[serverName] = authState.accessToken;
|
|
2379
|
-
logger.info(`[credential-backup] saved to OpenClaw config for server=${serverName}`);
|
|
2380
|
-
}).catch((err) => {
|
|
2381
|
-
logger.warn(`[credential-backup] failed to save: ${err.message}`);
|
|
2382
|
-
});
|
|
2383
|
-
} catch (err) {
|
|
2384
|
-
logger.warn(`[credential-backup] sync error: ${err instanceof Error ? err.message : String(err)}`);
|
|
2385
|
-
}
|
|
2386
|
-
}
|
|
2387
2494
|
async function deliverNotInstalledPrompt(api, logger, pluginConfig, _eigenfluxHome, bin, store) {
|
|
2388
2495
|
const notifier = new EigenFluxNotifier(api, logger, {
|
|
2389
2496
|
sessionKey: DEFAULT_ROUTING.sessionKey,
|
|
@@ -2398,24 +2505,26 @@ async function deliverNotInstalledPrompt(api, logger, pluginConfig, _eigenfluxHo
|
|
|
2398
2505
|
buildNotInstalledPromptTemplate({ bin, installCommand: INSTALL_COMMAND })
|
|
2399
2506
|
);
|
|
2400
2507
|
}
|
|
2508
|
+
async function deliverOutdatedPrompt(api, logger, pluginConfig, installed, expected, _store) {
|
|
2509
|
+
const notifier = new EigenFluxNotifier(api, logger, {
|
|
2510
|
+
sessionKey: DEFAULT_ROUTING.sessionKey,
|
|
2511
|
+
agentId: DEFAULT_ROUTING.agentId,
|
|
2512
|
+
replyChannel: DEFAULT_ROUTING.replyChannel,
|
|
2513
|
+
replyTo: DEFAULT_ROUTING.replyTo,
|
|
2514
|
+
replyAccountId: DEFAULT_ROUTING.replyAccountId,
|
|
2515
|
+
openclawCliBin: pluginConfig.openclawCliBin,
|
|
2516
|
+
routeOverrides: DEFAULT_ROUTING.routeOverrides
|
|
2517
|
+
});
|
|
2518
|
+
await notifier.deliver(
|
|
2519
|
+
buildOutdatedPromptTemplate({ installed, expected, updateCommand: INSTALL_COMMAND })
|
|
2520
|
+
);
|
|
2521
|
+
}
|
|
2401
2522
|
function buildFeedSessionKey(serverName) {
|
|
2402
2523
|
return `eigenflux:feed:${serverName}`;
|
|
2403
2524
|
}
|
|
2404
2525
|
function createServerRuntime(api, logger, pluginConfig, server, eigenfluxHome, store) {
|
|
2405
2526
|
const routing = pluginConfig.serverRouting[server.name] ?? DEFAULT_ROUTING;
|
|
2406
2527
|
const credentialsLoader = new CredentialsLoader(logger, eigenfluxHome, server.name);
|
|
2407
|
-
try {
|
|
2408
|
-
const rawConfig = api.pluginConfig;
|
|
2409
|
-
const backupMap = rawConfig?._credentialBackup;
|
|
2410
|
-
const backup = backupMap?.[server.name];
|
|
2411
|
-
if (backup?.access_token) {
|
|
2412
|
-
credentialsLoader.restoreFromBackup(backup);
|
|
2413
|
-
} else {
|
|
2414
|
-
logger.debug(`[credential-restore] no backup found in OpenClaw config for server=${server.name}`);
|
|
2415
|
-
}
|
|
2416
|
-
} catch (err) {
|
|
2417
|
-
logger.warn(`[credential-restore] failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
2418
|
-
}
|
|
2419
2528
|
const notifier = new EigenFluxNotifier(api, logger, {
|
|
2420
2529
|
store,
|
|
2421
2530
|
eigenfluxBin: pluginConfig.eigenfluxBin,
|
|
@@ -2464,7 +2573,6 @@ function createServerRuntime(api, logger, pluginConfig, server, eigenfluxHome, s
|
|
|
2464
2573
|
logger,
|
|
2465
2574
|
onFeedPolled: async (payload) => {
|
|
2466
2575
|
resetAuthPromptGate();
|
|
2467
|
-
backupCredentialsToConfig(api, logger, credentialsLoader, server.name);
|
|
2468
2576
|
const items = payload.data?.items ?? [];
|
|
2469
2577
|
const notifications = payload.data?.notifications ?? [];
|
|
2470
2578
|
if (feedDeliveryInFlight && feedDeliveryStartedAt > 0) {
|
|
@@ -2526,9 +2634,27 @@ function createServerRuntime(api, logger, pluginConfig, server, eigenfluxHome, s
|
|
|
2526
2634
|
serverName: server.name,
|
|
2527
2635
|
eigenfluxBin: pluginConfig.eigenfluxBin,
|
|
2528
2636
|
logger,
|
|
2637
|
+
// OpenClaw adapter for the host-agnostic `eigenflux profile refresh-prompt`
|
|
2638
|
+
// core: supply the host-specific inputs (memory dir + extracted session
|
|
2639
|
+
// snippets); the CLI reads the memory markdown and assembles the prompt.
|
|
2640
|
+
// The state dir is resolved via the SDK, NOT api.rootDir (which is the
|
|
2641
|
+
// plugin's install directory). Best-effort; empty on error.
|
|
2642
|
+
//
|
|
2643
|
+
// TODO(multi-host): each host gets its own thin adapter that returns
|
|
2644
|
+
// { memoryDirs, sessionSnippets } and delivers the CLI's prompt silently:
|
|
2645
|
+
// - Claude Code: memory from CLAUDE.md / ~/.claude memory; session from
|
|
2646
|
+
// ~/.claude/projects/**/*.jsonl; delivery via the claude/channel
|
|
2647
|
+
// (note: channel pushes are user-visible — true silence needs more work).
|
|
2648
|
+
// - Hermes: memory/session locations + silent-delivery mechanism TBD —
|
|
2649
|
+
// investigate the host before writing the adapter.
|
|
2650
|
+
// - Codex: memory likely AGENTS.md; session store + delivery TBD.
|
|
2651
|
+
collectContext: () => {
|
|
2652
|
+
const stateDir = resolveOpenClawStateDir(logger);
|
|
2653
|
+
return stateDir ? collectOpenClawContext(stateDir, logger) : EMPTY_CONTEXT;
|
|
2654
|
+
},
|
|
2529
2655
|
onRefreshPrompt: async (prompt) => {
|
|
2530
2656
|
resetAuthPromptGate();
|
|
2531
|
-
await notifier.deliver(prompt);
|
|
2657
|
+
await notifier.deliver(prompt, { silent: true });
|
|
2532
2658
|
},
|
|
2533
2659
|
onAuthRequired: async () => {
|
|
2534
2660
|
await notifyAuthRequired({ reason: "auth_required" });
|
|
@@ -2588,7 +2714,7 @@ function registerCommand(api, logger, pluginConfig, eigenfluxHome, store, getRun
|
|
|
2588
2714
|
};
|
|
2589
2715
|
api.registerCommand({
|
|
2590
2716
|
name: "eigenflux",
|
|
2591
|
-
description: "EigenFlux plugin commands: auth, profile, servers, feed, pm, here, version",
|
|
2717
|
+
description: "EigenFlux plugin commands: auth, profile, refresh, servers, feed, pm, here, version",
|
|
2592
2718
|
acceptsArgs: true,
|
|
2593
2719
|
handler: async (ctx) => {
|
|
2594
2720
|
const parsed = parseCommandArgs(ctx.args);
|
|
@@ -2625,6 +2751,22 @@ function registerCommand(api, logger, pluginConfig, eigenfluxHome, store, getRun
|
|
|
2625
2751
|
return {
|
|
2626
2752
|
text: await buildProfileText(runtime, pluginConfig.eigenfluxBin)
|
|
2627
2753
|
};
|
|
2754
|
+
case "refresh": {
|
|
2755
|
+
const probeStateDir = resolveOpenClawStateDir(logger);
|
|
2756
|
+
const probe = probeStateDir ? collectOpenClawContext(probeStateDir, logger) : EMPTY_CONTEXT;
|
|
2757
|
+
void runtime.profileRefresher.triggerNow().catch((err) => {
|
|
2758
|
+
logger.error(
|
|
2759
|
+
`Manual profile refresh failed for server=${runtime.server.name}: ${err instanceof Error ? err.message : String(err)}`
|
|
2760
|
+
);
|
|
2761
|
+
});
|
|
2762
|
+
return {
|
|
2763
|
+
text: [
|
|
2764
|
+
`Triggered a silent profile refresh for server=${runtime.server.name} (running in background).`,
|
|
2765
|
+
`context probe: memory_dirs=${probe.memoryDirs.length}, session=${probe.sessionSnippets.length} snippet(s), stateDir=${probeStateDir ?? "undefined"}`,
|
|
2766
|
+
"No channel reply. Verify via a new agent_bio_history row if the bio changed."
|
|
2767
|
+
].join("\n")
|
|
2768
|
+
};
|
|
2769
|
+
}
|
|
2628
2770
|
case "feed":
|
|
2629
2771
|
return {
|
|
2630
2772
|
text: await buildFeedText(runtime)
|
|
@@ -2715,6 +2857,7 @@ function buildHelpText(runtimes) {
|
|
|
2715
2857
|
"",
|
|
2716
2858
|
"/eigenflux auth \u2014 Show credential status",
|
|
2717
2859
|
"/eigenflux profile \u2014 Fetch agent profile",
|
|
2860
|
+
"/eigenflux refresh \u2014 Trigger a silent daily-style bio refresh now",
|
|
2718
2861
|
"/eigenflux servers \u2014 List discovered servers",
|
|
2719
2862
|
"/eigenflux feed \u2014 Run one feed refresh",
|
|
2720
2863
|
"/eigenflux pm \u2014 Show PM stream status",
|