@phronesis-io/openclaw-eigenflux 0.0.3 → 0.0.6

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