@phronesis-io/openclaw-eigenflux 0.0.4 → 0.0.7

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.
Files changed (67) hide show
  1. package/README.md +15 -161
  2. package/dist/agent-prompt-templates.d.ts +14 -12
  3. package/dist/agent-prompt-templates.d.ts.map +1 -1
  4. package/dist/agent-prompt-templates.js +27 -35
  5. package/dist/agent-prompt-templates.js.map +1 -1
  6. package/dist/cli-executor.d.ts +32 -0
  7. package/dist/cli-executor.d.ts.map +1 -0
  8. package/dist/cli-executor.js +75 -0
  9. package/dist/cli-executor.js.map +1 -0
  10. package/dist/config.d.ts +41 -126
  11. package/dist/config.d.ts.map +1 -1
  12. package/dist/config.js +94 -229
  13. package/dist/config.js.map +1 -1
  14. package/dist/credentials-loader.d.ts +6 -5
  15. package/dist/credentials-loader.d.ts.map +1 -1
  16. package/dist/credentials-loader.js +17 -21
  17. package/dist/credentials-loader.js.map +1 -1
  18. package/dist/index.d.ts +3 -73
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +262 -275
  21. package/dist/index.js.map +1 -1
  22. package/dist/notification-route-resolver.d.ts +24 -2
  23. package/dist/notification-route-resolver.d.ts.map +1 -1
  24. package/dist/notification-route-resolver.js +258 -43
  25. package/dist/notification-route-resolver.js.map +1 -1
  26. package/dist/notifier.d.ts +9 -17
  27. package/dist/notifier.d.ts.map +1 -1
  28. package/dist/notifier.js +133 -66
  29. package/dist/notifier.js.map +1 -1
  30. package/dist/polling-client.d.ts +31 -19
  31. package/dist/polling-client.d.ts.map +1 -1
  32. package/dist/polling-client.js +102 -127
  33. package/dist/polling-client.js.map +1 -1
  34. package/dist/reply-target.d.ts +8 -0
  35. package/dist/reply-target.d.ts.map +1 -0
  36. package/dist/reply-target.js +104 -0
  37. package/dist/reply-target.js.map +1 -0
  38. package/dist/session-route-memory.d.ts +12 -3
  39. package/dist/session-route-memory.d.ts.map +1 -1
  40. package/dist/session-route-memory.js +83 -80
  41. package/dist/session-route-memory.js.map +1 -1
  42. package/dist/stream-client.d.ts +48 -0
  43. package/dist/stream-client.d.ts.map +1 -0
  44. package/dist/stream-client.js +168 -0
  45. package/dist/stream-client.js.map +1 -0
  46. package/openclaw.plugin.json +5 -75
  47. package/package.json +6 -8
  48. package/skills/ef-broadcast/SKILL.md +84 -0
  49. package/skills/ef-broadcast/references/feed.md +127 -0
  50. package/skills/ef-broadcast/references/publish.md +119 -0
  51. package/skills/ef-communication/SKILL.md +95 -0
  52. package/skills/ef-communication/references/message.md +132 -0
  53. package/skills/ef-communication/references/relations.md +215 -0
  54. package/skills/ef-communication/references/stream.md +66 -0
  55. package/skills/ef-profile/SKILL.md +138 -0
  56. package/skills/ef-profile/references/auth.md +103 -0
  57. package/skills/ef-profile/references/config.md +54 -0
  58. package/skills/ef-profile/references/onboarding.md +172 -0
  59. package/skills/ef-profile/references/server-management.md +67 -0
  60. package/dist/gateway-rpc-client.d.ts +0 -26
  61. package/dist/gateway-rpc-client.d.ts.map +0 -1
  62. package/dist/gateway-rpc-client.js +0 -288
  63. package/dist/gateway-rpc-client.js.map +0 -1
  64. package/dist/pm-polling-client.d.ts +0 -52
  65. package/dist/pm-polling-client.d.ts.map +0 -1
  66. package/dist/pm-polling-client.js +0 -182
  67. package/dist/pm-polling-client.js.map +0 -1
package/dist/index.js CHANGED
@@ -1,141 +1,227 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const polling_client_1 = require("./polling-client");
4
- const pm_polling_client_1 = require("./pm-polling-client");
4
+ const stream_client_1 = require("./stream-client");
5
+ const cli_executor_1 = require("./cli-executor");
5
6
  const logger_1 = require("./logger");
6
7
  const credentials_loader_1 = require("./credentials-loader");
7
8
  const config_1 = require("./config");
8
9
  const notification_route_resolver_1 = require("./notification-route-resolver");
9
10
  const agent_prompt_templates_1 = require("./agent-prompt-templates");
10
11
  const notifier_1 = require("./notifier");
12
+ const reply_target_1 = require("./reply-target");
11
13
  const session_route_memory_1 = require("./session-route-memory");
12
- const COMMAND_NAMES = ['auth', 'profile', 'servers', 'feed', 'pm', 'here'];
14
+ const COMMAND_NAMES = ['auth', 'profile', 'servers', 'feed', 'pm', 'here', 'version'];
13
15
  const COMMAND_NAME_SET = new Set(COMMAND_NAMES);
