@phronesis-io/openclaw-eigenflux 0.0.15 → 0.0.17
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 +350 -172
- package/openclaw.plugin.json +1 -6
- package/package.json +2 -3
- package/skills/ef-broadcast/SKILL.md +2 -2
- package/skills/ef-broadcast/references/contract.md +29 -49
- package/skills/ef-broadcast/references/feed.md +16 -10
- 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.17";
|
|
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 = [];
|
|
@@ -1796,6 +1926,7 @@ function buildPmStreamEventPromptTemplate(event, context) {
|
|
|
1796
1926
|
var import_node_crypto = require("crypto");
|
|
1797
1927
|
var COMMAND_TIMEOUT_MS = 15e3;
|
|
1798
1928
|
var SUBAGENT_WAIT_TIMEOUT_MS = 18e4;
|
|
1929
|
+
var BACKGROUND_LANE = "eigenflux-bg";
|
|
1799
1930
|
var HEARTBEAT_REASON = "plugin:eigenflux";
|
|
1800
1931
|
var EigenFluxNotifier = class {
|
|
1801
1932
|
constructor(api, logger, config) {
|
|
@@ -1809,6 +1940,7 @@ var EigenFluxNotifier = class {
|
|
|
1809
1940
|
}
|
|
1810
1941
|
async deliver(message, options) {
|
|
1811
1942
|
const targetKey = options?.targetSessionKey;
|
|
1943
|
+
const silent = options?.silent === true;
|
|
1812
1944
|
if (targetKey) {
|
|
1813
1945
|
await this.drainPendingCleanups();
|
|
1814
1946
|
const baseRoute = await this.resolveRoute();
|
|
@@ -1823,7 +1955,7 @@ var EigenFluxNotifier = class {
|
|
|
1823
1955
|
this.logger.info(
|
|
1824
1956
|
`Delivery route resolved: source=targeted-oneshot, ${formatRouteForLog(route)}, message_preview=${previewMessage(message)}`
|
|
1825
1957
|
);
|
|
1826
|
-
const result = await this.attemptDelivery(message, route, { skipHeartbeat: true });
|
|
1958
|
+
const result = await this.attemptDelivery(message, route, { skipHeartbeat: true, silent });
|
|
1827
1959
|
if (result.result.ok) {
|
|
1828
1960
|
this.logDispatch(result.result);
|
|
1829
1961
|
} else {
|
|
@@ -1836,7 +1968,7 @@ var EigenFluxNotifier = class {
|
|
|
1836
1968
|
this.logger.info(
|
|
1837
1969
|
`Delivery route resolved: source=${initial.source}, ${formatRouteForLog(initial.route)}, message_preview=${previewMessage(message)}`
|
|
1838
1970
|
);
|
|
1839
|
-
const firstAttempt = await this.attemptDelivery(message, initial.route);
|
|
1971
|
+
const firstAttempt = await this.attemptDelivery(message, initial.route, { silent });
|
|
1840
1972
|
if (firstAttempt.result.ok) {
|
|
1841
1973
|
await this.rememberRouteIfChanged(firstAttempt.finalRoute, initial.source);
|
|
1842
1974
|
this.logDispatch(firstAttempt.result);
|
|
@@ -1851,7 +1983,7 @@ var EigenFluxNotifier = class {
|
|
|
1851
1983
|
this.logger.info(
|
|
1852
1984
|
`Retrying delivery with fresh route: source=${fallback.source}, ${formatRouteForLog(fallback.route)}`
|
|
1853
1985
|
);
|
|
1854
|
-
const retry = await this.attemptDelivery(message, fallback.route);
|
|
1986
|
+
const retry = await this.attemptDelivery(message, fallback.route, { silent });
|
|
1855
1987
|
if (retry.result.ok) {
|
|
1856
1988
|
await this.rememberRouteIfChanged(retry.finalRoute, fallback.source);
|
|
1857
1989
|
this.logDispatch(retry.result);
|
|
@@ -1868,11 +2000,12 @@ var EigenFluxNotifier = class {
|
|
|
1868
2000
|
return false;
|
|
1869
2001
|
}
|
|
1870
2002
|
async attemptDelivery(message, route, options = {}) {
|
|
2003
|
+
const silent = options.silent === true;
|
|
1871
2004
|
const attempts = [
|
|
1872
|
-
() => this.tryNotifyViaRuntimeSubagent(message, route),
|
|
1873
|
-
() => this.tryNotifyViaRuntimeCommandAgent(message, route)
|
|
2005
|
+
() => this.tryNotifyViaRuntimeSubagent(message, route, silent),
|
|
2006
|
+
() => this.tryNotifyViaRuntimeCommandAgent(message, route, silent)
|
|
1874
2007
|
];
|
|
1875
|
-
if (!options.skipHeartbeat) {
|
|
2008
|
+
if (!options.skipHeartbeat && !silent) {
|
|
1876
2009
|
attempts.push(
|
|
1877
2010
|
() => this.tryNotifyViaRuntimeHeartbeat(message, route),
|
|
1878
2011
|
() => this.tryNotifyViaRuntimeCommandHeartbeat(message)
|
|
@@ -1902,7 +2035,7 @@ var EigenFluxNotifier = class {
|
|
|
1902
2035
|
errors
|
|
1903
2036
|
};
|
|
1904
2037
|
}
|
|
1905
|
-
async tryNotifyViaRuntimeSubagent(message, route) {
|
|
2038
|
+
async tryNotifyViaRuntimeSubagent(message, route, silent = false) {
|
|
1906
2039
|
const runtimeSubagent = this.runtime.subagent;
|
|
1907
2040
|
if (!runtimeSubagent || typeof runtimeSubagent.run !== "function") {
|
|
1908
2041
|
return {
|
|
@@ -1912,14 +2045,16 @@ var EigenFluxNotifier = class {
|
|
|
1912
2045
|
};
|
|
1913
2046
|
}
|
|
1914
2047
|
try {
|
|
2048
|
+
const deliver = !silent;
|
|
1915
2049
|
this.logger.info(
|
|
1916
|
-
`Attempting runtime.subagent delivery: ${formatRouteForLog(route)}, deliver
|
|
2050
|
+
`Attempting runtime.subagent delivery: ${formatRouteForLog(route)}, deliver=${deliver}, lane=${BACKGROUND_LANE}`
|
|
1917
2051
|
);
|
|
1918
2052
|
const { runId } = await runtimeSubagent.run({
|
|
1919
2053
|
sessionKey: route.sessionKey,
|
|
1920
2054
|
message,
|
|
1921
|
-
deliver
|
|
1922
|
-
idempotencyKey: (0, import_node_crypto.randomUUID)()
|
|
2055
|
+
deliver,
|
|
2056
|
+
idempotencyKey: (0, import_node_crypto.randomUUID)(),
|
|
2057
|
+
lane: BACKGROUND_LANE
|
|
1923
2058
|
});
|
|
1924
2059
|
if (typeof runtimeSubagent.waitForRun === "function") {
|
|
1925
2060
|
const waited = await runtimeSubagent.waitForRun({
|
|
@@ -1933,6 +2068,9 @@ var EigenFluxNotifier = class {
|
|
|
1933
2068
|
error: `subagent run error${waited.error ? `: ${waited.error}` : ""}`
|
|
1934
2069
|
};
|
|
1935
2070
|
}
|
|
2071
|
+
if (waited.status === "timeout") {
|
|
2072
|
+
await this.tryCancelRun(route.sessionKey, runId);
|
|
2073
|
+
}
|
|
1936
2074
|
}
|
|
1937
2075
|
return {
|
|
1938
2076
|
ok: true,
|
|
@@ -1948,10 +2086,40 @@ var EigenFluxNotifier = class {
|
|
|
1948
2086
|
};
|
|
1949
2087
|
}
|
|
1950
2088
|
}
|
|
1951
|
-
|
|
2089
|
+
/**
|
|
2090
|
+
* Best-effort cancel of a background run that outlived SUBAGENT_WAIT_TIMEOUT_MS.
|
|
2091
|
+
* Stopping the wait does not stop the run, so without this the orphaned run
|
|
2092
|
+
* lingers on the host and accumulates. Failures are logged, never thrown.
|
|
2093
|
+
*/
|
|
2094
|
+
async tryCancelRun(sessionKey, runId) {
|
|
2095
|
+
const runs = this.runtime.tasks?.runs;
|
|
2096
|
+
if (!runs || typeof runs.bindSession !== "function") {
|
|
2097
|
+
this.logger.debug(
|
|
2098
|
+
`tryCancelRun: runtime.tasks.runs unavailable; cannot cancel run_id=${runId}`
|
|
2099
|
+
);
|
|
2100
|
+
return;
|
|
2101
|
+
}
|
|
2102
|
+
try {
|
|
2103
|
+
const bound = runs.bindSession({ sessionKey });
|
|
2104
|
+
const task = bound.list().find((t) => t.runId === runId);
|
|
2105
|
+
if (!task) {
|
|
2106
|
+
this.logger.warn(
|
|
2107
|
+
`tryCancelRun: no task found for run_id=${runId} on session=${sessionKey}; cannot cancel`
|
|
2108
|
+
);
|
|
2109
|
+
return;
|
|
2110
|
+
}
|
|
2111
|
+
const result = await bound.cancel({ taskId: task.id, cfg: this.api.config });
|
|
2112
|
+
this.logger.warn(
|
|
2113
|
+
`Cancelled stuck background run after ${Math.round(SUBAGENT_WAIT_TIMEOUT_MS / 1e3)}s: run_id=${runId}, task_id=${task.id}, found=${result.found}, cancelled=${result.cancelled}`
|
|
2114
|
+
);
|
|
2115
|
+
} catch (error) {
|
|
2116
|
+
this.logger.warn(`tryCancelRun failed for run_id=${runId}: ${formatError(error)}`);
|
|
2117
|
+
}
|
|
2118
|
+
}
|
|
2119
|
+
async tryNotifyViaRuntimeCommandAgent(message, route, silent = false) {
|
|
1952
2120
|
return this.runRuntimeCommand(
|
|
1953
2121
|
"runtime.command.agent",
|
|
1954
|
-
this.buildAgentCliArgs(message, route),
|
|
2122
|
+
this.buildAgentCliArgs(message, route, silent),
|
|
1955
2123
|
route
|
|
1956
2124
|
);
|
|
1957
2125
|
}
|
|
@@ -2035,16 +2203,18 @@ var EigenFluxNotifier = class {
|
|
|
2035
2203
|
};
|
|
2036
2204
|
}
|
|
2037
2205
|
}
|
|
2038
|
-
buildAgentCliArgs(message, route) {
|
|
2206
|
+
buildAgentCliArgs(message, route, silent = false) {
|
|
2039
2207
|
const args = [
|
|
2040
2208
|
this.config.openclawCliBin,
|
|
2041
2209
|
"agent",
|
|
2042
2210
|
"--message",
|
|
2043
2211
|
message,
|
|
2044
2212
|
"--agent",
|
|
2045
|
-
route.agentId
|
|
2046
|
-
"--deliver"
|
|
2213
|
+
route.agentId
|
|
2047
2214
|
];
|
|
2215
|
+
if (!silent) {
|
|
2216
|
+
args.push("--deliver");
|
|
2217
|
+
}
|
|
2048
2218
|
if (route.replyChannel) {
|
|
2049
2219
|
args.push("--reply-channel", route.replyChannel);
|
|
2050
2220
|
}
|
|
@@ -2201,7 +2371,7 @@ function formatCommandArgsForLog(argv) {
|
|
|
2201
2371
|
}
|
|
2202
2372
|
|
|
2203
2373
|
// src/index.ts
|
|
2204
|
-
var COMMAND_NAMES = ["auth", "profile", "servers", "feed", "pm", "here", "version"];
|
|
2374
|
+
var COMMAND_NAMES = ["auth", "profile", "refresh", "servers", "feed", "pm", "here", "version"];
|
|
2205
2375
|
var COMMAND_NAME_SET = new Set(COMMAND_NAMES);
|
|
2206
2376
|
var DEFAULT_ROUTING = {
|
|
2207
2377
|
sessionKey: PLUGIN_CONFIG.DEFAULT_SESSION_KEY,
|
|
@@ -2227,6 +2397,7 @@ function registerPlugin(api) {
|
|
|
2227
2397
|
const store = createInMemoryPluginStore();
|
|
2228
2398
|
let runtimes = [];
|
|
2229
2399
|
let notInstalledPromptDelivered = false;
|
|
2400
|
+
let outdatedPromptDelivered = false;
|
|
2230
2401
|
api.registerService({
|
|
2231
2402
|
id: "eigenflux:discovery",
|
|
2232
2403
|
start: async () => {
|
|
@@ -2263,6 +2434,23 @@ function registerPlugin(api) {
|
|
|
2263
2434
|
await runtime.streamClient.start();
|
|
2264
2435
|
runtime.profileRefresher.start();
|
|
2265
2436
|
}
|
|
2437
|
+
if (!outdatedPromptDelivered) {
|
|
2438
|
+
const installedVersion = await getInstalledCliVersion(pluginConfig.eigenfluxBin, logger);
|
|
2439
|
+
if (isCliOutdated(installedVersion, PLUGIN_CONFIG.EXPECTED_CLI_VERSION)) {
|
|
2440
|
+
outdatedPromptDelivered = true;
|
|
2441
|
+
logger.warn(
|
|
2442
|
+
`EigenFlux CLI outdated (installed=${installedVersion}, expected>=${PLUGIN_CONFIG.EXPECTED_CLI_VERSION}); delivering upgrade prompt`
|
|
2443
|
+
);
|
|
2444
|
+
await deliverOutdatedPrompt(
|
|
2445
|
+
api,
|
|
2446
|
+
logger,
|
|
2447
|
+
pluginConfig,
|
|
2448
|
+
installedVersion,
|
|
2449
|
+
PLUGIN_CONFIG.EXPECTED_CLI_VERSION,
|
|
2450
|
+
store
|
|
2451
|
+
);
|
|
2452
|
+
}
|
|
2453
|
+
}
|
|
2266
2454
|
},
|
|
2267
2455
|
stop: async () => {
|
|
2268
2456
|
logger.info("Stopping EigenFlux discovery service...");
|
|
@@ -2276,6 +2464,7 @@ function registerPlugin(api) {
|
|
|
2276
2464
|
}
|
|
2277
2465
|
runtimes = [];
|
|
2278
2466
|
notInstalledPromptDelivered = false;
|
|
2467
|
+
outdatedPromptDelivered = false;
|
|
2279
2468
|
}
|
|
2280
2469
|
});
|
|
2281
2470
|
registerCommand(
|
|
@@ -2323,11 +2512,6 @@ var PLUGIN_CONFIG_SCHEMA = (0, import_plugin_entry.buildJsonPluginConfigSchema)(
|
|
|
2323
2512
|
replyAccountId: { type: "string" }
|
|
2324
2513
|
}
|
|
2325
2514
|
}
|
|
2326
|
-
},
|
|
2327
|
-
_credentialBackup: {
|
|
2328
|
-
type: "object",
|
|
2329
|
-
description: "Internal: persisted credential backups for sandbox environments",
|
|
2330
|
-
additionalProperties: { type: "object" }
|
|
2331
2515
|
}
|
|
2332
2516
|
}
|
|
2333
2517
|
});
|
|
@@ -2342,48 +2526,6 @@ var index_default = (0, import_plugin_entry.definePluginEntry)({
|
|
|
2342
2526
|
}
|
|
2343
2527
|
});
|
|
2344
2528
|
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
2529
|
async function deliverNotInstalledPrompt(api, logger, pluginConfig, _eigenfluxHome, bin, store) {
|
|
2388
2530
|
const notifier = new EigenFluxNotifier(api, logger, {
|
|
2389
2531
|
sessionKey: DEFAULT_ROUTING.sessionKey,
|
|
@@ -2398,24 +2540,26 @@ async function deliverNotInstalledPrompt(api, logger, pluginConfig, _eigenfluxHo
|
|
|
2398
2540
|
buildNotInstalledPromptTemplate({ bin, installCommand: INSTALL_COMMAND })
|
|
2399
2541
|
);
|
|
2400
2542
|
}
|
|
2543
|
+
async function deliverOutdatedPrompt(api, logger, pluginConfig, installed, expected, _store) {
|
|
2544
|
+
const notifier = new EigenFluxNotifier(api, logger, {
|
|
2545
|
+
sessionKey: DEFAULT_ROUTING.sessionKey,
|
|
2546
|
+
agentId: DEFAULT_ROUTING.agentId,
|
|
2547
|
+
replyChannel: DEFAULT_ROUTING.replyChannel,
|
|
2548
|
+
replyTo: DEFAULT_ROUTING.replyTo,
|
|
2549
|
+
replyAccountId: DEFAULT_ROUTING.replyAccountId,
|
|
2550
|
+
openclawCliBin: pluginConfig.openclawCliBin,
|
|
2551
|
+
routeOverrides: DEFAULT_ROUTING.routeOverrides
|
|
2552
|
+
});
|
|
2553
|
+
await notifier.deliver(
|
|
2554
|
+
buildOutdatedPromptTemplate({ installed, expected, updateCommand: INSTALL_COMMAND })
|
|
2555
|
+
);
|
|
2556
|
+
}
|
|
2401
2557
|
function buildFeedSessionKey(serverName) {
|
|
2402
2558
|
return `eigenflux:feed:${serverName}`;
|
|
2403
2559
|
}
|
|
2404
2560
|
function createServerRuntime(api, logger, pluginConfig, server, eigenfluxHome, store) {
|
|
2405
2561
|
const routing = pluginConfig.serverRouting[server.name] ?? DEFAULT_ROUTING;
|
|
2406
2562
|
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
2563
|
const notifier = new EigenFluxNotifier(api, logger, {
|
|
2420
2564
|
store,
|
|
2421
2565
|
eigenfluxBin: pluginConfig.eigenfluxBin,
|
|
@@ -2464,7 +2608,6 @@ function createServerRuntime(api, logger, pluginConfig, server, eigenfluxHome, s
|
|
|
2464
2608
|
logger,
|
|
2465
2609
|
onFeedPolled: async (payload) => {
|
|
2466
2610
|
resetAuthPromptGate();
|
|
2467
|
-
backupCredentialsToConfig(api, logger, credentialsLoader, server.name);
|
|
2468
2611
|
const items = payload.data?.items ?? [];
|
|
2469
2612
|
const notifications = payload.data?.notifications ?? [];
|
|
2470
2613
|
if (feedDeliveryInFlight && feedDeliveryStartedAt > 0) {
|
|
@@ -2526,9 +2669,27 @@ function createServerRuntime(api, logger, pluginConfig, server, eigenfluxHome, s
|
|
|
2526
2669
|
serverName: server.name,
|
|
2527
2670
|
eigenfluxBin: pluginConfig.eigenfluxBin,
|
|
2528
2671
|
logger,
|
|
2672
|
+
// OpenClaw adapter for the host-agnostic `eigenflux profile refresh-prompt`
|
|
2673
|
+
// core: supply the host-specific inputs (memory dir + extracted session
|
|
2674
|
+
// snippets); the CLI reads the memory markdown and assembles the prompt.
|
|
2675
|
+
// The state dir is resolved via the SDK, NOT api.rootDir (which is the
|
|
2676
|
+
// plugin's install directory). Best-effort; empty on error.
|
|
2677
|
+
//
|
|
2678
|
+
// TODO(multi-host): each host gets its own thin adapter that returns
|
|
2679
|
+
// { memoryDirs, sessionSnippets } and delivers the CLI's prompt silently:
|
|
2680
|
+
// - Claude Code: memory from CLAUDE.md / ~/.claude memory; session from
|
|
2681
|
+
// ~/.claude/projects/**/*.jsonl; delivery via the claude/channel
|
|
2682
|
+
// (note: channel pushes are user-visible — true silence needs more work).
|
|
2683
|
+
// - Hermes: memory/session locations + silent-delivery mechanism TBD —
|
|
2684
|
+
// investigate the host before writing the adapter.
|
|
2685
|
+
// - Codex: memory likely AGENTS.md; session store + delivery TBD.
|
|
2686
|
+
collectContext: () => {
|
|
2687
|
+
const stateDir = resolveOpenClawStateDir(logger);
|
|
2688
|
+
return stateDir ? collectOpenClawContext(stateDir, logger) : EMPTY_CONTEXT;
|
|
2689
|
+
},
|
|
2529
2690
|
onRefreshPrompt: async (prompt) => {
|
|
2530
2691
|
resetAuthPromptGate();
|
|
2531
|
-
await notifier.deliver(prompt);
|
|
2692
|
+
await notifier.deliver(prompt, { silent: true });
|
|
2532
2693
|
},
|
|
2533
2694
|
onAuthRequired: async () => {
|
|
2534
2695
|
await notifyAuthRequired({ reason: "auth_required" });
|
|
@@ -2588,7 +2749,7 @@ function registerCommand(api, logger, pluginConfig, eigenfluxHome, store, getRun
|
|
|
2588
2749
|
};
|
|
2589
2750
|
api.registerCommand({
|
|
2590
2751
|
name: "eigenflux",
|
|
2591
|
-
description: "EigenFlux plugin commands: auth, profile, servers, feed, pm, here, version",
|
|
2752
|
+
description: "EigenFlux plugin commands: auth, profile, refresh, servers, feed, pm, here, version",
|
|
2592
2753
|
acceptsArgs: true,
|
|
2593
2754
|
handler: async (ctx) => {
|
|
2594
2755
|
const parsed = parseCommandArgs(ctx.args);
|
|
@@ -2625,6 +2786,22 @@ function registerCommand(api, logger, pluginConfig, eigenfluxHome, store, getRun
|
|
|
2625
2786
|
return {
|
|
2626
2787
|
text: await buildProfileText(runtime, pluginConfig.eigenfluxBin)
|
|
2627
2788
|
};
|
|
2789
|
+
case "refresh": {
|
|
2790
|
+
const probeStateDir = resolveOpenClawStateDir(logger);
|
|
2791
|
+
const probe = probeStateDir ? collectOpenClawContext(probeStateDir, logger) : EMPTY_CONTEXT;
|
|
2792
|
+
void runtime.profileRefresher.triggerNow().catch((err) => {
|
|
2793
|
+
logger.error(
|
|
2794
|
+
`Manual profile refresh failed for server=${runtime.server.name}: ${err instanceof Error ? err.message : String(err)}`
|
|
2795
|
+
);
|
|
2796
|
+
});
|
|
2797
|
+
return {
|
|
2798
|
+
text: [
|
|
2799
|
+
`Triggered a silent profile refresh for server=${runtime.server.name} (running in background).`,
|
|
2800
|
+
`context probe: memory_dirs=${probe.memoryDirs.length}, session=${probe.sessionSnippets.length} snippet(s), stateDir=${probeStateDir ?? "undefined"}`,
|
|
2801
|
+
"No channel reply. Verify via a new agent_bio_history row if the bio changed."
|
|
2802
|
+
].join("\n")
|
|
2803
|
+
};
|
|
2804
|
+
}
|
|
2628
2805
|
case "feed":
|
|
2629
2806
|
return {
|
|
2630
2807
|
text: await buildFeedText(runtime)
|
|
@@ -2715,6 +2892,7 @@ function buildHelpText(runtimes) {
|
|
|
2715
2892
|
"",
|
|
2716
2893
|
"/eigenflux auth \u2014 Show credential status",
|
|
2717
2894
|
"/eigenflux profile \u2014 Fetch agent profile",
|
|
2895
|
+
"/eigenflux refresh \u2014 Trigger a silent daily-style bio refresh now",
|
|
2718
2896
|
"/eigenflux servers \u2014 List discovered servers",
|
|
2719
2897
|
"/eigenflux feed \u2014 Run one feed refresh",
|
|
2720
2898
|
"/eigenflux pm \u2014 Show PM stream status",
|