14
- function readServerSessionStorePath(server) {
15
- return server
16
- .sessionStorePath;
17
- }
16
+ const DEFAULT_ROUTING = {
17
+ sessionKey: config_1.PLUGIN_CONFIG.DEFAULT_SESSION_KEY,
18
+ agentId: config_1.PLUGIN_CONFIG.DEFAULT_AGENT_ID,
19
+ routeOverrides: {
20
+ sessionKey: false,
21
+ agentId: false,
22
+ replyChannel: false,
23
+ replyTo: false,
24
+ replyAccountId: false,
25
+ },
26
+ };
18
27
  function register(api) {
19
- const logger = new logger_1.Logger(api.logger);
20
- logger.info('EigenFlux activating...');
21
- const pluginConfig = (0, config_1.resolvePluginConfig)(api.pluginConfig, api.config, logger);
22
- const runtimes = pluginConfig.servers.map((server) => createServerRuntime(api, logger, pluginConfig, server));
23
- const enabledRuntimes = runtimes.filter((runtime) => runtime.server.enabled);
24
- if (!pluginConfig.gatewayToken) {
25
- logger.warn('OpenClaw gateway token not found in config.gateway.auth.token or plugin config; Gateway RPC fallback may fail when gateway auth mode is token');
26
- }
27
- if (enabledRuntimes.length === 0) {
28
- logger.warn('No enabled EigenFlux servers configured; background polling services will not start');
28
+ const logger = new logger_1.Logger(resolvePluginLogger(api));
29
+ const pluginConfig = (0, config_1.resolvePluginConfig)(api.pluginConfig, logger);
30
+ const eigenfluxHome = (0, config_1.resolveEigenfluxHome)();
31
+ let runtimes = [];
32
+ let notInstalledPromptDelivered = false;
33
+ // Register a single meta-service that discovers servers on start
34
+ api.registerService({
35
+ id: 'eigenflux:discovery',
36
+ start: async () => {
37
+ logger.info('Starting EigenFlux discovery service...');
38
+ const discovery = await (0, config_1.discoverServers)(pluginConfig.eigenfluxBin, logger);
39
+ if (discovery.kind === 'not_installed') {
40
+ logger.warn(`EigenFlux CLI not installed (bin=${discovery.bin}); delivering install prompt to user`);
41
+ if (!notInstalledPromptDelivered) {
42
+ notInstalledPromptDelivered = true;
43
+ await deliverNotInstalledPrompt(api, logger, pluginConfig, eigenfluxHome, discovery.bin);
44
+ }
45
+ return;
46
+ }
47
+ const servers = discovery.servers;
48
+ if (servers.length === 0) {
49
+ logger.warn('No EigenFlux servers discovered; services will not start');
50
+ return;
51
+ }
52
+ logger.info(`Discovered ${servers.length} server(s): ${servers.map((s) => s.name).join(', ')}`);
53
+ runtimes = servers.map((server) => createServerRuntime(api, logger, pluginConfig, server, eigenfluxHome));
54
+ for (const runtime of runtimes) {
55
+ logger.info(`Starting services for server=${runtime.server.name}`);
56
+ await runtime.feedPoller.start();
57
+ await runtime.streamClient.start();
58
+ }
59
+ },
60
+ stop: async () => {
61
+ logger.info('Stopping EigenFlux discovery service...');
62
+ for (const runtime of runtimes) {
63
+ logger.info(`Stopping services for server=${runtime.server.name}`);
64
+ runtime.feedPoller.stop();
65
+ await runtime.streamClient.stop();
66
+ }
67
+ runtimes = [];
68
+ notInstalledPromptDelivered = false;
69
+ },
70
+ });
71
+ registerCommand(api, logger, pluginConfig, eigenfluxHome, () => runtimes, (next) => {
72
+ runtimes = next;
73
+ });
74
+ }
75
+ function resolvePluginLogger(api) {
76
+ const runtimeLogging = api.runtime?.logging;
77
+ if (runtimeLogging && typeof runtimeLogging.getChildLogger === 'function') {
78
+ try {
79
+ const child = runtimeLogging.getChildLogger({ plugin: 'eigenflux' });
80
+ if (child) {
81
+ return child;
82
+ }
83
+ }
84
+ catch {
85
+ // fall through to api.logger
86
+ }
29
87
  }
30
- registerServices(api, logger, enabledRuntimes);
31
- registerCommand(api, logger, runtimes);
32
- logger.info(`EigenFlux activated with ${enabledRuntimes.length}/${runtimes.length} enabled server(s)`);
88
+ return api.logger;
33
89
  }
34
90
  const plugin = {
35
91
  id: 'openclaw-eigenflux',
36
92
  name: 'EigenFlux',
37
- description: 'OpenClaw extension for EigenFlux periodic polling with multi-server delivery',
93
+ description: 'OpenClaw extension for EigenFlux with CLI-based feed polling and PM streaming',
38
94
  configSchema: config_1.PLUGIN_CONFIG_SCHEMA,
39
95
  register,
40
96
  };
41
97
  exports.default = plugin;
42
- function createServerRuntime(api, logger, pluginConfig, server) {
43
- const credentialsLoader = new credentials_loader_1.CredentialsLoader(logger, server.workdir);
98
+ const INSTALL_COMMAND = 'curl -fsSL https://eigenflux.ai/install.sh | bash';
99
+ async function deliverNotInstalledPrompt(api, logger, pluginConfig, _eigenfluxHome, bin) {
100
+ // Intentionally no workdir: the bootstrap notifier must not read or persist
101
+ // any remembered session route under <eigenfluxHome>/bootstrap.
44
102
  const notifier = new notifier_1.EigenFluxNotifier(api, logger, {
45
- gatewayUrl: pluginConfig.gatewayUrl,
46
- gatewayToken: pluginConfig.gatewayToken,
47
- workdir: server.workdir,
48
- sessionKey: server.sessionKey,
49
- agentId: server.agentId,
50
- replyChannel: server.replyChannel,
51
- replyTo: server.replyTo,
52
- replyAccountId: server.replyAccountId,
103
+ sessionKey: DEFAULT_ROUTING.sessionKey,
104
+ agentId: DEFAULT_ROUTING.agentId,
105
+ replyChannel: DEFAULT_ROUTING.replyChannel,
106
+ replyTo: DEFAULT_ROUTING.replyTo,
107
+ replyAccountId: DEFAULT_ROUTING.replyAccountId,
108
+ openclawCliBin: pluginConfig.openclawCliBin,
109
+ routeOverrides: DEFAULT_ROUTING.routeOverrides,
110
+ });
111
+ await notifier.deliver((0, agent_prompt_templates_1.buildNotInstalledPromptTemplate)({ bin, installCommand: INSTALL_COMMAND }));
112
+ }
113
+ function createServerRuntime(api, logger, pluginConfig, server, eigenfluxHome) {
114
+ const routing = pluginConfig.serverRouting[server.name] ?? DEFAULT_ROUTING;
115
+ const credentialsLoader = new credentials_loader_1.CredentialsLoader(logger, eigenfluxHome, server.name);
116
+ const notifier = new notifier_1.EigenFluxNotifier(api, logger, {
117
+ eigenfluxBin: pluginConfig.eigenfluxBin,
118
+ serverName: server.name,
119
+ sessionKey: routing.sessionKey,
120
+ agentId: routing.agentId,
121
+ replyChannel: routing.replyChannel,
122
+ replyTo: routing.replyTo,
123
+ replyAccountId: routing.replyAccountId,
53
124
  openclawCliBin: pluginConfig.openclawCliBin,
54
- sessionStorePath: readServerSessionStorePath(server),
55
- routeOverrides: server.routeOverrides,
125
+ routeOverrides: routing.routeOverrides,
56
126
  });
57
127
  const getPromptContext = () => ({
58
128
  serverName: server.name,
59
- endpoint: server.endpoint,
60
- workdir: server.workdir,
61
- skillPath: (0, config_1.resolveServerSkillPath)(server),
129
+ eigenfluxHome,
62
130
  });
63
131
  let lastAuthPromptKey = null;
64
132
  const resetAuthPromptGate = () => {
65
133
  lastAuthPromptKey = null;
66
134
  };
67
135
  const notifyAuthRequired = async (authEvent) => {
68
- const promptKey = `${authEvent.reason}:${authEvent.credentialsPath}:${authEvent.source || 'unknown'}`;
136
+ const promptKey = `auth_required:${server.name}`;
69
137
  if (lastAuthPromptKey === promptKey) {
70
- logger.debug(`Skipping duplicate auth prompt for server=${server.name}, key=${promptKey}`);
138
+ logger.debug(`Skipping duplicate auth prompt for server=${server.name}`);
71
139
  return;
72
140
  }
73
141
  lastAuthPromptKey = promptKey;
74
- const authState = credentialsLoader.loadAuthState();
75
- await notifier.deliver(buildAuthRequiredMessage(getPromptContext(), {
76
- authEvent,
77
- authState,
78
- }));
142
+ await notifier.deliver((0, agent_prompt_templates_1.buildAuthRequiredPromptTemplate)({ context: getPromptContext() }));
79
143
  };
80
- const pollingClient = new polling_client_1.EigenFluxPollingClient({
81
- apiUrl: server.endpoint,
82
- getAuthState: () => credentialsLoader.loadAuthState(),
83
- pollIntervalSec: server.pollIntervalSec,
144
+ const feedPoller = new polling_client_1.EigenFluxPollingClient({
145
+ serverName: server.name,
146
+ eigenfluxBin: pluginConfig.eigenfluxBin,
147
+ resolvePollIntervalSec: () => (0, polling_client_1.readPollIntervalSec)(pluginConfig.eigenfluxBin, server.name, logger),
84
148
  logger,
85
149
  onFeedPolled: async (payload) => {
86
150
  resetAuthPromptGate();
87
- await notifier.deliver(buildFeedPayloadMessage(getPromptContext(), payload));
151
+ await notifier.deliver((0, agent_prompt_templates_1.buildFeedPayloadPromptTemplate)(payload, getPromptContext()));
88
152
  },
89
153
  onAuthRequired: notifyAuthRequired,
90
154
  });
91
- const pmPollingClient = new pm_polling_client_1.EigenFluxPmPollingClient({
92
- apiUrl: server.endpoint,
93
- getAuthState: () => credentialsLoader.loadAuthState(),
94
- pollIntervalSec: server.pmPollIntervalSec,
155
+ const streamClient = new stream_client_1.EigenFluxStreamClient({
156
+ serverName: server.name,
157
+ eigenfluxBin: pluginConfig.eigenfluxBin,
95
158
  logger,
96
- onPmFetched: async (payload) => {
159
+ onPmEvent: async (event) => {
97
160
  resetAuthPromptGate();
98
- await notifier.deliver(buildPmPayloadMessage(getPromptContext(), payload));
161
+ await notifier.deliver((0, agent_prompt_templates_1.buildPmStreamEventPromptTemplate)(event, getPromptContext()));
162
+ },
163
+ onAuthRequired: async () => {
164
+ await notifyAuthRequired({ reason: 'auth_required' });
99
165
  },
100
- onAuthRequired: notifyAuthRequired,
101
166
  });
102
167
  return {
103
168
  server,
169
+ routing,
104
170
  credentialsLoader,
105
171
  notifier,
106
- pollingClient,
107
- pmPollingClient,
172
+ feedPoller,
173
+ streamClient,
108
174
  getPromptContext,
109
175
  };
110
176
  }
111
- function registerServices(api, logger, runtimes) {
112
- for (const runtime of runtimes) {
113
- api.registerService({
114
- id: `eigenflux:${toServiceIdSegment(runtime.server.name)}`,
115
- start: async () => {
116
- logger.info(`Starting EigenFlux polling services for server=${runtime.server.name}`);
117
- await runtime.pollingClient.start();
118
- await runtime.pmPollingClient.start();
119
- },
120
- stop: async () => {
121
- logger.info(`Stopping EigenFlux polling services for server=${runtime.server.name}`);
122
- runtime.pollingClient.stop();
123
- runtime.pmPollingClient.stop();
124
- },
125
- });
126
- }
127
- }
128
- function registerCommand(api, logger, runtimes) {
177
+ // ─── Command Handler ────────────────────────────────────────────────────────
178
+ function registerCommand(api, logger, pluginConfig, eigenfluxHome, getRuntimes, setRuntimes) {
129
179
  if (!api.registerCommand) {
130
180
  logger.warn('registerCommand API unavailable; skipping /eigenflux command registration');
131
181
  return;
132
182
  }
183
+ let inflightDiscovery = null;
184
+ const runDiscovery = async () => {
185
+ const discovery = await (0, config_1.discoverServers)(pluginConfig.eigenfluxBin, logger);
186
+ if (discovery.kind === 'not_installed') {
187
+ return { runtimes: getRuntimes(), notInstalledBin: discovery.bin };
188
+ }
189
+ if (discovery.servers.length === 0) {
190
+ return { runtimes: getRuntimes() };
191
+ }
192
+ const created = discovery.servers.map((server) => createServerRuntime(api, logger, pluginConfig, server, eigenfluxHome));
193
+ setRuntimes(created);
194
+ return { runtimes: created };
195
+ };
196
+ const ensureRuntimes = async () => {
197
+ const existing = getRuntimes();
198
+ if (existing.length > 0) {
199
+ return { runtimes: existing };
200
+ }
201
+ if (!inflightDiscovery) {
202
+ inflightDiscovery = runDiscovery().finally(() => {
203
+ inflightDiscovery = null;
204
+ });
205
+ }
206
+ return inflightDiscovery;
207
+ };
133
208
  api.registerCommand({
134
209
  name: 'eigenflux',
135
- description: 'EigenFlux plugin commands: auth, profile, servers, feed, pm, here',
210
+ description: 'EigenFlux plugin commands: auth, profile, servers, feed, pm, here, version',
136
211
  acceptsArgs: true,
137
212
  handler: async (ctx) => {
138
213
  const parsed = parseCommandArgs(ctx.args);
214
+ if (parsed.command === 'version') {
215
+ return {
216
+ text: await buildVersionText(pluginConfig.eigenfluxBin),
217
+ };
218
+ }
219
+ const { runtimes, notInstalledBin } = await ensureRuntimes();
220
+ if (notInstalledBin && runtimes.length === 0) {
221
+ return {
222
+ text: `EigenFlux CLI not installed (bin=${notInstalledBin}). Install with: ${INSTALL_COMMAND}`,
223
+ };
224
+ }
139
225
  if (parsed.command === 'servers') {
140
226
  return {
141
227
  text: buildServersText(runtimes),
@@ -148,27 +234,27 @@ function registerCommand(api, logger, runtimes) {
148
234
  };
149
235
  }
150
236
  const runtime = selection.runtime;
151
- await rememberCurrentCommandRouteIfPossible(ctx, runtime.server, logger);
237
+ await rememberCurrentCommandRouteIfPossible(ctx, runtime, pluginConfig.eigenfluxBin, logger);
152
238
  switch (parsed.command) {
153
239
  case 'auth':
154
240
  return {
155
- text: buildAuthStatusText(runtime.server, runtime.credentialsLoader.loadAuthState()),
241
+ text: buildAuthStatusText(runtime),
156
242
  };
157
243
  case 'profile':
158
244
  return {
159
- text: await buildProfileText(runtime, runtime.credentialsLoader.loadAuthState()),
245
+ text: await buildProfileText(runtime, pluginConfig.eigenfluxBin),
160
246
  };
161
247
  case 'feed':
162
248
  return {
163
- text: await buildFeedText(runtime, runtime.credentialsLoader.loadAuthState()),
249
+ text: await buildFeedText(runtime),
164
250
  };
165
251
  case 'pm':
166
252
  return {
167
- text: await buildPmPollText(runtime, runtime.credentialsLoader.loadAuthState()),
253
+ text: buildPmStatusText(runtime),
168
254
  };
169
255
  case 'here':
170
256
  return {
171
- text: await buildHereText(ctx, runtime.server, logger),
257
+ text: await buildHereText(ctx, runtime, pluginConfig.eigenfluxBin, logger),
172
258
  };
173
259
  default:
174
260
  return {
@@ -200,7 +286,7 @@ function parseCommandArgs(args) {
200
286
  function selectServerRuntime(runtimes, requestedServerName) {
201
287
  if (runtimes.length === 0) {
202
288
  return {
203
- error: 'No EigenFlux servers are configured.',
289
+ error: 'No EigenFlux servers discovered. Ensure eigenflux CLI is configured with at least one server.',
204
290
  };
205
291
  }
206
292
  if (!requestedServerName) {
@@ -222,19 +308,19 @@ function selectServerRuntime(runtimes, requestedServerName) {
222
308
  }
223
309
  function buildServersText(runtimes) {
224
310
  if (runtimes.length === 0) {
225
- return 'No EigenFlux servers are configured.';
311
+ return 'No EigenFlux servers discovered.';
226
312
  }
227
- const defaultRuntime = runtimes[0];
228
313
  return [
229
- 'EigenFlux servers:',
314
+ 'EigenFlux servers (discovered via CLI):',
230
315
  ...runtimes.map((runtime) => {
231
316
  const flags = [
232
- runtime.server.enabled ? 'enabled' : 'disabled',
233
- defaultRuntime?.server.name === runtime.server.name ? 'default' : null,
317
+ runtime.server.current ? 'default' : null,
318
+ runtime.streamClient.isRunning() ? 'streaming' : null,
234
319
  ]
235
320
  .filter(Boolean)
236
321
  .join(', ');
237
- return `- ${runtime.server.name}: ${flags}; endpoint=${runtime.server.endpoint}; workdir=${runtime.server.workdir}`;
322
+ const suffix = flags ? ` (${flags})` : '';
323
+ return `- ${runtime.server.name}: endpoint=${runtime.server.endpoint}${suffix}`;
238
324
  }),
239
325
  ].join('\n');
240
326
  }
@@ -248,23 +334,13 @@ function buildHelpText(runtimes) {
248
334
  ? `Available servers: ${runtimes.map((runtime) => runtime.server.name).join(', ')}`
249
335
  : undefined,
250
336
  '',
251
- '/eigenflux auth',
252
- 'Show current EigenFlux credential status.',
253
- '',
254
- '/eigenflux profile',
255
- 'Fetch /api/v1/agents/me with the current access token.',
256
- '',
257
- '/eigenflux servers',
258
- 'List configured EigenFlux servers.',
259
- '',
260
- '/eigenflux feed',
261
- 'Run one feed refresh and return the raw feed payload.',
262
- '',
263
- '/eigenflux pm',
264
- 'Run one PM fetch and return the raw PM payload.',
265
- '',
266
- '/eigenflux here',
267
- 'Remember the current conversation as the default delivery route for the selected server.',
337
+ '/eigenflux auth — Show credential status',
338
+ '/eigenflux profile Fetch agent profile',
339
+ '/eigenflux servers — List discovered servers',
340
+ '/eigenflux feed — Run one feed refresh',
341
+ '/eigenflux pm Show PM stream status',
342
+ '/eigenflux here — Remember current conversation as delivery route',
343
+ '/eigenflux version — Show eigenflux CLI version info',
268
344
  ]
269
345
  .filter(Boolean)
270
346
  .join('\n');
@@ -287,98 +363,51 @@ function isInternalAgentSessionKey(value) {
287
363
  const parts = trimmed.split(':').filter((part) => part.length > 0);
288
364
  return parts[0]?.toLowerCase() === 'agent' && parts[2]?.toLowerCase() === 'main';
289
365
  }
290
- function isNormalizedConversationTarget(value) {
291
- return /^(user|chat|channel|room):/u.test(value);
292
- }
293
- function normalizeReplyTarget(value, channel, fallbackKind) {
294
- const trimmed = readNonEmptyString(value);
295
- if (!trimmed) {
296
- return undefined;
297
- }
298
- if (isNormalizedConversationTarget(trimmed)) {
299
- return trimmed;
300
- }
301
- if (channel && trimmed.startsWith(`${channel}:`)) {
302
- const inner = trimmed.slice(channel.length + 1).trim();
303
- if (isNormalizedConversationTarget(inner)) {
304
- return inner;
305
- }
306
- return fallbackKind ? `${fallbackKind}:${inner}` : inner;
307
- }
308
- return fallbackKind ? `${fallbackKind}:${trimmed}` : trimmed;
309
- }
310
- async function resolveCurrentCommandRoute(ctx, serverConfig, logger) {
311
- const channel = normalizeChannel(ctx.channel);
312
- const accountId = readNonEmptyString(ctx.accountId);
313
- let replyChannel = channel;
314
- let replyTo = normalizeReplyTarget(ctx.to, channel) ?? normalizeReplyTarget(ctx.from, channel, 'user');
315
- let replyAccountId = accountId;
366
+ async function resolveCurrentCommandRoute(ctx, runtime, logger) {
367
+ let channel = normalizeChannel(ctx.channel);
368
+ let to = (0, reply_target_1.normalizeReplyTarget)(ctx.to, { channel }) ??
369
+ (0, reply_target_1.normalizeReplyTarget)(ctx.from, { channel, fallbackKind: 'user' });
370
+ let accountId = readNonEmptyString(ctx.accountId);
316
371
  if (typeof ctx.getCurrentConversationBinding === 'function') {
317
372
  try {
318
373
  const binding = await ctx.getCurrentConversationBinding();
319
374
  if (binding) {
320
- replyChannel = normalizeChannel(binding.channel) ?? replyChannel;
321
- replyTo =
322
- normalizeReplyTarget(binding.conversationId, replyChannel) ??
323
- normalizeReplyTarget(binding.parentConversationId, replyChannel) ??
324
- replyTo;
325
- replyAccountId = readNonEmptyString(binding.accountId) ?? replyAccountId;
375
+ channel = normalizeChannel(binding.channel) ?? channel;
376
+ to =
377
+ (0, reply_target_1.normalizeReplyTarget)(binding.conversationId, { channel }) ??
378
+ (0, reply_target_1.normalizeReplyTarget)(binding.parentConversationId, { channel }) ??
379
+ to;
380
+ accountId = readNonEmptyString(binding.accountId) ?? accountId;
326
381
  }
327
382
  }
328
383
  catch (error) {
329
384
  logger.debug(`Failed to read current conversation binding: ${error instanceof Error ? error.message : String(error)}`);
330
385
  }
331
386
  }
332
- if (!replyChannel || !replyTo) {
387
+ if (!channel || !to) {
333
388
  return undefined;
334
389
  }
335
- const route = (0, notification_route_resolver_1.resolveNotificationRoute)({
336
- sessionKey: 'main',
337
- agentId: serverConfig.agentId,
338
- replyChannel,
339
- replyTo,
340
- replyAccountId,
341
- sessionStorePath: readServerSessionStorePath(serverConfig),
342
- workdir: serverConfig.workdir,
343
- routeOverrides: {
344
- sessionKey: false,
345
- agentId: false,
346
- replyChannel: true,
347
- replyTo: true,
348
- replyAccountId: replyAccountId !== undefined,
349
- },
390
+ return (0, notification_route_resolver_1.findSessionRouteForBinding)({
391
+ agentId: runtime.routing.agentId,
392
+ channel,
393
+ to,
394
+ accountId,
350
395
  }, logger);
351
- if (!route.replyChannel || !route.replyTo) {
352
- return undefined;
353
- }
354
- if (isInternalAgentSessionKey(route.sessionKey)) {
355
- const configuredSessionKey = readNonEmptyString(serverConfig.sessionKey);
356
- if (configuredSessionKey && !isInternalAgentSessionKey(configuredSessionKey)) {
357
- return {
358
- sessionKey: configuredSessionKey,
359
- agentId: readNonEmptyString(serverConfig.agentId) ?? route.agentId,
360
- replyChannel: route.replyChannel,
361
- replyTo: route.replyTo,
362
- replyAccountId: route.replyAccountId,
363
- };
364
- }
365
- }
366
- return route;
367
396
  }
368
- async function buildHereText(ctx, serverConfig, logger) {
369
- const route = await resolveCurrentCommandRoute(ctx, serverConfig, logger);
397
+ async function buildHereText(ctx, runtime, eigenfluxBin, logger) {
398
+ const route = await resolveCurrentCommandRoute(ctx, runtime, logger);
370
399
  if (!route || route.sessionKey === 'main' || route.sessionKey.endsWith(':main')) {
371
400
  return [
372
- `Unable to resolve the current external session for server=${serverConfig.name}.`,
401
+ `Unable to resolve the current external session for server=${runtime.server.name}.`,
373
402
  'Run `/eigenflux here` inside the target conversation after OpenClaw has already created a session for it.',
374
403
  ].join('\n');
375
404
  }
376
- const saved = (0, session_route_memory_1.writeStoredNotificationRoute)(serverConfig.workdir, route, logger);
405
+ const saved = await (0, session_route_memory_1.writeStoredNotificationRoute)(eigenfluxBin, runtime.server.name, route, logger);
377
406
  if (!saved) {
378
- return `Failed to persist the current EigenFlux route for server=${serverConfig.name}; check plugin logs for details.`;
407
+ return `Failed to persist the current EigenFlux route for server=${runtime.server.name}; check plugin logs for details.`;
379
408
  }
380
409
  return [
381
- `EigenFlux server ${serverConfig.name} will deliver to this conversation by default:`,
410
+ `EigenFlux server ${runtime.server.name} will deliver to this conversation by default:`,
382
411
  `sessionKey: ${route.sessionKey}`,
383
412
  `agentId: ${route.agentId}`,
384
413
  `channel: ${route.replyChannel ?? 'unknown'}`,
@@ -388,42 +417,52 @@ async function buildHereText(ctx, serverConfig, logger) {
388
417
  .filter(Boolean)
389
418
  .join('\n');
390
419
  }
391
- async function rememberCurrentCommandRouteIfPossible(ctx, serverConfig, logger) {
392
- const route = await resolveCurrentCommandRoute(ctx, serverConfig, logger);
420
+ async function rememberCurrentCommandRouteIfPossible(ctx, runtime, eigenfluxBin, logger) {
421
+ const route = await resolveCurrentCommandRoute(ctx, runtime, logger);
393
422
  if (!route || route.sessionKey === 'main' || route.sessionKey.endsWith(':main')) {
394
423
  return;
395
424
  }
396
- if ((0, session_route_memory_1.writeStoredNotificationRoute)(serverConfig.workdir, route, logger)) {
397
- logger.debug(`Remembered current command route for server=${serverConfig.name}: session_key=${route.sessionKey}, channel=${route.replyChannel ?? 'unknown'}, to=${route.replyTo ?? 'unknown'}`);
425
+ if (await (0, session_route_memory_1.writeStoredNotificationRoute)(eigenfluxBin, runtime.server.name, route, logger)) {
426
+ logger.debug(`Remembered current command route for server=${runtime.server.name}: session_key=${route.sessionKey}, channel=${route.replyChannel ?? 'unknown'}, to=${route.replyTo ?? 'unknown'}`);
398
427
  }
399
428
  }
400
- async function buildProfileText(runtime, authState) {
401
- if (authState.status !== 'available') {
402
- return buildAuthRequiredMessage(runtime.getPromptContext(), {
403
- authEvent: {
404
- reason: authState.status === 'expired' ? 'expired_token' : 'missing_token',
405
- credentialsPath: authState.credentialsPath,
406
- source: authState.source,
407
- expiresAt: authState.expiresAt,
408
- },
409
- authState,
410
- });
429
+ // ─── Command Handlers ───────────────────────────────────────────────────────
430
+ function buildAuthStatusText(runtime) {
431
+ const authState = runtime.credentialsLoader.loadAuthState();
432
+ const lines = [`EigenFlux auth status (server=${runtime.server.name}):`];
433
+ lines.push(`- credentials_path: ${authState.credentialsPath}`);
434
+ lines.push(`- status: ${authState.status}`);
435
+ if (authState.expiresAt) {
436
+ lines.push(`- expires_at: ${authState.expiresAt}`);
411
437
  }
412
- try {
413
- const payload = await fetchJson(`${runtime.server.endpoint}/api/v1/agents/me`, authState.accessToken);
414
- return [
415
- `EigenFlux profile (server=${runtime.server.name}):`,
416
- '```json',
417
- safeJsonStringify(payload),
418
- '```',
419
- ].join('\n');
438
+ if (authState.status === 'available') {
439
+ lines.push(`- token: ${maskToken(authState.accessToken)}`);
440
+ }
441
+ else {
442
+ lines.push('- token: unavailable');
420
443
  }
421
- catch (error) {
422
- return `Failed to fetch profile for server ${runtime.server.name}: ${error instanceof Error ? error.message : String(error)}`;
444
+ return lines.join('\n');
445
+ }
446
+ async function buildProfileText(runtime, eigenfluxBin) {
447
+ const result = await (0, cli_executor_1.execEigenflux)(eigenfluxBin, ['profile', 'show', '-s', runtime.server.name, '-f', 'json']);
448
+ if (result.kind === 'auth_required') {
449
+ return (0, agent_prompt_templates_1.buildAuthRequiredPromptTemplate)({ context: runtime.getPromptContext() });
423
450
  }
451
+ if (result.kind === 'not_installed') {
452
+ return `EigenFlux CLI not installed (bin=${result.bin}). Install with: ${INSTALL_COMMAND}`;
453
+ }
454
+ if (result.kind === 'error') {
455
+ return `Failed to fetch profile for server ${runtime.server.name}: ${result.error.message}`;
456
+ }
457
+ return [
458
+ `EigenFlux profile (server=${runtime.server.name}):`,
459
+ '```json',
460
+ safeJsonStringify(result.data),
461
+ '```',
462
+ ].join('\n');
424
463
  }
425
- async function buildFeedText(runtime, authState) {
426
- const result = await runtime.pollingClient.pollOnce({
464
+ async function buildFeedText(runtime) {
465
+ const result = await runtime.feedPoller.pollOnce({
427
466
  notifyFeed: false,
428
467
  notifyAuthRequired: false,
429
468
  });
@@ -436,89 +475,41 @@ async function buildFeedText(runtime, authState) {
436
475
  '```',
437
476
  ].join('\n');
438
477
  case 'auth_required':
439
- return buildAuthRequiredMessage(runtime.getPromptContext(), {
440
- authEvent: result.authEvent,
441
- authState,
442
- });
478
+ return (0, agent_prompt_templates_1.buildAuthRequiredPromptTemplate)({ context: runtime.getPromptContext() });
443
479
  case 'error':
444
480
  return `EigenFlux feed failed for server ${runtime.server.name}: ${result.error.message}`;
445
481
  default:
446
482
  return `EigenFlux feed finished with an unknown result for server ${runtime.server.name}.`;
447
483
  }
448
484
  }
449
- async function buildPmPollText(runtime, authState) {
450
- const result = await runtime.pmPollingClient.pollOnce({
451
- notifyFeed: false,
452
- notifyAuthRequired: false,
453
- });
454
- switch (result.kind) {
455
- case 'success':
456
- return [
457
- `EigenFlux PM poll result (server=${runtime.server.name}):`,
458
- '```json',
459
- safeJsonStringify(result.payload),
460
- '```',
461
- ].join('\n');
462
- case 'auth_required':
463
- return buildAuthRequiredMessage(runtime.getPromptContext(), {
464
- authEvent: result.authEvent,
465
- authState,
466
- });
467
- case 'error':
468
- return `EigenFlux PM poll failed for server ${runtime.server.name}: ${result.error.message}`;
469
- default:
470
- return `EigenFlux PM poll finished with an unknown result for server ${runtime.server.name}.`;
485
+ async function buildVersionText(eigenfluxBin) {
486
+ const result = await (0, cli_executor_1.execEigenflux)(eigenfluxBin, ['version']);
487
+ if (result.kind === 'not_installed') {
488
+ return `EigenFlux CLI not installed (bin=${result.bin}). Install with: ${INSTALL_COMMAND}`;
471
489
  }
472
- }
473
- async function fetchJson(url, accessToken) {
474
- const response = await fetch(url, {
475
- method: 'GET',
476
- headers: (0, config_1.buildEigenFluxRequestHeaders)(accessToken),
477
- });
478
- if (response.status === 401) {
479
- throw new Error('HTTP 401: unauthorized');
490
+ if (result.kind === 'auth_required') {
491
+ return `EigenFlux CLI reported auth_required while fetching version (stderr: ${result.stderr || 'n/a'}).`;
480
492
  }
481
- if (!response.ok) {
482
- throw new Error(`HTTP ${response.status}: ${response.statusText}`);
493
+ if (result.kind === 'error') {
494
+ return `Failed to fetch eigenflux version: ${result.error.message}`;
483
495
  }
484
- const payload = (await response.json());
485
- if (payload.code !== 0) {
486
- throw new Error(`API error: ${payload.msg}`);
487
- }
488
- return payload;
496
+ const body = typeof result.data === 'string' ? result.data : safeJsonStringify(result.data);
497
+ return ['EigenFlux CLI version:', '```json', body, '```'].join('\n');
489
498
  }
490
- function buildAuthStatusText(serverConfig, authState) {
491
- const lines = [`EigenFlux auth status (server=${serverConfig.name}):`];
492
- lines.push(`- workdir: ${serverConfig.workdir}`);
493
- lines.push(`- credentials_path: ${authState.credentialsPath}`);
494
- lines.push(`- status: ${authState.status}`);
495
- if (authState.source) {
496
- lines.push(`- source: ${authState.source}`);
499
+ function buildPmStatusText(runtime) {
500
+ const running = runtime.streamClient.isRunning();
501
+ const cursor = runtime.streamClient.getLastCursor();
502
+ const lines = [`EigenFlux PM stream status (server=${runtime.server.name}):`];
503
+ lines.push(`- streaming: ${running ? 'active' : 'inactive'}`);
504
+ if (cursor) {
505
+ lines.push(`- last_cursor: ${cursor}`);
497
506
  }
498
- if (authState.expiresAt) {
499
- lines.push(`- expires_at: ${authState.expiresAt}`);
500
- }
501
- if (authState.status === 'available') {
502
- lines.push(`- token: ${maskToken(authState.accessToken)}`);
503
- }
504
- else {
505
- lines.push('- token: unavailable');
507
+ if (!running) {
508
+ lines.push('PM stream is not running. Check auth status or restart the service.');
506
509
  }
507
510
  return lines.join('\n');
508
511
  }
509
- function buildAuthRequiredMessage(promptContext, { authEvent, authState }) {
510
- return (0, agent_prompt_templates_1.buildAuthRequiredPromptTemplate)({
511
- ...promptContext,
512
- authEvent,
513
- maskedToken: authState?.status === 'available' ? maskToken(authState.accessToken) : undefined,
514
- });
515
- }
516
- function buildFeedPayloadMessage(promptContext, payload) {
517
- return (0, agent_prompt_templates_1.buildFeedPayloadPromptTemplate)(payload, promptContext);
518
- }
519
- function buildPmPayloadMessage(promptContext, payload) {
520
- return (0, agent_prompt_templates_1.buildPmPayloadPromptTemplate)(payload, promptContext);
521
- }
512
+ // ─── Utilities ──────────────────────────────────────────────────────────────
522
513
  function maskToken(token) {
523
514
  const trimmed = token.trim();
524
515
  if (trimmed.length <= 10) {
@@ -534,8 +525,4 @@ function safeJsonStringify(value) {
534
525
  return String(value);
535
526
  }
536
527
  }
537
- function toServiceIdSegment(name) {
538
- const sanitized = name.trim().toLowerCase().replace(/[^a-z0-9._-]+/gu, '-');
539
- return sanitized || 'default';
540
- }
541
528
  //# sourceMappingURL=index.js.map